[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