bring the cpu kernel driver in line with the fs driver. fixes include 0. full sata-ii support. 1. don't use edma register in pio mode during setup. 2. support hot-plugging and allow for graceful timeout and failure with a background support process. 3. use the background process to reset the drive. resetting the drive while doing a read or from the interrupt routine causes hangs. 4. support various sata drive quirks, including some odd western digital drives that sometimes drop commands on the floor. 5. staggered drive spinup; more robust startup process. 6. deal with option-rom quirks. Notes: Mon May 7 10:09:39 EDT 2007 rsc I think this is obsoleted by the driver I installed on Apr 24. If not please resubmit the diff. Reference: /n/sources/patch/sorry/cpu-sdmv50xx Date: Sat Mar 31 01:16:09 CES 2007 Signed-off-by: quanstro@quanstro.net Reviewed-by: rsc --- /sys/src/9/pc/sdmv50xx.c Sat Mar 31 01:07:12 2007 +++ /sys/src/9/pc/sdmv50xx.c Thu Apr 19 19:07:52 2007 @@ -1,24 +1,40 @@ /* - * Marvell 88SX5040, 5041, 5080, 5081 driver - * This is a heavily-modified version of a driver written by Coraid, Inc. + * 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 "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)iprint +#define dprint(...) // print(__VA_ARGS__) +#define idprint(...) +#define ioprint(...) + +enum{ + NCtlr = 4, + NCtlrdrv = 8, + NDrive = NCtlr*NCtlrdrv, + + Read = 0, + Write, +}; enum { SrbRing = 32, - + /* Addresses of ATA register */ ARcmd = 027, ARdev = 026, @@ -27,23 +43,25 @@ ARlba2 = 025, ARlba1 = 024, ARlba0 = 023, - ARseccnt = 022, + ARseccnt = 022, ARstat = 027, - - ATAerr = (1<<0), - ATAdrq = (1<<3), - ATAdf = (1<<5), + + 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), + ATAsrst = (1<<2), + ATAhob = (1<<7), + ATAbad = (ATAbusy|ATAdf|ATAdrq|ATAerr), - SFdone = (1<<0), - SFerror = (1<<1), + SFdone = (1<<0), + SFerror = (1<<1), - SRBident = 0, + SRBident = 0, SRBread, SRBwrite, SRBsmart, @@ -51,59 +69,72 @@ SRBnodata = 0, SRBdatain, SRBdataout, - - RQread = 1, /* data coming IN from device */ - - PRDeot = (1<<15), - + + 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), + 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), + ePrtIntErr = (1<<11), + eIORdyErr = (1<<12), + + // flags for sata 2 version + eSelfDis2 = (1<<7), + SerrInt = (1<<5), /* EDMA Command Register */ - eEnEDMA = (1<<0), + eEnEDMA = (1<<0), eDsEDMA = (1<<1), eAtaRst = (1<<2), /* Interrupt mask for errors we care about */ - IEM = (eDevDis | eDevCon | eSelfDis), - - Dnull = 0, + IEM = (eDevDis | eDevCon | eSelfDis), + IEM2 = (eDevDis | eDevCon | eSelfDis2), + + /* drive states */ + Dnull = 0, Dnew, - Dident, Dready, Derror, Dmissing, - Dunconfig, + 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, - 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 */ }; -static char* diskstates[] = -{ +static char* diskstates[Dlast] = { "null", "new", - "ident", "ready", "error", "missing", - "unconfigured", + "reset", }; extern SDifc sdmv50xxifc; @@ -119,102 +150,126 @@ typedef struct Srb Srb; typedef struct Tx Tx; -struct Chip /* pointers to per-Chip mmio */ +// there are 4 drives per chip. thus an 8-port +// card has two chips. +struct Chip { - Arb *arb; - Edma *edma; /* array of 4 */ + Arb *arb; + Edma *edma; }; -struct Drive /* a single disk */ +enum{ + DMautoneg, + DMsatai, + DMsataii, +}; + +struct Drive { Lock; - Ctlr *ctlr; + Ctlr *ctlr; SDunit *unit; - int subno; - char name[10]; + char name[10]; + ulong magic; Bridge *bridge; Edma *edma; - Chip *chip; - int chipx; - - int state; - int flag; - uvlong sectors; + Chip *chip; + int chipx; - char serial[20+1]; - char firmware[8+1]; - char model[40+1]; + int mediachange; + int state; + int flag; + uvlong sectors; + ulong pm2; // phymode 2 init state + ulong intick; // check for hung westerdigital 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; + + Srb *srb[SrbRing-1]; + int nsrb; + Prd *prd; + Tx *tx; + Rx *rx; + + Srb *srbhead; + Srb *srbtail; + int driveno; // ctlr*NCtlrdrv + unit }; -struct Ctlr /* a single PCI card */ +struct Ctlr { Lock; - int irq; - int tbdf; - SDev *sdev; + int irq; + int tbdf; + int rid; + ulong magic; + int enabled; + int type; + SDev *sdev; Pcidev *pcidev; uchar *mmio; - Chip chip[2]; - int nchip; - Drive drive[8]; - int ndrive; + ulong *lmmio; + Chip chip[2]; + int nchip; + Drive drive[NCtlrdrv]; + int ndrive; }; -struct Srb /* request buffer */ +struct Srb /* request buffer */ { Lock; Rendez; - Srb *next; + Srb *next; Drive *drive; uvlong blockno; - int count; - int req; - int flag; + int count; + int req; + int flag; uchar *data; - + uchar cmd; uchar lba[6]; uchar sectors; - int sta; - int err; + int sta; + int err; }; /* * Memory-mapped I/O registers in many forms. */ -struct Bridge /* memory-mapped per-Drive registers */ +struct Bridge /* memory-mapped per-Drive registers */ { ulong status; ulong serror; ulong sctrl; ulong phyctrl; - char fill1[0x2c]; + ulong phymode3; + ulong phymode4; + uchar fill0[0x14]; + ulong phymode1; + ulong phymode2; + char fill1[8]; ulong ctrl; - char fill2[0x34]; + char fill2[0x34]; ulong phymode; - char fill3[0x88]; /* pad to 0x100 in length */ -}; + char fill3[0x88]; +}; // most be 0x100 hex in length -struct Arb /* memory-mapped per-Chip registers */ +struct Arb /* memory-mapped per-Chip registers */ { - ulong fill0; - ulong rqop; /* request queue out-pointer */ + 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 */ @@ -222,78 +277,86 @@ ulong btc; /* bridges test control */ ulong bts; /* bridges test status */ ulong bpc; /* bridges pin configuration */ - char fill1[0xdc]; + char fill1[0xdc]; Bridge bridge[4]; }; -struct Edma /* memory-mapped per-Drive DMA-related registers */ +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[0xc8]; - 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 */ - char fill2[0x1edc]; /* pad to 0x2000 bytes */ + 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 */ +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 zero; /* high long of 64 bit address */ ulong reserved; }; -struct Tx /* command request block */ +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 */ + ulong zero; /* must be zero (high long of prd address) */ + ushort flag; /* control flags */ ushort regs[11]; }; -struct Rx /* command response block */ +struct Rx /* command response block */ { - ushort cid; /* cID of response */ - uchar cEdmaSts; /* EDMA status */ + ushort cid; /* cID of response */ + uchar cEdmaSts; /* EDMA status */ uchar cDevSts; /* status from disk */ - ulong ts; /* time stamp */ + ulong ts; /* time stamp */ }; +static Ctlr *mvsatactlr[NCtlr]; +static Drive *mvsatadrive[NDrive]; +static int nmvsatadrive; + /* * Little-endian parsing for drive data. */ @@ -377,100 +440,229 @@ { int i; -// DPRINT("satawait %p %#x %#x %d...", p, mask, v, ms); -// DPRINT("!%#x...", *p); - for(i=0; ictlr->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; - ulong *r; - - DPRINT("%s: configdrive\n", unit->name); - d->unit = unit; - d->ctlr = ctlr; - d->chipx = unit->subno%4; - d->chip = &ctlr->chip[unit->subno/4]; - d->bridge = &d->chip->arb->bridge[d->chipx]; - d->edma = &d->chip->edma[d->chipx]; - - if(d->tx == nil){ - d->tx = mallocalign(32*sizeof(Tx), 1024, 0, 0); - d->rx = mallocalign(32*sizeof(Rx), 256, 0, 0); - d->prd = mallocalign(32*sizeof(Prd), 32, 0, 0); - if(d->tx == nil || d->rx == nil || d->prd == nil){ - iprint("%s: out of memory allocating ring buffers\n", - unit->name); - free(d->tx); - d->tx = nil; - free(d->rx); - d->rx = nil; - free(d->prd); - d->prd = nil; - d->state = Dunconfig; - return 0; + Srb *srb; + + for(i=0; isrb); i++){ + if(srb = d->srb[i]){ + d->srb[i] = nil; + d->nsrb--; + srb->flag |= SFerror|SFdone; + wakeup(srb); } - for(i=0; i<32; i++) - d->tx[i].prdpa = PADDR(&d->prd[i]); - coherence(); } - - /* leave disk interrupts turned off until we use it ... */ - d->edma->iem = 0; - - /* ... but enable them on the controller */ - r = (ulong*)(d->ctlr->mmio + 0x1D64); - if(d->unit->subno < 4) - *r |= 3 << (d->chipx*2); - else - *r |= 3 << (d->chipx*2+9); + while(srb = d->srbhead){ + d->srbhead = srb->next; + srb->flag |= SFerror|SFdone; + wakeup(srb); + } +} - return 1; +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) +{ + Rendez r; + + 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); + memset(&r, 0, sizeof r); + tsleep(&r, return0, 0, 1400); // don't burn out the power supply. + } + return 0; } static int enabledrive(Drive *d) { Edma *edma; - - DPRINT("%s: enabledrive\n", d->unit->name); - if((d->bridge->status & 0xF) != 0x3){ /* Det */ - DPRINT("%s: not present\n", d->unit->name); + dprint("%s: enabledrive..", d->unit->name); + + if((d->bridge->status & 0xf) != 3){ + dprint("%s: not present\n", d->unit->name); d->state = Dmissing; - return 0; + return -1; } edma = d->edma; - if(satawait(&edma->cmdstat, ATAbusy, 0, 10*1000) == 0){ - print("%s: busy timeout\n", d->unit->name); + if(satawait(&edma->cmdstat, ATAbusy, 0, 5*1000) == 0){ + dprint("%s: busy timeout\n", d->unit->name); d->state = Dmissing; - return 0; + return -1; } - edma->iec = 0; d->chip->arb->ic &= ~(0x101 << d->chipx); - edma->config = 0x11F; + 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->txo = (ulong)d->tx & 0x3e0; + edma->rxi = (ulong)d->rx & 0xf8; edma->rxo = PADDR(d->rx); edma->ctl |= 1; /* enable dma */ - DPRINT("%s: enable interrupts\n", d->unit->name); - if(d->bridge->status = 0x113) + if(d->bridge->status = 0x113){ + dprint("%s: new\n", d->unit->name); d->state = Dnew; - d->edma->iem = IEM; - return 1; + }else + print("%s: status not forced (should be okay)\n", d->unit->name); + return 0; } static void @@ -479,7 +671,7 @@ int i; ulong *r; - DPRINT("%s: disabledrive\n", d->unit->name); + dprint("%s: disabledrive\n", d->unit->name); if(d->tx == nil) /* never enabled */ return; @@ -487,7 +679,7 @@ d->edma->ctl = 0; d->edma->iem = 0; - r = (ulong*)(d->ctlr->mmio + 0x1D64); + r = (ulong*)(d->ctlr->mmio + 0x1d64); i = d->chipx; if(d->chipx < 4) *r &= ~(3 << (i*2)); @@ -499,37 +691,39 @@ setudmamode(Drive *d, uchar mode) { Edma *edma; - - DPRINT("%s: setudmamode %d\n", d->unit->name, mode); + + dprint("%s: setudmamode %d\n", d->unit->name, mode); edma = d->edma; - if(satawait(&edma->cmdstat, ATAerr|ATAdrq|ATAdf|ATAdrdy|ATAbusy, ATAdrdy, 15*1000) == 0){ - iprint("%s: cmdstat 0x%.2ux ready timeout\n", - d->unit->name, edma->cmdstat); + 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; + edma->cmdstat = 0xef; microdelay(1); - if(satawait(&edma->cmdstat, ATAbusy, 0, 15*1000) == 0){ - iprint("%s: cmdstat 0x%.2ux busy timeout\n", - d->unit->name, edma->cmdstat); + 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 void +static int identifydrive(Drive *d) { int i; ushort *id; Edma *edma; SDunit *unit; - - DPRINT("%s: identifydrive\n", d->unit->name); + + dprint("%s: identifydrive\n", d->unit->name); if(setudmamode(d, 5) == 0) /* do all SATA support 5? */ goto Error; @@ -537,17 +731,17 @@ id = d->info; memset(d->info, 0, sizeof d->info); edma = d->edma; - if(satawait(&edma->cmdstat, 0xE9, 0x40, 15*1000) == 0) + if(satawait(&edma->cmdstat, ~ATAobs, ATAdrdy, 5*1000) == 0) goto Error; edma->altstat = ATAeIEN; /* no interrupts */ - edma->cmdstat = 0xEC; + edma->cmdstat = 0xec; microdelay(1); - if(satawait(&edma->cmdstat, ATAbusy, 0, 15*1000) == 0) + if(satawait(&edma->cmdstat, ATAbusy, 0, 5*1000) == 0) goto Error; - for(i=0; i<256; i++) + for(i = 0; i < 256; i++) id[i] = edma->pio; - if(edma->cmdstat & (ATAerr|ATAdf)) + if(edma->cmdstat & ATAbad) goto Error; i = lhgets(id+83) | lhgets(id+86); if(i & (1<<10)){ @@ -560,7 +754,7 @@ 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; @@ -568,50 +762,128 @@ unit->inquiry[4] = sizeof(unit->inquiry)-4; idmove((char*)unit->inquiry+8, id+27, 40); - if(enabledrive(d)) + if(enabledrive(d) == 0) { d->state = Dready; - else + d->mediachange = 1; + iprint("%s: LLBA %lld sectors\n", d->unit->name, d->sectors); + } else d->state = Derror; - return; - + if(d->state == Dready) + return 0; + return -1; Error: - DPRINT("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<unit->name, cause); + char buf[32+4+1]; edma = d->edma; - if(cause & eDevDis){ - d->state = Dmissing; - edma->ctl |= eAtaRst; - microdelay(25); - edma->ctl &= ~eAtaRst; - microdelay(25); + if((edma->ctl&eEnEDMA) == 0){ + // FEr SATA#4 40xx + x = d->edma->cmdstat; + USED(x); } - if(cause & eDevCon){ - d->bridge->sctrl = (d->bridge->sctrl & ~0xF) | 1; + 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; + } } - if(cause & eSelfDis) - d->state = Derror; - edma->iec = 0; - d->sectors = 0; - d->unit->sectors = 0; - abortallsrb(d); - x = edma->cmdstat; - USED(x); + edma->iec = ~cause; } /* @@ -624,14 +896,6 @@ Srb *srb; static uchar cmd[2][2] = { 0xC8, 0x25, 0xCA, 0x35 }; - switch(req){ - case SRBread: - case SRBwrite: - break; - default: - return nil; - } - srb = allocsrb(); srb->req = req; srb->drive = d; @@ -651,7 +915,7 @@ advance(uintptr pa, int shift) { int n, mask; - + mask = 0x1F<sectors); @@ -671,15 +935,14 @@ *cmd++ = CMD(ARlba1, srb->lba[1]); *cmd++ = CMD(ARlba2, srb->lba[5]); *cmd++ = CMD(ARlba2, srb->lba[2]); - *cmd++ = CMD(ARdev, 0xE0); + *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(ARdev, srb->lba[3] | 0xe0); } - *cmd++ = CMD(ARcmd, srb->cmd) | (1<<15); - USED(cmd); + *cmd = CMD(ARcmd, srb->cmd) | (1<<15); } static void @@ -689,7 +952,7 @@ Edma *edma; Prd *prd; Tx *tx; - + if(d->nsrb >= nelem(d->srb)){ srb->next = nil; if(d->srbhead) @@ -699,13 +962,14 @@ d->srbtail = srb; return; } - + d->nsrb++; for(i=0; isrb); 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); @@ -714,31 +978,38 @@ prd->pa = PADDR(srb->data); prd->count = srb->count; prd->flag = PRDeot; - atarequest(tx->regs, srb, d->flag&Dext); + 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 & (0x1F<<3)) != (edma->rxi & (0x1F<<3))){ + + 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 & (ATAerr|ATAdf)) + if(rx->cDevSts & ATAbad) srb->flag |= SFerror; - srb->flag |= SFdone; + if (rx->cEdmaSts) + iprint("cEdmaSts: %02ux\n", rx->cEdmaSts); srb->sta = rx->cDevSts; + srb->flag |= SFdone; wakeup(srb); }else iprint("srb missing\n"); @@ -750,32 +1021,11 @@ } } -static void -abortallsrb(Drive *d) -{ - int i; - Srb *srb; - - for(i=0; isrb); 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 int srbdone(void *v) { Srb *srb; - + srb = v; return srb->flag & SFdone; } @@ -790,88 +1040,216 @@ ulong cause; Ctlr *ctlr; Drive *drive; - + ctlr = a; ilock(ctlr); - cause = *(ulong*)(ctlr->mmio + 0x1D60); - DPRINT("sd%c: mv50interrupt: 0x%lux\n", ctlr->sdev->idno, cause); - for(i=0; indrive; i++){ + cause = ctlr->lmmio[0x1d60/4]; +// dprint("sd%c: mv50interrupt: 0x%lux\n", ctlr->sdev->idno, cause); + for(i=0; indrive; i++) if(cause & (3<<(i*2+i/4))){ drive = &ctlr->drive[i]; - if(drive->edma == nil) - continue; /* not ready yet */ + if(drive->edma == 0) + continue; // not ready yet. ilock(drive); - updatedrive(drive, drive->edma->iec); + 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; + static Rendez r; + + memset(&r, 0, sizeof r); + for(;;){ + tsleep(&r, return0, 0, Nms); + for(i = 0; i < nmvsatadrive; i++) + checkdrive(mvsatadrive[i], i); + } +} + /* * Device discovery */ static SDev* mv50pnp(void) { - int i, nunit; + int i, dno, nunit; uchar *base; - ulong io; - void *mem; + ulong io, n, *mem; Ctlr *ctlr; Pcidev *p; SDev *head, *tail, *sdev; + Drive *d; + static int ctlrno, done; - DPRINT("mv50pnp\n"); + dprint("mv50pnp\n"); + if(done++) + return nil; p = nil; head = nil; tail = nil; - while((p = pcimatch(p, 0x11AB, 0)) != nil){ + while((p = pcimatch(p, 0x11ab, 0)) != nil){ switch(p->did){ + case 0x5040: case 0x5041: - nunit = 4; - break; + case 0x5080: case 0x5081: - nunit = 8; + 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 = vmap(io, p->mem[0].size); + 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'; + sdev->idno = 'E' + ctlrno; ctlr->sdev = sdev; ctlr->irq = p->intl; ctlr->tbdf = p->tbdf; ctlr->pcidev = p; - ctlr->mmio = mem; + ctlr->lmmio = mem; + ctlr->mmio = (uchar*)mem; ctlr->nchip = (nunit+3)/4; ctlr->ndrive = nunit; - for(i=0; inchip; i++){ + 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 = dno = ctlrno*NCtlrdrv + i; + d->chipx = dno%4; + d->chip =&ctlr->chip[dno/4]; + d->edma = &d->chip->edma[d->chipx]; + mvsatadrive[d->driveno] = d; + } + mvsatactlr[ctlrno] = ctlr; + nmvsatadrive += i; + ctlrno++; if(head) tail->next = sdev; else @@ -891,11 +1269,14 @@ char name[32]; Ctlr *ctlr; - DPRINT("sd%c: enable\n", sdev->idno); + 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; } @@ -909,8 +1290,8 @@ int i; Ctlr *ctlr; Drive *drive; - - DPRINT("sd%c: disable\n", sdev->idno); + + dprint("sd%c: disable\n", sdev->idno); ctlr = sdev->ctlr; ilock(ctlr); @@ -938,7 +1319,7 @@ Ctlr *ctlr; Drive *d; - DPRINT("sd%c: clear\n", sdev->idno); + dprint("sd%c: clear\n", sdev->idno); ctlr = sdev->ctlr; for(i=0; indrive; i++){ @@ -959,33 +1340,14 @@ Ctlr *ctlr; Drive *drive; - DPRINT("%s: verify\n", unit->name); - - /* - * First access of unit. - */ - + dprint("%s: verify\n", unit->name); ctlr = unit->dev->ctlr; drive = &ctlr->drive[unit->subno]; ilock(ctlr); ilock(drive); - - if(!configdrive(ctlr, drive, unit) || !enabledrive(drive)){ - iunlock(drive); - iunlock(ctlr); - return 0; - } - /* - * Need to reset the drive before the first call to - * identifydrive, or else the satawait in setudma will - * freeze the machine when accessing edma->cmdstat. - * I do not understand this. -rsc - */ - updatedrive(drive, eDevDis); - + configdrive(ctlr, drive, unit); iunlock(drive); iunlock(ctlr); - return 1; } @@ -996,31 +1358,30 @@ mv50online(SDunit *unit) { Ctlr *ctlr; - Drive *drive; + Drive *d; + int r, s0; + static int once; + + if(once++ == 0) + kproc("mvsata", satakproc, 0); ctlr = unit->dev->ctlr; - drive = &ctlr->drive[unit->subno]; - ilock(drive); - if(drive->state == Dready){ - unit->sectors = drive->sectors; + d = &ctlr->drive[unit->subno]; + r = 0; + ilock(d); + s0 = d->state; + if(d->state == Dnew) + identifydrive(d); + if(d->mediachange){ + iprint("%s: online: %s -> %s\n", unit->name, diskstates[s0], diskstates[d->state]); + r = 2; + unit->sectors = d->sectors; unit->secsize = 512; - iunlock(drive); - return 1; - } - - DPRINT("%s: online %s\n", unit->name, diskstates[drive->state]); - - if(drive->state == Dnew){ - identifydrive(drive); - if(drive->state == Dready){ - unit->sectors = drive->sectors; - unit->secsize = 512; - iunlock(drive); - return 2; /* media changed */ - } - } - iunlock(drive); - return 0; + d->mediachange = 0; + } else if(d->state == Dready) + r = 1; + iunlock(d); + return r; } /* @@ -1112,12 +1473,12 @@ 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", + p = seprint(p, e, "%s%.4ux%s", i%8==0 ? "\t" : "", - info[i], + info[i], i%8==7 ? "\n" : ""); } return p; @@ -1163,7 +1524,7 @@ ctlr = unit->dev->ctlr; drive = &ctlr->drive[unit->subno]; ilock(drive); - updatedrive(drive, eDevDis); + drive->state = Dreset; iunlock(drive); return 0; } @@ -1192,21 +1553,48 @@ } static int +waitready(Drive *d) +{ + ulong s, i; + Rendez r; + + 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); + } + memset(&r, 0, sizeof r); + tsleep(&r, return0, 0, 1000); + } + print("%s: not responding after 2 minutes\n", d->unit->name); + return SDeio; +} + +static int mv50rio(SDreq *r) { - int count, max, n, status; + int count, max, n, status, try, flag; uchar *cmd, *data; uvlong lba; Ctlr *ctlr; Drive *drive; SDunit *unit; Srb *srb; - + Rendez rz; + 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; @@ -1218,19 +1606,23 @@ case 0x2A: /* write */ break; default: - print("sdmv50xx: bad cmd 0x%.2ux\n", cmd[0]); + 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; @@ -1256,57 +1648,45 @@ 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); - /* - * Cannot let user interrupt the DMA. - */ - while(waserror()) - ; - tsleep(srb, srbdone, srb, 60*1000); - poperror(); - - if(!(srb->flag & SFdone)){ - ilock(drive); - if(!(srb->flag & SFdone)){ - /* - * DMA didn't finish but we have to let go of - * the data buffer. Reset the drive to (try to) keep it - * from using the buffer after we're gone. - */ - iprint("%s: i/o timeout\n", unit->name); - updatedrive(drive, eDevDis); - enabledrive(drive); - freesrb(srb); - iunlock(drive); - error("i/o timeout"); + sleep(&srb->Rendez, srbdone, srb); + flag = srb->flag; + freesrb(srb); + if(flag == 0){ + tryagain: if(++try == 10){ + print("%s: bad disk\n", drive->unit->name); + return SDeio; } - iunlock(drive); + dprint("%s: retry\n", drive->unit->name); + memset(&rz, 0, sizeof rz); + tsleep(&rz, return0, 0, 1000); + goto retry; } - if(srb->flag & SFerror){ - freesrb(srb); - error("i/o error"); + print("%s: i/o error\n", drive->unit->name); + return SDeio; } - freesrb(srb); count -= n; lba += n; data += n*unit->secsize; } r->rlen = data - (uchar*)r->data; - return SDok; + return SDok; } SDifc sdmv50xxifc = { - "mv50xx", /* name */ + "mv50xx", /* name */ mv50pnp, /* pnp */ nil, /* legacy */ - mv50enable, /* enable */ - mv50disable, /* disable */ + mv50enable, /* enable */ + mv50disable, /* disable */ mv50verify, /* verify */ mv50online, /* online */ @@ -1318,7 +1698,6 @@ nil, /* probe */ mv50clear, /* clear */ mv50rtopctl, /* rtopctl */ - nil, /* wtopctl */ }; /*