pci: expand sub-page VFIO BAR mmap to page size

On aarch64 with 64K host pages, VFIO passthrough of devices with
sub-page BARs (e.g. 16K NVMe BAR0) crashes with EINVAL from
KVM_SET_USER_MEMORY_REGION, which requires memory_size to be a
multiple of the host page size.

Expand the mmap to page size instead of rejecting it, matching
QEMU's approach. The kernel's vfio_pci_probe_mmaps() already
verifies that sub-page BARs are page-aligned and reserves the
remainder of the page, so expansion is safe at offset 0. Reject
sub-page sparse areas at non-zero offsets where this guarantee
does not apply.

The expanded mmap region will not overlap with the relocated MSI-X
trap region because fixup_msix_region() ensures MSI-X relocation
at >= page_size offset.

Signed-off-by: Saravanan D <saravanand@crusoe.ai>
This commit is contained in:
Saravanan D
2026-03-31 06:17:43 +00:00
committed by Rob Bradford
parent 2ddbc5abbb
commit 23a980cd54

View File

@@ -21,7 +21,8 @@ use thiserror::Error;
use vfio_bindings::bindings::vfio::*;
use vfio_ioctls::{VfioDevice, VfioIrq, VfioOps, VfioRegionInfoCap, VfioRegionSparseMmapArea};
use vm_allocator::page_size::{
align_page_size_down, align_page_size_up, is_4k_aligned, is_4k_multiple, is_page_size_aligned,
align_page_size_down, align_page_size_up, get_page_size, is_4k_aligned, is_4k_multiple,
is_page_size_aligned,
};
use vm_allocator::{AddressAllocator, MemorySlotAllocator, SystemAllocator};
use vm_device::dma_mapping::ExternalDmaMapping;
@@ -1686,9 +1687,40 @@ impl VfioPciDevice {
self.common.interrupt.msix.as_ref(),
)?;
let page_size = get_page_size();
for area in sparse_areas.iter() {
// KVM_SET_USER_MEMORY_REGION requires memory_size to be a
// multiple of the host page size. On aarch64 with 64K pages
// a device BAR can be smaller than a page (e.g. 16K NVMe
// BAR).
//
// The kernel only sets VFIO_REGION_INFO_FLAG_MMAP on sub-page
// BARs after verifying the physical BAR start is page-aligned
// and reserving the rest of the page. Expansion is only safe
// at offset 0 where the kernel reservation applies.
//
// fixup_msix_region() ensures MSI-X relocation at >= page_size
// offset, so the expanded mmap cannot overlap the trap region.
let mmap_len = if area.size < page_size {
if area.offset != 0 {
error!(
"BAR {}: sub-page sparse area at non-zero offset 0x{:x} \
cannot be safely expanded to page size",
region.index, area.offset,
);
return Err(VfioPciError::MmapArea);
}
info!(
"BAR {}: expanding sub-page sparse area mmap from 0x{:x} to \
page size 0x{:x}",
region.index, area.size, page_size,
);
page_size
} else {
area.size
};
let mapping = match MmapRegion::mmap(
area.size,
mmap_len,
prot,
fd,
mmap_offset,
@@ -1699,7 +1731,7 @@ impl VfioPciDevice {
error!(
"Could not mmap sparse area (offset = 0x{:x}, size = 0x{:x}): {}",
mmap_offset,
area.size,
mmap_len,
std::io::Error::last_os_error()
);
return Err(VfioPciError::MmapArea);