Add a QEMU model of a Nexthop-style PCIe FPGA that exposes Xilinx AXI-IIC (i2c-xiic) controllers, so that SONiC's multifpgapci / i2c-xiic hardware path can be exercised without real switch hardware.
The device presents a PCIe function with a BAR0 register window that decodes the Xilinx AXI-IIC register map. Each channel exposes its own QEMU I2C bus, so slave models can be attached from the command line, for example: -device xiic-fpga-i2c,id=fpga,num-channels=2 \ -device at24c-eeprom,bus=xiic-fpga-i2c.0,address=0x50 Dynamic-mode DTR transfers drive the I2C core via the standard i2c_start_send()/i2c_start_recv()/i2c_send()/i2c_recv() helpers, a NACK is reported through IISR.TX_ERROR, and transfer completion is delivered via MSI (one vector per channel) with a legacy INTx fallback. The device is gated behind a new CONFIG_XIIC_FPGA_I2C Kconfig symbol (default y if TEST_DEVICES), following the convention used by other hw/misc devices such as EDU and pci-testdev. Signed-off-by: Nodoka Shibasaki <[email protected]> --- 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 diff --git a/MAINTAINERS b/MAINTAINERS index ecb8cfdc41..8bbc7202fa 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -2157,6 +2157,11 @@ S: Maintained F: hw/misc/edu.c F: docs/specs/edu.rst +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/hw/misc/Kconfig b/hw/misc/Kconfig index 1543ee6653..3aa26a6550 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 + select I2C + 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..0b36ba88bf --- /dev/null +++ b/hw/misc/xiic_fpga_i2c.c @@ -0,0 +1,651 @@ +/* + * xiic_fpga_i2c.c - QEMU model of a Nexthop-style PCIe FPGA that exposes + * Xilinx AXI-IIC controllers, for emulating the SONiC + * multifpgapci / i2c-xiic hardware path WITHOUT real switch + * hardware. + * + * Copyright (C) 2026 Nexthop Systems Inc. + * SPDX-License-Identifier: GPL-2.0-or-later + * + * =========================================================================== + * MILESTONE 1: PCIe device + BAR0 register window. + * - Appears in `lspci`. + * - Decodes the Xilinx AXI-IIC register map and LOGS every read/write so you + * can watch a kernel driver probe it. + * - Returns a correct idle Status Register (SR = TX/RX FIFO empty) and + * handles soft reset, so the i2c-xiic probe sequence can progress. + * + * MILESTONE 2: real I2C transfers. + * - Each channel gets its own QEMU I2CBus; attach slave models from the + * command line + * (e.g. -device at24c-eeprom,bus=xiic-fpga-i2c.0,address=0x50). + * - Decodes the dynamic-mode DTR protocol (START 0x100 / STOP 0x200), drives + * the bus via i2c_start_send/i2c_start_recv/i2c_send/i2c_recv, buffers RX + * data for DRR reads, and sets IISR.TX_ERROR on a NACK. + * + * MILESTONE 3: MSI interrupt delivery (+ legacy INTx fallback). + * - Per-channel MSI vector (vector index == channel) so the interrupt-driven + * upstream i2c-xiic.c path completes transfers. Falls back to INTx when the + * guest has not enabled MSI. See README for the multifpgapci IRQ caveat. + * + * Register map per channel (offset relative to that channel's base in BAR0): + * DGIER 0x1C global interrupt enable + * IISR 0x20 interrupt status (write-1-to-clear) + * IIER 0x28 interrupt enable + * RESETR 0x40 soft reset (write 0xA) + * CR 0x100 control + * SR 0x104 status (read-only, computed) + * DTR 0x108 tx data + dynamic START(0x100)/STOP(0x200) + * DRR 0x10C rx data + * RFD 0x120 rx fifo depth + * This matches drivers/i2c/busses/i2c-xiic.c and the Nexthop polled algo. + * =========================================================================== + */ + +#include "qemu/osdep.h" +#include "qemu/log.h" +#include "qemu/module.h" +#include "qemu/host-utils.h" +#include "hw/pci/pci_device.h" +#include "hw/pci/msi.h" +/* qdev-properties.h moved to hw/core/ in QEMU 11.0; support both locations. */ +#if defined(__has_include) && __has_include("hw/core/qdev-properties.h") +#include "hw/core/qdev-properties.h" +#else +#include "hw/qdev-properties.h" +#endif +#include "hw/i2c/i2c.h" + +/* ---- AXI-IIC register offsets (relative to a channel base) ---- */ +#define XIIC_DGIER_OFFSET 0x1C +#define XIIC_IISR_OFFSET 0x20 +#define XIIC_IIER_OFFSET 0x28 +#define XIIC_RESETR_OFFSET 0x40 +#define XIIC_CR_OFFSET 0x100 +#define XIIC_SR_OFFSET 0x104 +#define XIIC_DTR_OFFSET 0x108 +#define XIIC_DRR_OFFSET 0x10C +/* tx fifo occupancy (always 0, drains now) */ +#define XIIC_TFO_OFFSET 0x114 +#define XIIC_RFO_OFFSET 0x118 /* rx fifo occupancy */ +#define XIIC_RFD_OFFSET 0x120 + +#define XIIC_RESET_MASK 0xA + +/* Status register bits the drivers check. */ +#define XIIC_SR_BUS_BUSY_MASK 0x04 +#define XIIC_SR_RX_FIFO_FULL_MASK 0x20 +#define XIIC_SR_RX_FIFO_EMPTY_MASK 0x40 +#define XIIC_SR_TX_FIFO_EMPTY_MASK 0x80 + +/* + * An idle controller reports both FIFOs empty. The i2c-xiic probe relies on + * the TX bit (endianness check) and the RX bit (clear_rx_fifo loop). + */ +#define XIIC_SR_IDLE (XIIC_SR_TX_FIFO_EMPTY_MASK | XIIC_SR_RX_FIFO_EMPTY_MASK) + +/* + * Interrupt Status / Enable register bits (IISR/IIER), write-1-to-clear on + * IISR. Values match drivers/i2c/busses/i2c-xiic.c. + */ +#define XIIC_INTR_ARB_LOST_MASK 0x01 +#define XIIC_INTR_TX_ERROR_MASK 0x02 /* NACK, or (with RX_FULL) msg done */ +#define XIIC_INTR_TX_EMPTY_MASK 0x04 +#define XIIC_INTR_RX_FULL_MASK 0x08 +/* bus not busy -> transfer complete */ +#define XIIC_INTR_BNB_MASK 0x10 + +/* DGIER global interrupt enable bit. */ +#define XIIC_GINTR_ENABLE_MASK 0x80000000UL + +/* DTR dynamic-mode framing bits (the data byte is the low 8 bits). */ +#define XIIC_TX_DYN_START_MASK 0x0100 +#define XIIC_TX_DYN_STOP_MASK 0x0200 + +/* + * RX FIFO is 16 deep on real AXI-IIC, but dynamic reads can be up to 255 + * bytes; size our software buffer to the dynamic maximum. + */ +#define XIIC_RX_FIFO_MAX 256 + +#define TYPE_XIIC_FPGA_I2C "xiic-fpga-i2c" +OBJECT_DECLARE_SIMPLE_TYPE(XiicFpgaI2cState, XIIC_FPGA_I2C) + +/* Per-channel AXI-IIC register + transfer state. */ +typedef struct { + uint32_t cr; + uint32_t isr; /* IISR */ + uint32_t ier; /* IIER */ + uint32_t dgier; + uint32_t rfd; + + I2CBus *bus; /* slaves attach here */ + + /* Dynamic-mode transfer state (Milestone 2). */ + bool in_xfer; /* a START has been issued, no STOP yet */ + bool is_recv; /* current transfer direction is read */ + bool stop_pending;/* STOP issued; raise BNB once the RX FIFO drains */ + bool irq_level; /* last pending state, for MSI edge detection */ + + uint8_t rx_fifo[XIIC_RX_FIFO_MAX]; + int rx_len; /* bytes available */ + int rx_pos; /* next byte to hand to a DRR read */ +} XiicChannel; + +struct XiicFpgaI2cState { + PCIDevice parent_obj; + + MemoryRegion mmio; /* BAR0 */ + + /* Layout knobs (properties). Match these to the platform device JSON. */ + uint32_t num_channels; + uint32_t ch_base_offset; /* where channel 0 starts in BAR0 */ + uint32_t ch_stride; /* bytes between channels */ + uint32_t bar_size; + + uint32_t num_msi_vectors; /* >= num_channels, rounded to a power of two */ + + /* + * Top-level (FPGA-wide) interrupt block. The multifpgapci driver wires a + * single regmap-irq chip over one STATUS register (bit N == channel N) plus + * an UNMASK register, demuxing every channel IRQ from one shared MSI + * vector. Offsets are properties so they can be matched to the platform's + * use_msi JSON (interrupt_status_reg / interrupt_enable_reg). + */ + uint32_t irq_status_offset; /* BAR offset of the per-channel status bits */ + uint32_t irq_unmask_offset; /* BAR offset of the unmask/enable register */ + uint32_t irq_msi_vector; /* MSI vector backing the regmap-irq domain */ + + uint32_t irq_unmask; /* last value written to the unmask register */ + /* an MSI edge is outstanding (level emulation) */ + bool msi_asserted; + + XiicChannel *chan; +}; + +/* + * Map a BAR offset to (channel index, register offset within channel). + * Returns -1 if the offset is outside the channel region. + */ +static int offset_to_channel(XiicFpgaI2cState *s, hwaddr addr, hwaddr *reg) +{ + if (addr < s->ch_base_offset) { + return -1; + } + hwaddr rel = addr - s->ch_base_offset; + int idx = rel / s->ch_stride; + + if (idx >= s->num_channels) { + return -1; + } + *reg = rel % s->ch_stride; + return idx; +} + +/* Is this channel asserting an enabled, globally-unmasked interrupt? */ +static bool xiic_chan_pending(XiicChannel *c) +{ + if (!(c->dgier & XIIC_GINTR_ENABLE_MASK)) { + return false; + } + return (c->isr & c->ier) != 0; +} + +/* + * Top-level interrupt status: bit N is set when channel N has an enabled, + * globally-unmasked AXI-IIC interrupt. This is what the driver's regmap-irq + * chip reads (status_base) to demux per-channel IRQs. + */ +static uint32_t xiic_compute_irq_status(XiicFpgaI2cState *s) +{ + uint32_t st = 0; + for (unsigned i = 0; i < s->num_channels; i++) { + if (xiic_chan_pending(&s->chan[i])) { + st |= (1u << i); + } + } + return st; +} + +/* + * Re-evaluate interrupt delivery after any isr/ier/dgier change. + * + * The multifpgapci driver demuxes every channel IRQ from ONE shared MSI vector + * via a regmap-irq chip that reads the top-level status register. The driver's + * per-channel IRQ source is *level* (asserted while IISR & IIER is set), but + * MSI is edge, so we emulate a level source: keep an "asserted" flag and fire + * one MSI edge while any unmasked channel is pending. The flag is cleared when + * the regmap-irq handler acks the status register (xiic_fpga_i2c_mmio_write), + * which re-arms delivery so a still-pending channel (e.g. BNB raised while + * RX_FULL was being serviced) gets another edge instead of hanging. The ch + * argument is unused; the whole top-level state is recomputed each call. + */ +static void xiic_update_irq(XiicFpgaI2cState *s, int ch) +{ + PCIDevice *pci_dev = PCI_DEVICE(s); + uint32_t status = xiic_compute_irq_status(s); + uint32_t active = status & s->irq_unmask; + + (void)ch; + + if (msi_enabled(pci_dev)) { + if (active && !s->msi_asserted) { + unsigned vec = s->irq_msi_vector < s->num_msi_vectors ? + s->irq_msi_vector : 0; + qemu_log_mask(LOG_TRACE, + "xiic-fpga-i2c: MSI notify vec=%u status=0x%x\n", + vec, status); + msi_notify(pci_dev, vec); + s->msi_asserted = true; + } else if (!active) { + s->msi_asserted = false; + } + return; + } + + pci_set_irq(pci_dev, active != 0); +} + +static uint64_t xiic_fpga_i2c_mmio_read(void *opaque, hwaddr addr, + unsigned size) +{ + XiicFpgaI2cState *s = opaque; + hwaddr reg; + uint64_t val = 0; + + /* + * Top-level interrupt registers (read live so the driver's regmap-irq + * handler always sees the current per-channel pending bits). + */ + if (addr == s->irq_status_offset) { + val = xiic_compute_irq_status(s); + qemu_log_mask(LOG_TRACE, + "xiic-fpga-i2c: RD IRQ_STATUS -> 0x%08" PRIx64 "\n", + val); + return val; + } + if (addr == s->irq_unmask_offset) { + qemu_log_mask(LOG_TRACE, "xiic-fpga-i2c: RD IRQ_UNMASK -> 0x%08x\n", + s->irq_unmask); + return s->irq_unmask; + } + + int ch = offset_to_channel(s, addr, ®); + + if (ch < 0) { + qemu_log_mask(LOG_UNIMP, + "xiic-fpga-i2c: RD off=0x%04" HWADDR_PRIx + " (outside channels) -> 0\n", addr); + return 0; + } + + XiicChannel *c = &s->chan[ch]; + bool rx_empty = c->rx_pos >= c->rx_len; + + switch (reg) { + case XIIC_SR_OFFSET: + /* + * Transfers complete synchronously inside the DTR write, so the TX + * FIFO always reads empty and the bus only looks busy while we are + * mid-sequence. RX-empty reflects the software RX FIFO so the + * clear_rx_fifo loop and the atomic/polled read path terminate. + */ + val = XIIC_SR_TX_FIFO_EMPTY_MASK; + if (rx_empty) { + val |= XIIC_SR_RX_FIFO_EMPTY_MASK; + } else { + val |= XIIC_SR_RX_FIFO_FULL_MASK; + } + if (c->in_xfer) { + val |= XIIC_SR_BUS_BUSY_MASK; + } + break; + case XIIC_IISR_OFFSET: + val = c->isr; + break; + case XIIC_IIER_OFFSET: + val = c->ier; + break; + case XIIC_DGIER_OFFSET: + val = c->dgier; + break; + case XIIC_CR_OFFSET: + val = c->cr; + break; + case XIIC_RFD_OFFSET: + val = c->rfd; + break; + case XIIC_RFO_OFFSET: + /* + * Occupancy register; the driver reads (RFO + 1) bytes from DRR, so + * report (available - 1). Zero when the FIFO is empty. + */ + val = rx_empty ? 0 : (c->rx_len - c->rx_pos - 1); + break; + case XIIC_DRR_OFFSET: + if (!rx_empty) { + val = c->rx_fifo[c->rx_pos++]; + if (c->rx_pos >= c->rx_len) { + /* + * FIFO drained. If a STOP was framed with this read, the bus + * is now released: raise BNB so the driver reaches DONE. + */ + c->isr &= ~(uint32_t)XIIC_INTR_RX_FULL_MASK; + if (c->stop_pending) { + c->stop_pending = false; + c->in_xfer = false; + c->isr |= XIIC_INTR_BNB_MASK; + } + xiic_update_irq(s, ch); + } + } + break; + default: + val = 0; + break; + } + + qemu_log_mask(LOG_TRACE, + "xiic-fpga-i2c: RD ch%d reg=0x%03" HWADDR_PRIx + " sz=%u -> 0x%08" PRIx64 "\n", ch, reg, size, val); + return val; +} + +/* + * Dynamic-mode DTR write: drive the I2C bus and post interrupts (Milestone 2). + * + * The low 8 bits are the data byte (or, for the second write of a dynamic read, + * the byte count). Bit 8 (START) frames the address that opens a transfer; bit + * 9 (STOP) terminates it. Transfers complete synchronously here. + */ +static void xiic_fpga_i2c_dtr_write(XiicFpgaI2cState *s, int ch, uint64_t val) +{ + XiicChannel *c = &s->chan[ch]; + uint16_t word = val & 0xFFFF; + bool stop = word & XIIC_TX_DYN_STOP_MASK; + + if (word & XIIC_TX_DYN_START_MASK) { + /* START + 8-bit address (bit 0 = R/W) opens a new transfer. */ + uint8_t addr8 = word & 0xFF; + c->is_recv = addr8 & 1; + c->rx_len = 0; + c->rx_pos = 0; + + int nack = c->is_recv ? i2c_start_recv(c->bus, addr8 >> 1) + : i2c_start_send(c->bus, addr8 >> 1); + c->in_xfer = true; + if (nack) { + /* No slave ACKed the address: flag error, release the bus. */ + i2c_end_transfer(c->bus); + c->in_xfer = false; + c->isr |= XIIC_INTR_TX_ERROR_MASK | XIIC_INTR_BNB_MASK; + xiic_update_irq(s, ch); + } + return; + } + + if (!c->in_xfer) { + return; /* stray data outside a transfer; HW would drop it too */ + } + + if (c->is_recv) { + /* + * Dynamic read: this word is the byte count. The controller clocks + * that many bytes from the slave into the RX FIFO autonomously. + */ + unsigned n = MIN((unsigned)(word & 0xFF), (unsigned)XIIC_RX_FIFO_MAX); + + for (unsigned i = 0; i < n; i++) { + c->rx_fifo[c->rx_len++] = i2c_recv(c->bus); + } + if (stop) { + i2c_end_transfer(c->bus); + c->stop_pending = true; /* BNB raised once the FIFO is drained */ + } + if (c->rx_len > 0) { + c->isr |= XIIC_INTR_RX_FULL_MASK; + } + xiic_update_irq(s, ch); + return; + } + + /* Dynamic write: low byte is data; STOP terminates the message. */ + if (i2c_send(c->bus, word & 0xFF)) { + i2c_end_transfer(c->bus); + c->in_xfer = false; + c->isr |= XIIC_INTR_TX_ERROR_MASK | XIIC_INTR_BNB_MASK; + xiic_update_irq(s, ch); + return; + } + if (stop) { + i2c_end_transfer(c->bus); + c->in_xfer = false; + c->isr |= XIIC_INTR_TX_EMPTY_MASK | XIIC_INTR_BNB_MASK; + } else { + /* + * FIFO drained instantly; invite the driver to push more bytes + * (writes longer than the hardware FIFO depth rely on this). + */ + c->isr |= XIIC_INTR_TX_EMPTY_MASK; + } + xiic_update_irq(s, ch); +} + +static void xiic_fpga_i2c_mmio_write(void *opaque, hwaddr addr, uint64_t val, + unsigned size) +{ + XiicFpgaI2cState *s = opaque; + hwaddr reg; + + /* + * Top-level interrupt registers. Status is computed live from the channels, + * so a write (regmap-irq ack) is a harmless no-op: the bit clears when the + * channel's IISR is cleared. The unmask register is stored so the driver's + * regmap-irq sync_unlock reads back what it wrote. + */ + if (addr == s->irq_status_offset) { + qemu_log_mask(LOG_TRACE, + "xiic-fpga-i2c: WR IRQ_STATUS(ack) <- 0x%08" PRIx64 "\n", + val); + /* + * regmap-irq acks here after running the channel handler. Drop the + * outstanding MSI edge and re-arm: if a channel is still pending (a new + * IISR cause raised during handling), this delivers another edge. + */ + s->msi_asserted = false; + xiic_update_irq(s, -1); + return; + } + if (addr == s->irq_unmask_offset) { + s->irq_unmask = val; + qemu_log_mask(LOG_TRACE, + "xiic-fpga-i2c: WR IRQ_UNMASK <- 0x%08" PRIx64 "\n", + val); + return; + } + + int ch = offset_to_channel(s, addr, ®); + + if (ch < 0) { + qemu_log_mask(LOG_UNIMP, + "xiic-fpga-i2c: WR off=0x%04" HWADDR_PRIx + " (outside channels) <- 0x%08" PRIx64 "\n", addr, val); + return; + } + + XiicChannel *c = &s->chan[ch]; + + qemu_log_mask(LOG_TRACE, + "xiic-fpga-i2c: WR ch%d reg=0x%03" HWADDR_PRIx + " sz=%u <- 0x%08" PRIx64 "\n", ch, reg, size, val); + + switch (reg) { + case XIIC_RESETR_OFFSET: + if ((val & 0xf) == XIIC_RESET_MASK) { + /* Soft reset: abandon any transfer and clear interrupt state. */ + if (c->in_xfer) { + i2c_end_transfer(c->bus); + } + c->isr = 0; + c->cr = 0; + c->in_xfer = false; + c->is_recv = false; + c->stop_pending = false; + c->rx_len = 0; + c->rx_pos = 0; + xiic_update_irq(s, ch); + } + break; + case XIIC_CR_OFFSET: + c->cr = val; + break; + case XIIC_DGIER_OFFSET: + c->dgier = val; + xiic_update_irq(s, ch); + break; + case XIIC_IIER_OFFSET: + c->ier = val; + xiic_update_irq(s, ch); + break; + case XIIC_IISR_OFFSET: + c->isr &= ~(uint32_t)val; /* write-1-clear */ + xiic_update_irq(s, ch); + break; + case XIIC_RFD_OFFSET: + c->rfd = val; + break; + case XIIC_DTR_OFFSET: + xiic_fpga_i2c_dtr_write(s, ch, val); + break; + default: + break; + } +} + +static const MemoryRegionOps xiic_fpga_i2c_mmio_ops = { + .read = xiic_fpga_i2c_mmio_read, + .write = xiic_fpga_i2c_mmio_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) { + error_setg(errp, "num_channels must be >= 1"); + return; + } + /* + * Grow BAR0 if the channel layout or the interrupt registers need more + * room (the IRQ status/unmask registers live at their own BAR offsets). + */ + 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 + 4) { + s->bar_size = s->irq_status_offset + 4; + } + if (s->bar_size < s->irq_unmask_offset + 4) { + s->bar_size = s->irq_unmask_offset + 4; + } + + memory_region_init_io(&s->mmio, OBJECT(s), &xiic_fpga_i2c_mmio_ops, s, + "xiic-fpga-i2c-bar0", s->bar_size); + pci_register_bar(pci_dev, 0, PCI_BASE_ADDRESS_SPACE_MEMORY, &s->mmio); + + s->chan = g_new0(XiicChannel, s->num_channels); + + /* + * One I2C bus per channel. The bus name lets slaves attach from the + * command line, e.g. -device at24c-eeprom,bus=xiic-fpga-i2c.0,address=0x50 + */ + for (unsigned i = 0; i < s->num_channels; i++) { + g_autofree char *name = g_strdup_printf("xiic-fpga-i2c.%u", i); + s->chan[i].bus = i2c_init_bus(DEVICE(s), name); + } + + /* + * MSI: one vector per channel (vector index == channel), rounded up to a + * power of two as the MSI capability requires. msi_init caps at 32. + */ + s->num_msi_vectors = pow2ceil(s->num_channels); + if (s->num_msi_vectors > 32) { + s->num_msi_vectors = 32; + } + 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; + } + + qemu_log_mask(LOG_TRACE, + "xiic-fpga-i2c: realized bar0=0x%x channels=%u base=0x%x " + "stride=0x%x msi-vectors=%u\n", s->bar_size, s->num_channels, + s->ch_base_offset, s->ch_stride, s->num_msi_vectors); +} + +static void xiic_fpga_i2c_exit(PCIDevice *pci_dev) +{ + XiicFpgaI2cState *s = XIIC_FPGA_I2C(pci_dev); + msi_uninit(pci_dev); + g_free(s->chan); +} + +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), + /* + * Top-level interrupt block. Defaults sit above a 4x0x1000 channel layout; + * match them to the platform use_msi JSON (interrupt_status_reg / + * interrupt_enable_reg) and the regmap-irq vector. + */ + 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); + + k->realize = xiic_fpga_i2c_realize; + k->exit = xiic_fpga_i2c_exit; + /* + * PCI ids the guest sees in lspci. The kernel multifpgapci driver learns + * its match id from the platform JSON at runtime, so just keep the JSON's + * vendor/device equal to these two numbers. (Xilinx vendor id by default.) + */ + k->vendor_id = 0x10ee; + k->device_id = 0x7021; + k->revision = 0x01; + k->class_id = PCI_CLASS_OTHERS; + + dc->desc = "Nexthop 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) -- 2.50.1
