[RFC PATCH 0/1] hw/misc: emulated Nexthop FPGA I2C (Xilinx AXI-IIC) device

Nodoka Shibasaki posted 1 patch 4 weeks ago
Patches applied successfully (tree, apply log)
git fetch https://github.com/patchew-project/qemu tags/patchew/20260715221400.1484805-1-nodokaorganized@gmail.com
Maintainers: Paolo Bonzini <pbonzini@redhat.com>, Nodoka Shibasaki <nodokaorganized@gmail.com>
MAINTAINERS             |   5 +
hw/misc/Kconfig         |   6 +
hw/misc/meson.build     |   1 +
hw/misc/xiic_fpga_i2c.c | 651 ++++++++++++++++++++++++++++++++++++++++
4 files changed, 663 insertions(+)
create mode 100644 hw/misc/xiic_fpga_i2c.c
[RFC PATCH 0/1] hw/misc: emulated Nexthop FPGA I2C (Xilinx AXI-IIC) device
Posted by Nodoka Shibasaki 4 weeks ago
Hi all,

This RFC adds a QEMU device model, "xiic-fpga-i2c", for a Nexthop-style
PCIe FPGA that exposes Xilinx AXI-IIC (i2c-xiic) controllers. The goal
is to let the SONiC platform bring-up path (the multifpgapci + i2c-xiic
kernel drivers, plus the userspace platform code that walks the I2C
tree) be exercised under QEMU without access to real switch hardware.

The device presents a PCIe function with a BAR0 register window that
decodes the AXI-IIC register map, gives each channel its own QEMU I2C
bus (so standard slave models such as at24c-eeprom / tmp105 / tmp421 can
be attached from the command line), drives real transfers through the
i2c core in dynamic (DTR) mode, and delivers completion via MSI with a
legacy INTx fallback.

Open questions I would appreciate guidance on:

  1. Is an emulation of a vendor-specific FPGA appropriate for hw/misc,
     or would you prefer it live elsewhere / be structured differently?
     It is modelled as a TEST_DEVICES-gated device alongside EDU and
     pci-testdev, since its purpose is CI/bring-up rather than modelling
     a shipping board.

  2. The AXI-IIC register decode here is deliberately partial: it
     implements the subset the Linux i2c-xiic driver actually touches in
     dynamic mode. I can flesh out the static/manual mode if that is
     preferred for a general-purpose model.

  3. A documentation file under docs/specs/ can be added if this is
     something you would consider merging; I left it out of the RFC to
     keep the first posting focused on the device itself.

This series is based on current master (191489ba75). No documentation or
qtest is included yet (happy to add both if there is interest).

Thanks,
Nodoka

Nodoka Shibasaki (1):
  hw/misc: add xiic-fpga-i2c, an emulated Nexthop PCIe FPGA I2C
    controller

 MAINTAINERS             |   5 +
 hw/misc/Kconfig         |   6 +
 hw/misc/meson.build     |   1 +
 hw/misc/xiic_fpga_i2c.c | 651 ++++++++++++++++++++++++++++++++++++++++
 4 files changed, 663 insertions(+)
 create mode 100644 hw/misc/xiic_fpga_i2c.c

-- 
2.50.1
[RFC PATCH v2 0/2] hw/i2c, hw/misc: emulated Xilinx AXI-IIC controller + PCIe FPGA carrier
Posted by Nodoka Shibasaki 2 weeks, 1 day ago
Hi all,

This is a follow-up to the single-patch RFC I sent last week ("[RFC
PATCH 1/1] hw/misc: add xiic-fpga-i2c, an emulated PCIe FPGA I2C
controller"). Based on the split suggested there between a
standardized controller and its carrier, this v2 breaks the device in
two:

  1/2 - hw/i2c: a generic model of the AMD/Xilinx AXI IIC controller
        (LogiCORE IP, PG090) as a SysBus device, independent of any
        particular carrier.
  2/2 - hw/misc: a PCIe function, gated behind TEST_DEVICES, that
        embeds N of the above controllers behind one BAR0 window and
        aggregates their interrupts onto a single MSI vector (with a
        legacy INTx fallback). This models a specific FPGA carrier
        rather than a standardized board, hence hw/misc + TEST_DEVICES
        rather than hw/i2c proper.

Together they let the Linux i2c-xiic driver, and any I2C slave behind
it, be exercised under QEMU without real hardware.

Changes since v1:
  - Split the single device into a generic hw/i2c controller model and
    a separate hw/misc PCIe carrier that instantiates it.
  - Added a register-level spec for the controller
    (docs/specs/xlnx-axi-iic.rst).
  - Added a qtest (tests/qtest/xiic-fpga-i2c-test.c) that drives a
    transfer through BAR0 to a tmp105 slave and checks register,
    transfer and NACK behavior.
  - Reworded comments/commit messages to describe the hardware in
    generic terms rather than referencing a specific downstream
    project.

Open questions (carried over / updated from v1):

  1. Is hw/misc + TEST_DEVICES the right home for the carrier, given
     the controller itself is now a standalone hw/i2c device? I split
     it this way since the carrier's register aggregation is specific
     to one FPGA design rather than a standardized board.

  2. The AXI-IIC register decode is deliberately partial: it
     implements the subset the Linux i2c-xiic driver actually touches
     in dynamic mode. I can flesh out static/manual mode if that is
     preferred for a general-purpose model.

This series is based on current master. Happy to add more qtest
coverage or documentation if there's interest in merging either half.

Thanks

Nodoka Shibasaki (2):
  hw/i2c: add xlnx-axi-iic, a Xilinx AXI IIC controller
  hw/misc: add xiic-fpga-i2c, a PCIe FPGA embedding xlnx-axi-iic cores

 MAINTAINERS                                |  12 +
 configs/devices/x86_64-softmmu/default.mak |   5 +
 docs/specs/index.rst                       |   1 +
 docs/specs/xlnx-axi-iic.rst                |  62 +++++
 hw/i2c/Kconfig                             |   4 +
 hw/i2c/meson.build                         |   1 +
 hw/i2c/xlnx-axi-iic.c                      | 284 +++++++++++++++++++++
 hw/misc/Kconfig                            |   6 +
 hw/misc/meson.build                        |   1 +
 hw/misc/xiic_fpga_i2c.c                    | 261 +++++++++++++++++++
 include/hw/i2c/xlnx-axi-iic.h              |  69 +++++
 tests/qtest/meson.build                    |   2 +
 tests/qtest/xiic-fpga-i2c-test.c           | 159 ++++++++++++
 13 files changed, 867 insertions(+)
 create mode 100644 docs/specs/xlnx-axi-iic.rst
 create mode 100644 hw/i2c/xlnx-axi-iic.c
 create mode 100644 hw/misc/xiic_fpga_i2c.c
 create mode 100644 include/hw/i2c/xlnx-axi-iic.h
 create mode 100644 tests/qtest/xiic-fpga-i2c-test.c

-- 
2.50.1
[RFC PATCH v2 1/2] hw/i2c: add xlnx-axi-iic, a Xilinx AXI IIC controller
Posted by Nodoka Shibasaki 2 weeks, 1 day ago
Add a model of the AMD/Xilinx AXI IIC controller (LogiCORE IP, PG090),
an I2C bus controller. QEMU has no AXI IIC model today; this lets a guest
driver for it, and the I2C slaves behind it, be exercised without real
hardware.

The device is a SysBus device: it owns one I2C bus, decodes the AXI IIC
register map, implements the controller's dynamic transfer mode, and
drives a single level-triggered interrupt line while DGIER is enabled and
IISR & IIER is set. It has a reset handler and migration state, and a
register-level specification in docs/specs/xlnx-axi-iic.rst.

Signed-off-by: Nodoka Shibasaki <nodokaorganized@gmail.com>
---
 MAINTAINERS                   |  10 ++
 docs/specs/index.rst          |   1 +
 docs/specs/xlnx-axi-iic.rst   |  62 ++++++++
 hw/i2c/Kconfig                |   4 +
 hw/i2c/meson.build            |   1 +
 hw/i2c/xlnx-axi-iic.c         | 284 ++++++++++++++++++++++++++++++++++
 include/hw/i2c/xlnx-axi-iic.h |  69 +++++++++
 7 files changed, 431 insertions(+)
 create mode 100644 docs/specs/xlnx-axi-iic.rst
 create mode 100644 hw/i2c/xlnx-axi-iic.c
 create mode 100644 include/hw/i2c/xlnx-axi-iic.h

diff --git a/MAINTAINERS b/MAINTAINERS
index a28935c898..8339c9bf83 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -2158,6 +2158,16 @@ S: Maintained
 F: hw/misc/edu.c
 F: docs/specs/edu.rst
 
+<<<<<<< Updated upstream
+=======
+xiic-fpga-i2c
+M: Nodoka Shibasaki <nodokaorganized@gmail.com>
+S: Maintained
+F: hw/i2c/xlnx-axi-iic.c
+F: include/hw/i2c/xlnx-axi-iic.h
+F: docs/specs/xlnx-axi-iic.rst
+
+>>>>>>> Stashed changes
 IDE
 M: John Snow <jsnow@redhat.com>
 L: qemu-block@nongnu.org
diff --git a/docs/specs/index.rst b/docs/specs/index.rst
index b7909a108a..41b45a5be4 100644
--- a/docs/specs/index.rst
+++ b/docs/specs/index.rst
@@ -40,3 +40,4 @@ guest hardware that is specific to QEMU.
    riscv-aia
    aspeed-intc
    iommu-testdev
+   xlnx-axi-iic
diff --git a/docs/specs/xlnx-axi-iic.rst b/docs/specs/xlnx-axi-iic.rst
new file mode 100644
index 0000000000..a19b479522
--- /dev/null
+++ b/docs/specs/xlnx-axi-iic.rst
@@ -0,0 +1,62 @@
+.. SPDX-License-Identifier: GPL-2.0-or-later
+
+Xilinx AXI IIC device
+======================
+
+``xlnx-axi-iic`` models the AMD/Xilinx AXI IIC (LogiCORE IP, documented in
+Xilinx PG090) I2C bus controller. It is a SysBus device: a board or a parent
+device maps its single MMIO region and connects its interrupt line, and I2C
+slave models are attached to the ``i2c`` bus it creates.
+
+The model implements the controller's *dynamic* transfer mode, which is what
+a guest driver uses by default.
+
+Properties
+----------
+
+``bus-name``
+  Name given to the I2C bus the controller creates (default ``i2c``). A parent
+  device that instantiates several controllers uses this to give each bus a
+  unique, user-referenceable name.
+
+MMIO register map
+-----------------
+
+Offsets are relative to the start of the controller's MMIO region.
+
+  0x1C (RW) : DGIER, global interrupt enable (bit 31)
+  0x20 (RW) : IISR, interrupt status; write-1-to-clear
+  0x28 (RW) : IIER, interrupt enable
+  0x40 (WO) : RESETR, soft reset (write 0xA)
+  0x100 (RW) : CR, control
+  0x104 (RO) : SR, status (computed)
+  0x108 (WO) : DTR, tx data and dynamic START (bit 8) / STOP (bit 9)
+  0x10C (RO) : DRR, rx data
+  0x114 (RO) : TFO, tx FIFO occupancy (always 0; the FIFO drains immediately)
+  0x118 (RO) : RFO, rx FIFO occupancy
+  0x120 (RW) : RFD, rx FIFO programmable depth
+
+Status register (SR) bits: 0x04 bus busy, 0x20 rx FIFO full, 0x40 rx FIFO
+empty, 0x80 tx FIFO empty. Interrupt (IISR/IIER) bits: 0x01 arbitration lost,
+0x02 tx error / NACK, 0x04 tx FIFO empty, 0x08 rx FIFO full, 0x10 bus-not-busy.
+
+Dynamic-mode transfers
+----------------------
+
+The low 8 bits of a DTR write are the data byte; bit 8 (START) frames the
+8-bit address (bit 0 is the read/write flag) that opens a transfer, and bit 9
+(STOP) ends it.
+
+- Write: DTR <- addr|START, then each data byte, the last with STOP.
+- Read: DTR <- addr|START (read flag set), then DTR <- count|STOP. The
+  controller clocks ``count`` bytes from the slave into the rx FIFO, raises
+  IISR.RX_FULL, and raises IISR.BNB once the FIFO has been drained through DRR.
+
+A slave that does not acknowledge sets IISR.TX_ERROR and releases the bus.
+
+Interrupt
+---------
+
+The controller drives a single level-triggered output line, asserted while
+DGIER is enabled and ``IISR & IIER`` is non-zero, and deasserted when the guest
+clears the pending, enabled causes.
diff --git a/hw/i2c/Kconfig b/hw/i2c/Kconfig
index 0766130b59..4faf2bda99 100644
--- a/hw/i2c/Kconfig
+++ b/hw/i2c/Kconfig
@@ -43,6 +43,10 @@ config ALLWINNER_I2C
     bool
     select I2C
 
+config XLNX_AXI_IIC
+    bool
+    select I2C
+
 config PCA954X
     bool
     select I2C
diff --git a/hw/i2c/meson.build b/hw/i2c/meson.build
index 88aea35662..939f02f03b 100644
--- a/hw/i2c/meson.build
+++ b/hw/i2c/meson.build
@@ -9,6 +9,7 @@ i2c_ss.add(when: 'CONFIG_EXYNOS4', if_true: files('exynos4210_i2c.c'))
 i2c_ss.add(when: 'CONFIG_IMX_I2C', if_true: files('imx_i2c.c'))
 i2c_ss.add(when: 'CONFIG_MPC_I2C', if_true: files('mpc_i2c.c'))
 i2c_ss.add(when: 'CONFIG_ALLWINNER_I2C', if_true: files('allwinner-i2c.c'))
+i2c_ss.add(when: 'CONFIG_XLNX_AXI_IIC', if_true: files('xlnx-axi-iic.c'))
 i2c_ss.add(when: 'CONFIG_NRF51_SOC', if_true: files('microbit_i2c.c'))
 i2c_ss.add(when: 'CONFIG_NPCM7XX', if_true: files('npcm7xx_smbus.c'))
 i2c_ss.add(when: 'CONFIG_DESIGNWARE_I2C', if_true: files('designware_i2c.c'))
diff --git a/hw/i2c/xlnx-axi-iic.c b/hw/i2c/xlnx-axi-iic.c
new file mode 100644
index 0000000000..c27398c319
--- /dev/null
+++ b/hw/i2c/xlnx-axi-iic.c
@@ -0,0 +1,284 @@
+/*
+ * xlnx-axi-iic.c - QEMU model of the Xilinx AXI IIC (LogiCORE IP) controller.
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#include "qemu/osdep.h"
+#include "qemu/log.h"
+#include "qemu/module.h"
+#include "migration/vmstate.h"
+#include "hw/core/irq.h"
+#include "hw/core/sysbus.h"
+#include "hw/core/qdev-properties.h"
+#include "hw/i2c/xlnx-axi-iic.h"
+
+static bool xlnx_axi_iic_pending(XlnxAxiIicState *s)
+{
+    if (!(s->dgier & XLNX_AXI_IIC_GINTR_ENABLE_MASK)) {
+        return false;
+    }
+    return (s->isr & s->ier) != 0;
+}
+
+static void xlnx_axi_iic_update_irq(XlnxAxiIicState *s)
+{
+    qemu_set_irq(s->irq, xlnx_axi_iic_pending(s));
+}
+
+static void xlnx_axi_iic_reset_regs(XlnxAxiIicState *s)
+{
+    if (s->in_xfer) {
+        i2c_end_transfer(s->bus);
+    }
+    s->cr = 0;
+    s->isr = 0;
+    s->ier = 0;
+    s->dgier = 0;
+    s->rfd = 0;
+    s->in_xfer = false;
+    s->is_recv = false;
+    s->stop_pending = false;
+    s->rx_len = 0;
+    s->rx_pos = 0;
+    memset(s->rx_fifo, 0, sizeof(s->rx_fifo));
+}
+
+static uint32_t xlnx_axi_iic_status(XlnxAxiIicState *s)
+{
+    uint32_t sr = XLNX_AXI_IIC_SR_TX_FIFO_EMPTY_MASK;
+
+    sr |= (s->rx_pos >= s->rx_len) ? XLNX_AXI_IIC_SR_RX_FIFO_EMPTY_MASK
+                                   : XLNX_AXI_IIC_SR_RX_FIFO_FULL_MASK;
+    if (s->in_xfer) {
+        sr |= XLNX_AXI_IIC_SR_BUS_BUSY_MASK;
+    }
+    return sr;
+}
+
+static void xlnx_axi_iic_fail(XlnxAxiIicState *s)
+{
+    i2c_end_transfer(s->bus);
+    s->in_xfer = false;
+    s->isr |= XLNX_AXI_IIC_INTR_TX_ERROR_MASK | XLNX_AXI_IIC_INTR_BNB_MASK;
+    xlnx_axi_iic_update_irq(s);
+}
+
+static uint64_t xlnx_axi_iic_read(void *opaque, hwaddr addr, unsigned size)
+{
+    XlnxAxiIicState *s = opaque;
+    bool rx_empty = s->rx_pos >= s->rx_len;
+    uint64_t val = 0;
+
+    switch (addr) {
+    case XLNX_AXI_IIC_SR:
+        val = xlnx_axi_iic_status(s);
+        break;
+    case XLNX_AXI_IIC_IISR:
+        val = s->isr;
+        break;
+    case XLNX_AXI_IIC_IIER:
+        val = s->ier;
+        break;
+    case XLNX_AXI_IIC_DGIER:
+        val = s->dgier;
+        break;
+    case XLNX_AXI_IIC_CR:
+        val = s->cr;
+        break;
+    case XLNX_AXI_IIC_RFD:
+        val = s->rfd;
+        break;
+    case XLNX_AXI_IIC_RFO:
+        val = rx_empty ? 0 : (s->rx_len - s->rx_pos - 1);
+        break;
+    case XLNX_AXI_IIC_DRR:
+        if (!rx_empty) {
+            val = s->rx_fifo[s->rx_pos++];
+            if (s->rx_pos >= s->rx_len) {
+                s->isr &= ~(uint32_t)XLNX_AXI_IIC_INTR_RX_FULL_MASK;
+                if (s->stop_pending) {
+                    s->stop_pending = false;
+                    s->in_xfer = false;
+                    s->isr |= XLNX_AXI_IIC_INTR_BNB_MASK;
+                }
+                xlnx_axi_iic_update_irq(s);
+            }
+        }
+        break;
+    default:
+        break;
+    }
+    return val;
+}
+
+static void xlnx_axi_iic_dtr_write(XlnxAxiIicState *s, uint64_t val)
+{
+    uint16_t word = val & 0xFFFF;
+    bool stop = word & XLNX_AXI_IIC_TX_DYN_STOP_MASK;
+
+    if (word & XLNX_AXI_IIC_TX_DYN_START_MASK) {
+        uint8_t addr8 = word & 0xFF;
+        s->is_recv = addr8 & 1;
+        s->rx_len = 0;
+        s->rx_pos = 0;
+
+        int nack = s->is_recv ? i2c_start_recv(s->bus, addr8 >> 1)
+                              : i2c_start_send(s->bus, addr8 >> 1);
+        s->in_xfer = true;
+        if (nack) {
+            xlnx_axi_iic_fail(s);
+        }
+        return;
+    }
+
+    if (!s->in_xfer) {
+        return;
+    }
+
+    if (s->is_recv) {
+        unsigned room = XLNX_AXI_IIC_RX_FIFO_MAX - s->rx_len;
+        unsigned n = MIN((unsigned)(word & 0xFF), room);
+
+        for (unsigned i = 0; i < n; i++) {
+            s->rx_fifo[s->rx_len++] = i2c_recv(s->bus);
+        }
+        if (stop) {
+            i2c_end_transfer(s->bus);
+            s->stop_pending = true;
+        }
+        if (s->rx_len > 0) {
+            s->isr |= XLNX_AXI_IIC_INTR_RX_FULL_MASK;
+        }
+        xlnx_axi_iic_update_irq(s);
+        return;
+    }
+
+    if (i2c_send(s->bus, word & 0xFF)) {
+        xlnx_axi_iic_fail(s);
+        return;
+    }
+    if (stop) {
+        i2c_end_transfer(s->bus);
+        s->in_xfer = false;
+        s->isr |= XLNX_AXI_IIC_INTR_TX_EMPTY_MASK | XLNX_AXI_IIC_INTR_BNB_MASK;
+    } else {
+        s->isr |= XLNX_AXI_IIC_INTR_TX_EMPTY_MASK;
+    }
+    xlnx_axi_iic_update_irq(s);
+}
+
+static void xlnx_axi_iic_write(void *opaque, hwaddr addr, uint64_t val,
+                               unsigned size)
+{
+    XlnxAxiIicState *s = opaque;
+
+    switch (addr) {
+    case XLNX_AXI_IIC_RESETR:
+        if ((val & 0xf) == XLNX_AXI_IIC_RESET_MASK) {
+            device_cold_reset(DEVICE(s));
+        }
+        break;
+    case XLNX_AXI_IIC_CR:
+        s->cr = val;
+        break;
+    case XLNX_AXI_IIC_DGIER:
+        s->dgier = val;
+        xlnx_axi_iic_update_irq(s);
+        break;
+    case XLNX_AXI_IIC_IIER:
+        s->ier = val;
+        xlnx_axi_iic_update_irq(s);
+        break;
+    case XLNX_AXI_IIC_IISR:
+        s->isr &= ~(uint32_t)val;
+        xlnx_axi_iic_update_irq(s);
+        break;
+    case XLNX_AXI_IIC_RFD:
+        s->rfd = val;
+        break;
+    case XLNX_AXI_IIC_DTR:
+        xlnx_axi_iic_dtr_write(s, val);
+        break;
+    default:
+        break;
+    }
+}
+
+static const MemoryRegionOps xlnx_axi_iic_ops = {
+    .read = xlnx_axi_iic_read,
+    .write = xlnx_axi_iic_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 xlnx_axi_iic_realize(DeviceState *dev, Error **errp)
+{
+    XlnxAxiIicState *s = XLNX_AXI_IIC(dev);
+
+    memory_region_init_io(&s->mmio, OBJECT(s), &xlnx_axi_iic_ops, s,
+                          TYPE_XLNX_AXI_IIC, XLNX_AXI_IIC_REGS_SIZE);
+    sysbus_init_mmio(SYS_BUS_DEVICE(dev), &s->mmio);
+    sysbus_init_irq(SYS_BUS_DEVICE(dev), &s->irq);
+    s->bus = i2c_init_bus(dev, s->bus_name ? s->bus_name : "i2c");
+}
+
+static void xlnx_axi_iic_reset_hold(Object *obj, ResetType type)
+{
+    XlnxAxiIicState *s = XLNX_AXI_IIC(obj);
+
+    xlnx_axi_iic_reset_regs(s);
+    xlnx_axi_iic_update_irq(s);
+}
+
+static const VMStateDescription vmstate_xlnx_axi_iic = {
+    .name = TYPE_XLNX_AXI_IIC,
+    .version_id = 1,
+    .minimum_version_id = 1,
+    .fields = (const VMStateField[]) {
+        VMSTATE_UINT32(cr, XlnxAxiIicState),
+        VMSTATE_UINT32(isr, XlnxAxiIicState),
+        VMSTATE_UINT32(ier, XlnxAxiIicState),
+        VMSTATE_UINT32(dgier, XlnxAxiIicState),
+        VMSTATE_UINT32(rfd, XlnxAxiIicState),
+        VMSTATE_BOOL(in_xfer, XlnxAxiIicState),
+        VMSTATE_BOOL(is_recv, XlnxAxiIicState),
+        VMSTATE_BOOL(stop_pending, XlnxAxiIicState),
+        VMSTATE_UINT8_ARRAY(rx_fifo, XlnxAxiIicState, XLNX_AXI_IIC_RX_FIFO_MAX),
+        VMSTATE_INT32(rx_len, XlnxAxiIicState),
+        VMSTATE_INT32(rx_pos, XlnxAxiIicState),
+        VMSTATE_END_OF_LIST()
+    }
+};
+
+static const Property xlnx_axi_iic_props[] = {
+    DEFINE_PROP_STRING("bus-name", XlnxAxiIicState, bus_name),
+};
+
+static void xlnx_axi_iic_class_init(ObjectClass *klass, const void *data)
+{
+    DeviceClass *dc = DEVICE_CLASS(klass);
+    ResettableClass *rc = RESETTABLE_CLASS(klass);
+
+    dc->realize = xlnx_axi_iic_realize;
+    dc->vmsd = &vmstate_xlnx_axi_iic;
+    rc->phases.hold = xlnx_axi_iic_reset_hold;
+    dc->desc = "Xilinx AXI IIC controller";
+    set_bit(DEVICE_CATEGORY_MISC, dc->categories);
+    device_class_set_props(dc, xlnx_axi_iic_props);
+}
+
+static const TypeInfo xlnx_axi_iic_info = {
+    .name          = TYPE_XLNX_AXI_IIC,
+    .parent        = TYPE_SYS_BUS_DEVICE,
+    .instance_size = sizeof(XlnxAxiIicState),
+    .class_init    = xlnx_axi_iic_class_init,
+};
+
+static void xlnx_axi_iic_register_types(void)
+{
+    type_register_static(&xlnx_axi_iic_info);
+}
+
+type_init(xlnx_axi_iic_register_types)
diff --git a/include/hw/i2c/xlnx-axi-iic.h b/include/hw/i2c/xlnx-axi-iic.h
new file mode 100644
index 0000000000..daa65c4140
--- /dev/null
+++ b/include/hw/i2c/xlnx-axi-iic.h
@@ -0,0 +1,69 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+
+#ifndef HW_I2C_XLNX_AXI_IIC_H
+#define HW_I2C_XLNX_AXI_IIC_H
+
+#include "hw/core/sysbus.h"
+#include "hw/i2c/i2c.h"
+#include "qom/object.h"
+
+#define TYPE_XLNX_AXI_IIC "xlnx-axi-iic"
+OBJECT_DECLARE_SIMPLE_TYPE(XlnxAxiIicState, XLNX_AXI_IIC)
+
+#define XLNX_AXI_IIC_REGS_SIZE      0x1000
+#define XLNX_AXI_IIC_RX_FIFO_MAX    256
+
+#define XLNX_AXI_IIC_DGIER          0x1C
+#define XLNX_AXI_IIC_IISR           0x20
+#define XLNX_AXI_IIC_IIER           0x28
+#define XLNX_AXI_IIC_RESETR         0x40
+#define XLNX_AXI_IIC_CR             0x100
+#define XLNX_AXI_IIC_SR             0x104
+#define XLNX_AXI_IIC_DTR            0x108
+#define XLNX_AXI_IIC_DRR            0x10C
+#define XLNX_AXI_IIC_TFO            0x114
+#define XLNX_AXI_IIC_RFO            0x118
+#define XLNX_AXI_IIC_RFD            0x120
+
+#define XLNX_AXI_IIC_RESET_MASK             0xA
+
+#define XLNX_AXI_IIC_SR_BUS_BUSY_MASK       0x04
+#define XLNX_AXI_IIC_SR_RX_FIFO_FULL_MASK   0x20
+#define XLNX_AXI_IIC_SR_RX_FIFO_EMPTY_MASK  0x40
+#define XLNX_AXI_IIC_SR_TX_FIFO_EMPTY_MASK  0x80
+
+#define XLNX_AXI_IIC_INTR_ARB_LOST_MASK     0x01
+#define XLNX_AXI_IIC_INTR_TX_ERROR_MASK     0x02
+#define XLNX_AXI_IIC_INTR_TX_EMPTY_MASK     0x04
+#define XLNX_AXI_IIC_INTR_RX_FULL_MASK      0x08
+#define XLNX_AXI_IIC_INTR_BNB_MASK          0x10
+
+#define XLNX_AXI_IIC_GINTR_ENABLE_MASK      0x80000000UL
+
+#define XLNX_AXI_IIC_TX_DYN_START_MASK      0x0100
+#define XLNX_AXI_IIC_TX_DYN_STOP_MASK       0x0200
+
+struct XlnxAxiIicState {
+    SysBusDevice parent_obj;
+
+    MemoryRegion mmio;
+    qemu_irq irq;
+    I2CBus *bus;
+    char *bus_name;
+
+    uint32_t cr;
+    uint32_t isr;
+    uint32_t ier;
+    uint32_t dgier;
+    uint32_t rfd;
+
+    bool in_xfer;
+    bool is_recv;
+    bool stop_pending;
+
+    uint8_t rx_fifo[XLNX_AXI_IIC_RX_FIFO_MAX];
+    int rx_len;
+    int rx_pos;
+};
+
+#endif
-- 
2.50.1
[RFC PATCH v2 2/2] hw/misc: add xiic-fpga-i2c, a PCIe FPGA embedding xlnx-axi-iic cores
Posted by Nodoka Shibasaki 2 weeks, 1 day ago
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 <nodokaorganized@gmail.com>
---
 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 <nodokaorganized@gmail.com>
 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 <nodokaorganized@gmail.com>
+S: Maintained
+F: hw/misc/xiic_fpga_i2c.c
+
 IDE
 M: John Snow <jsnow@redhat.com>
 L: qemu-block@nongnu.org
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