aboutsummaryrefslogtreecommitdiff
path: root/hw/ide
diff options
context:
space:
mode:
Diffstat (limited to 'hw/ide')
-rw-r--r--hw/ide/ahci.c88
1 files changed, 63 insertions, 25 deletions
diff --git a/hw/ide/ahci.c b/hw/ide/ahci.c
index 8e36dec5a9..9e5d86297c 100644
--- a/hw/ide/ahci.c
+++ b/hw/ide/ahci.c
@@ -198,6 +198,61 @@ 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.
+ *
+ * @return 0 on success, -1 on error.
+ */
+static int ahci_cond_start_engines(AHCIDevice *ad, bool allow_stop)
+{
+ AHCIPortRegs *pr = &ad->port_regs;
+
+ if (pr->cmd & PORT_CMD_START) {
+ if (ahci_map_clb_address(ad)) {
+ pr->cmd |= PORT_CMD_LIST_ON;
+ } else {
+ 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;
+ }
+ }
+
+ if (pr->cmd & PORT_CMD_FIS_RX) {
+ if (ahci_map_fis_address(ad)) {
+ pr->cmd |= PORT_CMD_FIS_ON;
+ } else {
+ 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;
+ }
+ }
+
+ return 0;
+}
+
static void ahci_port_write(AHCIState *s, int port, int offset, uint32_t val)
{
AHCIPortRegs *pr = &s->dev[port].port_regs;
@@ -229,29 +284,8 @@ static void ahci_port_write(AHCIState *s, int port, int offset, uint32_t val)
* including LIST_ON and FIS_ON. */
pr->cmd = (pr->cmd & PORT_CMD_RO_MASK) | (val & ~PORT_CMD_RO_MASK);
- if (pr->cmd & PORT_CMD_START) {
- if (ahci_map_clb_address(&s->dev[port])) {
- pr->cmd |= PORT_CMD_LIST_ON;
- } else {
- error_report("AHCI: Failed to start DMA engine: "
- "bad command list buffer address");
- }
- } else if (pr->cmd & PORT_CMD_LIST_ON) {
- ahci_unmap_clb_address(&s->dev[port]);
- pr->cmd = pr->cmd & ~(PORT_CMD_LIST_ON);
- }
-
- if (pr->cmd & PORT_CMD_FIS_RX) {
- if (ahci_map_fis_address(&s->dev[port])) {
- pr->cmd |= PORT_CMD_FIS_ON;
- } else {
- error_report("AHCI: Failed to start FIS receive engine: "
- "bad FIS receive buffer address");
- }
- } else if (pr->cmd & PORT_CMD_FIS_ON) {
- ahci_unmap_fis_address(&s->dev[port]);
- pr->cmd = pr->cmd & ~(PORT_CMD_FIS_ON);
- }
+ /* Check FIS RX and CLB engines, allow transition to false: */
+ ahci_cond_start_engines(&s->dev[port], true);
/* XXX usually the FIS would be pending on the bus here and
issuing deferred until the OS enables FIS receival.
@@ -1404,8 +1438,12 @@ static int ahci_state_post_load(void *opaque, int version_id)
for (i = 0; i < s->ports; i++) {
ad = &s->dev[i];
- ahci_map_clb_address(ad);
- ahci_map_fis_address(ad);
+ /* 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) {
+ return -1;
+ }
+
/*
* If an error is present, ad->busy_slot will be valid and not -1.
* In this case, an operation is waiting to resume and will re-check