|
|
1.1 ! root 1: /*********************************************************************** ! 2: * Module: haict.c ! 3: * ! 4: * Unix device driver functions for accessing SCSI tape drives as ! 5: * character devices. Conforms to Mark Williams Coherent definition ! 6: * of the Unix Device Driver interface for Coherent v4.0.1. ! 7: * ! 8: * The philosophy of this driver is to support basic functions on ! 9: * the tape drive (read, write, retension, rewind, skip, etc). There ! 10: * are more features out there for all the SCSI tape drives out there ! 11: * than I know what to do with. I leave custom support for these ! 12: * drives to the people who have them. To this end this drive will ! 13: * blindly follow whatever information it can get using Mode Sense ! 14: * and Read Block Limits CDB's. These tests are done at open time. ! 15: * An application can change the operation of the driver by applying ! 16: * the mode select command through I/O Control mechanism. ! 17: * ! 18: * Copyright (c) 1993, Christopher Sean Hilton, All Rights Reserved. ! 19: * ! 20: * Last Modified: Mon Jul 26 17:18:59 1993 by [chris] ! 21: * ! 22: * $Id: haict.c,v 2.4 93/08/09 13:45:06 bin Exp Locker: bin $ ! 23: * ! 24: * $Log: haict.c,v $ ! 25: * Revision 2.4 93/08/09 13:45:06 bin ! 26: * Kernel 82 changes ! 27: * ! 28: */ ! 29: ! 30: #include <stddef.h> ! 31: #include <sys/coherent.h> ! 32: #include <sys/buf.h> ! 33: #include <sys/inode.h> ! 34: #include <sys/stat.h> ! 35: #include <sys/sched.h> ! 36: #include <errno.h> ! 37: #include <sys/mtioctl.h> ! 38: #include <sys/file.h> /* Louis */ ! 39: ! 40: #include <sys/haiscsi.h> ! 41: #include <sys/haiioctl.h> ! 42: ! 43: #define REWINDTAPE 0x01 ! 44: #define IMMEDIATE 0x0010 ! 45: #define REQSENSE 0x03 ! 46: #define READBLKLMT 0x05 ! 47: #define READ 0x08 ! 48: #define WRITE 0x0a ! 49: #define WRITEFM 0x10 ! 50: #define SPACE 0x11 ! 51: #define SKIP_BLK 0x00 ! 52: #define SKIP_FM 0x01 ! 53: #define SKIP_SEQFM 0x02 ! 54: #define SKIP_EOT 0x03 ! 55: #define MODESELECT 0x15 ! 56: #define ERASE 0x19 ! 57: #define ERASE_BLOCK 0x0000 ! 58: #define ERASE_TAPE 0x0001 ! 59: #define MODESENSE 0x1a ! 60: #define LOAD 0x1b ! 61: #define RETENSION 0x0020 ! 62: ! 63: /*********************************************************************** ! 64: * The Tandberg TDC3600 requires special handling because it doesn't ! 65: * respond properly to a write when the tape is at the logical end ! 66: * of tape. Tape should be organized as follows: ! 67: * ! 68: * File 1 --------------| File 2-m ! 69: * DATA[1] | ...DATA[n] | FILEMARK | DATA[1-n] | FILEMARK | FILEMARK | ! 70: * ! 71: * The double file marks signify the Logical End of Tape. When a ! 72: * tape opened in write mode is written to and then closed on the ! 73: * no rewind device it is necessary to write a Logical End of tape ! 74: * and then skip backwards over one of the filemarks. This leaves ! 75: * the tape positioned between the two filemarks. When this happens ! 76: * on my Tandberg the drive will then lock up (nice huh?) and fail ! 77: * to do anything until the tape is rewound or retensioned. ! 78: */ ! 79: ! 80: ! 81: #define CTDIRTY 0x0001 ! 82: #define CTCLOSING 0x0002 ! 83: ! 84: #define CTILI 0x0020 /* Sensekey's Illegal Length Indicator */ ! 85: #define CTEOM 0x0040 /* Sensekey's End OF Media bit */ ! 86: #define CTFILEMARK 0x0080 /* Sensekey's Filemark bit */ ! 87: #define CTSKMASK (CTILI | CTEOM | CTFILEMARK) ! 88: #define CTRDMD 0x0100 /* we are reading from the tape */ ! 89: #define CTWRTMD 0x0200 /* we are writing to the tape */ ! 90: ! 91: #ifndef HAICACHESZ ! 92: /* ! 93: * There wasn't much of a difference in speed between 32 and 40 block ! 94: * in my experiance so save as much kalloc memory as possible. ! 95: */ ! 96: ! 97: #define HAICACHESZ (32 * BSIZE)/* 32 Block Cache for each device */ ! 98: #endif ! 99: ! 100: #ifndef HAICTVERBOSE ! 101: /* ! 102: * HAICTVERBOSE is done as a define because I wanted to be able to use two ! 103: * tape drives in my system at one time (why?!). ld complains if it is a ! 104: * patchable constant. This problem must be solved but products must also ! 105: * ship. [csh] ! 106: */ ! 107: ! 108: #define HAICTVERBOSE 0x0001 /* Switch console messages on/off */ ! 109: #endif ! 110: ! 111: typedef enum { ! 112: CTIDLE = 0, ! 113: CTINIT, ! 114: CTFBRD, ! 115: CTVBRD, ! 116: CTFBWRT, ! 117: CTVBWRT, ! 118: CTLASTWRT, ! 119: CTSENSE, ! 120: CTWRITEFM, ! 121: CTSPACE, ! 122: CTREWIND, ! 123: CTERASE, ! 124: CTLOADRETEN, ! 125: CTIOCTL ! 126: } ctstate_t; ! 127: ! 128: /* Block Descriptors in the mode sense command. */ ! 129: ! 130: typedef struct blkdscr_s *blkdscr_p; ! 131: ! 132: typedef struct blkdscr_s { ! 133: union { ! 134: unsigned char mediatype; ! 135: unsigned long totalblocks; ! 136: } mt; ! 137: union { ! 138: unsigned char reserved; ! 139: unsigned long blocksize; ! 140: } rb; ! 141: } blkdscr_t; ! 142: ! 143: typedef struct blklim_s *blklim_p; ! 144: ! 145: typedef struct blklim_s { ! 146: unsigned blmax; /* Maximum size for Reads/Writes */ ! 147: unsigned short blmin; /* Minimum size for Reads/Writes */ ! 148: } blklim_t; ! 149: ! 150: typedef struct ctctrl_s *ctctrl_p; ! 151: ! 152: typedef struct ctctrl_s { ! 153: char *cache, /* Transfer Cache */ ! 154: *start; /* Start of data in cache */ ! 155: size_t cachesize, /* Size of cache */ ! 156: avail; /* bytes availaible in cache */ ! 157: ctstate_t state; ! 158: unsigned block, /* Block size of device */ ! 159: blmax; /* Block limits maximum */ ! 160: unsigned short blmin, /* Block Limits minimum */ ! 161: flags, /* Flags from device */ ! 162: inuse; /* In Use flag */ ! 163: srb_t srb; /* SCSI Request block for transfers */ ! 164: } ctctrl_t; ! 165: ! 166: static int ctinit(); /* Initialize a SCSI device at (id) */ ! 167: static void ctopen(); /* Open SCSI tape at (dev) */ ! 168: static void ctclose(); /* Close a SCSI tape at (dev) */ ! 169: static void ctread(); /* Read from SCSI tape at (dev) */ ! 170: static void ctwrite(); /* Write SCSI tape at (dev) */ ! 171: static void ctioctl(); /* I/O Control routine */ ! 172: static int fillcache(); /* Fill the tape cache */ ! 173: static int flushcache(); /* Flush the tape cache */ ! 174: ! 175: extern int nulldev(); ! 176: extern int nonedev(); ! 177: ! 178: #define min(a, b) ((a) < (b) ? (a) : (b)) ! 179: ! 180: #ifdef TDC3600 ! 181: #define ctdca haict3600 ! 182: #endif ! 183: ! 184: dca_t ctdca = { ! 185: ctopen, /* Open */ ! 186: ctclose, /* Close */ ! 187: hainonblk, /* No Block point here but don't just drop Buffers */ ! 188: ctread, /* Read */ ! 189: ctwrite, /* Write */ ! 190: ctioctl, /* Ioctl */ ! 191: ctinit, /* Load */ ! 192: nulldev, /* Unload */ ! 193: nulldev /* Poll */ ! 194: }; ! 195: ! 196: static ctctrl_p ctdevs[MAXDEVS]; ! 197: ! 198: /*********************************************************************** ! 199: * Utility functions. * ! 200: ***********************************************************************/ ! 201: ! 202: #define ctvmsg(l, cmd) { if (HAICTVERBOSE & (l)) { (cmd); } } ! 203: ! 204: /*********************************************************************** ! 205: * ctbusywait() ! 206: * ! 207: * Wait for the tape drive state to return to idle. This is easy ! 208: * for two reasons: 1) With no block entry point its safe to sleep ! 209: * at any time. 2) We shouldn't really need this anyhow. This is ! 210: * unneccessary because without a block routine and with only one ! 211: * process able to open the tape drive at a time the state of the ! 212: * tape drive driver is well defined. So, why is it here you ask? ! 213: * because one day some user might fork a process that owns the tape ! 214: * drive. This would cause 40 days and nights worth of rain etc. ! 215: * Now all that will happen is both processes will be able to write/read ! 216: * from the tape drive and the data that they get will be complete ! 217: * garbage. However, the kernel will not break. ! 218: */ ! 219: ! 220: static int ctbusywait(c, newstate) ! 221: register ctctrl_p c; ! 222: register ctstate_t newstate; ! 223: { ! 224: register int s; ! 225: int retval; ! 226: ! 227: s = sphi(); ! 228: retval = 1; ! 229: while (c->state != CTIDLE) ! 230: if (x_sleep(&(c->srb. status), pritape, slpriSigCatch, "ctbsywt")) { ! 231: u. u_error = EINTR; ! 232: retval = 0; ! 233: break; ! 234: } ! 235: c->state = newstate; ! 236: spl(s); ! 237: return retval; ! 238: } /* ctbusywait() */ ! 239: ! 240: /*********************************************************************** ! 241: * loadtape() ! 242: * ! 243: * Move the tape to the load point. ! 244: */ ! 245: ! 246: static int loadtape(c, opt) ! 247: register ctctrl_p c; ! 248: int opt; ! 249: { ! 250: register srb_p r = &(c->srb); ! 251: register g0cmd_p g0 = &(r->cdb. g0); ! 252: ! 253: if (!ctbusywait(c, CTLOADRETEN)) ! 254: return 0; ! 255: r->buf. space = (int) r->buf. addr. caddr = r->buf. size = 0; ! 256: r->xferdir = 0; ! 257: r->timeout = 300; ! 258: memset(g0, 0, sizeof(cdb_t)); ! 259: g0->opcode = LOAD; ! 260: g0->xfr_len = 1; /* Move tape to load point. */ ! 261: if (opt & IMMEDIATE) ! 262: g0->lun_lba |= 1; ! 263: if (opt & RETENSION) ! 264: g0->xfr_len |= 2; ! 265: doscsi(r, 4, pritape, slpriSigCatch, "loadtape"); ! 266: if (r->status != ST_GOOD && printerror(r, "Load failed")) ! 267: u. u_error = EIO; ! 268: ! 269: c->state = CTIDLE; ! 270: c->flags &= ~(CTFILEMARK | CTEOM); ! 271: return (r->status == ST_GOOD); ! 272: } /* loadtape() */ ! 273: ! 274: /*********************************************************************** ! 275: * writefm() ! 276: * ! 277: * Write Filemarks on the tape. ! 278: */ ! 279: ! 280: static void writefm(c, count) ! 281: register ctctrl_p c; ! 282: int count; ! 283: { ! 284: register srb_p r = &(c->srb); ! 285: register g0cmd_p g0 = &(r->cdb. g0); ! 286: ! 287: if (!ctbusywait(c, CTWRITEFM)) ! 288: return; ! 289: r->buf. space = (int) r->buf. addr. caddr = r->buf. size = 0; ! 290: r->xferdir = 0; ! 291: r->timeout = 40; ! 292: g0->opcode = WRITEFM; ! 293: g0->lun_lba = (r->lun << 5); ! 294: g0->lba_mid = ((unsigned char *) &count)[2]; ! 295: g0->lba_low = ((unsigned char *) &count)[1]; ! 296: g0->xfr_len = ((unsigned char *) &count)[0]; ! 297: g0->control = 0; ! 298: doscsi(r, 4, pritape, slpriSigCatch, "writefm"); ! 299: if (r->status != ST_GOOD && printerror(r, "Write filemarks failed")) ! 300: u. u_error = EIO; ! 301: c->state = CTIDLE; ! 302: } /* writefm() */ ! 303: ! 304: /*********************************************************************** ! 305: * space() ! 306: * ! 307: * Space over blocks/filemarks/etc. ! 308: */ ! 309: ! 310: static void space(c, count, object) ! 311: register ctctrl_p c; ! 312: int count; ! 313: int object; ! 314: { ! 315: register srb_p r = &(c->srb); ! 316: register g0cmd_p g0 = &(r->cdb. g0); ! 317: ! 318: if (!ctbusywait(c, CTSPACE)) ! 319: return; ! 320: r->buf. space = (int) r->buf. addr. caddr = r->buf. size = 0; ! 321: r->xferdir = 0; ! 322: r->timeout = 300; ! 323: g0->opcode = SPACE; ! 324: g0->lun_lba = (r->lun << 5) | (object & 3); ! 325: g0->lba_mid = ((unsigned char *) &count)[2]; ! 326: g0->lba_low = ((unsigned char *) &count)[1]; ! 327: g0->xfr_len = ((unsigned char *) &count)[0]; ! 328: g0->control = 0; ! 329: doscsi(r, 2, pritape, slpriSigCatch, "space"); ! 330: if (r->status != ST_GOOD && printerror(r, "Space failed")) ! 331: u. u_error = EIO; ! 332: c->state = CTIDLE; ! 333: } /* space() */ ! 334: ! 335: /*********************************************************************** ! 336: * rewind() ! 337: * ! 338: * Rewind the tape drive back to the load point. ! 339: */ ! 340: ! 341: static void rewind(c, wait) ! 342: register ctctrl_p c; ! 343: int wait; ! 344: { ! 345: register srb_p r = &(c->srb); ! 346: register g0cmd_p g0 = &(r->cdb. g0); ! 347: ! 348: if (!ctbusywait(c, CTREWIND)) ! 349: return; ! 350: r->buf. space = (int) r->buf. addr. caddr = r->buf. size = 0; ! 351: r->timeout = 300; ! 352: r->xferdir = 0; ! 353: memset(g0, 0, sizeof(cdb_t)); ! 354: g0->opcode = REWINDTAPE; ! 355: if (!wait) ! 356: g0->lun_lba = (r->lun << 5) | 1; ! 357: doscsi(r, 2, pritape, slpriSigCatch, "rewind"); ! 358: if (r->status != ST_GOOD && printerror(r, "Rewind failed")) ! 359: u. u_error = EIO; ! 360: c->flags = 0; ! 361: c->state = CTIDLE; ! 362: } /* rewind() */ ! 363: ! 364: static void erase(c, to_eot) ! 365: register ctctrl_p c; ! 366: int to_eot; ! 367: { ! 368: register srb_p r = &(c->srb); ! 369: register g0cmd_p g0 = &(r->cdb. g0); ! 370: ! 371: if (!ctbusywait(c, CTERASE)) ! 372: return; ! 373: r->buf. space = (int) r->buf. addr. caddr = r->buf. size = 0; ! 374: r->timeout = 300; ! 375: r->xferdir = 0; ! 376: memset(g0, 0, sizeof(cdb_t)); ! 377: g0->opcode = ERASE; ! 378: g0->lun_lba = (r->lun << 5); ! 379: if (to_eot) ! 380: g0->lun_lba |= 1; ! 381: doscsi(r, 2, pritape, slpriSigCatch, "erase"); ! 382: if (r->status != ST_GOOD && printerror(r, "Erase failed")) ! 383: u. u_error = EIO; ! 384: if (to_eot) ! 385: c->flags &= ~(CTFILEMARK | CTEOM | CTILI | CTDIRTY); ! 386: c->state = CTIDLE; ! 387: } /* erase() */ ! 388: ! 389: /*********************************************************************** ! 390: * Device Driver Entry Point routines. * ! 391: ***********************************************************************/ ! 392: ! 393: /*********************************************************************** ! 394: * ctinit() ! 395: * ! 396: * Initialize the tape device at (id). This doesn't do anything, ! 397: * not even verify that the drive is there because it could be powered ! 398: * off. ! 399: */ ! 400: ! 401: static int ctinit(id) ! 402: register int id; ! 403: { ! 404: register ctctrl_p c = kalloc(sizeof(ctctrl_t)); ! 405: ! 406: if (!c) { ! 407: printf("\tTape Driver: Could not allocate control structure.\n"); ! 408: return 0; ! 409: } ! 410: ! 411: printf("\tCoherent SCSI Tape driver v1.1\n"); ! 412: memset(c, 0, sizeof(ctctrl_t)); ! 413: c->inuse = 0; ! 414: c->cache = c->start = NULL; ! 415: c->cachesize = HAICACHESZ; ! 416: c->srb. target = id; ! 417: c->srb. lun = 0; ! 418: c->state = CTIDLE; ! 419: ctdevs[id] = c; ! 420: return 1; ! 421: } ! 422: ! 423: static void ctopen(dev, mode) ! 424: dev_t dev; ! 425: int mode; ! 426: { ! 427: register ctctrl_p c = ctdevs[tid(dev)]; ! 428: register srb_p r = &(c->srb); ! 429: register g0cmd_p g0 = &(r->cdb. g0); ! 430: int rblerf; /* read block limits error flag */ ! 431: int s; ! 432: char buf[64]; ! 433: blkdscr_p bd = (blkdscr_p) (buf + 4); ! 434: blklim_p bl = (blklim_p) (buf); ! 435: ! 436: if (!c) { ! 437: u. u_error = ENXIO; ! 438: return; ! 439: } ! 440: if ((mode != IPR) && (mode != IPW)) { ! 441: u. u_error = EINVAL; ! 442: return; ! 443: } ! 444: ! 445: s = sphi(); ! 446: ! 447: if (c->inuse) { ! 448: u. u_error = EDBUSY; ! 449: goto done; ! 450: } ! 451: ! 452: c->inuse = 1; ! 453: c->state = CTINIT; ! 454: r->dev = dev; /* Save the rewind bit for close. */ ! 455: ! 456: /*********************************************************************** ! 457: * Repeat the test unit ready command to the tape drive. This tells ! 458: * the state of the tape drive. Repeating the command also clears out ! 459: * the request sense condition which comes after each time the user ! 460: * changes tapes. ! 461: */ ! 462: ! 463: r->timeout = 2; ! 464: r->buf. space = (int) r->buf. addr. caddr = r->buf. size = 0; ! 465: r->xferdir = 0; ! 466: memset(g0, 0, sizeof(cdb_t)); /* Test Unit Ready */ ! 467: memset(r->sensebuf, 0, sizeof(r->sensebuf)); ! 468: doscsi(r, 4, pritape, slpriSigCatch, "ctopen"); ! 469: ! 470: /*********************************************************************** ! 471: * If the command fails there probably wasn't a tape in the drive. ! 472: */ ! 473: ! 474: if (r->status != ST_GOOD) { /* Is there a tape in the drive? */ ! 475: if (r->status != ST_USRABRT) { ! 476: /* Otherwise assume no tape. */ ! 477: u. u_error = ENXIO; ! 478: devmsg(r->dev, "Tape drive not ready."); ! 479: goto openfailed; ! 480: } ! 481: } ! 482: #ifdef TDC3600 ! 483: ! 484: /*********************************************************************** ! 485: * TDC3600's do a retension after all bus device resets and tape changes ! 486: * so I block in the open routine waiting for that operation to complete. ! 487: * This is a kludge to fix the author's system. I anticipate that ! 488: * other host adapters may not be so kind about retrying commands ! 489: * until a device reports that it is no longer busy. Hence the error ! 490: * message when this command fails. All this really does is allows ! 491: * me to safely start applications while the initial retension is still ! 492: * happening [csh]. ! 493: */ ! 494: ! 495: else { ! 496: if (r->sensebuf[0] == 0x70 && r->sensebuf[2] == 0x06) { ! 497: ! 498: /* Yep, The user just put it there */ ! 499: ! 500: r->buf. space = (int) r->buf. addr. caddr = r->buf. size = 0; ! 501: r->xferdir = 0; ! 502: r->timeout = 300; ! 503: memset(g0, 0, sizeof(cdb_t)); ! 504: g0->opcode = LOAD; ! 505: g0->xfr_len = 1; /* Move tape to load point. */ ! 506: doscsi(r, 4, pritape, slpriSigCatch, "ctopen"); ! 507: if (r->status != ST_GOOD && ! 508: printerror(r, "Load failed - TDC3600 not ready")) { ! 509: u. u_error = ENXIO; ! 510: goto openfailed; ! 511: } ! 512: } ! 513: } ! 514: #endif ! 515: ! 516: ctvmsg(0x0100, (devmsg(r->dev, "Read block limits."))); ! 517: #ifdef TDC3600 ! 518: c->blmin = c->blmax = 512; ! 519: ctvmsg(0x0100, (devmsg(r->dev, "Tandberg TDC36XX - Block limits set"))); ! 520: rblerf = 0; ! 521: #else ! 522: r->buf. space = KRNL_ADDR; ! 523: r->buf. addr. caddr = (caddr_t) bl; ! 524: r->buf. size = sizeof(blklim_t); ! 525: r->xferdir = DMAREAD; ! 526: r->timeout = 2; ! 527: memset(g0, 0, sizeof(cdb_t)); ! 528: g0->opcode = READBLKLMT; ! 529: g0->xfr_len = 6; ! 530: doscsi(r, 3, pritape, slpriSigCatch, "ctopen"); ! 531: ! 532: /*********************************************************************** ! 533: * This is a bit oblique: The Read Block limits is used to determine ! 534: * what the tape drive that we have can do. I have yet to run into ! 535: * a tape drive that can do variable mode yet so I'm just winging ! 536: * this. In any case all I did was make it okay for the Archive Tape ! 537: * drives to say "I don't support that command". ! 538: */ ! 539: ! 540: if (rblerf = (r->status != ST_GOOD)) { ! 541: ctvmsg(0x0010, (printerror(r, "Read Block LImits"))); ! 542: c->blmax = c->blmin = 0; ! 543: } ! 544: else { ! 545: flip(bl->blmax); /* SCSI to INTEL order */ ! 546: flip(bl->blmin); /* Ditto */ ! 547: c->blmax = (bl->blmax & 0x00ffffff); ! 548: c->blmin = bl->blmin; ! 549: } ! 550: #endif ! 551: ! 552: /*********************************************************************** ! 553: * Use mode sense to find out if the tape is write protected and also ! 554: * to find out what the current tape blocksize is. ! 555: */ ! 556: ! 557: r->buf. space = KRNL_ADDR; ! 558: r->buf. addr. caddr = (caddr_t) buf; ! 559: r->buf. size = sizeof(buf); ! 560: r->xferdir = DMAREAD; ! 561: r->timeout = 2; ! 562: memset(g0, 0, sizeof(cdb_t)); ! 563: g0->opcode = MODESENSE; ! 564: g0->xfr_len = sizeof(buf); ! 565: doscsi(r, 3, pritape, slpriSigCatch, "ctopen"); ! 566: if (r->status != ST_GOOD) { ! 567: if (printerror(r, "Mode sense failed")) ! 568: u. u_error = EIO; ! 569: goto openfailed; ! 570: } ! 571: ! 572: /*********************************************************************** ! 573: * If tape drive opened in write mode make sure the tape is not write ! 574: * protected now. ! 575: */ ! 576: ! 577: if (mode == IPW && (buf[2] & 0x80) != 0) { ! 578: devmsg(dev, "Tape is write protected"); ! 579: u. u_error = ENXIO; ! 580: goto openfailed; ! 581: } ! 582: ! 583: /*********************************************************************** ! 584: * If mode sense returned any media descriptors take the first one, ! 585: * it's the default, and use it. ! 586: * ! 587: * There are two possible pitfalls here: ! 588: * ! 589: * 1) According to SCSI-1 it not an error to return zero block ! 590: * descriptors. If you're favorite drive returns zero block descriptors ! 591: * this driver will fail here. ! 592: * ! 593: * 2) I am assuming that the first media descriptor returned is ! 594: * the "current" media type which the drive expects to use. This ! 595: * works with SCSI-2 but SCSI-1 says that the first descriptor is ! 596: * the "default" media type. SCSI-1 is lacking in the definition ! 597: * of "default" for my conservative taste (when programming is ! 598: * concerned). ! 599: * ! 600: * I would like to put the issue of supporting real oddball tape drives ! 601: * like my Tandberg to rest and only have one source source file for ! 602: * scsi tape drives but I don't think that will work. In the future ! 603: * there will probably be two or maybe three if DAT proves to be as ! 604: * much fun as this is. ! 605: */ ! 606: ! 607: if (buf[3]) { /* If mode sense returned any media descriptors */ ! 608: bd->rb. blocksize &= 0xffffff00; ! 609: flip(bd->rb. blocksize); ! 610: c->block = bd->rb. blocksize; ! 611: if (c->block && c->cachesize % c->block) ! 612: c->cachesize -= (c->cachesize % c->block); ! 613: } ! 614: else { ! 615: devmsg(r->dev, "No media descriptors: Contact Mark Williams Tech support"); ! 616: u. u_error = ENXIO; ! 617: goto openfailed; ! 618: } ! 619: ctvmsg(0x0001, devmsg(dev, "Blocksize: %d bytes.", c->block)); ! 620: ! 621: /*********************************************************************** ! 622: * One last check: If we aren't using block mode (!c->block) ! 623: * and we didn't get any block limits then we cannot support this ! 624: * drive. ! 625: */ ! 626: ! 627: if (!c->block && rblerf) { ! 628: devmsg(r->dev, "<No block limits on variable mode tape drive>"); ! 629: devmsg(r->dev, "<Contact Mark Williams Tech Support>"); ! 630: u. u_error = ENXIO; ! 631: goto openfailed; ! 632: } ! 633: ! 634: c->flags = (mode == IPR) ? CTRDMD : CTWRTMD; ! 635: if (c->block) { ! 636: c->cache = kalloc(HAICACHESZ); ! 637: if (!c->cache) { ! 638: devmsg(dev, "Could not allocate tape cache"); ! 639: u. u_error = ENOMEM; ! 640: goto openfailed; ! 641: } ! 642: c->avail = (c->flags & CTRDMD) ? 0 : HAICACHESZ; ! 643: c->start = c->cache; ! 644: } ! 645: c->state = CTIDLE; ! 646: goto done; ! 647: ! 648: openfailed: ! 649: c->state = CTIDLE; ! 650: c->inuse = 0; ! 651: ! 652: done: ! 653: spl(s); ! 654: } /* ctopen() */ ! 655: ! 656: /*********************************************************************** ! 657: * ctclose() ! 658: * ! 659: * Close the SCSI Device at (dev). ! 660: */ ! 661: ! 662: static void ctclose(dev) ! 663: register dev_t dev; ! 664: { ! 665: register ctctrl_p c = ctdevs[tid(dev)]; ! 666: register srb_p r = &(c->srb); ! 667: int s; ! 668: ! 669: if (!c) { ! 670: u. u_error = ENXIO; ! 671: return; ! 672: } ! 673: ! 674: s = sphi(); ! 675: if (c->block && (c->flags & CTWRTMD)) { ! 676: if (ctbusywait(c, CTLASTWRT)) ! 677: flushcache(c); ! 678: c->state = CTIDLE; ! 679: } ! 680: spl(s); ! 681: if (c->cache) { ! 682: kfree(c->cache); ! 683: c->cache = c->start = NULL; ! 684: c->avail = 0; ! 685: } ! 686: ! 687: #ifndef TDC3600 ! 688: /* ! 689: * Write two filemarks (Logical End of Tape) and then skip backwards ! 690: * over one of them to ready for the another write operation on the ! 691: * no rewind device. This code guarantees properly formed tapes according ! 692: * to the wisened old nine-track hacker that I work with. The problem ! 693: * is that... ! 694: */ ! 695: if (c->flags & CTDIRTY) { ! 696: writefm(c, 1); /* Write a file mark */ ! 697: writefm(c, 1); /* Write another filemark */ ! 698: space(c, -1, SKIP_FM); /* Go back to the first filemark */ ! 699: } ! 700: #else ! 701: /* ! 702: * ... problem is that my Tandberg considers it an error to do anything ! 703: * after it has skipped past a filemark. So all commands except load and ! 704: * rewind fail after the previous code. The following will work in ! 705: * all situations but there is a risk that a user's tapes will only ! 706: * have one filemark at Logical End-of-Tape if the user isn't careful ! 707: * to use the rewind device the last time he uses tape drive. This is ! 708: * only a problem with drives which insist upon doing a Retension each ! 709: * time the tape is changed or the drive gets reset. ! 710: */ ! 711: if (c->flags & CTDIRTY) { ! 712: writefm(c, 1); ! 713: if (r->dev & REWIND) ! 714: writefm(c, 1); ! 715: } ! 716: #endif ! 717: ! 718: if (r->dev & REWIND) ! 719: rewind(c, 0); ! 720: ! 721: c->inuse = 0; ! 722: return; ! 723: } /* ctclose() */ ! 724: ! 725: /*********************************************************************** ! 726: * fillcache() -- Read from the tape into the cache (really?) ! 727: * ! 728: * return 0 and set u. u_error on any errors. ! 729: */ ! 730: ! 731: static int fillcache(c) ! 732: register ctctrl_p c; ! 733: { ! 734: register srb_p r = (&c->srb); ! 735: register g0cmd_p g0 = &(r->cdb. g0); ! 736: size_t blocks; ! 737: extsense_p e; ! 738: int info; ! 739: int retval = 0; ! 740: ! 741: r->buf. space = KRNL_ADDR; ! 742: r->buf. addr. caddr = (caddr_t) c->cache; ! 743: r->buf. size = HAICACHESZ; ! 744: r->xferdir = DMAREAD; ! 745: r->timeout = 30; ! 746: r->tries = 0; ! 747: g0->opcode = READ; ! 748: g0->lun_lba = (r->lun << 5) | 1; ! 749: blocks = HAICACHESZ / c->block; ! 750: g0->lba_mid = ((unsigned char *) &blocks)[2]; ! 751: g0->lba_low = ((unsigned char *) &blocks)[1]; ! 752: g0->xfr_len = ((unsigned char *) &blocks)[0]; ! 753: g0->control = 0; ! 754: doscsi(r, 1, pritape, slpriSigCatch, "ctblkrd"); ! 755: switch (r->status) { ! 756: case ST_GOOD: ! 757: c->start = c->cache; ! 758: c->avail = r->buf. size; ! 759: retval = 1; ! 760: break; ! 761: case ST_CHKCOND: ! 762: e = r->sensebuf; ! 763: if ((e->errorcode & 0x70) == 0x70) { ! 764: info = 0; ! 765: if (e->errorcode & 0x80) { ! 766: info = e->info; ! 767: flip(info); ! 768: } ! 769: if (e->sensekey & (CTFILEMARK | CTEOM)) { ! 770: c->flags |= (e->sensekey & (CTFILEMARK | CTEOM)); ! 771: c->start = c->cache; ! 772: c->avail = HAICACHESZ - (info * c->block); ! 773: retval = 1; ! 774: break; ! 775: } ! 776: } ! 777: printsense(r->dev, "Read failed", r->sensebuf); ! 778: u. u_error = EIO; ! 779: retval = 0; ! 780: break; ! 781: case ST_USRABRT: ! 782: u. u_error = EINTR; ! 783: c->start = c->cache; ! 784: c->avail = 0; ! 785: retval = 0; ! 786: break; ! 787: default: ! 788: devmsg(r->dev, "Read failed: status (0x%x)", r->status); ! 789: u. u_error = EIO; ! 790: retval = 0; ! 791: break; ! 792: } ! 793: return retval; ! 794: } /* fillcache() */ ! 795: ! 796: /*********************************************************************** ! 797: * ctfbrd() -- Fixed block read handler. Reads from the tape ! 798: * drive through the cache when the tape drive is ! 799: * in fixed block mode. ! 800: */ ! 801: ! 802: static void ctfbrd(c, iop) ! 803: register ctctrl_p c; ! 804: register IO *iop; ! 805: { ! 806: register size_t reqcount, /* Total bytes transfered toward request */ ! 807: xfrsize; /* Current transfer size */ ! 808: size_t total, /* System global memory total transfer size */ ! 809: size; /* System global memory current transfer size */ ! 810: ! 811: if (!ctbusywait(c, CTFBRD)) ! 812: return; ! 813: reqcount = 0; ! 814: while (iop->io_ioc) { ! 815: xfrsize = min(c->avail, iop->io_ioc); ! 816: if (xfrsize > 0) { ! 817: switch (iop->io_seg) { ! 818: case IOSYS: ! 819: memcpy(iop->io. vbase + reqcount, c->start, xfrsize); ! 820: break; ! 821: case IOUSR: ! 822: kucopy(c->start, iop->io. vbase + reqcount, xfrsize); ! 823: break; ! 824: case IOPHY: ! 825: total = 0; ! 826: while (total < xfrsize) { ! 827: size = min(xfrsize - total, NBPC); ! 828: xpcopy(c->start + total, ! 829: iop->io. pbase + reqcount + total, ! 830: size, ! 831: SEG_386_KD | SEG_VIRT); ! 832: total += size; ! 833: } ! 834: break; ! 835: } ! 836: c->start += xfrsize; ! 837: c->avail -= xfrsize; ! 838: reqcount += xfrsize; ! 839: iop->io_ioc -= xfrsize; ! 840: } ! 841: if (iop->io_ioc) { ! 842: if (c->flags & CTFILEMARK) { ! 843: c->flags &= ~CTFILEMARK; ! 844: break; ! 845: } ! 846: ! 847: if (c->flags & CTEOM) { ! 848: u. u_error = EIO; ! 849: break; ! 850: } ! 851: ! 852: if (!fillcache(c)) ! 853: break; ! 854: } ! 855: } /* while */ ! 856: c->state = CTIDLE; ! 857: } /* ctfbrd() */ ! 858: ! 859: /*********************************************************************** ! 860: * ctvbrd() -- Variable block read entry point. ! 861: */ ! 862: ! 863: static void ctvbrd(c, iop) ! 864: register ctctrl_p c; ! 865: IO *iop; ! 866: { ! 867: register srb_p r = &(c->srb); ! 868: register g0cmd_p g0 = &(r->cdb. g0); ! 869: size_t xfrsize; ! 870: extsense_p e; ! 871: int info; ! 872: ! 873: if (!ctbusywait(c, CTVBRD)) ! 874: return; ! 875: ! 876: if (c->flags & CTEOM) { ! 877: u. u_error = EIO; ! 878: return; ! 879: } ! 880: if (c->flags & CTFILEMARK) { ! 881: c->flags &= ~CTFILEMARK; ! 882: return; ! 883: } ! 884: switch (iop->io_seg) { ! 885: case IOSYS: ! 886: r->buf. space = KRNL_ADDR; ! 887: r->buf. addr. caddr = iop->io. vbase; ! 888: break; ! 889: case IOUSR: ! 890: r->buf. space = USER_ADDR; ! 891: r->buf. addr. caddr = iop->io. vbase; ! 892: break; ! 893: case IOPHY: ! 894: r->buf. space = PHYS_ADDR; ! 895: r->buf. addr. paddr = iop->io. pbase; ! 896: break; ! 897: } ! 898: r->buf. size = xfrsize = iop->io_ioc; ! 899: r->xferdir = DMAREAD; ! 900: r->timeout = 30; ! 901: g0->opcode = READ; ! 902: g0->lun_lba = (r->lun << 5); ! 903: g0->lba_mid = ((unsigned char *) &xfrsize)[2]; ! 904: g0->lba_low = ((unsigned char *) &xfrsize)[1]; ! 905: g0->xfr_len = ((unsigned char *) &xfrsize)[0]; ! 906: g0->control = 0; ! 907: doscsi(r, 1, pritape, slpriSigCatch, "ctvbrd"); ! 908: switch (r->status) { ! 909: case ST_GOOD: ! 910: iop->io_ioc -= r->buf. size; ! 911: break; ! 912: case ST_CHKCOND: ! 913: e = r->sensebuf; ! 914: if ((e->errorcode & 0x70) == 0x70) { ! 915: info = 0; ! 916: if (e->errorcode & 0x80) { ! 917: info = (long) e->info; ! 918: flip(info); ! 919: } ! 920: if (e->sensekey & (CTFILEMARK | CTEOM)) { ! 921: c->flags |= (e->sensekey & (CTFILEMARK | CTEOM)); ! 922: break; ! 923: } ! 924: else if (e->sensekey & CTILI) { ! 925: devmsg(r->dev, ! 926: "Read failed buffer size %d blocksize %d", ! 927: xfrsize, ! 928: xfrsize - info); ! 929: if (info > 0) ! 930: iop->io_ioc -= (xfrsize - info); ! 931: else ! 932: u. u_error = EIO; ! 933: break; ! 934: } ! 935: } ! 936: printsense(r->dev, "Read failed", r->sensebuf); ! 937: u. u_error = EIO; ! 938: break; ! 939: case ST_USRABRT: ! 940: break; ! 941: default: ! 942: devmsg(r->dev, "Read failed: status (0x%x)", r->status); ! 943: u. u_error = EIO; ! 944: break; ! 945: } ! 946: c->state = CTIDLE; ! 947: } /* ctvbrd() */ ! 948: ! 949: /*********************************************************************** ! 950: * ctread() -- OS Read entry point. ! 951: */ ! 952: ! 953: static void ctread(dev, iop) ! 954: dev_t dev; ! 955: register IO *iop; ! 956: { ! 957: register ctctrl_p c = ctdevs[tid(dev)]; ! 958: ! 959: if (!c) { ! 960: u. u_error = EINVAL; ! 961: return; ! 962: } ! 963: ! 964: if (c->block) ! 965: ctfbrd(c, iop); ! 966: else ! 967: ctvbrd(c, iop); ! 968: } /* ctread() */ ! 969: ! 970: /*********************************************************************** ! 971: * flushcache() -- flush the data in the cache to the tape. ! 972: * ! 973: * returns 0 and sets u. u_error on failure else returns 1. ! 974: */ ! 975: ! 976: static int flushcache(c) ! 977: register ctctrl_p c; ! 978: { ! 979: register srb_p r = (&c->srb); ! 980: register g0cmd_p g0 = &(r->cdb. g0); ! 981: size_t xfrsize; ! 982: extsense_p e; ! 983: int info; ! 984: int retval = 0; ! 985: ! 986: if (c->avail >= HAICACHESZ) ! 987: return 1; ! 988: ! 989: r->buf. space = KRNL_ADDR; ! 990: r->buf. addr. caddr = (caddr_t) c->cache; ! 991: r->buf. size = xfrsize = HAICACHESZ - c->avail; ! 992: r->xferdir = DMAWRITE; ! 993: r->timeout = 30; ! 994: r->tries = 0; ! 995: g0->opcode = WRITE; ! 996: g0->lun_lba = (r->lun << 5); ! 997: if (c->block) { ! 998: g0->lun_lba |= 1; ! 999: xfrsize = (xfrsize + c->block - 1) / c->block; ! 1000: } ! 1001: g0->lba_mid = ((unsigned char *) &xfrsize)[2]; ! 1002: g0->lba_low = ((unsigned char *) &xfrsize)[1]; ! 1003: g0->xfr_len = ((unsigned char *) &xfrsize)[0]; ! 1004: g0->control = 0; ! 1005: doscsi(r, 1, pritape, slpriSigCatch, "ctblkwrt"); ! 1006: switch (r->status) { ! 1007: case ST_GOOD: ! 1008: c->start = c->cache; ! 1009: c->avail = HAICACHESZ; ! 1010: retval = 1; ! 1011: break; ! 1012: case ST_CHKCOND: ! 1013: e = r->sensebuf; ! 1014: if ((e->errorcode & 0x70) == 0x70) { ! 1015: info = 0; ! 1016: if (e->errorcode & 0x80) { ! 1017: info = e->info; ! 1018: flip(info); ! 1019: } ! 1020: if (e->sensekey & CTEOM) { ! 1021: c->flags |= CTEOM; ! 1022: devmsg(r->dev, "End of tape on block write"); ! 1023: } ! 1024: } ! 1025: printsense(r->dev, "Read failed", r->sensebuf); ! 1026: u. u_error = EIO; ! 1027: retval = 0; ! 1028: break; ! 1029: case ST_USRABRT: ! 1030: retval = 0; ! 1031: break; ! 1032: default: ! 1033: devmsg(r->dev, "Read failed: status (0x%x)", r->status); ! 1034: u. u_error = EIO; ! 1035: retval = 0; ! 1036: break; ! 1037: } ! 1038: return retval; ! 1039: } /* flushcache() */ ! 1040: ! 1041: /*********************************************************************** ! 1042: * ctfbwrt() -- Fixed block write. This should be fast because ! 1043: * it uses the tapes drives optimum setting and it ! 1044: * goes through a cache. ! 1045: */ ! 1046: ! 1047: static void ctfbwrt(c, iop) ! 1048: register ctctrl_p c; ! 1049: register IO *iop; ! 1050: { ! 1051: register size_t reqcount, /* Total bytes transfered */ ! 1052: xfrsize; /* Current transfer size */ ! 1053: size_t total, /* System global memory total transfer size */ ! 1054: size; /* System global memory current transfer size */ ! 1055: ! 1056: if (!ctbusywait(c, CTFBWRT)) ! 1057: return; ! 1058: ! 1059: reqcount = 0; ! 1060: while (iop->io_ioc) { ! 1061: xfrsize = min(c->avail, iop->io_ioc); ! 1062: if (xfrsize) { ! 1063: switch (iop->io_seg) { ! 1064: case IOSYS: ! 1065: memcpy(c->start, iop->io. vbase + reqcount, xfrsize); ! 1066: break; ! 1067: case IOUSR: ! 1068: ukcopy(iop->io. vbase + reqcount, c->start, xfrsize); ! 1069: break; ! 1070: case IOPHY: ! 1071: total = 0; ! 1072: while (total < xfrsize) { ! 1073: size = min(xfrsize - total, NBPC); ! 1074: pxcopy(iop->io. pbase + reqcount + total, ! 1075: c->start + total, ! 1076: size, ! 1077: SEG_386_KD | SEG_VIRT); ! 1078: total += size; ! 1079: } ! 1080: break; ! 1081: } ! 1082: c->start += xfrsize; ! 1083: c->avail -= xfrsize; ! 1084: reqcount += xfrsize; ! 1085: iop->io_ioc -= xfrsize; ! 1086: } ! 1087: if (iop->io_ioc) { ! 1088: if (!flushcache(c)) ! 1089: break; ! 1090: } ! 1091: } /* while */ ! 1092: c->state = CTIDLE; ! 1093: } /* ctfbwrt() */ ! 1094: ! 1095: /*********************************************************************** ! 1096: * ctvbwrt() -- Variable block writes. ! 1097: */ ! 1098: ! 1099: static void ctvbwrt(c, iop) ! 1100: register ctctrl_p c; ! 1101: register IO *iop; ! 1102: { ! 1103: register srb_p r = &(c->srb); ! 1104: register g0cmd_p g0 = &(r->cdb. g0); ! 1105: size_t xfrsize; ! 1106: extsense_p e; ! 1107: int info; ! 1108: ! 1109: if (!ctbusywait(c, CTVBWRT)) ! 1110: return; ! 1111: ! 1112: if (c->blmax && iop->io_ioc > c->blmax) { ! 1113: devmsg(r->dev, "Tape Error: maximum read/write size is %d bytes.", c->blmax); ! 1114: u. u_error = EIO; ! 1115: return; ! 1116: } ! 1117: switch (iop->io_seg) { ! 1118: case IOSYS: ! 1119: r->buf. space = KRNL_ADDR; ! 1120: r->buf. addr. caddr = iop->io. vbase; ! 1121: break; ! 1122: case IOUSR: ! 1123: r->buf. space = USER_ADDR; ! 1124: r->buf. addr. caddr = iop->io. vbase; ! 1125: break; ! 1126: case IOPHY: ! 1127: r->buf. space = PHYS_ADDR; ! 1128: r->buf. addr. paddr = iop->io. pbase; ! 1129: break; ! 1130: } ! 1131: xfrsize = min(iop->io_ioc, c->blmin); ! 1132: r->buf. size = xfrsize; ! 1133: r->xferdir = DMAWRITE; ! 1134: r->timeout = 30; ! 1135: g0->opcode = WRITE; ! 1136: g0->lun_lba = (r->lun << 5); ! 1137: g0->lba_mid = ((unsigned char *) &xfrsize)[2]; ! 1138: g0->lba_low = ((unsigned char *) &xfrsize)[1]; ! 1139: g0->xfr_len = ((unsigned char *) &xfrsize)[0]; ! 1140: g0->control = 0; ! 1141: doscsi(r, 1, pritape, slpriSigCatch, "ctvbwrt"); ! 1142: switch (r->status) { ! 1143: case ST_GOOD: ! 1144: iop->io_ioc -= r->buf. size; ! 1145: break; ! 1146: case ST_CHKCOND: ! 1147: e = r->sensebuf; ! 1148: if ((e->errorcode & 0x70) == 0x70) { ! 1149: info = 0; ! 1150: if (e->errorcode & 0x80) { ! 1151: info = (long) e->info; ! 1152: flip(info); ! 1153: } ! 1154: if (e->sensekey & CTEOM) { ! 1155: c->flags |= CTEOM; ! 1156: devmsg(r->dev, "End of tape"); ! 1157: } ! 1158: } ! 1159: printsense(r->dev, "Write failed", r->sensebuf); ! 1160: u. u_error = EIO; ! 1161: break; ! 1162: case ST_USRABRT: ! 1163: break; ! 1164: default: ! 1165: devmsg(r->dev, "Read failed: status (0x%x)", r->status); ! 1166: u. u_error = EIO; ! 1167: break; ! 1168: } ! 1169: c->state = CTIDLE; ! 1170: } /* ctvbwrt() */ ! 1171: ! 1172: /*********************************************************************** ! 1173: * ctwrite() -- Write entry point for tape drive. ! 1174: */ ! 1175: ! 1176: static void ctwrite(dev, iop) ! 1177: register dev_t dev; ! 1178: register IO *iop; ! 1179: { ! 1180: register ctctrl_p c = ctdevs[tid(dev)]; ! 1181: ! 1182: if (!c) { ! 1183: u. u_error = ENXIO; ! 1184: return; ! 1185: } ! 1186: ! 1187: c->flags |= CTDIRTY; ! 1188: if (c->block) ! 1189: ctfbwrt(c, iop); ! 1190: else ! 1191: ctvbwrt(c, iop); ! 1192: } /* ctwrite() */ ! 1193: ! 1194: /*********************************************************************** ! 1195: * ctioctl() ! 1196: * ! 1197: * I/O Control Entry point for Cartridge tape devices. ! 1198: * ! 1199: * This function had been modified to allow applications level programs ! 1200: * to select modes and features for the tape drive. As stated above, ! 1201: * the philosophy of this driver is to provide least common denominator ! 1202: * support for all tape drives. I know that you spend big bucks to ! 1203: * get that (insert your favorite drive brand/model). If I decide ! 1204: * to support everything out there on the market then I won't be able ! 1205: * to write network drivers, serial drivers, etc. So if you need ! 1206: * to do something to the tape drive to make it work (mode sense/select) ! 1207: * you can do it through this ctioctl as an applications program. ! 1208: */ ! 1209: ! 1210: static void ctioctl(dev, cmd, vec) ! 1211: dev_t dev; ! 1212: register int cmd; ! 1213: char *vec; ! 1214: { ! 1215: register ctctrl_p c = ctdevs[tid(dev)]; ! 1216: register srb_p r = &(c->srb); ! 1217: int s; ! 1218: ! 1219: if (!c) { ! 1220: u. u_error = EINVAL; ! 1221: return; ! 1222: } ! 1223: ! 1224: switch (cmd) { ! 1225: case MTREWIND: /* Rewind */ ! 1226: rewind(c, 1); ! 1227: break; ! 1228: case MTWEOF: /* Write end of file mark */ ! 1229: writefm(c, 1); ! 1230: break; ! 1231: case MTRSKIP: /* Record skip */ ! 1232: space(c, 1, SKIP_BLK); ! 1233: break; ! 1234: case MTFSKIP: /* File skip */ ! 1235: space(c, 1, SKIP_FM); ! 1236: break; ! 1237: case MTTENSE: /* Tension tape */ ! 1238: loadtape(c, RETENSION); ! 1239: break; ! 1240: case MTERASE: /* Erase tape */ ! 1241: erase(c, ERASE_TAPE); ! 1242: break; ! 1243: case MTDEC: /* DEC mode */ ! 1244: case MTIBM: /* IBM mode */ ! 1245: case MT800: /* 800 bpi */ ! 1246: case MT1600: /* 1600 bpi */ ! 1247: case MT6250: /* 6250 bpi */ ! 1248: return; ! 1249: default: ! 1250: if (!ctbusywait(c, CTIOCTL)) ! 1251: return; ! 1252: s = sphi(); ! 1253: haiioctl(r, cmd, vec); ! 1254: c->state = CTIDLE; ! 1255: spl(s); ! 1256: break; ! 1257: } ! 1258: } /* ctioctl() */ ! 1259: ! 1260: /* End of file */
This archive runs on limited infrastructure. Preserving old code on modern bandwidth. Automated agents are requested to crawl responsibly.