On Wed, Dec 03, 2025 at 02:18:17PM -0700, Stephen Bates wrote: > This patch introduces a PCI MMIO Bridge device that enables PCI devices > to perform MMIO operations on other PCI devices via command packets. This > provides software-defined PCIe peer-to-peer (P2P) communication without > requiring specific hardware topology. > > Configuration: > qemu-system-x86_64 -machine q35 \ > -device pci-mmio-bridge,shadow-gpa=0x80000000,shadow-size=4096 > > - shadow-gpa: Guest physical address (default: 0x80000000, 0=auto) > - shadow-size: Buffer size in bytes (default: 4096, min: 4096) > - poll-interval-ns: Polling interval (default: 1000000 = 1ms) > - enabled: Enable/disable bridge (default: true) > > The bridge exposes shadow buffer information via a vendor-specific PCI config > space: > > Offset 0x40: GPA bits [31:0] > Offset 0x44: GPA bits [63:32] > Offset 0x48: Buffer size > Offset 0x4C: Queue depth > > Guest software reads these registers to locate the command queue and > can then submit MMIO operations by writing command packets to guest > RAM and updating the producer index. > > Limitations: > - Currently supports only bus 0 (main PCI bus) > - No access control or security checks between devices > - Polling-based completion (no interrupt notification) > - Cannot target VFIO device BARs directly (future work) > > Testing: > make check-qtest-x86_64 > > The qtest suite validates PCI device discovery, config space registers, > command queue operations, and proper handling of invalid commands. > > Signed-off-by: Stephen Bates <[email protected]>
Not at all excited to support another device with an ad hoc guest/host communication protocol. Why not build a virtio-pci-bridge? > --- > MAINTAINERS | 8 + > docs/system/device-emulation.rst | 1 + > docs/system/devices/pci-mmio-bridge.rst | 302 +++++++++++++ > hw/pci/meson.build | 1 + > hw/pci/pci-mmio-bridge.c | 568 ++++++++++++++++++++++++ > hw/pci/trace-events | 18 + > include/hw/pci/pci-mmio-bridge.h | 162 +++++++ > include/hw/pci/pci.h | 1 + > tests/qtest/meson.build | 1 + > tests/qtest/pci-mmio-bridge-test.c | 417 +++++++++++++++++ > 10 files changed, 1479 insertions(+) > create mode 100644 docs/system/devices/pci-mmio-bridge.rst > create mode 100644 hw/pci/pci-mmio-bridge.c > create mode 100644 include/hw/pci/pci-mmio-bridge.h > create mode 100644 tests/qtest/pci-mmio-bridge-test.c > > diff --git a/MAINTAINERS b/MAINTAINERS > index a07086ed76..6bbbeca57e 100644 > --- a/MAINTAINERS > +++ b/MAINTAINERS > @@ -2098,6 +2098,14 @@ F: docs/pci* > F: docs/specs/*pci* > F: docs/system/sriov.rst > > +PCI MMIO Bridge > +M: Michael S. Tsirkin <[email protected]> > +S: Supported > +F: hw/pci/pci-mmio-bridge.c > +F: include/hw/pci/pci-mmio-bridge.h > +F: tests/qtest/pci-mmio-bridge-test.c > +F: docs/system/devices/pci-mmio-bridge.rst > + > PCIE DOE > M: Huai-Cheng Kuo <[email protected]> > M: Chris Browy <[email protected]> > diff --git a/docs/system/device-emulation.rst > b/docs/system/device-emulation.rst > index 911381643f..472b388162 100644 > --- a/docs/system/device-emulation.rst > +++ b/docs/system/device-emulation.rst > @@ -91,6 +91,7 @@ Emulated Devices > devices/keyboard.rst > devices/net.rst > devices/nvme.rst > + devices/pci-mmio-bridge.rst > devices/usb.rst > devices/vhost-user.rst > devices/virtio-gpu.rst > diff --git a/docs/system/devices/pci-mmio-bridge.rst > b/docs/system/devices/pci-mmio-bridge.rst > new file mode 100644 > index 0000000000..6cd069af0b > --- /dev/null > +++ b/docs/system/devices/pci-mmio-bridge.rst > @@ -0,0 +1,302 @@ > +.. SPDX-License-Identifier: GPL-2.0-or-later > + > +=============== > +PCI MMIO Bridge > +=============== > + > +The PCI MMIO Bridge is an emulated PCI device that provides a mechanism for > +PCI devices to perform MMIO (Memory-Mapped I/O) operations on other PCI > +devices via command packets. This enables software-defined PCIe peer-to-peer > +(P2P) communication between any combination of emulated and real PCI devices. > + > +Overview > +======== > + > +The PCI MMIO Bridge uses a hybrid architecture: > + > +1. **PCI Device**: Provides guest OS discovery via standard enumeration. > +2. **Shadow Buffer**: Allocated in guest RAM. > +3. **PCI Config Space**: Exposes shadow buffer GPA in vendor-specific > + registers. > + > +VFIO can only map guest RAM not emulated PCI MMIO space. And, at the present > +time, VFIO cannot map MMIO space into an IOVA mapping. Therefore the PCI MMIO > +Bridge uses a small amount of guest RAM mapped into its Guest Physical > Address > +(GPA) space. > + > +Command Queue Structure > +======================= > + > +The PCI MMIO Bridge uses a ring buffer in guest RAM. The first 24 bytes > +of the ring buffer contain metadata. The rest of the ring buffer contains > +command slots: > + > +.. code-block:: c > + > + struct pci_mmio_bridge_ring_meta { > + uint32_t producer_idx; /* Written by initiator */ > + uint32_t consumer_idx; /* Written by QEMU */ > + uint32_t queue_depth; /* Number of available slots */ > + uint32_t reserved[3]; > + }; > + > + struct pci_mmio_bridge_command { > + uint16_t target_bdf; /* Bus:Device:Function */ > + uint8_t target_bar; /* Which BAR on target */ > + uint8_t reserved1; > + uint32_t offset; /* Offset within BAR */ > + uint32_t value; /* Write value / read result */ > + uint8_t command; /* 0=NOP, 1=WRITE, 2=READ */ > + uint8_t size; /* 1, 2, 4, or 8 bytes */ > + uint8_t status; /* 0=pending, 1=complete, 2=error */ > + uint8_t reserved2; > + uint32_t sequence; /* For ordering */ > + uint32_t reserved3; > + }; > + > +With the default 4KiB shadow buffer size, the queue provides 169 command > slots. > + > +Processing Model > +================ > + > +QEMU polls the command queue periodically (default 1ms interval). When > +the producer index differs from the consumer index: > + > +1. QEMU reads pending command(s) from the queue > +2. For each command: > + > + * Locates the target PCI device (emulated or real) by BDF > + * Dispatches MMIO read/write to the target device's BAR > + * Updates the command status field (complete or error) > + * Updates the result value (for reads) > + > +3. Updates the consumer index > +4. Reschedules the next poll > + > +Device Configuration > +==================== > + > +The PCI MMIO Bridge appears as a standard PCI device: > + > +- **Vendor ID**: 0x1b36 (Red Hat/QEMU) > +- **Device ID**: 0x0015 (PCI MMIO Bridge) > +- **Class**: 0x08/0x80 (System/Other) > + > +Basic Usage > +----------- > + > +.. code-block:: console > + > + qemu-system-x86_64 \ > + -machine q35 \ > + -device pci-mmio-bridge,id=mmio-bridge > + > +This creates a bridge with defaults: > + > +- Shadow GPA: 0x80000000 > +- Shadow size: 4096 bytes > +- Polling interval: 1ms > + > +Custom Configuration > +--------------------- > + > +.. code-block:: console > + > + qemu-system-x86_64 \ > + -machine q35 \ > + -device pci-mmio-bridge,id=mmio-bridge,\ > + shadow-gpa=0x90000000,\ > + shadow-size=8192,\ > + poll-interval-ns=500000 > + > +Properties: > + > +- ``shadow-gpa``: Guest physical address for buffer (0 = auto, default: > + 0x80000000) > +- ``shadow-size``: Buffer size in bytes (default: 4096, min: 4096) > +- ``poll-interval-ns``: Polling interval in nanoseconds (default: > + 1000000) > +- ``enabled``: Enable/disable bridge (default: true) > +- ``addr``: PCI slot address (e.g., ``addr=5.0`` for slot 5) > + > +Guest Software Interface > +======================== > + > +Guest drivers can read the vendor-specific config space to find the GPA of > +the shadow buffer: > + > +.. code-block:: c > + > + #define PCI_MMIO_BRIDGE_CAP_OFFSET 0x40 > + #define PCI_MMIO_BRIDGE_CAP_GPA_LO 0x00 > + #define PCI_MMIO_BRIDGE_CAP_GPA_HI 0x04 > + #define PCI_MMIO_BRIDGE_CAP_SIZE 0x08 > + #define PCI_MMIO_BRIDGE_CAP_DEPTH 0x0C > + > + /* Read shadow buffer GPA */ > + uint32_t gpa_lo = pci_read_config_dword(pdev, > + PCI_MMIO_BRIDGE_CAP_OFFSET + 0); > + uint32_t gpa_hi = pci_read_config_dword(pdev, > + PCI_MMIO_BRIDGE_CAP_OFFSET + 4); > + uint64_t shadow_gpa = ((uint64_t)gpa_hi << 32) | gpa_lo; > + > + /* Read buffer size and queue depth */ > + uint32_t shadow_size = pci_read_config_dword(pdev, > + PCI_MMIO_BRIDGE_CAP_OFFSET + > 8); > + uint32_t queue_depth = pci_read_config_dword(pdev, > + PCI_MMIO_BRIDGE_CAP_OFFSET + > 12); > + > +Linux Kernel Driver Example > +--------------------------- > + > +Example PCI driver that discovers and uses the bridge: > + > +.. code-block:: c > + > + #include <linux/pci.h> > + #include <linux/module.h> > + #include <linux/io.h> > + > + #define PCI_VENDOR_ID_REDHAT_QEMU 0x1b36 > + #define PCI_DEVICE_ID_REDHAT_MMIO_BRIDGE 0x0015 > + > + /* Config space offsets */ > + #define CAP_OFFSET 0x40 > + #define CAP_GPA_LO 0x00 > + #define CAP_GPA_HI 0x04 > + #define CAP_SIZE 0x08 > + #define CAP_DEPTH 0x0C > + > + struct mmio_bridge_dev { > + struct pci_dev *pdev; > + void __iomem *shadow_buf; > + uint64_t shadow_gpa; > + uint32_t shadow_size; > + uint32_t queue_depth; > + }; > + > + static int mmio_bridge_probe(struct pci_dev *pdev, > + const struct pci_device_id *id) > + { > + struct mmio_bridge_dev *dev; > + uint32_t gpa_lo, gpa_hi; > + int err; > + > + dev = devm_kzalloc(&pdev->dev, sizeof(*dev), GFP_KERNEL); > + if (!dev) > + return -ENOMEM; > + > + dev->pdev = pdev; > + > + err = pci_enable_device(pdev); > + if (err) > + return err; > + > + /* Read shadow buffer location from config space */ > + pci_read_config_dword(pdev, CAP_OFFSET + CAP_GPA_LO, &gpa_lo); > + pci_read_config_dword(pdev, CAP_OFFSET + CAP_GPA_HI, &gpa_hi); > + pci_read_config_dword(pdev, CAP_OFFSET + CAP_SIZE, &dev->shadow_size); > + pci_read_config_dword(pdev, CAP_OFFSET + CAP_DEPTH, > &dev->queue_depth); > + > + dev->shadow_gpa = ((uint64_t)gpa_hi << 32) | gpa_lo; > + > + pr_info("PCI MMIO Bridge: GPA=0x%llx size=%u depth=%u\n", > + dev->shadow_gpa, dev->shadow_size, dev->queue_depth); > + > + /* Map shadow buffer (guest RAM, not MMIO) */ > + dev->shadow_buf = ioremap(dev->shadow_gpa, dev->shadow_size); > + if (!dev->shadow_buf) { > + err = -ENOMEM; > + goto err_disable; > + } > + > + pci_set_drvdata(pdev, dev); > + > + return 0; > + > + err_disable: > + pci_disable_device(pdev); > + return err; > + } > + > + static void mmio_bridge_remove(struct pci_dev *pdev) > + { > + struct mmio_bridge_dev *dev = pci_get_drvdata(pdev); > + > + if (dev->shadow_buf) > + iounmap(dev->shadow_buf); > + > + pci_disable_device(pdev); > + } > + > + static const struct pci_device_id mmio_bridge_ids[] = { > + { PCI_DEVICE(PCI_VENDOR_ID_REDHAT_QEMU, > + PCI_DEVICE_ID_MMIO_BRIDGE) }, > + { 0, } > + }; > + MODULE_DEVICE_TABLE(pci, mmio_bridge_ids); > + > + static struct pci_driver mmio_bridge_driver = { > + .name = "pci-mmio-bridge", > + .id_table = mmio_bridge_ids, > + .probe = mmio_bridge_probe, > + .remove = mmio_bridge_remove, > + }; > + > + module_pci_driver(mmio_bridge_driver); > + MODULE_LICENSE("GPL"); > + > +Submitting Commands > +------------------- > + > +Example code for writing to a PCI device's BAR: > + > +.. code-block:: c > + > + #include <linux/io.h> > + > + volatile struct pci_mmio_bridge_ring_meta *meta; > + volatile struct pci_mmio_bridge_command *cmds; > + > + void init_bridge(void __iomem *shadow_buf) > + { > + meta = (struct pci_mmio_bridge_ring_meta *)shadow_buf; > + cmds = (struct pci_mmio_bridge_command *)(shadow_buf + > + sizeof(*meta)); > + > + /* Verify queue is initialized */ > + if (meta->queue_depth == 0) { > + pr_err("PCI MMIO bridge not available\n"); > + return; > + } > + pr_info("Bridge ready: %u slots\n", > + meta->queue_depth); > + } > + > + int pci_mmio_write_bar(u16 bdf, u8 bar, u32 offset, > + u32 value, u8 size) > + { > + u32 slot = meta->producer_idx % meta->queue_depth; > + struct pci_mmio_bridge_command cmd = { > + .target_bdf = bdf, > + .target_bar = bar, > + .offset = offset, > + .value = value, > + .command = 1, /* WRITE */ > + .size = size, > + .status = 0, /* PENDING */ > + }; > + > + /* Write command to queue */ > + cmds[slot] = cmd; > + wmb(); > + > + /* Update producer index */ > + meta->producer_idx++; > + > + /* Poll for completion (or use interrupt) */ > + while (cmds[slot].status == 0) > + cpu_relax(); > + > + return (cmds[slot].status == 1) ? 0 : -EIO; > + } > diff --git a/hw/pci/meson.build b/hw/pci/meson.build > index b9c34b2acf..670645921f 100644 > --- a/hw/pci/meson.build > +++ b/hw/pci/meson.build > @@ -6,6 +6,7 @@ pci_ss.add(files( > 'pci_bridge.c', > 'pci_host.c', > 'pci-hmp-cmds.c', > + 'pci-mmio-bridge.c', > 'pci-qmp-cmds.c', > 'pcie_sriov.c', > 'shpc.c', > diff --git a/hw/pci/pci-mmio-bridge.c b/hw/pci/pci-mmio-bridge.c > new file mode 100644 > index 0000000000..a6cdf81885 > --- /dev/null > +++ b/hw/pci/pci-mmio-bridge.c > @@ -0,0 +1,568 @@ > +/* SPDX-License-Identifier: GPL-2.0-or-later */ > +/* > + * QEMU PCI MMIO Bridge Implementation > + * > + * Provides PCI device-to-device MMIO capability via a command queue. > + * > + * Copyright (c) 2025 Stephen Bates <[email protected]> > + */ > + > +#include "qemu/osdep.h" > +#include "qemu/memalign.h" > +#include "qemu/main-loop.h" > +#include "hw/pci/pci-mmio-bridge.h" > +#include "hw/pci/pci.h" > +#include "hw/pci/pci_device.h" > +#include "hw/pci/pci_bus.h" > +#include "system/address-spaces.h" > +#include "qemu/timer.h" > +#include "qemu/log.h" > +#include "qapi/error.h" > +#include "trace.h" > +#include "hw/qdev-properties.h" > +#include "hw/resettable.h" > +#include "qemu/module.h" > +#include "exec/target_page.h" > + > +/* Default polling interval: 1ms */ > +#define DEFAULT_POLL_INTERVAL_NS (1000 * 1000) > + > +/* Helper: Extract bus number from BDF */ > +static inline uint8_t bdf_to_bus(uint16_t bdf) > +{ > + return (bdf >> 8) & 0xFF; > +} > + > +/* Helper: Extract devfn from BDF */ > +static inline uint8_t bdf_to_devfn(uint16_t bdf) > +{ > + return bdf & 0xFF; > +} > + > +/* > + * Find PCI device by BDF > + * > + * Searches the configured PCI bus for a device matching the given BDF. > + * Returns NULL if not found. > + */ > +static PCIDevice *pci_mmio_bridge_find_device(PCIMMIOBridge *bridge, > + uint16_t bdf) > +{ > + uint8_t bus_num = bdf_to_bus(bdf); > + uint8_t devfn = bdf_to_devfn(bdf); > + PCIDevice *dev; > + > + if (!bridge->pci_bus) { > + return NULL; > + } > + > + /* For now, only support bus 0 (main PCI bus) */ > + if (bus_num != 0) { > + qemu_log_mask(LOG_GUEST_ERROR, > + "pci-mmio-bridge: Multi-bus not yet supported (BDF > %04x)\n", > + bdf); > + return NULL; > + } > + > + dev = bridge->pci_bus->devices[devfn]; > + if (!dev) { > + trace_pci_mmio_bridge_device_not_found(bdf); > + } > + > + return dev; > +} > + > +/* > + * Execute a single MMIO command > + * > + * Dispatches the command to the appropriate target device's MMIO handler. > + */ > +static void pci_mmio_bridge_execute_command( > + PCIMMIOBridge *bridge, struct pci_mmio_bridge_command *cmd) > +{ > + PCIDevice *target; > + uint64_t value = 0; > + MemTxResult result; > + > + /* Validate command type */ > + switch (cmd->command) { > + case PCI_MMIO_BRIDGE_CMD_WRITE: > + case PCI_MMIO_BRIDGE_CMD_READ: > + break; > + default: > + qatomic_set(&cmd->status, PCI_MMIO_BRIDGE_STATUS_ERROR); > + smp_wmb(); /* Ensure status visible before return */ > + trace_pci_mmio_bridge_invalid_command(cmd->command); > + return; > + } > + > + /* Find target device */ > + target = pci_mmio_bridge_find_device(bridge, cmd->target_bdf); > + if (!target) { > + qatomic_set(&cmd->status, PCI_MMIO_BRIDGE_STATUS_ERROR); > + smp_wmb(); /* Ensure status is visible to guest */ > + trace_pci_mmio_bridge_device_not_found(cmd->target_bdf); > + return; > + } > + > + /* Validate BAR number */ > + if (cmd->target_bar >= PCI_NUM_REGIONS) { > + qatomic_set(&cmd->status, PCI_MMIO_BRIDGE_STATUS_ERROR); > + smp_wmb(); /* Ensure status visible before return */ > + trace_pci_mmio_bridge_invalid_bar(cmd->target_bdf, cmd->target_bar); > + return; > + } > + > + /* Get target BAR address */ > + hwaddr bar_addr = pci_get_bar_addr(target, cmd->target_bar); > + if (bar_addr == PCI_BAR_UNMAPPED) { > + qatomic_set(&cmd->status, PCI_MMIO_BRIDGE_STATUS_ERROR); > + smp_wmb(); /* Ensure status visible to guest */ > + trace_pci_mmio_bridge_bar_not_mapped(cmd->target_bdf, > cmd->target_bar); > + return; > + } > + > + /* Validate size */ > + if (cmd->size != 1 && cmd->size != 2 && cmd->size != 4 && cmd->size != > 8) { > + qatomic_set(&cmd->status, PCI_MMIO_BRIDGE_STATUS_ERROR); > + smp_wmb(); /* Ensure status visible to guest */ > + trace_pci_mmio_bridge_invalid_size(cmd->size); > + return; > + } > + > + /* Calculate full MMIO address (BAR base + offset) */ > + hwaddr mmio_addr = bar_addr + cmd->offset; > + > + /* Execute the operation using address_space to access MMIO space */ > + switch (cmd->command) { > + case PCI_MMIO_BRIDGE_CMD_WRITE: > + result = address_space_write(&address_space_memory, mmio_addr, > + MEMTXATTRS_UNSPECIFIED, > + &cmd->value, cmd->size); > + if (result == MEMTX_OK) { > + qatomic_set(&cmd->status, PCI_MMIO_BRIDGE_STATUS_COMPLETE); > + smp_wmb(); /* Ensure status visible to guest */ > + bridge->total_writes++; > + trace_pci_mmio_bridge_write(cmd->target_bdf, cmd->target_bar, > + cmd->offset, cmd->value, cmd->size); > + } else { > + qatomic_set(&cmd->status, PCI_MMIO_BRIDGE_STATUS_ERROR); > + smp_wmb(); /* Ensure status visible to guest */ > + bridge->total_errors++; > + trace_pci_mmio_bridge_write_failed(cmd->target_bdf, > cmd->target_bar, > + cmd->offset, result); > + } > + break; > + > + case PCI_MMIO_BRIDGE_CMD_READ: > + result = address_space_read(&address_space_memory, mmio_addr, > + MEMTXATTRS_UNSPECIFIED, > + &value, cmd->size); > + if (result == MEMTX_OK) { > + cmd->value = value; > + smp_wmb(); /* Ensure value is visible before status update */ > + qatomic_set(&cmd->status, PCI_MMIO_BRIDGE_STATUS_COMPLETE); > + smp_wmb(); /* Ensure status visible to guest */ > + bridge->total_reads++; > + trace_pci_mmio_bridge_read(cmd->target_bdf, cmd->target_bar, > + cmd->offset, value, cmd->size); > + } else { > + qatomic_set(&cmd->status, PCI_MMIO_BRIDGE_STATUS_ERROR); > + smp_wmb(); /* Ensure status visible to guest */ > + bridge->total_errors++; > + trace_pci_mmio_bridge_read_failed(cmd->target_bdf, > cmd->target_bar, > + cmd->offset, result); > + } > + break; > + } > + > + /* Ensure status is visible */ > + smp_wmb(); > +} > + > +/* > + * Polling timer callback > + * > + * Checks shadow buffer for new commands and processes them. > + * This is exported for use by the PCI device wrapper. > + */ > +void pci_mmio_bridge_poll(void *opaque) > +{ > + PCIMMIOBridge *bridge = opaque; > + uint32_t producer_idx, consumer_idx; > + uint32_t commands_processed = 0; > + > + if (!bridge->enabled) { > + goto reschedule; > + } > + > + bridge->total_polls++; > + > + /* > + * Read metadata from guest memory using address_space_read. This ensures > + * we see any VFIO device writes and not cached copies. > + */ > + struct pci_mmio_bridge_ring_meta metadata; > + address_space_read(&address_space_memory, bridge->shadow_gpa, > + MEMTXATTRS_UNSPECIFIED, &metadata, sizeof(metadata)); > + > + producer_idx = metadata.producer_idx; > + consumer_idx = bridge->head; > + > + /* Process all pending commands */ > + while (consumer_idx != producer_idx) { > + uint32_t slot = consumer_idx % bridge->queue_depth; > + hwaddr cmd_addr = bridge->shadow_gpa + > + sizeof(struct pci_mmio_bridge_ring_meta) + > + (slot * sizeof(struct pci_mmio_bridge_command)); > + > + /* Read command from guest memory to see GPU writes */ > + struct pci_mmio_bridge_command cmd; > + address_space_read(&address_space_memory, cmd_addr, > + MEMTXATTRS_UNSPECIFIED, &cmd, sizeof(cmd)); > + > + if (cmd.status == PCI_MMIO_BRIDGE_STATUS_PENDING) { > + pci_mmio_bridge_execute_command(bridge, &cmd); > + > + /* Write back status using address_space_write */ > + address_space_write(&address_space_memory, cmd_addr, > + MEMTXATTRS_UNSPECIFIED, &cmd, sizeof(cmd)); > + > + bridge->total_commands++; > + commands_processed++; > + } > + > + consumer_idx++; > + } > + > + /* Update our head position */ > + bridge->head = consumer_idx; > + > + if (commands_processed > 0) { > + trace_pci_mmio_bridge_poll_processed(commands_processed); > + } > + > +reschedule: > + /* Reschedule for next poll cycle */ > + if (bridge->poll_timer) { > + timer_mod(bridge->poll_timer, > + qemu_clock_get_ns(QEMU_CLOCK_REALTIME) + > + bridge->poll_interval_ns); > + } > +} > + > +/* > + * Initialize PCI MMIO Bridge > + */ > +PCIMMIOBridge *pci_mmio_bridge_init(PCIBus *pci_bus, > + hwaddr gpa, uint32_t size, > + uint64_t poll_interval_ns, > + Error **errp) > +{ > + PCIMMIOBridge *bridge; > + struct pci_mmio_bridge_ring_meta *meta; > + > + /* Validate parameters */ > + if (!pci_bus) { > + error_setg(errp, "PCI bus must be provided"); > + return NULL; > + } > + > + if (size < TARGET_PAGE_SIZE) { > + error_setg(errp, "Shadow buffer size must be at least 4096 bytes"); > + return NULL; > + } > + > + if (!QEMU_IS_ALIGNED(gpa, TARGET_PAGE_SIZE)) { > + error_setg(errp, "Shadow buffer GPA must be page-aligned"); > + return NULL; > + } > + > + /* Allocate bridge state */ > + bridge = g_new0(PCIMMIOBridge, 1); > + > + /* Store PCI bus reference */ > + bridge->pci_bus = pci_bus; > + > + bridge->shadow_gpa = gpa; > + bridge->shadow_size = size; > + > + /* Allocate guest RAM for shadow buffer with unique name */ > + char *mr_name = g_strdup_printf("pci-mmio-bridge-shadow@0x%"PRIx64, gpa); > + memory_region_init_ram(&bridge->shadow_mr, NULL, mr_name, size, > + &error_fatal); > + g_free(mr_name); > + > + /* Add to system memory at specified GPA */ > + memory_region_add_subregion(get_system_memory(), gpa, > &bridge->shadow_mr); > + > + /* Get host virtual address for direct access */ > + bridge->shadow_hva = memory_region_get_ram_ptr(&bridge->shadow_mr); > + memset(bridge->shadow_hva, 0, size); > + > + /* Calculate queue depth (reserve first slot for metadata) */ > + bridge->queue_depth = (size / sizeof(struct pci_mmio_bridge_command)) - > 1; > + > + /* Initialize ring buffer metadata in first slot */ > + meta = (struct pci_mmio_bridge_ring_meta *)bridge->shadow_hva; > + meta->producer_idx = 0; > + meta->consumer_idx = 0; > + meta->queue_depth = bridge->queue_depth; > + meta->reserved = 0; > + > + /* Initialize polling infrastructure */ > + bridge->poll_interval_ns = poll_interval_ns ? poll_interval_ns : > + DEFAULT_POLL_INTERVAL_NS; > + > + /* Use bottom-half for immediate processing (for qtest) */ > + bridge->poll_bh = qemu_bh_new(pci_mmio_bridge_poll, bridge); > + > + /* Also create timer for periodic polling when BH isn't triggered */ > + bridge->poll_timer = timer_new_ns(QEMU_CLOCK_REALTIME, > + pci_mmio_bridge_poll, bridge); > + bridge->enabled = true; > + > + /* Start periodic polling */ > + timer_mod(bridge->poll_timer, > + qemu_clock_get_ns(QEMU_CLOCK_REALTIME) + > + bridge->poll_interval_ns); > + > + trace_pci_mmio_bridge_init(gpa, size, bridge->queue_depth, > + bridge->poll_interval_ns); > + > + return bridge; > +} > + > +/* > + * Cleanup PCI MMIO Bridge > + */ > +void pci_mmio_bridge_cleanup(PCIMMIOBridge *bridge) > +{ > + if (!bridge) { > + return; > + } > + > + trace_pci_mmio_bridge_cleanup(bridge->total_commands, > bridge->total_writes, > + bridge->total_reads, bridge->total_errors); > + > + /* Stop polling */ > + if (bridge->poll_timer) { > + timer_free(bridge->poll_timer); > + } > + if (bridge->poll_bh) { > + qemu_bh_delete(bridge->poll_bh); > + } > + > + /* Remove from system memory and cleanup */ > + memory_region_del_subregion(get_system_memory(), &bridge->shadow_mr); > + object_unparent(OBJECT(&bridge->shadow_mr)); > + > + /* Free backing memory */ > + qemu_vfree(bridge->shadow_hva); > + > + g_free(bridge); > +} > + > +/* > + * Enable/disable bridge > + */ > +void pci_mmio_bridge_set_enabled(PCIMMIOBridge *bridge, bool enabled) > +{ > + if (bridge) { > + bridge->enabled = enabled; > + trace_pci_mmio_bridge_set_enabled(enabled); > + } > +} > + > +/* > + * Get statistics > + */ > +void pci_mmio_bridge_get_stats(PCIMMIOBridge *bridge, > + uint64_t *total_commands, > + uint64_t *total_writes, > + uint64_t *total_reads, > + uint64_t *total_errors) > +{ > + if (bridge) { > + if (total_commands) { > + *total_commands = bridge->total_commands; > + } > + if (total_writes) { > + *total_writes = bridge->total_writes; > + } > + if (total_reads) { > + *total_reads = bridge->total_reads; > + } > + if (total_errors) { > + *total_errors = bridge->total_errors; > + } > + } > +} > + > +/* > + * Manually trigger one poll cycle > + * > + * This is primarily for testing - it processes pending commands immediately > + * without waiting for the timer. > + */ > +void pci_mmio_bridge_poll_once(PCIMMIOBridge *bridge) > +{ > + if (bridge) { > + pci_mmio_bridge_poll(bridge); > + } > +} > + > +/* > + * PCI Device Wrapper > + */ > + > +static void pci_mmio_bridge_pci_realize(PCIDevice *pci_dev, Error **errp) > +{ > + PCIMMIOBridge_NEW *s = PCI_MMIO_BRIDGE(pci_dev); > + uint8_t *pci_conf = pci_dev->config; > + Error *local_err = NULL; > + > + /* Set PCI config space */ > + pci_config_set_vendor_id(pci_conf, PCI_VENDOR_ID_REDHAT); > + pci_config_set_device_id(pci_conf, PCI_DEVICE_ID_REDHAT_MMIO_BRIDGE); > + pci_config_set_class(pci_conf, PCI_CLASS_SYSTEM_OTHER); > + pci_config_set_revision(pci_conf, 0x01); > + > + /* Subsystem vendor/device ID */ > + pci_set_word(pci_conf + PCI_SUBSYSTEM_VENDOR_ID, > + PCI_VENDOR_ID_REDHAT); > + pci_set_word(pci_conf + PCI_SUBSYSTEM_ID, 0x1100); > + > + /* Validate shadow_size */ > + if (s->shadow_size < TARGET_PAGE_SIZE) { > + error_setg(errp, "shadow-size must be at least %d bytes", > + TARGET_PAGE_SIZE); > + return; > + } > + > + /* Default GPA if not specified (below 4GB for 32-bit compatibility) */ > + if (s->shadow_gpa == 0) { > + s->shadow_gpa = 0x80000000ULL; /* Default: 2GB mark */ > + } > + > + /* Use existing bridge initialization (creates guest RAM) */ > + s->bridge = pci_mmio_bridge_init(pci_get_bus(pci_dev), > + s->shadow_gpa, > + s->shadow_size, > + s->poll_interval_ns, > + &local_err); > + if (local_err) { > + error_propagate(errp, local_err); > + return; > + } > + > + s->bridge->enabled = s->enabled; > + > + /* Expose shadow buffer GPA and size via vendor-specific config space */ > + pci_set_long(pci_conf + PCI_MMIO_BRIDGE_CAP_OFFSET + > + PCI_MMIO_BRIDGE_CAP_GPA_LO, > + (uint32_t)(s->shadow_gpa & 0xFFFFFFFF)); > + pci_set_long(pci_conf + PCI_MMIO_BRIDGE_CAP_OFFSET + > + PCI_MMIO_BRIDGE_CAP_GPA_HI, > + (uint32_t)(s->shadow_gpa >> 32)); > + pci_set_long(pci_conf + PCI_MMIO_BRIDGE_CAP_OFFSET + > + PCI_MMIO_BRIDGE_CAP_SIZE, > + s->shadow_size); > + pci_set_long(pci_conf + PCI_MMIO_BRIDGE_CAP_OFFSET + > + PCI_MMIO_BRIDGE_CAP_DEPTH, > + s->bridge->queue_depth); > + > + trace_pci_mmio_bridge_realize(s->shadow_gpa, s->shadow_size, > + s->bridge->queue_depth); > +} > + > +static void pci_mmio_bridge_pci_exit(PCIDevice *pci_dev) > +{ > + PCIMMIOBridge_NEW *s = PCI_MMIO_BRIDGE(pci_dev); > + > + if (!s->bridge) { > + return; > + } > + > + trace_pci_mmio_bridge_exit(s->bridge->total_commands, > + s->bridge->total_writes, > + s->bridge->total_reads, > + s->bridge->total_errors); > + > + /* Use bridge cleanup (handles guest RAM removal) */ > + pci_mmio_bridge_cleanup(s->bridge); > + s->bridge = NULL; > +} > + > +static void pci_mmio_bridge_pci_reset(Object *obj, ResetType type) > +{ > + PCIMMIOBridge_NEW *s = PCI_MMIO_BRIDGE(obj); > + struct pci_mmio_bridge_ring_meta *meta; > + > + if (!s->bridge || !s->bridge->shadow_hva) { > + return; > + } > + > + /* Reset ring buffer state in guest RAM */ > + meta = (struct pci_mmio_bridge_ring_meta *)s->bridge->shadow_hva; > + meta->producer_idx = 0; > + meta->consumer_idx = 0; > + s->bridge->head = 0; > + > + /* Reset statistics */ > + s->bridge->total_commands = 0; > + s->bridge->total_writes = 0; > + s->bridge->total_reads = 0; > + s->bridge->total_errors = 0; > + s->bridge->total_polls = 0; > + > + trace_pci_mmio_bridge_reset(); > +} > + > +static const Property pci_mmio_bridge_pci_properties[] = { > + DEFINE_PROP_UINT64("shadow-gpa", PCIMMIOBridge_NEW, shadow_gpa, 0), > + DEFINE_PROP_UINT32("shadow-size", PCIMMIOBridge_NEW, shadow_size, > + 4096), > + DEFINE_PROP_UINT64("poll-interval-ns", PCIMMIOBridge_NEW, > + poll_interval_ns, 1000000), > + DEFINE_PROP_BOOL("enabled", PCIMMIOBridge_NEW, enabled, true) > +}; > + > +static void pci_mmio_bridge_pci_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 = pci_mmio_bridge_pci_realize; > + k->exit = pci_mmio_bridge_pci_exit; > + k->vendor_id = PCI_VENDOR_ID_REDHAT; > + k->device_id = PCI_DEVICE_ID_REDHAT_MMIO_BRIDGE; > + k->class_id = PCI_CLASS_SYSTEM_OTHER; > + k->revision = 0x01; > + > + dc->desc = "PCI MMIO Bridge (device-to-device MMIO proxy)"; > + rc->phases.hold = pci_mmio_bridge_pci_reset; > + device_class_set_props(dc, pci_mmio_bridge_pci_properties); > + set_bit(DEVICE_CATEGORY_MISC, dc->categories); > +} > + > +static const TypeInfo pci_mmio_bridge_pci_info = { > + .name = TYPE_PCI_MMIO_BRIDGE, > + .parent = TYPE_PCI_DEVICE, > + .instance_size = sizeof(PCIMMIOBridge_NEW), > + .class_init = pci_mmio_bridge_pci_class_init, > + .interfaces = (const InterfaceInfo[]) { > + { INTERFACE_CONVENTIONAL_PCI_DEVICE }, > + { } > + } > +}; > + > +static void pci_mmio_bridge_pci_register_types(void) > +{ > + type_register_static(&pci_mmio_bridge_pci_info); > +} > + > +type_init(pci_mmio_bridge_pci_register_types) > + > diff --git a/hw/pci/trace-events b/hw/pci/trace-events > index 02c80d3ec8..0919ec0a68 100644 > --- a/hw/pci/trace-events > +++ b/hw/pci/trace-events > @@ -28,3 +28,21 @@ pcie_cap_slot_write_config(const char *parent, const char > *child, const char *pd > > # shpc.c > shpc_slot_command(const char *parent, int pci_slot, const char *child, const > char *old_pic, const char *new_pic, const char *old_aic, const char *new_aic, > const char *old_state, const char *new_state) "%s[%d] > %s: pic: %s->%s, aic: > %s->%s, state: %s->%s" > + > +# pci-mmio-bridge.c > +pci_mmio_bridge_init(uint64_t gpa, uint32_t size, uint32_t queue_depth, > uint64_t poll_interval_ns) "gpa=0x%"PRIx64" size=%u queue_depth=%u > poll_interval=%"PRIu64"ns" > +pci_mmio_bridge_cleanup(uint64_t total_commands, uint64_t total_writes, > uint64_t total_reads, uint64_t total_errors) "total_commands=%"PRIu64" > writes=%"PRIu64" reads=%"PRIu64" errors=%"PRIu64 > +pci_mmio_bridge_set_enabled(bool enabled) "enabled=%d" > +pci_mmio_bridge_write(uint16_t bdf, uint8_t bar, uint32_t offset, uint64_t > value, uint8_t size) "BDF=0x%04x BAR=%u offset=0x%x value=0x%"PRIx64" size=%u" > +pci_mmio_bridge_read(uint16_t bdf, uint8_t bar, uint32_t offset, uint64_t > value, uint8_t size) "BDF=0x%04x BAR=%u offset=0x%x value=0x%"PRIx64" size=%u" > +pci_mmio_bridge_write_failed(uint16_t bdf, uint8_t bar, uint32_t offset, int > result) "BDF=0x%04x BAR=%u offset=0x%x result=%d" > +pci_mmio_bridge_read_failed(uint16_t bdf, uint8_t bar, uint32_t offset, int > result) "BDF=0x%04x BAR=%u offset=0x%x result=%d" > +pci_mmio_bridge_device_not_found(uint16_t bdf) "BDF=0x%04x not found" > +pci_mmio_bridge_invalid_bar(uint16_t bdf, uint8_t bar) "BDF=0x%04x invalid > BAR=%u" > +pci_mmio_bridge_bar_not_mapped(uint16_t bdf, uint8_t bar) "BDF=0x%04x BAR=%u > not mapped" > +pci_mmio_bridge_invalid_command(uint8_t command) "invalid command=%u" > +pci_mmio_bridge_invalid_size(uint8_t size) "invalid size=%u" > +pci_mmio_bridge_poll_processed(uint32_t count) "processed %u commands" > +pci_mmio_bridge_realize(uint64_t gpa, uint32_t size, uint32_t queue_depth) > "shadow_gpa=0x%"PRIx64" size=%u queue_depth=%u" > +pci_mmio_bridge_exit(uint64_t commands, uint64_t writes, uint64_t reads, > uint64_t errors) "commands=%"PRIu64" writes=%"PRIu64" reads=%"PRIu64" > errors=%"PRIu64 > +pci_mmio_bridge_reset(void) "resetting bridge state" > diff --git a/include/hw/pci/pci-mmio-bridge.h > b/include/hw/pci/pci-mmio-bridge.h > new file mode 100644 > index 0000000000..38d852cb08 > --- /dev/null > +++ b/include/hw/pci/pci-mmio-bridge.h > @@ -0,0 +1,162 @@ > +/* SPDX-License-Identifier: GPL-2.0-or-later */ > +/* > + * QEMU PCI MMIO Bridge > + * > + * Copyright (c) 2025 Stephen Bates <[email protected]> > + */ > + > +#ifndef HW_PCI_MMIO_BRIDGE_H > +#define HW_PCI_MMIO_BRIDGE_H > + > +#include "hw/pci/pci.h" > +#include "hw/pci/pci_device.h" > +#include "qom/object.h" > + > +/* > + * MMIO Bridge Command Packet Structure > + * > + */ > +struct pci_mmio_bridge_command { > + uint16_t target_bdf; /* Bus:Device:Function (bus|devfn) */ > + uint8_t target_bar; /* Which BAR on target device (0-5) */ > + uint8_t reserved1; > + uint32_t offset; /* Offset within BAR */ > + > + uint64_t value; /* Value to write, or returned value for read > */ > + > + uint8_t command; /* Command type (see CMD_* below) */ > + uint8_t size; /* Transfer size: 1, 2, 4, or 8 bytes */ > + uint8_t status; /* Status (see STATUS_* below) */ > + uint8_t reserved2; > + uint32_t sequence; /* Sequence number for ordering */ > +} QEMU_PACKED; > + > +/* Command types */ > +#define PCI_MMIO_BRIDGE_CMD_NOP 0 > +#define PCI_MMIO_BRIDGE_CMD_WRITE 1 > +#define PCI_MMIO_BRIDGE_CMD_READ 2 > + > +/* Status codes */ > +#define PCI_MMIO_BRIDGE_STATUS_PENDING 0 > +#define PCI_MMIO_BRIDGE_STATUS_COMPLETE 1 > +#define PCI_MMIO_BRIDGE_STATUS_ERROR 2 > + > +/* Ring buffer metadata (stored in first command slot) */ > +struct pci_mmio_bridge_ring_meta { > + uint32_t producer_idx; /* Guest/device write index (tail) */ > + uint32_t consumer_idx; /* QEMU read index (head) */ > + uint32_t queue_depth; /* Total number of command slots */ > + uint32_t reserved; > +} QEMU_PACKED; > + > +/* > + * PCI MMIO Bridge State > + * > + */ > +typedef struct PCIMMIOBridge { > + /* Shadow buffer (guest RAM for DMA access) */ > + MemoryRegion shadow_mr; > + hwaddr shadow_gpa; /* Guest physical address */ > + void *shadow_hva; /* Host virtual address for polling */ > + uint32_t shadow_size; /* Size in bytes */ > + > + /* PCI bus to search for devices */ > + struct PCIBus *pci_bus; /* Main PCI bus */ > + > + /* Ring buffer management */ > + uint32_t queue_depth; /* Number of command slots available */ > + uint32_t head; /* Consumer index (QEMU's position) */ > + > + /* Polling infrastructure */ > + QEMUTimer *poll_timer; > + QEMUBH *poll_bh; > + uint64_t poll_interval_ns; > + bool enabled; > + bool use_bh; /* Use BH instead of timer (for qtest) */ > + > + /* Statistics for monitoring/debugging */ > + uint64_t total_commands; > + uint64_t total_writes; > + uint64_t total_reads; > + uint64_t total_errors; > + uint64_t total_polls; > +} PCIMMIOBridge; > + > +/* > + * Initialize the PCI MMIO bridge > + * > + * @pci_bus: PCI bus to monitor for devices > + * @gpa: Guest physical address for shadow buffer (must be page-aligned) > + * @size: Size of shadow buffer in bytes (must be >= 4096) > + * @poll_interval_ns: Polling interval in nanoseconds (0 = default) > + * @errp: Error pointer > + * > + * Returns: Pointer to bridge state on success, NULL on error > + */ > +PCIMMIOBridge *pci_mmio_bridge_init(struct PCIBus *pci_bus, > + hwaddr gpa, uint32_t size, > + uint64_t poll_interval_ns, > + Error **errp); > + > +/* > + * Cleanup the PCI MMIO bridge > + */ > +void pci_mmio_bridge_cleanup(PCIMMIOBridge *bridge); > + > +/* > + * Enable/disable the bridge > + */ > +void pci_mmio_bridge_set_enabled(PCIMMIOBridge *bridge, bool enabled); > + > +/* > + * Get bridge statistics (for QMP/monitor) > + */ > +void pci_mmio_bridge_get_stats(PCIMMIOBridge *bridge, > + uint64_t *total_commands, > + uint64_t *total_writes, > + uint64_t *total_reads, > + uint64_t *total_errors); > + > +/* > + * Manually trigger one poll cycle (for testing) > + * > + * This is useful in qtest environments where timers don't automatically > fire. > + */ > +void pci_mmio_bridge_poll_once(PCIMMIOBridge *bridge); > + > +/* > + * Internal polling callback > + */ > +void pci_mmio_bridge_poll(void *opaque); > + > + > +/* > + * PCI Device Interface > + */ > + > +/* > + * Vendor-specific capability offsets in PCI config space > + * These expose the shadow buffer GPA and size to guest drivers > + */ > +#define PCI_MMIO_BRIDGE_CAP_OFFSET 0x40 > +#define PCI_MMIO_BRIDGE_CAP_GPA_LO 0x00 /* Lower 32 bits of GPA */ > +#define PCI_MMIO_BRIDGE_CAP_GPA_HI 0x04 /* Upper 32 bits of GPA */ > +#define PCI_MMIO_BRIDGE_CAP_SIZE 0x08 /* Buffer size */ > +#define PCI_MMIO_BRIDGE_CAP_DEPTH 0x0C /* Queue depth */ > + > +#define TYPE_PCI_MMIO_BRIDGE "pci-mmio-bridge" > +OBJECT_DECLARE_SIMPLE_TYPE(PCIMMIOBridge_NEW, PCI_MMIO_BRIDGE) > + > +struct PCIMMIOBridge_NEW { > + PCIDevice parent_obj; > + > + /* Core bridge state */ > + PCIMMIOBridge *bridge; > + > + /* Configuration properties */ > + uint64_t shadow_gpa; /* Guest physical address (0 = auto) */ > + uint32_t shadow_size; /* Size of shadow buffer */ > + uint64_t poll_interval_ns; /* Polling interval */ > + bool enabled; /* Whether bridge is active */ > +}; > +#endif /* HW_PCI_MMIO_BRIDGE_H */ > diff --git a/include/hw/pci/pci.h b/include/hw/pci/pci.h > index 6b7d3ac8a3..d5c6e546a8 100644 > --- a/include/hw/pci/pci.h > +++ b/include/hw/pci/pci.h > @@ -121,6 +121,7 @@ extern bool pci_available; > #define PCI_DEVICE_ID_REDHAT_ACPI_ERST 0x0012 > #define PCI_DEVICE_ID_REDHAT_UFS 0x0013 > #define PCI_DEVICE_ID_REDHAT_RISCV_IOMMU 0x0014 > +#define PCI_DEVICE_ID_REDHAT_MMIO_BRIDGE 0x0015 > #define PCI_DEVICE_ID_REDHAT_QXL 0x0100 > > #define FMT_PCIBUS PRIx64 > diff --git a/tests/qtest/meson.build b/tests/qtest/meson.build > index 669d07c06b..718f2e0606 100644 > --- a/tests/qtest/meson.build > +++ b/tests/qtest/meson.build > @@ -74,6 +74,7 @@ qtests_i386 = \ > (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'] : []) + > \ > + ['pci-mmio-bridge-test'] + \ > (config_all_devices.has_key('CONFIG_LPC_ICH9') ? ['lpc-ich9-test'] : []) + > \ > (config_all_devices.has_key('CONFIG_MC146818RTC') ? ['rtc-test'] : []) + > \ > (config_all_devices.has_key('CONFIG_USB_UHCI') ? ['usb-hcd-uhci-test'] : > []) + \ > diff --git a/tests/qtest/pci-mmio-bridge-test.c > b/tests/qtest/pci-mmio-bridge-test.c > new file mode 100644 > index 0000000000..ad795a2f7e > --- /dev/null > +++ b/tests/qtest/pci-mmio-bridge-test.c > @@ -0,0 +1,417 @@ > +/* SPDX-License-Identifier: GPL-2.0-or-later */ > +/* > + * QTest testcases for PCI MMIO Bridge (PCI Device) > + * > + * Copyright (c) 2025 Stephen Bates <[email protected]> > + */ > + > +#include "qemu/osdep.h" > +#include "libqtest.h" > +#include "libqos/pci.h" > +#include "libqos/pci-pc.h" > +#include "hw/pci/pci_regs.h" > +#include "hw/pci/pci-mmio-bridge.h" > + > +/* Helper: Read shadow buffer GPA from PCI config space */ > +static uint64_t read_shadow_gpa(QPCIDevice *dev) > +{ > + uint32_t gpa_lo = qpci_config_readl(dev, PCI_MMIO_BRIDGE_CAP_OFFSET + > + PCI_MMIO_BRIDGE_CAP_GPA_LO); > + uint32_t gpa_hi = qpci_config_readl(dev, PCI_MMIO_BRIDGE_CAP_OFFSET + > + PCI_MMIO_BRIDGE_CAP_GPA_HI); > + return ((uint64_t)gpa_hi << 32) | gpa_lo; > +} > + > +/* Helper: Read shadow buffer size from PCI config space */ > +static uint32_t read_shadow_size(QPCIDevice *dev) > +{ > + return qpci_config_readl(dev, PCI_MMIO_BRIDGE_CAP_OFFSET + > + PCI_MMIO_BRIDGE_CAP_SIZE); > +} > + > +/* Helper: Read queue depth from PCI config space */ > +static uint32_t read_queue_depth(QPCIDevice *dev) > +{ > + return qpci_config_readl(dev, PCI_MMIO_BRIDGE_CAP_OFFSET + > + PCI_MMIO_BRIDGE_CAP_DEPTH); > +} > + > +/* Helper: Find bridge device on PCI bus */ > +static QPCIDevice *find_pci_mmio_bridge(QPCIBus *bus) > +{ > + for (int devfn = 0; devfn < 256; devfn++) { > + QPCIDevice *dev = qpci_device_find(bus, devfn); > + if (dev) { > + uint16_t vid = qpci_config_readw(dev, PCI_VENDOR_ID); > + uint16_t did = qpci_config_readw(dev, PCI_DEVICE_ID); > + if (vid == PCI_VENDOR_ID_REDHAT && > + did == PCI_DEVICE_ID_REDHAT_MMIO_BRIDGE) { > + return dev; > + } > + g_free(dev); > + } > + } > + return NULL; > +} > + > +/* Helper: Find pci-testdev on PCI bus */ > +static QPCIDevice *find_pci_testdev(QPCIBus *bus) > +{ > + for (int devfn = 0; devfn < 256; devfn++) { > + QPCIDevice *dev = qpci_device_find(bus, devfn); > + if (dev) { > + uint16_t did = qpci_config_readw(dev, PCI_DEVICE_ID); > + if (did == 0x5050) { /* pci-testdev device ID */ > + return dev; > + } > + g_free(dev); > + } > + } > + return NULL; > +} > + > +/* Test: Device discovery via PCI bus enumeration */ > +static void test_pci_device_discovery(void) > +{ > + QTestState *qts; > + QPCIBus *pcibus; > + QPCIDevice *dev; > + uint16_t vendor_id, device_id; > + uint8_t class_id; > + > + qts = qtest_init("-machine q35 " > + "-device pci-mmio-bridge,id=bridge0"); > + > + pcibus = qpci_new_pc(qts, NULL); > + g_assert_nonnull(pcibus); > + > + /* Find the bridge device */ > + dev = find_pci_mmio_bridge(pcibus); > + g_assert_nonnull(dev); > + > + /* Verify PCI IDs */ > + vendor_id = qpci_config_readw(dev, PCI_VENDOR_ID); > + device_id = qpci_config_readw(dev, PCI_DEVICE_ID); > + class_id = qpci_config_readb(dev, PCI_CLASS_DEVICE); > + > + g_assert_cmpuint(vendor_id, ==, PCI_VENDOR_ID_REDHAT); > + g_assert_cmpuint(device_id, ==, PCI_DEVICE_ID_REDHAT_MMIO_BRIDGE); > + g_assert_cmpuint(class_id, ==, 0x80); /* PCI_CLASS_SYSTEM_OTHER */ > + > + g_free(dev); > + qpci_free_pc(pcibus); > + qtest_quit(qts); > +} > + > +/* Test: Shadow buffer GPA is exposed via config space */ > +static void test_pci_shadow_gpa_access(void) > +{ > + QTestState *qts; > + QPCIBus *pcibus; > + QPCIDevice *dev; > + uint64_t shadow_gpa; > + uint32_t shadow_size, queue_depth; > + struct pci_mmio_bridge_ring_meta meta; > + > + qts = qtest_init("-machine q35 " > + "-device pci-mmio-bridge,id=bridge0,shadow-size=4096"); > + > + pcibus = qpci_new_pc(qts, NULL); > + dev = find_pci_mmio_bridge(pcibus); > + g_assert_nonnull(dev); > + > + /* Enable device */ > + qpci_device_enable(dev); > + > + /* Read shadow buffer location from PCI config space */ > + shadow_gpa = read_shadow_gpa(dev); > + shadow_size = read_shadow_size(dev); > + queue_depth = read_queue_depth(dev); > + > + /* Verify config space values */ > + g_assert_cmpuint(shadow_gpa, >, 0); > + g_assert_cmpuint(shadow_size, ==, 4096); > + g_assert_cmpuint(queue_depth, ==, 169); /* (4096/24)-1 */ > + > + /* Read ring metadata from guest RAM at shadow_gpa */ > + qtest_memread(qts, shadow_gpa, &meta, sizeof(meta)); > + > + /* Verify metadata is initialized */ > + g_assert_cmpuint(meta.producer_idx, ==, 0); > + g_assert_cmpuint(meta.consumer_idx, ==, 0); > + g_assert_cmpuint(meta.queue_depth, ==, 169); > + > + g_free(dev); > + qpci_free_pc(pcibus); > + qtest_quit(qts); > +} > + > +/* Test: Shadow size property */ > +static void test_pci_shadow_size_property(void) > +{ > + QTestState *qts; > + QPCIBus *pcibus; > + QPCIDevice *dev; > + uint64_t shadow_gpa; > + uint32_t shadow_size, queue_depth; > + struct pci_mmio_bridge_ring_meta meta; > + > + qts = qtest_init("-machine q35 " > + "-device pci-mmio-bridge,shadow-size=8192"); > + > + pcibus = qpci_new_pc(qts, NULL); > + dev = find_pci_mmio_bridge(pcibus); > + g_assert_nonnull(dev); > + > + qpci_device_enable(dev); > + > + /* Read shadow buffer info from config space */ > + shadow_gpa = read_shadow_gpa(dev); > + shadow_size = read_shadow_size(dev); > + queue_depth = read_queue_depth(dev); > + > + /* Verify size is 8192 and queue depth matches: (8192/24)-1 = 340 */ > + g_assert_cmpuint(shadow_size, ==, 8192); > + g_assert_cmpuint(queue_depth, ==, 340); > + > + /* Verify metadata in guest RAM */ > + qtest_memread(qts, shadow_gpa, &meta, sizeof(meta)); > + g_assert_cmpuint(meta.queue_depth, ==, 340); > + > + g_free(dev); > + qpci_free_pc(pcibus); > + qtest_quit(qts); > +} > + > +/* Test: Basic write command via shadow buffer */ > +static void test_pci_write_command(void) > +{ > + QTestState *qts; > + QPCIBus *pcibus; > + QPCIDevice *bridge_dev, *test_dev; > + QPCIBar test_bar; > + uint64_t shadow_gpa; > + struct pci_mmio_bridge_command cmd = {0}; > + struct pci_mmio_bridge_ring_meta meta; > + uint32_t test_value_read; > + > + qts = qtest_init("-machine q35 " > + "-device pci-mmio-bridge,id=bridge0 " > + "-device pci-testdev,id=testdev0"); > + > + pcibus = qpci_new_pc(qts, NULL); > + > + /* Get bridge device and shadow GPA */ > + bridge_dev = find_pci_mmio_bridge(pcibus); > + g_assert_nonnull(bridge_dev); > + qpci_device_enable(bridge_dev); > + shadow_gpa = read_shadow_gpa(bridge_dev); > + > + /* Get test device */ > + test_dev = find_pci_testdev(pcibus); > + if (!test_dev) { > + g_test_skip("pci-testdev not available"); > + g_free(bridge_dev); > + qpci_free_pc(pcibus); > + qtest_quit(qts); > + return; > + } > + qpci_device_enable(test_dev); > + test_bar = qpci_iomap(test_dev, 0, NULL); > + > + /* Prepare write command */ > + cmd.target_bdf = (0 << 8) | test_dev->devfn; > + cmd.target_bar = 0; > + cmd.offset = 0; > + cmd.value = 0xDEADBEEF; > + cmd.command = PCI_MMIO_BRIDGE_CMD_WRITE; > + cmd.size = 4; > + cmd.status = PCI_MMIO_BRIDGE_STATUS_PENDING; > + cmd.sequence = 1; > + > + /* Write command to slot 1 in guest RAM (slot 0 is metadata) */ > + qtest_memwrite(qts, shadow_gpa + sizeof(meta), &cmd, sizeof(cmd)); > + > + /* Update producer index to signal command */ > + meta.producer_idx = 1; > + qtest_memwrite(qts, shadow_gpa, &meta.producer_idx, 4); > + > + /* Give QEMU time to process (BH should trigger) */ > + qtest_clock_step(qts, 10000000); /* 10ms */ > + > + /* Read back command to check status */ > + qtest_memread(qts, shadow_gpa + sizeof(meta), &cmd, sizeof(cmd)); > + g_assert_cmpuint(cmd.status, ==, PCI_MMIO_BRIDGE_STATUS_COMPLETE); > + > + /* Verify write reached target device */ > + test_value_read = qpci_io_readl(test_dev, test_bar, 0); > + g_assert_cmpuint(test_value_read, ==, 0xDEADBEEF); > + > + g_free(bridge_dev); > + g_free(test_dev); > + qpci_free_pc(pcibus); > + qtest_quit(qts); > +} > + > +/* Test: Basic read command via shadow buffer */ > +static void test_pci_read_command(void) > +{ > + QTestState *qts; > + QPCIBus *pcibus; > + QPCIDevice *bridge_dev, *test_dev; > + QPCIBar test_bar; > + uint64_t shadow_gpa; > + struct pci_mmio_bridge_command cmd = {0}; > + struct pci_mmio_bridge_ring_meta meta; > + uint32_t expected_value = 0xCAFEBABE; > + > + qts = qtest_init("-machine q35 " > + "-device pci-mmio-bridge,id=bridge0 " > + "-device pci-testdev,id=testdev0"); > + > + pcibus = qpci_new_pc(qts, NULL); > + > + bridge_dev = find_pci_mmio_bridge(pcibus); > + g_assert_nonnull(bridge_dev); > + qpci_device_enable(bridge_dev); > + shadow_gpa = read_shadow_gpa(bridge_dev); > + > + test_dev = find_pci_testdev(pcibus); > + if (!test_dev) { > + g_test_skip("pci-testdev not available"); > + g_free(bridge_dev); > + qpci_free_pc(pcibus); > + qtest_quit(qts); > + return; > + } > + qpci_device_enable(test_dev); > + test_bar = qpci_iomap(test_dev, 0, NULL); > + > + /* Write a value to test device first */ > + qpci_io_writel(test_dev, test_bar, 0, expected_value); > + > + /* Prepare read command */ > + cmd.target_bdf = (0 << 8) | test_dev->devfn; > + cmd.target_bar = 0; > + cmd.offset = 0; > + cmd.value = 0; > + cmd.command = PCI_MMIO_BRIDGE_CMD_READ; > + cmd.size = 4; > + cmd.status = PCI_MMIO_BRIDGE_STATUS_PENDING; > + cmd.sequence = 1; > + > + /* Write command to slot 1 in guest RAM */ > + qtest_memwrite(qts, shadow_gpa + sizeof(meta), &cmd, sizeof(cmd)); > + > + /* Signal command */ > + meta.producer_idx = 1; > + qtest_memwrite(qts, shadow_gpa, &meta.producer_idx, 4); > + > + /* Wait for processing */ > + qtest_clock_step(qts, 10000000); > + > + /* Read back command */ > + qtest_memread(qts, shadow_gpa + sizeof(meta), &cmd, sizeof(cmd)); > + g_assert_cmpuint(cmd.status, ==, PCI_MMIO_BRIDGE_STATUS_COMPLETE); > + g_assert_cmpuint(cmd.value, ==, expected_value); > + > + g_free(bridge_dev); > + g_free(test_dev); > + qpci_free_pc(pcibus); > + qtest_quit(qts); > +} > + > +/* Test: Device reset clears statistics */ > +static void test_pci_device_reset(void) > +{ > + QTestState *qts; > + QPCIBus *pcibus; > + QPCIDevice *dev; > + uint64_t shadow_gpa; > + struct pci_mmio_bridge_ring_meta meta; > + > + qts = qtest_init("-machine q35 " > + "-device pci-mmio-bridge,id=bridge0"); > + > + pcibus = qpci_new_pc(qts, NULL); > + dev = find_pci_mmio_bridge(pcibus); > + g_assert_nonnull(dev); > + > + qpci_device_enable(dev); > + shadow_gpa = read_shadow_gpa(dev); > + > + /* Write some data to producer index */ > + meta.producer_idx = 5; > + qtest_memwrite(qts, shadow_gpa, &meta.producer_idx, 4); > + > + /* Reset device */ > + qtest_qmp_send(qts, "{ 'execute': 'system_reset' }"); > + qtest_qmp_receive(qts); > + qtest_clock_step(qts, 1000000); > + > + /* Verify producer index reset to 0 */ > + qtest_memread(qts, shadow_gpa, &meta, sizeof(meta)); > + g_assert_cmpuint(meta.producer_idx, ==, 0); > + g_assert_cmpuint(meta.consumer_idx, ==, 0); > + > + g_free(dev); > + qpci_free_pc(pcibus); > + qtest_quit(qts); > +} > + > +/* Test: Multiple bridges can coexist */ > +static void test_pci_multiple_bridges(void) > +{ > + QTestState *qts; > + QPCIBus *pcibus; > + QPCIDevice *dev1, *dev2; > + uint16_t device_id; > + > + qts = qtest_init("-machine q35 " > + "-device pci-mmio-bridge,id=bridge0,addr=4.0," > + "shadow-gpa=0x80000000 " > + "-device pci-mmio-bridge,id=bridge1,addr=5.0," > + "shadow-gpa=0x81000000"); > + > + pcibus = qpci_new_pc(qts, NULL); > + > + /* Find first bridge */ > + dev1 = qpci_device_find(pcibus, QPCI_DEVFN(4, 0)); > + g_assert_nonnull(dev1); > + device_id = qpci_config_readw(dev1, PCI_DEVICE_ID); > + g_assert_cmpuint(device_id, ==, PCI_DEVICE_ID_REDHAT_MMIO_BRIDGE); > + > + /* Find second bridge */ > + dev2 = qpci_device_find(pcibus, QPCI_DEVFN(5, 0)); > + g_assert_nonnull(dev2); > + device_id = qpci_config_readw(dev2, PCI_DEVICE_ID); > + g_assert_cmpuint(device_id, ==, PCI_DEVICE_ID_REDHAT_MMIO_BRIDGE); > + > + g_free(dev1); > + g_free(dev2); > + qpci_free_pc(pcibus); > + qtest_quit(qts); > +} > + > +int main(int argc, char **argv) > +{ > + g_test_init(&argc, &argv, NULL); > + > + qtest_add_func("/pci-mmio-bridge-pci/device-discovery", > + test_pci_device_discovery); > + qtest_add_func("/pci-mmio-bridge-pci/shadow-gpa-access", > + test_pci_shadow_gpa_access); > + qtest_add_func("/pci-mmio-bridge-pci/shadow-size-property", > + test_pci_shadow_size_property); > + qtest_add_func("/pci-mmio-bridge-pci/write-command", > + test_pci_write_command); > + qtest_add_func("/pci-mmio-bridge-pci/read-command", > + test_pci_read_command); > + qtest_add_func("/pci-mmio-bridge-pci/device-reset", > + test_pci_device_reset); > + qtest_add_func("/pci-mmio-bridge-pci/multiple-bridges", > + test_pci_multiple_bridges); > + > + return g_test_run(); > +} > + > -- > 2.43.0 > > > -- > > Cheers > > Stephen Bates, PhD.
