add ahci support
authorGerd Hoffmann <kraxel@redhat.com>
Mon, 29 Nov 2010 08:42:13 +0000 (09:42 +0100)
committerKevin O'Connor <kevin@koconnor.net>
Sun, 5 Dec 2010 17:23:23 +0000 (12:23 -0500)
This patch adds AHCI support to seabios.  Tested with virtual hardware
only (upcoming ahci support in qemu).  Coded by looking at the
recommandations in the intel ahci specs, so I don't expect much trouble
on real hardware.  Tested booting fedora install from hard disk and a
opensuse live iso from cdrom.

[ v2: disable by default           ]
[ v2: add check for malloc failure ]
[ v2: wind up disk write support   ]

Signed-off-by: Gerd Hoffmann <kraxel@redhat.com>
Makefile
src/ahci.c [new file with mode: 0644]
src/ahci.h [new file with mode: 0644]
src/block.c
src/blockcmd.c
src/config.h
src/disk.h
src/post.c

index 1663a5db05b2687823438b13a90e36e40eecf8aa..acc238586d259c8e9fd82ed3fe07c5f5366da328 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -15,7 +15,7 @@ SRCBOTH=misc.c pmm.c stacks.c output.c util.c block.c floppy.c ata.c mouse.c \
         kbd.c pci.c serial.c clock.c pic.c cdrom.c ps2port.c smp.c resume.c \
         pnpbios.c pirtable.c vgahooks.c ramdisk.c pcibios.c blockcmd.c \
         usb.c usb-uhci.c usb-ohci.c usb-ehci.c usb-hid.c usb-msc.c \
-        virtio-ring.c virtio-pci.c virtio-blk.c apm.c
+        virtio-ring.c virtio-pci.c virtio-blk.c apm.c ahci.c
 SRC16=$(SRCBOTH) system.c disk.c font.c
 SRC32FLAT=$(SRCBOTH) post.c shadow.c memmap.c coreboot.c boot.c \
       acpi.c smm.c mptable.c smbios.c pciinit.c optionroms.c mtrr.c \
diff --git a/src/ahci.c b/src/ahci.c
new file mode 100644 (file)
index 0000000..ee404d4
--- /dev/null
@@ -0,0 +1,480 @@
+// Low level AHCI disk access
+//
+// Copyright (C) 2010 Gerd Hoffmann <kraxel@redhat.com>
+//
+// This file may be distributed under the terms of the GNU LGPLv3 license.
+
+#include "types.h" // u8
+#include "ioport.h" // inb
+#include "util.h" // dprintf
+#include "biosvar.h" // GET_EBDA
+#include "pci.h" // foreachpci
+#include "pci_ids.h" // PCI_CLASS_STORAGE_OTHER
+#include "pci_regs.h" // PCI_INTERRUPT_LINE
+#include "boot.h" // add_bcv_hd
+#include "disk.h" // struct ata_s
+#include "ata.h" // ATA_CB_STAT
+#include "ahci.h" // CDB_CMD_READ_10
+#include "blockcmd.h" // CDB_CMD_READ_10
+
+#define AHCI_MAX_RETRIES 5
+
+/****************************************************************
+ * these bits must run in both 16bit and 32bit modes
+ ****************************************************************/
+
+// prepare sata command fis
+static void sata_prep_simple(struct sata_cmd_fis *fis, u8 command)
+{
+    memset_fl(fis, 0, sizeof(*fis));
+    SET_FLATPTR(fis->command, command);
+}
+
+static void sata_prep_readwrite(struct sata_cmd_fis *fis,
+                                struct disk_op_s *op, int iswrite)
+{
+    u64 lba = op->lba;
+    u8 command;
+
+    memset_fl(fis, 0, sizeof(*fis));
+
+    if (op->count >= (1<<8) || lba + op->count >= (1<<28)) {
+        SET_FLATPTR(fis->sector_count2, op->count >> 8);
+        SET_FLATPTR(fis->lba_low2,      lba >> 24);
+        SET_FLATPTR(fis->lba_mid2,      lba >> 32);
+        SET_FLATPTR(fis->lba_high2,     lba >> 40);
+        lba &= 0xffffff;
+        command = (iswrite ? ATA_CMD_WRITE_DMA_EXT
+                   : ATA_CMD_READ_DMA_EXT);
+    } else {
+        command = (iswrite ? ATA_CMD_WRITE_DMA
+                   : ATA_CMD_READ_DMA);
+    }
+    SET_FLATPTR(fis->command,      command);
+    SET_FLATPTR(fis->sector_count, op->count);
+    SET_FLATPTR(fis->lba_low,      lba);
+    SET_FLATPTR(fis->lba_mid,      lba >> 8);
+    SET_FLATPTR(fis->lba_high,     lba >> 16);
+    SET_FLATPTR(fis->device,       ((lba >> 24) & 0xf) | ATA_CB_DH_LBA);
+}
+
+static void sata_prep_atapi(struct sata_cmd_fis *fis, u16 blocksize)
+{
+    memset_fl(fis, 0, sizeof(*fis));
+    SET_FLATPTR(fis->command,  ATA_CMD_PACKET);
+    SET_FLATPTR(fis->lba_mid,  blocksize);
+    SET_FLATPTR(fis->lba_high, blocksize >> 8);
+}
+
+// ahci register access helpers
+static u32 ahci_ctrl_readl(struct ahci_ctrl_s *ctrl, u32 reg)
+{
+    u32 addr = GET_GLOBALFLAT(ctrl->iobase) + reg;
+    return pci_readl(addr);
+}
+
+static void ahci_ctrl_writel(struct ahci_ctrl_s *ctrl, u32 reg, u32 val)
+{
+    u32 addr = GET_GLOBALFLAT(ctrl->iobase) + reg;
+    pci_writel(addr, val);
+}
+
+static u32 ahci_port_to_ctrl(u32 pnr, u32 port_reg)
+{
+    u32 ctrl_reg = 0x100;
+    ctrl_reg += pnr * 0x80;
+    ctrl_reg += port_reg;
+    return ctrl_reg;
+}
+
+static u32 ahci_port_readl(struct ahci_ctrl_s *ctrl, u32 pnr, u32 reg)
+{
+    u32 ctrl_reg = ahci_port_to_ctrl(pnr, reg);
+    return ahci_ctrl_readl(ctrl, ctrl_reg);
+}
+
+static void ahci_port_writel(struct ahci_ctrl_s *ctrl, u32 pnr, u32 reg, u32 val)
+{
+    u32 ctrl_reg = ahci_port_to_ctrl(pnr, reg);
+    ahci_ctrl_writel(ctrl, ctrl_reg, val);
+}
+
+// submit ahci command + wait for result
+static int ahci_command(struct ahci_port_s *port, int iswrite, int isatapi,
+                        void *buffer, u32 bsize)
+{
+    u32 val, status, success, flags;
+    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);
+
+    SET_FLATPTR(cmd->fis.reg,       0x27);
+    SET_FLATPTR(cmd->fis.pmp_type,  (1 << 7)); /* cmd fis */
+    SET_FLATPTR(cmd->prdt[0].base,  ((u32)buffer));
+    SET_FLATPTR(cmd->prdt[0].baseu, 0);
+    SET_FLATPTR(cmd->prdt[0].flags, bsize-1);
+
+    val = ahci_port_readl(ctrl, pnr, PORT_CMD);
+    ahci_port_writel(ctrl, pnr, PORT_CMD, val | PORT_CMD_START);
+
+    if (ahci_port_readl(ctrl, pnr, PORT_CMD_ISSUE))
+        return -1;
+
+    flags = ((1 << 16) | /* one prd entry */
+             (1 << 10) | /* clear busy on ok */
+             (iswrite ? (1 << 6) : 0) |
+             (isatapi ? (1 << 5) : 0) |
+             (4 << 0)); /* fis length (dwords) */
+    SET_FLATPTR(list[0].flags, flags);
+    SET_FLATPTR(list[0].bytes,  bsize);
+    SET_FLATPTR(list[0].base,   ((u32)(cmd)));
+    SET_FLATPTR(list[0].baseu,  0);
+
+    dprintf(2, "AHCI/%d: send cmd ...\n", pnr);
+    SET_FLATPTR(fis->rfis[2], 0);
+    ahci_port_writel(ctrl, pnr, PORT_SCR_ACT, 1);
+    ahci_port_writel(ctrl, pnr, PORT_CMD_ISSUE, 1);
+    while (ahci_port_readl(ctrl, pnr, PORT_CMD_ISSUE)) {
+        yield();
+    }
+    while ((status = GET_FLATPTR(fis->rfis[2])) == 0) {
+        yield();
+    }
+
+    success = (0x00 == (status & (ATA_CB_STAT_BSY | ATA_CB_STAT_DF |
+                                  ATA_CB_STAT_DRQ | 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");
+    return success ? 0 : -1;
+}
+
+#define CDROM_CDB_SIZE 12
+
+int ahci_cmd_data(struct disk_op_s *op, void *cdbcmd, u16 blocksize)
+{
+    struct ahci_port_s *port = container_of(
+        op->drive_g, struct ahci_port_s, drive);
+    struct ahci_cmd_s *cmd = GET_GLOBAL(port->cmd);
+    u8 *atapi = cdbcmd;
+    int i, rc;
+
+    sata_prep_atapi(&cmd->fis, blocksize);
+    for (i = 0; i < CDROM_CDB_SIZE; i++) {
+        SET_FLATPTR(cmd->atapi[i], atapi[i]);
+    }
+    rc = ahci_command(port, 0, 1, op->buf_fl,
+                      op->count * blocksize);
+    if (rc < 0)
+        return DISK_RET_EBADTRACK;
+    return DISK_RET_SUCCESS;
+}
+
+// read/write count blocks from a harddrive.
+static int
+ahci_disk_readwrite(struct disk_op_s *op, int iswrite)
+{
+    struct ahci_port_s *port = container_of(
+        op->drive_g, struct ahci_port_s, drive);
+    struct ahci_cmd_s *cmd = GET_GLOBAL(port->cmd);
+    int rc;
+
+    sata_prep_readwrite(&cmd->fis, op, iswrite);
+    rc = ahci_command(port, iswrite, 0, op->buf_fl,
+                      op->count * DISK_SECTOR_SIZE);
+    dprintf(2, "ahci disk %s, lba %6x, count %3x, buf %p, rc %d\n",
+            iswrite ? "write" : "read", (u32)op->lba, op->count, op->buf_fl, rc);
+    if (rc < 0)
+        return DISK_RET_EBADTRACK;
+    return DISK_RET_SUCCESS;
+}
+
+// command demuxer
+int process_ahci_op(struct disk_op_s *op)
+{
+    struct ahci_port_s *port;
+    u32 atapi;
+
+    if (!CONFIG_AHCI)
+        return 0;
+
+    port = container_of(op->drive_g, struct ahci_port_s, drive);
+    atapi = GET_GLOBAL(port->atapi);
+
+    if (atapi) {
+        switch (op->command) {
+        case CMD_READ:
+            return cdb_read(op);
+        case CMD_WRITE:
+        case CMD_FORMAT:
+            return DISK_RET_EWRITEPROTECT;
+        case CMD_RESET:
+            /* FIXME: what should we do here? */
+        case CMD_VERIFY:
+        case CMD_SEEK:
+            return DISK_RET_SUCCESS;
+        default:
+            dprintf(1, "AHCI: unknown cdrom command %d\n", op->command);
+            op->count = 0;
+            return DISK_RET_EPARAM;
+        }
+    } else {
+        switch (op->command) {
+        case CMD_READ:
+            return ahci_disk_readwrite(op, 0);
+        case CMD_WRITE:
+            return ahci_disk_readwrite(op, 1);
+        case CMD_RESET:
+            /* FIXME: what should we do here? */
+        case CMD_FORMAT:
+        case CMD_VERIFY:
+        case CMD_SEEK:
+            return DISK_RET_SUCCESS;
+        default:
+            dprintf(1, "AHCI: unknown disk command %d\n", op->command);
+            op->count = 0;
+            return DISK_RET_EPARAM;
+        }
+    }
+}
+
+/****************************************************************
+ * everything below is pure 32bit code
+ ****************************************************************/
+
+static void
+ahci_port_reset(struct ahci_ctrl_s *ctrl, u32 pnr)
+{
+    u32 val, count = 0;
+
+    /* 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) {
+        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++;
+    }
+
+    /* 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);
+    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)
+{
+    struct ahci_port_s *port = malloc_fseg(sizeof(*port));
+    char model[MAXMODEL+1];
+    u16 buffer[256];
+    u32 val;
+    int rc;
+
+    if (!port) {
+        warn_noalloc();
+        return NULL;
+    }
+    port->pnr = pnr;
+    port->ctrl = ctrl;
+    port->list = memalign_low(1024, 1024);
+    port->fis = memalign_low(256, 256);
+    port->cmd = memalign_low(256, 256);
+    if (port->list == NULL || port->fis == NULL || port->cmd == NULL) {
+        warn_noalloc();
+        return NULL;
+    }
+    memset(port->list, 0, 1024);
+    memset(port->fis, 0, 256);
+    memset(port->cmd, 0, 256);
+
+    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);
+    ahci_port_writel(ctrl, pnr, PORT_CMD, val | PORT_CMD_FIS_RX);
+
+    sata_prep_simple(&port->cmd->fis, ATA_CMD_IDENTIFY_PACKET_DEVICE);
+    rc = ahci_command(port, 0, 0, buffer, sizeof(buffer));
+    if (rc == 0) {
+        port->atapi = 1;
+    } else {
+        port->atapi = 0;
+        sata_prep_simple(&port->cmd->fis, ATA_CMD_IDENTIFY_DEVICE);
+        rc = ahci_command(port, 0, 0, buffer, sizeof(buffer));
+        if (rc < 0)
+            goto err;
+    }
+
+    port->drive.type = DTYPE_AHCI;
+    port->drive.removable = (buffer[0] & 0x80) ? 1 : 0;
+    port->drive.desc = malloc_tmp(MAXDESCSIZE);
+    if (!port->drive.desc) {
+        warn_noalloc();
+        return NULL;
+    }
+
+    if (!port->atapi) {
+        // found disk (ata)
+        port->drive.blksize = DISK_SECTOR_SIZE;
+        port->drive.pchs.cylinders = buffer[1];
+        port->drive.pchs.heads = buffer[3];
+        port->drive.pchs.spt = buffer[6];
+
+        u64 sectors;
+        if (buffer[83] & (1 << 10)) // word 83 - lba48 support
+            sectors = *(u64*)&buffer[100]; // word 100-103
+        else
+            sectors = *(u32*)&buffer[60]; // word 60 and word 61
+        port->drive.sectors = sectors;
+        u64 adjsize = sectors >> 11;
+        char adjprefix = 'M';
+        if (adjsize >= (1 << 16)) {
+            adjsize >>= 10;
+            adjprefix = 'G';
+        }
+        snprintf(port->drive.desc, 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);
+
+        // Setup disk geometry translation.
+        setup_translation(&port->drive);
+        // Register with bcv system.
+        add_bcv_internal(&port->drive);
+    } else {
+        // found cdrom (atapi)
+        port->drive.blksize = CDROM_SECTOR_SIZE;
+        port->drive.sectors = (u64)-1;
+        u8 iscd = ((buffer[0] >> 8) & 0x1f) == 0x05;
+        snprintf(port->drive.desc, MAXDESCSIZE, "AHCI/%d: %s ATAPI-%d %s"
+                 , port->pnr
+                 , ata_extract_model(model, MAXMODEL, buffer)
+                 , ata_extract_version(buffer)
+                 , (iscd ? "DVD/CD" : "Device"));
+
+        // fill cdidmap
+        if (iscd)
+            map_cd_drive(&port->drive);
+    }
+    dprintf(1, "%s\n", port->drive.desc);
+
+    return port;
+
+err:
+    dprintf(1, "AHCI/%d: init failure, reset\n", port->pnr);
+    ahci_port_reset(ctrl, pnr);
+    return NULL;
+}
+
+// Detect any drives attached to a given controller.
+static void
+ahci_detect(void *data)
+{
+    struct ahci_ctrl_s *ctrl = data;
+    struct ahci_port_s *port;
+    u32 pnr, max;
+    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);
+    }
+}
+
+// Initialize an ata controller and detect its drives.
+static void
+ahci_init_controller(int bdf)
+{
+    struct ahci_ctrl_s *ctrl = malloc_fseg(sizeof(*ctrl));
+    u32 val;
+
+    if (!ctrl) {
+        warn_noalloc();
+        return;
+    }
+    ctrl->pci_bdf = bdf;
+    ctrl->iobase = pci_config_readl(bdf, PCI_BASE_ADDRESS_5);
+    ctrl->irq = pci_config_readb(bdf, PCI_INTERRUPT_LINE);
+    dprintf(1, "AHCI controller at %02x.%x, iobase %x, irq %d\n",
+            bdf >> 3, bdf & 7, ctrl->iobase, ctrl->irq);
+
+    val = ahci_ctrl_readl(ctrl, HOST_CTL);
+    ahci_ctrl_writel(ctrl, HOST_CTL, val | HOST_CTL_AHCI_EN);
+
+    ctrl->caps = ahci_ctrl_readl(ctrl, HOST_CAP);
+    ctrl->ports = ahci_ctrl_readl(ctrl, HOST_PORTS_IMPL);
+    dprintf(2, "AHCI: cap 0x%x, ports_impl 0x%x\n",
+            ctrl->caps, ctrl->ports);
+
+    run_thread(ahci_detect, ctrl);
+}
+
+// Locate and init ahci controllers.
+static void
+ahci_init(void)
+{
+    // Scan PCI bus for ATA adapters
+    int bdf, max;
+    foreachpci(bdf, max) {
+        if (pci_config_readw(bdf, PCI_CLASS_DEVICE) != PCI_CLASS_STORAGE_SATA)
+            continue;
+        if (pci_config_readb(bdf, PCI_CLASS_PROG) != 1 /* AHCI rev 1 */)
+            continue;
+        ahci_init_controller(bdf);
+    }
+}
+
+void
+ahci_setup(void)
+{
+    ASSERT32FLAT();
+    if (!CONFIG_AHCI)
+        return;
+
+    dprintf(3, "init ahci\n");
+    ahci_init();
+}
diff --git a/src/ahci.h b/src/ahci.h
new file mode 100644 (file)
index 0000000..0e13e00
--- /dev/null
@@ -0,0 +1,196 @@
+#ifndef __AHCI_H
+#define __AHCI_H
+
+struct sata_cmd_fis {
+    u8 reg;
+    u8 pmp_type;
+    u8 command;
+    u8 feature;
+
+    u8 lba_low;
+    u8 lba_mid;
+    u8 lba_high;
+    u8 device;
+
+    u8 lba_low2;
+    u8 lba_mid2;
+    u8 lba_high2;
+    u8 feature2;
+
+    u8 sector_count;
+    u8 sector_count2;
+    u8 res_1;
+    u8 control;
+
+    u8 res_2[64 - 16];
+};
+
+struct ahci_ctrl_s {
+    int pci_bdf;
+    u8  irq;
+    u32 iobase;
+    u32 caps;
+    u32 ports;
+};
+
+struct ahci_cmd_s {
+    struct sata_cmd_fis fis;
+    u8 atapi[0x20];
+    u8 res[0x20];
+    struct {
+        u32 base;
+        u32 baseu;
+        u32 res;
+        u32 flags;
+    } prdt[];
+};
+
+/* command list */
+struct ahci_list_s {
+    u32 flags;
+    u32 bytes;
+    u32 base;
+    u32 baseu;
+    u32 res[4];
+};
+
+struct ahci_fis_s {
+    u8 dsfis[0x1c];  /* dma setup */
+    u8 res_1[0x04];
+    u8 psfis[0x14];  /* pio setup */
+    u8 res_2[0x0c];
+    u8 rfis[0x14];   /* d2h register */
+    u8 res_3[0x04];
+    u8 sdbfis[0x08]; /* set device bits */
+    u8 ufis[0x40];   /* unknown */
+    u8 res_4[0x60];
+};
+
+struct ahci_port_s {
+    struct drive_s     drive;
+    struct ahci_ctrl_s *ctrl;
+    struct ahci_list_s *list;
+    struct ahci_fis_s  *fis;
+    struct ahci_cmd_s  *cmd;
+    u32                pnr;
+    u32                atapi;
+};
+
+void ahci_setup(void);
+int process_ahci_op(struct disk_op_s *op);
+int ahci_cmd_data(struct disk_op_s *op, void *cdbcmd, u16 blocksize);
+
+#define AHCI_IRQ_ON_SG            (1 << 31)
+#define AHCI_CMD_ATAPI            (1 << 5)
+#define AHCI_CMD_WRITE            (1 << 6)
+#define AHCI_CMD_PREFETCH         (1 << 7)
+#define AHCI_CMD_RESET            (1 << 8)
+#define AHCI_CMD_CLR_BUSY         (1 << 10)
+
+#define RX_FIS_D2H_REG            0x40 /* offset of D2H Register FIS data */
+#define RX_FIS_SDB                0x58 /* offset of SDB FIS data */
+#define RX_FIS_UNK                0x60 /* offset of Unknown FIS data */
+
+/* global controller registers */
+#define HOST_CAP                  0x00 /* host capabilities */
+#define HOST_CTL                  0x04 /* global host control */
+#define HOST_IRQ_STAT             0x08 /* interrupt status */
+#define HOST_PORTS_IMPL           0x0c /* bitmap of implemented ports */
+#define HOST_VERSION              0x10 /* AHCI spec. version compliancy */
+
+/* HOST_CTL bits */
+#define HOST_CTL_RESET            (1 << 0)  /* reset controller; self-clear */
+#define HOST_CTL_IRQ_EN           (1 << 1)  /* global IRQ enable */
+#define HOST_CTL_AHCI_EN          (1 << 31) /* AHCI enabled */
+
+/* HOST_CAP bits */
+#define HOST_CAP_SSC              (1 << 14) /* Slumber capable */
+#define HOST_CAP_AHCI             (1 << 18) /* AHCI only */
+#define HOST_CAP_CLO              (1 << 24) /* Command List Override support */
+#define HOST_CAP_SSS              (1 << 27) /* Staggered Spin-up */
+#define HOST_CAP_NCQ              (1 << 30) /* Native Command Queueing */
+#define HOST_CAP_64               (1 << 31) /* PCI DAC (64-bit DMA) support */
+
+/* registers for each SATA port */
+#define PORT_LST_ADDR             0x00 /* command list DMA addr */
+#define PORT_LST_ADDR_HI          0x04 /* command list DMA addr hi */
+#define PORT_FIS_ADDR             0x08 /* FIS rx buf addr */
+#define PORT_FIS_ADDR_HI          0x0c /* FIS rx buf addr hi */
+#define PORT_IRQ_STAT             0x10 /* interrupt status */
+#define PORT_IRQ_MASK             0x14 /* interrupt enable/disable mask */
+#define PORT_CMD                  0x18 /* port command */
+#define PORT_TFDATA               0x20 /* taskfile data */
+#define PORT_SIG                  0x24 /* device TF signature */
+#define PORT_SCR_STAT             0x28 /* SATA phy register: SStatus */
+#define PORT_SCR_CTL              0x2c /* SATA phy register: SControl */
+#define PORT_SCR_ERR              0x30 /* SATA phy register: SError */
+#define PORT_SCR_ACT              0x34 /* SATA phy register: SActive */
+#define PORT_CMD_ISSUE            0x38 /* command issue */
+#define PORT_RESERVED             0x3c /* reserved */
+
+/* PORT_IRQ_{STAT,MASK} bits */
+#define PORT_IRQ_COLD_PRES        (1 << 31) /* cold presence detect */
+#define PORT_IRQ_TF_ERR           (1 << 30) /* task file error */
+#define PORT_IRQ_HBUS_ERR         (1 << 29) /* host bus fatal error */
+#define PORT_IRQ_HBUS_DATA_ERR    (1 << 28) /* host bus data error */
+#define PORT_IRQ_IF_ERR           (1 << 27) /* interface fatal error */
+#define PORT_IRQ_IF_NONFATAL      (1 << 26) /* interface non-fatal error */
+#define PORT_IRQ_OVERFLOW         (1 << 24) /* xfer exhausted available S/G */
+#define PORT_IRQ_BAD_PMP          (1 << 23) /* incorrect port multiplier */
+
+#define PORT_IRQ_PHYRDY           (1 << 22) /* PhyRdy changed */
+#define PORT_IRQ_DEV_ILCK         (1 << 7) /* device interlock */
+#define PORT_IRQ_CONNECT          (1 << 6) /* port connect change status */
+#define PORT_IRQ_SG_DONE          (1 << 5) /* descriptor processed */
+#define PORT_IRQ_UNK_FIS          (1 << 4) /* unknown FIS rx'd */
+#define PORT_IRQ_SDB_FIS          (1 << 3) /* Set Device Bits FIS rx'd */
+#define PORT_IRQ_DMAS_FIS         (1 << 2) /* DMA Setup FIS rx'd */
+#define PORT_IRQ_PIOS_FIS         (1 << 1) /* PIO Setup FIS rx'd */
+#define PORT_IRQ_D2H_REG_FIS      (1 << 0) /* D2H Register FIS rx'd */
+
+#define PORT_IRQ_FREEZE           (PORT_IRQ_HBUS_ERR | PORT_IRQ_IF_ERR |   \
+                                   PORT_IRQ_CONNECT | PORT_IRQ_PHYRDY |    \
+                                   PORT_IRQ_UNK_FIS)
+#define PORT_IRQ_ERROR            (PORT_IRQ_FREEZE | PORT_IRQ_TF_ERR |     \
+                                   PORT_IRQ_HBUS_DATA_ERR)
+#define DEF_PORT_IRQ              (PORT_IRQ_ERROR | PORT_IRQ_SG_DONE |     \
+                                   PORT_IRQ_SDB_FIS | PORT_IRQ_DMAS_FIS |  \
+                                   PORT_IRQ_PIOS_FIS | PORT_IRQ_D2H_REG_FIS)
+
+/* PORT_CMD bits */
+#define PORT_CMD_ATAPI            (1 << 24) /* Device is ATAPI */
+#define PORT_CMD_LIST_ON          (1 << 15) /* cmd list DMA engine running */
+#define PORT_CMD_FIS_ON           (1 << 14) /* FIS DMA engine running */
+#define PORT_CMD_FIS_RX           (1 << 4) /* Enable FIS receive DMA engine */
+#define PORT_CMD_CLO              (1 << 3) /* Command list override */
+#define PORT_CMD_POWER_ON         (1 << 2) /* Power up device */
+#define PORT_CMD_SPIN_UP          (1 << 1) /* Spin up device */
+#define PORT_CMD_START            (1 << 0) /* Enable port DMA engine */
+
+#define PORT_CMD_ICC_MASK         (0xf << 28) /* i/f ICC state mask */
+#define PORT_CMD_ICC_ACTIVE       (0x1 << 28) /* Put i/f in active state */
+#define PORT_CMD_ICC_PARTIAL      (0x2 << 28) /* Put i/f in partial state */
+#define PORT_CMD_ICC_SLUMBER      (0x6 << 28) /* Put i/f in slumber state */
+
+#define PORT_IRQ_STAT_DHRS        (1 << 0) /* Device to Host Register FIS */
+#define PORT_IRQ_STAT_PSS         (1 << 1) /* PIO Setup FIS */
+#define PORT_IRQ_STAT_DSS         (1 << 2) /* DMA Setup FIS */
+#define PORT_IRQ_STAT_SDBS        (1 << 3) /* Set Device Bits */
+#define PORT_IRQ_STAT_UFS         (1 << 4) /* Unknown FIS */
+#define PORT_IRQ_STAT_DPS         (1 << 5) /* Descriptor Processed */
+#define PORT_IRQ_STAT_PCS         (1 << 6) /* Port Connect Change Status */
+#define PORT_IRQ_STAT_DMPS        (1 << 7) /* Device Mechanical Presence
+                                              Status */
+#define PORT_IRQ_STAT_PRCS        (1 << 22) /* File Ready Status */
+#define PORT_IRQ_STAT_IPMS        (1 << 23) /* Incorrect Port Multiplier
+                                               Status */
+#define PORT_IRQ_STAT_OFS         (1 << 24) /* Overflow Status */
+#define PORT_IRQ_STAT_INFS        (1 << 26) /* Interface Non-Fatal Error
+                                               Status */
+#define PORT_IRQ_STAT_IFS         (1 << 27) /* Interface Fatal Error */
+#define PORT_IRQ_STAT_HBDS        (1 << 28) /* Host Bus Data Error Status */
+#define PORT_IRQ_STAT_HBFS        (1 << 29) /* Host Bus Fatal Error Status */
+#define PORT_IRQ_STAT_TFES        (1 << 30) /* Task File Error Status */
+#define PORT_IRQ_STAT_CPDS        (1 << 31) /* Code Port Detect Status */
+
+#endif // ahci.h
index 3f4b13faaad9d314da0f0d7caa59dba31fbf7c15..818c9f9dc3b54c23114f19ca6ece1968a828cfbf 100644 (file)
@@ -10,6 +10,7 @@
 #include "cmos.h" // inb_cmos
 #include "util.h" // dprintf
 #include "ata.h" // process_ata_op
+#include "ahci.h" // process_ahci_op
 #include "usb-msc.h" // process_usb_op
 #include "virtio-blk.h" // process_virtio_op
 
@@ -292,6 +293,8 @@ process_op(struct disk_op_s *op)
         return process_usb_op(op);
     case DTYPE_VIRTIO:
        return process_virtio_op(op);
+    case DTYPE_AHCI:
+       return process_ahci_op(op);
     default:
         op->count = 0;
         return DISK_RET_EPARAM;
index 48568e63085d46a162247294f080838f38113c2c..c9c6845ae752540ca35f6887be2b9b3387e45e06 100644 (file)
@@ -10,6 +10,7 @@
 #include "disk.h" // struct disk_op_s
 #include "blockcmd.h" // struct cdb_request_sense
 #include "ata.h" // atapi_cmd_data
+#include "ahci.h" // atapi_cmd_data
 #include "usb-msc.h" // usb_cmd_data
 
 // Route command to low-level handler.
@@ -22,6 +23,8 @@ cdb_cmd_data(struct disk_op_s *op, void *cdbcmd, u16 blocksize)
         return atapi_cmd_data(op, cdbcmd, blocksize);
     case DTYPE_USB:
         return usb_cmd_data(op, cdbcmd, blocksize);
+    case DTYPE_AHCI:
+        return ahci_cmd_data(op, cdbcmd, blocksize);
     default:
         op->count = 0;
         return DISK_RET_EPARAM;
index f9bf3b84e4059fe6df38174c4f1c7e0df11812a6..db3e578abb2008241c429c6f5ffeffed8677bba6 100644 (file)
@@ -56,6 +56,8 @@
 #define CONFIG_ATA_DMA 0
 // Use 32bit PIO accesses on ATA (minor optimization on PCI transfers)
 #define CONFIG_ATA_PIO32 0
+// Support for AHCI disk code
+#define CONFIG_AHCI 0
 // Support for booting from a CD
 #define CONFIG_CDROM_BOOT 1
 // Support for emulating a boot CD as a floppy/harddrive
index 55a5da3ba7031afe61bc8f55583143bc2ab0f563..c1ba6ae44c0ad468fbd4c618865da83e8c661722 100644 (file)
@@ -198,6 +198,7 @@ struct drive_s {
 #define DTYPE_CDEMU    0x05
 #define DTYPE_USB      0x06
 #define DTYPE_VIRTIO   0x07
+#define DTYPE_AHCI     0x08
 
 #define MAXDESCSIZE 80
 
index 1e351421034abf05b2ff233195dec40cbc022811..ff6813cd0c44505e9eefdda5faf6cf9dbdcaf3c7 100644 (file)
@@ -12,6 +12,7 @@
 #include "biosvar.h" // struct bios_data_area_s
 #include "disk.h" // floppy_drive_setup
 #include "ata.h" // ata_setup
+#include "ahci.h" // ahci_setup
 #include "memmap.h" // add_e820
 #include "pic.h" // pic_setup
 #include "pci.h" // create_pirtable
@@ -178,6 +179,7 @@ init_hw(void)
 
     floppy_setup();
     ata_setup();
+    ahci_setup();
     ramdisk_setup();
     virtio_blk_setup();
 }