diff options
author | Taru Karttunen <taruti@taruti.net> | 2011-03-30 15:46:40 +0300 |
---|---|---|
committer | Taru Karttunen <taruti@taruti.net> | 2011-03-30 15:46:40 +0300 |
commit | e5888a1ffdae813d7575f5fb02275c6bb07e5199 (patch) | |
tree | d8d51eac403f07814b9e936eed0c9a79195e2450 /sys/src/9/pc/sdmv50xx.c |
Import sources from 2011-03-30 iso image
Diffstat (limited to 'sys/src/9/pc/sdmv50xx.c')
-rwxr-xr-x | sys/src/9/pc/sdmv50xx.c | 1762 |
1 files changed, 1762 insertions, 0 deletions
diff --git a/sys/src/9/pc/sdmv50xx.c b/sys/src/9/pc/sdmv50xx.c new file mode 100755 index 000000000..e9ba87c47 --- /dev/null +++ b/sys/src/9/pc/sdmv50xx.c @@ -0,0 +1,1762 @@ +/* + * Marvell 88SX[56]0[48][01] fileserver Serial ATA (SATA) driver + * + * See MV-S101357-00 Rev B Marvell PCI/PCI-X to 8-Port/4-Port + * SATA Host Controller, ATA-5 ANSI NCITS 340-2000. + * + * This is a heavily-modified version (by Coraid) of a heavily-modified + * version (from The Labs) of a driver written by Coraid, Inc. + * The original copyright notice appears at the end of this file. + */ + +#include "u.h" +#include "../port/lib.h" +#include "mem.h" +#include "dat.h" +#include "fns.h" +#include "io.h" +#include "../port/error.h" + +#include "../port/sd.h" + +#define dprint if(!0){}else iprint +#define idprint if(!0){}else iprint +#define ioprint if(!0){}else iprint + +enum { + NCtlr = 4, + NCtlrdrv = 8, + NDrive = NCtlr*NCtlrdrv, + + Read = 0, + Write, + + Coraiddebug = 0, +}; + +enum { + SrbRing = 32, + + /* Addresses of ATA register */ + ARcmd = 027, + ARdev = 026, + ARerr = 021, + ARfea = 021, + ARlba2 = 025, + ARlba1 = 024, + ARlba0 = 023, + ARseccnt = 022, + ARstat = 027, + + ATAerr = (1<<0), + ATAdrq = (1<<3), + ATAdf = (1<<5), + ATAdrdy = (1<<6), + ATAbusy = (1<<7), + ATAabort = (1<<2), + ATAobs = (1<<1 | 1<<2 | 1<<4), + ATAeIEN = (1<<1), + ATAsrst = (1<<2), + ATAhob = (1<<7), + ATAbad = (ATAbusy|ATAdf|ATAdrq|ATAerr), + + SFdone = (1<<0), + SFerror = (1<<1), + + SRBident = 0, + SRBread, + SRBwrite, + SRBsmart, + + SRBnodata = 0, + SRBdatain, + SRBdataout, + + RQread = 1, /* data coming IN from device */ + + PRDeot = (1<<15), + + /* EDMA interrupt error cause register */ + + ePrtDataErr = (1<<0), + ePrtPRDErr = (1<<1), + eDevErr = (1<<2), + eDevDis = (1<<3), + eDevCon = (1<<4), + eOverrun = (1<<5), + eUnderrun = (1<<6), + eSelfDis = (1<<8), + ePrtCRQBErr = (1<<9), + ePrtCRPBErr = (1<<10), + ePrtIntErr = (1<<11), + eIORdyErr = (1<<12), + + /* flags for sata 2 version */ + eSelfDis2 = (1<<7), + SerrInt = (1<<5), + + /* EDMA Command Register */ + + eEnEDMA = (1<<0), + eDsEDMA = (1<<1), + eAtaRst = (1<<2), + + /* Interrupt mask for errors we care about */ + IEM = (eDevDis | eDevCon | eSelfDis), + IEM2 = (eDevDis | eDevCon | eSelfDis2), + + /* drive states */ + Dnull = 0, + Dnew, + Dready, + Derror, + Dmissing, + Dreset, + Dlast, + + /* drive flags */ + Dext = (1<<0), /* use ext commands */ + Dpio = (1<<1), /* doing pio */ + Dwanted = (1<<2), /* someone wants an srb entry */ + Dedma = (1<<3), /* device in edma mode */ + Dpiowant = (1<<4), /* some wants to use the pio mode */ + + /* phyerrata magic crap */ + Mpreamp = 0x7e0, + Dpreamp = 0x720, + + REV60X1B2 = 0x7, + REV60X1C0 = 0x9, + +}; + +static char* diskstates[Dlast] = { + "null", + "new", + "ready", + "error", + "missing", + "reset", +}; + +extern SDifc sdmv50xxifc; + +typedef struct Arb Arb; +typedef struct Bridge Bridge; +typedef struct Chip Chip; +typedef struct Ctlr Ctlr; +typedef struct Drive Drive; +typedef struct Edma Edma; +typedef struct Prd Prd; +typedef struct Rx Rx; +typedef struct Srb Srb; +typedef struct Tx Tx; + +/* + * there are 4 drives per chip. thus an 8-port + * card has two chips. + */ +struct Chip +{ + Arb *arb; + Edma *edma; +}; + +enum { + DMautoneg, + DMsatai, + DMsataii, +}; + +struct Drive +{ + Lock; + + Ctlr *ctlr; + SDunit *unit; + char name[10]; + ulong magic; + + Bridge *bridge; + Edma *edma; + Chip *chip; + int chipx; + + int mediachange; + int state; + int flag; + uvlong sectors; + ulong pm2; /* phymode 2 init state */ + ulong intick; /* check for hung western digital drives. */ + int wait; + int mode; /* DMautoneg, satai or sataii. */ + + char serial[20+1]; + char firmware[8+1]; + char model[40+1]; + + ushort info[256]; + + Srb *srb[SrbRing-1]; + int nsrb; + Prd *prd; + Tx *tx; + Rx *rx; + + Srb *srbhead; + Srb *srbtail; + int driveno; /* ctlr*NCtlrdrv + unit */ +}; + +struct Ctlr +{ + Lock; + + int irq; + int tbdf; + int rid; + ulong magic; + int enabled; + int type; + SDev *sdev; + Pcidev *pcidev; + + uchar *mmio; + ulong *lmmio; + Chip chip[2]; + int nchip; + Drive drive[NCtlrdrv]; + int ndrive; +}; + +struct Srb /* request buffer */ +{ + Lock; + Rendez; + Srb *next; + + Drive *drive; + uvlong blockno; + int count; + int req; + int flag; + uchar *data; + + uchar cmd; + uchar lba[6]; + uchar sectors; + int sta; + int err; +}; + +/* + * Memory-mapped I/O registers in many forms. + */ +struct Bridge /* memory-mapped per-Drive registers */ +{ + ulong status; + ulong serror; + ulong sctrl; + ulong phyctrl; + ulong phymode3; + ulong phymode4; + uchar fill0[0x14]; + ulong phymode1; + ulong phymode2; + char fill1[8]; + ulong ctrl; + char fill2[0x34]; + ulong phymode; + char fill3[0x88]; +}; /* length must be 0x100 */ + +struct Arb /* memory-mapped per-Chip registers */ +{ + ulong config; /* satahc configuration register (sata2 only) */ + ulong rqop; /* request queue out-pointer */ + ulong rqip; /* response queue in pointer */ + ulong ict; /* inerrupt caolescing threshold */ + ulong itt; /* interrupt timer threshold */ + ulong ic; /* interrupt cause */ + ulong btc; /* bridges test control */ + ulong bts; /* bridges test status */ + ulong bpc; /* bridges pin configuration */ + char fill1[0xdc]; + Bridge bridge[4]; +}; + +struct Edma /* memory-mapped per-Drive DMA-related registers */ +{ + ulong config; /* configuration register */ + ulong timer; + ulong iec; /* interrupt error cause */ + ulong iem; /* interrupt error mask */ + + ulong txbasehi; /* request queue base address high */ + ulong txi; /* request queue in pointer */ + ulong txo; /* request queue out pointer */ + + ulong rxbasehi; /* response queue base address high */ + ulong rxi; /* response queue in pointer */ + ulong rxo; /* response queue out pointer */ + + ulong ctl; /* command register */ + ulong testctl; /* test control */ + ulong status; + ulong iordyto; /* IORDY timeout */ + char fill[0x18]; + ulong sataconfig; /* sata 2 */ + char fill[0xac]; + ushort pio; /* data register */ + char pad0[2]; + uchar err; /* features and error */ + char pad1[3]; + uchar seccnt; /* sector count */ + char pad2[3]; + uchar lba0; + char pad3[3]; + uchar lba1; + char pad4[3]; + uchar lba2; + char pad5[3]; + uchar lba3; + char pad6[3]; + uchar cmdstat; /* cmd/status */ + char pad7[3]; + uchar altstat; /* alternate status */ + uchar fill2[0x1df]; + Bridge port; + char fill3[0x1c00]; /* pad to 0x2000 bytes */ +}; + +/* + * Memory structures shared with card. + */ +struct Prd /* physical region descriptor */ +{ + ulong pa; /* byte address of physical memory */ + ushort count; /* byte count (bit0 must be 0) */ + ushort flag; + ulong zero; /* high long of 64 bit address */ + ulong reserved; +}; + +struct Tx /* command request block */ +{ + ulong prdpa; /* physical region descriptor table structures */ + ulong zero; /* must be zero (high long of prd address) */ + ushort flag; /* control flags */ + ushort regs[11]; +}; + +struct Rx /* command response block */ +{ + ushort cid; /* cID of response */ + uchar cEdmaSts; /* EDMA status */ + uchar cDevSts; /* status from disk */ + ulong ts; /* time stamp */ +}; + +static Drive *mvsatadrive[NDrive]; +static int nmvsatadrive; + +/* + * Little-endian parsing for drive data. + */ +static ushort +lhgets(void *p) +{ + uchar *a = p; + return ((ushort) a[1] << 8) | a[0]; +} + +static ulong +lhgetl(void *p) +{ + uchar *a = p; + return ((ulong) lhgets(a+2) << 16) | lhgets(a); +} + +static uvlong +lhgetv(void *p) +{ + uchar *a = p; + return ((uvlong) lhgetl(a+4) << 32) | lhgetl(a); +} + +static void +idmove(char *p, ushort *a, int n) +{ + char *op; + int i; + + op = p; + for(i=0; i<n/2; i++){ + *p++ = a[i]>>8; + *p++ = a[i]; + } + while(p>op && *--p == ' ') + *p = 0; +} + +/* + * Request buffers. + */ +struct +{ + Lock; + Srb *freechain; + int nalloc; +} srblist; + +static Srb* +allocsrb(void) +{ + Srb *p; + + ilock(&srblist); + if((p = srblist.freechain) == nil){ + srblist.nalloc++; + iunlock(&srblist); + p = smalloc(sizeof *p); + }else{ + srblist.freechain = p->next; + iunlock(&srblist); + } + return p; +} + +static void +freesrb(Srb *p) +{ + ilock(&srblist); + p->next = srblist.freechain; + srblist.freechain = p; + iunlock(&srblist); +} + +/* + * Wait for a byte to be a particular value. + */ +static int +satawait(uchar *p, uchar mask, uchar v, int ms) +{ + int i; + + for(i=0; i<ms && (*p & mask) != v; i++) + microdelay(1000); + return (*p & mask) == v; +} + +/* + * Drive initialization + */ +/* unmask in the pci registers err done */ +static void +unmask(ulong *mmio, int port, int coal) +{ + port &= 7; + if(coal) + coal = 1; + if (port < 4) + mmio[0x1d64/4] |= (3 << (((port&3)*2)) | (coal<<8)); + else + mmio[0x1d64/4] |= (3 << (((port&3)*2+9)) | (coal<<17)); +} + +static void +mask(ulong *mmio, int port, int coal) +{ + port &= 7; + if(coal) + coal = 1; + if (port < 4) + mmio[0x1d64/4] &= ~(3 << (((port&3)*2)) | (coal<<8)); + else + mmio[0x1d64/4] &= ~(3 << (((port&3)*2+9)) | (coal<<17)); +} + +/* I give up, marvell. You win. */ +static void +phyerrata(Drive *d) +{ + ulong n, m; + enum { BadAutoCal = 0xf << 26, }; + + if (d->ctlr->type == 1) + return; + microdelay(200); + n = d->bridge->phymode2; + while ((n & BadAutoCal) == BadAutoCal) { + dprint("%s: badautocal\n", d->unit->name); + n &= ~(1<<16); + n |= (1<<31); + d->bridge->phymode2 = n; + microdelay(200); + d->bridge->phymode2 &= ~((1<<16) | (1<<31)); + microdelay(200); + n = d->bridge->phymode2; + } + n &= ~(1<<31); + d->bridge->phymode2 = n; + microdelay(200); + + /* abra cadabra! (random magic) */ + m = d->bridge->phymode3; + m &= ~0x7f800000; + m |= 0x2a800000; + d->bridge->phymode3 = m; + + /* fix phy mode 4 */ + m = d->bridge->phymode3; + n = d->bridge->phymode4; + n &= ~(1<<1); + n |= 1; + switch(d->ctlr->rid){ + case REV60X1B2: + default: + d->bridge->phymode4 = n; + d->bridge->phymode3 = m; + break; + case REV60X1C0: + d->bridge->phymode4 = n; + break; + } + + /* revert values of pre-emphasis and signal amps to the saved ones */ + n = d->bridge->phymode2; + n &= ~Mpreamp; + n |= d->pm2; + n &= ~(1<<16); + d->bridge->phymode2 = n; +} + +static void +edmacleanout(Drive *d) +{ + int i; + Srb *srb; + + for(i=0; i<nelem(d->srb); i++){ + if(srb = d->srb[i]){ + d->srb[i] = nil; + d->nsrb--; + srb->flag |= SFerror|SFdone; + wakeup(srb); + } + } + while(srb = d->srbhead){ + d->srbhead = srb->next; + srb->flag |= SFerror|SFdone; + wakeup(srb); + } +} + +static void +resetdisk(Drive *d) +{ + ulong n; + + d->sectors = 0; + d->unit->sectors = 0; + if (d->ctlr->type == 2) { + /* + * without bit 8 we can boot without disks, but + * inserted disks will never appear. :-X + */ + n = d->edma->sataconfig; + n &= 0xff; + n |= 0x9b1100; + d->edma->sataconfig = n; + n = d->edma->sataconfig; /* flush */ + USED(n); + } + d->edma->ctl = eDsEDMA; + microdelay(1); + d->edma->ctl = eAtaRst; + microdelay(25); + d->edma->ctl = 0; + if (satawait((uchar *)&d->edma->ctl, eEnEDMA, 0, 3*1000) == 0) + print("%s: eEnEDMA never cleared on reset\n", d->unit->name); + edmacleanout(d); + phyerrata(d); + d->bridge->sctrl = 0x301 | (d->mode << 4); + d->state = Dmissing; +} + +static void +edmainit(Drive *d) +{ + int i; + + if(d->tx != nil) + return; + + d->tx = xspanalloc(32*sizeof(Tx), 1024, 0); + d->rx = xspanalloc(32*sizeof(Rx), 256, 0); + d->prd = xspanalloc(32*sizeof(Prd), 32, 0); + for(i = 0; i < 32; i++) + d->tx[i].prdpa = PADDR(&d->prd[i]); + coherence(); +} + +static int +configdrive(Ctlr *ctlr, Drive *d, SDunit *unit) +{ + dprint("%s: configdrive\n", unit->name); + if(d->driveno < 0) + panic("mv50xx: configdrive: unset driveno\n"); + d->unit = unit; + edmainit(d); + d->mode = DMsatai; + if(d->ctlr->type == 1){ + d->edma->iem = IEM; + d->bridge = &d->chip->arb->bridge[d->chipx]; + }else{ + d->edma->iem = IEM2; + d->bridge = &d->chip->edma[d->chipx].port; + d->edma->iem = ~(1<<6); + d->pm2 = Dpreamp; + if(d->ctlr->lmmio[0x180d8/4] & 1) + d->pm2 = d->bridge->phymode2 & Mpreamp; + } + resetdisk(d); + unmask(ctlr->lmmio, d->driveno, 0); + delay(100); + if(d->bridge->status){ + dprint("%s: configdrive: found drive %lx\n", unit->name, d->bridge->status); + return 0; + } + return -1; +} + +static int +enabledrive(Drive *d) +{ + Edma *edma; + + dprint("%s: enabledrive..", d->unit->name); + + if((d->bridge->status & 0xf) != 3){ + dprint("%s: not present\n", d->unit->name); + d->state = Dmissing; + return -1; + } + edma = d->edma; + if(satawait(&edma->cmdstat, ATAbusy, 0, 5*1000) == 0){ + dprint("%s: busy timeout\n", d->unit->name); + d->state = Dmissing; + return -1; + } + edma->iec = 0; + d->chip->arb->ic &= ~(0x101 << d->chipx); + edma->config = 0x51f; + if (d->ctlr->type == 2) + edma->config |= 7<<11; + edma->txi = PADDR(d->tx); + edma->txo = (ulong)d->tx & 0x3e0; + edma->rxi = (ulong)d->rx & 0xf8; + edma->rxo = PADDR(d->rx); + edma->ctl |= 1; /* enable dma */ + + if(d->bridge->status = 0x113){ + dprint("%s: new\n", d->unit->name); + d->state = Dnew; + }else + print("%s: status not forced (should be okay)\n", d->unit->name); + return 0; +} + +static void +disabledrive(Drive *d) +{ + int i; + ulong *r; + + dprint("%s: disabledrive\n", d->unit->name); + + if(d->tx == nil) /* never enabled */ + return; + + d->edma->ctl = 0; + d->edma->iem = 0; + + r = (ulong*)(d->ctlr->mmio + 0x1d64); + i = d->chipx; + if(d->chipx < 4) + *r &= ~(3 << (i*2)); + else + *r |= ~(3 << (i*2+9)); +} + +static int +setudmamode(Drive *d, uchar mode) +{ + Edma *edma; + + dprint("%s: setudmamode %d\n", d->unit->name, mode); + + edma = d->edma; + if (edma == nil) { + iprint("setudamode(m%d): zero d->edma\m", d->driveno); + return 0; + } + if(satawait(&edma->cmdstat, ~ATAobs, ATAdrdy, 9*1000) == 0){ + iprint("%s: cmdstat 0x%.2ux ready timeout\n", d->unit->name, edma->cmdstat); + return 0; + } + edma->altstat = ATAeIEN; + edma->err = 3; + edma->seccnt = 0x40 | mode; + edma->cmdstat = 0xef; + microdelay(1); + if(satawait(&edma->cmdstat, ATAbusy, 0, 5*1000) == 0){ + iprint("%s: cmdstat 0x%.2ux busy timeout\n", d->unit->name, edma->cmdstat); + return 0; + } + return 1; +} + +static int +identifydrive(Drive *d) +{ + int i; + ushort *id; + Edma *edma; + SDunit *unit; + + dprint("%s: identifydrive\n", d->unit->name); + + if(setudmamode(d, 5) == 0) /* do all SATA support 5? */ + goto Error; + + id = d->info; + memset(d->info, 0, sizeof d->info); + edma = d->edma; + if(satawait(&edma->cmdstat, ~ATAobs, ATAdrdy, 5*1000) == 0) + goto Error; + + edma->altstat = ATAeIEN; /* no interrupts */ + edma->cmdstat = 0xec; + microdelay(1); + if(satawait(&edma->cmdstat, ATAbusy, 0, 5*1000) == 0) + goto Error; + for(i = 0; i < 256; i++) + id[i] = edma->pio; + if(edma->cmdstat & ATAbad) + goto Error; + i = lhgets(id+83) | lhgets(id+86); + if(i & (1<<10)){ + d->flag |= Dext; + d->sectors = lhgetv(id+100); + }else{ + d->flag &= ~Dext; + d->sectors = lhgetl(id+60); + } + idmove(d->serial, id+10, 20); + idmove(d->firmware, id+23, 8); + idmove(d->model, id+27, 40); + + unit = d->unit; + memset(unit->inquiry, 0, sizeof unit->inquiry); + unit->inquiry[2] = 2; + unit->inquiry[3] = 2; + unit->inquiry[4] = sizeof(unit->inquiry)-4; + idmove((char*)unit->inquiry+8, id+27, 40); + + if(enabledrive(d) == 0) { + d->state = Dready; + d->mediachange = 1; + idprint("%s: LLBA %lld sectors\n", d->unit->name, d->sectors); + } else + d->state = Derror; + if(d->state == Dready) + return 0; + return -1; +Error: + dprint("error..."); + d->state = Derror; + return -1; +} + +/* p. 163: + M recovered error + P protocol error + N PhyRdy change + W CommWake + B 8-to-10 encoding error + D disparity error + C crc error + H handshake error + S link sequence error + T transport state transition error + F unrecognized fis type + X device changed +*/ + +static char stab[] = { +[1] 'M', +[10] 'P', +[16] 'N', +[18] 'W', 'B', 'D', 'C', 'H', 'S', 'T', 'F', 'X' +}; +static ulong sbad = (7<<20)|(3<<23); + +static void +serrdecode(ulong r, char *s, char *e) +{ + int i; + + e -= 3; + for(i = 0; i < nelem(stab) && s < e; i++){ + if((r&(1<<i)) && stab[i]){ + *s++ = stab[i]; + if(sbad&(1<<i)) + *s++ = '*'; + } + } + *s = 0; +} + +char *iectab[] = { + "ePrtDataErr", + "ePrtPRDErr", + "eDevErr", + "eDevDis", + "eDevCon", + "SerrInt", + "eUnderrun", + "eSelfDis2", + "eSelfDis", + "ePrtCRQBErr", + "ePrtCRPBErr", + "ePrtIntErr", + "eIORdyErr", +}; + +static char* +iecdecode(ulong cause) +{ + int i; + + for(i = 0; i < nelem(iectab); i++) + if(cause&(1<<i)) + return iectab[i]; + return ""; +} + +enum{ + Cerror = ePrtDataErr|ePrtPRDErr|eDevErr|eSelfDis2|ePrtCRPBErr|ePrtIntErr, +}; + +static void +updatedrive(Drive *d) +{ + int x; + ulong cause; + Edma *edma; + char buf[32+4+1]; + + edma = d->edma; + if((edma->ctl&eEnEDMA) == 0){ + /* FEr SATA#4 40xx */ + x = d->edma->cmdstat; + USED(x); + } + cause = edma->iec; + if(cause == 0) + return; + dprint("%s: cause %08ulx [%s]\n", d->unit->name, cause, iecdecode(cause)); + if(cause & eDevCon) + d->state = Dnew; + if(cause&eDevDis && d->state == Dready) + iprint("%s: pulled: st=%08ulx\n", d->unit->name, cause); + switch(d->ctlr->type){ + case 1: + if(cause&eSelfDis) + d->state = Derror; + break; + case 2: + if(cause&Cerror) + d->state = Derror; + if(cause&SerrInt){ + serrdecode(d->bridge->serror, buf, buf+sizeof buf); + dprint("%s: serror %08ulx [%s]\n", d->unit->name, (ulong)d->bridge->serror, buf); + d->bridge->serror = d->bridge->serror; + } + } + edma->iec = ~cause; +} + +/* + * Requests + */ +static Srb* +srbrw(int req, Drive *d, uchar *data, uint sectors, uvlong lba) +{ + int i; + Srb *srb; + static uchar cmd[2][2] = { 0xC8, 0x25, 0xCA, 0x35 }; + + srb = allocsrb(); + srb->req = req; + srb->drive = d; + srb->blockno = lba; + srb->sectors = sectors; + srb->count = sectors*512; + srb->flag = 0; + srb->data = data; + + for(i=0; i<6; i++) + srb->lba[i] = lba >> (8*i); + srb->cmd = cmd[srb->req!=SRBread][(d->flag&Dext)!=0]; + return srb; +} + +static uintptr +advance(uintptr pa, int shift) +{ + int n, mask; + + mask = 0x1F<<shift; + n = (pa & mask) + (1<<shift); + return (pa & ~mask) | (n & mask); +} + +#define CMD(r, v) (((r)<<8) | ((v)&0xFF)) +static void +mvsatarequest(ushort *cmd, Srb *srb, int ext) +{ + *cmd++ = CMD(ARseccnt, 0); + *cmd++ = CMD(ARseccnt, srb->sectors); + *cmd++ = CMD(ARfea, 0); + if(ext){ + *cmd++ = CMD(ARlba0, srb->lba[3]); + *cmd++ = CMD(ARlba0, srb->lba[0]); + *cmd++ = CMD(ARlba1, srb->lba[4]); + *cmd++ = CMD(ARlba1, srb->lba[1]); + *cmd++ = CMD(ARlba2, srb->lba[5]); + *cmd++ = CMD(ARlba2, srb->lba[2]); + *cmd++ = CMD(ARdev, 0xe0); + }else{ + *cmd++ = CMD(ARlba0, srb->lba[0]); + *cmd++ = CMD(ARlba1, srb->lba[1]); + *cmd++ = CMD(ARlba2, srb->lba[2]); + *cmd++ = CMD(ARdev, srb->lba[3] | 0xe0); + } + *cmd = CMD(ARcmd, srb->cmd) | (1<<15); +} + +static void +startsrb(Drive *d, Srb *srb) +{ + int i; + Edma *edma; + Prd *prd; + Tx *tx; + + if(d->nsrb >= nelem(d->srb)){ + srb->next = nil; + if(d->srbhead) + d->srbtail->next = srb; + else + d->srbhead = srb; + d->srbtail = srb; + return; + } + + d->nsrb++; + for(i=0; i<nelem(d->srb); i++) + if(d->srb[i] == nil) + break; + if(i == nelem(d->srb)) + panic("sdmv50xx: no free srbs"); + d->intick = MACHP(0)->ticks; + d->srb[i] = srb; + edma = d->edma; + tx = (Tx*)KADDR(edma->txi); + tx->flag = (i<<1) | (srb->req == SRBread); + prd = KADDR(tx->prdpa); + prd->pa = PADDR(srb->data); + prd->count = srb->count; + prd->flag = PRDeot; + mvsatarequest(tx->regs, srb, d->flag&Dext); + coherence(); + edma->txi = advance(edma->txi, 5); + d->intick = MACHP(0)->ticks; +} + +enum{ + Rpidx = 0x1f<<3, +}; + +static void +completesrb(Drive *d) +{ + Edma *edma; + Rx *rx; + Srb *srb; + + edma = d->edma; + if((edma->ctl & eEnEDMA) == 0) + return; + + while((edma->rxo&Rpidx) != (edma->rxi&Rpidx)){ + rx = (Rx*)KADDR(edma->rxo); + if(srb = d->srb[rx->cid]){ + d->srb[rx->cid] = nil; + d->nsrb--; + if(rx->cDevSts & ATAbad) + srb->flag |= SFerror; + if (rx->cEdmaSts) + iprint("cEdmaSts: %02ux\n", rx->cEdmaSts); + srb->sta = rx->cDevSts; + srb->flag |= SFdone; + wakeup(srb); + }else + iprint("srb missing\n"); + edma->rxo = advance(edma->rxo, 3); + if(srb = d->srbhead){ + d->srbhead = srb->next; + startsrb(d, srb); + } + } +} + +static int +srbdone(void *v) +{ + Srb *srb; + + srb = v; + return srb->flag & SFdone; +} + +/* + * Interrupts + */ +static void +mv50interrupt(Ureg*, void *a) +{ + int i; + ulong cause; + Ctlr *ctlr; + Drive *drive; + + ctlr = a; + ilock(ctlr); + cause = ctlr->lmmio[0x1d60/4]; +// dprint("sd%c: mv50interrupt: 0x%lux\n", ctlr->sdev->idno, cause); + for(i=0; i<ctlr->ndrive; i++) + if(cause & (3<<(i*2+i/4))){ + drive = &ctlr->drive[i]; + if(drive->edma == 0) + continue; /* not ready yet. */ + ilock(drive); + updatedrive(drive); + while(ctlr->chip[i/4].arb->ic & (0x0101 << (i%4))){ + ctlr->chip[i/4].arb->ic = ~(0x101 << (i%4)); + completesrb(drive); + } + iunlock(drive); + } + iunlock(ctlr); +} + +enum{ + Nms = 256, + Midwait = 16*1024/Nms-1, + Mphywait = 512/Nms-1, +}; + +static void +westerndigitalhung(Drive *d) +{ + Edma *e; + + e = d->edma; + if(d->srb + && TK2MS(MACHP(0)->ticks-d->intick) > 5*1000 + && (e->rxo&Rpidx) == (e->rxi&Rpidx)){ + dprint("westerndigital drive hung; resetting\n"); + d->state = Dreset; + } +} + +static void +checkdrive(Drive *d, int i) +{ + static ulong s, olds[NCtlr*NCtlrdrv]; + char *name; + + ilock(d); + name = d->unit->name; + s = d->bridge->status; + if(s != olds[i]){ + dprint("%s: status: %08lx -> %08lx: %s\n", name, olds[i], s, diskstates[d->state]); + olds[i] = s; + } + /* westerndigitalhung(d); */ + switch(d->state){ + case Dnew: + case Dmissing: + switch(s){ + case 0x000: + break; + default: + dprint("%s: unknown state %8lx\n", name, s); + case 0x100: + if(++d->wait&Mphywait) + break; + reset: d->mode ^= 1; + dprint("%s: reset; new mode %d\n", name, d->mode); + resetdisk(d); + break; + case 0x123: + case 0x113: + s = d->edma->cmdstat; + if(s == 0x7f || (s&~ATAobs) != ATAdrdy){ + if((++d->wait&Midwait) == 0) + goto reset; + }else if(identifydrive(d) == -1) + goto reset; + } + break; + case Dready: + if(s != 0) + break; + iprint("%s: pulled: st=%08ulx\n", name, s); /* never happens */ + case Dreset: + case Derror: + dprint("%s reset: mode %d\n", name, d->mode); + resetdisk(d); + break; + } + iunlock(d); +} + +static void +satakproc(void*) +{ + int i; + + while(waserror()) + ; + + for(;;){ + tsleep(&up->sleep, return0, 0, Nms); + for(i = 0; i < nmvsatadrive; i++) + checkdrive(mvsatadrive[i], i); + } +} + +/* + * Device discovery + */ +static SDev* +mv50pnp(void) +{ + int i, nunit; + uchar *base; + ulong io, n, *mem; + Ctlr *ctlr; + Pcidev *p; + SDev *head, *tail, *sdev; + Drive *d; + static int ctlrno, done; + + dprint("mv50pnp\n"); + if(done++) + return nil; + + p = nil; + head = nil; + tail = nil; + while((p = pcimatch(p, 0x11ab, 0)) != nil){ + switch(p->did){ + case 0x5040: + case 0x5041: + case 0x5080: + case 0x5081: + case 0x6041: + case 0x6081: + break; + default: + print("mv50pnp: unknown did %ux ignored\n", (ushort)p->did); + continue; + } + if (ctlrno >= NCtlr) { + print("mv50pnp: too many controllers\n"); + break; + } + nunit = (p->did&0xf0) >> 4; + print("Marvell 88SX%ux: %d SATA-%s ports with%s flash\n", + (ushort)p->did, nunit, + ((p->did&0xf000)==0x6000? "II": "I"), + (p->did&1? "": "out")); + if((sdev = malloc(sizeof(SDev))) == nil) + continue; + if((ctlr = malloc(sizeof(Ctlr))) == nil){ + free(sdev); + continue; + } + memset(sdev, 0, sizeof *sdev); + memset(ctlr, 0, sizeof *ctlr); + + io = p->mem[0].bar & ~0x0F; + mem = (ulong*)vmap(io, p->mem[0].size); + if(mem == 0){ + print("sdmv50xx: address 0x%luX in use\n", io); + free(sdev); + free(ctlr); + continue; + } + ctlr->rid = p->rid; + + /* avert thine eyes! (what does this do?) */ + mem[0x104f0/4] = 0; + ctlr->type = (p->did >> 12) & 3; + if(ctlr->type == 1){ + n = mem[0xc00/4]; + n &= ~(3<<4); + mem[0xc00/4] = n; + } + + sdev->ifc = &sdmv50xxifc; + sdev->ctlr = ctlr; + sdev->nunit = nunit; + sdev->idno = 'E'; + ctlr->sdev = sdev; + ctlr->irq = p->intl; + ctlr->tbdf = p->tbdf; + ctlr->pcidev = p; + ctlr->lmmio = mem; + ctlr->mmio = (uchar*)mem; + ctlr->nchip = (nunit+3)/4; + ctlr->ndrive = nunit; + ctlr->enabled = 0; + for(i = 0; i < ctlr->nchip; i++){ + base = ctlr->mmio+0x20000+0x10000*i; + ctlr->chip[i].arb = (Arb*)base; + ctlr->chip[i].edma = (Edma*)(base + 0x2000); + } + for (i = 0; i < nunit; i++) { + d = &ctlr->drive[i]; + d->sectors = 0; + d->ctlr = ctlr; + d->driveno = ctlrno*NCtlrdrv + i; + d->chipx = i%4; + d->chip = &ctlr->chip[i/4]; + d->edma = &d->chip->edma[d->chipx]; + mvsatadrive[d->driveno] = d; + } + nmvsatadrive += nunit; + ctlrno++; + if(head) + tail->next = sdev; + else + head = sdev; + tail = sdev; + } + return head; +} + +/* + * Enable the controller. Each disk has its own interrupt mask, + * and those get enabled as the disks are brought online. + */ +static int +mv50enable(SDev *sdev) +{ + char name[32]; + Ctlr *ctlr; + + dprint("sd%c: enable\n", sdev->idno); + + ctlr = sdev->ctlr; + if (ctlr->enabled) + return 1; + snprint(name, sizeof name, "%s (%s)", sdev->name, sdev->ifc->name); + intrenable(ctlr->irq, mv50interrupt, ctlr, ctlr->tbdf, name); + ctlr->enabled = 1; + return 1; +} + +/* + * Disable the controller. + */ +static int +mv50disable(SDev *sdev) +{ + char name[32]; + int i; + Ctlr *ctlr; + Drive *drive; + + dprint("sd%c: disable\n", sdev->idno); + + ctlr = sdev->ctlr; + ilock(ctlr); + for(i=0; i<ctlr->sdev->nunit; i++){ + drive = &ctlr->drive[i]; + ilock(drive); + disabledrive(drive); + iunlock(drive); + } + iunlock(ctlr); + snprint(name, sizeof name, "%s (%s)", sdev->name, sdev->ifc->name); + intrdisable(ctlr->irq, mv50interrupt, ctlr, ctlr->tbdf, name); + return 0; +} + +/* + * Clean up all disk structures. Already disabled. + * Could keep count of number of allocated controllers + * and free the srblist when it drops to zero. + */ +static void +mv50clear(SDev *sdev) +{ + int i; + Ctlr *ctlr; + Drive *d; + + dprint("sd%c: clear\n", sdev->idno); + + ctlr = sdev->ctlr; + for(i=0; i<ctlr->ndrive; i++){ + d = &ctlr->drive[i]; + free(d->tx); + free(d->rx); + free(d->prd); + } + free(ctlr); +} + +/* + * Check that there is a disk or at least a hot swap bay in the drive. + */ +static int +mv50verify(SDunit *unit) +{ + Ctlr *ctlr; + Drive *drive; + int i; + + dprint("%s: verify\n", unit->name); + ctlr = unit->dev->ctlr; + drive = &ctlr->drive[unit->subno]; + ilock(ctlr); + ilock(drive); + i = configdrive(ctlr, drive, unit); + iunlock(drive); + iunlock(ctlr); + + /* + * If ctlr->type == 1, then the drives spin up whenever + * the controller feels like it; if ctlr->type != 1, then + * they spin up as a result of configdrive. + * + * If there is a drive in the slot, give it 1.5s to spin up + * before returning. There is a noticeable drag on the + * power supply when spinning up fifteen drives + * all at once (like in the Coraid enclosures). + */ + if(ctlr->type != 1 && i == 0){ + if(!waserror()){ + tsleep(&up->sleep, return0, 0, 1500); + poperror(); + } + } + return 1; +} + +/* + * Check whether the disk is online. + */ +static int +mv50online(SDunit *unit) +{ + Ctlr *ctlr; + Drive *d; + int r, s0; + static int once; + + if(once++ == 0) + kproc("mvsata", satakproc, 0); + + ctlr = unit->dev->ctlr; + d = &ctlr->drive[unit->subno]; + r = 0; + ilock(d); + s0 = d->state; + USED(s0); + if(d->state == Dnew) + identifydrive(d); + if(d->mediachange){ + idprint("%s: online: %s -> %s\n", unit->name, diskstates[s0], diskstates[d->state]); + r = 2; + unit->sectors = d->sectors; + unit->secsize = 512; + d->mediachange = 0; + } else if(d->state == Dready) + r = 1; + iunlock(d); + return r; +} + +/* + * Register dumps + */ +typedef struct Regs Regs; +struct Regs +{ + ulong offset; + char *name; +}; + +static Regs regsctlr[] = +{ + 0x0C28, "pci serr# mask", + 0x1D40, "pci err addr low", + 0x1D44, "pci err addr hi", + 0x1D48, "pci err attr", + 0x1D50, "pci err cmd", + 0x1D58, "pci intr cause", + 0x1D5C, "pci mask cause", + 0x1D60, "device micr", + 0x1D64, "device mimr", +}; + +static Regs regsarb[] = +{ + 0x0004, "arb rqop", + 0x0008, "arb rqip", + 0x000C, "arb ict", + 0x0010, "arb itt", + 0x0014, "arb ic", + 0x0018, "arb btc", + 0x001C, "arb bts", + 0x0020, "arb bpc", +}; + +static Regs regsbridge[] = +{ + 0x0000, "bridge status", + 0x0004, "bridge serror", + 0x0008, "bridge sctrl", + 0x000C, "bridge phyctrl", + 0x003C, "bridge ctrl", + 0x0074, "bridge phymode", +}; + +static Regs regsedma[] = +{ + 0x0000, "edma config", + 0x0004, "edma timer", + 0x0008, "edma iec", + 0x000C, "edma iem", + 0x0010, "edma txbasehi", + 0x0014, "edma txi", + 0x0018, "edma txo", + 0x001C, "edma rxbasehi", + 0x0020, "edma rxi", + 0x0024, "edma rxo", + 0x0028, "edma c", + 0x002C, "edma tc", + 0x0030, "edma status", + 0x0034, "edma iordyto", +/* 0x0100, "edma pio", + 0x0104, "edma err", + 0x0108, "edma sectors", + 0x010C, "edma lba0", + 0x0110, "edma lba1", + 0x0114, "edma lba2", + 0x0118, "edma lba3", + 0x011C, "edma cmdstat", + 0x0120, "edma altstat", +*/ +}; + +static char* +rdregs(char *p, char *e, void *base, Regs *r, int n, char *prefix) +{ + int i; + + for(i = 0; i < n; i++) + p = seprint(p, e, "%s%s%-19s %.8lux\n", + prefix? prefix: "", prefix? ": ": "", + r[i].name, *(ulong *)((uchar*)base + r[i].offset)); + return p; +} + +static char* +rdinfo(char *p, char *e, ushort *info) +{ + int i; + + p = seprint(p, e, "info"); + for(i = 0; i < 256; i++) + p = seprint(p, e, "%s%.4ux%s", i%8 == 0? "\t": "", info[i], + i%8 == 7? "\n": ""); + return p; +} + +static int +mv50rctl(SDunit *unit, char *p, int l) +{ + char *e, *op; + Ctlr *ctlr; + Drive *drive; + + if((ctlr = unit->dev->ctlr) == nil) + return 0; + drive = &ctlr->drive[unit->subno]; + + e = p+l; + op = p; + if(drive->state == Dready){ + p = seprint(p, e, "model %s\n", drive->model); + p = seprint(p, e, "serial %s\n", drive->serial); + p = seprint(p, e, "firmware %s\n", drive->firmware); + }else + p = seprint(p, e, "no disk present\n"); + p = seprint(p, e, "geometry %llud 512\n", drive->sectors); + p = rdinfo(p, e, drive->info); + + p = rdregs(p, e, drive->chip->arb, regsarb, nelem(regsarb), nil); + p = rdregs(p, e, drive->bridge, regsbridge, nelem(regsbridge), nil); + p = rdregs(p, e, drive->edma, regsedma, nelem(regsedma), nil); + + return p-op; +} + +static int +mv50wctl(SDunit *unit, Cmdbuf *cb) +{ + Ctlr *ctlr; + Drive *drive; + + USED(unit); + if(strcmp(cb->f[0], "reset") == 0){ + ctlr = unit->dev->ctlr; + drive = &ctlr->drive[unit->subno]; + ilock(drive); + drive->state = Dreset; + iunlock(drive); + return 0; + } + cmderror(cb, Ebadctl); + return -1; +} + +/* + * sd(3): ``Reading /dev/sdctl yields information about each controller, + * one line per controller.'' + */ +static char* +mv50rtopctl(SDev *sdev, char *p, char *e) +{ + char name[10]; + Ctlr *ctlr; + + ctlr = sdev->ctlr; + if(ctlr == nil) + return p; + + snprint(name, sizeof name, "sd%c", sdev->idno); + p = rdregs(p, e, ctlr->mmio, regsctlr, nelem(regsctlr), name); + if (Coraiddebug) { + /* info for first disk. BUG: this shouldn't be here. */ + p = rdregs(p, e, ctlr->chip[0].arb, + regsarb, nelem(regsarb), name); + p = rdregs(p, e, &ctlr->chip[0].arb->bridge[0], + regsbridge, nelem(regsbridge), name); + p = rdregs(p, e, &ctlr->chip[0].edma[0], + regsedma, nelem(regsedma), name); + } + return p; +} + +static int +waitready(Drive *d) +{ + ulong s, i; + + for(i = 0; i < 120; i++){ + ilock(d); + s = d->bridge->status; + iunlock(d); + if(s == 0) + return SDeio; + if (d->state == Dready) + return SDok; + if ((i+1)%60 == 0){ + ilock(d); + resetdisk(d); + iunlock(d); + } + if(!waserror()){ + tsleep(&up->sleep, return0, 0, 1000); + poperror(); + } + } + print("%s: not responding after 2 minutes\n", d->unit->name); + return SDeio; +} + +static int +mv50rio(SDreq *r) +{ + int count, max, n, status, try, flag; + uchar *cmd, *data; + uvlong lba; + Ctlr *ctlr; + Drive *drive; + SDunit *unit; + Srb *srb; + + unit = r->unit; + ctlr = unit->dev->ctlr; + drive = &ctlr->drive[unit->subno]; + cmd = r->cmd; + + if((status = sdfakescsi(r, drive->info, sizeof drive->info)) != SDnostatus){ + /* XXX check for SDcheck here */ + r->status = status; + return status; + } + + switch(cmd[0]){ + case 0x28: /* read */ + case 0x2A: /* write */ + break; + default: + iprint("%s: bad cmd 0x%.2ux\n", drive->unit->name, cmd[0]); + r->status = SDcheck; + return SDcheck; + } + + lba = (cmd[2]<<24)|(cmd[3]<<16)|(cmd[4]<<8)|cmd[5]; + count = (cmd[7]<<8)|cmd[8]; + if(r->data == nil) + return SDok; + if(r->dlen < count*unit->secsize) + count = r->dlen/unit->secsize; + + try = 0; +retry: + if(waitready(drive) != SDok) + return SDeio; + /* + * Could arrange here to have an Srb always outstanding: + * + * lsrb = nil; + * while(count > 0 || lsrb != nil){ + * srb = nil; + * if(count > 0){ + * srb = issue next srb; + * } + * if(lsrb){ + * sleep on lsrb and handle it + * } + * } + * + * On the disks I tried, this didn't help. If anything, + * it's a little slower. -rsc + */ + data = r->data; + while(count > 0){ + /* + * Max is 128 sectors (64kB) because prd->count is 16 bits. + */ + max = 128; + n = count; + if(n > max) + n = max; + if((drive->edma->ctl&eEnEDMA) == 0) + goto tryagain; + srb = srbrw(cmd[0]==0x28 ? SRBread : SRBwrite, drive, data, n, lba); + ilock(drive); + startsrb(drive, srb); + iunlock(drive); + + /* Don't let user interrupt DMA. */ + while(waserror()) + ; + sleep(srb, srbdone, srb); + poperror(); + + flag = srb->flag; + freesrb(srb); + if(flag == 0){ +tryagain: + if(++try == 10){ + print("%s: bad disk\n", drive->unit->name); + return SDeio; + } + dprint("%s: retry\n", drive->unit->name); + if(!waserror()){ + tsleep(&up->sleep, return0, 0, 1000); + poperror(); + } + goto retry; + } + if(flag & SFerror){ + print("%s: i/o error\n", drive->unit->name); + return SDeio; + } + count -= n; + lba += n; + data += n*unit->secsize; + } + r->rlen = data - (uchar*)r->data; + return SDok; +} + +SDifc sdmv50xxifc = { + "mv50xx", /* name */ + + mv50pnp, /* pnp */ + nil, /* legacy */ + mv50enable, /* enable */ + mv50disable, /* disable */ + + mv50verify, /* verify */ + mv50online, /* online */ + mv50rio, /* rio */ + mv50rctl, /* rctl */ + mv50wctl, /* wctl */ + + scsibio, /* bio */ + nil, /* probe */ + mv50clear, /* clear */ + mv50rtopctl, /* rtopctl */ +}; + +/* + * The original driver on which this one is based came with the + * following notice: + * + * Copyright 2005 + * Coraid, Inc. + * + * This software is provided `as-is,' without any express or implied + * warranty. In no event will the author be held liable for any damages + * arising from the use of this software. + * + * Permission is granted to anyone to use this software for any purpose, + * including commercial applications, and to alter it and redistribute it + * freely, subject to the following restrictions: + * + * 1. The origin of this software must not be misrepresented; you must + * not claim that you wrote the original software. If you use this + * software in a product, an acknowledgment in the product documentation + * would be appreciated but is not required. + * + * 2. Altered source versions must be plainly marked as such, and must + * not be misrepresented as being the original software. + * + * 3. This notice may not be removed or altered from any source + * distribution. + */ |