[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