* [PATCH v2 0/4] hw/riscv/virt: Add CXL support to the RISC-V virt machine
@ 2026-06-18 9:38 Chen Pei
2026-06-18 9:38 ` [PATCH v2 1/4] " Chen Pei
` (3 more replies)
0 siblings, 4 replies; 16+ messages in thread
From: Chen Pei @ 2026-06-18 9:38 UTC (permalink / raw)
To: jic23, pbonzini, palmer, alistair.francis, liwei1518,
daniel.barboza, zhiwei_liu, chao.liu.zevorn, sunilvl, dave.jiang,
alison.schofield, imammedo, mst, guoren
Cc: qemu-riscv, qemu-devel, linux-cxl
This series adds CXL (Compute Express Link) support to the RISC-V virt
machine in QEMU, following the same approach as the ARM virt machine.
Prerequisite
------------
This series depends on Alireza Sanaee's v8 series [1]:
[1/3] hw/cxl: Use HPA in cxl_cfmws_find_device() rather than offset
in window
[2/3] hw/cxl: Allow cxl_cfmws_find_device() to filter on whether
interleaved paths are accepted
[3/3] hw/cxl: Add a performant (and correct) path for the non
interleaved cases
Patch [1/3] is already in the tree. Patches [2/3] and [3/3] have been
tested and verified functional on RISC-V QEMU with CXL enabled. They
are not included in this posting; please apply them before this series.
Series overview
---------------
[1/4] (hw/riscv/virt): Wires up the core CXL machine support --
Kconfig selects, CXLState, PCIBus pointer (pci_bus), cxl_machine_init(),
CXL host register region, FMW mapping, and machine_done hooks.
[2/4] (hw/riscv/virt-acpi-build): Adds ACPI0017 (CXLM) to the
DSDT with a _DEP on every ACPI0016 CXL host bridge device. The _DEP
ensures the OS defers ACPI0017 enumeration until acpi_pci_root has
attached all CXL host bridges, fixing a probe-ordering race that
leaves the CXL port topology incomplete. A corresponding kernel
change to call acpi_dev_clear_dependencies() in acpi_pci_root_add()
is required on the Linux side. That kernel patch has merged:
https://lore.kernel.org/linux-pci/20260526025118.38935-1-cp0613@linux.alibaba.com/
[3/4] (hw/riscv/virt, gpex): Reserves the top 256 MiB of the
32-bit MMIO window for CXL host bridges. CXL component-register
BARs are 64-bit non-prefetchable; per PCI-to-PCI Bridge Architecture
Specification Rev 1.2 §3.2.5.8-10, only the prefetchable window can
be 64-bit, so Linux places these BARs in the 32-bit non-prefetchable
bridge window. Without this reservation PCI0 consumes the entire
1 GiB 32-bit range and CXL bridges get an empty _CRS, preventing
BAR assignment.
[4/4] (tests/qtest): Adds a RISC-V bios-tables test for the CXL
variant. Expected AML golden files (DSDT.cxl, CEDT.cxl) are
generated and included.
Changes from v1
---------------
- hw/riscv/virt: PCIBus *bus renamed to PCIBus *pci_bus (Jonathan).
- hw/riscv/virt: Dropped outer if (s->pci_bus) guard around
cxl_hook_up_pxb_registers(); the function already handles a NULL
bus internally (Jonathan).
- hw/riscv/virt-acpi-build: All s->bus references updated to
s->pci_bus; iasl -d decompiled DSDT fragment added to commit
message.
- hw/riscv/virt,gpex: Commit message expanded with PCI-to-PCI Bridge
Spec §3.2.5.8/9/10 citations (Jonathan).
- Original patch 4 ("Map committed HDM decoder ranges as RAM for
direct DMA") dropped; superseded by Alireza Sanaee's v8 series [1],
which correctly handles the interleaved/non-interleaved split and
supports KVM.
- New patch 4: RISC-V ACPI bios-tables test for CXL, with golden AML
files generated and included.
[1] https://lore.kernel.org/qemu-devel/20260318171918.146-1-alireza.sanaee@huawei.com/
Test
---------------
Tested on the RISC-V virt machine with EDK2 firmware and a buildroot
guest carrying the kernel patches above.
QEMU invocation (CXL-relevant options shown; EDK2 pflash, -bios,
-kernel, -append and the virtio-blk rootfs are as usual):
qemu-system-riscv64 \
-M virt,aia=aplic-imsic,acpi=on,cxl=on \
-cpu rv64 -smp 2 -m 4G,maxmem=8G,slots=8 \
-object memory-backend-ram,id=vmem0,share=on,size=4G \
-device pxb-cxl,bus_nr=12,bus=pcie.0,id=cxl.1 \
-device cxl-rp,port=0,bus=cxl.1,id=rp0,chassis=0,slot=2 \
-device cxl-type3,bus=rp0,volatile-memdev=vmem0,id=cxl-vmem0 \
-M cxl-fmw.0.targets.0=cxl.1,cxl-fmw.0.size=4G \
... # EDK2 pflash + -bios fw_dynamic.bin + -kernel Image
# + virtio-blk rootfs + -append "root=/dev/vda ..."
Verification (compare the two `free -h` outputs: total system memory
grows by 4 GiB after onlining):
# free -h
# cxl list
# cxl enable-memdev mem0
# cxl create-region -m -t ram -d decoder0.0 -w 1 mem0 -s 4G
# daxctl online-memory dax0.0
# free -h
Chen Pei (4):
hw/riscv/virt: Add CXL support to the RISC-V virt machine
hw/riscv/virt-acpi-build: Add _DEP to ACPI0017 for CXL host bridge
dependency
hw/riscv/virt,gpex: Provide 32-bit MMIO window for CXL host bridges
tests/qtest: Add RISC-V ACPI bios tables test for CXL
hw/pci-host/gpex-acpi.c | 36 +++++++++-
hw/riscv/Kconfig | 2 +
hw/riscv/virt-acpi-build.c | 52 ++++++++++++++
hw/riscv/virt.c | 98 +++++++++++++++++++++++---
include/hw/pci-host/gpex.h | 1 +
include/hw/riscv/virt.h | 3 +
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 ++++++++++++++
9 files changed, 234 insertions(+), 12 deletions(-)
create mode 100644 tests/data/acpi/riscv64/virt/CEDT.cxl
create mode 100644 tests/data/acpi/riscv64/virt/DSDT.cxl
--
2.50.1
^ permalink raw reply [flat|nested] 16+ messages in thread
* [PATCH v2 1/4] hw/riscv/virt: Add CXL support to the RISC-V virt machine
2026-06-18 9:38 [PATCH v2 0/4] hw/riscv/virt: Add CXL support to the RISC-V virt machine Chen Pei
@ 2026-06-18 9:38 ` Chen Pei
2026-06-24 16:42 ` Daniel Henrique Barboza
2026-06-18 9:38 ` [PATCH v2 2/4] hw/riscv/virt-acpi-build: Add _DEP to ACPI0017 for CXL host bridge dependency Chen Pei
` (2 subsequent siblings)
3 siblings, 1 reply; 16+ messages in thread
From: Chen Pei @ 2026-06-18 9:38 UTC (permalink / raw)
To: jic23, pbonzini, palmer, alistair.francis, liwei1518,
daniel.barboza, zhiwei_liu, chao.liu.zevorn, sunilvl, dave.jiang,
alison.schofield, imammedo, mst, guoren
Cc: 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 | 40 ++++++++++++++++++++++++++++++++++++++
include/hw/riscv/virt.h | 3 +++
4 files changed, 65 insertions(+)
diff --git a/hw/riscv/Kconfig b/hw/riscv/Kconfig
index 2518b04175..ebd0355f09 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 413d47d70e..309d64b322 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"
@@ -503,6 +507,17 @@ static void build_dsdt(GArray *table_data,
acpi_dsdt_add_gpex_host(scope, PCIE_IRQ + VIRT_IRQCHIP_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 */
@@ -910,6 +925,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 ce64eaaef7..84b91b4322 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"
@@ -1259,9 +1261,27 @@ 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 create_cxl_host_reg_region(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);
+}
+
static FWCfgState *create_fw_cfg(const MachineState *ms, hwaddr base)
{
FWCfgState *fw_cfg;
@@ -1426,6 +1446,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]);
@@ -1663,6 +1690,17 @@ static void virt_machine_init(MachineState *machine)
ROUND_UP(virt_high_pcie_memmap.base, virt_high_pcie_memmap.size);
}
+ create_cxl_host_reg_region(s);
+
+ if (s->cxl_devices_state.is_enabled) {
+ hwaddr cxl_base = virt_high_pcie_memmap.base +
+ virt_high_pcie_memmap.size;
+ cxl_base += memory_region_size(&s->cxl_devices_state.host_mr);
+ cxl_base = ROUND_UP(cxl_base, 256 * MiB);
+ cxl_fmws_set_memmap(cxl_base, UINT64_MAX);
+ cxl_fmws_update_mmio();
+ }
+
/* register system main memory (actual RAM) */
memory_region_add_subregion(system_memory, s->memmap[VIRT_DRAM].base,
machine->ram);
@@ -1769,6 +1807,8 @@ static void virt_machine_instance_init(Object *obj)
s->oem_table_id = g_strndup(ACPI_BUILD_APPNAME8, 8);
s->acpi = ON_OFF_AUTO_AUTO;
s->iommu_sys = ON_OFF_AUTO_AUTO;
+
+ 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 18a2a323a3..7d559ac610 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)
@@ -64,6 +65,8 @@ struct RISCVVirtState {
struct GPEXHost *gpex_host;
OnOffAuto iommu_sys;
uint16_t pci_iommu_bdf;
+ CXLState cxl_devices_state;
+ PCIBus *pci_bus;
};
enum {
--
2.50.1
^ permalink raw reply related [flat|nested] 16+ messages in thread
* [PATCH v2 2/4] hw/riscv/virt-acpi-build: Add _DEP to ACPI0017 for CXL host bridge dependency
2026-06-18 9:38 [PATCH v2 0/4] hw/riscv/virt: Add CXL support to the RISC-V virt machine Chen Pei
2026-06-18 9:38 ` [PATCH v2 1/4] " Chen Pei
@ 2026-06-18 9:38 ` Chen Pei
2026-07-06 4:08 ` Alistair Francis
2026-06-18 9:38 ` [PATCH v2 3/4] hw/riscv/virt,gpex: Provide 32-bit MMIO window for CXL host bridges Chen Pei
2026-06-18 9:38 ` [PATCH v2 4/4] tests/qtest: Add RISC-V ACPI bios tables test for CXL Chen Pei
3 siblings, 1 reply; 16+ messages in thread
From: Chen Pei @ 2026-06-18 9:38 UTC (permalink / raw)
To: jic23, pbonzini, palmer, alistair.francis, liwei1518,
daniel.barboza, zhiwei_liu, chao.liu.zevorn, sunilvl, dave.jiang,
alison.schofield, imammedo, mst, guoren
Cc: qemu-riscv, qemu-devel, linux-cxl
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().
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)
}
...
}
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 309d64b322..3598e55243 100644
--- a/hw/riscv/virt-acpi-build.c
+++ b/hw/riscv/virt-acpi-build.c
@@ -510,6 +510,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] 16+ messages in thread
* [PATCH v2 3/4] hw/riscv/virt,gpex: Provide 32-bit MMIO window for CXL host bridges
2026-06-18 9:38 [PATCH v2 0/4] hw/riscv/virt: Add CXL support to the RISC-V virt machine Chen Pei
2026-06-18 9:38 ` [PATCH v2 1/4] " Chen Pei
2026-06-18 9:38 ` [PATCH v2 2/4] hw/riscv/virt-acpi-build: Add _DEP to ACPI0017 for CXL host bridge dependency Chen Pei
@ 2026-06-18 9:38 ` Chen Pei
2026-06-24 17:21 ` Daniel Henrique Barboza
` (2 more replies)
2026-06-18 9:38 ` [PATCH v2 4/4] tests/qtest: Add RISC-V ACPI bios tables test for CXL Chen Pei
3 siblings, 3 replies; 16+ messages in thread
From: Chen Pei @ 2026-06-18 9:38 UTC (permalink / raw)
To: jic23, pbonzini, palmer, alistair.francis, liwei1518,
daniel.barboza, zhiwei_liu, chao.liu.zevorn, sunilvl, dave.jiang,
alison.schofield, imammedo, mst, guoren
Cc: 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.
Marking the BARs prefetchable would work around it, but the CXL
component registers have read/write side effects and are not
prefetchable per the PCIe specification.
Reserve the top 256 MiB of the 32-bit MMIO window exclusively for
CXL host bridges:
- Shrink PCI0's mmio32 window by 256 MiB in virt.c so that UEFI's
PciHostBridgeDxe and the ACPI _CRS for PCI0 never claim that range
- Store the reserved range in a new gpex_cfg.cxl_mmio32 field
- In gpex-acpi.c, emit the cxl_mmio32 range as the Memory resource
in the CXL host bridge _CRS instead of re-using build_crs() (which
returns an empty set when UEFI has not assigned resources yet)
- Reduce the FDT 'ranges' for PCI0 by the same 256 MiB so that UEFI
firmware driven by device-tree also respects the reservation
Signed-off-by: Chen Pei <cp0613@linux.alibaba.com>
---
hw/pci-host/gpex-acpi.c | 36 +++++++++++++++++++++--
hw/riscv/virt.c | 58 +++++++++++++++++++++++++++++++-------
include/hw/pci-host/gpex.h | 1 +
3 files changed, 83 insertions(+), 12 deletions(-)
diff --git a/hw/pci-host/gpex-acpi.c b/hw/pci-host/gpex-acpi.c
index d9820f9b41..d8b943b665 100644
--- a/hw/pci-host/gpex-acpi.c
+++ b/hw/pci-host/gpex-acpi.c
@@ -158,9 +158,41 @@ void acpi_dsdt_add_gpex(Aml *scope, struct GPEXConfig *cfg)
* Resources defined for PXBs are composed of the following parts:
* 1. The resources the pci-bridge/pcie-root-port need.
* 2. The resources the devices behind pxb need.
+ *
+ * For CXL host bridges on platforms where UEFI (driven by
+ * FDT 'ranges') does not assign PCI resources for the CXL
+ * root bridge before ACPI table construction, build_crs()
+ * would return an empty resource set. When the platform
+ * has reserved a dedicated MMIO window for CXL host bridges
+ * (cfg->cxl_mmio32), emit that window as a static _CRS
+ * instead. The platform is responsible for shrinking PCI0's
+ * mmio32 window so the two do not overlap.
*/
- crs = build_crs(PCI_HOST_BRIDGE(BUS(bus)->parent), &crs_range_set,
- cfg->pio.base, 0, 0, 0);
+ if (is_cxl && cfg->cxl_mmio32.size) {
+ uint64_t cxl_base = cfg->cxl_mmio32.base;
+ uint64_t cxl_size = cfg->cxl_mmio32.size;
+
+ crs = aml_resource_template();
+
+ /* 32-bit MMIO range for CXL devices */
+ aml_append(crs,
+ aml_dword_memory(AML_POS_DECODE, AML_MIN_FIXED,
+ AML_MAX_FIXED, AML_NON_CACHEABLE,
+ AML_READ_WRITE, 0,
+ cxl_base,
+ cxl_base + cxl_size - 1,
+ 0, cxl_size));
+
+ /* Bus number range */
+ aml_append(crs,
+ aml_word_bus_number(AML_MIN_FIXED, AML_MAX_FIXED,
+ AML_POS_DECODE, 0,
+ bus_num, bus_num + 15,
+ 0, 16));
+ } else {
+ crs = build_crs(PCI_HOST_BRIDGE(BUS(bus)->parent),
+ &crs_range_set, cfg->pio.base, 0, 0, 0);
+ }
aml_append(dev, aml_name_decl("_CRS", crs));
if (is_cxl) {
diff --git a/hw/riscv/virt.c b/hw/riscv/virt.c
index 84b91b4322..9c1a001553 100644
--- a/hw/riscv/virt.c
+++ b/hw/riscv/virt.c
@@ -113,6 +113,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)
@@ -890,15 +893,28 @@ static void create_fdt_pcie(RISCVVirtState *s,
}
qemu_fdt_setprop_sized_cells(ms->fdt, name, "reg", 2,
s->memmap[VIRT_PCIE_ECAM].base, 2, s->memmap[VIRT_PCIE_ECAM].size);
- qemu_fdt_setprop_sized_cells(ms->fdt, name, "ranges",
- 1, FDT_PCI_RANGE_IOPORT, 2, 0,
- 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,
- 1, FDT_PCI_RANGE_MMIO_64BIT,
- 2, virt_high_pcie_memmap.base,
- 2, virt_high_pcie_memmap.base, 2, virt_high_pcie_memmap.size);
+ {
+ /*
+ * 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;
+ }
+ qemu_fdt_setprop_sized_cells(ms->fdt, name, "ranges",
+ 1, FDT_PCI_RANGE_IOPORT, 2, 0,
+ 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, 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);
+ }
if (virt_is_iommu_sys_enabled(s)) {
qemu_fdt_setprop_cells(ms->fdt, name, "iommu-map",
@@ -1728,7 +1744,29 @@ static void virt_machine_init(MachineState *machine)
qdev_get_gpio_in(virtio_irqchip, VIRTIO_IRQ + i));
}
- gpex_pcie_init(system_memory, pcie_irqchip, s);
+ DeviceState *pcie_dev = gpex_pcie_init(system_memory, pcie_irqchip, s);
+
+ /*
+ * If CXL is enabled, reserve the last 256 MiB of the 32-bit MMIO
+ * window for CXL host bridges so the bridge non-prefetchable window
+ * can hold CXL device BARs (component registers and similar 64-bit
+ * non-prefetchable BARs that need a < 4 GiB address).
+ *
+ * - Shrink PCI0's mmio32 advertised in the ACPI _CRS by the same
+ * 256 MiB so the two ranges do not overlap (the FDT 'ranges'
+ * shrink happens in create_fdt_pcie()).
+ * - Store the reserved range in cxl_mmio32 so gpex-acpi.c can emit
+ * a correct _CRS for the CXL host bridge (ACPI0016).
+ */
+ if (s->cxl_devices_state.is_enabled) {
+ GPEXHost *gpex = GPEX_HOST(pcie_dev);
+ gpex->gpex_cfg.cxl_mmio32.size = VIRT_CXL_MMIO32_SIZE;
+ gpex->gpex_cfg.cxl_mmio32.base =
+ s->memmap[VIRT_PCIE_MMIO].base +
+ s->memmap[VIRT_PCIE_MMIO].size - VIRT_CXL_MMIO32_SIZE;
+ /* Shrink PCI0's advertised 32-bit MMIO window to exclude CXL range */
+ gpex->gpex_cfg.mmio32.size -= VIRT_CXL_MMIO32_SIZE;
+ }
create_platform_bus(s, mmio_irqchip);
diff --git a/include/hw/pci-host/gpex.h b/include/hw/pci-host/gpex.h
index 1da9c85bce..d38fbbacd6 100644
--- a/include/hw/pci-host/gpex.h
+++ b/include/hw/pci-host/gpex.h
@@ -43,6 +43,7 @@ struct GPEXConfig {
MemMapEntry mmio32;
MemMapEntry mmio64;
MemMapEntry pio;
+ MemMapEntry cxl_mmio32;
int irq;
PCIBus *bus;
bool pci_native_hotplug;
--
2.50.1
^ permalink raw reply related [flat|nested] 16+ messages in thread
* [PATCH v2 4/4] tests/qtest: Add RISC-V ACPI bios tables test for CXL
2026-06-18 9:38 [PATCH v2 0/4] hw/riscv/virt: Add CXL support to the RISC-V virt machine Chen Pei
` (2 preceding siblings ...)
2026-06-18 9:38 ` [PATCH v2 3/4] hw/riscv/virt,gpex: Provide 32-bit MMIO window for CXL host bridges Chen Pei
@ 2026-06-18 9:38 ` Chen Pei
2026-07-06 4:10 ` Alistair Francis
3 siblings, 1 reply; 16+ messages in thread
From: Chen Pei @ 2026-06-18 9:38 UTC (permalink / raw)
To: jic23, pbonzini, palmer, alistair.francis, liwei1518,
daniel.barboza, zhiwei_liu, chao.liu.zevorn, sunilvl, dave.jiang,
alison.schofield, imammedo, mst, guoren
Cc: 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().
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..45999636a05a16f68d1f418a58d9b80a8a62c9bd
GIT binary patch
literal 6212
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
Cu_#Oc
literal 0
Hc-jL100001
diff --git a/tests/qtest/bios-tables-test.c b/tests/qtest/bios-tables-test.c
index 510751799e..4625b76692 100644
--- a/tests/qtest/bios-tables-test.c
+++ b/tests/qtest/bios-tables-test.c
@@ -2194,6 +2194,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 = {
@@ -2888,6 +2938,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] 16+ messages in thread
* Re: [PATCH v2 1/4] hw/riscv/virt: Add CXL support to the RISC-V virt machine
2026-06-18 9:38 ` [PATCH v2 1/4] " Chen Pei
@ 2026-06-24 16:42 ` Daniel Henrique Barboza
2026-06-29 9:06 ` Chen Pei
0 siblings, 1 reply; 16+ messages in thread
From: Daniel Henrique Barboza @ 2026-06-24 16:42 UTC (permalink / raw)
To: Chen Pei, jic23, pbonzini, palmer, alistair.francis, liwei1518,
zhiwei_liu, chao.liu.zevorn, sunilvl, dave.jiang,
alison.schofield, imammedo, mst, guoren
Cc: qemu-riscv, qemu-devel, linux-cxl
Hi,
On 6/18/2026 6:38 AM, Chen Pei wrote:
> 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 | 40 ++++++++++++++++++++++++++++++++++++++
> include/hw/riscv/virt.h | 3 +++
> 4 files changed, 65 insertions(+)
>
> diff --git a/hw/riscv/Kconfig b/hw/riscv/Kconfig
> index 2518b04175..ebd0355f09 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 413d47d70e..309d64b322 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"
> @@ -503,6 +507,17 @@ static void build_dsdt(GArray *table_data,
> acpi_dsdt_add_gpex_host(scope, PCIE_IRQ + VIRT_IRQCHIP_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 */
> @@ -910,6 +925,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 ce64eaaef7..84b91b4322 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"
> @@ -1259,9 +1261,27 @@ 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 create_cxl_host_reg_region(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);
> +}
> +
> static FWCfgState *create_fw_cfg(const MachineState *ms, hwaddr base)
> {
> FWCfgState *fw_cfg;
> @@ -1426,6 +1446,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]);
> @@ -1663,6 +1690,17 @@ static void virt_machine_init(MachineState *machine)
> ROUND_UP(virt_high_pcie_memmap.base, virt_high_pcie_memmap.size);
> }
>
> + create_cxl_host_reg_region(s);
> +
> + if (s->cxl_devices_state.is_enabled) {
> + hwaddr cxl_base = virt_high_pcie_memmap.base +
> + virt_high_pcie_memmap.size;
> + cxl_base += memory_region_size(&s->cxl_devices_state.host_mr);
> + cxl_base = ROUND_UP(cxl_base, 256 * MiB);
> + cxl_fmws_set_memmap(cxl_base, UINT64_MAX);
> + cxl_fmws_update_mmio();
> + }
> +
create_cxl_host_reg_region() is doing the same check:
if (!s->cxl_devices_state.is_enabled) {
(...)
Maybe we could squash these update_mmio() lines in the same helper to have
everything CXL related in the same place. The helper would need to be
renamed to something more appropriate (e.g. cxl_host_state_init() since we're
at init time).
Patch LGTM otherwise. Thanks,
Daniel
> /* register system main memory (actual RAM) */
> memory_region_add_subregion(system_memory, s->memmap[VIRT_DRAM].base,
> machine->ram);
> @@ -1769,6 +1807,8 @@ static void virt_machine_instance_init(Object *obj)
> s->oem_table_id = g_strndup(ACPI_BUILD_APPNAME8, 8);
> s->acpi = ON_OFF_AUTO_AUTO;
> s->iommu_sys = ON_OFF_AUTO_AUTO;
> +
> + 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 18a2a323a3..7d559ac610 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)
> @@ -64,6 +65,8 @@ struct RISCVVirtState {
> struct GPEXHost *gpex_host;
> OnOffAuto iommu_sys;
> uint16_t pci_iommu_bdf;
> + CXLState cxl_devices_state;
> + PCIBus *pci_bus;
> };
>
> enum {
^ permalink raw reply [flat|nested] 16+ messages in thread
* Re: [PATCH v2 3/4] hw/riscv/virt,gpex: Provide 32-bit MMIO window for CXL host bridges
2026-06-18 9:38 ` [PATCH v2 3/4] hw/riscv/virt,gpex: Provide 32-bit MMIO window for CXL host bridges Chen Pei
@ 2026-06-24 17:21 ` Daniel Henrique Barboza
2026-06-29 9:10 ` Chen Pei
2026-07-22 13:52 ` Junjie Cao
2026-07-27 9:06 ` Igor Mammedov
2 siblings, 1 reply; 16+ messages in thread
From: Daniel Henrique Barboza @ 2026-06-24 17:21 UTC (permalink / raw)
To: Chen Pei, jic23, pbonzini, palmer, alistair.francis, liwei1518,
zhiwei_liu, chao.liu.zevorn, sunilvl, dave.jiang,
alison.schofield, imammedo, mst, guoren
Cc: qemu-riscv, qemu-devel, linux-cxl
On 6/18/2026 6:38 AM, Chen Pei wrote:
> 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.
> Marking the BARs prefetchable would work around it, but the CXL
> component registers have read/write side effects and are not
> prefetchable per the PCIe specification.
>
> Reserve the top 256 MiB of the 32-bit MMIO window exclusively for
> CXL host bridges:
> - Shrink PCI0's mmio32 window by 256 MiB in virt.c so that UEFI's
> PciHostBridgeDxe and the ACPI _CRS for PCI0 never claim that range
> - Store the reserved range in a new gpex_cfg.cxl_mmio32 field
> - In gpex-acpi.c, emit the cxl_mmio32 range as the Memory resource
> in the CXL host bridge _CRS instead of re-using build_crs() (which
> returns an empty set when UEFI has not assigned resources yet)
> - Reduce the FDT 'ranges' for PCI0 by the same 256 MiB so that UEFI
> firmware driven by device-tree also respects the reservation
>
> Signed-off-by: Chen Pei <cp0613@linux.alibaba.com>
> ---
> hw/pci-host/gpex-acpi.c | 36 +++++++++++++++++++++--
> hw/riscv/virt.c | 58 +++++++++++++++++++++++++++++++-------
> include/hw/pci-host/gpex.h | 1 +
> 3 files changed, 83 insertions(+), 12 deletions(-)
>
> diff --git a/hw/pci-host/gpex-acpi.c b/hw/pci-host/gpex-acpi.c
> index d9820f9b41..d8b943b665 100644
> --- a/hw/pci-host/gpex-acpi.c
> +++ b/hw/pci-host/gpex-acpi.c
> @@ -158,9 +158,41 @@ void acpi_dsdt_add_gpex(Aml *scope, struct GPEXConfig *cfg)
> * Resources defined for PXBs are composed of the following parts:
> * 1. The resources the pci-bridge/pcie-root-port need.
> * 2. The resources the devices behind pxb need.
> + *
> + * For CXL host bridges on platforms where UEFI (driven by
> + * FDT 'ranges') does not assign PCI resources for the CXL
> + * root bridge before ACPI table construction, build_crs()
> + * would return an empty resource set. When the platform
> + * has reserved a dedicated MMIO window for CXL host bridges
> + * (cfg->cxl_mmio32), emit that window as a static _CRS
> + * instead. The platform is responsible for shrinking PCI0's
> + * mmio32 window so the two do not overlap.
> */
> - crs = build_crs(PCI_HOST_BRIDGE(BUS(bus)->parent), &crs_range_set,
> - cfg->pio.base, 0, 0, 0);
> + if (is_cxl && cfg->cxl_mmio32.size) {
> + uint64_t cxl_base = cfg->cxl_mmio32.base;
> + uint64_t cxl_size = cfg->cxl_mmio32.size;
> +
> + crs = aml_resource_template();
> +
> + /* 32-bit MMIO range for CXL devices */
> + aml_append(crs,
> + aml_dword_memory(AML_POS_DECODE, AML_MIN_FIXED,
> + AML_MAX_FIXED, AML_NON_CACHEABLE,
> + AML_READ_WRITE, 0,
> + cxl_base,
> + cxl_base + cxl_size - 1,
> + 0, cxl_size));
> +
> + /* Bus number range */
> + aml_append(crs,
> + aml_word_bus_number(AML_MIN_FIXED, AML_MAX_FIXED,
> + AML_POS_DECODE, 0,
> + bus_num, bus_num + 15,
> + 0, 16));
> + } else {
> + crs = build_crs(PCI_HOST_BRIDGE(BUS(bus)->parent),
> + &crs_range_set, cfg->pio.base, 0, 0, 0);
> + }
> aml_append(dev, aml_name_decl("_CRS", crs));
>
> if (is_cxl) {
> diff --git a/hw/riscv/virt.c b/hw/riscv/virt.c
> index 84b91b4322..9c1a001553 100644
> --- a/hw/riscv/virt.c
> +++ b/hw/riscv/virt.c
> @@ -113,6 +113,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)
> @@ -890,15 +893,28 @@ static void create_fdt_pcie(RISCVVirtState *s,
> }
> qemu_fdt_setprop_sized_cells(ms->fdt, name, "reg", 2,
> s->memmap[VIRT_PCIE_ECAM].base, 2, s->memmap[VIRT_PCIE_ECAM].size);
> - qemu_fdt_setprop_sized_cells(ms->fdt, name, "ranges",
> - 1, FDT_PCI_RANGE_IOPORT, 2, 0,
> - 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,
> - 1, FDT_PCI_RANGE_MMIO_64BIT,
> - 2, virt_high_pcie_memmap.base,
> - 2, virt_high_pcie_memmap.base, 2, virt_high_pcie_memmap.size);
> + {
> + /*
> + * 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;
> + }
> + qemu_fdt_setprop_sized_cells(ms->fdt, name, "ranges",
> + 1, FDT_PCI_RANGE_IOPORT, 2, 0,
> + 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, 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);
> + }
We can get rid of the braces - it's just adding an extra identation level while
not doing anything special. 'mmio32_size' can be declared at the start
of create_fdt_pcie().
Thanks,
Daniel
>
> if (virt_is_iommu_sys_enabled(s)) {
> qemu_fdt_setprop_cells(ms->fdt, name, "iommu-map",
> @@ -1728,7 +1744,29 @@ static void virt_machine_init(MachineState *machine)
> qdev_get_gpio_in(virtio_irqchip, VIRTIO_IRQ + i));
> }
>
> - gpex_pcie_init(system_memory, pcie_irqchip, s);
> + DeviceState *pcie_dev = gpex_pcie_init(system_memory, pcie_irqchip, s);
> +
> + /*
> + * If CXL is enabled, reserve the last 256 MiB of the 32-bit MMIO
> + * window for CXL host bridges so the bridge non-prefetchable window
> + * can hold CXL device BARs (component registers and similar 64-bit
> + * non-prefetchable BARs that need a < 4 GiB address).
> + *
> + * - Shrink PCI0's mmio32 advertised in the ACPI _CRS by the same
> + * 256 MiB so the two ranges do not overlap (the FDT 'ranges'
> + * shrink happens in create_fdt_pcie()).
> + * - Store the reserved range in cxl_mmio32 so gpex-acpi.c can emit
> + * a correct _CRS for the CXL host bridge (ACPI0016).
> + */
> + if (s->cxl_devices_state.is_enabled) {
> + GPEXHost *gpex = GPEX_HOST(pcie_dev);
> + gpex->gpex_cfg.cxl_mmio32.size = VIRT_CXL_MMIO32_SIZE;
> + gpex->gpex_cfg.cxl_mmio32.base =
> + s->memmap[VIRT_PCIE_MMIO].base +
> + s->memmap[VIRT_PCIE_MMIO].size - VIRT_CXL_MMIO32_SIZE;
> + /* Shrink PCI0's advertised 32-bit MMIO window to exclude CXL range */
> + gpex->gpex_cfg.mmio32.size -= VIRT_CXL_MMIO32_SIZE;
> + }
>
> create_platform_bus(s, mmio_irqchip);
>
> diff --git a/include/hw/pci-host/gpex.h b/include/hw/pci-host/gpex.h
> index 1da9c85bce..d38fbbacd6 100644
> --- a/include/hw/pci-host/gpex.h
> +++ b/include/hw/pci-host/gpex.h
> @@ -43,6 +43,7 @@ struct GPEXConfig {
> MemMapEntry mmio32;
> MemMapEntry mmio64;
> MemMapEntry pio;
> + MemMapEntry cxl_mmio32;
> int irq;
> PCIBus *bus;
> bool pci_native_hotplug;
^ permalink raw reply [flat|nested] 16+ messages in thread
* Re: [PATCH v2 1/4] hw/riscv/virt: Add CXL support to the RISC-V virt machine
2026-06-24 16:42 ` Daniel Henrique Barboza
@ 2026-06-29 9:06 ` Chen Pei
0 siblings, 0 replies; 16+ messages in thread
From: Chen Pei @ 2026-06-29 9:06 UTC (permalink / raw)
To: Daniel Henrique Barboza
Cc: jic23, pbonzini, palmer, alistair.francis, liwei1518, zhiwei_liu,
chao.liu.zevorn, sunilvl, dave.jiang, alison.schofield, imammedo,
mst, guoren, qemu-riscv, qemu-devel, linux-cxl
Hi Daniel,
On 6/24/2026 1:42 PM, Daniel Henrique Barboza wrote:
> create_cxl_host_reg_region() is doing the same check:
>
> if (!s->cxl_devices_state.is_enabled) {
> (...)
>
> Maybe we could squash these update_mmio() lines in the same helper to have
> everything CXL related in the same place. The helper would need to be
> renamed to something more appropriate (e.g. cxl_host_state_init() since we're
> at init time).
Thanks for the review. You are right, the x86 pc_memory_init() path keeps
the CXL host register region creation and the FMW address programming
inside a single 'if (is_enabled)' block, and there is no reason for
RISC-V to split them. I'll fold the cxl_fmws_set_memmap() and
cxl_fmws_update_mmio() calls into create_cxl_host_reg_region(), rename
the helper to cxl_host_state_init(), and drop the duplicate guard in
virt_machine_init() in v3.
Thanks,
Pei
^ permalink raw reply [flat|nested] 16+ messages in thread
* Re: [PATCH v2 3/4] hw/riscv/virt,gpex: Provide 32-bit MMIO window for CXL host bridges
2026-06-24 17:21 ` Daniel Henrique Barboza
@ 2026-06-29 9:10 ` Chen Pei
0 siblings, 0 replies; 16+ messages in thread
From: Chen Pei @ 2026-06-29 9:10 UTC (permalink / raw)
To: Daniel Henrique Barboza
Cc: jic23, pbonzini, palmer, alistair.francis, liwei1518, zhiwei_liu,
chao.liu.zevorn, sunilvl, dave.jiang, alison.schofield, imammedo,
mst, guoren, qemu-riscv, qemu-devel, linux-cxl
Hi Daniel,
On 6/24/2026 2:21 PM, Daniel Henrique Barboza wrote:
> We can get rid of the braces - it's just adding an extra identation level while
> not doing anything special. 'mmio32_size' can be declared at the start
> of create_fdt_pcie().
Makes sense. I'll move mmio32_size to the top of create_fdt_pcie() and
remove the extra braces in v3.
Thanks,
Pei
^ permalink raw reply [flat|nested] 16+ messages in thread
* Re: [PATCH v2 2/4] hw/riscv/virt-acpi-build: Add _DEP to ACPI0017 for CXL host bridge dependency
2026-06-18 9:38 ` [PATCH v2 2/4] hw/riscv/virt-acpi-build: Add _DEP to ACPI0017 for CXL host bridge dependency Chen Pei
@ 2026-07-06 4:08 ` Alistair Francis
2026-07-07 11:50 ` Chen Pei
0 siblings, 1 reply; 16+ messages in thread
From: Alistair Francis @ 2026-07-06 4:08 UTC (permalink / raw)
To: Chen Pei
Cc: jic23, pbonzini, palmer, alistair.francis, liwei1518,
daniel.barboza, zhiwei_liu, chao.liu.zevorn, sunilvl, dave.jiang,
alison.schofield, imammedo, mst, guoren, qemu-riscv, qemu-devel,
linux-cxl
On Thu, Jun 18, 2026 at 7:40 PM Chen Pei <cp0613@linux.alibaba.com> wrote:
>
> 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().
What happens to kernels that don't have this change?
Alistair
>
> 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)
> }
> ...
> }
>
> 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 309d64b322..3598e55243 100644
> --- a/hw/riscv/virt-acpi-build.c
> +++ b/hw/riscv/virt-acpi-build.c
> @@ -510,6 +510,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 [flat|nested] 16+ messages in thread
* Re: [PATCH v2 4/4] tests/qtest: Add RISC-V ACPI bios tables test for CXL
2026-06-18 9:38 ` [PATCH v2 4/4] tests/qtest: Add RISC-V ACPI bios tables test for CXL Chen Pei
@ 2026-07-06 4:10 ` Alistair Francis
0 siblings, 0 replies; 16+ messages in thread
From: Alistair Francis @ 2026-07-06 4:10 UTC (permalink / raw)
To: Chen Pei
Cc: jic23, pbonzini, palmer, alistair.francis, liwei1518,
daniel.barboza, zhiwei_liu, chao.liu.zevorn, sunilvl, dave.jiang,
alison.schofield, imammedo, mst, guoren, qemu-riscv, qemu-devel,
linux-cxl
On Thu, Jun 18, 2026 at 7:39 PM Chen Pei <cp0613@linux.alibaba.com> wrote:
>
> 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().
>
> Signed-off-by: Chen Pei <cp0613@linux.alibaba.com>
Acked-by: Alistair Francis <alistair.francis@wdc.com>
Alistair
> ---
> 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..45999636a05a16f68d1f418a58d9b80a8a62c9bd
> GIT binary patch
> literal 6212
> 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
> Cu_#Oc
>
> literal 0
> Hc-jL100001
>
> diff --git a/tests/qtest/bios-tables-test.c b/tests/qtest/bios-tables-test.c
> index 510751799e..4625b76692 100644
> --- a/tests/qtest/bios-tables-test.c
> +++ b/tests/qtest/bios-tables-test.c
> @@ -2194,6 +2194,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 = {
> @@ -2888,6 +2938,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 [flat|nested] 16+ messages in thread
* Re: [PATCH v2 2/4] hw/riscv/virt-acpi-build: Add _DEP to ACPI0017 for CXL host bridge dependency
2026-07-06 4:08 ` Alistair Francis
@ 2026-07-07 11:50 ` Chen Pei
2026-07-09 3:56 ` Alistair Francis
0 siblings, 1 reply; 16+ messages in thread
From: Chen Pei @ 2026-07-07 11:50 UTC (permalink / raw)
To: Alistair Francis
Cc: jic23, pbonzini, palmer, alistair.francis, liwei1518,
daniel.barboza, zhiwei_liu, chao.liu.zevorn, sunilvl, dave.jiang,
alison.schofield, imammedo, mst, guoren, qemu-riscv, qemu-devel,
linux-cxl
[-- Warning: decoded text below may be mangled, UTF-8 assumed --]
[-- Attachment #1: Type: text/plain; charset=y, Size: 983 bytes --]
Hi Alistair,
On Mon, 6 Jul 2026 14:08:45 +1000, Alistair Francis wrote:
> What happens to kernels that don't have this change?
Good question. The behavior depends on how old the kernel is:
- Kernels that support _DEP processing but lack the
acpi_dev_clear_dependencies() call in acpi_pci_root_add(): the
ACPI0017 (CXLM) device will remain deferred indefinitely because
its declared dependency on ACPI0016 is never cleared. CXL memory
would be unusable on such kernels with this QEMU.
- Very old kernels that ignore _DEP entirely: the behavior is the
same as v1 — the probe-ordering race between acpi_pci_root and
cxl_acpi remains, but nothing is worse than before.
The kernel-side fix has already merged:
https://lore.kernel.org/linux-pci/20260526025118.38935-1-cp0613@linux.alibaba.com/
So this is effectively a "new QEMU requires a sufficiently new kernel
for CXL to work" situation, similar to other QEMU features that depend
on kernel support.
Thanks,
Pei
^ permalink raw reply [flat|nested] 16+ messages in thread
* Re: [PATCH v2 2/4] hw/riscv/virt-acpi-build: Add _DEP to ACPI0017 for CXL host bridge dependency
2026-07-07 11:50 ` Chen Pei
@ 2026-07-09 3:56 ` Alistair Francis
0 siblings, 0 replies; 16+ messages in thread
From: Alistair Francis @ 2026-07-09 3:56 UTC (permalink / raw)
To: Chen Pei
Cc: jic23, pbonzini, palmer, alistair.francis, liwei1518,
daniel.barboza, zhiwei_liu, chao.liu.zevorn, sunilvl, dave.jiang,
alison.schofield, imammedo, mst, guoren, qemu-riscv, qemu-devel,
linux-cxl
On Tue, Jul 7, 2026 at 9:51 PM Chen Pei <cp0613@linux.alibaba.com> wrote:
>
> Hi Alistair,
>
> On Mon, 6 Jul 2026 14:08:45 +1000, Alistair Francis wrote:
> > What happens to kernels that don't have this change?
>
> Good question. The behavior depends on how old the kernel is:
>
> - Kernels that support _DEP processing but lack the
> acpi_dev_clear_dependencies() call in acpi_pci_root_add(): the
> ACPI0017 (CXLM) device will remain deferred indefinitely because
> its declared dependency on ACPI0016 is never cleared. CXL memory
> would be unusable on such kernels with this QEMU.
>
> - Very old kernels that ignore _DEP entirely: the behavior is the
> same as v1 — the probe-ordering race between acpi_pci_root and
> cxl_acpi remains, but nothing is worse than before.
>
> The kernel-side fix has already merged:
> https://lore.kernel.org/linux-pci/20260526025118.38935-1-cp0613@linux.alibaba.com/
>
> So this is effectively a "new QEMU requires a sufficiently new kernel
> for CXL to work" situation, similar to other QEMU features that depend
> on kernel support.
That's fine, we can say you need a new kernel for this new feature to
work, as long as it doesn't break existing PCIe for old kernels
Alistair
>
> Thanks,
> Pei
^ permalink raw reply [flat|nested] 16+ messages in thread
* Re: [PATCH v2 3/4] hw/riscv/virt,gpex: Provide 32-bit MMIO window for CXL host bridges
2026-06-18 9:38 ` [PATCH v2 3/4] hw/riscv/virt,gpex: Provide 32-bit MMIO window for CXL host bridges Chen Pei
2026-06-24 17:21 ` Daniel Henrique Barboza
@ 2026-07-22 13:52 ` Junjie Cao
2026-07-24 12:10 ` Chen Pei
2026-07-27 9:06 ` Igor Mammedov
2 siblings, 1 reply; 16+ messages in thread
From: Junjie Cao @ 2026-07-22 13:52 UTC (permalink / raw)
To: Chen Pei
Cc: jic23, pbonzini, palmer, alistair.francis, liwei1518,
daniel.barboza, zhiwei_liu, chao.liu.zevorn, sunilvl, dave.jiang,
alison.schofield, imammedo, mst, guoren, qemu-riscv, qemu-devel,
linux-cxl
Hi Chen Pei,
On Thu, 18 Jun 2026 17:38:25 +0800, Chen Pei wrote:
> + if (is_cxl && cfg->cxl_mmio32.size) {
> + uint64_t cxl_base = cfg->cxl_mmio32.base;
> + uint64_t cxl_size = cfg->cxl_mmio32.size;
> + crs = aml_resource_template();
> + /* 32-bit MMIO range for CXL devices */
> + aml_append(crs,
> + aml_dword_memory(AML_POS_DECODE, AML_MIN_FIXED,
> + AML_MAX_FIXED, AML_NON_CACHEABLE,
> + AML_READ_WRITE, 0,
> + cxl_base, cxl_base + cxl_size - 1,
> + 0, cxl_size));
cfg->cxl_mmio32 is a single range on the main GPEX host, but this block
runs once per CXL host bridge in the QLIST_FOREACH(bus, &bus->child)
loop, so with more than one pxb-cxl every ACPI0016 gets a _CRS that
declares the *same* [cxl_base, cxl_base + 256 MiB) window.
I realise build_crs() returns nothing usable for these bridges at
ACPI-build time (the BARs aren't assigned yet, per your commit message),
which is why the static path exists -- but it hands that one shared
cfg->cxl_mmio32 to every CXL bridge.
I checked the generated DSDT for a two-pxb-cxl riscv64 config (cxl.1 at
bus_nr=12, cxl.2 at bus_nr=200): both ACPI0016 host bridges (PC0C and
PCC8) come up with the identical producer window
DWordMemory (ResourceProducer, PosDecode, MinFixed, MaxFixed, ...)
0x70000000, // Range Minimum
0x7FFFFFFF, // Range Maximum
0x10000000, // Length
while PCI0 is correctly shrunk to 0x40000000-0x6FFFFFFF. That 256 MiB
window is the only memory range either ACPI0016 advertises, so every BAR
behind the two bridges has to come from it; and since two host bridges
can't both decode the same range, at most one of them can actually
claim it. Multiple pxb-cxl is a supported topology in QEMU generally.
Partitioning the window per bridge may be one way out, but whether
that's the right model is your call -- is supporting more than one
pxb-cxl in scope for this series, or is a single CXL host bridge the
intended target for now? If the latter, a note to that effect in the
commit message would help.
One smaller thing about the hand-built template: the bus range is
hard-coded to [bus_num, bus_num + 15], so with cxl.1 at bus_nr=12 and
cxl.2 at bus_nr=16 the two _CRS come out as [0x0C,0x1B] and [0x10,0x1F],
which overlap on buses 0x10-0x1B. The real width depends on how many
buses the pxb-cxl spans, which isn't known at ACPI-build time (pxb-cxl
only carries a bus_nr). build_crs() is no drop-in here either: with the
static path disabled I saw the range collapse to a single bus
[bus_num, bus_num], since no subordinate buses are enumerated yet -- so
it under-sizes rather than over-sizes.
Both really trace back to the same spot: the static branch synthesises
the bridge's window and bus range from machine-global constants
(cfg->cxl_mmio32 and the fixed +15) rather than anything per-bridge, so
neither scales past one pxb-cxl. Going static is fair enough given
build_crs() has nothing to offer at build time -- as above, it supplies
neither the window nor a usable bus range -- so the real question is
just where the per-bridge window size and bus span should come from.
Many thanks,
Junjie
^ permalink raw reply [flat|nested] 16+ messages in thread
* Re: [PATCH v2 3/4] hw/riscv/virt,gpex: Provide 32-bit MMIO window for CXL host bridges
2026-07-22 13:52 ` Junjie Cao
@ 2026-07-24 12:10 ` Chen Pei
0 siblings, 0 replies; 16+ messages in thread
From: Chen Pei @ 2026-07-24 12:10 UTC (permalink / raw)
To: Junjie Cao
Cc: jic23, pbonzini, palmer, alistair.francis, liwei1518,
daniel.barboza, zhiwei_liu, chao.liu.zevorn, sunilvl, dave.jiang,
alison.schofield, imammedo, mst, guoren, qemu-riscv, qemu-devel,
linux-cxl
Hi Junjie,
Thanks for the thorough review.
> cfg->cxl_mmio32 is a single range on the main GPEX host, but this block
> runs once per CXL host bridge in the QLIST_FOREACH(bus, &bus->child)
> loop, so with more than one pxb-cxl every ACPI0016 gets a _CRS that
> declares the *same* [cxl_base, cxl_base + 256 MiB) window.
You're right. The static path synthesises the bridge window from the
machine-global cfg->cxl_mmio32 rather than per-bridge state, so it
does not scale past one pxb-cxl.
> is supporting more than one pxb-cxl in scope for this series, or is a
> single CXL host bridge the intended target for now? If the latter, a
> note to that effect in the commit message would help.
For this series the intended target is a single CXL host bridge,
which matches the bios-tables test case (one pxb-cxl at bus_nr=12).
I'll add a note to the commit message in v3.
> One smaller thing about the hand-built template: the bus range is
> hard-coded to [bus_num, bus_num + 15], so with cxl.1 at bus_nr=12 and
> cxl.2 at bus_nr=16 the two _CRS come out as [0x0C,0x1B] and [0x10,0x1F],
> which overlap on buses 0x10-0x1B.
This is a related symptom of the same root cause: pxb-cxl only carries
a bus_nr and the actual subordinate bus count is not known at
ACPI-build time, so neither build_crs() nor a static constant can
produce a correct span for the multi-bridge case. The +15 heuristic
is borrowed from the ARM virt machine's gpex handling where a single
pxb is the norm.
> Both really trace back to the same spot: the static branch synthesises
> the bridge's window and bus range from machine-global constants
> (cfg->cxl_mmio32 and the fixed +15) rather than anything per-bridge, so
> neither scales past one pxb-cxl.
Agreed. I'll add a TODO comment at the static branch in gpex-acpi.c
to flag this for future work, and document the single-bridge limitation
in the commit message as noted above.
Thanks,
Pei
^ permalink raw reply [flat|nested] 16+ messages in thread
* Re: [PATCH v2 3/4] hw/riscv/virt,gpex: Provide 32-bit MMIO window for CXL host bridges
2026-06-18 9:38 ` [PATCH v2 3/4] hw/riscv/virt,gpex: Provide 32-bit MMIO window for CXL host bridges Chen Pei
2026-06-24 17:21 ` Daniel Henrique Barboza
2026-07-22 13:52 ` Junjie Cao
@ 2026-07-27 9:06 ` Igor Mammedov
2 siblings, 0 replies; 16+ messages in thread
From: Igor Mammedov @ 2026-07-27 9:06 UTC (permalink / raw)
To: Chen Pei
Cc: jic23, pbonzini, palmer, alistair.francis, liwei1518,
daniel.barboza, zhiwei_liu, chao.liu.zevorn, sunilvl, dave.jiang,
alison.schofield, mst, guoren, qemu-riscv, qemu-devel, linux-cxl
On Thu, 18 Jun 2026 17:38:25 +0800
Chen Pei <cp0613@linux.alibaba.com> wrote:
> 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.
> Marking the BARs prefetchable would work around it, but the CXL
> component registers have read/write side effects and are not
> prefetchable per the PCIe specification.
>
> Reserve the top 256 MiB of the 32-bit MMIO window exclusively for
> CXL host bridges:
> - Shrink PCI0's mmio32 window by 256 MiB in virt.c so that UEFI's
> PciHostBridgeDxe and the ACPI _CRS for PCI0 never claim that range
> - Store the reserved range in a new gpex_cfg.cxl_mmio32 field
> - In gpex-acpi.c, emit the cxl_mmio32 range as the Memory resource
> in the CXL host bridge _CRS instead of re-using build_crs() (which
> returns an empty set when UEFI has not assigned resources yet)
> - Reduce the FDT 'ranges' for PCI0 by the same 256 MiB so that UEFI
> firmware driven by device-tree also respects the reservation
>
> Signed-off-by: Chen Pei <cp0613@linux.alibaba.com>
> ---
> hw/pci-host/gpex-acpi.c | 36 +++++++++++++++++++++--
> hw/riscv/virt.c | 58 +++++++++++++++++++++++++++++++-------
> include/hw/pci-host/gpex.h | 1 +
> 3 files changed, 83 insertions(+), 12 deletions(-)
>
> diff --git a/hw/pci-host/gpex-acpi.c b/hw/pci-host/gpex-acpi.c
> index d9820f9b41..d8b943b665 100644
> --- a/hw/pci-host/gpex-acpi.c
> +++ b/hw/pci-host/gpex-acpi.c
> @@ -158,9 +158,41 @@ void acpi_dsdt_add_gpex(Aml *scope, struct GPEXConfig *cfg)
> * Resources defined for PXBs are composed of the following parts:
> * 1. The resources the pci-bridge/pcie-root-port need.
> * 2. The resources the devices behind pxb need.
> + *
> + * For CXL host bridges on platforms where UEFI (driven by
> + * FDT 'ranges') does not assign PCI resources for the CXL
> + * root bridge before ACPI table construction, build_crs()
I kind of dislike (basically riscv specific hack) in generic code.
well, correct way to tackle this is
1: fix UEFI to perform required initialization.
> + * would return an empty resource set. When the platform
> + * has reserved a dedicated MMIO window for CXL host bridges
> + * (cfg->cxl_mmio32), emit that window as a static _CRS
> + * instead. The platform is responsible for shrinking PCI0's
> + * mmio32 window so the two do not overlap.
if you have to put a hack into QEMU, make it work with current build_crs()
and localize 'proper simulated initialization' in riscv codebase
(i.e. do not spread it to common codebase). But before doing it see (#1 note).
> */
> - crs = build_crs(PCI_HOST_BRIDGE(BUS(bus)->parent), &crs_range_set,
> - cfg->pio.base, 0, 0, 0);
> + if (is_cxl && cfg->cxl_mmio32.size) {
> + uint64_t cxl_base = cfg->cxl_mmio32.base;
> + uint64_t cxl_size = cfg->cxl_mmio32.size;
> +
> + crs = aml_resource_template();
> +
> + /* 32-bit MMIO range for CXL devices */
> + aml_append(crs,
> + aml_dword_memory(AML_POS_DECODE, AML_MIN_FIXED,
> + AML_MAX_FIXED, AML_NON_CACHEABLE,
> + AML_READ_WRITE, 0,
> + cxl_base,
> + cxl_base + cxl_size - 1,
> + 0, cxl_size));
> +
> + /* Bus number range */
> + aml_append(crs,
> + aml_word_bus_number(AML_MIN_FIXED, AML_MAX_FIXED,
> + AML_POS_DECODE, 0,
> + bus_num, bus_num + 15,
> + 0, 16));
> + } else {
> + crs = build_crs(PCI_HOST_BRIDGE(BUS(bus)->parent),
> + &crs_range_set, cfg->pio.base, 0, 0, 0);
> + }
> aml_append(dev, aml_name_decl("_CRS", crs));
>
> if (is_cxl) {
> diff --git a/hw/riscv/virt.c b/hw/riscv/virt.c
> index 84b91b4322..9c1a001553 100644
> --- a/hw/riscv/virt.c
> +++ b/hw/riscv/virt.c
> @@ -113,6 +113,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)
> @@ -890,15 +893,28 @@ static void create_fdt_pcie(RISCVVirtState *s,
> }
> qemu_fdt_setprop_sized_cells(ms->fdt, name, "reg", 2,
> s->memmap[VIRT_PCIE_ECAM].base, 2, s->memmap[VIRT_PCIE_ECAM].size);
> - qemu_fdt_setprop_sized_cells(ms->fdt, name, "ranges",
> - 1, FDT_PCI_RANGE_IOPORT, 2, 0,
> - 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,
> - 1, FDT_PCI_RANGE_MMIO_64BIT,
> - 2, virt_high_pcie_memmap.base,
> - 2, virt_high_pcie_memmap.base, 2, virt_high_pcie_memmap.size);
> + {
> + /*
> + * 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;
> + }
> + qemu_fdt_setprop_sized_cells(ms->fdt, name, "ranges",
> + 1, FDT_PCI_RANGE_IOPORT, 2, 0,
> + 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, 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);
> + }
>
> if (virt_is_iommu_sys_enabled(s)) {
> qemu_fdt_setprop_cells(ms->fdt, name, "iommu-map",
> @@ -1728,7 +1744,29 @@ static void virt_machine_init(MachineState *machine)
> qdev_get_gpio_in(virtio_irqchip, VIRTIO_IRQ + i));
> }
>
> - gpex_pcie_init(system_memory, pcie_irqchip, s);
> + DeviceState *pcie_dev = gpex_pcie_init(system_memory, pcie_irqchip, s);
> +
> + /*
> + * If CXL is enabled, reserve the last 256 MiB of the 32-bit MMIO
> + * window for CXL host bridges so the bridge non-prefetchable window
> + * can hold CXL device BARs (component registers and similar 64-bit
> + * non-prefetchable BARs that need a < 4 GiB address).
> + *
> + * - Shrink PCI0's mmio32 advertised in the ACPI _CRS by the same
> + * 256 MiB so the two ranges do not overlap (the FDT 'ranges'
> + * shrink happens in create_fdt_pcie()).
> + * - Store the reserved range in cxl_mmio32 so gpex-acpi.c can emit
> + * a correct _CRS for the CXL host bridge (ACPI0016).
> + */
> + if (s->cxl_devices_state.is_enabled) {
> + GPEXHost *gpex = GPEX_HOST(pcie_dev);
> + gpex->gpex_cfg.cxl_mmio32.size = VIRT_CXL_MMIO32_SIZE;
> + gpex->gpex_cfg.cxl_mmio32.base =
> + s->memmap[VIRT_PCIE_MMIO].base +
> + s->memmap[VIRT_PCIE_MMIO].size - VIRT_CXL_MMIO32_SIZE;
> + /* Shrink PCI0's advertised 32-bit MMIO window to exclude CXL range */
> + gpex->gpex_cfg.mmio32.size -= VIRT_CXL_MMIO32_SIZE;
> + }
>
> create_platform_bus(s, mmio_irqchip);
>
> diff --git a/include/hw/pci-host/gpex.h b/include/hw/pci-host/gpex.h
> index 1da9c85bce..d38fbbacd6 100644
> --- a/include/hw/pci-host/gpex.h
> +++ b/include/hw/pci-host/gpex.h
> @@ -43,6 +43,7 @@ struct GPEXConfig {
> MemMapEntry mmio32;
> MemMapEntry mmio64;
> MemMapEntry pio;
> + MemMapEntry cxl_mmio32;
> int irq;
> PCIBus *bus;
> bool pci_native_hotplug;
^ permalink raw reply [flat|nested] 16+ messages in thread
end of thread, other threads:[~2026-07-27 9:06 UTC | newest]
Thread overview: 16+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-06-18 9:38 [PATCH v2 0/4] hw/riscv/virt: Add CXL support to the RISC-V virt machine Chen Pei
2026-06-18 9:38 ` [PATCH v2 1/4] " Chen Pei
2026-06-24 16:42 ` Daniel Henrique Barboza
2026-06-29 9:06 ` Chen Pei
2026-06-18 9:38 ` [PATCH v2 2/4] hw/riscv/virt-acpi-build: Add _DEP to ACPI0017 for CXL host bridge dependency Chen Pei
2026-07-06 4:08 ` Alistair Francis
2026-07-07 11:50 ` Chen Pei
2026-07-09 3:56 ` Alistair Francis
2026-06-18 9:38 ` [PATCH v2 3/4] hw/riscv/virt,gpex: Provide 32-bit MMIO window for CXL host bridges Chen Pei
2026-06-24 17:21 ` Daniel Henrique Barboza
2026-06-29 9:10 ` Chen Pei
2026-07-22 13:52 ` Junjie Cao
2026-07-24 12:10 ` Chen Pei
2026-07-27 9:06 ` Igor Mammedov
2026-06-18 9:38 ` [PATCH v2 4/4] tests/qtest: Add RISC-V ACPI bios tables test for CXL Chen Pei
2026-07-06 4:10 ` Alistair Francis
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox