#include "all.h" #include "io.h" #include "mem.h" enum { Rownid = 0x00, /* own ID/CDB size register */ Eaf = 0x08, /* enable advanced features */ Fs16MHz = 0x80, /* frequency select */ Rcontrol = 0x01, /* control register */ Hsp = 0x01, /* halt on scsi parity error */ Idi = 0x04, /* intermediate disconnect interrupt */ Edi = 0x08, /* ending disconnect interrupt */ BurstDma = 0x20, /* burst mode dma */ Rtimeout = 0x02, /* timeout period register */ Rcdb = 0x03, /* CDB first byte */ Rlun = 0x0F, /* target LUN register */ Rcmdphase = 0x10, /* command phase register */ Rsync = 0x11, /* synchronous transfer register */ Rtchi = 0x12, /* transfer count hi register */ Rtcmi = 0x13, /* transfer count mi register */ Rtclo = 0x14, /* transfer count lo register */ Rdestid = 0x15, /* destination ID */ Dpd = 0x40, /* data-phase direction (read) */ Rsrcid = 0x16, /* source ID */ Siv = 0x08, /* source ID is valid */ Er = 0x80, /* enable reselection */ Rstatus = 0x17, /* scsi status register */ Rcmd = 0x18, /* command register */ Rdata = 0x19, /* data register */ Raux = 0x1F, /* auxiliary status register */ Dbr = 0x01, /* data buffer ready */ Cip = 0x10, /* command in progress */ Intrpend = 0x80, /* interrupt pending */ Creset = 0x00, /* reset */ Catn = 0x02, /* assert ATN */ Cnack = 0x03, /* negate ACK */ CselectATN = 0x06, /* select with ATN */ Cselect = 0x07, /* select without ATN */ CtransferATN = 0x08, /* select with ATN and transfer */ Ctransfer = 0x09, /* select without ATN and transfer */ Ctinfo = 0x20, /* transfer info */ Csbt = 0x80, /* single-byte transfer */ Mcomplete = 0x00, /* complete */ Msaveptr = 0x02, /* save pointers */ Mdisconnect = 0x04, /* disconnect */ Mabort = 0x06, /* abort */ Mreject = 0x07, /* reject */ Mnop = 0x08, /* nop */ Mbdr = 0x0C, /* bus device reset */ Midentify = (0x80|0x40), /* identify|disconnectok */ }; typedef struct Target Target; struct Target { QLock; Lock regs; Rendez; ulong flags; uchar sync; uchar transfer; uchar cmdphase; Target *active; /* link on active list */ int target; /* SCSI Unit and logical unit */ int lun; int status; int probe; Drive drv; /* Kens idea of this device */ struct { /* DMA things */ ulong dmaaddr; ulong dmalen; int rflag; ulong *dmacnt; uchar *dmafls; ulong dmamap; }; uchar cdb[16]; int clen; uchar *addr; uchar *data; Target *base; Target *ctlr; }; enum { NCtlr = 2, NTarget = 8, CtlrID = 0, Fbusy = 0x0001, Fidentified = 0x0002, Fdisconnected = 0x0004, Freselect = 0x0008, Fcomplete = 0x0010, Fsyncxfer = 0x0100, Freadxfer = 0x0200, Fdmaxfer = 0x0400, Fretry = 0x1000, Fidentifying = 0x2000, }; static Target target[NCtlr][NTarget]; static void dmamap(Target*); static int scsiexec(Target*, int, uchar*, int, void*, int); static void issue(Target *ctlr, uchar cmd) { while(*ctlr->addr & Cip) ; *ctlr->addr = Rcmd; *ctlr->data = cmd; } static int reqsense(Target *tp) { uchar cmd[6], sense[255]; int status; memset(cmd, 0, sizeof(cmd)); cmd[0] = 0x03; cmd[4] = sizeof(sense); memset(sense, 0, sizeof(sense)); status = scsiexec(tp, SCSIread, cmd, sizeof(cmd), sense, sizeof(sense)); if(status != 0x6000) return status; /* * Unit attention. We can handle that. */ if((sense[2] & 0x0F) == 0x00 || (sense[2] & 0x0F) == 0x06) return 0; /* * Recovered error. Why bother telling me. */ if((sense[2] & 0x0F) == 0x01) return 0; print("scsi reqsense %D: key #%2.2ux code #%2.2ux #%2.2ux\n", tp->drv.dev, sense[2], sense[12], sense[13]); return 0x6002; } /* * Set up and start a data transfer. * We don't load the command phase here as * we may be continuing a stopped operation. */ static void start(Target *tp) { ulong count; Target *dev = tp->ctlr; /* * Set up the chip for the transfer. * First the LUN and transfer mode. */ *dev->addr = Rlun; *dev->data = tp->lun; *dev->data = tp->cmdphase; *dev->data = tp->sync; /* * Set up transfer count and targetID+direction. * No need to set the address register here, we're * already pointing to the right one. */ count = tp->dmalen; *dev->data = (count>>16) & 0xFF; *dev->data = (count>>8) & 0xFF; *dev->data = count & 0xFF; if(tp->rflag == 0) *dev->data = Dpd|tp->target; else *dev->data = tp->target; /* * Load the command into the chip. */ if(tp->cmdphase < 0x40) { *dev->addr = Rownid; *dev->data = tp->clen; *dev->addr = Rcdb; for(count = 0; count < tp->clen; count++) *dev->data = tp->cdb[count]; } dmamap(tp); /* * Select and transfer. */ issue(dev, tp->transfer); } /* * Arbitrate for the bus and start a data transfer. * If this is the first time through for the target, * i.e. we haven't identified yet, just set up for * selection so we can try a synchronous negotiation. */ static void arbitrate(Target *dev) { Target *tp; tp = dev->active; tp->cmdphase = 0x00; if(tp->flags & Fidentified) { start(tp); return; } /* * Fretry means that we will try this request again, * but without initialising the command phase, so we will * continue from where we left off. */ tp->flags |= Fidentifying|Fretry; *dev->addr = Rlun; *dev->data = tp->lun; *dev->data = 0x00; /* cmdphase */ *dev->addr = Rdestid; *dev->data = tp->target; issue(dev, CselectATN); } static int done(void *arg) { return (((Target*)arg)->flags & Fbusy) == 0; } int scsiio(Device d, int rw, uchar *cmd, int clen, void *data, int dlen) { ulong s; Target *tp; if(d.ctrl >= NCtlr) panic("scsiio: d.ctrl = %d\n", d.ctrl); tp = &target[d.ctrl][d.unit]; tp->lun = cmd[1]>>5; s = scsiexec(tp, rw, cmd, clen, data, dlen); /* print("%D: cmd #%2.2lux #%4.4lux\n", d, cmd[0], s); /**/ if(s == 0x6000) s = 0; return s; } static int scsiexec(Target *tp, int rw, uchar *cmd, int cbytes, void *data, int dbytes) { ulong s; Drive *dr; int status; Target *ctlr, **l, *f; ctlr = tp->ctlr; dr = &tp->drv; if(dr->fflag == 0) { dofilter(&dr->rate); dofilter(&dr->work); dr->fflag = 1; } dr->work.count++; dr->rate.count += dbytes; qlock(tp); tp->flags |= Fbusy; tp->rflag = rw; tp->dmaaddr = (ulong)data; tp->dmalen = dbytes; tp->status = 0x6002; memmove(tp->cdb, cmd, cbytes); tp->clen = cbytes; s = splhi(); lock(&ctlr->regs); /* * Link the target onto the end of the * ctlr active list and start the request if * the controller is idle. */ tp->active = 0; l = &ctlr->active; for(f = *l; f; f = f->active) l = &f->active; *l = tp; if((ctlr->flags & Fbusy) == 0){ ctlr->flags |= Fbusy; arbitrate(ctlr); } unlock(&ctlr->regs); splx(s); /* * Wait for the request to complete * and return the status. */ sleep(tp, done, tp); status = tp->status; qunlock(tp); return status; } /* * Target request complete. * Only called from interrupt level, * i.e. ctlr is 'locked'. */ static void complete(Target *ctlr) { Target *tp; tp = ctlr->active; tp->flags &= (Fretry|Fidentified|Fbusy); /* * Retry (continue) the operation if necessary * (used for initial identify), otherwise unlink from * the ctlr active list. */ if(tp->flags & Fretry){ tp->flags &= ~Fretry; start(tp); return; } ctlr->active = tp->active; /* * All done with this target, wakeup scsiio. * If there are other targets queued, start * one up. */ tp->flags &= Fidentified; *tp->dmafls = 0; wakeup(tp); if(ctlr->active){ arbitrate(ctlr); return; } ctlr->flags &= ~Fbusy; } /* * A target has disconnected. * Unlink it from the active list and * start another if possible. * Only called from interrupt level, * i.e. ctlr is 'locked'. */ static void disconnect(Target *ctlr) { Target *tp; tp = ctlr->active; tp->flags |= Fdisconnected; ctlr->active = tp->active; if(ctlr->active) arbitrate(ctlr); else ctlr->flags &= ~Fbusy; } /* * A target reselected, put it back * on the ctlr active list. We mark the ctlr * busy, it's the responsibility of the caller * to restart the transfer. * Only called from interrupt level, * i.e. ctlr is 'locked'. */ static void reselect(Target *ctlr, uchar id) { Target *tp; tp = &ctlr->base[id]; tp->active = ctlr->active; ctlr->active = tp; ctlr->flags |= Fbusy; tp->flags &= ~Fdisconnected; } static int identify(Target *tp, uchar status) { int i, n; Target *dev; static uchar msgout[6] = { 0x00, 0x01, 0x03, 0x01, 0x3F, 0x0C }; uchar msgin[16]; dev = tp->ctlr; switch(status){ case 0x11: /* select complete */ /* * This is what WD calls an 'extended interrupt', * the next interrupt (0x8E) is already in the * pipe, we don't have to do anything. */ break; case 0x8E: /* service required - MSGOUT */ /* * Send a synchronous data transfer request. * The target may act strangely here and not respond * correctly. */ tp->sync = 0x00; /* asynchronous transfer mode */ tp->flags |= Fidentified; *dev->addr = Rtchi; *dev->data = 0x00; *dev->data = 0x00; *dev->data = sizeof(msgout); issue(dev, Ctinfo); msgout[0] = Midentify|tp->lun; *dev->addr = Rdata; n = 0; while(n < sizeof(msgout)) { /* * Wait until the chip says ready. * If an interrupt is indicated, something * went wrong, so get out of here and let * the interrupt happen. */ if(*dev->addr & Intrpend) return 0; if(*dev->addr & Dbr){ *dev->data = msgout[n]; n++; } } break; case 0x1F: /* transfer complete - MSGIN */ case 0x4F: /* unexpected MSGIN */ /* * Collect the response. The first 3 bytes * of an extended message are: * 0. extended message code (0x01) * 1. extended message length (0x03) * 2. sync. tranfer req. code (0x01) * Only other defined code with length >1 is * 'modify data pointer', which has length 7; * we better not receive anything much longer... * Adjust the number of bytes to collect once we see * how long the response is (byte 2). * If we collect anything other than an extended message * or sync. transfer req. response, toss it * and assume async. */ *dev->addr = Rtchi; *dev->data = 0x00; *dev->data = 0x00; *dev->data = 5; issue(dev, Ctinfo); *dev->addr = Rdata; for(i = sizeof(msgin)-1, n = 0; n < i; n++){ while((*dev->addr & Dbr) == 0){ if(*dev->addr & Intrpend) return 0; } msgin[n] = *dev->data; if(n == 1) i = msgin[1] + 2; } if(msgin[2] == 0x01 && (n = msgin[3]) && n != 0xFF){ if(n < 0x40) n = 0x02; else if(n < 0x80) n = 0x04; else n = 0; tp->sync = (n<<4)|msgin[4]; } break; case 0x4E: /* * The target went to message-out phase, so send * it a NOP to keep it happy. * There seems to be no reason for this delay, other than * that we just end up busy without an interrupt if it * isn't there. * I wish I understood this. */ delay(10); *dev->addr = Rstatus; n = *dev->data; USED(n); *dev->addr = Rtchi; *dev->data = 0x00; *dev->data = 0x00; *dev->data = 1; issue(dev, Ctinfo); *dev->addr = Rdata; while((*dev->addr & Dbr) == 0){ if(*dev->addr & Intrpend) return 0; } *dev->data = Mnop; break; case 0x20: /* transfer info paused */ issue(dev, Cnack); break; case 0x1A: case 0x8A: /* * Fretry should be set, so we will * pick up from here. */ tp->cmdphase = 0x30; /* command phase has begun */ complete(dev); break; default: return 1; } return 0; } static void saveptr(Target *tp) { Target *dev; ulong count, tx; *tp->dmafls = 0; dev = tp->ctlr; /* * The transfer count register contains the * number of bytes NOT transferred. */ *dev->addr = Rtchi; count = (*dev->data & 0xFF)<<16; count += (*dev->data & 0xFF)<<8; count += (*dev->data & 0xFF); *dev->addr = Rtchi; *dev->data = 0; *dev->data = 0; *dev->data = 0; tx = tp->dmalen - count; tp->dmaaddr += tx; tp->dmalen = count; } int scsistatus(Target *ctlr) { ulong status; *ctlr->addr = Rlun; status = *ctlr->data & 0xFF; /* target status */ status |= *ctlr->data<<8; /* command phase */ return status; } static void scsiprocessint(int ctlrno, Target *ctlr) { Target *tp; uchar aux, status, sid, phase, x; tp = ctlr->active; while(*ctlr->addr & 0x20) /* Wait for busy to clear */ ; aux = *ctlr->addr; if((aux&0x80) == 0) { print("scsi%d: bogus int #%2.2lux\n", ctlrno, *ctlr->addr); return; } if(aux & 0x02) print("scsi%d: parity error (ignored)\n", ctlrno); if(aux & 0x10) print("scsi%d: cip #%2.2lux\n", ctlrno, aux); *ctlr->addr = Rcmdphase; phase = *ctlr->data; *ctlr->addr = Rsrcid; sid = *ctlr->data; *ctlr->addr = Rstatus; status = *ctlr->data; if((aux&0x40) && status != 0x81) print("lci but not reconnect #%2.2lux\n", status); switch(status) { case 0x42: /* timeout */ tp->flags &= ~Fretry; scsistatus(ctlr); /* Must read lun register */ complete(ctlr); return; case 0x16: /* select-and-transfer complete */ saveptr(tp); tp->status = scsistatus(ctlr); /* target status */ complete(ctlr); return; case 0x21: /* save data pointers */ saveptr(tp); issue(ctlr, tp->transfer); return; case 0x4B: /* unexpected status phase */ saveptr(tp); *ctlr->addr = Rcmdphase; if(phase <= 0x3c) *ctlr->data = 0x41; /* resume no data */ else *ctlr->data = 0x46; /* collect status */ issue(ctlr, tp->transfer); return; case 0x85: /* disconnect */ if(tp->flags & Fidentifying){ tp->cmdphase = 0x00; /* complete retry */ complete(ctlr); return; } disconnect(ctlr); return; case 0x81: *ctlr->addr = Rdata; x = *ctlr->data; /* the identify message */ USED(x); if((sid & Siv) == 0) { /* source ID valid */ print("scsi%d: invalid src id\n", ctlrno); break; } reselect(ctlr, sid & 0x07); ctlr->active->cmdphase = 0x45; /* reselected by target */ start(ctlr->active); return; default: if(tp == 0) { print("scsi%d: bogus sr #%2.2lux phase #%2.2lux\n", ctlrno, status, phase); return; } if((tp->flags & Fidentifying) && identify(tp, status) == 0) return; break; } print("scsi%d:\n", ctlrno); if(tp) print("unit %d cmd #%2.2lux flags #%4.4lux phase #%2.2lux\n", tp->target, tp->cdb[0], tp->flags, tp->cmdphase); print("scsi status #%.2ux phase #%2.2lux\n", status, phase); print("aux status #%2.2ux was #%2.2lux\n", *ctlr->addr, aux); for(*ctlr->addr = 0x00, x = 0x00; x < 0x19; x++){ if((x & 0x07) == 0x07) print("#%.2ux: #%2.2ux\n", x, *ctlr->data); else print("#%.2ux: #%2.2ux ", x, *ctlr->data); } panic("\nscsiintr"); } void scsiintr(int ctlrno) { Target *ctlr; ctlr = &target[ctlrno][CtlrID]; lock(&ctlr->regs); scsiprocessint(ctlrno, ctlr); unlock(&ctlr->regs); } static void scsiprobe(Target *c, Device d) { uchar cmd[6]; int i, status; Target *tp; c = c->ctlr->base; for(i = 0; i < NTarget; i++) { if(i == CtlrID) { c[i].drv.status = Dself; continue; } tp = &c[i]; tp->drv.status = Dready; /* Test unit ready */ memset(cmd, 0, sizeof(cmd)); d.unit = i; status = scsiexec(tp, SCSIread, cmd, sizeof(cmd), 0, 0); if(status == 0x6000) continue; status = scsiexec(tp, SCSIread, cmd, sizeof(cmd), 0, 0); if(status == 0x6000) continue; /* * Determine if the drive exists and is not ready or * is simply not there */ tp->drv.status = Dunavail; if(reqsense(tp) != 0) tp->drv.status = Dnready; } } Drive* scsidrive(Device d) { Target *c; if(d.ctrl >= NCtlr || d.unit >= NTarget || d.unit == CtlrID) return 0; c = &target[d.ctrl][d.unit]; c->drv.dev = d; if(c->probe == 0) { scsiprobe(c, d); c->probe = 1; } return &c->drv; } /* * Called at boot time. * Initialise the hardware. * There will be an interrupt in reponse * to the reset. */ void resetscsi(int ctlrnr, uchar *data, uchar *addr, ulong *cnt, uchar *fls, ulong map) { int i; uchar status; Target *ctlr, *tp; ctlr = &target[ctlrnr][CtlrID]; ctlr->data = data; ctlr->addr = addr; ctlr->base = target[ctlrnr]; if(*ctlr->addr & Intrpend) { *ctlr->addr = Rstatus; status = *ctlr->data; USED(status); } /* * Give the chip a soft reset. This will * cause an interrupt. Wait for it and clear it. */ *ctlr->addr = Rownid; *ctlr->data = Fs16MHz|Eaf|CtlrID; issue(ctlr, Creset); while((*ctlr->addr & Intrpend) == 0) delay(250); *ctlr->addr = Rstatus; status = *ctlr->data; USED(status); /* * Freselect means enable the chip to * respond to reselects. */ ctlr->flags = Freselect|Fidentified; *ctlr->addr = Rcontrol; *ctlr->data = BurstDma|Edi|Idi|Hsp; *ctlr->addr = Rtimeout; *ctlr->data = 40; /* 200ms */ *ctlr->addr = Rsrcid; if(ctlr->flags & Freselect) *ctlr->data = Er; else *ctlr->data = 0x00; for(i = 0; i < NTarget; i++){ if(i == CtlrID) continue; tp = &target[ctlrnr][i]; tp->lun = 0; tp->target = i; tp->ctlr = ctlr; tp->dmacnt = cnt; tp->dmafls = fls; tp->dmamap = map; if(ctlr->flags & Freselect){ tp->flags = 0; tp->transfer = CtransferATN; } else { tp->flags = Fidentified; tp->transfer = Ctransfer; } } } static void stats(Target *ctl, int ctlnr) { Drive *dr; int i, s; print("scsi stats %d\n", ctlnr); for(i=0; ifflag == 0) continue; s = dr->status; if(s != Dready) print(" drive %.2o (%d)\n", i, s); else print(" drive %.2o = %10ld\n", i, dr->max); print(" work = %F\n", (Filta){&dr->work, 1}); print(" rate = %F tBps\n", (Filta){&dr->rate, 1000}); } } static void cmd_statd(int argc, char *argv[]) { int i; USED(argc, argv); for(i=0; ipromenet; m3 &= ~((1<<6)|(1<<7)); MODEREG->promenet = m3 | (1<<6) | (1<<7); delay(500); MODEREG->promenet = m3; delay(100); resetscsi(0, SCSI0DATA, SCSI0ADDR, SCSI0CNT, SCSI0FLS, 0x1fc0); resetscsi(1, SCSI1DATA, SCSI1ADDR, SCSI1CNT, SCSI1FLS, 0x1f80); cmd_install("statd", "-- scsi stats", cmd_statd); } static void dmamap(Target *tp) { ulong y, len, addr, index, off; *tp->dmafls = 0; len = tp->dmalen; if(len == 0) return; addr = tp->dmaaddr; index = tp->dmamap; for(y = addr+len+BY2PG; addr < y; addr += BY2PG){ *WRITEMAP = (index<<16)|(addr>>12)&0xFFFF; index++; } off = tp->dmaaddr & (BY2PG-1); if(tp->rflag == 0) off |= 1<<15; *tp->dmacnt = off; }