static void ahci_init_d2h(AHCIDevice *ad);
static int ahci_dma_prepare_buf(IDEDMA *dma, int is_write);
static void ahci_commit_buf(IDEDMA *dma, uint32_t tx_bytes);
+static bool ahci_map_clb_address(AHCIDevice *ad);
+static bool ahci_map_fis_address(AHCIDevice *ad);
+static void ahci_unmap_clb_address(AHCIDevice *ad);
+static void ahci_unmap_fis_address(AHCIDevice *ad);
static uint32_t ahci_port_read(AHCIState *s, int port, int offset)
}
}
+/**
+ * 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;
switch (offset) {
case PORT_LST_ADDR:
pr->lst_addr = val;
- map_page(s->as, &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->as, &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->as, &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->as, &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_CMD:
- pr->cmd = val & ~(PORT_CMD_LIST_ON | PORT_CMD_FIS_ON);
+ /* Block any Read-only fields from being set;
+ * 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) {
- pr->cmd |= PORT_CMD_LIST_ON;
- }
-
- if (pr->cmd & PORT_CMD_FIS_RX) {
- 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.
#endif
}
+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;
+}
+
+static void ahci_unmap_fis_address(AHCIDevice *ad)
+{
+ dma_memory_unmap(ad->hba->as, ad->res_fis, 256,
+ DMA_DIRECTION_FROM_DEVICE, 256);
+ ad->res_fis = NULL;
+}
+
+static bool ahci_map_clb_address(AHCIDevice *ad)
+{
+ AHCIPortRegs *pr = &ad->port_regs;
+ 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;
+}
+
+static void ahci_unmap_clb_address(AHCIDevice *ad)
+{
+ dma_memory_unmap(ad->hba->as, ad->lst, 1024,
+ DMA_DIRECTION_FROM_DEVICE, 1024);
+ ad->lst = NULL;
+}
+
static void ahci_write_fis_sdb(AHCIState *s, int port, uint32_t finished)
{
AHCIDevice *ad = &s->dev[port];
for (i = 0; i < s->ports; i++) {
ad = &s->dev[i];
- AHCIPortRegs *pr = &ad->port_regs;
- map_page(s->as, &ad->lst,
- ((uint64_t)pr->lst_addr_hi << 32) | pr->lst_addr, 1024);
- map_page(s->as, &ad->res_fis,
- ((uint64_t)pr->fis_addr_hi << 32) | pr->fis_addr, 256);
+ /* 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
static const VMStateDescription vmstate_sysbus_ahci = {
.name = "sysbus-ahci",
- .unmigratable = 1, /* Still buggy under I/O load */
.fields = (VMStateField[]) {
VMSTATE_AHCI(ahci, SysbusAHCIState),
VMSTATE_END_OF_LIST()