diff options
Diffstat (limited to 'drivers/md/dm-flakey.c')
| -rw-r--r-- | drivers/md/dm-flakey.c | 210 | 
1 files changed, 188 insertions, 22 deletions
diff --git a/drivers/md/dm-flakey.c b/drivers/md/dm-flakey.c index bd80bcafbe50..120153e44ae0 100644 --- a/drivers/md/dm-flakey.c +++ b/drivers/md/dm-flakey.c @@ -16,6 +16,8 @@  #define DM_MSG_PREFIX "flakey" +#define PROBABILITY_BASE	1000000000 +  #define all_corrupt_bio_flags_match(bio, fc)	\  	(((bio)->bi_opf & (fc)->corrupt_bio_flags) == (fc)->corrupt_bio_flags) @@ -34,6 +36,8 @@ struct flakey_c {  	unsigned int corrupt_bio_rw;  	unsigned int corrupt_bio_value;  	blk_opf_t corrupt_bio_flags; +	unsigned int random_read_corrupt; +	unsigned int random_write_corrupt;  };  enum feature_flag_bits { @@ -54,10 +58,11 @@ static int parse_features(struct dm_arg_set *as, struct flakey_c *fc,  	const char *arg_name;  	static const struct dm_arg _args[] = { -		{0, 7, "Invalid number of feature args"}, +		{0, 11, "Invalid number of feature args"},  		{1, UINT_MAX, "Invalid corrupt bio byte"},  		{0, 255, "Invalid corrupt value to write into bio byte (0-255)"},  		{0, UINT_MAX, "Invalid corrupt bio flags mask"}, +		{0, PROBABILITY_BASE, "Invalid random corrupt argument"},  	};  	/* No feature arguments supplied. */ @@ -170,6 +175,32 @@ static int parse_features(struct dm_arg_set *as, struct flakey_c *fc,  			continue;  		} +		if (!strcasecmp(arg_name, "random_read_corrupt")) { +			if (!argc) { +				ti->error = "Feature random_read_corrupt requires a parameter"; +				return -EINVAL; +			} +			r = dm_read_arg(_args + 4, as, &fc->random_read_corrupt, &ti->error); +			if (r) +				return r; +			argc--; + +			continue; +		} + +		if (!strcasecmp(arg_name, "random_write_corrupt")) { +			if (!argc) { +				ti->error = "Feature random_write_corrupt requires a parameter"; +				return -EINVAL; +			} +			r = dm_read_arg(_args + 4, as, &fc->random_write_corrupt, &ti->error); +			if (r) +				return r; +			argc--; + +			continue; +		} +  		ti->error = "Unrecognised flakey feature requested";  		return -EINVAL;  	} @@ -184,7 +215,8 @@ static int parse_features(struct dm_arg_set *as, struct flakey_c *fc,  	}  	if (!fc->corrupt_bio_byte && !test_bit(ERROR_READS, &fc->flags) && -	    !test_bit(DROP_WRITES, &fc->flags) && !test_bit(ERROR_WRITES, &fc->flags)) { +	    !test_bit(DROP_WRITES, &fc->flags) && !test_bit(ERROR_WRITES, &fc->flags) && +	    !fc->random_read_corrupt && !fc->random_write_corrupt) {  		set_bit(ERROR_WRITES, &fc->flags);  		set_bit(ERROR_READS, &fc->flags);  	} @@ -306,40 +338,143 @@ static void flakey_map_bio(struct dm_target *ti, struct bio *bio)  	bio->bi_iter.bi_sector = flakey_map_sector(ti, bio->bi_iter.bi_sector);  } -static void corrupt_bio_data(struct bio *bio, struct flakey_c *fc) +static void corrupt_bio_common(struct bio *bio, unsigned int corrupt_bio_byte, +			       unsigned char corrupt_bio_value)  { -	unsigned int corrupt_bio_byte = fc->corrupt_bio_byte - 1; -  	struct bvec_iter iter;  	struct bio_vec bvec; -	if (!bio_has_data(bio)) -		return; -  	/*  	 * Overwrite the Nth byte of the bio's data, on whichever page  	 * it falls.  	 */  	bio_for_each_segment(bvec, bio, iter) {  		if (bio_iter_len(bio, iter) > corrupt_bio_byte) { -			char *segment; -			struct page *page = bio_iter_page(bio, iter); -			if (unlikely(page == ZERO_PAGE(0))) -				break; -			segment = bvec_kmap_local(&bvec); -			segment[corrupt_bio_byte] = fc->corrupt_bio_value; +			unsigned char *segment = bvec_kmap_local(&bvec); +			segment[corrupt_bio_byte] = corrupt_bio_value;  			kunmap_local(segment);  			DMDEBUG("Corrupting data bio=%p by writing %u to byte %u "  				"(rw=%c bi_opf=%u bi_sector=%llu size=%u)\n", -				bio, fc->corrupt_bio_value, fc->corrupt_bio_byte, +				bio, corrupt_bio_value, corrupt_bio_byte,  				(bio_data_dir(bio) == WRITE) ? 'w' : 'r', bio->bi_opf, -				(unsigned long long)bio->bi_iter.bi_sector, bio->bi_iter.bi_size); +				(unsigned long long)bio->bi_iter.bi_sector, +				bio->bi_iter.bi_size);  			break;  		}  		corrupt_bio_byte -= bio_iter_len(bio, iter);  	}  } +static void corrupt_bio_data(struct bio *bio, struct flakey_c *fc) +{ +	unsigned int corrupt_bio_byte = fc->corrupt_bio_byte - 1; + +	if (!bio_has_data(bio)) +		return; + +	corrupt_bio_common(bio, corrupt_bio_byte, fc->corrupt_bio_value); +} + +static void corrupt_bio_random(struct bio *bio) +{ +	unsigned int corrupt_byte; +	unsigned char corrupt_value; + +	if (!bio_has_data(bio)) +		return; + +	corrupt_byte = get_random_u32() % bio->bi_iter.bi_size; +	corrupt_value = get_random_u8(); + +	corrupt_bio_common(bio, corrupt_byte, corrupt_value); +} + +static void clone_free(struct bio *clone) +{ +	struct folio_iter fi; + +	if (clone->bi_vcnt > 0) { /* bio_for_each_folio_all crashes with an empty bio */ +		bio_for_each_folio_all(fi, clone) +			folio_put(fi.folio); +	} + +	bio_uninit(clone); +	kfree(clone); +} + +static void clone_endio(struct bio *clone) +{ +	struct bio *bio = clone->bi_private; +	bio->bi_status = clone->bi_status; +	clone_free(clone); +	bio_endio(bio); +} + +static struct bio *clone_bio(struct dm_target *ti, struct flakey_c *fc, struct bio *bio) +{ +	struct bio *clone; +	unsigned size, remaining_size, nr_iovecs, order; +	struct bvec_iter iter = bio->bi_iter; + +	if (unlikely(bio->bi_iter.bi_size > UIO_MAXIOV << PAGE_SHIFT)) +		dm_accept_partial_bio(bio, UIO_MAXIOV << PAGE_SHIFT >> SECTOR_SHIFT); + +	size = bio->bi_iter.bi_size; +	nr_iovecs = (size + PAGE_SIZE - 1) >> PAGE_SHIFT; + +	clone = bio_kmalloc(nr_iovecs, GFP_NOIO | __GFP_NORETRY | __GFP_NOWARN); +	if (!clone) +		return NULL; + +	bio_init(clone, fc->dev->bdev, bio->bi_inline_vecs, nr_iovecs, bio->bi_opf); + +	clone->bi_iter.bi_sector = flakey_map_sector(ti, bio->bi_iter.bi_sector); +	clone->bi_private = bio; +	clone->bi_end_io = clone_endio; + +	remaining_size = size; + +	order = MAX_ORDER - 1; +	while (remaining_size) { +		struct page *pages; +		unsigned size_to_add, to_copy; +		unsigned char *virt; +		unsigned remaining_order = __fls((remaining_size + PAGE_SIZE - 1) >> PAGE_SHIFT); +		order = min(order, remaining_order); + +retry_alloc_pages: +		pages = alloc_pages(GFP_NOIO | __GFP_NORETRY | __GFP_NOWARN | __GFP_COMP, order); +		if (unlikely(!pages)) { +			if (order) { +				order--; +				goto retry_alloc_pages; +			} +			clone_free(clone); +			return NULL; +		} +		size_to_add = min((unsigned)PAGE_SIZE << order, remaining_size); + +		virt = page_to_virt(pages); +		to_copy = size_to_add; +		do { +			struct bio_vec bvec = bvec_iter_bvec(bio->bi_io_vec, iter); +			unsigned this_step = min(bvec.bv_len, to_copy); +			void *map = bvec_kmap_local(&bvec); +			memcpy(virt, map, this_step); +			kunmap_local(map); + +			bvec_iter_advance(bio->bi_io_vec, &iter, this_step); +			to_copy -= this_step; +			virt += this_step; +		} while (to_copy); + +		__bio_add_page(clone, pages, size_to_add, 0); +		remaining_size -= size_to_add; +	} + +	return clone; +} +  static int flakey_map(struct dm_target *ti, struct bio *bio)  {  	struct flakey_c *fc = ti->private; @@ -354,6 +489,7 @@ static int flakey_map(struct dm_target *ti, struct bio *bio)  	/* Are we alive ? */  	elapsed = (jiffies - fc->start_time) / HZ;  	if (elapsed % (fc->up_interval + fc->down_interval) >= fc->up_interval) { +		bool corrupt_fixed, corrupt_random;  		/*  		 * Flag this bio as submitted while down.  		 */ @@ -383,12 +519,28 @@ static int flakey_map(struct dm_target *ti, struct bio *bio)  		/*  		 * Corrupt matching writes.  		 */ -		if (fc->corrupt_bio_byte) { -			if (fc->corrupt_bio_rw == WRITE) { -				if (all_corrupt_bio_flags_match(bio, fc)) -					corrupt_bio_data(bio, fc); +		corrupt_fixed = false; +		corrupt_random = false; +		if (fc->corrupt_bio_byte && fc->corrupt_bio_rw == WRITE) { +			if (all_corrupt_bio_flags_match(bio, fc)) +				corrupt_fixed = true; +		} +		if (fc->random_write_corrupt) { +			u64 rnd = get_random_u64(); +			u32 rem = do_div(rnd, PROBABILITY_BASE); +			if (rem < fc->random_write_corrupt) +				corrupt_random = true; +		} +		if (corrupt_fixed || corrupt_random) { +			struct bio *clone = clone_bio(ti, fc, bio); +			if (clone) { +				if (corrupt_fixed) +					corrupt_bio_data(clone, fc); +				if (corrupt_random) +					corrupt_bio_random(clone); +				submit_bio(clone); +				return DM_MAPIO_SUBMITTED;  			} -			goto map_bio;  		}  	} @@ -417,6 +569,12 @@ static int flakey_end_io(struct dm_target *ti, struct bio *bio,  				corrupt_bio_data(bio, fc);  			}  		} +		if (fc->random_read_corrupt) { +			u64 rnd = get_random_u64(); +			u32 rem = do_div(rnd, PROBABILITY_BASE); +			if (rem < fc->random_read_corrupt) +				corrupt_bio_random(bio); +		}  		if (test_bit(ERROR_READS, &fc->flags)) {  			/*  			 * Error read during the down_interval if drop_writes @@ -449,7 +607,10 @@ static void flakey_status(struct dm_target *ti, status_type_t type,  		error_reads = test_bit(ERROR_READS, &fc->flags);  		drop_writes = test_bit(DROP_WRITES, &fc->flags);  		error_writes = test_bit(ERROR_WRITES, &fc->flags); -		DMEMIT(" %u", error_reads + drop_writes + error_writes + (fc->corrupt_bio_byte > 0) * 5); +		DMEMIT(" %u", error_reads + drop_writes + error_writes + +			(fc->corrupt_bio_byte > 0) * 5 + +			(fc->random_read_corrupt > 0) * 2 + +			(fc->random_write_corrupt > 0) * 2);  		if (error_reads)  			DMEMIT(" error_reads"); @@ -464,6 +625,11 @@ static void flakey_status(struct dm_target *ti, status_type_t type,  			       (fc->corrupt_bio_rw == WRITE) ? 'w' : 'r',  			       fc->corrupt_bio_value, fc->corrupt_bio_flags); +		if (fc->random_read_corrupt > 0) +			DMEMIT(" random_read_corrupt %u", fc->random_read_corrupt); +		if (fc->random_write_corrupt > 0) +			DMEMIT(" random_write_corrupt %u", fc->random_write_corrupt); +  		break;  	case STATUSTYPE_IMA:  | 
