Hi,
This patch series adds ahci support to seabios. It depends on the call32 patches just posted by Kevin.
For convinience the patches are available in the git repository at: git://anongit.freedesktop.org/~kraxel/seabios ahci.2
enjoy, Gerd
Gerd Hoffmann (3): pci: add helper functions for mmio bar access from real mode. make ata helpers available add ahci support
Makefile | 2 +- src/ahci.c | 484 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ src/ahci.h | 196 +++++++++++++++++++++++ src/ata.c | 20 ++- src/ata.h | 2 + src/block.c | 3 + src/blockcmd.c | 3 + src/config.h | 2 + src/disk.h | 1 + src/pci.c | 51 ++++++ src/pci.h | 4 + src/post.c | 2 + 12 files changed, 760 insertions(+), 10 deletions(-) create mode 100644 src/ahci.c create mode 100644 src/ahci.h
This patch adds helper pci_readl and pci_writel which can be used to access pci mmio bars from real mode. They work in 32bit mode too. ahci support needs this, also ohci for bulk transfers, and probably more devices in the future.
Signed-off-by: Gerd Hoffmann kraxel@redhat.com --- src/pci.c | 51 +++++++++++++++++++++++++++++++++++++++++++++++++++ src/pci.h | 4 ++++ 2 files changed, 55 insertions(+), 0 deletions(-)
diff --git a/src/pci.c b/src/pci.c index 115689d..e43c83a 100644 --- a/src/pci.c +++ b/src/pci.c @@ -9,6 +9,7 @@ #include "ioport.h" // outl #include "util.h" // dprintf #include "config.h" // CONFIG_* +#include "farptr.h" // CONFIG_* #include "pci_regs.h" // PCI_VENDOR_ID #include "pci_ids.h" // PCI_CLASS_DISPLAY_VGA
@@ -225,3 +226,53 @@ pci_reboot(void) outb(v|6, PORT_PCI_REBOOT); /* Actually do the reset */ udelay(50); } + +// helper functions to access pci mmio bars from real mode + +extern u32 pci_readl_32(u32 addr); +#if MODESEGMENT == 0 +u32 VISIBLE32FLAT +pci_readl_32(u32 addr) +{ + dprintf(3, "32: pci read : %x\n", addr); + return readl((void*)addr); +} +#endif + +u32 pci_readl(u32 addr) +{ + if (MODESEGMENT) { + dprintf(3, "16: pci read : %x\n", addr); + return call32(pci_readl_32, addr, -1); + } else { + return pci_readl_32(addr); + } +} + +struct reg32 { + u32 addr; + u32 data; +}; + +extern void pci_writel_32(struct reg32 *reg32); +#if MODESEGMENT == 0 +void VISIBLE32FLAT +pci_writel_32(struct reg32 *reg32) +{ + dprintf(3, "32: pci write: %x, %x (%p)\n", reg32->addr, reg32->data, reg32); + writel((void*)(reg32->addr), reg32->data); +} +#endif + +void pci_writel(u32 addr, u32 val) +{ + struct reg32 reg32 = { .addr = addr, .data = val }; + if (MODESEGMENT) { + dprintf(3, "16: pci write: %x, %x (%x:%p)\n", + reg32.addr, reg32.data, GET_SEG(SS), ®32); + void *flatptr = MAKE_FLATPTR(GET_SEG(SS), ®32); + call32(pci_writel_32, (u32)flatptr, -1); + } else { + pci_writel_32(®32); + } +} diff --git a/src/pci.h b/src/pci.h index 64bd43b..46af207 100644 --- a/src/pci.h +++ b/src/pci.h @@ -96,6 +96,10 @@ int pci_init_device(const struct pci_device_id *table, u16 bdf, void *arg); int pci_find_init_device(const struct pci_device_id *ids, void *arg); void pci_reboot(void);
+// helper functions to access pci mmio bars from real mode +u32 pci_readl(u32 addr); +void pci_writel(u32 addr, u32 val); + // pirtable.c void create_pirtable(void);
Make ata helper functions available outside ata.c, so others (i.e. upcoming ahci support) can use them. Prefix them with ata_ to avoid name clashes. Also don't hard-code buffer size for the model name.
Signed-off-by: Gerd Hoffmann kraxel@redhat.com --- src/ata.c | 20 +++++++++++--------- src/ata.h | 2 ++ 2 files changed, 13 insertions(+), 9 deletions(-)
diff --git a/src/ata.c b/src/ata.c index e9a8df7..7079bf2 100644 --- a/src/ata.c +++ b/src/ata.c @@ -701,8 +701,8 @@ send_ata_identity(struct atadrive_s *adrive_g, u16 *buffer, int command) }
// Extract the ATA/ATAPI version info. -static int -extract_version(u16 *buffer) +int +ata_extract_version(u16 *buffer) { // Extract ATA/ATAPI version. u16 ataversion = buffer[80]; @@ -716,17 +716,17 @@ extract_version(u16 *buffer) #define MAXMODEL 40
// Extract the ATA/ATAPI model info. -static char * -extract_model(char *model, u16 *buffer) +char * +ata_extract_model(char *model, u32 size, u16 *buffer) { // Read model name int i; - for (i=0; i<MAXMODEL/2; i++) + for (i=0; i<size/2; i++) *(u16*)&model[i*2] = ntohs(buffer[27+i]); - model[MAXMODEL] = 0x00; + model[size] = 0x00;
// Trim trailing spaces from model name. - for (i=MAXMODEL-1; i>0 && model[i] == 0x20; i--) + for (i=size-1; i>0 && model[i] == 0x20; i--) model[i] = 0x00;
return model; @@ -773,7 +773,8 @@ init_drive_atapi(struct atadrive_s *dummy, u16 *buffer) char model[MAXMODEL+1]; snprintf(adrive_g->drive.desc, MAXDESCSIZE, "ata%d-%d: %s ATAPI-%d %s" , adrive_g->chan_gf->chanid, adrive_g->slave - , extract_model(model, buffer), extract_version(buffer) + , ata_extract_model(model, MAXMODEL, buffer) + , ata_extract_version(buffer) , (iscd ? "DVD/CD" : "Device")); dprintf(1, "%s\n", adrive_g->drive.desc);
@@ -820,7 +821,8 @@ init_drive_ata(struct atadrive_s *dummy, u16 *buffer) snprintf(adrive_g->drive.desc, MAXDESCSIZE , "ata%d-%d: %s ATA-%d Hard-Disk (%u %ciBytes)" , adrive_g->chan_gf->chanid, adrive_g->slave - , extract_model(model, buffer), extract_version(buffer) + , ata_extract_model(model, MAXMODEL, buffer) + , ata_extract_version(buffer) , (u32)adjsize, adjprefix); dprintf(1, "%s\n", adrive_g->drive.desc);
diff --git a/src/ata.h b/src/ata.h index 94f60ee..8fa2872 100644 --- a/src/ata.h +++ b/src/ata.h @@ -21,6 +21,8 @@ struct atadrive_s { };
// ata.c +char *ata_extract_model(char *model, u32 size, u16 *buffer); +int ata_extract_version(u16 *buffer); int cdrom_read(struct disk_op_s *op); int atapi_cmd_data(struct disk_op_s *op, void *cdbcmd, u16 blocksize); void ata_setup(void);
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.
Signed-off-by: Gerd Hoffmann kraxel@redhat.com --- Makefile | 2 +- src/ahci.c | 484 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ src/ahci.h | 196 +++++++++++++++++++++++ src/block.c | 3 + src/blockcmd.c | 3 + src/config.h | 2 + src/disk.h | 1 + src/post.c | 2 + 8 files changed, 692 insertions(+), 1 deletions(-) create mode 100644 src/ahci.c create mode 100644 src/ahci.h
diff --git a/Makefile b/Makefile index 1663a5d..acc2385 100644 --- 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 index 0000000..9633951 --- /dev/null +++ b/src/ahci.c @@ -0,0 +1,484 @@ +// 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 memset_fl(void *ptr, u8 val, size_t size) +{ +#if MODESEGMENT == 1 + memset_far(FLATPTR_TO_SEG(ptr), (void*)(FLATPTR_TO_OFFSET(ptr)), + val, size); +#else + memset(ptr, val, size); +#endif +} + +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_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.desc = malloc_tmp(MAXDESCSIZE); + port->drive.removable = (buffer[0] & 0x80) ? 1 : 0; + + 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 index 0000000..0e13e00 --- /dev/null +++ b/src/ahci.h @@ -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 diff --git a/src/block.c b/src/block.c index 3f4b13f..818c9f9 100644 --- a/src/block.c +++ b/src/block.c @@ -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; diff --git a/src/blockcmd.c b/src/blockcmd.c index 48568e6..c9c6845 100644 --- a/src/blockcmd.c +++ b/src/blockcmd.c @@ -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; diff --git a/src/config.h b/src/config.h index f9bf3b8..ea525b1 100644 --- a/src/config.h +++ b/src/config.h @@ -52,6 +52,8 @@ #define CONFIG_PS2PORT 1 // Support for IDE disk code #define CONFIG_ATA 1 +// Support for AHCI disk code +#define CONFIG_AHCI 1 // Detect and try to use ATA bus mastering DMA controllers. #define CONFIG_ATA_DMA 0 // Use 32bit PIO accesses on ATA (minor optimization on PCI transfers) diff --git a/src/disk.h b/src/disk.h index 55a5da3..c1ba6ae 100644 --- a/src/disk.h +++ b/src/disk.h @@ -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
diff --git a/src/post.c b/src/post.c index 1e35142..ff6813c 100644 --- a/src/post.c +++ b/src/post.c @@ -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(); }
On Thu, Nov 25, 2010 at 04:42:31PM +0100, Gerd Hoffmann wrote:
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.
Hi Gerd,
Looks good to me. I have a few comments below.
[...]
--- /dev/null +++ b/src/ahci.c
[...]
+// prepare sata command fis +static void memset_fl(void *ptr, u8 val, size_t size) +{ +#if MODESEGMENT == 1
- memset_far(FLATPTR_TO_SEG(ptr), (void*)(FLATPTR_TO_OFFSET(ptr)),
val, size);
+#else
- memset(ptr, val, size);
+#endif +}
This should move to util.c. Also, I'd prefer to avoid "#if" where possible.
[...]
+// command demuxer +int process_ahci_op(struct disk_op_s *op) +{
switch (op->command) {
case CMD_READ:
return ahci_disk_readwrite(op, 0);
case CMD_RESET:
[...]
Is CMD_WRITE purposefuly not supported? If so, I think it should return DISK_RET_EWRITEPROTECT.
[...]
- 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;
- }
[...]
- port->drive.desc = malloc_tmp(MAXDESCSIZE);
Should check for no memory on this malloc too.
[...]
--- a/src/config.h +++ b/src/config.h @@ -52,6 +52,8 @@ #define CONFIG_PS2PORT 1 // Support for IDE disk code #define CONFIG_ATA 1 +// Support for AHCI disk code +#define CONFIG_AHCI 1
I think this should default to off until there has been some additional feedback - two things are unknown - how this handles real hardware and how popular OSes handle real->protected switching.
Otherwise, looks good to me. I'm okay with committing.
-Kevin