From e588764e315cb1647baadd52e5f29f3bb9212e40 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fabiano=20Fid=C3=AAncio?= Date: Tue, 26 May 2026 14:47:17 +0200 Subject: [PATCH] agent: fix OVMF nested PCI path resolution MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Treat three-segment PCI paths as pxb-only when the bus indicates pxb, preventing wrong root-bus lookup for nested OVMF hot-plug devices. Signed-off-by: Fabiano FidĂȘncio Assisted-by: OpenAI Codex --- src/agent/src/linux_abi.rs | 57 ++++++++++++++++++++++++++++++++++---- 1 file changed, 52 insertions(+), 5 deletions(-) diff --git a/src/agent/src/linux_abi.rs b/src/agent/src/linux_abi.rs index a89454263e..8397f768d6 100644 --- a/src/agent/src/linux_abi.rs +++ b/src/agent/src/linux_abi.rs @@ -26,15 +26,29 @@ pub fn create_pci_root_bus_path(root_complex: &str) -> String { format!("/devices/pci0000:{root_complex}") } +/// Minimum guest PCI bus number used by pxb-pcie NUMA expander bridges in the +/// runtime (see `busNrSpacing` in createNUMAPCIeTopology). +const PXB_PCIE_ROOT_BUS_MIN: u8 = 0x20; + +fn is_pxb_pcie_root_complex(segment: &str) -> bool { + u8::from_str_radix(segment, 16) + .map(|bus| bus >= PXB_PCIE_ROOT_BUS_MIN) + .unwrap_or(false) +} + // Parses a device tree path into a (root_complex, PCI path) pair. // -// Supports two formats: -// - Full NUMA path: "root_complex/bus/device" (e.g. "10/00/02") where the -// first segment is the root complex and the rest form the PCI path. -// - Legacy path: "bus/device" (e.g. "00/02") which defaults to root complex "00". +// Supports three formats: +// - NUMA pxb-pcie path: "root_complex/bus/device" (e.g. "20/00/02") where +// the first segment is a pxb-pcie guest bus number (>= 0x20) and the rest +// form the PCI path under that root complex. +// - Nested hot-plug path on pci0000:00: "parent/bridge/device" (e.g. +// "02/00/01") used by OVMF Q35 networking; stays on root complex "00". +// - Legacy path: "bus/device" (e.g. "00/02") which defaults to root complex +// "00". pub fn pcipath_from_dev_tree_path(dev_tree_path: &str) -> Result<(&str, pci::Path)> { let segments: Vec<&str> = dev_tree_path.split('/').collect(); - if segments.len() >= 3 { + if segments.len() >= 3 && is_pxb_pcie_root_complex(segments[0]) { let root_complex = segments[0]; let pci_part = &dev_tree_path[root_complex.len() + 1..]; let pci_path = pci::Path::from_str(pci_part).with_context(|| { @@ -141,3 +155,36 @@ pub const U_EVENT_SUB_SYSTEM: &str = "SUBSYSTEM"; pub const U_EVENT_SEQ_NUM: &str = "SEQNUM"; pub const U_EVENT_DEV_NAME: &str = "DEVNAME"; pub const U_EVENT_INTERFACE: &str = "INTERFACE"; + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_pcipath_from_dev_tree_path_legacy() { + let (root, path) = pcipath_from_dev_tree_path("02/01").unwrap(); + assert_eq!(root, "00"); + assert_eq!(path.len(), 2); + assert_eq!(path[0].slot(), 0x02); + assert_eq!(path[1].slot(), 0x01); + } + + #[test] + fn test_pcipath_from_dev_tree_path_nested_ovmf() { + let (root, path) = pcipath_from_dev_tree_path("02/00/01").unwrap(); + assert_eq!(root, "00"); + assert_eq!(path.len(), 3); + assert_eq!(path[0].slot(), 0x02); + assert_eq!(path[1].slot(), 0x00); + assert_eq!(path[2].slot(), 0x01); + } + + #[test] + fn test_pcipath_from_dev_tree_path_numa_pxb() { + let (root, path) = pcipath_from_dev_tree_path("20/00/02").unwrap(); + assert_eq!(root, "20"); + assert_eq!(path.len(), 2); + assert_eq!(path[0].slot(), 0x00); + assert_eq!(path[1].slot(), 0x02); + } +}