From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from gate001.proxmox.com (gate001.proxmox.com [45.144.208.40]) by lore.proxmox.com (Postfix) with ESMTPS id 7C6F91FF0DF for ; Thu, 15 Oct 2026 10:10:48 +0200 (CEST) Received: from gate001.proxmox.com (localhost.localdomain [127.0.0.1]) by gate001.proxmox.com (Proxmox) with ESMTP id 0A06E21472; Thu, 15 Oct 2026 10:10:46 +0200 (CEST) From: Dominik Csapak To: pve-devel@lists.proxmox.com Subject: [PATCH pve-qemu-server-rs v2 2/9] pci: legacy: generate pci bridges Date: Thu, 15 Oct 2026 10:10:24 +0200 Message-ID: <20261015081041.743422-3-d.csapak@proxmox.com> X-Mailer: git-send-email 2.47.3 In-Reply-To: <20261015081041.743422-1-d.csapak@proxmox.com> References: <20261015081041.743422-1-d.csapak@proxmox.com> MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-SPAM-LEVEL: Spam detection results: 0 AWL 0.368 Adjusted score from AWL reputation of From: address DMARC_MISSING 0.1 Missing DMARC policy KAM_DMARC_STATUS 0.01 Test Rule for DKIM or SPF Failure with Strict Alignment (newer systems) RCVD_IN_DNSWL_MED -2.3 Sender listed at https://www.dnswl.org/, medium trust SPF_HELO_NONE 0.001 SPF: HELO does not publish an SPF Record SPF_PASS -0.001 SPF: sender matches SPF record Message-ID-Hash: U4KVLMB6QCTCZWSZLVLJQXVPF7XRVZNC X-Message-ID-Hash: U4KVLMB6QCTCZWSZLVLJQXVPF7XRVZNC X-MailFrom: d.csapak@proxmox.com X-Mailman-Rule-Misses: dmarc-mitigation; no-senders; approved; loop; banned-address; emergency; member-moderation; nonmember-moderation; administrivia; implicit-dest; max-recipients; max-size; news-moderation; no-subject; digests; suspicious-header X-Mailman-Version: 3.3.10 Precedence: list List-Id: Proxmox VE development discussion List-Help: List-Owner: List-Post: List-Subscribe: List-Unsubscribe: Besides the address of a single device, we also need the list of bridges a guest has to be started with. Return them from `pci_bus_devices`, which is intended to return all devices that form a bus, since those must come before the devices using them on the commandline. The list itself follows the perl implementation: * q35 machines already have the bridges up to 3 in their machine config * pci.3 is only needed for virtio-scsi-single * pci.4 is only needed for scsi controllers with an index > 1, or starting with machine version 11.1 Signed-off-by: Dominik Csapak --- pve-qemu-server-pci/src/layout/legacy.rs | 109 ++++++++++++++++++++++- pve-qemu-server-pci/src/lib.rs | 9 ++ 2 files changed, 117 insertions(+), 1 deletion(-) diff --git a/pve-qemu-server-pci/src/layout/legacy.rs b/pve-qemu-server-pci/src/layout/legacy.rs index e6693ca..6d5cc81 100644 --- a/pve-qemu-server-pci/src/layout/legacy.rs +++ b/pve-qemu-server-pci/src/layout/legacy.rs @@ -331,6 +331,43 @@ pub(crate) fn pci_bridge_for_device(id: &str) -> Option { Some(addr.bus) } +/// Renders the bridge devices a guest needs, see +/// [`crate::pci_bus_devices`]. +pub(crate) fn pci_bus_devices(hw: &HardwareConfig) -> Result, LayoutError> { + let mut res = Vec::new(); + + // some SCSI controllers can only have 7 disks each, so scsi14 and upwards + // need scsihw2 and above, which live on pci.4 + let include_pci4 = hw.version_at_least(11, 1, None) || hw.max_scsihw > 1; + + for i in 1..=4 { + // q35.cfg already includes the bridges up to three + if i < 4 && hw.mainboard == Mainboard::Q35 { + continue; + } + + if i == 3 && !hw.virtio_scsi_single() { + continue; + } + + if i == 4 && !include_pci4 { + continue; + } + + let id = if i == 2 && hw.legacy_igd { + format!("pci.{i}-igd") + } else { + format!("pci.{i}") + }; + + let addr = address(hw, &id)?.to_qemu_addr(); + res.push("-device".to_string()); + res.push(format!("pci-bridge,id=pci.{i},chassis_nr={i}{addr}")); + } + + Ok(res) +} + /// Returns if the caller wants a PCIE address instead of a PCI one based on the id. fn is_pcie_id(id: &str) -> bool { id.starts_with("hostpcie") || id == "vga" || id == "ivshmem" @@ -363,7 +400,9 @@ pub(crate) fn address(hw: &HardwareConfig, id: &str) -> Result, + max_scsihw: u8, + legacy_igd: bool, + ) -> HardwareConfig { + HardwareConfig::new( + "x86_64", + format!("pc-{}-9.2-pve0", if q35 { "q35" } else { "i440fx" }), + scsihw.map(str::to_string), + max_scsihw, + legacy_igd, + None, + 9, + 2, + None, + ) + } + + fn bridge(nr: u8, addr: &str) -> [String; 2] { + [ + "-device".to_string(), + format!("pci-bridge,id=pci.{nr},chassis_nr={nr}{addr}"), + ] + } + + #[test] + fn test_pci_bus_devices() { + let pci1 = bridge(1, ",bus=pci.0,addr=0x1e"); + let pci2 = bridge(2, ",bus=pci.0,addr=0x1f"); + let pci3 = bridge(3, ",bus=pci.0,addr=0x5"); + let pci4 = bridge(4, ",bus=pci.1,addr=0x1c"); + + let test = |hw: HardwareConfig, expected: &[&[String; 2]]| { + let expected: Vec = expected.iter().flat_map(|b| b.iter().cloned()).collect(); + assert_eq!(pci_bus_devices(&hw), Ok(expected)); + }; + + test(hw_config(false, None, 0, false), &[&pci1, &pci2]); + + // q35 already brings the bridges up to three along + test(hw_config(true, None, 0, false), &[]); + + // every SCSI disk gets its own controller on pci.3 + test( + hw_config(false, Some("virtio-scsi-single"), 0, false), + &[&pci1, &pci2, &pci3], + ); + + // scsihw2 and above live on pci.4 + test(hw_config(false, Some("lsi"), 1, false), &[&pci1, &pci2]); + test( + hw_config(false, Some("lsi"), 2, false), + &[&pci1, &pci2, &pci4], + ); + test(hw_config(true, Some("lsi"), 2, false), &[&pci4]); + + // pci.4 is always added from machine version 11.1 on + let mut new_machine = hw_config(false, None, 0, false); + new_machine.machine_version = (11, 1, None); + test(new_machine, &[&pci1, &pci2, &pci4]); + + // with legacy IGD passthrough pci.2 moves behind pci.1 + let mut expected_igd = pci2.clone(); + expected_igd[1] = "pci-bridge,id=pci.2,chassis_nr=2,bus=pci.1,addr=0x1e".to_string(); + test(hw_config(false, None, 0, true), &[&pci1, &expected_igd]); + } } diff --git a/pve-qemu-server-pci/src/lib.rs b/pve-qemu-server-pci/src/lib.rs index 43a3886..fc982ce 100644 --- a/pve-qemu-server-pci/src/lib.rs +++ b/pve-qemu-server-pci/src/lib.rs @@ -27,6 +27,15 @@ pub fn pci_bridge_for_device(id: &str) -> Option { layout::legacy::pci_bridge_for_device(id) } +/// Returns the QEMU arguments for all devices that form the buses `hw` +/// needs, as alternating `-device` flags and their values. +/// +/// They are ordered so that every bus is created before the devices on it, +/// and have to be passed before any other device that sits on these buses. +pub fn pci_bus_devices(hw: &HardwareConfig) -> Result, LayoutError> { + layout::legacy::pci_bus_devices(hw) +} + /// Returns the address of the device with the configuration id `id`. pub fn address(hw: &HardwareConfig, id: &str) -> Result { layout::legacy::address(hw, id) -- 2.47.3