diff mbox series

[RFC,4/9] hw/misc: Add interleaver device to make interleaved memory accesses

Message ID 20200817161853.593247-5-f4bug@amsat.org
State New
Headers show
Series [RFC,1/9] memory: Initialize MemoryRegionOps for RAM memory regions | expand

Commit Message

Philippe Mathieu-Daudé Aug. 17, 2020, 4:18 p.m. UTC
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
diff mbox series

Patch

diff --git a/include/hw/misc/interleaver.h b/include/hw/misc/interleaver.h
new file mode 100644
index 0000000000..953d987556
--- /dev/null
+++ b/include/hw/misc/interleaver.h
@@ -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
+
diff --git a/hw/misc/interleaver.c b/hw/misc/interleaver.c
new file mode 100644
index 0000000000..46099e9e11
--- /dev/null
+++ b/hw/misc/interleaver.c
@@ -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)
diff --git a/MAINTAINERS b/MAINTAINERS
index 0886eb3d2b..1efce3dd27 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -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
diff --git a/hw/misc/Kconfig b/hw/misc/Kconfig
index 92c397ca07..7ed0f4ccc7 100644
--- a/hw/misc/Kconfig
+++ b/hw/misc/Kconfig
@@ -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
diff --git a/hw/misc/Makefile.objs b/hw/misc/Makefile.objs
index 6be3d255ab..aa753a847f 100644
--- a/hw/misc/Makefile.objs
+++ b/hw/misc/Makefile.objs
@@ -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
diff --git a/hw/misc/trace-events b/hw/misc/trace-events
index 066752aa90..1b0db146b4 100644
--- a/hw/misc/trace-events
+++ b/hw/misc/trace-events
@@ -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"