| /* |
| * This file is part of the UCB release of Plan 9. It is subject to the license |
| * terms in the LICENSE file found in the top-level directory of this |
| * distribution and at http://akaros.cs.berkeley.edu/files/Plan9License. No |
| * part of the UCB release of Plan 9, including this file, may be copied, |
| * modified, propagated, or distributed except according to the terms contained |
| * in the LICENSE file. |
| */ |
| |
| /* |
| * ahci serial ata driver |
| * copyright © 2007-8 coraid, inc. |
| */ |
| |
| #include <assert.h> |
| #include <cpio.h> |
| #include <error.h> |
| #include <kmalloc.h> |
| #include <kref.h> |
| #include <net/ip.h> |
| #include <pmap.h> |
| #include <sd.h> |
| #include <slab.h> |
| #include <smp.h> |
| #include <stdio.h> |
| #include <string.h> |
| |
| #include <ahci.h> |
| |
| enum { Vatiamd = 0x1002, |
| Vintel = 0x8086, |
| Vmarvell = 0x1b4b, |
| }; |
| |
| #define iprintd(...) \ |
| do { \ |
| if (prid) \ |
| printd(__VA_ARGS__); \ |
| } while (0) |
| #define aprintd(...) \ |
| do { \ |
| if (datapi) \ |
| printd(__VA_ARGS__); \ |
| } while (0) |
| #define Tname(c) tname[(c)->type] |
| #define Intel(x) ((x)->pci->ven_id == Vintel) |
| |
| enum { NCtlr = 16, |
| NCtlrdrv = 32, |
| NDrive = NCtlr * NCtlrdrv, |
| |
| Read = 0, |
| Write, |
| |
| Nms = 256, /* ms. between drive checks */ |
| Mphywait = 2 * 1024 / Nms - 1, |
| Midwait = 16 * 1024 / Nms - 1, |
| Mcomrwait = 64 * 1024 / Nms - 1, |
| |
| Obs = 0xa0, /* obsolete device bits */ |
| |
| /* |
| * if we get more than this many interrupts per tick for a drive, |
| * either the hardware is broken or we've got a bug in this driver. |
| */ |
| Maxintrspertick = 2000, /* was 1000 */ |
| }; |
| |
| /* pci space configuration */ |
| enum { Pmap = 0x90, |
| Ppcs = 0x91, |
| Prev = 0xa8, |
| }; |
| |
| enum { Tesb, |
| Tich, |
| Tsb600, |
| Tunk, |
| }; |
| |
| static char *tname[] = { |
| "63xxesb", |
| "ich", |
| "sb600", |
| "unknown", |
| }; |
| |
| enum { Dnull, |
| Dmissing, |
| Dnew, |
| Dready, |
| Derror, |
| Dreset, |
| Doffline, |
| Dportreset, |
| Dlast, |
| }; |
| |
| static char *diskstates[Dlast] = { |
| "null", "missing", "new", "ready", "error", "reset", "offline", "portreset", |
| }; |
| |
| enum { DMautoneg, |
| DMsatai, |
| DMsataii, |
| DMsata3, |
| }; |
| |
| static char *modename[] = { |
| /* used in control messages */ |
| "auto", |
| "satai", |
| "sataii", |
| "sata3", |
| }; |
| static char *descmode[] = { |
| /* only printed */ |
| "auto", |
| "sata 1", |
| "sata 2", |
| "sata 3", |
| }; |
| |
| static char *flagname[] = { |
| "llba", "smart", "power", "nop", "atapi", "atapi16", |
| }; |
| |
| struct drive { |
| spinlock_t Lock; |
| |
| struct ctlr *ctlr; |
| struct sdunit *unit; |
| char name[10]; |
| void *port; |
| struct aportm portm; |
| struct aportc portc; /* redundant ptr to port and portm */ |
| |
| unsigned char mediachange; |
| unsigned char state; |
| unsigned char smartrs; |
| |
| uint64_t sectors; |
| uint32_t secsize; |
| uint32_t intick; /* start tick of current transfer */ |
| uint32_t lastseen; |
| int wait; |
| unsigned char mode; /* DMautoneg, satai or sataii */ |
| unsigned char active; |
| |
| char serial[20 + 1]; |
| char firmware[8 + 1]; |
| char model[40 + 1]; |
| |
| int infosz; |
| uint16_t *info; |
| uint16_t tinyinfo[2]; /* used iff malloc fails */ |
| |
| int driveno; /* ctlr*NCtlrdrv + unit */ |
| /* controller port # != driveno when not all ports are enabled */ |
| int portno; |
| |
| uint32_t lastintr0; |
| uint32_t intrs; |
| }; |
| |
| struct ctlr { |
| spinlock_t Lock; |
| |
| int type; |
| int enabled; |
| struct sdev *sdev; |
| struct pci_device *pci; |
| void *vector; |
| |
| /* virtual register addresses */ |
| void *mmio; |
| void *hba; |
| |
| /* phyical register address */ |
| uintptr_t physio; |
| |
| struct drive *rawdrive; |
| struct drive *drive[NCtlrdrv]; |
| int ndrive; |
| int mport; /* highest drive # (0-origin) on ich9 at least */ |
| |
| uint32_t lastintr0; |
| uint32_t intrs; /* not attributable to any drive */ |
| }; |
| |
| struct Asleep { |
| void *p; |
| int i; |
| }; |
| |
| struct sdifc sdiahciifc; // TODO(afergs): make static??? |
| |
| static struct ctlr iactlr[NCtlr]; |
| static struct sdev sdevs[NCtlr]; |
| static int niactlr; |
| |
| static struct drive *iadrive[NDrive]; |
| static int niadrive; |
| |
| /* these are fiddled in iawtopctl() */ |
| static int debug; |
| static int prid = 1; |
| static int datapi; |
| |
| // TODO: does this get initialized correctly? |
| static char stab[] = {[0] = 'i', 'm', [8] = 't', 'c', 'p', 'e', |
| [16] = 'N', 'I', 'W', 'B', 'D', 'C', |
| 'H', 'S', 'T', 'F', 'X'}; |
| |
| // AHCI register access helper functions |
| // TODO(afergs): Figure out what the datatype for reg/offset should really be! |
| |
| static inline uint32_t ahci_hba_read32(void *base, uint32_t reg) |
| { |
| return read_mmreg32((uintptr_t)base + reg); |
| } |
| |
| static inline void ahci_hba_write32(void *base, uint32_t reg, uint32_t val) |
| { |
| write_mmreg32((uintptr_t)base + reg, val); |
| } |
| |
| static inline uint32_t ahci_port_read32(void *aport, uint32_t reg) |
| { |
| return read_mmreg32((uintptr_t)aport + reg); |
| } |
| |
| static inline void ahci_port_write32(void *aport, uint32_t reg, uint32_t val) |
| { |
| write_mmreg32((uintptr_t)aport + reg, val); |
| } |
| |
| static inline uint32_t ahci_list_read32(void *alist, uint32_t reg) |
| { |
| return read_mmreg32((uintptr_t)alist + reg); |
| } |
| |
| static inline void ahci_list_write32(void *alist, uint32_t reg, uint32_t val) |
| { |
| write_mmreg32((uintptr_t)alist + reg, val); |
| } |
| |
| static inline uint32_t ahci_prdt_read32(void *aprdt, uint32_t reg) |
| { |
| return read_mmreg32((uintptr_t)aprdt + reg); |
| } |
| |
| static inline void ahci_prdt_write32(void *aprdt, uint32_t reg, uint32_t val) |
| { |
| write_mmreg32((uintptr_t)aprdt + reg, val); |
| } |
| |
| static inline uint8_t ahci_cfis_read8(void *acfis, uint8_t offset) |
| { |
| return read_mmreg8((uintptr_t)acfis + offset); |
| } |
| |
| static inline void ahci_cfis_write8(void *acfis, uint8_t offset, uint8_t val) |
| { |
| write_mmreg8((uintptr_t)acfis + offset, val); |
| } |
| |
| static uint32_t set_bit32(void *base, uint32_t offset, uint32_t mask) |
| { |
| uint32_t value = read_mmreg32((uintptr_t)base + offset); |
| |
| value |= mask; |
| write_mmreg32((uintptr_t)base + offset, value); |
| return value; |
| } |
| |
| static uint32_t clear_bit32(void *base, uint32_t offset, uint32_t mask) |
| { |
| uint32_t value = read_mmreg32((uintptr_t)base + offset); |
| |
| value &= ~mask; |
| write_mmreg32((uintptr_t)base + offset, value); |
| return value; |
| } |
| |
| static uint8_t set_bit8(void *base, uint32_t offset, uint8_t mask) |
| { |
| uint8_t value = read_mmreg8((uintptr_t)base + offset); |
| |
| value |= mask; |
| write_mmreg8((uintptr_t)base + offset, value); |
| return value; |
| } |
| |
| static uint8_t clear_bit8(void *base, uint32_t offset, uint8_t mask) |
| { |
| uint8_t value = read_mmreg8((uintptr_t)base + offset); |
| |
| value &= ~mask; |
| write_mmreg8((uintptr_t)base + offset, value); |
| return value; |
| } |
| |
| /* ALL time units in this file are in milliseconds. */ |
| static uint32_t ms(void) |
| { |
| return (uint32_t)(epoch_nsec() / 1048576); |
| } |
| |
| /* TODO: if we like this, make it useable elsewhere. */ |
| static void sdierror(struct cmdbuf *cb, char *fmt, ...) |
| { |
| char *c = kzmalloc(512, MEM_WAIT); |
| va_list ap; |
| |
| assert(fmt); |
| va_start(ap, fmt); |
| vsnprintf(c, 512, fmt, ap); |
| va_end(ap); |
| cmderror(cb, c); |
| kfree(c); |
| } |
| |
| static void serrstr(uint32_t r, char *s, char *e) |
| { |
| int i; |
| |
| e -= 3; |
| for (i = 0; i < ARRAY_SIZE(stab) && s < e; i++) |
| if (r & (1 << i) && stab[i]) { |
| *s++ = stab[i]; |
| if (SerrBad & (1 << i)) |
| *s++ = '*'; |
| } |
| *s = 0; |
| } |
| |
| static char ntab[] = "0123456789abcdef"; |
| |
| static void preg(volatile unsigned char *reg, int n) |
| { |
| int i; |
| char buf[25 * 3 + 1], *e; |
| |
| e = buf; |
| for (i = 0; i < n; i++) { |
| *e++ = ntab[reg[i] >> 4]; |
| *e++ = ntab[reg[i] & 0xf]; |
| *e++ = ' '; |
| } |
| *e++ = '\n'; |
| *e = 0; |
| printd(buf); |
| } |
| |
| static void dreg(char *s, void *p) |
| { |
| printd("ahci: %stask=%#lx; cmd=%#lx; ci=%#lx; is=%#lx\n", s, |
| ahci_port_read32(p, PORT_TFD), ahci_port_read32(p, PORT_CMD), |
| ahci_port_read32(p, PORT_CI), ahci_port_read32(p, PORT_IS)); |
| } |
| |
| static void esleep(int ms) |
| { |
| ERRSTACK(1); |
| |
| if (waserror()) { |
| poperror(); |
| return; |
| } |
| kthread_usleep(ms * 1000); |
| poperror(); |
| } |
| |
| static int ahciclear(void *v) |
| { |
| struct Asleep *s; |
| |
| s = v; |
| return (ahci_port_read32(s->p, PORT_CI) & s->i) == 0; |
| } |
| |
| static void aesleep(struct aportm *pm, struct Asleep *a, int ms) |
| { |
| ERRSTACK(1); |
| |
| if (waserror()) { |
| poperror(); |
| return; |
| } |
| rendez_sleep_timeout(&pm->Rendez, ahciclear, a, ms * 1000); |
| poperror(); |
| } |
| |
| static int ahciwait(struct aportc *c, int ms) |
| { |
| struct Asleep as; |
| void *port; |
| uint32_t cmd, tfd; |
| |
| port = c->p; |
| cmd = ahci_port_read32(port, PORT_CMD); |
| printd("ahci: %s: CMD=0x%08x\n", __func__, cmd); |
| |
| // TODO: Set the correct CI bit for the slot, not always slot 0! |
| ahci_port_write32(port, PORT_CI, 1); |
| as.p = port; |
| as.i = 1; |
| aesleep(c->pm, &as, ms); |
| tfd = ahci_port_read32(port, PORT_TFD); |
| if (((tfd & 0x81) == 0) && // Not busy and not error |
| (ahci_port_read32(port, PORT_CI) == 0)) |
| return 0; |
| dreg("ahci: ahciwait: timeout ", c->p); |
| return -1; |
| } |
| |
| /* fill in cfis boilerplate */ |
| static void *cfissetup(struct aportc *pc) |
| { |
| void *cfis; |
| |
| cfis = pc->pm->ctab; // cfis is the first thing in ctab, same location |
| memset((void *)cfis, 0, 0x20); |
| ahci_cfis_write8(cfis, 0, 0x27); // H2D |
| ahci_cfis_write8(cfis, 1, |
| 0x80); // Transfer due to update to CMD register |
| ahci_cfis_write8(cfis, 7, Obs); |
| return cfis; |
| } |
| |
| /* initialize pc's list */ |
| static void listsetup(struct aportc *pc, int flags) |
| { |
| void *list; |
| |
| list = pc->pm->list; |
| ahci_list_write32(list, ALIST_FLAGS, flags | 5); |
| ahci_list_write32(list, ALIST_LEN, 0); |
| ahci_list_write32(list, ALIST_CTAB, paddr_low32(pc->pm->ctab)); |
| ahci_list_write32(list, ALIST_CTABHI, paddr_high32(pc->pm->ctab)); |
| printd("ahci: %s: CTAB physical address=0x%08x:0x%08x\n", __func__, |
| paddr_high32(pc->pm->ctab), paddr_low32(pc->pm->ctab)); |
| } |
| |
| static int nop(struct aportc *pc) |
| { |
| void *cfis; |
| |
| if ((pc->pm->feat & Dnop) == 0) |
| return -1; |
| cfis = cfissetup(pc); |
| ahci_cfis_write8(cfis, 2, 0); |
| listsetup(pc, Lwrite); |
| return ahciwait(pc, 3 * 1000); |
| } |
| |
| static int setfeatures(struct aportc *pc, unsigned char f) |
| { |
| void *cfis; |
| |
| cfis = cfissetup(pc); |
| ahci_cfis_write8(cfis, 2, 0xef); |
| ahci_cfis_write8(cfis, 3, f); |
| listsetup(pc, Lwrite); |
| return ahciwait(pc, 3 * 1000); |
| } |
| |
| static int setudmamode(struct aportc *pc, unsigned char f) |
| { |
| void *cfis; |
| |
| printd("ahci: %s: PORT_SIG=0x%08x\n", __func__, |
| ahci_port_read32(pc->p, PORT_SIG)); |
| /* hack */ |
| if ((ahci_port_read32(pc->p, PORT_SIG) >> 16) == 0xeb14) |
| return 0; |
| cfis = cfissetup(pc); |
| ahci_cfis_write8(cfis, 2, 0xef); |
| ahci_cfis_write8(cfis, 3, 3); /* set transfer mode */ |
| ahci_cfis_write8(cfis, 12, 0x40 | f); /* sector count */ |
| listsetup(pc, Lwrite); |
| return ahciwait(pc, 3 * 1000); |
| } |
| |
| static void asleep(int ms) |
| { |
| udelay(ms * 1000); |
| } |
| |
| static int ahciportreset(struct aportc *c) |
| { |
| uint32_t cmd, i, scr_ctl; |
| void *port; |
| |
| port = c->p; |
| clear_bit32(port, PORT_CMD, Afre | Ast); |
| for (i = 0; i < 500; i += 25) { |
| if ((ahci_port_read32(port, PORT_CMD) & Acr) == 0) |
| break; |
| asleep(25); |
| } |
| |
| scr_ctl = ahci_port_read32(port, PORT_SCTL); |
| scr_ctl = 1 | (scr_ctl & ~7); |
| ahci_port_write32(port, PORT_SCTL, scr_ctl); |
| printk("Sleeping one second\n"); |
| udelay(1000 * 1000); |
| clear_bit32(port, PORT_SCTL, 7); // TODO: Make sure PxCMD.ST == 0 first? |
| return 0; |
| } |
| |
| static int smart(struct aportc *pc, int n) |
| { |
| void *cfis; |
| |
| if ((pc->pm->feat & Dsmart) == 0) |
| return -1; |
| cfis = cfissetup(pc); |
| ahci_cfis_write8(cfis, 2, 0xb0); |
| ahci_cfis_write8(cfis, 3, 0xd8 + n); /* able smart */ |
| ahci_cfis_write8(cfis, 5, 0x4f); |
| ahci_cfis_write8(cfis, 6, 0xc2); |
| listsetup(pc, Lwrite); |
| // TODO(afergs): Replace 1|32 with constants |
| if (ahciwait(pc, 1000) == -1 || |
| ahci_port_read32(pc->p, PORT_TFD) & (1 | 32)) { |
| printd("ahci: smart fail %#lx\n", |
| ahci_port_read32(pc->p, PORT_TFD)); |
| return -1; |
| } |
| if (n) |
| return 0; |
| return 1; |
| } |
| |
| static int smartrs(struct aportc *pc) |
| { |
| void *cfis; |
| volatile unsigned char *rfis; |
| uint32_t tfd; |
| |
| cfis = cfissetup(pc); |
| ahci_cfis_write8(cfis, 2, 0xb0); |
| ahci_cfis_write8(cfis, 3, 0xda); /* return smart status */ |
| ahci_cfis_write8(cfis, 5, 0x4f); |
| ahci_cfis_write8(cfis, 6, 0xc2); |
| listsetup(pc, Lwrite); |
| |
| rfis = pc->pm->fis.r; |
| tfd = ahci_port_read32(pc->p, PORT_TFD); |
| // TODO(afergs): Replace 1|32 with constants |
| if (ahciwait(pc, 1000) == -1 || tfd & (1 | 32)) { |
| printd("ahci: smart fail %#lx\n", tfd); |
| preg(rfis, 20); |
| return -1; |
| } |
| if (rfis[5] == 0x4f && rfis[6] == 0xc2) |
| return 1; |
| return 0; |
| } |
| |
| static int ahciflushcache(struct aportc *pc) |
| { |
| void *cfis; |
| |
| cfis = cfissetup(pc); |
| ahci_cfis_write8(cfis, 2, pc->pm->feat & Dllba ? 0xea : 0xe7); |
| listsetup(pc, Lwrite); |
| if (ahciwait(pc, 60000) == -1 || |
| ahci_port_read32(pc->p, PORT_TFD) & (1 | 32)) { |
| printd("ahciflushcache: fail %#lx\n", |
| ahci_port_read32(pc->p, PORT_TFD)); |
| // preg(pc->m->fis.r, 20); |
| return -1; |
| } |
| return 0; |
| } |
| |
| static uint16_t gbit16(void *a) |
| { |
| unsigned char *i; |
| |
| i = a; |
| return i[1] << 8 | i[0]; |
| } |
| |
| static uint32_t gbit32(void *a) |
| { |
| uint32_t j; |
| unsigned char *i; |
| |
| i = a; |
| j = i[3] << 24; |
| j |= i[2] << 16; |
| j |= i[1] << 8; |
| j |= i[0]; |
| return j; |
| } |
| |
| static uint64_t gbit64(void *a) |
| { |
| unsigned char *i; |
| |
| i = a; |
| return (uint64_t)gbit32(i + 4) << 32 | gbit32(a); |
| } |
| |
| static int ahciidentify0(struct aportc *pc, void *id, int atapi) |
| { |
| void *cfis, *prdt; |
| static unsigned char tab[] = { |
| 0xec, |
| 0xa1, |
| }; |
| |
| cfis = cfissetup(pc); |
| ahci_cfis_write8(cfis, 2, tab[atapi]); |
| printd("ahci: %s: ATAPI=%d, cfis[2]: 0x%02x\n", __func__, atapi, |
| ahci_cfis_read8(cfis, 2)); |
| printd("ahci: %s: CFIS physical address=0x%08x:0x%08x\n", __func__, |
| paddr_high32(cfis), paddr_low32(cfis)); |
| |
| listsetup(pc, 1 << 16); |
| |
| memset(id, 0, 0x200); /* Fill 512 bytes with 0's */ |
| prdt = pc->pm->ctab + ACTAB_PRDT; |
| ahci_prdt_write32(prdt, APRDT_DBA, paddr_low32(id)); |
| ahci_prdt_write32(prdt, APRDT_DBAHI, paddr_high32(id)); |
| ahci_prdt_write32(prdt, APRDT_COUNT, 1 << 31 | (0x200 - 2) | 1); |
| printd("ahci: %s: DBA physical address=0x%08x:0x%08x\n", __func__, |
| paddr_high32(id), paddr_low32(id)); |
| |
| return ahciwait(pc, 3 * 1000); |
| } |
| |
| static int64_t ahciidentify(struct aportc *pc, uint16_t *id) |
| { |
| int i, sig; |
| int64_t s; |
| struct aportm *pm; |
| int cnt; |
| |
| pm = pc->pm; |
| pm->feat = 0; |
| pm->smart = 0; |
| i = 0; |
| sig = ahci_port_read32(pc->p, PORT_SIG) >> 16; |
| if (sig == 0xeb14) { |
| pm->feat |= Datapi; |
| i = 1; |
| } |
| if (ahciidentify0(pc, id, i) == -1) |
| return -1; |
| |
| #ifdef DEBUG |
| printd("ahci: %s: ahciidentify0 return dump=\n\t", __func__); |
| for (cnt = 0; cnt < 64; cnt++) { |
| printd("0x%08x ", id[cnt]); |
| if (cnt % 4 == 3 && cnt != 63) |
| printd("\n\t"); |
| } |
| printd("\n"); |
| #endif |
| |
| i = gbit16(id + 83) | gbit16(id + 86); |
| if (i & (1 << 10)) { |
| pm->feat |= Dllba; |
| s = gbit64(id + 100); |
| } else |
| s = gbit32(id + 60); |
| |
| if (pm->feat & Datapi) { |
| i = gbit16(id + 0); |
| if (i & 1) |
| pm->feat |= Datapi16; |
| } |
| |
| i = gbit16(id + 83); |
| if ((i >> 14) == 1) { |
| if (i & (1 << 3)) |
| pm->feat |= Dpower; |
| i = gbit16(id + 82); |
| if (i & 1) |
| pm->feat |= Dsmart; |
| if (i & (1 << 14)) |
| pm->feat |= Dnop; |
| } |
| return s; |
| } |
| |
| #if 0 |
| static int |
| ahciquiet(struct aport *a) |
| { |
| uint32_t *p, i; |
| |
| p = &a->cmd; |
| *p &= ~Ast; |
| for(i = 0; i < 500; i += 50){ |
| if((*p & Acr) == 0) |
| goto stop; |
| asleep(50); |
| } |
| return -1; |
| stop: |
| if((a->task & (ASdrq|ASbsy)) == 0){ |
| *p |= Ast; |
| return 0; |
| } |
| |
| *p |= Aclo; |
| for(i = 0; i < 500; i += 50){ |
| if((*p & Aclo) == 0) |
| goto stop1; |
| asleep(50); |
| } |
| return -1; |
| stop1: |
| /* extra check */ |
| printd("ahci: clo clear %#lx\n", a->task); |
| if(a->task & ASbsy) |
| return -1; |
| *p |= Ast; |
| return 0; |
| } |
| #endif |
| |
| #if 0 |
| static int |
| ahcicomreset(struct aportc *pc) |
| { |
| unsigned char *c; |
| |
| printd("ahcicomreset\n"); |
| dreg("ahci: comreset ", pc->p); |
| if(ahciquiet(pc->p) == -1){ |
| printd("ahciquiet failed\n"); |
| return -1; |
| } |
| dreg("comreset ", pc->p); |
| |
| c = cfissetup(pc); |
| ahci_cfis_write8(cfis, 1, 0); |
| ahci_cfis_write8(cfis, 15, 1<<2); /* srst */ |
| listsetup(pc, Lclear | Lreset); |
| if(ahciwait(pc, 500) == -1){ |
| printd("ahcicomreset: first command failed\n"); |
| return -1; |
| } |
| microdelay(250); |
| dreg("comreset ", pc->p); |
| |
| c = cfissetup(pc); |
| ahci_cfis_write8(cfis, 1, 0); |
| listsetup(pc, Lwrite); |
| if (ahciwait(pc, 150) == -1) { |
| printd("ahcicomreset: second command failed\n"); |
| return -1; |
| } |
| dreg("comreset ", pc->p); |
| return 0; |
| } |
| #endif |
| |
| static int ahciidle(void *port) |
| { |
| uint32_t i, r, cmd; |
| |
| cmd = ahci_port_read32(port, PORT_CMD); |
| if ((cmd & Arun) == 0) |
| return 0; |
| cmd &= ~Ast; |
| ahci_port_write32(port, PORT_CMD, cmd); |
| |
| r = 0; |
| for (i = 0; i < 500; i += 25) { |
| if ((ahci_port_read32(port, PORT_CMD) & Acr) == 0) |
| goto stop; |
| asleep(25); |
| } |
| r = -1; |
| stop: |
| if ((ahci_port_read32(port, PORT_CMD) & Afre) == 0) |
| return r; |
| |
| clear_bit32(port, PORT_CMD, Afre); |
| for (i = 0; i < 500; i += 25) { |
| if ((ahci_port_read32(port, PORT_CMD) & Afre) == 0) |
| return 0; |
| asleep(25); |
| } |
| return -1; |
| } |
| |
| /* |
| * § 6.2.2.1 first part; comreset handled by reset disk. |
| * - remainder is handled by configdisk. |
| * - ahcirecover is a quick recovery from a failed command. |
| */ |
| static int ahciswreset(struct aportc *pc) |
| { |
| int i; |
| |
| i = ahciidle(pc->p); |
| |
| set_bit32(pc->p, PORT_CMD, Afre); |
| if (i == -1) |
| return -1; |
| if (ahci_port_read32(pc->p, PORT_TFD) & (ASdrq | ASbsy)) |
| return -1; |
| return 0; |
| } |
| |
| static int ahcirecover(struct aportc *pc) |
| { |
| ahciswreset(pc); |
| |
| set_bit32(pc->p, PORT_CMD, Ast); |
| if (setudmamode(pc, 5) == -1) |
| return -1; |
| return 0; |
| } |
| |
| static void *malign(int size, int align) |
| { |
| return kmalloc_align(size, MEM_WAIT, align); |
| } |
| |
| static void setupfis(struct afis *f) |
| { |
| f->base = malign(0x100, 0x100); /* magic */ |
| f->d = f->base + 0; |
| f->p = f->base + 0x20; |
| f->r = f->base + 0x40; |
| f->u = f->base + 0x60; |
| f->devicebits = (uint32_t *)(f->base + 0x58); |
| } |
| |
| static void ahciwakeup(void *port) |
| { |
| uint16_t s; |
| uint32_t sctl; |
| |
| s = ahci_port_read32(port, PORT_SSTS); |
| if ((s & Intpm) != Intslumber && (s & Intpm) != Intpartpwr) |
| return; |
| if ((s & Devdet) != Devpresent) { /* not (device, no phy) */ |
| iprint("ahci: slumbering drive unwakable %#x\n", s); |
| return; |
| } |
| ahci_port_write32(port, PORT_SCTL, 3 * Aipm | 0 * Aspd | Adet); |
| udelay(1000 * 1000); |
| clear_bit32(port, PORT_SCTL, 7); |
| // iprint("ahci: wake %#x -> %#x\n", s, p->sstatus); |
| } |
| |
| static int ahciconfigdrive(struct drive *d) |
| { |
| char *name; |
| void *hba, *port; |
| struct aportm *pm; |
| uint32_t cap, sstatus; |
| |
| hba = d->ctlr->hba; |
| port = d->portc.p; |
| pm = d->portc.pm; |
| if (pm->list == 0) { |
| setupfis(&pm->fis); |
| pm->list = malign(ALIST_SIZE, 1024); |
| pm->ctab = malign(ACTAB_PRDT + APRDT_SIZE, 128); |
| } |
| |
| if (d->unit) |
| name = d->unit->sdperm.name; |
| else |
| name = NULL; |
| sstatus = ahci_port_read32(port, PORT_SSTS); |
| cap = ahci_hba_read32(hba, HBA_CAP); |
| if (sstatus & (Devphycomm | Devpresent) && cap & Hsss) { |
| /* device connected & staggered spin-up */ |
| printd("ahci: configdrive: %s: spinning up ... [%#lx]\n", name, |
| sstatus); |
| set_bit32(port, PORT_CMD, Apod | Asud); |
| asleep(1400); |
| } |
| |
| ahci_port_write32(port, PORT_SERR, SerrAll); |
| |
| ahci_port_write32(port, PORT_CLB, paddr_low32(pm->list)); |
| ahci_port_write32(port, PORT_CLBU, paddr_high32(pm->list)); |
| printd("ahci: %s: PORT_CLB physical address=0x%08x:0x%08x\n", __func__, |
| paddr_high32(pm->list), paddr_low32(pm->list)); |
| ahci_port_write32(port, PORT_FB, paddr_low32(pm->fis.base)); |
| ahci_port_write32(port, PORT_FBU, paddr_high32(pm->fis.base)); |
| printd("ahci: %s: PORT_FB physical address=0x%08x:0x%08x\n", __func__, |
| paddr_high32(pm->fis.base), paddr_low32(pm->fis.base)); |
| |
| set_bit32(port, PORT_CMD, Afre | Ast); |
| |
| /* drive coming up in slumbering? */ |
| sstatus = ahci_port_read32(port, PORT_SSTS); |
| if ((sstatus & Devdet) == Devpresent && |
| ((sstatus & Intpm) == Intslumber || |
| (sstatus & Intpm) == Intpartpwr)) |
| ahciwakeup(port); |
| |
| /* "disable power management" sequence from book. */ |
| ahci_port_write32(port, PORT_SCTL, |
| (3 * Aipm) | (d->mode * Aspd) | (0 * Adet)); |
| clear_bit32(port, PORT_CMD, Aalpe); |
| ahci_port_write32(port, PORT_IE, IEM); |
| |
| return 0; |
| } |
| |
| static void ahcienable(void *hba) |
| { |
| set_bit32(hba, HBA_GHC, Hie); |
| } |
| |
| static void ahcidisable(void *hba) |
| { |
| clear_bit32(hba, HBA_GHC, Hie); |
| } |
| |
| static int countbits(uint32_t u) |
| { |
| int n; |
| |
| n = 0; |
| for (; u != 0; u >>= 1) |
| if (u & 1) |
| n++; |
| return n; |
| } |
| |
| static int ahciconf(struct ctlr *ctlr) |
| { |
| void *hba; |
| uint32_t cap; |
| |
| hba = ctlr->hba = ctlr->mmio; |
| cap = ahci_hba_read32(hba, HBA_CAP); |
| |
| if ((cap & Hsam) == 0) |
| set_bit32(hba, HBA_GHC, Hae); |
| |
| printd("#S/sd%c: type %s port %#p: sss %ld ncs %ld coal %ld " |
| "%ld ports, led %ld clo %ld ems %ld\n", |
| ctlr->sdev->idno, tname[ctlr->type], hba, (cap >> 27) & 1, |
| (cap >> 8) & 0x1f, (cap >> 7) & 1, (cap & 0x1f) + 1, |
| (cap >> 25) & 1, (cap >> 24) & 1, (cap >> 6) & 1); |
| return countbits(ahci_hba_read32(hba, HBA_PI)); |
| } |
| |
| #if 0 |
| static int |
| ahcihbareset(void *hba) |
| { |
| int wait; |
| |
| h->ghc |= 1; |
| for(wait = 0; wait < 1000; wait += 100){ |
| if(h->ghc == 0) |
| return 0; |
| delay(100); |
| } |
| return -1; |
| } |
| #endif |
| |
| static void idmove(char *p, uint16_t *a, int n) |
| { |
| int i; |
| char *op, *e; |
| |
| op = p; |
| for (i = 0; i < n / 2; i++) { |
| *p++ = a[i] >> 8; |
| *p++ = a[i]; |
| } |
| *p = 0; |
| while (p > op && *--p == ' ') |
| *p = 0; |
| e = p; |
| for (p = op; *p == ' '; p++) |
| ; |
| memmove(op, p, n - (e - p)); |
| } |
| |
| static int identify(struct drive *d) |
| { |
| uint16_t *id; |
| int64_t osectors, s; |
| unsigned char oserial[21]; |
| struct sdunit *u; |
| |
| if (d->info == NULL) { |
| d->infosz = 512 * sizeof(uint16_t); |
| d->info = kzmalloc(d->infosz, 0); |
| printd("ahci: %s: HBA physical address (hack): 0x%08x:0x%08x\n", |
| __func__, paddr_high32(d->info), paddr_low32(d->info)); |
| } |
| if (d->info == NULL) { |
| d->info = d->tinyinfo; |
| d->infosz = sizeof d->tinyinfo; |
| } |
| id = d->info; |
| s = ahciidentify(&d->portc, id); |
| if (s == -1) { |
| d->state = Derror; |
| return -1; |
| } |
| osectors = d->sectors; |
| memmove(oserial, d->serial, sizeof d->serial); |
| |
| u = d->unit; |
| d->sectors = s; |
| d->secsize = u->secsize; |
| if (d->secsize == 0) |
| d->secsize = 512; /* default */ |
| d->smartrs = 0; |
| |
| idmove(d->serial, id + 10, 20); |
| idmove(d->firmware, id + 23, 8); |
| idmove(d->model, id + 27, 40); |
| |
| memset(u->inquiry, 0, sizeof u->inquiry); |
| u->inquiry[2] = 2; |
| u->inquiry[3] = 2; |
| u->inquiry[4] = sizeof u->inquiry - 4; |
| memmove(u->inquiry + 8, d->model, 40); |
| |
| if (osectors != s || memcmp(oserial, d->serial, sizeof oserial) != 0) { |
| d->mediachange = 1; |
| u->sectors = 0; |
| } |
| return 0; |
| } |
| |
| static void clearci(void *p) |
| { |
| uint32_t cmd; |
| |
| cmd = ahci_port_read32(p, PORT_CMD); |
| if (cmd & Ast) { |
| clear_bit32(p, PORT_CMD, Ast); |
| set_bit32(p, PORT_CMD, Ast); |
| } |
| } |
| |
| static void updatedrive(struct drive *d) |
| { |
| uint32_t cause, serr, task, sstatus, ie, s0, pr, ewake; |
| char *name; |
| void *port; |
| static uint32_t last; |
| |
| pr = 1; |
| ewake = 0; |
| port = d->port; |
| cause = ahci_port_read32(port, PORT_IS); |
| serr = ahci_port_read32(port, PORT_SERR); |
| ahci_port_write32(port, PORT_IS, cause); |
| name = "??"; |
| if (d->unit && d->unit->sdperm.name) |
| name = d->unit->sdperm.name; |
| |
| if (ahci_port_read32(port, PORT_CI) == 0) { |
| d->portm.flag |= Fdone; |
| rendez_wakeup(&d->portm.Rendez); |
| pr = 0; |
| } else if (cause & Adps) |
| pr = 0; |
| if (cause & Ifatal) { |
| ewake = 1; |
| printd("ahci: updatedrive: %s: fatal\n", name); |
| } |
| task = ahci_port_read32(port, PORT_TFD); |
| if (cause & Adhrs) { |
| if (task & (1 << 5 | 1)) { |
| printd( |
| "ahci: %s: Adhrs cause %#lx serr %#lx task %#lx\n", |
| name, cause, serr, task); |
| d->portm.flag |= Ferror; |
| ewake = 1; |
| } |
| pr = 0; |
| } |
| sstatus = ahci_port_read32(port, PORT_SSTS); |
| if (task & 1 && last != cause) |
| printd("%s: err ca %#lx serr %#lx task %#lx sstat %#lx\n", name, |
| cause, serr, task, sstatus); |
| if (pr) |
| printd("%s: upd %#lx ta %#lx\n", name, cause, task); |
| |
| if (cause & (Aprcs | Aifs)) { |
| s0 = d->state; |
| switch (sstatus & Devdet) { |
| case 0: /* no device */ |
| d->state = Dmissing; |
| break; |
| case Devpresent: /* device but no phy comm. */ |
| if ((sstatus & Intpm) == Intslumber || |
| (sstatus & Intpm) == Intpartpwr) |
| d->state = Dnew; /* slumbering */ |
| else |
| d->state = Derror; |
| break; |
| case Devpresent | Devphycomm: |
| /* power mgnt crap for surprise removal */ |
| set_bit32(port, PORT_IE, |
| Aprcs | Apcs); /* is this required? */ |
| d->state = Dreset; |
| break; |
| case Devphyoffline: |
| d->state = Doffline; |
| break; |
| } |
| printd("%s: %s → %s [Apcrs] %#lx\n", name, diskstates[s0], |
| diskstates[d->state], sstatus); |
| /* print pulled message here. */ |
| if (s0 == Dready && d->state != Dready) |
| iprintd("%s: pulled\n", name); /* wtf? */ |
| if (d->state != Dready) |
| d->portm.flag |= Ferror; |
| ewake = 1; |
| } |
| ahci_port_write32(port, PORT_SERR, serr); |
| if (ewake) { |
| clearci(port); |
| rendez_wakeup(&d->portm.Rendez); |
| } |
| last = cause; |
| } |
| |
| static void pstatus(struct drive *d, uint32_t s) |
| { |
| /* |
| * s is masked with Devdet. |
| * |
| * bogus code because the first interrupt is currently dropped. |
| * likely my fault. serror may be cleared at the wrong time. |
| */ |
| switch (s) { |
| case 0: /* no device */ |
| d->state = Dmissing; |
| break; |
| case Devpresent: /* device but no phy. comm. */ |
| break; |
| case Devphycomm: /* should this be missing? need testcase. */ |
| printd("ahci: pstatus 2\n"); |
| /* fallthrough */ |
| case Devpresent | Devphycomm: |
| d->wait = 0; |
| d->state = Dnew; |
| break; |
| case Devphyoffline: |
| d->state = Doffline; |
| break; |
| case Devphyoffline | Devphycomm: /* does this make sense? */ |
| d->state = Dnew; |
| break; |
| } |
| } |
| |
| static int configdrive(struct drive *d) |
| { |
| if (ahciconfigdrive(d) == -1) |
| return -1; |
| spin_lock_irqsave(&d->Lock); |
| pstatus(d, ahci_port_read32(d->port, PORT_SSTS) & Devdet); |
| spin_unlock_irqsave(&d->Lock); |
| return 0; |
| } |
| |
| static void setstate(struct drive *d, int state) |
| { |
| spin_lock_irqsave(&d->Lock); |
| d->state = state; |
| spin_unlock_irqsave(&d->Lock); |
| } |
| |
| static void resetdisk(struct drive *d) |
| { |
| unsigned int state, det, cmd, stat; |
| void *port; |
| |
| port = d->port; |
| det = ahci_port_read32(port, PORT_SCTL) & 7; |
| stat = ahci_port_read32(port, PORT_SSTS) & Devdet; |
| cmd = ahci_port_read32(port, PORT_CMD); |
| state = (cmd >> 28) & 0xf; |
| printd("ahci: resetdisk: icc %#x det %d sdet %d\n", state, det, stat); |
| |
| spin_lock_irqsave(&d->Lock); |
| state = d->state; |
| if (d->state != Dready || d->state != Dnew) |
| d->portm.flag |= Ferror; |
| clearci(port); /* satisfy sleep condition. */ |
| rendez_wakeup(&d->portm.Rendez); |
| if (stat != (Devpresent | Devphycomm)) { |
| /* device absent or phy not communicating */ |
| d->state = Dportreset; |
| spin_unlock_irqsave(&d->Lock); |
| return; |
| } |
| d->state = Derror; |
| spin_unlock_irqsave(&d->Lock); |
| |
| qlock(&d->portm.ql); |
| if (cmd & Ast && ahciswreset(&d->portc) == -1) |
| setstate(d, Dportreset); /* get a bigger stick. */ |
| else { |
| setstate(d, Dmissing); |
| configdrive(d); |
| } |
| printd("ahci: %s: resetdisk: %s → %s\n", |
| (d->unit ? d->unit->sdperm.name : NULL), diskstates[state], |
| diskstates[d->state]); |
| qunlock(&d->portm.ql); |
| } |
| |
| static int newdrive(struct drive *d) |
| { |
| char *name; |
| struct aportc *pc; |
| struct aportm *pm; |
| |
| pc = &d->portc; |
| pm = &d->portm; |
| |
| name = d->unit->sdperm.name; |
| if (name == 0) |
| name = "??"; |
| |
| if (ahci_port_read32(d->port, PORT_TFD) == 0x80) |
| return -1; |
| qlock(&pc->pm->ql); |
| if (setudmamode(pc, 5) == -1) { |
| printd("%s: can't set udma mode\n", name); |
| goto lose; |
| } |
| if (identify(d) == -1) { |
| printd("%s: identify failure\n", name); |
| goto lose; |
| } |
| if (pm->feat & Dpower && setfeatures(pc, 0x85) == -1) { |
| pm->feat &= ~Dpower; |
| if (ahcirecover(pc) == -1) |
| goto lose; |
| } |
| setstate(d, Dready); |
| qunlock(&pc->pm->ql); |
| |
| iprintd("%s: %sLBA %llu sectors: %s %s %s %s\n", d->unit->sdperm.name, |
| (pm->feat & Dllba ? "L" : ""), d->sectors, d->model, |
| d->firmware, d->serial, d->mediachange ? "[mediachange]" : ""); |
| return 0; |
| |
| lose: |
| iprintd("%s: can't be initialized\n", d->unit->sdperm.name); |
| setstate(d, Dnull); |
| qunlock(&pc->pm->ql); |
| return -1; |
| } |
| |
| static void westerndigitalhung(struct drive *d) |
| { |
| uint32_t task, ci; |
| |
| task = ahci_port_read32(d->port, PORT_TFD); |
| ci = ahci_port_read32(d->port, PORT_CI); |
| |
| if ((d->portm.feat & Datapi) == 0 && d->active && |
| (ms() - d->intick) > 5000) { |
| printd("%s: drive hung; resetting [%#lx] ci %#lx\n", |
| d->unit->sdperm.name, task, ci); |
| d->state = Dreset; |
| } |
| } |
| |
| static uint16_t olds[NCtlr * NCtlrdrv]; |
| |
| static int doportreset(struct drive *d) |
| { |
| int i; |
| |
| i = -1; |
| qlock(&d->portm.ql); |
| if (ahciportreset(&d->portc) == -1) |
| printd("ahci: doportreset: fails\n") else i = 0; |
| qunlock(&d->portm.ql); |
| printd("ahci: doportreset: portreset → %s [task %#lx]\n", |
| diskstates[d->state], ahci_port_read32(d->port, PORT_TFD)); |
| return i; |
| } |
| |
| /* drive must be locked */ |
| static void statechange(struct drive *d) |
| { |
| switch (d->state) { |
| case Dnull: |
| case Doffline: |
| if (d->unit->sectors != 0) { |
| d->sectors = 0; |
| d->mediachange = 1; |
| } |
| /* fallthrough */ |
| case Dready: |
| d->wait = 0; |
| break; |
| } |
| } |
| |
| static void checkdrive(struct drive *d, int i) |
| { |
| uint16_t s; |
| uint32_t task; |
| char *name; |
| |
| if (d == NULL) { |
| printd("checkdrive: NULL d\n"); |
| return; |
| } |
| spin_lock_irqsave(&d->Lock); |
| if (d->unit == NULL || d->port == NULL) { |
| if (0) |
| printk("checkdrive: nil d->%s\n", |
| d->unit == NULL ? "unit" : "port"); |
| spin_unlock_irqsave(&d->Lock); |
| return; |
| } |
| name = d->unit->sdperm.name; |
| s = ahci_port_read32(d->port, PORT_SSTS); |
| if (s) |
| d->lastseen = ms(); |
| if (s != olds[i]) { |
| printd("%s: status: %06#x -> %06#x: %s\n", name, olds[i], s, |
| diskstates[d->state]); |
| olds[i] = s; |
| d->wait = 0; |
| } |
| westerndigitalhung(d); |
| |
| switch (d->state) { |
| case Dnull: |
| case Dready: |
| break; |
| case Dmissing: |
| case Dnew: |
| switch (s & (Intactive | Devdet)) { |
| case Devpresent: /* no device (pm), device but no phy. comm. */ |
| ahciwakeup(d->port); |
| /* fall through */ |
| case 0: /* no device */ |
| break; |
| default: |
| printd("%s: unknown status %06#x\n", name, s); |
| /* fall through */ |
| case Intactive: /* active, no device */ |
| if (++d->wait & Mphywait) |
| break; |
| reset: |
| if (++d->mode > DMsataii) |
| d->mode = 0; |
| if (d->mode == DMsatai) { /* we tried everything */ |
| d->state = Dportreset; |
| goto portreset; |
| } |
| printd("%s: reset; new mode %s\n", name, |
| modename[d->mode]); |
| spin_unlock_irqsave(&d->Lock); |
| resetdisk(d); |
| spin_lock_irqsave(&d->Lock); |
| break; |
| case Intactive | Devphycomm | Devpresent: |
| task = ahci_port_read32(d->port, PORT_TFD); |
| if ((++d->wait & Midwait) == 0) { |
| printd("%s: slow reset %06#x task=%#lx; %d\n", |
| name, s, task, d->wait); |
| goto reset; |
| } |
| s = (unsigned char)task; |
| if (s == 0x7f || |
| ((ahci_port_read32(d->port, PORT_SIG) >> 16) != |
| 0xeb14 && |
| (s & ~0x17) != (1 << 6))) |
| break; |
| spin_unlock_irqsave(&d->Lock); |
| newdrive(d); |
| spin_lock_irqsave(&d->Lock); |
| break; |
| } |
| break; |
| case Doffline: |
| if (d->wait++ & Mcomrwait) |
| break; |
| /* fallthrough */ |
| case Derror: |
| case Dreset: |
| printd("%s: reset [%s]: mode %d; status %06#x\n", name, |
| diskstates[d->state], d->mode, s); |
| spin_unlock_irqsave(&d->Lock); |
| resetdisk(d); |
| spin_lock_irqsave(&d->Lock); |
| break; |
| case Dportreset: |
| portreset: |
| if (d->wait++ & 0xff && (s & Intactive) == 0) |
| break; |
| /* device is active */ |
| printd("%s: portreset [%s]: mode %d; status %06#x\n", name, |
| diskstates[d->state], d->mode, s); |
| d->portm.flag |= Ferror; |
| clearci(d->port); |
| rendez_wakeup(&d->portm.Rendez); |
| if ((s & Devdet) == 0) { /* no device */ |
| d->state = Dmissing; |
| break; |
| } |
| spin_unlock_irqsave(&d->Lock); |
| doportreset(d); |
| spin_lock_irqsave(&d->Lock); |
| break; |
| } |
| statechange(d); |
| spin_unlock_irqsave(&d->Lock); |
| } |
| |
| static void satakproc(void *v) |
| { |
| int i; |
| for (;;) { |
| kthread_usleep(Nms * 1000); |
| for (i = 0; i < niadrive; i++) |
| if (iadrive[i] != NULL) |
| checkdrive(iadrive[i], i); |
| } |
| } |
| |
| static void isctlrjabbering(struct ctlr *c, uint32_t cause) |
| { |
| uint32_t now; |
| |
| now = ms(); |
| if (now > c->lastintr0) { |
| c->intrs = 0; |
| c->lastintr0 = now; |
| } |
| if (++c->intrs > Maxintrspertick) { |
| iprint("sdiahci: %lu intrs per tick for no serviced " |
| "drive; cause %#lx mport %d\n", |
| c->intrs, cause, c->mport); |
| c->intrs = 0; |
| } |
| } |
| |
| static void isdrivejabbering(struct drive *d) |
| { |
| uint32_t now = ms(); |
| |
| if (now > d->lastintr0) { |
| d->intrs = 0; |
| d->lastintr0 = now; |
| } |
| if (++d->intrs > Maxintrspertick) { |
| iprint("sdiahci: %lu interrupts per tick for %s\n", d->intrs, |
| d->unit->sdperm.name); |
| d->intrs = 0; |
| } |
| } |
| |
| static void iainterrupt(struct hw_trapframe *unused_hw_trapframe, void *a) |
| { |
| int i; |
| uint32_t cause, mask, p_is, h_is, pi; |
| struct ctlr *c; |
| struct drive *d; |
| |
| c = a; |
| spin_lock_irqsave(&c->Lock); |
| cause = ahci_hba_read32(c->hba, HBA_ISR); |
| if (cause == 0) { |
| isctlrjabbering(c, cause); |
| // iprint("sdiahci: interrupt for no drive\n"); |
| spin_unlock_irqsave(&c->Lock); |
| return; |
| } |
| for (i = 0; cause && i <= c->mport; i++) { |
| mask = 1 << i; |
| if ((cause & mask) == 0) |
| continue; |
| d = c->rawdrive + i; |
| spin_lock_irqsave(&d->Lock); |
| isdrivejabbering(d); |
| p_is = ahci_port_read32(d->port, PORT_IS); |
| pi = ahci_hba_read32(c->hba, HBA_PI); |
| if (p_is && pi & mask) |
| updatedrive(d); |
| ahci_hba_write32(c->hba, HBA_ISR, mask); |
| spin_unlock_irqsave(&d->Lock); |
| |
| cause &= ~mask; |
| } |
| if (cause) { |
| isctlrjabbering(c, cause); |
| iprint("sdiahci: intr cause unserviced: %#lx\n", cause); |
| } |
| spin_unlock_irqsave(&c->Lock); |
| } |
| |
| /* checkdrive, called from satakproc, will prod the drive while we wait */ |
| static void awaitspinup(struct drive *d) |
| { |
| int ms; |
| uint16_t s; |
| char *name; |
| |
| spin_lock_irqsave(&d->Lock); |
| if (d->unit == NULL || d->port == NULL) { |
| panic("awaitspinup: NULL d->unit or d->port"); |
| spin_unlock_irqsave(&d->Lock); |
| return; |
| } |
| name = (d->unit ? d->unit->sdperm.name : NULL); |
| s = ahci_port_read32(d->port, PORT_SSTS); |
| if (!(s & Devpresent)) { /* never going to be ready */ |
| printd("awaitspinup: %s absent, not waiting\n", name); |
| spin_unlock_irqsave(&d->Lock); |
| return; |
| } |
| |
| for (ms = 20000; ms > 0; ms -= 50) |
| switch (d->state) { |
| case Dnull: |
| /* absent; done */ |
| spin_unlock_irqsave(&d->Lock); |
| printd("awaitspinup: %s in null state\n", name); |
| return; |
| case Dready: |
| case Dnew: |
| if (d->sectors || d->mediachange) { |
| /* ready to use; done */ |
| spin_unlock_irqsave(&d->Lock); |
| printd("awaitspinup: %s ready!\n", name); |
| return; |
| } |
| /* fall through */ |
| default: |
| case Dmissing: /* normal waiting states */ |
| case Dreset: |
| case Doffline: /* transitional states */ |
| case Derror: |
| case Dportreset: |
| spin_unlock_irqsave(&d->Lock); |
| asleep(50); |
| spin_lock_irqsave(&d->Lock); |
| break; |
| } |
| printd("awaitspinup: %s didn't spin up after 20 seconds\n", name); |
| spin_unlock_irqsave(&d->Lock); |
| } |
| |
| static int iaverify(struct sdunit *u) |
| { |
| struct ctlr *c; |
| struct drive *d; |
| |
| c = u->dev->ctlr; |
| d = c->drive[u->subno]; |
| spin_lock_irqsave(&c->Lock); |
| spin_lock_irqsave(&d->Lock); |
| d->unit = u; |
| spin_unlock_irqsave(&d->Lock); |
| spin_unlock_irqsave(&c->Lock); |
| checkdrive(d, d->driveno); /* c->d0 + d->driveno */ |
| |
| /* |
| * hang around until disks are spun up and thus available as |
| * nvram, dos file systems, etc. you wouldn't expect it, but |
| * the intel 330 ssd takes a while to `spin up'. |
| */ |
| awaitspinup(d); |
| return 1; |
| } |
| |
| static int iaenable(struct sdev *s) |
| { |
| char name[32]; |
| struct ctlr *c; |
| static int once; |
| |
| c = s->ctlr; |
| spin_lock_irqsave(&c->Lock); |
| if (!c->enabled) { |
| if (once == 0) { |
| once = 1; |
| ktask("ahci", satakproc, 0); |
| } |
| if (c->ndrive == 0) |
| panic("iaenable: zero s->ctlr->ndrive"); |
| pci_set_bus_master(c->pci); |
| snprintf(name, sizeof(name), "%s (%s)", s->name, s->ifc->name); |
| /*c->vector = intrenable(c->pci->intl, iainterrupt, c, |
| *c->pci->tbdf, name);*/ |
| /* what do we do about the arg? */ |
| register_irq(c->pci->irqline, iainterrupt, c, |
| pci_to_tbdf(c->pci)); |
| /* supposed to squelch leftover interrupts here. */ |
| ahcienable(c->hba); |
| c->enabled = 1; |
| } |
| spin_unlock_irqsave(&c->Lock); |
| return 1; |
| } |
| |
| static int iadisable(struct sdev *s) |
| { |
| char name[32]; |
| struct ctlr *c; |
| |
| c = s->ctlr; |
| spin_lock_irqsave(&c->Lock); |
| ahcidisable(c->hba); |
| snprintf(name, sizeof(name), "%s (%s)", s->name, s->ifc->name); |
| // TODO: what to do here? |
| // intrdisable(c->vector); |
| c->enabled = 0; |
| spin_unlock_irqsave(&c->Lock); |
| return 1; |
| } |
| |
| static int iaonline(struct sdunit *unit) |
| { |
| int r; |
| struct ctlr *c; |
| struct drive *d; |
| |
| c = unit->dev->ctlr; |
| d = c->drive[unit->subno]; |
| r = 0; |
| |
| if ((d->portm.feat & Datapi) && d->mediachange) { |
| r = scsionline(unit); |
| if (r > 0) |
| d->mediachange = 0; |
| return r; |
| } |
| |
| spin_lock_irqsave(&d->Lock); |
| if (d->mediachange) { |
| r = 2; |
| d->mediachange = 0; |
| /* devsd resets this after online is called; why? */ |
| unit->sectors = d->sectors; |
| unit->secsize = 512; /* default size */ |
| } else if (d->state == Dready) |
| r = 1; |
| spin_unlock_irqsave(&d->Lock); |
| return r; |
| } |
| |
| /* returns locked list! */ |
| static void *ahcibuild(struct drive *d, unsigned char *cmd, void *data, int n, |
| int64_t lba) |
| { |
| void *cfis, *list, *prdt, *ctab; |
| unsigned char acmd, dir, llba, c7; |
| struct aportm *pm; |
| uint32_t flags; |
| static unsigned char tab[2][2] = { |
| {0xc8, 0x25}, |
| {0xca, 0x35}, |
| }; |
| |
| pm = &d->portm; |
| dir = *cmd != 0x28; |
| llba = pm->feat & Dllba ? 1 : 0; |
| acmd = tab[dir][llba]; |
| qlock(&pm->ql); |
| list = pm->list; |
| ctab = pm->ctab; |
| cfis = ctab; |
| |
| ahci_cfis_write8(cfis, 0, 0x27); |
| ahci_cfis_write8(cfis, 1, 0x80); |
| ahci_cfis_write8(cfis, 2, acmd); |
| ahci_cfis_write8(cfis, 3, 0); |
| |
| ahci_cfis_write8(cfis, 4, lba); /* sector lba low |
| 7:0 */ |
| ahci_cfis_write8(cfis, 5, lba >> 8); /* cylinder low lba mid 15:8 */ |
| ahci_cfis_write8(cfis, 6, lba >> 16); /* cylinder hi lba hi 23:16 */ |
| c7 = Obs | 0x40; /* 0x40 == lba */ |
| if (llba == 0) |
| c7 |= (lba >> 24) & 7; |
| ahci_cfis_write8(cfis, 7, c7); |
| |
| ahci_cfis_write8( |
| cfis, 8, |
| lba >> 24); /* sector (exp) lba 31:24 */ |
| ahci_cfis_write8(cfis, 9, |
| lba >> 32); /* cylinder low (exp) lba 39:32 */ |
| ahci_cfis_write8( |
| cfis, 10, lba >> 48); /* cylinder hi (exp) lba 48:40 */ |
| ahci_cfis_write8(cfis, 11, 0); /* features (exp); */ |
| |
| ahci_cfis_write8(cfis, 12, n); /* sector count */ |
| ahci_cfis_write8(cfis, 13, n >> 8); /* sector count (exp) */ |
| ahci_cfis_write8(cfis, 14, 0); /* r */ |
| ahci_cfis_write8(cfis, 15, 0); /* control */ |
| |
| ahci_cfis_write8(cfis, 16, 0); |
| ahci_cfis_write8(cfis, 17, 0); |
| ahci_cfis_write8(cfis, 18, 0); |
| ahci_cfis_write8(cfis, 19, 0); |
| |
| flags = 1 << 16 | Lpref | 0x5; /* Lpref ?? */ |
| if (dir == Write) |
| flags |= Lwrite; |
| ahci_list_write32(list, ALIST_FLAGS, flags); |
| ahci_list_write32(list, ALIST_LEN, 0); |
| ahci_list_write32(list, ALIST_CTAB, paddr_low32(ctab)); |
| ahci_list_write32(list, ALIST_CTABHI, paddr_high32(ctab)); |
| |
| prdt = ctab + ACTAB_PRDT; |
| ahci_prdt_write32(prdt, APRDT_DBA, paddr_low32(data)); |
| ahci_prdt_write32(prdt, APRDT_DBAHI, paddr_high32(data)); |
| if (d->unit == NULL) |
| panic("ahcibuild: NULL d->unit"); |
| ahci_prdt_write32(prdt, APRDT_COUNT, |
| 1 << 31 | (d->unit->secsize * n - 2) | 1); |
| |
| return list; |
| } |
| |
| static void *ahcibuildpkt(struct aportm *pm, struct sdreq *r, void *data, int n) |
| { |
| int fill, len, i; |
| void *cfis, *list, *ctab, *prdt; |
| uint32_t flags; |
| |
| qlock(&pm->ql); |
| list = pm->list; |
| ctab = pm->ctab; |
| cfis = ctab; |
| |
| fill = pm->feat & Datapi16 ? 16 : 12; |
| if ((len = r->clen) > fill) |
| len = fill; |
| memmove(ctab + ACTAB_ATAPI, r->cmd, len); |
| memset(ctab + ACTAB_ATAPI + len, 0, fill - len); |
| |
| ahci_cfis_write8(cfis, 0, 0x27); |
| ahci_cfis_write8(cfis, 1, 0x80); |
| ahci_cfis_write8(cfis, 2, 0xa0); |
| if (n != 0) |
| ahci_cfis_write8(cfis, 3, 1); /* dma */ |
| else |
| ahci_cfis_write8(cfis, 3, 0); /* features (exp); */ |
| |
| ahci_cfis_write8(cfis, 4, 0); /* sector lba low 7:0 */ |
| ahci_cfis_write8(cfis, 5, n); /* cylinder low lba mid 15:8 */ |
| ahci_cfis_write8(cfis, 6, |
| n >> 8); /* cylinder hi lba hi 23:16 */ |
| ahci_cfis_write8(cfis, 7, Obs); |
| |
| for (i = 0; i < 12; i++) |
| ahci_cfis_write8(cfis, 8 + i, 0); |
| |
| flags = 1 << 16 | Lpref | Latapi | 0x5; |
| if (r->write != 0 && data) |
| flags |= Lwrite; |
| ahci_list_write32(list, ALIST_FLAGS, flags); |
| ahci_list_write32(list, ALIST_LEN, 0); |
| ahci_list_write32(list, ALIST_CTAB, paddr_low32(ctab)); |
| ahci_list_write32(list, ALIST_CTABHI, paddr_high32(ctab)); |
| printd("ahci: %s: LIST->CTAB physical address=0x%08x:0x%08x\n", |
| __func__, paddr_high32(ctab), paddr_low32(ctab)); |
| |
| if (data == 0) |
| return list; |
| |
| prdt = ctab + ACTAB_PRDT; |
| ahci_prdt_write32(prdt, APRDT_DBA, paddr_low32(data)); |
| ahci_prdt_write32(prdt, APRDT_DBAHI, paddr_high32(data)); |
| ahci_prdt_write32(prdt, APRDT_COUNT, 1 << 31 | (n - 2) | 1); |
| printd("ahci: %s: PRDT->DBA physical address=0x%08x:0x%08x\n", |
| __func__, paddr_high32(data), paddr_low32(data)); |
| |
| return list; |
| } |
| |
| static int waitready(struct drive *d) |
| { |
| uint32_t s, i, delta; |
| |
| for (i = 0; i < 15000; i += 250) { |
| if (d->state == Dreset || d->state == Dportreset || |
| d->state == Dnew) |
| return 1; |
| delta = ms() - d->lastseen; |
| if (d->state == Dnull || delta > 10 * 1000) |
| return -1; |
| spin_lock_irqsave(&d->Lock); |
| s = ahci_port_read32(d->port, PORT_SSTS); |
| spin_unlock_irqsave(&d->Lock); |
| if ((s & Intpm) == 0 && delta > 1500) |
| return -1; /* no detect */ |
| if (d->state == Dready && |
| (s & Devdet) == (Devphycomm | Devpresent)) |
| return 0; /* ready, present & phy. comm. */ |
| esleep(250); |
| } |
| printd("%s: not responding; offline\n", d->unit->sdperm.name); |
| setstate(d, Doffline); |
| return -1; |
| } |
| |
| static int lockready(struct drive *d) |
| { |
| int i; |
| |
| qlock(&d->portm.ql); |
| while ((i = waitready(d)) == 1) { /* could wait forever? */ |
| qunlock(&d->portm.ql); |
| esleep(1); |
| qlock(&d->portm.ql); |
| } |
| return i; |
| } |
| |
| static int flushcache(struct drive *d) |
| { |
| int i; |
| |
| i = -1; |
| if (lockready(d) == 0) |
| i = ahciflushcache(&d->portc); |
| qunlock(&d->portm.ql); |
| return i; |
| } |
| |
| static int iariopkt(struct sdreq *r, struct drive *d) |
| { |
| ERRSTACK(2); |
| int n, count, try |
| , max, flag, task, wormwrite; |
| char *name; |
| unsigned char *cmd, *data; |
| void *port; |
| struct Asleep as; |
| |
| cmd = r->cmd; |
| name = d->unit->sdperm.name; |
| port = d->port; |
| |
| aprintd("ahci: iariopkt: %04#x %04#x %c %d %p\n", cmd[0], cmd[2], |
| "rw"[r->write], r -> dlen, r -> data); |
| if (cmd[0] == 0x5a && (cmd[2] & 0x3f) == 0x3f) |
| return sdmodesense(r, cmd, d->info, d->infosz); |
| r->rlen = 0; |
| count = r->dlen; |
| max = 65536; |
| |
| try |
| = 0; |
| retry: |
| data = r->data; |
| n = count; |
| if (n > max) |
| n = max; |
| ahcibuildpkt(&d->portm, r, data, n); |
| switch (waitready(d)) { |
| case -1: |
| qunlock(&d->portm.ql); |
| return SDeio; |
| case 1: |
| qunlock(&d->portm.ql); |
| esleep(1); |
| goto retry; |
| } |
| /* d->portm qlock held here */ |
| |
| spin_lock_irqsave(&d->Lock); |
| d->portm.flag = 0; |
| spin_unlock_irqsave(&d->Lock); |
| ahci_port_write32(port, PORT_CI, 1); |
| |
| as.p = port; |
| as.i = 1; |
| d->intick = ms(); |
| d->active++; |
| |
| while (waserror()) |
| poperror(); |
| /* don't sleep here forever */ |
| rendez_sleep_timeout(&d->portm.Rendez, ahciclear, &as, |
| (3 * 1000) * 1000); |
| poperror(); |
| if (!ahciclear(&as)) { |
| qunlock(&d->portm.ql); |
| printd("%s: ahciclear not true after 3 seconds\n", name); |
| r->status = SDcheck; |
| return SDcheck; |
| } |
| |
| d->active--; |
| spin_lock_irqsave(&d->Lock); |
| flag = d->portm.flag; |
| task = ahci_port_read32(port, PORT_TFD); |
| spin_unlock_irqsave(&d->Lock); |
| |
| if ((task & (Efatal << 8)) || |
| ((task & (ASbsy | ASdrq)) && (d->state == Dready))) { |
| ahci_port_write32(port, PORT_CI, 0); |
| ahcirecover(&d->portc); |
| task = ahci_port_read32(port, PORT_TFD); |
| flag &= ~Fdone; /* either an error or do-over */ |
| } |
| qunlock(&d->portm.ql); |
| if (flag == 0) { |
| if (++try == 10) { |
| printd("%s: bad disk\n", name); |
| r->status = SDcheck; |
| return SDcheck; |
| } |
| /* |
| * write retries cannot succeed on write-once media, |
| * so just accept any failure. |
| */ |
| wormwrite = 0; |
| switch (d->unit->inquiry[0] & SDinq0periphtype) { |
| case SDperworm: |
| case SDpercd: |
| switch (cmd[0]) { |
| case 0x0a: /* write (6?) */ |
| case 0x2a: /* write (10) */ |
| case 0x8a: /* int32_t write (16) */ |
| case 0x2e: /* write and verify (10) */ |
| wormwrite = 1; |
| break; |
| } |
| break; |
| } |
| if (!wormwrite) { |
| printd("%s: retry\n", name); |
| goto retry; |
| } |
| } |
| if (flag & Ferror) { |
| if ((task & Eidnf) == 0) |
| printd("%s: i/o error task=%#x\n", name, task); |
| r->status = SDcheck; |
| return SDcheck; |
| } |
| |
| data += n; |
| |
| r->rlen = data - (unsigned char *)r->data; |
| r->status = SDok; |
| return SDok; |
| } |
| |
| static int iario(struct sdreq *r) |
| { |
| ERRSTACK(1); |
| int i, n, count, try |
| , max, flag, task; |
| uint64_t lba; |
| char *name; |
| unsigned char *cmd, *data; |
| void *port; |
| struct Asleep as; |
| struct ctlr *c; |
| struct drive *d; |
| struct sdunit *unit; |
| |
| unit = r->unit; |
| c = unit->dev->ctlr; |
| d = c->drive[unit->subno]; |
| if (d->portm.feat & Datapi) |
| return iariopkt(r, d); |
| cmd = r->cmd; |
| name = d->unit->sdperm.name; |
| port = d->port; |
| |
| if (r->cmd[0] == 0x35 || r->cmd[0] == 0x91) { |
| if (flushcache(d) == 0) |
| return sdsetsense(r, SDok, 0, 0, 0); |
| return sdsetsense(r, SDcheck, 3, 0xc, 2); |
| } |
| |
| if ((i = sdfakescsi(r, d->info, d->infosz)) != SDnostatus) { |
| r->status = i; |
| return i; |
| } |
| |
| if (*cmd != 0x28 && *cmd != 0x2a) { |
| printd("%s: bad cmd %.2#x\n", name, cmd[0]); |
| r->status = SDcheck; |
| return SDcheck; |
| } |
| |
| // TODO: make cmd bigger to support drives with >= 2 TiB capacity, |
| // with 32 bits and 512 B blocks only 2^(9+32) = 2 TiB addressable |
| lba = (uint32_t)(cmd[2] << 24) | cmd[3] << 16 | cmd[4] << 8 | cmd[5]; |
| count = cmd[7] << 8 | cmd[8]; |
| if (r->data == NULL) |
| return SDok; |
| if (r->dlen < count * unit->secsize) |
| count = r->dlen / unit->secsize; |
| max = 128; |
| |
| try |
| = 0; |
| retry: |
| data = r->data; |
| while (count > 0) { |
| n = count; |
| if (n > max) |
| n = max; |
| ahcibuild(d, cmd, data, n, lba); |
| switch (waitready(d)) { |
| case -1: |
| qunlock(&d->portm.ql); |
| return SDeio; |
| case 1: |
| qunlock(&d->portm.ql); |
| esleep(1); |
| goto retry; |
| } |
| /* d->portm qlock held here */ |
| spin_lock_irqsave(&d->Lock); |
| d->portm.flag = 0; |
| spin_unlock_irqsave(&d->Lock); |
| ahci_port_write32(port, PORT_CI, 1); |
| |
| as.p = port; |
| as.i = 1; |
| d->intick = ms(); |
| d->active++; |
| |
| while (waserror()) |
| poperror(); |
| /* don't sleep here forever */ |
| rendez_sleep_timeout(&d->portm.Rendez, ahciclear, &as, |
| (3 * 1000) * 1000); |
| poperror(); |
| if (!ahciclear(&as)) { |
| qunlock(&d->portm.ql); |
| printd("%s: ahciclear not true after 3 seconds\n", |
| name); |
| r->status = SDcheck; |
| return SDcheck; |
| } |
| |
| d->active--; |
| spin_lock_irqsave(&d->Lock); |
| flag = d->portm.flag; |
| task = ahci_port_read32(port, PORT_TFD); |
| spin_unlock_irqsave(&d->Lock); |
| |
| if ((task & (Efatal << 8)) || |
| ((task & (ASbsy | ASdrq)) && d->state == Dready)) { |
| ahci_port_write32(port, PORT_CI, 0); |
| ahcirecover(&d->portc); |
| task = ahci_port_read32(port, PORT_TFD); |
| } |
| qunlock(&d->portm.ql); |
| if (flag == 0) { |
| if (++try == 10) { |
| printd("%s: bad disk\n", name); |
| r->status = SDeio; |
| return SDeio; |
| } |
| printd("%s: retry blk %lld\n", name, lba); |
| goto retry; |
| } |
| if (flag & Ferror) { |
| printk("%s: i/o error task=%#x @%lld\n", name, task, |
| lba); |
| r->status = SDeio; |
| return SDeio; |
| } |
| |
| count -= n; |
| lba += n; |
| data += n * unit->secsize; |
| } |
| r->rlen = data - (unsigned char *)r->data; |
| r->status = SDok; |
| return SDok; |
| } |
| |
| /* |
| * configure drives 0-5 as ahci sata (c.f. errata). |
| * what about 6 & 7, as claimed by marvell 0x9123? |
| */ |
| static int iaahcimode(struct pci_device *p) |
| { |
| printd("iaahcimode: %#x %#x %#x\n", pcidev_read8(p, 0x91), |
| pcidev_read8(p, 92), pcidev_read8(p, 93)); |
| pcidev_write16(p, 0x92, pcidev_read16(p, 0x92) | 0x3f); /* ports 0-5 */ |
| return 0; |
| } |
| |
| static void iasetupahci(struct ctlr *c) |
| { |
| void *p = c->mmio; /* This is actually a pointer to HBA */ |
| /* disable cmd block decoding. */ |
| pcidev_write16(c->pci, 0x40, pcidev_read16(c->pci, 0x40) & ~(1 << 15)); |
| pcidev_write16(c->pci, 0x42, pcidev_read16(c->pci, 0x42) & ~(1 << 15)); |
| |
| ahci_hba_write32(p, HBA_GHC, |
| 1 << 31); /* enable ahci mode (ghc register) */ |
| ahci_hba_write32(p, HBA_PI, |
| (1 << 6) - 1); /* 5 ports. (supposedly ro pi reg.) */ |
| |
| /* enable ahci mode and 6 ports; from ich9 datasheet */ |
| pcidev_write16(c->pci, 0x90, 1 << 6 | 1 << 5); |
| } |
| |
| static int didtype(struct pci_device *p) |
| { |
| switch (p->ven_id) { |
| case Vintel: |
| if ((p->dev_id & 0xfffc) == 0x2680) |
| return Tesb; |
| /* |
| * 0x27c4 is the intel 82801 in compatibility (not sata) mode. |
| */ |
| if (p->dev_id == 0x1e02 || /* c210 */ |
| p->dev_id == 0x24d1 || /* 82801eb/er */ |
| (p->dev_id & 0xfffb) == 0x27c1 || /* 82801g[bh]m ich7 */ |
| p->dev_id == 0x2821 || /* 82801h[roh] */ |
| (p->dev_id & 0xfffe) == 0x2824 || /* 82801h[b] */ |
| (p->dev_id & 0xfeff) == 0x2829 || /* ich8/9m */ |
| (p->dev_id & 0xfffe) == 0x2922 || /* ich9 */ |
| p->dev_id == 0x3a02 || /* 82801jd/do */ |
| (p->dev_id & 0xfefe) == 0x3a22 || /* ich10, pch */ |
| (p->dev_id & 0xfff8) == 0x3b28 || /* pchm */ |
| p->dev_id == 0x1d02) /* c600/x79 pch */ |
| return Tich; |
| break; |
| case Vatiamd: |
| if (p->dev_id == 0x4380 || p->dev_id == 0x4390 || |
| p->dev_id == 0x4391) { |
| printd("detected sb600 vid %#x did %#x\n", p->ven_id, |
| p->dev_id); |
| return Tsb600; |
| } |
| break; |
| case Vmarvell: |
| if (p->dev_id == 0x9123) |
| printk("ahci: marvell sata 3 controller has delusions " |
| "of something on unit 7\n"); |
| break; |
| } |
| if (p->class == Pcibcstore && p->subclass == Pciscsata && |
| p->progif == 1) { |
| printd("ahci: Tunk: vid %#4.4x did %#4.4x\n", p->ven_id, |
| p->dev_id); |
| return Tunk; |
| } |
| return -1; |
| } |
| |
| static int newctlr(struct ctlr *ctlr, struct sdev *sdev, int nunit) |
| { |
| int i, n; |
| struct drive *drive; |
| uint32_t h_cap, pi; |
| |
| ctlr->ndrive = sdev->nunit = nunit; |
| h_cap = ahci_hba_read32(ctlr->hba, HBA_CAP); |
| printd("ahci: %s: HBA_CAP=0x%08x\n", __func__, h_cap); |
| ctlr->mport = h_cap & ((1 << 5) - 1); |
| |
| i = (h_cap >> 20) & ((1 << 4) - 1); /* iss */ |
| printk("#S/sd%c: %s: %#p %s, %d ports, irq %d\n", sdev->idno, |
| Tname(ctlr), ctlr->physio, descmode[i], nunit, |
| ctlr->pci->irqline); |
| /* map the drives -- they don't all need to be enabled. */ |
| n = 0; |
| ctlr->rawdrive = kzmalloc(NCtlrdrv * sizeof(struct drive), 0); |
| if (ctlr->rawdrive == NULL) { |
| printd("ahci: out of memory\n"); |
| return -1; |
| } |
| pi = ahci_hba_read32(ctlr->hba, HBA_PI); |
| for (i = 0; i < NCtlrdrv; i++) { |
| drive = ctlr->rawdrive + i; |
| spinlock_init_irqsave(&drive->Lock); |
| drive->portno = i; |
| drive->driveno = -1; |
| drive->sectors = 0; |
| drive->serial[0] = ' '; |
| drive->ctlr = ctlr; |
| if ((pi & (1 << i)) == 0) |
| continue; |
| drive->port = ctlr->mmio + 0x80 * i + 0x100; |
| drive->portc.p = drive->port; |
| drive->portc.pm = &drive->portm; |
| qlock_init(&drive->portm.ql); |
| rendez_init(&drive->portm.Rendez); |
| drive->driveno = n++; |
| ctlr->drive[drive->driveno] = drive; |
| iadrive[niadrive + drive->driveno] = drive; |
| } |
| for (i = 0; i < n; i++) |
| if (ahciidle(ctlr->drive[i]->port) == -1) { |
| printd("ahci: %s: port %d wedged; abort\n", Tname(ctlr), |
| i); |
| return -1; |
| } |
| for (i = 0; i < n; i++) { |
| ctlr->drive[i]->mode = DMsatai; |
| configdrive(ctlr->drive[i]); |
| } |
| return n; |
| } |
| |
| static void releasedrive(struct kref *kref) |
| { |
| printk("release drive called, but we don't do that yet\n"); |
| } |
| |
| static struct sdev *iapnp(void) |
| { |
| int n, nunit, type; |
| struct ctlr *c; |
| struct pci_device *p; |
| struct sdev *head, *tail, *s; |
| |
| // TODO: ensure we're only called once. |
| |
| memset(olds, 0xff, sizeof olds); |
| p = NULL; |
| head = tail = NULL; |
| STAILQ_FOREACH (p, &pci_devices, all_dev) { |
| type = didtype(p); |
| if (type == -1) |
| continue; |
| printd("ahci: %s: ven_id=0x%04x, dev_id=0x%04x, didtype=%d\n", |
| __func__, p->ven_id, p->dev_id, type); |
| if (p->bar[Abar].mmio_base32 == 0) |
| continue; |
| if (niactlr == NCtlr) { |
| printk("ahci: iapnp: %s: too many controllers\n", |
| tname[type]); |
| break; |
| } |
| c = iactlr + niactlr; |
| s = sdevs + niactlr; |
| memset(c, 0, sizeof *c); |
| memset(s, 0, sizeof *s); |
| kref_init(&s->r, releasedrive, 1); |
| qlock_init(&s->ql); |
| qlock_init(&s->unitlock); |
| c->physio = p->bar[Abar].mmio_base32 & ~0xf; |
| c->mmio = |
| (void *)vmap_pmem_nocache(c->physio, p->bar[Abar].mmio_sz); |
| spinlock_init_irqsave(&c->Lock); |
| if (c->mmio == 0) { |
| printk("ahci: %s: address %#lX in use did=%#x\n", |
| Tname(c), c->physio, p->dev_id); |
| continue; |
| } |
| printk("sdiahci %s: Mapped %p/%d to %p\n", tname[type], |
| c->physio, p->bar[Abar].mmio_sz, c->mmio); |
| c->pci = p; |
| c->type = type; |
| |
| s->ifc = &sdiahciifc; |
| s->idno = 'E' + niactlr; |
| s->ctlr = c; |
| c->sdev = s; |
| |
| if (Intel(c) && p->dev_id != 0x2681) |
| iasetupahci(c); |
| nunit = ahciconf(c); |
| // ahcihbareset((void *)c->mmio); |
| if (Intel(c) && iaahcimode(p) == -1) |
| break; |
| if (nunit < 1) { |
| vunmap_vmem((uintptr_t)c->mmio, p->bar[Abar].mmio_sz); |
| continue; |
| } |
| n = newctlr(c, s, nunit); |
| if (n < 0) |
| continue; |
| niadrive += n; |
| niactlr++; |
| if (head) |
| tail->next = s; |
| else |
| head = s; |
| tail = s; |
| } |
| return head; |
| } |
| |
| static char *smarttab[] = {"unset", "error", "threshold exceeded", "normal"}; |
| |
| static char *pflag(char *s, char *e, unsigned char f) |
| { |
| unsigned char i; |
| |
| for (i = 0; i < 8; i++) |
| if (f & (1 << i)) |
| s = seprintf(s, e, "%s ", flagname[i]); |
| return seprintf(s, e, "\n"); |
| } |
| |
| static int iarctl(struct sdunit *u, char *p, int l) |
| { |
| char buf[32]; |
| char *e, *op; |
| void *port; |
| struct ctlr *c; |
| struct drive *d; |
| uint32_t serror, task, cmd, ci, is, sig, sstatus; |
| |
| c = u->dev->ctlr; |
| if (c == NULL) { |
| printk("iarctl: nil u->dev->ctlr\n"); |
| return 0; |
| } |
| d = c->drive[u->subno]; |
| port = d->port; |
| |
| e = p + l; |
| op = p; |
| if (d->state == Dready) { |
| p = seprintf(p, e, "model\t%s\n", d->model); |
| p = seprintf(p, e, "serial\t%s\n", d->serial); |
| p = seprintf(p, e, "firm\t%s\n", d->firmware); |
| if (d->smartrs == 0xff) |
| p = seprintf(p, e, "smart\tenable error\n"); |
| else if (d->smartrs == 0) |
| p = seprintf(p, e, "smart\tdisabled\n"); |
| else |
| p = seprintf(p, e, "smart\t%s\n", |
| smarttab[d->portm.smart]); |
| p = seprintf(p, e, "flag\t"); |
| p = pflag(p, e, d->portm.feat); |
| } else |
| p = seprintf(p, e, "no disk present [%s]\n", |
| diskstates[d->state]); |
| serror = ahci_port_read32(port, PORT_SERR); |
| task = ahci_port_read32(port, PORT_TFD); |
| cmd = ahci_port_read32(port, PORT_CMD); |
| ci = ahci_port_read32(port, PORT_CI); |
| is = ahci_port_read32(port, PORT_IS); |
| sig = ahci_port_read32(port, PORT_SIG); |
| sstatus = ahci_port_read32(port, PORT_SSTS); |
| serrstr(serror, buf, buf + sizeof(buf) - 1); |
| p = seprintf(p, e, |
| "reg\ttask %#lx cmd %#lx serr %#lx %s ci %#lx is %#lx; " |
| "sig %#lx sstatus %06#lx\n", |
| task, cmd, serror, buf, ci, is, sig, sstatus); |
| if (d->unit == NULL) |
| panic("iarctl: nil d->unit"); |
| p = seprintf(p, e, "geometry %llu %lu\n", d->sectors, d->unit->secsize); |
| return p - op; |
| } |
| |
| static void runflushcache(struct drive *d) |
| { |
| int32_t t0; |
| |
| t0 = ms(); |
| if (flushcache(d) != 0) |
| error(EIO, "Flush cache failed"); |
| printd("ahci: flush in %ld ms\n", ms() - t0); |
| } |
| |
| static void forcemode(struct drive *d, char *mode) |
| { |
| int i; |
| |
| for (i = 0; i < ARRAY_SIZE(modename); i++) |
| if (strcmp(mode, modename[i]) == 0) |
| break; |
| if (i == ARRAY_SIZE(modename)) |
| i = 0; |
| spin_lock_irqsave(&d->Lock); |
| d->mode = i; |
| spin_unlock_irqsave(&d->Lock); |
| } |
| |
| static void runsmartable(struct drive *d, int i) |
| { |
| ERRSTACK(1); |
| |
| if (waserror()) { |
| qunlock(&d->portm.ql); |
| d->smartrs = 0; |
| nexterror(); |
| } |
| if (lockready(d) == -1) |
| error(EIO, "runsmartable: lockready returned -1"); |
| d->smartrs = smart(&d->portc, i); |
| d->portm.smart = 0; |
| qunlock(&d->portm.ql); |
| poperror(); |
| } |
| |
| static void forcestate(struct drive *d, char *state) |
| { |
| int i; |
| |
| for (i = 0; i < ARRAY_SIZE(diskstates); i++) |
| if (strcmp(state, diskstates[i]) == 0) |
| break; |
| if (i == ARRAY_SIZE(diskstates)) |
| error(EINVAL, "Can't set state to invalid value '%s'", state); |
| setstate(d, i); |
| } |
| |
| /* |
| * force this driver to notice a change of medium if the hardware doesn't |
| * report it. |
| */ |
| static void changemedia(struct sdunit *u) |
| { |
| struct ctlr *c; |
| struct drive *d; |
| |
| c = u->dev->ctlr; |
| d = c->drive[u->subno]; |
| spin_lock_irqsave(&d->Lock); |
| d->mediachange = 1; |
| u->sectors = 0; |
| spin_unlock_irqsave(&d->Lock); |
| } |
| |
| static int iawctl(struct sdunit *u, struct cmdbuf *cmd) |
| { |
| ERRSTACK(1); |
| char **f; |
| struct ctlr *c; |
| struct drive *d; |
| unsigned int i; |
| |
| c = u->dev->ctlr; |
| d = c->drive[u->subno]; |
| f = cmd->f; |
| |
| if (strcmp(f[0], "change") == 0) |
| changemedia(u); |
| else if (strcmp(f[0], "flushcache") == 0) |
| runflushcache(d); |
| else if (strcmp(f[0], "identify") == 0) { |
| i = strtoul(f[1] ? f[1] : "0", 0, 0); |
| if (i > 0xff) |
| i = 0; |
| printd("ahci: %04d %#x\n", i, d->info[i]); |
| } else if (strcmp(f[0], "mode") == 0) |
| forcemode(d, f[1] ? f[1] : "satai"); |
| else if (strcmp(f[0], "nop") == 0) { |
| if ((d->portm.feat & Dnop) == 0) { |
| sdierror(cmd, "no drive support"); |
| return -1; |
| } |
| if (waserror()) { |
| qunlock(&d->portm.ql); |
| nexterror(); |
| } |
| if (lockready(d) == -1) |
| error(EIO, "%s: lockready returned -1", __func__); |
| nop(&d->portc); |
| qunlock(&d->portm.ql); |
| poperror(); |
| } else if (strcmp(f[0], "reset") == 0) |
| forcestate(d, "reset"); |
| else if (strcmp(f[0], "smart") == 0) { |
| if (d->smartrs == 0) |
| sdierror(cmd, "smart not enabled"); |
| if (waserror()) { |
| qunlock(&d->portm.ql); |
| d->smartrs = 0; |
| nexterror(); |
| } |
| if (lockready(d) == -1) |
| error(EIO, "%s: lockready returned -1", __func__); |
| d->portm.smart = 2 + smartrs(&d->portc); |
| qunlock(&d->portm.ql); |
| poperror(); |
| } else if (strcmp(f[0], "smartdisable") == 0) |
| runsmartable(d, 1); |
| else if (strcmp(f[0], "smartenable") == 0) |
| runsmartable(d, 0); |
| else if (strcmp(f[0], "state") == 0) |
| forcestate(d, f[1] ? f[1] : "null"); |
| else { |
| sdierror(cmd, "%s: unknown control '%s'", __func__, f[0]); |
| return -1; |
| } |
| return 0; |
| } |
| |
| static char *portr(char *p, char *e, unsigned int x) |
| { |
| int i, a; |
| |
| p[0] = 0; |
| a = -1; |
| for (i = 0; i < 32; i++) { |
| if ((x & (1 << i)) == 0) { |
| if (a != -1 && i - 1 != a) |
| p = seprintf(p, e, "-%d", i - 1); |
| a = -1; |
| continue; |
| } |
| if (a == -1) { |
| if (i > 0) |
| p = seprintf(p, e, ", "); |
| p = seprintf(p, e, "%d", a = i); |
| } |
| } |
| if (a != -1 && i - 1 != a) |
| p = seprintf(p, e, "-%d", i - 1); |
| return p; |
| } |
| |
| /* must emit exactly one line per controller (sd(3)) */ |
| static char *iartopctl(struct sdev *sdev, char *p, char *e) |
| { |
| uint32_t cap, ghc, isr, pi, ver; |
| char pr[25]; |
| void *hba; |
| struct ctlr *ctlr; |
| |
| #define has(x, str) \ |
| do { \ |
| if (cap & (x)) \ |
| p = seprintf(p, e, "%s ", (str)); \ |
| } while (0) |
| |
| ctlr = sdev->ctlr; |
| hba = ctlr->hba; |
| p = seprintf(p, e, "sd%c ahci port %#p: ", sdev->idno, ctlr->physio); |
| cap = ahci_hba_read32(hba, HBA_CAP); |
| has(Hs64a, "64a"); |
| has(Hsalp, "alp"); |
| has(Hsam, "am"); |
| has(Hsclo, "clo"); |
| has(Hcccs, "coal"); |
| has(Hems, "ems"); |
| has(Hsal, "led"); |
| has(Hsmps, "mps"); |
| has(Hsncq, "ncq"); |
| has(Hssntf, "ntf"); |
| has(Hspm, "pm"); |
| has(Hpsc, "pslum"); |
| has(Hssc, "slum"); |
| has(Hsss, "ss"); |
| has(Hsxs, "sxs"); |
| pi = ahci_hba_read32(hba, HBA_PI); |
| portr(pr, pr + sizeof(pr), pi); |
| |
| ghc = ahci_hba_read32(hba, HBA_GHC); |
| isr = ahci_hba_read32(hba, HBA_ISR); |
| ver = ahci_hba_read32(hba, HBA_VS); |
| return seprintf( |
| p, e, |
| "iss %ld ncs %ld np %ld; ghc %#lx isr %#lx pi %#lx %s ver %#lx\n", |
| (cap >> 20) & 0xf, (cap >> 8) & 0x1f, 1 + (cap & 0x1f), ghc, isr, |
| pi, ver); |
| #undef has |
| } |
| |
| static int iawtopctl(struct sdev *sdev, struct cmdbuf *cmd) |
| { |
| int *v; |
| char **f; |
| |
| f = cmd->f; |
| v = 0; |
| |
| if (f[0] == NULL) |
| return 0; |
| if (strcmp(f[0], "debug") == 0) |
| v = &debug; |
| else if (strcmp(f[0], "iprintd") == 0) |
| v = &prid; |
| else if (strcmp(f[0], "aprint") == 0) |
| v = &datapi; |
| else |
| sdierror(cmd, "%s: bad control '%s'", __func__, f[0]); |
| |
| switch (cmd->nf) { |
| default: |
| sdierror(cmd, "%s: %d args, only 1 or 2 allowed", __func__, |
| cmd->nf); |
| case 1: |
| *v ^= 1; |
| break; |
| case 2: |
| if (f[1]) |
| *v = strcmp(f[1], "on") == 0; |
| else |
| *v ^= 1; |
| break; |
| } |
| return 0; |
| } |
| |
| struct sdifc sdiahciifc = { |
| "iahci", |
| |
| iapnp, NULL, /* legacy */ |
| iaenable, iadisable, |
| |
| iaverify, iaonline, iario, iarctl, iawctl, |
| |
| scsibio, NULL, /* probe */ |
| NULL, /* clear */ |
| iartopctl, iawtopctl, |
| }; |