@@ -187,6 +187,10 @@ obj-$(CONFIG_USB_OHCI) += usb-ohci.o
obj-y += rtl8139.o
obj-y += e1000.o
+# ahci
+#
+obj-$(CONFIG_AHCI) += ahci.o
+
# Hardware support
obj-i386-y = pckbd.o dma.o
obj-i386-y += vga.o
new file mode 100644
@@ -0,0 +1,805 @@
+/*
+ * QEMU AHCI Emulation
+ * Copyright (c) 2010 qiaochong@loongson.cn
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ *
+ * TODO:
+ * o ahci cd support
+ */
+#include "hw.h"
+#include "qemu-timer.h"
+#include "monitor.h"
+#include "sysbus.h"
+#include "pci.h"
+#include "dma.h"
+#include "cpu-common.h"
+#include <hw/ide/internal.h>
+#define DPRINTF(...)
+
+
+enum {
+ AHCI_PCI_BAR = 5,
+ AHCI_MAX_PORTS = 32,
+ AHCI_MAX_SG = 168, /* hardware max is 64K */
+ AHCI_DMA_BOUNDARY = 0xffffffff,
+ AHCI_USE_CLUSTERING = 0,
+ AHCI_MAX_CMDS = 32,
+ AHCI_CMD_SZ = 32,
+ AHCI_CMD_SLOT_SZ = AHCI_MAX_CMDS * AHCI_CMD_SZ,
+ AHCI_RX_FIS_SZ = 256,
+ AHCI_CMD_TBL_CDB = 0x40,
+ AHCI_CMD_TBL_HDR_SZ = 0x80,
+ AHCI_CMD_TBL_SZ = AHCI_CMD_TBL_HDR_SZ + (AHCI_MAX_SG * 16),
+ AHCI_CMD_TBL_AR_SZ = AHCI_CMD_TBL_SZ * AHCI_MAX_CMDS,
+ AHCI_PORT_PRIV_DMA_SZ = AHCI_CMD_SLOT_SZ + AHCI_CMD_TBL_AR_SZ +
+ AHCI_RX_FIS_SZ,
+ AHCI_IRQ_ON_SG = (1 << 31),
+ AHCI_CMD_ATAPI = (1 << 5),
+ AHCI_CMD_WRITE = (1 << 6),
+ AHCI_CMD_PREFETCH = (1 << 7),
+ AHCI_CMD_RESET = (1 << 8),
+ AHCI_CMD_CLR_BUSY = (1 << 10),
+
+ RX_FIS_D2H_REG = 0x40, /* offset of D2H Register FIS data */
+ RX_FIS_SDB = 0x58, /* offset of SDB FIS data */
+ RX_FIS_UNK = 0x60, /* offset of Unknown FIS data */
+
+ board_ahci = 0,
+ board_ahci_pi = 1,
+ board_ahci_vt8251 = 2,
+ board_ahci_ign_iferr = 3,
+ board_ahci_sb600 = 4,
+
+ /* global controller registers */
+ HOST_CAP = 0x00, /* host capabilities */
+ HOST_CTL = 0x04, /* global host control */
+ HOST_IRQ_STAT = 0x08, /* interrupt status */
+ HOST_PORTS_IMPL = 0x0c, /* bitmap of implemented ports */
+ HOST_VERSION = 0x10, /* AHCI spec. version compliancy */
+
+ /* HOST_CTL bits */
+ HOST_RESET = (1 << 0), /* reset controller; self-clear */
+ HOST_IRQ_EN = (1 << 1), /* global IRQ enable */
+ HOST_AHCI_EN = (1 << 31), /* AHCI enabled */
+
+ /* HOST_CAP bits */
+ HOST_CAP_SSC = (1 << 14), /* Slumber capable */
+ HOST_CAP_CLO = (1 << 24), /* Command List Override support */
+ HOST_CAP_SSS = (1 << 27), /* Staggered Spin-up */
+ HOST_CAP_NCQ = (1 << 30), /* Native Command Queueing */
+ HOST_CAP_64 = (1 << 31), /* PCI DAC (64-bit DMA) support */
+
+ /* registers for each SATA port */
+ PORT_LST_ADDR = 0x00, /* command list DMA addr */
+ PORT_LST_ADDR_HI = 0x04, /* command list DMA addr hi */
+ PORT_FIS_ADDR = 0x08, /* FIS rx buf addr */
+ PORT_FIS_ADDR_HI = 0x0c, /* FIS rx buf addr hi */
+ PORT_IRQ_STAT = 0x10, /* interrupt status */
+ PORT_IRQ_MASK = 0x14, /* interrupt enable/disable mask */
+ PORT_CMD = 0x18, /* port command */
+ PORT_TFDATA = 0x20, /* taskfile data */
+ PORT_SIG = 0x24, /* device TF signature */
+ PORT_CMD_ISSUE = 0x38, /* command issue */
+ PORT_SCR = 0x28, /* SATA phy register block */
+ PORT_SCR_STAT = 0x28, /* SATA phy register: SStatus */
+ PORT_SCR_CTL = 0x2c, /* SATA phy register: SControl */
+ PORT_SCR_ERR = 0x30, /* SATA phy register: SError */
+ PORT_SCR_ACT = 0x34, /* SATA phy register: SActive */
+
+ /* PORT_IRQ_{STAT,MASK} bits */
+ PORT_IRQ_COLD_PRES = (1 << 31), /* cold presence detect */
+ PORT_IRQ_TF_ERR = (1 << 30), /* task file error */
+ PORT_IRQ_HBUS_ERR = (1 << 29), /* host bus fatal error */
+ PORT_IRQ_HBUS_DATA_ERR = (1 << 28), /* host bus data error */
+ PORT_IRQ_IF_ERR = (1 << 27), /* interface fatal error */
+ PORT_IRQ_IF_NONFATAL = (1 << 26), /* interface non-fatal error */
+ PORT_IRQ_OVERFLOW = (1 << 24), /* xfer exhausted available S/G */
+ PORT_IRQ_BAD_PMP = (1 << 23), /* incorrect port multiplier */
+
+ PORT_IRQ_PHYRDY = (1 << 22), /* PhyRdy changed */
+ PORT_IRQ_DEV_ILCK = (1 << 7), /* device interlock */
+ PORT_IRQ_CONNECT = (1 << 6), /* port connect change status */
+ PORT_IRQ_SG_DONE = (1 << 5), /* descriptor processed */
+ PORT_IRQ_UNK_FIS = (1 << 4), /* unknown FIS rx'd */
+ PORT_IRQ_SDB_FIS = (1 << 3), /* Set Device Bits FIS rx'd */
+ PORT_IRQ_DMAS_FIS = (1 << 2), /* DMA Setup FIS rx'd */
+ PORT_IRQ_PIOS_FIS = (1 << 1), /* PIO Setup FIS rx'd */
+ PORT_IRQ_D2H_REG_FIS = (1 << 0), /* D2H Register FIS rx'd */
+
+ PORT_IRQ_FREEZE = PORT_IRQ_HBUS_ERR |
+ PORT_IRQ_IF_ERR |
+ PORT_IRQ_CONNECT |
+ PORT_IRQ_PHYRDY |
+ PORT_IRQ_UNK_FIS,
+ PORT_IRQ_ERROR = PORT_IRQ_FREEZE |
+ PORT_IRQ_TF_ERR |
+ PORT_IRQ_HBUS_DATA_ERR,
+ 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 */
+ PORT_CMD_ATAPI = (1 << 24), /* Device is ATAPI */
+ PORT_CMD_LIST_ON = (1 << 15), /* cmd list DMA engine running */
+ PORT_CMD_FIS_ON = (1 << 14), /* FIS DMA engine running */
+ PORT_CMD_FIS_RX = (1 << 4), /* Enable FIS receive DMA engine */
+ PORT_CMD_CLO = (1 << 3), /* Command list override */
+ PORT_CMD_POWER_ON = (1 << 2), /* Power up device */
+ PORT_CMD_SPIN_UP = (1 << 1), /* Spin up device */
+ PORT_CMD_START = (1 << 0), /* Enable port DMA engine */
+
+ PORT_CMD_ICC_MASK = (0xf << 28), /* i/f ICC state mask */
+ PORT_CMD_ICC_ACTIVE = (0x1 << 28), /* Put i/f in active state */
+ PORT_CMD_ICC_PARTIAL = (0x2 << 28), /* Put i/f in partial state */
+ PORT_CMD_ICC_SLUMBER = (0x6 << 28), /* Put i/f in slumber state */
+
+ /* ap->flags bits */
+ AHCI_FLAG_NO_NCQ = (1 << 24),
+ AHCI_FLAG_IGN_IRQ_IF_ERR = (1 << 25), /* ignore IRQ_IF_ERR */
+ AHCI_FLAG_HONOR_PI = (1 << 26), /* honor PORTS_IMPL */
+ AHCI_FLAG_IGN_SERR_INTERNAL = (1 << 27), /* ignore SERR_INTERNAL */
+ AHCI_FLAG_32BIT_ONLY = (1 << 28), /* force 32bit */
+};
+
+/*
+ * ATA Commands (only mandatory commands listed here)
+ */
+#define ATA_CMD_READ 0x20 /* Read Sectors (with retries) */
+#define ATA_CMD_READN 0x21 /* Read Sectors ( no retries) */
+#define ATA_CMD_WRITE 0x30 /* Write Sectores (with retries)*/
+#define ATA_CMD_WRITEN 0x31 /* Write Sectors ( no retries)*/
+#define ATA_CMD_VRFY 0x40 /* Read Verify (with retries) */
+#define ATA_CMD_VRFYN 0x41 /* Read verify ( no retries) */
+#define ATA_CMD_SEEK 0x70 /* Seek */
+#define ATA_CMD_DIAG 0x90 /* Execute Device Diagnostic */
+#define ATA_CMD_INIT 0x91 /* Initialize Device Parameters */
+#define ATA_CMD_RD_MULT 0xC4 /* Read Multiple */
+#define ATA_CMD_WR_MULT 0xC5 /* Write Multiple */
+#define ATA_CMD_SETMULT 0xC6 /* Set Multiple Mode */
+#define ATA_CMD_RD_DMA 0xC8 /* Read DMA (with retries) */
+#define ATA_CMD_RD_DMAN 0xC9 /* Read DMS ( no retries) */
+#define ATA_CMD_WR_DMA 0xCA /* Write DMA (with retries) */
+#define ATA_CMD_WR_DMAN 0xCB /* Write DMA ( no retires) */
+#define ATA_CMD_IDENT 0xEC /* Identify Device */
+#define ATA_CMD_SETF 0xEF /* Set Features */
+#define ATA_CMD_CHK_PWR 0xE5 /* Check Power Mode */
+
+#define ATA_CMD_READ_EXT 0x24 /* Read Sectors (with retries) with 48bit addressing */
+#define ATA_CMD_WRITE_EXT 0x34 /* Write Sectores (with retries) with 48bit addressing */
+#define ATA_CMD_VRFY_EXT 0x42 /* Read Verify (with retries) with 48bit addressing */
+
+/*
+ * ATAPI Commands
+ */
+#define ATAPI_CMD_IDENT 0xA1 /* Identify AT Atachment Packed Interface Device */
+#define ATAPI_CMD_PACKET 0xA0 /* Packed Command */
+
+
+#define ATAPI_CMD_INQUIRY 0x12
+#define ATAPI_CMD_REQ_SENSE 0x03
+#define ATAPI_CMD_READ_CAP 0x25
+#define ATAPI_CMD_START_STOP 0x1B
+#define ATAPI_CMD_READ_12 0xA8
+
+
+
+typedef struct ahci_control_regs {
+ uint32_t cap;
+ uint32_t ghc;
+ uint32_t irqstatus;
+ uint32_t impl;
+ uint32_t version;
+} ahci_control_regs;
+
+typedef struct ahci_port_regs {
+ uint32_t lst_addr;
+ uint32_t lst_addr_hi;
+ uint32_t fis_addr;
+ uint32_t fis_addr_hi;
+ uint32_t irq_stat;
+ uint32_t irq_mask;
+ uint32_t cmd;
+ uint32_t unused0;
+ uint32_t tfdata;
+ uint32_t sig;
+ uint32_t scr_stat;
+ uint32_t scr_ctl;
+ uint32_t scr_err;
+ uint32_t scr_act;
+ uint32_t cmd_issue;
+} ahci_port_regs;
+
+
+typedef struct ahci_cmd_hdr {
+ uint32_t opts;
+ uint32_t status;
+ uint32_t tbl_addr;
+ uint32_t tbl_addr_hi;
+ uint32_t reserved[4];
+} ahci_cmd_hdr;
+
+typedef struct ahci_sg {
+ uint32_t addr;
+ uint32_t addr_hi;
+ uint32_t reserved;
+ uint32_t flags_size;
+} ahci_sg;
+
+
+typedef struct AHCIState{
+ ahci_control_regs control_regs;
+ ahci_port_regs port_regs[2];
+ int mem;
+ QEMUTimer *timer;
+ IDEBus *ide;
+ ahci_sg *prdt_buf;
+ qemu_irq irq;
+} AHCIState;
+
+typedef struct ahci_pci_state {
+ PCIDevice card;
+ AHCIState *ahci;
+} ahci_pci_state;
+
+typedef struct ahci_sysbus_state {
+ SysBusDevice busdev;
+ AHCIState *ahci;
+} ahci_sysbus_state;
+
+static uint32_t ahci_port_read(AHCIState *s,int port,int offset)
+{
+ uint32_t val;
+ uint32_t *p;
+ ahci_port_regs *pr;
+ pr=&s->port_regs[port];
+
+ switch(offset)
+ {
+ case PORT_SCR:
+ if(s->ide && port==0) val=3;
+ else val=0;
+ break;
+ case PORT_IRQ_STAT:
+ val=pr->irq_stat;
+ break;
+ case PORT_TFDATA:
+
+ case PORT_SIG:
+
+ case PORT_CMD_ISSUE:
+
+
+ case PORT_SCR_CTL:
+
+ case PORT_SCR_ERR:
+
+ case PORT_SCR_ACT:
+ default:
+ p=(uint32_t *)&s->port_regs[port];
+ val= p[offset>>2];
+ break;
+ }
+ return val;
+
+}
+
+static void ahci_check_irq(AHCIState *s)
+{
+ ahci_port_regs *pr;
+ int i;
+ for(i=0;i<2;i++)
+ {
+ pr=&s->port_regs[i];
+
+ if(pr->irq_stat&pr->irq_mask){
+ s->control_regs.irqstatus |= (1<<i);
+ }
+ }
+
+ if(s->control_regs.irqstatus)
+ qemu_irq_raise(s->irq);
+ else qemu_irq_lower(s->irq);
+}
+
+
+static void ahci_port_write(AHCIState *s,int port,int offset,uint32_t val)
+{
+ ahci_port_regs *pr=&s->port_regs[port];
+ uint32_t *p;
+
+ switch(offset)
+ {
+ case PORT_LST_ADDR:
+ pr->lst_addr=val; break;
+
+ case PORT_LST_ADDR_HI:
+ pr->lst_addr_hi=val; break;
+
+ case PORT_FIS_ADDR:
+ pr->fis_addr = val; break;
+
+ case PORT_FIS_ADDR_HI:
+ pr->fis_addr_hi = val; break;
+
+ case PORT_IRQ_STAT:
+ pr->irq_stat &= ~val;
+ ahci_check_irq(s);
+ break;
+
+ case PORT_IRQ_MASK:
+ pr->irq_mask = val;
+ ahci_check_irq(s);
+ break;
+
+ case PORT_CMD:
+ pr->cmd=val&(PORT_CMD_ATAPI|PORT_CMD_LIST_ON|PORT_CMD_FIS_ON|PORT_CMD_FIS_RX|PORT_CMD_CLO|PORT_CMD_POWER_ON|PORT_CMD_SPIN_UP|PORT_CMD_START);
+ if(pr->cmd&PORT_CMD_START)
+ qemu_mod_timer(s->timer,qemu_get_clock(vm_clock)+muldiv64(1, get_ticks_per_sec(), 1000));
+ break;
+
+
+ case PORT_CMD_ISSUE:
+ pr->cmd_issue=val;
+ if(pr->cmd&PORT_CMD_START)
+ qemu_mod_timer(s->timer,qemu_get_clock(vm_clock)+muldiv64(1, get_ticks_per_sec(), 1000));
+ break;
+
+ case PORT_TFDATA:
+
+ case PORT_SIG:
+
+
+ case PORT_SCR:
+
+ case PORT_SCR_CTL:
+
+ case PORT_SCR_ERR:
+
+ case PORT_SCR_ACT:
+
+
+ default:
+ p=(uint32_t *)pr;
+ p[offset>>2]=val;
+ break;
+ }
+
+}
+
+static uint32_t ahci_mem_readl(void *ptr, target_phys_addr_t addr)
+{
+ AHCIState *s = ptr;
+ uint32_t val;
+ uint32_t *p;
+ addr=addr&0xfff;
+ if(addr<0x20)
+ {
+ switch(addr)
+ {
+ case HOST_IRQ_STAT:
+
+ default:
+ /* genernal host control */
+ p=(uint32_t *)&s->control_regs;
+ val=p[addr>>2];
+ }
+ }
+ else if(addr>=0x100 && addr<0x200)
+ {
+ val=ahci_port_read(s,(addr-0x100)>>7,addr&0x7f);
+ }
+ else val=0;
+
+
+ DPRINTF("ahci_mem_readl: (addr 0x%08X), val 0x%08X\n", (unsigned) addr, val);
+
+ return val;
+}
+
+
+
+static void ahci_mem_writel(void *ptr, target_phys_addr_t addr, uint32_t val)
+{
+ AHCIState *s = ptr;
+ uint32_t *p;
+ addr=addr&0xfff;
+
+ /* Only aligned reads are allowed on OHCI */
+ if (addr & 3) {
+ fprintf(stderr, "ahci: Mis-aligned write to addr 0x"
+ TARGET_FMT_plx "\n", addr);
+ return;
+ }
+
+ if(addr<0x20)
+ {
+ switch(addr)
+ {
+ case HOST_IRQ_STAT:
+ s->control_regs.irqstatus &= ~val;
+ ahci_check_irq(s);
+ break;
+ default:
+ /* genernal host control */
+ p=(uint32_t *)&s->control_regs;
+ }
+ }
+ else if(addr>=0x100 && addr<0x200)
+ {
+ ahci_port_write(s,(addr-0x100)>>7,addr&0x7f,val);
+ }
+
+ DPRINTF("ahci_mem_writel: (addr 0x%08X), val 0x%08X\n", (unsigned) addr, val);
+
+}
+
+static CPUReadMemoryFunc *ahci_readfn[3]={
+ ahci_mem_readl,
+ ahci_mem_readl,
+ ahci_mem_readl
+};
+
+static CPUWriteMemoryFunc *ahci_writefn[3]={
+ ahci_mem_writel,
+ ahci_mem_writel,
+ ahci_mem_writel
+};
+
+static void ahci_reg_init(AHCIState *s)
+{
+ s->control_regs.cap=2|(0x1f<<8); /*2 ports,32 cmd slot*/
+ s->control_regs.ghc=1<<31;
+ s->control_regs.impl=1;/*2 ports*/
+ s->control_regs.version=0x10100;
+}
+
+static void padstr(char *str, const char *src, int len)
+{
+ int i, v;
+ for(i = 0; i < len; i++) {
+ if (*src)
+ v = *src++;
+ else
+ v = ' ';
+ str[i^1] = v;
+ }
+}
+
+
+static void put_le16(uint16_t *p, unsigned int v)
+{
+ *p = cpu_to_le16(v);
+}
+
+
+static void ide_identify(IDEState *s)
+{
+ uint16_t *p;
+ unsigned int oldsize;
+
+ if (s->identify_set) {
+ memcpy(s->io_buffer, s->identify_data, sizeof(s->identify_data));
+ return;
+ }
+
+ memset(s->io_buffer, 0, 512);
+ p = (uint16_t *)s->io_buffer;
+ put_le16(p + 0, 0x0040);
+ put_le16(p + 1, s->cylinders);
+ put_le16(p + 3, s->heads);
+ put_le16(p + 4, 512 * s->sectors); /* XXX: retired, remove ? */
+ put_le16(p + 5, 512); /* XXX: retired, remove ? */
+ put_le16(p + 6, s->sectors);
+ padstr((char *)(p + 10), s->drive_serial_str, 20); /* serial number */
+ put_le16(p + 20, 3); /* XXX: retired, remove ? */
+ put_le16(p + 21, 512); /* cache size in sectors */
+ put_le16(p + 22, 4); /* ecc bytes */
+ padstr((char *)(p + 23), s->version, 8); /* firmware version */
+ padstr((char *)(p + 27), "QEMU HARDDISK", 40); /* model */
+#if MAX_MULT_SECTORS > 1
+ put_le16(p + 47, 0x8000 | MAX_MULT_SECTORS);
+#endif
+ put_le16(p + 48, 1); /* dword I/O */
+ put_le16(p + 49, (1 << 11) | (1 << 9) | (1 << 8)); /* DMA and LBA supported */
+ put_le16(p + 51, 0x200); /* PIO transfer cycle */
+ put_le16(p + 52, 0x200); /* DMA transfer cycle */
+ put_le16(p + 53, 1 | (1 << 1) | (1 << 2)); /* words 54-58,64-70,88 are valid */
+ put_le16(p + 54, s->cylinders);
+ put_le16(p + 55, s->heads);
+ put_le16(p + 56, s->sectors);
+ oldsize = s->cylinders * s->heads * s->sectors;
+ put_le16(p + 57, oldsize);
+ put_le16(p + 58, oldsize >> 16);
+ if (s->mult_sectors)
+ put_le16(p + 59, 0x100 | s->mult_sectors);
+ put_le16(p + 60, s->nb_sectors);
+ put_le16(p + 61, s->nb_sectors >> 16);
+ put_le16(p + 62, 0x07); /* single word dma0-2 supported */
+ put_le16(p + 63, 0x07); /* mdma0-2 supported */
+ put_le16(p + 65, 120);
+ put_le16(p + 66, 120);
+ put_le16(p + 67, 120);
+ put_le16(p + 68, 120);
+ put_le16(p + 80, 0xf0); /* ata3 -> ata6 supported */
+ put_le16(p + 81, 0x16); /* conforms to ata5 */
+ /* 14=NOP supported, 0=SMART supported */
+ put_le16(p + 82, (1 << 14) | 1);
+ /* 13=flush_cache_ext,12=flush_cache,10=lba48 */
+ put_le16(p + 83, (1 << 14) | (1 << 13) | (1 <<12) | (1 << 10));
+ /* 14=set to 1, 1=SMART self test, 0=SMART error logging */
+ put_le16(p + 84, (1 << 14) | 0);
+ /* 14 = NOP supported, 5=WCACHE enabled, 0=SMART feature set enabled */
+ if (bdrv_enable_write_cache(s->bs))
+ put_le16(p + 85, (1 << 14) | (1 << 5) | 1);
+ else
+ put_le16(p + 85, (1 << 14) | 1);
+ /* 13=flush_cache_ext,12=flush_cache,10=lba48 */
+ put_le16(p + 86, (1 << 14) | (1 << 13) | (1 <<12) | (1 << 10));
+ /* 14=set to 1, 1=smart self test, 0=smart error logging */
+ put_le16(p + 87, (1 << 14) | 0);
+ put_le16(p + 88, 0x3f | (1 << 13)); /* udma5 set and supported */
+ put_le16(p + 93, 1 | (1 << 14) | 0x2000);
+ put_le16(p + 100, s->nb_sectors);
+ put_le16(p + 101, s->nb_sectors >> 16);
+ put_le16(p + 102, s->nb_sectors >> 32);
+ put_le16(p + 103, s->nb_sectors >> 48);
+
+ memcpy(s->identify_data, p, sizeof(s->identify_data));
+ s->identify_set = 1;
+}
+
+#define min(a,b) (((a)<(b))?(a):(b))
+
+static uint32_t write_to_sglist(uint8_t *buffer,uint32_t len,ahci_sg *sglist,uint32_t sgcount)
+{
+ uint32_t i=0;
+ uint32_t total=0,once;
+ for(i=0;len&&sgcount;i++)
+ {
+ once=min(sglist->flags_size+1,len);
+ cpu_physical_memory_write(sglist->addr,buffer,once);
+ sglist++;
+ sgcount--;
+ len -= once;
+ buffer += once;
+ total += once;
+ }
+
+ return total;
+}
+
+static uint32_t read_from_sglist(uint8_t *buffer,uint32_t len,ahci_sg *sglist,uint32_t sgcount)
+{
+ uint32_t i=0;
+ uint32_t total=0,once;
+ for(i=0;len&&sgcount;i++)
+ {
+ once=min(sglist->flags_size+1,len);
+ cpu_physical_memory_read(sglist->addr,buffer,once);
+ sglist++;
+ sgcount--;
+ len -= once;
+ buffer += once;
+ total += once;
+ }
+
+ return total;
+}
+
+static void handle_cmd(AHCIState *s,int port,int slot)
+{
+ int64_t sector_num;
+ int nb_sectors;
+ IDEState *ide_state;
+ int ret;
+ int cmdaddr;
+ uint8_t fis[0x80];
+ int cmd_len;
+ int prdt_num;
+ int i;
+ ahci_port_regs *pr;
+ ahci_cmd_hdr cmd_hdr;
+ pr=&s->port_regs[port];
+ cmdaddr=pr->lst_addr+slot*32;
+ cpu_physical_memory_read(cmdaddr,(uint8_t *)&cmd_hdr,16);
+ cmd_len=(cmd_hdr.opts&0x1f)*4;
+ cpu_physical_memory_read(cmd_hdr.tbl_addr,fis,cmd_len);
+ prdt_num=cmd_hdr.opts>>16;
+ if(prdt_num) cpu_physical_memory_read(cmd_hdr.tbl_addr+0x80,(uint8_t *)s->prdt_buf,prdt_num*32);
+
+
+ for(i=0;i<cmd_len;i++)
+ {
+ if((i&0xf)==0)DPRINTF("\n%02x:",i);
+ DPRINTF("%02x ",fis[i]);
+ }
+
+ switch(fis[0])
+ {
+ case 0x27:
+ break;
+ default:
+ hw_error("unkonow command fis[0]=%02x fis[1]=%02x fis[2]=%02x\n",fis[0],fis[1],fis[2]);break;
+ }
+
+ switch(fis[1])
+ {
+ case 1<<7: /* cmd fis */
+ break;
+ case 0:
+ break;
+ default:
+ hw_error("unkonow command fis[0]=%02x fis[1]=%02x fis[2]=%02x\n",fis[0],fis[1],fis[2]);break;
+ }
+
+ if(fis[1]==0)
+ {
+
+ }
+
+ if(fis[1]==(1<<7))
+ {
+ if(!s->ide)hw_error("no ahci sata disk now\n");
+ ide_state=&s->ide->ifs[0];
+ switch(fis[2])
+ {
+ case ATA_CMD_IDENT:
+ ide_identify(ide_state);
+ write_to_sglist(ide_state->identify_data, sizeof(ide_state->identify_data),s->prdt_buf,prdt_num);
+ pr->irq_stat |= (1<<2);
+ break;
+ case WIN_SETFEATURES:
+ pr->irq_stat |= (1<<2);
+ break;
+ case ATA_CMD_RD_DMA:
+ sector_num=(((int64_t)fis[10])<<40)|(((int64_t)fis[9])<<32)|(fis[8]<<24)|(fis[6]<<16)|(fis[5]<<8)|fis[4];
+ nb_sectors=(fis[13]<<8)|fis[12];
+ if(!nb_sectors)nb_sectors=256;
+ printf("nb_sectors=%x,prdt_num=%x\n",nb_sectors,prdt_num);
+ ret = bdrv_read(ide_state->bs, sector_num, ide_state->io_buffer, nb_sectors);
+ if(ret==0)
+ {
+ write_to_sglist(ide_state->io_buffer,nb_sectors*512,s->prdt_buf,prdt_num);
+ }
+ pr->irq_stat |= (1<<2);
+ break;
+ case ATA_CMD_WR_DMA:
+ sector_num=(((int64_t)fis[10])<<40)|(((int64_t)fis[9])<<32)|(fis[8]<<24)|(fis[6]<<16)|(fis[5]<<8)|fis[4];
+ nb_sectors=(fis[13]<<8)|fis[12];
+ if(!nb_sectors)nb_sectors=256;
+ read_from_sglist(ide_state->io_buffer,nb_sectors*512,s->prdt_buf,prdt_num);
+ ret = bdrv_write(ide_state->bs, sector_num, ide_state->io_buffer, nb_sectors);
+ pr->irq_stat |= (1<<2);
+ break;
+ default:
+ hw_error("unkonow command fis[0]=%02x fis[1]=%02x fis[2]=%02x\n",fis[0],fis[1],fis[2]);break;
+ }
+
+ }
+
+ pr->cmd_issue &=~(1<<slot);
+ ahci_check_irq(s);
+}
+
+static void ahci_timer_function(void *opaque)
+{
+ AHCIState *s = opaque;
+ ahci_port_regs *pr;
+ int i,j;
+ for(i=0;i<2;i++)
+ {
+ pr=&s->port_regs[i];
+ for(j=0;j<32 && pr->cmd_issue;j++)
+ {
+ if(pr->cmd_issue&(1<<j))
+ {
+ handle_cmd(s,i,j);
+ }
+ }
+ }
+}
+
+static AHCIState *ahci_new(void)
+{
+ DriveInfo *dinfo;
+ IDEBus *bus = qemu_mallocz(sizeof(IDEBus));
+ AHCIState *s = qemu_mallocz(sizeof(AHCIState));
+ ahci_reg_init(s);
+ s->mem = cpu_register_io_memory(ahci_readfn, ahci_writefn, s);
+ s->timer = qemu_new_timer(vm_clock, ahci_timer_function, s);
+ s->prdt_buf = qemu_malloc(65535*32);
+
+ if ((dinfo = drive_get(IF_SD, 0, 0)) != NULL)
+ {
+ ide_init2(bus, dinfo, NULL,0);
+ s->ide=bus;
+ }
+ return s;
+}
+
+static void ahci_pci_map(PCIDevice *pci_dev, int region_num,
+ pcibus_t addr, pcibus_t size, int type)
+{
+ struct ahci_pci_state *d = (struct ahci_pci_state *)pci_dev;
+ AHCIState *s = d->ahci;
+
+ cpu_register_physical_memory(addr, size, s->mem);
+}
+
+#define PCI_VENDOR_MYDEVICE 0x8086
+#define PCI_PRODUCT_MYDEVICE 0x2652
+
+#define PCI_CLASS_HEADERTYPE_00h 0x00
+
+static int pci_ahci_init(PCIDevice *dev)
+{
+ struct ahci_pci_state *d;
+ d = DO_UPCAST(struct ahci_pci_state, card, dev);
+ pci_config_set_vendor_id(d->card.config,PCI_VENDOR_MYDEVICE);
+ pci_config_set_device_id(d->card.config,PCI_PRODUCT_MYDEVICE);
+ d->card.config[PCI_COMMAND] = 0x07; /* I/O + Memory */
+ d->card.config[PCI_CLASS_DEVICE] = 0;
+ d->card.config[0x0b] = 1;//storage
+ d->card.config[0x0c] = 0x08; /* Cache line size */
+ d->card.config[0x0d] = 0x40; /* Latency timer */
+ d->card.config[0x0e] = PCI_CLASS_HEADERTYPE_00h;
+ d->card.config[0x3d] = 1; /* interrupt pin 0 */
+
+ pci_register_bar(&d->card, 5, 0x200,
+ PCI_BASE_ADDRESS_SPACE_MEMORY, ahci_pci_map);
+ d->ahci=ahci_new();
+ d->ahci->irq = d->card.irq[0];
+ return 0;
+}
+
+static int pci_ahci_uninit(PCIDevice *dev)
+{
+ return 0;
+}
+
+static PCIDeviceInfo ahci_info = {
+ .qdev.name = "ahci",
+ .qdev.size = sizeof(ahci_pci_state),
+ .init = pci_ahci_init,
+ .exit = pci_ahci_uninit,
+ .qdev.props = (Property[]) {
+ DEFINE_PROP_END_OF_LIST(),
+ }
+};
+
+static void ahci_pci_register_devices(void)
+{
+ pci_qdev_register(&ahci_info);
+}
+
+device_init(ahci_pci_register_devices)
+
+
+
+static int ahci_sysbus_init(SysBusDevice *dev)
+{
+ ahci_sysbus_state *d = FROM_SYSBUS(ahci_sysbus_state, dev);
+ d->ahci=ahci_new();
+ sysbus_init_mmio(dev, 0x200, d->ahci->mem);
+ sysbus_init_irq(dev, &d->ahci->irq);
+ return 0;
+}
+
+static void ahci_sysbus_register_devices(void)
+{
+ sysbus_register_dev("ahci", sizeof(ahci_sysbus_state), ahci_sysbus_init);
+}
+
+device_init(ahci_sysbus_register_devices)
use -drive if=sd,file=diskname to add a ahci disk into qemu. Signed-off-by: QiaoChong <qiaochong@loongson.cn> --- Makefile.target | 4 + hw/ahci.c | 805 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 809 insertions(+), 0 deletions(-) create mode 100644 hw/ahci.c