/* $NetBSD: seagate.c,v 1.58 2005/05/30 04:32:38 christos Exp $ */ /* * ST01/02, Future Domain TMC-885, TMC-950 SCSI driver * * Copyright 1994, Charles M. Hannum (mycroft@ai.mit.edu) * Copyright 1994, Kent Palmkvist (kentp@isy.liu.se) * Copyright 1994, Robert Knier (rknier@qgraph.com) * Copyright 1992, 1994 Drew Eckhardt (drew@colorado.edu) * Copyright 1994, Julian Elischer (julian@tfs.com) * * Others that has contributed by example code is * Glen Overby (overby@cray.com) * Tatu Yllnen * Brian E Litzinger * * 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. * * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``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 DEVELOPERS 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. */ /* * kentp 940307 alpha version based on newscsi-03 version of Julians SCSI-code * kentp 940314 Added possibility to not use messages * rknier 940331 Added fast transfer code * rknier 940407 Added assembler coded data transfers */ /* * What should really be done: * * Add missing tests for timeouts * Restructure interrupt enable/disable code (runs to long with int disabled) * Find bug? giving problem with tape status * Add code to handle Future Domain 840, 841, 880 and 881 * adjust timeouts (startup is very slow) * add code to use tagged commands in SCSI2 * Add code to handle slow devices better (sleep if device not disconnecting) * Fix unnecessary interrupts */ /* * Note to users trying to share a disk between DOS and unix: * The ST01/02 is a translating host-adapter. It is not giving DOS * the same number of heads/tracks/sectors as specified by the disk. * It is therefore important to look at what numbers DOS thinks the * disk has. Use these to disklabel your disk in an appropriate manner */ #include __KERNEL_RCSID(0, "$NetBSD: seagate.c,v 1.58 2005/05/30 04:32:38 christos Exp $"); #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /* XXX USES ISA HOLE DIRECTLY */ #define SEA_SCB_MAX 32 /* allow maximally 8 scsi control blocks */ #define SCB_TABLE_SIZE 8 /* start with 8 scb entries in table */ #define BLOCK_SIZE 512 /* size of READ/WRITE areas on SCSI card */ /* * defining SEA_BLINDTRANSFER will make DATA IN and DATA OUT to be done with * blind transfers, i.e. no check is done for scsi phase changes. This will * result in data loss if the scsi device does not send its data using * BLOCK_SIZE bytes at a time. * If SEA_BLINDTRANSFER defined and SEA_ASSEMBLER also defined will result in * the use of blind transfers coded in assembler. SEA_ASSEMBLER is no good * without SEA_BLINDTRANSFER defined. */ #define SEA_BLINDTRANSFER /* do blind transfers */ #define SEA_ASSEMBLER /* Use assembly code for fast transfers */ /* * defining SEA_NOMSGS causes messages not to be used (thereby disabling * disconnects) */ #undef SEA_NOMSGS /* * defining SEA_NODATAOUT makes dataout phase being aborted */ #undef SEA_NODATAOUT /* Debugging definitions. Should not be used unless you want a lot of printouts even under normal conditions */ #undef SEA_DEBUGQUEUE /* Display info about queue-lengths */ /******************************* board definitions **************************/ /* * CONTROL defines */ #define CMD_RST 0x01 /* scsi reset */ #define CMD_SEL 0x02 /* scsi select */ #define CMD_BSY 0x04 /* scsi busy */ #define CMD_ATTN 0x08 /* scsi attention */ #define CMD_START_ARB 0x10 /* start arbitration bit */ #define CMD_EN_PARITY 0x20 /* enable scsi parity generation */ #define CMD_INTR 0x40 /* enable scsi interrupts */ #define CMD_DRVR_ENABLE 0x80 /* scsi enable */ /* * STATUS */ #define STAT_BSY 0x01 /* scsi busy */ #define STAT_MSG 0x02 /* scsi msg */ #define STAT_IO 0x04 /* scsi I/O */ #define STAT_CD 0x08 /* scsi C/D */ #define STAT_REQ 0x10 /* scsi req */ #define STAT_SEL 0x20 /* scsi select */ #define STAT_PARITY 0x40 /* parity error bit */ #define STAT_ARB_CMPL 0x80 /* arbitration complete bit */ /* * REQUESTS */ #define PH_DATAOUT (0) #define PH_DATAIN (STAT_IO) #define PH_CMD (STAT_CD) #define PH_STAT (STAT_CD | STAT_IO) #define PH_MSGOUT (STAT_MSG | STAT_CD) #define PH_MSGIN (STAT_MSG | STAT_CD | STAT_IO) #define PH_MASK (STAT_MSG | STAT_CD | STAT_IO) #define PH_INVALID 0xff #define SEA_RAMOFFSET 0x00001800 #define BASE_CMD (CMD_INTR | CMD_EN_PARITY) #define SEAGATE 1 /* Seagate ST0[12] */ #define FDOMAIN 2 /* Future Domain TMC-{885,950} */ #define FDOMAIN840 3 /* Future Domain TMC-{84[01],88[01]} */ /******************************************************************************/ /* scsi control block used to keep info about a scsi command */ struct sea_scb { u_char *data; /* position in data buffer so far */ int datalen; /* bytes remaining to transfer */ TAILQ_ENTRY(sea_scb) chain; struct scsipi_xfer *xs; /* the scsipi_xfer for this cmd */ int flags; /* status of the instruction */ #define SCB_FREE 0 #define SCB_ACTIVE 1 #define SCB_ABORTED 2 #define SCB_TIMEOUT 4 #define SCB_ERROR 8 }; /* * data structure describing current status of the scsi bus. One for each * controller card. */ struct sea_softc { struct device sc_dev; void *sc_ih; int type; /* board type */ caddr_t maddr; /* Base address for card */ caddr_t maddr_cr_sr; /* Address of control and status reg */ caddr_t maddr_dr; /* Address of data register */ struct scsipi_adapter sc_adapter; struct scsipi_channel sc_channel; TAILQ_HEAD(, sea_scb) free_list, ready_list, nexus_list; struct sea_scb *nexus; /* currently connected command */ int numscbs; /* number of scsi control blocks */ struct sea_scb scb[SCB_TABLE_SIZE]; int our_id; /* our scsi id */ u_char our_id_mask; volatile u_char busy[8]; /* index=target, bit=lun, Keep track of busy luns at device target */ }; /* flag showing if main routine is running. */ static volatile int main_running = 0; #define STATUS (*(volatile u_char *)sea->maddr_cr_sr) #define CONTROL STATUS #define DATA (*(volatile u_char *)sea->maddr_dr) /* * These are "special" values for the tag parameter passed to sea_select * Not implemented right now. */ #define TAG_NEXT -1 /* Use next free tag */ #define TAG_NONE -2 /* * Establish I_T_L nexus instead of I_T_L_Q * even on SCSI-II devices. */ typedef struct { const char *signature; int offset, length; int type; } BiosSignature; /* * Signatures for automatic recognition of board type */ static const BiosSignature signatures[] = { {"ST01 v1.7 (C) Copyright 1987 Seagate", 15, 37, SEAGATE}, {"SCSI BIOS 2.00 (C) Copyright 1987 Seagate", 15, 40, SEAGATE}, /* * The following two lines are NOT mistakes. One detects ROM revision * 3.0.0, the other 3.2. Since seagate has only one type of SCSI adapter, * and this is not going to change, the "SEAGATE" and "SCSI" together * are probably "good enough" */ {"SEAGATE SCSI BIOS ", 16, 17, SEAGATE}, {"SEAGATE SCSI BIOS ", 17, 17, SEAGATE}, /* * However, future domain makes several incompatible SCSI boards, so specific * signatures must be used. */ {"FUTURE DOMAIN CORP. (C) 1986-1989 V5.0C2/14/89", 5, 45, FDOMAIN}, {"FUTURE DOMAIN CORP. (C) 1986-1989 V6.0A7/28/89", 5, 46, FDOMAIN}, {"FUTURE DOMAIN CORP. (C) 1986-1990 V6.0105/31/90",5, 47, FDOMAIN}, {"FUTURE DOMAIN CORP. (C) 1986-1990 V6.0209/18/90",5, 47, FDOMAIN}, {"FUTURE DOMAIN CORP. (C) 1986-1990 V7.009/18/90", 5, 46, FDOMAIN}, {"FUTURE DOMAIN CORP. (C) 1992 V8.00.004/02/92", 5, 44, FDOMAIN}, {"FUTURE DOMAIN TMC-950", 5, 21, FDOMAIN}, }; #define nsignatures (sizeof(signatures) / sizeof(signatures[0])) #ifdef notdef static const char *bases[] = { (char *) 0xc8000, (char *) 0xca000, (char *) 0xcc000, (char *) 0xce000, (char *) 0xdc000, (char *) 0xde000 }; #define nbases (sizeof(bases) / sizeof(bases[0])) #endif int seaintr(void *); void sea_scsipi_request(struct scsipi_channel *, scsipi_adapter_req_t, void *); void sea_timeout(void *); void sea_done(struct sea_softc *, struct sea_scb *); struct sea_scb *sea_get_scb(struct sea_softc *, int); void sea_free_scb(struct sea_softc *, struct sea_scb *, int); static void sea_main(void); static void sea_information_transfer(struct sea_softc *); int sea_poll(struct sea_softc *, struct scsipi_xfer *, int); void sea_init(struct sea_softc *); void sea_send_scb(struct sea_softc *sea, struct sea_scb *scb); void sea_reselect(struct sea_softc *sea); int sea_select(struct sea_softc *sea, struct sea_scb *scb); int sea_transfer_pio(struct sea_softc *sea, u_char *phase, int *count, u_char **data); int sea_abort(struct sea_softc *, struct sea_scb *scb); void sea_grow_scb(struct sea_softc *); int seaprobe(struct device *, struct cfdata *, void *); void seaattach(struct device *, struct device *, void *); CFATTACH_DECL(sea, sizeof(struct sea_softc), seaprobe, seaattach, NULL, NULL); extern struct cfdriver sea_cd; #ifdef SEA_DEBUGQUEUE void sea_queue_length(sea) struct sea_softc *sea; { struct sea_scb *scb; int connected, issued, disconnected; connected = sea->nexus ? 1 : 0; for (scb = sea->ready_list.tqh_first, issued = 0; scb; scb = scb->chain.tqe_next, issued++); for (scb = sea->nexus_list.tqh_first, disconnected = 0; scb; scb = scb->chain.tqe_next, disconnected++); printf("%s: length: %d/%d/%d\n", sea->sc_dev.dv_xname, connected, issued, disconnected); } #endif /* * Check if the device can be found at the port given and if so, detect the * type the type of board. Set it up ready for further work. Takes the isa_dev * structure from autoconf as an argument. * Returns 1 if card recognized, 0 if errors. */ int seaprobe(parent, match, aux) struct device *parent; struct cfdata *match; void *aux; { struct isa_attach_args *ia = aux; int i, type = 0; caddr_t maddr; if (ia->ia_niomem < 1) return (0); if (ia->ia_nirq < 1) return (0); if (ISA_DIRECT_CONFIG(ia)) return (0); if (ia->ia_iomem[0].ir_addr == ISA_UNKNOWN_IOMEM) return (0); if (ia->ia_irq[0].ir_irq == ISA_UNKNOWN_IRQ) return (0); /* XXX XXX XXX */ maddr = ISA_HOLE_VADDR(ia->ia_iomem[0].ir_addr); /* check board type */ /* No way to define this through config */ for (i = 0; i < nsignatures; i++) if (!memcmp(maddr + signatures[i].offset, signatures[i].signature, signatures[i].length)) { type = signatures[i].type; break; } /* Find controller and data memory addresses */ switch (type) { case SEAGATE: case FDOMAIN840: case FDOMAIN: break; default: #ifdef SEA_DEBUG printf("seaprobe: board type unknown at address %p\n", maddr); #endif return 0; } ia->ia_niomem = 1; ia->ia_iomem[0].ir_size = 0x2000; ia->ia_nirq = 1; ia->ia_nio = 0; ia->ia_ndrq = 0; return 1; } /* * Attach all sub-devices we can find */ void seaattach(parent, self, aux) struct device *parent, *self; void *aux; { struct isa_attach_args *ia = aux; struct sea_softc *sea = (void *)self; struct scsipi_adapter *adapt = &sea->sc_adapter; struct scsipi_channel *chan = &sea->sc_channel; int i; /* XXX XXX XXX */ sea->maddr = ISA_HOLE_VADDR(ia->ia_iomem[0].ir_addr); /* check board type */ /* No way to define this through config */ for (i = 0; i < nsignatures; i++) if (!memcmp(sea->maddr + signatures[i].offset, signatures[i].signature, signatures[i].length)) { sea->type = signatures[i].type; break; } /* Find controller and data memory addresses */ switch (sea->type) { case SEAGATE: case FDOMAIN840: sea->maddr_cr_sr = (void *) (((u_char *)sea->maddr) + 0x1a00); sea->maddr_dr = (void *) (((u_char *)sea->maddr) + 0x1c00); break; case FDOMAIN: sea->maddr_cr_sr = (void *) (((u_char *)sea->maddr) + 0x1c00); sea->maddr_dr = (void *) (((u_char *)sea->maddr) + 0x1e00); break; default: #ifdef DEBUG printf("%s: board type unknown at address %p\n", sea->sc_dev.dv_xname, sea->maddr); #endif return; } /* Test controller RAM (works the same way on future domain cards?) */ *((u_char *)sea->maddr + SEA_RAMOFFSET) = 0xa5; *((u_char *)sea->maddr + SEA_RAMOFFSET + 1) = 0x5a; if ((*((u_char *)sea->maddr + SEA_RAMOFFSET) != 0xa5) || (*((u_char *)sea->maddr + SEA_RAMOFFSET + 1) != 0x5a)) { printf("%s: board RAM failure\n", sea->sc_dev.dv_xname); return; } sea_init(sea); /* * Fill in the scsipi_adapter. */ memset(adapt, 0, sizeof(*adapt)); adapt->adapt_dev = &sea->sc_dev; adapt->adapt_nchannels = 1; adapt->adapt_openings = sea->numscbs; adapt->adapt_max_periph = 1; adapt->adapt_request = sea_scsipi_request; adapt->adapt_minphys = minphys; /* * Fill in the scsipi_channel. */ memset(chan, 0, sizeof(*chan)); chan->chan_adapter = adapt; chan->chan_bustype = &scsi_bustype; chan->chan_channel = 0; chan->chan_ntargets = 8; chan->chan_nluns = 8; chan->chan_id = sea->our_id; chan->chan_flags = SCSIPI_CHAN_CANGROW; printf("\n"); sea->sc_ih = isa_intr_establish(ia->ia_ic, ia->ia_irq[0].ir_irq, IST_EDGE, IPL_BIO, seaintr, sea); /* * ask the adapter what subunits are present */ config_found(self, &sea->sc_channel, scsiprint); } /* * Catch an interrupt from the adaptor */ int seaintr(arg) void *arg; { struct sea_softc *sea = arg; #ifdef DEBUG /* extra overhead, and only needed for intr debugging */ if ((STATUS & STAT_PARITY) == 0 && (STATUS & (STAT_SEL | STAT_IO)) != (STAT_SEL | STAT_IO)) return 0; #endif loop: /* dispatch to appropriate routine if found and done=0 */ /* should check to see that this card really caused the interrupt */ if (STATUS & STAT_PARITY) { /* Parity error interrupt */ printf("%s: parity error\n", sea->sc_dev.dv_xname); return 1; } if ((STATUS & (STAT_SEL | STAT_IO)) == (STAT_SEL | STAT_IO)) { /* Reselect interrupt */ sea_reselect(sea); if (!main_running) sea_main(); goto loop; } return 1; } /* * Setup data structures, and reset the board and the SCSI bus. */ void sea_init(sea) struct sea_softc *sea; { int i; /* Reset the scsi bus (I don't know if this is needed */ CONTROL = BASE_CMD | CMD_DRVR_ENABLE | CMD_RST; delay(25); /* hold reset for at least 25 microseconds */ CONTROL = BASE_CMD; delay(10); /* wait a Bus Clear Delay (800 ns + bus free delay (800 ns) */ /* Set our id (don't know anything about this) */ switch (sea->type) { case SEAGATE: sea->our_id = 7; break; case FDOMAIN: case FDOMAIN840: sea->our_id = 6; break; } sea->our_id_mask = 1 << sea->our_id; /* init fields used by our routines */ sea->nexus = 0; TAILQ_INIT(&sea->ready_list); TAILQ_INIT(&sea->nexus_list); TAILQ_INIT(&sea->free_list); for (i = 0; i < 8; i++) sea->busy[i] = 0x00; /* link up the free list of scbs */ sea->numscbs = SCB_TABLE_SIZE; for (i = 0; i < SCB_TABLE_SIZE; i++) { TAILQ_INSERT_TAIL(&sea->free_list, &sea->scb[i], chain); } } /* * start a scsi operation given the command and the data address. Also needs * the unit, target and lu. */ void sea_scsipi_request(chan, req, arg) struct scsipi_channel *chan; scsipi_adapter_req_t req; void *arg; { struct scsipi_xfer *xs; struct scsipi_periph *periph; struct sea_softc *sea = (void *)chan->chan_adapter->adapt_dev; struct sea_scb *scb; int flags; int s; switch (req) { case ADAPTER_REQ_RUN_XFER: xs = arg; periph = xs->xs_periph; flags = xs->xs_control; SC_DEBUG(periph, SCSIPI_DB2, ("sea_scsipi_requeset\n")); /* XXX Reset not implemented. */ if (flags & XS_CTL_RESET) { printf("%s: resetting\n", sea->sc_dev.dv_xname); xs->error = XS_DRIVER_STUFFUP; scsipi_done(xs); return; } /* Get an SCB to use. */ scb = sea_get_scb(sea, flags); #ifdef DIAGNOSTIC /* * This should never happen as we track the resources * in the mid-layer. */ if (scb == NULL) { scsipi_printaddr(periph); printf("unable to allocate scb\n"); panic("sea_scsipi_request"); } #endif scb->flags = SCB_ACTIVE; scb->xs = xs; /* * Put all the arguments for the xfer in the scb */ scb->datalen = xs->datalen; scb->data = xs->data; #ifdef SEA_DEBUGQUEUE sea_queue_length(sea); #endif s = splbio(); sea_send_scb(sea, scb); if ((flags & XS_CTL_POLL) == 0) { callout_reset(&scb->xs->xs_callout, mstohz(xs->timeout), sea_timeout, scb); splx(s); return; } splx(s); /* * If we can't use interrupts, poll on completion */ if (sea_poll(sea, xs, xs->timeout)) { sea_timeout(scb); if (sea_poll(sea, xs, 2000)) sea_timeout(scb); } return; case ADAPTER_REQ_GROW_RESOURCES: sea_grow_scb(sea); return; case ADAPTER_REQ_SET_XFER_MODE: { struct scsipi_xfer_mode *xm = arg; /* * We don't support sync or wide or tagged queueing, * so announce that now. */ xm->xm_mode = 0; xm->xm_period = 0; xm->xm_offset = 0; scsipi_async_event(chan, ASYNC_EVENT_XFER_MODE, xm); return; } } } /* * Get a free scb. If there are none, see if we can allocate a new one. If so, * put it in the hash table too; otherwise return an error or sleep. */ struct sea_scb * sea_get_scb(sea, flags) struct sea_softc *sea; int flags; { int s; struct sea_scb *scb; s = splbio(); if ((scb = TAILQ_FIRST(&sea->free_list)) != NULL) TAILQ_REMOVE(&sea->free_list, scb, chain); splx(s); return (scb); } /* * Try to send this command to the board. Because this board does not use any * mailboxes, this routine simply adds the command to the queue held by the * sea_softc structure. * A check is done to see if the command contains a REQUEST_SENSE command, and * if so the command is put first in the queue, otherwise the command is added * to the end of the queue. ?? Not correct ?? */ void sea_send_scb(sea, scb) struct sea_softc *sea; struct sea_scb *scb; { TAILQ_INSERT_TAIL(&sea->ready_list, scb, chain); /* Try to do some work on the card. */ if (!main_running) sea_main(); } /* * Coroutine that runs as long as more work can be done on the seagate host * adapter in a system. Both sea_scsi_cmd and sea_intr will try to start it in * case it is not running. */ void sea_main() { struct sea_softc *sea; struct sea_scb *scb; int done; int unit; int s; main_running = 1; /* * This should not be run with interrupts disabled, but use the splx * code instead. */ loop: done = 1; for (unit = 0; unit < sea_cd.cd_ndevs; unit++) { sea = device_lookup(&sea_cd, unit); if (!sea) continue; s = splbio(); if (!sea->nexus) { /* * Search through the ready_list for a command * destined for a target that's not busy. */ for (scb = sea->ready_list.tqh_first; scb; scb = scb->chain.tqe_next) { if (!(sea->busy[scb->xs->xs_periph->periph_target] & (1 << scb->xs->xs_periph->periph_lun))) { TAILQ_REMOVE(&sea->ready_list, scb, chain); /* Re-enable interrupts. */ splx(s); /* * Attempt to establish an I_T_L nexus. * On success, sea->nexus is set. * On failure, we must add the command * back to the issue queue so we can * keep trying. */ /* * REQUEST_SENSE commands are issued * without tagged queueing, even on * SCSI-II devices because the * contingent alligence condition * exists for the entire unit. */ /* * First check that if any device has * tried a reconnect while we have done * other things with interrupts * disabled. */ if ((STATUS & (STAT_SEL | STAT_IO)) == (STAT_SEL | STAT_IO)) { sea_reselect(sea); break; } if (sea_select(sea, scb)) { s = splbio(); TAILQ_INSERT_HEAD(&sea->ready_list, scb, chain); splx(s); } else break; } /* if target/lun is not busy */ } /* for scb */ if (!sea->nexus) { /* check for reselection phase */ if ((STATUS & (STAT_SEL | STAT_IO)) == (STAT_SEL | STAT_IO)) { sea_reselect(sea); } } } /* if (!sea->nexus) */ splx(s); if (sea->nexus) { /* we are connected. Do the task */ sea_information_transfer(sea); done = 0; } else break; } /* for instance */ if (!done) goto loop; main_running = 0; } /* * Allocate an scb and add it to the free list. * We are called at splbio. */ void sea_grow_scb(sea) struct sea_softc *sea; { struct sea_scb *scb; if (sea->numscbs == SEA_SCB_MAX) { sea->sc_channel.chan_flags &= ~SCSIPI_CHAN_CANGROW; return; } scb = malloc(sizeof(struct sea_scb), M_DEVBUF, M_NOWAIT|M_ZERO); if (scb == NULL) return; TAILQ_INSERT_TAIL(&sea->free_list, scb, chain); sea->numscbs++; sea->sc_adapter.adapt_openings++; } void sea_free_scb(sea, scb, flags) struct sea_softc *sea; struct sea_scb *scb; int flags; { int s; s = splbio(); scb->flags = SCB_FREE; TAILQ_INSERT_HEAD(&sea->free_list, scb, chain); splx(s); } void sea_timeout(arg) void *arg; { struct sea_scb *scb = arg; struct scsipi_xfer *xs = scb->xs; struct scsipi_periph *periph = xs->xs_periph; struct sea_softc *sea = (void *)periph->periph_channel->chan_adapter->adapt_dev; int s; scsipi_printaddr(periph); printf("timed out"); s = splbio(); /* * If it has been through before, then * a previous abort has failed, don't * try abort again */ if (scb->flags & SCB_ABORTED) { /* abort timed out */ printf(" AGAIN\n"); scb->xs->xs_retries = 0; scb->flags |= SCB_ABORTED; sea_done(sea, scb); } else { /* abort the operation that has timed out */ printf("\n"); scb->flags |= SCB_ABORTED; sea_abort(sea, scb); /* 2 secs for the abort */ if ((xs->xs_control & XS_CTL_POLL) == 0) callout_reset(&scb->xs->xs_callout, 2 * hz, sea_timeout, scb); } splx(s); } void sea_reselect(sea) struct sea_softc *sea; { u_char target_mask; int i; u_char lun, phase; u_char msg[3]; int len; u_char *data; struct sea_scb *scb; int abort = 0; if (!((target_mask = STATUS) & STAT_SEL)) { printf("%s: wrong state 0x%x\n", sea->sc_dev.dv_xname, target_mask); return; } /* wait for a device to win the reselection phase */ /* signals this by asserting the I/O signal */ for (i = 10; i && (STATUS & (STAT_SEL | STAT_IO | STAT_BSY)) != (STAT_SEL | STAT_IO | 0); i--); /* !! Check for timeout here */ /* the data bus contains original initiator id ORed with target id */ target_mask = DATA; /* see that we really are the initiator */ if (!(target_mask & sea->our_id_mask)) { printf("%s: polled reselection was not for me: 0x%x\n", sea->sc_dev.dv_xname, target_mask); return; } /* find target who won */ target_mask &= ~sea->our_id_mask; /* host responds by asserting the BSY signal */ CONTROL = BASE_CMD | CMD_DRVR_ENABLE | CMD_BSY; /* target should respond by deasserting the SEL signal */ for (i = 50000; i && (STATUS & STAT_SEL); i++); /* remove the busy status */ CONTROL = BASE_CMD | CMD_DRVR_ENABLE; /* we are connected. Now we wait for the MSGIN condition */ for (i = 50000; i && !(STATUS & STAT_REQ); i--); /* !! Add timeout check here */ /* hope we get an IDENTIFY message */ len = 3; data = msg; phase = PH_MSGIN; sea_transfer_pio(sea, &phase, &len, &data); if (!MSG_ISIDENTIFY(msg[0])) { printf("%s: expecting IDENTIFY message, got 0x%x\n", sea->sc_dev.dv_xname, msg[0]); abort = 1; scb = NULL; } else { lun = msg[0] & 0x07; /* * Find the command corresponding to the I_T_L or I_T_L_Q nexus * we just reestablished, and remove it from the disconnected * queue. */ for (scb = sea->nexus_list.tqh_first; scb; scb = scb->chain.tqe_next) if (target_mask == (1 << scb->xs->xs_periph->periph_target) && lun == scb->xs->xs_periph->periph_lun) { TAILQ_REMOVE(&sea->nexus_list, scb, chain); break; } if (!scb) { printf("%s: target %02x lun %d not disconnected\n", sea->sc_dev.dv_xname, target_mask, lun); /* * Since we have an established nexus that we can't do * anything with, we must abort it. */ abort = 1; } } if (abort) { msg[0] = MSG_ABORT; len = 1; data = msg; phase = PH_MSGOUT; CONTROL = BASE_CMD | CMD_ATTN; sea_transfer_pio(sea, &phase, &len, &data); } else sea->nexus = scb; return; } /* * Transfer data in given phase using polled I/O. */ int sea_transfer_pio(sea, phase, count, data) struct sea_softc *sea; u_char *phase; int *count; u_char **data; { u_char p = *phase, tmp; int c = *count; u_char *d = *data; int timeout; do { /* * Wait for assertion of REQ, after which the phase bits will * be valid. */ for (timeout = 0; timeout < 50000; timeout++) if ((tmp = STATUS) & STAT_REQ) break; if (!(tmp & STAT_REQ)) { printf("%s: timeout waiting for STAT_REQ\n", sea->sc_dev.dv_xname); break; } /* * Check for phase mismatch. Reached if the target decides * that it has finished the transfer. */ if (sea->type == FDOMAIN840) tmp = ((tmp & 0x08) >> 2) | ((tmp & 0x02) << 2) | (tmp & 0xf5); if ((tmp & PH_MASK) != p) break; /* Do actual transfer from SCSI bus to/from memory. */ if (!(p & STAT_IO)) DATA = *d; else *d = DATA; ++d; /* * The SCSI standard suggests that in MSGOUT phase, the * initiator should drop ATN on the last byte of the message * phase after REQ has been asserted for the handshake but * before the initiator raises ACK. * Don't know how to accomplish this on the ST01/02. */ #if 0 /* * XXX * The st01 code doesn't wait for STAT_REQ to be deasserted. * Is this ok? */ for (timeout = 0; timeout < 200000L; timeout++) if (!(STATUS & STAT_REQ)) break; if (STATUS & STAT_REQ) printf("%s: timeout on wait for !STAT_REQ", sea->sc_dev.dv_xname); #endif } while (--c); *count = c; *data = d; tmp = STATUS; if (tmp & STAT_REQ) *phase = tmp & PH_MASK; else *phase = PH_INVALID; if (c && (*phase != p)) return -1; return 0; } /* * Establish I_T_L or I_T_L_Q nexus for new or existing command including * ARBITRATION, SELECTION, and initial message out for IDENTIFY and queue * messages. Return -1 if selection could not execute for some reason, 0 if * selection succeded or failed because the target did not respond. */ int sea_select(sea, scb) struct sea_softc *sea; struct sea_scb *scb; { u_char msg[3], phase; u_char *data; int len; int timeout; CONTROL = BASE_CMD; DATA = sea->our_id_mask; CONTROL = (BASE_CMD & ~CMD_INTR) | CMD_START_ARB; /* wait for arbitration to complete */ for (timeout = 0; timeout < 3000000L; timeout++) if (STATUS & STAT_ARB_CMPL) break; if (!(STATUS & STAT_ARB_CMPL)) { if (STATUS & STAT_SEL) { printf("%s: arbitration lost\n", sea->sc_dev.dv_xname); scb->flags |= SCB_ERROR; } else { printf("%s: arbitration timeout\n", sea->sc_dev.dv_xname); scb->flags |= SCB_TIMEOUT; } CONTROL = BASE_CMD; return -1; } delay(2); DATA = (u_char)((1 << scb->xs->xs_periph->periph_target) | sea->our_id_mask); CONTROL = #ifdef SEA_NOMSGS (BASE_CMD & ~CMD_INTR) | CMD_DRVR_ENABLE | CMD_SEL; #else (BASE_CMD & ~CMD_INTR) | CMD_DRVR_ENABLE | CMD_SEL | CMD_ATTN; #endif delay(1); /* wait for a bsy from target */ for (timeout = 0; timeout < 2000000L; timeout++) if (STATUS & STAT_BSY) break; if (!(STATUS & STAT_BSY)) { /* should return some error to the higher level driver */ CONTROL = BASE_CMD; scb->flags |= SCB_TIMEOUT; return 0; } /* Try to make the target to take a message from us */ #ifdef SEA_NOMSGS CONTROL = (BASE_CMD & ~CMD_INTR) | CMD_DRVR_ENABLE; #else CONTROL = (BASE_CMD & ~CMD_INTR) | CMD_DRVR_ENABLE | CMD_ATTN; #endif delay(1); /* should start a msg_out phase */ for (timeout = 0; timeout < 2000000L; timeout++) if (STATUS & STAT_REQ) break; /* Remove ATN. */ CONTROL = BASE_CMD | CMD_DRVR_ENABLE; if (!(STATUS & STAT_REQ)) { /* * This should not be taken as an error, but more like an * unsupported feature! Should set a flag indicating that the * target don't support messages, and continue without failure. * (THIS IS NOT AN ERROR!) */ } else { msg[0] = MSG_IDENTIFY(scb->xs->xs_periph->periph_lun, 1); len = 1; data = msg; phase = PH_MSGOUT; /* Should do test on result of sea_transfer_pio(). */ sea_transfer_pio(sea, &phase, &len, &data); } if (!(STATUS & STAT_BSY)) printf("%s: after successful arbitrate: no STAT_BSY!\n", sea->sc_dev.dv_xname); sea->nexus = scb; sea->busy[scb->xs->xs_periph->periph_target] |= 1 << scb->xs->xs_periph->periph_lun; /* This assignment should depend on possibility to send a message to target. */ CONTROL = BASE_CMD | CMD_DRVR_ENABLE; /* XXX Reset pointer in command? */ return 0; } /* * Send an abort to the target. Return 1 success, 0 on failure. */ int sea_abort(sea, scb) struct sea_softc *sea; struct sea_scb *scb; { struct sea_scb *tmp; u_char msg, phase, *msgptr; int len; /* * If the command hasn't been issued yet, we simply remove it from the * issue queue * XXX Could avoid this loop. */ for (tmp = sea->ready_list.tqh_first; tmp; tmp = tmp->chain.tqe_next) if (scb == tmp) { TAILQ_REMOVE(&sea->ready_list, scb, chain); /* XXX Set some type of error result for operation. */ return 1; } /* * If any commands are connected, we're going to fail the abort and let * the high level SCSI driver retry at a later time or issue a reset. */ if (sea->nexus) return 0; /* * If the command is currently disconnected from the bus, and there are * no connected commands, we reconnect the I_T_L or I_T_L_Q nexus * associated with it, go into message out, and send an abort message. */ for (tmp = sea->nexus_list.tqh_first; tmp; tmp = tmp->chain.tqe_next) if (scb == tmp) { if (sea_select(sea, scb)) return 0; msg = MSG_ABORT; msgptr = &msg; len = 1; phase = PH_MSGOUT; CONTROL = BASE_CMD | CMD_ATTN; sea_transfer_pio(sea, &phase, &len, &msgptr); for (tmp = sea->nexus_list.tqh_first; tmp; tmp = tmp->chain.tqe_next) if (scb == tmp) { TAILQ_REMOVE(&sea->nexus_list, scb, chain); /* XXX Set some type of error result for the operation. */ return 1; } } /* Command not found in any queue; race condition? */ return 1; } void sea_done(sea, scb) struct sea_softc *sea; struct sea_scb *scb; { struct scsipi_xfer *xs = scb->xs; callout_stop(&scb->xs->xs_callout); xs->resid = scb->datalen; /* XXXX need to get status */ if (scb->flags == SCB_ACTIVE) { xs->resid = 0; } else { if (scb->flags & (SCB_TIMEOUT | SCB_ABORTED)) xs->error = XS_TIMEOUT; if (scb->flags & SCB_ERROR) xs->error = XS_DRIVER_STUFFUP; } sea_free_scb(sea, scb, xs->xs_control); scsipi_done(xs); } /* * Wait for completion of command in polled mode. */ int sea_poll(sea, xs, count) struct sea_softc *sea; struct scsipi_xfer *xs; int count; { int s; while (count) { /* try to do something */ s = splbio(); if (!main_running) sea_main(); splx(s); if (xs->xs_status & XS_STS_DONE) return 0; delay(1000); count--; } return 1; } /* * Do the transfer. We know we are connected. Update the flags, and call * sea_done() when task accomplished. Dialog controlled by the target. */ void sea_information_transfer(sea) struct sea_softc *sea; { int timeout; u_char msgout = MSG_NOOP; int len; int s; u_char *data; u_char phase, tmp, old_phase = PH_INVALID; struct sea_scb *scb = sea->nexus; int loop; for (timeout = 0; timeout < 10000000L; timeout++) { tmp = STATUS; if (tmp & STAT_PARITY) printf("%s: parity error detected\n", sea->sc_dev.dv_xname); if (!(tmp & STAT_BSY)) { for (loop = 0; loop < 20; loop++) if ((tmp = STATUS) & STAT_BSY) break; if (!(tmp & STAT_BSY)) { printf("%s: !STAT_BSY unit in data transfer!\n", sea->sc_dev.dv_xname); s = splbio(); sea->nexus = NULL; scb->flags = SCB_ERROR; splx(s); sea_done(sea, scb); return; } } /* we only have a valid SCSI phase when REQ is asserted */ if (!(tmp & STAT_REQ)) continue; if (sea->type == FDOMAIN840) tmp = ((tmp & 0x08) >> 2) | ((tmp & 0x02) << 2) | (tmp & 0xf5); phase = tmp & PH_MASK; if (phase != old_phase) old_phase = phase; switch (phase) { case PH_DATAOUT: #ifdef SEA_NODATAOUT printf("%s: SEA_NODATAOUT set, attempted DATAOUT aborted\n", sea->sc_dev.dv_xname); msgout = MSG_ABORT; CONTROL = BASE_CMD | CMD_ATTN; break; #endif case PH_DATAIN: if (!scb->data) printf("no data address!\n"); #ifdef SEA_BLINDTRANSFER if (scb->datalen && !(scb->datalen % BLOCK_SIZE)) { while (scb->datalen) { for (loop = 0; loop < 50000; loop++) if ((tmp = STATUS) & STAT_REQ) break; if (!(tmp & STAT_REQ)) { printf("%s: timeout waiting for STAT_REQ\n", sea->sc_dev.dv_xname); /* XXX Do something? */ } if (sea->type == FDOMAIN840) tmp = ((tmp & 0x08) >> 2) | ((tmp & 0x02) << 2) | (tmp & 0xf5); if ((tmp & PH_MASK) != phase) break; if (!(phase & STAT_IO)) { #ifdef SEA_ASSEMBLER caddr_t junk; __asm("cld\n\t\ rep\n\t\ movsl" : "=S" (scb->data), "=c" (len), "=D" (junk) : "0" (scb->data), "1" (BLOCK_SIZE >> 2), "2" (sea->maddr_dr)); #else for (len = BLOCK_SIZE; len; len--) DATA = *(scb->data++); #endif } else { #ifdef SEA_ASSEMBLER caddr_t junk; __asm("cld\n\t\ rep\n\t\ movsl" : "=D" (scb->data), "=c" (len), "=S" (junk) : "0" (scb->data), "1" (BLOCK_SIZE >> 2), "2" (sea->maddr_dr)); #else for (len = BLOCK_SIZE; len; len--) *(scb->data++) = DATA; #endif } scb->datalen -= BLOCK_SIZE; } } #endif if (scb->datalen) sea_transfer_pio(sea, &phase, &scb->datalen, &scb->data); break; case PH_MSGIN: /* Multibyte messages should not be present here. */ len = 1; data = &tmp; sea_transfer_pio(sea, &phase, &len, &data); /* scb->MessageIn = tmp; */ switch (tmp) { case MSG_ABORT: scb->flags = SCB_ABORTED; printf("sea: command aborted by target\n"); CONTROL = BASE_CMD; sea_done(sea, scb); return; case MSG_CMDCOMPLETE: s = splbio(); sea->nexus = NULL; splx(s); sea->busy[scb->xs->xs_periph->periph_target] &= ~(1 << scb->xs->xs_periph->periph_lun); CONTROL = BASE_CMD; sea_done(sea, scb); return; case MSG_MESSAGE_REJECT: printf("%s: message_reject received\n", sea->sc_dev.dv_xname); break; case MSG_DISCONNECT: s = splbio(); TAILQ_INSERT_TAIL(&sea->nexus_list, scb, chain); sea->nexus = NULL; CONTROL = BASE_CMD; splx(s); return; case MSG_SAVEDATAPOINTER: case MSG_RESTOREPOINTERS: /* save/restore of pointers are ignored */ break; default: /* * This should be handled in the pio data * transfer phase, as the ATN should be raised * before ACK goes false when rejecting a * message. */ printf("%s: unknown message in: %x\n", sea->sc_dev.dv_xname, tmp); break; } /* switch (tmp) */ break; case PH_MSGOUT: len = 1; data = &msgout; /* sea->last_message = msgout; */ sea_transfer_pio(sea, &phase, &len, &data); if (msgout == MSG_ABORT) { printf("%s: sent message abort to target\n", sea->sc_dev.dv_xname); s = splbio(); sea->busy[scb->xs->xs_periph->periph_target] &= ~(1 << scb->xs->xs_periph->periph_lun); sea->nexus = NULL; scb->flags = SCB_ABORTED; splx(s); /* enable interrupt from scsi */ sea_done(sea, scb); return; } msgout = MSG_NOOP; break; case PH_CMD: len = scb->xs->cmdlen; data = (char *) scb->xs->cmd; sea_transfer_pio(sea, &phase, &len, &data); break; case PH_STAT: len = 1; data = &tmp; sea_transfer_pio(sea, &phase, &len, &data); scb->xs->status = tmp; break; default: printf("sea: unknown phase\n"); } /* switch (phase) */ } /* for (...) */ /* If we get here we have got a timeout! */ printf("%s: timeout in data transfer\n", sea->sc_dev.dv_xname); scb->flags = SCB_TIMEOUT; /* XXX Should I clear scsi-bus state? */ sea_done(sea, scb); }