1 /* $NetBSD: dm_target_flakey.c,v 1.3 2020/01/21 16:27:53 tkusumi 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@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.3 2020/01/21 16:27:53 tkusumi 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 transfered, 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