From: Gerd Hoffmann <kra...@redhat.com> First draft of a fw_cfg dma interface. Designed as add-on to the extisting fw_cfg interface, i.e. there is no select register. There are four 32bit registers: Target address (low and high bits), transfer length, control register.
See docs/specs/fw_cfg.txt update for details. Possible improvements (can be done incremental): * Better error reporting. Right now we throw errors on invalid addresses only. We could also throw errors on invalid reads/writes instead of using fw_cfg_read (return zeros on error) behavior. * Use memcpy instead of calling fw_cfg_read in a loop. Signed-off-by: Gerd Hoffmann <kra...@redhat.com> --- docs/specs/fw_cfg.txt | 35 ++++++++++ hw/arm/virt.c | 2 +- hw/nvram/fw_cfg.c | 159 ++++++++++++++++++++++++++++++++++++++++++++-- include/hw/nvram/fw_cfg.h | 5 +- 4 files changed, 194 insertions(+), 7 deletions(-) diff --git a/docs/specs/fw_cfg.txt b/docs/specs/fw_cfg.txt index 5bc7b96..64d9192 100644 --- a/docs/specs/fw_cfg.txt +++ b/docs/specs/fw_cfg.txt @@ -132,6 +132,41 @@ Selector Reg. Range Usage In practice, the number of allowed firmware configuration items is given by the value of FW_CFG_MAX_ENTRY (see fw_cfg.h). += Guest-side DMA Interface = + +== Registers == + +This does not replace the existing fw_cfg interface, it is an add-on. +Specifically there is no select register in the dma interface, guests +must use the select register of the classic fw_cfg interface instead. + +There are four 32bit registers: Target address (low and high bits), +transfer length, control register. + +== Protocol == + +Probing for dma support being available: Write target address, read it +back, verify you got back the value you wrote. + +Kick a transfer: Write select, target address and transfer length +registers (order doesn't matter). Then flip read bit in the control +register to '1'. Also make sure the error bit is cleared. + +Check result: Read control register: + error bit set -> something went wrong. + all bits cleared -> transfer finished successfully. + otherwise -> transfer still in progress (doesn't happen + today due to implementation not being async, + but may in the future). + +Target address goes up and transfer length goes down as the transfer +happens, so after a successfull transfer the length register is zero +and the address register points right after the memory block written. + +If a partial transfer happened before an error occured the address and +length registers indicate how much data has been transfered +successfully. + = Host-side API = The following functions are available to the QEMU programmer for adding diff --git a/hw/arm/virt.c b/hw/arm/virt.c index 4846892..374660c 100644 --- a/hw/arm/virt.c +++ b/hw/arm/virt.c @@ -612,7 +612,7 @@ static void create_fw_cfg(const VirtBoardInfo *vbi) hwaddr size = vbi->memmap[VIRT_FW_CFG].size; char *nodename; - fw_cfg_init_mem_wide(base + 8, base, 8); + fw_cfg_init_mem_wide(base + 8, base, 8, 0, NULL); nodename = g_strdup_printf("/fw-cfg@%" PRIx64, base); qemu_fdt_add_subnode(vbi->fdt, nodename); diff --git a/hw/nvram/fw_cfg.c b/hw/nvram/fw_cfg.c index 88481b7..5bcd0e0 100644 --- a/hw/nvram/fw_cfg.c +++ b/hw/nvram/fw_cfg.c @@ -23,6 +23,7 @@ */ #include "hw/hw.h" #include "sysemu/sysemu.h" +#include "sysemu/dma.h" #include "hw/isa/isa.h" #include "hw/nvram/fw_cfg.h" #include "hw/sysbus.h" @@ -42,6 +43,18 @@ #define FW_CFG_IO(obj) OBJECT_CHECK(FWCfgIoState, (obj), TYPE_FW_CFG_IO) #define FW_CFG_MEM(obj) OBJECT_CHECK(FWCfgMemState, (obj), TYPE_FW_CFG_MEM) +/* fw_cfg dma registers */ +#define FW_CFG_DMA_ADDR_LO 0 +#define FW_CFG_DMA_ADDR_HI 4 +#define FW_CFG_DMA_LENGTH 8 +#define FW_CFG_DMA_CONTROL 12 +#define FW_CFG_DMA_SIZE 16 + +/* FW_CFG_DMA_CONTROL bits */ +#define FW_CFG_DMA_CTL_ERROR 0x01 +#define FW_CFG_DMA_CTL_READ 0x02 +#define FW_CFG_DMA_CTL_MASK 0x03 + typedef struct FWCfgEntry { uint32_t len; uint8_t *data; @@ -59,6 +72,12 @@ struct FWCfgState { uint16_t cur_entry; uint32_t cur_offset; Notifier machine_ready; + + bool dma_enabled; + AddressSpace *dma_as; + dma_addr_t dma_addr; + uint32_t dma_len; + uint32_t dma_ctl; }; struct FWCfgIoState { @@ -75,7 +94,10 @@ struct FWCfgMemState { FWCfgState parent_obj; /*< public >*/ - MemoryRegion ctl_iomem, data_iomem; + MemoryRegion ctl_iomem, data_iomem, dma_iomem; +#if 0 + hwaddr ctl_addr, data_addr, dma_addr; +#endif uint32_t data_width; MemoryRegionOps wide_data_ops; }; @@ -294,6 +316,88 @@ static void fw_cfg_data_mem_write(void *opaque, hwaddr addr, } while (i); } +static void fw_cfg_dma_transfer(FWCfgState *s) +{ + dma_addr_t len; + uint8_t *ptr; + uint32_t i; + + if (s->dma_ctl & FW_CFG_DMA_CTL_ERROR) { + return; + } + if (!(s->dma_ctl & FW_CFG_DMA_CTL_READ)) { + return; + } + + while (s->dma_len > 0) { + len = s->dma_len; + ptr = dma_memory_map(s->dma_as, s->dma_addr, &len, + DMA_DIRECTION_FROM_DEVICE); + if (!ptr || !len) { + s->dma_ctl |= FW_CFG_DMA_CTL_ERROR; + return; + } + + for (i = 0; i < len; i++) { + ptr[i] = fw_cfg_read(s); + } + + s->dma_addr += i; + s->dma_len -= i; + dma_memory_unmap(s->dma_as, ptr, len, + DMA_DIRECTION_FROM_DEVICE, i); + } + s->dma_ctl = 0; +} + +static uint64_t fw_cfg_dma_mem_read(void *opaque, hwaddr addr, + unsigned size) +{ + FWCfgState *s = opaque; + uint64_t ret = 0; + + switch (addr) { + case FW_CFG_DMA_ADDR_LO: + ret = s->dma_addr & 0xffffffff; + break; + case FW_CFG_DMA_ADDR_HI: + ret = (s->dma_addr >> 32) & 0xffffffff; + break; + case FW_CFG_DMA_LENGTH: + ret = s->dma_len; + break; + case FW_CFG_DMA_CONTROL: + ret = s->dma_ctl; + break; + } + return ret; +} + +static void fw_cfg_dma_mem_write(void *opaque, hwaddr addr, + uint64_t value, unsigned size) +{ + FWCfgState *s = opaque; + + switch (addr) { + case FW_CFG_DMA_ADDR_LO: + s->dma_addr &= ~((dma_addr_t)0xffffffff); + s->dma_addr |= value; + break; + case FW_CFG_DMA_ADDR_HI: + s->dma_addr &= ~(((dma_addr_t)0xffffffff) << 32); + s->dma_addr |= ((dma_addr_t)value) << 32; + break; + case FW_CFG_DMA_LENGTH: + s->dma_len = value; + break; + case FW_CFG_DMA_CONTROL: + value &= FW_CFG_DMA_CTL_MASK; + s->dma_ctl = value; + fw_cfg_dma_transfer(s); + break; + } +} + static bool fw_cfg_data_mem_valid(void *opaque, hwaddr addr, unsigned size, bool is_write) { @@ -361,6 +465,16 @@ static const MemoryRegionOps fw_cfg_comb_mem_ops = { .valid.accepts = fw_cfg_comb_valid, }; +static const MemoryRegionOps fw_cfg_dma_mem_ops = { + .read = fw_cfg_dma_mem_read, + .write = fw_cfg_dma_mem_write, + .endianness = DEVICE_BIG_ENDIAN, + .valid = { + .min_access_size = 4, + .max_access_size = 4, + }, +}; + static void fw_cfg_reset(DeviceState *d) { FWCfgState *s = FW_CFG(d); @@ -401,6 +515,23 @@ static bool is_version_1(void *opaque, int version_id) return version_id == 1; } +static VMStateDescription vmstate_fw_cfg_dma = { + .name = "fw_cfg/dma", + .fields = (VMStateField[]) { + VMSTATE_UINT64(dma_addr, FWCfgState), + VMSTATE_UINT32(dma_len, FWCfgState), + VMSTATE_UINT32(dma_ctl, FWCfgState), + VMSTATE_END_OF_LIST() + }, +}; + +static bool fw_cfg_dma_enabled(void *opaque) +{ + FWCfgState *s = opaque; + + return s->dma_enabled; +} + static const VMStateDescription vmstate_fw_cfg = { .name = "fw_cfg", .version_id = 2, @@ -410,6 +541,14 @@ static const VMStateDescription vmstate_fw_cfg = { VMSTATE_UINT16_HACK(cur_offset, FWCfgState, is_version_1), VMSTATE_UINT32_V(cur_offset, FWCfgState, 2), VMSTATE_END_OF_LIST() + }, + .subsections = (VMStateSubsection[]) { + { + .vmsd = &vmstate_fw_cfg_dma, + .needed = fw_cfg_dma_enabled, + }, { + /* end of list */ + } } }; @@ -618,8 +757,9 @@ FWCfgState *fw_cfg_init_io(uint32_t iobase) return FW_CFG(dev); } -FWCfgState *fw_cfg_init_mem_wide(hwaddr ctl_addr, hwaddr data_addr, - uint32_t data_width) +FWCfgState *fw_cfg_init_mem_wide(hwaddr ctl_addr, + hwaddr data_addr, uint32_t data_width, + hwaddr dma_addr, AddressSpace *dma_as) { DeviceState *dev; SysBusDevice *sbd; @@ -633,13 +773,20 @@ FWCfgState *fw_cfg_init_mem_wide(hwaddr ctl_addr, hwaddr data_addr, sysbus_mmio_map(sbd, 0, ctl_addr); sysbus_mmio_map(sbd, 1, data_addr); + if (dma_addr && dma_as) { + FW_CFG(dev)->dma_as = dma_as; + FW_CFG(dev)->dma_enabled = true; + sysbus_mmio_map(sbd, 1, dma_addr); + } + return FW_CFG(dev); } FWCfgState *fw_cfg_init_mem(hwaddr ctl_addr, hwaddr data_addr) { return fw_cfg_init_mem_wide(ctl_addr, data_addr, - fw_cfg_data_mem_ops.valid.max_access_size); + fw_cfg_data_mem_ops.valid.max_access_size, + 0, NULL); } @@ -725,6 +872,10 @@ static void fw_cfg_mem_realize(DeviceState *dev, Error **errp) memory_region_init_io(&s->data_iomem, OBJECT(s), data_ops, FW_CFG(s), "fwcfg.data", data_ops->valid.max_access_size); sysbus_init_mmio(sbd, &s->data_iomem); + + memory_region_init_io(&s->dma_iomem, OBJECT(s), &fw_cfg_dma_mem_ops, + FW_CFG(s), "fwcfg.dma", FW_CFG_DMA_SIZE); + sysbus_init_mmio(sbd, &s->dma_iomem); } static void fw_cfg_mem_class_init(ObjectClass *klass, void *data) diff --git a/include/hw/nvram/fw_cfg.h b/include/hw/nvram/fw_cfg.h index e60d3ca..2fe31ea 100644 --- a/include/hw/nvram/fw_cfg.h +++ b/include/hw/nvram/fw_cfg.h @@ -79,8 +79,9 @@ void *fw_cfg_modify_file(FWCfgState *s, const char *filename, void *data, size_t len); FWCfgState *fw_cfg_init_io(uint32_t iobase); FWCfgState *fw_cfg_init_mem(hwaddr ctl_addr, hwaddr data_addr); -FWCfgState *fw_cfg_init_mem_wide(hwaddr ctl_addr, hwaddr data_addr, - uint32_t data_width); +FWCfgState *fw_cfg_init_mem_wide(hwaddr ctl_addr, + hwaddr data_addr, uint32_t data_width, + hwaddr dma_addr, AddressSpace *dma_as); FWCfgState *fw_cfg_find(void); -- 2.4.3