[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[cdi-devel] [PATCH v2 4/5] ahci: Controller and hard disk support



Signed-off-by: Kevin Wolf <kevin@xxxxxxxxxx>
---
 ahci/ahci.h       |  336 ++++++++++++++++++++++++++++++++++++++++++++++++
 ahci/disk.c       |  366 +++++++++++++++++++++++++++++++++++++++++++++++++++++
 ahci/main.c       |  257 +++++++++++++++++++++++++++++++++++++
 include/cdi.h     |    1 +
 include/cdi/pci.h |    3 +
 5 files changed, 963 insertions(+), 0 deletions(-)
 create mode 100644 ahci/ahci.h
 create mode 100644 ahci/disk.c
 create mode 100644 ahci/main.c

diff --git a/ahci/ahci.h b/ahci/ahci.h
new file mode 100644
index 0000000..46e8fd5
--- /dev/null
+++ b/ahci/ahci.h
@@ -0,0 +1,336 @@
+/*
+ * Copyright (c) 2013-2014 Kevin Wolf
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef AHCI_H
+#define AHCI_H
+
+#include <stdint.h>
+
+#include "cdi/storage.h"
+#include "cdi/mem.h"
+
+#define BIT(x) (1 << x)
+
+#define MAX_PORTS 32
+#define FIS_BYTES 256
+#define CMD_LIST_BYTES 1024
+#define CMD_TABLE_BYTES (sizeof(struct cmd_table) * sizeof(struct ahci_prd))
+
+#define AHCI_IRQ_TIMEOUT 5000 /* ms */
+
+enum {
+    ATA_CMD_READ_DMA            = 0xc8,
+    ATA_CMD_READ_DMA_EXT        = 0x25,
+    ATA_CMD_WRITE_DMA           = 0xca,
+    ATA_CMD_WRITE_DMA_EXT       = 0x35,
+    ATA_CMD_IDENTIFY_DEVICE     = 0xec,
+};
+
+enum {
+    REG_CAP     = 0x00, /* Host Capabilities */
+    REG_GHC     = 0x04, /* Global Host Control */
+    REG_IS      = 0x08, /* Interrupt Status */
+    REG_PI      = 0x0c, /* Ports Implemented */
+
+    REG_PORTS   = 0x100,
+
+    PORTSIZE    = 0x80,
+    REG_PxCLB   = 0x00, /* Command List Base Address */
+    REG_PxCLBU  = 0x04, /* Command List Base Address Upper */
+    REG_PxFB    = 0x08, /* FIS Base Address */
+    REG_PxFBU   = 0x0c, /* FIS Base Address Upper */
+    REG_PxIS    = 0x10, /* Interrupt Status */
+    REG_PxIE    = 0x14, /* Interrupt Enable */
+    REG_PxCMD   = 0x18, /* Command and status */
+    REG_PxTFD   = 0x20, /* Task File Data*/
+    REG_PxSIG   = 0x24, /* Signature */
+    REG_PxSSTS  = 0x28, /* Serial ATA Status */
+    REG_PxSCTL  = 0x2c, /* Serial ATA Control */
+    REG_PxSERR  = 0x30, /* Serial ATA Error */
+    REG_PxCI    = 0x38, /* Command Issue */
+};
+
+enum {
+    CAP_NCS_SHIFT   = 8,
+    CAP_NCS_MASK    = (0x1f << CAP_NCS_SHIFT),
+};
+
+enum {
+    GHC_HR      = (1 <<  0), /* HBA Reset */
+    GHC_IE      = (1 <<  1), /* Interrupt Enable */
+    GHC_AE      = (1 << 31), /* AHCI Enable */
+};
+
+enum {
+    PxIS_IFS    = (1 << 27), /* Interface Fatal Error Status */
+    PxIS_HBDS   = (1 << 28), /* Host Bus Data Error Status */
+    PxIS_HBFS   = (1 << 29), /* Host Bus Fatal Error Status */
+    PxIS_TFES   = (1 << 30), /* Task File Error Status */
+};
+
+enum {
+    PxCMD_ST    = (1 <<  0), /* Start */
+    PxCMD_FRE   = (1 <<  4), /* FIS Receive Enable */
+    PxCMD_FR    = (1 << 14), /* FIS Receive Running */
+    PxCMD_CR    = (1 << 15), /* Command List Running */
+};
+
+enum {
+    PxTFD_BSY   = (1 << 7), /* Interface is busy */
+    PxTFD_DRQ   = (1 << 3), /* Data transfer is requested */
+    PxTFD_ERR   = (1 << 0), /* Error during transfer */
+};
+
+enum {
+    PxSSTS_DET_MASK     = (0xf << 0),
+    PxSSTS_IPM_MASK     = (0xf << 8),
+
+    PxSSTS_DET_PRESENT  = (0x3 << 0),
+    PxSSTS_IPM_ACTIVE   = (0x1 << 8),
+};
+
+enum {
+    PxSCTL_DET_MASK     = (0xf << 0),
+};
+
+/* SATA 2.6: "11.3 Software reset protocol" */
+enum {
+    SATA_SIG_DISK       = 0x00000101,
+    SATA_SIG_PACKET     = 0xeb140101,
+};
+
+struct ahci_port {
+    struct cdi_mem_area*        fis;
+    uint64_t                    fis_phys;
+
+    struct cmd_header*          cmd_list;
+    struct cdi_mem_area*        cmd_list_mem;
+    uint64_t                    cmd_list_phys;
+
+    /* TODO More than one command with one PRDT entry */
+    struct cmd_table*           cmd_table;
+    struct cdi_mem_area*        cmd_table_mem;
+    uint64_t                    cmd_table_phys;
+
+    uint32_t                    last_is;
+};
+
+struct ahci_device {
+    struct cdi_device           dev;
+    struct cdi_mem_area*        mmio;
+    int                         irq;
+
+    uint32_t                    ports;
+    uint32_t                    cmd_slots;
+
+    struct ahci_port            port[MAX_PORTS];
+};
+
+struct ahci_bus_data {
+    struct cdi_bus_data         cdi;
+    struct ahci_device*         ahci;
+    int                         port;
+};
+
+struct ahci_disk {
+    struct cdi_storage_device   storage;
+    struct ahci_device*         ahci;
+    int                         port;
+
+    bool                        lba48;
+};
+
+enum {
+    FIS_TYPE_H2D            = 0x27,
+    FIS_TYPE_D2H            = 0x34,
+    FIS_TYPE_DMA_ACTIVATE   = 0x39,
+    FIS_TYPE_DMA_SETUP      = 0x41,
+    FIS_TYPE_DATA           = 0x46,
+    FIS_TYPE_BIST_ACTIVATE  = 0x58,
+    FIS_TYPE_PIO_SETUP      = 0x5f,
+    FIS_TYPE_SET_DEV_BITS   = 0xa1,
+};
+
+struct received_fis {
+    /* DMA Setup FIS */
+    struct dsfis {
+        uint8_t     type;
+        uint8_t     flags;
+        uint16_t    reserved;
+        uint32_t    dma_buf_low;
+        uint32_t    dma_buf_high;
+        uint32_t    reserved_2;
+        uint32_t    dma_buf_offset;
+        uint32_t    dma_count;
+        uint32_t    reserved_3;
+    } dsfis;
+    uint32_t padding_1;
+
+    /* PIO Setup FIS */
+    struct psfis {
+        uint8_t     type;
+        uint8_t     flags;
+        uint8_t     status;
+        uint8_t     error;
+        uint8_t     lba_low;
+        uint8_t     lba_mid;
+        uint8_t     lba_high;
+        uint8_t     device;
+        uint8_t     lba_low_exp;
+        uint8_t     lba_mid_exp;
+        uint8_t     lba_high_exp;
+        uint8_t     reserved;
+        uint16_t    sector_count;
+        uint8_t     reserved_2;
+        uint8_t     e_status;
+        uint16_t    xfer_count;
+        uint16_t    reserved_3;
+    } psfis;
+    uint32_t padding_2[3];
+
+    /* D2H Register FIS */
+    struct rfis {
+        uint8_t     type;
+        uint8_t     flags;
+        uint8_t     status;
+        uint8_t     error;
+        uint8_t     lba_low;
+        uint8_t     lba_mid;
+        uint8_t     lba_high;
+        uint8_t     device;
+        uint8_t     lba_low_exp;
+        uint8_t     lba_mid_exp;
+        uint8_t     lba_high_exp;
+        uint8_t     reserved;
+        uint16_t    sector_count;
+        uint16_t    reserved_2[3];
+    } rfis;
+    uint32_t padding_3;
+
+    /* Set Device Bits FIS */
+    struct sdbfis {
+        uint8_t     type;
+        uint8_t     flags;
+        uint8_t     status;
+        uint8_t     error;
+        uint32_t    reserved;
+    } sdbfis;
+
+    /* Unknown FIS */
+    uint8_t ufis[64];
+} __attribute__((packed));
+CDI_BUILD_BUG_ON(sizeof(struct received_fis) != 0xa0);
+
+enum {
+    H2D_FIS_F_COMMAND = 0x80,
+};
+
+struct h2d_fis {
+    uint8_t     type;
+    uint8_t     flags;
+    uint8_t     command;
+    uint8_t     features;
+    uint8_t     lba_low;
+    uint8_t     lba_mid;
+    uint8_t     lba_high;
+    uint8_t     device;
+    uint8_t     lba_low_exp;
+    uint8_t     lba_mid_exp;
+    uint8_t     lba_high_exp;
+    uint8_t     features_exp;
+    uint16_t    sector_count;
+    uint8_t     reserved;
+    uint8_t     control;
+    uint32_t    reserved_2;
+} __attribute__((packed));
+
+struct ahci_prd {
+    uint32_t    dba;        /* Data Base Address */
+    uint32_t    dbau;       /* Data Base Address Upper */
+    uint32_t    reserved;
+    uint32_t    dbc;        /* Data Byte Count */
+} __attribute__((packed));
+
+enum {
+    CMD_HEADER_F_FIS_LENGTH_5_DW    = (5 << 0),
+    CMD_HEADER_F_WRITE              = (1 << 6),
+};
+
+struct cmd_header {
+    uint16_t    flags;
+    uint16_t    prdtl;      /* PRDT Length (in entries) */
+    uint32_t    prdbc;      /* PRD Byte Count */
+    uint32_t    ctba0;      /* Command Table Base Address */
+    uint32_t    ctba_u0;    /* Command Table Base Address (Upper 32 Bits) */
+    uint64_t    reserved1;
+    uint64_t    reserved2;
+} __attribute__((packed));
+
+struct cmd_table {
+    struct h2d_fis  cfis;
+    uint8_t         padding[0x40 - sizeof(struct h2d_fis)];
+    uint8_t         acmd[0x10];         /* ATAPI command */
+    uint8_t         reserved[0x30];
+    struct ahci_prd prdt[];
+} __attribute__((packed));
+
+/* Register access functions */
+
+static inline void reg_outl(struct ahci_device* ahci, int reg, uint32_t value)
+{
+    volatile uint32_t* mmio = (uint32_t*) ((uint8_t*) ahci->mmio->vaddr + reg);
+    *mmio = value;
+}
+
+static inline uint32_t reg_inl(struct ahci_device* ahci, int reg)
+{
+    volatile uint32_t* mmio = (uint32_t*) ((uint8_t*) ahci->mmio->vaddr + reg);
+    return *mmio;
+}
+
+
+static inline void pxreg_outl(struct ahci_device* ahci, int port, int reg,
+                              uint32_t value)
+{
+    volatile uint32_t* mmio = (uint32_t*) ((uint8_t*) ahci->mmio->vaddr +
+        REG_PORTS + port * PORTSIZE + reg);
+    *mmio = value;
+}
+
+static inline uint32_t pxreg_inl(struct ahci_device* ahci, int port, int reg)
+{
+    volatile uint32_t* mmio = (uint32_t*) ((uint8_t*) ahci->mmio->vaddr +
+        REG_PORTS + port * PORTSIZE + reg);
+    return *mmio;
+}
+
+/* ahci/main.c */
+void ahci_port_comreset(struct ahci_device* ahci, int port);
+
+/* ahci/disk.c */
+extern struct cdi_storage_driver ahci_disk_driver;
+
+
+#endif
diff --git a/ahci/disk.c b/ahci/disk.c
new file mode 100644
index 0000000..0879f20
--- /dev/null
+++ b/ahci/disk.c
@@ -0,0 +1,366 @@
+/*
+ * Copyright (c) 2013-2014 Kevin Wolf
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+
+#include "cdi.h"
+#include "cdi/misc.h"
+
+#include "ahci.h"
+
+#define DISK_DRIVER_NAME "ahci-disk"
+
+/**
+ * This function is called for any requests (even succeeding ones). Its job is
+ * to recover from any failures and decide whether a request must return
+ * failues.
+ *
+ * @return 0 if the command can still be completed successfully, -1 if failure
+ * should be returned.
+ */
+static int ahci_request_handle_error(struct ahci_disk* disk, uint32_t is)
+{
+    /* AHCI 1.3: "6.2.2 Software Error Recovery" */
+    if (is & (PxIS_HBFS | PxIS_HBDS | PxIS_IFS | PxIS_TFES)) {
+        /* Fatal errors. Restart the port and return failure. */
+        uint32_t cmd, tfd;
+
+        /* Stor processing of the command queue */
+        cmd = pxreg_inl(disk->ahci, disk->port, REG_PxCMD);
+        pxreg_outl(disk->ahci, disk->port, REG_PxCMD, cmd & ~PxCMD_ST);
+        while (pxreg_inl(disk->ahci, disk->port, REG_PxCMD) & PxCMD_CR);
+
+        /* Reset SATA error register */
+        pxreg_outl(disk->ahci, disk->port, REG_PxSERR, 0xffffffff);
+
+        /* COMRESET if PxTFD.STS.(BSY|DRQ) == 1 */
+        tfd = pxreg_inl(disk->ahci, disk->port, REG_PxTFD);
+        if (tfd & (PxTFD_BSY | PxTFD_DRQ)) {
+            ahci_port_comreset(disk->ahci, disk->port);
+        }
+
+        /* Restart port */
+        pxreg_outl(disk->ahci, disk->port, REG_PxCMD, cmd | PxCMD_ST);
+        return -1;
+    }
+
+    return 0;
+}
+
+static int ahci_request(struct ahci_disk* disk, int cmd, uint64_t lba,
+                        uint64_t bytes, struct cdi_mem_area* buf)
+{
+    struct ahci_port *port = &disk->ahci->port[disk->port];
+    uint32_t flags, device;
+
+    if (buf->paddr.num != 1) {
+        return -1;
+    }
+
+    device = 0;
+    if (cmd == ATA_CMD_READ_DMA || cmd == ATA_CMD_READ_DMA_EXT ||
+        cmd == ATA_CMD_WRITE_DMA || cmd == ATA_CMD_WRITE_DMA_EXT)
+    {
+        device |= 0x40;
+    }
+
+    port->cmd_table->cfis = (struct h2d_fis) {
+        .type           = FIS_TYPE_H2D,
+        .flags          = H2D_FIS_F_COMMAND,
+        .command        = cmd,
+        .lba_low        = lba & 0xff,
+        .lba_mid        = (lba >> 8) & 0xff,
+        .lba_high       = (lba >> 16) & 0xff,
+        .lba_low_exp    = (lba >> 24) & 0xff,
+        .lba_mid_exp    = (lba >> 32) & 0xff,
+        .lba_high_exp   = (lba >> 40) & 0xff,
+        .device         = device,
+        .sector_count   = bytes / disk->storage.block_size,
+    };
+    port->cmd_table->prdt[0] = (struct ahci_prd) {
+        .dba        = buf->paddr.items[0].start,
+        .dbc        = bytes - 1,
+    };
+
+    flags = CMD_HEADER_F_FIS_LENGTH_5_DW;
+    if (cmd == ATA_CMD_WRITE_DMA || cmd == ATA_CMD_WRITE_DMA_EXT) {
+        flags |= CMD_HEADER_F_WRITE;
+    }
+
+    port->cmd_list[0] = (struct cmd_header) {
+        .flags      = flags,
+        .prdtl      = 1,
+        .prdbc      = buf->size,
+        .ctba0      = port->cmd_table_phys,
+    };
+
+    cdi_reset_wait_irq(disk->ahci->irq);
+    pxreg_outl(disk->ahci, disk->port, REG_PxCI, 0x1);
+
+    do {
+        cdi_wait_irq(disk->ahci->irq, AHCI_IRQ_TIMEOUT);
+        if (ahci_request_handle_error(disk, port->last_is) < 0) {
+            return -1;
+        }
+        cdi_reset_wait_irq(disk->ahci->irq);
+    } while (pxreg_inl(disk->ahci, disk->port, REG_PxCI) & 0x1);
+
+    if (ahci_request_handle_error(disk, port->last_is) < 0) {
+        return -1;
+    }
+
+    return 0;
+}
+
+static int ahci_identify(struct ahci_disk* disk)
+{
+    struct cdi_mem_area* buf;
+    uint16_t* words;
+    int ret;
+
+    buf = cdi_mem_alloc(512, CDI_MEM_PHYS_CONTIGUOUS | CDI_MEM_DMA_4G);
+    if (buf == NULL) {
+        return -1;
+    }
+
+    ret = ahci_request(disk, ATA_CMD_IDENTIFY_DEVICE, 0, 512, buf);
+    if (ret == 0) {
+        words = buf->vaddr;
+        disk->lba48 = !!(words[86] & (1 << 10));
+
+        if (disk->lba48) {
+            disk->storage.block_count = *(uint64_t*) &words[100];
+        } else {
+            disk->storage.block_count = *(uint32_t*) &words[60];
+        }
+    }
+
+    cdi_mem_free(buf);
+
+    return ret;
+}
+
+static int ahci_rw_blocks(struct cdi_storage_device* device, uint64_t start,
+                          uint64_t count, void* buffer, bool read)
+{
+    struct ahci_disk* disk = (struct ahci_disk*) device;
+    uint64_t bs = disk->storage.block_size;
+    struct cdi_mem_area* buf;
+    int cmd;
+    int ret;
+
+    buf = cdi_mem_alloc(count * bs,
+                        CDI_MEM_PHYS_CONTIGUOUS | CDI_MEM_DMA_4G | 1);
+    if (buf == NULL) {
+        return -1;
+    }
+
+    if (read) {
+        cmd = disk->lba48 ? ATA_CMD_READ_DMA_EXT : ATA_CMD_READ_DMA;
+    } else {
+        cmd = disk->lba48 ? ATA_CMD_WRITE_DMA_EXT : ATA_CMD_WRITE_DMA;
+        memcpy(buf->vaddr, buffer, bs * count);
+    }
+
+    ret = ahci_request(disk, cmd, start, bs * count, buf);
+
+    if (ret == 0 && read) {
+        memcpy(buffer, buf->vaddr, bs * count);
+    }
+
+    cdi_mem_free(buf);
+    return ret;
+}
+
+static int ahci_read_blocks(struct cdi_storage_device* device, uint64_t start,
+                            uint64_t count, void* buffer)
+{
+    return ahci_rw_blocks(device, start, count, buffer, true);
+}
+
+static int ahci_write_blocks(struct cdi_storage_device* device, uint64_t start,
+                             uint64_t count, void* buffer)
+{
+    return ahci_rw_blocks(device, start, count, buffer, false);
+}
+
+
+/* AHCI 1.3: "10.1.2 System Software Specific Initialization" */
+/* See ahci_init_hardware() for first part of the initialisation */
+static int ahci_disk_init(struct ahci_disk* disk)
+{
+    struct ahci_device* ahci = disk->ahci;
+    int port = disk->port;
+    struct ahci_port* p = &ahci->port[port];
+    uint32_t cmd;
+
+    /* The FIS must be 256-byte aligned */
+    p->fis = cdi_mem_alloc(FIS_BYTES,
+                           CDI_MEM_PHYS_CONTIGUOUS | CDI_MEM_DMA_4G | 8);
+    if (p->fis == NULL) {
+        printf("ahci: Could not allocate FIS\n");
+        goto fail;
+    }
+
+    memset(p->fis->vaddr, 0, FIS_BYTES);
+
+    p->fis_phys = p->fis->paddr.items[0].start;
+    pxreg_outl(ahci, port, REG_PxFB, p->fis_phys);
+    pxreg_outl(ahci, port, REG_PxFBU, 0); /* TODO Support 64 bit */
+
+    /* The Command List must be 1k aligned */
+    p->cmd_list_mem =
+        cdi_mem_alloc(CMD_LIST_BYTES,
+                      CDI_MEM_PHYS_CONTIGUOUS | CDI_MEM_DMA_4G | 10);
+    if (p->cmd_list_mem == NULL) {
+        printf("ahci: Could not allocate Command List\n");
+        goto fail;
+    }
+
+    p->cmd_list = p->cmd_list_mem->vaddr;
+    p->cmd_list_phys = p->cmd_list_mem->paddr.items[0].start;
+
+    memset(p->cmd_list, 0, CMD_LIST_BYTES);
+
+    pxreg_outl(ahci, port, REG_PxCLB, p->cmd_list_phys);
+    pxreg_outl(ahci, port, REG_PxCLBU, 0); /* TODO Support 64 bit */
+
+    /* Command tables must be 128-byte aligned */
+    p->cmd_table_mem =
+        cdi_mem_alloc(CMD_TABLE_BYTES,
+                      CDI_MEM_PHYS_CONTIGUOUS | CDI_MEM_DMA_4G | 7);
+    if (p->cmd_table_mem == NULL) {
+        printf("ahci: Could not allocate Command Table\n");
+        goto fail;
+    }
+
+    p->cmd_table = p->cmd_table_mem->vaddr;
+    p->cmd_table_phys = p->cmd_table_mem->paddr.items[0].start;
+
+    /* Enable FIS Receive and start processing command list */
+    cmd = pxreg_inl(ahci, port, REG_PxCMD);
+    pxreg_outl(ahci, port, REG_PxCMD, cmd | PxCMD_FRE | PxCMD_ST);
+
+    /* Clear Serial ATA Error register*/
+    pxreg_outl(ahci, port, REG_PxSERR, 0xffffffff);
+
+    /* Set PxIE for interrupts */
+    pxreg_outl(ahci, port, REG_PxIE, 0xffffffff);
+
+    return 0;
+
+fail:
+    if (p->fis) {
+        cdi_mem_free(p->fis);
+        p->fis = NULL;
+    }
+    if (p->cmd_list) {
+        cdi_mem_free(p->cmd_list_mem);
+        p->cmd_list_mem = NULL;
+    }
+    if (p->cmd_table_mem) {
+        cdi_mem_free(p->cmd_table_mem);
+        p->cmd_table_mem = NULL;
+    }
+    return -1;
+}
+
+static struct cdi_device* disk_init_device(struct cdi_bus_data* bus_data)
+{
+    struct ahci_bus_data* ahci_bus_data = (struct ahci_bus_data*) bus_data;
+    struct ahci_disk* disk;
+
+    if (bus_data->bus_type != CDI_AHCI) {
+        return NULL;
+    }
+
+    disk = calloc(1, sizeof(*disk));
+    disk->ahci = ahci_bus_data->ahci;
+    disk->port = ahci_bus_data->port;
+    disk->storage.block_size = 512;
+    disk->storage.dev.driver = &ahci_disk_driver.drv;
+    asprintf((char**) &disk->storage.dev.name, "ahci%d", disk->port);
+
+    if (ahci_disk_init(disk) < 0) {
+        goto fail;
+    }
+
+    if (ahci_identify(disk) < 0) {
+        goto fail;
+    }
+
+    cdi_storage_device_init(&disk->storage);
+
+    return &disk->storage.dev;
+
+fail:
+    free(disk);
+    return NULL;
+}
+
+static void disk_remove_device(struct cdi_device* device)
+{
+    /* TODO */
+}
+
+/**
+ * Initialises the AHCI disk driver
+ */
+static int ahci_disk_driver_init(void)
+{
+    cdi_storage_driver_init(&ahci_disk_driver);
+    return 0;
+}
+
+/**
+ * Deinitialises the AHCI disk driver
+ */
+static int ahci_disk_driver_destroy(void)
+{
+    cdi_storage_driver_destroy(&ahci_disk_driver);
+
+    /* TODO Deinitialise all devices */
+
+    return 0;
+}
+
+
+struct cdi_storage_driver ahci_disk_driver = {
+    .drv = {
+        .type           = CDI_STORAGE,
+        .bus            = CDI_AHCI,
+        .name           = DISK_DRIVER_NAME,
+        .init           = ahci_disk_driver_init,
+        .destroy        = ahci_disk_driver_destroy,
+        .init_device    = disk_init_device,
+        .remove_device  = disk_remove_device,
+    },
+    .read_blocks        = ahci_read_blocks,
+    .write_blocks       = ahci_write_blocks,
+};
+
+CDI_DRIVER(DISK_DRIVER_NAME, ahci_disk_driver)
diff --git a/ahci/main.c b/ahci/main.c
new file mode 100644
index 0000000..6a69775
--- /dev/null
+++ b/ahci/main.c
@@ -0,0 +1,257 @@
+/*
+ * Copyright (c) 2013-2014 Kevin Wolf
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <stdint.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+
+#include "cdi/lists.h"
+#include "cdi/pci.h"
+#include "cdi/misc.h"
+#include "cdi.h"
+
+#include "ahci.h"
+
+#define DRIVER_NAME "ahci"
+
+static struct cdi_storage_driver ahci_driver;
+
+
+/* AHCI 1.3: "10.1.2 System Software Specific Initialization" */
+/* See ahci_disk_init() for second part of the initialisation */
+static int ahci_init_hardware(struct ahci_device* ahci)
+{
+    int port;
+    bool all_idle;
+    uint32_t cmd, ssts, sig;
+    int retries;
+
+    /* HBA reset (see AHCI 1.3, chapter 10.4.3) */
+    reg_outl(ahci, REG_GHC, GHC_AE);
+    reg_outl(ahci, REG_GHC, GHC_AE | GHC_HR);
+
+    while (reg_inl(ahci, REG_GHC) & GHC_HR) {
+        /* wait for reset to complete */
+    }
+
+    reg_outl(ahci, REG_GHC, GHC_AE | GHC_IE);
+
+    /* Determine implemented ports */
+    ahci->ports = reg_inl(ahci, REG_PI);
+
+    /* Ensure that all ports are idle */
+    retries = 10;
+    do {
+        all_idle = true;
+        for (port = 0; port < MAX_PORTS; port++) {
+            if (!(ahci->ports & BIT(port))) {
+                continue;
+            }
+            cmd = pxreg_inl(ahci, port, REG_PxCMD);
+            if (cmd & (PxCMD_ST | PxCMD_CR | PxCMD_FR | PxCMD_FRE)) {
+                all_idle = false;
+                cmd &= ~(PxCMD_ST | PxCMD_FR);
+                pxreg_outl(ahci, port, REG_PxCMD, cmd);
+            }
+        }
+        if (!all_idle) {
+            cdi_sleep_ms(100);
+        }
+    } while (!all_idle && --retries);
+
+    if (!all_idle) {
+        printf("ahci: Couldn't place ports into idle state\n");
+    }
+
+    /* Determine number of command slots */
+    ahci->cmd_slots = (reg_inl(ahci, REG_CAP) & CAP_NCS_MASK) >> CAP_NCS_SHIFT;
+
+    /* Allocate Command List and FIS for each port */
+    for (port = 0; port < MAX_PORTS; port++) {
+        if (!(ahci->ports & BIT(port))) {
+            continue;
+        }
+
+        /* If there is a device attached, register it */
+        ssts = pxreg_inl(ahci, port, REG_PxSSTS);
+
+        if ((ssts & PxSSTS_DET_MASK) != PxSSTS_DET_PRESENT ||
+            (ssts & PxSSTS_IPM_MASK) != PxSSTS_IPM_ACTIVE)
+        {
+            continue;
+        }
+
+        /* Create a device with the right driver */
+        sig = pxreg_inl(ahci, port, REG_PxSIG);
+
+        switch (sig) {
+            case SATA_SIG_DISK: {
+                struct ahci_bus_data bus_data = {
+                    .cdi = {
+                        .bus_type   = CDI_AHCI,
+                    },
+                    .ahci = ahci,
+                    .port = port,
+                };
+                cdi_provide_device_internal_drv(&bus_data.cdi,
+                                                &ahci_disk_driver.drv);
+                break;
+            }
+            default:
+                printf("ahci: Can't handle device with signature %x", sig);
+                break;
+        }
+    }
+
+    return 0;
+}
+
+void ahci_port_comreset(struct ahci_device* ahci, int port)
+{
+    uint32_t sctl = pxreg_inl(ahci, port, REG_PxSCTL);
+
+    sctl &= ~PxSCTL_DET_MASK;
+    sctl |= 0x1;
+    pxreg_outl(ahci, port, REG_PxSCTL, sctl);
+
+    cdi_sleep_ms(1);
+
+    sctl &= ~PxSCTL_DET_MASK;
+    pxreg_outl(ahci, port, REG_PxSCTL, sctl);
+}
+
+static void irq_handler(struct cdi_device* dev)
+{
+    struct ahci_device* ahci = (struct ahci_device*) dev;
+    uint32_t is;
+    int port;
+
+    is = reg_inl(ahci, REG_IS);
+
+    for (port = 0; port < MAX_PORTS; port++) {
+        struct ahci_port* p = &ahci->port[port];
+        uint32_t port_is;
+
+        if (!(is & ahci->ports & BIT(port))) {
+            continue;
+        }
+
+        port_is = pxreg_inl(ahci, port, REG_PxIS);
+        pxreg_outl(ahci, port, REG_PxIS, port_is);
+        p->last_is = port_is;
+    }
+
+    /* For the actual interrupt handling, we use cdi_wait_irq(). */
+    reg_outl(ahci, REG_IS, is);
+}
+
+static struct cdi_device* ahci_init_device(struct cdi_bus_data* bus_data)
+{
+    struct cdi_pci_device* pci = (struct cdi_pci_device*) bus_data;
+    struct ahci_device* ahci;
+    int i, ret;
+
+    /* Check PCI class/subclass */
+    if (pci->class_id != PCI_CLASS_STORAGE ||
+        pci->subclass_id != PCI_SUBCLASS_ST_SATA ||
+        pci->interface_id != 1)
+    {
+        return NULL;
+    }
+
+    ahci = calloc(1, sizeof(*ahci));
+
+    /* Map AHCI BAR */
+    struct cdi_pci_resource* res;
+    cdi_list_t reslist = pci->resources;
+
+    for (i = 0; (res = cdi_list_get(reslist, i)); i++) {
+        if (res->index == 5 && res->type == CDI_PCI_MEMORY) {
+            ahci->mmio = cdi_mem_map(res->start, res->length);
+        }
+    }
+
+    if (ahci->mmio == NULL) {
+        printf("ahci: Could not find MMIO area\n");
+        goto fail;
+    }
+
+    ahci->irq = pci->irq;
+    cdi_register_irq(ahci->irq, irq_handler, &ahci->dev);
+
+    /* The actual hardware initialisation as described in the spec */
+    ret = ahci_init_hardware(ahci);
+    if (ret < 0) {
+        goto fail;
+    }
+
+    return &ahci->dev;
+
+fail:
+    free(ahci);
+    return NULL;
+}
+
+static void ahci_remove_device(struct cdi_device* device)
+{
+    /* TODO */
+}
+
+/**
+ * Initialises the AHCI controller driver
+ */
+static int ahci_driver_init(void)
+{
+    cdi_storage_driver_init(&ahci_driver);
+
+    return 0;
+}
+
+/**
+ * Deinitialises the AHCI controller driver
+ */
+static int ahci_driver_destroy(void)
+{
+    cdi_storage_driver_destroy(&ahci_driver);
+
+    /* TODO Deinitialise all devices */
+
+    return 0;
+}
+
+static struct cdi_storage_driver ahci_driver = {
+    .drv = {
+        .type           = CDI_AHCI,
+        .bus            = CDI_PCI,
+        .name           = DRIVER_NAME,
+        .init           = ahci_driver_init,
+        .destroy        = ahci_driver_destroy,
+        .init_device    = ahci_init_device,
+        .remove_device  = ahci_remove_device,
+    },
+};
+
+CDI_DRIVER(DRIVER_NAME, ahci_driver)
diff --git a/include/cdi.h b/include/cdi.h
index 7c21836..a9207db 100644
--- a/include/cdi.h
+++ b/include/cdi.h
@@ -118,6 +118,7 @@ typedef enum {
     CDI_USB             = 8,
     CDI_FILESYSTEM      = 9,
     CDI_PCI             = 10,
+    CDI_AHCI            = 11,
 } cdi_device_type_t;
 
 struct cdi_driver;
diff --git a/include/cdi/pci.h b/include/cdi/pci.h
index d60a105..39c92cb 100644
--- a/include/cdi/pci.h
+++ b/include/cdi/pci.h
@@ -17,6 +17,9 @@
 #include <cdi-osdep.h>
 #include <cdi/lists.h>
 
+#define PCI_CLASS_STORAGE         0x01
+#define PCI_SUBCLASS_ST_SATA      0x06
+
 #define PCI_CLASS_MULTIMEDIA      0x04
 #define PCI_SUBCLASS_MM_HDAUDIO   0x03
 
-- 
1.7.7