From: <mhonap@nvidia.com>
To: <alex@shazbot.org>, <jgg@ziepe.ca>, <ankita@nvidia.com>,
<jic23@kernel.org>, <dave.jiang@intel.com>,
<alejandro.lucero-palau@amd.com>, <smadhavan@nvidia.com>,
<corbet@lwn.net>, <skhan@linuxfoundation.org>,
<dave@stgolabs.net>, <alison.schofield@intel.com>,
<vishal.l.verma@intel.com>, <iweiny@kernel.org>,
<ming.li@zohomail.com>, <yishaih@nvidia.com>,
<skolothumtho@nvidia.com>, <kevin.tian@intel.com>,
<bhelgaas@google.com>, <dmatlack@google.com>, <kees@kernel.org>,
<gustavoars@kernel.org>
Cc: <cjia@nvidia.com>, <kjaju@nvidia.com>, <vsethi@nvidia.com>,
<zhiw@nvidia.com>, <mhonap@nvidia.com>,
<linux-doc@vger.kernel.org>, <linux-kernel@vger.kernel.org>,
<kvm@vger.kernel.org>, <linux-cxl@vger.kernel.org>,
<linux-pci@vger.kernel.org>, <linux-kselftest@vger.kernel.org>,
<linux-hardening@vger.kernel.org>
Subject: [PATCH v6 11/31] vfio/pci: Virtualize the CXL DVSEC in vfio_pci_config.c
Date: Fri, 16 Oct 2026 21:48:18 +0530 [thread overview]
Message-ID: <20261016161839.2765592-12-mhonap@nvidia.com> (raw)
In-Reply-To: <20261016161839.2765592-1-mhonap@nvidia.com>
From: Manish Honap <mhonap@nvidia.com>
A CXL Type-2 device is reprogrammable through its CXL DVSEC: a guest could
set Config Lock, toggle CXL.cache and CXL.mem enable, or rewrite the HDM
range registers that govern host memory decode. Virtualize the DVSEC so
the guest sees a shadow it cannot use to reprogram the hardware.
Build a per-device cxl_perm permission map, modeled on msi_perm. When
the device is bound through the CXL provider (e.g. vfio-cxl), the whole
CXL DVSEC is served from the vconfig shadow; only Control and Control2
are guest programmable, while Capability, Status, Lock and the Range
registers keep their firmware snapshot. A vendor DVSEC on the same
device is unaffected: the map is selected by the CXL DVSEC's range,
since every DVSEC has the same ID in the config map and two DVSECs can
be adjacent.
A Type-2 function bound without the provider (the provider is absent,
its init failed, or the extensions are disabled) gets a guard map
instead. The DVSEC reads from hardware, Control and Control2 stay
writable and the RW1C status bits stay clearable, but writes to Lock and
the Range registers are dropped, since they outlive the user. Initiate
CXL Reset uses the same doorbell; a cache write-back goes to the device.
Control2 carries the CXL reset and cache write-back-invalidate initiate
bits as self-clearing doorbells. With the provider, vfio never forwards
them to hardware, so a custom writefn synthesizes their completion in
the shadow: the initiate bit self-clears and the matching Status2 bit
(Cache Invalid or Reset Done) is set, so a guest that writes back its
cache (CXL r4.0 sec 9.6) and then runs CXL Reset (sec 9.7.2) progresses
instead of timing out. Cache Invalid is reported only while Disable
Caching is set, as the spec requires. The host performs the real cache
write-back and reset at the vfio reset points. Until a later patch in
this series runs the real reset from the doorbell, Initiate CXL Reset
completes in the shadow only.
Assisted-by: LLM
Signed-off-by: Manish Honap <mhonap@nvidia.com>
---
drivers/vfio/pci/vfio_pci_config.c | 206 ++++++++++++++++++++++++++++-
include/linux/vfio_pci_core.h | 3 +
2 files changed, 205 insertions(+), 4 deletions(-)
diff --git a/drivers/vfio/pci/vfio_pci_config.c b/drivers/vfio/pci/vfio_pci_config.c
index 9914f3ac69ae..1fc463540211 100644
--- a/drivers/vfio/pci/vfio_pci_config.c
+++ b/drivers/vfio/pci/vfio_pci_config.c
@@ -1747,6 +1747,194 @@ static const struct pci_device_id known_bogus_vf_intx_pin[] = {
* for each area requiring emulated bits, but the array of pointers
* would be comparable in size (at least for standard config space).
*/
+static int vfio_cxl_dvsec_write(struct vfio_pci_core_device *vdev, int pos,
+ int count, struct perm_bits *perm,
+ int offset, __le32 val)
+{
+ u16 start = vdev->cxl_dvsec;
+ __le16 *pctrl2, *pstatus2;
+ u16 ctrl2, status2;
+
+ count = vfio_default_config_write(vdev, pos, count, perm, offset, val);
+ if (count < 0)
+ return count;
+
+ /* Only Control2 carries the self-clearing doorbells. */
+ if (offset > PCI_DVSEC_CXL_CTRL2 ||
+ offset + count <= PCI_DVSEC_CXL_CTRL2)
+ return count;
+
+ pctrl2 = (__le16 *)&vdev->vconfig[start + PCI_DVSEC_CXL_CTRL2];
+ pstatus2 = (__le16 *)&vdev->vconfig[start + PCI_DVSEC_CXL_STATUS2];
+ ctrl2 = le16_to_cpu(*pctrl2);
+ status2 = le16_to_cpu(*pstatus2);
+
+ /*
+ * INIT_CACHE_WBI and INIT_CXL_RST are self-clearing doorbells. With a
+ * provider neither reaches hardware: synthesize their completion in the
+ * shadow so the guest poll finishes, clearing the initiate bit and
+ * setting the matching Status2 completion. Cache Invalid is set only
+ * while caching is disabled and reads 0 otherwise (CXL r4.0 sec 8.1.3.5,
+ * Table 8-9). Under the guard map INIT_CACHE_WBI and Cache Invalid go to
+ * the device; only INIT_CXL_RST and its completion are virtualized.
+ */
+ if (ctrl2 & PCI_DVSEC_CXL_INIT_CACHE_WBI) {
+ ctrl2 &= ~PCI_DVSEC_CXL_INIT_CACHE_WBI;
+ if (ctrl2 & PCI_DVSEC_CXL_DISABLE_CACHING)
+ status2 |= PCI_DVSEC_CXL_CACHE_INV;
+ }
+ if (!(ctrl2 & PCI_DVSEC_CXL_DISABLE_CACHING))
+ status2 &= ~PCI_DVSEC_CXL_CACHE_INV;
+ if (ctrl2 & PCI_DVSEC_CXL_INIT_CXL_RST) {
+ ctrl2 &= ~PCI_DVSEC_CXL_INIT_CXL_RST;
+ status2 &= ~PCI_DVSEC_CXL_RST_ERR;
+ status2 |= PCI_DVSEC_CXL_RST_DONE;
+ }
+
+ *pctrl2 = cpu_to_le16(ctrl2);
+ *pstatus2 = cpu_to_le16(status2);
+
+ return count;
+}
+
+static int init_cxl_dvsec_perm(struct perm_bits *perm, int len)
+{
+ if (alloc_perm_bits(perm, len))
+ return -ENOMEM;
+
+ perm->writefn = vfio_cxl_dvsec_write;
+
+ /* Serve the whole CXL DVSEC from the shadow. */
+ for (int i = 0; i < len; i++)
+ p_setb(perm, i, (u8)ALL_VIRT, NO_WRITE);
+
+ /*
+ * Control and Control2 are guest programmable; Capability, Status,
+ * Lock and the Range registers keep their firmware snapshot, so the
+ * guest cannot set Config Lock or rewrite the capability and ranges.
+ */
+ p_setw(perm, PCI_DVSEC_CXL_CTRL, (u16)ALL_VIRT, (u16)ALL_WRITE);
+ p_setw(perm, PCI_DVSEC_CXL_CTRL2, (u16)ALL_VIRT, (u16)ALL_WRITE);
+
+ return 0;
+}
+
+/*
+ * Without a CXL provider the device is bound without the CXL extensions, so
+ * the DVSEC is read from hardware, Control and Control2 stay writable, and
+ * the RW1C status bits in Status and Status2 (for example Viral_Status) can
+ * still be cleared. Drop the writes whose effect outlives the user: Config
+ * Lock and the Range registers. Initiate CXL Reset goes through the same
+ * doorbell as with a provider, so the reset runs at a vfio reset point under
+ * memory_lock.
+ */
+static int init_cxl_dvsec_guard_perm(struct perm_bits *perm, int len)
+{
+ if (alloc_perm_bits(perm, len))
+ return -ENOMEM;
+
+ perm->writefn = vfio_cxl_dvsec_write;
+
+ /* Extended capability header: ID and next pointer from vconfig. */
+ p_setd(perm, 0, ALL_VIRT, NO_WRITE);
+
+ p_setw(perm, PCI_DVSEC_CXL_CTRL, NO_VIRT, (u16)ALL_WRITE);
+ p_setw(perm, PCI_DVSEC_CXL_CTRL2, PCI_DVSEC_CXL_INIT_CXL_RST,
+ (u16)ALL_WRITE);
+ /* Status (offset 0Eh, between Control and Control2): RW1C to the device. */
+ p_setw(perm, PCI_DVSEC_CXL_CTRL + 2, NO_VIRT, (u16)ALL_WRITE);
+ p_setw(perm, PCI_DVSEC_CXL_STATUS2,
+ PCI_DVSEC_CXL_RST_DONE | PCI_DVSEC_CXL_RST_ERR,
+ (u16)~(PCI_DVSEC_CXL_RST_DONE | PCI_DVSEC_CXL_RST_ERR));
+
+ return 0;
+}
+
+/* Virtualize the CXL DVSEC so a guest cannot reprogram the device through it. */
+static int vfio_cxl_dvsec_init(struct vfio_pci_core_device *vdev)
+{
+ struct pci_dev *pdev = vdev->pdev;
+ u32 dword;
+ u16 dvsec, cap;
+ int len, ret;
+
+ dvsec = pci_find_dvsec_capability(pdev, PCI_VENDOR_ID_CXL,
+ PCI_DVSEC_CXL_DEVICE);
+ if (!dvsec)
+ return 0;
+
+ ret = pci_read_config_dword(pdev, dvsec + PCI_DVSEC_HEADER1, &dword);
+ if (ret)
+ return pcibios_err_to_errno(ret);
+ len = PCI_DVSEC_HEADER1_LEN(dword);
+
+ /*
+ * The virtualization writes fixed DVSEC offsets up to Status2 (the reset
+ * doorbell stamps it). A device that reports a shorter DVSEC is not a
+ * usable Type-2 function; leave its DVSEC unvirtualized rather than
+ * index the device-length-sized perm allocation past its end.
+ */
+ if (len < PCI_DVSEC_CXL_STATUS2 + 2)
+ return 0;
+
+ /*
+ * Without a provider, guard only a CXL Type-2 function, with the same
+ * test vfio-pci-core uses to load the provider.
+ */
+ if (!vdev->cxl_ops) {
+ if (!pcie_is_cxl(pdev))
+ return 0;
+ ret = pci_read_config_word(pdev, dvsec + PCI_DVSEC_CXL_CAP, &cap);
+ if (ret)
+ return pcibios_err_to_errno(ret);
+ if (!(cap & PCI_DVSEC_CXL_CACHE_CAPABLE) ||
+ !(cap & PCI_DVSEC_CXL_MEM_CAPABLE))
+ return 0;
+ }
+
+ vdev->cxl_perm = kmalloc_obj(*vdev->cxl_perm, GFP_KERNEL_ACCOUNT);
+ if (!vdev->cxl_perm)
+ return -ENOMEM;
+
+ if (vdev->cxl_ops)
+ ret = init_cxl_dvsec_perm(vdev->cxl_perm, len);
+ else
+ ret = init_cxl_dvsec_guard_perm(vdev->cxl_perm, len);
+ if (ret) {
+ kfree(vdev->cxl_perm);
+ vdev->cxl_perm = NULL;
+ return ret;
+ }
+
+ vdev->cxl_dvsec = dvsec;
+ vdev->cxl_dvsec_len = len;
+
+ return 0;
+}
+
+static void vfio_cxl_dvsec_exit(struct vfio_pci_core_device *vdev)
+{
+ if (!vdev->cxl_perm)
+ return;
+
+ free_perm_bits(vdev->cxl_perm);
+ kfree(vdev->cxl_perm);
+ vdev->cxl_perm = NULL;
+ vdev->cxl_dvsec = 0;
+ vdev->cxl_dvsec_len = 0;
+}
+
+/*
+ * Every DVSEC carries the same ID in pci_config_map, so vfio_find_cap_start()
+ * can walk from one DVSEC into an adjacent one. Match the CXL DVSEC by range.
+ */
+static bool vfio_cxl_dvsec_contains(struct vfio_pci_core_device *vdev,
+ loff_t pos)
+{
+ return vdev->cxl_perm && pos >= vdev->cxl_dvsec &&
+ pos < vdev->cxl_dvsec + vdev->cxl_dvsec_len;
+}
+
int vfio_config_init(struct vfio_pci_core_device *vdev)
{
struct pci_dev *pdev = vdev->pdev;
@@ -1842,6 +2030,10 @@ int vfio_config_init(struct vfio_pci_core_device *vdev)
if (ret)
goto out;
+ ret = vfio_cxl_dvsec_init(vdev);
+ if (ret)
+ goto out;
+
return 0;
out:
@@ -1863,6 +2055,7 @@ void vfio_config_free(struct vfio_pci_core_device *vdev)
kfree(vdev->msi_perm);
vdev->msi_perm = NULL;
}
+ vfio_cxl_dvsec_exit(vdev);
}
/*
@@ -1926,12 +2119,17 @@ ssize_t vfio_pci_config_rw_single(struct vfio_pci_core_device *vdev,
* of the extended capability list. Use default, ro
* access, which will virtualize the id and next values.
*/
- if (cap_id > PCI_EXT_CAP_ID_MAX)
+ cap_start = vfio_find_cap_start(vdev, *ppos);
+
+ if (cap_id > PCI_EXT_CAP_ID_MAX) {
perm = &direct_ro_perms;
- else
+ } else if (cap_id == PCI_EXT_CAP_ID_DVSEC &&
+ vfio_cxl_dvsec_contains(vdev, *ppos)) {
+ perm = vdev->cxl_perm;
+ cap_start = vdev->cxl_dvsec;
+ } else {
perm = &ecap_perms[cap_id];
-
- cap_start = vfio_find_cap_start(vdev, *ppos);
+ }
} else {
WARN_ON(cap_id > PCI_CAP_ID_MAX);
diff --git a/include/linux/vfio_pci_core.h b/include/linux/vfio_pci_core.h
index 70acb03d466e..fd1ad0baf470 100644
--- a/include/linux/vfio_pci_core.h
+++ b/include/linux/vfio_pci_core.h
@@ -120,6 +120,9 @@ struct vfio_pci_core_device {
u8 *pci_config_map;
u8 *vconfig;
struct perm_bits *msi_perm;
+ struct perm_bits *cxl_perm;
+ u16 cxl_dvsec;
+ u16 cxl_dvsec_len;
spinlock_t irqlock;
struct mutex igate;
struct xarray ctx;
--
2.53.0
next prev parent reply other threads:[~2026-10-16 16:21 UTC|newest]
Thread overview: 32+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-10-16 16:18 [PATCH v6 00/31] vfio/pci: Add CXL Type-2 device passthrough support mhonap
2026-10-16 16:18 ` [PATCH v6 01/31] cxl/regs: Split the BAR block request and ioremap helpers mhonap
2026-10-16 16:18 ` [PATCH v6 02/31] cxl/regs: Let a BAR-owning driver own the component register block mhonap
2026-10-16 16:18 ` [PATCH v6 03/31] cxl: Move component register defines to uapi/cxl/cxl_regs.h mhonap
2026-10-16 16:18 ` [PATCH v6 04/31] PCI/CXL: Let the driver that owns the HDM range run CXL Reset mhonap
2026-10-16 16:18 ` [PATCH v6 05/31] PCI: Refuse bus and slot reset below a CXL port with SBR masked mhonap
2026-10-16 16:18 ` [PATCH v6 06/31] vfio/pci: Add the CXL provider ops registration interface mhonap
2026-10-16 16:18 ` [PATCH v6 07/31] vfio/pci: Detect CXL devices and load the CXL provider on demand mhonap
2026-10-16 16:18 ` [PATCH v6 08/31] vfio/pci: Add a generic excluded-range list mhonap
2026-10-16 16:18 ` [PATCH v6 09/31] vfio/pci: Advertise the mmap areas around excluded ranges mhonap
2026-10-16 16:18 ` [PATCH v6 10/31] vfio/pci: Refuse ioeventfds and dma-buf exports over " mhonap
2026-10-16 16:18 ` mhonap [this message]
2026-10-16 16:18 ` [PATCH v6 12/31] vfio/pci: Call the CXL open and close hooks around device use mhonap
2026-10-16 16:18 ` [PATCH v6 13/31] vfio/pci: Bracket PCI resets with the CXL reset hooks mhonap
2026-10-16 16:18 ` [PATCH v6 14/31] vfio/pci: Provide an opt-out for the CXL Type-2 extensions mhonap
2026-10-16 16:18 ` [PATCH v6 15/31] vfio/pci: Route every reset point through a try-reset helper mhonap
2026-10-16 16:18 ` [PATCH v6 16/31] vfio/pci: Revoke and re-arm one device-specific region mhonap
2026-10-16 16:18 ` [PATCH v6 17/31] vfio/pci: Call the device provider across low-power transitions mhonap
2026-10-16 16:18 ` [PATCH v6 18/31] vfio/cxl: Add the vfio-cxl provider module skeleton mhonap
2026-10-16 16:18 ` [PATCH v6 19/31] vfio/cxl: Create the CXL memdev and set media ready at bind mhonap
2026-10-16 16:18 ` [PATCH v6 20/31] vfio/cxl: Own the whole component register BAR mhonap
2026-10-16 16:18 ` [PATCH v6 21/31] vfio/cxl: Expose the HDM memory region to the guest mhonap
2026-10-16 16:18 ` [PATCH v6 22/31] vfio/cxl: Allow mmap of the HDM memory region mhonap
2026-10-16 16:18 ` [PATCH v6 23/31] vfio/cxl: Contain HDM memory errors with memory_failure() mhonap
2026-10-16 16:18 ` [PATCH v6 24/31] vfio/cxl: Expose the HDM decoder registers read-only to the guest mhonap
2026-10-16 16:18 ` [PATCH v6 25/31] vfio/cxl: Exclude the HDM decoder registers from direct BAR access mhonap
2026-10-16 16:18 ` [PATCH v6 26/31] vfio/cxl: Restore the HDM decoder after a hot reset mhonap
2026-10-16 16:18 ` [PATCH v6 27/31] vfio/cxl: Describe the CXL device and decoder geometry to userspace mhonap
2026-10-16 16:18 ` [PATCH v6 28/31] vfio/cxl: Export the HDM memory region as a dma-buf mhonap
2026-10-16 16:18 ` [PATCH v6 29/31] vfio/cxl: Run the CXL reset at the vfio reset points mhonap
2026-10-16 16:18 ` [PATCH v6 30/31] Documentation: vfio-pci: Document CXL Type-2 device passthrough mhonap
2026-10-16 16:18 ` [PATCH v6 31/31] selftests/vfio: Add CXL Type-2 passthrough tests mhonap
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20261016161839.2765592-12-mhonap@nvidia.com \
--to=mhonap@nvidia.com \
--cc=alejandro.lucero-palau@amd.com \
--cc=alex@shazbot.org \
--cc=alison.schofield@intel.com \
--cc=ankita@nvidia.com \
--cc=bhelgaas@google.com \
--cc=cjia@nvidia.com \
--cc=corbet@lwn.net \
--cc=dave.jiang@intel.com \
--cc=dave@stgolabs.net \
--cc=dmatlack@google.com \
--cc=gustavoars@kernel.org \
--cc=iweiny@kernel.org \
--cc=jgg@ziepe.ca \
--cc=jic23@kernel.org \
--cc=kees@kernel.org \
--cc=kevin.tian@intel.com \
--cc=kjaju@nvidia.com \
--cc=kvm@vger.kernel.org \
--cc=linux-cxl@vger.kernel.org \
--cc=linux-doc@vger.kernel.org \
--cc=linux-hardening@vger.kernel.org \
--cc=linux-kernel@vger.kernel.org \
--cc=linux-kselftest@vger.kernel.org \
--cc=linux-pci@vger.kernel.org \
--cc=ming.li@zohomail.com \
--cc=skhan@linuxfoundation.org \
--cc=skolothumtho@nvidia.com \
--cc=smadhavan@nvidia.com \
--cc=vishal.l.verma@intel.com \
--cc=vsethi@nvidia.com \
--cc=yishaih@nvidia.com \
--cc=zhiw@nvidia.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox