blob: 4213c4a4106ef0f0397361ce45cbbb8ea9f15d08 [file] [log] [blame]
/*
* 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,
};