Index: head/sys/i386/isa/atapi.c =================================================================== --- head/sys/i386/isa/atapi.c (revision 10600) +++ head/sys/i386/isa/atapi.c (revision 10601) @@ -1,822 +1,822 @@ /* * Device-independent level for ATAPI drivers. * * Copyright (C) 1995 Cronyx Ltd. * Author Serge Vakulenko, * * This software is distributed with NO WARRANTIES, not even the implied * warranties for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * Authors grant any other persons or organisations permission to use * or modify this software as long as this message is kept with the software, * all derivative works or modified versions. * * Version 1.1, Mon Jul 10 21:55:11 MSD 1995 */ /* * The ATAPI level is implemented as a machine-dependent layer * between the device driver and the IDE controller. * All the machine- and controller dependency is isolated inside * the ATAPI level, while all the device dependency is located * in the device subdriver. * * It seems that an ATAPI bus will became popular for medium-speed * storage devices such as CD-ROMs, magneto-optical disks, tape streamers etc. * * To ease the development of new ATAPI drivers, the subdriver * interface was designed to be as simple as possible. * * Three routines are available for the subdriver to access the device: * * struct atapires atapi_request_wait (ata, unit, cmd, a1, a2, a3, a4, a5, * a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, addr, count); * struct atapi *ata; -- atapi controller descriptor * int unit; -- device unit number on the IDE bus * u_char cmd; -- ATAPI command code * u_char a1..a15; -- ATAPI command arguments * char *addr; -- address of the data buffer for i/o * int count; -- data length, >0 for read ops, <0 for write ops * * The atapi_request_wait() function puts the op in the queue of ATAPI * commands for the IDE controller, starts the controller, the waits for * operation to be completed (using tsleep). * The function should be called from the user phase only (open(), close(), * ioctl() etc). * Ata and unit args are the values which the subdriver gets from the ATAPI * level via attach() call. * Buffer pointed to by *addr should be placed in core memory, static * or dynamic, but not in stack. * The function returns the error code structure, which consists of: * - atapi driver code value * - controller status port value * - controller error port value * * struct atapires atapi_request_immediate (ata, unit, cmd, a1, a2, a3, * a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, * addr, count); * * The atapi_request_immediate() function is similar to atapi_request_wait(), * but it does not use interrupts for performing the request. * It should be used during an attach phase to get parameters from the device. * * void atapi_request_callback (ata, unit, cmd, a1, a2, a3, a4, a5, * a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, * addr, count, done, x, y); * struct atapi *ata; -- atapi controller descriptor * int unit; -- device unit number on the IDE bus * u_char cmd; -- ATAPI command code * u_char a1..a15; -- ATAPI command arguments * char *addr; -- address of the data buffer for i/o * int count; -- data length, >0 for read ops, <0 for write ops * void (*done)(); -- function to call when op finished * void *x, *y; -- arguments for done() function * * The atapi_request_callback() function puts the op in the queue of ATAPI * commands for the IDE controller, starts the controller, then returns. * When the operation finishes, then the callback function done() * will be called on the interrupt level. * The function is designed to be callable from the interrupt phase. * The done() functions is called with the following arguments: * (void) (*done) (x, y, count, errcode) * void *x, *y; -- arguments from the atapi_request_callback() * int count; -- the data residual count * struct atapires errcode; -- error code structure, see above * * The new driver could be added in three steps: * 1. Add entries for the new driver to bdevsw and cdevsw tables in conf.c. * You will need to make at least three routines: open(), close(), * strategy() and possibly ioctl(). * 2. Make attach() routine, which should allocate all the needed data * structures and print the device description string (see wcdattach()). * 3. Add an appropriate case to the switch in atapi_attach() routine, * call attach() routine of the new driver here. Add the appropriate * #include line at the top of attach.c. * That's all! * * Use #define DEBUG in atapi.c to enable tracing of all i/o operations * on the IDE bus. */ #undef DEBUG -#include "wd.h" +#include "wdc.h" #include "wcd.h" /* #include "whd.h" -- add your driver here */ /* #include "wmt.h" -- add your driver here */ /* #include "wmd.h" -- add your driver here */ #if NWDC > 0 && defined (ATAPI) #include #include #include #include #include #include #include #ifdef DEBUG # define print(s) printf s #else # define print(s) {/*void*/} #endif #define MAXCMD (8*NWDC) /* * ATAPI packet command phase. */ #define PHASE_CMDOUT (ARS_DRQ | ARI_CMD) #define PHASE_DATAIN (ARS_DRQ | ARI_IN) #define PHASE_DATAOUT ARS_DRQ #define PHASE_COMPLETED (ARI_IN | ARI_CMD) struct atapicmd { /* ATAPI command block */ struct atapicmd *next; /* next command in queue */ int busy; /* busy flag */ u_char cmd[16]; /* command and args */ int unit; /* drive unit number */ int count; /* byte count, >0 - read, <0 - write */ char *addr; /* data to transfer */ void (*callback) (); /* call when done */ void *cbarg1; /* callback arg 1 */ void *cbarg2; /* callback arg 1 */ struct atapires result; /* resulting error code */ }; struct atapi { /* ATAPI controller data */ u_short port; /* i/o port base */ u_char ctrlr; /* physical controller number */ u_char debug : 1; /* trace enable flag */ u_char cmd16 : 1; /* 16-byte command flag */ u_char intrcmd : 1; /* interrupt before cmd flag */ u_char slow : 1; /* slow reaction device */ struct atapicmd *queue; /* queue of commands to perform */ struct atapicmd *tail; /* tail of queue */ struct atapicmd *free; /* queue of free command blocks */ struct atapicmd cmdrq[MAXCMD]; /* pool of command requests */ }; struct atapi atapitab[NWDC]; static struct atapi_params *atapi_probe (int port, int unit); static int atapi_wait (int port, u_char bits_wanted); static void atapi_send_cmd (struct atapi *ata, struct atapicmd *ac); static int atapi_io (struct atapi *ata, struct atapicmd *ac); static int atapi_start_cmd (struct atapi *ata, struct atapicmd *ac); static int atapi_wait_cmd (struct atapi *ata, struct atapicmd *ac); extern int wdstart (int ctrlr); void atapi_attach (int ctlr, int unit, int port, struct kern_devconf *parent) { struct atapi *ata = atapitab + ctlr; struct atapi_params *ap; char buf [sizeof(ap->model) + 1]; struct atapicmd *ac; print (("atapi%d.%d at 0x%x: attach called\n", ctlr, unit, port)); ap = atapi_probe (port, unit); if (! ap) return; bcopy (ap->model, buf, sizeof(buf)-1); buf[sizeof(buf)-1] = 0; printf ("wdc%d: unit %d (atapi): <%s>", ctlr, unit, buf); /* device is removable */ if (ap->removable) printf (", removable"); /* packet command size */ switch (ap->cmdsz) { case AT_PSIZE_12: break; case AT_PSIZE_16: printf (", cmd16"); ata->cmd16 = 1; break; default: printf (", cmd%d", ap->cmdsz); } /* DRQ type */ switch (ap->drqtype) { case AT_DRQT_MPROC: ata->slow = 1; break; case AT_DRQT_INTR: printf (", intr"); ata->intrcmd = 1; break; case AT_DRQT_ACCEL: printf (", accel"); break; default: printf (", drq%d", ap->cmdsz); } /* overlap operation supported */ if (ap->ovlapflag) printf (", ovlap"); /* interleaved DMA supported */ if (ap->idmaflag) printf (", idma"); /* DMA supported */ else if (ap->dmaflag) printf (", dma"); /* IORDY can be disabled */ if (ap->iordydis) printf (", iordis"); /* IORDY supported */ else if (ap->iordyflag) printf (", iordy"); printf ("\n"); ata->port = port; ata->ctrlr = ctlr; #ifdef DEBUG ata->debug = 1; #else ata->debug = 0; #endif /* Initialize free queue. */ ata->cmdrq[MAXCMD-1].next = 0; for (ac = ata->cmdrq+MAXCMD-2; ac >= ata->cmdrq; --ac) ac->next = ac+1; ata->free = ata->cmdrq; if (ap->proto != AT_PROTO_ATAPI) { printf ("wdc%d: unit %d: unknown ATAPI protocol=%d\n", ctlr, unit, ap->proto); free (ap, M_TEMP); return; } switch (ap->devtype) { default: /* unknown ATAPI device */ printf ("wdc%d: unit %d: unknown ATAPI type=%d\n", ctlr, unit, ap->devtype); break; case AT_TYPE_CDROM: /* CD-ROM device */ #if NWCD > 0 /* ATAPI CD-ROM */ { int wcdattach (struct atapi*, int, struct atapi_params*, int, struct kern_devconf*); if (wcdattach (ata, unit, ap, ata->debug, parent) < 0) break; } /* Device attached successfully. */ return; #else printf ("wdc%d: ATAPI CD-ROMs not configured\n", ctlr); break; #endif case AT_TYPE_DIRECT: /* direct-access (magnetic disk) */ #if NWHD > 0 /* Add your driver here */ #else printf ("wdc%d: ATAPI hard disks not supported\n", ctlr); break; #endif case AT_TYPE_TAPE: /* streaming tape (QIC-121 model) */ #if NWMT > 0 /* Add your driver here */ #else printf ("wdc%d: ATAPI streaming tapes not supported yet\n", ctlr); break; #endif case AT_TYPE_OPTICAL: /* optical disk */ #if NWMD > 0 /* Add your driver here */ #else printf ("wdc%d: ATAPI optical disks not supported yet\n", ctlr); break; #endif } /* Attach failed. */ free (ap, M_TEMP); } /* * Issue IDENTIFY command to ATAPI drive to ask it what it is. */ static struct atapi_params *atapi_probe (int port, int unit) { struct atapi_params *ap; char tb [DEV_BSIZE]; int i; /* Wait for controller not busy. */ if (atapi_wait (port, 0) < 0) { print (("atapi.%d at 0x%x: controller busy, status=%b\n", unit, port, inb (port + AR_STATUS), ARS_BITS)); return (0); } /* Issue ATAPI IDENTIFY command. */ outb (port + AR_DRIVE, unit ? ARD_DRIVE1 : ARD_DRIVE0); outb (port + AR_COMMAND, ATAPIC_IDENTIFY); /* Check that device is present. */ if (inb (port + AR_STATUS) == 0xff) { print (("atapi.%d at 0x%x: no device\n", unit, port)); if (unit == 1) /* Select unit 0. */ outb (port + AR_DRIVE, ARD_DRIVE0); return (0); } /* Wait for data ready. */ if (atapi_wait (port, ARS_DRQ) != 0) { print (("atapi.%d at 0x%x: identify not ready, status=%b\n", unit, port, inb (port + AR_STATUS), ARS_BITS)); if (unit == 1) /* Select unit 0. */ outb (port + AR_DRIVE, ARD_DRIVE0); return (0); } /* Obtain parameters. */ insw (port + AR_DATA, tb, sizeof(tb) / sizeof(short)); ap = malloc (sizeof *ap, M_TEMP, M_NOWAIT); if (! ap) return (0); bcopy (tb, ap, sizeof *ap); /* Shuffle string byte order. */ for (i=0; imodel); i+=2) { u_short *p = (u_short*) (ap->model + i); *p = ntohs (*p); } /* Clean up the model by converting nulls to spaces, and * then removing the trailing spaces. */ for (i=0; i < sizeof(ap->model); i++) if (! ap->model[i]) ap->model[i] = ' '; for (i=sizeof(ap->model)-1; i>=0 && ap->model[i]==' '; i--) ap->model[i] = 0; return (ap); } /* * Wait uninterruptibly until controller is not busy and certain * status bits are set. * The wait is usually short unless it is for the controller to process * an entire critical command. * Return 1 for (possibly stale) controller errors, -1 for timeout errors, * or 0 for no errors. */ static int atapi_wait (int port, u_char bits_wanted) { int cnt; u_char s; /* Wait 5 sec for BUSY deassert. */ for (cnt=500000; cnt>0; --cnt) { s = inb (port + AR_STATUS); if (! (s & ARS_BSY)) break; DELAY (10); } if (cnt <= 0) return (-1); if (! bits_wanted) return (s & ARS_CHECK); /* Wait 50 msec for bits wanted. */ for (cnt=5000; cnt>0; --cnt) { s = inb (port + AR_STATUS); if ((s & bits_wanted) == bits_wanted) return (s & ARS_CHECK); DELAY (10); } return (-1); } void atapi_debug (struct atapi *ata, int on) { ata->debug = on; } static struct atapicmd *atapi_alloc (struct atapi *ata) { struct atapicmd *ac; while (! ata->free) tsleep ((caddr_t)ata, PRIBIO, "atacmd", 0); ac = ata->free; ata->free = ac->next; ac->busy = 1; return (ac); } static void atapi_free (struct atapi *ata, struct atapicmd *ac) { if (! ata->free) wakeup ((caddr_t)&ata); ac->busy = 0; ac->next = ata->free; ata->free = ac; } /* * Add new command request to the end of the queue. */ static void atapi_enqueue (struct atapi *ata, struct atapicmd *ac) { ac->next = 0; if (ata->tail) ata->tail->next = ac; else ata->queue = ac; ata->tail = ac; } static void atapi_done (struct atapi *ata) { struct atapicmd *ac = ata->queue; if (! ac) return; /* cannot happen */ ata->queue = ac->next; if (! ata->queue) ata->tail = 0; if (ac->callback) { (*ac->callback) (ac->cbarg1, ac->cbarg2, ac->count, ac->result); atapi_free (ata, ac); } else wakeup ((caddr_t)ac); } /* * Start new packet op. Called from wdstart(). * Return 1 if op started, and we are waiting for interrupt. * Return 0 when idle. */ int atapi_start (int ctrlr) { struct atapi *ata = atapitab + ctrlr; struct atapicmd *ac; again: ac = ata->queue; if (! ac) return (0); /* Start packet command. */ if (atapi_start_cmd (ata, ac) < 0) { atapi_done (ata); goto again; } if (ata->intrcmd) /* Wait for interrupt before sending packet command */ return (1); /* Wait for DRQ. */ if (atapi_wait_cmd (ata, ac) < 0) { atapi_done (ata); goto again; } /* Send packet command. */ atapi_send_cmd (ata, ac); return (1); } /* * Start new packet op. Returns -1 on errors. */ int atapi_start_cmd (struct atapi *ata, struct atapicmd *ac) { ac->result.error = 0; ac->result.status = 0; outb (ata->port + AR_DRIVE, ac->unit ? ARD_DRIVE1 : ARD_DRIVE0); if (atapi_wait (ata->port, 0) < 0) { printf ("atapi%d.%d: controller not ready for cmd\n", ata->ctrlr, ac->unit); ac->result.code = RES_NOTRDY; return (-1); } /* Set up the controller registers. */ outb (ata->port + AR_FEATURES, 0); outb (ata->port + AR_IREASON, 0); outb (ata->port + AR_TAG, 0); outb (ata->port + AR_CNTLO, ac->count & 0xff); outb (ata->port + AR_CNTHI, ac->count >> 8); outb (ata->port + AR_COMMAND, ATAPIC_PACKET); if (ata->debug) printf ("atapi%d.%d: start\n", ata->ctrlr, ac->unit); return (0); } /* * Wait for DRQ before sending packet cmd. Returns -1 on errors. */ int atapi_wait_cmd (struct atapi *ata, struct atapicmd *ac) { /* Wait for DRQ from 50 usec to 3 msec for slow devices */ int cnt = ata->intrcmd ? 10000 : ata->slow ? 3000 : 50; for (; cnt>0; cnt-=10) { ac->result.status = inb (ata->port + AR_STATUS); if (ac->result.status & ARS_DRQ) break; DELAY (10); } if (! (ac->result.status & ARS_DRQ)) { printf ("atapi%d.%d: no cmd drq\n", ata->ctrlr, ac->unit); ac->result.code = RES_NODRQ; ac->result.error = inb (ata->port + AR_ERROR); return (-1); } return (0); } /* * Send packet cmd. */ void atapi_send_cmd (struct atapi *ata, struct atapicmd *ac) { outsw (ata->port + AR_DATA, ac->cmd, ata->cmd16 ? 8 : 6); if (ata->debug) printf ("atapi%d.%d: send cmd %x-%x-%x-%x-%x-%x-%x-%x-%x-%x-%x-%x-%x-%x-%x-%x\n", ata->ctrlr, ac->unit, ac->cmd[0], ac->cmd[1], ac->cmd[2], ac->cmd[3], ac->cmd[4], ac->cmd[5], ac->cmd[6], ac->cmd[7], ac->cmd[8], ac->cmd[9], ac->cmd[10], ac->cmd[11], ac->cmd[12], ac->cmd[13], ac->cmd[14], ac->cmd[15]); } /* * Interrupt routine for the controller. Called from wdintr(). * Finish the started op, wakeup wait-type commands, * run callbacks for callback-type commands, then return. * Do not start new op here, it will be done by wdstart, * which is called just after us. * Return 1 if op continues, and we are waiting for new interrupt. * Return 0 when idle. */ int atapi_intr (int ctrlr) { struct atapi *ata = atapitab + ctrlr; struct atapicmd *ac = ata->queue; if (! ac) { printf ("atapi%d: stray interrupt\n", ata->ctrlr); return (0); } if (atapi_io (ata, ac) > 0) return (1); atapi_done (ata); return (0); } /* * Process the i/o phase, transferring the command/data to/from the device. * Return 1 if op continues, and we are waiting for new interrupt. * Return 0 when idle. */ int atapi_io (struct atapi *ata, struct atapicmd *ac) { u_char ireason; u_short len; if (atapi_wait (ata->port, ac->count ? ARS_DRQ : 0) < 0) { printf ("atapi%d.%d: controller not ready\n", ata->ctrlr, ac->unit); ac->result.code = RES_NOTRDY; ac->result.status = 0; ac->result.error = 0; return (0); } ac->result.status = inb (ata->port + AR_STATUS); ac->result.error = inb (ata->port + AR_ERROR); len = inb (ata->port + AR_CNTLO); len |= inb (ata->port + AR_CNTHI) << 8; ireason = inb (ata->port + AR_IREASON); if (ata->debug) { printf ("atapi%d.%d: intr ireason=0x%x, len=%d, status=%b, error=%b\n", ata->ctrlr, ac->unit, ireason, len, ac->result.status, ARS_BITS, ac->result.error, AER_BITS); } switch ((ireason & (ARI_CMD | ARI_IN)) | (ac->result.status & ARS_DRQ)) { default: printf ("atapi%d.%d: unknown phase\n", ata->ctrlr, ac->unit); ac->result.code = RES_ERR; break; case PHASE_CMDOUT: /* Send packet command. */ if (! (ac->result.status & ARS_DRQ)) { printf ("atapi%d.%d: no cmd drq\n", ata->ctrlr, ac->unit); ac->result.code = RES_NODRQ; break; } atapi_send_cmd (ata, ac); return (1); case PHASE_DATAOUT: /* Write data */ if (ac->count > 0) { printf ("atapi%d.%d: invalid data direction\n", ata->ctrlr, ac->unit); ac->result.code = RES_INVDIR; break; } if (-ac->count < len) { printf ("atapi%d.%d: send data underrun, %d bytes left\n", ata->ctrlr, ac->unit, -ac->count); ac->result.code = RES_UNDERRUN; break; } outsw (ata->port + AR_DATA, ac->addr, len / sizeof(short)); ac->addr += len; ac->count += len; return (1); case PHASE_DATAIN: /* Read data */ if (ac->count < 0) { printf ("atapi%d.%d: invalid data direction\n", ata->ctrlr, ac->unit); ac->result.code = RES_INVDIR; break; } if (ac->count < len) { printf ("atapi%d.%d: recv data overrun, %d bytes left\n", ata->ctrlr, ac->unit, ac->count); ac->result.code = RES_OVERRUN; break; } insw (ata->port + AR_DATA, ac->addr, len / sizeof(short)); ac->addr += len; ac->count -= len; return (1); case PHASE_COMPLETED: if (ac->result.status & (ARS_CHECK | ARS_DF)) { ac->result.code = RES_ERR; break; } if (ac->count < 0) { printf ("atapi%d.%d: send data overrun, %d bytes left\n", ata->ctrlr, ac->unit, -ac->count); ac->result.code = RES_OVERRUN; break; } if (ac->count > 0) { printf ("atapi%d.%d: recv data underrun, %d bytes left\n", ata->ctrlr, ac->unit, ac->count); ac->result.code = RES_UNDERRUN; break; } ac->result.code = RES_OK; break; } return (0); } /* * Queue new packet request, then call wdstart(). * Called on splbio(). */ void atapi_request_callback (struct atapi *ata, int unit, u_char cmd, u_char a1, u_char a2, u_char a3, u_char a4, u_char a5, u_char a6, u_char a7, u_char a8, u_char a9, u_char a10, u_char a11, u_char a12, u_char a13, u_char a14, u_char a15, char *addr, int count, void (*done)(), void *x, void *y) { struct atapicmd *ac; ac = atapi_alloc (ata); ac->cmd[0] = cmd; ac->cmd[1] = a1; ac->cmd[2] = a2; ac->cmd[3] = a3; ac->cmd[4] = a4; ac->cmd[5] = a5; ac->cmd[6] = a6; ac->cmd[7] = a7; ac->cmd[8] = a8; ac->cmd[9] = a9; ac->cmd[10] = a10; ac->cmd[11] = a11; ac->cmd[12] = a12; ac->cmd[13] = a13; ac->cmd[14] = a14; ac->cmd[15] = a15; ac->unit = unit; ac->addr = addr; ac->count = count; ac->callback = done; ac->cbarg1 = x; ac->cbarg2 = y; if (ata->debug) printf ("atapi%d.%d: req cb %x-%x-%x-%x-%x-%x-%x-%x-%x-%x-%x-%x-%x-%x-%x-%x len=%d\n", ata->ctrlr, ac->unit, ac->cmd[0], ac->cmd[1], ac->cmd[2], ac->cmd[3], ac->cmd[4], ac->cmd[5], ac->cmd[6], ac->cmd[7], ac->cmd[8], ac->cmd[9], ac->cmd[10], ac->cmd[11], ac->cmd[12], ac->cmd[13], ac->cmd[14], ac->cmd[15], count); atapi_enqueue (ata, ac); wdstart (ata->ctrlr); } /* * Queue new packet request, then call wdstart(). * Wait until the request is finished. * Called on spl0(). * Return atapi error. * Buffer pointed to by *addr should be placed in core memory, not in stack! */ struct atapires atapi_request_wait (struct atapi *ata, int unit, u_char cmd, u_char a1, u_char a2, u_char a3, u_char a4, u_char a5, u_char a6, u_char a7, u_char a8, u_char a9, u_char a10, u_char a11, u_char a12, u_char a13, u_char a14, u_char a15, char *addr, int count) { struct atapicmd *ac; int x = splbio (); struct atapires result; ac = atapi_alloc (ata); ac->cmd[0] = cmd; ac->cmd[1] = a1; ac->cmd[2] = a2; ac->cmd[3] = a3; ac->cmd[4] = a4; ac->cmd[5] = a5; ac->cmd[6] = a6; ac->cmd[7] = a7; ac->cmd[8] = a8; ac->cmd[9] = a9; ac->cmd[10] = a10; ac->cmd[11] = a11; ac->cmd[12] = a12; ac->cmd[13] = a13; ac->cmd[14] = a14; ac->cmd[15] = a15; ac->unit = unit; ac->addr = addr; ac->count = count; ac->callback = 0; ac->cbarg1 = 0; ac->cbarg2 = 0; if (ata->debug) printf ("atapi%d.%d: req w %x-%x-%x-%x-%x-%x-%x-%x-%x-%x-%x-%x-%x-%x-%x-%x len=%d\n", ata->ctrlr, ac->unit, ac->cmd[0], ac->cmd[1], ac->cmd[2], ac->cmd[3], ac->cmd[4], ac->cmd[5], ac->cmd[6], ac->cmd[7], ac->cmd[8], ac->cmd[9], ac->cmd[10], ac->cmd[11], ac->cmd[12], ac->cmd[13], ac->cmd[14], ac->cmd[15], count); atapi_enqueue (ata, ac); wdstart (ata->ctrlr); tsleep ((caddr_t)ac, PRIBIO, "atareq", 0); result = ac->result; atapi_free (ata, ac); splx (x); return (result); } /* * Perform a packet command on the device. * Should be called on splbio(). * Return atapi error. */ struct atapires atapi_request_immediate (struct atapi *ata, int unit, u_char cmd, u_char a1, u_char a2, u_char a3, u_char a4, u_char a5, u_char a6, u_char a7, u_char a8, u_char a9, u_char a10, u_char a11, u_char a12, u_char a13, u_char a14, u_char a15, char *addr, int count) { struct atapicmd cmdbuf, *ac = &cmdbuf; int cnt; ac->cmd[0] = cmd; ac->cmd[1] = a1; ac->cmd[2] = a2; ac->cmd[3] = a3; ac->cmd[4] = a4; ac->cmd[5] = a5; ac->cmd[6] = a6; ac->cmd[7] = a7; ac->cmd[8] = a8; ac->cmd[9] = a9; ac->cmd[10] = a10; ac->cmd[11] = a11; ac->cmd[12] = a12; ac->cmd[13] = a13; ac->cmd[14] = a14; ac->cmd[15] = a15; ac->unit = unit; ac->addr = addr; ac->count = count; ac->callback = 0; ac->cbarg1 = 0; ac->cbarg2 = 0; if (ata->debug) printf ("atapi%d.%d: req im %x-%x-%x-%x-%x-%x-%x-%x-%x-%x-%x-%x-%x-%x-%x-%x len=%d\n", ata->ctrlr, ac->unit, ac->cmd[0], ac->cmd[1], ac->cmd[2], ac->cmd[3], ac->cmd[4], ac->cmd[5], ac->cmd[6], ac->cmd[7], ac->cmd[8], ac->cmd[9], ac->cmd[10], ac->cmd[11], ac->cmd[12], ac->cmd[13], ac->cmd[14], ac->cmd[15], count); /* Start packet command, wait for DRQ. */ if (atapi_start_cmd (ata, ac) >= 0 && atapi_wait_cmd (ata, ac) >= 0) { /* Send packet command. */ atapi_send_cmd (ata, ac); /* Do all needed i/o. */ while (atapi_io (ata, ac)) /* Wait for DRQ deassert. */ for (cnt=2000; cnt>0; --cnt) if (! (inb (ata->port + AR_STATUS) & ARS_DRQ)) break; } return (ac->result); } #endif /* NWDC && ATAPI */ Index: head/sys/i386/isa/wcd.c =================================================================== --- head/sys/i386/isa/wcd.c (revision 10600) +++ head/sys/i386/isa/wcd.c (revision 10601) @@ -1,921 +1,921 @@ /* * IDE CD-ROM driver for FreeBSD. * Supports ATAPI-compatible drives. * * Copyright (C) 1995 Cronyx Ltd. * Author Serge Vakulenko, * * This software is distributed with NO WARRANTIES, not even the implied * warranties for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * Authors grant any other persons or organisations permission to use * or modify this software as long as this message is kept with the software, * all derivative works or modified versions. * * Version 1.1, Mon Jul 10 21:55:11 MSD 1995 */ /* * The driver was tested on Toshiba XM-5302TA drive. (vak) */ -#include "wd.h" +#include "wdc.h" #include "wcd.h" #if NWCD > 0 && NWDC > 0 && defined (ATAPI) #include #include #include #include #include #include #include #include #include #include #include #include #define NUNIT (NWDC*2) /* Max. number of devices */ #define UNIT(d) (minor(d) & 3) /* Unit part of minor device number */ #define SECSIZE 2048 /* CD-ROM sector size in bytes */ #define F_OPEN 0x0001 /* The drive os opened */ #define F_MEDIA_CHANGED 0x0002 /* The media have changed since open */ #define F_DEBUG 0x0004 /* The media have changed since open */ /* * Disc table of contents. */ #define MAXTRK 99 struct toc { struct ioc_toc_header hdr; struct cd_toc_entry tab[MAXTRK+1]; /* One extra for the leadout */ }; /* * Volume size info. */ struct volinfo { u_long volsize; /* Volume size in blocks */ u_long blksize; /* Block size in bytes */ } info; /* * Current subchannel status. */ struct subchan { u_char void0; u_char audio_status; u_short data_length; u_char data_format; u_char control; u_char track; u_char indx; u_long abslba; u_long rellba; }; /* * Audio Control Parameters Page */ struct audiopage { /* Mode data header */ u_short data_length; u_char medium_type; u_char reserved1[5]; /* Audio control page */ u_char page_code; #define AUDIO_PAGE 0x0e #define AUDIO_PAGE_MASK 0x4e /* changeable values */ u_char param_len; u_char flags; #define CD_PA_SOTC 0x02 /* mandatory */ #define CD_PA_IMMED 0x04 /* always 1 */ u_char reserved3[3]; u_short lb_per_sec; struct port_control { u_char channels : 4; #define CHANNEL_0 1 /* mandatory */ #define CHANNEL_1 2 /* mandatory */ #define CHANNEL_2 4 /* optional */ #define CHANNEL_3 8 /* optional */ u_char volume; } port[4]; }; /* * CD-ROM Capabilities and Mechanical Status Page */ struct cappage { /* Mode data header */ u_short data_length; u_char medium_type; #define MDT_UNKNOWN 0x00 #define MDT_DATA_120 0x01 #define MDT_AUDIO_120 0x02 #define MDT_COMB_120 0x03 #define MDT_PHOTO_120 0x04 #define MDT_DATA_80 0x05 #define MDT_AUDIO_80 0x06 #define MDT_COMB_80 0x07 #define MDT_PHOTO_80 0x08 #define MDT_NO_DISC 0x70 #define MDT_DOOR_OPEN 0x71 #define MDT_FMT_ERROR 0x72 u_char reserved1[5]; /* Capabilities page */ u_char page_code; #define CAP_PAGE 0x2a u_char param_len; u_char reserved2[2]; u_char audio_play : 1; /* audio play supported */ u_char composite : 1; /* composite audio/video supported */ u_char dport1 : 1; /* digital audio on port 1 */ u_char dport2 : 1; /* digital audio on port 2 */ u_char mode2_form1 : 1; /* mode 2 form 1 (XA) read */ u_char mode2_form2 : 1; /* mode 2 form 2 format */ u_char multisession : 1; /* multi-session photo-CD */ u_char : 1; u_char cd_da : 1; /* audio-CD read supported */ u_char cd_da_stream : 1; /* CD-DA streaming */ u_char rw : 1; /* combined R-W subchannels */ u_char rw_corr : 1; /* R-W subchannel data corrected */ u_char c2 : 1; /* C2 error pointers supported */ u_char isrc : 1; /* can return the ISRC info */ u_char upc : 1; /* can return the catalog number UPC */ u_char : 1; u_char lock : 1; /* could be locked */ u_char locked : 1; /* current lock state */ u_char prevent : 1; /* prevent jumper installed */ u_char eject : 1; /* can eject */ u_char : 1; u_char mech : 3; /* loading mechanism type */ #define MECH_CADDY 0 #define MECH_TRAY 1 #define MECH_POPUP 2 #define MECH_CHANGER 4 #define MECH_CARTRIDGE 5 u_char sep_vol : 1; /* independent volume of channels */ u_char sep_mute : 1; /* independent mute of channels */ u_char : 6; u_short max_speed; /* max raw data rate in bytes/1000 */ u_short max_vol_levels; /* number of discrete volume levels */ u_short buf_size; /* internal buffer size in bytes/1024 */ u_short cur_speed; /* current data rate in bytes/1000 */ }; struct wcd { struct atapi *ata; /* Controller structure */ int unit; /* IDE bus drive unit */ int lun; /* Logical device unit */ int flags; /* Device state flags */ struct buf queue; /* Queue of i/o requests */ struct atapi_params *param; /* Drive parameters table */ struct toc toc; /* Table of disc contents */ struct volinfo info; /* Volume size info */ struct audiopage au; /* Audio page info */ struct cappage cap; /* Capabilities page info */ struct audiopage aumask; /* Audio page mask */ struct subchan subchan; /* Subchannel info */ struct kern_devconf cf; /* Driver configuration info */ char description[80]; /* Device description */ }; struct wcd *wcdtab[NUNIT]; /* Drive info by unit number */ static int wcdnlun = 0; /* Number of configured drives */ static void wcd_start (struct wcd *t); static void wcd_done (struct wcd *t, struct buf *bp, int resid, struct atapires result); static void wcd_error (struct wcd *t, struct atapires result); static int wcd_read_toc (struct wcd *t); static int wcd_request_wait (struct wcd *t, u_char cmd, u_char a1, u_char a2, u_char a3, u_char a4, u_char a5, u_char a6, u_char a7, u_char a8, u_char a9, char *addr, int count); static int wcd_externalize (struct proc*, struct kern_devconf*, void*, size_t); static int wcd_goaway (struct kern_devconf *kdc, int force); static void wcd_describe (struct wcd *t); static int wcd_setchan (struct wcd *t, u_char c0, u_char c1, u_char c2, u_char c3); static struct kern_devconf cftemplate = { 0, 0, 0, "wcd", 0, { MDDT_DISK, 0 }, wcd_externalize, 0, wcd_goaway, DISK_EXTERNALLEN, 0, 0, DC_IDLE, "ATAPI compact disc", }; /* * Dump the array in hexadecimal format for debugging purposes. */ static void wcd_dump (int lun, char *label, void *data, int len) { u_char *p = data; printf ("wcd%d: %s %x", lun, label, *p++); while (--len > 0) printf ("-%x", *p++); printf ("\n"); } static int wcd_externalize (struct proc *p, struct kern_devconf *kdc, void *userp, size_t len) { return disk_externalize (wcdtab[kdc->kdc_unit]->unit, userp, &len); } static int wcd_goaway (struct kern_devconf *kdc, int force) { dev_detach (kdc); return 0; } void wcdattach (struct atapi *ata, int unit, struct atapi_params *ap, int debug, struct kern_devconf *parent) { struct wcd *t; struct atapires result; if (wcdnlun >= NUNIT) { printf ("wcd: too many units\n"); return; } t = malloc (sizeof (struct wcd), M_TEMP, M_NOWAIT); if (! t) { printf ("wcd: out of memory\n"); return; } wcdtab[wcdnlun] = t; bzero (t, sizeof (struct wcd)); t->ata = ata; t->unit = unit; t->lun = wcdnlun++; t->param = ap; t->flags = F_MEDIA_CHANGED; if (debug) { t->flags |= F_DEBUG; /* Print params. */ wcd_dump (t->lun, "info", ap, sizeof *ap); } /* Get drive capabilities. */ result = atapi_request_immediate (ata, unit, ATAPI_MODE_SENSE, 0, CAP_PAGE, 0, 0, 0, 0, sizeof (t->cap) >> 8, sizeof (t->cap), 0, 0, 0, 0, 0, 0, 0, (char*) &t->cap, sizeof (t->cap)); if (result.code == 0) { wcd_describe (t); if (t->flags & F_DEBUG) wcd_dump (t->lun, "cap", &t->cap, sizeof t->cap); } /* Register driver */ t->cf = cftemplate; t->cf.kdc_unit = t->lun; t->cf.kdc_parent = parent; t->cf.kdc_description = t->description; strcpy (t->description, cftemplate.kdc_description); strcat (t->description, ": "); strncpy (t->description + strlen(t->description), ap->model, sizeof(ap->model)); dev_attach (&t->cf); } void wcd_describe (struct wcd *t) { char *m; t->cap.max_speed = ntohs (t->cap.max_speed); t->cap.max_vol_levels = ntohs (t->cap.max_vol_levels); t->cap.buf_size = ntohs (t->cap.buf_size); t->cap.cur_speed = ntohs (t->cap.cur_speed); printf ("wcd%d: ", t->lun); if (t->cap.cur_speed != t->cap.max_speed) printf ("%d/", t->cap.cur_speed * 1000 / 1024); printf ("%dKb/sec", t->cap.max_speed * 1000 / 1024, t->cap.buf_size); if (t->cap.buf_size) printf (", %dKb cache", t->cap.buf_size); if (t->cap.audio_play) printf (", audio play"); if (t->cap.max_vol_levels) printf (", %d volume levels", t->cap.max_vol_levels); switch (t->cap.mech) { default: m = 0; break; case MECH_CADDY: m = "caddy"; break; case MECH_TRAY: m = "tray"; break; case MECH_POPUP: m = "popup"; break; case MECH_CHANGER: m = "changer"; break; case MECH_CARTRIDGE: m = "cartridge"; break; } if (m) printf (", %s%s", t->cap.eject ? "ejectable " : "", m); else if (t->cap.eject) printf (", eject"); printf ("\n"); printf ("wcd%d: ", t->lun); switch (t->cap.medium_type) { case MDT_UNKNOWN: printf ("medium type unknown"); break; case MDT_DATA_120: printf ("120mm data disc loaded"); break; case MDT_AUDIO_120: printf ("120mm audio disc loaded"); break; case MDT_COMB_120: printf ("120mm data/audio disc loaded"); break; case MDT_PHOTO_120: printf ("120mm photo disc loaded"); break; case MDT_DATA_80: printf ("80mm data disc loaded"); break; case MDT_AUDIO_80: printf ("80mm audio disc loaded"); break; case MDT_COMB_80: printf ("80mm data/audio disc loaded"); break; case MDT_PHOTO_80: printf ("80mm photo disc loaded"); break; case MDT_NO_DISC: printf ("no disc inside"); break; case MDT_DOOR_OPEN: printf ("door open"); break; case MDT_FMT_ERROR: printf ("medium format error"); break; default: printf ("medium type=0x%x", t->cap.medium_type); break; } if (t->cap.lock) printf (t->cap.locked ? ", locked" : ", unlocked"); if (t->cap.prevent) printf (", lock protected"); printf ("\n"); } int wcdopen (dev_t dev) { int lun = UNIT(dev); struct wcd *t; struct atapires result; /* Check the unit is legal. */ if (lun >= wcdnlun) return (ENXIO); t = wcdtab[lun]; /* If already opened, that's all. */ if (t->flags & F_OPEN) { /* If it's been invalidated, forbid re-entry. * (may have changed media) */ if (t->flags & F_MEDIA_CHANGED) return (ENXIO); return (0); } /* On the first open: check for the media. * Do it twice to avoid the stale media changed state. */ result = atapi_request_wait (t->ata, t->unit, ATAPI_TEST_UNIT_READY, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0); if (result.code == RES_ERR && result.error == AER_SK_UNIT_ATTENTION) t->flags |= F_MEDIA_CHANGED; if (result.code) result = atapi_request_wait (t->ata, t->unit, ATAPI_TEST_UNIT_READY, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0); if (result.code) { wcd_error (t, result); return (ENXIO); } /* Read table of contents. */ if (wcd_read_toc (t) != 0) return (EIO); /* Read disc capacity. */ if (wcd_request_wait (t, ATAPI_READ_CAPACITY, 0, 0, 0, 0, 0, 0, 0, sizeof(t->info), 0, (char*)&t->info, sizeof(t->info)) != 0) return (EIO); t->info.volsize = ntohl (t->info.volsize); t->info.blksize = ntohl (t->info.blksize); if (t->flags & (F_MEDIA_CHANGED | F_DEBUG)) { printf ("wcd%d: ", t->lun); if (t->toc.tab[0].control & 4) printf ("%ldMB ", t->info.volsize / 512); else printf ("%ld:%ld audio ", t->info.volsize/75/60, t->info.volsize/75%60); printf ("(%ld sectors), %d tracks\n", t->info.volsize, t->toc.hdr.ending_track - t->toc.hdr.starting_track + 1); } /* Lock the media. */ wcd_request_wait (t, ATAPI_PREVENT_ALLOW, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0); t->flags &= ~F_MEDIA_CHANGED; t->flags |= F_OPEN; return (0); } /* * Close the device. Only called if we are the LAST * occurence of an open device. */ int wcdclose (dev_t dev) { int lun = UNIT(dev); struct wcd *t = wcdtab[lun]; /* If we were the last open of the entire device, release it. */ wcd_request_wait (t, ATAPI_PREVENT_ALLOW, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0); t->flags &= ~F_OPEN; return (0); } /* * Actually translate the requested transfer into one the physical driver can * understand. The transfer is described by a buf and will include only one * physical transfer. */ int wcdstrategy (struct buf *bp) { int lun = UNIT(bp->b_dev); struct wcd *t = wcdtab[lun]; int x; /* If the device has been made invalid, error out * maybe the media changed. */ if (t->flags & F_MEDIA_CHANGED) { bp->b_error = EIO; bp->b_flags |= B_ERROR; biodone (bp); return (0); } /* Can't ever write to a CD. */ if (! (bp->b_flags & B_READ)) { bp->b_error = EROFS; bp->b_flags |= B_ERROR; biodone (bp); return (0); } /* If it's a null transfer, return immediatly. */ if (bp->b_bcount == 0) { bp->b_resid = 0; biodone (bp); return (0); } /* Process transfer request. */ bp->b_pblkno = bp->b_blkno; bp->b_resid = bp->b_bcount; x = splbio(); /* Place it in the queue of disk activities for this disk. */ disksort (&t->queue, bp); /* Tell the device to get going on the transfer if it's * not doing anything, otherwise just wait for completion. */ wcd_start (t); splx(x); return (0); } /* * Look to see if there is a buf waiting for the device * and that the device is not already busy. If both are true, * It dequeues the buf and creates an ATAPI command to perform the * transfer in the buf. * The bufs are queued by the strategy routine (wcdstrategy). * Must be called at the correct (splbio) level. */ static void wcd_start (struct wcd *t) { struct buf *bp = t->queue.b_actf; u_long blkno, nblk; /* See if there is a buf to do and we are not already doing one. */ if (! bp) return; /* Unqueue the request. */ t->queue.b_actf = bp->b_actf; /* Should reject all queued entries if media have changed. */ if (t->flags & F_MEDIA_CHANGED) { bp->b_error = EIO; bp->b_flags |= B_ERROR; biodone (bp); return; } /* We have a buf, now we should make a command * First, translate the block to absolute and put it in terms of the * logical blocksize of the device. * What if something asks for 512 bytes not on a 2k boundary? */ blkno = bp->b_blkno / (SECSIZE / 512); nblk = (bp->b_bcount + (SECSIZE - 1)) / SECSIZE; atapi_request_callback (t->ata, t->unit, ATAPI_READ_BIG, 0, blkno>>24, blkno>>16, blkno>>8, blkno, 0, nblk>>8, nblk, 0, 0, 0, 0, 0, 0, 0, (u_char*) bp->b_un.b_addr, bp->b_bcount, wcd_done, t, bp); t->cf.kdc_state = DC_BUSY; } static void wcd_done (struct wcd *t, struct buf *bp, int resid, struct atapires result) { if (result.code) { wcd_error (t, result); bp->b_error = EIO; bp->b_flags |= B_ERROR; } else bp->b_resid = resid; biodone (bp); t->cf.kdc_state = DC_IDLE; wcd_start (t); } static void wcd_error (struct wcd *t, struct atapires result) { if (result.code != RES_ERR) return; switch (result.error) { case AER_SK_NOT_READY: /* Tray open. */ if (! (t->flags & F_MEDIA_CHANGED)) printf ("wcd%d: tray open\n", t->lun); t->flags |= F_MEDIA_CHANGED; break; case AER_SK_UNIT_ATTENTION: /* Media changed. */ if (! (t->flags & F_MEDIA_CHANGED)) printf ("wcd%d: media changed\n", t->lun); t->flags |= F_MEDIA_CHANGED; break; case AER_SK_NOT_READY | AER_ILI | AER_EOM: /* Audio disc. */ printf ("wcd%d: cannot read audio disc\n", t->lun); break; case AER_SK_ILLEGAL_REQUEST: /* Unknown command or invalid command arguments. */ if (t->flags & F_DEBUG) printf ("wcd%d: invalid command\n", t->lun); break; default: printf ("wcd%d: i/o error, status=%b, error=%b\n", t->lun, result.status, ARS_BITS, result.error, AER_BITS); break; } } static int wcd_request_wait (struct wcd *t, u_char cmd, u_char a1, u_char a2, u_char a3, u_char a4, u_char a5, u_char a6, u_char a7, u_char a8, u_char a9, char *addr, int count) { struct atapires result; t->cf.kdc_state = DC_BUSY; result = atapi_request_wait (t->ata, t->unit, cmd, a1, a2, a3, a4, a5, a6, a7, a8, a9, 0, 0, 0, 0, 0, 0, addr, count); t->cf.kdc_state = DC_IDLE; if (result.code) { wcd_error (t, result); return (EIO); } return (0); } static inline void lba2msf (int lba, u_char *m, u_char *s, u_char *f) { lba += 150; /* offset of first logical frame */ lba &= 0xffffff; /* negative lbas use only 24 bits */ *m = lba / (60 * 75); lba %= (60 * 75); *s = lba / 75; *f = lba % 75; } /* * Perform special action on behalf of the user. * Knows about the internals of this device */ int wcdioctl (dev_t dev, int cmd, caddr_t addr, int flag) { int lun = UNIT(dev); struct wcd *t = wcdtab[lun]; int error = 0; /* If the device is not valid.. abandon ship. */ if (t->flags & F_MEDIA_CHANGED) return (EIO); switch (cmd) { default: return (ENOTTY); case CDIOCSETDEBUG: t->flags |= F_DEBUG; atapi_debug (t->ata, 1); return 0; case CDIOCCLRDEBUG: t->flags &= ~F_DEBUG; atapi_debug (t->ata, 0); return 0; case CDIOCRESUME: return wcd_request_wait (t, ATAPI_PAUSE, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0); case CDIOCPAUSE: return wcd_request_wait (t, ATAPI_PAUSE, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0); case CDIOCSTART: return wcd_request_wait (t, ATAPI_START_STOP, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0); case CDIOCSTOP: return wcd_request_wait (t, ATAPI_START_STOP, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0); case CDIOCALLOW: return wcd_request_wait (t, ATAPI_PREVENT_ALLOW, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0); case CDIOCPREVENT: return wcd_request_wait (t, ATAPI_PREVENT_ALLOW, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0); case CDIOCRESET: return wcd_request_wait (t, ATAPI_TEST_UNIT_READY, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0); case CDIOCEJECT: /* Stop the disc. */ error = wcd_request_wait (t, ATAPI_START_STOP, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0); if (error) return (error); /* Give it some time to stop spinning. */ tsleep ((caddr_t)&lbolt, PRIBIO, "wcdejct", 0); tsleep ((caddr_t)&lbolt, PRIBIO, "wcdejct", 0); /* Eject. */ return wcd_request_wait (t, ATAPI_START_STOP, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0); case CDIOREADTOCHEADER: bcopy (&t->toc.hdr, addr, sizeof t->toc.hdr); break; case CDIOREADTOCENTRYS: { struct ioc_read_toc_entry *te = (struct ioc_read_toc_entry*) addr; struct toc *toc = &t->toc; struct toc buf; u_long len; if (te->starting_track < toc->hdr.starting_track || te->starting_track > toc->hdr.ending_track) return (EINVAL); len = (toc->hdr.ending_track - te->starting_track + 2) * sizeof(toc->tab[0]); if (te->data_len < len) len = te->data_len; if (len <= 0) return (EINVAL); /* Convert to MSF format, if needed. */ if (te->address_format == CD_MSF_FORMAT) { struct cd_toc_entry *e; buf = t->toc; toc = &buf; e = toc->tab + toc->hdr.ending_track - te->starting_track + 2; while (--e >= toc->tab) lba2msf (e->addr.lba, &e->addr.msf.minute, &e->addr.msf.second, &e->addr.msf.frame); } if (copyout (toc->tab + te->starting_track - toc->hdr.starting_track, te->data, len) != 0) error = EFAULT; break; } case CDIOCREADSUBCHANNEL: { struct ioc_read_subchannel *args = (struct ioc_read_subchannel*) addr; struct cd_sub_channel_info data; u_long len = args->data_len; int abslba, rellba; if (len > sizeof(data) || len < sizeof(struct cd_sub_channel_header)) return (EINVAL); if (wcd_request_wait (t, ATAPI_READ_SUBCHANNEL, 0, 0x40, 1, 0, 0, 0, sizeof (t->subchan) >> 8, sizeof (t->subchan), 0, (char*)&t->subchan, sizeof (t->subchan)) != 0) return (EIO); if (t->flags & F_DEBUG) wcd_dump (t->lun, "subchan", &t->subchan, sizeof t->subchan); abslba = ntohl (t->subchan.abslba); rellba = ntohl (t->subchan.rellba); if (args->address_format == CD_MSF_FORMAT) { lba2msf (abslba, &data.what.position.absaddr.msf.minute, &data.what.position.absaddr.msf.second, &data.what.position.absaddr.msf.frame); lba2msf (rellba, &data.what.position.reladdr.msf.minute, &data.what.position.reladdr.msf.second, &data.what.position.reladdr.msf.frame); } else { data.what.position.absaddr.lba = abslba; data.what.position.reladdr.lba = rellba; } data.header.audio_status = t->subchan.audio_status; data.what.position.control = t->subchan.control & 0xf; data.what.position.track_number = t->subchan.track; data.what.position.index_number = t->subchan.indx; if (copyout (&data, args->data, len) != 0) error = EFAULT; break; } case CDIOCPLAYMSF: { struct ioc_play_msf *args = (struct ioc_play_msf*) addr; return wcd_request_wait (t, ATAPI_PLAY_MSF, 0, 0, args->start_m, args->start_s, args->start_f, args->end_m, args->end_s, args->end_f, 0, 0, 0); } case CDIOCPLAYBLOCKS: { struct ioc_play_blocks *args = (struct ioc_play_blocks*) addr; return wcd_request_wait (t, ATAPI_PLAY_BIG, 0, args->blk >> 24 & 0xff, args->blk >> 16 & 0xff, args->blk >> 8 & 0xff, args->blk & 0xff, args->len >> 24 & 0xff, args->len >> 16 & 0xff, args->len >> 8 & 0xff, args->len & 0xff, 0, 0); } case CDIOCPLAYTRACKS: { struct ioc_play_track *args = (struct ioc_play_track*) addr; u_long start, len; int t1, t2; /* Ignore index fields, * play from start_track to end_track inclusive. */ if (args->end_track < t->toc.hdr.ending_track+1) ++args->end_track; if (args->end_track > t->toc.hdr.ending_track+1) args->end_track = t->toc.hdr.ending_track+1; t1 = args->start_track - t->toc.hdr.starting_track; t2 = args->end_track - t->toc.hdr.starting_track; if (t1 < 0 || t2 < 0) return (EINVAL); start = t->toc.tab[t1].addr.lba; len = t->toc.tab[t2].addr.lba - start; return wcd_request_wait (t, ATAPI_PLAY_BIG, 0, start >> 24 & 0xff, start >> 16 & 0xff, start >> 8 & 0xff, start & 0xff, len >> 24 & 0xff, len >> 16 & 0xff, len >> 8 & 0xff, len & 0xff, 0, 0); } case CDIOCGETVOL: { struct ioc_vol *arg = (struct ioc_vol*) addr; error = wcd_request_wait (t, ATAPI_MODE_SENSE, 0, AUDIO_PAGE, 0, 0, 0, 0, sizeof (t->au) >> 8, sizeof (t->au), 0, (char*) &t->au, sizeof (t->au)); if (error) return (error); if (t->flags & F_DEBUG) wcd_dump (t->lun, "au", &t->au, sizeof t->au); if (t->au.page_code != AUDIO_PAGE) return (EIO); arg->vol[0] = t->au.port[0].volume; arg->vol[1] = t->au.port[1].volume; arg->vol[2] = t->au.port[2].volume; arg->vol[3] = t->au.port[3].volume; break; } case CDIOCSETVOL: { struct ioc_vol *arg = (struct ioc_vol*) addr; error = wcd_request_wait (t, ATAPI_MODE_SENSE, 0, AUDIO_PAGE, 0, 0, 0, 0, sizeof (t->au) >> 8, sizeof (t->au), 0, (char*) &t->au, sizeof (t->au)); if (error) return (error); if (t->flags & F_DEBUG) wcd_dump (t->lun, "au", &t->au, sizeof t->au); if (t->au.page_code != AUDIO_PAGE) return (EIO); error = wcd_request_wait (t, ATAPI_MODE_SENSE, 0, AUDIO_PAGE_MASK, 0, 0, 0, 0, sizeof (t->aumask) >> 8, sizeof (t->aumask), 0, (char*) &t->aumask, sizeof (t->aumask)); if (error) return (error); if (t->flags & F_DEBUG) wcd_dump (t->lun, "mask", &t->aumask, sizeof t->aumask); t->au.port[0].channels = CHANNEL_0; t->au.port[1].channels = CHANNEL_1; t->au.port[0].volume = arg->vol[0] & t->aumask.port[0].volume; t->au.port[1].volume = arg->vol[1] & t->aumask.port[1].volume; t->au.port[2].volume = arg->vol[2] & t->aumask.port[2].volume; t->au.port[3].volume = arg->vol[3] & t->aumask.port[3].volume; return wcd_request_wait (t, ATAPI_MODE_SELECT_BIG, 0x10, 0, 0, 0, 0, 0, sizeof (t->au) >> 8, sizeof (t->au), 0, (char*) &t->au, - sizeof (t->au)); } case CDIOCSETPATCH: { struct ioc_patch *arg = (struct ioc_patch*) addr; return wcd_setchan (t, arg->patch[0], arg->patch[1], arg->patch[2], arg->patch[3]); } case CDIOCSETMONO: return wcd_setchan (t, CHANNEL_0 | CHANNEL_1, CHANNEL_0 | CHANNEL_1, 0, 0); case CDIOCSETSTERIO: return wcd_setchan (t, CHANNEL_0, CHANNEL_1, 0, 0); case CDIOCSETMUTE: return wcd_setchan (t, 0, 0, 0, 0); case CDIOCSETLEFT: return wcd_setchan (t, CHANNEL_0, CHANNEL_0, 0, 0); case CDIOCSETRIGHT: return wcd_setchan (t, CHANNEL_1, CHANNEL_1, 0, 0); } return (error); } /* * Read the entire TOC for the disc into our internal buffer. */ static int wcd_read_toc (struct wcd *t) { int ntracks, len, i; /* First read just the header, so we know how long the TOC is. */ len = sizeof(struct ioc_toc_header) + sizeof(struct cd_toc_entry); if (wcd_request_wait (t, ATAPI_READ_TOC, 0, 0, 0, 0, 0, 0, len >> 8, len & 0xff, 0, (char*)&t->toc, len) != 0) return (EIO); ntracks = t->toc.hdr.ending_track - t->toc.hdr.starting_track + 1; if (ntracks <= 0) return (EIO); if (ntracks > MAXTRK) ntracks = MAXTRK; /* Now read the whole schmeer. */ len = sizeof(struct ioc_toc_header) + (ntracks+1) * sizeof(struct cd_toc_entry); if (wcd_request_wait (t, ATAPI_READ_TOC, 0, 0, 0, 0, 0, 0, len >> 8, len & 0xff, 0, (char*)&t->toc, len) & 0xff) return (EIO); t->toc.hdr.len = ntohs (t->toc.hdr.len); for (i=0; i<=ntracks; i++) t->toc.tab[i].addr.lba = ntohl (t->toc.tab[i].addr.lba); return (0); } /* * Set up the audio channel masks. */ static int wcd_setchan (struct wcd *t, u_char c0, u_char c1, u_char c2, u_char c3) { int error; error = wcd_request_wait (t, ATAPI_MODE_SENSE, 0, AUDIO_PAGE, 0, 0, 0, 0, sizeof (t->au) >> 8, sizeof (t->au), 0, (char*) &t->au, sizeof (t->au)); if (error) return (error); if (t->flags & F_DEBUG) wcd_dump (t->lun, "au", &t->au, sizeof t->au); if (t->au.page_code != AUDIO_PAGE) return (EIO); t->au.port[0].channels = c0; t->au.port[1].channels = c1; t->au.port[2].channels = c2; t->au.port[3].channels = c3; return wcd_request_wait (t, ATAPI_MODE_SELECT_BIG, 0x10, 0, 0, 0, 0, 0, sizeof (t->au) >> 8, sizeof (t->au), 0, (char*) &t->au, - sizeof (t->au)); } #endif /* NWCD && NWDC && ATAPI */ Index: head/sys/i386/isa/wd.c =================================================================== --- head/sys/i386/isa/wd.c (revision 10600) +++ head/sys/i386/isa/wd.c (revision 10601) @@ -1,2056 +1,2061 @@ /*- * Copyright (c) 1990 The Regents of the University of California. * All rights reserved. * * This code is derived from software contributed to Berkeley by * William Jolitz. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. All advertising materials mentioning features or use of this software * must display the following acknowledgement: * This product includes software developed by the University of * California, Berkeley and its contributors. * 4. Neither the name of the University nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * * from: @(#)wd.c 7.2 (Berkeley) 5/9/91 - * $Id: wd.c,v 1.82 1995/08/18 11:26:35 jkh Exp $ + * $Id: wd.c,v 1.83 1995/09/06 05:06:18 dyson Exp $ */ /* TODO: * o Bump error count after timeout. * o Satisfy ATA timing in all cases. * o Finish merging berry/sos timeout code (bump error count...). * o Merge/fix TIH/NetBSD bad144 code. * o Don't use polling except for initialization. Need to * reorganize the state machine. Then "extra" interrupts * shouldn't happen (except maybe one for initialization). * o Fix disklabel, boot and driver inconsistencies with * bad144 in standard versions. * o Support extended DOS partitions. * o Support swapping to DOS partitions. * o Handle bad sectors, clustering, disklabelling, DOS * partitions and swapping driver-independently. Use * i386/dkbad.c for bad sectors. Swapping will need new * driver entries for polled reinit and polled write). */ #include "wd.h" -#if NWDC > 0 +#ifdef NWDC +#undef NWDC +#endif + +#include "wdc.h" +#if NWDC > 0 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef ATAPI #include #endif #define TIMEOUT 10000 #define RETRIES 5 /* number of retries before giving up */ #define RECOVERYTIME 500000 /* usec for controller to recover after err */ #define MAXTRANSFER 255 /* max size of transfer in sectors */ /* correct max is 256 but some controllers */ /* can't handle that in all cases */ #define WDOPT_32BIT 0x8000 #define WDOPT_SLEEPHACK 0x4000 #define WDOPT_MULTIMASK 0x00ff static int wd_goaway(struct kern_devconf *, int); static int wdc_goaway(struct kern_devconf *, int); static int wd_externalize(struct proc *, struct kern_devconf *, void *, size_t); static int wdc_externalize(struct proc *, struct kern_devconf *, void *, size_t); /* * Templates for the kern_devconf structures used when we attach. */ static struct kern_devconf kdc_wd[NWD] = { { 0, 0, 0, /* filled in by kern_devconf.c */ "wd", 0, { MDDT_DISK, 0 }, wd_externalize, 0, wd_goaway, DISK_EXTERNALLEN, 0, /* parent */ 0, /* parentdata */ DC_UNKNOWN, /* state */ "ST506/ESDI/IDE disk", /* description */ DC_CLS_DISK /* class */ } }; struct kern_devconf kdc_wdc[NWDC] = { { 0, 0, 0, /* filled in by kern_devconf.c */ "wdc", 0, { MDDT_ISA, 0 }, isa_generic_externalize, 0, wdc_goaway, ISA_EXTERNALLEN, &kdc_isa0, /* parent */ 0, /* parentdata */ DC_UNCONFIGURED, /* state */ "ST506/ESDI/IDE disk controller", DC_CLS_MISC /* just an ordinary device */ } }; static inline void wd_registerdev(int ctlr, int unit) { if(unit != 0) kdc_wd[unit] = kdc_wd[0]; kdc_wd[unit].kdc_unit = unit; kdc_wd[unit].kdc_parent = &kdc_wdc[ctlr]; dev_attach(&kdc_wd[unit]); } static inline void wdc_registerdev(struct isa_device *dvp) { int unit = dvp->id_unit; if(unit != 0) kdc_wdc[unit] = kdc_wdc[0]; kdc_wdc[unit].kdc_unit = unit; kdc_wdc[unit].kdc_parentdata = dvp; dev_attach(&kdc_wdc[unit]); } static int wdc_goaway(struct kern_devconf *kdc, int force) { if(force) { dev_detach(kdc); return 0; } else { return EBUSY; /* XXX fix */ } } static int wd_goaway(struct kern_devconf *kdc, int force) { dev_detach(kdc); return 0; } /* * This biotab field doubles as a field for the physical unit number on * the controller. */ #define id_physid id_scsiid /* * Drive states. Used to initialize drive. */ #define CLOSED 0 /* disk is closed. */ #define WANTOPEN 1 /* open requested, not started */ #define RECAL 2 /* doing restore */ #define OPEN 3 /* done with open */ /* * Disk geometry. A small part of struct disklabel. * XXX disklabel.5 contains an old clone of disklabel.h. */ struct diskgeom { u_long d_secsize; /* # of bytes per sector */ u_long d_nsectors; /* # of data sectors per track */ u_long d_ntracks; /* # of tracks per cylinder */ u_long d_ncylinders; /* # of data cylinders per unit */ u_long d_secpercyl; /* # of data sectors per cylinder */ u_long d_secperunit; /* # of data sectors per unit */ u_long d_precompcyl; /* XXX always 0 */ }; /* * The structure of a disk drive. */ struct disk { long dk_bc; /* byte count left */ short dk_skip; /* blocks already transferred */ char dk_ctrlr; /* physical controller number */ char dk_unit; /* physical unit number */ char dk_lunit; /* logical unit number */ char dk_state; /* control state */ u_char dk_status; /* copy of status reg. */ u_char dk_error; /* copy of error reg. */ u_char dk_timeout; /* countdown to next timeout */ short dk_port; /* i/o port base */ u_long cfg_flags; /* configured characteristics */ short dk_flags; /* drive characteristics found */ #define DKFL_SINGLE 0x00004 /* sector at a time mode */ #define DKFL_ERROR 0x00008 /* processing a disk error */ #define DKFL_LABELLING 0x00080 /* readdisklabel() in progress */ #define DKFL_32BIT 0x00100 /* use 32-bit i/o mode */ #define DKFL_MULTI 0x00200 /* use multi-i/o mode */ #define DKFL_BADSCAN 0x00400 /* report all errors */ struct wdparams dk_params; /* ESDI/IDE drive/controller parameters */ int dk_dkunit; /* disk stats unit number */ int dk_multi; /* multi transfers */ int dk_currentiosize; /* current io size */ struct diskgeom dk_dd; /* device configuration data */ struct diskslices *dk_slices; /* virtual drives */ }; #define WD_COUNT_RETRIES static int wdtest = 0; static struct disk *wddrives[NWD]; /* table of units */ static struct buf wdtab[NWDC]; static struct buf wdutab[NWD]; /* head of queue per drive */ #ifdef notyet static struct buf rwdbuf[NWD]; /* buffers for raw IO */ #endif static int wdprobe(struct isa_device *dvp); static int wdattach(struct isa_device *dvp); static void wdustart(struct disk *du); void wdstart(int ctrlr); static int wdcontrol(struct buf *bp); static int wdcommand(struct disk *du, u_int cylinder, u_int head, u_int sector, u_int count, u_int command); static int wdsetctlr(struct disk *du); static int wdwsetctlr(struct disk *du); static int wdgetctlr(struct disk *du); static void wderror(struct buf *bp, struct disk *du, char *mesg); static void wdflushirq(struct disk *du, int old_ipl); static int wdreset(struct disk *du); static void wdsleep(int ctrlr, char *wmesg); static void wdstrategy1(struct buf *bp); static timeout_t wdtimeout; static int wdunwedge(struct disk *du); static int wdwait(struct disk *du, u_char bits_wanted, int timeout); /* * Provide hw.devconf information. */ static int wd_externalize(struct proc *p, struct kern_devconf *kdc, void *userp, size_t len) { return disk_externalize(wddrives[kdc->kdc_unit]->dk_unit, userp, &len); } struct isa_driver wdcdriver = { wdprobe, wdattach, "wdc", }; /* * Probe for controller. */ static int wdprobe(struct isa_device *dvp) { int unit = dvp->id_unit; struct disk *du; if (unit >= NWDC) return (0); du = malloc(sizeof *du, M_TEMP, M_NOWAIT); if (du == NULL) return (0); bzero(du, sizeof *du); du->dk_ctrlr = dvp->id_unit; du->dk_port = dvp->id_iobase; wdc_registerdev(dvp); /* check if we have registers that work */ outb(du->dk_port + wd_cyl_lo, 0xa5); /* wd_cyl_lo is read/write */ if (inb(du->dk_port + wd_cyl_lo) == 0xff) /* XXX too weak */ goto nodevice; if (wdreset(du) != 0 && (DELAY(RECOVERYTIME), wdreset(du)) != 0) goto nodevice; /* execute a controller only command */ if (wdcommand(du, 0, 0, 0, 0, WDCC_DIAGNOSE) != 0 || wdwait(du, 0, TIMEOUT) < 0) goto nodevice; /* * drive(s) did not time out during diagnostic : * Get error status and check that both drives are OK. * Table 9-2 of ATA specs suggests that we must check for * a value of 0x01 * * Strangely, some controllers will return a status of * 0x81 (drive 0 OK, drive 1 failure), and then when * the DRV bit is set, return status of 0x01 (OK) for * drive 2. (This seems to contradict the ATA spec.) */ du->dk_error = inb(du->dk_port + wd_error); /* printf("Error : %x\n", du->dk_error); */ if(du->dk_error != 0x01) { if(du->dk_error & 0x80) { /* drive 1 failure */ /* first set the DRV bit */ u_int sdh; sdh = inb(du->dk_port+ wd_sdh); sdh = sdh | 0x10; outb(du->dk_port+ wd_sdh, sdh); /* Wait, to make sure drv 1 has completed diags */ if ( wdwait(du, 0, TIMEOUT) < 0) goto nodevice; /* Get status for drive 1 */ du->dk_error = inb(du->dk_port + wd_error); /* printf("Error (drv 1) : %x\n", du->dk_error); */ if(du->dk_error != 0x01) goto nodevice; } else /* drive 0 fail */ goto nodevice; } free(du, M_TEMP); return (IO_WDCSIZE); nodevice: free(du, M_TEMP); return (0); } /* * Attach each drive if possible. */ static int wdattach(struct isa_device *dvp) { int unit, lunit; struct isa_device *wdup; struct disk *du; if (dvp->id_unit >= NWDC) return (0); kdc_wdc[dvp->id_unit].kdc_state = DC_UNKNOWN; /* XXX */ for (wdup = isa_biotab_wdc; wdup->id_driver != 0; wdup++) { if (wdup->id_iobase != dvp->id_iobase) continue; lunit = wdup->id_unit; if (lunit >= NWD) continue; unit = wdup->id_physid; du = malloc(sizeof *du, M_TEMP, M_NOWAIT); if (du == NULL) continue; if (wddrives[lunit] != NULL) panic("drive attached twice"); wddrives[lunit] = du; bzero(du, sizeof *du); du->dk_ctrlr = dvp->id_unit; du->dk_unit = unit; du->dk_lunit = lunit; du->dk_port = dvp->id_iobase; /* * Use the individual device flags or the controller * flags. */ du->cfg_flags = wdup->id_flags | ((dvp->id_flags) >> (16 * unit)); if (wdgetctlr(du) == 0) { char buf[sizeof du->dk_params.wdp_model + 1]; /* * Print out description of drive. * wdp_model may not be null terminated, and printf * doesn't support "%.*s" :-(, so copy wdp_model * and add a null before printing. */ bcopy(du->dk_params.wdp_model, buf, sizeof buf - 1); buf[sizeof buf - 1] = '\0'; printf("wdc%d: unit %d (wd%d): <%s>", dvp->id_unit, unit, lunit, buf); if (du->dk_flags & DKFL_32BIT) printf(", 32-bit"); if (du->dk_multi > 1) printf(", multi-block-%d", du->dk_multi); if (du->cfg_flags & WDOPT_SLEEPHACK) printf(", sleep-hack"); printf("\n"); if (du->dk_params.wdp_heads == 0) printf("wd%d: size unknown, using %s values\n", lunit, du->dk_dd.d_secperunit > 17 ? "BIOS" : "fake"); printf( "wd%d: %luMB (%lu sectors), %lu cyls, %lu heads, %lu S/T, %lu B/S\n", lunit, du->dk_dd.d_secperunit * du->dk_dd.d_secsize / (1024 * 1024), du->dk_dd.d_secperunit, du->dk_dd.d_ncylinders, du->dk_dd.d_ntracks, du->dk_dd.d_nsectors, du->dk_dd.d_secsize); /* * Start timeout routine for this drive. * XXX timeout should be per controller. */ wdtimeout(du); wd_registerdev(dvp->id_unit, lunit); if (dk_ndrive < DK_NDRIVE) { sprintf(dk_names[dk_ndrive], "wd%d", lunit); /* * XXX we don't know the transfer rate of the * drive. Guess the maximum ISA rate of * 4MB/sec. `wpms' is words per _second_ * according to iostat. */ dk_wpms[dk_ndrive] = 4 * 1024 * 1024 / 2; du->dk_dkunit = dk_ndrive++; } else { du->dk_dkunit = -1; } } else { free(du, M_TEMP); wddrives[lunit] = NULL; } } #ifdef ATAPI /* * Probe all free IDE units, searching for ATAPI drives. */ for (unit=0; unit<2; ++unit) { for (lunit=0; lunitdk_ctrlr == dvp->id_unit && wddrives[lunit]->dk_unit == unit) goto next; atapi_attach (dvp->id_unit, unit, dvp->id_iobase, &kdc_wdc[dvp->id_unit]); next: } #endif /* * Discard any interrupts generated by wdgetctlr(). wdflushirq() * doesn't work now because the ambient ipl is too high. */ wdtab[dvp->id_unit].b_active = 2; return (1); } /* Read/write routine for a buffer. Finds the proper unit, range checks * arguments, and schedules the transfer. Does not wait for the transfer * to complete. Multi-page transfers are supported. All I/O requests must * be a multiple of a sector in length. */ void wdstrategy(register struct buf *bp) { register struct buf *dp; struct disk *du; int lunit = dkunit(bp->b_dev); int s; /* valid unit, controller, and request? */ if (lunit >= NWD || bp->b_blkno < 0 || (du = wddrives[lunit]) == NULL || bp->b_bcount % DEV_BSIZE != 0) { bp->b_error = EINVAL; bp->b_flags |= B_ERROR; goto done; } /* * Do bounds checking, adjust transfer, set b_cylin and b_pbklno. */ if (dscheck(bp, du->dk_slices) <= 0) goto done; /* * Check for *any* block on this transfer being on the bad block list * if it is, then flag the block as a transfer that requires * bad block handling. Also, used as a hint for low level disksort * clustering code to keep from coalescing a bad transfer into * a normal transfer. Single block transfers for a large number of * blocks associated with a cluster I/O are undesirable. * * XXX the old disksort() doesn't look at B_BAD. Coalescing _is_ * desirable. We should split the results at bad blocks just * like we should split them at MAXTRANSFER boundaries. */ if (dsgetbad(bp->b_dev, du->dk_slices) != NULL) { long *badsect = dsgetbad(bp->b_dev, du->dk_slices)->bi_bad; int i; int nsecs = howmany(bp->b_bcount, DEV_BSIZE); /* XXX pblkno is too physical. */ daddr_t nspblkno = bp->b_pblkno - du->dk_slices->dss_slices[dkslice(bp->b_dev)].ds_offset; int blkend = nspblkno + nsecs; for (i = 0; badsect[i] != -1 && badsect[i] < blkend; i++) { if (badsect[i] >= nspblkno) { bp->b_flags |= B_BAD; break; } } } /* queue transfer on drive, activate drive and controller if idle */ dp = &wdutab[lunit]; s = splbio(); disksort(dp, bp); if (dp->b_active == 0) wdustart(du); /* start drive */ /* Pick up changes made by readdisklabel(). */ if (du->dk_flags & DKFL_LABELLING && du->dk_state > RECAL) { wdsleep(du->dk_ctrlr, "wdlab"); du->dk_state = WANTOPEN; } if (wdtab[du->dk_ctrlr].b_active == 0) wdstart(du->dk_ctrlr); /* start controller */ if (du->dk_dkunit >= 0) { /* * XXX perhaps we should only count successful transfers. */ dk_xfer[du->dk_dkunit]++; /* * XXX we can't count seeks correctly but we can do better * than this. E.g., assume that the geometry is correct * and count 1 seek if the starting cylinder of this i/o * differs from the starting cylinder of the previous i/o, * or count 1 seek if the starting bn of this i/o doesn't * immediately follow the ending bn of the previos i/o. */ dk_seek[du->dk_dkunit]++; } splx(s); return; done: s = splbio(); /* toss transfer, we're done early */ biodone(bp); splx(s); } static void wdstrategy1(struct buf *bp) { /* * XXX - do something to make wdstrategy() but not this block while * we're doing dsinit() and dsioctl(). */ wdstrategy(bp); } /* * Routine to queue a command to the controller. The unit's * request is linked into the active list for the controller. * If the controller is idle, the transfer is started. */ static void wdustart(register struct disk *du) { register struct buf *bp, *dp = &wdutab[du->dk_lunit]; int ctrlr = du->dk_ctrlr; /* unit already active? */ if (dp->b_active) return; /* anything to start? */ bp = dp->b_actf; if (bp == NULL) return; dp->b_actf = bp->b_actf; bp->b_actf = NULL; /* link onto controller queue */ if (wdtab[ctrlr].b_actf == NULL) { wdtab[ctrlr].b_actf = bp; } else { *wdtab[ctrlr].b_actb = bp; } wdtab[ctrlr].b_actb = &bp->b_actf; /* mark the drive unit as busy */ dp->b_active = 1; } /* * Controller startup routine. This does the calculation, and starts * a single-sector read or write operation. Called to start a transfer, * or from the interrupt routine to continue a multi-sector transfer. * RESTRICTIONS: * 1. The transfer length must be an exact multiple of the sector size. */ void wdstart(int ctrlr) { register struct disk *du; register struct buf *bp; struct diskgeom *lp; /* XXX sic */ struct buf *dp; long blknum; long secpertrk, secpercyl; int lunit; int count; if (wdtab[ctrlr].b_active == 2) wdtab[ctrlr].b_active = 0; if (wdtab[ctrlr].b_active) return; loop: /* is there a drive for the controller to do a transfer with? */ bp = wdtab[ctrlr].b_actf; if (bp == NULL) { #ifdef ATAPI if (atapi_start (ctrlr)) /* mark controller active in ATAPI mode */ wdtab[ctrlr].b_active = 3; #endif return; } /* obtain controller and drive information */ lunit = dkunit(bp->b_dev); du = wddrives[lunit]; /* if not really a transfer, do control operations specially */ if (du->dk_state < OPEN) { if (du->dk_state != WANTOPEN) printf("wd%d: wdstart: weird dk_state %d\n", du->dk_lunit, du->dk_state); if (wdcontrol(bp) != 0) printf("wd%d: wdstart: wdcontrol returned nonzero, state = %d\n", du->dk_lunit, du->dk_state); return; } /* calculate transfer details */ blknum = bp->b_pblkno + du->dk_skip; #ifdef WDDEBUG if (du->dk_skip == 0) printf("wd%d: wdstart: %s %d@%d; map ", lunit, (bp->b_flags & B_READ) ? "read" : "write", bp->b_bcount, blknum); else printf(" %d)%x", du->dk_skip, inb(du->dk_port + wd_altsts)); #endif lp = &du->dk_dd; secpertrk = lp->d_nsectors; secpercyl = lp->d_secpercyl; if (du->dk_skip == 0) { du->dk_bc = bp->b_bcount; if (bp->b_flags & B_BAD /* * XXX handle large transfers inefficiently instead * of crashing on them. */ || howmany(du->dk_bc, DEV_BSIZE) > MAXTRANSFER) du->dk_flags |= DKFL_SINGLE; } if (du->dk_flags & DKFL_SINGLE && dsgetbad(bp->b_dev, du->dk_slices) != NULL) { /* XXX */ u_long ds_offset = du->dk_slices->dss_slices[dkslice(bp->b_dev)].ds_offset; blknum = transbad144(dsgetbad(bp->b_dev, du->dk_slices), blknum - ds_offset) + ds_offset; } wdtab[ctrlr].b_active = 1; /* mark controller active */ /* if starting a multisector transfer, or doing single transfers */ if (du->dk_skip == 0 || (du->dk_flags & DKFL_SINGLE)) { u_int command; u_int count; long cylin, head, sector; cylin = blknum / secpercyl; head = (blknum % secpercyl) / secpertrk; sector = blknum % secpertrk; if (wdtab[ctrlr].b_errcnt && (bp->b_flags & B_READ) == 0) du->dk_bc += DEV_BSIZE; count = howmany( du->dk_bc, DEV_BSIZE); du->dk_flags &= ~DKFL_MULTI; #ifdef B_FORMAT if (bp->b_flags & B_FORMAT) { command = WDCC_FORMAT; count = lp->d_nsectors; sector = lp->d_gap3 - 1; /* + 1 later */ } else #endif { if (du->dk_flags & DKFL_SINGLE) { command = (bp->b_flags & B_READ) ? WDCC_READ : WDCC_WRITE; count = 1; du->dk_currentiosize = 1; } else { if( (count > 1) && (du->dk_multi > 1)) { du->dk_flags |= DKFL_MULTI; if( bp->b_flags & B_READ) { command = WDCC_READ_MULTI; } else { command = WDCC_WRITE_MULTI; } du->dk_currentiosize = du->dk_multi; if( du->dk_currentiosize > count) du->dk_currentiosize = count; } else { if( bp->b_flags & B_READ) { command = WDCC_READ; } else { command = WDCC_WRITE; } du->dk_currentiosize = 1; } } } /* * XXX this loop may never terminate. The code to handle * counting down of retries and eventually failing the i/o * is in wdintr() and we can't get there from here. */ if (wdtest != 0) { if (--wdtest == 0) { wdtest = 100; printf("dummy wdunwedge\n"); wdunwedge(du); } } if(du->dk_dkunit >= 0) { dk_busy |= 1 << du->dk_dkunit; } while (wdcommand(du, cylin, head, sector, count, command) != 0) { wderror(bp, du, "wdstart: timeout waiting to give command"); wdunwedge(du); } #ifdef WDDEBUG printf("cylin %ld head %ld sector %ld addr %x sts %x\n", cylin, head, sector, (int)bp->b_un.b_addr + du->dk_skip * DEV_BSIZE, inb(du->dk_port + wd_altsts)); #endif } /* * Schedule wdtimeout() to wake up after a few seconds. Retrying * unmarked bad blocks can take 3 seconds! Then it is not good that * we retry 5 times. * * XXX wdtimeout() doesn't increment the error count so we may loop * forever. More seriously, the loop isn't forever but causes a * crash. * * TODO fix b_resid bug elsewhere (fd.c....). Fix short but positive * counts being discarded after there is an error (in physio I * think). Discarding them would be OK if the (special) file offset * was not advanced. */ du->dk_timeout = 1 + 3; /* If this is a read operation, just go away until it's done. */ if (bp->b_flags & B_READ) return; /* Ready to send data? */ if (wdwait(du, WDCS_READY | WDCS_SEEKCMPLT | WDCS_DRQ, TIMEOUT) < 0) { wderror(bp, du, "wdstart: timeout waiting for DRQ"); /* * XXX what do we do now? If we've just issued the command, * then we can treat this failure the same as a command * failure. But if we are continuing a multi-sector write, * the command was issued ages ago, so we can't simply * restart it. * * XXX we waste a lot of time unnecessarily translating block * numbers to cylin/head/sector for continued i/o's. */ } count = 1; if( du->dk_flags & DKFL_MULTI) { count = howmany(du->dk_bc, DEV_BSIZE); if( count > du->dk_multi) count = du->dk_multi; if( du->dk_currentiosize > count) du->dk_currentiosize = count; } if (du->dk_flags & DKFL_32BIT) outsl(du->dk_port + wd_data, (void *)((int)bp->b_un.b_addr + du->dk_skip * DEV_BSIZE), (count * DEV_BSIZE) / sizeof(long)); else outsw(du->dk_port + wd_data, (void *)((int)bp->b_un.b_addr + du->dk_skip * DEV_BSIZE), (count * DEV_BSIZE) / sizeof(short)); du->dk_bc -= DEV_BSIZE * count; if (du->dk_dkunit >= 0) { /* * `wd's are blocks of 32 16-bit `word's according to * iostat. dk_wds[] is the one disk i/o statistic that * we can record correctly. * XXX perhaps we shouldn't record words for failed * transfers. */ dk_wds[du->dk_dkunit] += (count * DEV_BSIZE) >> 6; } } /* Interrupt routine for the controller. Acknowledge the interrupt, check for * errors on the current operation, mark it done if necessary, and start * the next request. Also check for a partially done transfer, and * continue with the next chunk if so. */ void wdintr(int unit) { register struct disk *du; register struct buf *bp, *dp; if (wdtab[unit].b_active == 2) return; /* intr in wdflushirq() */ if (!wdtab[unit].b_active) { #ifdef WDDEBUG /* * These happen mostly because the power-mgt part of the * bios shuts us down, and we just manage to see the * interrupt from the "SLEEP" command. */ printf("wdc%d: extra interrupt\n", unit); #endif return; } #ifdef ATAPI if (wdtab[unit].b_active == 3) { /* process an ATAPI interrupt */ if (atapi_intr (unit)) /* ATAPI op continues */ return; /* controller is free, start new op */ wdtab[unit].b_active = 0; wdstart (unit); return; } #endif bp = wdtab[unit].b_actf; du = wddrives[dkunit(bp->b_dev)]; dp = &wdutab[du->dk_lunit]; du->dk_timeout = 0; if (wdwait(du, 0, TIMEOUT) < 0) { wderror(bp, du, "wdintr: timeout waiting for status"); du->dk_status |= WDCS_ERR; /* XXX */ } /* is it not a transfer, but a control operation? */ if (du->dk_state < OPEN) { wdtab[unit].b_active = 0; switch (wdcontrol(bp)) { case 0: return; case 1: wdstart(unit); return; case 2: goto done; } } /* have we an error? */ if (du->dk_status & (WDCS_ERR | WDCS_ECCCOR)) { oops: /* * XXX bogus inb() here, register 0 is assumed and intr status * is reset. */ if( (du->dk_status & DKFL_MULTI) && (inb(du->dk_port) & WDERR_ABORT)) { wderror(bp, du, "reverting to non-multi sector mode"); du->dk_multi = 1; } #ifdef WDDEBUG wderror(bp, du, "wdintr"); #endif if ((du->dk_flags & DKFL_SINGLE) == 0) { du->dk_flags |= DKFL_ERROR; goto outt; } #ifdef B_FORMAT if (bp->b_flags & B_FORMAT) { bp->b_error = EIO; bp->b_flags |= B_ERROR; goto done; } #endif if (du->dk_flags & DKFL_BADSCAN) { bp->b_error = EIO; bp->b_flags |= B_ERROR; } else if (du->dk_status & WDCS_ERR) { if (++wdtab[unit].b_errcnt < RETRIES) { wdtab[unit].b_active = 0; } else { wderror(bp, du, "hard error"); bp->b_error = EIO; bp->b_flags |= B_ERROR; /* flag the error */ } } else wderror(bp, du, "soft ecc"); } /* * If this was a successful read operation, fetch the data. */ if (((bp->b_flags & (B_READ | B_ERROR)) == B_READ) && wdtab[unit].b_active) { int chk, dummy, multisize; multisize = chk = du->dk_currentiosize * DEV_BSIZE; if( du->dk_bc < chk) { chk = du->dk_bc; if( ((chk + DEV_BSIZE - 1) / DEV_BSIZE) < du->dk_currentiosize) { du->dk_currentiosize = (chk + DEV_BSIZE - 1) / DEV_BSIZE; multisize = du->dk_currentiosize * DEV_BSIZE; } } /* ready to receive data? */ if ((du->dk_status & (WDCS_READY | WDCS_SEEKCMPLT | WDCS_DRQ)) != (WDCS_READY | WDCS_SEEKCMPLT | WDCS_DRQ)) wderror(bp, du, "wdintr: read intr arrived early"); if (wdwait(du, WDCS_READY | WDCS_SEEKCMPLT | WDCS_DRQ, TIMEOUT) != 0) { wderror(bp, du, "wdintr: read error detected late"); goto oops; } /* suck in data */ if( du->dk_flags & DKFL_32BIT) insl(du->dk_port + wd_data, (void *)((int)bp->b_un.b_addr + du->dk_skip * DEV_BSIZE), chk / sizeof(long)); else insw(du->dk_port + wd_data, (void *)((int)bp->b_un.b_addr + du->dk_skip * DEV_BSIZE), chk / sizeof(short)); du->dk_bc -= chk; /* XXX for obsolete fractional sector reads. */ while (chk < multisize) { insw(du->dk_port + wd_data, &dummy, 1); chk += sizeof(short); } if (du->dk_dkunit >= 0) dk_wds[du->dk_dkunit] += chk >> 6; } outt: if (wdtab[unit].b_active) { if ((bp->b_flags & B_ERROR) == 0) { du->dk_skip += du->dk_currentiosize;/* add to successful sectors */ if (wdtab[unit].b_errcnt) wderror(bp, du, "soft error"); wdtab[unit].b_errcnt = 0; /* see if more to transfer */ if (du->dk_bc > 0 && (du->dk_flags & DKFL_ERROR) == 0) { if( (du->dk_flags & DKFL_SINGLE) || ((bp->b_flags & B_READ) == 0)) { wdtab[unit].b_active = 0; wdstart(unit); } else { du->dk_timeout = 1 + 3; } return; /* next chunk is started */ } else if ((du->dk_flags & (DKFL_SINGLE | DKFL_ERROR)) == DKFL_ERROR) { du->dk_skip = 0; du->dk_flags &= ~DKFL_ERROR; du->dk_flags |= DKFL_SINGLE; wdtab[unit].b_active = 0; wdstart(unit); return; /* redo xfer sector by sector */ } } done: ; /* done with this transfer, with or without error */ du->dk_flags &= ~DKFL_SINGLE; wdtab[unit].b_actf = bp->b_actf; wdtab[unit].b_errcnt = 0; bp->b_resid = bp->b_bcount - du->dk_skip * DEV_BSIZE; dp->b_active = 0; dp->b_errcnt = 0; du->dk_skip = 0; biodone(bp); } if(du->dk_dkunit >= 0) { dk_busy &= ~(1 << du->dk_dkunit); } /* controller idle */ wdtab[unit].b_active = 0; /* anything more on drive queue? */ wdustart(du); /* anything more for controller to do? */ #ifndef ATAPI /* This is not valid in ATAPI mode. */ if (wdtab[unit].b_actf) #endif wdstart(unit); } /* * Initialize a drive. */ int wdopen(dev_t dev, int flags, int fmt, struct proc *p) { register unsigned int lunit; register struct disk *du; int error; int part = dkpart(dev), mask = 1 << part; struct partition *pp; char *msg; lunit = dkunit(dev); if (lunit >= NWD || dktype(dev) != 0) return (ENXIO); du = wddrives[lunit]; if (du == NULL) return (ENXIO); /* Finish flushing IRQs left over from wdattach(). */ if (wdtab[du->dk_ctrlr].b_active == 2) wdtab[du->dk_ctrlr].b_active = 0; du->dk_flags &= ~DKFL_BADSCAN; while (du->dk_flags & DKFL_LABELLING) tsleep((caddr_t)&du->dk_flags, PZERO - 1, "wdopen", 1); #if 1 wdsleep(du->dk_ctrlr, "wdopn1"); du->dk_flags |= DKFL_LABELLING; du->dk_state = WANTOPEN; wdutab[lunit].b_actf = NULL; { struct disklabel label; bzero(&label, sizeof label); label.d_secsize = du->dk_dd.d_secsize; label.d_nsectors = du->dk_dd.d_nsectors; label.d_ntracks = du->dk_dd.d_ntracks; label.d_ncylinders = du->dk_dd.d_ncylinders; label.d_secpercyl = du->dk_dd.d_secpercyl; label.d_secperunit = du->dk_dd.d_secperunit; error = dsopen("wd", dev, fmt, &du->dk_slices, &label, wdstrategy1, (ds_setgeom_t *)NULL); } du->dk_flags &= ~DKFL_LABELLING; wdsleep(du->dk_ctrlr, "wdopn2"); return (error); #else if ((du->dk_flags & DKFL_BSDLABEL) == 0) { /* * wdtab[ctrlr].b_active != 0 implies * wdutab[lunit].b_actf == NULL (?) * so the following guards most things (until the next i/o). * It doesn't guard against a new i/o starting and being * affected by the label being changed. Sigh. */ wdsleep(du->dk_ctrlr, "wdopn1"); du->dk_flags |= DKFL_LABELLING; du->dk_state = WANTOPEN; wdutab[lunit].b_actf = NULL; error = dsinit(dkmodpart(dev, RAW_PART), wdstrategy, &du->dk_dd, &du->dk_slices); if (error != 0) { du->dk_flags &= ~DKFL_LABELLING; return (error); } /* XXX check value returned by wdwsetctlr(). */ wdwsetctlr(du); if (dkslice(dev) == WHOLE_DISK_SLICE) { dsopen(dev, fmt, du->dk_slices); return (0); } /* * Read label using RAW_PART partition. * * If the drive has an MBR, then the current geometry (from * wdgetctlr()) is used to read it; then the BIOS/DOS * geometry is inferred and used to read the label off the * 'c' partition. Otherwise the label is read using the * current geometry. The label gives the final geometry. * If bad sector handling is enabled, then this geometry * is used to read the bad sector table. The geometry * changes occur inside readdisklabel() and are propagated * to the driver by resetting the state machine. * * XXX can now handle changes directly since dsinit() doesn't * do too much. */ msg = correct_readdisklabel(dkmodpart(dev, RAW_PART), wdstrategy, &du->dk_dd); /* XXX check value returned by wdwsetctlr(). */ wdwsetctlr(du); if (msg == NULL && du->dk_dd.d_flags & D_BADSECT) msg = readbad144(dkmodpart(dev, RAW_PART), wdstrategy, &du->dk_dd, &du->dk_bad); du->dk_flags &= ~DKFL_LABELLING; if (msg != NULL) { log(LOG_WARNING, "wd%d: cannot find label (%s)\n", lunit, msg); if (part != RAW_PART) return (EINVAL); /* XXX needs translation */ /* * Soon return. This is how slices without labels * are allowed. They only work on the raw partition. */ } else { unsigned long newsize, offset, size; #if 0 /* * Force RAW_PART partition to be the whole disk. */ offset = du->dk_dd.d_partitions[RAW_PART].p_offset; if (offset != 0) { printf( "wd%d: changing offset of '%c' partition from %lu to 0\n", du->dk_lunit, 'a' + RAW_PART, offset); du->dk_dd.d_partitions[RAW_PART].p_offset = 0; } size = du->dk_dd.d_partitions[RAW_PART].p_size; newsize = du->dk_dd.d_secperunit; /* XXX */ if (size != newsize) { printf( "wd%d: changing size of '%c' partition from %lu to %lu\n", du->dk_lunit, 'a' + RAW_PART, size, newsize); du->dk_dd.d_partitions[RAW_PART].p_size = newsize; } #endif } /* Pick up changes made by readdisklabel(). */ wdsleep(du->dk_ctrlr, "wdopn2"); du->dk_state = WANTOPEN; } /* * Warn if a partion is opened that overlaps another partition which * is open unless one is the "raw" partition (whole disk). */ if ((du->dk_openpart & mask) == 0 && part != RAW_PART) { int start, end; pp = &du->dk_dd.d_partitions[part]; start = pp->p_offset; end = pp->p_offset + pp->p_size; for (pp = du->dk_dd.d_partitions; pp < &du->dk_dd.d_partitions[du->dk_dd.d_npartitions]; pp++) { if (pp->p_offset + pp->p_size <= start || pp->p_offset >= end) continue; if (pp - du->dk_dd.d_partitions == RAW_PART) continue; if (du->dk_openpart & (1 << (pp - du->dk_dd.d_partitions))) log(LOG_WARNING, "wd%d%c: overlaps open partition (%c)\n", lunit, part + 'a', pp - du->dk_dd.d_partitions + 'a'); } } if (part >= du->dk_dd.d_npartitions && part != RAW_PART) return (ENXIO); dsopen(dev, fmt, du->dk_slices); return (0); #endif } /* * Implement operations other than read/write. * Called from wdstart or wdintr during opens and formats. * Uses finite-state-machine to track progress of operation in progress. * Returns 0 if operation still in progress, 1 if completed, 2 if error. */ static int wdcontrol(register struct buf *bp) { register struct disk *du; int ctrlr; du = wddrives[dkunit(bp->b_dev)]; ctrlr = du->dk_ctrlr; switch (du->dk_state) { case WANTOPEN: tryagainrecal: wdtab[ctrlr].b_active = 1; if (wdcommand(du, 0, 0, 0, 0, WDCC_RESTORE | WD_STEP) != 0) { wderror(bp, du, "wdcontrol: wdcommand failed"); goto maybe_retry; } du->dk_state = RECAL; return (0); case RECAL: if (du->dk_status & WDCS_ERR || wdsetctlr(du) != 0) { wderror(bp, du, "wdcontrol: recal failed"); maybe_retry: if (du->dk_status & WDCS_ERR) wdunwedge(du); du->dk_state = WANTOPEN; if (++wdtab[ctrlr].b_errcnt < RETRIES) goto tryagainrecal; bp->b_error = ENXIO; /* XXX needs translation */ bp->b_flags |= B_ERROR; return (2); } wdtab[ctrlr].b_errcnt = 0; du->dk_state = OPEN; /* * The rest of the initialization can be done by normal * means. */ return (1); } panic("wdcontrol"); return (2); } /* * Wait uninterruptibly until controller is not busy, then send it a command. * The wait usually terminates immediately because we waited for the previous * command to terminate. */ static int wdcommand(struct disk *du, u_int cylinder, u_int head, u_int sector, u_int count, u_int command) { u_int wdc; wdc = du->dk_port; if (du->cfg_flags & WDOPT_SLEEPHACK) if(inb(wdc + wd_status) == WDCS_BUSY) wdunwedge(du); if (wdwait(du, 0, TIMEOUT) < 0) return (1); if( command == WDCC_FEATURES) { outb(wdc + wd_features, count); } else { outb(wdc + wd_precomp, du->dk_dd.d_precompcyl / 4); outb(wdc + wd_cyl_lo, cylinder); outb(wdc + wd_cyl_hi, cylinder >> 8); outb(wdc + wd_sdh, WDSD_IBM | (du->dk_unit << 4) | head); outb(wdc + wd_sector, sector + 1); outb(wdc + wd_seccnt, count); } if (wdwait(du, command == WDCC_DIAGNOSE || command == WDCC_IDC ? 0 : WDCS_READY, TIMEOUT) < 0) return (1); outb(wdc + wd_command, command); return (0); } /* * issue IDC to drive to tell it just what geometry it is to be. */ static int wdsetctlr(struct disk *du) { int error = 0; #ifdef WDDEBUG printf("wd(%d,%d): wdsetctlr: C %lu H %lu S %lu\n", du->dk_ctrlr, du->dk_unit, du->dk_dd.d_ncylinders, du->dk_dd.d_ntracks, du->dk_dd.d_nsectors); #endif if (du->dk_dd.d_ntracks == 0 || du->dk_dd.d_ntracks > 16) { struct wdparams *wp; printf("wd%d: can't handle %lu heads from partition table ", du->dk_lunit, du->dk_dd.d_ntracks); /* obtain parameters */ wp = &du->dk_params; if (wp->wdp_heads > 0 && wp->wdp_heads <= 16) { printf("(controller value %u restored)\n", wp->wdp_heads); du->dk_dd.d_ntracks = wp->wdp_heads; } else { printf("(truncating to 16)\n"); du->dk_dd.d_ntracks = 16; } } if (du->dk_dd.d_nsectors == 0 || du->dk_dd.d_nsectors > 255) { printf("wd%d: cannot handle %lu sectors (max 255)\n", du->dk_lunit, du->dk_dd.d_nsectors); error = 1; } if (error) { wdtab[du->dk_ctrlr].b_errcnt += RETRIES; return (1); } if (wdcommand(du, du->dk_dd.d_ncylinders, du->dk_dd.d_ntracks - 1, 0, du->dk_dd.d_nsectors, WDCC_IDC) != 0 || wdwait(du, WDCS_READY, TIMEOUT) < 0) { wderror((struct buf *)NULL, du, "wdsetctlr failed"); return (1); } return (0); } /* * Wait until driver is inactive, then set up controller. */ static int wdwsetctlr(struct disk *du) { int stat; int x; wdsleep(du->dk_ctrlr, "wdwset"); x = splbio(); stat = wdsetctlr(du); wdflushirq(du, x); splx(x); return (stat); } /* * issue READP to drive to ask it what it is. */ static int wdgetctlr(struct disk *du) { int i; char tb[DEV_BSIZE], tb2[DEV_BSIZE]; struct wdparams *wp = NULL; u_long flags = du->cfg_flags; again: if (wdcommand(du, 0, 0, 0, 0, WDCC_READP) != 0 || wdwait(du, WDCS_READY | WDCS_SEEKCMPLT | WDCS_DRQ, TIMEOUT) != 0) { /* * if we failed on the second try, assume non-32bit */ if( du->dk_flags & DKFL_32BIT) goto failed; /* XXX need to check error status after final transfer. */ /* * Old drives don't support WDCC_READP. Try a seek to 0. * Some IDE controllers return trash if there is no drive * attached, so first test that the drive can be selected. * This also avoids long waits for nonexistent drives. */ if (wdwait(du, 0, TIMEOUT) < 0) return (1); outb(du->dk_port + wd_sdh, WDSD_IBM | (du->dk_unit << 4)); DELAY(5000); /* usually unnecessary; drive select is fast */ if ((inb(du->dk_port + wd_status) & (WDCS_BUSY | WDCS_READY)) != WDCS_READY || wdcommand(du, 0, 0, 0, 0, WDCC_RESTORE | WD_STEP) != 0 || wdwait(du, WDCS_READY | WDCS_SEEKCMPLT, TIMEOUT) != 0) return (1); if (du->dk_unit == bootinfo.bi_n_bios_used) { du->dk_dd.d_secsize = DEV_BSIZE; du->dk_dd.d_nsectors = bootinfo.bi_bios_geom[du->dk_unit] & 0xff; du->dk_dd.d_ntracks = ((bootinfo.bi_bios_geom[du->dk_unit] >> 8) & 0xff) + 1; /* XXX Why 2 ? */ du->dk_dd.d_ncylinders = (bootinfo.bi_bios_geom[du->dk_unit] >> 16) + 2; du->dk_dd.d_secpercyl = du->dk_dd.d_ntracks * du->dk_dd.d_nsectors; du->dk_dd.d_secperunit = du->dk_dd.d_secpercyl * du->dk_dd.d_ncylinders; #if 0 du->dk_dd.d_partitions[WDRAW].p_size = du->dk_dd.d_secperunit; du->dk_dd.d_type = DTYPE_ST506; du->dk_dd.d_subtype |= DSTYPE_GEOMETRY; strncpy(du->dk_dd.d_typename, "Bios geometry", sizeof du->dk_dd.d_typename); strncpy(du->dk_params.wdp_model, "ST506", sizeof du->dk_params.wdp_model); #endif bootinfo.bi_n_bios_used ++; return 0; } /* * Fake minimal drive geometry for reading the MBR. * readdisklabel() may enlarge it to read the label and the * bad sector table. */ du->dk_dd.d_secsize = DEV_BSIZE; du->dk_dd.d_nsectors = 17; du->dk_dd.d_ntracks = 1; du->dk_dd.d_ncylinders = 1; du->dk_dd.d_secpercyl = 17; du->dk_dd.d_secperunit = 17; #if 0 /* * Fake maximal drive size for writing the label. */ du->dk_dd.d_partitions[RAW_PART].p_size = 64 * 16 * 1024; /* * Fake some more of the label for printing by disklabel(1) * in case there is no real label. */ du->dk_dd.d_type = DTYPE_ST506; du->dk_dd.d_subtype |= DSTYPE_GEOMETRY; strncpy(du->dk_dd.d_typename, "Fake geometry", sizeof du->dk_dd.d_typename); #endif /* Fake the model name for printing by wdattach(). */ strncpy(du->dk_params.wdp_model, "unknown", sizeof du->dk_params.wdp_model); return (0); } /* obtain parameters */ wp = &du->dk_params; if (du->dk_flags & DKFL_32BIT) insl(du->dk_port + wd_data, tb, sizeof(tb) / sizeof(long)); else insw(du->dk_port + wd_data, tb, sizeof(tb) / sizeof(short)); /* try 32-bit data path (VLB IDE controller) */ if (flags & WDOPT_32BIT) { if (! (du->dk_flags & DKFL_32BIT)) { bcopy(tb, tb2, sizeof(struct wdparams)); du->dk_flags |= DKFL_32BIT; goto again; } /* check that we really have 32-bit controller */ if (bcmp (tb, tb2, sizeof(struct wdparams)) != 0) { failed: /* test failed, use 16-bit i/o mode */ bcopy(tb2, tb, sizeof(struct wdparams)); du->dk_flags &= ~DKFL_32BIT; } } bcopy(tb, wp, sizeof(struct wdparams)); /* shuffle string byte order */ for (i = 0; i < sizeof(wp->wdp_model); i += 2) { u_short *p; p = (u_short *) (wp->wdp_model + i); *p = ntohs(*p); } /* * Clean up the wdp_model by converting nulls to spaces, and * then removing the trailing spaces. */ for (i=0; i < sizeof(wp->wdp_model); i++) { if (wp->wdp_model[i] == '\0') { wp->wdp_model[i] = ' '; } } for (i=sizeof(wp->wdp_model)-1; i>=0 && wp->wdp_model[i]==' '; i--) { wp->wdp_model[i] = '\0'; } #ifdef WDDEBUG printf( "\nwd(%d,%d): wdgetctlr: gc %x cyl %d trk %d sec %d type %d sz %d model %s\n", du->dk_ctrlr, du->dk_unit, wp->wdp_config, wp->wdp_fixedcyl + wp->wdp_removcyl, wp->wdp_heads, wp->wdp_sectors, wp->wdp_cntype, wp->wdp_cnsbsz, wp->wdp_model); #endif /* update disklabel given drive information */ du->dk_dd.d_secsize = DEV_BSIZE; du->dk_dd.d_ncylinders = wp->wdp_fixedcyl + wp->wdp_removcyl /*+- 1*/ ; du->dk_dd.d_ntracks = wp->wdp_heads; du->dk_dd.d_nsectors = wp->wdp_sectors; du->dk_dd.d_secpercyl = du->dk_dd.d_ntracks * du->dk_dd.d_nsectors; du->dk_dd.d_secperunit = du->dk_dd.d_secpercyl * du->dk_dd.d_ncylinders; #if 0 du->dk_dd.d_partitions[RAW_PART].p_size = du->dk_dd.d_secperunit; /* dubious ... */ bcopy("ESDI/IDE", du->dk_dd.d_typename, 9); bcopy(wp->wdp_model + 20, du->dk_dd.d_packname, 14 - 1); /* better ... */ du->dk_dd.d_type = DTYPE_ESDI; du->dk_dd.d_subtype |= DSTYPE_GEOMETRY; #endif /* * find out the drives maximum multi-block transfer capability */ du->dk_multi = wp->wdp_nsecperint & 0xff; /* * The config option flags low 8 bits define the maximum multi-block * transfer size. If the user wants the maximum that the drive * is capable of, just set the low bits of the config option to * 0x00ff. */ if ((flags & WDOPT_MULTIMASK) != 0 && (du->dk_multi > 1)) { if (du->dk_multi > (flags & WDOPT_MULTIMASK)) du->dk_multi = flags & WDOPT_MULTIMASK; if (wdcommand(du, 0, 0, 0, du->dk_multi, WDCC_SET_MULTI)) { du->dk_multi = 1; } } else { du->dk_multi = 1; } #ifdef NOTYET /* set read caching and write caching */ wdcommand(du, 0, 0, 0, WDFEA_RCACHE, WDCC_FEATURES); wdcommand(du, 0, 0, 0, WDFEA_WCACHE, WDCC_FEATURES); #endif return (0); } /* ARGSUSED */ int wdclose(dev_t dev, int flags, int fmt, struct proc *p) { dsclose(dev, fmt, wddrives[dkunit(dev)]->dk_slices); return (0); } int wdioctl(dev_t dev, int cmd, caddr_t addr, int flags, struct proc *p) { int lunit = dkunit(dev); register struct disk *du; int error; #ifdef notyet struct uio auio; struct iovec aiov; struct format_op *fop; #endif du = wddrives[lunit]; wdsleep(du->dk_ctrlr, "wdioct"); error = dsioctl("wd", dev, cmd, addr, flags, &du->dk_slices, wdstrategy1, (ds_setgeom_t *)NULL); if (error != -1) return (error); switch (cmd) { case DIOCSBADSCAN: if (*(int *)addr) du->dk_flags |= DKFL_BADSCAN; else du->dk_flags &= ~DKFL_BADSCAN; return (0); #ifdef notyet case DIOCWFORMAT: if (!(flag & FWRITE)) return (EBADF); fop = (struct format_op *)addr; aiov.iov_base = fop->df_buf; aiov.iov_len = fop->df_count; auio.uio_iov = &aiov; auio.uio_iovcnt = 1; auio.uio_resid = fop->df_count; auio.uio_segflg = 0; auio.uio_offset = fop->df_startblk * du->dk_dd.d_secsize; #error /* XXX the 386BSD interface is different */ error = physio(wdformat, &rwdbuf[lunit], 0, dev, B_WRITE, minphys, &auio); fop->df_count -= auio.uio_resid; fop->df_reg[0] = du->dk_status; fop->df_reg[1] = du->dk_error; return (error); #endif default: return (ENOTTY); } } #ifdef B_FORMAT int wdformat(struct buf *bp) { bp->b_flags |= B_FORMAT; wdstrategy(bp); /* * phk put this here, better that return(wdstrategy(bp)); * XXX */ return -1; } #endif int wdsize(dev_t dev) { struct disk *du; int lunit; lunit = dkunit(dev); if (lunit >= NWD || dktype(dev) != 0) return (-1); du = wddrives[lunit]; if (du == NULL) return (-1); return (dssize(dev, &du->dk_slices, wdopen, wdclose)); } /* * Dump core after a system crash. */ int wddump(dev_t dev) { register struct disk *du; struct disklabel *lp; long num; /* number of sectors to write */ int lunit, part; long blkoff, blknum; long blkchk, blkcnt, blknext; long cylin, head, sector; long secpertrk, secpercyl, nblocks; u_long ds_offset; char *addr; static int wddoingadump = 0; /* Toss any characters present prior to dump. */ while (cncheckc()) ; /* Check for acceptable device. */ /* XXX should reset to maybe allow du->dk_state < OPEN. */ lunit = dkunit(dev); /* eventually support floppies? */ part = dkpart(dev); if (lunit >= NWD || (du = wddrives[lunit]) == NULL || du->dk_state < OPEN || (lp = dsgetlabel(dev, du->dk_slices)) == NULL) return (ENXIO); /* Size of memory to dump, in disk sectors. */ num = (u_long)Maxmem * NBPG / du->dk_dd.d_secsize; secpertrk = du->dk_dd.d_nsectors; secpercyl = du->dk_dd.d_secpercyl; nblocks = lp->d_partitions[part].p_size; blkoff = lp->d_partitions[part].p_offset; /* XXX */ ds_offset = du->dk_slices->dss_slices[dkslice(dev)].ds_offset; blkoff += ds_offset; #if 0 pg("part %x, nblocks %d, dumplo %d num %d\n", part, nblocks, dumplo, num); #endif /* Check transfer bounds against partition size. */ if (dumplo < 0 || dumplo + num > nblocks) return (EINVAL); /* Check if we are being called recursively. */ if (wddoingadump) return (EFAULT); #if 0 /* Mark controller active for if we panic during the dump. */ wdtab[du->dk_ctrlr].b_active = 1; #endif wddoingadump = 1; /* Recalibrate the drive. */ DELAY(5); /* ATA spec XXX NOT */ if (wdcommand(du, 0, 0, 0, 0, WDCC_RESTORE | WD_STEP) != 0 || wdwait(du, WDCS_READY | WDCS_SEEKCMPLT, TIMEOUT) != 0 || wdsetctlr(du) != 0) { wderror((struct buf *)NULL, du, "wddump: recalibrate failed"); return (EIO); } du->dk_flags |= DKFL_SINGLE; addr = (char *) 0; blknum = dumplo + blkoff; while (num > 0) { blkcnt = num; if (blkcnt > MAXTRANSFER) blkcnt = MAXTRANSFER; /* Keep transfer within current cylinder. */ if ((blknum + blkcnt - 1) / secpercyl != blknum / secpercyl) blkcnt = secpercyl - (blknum % secpercyl); blknext = blknum + blkcnt; /* * See if one of the sectors is in the bad sector list * (if we have one). If the first sector is bad, then * reduce the transfer to this one bad sector; if another * sector is bad, then reduce reduce the transfer to * avoid any bad sectors. */ if (du->dk_flags & DKFL_SINGLE && dsgetbad(dev, du->dk_slices) != NULL) { for (blkchk = blknum; blkchk < blknum + blkcnt; blkchk++) { daddr_t blknew; blknew = transbad144(dsgetbad(dev, du->dk_slices), blkchk - ds_offset) + ds_offset; if (blknew != blkchk) { /* Found bad block. */ blkcnt = blkchk - blknum; if (blkcnt > 0) { blknext = blknum + blkcnt; goto out; } blkcnt = 1; blknext = blknum + blkcnt; #if 1 || defined(WDDEBUG) printf("bad block %lu -> %lu\n", blknum, blknew); #endif break; } } } out: /* Compute disk address. */ cylin = blknum / secpercyl; head = (blknum % secpercyl) / secpertrk; sector = blknum % secpertrk; #if 0 /* Let's just talk about this first... */ pg("cylin l%d head %ld sector %ld addr 0x%x count %ld", cylin, head, sector, addr, blkcnt); #endif /* Do the write. */ if (wdcommand(du, cylin, head, sector, blkcnt, WDCC_WRITE) != 0) { wderror((struct buf *)NULL, du, "wddump: timeout waiting to to give command"); return (EIO); } while (blkcnt != 0) { pmap_enter(kernel_pmap, (vm_offset_t)CADDR1, trunc_page(addr), VM_PROT_READ, TRUE); /* Ready to send data? */ DELAY(5); /* ATA spec */ if (wdwait(du, WDCS_READY | WDCS_SEEKCMPLT | WDCS_DRQ, TIMEOUT) < 0) { wderror((struct buf *)NULL, du, "wddump: timeout waiting for DRQ"); return (EIO); } if (du->dk_flags & DKFL_32BIT) outsl(du->dk_port + wd_data, CADDR1 + ((int)addr & (NBPG - 1)), DEV_BSIZE / sizeof(long)); else outsw(du->dk_port + wd_data, CADDR1 + ((int)addr & (NBPG - 1)), DEV_BSIZE / sizeof(short)); addr += DEV_BSIZE; if ((unsigned)addr % (1024 * 1024) == 0) printf("%ld ", num / (1024 * 1024 / DEV_BSIZE)); num--; blkcnt--; } /* Wait for completion. */ DELAY(5); /* ATA spec XXX NOT */ if (wdwait(du, WDCS_READY | WDCS_SEEKCMPLT, TIMEOUT) < 0) { wderror((struct buf *)NULL, du, "wddump: timeout waiting for status"); return (EIO); } /* Check final status. */ if (du->dk_status & (WDCS_READY | WDCS_SEEKCMPLT | WDCS_DRQ | WDCS_ERR) != (WDCS_READY | WDCS_SEEKCMPLT)) { wderror((struct buf *)NULL, du, "wddump: extra DRQ, or error"); return (EIO); } /* Update block count. */ blknum = blknext; /* Operator aborting dump? */ if (cncheckc()) return (EINTR); } return (0); } static void wderror(struct buf *bp, struct disk *du, char *mesg) { if (bp == NULL) printf("wd%d: %s:\n", du->dk_lunit, mesg); else diskerr(bp, "wd", mesg, LOG_PRINTF, du->dk_skip, dsgetlabel(bp->b_dev, du->dk_slices)); printf("wd%d: status %b error %b\n", du->dk_lunit, du->dk_status, WDCS_BITS, du->dk_error, WDERR_BITS); } /* * Discard any interrupts that were latched by the interrupt system while * we were doing polled i/o. */ static void wdflushirq(struct disk *du, int old_ipl) { wdtab[du->dk_ctrlr].b_active = 2; splx(old_ipl); (void)splbio(); wdtab[du->dk_ctrlr].b_active = 0; } /* * Reset the controller. */ static int wdreset(struct disk *du) { int wdc; wdc = du->dk_port; (void)wdwait(du, 0, TIMEOUT); outb(wdc + wd_ctlr, WDCTL_IDS | WDCTL_RST); DELAY(10 * 1000); outb(wdc + wd_ctlr, WDCTL_IDS); if (wdwait(du, 0, TIMEOUT) != 0) return (1); du->dk_status = inb(wdc + wd_status); du->dk_error = inb(wdc + wd_error); if ((du->dk_status & ~(WDCS_READY | WDCS_SEEKCMPLT)) != 0 || du->dk_error != 0x01) return (1); outb(wdc + wd_ctlr, WDCTL_4BIT); return (0); } /* * Sleep until driver is inactive. * This is used only for avoiding rare race conditions, so it is unimportant * that the sleep may be far too short or too long. */ static void wdsleep(int ctrlr, char *wmesg) { while (wdtab[ctrlr].b_active) tsleep((caddr_t)&wdtab[ctrlr].b_active, PZERO - 1, wmesg, 1); } static void wdtimeout(void *cdu) { struct disk *du; int x; static int timeouts; du = (struct disk *)cdu; x = splbio(); if (du->dk_timeout != 0 && --du->dk_timeout == 0) { if(timeouts++ == 5) wderror((struct buf *)NULL, du, "Last time I say: interrupt timeout. Probably a portable PC."); else if(timeouts++ < 5) wderror((struct buf *)NULL, du, "interrupt timeout"); wdunwedge(du); wdflushirq(du, x); du->dk_skip = 0; du->dk_flags |= DKFL_SINGLE; wdstart(du->dk_ctrlr); } timeout(wdtimeout, cdu, hz); splx(x); } /* * Reset the controller after it has become wedged. This is different from * wdreset() so that wdreset() can be used in the probe and so that this * can restore the geometry . */ static int wdunwedge(struct disk *du) { struct disk *du1; int lunit; /* Schedule other drives for recalibration. */ for (lunit = 0; lunit < NWD; lunit++) if ((du1 = wddrives[lunit]) != NULL && du1 != du && du1->dk_ctrlr == du->dk_ctrlr && du1->dk_state > WANTOPEN) du1->dk_state = WANTOPEN; DELAY(RECOVERYTIME); if (wdreset(du) == 0) { /* * XXX - recalibrate current drive now because some callers * aren't prepared to have its state change. */ if (wdcommand(du, 0, 0, 0, 0, WDCC_RESTORE | WD_STEP) == 0 && wdwait(du, WDCS_READY | WDCS_SEEKCMPLT, TIMEOUT) == 0 && wdsetctlr(du) == 0) return (0); } wderror((struct buf *)NULL, du, "wdunwedge failed"); return (1); } /* * Wait uninterruptibly until controller is not busy and either certain * status bits are set or an error has occurred. * The wait is usually short unless it is for the controller to process * an entire critical command. * Return 1 for (possibly stale) controller errors, -1 for timeout errors, * or 0 for no errors. * Return controller status in du->dk_status and, if there was a controller * error, return the error code in du->dk_error. */ #ifdef WD_COUNT_RETRIES static int min_retries[NWDC]; #endif static int wdwait(struct disk *du, u_char bits_wanted, int timeout) { int wdc; u_char status; #define POLLING 1000 wdc = du->dk_port; timeout += POLLING; /* * This delay is really too long, but does not impact the performance * as much when using the multi-sector option. Shorter delays have * caused I/O errors on some drives and system configs. This should * probably be fixed if we develop a better short term delay mechanism. */ DELAY(1); do { #ifdef WD_COUNT_RETRIES if (min_retries[du->dk_ctrlr] > timeout || min_retries[du->dk_ctrlr] == 0) min_retries[du->dk_ctrlr] = timeout; #endif du->dk_status = status = inb(wdc + wd_status); if (!(status & WDCS_BUSY)) { if (status & WDCS_ERR) { du->dk_error = inb(wdc + wd_error); /* * We once returned here. This is wrong * because the error bit is apparently only * valid after the controller has interrupted * (e.g., the error bit is stale when we wait * for DRQ for writes). So we can't depend * on the error bit at all when polling for * command completion. */ } if ((status & bits_wanted) == bits_wanted) return (status & WDCS_ERR); } if (timeout < TIMEOUT) /* * Switch to a polling rate of about 1 KHz so that * the timeout is almost machine-independent. The * controller is taking a long time to respond, so * an extra msec won't matter. */ DELAY(1000); else DELAY(1); } while (--timeout != 0); return (-1); } #endif /* NWDC > 0 */