Re: [PATCH v6 09/31] vfio/pci: Advertise the mmap areas around excluded ranges
From: Alex Williamson
Date: Fri Nov 06 2026 - 15:42:28 EST
On Fri, 16 Oct 2026 21:48:16 +0530
<mhonap@xxxxxxxxxx> wrote:
> From: Manish Honap <mhonap@xxxxxxxxxx>
>
> A range excluded from mmap must not be reachable through an mmap of its
> BAR. Refuse an mmap that overlaps such a range, and advertise the BAR
> with a sparse mmap capability that lists the areas between the excluded
> ranges, so a VMM maps those and traps the rest.
>
> mmap is page granular, so each range is rounded out to whole pages, and
> ranges that meet after rounding form one hole. The sorted list gives the
> areas in two walks: the first counts them and the second fills them.
>
> A BAR without mmap-excluded ranges gets no new capability. The MSI-X
> table is excluded from read and write only, so the MSI-X BAR is
> unchanged.
>
> Assisted-by: LLM
> Signed-off-by: Manish Honap <mhonap@xxxxxxxxxx>
> ---
> drivers/vfio/pci/vfio_pci_core.c | 91 ++++++++++++++++++++++++++++++++
> 1 file changed, 91 insertions(+)
>
> diff --git a/drivers/vfio/pci/vfio_pci_core.c b/drivers/vfio/pci/vfio_pci_core.c
> index bee3d1b4d8d2..f099a294af34 100644
> --- a/drivers/vfio/pci/vfio_pci_core.c
> +++ b/drivers/vfio/pci/vfio_pci_core.c
> @@ -1106,6 +1106,88 @@ static void vfio_pci_free_excluded_ranges(struct vfio_pci_core_device *vdev)
> }
> }
>
> +/* True when [start, start + len) on @bar overlaps a range with any of @flags. */
> +static bool vfio_pci_bar_range_excluded(struct vfio_pci_core_device *vdev,
> + int bar, u64 start, u64 len, u32 flags)
> +{
> + struct vfio_pci_excluded_range *range;
> +
> + /* Only BARs have excluded ranges; a device-specific region has none. */
> + if (bar >= PCI_STD_NUM_BARS)
> + return false;
> +
> + list_for_each_entry(range, &vdev->excluded_ranges[bar], entry) {
> + if (range->start >= start + len)
> + break;
> + if ((range->flags & flags) &&
> + start < range->start + range->size)
> + return true;
> + }
> +
> + return false;
> +}
> +
> +/*
> + * Advertise a BAR with mmap-excluded ranges as the mmap-able areas between
> + * them. mmap is page granular, so each range is rounded out to whole pages;
> + * ranges that meet after rounding merge into one hole. The first walk counts
> + * the areas and the second fills them.
> + */
> +static int vfio_pci_excluded_sparse_cap(struct vfio_pci_core_device *vdev,
> + int bar, struct vfio_info_cap *caps)
> +{
> + struct vfio_region_info_cap_sparse_mmap *sparse __free(kfree) = NULL;
> + u64 bar_len = pci_resource_len(vdev->pdev, bar);
> + struct vfio_pci_excluded_range *range;
> + u32 nr_areas = 0;
> + bool holes = false;
> + u64 pos = 0;
> + u32 i = 0;
> +
> + list_for_each_entry(range, &vdev->excluded_ranges[bar], entry) {
> + if (!(range->flags & VFIO_PCI_EXCLUDE_MMAP))
> + continue;
> + holes = true;
> + if (ALIGN_DOWN(range->start, PAGE_SIZE) > pos)
> + nr_areas++;
> + pos = max(pos, ALIGN(range->start + range->size, PAGE_SIZE));
> + }
> + if (!holes)
> + return 0;
> + if (pos < bar_len)
> + nr_areas++;
The number of sparse ranges is bounded by (num_mmap_excluded_ranges +
1). We're dealing with a very low number of excluded ranges, bounded by
insertion via kernel drivers and core. It seems worthwhile to avoid
the duplication with a simple list walk that counts the mmap excluded
ranges, then all of the alignment happens once, and the resulting
nr_areas is calculated as we fill.
Note that with the use of __free, it's generally recommended to
allocate at the point it's declared and mixed declarations and
statements are allowed for this. We can therefore do the declaration
and allocation after we've done the simple version of the counting loop.
Thanks,
Alex
> +
> + sparse = kzalloc_flex(*sparse, areas, nr_areas);
> + if (!sparse)
> + return -ENOMEM;
> +
> + sparse->header.id = VFIO_REGION_INFO_CAP_SPARSE_MMAP;
> + sparse->header.version = 1;
> + sparse->nr_areas = nr_areas;
> +
> + pos = 0;
> + list_for_each_entry(range, &vdev->excluded_ranges[bar], entry) {
> + u64 hole = ALIGN_DOWN(range->start, PAGE_SIZE);
> +
> + if (!(range->flags & VFIO_PCI_EXCLUDE_MMAP))
> + continue;
> + if (hole > pos)
> + sparse->areas[i++] = (struct vfio_region_sparse_mmap_area) {
> + .offset = pos,
> + .size = hole - pos,
> + };
> + pos = max(pos, ALIGN(range->start + range->size, PAGE_SIZE));
> + }
> + if (pos < bar_len)
> + sparse->areas[i] = (struct vfio_region_sparse_mmap_area) {
> + .offset = pos,
> + .size = bar_len - pos,
> + };
> +
> + return vfio_info_add_capability(caps, &sparse->header,
> + struct_size(sparse, areas, nr_areas));
> +}
> +
> int vfio_pci_core_register_dev_region(struct vfio_pci_core_device *vdev,
> unsigned int type, unsigned int subtype,
> const struct vfio_pci_regops *ops,
> @@ -1254,6 +1336,10 @@ int vfio_pci_ioctl_get_region_info(struct vfio_device *core_vdev,
> if (ret)
> return ret;
> }
> + ret = vfio_pci_excluded_sparse_cap(vdev, info->index,
> + caps);
> + if (ret)
> + return ret;
> }
>
> break;
> @@ -1947,6 +2033,11 @@ int vfio_pci_core_mmap(struct vfio_device *core_vdev, struct vm_area_struct *vma
> if (req_start + req_len > phys_len)
> return -EINVAL;
>
> + /* An excluded sub-range is reachable only through its trap, not mmap. */
> + if (vfio_pci_bar_range_excluded(vdev, index, req_start, req_len,
> + VFIO_PCI_EXCLUDE_MMAP))
> + return -EINVAL;
> +
> /*
> * Ensure the BAR resource region is reserved for use.
> */