[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
[Qemu-devel] [PATCH v2 3/5] Implement fw_cfg DMA interface
From: |
Marc Marí |
Subject: |
[Qemu-devel] [PATCH v2 3/5] Implement fw_cfg DMA interface |
Date: |
Mon, 31 Aug 2015 11:10:15 +0200 |
Based on the specifications on docs/specs/fw_cfg.txt
This interface is an addon. The old interface can still be used as usual.
Based on Gerd Hoffman's initial implementation.
Signed-off-by: Marc Marí <address@hidden>
---
hw/arm/virt.c | 2 +-
hw/nvram/fw_cfg.c | 261 +++++++++++++++++++++++++++++++++++++++++++---
include/hw/nvram/fw_cfg.h | 15 ++-
3 files changed, 260 insertions(+), 18 deletions(-)
diff --git a/hw/arm/virt.c b/hw/arm/virt.c
index d5a8417..b88c104 100644
--- a/hw/arm/virt.c
+++ b/hw/arm/virt.c
@@ -620,7 +620,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("/address@hidden" 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..7e5ba96 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"
@@ -30,7 +31,8 @@
#include "qemu/error-report.h"
#include "qemu/config-file.h"
-#define FW_CFG_SIZE 2
+#define FW_CFG_IO_SIZE 12 /* Address aligned to 4 bytes */
+#define FW_CFG_CTL_SIZE 2
#define FW_CFG_NAME "fw_cfg"
#define FW_CFG_PATH "/machine/" FW_CFG_NAME
@@ -42,6 +44,15 @@
#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_VERSION bits */
+#define FW_CFG_VERSION 0x01
+#define FW_CFG_VERSION_DMA 0x02
+
+/* FW_CFG_DMA_CONTROL bits */
+#define FW_CFG_DMA_CTL_ERROR 0x01
+#define FW_CFG_DMA_CTL_READ 0x02
+#define FW_CFG_DMA_CTL_SKIP 0x04
+
typedef struct FWCfgEntry {
uint32_t len;
uint8_t *data;
@@ -59,6 +70,10 @@ struct FWCfgState {
uint16_t cur_entry;
uint32_t cur_offset;
Notifier machine_ready;
+
+ bool dma_enabled;
+ AddressSpace *dma_as;
+ dma_addr_t dma_addr;
};
struct FWCfgIoState {
@@ -75,7 +90,7 @@ struct FWCfgMemState {
FWCfgState parent_obj;
/*< public >*/
- MemoryRegion ctl_iomem, data_iomem;
+ MemoryRegion ctl_iomem, data_iomem, dma_iomem;
uint32_t data_width;
MemoryRegionOps wide_data_ops;
};
@@ -294,6 +309,142 @@ 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;
+ void *addr;
+ FWCfgDmaAccess dma;
+ int arch = !!(s->cur_entry & FW_CFG_ARCH_LOCAL);
+ FWCfgEntry *e = &s->entries[arch][s->cur_entry & FW_CFG_ENTRY_MASK];
+
+ len = sizeof(dma);
+ addr = dma_memory_map(s->dma_as, s->dma_addr, &len,
+ DMA_DIRECTION_FROM_DEVICE);
+
+ s->dma_addr = 0;
+
+ if (!addr || !len) {
+ return;
+ }
+
+ dma.address = be64_to_cpu(*(uint64_t *)(addr +
+ offsetof(FWCfgDmaAccess, address)));
+ dma.length = be32_to_cpu(*(uint32_t *)(addr +
+ offsetof(FWCfgDmaAccess, length)));
+ dma.control = be32_to_cpu(*(uint32_t *)(addr +
+ offsetof(FWCfgDmaAccess, control)));
+
+ if (dma.control & FW_CFG_DMA_CTL_ERROR) {
+ goto out;
+ }
+
+ if (!(dma.control & (FW_CFG_DMA_CTL_READ | FW_CFG_DMA_CTL_SKIP))) {
+ goto out;
+ }
+
+ while (dma.length > 0) {
+ if (s->cur_entry == FW_CFG_INVALID || !e->data ||
+ s->cur_offset >= e->len) {
+ len = dma.length;
+
+ /* If the access is not a read access, it will be a skip access,
+ * tested before.
+ */
+ if (dma.control & FW_CFG_DMA_CTL_READ) {
+ ptr = dma_memory_map(s->dma_as, dma.address, &len,
+ DMA_DIRECTION_FROM_DEVICE);
+ if (!ptr || !len) {
+ dma.control |= FW_CFG_DMA_CTL_ERROR;
+ goto out;
+ }
+
+ memset(ptr, 0, len);
+
+ dma_memory_unmap(s->dma_as, ptr, len,
+ DMA_DIRECTION_FROM_DEVICE, len);
+ }
+
+ } else {
+ if (dma.length <= e->len) {
+ len = dma.length;
+ } else {
+ len = e->len;
+ }
+
+ if (e->read_callback) {
+ e->read_callback(e->callback_opaque, s->cur_offset);
+ }
+
+ /* If the access is not a read access, it will be a skip access,
+ * tested before.
+ */
+ if (dma.control & FW_CFG_DMA_CTL_READ) {
+ ptr = dma_memory_map(s->dma_as, dma.address, &len,
+ DMA_DIRECTION_FROM_DEVICE);
+ if (!ptr || !len) {
+ dma.control |= FW_CFG_DMA_CTL_ERROR;
+ goto out;
+ }
+
+ memcpy(ptr, &e->data[s->cur_offset], len);
+
+ dma_memory_unmap(s->dma_as, ptr, len,
+ DMA_DIRECTION_FROM_DEVICE, len);
+ }
+
+ s->cur_offset += len;
+
+ }
+
+ dma.address += len;
+ dma.length -= len;
+ dma.control = 0;
+
+ *(uint64_t *)(addr + offsetof(FWCfgDmaAccess, address)) =
+ cpu_to_be64(dma.address);
+ *(uint32_t *)(addr + offsetof(FWCfgDmaAccess, length)) =
+ cpu_to_be32(dma.length);
+ *(uint32_t *)(addr + offsetof(FWCfgDmaAccess, control)) =
+ cpu_to_be32(dma.control);
+ }
+
+ trace_fw_cfg_read(s, 0);
+
+out:
+ dma_memory_unmap(s->dma_as, addr, sizeof(dma),
+ DMA_DIRECTION_FROM_DEVICE, sizeof(dma));
+ return;
+
+}
+
+static void fw_cfg_dma_mem_write(void *opaque, hwaddr addr,
+ uint64_t value, unsigned size)
+{
+ FWCfgState *s = opaque;
+
+ if (size == 4) {
+ if (addr == 0) {
+ /* FWCfgDmaAccess high address */
+ s->dma_addr = (0xFFFFFFFF & value) << 32;
+ } else if (addr == 4) {
+ /* FWCfgDmaAccess low address */
+ s->dma_addr |= value;
+ fw_cfg_dma_transfer(s);
+ }
+ } else if (size == 8 && addr == 0) {
+ s->dma_addr = value;
+ fw_cfg_dma_transfer(s);
+ }
+}
+
+static bool fw_cfg_dma_mem_valid(void *opaque, hwaddr addr,
+ unsigned size, bool is_write)
+{
+ return is_write && ((size == 4 && (addr == 0 || addr == 4)) ||
+ (size == 8 && addr == 0));
+}
+
static bool fw_cfg_data_mem_valid(void *opaque, hwaddr addr,
unsigned size, bool is_write)
{
@@ -321,20 +472,35 @@ static uint64_t fw_cfg_comb_read(void *opaque, hwaddr
addr,
static void fw_cfg_comb_write(void *opaque, hwaddr addr,
uint64_t value, unsigned size)
{
- switch (size) {
+ FWCfgState *s;
+
+ switch (addr) {
+ case 0:
+ fw_cfg_select(opaque, (uint16_t)value);
+ break;
case 1:
fw_cfg_write(opaque, (uint8_t)value);
break;
- case 2:
- fw_cfg_select(opaque, (uint16_t)value);
+ case 4:
+ /* FWCfgDmaAccess low address */
+ s = opaque;
+ s->dma_addr |= value;
+ fw_cfg_dma_transfer(s);
break;
+ case 8:
+ /* FWCfgDmaAccess high address */
+ s = opaque;
+ s->dma_addr = (0xFFFFFFFF & value) << 32;
}
}
static bool fw_cfg_comb_valid(void *opaque, hwaddr addr,
unsigned size, bool is_write)
{
- return (size == 1) || (is_write && size == 2);
+ FWCfgState *s = opaque;
+
+ return (size == 1) || (is_write && size == 2) ||
+ (s->dma_enabled && is_write && addr >= 4);
}
static const MemoryRegionOps fw_cfg_ctl_mem_ops = {
@@ -361,6 +527,12 @@ static const MemoryRegionOps fw_cfg_comb_mem_ops = {
.valid.accepts = fw_cfg_comb_valid,
};
+static const MemoryRegionOps fw_cfg_dma_mem_ops = {
+ .write = fw_cfg_dma_mem_write,
+ .endianness = DEVICE_BIG_ENDIAN,
+ .valid.accepts = fw_cfg_dma_mem_valid,
+};
+
static void fw_cfg_reset(DeviceState *d)
{
FWCfgState *s = FW_CFG(d);
@@ -401,6 +573,22 @@ static bool is_version_1(void *opaque, int version_id)
return version_id == 1;
}
+static bool fw_cfg_dma_enabled(void *opaque)
+{
+ FWCfgState *s = opaque;
+
+ return s->dma_enabled;
+}
+
+static VMStateDescription vmstate_fw_cfg_dma = {
+ .name = "fw_cfg/dma",
+ .needed = fw_cfg_dma_enabled,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT64(dma_addr, FWCfgState),
+ VMSTATE_END_OF_LIST()
+ },
+};
+
static const VMStateDescription vmstate_fw_cfg = {
.name = "fw_cfg",
.version_id = 2,
@@ -410,6 +598,10 @@ 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 = (const VMStateDescription*[]) {
+ &vmstate_fw_cfg_dma,
+ NULL,
}
};
@@ -595,7 +787,6 @@ static void fw_cfg_init1(DeviceState *dev)
qdev_init_nofail(dev);
fw_cfg_add_bytes(s, FW_CFG_SIGNATURE, (char *)"QEMU", 4);
- fw_cfg_add_i32(s, FW_CFG_ID, 1);
fw_cfg_add_bytes(s, FW_CFG_UUID, qemu_uuid, 16);
fw_cfg_add_i16(s, FW_CFG_NOGRAPHIC, (uint16_t)(display_type ==
DT_NOGRAPHIC));
fw_cfg_add_i16(s, FW_CFG_NB_CPUS, (uint16_t)smp_cpus);
@@ -607,22 +798,45 @@ static void fw_cfg_init1(DeviceState *dev)
qemu_add_machine_init_done_notifier(&s->machine_ready);
}
-FWCfgState *fw_cfg_init_io(uint32_t iobase)
+FWCfgState *fw_cfg_init_io_dma(uint32_t iobase, AddressSpace *dma_as)
{
DeviceState *dev;
+ FWCfgState *s;
+ uint32_t version = FW_CFG_VERSION;
dev = qdev_create(NULL, TYPE_FW_CFG_IO);
qdev_prop_set_uint32(dev, "iobase", iobase);
+
fw_cfg_init1(dev);
+ s = FW_CFG(dev);
+
+ if (dma_as) {
+ /* 64 bits for the address field */
+ s->dma_as = dma_as;
+ s->dma_enabled = true;
+ s->dma_addr = 0;
+
+ version |= FW_CFG_VERSION_DMA;
+ }
+
+ fw_cfg_add_i32(s, FW_CFG_ID, version);
- return FW_CFG(dev);
+ return s;
}
-FWCfgState *fw_cfg_init_mem_wide(hwaddr ctl_addr, hwaddr data_addr,
- uint32_t data_width)
+FWCfgState *fw_cfg_init_io(uint32_t iobase)
+{
+ return fw_cfg_init_io_dma(iobase, NULL);
+}
+
+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;
+ FWCfgState *s;
+ uint32_t version = FW_CFG_VERSION;
dev = qdev_create(NULL, TYPE_FW_CFG_MEM);
qdev_prop_set_uint32(dev, "data_width", data_width);
@@ -633,13 +847,26 @@ 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);
- return FW_CFG(dev);
+ s = FW_CFG(dev);
+
+ if (dma_addr && dma_as) {
+ s->dma_as = dma_as;
+ s->dma_enabled = true;
+ s->dma_addr = 0;
+ sysbus_mmio_map(sbd, 2, dma_addr);
+ version |= FW_CFG_VERSION_DMA;
+ }
+
+ fw_cfg_add_i32(s, FW_CFG_ID, version);
+
+ return s;
}
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);
}
@@ -675,7 +902,7 @@ static void fw_cfg_io_realize(DeviceState *dev, Error
**errp)
SysBusDevice *sbd = SYS_BUS_DEVICE(dev);
memory_region_init_io(&s->comb_iomem, OBJECT(s), &fw_cfg_comb_mem_ops,
- FW_CFG(s), "fwcfg", FW_CFG_SIZE);
+ FW_CFG(s), "fwcfg", FW_CFG_IO_SIZE);
sysbus_add_io(sbd, s->iobase, &s->comb_iomem);
}
@@ -707,7 +934,7 @@ static void fw_cfg_mem_realize(DeviceState *dev, Error
**errp)
const MemoryRegionOps *data_ops = &fw_cfg_data_mem_ops;
memory_region_init_io(&s->ctl_iomem, OBJECT(s), &fw_cfg_ctl_mem_ops,
- FW_CFG(s), "fwcfg.ctl", FW_CFG_SIZE);
+ FW_CFG(s), "fwcfg.ctl", FW_CFG_CTL_SIZE);
sysbus_init_mmio(sbd, &s->ctl_iomem);
if (s->data_width > data_ops->valid.max_access_size) {
@@ -725,6 +952,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", sizeof(dma_addr_t));
+ 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..d0cbc88 100644
--- a/include/hw/nvram/fw_cfg.h
+++ b/include/hw/nvram/fw_cfg.h
@@ -61,6 +61,15 @@ typedef struct FWCfgFiles {
FWCfgFile f[];
} FWCfgFiles;
+/* Control as first field allows for different structures selected by this
+ * field, which might be useful in the future
+ */
+typedef struct FWCfgDmaAccess {
+ uint32_t control;
+ uint32_t length;
+ uint64_t address;
+} QEMU_PACKED FWCfgDmaAccess;
+
typedef void (*FWCfgCallback)(void *opaque, uint8_t *data);
typedef void (*FWCfgReadCallback)(void *opaque, uint32_t offset);
@@ -77,10 +86,12 @@ void fw_cfg_add_file_callback(FWCfgState *s, const char
*filename,
void *data, size_t len);
void *fw_cfg_modify_file(FWCfgState *s, const char *filename, void *data,
size_t len);
+FWCfgState *fw_cfg_init_io_dma(uint32_t iobase, AddressSpace *dma_as);
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
- Re: [Qemu-devel] QEMU fw_cfg DMA interface, (continued)
[Qemu-devel] QEMU fw_cfg DMA interface, Marc Marí, 2015/08/31
- [Qemu-devel] [PATCH v2 0/5] fw_cfg DMA interface, Marc Marí, 2015/08/31
- [Qemu-devel] [PATCH v2 1/5] fw_cfg: document fw_cfg_modify_iXX() update functions, Marc Marí, 2015/08/31
- [Qemu-devel] [PATCH v2 4/5] Enable fw_cfg DMA interface for ARM, Marc Marí, 2015/08/31
- [Qemu-devel] [PATCH v2 5/5] Enable fw_cfg DMA interface for x86, Marc Marí, 2015/08/31
- [Qemu-devel] [PATCH v2 2/5] fw_cfg DMA interface documentation, Marc Marí, 2015/08/31
- Re: [Qemu-devel] [PATCH v2 2/5] fw_cfg DMA interface documentation, Kevin O'Connor, 2015/08/31
- [Qemu-devel] [PATCH v2 3/5] Implement fw_cfg DMA interface,
Marc Marí <=
- Re: [Qemu-devel] [PATCH v2 3/5] Implement fw_cfg DMA interface, Kevin O'Connor, 2015/08/31