]> git.lizzy.rs Git - plan9front.git/blobdiff - sys/src/9/pc/sdiahci.c
kernel: cleanup the software mouse cursor mess
[plan9front.git] / sys / src / 9 / pc / sdiahci.c
index 98e8d7fc8fee3aa44e4d66e4138948976ce0b1be..d474f6209a37b6e513f40b3ae6392ed2ce1a7886 100644 (file)
 #include "../port/led.h"
 
 #pragma        varargck        type    "T"     int
-#define        dprint(...)     if(debug)       iprint(__VA_ARGS__); else USED(debug)
-#define        idprint(...)    if(prid)                print(__VA_ARGS__); else USED(prid)
+#define        dprint(...)     if(debug)       print(__VA_ARGS__); else USED(debug)
+#define        idprint(...)    if(prid)        print(__VA_ARGS__); else USED(prid)
 #define        aprint(...)     if(datapi)      print(__VA_ARGS__); else USED(datapi)
-#define        ledprint(...)   if(dled)                print(__VA_ARGS__); else USED(dled)
+#define        ledprint(...)   if(dled)        print(__VA_ARGS__); else USED(dled)
 #define        Pciwaddrh(a)    0
 #define Tname(c)       tname[(c)->type]
 #define        Ticks           MACHP(0)->ticks
@@ -120,6 +120,7 @@ typedef struct {
        Ledport;
 
        uchar   drivechange;
+       uchar   nodma;
        uchar   state;
 
        uvlong  sectors;
@@ -164,6 +165,8 @@ struct Ctlr {
        Drive   rawdrive[NCtlrdrv];
        Drive*  drive[NCtlrdrv];
        int     ndrive;
+
+       uint    missirq;
 };
 
 static Ctlr    iactlr[NCtlr];
@@ -274,7 +277,7 @@ ahciwait(Aportc *c, int ms)
        return -1;
 }
 
-static void
+static Alist*
 mkalist(Aportm *m, uint flags, uchar *data, int len)
 {
        Actab *t;
@@ -282,18 +285,19 @@ mkalist(Aportm *m, uint flags, uchar *data, int len)
        Aprdt *p;
 
        t = m->ctab;
-       l = m->list;
-       l->flags = flags | 0x5;
-       l->len = 0;
-       l->ctab = PCIWADDR(t);
-       l->ctabhi = Pciwaddrh(t);
-       if(data){
-               l->flags |= 1<<16;
+       if(data && len > 0){
                p = &t->prdt;
                p->dba = PCIWADDR(data);
                p->dbahi = Pciwaddrh(data);
                p->count = 1<<31 | len - 2 | 1;
+               flags |= 1<<16;
        }
+       l = m->list;
+       l->flags = flags | 0x5;
+       l->len = 0;
+       l->ctab = PCIWADDR(t);
+       l->ctabhi = Pciwaddrh(t);
+       return l;
 }
 
 static int
@@ -343,13 +347,13 @@ settxmode(Aportc *pc, uchar f)
 static void
 asleep(int ms)
 {
-       if(up == nil)
+       if(up == nil || !islo())
                delay(ms);
        else
                esleep(ms);
 }
 
-static int
+static void
 ahciportreset(Aportc *c, uint mode)
 {
        ulong *cmd, i;
@@ -363,10 +367,11 @@ ahciportreset(Aportc *c, uint mode)
                        break;
                asleep(25);
        }
+       if((*cmd & Apwr) != Apwr)
+               *cmd |= Apwr;
        p->sctl = 3*Aipm | 0*Aspd | Adet;
        delay(1);
        p->sctl = 3*Aipm | mode*Aspd;
-       return 0;
 }
 
 static int
@@ -379,7 +384,7 @@ ahciflushcache(Aportc *pc)
        mkalist(pc->m, Lwrite, 0, 0);
 
        if(ahciwait(pc, 60000) == -1 || pc->p->task & (1|32)){
-               dprint("ahciflushcache fail %lux\n", pc->p->task);
+               dprint("ahciflushcache fail [task %lux]\n", pc->p->task);
 //             preg(pc->m->fis.r, 20);
                return -1;
        }
@@ -456,7 +461,7 @@ stop:
        return -1;
 stop1:
        /* extra check */
-       dprint("ahci: clo clear %lux\n", a->task);
+       dprint("ahci: clo clear [task %lux]\n", a->task);
        if(a->task & ASbsy)
                return -1;
        *p |= Afre | Ast;
@@ -577,16 +582,18 @@ static void
 ahciwakeup(Aportc *c, uint mode)
 {
        ushort s;
+       Aport *p;
 
-       s = c->p->sstatus;
+       p = c->p;
+       s = p->sstatus;
        if((s & Isleepy) == 0)
                return;
        if((s & Smask) != Spresent){
-               print("ahci: slumbering drive missing %.3ux\n", s);
+               dprint("ahci: slumbering drive missing [ss %.3ux]\n", s);
                return;
        }
        ahciportreset(c, mode);
-//     iprint("ahci: wake %.3ux -> %.3lux\n", s, c->p->sstatus);
+       dprint("ahci: wake %.3ux -> %.3lux\n", s, c->p->sstatus);
 }
 
 static int
@@ -594,6 +601,7 @@ ahciconfigdrive(Ahba *h, Aportc *c, int mode)
 {
        Aportm *m;
        Aport *p;
+       int i;
 
        p = c->p;
        m = c->m;
@@ -604,6 +612,11 @@ ahciconfigdrive(Ahba *h, Aportc *c, int mode)
                m->ctab = malign(sizeof *m->ctab, 128);
        }
 
+       if(ahciidle(p) == -1){
+               dprint("ahci: port not idle\n");
+               return -1;
+       }
+
        p->list = PCIWADDR(m->list);
        p->listhi = Pciwaddrh(m->list);
        p->fis = PCIWADDR(m->fis.base);
@@ -611,28 +624,35 @@ ahciconfigdrive(Ahba *h, Aportc *c, int mode)
 
        p->cmd |= Afre;
 
-       if((p->sstatus & Sbist) == 0 && (p->cmd & Apwr) != Apwr)
-       if((p->sstatus & Sphylink) == 0 && h->cap & Hss){
-               /* staggered spin-up? */
-               dprint("ahci:  spin up ... [%.3lux]\n", p->sstatus);
+       if((p->cmd & Apwr) != Apwr)
                p->cmd |= Apwr;
-               for(int i = 0; i < 1400; i += 50){
-                       if(p->sstatus & (Sphylink | Sbist))
+
+       if((h->cap & Hss) != 0){
+               dprint("ahci: spin up ... [%.3lux]\n", p->sstatus);
+               for(i = 0; i < 1400; i += 50){
+                       if((p->sstatus & Sbist) != 0)
+                               break;
+                       if((p->sstatus & Smask) == Sphylink)
                                break;
                        asleep(50);
                }
        }
 
-       p->serror = SerrAll;
-
        if((p->sstatus & SSmask) == (Isleepy | Spresent))
                ahciwakeup(c, mode);
+
+       p->serror = SerrAll;
+       p->ie = IEM;
+
+       /* we will get called again once phylink has been established */
+       if((p->sstatus & Smask) != Sphylink)
+               return 0;
+
        /* disable power managment sequence from book. */
        p->sctl = 3*Aipm | mode*Aspd | 0*Adet;
        p->cmd &= ~Aalpe;
 
-       p->cmd |= Ast;
-       p->ie = IEM;
+       p->cmd |= Afre | Ast;
 
        return 0;
 }
@@ -675,14 +695,26 @@ ahciconf(Ctlr *c)
        if((u & Ham) == 0)
                h->ghc |= Hae;
 
-//     print("#S/sd%c: ahci %s port %#p: sss %d ncs %d coal %d "
-//             "mport %d led %d clo %d ems %d\n",
-//             c->sdev->idno, Tname(c), h,
-//             (u>>27) & 1, (u>>8) & 0x1f, (u>>7) & 1, u & 0x1f, (u>>25) & 1,
-//             (u>>24) & 1, (u>>6) & 1);
        return countbits(h->pi);
 }
 
+static int
+ahcihandoff(Ahba *h)
+{
+       int wait;
+
+       if((h->cap2 & Boh) == 0)
+               return 0;
+       h->bios |= Oos;
+       for(wait = 0; wait < 2000; wait += 100){
+               if((h->bios & Bos) == 0)
+                       return 0;
+               delay(100);
+       }
+       iprint("ahci: bios handoff timed out\n");
+       return -1;
+}
+
 static int
 ahcihbareset(Ahba *h)
 {
@@ -690,7 +722,7 @@ ahcihbareset(Ahba *h)
 
        h->ghc |= Hhr;
        for(wait = 0; wait < 1000; wait += 100){
-               if(h->ghc == 0)
+               if((h->ghc & Hhr) == 0)
                        return 0;
                delay(100);
        }
@@ -718,10 +750,8 @@ identify(Drive *d)
 
        id = d->info;
        s = ahciidentify(&d->portc, id, &d->secsize, dnam(d));
-       if(s == -1){
-               d->state = Derror;
+       if(s == -1)
                return -1;
-       }
        osectors = d->sectors;
        memmove(oserial, d->serial, sizeof d->serial);
 
@@ -741,6 +771,7 @@ identify(Drive *d)
 
        if(osectors != s || memcmp(oserial, d->serial, sizeof oserial)){
                d->drivechange = 1;
+               d->nodma = 0;
                u->sectors = 0;
        }
        return 0;
@@ -755,12 +786,6 @@ clearci(Aport *p)
        }
 }
 
-static int
-intel(Ctlr *c)
-{
-       return c->pci->vid == 0x8086;
-}
-
 static int
 ignoreahdrs(Drive *d)
 {
@@ -787,8 +812,13 @@ updatedrive(Drive *d)
        if(p->ci == 0){
                f |= Fdone;
                pr = 0;
-       }else if(cause & Adps)
+       }else if(cause & Adps){
                pr = 0;
+       }else if(cause & Atfes){
+               f |= Ferror;
+               ewake = 1;
+               pr = 0;
+       }
        if(cause & Ifatal){
                ewake = 1;
                dprint("%s: fatal\n", dnam(d));
@@ -831,10 +861,10 @@ updatedrive(Drive *d)
                        d->state = Doffline;
                        break;
                }
-               dprint("%s: %s → %s [Apcrs] %.3lux\n", dnam(d), diskstates[s0],
-                       diskstates[d->state], p->sstatus);
+               dprint("%s: updatedrive: %s → %s [ss %.3lux]\n",
+                       dnam(d), diskstates[s0], diskstates[d->state], p->sstatus);
                if(s0 == Dready && d->state != Dready)
-                       idprint("%s: pulled\n", dnam(d));
+                       dprint("%s: pulled\n", dnam(d));
                if(d->state != Dready)
                        f |= Ferror;
                if(d->state != Dready || p->ci)
@@ -851,21 +881,37 @@ updatedrive(Drive *d)
 }
 
 static void
-pstatus(Drive *d, ulong s)
+dstatus(Drive *d, int s)
 {
-       /*
-        * bogus code because the first interrupt is currently dropped.
-        * likely my fault.  serror is maybe cleared at the wrong time.
-        */
-       switch(s){
+       dprint("%s: dstatus: %s → %s from pc=%p\n", dnam(d), 
+               diskstates[d->state], diskstates[s], getcallerpc(&d));
+
+       ilock(d);
+       d->state = s;
+       iunlock(d);
+}
+
+static void
+configdrive(Drive *d)
+{
+       if(ahciconfigdrive(d->ctlr->hba, &d->portc, d->mode) == -1){
+               dstatus(d, Dportreset);
+               return;
+       }
+
+       ilock(d);
+       switch(d->port->sstatus & Smask){
        default:
-               print("%s: pstatus: bad status %.3lux\n", dnam(d), s);
        case Smissing:
                d->state = Dmissing;
                break;
        case Spresent:
+               if(d->state == Dnull)
+                       d->state = Dportreset;
                break;
        case Sphylink:
+               if(d->state == Dready)
+                       break;
                d->wait = 0;
                d->state = Dnew;
                break;
@@ -873,17 +919,9 @@ pstatus(Drive *d, ulong s)
                d->state = Doffline;
                break;
        }
-}
-
-static int
-configdrive(Drive *d)
-{
-       if(ahciconfigdrive(d->ctlr->hba, &d->portc, d->mode) == -1)
-               return -1;
-       ilock(d);
-       pstatus(d, d->port->sstatus & Smask);
        iunlock(d);
-       return 0;
+
+       dprint("%s: configdrive: %s\n", dnam(d), diskstates[d->state]);
 }
 
 static void
@@ -896,36 +934,27 @@ resetdisk(Drive *d)
        det = p->sctl & 7;
        stat = p->sstatus & Smask;
        state = (p->cmd>>28) & 0xf;
-       dprint("%s: resetdisk: icc %ux  det %.3ux sdet %.3ux\n", dnam(d), state, det, stat);
+       dprint("%s: resetdisk [icc %ux; det %.3ux; sdet %.3ux]\n", dnam(d), state, det, stat);
 
        ilock(d);
-       state = d->state;
-       if(d->state != Dready || d->state != Dnew)
+       if(d->state != Dready && d->state != Dnew)
                d->portm.flag |= Ferror;
+       if(stat != Sphylink)
+               d->state = Dportreset;
+       else
+               d->state = Dreset;
        clearci(p);                     /* satisfy sleep condition. */
        wakeup(&d->portm);
-       d->state = Derror;
        iunlock(d);
 
-       if(stat != Sphylink){
-               ilock(d);
-               d->state = Dportreset;
-               iunlock(d);
+       if(stat != Sphylink)
                return;
-       }
 
        qlock(&d->portm);
-       if(p->cmd&Ast && ahciswreset(&d->portc) == -1){
-               ilock(d);
-               d->state = Dportreset;  /* get a bigger stick. */
-               iunlock(d);
-       }else{
-               ilock(d);
-               d->state = Dmissing;
-               iunlock(d);
+       if(p->cmd&Ast && ahciswreset(&d->portc) == -1)
+               dstatus(d, Dportreset); /* get a bigger stick. */
+       else
                configdrive(d);
-       }
-       dprint("%s: resetdisk: %s → %s\n", dnam(d), diskstates[state], diskstates[d->state]);
        qunlock(&d->portm);
 }
 
@@ -950,15 +979,12 @@ newdrive(Drive *d)
                goto lose;
        }
        if(m->feat & Dpower && setfeatures(c, 0x85, 3*1000) == -1){
+               dprint("%s: can't disable apm\n", dnam(d));
                m->feat &= ~Dpower;
                if(ahcirecover(c) == -1)
                        goto lose;
        }
-
-       ilock(d);
-       d->state = Dready;
-       iunlock(d);
-
+       dstatus(d, Dready);
        qunlock(c->m);
 
        s = "";
@@ -971,48 +997,41 @@ newdrive(Drive *d)
 
 lose:
        idprint("%s: can't be initialized\n", dnam(d));
-       ilock(d);
-       d->state = Dnull;
-       iunlock(d);
+       dstatus(d, Dnull);
        qunlock(c->m);
        return -1;
 }
 
 enum {
        Nms             = 256,
+       Mcomrwait       =  1*1024/Nms - 1,
        Mphywait        =  2*1024/Nms - 1,
        Midwait         = 16*1024/Nms - 1,
-       Mcomrwait       = 64*1024/Nms - 1,
 };
 
 static void
 hangck(Drive *d)
 {
-       if((d->portm.feat & Datapi) == 0 && d->active &&
-           d->totick != 0 && (long)(Ticks - d->totick) > 0){
-               dprint("%s: drive hung; resetting [%lux] ci %lux\n",
-                       dnam(d), d->port->task, d->port->ci);
+       if(d->active && d->totick != 0 && (long)(Ticks - d->totick) > 0){
+               dprint("%s: drive hung [task %lux; ci %lux; serr %lux]%s\n",
+                       dnam(d), d->port->task, d->port->ci, d->port->serror,
+                       d->nodma == 0 ? "; disabling dma" : "");
+               d->nodma = 1;
                d->state = Dreset;
        }
 }
 
 static ushort olds[NCtlr*NCtlrdrv];
 
-static int
+static void
 doportreset(Drive *d)
 {
-       int i;
-
-       i = -1;
        qlock(&d->portm);
-       if(ahciportreset(&d->portc, d->mode) == -1)
-               dprint("ahci: ahciportreset fails\n");
-       else
-               i = 0;
+       ahciportreset(&d->portc, d->mode);
        qunlock(&d->portm);
-       dprint("ahci: portreset → %s  [task %.4lux ss %.3lux]\n",
+
+       dprint("ahci: portreset: %s [task %lux; ss %.3lux]\n",
                diskstates[d->state], d->port->task, d->port->sstatus);
-       return i;
 }
 
 /* drive must be locked */
@@ -1038,11 +1057,18 @@ maxmode(Ctlr *c)
        return (c->hba->cap & 0xf*Hiss)/Hiss;
 }
 
+static void iainterrupt(Ureg*, void *);
+
 static void
 checkdrive(Drive *d, int i)
 {
        ushort s, sig;
 
+       if(d->ctlr->enabled == 0)
+               return;
+       if(d->driveno == 0)
+               iainterrupt(0, d->ctlr);        /* check for missed irq's */
+
        ilock(d);
        s = d->port->sstatus;
        if(s)
@@ -1077,6 +1103,7 @@ reset:
                        else
                                d->mode--;
                        if(d->mode == DMautoneg){
+                               d->wait = 0;
                                d->state = Dportreset;
                                goto portreset;
                        }
@@ -1090,8 +1117,8 @@ reset:
                        if(d->unit == nil)
                                break;
                        if((++d->wait&Midwait) == 0){
-                               dprint("%s: slow reset %.3ux task=%lux; %d\n",
-                                       dnam(d), s, d->port->task, d->wait);
+                               dprint("%s: slow reset [task %lux; ss %.3ux; wait %d]\n",
+                                       dnam(d), d->port->task, s, d->wait);
                                goto reset;
                        }
                        s = (uchar)d->port->task;
@@ -1108,7 +1135,6 @@ reset:
        case Doffline:
                if(d->wait++ & Mcomrwait)
                        break;
-               /* fallthrough */
        case Derror:
        case Dreset:
                dprint("%s: reset [%s]: mode %d; status %.3ux\n",
@@ -1119,14 +1145,18 @@ reset:
                break;
        case Dportreset:
 portreset:
-               if(d->wait++ & 0xff && (s & Iactive) == 0)
+               if(d->wait++ & Mcomrwait)
                        break;
+               if(d->wait > Mcomrwait && (s & Iactive) == 0){
+                       d->state = Dnull;       /* stuck in portreset */
+                       break;
+               }
                dprint("%s: portreset [%s]: mode %d; status %.3ux\n",
                        dnam(d), diskstates[d->state], d->mode, s);
                d->portm.flag |= Ferror;
                clearci(d->port);
                wakeup(&d->portm);
-               if((s & Smask) == 0){
+               if((s & Smask) == Smissing){
                        d->state = Dmissing;
                        break;
                }
@@ -1144,6 +1174,8 @@ satakproc(void*)
 {
        int i;
 
+       while(waserror())
+               ;
        for(;;){
                tsleep(&up->sleep, return0, 0, Nms);
                for(i = 0; i < niadrive; i++)
@@ -1152,7 +1184,7 @@ satakproc(void*)
 }
 
 static void
-iainterrupt(Ureg*, void *a)
+iainterrupt(Ureg *u, void *a)
 {
        int i;
        ulong cause, m;
@@ -1169,11 +1201,13 @@ iainterrupt(Ureg*, void *a)
                cause &= ~m;
                d = c->rawdrive + i;
                ilock(d);
-               if(d->port->isr && c->hba->pi & m)
+               if(d->port != nil && d->port->isr && c->hba->pi & m)
                        updatedrive(d);
                c->hba->isr = m;
                iunlock(d);
        }
+       if(u == 0 && i > 0)
+               c->missirq++;
        iunlock(c);
 }
 
@@ -1181,14 +1215,19 @@ static int
 ahciencreset(Ctlr *c)
 {
        Ahba *h;
+       int i;
 
        if(c->enctype == Eesb)
                return 0;
        h = c->hba;
        h->emctl |= Emrst;
-       while(h->emctl & Emrst)
-               delay(1);
-       return 0;
+       for(i = 0; i < 1000; i++){
+               if((h->emctl & Emrst) == 0)
+                       return 0;
+               esleep(1);
+       }
+       print("%s: ahciencreset: hung ctlr\n", Tname(c));
+       return -1;
 }
 
 /*
@@ -1267,12 +1306,14 @@ blink(Drive *d, ulong t)
                return 0;
        c = d->ctlr;
        h = c->hba;
+
        /* ensure last message has been transmitted */
-       while(h->emctl & Tmsg)
-               microdelay(1);
+       if(h->emctl & Tmsg)
+               return -1;
+
        switch(c->enctype){
        default:
-               panic("%s: bad led type %d\n", dnam(d), c->enctype);
+               panic("%s: bad led type %d", dnam(d), c->enctype);
        case Elmt:
                memset(&msg, 0, sizeof msg);
                msg.type = Mled;
@@ -1369,30 +1410,34 @@ ledkproc(void*)
        memset(map, 0, sizeof map);
        for(i = 0; i < niactlr; i++)
                if(iactlr[i].enctype != 0){
-                       ahciencreset(iactlr + i);
+                       if(ahciencreset(iactlr + i) == -1)
+                               continue;
                        map[i] = 1;
                        j++;
                }
        if(j == 0)
                pexit("no work", 1);
        for(i = 0; i < niadrive; i++){
-               iadrive[i]->nled = 3;           /* hardcoded */
-               if(iadrive[i]->ctlr->enctype == Eesb)
-                       iadrive[i]->nled = 3;
-               iadrive[i]->ledbits = -1;
+               d = iadrive[i];
+               d->nled = 3;            /* hardcoded */
+               if(d->ctlr->enctype == Eesb)
+                       d->nled = 3;
+               d->ledbits = -1;
        }
        for(i = 0; ; i++){
                t0 = Ticks;
                for(j = 0; j < niadrive; ){
-                       c = iadrive[j]->ctlr;
-                       if(map[j] == 0)
-                               j += c->enctype;
-                       else if(c->enctype == Eesb){
+                       d = iadrive[j];
+                       c = d->ctlr;
+                       if(map[c - iactlr] == 0)
+                               j++;
+                       else
+                       if(c->enctype == Eesb){
                                blinkesb(c, i);
                                j += c->ndrive;
                        }else{
-                               d = iadrive[j++];
                                blink(d, i);
+                               j++;
                        }
                }
                t1 = Ticks;
@@ -1400,6 +1445,38 @@ ledkproc(void*)
        }
 }
 
+static int
+waitready(Drive *d)
+{
+       ulong s, i, δ;
+
+       for(i = 0;; i += 250){
+               if(d->state == Dreset || d->state == Dportreset || d->state == Dnew)
+                       return 1;
+               ilock(d);
+               s = d->port->sstatus;
+               if(d->state == Dready && (s & Smask) == Sphylink){
+                       iunlock(d);
+                       return 0;
+               }
+               δ = Ticks - d->lastseen;
+               if(d->state == Dnull || δ > 10*1000)
+                       break;
+               if((s & Imask) == 0 && δ > 1500)
+                       break;
+               if(i >= 15*1000){
+                       d->state = Doffline;
+                       iunlock(d);
+                       print("%s: not responding; offline\n", dnam(d));
+                       return -1;
+               }
+               iunlock(d);
+               esleep(250);
+       }
+       iunlock(d);
+       return -1;
+}
+
 static int
 iaverify(SDunit *u)
 {
@@ -1421,6 +1498,45 @@ iaverify(SDunit *u)
        return 1;
 }
 
+static int
+iaonline(SDunit *u)
+{
+       int r;
+       Ctlr *c;
+       Drive *d;
+
+       c = u->dev->ctlr;
+       d = c->drive[u->subno];
+
+       while(d->state != Dmissing && waitready(d) == 1)
+               esleep(1);
+
+       dprint("%s: iaonline: %s\n", dnam(d), diskstates[d->state]);
+
+       ilock(d);
+       if(d->portm.feat & Datapi){
+               r = d->drivechange;
+               d->drivechange = 0;
+               iunlock(d);
+               if(r != 0)
+                       scsiverify(u);
+               return scsionline(u);
+       }
+       r = 0;
+       if(d->drivechange){
+               d->drivechange = 0;
+               r = 2;
+       }else if(d->state == Dready)
+               r = 1;
+       if(r){
+               u->sectors = d->sectors;
+               u->secsize = d->secsize;
+       }
+       iunlock(d);
+
+       return r;
+}
+
 static int
 iaenable(SDev *s)
 {
@@ -1430,21 +1546,25 @@ iaenable(SDev *s)
 
        c = s->ctlr;
        ilock(c);
-       if(!c->enabled){
-               if(once == 0)
-                       kproc("iasata", satakproc, 0);
-               if(c->ndrive == 0)
-                       panic("iaenable: zero s->ctlr->ndrive");
-               pcisetbme(c->pci);
-               snprint(name, sizeof name, "%s (%s)", s->name, s->ifc->name);
-               intrenable(c->pci->intl, iainterrupt, c, c->pci->tbdf, name);
-               /* supposed to squelch leftover interrupts here. */
-               ahcienable(c->hba);
-               c->enabled = 1;
-               if(++once == niactlr)
-                       kproc("ialed", ledkproc, 0);
+       if(c->enabled){
+               iunlock(c);
+               return 1;
        }
+       if(c->ndrive == 0)
+               panic("iaenable: zero s->ctlr->ndrive");
+       pcisetbme(c->pci);
+       snprint(name, sizeof name, "%s (%s)", s->name, s->ifc->name);
+       intrenable(c->pci->intl, iainterrupt, c, c->pci->tbdf, name);
+       /* supposed to squelch leftover interrupts here. */
+       ahcienable(c->hba);
+       c->enabled = 1;
        iunlock(c);
+
+       if(once == 0)
+               kproc("iasata", satakproc, 0);
+       if(++once == niactlr)
+               kproc("ialed", ledkproc, 0);
+
        return 1;
 }
 
@@ -1464,124 +1584,62 @@ iadisable(SDev *s)
        return 1;
 }
 
-static int
-iaonline(SDunit *unit)
-{
-       int r;
-       Ctlr *c;
-       Drive *d;
-
-       c = unit->dev->ctlr;
-       d = c->drive[unit->subno];
-       r = 0;
-
-       if(d->portm.feat & Datapi && d->drivechange){
-               r = scsionline(unit);
-               if(r > 0)
-                       d->drivechange = 0;
-               return r;
-       }
-
-       ilock(d);
-       if(d->drivechange){
-               r = 2;
-               d->drivechange = 0;
-               /* devsd resets this after online is called; why? */
-               unit->sectors = d->sectors;
-               unit->secsize = d->secsize;
-       }else if(d->state == Dready)
-               r = 1;
-       iunlock(d);
-       return r;
-}
-
 static Alist*
-ahcibuild(Aportm *m, int rw, void *data, uint n, vlong lba)
+ahcibuild(Drive *d, int rw, void *data, int nsect, vlong lba)
 {
        uchar *c;
        uint flags;
-       Alist *l;
+       Aportm *m;
 
-       l = m->list;
+       m = &d->portm;
        c = m->ctab->cfis;
-       rwfis(m, c, rw, n, lba);
+       rwfis(m, c, rw, nsect, lba);
        flags = Lpref;
        if(rw == SDwrite)
                flags |= Lwrite;
-       mkalist(m, flags, data, 512*n);
-       return l;
+       return mkalist(m, flags, data, nsect * d->secsize);
 }
 
 static Alist*
-ahcibuildpkt(Aportm *m, SDreq *r, void *data, int n)
+ahcibuildpkt(Drive *d, SDreq *r, void *data, int n)
 {
        uint flags;
+       Aportm *m;
        uchar *c;
        Actab *t;
-       Alist *l;
 
-       l = m->list;
+       m = &d->portm;
        t = m->ctab;
        c = t->cfis;
-       atapirwfis(m, c, r->cmd, r->clen, n);
-       flags = 1<<16 | Lpref | Latapi;
+
+       atapirwfis(m, c, r->cmd, r->clen, 0x2000);
+       if((n & 15) != 0 || d->nodma)
+               c[Ffeat] &= ~1; /* use pio */
+       else if(c[Ffeat] & 1 && d->info[62] & (1<<15))  /* dma direction */
+               c[Ffeat] = (c[Ffeat] & ~(1<<2)) | ((r->write == 0) << 2);
+       flags = Lpref | Latapi;
        if(r->write != 0 && data)
                flags |= Lwrite;
-       mkalist(m, flags, data, n);
-       return l;
+       return mkalist(m, flags, data, n);
 }
 
 static Alist*
-ahcibuildfis(Aportm *m, SDreq *r, void *data, uint n)
+ahcibuildfis(Drive *d, SDreq *r, void *data, uint n)
 {
-       uchar *c;
        uint flags;
-       Alist *l;
-
-       l = m->list;
-       c = m->ctab->cfis;
-       if((r->ataproto & Pprotom) != Ppkt){
-               memmove(c, r->cmd, r->clen);
-               flags = Lpref;
-               if(r->write || n == 0)
-                       flags |= Lwrite;
-               mkalist(m, flags, data, n);
-       }else{
-               atapirwfis(m, c, r->cmd, r->clen, n);
-               flags = 1<<16 | Lpref | Latapi;
-               if(r->write && data)
-                       flags |= Lwrite;
-               mkalist(m, flags, data, n);
-       }
-       return l;
-}
+       uchar *c;
+       Aportm *m;
 
-static int
-waitready(Drive *d)
-{
-       ulong s, i, δ;
+       if((r->ataproto & Pprotom) == Ppkt)
+               return ahcibuildpkt(d, r, data, n);
 
-       for(i = 0; i < 15000; i += 250){
-               if(d->state == Dreset || d->state == Dportreset ||
-                   d->state == Dnew)
-                       return 1;
-               δ = Ticks - d->lastseen;
-               if(d->state == Dnull || δ > 10*1000)
-                       return -1;
-               ilock(d);
-               s = d->port->sstatus;
-               iunlock(d);
-               if((s & Imask) == 0 && δ > 1500)
-                       return -1;
-               if(d->state == Dready && (s & Smask) == Sphylink)
-                       return 0;
-               esleep(250);
-       }
-       print("%s: not responding; offline\n", dnam(d));
-       ilock(d);
-       d->state = Doffline;
-       iunlock(d);
-       return -1;
+       m = &d->portm;
+       c = m->ctab->cfis;
+       memmove(c, r->cmd, r->clen);
+       flags = Lpref;
+       if(r->write || n == 0)
+               flags |= Lwrite;
+       return mkalist(m, flags, data, n);
 }
 
 static int
@@ -1613,7 +1671,7 @@ flushcache(Drive *d)
 static int
 io(Drive *d, uint proto, int to, int interrupt)
 {
-       uint task, flag, rv;
+       uint task, flag, stat, rv;
        Aport *p;
        Asleep as;
 
@@ -1641,28 +1699,27 @@ io(Drive *d, uint proto, int to, int interrupt)
                if(interrupt){
                        d->active--;
                        d->port->ci = 0;
-                       if(ahcicomreset(&d->portc) == -1){
-                               ilock(d);
-                               d->state = Dreset;
-                               iunlock(d);
-                       }
+                       if(ahcicomreset(&d->portc) == -1)
+                               dstatus(d, Dreset);
                        return SDtimeout;
                }
+       
        sleep(&d->portm, ahciclear, &as);
        poperror();
 
        d->active--;
        ilock(d);
+       stat = d->state;
        flag = d->portm.flag;
        task = d->port->task;
        iunlock(d);
 
        rv = SDok;
-       if(proto & Ppkt){
+       if(proto & Ppkt && stat == Dready){
                rv = task >> 8 + 4 & 0xf;
                flag &= ~Fahdrs;
                flag |= Fdone;
-       }else if(task & (Efatal<<8) || task & (ASbsy|ASdrq) && d->state == Dready){
+       }else if(task & (Efatal<<8) || task & (ASbsy|ASdrq) && stat == Dready){
                d->port->ci = 0;
                ahcirecover(&d->portc);
                task = d->port->task;
@@ -1683,32 +1740,43 @@ io(Drive *d, uint proto, int to, int interrupt)
 static int
 iariopkt(SDreq *r, Drive *d)
 {
-       int n, count, try, max;
+       int try, to;
        uchar *cmd;
+       Alist *l;
 
        cmd = r->cmd;
        aprint("%s: %.2ux %.2ux %c %d %p\n", dnam(d), cmd[0], cmd[2],
                "rw"[r->write], r->dlen, r->data);
+
        r->rlen = 0;
-       count = r->dlen;
-       max = 65536;
+
+       /*
+        * prevent iaonline() to hang forever by timing out
+        * inquiry and capacity commands after 5 seconds.
+        */
+       to = 30*1000;
+       switch(cmd[0]){
+       case 0x9e: if(cmd[1] != 0x10) break;
+       case 0x25:
+       case 0x12:
+               to = 5*1000;
+               break;
+       }
 
        for(try = 0; try < 10; try++){
-               n = count;
-               if(n > max)
-                       n = max;
                qlock(&d->portm);
-               ahcibuildpkt(&d->portm, r, r->data, n);
-               r->status = io(d, Ppkt, 5000, 0);
-               qunlock(&d->portm);
+               l = ahcibuildpkt(d, r, r->data, r->dlen);
+               r->status = io(d, Ppkt, to, 0);
                switch(r->status){
                case SDeio:
+                       qunlock(&d->portm);
                        return SDeio;
                case SDretry:
+                       qunlock(&d->portm);
                        continue;
                }
-//             print("%.2ux :: %.2ux :: %.4ux\n", r->cmd[0], r->status, d->port->task);
-               r->rlen = d->portm.list->len;
+               r->rlen = l->len;
+               qunlock(&d->portm);
                return SDok;
        }
        print("%s: bad disk\n", dnam(d));
@@ -1736,12 +1804,15 @@ ahcibio(SDunit *u, int lun, int write, void *a, long count, uvlong lba)
        }
        rw = write? SDwrite: SDread;
        data = a;
+       dprint("%s: bio: %llud %c %lud %p\n",
+               dnam(d), lba, "rw"[rw], count, data);
+
        for(try = 0; try < 10;){
                n = count;
                if(n > max)
                        n = max;
                qlock(&d->portm);
-               ahcibuild(&d->portm, rw, data, n, lba);
+               ahcibuild(d, rw, data, n, lba);
                status = io(d, Pdma, 5000, 0);
                qunlock(&d->portm);
                switch(status){
@@ -1753,7 +1824,7 @@ ahcibio(SDunit *u, int lun, int write, void *a, long count, uvlong lba)
                }
                try = 0;
                count -= n;
-               lba   += n;
+               lba += n;
                data += n * u->secsize;
                if(count == 0)
                        return data - (uchar*)a;
@@ -1770,11 +1841,11 @@ iario(SDreq *r)
        uvlong lba;
        Ctlr *c;
        Drive *d;
-       SDunit *unit;
+       SDunit *u;
 
-       unit = r->unit;
-       c = unit->dev->ctlr;
-       d = c->drive[unit->subno];
+       u = r->unit;
+       c = u->dev->ctlr;
+       d = c->drive[u->subno];
        if(d->portm.feat & Datapi)
                return iariopkt(r, d);
        cmd = r->cmd;
@@ -1792,7 +1863,7 @@ iario(SDreq *r)
 
        if((i = sdfakescsirw(r, &lba, &count, &rw)) != SDnostatus)
                return i;
-       n = ahcibio(unit, r->lun, r->write, r->data, count, lba);
+       n = ahcibio(u, r->lun, r->write, r->data, count, lba);
        if(n == -1)
                return SDeio;
        r->rlen = n;
@@ -1802,8 +1873,7 @@ iario(SDreq *r)
 static uchar bogusrfis[16] = {
 [Ftype]                0x34,
 [Fioport]      0x40,
-[Fstatus]              0x50,
-
+[Fstatus]      0x50,
 [Fdev]         0xa0,
 };
 
@@ -1865,6 +1935,7 @@ iaataio(SDreq *r)
        Ctlr *c;
        Drive *d;
        SDunit *u;
+       Alist *l;
 
        u = r->unit;
        c = u->dev->ctlr;
@@ -1876,39 +1947,26 @@ iaataio(SDreq *r)
        sdr0(d);
        for(try = 0; try < 10; try++){
                qlock(&d->portm);
-               ahcibuildfis(&d->portm, r, r->data, r->dlen);
+               l = ahcibuildfis(d, r, r->data, r->dlen);
                r->status = io(d, r->ataproto & Pprotom, -1, 1);
-               qunlock(&d->portm);
                switch(r->status){
                case SDtimeout:
+                       qunlock(&d->portm);
                        return sdsetsense(r, SDcheck, 11, 0, 6);
                case SDeio:
+                       qunlock(&d->portm);
                        return SDeio;
                case SDretry:
+                       qunlock(&d->portm);
                        continue;
                }
-               r->rlen = r->dlen;
-               if((r->ataproto & Pprotom) == Ppkt)
-                       r->rlen = d->portm.list->len;
-               return sdr(r, d, r->status);
+               r->rlen = (r->ataproto & Pprotom) == Ppkt ? l->len : r->dlen;
+               try = sdr(r, d, r->status);
+               qunlock(&d->portm);
+               return try;
        }
        print("%s: bad disk\n", dnam(d));
-       r->status = SDeio;
-       return SDeio;
-}
-
-/*
- * configure drives 0-5 as ahci sata  (c.f. errata)
- */
-static int
-iaahcimode(Pcidev *p)
-{
-       uint u;
-
-       u = pcicfgr16(p, 0x92);
-       dprint("ahci: %T: iaahcimode %.2ux %.4ux\n", p->tbdf, pcicfgr8(p, 0x91), u);
-       pcicfgw16(p, 0x92, u | 0xf);    /* ports 0-15 */
-       return 0;
+       return r->status = SDeio;
 }
 
 enum{
@@ -1923,14 +1981,20 @@ enum{
 static void
 iasetupahci(Ctlr *c)
 {
+       if(c->type != Tich)
+               return;
+
        pcicfgw16(c->pci, 0x40, pcicfgr16(c->pci, 0x40) & ~Cmddec);
        pcicfgw16(c->pci, 0x42, pcicfgr16(c->pci, 0x42) & ~Cmddec);
 
        c->lmmio[Ghc] |= Ahcien;
-       c->lmmio[Pi] = (1 << 6) - 1;    /* 5 ports (supposedly ro pi reg) */
+       c->lmmio[Pi] = (1 << 6) - 1;    /* 6 ports (supposedly ro pi reg) */
 
        /* enable ahci mode; from ich9 datasheet */
        pcicfgw16(c->pci, 0x90, 1<<6 | 1<<5);
+
+       /* configure drives 0-5 as ahci sata  (c.f. errata) */
+       pcicfgw16(c->pci, 0x92, pcicfgr16(c->pci, 0x92) | 0xf);
 }
 
 static void
@@ -2006,10 +2070,20 @@ didtype(Pcidev *p)
        default:
                return -1;
        case 0x8086:
+               if((p->did & 0xffff) == 0x1e02)
+                       return Tich;            /* c210 */
+               if((p->did & 0xffff) == 0x8c02)
+                       return Tich;            /* c220 */
+               if((p->did & 0xffff) == 0x24d1)
+                       return Tich;            /* 82801eb/er */
+               if((p->did & 0xffff) == 0x2653)
+                       return Tich;            /* 82801fbm */
                if((p->did & 0xfffc) == 0x2680)
                        return Tesb;
                if((p->did & 0xfffb) == 0x27c1)
                        return Tich;            /* 82801g[bh]m */
+               if((p->did & 0xffff) == 0x2822)
+                       return Tich;            /* 82801 SATA RAID */
                if((p->did & 0xffff) == 0x2821)
                        return Tich;            /* 82801h[roh] */
                if((p->did & 0xfffe) == 0x2824)
@@ -2018,11 +2092,11 @@ didtype(Pcidev *p)
                        return Tich;            /* ich8 */
                if((p->did & 0xfffe) == 0x2922)
                        return Tich;            /* ich9 */
-               if((p->did & 0xffff)  == 0x3a02)
+               if((p->did & 0xffff) == 0x3a02)
                        return Tich;            /* 82801jd/do */
-               if((p->did & 0xfefe)  == 0x3a22)
+               if((p->did & 0xfefe) == 0x3a22)
                        return Tich;            /* ich10, pch */
-               if((p->did & 0xfff7)  == 0x3b28)
+               if((p->did & 0xfff7) == 0x3b28)
                        return Tich;            /* pchm */
                if((p->did & 0xfffe) == 0x3b22)
                        return Tich;            /* pch */
@@ -2041,6 +2115,13 @@ didtype(Pcidev *p)
                if(p->did == 0x3349)
                        return Tahci;
                break;
+       case 0x1022:
+               /* Hudson SATA Controller [AHCI mode] */
+               if((p->did & 0xfffe) == 0x7800){
+                       sbsetupahci(p);
+                       return Tahci;
+               }
+               break;
        case 0x10de:
        case 0x1039:
        case 0x1b4b:
@@ -2060,7 +2141,7 @@ static SDev*
 iapnp(void)
 {
        int i, n, nunit, type;
-       ulong io;
+       uintptr io;
        Ctlr *c;
        Drive *d;
        Pcidev *p;
@@ -2070,14 +2151,24 @@ iapnp(void)
        if(done)
                return nil;
        done = 1;
+
+       if(getconf("*noahci") != nil)
+               return nil;
+
+       if(getconf("*ahcidebug") != nil){
+               debug = 1;
+               datapi = 1;
+       }
+
        memset(olds, 0xff, sizeof olds);
        p = nil;
-loop:
        while((p = pcimatch(p, 0, 0)) != nil){
                if((type = didtype(p)) == -1)
                        continue;
-               if(p->mem[Abar].bar == 0)
+               io = p->mem[Abar].bar;
+               if(io == 0 || (io & 1) != 0 || p->mem[Abar].size < 0x180)
                        continue;
+               io &= ~0xf;
                if(niactlr == NCtlr){
                        print("iapnp: %s: too many controllers\n", tname[type]);
                        break;
@@ -2086,7 +2177,6 @@ loop:
                s = sdevs + niactlr;
                memset(c, 0, sizeof *c);
                memset(s, 0, sizeof *s);
-               io = p->mem[Abar].bar & ~0xf;
                c->mmio = vmap(io, p->mem[Abar].size);
                if(c->mmio == 0){
                        print("%s: address %#p in use did %.4ux\n",
@@ -2102,12 +2192,14 @@ loop:
                s->ctlr = c;
                c->sdev = s;
 
-               if(intel(c) && p->did != 0x2681)
+               pcienable(p);
+               ahcihandoff((Ahba*)c->mmio);
+               if(p->vid == 0x8086)
                        iasetupahci(c);
-//             ahcihbareset((Ahba*)c->mmio);
                nunit = ahciconf(c);
-               if(intel(c) && iaahcimode(p) == -1 || nunit < 1){
+               if(nunit < 1){
                        vunmap(c->mmio, p->mem[Abar].size);
+                       pcidisable(p);
                        continue;
                }
                c->ndrive = s->nunit = nunit;
@@ -2124,20 +2216,17 @@ loop:
                        d->ctlr = c;
                        if((c->hba->pi & 1<<i) == 0)
                                continue;
-                       snprint(d->name, sizeof d->name, "iahci%d.%d", niactlr, i);
-                       d->port = (Aport*)(c->mmio + 0x80*i + 0x100);
+                       io = 0x100 + 0x80*i;
+                       if((io + 0x80) > p->mem[Abar].size)
+                               continue;
+                       d->port = (Aport*)(c->mmio + io);
                        d->portc.p = d->port;
                        d->portc.m = &d->portm;
                        d->driveno = n++;
+                       snprint(d->name, sizeof d->name, "iahci%d.%d", niactlr, i);
                        c->drive[d->driveno] = d;
                        iadrive[niadrive + d->driveno] = d;
                }
-               for(i = 0; i < n; i++)
-                       if(ahciidle(c->drive[i]->port) == -1){
-                               print("%s: port %d wedged; abort\n",
-                                       Tname(c), i);
-                               goto loop;
-                       }
                for(i = 0; i < n; i++){
                        c->drive[i]->mode = DMautoneg;
                        configdrive(c->drive[i]);
@@ -2221,7 +2310,10 @@ iarctl(SDunit *u, char *p, int l)
        p = capfmt(p, e, ctab, nelem(ctab), o->cmd);
        p = seprint(p, e, "\n");
        p = seprint(p, e, "mode\t%s %s\n", modes[d->mode], modes[maxmode(c)]);
-       p = seprint(p, e, "geometry %llud %lud\n", d->sectors, u->secsize);
+       p = seprint(p, e, "geometry %llud %d\n", d->sectors, d->secsize);
+       p = seprint(p, e, "alignment %d %d\n", 
+               d->secsize<<d->portm.physshift, d->portm.physalign);
+       p = seprint(p, e, "missirq\t%ud\n", c->missirq);
        return p - op;
 }
 
@@ -2250,10 +2342,7 @@ forcestate(Drive *d, char *state)
                        break;
        if(i == nelem(diskstates))
                error(Ebadctl);
-       ilock(d);
-       d->state = i;
-//     statechange(d);
-       iunlock(d);
+       dstatus(d, i);
 }
 
 static int