[RFC PATCH v2 2/2] hw/misc: add xiic-fpga-i2c, a PCIe FPGA embedding xlnx-axi-iic cores

Nodoka Shibasaki <[email protected]>
Newsgroups gmane.comp.emulators.qemu
Message-ID <[email protected]>
Add a PCIe function that embeds N xlnx-axi-iic controllers, maps each
core's register window into BAR0, and adds a top-level interrupt block
that aggregates the per-channel level lines into a bit-per-channel status
register delivered on a single MSI vector (with a legacy INTx fallback),
performing the level-to-edge translation a guest driver expects.

The aggregator's register layout models a specific FPGA carrier rather
than a standardized controller, so the device is gated behind
CONFIG_XIIC_FPGA_I2C (default y if TEST_DEVICES). A qtest drives a
transfer through BAR0 to a tmp105 slave and checks register, transfer and
NACK behavior; the test slaves it needs are enabled in the x86_64-softmmu
test config.

Signed-off-by: Nodoka Shibasaki <[email protected]>
---
 MAINTAINERS                                |   8 +-
 configs/devices/x86_64-softmmu/default.mak |   5 +
 hw/misc/Kconfig                            |   6 +
 hw/misc/meson.build                        |   1 +
 hw/misc/xiic_fpga_i2c.c                    | 261 +++++++++++++++++++++
 tests/qtest/meson.build                    |   2 +
 tests/qtest/xiic-fpga-i2c-test.c           | 159 +++++++++++++
 7 files changed, 439 insertions(+), 3 deletions(-)
 create mode 100644 hw/misc/xiic_fpga_i2c.c
 create mode 100644 tests/qtest/xiic-fpga-i2c-test.c

diff --git a/MAINTAINERS b/MAINTAINERS
index 8339c9bf83..85f03cf4b6 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -2158,8 +2158,6 @@ S: Maintained
 F: hw/misc/edu.c
 F: docs/specs/edu.rst
 
-<<<<<<< Updated upstream
-=======
 xiic-fpga-i2c
 M: Nodoka Shibasaki <[email protected]>
 S: Maintained
@@ -2167,7 +2165,11 @@ F: hw/i2c/xlnx-axi-iic.c
 F: include/hw/i2c/xlnx-axi-iic.h
 F: docs/specs/xlnx-axi-iic.rst
 
->>>>>>> Stashed changes
+xiic-fpga-i2c
+M: Nodoka Shibasaki <[email protected]>
+S: Maintained
+F: hw/misc/xiic_fpga_i2c.c
+
 IDE
 M: John Snow <[email protected]>
 L: [email protected]
diff --git a/configs/devices/x86_64-softmmu/default.mak b/configs/devices/x86_64-softmmu/default.mak
index ddfc2ea626..d7a88deccc 100644
--- a/configs/devices/x86_64-softmmu/default.mak
+++ b/configs/devices/x86_64-softmmu/default.mak
@@ -1,3 +1,8 @@
 # Default configuration for x86_64-softmmu
 
 include ../i386-softmmu/default.mak
+
+# Enable I2C slave models used by the xiic-fpga-i2c qtest.
+CONFIG_AT24C=y
+CONFIG_TMP105=y
+CONFIG_TMP421=y
diff --git a/hw/misc/Kconfig b/hw/misc/Kconfig
index 1543ee6653..12acca66bb 100644
--- a/hw/misc/Kconfig
+++ b/hw/misc/Kconfig
@@ -25,6 +25,12 @@ config PCI_TESTDEV
     default y if TEST_DEVICES
     depends on PCI
 
+config XIIC_FPGA_I2C
+    bool
+    default y if TEST_DEVICES
+    depends on PCI && MSI_NONBROKEN
+    select XLNX_AXI_IIC
+
 config IOMMU_TESTDEV
     bool
     default y if TEST_DEVICES
diff --git a/hw/misc/meson.build b/hw/misc/meson.build
index 23265f6035..69d816cad9 100644
--- a/hw/misc/meson.build
+++ b/hw/misc/meson.build
@@ -4,6 +4,7 @@ system_ss.add(when: 'CONFIG_FW_CFG_DMA', if_true: files('vmcoreinfo.c'))
 system_ss.add(when: 'CONFIG_ISA_DEBUG', if_true: files('debugexit.c'))
 system_ss.add(when: 'CONFIG_ISA_TESTDEV', if_true: files('pc-testdev.c'))
 system_ss.add(when: 'CONFIG_PCI_TESTDEV', if_true: files('pci-testdev.c'))
+system_ss.add(when: 'CONFIG_XIIC_FPGA_I2C', if_true: files('xiic_fpga_i2c.c'))
 system_ss.add(when: 'CONFIG_IOMMU_TESTDEV', if_true: files('iommu-testdev.c'))
 system_ss.add(when: 'CONFIG_UNIMP', if_true: files('unimp.c'))
 system_ss.add(when: 'CONFIG_EMPTY_SLOT', if_true: files('empty_slot.c'))
diff --git a/hw/misc/xiic_fpga_i2c.c b/hw/misc/xiic_fpga_i2c.c
new file mode 100644
index 0000000000..c4425fa84d
--- /dev/null
+++ b/hw/misc/xiic_fpga_i2c.c
@@ -0,0 +1,261 @@
+/*
+ * xiic_fpga_i2c.c - QEMU model of a PCIe FPGA that embeds Xilinx AXI-IIC
+ *                   controllers behind a shared MSI interrupt aggregator.
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#include "qemu/osdep.h"
+#include "qemu/module.h"
+#include "qemu/host-utils.h"
+#include "migration/vmstate.h"
+#include "hw/pci/pci_device.h"
+#include "hw/pci/msi.h"
+#include "hw/core/sysbus.h"
+#include "hw/core/qdev-properties.h"
+#include "hw/i2c/xlnx-axi-iic.h"
+
+#define TYPE_XIIC_FPGA_I2C "xiic-fpga-i2c"
+OBJECT_DECLARE_SIMPLE_TYPE(XiicFpgaI2cState, XIIC_FPGA_I2C)
+
+#define XIIC_FPGA_MAX_CHANNELS 32
+
+#define XIIC_FPGA_IRQ_STATUS_REG 0
+#define XIIC_FPGA_IRQ_UNMASK_REG 4
+#define XIIC_FPGA_IRQ_REGION_SIZE 8
+
+struct XiicFpgaI2cState {
+    PCIDevice parent_obj;
+
+    MemoryRegion bar0;
+    MemoryRegion irq_mmio;
+
+    uint32_t num_channels;
+    uint32_t ch_base_offset;
+    uint32_t ch_stride;
+    uint32_t bar_size;
+    uint32_t num_msi_vectors;
+
+    uint32_t irq_status_offset;
+    uint32_t irq_unmask_offset;
+    uint32_t irq_msi_vector;
+
+    uint32_t irq_status;
+    uint32_t irq_unmask;
+    bool msi_asserted;
+
+    XlnxAxiIicState chan[XIIC_FPGA_MAX_CHANNELS];
+};
+
+static void xiic_fpga_update_msi(XiicFpgaI2cState *s)
+{
+    PCIDevice *pci_dev = PCI_DEVICE(s);
+    uint32_t active = s->irq_status & s->irq_unmask;
+
+    if (msi_enabled(pci_dev)) {
+        if (active && !s->msi_asserted) {
+            msi_notify(pci_dev, s->irq_msi_vector);
+            s->msi_asserted = true;
+        } else if (!active) {
+            s->msi_asserted = false;
+        }
+        return;
+    }
+
+    pci_set_irq(pci_dev, active != 0);
+}
+
+static void xiic_fpga_irq_set(void *opaque, int n, int level)
+{
+    XiicFpgaI2cState *s = opaque;
+
+    if (level) {
+        s->irq_status |= (1u << n);
+    } else {
+        s->irq_status &= ~(1u << n);
+    }
+    xiic_fpga_update_msi(s);
+}
+
+static uint64_t xiic_fpga_irq_read(void *opaque, hwaddr addr, unsigned size)
+{
+    XiicFpgaI2cState *s = opaque;
+
+    switch (addr) {
+    case XIIC_FPGA_IRQ_STATUS_REG:
+        return s->irq_status;
+    case XIIC_FPGA_IRQ_UNMASK_REG:
+        return s->irq_unmask;
+    default:
+        return 0;
+    }
+}
+
+static void xiic_fpga_irq_write(void *opaque, hwaddr addr, uint64_t val,
+                                unsigned size)
+{
+    XiicFpgaI2cState *s = opaque;
+
+    switch (addr) {
+    case XIIC_FPGA_IRQ_STATUS_REG:
+        s->msi_asserted = false;
+        xiic_fpga_update_msi(s);
+        break;
+    case XIIC_FPGA_IRQ_UNMASK_REG:
+        s->irq_unmask = val;
+        xiic_fpga_update_msi(s);
+        break;
+    default:
+        break;
+    }
+}
+
+static const MemoryRegionOps xiic_fpga_irq_ops = {
+    .read = xiic_fpga_irq_read,
+    .write = xiic_fpga_irq_write,
+    .endianness = DEVICE_LITTLE_ENDIAN,
+    .impl  = { .min_access_size = 1, .max_access_size = 4 },
+    .valid = { .min_access_size = 1, .max_access_size = 4 },
+};
+
+static void xiic_fpga_i2c_realize(PCIDevice *pci_dev, Error **errp)
+{
+    XiicFpgaI2cState *s = XIIC_FPGA_I2C(pci_dev);
+
+    if (s->num_channels < 1 || s->num_channels > XIIC_FPGA_MAX_CHANNELS) {
+        error_setg(errp, "num-channels must be between 1 and %d",
+                   XIIC_FPGA_MAX_CHANNELS);
+        return;
+    }
+    if (s->irq_unmask_offset != s->irq_status_offset + 4) {
+        error_setg(errp, "irq-unmask-offset must be irq-status-offset + 4");
+        return;
+    }
+
+    if (s->bar_size < s->ch_base_offset + s->num_channels * s->ch_stride) {
+        s->bar_size = s->ch_base_offset + s->num_channels * s->ch_stride;
+    }
+    if (s->bar_size < s->irq_status_offset + XIIC_FPGA_IRQ_REGION_SIZE) {
+        s->bar_size = s->irq_status_offset + XIIC_FPGA_IRQ_REGION_SIZE;
+    }
+    s->bar_size = pow2ceil(s->bar_size);
+
+    s->num_msi_vectors = pow2ceil(s->num_channels);
+    if (s->num_msi_vectors > 32) {
+        s->num_msi_vectors = 32;
+    }
+    if (s->irq_msi_vector >= s->num_msi_vectors) {
+        error_setg(errp, "irq-msi-vector %u out of range (0..%u)",
+                   s->irq_msi_vector, s->num_msi_vectors - 1);
+        return;
+    }
+
+    memory_region_init(&s->bar0, OBJECT(s), "xiic-fpga-i2c-bar0", s->bar_size);
+
+    qdev_init_gpio_in(DEVICE(s), xiic_fpga_irq_set, s->num_channels);
+
+    for (unsigned i = 0; i < s->num_channels; i++) {
+        g_autofree char *name = g_strdup_printf("channel[%u]", i);
+        g_autofree char *bus_name = g_strdup_printf("xiic-fpga-i2c.%u", i);
+        object_initialize_child(OBJECT(s), name, &s->chan[i],
+                                TYPE_XLNX_AXI_IIC);
+        qdev_prop_set_string(DEVICE(&s->chan[i]), "bus-name", bus_name);
+        if (!sysbus_realize(SYS_BUS_DEVICE(&s->chan[i]), errp)) {
+            return;
+        }
+        memory_region_add_subregion(&s->bar0,
+            s->ch_base_offset + i * s->ch_stride,
+            sysbus_mmio_get_region(SYS_BUS_DEVICE(&s->chan[i]), 0));
+        sysbus_connect_irq(SYS_BUS_DEVICE(&s->chan[i]), 0,
+                           qdev_get_gpio_in(DEVICE(s), i));
+    }
+
+    memory_region_init_io(&s->irq_mmio, OBJECT(s), &xiic_fpga_irq_ops, s,
+                          "xiic-fpga-i2c-irq", XIIC_FPGA_IRQ_REGION_SIZE);
+    memory_region_add_subregion(&s->bar0, s->irq_status_offset, &s->irq_mmio);
+
+    pci_register_bar(pci_dev, 0, PCI_BASE_ADDRESS_SPACE_MEMORY, &s->bar0);
+    pci_config_set_interrupt_pin(pci_dev->config, 1);
+
+    if (msi_init(pci_dev, 0, s->num_msi_vectors, true, false, errp) < 0) {
+        error_prepend(errp, "xiic-fpga-i2c: failed to init MSI: ");
+        return;
+    }
+}
+
+static void xiic_fpga_i2c_exit(PCIDevice *pci_dev)
+{
+    msi_uninit(pci_dev);
+}
+
+static void xiic_fpga_i2c_reset_hold(Object *obj, ResetType type)
+{
+    XiicFpgaI2cState *s = XIIC_FPGA_I2C(obj);
+
+    s->irq_status = 0;
+    s->irq_unmask = 0;
+    s->msi_asserted = false;
+}
+
+static const VMStateDescription vmstate_xiic_fpga_i2c = {
+    .name = "xiic-fpga-i2c",
+    .version_id = 1,
+    .minimum_version_id = 1,
+    .fields = (const VMStateField[]) {
+        VMSTATE_PCI_DEVICE(parent_obj, XiicFpgaI2cState),
+        VMSTATE_UINT32(irq_status, XiicFpgaI2cState),
+        VMSTATE_UINT32(irq_unmask, XiicFpgaI2cState),
+        VMSTATE_BOOL(msi_asserted, XiicFpgaI2cState),
+        VMSTATE_END_OF_LIST()
+    }
+};
+
+static const Property xiic_fpga_i2c_props[] = {
+    DEFINE_PROP_UINT32("num-channels", XiicFpgaI2cState, num_channels, 4),
+    DEFINE_PROP_UINT32("ch-base-offset", XiicFpgaI2cState, ch_base_offset, 0x0),
+    DEFINE_PROP_UINT32("ch-stride", XiicFpgaI2cState, ch_stride, 0x1000),
+    DEFINE_PROP_UINT32("bar-size", XiicFpgaI2cState, bar_size, 0x8000),
+    DEFINE_PROP_UINT32("irq-status-offset", XiicFpgaI2cState,
+                       irq_status_offset, 0x6000),
+    DEFINE_PROP_UINT32("irq-unmask-offset", XiicFpgaI2cState,
+                       irq_unmask_offset, 0x6004),
+    DEFINE_PROP_UINT32("irq-msi-vector", XiicFpgaI2cState, irq_msi_vector, 0),
+};
+
+static void xiic_fpga_i2c_class_init(ObjectClass *klass, const void *data)
+{
+    DeviceClass *dc = DEVICE_CLASS(klass);
+    PCIDeviceClass *k = PCI_DEVICE_CLASS(klass);
+    ResettableClass *rc = RESETTABLE_CLASS(klass);
+
+    k->realize   = xiic_fpga_i2c_realize;
+    k->exit      = xiic_fpga_i2c_exit;
+    k->vendor_id = 0x10ee;
+    k->device_id = 0x7021;
+    k->revision  = 0x01;
+    k->class_id  = PCI_CLASS_OTHERS;
+
+    rc->phases.hold = xiic_fpga_i2c_reset_hold;
+    dc->vmsd = &vmstate_xiic_fpga_i2c;
+    dc->desc = "FPGA I2C (Xilinx AXI-IIC) emulation";
+    set_bit(DEVICE_CATEGORY_MISC, dc->categories);
+    device_class_set_props(dc, xiic_fpga_i2c_props);
+}
+
+static const TypeInfo xiic_fpga_i2c_info = {
+    .name          = TYPE_XIIC_FPGA_I2C,
+    .parent        = TYPE_PCI_DEVICE,
+    .instance_size = sizeof(XiicFpgaI2cState),
+    .class_init    = xiic_fpga_i2c_class_init,
+    .interfaces    = (InterfaceInfo[]) {
+        { INTERFACE_CONVENTIONAL_PCI_DEVICE },
+        { },
+    },
+};
+
+static void xiic_fpga_i2c_register_types(void)
+{
+    type_register_static(&xiic_fpga_i2c_info);
+}
+
+type_init(xiic_fpga_i2c_register_types)
diff --git a/tests/qtest/meson.build b/tests/qtest/meson.build
index 56ff860e21..7abd2c7520 100644
--- a/tests/qtest/meson.build
+++ b/tests/qtest/meson.build
@@ -74,6 +74,8 @@ qtests_i386 = \
   (config_all_devices.has_key('CONFIG_WDT_IB700') ? ['wdt_ib700-test'] : []) +              \
   (config_all_devices.has_key('CONFIG_PVPANIC_ISA') ? ['pvpanic-test'] : []) +              \
   (config_all_devices.has_key('CONFIG_PVPANIC_PCI') ? ['pvpanic-pci-test'] : []) +          \
+  (config_all_devices.has_key('CONFIG_XIIC_FPGA_I2C') and
+   config_all_devices.has_key('CONFIG_TMP105') ? ['xiic-fpga-i2c-test'] : []) +             \
   (config_all_devices.has_key('CONFIG_HDA') ? ['intel-hda-test'] : []) +                    \
   (config_all_devices.has_key('CONFIG_I82801B11') ? ['i82801b11-test'] : []) +             \
   (config_all_devices.has_key('CONFIG_IOH3420') ? ['ioh3420-test'] : []) +                  \
diff --git a/tests/qtest/xiic-fpga-i2c-test.c b/tests/qtest/xiic-fpga-i2c-test.c
new file mode 100644
index 0000000000..8c5f862d7c
--- /dev/null
+++ b/tests/qtest/xiic-fpga-i2c-test.c
@@ -0,0 +1,159 @@
+/*
+ * QTest for the xiic-fpga-i2c PCIe FPGA and its embedded xlnx-axi-iic cores.
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#include "qemu/osdep.h"
+#include "libqtest.h"
+#include "libqos/pci.h"
+#include "libqos/pci-pc.h"
+
+#define XIIC_IISR   0x20
+#define XIIC_IIER   0x28
+#define XIIC_DGIER  0x1C
+#define XIIC_SR     0x104
+#define XIIC_DTR    0x108
+#define XIIC_DRR    0x10C
+
+#define XIIC_SR_RX_FIFO_EMPTY   0x40
+#define XIIC_SR_TX_FIFO_EMPTY   0x80
+#define XIIC_SR_BUS_BUSY        0x04
+
+#define XIIC_INTR_TX_ERROR      0x02
+#define XIIC_INTR_BNB           0x10
+
+#define XIIC_DYN_START          0x100
+#define XIIC_DYN_STOP           0x200
+
+#define TMP105_ADDR             0x4c
+#define TMP105_REG_CONFIG       0x01
+
+typedef struct {
+    QTestState *qts;
+    QPCIBus *pcibus;
+    QPCIDevice *dev;
+    QPCIBar bar;
+} XiicFixture;
+
+static void save_dev(QPCIDevice *dev, int devfn, void *data)
+{
+    QPCIDevice **out = data;
+
+    if (*out) {
+        g_free(dev);
+    } else {
+        *out = dev;
+    }
+}
+
+static void fixture_setup(XiicFixture *f)
+{
+    f->qts = qtest_init("-device xiic-fpga-i2c,num-channels=1 "
+                        "-device tmp105,id=temp,bus=xiic-fpga-i2c.0,address=0x4c");
+    f->pcibus = qpci_new_pc(f->qts, NULL);
+    f->dev = NULL;
+    qpci_device_foreach(f->pcibus, 0x10ee, 0x7021, save_dev, &f->dev);
+    g_assert(f->dev != NULL);
+    qpci_device_enable(f->dev);
+    f->bar = qpci_iomap(f->dev, 0, NULL);
+}
+
+static void fixture_teardown(XiicFixture *f)
+{
+    qpci_iounmap(f->dev, f->bar);
+    g_free(f->dev);
+    qpci_free_pc(f->pcibus);
+    qtest_quit(f->qts);
+}
+
+static void wr(XiicFixture *f, uint64_t off, uint32_t val)
+{
+    qpci_io_writel(f->dev, f->bar, off, val);
+}
+
+static uint32_t rd(XiicFixture *f, uint64_t off)
+{
+    return qpci_io_readl(f->dev, f->bar, off);
+}
+
+static void test_registers(void)
+{
+    XiicFixture f;
+    uint32_t sr;
+
+    fixture_setup(&f);
+
+    sr = rd(&f, XIIC_SR);
+    g_assert_cmphex(sr & XIIC_SR_TX_FIFO_EMPTY, ==, XIIC_SR_TX_FIFO_EMPTY);
+    g_assert_cmphex(sr & XIIC_SR_RX_FIFO_EMPTY, ==, XIIC_SR_RX_FIFO_EMPTY);
+    g_assert_cmphex(sr & XIIC_SR_BUS_BUSY, ==, 0);
+
+    wr(&f, XIIC_IIER, 0x08);
+    g_assert_cmphex(rd(&f, XIIC_IIER), ==, 0x08);
+    wr(&f, XIIC_DGIER, 0x80000000);
+    g_assert_cmphex(rd(&f, XIIC_DGIER), ==, 0x80000000);
+
+    fixture_teardown(&f);
+}
+
+static void test_read(void)
+{
+    XiicFixture f;
+
+    fixture_setup(&f);
+
+    qtest_qmp_assert_success(f.qts,
+        "{ 'execute': 'qom-set', 'arguments':"
+        " { 'path': '/machine/peripheral/temp',"
+        "   'property': 'temperature', 'value': 21000 } }");
+
+    wr(&f, XIIC_DTR, XIIC_DYN_START | (TMP105_ADDR << 1) | 1);
+    wr(&f, XIIC_DTR, XIIC_DYN_STOP | 2);
+
+    g_assert_cmphex(rd(&f, XIIC_DRR) & 0xff, ==, 0x15);
+
+    fixture_teardown(&f);
+}
+
+static void test_write(void)
+{
+    XiicFixture f;
+    uint32_t isr;
+
+    fixture_setup(&f);
+
+    wr(&f, XIIC_DTR, XIIC_DYN_START | (TMP105_ADDR << 1) | 0);
+    wr(&f, XIIC_DTR, TMP105_REG_CONFIG);
+    wr(&f, XIIC_DTR, XIIC_DYN_STOP | 0x00);
+
+    isr = rd(&f, XIIC_IISR);
+    g_assert_cmphex(isr & XIIC_INTR_TX_ERROR, ==, 0);
+    g_assert_cmphex(isr & XIIC_INTR_BNB, ==, XIIC_INTR_BNB);
+    g_assert_cmphex(rd(&f, XIIC_SR) & XIIC_SR_BUS_BUSY, ==, 0);
+
+    fixture_teardown(&f);
+}
+
+static void test_nack(void)
+{
+    XiicFixture f;
+
+    fixture_setup(&f);
+
+    wr(&f, XIIC_DTR, XIIC_DYN_START | (0x20 << 1) | 1);
+    g_assert_cmphex(rd(&f, XIIC_IISR) & XIIC_INTR_TX_ERROR, ==,
+                    XIIC_INTR_TX_ERROR);
+
+    fixture_teardown(&f);
+}
+
+int main(int argc, char **argv)
+{
+    g_test_init(&argc, &argv, NULL);
+    qtest_add_func("/xiic-fpga-i2c/registers", test_registers);
+    qtest_add_func("/xiic-fpga-i2c/read", test_read);
+    qtest_add_func("/xiic-fpga-i2c/write", test_write);
+    qtest_add_func("/xiic-fpga-i2c/nack", test_nack);
+    return g_test_run();
+}
-- 
2.50.1
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.