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
