The FMC controller 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.
The model introduces a custom address space for DMAs populated with the required regions : an alias region on the AHB window for the flash devices and another alias on the SDRAM. Our primary need is to support the checksum calculation mode and the model only implements synchronous DMA accesses. Something to improve in the future. Signed-off-by: Cédric Le Goater <c...@kaod.org> --- include/hw/ssi/aspeed_smc.h | 2 + hw/ssi/aspeed_smc.c | 227 +++++++++++++++++++++++++++++++++++- 2 files changed, 223 insertions(+), 6 deletions(-) diff --git a/include/hw/ssi/aspeed_smc.h b/include/hw/ssi/aspeed_smc.h index d7090bb5e9b7..b8b2dfbec280 100644 --- a/include/hw/ssi/aspeed_smc.h +++ b/include/hw/ssi/aspeed_smc.h @@ -100,6 +100,8 @@ typedef struct AspeedSMCState { /* for DMA support */ uint64_t sdram_base; uint64_t max_ram_size; + MemoryRegion dma_mr; + AddressSpace dma_as; AspeedSMCFlash *flashes; } AspeedSMCState; diff --git a/hw/ssi/aspeed_smc.c b/hw/ssi/aspeed_smc.c index 500de6d16d09..625c29ba4ad2 100644 --- a/hw/ssi/aspeed_smc.c +++ b/hw/ssi/aspeed_smc.c @@ -23,10 +23,12 @@ */ #include "qemu/osdep.h" +#include "qapi/error.h" #include "hw/sysbus.h" #include "sysemu/sysemu.h" #include "qemu/log.h" #include "qemu/error-report.h" +#include "exec/address-spaces.h" #include "hw/ssi/aspeed_smc.h" @@ -109,8 +111,8 @@ #define DMA_CTRL_FREQ_SHIFT 4 #define DMA_CTRL_MODE (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) @@ -142,6 +144,21 @@ #define ASPEED_SOC_SPI_FLASH_BASE 0x30000000 #define ASPEED_SOC_SPI2_FLASH_BASE 0x38000000 +/* + * DMA DRAM addresses should be 4 bytes aligned and the valid address + * range is the full address space of the SoC + * + * DMA flash addresses should be 4 bytes aligned and the valid address + * range is 0x20000000 - 0x2FFFFFFF. + * + * DMA length is from 4 bytes to 32MB + * 0: 4 bytes + * 0x7FFFFF: 32M bytes + */ +#define DMA_DRAM_MASK(s) ((s)->max_ram_size - 4) +#define DMA_FLASH_MASK 0x0FFFFFFC +#define DMA_LENGTH_MASK 0x01FFFFFC + /* Flash opcodes. */ #define SPI_OP_READ 0x03 /* Read data bytes (low frequency) */ @@ -625,9 +642,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; @@ -664,6 +678,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->ctrl->max_slaves)) { return s->regs[addr]; @@ -674,6 +693,154 @@ static uint64_t aspeed_smc_read(void *opaque, hwaddr addr, unsigned int size) } } +/* + * Accumulate the result of the reads to provide a checksum that will + * be used to validate the read timing settings. + */ +static void aspeed_smc_dma_checksum(AspeedSMCState *s) +{ + MemTxResult result; + uint32_t data; + + if (s->regs[R_DMA_CTRL] & DMA_CTRL_WRITE) { + qemu_log_mask(LOG_GUEST_ERROR, + "%s: invalid direction for DMA checksum\n", __func__); + return; + } + + while (s->regs[R_DMA_LEN]) { + result = address_space_read(&s->dma_as, s->regs[R_DMA_FLASH_ADDR], + MEMTXATTRS_UNSPECIFIED, + (uint8_t *)&data, 4); + if (result != MEMTX_OK) { + qemu_log_mask(LOG_GUEST_ERROR, "%s: Flash read failed @%08x\n", + __func__, s->regs[R_DMA_FLASH_ADDR]); + return; + } + + /* + * When the DMA is on-going, the DMA registers are updated + * with the current working addresses and length. + */ + s->regs[R_DMA_CHECKSUM] += data; + s->regs[R_DMA_FLASH_ADDR] += 4; + s->regs[R_DMA_LEN] -= 4; + } +} + +static void aspeed_smc_dma_rw(AspeedSMCState *s) +{ + MemTxResult result; + uint32_t data; + + while (s->regs[R_DMA_LEN]) { + if (s->regs[R_DMA_CTRL] & DMA_CTRL_WRITE) { + result = address_space_read(&s->dma_as, s->regs[R_DMA_DRAM_ADDR], + MEMTXATTRS_UNSPECIFIED, + (uint8_t *)&data, 4); + if (result != MEMTX_OK) { + qemu_log_mask(LOG_GUEST_ERROR, "%s: DRAM read failed @%08x\n", + __func__, s->regs[R_DMA_DRAM_ADDR]); + return; + } + + result = address_space_write(&s->dma_as, s->regs[R_DMA_FLASH_ADDR], + MEMTXATTRS_UNSPECIFIED, + (uint8_t *)&data, 4); + if (result != MEMTX_OK) { + qemu_log_mask(LOG_GUEST_ERROR, "%s: Flash write failed @%08x\n", + __func__, s->regs[R_DMA_FLASH_ADDR]); + return; + } + } else { + result = address_space_read(&s->dma_as, s->regs[R_DMA_FLASH_ADDR], + MEMTXATTRS_UNSPECIFIED, + (uint8_t *)&data, 4); + if (result != MEMTX_OK) { + qemu_log_mask(LOG_GUEST_ERROR, "%s: Flash read failed @%08x\n", + __func__, s->regs[R_DMA_FLASH_ADDR]); + return; + } + + result = address_space_write(&s->dma_as, s->regs[R_DMA_DRAM_ADDR], + MEMTXATTRS_UNSPECIFIED, + (uint8_t *)&data, 4); + if (result != MEMTX_OK) { + qemu_log_mask(LOG_GUEST_ERROR, "%s: DRAM write failed @%08x\n", + __func__, s->regs[R_DMA_DRAM_ADDR]); + return; + } + } + + /* + * When the DMA is on-going, the DMA registers are updated + * with the current working addresses and length. + */ + s->regs[R_DMA_FLASH_ADDR] += 4; + s->regs[R_DMA_DRAM_ADDR] += 4; + s->regs[R_DMA_LEN] -= 4; + } +} + +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] = 0; + + /* + * Lower the DMA irq in any case. The IRQ control register could + * have been cleared before disabling the DMA. + */ + qemu_irq_lower(s->irq); +} + +/* + * When INTR_CTRL_DMA_STATUS=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) +{ + return s->regs[R_DMA_CTRL] & DMA_CTRL_ENABLE && + !(s->regs[R_INTR_CTRL] & INTR_CTRL_DMA_STATUS); +} + +static void aspeed_smc_dma_done(AspeedSMCState *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 void aspeed_smc_dma_ctrl(AspeedSMCState *s, uint64_t dma_ctrl) +{ + if (!(dma_ctrl & DMA_CTRL_ENABLE)) { + s->regs[R_DMA_CTRL] = dma_ctrl; + + aspeed_smc_dma_stop(s); + return; + } + + 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; + + if (s->regs[R_DMA_CTRL] & DMA_CTRL_CKSUM) { + aspeed_smc_dma_checksum(s); + } else { + aspeed_smc_dma_rw(s); + } + + aspeed_smc_dma_done(s); +} + static void aspeed_smc_write(void *opaque, hwaddr addr, uint64_t data, unsigned int size) { @@ -697,6 +864,19 @@ 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) { + value &= DMA_DRAM_MASK(s); + s->regs[addr] = s->sdram_base | value; + } else if (s->ctrl->has_dma && addr == R_DMA_FLASH_ADDR) { + value &= DMA_FLASH_MASK; + s->regs[addr] = s->ctrl->flash_window_base | value; + } else if (s->ctrl->has_dma && addr == R_DMA_LEN) { + value &= DMA_LENGTH_MASK; + s->regs[addr] = value; } else { qemu_log_mask(LOG_UNIMP, "%s: not implemented: 0x%" HWADDR_PRIx "\n", __func__, addr); @@ -711,6 +891,34 @@ static const MemoryRegionOps aspeed_smc_ops = { .valid.unaligned = true, }; + +/* + * Populate our custom address space for DMAs with only the regions we + * need : the AHB window for the flash devices and the SDRAM. + */ +static void aspeed_smc_dma_setup(AspeedSMCState *s) +{ + char name[32]; + MemoryRegion *sysmem = get_system_memory(); + MemoryRegion *flash_alias = g_new(MemoryRegion, 1); + MemoryRegion *sdram_alias = g_new(MemoryRegion, 1); + + snprintf(name, sizeof(name), "%s-dma", s->ctrl->name); + memory_region_init(&s->dma_mr, OBJECT(s), name, + s->sdram_base + s->max_ram_size); + address_space_init(&s->dma_as, &s->dma_mr, name); + + snprintf(name, sizeof(name), "%s.flash", s->ctrl->name); + memory_region_init_alias(flash_alias, OBJECT(s), name, &s->mmio_flash, + 0, s->ctrl->flash_window_size); + memory_region_add_subregion(&s->dma_mr, s->ctrl->flash_window_base, + flash_alias); + + memory_region_init_alias(sdram_alias, OBJECT(s), "ram", sysmem, + s->sdram_base, s->max_ram_size); + memory_region_add_subregion(&s->dma_mr, s->sdram_base, sdram_alias); +} + static void aspeed_smc_realize(DeviceState *dev, Error **errp) { SysBusDevice *sbd = SYS_BUS_DEVICE(dev); @@ -736,10 +944,12 @@ static void aspeed_smc_realize(DeviceState *dev, Error **errp) s->num_cs = s->ctrl->max_slaves; } + /* DMA irq. Keep it first for the initialization in the SoC */ + sysbus_init_irq(sbd, &s->irq); + 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); @@ -786,6 +996,11 @@ static void aspeed_smc_realize(DeviceState *dev, Error **errp) memory_region_add_subregion(&s->mmio_flash, offset, &fl->mmio); offset += fl->size; } + + /* DMA support */ + if (s->ctrl->has_dma) { + aspeed_smc_dma_setup(s); + } } static const VMStateDescription vmstate_aspeed_smc = { -- 2.17.1