[PATCH v5 10/27] vfio/pci: Migrate MSI-X exclusion onto the generic excluded-range list
From: mhonap
Date: Wed Sep 16 2026 - 15:07:28 EST
From: Manish Honap <mhonap@xxxxxxxxxx>
The MSI-X table is virtualized in vfio_pci_bar_rw() by an open-coded
x_start/x_end window that fills reads with -1 and drops writes. Now that
a generic excluded-range list expresses the same fill/drop behavior,
register the MSI-X table as a read and write excluded range instead of
special-casing it in the read/write path.
Add the range when the MSI-X capability is parsed in
vfio_pci_core_enable() and clear the list in vfio_pci_core_disable()
alongside the config teardown. The read/write path now relies solely on
vfio_pci_bar_find_exclusion(), so MSI-X and a provider's (e.g. vfio-cxl)
trapped registers share one mechanism.
No behavioral change: an access to the MSI-X table still reads -1 and
drops writes.
Assisted-by: LLM
Signed-off-by: Manish Honap <mhonap@xxxxxxxxxx>
---
drivers/vfio/pci/vfio_pci_core.c | 19 +++++++++++
drivers/vfio/pci/vfio_pci_rdwr.c | 56 ++++++++++++++++++++++++--------
2 files changed, 62 insertions(+), 13 deletions(-)
diff --git a/drivers/vfio/pci/vfio_pci_core.c b/drivers/vfio/pci/vfio_pci_core.c
index 9e4fa5d088a4..c5b7a59a4548 100644
--- a/drivers/vfio/pci/vfio_pci_core.c
+++ b/drivers/vfio/pci/vfio_pci_core.c
@@ -589,6 +589,8 @@ static const struct dev_pm_ops vfio_pci_core_pm_ops = {
NULL)
};
+static void vfio_pci_free_excluded_ranges(struct vfio_pci_core_device *vdev);
+
int vfio_pci_core_enable(struct vfio_pci_core_device *vdev)
{
struct pci_dev *pdev = vdev->pdev;
@@ -655,6 +657,22 @@ int vfio_pci_core_enable(struct vfio_pci_core_device *vdev)
vdev->msix_offset = table & PCI_MSIX_TABLE_OFFSET;
vdev->msix_size = ((flags & PCI_MSIX_FLAGS_QSIZE) + 1) * 16;
vdev->has_dyn_msix = pci_msix_can_alloc_dyn(pdev);
+
+ /*
+ * Virtualize the MSI-X table through the excluded-range list:
+ * reads fill -1 and writes are dropped so the guest never
+ * reaches the hardware table directly.
+ */
+ ret = vfio_pci_core_add_excluded_range(vdev, vdev->msix_bar,
+ vdev->msix_offset,
+ vdev->msix_size,
+ VFIO_PCI_EXCLUDE_READ |
+ VFIO_PCI_EXCLUDE_WRITE);
+ if (ret) {
+ vfio_pci_free_excluded_ranges(vdev);
+ vfio_config_free(vdev);
+ goto out_free_zdev;
+ }
} else {
vdev->msix_bar = 0xFF;
vdev->has_dyn_msix = false;
@@ -741,6 +759,7 @@ void vfio_pci_core_disable(struct vfio_pci_core_device *vdev)
vdev->region = NULL; /* don't krealloc a freed pointer */
vfio_config_free(vdev);
+ vfio_pci_free_excluded_ranges(vdev);
for (i = 0; i < PCI_STD_NUM_BARS; i++) {
bar = i + PCI_STD_RESOURCES;
diff --git a/drivers/vfio/pci/vfio_pci_rdwr.c b/drivers/vfio/pci/vfio_pci_rdwr.c
index 48da1cb08296..f8e5f94a2e8a 100644
--- a/drivers/vfio/pci/vfio_pci_rdwr.c
+++ b/drivers/vfio/pci/vfio_pci_rdwr.c
@@ -256,21 +256,51 @@ ssize_t vfio_pci_bar_rw(struct vfio_pci_core_device *vdev, char __user *buf,
}
}
- if (bar == vdev->msix_bar) {
- x_start = vdev->msix_offset;
- x_end = vdev->msix_offset + vdev->msix_size;
- }
-
/*
- * A provider-excluded sub-range is filled with -1 on read and dropped on
- * write for the same reason: the guest reaches it only through the trap.
- * An access spans at most one exclusion window.
+ * The MSI-X table and any provider-excluded sub-ranges (such as a CXL
+ * HDM decoder block) are filled with -1 on read and dropped on write:
+ * the guest reaches them only through the virtualized path, never the
+ * hardware directly. A BAR can hold several such windows and a single
+ * access may span more than one, so walk the access one window at a
+ * time. The ROM BAR uses the single trailing window set above.
*/
- vfio_pci_bar_find_exclusion(vdev, bar, pos, count, iswrite,
- &x_start, &x_end);
-
- done = vfio_pci_core_do_io_rw(vdev, res->flags & IORESOURCE_MEM, io, buf, pos,
- count, x_start, x_end, iswrite, max_width);
+ if (bar == PCI_ROM_RESOURCE) {
+ done = vfio_pci_core_do_io_rw(vdev, res->flags & IORESOURCE_MEM,
+ io, buf, pos, count, x_start, x_end,
+ iswrite, max_width);
+ } else {
+ done = 0;
+ while (count) {
+ size_t chunk;
+ ssize_t ret;
+
+ x_start = 0;
+ x_end = 0;
+ if (vfio_pci_bar_find_exclusion(vdev, bar, pos, count,
+ iswrite, &x_start,
+ &x_end))
+ chunk = min(count, (size_t)(x_end - pos));
+ else
+ chunk = count;
+
+ ret = vfio_pci_core_do_io_rw(vdev,
+ res->flags & IORESOURCE_MEM,
+ io, buf, pos, chunk,
+ x_start, x_end, iswrite,
+ max_width);
+ if (ret < 0) {
+ if (!done)
+ done = ret;
+ break;
+ }
+ done += ret;
+ pos += ret;
+ buf += ret;
+ count -= ret;
+ if ((size_t)ret < chunk)
+ break;
+ }
+ }
if (done >= 0)
*ppos += done;
--
2.25.1