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 two custom address spaces for DMAs: one for the AHB window of the FMC flash devices and one for the DRAM. The latter is populated using a "dram" link set from the machine with the RAM container region. Signed-off-by: Cédric Le Goater <c...@kaod.org> Acked-by: Joel Stanley <j...@jms.id.au> --- include/hw/ssi/aspeed_smc.h | 6 + hw/arm/aspeed.c | 2 + hw/arm/aspeed_soc.c | 2 + hw/ssi/aspeed_smc.c | 228 +++++++++++++++++++++++++++++++++++- 4 files changed, 232 insertions(+), 6 deletions(-) diff --git a/include/hw/ssi/aspeed_smc.h b/include/hw/ssi/aspeed_smc.h index 591279ba1f43..453357cc09bf 100644 --- a/include/hw/ssi/aspeed_smc.h +++ b/include/hw/ssi/aspeed_smc.h @@ -45,6 +45,8 @@ typedef struct AspeedSMCController { hwaddr flash_window_base; uint32_t flash_window_size; bool has_dma; + hwaddr dma_flash_mask; + hwaddr dma_dram_mask; uint32_t nregs; } AspeedSMCController; @@ -100,6 +102,10 @@ typedef struct AspeedSMCState { /* for DMA support */ uint64_t sdram_base; + AddressSpace flash_as; + MemoryRegion *dram_mr; + AddressSpace dram_as; + AspeedSMCFlash *flashes; uint8_t snoop_index; diff --git a/hw/arm/aspeed.c b/hw/arm/aspeed.c index 7f01df1b61d8..e404f8b244a9 100644 --- a/hw/arm/aspeed.c +++ b/hw/arm/aspeed.c @@ -178,6 +178,8 @@ static void aspeed_board_init(MachineState *machine, &error_abort); object_property_set_int(OBJECT(&bmc->soc), smp_cpus, "num-cpus", &error_abort); + object_property_set_link(OBJECT(&bmc->soc), OBJECT(&bmc->ram_container), + "dram", &error_abort); if (machine->kernel_filename) { /* * When booting with a -kernel command line there is no u-boot diff --git a/hw/arm/aspeed_soc.c b/hw/arm/aspeed_soc.c index 02feb4361ba4..8a1545545faf 100644 --- a/hw/arm/aspeed_soc.c +++ b/hw/arm/aspeed_soc.c @@ -212,6 +212,8 @@ static void aspeed_soc_init(Object *obj) sc->info->fmc_typename); object_property_add_alias(obj, "num-cs", OBJECT(&s->fmc), "num-cs", &error_abort); + object_property_add_alias(obj, "dram", OBJECT(&s->fmc), "dram", + &error_abort); for (i = 0; i < sc->info->spis_num; i++) { sysbus_init_child_obj(obj, "spi[*]", OBJECT(&s->spi[i]), diff --git a/hw/ssi/aspeed_smc.c b/hw/ssi/aspeed_smc.c index 81f2fb7f707a..e00cdebae232 100644 --- a/hw/ssi/aspeed_smc.c +++ b/hw/ssi/aspeed_smc.c @@ -23,11 +23,13 @@ */ #include "qemu/osdep.h" +#include "qapi/error.h" #include "hw/sysbus.h" #include "sysemu/sysemu.h" #include "qemu/log.h" #include "qemu/module.h" #include "qemu/error-report.h" +#include "exec/address-spaces.h" #include "hw/ssi/aspeed_smc.h" @@ -110,8 +112,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) @@ -143,6 +145,24 @@ #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 0x40000000 - 0x5FFFFFFF (AST2400) + * 0x80000000 - 0xBFFFFFFF (AST2500) + * + * 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_ADDR(s, val) ((s)->sdram_base | \ + ((val) & (s)->ctrl->dma_dram_mask)) +#define DMA_FLASH_ADDR(s, val) ((s)->ctrl->flash_window_base | \ + ((val) & (s)->ctrl->dma_flash_mask)) +#define DMA_LENGTH(val) ((val) & 0x01FFFFFC) + /* Flash opcodes. */ #define SPI_OP_READ 0x03 /* Read data bytes (low frequency) */ @@ -212,6 +232,8 @@ static const AspeedSMCController controllers[] = { .flash_window_base = ASPEED_SOC_FMC_FLASH_BASE, .flash_window_size = 0x10000000, .has_dma = true, + .dma_flash_mask = 0x0FFFFFFC, + .dma_dram_mask = 0x1FFFFFFC, .nregs = ASPEED_SMC_R_MAX, }, { .name = "aspeed.smc.spi", @@ -238,6 +260,8 @@ static const AspeedSMCController controllers[] = { .flash_window_base = ASPEED_SOC_FMC_FLASH_BASE, .flash_window_size = 0x10000000, .has_dma = true, + .dma_flash_mask = 0x0FFFFFFC, + .dma_dram_mask = 0x3FFFFFFC, .nregs = ASPEED_SMC_R_MAX, }, { .name = "aspeed.smc.ast2500-spi1", @@ -730,9 +754,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; @@ -773,6 +794,11 @@ static uint64_t aspeed_smc_read(void *opaque, hwaddr addr, unsigned int size) addr == s->r_ce_ctrl || addr == R_INTR_CTRL || addr == R_DUMMY_DATA || + (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]; @@ -783,6 +809,155 @@ 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->flash_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->dram_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->flash_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->flash_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->dram_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) { @@ -808,6 +983,16 @@ static void aspeed_smc_write(void *opaque, hwaddr addr, uint64_t data, } } else if (addr == R_DUMMY_DATA) { s->regs[addr] = value & 0xff; + } 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, value); + } else if (s->ctrl->has_dma && addr == R_DMA_FLASH_ADDR) { + s->regs[addr] = DMA_FLASH_ADDR(s, 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); @@ -822,6 +1007,28 @@ static const MemoryRegionOps aspeed_smc_ops = { .valid.unaligned = true, }; + +/* + * Initialize the custom address spaces for DMAs + */ +static void aspeed_smc_dma_setup(AspeedSMCState *s, Error **errp) +{ + char *name; + + if (!s->dram_mr) { + error_setg(errp, TYPE_ASPEED_SMC ": 'dram' link not set"); + return; + } + + name = g_strdup_printf("%s-dma-flash", s->ctrl->name); + address_space_init(&s->flash_as, &s->mmio_flash, name); + g_free(name); + + name = g_strdup_printf("%s-dma-dram", s->ctrl->name); + address_space_init(&s->dram_as, s->dram_mr, name); + g_free(name); +} + static void aspeed_smc_realize(DeviceState *dev, Error **errp) { SysBusDevice *sbd = SYS_BUS_DEVICE(dev); @@ -847,10 +1054,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); @@ -897,6 +1106,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, errp); + } } static const VMStateDescription vmstate_aspeed_smc = { @@ -914,6 +1128,8 @@ static const VMStateDescription vmstate_aspeed_smc = { static Property aspeed_smc_properties[] = { DEFINE_PROP_UINT32("num-cs", AspeedSMCState, num_cs, 1), DEFINE_PROP_UINT64("sdram-base", AspeedSMCState, sdram_base, 0), + DEFINE_PROP_LINK("dram", AspeedSMCState, dram_mr, + TYPE_MEMORY_REGION, MemoryRegion *), DEFINE_PROP_END_OF_LIST(), }; -- 2.21.0