12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262 |
- /*
- * QEMU AHCI Emulation
- *
- * Copyright (c) 2010 qiaochong@loongson.cn
- * Copyright (c) 2010 Roland Elek <elek.roland@gmail.com>
- * Copyright (c) 2010 Sebastian Herbszt <herbszt@gmx.de>
- * Copyright (c) 2010 Alexander Graf <agraf@suse.de>
- *
- * 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/>.
- *
- */
- #include <hw/hw.h>
- #include <hw/pci/msi.h>
- #include <hw/pc.h>
- #include <hw/pci/pci.h>
- #include <hw/sysbus.h>
- #include "monitor.h"
- #include "dma.h"
- #include "cpu-common.h"
- #include "internal.h"
- #include <hw/ide/pci.h>
- #include <hw/ide/ahci.h>
- /* #define DEBUG_AHCI */
- #ifdef DEBUG_AHCI
- #define DPRINTF(port, fmt, ...) \
- do { fprintf(stderr, "ahci: %s: [%d] ", __FUNCTION__, port); \
- fprintf(stderr, fmt, ## __VA_ARGS__); } while (0)
- #else
- #define DPRINTF(port, fmt, ...) do {} while(0)
- #endif
- static void check_cmd(AHCIState *s, int port);
- static int handle_cmd(AHCIState *s,int port,int slot);
- static void ahci_reset_port(AHCIState *s, int port);
- static void ahci_write_fis_d2h(AHCIDevice *ad, uint8_t *cmd_fis);
- static void ahci_init_d2h(AHCIDevice *ad);
- static uint32_t ahci_port_read(AHCIState *s, int port, int offset)
- {
- uint32_t val;
- AHCIPortRegs *pr;
- pr = &s->dev[port].port_regs;
- switch (offset) {
- case PORT_LST_ADDR:
- val = pr->lst_addr;
- break;
- case PORT_LST_ADDR_HI:
- val = pr->lst_addr_hi;
- break;
- case PORT_FIS_ADDR:
- val = pr->fis_addr;
- break;
- case PORT_FIS_ADDR_HI:
- val = pr->fis_addr_hi;
- break;
- case PORT_IRQ_STAT:
- val = pr->irq_stat;
- break;
- case PORT_IRQ_MASK:
- val = pr->irq_mask;
- break;
- case PORT_CMD:
- val = pr->cmd;
- break;
- case PORT_TFDATA:
- val = ((uint16_t)s->dev[port].port.ifs[0].error << 8) |
- s->dev[port].port.ifs[0].status;
- break;
- case PORT_SIG:
- val = pr->sig;
- break;
- case PORT_SCR_STAT:
- if (s->dev[port].port.ifs[0].bs) {
- val = SATA_SCR_SSTATUS_DET_DEV_PRESENT_PHY_UP |
- SATA_SCR_SSTATUS_SPD_GEN1 | SATA_SCR_SSTATUS_IPM_ACTIVE;
- } else {
- val = SATA_SCR_SSTATUS_DET_NODEV;
- }
- break;
- case PORT_SCR_CTL:
- val = pr->scr_ctl;
- break;
- case PORT_SCR_ERR:
- val = pr->scr_err;
- break;
- case PORT_SCR_ACT:
- pr->scr_act &= ~s->dev[port].finished;
- s->dev[port].finished = 0;
- val = pr->scr_act;
- break;
- case PORT_CMD_ISSUE:
- val = pr->cmd_issue;
- break;
- case PORT_RESERVED:
- default:
- val = 0;
- }
- DPRINTF(port, "offset: 0x%x val: 0x%x\n", offset, val);
- return val;
- }
- static void ahci_irq_raise(AHCIState *s, AHCIDevice *dev)
- {
- struct AHCIPCIState *d = container_of(s, AHCIPCIState, ahci);
- DPRINTF(0, "raise irq\n");
- if (msi_enabled(&d->card)) {
- msi_notify(&d->card, 0);
- } else {
- qemu_irq_raise(s->irq);
- }
- }
- static void ahci_irq_lower(AHCIState *s, AHCIDevice *dev)
- {
- struct AHCIPCIState *d = container_of(s, AHCIPCIState, ahci);
- DPRINTF(0, "lower irq\n");
- if (!msi_enabled(&d->card)) {
- qemu_irq_lower(s->irq);
- }
- }
- static void ahci_check_irq(AHCIState *s)
- {
- int i;
- DPRINTF(-1, "check irq %#x\n", s->control_regs.irqstatus);
- s->control_regs.irqstatus = 0;
- for (i = 0; i < s->ports; i++) {
- AHCIPortRegs *pr = &s->dev[i].port_regs;
- if (pr->irq_stat & pr->irq_mask) {
- s->control_regs.irqstatus |= (1 << i);
- }
- }
- if (s->control_regs.irqstatus &&
- (s->control_regs.ghc & HOST_CTL_IRQ_EN)) {
- ahci_irq_raise(s, NULL);
- } else {
- ahci_irq_lower(s, NULL);
- }
- }
- static void ahci_trigger_irq(AHCIState *s, AHCIDevice *d,
- int irq_type)
- {
- DPRINTF(d->port_no, "trigger irq %#x -> %x\n",
- irq_type, d->port_regs.irq_mask & irq_type);
- d->port_regs.irq_stat |= irq_type;
- ahci_check_irq(s);
- }
- static void map_page(uint8_t **ptr, uint64_t addr, uint32_t wanted)
- {
- hwaddr len = wanted;
- if (*ptr) {
- cpu_physical_memory_unmap(*ptr, len, 1, len);
- }
- *ptr = cpu_physical_memory_map(addr, &len, 1);
- if (len < wanted) {
- cpu_physical_memory_unmap(*ptr, len, 1, len);
- *ptr = NULL;
- }
- }
- static void ahci_port_write(AHCIState *s, int port, int offset, uint32_t val)
- {
- AHCIPortRegs *pr = &s->dev[port].port_regs;
- DPRINTF(port, "offset: 0x%x val: 0x%x\n", offset, val);
- switch (offset) {
- case PORT_LST_ADDR:
- pr->lst_addr = val;
- map_page(&s->dev[port].lst,
- ((uint64_t)pr->lst_addr_hi << 32) | pr->lst_addr, 1024);
- s->dev[port].cur_cmd = NULL;
- break;
- case PORT_LST_ADDR_HI:
- pr->lst_addr_hi = val;
- map_page(&s->dev[port].lst,
- ((uint64_t)pr->lst_addr_hi << 32) | pr->lst_addr, 1024);
- s->dev[port].cur_cmd = NULL;
- break;
- case PORT_FIS_ADDR:
- pr->fis_addr = val;
- map_page(&s->dev[port].res_fis,
- ((uint64_t)pr->fis_addr_hi << 32) | pr->fis_addr, 256);
- break;
- case PORT_FIS_ADDR_HI:
- pr->fis_addr_hi = val;
- map_page(&s->dev[port].res_fis,
- ((uint64_t)pr->fis_addr_hi << 32) | pr->fis_addr, 256);
- break;
- case PORT_IRQ_STAT:
- pr->irq_stat &= ~val;
- ahci_check_irq(s);
- break;
- case PORT_IRQ_MASK:
- pr->irq_mask = val & 0xfdc000ff;
- ahci_check_irq(s);
- break;
- case PORT_CMD:
- pr->cmd = val & ~(PORT_CMD_LIST_ON | PORT_CMD_FIS_ON);
- if (pr->cmd & PORT_CMD_START) {
- pr->cmd |= PORT_CMD_LIST_ON;
- }
- if (pr->cmd & PORT_CMD_FIS_RX) {
- pr->cmd |= PORT_CMD_FIS_ON;
- }
- /* XXX usually the FIS would be pending on the bus here and
- issuing deferred until the OS enables FIS receival.
- Instead, we only submit it once - which works in most
- cases, but is a hack. */
- if ((pr->cmd & PORT_CMD_FIS_ON) &&
- !s->dev[port].init_d2h_sent) {
- ahci_init_d2h(&s->dev[port]);
- s->dev[port].init_d2h_sent = 1;
- }
- check_cmd(s, port);
- break;
- case PORT_TFDATA:
- s->dev[port].port.ifs[0].error = (val >> 8) & 0xff;
- s->dev[port].port.ifs[0].status = val & 0xff;
- break;
- case PORT_SIG:
- pr->sig = val;
- break;
- case PORT_SCR_STAT:
- pr->scr_stat = val;
- break;
- case PORT_SCR_CTL:
- if (((pr->scr_ctl & AHCI_SCR_SCTL_DET) == 1) &&
- ((val & AHCI_SCR_SCTL_DET) == 0)) {
- ahci_reset_port(s, port);
- }
- pr->scr_ctl = val;
- break;
- case PORT_SCR_ERR:
- pr->scr_err &= ~val;
- break;
- case PORT_SCR_ACT:
- /* RW1 */
- pr->scr_act |= val;
- break;
- case PORT_CMD_ISSUE:
- pr->cmd_issue |= val;
- check_cmd(s, port);
- break;
- default:
- break;
- }
- }
- static uint64_t ahci_mem_read(void *opaque, hwaddr addr,
- unsigned size)
- {
- AHCIState *s = opaque;
- uint32_t val = 0;
- if (addr < AHCI_GENERIC_HOST_CONTROL_REGS_MAX_ADDR) {
- switch (addr) {
- case HOST_CAP:
- val = s->control_regs.cap;
- break;
- case HOST_CTL:
- val = s->control_regs.ghc;
- break;
- case HOST_IRQ_STAT:
- val = s->control_regs.irqstatus;
- break;
- case HOST_PORTS_IMPL:
- val = s->control_regs.impl;
- break;
- case HOST_VERSION:
- val = s->control_regs.version;
- break;
- }
- DPRINTF(-1, "(addr 0x%08X), val 0x%08X\n", (unsigned) addr, val);
- } else if ((addr >= AHCI_PORT_REGS_START_ADDR) &&
- (addr < (AHCI_PORT_REGS_START_ADDR +
- (s->ports * AHCI_PORT_ADDR_OFFSET_LEN)))) {
- val = ahci_port_read(s, (addr - AHCI_PORT_REGS_START_ADDR) >> 7,
- addr & AHCI_PORT_ADDR_OFFSET_MASK);
- }
- return val;
- }
- static void ahci_mem_write(void *opaque, hwaddr addr,
- uint64_t val, unsigned size)
- {
- AHCIState *s = opaque;
- /* Only aligned reads are allowed on AHCI */
- if (addr & 3) {
- fprintf(stderr, "ahci: Mis-aligned write to addr 0x"
- TARGET_FMT_plx "\n", addr);
- return;
- }
- if (addr < AHCI_GENERIC_HOST_CONTROL_REGS_MAX_ADDR) {
- DPRINTF(-1, "(addr 0x%08X), val 0x%08"PRIX64"\n", (unsigned) addr, val);
- switch (addr) {
- case HOST_CAP: /* R/WO, RO */
- /* FIXME handle R/WO */
- break;
- case HOST_CTL: /* R/W */
- if (val & HOST_CTL_RESET) {
- DPRINTF(-1, "HBA Reset\n");
- ahci_reset(s);
- } else {
- s->control_regs.ghc = (val & 0x3) | HOST_CTL_AHCI_EN;
- ahci_check_irq(s);
- }
- break;
- case HOST_IRQ_STAT: /* R/WC, RO */
- s->control_regs.irqstatus &= ~val;
- ahci_check_irq(s);
- break;
- case HOST_PORTS_IMPL: /* R/WO, RO */
- /* FIXME handle R/WO */
- break;
- case HOST_VERSION: /* RO */
- /* FIXME report write? */
- break;
- default:
- DPRINTF(-1, "write to unknown register 0x%x\n", (unsigned)addr);
- }
- } else if ((addr >= AHCI_PORT_REGS_START_ADDR) &&
- (addr < (AHCI_PORT_REGS_START_ADDR +
- (s->ports * AHCI_PORT_ADDR_OFFSET_LEN)))) {
- ahci_port_write(s, (addr - AHCI_PORT_REGS_START_ADDR) >> 7,
- addr & AHCI_PORT_ADDR_OFFSET_MASK, val);
- }
- }
- static const MemoryRegionOps ahci_mem_ops = {
- .read = ahci_mem_read,
- .write = ahci_mem_write,
- .endianness = DEVICE_LITTLE_ENDIAN,
- };
- static uint64_t ahci_idp_read(void *opaque, hwaddr addr,
- unsigned size)
- {
- AHCIState *s = opaque;
- if (addr == s->idp_offset) {
- /* index register */
- return s->idp_index;
- } else if (addr == s->idp_offset + 4) {
- /* data register - do memory read at location selected by index */
- return ahci_mem_read(opaque, s->idp_index, size);
- } else {
- return 0;
- }
- }
- static void ahci_idp_write(void *opaque, hwaddr addr,
- uint64_t val, unsigned size)
- {
- AHCIState *s = opaque;
- if (addr == s->idp_offset) {
- /* index register - mask off reserved bits */
- s->idp_index = (uint32_t)val & ((AHCI_MEM_BAR_SIZE - 1) & ~3);
- } else if (addr == s->idp_offset + 4) {
- /* data register - do memory write at location selected by index */
- ahci_mem_write(opaque, s->idp_index, val, size);
- }
- }
- static const MemoryRegionOps ahci_idp_ops = {
- .read = ahci_idp_read,
- .write = ahci_idp_write,
- .endianness = DEVICE_LITTLE_ENDIAN,
- };
- static void ahci_reg_init(AHCIState *s)
- {
- int i;
- s->control_regs.cap = (s->ports - 1) |
- (AHCI_NUM_COMMAND_SLOTS << 8) |
- (AHCI_SUPPORTED_SPEED_GEN1 << AHCI_SUPPORTED_SPEED) |
- HOST_CAP_NCQ | HOST_CAP_AHCI;
- s->control_regs.impl = (1 << s->ports) - 1;
- s->control_regs.version = AHCI_VERSION_1_0;
- for (i = 0; i < s->ports; i++) {
- s->dev[i].port_state = STATE_RUN;
- }
- }
- static void check_cmd(AHCIState *s, int port)
- {
- AHCIPortRegs *pr = &s->dev[port].port_regs;
- int slot;
- if ((pr->cmd & PORT_CMD_START) && pr->cmd_issue) {
- for (slot = 0; (slot < 32) && pr->cmd_issue; slot++) {
- if ((pr->cmd_issue & (1 << slot)) &&
- !handle_cmd(s, port, slot)) {
- pr->cmd_issue &= ~(1 << slot);
- }
- }
- }
- }
- static void ahci_check_cmd_bh(void *opaque)
- {
- AHCIDevice *ad = opaque;
- qemu_bh_delete(ad->check_bh);
- ad->check_bh = NULL;
- if ((ad->busy_slot != -1) &&
- !(ad->port.ifs[0].status & (BUSY_STAT|DRQ_STAT))) {
- /* no longer busy */
- ad->port_regs.cmd_issue &= ~(1 << ad->busy_slot);
- ad->busy_slot = -1;
- }
- check_cmd(ad->hba, ad->port_no);
- }
- static void ahci_init_d2h(AHCIDevice *ad)
- {
- uint8_t init_fis[20];
- IDEState *ide_state = &ad->port.ifs[0];
- memset(init_fis, 0, sizeof(init_fis));
- init_fis[4] = 1;
- init_fis[12] = 1;
- if (ide_state->drive_kind == IDE_CD) {
- init_fis[5] = ide_state->lcyl;
- init_fis[6] = ide_state->hcyl;
- }
- ahci_write_fis_d2h(ad, init_fis);
- }
- static void ahci_reset_port(AHCIState *s, int port)
- {
- AHCIDevice *d = &s->dev[port];
- AHCIPortRegs *pr = &d->port_regs;
- IDEState *ide_state = &d->port.ifs[0];
- int i;
- DPRINTF(port, "reset port\n");
- ide_bus_reset(&d->port);
- ide_state->ncq_queues = AHCI_MAX_CMDS;
- pr->scr_stat = 0;
- pr->scr_err = 0;
- pr->scr_act = 0;
- d->busy_slot = -1;
- d->init_d2h_sent = 0;
- ide_state = &s->dev[port].port.ifs[0];
- if (!ide_state->bs) {
- return;
- }
- /* reset ncq queue */
- for (i = 0; i < AHCI_MAX_CMDS; i++) {
- NCQTransferState *ncq_tfs = &s->dev[port].ncq_tfs[i];
- if (!ncq_tfs->used) {
- continue;
- }
- if (ncq_tfs->aiocb) {
- bdrv_aio_cancel(ncq_tfs->aiocb);
- ncq_tfs->aiocb = NULL;
- }
- /* Maybe we just finished the request thanks to bdrv_aio_cancel() */
- if (!ncq_tfs->used) {
- continue;
- }
- qemu_sglist_destroy(&ncq_tfs->sglist);
- ncq_tfs->used = 0;
- }
- s->dev[port].port_state = STATE_RUN;
- if (!ide_state->bs) {
- s->dev[port].port_regs.sig = 0;
- ide_state->status = SEEK_STAT | WRERR_STAT;
- } else if (ide_state->drive_kind == IDE_CD) {
- s->dev[port].port_regs.sig = SATA_SIGNATURE_CDROM;
- ide_state->lcyl = 0x14;
- ide_state->hcyl = 0xeb;
- DPRINTF(port, "set lcyl = %d\n", ide_state->lcyl);
- ide_state->status = SEEK_STAT | WRERR_STAT | READY_STAT;
- } else {
- s->dev[port].port_regs.sig = SATA_SIGNATURE_DISK;
- ide_state->status = SEEK_STAT | WRERR_STAT;
- }
- ide_state->error = 1;
- ahci_init_d2h(d);
- }
- static void debug_print_fis(uint8_t *fis, int cmd_len)
- {
- #ifdef DEBUG_AHCI
- int i;
- fprintf(stderr, "fis:");
- for (i = 0; i < cmd_len; i++) {
- if ((i & 0xf) == 0) {
- fprintf(stderr, "\n%02x:",i);
- }
- fprintf(stderr, "%02x ",fis[i]);
- }
- fprintf(stderr, "\n");
- #endif
- }
- static void ahci_write_fis_sdb(AHCIState *s, int port, uint32_t finished)
- {
- AHCIPortRegs *pr = &s->dev[port].port_regs;
- IDEState *ide_state;
- uint8_t *sdb_fis;
- if (!s->dev[port].res_fis ||
- !(pr->cmd & PORT_CMD_FIS_RX)) {
- return;
- }
- sdb_fis = &s->dev[port].res_fis[RES_FIS_SDBFIS];
- ide_state = &s->dev[port].port.ifs[0];
- /* clear memory */
- *(uint32_t*)sdb_fis = 0;
- /* write values */
- sdb_fis[0] = ide_state->error;
- sdb_fis[2] = ide_state->status & 0x77;
- s->dev[port].finished |= finished;
- *(uint32_t*)(sdb_fis + 4) = cpu_to_le32(s->dev[port].finished);
- ahci_trigger_irq(s, &s->dev[port], PORT_IRQ_STAT_SDBS);
- }
- static void ahci_write_fis_d2h(AHCIDevice *ad, uint8_t *cmd_fis)
- {
- AHCIPortRegs *pr = &ad->port_regs;
- uint8_t *d2h_fis;
- int i;
- dma_addr_t cmd_len = 0x80;
- int cmd_mapped = 0;
- if (!ad->res_fis || !(pr->cmd & PORT_CMD_FIS_RX)) {
- return;
- }
- if (!cmd_fis) {
- /* map cmd_fis */
- uint64_t tbl_addr = le64_to_cpu(ad->cur_cmd->tbl_addr);
- cmd_fis = dma_memory_map(ad->hba->dma, tbl_addr, &cmd_len,
- DMA_DIRECTION_TO_DEVICE);
- cmd_mapped = 1;
- }
- d2h_fis = &ad->res_fis[RES_FIS_RFIS];
- d2h_fis[0] = 0x34;
- d2h_fis[1] = (ad->hba->control_regs.irqstatus ? (1 << 6) : 0);
- d2h_fis[2] = ad->port.ifs[0].status;
- d2h_fis[3] = ad->port.ifs[0].error;
- d2h_fis[4] = cmd_fis[4];
- d2h_fis[5] = cmd_fis[5];
- d2h_fis[6] = cmd_fis[6];
- d2h_fis[7] = cmd_fis[7];
- d2h_fis[8] = cmd_fis[8];
- d2h_fis[9] = cmd_fis[9];
- d2h_fis[10] = cmd_fis[10];
- d2h_fis[11] = cmd_fis[11];
- d2h_fis[12] = cmd_fis[12];
- d2h_fis[13] = cmd_fis[13];
- for (i = 14; i < 20; i++) {
- d2h_fis[i] = 0;
- }
- if (d2h_fis[2] & ERR_STAT) {
- ahci_trigger_irq(ad->hba, ad, PORT_IRQ_STAT_TFES);
- }
- ahci_trigger_irq(ad->hba, ad, PORT_IRQ_D2H_REG_FIS);
- if (cmd_mapped) {
- dma_memory_unmap(ad->hba->dma, cmd_fis, cmd_len,
- DMA_DIRECTION_TO_DEVICE, cmd_len);
- }
- }
- static int ahci_populate_sglist(AHCIDevice *ad, QEMUSGList *sglist, int offset)
- {
- AHCICmdHdr *cmd = ad->cur_cmd;
- uint32_t opts = le32_to_cpu(cmd->opts);
- uint64_t prdt_addr = le64_to_cpu(cmd->tbl_addr) + 0x80;
- int sglist_alloc_hint = opts >> AHCI_CMD_HDR_PRDT_LEN;
- dma_addr_t prdt_len = (sglist_alloc_hint * sizeof(AHCI_SG));
- dma_addr_t real_prdt_len = prdt_len;
- uint8_t *prdt;
- int i;
- int r = 0;
- int sum = 0;
- int off_idx = -1;
- int off_pos = -1;
- int tbl_entry_size;
- if (!sglist_alloc_hint) {
- DPRINTF(ad->port_no, "no sg list given by guest: 0x%08x\n", opts);
- return -1;
- }
- /* map PRDT */
- if (!(prdt = dma_memory_map(ad->hba->dma, prdt_addr, &prdt_len,
- DMA_DIRECTION_TO_DEVICE))){
- DPRINTF(ad->port_no, "map failed\n");
- return -1;
- }
- if (prdt_len < real_prdt_len) {
- DPRINTF(ad->port_no, "mapped less than expected\n");
- r = -1;
- goto out;
- }
- /* Get entries in the PRDT, init a qemu sglist accordingly */
- if (sglist_alloc_hint > 0) {
- AHCI_SG *tbl = (AHCI_SG *)prdt;
- sum = 0;
- for (i = 0; i < sglist_alloc_hint; i++) {
- /* flags_size is zero-based */
- tbl_entry_size = (le32_to_cpu(tbl[i].flags_size) + 1);
- if (offset <= (sum + tbl_entry_size)) {
- off_idx = i;
- off_pos = offset - sum;
- break;
- }
- sum += tbl_entry_size;
- }
- if ((off_idx == -1) || (off_pos < 0) || (off_pos > tbl_entry_size)) {
- DPRINTF(ad->port_no, "%s: Incorrect offset! "
- "off_idx: %d, off_pos: %d\n",
- __func__, off_idx, off_pos);
- r = -1;
- goto out;
- }
- qemu_sglist_init(sglist, (sglist_alloc_hint - off_idx), ad->hba->dma);
- qemu_sglist_add(sglist, le64_to_cpu(tbl[off_idx].addr + off_pos),
- le32_to_cpu(tbl[off_idx].flags_size) + 1 - off_pos);
- for (i = off_idx + 1; i < sglist_alloc_hint; i++) {
- /* flags_size is zero-based */
- qemu_sglist_add(sglist, le64_to_cpu(tbl[i].addr),
- le32_to_cpu(tbl[i].flags_size) + 1);
- }
- }
- out:
- dma_memory_unmap(ad->hba->dma, prdt, prdt_len,
- DMA_DIRECTION_TO_DEVICE, prdt_len);
- return r;
- }
- static void ncq_cb(void *opaque, int ret)
- {
- NCQTransferState *ncq_tfs = (NCQTransferState *)opaque;
- IDEState *ide_state = &ncq_tfs->drive->port.ifs[0];
- /* Clear bit for this tag in SActive */
- ncq_tfs->drive->port_regs.scr_act &= ~(1 << ncq_tfs->tag);
- if (ret < 0) {
- /* error */
- ide_state->error = ABRT_ERR;
- ide_state->status = READY_STAT | ERR_STAT;
- ncq_tfs->drive->port_regs.scr_err |= (1 << ncq_tfs->tag);
- } else {
- ide_state->status = READY_STAT | SEEK_STAT;
- }
- ahci_write_fis_sdb(ncq_tfs->drive->hba, ncq_tfs->drive->port_no,
- (1 << ncq_tfs->tag));
- DPRINTF(ncq_tfs->drive->port_no, "NCQ transfer tag %d finished\n",
- ncq_tfs->tag);
- bdrv_acct_done(ncq_tfs->drive->port.ifs[0].bs, &ncq_tfs->acct);
- qemu_sglist_destroy(&ncq_tfs->sglist);
- ncq_tfs->used = 0;
- }
- static void process_ncq_command(AHCIState *s, int port, uint8_t *cmd_fis,
- int slot)
- {
- NCQFrame *ncq_fis = (NCQFrame*)cmd_fis;
- uint8_t tag = ncq_fis->tag >> 3;
- NCQTransferState *ncq_tfs = &s->dev[port].ncq_tfs[tag];
- if (ncq_tfs->used) {
- /* error - already in use */
- fprintf(stderr, "%s: tag %d already used\n", __FUNCTION__, tag);
- return;
- }
- ncq_tfs->used = 1;
- ncq_tfs->drive = &s->dev[port];
- ncq_tfs->slot = slot;
- ncq_tfs->lba = ((uint64_t)ncq_fis->lba5 << 40) |
- ((uint64_t)ncq_fis->lba4 << 32) |
- ((uint64_t)ncq_fis->lba3 << 24) |
- ((uint64_t)ncq_fis->lba2 << 16) |
- ((uint64_t)ncq_fis->lba1 << 8) |
- (uint64_t)ncq_fis->lba0;
- /* Note: We calculate the sector count, but don't currently rely on it.
- * The total size of the DMA buffer tells us the transfer size instead. */
- ncq_tfs->sector_count = ((uint16_t)ncq_fis->sector_count_high << 8) |
- ncq_fis->sector_count_low;
- DPRINTF(port, "NCQ transfer LBA from %"PRId64" to %"PRId64", "
- "drive max %"PRId64"\n",
- ncq_tfs->lba, ncq_tfs->lba + ncq_tfs->sector_count - 2,
- s->dev[port].port.ifs[0].nb_sectors - 1);
- ahci_populate_sglist(&s->dev[port], &ncq_tfs->sglist, 0);
- ncq_tfs->tag = tag;
- switch(ncq_fis->command) {
- case READ_FPDMA_QUEUED:
- DPRINTF(port, "NCQ reading %d sectors from LBA %"PRId64", "
- "tag %d\n",
- ncq_tfs->sector_count-1, ncq_tfs->lba, ncq_tfs->tag);
- DPRINTF(port, "tag %d aio read %"PRId64"\n",
- ncq_tfs->tag, ncq_tfs->lba);
- dma_acct_start(ncq_tfs->drive->port.ifs[0].bs, &ncq_tfs->acct,
- &ncq_tfs->sglist, BDRV_ACCT_READ);
- ncq_tfs->aiocb = dma_bdrv_read(ncq_tfs->drive->port.ifs[0].bs,
- &ncq_tfs->sglist, ncq_tfs->lba,
- ncq_cb, ncq_tfs);
- break;
- case WRITE_FPDMA_QUEUED:
- DPRINTF(port, "NCQ writing %d sectors to LBA %"PRId64", tag %d\n",
- ncq_tfs->sector_count-1, ncq_tfs->lba, ncq_tfs->tag);
- DPRINTF(port, "tag %d aio write %"PRId64"\n",
- ncq_tfs->tag, ncq_tfs->lba);
- dma_acct_start(ncq_tfs->drive->port.ifs[0].bs, &ncq_tfs->acct,
- &ncq_tfs->sglist, BDRV_ACCT_WRITE);
- ncq_tfs->aiocb = dma_bdrv_write(ncq_tfs->drive->port.ifs[0].bs,
- &ncq_tfs->sglist, ncq_tfs->lba,
- ncq_cb, ncq_tfs);
- break;
- default:
- DPRINTF(port, "error: tried to process non-NCQ command as NCQ\n");
- qemu_sglist_destroy(&ncq_tfs->sglist);
- break;
- }
- }
- static int handle_cmd(AHCIState *s, int port, int slot)
- {
- IDEState *ide_state;
- uint32_t opts;
- uint64_t tbl_addr;
- AHCICmdHdr *cmd;
- uint8_t *cmd_fis;
- dma_addr_t cmd_len;
- if (s->dev[port].port.ifs[0].status & (BUSY_STAT|DRQ_STAT)) {
- /* Engine currently busy, try again later */
- DPRINTF(port, "engine busy\n");
- return -1;
- }
- cmd = &((AHCICmdHdr *)s->dev[port].lst)[slot];
- if (!s->dev[port].lst) {
- DPRINTF(port, "error: lst not given but cmd handled");
- return -1;
- }
- /* remember current slot handle for later */
- s->dev[port].cur_cmd = cmd;
- opts = le32_to_cpu(cmd->opts);
- tbl_addr = le64_to_cpu(cmd->tbl_addr);
- cmd_len = 0x80;
- cmd_fis = dma_memory_map(s->dma, tbl_addr, &cmd_len,
- DMA_DIRECTION_FROM_DEVICE);
- if (!cmd_fis) {
- DPRINTF(port, "error: guest passed us an invalid cmd fis\n");
- return -1;
- }
- /* The device we are working for */
- ide_state = &s->dev[port].port.ifs[0];
- if (!ide_state->bs) {
- DPRINTF(port, "error: guest accessed unused port");
- goto out;
- }
- debug_print_fis(cmd_fis, 0x90);
- //debug_print_fis(cmd_fis, (opts & AHCI_CMD_HDR_CMD_FIS_LEN) * 4);
- switch (cmd_fis[0]) {
- case SATA_FIS_TYPE_REGISTER_H2D:
- break;
- default:
- DPRINTF(port, "unknown command cmd_fis[0]=%02x cmd_fis[1]=%02x "
- "cmd_fis[2]=%02x\n", cmd_fis[0], cmd_fis[1],
- cmd_fis[2]);
- goto out;
- break;
- }
- switch (cmd_fis[1]) {
- case SATA_FIS_REG_H2D_UPDATE_COMMAND_REGISTER:
- break;
- case 0:
- break;
- default:
- DPRINTF(port, "unknown command cmd_fis[0]=%02x cmd_fis[1]=%02x "
- "cmd_fis[2]=%02x\n", cmd_fis[0], cmd_fis[1],
- cmd_fis[2]);
- goto out;
- break;
- }
- switch (s->dev[port].port_state) {
- case STATE_RUN:
- if (cmd_fis[15] & ATA_SRST) {
- s->dev[port].port_state = STATE_RESET;
- }
- break;
- case STATE_RESET:
- if (!(cmd_fis[15] & ATA_SRST)) {
- ahci_reset_port(s, port);
- }
- break;
- }
- if (cmd_fis[1] == SATA_FIS_REG_H2D_UPDATE_COMMAND_REGISTER) {
- /* Check for NCQ command */
- if ((cmd_fis[2] == READ_FPDMA_QUEUED) ||
- (cmd_fis[2] == WRITE_FPDMA_QUEUED)) {
- process_ncq_command(s, port, cmd_fis, slot);
- goto out;
- }
- /* Decompose the FIS */
- ide_state->nsector = (int64_t)((cmd_fis[13] << 8) | cmd_fis[12]);
- ide_state->feature = cmd_fis[3];
- if (!ide_state->nsector) {
- ide_state->nsector = 256;
- }
- if (ide_state->drive_kind != IDE_CD) {
- /*
- * We set the sector depending on the sector defined in the FIS.
- * Unfortunately, the spec isn't exactly obvious on this one.
- *
- * Apparently LBA48 commands set fis bytes 10,9,8,6,5,4 to the
- * 48 bit sector number. ATA_CMD_READ_DMA_EXT is an example for
- * such a command.
- *
- * Non-LBA48 commands however use 7[lower 4 bits],6,5,4 to define a
- * 28-bit sector number. ATA_CMD_READ_DMA is an example for such
- * a command.
- *
- * Since the spec doesn't explicitly state what each field should
- * do, I simply assume non-used fields as reserved and OR everything
- * together, independent of the command.
- */
- ide_set_sector(ide_state, ((uint64_t)cmd_fis[10] << 40)
- | ((uint64_t)cmd_fis[9] << 32)
- /* This is used for LBA48 commands */
- | ((uint64_t)cmd_fis[8] << 24)
- /* This is used for non-LBA48 commands */
- | ((uint64_t)(cmd_fis[7] & 0xf) << 24)
- | ((uint64_t)cmd_fis[6] << 16)
- | ((uint64_t)cmd_fis[5] << 8)
- | cmd_fis[4]);
- }
- /* Copy the ACMD field (ATAPI packet, if any) from the AHCI command
- * table to ide_state->io_buffer
- */
- if (opts & AHCI_CMD_ATAPI) {
- memcpy(ide_state->io_buffer, &cmd_fis[AHCI_COMMAND_TABLE_ACMD], 0x10);
- ide_state->lcyl = 0x14;
- ide_state->hcyl = 0xeb;
- debug_print_fis(ide_state->io_buffer, 0x10);
- ide_state->feature = IDE_FEATURE_DMA;
- s->dev[port].done_atapi_packet = 0;
- /* XXX send PIO setup FIS */
- }
- ide_state->error = 0;
- /* Reset transferred byte counter */
- cmd->status = 0;
- /* We're ready to process the command in FIS byte 2. */
- ide_exec_cmd(&s->dev[port].port, cmd_fis[2]);
- if (s->dev[port].port.ifs[0].status & READY_STAT) {
- ahci_write_fis_d2h(&s->dev[port], cmd_fis);
- }
- }
- out:
- dma_memory_unmap(s->dma, cmd_fis, cmd_len, DMA_DIRECTION_FROM_DEVICE,
- cmd_len);
- if (s->dev[port].port.ifs[0].status & (BUSY_STAT|DRQ_STAT)) {
- /* async command, complete later */
- s->dev[port].busy_slot = slot;
- return -1;
- }
- /* done handling the command */
- return 0;
- }
- /* DMA dev <-> ram */
- static int ahci_start_transfer(IDEDMA *dma)
- {
- AHCIDevice *ad = DO_UPCAST(AHCIDevice, dma, dma);
- IDEState *s = &ad->port.ifs[0];
- uint32_t size = (uint32_t)(s->data_end - s->data_ptr);
- /* write == ram -> device */
- uint32_t opts = le32_to_cpu(ad->cur_cmd->opts);
- int is_write = opts & AHCI_CMD_WRITE;
- int is_atapi = opts & AHCI_CMD_ATAPI;
- int has_sglist = 0;
- if (is_atapi && !ad->done_atapi_packet) {
- /* already prepopulated iobuffer */
- ad->done_atapi_packet = 1;
- goto out;
- }
- if (!ahci_populate_sglist(ad, &s->sg, 0)) {
- has_sglist = 1;
- }
- DPRINTF(ad->port_no, "%sing %d bytes on %s w/%s sglist\n",
- is_write ? "writ" : "read", size, is_atapi ? "atapi" : "ata",
- has_sglist ? "" : "o");
- if (has_sglist && size) {
- if (is_write) {
- dma_buf_write(s->data_ptr, size, &s->sg);
- } else {
- dma_buf_read(s->data_ptr, size, &s->sg);
- }
- }
- /* update number of transferred bytes */
- ad->cur_cmd->status = cpu_to_le32(le32_to_cpu(ad->cur_cmd->status) + size);
- out:
- /* declare that we processed everything */
- s->data_ptr = s->data_end;
- if (has_sglist) {
- qemu_sglist_destroy(&s->sg);
- }
- s->end_transfer_func(s);
- if (!(s->status & DRQ_STAT)) {
- /* done with DMA */
- ahci_trigger_irq(ad->hba, ad, PORT_IRQ_STAT_DSS);
- }
- return 0;
- }
- static void ahci_start_dma(IDEDMA *dma, IDEState *s,
- BlockDriverCompletionFunc *dma_cb)
- {
- AHCIDevice *ad = DO_UPCAST(AHCIDevice, dma, dma);
- DPRINTF(ad->port_no, "\n");
- ad->dma_cb = dma_cb;
- ad->dma_status |= BM_STATUS_DMAING;
- s->io_buffer_offset = 0;
- dma_cb(s, 0);
- }
- static int ahci_dma_prepare_buf(IDEDMA *dma, int is_write)
- {
- AHCIDevice *ad = DO_UPCAST(AHCIDevice, dma, dma);
- IDEState *s = &ad->port.ifs[0];
- ahci_populate_sglist(ad, &s->sg, 0);
- s->io_buffer_size = s->sg.size;
- DPRINTF(ad->port_no, "len=%#x\n", s->io_buffer_size);
- return s->io_buffer_size != 0;
- }
- static int ahci_dma_rw_buf(IDEDMA *dma, int is_write)
- {
- AHCIDevice *ad = DO_UPCAST(AHCIDevice, dma, dma);
- IDEState *s = &ad->port.ifs[0];
- uint8_t *p = s->io_buffer + s->io_buffer_index;
- int l = s->io_buffer_size - s->io_buffer_index;
- if (ahci_populate_sglist(ad, &s->sg, s->io_buffer_offset)) {
- return 0;
- }
- if (is_write) {
- dma_buf_read(p, l, &s->sg);
- } else {
- dma_buf_write(p, l, &s->sg);
- }
- /* free sglist that was created in ahci_populate_sglist() */
- qemu_sglist_destroy(&s->sg);
- /* update number of transferred bytes */
- ad->cur_cmd->status = cpu_to_le32(le32_to_cpu(ad->cur_cmd->status) + l);
- s->io_buffer_index += l;
- s->io_buffer_offset += l;
- DPRINTF(ad->port_no, "len=%#x\n", l);
- return 1;
- }
- static int ahci_dma_set_unit(IDEDMA *dma, int unit)
- {
- /* only a single unit per link */
- return 0;
- }
- static int ahci_dma_add_status(IDEDMA *dma, int status)
- {
- AHCIDevice *ad = DO_UPCAST(AHCIDevice, dma, dma);
- ad->dma_status |= status;
- DPRINTF(ad->port_no, "set status: %x\n", status);
- if (status & BM_STATUS_INT) {
- ahci_trigger_irq(ad->hba, ad, PORT_IRQ_STAT_DSS);
- }
- return 0;
- }
- static int ahci_dma_set_inactive(IDEDMA *dma)
- {
- AHCIDevice *ad = DO_UPCAST(AHCIDevice, dma, dma);
- DPRINTF(ad->port_no, "dma done\n");
- /* update d2h status */
- ahci_write_fis_d2h(ad, NULL);
- ad->dma_cb = NULL;
- if (!ad->check_bh) {
- /* maybe we still have something to process, check later */
- ad->check_bh = qemu_bh_new(ahci_check_cmd_bh, ad);
- qemu_bh_schedule(ad->check_bh);
- }
- return 0;
- }
- static void ahci_irq_set(void *opaque, int n, int level)
- {
- }
- static void ahci_dma_restart_cb(void *opaque, int running, RunState state)
- {
- }
- static int ahci_dma_reset(IDEDMA *dma)
- {
- return 0;
- }
- static const IDEDMAOps ahci_dma_ops = {
- .start_dma = ahci_start_dma,
- .start_transfer = ahci_start_transfer,
- .prepare_buf = ahci_dma_prepare_buf,
- .rw_buf = ahci_dma_rw_buf,
- .set_unit = ahci_dma_set_unit,
- .add_status = ahci_dma_add_status,
- .set_inactive = ahci_dma_set_inactive,
- .restart_cb = ahci_dma_restart_cb,
- .reset = ahci_dma_reset,
- };
- void ahci_init(AHCIState *s, DeviceState *qdev, DMAContext *dma, int ports)
- {
- qemu_irq *irqs;
- int i;
- s->dma = dma;
- s->ports = ports;
- s->dev = g_malloc0(sizeof(AHCIDevice) * ports);
- ahci_reg_init(s);
- /* XXX BAR size should be 1k, but that breaks, so bump it to 4k for now */
- memory_region_init_io(&s->mem, &ahci_mem_ops, s, "ahci", AHCI_MEM_BAR_SIZE);
- memory_region_init_io(&s->idp, &ahci_idp_ops, s, "ahci-idp", 32);
- irqs = qemu_allocate_irqs(ahci_irq_set, s, s->ports);
- for (i = 0; i < s->ports; i++) {
- AHCIDevice *ad = &s->dev[i];
- ide_bus_new(&ad->port, qdev, i);
- ide_init2(&ad->port, irqs[i]);
- ad->hba = s;
- ad->port_no = i;
- ad->port.dma = &ad->dma;
- ad->port.dma->ops = &ahci_dma_ops;
- }
- }
- void ahci_uninit(AHCIState *s)
- {
- memory_region_destroy(&s->mem);
- memory_region_destroy(&s->idp);
- g_free(s->dev);
- }
- void ahci_reset(AHCIState *s)
- {
- AHCIPortRegs *pr;
- int i;
- s->control_regs.irqstatus = 0;
- s->control_regs.ghc = 0;
- for (i = 0; i < s->ports; i++) {
- pr = &s->dev[i].port_regs;
- pr->irq_stat = 0;
- pr->irq_mask = 0;
- pr->scr_ctl = 0;
- pr->cmd = PORT_CMD_SPIN_UP | PORT_CMD_POWER_ON;
- ahci_reset_port(s, i);
- }
- }
- typedef struct SysbusAHCIState {
- SysBusDevice busdev;
- AHCIState ahci;
- uint32_t num_ports;
- } SysbusAHCIState;
- static const VMStateDescription vmstate_sysbus_ahci = {
- .name = "sysbus-ahci",
- .unmigratable = 1,
- };
- static void sysbus_ahci_reset(DeviceState *dev)
- {
- SysbusAHCIState *s = DO_UPCAST(SysbusAHCIState, busdev.qdev, dev);
- ahci_reset(&s->ahci);
- }
- static int sysbus_ahci_init(SysBusDevice *dev)
- {
- SysbusAHCIState *s = FROM_SYSBUS(SysbusAHCIState, dev);
- ahci_init(&s->ahci, &dev->qdev, NULL, s->num_ports);
- sysbus_init_mmio(dev, &s->ahci.mem);
- sysbus_init_irq(dev, &s->ahci.irq);
- return 0;
- }
- static Property sysbus_ahci_properties[] = {
- DEFINE_PROP_UINT32("num-ports", SysbusAHCIState, num_ports, 1),
- DEFINE_PROP_END_OF_LIST(),
- };
- static void sysbus_ahci_class_init(ObjectClass *klass, void *data)
- {
- SysBusDeviceClass *sbc = SYS_BUS_DEVICE_CLASS(klass);
- DeviceClass *dc = DEVICE_CLASS(klass);
- sbc->init = sysbus_ahci_init;
- dc->vmsd = &vmstate_sysbus_ahci;
- dc->props = sysbus_ahci_properties;
- dc->reset = sysbus_ahci_reset;
- }
- static TypeInfo sysbus_ahci_info = {
- .name = "sysbus-ahci",
- .parent = TYPE_SYS_BUS_DEVICE,
- .instance_size = sizeof(SysbusAHCIState),
- .class_init = sysbus_ahci_class_init,
- };
- static void sysbus_ahci_register_types(void)
- {
- type_register_static(&sysbus_ahci_info);
- }
- type_init(sysbus_ahci_register_types)
|