Home | History | Annotate | Line # | Download | only in dm
      1 /*        $NetBSD: dm_target_flakey.c,v 1.4 2021/07/24 21:31:37 andvar Exp $      */
      2 
      3 /*
      4  * Copyright (c) 2020 The NetBSD Foundation, Inc.
      5  * Copyright (c) 2015 The DragonFly Project.  All rights reserved.
      6  * All rights reserved.
      7  *
      8  * This code is derived from software contributed to The NetBSD Foundation
      9  * by Tomohiro Kusumi <tkusumi (at) netbsd.org>.
     10  *
     11  * Redistribution and use in source and binary forms, with or without
     12  * modification, are permitted provided that the following conditions
     13  * are met:
     14  * 1. Redistributions of source code must retain the above copyright
     15  *    notice, this list of conditions and the following disclaimer.
     16  * 2. Redistributions in binary form must reproduce the above copyright
     17  *    notice, this list of conditions and the following disclaimer in the
     18  *    documentation and/or other materials provided with the distribution.
     19  *
     20  * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
     21  * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
     22  * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
     23  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
     24  * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
     25  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
     26  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
     27  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
     28  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
     29  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
     30  * POSSIBILITY OF SUCH DAMAGE.
     31  */
     32 #include <sys/cdefs.h>
     33 __KERNEL_RCSID(0, "$NetBSD: dm_target_flakey.c,v 1.4 2021/07/24 21:31:37 andvar Exp $");
     34 
     35 #include <sys/types.h>
     36 #include <sys/param.h>
     37 #include <sys/kernel.h>
     38 #include <sys/buf.h>
     39 #include <sys/kmem.h>
     40 
     41 #include "dm.h"
     42 
     43 //#define DEBUG_FLAKEY
     44 //#define HAS_BUF_PRIV2 /* XXX requires nonexistent buf::b_private2. */
     45 
     46 typedef struct target_flakey_config {
     47 	dm_pdev_t *pdev;
     48 	uint64_t offset;
     49 	int up_int;
     50 	int down_int;
     51 	int offset_time; /* XXX "tick" in hz(9) not working. */
     52 
     53 	/* drop_writes feature */
     54 	int drop_writes;
     55 
     56 	/* corrupt_bio_byte feature */
     57 	unsigned int corrupt_buf_byte;
     58 	unsigned int corrupt_buf_rw;
     59 	unsigned int corrupt_buf_value;
     60 	unsigned int corrupt_buf_flags; /* for B_XXX flags */
     61 } dm_target_flakey_config_t;
     62 
     63 #define BUF_CMD_READ	1
     64 #define BUF_CMD_WRITE	2
     65 
     66 #define FLAKEY_CORRUPT_DIR(tfc) \
     67 	((tfc)->corrupt_buf_rw == BUF_CMD_READ ? 'r' : 'w')
     68 
     69 static int _init_features(dm_target_flakey_config_t*, int, char**);
     70 static __inline void _submit(dm_target_flakey_config_t*, struct buf*);
     71 static int _flakey_read(dm_target_flakey_config_t*, struct buf*);
     72 static int _flakey_write(dm_target_flakey_config_t*, struct buf*);
     73 static int _flakey_corrupt_buf(dm_target_flakey_config_t*, struct buf*);
     74 
     75 #ifdef DM_TARGET_MODULE
     76 /*
     77  * Every target can be compiled directly to dm driver or as a
     78  * separate module this part of target is used for loading targets
     79  * to dm driver.
     80  * Target can be unloaded from kernel only if there are no users of
     81  * it e.g. there are no devices which uses that target.
     82  */
     83 #include <sys/kernel.h>
     84 #include <sys/module.h>
     85 
     86 MODULE(MODULE_CLASS_MISC, dm_target_flakey, NULL);
     87 
     88 static int
     89 dm_target_flakey_modcmd(modcmd_t cmd, void *arg)
     90 {
     91 	dm_target_t *dmt;
     92 	int r;
     93 
     94 	switch (cmd) {
     95 	case MODULE_CMD_INIT:
     96 		if ((dmt = dm_target_lookup("flakey")) != NULL) {
     97 			dm_target_unbusy(dmt);
     98 			return EEXIST;
     99 		}
    100 		dmt = dm_target_alloc("flakey");
    101 
    102 		dmt->version[0] = 1;
    103 		dmt->version[1] = 0;
    104 		dmt->version[2] = 0;
    105 		dmt->init = &dm_target_flakey_init;
    106 		dmt->table = &dm_target_flakey_table;
    107 		dmt->strategy = &dm_target_flakey_strategy;
    108 		dmt->sync = &dm_target_flakey_sync;
    109 		dmt->destroy = &dm_target_flakey_destroy;
    110 		//dmt->upcall = &dm_target_flakey_upcall;
    111 		dmt->secsize = &dm_target_flakey_secsize;
    112 
    113 		r = dm_target_insert(dmt);
    114 
    115 		break;
    116 
    117 	case MODULE_CMD_FINI:
    118 		r = dm_target_rem("flakey");
    119 		break;
    120 
    121 	case MODULE_CMD_STAT:
    122 		return ENOTTY;
    123 
    124 	default:
    125 		return ENOTTY;
    126 	}
    127 
    128 	return r;
    129 }
    130 #endif
    131 
    132 int
    133 dm_target_flakey_init(dm_table_entry_t *table_en, int argc, char **argv)
    134 {
    135 	dm_target_flakey_config_t *tfc;
    136 	dm_pdev_t *dmp;
    137 	int err;
    138 
    139 	if (argc < 4) {
    140 		printf("Flakey target takes at least 4 args, %d given\n", argc);
    141 		return EINVAL;
    142 	}
    143 
    144 	aprint_debug("Flakey target init function called: argc=%d\n", argc);
    145 
    146 	/* Insert dmp to global pdev list */
    147 	if ((dmp = dm_pdev_insert(argv[0])) == NULL)
    148 		return ENOENT;
    149 
    150 	tfc = kmem_alloc(sizeof(dm_target_flakey_config_t), KM_SLEEP);
    151 	tfc->pdev = dmp;
    152 	tfc->offset = atoi64(argv[1]);
    153 	tfc->up_int = atoi64(argv[2]);
    154 	tfc->down_int = atoi64(argv[3]);
    155 	tfc->offset_time = tick;
    156 
    157 	if ((tfc->up_int + tfc->down_int) == 0) {
    158 		printf("Sum of up/down interval is 0\n");
    159 		err = EINVAL;
    160 		goto fail;
    161 	}
    162 
    163 	if (tfc->up_int + tfc->down_int < tfc->up_int) {
    164 		printf("Interval time overflow\n");
    165 		err = EINVAL;
    166 		goto fail;
    167 	}
    168 
    169 	err = _init_features(tfc, argc - 4, argv + 4);
    170 	if (err)
    171 		goto fail;
    172 
    173 	dm_table_add_deps(table_en, dmp);
    174 	table_en->target_config = tfc;
    175 
    176 	return 0;
    177 fail:
    178 	kmem_free(tfc, sizeof(*tfc));
    179 	return err;
    180 }
    181 
    182 static int
    183 _init_features(dm_target_flakey_config_t *tfc, int argc, char **argv)
    184 {
    185 	char *arg;
    186 	unsigned int value;
    187 
    188 	if (argc == 0)
    189 		return 0;
    190 
    191 	argc = atoi64(*argv++); /* # of args for features */
    192 	if (argc > 6) {
    193 		printf("Invalid # of feature args %d\n", argc);
    194 		return EINVAL;
    195 	}
    196 
    197 	while (argc) {
    198 		argc--;
    199 		arg = *argv++;
    200 
    201 		/* drop_writes */
    202 		if (strcmp(arg, "drop_writes") == 0) {
    203 			tfc->drop_writes = 1;
    204 			continue;
    205 		}
    206 
    207 		/* corrupt_bio_byte <Nth_byte> <direction> <value> <flags> */
    208 		if (strcmp(arg, "corrupt_bio_byte") == 0) {
    209 			if (argc < 4) {
    210 				printf("Invalid # of feature args %d for "
    211 				    "corrupt_bio_byte\n", argc);
    212 				return EINVAL;
    213 			}
    214 
    215 			/* <Nth_byte> */
    216 			argc--;
    217 			value = atoi64(*argv++);
    218 			if (value < 1) {
    219 				printf("Invalid corrupt_bio_byte "
    220 				    "<Nth_byte> arg %u\n", value);
    221 				return EINVAL;
    222 			}
    223 			tfc->corrupt_buf_byte = value;
    224 
    225 			/* <direction> */
    226 			argc--;
    227 			arg = *argv++;
    228 			if (strcmp(arg, "r") == 0) {
    229 				tfc->corrupt_buf_rw = BUF_CMD_READ;
    230 			} else if (strcmp(arg, "w") == 0) {
    231 				tfc->corrupt_buf_rw = BUF_CMD_WRITE;
    232 			} else {
    233 				printf("Invalid corrupt_bio_byte "
    234 				    "<direction> arg %s\n", arg);
    235 				return EINVAL;
    236 			}
    237 
    238 			/* <value> */
    239 			argc--;
    240 			value = atoi64(*argv++);
    241 			if (value > 0xff) {
    242 				printf("Invalid corrupt_bio_byte "
    243 				    "<value> arg %u\n", value);
    244 				return EINVAL;
    245 			}
    246 			tfc->corrupt_buf_value = value;
    247 
    248 			/* <flags> */
    249 			argc--;
    250 			tfc->corrupt_buf_flags = atoi64(*argv++);
    251 
    252 			continue;
    253 		}
    254 
    255 		printf("Unknown Flakey target feature %s\n", arg);
    256 		return EINVAL;
    257 	}
    258 
    259 	if (tfc->drop_writes && (tfc->corrupt_buf_rw == BUF_CMD_WRITE)) {
    260 		printf("Flakey target doesn't allow drop_writes feature and "
    261 		    "corrupt_bio_byte feature with 'w' set\n");
    262 		return EINVAL;
    263 	}
    264 
    265 	return 0;
    266 }
    267 
    268 char *
    269 dm_target_flakey_table(void *target_config)
    270 {
    271 	dm_target_flakey_config_t *tfc;
    272 	char *params, *p;
    273 	int drop_writes;
    274 
    275 	tfc = target_config;
    276 	KASSERT(tfc != NULL);
    277 
    278 	aprint_debug("Flakey target table function called\n");
    279 
    280 	drop_writes = tfc->drop_writes;
    281 
    282 	params = kmem_alloc(DM_MAX_PARAMS_SIZE, KM_SLEEP);
    283 	p = params;
    284 	p += snprintf(p, DM_MAX_PARAMS_SIZE, "%s %d %d %d %u ",
    285 	    tfc->pdev->udev_name, tfc->offset_time,
    286 	    tfc->up_int, tfc->down_int,
    287 	    drop_writes + (tfc->corrupt_buf_byte > 0) * 5);
    288 
    289 	if (drop_writes)
    290 		p += snprintf(p, DM_MAX_PARAMS_SIZE, "drop_writes ");
    291 
    292 	if (tfc->corrupt_buf_byte)
    293 		p += snprintf(p, DM_MAX_PARAMS_SIZE,
    294 		    "corrupt_bio_byte %u %c %u %u ",
    295 		    tfc->corrupt_buf_byte,
    296 		    FLAKEY_CORRUPT_DIR(tfc),
    297 		    tfc->corrupt_buf_value,
    298 		    tfc->corrupt_buf_flags);
    299 	*(--p) = '\0';
    300 
    301 	return params;
    302 }
    303 
    304 #ifdef DEBUG_FLAKEY
    305 static int count = 0;
    306 #endif
    307 
    308 int
    309 dm_target_flakey_strategy(dm_table_entry_t *table_en, struct buf *bp)
    310 {
    311 	dm_target_flakey_config_t *tfc;
    312 #ifndef DEBUG_FLAKEY
    313 	int elapsed;
    314 #endif
    315 
    316 	tfc = table_en->target_config;
    317 #ifndef DEBUG_FLAKEY
    318 	elapsed = (tick - tfc->offset_time) / hz;
    319 	if (elapsed % (tfc->up_int + tfc->down_int) >= tfc->up_int) {
    320 #else
    321 	if (++count % 100 == 0) {
    322 #endif
    323 		if (bp->b_flags & B_READ)
    324 			return _flakey_read(tfc, bp);
    325 		else
    326 			return _flakey_write(tfc, bp);
    327 	}
    328 
    329 	/* This is what linear target does */
    330 	_submit(tfc, bp);
    331 
    332 	return 0;
    333 }
    334 
    335 static __inline void
    336 _submit(dm_target_flakey_config_t *tfc, struct buf *bp)
    337 {
    338 
    339 	bp->b_blkno += tfc->offset;
    340 	VOP_STRATEGY(tfc->pdev->pdev_vnode, bp);
    341 }
    342 
    343 static __inline void
    344 _flakey_eio_buf(struct buf *bp)
    345 {
    346 
    347 	bp->b_error = EIO;
    348 	bp->b_resid = 0;
    349 }
    350 
    351 static void
    352 _flakey_nestiobuf_iodone(buf_t *bp)
    353 {
    354 #ifdef HAS_BUF_PRIV2
    355 	dm_target_flakey_config_t *tfc;
    356 #endif
    357 	buf_t *mbp = bp->b_private;
    358 	int error;
    359 	int donebytes;
    360 
    361 	KASSERT(bp->b_bcount <= bp->b_bufsize);
    362 	KASSERT(mbp != bp);
    363 
    364 	error = bp->b_error;
    365 	if (bp->b_error == 0 &&
    366 	    (bp->b_bcount < bp->b_bufsize || bp->b_resid > 0)) {
    367 		/*
    368 		 * Not all got transferred, raise an error. We have no way to
    369 		 * propagate these conditions to mbp.
    370 		 */
    371 		error = EIO;
    372 	}
    373 
    374 #ifdef HAS_BUF_PRIV2
    375 	tfc = bp->b_private2;
    376 	/*
    377 	 * Linux dm-flakey has changed its read behavior in 2016.
    378 	 * This conditional is to sync with that change.
    379 	 */
    380 	if (tfc->corrupt_buf_byte && tfc->corrupt_buf_rw == BUF_CMD_READ)
    381 		_flakey_corrupt_buf(tfc, mbp);
    382 	else if (!tfc->drop_writes)
    383 		_flakey_eio_buf(mbp);
    384 #endif
    385 	donebytes = bp->b_bufsize;
    386 	putiobuf(bp);
    387 	nestiobuf_done(mbp, donebytes, error);
    388 }
    389 
    390 static int
    391 _flakey_read(dm_target_flakey_config_t *tfc, struct buf *bp)
    392 {
    393 	struct buf *nestbuf;
    394 
    395 	/*
    396 	 * Linux dm-flakey has changed its read behavior in 2016.
    397 	 * This conditional is to sync with that change.
    398 	 */
    399 	if (!tfc->corrupt_buf_byte && !tfc->drop_writes) {
    400 		_flakey_eio_buf(bp);
    401 		biodone(bp);
    402 		return 0;
    403 	}
    404 
    405 	nestbuf = getiobuf(NULL, true);
    406 	nestiobuf_setup(bp, nestbuf, 0, bp->b_bcount);
    407 	nestbuf->b_iodone = _flakey_nestiobuf_iodone;
    408 	nestbuf->b_blkno = bp->b_blkno;
    409 #ifdef HAS_BUF_PRIV2
    410 	nestbuf->b_private2 = tfc;
    411 #endif
    412 	_submit(tfc, nestbuf);
    413 
    414 	return 0;
    415 }
    416 
    417 static int
    418 _flakey_write(dm_target_flakey_config_t *tfc, struct buf *bp)
    419 {
    420 
    421 	if (tfc->drop_writes) {
    422 		aprint_debug("bp=%p drop_writes blkno=%ju\n", bp, bp->b_blkno);
    423 		biodone(bp);
    424 		return 0;
    425 	}
    426 
    427 	if (tfc->corrupt_buf_byte && tfc->corrupt_buf_rw == BUF_CMD_WRITE) {
    428 		_flakey_corrupt_buf(tfc, bp);
    429 		_submit(tfc, bp);
    430 		return 0;
    431 	}
    432 
    433 	/* Error all I/Os if neither of the above two */
    434 	_flakey_eio_buf(bp);
    435 	biodone(bp);
    436 
    437 	return 0;
    438 }
    439 
    440 static int
    441 _flakey_corrupt_buf(dm_target_flakey_config_t *tfc, struct buf *bp)
    442 {
    443 	char *buf;
    444 
    445 	if (bp->b_data == NULL)
    446 		return 1;
    447 	if (bp->b_error)
    448 		return 1; /* Don't corrupt on error */
    449 	if (bp->b_bcount < tfc->corrupt_buf_byte)
    450 		return 1;
    451 	if ((bp->b_flags & tfc->corrupt_buf_flags) != tfc->corrupt_buf_flags)
    452 		return 1;
    453 
    454 	buf = bp->b_data;
    455 	buf[tfc->corrupt_buf_byte - 1] = tfc->corrupt_buf_value;
    456 
    457 	aprint_debug("bp=%p dir=%c blkno=%ju Nth=%u value=%u\n",
    458 	    bp, FLAKEY_CORRUPT_DIR(tfc), bp->b_blkno, tfc->corrupt_buf_byte,
    459 	    tfc->corrupt_buf_value);
    460 
    461 	return 0;
    462 }
    463 
    464 int
    465 dm_target_flakey_sync(dm_table_entry_t *table_en)
    466 {
    467 	dm_target_flakey_config_t *tfc;
    468 	int cmd;
    469 
    470 	tfc = table_en->target_config;
    471 	cmd = 1;
    472 
    473 	return VOP_IOCTL(tfc->pdev->pdev_vnode, DIOCCACHESYNC, &cmd,
    474 	    FREAD | FWRITE, kauth_cred_get());
    475 }
    476 
    477 int
    478 dm_target_flakey_destroy(dm_table_entry_t *table_en)
    479 {
    480 
    481 	if (table_en->target_config == NULL)
    482 		goto out;
    483 
    484 	dm_target_flakey_config_t *tfc = table_en->target_config;
    485 
    486 	/* Decrement pdev ref counter if 0 remove it */
    487 	dm_pdev_decr(tfc->pdev);
    488 
    489 	kmem_free(tfc, sizeof(*tfc));
    490 out:
    491 	/* Unbusy target so we can unload it */
    492 	dm_target_unbusy(table_en->target);
    493 
    494 	return 0;
    495 }
    496 
    497 #if 0
    498 int
    499 dm_target_flakey_upcall(dm_table_entry_t *table_en, struct buf *bp)
    500 {
    501 
    502 	return 0;
    503 }
    504 #endif
    505 
    506 int
    507 dm_target_flakey_secsize(dm_table_entry_t *table_en, unsigned int *secsizep)
    508 {
    509 	dm_target_flakey_config_t *tfc;
    510 	unsigned int secsize;
    511 
    512 	secsize = 0;
    513 
    514 	tfc = table_en->target_config;
    515 	if (tfc != NULL)
    516 		secsize = tfc->pdev->pdev_secsize;
    517 
    518 	*secsizep = secsize;
    519 
    520 	return 0;
    521 }
    522