#include "u.h" #include "../port/lib.h" #include "mem.h" #include "dat.h" #include "fns.h" #include "io.h" #include "ureg.h" #include "../port/error.h" #include "../port/sd.h" #include #define HOWMANY(x, y) (((x)+((y)-1))/(y)) #define ROUNDUP(x, y) (HOWMANY((x), (y))*(y)) #define uprint(...) snprint(up->genbuf, sizeof up->genbuf, __VA_ARGS__); #pragma varargck argpos atadebug 3 extern SDifc sdideifc; enum { DbgCONFIG = 0x0001, /* detected drive config info */ DbgIDENTIFY = 0x0002, /* detected drive identify info */ DbgSTATE = 0x0004, /* dump state on panic */ DbgPROBE = 0x0008, /* trace device probing */ DbgDEBUG = 0x0080, /* the current problem... */ DbgINL = 0x0100, /* That Inil20+ message we hate */ Dbg48BIT = 0x0200, /* 48-bit LBA */ DbgBsy = 0x0400, /* interrupt but Bsy (shared IRQ) */ DbgAtazz = 0x0800, /* debug raw ata io */ }; #define DEBUG (DbgDEBUG|DbgSTATE) enum { /* I/O ports */ Data = 0, Error = 1, /* (read) */ Features = 1, /* (write) */ Count = 2, /* sector count<7-0>, sector count<15-8> */ Ir = 2, /* interrupt reason (PACKET) */ Sector = 3, /* sector number */ Lbalo = 3, /* LBA<7-0>, LBA<31-24> */ Cyllo = 4, /* cylinder low */ Bytelo = 4, /* byte count low (PACKET) */ Lbamid = 4, /* LBA<15-8>, LBA<39-32> */ Cylhi = 5, /* cylinder high */ Bytehi = 5, /* byte count hi (PACKET) */ Lbahi = 5, /* LBA<23-16>, LBA<47-40> */ Dh = 6, /* Device/Head, LBA<27-24> */ Status = 7, /* (read) */ Command = 7, /* (write) */ As = 2, /* Alternate Status (read) */ Dc = 2, /* Device Control (write) */ }; enum { /* Error */ Med = 0x01, /* Media error */ Ili = 0x01, /* command set specific (PACKET) */ Nm = 0x02, /* No Media */ Eom = 0x02, /* command set specific (PACKET) */ Abrt = 0x04, /* Aborted command */ Mcr = 0x08, /* Media Change Request */ Idnf = 0x10, /* no user-accessible address */ Mc = 0x20, /* Media Change */ Unc = 0x40, /* Uncorrectable data error */ Wp = 0x40, /* Write Protect */ Icrc = 0x80, /* Interface CRC error */ }; enum { /* Features */ Dma = 0x01, /* data transfer via DMA (PACKET) */ Ovl = 0x02, /* command overlapped (PACKET) */ }; enum { /* Interrupt Reason */ Cd = 0x01, /* Command/Data */ Io = 0x02, /* I/O direction */ Rel = 0x04, /* Bus Release */ }; enum { /* Device/Head */ Dev0 = 0xA0, /* Master */ Dev1 = 0xB0, /* Slave */ Devs = Dev0 | Dev1, Lba = 0x40, /* LBA mode */ }; enum { /* Status, Alternate Status */ Err = 0x01, /* Error */ Chk = 0x01, /* Check error (PACKET) */ Drq = 0x08, /* Data Request */ Dsc = 0x10, /* Device Seek Complete */ Serv = 0x10, /* Service */ Df = 0x20, /* Device Fault */ Dmrd = 0x20, /* DMA ready (PACKET) */ Drdy = 0x40, /* Device Ready */ Bsy = 0x80, /* Busy */ }; enum { /* Command */ Cnop = 0x00, /* NOP */ Crs = 0x20, /* Read Sectors */ Crs48 = 0x24, /* Read Sectors Ext */ Crd48 = 0x25, /* Read w/ DMA Ext */ Crsm48 = 0x29, /* Read Multiple Ext */ Cws = 0x30, /* Write Sectors */ Cws48 = 0x34, /* Write Sectors Ext */ Cwd48 = 0x35, /* Write w/ DMA Ext */ Cwsm48 = 0x39, /* Write Multiple Ext */ Cedd = 0x90, /* Execute Device Diagnostics */ Cpkt = 0xA0, /* Packet */ Cidpkt = 0xA1, /* Identify Packet Device */ Crsm = 0xC4, /* Read Multiple */ Cwsm = 0xC5, /* Write Multiple */ Csm = 0xC6, /* Set Multiple */ Crd = 0xC8, /* Read DMA */ Cwd = 0xCA, /* Write DMA */ Cid = 0xEC, /* Identify Device */ }; enum { /* Device Control */ Nien = 0x02, /* (not) Interrupt Enable */ Srst = 0x04, /* Software Reset */ Hob = 0x80, /* High Order Bit [sic] */ }; enum { /* PCI Configuration Registers */ Bmiba = 0x20, /* Bus Master Interface Base Address */ Idetim = 0x40, /* IE Timing */ Sidetim = 0x44, /* Slave IE Timing */ Udmactl = 0x48, /* Ultra DMA/33 Control */ Udmatim = 0x4A, /* Ultra DMA/33 Timing */ }; enum { /* Bus Master IDE I/O Ports */ Bmicx = 0, /* Command */ Bmisx = 2, /* Status */ Bmidtpx = 4, /* Descriptor Table Pointer */ }; enum { /* Bmicx */ Ssbm = 0x01, /* Start/Stop Bus Master */ Rwcon = 0x08, /* Read/Write Control */ }; enum { /* Bmisx */ Bmidea = 0x01, /* Bus Master IDE Active */ Idedmae = 0x02, /* IDE DMA Error (R/WC) */ Ideints = 0x04, /* IDE Interrupt Status (R/WC) */ Dma0cap = 0x20, /* Drive 0 DMA Capable */ Dma1cap = 0x40, /* Drive 0 DMA Capable */ }; enum { /* Physical Region Descriptor */ PrdEOT = 0x80000000, /* End of Transfer */ }; enum { /* offsets into the identify info. */ Iconfig = 0, /* general configuration */ Ilcyl = 1, /* logical cylinders */ Ilhead = 3, /* logical heads */ Ilsec = 6, /* logical sectors per logical track */ Iserial = 10, /* serial number */ Ifirmware = 23, /* firmware revision */ Imodel = 27, /* model number */ Imaxrwm = 47, /* max. read/write multiple sectors */ Icapabilities = 49, /* capabilities */ Istandby = 50, /* device specific standby timer */ Ipiomode = 51, /* PIO data transfer mode number */ Ivalid = 53, Iccyl = 54, /* cylinders if (valid&0x01) */ Ichead = 55, /* heads if (valid&0x01) */ Icsec = 56, /* sectors if (valid&0x01) */ Iccap = 57, /* capacity if (valid&0x01) */ Irwm = 59, /* read/write multiple */ Ilba = 60, /* LBA size */ Imwdma = 63, /* multiword DMA mode */ Iapiomode = 64, /* advanced PIO modes supported */ Iminmwdma = 65, /* min. multiword DMA cycle time */ Irecmwdma = 66, /* rec. multiword DMA cycle time */ Iminpio = 67, /* min. PIO cycle w/o flow control */ Iminiordy = 68, /* min. PIO cycle with IORDY */ Ipcktbr = 71, /* time from PACKET to bus release */ Iserbsy = 72, /* time from SERVICE to !Bsy */ Iqdepth = 75, /* max. queue depth */ Imajor = 80, /* major version number */ Iminor = 81, /* minor version number */ Icsfs = 82, /* command set/feature supported */ Icsfe = 85, /* command set/feature enabled */ Iudma = 88, /* ultra DMA mode */ Ierase = 89, /* time for security erase */ Ieerase = 90, /* time for enhanced security erase */ Ipower = 91, /* current advanced power management */ Ilba48 = 100, /* 48-bit LBA size (64 bits in 100-103) */ Irmsn = 127, /* removable status notification */ Isecstat = 128, /* security status */ Icfapwr = 160, /* CFA power mode */ Imediaserial = 176, /* current media serial number */ Icksum = 255, /* checksum */ }; enum { /* bit masks for config identify info */ Mpktsz = 0x0003, /* packet command size */ Mincomplete = 0x0004, /* incomplete information */ Mdrq = 0x0060, /* DRQ type */ Mrmdev = 0x0080, /* device is removable */ Mtype = 0x1F00, /* device type */ Mproto = 0x8000, /* command protocol */ }; enum { /* bit masks for capabilities identify info */ Mdma = 0x0100, /* DMA supported */ Mlba = 0x0200, /* LBA supported */ Mnoiordy = 0x0400, /* IORDY may be disabled */ Miordy = 0x0800, /* IORDY supported */ Msoftrst = 0x1000, /* needs soft reset when Bsy */ Mqueueing = 0x4000, /* queueing overlap supported */ Midma = 0x8000, /* interleaved DMA supported */ }; enum { /* bit masks for supported/enabled features */ Msmart = 0x0001, Msecurity = 0x0002, Mrmmedia = 0x0004, Mpwrmgmt = 0x0008, Mpkt = 0x0010, Mwcache = 0x0020, Mlookahead = 0x0040, Mrelirq = 0x0080, Msvcirq = 0x0100, Mreset = 0x0200, Mprotected = 0x0400, Mwbuf = 0x1000, Mrbuf = 0x2000, Mnop = 0x4000, Mmicrocode = 0x0001, Mqueued = 0x0002, Mcfa = 0x0004, Mapm = 0x0008, Mnotify = 0x0010, Mspinup = 0x0040, Mmaxsec = 0x0100, Mautoacoustic = 0x0200, Maddr48 = 0x0400, Mdevconfov = 0x0800, Mflush = 0x1000, Mflush48 = 0x2000, Msmarterror = 0x0001, Msmartselftest = 0x0002, Mmserial = 0x0004, Mmpassthru = 0x0008, Mlogging = 0x0020, }; typedef struct Ctlr Ctlr; typedef struct Drive Drive; typedef struct Prd { /* Physical Region Descriptor */ ulong pa; /* Physical Base Address */ int count; } Prd; enum { BMspan = 32*1024, /* must be power of 2 <= 64*1024 */ Nprd = SDmaxio/BMspan+2, }; typedef struct Ctlr { int cmdport; int ctlport; int irq; int tbdf; int bmiba; /* bus master interface base address */ int maxio; /* sector count transfer maximum */ int span; /* don't span this boundary with dma */ int maxdma; /* don't attempt dma transfers bigger than this */ Pcidev* pcidev; void (*ienable)(Ctlr*); void (*idisable)(Ctlr*); SDev* sdev; Drive* drive[2]; Prd* prdt; /* physical region descriptor table */ void (*irqack)(Ctlr*); QLock; /* current command */ Drive* curdrive; int command; /* last command issued (debugging) */ Rendez; int done; uint nrq; uint nildrive; uint bsy; Lock; /* register access */ } Ctlr; typedef struct Drive { Ctlr* ctlr; SDunit *unit; int dev; ushort info[256]; Sfis; int dma; /* DMA R/W possible */ int dmactl; int rwm; /* read/write multiple possible */ int rwmctl; int pkt; /* PACKET device, length of pktcmd */ uchar pktcmd[16]; int pktdma; /* this PACKET command using dma */ uvlong sectors; uint secsize; char serial[20+1]; char firmware[8+1]; char model[40+1]; QLock; /* drive access */ int command; /* current command */ int write; uchar* data; int dlen; uchar* limit; int count; /* sectors */ int block; /* R/W bytes per block */ int status; int error; int flags; /* internal flags */ uint missirq; uint spurloop; uint irq; uint bsy; } Drive; enum { /* internal flags */ Lba48always = 0x2, /* ... */ Online = 0x4, /* drive onlined */ }; static void pc87415ienable(Ctlr* ctlr) { Pcidev *p; int x; p = ctlr->pcidev; if(p == nil) return; x = pcicfgr32(p, 0x40); if(ctlr->cmdport == p->mem[0].bar) x &= ~0x00000100; else x &= ~0x00000200; pcicfgw32(p, 0x40, x); } static void atadumpstate(Drive* drive, SDreq *r, uvlong lba, int count) { Prd *prd; Pcidev *p; Ctlr *ctlr; int i, bmiba, ccnt; uvlong clba; if(!(DEBUG & DbgSTATE)) return; ctlr = drive->ctlr; print("command %2.2uX\n", ctlr->command); print("data %8.8p limit %8.8p dlen %d status %uX error %uX\n", drive->data, drive->limit, drive->dlen, drive->status, drive->error); if(r->clen == -16) clba = fisrw(nil, r->cmd, &ccnt); else sdfakescsirw(r, &clba, &ccnt, 0); print("lba %llud -> %llud, count %d -> %d (%d)\n", clba, lba, ccnt, count, drive->count); if(!(inb(ctlr->ctlport+As) & Bsy)){ for(i = 1; i < 7; i++) print(" 0x%2.2uX", inb(ctlr->cmdport+i)); print(" 0x%2.2uX\n", inb(ctlr->ctlport+As)); } if(drive->command == Cwd || drive->command == Crd || drive->command == (Pdma|Pin) || drive->command == (Pdma|Pout)){ bmiba = ctlr->bmiba; prd = ctlr->prdt; print("bmicx %2.2uX bmisx %2.2uX prdt %8.8p\n", inb(bmiba+Bmicx), inb(bmiba+Bmisx), prd); for(;;){ print("pa 0x%8.8luX count %8.8uX\n", prd->pa, prd->count); if(prd->count & PrdEOT) break; prd++; } } if(ctlr->pcidev && ctlr->pcidev->vid == 0x8086){ p = ctlr->pcidev; print("0x40: %4.4uX 0x42: %4.4uX ", pcicfgr16(p, 0x40), pcicfgr16(p, 0x42)); print("0x48: %2.2uX\n", pcicfgr8(p, 0x48)); print("0x4A: %4.4uX\n", pcicfgr16(p, 0x4A)); } } static void atadebug(int cmdport, int ctlport, char* fmt, ...) { char *p, *e, buf[PRINTSIZE]; int i; va_list arg; if(!(DEBUG & DbgPROBE)) return; p = buf; e = buf + sizeof buf; va_start(arg, fmt); p = vseprint(p, e, fmt, arg); va_end(arg); if(cmdport){ if(p > buf && p[-1] == '\n') p--; p = seprint(p, e, " ataregs 0x%uX:", cmdport); for(i = Features; i < Command; i++) p = seprint(p, e, " 0x%2.2uX", inb(cmdport+i)); if(ctlport) p = seprint(p, e, " 0x%2.2uX", inb(ctlport+As)); p = seprint(p, e, "\n"); } putstrn(buf, p - buf); } static int ataready(int cmdport, int ctlport, int dev, int reset, int ready, int m) { int as, m0; atadebug(cmdport, ctlport, "ataready: dev %ux:%ux reset %ux ready %ux", cmdport, dev, reset, ready); m0 = m; do{ /* * Wait for the controller to become not busy and * possibly for a status bit to become true (usually * Drdy). Must change to the appropriate device * register set if necessary before testing for ready. * Always run through the loop at least once so it * can be used as a test for !Bsy. */ as = inb(ctlport+As); if(as & reset){ /* nothing to do */ } else if(dev){ outb(cmdport+Dh, dev); dev = 0; } else if(ready == 0 || (as & ready)){ atadebug(0, 0, "ataready: %d:%d %#.2ux\n", m, m0, as); return as; } microdelay(1); }while(m-- > 0); atadebug(0, 0, "ataready: timeout %d %#.2ux\n", m0, as); return -1; } static int atadone(void* arg) { return ((Ctlr*)arg)->done; } static int atarwmmode(Drive* drive, int cmdport, int ctlport, int dev) { int as, maxrwm, rwm; maxrwm = drive->info[Imaxrwm] & 0xFF; if(maxrwm == 0) return 0; /* * Sometimes drives come up with the current count set * to 0; if so, set a suitable value, otherwise believe * the value in Irwm if the 0x100 bit is set. */ if(drive->info[Irwm] & 0x100) rwm = drive->info[Irwm] & 0xFF; else rwm = 0; if(rwm == 0) rwm = maxrwm; if(rwm > 16) rwm = 16; if(ataready(cmdport, ctlport, dev, Bsy|Drq, Drdy, 102*1000) < 0) return 0; outb(cmdport+Count, rwm); outb(cmdport+Command, Csm); microdelay(1); as = ataready(cmdport, ctlport, 0, Bsy, Drdy|Df|Err, 1000); inb(cmdport+Status); if(as < 0 || (as & (Df|Err))) return 0; drive->rwm = rwm; return rwm; } static int atadmamode(SDunit *unit, Drive* drive) { char buf[32], *s; int dma; /* * Check if any DMA mode enabled. * Assumes the BIOS has picked and enabled the best. * This is completely passive at the moment, no attempt is * made to ensure the hardware is correctly set up. */ dma = drive->info[Imwdma] & 0x0707; drive->dma = (dma>>8) & dma; if(drive->dma == 0 && (drive->info[Ivalid] & 0x04)){ dma = drive->info[Iudma] & 0x7F7F; drive->dma = (dma>>8) & dma; if(drive->dma) drive->dma |= 'U'<<16; } if(unit != nil){ snprint(buf, sizeof buf, "*%sdma", unit->name); s = getconf(buf); if((s && !strcmp(s, "on")) || (!s && !getconf("*nodma"))) drive->dmactl = drive->dma; } return dma; } static int ataidentify(Ctlr*, int cmdport, int ctlport, int dev, int pkt, void* info) { int as, command, drdy; if(pkt){ command = Cidpkt; drdy = 0; } else{ command = Cid; drdy = Drdy; } dev &= ~Lba; as = ataready(cmdport, ctlport, dev, Bsy|Drq, drdy, 103*1000); if(as < 0) return as; outb(cmdport+Command, command); microdelay(1); as = ataready(cmdport, ctlport, 0, Bsy, Drq|Err, 400*1000); if(as < 0) return -1; if(as & Err) return as; memset(info, 0, 512); inss(cmdport+Data, info, 256); ataready(cmdport, ctlport, dev, Bsy|Drq, Drdy, 3*1000); inb(cmdport+Status); return 0; } static Drive* atadrive(SDunit *unit, Drive *drive, int cmdport, int ctlport, int dev) { int as, pkt; uchar buf[512], oserial[21]; uvlong osectors; Ctlr *ctlr; if(DEBUG & DbgIDENTIFY) print("identify: port %ux dev %.2ux\n", cmdport, dev & ~Lba); atadebug(0, 0, "identify: port 0x%uX dev 0x%2.2uX\n", cmdport, dev); pkt = 1; if(drive != nil){ osectors = drive->sectors; memmove(oserial, drive->serial, sizeof drive->serial); ctlr = drive->ctlr; }else{ osectors = 0; memset(oserial, 0, sizeof drive->serial); ctlr = nil; } retry: as = ataidentify(ctlr, cmdport, ctlport, dev, pkt, buf); if(as < 0) return nil; if(as & Err){ if(pkt == 0) return nil; pkt = 0; goto retry; } if(drive == 0){ if((drive = malloc(sizeof(Drive))) == nil) return nil; drive->serial[0] = ' '; drive->dev = dev; } memmove(drive->info, buf, sizeof(drive->info)); setfissig(drive, pkt? 0xeb140000: 0x0101); drive->sectors = idfeat(drive, drive->info); drive->secsize = idss(drive, drive->info); idmove(drive->serial, drive->info+10, 20); idmove(drive->firmware, drive->info+23, 8); idmove(drive->model, drive->info+27, 40); if(unit != nil){ memset(unit->inquiry, 0, sizeof unit->inquiry); unit->inquiry[2] = 2; unit->inquiry[3] = 2; unit->inquiry[4] = sizeof unit->inquiry - 4; memmove(unit->inquiry+8, drive->model, 40); } if(pkt){ drive->pkt = 12; if(drive->feat & Datapi16) drive->pkt = 16; }else{ drive->pkt = 0; if(drive->feat & Dlba) drive->dev |= Lba; atarwmmode(drive, cmdport, ctlport, dev); } atadmamode(unit, drive); if(osectors != 0 && memcmp(oserial, drive->serial, sizeof oserial) != 0) if(unit) unit->sectors = 0; drive->unit = unit; if(DEBUG & DbgCONFIG){ print("dev %2.2uX port %uX config %4.4uX capabilities %4.4uX", dev, cmdport, drive->info[Iconfig], drive->info[Icapabilities]); print(" mwdma %4.4uX", drive->info[Imwdma]); if(drive->info[Ivalid] & 0x04) print(" udma %4.4uX", drive->info[Iudma]); print(" dma %8.8uX rwm %ud", drive->dma, drive->rwm); if(drive->feat&Dllba) print("\tLLBA sectors %llud", drive->sectors); print("\n"); } return drive; } static void atasrst(int ctlport) { int dc0; /* * Srst is a big stick and may cause problems if further * commands are tried before the drives become ready again. * Also, there will be problems here if overlapped commands * are ever supported. */ dc0 = inb(ctlport+Dc); microdelay(5); outb(ctlport+Dc, Srst|dc0); microdelay(5); outb(ctlport+Dc, dc0); microdelay(2*1000); } static int seldev(int dev, int map) { if((dev & Devs) == Dev0 && map&1) return dev; if((dev & Devs) == Dev1 && map&2) return dev; return -1; } static SDev* ataprobe(int cmdport, int ctlport, int irq, int map) { Ctlr* ctlr; SDev *sdev; Drive *drive; int dev, error, rhi, rlo; static int nonlegacy = 'C'; if(ioalloc(cmdport, 8, 0, "atacmd") < 0) { print("ataprobe: Cannot allocate %X\n", cmdport); return nil; } if(ioalloc(ctlport+As, 1, 0, "atactl") < 0){ print("ataprobe: Cannot allocate %X\n", ctlport + As); iofree(cmdport); return nil; } /* * Try to detect a floating bus. * Bsy should be cleared. If not, see if the cylinder registers * are read/write capable. * If the master fails, try the slave to catch slave-only * configurations. * There's no need to restore the tested registers as they will * be reset on any detected drives by the Cedd command. * All this indicates is that there is at least one drive on the * controller; when the non-existent drive is selected in a * single-drive configuration the registers of the existing drive * are often seen, only command execution fails. */ if((dev = seldev(Dev0, map)) == -1) if((dev = seldev(Dev1, map)) == -1) goto release; if(inb(ctlport+As) & Bsy){ outb(cmdport+Dh, dev); microdelay(1); trydev1: atadebug(cmdport, ctlport, "ataprobe bsy"); outb(cmdport+Cyllo, 0xAA); outb(cmdport+Cylhi, 0x55); outb(cmdport+Sector, 0xFF); rlo = inb(cmdport+Cyllo); rhi = inb(cmdport+Cylhi); if(rlo != 0xAA && (rlo == 0xFF || rhi != 0x55)){ if(dev == Dev1 || (dev = seldev(Dev1, map)) == -1){ release: outb(cmdport+Dc, Nien); inb(cmdport+Status); /* further measures to prevent irqs? */ iofree(cmdport); iofree(ctlport+As); return nil; } if(ataready(cmdport, ctlport, dev, Bsy, 0, 20*1000) < 0) goto trydev1; } } /* * Disable interrupts on any detected controllers. */ outb(ctlport+Dc, Nien); tryedd1: if(ataready(cmdport, ctlport, dev, Bsy|Drq, 0, 105*1000) < 0){ /* * There's something there, but it didn't come up clean, * so try hitting it with a big stick. The timing here is * wrong but this is a last-ditch effort and it sometimes * gets some marginal hardware back online. */ atasrst(ctlport); if(ataready(cmdport, ctlport, dev, Bsy|Drq, 0, 106*1000) < 0) goto release; } /* * Can only get here if controller is not busy. * If there are drives Bsy will be set within 400nS, * must wait 2mS before testing Status. * Wait for the command to complete (6 seconds max). */ outb(cmdport+Command, Cedd); delay(2); if(ataready(cmdport, ctlport, dev, Bsy|Drq, 0, 6*1000*1000) < 0) goto release; /* * If bit 0 of the error register is set then the selected drive * exists. This is enough to detect single-drive configurations. * However, if the master exists there is no way short of executing * a command to determine if a slave is present. * It appears possible to get here testing Dev0 although it doesn't * exist and the EDD won't take, so try again with Dev1. */ error = inb(cmdport+Error); atadebug(cmdport, ctlport, "ataprobe: dev %uX", dev); if((error & ~0x80) != 0x01){ if(dev == Dev1) goto release; if((dev = seldev(Dev1, map)) == -1) goto release; goto tryedd1; } /* * At least one drive is known to exist, try to * identify it. If that fails, don't bother checking * any further. * If the one drive found is Dev0 and the EDD command * didn't indicate Dev1 doesn't exist, check for it. */ if((drive = atadrive(0, 0, cmdport, ctlport, dev)) == nil) goto release; if((ctlr = malloc(sizeof(Ctlr))) == nil){ free(drive); goto release; } if((sdev = malloc(sizeof(SDev))) == nil){ free(ctlr); free(drive); goto release; } drive->ctlr = ctlr; if(dev == Dev0){ ctlr->drive[0] = drive; if(!(error & 0x80)){ /* * Always leave Dh pointing to a valid drive, * otherwise a subsequent call to ataready on * this controller may try to test a bogus Status. * Ataprobe is the only place possibly invalid * drives should be selected. */ drive = atadrive(0, 0, cmdport, ctlport, Dev1); if(drive != nil){ drive->ctlr = ctlr; ctlr->drive[1] = drive; } else{ outb(cmdport+Dh, Dev0); microdelay(1); } } } else ctlr->drive[1] = drive; ctlr->cmdport = cmdport; ctlr->ctlport = ctlport; ctlr->irq = irq; ctlr->tbdf = BUSUNKNOWN; ctlr->command = Cedd; /* debugging */ switch(cmdport){ default: sdev->idno = nonlegacy; break; case 0x1F0: sdev->idno = 'C'; nonlegacy = 'E'; break; case 0x170: sdev->idno = 'D'; nonlegacy = 'E'; break; } sdev->ifc = &sdideifc; sdev->ctlr = ctlr; sdev->nunit = 2; ctlr->sdev = sdev; return sdev; } static void ataclear(SDev *sdev) { Ctlr* ctlr; ctlr = sdev->ctlr; iofree(ctlr->cmdport); iofree(ctlr->ctlport + As); if (ctlr->drive[0]) free(ctlr->drive[0]); if (ctlr->drive[1]) free(ctlr->drive[1]); if (sdev->name) free(sdev->name); if (sdev->unitflg) free(sdev->unitflg); if (sdev->unit) free(sdev->unit); free(ctlr); free(sdev); } static char * atastat(SDev *sdev, char *p, char *e) { Ctlr *ctlr; ctlr = sdev->ctlr; // return seprint(p, e, "%s ata port %X ctl %X irq %d %T\n", // sdev->name, ctlr->cmdport, ctlr->ctlport, ctlr->irq, ctlr->tbdf); return seprint(p, e, "%s ata port %X ctl %X irq %d\n", sdev->name, ctlr->cmdport, ctlr->ctlport, ctlr->irq); } static SDev* ataprobew(DevConf *cf) { char *p; ISAConf isa; if (cf->nports != 2) error(Ebadarg); memset(&isa, 0, sizeof isa); isa.port = cf->ports[0].port; isa.irq = cf->intnum; if((p=strchr(cf->type, '/')) == nil || pcmspecial(p+1, &isa) < 0) error("cannot find controller"); return ataprobe(cf->ports[0].port, cf->ports[1].port, cf->intnum, 3); } static void atainterrupt(Ureg*, void*); static int iowait(Drive *drive, int ms, int interrupt) { int msec, step; Ctlr *ctlr; step = 1000; if(drive->missirq > 10) step = 50; ctlr = drive->ctlr; for(msec = 0; msec < ms; msec += step){ while(waserror()) if(interrupt) return -1; tsleep(ctlr, atadone, ctlr, step); poperror(); if(ctlr->done) break; atainterrupt(nil, ctlr); if(ctlr->done){ if(drive->missirq++ < 3) print("ide: caught missed irq\n"); break; }else drive->spurloop++; } return ctlr->done; } static void atanop(Drive* drive, int subcommand) { Ctlr* ctlr; int as, cmdport, ctlport, timeo; /* * Attempt to abort a command by using NOP. * In response, the drive is supposed to set Abrt * in the Error register, set (Drdy|Err) in Status * and clear Bsy when done. However, some drives * (e.g. ATAPI Zip) just go Bsy then clear Status * when done, hence the timeout loop only on Bsy * and the forced setting of drive->error. */ ctlr = drive->ctlr; cmdport = ctlr->cmdport; outb(cmdport+Features, subcommand); outb(cmdport+Dh, drive->dev); ctlr->command = Cnop; /* debugging */ outb(cmdport+Command, Cnop); microdelay(1); ctlport = ctlr->ctlport; for(timeo = 0; timeo < 1000; timeo++){ as = inb(ctlport+As); if(!(as & Bsy)) break; microdelay(1); } drive->error |= Abrt; } static void ataabort(Drive* drive, int dolock) { /* * If NOP is available use it otherwise * must try a software reset. */ if(dolock) ilock(drive->ctlr); if(drive->feat & Dnop) atanop(drive, 0); else{ atasrst(drive->ctlr->ctlport); drive->error |= Abrt; } if(dolock) iunlock(drive->ctlr); } static int atadmasetup(Drive* drive, int len) { Prd *prd; ulong pa; Ctlr *ctlr; int bmiba, bmisx, count, i, span; ctlr = drive->ctlr; pa = PCIWADDR(drive->data); if(pa & 0x03) return -1; if(ctlr->maxdma && len > ctlr->maxdma) return -1; /* * Sometimes drives identify themselves as being DMA capable * although they are not on a busmastering controller. */ prd = ctlr->prdt; if(prd == nil){ drive->dmactl = 0; print("disabling dma: not on a busmastering controller\n"); return -1; } for(i = 0; len && i < Nprd; i++){ prd->pa = pa; span = ROUNDUP(pa, ctlr->span); if(span == pa) span += ctlr->span; count = span - pa; if(count >= len){ prd->count = PrdEOT|len; break; } prd->count = count; len -= count; pa += count; prd++; } if(i == Nprd) return -1; bmiba = ctlr->bmiba; outl(bmiba+Bmidtpx, PCIWADDR(ctlr->prdt)); if(drive->write) outb(bmiba+Bmicx, 0); else outb(bmiba+Bmicx, Rwcon); bmisx = inb(bmiba+Bmisx); outb(bmiba+Bmisx, bmisx|Ideints|Idedmae); return 0; } static void atadmastart(Ctlr* ctlr, int write) { if(write) outb(ctlr->bmiba+Bmicx, Ssbm); else outb(ctlr->bmiba+Bmicx, Rwcon|Ssbm); } static int atadmastop(Ctlr* ctlr) { int bmiba; bmiba = ctlr->bmiba; outb(bmiba+Bmicx, inb(bmiba+Bmicx) & ~Ssbm); return inb(bmiba+Bmisx); } static void atadmainterrupt(Drive* drive, int count) { Ctlr* ctlr; int bmiba, bmisx; ctlr = drive->ctlr; bmiba = ctlr->bmiba; bmisx = inb(bmiba+Bmisx); switch(bmisx & (Ideints|Idedmae|Bmidea)){ case Bmidea: /* * Data transfer still in progress, nothing to do * (this should never happen). */ return; case Ideints: case Ideints|Bmidea: /* * Normal termination, tidy up. */ drive->data += count; break; default: /* * What's left are error conditions (memory transfer * problem) and the device is not done but the PRD is * exhausted. For both cases must somehow tell the * drive to abort. */ ataabort(drive, 0); break; } atadmastop(ctlr); ctlr->done = 1; } static void atapktinterrupt(Drive* drive) { Ctlr* ctlr; int cmdport, len; ctlr = drive->ctlr; cmdport = ctlr->cmdport; switch(inb(cmdport+Ir) & (/*Rel|*/Io|Cd)){ case Cd: outss(cmdport+Data, drive->pktcmd, drive->pkt/2); break; case 0: if(drive->pktdma) goto Pktdma; len = (inb(cmdport+Bytehi)<<8)|inb(cmdport+Bytelo); if(drive->data+len > drive->limit){ atanop(drive, 0); break; } outss(cmdport+Data, drive->data, len/2); drive->data += len; break; case Io: if(drive->pktdma) goto Pktdma; len = (inb(cmdport+Bytehi)<<8)|inb(cmdport+Bytelo); if(drive->data+len > drive->limit){ atanop(drive, 0); break; } inss(cmdport+Data, drive->data, len/2); drive->data += len; break; case Io|Cd: if(drive->pktdma){ Pktdma: atadmainterrupt(drive, drive->dlen); } else ctlr->done = 1; break; } } static int atapktio0(Drive *drive, SDreq *r) { uchar *cmd; int as, cmdport, ctlport, len, rv; Ctlr *ctlr; rv = SDok; cmd = r->cmd; drive->command = Cpkt; memmove(drive->pktcmd, cmd, r->clen); memset(drive->pktcmd+r->clen, 0, drive->pkt-r->clen); drive->limit = drive->data+drive->dlen; ctlr = drive->ctlr; cmdport = ctlr->cmdport; ctlport = ctlr->ctlport; as = ataready(cmdport, ctlport, drive->dev, Bsy|Drq, Drdy, 107*1000); /* used to test as&Chk as failure too, but some CD readers use that for media change */ if(as < 0) return SDnostatus; ilock(ctlr); if(drive->dlen && drive->dmactl && !atadmasetup(drive, drive->dlen)){ drive->pktdma = Dma; len = 0; /* bytecount should be 0 for dma */ }else{ drive->pktdma = 0; if(drive->secsize) len = 16*drive->secsize; else len = 0x8000; } outb(cmdport+Features, drive->pktdma); outb(cmdport+Count, 0); outb(cmdport+Sector, 0); outb(cmdport+Bytelo, len); outb(cmdport+Bytehi, len>>8); outb(cmdport+Dh, drive->dev); ctlr->done = 0; ctlr->curdrive = drive; ctlr->command = Cpkt; /* debugging */ outb(cmdport+Command, Cpkt); if((drive->info[Iconfig] & Mdrq) != 0x0020){ microdelay(1); as = ataready(cmdport, ctlport, 0, Bsy, Drq|Chk, 4*1000); if(as < 0 || (as & (Bsy|Chk))){ drive->status = as<0 ? 0 : as; ctlr->curdrive = nil; ctlr->done = 1; rv = SDtimeout; }else atapktinterrupt(drive); } if(drive->pktdma) atadmastart(ctlr, drive->write); iunlock(ctlr); if(iowait(drive, 20*1000, 1) <= 0){ ilock(ctlr); ataabort(drive, 0); } else ilock(ctlr); if(drive->error){ if(drive->pktdma) atadmastop(ctlr); drive->status |= Chk; ctlr->curdrive = nil; } iunlock(ctlr); if(drive->status & Chk) rv = SDcheck; return rv; } static int atapktio(Drive* drive, SDreq *r) { int n; Ctlr *ctlr; ctlr = drive->ctlr; qlock(ctlr); n = atapktio0(drive, r); qunlock(ctlr); return n; } static uchar cmd48[256] = { [Crs] Crs48, [Crd] Crd48, [Crsm] Crsm48, [Cws] Cws48, [Cwd] Cwd48, [Cwsm] Cwsm48, }; enum{ Last28 = (1<<28) - 1 - 1, }; static int atageniostart(Drive* drive, uvlong lba) { Ctlr *ctlr; uchar cmd; int as, c, cmdport, ctlport, h, len, s, use48; use48 = 0; if((drive->flags&Lba48always) || lba > Last28 || drive->count > 256){ if((drive->feat & Dllba) == 0) return -1; use48 = 1; c = h = s = 0; }else if(drive->dev & Lba){ c = (lba>>8) & 0xFFFF; h = (lba>>24) & 0x0F; s = lba & 0xFF; }else{ if (drive->s == 0 || drive->h == 0){ print("sdide: chs address botch"); return -1; } c = lba/(drive->s*drive->h); h = (lba/drive->s) % drive->h; s = (lba % drive->s) + 1; } ctlr = drive->ctlr; cmdport = ctlr->cmdport; ctlport = ctlr->ctlport; if(ataready(cmdport, ctlport, drive->dev, Bsy|Drq, Drdy, 101*1000) < 0) return -1; ilock(ctlr); if(drive->dmactl && !atadmasetup(drive, drive->count*drive->secsize)){ if(drive->write) drive->command = Cwd; else drive->command = Crd; } else if(drive->rwmctl){ drive->block = drive->rwm*drive->secsize; if(drive->write) drive->command = Cwsm; else drive->command = Crsm; } else{ drive->block = drive->secsize; if(drive->write) drive->command = Cws; else drive->command = Crs; } drive->limit = drive->data + drive->count*drive->secsize; cmd = drive->command; if(use48){ outb(cmdport+Count, drive->count>>8); outb(cmdport+Count, drive->count); outb(cmdport+Lbalo, lba>>24); outb(cmdport+Lbalo, lba); outb(cmdport+Lbamid, lba>>32); outb(cmdport+Lbamid, lba>>8); outb(cmdport+Lbahi, lba>>40); outb(cmdport+Lbahi, lba>>16); outb(cmdport+Dh, drive->dev|Lba); cmd = cmd48[cmd]; if(DEBUG & Dbg48BIT) print("using 48-bit commands\n"); }else{ outb(cmdport+Count, drive->count); outb(cmdport+Sector, s); outb(cmdport+Cyllo, c); outb(cmdport+Cylhi, c>>8); outb(cmdport+Dh, drive->dev|h); } ctlr->done = 0; ctlr->curdrive = drive; ctlr->command = drive->command; /* debugging */ outb(cmdport+Command, cmd); switch(drive->command){ case Cws: case Cwsm: microdelay(1); /* 10*1000 for flash ide drives - maybe detect them? */ as = ataready(cmdport, ctlport, 0, Bsy, Drq|Err, 10*1000); if(as < 0 || (as & Err)){ iunlock(ctlr); return -1; } len = drive->block; if(drive->data+len > drive->limit) len = drive->limit-drive->data; outss(cmdport+Data, drive->data, len/2); break; case Crd: case Cwd: atadmastart(ctlr, drive->write); break; } iunlock(ctlr); return 0; } static int atagenioretry(Drive* drive, SDreq *r, uvlong lba, int count) { char *s; int rv, count0, rw; uvlong lba0; if(drive->dmactl){ drive->dmactl = 0; s = "disabling dma"; rv = SDretry; }else if(drive->rwmctl){ drive->rwmctl = 0; s = "disabling rwm"; rv = SDretry; }else{ s = "nondma"; rv = sdsetsense(r, SDcheck, 4, 8, drive->error); } sdfakescsirw(r, &lba0, &count0, &rw); print("atagenioretry: %s %c:%llud:%d @%llud:%d\n", s, "rw"[rw], lba0, count0, lba, count); return rv; } static int atagenio(Drive* drive, SDreq *r) { Ctlr *ctlr; uvlong lba; int i, rw, count, maxio; if((i = sdfakescsi(r)) != SDnostatus) return i; if((i = sdfakescsirw(r, &lba, &count, &rw)) != SDnostatus) return i; ctlr = drive->ctlr; if(drive->data == nil) return SDok; if(drive->dlen < count*drive->secsize) count = drive->dlen/drive->secsize; qlock(ctlr); if(ctlr->maxio) maxio = ctlr->maxio; else if(drive->feat & Dllba) maxio = 65536; else maxio = 256; while(count){ if(count > maxio) drive->count = maxio; else drive->count = count; if(atageniostart(drive, lba)){ ilock(ctlr); atanop(drive, 0); iunlock(ctlr); qunlock(ctlr); return atagenioretry(drive, r, lba, count); } iowait(drive, 60*1000, 0); if(!ctlr->done){ /* * What should the above timeout be? In * standby and sleep modes it could take as * long as 30 seconds for a drive to respond. * Very hard to get out of this cleanly. */ atadumpstate(drive, r, lba, count); ataabort(drive, 1); qunlock(ctlr); return atagenioretry(drive, r, lba, count); } if(drive->status & Err){ qunlock(ctlr); print("atagenio: %llud:%d\n", lba, drive->count); return sdsetsense(r, SDcheck, 4, 8, drive->error); } count -= drive->count; lba += drive->count; } qunlock(ctlr); return SDok; } static int atario(SDreq* r) { uchar *p; int status; Ctlr *ctlr; Drive *drive; SDunit *unit; unit = r->unit; if((ctlr = unit->dev->ctlr) == nil || ctlr->drive[unit->subno] == nil){ r->status = SDtimeout; return SDtimeout; } drive = ctlr->drive[unit->subno]; qlock(drive); for(;;){ drive->write = r->write; drive->data = r->data; drive->dlen = r->dlen; drive->status = 0; drive->error = 0; if(drive->pkt) status = atapktio(drive, r); else status = atagenio(drive, r); if(status != SDretry) break; if(DbgDEBUG) print("%s: retry: dma %8.8uX rwm %4.4uX\n", unit->name, drive->dmactl, drive->rwmctl); } if(status == SDok && r->rlen == 0 && (r->flags & SDvalidsense) == 0){ sdsetsense(r, SDok, 0, 0, 0); if(drive->data){ p = r->data; r->rlen = drive->data - p; } else r->rlen = 0; } qunlock(drive); return status; } /**/ static int isdmacmd(Drive *d, SDreq *r) { switch(r->ataproto & Pprotom){ default: return 0; case Pdmq: error("no queued support"); case Pdma: if(!(d->dmactl || d->rwmctl)) error("dma in non dma mode\n"); return 1; } } static int atagenatastart(Drive* d, SDreq *r) { uchar u; int as, cmdport, ctlport, len, pr, isdma; Ctlr *ctlr; isdma = isdmacmd(d, r); ctlr = d->ctlr; cmdport = ctlr->cmdport; ctlport = ctlr->ctlport; if(ataready(cmdport, ctlport, d->dev, Bsy|Drq, d->pkt? 0: Drdy, 101*1000) < 0) return -1; ilock(ctlr); if(isdma && atadmasetup(d, d->block)){ iunlock(ctlr); return -1; } if(d->feat & Dllba && (r->ataproto & P28) == 0){ outb(cmdport+Features, r->cmd[Ffeat8]); outb(cmdport+Features, r->cmd[Ffeat]); outb(cmdport+Count, r->cmd[Fsc8]); outb(cmdport+Count, r->cmd[Fsc]); outb(cmdport+Lbalo, r->cmd[Flba24]); outb(cmdport+Lbalo, r->cmd[Flba0]); outb(cmdport+Lbamid, r->cmd[Flba32]); outb(cmdport+Lbamid, r->cmd[Flba8]); outb(cmdport+Lbahi, r->cmd[Flba40]); outb(cmdport+Lbahi, r->cmd[Flba16]); u = r->cmd[Fdev] & ~0xb0; outb(cmdport+Dh, d->dev|u); }else{ outb(cmdport+Features, r->cmd[Ffeat]); outb(cmdport+Count, r->cmd[Fsc]); outb(cmdport+Lbalo, r->cmd[Flba0]); outb(cmdport+Lbamid, r->cmd[Flba8]); outb(cmdport+Lbahi, r->cmd[Flba16]); u = r->cmd[Fdev] & ~0xb0; outb(cmdport+Dh, d->dev|u); } ctlr->done = 0; ctlr->curdrive = d; d->command = r->ataproto & (Pprotom|Pdatam); ctlr->command = r->cmd[Fcmd]; outb(cmdport+Command, r->cmd[Fcmd]); pr = r->ataproto & Pprotom; if(pr == Pnd || pr == Preset) USED(d); else if(!isdma){ microdelay(1); /* 10*1000 for flash ide drives - maybe detect them? */ as = ataready(cmdport, ctlport, 0, Bsy, Drq|Err, 10*1000); if(as < 0 || (as & Err)){ iunlock(ctlr); return -1; } len = d->block; if(r->write && len > 0) outss(cmdport+Data, d->data, len/2); }else atadmastart(ctlr, d->write); iunlock(ctlr); return 0; } static void mkrfis(Drive *d, SDreq *r) { uchar *u; int cmdport; Ctlr *ctlr; ctlr = d->ctlr; cmdport = ctlr->cmdport; u = r->cmd; ilock(ctlr); u[Ftype] = 0x34; u[Fioport] = 0; if((d->feat & Dllba) && (r->ataproto & P28) == 0){ u[Frerror] = inb(cmdport+Error); u[Fsc8] = inb(cmdport+Count); u[Fsc] = inb(cmdport+Count); u[Flba24] = inb(cmdport+Lbalo); u[Flba0] = inb(cmdport+Lbalo); u[Flba32] = inb(cmdport+Lbamid); u[Flba8] = inb(cmdport+Lbamid); u[Flba40] = inb(cmdport+Lbahi); u[Flba16] = inb(cmdport+Lbahi); u[Fdev] = inb(cmdport+Dh); u[Fstatus] = inb(cmdport+Status); }else{ u[Frerror] = inb(cmdport+Error); u[Fsc] = inb(cmdport+Count); u[Flba0] = inb(cmdport+Lbalo); u[Flba8] = inb(cmdport+Lbamid); u[Flba16] = inb(cmdport+Lbahi); u[Fdev] = inb(cmdport+Dh); u[Fstatus] = inb(cmdport+Status); } iunlock(ctlr); } static int atarstdone(Drive *d) { int as; Ctlr *c; c = d->ctlr; as = ataready(c->cmdport, c->ctlport, 0, Bsy|Drq, 0, 5*1000); c->done = as >= 0; return c->done; } static uint cmdss(Drive *d, SDreq *r) { switch(r->cmd[Fcmd]){ case Cid: case Cidpkt: return 512; default: return d->secsize; } } /* * various checks. we should be craftier and * avoid figuring out how big stuff is supposed to be. */ static uint patasizeck(Drive *d, SDreq *r) { uint count, maxio, secsize; Ctlr *ctlr; secsize = cmdss(d, r); /* BOTCH */ if(secsize == 0) error(Eio); count = r->dlen / secsize; ctlr = d->ctlr; if(ctlr->maxio) maxio = ctlr->maxio; else if((d->feat & Dllba) && (r->ataproto & P28) == 0) maxio = 65536; else maxio = 256; if(count > maxio){ uprint("i/o too large, lim %d", maxio); error(up->genbuf); } if(r->ataproto&Ppio && count > 1) error("invalid # of sectors"); return count; } static int atapataio(Drive *d, SDreq *r) { int rv; Ctlr *ctlr; d->count = 0; if(r->ataproto & Pdatam) d->count = patasizeck(d, r); d->block = r->dlen; d->limit = d->data + r->dlen; ctlr = d->ctlr; qlock(ctlr); if(waserror()){ qunlock(ctlr); nexterror(); } rv = atagenatastart(d, r); poperror(); if(rv){ if(DEBUG & DbgAtazz) print("sdide: !atageatastart\n"); ilock(ctlr); atanop(d, 0); iunlock(ctlr); qunlock(ctlr); return sdsetsense(r, SDcheck, 4, 8, d->error); } if((r->ataproto & Pprotom) == Preset) atarstdone(d); else while(iowait(d, 30*1000, 1) == 0) ; if(!ctlr->done){ if(DEBUG & DbgAtazz){ print("sdide: !done\n"); atadumpstate(d, r, 0, d->count); } ataabort(d, 1); qunlock(ctlr); return sdsetsense(r, SDcheck, 11, 0, 6); /* aborted; i/o process terminated */ } mkrfis(d, r); if(d->status & Err){ if(DEBUG & DbgAtazz) print("sdide: status&Err\n"); qunlock(ctlr); return sdsetsense(r, SDcheck, 4, 8, d->error); } qunlock(ctlr); return SDok; } static int ataataio0(Drive *d, SDreq *r) { int i; if((r->ataproto & Pprotom) == Ppkt){ if(r->clen > d->pkt) error(Eio); qlock(d->ctlr); i = atapktio0(d, r); d->block = d->data - (uchar*)r->data; mkrfis(d, r); qunlock(d->ctlr); return i; }else return atapataio(d, r); } /* * hack to allow udma mode to be set or unset * via direct ata command. it would be better * to move the assumptions about dma mode out * of some of the helper functions. */ static int isudm(SDreq *r) { uchar *c; c = r->cmd; if(c[Fcmd] == 0xef && c[Ffeat] == 0x03){ if(c[Fsc]&0x40) return 1; return -1; } return 0; } static int fisreqchk(Sfis *f, SDreq *r) { if((r->ataproto & Pprotom) == Ppkt) return SDnostatus; /* * handle oob requests; * restrict & sanitize commands */ if(r->clen != 16) error(Eio); if(r->cmd[0] == 0xf0){ sigtofis(f, r->cmd); r->status = SDok; return SDok; } r->cmd[0] = 0x27; r->cmd[1] = 0x80; r->cmd[7] |= 0xa0; return SDnostatus; } static int ataataio(SDreq *r) { int status, udm; Ctlr *c; Drive *d; SDunit *u; u = r->unit; if((c = u->dev->ctlr) == nil || (d = c->drive[u->subno]) == nil){ r->status = SDtimeout; return SDtimeout; } if((status = fisreqchk(d, r)) != SDnostatus) return status; udm = isudm(r); qlock(d); if(waserror()){ qunlock(d); nexterror(); } retry: d->write = r->write; d->data = r->data; d->dlen = r->dlen; d->status = 0; d->error = 0; switch(status = ataataio0(d, r)){ case SDretry: if(DbgDEBUG) print("%s: retry: dma %.8ux rwm %.4ux\n", u->name, d->dmactl, d->rwmctl); goto retry; case SDok: if(udm == 1) d->dmactl = d->dma; else if(udm == -1) d->dmactl = 0; sdsetsense(r, SDok, 0, 0, 0); r->rlen = d->block; break; } poperror(); qunlock(d); r->status = status; return status; } /**/ static void ichirqack(Ctlr *ctlr) { int bmiba; if(bmiba = ctlr->bmiba) outb(bmiba+Bmisx, inb(bmiba+Bmisx)); } static void atainterrupt(Ureg*, void* arg) { Ctlr *ctlr; Drive *drive; int cmdport, len, status; ctlr = arg; ilock(ctlr); ctlr->nrq++; if(ctlr->curdrive) ctlr->curdrive->irq++; if(inb(ctlr->ctlport+As) & Bsy){ ctlr->bsy++; if(ctlr->curdrive) ctlr->curdrive->bsy++; iunlock(ctlr); if(DEBUG & DbgBsy) print("IBsy+"); return; } cmdport = ctlr->cmdport; status = inb(cmdport+Status); if((drive = ctlr->curdrive) == nil){ ctlr->nildrive++; if(ctlr->irqack != nil) ctlr->irqack(ctlr); iunlock(ctlr); if((DEBUG & DbgINL) && ctlr->command != Cedd) print("Inil%2.2uX+", ctlr->command); return; } if(status & Err) drive->error = inb(cmdport+Error); else switch(drive->command){ default: drive->error = Abrt; break; case Crs: case Crsm: case Ppio|Pin: if(!(status & Drq)){ drive->error = Abrt; break; } len = drive->block; if(drive->data+len > drive->limit) len = drive->limit-drive->data; inss(cmdport+Data, drive->data, len/2); drive->data += len; if(drive->data >= drive->limit) ctlr->done = 1; break; case Cws: case Cwsm: case Ppio|Pout: len = drive->block; if(drive->data+len > drive->limit) len = drive->limit-drive->data; drive->data += len; if(drive->data >= drive->limit){ ctlr->done = 1; break; } if(!(status & Drq)){ drive->error = Abrt; break; } len = drive->block; if(drive->data+len > drive->limit) len = drive->limit-drive->data; outss(cmdport+Data, drive->data, len/2); break; case Cpkt: case Ppkt|Pin: case Ppkt|Pout: atapktinterrupt(drive); break; case Crd: case Cwd: case Pdma|Pin: case Pdma|Pout: atadmainterrupt(drive, drive->count*drive->secsize); break; case Pnd: case Preset: ctlr->done = 1; break; } if(ctlr->irqack != nil) ctlr->irqack(ctlr); iunlock(ctlr); if(drive->error){ status |= Err; ctlr->done = 1; } if(ctlr->done){ ctlr->curdrive = nil; drive->status = status; wakeup(ctlr); } } typedef struct Lchan Lchan; struct Lchan { int cmdport; int ctlport; int irq; int probed; }; static Lchan lchan[2] = { 0x1f0, 0x3f4, IrqATA0, 0, 0x170, 0x374, IrqATA1, 0, }; static int badccru(Pcidev *p) { switch(p->did<<16 | p->did){ case 0x439c<<16 | 0x1002: case 0x438c<<16 | 0x1002: print("hi, anothy\n"); print("%T: allowing bad ccru %.2ux for suspected ide controller\n", p->tbdf, p->ccru); return 1; default: return 0; } } static SDev* atapnp(void) { char *s; int channel, map, ispc87415, maxio, pi, r, span, maxdma, tbdf; Ctlr *ctlr; Pcidev *p; SDev *sdev, *head, *tail; void (*irqack)(Ctlr*); head = tail = nil; for(p = nil; p = pcimatch(p, 0, 0); ){ /* * Look for devices with the correct class and sub-class * code and known device and vendor ID; add native-mode * channels to the list to be probed, save info for the * compatibility mode channels. * Note that the legacy devices should not be considered * PCI devices by the interrupt controller. * For both native and legacy, save info for busmastering * if capable. * Promise Ultra ATA/66 (PDC20262) appears to * 1) give a sub-class of 'other mass storage controller' * instead of 'IDE controller', regardless of whether it's * the only controller or not; * 2) put 0 in the programming interface byte (probably * as a consequence of 1) above). * Sub-class code 0x04 is 'RAID controller', e.g. VIA VT8237. */ if(p->ccrb != 0x01) continue; if(!badccru(p)) if(p->ccru != 0x01 && p->ccru != 0x04 && p->ccru != 0x80) continue; pi = p->ccrp; map = 3; ispc87415 = 0; maxdma = 0; maxio = 0; if(s = getconf("*idemaxio")) maxio = atoi(s); span = BMspan; irqack = nil; switch((p->did<<16)|p->vid){ default: continue; case (0x0002<<16)|0x100B: /* NS PC87415 */ /* * Disable interrupts on both channels until * after they are probed for drives. * This must be called before interrupts are * enabled because the IRQ may be shared. */ ispc87415 = 1; pcicfgw32(p, 0x40, 0x00000300); break; case (0x1000<<16)|0x1042: /* PC-Tech RZ1000 */ /* * Turn off prefetch. Overkill, but cheap. */ r = pcicfgr32(p, 0x40); r &= ~0x2000; pcicfgw32(p, 0x40, r); break; case (0x4D38<<16)|0x105A: /* Promise PDC20262 */ case (0x4D30<<16)|0x105A: /* Promise PDC202xx */ case (0x4D68<<16)|0x105A: /* Promise PDC20268 */ case (0x4D69<<16)|0x105A: /* Promise Ultra/133 TX2 */ case (0x3373<<16)|0x105A: /* Promise 20378 RAID */ case (0x3149<<16)|0x1106: /* VIA VT8237 SATA/RAID */ case (0x3112<<16)|0x1095: /* SiL 3112 SATA/RAID */ maxio = 15; span = 8*1024; /*FALLTHROUGH*/ case (0x3114<<16)|0x1095: /* SiL 3114 SATA/RAID */ case (0x0680<<16)|0x1095: /* SiI 0680/680A PATA133 ATAPI/RAID */ pi = 0x85; break; case (0x0004<<16)|0x1103: /* HighPoint HPT366 */ pi = 0x85; /* * Turn off fast interrupt prediction. */ if((r = pcicfgr8(p, 0x51)) & 0x80) pcicfgw8(p, 0x51, r & ~0x80); if((r = pcicfgr8(p, 0x55)) & 0x80) pcicfgw8(p, 0x55, r & ~0x80); break; case (0x0640<<16)|0x1095: /* CMD 640B */ /* * Bugfix code here... */ break; case (0x7441<<16)|0x1022: /* AMD 768 */ /* * Set: * 0x41 prefetch, postwrite; * 0x43 FIFO configuration 1/2 and 1/2; * 0x44 status register read retry; * 0x46 DMA read and end of sector flush. */ r = pcicfgr8(p, 0x41); pcicfgw8(p, 0x41, r|0xF0); r = pcicfgr8(p, 0x43); pcicfgw8(p, 0x43, (r & 0x90)|0x2A); r = pcicfgr8(p, 0x44); pcicfgw8(p, 0x44, r|0x08); r = pcicfgr8(p, 0x46); pcicfgw8(p, 0x46, (r & 0x0C)|0xF0); /*FALLTHROUGH*/ case (0x01BC<<16)|0x10DE: /* nVidia nForce1 */ case (0x0065<<16)|0x10DE: /* nVidia nForce2 */ case (0x0085<<16)|0x10DE: /* nVidia nForce2 MCP */ case (0x00E3<<16)|0x10DE: /* nVidia nForce2 250 SATA */ case (0x00D5<<16)|0x10DE: /* nVidia nForce3 */ case (0x00E5<<16)|0x10DE: /* nVidia nForce3 Pro */ case (0x00EE<<16)|0x10DE: /* nVidia nForce3 250 SATA */ case (0x0035<<16)|0x10DE: /* nVidia nForce3 MCP */ case (0x0053<<16)|0x10DE: /* nVidia nForce4 */ case (0x0054<<16)|0x10DE: /* nVidia nForce4 SATA */ case (0x0055<<16)|0x10DE: /* nVidia nForce4 SATA */ case (0x0266<<16)|0x10DE: /* nVidia nForce4 430 SATA */ case (0x0265<<16)|0x10DE: /* nVidia nForce 51 MCP */ case (0x0267<<16)|0x10DE: /* nVidia nForce 55 MCP SATA */ case (0x03ec<<16)|0x10DE: /* nVidia nForce 61 MCP SATA */ case (0x03f6<<16)|0x10DE: /* nVidia nForce 61 MCP PATA */ case (0x0448<<16)|0x10DE: /* nVidia nForce 65 MCP SATA */ case (0x0560<<16)|0x10DE: /* nVidia nForce 69 MCP SATA */ /* * Ditto, although it may have a different base * address for the registers (0x50?). */ /*FALLTHROUGH*/ case (0x209A<<16)|0x1022: /* AMD CS5536 */ case (0x7401<<16)|0x1022: /* AMD 755 Cobra */ case (0x7409<<16)|0x1022: /* AMD 756 Viper */ case (0x7410<<16)|0x1022: /* AMD 766 Viper Plus */ case (0x7469<<16)|0x1022: /* AMD 3111 */ case (0x4376<<16)|0x1002: /* SB4xx pata */ case (0x4379<<16)|0x1002: /* SB4xx sata */ case (0x437a<<16)|0x1002: /* SB4xx sata ctlr #2 */ case (0x437c<<16)|0x1002: /* Rx6xx pata */ case (0x438c<<16)|0x1002: /* ATI SB600 PATA */ case (0x439c<<16)|0x1002: /* SB7xx pata */ break; case (0x0211<<16)|0x1166: /* ServerWorks IB6566 */ { Pcidev *sb; sb = pcimatch(nil, 0x1166, 0x0200); if(sb == nil) break; r = pcicfgr32(sb, 0x64); r &= ~0x2000; pcicfgw32(sb, 0x64, r); } span = 32*1024; break; case (0x5229<<16)|0x10B9: /* ALi M1543 */ case (0x5288<<16)|0x10B9: /* ALi M5288 SATA */ /*FALLTHROUGH*/ case (0x5513<<16)|0x1039: /* SiS 962 */ case (0x0646<<16)|0x1095: /* CMD 646 */ case (0x0571<<16)|0x1106: /* VIA 82C686 */ case (0x9001<<16)|0x1106: /* VIA chipset in VIA PV530 */ case (0x0502<<16)|0x100b: /* National Semiconductor SC1100/SCx200 */ break; case (0x2360<<16)|0x197b: /* jmicron jmb360 */ case (0x2361<<16)|0x197b: /* jmicron jmb361 */ case (0x2363<<16)|0x197b: /* jmicron jmb363 */ case (0x2365<<16)|0x197b: /* jmicron jmb365 */ case (0x2366<<16)|0x197b: /* jmicron jmb366 */ case (0x2368<<16)|0x197b: /* jmicron jmb368 */ break; case (0x7010<<16)|0x8086: /* 82371SB (PIIX3) */ case (0x1230<<16)|0x8086: /* 82371FB (PIIX) */ case (0x7111<<16)|0x8086: /* 82371[AE]B (PIIX4[E]) */ maxdma = 0x20000; break; case (0x2411<<16)|0x8086: /* 82801AA (ICH) */ case (0x2421<<16)|0x8086: /* 82801AB (ICH0) */ case (0x244A<<16)|0x8086: /* 82801BA (ICH2, Mobile) */ case (0x244B<<16)|0x8086: /* 82801BA (ICH2, High-End) */ case (0x248A<<16)|0x8086: /* 82801CA (ICH3, Mobile) */ case (0x248B<<16)|0x8086: /* 82801CA (ICH3, High-End) */ case (0x24CA<<16)|0x8086: /* 82801DBM (ICH4, Mobile) */ case (0x24CB<<16)|0x8086: /* 82801DB (ICH4, High-End) */ case (0x24D1<<16)|0x8086: /* 82801er (ich5) */ case (0x24DB<<16)|0x8086: /* 82801EB (ICH5) */ case (0x25A2<<16)|0x8086: /* 6300ESB pata */ case (0x25A3<<16)|0x8086: /* 6300ESB (E7210) */ case (0x266F<<16)|0x8086: /* 82801FB (ICH6) */ case (0x2653<<16)|0x8086: /* 82801FBM (ICH6, Mobile) */ case (0x269e<<16)|0x8086: /* 63xxESB (intel 5000) */ case (0x27DF<<16)|0x8086: /* 82801G PATA (ICH7) */ case (0x27C0<<16)|0x8086: /* 82801GB SATA (ICH7) */ case (0x27C4<<16)|0x8086: /* 82801GBM SATA (ICH7) */ case (0x27C5<<16)|0x8086: /* 82801GBM SATA AHCI (ICH7) */ case (0x2850<<16)|0x8086: /* 82801HBM/HEM PATA */ case (0x2820<<16)|0x8086: /* 82801HB/HR/HH/HO SATA IDE */ case (0x2828<<16)|0x8086: /* 82801HBM SATA (ICH8-M) */ case (0x2829<<16)|0x8086: /* 82801HBM SATA AHCI (ICH8-M) */ case (0x2920<<16)|0x8086: /* 82801(IB)/IR/IH/IO SATA (ICH9) port 0-3 */ case (0x2921<<16)|0x8086: /* 82801(IB)/IR/IH/IO SATA (ICH9) port 0-1 */ case (0x2926<<16)|0x8086: /* 82801(IB)/IR/IH/IO SATA (ICH9) port 4-5 */ case (0x2928<<16)|0x8086: /* 82801(IB)/IR/IH/IO SATA (ICH9m) port 0-1 */ case (0x2929<<16)|0x8086: /* 82801(IB)/IR/IH/IO SATA (ICH9m) port 0-1, 4-5 */ case (0x292d<<16)|0x8086: /* 82801(IB)/IR/IH/IO SATA (ICH9m) port 4-5*/ case (0x3a20<<16)|0x8086: /* 82801ji (ich10) */ case (0x3a26<<16)|0x8086: /* 82801ji (ich10) */ case (0x3b20<<16)|0x8086: /* 34x0 (pch) port 0-3 */ case (0x3b21<<16)|0x8086: /* 34x0 (pch) port 4-5 */ case (0x3b28<<16)|0x8086: /* 34x0pm (pch) port 0-1, 4-5 */ case (0x3b2e<<16)|0x8086: /* 34x0pm (pch) port 0-3 */ map = 0; if(pcicfgr16(p, 0x40) & 0x8000) map |= 1; if(pcicfgr16(p, 0x42) & 0x8000) map |= 2; irqack = ichirqack; break; } for(channel = 0; channel < 2; channel++){ if((map & 1<mem[0+2*channel].bar & ~0x01, p->mem[1+2*channel].bar & ~0x01, p->intl, 3); tbdf = p->tbdf; } else if(lchan[channel].probed == 0){ sdev = ataprobe(lchan[channel].cmdport, lchan[channel].ctlport, lchan[channel].irq, 3); lchan[channel].probed = 1; tbdf = BUSUNKNOWN; } else continue; if(sdev == nil) continue; ctlr = sdev->ctlr; if(ispc87415) { ctlr->ienable = pc87415ienable; print("pc87415disable: not yet implemented\n"); } ctlr->tbdf = tbdf; ctlr->pcidev = p; ctlr->maxio = maxio; ctlr->maxdma = maxdma; ctlr->span = span; ctlr->irqack = irqack; if(pi & 0x80) ctlr->bmiba = (p->mem[4].bar & ~0x01) + channel*8; if(head != nil) tail->next = sdev; else head = sdev; tail = sdev; } } if(lchan[0].probed + lchan[1].probed == 0) for(channel = 0; channel < 2; channel++){ sdev = nil; if(lchan[channel].probed == 0){ // print("sdide: blind probe %.3ux\n", lchan[channel].cmdport); sdev = ataprobe(lchan[channel].cmdport, lchan[channel].ctlport, lchan[channel].irq, 3); lchan[channel].probed = 1; } if(sdev == nil) continue; if(head != nil) tail->next = sdev; else head = sdev; tail = sdev; } if(0){ int port; ISAConf isa; /* * Hack for PCMCIA drives. * This will be tidied once we figure out how the whole * removeable device thing is going to work. */ memset(&isa, 0, sizeof(isa)); isa.port = 0x180; /* change this for your machine */ isa.irq = 11; /* change this for your machine */ port = isa.port+0x0C; channel = pcmspecial("MK2001MPL", &isa); if(channel == -1) channel = pcmspecial("SunDisk", &isa); if(channel == -1){ isa.irq = 10; channel = pcmspecial("CF", &isa); } if(channel == -1){ isa.irq = 10; channel = pcmspecial("OLYMPUS", &isa); } if(channel == -1){ port = isa.port+0x204; channel = pcmspecial("ATA/ATAPI", &isa); } if(channel >= 0 && (sdev = ataprobe(isa.port, port, isa.irq, 3)) != nil){ if(head != nil) tail->next = sdev; else head = sdev; } } return head; } static void atadmaclr(Ctlr *ctlr) { int bmiba, bmisx; if(ctlr->curdrive) ataabort(ctlr->curdrive, 1); bmiba = ctlr->bmiba; if(bmiba == 0) return; atadmastop(ctlr); outl(bmiba+Bmidtpx, 0); bmisx = inb(bmiba+Bmisx) & ~Bmidea; outb(bmiba+Bmisx, bmisx|Ideints|Idedmae); // pciintst(ctlr->pcidev); } static int ataenable(SDev* sdev) { Ctlr *ctlr; char name[32]; ctlr = sdev->ctlr; if(ctlr->bmiba){ atadmaclr(ctlr); if(ctlr->pcidev != nil) pcisetbme(ctlr->pcidev); ctlr->prdt = mallocalign(Nprd*sizeof(Prd), 4, 0, 64*1024); } snprint(name, sizeof(name), "%s (%s)", sdev->name, sdev->ifc->name); intrenable(ctlr->irq, atainterrupt, ctlr, ctlr->tbdf, name); outb(ctlr->ctlport+Dc, 0); if(ctlr->ienable) ctlr->ienable(ctlr); return 1; } static int atadisable(SDev *sdev) { Ctlr *ctlr; char name[32]; ctlr = sdev->ctlr; outb(ctlr->ctlport+Dc, Nien); /* disable interrupts */ if (ctlr->idisable) ctlr->idisable(ctlr); snprint(name, sizeof(name), "%s (%s)", sdev->name, sdev->ifc->name); intrdisable(ctlr->irq, atainterrupt, ctlr, ctlr->tbdf, name); if(ctlr->bmiba) { // atadmaclr(ctlr); if (ctlr->pcidev) pciclrbme(ctlr->pcidev); free(ctlr->prdt); } return 0; } static int ataonline(SDunit *unit) { Ctlr *ctlr; Drive *drive; if((ctlr = unit->dev->ctlr) == nil || ctlr->drive[unit->subno] == nil) return 0; drive = ctlr->drive[unit->subno]; if((drive->flags & Online) == 0){ drive->flags |= Online; atadrive(unit, drive, ctlr->cmdport, ctlr->ctlport, drive->dev); } unit->sectors = drive->sectors; unit->secsize = drive->secsize; if(drive->feat & Datapi) return scsionline(unit); return 1; } static int atarctl(SDunit* unit, char* p, int l) { Ctlr *ctlr; Drive *drive; char *e, *op; if((ctlr = unit->dev->ctlr) == nil || ctlr->drive[unit->subno] == nil) return 0; drive = ctlr->drive[unit->subno]; e = p+l; op = p; qlock(drive); p = seprint(p, e, "config %4.4uX capabilities %4.4uX", drive->info[Iconfig], drive->info[Icapabilities]); if(drive->dma) p = seprint(p, e, " dma %8.8uX dmactl %8.8uX", drive->dma, drive->dmactl); if(drive->rwm) p = seprint(p, e, " rwm %ud rwmctl %ud", drive->rwm, drive->rwmctl); if(drive->feat & Dllba) p = seprint(p, e, " lba48always %s", (drive->flags&Lba48always) ? "on" : "off"); p = seprint(p, e, "\n"); p = seprint(p, e, "model %s\n", drive->model); p = seprint(p, e, "serial %s\n", drive->serial); p = seprint(p, e, "firm %s\n", drive->firmware); p = seprint(p, e, "feat "); p = pflag(p, e, drive); if(drive->sectors){ p = seprint(p, e, "geometry %llud %d", drive->sectors, drive->secsize); if(drive->pkt == 0 && (drive->feat & Dlba) == 0) p = seprint(p, e, " %d %d %d", drive->c, drive->h, drive->s); p = seprint(p, e, "\n"); } p = seprint(p, e, "missirq %ud\n", drive->missirq); p = seprint(p, e, "sloop %ud\n", drive->spurloop); p = seprint(p, e, "irq %ud %ud\n", ctlr->nrq, drive->irq); p = seprint(p, e, "bsy %ud %ud\n", ctlr->bsy, drive->bsy); p = seprint(p, e, "nildrive %ud\n", ctlr->nildrive); qunlock(drive); return p - op; } static int atawctl(SDunit* unit, Cmdbuf* cb) { Ctlr *ctlr; Drive *drive; if((ctlr = unit->dev->ctlr) == nil || ctlr->drive[unit->subno] == nil) return 0; drive = ctlr->drive[unit->subno]; qlock(drive); if(waserror()){ qunlock(drive); nexterror(); } /* * Dma and rwm control is passive at the moment, * i.e. it is assumed that the hardware is set up * correctly already either by the BIOS or when * the drive was initially identified. */ if(strcmp(cb->f[0], "dma") == 0){ if(cb->nf != 2 || drive->dma == 0) error(Ebadctl); if(strcmp(cb->f[1], "on") == 0) drive->dmactl = drive->dma; else if(strcmp(cb->f[1], "off") == 0) drive->dmactl = 0; else error(Ebadctl); } else if(strcmp(cb->f[0], "rwm") == 0){ if(cb->nf != 2 || drive->rwm == 0) error(Ebadctl); if(strcmp(cb->f[1], "on") == 0) drive->rwmctl = drive->rwm; else if(strcmp(cb->f[1], "off") == 0) drive->rwmctl = 0; else error(Ebadctl); } else if(strcmp(cb->f[0], "lba48always") == 0){ if(cb->nf != 2 || !(drive->feat & Dllba)) error(Ebadctl); if(strcmp(cb->f[1], "on") == 0) drive->flags |= Lba48always; else if(strcmp(cb->f[1], "off") == 0) drive->flags &= ~Lba48always; else error(Ebadctl); } else if(strcmp(cb->f[0], "identify") == 0){ atadrive(unit, drive, ctlr->cmdport, ctlr->ctlport, drive->dev); } else error(Ebadctl); qunlock(drive); poperror(); return 0; } SDifc sdideifc = { "ide", /* name */ atapnp, /* pnp */ nil, /* legacy */ ataenable, /* enable */ atadisable, /* disable */ scsiverify, /* verify */ ataonline, /* online */ atario, /* rio */ atarctl, /* rctl */ atawctl, /* wctl */ scsibio, /* bio */ ataprobew, /* probe */ ataclear, /* clear */ atastat, /* rtopctl */ nil, /* wtopctl */ ataataio, };