* [PATCH 1/4] hw/riscv/virt: Add CXL support to the RISC-V virt machine
2026-08-21 8:19 [PATCH 0/4] hw/riscv/virt: Add CXL support to the RISC-V virt machine Chen Pei
@ 2026-08-21 8:19 ` Chen Pei
2026-08-21 8:19 ` [PATCH 2/4] hw/riscv/virt-acpi-build: Add _DEP to ACPI0017 for CXL host bridge dependency Chen Pei
` (3 subsequent siblings)
4 siblings, 0 replies; 6+ messages in thread
From: Chen Pei @ 2026-08-21 8:19 UTC (permalink / raw)
To: palmer, alistair.francis, mst, imammedo, sunilvl, jic23
Cc: pbonzini, liwei1518, daniel.barboza, zhiwei_liu, chao.liu,
anisinha, dave.jiang, alison.schofield, junjie.cao, guoren,
qemu-riscv, qemu-devel, linux-cxl
Enable CXL support on the RISC-V virt machine following the same
approach used by the ARM virt machine:
- Add PXB and ACPI_CXL Kconfig selections
- Add CXLState and PCIBus pointer to RISCVVirtState
- Register CXL machine properties via cxl_machine_init()
- Create CXL host register region above the PCIe high MMIO region
- Call cxl_hook_up_pxb_registers() and cxl_fmws_link_targets() at
machine_done time
- Map Fixed Memory Windows above the CXL host register region
- Add ACPI0017 device in DSDT and build CEDT table in virt-acpi-build.c
Signed-off-by: Chen Pei <cp0613@linux.alibaba.com>
---
hw/riscv/Kconfig | 2 ++
hw/riscv/virt-acpi-build.c | 20 ++++++++++++++++++++
hw/riscv/virt.c | 37 +++++++++++++++++++++++++++++++++++++
include/hw/riscv/virt.h | 3 +++
4 files changed, 62 insertions(+)
diff --git a/hw/riscv/Kconfig b/hw/riscv/Kconfig
index de37c08cae..0ba3cd9e15 100644
--- a/hw/riscv/Kconfig
+++ b/hw/riscv/Kconfig
@@ -66,8 +66,10 @@ config RISCV_VIRT
select VIRTIO_MMIO
select FW_CFG_DMA
select PLATFORM_BUS
+ select PXB
select ACPI
select ACPI_PCI
+ select ACPI_CXL
config SHAKTI_C
bool
diff --git a/hw/riscv/virt-acpi-build.c b/hw/riscv/virt-acpi-build.c
index 59c454f4f9..3188cdc5bf 100644
--- a/hw/riscv/virt-acpi-build.c
+++ b/hw/riscv/virt-acpi-build.c
@@ -29,12 +29,16 @@
#include "hw/acpi/aml-build.h"
#include "hw/acpi/pci.h"
#include "hw/acpi/utils.h"
+#include "hw/acpi/cxl.h"
#include "hw/intc/riscv_aclint.h"
#include "hw/nvram/fw_cfg_acpi.h"
#include "hw/pci-host/gpex.h"
+#include "hw/pci/pci_bus.h"
#include "hw/riscv/virt.h"
#include "hw/riscv/numa.h"
#include "hw/virtio/virtio-acpi.h"
+#include "hw/cxl/cxl.h"
+#include "hw/cxl/cxl_host.h"
#include "kvm/kvm_riscv.h"
#include "migration/vmstate.h"
#include "qapi/error.h"
@@ -514,6 +518,17 @@ static void build_dsdt(GArray *table_data,
acpi_dsdt_add_gpex_host(scope, PCIE_IRQ + s->num_sources * 2);
}
+ if (s->cxl_devices_state.is_enabled) {
+ Aml *cxl_dev = aml_device("CXLM");
+ aml_append(cxl_dev, aml_name_decl("_HID", aml_string("ACPI0017")));
+ Aml *method = aml_method("_STA", 0, AML_NOTSERIALIZED);
+ aml_append(method, aml_return(aml_int(0x0B)));
+ aml_append(cxl_dev, method);
+ build_cxl_dsm_method(cxl_dev);
+
+ aml_append(scope, cxl_dev);
+ }
+
aml_append(dsdt, scope);
/* copy AML table into ACPI tables blob and patch header there */
@@ -921,6 +936,11 @@ static void virt_acpi_build(RISCVVirtState *s, AcpiBuildTables *tables)
s->oem_table_id);
}
+ if (s->cxl_devices_state.is_enabled) {
+ cxl_build_cedt(table_offsets, tables_blob, tables->linker,
+ s->oem_id, s->oem_table_id, &s->cxl_devices_state);
+ }
+
if (ms->numa_state->num_nodes > 0) {
acpi_add_table(table_offsets, tables_blob);
build_srat(tables_blob, tables->linker, s);
diff --git a/hw/riscv/virt.c b/hw/riscv/virt.c
index 51bac47a91..e08c76af4d 100644
--- a/hw/riscv/virt.c
+++ b/hw/riscv/virt.c
@@ -55,6 +55,8 @@
#include "hw/pci/pci.h"
#include "hw/pci-host/gpex.h"
#include "hw/display/ramfb.h"
+#include "hw/cxl/cxl.h"
+#include "hw/cxl/cxl_host.h"
#include "hw/acpi/aml-build.h"
#include "qapi/qapi-visit-common.h"
#include "hw/virtio/virtio-iommu.h"
@@ -1101,9 +1103,33 @@ static inline DeviceState *gpex_pcie_init(MemoryRegion *sys_mem,
}
GPEX_HOST(dev)->gpex_cfg.bus = PCI_HOST_BRIDGE(dev)->bus;
+ s->pci_bus = PCI_HOST_BRIDGE(dev)->bus;
return dev;
}
+static void cxl_host_state_init(RISCVVirtState *s)
+{
+ MemoryRegion *sysmem = get_system_memory();
+ MemoryRegion *mr = &s->cxl_devices_state.host_mr;
+ hwaddr base;
+
+ if (!s->cxl_devices_state.is_enabled) {
+ return;
+ }
+
+ base = virt_high_pcie_memmap.base + virt_high_pcie_memmap.size;
+ base = ROUND_UP(base, 64 * KiB);
+
+ memory_region_init(mr, OBJECT(s), "cxl_host_reg", 64 * KiB * 16);
+ memory_region_add_subregion(sysmem, base, mr);
+
+ /* Map the Fixed Memory Windows above the CXL host register region. */
+ base += memory_region_size(mr);
+ base = ROUND_UP(base, 256 * MiB);
+ cxl_fmws_set_memmap(base, UINT64_MAX);
+ cxl_fmws_update_mmio();
+}
+
static FWCfgState *create_fw_cfg(const MachineState *ms, hwaddr base)
{
FWCfgState *fw_cfg;
@@ -1205,6 +1231,13 @@ static void virt_machine_done(Notifier *notifier, void *data)
machine_done);
MachineState *machine = MACHINE(s);
hwaddr start_addr = s->memmap[VIRT_DRAM].base;
+
+ cxl_hook_up_pxb_registers(s->pci_bus, &s->cxl_devices_state,
+ &error_fatal);
+
+ if (s->cxl_devices_state.is_enabled) {
+ cxl_fmws_link_targets(&error_fatal);
+ }
hwaddr firmware_end_addr;
vaddr kernel_start_addr;
const char *firmware_name = riscv_default_firmware_name(&s->soc[0]);
@@ -1451,6 +1484,8 @@ static void virt_machine_init(MachineState *machine)
ROUND_UP(virt_high_pcie_memmap.base, virt_high_pcie_memmap.size);
}
+ cxl_host_state_init(s);
+
/* register system main memory (actual RAM) */
memory_region_add_subregion(system_memory, s->memmap[VIRT_DRAM].base,
machine->ram);
@@ -1558,6 +1593,8 @@ static void virt_machine_instance_init(Object *obj)
s->acpi = ON_OFF_AUTO_AUTO;
s->iommu_sys = ON_OFF_AUTO_AUTO;
s->num_sources = VIRT_IRQCHIP_NUM_SOURCES;
+
+ cxl_machine_init(obj, &s->cxl_devices_state);
}
static char *virt_get_aia_guests(Object *obj, Error **errp)
diff --git a/include/hw/riscv/virt.h b/include/hw/riscv/virt.h
index 36a2def410..4cf930ab9f 100644
--- a/include/hw/riscv/virt.h
+++ b/include/hw/riscv/virt.h
@@ -24,6 +24,7 @@
#include "hw/core/sysbus.h"
#include "hw/block/flash.h"
#include "hw/intc/riscv_imsic.h"
+#include "hw/cxl/cxl.h"
#define VIRT_CPUS_MAX_BITS 9
#define VIRT_CPUS_MAX (1 << VIRT_CPUS_MAX_BITS)
@@ -65,6 +66,8 @@ struct RISCVVirtState {
OnOffAuto iommu_sys;
uint16_t pci_iommu_bdf;
uint16_t num_sources;
+ CXLState cxl_devices_state;
+ PCIBus *pci_bus;
};
enum {
--
2.50.1
^ permalink raw reply related [flat|nested] 6+ messages in thread* [PATCH 2/4] hw/riscv/virt-acpi-build: Add _DEP to ACPI0017 for CXL host bridge dependency
2026-08-21 8:19 [PATCH 0/4] hw/riscv/virt: Add CXL support to the RISC-V virt machine Chen Pei
2026-08-21 8:19 ` [PATCH 1/4] " Chen Pei
@ 2026-08-21 8:19 ` Chen Pei
2026-08-21 8:19 ` [PATCH 3/4] hw/riscv/virt: Provide a 32-bit MMIO window for CXL host bridges Chen Pei
` (2 subsequent siblings)
4 siblings, 0 replies; 6+ messages in thread
From: Chen Pei @ 2026-08-21 8:19 UTC (permalink / raw)
To: palmer, alistair.francis, mst, imammedo, sunilvl, jic23
Cc: pbonzini, liwei1518, daniel.barboza, zhiwei_liu, chao.liu,
anisinha, dave.jiang, alison.schofield, junjie.cao, guoren,
qemu-riscv, qemu-devel, linux-cxl, Sunil V L
On RISC-V QEMU virt platform with CXL enabled, the probe ordering
of acpi_pci_root (ACPI0016) and cxl_acpi (ACPI0017) is not
guaranteed. If cxl_acpi probes before acpi_pci_root has attached
the CXL host bridges, the CXL port topology will be incomplete
because to_cxl_host_bridge() silently skips devices whose PCI root
is not yet ready.
Add a _DEP object to the ACPI0017 device in the DSDT, declaring
its dependency on the ACPI0016 CXL host bridge devices. This tells
the OS to defer ACPI0017 enumeration until all ACPI0016 devices
have been attached by acpi_pci_root.
This requires a corresponding kernel change to call
acpi_dev_clear_dependencies() in acpi_pci_root_add().
Note on kernel compatibility: this is a "new QEMU feature requires a
sufficiently new kernel" situation. Kernels that process _DEP but
lack the acpi_dev_clear_dependencies() call will leave the CXLM device
deferred, so CXL memory is unusable there; kernels old enough to ignore
_DEP entirely behave exactly as before this change. In neither case is
existing (non-CXL) PCIe affected.
The resulting DSDT fragment (iasl -d output) for a single CXL host
bridge at bus 0x01 looks like:
Device (CXLM)
{
Name (_HID, "ACPI0017")
Name (_DEP, Package (0x01)
{
\_SB.PC01
})
Method (_STA, 0, NotSerialized)
{
Return (0x0B)
}
...
}
Reviewed-by: Sunil V L <sunilvl@oss.qualcomm.com>
Signed-off-by: Chen Pei <cp0613@linux.alibaba.com>
---
hw/riscv/virt-acpi-build.c | 32 ++++++++++++++++++++++++++++++++
1 file changed, 32 insertions(+)
diff --git a/hw/riscv/virt-acpi-build.c b/hw/riscv/virt-acpi-build.c
index 3188cdc5bf..56292f04a7 100644
--- a/hw/riscv/virt-acpi-build.c
+++ b/hw/riscv/virt-acpi-build.c
@@ -521,6 +521,38 @@ static void build_dsdt(GArray *table_data,
if (s->cxl_devices_state.is_enabled) {
Aml *cxl_dev = aml_device("CXLM");
aml_append(cxl_dev, aml_name_decl("_HID", aml_string("ACPI0017")));
+
+ /*
+ * Declare a _DEP on every ACPI0016 CXL host bridge so the OS
+ * defers ACPI0017 enumeration until acpi_pci_root has attached
+ * the CXL host bridges. Without this, cxl_acpi may probe before
+ * to_cxl_host_bridge() can resolve the PCI root and the CXL
+ * port topology comes up empty.
+ */
+ if (s->pci_bus) {
+ PCIBus *bus;
+ uint32_t num_cxl_hbs = 0;
+
+ QLIST_FOREACH(bus, &s->pci_bus->child, sibling) {
+ if (pci_bus_is_root(bus) && pci_bus_is_cxl(bus)) {
+ num_cxl_hbs++;
+ }
+ }
+
+ if (num_cxl_hbs > 0) {
+ Aml *dep_pkg = aml_package(num_cxl_hbs);
+
+ QLIST_FOREACH(bus, &s->pci_bus->child, sibling) {
+ if (pci_bus_is_root(bus) && pci_bus_is_cxl(bus)) {
+ aml_append(dep_pkg,
+ aml_name("\\_SB.PC%.02X",
+ pci_bus_num(bus)));
+ }
+ }
+ aml_append(cxl_dev, aml_name_decl("_DEP", dep_pkg));
+ }
+ }
+
Aml *method = aml_method("_STA", 0, AML_NOTSERIALIZED);
aml_append(method, aml_return(aml_int(0x0B)));
aml_append(cxl_dev, method);
--
2.50.1
^ permalink raw reply related [flat|nested] 6+ messages in thread* [PATCH 3/4] hw/riscv/virt: Provide a 32-bit MMIO window for CXL host bridges
2026-08-21 8:19 [PATCH 0/4] hw/riscv/virt: Add CXL support to the RISC-V virt machine Chen Pei
2026-08-21 8:19 ` [PATCH 1/4] " Chen Pei
2026-08-21 8:19 ` [PATCH 2/4] hw/riscv/virt-acpi-build: Add _DEP to ACPI0017 for CXL host bridge dependency Chen Pei
@ 2026-08-21 8:19 ` Chen Pei
2026-08-21 8:19 ` [PATCH 4/4] tests/qtest: Add RISC-V ACPI bios tables test for CXL Chen Pei
2026-08-21 8:43 ` [PATCH 0/4] hw/riscv/virt: Add CXL support to the RISC-V virt machine Chen Pei
4 siblings, 0 replies; 6+ messages in thread
From: Chen Pei @ 2026-08-21 8:19 UTC (permalink / raw)
To: palmer, alistair.francis, mst, imammedo, sunilvl, jic23
Cc: pbonzini, liwei1518, daniel.barboza, zhiwei_liu, chao.liu,
anisinha, dave.jiang, alison.schofield, junjie.cao, guoren,
qemu-riscv, qemu-devel, linux-cxl
CXL component register BAR (BAR0 on CXL Root Port and Type3 device)
and the CXL device register BAR (BAR2 on Type3 device) are declared
as 64-bit non-prefetchable memory. Per the PCI-to-PCI Bridge
Architecture Specification Rev 1.2 (PCI-SIG, 2003):
- §3.2.5.8 (Memory Base/Limit): the non-prefetchable window covers
only 32-bit addresses (AD[31:20]); the Type 1 header defines no
upper-32-bit extension for it.
- §3.2.5.9 (Prefetchable Memory Base/Limit): the bottom 4 bits
encode 64-bit support (01h), but this applies exclusively to the
*prefetchable* window.
- §3.2.5.10 (Prefetchable Base/Limit Upper 32 Bits): optional
registers for AD[63:32] of the prefetchable range only.
The architecture therefore allows a 64-bit window only when it is also
prefetchable; there is no 64-bit non-prefetchable form. PCIe inherits
this Type 1 header layout unchanged. Linux thus places 64-bit
non-prefetchable BARs in the 32-bit non-prefetchable bridge window,
which requires the bridge to own enough address space below 4 GiB.
On RISC-V virt the 32-bit PCIe MMIO range (1 GiB at 0x40000000) is
currently consumed entirely by PCI0, so CXL host bridges (ACPI0016)
have no non-prefetchable window and Linux fails to assign these BARs.
Reserve the top 256 MiB of the 32-bit MMIO window exclusively for CXL
host bridges, keeping all of the logic in the riscv code:
- Reduce the FDT 'ranges' for PCI0 by 256 MiB so that device-tree
driven firmware does not hand that range to PCI0.
- The CXL host bridge _CRS is produced by the generic build_crs()
path. EDK2's PciBusDxe only recurses into PCI-to-PCI bridges, while
the pxb-cxl expander bridge presents as a class 0x0600 host bridge
with a type-0 header, so firmware never enumerates behind it and
leaves the CXL root port's window and bus-number registers unset;
build_crs() would therefore return an empty _CRS. Simulate the
firmware PCI initialization in virt_cxl_init_bridge_windows(): assign
the reserved window to the root port's memory window, and allocate
primary/secondary/subordinate bus numbers depth-first. build_crs()
then emits both the memory range and a correct bus-range resource
(without a bus range Linux only claims the host-bridge bus and cannot
enumerate the devices behind the root port), and the range is
excluded from PCI0's _CRS. A reset handler re-applies the state, as
a PCI reset clears those registers.
This targets a single pxb-cxl (one CXL host bridge), which matches the
bios-tables test added later in the series. With more than one
pxb-cxl the single reserved window would be claimed by every ACPI0016
bridge, so it would need to be partitioned per bridge; that is left as
future work (see the TODO in virt_cxl_init_bridge_windows()).
Verified by booting an RVA22 kernel: the CXL root port and Type3 device
are enumerated (0000:0c:00.0 / 0000:0d:00.0) and 'cxl list' reports the
memdev and the CFMWS root decoder.
Signed-off-by: Chen Pei <cp0613@linux.alibaba.com>
---
hw/riscv/virt.c | 154 +++++++++++++++++++++++++++++++++++++++++++++++-
1 file changed, 153 insertions(+), 1 deletion(-)
diff --git a/hw/riscv/virt.c b/hw/riscv/virt.c
index e08c76af4d..af83603293 100644
--- a/hw/riscv/virt.c
+++ b/hw/riscv/virt.c
@@ -48,11 +48,13 @@
#include "chardev/char.h"
#include "system/device_tree.h"
#include "system/system.h"
+#include "system/reset.h"
#include "system/tcg.h"
#include "system/kvm.h"
#include "system/tpm.h"
#include "system/qtest.h"
#include "hw/pci/pci.h"
+#include "hw/pci/pci_bridge.h"
#include "hw/pci-host/gpex.h"
#include "hw/display/ramfb.h"
#include "hw/cxl/cxl.h"
@@ -115,6 +117,9 @@ static const MemMapEntry virt_memmap[] = {
/* PCIe high mmio for RV64, size is fixed but base depends on top of RAM */
#define VIRT64_HIGH_PCIE_MMIO_SIZE (16 * GiB)
+/* 32-bit MMIO range carved out of VIRT_PCIE_MMIO for CXL host bridges */
+#define VIRT_CXL_MMIO32_SIZE (256 * MiB)
+
static MemMapEntry virt_high_pcie_memmap;
#define VIRT_FLASH_SECTOR_SIZE (256 * KiB)
@@ -727,6 +732,18 @@ static void create_fdt_pcie(RISCVVirtState *s,
{
g_autofree char *name = NULL;
MachineState *ms = MACHINE(s);
+ /*
+ * When CXL is enabled, reserve the last 256 MiB of the 32-bit MMIO
+ * window for CXL host bridges and exclude it from the main PCIe host
+ * bridge's FDT 'ranges' so UEFI's PciHostBridgeDxe does not allocate
+ * that range to PCI0. The CXL host bridge _CRS declares this range
+ * independently.
+ */
+ hwaddr mmio32_size = s->memmap[VIRT_PCIE_MMIO].size;
+
+ if (s->cxl_devices_state.is_enabled) {
+ mmio32_size -= VIRT_CXL_MMIO32_SIZE;
+ }
name = g_strdup_printf("/soc/pci@%"HWADDR_PRIx,
s->memmap[VIRT_PCIE_ECAM].base);
@@ -752,7 +769,7 @@ static void create_fdt_pcie(RISCVVirtState *s,
2, s->memmap[VIRT_PCIE_PIO].base, 2, s->memmap[VIRT_PCIE_PIO].size,
1, FDT_PCI_RANGE_MMIO,
2, s->memmap[VIRT_PCIE_MMIO].base,
- 2, s->memmap[VIRT_PCIE_MMIO].base, 2, s->memmap[VIRT_PCIE_MMIO].size,
+ 2, s->memmap[VIRT_PCIE_MMIO].base, 2, mmio32_size,
1, FDT_PCI_RANGE_MMIO_64BIT,
2, virt_high_pcie_memmap.base,
2, virt_high_pcie_memmap.base, 2, virt_high_pcie_memmap.size);
@@ -1130,6 +1147,132 @@ static void cxl_host_state_init(RISCVVirtState *s)
cxl_fmws_update_mmio();
}
+/*
+ * Assign PCI bus numbers to the bridges under @bus in depth-first order,
+ * the way firmware does during enumeration. QEMU leaves the secondary and
+ * subordinate bus registers at 0 until firmware programs them, so without
+ * this build_crs() would see an empty bus range for the CXL host bridge.
+ * @bus itself is numbered @bus_num. Returns the highest bus number used.
+ */
+static int virt_cxl_assign_bus_numbers(PCIBus *bus, int bus_num)
+{
+ int max_bus = bus_num;
+ int next_bus = bus_num + 1;
+ int devfn;
+
+ for (devfn = 0; devfn < ARRAY_SIZE(bus->devices); devfn++) {
+ PCIDevice *dev = bus->devices[devfn];
+ PCIBus *sec_bus;
+ int subordinate;
+
+ if (!dev) {
+ continue;
+ }
+ if ((dev->config[PCI_HEADER_TYPE] &
+ ~PCI_HEADER_TYPE_MULTI_FUNCTION) != PCI_HEADER_TYPE_BRIDGE) {
+ continue;
+ }
+
+ sec_bus = pci_bridge_get_sec_bus(PCI_BRIDGE(dev));
+ subordinate = virt_cxl_assign_bus_numbers(sec_bus, next_bus);
+
+ pci_set_byte(dev->config + PCI_PRIMARY_BUS, bus_num);
+ pci_set_byte(dev->config + PCI_SECONDARY_BUS, next_bus);
+ pci_set_byte(dev->config + PCI_SUBORDINATE_BUS, subordinate);
+
+ if (subordinate > max_bus) {
+ max_bus = subordinate;
+ }
+ next_bus = subordinate + 1;
+ }
+
+ return max_bus;
+}
+
+/*
+ * Simulate the PCI resource initialization that firmware (UEFI) would
+ * normally perform for the CXL host bridge.
+ *
+ * EDK2's PciBusDxe only recurses into devices that look like PCI-to-PCI
+ * bridges, while the pxb-cxl expander bridge presents as a class 0x0600
+ * host bridge with a standard (type 0) header, so the firmware neither
+ * enumerates behind it nor assigns it a memory window or bus numbers. As a
+ * result the generic build_crs() path would produce an empty _CRS for the
+ * CXL host bridge (ACPI0016).
+ *
+ * Program each CXL root port the way firmware would:
+ * - assign the reserved 32-bit MMIO range (see create_fdt_pcie()) to its
+ * memory window, so build_crs() emits that range in the host bridge _CRS
+ * and excludes it from PCI0's _CRS;
+ * - set the primary/secondary/subordinate bus numbers, so build_crs()
+ * emits a correct bus-range resource. Without a bus range Linux only
+ * claims the single host-bridge bus and cannot enumerate the devices
+ * behind the root port.
+ *
+ * TODO: this handles a single pxb-cxl with a single root port, which is
+ * the supported topology for now. With multiple CXL host bridges or root
+ * ports the reserved window would need to be partitioned per bridge.
+ */
+static void virt_cxl_init_bridge_windows(RISCVVirtState *s)
+{
+ hwaddr cxl_mmio_base, cxl_mmio_limit;
+ PCIBus *bus;
+
+ if (!s->cxl_devices_state.is_enabled) {
+ return;
+ }
+
+ cxl_mmio_base = s->memmap[VIRT_PCIE_MMIO].base +
+ s->memmap[VIRT_PCIE_MMIO].size - VIRT_CXL_MMIO32_SIZE;
+ cxl_mmio_limit = cxl_mmio_base + VIRT_CXL_MMIO32_SIZE - 1;
+
+ QLIST_FOREACH(bus, &s->pci_bus->child, sibling) {
+ int devfn;
+
+ if (!pci_bus_is_root(bus) || !pci_bus_is_cxl(bus)) {
+ continue;
+ }
+
+ /* Assign bus numbers for the whole CXL subtree (firmware would). */
+ virt_cxl_assign_bus_numbers(bus, pci_bus_num(bus));
+
+ /* Assign the reserved MMIO window to the root-port bridge. */
+ for (devfn = 0; devfn < ARRAY_SIZE(bus->devices); devfn++) {
+ PCIDevice *dev = bus->devices[devfn];
+ PCIBridge *br;
+
+ if (!dev) {
+ continue;
+ }
+ if ((dev->config[PCI_HEADER_TYPE] &
+ ~PCI_HEADER_TYPE_MULTI_FUNCTION) != PCI_HEADER_TYPE_BRIDGE) {
+ continue;
+ }
+
+ br = PCI_BRIDGE(dev);
+
+ pci_set_word(dev->config + PCI_MEMORY_BASE,
+ (cxl_mmio_base >> 16) & PCI_MEMORY_RANGE_MASK);
+ pci_set_word(dev->config + PCI_MEMORY_LIMIT,
+ (cxl_mmio_limit >> 16) & PCI_MEMORY_RANGE_MASK);
+ pci_word_test_and_set_mask(dev->config + PCI_COMMAND,
+ PCI_COMMAND_MEMORY);
+ pci_bridge_update_mappings(br);
+ break;
+ }
+ }
+}
+
+/*
+ * A PCI reset clears the bridge window and bus-number registers programmed
+ * above, so re-apply them after every reset, the way firmware would during
+ * its PCI enumeration.
+ */
+static void virt_cxl_reset_bridge_windows(void *opaque)
+{
+ virt_cxl_init_bridge_windows(RISCV_VIRT_MACHINE(opaque));
+}
+
static FWCfgState *create_fw_cfg(const MachineState *ms, hwaddr base)
{
FWCfgState *fw_cfg;
@@ -1238,6 +1381,15 @@ static void virt_machine_done(Notifier *notifier, void *data)
if (s->cxl_devices_state.is_enabled) {
cxl_fmws_link_targets(&error_fatal);
}
+
+ /*
+ * Assign the CXL bridge memory window (simulating firmware) so the
+ * ACPI _CRS built for the CXL host bridge is populated. Do it here for
+ * the initial build, and register a reset handler so the window is
+ * re-applied after any PCI reset (which clears the bridge registers).
+ */
+ virt_cxl_init_bridge_windows(s);
+ qemu_register_reset(virt_cxl_reset_bridge_windows, s);
hwaddr firmware_end_addr;
vaddr kernel_start_addr;
const char *firmware_name = riscv_default_firmware_name(&s->soc[0]);
--
2.50.1
^ permalink raw reply related [flat|nested] 6+ messages in thread* [PATCH 4/4] tests/qtest: Add RISC-V ACPI bios tables test for CXL
2026-08-21 8:19 [PATCH 0/4] hw/riscv/virt: Add CXL support to the RISC-V virt machine Chen Pei
` (2 preceding siblings ...)
2026-08-21 8:19 ` [PATCH 3/4] hw/riscv/virt: Provide a 32-bit MMIO window for CXL host bridges Chen Pei
@ 2026-08-21 8:19 ` Chen Pei
2026-08-21 8:43 ` [PATCH 0/4] hw/riscv/virt: Add CXL support to the RISC-V virt machine Chen Pei
4 siblings, 0 replies; 6+ messages in thread
From: Chen Pei @ 2026-08-21 8:19 UTC (permalink / raw)
To: palmer, alistair.francis, mst, imammedo, sunilvl, jic23
Cc: pbonzini, liwei1518, daniel.barboza, zhiwei_liu, chao.liu,
anisinha, dave.jiang, alison.schofield, junjie.cao, guoren,
qemu-riscv, qemu-devel, linux-cxl
Add test_acpi_riscv64_virt_tcg_cxl() to verify that enabling CXL on
the RISC-V virt machine produces correct ACPI tables, including the
ACPI0017 CXLM device with _DEP in the DSDT and the CEDT table.
The test boots with cxl=on, one pxb-cxl bus (bus_nr=12), a CXL root
port, a cxl-type3 persistent memory device, and a fixed memory window,
mirroring the existing x86 q35 CXL test pattern.
Since pxb-cxl is a root bus, using -cdrom causes QEMU to auto-plug the
cdrom drive into pxb-cxl, triggering "Only PCI/PCIe bridges can be
plugged into pxb-cxl". The ISO is instead attached explicitly via a
virtio-scsi-pci controller on pcie.0, following the same approach as
test_acpi_aarch64_virt_tcg_pxb().
Acked-by: Alistair Francis <alistair.francis@wdc.com>
Signed-off-by: Chen Pei <cp0613@linux.alibaba.com>
---
tests/data/acpi/riscv64/virt/CEDT.cxl | Bin 0 -> 108 bytes
tests/data/acpi/riscv64/virt/DSDT.cxl | Bin 0 -> 6212 bytes
tests/qtest/bios-tables-test.c | 54 ++++++++++++++++++++++++++
3 files changed, 54 insertions(+)
create mode 100644 tests/data/acpi/riscv64/virt/CEDT.cxl
create mode 100644 tests/data/acpi/riscv64/virt/DSDT.cxl
diff --git a/tests/data/acpi/riscv64/virt/CEDT.cxl b/tests/data/acpi/riscv64/virt/CEDT.cxl
new file mode 100644
index 0000000000000000000000000000000000000000..81d746dfb09ccb147e26bb64060404ca3191d097
GIT binary patch
literal 108
zc-nJzbqUE~U|?VjaPoKd2v%^42yj*a0!E-1hz+6{7!(+IKx!BefCEf2LP-snumA^?
O0aMEg#QH!C(hmS*@&^F`
literal 0
Hc-jL100001
diff --git a/tests/data/acpi/riscv64/virt/DSDT.cxl b/tests/data/acpi/riscv64/virt/DSDT.cxl
new file mode 100644
index 0000000000000000000000000000000000000000..4587f682c0c2053d4fb01b566e8e1782220c6bd5
GIT binary patch
literal 6212
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
C^C(OJ
literal 0
Hc-jL100001
diff --git a/tests/qtest/bios-tables-test.c b/tests/qtest/bios-tables-test.c
index 5cc526510a..d2b18d8ecb 100644
--- a/tests/qtest/bios-tables-test.c
+++ b/tests/qtest/bios-tables-test.c
@@ -2214,6 +2214,56 @@ static void test_acpi_riscv64_virt_tcg(void)
free_test_data(&data);
}
+#ifdef CONFIG_POSIX
+static void test_acpi_riscv64_virt_tcg_cxl(void)
+{
+ gchar *tmp_path = g_dir_make_tmp("qemu-test-cxl.XXXXXX", NULL);
+ gchar *params;
+
+ test_data data = {
+ .machine = "virt",
+ .arch = "riscv64",
+ .tcg_only = true,
+ .uefi_fl1 = "pc-bios/edk2-riscv-code.fd",
+ .uefi_fl2 = "pc-bios/edk2-riscv-vars.fd",
+ .ram_start = 0x80000000ULL,
+ .scan_len = 128ULL * MiB,
+ .variant = ".cxl",
+ };
+
+ /*
+ * While using -cdrom, the cdrom would auto-plug into pxb-cxl because
+ * its bus is also a root bus, triggering "Only PCI/PCIe bridges can be
+ * plugged into pxb-cxl". Attach the ISO explicitly to a scsi controller
+ * on pcie.0 instead, following the same pattern as
+ * test_acpi_aarch64_virt_tcg_pxb().
+ */
+ params = g_strdup_printf("-cpu rva22s64"
+ " -machine cxl=on"
+ " -device pcie-root-port,chassis=1,id=pci.1,bus=pcie.0"
+ " -device virtio-scsi-pci,id=scsi0,bus=pci.1"
+ " -drive file=tests/data/uefi-boot-images/"
+ "bios-tables-test.riscv64.iso.qcow2,"
+ "if=none,media=cdrom,id=drive-scsi0-0-0-1,readonly=on"
+ " -device scsi-cd,bus=scsi0.0,scsi-id=0,"
+ "drive=drive-scsi0-0-0-1,id=scsi0-0-0-1,bootindex=1"
+ " -object memory-backend-file,id=cxl-mem1,mem-path=%s,size=256M"
+ " -object memory-backend-file,id=lsa1,mem-path=%s,size=256M"
+ " -device pxb-cxl,bus_nr=12,bus=pcie.0,id=cxl.1"
+ " -device cxl-rp,port=0,bus=cxl.1,id=rp1,chassis=0,slot=2"
+ " -device cxl-type3,bus=rp1,persistent-memdev=cxl-mem1,lsa=lsa1"
+ " -M cxl-fmw.0.targets.0=cxl.1,cxl-fmw.0.size=4G,"
+ "cxl-fmw.0.interleave-granularity=8k",
+ tmp_path, tmp_path);
+ test_acpi_one(params, &data);
+
+ g_free(params);
+ g_assert(g_rmdir(tmp_path) == 0);
+ g_free(tmp_path);
+ free_test_data(&data);
+}
+#endif /* CONFIG_POSIX */
+
static void test_acpi_aarch64_virt_tcg(void)
{
test_data data = {
@@ -2963,6 +3013,10 @@ int main(int argc, char *argv[])
test_acpi_riscv64_virt_tcg_numamem);
qtest_add_func("acpi/virt/acpispcr",
test_acpi_riscv64_virt_tcg_acpi_spcr);
+#ifdef CONFIG_POSIX
+ qtest_add_func("acpi/virt/cxl",
+ test_acpi_riscv64_virt_tcg_cxl);
+#endif
}
} else if (strcmp(arch, "loongarch64") == 0) {
if (has_tcg && qtest_has_machine("virt")) {
--
2.50.1
^ permalink raw reply related [flat|nested] 6+ messages in thread* Re: [PATCH 0/4] hw/riscv/virt: Add CXL support to the RISC-V virt machine
2026-08-21 8:19 [PATCH 0/4] hw/riscv/virt: Add CXL support to the RISC-V virt machine Chen Pei
` (3 preceding siblings ...)
2026-08-21 8:19 ` [PATCH 4/4] tests/qtest: Add RISC-V ACPI bios tables test for CXL Chen Pei
@ 2026-08-21 8:43 ` Chen Pei
4 siblings, 0 replies; 6+ messages in thread
From: Chen Pei @ 2026-08-21 8:43 UTC (permalink / raw)
To: palmer, alistair.francis, mst, imammedo, sunilvl, jic23
Cc: pbonzini, liwei1518, daniel.barboza, zhiwei_liu, chao.liu,
anisinha, dave.jiang, alison.schofield, junjie.cao, guoren,
qemu-riscv, qemu-devel, linux-cxl
Hi all,
Apologies, I forgot to add the version tag in the subject prefix. This
is actually v3 of the series; the changelog against v2 and v1 is in the
cover letter. Please ignore the missing "v3" tag for now -- it will be
tagged correctly in the next revision.
Sorry for the noise.
Thanks,
Pei
^ permalink raw reply [flat|nested] 6+ messages in thread