aboutsummaryrefslogtreecommitdiff
path: root/hw/ide/ahci.c
diff options
context:
space:
mode:
Diffstat (limited to 'hw/ide/ahci.c')
-rw-r--r--hw/ide/ahci.c98
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;
}