runtime-rs: drop non-passthrough functions from VFIO groups

A discrete GPU usually shares its IOMMU group with an audio function
(e.g. NVIDIA 02:00.1). Cold-plugging every function in the group made
QEMU emit a vfio-pci/pcie-root-port/iommufd triple per function while
reusing the GPU's root-port and IOMMUFD object ids, so QEMU aborted with
a duplicate-id error before it was reachable over QMP.

Apply the existing IOMMU_IGNORE classification while discovering the
group so audio controllers and bridges are pruned, leaving only the
passthrough-capable device(s). Also fix filter_bridge_device to parse
the sysfs class attribute as hex; the previous decimal parse always
failed, which is why the filter never removed anything.

Signed-off-by: Fabiano Fidêncio <ffidencio@nvidia.com>
Assisted-by: Cursor <noreply@cursor.com>
This commit is contained in:
Fabiano Fidêncio
2026-05-31 19:15:02 +02:00
parent 354e2ad637
commit 403fc1400a

View File

@@ -380,8 +380,11 @@ fn filter_bridge_device(bdf: &str, bitmasks: &[u64]) -> Option<u64> {
return None;
}
match device_class.parse::<u32>() {
Ok(cid_u32) => {
// The sysfs `class` attribute is a hex string (e.g. "0x040300"), so it must
// be parsed base-16. Parsing it as decimal always fails, which previously
// caused every device to be treated as passthrough-capable.
match parse_class_code_u32(&device_class) {
Some(cid_u32) => {
// PCI class code is 24 bits, shift right 8 to get base+sub class
let class_code = u64::from(cid_u32) >> 8;
for &bitmask in bitmasks {
@@ -391,7 +394,7 @@ fn filter_bridge_device(bdf: &str, bitmasks: &[u64]) -> Option<u64> {
}
None
}
_ => None,
None => None,
}
}
@@ -605,6 +608,24 @@ fn discover_vfio_device_for_iommu_group(gid: u32, group_devnode: PathBuf) -> Res
}
}
// Drop functions that only share the GPU's IOMMU group but must not be
// passed through (audio controllers, bridges), reusing the same
// IOMMU_IGNORE classification as validate_group_basic. Everything derived
// below (vfio_cdevs, the IOMMUFD backend cdevs and the primary device) is
// built from this filtered list. Passing such a function would otherwise
// emit an extra vfio-pci entry reusing the GPU's cold-plug root-port and
// IOMMUFD object ids, making QEMU abort with a duplicate-id error.
devices.retain(|d| match &d.addr {
DeviceAddress::Pci(bdf) => filter_bridge_device(&bdf.to_string(), IOMMU_IGNORE).is_none(),
_ => true,
});
if devices.is_empty() {
return Err(anyhow!(
"IOMMU group {} has no passthrough-capable devices",
gid
));
}
let labels = build_group_labels(&devices);
let is_viable = validate_group_basic(&devices);
let primary_device = select_primary_device(&devices);