[PATCH qemu 19/38] aspeed/smc: add support for DMAs

Cédric Le Goater clg at kaod.org
Sat Nov 19 01:21:59 AEDT 2016


Some of SMC controllers on the Aspeed SoCs support DMA to access the
flash modules. It can operate in a normal mode, to copy to or from the
flash module mapping window, or in a checksum calculation mode, to
evaluate the best clock settings for reads.

When DMA is enabled, a DMA request is built and passed on to a bottom
half to handle the memory transaction. The CPU is notified of the
completion with an IRQ if it was enabled.

Signed-off-by: Cédric Le Goater <clg at kaod.org>
---
 hw/ssi/aspeed_smc.c | 234 ++++++++++++++++++++++++++++++++++++++++++++++++++--
 1 file changed, 227 insertions(+), 7 deletions(-)

diff --git a/hw/ssi/aspeed_smc.c b/hw/ssi/aspeed_smc.c
index 24c78aa57537..9596ea94a3bc 100644
--- a/hw/ssi/aspeed_smc.c
+++ b/hw/ssi/aspeed_smc.c
@@ -26,8 +26,10 @@
 #include "hw/sysbus.h"
 #include "sysemu/sysemu.h"
 #include "qemu/log.h"
+#include "qemu/coroutine.h"
 #include "include/qemu/error-report.h"
 #include "exec/address-spaces.h"
+#include "sysemu/dma.h"
 
 #include "hw/ssi/aspeed_smc.h"
 
@@ -104,10 +106,10 @@
 #define   DMA_CTRL_DELAY_SHIFT  8
 #define   DMA_CTRL_FREQ_MASK    0xf
 #define   DMA_CTRL_FREQ_SHIFT   4
-#define   DMA_CTRL_MODE         (1 << 3)
+#define   DMA_CTRL_CALIB        (1 << 3)
 #define   DMA_CTRL_CKSUM        (1 << 2)
-#define   DMA_CTRL_DIR          (1 << 1)
-#define   DMA_CTRL_EN           (1 << 0)
+#define   DMA_CTRL_WRITE        (1 << 1)
+#define   DMA_CTRL_ENABLE       (1 << 0)
 
 /* DMA Flash Side Address */
 #define R_DMA_FLASH_ADDR  (0x84 / 4)
@@ -136,6 +138,14 @@
 #define ASPEED_SOC_SPI_FLASH_BASE   0x30000000
 #define ASPEED_SOC_SPI2_FLASH_BASE  0x38000000
 
+/*
+ * DMA address and size encoding
+ */
+#define DMA_LENGTH(x)           (((x) & ~0xFE000003))
+#define DMA_DRAM_ADDR(base, x)  (((x) & ~0xE0000003) | base)
+#define DMA_FLASH_ADDR(x)       (((x) & ~0xE0000003) | \
+                                 ASPEED_SOC_FMC_FLASH_BASE)
+
 /* Flash opcodes. */
 #define SPI_OP_READ       0x03    /* Read data bytes (low frequency) */
 #define SPI_OP_WRDI       0x04    /* Write disable */
@@ -629,9 +639,6 @@ static void aspeed_smc_reset(DeviceState *d)
 
     memset(s->regs, 0, sizeof s->regs);
 
-    /* Pretend DMA is done (u-boot initialization) */
-    s->regs[R_INTR_CTRL] = INTR_CTRL_DMA_STATUS;
-
     /* Unselect all slaves */
     for (i = 0; i < s->num_cs; ++i) {
         s->regs[s->r_ctrl0 + i] |= CTRL_CE_STOP_ACTIVE;
@@ -675,6 +682,11 @@ static uint64_t aspeed_smc_read(void *opaque, hwaddr addr, unsigned int size)
         addr == s->r_timings ||
         addr == s->r_ce_ctrl ||
         addr == R_INTR_CTRL ||
+        (s->ctrl->has_dma && addr == R_DMA_CTRL) ||
+        (s->ctrl->has_dma && addr == R_DMA_FLASH_ADDR) ||
+        (s->ctrl->has_dma && addr == R_DMA_DRAM_ADDR) ||
+        (s->ctrl->has_dma && addr == R_DMA_LEN) ||
+        (s->ctrl->has_dma && addr == R_DMA_CHECKSUM) ||
         (addr >= R_SEG_ADDR0 && addr < R_SEG_ADDR0 + s->ctrl->max_slaves) ||
         (addr >= s->r_ctrl0 && addr < s->r_ctrl0 + s->num_cs)) {
         return s->regs[addr];
@@ -685,6 +697,202 @@ static uint64_t aspeed_smc_read(void *opaque, hwaddr addr, unsigned int size)
     }
 }
 
+typedef struct AspeedDmaCo {
+    AspeedSMCState *s;
+    int len;
+    uint32_t flash_addr;
+    uint32_t dram_addr;
+    uint32_t checksum;
+    bool direction;
+} AspeedDmaCo;
+
+static void coroutine_fn aspeed_smc_dma_done(AspeedDmaCo *dmaco)
+{
+    AspeedSMCState *s = dmaco->s;
+
+    s->regs[R_INTR_CTRL] |= INTR_CTRL_DMA_STATUS;
+    if (s->regs[R_INTR_CTRL] & INTR_CTRL_DMA_EN) {
+        qemu_irq_raise(s->irq);
+    }
+}
+
+static bool coroutine_fn aspeed_smc_dma_update(AspeedDmaCo *dmaco)
+{
+    AspeedSMCState *s = dmaco->s;
+    bool ret;
+
+    /* add locking on R_DMA_CTRL ? */
+    if (s->regs[R_DMA_CTRL] & DMA_CTRL_ENABLE) {
+        s->regs[R_DMA_FLASH_ADDR] = dmaco->flash_addr;
+        s->regs[R_DMA_DRAM_ADDR] = dmaco->dram_addr;
+        s->regs[R_DMA_LEN] = dmaco->len - 4;
+        s->regs[R_DMA_CHECKSUM] = dmaco->checksum;
+        ret = true;
+    } else {
+        ret = false;
+    }
+
+    return ret;
+}
+
+/*
+ * Accumulate the result of the reads in a register. It will be used
+ * later to do timing calibration.
+ */
+static void coroutine_fn aspeed_smc_dma_checksum(void* opaque)
+{
+    AspeedDmaCo *dmaco = opaque;
+    uint32_t data;
+
+    while (dmaco->len) {
+        /* check for disablement and update register values */
+        if (!aspeed_smc_dma_update(dmaco)) {
+            goto out;
+        }
+
+        cpu_physical_memory_read(dmaco->flash_addr, &data, 4);
+        dmaco->checksum += data;
+        dmaco->flash_addr += 4;
+        dmaco->len -= 4;
+    }
+
+    aspeed_smc_dma_done(dmaco);
+out:
+    g_free(dmaco);
+}
+
+static void coroutine_fn aspeed_smc_dma_rw(void* opaque)
+{
+    AspeedDmaCo *dmaco = opaque;
+    uint32_t data;
+
+    while (dmaco->len) {
+        /* check for disablement and update register values */
+        if (!aspeed_smc_dma_update(dmaco)) {
+            goto out;
+        }
+
+        if (dmaco->direction) {
+            dma_memory_read(&address_space_memory, dmaco->dram_addr, &data, 4);
+            cpu_physical_memory_write(dmaco->flash_addr, &data, 4);
+        } else {
+            cpu_physical_memory_read(dmaco->flash_addr, &data, 4);
+            dma_memory_write(&address_space_memory, dmaco->dram_addr,
+                             &data, 4);
+        }
+
+        dmaco->flash_addr += 4;
+        dmaco->dram_addr += 4;
+        dmaco->len -= 4;
+    }
+
+    aspeed_smc_dma_done(dmaco);
+out:
+    g_free(dmaco);
+}
+
+
+static void aspeed_smc_dma_stop(AspeedSMCState *s)
+{
+    /*
+     * When the DMA is disabled, INTR_CTRL_DMA_STATUS=0 means the
+     * engine is idle
+     */
+    s->regs[R_INTR_CTRL] &= ~INTR_CTRL_DMA_STATUS;
+    s->regs[R_DMA_CHECKSUM] = 0x0;
+    s->regs[R_DMA_FLASH_ADDR] = 0;
+    s->regs[R_DMA_DRAM_ADDR] = 0;
+    s->regs[R_DMA_LEN] = 0;
+
+    /*
+     * Lower DMA irq even in any case. The IRQ control register could
+     * have been cleared before disabling the DMA.
+     */
+    qemu_irq_lower(s->irq);
+}
+
+typedef struct AspeedDmaRequest {
+    Coroutine *co;
+    QEMUBH *bh;
+} AspeedDmaRequest;
+
+static void aspeed_smc_dma_run(void *opaque)
+{
+    AspeedDmaRequest *dmareq = opaque;
+
+    qemu_coroutine_enter(dmareq->co);
+    qemu_bh_delete(dmareq->bh);
+    g_free(dmareq);
+}
+
+static void aspeed_smc_dma_schedule(Coroutine *co)
+{
+    AspeedDmaRequest *dmareq;
+
+    dmareq = g_new0(AspeedDmaRequest, 1);
+
+    dmareq->co = co;
+    dmareq->bh = qemu_bh_new(aspeed_smc_dma_run, dmareq);
+    qemu_bh_schedule(dmareq->bh);
+}
+
+static void aspeed_smc_dma_start(void *opaque)
+{
+    AspeedSMCState *s = opaque;
+    AspeedDmaCo *dmaco;
+    Coroutine *co;
+
+    /* freed in the coroutine */
+    dmaco = g_new0(AspeedDmaCo, 1);
+
+    /* A DMA transaction has a minimum of 4 bytes */
+    dmaco->len        = s->regs[R_DMA_LEN] + 4;
+    dmaco->flash_addr = s->regs[R_DMA_FLASH_ADDR];
+    dmaco->dram_addr  = s->regs[R_DMA_DRAM_ADDR];
+    dmaco->direction  = (s->regs[R_DMA_CTRL] & DMA_CTRL_WRITE);
+    dmaco->s          = s;
+
+    if (s->regs[R_DMA_CTRL] & DMA_CTRL_CKSUM) {
+        co = qemu_coroutine_create(aspeed_smc_dma_checksum, dmaco);
+    } else {
+        co = qemu_coroutine_create(aspeed_smc_dma_rw, dmaco);
+    }
+
+    aspeed_smc_dma_schedule(co);
+}
+
+/*
+ * This is to run one DMA at a time. When INTR_CTRL_DMA_STATUS becomes
+ * 1, the DMA has completed and a new DMA can start even if the result
+ * of the previous was not collected.
+ */
+static bool aspeed_smc_dma_in_progress(AspeedSMCState *s)
+{
+    bool ret = (s->regs[R_DMA_CTRL] & DMA_CTRL_ENABLE) &&
+        !(s->regs[R_INTR_CTRL] & INTR_CTRL_DMA_STATUS);
+    return ret;
+}
+
+static void aspeed_smc_dma_ctrl(AspeedSMCState *s, uint64_t dma_ctrl)
+{
+    if (dma_ctrl & DMA_CTRL_ENABLE) {
+        /* add locking on R_DMA_CTRL ? */
+        if (aspeed_smc_dma_in_progress(s)) {
+            qemu_log_mask(LOG_GUEST_ERROR, "%s: DMA in progress\n",
+                          __func__);
+            return;
+        }
+
+        s->regs[R_DMA_CTRL] = dma_ctrl;
+
+        aspeed_smc_dma_start(s);
+    } else {
+        s->regs[R_DMA_CTRL] = dma_ctrl;
+
+        aspeed_smc_dma_stop(s);
+    }
+}
+
 static void aspeed_smc_write(void *opaque, hwaddr addr, uint64_t data,
                              unsigned int size)
 {
@@ -715,6 +923,16 @@ static void aspeed_smc_write(void *opaque, hwaddr addr, uint64_t data,
         if (value != s->regs[R_SEG_ADDR0 + cs]) {
             aspeed_smc_flash_set_segment(s, cs, value);
         }
+    } else if (addr == R_INTR_CTRL) {
+        s->regs[addr] = value;
+    } else if (s->ctrl->has_dma && addr == R_DMA_CTRL) {
+        aspeed_smc_dma_ctrl(s, value);
+    } else if (s->ctrl->has_dma && addr == R_DMA_DRAM_ADDR) {
+        s->regs[addr] = DMA_DRAM_ADDR(s->sdram_base, value);
+    } else if (s->ctrl->has_dma && addr == R_DMA_FLASH_ADDR) {
+        s->regs[addr] = DMA_FLASH_ADDR(value);
+    } else if (s->ctrl->has_dma && addr == R_DMA_LEN) {
+        s->regs[addr] = DMA_LENGTH(value);
     } else {
         qemu_log_mask(LOG_UNIMP, "%s: not implemented: 0x%" HWADDR_PRIx "\n",
                       __func__, addr);
@@ -747,6 +965,9 @@ static void aspeed_smc_realize(DeviceState *dev, Error **errp)
     s->r_timings = s->ctrl->r_timings;
     s->conf_enable_w0 = s->ctrl->conf_enable_w0;
 
+    /* DMA irq */
+    sysbus_init_irq(sbd, &s->irq);
+
     /* Enforce some real HW limits */
     if (s->num_cs > s->ctrl->max_slaves) {
         qemu_log_mask(LOG_GUEST_ERROR, "%s: num_cs cannot exceed: %d\n",
@@ -757,7 +978,6 @@ static void aspeed_smc_realize(DeviceState *dev, Error **errp)
     s->spi = ssi_create_bus(dev, "spi");
 
     /* Setup cs_lines for slaves */
-    sysbus_init_irq(sbd, &s->irq);
     s->cs_lines = g_new0(qemu_irq, s->num_cs);
     ssi_auto_connect_slaves(dev, s->cs_lines, s->spi);
 
-- 
2.7.4



More information about the openbmc mailing list