|
|
1.1 ! root 1: /* ! 2: * Mach Operating System ! 3: * Copyright (c) 1991,1990 Carnegie Mellon University ! 4: * All Rights Reserved. ! 5: * ! 6: * Permission to use, copy, modify and distribute this software and its ! 7: * documentation is hereby granted, provided that both the copyright ! 8: * notice and this permission notice appear in all copies of the ! 9: * software, derivative works or modified versions, and any portions ! 10: * thereof, and that both notices appear in supporting documentation. ! 11: * ! 12: * CARNEGIE MELLON ALLOWS FREE USE OF THIS SOFTWARE IN ITS "AS IS" ! 13: * CONDITION. CARNEGIE MELLON DISCLAIMS ANY LIABILITY OF ANY KIND FOR ! 14: * ANY DAMAGES WHATSOEVER RESULTING FROM THE USE OF THIS SOFTWARE. ! 15: * ! 16: * Carnegie Mellon requests users of this software to return to ! 17: * ! 18: * Software Distribution Coordinator or [email protected] ! 19: * School of Computer Science ! 20: * Carnegie Mellon University ! 21: * Pittsburgh PA 15213-3890 ! 22: * ! 23: * any improvements or extensions that they make and grant Carnegie Mellon ! 24: * the rights to redistribute these changes. ! 25: */ ! 26: /* ! 27: * File: rz_disk.c ! 28: * Author: Alessandro Forin, Carnegie Mellon University ! 29: * Date: 10/90 ! 30: * ! 31: * Top layer of the SCSI driver: interface with the MI. ! 32: * This file contains operations specific to disk-like devices. ! 33: * ! 34: * Modified by Kevin T. Van Maren to use a common partition code ! 35: * with the ide driver, and support 'slices'. ! 36: */ ! 37: ! 38: ! 39: #include <scsi/scsi.h> ! 40: #if (NSCSI > 0) ! 41: ! 42: #include <device/buf.h> ! 43: #include <device/disk_status.h> ! 44: #include <device/device_types.h> ! 45: #include <device/param.h> ! 46: #include <device/errno.h> ! 47: ! 48: #include <kern/time_out.h> ! 49: #include <machine/machspl.h> /* spl definitions */ ! 50: #include <mach/std_types.h> ! 51: #include <platforms.h> ! 52: ! 53: #include <scsi/compat_30.h> ! 54: #include <scsi/scsi.h> ! 55: #include <scsi/scsi_defs.h> ! 56: #include <scsi/rz.h> ! 57: #include <scsi/rz_labels.h> ! 58: ! 59: #ifdef MACH_KERNEL ! 60: #else /*MACH_KERNEL*/ ! 61: #include <sys/kernel.h> /* for hz */ ! 62: #endif /*MACH_KERNEL*/ ! 63: ! 64: #include <sys/types.h> ! 65: #include <sys/ioctl.h> ! 66: #include "vm_param.h" ! 67: #include <vm/vm_kern.h> ! 68: #include <vm/pmap.h> ! 69: ! 70: extern void scdisk_read(), scdisk_write(), ! 71: scsi_long_read(), scsi_long_write(); ! 72: ! 73: void scdisk_start(); /* forwards */ ! 74: void scdisk_start_rw(); ! 75: unsigned dkcksum(); ! 76: ! 77: #if 0 ! 78: struct diskpart scsi_array[8*64]; ! 79: #endif 0 ! 80: ! 81: ! 82: /* THIS IS THE BOTTOM LAYER FOR THE SCSI PARTITION CODE */ ! 83: typedef struct scsi_driver_info { ! 84: target_info_t *tgt; ! 85: io_req_t ior; ! 86: void (*readfun)(); ! 87: int sectorsize; ! 88: } scsi_driver_info; ! 89: ! 90: int scsi_read_fun(scsi_driver_info *param, int sectornum, void *buff) ! 91: { ! 92: char *psave; ! 93: int result = TRUE; /* SUCCESS */ ! 94: psave=param->ior->io_data; /* is this necessary ? */ ! 95: ! 96: param->ior->io_data=buff; ! 97: param->ior->io_count = param->sectorsize; ! 98: param->ior->io_op = IO_READ; ! 99: param->ior->io_error = 0; ! 100: param->tgt->ior = param->ior; ! 101: ! 102: (*param->readfun)( param->tgt, sectornum, param->ior); ! 103: iowait(param->ior); ! 104: ! 105: param->ior->io_data=psave; /* restore the io_data pointer ?? */ ! 106: return(result); ! 107: } ! 108: ! 109: ! 110: ! 111: /* ! 112: * Specialized side of the open routine for disks ! 113: */ ! 114: scsi_ret_t scdisk_open(tgt, req) ! 115: target_info_t *tgt; ! 116: io_req_t req; ! 117: { ! 118: register int i, dev_bsize; ! 119: scsi_ret_t ret = /* SCSI_RET_SUCCESS; */ -1; ! 120: unsigned int disk_size, secs_per_cyl, sector_size; ! 121: scsi_rcap_data_t *cap; ! 122: struct disklabel *label; ! 123: io_req_t ior; ! 124: void (*readfun)() = scdisk_read; ! 125: char *data = (char *)0; ! 126: ! 127: int numpart; ! 128: ! 129: scsi_driver_info scsi_info; ! 130: char drive_name[10]; /* used for disklabel strings */ ! 131: ! 132: if (tgt->flags & TGT_ONLINE) ! 133: return SCSI_RET_SUCCESS; ! 134: ! 135: /* ! 136: * Dummy ior for proper sync purposes ! 137: */ ! 138: io_req_alloc(ior,0); ! 139: ior->io_next = 0; ! 140: ior->io_count = 0; ! 141: ! 142: /* ! 143: * Set the LBN to DEV_BSIZE with a MODE SELECT. ! 144: * If this fails we try a couple other tricks. ! 145: */ ! 146: dev_bsize = 0; ! 147: for (i = 0; i < 5; i++) { ! 148: ior->io_op = IO_INTERNAL; ! 149: ior->io_error = 0; ! 150: ret = scdisk_mode_select(tgt, DEV_BSIZE, ior, 0, 0, 0); ! 151: if (ret == SCSI_RET_SUCCESS) { ! 152: dev_bsize = DEV_BSIZE; ! 153: break; ! 154: } ! 155: if (ret == SCSI_RET_RETRY) { ! 156: timeout(wakeup, tgt, 2*hz); ! 157: await(tgt); ! 158: } ! 159: if (ret == SCSI_RET_DEVICE_DOWN) ! 160: goto done; ! 161: } ! 162: #if 0 ! 163: if (ret != SCSI_RET_SUCCESS) { ! 164: scsi_error( tgt, SCSI_ERR_MSEL, ret, 0); ! 165: ret = D_INVALID_SIZE; ! 166: goto done; ! 167: } ! 168: #endif ! 169: /* ! 170: * Do a READ CAPACITY to get max size. Check LBN too. ! 171: */ ! 172: for (i = 0; i < 5; i++) { ! 173: ior->io_op = IO_INTERNAL; ! 174: ior->io_error = 0; ! 175: ret = scsi_read_capacity(tgt, 0, ior); ! 176: if (ret == SCSI_RET_SUCCESS) ! 177: break; ! 178: } ! 179: if (ret == SCSI_RET_SUCCESS) { ! 180: int val; ! 181: ! 182: cap = (scsi_rcap_data_t*) tgt->cmd_ptr; ! 183: disk_size = (cap->lba1<<24) | ! 184: (cap->lba2<<16) | ! 185: (cap->lba3<< 8) | ! 186: cap->lba4; ! 187: if (scsi_debug) ! 188: printf("rz%d holds %d blocks\n", tgt->unit_no, disk_size); ! 189: val = (cap->blen1<<24) | ! 190: (cap->blen2<<16) | ! 191: (cap->blen3<<8 ) | ! 192: cap->blen4; ! 193: if (dev_bsize == 0) ! 194: dev_bsize = val; ! 195: else ! 196: if (val != dev_bsize) panic("read capacity bad"); ! 197: ! 198: if (disk_size > SCSI_CMD_READ_MAX_LBA) ! 199: tgt->flags |= TGT_BIG; ! 200: ! 201: } else { ! 202: printf("Unknown disk capacity??\n"); ! 203: disk_size = -1; ! 204: } ! 205: /* ! 206: * Mandatory long-form commands ? ! 207: */ ! 208: if (BGET(scsi_use_long_form,(unsigned char)tgt->masterno,tgt->target_id)) ! 209: tgt->flags |= TGT_BIG; ! 210: if (tgt->flags & TGT_BIG) ! 211: readfun = scsi_long_read; ! 212: ! 213: /* ! 214: * Some CDROMS truly dislike 512 as LBN. ! 215: * Use a MODE_SENSE to cover for this case. ! 216: */ ! 217: if (dev_bsize == 0) { ! 218: scsi_mode_sense_data_t *m; ! 219: ! 220: ior->io_op = IO_INTERNAL; ! 221: ior->io_error = 0; ! 222: ret = scsi_mode_sense(tgt, 0/*pagecode*/, 32/*?*/, ior); ! 223: if (ret == SCSI_RET_SUCCESS) { ! 224: m = (scsi_mode_sense_data_t *) tgt->cmd_ptr; ! 225: dev_bsize = ! 226: m->bdesc[0].blen_msb << 16 | ! 227: m->bdesc[0].blen << 8 | ! 228: m->bdesc[0].blen_lsb; ! 229: } ! 230: } ! 231: ! 232: /* ! 233: * Find out about the phys disk geometry -- scsi specific ! 234: */ ! 235: ! 236: ior->io_op = IO_INTERNAL; ! 237: ior->io_error = 0; ! 238: ret = scsi_read_capacity( tgt, 1, ior); ! 239: if (ret == SCSI_RET_SUCCESS) { ! 240: cap = (scsi_rcap_data_t*) tgt->cmd_ptr; ! 241: secs_per_cyl = (cap->lba1<<24) | (cap->lba2<<16) | ! 242: (cap->lba3<< 8) | cap->lba4; ! 243: secs_per_cyl += 1; ! 244: sector_size = (cap->blen1<<24) | (cap->blen2<<16) | ! 245: (cap->blen3<<8 ) | cap->blen4; ! 246: } else { ! 247: sector_size = dev_bsize ? dev_bsize : DEV_BSIZE; ! 248: secs_per_cyl = disk_size; ! 249: } ! 250: if (dev_bsize == 0) ! 251: dev_bsize = sector_size; ! 252: ! 253: if (scsi_debug) ! 254: printf("rz%d: %d sect/cyl %d bytes/sec\n", tgt->unit_no, ! 255: secs_per_cyl, sector_size); ! 256: ! 257: /* ! 258: * At this point, one way or other, we are committed to ! 259: * a given disk capacity and sector size. ! 260: */ ! 261: tgt->block_size = dev_bsize; ! 262: ! 263: /* ! 264: * Get partition table off pack ! 265: */ ! 266: ! 267: #ifdef MACH_KERNEL ! 268: ior->io_data = (char *)kalloc(sector_size); ! 269: #endif /*MACH_KERNEL*/ ! 270: ! 271: scsi_info.tgt=tgt; ! 272: scsi_info.ior=ior; ! 273: scsi_info.readfun=readfun; ! 274: scsi_info.sectorsize=sector_size; ! 275: ! 276: /* label has NOT been allocated space yet! set to the tgt disklabel */ ! 277: label=&scsi_info.tgt->dev_info.disk.l; ! 278: ! 279: sprintf(drive_name, "sd%d:", tgt->unit_no); ! 280: ! 281: if (scsi_debug) ! 282: printf("Using bogus geometry: 32 sectors/track, 64 heads\n"); ! 283: ! 284: fudge_bsd_label(label, DTYPE_SCSI, disk_size /* /(32*64)*/ , ! 285: 64, 32, sector_size, 8); ! 286: ! 287: numpart=get_only_partition(&scsi_info, (*scsi_read_fun), ! 288: tgt->dev_info.disk.scsi_array, MAX_SCSI_PARTS, disk_size, drive_name); ! 289: ! 290: printf("%s %d partitions found\n",drive_name,numpart); ! 291: ! 292: ret=SCSI_RET_SUCCESS; /* if 0, return SCSI_RET_SUCCESS */ ! 293: ! 294: ! 295: done: ! 296: io_req_free(ior); ! 297: ! 298: return(ret); ! 299: } ! 300: ! 301: ! 302: /* ! 303: * Disk strategy routine ! 304: */ ! 305: int scdisk_strategy(ior) ! 306: register io_req_t ior; ! 307: { ! 308: target_info_t *tgt; ! 309: register scsi_softc_t *sc; ! 310: register int i = ior->io_unit, part; ! 311: register unsigned rec, max; ! 312: spl_t s; ! 313: struct diskpart *label; ! 314: ! 315: sc = scsi_softc[rzcontroller(i)]; ! 316: tgt = sc->target[rzslave(i)]; ! 317: part = rzpartition(i); ! 318: ! 319: /* ! 320: * Validate request ! 321: */ ! 322: ! 323: /* readonly ? */ ! 324: if ((tgt->flags & TGT_READONLY) && ! 325: (ior->io_op & (IO_READ|IO_INTERNAL) == 0)) { ! 326: ior->io_error = D_READ_ONLY; ! 327: ior->io_op |= IO_ERROR; ! 328: ior->io_residual = ior->io_count; ! 329: iodone(ior); ! 330: return ior->io_error; ! 331: } ! 332: ! 333: rec = ior->io_recnum; ! 334: ! 335: label=lookup_part(tgt->dev_info.disk.scsi_array, part); ! 336: if (!label) { ! 337: if (scsi_debug) ! 338: printf("sc strategy -- bad partition\n"); ! 339: ior->io_error = D_INVALID_SIZE; ! 340: ior->io_op |= IO_ERROR; ! 341: ior->io_residual = ior->io_count; ! 342: iodone(ior); ! 343: return ior->io_error; ! 344: } ! 345: else max=label->size; ! 346: if (max == -1) /* what about 0? */ ! 347: max = tgt->dev_info.disk.l.d_secperunit - ! 348: ! 349: label->start; ! 350: ! 351: i = (ior->io_count + tgt->block_size - 1) / tgt->block_size; ! 352: if (((rec + i) > max) || (ior->io_count < 0) || ! 353: #if later ! 354: ((rec <= LABELSECTOR) && ((tgt->flags & TGT_WRITE_LABEL) == 0)) ! 355: #else ! 356: FALSE ! 357: #endif ! 358: ) { ! 359: ior->io_error = D_INVALID_SIZE; ! 360: ior->io_op |= IO_ERROR; ! 361: ior->io_residual = ior->io_count; ! 362: iodone(ior); ! 363: return ior->io_error; ! 364: } ! 365: /* ! 366: * Find location on disk: secno and cyl (for disksort) ! 367: */ ! 368: rec += label->start; ! 369: ior->io_residual = rec / tgt->dev_info.disk.l.d_secpercyl; ! 370: ! 371: /* ! 372: * Enqueue operation ! 373: */ ! 374: s = splbio(); ! 375: simple_lock(&tgt->target_lock); ! 376: if (tgt->ior) { ! 377: disksort(tgt->ior, ior); ! 378: simple_unlock(&tgt->target_lock); ! 379: splx(s); ! 380: } else { ! 381: ior->io_next = 0; ! 382: tgt->ior = ior; ! 383: simple_unlock(&tgt->target_lock); ! 384: splx(s); ! 385: ! 386: scdisk_start(tgt,FALSE); ! 387: } ! 388: ! 389: return D_SUCCESS; ! 390: } ! 391: ! 392: /*#define CHECKSUM*/ ! 393: #ifdef CHECKSUM ! 394: int max_checksum_size = 0x2000; ! 395: #endif CHECKSUM ! 396: ! 397: /* ! 398: * Start/completion routine for disks ! 399: */ ! 400: void scdisk_start(tgt, done) ! 401: target_info_t *tgt; ! 402: boolean_t done; ! 403: { ! 404: register io_req_t ior = tgt->ior; ! 405: register unsigned part; ! 406: #ifdef CHECKSUM ! 407: register unsigned secno; ! 408: #endif ! 409: struct diskpart *label; ! 410: ! 411: if (ior == 0) ! 412: return; ! 413: ! 414: if (tgt->flags & TGT_BBR_ACTIVE) ! 415: { ! 416: scdisk_bbr_start(tgt, done); ! 417: return; ! 418: } ! 419: ! 420: if (done) { ! 421: register unsigned int xferred; ! 422: unsigned int max_dma_data; ! 423: ! 424: max_dma_data = scsi_softc[(unsigned char)tgt->masterno]->max_dma_data; ! 425: /* see if we must retry */ ! 426: if ((tgt->done == SCSI_RET_RETRY) && ! 427: ((ior->io_op & IO_INTERNAL) == 0)) { ! 428: delay(1000000);/*XXX*/ ! 429: goto start; ! 430: } else ! 431: /* got a bus reset ? pifff.. */ ! 432: if ((tgt->done == (SCSI_RET_ABORTED|SCSI_RET_RETRY)) && ! 433: ((ior->io_op & IO_INTERNAL) == 0)) { ! 434: if (xferred = ior->io_residual) { ! 435: ior->io_data -= xferred; ! 436: ior->io_count += xferred; ! 437: ior->io_recnum -= xferred / tgt->block_size; ! 438: ior->io_residual = 0; ! 439: } ! 440: goto start; ! 441: } else ! 442: /* ! 443: * Quickly check for errors: if anything goes wrong ! 444: * we do a request sense, see if that is what we did. ! 445: */ ! 446: if (tgt->cur_cmd == SCSI_CMD_REQUEST_SENSE) { ! 447: scsi_sense_data_t *sns; ! 448: unsigned int blockno; ! 449: char *outcome; ! 450: ! 451: ior->io_op = ior->io_temporary; ! 452: ! 453: sns = (scsi_sense_data_t *)tgt->cmd_ptr; ! 454: if (sns->addr_valid) ! 455: blockno = sns->u.xtended.info0 << 24 | ! 456: sns->u.xtended.info1 << 16 | ! 457: sns->u.xtended.info2 << 8 | ! 458: sns->u.xtended.info3; ! 459: else { ! 460: part = rzpartition(ior->io_unit); ! 461: label = lookup_part(tgt->dev_info.disk.scsi_array, part); ! 462: blockno = label->start; ! 463: blockno += ior->io_recnum; ! 464: if (!label) blockno=-1; ! 465: } ! 466: ! 467: if (scsi_check_sense_data(tgt, sns)) { ! 468: ior->io_error = 0; ! 469: if ((tgt->done == SCSI_RET_RETRY) && ! 470: ((ior->io_op & IO_INTERNAL) == 0)) { ! 471: delay(1000000);/*XXX*/ ! 472: goto start; ! 473: } ! 474: outcome = "Recovered"; ! 475: } else { ! 476: outcome = "Unrecoverable"; ! 477: ior->io_error = D_IO_ERROR; ! 478: ior->io_op |= IO_ERROR; ! 479: } ! 480: if ((tgt->flags & TGT_OPTIONAL_CMD) == 0) { ! 481: printf("%s Error, rz%d: %s%s%d\n", outcome, ! 482: tgt->target_id + (tgt->masterno * 8), ! 483: (ior->io_op & IO_READ) ? "Read" : ! 484: ((ior->io_op & IO_INTERNAL) ? "(command)" : "Write"), ! 485: " disk error, phys block no. ", blockno); ! 486: ! 487: scsi_print_sense_data(sns); ! 488: ! 489: /* ! 490: * On fatal read/write errors try replacing the bad block ! 491: * The bbr routine will return TRUE iff it took control ! 492: * over the target for all subsequent operations. In this ! 493: * event, the queue of requests is effectively frozen. ! 494: */ ! 495: if (ior->io_error && ! 496: ((sns->error_class == SCSI_SNS_XTENDED_SENSE_DATA) && ! 497: ((sns->u.xtended.sense_key == SCSI_SNS_HW_ERR) || ! 498: (sns->u.xtended.sense_key == SCSI_SNS_MEDIUM_ERR))) && ! 499: scdisk_bad_block_repl(tgt, blockno)) ! 500: return; ! 501: } ! 502: } ! 503: ! 504: /* ! 505: * See if we had errors ! 506: */ ! 507: else if (tgt->done != SCSI_RET_SUCCESS) { ! 508: ! 509: if (tgt->done == SCSI_RET_NEED_SENSE) { ! 510: ! 511: ior->io_temporary = ior->io_op; ! 512: ior->io_op = IO_INTERNAL; ! 513: scsi_request_sense(tgt, ior, 0); ! 514: return; ! 515: ! 516: } else if (tgt->done == SCSI_RET_DEVICE_DOWN) { ! 517: ior->io_error = D_DEVICE_DOWN; ! 518: ior->io_op |= IO_ERROR; ! 519: } else { ! 520: printf("%s%x\n", "?rz_disk Disk error, ret=x", tgt->done); ! 521: ior->io_error = D_IO_ERROR; ! 522: ior->io_op |= IO_ERROR; ! 523: } ! 524: } ! 525: /* ! 526: * No errors. ! 527: * See if we requested more than the max ! 528: * (We use io_residual in a flip-side way here) ! 529: */ ! 530: else if (ior->io_count > (xferred = max_dma_data)) { ! 531: ior->io_residual += xferred; ! 532: ior->io_count -= xferred; ! 533: ior->io_data += xferred; ! 534: ior->io_recnum += xferred / tgt->block_size; ! 535: goto start; ! 536: } ! 537: else if (xferred = ior->io_residual) { ! 538: ior->io_data -= xferred; ! 539: ior->io_count += xferred; ! 540: ior->io_recnum -= xferred / tgt->block_size; ! 541: ior->io_residual = 0; ! 542: } /* that's it */ ! 543: ! 544: #ifdef CHECKSUM ! 545: if ((ior->io_op & IO_READ) && (ior->io_count < max_checksum_size)) { ! 546: part = rzpartition(ior->io_unit); ! 547: label=lookup_part(tgt->dev_info.disk.scsi_array, part); ! 548: if (!label) printf("NOT FOUND!\n"); ! 549: secno = ior->io_recnum + label->start; ! 550: scdisk_bcheck(secno, ior->io_data, ior->io_count); ! 551: } ! 552: #endif CHECKSUM ! 553: ! 554: /* dequeue next one */ ! 555: { ! 556: io_req_t next; ! 557: ! 558: simple_lock(&tgt->target_lock); ! 559: next = ior->io_next; ! 560: tgt->ior = next; ! 561: simple_unlock(&tgt->target_lock); ! 562: ! 563: iodone(ior); ! 564: if (next == 0) ! 565: return; ! 566: ! 567: ior = next; ! 568: } ! 569: ! 570: #ifdef CHECKSUM ! 571: if (((ior->io_op & IO_READ) == 0) && (ior->io_count < max_checksum_size)) { ! 572: part = rzpartition(ior->io_unit); ! 573: label=lookup_part(tgt->dev_info.disk.scsi_array, part); ! 574: secno = ior->io_recnum + label->start; ! 575: scdisk_checksum(secno, ior->io_data, ior->io_count); ! 576: } ! 577: #endif CHECKSUM ! 578: } ! 579: ior->io_residual = 0; ! 580: start: ! 581: scdisk_start_rw( tgt, ior); ! 582: } ! 583: ! 584: void scdisk_start_rw( tgt, ior) ! 585: target_info_t *tgt; ! 586: register io_req_t ior; ! 587: { ! 588: unsigned int part, secno; ! 589: register boolean_t long_form; ! 590: struct diskpart *label; ! 591: ! 592: part = rzpartition(ior->io_unit); ! 593: label=lookup_part(tgt->dev_info.disk.scsi_array, part); ! 594: if (!label) ! 595: printf("NOT FOUND!\n"); ! 596: secno = ior->io_recnum + label->start; ! 597: ! 598: /* Use long form if either big block addresses or ! 599: the size is more than we can fit in one byte */ ! 600: long_form = (tgt->flags & TGT_BIG) || ! 601: (ior->io_count > (256 * tgt->block_size)); ! 602: if (ior->io_op & IO_READ) ! 603: (long_form ? scsi_long_read : scdisk_read)(tgt, secno, ior); ! 604: else if ((ior->io_op & IO_INTERNAL) == 0) ! 605: (long_form ? scsi_long_write : scdisk_write)(tgt, secno, ior); ! 606: } ! 607: ! 608: #include <sys/ioctl.h> ! 609: #ifdef ULTRIX_COMPAT ! 610: #include <mips/PMAX/rzdisk.h> ! 611: #endif /*ULTRIX_COMPAT*/ ! 612: ! 613: io_return_t ! 614: scdisk_get_status(dev, tgt, flavor, status, status_count) ! 615: int dev; ! 616: target_info_t *tgt; ! 617: dev_flavor_t flavor; ! 618: dev_status_t status; ! 619: natural_t *status_count; ! 620: { ! 621: struct disklabel *lp; ! 622: struct diskpart *label; ! 623: ! 624: lp = &tgt->dev_info.disk.l; ! 625: ! 626: switch (flavor) { ! 627: #ifdef MACH_KERNEL ! 628: case DEV_GET_SIZE: ! 629: ! 630: label=lookup_part(tgt->dev_info.disk.scsi_array, rzpartition(dev)); ! 631: status[DEV_GET_SIZE_DEVICE_SIZE] = label->size * lp->d_secsize; ! 632: status[DEV_GET_SIZE_RECORD_SIZE] = tgt->block_size; ! 633: *status_count = DEV_GET_SIZE_COUNT; ! 634: break; ! 635: #endif ! 636: ! 637: case DIOCGDINFO: ! 638: *(struct disklabel *)status = *lp; ! 639: #ifdef MACH_KERNEL ! 640: *status_count = sizeof(struct disklabel)/sizeof(int); ! 641: #endif MACH_KERNEL ! 642: break; ! 643: ! 644: case DIOCGDINFO - (0x10<<16): ! 645: *(struct disklabel *)status = *lp; ! 646: #ifdef MACH_KERNEL ! 647: *status_count = sizeof(struct disklabel)/sizeof(int) - 4; ! 648: #endif MACH_KERNEL ! 649: break; ! 650: ! 651: #ifdef MACH_KERNEL ! 652: #else /*MACH_KERNEL*/ ! 653: #if ULTRIX_COMPAT ! 654: case SCSI_MODE_SENSE: /*_IOWR(p, 9, struct mode_sel_sns_params) */ ! 655: break; ! 656: case DIOCGETPT: /*_IOR(p, 1, struct pt) */ ! 657: case SCSI_GET_SENSE: /*_IOR(p, 10, struct extended_sense) */ ! 658: return ul_disk_ioctl(tgt, flavor, status, status_count); ! 659: #endif /*ULTRIX_COMPAT*/ ! 660: #endif /*!MACH_KERNEL*/ ! 661: ! 662: #if 0 ! 663: case DIOCRFORMAT: ! 664: break; ! 665: #endif ! 666: default: ! 667: #ifdef i386 ! 668: return(scsi_i386_get_status(dev, tgt, flavor, status, status_count)); ! 669: #else i386 ! 670: return(D_INVALID_OPERATION); ! 671: #endif i386 ! 672: } ! 673: return D_SUCCESS; ! 674: } ! 675: ! 676: io_return_t ! 677: scdisk_set_status(dev, tgt, flavor, status, status_count) ! 678: int dev; ! 679: target_info_t *tgt; ! 680: dev_flavor_t flavor; ! 681: dev_status_t status; ! 682: natural_t status_count; ! 683: { ! 684: io_return_t error = D_SUCCESS; ! 685: struct disklabel *lp; ! 686: ! 687: lp = &tgt->dev_info.disk.l; ! 688: ! 689: ! 690: switch (flavor) { ! 691: case DIOCSRETRIES: ! 692: #ifdef MACH_KERNEL ! 693: if (status_count != sizeof(int)) ! 694: return D_INVALID_SIZE; ! 695: #endif /* MACH_KERNEL */ ! 696: scsi_bbr_retries = *(int *)status; ! 697: break; ! 698: ! 699: case DIOCWLABEL: ! 700: case DIOCWLABEL - (0x10<<16): ! 701: if (*(int*)status) ! 702: tgt->flags |= TGT_WRITE_LABEL; ! 703: else ! 704: tgt->flags &= ~TGT_WRITE_LABEL; ! 705: break; ! 706: case DIOCSDINFO: ! 707: case DIOCSDINFO - (0x10<<16): ! 708: case DIOCWDINFO: ! 709: case DIOCWDINFO - (0x10<<16): ! 710: #ifdef MACH_KERNEL ! 711: if (status_count != sizeof(struct disklabel) / sizeof(int)) ! 712: return D_INVALID_SIZE; ! 713: #endif /* MACH_KERNEL */ ! 714: error = setdisklabel(lp, (struct disklabel*) status); ! 715: if (error || (flavor == DIOCSDINFO) || (flavor == DIOCSDINFO - (0x10<<16))) ! 716: return error; ! 717: error = scdisk_writelabel(tgt); ! 718: break; ! 719: ! 720: #ifdef MACH_KERNEL ! 721: #else /*MACH_KERNEL*/ ! 722: #if ULTRIX_COMPAT ! 723: case SCSI_FORMAT_UNIT: /*_IOW(p, 4, struct format_params) */ ! 724: case SCSI_REASSIGN_BLOCK: /*_IOW(p, 5, struct reassign_params) */ ! 725: case SCSI_READ_DEFECT_DATA: /*_IOW(p, 6, struct read_defect_params) */ ! 726: case SCSI_VERIFY_DATA: /*_IOW(p, 7, struct verify_params) */ ! 727: case SCSI_MODE_SELECT: /*_IOW(p, 8, struct mode_sel_sns_params) */ ! 728: case SCSI_MODE_SENSE: /*_IOW(p, 9, struct mode_sel_sns_params) */ ! 729: case SCSI_GET_INQUIRY_DATA: /*_IOW(p, 11, struct inquiry_info) */ ! 730: return ul_disk_ioctl(tgt, flavor, status, status_count); ! 731: #endif /*ULTRIX_COMPAT*/ ! 732: #endif /*!MACH_KERNEL*/ ! 733: ! 734: #if notyet ! 735: case DIOCWFORMAT: ! 736: case DIOCSBAD: /* ?? how ? */ ! 737: #endif ! 738: default: ! 739: #ifdef i386 ! 740: error = scsi_i386_set_status(dev, tgt, flavor, status, status_count); ! 741: #else i386 ! 742: error = D_INVALID_OPERATION; ! 743: #endif i386 ! 744: } ! 745: return error; ! 746: } ! 747: ! 748: static int grab_it(tgt, ior) ! 749: target_info_t *tgt; ! 750: io_req_t ior; ! 751: { ! 752: spl_t s; ! 753: ! 754: s = splbio(); ! 755: simple_lock(&tgt->target_lock); ! 756: if (!tgt->ior) ! 757: tgt->ior = ior; ! 758: simple_unlock(&tgt->target_lock); ! 759: splx(s); ! 760: ! 761: if (tgt->ior != ior) ! 762: return D_ALREADY_OPEN; ! 763: return D_SUCCESS; ! 764: } ! 765: ! 766: /* Write back a label to the disk */ ! 767: io_return_t scdisk_writelabel(tgt) ! 768: target_info_t *tgt; ! 769: { ! 770: ! 771: printf("scdisk_writelabel: NO LONGER IMPLEMENTED\n"); ! 772: #if 0 ! 773: /* Taken out at Bryan's suggestion until 'fixed' for slices */ ! 774: ! 775: io_req_t ior; ! 776: char *data = (char *)0; ! 777: struct disklabel *label; ! 778: io_return_t error; ! 779: int dev_bsize = tgt->block_size; ! 780: ! 781: io_req_alloc(ior,0); ! 782: #ifdef MACH_KERNEL ! 783: data = (char *)kalloc(dev_bsize); ! 784: #else /*MACH_KERNEL*/ ! 785: data = (char *)ior->io_data; ! 786: #endif /*MACH_KERNEL*/ ! 787: ior->io_next = 0; ! 788: ior->io_prev = 0; ! 789: ior->io_data = data; ! 790: ior->io_count = dev_bsize; ! 791: ior->io_op = IO_READ; ! 792: ior->io_error = 0; ! 793: ! 794: if (grab_it(tgt, ior) != D_SUCCESS) { ! 795: error = D_ALREADY_OPEN; ! 796: goto ret; ! 797: } ! 798: ! 799: scdisk_read( tgt, tgt->dev_info.disk.labelsector, ior); ! 800: iowait(ior); ! 801: if (error = ior->io_error) ! 802: goto ret; ! 803: ! 804: label = (struct disklabel *) &data[tgt->dev_info.disk.labeloffset]; ! 805: *label = tgt->dev_info.disk.l; ! 806: ! 807: ior->io_next = 0; ! 808: ior->io_prev = 0; ! 809: ior->io_data = data; ! 810: ior->io_count = dev_bsize; ! 811: ior->io_op = IO_WRITE; ! 812: ! 813: while (grab_it(tgt, ior) != D_SUCCESS) ; /* ahem */ ! 814: ! 815: scdisk_write( tgt, tgt->dev_info.disk.labelsector, ior); ! 816: iowait(ior); ! 817: ! 818: error = ior->io_error; ! 819: ret: ! 820: #ifdef MACH_KERNEL ! 821: if (data) kfree((int)data, dev_bsize); ! 822: #endif /*MACH_KERNEL*/ ! 823: io_req_free(ior); ! 824: return error; ! 825: ! 826: #endif 0 scdisk_writelabel ! 827: return -1; /* FAILURE ? */ ! 828: } ! 829: ! 830: #ifdef MACH_KERNEL ! 831: #else /*MACH_KERNEL*/ ! 832: #if ULTRIX_COMPAT ! 833: ! 834: io_return_t ul_disk_ioctl(tgt, flavor, status, status_count) ! 835: target_info_t *tgt; ! 836: dev_flavor_t flavor; ! 837: dev_status_t status; ! 838: natural_t status_count; ! 839: { ! 840: io_return_t ret; ! 841: scsi_ret_t err = SCSI_RET_ABORTED;/*xxx*/ ! 842: io_req_t ior; ! 843: ! 844: if (!suser()) ! 845: return EACCES; ! 846: ! 847: ior = geteblk(sizeof(struct defect_descriptors)); ! 848: ior->io_next = 0; ! 849: ior->io_count = 0; ! 850: ior->io_op = IO_INTERNAL; ! 851: ior->io_error = 0; ! 852: ior->io_recnum = 0; ! 853: ior->io_residual = 0; ! 854: ! 855: switch (flavor) { ! 856: ! 857: case DIOCGETPT: { /*_IOR(p, 1, struct pt) */ ! 858: scsi_dec_label_t *p; ! 859: struct disklabel *lp; ! 860: int i; ! 861: ! 862: lp = &tgt->dev_info.disk.l; ! 863: p = (scsi_dec_label_t *)status; ! 864: ! 865: p->magic = DEC_PARTITION_MAGIC; ! 866: p->in_use = 1; ! 867: for (i = 0; i < 8; i++) { ! 868: label=lookup_part(tgt->dev_info.disk.scsi_array, part); ! 869: p->partitions[i].n_sectors = label->size; ! 870: p->partitions[i].offset = label->start; ! 871: } ! 872: err = SCSI_RET_SUCCESS; ! 873: } ! 874: break; ! 875: ! 876: case SCSI_GET_SENSE: { /*_IOR(p, 10, struct extended_sense) */ ! 877: scsi_sense_data_t *s; ! 878: ! 879: s = (scsi_sense_data_t*)tgt->cmd_ptr; ! 880: bcopy(s, status, sizeof(*s) + s->u.xtended.add_len - 1); ! 881: err = SCSI_RET_SUCCESS; ! 882: /* only once */ ! 883: bzero(tgt->cmd_ptr, sizeof(scsi_sense_data_t)); ! 884: } ! 885: break; ! 886: ! 887: case SCSI_GET_INQUIRY_DATA: { /*_IOR(p, 11, struct inquiry_info) */ ! 888: struct mode_sel_sns_params *ms; ! 889: ! 890: ms = (struct mode_sel_sns_params*)status; ! 891: err = scsi_inquiry( tgt, SCSI_INQ_STD_DATA); ! 892: if (copyout(tgt->cmd_ptr, ms->msp_addr, sizeof(struct inquiry_info))){ ! 893: ret = EFAULT; ! 894: goto out; ! 895: } ! 896: } ! 897: break; ! 898: ! 899: case SCSI_FORMAT_UNIT: { /*_IOW(p, 4, struct format_params) */ ! 900: struct format_params *fp; ! 901: struct defect_descriptors *df; ! 902: unsigned char mode; ! 903: unsigned int old_timeout; ! 904: ! 905: fp = (struct format_params *)status; ! 906: df = (struct defect_descriptors*)ior->io_data; ! 907: if (fp->fp_length != 0) { ! 908: if (copyin(fp->fp_addr, df, sizeof(*df))) { ! 909: ret = EFAULT; ! 910: goto out; ! 911: } ! 912: ior->io_count = sizeof(*df); ! 913: } else ! 914: ior->io_count = 0; ! 915: mode = fp->fp_format & SCSI_CMD_FMT_LIST_TYPE; ! 916: switch (fp->fp_defects) { ! 917: case VENDOR_DEFECTS: ! 918: mode |= SCSI_CMD_FMT_FMTDATA|SCSI_CMD_FMT_CMPLIST; ! 919: break; ! 920: case KNOWN_DEFECTS: ! 921: mode |= SCSI_CMD_FMT_FMTDATA; ! 922: break; ! 923: case NO_DEFECTS: ! 924: default: ! 925: break; ! 926: } ! 927: old_timeout = scsi_watchdog_period; ! 928: scsi_watchdog_period = 60*60; /* 1 hour should be enough, I hope */ ! 929: err = scsi_format_unit( tgt, mode, fp->fp_pattern, ! 930: fp->fp_interleave, ior); ! 931: scsi_watchdog_period = old_timeout; ! 932: /* Make sure we re-read all info afresh */ ! 933: tgt->flags = TGT_ALIVE | ! 934: (tgt->flags & (TGT_REMOVABLE_MEDIA|TGT_FULLY_PROBED)); ! 935: } ! 936: break; ! 937: ! 938: case SCSI_REASSIGN_BLOCK: { /*_IOW(p, 5, struct reassign_params) */ ! 939: struct reassign_params *r; ! 940: int ndef; ! 941: ! 942: r = (struct reassign_params*) status; ! 943: ndef = r->rp_header.defect_len0 | (r->rp_header.defect_len1 >> 8); ! 944: ndef >>= 2; ! 945: tgt->ior = ior; ! 946: (void) scsi_reassign_blocks( tgt, &r->rp_lbn3, ndef, ior); ! 947: iowait(ior); ! 948: err = tgt->done; ! 949: } ! 950: break; ! 951: ! 952: case SCSI_READ_DEFECT_DATA: { /*_IOW(p, 6, struct read_defect_params) */ ! 953: struct read_defect_params *dp; ! 954: ! 955: dp = (struct read_defect_params *)status; ! 956: ior->io_count = ior->io_alloc_size; ! 957: if (dp->rdp_alclen > ior->io_count) ! 958: dp->rdp_alclen = ior->io_count; ! 959: else ! 960: ior->io_count = dp->rdp_alclen; ! 961: ior->io_op |= IO_READ; ! 962: tgt->ior = ior; ! 963: err = scsi_read_defect(tgt, dp->rdp_format|0x18, ior); ! 964: if (copyout(ior->io_data, dp->rdp_addr, dp->rdp_alclen)) { ! 965: ret = EFAULT; ! 966: goto out; ! 967: } ! 968: } ! 969: break; ! 970: ! 971: case SCSI_VERIFY_DATA: { /*_IOW(p, 7, struct verify_params) */ ! 972: struct verify_params *v; ! 973: unsigned int old_timeout; ! 974: ! 975: old_timeout = scsi_watchdog_period; ! 976: scsi_watchdog_period = 5*60; /* 5 mins enough, I hope */ ! 977: v = (struct verify_params *)status; ! 978: ior->io_count = 0; ! 979: err = scdisk_verify( tgt, v->vp_lbn, v->vp_length, ior); ! 980: scsi_watchdog_period = old_timeout; ! 981: } ! 982: break; ! 983: ! 984: case SCSI_MODE_SELECT: { /*_IOW(p, 8, struct mode_sel_sns_params) */ ! 985: struct mode_sel_sns_params *ms; ! 986: ! 987: ms = (struct mode_sel_sns_params*)status; ! 988: if(copyin(ms->msp_addr, ior->io_data, ms->msp_length)) { ! 989: ret = EFAULT; ! 990: goto out; ! 991: } ! 992: err = scdisk_mode_select( tgt, DEV_BSIZE, ior, ior->io_data, ! 993: ms->msp_length, ms->msp_setps); ! 994: } ! 995: break; ! 996: ! 997: case SCSI_MODE_SENSE: { /*_IOWR(p, 9, struct mode_sel_sns_params) */ ! 998: struct mode_sel_sns_params *ms; ! 999: unsigned char pagecode; ! 1000: ! 1001: ms = (struct mode_sel_sns_params*)status; ! 1002: pagecode = (ms->msp_pgcode & 0x3f) | (ms->msp_pgctrl << 6); ! 1003: err = scsi_mode_sense( tgt, pagecode, ms->msp_length, ior); ! 1004: if (copyout(tgt->cmd_ptr, ms->msp_addr, ms->msp_length)){ ! 1005: ret = EFAULT; ! 1006: goto out; ! 1007: } ! 1008: } ! 1009: break; ! 1010: } ! 1011: ! 1012: ret = (err == SCSI_RET_SUCCESS) ? D_SUCCESS : D_IO_ERROR; ! 1013: if (ior->io_op & IO_ERROR) ! 1014: ret = D_IO_ERROR; ! 1015: out: ! 1016: brelse(ior); ! 1017: return ret; ! 1018: } ! 1019: #endif /*ULTRIX_COMPAT*/ ! 1020: #endif /*!MACH_KERNEL*/ ! 1021: ! 1022: #ifdef CHECKSUM ! 1023: ! 1024: #define SUMSIZE 0x10000 ! 1025: #define SUMHASH(b) (((b)>>1) & (SUMSIZE - 1)) ! 1026: struct { ! 1027: long blockno; ! 1028: long sum; ! 1029: } scdisk_checksums[SUMSIZE]; ! 1030: ! 1031: void scdisk_checksum(bno, addr, size) ! 1032: long bno; ! 1033: register unsigned int *addr; ! 1034: { ! 1035: register int i = size/sizeof(int); ! 1036: register unsigned int sum = -1; ! 1037: ! 1038: while (i-- > 0) ! 1039: sum ^= *addr++; ! 1040: scdisk_checksums[SUMHASH(bno)].blockno = bno; ! 1041: scdisk_checksums[SUMHASH(bno)].sum = sum; ! 1042: } ! 1043: ! 1044: void scdisk_bcheck(bno, addr, size) ! 1045: long bno; ! 1046: register unsigned int *addr; ! 1047: { ! 1048: register int i = size/sizeof(int); ! 1049: register unsigned int sum = -1; ! 1050: unsigned int *start = addr; ! 1051: ! 1052: if (scdisk_checksums[SUMHASH(bno)].blockno != bno) { ! 1053: if (scsi_debug) printf("No checksum for block x%x\n", bno); ! 1054: return; ! 1055: } ! 1056: ! 1057: while (i-- > 0) ! 1058: sum ^= *addr++; ! 1059: ! 1060: if (scdisk_checksums[SUMHASH(bno)].sum != sum) { ! 1061: printf("Bad checksum (x%x != x%x), bno x%x size x%x at x%x\n", ! 1062: sum, ! 1063: scdisk_checksums[bno & (SUMSIZE - 1)].sum, ! 1064: bno, size, start); ! 1065: gimmeabreak(); ! 1066: scdisk_checksums[SUMHASH(bno)].sum = sum; ! 1067: } ! 1068: } ! 1069: ! 1070: ! 1071: #endif CHECKSUM ! 1072: ! 1073: /*#define PERF */ ! 1074: #ifdef PERF ! 1075: int test_read_size = 512; ! 1076: int test_read_skew = 12; ! 1077: int test_read_skew_min = 0; ! 1078: int test_read_nreads = 1000; ! 1079: int test_read_bdev = 0; ! 1080: ! 1081: #include <sys/time.h> ! 1082: ! 1083: void test_read(max) ! 1084: { ! 1085: int i, ssk, usecs; ! 1086: struct timeval start, stop; ! 1087: ! 1088: if (max == 0) ! 1089: max = test_read_skew + 1; ! 1090: ssk = test_read_skew; ! 1091: for (i = test_read_skew_min; i < max; i++){ ! 1092: test_read_skew = i; ! 1093: ! 1094: start = time; ! 1095: read_test(); ! 1096: stop = time; ! 1097: ! 1098: usecs = stop.tv_usec - start.tv_usec; ! 1099: if (usecs < 0) { ! 1100: stop.tv_sec -= 1; ! 1101: usecs += 1000000; ! 1102: } ! 1103: printf("Skew %3d size %d count %d time %3d sec %d us\n", ! 1104: i, test_read_size, test_read_nreads, ! 1105: stop.tv_sec - start.tv_sec, usecs); ! 1106: } ! 1107: test_read_skew = ssk; ! 1108: } ! 1109: ! 1110: void read_test() ! 1111: { ! 1112: static int buffer[(8192*2)/sizeof(int)]; ! 1113: struct io_req io; ! 1114: register int i, rec; ! 1115: ! 1116: bzero(&io, sizeof(io)); ! 1117: io.io_unit = test_read_bdev; ! 1118: io.io_op = IO_READ; ! 1119: io.io_count = test_read_size; ! 1120: io.io_data = (char*) buffer; ! 1121: ! 1122: for (rec = 0, i = 0; i < test_read_nreads; i++) { ! 1123: io.io_op = IO_READ; ! 1124: io.io_recnum = rec; ! 1125: scdisk_strategy(&io); ! 1126: rec += test_read_skew; ! 1127: iowait(&io); ! 1128: } ! 1129: } ! 1130: ! 1131: void tur_test() ! 1132: { ! 1133: struct io_req io; ! 1134: register int i; ! 1135: char *a, *b; ! 1136: struct timeval start, stop; ! 1137: ! 1138: bzero(&io, sizeof(io)); ! 1139: io.io_unit = test_read_bdev; ! 1140: io.io_data = (char*)&io;/*unused but kernel space*/ ! 1141: ! 1142: start = time; ! 1143: for (i = 0; i < test_read_nreads; i++) { ! 1144: io.io_op = IO_INTERNAL; ! 1145: rz_check(io.io_unit, &a, &b); ! 1146: scsi_test_unit_ready(b,&io); ! 1147: } ! 1148: stop = time; ! 1149: i = stop.tv_usec - start.tv_usec; ! 1150: if (i < 0) { ! 1151: stop.tv_sec -= 1; ! 1152: i += 1000000; ! 1153: } ! 1154: printf("%d test-unit-ready took %3d sec %d us\n", ! 1155: test_read_nreads, ! 1156: stop.tv_sec - start.tv_sec, i); ! 1157: } ! 1158: ! 1159: #endif PERF ! 1160: ! 1161: /*#define WDEBUG*/ ! 1162: #ifdef WDEBUG ! 1163: ! 1164: int buggo_write_size = 8192; ! 1165: int buggo_dev = 2; /* rz0b */ /* changed by KTVM from 1 (still b) */ ! 1166: int buggo_out_buffer[8192/2]; ! 1167: int buggo_in_buffer[8192/2]; ! 1168: int buggotest(n, pattern, verbose) ! 1169: { ! 1170: struct io_req io; ! 1171: register int i, rec; ! 1172: ! 1173: if (n <= 0) ! 1174: n = 1; ! 1175: ! 1176: if(pattern) ! 1177: for (i = 0; i < buggo_write_size/4; i++) ! 1178: buggo_out_buffer[i] = i + pattern; ! 1179: ! 1180: for (i = 0; i < n; i++) { ! 1181: register int j; ! 1182: ! 1183: buggo_out_buffer[0] = i + pattern; ! 1184: buggo_out_buffer[(buggo_write_size/4)-1] = i + pattern; ! 1185: bzero(&io, sizeof(io)); ! 1186: io.io_unit = buggo_dev; ! 1187: io.io_data = (char*)buggo_out_buffer; ! 1188: io.io_op = IO_WRITE; ! 1189: io.io_count = buggo_write_size; ! 1190: io.io_recnum = i % 1024; ! 1191: scdisk_strategy(&io); ! 1192: ! 1193: bzero(buggo_in_buffer, sizeof(buggo_in_buffer)); ! 1194: iowait(&io); ! 1195: ! 1196: if (verbose) ! 1197: printf("Done write with %x", io.io_error); ! 1198: ! 1199: bzero(&io, sizeof(io)); ! 1200: io.io_unit = buggo_dev; ! 1201: io.io_data = (char*)buggo_in_buffer; ! 1202: io.io_op = IO_READ; ! 1203: io.io_count = buggo_write_size; ! 1204: io.io_recnum = i % 1024; ! 1205: scdisk_strategy(&io); ! 1206: iowait(&io); ! 1207: ! 1208: if (verbose) ! 1209: printf("Done read with %x", io.io_error); ! 1210: ! 1211: for (j = 0; j < buggo_write_size/4; j++) ! 1212: if (buggo_out_buffer[j] != buggo_in_buffer[j]){ ! 1213: printf("Difference at %d-th word: %x %x\n", ! 1214: buggo_out_buffer[j], buggo_in_buffer[j]); ! 1215: return i; ! 1216: } ! 1217: } ! 1218: printf("Test ok\n"); ! 1219: return n; ! 1220: } ! 1221: #endif WDEBUG ! 1222: #endif /* NSCSI > 0 */
This archive runs on limited infrastructure. Preserving old code on modern bandwidth. Automated agents are requested to crawl responsibly.