diff options
Diffstat (limited to 'hw/ide/ahci.c')
-rw-r--r-- | hw/ide/ahci.c | 98 |
1 files changed, 60 insertions, 38 deletions
diff --git a/hw/ide/ahci.c b/hw/ide/ahci.c index 7e87b1805e..f244bc01c9 100644 --- a/hw/ide/ahci.c +++ b/hw/ide/ahci.c @@ -199,52 +199,38 @@ static void map_page(AddressSpace *as, uint8_t **ptr, uint64_t addr, * Check the cmd register to see if we should start or stop * the DMA or FIS RX engines. * - * @ad: Device to engage. - * @allow_stop: Allow device to transition from started to stopped? - * 'no' is useful for migration post_load, which does not expect a transition. + * @ad: Device to dis/engage. * * @return 0 on success, -1 on error. */ -static int ahci_cond_start_engines(AHCIDevice *ad, bool allow_stop) +static int ahci_cond_start_engines(AHCIDevice *ad) { AHCIPortRegs *pr = &ad->port_regs; - - if (pr->cmd & PORT_CMD_START) { - if (ahci_map_clb_address(ad)) { - pr->cmd |= PORT_CMD_LIST_ON; - } else { + bool cmd_start = pr->cmd & PORT_CMD_START; + bool cmd_on = pr->cmd & PORT_CMD_LIST_ON; + bool fis_start = pr->cmd & PORT_CMD_FIS_RX; + bool fis_on = pr->cmd & PORT_CMD_FIS_ON; + + if (cmd_start && !cmd_on) { + if (!ahci_map_clb_address(ad)) { + pr->cmd &= ~PORT_CMD_START; error_report("AHCI: Failed to start DMA engine: " "bad command list buffer address"); return -1; } - } else if (pr->cmd & PORT_CMD_LIST_ON) { - if (allow_stop) { - ahci_unmap_clb_address(ad); - pr->cmd = pr->cmd & ~(PORT_CMD_LIST_ON); - } else { - error_report("AHCI: DMA engine should be off, " - "but appears to still be running"); - return -1; - } + } else if (!cmd_start && cmd_on) { + ahci_unmap_clb_address(ad); } - if (pr->cmd & PORT_CMD_FIS_RX) { - if (ahci_map_fis_address(ad)) { - pr->cmd |= PORT_CMD_FIS_ON; - } else { + if (fis_start && !fis_on) { + if (!ahci_map_fis_address(ad)) { + pr->cmd &= ~PORT_CMD_FIS_RX; error_report("AHCI: Failed to start FIS receive engine: " "bad FIS receive buffer address"); return -1; } - } else if (pr->cmd & PORT_CMD_FIS_ON) { - if (allow_stop) { - ahci_unmap_fis_address(ad); - pr->cmd = pr->cmd & ~(PORT_CMD_FIS_ON); - } else { - error_report("AHCI: FIS receive engine should be off, " - "but appears to still be running"); - return -1; - } + } else if (!fis_start && fis_on) { + ahci_unmap_fis_address(ad); } return 0; @@ -286,8 +272,8 @@ static void ahci_port_write(AHCIState *s, int port, int offset, uint32_t val) pr->cmd = (pr->cmd & PORT_CMD_RO_MASK) | (val & ~(PORT_CMD_RO_MASK|PORT_CMD_ICC_MASK)); - /* Check FIS RX and CLB engines, allow transition to false: */ - ahci_cond_start_engines(&s->dev[port], true); + /* Check FIS RX and CLB engines */ + ahci_cond_start_engines(&s->dev[port]); /* XXX usually the FIS would be pending on the bus here and issuing deferred until the OS enables FIS receival. @@ -657,11 +643,22 @@ static bool ahci_map_fis_address(AHCIDevice *ad) AHCIPortRegs *pr = &ad->port_regs; map_page(ad->hba->as, &ad->res_fis, ((uint64_t)pr->fis_addr_hi << 32) | pr->fis_addr, 256); - return ad->res_fis != NULL; + if (ad->res_fis != NULL) { + pr->cmd |= PORT_CMD_FIS_ON; + return true; + } + + pr->cmd &= ~PORT_CMD_FIS_ON; + return false; } static void ahci_unmap_fis_address(AHCIDevice *ad) { + if (ad->res_fis == NULL) { + DPRINTF(ad->port_no, "Attempt to unmap NULL FIS address\n"); + return; + } + ad->port_regs.cmd &= ~PORT_CMD_FIS_ON; dma_memory_unmap(ad->hba->as, ad->res_fis, 256, DMA_DIRECTION_FROM_DEVICE, 256); ad->res_fis = NULL; @@ -673,11 +670,22 @@ static bool ahci_map_clb_address(AHCIDevice *ad) ad->cur_cmd = NULL; map_page(ad->hba->as, &ad->lst, ((uint64_t)pr->lst_addr_hi << 32) | pr->lst_addr, 1024); - return ad->lst != NULL; + if (ad->lst != NULL) { + pr->cmd |= PORT_CMD_LIST_ON; + return true; + } + + pr->cmd &= ~PORT_CMD_LIST_ON; + return false; } static void ahci_unmap_clb_address(AHCIDevice *ad) { + if (ad->lst == NULL) { + DPRINTF(ad->port_no, "Attempt to unmap NULL CLB address\n"); + return; + } + ad->port_regs.cmd &= ~PORT_CMD_LIST_ON; dma_memory_unmap(ad->hba->as, ad->lst, 1024, DMA_DIRECTION_FROM_DEVICE, 1024); ad->lst = NULL; @@ -1552,14 +1560,28 @@ static int ahci_state_post_load(void *opaque, int version_id) int i, j; struct AHCIDevice *ad; NCQTransferState *ncq_tfs; + AHCIPortRegs *pr; AHCIState *s = opaque; for (i = 0; i < s->ports; i++) { ad = &s->dev[i]; + pr = &ad->port_regs; + + if (!(pr->cmd & PORT_CMD_START) && (pr->cmd & PORT_CMD_LIST_ON)) { + error_report("AHCI: DMA engine should be off, but status bit " + "indicates it is still running."); + return -1; + } + if (!(pr->cmd & PORT_CMD_FIS_RX) && (pr->cmd & PORT_CMD_FIS_ON)) { + error_report("AHCI: FIS RX engine should be off, but status bit " + "indicates it is still running."); + return -1; + } - /* Only remap the CLB address if appropriate, disallowing a state - * transition from 'on' to 'off' it should be consistent here. */ - if (ahci_cond_start_engines(ad, false) != 0) { + /* After a migrate, the DMA/FIS engines are "off" and + * need to be conditionally restarted */ + pr->cmd &= ~(PORT_CMD_LIST_ON | PORT_CMD_FIS_ON); + if (ahci_cond_start_engines(ad) != 0) { return -1; } |