> -----Original Message-----
> From: Cédric Le Goater <[email protected]>
> Sent: Thursday, September 17, 2026 9:30 PM
> To: Manish Honap <[email protected]>; [email protected]; Ankit Agrawal
> <[email protected]>; [email protected]; [email protected];
> [email protected]; Srirangan Madhavan
> <[email protected]>; [email protected];
> [email protected]; [email protected]; [email protected];
> [email protected]; [email protected]; [email protected];
> [email protected]; [email protected]; Nitesh Narayan Lal
> <[email protected]>
> Cc: Krishnakant Jaju <[email protected]>; Vikram Sethi <[email protected]>;
> Zhi Wang <[email protected]>; [email protected]; qemu-
> [email protected]; [email protected]
> Subject: Re: [PATCH v2 06/10] hw/vfio/pci: Bind a CXL device to its fixed
> memory window
>
> External email: Use caution opening links or attachments
>
>
> On 9/16/26 20:44, [email protected] wrote:
> > From: Manish Honap <[email protected]>
> >
> > The guest programs a GPA inside a CXL fixed memory window and QEMU
> > maps the HDM memory there, so the device must sit under exactly one
> > single-target CFMWS.
>
> CFMWS (CXL Fixed Memory Window Structure). Pity that the fields are named
> fmws_base, fmws_size.
>
> > Reject any ambiguous, interleaved, unsized, or missing window rather
> > than guess. Match windows by target name, since the resolved target
> > pointers are only filled in by a machine-done notifier that may run
> > after this one.
> >
> > Require exactly one endpoint function below the root port: two
> > functions in the same slot would each match that single-target CFMWS
> > and alias their HDM memory at one base, so count every present
> > function rather than one per slot. The CFMWS windows are placed at
> > machine-init-done, so a cold-plugged device can only be validated from
> > that notifier, where a bad topology is fatal to startup. Validate a
> > hotplugged device inline from realize instead, reporting through errp
> > so a bad device_add fails cleanly rather than aborting the running VM,
> > and arm the notifier only for a cold-plugged device.
> >
> > The endpoint's HDM size comes from the kernel region.
> > cxl_fmws_set_memmap() places the window in the guest PA map before
> > this device is realized, so the window cannot yet be sized from the
> > device; check the configured window against the device geometry
> > instead. Reject a window too small to hold the endpoint and name the
> > size to set, and warn on an oversized window, since the padding becomes
> guest CEDT that migration has to preserve.
>
> I am lost ... Too much stuff there.
>
> Each paragraph introduces a new topic/problem, and implementation details,
> all mixed up, without first explaining what problem is being solved:
>
> A passed-through CXL Type-2 device needs to know where its memory
> window lives in the guest PA space so QEMU can map the device memory
> there.
>
> Is that it ?
>
> A maintainer reviewing this patch shouldn't need to know what a CFMWS is to
> understand the commit. CXL is still an emerging technology.
> Most VFIO and QEMU reviewers will not have CXL spec background and CXL-
> specific mechanic knowledge.
>
> > AI-used-for: code (prototype)
>
> The code still reads like a proof-of-concept "prototype". This patch in
> particular is very hard to follow. The problem isn't clearly described, and
> likely
> isn't fully understood yet. The series should be broken into smaller patches.
>
> > Signed-off-by: Manish Honap <[email protected]>
> > ---
> > hw/pci-bridge/pci_expander_bridge_stubs.c | 6 +
> > hw/vfio/pci.c | 192 ++++++++++++++++++++++
> > hw/vfio/pci.h | 3 +
> > 3 files changed, 201 insertions(+)
> >
> > diff --git a/hw/pci-bridge/pci_expander_bridge_stubs.c
> > b/hw/pci-bridge/pci_expander_bridge_stubs.c
> > index b35180311f..c44ad7fab6 100644
> > --- a/hw/pci-bridge/pci_expander_bridge_stubs.c
> > +++ b/hw/pci-bridge/pci_expander_bridge_stubs.c
> > @@ -10,5 +10,11 @@
> > #include "hw/pci/pci_bus.h"
> > #include "hw/pci-bridge/pci_expander_bridge.h"
> > #include "hw/cxl/cxl.h"
> > +#include "hw/cxl/cxl_component.h"
> >
> > void pxb_cxl_hook_up_registers(CXLState *state, PCIBus *bus, Error
> > **errp) {};
> > +
> > +bool cxl_get_hb_passthrough(PCIHostState *hb) {
> > + return false;
> > +}
> > diff --git a/hw/vfio/pci.c b/hw/vfio/pci.c index
> > 4716266595..670e0d1da4 100644
> > --- a/hw/vfio/pci.c
> > +++ b/hw/vfio/pci.c
> > @@ -27,7 +27,12 @@
> > #include "hw/pci/msi.h"
> > #include "hw/pci/msix.h"
> > #include "hw/pci/pci_bridge.h"
> > +#include "hw/pci/pci_host.h"
> > +#include "hw/pci/pcie_port.h"
> > #include "hw/cxl/cxl.h"
> > +#include "hw/cxl/cxl_host.h"
> > +#include "hw/cxl/cxl_component.h"
> > +#include "system/system.h"
> > #include "hw/core/qdev-properties.h"
> > #include "hw/core/qdev-properties-system.h"
> > #include "hw/vfio/vfio-cpr.h"
> > @@ -3641,6 +3646,171 @@ static bool
> vfio_cxl_check_topology(VFIOPCIDevice *vdev, Error **errp)
> > return true;
> > }
> >
> > +/*
> > + * Count the CXL fixed memory windows that target this device's
> > +pxb-cxl and
> > + * report the matched window's base, size and target count. Match on
> > +the target
> > + * names: cxl_fmws_link_targets() resolves target_hbs[] only at
> > +machine_done,
> > + * which may run after this, so the resolved pointers can still be NULL
> > here.
> > + */
> > +static int vfio_cxl_match_fmws(PXBCXLDev *pxb, hwaddr *base, uint64_t
> *size,
> > + int *nwindows, int *ntargets)
>
> I think this should be in the CXL subsystem. That's a *lot* of out parameters
> ...
>
> > +{
> > + GSList *list = cxl_fmws_get_all_sorted();
> > + GSList *iter;
> > + int matches = 0;
> > +
> > + *nwindows = g_slist_length(list);
> > + *base = 0;
> > + *size = 0;
> > + *ntargets = 0;
> > +
> > + for (iter = list; iter; iter = iter->next) {
> > + CXLFixedWindow *fw = CXL_FMW(iter->data);
> > + int i;
> > +
> > + for (i = 0; i < fw->num_targets; i++) {
> > + bool ambiguous = false;
> > + Object *t = object_resolve_path_type(fw->targets[i],
> > + TYPE_PXB_CXL_DEV,
> > + &ambiguous);
> > +
> > + if (t && !ambiguous && PXB_CXL_DEV(t) == pxb) {
> > + *base = fw->base;
> > + *size = fw->size;
> > + *ntargets = fw->num_targets;
> > + matches++;
> > + break;
> > + }
> > + }
> > + }
> > + g_slist_free(list);
> > + return matches;
> > +}
> > +
> > +/*
> > + * Fix the bounds of the device's memory window once the topology has
> settled.
> > + * Exactly one single-target CFMWS, with an assigned base and room
> > +for the HDM
> > + * memory, is required; anything else is a misconfiguration the guest
> > +cannot
> > + * recover from, so reject it rather than guess a window.
> > + */
> > +static bool vfio_cxl_do_bind_fmws(VFIOPCIDevice *vdev, Error **errp)
> > +{
>
> This does too much :
>
> topology validation
> CFMWS window matching
> size validation
>
> It looks like vfio_cxl_check_topology() and it should be in the CXL subsystem.
>
> > + VFIOCXL *cxl = &vdev->cxl;
> > + const char *name = vdev->vbasedev.name;
> > + int nwindows = 0, ntargets = 0, matches;
> > + hwaddr base = 0;
> > + uint64_t size = 0, need;
> > + PXBCXLDev *pxb;
> > +
> > + pxb = vfio_cxl_find_pxb(vdev, errp);
> > + if (!pxb) {
> > + return false;
> > + }
> > +
> > + if
> > + (pcie_count_ds_ports(PCI_HOST_BRIDGE(pxb->cxl_host_bridge)->bus) !=
> > + 1) {
>
> This won't build on all platforms.
>
> > + error_setg(errp,
> > + "vfio-cxl: %s: pxb-cxl not in HDM passthrough mode "
> > + "(use a single cxl-rp)", name);
> > + return false;
> > + }
> > +
> > + /*
> > + * Count every present function, not one per slot, and require exactly
> > one
> > + * endpoint below the root port.
> > + */> + {
> > + PCIBus *ep_bus = pci_get_bus(&vdev->parent_obj);
> > + int slot, fn, nendpoints = 0;
>
> This smells like a function.
>
> > + for (slot = 0; slot < PCI_SLOT_MAX; slot++) {
> > + for (fn = 0; fn < PCI_FUNC_MAX; fn++) {
> > + if (ep_bus->devices[PCI_DEVFN(slot, fn)]) {
> > + nendpoints++;
> > + }
> > + }
> > + }
> > + if (nendpoints != 1) {
> > + error_setg(errp,
> > + "vfio-cxl: %s: %d devices below the cxl-rp; a
> > passed "
> > + "through CXL endpoint must be alone below its root
> > port",
> > + name, nendpoints);
> > + return false;
> > + }
> > + }
> > +
> > + matches = vfio_cxl_match_fmws(pxb, &base, &size, &nwindows,
> &ntargets);
> > + if (matches == 1 && ntargets == 1) {
> > + if (!base) {
> > + error_setg(errp,
> > + "vfio-cxl: %s: matched CFMWS has no base; reduce
> > its "
> > + "size or grow the guest PA space", name);
> > + return false;
> > + }
> > + /*
> > + * QEMU reads the endpoint's HDM size from the kernel region, so
> > the
> > + * window size is really device information, not a value to make
> > the
> > + * caller guess. Sizing the CFMWS from the device the way firmware
> does
> > + * is not possible here: the window is placed in the guest PA map
> > by
> > + * cxl_fmws_set_memmap() before this device is realized, so its
> > size is
> > + * fixed before dpa_size is known. Until a core change can size the
> > + * window from the device, take the device geometry as the source
> > of
> > + * truth: reject a window too small to hold the endpoint, and warn
> when
> > + * it is larger than needed, since the padding becomes guest CEDT
> > that
> > + * migration has to preserve.
> > + */
> > + need = ROUND_UP(cxl->dpa_size, 256 * MiB);
> > + if (size < need) {
> > + error_setg(errp,
> > + "vfio-cxl: %s: CFMWS size 0x%" PRIx64 " cannot hold
> > the "
> > + "endpoint; set the cxl-fmw size to 0x%" PRIx64,
> > + name, size, need);
> > + return false;
> > + }
> > + if (size > need) {
> > + warn_report("vfio-cxl: %s: CFMWS size 0x%" PRIx64 " exceeds
> > the "
> > + "endpoint's 0x%" PRIx64 "; set the cxl-fmw size to
> > 0x%"
> > + PRIx64 " to keep the guest memory map stable
> > across "
> > + "migration", name, size, cxl->dpa_size, need);
> > + }
> > + cxl->fmws_base = base;
> > + cxl->fmws_size = size;
> > + return true;
> > + }
> > +
> > + if (matches == 1) {
> > + error_setg(errp,
> > + "vfio-cxl: %s: its CFMWS interleaves %d targets; use a "
> > + "single-target window", name, ntargets);
> > + } else if (matches > 1) {
> > + error_setg(errp,
> > + "vfio-cxl: %s: pxb-cxl is targeted by %d CFMWS; use
> > one",
> > + name, matches);
> > + } else {
> > + error_setg(errp,
> > + "vfio-cxl: %s: no CFMWS targets this device (%d
> > present)",
> > + name, nwindows);
> > + }
> > + return false;
> > +}
> > +
> > +/*
> > + * Cold-plug path: the CFMWS windows are placed at machine init done,
> > +so the
>
> are you sure of that ? I think fw->base is already set when vfio_cxl_setup()
> runs for cold-plug.
>
> Normal vfio-pci has no hotplug-specific code, vfio_pci_realize() runs the same
> path regardless of DEVICE(vdev)->hotplugged. The CXL case should be the
> same.
>
> > + * binding can only be validated from this notifier. The VM has not
> > +run yet, so
> > + * a configuration error is fatal to startup. A hotplugged device is
> > +validated
> > + * in realize instead (see vfio_cxl_setup), where the failure fails
> > +device_add
> > + * without taking down the running VM.
> > + */
> > +static void vfio_cxl_bind_fmws(Notifier *n, void *data) {
> > + VFIOCXL *cxl = container_of(n, VFIOCXL, machine_done);
> > + VFIOPCIDevice *vdev = container_of(cxl, VFIOPCIDevice, cxl);
> > + Error *err = NULL;
> > +
> > + if (!vfio_cxl_do_bind_fmws(vdev, &err)) {
> > + error_report_err(err);
> > + exit(1);
> > + }
> > +}
> > +
> > /*
> > * Learn the CXL geometry the kernel reports: the HPA-backed HDM memory
> region
> > * and the trapped HDM decoder block (which BAR carries it and at what
> offset).
> > @@ -3717,6 +3887,22 @@ static bool vfio_cxl_setup(VFIOPCIDevice *vdev,
> Error **errp)
> > "performance may be slow", vbasedev->name);
> > }
> >
> > + if (DEVICE(vdev)->hotplugged) {
> > + /*
> > + * The machine is already up, so the CFMWS windows are placed and
> the
> > + * binding can be validated now. Report a failure through errp so
> > a bad
> > + * device_add fails cleanly instead of aborting the running VM.
> > + */
> > + if (!vfio_cxl_do_bind_fmws(vdev, errp)) {
> > + vfio_region_exit(&cxl->mem_region);
> > + vfio_region_finalize(&cxl->mem_region);
>
> as said before, the tear down has 2 parts. Anyhow, I don't think we need a
> CXL-specific hotplug patch.
>
I Agree this patch is doing too much. I will break this patch into topology
check, window match, size validation, and the suggested fixes in this
thread. I will start the commit message with the problem description and define
the relevant terms (e.g. CFMWS etc.), so a reviewer without CXL background can
follow it.
> C.
>
>
>
> > + return false;
> > + }
> > + } else {
> > + cxl->machine_done.notify = vfio_cxl_bind_fmws;
> > + qemu_add_machine_init_done_notifier(&cxl->machine_done);
> > + }
> > +
> > cxl->enabled = true;
> >
> > return true;
> > @@ -3729,6 +3915,12 @@ static void vfio_cxl_teardown(VFIOPCIDevice
> *vdev)
> > if (!cxl->enabled) {
> > return;
> > }
> > +
> > + if (cxl->machine_done.notify) {
> > + qemu_remove_machine_init_done_notifier(&cxl->machine_done);
> > + cxl->machine_done.notify = NULL;
> > + }
> > +
> > if (cxl->mem_region.mem) {
> > vfio_region_exit(&cxl->mem_region);
> > vfio_region_finalize(&cxl->mem_region);
> > diff --git a/hw/vfio/pci.h b/hw/vfio/pci.h index
> > 06d15e807d..22fe8d7ff7 100644
> > --- a/hw/vfio/pci.h
> > +++ b/hw/vfio/pci.h
> > @@ -135,6 +135,9 @@ typedef struct VFIOCXL {
> > uint64_t hdm_offset; /* block offset within the component
> > BAR */
> > uint64_t dpa_size; /* size of the HDM memory region */
> > VFIORegion mem_region; /* HDM memory, mapped at committed
> GPA */
> > + Notifier machine_done; /* CFMWS validated at machine_done */
> > + hwaddr fmws_base; /* base of the memory window */
> > + uint64_t fmws_size; /* size of the memory window */
> > } VFIOCXL;
> >
> > struct VFIOPCIDevice {