new file mode 100644
@@ -0,0 +1,40 @@
+/*
+ * QEMU Interleaver device
+ *
+ * Copyright (C) 2020 Philippe Mathieu-Daudé <f4bug@amsat.org>
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#ifndef HW_MISC_INTERLEAVER_H
+#define HW_MISC_INTERLEAVER_H
+
+/*
+ * Example of 32x16 interleaver accesses (32-bit bus, 2x 16-bit banks):
+ *
+ * Each interleaved 32-bit access on the bus results in contiguous 16-bit
+ * access on each banked device:
+ *
+ * ____________________________________________________
+ * Bus accesses | 1st 32-bit | 2nd 32-bit |
+ * -----------------------------------------------------
+ * | | | |
+ * v | v |
+ * ______________ | ______________ |
+ * 1st bank accesses | 1st 16-bit | | | 2nd 16-bit | |
+ * -------------- | -------------- |
+ * v v
+ * ______________ ______________
+ * 2nd bank accesses | 1st 16-bit | | 2nd 16-bit |
+ * -------------- --------------
+ */
+
+#define TYPE_INTERLEAVER_16X8_DEVICE "interleaver-16x8-device"
+#define TYPE_INTERLEAVER_32X8_DEVICE "interleaver-32x8-device"
+#define TYPE_INTERLEAVER_32X16_DEVICE "interleaver-32x16-device"
+#define TYPE_INTERLEAVER_64X8_DEVICE "interleaver-64x8-device"
+#define TYPE_INTERLEAVER_64X16_DEVICE "interleaver-64x16-device"
+#define TYPE_INTERLEAVER_64X32_DEVICE "interleaver-64x32-device"
+
+#endif
+
new file mode 100644
@@ -0,0 +1,254 @@
+/*
+ * QEMU Interleaver device
+ *
+ * The interleaver device to allow making interleaved memory accesses.
+ *
+ * This device support using the following configurations (INPUT x OUTPUT):
+ * 16x8, 32x8, 32x16, 64x8, 64x16 and 64x32.
+ *
+ * Copyright (C) 2020 Philippe Mathieu-Daudé <f4bug@amsat.org>
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#include "qemu/osdep.h"
+#include "qemu/log.h"
+#include "qapi/error.h"
+#include "hw/sysbus.h"
+#include "hw/qdev-properties.h"
+#include "hw/misc/interleaver.h"
+#include "trace.h"
+
+#define TYPE_INTERLEAVER_DEVICE "interleaver-device"
+
+typedef struct InterleaverDeviceClass {
+ /*< private >*/
+ SysBusDeviceClass parent_class;
+ /*< public >*/
+ MemoryRegionOps ops;
+ unsigned input_access_size;
+ unsigned output_access_size;
+ MemOp output_memop;
+ unsigned mr_count;
+ char *name;
+} InterleaverDeviceClass;
+
+#define INTERLEAVER_DEVICE_CLASS(klass) \
+ OBJECT_CLASS_CHECK(InterleaverDeviceClass, (klass), TYPE_INTERLEAVER_DEVICE)
+#define INTERLEAVER_DEVICE_GET_CLASS(obj) \
+ OBJECT_GET_CLASS(InterleaverDeviceClass, (obj), TYPE_INTERLEAVER_DEVICE)
+
+#define INTERLEAVER_REGIONS_MAX 8 /* 64x8 */
+
+typedef struct {
+ /*< private >*/
+ SysBusDevice parent_obj;
+ /*< public >*/
+ MemoryRegion iomem;
+ uint64_t size;
+ MemoryRegion *mr[INTERLEAVER_REGIONS_MAX];
+} InterleaverDeviceState;
+
+#define INTERLEAVER_DEVICE(obj) \
+ OBJECT_CHECK(InterleaverDeviceState, (obj), TYPE_INTERLEAVER_DEVICE)
+
+static const char *memresult_str[] = {"OK", "ERROR", "DECODE_ERROR"};
+
+static const char *emtpy_mr_name = "EMPTY";
+
+static MemTxResult interleaver_read(void *opaque,
+ hwaddr offset, uint64_t *data,
+ unsigned size, MemTxAttrs attrs)
+{
+ InterleaverDeviceState *s = INTERLEAVER_DEVICE(opaque);
+ InterleaverDeviceClass *idc = INTERLEAVER_DEVICE_GET_CLASS(s);
+ unsigned idx = (offset / idc->output_access_size) & (idc->mr_count - 1);
+ hwaddr addr = (offset & ~(idc->input_access_size - 1)) / idc->mr_count;
+ MemTxResult r = MEMTX_ERROR;
+
+ trace_interleaver_read_enter(idc->input_access_size,
+ idc->output_access_size, size,
+ idc->mr_count, idx,
+ s->mr[idx] ? memory_region_name(s->mr[idx])
+ : emtpy_mr_name,
+ offset, addr);
+ if (s->mr[idx]) {
+ r = memory_region_dispatch_read(s->mr[idx],
+ addr,
+ data,
+ idc->output_memop,
+ attrs);
+ }
+ trace_interleaver_read_exit(size, *data, memresult_str[r]);
+
+ return r;
+}
+
+static MemTxResult interleaver_write(void *opaque,
+ hwaddr offset, uint64_t data,
+ unsigned size, MemTxAttrs attrs)
+{
+ InterleaverDeviceState *s = INTERLEAVER_DEVICE(opaque);
+ InterleaverDeviceClass *idc = INTERLEAVER_DEVICE_GET_CLASS(s);
+ unsigned idx = (offset / idc->output_access_size) & (idc->mr_count - 1);
+ hwaddr addr = (offset & ~(idc->input_access_size - 1)) / idc->mr_count;
+ MemTxResult r = MEMTX_ERROR;
+
+ trace_interleaver_write_enter(idc->input_access_size,
+ idc->output_access_size, size,
+ idc->mr_count, idx,
+ s->mr[idx] ? memory_region_name(s->mr[idx])
+ : emtpy_mr_name,
+ offset, addr);
+ if (s->mr[idx]) {
+ r = memory_region_dispatch_write(s->mr[idx],
+ addr,
+ data,
+ idc->output_memop,
+ attrs);
+ }
+ trace_interleaver_write_exit(size, data, memresult_str[r]);
+
+ return r;
+}
+
+static void interleaver_realize(DeviceState *dev, Error **errp)
+{
+ InterleaverDeviceState *s = INTERLEAVER_DEVICE(dev);
+ InterleaverDeviceClass *idc = INTERLEAVER_DEVICE_GET_CLASS(dev);
+ uint64_t expected_mr_size;
+
+ if (s->size == 0) {
+ error_setg(errp, "property 'size' not specified or zero");
+ return;
+ }
+ if (!QEMU_IS_ALIGNED(s->size, idc->input_access_size)) {
+ error_setg(errp, "property 'size' must be multiple of %u",
+ idc->input_access_size);
+ return;
+ }
+
+ expected_mr_size = s->size / idc->mr_count;
+ for (unsigned i = 0; i < idc->mr_count; i++) {
+ if (s->mr[i] && memory_region_size(s->mr[i]) != expected_mr_size) {
+ error_setg(errp,
+ "memory region #%u (%s) size mismatches interleaver",
+ i, memory_region_name(s->mr[i]));
+ return;
+ }
+ }
+ memory_region_init_io(&s->iomem, OBJECT(s), &idc->ops, s,
+ idc->name, s->size);
+ sysbus_init_mmio(SYS_BUS_DEVICE(s), &s->iomem);
+}
+
+static Property interleaver_properties[] = {
+ DEFINE_PROP_UINT64("size", InterleaverDeviceState, size, 0),
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+static void interleaver_class_init(ObjectClass *oc, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(oc);
+
+ dc->realize = interleaver_realize;
+ device_class_set_props(dc, interleaver_properties);
+ set_bit(DEVICE_CATEGORY_MISC, dc->categories);
+}
+
+static void interleaver_class_add_properties(ObjectClass *oc,
+ unsigned input_bits,
+ unsigned output_bits)
+{
+ InterleaverDeviceClass *idc = INTERLEAVER_DEVICE_CLASS(oc);
+
+ idc->name = g_strdup_printf("interleaver-%ux%u", input_bits, output_bits);
+ idc->input_access_size = input_bits >> 3;
+ idc->output_access_size = output_bits >> 3;
+ idc->output_memop = size_memop(idc->output_access_size);
+ idc->mr_count = input_bits / output_bits;
+ idc->ops = (MemoryRegionOps){
+ .read_with_attrs = interleaver_read,
+ .write_with_attrs = interleaver_write,
+ .valid.min_access_size = 1,
+ .valid.max_access_size = idc->input_access_size,
+ .impl.min_access_size = idc->output_access_size,
+ .impl.max_access_size = idc->output_access_size,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+ };
+
+ for (unsigned i = 0; i < idc->mr_count; i++) {
+ g_autofree char *name = g_strdup_printf("mr%u", i);
+ object_class_property_add_link(oc, name, TYPE_MEMORY_REGION,
+ offsetof(InterleaverDeviceState, mr[i]),
+ qdev_prop_allow_set_link_before_realize,
+ OBJ_PROP_LINK_STRONG);
+ }
+}
+
+static void interleaver_16x8_class_init(ObjectClass *oc, void *data)
+{
+ interleaver_class_add_properties(oc, 16, 8);
+};
+
+static void interleaver_32x8_class_init(ObjectClass *oc, void *data)
+{
+ interleaver_class_add_properties(oc, 32, 8);
+};
+
+static void interleaver_32x16_class_init(ObjectClass *oc, void *data)
+{
+ interleaver_class_add_properties(oc, 32, 16);
+};
+
+static void interleaver_64x8_class_init(ObjectClass *oc, void *data)
+{
+ interleaver_class_add_properties(oc, 64, 8);
+};
+
+static void interleaver_64x16_class_init(ObjectClass *oc, void *data)
+{
+ interleaver_class_add_properties(oc, 64, 16);
+};
+
+static void interleaver_64x32_class_init(ObjectClass *oc, void *data)
+{
+ interleaver_class_add_properties(oc, 64, 32);
+};
+
+static const TypeInfo interleaver_device_types[] = {
+ {
+ .name = TYPE_INTERLEAVER_16X8_DEVICE,
+ .parent = TYPE_INTERLEAVER_DEVICE,
+ .class_init = interleaver_16x8_class_init,
+ }, {
+ .name = TYPE_INTERLEAVER_32X8_DEVICE,
+ .parent = TYPE_INTERLEAVER_DEVICE,
+ .class_init = interleaver_32x8_class_init,
+ }, {
+ .name = TYPE_INTERLEAVER_32X16_DEVICE,
+ .parent = TYPE_INTERLEAVER_DEVICE,
+ .class_init = interleaver_32x16_class_init,
+ }, {
+ .name = TYPE_INTERLEAVER_64X8_DEVICE,
+ .parent = TYPE_INTERLEAVER_DEVICE,
+ .class_init = interleaver_64x8_class_init,
+ }, {
+ .name = TYPE_INTERLEAVER_64X16_DEVICE,
+ .parent = TYPE_INTERLEAVER_DEVICE,
+ .class_init = interleaver_64x16_class_init,
+ }, {
+ .name = TYPE_INTERLEAVER_64X32_DEVICE,
+ .parent = TYPE_INTERLEAVER_DEVICE,
+ .class_init = interleaver_64x32_class_init,
+ }, {
+ .name = TYPE_INTERLEAVER_DEVICE,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(InterleaverDeviceState),
+ .class_size = sizeof(InterleaverDeviceClass),
+ .class_init = interleaver_class_init,
+ .abstract = true,
+ }
+};
+
+DEFINE_TYPES(interleaver_device_types)
@@ -1961,6 +1961,12 @@ S: Maintained
F: include/hw/misc/empty_slot.h
F: hw/misc/empty_slot.c
+Interleaver device
+M: Philippe Mathieu-Daudé <f4bug@amsat.org>
+S: Maintained
+F: include/hw/misc/interleaver.h
+F: hw/misc/interleaver.c
+
Standard VGA
M: Gerd Hoffmann <kraxel@redhat.com>
S: Maintained
@@ -21,6 +21,10 @@ config SGA
bool
depends on ISA_BUS
+config INTERLEAVER
+ bool
+ default y
+
config ISA_TESTDEV
bool
default y if TEST_DEVICES
@@ -12,6 +12,7 @@ common-obj-$(CONFIG_PCA9552) += pca9552.o
common-obj-$(CONFIG_UNIMP) += unimp.o
common-obj-$(CONFIG_EMPTY_SLOT) += empty_slot.o
common-obj-$(CONFIG_FW_CFG_DMA) += vmcoreinfo.o
+common-obj-$(CONFIG_INTERLEAVER) += interleaver.o
# ARM devices
common-obj-$(CONFIG_PL310) += arm_l2x0.o
@@ -217,3 +217,9 @@ grlib_apb_pnp_read(uint64_t addr, uint32_t value) "APB PnP read addr:0x%03"PRIx6
# pca9552.c
pca955x_gpio_status(const char *description, const char *buf) "%s GPIOs 0-15 [%s]"
pca955x_gpio_change(const char *description, unsigned id, unsigned prev_state, unsigned current_state) "%s GPIO id:%u status: %u -> %u"
+
+# interleaver.c
+interleaver_read_enter(unsigned input_access_size, unsigned output_access_size, unsigned size, unsigned mr_count, unsigned index, const char *mr_name, uint64_t in_addr, uint64_t out_addr) "rd ixs:%u oxs:%u sz:%u mr_cnt:%u mr_idx:%u mr_name:'%s' iadr:0x%"PRIx64" oadr:0x%"PRIx64
+interleaver_read_exit(unsigned size, uint64_t value, const char *result) "rd size:%u value:0x%08"PRIx64" result: %s"
+interleaver_write_enter(unsigned input_access_size, unsigned output_access_size, unsigned size, unsigned mr_count, unsigned index, const char *mr_name, uint64_t in_addr, uint64_t out_addr) "wr ixs:%u oxs:%u sz:%u mr_cnt:%u mr_idx:%u mr_name:'%s' iadr:0x%"PRIx64" oadr:0x%"PRIx64
+interleaver_write_exit(unsigned size, uint64_t value, const char *result) "wr size:%u value:0x%08"PRIx64" result: %s"
Some slow devices might be arranged in an interleaved setup to reduce waiting for memory banks and improve memory throughput. Classical examples are NOR flashes. Add an 'interleaver' device to allow making such interleaved memory accesses. This device support using the 16x8, 32x8, 32x16, 64x8, 64x16 and 64x32 configurations. Example of 32x16 interleaver accesses (32-bit bus, 2x 16-bit banks): Each interleaved 32-bit access on the bus results in contiguous 16-bit access on each banked device: ____________________________________________________ Bus accesses | 1st 32-bit | 2nd 32-bit | ----------------------------------------------------- | | | | v | v | ______________ | ______________ | 1st bank accesses | 1st 16-bit | | | 2nd 16-bit | | -------------- | -------------- | v v ______________ ______________ 2nd bank accesses | 1st 16-bit | | 2nd 16-bit | -------------- -------------- Signed-off-by: Philippe Mathieu-Daudé <f4bug@amsat.org> --- How to simplify idx/addr initialization in the read/write handlers? --- include/hw/misc/interleaver.h | 40 ++++++ hw/misc/interleaver.c | 254 ++++++++++++++++++++++++++++++++++ MAINTAINERS | 6 + hw/misc/Kconfig | 4 + hw/misc/Makefile.objs | 1 + hw/misc/trace-events | 6 + 6 files changed, 311 insertions(+) create mode 100644 include/hw/misc/interleaver.h create mode 100644 hw/misc/interleaver.c