From 873bac8339c32a2140ea5c8e9aeb3dc5db22a2cb Mon Sep 17 00:00:00 2001 From: David du Colombier <0intro@gmail.com> Date: Sat, 6 May 2000 00:00:00 +0000 Subject: [PATCH] Plan 9 from Bell Labs 2000-05-06 --- boot/local.c | 7 +- pc/io.h | 62 +- pc/pci.c | 33 +- pc/scsi.c | 468 ----------- pc/sd53c8xx.c | 2068 ++++++++++++++++++++++++++++++++++++++++++++++++ pc/sdata.c | 1581 ++++++++++++++++++++++++++++++++++++ pc/sdmylex.c | 1222 ++++++++++++++++++++++++++++ pc/sdscsi.c | 377 +++++++++ port/devsd.c | 1146 ++++++++++++++++++--------- port/portdat.h | 23 - port/portfns.h | 14 - port/sd.h | 115 +++ 12 files changed, 6209 insertions(+), 907 deletions(-) delete mode 100644 pc/scsi.c create mode 100644 pc/sd53c8xx.c create mode 100644 pc/sdata.c create mode 100644 pc/sdmylex.c create mode 100644 pc/sdscsi.c create mode 100644 port/sd.h diff --git a/boot/local.c b/boot/local.c index 2139b7c738fa254ef057e1e25be94f21abede967..51e0319486923d85f853be766134af6096055bbe 100644 --- a/boot/local.c +++ b/boot/local.c @@ -57,6 +57,7 @@ int connectlocal(void) { int p[2]; + Dir dir; char d[DIRLEN]; char partition[2*NAMELEN]; char *dev; @@ -67,11 +68,13 @@ connectlocal(void) dev = disk ? disk : bootdisk; sprint(partition, "%sfs", dev); - if(stat(partition, d) < 0){ + if(dirstat(partition, &dir) < 0){ strcpy(partition, dev); - if(stat(partition, d) < 0) + if(dirstat(partition, &dir) < 0) return -1; } + if(dir.mode & CHDIR) + return -1; print("kfs..."); if(bind("#c", "/dev", MREPL) < 0) diff --git a/pc/io.h b/pc/io.h index da8678be560f70fbcb1f93bcd55e9ee2b238aeee..35454e020e7f9af9ddab7edcfb7ee6f7a2c11b79 100644 --- a/pc/io.h +++ b/pc/io.h @@ -86,7 +86,7 @@ enum { enum { MaxEISA = 16, - EISAconfig = 0xC80, + CfgEISA = 0xC80, }; /* @@ -146,6 +146,26 @@ enum { /* type 1 pre-defined header */ PciBCR = 0x3E, /* bridge control register */ }; +enum { /* type 2 pre-defined header */ + PciCBExCA = 0x10, + PciCBSPSR = 0x16, + PciCBPBN = 0x18, /* primary bus number */ + PciCBSBN = 0x19, /* secondary bus number */ + PciCBUBN = 0x1A, /* subordinate bus number */ + PciCBSLTR = 0x1B, /* secondary latency timer */ + PciCBMBR0 = 0x1C, + PciCBMLR0 = 0x20, + PciCBMBR1 = 0x24, + PciCBMLR1 = 0x28, + PciCBIBR0 = 0x2C, /* I/O base */ + PciCBILR0 = 0x30, /* I/O limit */ + PciCBIBR1 = 0x34, /* I/O base */ + PciCBILR1 = 0x38, /* I/O limit */ + PciCBSVID = 0x40, /* subsystem vendor ID */ + PciCBSID = 0x42, /* subsystem ID */ + PciCBLMBAR = 0x44, /* legacy mode base address */ +}; + typedef struct Pcisiz Pcisiz; struct Pcisiz { @@ -161,16 +181,17 @@ struct Pcidev ushort vid; /* vendor ID */ ushort did; /* device ID */ + uchar rid; + uchar ccrp; + uchar ccru; + uchar ccrb; + struct { ulong bar; /* base address */ int size; } mem[6]; - ulong pcr; uchar intl; /* interrupt line */ - uchar ccrp; - uchar ccru; - uchar ccrb; Pcidev* list; Pcidev* link; /* next device on this bno */ @@ -180,8 +201,14 @@ struct Pcidev ulong bar; int size; } ioa, mema; + ulong pcr; }; +#define PCIWINDOW 0 +#define PCIWADDR(va) (PADDR(va)+PCIWINDOW) +#define ISAWINDOW 0 +#define ISAWADDR(va) (PADDR(va)+ISAWINDOW) + /* * PCMCIA support code. */ @@ -197,31 +224,6 @@ struct PCMmap { int ref; }; -/* - * SCSI bus - */ -enum { - MaxScsi = 8, - NTarget = 8, /* should be 16... */ -}; -struct Target { - int ctlrno; - int target; - uchar* inq; - uchar* scratch; - - Rendez rendez; - - int ok; -}; - -typedef int (*Scsiio)(Target*, int, uchar*, int, void*, int*); - -typedef struct SCSIdev { - char* type; - Scsiio (*reset)(int, ISAConf*); -} SCSIdev; - /* SMBus transactions */ enum { diff --git a/pc/pci.c b/pc/pci.c index 68fe58af055025aef51d5ba6015476aa6c62d129..e05762c9402c16e85ab64afc88b97d190537c588 100644 --- a/pc/pci.c +++ b/pc/pci.c @@ -142,13 +142,13 @@ pcibarsize(Pcidev *p, int rno) } static int -pcisizcmp(void* va, void* vb) +pcisizcmp(void *a, void *b) { - Pcisiz *a, *b; + Pcisiz *aa, *bb; - a = va; - b = vb; - return a->siz - b->siz; + aa = a; + bb = b; + return aa->siz - bb->siz; } static ulong @@ -185,7 +185,7 @@ pcibusmap(Pcidev *root, ulong *pmema, ulong *pioa, int wrreg) ioa = *pioa; mema = *pmema; - DBG("pcibusmap wr=%d %T mem=%lux io=%lux\n", + DBG("pcibusmap wr=%d %T mem=%luX io=%luX\n", wrreg, root->tbdf, mema, ioa); ntb = 0; @@ -202,7 +202,8 @@ pcibusmap(Pcidev *root, ulong *pmema, ulong *pioa, int wrreg) */ for(p = root; p != nil; p = p->link) { if(p->ccrb == 0x06) { - if(p->ccru != 0x04 || p->bridge == nil) { + if((p->ccru != 0x04 && p->ccru != 0x07) + || p->bridge == nil) { // DBG("pci: ignored bridge %T\n", p->tbdf); continue; } @@ -393,12 +394,14 @@ pciscan(int bno, Pcidev** list) pcilist = p; pcitail = p; - p->intl = pcicfgr8(p, PciINTL); + p->rid = pcicfgr8(p, PciRID); p->ccrp = pcicfgr8(p, PciCCRp); p->ccru = pcicfgr8(p, PciCCRu); p->ccrb = pcicfgr8(p, PciCCRb); p->pcr = pcicfgr32(p, PciPCR); + p->intl = pcicfgr8(p, PciINTL); + /* * If the device is a multi-function device adjust the * loop count so all possible functions are checked. @@ -452,7 +455,7 @@ pciscan(int bno, Pcidev** list) /* * Find PCI-PCI bridges and recursively descend the tree. */ - if(p->ccrb != 0x06 || p->ccru != 0x04) + if(p->ccrb != 0x06 || (p->ccru != 0x04 && p->ccru != 0x07)) continue; /* @@ -553,7 +556,8 @@ pcicfginit(void) ioa = 0; pcibusmap(pciroot, &mema, &ioa, 0); -DBG("Sizes: mem=%8.8lux size=%8.8lux io=%8.8lux\n", mema, pcimask(mema), ioa); + DBG("Sizes: mem=%8.8lux size=%8.8lux io=%8.8lux\n", + mema, pcimask(mema), ioa); /* * Align the windows and map it @@ -564,7 +568,7 @@ DBG("Sizes: mem=%8.8lux size=%8.8lux io=%8.8lux\n", mema, pcimask(mema), ioa); pcilog("Mask sizes: mem=%lux io=%lux\n", mema, ioa); pcibusmap(pciroot, &mema, &ioa, 1); -DBG("Sizes2: mem=%lux io=%lux\n", mema, ioa); + DBG("Sizes2: mem=%lux io=%lux\n", mema, ioa); unlock(&pcicfginitlock); return; @@ -785,7 +789,7 @@ pcihinv(Pcidev* p) Pcidev *t; if(p == nil) { -putstrn(PCICONS.output, PCICONS.ptr); + putstrn(PCICONS.output, PCICONS.ptr); p = pciroot; print("bus dev type vid did intl memory\n"); } @@ -808,16 +812,11 @@ putstrn(PCICONS.output, PCICONS.ptr); print("->%d", BUSBNO(t->bridge->tbdf)); print("\n"); } -#define notdef -#ifdef notdef while(p != nil) { if(p->bridge != nil) pcihinv(p->bridge); p = p->link; } -#else -print("more...\n"); -#endif /* notdef */ } void diff --git a/pc/scsi.c b/pc/scsi.c deleted file mode 100644 index 3eb3ee8529d66206fc975a1d133fb79bdae5f5bb..0000000000000000000000000000000000000000 --- a/pc/scsi.c +++ /dev/null @@ -1,468 +0,0 @@ -#include "u.h" -#include "../port/lib.h" -#include "mem.h" -#include "dat.h" -#include "fns.h" -#include "io.h" -#include "ureg.h" -#include "../port/error.h" - -enum { - Ninq = 255, - Nscratch = 255, - - CMDtest = 0x00, - CMDreqsense = 0x03, - CMDinquire = 0x12, - CMDstart = 0x1B, - CMDcapacity = 0x25, - CMDread10 = 0x28, - CMDwrite10 = 0x2A, -}; - -typedef struct { - ISAConf; - Scsiio io; - - Target target[NTarget]; -} Ctlr; - -static Ctlr* scsi[MaxScsi]; - -extern SCSIdev* scsidev[]; - -void -scsireset(void) -{ - Ctlr *ctlr; - int ctlrno, i, t; - - for(ctlr = 0, ctlrno = 0; ctlrno < MaxScsi; ctlrno++){ - if(ctlr == 0) - ctlr = malloc(sizeof(Ctlr)); - memset(ctlr, 0, sizeof(Ctlr)); - if(isaconfig("scsi", ctlrno, ctlr) == 0) - continue; - for(i = 0; scsidev[i]; i++){ - if(cistrcmp(ctlr->type, scsidev[i]->type)) - continue; - if((ctlr->io = scsidev[i]->reset(ctlrno, ctlr)) == 0) - break; - - print("scsi#%d: %s: port 0x%luX irq %lud", - ctlrno, ctlr->type, ctlr->port, - ctlr->irq); - if(ctlr->mem) - print(" addr 0x%luX", PADDR(ctlr->mem)); - if(ctlr->size) - print(" size 0x%luX", ctlr->size); - print("\n"); - - for(t = 0; t < NTarget; t++){ - ctlr->target[t].ctlrno = ctlrno; - ctlr->target[t].target = t; - ctlr->target[t].inq = xalloc(Ninq); - ctlr->target[t].scratch = xalloc(Nscratch); - } - - scsi[ctlrno] = ctlr; - ctlr = 0; - break; - } - } - if(ctlr) - free(ctlr); -} - -static uchar lastcmd[16]; -static int lastcmdsz; - -int -scsiexec(Target *t, int rw, uchar *cmd, int cbytes, void *data, int *dbytes) -{ - int s; - - /* - * Call the device-specific I/O routine. - * There should be no calls to 'error()' below this - * which percolate back up. - */ - switch(s = scsi[t->ctlrno]->io(t, rw, cmd, cbytes, data, dbytes)){ - - case STcheck: - memmove(lastcmd, cmd, cbytes); - lastcmdsz = cbytes; - /*FALLTHROUGH*/ - - default: - /* - * It's more complicated than this. There are conditions - * which are 'ok' but for which the returned status code - * is not 'STok'. - * Also, not all conditions require a reqsense, might - * need to do a reqsense here and make it available to the - * caller somehow. - * - * Later. - */ - break; - } - - return s; -} - -Target* -scsiunit(int ctlr, int unit) -{ - Target *t; - - if(ctlr < 0 || ctlr >= MaxScsi || scsi[ctlr] == 0) - return 0; - if(unit < 0 || unit >= NTarget) - return 0; - t = &scsi[ctlr]->target[unit]; - if(t->ok == 0) - return 0; - return t; -} - -static void -scsiprobe(Ctlr *ctlr) -{ - Target *t; - int i, s, nbytes; - - for(i = 0; i < NTarget; i++) { - t = &ctlr->target[i]; - - if(scsitest(t, 0) < 0) - continue; - - /* - * Determine if the drive exists and is not ready or - * is simply not responding - */ - nbytes = Nscratch; - s = scsireqsense(t, 0, t->scratch, &nbytes, 0); - if(s != STok){ - print("scsi#%d: unit %d unavailable, status %d\n", - t->ctlrno, i, s); - continue; - } - - /* - * Inquire to find out what the device is. - * Drivers then use the result to attach to targets - */ - memset(t->inq, 0, Ninq); - nbytes = Ninq; - s = scsiinquiry(t, 0, t->inq, &nbytes); - if(s != STok) { - print("scsi#%d: unit %d inquire failed, status %d\n", - t->ctlrno, i, s); - continue; - } - print("scsi#%d: unit %d: %s\n", - t->ctlrno, i, (char*)(t->inq+8)); - t->ok = 1; - } -} - -static void -inventory(void) -{ - int i; - static Lock ilock; - static int inited; - - lock(&ilock); - if(inited) { - unlock(&ilock); - return; - } - inited = 1; - unlock(&ilock); - - for(i = 0; i < MaxScsi; i++){ - if(scsi[i]) - scsiprobe(scsi[i]); - } -} - -int -scsiinv(int devno, int *type, Target **rt, uchar **inq, char *id) -{ - Target *t; - int ctlr, *i, unit; - - inventory(); - - for(;;){ - ctlr = devno/NTarget; - unit = devno%NTarget; - if(ctlr >= MaxScsi || scsi[ctlr] == 0) - return -1; - - t = &scsi[ctlr]->target[unit]; - devno++; - if(t->ok){ - for(i = type; *i >= 0; i++){ - if((t->inq[0]&0x1F) != *i) - continue; - *rt = t; - *inq = t->inq; - if(id) - sprint(id, "scsi%d: unit %d", - ctlr, unit); - return devno; - } - } - } - return -1; -} - -int -scsitest(Target *t, int lun) -{ - uchar cmd[6]; - - memset(cmd, 0, sizeof(cmd)); - cmd[0] = CMDtest; - cmd[1] = lun<<5; - return scsiexec(t, SCSIread, cmd, sizeof(cmd), 0, 0); -} - -int -scsistart(Target *t, int lun, int s) -{ - uchar cmd[6]; - - memset(cmd, 0, sizeof cmd); - cmd[0] = CMDstart; - cmd[1] = lun<<5; - cmd[4] = s? 1: 0; - return scsiexec(t, SCSIread, cmd, sizeof(cmd), 0, 0); -} - -int -scsiinquiry(Target *t, int lun, void *data, int *datalen) -{ - uchar cmd[6]; - - memset(cmd, 0, sizeof cmd); - cmd[0] = CMDinquire; - cmd[1] = lun<<5; - cmd[4] = *datalen; - return scsiexec(t, SCSIread, cmd, sizeof(cmd), data, datalen); -} - -int -scsicap(Target *t, int lun, ulong *size, ulong *bsize) -{ - int s, nbytes; - uchar cmd[10], *d; - - memset(cmd, 0, sizeof(cmd)); - cmd[0] = CMDcapacity; - cmd[1] = lun<<5; - - nbytes = 8; - d = scsialloc(nbytes); - if(d == 0) - return scsierrstr(STnomem); - - s = scsiexec(t, SCSIread, cmd, sizeof(cmd), d, &nbytes); - if(s == STok) { - *size = (d[0]<<24)|(d[1]<<16)|(d[2]<<8)|d[3]; - *bsize = (d[4]<<24)|(d[5]<<16)|(d[6]<<8)|d[7]; - } - scsifree(d); - return s; -} - -int -scsibio(Target *t, int lun, int dir, void *b, long n, long bsize, long bno) -{ - uchar cmd[10]; - int s, cdbsiz, nbytes; - - cmd[0] = CMDwrite10; - if(dir == SCSIread) - cmd[0] = CMDread10; - cmd[1] = (lun<<5); - cmd[2] = bno>>24; - cmd[3] = bno>>16; - cmd[4] = bno>>8; - cmd[5] = bno; - cmd[6] = 0; - cmd[7] = n>>8; - cmd[8] = n; - cmd[9] = 0; - cdbsiz = 10; - - nbytes = n*bsize; - s = scsiexec(t, dir, cmd, cdbsiz, b, &nbytes); - if(s != STok) { - nbytes = Nscratch; - scsireqsense(t, lun, t->scratch, &nbytes, 0); - return scsierrstr(s); - } - return nbytes; -} - -static char *key[] = -{ - "no sense", - "recovered error", - "not ready", - "medium error", - "hardware error", - "illegal request", - "unit attention", - "data protect", - "blank check", - "vendor specific", - "copy aborted", - "aborted command", - "equal", - "volume overflow", - "miscompare", - "reserved" -}; - -int -scsireqsense(Target *t, int lun, void *data, int *nbytes, int quiet) -{ - char *s; - int n, status, try; - uchar cmd[6], *sense; - - n = *nbytes; - sense = malloc(n); - - for(try = 0; try < 20; try++) { - memset(cmd, 0, sizeof(cmd)); - cmd[0] = CMDreqsense; - cmd[1] = lun<<5; - cmd[4] = n; - memset(sense, 0, n); - - *nbytes = n; - status = scsiexec(t, SCSIread, cmd, sizeof(cmd), sense, nbytes); - if(status != STok){ - free(sense); - return status; - } - *nbytes = sense[0x07]+8; - memmove(data, sense, *nbytes); - - switch(sense[2] & 0x0F){ - - case 6: /* unit attention */ - /* - * 0x28 - not ready to ready transition, - * medium may have changed. - * 0x29 - power on, RESET or BUS DEVICE RESET occurred. - */ - if(sense[12] != 0x28 && sense[12] != 0x29) - goto buggery; - /*FALLTHROUGH*/ - case 0: /* no sense */ - case 1: /* recovered error */ - free(sense); - return STok; - - case 2: /* not ready */ - if(sense[12] == 0x3A) /* medium not present */ - goto buggery; - /*FALLTHROUGH*/ - - default: - /* - * If unit is becoming ready, rather than not ready, - * then wait a little then poke it again; should this - * be here or in the caller? - */ - if((sense[12] == 0x04 && sense[13] == 0x01)){ - while(waserror()) - ; - tsleep(&t->rendez, return0, 0, 500); - poperror(); - scsitest(t, lun); - break; - } - goto buggery; - } - } - -buggery: - if(quiet == 0){ - s = key[sense[2]&0x0F]; - print("scsi#%d: unit %d reqsense: '%s' code #%2.2ux #%2.2ux\n", - t->ctlrno, t->target, s, sense[12], sense[13]); - print("scsi#%d: byte 2: #%2.2uX, bytes 15-17: #%2.2uX #%2.2uX #%2.2uX\n", - t->ctlrno, sense[2], sense[15], sense[16], sense[17]); - print("lastcmd (%d): ", lastcmdsz); - for(n = 0; n < lastcmdsz; n++) - print(" #%2.2uX", lastcmd[n]); - print("\n"); - } - free(sense); - return STcheck; -} - - -int -scsierrstr(int errno) -{ - char *p; - - switch(errno){ - case STnomem: - p = Enomem; - break; - case STtimeout: - p = "bus timeout"; - break; - case STownid: - p = "playing with myself"; - break; - case STharderr: - p = Eio; - break; - case STok: - p = Enoerror; - break; - case STcheck: - p = "check condition"; - break; - case STcondmet: - p = "condition met/good"; - break; - case STbusy: - p = "busy"; - break; - case STintok: - p = "intermediate/good"; - break; - case STintcondmet: - p = "intermediate/condition met/good"; - break; - case STresconf: - p = "reservation conflict"; - break; - case STterminated: - p = "command terminated"; - break; - case STqfull: - p = "queue full"; - break; - - default: - p = "unknown SCSI error"; - break; - } - strncpy(up->error, p, NAMELEN); - - return -1; -} diff --git a/pc/sd53c8xx.c b/pc/sd53c8xx.c new file mode 100644 index 0000000000000000000000000000000000000000..597a3936270b4113c03f23b62d13c7b4310561ad --- /dev/null +++ b/pc/sd53c8xx.c @@ -0,0 +1,2068 @@ +/* + * NCR 53c8xx driver for Plan 9 + * Nigel Roles (ngr@cotswold.demon.co.uk) + * + * 08/07/99 Ultra2 fixed. Brazil #ifdefs added. Fixed script error 6 diagnostics. + * + * 09/06/99 Enhancements to support 895 and 896 correctly. Attempt at Ultra 2 negotiation, + * though no device to test with yet. + * Variant now contains the number of valid chip registers to assist + * dumpncrregs() + * + * 06/10/98 Various bug fixes and Brazil compiler inspired changes from jmk + * + * 05/10/98 Small fix to handle command length being greater than expected by device + * + * 04/08/98 Added missing locks to interrupt handler. Marked places where + * multiple controller extensions could go + * + * 18/05/97 Fixed overestimate in size of local SCRIPT RAM + * + * 17/05/97 Bug fix to return status + * + * 06/10/96 Enhanced list of chip IDs. 875 revision 1 has no clock doubler, so assume it + * is shipped with 80MHz crystal. Use bit 3 of the GPREG to recognise differential + * boards. This is Symbios specific, but since they are about the only suppliers of + * differential cards. + * + * 23/9/96 Wide and Ultra supported. 825A and 860 added to variants. Dual compiling + * version for fileserver and cpu. 80MHz default clock for 860 + * + * 5/8/96 Waits for an Inquiry message before initiating synchronous negotiation + * in case capabilities byte [7] indicates device does not support it. Devices + * which do target initiated negotiation will typically get in first; a few + * bugs in handling this have been fixed + * + * 3/8/96 Added differential support (put scsi0=diff in plan9.ini) + * Split exec() into exec() and io(). Exec() is small, and Io() does not + * use any Plan 9 specific data structures, so alternate exec() functions + * may be done for other environments, such as the fileserver + * + * GENERAL + * + * Works on 810 and 875 + * Should work on 815, 825, 810A, 825A, 860A + * Uses local RAM, large FIFO, prefetch, burst opcode fetch, and 16 byte synch. offset + * where applicable + * Supports multi-target, wide, Ultra + * Differential mode can be enabled by putting scsi0=diff in plan9.ini + * NO SUPPORT FOR tagged queuing (yet) + * + * Known problems + */ + +#define MAXTARGET 16 /* can be 8 or 16 */ + +#include "u.h" +#include "../port/lib.h" +#include "mem.h" +#include "dat.h" +#include "fns.h" +#include "io.h" + +#include "sd.h" +extern SDifc sd53c8xxifc; + +/**********************************/ +/* Portable configuration macros */ +/**********************************/ + +//#define BOOTDEBUG +//#define ASYNC_ONLY +//#define INTERNAL_SCLK +//#define ALWAYS_DO_WDTR +#define WMR_DEBUG + +/**********************************/ +/* CPU specific macros */ +/**********************************/ + +#ifdef BOOTDEBUG + +#define KPRINT oprint +#define IPRINT intrprint +#define DEBUG(n) 0 +#define IFLUSH() iflush() + +#else + +#define KPRINT if(0)print +#define IPRINT if(0)print +#define DEBUG(n) 0 +#define IFLUSH() + +#endif /* BOOTDEBUG */ + +/*******************************/ +/* General */ +/*******************************/ + +#ifndef DMASEG +#define DMASEG(x) PADDR(x) +#define legetl(x) (*(ulong*)(x)) +#define lesetl(x,v) (*(ulong*)(x) = (v)) +#define swabl(a,b,c) +#else +#endif /*DMASEG */ +#define DMASEG_TO_KADDR(x) KADDR(PADDR(x)) +#define KPTR(x) ((x) == 0 ? 0 : DMASEG_TO_KADDR(x)) + +#define MEGA 1000000L +#ifdef INTERNAL_SCLK +#define SCLK (33 * MEGA) +#else +#define SCLK (40 * MEGA) +#endif /* INTERNAL_SCLK */ +#define ULTRA_NOCLOCKDOUBLE_SCLK (80 * MEGA) + +#define MAXSYNCSCSIRATE (5 * MEGA) +#define MAXFASTSYNCSCSIRATE (10 * MEGA) +#define MAXULTRASYNCSCSIRATE (20 * MEGA) +#define MAXULTRA2SYNCSCSIRATE (40 * MEGA) +#define MAXASYNCCORERATE (25 * MEGA) +#define MAXSYNCCORERATE (25 * MEGA) +#define MAXFASTSYNCCORERATE (50 * MEGA) +#define MAXULTRASYNCCORERATE (80 * MEGA) +#define MAXULTRA2SYNCCORERATE (160 * MEGA) + + +#define X_MSG 1 +#define X_MSG_SDTR 1 +#define X_MSG_WDTR 3 + +struct na_patch { + unsigned lwoff; + unsigned char type; +}; + +typedef struct Ncr { + uchar scntl0; /* 00 */ + uchar scntl1; + uchar scntl2; + uchar scntl3; + + uchar scid; /* 04 */ + uchar sxfer; + uchar sdid; + uchar gpreg; + + uchar sfbr; /* 08 */ + uchar socl; + uchar ssid; + uchar sbcl; + + uchar dstat; /* 0c */ + uchar sstat0; + uchar sstat1; + uchar sstat2; + + uchar dsa[4]; /* 10 */ + + uchar istat; /* 14 */ + uchar istatpad[3]; + + uchar ctest0; /* 18 */ + uchar ctest1; + uchar ctest2; + uchar ctest3; + + uchar temp[4]; /* 1c */ + + uchar dfifo; /* 20 */ + uchar ctest4; + uchar ctest5; + uchar ctest6; + + union { /* 24 */ + ulong dbc; /* only 24 bits usable LONG */ + struct { + uchar dcmdpad[3]; + uchar dcmd; + }; + }; + + uchar dnad[4]; /* 28 */ + uchar dsp[4]; /* 2c */ + uchar dsps[4]; /* 30 */ + + uchar scratcha[4]; /* 34 */ + + uchar dmode; /* 38 */ + uchar dien; + uchar dwt; + uchar dcntl; + + uchar adder[4]; /* 3c */ + + uchar sien0; /* 40 */ + uchar sien1; + uchar sist0; + uchar sist1; + + uchar slpar; /* 44 */ + uchar slparpad0; + uchar macntl; + uchar gpcntl; + + uchar stime0; /* 48 */ + uchar stime1; + uchar respid; + uchar respidpad0; + + uchar stest0; /* 4c */ + uchar stest1; + uchar stest2; + uchar stest3; + + uchar sidl; /* 50 */ + uchar sidlpad[3]; + + uchar sodl; /* 54 */ + uchar sodlpad[3]; + + uchar sbdl; /* 58 */ + uchar sbdlpad[3]; + + union { /* 5c */ + uchar scratchb[4]; + ulong scratchbncr; + }; +} Ncr; + +typedef struct Movedata { + uchar dbc[4]; + uchar pa[4]; +} Movedata; + +typedef enum NegoState { + NeitherDone, WideInit, WideResponse, WideDone, + SyncInit, SyncResponse, BothDone +} NegoState; + +typedef enum State { + Allocated, Queued, Active, Done +} State; + +typedef struct Dsa { + union { + uchar state[4]; + struct { + uchar stateb; + uchar result; + uchar dmablks; + uchar flag; /* setbyte(state,3,...) */ + }; + }; + + union { + ulong dmancr; /* For block transfer: NCR order (little-endian) */ + uchar dmaaddr[4]; + }; + + uchar target; /* Target */ + uchar pad0[3]; + + uchar lun; /* Logical Unit Number */ + uchar pad1[3]; + + uchar scntl3; + uchar sxfer; + uchar pad2[2]; + + uchar next[4]; /* chaining for SCRIPT (NCR byte order) */ + struct Dsa *freechain; /* chaining for freelist */ + Rendez; + uchar scsi_id_buf[4]; + Movedata msg_out_buf; + Movedata cmd_buf; + Movedata data_buf; + Movedata status_buf; + uchar msg_out[10]; /* enough to include SDTR */ + uchar status; + int p9status; + uchar parityerror; +} Dsa; + +typedef enum Feature { + BigFifo = 1, /* 536 byte fifo */ + BurstOpCodeFetch = 2, /* burst fetch opcodes */ + Prefetch = 4, /* prefetch 8 longwords */ + LocalRAM = 8, /* 4K longwords of local RAM */ + Differential = 16, /* Differential support */ + Wide = 32, /* Wide capable */ + Ultra = 64, /* Ultra capable */ + ClockDouble = 128, /* Has clock doubler */ + ClockQuad = 256, /* Has clock quadrupler (same as Ultra2) */ + Ultra2 = 256, +} Feature; + +typedef enum Burst { + Burst2 = 0, + Burst4 = 1, + Burst8 = 2, + Burst16 = 3, + Burst32 = 4, + Burst64 = 5, + Burst128 = 6 +} Burst; + +typedef struct Variant { + ushort did; + uchar maxrid; /* maximum allowed revision ID */ + char *name; + Burst burst; /* codings for max burst */ + uchar maxsyncoff; /* max synchronous offset */ + uchar registers; /* number of 32 bit registers */ + unsigned feature; +} Variant; + +static unsigned char cf2[] = { 6, 2, 3, 4, 6, 8, 12, 16 }; +#define NULTRA2SCF (sizeof(cf2)/sizeof(cf2[0])) +#define NULTRASCF (NULTRA2SCF - 2) +#define NSCF (NULTRASCF - 1) + +typedef struct Controller { + Lock; + uchar synctab[NULTRA2SCF - 1][8];/* table of legal tpfs */ + NegoState s[MAXTARGET]; + uchar scntl3[MAXTARGET]; + uchar sxfer[MAXTARGET]; + uchar cap[MAXTARGET]; /* capabilities byte from Identify */ + ushort capvalid; /* bit per target for validity of cap[] */ + ushort wide; /* bit per target set if wide negotiated */ + ulong sclk; /* clock speed of controller */ + uchar clockmult; /* set by synctabinit */ + uchar ccf; /* CCF bits */ + uchar tpf; /* best tpf value for this controller */ + uchar feature; /* requested features */ + int running; /* is the script processor running? */ + int ssm; /* single step mode */ + Ncr *n; /* pointer to registers */ + Variant *v; /* pointer to variant type */ + ulong *script; /* where the real script is */ + ulong scriptpa; /* where the real script is */ + Pcidev* pcidev; + SDev* sdev; + + struct { + Lock; + uchar head[4]; /* head of free list (NCR byte order) */ + Dsa *tail; + Dsa *free; + } dsalist; + + QLock q[MAXTARGET]; /* queues for each target */ +} Controller; + +static Controller controller; + +/* ISTAT */ +enum { Abrt = 0x80, Srst = 0x40, Sigp = 0x20, Sem = 0x10, Con = 0x08, Intf = 0x04, Sip = 0x02, Dip = 0x01 }; + +/* DSTAT */ +enum { Dfe = 0x80, Mdpe = 0x40, Bf = 0x20, Abrted = 0x10, Ssi = 0x08, Sir = 0x04, Iid = 0x01 }; + +/* SSTAT */ +enum { DataOut, DataIn, Cmd, Status, ReservedOut, ReservedIn, MessageOut, MessageIn }; + +static void setmovedata(Movedata*, ulong, ulong); +static void advancedata(Movedata*, long); + +static char *phase[] = { + "data out", "data in", "command", "status", + "reserved out", "reserved in", "message out", "message in" +}; + +#ifdef BOOTDEBUG +#define DEBUGSIZE 10240 +char debugbuf[DEBUGSIZE]; +char *debuglast; + +static void +intrprint(char *format, ...) +{ + if (debuglast == 0) + debuglast = debugbuf; + debuglast = doprint(debuglast, debugbuf + (DEBUGSIZE - 1), format, (&format + 1)); +} + +static void +iflush() +{ + int s; + char *endp; + s = splhi(); + if (debuglast == 0) + debuglast = debugbuf; + if (debuglast == debugbuf) { + splx(s); + return; + } + endp = debuglast; + splx(s); + screenputs(debugbuf, endp - debugbuf); + s = splhi(); + memmove(debugbuf, endp, debuglast - endp); + debuglast -= endp - debugbuf; + splx(s); +} + +static void +oprint(char *format, ...) +{ + int s; + + iflush(); + s = splhi(); + if (debuglast == 0) + debuglast = debugbuf; + debuglast = doprint(debuglast, debugbuf + (DEBUGSIZE - 1), format, (&format + 1)); + splx(s); + iflush(); +} +#endif + +#include "sd53c8xx.i" + +static Dsa * +dsaalloc(Controller *c, int target, int lun) +{ + Dsa *d; + + ilock(&c->dsalist); + if ((d = c->dsalist.free) == 0) { + d = xalloc(sizeof(*d)); + if (DEBUG(1)) + KPRINT("sd53c8xx: %d/%d: allocated new dsa %lux\n", target, lun, d); + lesetl(d->next, 0); + lesetl(d->state, A_STATE_ALLOCATED); + if (legetl(c->dsalist.head) == 0) + lesetl(c->dsalist.head, DMASEG(d)); /* ATOMIC?!? */ + else + lesetl(c->dsalist.tail->next, DMASEG(d)); /* ATOMIC?!? */ + c->dsalist.tail = d; + } + else { + if (DEBUG(1)) + KPRINT("sd53c8xx: %d/%d: reused dsa %lux\n", target, lun, d); + c->dsalist.free = d->freechain; + lesetl(d->state, A_STATE_ALLOCATED); + } + iunlock(&c->dsalist); + d->target = target; + d->lun = lun; + return d; +} + +static void +dsafree(Controller *c, Dsa *d) +{ + ilock(&c->dsalist); + d->freechain = c->dsalist.free; + c->dsalist.free = d; + lesetl(d->state, A_STATE_FREE); + iunlock(&c->dsalist); +} + +static Dsa * +dsafind(Controller *c, uchar target, uchar lun, uchar state) +{ + Dsa *d; + for (d = KPTR(legetl(c->dsalist.head)); d; d = KPTR(legetl(d->next))) { + if (d->target != 0xff && d->target != target) + continue; + if (lun != 0xff && d->lun != lun) + continue; + if (state != 0xff && d->stateb != state) + continue; + break; + } + return d; +} + +static void +dumpncrregs(Controller *c, int intr) +{ + int i; + Ncr *n = c->n; + int depth = c->v->registers / 4; + + KPRINT("sa = %.8lux\n", c->scriptpa); + for (i = 0; i < depth; i++) { + int j; + for (j = 0; j < 4; j++) { + int k = j * depth + i; + uchar *p; + + /* display little-endian to make 32-bit values readable */ + p = (uchar*)n+k*4; + if (intr) + IPRINT(" %.2x%.2x%.2x%.2x %.2x %.2x", p[3], p[2], p[1], p[0], k * 4, (k * 4) + 0x80); + else + KPRINT(" %.2x%.2x%.2x%.2x %.2x %.2x", p[3], p[2], p[1], p[0], k * 4, (k * 4) + 0x80); + USED(p); + } + if (intr) + IPRINT("\n"); + else + KPRINT("\n"); + } +} + +static int +chooserate(Controller *c, int tpf, int *scfp, int *xferpp) +{ + /* find lowest entry >= tpf */ + int besttpf = 1000; + int bestscfi = 0; + int bestxferp = 0; + int scf, xferp; + int maxscf; + + if (c->v->feature & Ultra2) + maxscf = NULTRA2SCF; + else if (c->v->feature & Ultra) + maxscf = NULTRASCF; + else + maxscf = NSCF; + + /* + * search large clock factors first since this should + * result in more reliable transfers + */ + for (scf = maxscf; scf >= 1; scf--) { + for (xferp = 0; xferp < 8; xferp++) { + unsigned char v = c->synctab[scf - 1][xferp]; + if (v == 0) + continue; + if (v >= tpf && v < besttpf) { + besttpf = v; + bestscfi = scf; + bestxferp = xferp; + } + } + } + if (besttpf == 1000) + return 0; + if (scfp) + *scfp = bestscfi; + if (xferpp) + *xferpp = bestxferp; + return besttpf; +} + +static void +synctabinit(Controller *c) +{ + int scf; + unsigned long scsilimit; + int xferp; + unsigned long cr, sr; + int tpf; + int fast; + int maxscf; + + if (c->v->feature & Ultra2) + maxscf = NULTRA2SCF; + else if (c->v->feature & Ultra) + maxscf = NULTRASCF; + else + maxscf = NSCF; + + /* + * for chips with no clock doubler, but Ultra capable (e.g. 860, or interestingly the + * first spin of the 875), assume 80MHz + * otherwise use the internal (33 Mhz) or external (40MHz) default + */ + + if ((c->v->feature & Ultra) != 0 && (c->v->feature & (ClockDouble | ClockQuad)) == 0) + c->sclk = ULTRA_NOCLOCKDOUBLE_SCLK; + else + c->sclk = SCLK; + + /* + * otherwise, if the chip is Ultra capable, but has a slow(ish) clock, + * invoke the doubler + */ + + if (SCLK <= 40000000) { + if (c->v->feature & ClockDouble) { + c->sclk *= 2; + c->clockmult = 1; + } + else if (c->v->feature & ClockQuad) { + c->sclk *= 4; + c->clockmult = 1; + } + else + c->clockmult = 0; + } + else + c->clockmult = 0; + + /* derive CCF from sclk */ + /* woebetide anyone with SCLK < 16.7 or > 80MHz */ + if (c->sclk <= 25 * MEGA) + c->ccf = 1; + else if (c->sclk <= 3750000) + c->ccf = 2; + else if (c->sclk <= 50 * MEGA) + c->ccf = 3; + else if (c->sclk <= 75 * MEGA) + c->ccf = 4; + else if ((c->v->feature & ClockDouble) && c->sclk <= 80 * MEGA) + c->ccf = 5; + else if ((c->v->feature & ClockQuad) && c->sclk <= 120 * MEGA) + c->ccf = 6; + else if ((c->v->feature & ClockQuad) && c->sclk <= 160 * MEGA) + c->ccf = 7; + + for (scf = 1; scf < maxscf; scf++) { + /* check for legal core rate */ + /* round up so we run slower for safety */ + cr = (c->sclk * 2 + cf2[scf] - 1) / cf2[scf]; + if (cr <= MAXSYNCCORERATE) { + scsilimit = MAXSYNCSCSIRATE; + fast = 0; + } + else if (cr <= MAXFASTSYNCCORERATE) { + scsilimit = MAXFASTSYNCSCSIRATE; + fast = 1; + } + else if ((c->v->feature & Ultra) && cr <= MAXULTRASYNCCORERATE) { + scsilimit = MAXULTRASYNCSCSIRATE; + fast = 2; + } + else if ((c->v->feature & Ultra2) && cr <= MAXULTRA2SYNCCORERATE) { + scsilimit = MAXULTRA2SYNCSCSIRATE; + fast = 3; + } + else + continue; + for (xferp = 11; xferp >= 4; xferp--) { + int ok; + int tp; + /* calculate scsi rate - round up again */ + /* start from sclk for accuracy */ + int totaldivide = xferp * cf2[scf]; + sr = (c->sclk * 2 + totaldivide - 1) / totaldivide; + if (sr > scsilimit) + break; + /* + * now work out transfer period + * round down now so that period is pessimistic + */ + tp = (MEGA * 1000) / sr; + /* + * bounds check it + */ + if (tp < 25 || tp > 255 * 4) + continue; + /* + * spot stupid special case for Ultra or Ultra2 + * while working out factor + */ + if (tp == 25) + tpf = 10; + else if (tp == 50) + tpf = 12; + else if (tp < 52) + continue; + else + tpf = tp / 4; + /* + * now check tpf looks sensible + * given core rate + */ + switch (fast) { + case 0: + /* scf must be ccf for SCSI 1 */ + ok = tpf >= 50 && scf == c->ccf; + break; + case 1: + ok = tpf >= 25 && tpf < 50; + break; + case 2: + /* + * must use xferp of 4, or 5 at a pinch + * for an Ultra transfer + */ + ok = xferp <= 5 && tpf >= 12 && tpf < 25; + break; + case 3: + ok = xferp == 4 && (tpf == 10 || tpf == 11); + break; + default: + ok = 0; + } + if (!ok) + continue; + c->synctab[scf - 1][xferp - 4] = tpf; + } + } + +#ifndef NO_ULTRA2 + if (c->v->feature & Ultra2) + tpf = 10; + else +#endif + if (c->v->feature & Ultra) + tpf = 12; + else + tpf = 25; + for (; tpf < 256; tpf++) { + if (chooserate(c, tpf, &scf, &xferp) == tpf) { + unsigned tp = tpf == 10 ? 25 : (tpf == 12 ? 50 : tpf * 4); + unsigned long khz = (MEGA + tp - 1) / (tp); + KPRINT("sd53c8xx: tpf=%d scf=%d.%.1d xferp=%d mhz=%ld.%.3ld\n", + tpf, cf2[scf] / 2, (cf2[scf] & 1) ? 5 : 0, + xferp + 4, khz / 1000, khz % 1000); + USED(khz); + if (c->tpf == 0) + c->tpf = tpf; /* note lowest value for controller */ + } + } +} + +static void +synctodsa(Dsa *dsa, Controller *c) +{ +/* + KPRINT("synctodsa(dsa=%lux, target=%d, scntl3=%.2lx sxfer=%.2x)\n", + dsa, dsa->target, c->scntl3[dsa->target], c->sxfer[dsa->target]); +*/ + dsa->scntl3 = c->scntl3[dsa->target]; + dsa->sxfer = c->sxfer[dsa->target]; +} + +static void +setsync(Dsa *dsa, Controller *c, int target, uchar ultra, uchar scf, uchar xferp, uchar reqack) +{ + c->scntl3[target] = + (c->scntl3[target] & 0x08) | (((scf << 4) | c->ccf | (ultra << 7)) & ~0x08); + c->sxfer[target] = (xferp << 5) | reqack; + c->s[target] = BothDone; + if (dsa) { + synctodsa(dsa, c); + c->n->scntl3 = c->scntl3[target]; + c->n->sxfer = c->sxfer[target]; + } +} + +static void +setasync(Dsa *dsa, Controller *c, int target) +{ + setsync(dsa, c, target, 0, c->ccf, 0, 0); +} + +static void +setwide(Dsa *dsa, Controller *c, int target, uchar wide) +{ + c->scntl3[target] = wide ? (1 << 3) : 0; + setasync(dsa, c, target); + c->s[target] = WideDone; +} + +static int +buildsdtrmsg(uchar *buf, uchar tpf, uchar offset) +{ + *buf++ = X_MSG; + *buf++ = 3; + *buf++ = X_MSG_SDTR; + *buf++ = tpf; + *buf = offset; + return 5; +} + +static int +buildwdtrmsg(uchar *buf, uchar expo) +{ + *buf++ = X_MSG; + *buf++ = 2; + *buf++ = X_MSG_WDTR; + *buf = expo; + return 4; +} + +static void +start(Controller *c, long entry) +{ + ulong p; + + if (c->running) + panic("sd53c8xx: start called while running"); + c->running = 1; + p = c->scriptpa + entry; + lesetl(c->n->dsp, p); + if (c->ssm) + c->n->dcntl |= 0x4; /* start DMA in SSI mode */ +} + +static void +ncrcontinue(Controller *c) +{ + if (c->running) + panic("sd53c8xx: ncrcontinue called while running"); + /* set the start DMA bit to continue execution */ + c->running = 1; + c->n->dcntl |= 0x4; +} + +static void +softreset(Controller *c) +{ + Ncr *n = c->n; + + n->istat = Srst; /* software reset */ + n->istat = 0; + /* general initialisation */ + n->scid = (1 << 6) | 7; /* respond to reselect, ID 7 */ + n->respid = 1 << 7; /* response ID = 7 */ + +#ifdef INTERNAL_SCLK + n->stest1 = 0x80; /* disable external scsi clock */ +#else + n->stest1 = 0x00; +#endif + + n->stime0 = 0xdd; /* about 0.5 second timeout on each device */ + n->scntl0 |= 0x8; /* Enable parity checking */ + + /* continued setup */ + n->sien0 = 0x8f; + n->sien1 = 0x04; + n->dien = 0x7d; + n->stest3 = 0x80; /* TolerANT enable */ + c->running = 0; + + if (c->v->feature & BigFifo) + n->ctest5 = (1 << 5); + n->dmode = c->v->burst << 6; /* set burst length bits */ + if (c->v->burst & 4) + n->ctest5 |= (1 << 2); /* including overflow into ctest5 bit 2 */ + if (c->v->feature & Prefetch) + n->dcntl |= (1 << 5); /* prefetch enable */ + else if (c->v->feature & BurstOpCodeFetch) + n->dmode |= (1 << 1); /* burst opcode fetch */ + if (c->v->feature & Differential) { + /* chip capable */ + if ((c->feature & Differential) || (n->gpreg & 0x8) == 0) { + /* user enabled, or bit 3 of GPREG clear (Symbios cards) */ + if (n->sstat2 & (1 << 2)) + print("sd53c8xx: can't go differential; wrong cable\n"); + else { + n->stest2 = (1 << 5); + print("sd53c8xx: differential mode set\n"); + } + } + } + if (c->clockmult) { + n->stest1 |= (1 << 3); /* power up doubler */ + delay(2); + n->stest3 |= (1 << 5); /* stop clock */ + n->stest1 |= (1 << 2); /* enable doubler */ + n->stest3 &= ~(1 << 5); /* start clock */ + /* pray */ + } +} + +static void +msgsm(Dsa *dsa, Controller *c, int msg, int *cont, int *wakeme) +{ + uchar histpf, hisreqack; + int tpf; + int scf, xferp; + int len; + + Ncr *n = c->n; + + switch (c->s[dsa->target]) { + case SyncInit: + switch (msg) { + case A_SIR_MSG_SDTR: + /* reply to my SDTR */ + histpf = n->scratcha[2]; + hisreqack = n->scratcha[3]; + KPRINT("sd53c8xx: %d: SDTN response %d %d\n", + dsa->target, histpf, hisreqack); + + if (hisreqack == 0) + setasync(dsa, c, dsa->target); + else { + /* hisreqack should be <= c->v->maxsyncoff */ + tpf = chooserate(c, histpf, &scf, &xferp); + KPRINT("sd53c8xx: %d: SDTN: using %d %d\n", + dsa->target, tpf, hisreqack); + setsync(dsa, c, dsa->target, tpf < 25, scf, xferp, hisreqack); + } + *cont = -2; + return; + case A_SIR_EV_PHASE_SWITCH_AFTER_ID: + /* target ignored ATN for message after IDENTIFY - not SCSI-II */ + KPRINT("sd53c8xx: %d: illegal phase switch after ID message - SCSI-1 device?\n", dsa->target); + KPRINT("sd53c8xx: %d: SDTN: async\n", dsa->target); + setasync(dsa, c, dsa->target); + *cont = E_to_decisions; + return; + case A_SIR_MSG_REJECT: + /* rejection of my SDTR */ + KPRINT("sd53c8xx: %d: SDTN: rejected SDTR\n", dsa->target); + //async: + KPRINT("sd53c8xx: %d: SDTN: async\n", dsa->target); + setasync(dsa, c, dsa->target); + *cont = -2; + return; + } + break; + case WideInit: + switch (msg) { + case A_SIR_MSG_WDTR: + /* reply to my WDTR */ + KPRINT("sd53c8xx: %d: WDTN: response %d\n", + dsa->target, n->scratcha[2]); + setwide(dsa, c, dsa->target, n->scratcha[2]); + *cont = -2; + return; + case A_SIR_EV_PHASE_SWITCH_AFTER_ID: + /* target ignored ATN for message after IDENTIFY - not SCSI-II */ + KPRINT("sd53c8xx: %d: illegal phase switch after ID message - SCSI-1 device?\n", dsa->target); + setwide(dsa, c, dsa->target, 0); + *cont = E_to_decisions; + return; + case A_SIR_MSG_REJECT: + /* rejection of my SDTR */ + KPRINT("sd53c8xx: %d: WDTN: rejected WDTR\n", dsa->target); + setwide(dsa, c, dsa->target, 0); + *cont = -2; + return; + } + break; + + case NeitherDone: + case WideDone: + case BothDone: + switch (msg) { + case A_SIR_MSG_WDTR: { + uchar hiswide, mywide; + hiswide = n->scratcha[2]; + mywide = (c->v->feature & Wide) != 0; + KPRINT("sd53c8xx: %d: WDTN: target init %d\n", + dsa->target, hiswide); + if (hiswide < mywide) + mywide = hiswide; + KPRINT("sd53c8xx: %d: WDTN: responding %d\n", + dsa->target, mywide); + setwide(dsa, c, dsa->target, mywide); + len = buildwdtrmsg(dsa->msg_out, mywide); + setmovedata(&dsa->msg_out_buf, DMASEG(dsa->msg_out), len); + *cont = E_response; + c->s[dsa->target] = WideResponse; + return; + } + case A_SIR_MSG_SDTR: +#ifdef ASYNC_ONLY + *cont = E_reject; + return; +#else + /* target decides to renegotiate */ + histpf = n->scratcha[2]; + hisreqack = n->scratcha[3]; + KPRINT("sd53c8xx: %d: SDTN: target init %d %d\n", + dsa->target, histpf, hisreqack); + if (hisreqack == 0) { + /* he wants asynchronous */ + setasync(dsa, c, dsa->target); + tpf = 0; + } + else { + /* he wants synchronous */ + tpf = chooserate(c, histpf, &scf, &xferp); + if (hisreqack > c->v->maxsyncoff) + hisreqack = c->v->maxsyncoff; + KPRINT("sd53c8xx: %d: using %d %d\n", + dsa->target, tpf, hisreqack); + setsync(dsa, c, dsa->target, tpf < 25, scf, xferp, hisreqack); + } + /* build my SDTR message */ + len = buildsdtrmsg(dsa->msg_out, tpf, hisreqack); + setmovedata(&dsa->msg_out_buf, DMASEG(dsa->msg_out), len); + *cont = E_response; + c->s[dsa->target] = SyncResponse; + return; +#endif + } + break; + case WideResponse: + switch (msg) { + case A_SIR_EV_RESPONSE_OK: + c->s[dsa->target] = WideDone; + KPRINT("sd53c8xx: %d: WDTN: response accepted\n", dsa->target); + *cont = -2; + return; + case A_SIR_MSG_REJECT: + setwide(dsa, c, dsa->target, 0); + KPRINT("sd53c8xx: %d: WDTN: response REJECTed\n", dsa->target); + *cont = -2; + return; + } + break; + case SyncResponse: + switch (msg) { + case A_SIR_EV_RESPONSE_OK: + c->s[dsa->target] = BothDone; + KPRINT("sd53c8xx: %d: SDTN: response accepted (%s)\n", + dsa->target, phase[n->sstat1 & 7]); + *cont = -2; + return; /* chf */ + case A_SIR_MSG_REJECT: + setasync(dsa, c, dsa->target); + KPRINT("sd53c8xx: %d: SDTN: response REJECTed\n", dsa->target); + *cont = -2; + return; + } + break; + } + KPRINT("sd53c8xx: %d: msgsm: state %d msg %d\n", + dsa->target, c->s[dsa->target], msg); + *wakeme = 1; + return; +} + +static void +calcblockdma(Dsa *d, ulong base, ulong count) +{ + ulong blocks; + if (DEBUG(3)) + blocks = 0; + else { + blocks = count / A_BSIZE; + if (blocks > 255) + blocks = 255; + } + d->dmablks = blocks; + d->dmaaddr[0] = base; + d->dmaaddr[1] = base >> 8; + d->dmaaddr[2] = base >> 16; + d->dmaaddr[3] = base >> 24; + setmovedata(&d->data_buf, base + blocks * A_BSIZE, count - blocks * A_BSIZE); + if (legetl(d->data_buf.dbc) == 0) + d->flag = 1; +} + +static ulong +read_mismatch_recover(Controller *c, Ncr *n, Dsa *dsa) +{ + ulong dbc = n->dbc & 0xffffff; + uchar dfifo = n->dfifo; + int inchip; + if (n->ctest5 & (1 << 5)) + inchip = ((dfifo | ((n->ctest5 & 3) << 8)) - (dbc & 0x3ff)) & 0x3ff; + else + inchip = ((dfifo & 0x7f) - (dbc & 0x7f)) & 0x7f; + if (inchip) { + IPRINT("sd53c8xx: %d/%d: read_mismatch_recover: DMA FIFO = %d\n", + dsa->target, dsa->lun, inchip); + } + if (n->sxfer & 0xf) { + /* SCSI FIFO */ + uchar fifo = n->sstat1 >> 4; + if (c->v->maxsyncoff > 8) + fifo |= (n->sstat2 & (1 << 4)); + if (fifo) { + inchip += fifo; + IPRINT("sd53c8xx: %d/%d: read_mismatch_recover: SCSI FIFO = %d\n", + dsa->target, dsa->lun, fifo); + } + } + else { + if (n->sstat0 & (1 << 7)) { + inchip++; + IPRINT("sd53c8xx: %d/%d: read_mismatch_recover: SIDL full\n", + dsa->target, dsa->lun); + } + if (n->sstat2 & (1 << 7)) { + inchip++; + IPRINT("sd53c8xx: %d/%d: read_mismatch_recover: SIDL msb full\n", + dsa->target, dsa->lun); + } + } + USED(inchip); + return dbc; +} + +static ulong +write_mismatch_recover(Ncr *n, Dsa *dsa) +{ + ulong dbc = n->dbc & 0xffffff; + uchar dfifo = n->dfifo; + int inchip; + + USED(dsa); + if (n->ctest5 & (1 << 5)) + inchip = ((dfifo | ((n->ctest5 & 3) << 8)) - (dbc & 0x3ff)) & 0x3ff; + else + inchip = ((dfifo & 0x7f) - (dbc & 0x7f)) & 0x7f; +#ifdef WMR_DEBUG + if (inchip) { + IPRINT("sd53c8xx: %d/%d: write_mismatch_recover: DMA FIFO = %d\n", + dsa->target, dsa->lun, inchip); + } +#endif + if (n->sstat0 & (1 << 5)) { + inchip++; +#ifdef WMR_DEBUG + IPRINT("sd53c8xx: %d/%d: write_mismatch_recover: SODL full\n", dsa->target, dsa->lun); +#endif + } + if (n->sstat2 & (1 << 5)) { + inchip++; +#ifdef WMR_DEBUG + IPRINT("sd53c8xx: %d/%d: write_mismatch_recover: SODL msb full\n", dsa->target, dsa->lun); +#endif + } + if (n->sxfer & 0xf) { + /* synchronous SODR */ + if (n->sstat0 & (1 << 6)) { + inchip++; +#ifdef WMR_DEBUG + IPRINT("sd53c8xx: %d/%d: write_mismatch_recover: SODR full\n", + dsa->target, dsa->lun); +#endif + } + if (n->sstat2 & (1 << 6)) { + inchip++; +#ifdef WMR_DEBUG + IPRINT("sd53c8xx: %d/%d: write_mismatch_recover: SODR msb full\n", + dsa->target, dsa->lun); +#endif + } + } + /* clear the dma fifo */ + n->ctest3 |= (1 << 2); + /* wait till done */ + while ((n->dstat & Dfe) == 0) + ; + return dbc + inchip; +} + +static void +interrupt(Ureg *ur, void *a) +{ + uchar istat; + ushort sist; + uchar dstat; + int wakeme = 0; + int cont = -1; + Dsa *dsa; + Controller *c = a; + Ncr *n = c->n; + + USED(ur); + if (DEBUG(1)) + IPRINT("sd53c8xx: int\n"); + ilock(c); + istat = n->istat; + if (istat & Intf) { + Dsa *d; + int wokesomething = 0; + if (DEBUG(1)) + IPRINT("sd53c8xx: Intfly\n"); + n->istat = Intf; + /* search for structures in A_STATE_DONE */ + for (d = KPTR(legetl(c->dsalist.head)); d; d = KPTR(legetl(d->next))) { + if (d->stateb == A_STATE_DONE) { + d->p9status = d->status; + if (DEBUG(1)) + IPRINT("sd53c8xx: waking up dsa %lux\n", d); + wakeup(d); + wokesomething = 1; + } + } + if (!wokesomething) + IPRINT("sd53c8xx: nothing to wake up\n"); + } + + if ((istat & (Sip | Dip)) == 0) { + if (DEBUG(1)) + IPRINT("sd53c8xx: int end %x\n", istat); + iunlock(c); + return; + } + + sist = (n->sist1<<8)|n->sist0; /* BUG? can two-byte read be inconsistent? */ + dstat = n->dstat; + dsa = (Dsa *)DMASEG_TO_KADDR(legetl(n->dsa)); + c->running = 0; + if (istat & Sip) { + if (DEBUG(1)) + IPRINT("sist = %.4x\n", sist); + if (sist & 0x80) { + ulong addr; + ulong sa; + ulong dbc; + ulong tbc; + int dmablks; + ulong dmaaddr; + + addr = legetl(n->dsp); + sa = addr - c->scriptpa; + if (DEBUG(1) || DEBUG(2)) + IPRINT("sd53c8xx: %d/%d: Phase Mismatch sa=%.8lux\n", + dsa->target, dsa->lun, sa); + /* + * now recover + */ + if (sa == E_data_in_mismatch) { + dbc = read_mismatch_recover(c, n, dsa); + tbc = legetl(dsa->data_buf.dbc) - dbc; + advancedata(&dsa->data_buf, tbc); + if (DEBUG(1) || DEBUG(2)) + IPRINT("sd53c8xx: %d/%d: transferred = %ld residue = %ld\n", + dsa->target, dsa->lun, tbc, legetl(dsa->data_buf.dbc)); + cont = E_to_decisions; + } + else if (sa == E_data_in_block_mismatch) { + dbc = read_mismatch_recover(c, n, dsa); + tbc = A_BSIZE - dbc; + /* recover current state from registers */ + dmablks = n->scratcha[2]; + dmaaddr = legetl(n->scratchb); + /* we have got to dmaaddr + tbc */ + /* we have dmablks * A_BSIZE - tbc + residue left to do */ + /* so remaining transfer is */ + IPRINT("in_block_mismatch: dmaaddr = 0x%lux tbc=%lud dmablks=%d\n", + dmaaddr, tbc, dmablks); + calcblockdma(dsa, dmaaddr + tbc, + dmablks * A_BSIZE - tbc + legetl(dsa->data_buf.dbc)); + /* copy changes into scratch registers */ + IPRINT("recalc: dmablks %d dmaaddr 0x%lx pa 0x%lx dbc %ld\n", + dsa->dmablks, legetl(dsa->dmaaddr), + legetl(dsa->data_buf.pa), legetl(dsa->data_buf.dbc)); + n->scratcha[2] = dsa->dmablks; + n->scratchbncr = dsa->dmancr; + cont = E_data_block_mismatch_recover; + } + else if (sa == E_data_out_mismatch) { + dbc = write_mismatch_recover(n, dsa); + tbc = legetl(dsa->data_buf.dbc) - dbc; + advancedata(&dsa->data_buf, tbc); + if (DEBUG(1) || DEBUG(2)) + IPRINT("sd53c8xx: %d/%d: transferred = %ld residue = %ld\n", + dsa->target, dsa->lun, tbc, legetl(dsa->data_buf.dbc)); + cont = E_to_decisions; + } + else if (sa == E_data_out_block_mismatch) { + dbc = write_mismatch_recover(n, dsa); + tbc = legetl(dsa->data_buf.dbc) - dbc; + /* recover current state from registers */ + dmablks = n->scratcha[2]; + dmaaddr = legetl(n->scratchb); + /* we have got to dmaaddr + tbc */ + /* we have dmablks blocks - tbc + residue left to do */ + /* so remaining transfer is */ + IPRINT("out_block_mismatch: dmaaddr = %lux tbc=%lud dmablks=%d\n", + dmaaddr, tbc, dmablks); + calcblockdma(dsa, dmaaddr + tbc, + dmablks * A_BSIZE - tbc + legetl(dsa->data_buf.dbc)); + /* copy changes into scratch registers */ + n->scratcha[2] = dsa->dmablks; + n->scratchbncr = dsa->dmancr; + cont = E_data_block_mismatch_recover; + } + else if (sa == E_id_out_mismatch) { + /* + * target switched phases while attention held during + * message out. The possibilities are: + * 1. It didn't like the last message. This is indicated + * by the new phase being message_in. Use script to recover + * + * 2. It's not SCSI-II compliant. The new phase will be other + * than message_in. We should also indicate that the device + * is asynchronous, if it's the SDTR that got ignored + * + * For now, if the phase switch is not to message_in, and + * and it happens after IDENTIFY and before SDTR, we + * notify the negotiation state machine. + */ + ulong lim = legetl(dsa->msg_out_buf.dbc); + uchar p = n->sstat1 & 7; + dbc = write_mismatch_recover(n, dsa); + tbc = lim - dbc; + IPRINT("sd53c8xx: %d/%d: msg_out_mismatch: %lud/%lud sent, phase %s\n", + dsa->target, dsa->lun, tbc, lim, phase[p]); + if (p != MessageIn && tbc == 1) { + msgsm(dsa, c, A_SIR_EV_PHASE_SWITCH_AFTER_ID, &cont, &wakeme); + } + else + cont = E_id_out_mismatch_recover; + } + else if (sa == E_cmd_out_mismatch) { + /* + * probably the command count is longer than the device wants ... + */ + ulong lim = legetl(dsa->cmd_buf.dbc); + uchar p = n->sstat1 & 7; + dbc = write_mismatch_recover(n, dsa); + tbc = lim - dbc; + IPRINT("sd53c8xx: %d/%d: cmd_out_mismatch: %lud/%lud sent, phase %s\n", + dsa->target, dsa->lun, tbc, lim, phase[p]); + USED(p, tbc); + cont = E_to_decisions; + } + else { + IPRINT("sd53c8xx: %d/%d: ma sa=%.8lux wanted=%s got=%s\n", + dsa->target, dsa->lun, sa, + phase[n->dcmd & 7], + phase[n->sstat1 & 7]); + dumpncrregs(c, 1); + dsa->p9status = SDeio; /* chf */ + wakeme = 1; + } + } + /*else*/ if (sist & 0x400) { + if (DEBUG(0)) + IPRINT("sd53c8xx: %d/%d Sto\n", dsa->target, dsa->lun); + dsa->p9status = SDtimeout; + dsa->stateb = A_STATE_DONE; + softreset(c); + cont = E_issue_check; + wakeme = 1; + } + if (sist & 0x1) { + IPRINT("sd53c8xx: %d/%d: parity error\n", dsa->target, dsa->lun); + dsa->parityerror = 1; + } + if (sist & 0x4) { + IPRINT("sd53c8xx: %d/%d: unexpected disconnect\n", + dsa->target, dsa->lun); + dumpncrregs(c, 1); + //wakeme = 1; + dsa->p9status = SDeio; + } + } + if (istat & Dip) { + if (DEBUG(1)) + IPRINT("dstat = %.2x\n", dstat); + /*else*/ if (dstat & Ssi) { + ulong *p = DMASEG_TO_KADDR(legetl(n->dsp)); + ulong w = (uchar *)p - (uchar *)c->script; + IPRINT("[%lux]", w); + USED(w); + cont = -2; /* restart */ + } + if (dstat & Sir) { + switch (legetl(n->dsps)) { + case A_SIR_MSG_IO_COMPLETE: + dsa->p9status = dsa->status; + wakeme = 1; + break; + case A_SIR_MSG_SDTR: + case A_SIR_MSG_WDTR: + case A_SIR_MSG_REJECT: + case A_SIR_EV_RESPONSE_OK: + msgsm(dsa, c, legetl(n->dsps), &cont, &wakeme); + break; + case A_SIR_MSG_IGNORE_WIDE_RESIDUE: + /* back up one in the data transfer */ + IPRINT("sd53c8xx: %d/%d: ignore wide residue %d, WSR = %d\n", + dsa->target, dsa->lun, n->scratcha[1], n->scntl2 & 1); + if (dsa->dmablks == 0 && dsa->flag) + IPRINT("sd53c8xx: %d/%d: transfer over; residue ignored\n", + dsa->target, dsa->lun); + else + calcblockdma(dsa, legetl(dsa->dmaaddr) - 1, + dsa->dmablks * A_BSIZE + legetl(dsa->data_buf.dbc) + 1); + cont = -2; + break; + case A_SIR_ERROR_NOT_MSG_IN_AFTER_RESELECT: + IPRINT("sd53c8xx: %d: not msg_in after reselect (%s)", + n->ssid & 7, phase[n->sstat1 & 7]); + dsa = dsafind(c, n->ssid & 7, -1, A_STATE_DISCONNECTED); + dumpncrregs(c, 1); + wakeme = 1; + break; + case A_SIR_NOTIFY_MSG_IN: + IPRINT("sd53c8xx: %d/%d: msg_in %d\n", + dsa->target, dsa->lun, n->sfbr); + cont = -2; + break; + case A_SIR_NOTIFY_DISC: + IPRINT("sd53c8xx: %d/%d: disconnect:", dsa->target, dsa->lun); + goto dsadump; + case A_SIR_NOTIFY_STATUS: + IPRINT("sd53c8xx: %d/%d: status\n", dsa->target, dsa->lun); + cont = -2; + break; + case A_SIR_NOTIFY_COMMAND: + IPRINT("sd53c8xx: %d/%d: commands\n", dsa->target, dsa->lun); + cont = -2; + break; + case A_SIR_NOTIFY_DATA_IN: + IPRINT("sd53c8xx: %d/%d: data in a %lx b %lx\n", + dsa->target, dsa->lun, legetl(n->scratcha), legetl(n->scratchb)); + cont = -2; + break; + case A_SIR_NOTIFY_BLOCK_DATA_IN: + IPRINT("sd53c8xx: %d/%d: block data in: a2 %x b %lx\n", + dsa->target, dsa->lun, n->scratcha[2], legetl(n->scratchb)); + cont = -2; + break; + case A_SIR_NOTIFY_DATA_OUT: + IPRINT("sd53c8xx: %d/%d: data out\n", dsa->target, dsa->lun); + cont = -2; + break; + case A_SIR_NOTIFY_DUMP: + IPRINT("sd53c8xx: %d/%d: dump\n", dsa->target, dsa->lun); + dumpncrregs(c, 1); + cont = -2; + break; + case A_SIR_NOTIFY_DUMP2: + IPRINT("sd53c8xx: %d/%d: dump2:", dsa->target, dsa->lun); + IPRINT(" sa %lux", legetl(n->dsp) - c->scriptpa); + IPRINT(" dsa %lux", legetl(n->dsa)); + IPRINT(" sfbr %ux", n->sfbr); + IPRINT(" a %lux", n->scratcha); + IPRINT(" b %lux", legetl(n->scratchb)); + IPRINT(" ssid %ux", n->ssid); + IPRINT("\n"); + cont = -2; + break; + case A_SIR_NOTIFY_WAIT_RESELECT: + IPRINT("sd53c8xx: wait reselect\n"); + cont = -2; + break; + case A_SIR_NOTIFY_RESELECT: + IPRINT("sd53c8xx: reselect: ssid %.2x sfbr %.2x at %ld\n", + n->ssid, n->sfbr, TK2MS(m->ticks)); + cont = -2; + break; + case A_SIR_NOTIFY_ISSUE: + IPRINT("sd53c8xx: %d/%d: issue:", dsa->target, dsa->lun); + dsadump: + IPRINT(" tgt=%d", dsa->target); + IPRINT(" time=%ld", TK2MS(m->ticks)); + IPRINT("\n"); + cont = -2; + break; + case A_SIR_NOTIFY_ISSUE_CHECK: + IPRINT("sd53c8xx: issue check\n"); + cont = -2; + break; + case A_SIR_NOTIFY_SIGP: + IPRINT("sd53c8xx: responded to SIGP\n"); + cont = -2; + break; + case A_SIR_NOTIFY_DUMP_NEXT_CODE: { + ulong *dsp = DMASEG_TO_KADDR(legetl(n->dsp)); + int x; + IPRINT("sd53c8xx: code at %lux", dsp - c->script); + for (x = 0; x < 6; x++) + IPRINT(" %.8lux", dsp[x]); + IPRINT("\n"); + USED(dsp); + cont = -2; + break; + } + case A_SIR_NOTIFY_WSR: + IPRINT("sd53c8xx: %d/%d: WSR set\n", dsa->target, dsa->lun); + cont = -2; + break; + case A_SIR_NOTIFY_LOAD_SYNC: + IPRINT("sd53c8xx: %d/%d: scntl=%.2x sxfer=%.2x\n", + dsa->target, dsa->lun, n->scntl3, n->sxfer); + cont = -2; + break; + case A_SIR_NOTIFY_RESELECTED_ON_SELECT: + IPRINT("sd53c8xx: %d/%d: reselected during select\n", + dsa->target, dsa->lun); + cont = -2; + break; + default: + IPRINT("sd53c8xx: %d/%d: script error %ld\n", + dsa->target, dsa->lun, legetl(n->dsps)); + dumpncrregs(c, 1); + wakeme = 1; + } + } + /*else*/ if (dstat & Iid) { + ulong addr = legetl(n->dsp); + IPRINT("sd53c8xx: %d/%d: Iid pa=%.8lux sa=%.8lux dbc=%lux\n", + dsa->target, dsa->lun, + addr, addr - c->scriptpa, n->dbc); + addr = (ulong)DMASEG_TO_KADDR(addr); + IPRINT("%.8lux %.8lux %.8lux\n", + *(ulong *)(addr - 12), *(ulong *)(addr - 8), *(ulong *)(addr - 4)); + USED(addr); + dsa->p9status = SDeio; + wakeme = 1; + } + /*else*/ if (dstat & Bf) { + IPRINT("sd53c8xx: %d/%d: Bus Fault\n", dsa->target, dsa->lun); + dumpncrregs(c, 1); + dsa->p9status = SDeio; + wakeme = 1; + } + } + if (cont == -2) + ncrcontinue(c); + else if (cont >= 0) + start(c, cont); + if (wakeme){ + if(dsa->p9status == SDnostatus) + dsa->p9status = SDeio; + wakeup(dsa); + } + iunlock(c); + if (DEBUG(1)) { + IPRINT("sd53c8xx: int end 1\n"); + } +} + +static int +done(void *arg) +{ + return ((Dsa *)arg)->p9status != SDnostatus; +} + +static void +setmovedata(Movedata *d, ulong pa, ulong bc) +{ + d->pa[0] = pa; + d->pa[1] = pa>>8; + d->pa[2] = pa>>16; + d->pa[3] = pa>>24; + d->dbc[0] = bc; + d->dbc[1] = bc>>8; + d->dbc[2] = bc>>16; + d->dbc[3] = bc>>24; +} + +static void +advancedata(Movedata *d, long v) +{ + lesetl(d->pa, legetl(d->pa) + v); + lesetl(d->dbc, legetl(d->dbc) - v); +} + +static void +dumpwritedata(uchar *data, int datalen) +{ + int i; + uchar *bp; + if (!DEBUG(0)){ + USED(data, datalen); + return; + } + + if (datalen) { + KPRINT("sd53c8xx:write:"); + for (i = 0, bp = data; i < 50 && i < datalen; i++, bp++) + KPRINT("%.2ux", *bp); + if (i < datalen) { + KPRINT("..."); + } + KPRINT("\n"); + } +} + +static void +dumpreaddata(uchar *data, int datalen) +{ + int i; + uchar *bp; + if (!DEBUG(0)){ + USED(data, datalen); + return; + } + + if (datalen) { + KPRINT("sd53c8xx:read:"); + for (i = 0, bp = data; i < 50 && i < datalen; i++, bp++) + KPRINT("%.2ux", *bp); + if (i < datalen) { + KPRINT("..."); + } + KPRINT("\n"); + } +} + +static void +busreset(Controller *c) +{ + int x, ntarget; + + /* bus reset */ + c->n->scntl1 |= (1 << 3); + delay(500); + c->n->scntl1 &= ~(1 << 3); + if(!(c->v->feature & Wide)) + ntarget = 8; + else + ntarget = MAXTARGET; + for (x = 0; x < ntarget; x++) { + setwide(0, c, x, 0); +#ifndef ASYNC_ONLY + c->s[x] = NeitherDone; +#endif + } + c->capvalid = 0; +} + +static void +reset(Controller *c) +{ + /* should wakeup all pending tasks */ + softreset(c); + busreset(c); +} + +static int +symrio(SDreq* r) +{ + Dsa *d; + uchar *bp; + Controller *c; + uchar target_expo, my_expo; + int bc, check, status, target; + + if((target = r->unit->subno) == 0x07) + return r->status = SDtimeout; /* assign */ + c = r->unit->dev->ctlr; + + check = 0; + d = dsaalloc(c, target, r->lun); + + qlock(&c->q[target]); /* obtain access to target */ +docheck: + /* load the transfer control stuff */ + d->scsi_id_buf[0] = 0; + d->scsi_id_buf[1] = c->sxfer[target]; + d->scsi_id_buf[2] = target; + d->scsi_id_buf[3] = c->scntl3[target]; + synctodsa(d, c); + + bc = 0; + + d->msg_out[bc] = 0x80 | r->lun; + +#ifndef NO_DISCONNECT + d->msg_out[bc] |= (1 << 6); +#endif + bc++; + + /* work out what to do about negotiation */ + switch (c->s[target]) { + default: + KPRINT("sd53c8xx: %d: strange nego state %d\n", target, c->s[target]); + c->s[target] = NeitherDone; + /* fall through */ + case NeitherDone: + if ((c->capvalid & (1 << target)) == 0) + break; + target_expo = (c->cap[target] >> 5) & 3; + my_expo = (c->v->feature & Wide) != 0; + if (target_expo < my_expo) + my_expo = target_expo; +#ifdef ALWAYS_DO_WDTR + bc += buildwdtrmsg(d->msg_out + bc, my_expo); + KPRINT("sd53c8xx: %d: WDTN: initiating expo %d\n", target, my_expo); + c->s[target] = WideInit; + break; +#else + if (my_expo) { + bc += buildwdtrmsg(d->msg_out + bc, (c->v->feature & Wide) ? 1 : 0); + KPRINT("sd53c8xx: %d: WDTN: initiating expo %d\n", target, my_expo); + c->s[target] = WideInit; + break; + } + KPRINT("sd53c8xx: %d: WDTN: narrow\n", target); + /* fall through */ +#endif + case WideDone: + if (c->cap[target] & (1 << 4)) { + KPRINT("sd53c8xx: %d: SDTN: initiating %d %d\n", target, c->tpf, c->v->maxsyncoff); + bc += buildsdtrmsg(d->msg_out + bc, c->tpf, c->v->maxsyncoff); + c->s[target] = SyncInit; + break; + } + KPRINT("sd53c8xx: %d: SDTN: async only\n", target); + c->s[target] = BothDone; + break; + + case BothDone: + break; + } + + setmovedata(&d->msg_out_buf, DMASEG(d->msg_out), bc); + setmovedata(&d->cmd_buf, DMASEG(r->cmd), r->clen); + calcblockdma(d, DMASEG(r->data), r->dlen); + + if (DEBUG(0)) { + KPRINT("sd53c8xx: %d/%d: exec: ", target, r->lun); + for (bp = r->cmd; bp < &r->cmd[r->clen]; bp++) + KPRINT("%.2ux", *bp); + KPRINT("\n"); + if (!r->write) + KPRINT("sd53c8xx: %d/%d: exec: limit=(%d)%ld\n", + target, r->lun, d->dmablks, legetl(d->data_buf.dbc)); + else + dumpwritedata(r->data, r->dlen); + } + + setmovedata(&d->status_buf, DMASEG(&d->status), 1); + + d->p9status = SDnostatus; + d->parityerror = 0; + + d->stateb = A_STATE_ISSUE; /* start operation */ + + ilock(c); + if (c->ssm) + c->n->dcntl |= 0x10; /* SSI */ + if (c->running) { + c->n->istat |= Sigp; + } + else { + start(c, E_issue_check); + } + iunlock(c); + + while(waserror()) + ; + tsleep(d, done, d, 30 * 1000); + poperror(); + + if (!done(d)) { + KPRINT("sd53c8xx: %d/%d: exec: Timed out", target, r->lun); + dumpncrregs(c, 0); + dsafree(c, d); + reset(c); + qunlock(&c->q[target]); + r->status = SDtimeout; + return r->status = SDtimeout; /* assign */ + } + + if((status = d->p9status) == SDeio) + c->s[target] = NeitherDone; + if (d->parityerror) { + status = SDeio; + } + + /* + * adjust datalen + */ + r->rlen = r->dlen; + if (d->dmablks > 0) + r->rlen -= d->dmablks * A_BSIZE; + else if (d->flag == 0) + r->rlen -= legetl(d->data_buf.dbc); + if(!r->write) + dumpreaddata(r->data, r->rlen); + if (DEBUG(0)) + KPRINT("53c8xx: %d/%d: exec: p9status=%d status %d rlen %ld\n", + target, r->lun, d->p9status, status, r->rlen); + /* + * spot the identify + */ + if ((c->capvalid & (1 << target)) == 0 + && (status == SDok || status == SDcheck) + && r->cmd[0] == 0x12 && r->dlen >= 8) { + c->capvalid |= 1 << target; + bp = r->data; + c->cap[target] = bp[7]; + KPRINT("sd53c8xx: %d: capabilities %.2x\n", target, bp[7]); + } + if(!check && status == SDcheck && !(r->flags & SDnosense)){ + check = 1; + r->write = 0; + memset(r->cmd, 0, sizeof(r->cmd)); + r->cmd[0] = 0x03; + r->cmd[1] = r->lun<<5; + r->cmd[4] = sizeof(r->sense)-1; + r->clen = 6; + r->data = r->sense; + r->dlen = sizeof(r->sense)-1; + /* + * Clear out the microcode state + * so the Dsa can be re-used. + */ + lesetl(d->state, A_STATE_ALLOCATED); + goto docheck; + } + qunlock(&c->q[target]); + dsafree(c, d); + + if(status == SDok && check){ + status = SDcheck; + r->flags |= SDvalidsense; + } + KPRINT("sd53c8xx: %d: r flags %8.8uX status %d rlen %d\n", + target, r->flags, r->status, r->dlen); + return status; +} + +#define NCR_VID 0x1000 +#define NCR_810_DID 0x0001 +#define NCR_820_DID 0x0002 /* don't know enough about this one to support it */ +#define NCR_825_DID 0x0003 +#define NCR_815_DID 0x0004 +#define SYM_810AP_DID 0x0005 +#define SYM_860_DID 0x0006 +#define SYM_896_DID 0x000b +#define SYM_895_DID 0x000c +#define SYM_885_DID 0x000d /* ditto */ +#define SYM_875_DID 0x000f /* ditto */ +#define SYM_875J_DID 0x008f + +static Variant variant[] = { +{ NCR_810_DID, 0x0f, "NCR53C810", Burst16, 8, 24, 0 }, +{ NCR_810_DID, 0x1f, "SYM53C810ALV", Burst16, 8, 24, Prefetch }, +{ NCR_810_DID, 0xff, "SYM53C810A", Burst16, 8, 24, Prefetch }, +{ SYM_810AP_DID, 0xff, "SYM53C810AP", Burst16, 8, 24, Prefetch }, +{ NCR_815_DID, 0xff, "NCR53C815", Burst16, 8, 24, BurstOpCodeFetch }, +{ NCR_825_DID, 0x0f, "NCR53C825", Burst16, 8, 24, Wide|BurstOpCodeFetch|Differential }, +{ NCR_825_DID, 0xff, "SYM53C825A", Burst128, 16, 24, Prefetch|LocalRAM|BigFifo|Differential|Wide }, +{ SYM_860_DID, 0x0f, "SYM53C860", Burst16, 8, 24, Prefetch|Ultra }, +{ SYM_860_DID, 0xff, "SYM53C860LV", Burst16, 8, 24, Prefetch|Ultra }, +{ SYM_875_DID, 0x01, "SYM53C875r1", Burst128, 16, 24, Prefetch|LocalRAM|BigFifo|Differential|Wide|Ultra }, +{ SYM_875_DID, 0xff, "SYM53C875", Burst128, 16, 24, Prefetch|LocalRAM|BigFifo|Differential|Wide|Ultra|ClockDouble }, +{ SYM_875J_DID, 0xff, "SYM53C875j", Burst128, 16, 24, Prefetch|LocalRAM|BigFifo|Differential|Wide|Ultra|ClockDouble }, +{ SYM_885_DID, 0xff, "SYM53C885", Burst128, 16, 24, Prefetch|LocalRAM|BigFifo|Wide|Ultra|ClockDouble }, +{ SYM_895_DID, 0xff, "SYM53C895", Burst128, 16, 24, Prefetch|LocalRAM|BigFifo|Wide|Ultra|Ultra2 }, +{ SYM_896_DID, 0xff, "SYM53C896", Burst128, 16, 64, Prefetch|LocalRAM|BigFifo|Wide|Ultra|Ultra2 }, +}; + +#define offsetof(s, t) ((ulong)&((s *)0)->t) + +static int +xfunc(Controller *c, enum na_external x, unsigned long *v) +{ + switch (x) + { + case X_scsi_id_buf: + *v = offsetof(Dsa, scsi_id_buf[0]); return 1; + case X_msg_out_buf: + *v = offsetof(Dsa, msg_out_buf); return 1; + case X_cmd_buf: + *v = offsetof(Dsa, cmd_buf); return 1; + case X_data_buf: + *v = offsetof(Dsa, data_buf); return 1; + case X_status_buf: + *v = offsetof(Dsa, status_buf); return 1; + case X_dsa_head: + *v = DMASEG(&c->dsalist.head[0]); return 1; + default: + print("xfunc: can't find external %d\n", x); + return 0; + } + return 1; +} + +static int +na_fixup(Controller *c, ulong pa_reg, + struct na_patch *patch, int patches, + int (*externval)(Controller*, int, ulong*)) +{ + int p; + int v; + ulong *script, pa_script; + unsigned long lw, lv; + + script = c->script; + pa_script = c->scriptpa; + for (p = 0; p < patches; p++) { + switch (patch[p].type) { + case 1: + /* script relative */ + script[patch[p].lwoff] += pa_script; + break; + case 2: + /* register i/o relative */ + script[patch[p].lwoff] += pa_reg; + break; + case 3: + /* data external */ + lw = script[patch[p].lwoff]; + v = (lw >> 8) & 0xff; + if (!(*externval)(c, v, &lv)) + return 0; + v = lv & 0xff; + script[patch[p].lwoff] = (lw & 0xffff00ffL) | (v << 8); + break; + case 4: + /* 32 bit external */ + lw = script[patch[p].lwoff]; + if (!(*externval)(c, lw, &lv)) + return 0; + script[patch[p].lwoff] = lv; + break; + case 5: + /* 24 bit external */ + lw = script[patch[p].lwoff]; + if (!(*externval)(c, lw & 0xffffff, &lv)) + return 0; + script[patch[p].lwoff] = (lw & 0xff000000L) | (lv & 0xffffffL); + break; + } + } + return 1; +} + +static SDev* +sympnp(void) +{ + Pcidev *p; + Variant *v; + int scriptba; + void *scriptma; + Controller *ctlr; + SDev *sdev, *head, *tail; + ulong regpa, *script, scriptpa; + + p = nil; + head = tail = nil; + while(p = pcimatch(p, NCR_VID, 0)){ + for(v = variant; v < &variant[nelem(variant)]; v++){ + if(p->did == v->did && p->rid <= v->maxrid) + break; + } + if(v >= &variant[nelem(variant)]) + continue; + print("sd53c8xx: %s rev. 0x%2.2x intr=%d command=%4.4luX\n", + v->name, p->rid, p->intl, p->pcr); + + regpa = p->mem[1].bar; + scriptba = 2; + if(regpa & 0x04){ + if(p->mem[2].bar) + continue; + scriptba++; + } + regpa = upamalloc(regpa & ~0x0F, p->mem[1].size, 0); + if(regpa == 0) + continue; + + script = nil; + scriptpa = 0; + scriptma = nil; + if((v->feature & LocalRAM) && sizeof(na_script) <= 4096){ + scriptpa = p->mem[scriptba].bar; + if((scriptpa & 0x04) && p->mem[scriptba+1].bar){ + upafree(regpa, p->mem[1].size); + continue; + } + scriptpa = upamalloc(scriptpa & ~0x0F, + p->mem[scriptba].size, 0); + if(scriptpa) + script = KADDR(scriptpa); + } + if(scriptpa == 0){ + /* + * Either the map failed, or this chip does not have + * local RAM. It will need a copy of the microcode. + */ + scriptma = malloc(sizeof(na_script)); + if(scriptma == nil){ + upafree(regpa, p->mem[1].size); + continue; + } + scriptpa = DMASEG(scriptma); + script = scriptma; + } + + ctlr = malloc(sizeof(Controller)); + sdev = malloc(sizeof(SDev)); + if(ctlr == nil || sdev == nil){ +buggery: + if(ctlr) + free(ctlr); + if(sdev) + free(sdev); + if(scriptma) + free(scriptma); + else + upafree(scriptpa, p->mem[scriptba].size); + upafree(regpa, p->mem[1].size); + continue; + } + + ctlr->n = KADDR(regpa); + ctlr->v = v; + ctlr->script = script; + memmove(ctlr->script, na_script, sizeof(na_script)); + ctlr->scriptpa = scriptpa; + if(!na_fixup(ctlr, regpa, na_patches, NA_PATCHES, xfunc)){ + print("script fixup failed\n"); + goto buggery; + } + swabl(ctlr->script, ctlr->script, sizeof(na_script)); + + ctlr->dsalist.free = 0; + lesetl(ctlr->dsalist.head, 0); + + ctlr->pcidev = p; + + sdev->ifc = &sd53c8xxifc; + sdev->ctlr = ctlr; + if(!(v->feature & Wide)) + sdev->nunit = 8; + else + sdev->nunit = MAXTARGET; + ctlr->sdev = sdev; + + if(head != nil) + tail->next = sdev; + else + head = sdev; + tail = sdev; + } + + return head; +} + +static SDev* +symid(SDev* sdev) +{ + return scsiid(sdev, &sd53c8xxifc); +} + +static int +symenable(SDev* sdev) +{ + Pcidev *pcidev; + Controller *ctlr; + char name[NAMELEN]; + + ctlr = sdev->ctlr; + pcidev = ctlr->pcidev; + + pcisetbme(pcidev); + snprint(name, NAMELEN, "%s (%s)", sdev->name, sdev->ifc->name); + intrenable(pcidev->intl, interrupt, ctlr, pcidev->tbdf, name); + + ilock(ctlr); + synctabinit(ctlr); + reset(ctlr); + iunlock(ctlr); + + return 1; +} + +SDifc sd53c8xxifc = { + "53c8xx", /* name */ + + sympnp, /* pnp */ + nil, /* legacy */ + symid, /* id */ + symenable, /* enable */ + nil, /* disable */ + + scsiverify, /* verify */ + scsionline, /* online */ + symrio, /* rio */ + nil, /* rctl */ + nil, /* wctl */ + + scsibio, /* bio */ +}; + diff --git a/pc/sdata.c b/pc/sdata.c new file mode 100644 index 0000000000000000000000000000000000000000..d4d2c39c42139eac3ef5632b04a4e95fc3a4bf24 --- /dev/null +++ b/pc/sdata.c @@ -0,0 +1,1581 @@ +#include "u.h" +#include "../port/lib.h" +#include "mem.h" +#include "dat.h" +#include "fns.h" +#include "io.h" +#include "ureg.h" +#include "../port/error.h" + +#include "sd.h" + +extern SDifc sdataifc; + +#define DEBUG 0 +#define DPRINT if(DEBUG)print + +enum { /* I/O ports */ + Data = 0, + Error = 1, /* (read) */ + Features = 1, /* (write) */ + Count = 2, /* sector count */ + Ir = 2, /* interrupt reason (PACKET) */ + Sector = 3, /* sector number, LBA<7-0> */ + Cyllo = 4, /* cylinder low, LBA<15-8> */ + Bytelo = 4, /* byte count low (PACKET) */ + Cylhi = 5, /* cylinder high, LBA<23-16> */ + Bytehi = 5, /* byte count hi (PACKET) */ + Dh = 6, /* Device/Head, LBA<32-14> */ + Status = 7, /* (read) */ + Command = 7, /* (write) */ + + As = 2, /* Alternate Status (read) */ + Dc = 2, /* Device Control (write) */ +}; + +enum { /* Error */ + Med = 0x01, /* Media error */ + Ili = 0x01, /* command set specific (PACKET) */ + Nm = 0x02, /* No Media */ + Eom = 0x02, /* command set specific (PACKET) */ + Abrt = 0x04, /* Aborted command */ + Mcr = 0x08, /* Media Change Request */ + Idnf = 0x10, /* no user-accessible address */ + Mc = 0x20, /* Media Change */ + Unc = 0x40, /* Uncorrectable data error */ + Wp = 0x40, /* Write Protect */ + Icrc = 0x80, /* Interface CRC error */ +}; + +enum { /* Features */ + Dma = 0x01, /* data transfer via DMA (PACKET) */ + Ovl = 0x02, /* command overlapped (PACKET) */ +}; + +enum { /* Interrupt Reason */ + Cd = 0x01, /* Command/Data */ + Io = 0x02, /* I/O direction */ + Rel = 0x04, /* Bus Release */ +}; + +enum { /* Device/Head */ + Dev0 = 0xA0, /* Master */ + Dev1 = 0xB0, /* Slave */ + Lba = 0x40, /* LBA mode */ +}; + +enum { /* Status, Alternate Status */ + Err = 0x01, /* Error */ + Chk = 0x01, /* Check error (PACKET) */ + Drq = 0x08, /* Data Request */ + Dsc = 0x10, /* Device Seek Complete */ + Serv = 0x10, /* Service */ + Df = 0x20, /* Device Fault */ + Dmrd = 0x20, /* DMA ready (PACKET) */ + Drdy = 0x40, /* Device Ready */ + Bsy = 0x80, /* Busy */ +}; + +enum { /* Command */ + Cnop = 0x00, /* NOP */ + Cdr = 0x08, /* Device Reset */ + Crs = 0x20, /* Read Sectors */ + Cws = 0x30, /* Write Sectors */ + Cedd = 0x90, /* Execute Device Diagnostics */ + Cpkt = 0xA0, /* Packet */ + Cidpkt = 0xA1, /* Identify Packet Device */ + Crsm = 0xC4, /* Read Multiple */ + Cwsm = 0xC5, /* Write Multiple */ + Crdq = 0xC7, /* Read DMA queued */ + Crd = 0xC8, /* Read DMA */ + Cwd = 0xCA, /* Write DMA */ + Cwdq = 0xCC, /* Write DMA queued */ + Cid = 0xEC, /* Identify Device */ + Csf = 0xEF, /* Set Features */ +}; + +enum { /* Device Control */ + Nien = 0x02, /* (not) Interrupt Enable */ + Srst = 0x04, /* Software Reset */ +}; + +enum { /* PCI Configuration Registers */ + Bmiba = 0x20, /* Bus Master Interface Base Address */ + Idetim = 0x40, /* IE Timing */ + Sidetim = 0x44, /* Slave IE Timing */ + Udmactl = 0x48, /* Ultra DMA/33 Control */ + Udmatim = 0x4A, /* Ultra DMA/33 Timing */ +}; + +enum { /* Bus Master IDE I/O Ports */ + Bmicx = 0, /* Command */ + Bmisx = 2, /* Status */ + Bmidtpx = 4, /* Descriptor Table Pointer */ +}; + +enum { /* Bmicx */ + Ssbm = 0x01, /* Start/Stop Bus Master */ + Rwcon = 0x08, /* Read/Write Control */ +}; + +enum { /* Bmisx */ + Bmidea = 0x01, /* Bus Master IDE Active */ + Idedmae = 0x02, /* IDE DMA Error (R/WC) */ + Ideints = 0x04, /* IDE Interrupt Status (R/WC) */ + Dma0cap = 0x20, /* Drive 0 DMA Capable */ + Dma1cap = 0x40, /* Drive 0 DMA Capable */ +}; +enum { /* Physical Region Descriptor */ + PrdEOT = 0x80000000, /* Bus Master IDE Active */ +}; + +enum { /* offsets into the identify info. */ + Iconfig = 0, /* general configuration */ + Ilcyl = 1, /* logical cylinders */ + Ilhead = 3, /* logical heads */ + Ilsec = 6, /* logical sectors per logical track */ + Iserial = 10, /* serial number */ + Ifirmware = 23, /* firmware revision */ + Imodel = 27, /* model number */ + Imaxrwm = 47, /* max. read/write multiple sectors */ + Icapabilities = 49, /* capabilities */ + Istandby = 50, /* device specific standby timer */ + Ipiomode = 51, /* PIO data transfer mode number */ + Ivalid = 53, + Iccyl = 54, /* cylinders if (valid&0x01) */ + Ichead = 55, /* heads if (valid&0x01) */ + Icsec = 56, /* sectors if (valid&0x01) */ + Iccap = 57, /* capacity if (valid&0x01) */ + Irwm = 59, /* read/write multiple */ + Ilba0 = 60, /* LBA size */ + Ilba1 = 61, /* LBA size */ + Imwdma = 63, /* multiword DMA mode */ + Iapiomode = 64, /* advanced PIO modes supported */ + Iminmwdma = 65, /* min. multiword DMA cycle time */ + Irecmwdma = 66, /* rec. multiword DMA cycle time */ + Iminpio = 67, /* min. PIO cycle w/o flow control */ + Iminiordy = 68, /* min. PIO cycle with IORDY */ + Ipcktbr = 71, /* time from PACKET to bus release */ + Iserbsy = 72, /* time from SERVICE to !Bsy */ + Iqdepth = 75, /* max. queue depth */ + Imajor = 80, /* major version number */ + Iminor = 81, /* minor version number */ + Icmdset0 = 82, /* command sets supported */ + Icmdset1 = 83, /* command sets supported */ + Icmdset2 = 84, /* command sets supported extension */ + Icmdset3 = 85, /* command sets enabled */ + Icmdset4 = 86, /* command sets enabled */ + Icmdset5 = 87, /* command sets enabled extension */ + Iudma = 88, /* ultra DMA mode */ + Ierase = 89, /* time for security erase */ + Ieerase = 90, /* time for enhanced security erase */ + Ipower = 91, /* current advanced power management */ + Irmsn = 127, /* removable status notification */ + Istatus = 128, /* security status */ +}; + +typedef struct Ctlr Ctlr; +typedef struct Drive Drive; + +typedef struct Prd { + ulong pa; /* Physical Base Address */ + int count; +} Prd; + +enum { + Nprd = SDmaxio/(64*1024)+2, +}; + +typedef struct Ctlr { + int cmdport; + int ctlport; + int irq; + int bmiba; /* bus master interface base address */ + + Pcidev* pcidev; + void (*ienable)(Ctlr*); + SDev* sdev; + + Drive* drive[2]; + + Prd* prdt; /* physical region descriptor table */ + + QLock; /* current command */ + Drive* curdrive; + Rendez; + int done; + + Lock; /* register access */ +} Ctlr; + +typedef struct Drive { + Ctlr* ctlr; + + int dev; + ushort info[256]; + int c; /* cylinder */ + int h; /* head */ + int s; /* sector */ + int sectors; /* total */ + int secsize; /* sector size */ + int block; /* R/W multiple size */ + int pior; /* PIO read command */ + int piow; /* PIO write command */ + int dma; /* DMA R/W possible */ + int pkt; /* PACKET device, length of pktcmd */ + + uchar sense[18]; + uchar inquiry[48]; + + QLock; /* drive access */ + int command; /* current command */ + int write; + uchar* data; + int dlen; + uchar* limit; + int count; /* sectors */ + int status; + int error; + + uchar pktcmd[16]; + int pktdma; +} Drive; + +static void +pc87415ienable(Ctlr* ctlr) +{ + Pcidev *p; + int x; + + p = ctlr->pcidev; + if(p == nil) + return; + + x = pcicfgr32(p, 0x40); + if(ctlr->cmdport == p->mem[0].bar) + x &= ~0x00000100; + else + x &= ~0x00000200; + pcicfgw32(p, 0x40, x); +} + +static int +atadebug(int cmdport, int ctlport, char* fmt, ...) +{ + int i, n; + va_list arg; + char buf[PRINTSIZE]; + + if(!DEBUG){ + USED(cmdport, ctlport, fmt); + return 0; + } + + va_start(arg, fmt); + n = doprint(buf, buf+sizeof(buf), fmt, arg) - buf; + va_end(arg); + + if(cmdport){ + if(buf[n-1] == '\n') + n--; + n += snprint(buf+n, PRINTSIZE-n, " ataregs 0x%uX:", + cmdport); + for(i = 1; i < 7; i++) + n += snprint(buf+n, PRINTSIZE-n, " 0x%2.2uX", + inb(cmdport+i)); + if(ctlport) + n += snprint(buf+n, PRINTSIZE-n, " 0x%2.2uX", + inb(ctlport+As)); + n += snprint(buf+n, PRINTSIZE-n, "\n"); + } + putstrn(buf, n); + + return n; +} + +static int +ataready(int cmdport, int ctlport, int dev, int reset, int ready, int micro) +{ + int as; + + atadebug(cmdport, ctlport, "ataready: dev %uX reset %uX ready %uX", + dev, reset, ready); + + for(;;){ + /* + * Wait for the controller to become not busy and + * possibly for a status bit to become true (usually + * Drdy). Must change to the appropriate device + * register set before testing for ready. + * Always run through the loop at least once so it + * can be used as a test for !Bsy. + */ + as = inb(ctlport+As); + if(dev && !(as & (Bsy|Drq))){ + outb(cmdport+Dh, dev); + dev = 0; + continue; + } + else if(!(as & reset)){ + if(ready == 0 || (as & ready)){ + atadebug(0, 0, "ataready: %d 0x%2.2uX\n", + micro, as); + return as; + } + } + + if(micro-- <= 0){ + atadebug(0, 0, "ataready: %d 0x%2.2uX\n", + micro, as); + break; + } + microdelay(1); + } + atadebug(cmdport, ctlport, "ataready: timeout"); + + return -1; +} + +static int +atacsfenabled(Drive* drive, vlong csf) +{ + int cmdset, i, x; + + for(i = 0; i < 3; i++){ + x = (csf>>(16*i)) & 0xFFFF; + if(x == 0) + continue; + cmdset = drive->info[Icmdset3+i]; + if(cmdset == 0 || cmdset == 0xFFFF) + return 0; + return cmdset & x; + } + + return 0; +} + +static Drive* +ataidentify(int cmdport, int ctlport, int dev) +{ + Drive *drive; + uchar buf[512], *p; + int command, dma, i, as; + ushort *sp; + + atadebug(0, 0, "identify: port 0x%uX dev 0x%2.2uX\n", cmdport, dev); + command = Cidpkt; +retry: + as = ataready(cmdport, ctlport, dev, Bsy|Drq, 0, 100*1000); + if(as < 0) + return nil; + outb(cmdport+Command, command); + microdelay(1); + + as = ataready(cmdport, ctlport, dev, Bsy, Drq|Err, 100*1000); + if(as < 0) + return nil; + if(as & Err){ + if(command == Cid) + return nil; + command = Cid; + goto retry; + } + memset(buf, 0, sizeof(buf)); + inss(cmdport+Data, buf, 256); + inb(cmdport+Status); + + if(DEBUG > 1){ + int i; + ushort *sp; + + sp = (ushort*)buf; + for(i = 0; i < 256; i++){ + if(i && (i%16) == 0) + print("\n"); + print("%4.4uX ", *sp); + sp++; + } + print("\n"); + } + + if((drive = malloc(sizeof(Drive))) == nil) + return nil; + drive->dev = dev; + memmove(drive->info, buf, sizeof(drive->info)); + drive->sense[0] = 0x70; + drive->sense[7] = sizeof(drive->sense)-7; + + drive->inquiry[2] = 2; + drive->inquiry[3] = 2; + drive->inquiry[4] = sizeof(drive->inquiry)-4; + p = &drive->inquiry[8]; + sp = &drive->info[Imodel]; + for(i = 0; i < 20; i++){ + *p++ = *sp>>8; + *p++ = *sp++; + } + if((drive->info[Iconfig] & 0xC000) == 0x8000){ + if(drive->info[Iconfig] & 0x01) + drive->pkt = 16; + else + drive->pkt = 12; + } + else{ + if(drive->info[Ivalid] & 0x0001){ + drive->c = drive->info[Ilcyl]; + drive->h = drive->info[Ilhead]; + drive->s = drive->info[Ilsec]; + } + else{ + drive->c = drive->info[Iccyl]; + drive->h = drive->info[Ichead]; + drive->s = drive->info[Icsec]; + } + if(drive->info[Icapabilities] & 0x0200){ + drive->sectors = (drive->info[Ilba1]<<16) + |drive->info[Ilba0]; + drive->dev |= Lba; + } + else + drive->sectors = drive->c*drive->h*drive->s; + } + drive->secsize = 512; + + if((drive->info[Imaxrwm] & 0xFF) && (drive->info[Irwm] & 0x0100)){ + drive->block = drive->info[Imaxrwm] & 0x00FF; + drive->pior = Crsm; + drive->piow = Cwsm; + } + else{ + drive->block = 1; + drive->pior = Crs; + drive->piow = Cws; + } + drive->block *= drive->secsize; + + /* + * Check if any DMA mode enabled. + * Assumes the BIOS has picked and enabled the best. + */ + dma = drive->info[Imwdma] & 0x0707; + drive->dma = (dma>>8) & dma; + if(drive->dma == 0 && (drive->info[Ivalid] & 0x04)){ + dma = drive->info[Iudma] & 0x1F1F; + drive->dma = (dma>>8) & dma; + if(drive->dma) + drive->dma |= 'U'<<16; + } +print("dev %4.4uX capabilities %4.4uX config %4.4uX mwdma %4.4uX dma %8.8uX\n", + dev, drive->info[Icapabilities], drive->info[Iconfig], + drive->info[Imwdma], drive->dma); + + return drive; +} + +static void +atasrst(int ctlport) +{ + /* + * Srst is a big stick and may cause problems if further + * commands are tried before the drives become ready again. + * Also, there will be problems here if overlapped commands + * are ever supported. + */ + microdelay(5); + outb(ctlport+Dc, Srst); + microdelay(5); + outb(ctlport+Dc, 0); + microdelay(2*1000); +} + +static SDev* +ataprobe(int cmdport, int ctlport, int irq) +{ + Ctlr* ctlr; + SDev *sdev; + Drive *drive; + int dev, error, rhi, rlo; + + if(ioalloc(cmdport, 8, 0, "atacmd") < 0) + return nil; + if(ioalloc(ctlport+As, 1, 0, "atactl") < 0){ + iofree(cmdport); + return nil; + } + + /* + * Try to detect a floating bus. + * Bsy should be cleared. If not, see if the cylinder registers + * are read/write capable. + * If the master fails, try the slave to catch slave-only + * configurations. + * There's no need to restore the tested registers as they will + * be reset on any detected drives by the Cedd command. + * All this indicates is that there is at least one drive on the + * controller; when the non-existent drive is selected in a + * single-drive configuration the registers of the existing drive + * are often seen, only command execution fails. + */ + dev = Dev0; + if(inb(ctlport+As) & Bsy){ + outb(cmdport+Dh, dev); +trydev1: + atadebug(cmdport, ctlport, "ataprobe bsy"); + outb(cmdport+Cyllo, 0xAA); + outb(cmdport+Cylhi, 0x55); + outb(cmdport+Sector, 0xFF); + rlo = inb(cmdport+Cyllo); + rhi = inb(cmdport+Cylhi); + if(rlo != 0xAA && (rlo == 0xFF || rhi != 0x55)){ + if(dev == Dev1){ +release: + iofree(cmdport); + iofree(ctlport+As); + return nil; + } + dev = Dev1; + if(ataready(cmdport, ctlport, dev, Bsy, 0, 20*1000) < 0) + goto trydev1; + } + } + + /* + * Disable interrupts on any detected controllers. + */ + outb(ctlport+Dc, Nien); +tryedd1: + if(ataready(cmdport, ctlport, dev, Bsy|Drq, 0, 100*1000) < 0){ + /* + * There's something there, but it didn't come up clean, + * so try hitting it with a big stick. The timing here is + * wrong but this is a last-ditch effort and it sometimes + * gets some marginal hardware back online. + */ + atasrst(ctlport); + if(ataready(cmdport, ctlport, dev, Bsy|Drq, 0, 100*1000) < 0) + goto release; + } + + /* + * Can only get here if controller is not busy. + * If there are drives Bsy will be set within 400nS, + * must wait 2mS before testing Status. + * Wait for the command to complete (6 seconds max). + */ + outb(cmdport+Command, Cedd); + delay(2); + if(ataready(cmdport, ctlport, dev, Bsy|Drq, 0, 6*1000*1000) < 0) + goto release; + + /* + * If bit 0 of the error register is set then the selected drive + * exists. This is enough to detect single-drive configurations. + * However, if the master exists there is no way short of executing + * a command to determine if a slave is present. + * It appears possible to get here testing Dev0 although it doesn't + * exist and the EDD won't take, so try again with Dev1. + */ + error = inb(cmdport+Error); + atadebug(cmdport, ctlport, "ataprobe: dev %uX", dev); + if((error & ~0x80) != 0x01){ + if(dev == Dev1) + goto release; + dev = Dev1; + goto tryedd1; + } + + /* + * At least one drive is known to exist, try to + * identify it. If that fails, don't bother checking + * any further. + * If the one drive found is Dev0 and the EDD command + * didn't indicate Dev1 doesn't exist, check for it. + */ + if((drive = ataidentify(cmdport, ctlport, dev)) == nil) + goto release; + if((ctlr = malloc(sizeof(Ctlr))) == nil){ + free(drive); + goto release; + } + if((sdev = malloc(sizeof(SDev))) == nil){ + free(ctlr); + free(drive); + goto release; + } + drive->ctlr = ctlr; + if(dev == Dev0){ + ctlr->drive[0] = drive; + if(!(error & 0x80)){ + /* + * Always leave Dh pointing to a valid drive, + * otherwise a subsequent call to ataready on + * this controller may try to test a bogus Status. + * Ataprobe is the only place possibly invalid + * drives should be selected. + */ + drive = ataidentify(cmdport, ctlport, Dev1); + if(drive != nil){ + drive->ctlr = ctlr; + ctlr->drive[1] = drive; + } + else + outb(cmdport+Dh, Dev0); + } + } + else + ctlr->drive[1] = drive; + + ctlr->cmdport = cmdport; + ctlr->ctlport = ctlport; + ctlr->irq = irq; + + sdev->ifc = &sdataifc; + sdev->ctlr = ctlr; + sdev->nunit = 2; + ctlr->sdev = sdev; + + return sdev; +} + +static int +atasetsense(Drive* drive, int status, int key, int asc, int ascq) +{ + drive->sense[2] = key; + drive->sense[12] = asc; + drive->sense[13] = ascq; + + return status; +} + +static int +atamodesense(Drive* drive, uchar* cmd) +{ + int len; + + /* + * Fake a vendor-specific request with page code 0, + * return the drive info. + */ + if((cmd[2] & 0x3F) != 0 && (cmd[2] & 0x3F) != 0x3F) + return atasetsense(drive, SDcheck, 0x05, 0x24, 0); + len = (cmd[7]<<8)|cmd[8]; + if(len == 0) + return SDok; + if(len < 8+sizeof(drive->info)) + return atasetsense(drive, SDcheck, 0x05, 0x1A, 0); + if(drive->data == nil || drive->dlen < len) + return atasetsense(drive, SDcheck, 0x05, 0x20, 1); + memset(drive->data, 0, 8); + drive->data[0] = sizeof(drive->info)>>8; + drive->data[1] = sizeof(drive->info); + memmove(drive->data+8, drive->info, sizeof(drive->info)); + drive->data += 8+sizeof(drive->info); + + return SDok; +} + +static void +atanop(Drive* drive, int subcommand) +{ + Ctlr* ctlr; + int as, cmdport, ctlport, timeo; + + /* + * Attempt to abort a command by using NOP. + * In response, the drive is supposed to set Abrt + * in the Error register, set (Drdy|Err) in Status + * and clear Bsy when done. However, some drives + * (e.g. ATAPI Zip) just go Bsy then clear Status + * when done, hence the timeout loop only on Bsy + * and the forced setting of drive->error. + */ + ctlr = drive->ctlr; + cmdport = ctlr->cmdport; + outb(cmdport+Features, subcommand); + outb(cmdport+Dh, drive->dev); + outb(cmdport+Command, 0); + + microdelay(1); + ctlport = ctlr->ctlport; + for(timeo = 0; timeo < 1000; timeo++){ + as = inb(ctlport+As); + if(!(as & Bsy)) + break; + microdelay(1); + } + drive->error |= Abrt; +} + +static void +ataabort(Drive* drive) +{ + /* + * If NOP is available (packet commands) use it otherwise + * must try a software reset. + */ + if(atacsfenabled(drive, 0x0000000000004000LL)) + atanop(drive, 0); + else{ + atasrst(drive->ctlr->ctlport); + drive->error |= Abrt; + } +} + +static int +atadmasetup(Drive* drive, int len) +{ + Prd *prd; + ulong pa; + Ctlr *ctlr; + int bmiba, bmisx, count; + + pa = PCIWADDR(drive->data); + if(pa & 0x03) + return -1; + ctlr = drive->ctlr; + prd = ctlr->prdt; + + /* + * Sometimes drives identify themselves as being DMA capable + * although they are not on a busmastering controller. + */ + if(prd == nil){ + drive->dma = 0; + return -1; + } + + for(;;){ + prd->pa = pa; + count = 64*1024 - (pa & 0xFFFF); + if(count >= len){ + prd->count = PrdEOT|(len & 0xFFFF); + break; + } + prd->count = count; + len -= count; + pa += count; + prd++; + } + + bmiba = ctlr->bmiba; + outl(bmiba+Bmidtpx, PCIWADDR(ctlr->prdt)); + if(drive->write) + outb(ctlr->bmiba+Bmicx, 0); + else + outb(ctlr->bmiba+Bmicx, Rwcon); + bmisx = inb(bmiba+Bmisx); + outb(bmiba+Bmisx, bmisx|Ideints|Idedmae); + + return 0; +} + +static void +atadmastart(Ctlr* ctlr, int write) +{ + if(write) + outb(ctlr->bmiba+Bmicx, Ssbm); + else + outb(ctlr->bmiba+Bmicx, Rwcon|Ssbm); +} + +static void +atadmastop(Ctlr* ctlr) +{ + int bmiba; + + bmiba = ctlr->bmiba; + outb(bmiba+Bmicx, inb(bmiba+Bmicx) & ~Ssbm); + outb(bmiba+Bmisx, inb(bmiba+Bmisx)|Ideints|Idedmae); +} + +static void +atadmainterrupt(Drive* drive, int count) +{ + Ctlr* ctlr; + int bmiba, bmisx; + + ctlr = drive->ctlr; + bmiba = ctlr->bmiba; + bmisx = inb(bmiba+Bmisx); + switch(bmisx & (Ideints|Idedmae|Bmidea)){ + case Bmidea: + /* + * Data transfer still in progress, nothing to do + * (this should never happen). + */ + return; + + case Ideints: + case Ideints|Bmidea: + /* + * Normal termination, tidy up. + */ + drive->data += count; + break; + + default: + /* + * What's left are error conditions (memory transfer + * problem) and the device is not done but the PRD is + * exhausted. For both cases must somehow tell the + * drive to abort. + */ + ataabort(drive); + break; + } + atadmastop(ctlr); + ctlr->done = 1; +} + +static int +atapktiodone(void* arg) +{ + return ((Ctlr*)arg)->done; +} + +static void +atapktinterrupt(Drive* drive) +{ + Ctlr* ctlr; + int cmdport, len; + + ctlr = drive->ctlr; + cmdport = ctlr->cmdport; + switch(inb(cmdport+Ir) & (/*Rel|*/Io|Cd)){ + case Cd: + outss(cmdport+Data, drive->pktcmd, drive->pkt/2); + break; + + case 0: + len = (inb(cmdport+Bytehi)<<8)|inb(cmdport+Bytelo); + if(drive->data+len > drive->limit){ + atanop(drive, 0); + break; + } + outss(cmdport+Data, drive->data, len/2); + drive->data += len; + break; + + case Io: + len = (inb(cmdport+Bytehi)<<8)|inb(cmdport+Bytelo); + if(drive->data+len > drive->limit){ + atanop(drive, 0); + break; + } + inss(cmdport+Data, drive->data, len/2); + drive->data += len; + break; + + case Io|Cd: + if(drive->pktdma) + atadmainterrupt(drive, drive->dlen); + else + ctlr->done = 1; + break; + } +} + +static int +atapktio(Drive* drive, uchar* cmd, int clen) +{ + Ctlr *ctlr; + int as, cmdport, ctlport, len, r, timeo; + + if(cmd[0] == 0x5A && (cmd[2] & 0x3F) == 0) + return atamodesense(drive, cmd); + + r = SDok; + + drive->command = Cpkt; + memmove(drive->pktcmd, cmd, clen); + memset(drive->pktcmd+clen, 0, drive->pkt-clen); + drive->limit = drive->data+drive->dlen; + + ctlr = drive->ctlr; + cmdport = ctlr->cmdport; + ctlport = ctlr->ctlport; + + qlock(ctlr); + + if(ataready(cmdport, ctlport, drive->dev, Bsy|Drq, 0, 100*1000) < 0){ + qunlock(ctlr); + return -1; + } + + ilock(ctlr); + if(drive->dlen && drive->dma && !atadmasetup(drive, drive->dlen)) + drive->pktdma = Dma; + else + drive->pktdma = 0; + + outb(cmdport+Features, drive->pktdma); + outb(cmdport+Count, 0); + outb(cmdport+Sector, 0); + len = 16*drive->secsize; + outb(cmdport+Bytelo, len); + outb(cmdport+Bytehi, len>>8); + outb(cmdport+Dh, drive->dev); + ctlr->done = 0; + if(drive->pktdma) + atadmastart(ctlr, drive->write); + outb(cmdport+Command, Cpkt); + + if((drive->info[Iconfig] & 0x0060) != 0x0020){ + as = ataready(cmdport, ctlport, + drive->dev, Bsy, Drq|Chk, 4*1000); + if(as < 0) + r = SDtimeout; + else if(as & Chk) + r = SDcheck; + else + atapktinterrupt(drive); + } + ctlr->curdrive = drive; + iunlock(ctlr); + + while(waserror()) + ; + if(!drive->pktdma) + sleep(ctlr, atapktiodone, ctlr); + else for(timeo = 0; !ctlr->done; timeo++){ + tsleep(ctlr, atapktiodone, ctlr, 1000); + if(ctlr->done) + break; + ilock(ctlr); + atadmainterrupt(drive, 0); + if(!drive->error && timeo > 10){ + ataabort(drive); + atadmastop(ctlr); + drive->dma = 0; + drive->error |= Abrt; + } + if(drive->error){ + drive->status |= Chk; + ctlr->curdrive = nil; + } + iunlock(ctlr); + } + poperror(); + + qunlock(ctlr); + + if(drive->status & Chk) + r = SDcheck; + + return r; +} + +static int +atageniodone(void* arg) +{ + return ((Ctlr*)arg)->done; +} + +static int +atageniostart(Drive* drive, int lba) +{ + Ctlr *ctlr; + int as, c, cmdport, ctlport, h, len, s; + + if(drive->dev & Lba){ + c = (lba>>8) & 0xFFFF; + h = (lba>>24) & 0x0F; + s = lba & 0xFF; + } + else{ + c = lba/(drive->s*drive->h); + h = ((lba/drive->s) % drive->h); + s = (lba % drive->s) + 1; + } + + ctlr = drive->ctlr; + cmdport = ctlr->cmdport; + ctlport = ctlr->ctlport; + if(ataready(cmdport, ctlport, drive->dev, Bsy|Drq, 0, 100*1000) < 0) + return -1; + + ilock(ctlr); + if(drive->dma && !atadmasetup(drive, drive->count*drive->secsize)){ + if(drive->write) + drive->command = Cwd; + else + drive->command = Crd; + } + else if(drive->write) + drive->command = drive->piow; + else + drive->command = drive->pior; + + outb(cmdport+Count, drive->count); + outb(cmdport+Sector, s); + outb(cmdport+Dh, drive->dev|h); + outb(cmdport+Cyllo, c); + outb(cmdport+Cylhi, c>>8); + ctlr->done = 0; + outb(cmdport+Command, drive->command); + microdelay(1); + + switch(drive->command){ + case Cws: + case Cwsm: + as = ataready(cmdport, ctlport, drive->dev, Bsy, Drq|Err, 1000); + if(as < 0 || (as & Err)){ + iunlock(ctlr); + return -1; + } + len = drive->block; + if(drive->data+len > drive->limit) + len = drive->limit-drive->data; + outss(cmdport+Data, drive->data, len/2); + break; + + case Crd: + case Cwd: + atadmastart(ctlr, drive->write); + break; + } + ctlr->curdrive = drive; + iunlock(ctlr); + + return 0; +} + +static int +atagenio(Drive* drive, uchar* cmd, int) +{ + uchar *p; + Ctlr *ctlr; + int count, lba, len; + + /* + * Map SCSI commands into ATA commands for discs. + * Fail any command with a LUN except INQUIRY which + * will return 'logical unit not supported'. + */ + if((cmd[1]>>5) && cmd[0] != 0x12) + return atasetsense(drive, SDcheck, 0x05, 0x25, 0); + + switch(cmd[0]){ + default: + return atasetsense(drive, SDcheck, 0x05, 0x20, 0); + + case 0x00: /* test unit ready */ + return SDok; + + case 0x03: /* request sense */ + if(cmd[4] < sizeof(drive->sense)) + len = cmd[4]; + else + len = sizeof(drive->sense); + if(drive->data && drive->dlen >= len){ + memmove(drive->data, drive->sense, len); + drive->data += len; + } + return SDok; + + case 0x12: /* inquiry */ + if(cmd[4] < sizeof(drive->inquiry)) + len = cmd[4]; + else + len = sizeof(drive->inquiry); + if(drive->data && drive->dlen >= len){ + memmove(drive->data, drive->inquiry, len); + drive->data += len; + } + return SDok; + + case 0x1B: /* start/stop unit */ + /* + * NOP for now, can use the power management feature + * set later. + */ + return SDok; + + case 0x25: /* read capacity */ + if((cmd[1] & 0x01) || cmd[2] || cmd[3]) + return atasetsense(drive, SDcheck, 0x05, 0x24, 0); + if(drive->data == nil || drive->dlen < 8) + return atasetsense(drive, SDcheck, 0x05, 0x20, 1); + /* + * Read capacity returns the LBA of the last sector. + */ + len = drive->sectors-1; + p = drive->data; + *p++ = len>>24; + *p++ = len>>16; + *p++ = len>>8; + *p++ = len; + len = drive->secsize; + *p++ = len>>24; + *p++ = len>>16; + *p++ = len>>8; + *p = len; + drive->data += 8; + return SDok; + + case 0x28: /* read */ + case 0x2A: /* write */ + break; + + case 0x5A: + return atamodesense(drive, cmd); + } + + ctlr = drive->ctlr; + lba = (cmd[2]<<24)|(cmd[3]<<16)|(cmd[4]<<8)|cmd[5]; + count = (cmd[7]<<8)|cmd[8]; + if(drive->data == nil) + return SDok; + if(drive->dlen < count*drive->secsize) + count = drive->dlen/drive->secsize; + qlock(ctlr); + while(count){ + if(count > 256) + drive->count = 256; + else + drive->count = count; + drive->limit += drive->count*drive->secsize; + if(atageniostart(drive, lba)){ + qunlock(ctlr); + return atasetsense(drive, SDcheck, 2, 5, 0); + } + lba += drive->count; + + while(waserror()) + ; + tsleep(ctlr, atageniodone, ctlr, 10*1000); + poperror(); + if(!ctlr->done){ + /* + * What should the above timeout be? In + * standby and sleep modes it could take as + * long as 30 seconds for a drive to respond. + * Very hard to get out of this cleanly. + * Let's see if it ever happens first... + */ + panic("atagenio"); + } + + if(drive->status & Err){ + qunlock(ctlr); + return atasetsense(drive, SDcheck, 4, 8, drive->error); + } + if(drive->data != drive->limit) + print("%s: atagenio: %p != %p\n", + ctlr->sdev->name, + drive->data, drive->limit); + count -= drive->count; + } + qunlock(ctlr); + + return SDok; +} + +static int +atario(SDreq* r) +{ + Ctlr *ctlr; + Drive *drive; + SDunit *unit; + uchar cmd10[10], *cmdp, *p; + int clen, reqstatus, status; + + unit = r->unit; + if((ctlr = unit->dev->ctlr) == nil || ctlr->drive[unit->subno] == nil){ + r->status = SDtimeout; + return SDtimeout; + } + drive = ctlr->drive[unit->subno]; + + /* + * Most SCSI commands can be passed unchanged except for + * the padding on the end. The few which require munging + * are not used internally. Mode select/sense(6) could be + * converted to the 10-byte form but it's not worth the + * effort. Read/write(6) are easy. + */ + switch(r->cmd[0]){ + case 0x08: /* read */ + case 0x0A: /* write */ + cmdp = cmd10; + memset(cmdp, 0, sizeof(cmd10)); + cmdp[0] = r->cmd[0]|0x20; + cmdp[1] = r->cmd[1] & 0xE0; + cmdp[5] = r->cmd[3]; + cmdp[4] = r->cmd[2]; + cmdp[3] = r->cmd[1] & 0x0F; + cmdp[8] = r->cmd[4]; + clen = sizeof(cmd10); + break; + + default: + cmdp = r->cmd; + clen = r->clen; + break; + } + + qlock(drive); + drive->write = r->write; + drive->data = r->data; + drive->dlen = r->dlen; + drive->limit = r->data; + + drive->status = 0; + drive->error = 0; + if(drive->pkt) + status = atapktio(drive, cmdp, clen); + else + status = atagenio(drive, cmdp, clen); + if(status == SDok){ + atasetsense(drive, SDok, 0, 0, 0); + if(drive->data){ + p = r->data; + r->rlen = drive->data - p; + } + else + r->rlen = 0; + } + else if(status == SDcheck && !(r->flags & SDnosense)){ + drive->write = 0; + memset(cmd10, 0, sizeof(cmd10)); + cmd10[0] = 0x03; + cmd10[1] = r->lun<<5; + cmd10[4] = sizeof(r->sense)-1; + drive->data = r->sense; + drive->dlen = sizeof(r->sense)-1; + drive->limit = r->sense; + drive->status = 0; + drive->error = 0; + if(drive->pkt) + reqstatus = atapktio(drive, cmd10, 6); + else + reqstatus = atagenio(drive, cmd10, 6); + if(reqstatus == SDok){ + r->flags |= SDvalidsense; + atasetsense(drive, SDok, 0, 0, 0); + } + } + qunlock(drive); + r->status = status; + if(status != SDok) + return status; + + /* + * Fix up any results. + * Many ATAPI CD-ROMs ignore the LUN field completely and + * return valid INQUIRY data. Patch the response to indicate + * 'logical unit not supported' if the LUN is non-zero. + */ + switch(cmdp[0]){ + case 0x12: /* inquiry */ + if((p = r->data) == nil) + break; + if((cmdp[1]>>5) && (!drive->pkt || (p[0] & 0x1F) == 0x05)) + p[0] = 0x7F; + /*FALLTHROUGH*/ + default: + break; + } + + return SDok; +} + +static void +atainterrupt(Ureg*, void* arg) +{ + Ctlr *ctlr; + Drive *drive; + int cmdport, len, status; + + ctlr = arg; + + ilock(ctlr); + if(inb(ctlr->ctlport+As) & Bsy){ + iunlock(ctlr); + return; + } + cmdport = ctlr->cmdport; + status = inb(cmdport+Status); + if((drive = ctlr->curdrive) == nil){ + iunlock(ctlr); + return; + } + + if(status & Err) + drive->error = inb(cmdport+Error); + else switch(drive->command){ + default: + drive->error = Abrt; + break; + + case Crs: + case Crsm: + if(!(status & Drq)){ + drive->error = Abrt; + break; + } + len = drive->block; + if(drive->data+len > drive->limit) + len = drive->limit-drive->data; + inss(cmdport+Data, drive->data, len/2); + drive->data += len; + if(drive->data >= drive->limit) + ctlr->done = 1; + break; + + case Cws: + case Cwsm: + len = drive->block; + if(drive->data+len > drive->limit) + len = drive->limit-drive->data; + drive->data += len; + if(drive->data >= drive->limit){ + ctlr->done = 1; + break; + } + if(!(status & Drq)){ + drive->error = Abrt; + break; + } + len = drive->block; + if(drive->data+len > drive->limit) + len = drive->limit-drive->data; + outss(cmdport+Data, drive->data, len/2); + break; + + case Cpkt: + atapktinterrupt(drive); + break; + + case Crd: + case Cwd: + atadmainterrupt(drive, drive->count*drive->secsize); + break; + } + iunlock(ctlr); + + if(drive->error){ + status |= Err; + ctlr->done = 1; + } + + if(ctlr->done){ + ctlr->curdrive = nil; + drive->status = status; + wakeup(ctlr); + } +} + +static SDev* +atapnp(void) +{ + Ctlr *ctlr; + Pcidev *p; + int channel, ispc87415, pi; + SDev *legacy[2], *sdev, *head, *tail; + + legacy[0] = legacy[1] = head = tail = nil; + if(sdev = ataprobe(0x1F0, 0x3F4, IrqATA0)){ + head = tail = sdev; + legacy[0] = sdev; + } + if(sdev = ataprobe(0x170, 0x374, IrqATA1)){ + if(head != nil) + tail->next = sdev; + else + head = sdev; + tail = sdev; + legacy[1] = sdev; + } + + p = nil; + while(p = pcimatch(p, 0, 0)){ + /* + * Look for devices with the correct class and sub-class + * code and known device and vendor ID; add native-mode + * channels to the list to be probed, save info for the + * compatibility mode channels. + * Note that the legacy devices should not be considered + * PCI devices by the interrupt controller. + * For both native and legacy, save info for busmastering + * if capable. + * Promise Ultra ATA/66 (PDC20262) appears to + * 1) give a sub-class of 'other mass storage controller' + * instead of 'IDE controller', regardless of whether it's + * the only controller or not; + * 2) put 0 in the programming interface byte (probably + * as a consequence of 1) above). + */ + if(p->ccrb != 0x01 || (p->ccru != 0x01 && p->ccru != 0x80)) + continue; + pi = p->ccrp; + ispc87415 = 0; + + switch((p->did<<16)|p->vid){ + default: + continue; + + case (0x0002<<16)|0x100B: /* NS PC87415 */ + /* + * Disable interrupts on both channels until + * after they are probed for drives. + * This must be called before interrupts are + * enabled because the IRQ may be shared. + */ + ispc87415 = 1; + pcicfgw32(p, 0x40, 0x00000300); + break; + + case (0x4D38<<16)|0x105A: /* Promise PDC20262 */ + pi = 0x85; + break; + case (0x1230<<16)|0x8086: /* 82371FB (PIIX) */ + case (0x7010<<16)|0x8086: /* 82371SB (PIIX3) */ + case (0x7111<<16)|0x8086: /* 82371[AE]B (PIIX4[E]) */ + case (0x0646<<16)|0x1095: /* CMD 646 */ + break; + case (0x0571<<16)|0x1106: /* VIA 82C686 */ + break; + } + + for(channel = 0; channel < 2; channel++){ + if(pi & (1<<(2*channel))){ + sdev = ataprobe(p->mem[0+2*channel].bar & ~0x01, + p->mem[1+2*channel].bar & ~0x01, + p->intl); + if(sdev == nil) + continue; + + ctlr = sdev->ctlr; + if(ispc87415) + ctlr->ienable = pc87415ienable; + + if(head != nil) + tail->next = sdev; + else + head = sdev; + tail = sdev; + ctlr->pcidev = p; + } + else if((sdev = legacy[channel]) == nil) + continue; + else + ctlr = sdev->ctlr; + + if(!(pi & 0x80)) + continue; + ctlr->bmiba = (p->mem[4].bar & ~0x01) + channel*8; + } + } + + return head; +} + +static SDev* +atalegacy(int port, int irq) +{ + return ataprobe(port, port+0x204, irq); +} + +static SDev* +ataid(SDev* sdev) +{ + int i; + Ctlr *ctlr; + + /* + * Legacy controllers are always 'C' and 'D' and if + * they exist and have drives will be first in the list. + * If there are no active legacy controllers, native + * controllers start at 'C'. + */ + if(sdev == nil) + return nil; + ctlr = sdev->ctlr; + if(ctlr->cmdport == 0x1F0 || ctlr->cmdport == 0x170) + i = 2; + else + i = 0; + while(sdev){ + if(sdev->ifc == &sdataifc){ + ctlr = sdev->ctlr; + if(ctlr->cmdport == 0x1F0) + sdev->idno = 'C'; + else if(ctlr->cmdport == 0x170) + sdev->idno = 'D'; + else{ + sdev->idno = 'C'+i; + i++; + } + snprint(sdev->name, NAMELEN, "sd%c", sdev->idno); + } + sdev = sdev->next; + } + + return nil; +} + +static int +ataenable(SDev* sdev) +{ + int tbdf; + Ctlr *ctlr; + char name[NAMELEN]; + + ctlr = sdev->ctlr; + + if(ctlr->bmiba){ + if(ctlr->pcidev != nil) + pcisetbme(ctlr->pcidev); + ctlr->prdt = xspanalloc(Nprd*sizeof(Prd), 4, 4*1024); + } + if(ctlr->pcidev) + tbdf = ctlr->pcidev->tbdf; + else + tbdf = BUSUNKNOWN; + snprint(name, NAMELEN, "%s (%s)", sdev->name, sdev->ifc->name); + intrenable(ctlr->irq, atainterrupt, ctlr, tbdf, name); + outb(ctlr->ctlport+Dc, 0); + if(ctlr->ienable) + ctlr->ienable(ctlr); + + return 1; +} + +static int +atarctl(SDunit* unit, char* p, int l) +{ + int n; + Ctlr *ctlr; + Drive *drive; + + if((ctlr = unit->dev->ctlr) == nil || ctlr->drive[unit->subno] == nil) + return 0; + drive = ctlr->drive[unit->subno]; + + n = snprint(p, l, "geometry %ld %ld", unit->sectors, unit->secsize); + if(drive->pkt == 0) + n += snprint(p+n, l-n, " %d %d %d", + drive->c, drive->h, drive->s); + n += snprint(p+n, l-n, "\n"); + + return n; +} + +SDifc sdataifc = { + "ata", /* name */ + + atapnp, /* pnp */ + atalegacy, /* legacy */ + ataid, /* id */ + ataenable, /* enable */ + nil, /* disable */ + + scsiverify, /* verify */ + scsionline, /* online */ + atario, /* rio */ + atarctl, /* rctl */ + nil, /* wctl */ + + scsibio, /* bio */ +}; diff --git a/pc/sdmylex.c b/pc/sdmylex.c new file mode 100644 index 0000000000000000000000000000000000000000..e6a576c89dd06f782edbfc2aff91b68fdb8b21a8 --- /dev/null +++ b/pc/sdmylex.c @@ -0,0 +1,1222 @@ +/* + * Mylex MultiMaster (Buslogic BT-*) SCSI Host Adapter + * in both 24-bit and 32-bit mode. + * 24-bit mode works for Adaptec AHA-154xx series too. + * + * To do: + * tidy the PCI probe and do EISA; + * allocate more Ccb's as needed, up to NMbox-1; + * add nmbox and nccb to Ctlr struct for the above; + * 64-bit LUN/explicit wide support necessary? + * + */ +#include "u.h" +#include "../port/lib.h" +#include "mem.h" +#include "dat.h" +#include "fns.h" +#include "io.h" +#include "ureg.h" +#include "../port/error.h" + +#include "sd.h" + +#define K2BPA(va, tbdf) PADDR(va) +#define BPA2K(pa, tbdf) KADDR(pa) + +extern SDifc sdmylexifc; + +enum { /* registers */ + Rcontrol = 0x00, /* WO: control register */ + Rstatus = 0x00, /* RO: status register */ + Rcpr = 0x01, /* WO: command/parameter register */ + Rdatain = 0x01, /* RO: data-in register */ + Rinterrupt = 0x02, /* RO: interrupt register */ +}; + +enum { /* Rcontrol */ + Rsbus = 0x10, /* SCSI Bus Reset */ + Rint = 0x20, /* Interrupt Reset */ + Rsoft = 0x40, /* Soft Reset */ + Rhard = 0x80, /* Hard Reset */ +}; + +enum { /* Rstatus */ + Cmdinv = 0x01, /* Command Invalid */ + Dirrdy = 0x04, /* Data In Register Ready */ + Cprbsy = 0x08, /* Command/Parameter Register Busy */ + Hardy = 0x10, /* Host Adapter Ready */ + Inreq = 0x20, /* Initialisation Required */ + Dfail = 0x40, /* Diagnostic Failure */ + Dact = 0x80, /* Diagnostic Active */ +}; + +enum { /* Rcpr */ + Cinitialise = 0x01, /* Initialise Mailbox */ + Cstart = 0x02, /* Start Mailbox Command */ + Cinquiry = 0x04, /* Adapter Anquiry */ + Ceombri = 0x05, /* Enable OMBR Interrupt */ + Cinquire = 0x0B, /* Inquire Configuration */ + Cextbios = 0x28, /* AHA-1542: extended BIOS info. */ + Cmbienable = 0x29, /* AHA-1542: Mailbox interface enable */ + Ciem = 0x81, /* Initialise Extended Mailbox */ + Ciesi = 0x8D, /* Inquire Extended Setup Information */ + Cerrm = 0x8F, /* Enable strict round-robin mode */ + Cwide = 0x96, /* Wide CCB */ +}; + +enum { /* Rinterrupt */ + Imbl = 0x01, /* Incoming Mailbox Loaded */ + Mbor = 0x02, /* Mailbox Out Ready */ + Cmdc = 0x04, /* Command Complete */ + Rsts = 0x08, /* SCSI Reset State */ + Intv = 0x80, /* Interrupt Valid */ +}; + +typedef struct { + uchar code; /* action/completion code */ + uchar ccb[3]; /* CCB pointer (MSB, ..., LSB) */ +} Mbox24; + +typedef struct { + uchar ccb[4]; /* CCB pointer (LSB, ..., MSB) */ + uchar btstat; /* BT-7[45]7[SD] status */ + uchar sdstat; /* SCSI device status */ + uchar pad; + uchar code; /* action/completion code */ +} Mbox32; + +enum { /* mailbox commands */ + Mbfree = 0x00, /* Mailbox not in use */ + + Mbostart = 0x01, /* Start a mailbox command */ + Mboabort = 0x02, /* Abort a mailbox command */ + + Mbiok = 0x01, /* CCB completed without error */ + Mbiabort = 0x02, /* CCB aborted at request of host */ + Mbinx = 0x03, /* Aborted CCB not found */ + Mbierror = 0x04, /* CCB completed with error */ +}; + +typedef struct Ccb24 Ccb24; +typedef struct Ccb32 Ccb32; +typedef union Ccb Ccb; + +typedef struct Ccb24 { + uchar opcode; /* Operation code */ + uchar datadir; /* Data direction control */ + uchar cdblen; /* Length of CDB */ + uchar senselen; /* Length of sense area */ + uchar datalen[3]; /* Data length (MSB, ..., LSB) */ + uchar dataptr[3]; /* Data pointer (MSB, ..., LSB) */ + uchar linkptr[3]; /* Link pointer (MSB, ..., LSB) */ + uchar linkid; /* command linking identifier */ + uchar btstat; /* BT-* adapter status */ + uchar sdstat; /* SCSI device status */ + uchar reserved[2]; /* */ + uchar cs[12+0xFF]; /* Command descriptor block + Sense */ + + void* data; /* buffer if address > 24-bits */ + + Rendez; + int done; /* command completed */ + + Ccb* ccb; /* link on free list */ +} Ccb24; + + +typedef struct Ccb32 { + uchar opcode; /* Operation code */ + uchar datadir; /* Data direction control */ + uchar cdblen; /* Length of CDB */ + uchar senselen; /* Length of sense area */ + uchar datalen[4]; /* Data length (LSB, ..., MSB) */ + uchar dataptr[4]; /* Data pointer (LSB, ..., MSB) */ + uchar reserved[2]; + uchar btstat; /* BT-* adapter status */ + uchar sdstat; /* SCSI device status */ + uchar targetid; /* Target ID */ + uchar luntag; /* LUN & tag */ + uchar cdb[12]; /* Command descriptor block */ + uchar ccbctl; /* CCB control */ + uchar linkid; /* command linking identifier */ + uchar linkptr[4]; /* Link pointer (LSB, ..., MSB) */ + uchar senseptr[4]; /* Sense pointer (LSB, ..., MSB) */ + uchar sense[0xFF]; /* Sense bytes */ + + Rendez; + int done; /* command completed */ + + Ccb* ccb; /* link on free list */ +} Ccb32; + +typedef union Ccb { + Ccb24; + Ccb32; +} Ccb; + +enum { /* opcode */ + OInitiator = 0x00, /* initiator CCB */ + Ordl = 0x03, /* initiator CCB with + * residual data length returned + */ +}; + +enum { /* datadir */ + CCBdatain = 0x08, /* inbound, length is checked */ + CCBdataout = 0x10, /* outbound, length is checked */ +}; + +enum { /* btstat */ + Eok = 0x00, /* normal completion with no errors */ +}; + +enum { /* luntag */ + TagEnable = 0x20, /* Tag enable */ + SQTag = 0x00, /* Simple Queue Tag */ + HQTag = 0x40, /* Head of Queue Tag */ + OQTag = 0x80, /* Ordered Queue Tag */ +}; + +enum { /* CCB control */ + NoDisc = 0x08, /* No disconnect */ + NoUnd = 0x10, /* No underrrun error report */ + NoData = 0x20, /* No data transfer */ + NoStat = 0x40, /* No CCB status if zero */ + NoIntr = 0x80, /* No Interrupts */ +}; + +typedef struct { + int port; /* I/O port */ + int id; /* adapter SCSI id */ + int bus; /* 24 or 32 -bit */ + int irq; + int wide; + Pcidev* pcidev; + SDev* sdev; + int spurious; + + Lock issuelock; + + Lock ccblock; + QLock ccbq; + Rendez ccbr; + + Lock mboxlock; + void* mb; /* mailbox out + mailbox in */ + int mbox; /* current mailbox out index into mb */ + int mbix; /* current mailbox in index into mb */ + + Lock cachelock; + Ccb* ccb; /* list of free Ccb's */ + Ccb** cache; /* last completed Ccb */ +} Ctlr; + +/* + * The number of mailboxes should be a multiple of 8 (4 for Mbox32) + * to ensure the boundary between the out and in mailboxes doesn't + * straddle a cache-line boundary. + * The number of Ccb's should be less than the number of mailboxes to + * ensure no queueing is necessary on mailbox allocation. + */ +enum { + NMbox = 8*8, /* number of Mbox's */ + NCcb = NMbox-1, /* number of Ccb's */ +}; + +#define PADDR24(a, n) (PADDR(a)+n <= (1<<24)) + +static void +ccbfree(Ctlr* ctlr, Ccb* ccb) +{ + lock(&ctlr->ccblock); + if(ctlr->bus == 24) + ((Ccb24*)ccb)->ccb = ctlr->ccb; + else + ((Ccb32*)ccb)->ccb = ctlr->ccb; + if(ctlr->ccb == nil) + wakeup(&ctlr->ccbr); + ctlr->ccb = ccb; + unlock(&ctlr->ccblock); +} + +static int +ccbavailable(void* a) +{ + return ((Ctlr*)a)->ccb != nil; +} + +static Ccb* +ccballoc(Ctlr* ctlr) +{ + Ccb *ccb; + + for(;;){ + lock(&ctlr->ccblock); + if(ccb = ctlr->ccb){ + if(ctlr->bus == 24) + ctlr->ccb = ((Ccb24*)ccb)->ccb; + else + ctlr->ccb = ((Ccb32*)ccb)->ccb; + unlock(&ctlr->ccblock); + break; + } + + unlock(&ctlr->ccblock); + qlock(&ctlr->ccbq); + if(waserror()){ + qunlock(&ctlr->ccbq); + continue; + } + sleep(&ctlr->ccbr, ccbavailable, ctlr); + qunlock(&ctlr->ccbq); + poperror(); + } + + return ccb; +} + +static int +done24(void* arg) +{ + return ((Ccb24*)arg)->done; +} + +static int +mylex24rio(SDreq* r) +{ + ulong p; + Ctlr *ctlr; + Ccb24 *ccb; + Mbox32 *mb; + uchar *data, lun, *sense; + int d, n, btstat, sdstat, target; + + ctlr = r->unit->dev->ctlr; + target = r->unit->subno; + lun = (r->cmd[1]>>5) & 0x07; + + /* + * Ctlr->cache holds the last completed Ccb for this target if it + * returned 'check condition'. + * If this command is a request-sense and there is valid sense data + * from the last completed Ccb, return it immediately. + */ + lock(&ctlr->cachelock); + if(ccb = ctlr->cache[target]){ + ctlr->cache[target] = nil; + if(r->cmd[0] == 0x03 + && ccb->sdstat == SDcheck && lun == ((ccb->cs[1]>>5) & 0x07)){ + unlock(&ctlr->cachelock); + if(r->dlen){ + sense = &ccb->cs[ccb->cdblen]; + n = 8+sense[7]; + if(n > r->dlen) + n = r->dlen; + memmove(r->data, sense, n); + r->rlen = n; + } + ccbfree(ctlr, (Ccb*)ccb); + return SDok; + } + } + unlock(&ctlr->cachelock); + if(ccb == nil) + ccb = ccballoc(ctlr); + + /* + * Check if the transfer is to memory above the 24-bit limit the + * controller can address. If it is, try to allocate a temporary + * buffer as a staging area. + */ + n = r->dlen; + if(n && !PADDR24(r->data, n)){ + ccb->data = r->data; + data = mallocz(n, 0); + if(data == nil || !PADDR24(data, n)){ + if(data != nil){ + free(data); + ccb->data = nil; + } + ccbfree(ctlr, (Ccb*)ccb); + return SDmalloc; + } + if(r->write) + memmove(data, ccb->data, n); + } + else + data = r->data; + + /* + * Fill in the ccb. + */ + ccb->opcode = Ordl; + + ccb->datadir = (target<<5)|lun; + if(n == 0) + ccb->datadir |= CCBdataout|CCBdatain; + else if(!r->write) + ccb->datadir |= CCBdatain; + else + ccb->datadir |= CCBdataout; + + ccb->cdblen = r->clen; + ccb->senselen = 0xFF; + + ccb->datalen[0] = n>>16; + ccb->datalen[1] = n>>8; + ccb->datalen[2] = n; + p = PADDR(data); + ccb->dataptr[0] = p>>16; + ccb->dataptr[1] = p>>8; + ccb->dataptr[2] = p; + + ccb->linkptr[0] = ccb->linkptr[1] = ccb->linkptr[2] = 0; + ccb->linkid = 0; + ccb->btstat = ccb->sdstat = 0; + ccb->reserved[0] = ccb->reserved[1] = 0; + + memmove(ccb->cs, r->cmd, r->clen); + + /* + * There's one more mbox than there there is + * ccb so there is always one free. + */ + lock(&ctlr->mboxlock); + mb = ctlr->mb; + mb += ctlr->mbox; + p = PADDR(ccb); + mb->ccb[0] = p>>16; + mb->ccb[1] = p>>8; + mb->ccb[2] = p; + mb->code = Mbostart; + ctlr->mbox++; + if(ctlr->mbox >= NMbox) + ctlr->mbox = 0; + + /* + * This command does not require Hardy + * and doesn't generate a Cmdc interrupt. + */ + ccb->done = 0; + outb(ctlr->port+Rcpr, Cstart); + unlock(&ctlr->mboxlock); + + /* + * Wait for the request to complete and return the status. + * Since the buffer is not reference counted cannot return + * until the DMA is done writing into the buffer so the caller + * cannot free the buffer prematurely. + */ + while(waserror()) + ; + sleep(ccb, done24, ccb); + poperror(); + + /* + * Save the status and patch up the number of + * bytes actually transferred. + * There's a firmware bug on some 956C controllers + * which causes the return count from a successful + * READ CAPACITY not be updated, so fix it here. + */ + sdstat = ccb->sdstat; + btstat = ccb->btstat; + + d = ccb->datalen[0]<<16; + d |= ccb->datalen[1]<<8; + d |= ccb->datalen[2]; + if(ccb->cs[0] == 0x25 && sdstat == SDok) + d = 0; + n -= d; + r->rlen = n; + + /* + * Tidy things up if a staging area was used for the data, + */ + if(ccb->data){ + if(sdstat == SDok && btstat == 0 && !r->write) + memmove(ccb->data, data, n); + free(data); + ccb->data = nil; + } + + /* + * If there was a check-condition, save the + * ccb for a possible request-sense command. + */ + if(sdstat == SDcheck){ + if(r->flags & SDnosense){ + lock(&ctlr->cachelock); + if(ctlr->cache[target]) + ccbfree(ctlr, ctlr->cache[target]); + ctlr->cache[target] = (Ccb*)ccb; + unlock(&ctlr->cachelock); + return SDcheck; + } + sense = &ccb->cs[ccb->cdblen]; + n = 8+sense[7]; + if(n > sizeof(r->sense)-1) + n = sizeof(r->sense)-1; + memmove(r->sense, sense, n); + r->flags |= SDvalidsense; + } + ccbfree(ctlr, (Ccb*)ccb); + + if(btstat){ + if(btstat == 0x11) + return SDtimeout; + return SDeio; + } + return sdstat; +} + +static void +mylex24interrupt(Ureg*, void* arg) +{ + ulong pa; + Ctlr *ctlr; + Ccb24 *ccb; + Mbox24 *mb, *mbox; + int port, rinterrupt, rstatus; + + ctlr = arg; + port = ctlr->port; + + /* + * Save and clear the interrupt(s). The only + * interrupts expected are Cmdc, which is ignored, + * and Imbl which means something completed. + * There's one spurious interrupt left over from + * initialisation, ignore it. + */ + rinterrupt = inb(port+Rinterrupt); + rstatus = inb(port+Rstatus); + outb(port+Rcontrol, Rint); + if((rinterrupt & ~(Cmdc|Imbl)) != Intv && ctlr->spurious++) + print("%s: interrupt 0x%2.2ux\n", + ctlr->sdev->name, rinterrupt); + if((rinterrupt & Cmdc) && (rstatus & Cmdinv)) + print("%s: command invalid\n", ctlr->sdev->name); + + /* + * Look for something in the mail. + * If there is, save the status, free the mailbox + * and wakeup whoever. + */ + mb = ctlr->mb; + for(mbox = &mb[ctlr->mbix]; mbox->code; mbox = &mb[ctlr->mbix]){ + pa = (mbox->ccb[0]<<16)|(mbox->ccb[1]<<8)|mbox->ccb[2]; + ccb = BPA2K(pa, BUSUNKNOWN); + mbox->code = 0; + ccb->done = 1; + wakeup(ccb); + + ctlr->mbix++; + if(ctlr->mbix >= NMbox+NMbox) + ctlr->mbix = NMbox; + } +} + +static int +done32(void* arg) +{ + return ((Ccb32*)arg)->done; +} + +static int +mylex32rio(SDreq* r) +{ + ulong p; + uchar lun; + Ctlr *ctlr; + Ccb32 *ccb; + Mbox32 *mb; + int d, n, btstat, sdstat, target; + + ctlr = r->unit->dev->ctlr; + target = r->unit->subno; + lun = (r->cmd[1]>>5) & 0x07; + + /* + * Ctlr->cache holds the last completed Ccb for this target if it + * returned 'check condition'. + * If this command is a request-sense and there is valid sense data + * from the last completed Ccb, return it immediately. + */ + lock(&ctlr->cachelock); + if(ccb = ctlr->cache[target]){ + ctlr->cache[target] = nil; + if(r->cmd[0] == 0x03 + && ccb->sdstat == SDcheck && lun == (ccb->luntag & 0x07)){ + unlock(&ctlr->cachelock); + if(r->dlen){ + n = 8+ccb->sense[7]; + if(n > r->dlen) + n = r->dlen; + memmove(r->data, ccb->sense, n); + r->rlen = n; + } + ccbfree(ctlr, (Ccb*)ccb); + return SDok; + } + } + unlock(&ctlr->cachelock); + if(ccb == nil) + ccb = ccballoc(ctlr); + + /* + * Fill in the ccb. + */ + ccb->opcode = Ordl; + + n = r->dlen; + if(n == 0) + ccb->datadir |= CCBdataout|CCBdatain; + else if(!r->write) + ccb->datadir |= CCBdatain; + else + ccb->datadir |= CCBdataout; + + ccb->cdblen = r->clen; + + ccb->datalen[0] = n; + ccb->datalen[1] = n>>8; + ccb->datalen[2] = n>>16; + ccb->datalen[3] = n>>24; + p = PADDR(r->data); + ccb->dataptr[0] = p; + ccb->dataptr[1] = p>>8; + ccb->dataptr[2] = p>>16; + ccb->dataptr[3] = p>>24; + + ccb->targetid = target; + ccb->luntag = lun; + if(r->unit->inquiry[7] & 0x02) + ccb->luntag |= SQTag|TagEnable; + memmove(ccb->cdb, r->cmd, r->clen); + ccb->btstat = ccb->sdstat = 0; + ccb->ccbctl = 0; + + /* + * There's one more mbox than there there is + * ccb so there is always one free. + */ + lock(&ctlr->mboxlock); + mb = ctlr->mb; + mb += ctlr->mbox; + p = PADDR(ccb); + mb->ccb[0] = p; + mb->ccb[1] = p>>8; + mb->ccb[2] = p>>16; + mb->ccb[3] = p>>24; + mb->code = Mbostart; + ctlr->mbox++; + if(ctlr->mbox >= NMbox) + ctlr->mbox = 0; + + /* + * This command does not require Hardy + * and doesn't generate a Cmdc interrupt. + */ + ccb->done = 0; + outb(ctlr->port+Rcpr, Cstart); + unlock(&ctlr->mboxlock); + + /* + * Wait for the request to complete and return the status. + * Since the buffer is not reference counted cannot return + * until the DMA is done writing into the buffer so the caller + * cannot free the buffer prematurely. + */ + while(waserror()) + ; + sleep(ccb, done32, ccb); + poperror(); + + /* + * Save the status and patch up the number of + * bytes actually transferred. + * There's a firmware bug on some 956C controllers + * which causes the return count from a successful + * READ CAPACITY not to be updated, so fix it here. + */ + sdstat = ccb->sdstat; + btstat = ccb->btstat; + + d = ccb->datalen[0]; + d |= (ccb->datalen[1]<<8); + d |= (ccb->datalen[2]<<16); + d |= (ccb->datalen[3]<<24); + if(ccb->cdb[0] == 0x25 && sdstat == SDok) + d = 0; + n -= d; + r->rlen = n; + + /* + * If there was a check-condition, save the + * ccb for a possible request-sense command. + */ + if(sdstat == SDcheck){ + if(r->flags & SDnosense){ + lock(&ctlr->cachelock); + if(ctlr->cache[target]) + ccbfree(ctlr, ctlr->cache[target]); + ctlr->cache[target] = (Ccb*)ccb; + unlock(&ctlr->cachelock); + return SDcheck; + } + n = 8+ccb->sense[7]; + if(n > sizeof(r->sense)-1) + n = sizeof(r->sense)-1; + memmove(r->sense, ccb->sense, n); + r->flags |= SDvalidsense; + } + ccbfree(ctlr, (Ccb*)ccb); + + if(btstat){ + if(btstat == 0x11) + return SDtimeout; + return SDeio; + } + return sdstat; +} + +static void +mylex32interrupt(Ureg*, void* arg) +{ + ulong pa; + Ctlr *ctlr; + Ccb32 *ccb; + Mbox32 *mb, *mbox; + int port, rinterrupt, rstatus; + + ctlr = arg; + port = ctlr->port; + + /* + * Save and clear the interrupt(s). The only + * interrupts expected are Cmdc, which is ignored, + * and Imbl which means something completed. + * There's one spurious interrupt left over from + * initialisation, ignore it. + */ + rinterrupt = inb(port+Rinterrupt); + rstatus = inb(port+Rstatus); + outb(port+Rcontrol, Rint); + if((rinterrupt & ~(Cmdc|Imbl)) != Intv && ctlr->spurious++) + print("%s: interrupt 0x%2.2ux\n", + ctlr->sdev->name, rinterrupt); + if((rinterrupt & Cmdc) && (rstatus & Cmdinv)) + print("%s: command invalid\n", ctlr->sdev->name); + + /* + * Look for something in the mail. + * If there is, free the mailbox and wakeup whoever. + */ + mb = ctlr->mb; + for(mbox = &mb[ctlr->mbix]; mbox->code; mbox = &mb[ctlr->mbix]){ + pa = (mbox->ccb[3]<<24) + |(mbox->ccb[2]<<16) + |(mbox->ccb[1]<<8) + |mbox->ccb[0]; + if(ctlr->pcidev) + ccb = BPA2K(pa, ctlr->pcidev->tbdf); + else + ccb = BPA2K(pa, BUSUNKNOWN); + mbox->code = 0; + ccb->done = 1; + wakeup(ccb); + + ctlr->mbix++; + if(ctlr->mbix >= NMbox+NMbox) + ctlr->mbix = NMbox; + } +} + +static int +mylexrio(SDreq* r) +{ + Ctlr *ctlr; + + ctlr = r->unit->dev->ctlr; + if((r->unit->subno) == ctlr->id) + r->status = SDtimeout; + else if(ctlr->bus == 24) + r->status = mylex24rio(r); + else + r->status = mylex32rio(r); + return r->status; +} + +/* + * Issue a command to a controller. The command and its length is + * contained in cmd and cmdlen. If any data is to be + * returned, datalen should be non-zero, and the returned data + * will be placed in data. + * If Cmdc is set, bail out, the invalid command will be handled + * when the interrupt is processed. + */ +static void +issueio(int port, uchar* cmd, int cmdlen, uchar* data, int datalen) +{ + int len; + + if(cmd[0] != Cstart && cmd[0] != Ceombri){ + while(!(inb(port+Rstatus) & Hardy)) + ; + } + outb(port+Rcpr, cmd[0]); + + len = 1; + while(len < cmdlen){ + if(!(inb(port+Rstatus) & Cprbsy)){ + outb(port+Rcpr, cmd[len]); + len++; + } + if(inb(port+Rinterrupt) & Cmdc) + return; + } + + if(datalen){ + len = 0; + while(len < datalen){ + if(inb(port+Rstatus) & Dirrdy){ + data[len] = inb(port+Rdatain); + len++; + } + if(inb(port+Rinterrupt) & Cmdc) + return; + } + } +} + +/* + * Issue a command to a controller, wait for it to complete then + * try to reset the interrupt. Should only be called at initialisation. + */ +static int +issue(Ctlr* ctlr, uchar* cmd, int cmdlen, uchar* data, int datalen) +{ + int port; + uchar rinterrupt, rstatus; + static Lock mylexissuelock; + + port = ctlr->port; + + ilock(&ctlr->issuelock); + issueio(port, cmd, cmdlen, data, datalen); + + while(!((rinterrupt = inb(port+Rinterrupt)) & Cmdc)) + ; + + rstatus = inb(port+Rstatus); + outb(port+Rcontrol, Rint); + iunlock(&ctlr->issuelock); + + if((rinterrupt & Cmdc) && (rstatus & Cmdinv)) + return 0; + return 1; +} + +static SDev* +mylexprobe(int port, int irq) +{ + SDev *sdev; + Ctlr *ctlr; + uchar cmd[6], data[256]; + int clen, dlen, timeo; + + if(ioalloc(port, 0x3, 0, "mylex") < 0) + return nil; + ctlr = nil; + + /* + * Attempt to hard-reset the board and reset + * the SCSI bus. If the board state doesn't settle to + * idle with mailbox initialisation required, either + * it isn't a compatible board or it's broken. + * If the controller has SCAM set this can take a while. + */ + outb(port+Rcontrol, Rhard|Rsbus); + for(timeo = 0; timeo < 100; timeo++){ + if(inb(port+Rstatus) == (Inreq|Hardy)) + break; + delay(100); + } + if(inb(port+Rstatus) != (Inreq|Hardy)){ +buggery: + if(ctlr != nil) + free(ctlr); + iofree(port); + return nil; + } + + if((ctlr = malloc(sizeof(Ctlr))) == nil) + goto buggery; + ctlr->port = port; + ctlr->irq = irq; + ctlr->bus = 24; + ctlr->wide = 0; + + /* + * Try to determine if this is a 32-bit MultiMaster controller + * by attempting to obtain the extended inquiry information; + * this command is not implemented on Adaptec 154xx + * controllers. If successful, the first byte of the returned + * data is the host adapter bus type, 'E' for 32-bit EISA, + * PCI and VLB buses. + */ + cmd[0] = Ciesi; + cmd[1] = 4; + clen = 2; + dlen = 256; + if(issue(ctlr, cmd, clen, data, dlen)){ + if(data[0] == 'E') + ctlr->bus = 32; + ctlr->wide = data[0x0D] & 0x01; + } + else{ + /* + * Inconceivable though it may seem, a hard controller reset + * is necessary here to clear out the command queue. Every + * board seems to lock-up in a different way if you give an + * invalid command and then try to clear out the + * command/parameter and/or data-in register. + * Soft reset doesn't do the job either. Fortunately no + * serious initialisation has been done yet so there's nothing + * to tidy up. + */ + outb(port+Rcontrol, Rhard); + for(timeo = 0; timeo < 100; timeo++){ + if(inb(port+Rstatus) == (Inreq|Hardy)) + break; + delay(100); + } + if(inb(port+Rstatus) != (Inreq|Hardy)) + goto buggery; + } + + /* + * If the BIOS is enabled on the AHA-1542C/CF and BIOS options for + * support of drives > 1Gb, dynamic scanning of the SCSI bus or more + * than 2 drives under DOS 5.0 are enabled, the BIOS disables + * accepting Cmbinit to protect against running with drivers which + * don't support those options. In order to unlock the interface it + * is necessary to read a lock-code using Cextbios and write it back + * using Cmbienable; the lock-code is non-zero. + */ + cmd[0] = Cinquiry; + clen = 1; + dlen = 4; + if(issue(ctlr, cmd, clen, data, dlen) == 0) + goto buggery; + if(data[0] >= 0x43){ + cmd[0] = Cextbios; + clen = 1; + dlen = 2; + if(issue(ctlr, cmd, clen, data, dlen) == 0) + goto buggery; + + /* + * Lock-code returned in data[1]. If it's non-zero write + * it back along with bit 0 of byte 0 cleared to enable + * mailbox initialisation. + */ + if(data[1]){ + cmd[0] = Cmbienable; + cmd[1] = 0; + cmd[2] = data[1]; + clen = 3; + if(issue(ctlr, cmd, clen, 0, 0) == 0) + goto buggery; + } + } + + /* + * Get the id, DMA and IRQ info from the board. This will + * cause an interrupt which will hopefully not cause any + * trouble because the interrupt number isn't known yet. + * This is necessary as the DMA won't be set up if the + * board has the BIOS disabled. + * + * If the IRQ is already known, this must be a 32-bit PCI + * or EISA card, in which case the returned DMA and IRQ can + * be ignored. + */ + cmd[0] = Cinquire; + clen = 1; + dlen = 3; + if(issue(ctlr, cmd, clen, data, dlen) == 0) + goto buggery; + + ctlr->id = data[2] & 0x07; + if(ctlr->irq < 0){ + switch(data[0]){ /* DMA Arbitration Priority */ + case 0x80: /* Channel 7 */ + outb(0xD6, 0xC3); + outb(0xD4, 0x03); + break; + case 0x40: /* Channel 6 */ + outb(0xD6, 0xC2); + outb(0xD4, 0x02); + break; + case 0x20: /* Channel 5 */ + outb(0xD6, 0xC1); + outb(0xD4, 0x01); + break; + case 0x01: /* Channel 0 */ + outb(0x0B, 0xC0); + outb(0x0A, 0x00); + break; + default: + break; + } + + switch(data[1]){ /* Interrupt Channel */ + case 0x40: + ctlr->irq = 15; + break; + case 0x20: + ctlr->irq = 14; + break; + case 0x08: + ctlr->irq = 12; + break; + case 0x04: + ctlr->irq = 11; + break; + case 0x02: + ctlr->irq = 10; + break; + case 0x01: + ctlr->irq = 9; + break; + default: + goto buggery; + } + } + + if((sdev = malloc(sizeof(SDev))) == nil) + goto buggery; + sdev->ifc = &sdmylexifc; + sdev->ctlr = ctlr; + ctlr->sdev = sdev; + if(!ctlr->wide) + sdev->nunit = 8; + else + sdev->nunit = 16; + + return sdev; +} + +static int mylexport[8] = { + 0x330, 0x334, 0x230, 0x234, 0x130, 0x134, 0x000, 0x000, +}; +static int mylexirq[8] = { + 9, 10, 11, 12, 14, 15, 0, 0, +}; + +static SDev* +mylexpnp(void) +{ + Pcidev *p; + Ctlr *ctlr; + int cfg, i, x; + SDev *sdev, *head, *tail; + + p = nil; + head = tail = nil; + while(p = pcimatch(p, 0x104B, 0)){ + if((sdev = mylexprobe(p->mem[0].bar & ~0x01, p->intl)) == nil) + continue; + + ctlr = sdev->ctlr; + ctlr->pcidev = p; + + if(head != nil) + tail->next = sdev; + else + head = sdev; + tail = sdev; + } + + if(strncmp(KADDR(0xFFFD9), "EISA", 4)) + return head; + for(cfg = 0x1000; cfg < MaxEISA*0x1000; cfg += 0x1000){ + x = 0; + for(i = 0; i < 4; i++) + x |= inb(cfg+CfgEISA+i)<<(i*8); + if(x != 0x0142B30A && x != 0x0242B30A) + continue; + + x = inb(cfg+0xC8C); + if((sdev = mylexprobe(mylexport[x & 0x07], -1)) == nil) + continue; + + if(head != nil) + tail->next = sdev; + else + head = sdev; + tail = sdev; + } + + return head; +} + +static SDev* +mylexid(SDev* sdev) +{ + return scsiid(sdev, &sdmylexifc); +} + +static int +mylex24enable(Ctlr* ctlr) +{ + ulong p; + Ccb24 *ccb, *ccbp; + uchar cmd[6], *v; + int len; + + len = (sizeof(Mbox24)*NMbox*2)+(sizeof(Ccb24)*NCcb); + v = xspanalloc(len, 32, 0); + + if(!PADDR24(ctlr, sizeof(Ctlr)) || !PADDR24(v, len)) + return 0; + + ctlr->mb = v; + v += sizeof(Mbox24)*NMbox*2; + + ccb = (Ccb24*)v; + for(ccbp = ccb; ccbp < &ccb[NCcb]; ccbp++){ + ccbp->ccb = ctlr->ccb; + ctlr->ccb = (Ccb*)ccbp; + } + + /* + * Initialise the software controller and + * set the board scanning the mailboxes. + */ + ctlr->mbix = NMbox; + + cmd[0] = Cinitialise; + cmd[1] = NMbox; + p = K2BPA(ctlr->mb, BUSUNKNOWN); + cmd[2] = p>>16; + cmd[3] = p>>8; + cmd[4] = p; + len = 5; + + return issue(ctlr, cmd, len, 0, 0); +} + +static int +mylex32enable(Ctlr* ctlr) +{ + ulong p; + Ccb32 *ccb, *ccbp; + uchar cmd[6], *v; + + v = xspanalloc((sizeof(Mbox32)*NMbox*2)+(sizeof(Ccb32)*NCcb), 32, 0); + + ctlr->mb = v; + v += sizeof(Mbox32)*NMbox*2; + + ccb = (Ccb32*)v; + for(ccbp = ccb; ccbp < &ccb[NCcb]; ccbp++){ + /* + * Fill in some stuff that doesn't change. + */ + ccbp->senselen = sizeof(ccbp->sense); + p = PADDR(ccbp->sense); + ccbp->senseptr[0] = p; + ccbp->senseptr[1] = p>>8; + ccbp->senseptr[2] = p>>16; + ccbp->senseptr[3] = p>>24; + + ccbp->ccb = ctlr->ccb; + ctlr->ccb = (Ccb*)ccbp; + } + + /* + * Attempt wide mode setup. + */ + if(ctlr->wide){ + cmd[0] = Cwide; + cmd[1] = 1; + if(!issue(ctlr, cmd, 2, 0, 0)) + ctlr->wide = 0; + } + + /* + * Initialise the software controller and + * set the board scanning the mailboxes. + */ + ctlr->mbix = NMbox; + + cmd[0] = Ciem; + cmd[1] = NMbox; + if(ctlr->pcidev) + p = K2BPA(ctlr->mb, ctlr->tbdf); + else + p = K2BPA(ctlr->mb, BUSUNKNOWN); + cmd[2] = p; + cmd[3] = p>>8; + cmd[4] = p>>16; + cmd[5] = p>>24; + + return issue(ctlr, cmd, 6, 0, 0); +} + +static int +mylexenable(SDev* sdev) +{ + int tbdf; + Ctlr *ctlr; + void (*interrupt)(Ureg*, void*); + char name[NAMELEN]; + + ctlr = sdev->ctlr; + if(ctlr->cache == nil){ + if((ctlr->cache = malloc(sdev->nunit*sizeof(Ccb*))) == nil) + return 0; + } + + tbdf = BUSUNKNOWN; + if(ctlr->bus == 32){ + if(ctlr->pcidev){ + tbdf = ctlr->pcidev->tbdf; + pcisetbme(ctlr->pcidev); + } + if(!mylex32enable(ctlr)) + return 0; + interrupt = mylex32interrupt; + } + else if(mylex24enable(ctlr)) + interrupt = mylex24interrupt; + else + return 0; + + snprint(name, NAMELEN, "sd%c (%s)", sdev->idno, sdev->ifc->name); + intrenable(ctlr->irq, interrupt, ctlr, tbdf, name); + + return 1; +} + +SDifc sdmylexifc = { + "mylex", /* name */ + + mylexpnp, /* pnp */ + nil, /* legacy */ + mylexid, /* id */ + mylexenable, /* enable */ + nil, /* disable */ + + scsiverify, /* verify */ + scsionline, /* online */ + mylexrio, /* rio */ + nil, /* rctl */ + nil, /* wctl */ + + scsibio, /* bio */ +}; diff --git a/pc/sdscsi.c b/pc/sdscsi.c new file mode 100644 index 0000000000000000000000000000000000000000..47ccf06a1c26fcccdf9a40dbc8652bdae7301cec --- /dev/null +++ b/pc/sdscsi.c @@ -0,0 +1,377 @@ +#include "u.h" +#include "../port/lib.h" +#include "mem.h" +#include "dat.h" +#include "fns.h" +#include "io.h" +#include "ureg.h" +#include "../port/error.h" + +#include "sd.h" + +static int +scsitest(SDreq* r) +{ + r->write = 0; + memset(r->cmd, 0, sizeof(r->cmd)); + r->cmd[1] = r->lun<<5; + r->clen = 6; + r->data = nil; + r->dlen = 0; + r->flags = 0; + + r->status = ~0; + + return r->unit->dev->ifc->rio(r); +} + +int +scsiverify(SDunit* unit) +{ + SDreq *r; + int i, status; + uchar *inquiry; + + if((r = malloc(sizeof(SDreq))) == nil) + return 0; + if((inquiry = sdmalloc(sizeof(unit->inquiry))) == nil){ + free(r); + return 0; + } + r->unit = unit; + r->lun = 0; /* ??? */ + + memset(unit->inquiry, 0, sizeof(unit->inquiry)); + r->write = 0; + r->cmd[0] = 0x12; + r->cmd[1] = r->lun<<5; + r->cmd[4] = sizeof(unit->inquiry)-1; + r->clen = 6; + r->data = inquiry; + r->dlen = sizeof(unit->inquiry)-1; + r->flags = 0; + + r->status = ~0; + if(unit->dev->ifc->rio(r) != SDok){ + free(r); + return 0; + } + memmove(unit->inquiry, inquiry, r->dlen); + free(inquiry); + + SET(status); + for(i = 0; i < 3; i++){ + while((status = scsitest(r)) == SDbusy) + ; + if(status == SDok || status != SDcheck) + break; + if(!(r->flags & SDvalidsense)) + break; + if((r->sense[2] & 0x0F) != 0x02) + continue; + /* + * Unit is 'not ready'. + * If it needs an initialising command, set status + * so it will be spun-up below. + * If there's no medium, that's OK too, but don't + * try to spin it up. + */ + if(r->sense[12] == 0x04 && r->sense[13] == 0x02){ + status = SDok; + break; + } + if(r->sense[12] == 0x3A) + break; + } + + if(status == SDok){ + /* + * Try to ensure a direct-access device is spinning. + * Don't wait for completion, ignore the result. + */ + if((unit->inquiry[0] & 0x1F) == 0){ + memset(r->cmd, 0, sizeof(r->cmd)); + r->write = 0; + r->cmd[0] = 0x1B; + r->cmd[1] = (r->lun<<5)|0x01; + r->cmd[4] = 1; + r->clen = 6; + r->data = nil; + r->dlen = 0; + r->flags = 0; + + r->status = ~0; + unit->dev->ifc->rio(r); + } + } + free(r); + + if(status == SDok || status == SDcheck) + return 1; + return 0; +} + +static int +scsirio(SDreq* r) +{ + /* + * Perform an I/O request, returning + * -1 failure + * 0 ok + * 1 no medium present + * 2 retry + * The contents of r may be altered so the + * caller should re-initialise if necesary. + */ + r->status = ~0; + switch(r->unit->dev->ifc->rio(r)){ + default: + return -1; + case SDcheck: + if(!(r->flags & SDvalidsense)) + return -1; + switch(r->sense[2] & 0x0F){ + case 0x00: /* no sense */ + case 0x01: /* recovered error */ + return 2; + case 0x06: /* check condition */ + /* + * 0x28 - not ready to ready transition, + * medium may have changed. + * 0x29 - power on or some type of reset. + */ + if(r->sense[12] == 0x28 && r->sense[13] == 0) + return 2; + if(r->sense[12] == 0x29) + return 2; + return -1; + case 0x02: /* not ready */ + /* + * If no medium present, bail out. + * If unit is becoming ready, rather than not + * not ready, wait a little then poke it again. */ + if(r->sense[12] == 0x3A) + return 1; + if(r->sense[12] != 0x04 || r->sense[13] != 0x01) + return -1; + + while(waserror()) + ; + tsleep(&r->unit->rendez, return0, 0, 500); + poperror(); + scsitest(r); + return 2; + default: + return -1; + } + return -1; + case SDok: + return 0; + } + return -1; +} + +int +scsionline(SDunit* unit) +{ + SDreq *r; + uchar *p; + int ok, retries; + + if((r = malloc(sizeof(SDreq))) == nil) + return 0; + if((p = sdmalloc(8)) == nil){ + free(r); + return 0; + } + + ok = 0; + + r->unit = unit; + r->lun = 0; /* ??? */ + for(retries = 0; retries < 10; retries++){ + /* + * Read-capacity is mandatory for DA, WORM, CD-ROM and + * MO. It may return 'not ready' if type DA is not + * spun up, type MO or type CD-ROM are not loaded or just + * plain slow getting their act together after a reset. + */ + r->write = 0; + memset(r->cmd, 0, sizeof(r->cmd)); + r->cmd[0] = 0x25; + r->cmd[1] = r->lun<<5; + r->clen = 10; + r->data = p; + r->dlen = 8; + r->flags = 0; + + r->status = ~0; + switch(scsirio(r)){ + default: + break; + case 0: + unit->sectors = (p[0]<<24)|(p[1]<<16)|(p[2]<<8)|p[3]; + /* + * Read-capacity returns the LBA of the last sector, + * therefore the number of sectors must be incremented. + */ + unit->sectors++; + unit->secsize = (p[4]<<24)|(p[5]<<16)|(p[6]<<8)|p[7]; + ok = 1; + break; + case 1: + ok = 1; + break; + case 2: + continue; + } + break; + } + free(p); + free(r); + + return ok; +} + +int +scsiexec(SDunit* unit, int write, uchar* cmd, int clen, void* data, int* dlen) +{ + SDreq *r; + int status; + + if((r = malloc(sizeof(SDreq))) == nil) + return SDmalloc; + r->unit = unit; + r->lun = cmd[1]>>5; /* ??? */ + r->write = write; + memmove(r->cmd, cmd, clen); + r->clen = clen; + r->data = data; + if(dlen) + r->dlen = *dlen; + r->flags = 0; + + r->status = ~0; + + /* + * Call the device-specific I/O routine. + * There should be no calls to 'error()' below this + * which percolate back up. + */ + switch(status = unit->dev->ifc->rio(r)){ + case SDok: + if(dlen) + *dlen = r->rlen; + /*FALLTHROUGH*/ + case SDcheck: + /*FALLTHROUGH*/ + default: + /* + * It's more complicated than this. There are conditions + * which are 'ok' but for which the returned status code + * is not 'SDok'. + * Also, not all conditions require a reqsense, might + * need to do a reqsense here and make it available to the + * caller somehow. + * + * MaƱana. + */ + break; + } + sdfree(r); + + return status; +} + +long +scsibio(SDunit* unit, int lun, int write, void* data, long nb, long bno) +{ + SDreq *r; + long rlen; + + if((r = malloc(sizeof(SDreq))) == nil) + return SDmalloc; + r->unit = unit; + r->lun = lun; +again: + r->write = write; + if(write == 0) + r->cmd[0] = 0x28; + else + r->cmd[0] = 0x2A; + r->cmd[1] = (lun<<5); + r->cmd[2] = bno>>24; + r->cmd[3] = bno>>16; + r->cmd[4] = bno>>8; + r->cmd[5] = bno; + r->cmd[6] = 0; + r->cmd[7] = nb>>8; + r->cmd[8] = nb; + r->cmd[9] = 0; + r->clen = 10; + r->data = data; + r->dlen = nb*unit->secsize; + r->flags = 0; + + r->status = ~0; + switch(scsirio(r)){ + default: + rlen = -1; + break; + case 0: + rlen = r->rlen; + break; + case 2: + rlen = -1; + if(!(r->flags & SDvalidsense)) + break; + switch(r->sense[2] & 0x0F){ + default: + break; + case 0x06: /* check condition */ + /* + * Check for a removeable media change. + * If so, mark it and zap the geometry info + * to force an online request. + */ + if(r->sense[12] != 0x28 || r->sense[13] != 0) + break; + if(unit->inquiry[1] & 0x80){ + unit->changed = 1; + unit->sectors = 0; + } + break; + case 0x02: /* not ready */ + /* + * If unit is becoming ready, + * rather than not not ready, try again. + */ + if(r->sense[12] == 0x04 && r->sense[13] == 0x01) + goto again; + break; + } + break; + } + free(r); + + return rlen; +} + +SDev* +scsiid(SDev* sdev, SDifc* ifc) +{ + static char idno[16] = "0123456789abcdef"; + static char *p = idno; + + while(sdev){ + if(sdev->ifc == ifc){ + sdev->idno = *p++; + snprint(sdev->name, NAMELEN, "sd%c", sdev->idno); + if(p >= &idno[sizeof(idno)]) + break; + } + sdev = sdev->next; + } + + return nil; +} diff --git a/port/devsd.c b/port/devsd.c index 1ae8a54cd3418683c12a450c91b1fa0bf972e2d3..9cb3c78930c14b86dcb23003db6a89cfaa53422e 100644 --- a/port/devsd.c +++ b/port/devsd.c @@ -1,461 +1,901 @@ /* - * SCSI disc driver + * Storage Device. */ -#include "u.h" -#include "../port/lib.h" -#include "mem.h" -#include "dat.h" -#include "fns.h" -#include "../port/error.h" - -enum -{ - TypeDA = 0x00, /* Direct Access */ - TypeSA = 0x01, /* Sequential Access */ - TypeWO = 0x04, /* Worm */ - TypeCD = 0x05, /* CD-ROM */ - TypeMO = 0x07, /* rewriteable Magneto-Optical */ - TYpeMC = 0x08, /* Medium Changer */ -}; - -enum -{ - LogNpart = 4, - Npart = 1<>LogNpart) -#define PART(qid) ((qid).path&(Npart-1)) -typedef struct Part Part; -typedef struct Disk Disk; +enum { + Qtopdir = 1, /* top level directory */ + Qtopbase, -struct Part -{ - ulong beg; - ulong end; - char name[NAMELEN]; + Qunitdir, /* directory per unit */ + Qunitbase, + Qctl = Qunitbase, + Qraw, + Qpart, }; -struct Disk +#define TYPE(q) ((q).path & 0x0F) +#define PART(q) (((q).path>>4) & 0x0F) +#define UNIT(q) (((q).path>>8) & 0xFF) +#define QID(u, p, t) (((u)<<8)|((p)<<4)|(t)) + +static void +sdaddpart(SDunit* unit, char* name, ulong start, ulong end) { - QLock; - Target* t; - int lun; - char id[NAMELEN]; - char vol[NAMELEN]; - - uchar* inquire; - int partok; - ulong version; - ulong size; - ulong bsize; - - int npart; - Part table[Npart]; -}; + SDpart *pp; + int i, partno; -int ndisk; -Disk disk[Ndisk]; + /* + * Check name not already used + * and look for a free slot. + */ + if(unit->part != nil){ + partno = -1; + for(i = 0; i < SDnpart; i++){ + pp = &unit->part[i]; + if(!pp->valid){ + if(partno == -1) + partno = i; + break; + } + if(strcmp(name, pp->name) == 0){ + if(pp->start == start && pp->end == end) + return; + error(Ebadctl); + } + } + } + else{ + if((unit->part = malloc(sizeof(SDpart)*SDnpart)) == nil) + error(Enomem); + partno = 0; + } -static int sdrdpart(Disk*); -static long sdio(Chan*, int, char*, ulong, vlong); + /* + * Check there is a free slot and size and extent are valid. + */ + if(partno == -1 || start > end || end > unit->sectors) + error(Eio); + pp = &unit->part[partno]; + pp->start = start; + pp->end = end; + strncpy(pp->name, name, NAMELEN); + strncpy(pp->user, eve, NAMELEN); + pp->perm = 0640; + pp->valid = 1; + unit->npart++; +} -static int types[] = +static void +sddelpart(SDunit* unit, char* name) { - TypeDA, TypeCD, TypeMO, - -1, -}; + int i; + SDpart *pp; + + /* + * Look for the partition to delete. + * Can't delete if someone still has it open. + * If it's the last valid partition zap the + * whole table. + */ + pp = unit->part; + for(i = 0; i < SDnpart; i++){ + if(strncmp(name, pp->name, NAMELEN) == 0) + break; + pp++; + } + if(i >= SDnpart) + error(Ebadctl); + if(pp->nopen) + error(Einuse); + pp->valid = 0; + + unit->npart--; + if(unit->npart == 0){ + free(unit->part); + unit->part = nil; + } +} static int -sdgen(Chan *c, Dirtab*, int, int s, Dir *dirp) +sdinitpart(SDunit* unit) { - Qid qid; - Disk *d; - Part *p; - int unit; - char name[2*NAMELEN]; - vlong l; + int nf; + ulong start, end; + char *f[4], *p, *q; + + unit->sectors = unit->secsize = 0; + unit->npart = 0; + if(unit->part){ + free(unit->part); + unit->part = nil; + } - if(s == DEVDOTDOT){ - devdir(c, qid, "#w", 0, eve, 0555, dirp); - return 1; + if(unit->inquiry[0] & 0xC0) + return 0; + switch(unit->inquiry[0] & 0x1F){ + case 0x00: /* DA */ + case 0x04: /* WORM */ + case 0x05: /* CD-ROM */ + case 0x07: /* MO */ + break; + default: + return 0; } - d = disk; - while(s >= d->npart) { - s -= d->npart; - d++; + if(unit->dev->ifc->online) + unit->dev->ifc->online(unit); + if(unit->sectors){ + sdaddpart(unit, "data", 0, unit->sectors); + + /* + * Use partitions passed from boot program, + * e.g. + * sdC0=dos 63 123123/plan9 123123 456456 + */ + for(p = getconf(unit->name); p != nil; p = q){ + if(q = strchr(p, '/')) + *q++ = '\0'; + nf = getfields(p, f, nelem(f), 1, " \t\r"); + if(nf < 3) + continue; + + start = strtoul(f[1], 0, 0); + end = strtoul(f[2], 0, 0); + if(!waserror()){ + sdaddpart(unit, f[0], start, end); + poperror(); + } + } } - unit = d - disk; - if(unit > ndisk) - return -1; - p = d->table+s; - sprint(name, "%s%s", d->vol, p->name); - name[NAMELEN-1] = '\0'; - qid = (Qid){(unit<end - p->beg) * (vlong)d->bsize; - devdir(c, qid, name, l, eve, 0666, dirp); return 1; } -static void -sdinit(void) +static SDunit* +sdgetunit(SDev* sdev, int subno) { - Disk *d; - uchar *scratch, type; - int dev, i, nbytes; - - dev = 0; - scratch = scsialloc(0xFF); - for(;;) { - d = &disk[ndisk]; - dev = scsiinv(dev, types, &d->t, &d->inquire, d->id); - if(dev < 0) - break; + int index; + SDunit *unit; + /* + * Associate a unit with a given device and sub-unit + * number on that device. + * The device will be probed if it has not already been + * successfully accessed. + */ + qlock(&sdqlock); + index = sdev->index+subno; + unit = sdunit[index]; + if(unit == nil){ /* - * Search for all the lun's. + * Probe the unit only once. This decision + * may be a little severe and reviewed later. */ - for(i = 0; i < Nlun; i++) { - d->lun = i; + if(sdunitflg[index]){ + qunlock(&sdqlock); + return nil; + } + if((unit = malloc(sizeof(SDunit))) == nil){ + qunlock(&sdqlock); + return nil; + } + sdunitflg[index] = 1; - /* - * A SCSI target does not support a lun if the - * the peripheral device type and qualifier fields - * in the response to an inquiry command are 0x7F. - */ - nbytes = 0xFF; - memset(scratch, 0, nbytes); + if(sdev->enabled == 0 && sdev->ifc->enable) + sdev->ifc->enable(sdev); + sdev->enabled = 1; - if(scsiinquiry(d->t, d->lun, scratch, &nbytes) != STok) - continue; - if(scratch[0] == 0x7F) - continue; - type = scratch[0] & 0x1F; + snprint(unit->name, NAMELEN, "%s%d", sdev->name, subno); + unit->subno = subno; + unit->dev = sdev; - /* - * Read-capacity is mandatory for TypeDA, TypeMO and - * TypeCD. It may return 'not ready' if TypeDA is not - * spun up, TypeMO or TypeCD are not loaded or just - * plain slow getting their act together after a reset. - * If 'not ready' comes back, try starting a TypeDA and - * punt the get capacity until the drive is attached. - */ - if(scsicap(d->t, d->lun, &d->size, &d->bsize) != STok) { - nbytes = 0xFF; - memset(scratch, 0, nbytes); - scsireqsense(d->t, 0, scratch, &nbytes, 1); - switch(scratch[2] & 0x0F){ - - case 0x00: - case 0x01: - case 0x02: - break; - case 0x06: - if(scratch[12] == 0x28 && !scratch[13]) - break; - if(scratch[12] == 0x29 && !scratch[13]) - break; - /*FALLTHROUGH*/ - default: - continue; - } - if(type == TypeDA) - scsistart(d->t, d->lun, 0); - d->size = d->bsize = 0; - } + /* + * No need to lock anything here as this is only + * called before the unit is made available in the + * sdunit[] array. + */ + if(unit->dev->ifc->verify(unit) == 0){ + qunlock(&sdqlock); + free(unit); + return nil; + } + sdunit[index] = unit; + } + qunlock(&sdqlock); - switch(type){ + return unit; +} - case TypeDA: - case TypeMO: - sprint(d->vol, "sd%d", ndisk); - break; +static SDunit* +sdindex2unit(int index) +{ + SDev *sdev; - case TypeCD: - sprint(d->vol, "cd%d", ndisk); - break; + /* + * Associate a unit with a given index into the top-level + * device directory. + * The device will be probed if it has not already been + * successfully accessed. + */ + for(sdev = sdlist; sdev != nil; sdev = sdev->next){ + if(index >= sdev->index && index < sdev->index+sdev->nunit) + return sdgetunit(sdev, index-sdev->index); + } - default: - continue; - } + return nil; - if(++ndisk >= Ndisk) - break; - d++; - d->t = d[-1].t; - d->inquire = d[-1].inquire; - strcpy(d->id, d[-1].id); +} + +static void +sdreset(void) +{ + int i; + SDev *sdev, *tail; + + /* + * Probe all configured controllers and make a list + * of devices found, accumulating a possible maximum number + * of units attached and marking each device with an index + * into the linear top-level directory array of units. + */ + tail = nil; + for(i = 0; sdifc[i] != nil; i++){ + if(sdifc[i]->pnp == nil || (sdev = sdifc[i]->pnp()) == nil) + continue; + if(sdlist != nil) + tail->next = sdev; + else + sdlist = sdev; + for(tail = sdev; tail->next != nil; tail = tail->next){ + sdev->index = sdnunit; + sdnunit += tail->nunit; } + tail->index = sdnunit; + sdnunit += tail->nunit; + } + + /* + * Legacy and option code goes here. This will be hard... + */ + + /* + * The maximum number of possible units is known, allocate + * placeholders for their datastructures; the units will be + * probed and structures allocated when attached. + * Allocate controller names for the different types. + */ + if(sdnunit == 0) + return; + if((sdunit = malloc(sdnunit*sizeof(SDunit*))) == nil) + return; + if((sdunitflg = malloc(sdnunit*sizeof(int))) == nil){ + free(sdunit); + sdunit = nil; + return; + } + for(i = 0; sdifc[i] != nil; i++){ + if(sdifc[i]->id) + sdifc[i]->id(sdlist); + } +} + +static int +sd2gen(Chan* c, int i, Dir* dp) +{ + Qid q; + vlong l; + SDpart *pp; + SDunit *unit; + + switch(i){ + case Qctl: + q = (Qid){QID(UNIT(c->qid), PART(c->qid), Qctl), c->qid.vers}; + devdir(c, q, "ctl", 0, eve, 0640, dp); + return 1; + case Qraw: + q = (Qid){QID(UNIT(c->qid), PART(c->qid), Qraw), c->qid.vers}; + devdir(c, q, "raw", 0, eve, 0600, dp); + return 1; + case Qpart: + unit = sdunit[UNIT(c->qid)]; + if(unit->changed) + break; + pp = &unit->part[PART(c->qid)]; + l = (pp->end - pp->start) * (vlong)unit->secsize; + q = (Qid){QID(UNIT(c->qid), PART(c->qid), Qpart), c->qid.vers}; + devdir(c, q, pp->name, l, pp->user, pp->perm, dp); + return 1; + } + return -1; +} - if(ndisk >= Ndisk) { - print("devsd: configure more disks\n"); +static int +sd1gen(Chan*, int i, Dir*) +{ + switch(i){ + default: + return -1; + } + return -1; +} + +static int +sdgen(Chan* c, Dirtab*, int, int s, Dir* dp) +{ + Qid q; + vlong l; + int i, r; + SDpart *pp; + SDunit *unit; + char name[NAMELEN]; + + switch(TYPE(c->qid)){ + case Qtopdir: + if(s == DEVDOTDOT){ + q = (Qid){QID(0, 0, Qtopdir)|CHDIR, 0}; + snprint(name, NAMELEN, "#%C", sddevtab.dc); + devdir(c, q, name, 0, eve, 0555, dp); + return 1; + } + if(s < sdnunit){ + if(sdunit[s] == nil && sdindex2unit(s) == nil) + return 0; + q = (Qid){QID(s, 0, Qunitdir)|CHDIR, 0}; + devdir(c, q, sdunit[s]->name, 0, eve, 0555, dp); + return 1; + } + s -= sdnunit; + return sd1gen(c, s+Qtopbase, dp); + case Qunitdir: + if(s == DEVDOTDOT){ + q = (Qid){QID(0, 0, Qtopdir)|CHDIR, 0}; + snprint(name, NAMELEN, "#%C", sddevtab.dc); + devdir(c, q, name, 0, eve, 0555, dp); + return 1; + } + unit = sdunit[UNIT(c->qid)]; + qlock(&unit->ctl); + if(!unit->changed && unit->sectors == 0) + sdinitpart(unit); + i = s+Qunitbase; + if(i < Qpart){ + r = sd2gen(c, i, dp); + qunlock(&unit->ctl); + return r; + } + i -= Qpart; + if(unit->npart == 0 || i >= SDnpart){ + qunlock(&unit->ctl); break; } + pp = &unit->part[i]; + if(unit->changed || !pp->valid){ + qunlock(&unit->ctl); + return 0; + } + l = (pp->end - pp->start) * (vlong)unit->secsize; + q = (Qid){QID(UNIT(c->qid), i, Qpart), c->qid.vers}; + devdir(c, q, pp->name, l, pp->user, pp->perm, dp); + qunlock(&unit->ctl); + return 1; + case Qraw: + case Qctl: + case Qpart: + unit = sdunit[UNIT(c->qid)]; + qlock(&unit->ctl); + r = sd2gen(c, TYPE(c->qid), dp); + qunlock(&unit->ctl); + return r; + default: + break; } - scsifree(scratch); + + return -1; } static Chan* -sdattach(char *spec) +sdattach(char* spec) { - int i; + Chan *c; + char *p; + SDev *sdev; + int idno, subno; + + if(sdnunit == 0 || *spec == '\0'){ + c = devattach(sddevtab.dc, spec); + c->qid = (Qid){QID(0, 0, Qtopdir)|CHDIR, 0}; + return c; + } - for(i = 0; i < ndisk; i++){ - qlock(&disk[i]); - if(disk[i].partok == 0) - sdrdpart(&disk[i]); - qunlock(&disk[i]); + if(spec[0] != 's' || spec[1] != 'd') + error(Ebadspec); + idno = spec[2]; + subno = strtol(&spec[3], &p, 0); + if(p == &spec[3]) + error(Ebadspec); + for(sdev = sdlist; sdev != nil; sdev = sdev->next){ + if(sdev->idno == idno && subno < sdev->nunit) + break; } + if(sdev == nil || sdgetunit(sdev, subno) == nil) + error(Enonexist); - return devattach('w', spec); + c = devattach(sddevtab.dc, spec); + c->qid = (Qid){QID(sdev->index+subno, 0, Qunitdir)|CHDIR, 0}; + c->dev = sdev->index+subno; + return c; +} + +static Chan* +sdclone(Chan* c, Chan* nc) +{ + return devclone(c, nc); } static int -sdwalk(Chan *c, char *name) +sdwalk(Chan* c, char* name) { - return devwalk(c, name, 0, 0, sdgen); + return devwalk(c, name, nil, 0, sdgen); } static void -sdstat(Chan *c, char *db) +sdstat(Chan* c, char* db) { - devstat(c, db, 0, 0, sdgen); + devstat(c, db, nil, 0, sdgen); } static Chan* -sdopen(Chan *c, int omode) +sdopen(Chan* c, int omode) { - return devopen(c, omode, 0, 0, sdgen); + SDpart *pp; + SDunit *unit; + + c = devopen(c, omode, 0, 0, sdgen); + switch(TYPE(c->qid)){ + default: + break; + case Qpart: + unit = sdunit[UNIT(c->qid)]; + qlock(&unit->ctl); + if(waserror()){ + qunlock(&unit->ctl); + c->flag &= ~COPEN; + nexterror(); + } + if(unit->changed) + error(Eio); + pp = &unit->part[PART(c->qid)]; + pp->nopen++; + unit->nopen++; + qunlock(&unit->ctl); + poperror(); + break; + } + return c; } static void -sdclose(Chan *c) +sdclose(Chan* c) { - Disk *d; - Part *p; + SDpart *pp; + SDunit *unit; if(c->qid.path & CHDIR) return; + if(!(c->flag & COPEN)) + return; - d = &disk[DRIVE(c->qid)]; - p = &d->table[PART(c->qid)]; - if((c->mode&3) != OREAD && strcmp(p->name, "partition") == 0){ - qlock(d); - d->partok = 0; - sdrdpart(d); - qunlock(d); + switch(TYPE(c->qid)){ + default: + break; + case Qraw: + unit = sdunit[UNIT(c->qid)]; + if(canqlock(&unit->raw) || unit->pid == up->pid){ + unit->pid = 0; + unit->state = Rawcmd; + qunlock(&unit->raw); + } + break; + case Qpart: + unit = sdunit[UNIT(c->qid)]; + qlock(&unit->ctl); + if(waserror()){ + qunlock(&unit->ctl); + c->flag &= ~COPEN; + nexterror(); + } + pp = &unit->part[PART(c->qid)]; + pp->nopen--; + unit->nopen--; + if(unit->nopen == 0) + unit->changed = 0; + qunlock(&unit->ctl); + poperror(); + break; } } static long -sdread(Chan *c, void *a, long n, vlong off) +sdbio(Chan* c, int write, char* a, long len, vlong off) { + long l; + uchar *b; + SDpart *pp; + SDunit *unit; + ulong bno, max, nb, offset; - if(c->qid.path & CHDIR) - return devdirread(c, a, n, 0, 0, sdgen); + unit = sdunit[UNIT(c->qid)]; - return sdio(c, 0, a, n, off); -} + qlock(&unit->ctl); + if(waserror()){ + qunlock(&unit->ctl); + nexterror(); + } + if(unit->changed) + error(Eio); -static long -sdwrite(Chan *c, void *a, long n, vlong off) -{ - Disk *d; + /* + * Check the request is within bounds. + * Removeable drives are locked throughout the I/O + * in case the media changes unexpectedly. + * Non-removeable drives are not locked during the I/O + * to allow the hardware to optimise if it can; this is + * a little fast and loose. + * It's assumed that non-removeable media parameters + * (sectors, secsize) can't change once the drive has + * been brought online. + */ + pp = &unit->part[PART(c->qid)]; + bno = (off/unit->secsize) + pp->start; + nb = ((off+len+unit->secsize-1)/unit->secsize) + pp->start - bno; + max = SDmaxio/unit->secsize; + if(nb > max) + nb = max; + if(bno+nb > pp->end) + nb = pp->end - bno; + if(bno >= pp->end || nb == 0){ + if(write) + error(Eio); + qunlock(&unit->ctl); + poperror(); + return 0; + } + if(!(unit->inquiry[1] & 0x80)){ + qunlock(&unit->ctl); + poperror(); + } - d = &disk[DRIVE(c->qid)]; + b = sdmalloc(nb*unit->secsize); + if(b == nil) + error(Enomem); + if(waserror()){ + sdfree(b); + nexterror(); + } - if((d->inquire[0] & 0x1F) == TypeCD) - error(Eperm); - return sdio(c, 1, a, n, off); -} + offset = off%unit->secsize; + if(write){ + if(offset || (len%unit->secsize)){ + if((l = unit->dev->ifc->bio(unit, 0, 0, b, nb, bno)) < 0) + error(Eio); + if(l < (nb*unit->secsize)){ + nb = l/unit->secsize; + l = nb*unit->secsize - offset; + if(len > l) + len = l; + } + } + memmove(b+offset, a, len); + if((l = unit->dev->ifc->bio(unit, 0, 1, b, nb, bno)) < 0) + error(Eio); + if(l < offset) + len = 0; + else if(len > l - offset) + len = l - offset; + } + else { + if((l = unit->dev->ifc->bio(unit, 0, 0, b, nb, bno)) < 0) + error(Eio); + if(l < offset) + len = 0; + else if(len > l - offset) + len = l - offset; + memmove(a, b+offset, len); + } + sdfree(b); + poperror(); -Dev sddevtab = { - 'w', - "sd", + if(unit->inquiry[1] & 0x80){ + qunlock(&unit->ctl); + poperror(); + } - devreset, - sdinit, - sdattach, - devclone, - sdwalk, - sdstat, - sdopen, - devcreate, - sdclose, - sdread, - devbread, - sdwrite, - devbwrite, - devremove, - devwstat, -}; + return len; +} -static int -sdrdpart(Disk *d) +static long +sdrio(SDreq* r, void* a, long n) { - Part *p; - int n, i; - char *b, *line[Npart+2], *field[3]; - static char MAGIC[] = "plan9 partitions"; + void *data; - if(d->partok) - return STok; + if(n >= SDmaxio || n < 0) + error(Etoobig); - p = d->table; - strcpy(p->name, "disk"); - p->beg = 0; - p->end = 0; - d->npart = 1; + data = nil; + if(n){ + if((data = sdmalloc(n)) == nil) + error(Enomem); + if(r->write) + memmove(data, a, n); + } + r->data = data; + r->dlen = n; - /* - * If the drive wasn't ready when a read-capacity was tried - * earlier (in sdinit()), try again. - */ - if(d->size == 0 || d->bsize == 0){ - n = scsicap(d->t, d->lun, &d->size, &d->bsize); - if(n != STok){ - d->size = d->bsize = 0; - return scsierrstr(n); + if(waserror()){ + if(data != nil){ + sdfree(data); + r->data = nil; } + nexterror(); } - p->end = d->size + 1; + if(r->unit->dev->ifc->rio(r) != SDok) + error(Eio); - if((d->inquire[0] & 0x1F) == TypeCD) - return STok; + if(!r->write && r->rlen > 0) + memmove(a, data, r->rlen); + if(data != nil){ + sdfree(data); + r->data = nil; + } + poperror(); - b = scsialloc(d->bsize); - if(b == 0) - return scsierrstr(STnomem); + return r->rlen; +} - p++; - strcpy(p->name, "partition"); - p->beg = d->table[0].end - 2; - p->end = d->table[0].end - 1; - p++; - d->npart = 2; +static long +sdread(Chan *c, void *a, long n, vlong off) +{ + char *p; + SDpart *pp; + SDunit *unit; + ulong offset; + int i, l, status; - /* - * Read second last sector from disk, null terminate. - * The last sector used to hold the partition tables. - * However, this sector is special on some PC's so we've - * started to use the second last sector as the partition - * table instead. To avoid reconfiguring all our old systems - * we still check if there is a valid partition table in - * the last sector if none is found in the second last. - */ - scsibio(d->t, d->lun, SCSIread, b, 1, d->bsize, d->table[0].end-2); - b[d->bsize-1] = '\0'; - n = getfields(b, line, nelem(line), 1, "\n"); - if(n <= 0 || strncmp(line[0], MAGIC, sizeof(MAGIC)-1) != 0){ - /* try the last */ - scsibio(d->t, d->lun, SCSIread, b, 1, d->bsize, d->table[0].end-1); - b[d->bsize-1] = '\0'; - n = getfields(b, line, nelem(line), 1, "\n"); - /* only point partition file at last sector if there is one there */ - if(n > 0 && strncmp(line[0], MAGIC, sizeof(MAGIC)-1) == 0){ - d->table[1].beg++; - d->table[1].end++; + offset = off; + switch(TYPE(c->qid)){ + default: + error(Eperm); + case Qtopdir: + case Qunitdir: + return devdirread(c, a, n, 0, 0, sdgen); + case Qctl: + unit = sdunit[UNIT(c->qid)]; + p = malloc(READSTR); + l = snprint(p, READSTR, "inquiry %.48s\n", + (char*)unit->inquiry+8); + qlock(&unit->ctl); + if(!unit->changed && unit->sectors){ + /* + * If there's a device specific routine it must + * provide all information pertaining to night geometry + * and the garscadden trains. + */ + if(unit->dev->ifc->rctl) + l += unit->dev->ifc->rctl(unit, p+l, READSTR-l); + else + l += snprint(p+l, READSTR-l, + "geometry %ld %ld\n", + unit->sectors, unit->secsize); + pp = unit->part; + for(i = 0; i < SDnpart; i++){ + if(pp->valid) + l += snprint(p+l, READSTR-l, + "part %.*s %lud %lud\n", + NAMELEN, pp->name, + pp->start, pp->end); + pp++; + } } - } - - /* - * parse partition table. - */ - if(n > 0 && strncmp(line[0], MAGIC, sizeof(MAGIC)-1) == 0) { - for(i = 1; i < n; i++) { - if(getfields(line[i], field, nelem(field), 1, " ") != 3) - break; - if(p >= &d->table[Npart]) - break; - strncpy(p->name, field[0], NAMELEN); - p->beg = strtoul(field[1], 0, 0); - p->end = strtoul(field[2], 0, 0); - if(p->beg > p->end || p->beg >= d->table[0].end) - break; - p++; + qunlock(&unit->ctl); + l = readstr(offset, a, n, p); + free(p); + return l; + case Qraw: + unit = sdunit[UNIT(c->qid)]; + if(canqlock(&unit->raw)){ + qunlock(&unit->raw); + error(Ebadusefd); + } + if(unit->pid != up->pid) + error(Eperm); + if(unit->state == Rawdata){ + unit->state = Rawstatus; + return sdrio(unit->req, a, n); } + else if(unit->state == Rawstatus){ + status = unit->req->status; + unit->pid = 0; + unit->state = Rawcmd; + free(unit->req); + unit->req = nil; + qunlock(&unit->raw); + return readnum(0, a, n, status, NUMSIZE); + } + break; + case Qpart: + return sdbio(c, 0, a, n, off); } - d->npart = p - d->table; - scsifree(b); - - d->partok = 1; - return STok; + return 0; } static long -sdio(Chan *c, int write, char *a, ulong len, vlong off) +sdwrite(Chan *c, void *a, long n, vlong off) { - Disk *d; - Part *p; - uchar *b; - ulong block, n, max, x; - ulong offset; - - d = &disk[DRIVE(c->qid)]; - p = &d->table[PART(c->qid)]; - - block = (off / d->bsize) + p->beg; - n = (off + len + d->bsize - 1) / d->bsize + p->beg - block; - max = SCSImaxxfer / d->bsize; - if(n > max) - n = max; - if(block + n > p->end) - n = p->end - block; - if(block >= p->end || n == 0) - return 0; + Cmdbuf *cb; + SDreq *req; + SDunit *unit; + ulong end, start; - b = scsialloc(n*d->bsize); - if(b == 0) - return scsierrstr(STnomem); - - offset = off % d->bsize; - if(write) { - if(offset || len % d->bsize) { - x = scsibio(d->t, d->lun, SCSIread, b, n, d->bsize, block); - if(x < 0){ - len = -1; - goto buggery; + switch(TYPE(c->qid)){ + default: + error(Eperm); + case Qctl: + cb = parsecmd(a, n); + unit = sdunit[UNIT(c->qid)]; + + qlock(&unit->ctl); + if(waserror()){ + qunlock(&unit->ctl); + free(cb); + nexterror(); + } + if(unit->changed) + error(Eio); + + if(cb->nf < 1) + error(Ebadctl); + if(strcmp(cb->f[0], "part") == 0){ + if(cb->nf != 4 || unit->npart >= SDnpart) + error(Ebadctl); + if(unit->sectors == 0 && !sdinitpart(unit)) + error(Eio); + start = strtoul(cb->f[2], 0, 0); + end = strtoul(cb->f[3], 0, 0); + sdaddpart(unit, cb->f[1], start, end); + } + else if(strcmp(cb->f[0], "delpart") == 0){ + if(cb->nf != 2 || unit->part == nil) + error(Ebadctl); + sddelpart(unit, cb->f[1]); + } + else if(unit->dev->ifc->wctl) + unit->dev->ifc->wctl(unit, cb); + else + error(Ebadctl); + qunlock(&unit->ctl); + poperror(); + free(cb); + break; + case Qraw: + unit = sdunit[UNIT(c->qid)]; + if(canqlock(&unit->raw)){ + if(unit->state != Rawcmd){ + qunlock(&unit->raw); + error(Ebadusefd); + } + if(n < 6 || n > sizeof(req->cmd)){ + qunlock(&unit->raw); + error(Ebadarg); } - if(x < n * d->bsize) { - n = x / d->bsize; - x = n * d->bsize - offset; - if(len > x) - len = x; + if((req = malloc(sizeof(SDreq))) == nil){ + qunlock(&unit->raw); + error(Enomem); } + req->unit = unit; + memmove(req->cmd, a, n); + req->clen = n; + req->flags = SDnosense; + req->status = ~0; + + unit->req = req; + unit->pid = up->pid; + unit->state = Rawdata; } - memmove(b + offset, a, len); - x = scsibio(d->t, d->lun, SCSIwrite, b, n, d->bsize, block); - if(x < 0){ - len = -1; - goto buggery; + else{ + if(unit->pid != up->pid) + error(Eperm); + if(unit->state != Rawdata) + error(Ebadusefd); + unit->state = Rawstatus; + + unit->req->write = 1; + return sdrio(unit->req, a, n); } - if(x < offset) - len = 0; - else - if(len > x - offset) - len = x - offset; + break; + case Qpart: + return sdbio(c, 1, a, n, off); } - else { - x = scsibio(d->t, d->lun, SCSIread, b, n, d->bsize, block); - if(x < 0){ - len = -1; - goto buggery; - } - if(x < offset) - len = 0; - else - if(len > x - offset) - len = x - offset; - memmove(a, b+offset, len); + + return n; +} + +static void +sdwstat(Chan* c, char* dp) +{ + Dir d; + SDpart *pp; + SDunit *unit; + + if((c->qid.path & CHDIR) || TYPE(c->qid) != Qpart) + error(Eperm); + + unit = sdunit[UNIT(c->qid)]; + qlock(&unit->ctl); + if(waserror()){ + qunlock(&unit->ctl); + nexterror(); } -buggery: - scsifree(b); - return len; + if(unit->changed) + error(Enonexist); + pp = &unit->part[PART(c->qid)]; + if(!pp->valid) + error(Enonexist); + if(strcmp(up->user, pp->user) && !iseve()) + error(Eperm); + + convM2D(dp, &d); + strncpy(pp->user, d.uid, NAMELEN); + pp->perm = d.mode&0777; + + qunlock(&unit->ctl); + poperror(); } + +Dev sddevtab = { + 'S', + "sd", + + sdreset, + devinit, + sdattach, + sdclone, + sdwalk, + sdstat, + sdopen, + devcreate, + sdclose, + sdread, + devbread, + sdwrite, + devbwrite, + devremove, + sdwstat, +}; diff --git a/port/portdat.h b/port/portdat.h index accdd19707953c35c05747b989e68688cae913bc..67bca5e7eef9901c87742e141c804f51f61372da 100644 --- a/port/portdat.h +++ b/port/portdat.h @@ -682,29 +682,6 @@ enum AUTHLEN = 8, }; -/* Things scsi */ -enum -{ - STnomem =-4, /* buffer allocation failed */ - STtimeout =-3, /* bus timeout */ - STownid =-2, /* playing with myself */ - STharderr =-1, /* controller error of some kind */ - STok = 0, /* good */ - STcheck = 0x02, /* check condition */ - STcondmet = 0x04, /* condition met/good */ - STbusy = 0x08, /* busy */ - STintok = 0x10, /* intermediate/good */ - STintcondmet = 0x14, /* intermediate/condition met/good */ - STresconf = 0x18, /* reservation conflict */ - STterminated = 0x22, /* command terminated */ - STqfull = 0x28, /* queue full */ - - SCSIread = 0, - SCSIwrite, - - SCSImaxxfer = 2048*1024, -}; - enum { LRESPROF = 3, diff --git a/port/portfns.h b/port/portfns.h index 85b3984aca56bb5904b5bb68adb8f9116566576e..025338d6d862bff99b29ff068ce861ef079c101b 100644 --- a/port/portfns.h +++ b/port/portfns.h @@ -127,7 +127,6 @@ long ibrk(ulong, int); void ilock(Lock*); void iunlock(Lock*); int incref(Ref*); -void initscsi(void); void initseg(void); void isdir(Chan*); int iseve(void); @@ -258,7 +257,6 @@ void ready(Proc*); void relocateseg(Segment*, ulong); void renameuser(char*, char*); void resched(char*); -void resetscsi(void); void resrcwait(char*); int return0(void*); void rlock(RWlock*); @@ -272,18 +270,6 @@ void sched(void); void scheddump(void); void schedinit(void); int screenbits(void); -#define scsialloc(n) mallocz((n)+512, 0) -int scsibio(Target*, int, int, void*, long, long, long); -int scsicap(Target*, int, ulong*, ulong*); -int scsierrstr(int); -int scsiexec(Target*, int, uchar*, int, void*, int*); -#define scsifree(p) free(p) -int scsiinquiry(Target*, int, void*, int*); -int scsiinv(int, int*, Target**, uchar**, char*); -int scsireqsense(Target*, int, void*, int*, int); -int scsistart(Target*, int, int); -int scsitest(Target*, int); -Target* scsiunit(int, int); long seconds(void); ulong segattach(Proc*, ulong, char *, ulong, ulong); void segclock(ulong); diff --git a/port/sd.h b/port/sd.h new file mode 100644 index 0000000000000000000000000000000000000000..8d4c68b4eb5cfdd435b1c9c6af309b5f6f17017e --- /dev/null +++ b/port/sd.h @@ -0,0 +1,115 @@ +/* + * Storage Device. + */ +typedef struct SDev SDev; +typedef struct SDifc SDifc; +typedef struct SDpart SDpart; +typedef struct SDreq SDreq; +typedef struct SDunit SDunit; + +typedef struct SDpart { + ulong start; + ulong end; + char name[NAMELEN]; + char user[NAMELEN]; + ulong perm; + int valid; + int nopen; /* of this partition */ +} SDpart; + +typedef struct SDunit { + SDev* dev; + int subno; + uchar inquiry[256]; /* format follows SCSI spec */ + char name[NAMELEN]; + Rendez rendez; + + QLock ctl; + ulong sectors; + ulong secsize; + SDpart* part; + int npart; /* of valid partitions */ + int nopen; /* of partitions on this unit */ + int changed; + + QLock raw; + int state; + ulong pid; + SDreq* req; +} SDunit; + +typedef struct SDev { + SDifc* ifc; /* pnp/legacy */ + void *ctlr; + int idno; + char name[NAMELEN]; + int index; /* into unit space */ + int nunit; + SDev* next; + + QLock; /* enable/disable */ + int enabled; +} SDev; + +typedef struct SDifc { + char* name; + + SDev* (*pnp)(void); + SDev* (*legacy)(int, int); + SDev* (*id)(SDev*); + int (*enable)(SDev*); + int (*disable)(SDev*); + + int (*verify)(SDunit*); + int (*online)(SDunit*); + int (*rio)(SDreq*); + int (*rctl)(SDunit*, char*, int); + int (*wctl)(SDunit*, Cmdbuf*); + + long (*bio)(SDunit*, int, int, void*, long, long); +} SDifc; + +typedef struct SDreq { + SDunit* unit; + int lun; + int write; + uchar cmd[16]; + int clen; + void* data; + int dlen; + + int flags; + + int status; + long rlen; + uchar sense[256]; +} SDreq; + +enum { + SDnosense = 0x00000001, + SDvalidsense = 0x00010000, +}; + +enum { + SDmalloc = -4, + SDeio = -3, + SDtimeout = -2, + SDnostatus = -1, + + SDok = 0, + + SDcheck = 0x02, /* check condition */ + SDbusy = 0x08, /* busy */ + + SDmaxio = 2048*1024, + SDnpart = 16, +}; + +#define sdmalloc(n) malloc(n) +#define sdfree(p) free(p) + +/* sdscsi.c */ +extern int scsiverify(SDunit*); +extern int scsionline(SDunit*); +extern long scsibio(SDunit*, int, int, void*, long, long); +extern SDev* scsiid(SDev*, SDifc*);