#include "ahci.h" // CDB_CMD_READ_10
#include "blockcmd.h" // CDB_CMD_READ_10
-#define AHCI_MAX_RETRIES 5
+#define AHCI_REQUEST_TIMEOUT 32000 // 32 seconds max for IDE ops
+#define AHCI_RESET_TIMEOUT 500 // 500 miliseconds
+#define AHCI_LINK_TIMEOUT 10 // 10 miliseconds
/****************************************************************
* these bits must run in both 16bit and 32bit modes
static int ahci_command(struct ahci_port_s *port, int iswrite, int isatapi,
void *buffer, u32 bsize)
{
- u32 val, status, success, flags, intbits;
+ u32 val, status, success, flags, intbits, error;
struct ahci_ctrl_s *ctrl = GET_GLOBAL(port->ctrl);
struct ahci_cmd_s *cmd = GET_GLOBAL(port->cmd);
struct ahci_fis_s *fis = GET_GLOBAL(port->fis);
struct ahci_list_s *list = GET_GLOBAL(port->list);
u32 pnr = GET_GLOBAL(port->pnr);
+ u64 end;
SET_FLATPTR(cmd->fis.reg, 0x27);
SET_FLATPTR(cmd->fis.pmp_type, (1 << 7)); /* cmd fis */
ahci_port_writel(ctrl, pnr, PORT_SCR_ACT, 1);
ahci_port_writel(ctrl, pnr, PORT_CMD_ISSUE, 1);
+ end = calc_future_tsc(AHCI_REQUEST_TIMEOUT);
do {
for (;;) {
intbits = ahci_port_readl(ctrl, pnr, PORT_IRQ_STAT);
ahci_port_writel(ctrl, pnr, PORT_IRQ_STAT, intbits);
if (intbits & 0x02) {
status = GET_FLATPTR(fis->psfis[2]);
+ error = GET_FLATPTR(fis->psfis[3]);
break;
}
if (intbits & 0x01) {
status = GET_FLATPTR(fis->rfis[2]);
+ error = GET_FLATPTR(fis->rfis[3]);
break;
}
}
+ if (check_tsc(end)) {
+ warn_timeout();
+ return -1;
+ }
yield();
}
dprintf(2, "AHCI/%d: ... intbits 0x%x, status 0x%x ...\n",
} while (status & ATA_CB_STAT_BSY);
success = (0x00 == (status & (ATA_CB_STAT_BSY | ATA_CB_STAT_DF |
- ATA_CB_STAT_DRQ | ATA_CB_STAT_ERR)) &&
+ ATA_CB_STAT_ERR)) &&
ATA_CB_STAT_RDY == (status & (ATA_CB_STAT_RDY)));
- dprintf(2, "AHCI/%d: ... finished, status 0x%x, %s\n", pnr,
- status, success ? "OK" : "ERROR");
+ if (success) {
+ dprintf(2, "AHCI/%d: ... finished, status 0x%x, OK\n", pnr,
+ status);
+ } else {
+ dprintf(2, "AHCI/%d: ... finished, status 0x%x, ERROR 0x%x\n", pnr,
+ status, error);
+
+ // non-queued error recovery (AHCI 1.3 section 6.2.2.1)
+ // Clears PxCMD.ST to 0 to reset the PxCI register
+ val = ahci_port_readl(ctrl, pnr, PORT_CMD);
+ ahci_port_writel(ctrl, pnr, PORT_CMD, val & ~PORT_CMD_START);
+
+ // waits for PxCMD.CR to clear to 0
+ while (1) {
+ val = ahci_port_readl(ctrl, pnr, PORT_CMD);
+ if ((val & PORT_CMD_LIST_ON) == 0)
+ break;
+ yield();
+ }
+
+ // Clears any error bits in PxSERR to enable capturing new errors
+ val = ahci_port_readl(ctrl, pnr, PORT_SCR_ERR);
+ ahci_port_writel(ctrl, pnr, PORT_SCR_ERR, val);
+
+ // Clears status bits in PxIS as appropriate
+ val = ahci_port_readl(ctrl, pnr, PORT_IRQ_STAT);
+ ahci_port_writel(ctrl, pnr, PORT_IRQ_STAT, val);
+
+ // If PxTFD.STS.BSY or PxTFD.STS.DRQ is set to 1, issue
+ // a COMRESET to the device to put it in an idle state
+ val = ahci_port_readl(ctrl, pnr, PORT_TFDATA);
+ if (val & (ATA_CB_STAT_BSY | ATA_CB_STAT_DRQ)) {
+ dprintf(2, "AHCI/%d: issue comreset\n", pnr);
+ val = ahci_port_readl(ctrl, pnr, PORT_SCR_CTL);
+ // set Device Detection Initialization (DET) to 1 for 1 ms for comreset
+ ahci_port_writel(ctrl, pnr, PORT_SCR_CTL, val | 1);
+ mdelay (1);
+ ahci_port_writel(ctrl, pnr, PORT_SCR_CTL, val);
+ }
+
+ // Sets PxCMD.ST to 1 to enable issuing new commands
+ val = ahci_port_readl(ctrl, pnr, PORT_CMD);
+ ahci_port_writel(ctrl, pnr, PORT_CMD, val | PORT_CMD_START);
+ }
return success ? 0 : -1;
}
return DISK_RET_SUCCESS;
}
-// read/write count blocks from a harddrive.
+// read/write count blocks from a harddrive, op->buf_fl must be word aligned
static int
-ahci_disk_readwrite(struct disk_op_s *op, int iswrite)
+ahci_disk_readwrite_aligned(struct disk_op_s *op, int iswrite)
{
struct ahci_port_s *port = container_of(
op->drive_g, struct ahci_port_s, drive);
return DISK_RET_SUCCESS;
}
+// read/write count blocks from a harddrive.
+static int
+ahci_disk_readwrite(struct disk_op_s *op, int iswrite)
+{
+ // if caller's buffer is word aligned, use it directly
+ if (((u32) op->buf_fl & 1) == 0)
+ return ahci_disk_readwrite_aligned(op, iswrite);
+
+ // Use a word aligned buffer for AHCI I/O
+ int rc;
+ struct disk_op_s localop = *op;
+ u8 *alignedbuf_fl = GET_GLOBAL(bounce_buf_fl);
+ u8 *position = op->buf_fl;
+
+ localop.buf_fl = alignedbuf_fl;
+ localop.count = 1;
+
+ if (iswrite) {
+ u16 block;
+ for (block = 0; block < op->count; block++) {
+ memcpy_fl (alignedbuf_fl, position, DISK_SECTOR_SIZE);
+ rc = ahci_disk_readwrite_aligned (&localop, 1);
+ if (rc)
+ return rc;
+ position += DISK_SECTOR_SIZE;
+ localop.lba++;
+ }
+ } else { // read
+ u16 block;
+ for (block = 0; block < op->count; block++) {
+ rc = ahci_disk_readwrite_aligned (&localop, 0);
+ if (rc)
+ return rc;
+ memcpy_fl (position, alignedbuf_fl, DISK_SECTOR_SIZE);
+ position += DISK_SECTOR_SIZE;
+ localop.lba++;
+ }
+ }
+ return DISK_RET_SUCCESS;
+}
+
// command demuxer
int process_ahci_op(struct disk_op_s *op)
{
static void
ahci_port_reset(struct ahci_ctrl_s *ctrl, u32 pnr)
{
- u32 val, count = 0;
+ u32 val;
+ u64 end;
/* disable FIS + CMD */
- val = ahci_port_readl(ctrl, pnr, PORT_CMD);
- while (val & (PORT_CMD_FIS_RX | PORT_CMD_START |
- PORT_CMD_FIS_ON | PORT_CMD_LIST_ON) &&
- count < AHCI_MAX_RETRIES) {
+ end = calc_future_tsc(AHCI_RESET_TIMEOUT);
+ for (;;) {
+ val = ahci_port_readl(ctrl, pnr, PORT_CMD);
+ if (!(val & (PORT_CMD_FIS_RX | PORT_CMD_START |
+ PORT_CMD_FIS_ON | PORT_CMD_LIST_ON)))
+ break;
val &= ~(PORT_CMD_FIS_RX | PORT_CMD_START);
ahci_port_writel(ctrl, pnr, PORT_CMD, val);
- ndelay(500);
- val = ahci_port_readl(ctrl, pnr, PORT_CMD);
- count++;
+ if (check_tsc(end)) {
+ warn_timeout();
+ break;
+ }
+ yield();
}
- /* clear status */
- val = ahci_port_readl(ctrl, pnr, PORT_SCR_ERR);
- if (val)
- ahci_port_writel(ctrl, pnr, PORT_SCR_ERR, val);
-
/* disable + clear IRQs */
- ahci_port_writel(ctrl, pnr, PORT_IRQ_MASK, val);
+ ahci_port_writel(ctrl, pnr, PORT_IRQ_MASK, 0);
val = ahci_port_readl(ctrl, pnr, PORT_IRQ_STAT);
if (val)
ahci_port_writel(ctrl, pnr, PORT_IRQ_STAT, val);
}
-static int
-ahci_port_probe(struct ahci_ctrl_s *ctrl, u32 pnr)
-{
- u32 val, count = 0;
-
- val = ahci_port_readl(ctrl, pnr, PORT_TFDATA);
- while (val & ((1 << 7) /* BSY */ |
- (1 << 3) /* DRQ */)) {
- ndelay(500);
- val = ahci_port_readl(ctrl, pnr, PORT_TFDATA);
- count++;
- if (count >= AHCI_MAX_RETRIES)
- return -1;
- }
-
- val = ahci_port_readl(ctrl, pnr, PORT_SCR_STAT);
- if ((val & 0x07) != 0x03)
- return -1;
- return 0;
-}
-
-#define MAXMODEL 40
-
static struct ahci_port_s*
-ahci_port_init(struct ahci_ctrl_s *ctrl, u32 pnr)
+ahci_port_alloc(struct ahci_ctrl_s *ctrl, u32 pnr)
{
- struct ahci_port_s *port = malloc_fseg(sizeof(*port));
- char model[MAXMODEL+1];
- u16 buffer[256];
- u32 val;
- int rc;
+ struct ahci_port_s *port = malloc_tmp(sizeof(*port));
if (!port) {
warn_noalloc();
}
port->pnr = pnr;
port->ctrl = ctrl;
- port->list = memalign_low(1024, 1024);
- port->fis = memalign_low(256, 256);
- port->cmd = memalign_low(256, 256);
+ port->list = memalign_tmp(1024, 1024);
+ port->fis = memalign_tmp(256, 256);
+ port->cmd = memalign_tmp(256, 256);
if (port->list == NULL || port->fis == NULL || port->cmd == NULL) {
warn_noalloc();
return NULL;
ahci_port_writel(ctrl, pnr, PORT_LST_ADDR, (u32)port->list);
ahci_port_writel(ctrl, pnr, PORT_FIS_ADDR, (u32)port->fis);
- val = ahci_port_readl(ctrl, pnr, PORT_CMD);
- val |= PORT_CMD_FIS_RX;
- ahci_port_writel(ctrl, pnr, PORT_CMD, val);
- val |= PORT_CMD_START;
- ahci_port_writel(ctrl, pnr, PORT_CMD, val);
+ return port;
+}
+
+static struct ahci_port_s* ahci_port_realloc(struct ahci_port_s *port)
+{
+ struct ahci_port_s *tmp;
+ u32 cmd;
+
+ tmp = malloc_fseg(sizeof(*port));
+ *tmp = *port;
+ free(port);
+ port = tmp;
+
+ ahci_port_reset(port->ctrl, port->pnr);
+
+ free(port->list);
+ free(port->fis);
+ free(port->cmd);
+ port->list = memalign_low(1024, 1024);
+ port->fis = memalign_low(256, 256);
+ port->cmd = memalign_low(256, 256);
+
+ ahci_port_writel(port->ctrl, port->pnr, PORT_LST_ADDR, (u32)port->list);
+ ahci_port_writel(port->ctrl, port->pnr, PORT_FIS_ADDR, (u32)port->fis);
+
+ cmd = ahci_port_readl(port->ctrl, port->pnr, PORT_CMD);
+ cmd |= (PORT_CMD_FIS_RX|PORT_CMD_START);
+ ahci_port_writel(port->ctrl, port->pnr, PORT_CMD, cmd);
+
+ return port;
+}
+
+static void ahci_port_release(struct ahci_port_s *port)
+{
+ ahci_port_reset(port->ctrl, port->pnr);
+ free(port->list);
+ free(port->fis);
+ free(port->cmd);
+ free(port);
+}
+
+#define MAXMODEL 40
+
+/* See ahci spec chapter 10.1 "Software Initialization of HBA" */
+static int ahci_port_init(struct ahci_port_s *port)
+{
+ struct ahci_ctrl_s *ctrl = port->ctrl;
+ u32 pnr = port->pnr;
+ char model[MAXMODEL+1];
+ u16 buffer[256];
+ u32 cmd, stat, err, tf;
+ u64 end;
+ int rc;
+
+ /* enable FIS recv */
+ cmd = ahci_port_readl(ctrl, pnr, PORT_CMD);
+ cmd |= PORT_CMD_FIS_RX;
+ ahci_port_writel(ctrl, pnr, PORT_CMD, cmd);
+
+ /* spin up */
+ cmd |= PORT_CMD_SPIN_UP;
+ ahci_port_writel(ctrl, pnr, PORT_CMD, cmd);
+ end = calc_future_tsc(AHCI_LINK_TIMEOUT);
+ for (;;) {
+ stat = ahci_port_readl(ctrl, pnr, PORT_SCR_STAT);
+ if ((stat & 0x07) == 0x03) {
+ dprintf(1, "AHCI/%d: link up\n", port->pnr);
+ break;
+ }
+ if (check_tsc(end)) {
+ dprintf(1, "AHCI/%d: link down\n", port->pnr);
+ return -1;
+ }
+ yield();
+ }
+
+ /* clear error status */
+ err = ahci_port_readl(ctrl, pnr, PORT_SCR_ERR);
+ if (err)
+ ahci_port_writel(ctrl, pnr, PORT_SCR_ERR, err);
+
+ /* wait for device becoming ready */
+ end = calc_future_tsc(AHCI_REQUEST_TIMEOUT);
+ for (;;) {
+ tf = ahci_port_readl(ctrl, pnr, PORT_TFDATA);
+ if (!(tf & (ATA_CB_STAT_BSY |
+ ATA_CB_STAT_DRQ)))
+ break;
+ if (check_tsc(end)) {
+ warn_timeout();
+ dprintf(1, "AHCI/%d: device not ready (tf 0x%x)\n", port->pnr, tf);
+ return -1;
+ }
+ yield();
+ }
+
+ /* start device */
+ cmd |= PORT_CMD_START;
+ ahci_port_writel(ctrl, pnr, PORT_CMD, cmd);
sata_prep_simple(&port->cmd->fis, ATA_CMD_IDENTIFY_PACKET_DEVICE);
rc = ahci_command(port, 0, 0, buffer, sizeof(buffer));
sata_prep_simple(&port->cmd->fis, ATA_CMD_IDENTIFY_DEVICE);
rc = ahci_command(port, 0, 0, buffer, sizeof(buffer));
if (rc < 0)
- goto err;
+ return -1;
}
port->drive.type = DTYPE_AHCI;
adjsize >>= 10;
adjprefix = 'G';
}
- char *desc = znprintf(MAXDESCSIZE
+ port->desc = znprintf(MAXDESCSIZE
, "AHCI/%d: %s ATA-%d Hard-Disk (%u %ciBytes)"
, port->pnr
, ata_extract_model(model, MAXMODEL, buffer)
, ata_extract_version(buffer)
, (u32)adjsize, adjprefix);
- dprintf(1, "%s\n", desc);
-
- // Register with bcv system.
- boot_add_hd(&port->drive, desc, -1);
+ port->prio = bootprio_find_ata_device(ctrl->pci_tmp, pnr, 0);
} else {
// found cdrom (atapi)
port->drive.blksize = CDROM_SECTOR_SIZE;
port->drive.sectors = (u64)-1;
u8 iscd = ((buffer[0] >> 8) & 0x1f) == 0x05;
- char *desc = znprintf(MAXDESCSIZE
- , "DVD/CD [AHCI/%d: %s ATAPI-%d %s]"
+ if (!iscd) {
+ dprintf(1, "AHCI/%d: atapi device is'nt a cdrom\n", port->pnr);
+ return -1;
+ }
+ port->desc = znprintf(MAXDESCSIZE
+ , "DVD/CD [AHCI/%d: %s ATAPI-%d DVD/CD]"
, port->pnr
, ata_extract_model(model, MAXMODEL, buffer)
- , ata_extract_version(buffer)
- , (iscd ? "DVD/CD" : "Device"));
- dprintf(1, "%s\n", desc);
-
- // fill cdidmap
- if (iscd)
- boot_add_cd(&port->drive, desc, -1);
+ , ata_extract_version(buffer));
+ port->prio = bootprio_find_ata_device(ctrl->pci_tmp, pnr, 0);
}
-
- return port;
-
-err:
- dprintf(1, "AHCI/%d: init failure, reset\n", port->pnr);
- ahci_port_reset(ctrl, pnr);
- return NULL;
+ return 0;
}
// Detect any drives attached to a given controller.
static void
-ahci_detect(void *data)
+ahci_port_detect(void *data)
{
- struct ahci_ctrl_s *ctrl = data;
- struct ahci_port_s *port;
- u32 pnr, max;
+ struct ahci_port_s *port = data;
int rc;
- max = ctrl->caps & 0x1f;
- for (pnr = 0; pnr <= max; pnr++) {
- if (!(ctrl->ports & (1 << pnr)))
- continue;
- dprintf(2, "AHCI/%d: probing\n", pnr);
- ahci_port_reset(ctrl, pnr);
- rc = ahci_port_probe(ctrl, pnr);
- dprintf(1, "AHCI/%d: link %s\n", pnr, rc == 0 ? "up" : "down");
- if (rc != 0)
- continue;
- port = ahci_port_init(ctrl, pnr);
+ dprintf(2, "AHCI/%d: probing\n", port->pnr);
+ ahci_port_reset(port->ctrl, port->pnr);
+ rc = ahci_port_init(port);
+ if (rc < 0)
+ ahci_port_release(port);
+ else {
+ port = ahci_port_realloc(port);
+ dprintf(1, "AHCI/%d: registering: \"%s\"\n", port->pnr, port->desc);
+ if (!port->atapi) {
+ // Register with bcv system.
+ boot_add_hd(&port->drive, port->desc, port->prio);
+ } else {
+ // fill cdidmap
+ boot_add_cd(&port->drive, port->desc, port->prio);
+ }
}
}
// Initialize an ata controller and detect its drives.
static void
-ahci_init_controller(int bdf)
+ahci_init_controller(struct pci_device *pci)
{
struct ahci_ctrl_s *ctrl = malloc_fseg(sizeof(*ctrl));
- u32 val;
+ struct ahci_port_s *port;
+ u16 bdf = pci->bdf;
+ u32 val, pnr, max;
if (!ctrl) {
warn_noalloc();
return;
}
+
+ if (bounce_buf_init() < 0) {
+ warn_noalloc();
+ free(ctrl);
+ return;
+ }
+
+ ctrl->pci_tmp = pci;
ctrl->pci_bdf = bdf;
ctrl->iobase = pci_config_readl(bdf, PCI_BASE_ADDRESS_5);
ctrl->irq = pci_config_readb(bdf, PCI_INTERRUPT_LINE);
dprintf(2, "AHCI: cap 0x%x, ports_impl 0x%x\n",
ctrl->caps, ctrl->ports);
- run_thread(ahci_detect, ctrl);
+ max = ctrl->caps & 0x1f;
+ for (pnr = 0; pnr <= max; pnr++) {
+ if (!(ctrl->ports & (1 << pnr)))
+ continue;
+ port = ahci_port_alloc(ctrl, pnr);
+ if (port == NULL)
+ continue;
+ run_thread(ahci_port_detect, port);
+ }
}
// Locate and init ahci controllers.
continue;
if (pci->prog_if != 1 /* AHCI rev 1 */)
continue;
- ahci_init_controller(pci->bdf);
+ ahci_init_controller(pci);
}
}