From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from gate001.proxmox.com (gate001.proxmox.com [IPv6:2a0f:8001:1:32::40]) by lore.proxmox.com (Postfix) with ESMTPS id 2736F1FF0DF for ; Thu, 15 Oct 2026 10:11:36 +0200 (CEST) Received: from gate001.proxmox.com (localhost.localdomain [127.0.0.1]) by gate001.proxmox.com (Proxmox) with ESMTP id A016A213BC; Thu, 15 Oct 2026 10:11:11 +0200 (CEST) From: Dominik Csapak To: pve-devel@lists.proxmox.com Subject: [PATCH pve-qemu-server-rs v2 3/9] pci: legacy: generate q35 chipset devices and hostpcie root ports Date: Thu, 15 Oct 2026 10:10:25 +0200 Message-ID: <20261015081041.743422-4-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.634 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) KAM_MAILER 2 Automated Mailer Tag Left in Email 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: VAQIIOM42LAPZVRRRW55SHOYA425UASX X-Message-ID-Hash: VAQIIOM42LAPZVRRRW55SHOYA425UASX 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: Until now, the q35 chipset devices were added via the pve-q35-4.0.cfg machine config file, and the root ports for hostpcie4 and above were added by the caller for each configured device. The latter can leave a root port without a function 0 in its slot, e.g. when only hostpci5 is configured. Since guests only scan the other functions of a slot when function 0 exists (Linux stops in `pci_scan_slot()`), such a device was not visible in the guest. To fix that and to have the layout as the single source for the pci hierarchy, generate both in `pci_bus_devices`: * the chipset devices with the same ids, properties and order as in the config file, which a test checks against a copy of it. Machines older than 4.0 used pve-q35.cfg instead, but those are not supported anymore. * the root ports of the configured hostpcie devices, plus the one on function 0 of each used slot. Devices that were already visible keep their address, and Windows 7 guests still don't get any root ports. Since the devices of running guests must not change for live migration, we only generate the chipset devices starting with machine version 11.1+pve1. For older machines, `uses_q35_config_file` tells the caller to keep using the config file. This replaces `print_pcie_root_port`, as we now decide here which root ports are needed. Signed-off-by: Dominik Csapak --- pve-qemu-server-pci/src/hardware_config.rs | 28 ++ pve-qemu-server-pci/src/layout/legacy.rs | 325 +++++++++++++++++- pve-qemu-server-pci/src/lib.rs | 2 +- pve-qemu-server-pci/src/types/layout.rs | 63 +++- pve-qemu-server-pci/src/types/mod.rs | 2 +- pve-qemu-server-pci/test-data/pve-q35-4.0.cfg | 163 +++++++++ 6 files changed, 572 insertions(+), 11 deletions(-) create mode 100644 pve-qemu-server-pci/test-data/pve-q35-4.0.cfg diff --git a/pve-qemu-server-pci/src/hardware_config.rs b/pve-qemu-server-pci/src/hardware_config.rs index a348353..677fe0a 100644 --- a/pve-qemu-server-pci/src/hardware_config.rs +++ b/pve-qemu-server-pci/src/hardware_config.rs @@ -1,8 +1,11 @@ +use std::collections::BTreeMap; use std::sync::Arc; use std::sync::atomic::AtomicU16; use pve_api_types::{ClusterResourceHostArch as Arch, QemuConfigOstype, QemuConfigScsihw}; +use crate::Device; + #[derive(Default, Clone, Debug, PartialEq)] pub enum Mainboard { #[default] @@ -31,6 +34,10 @@ impl Mainboard { } } +/// A passed through PCIe device of the guest. +#[derive(Default, Clone, Debug, PartialEq, Eq)] +pub struct HostpcieDevice {} + /// The parts of a guest configuration that influence which PCI layout is used /// and which quirks have to be applied to it. /// @@ -52,6 +59,9 @@ pub struct HardwareConfig { pub machine_version: (u16, u16, Option), /// The recorded required `+pveX` version pub required_pve_version: Arc, + /// The configured `hostpcie` devices, keyed by their index, which decide + /// what root ports are needed. + pub hostpcie_devices: BTreeMap, } impl HardwareConfig { @@ -93,6 +103,7 @@ impl HardwareConfig { ostype, machine_version: (major, minor, pve), required_pve_version: Arc::new(AtomicU16::new(0)), + hostpcie_devices: BTreeMap::new(), } } @@ -107,6 +118,14 @@ impl HardwareConfig { self.mainboard == Mainboard::Q35 } + /// Whether the legacy q35 chipset devices come from qemu-server's + /// `pve-q35-4.0.cfg` machine config file. Only machines from 11.1+pve1 on + /// get them from [`crate::pci_bus_devices`], so older guests stay unchanged + /// for live migration. + pub fn uses_q35_config_file(&self) -> bool { + self.is_q35() && !self.version_at_least(11, 1, Some(1)) + } + /// Whether each SCSI disk gets its own controller, which needs the extra /// `pci.3` bridge. pub fn virtio_scsi_single(&self) -> bool { @@ -133,6 +152,15 @@ impl HardwareConfig { } } + /// The configured `hostpcie` devices. + pub(crate) fn hostpcie(&self) -> Vec { + self.hostpcie_devices + .keys() + .copied() + .map(Device::Hostpcie) + .collect() + } + /// Returns the version number for Windows OS types or 0 for other. /// /// Windows XP era OS types are represented by `5`, and Vista era ones by `6`. diff --git a/pve-qemu-server-pci/src/layout/legacy.rs b/pve-qemu-server-pci/src/layout/legacy.rs index 6d5cc81..d89c0bf 100644 --- a/pve-qemu-server-pci/src/layout/legacy.rs +++ b/pve-qemu-server-pci/src/layout/legacy.rs @@ -1,7 +1,9 @@ use crate::constants::SLOTS_PER_BUS; use crate::hardware_config::Mainboard; use crate::layout::bridge_slots; -use crate::{Bus, Device, Function, HardwareConfig, Layout, LayoutError, PciAddress, Slot}; +use crate::{ + Bus, Device, Function, HardwareConfig, Layout, LayoutError, PciAddress, RootPort, Slot, +}; const PCI_1: [Slot; SLOTS_PER_BUS] = bridge_slots(&[ single_device!(Net(6)), @@ -319,6 +321,69 @@ fn map_legacy_pci_id(original: &str) -> &str { original } +/// The q35 chipset devices that are not part of the QEMU machine type, in the +/// order they were defined in pve-q35-4.0.cfg, which added them previously. +/// UHCI controllers use the bus of their EHCI controller, so it comes first. +#[expect(deprecated)] +const Q35_CHIPSET_DEVICES: [Device; 18] = [ + Device::Ich9Ehci(1), + Device::Ich9Uhci(1), + Device::Ich9Uhci(2), + Device::Ich9Uhci(3), + Device::Ich9Ehci(2), + Device::Ich9Uhci(4), + Device::Ich9Uhci(5), + Device::Ich9Uhci(6), + Device::Ich9Audio, + Device::RootPort(Bus::LegacyRootPort(1)), + Device::RootPort(Bus::LegacyRootPort(2)), + Device::RootPort(Bus::LegacyRootPort(3)), + Device::RootPort(Bus::LegacyRootPort(4)), + Device::Bridge(Bus::Pcidmi), + Device::Bridge(Bus::Pci(0)), + Device::Bridge(Bus::Pci(1)), + Device::Bridge(Bus::Pci(2)), + Device::Bridge(Bus::Pci(3)), +]; + +/// Renders a device of [`Q35_CHIPSET_DEVICES`] like the config file did. +fn print_q35_chipset_device(device: Device) -> Result { + let addr = LEGACY_PCIE_LAYOUT.find_device(&device)?; + let qemu_addr = addr.to_qemu_addr(); + let ehci_id = |index: u8| match index { + 1 => "ehci".to_string(), + _ => format!("ehci-{index}"), + }; + + let res = match device { + Device::Ich9Ehci(index) => { + let id = ehci_id(index); + format!("ich9-usb-ehci{index},id={id},multifunction=on{qemu_addr}") + } + Device::Ich9Uhci(index) => { + // each EHCI controller has three UHCI companions with two ports each + let masterbus = ehci_id((index - 1) / 3 + 1); + let firstport = (index - 1) % 3 * 2; + format!( + "ich9-usb-uhci{index},id=uhci-{index},multifunction=on{qemu_addr},masterbus={masterbus}.0,firstport={firstport}" + ) + } + Device::Ich9Audio => format!("ich9-intel-hda,id=audio0{qemu_addr}"), + #[expect(deprecated)] + Device::RootPort(id @ Bus::LegacyRootPort(index)) => { + RootPort { addr, id, index }.to_qemu_device() + } + Device::Bridge(Bus::Pcidmi) => format!("i82801b11-bridge,id=pcidmi{qemu_addr}"), + Device::Bridge(Bus::Pci(index)) => { + let chassis_nr = index + 1; + format!("pci-bridge,id=pci.{index},chassis_nr={chassis_nr}{qemu_addr}") + } + _ => return Err(LayoutError::NoAddressFound(device)), + }; + + Ok(res) +} + /// Looks up the PCI bridge a device sits on, see /// [`crate::pci_bridge_for_device`]. pub(crate) fn pci_bridge_for_device(id: &str) -> Option { @@ -331,17 +396,24 @@ pub(crate) fn pci_bridge_for_device(id: &str) -> Option { Some(addr.bus) } -/// Renders the bridge devices a guest needs, see +/// Renders the chipset devices, bridges and root ports a guest needs, see /// [`crate::pci_bus_devices`]. pub(crate) fn pci_bus_devices(hw: &HardwareConfig) -> Result, LayoutError> { let mut res = Vec::new(); + if hw.mainboard == Mainboard::Q35 && !hw.uses_q35_config_file() { + for device in Q35_CHIPSET_DEVICES { + res.push("-device".to_string()); + res.push(print_q35_chipset_device(device)?); + } + } + // 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 + // the bridges up to three are part of the q35 chipset if i < 4 && hw.mainboard == Mainboard::Q35 { continue; } @@ -365,6 +437,18 @@ pub(crate) fn pci_bus_devices(hw: &HardwareConfig) -> Result, Layout res.push(format!("pci-bridge,id=pci.{i},chassis_nr={i}{addr}")); } + // Windows 7 gets its PCIe devices directly on the root bus + if hw.mainboard == Mainboard::Q35 && hw.windows_version() != 7 { + for port in LEGACY_PCIE_LAYOUT.root_ports(&hw.hostpcie()) { + // the first four root ports are part of the q35 chipset + if port.index <= 4 { + continue; + } + res.push("-device".to_string()); + res.push(port.to_qemu_device()); + } + } + Ok(res) } @@ -398,14 +482,15 @@ pub(crate) fn address(hw: &HardwareConfig, id: &str) -> Result = (0..expected.len() as u8).map(Device::Hostpcie).collect(); + let expected: Vec = expected + .iter() + .enumerate() + .map(|(i, (addr, fnr))| { + let index = i + 1; + format!( + "pcie-root-port,id=ich9-pcie-port-{index},addr={addr:02x}.{fnr},x-speed=16,x-width=32,multifunction=on,bus=pcie.0,port={index},chassis={index}", + ) + }) + .collect(); + let mut found: Vec = LEGACY_PCIE_LAYOUT + .root_ports(&devices) + .iter() + .map(|port| port.to_qemu_device()) + .collect(); + // the ports are returned in layout order, where slot 0x1c comes last + found.rotate_right(4); + assert_eq!(found, expected); + } + /// A bus can only be used once the bridge or root port creating it has /// been yielded, otherwise a command line built in iteration order would /// reference a bus QEMU does not know yet. @@ -665,6 +792,35 @@ pub(crate) mod test { assert_eq!(pci_bridge_for_device("no-such-device"), None); } + /// The number of q35 chipset devices `pci_bus_devices` generates for `hw`. + fn generated_chipset(hw: &HardwareConfig) -> usize { + if hw.is_q35() && !hw.uses_q35_config_file() { + Q35_CHIPSET_DEVICES.len() + } else { + 0 + } + } + + #[test] + fn test_q35_chipset_version() { + let test = |q35: bool, version: (u16, u16, Option), config_file: bool| { + let mut hw = hw_config(q35, None, 0, false); + hw.machine_version = version; + assert_eq!(hw.uses_q35_config_file(), config_file, "for {version:?}"); + let res = pci_bus_devices(&hw).expect("valid machine"); + let has_chipset = res + .iter() + .any(|device| device.starts_with("ich9-usb-ehci1,")); + assert_eq!(has_chipset, q35 && !config_file, "for {version:?}"); + }; + + test(true, (11, 1, None), true); + test(true, (11, 1, Some(0)), true); + test(true, (11, 1, Some(1)), false); + test(true, (12, 0, Some(0)), false); + test(false, (11, 1, Some(1)), false); + } + fn hw_config( q35: bool, scsihw: Option<&str>, @@ -700,12 +856,20 @@ pub(crate) mod test { 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)); + let res = pci_bus_devices(&hw).expect("valid machine"); + // the q35 chipset is tested separately + let res: Vec = res + .chunks(2) + .skip(generated_chipset(&hw)) + .flatten() + .cloned() + .collect(); + assert_eq!(res, expected); }; test(hw_config(false, None, 0, false), &[&pci1, &pci2]); - // q35 already brings the bridges up to three along + // the bridges up to three are part of the q35 chipset test(hw_config(true, None, 0, false), &[]); // every SCSI disk gets its own controller on pci.3 @@ -732,4 +896,149 @@ pub(crate) mod test { 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]); } + + fn root_port(index: u8, addr: &str) -> [String; 2] { + [ + "-device".to_string(), + format!( + "pcie-root-port,id=ich9-pcie-port-{index},addr={addr},x-speed=16,x-width=32,multifunction=on,bus=pcie.0,port={index},chassis={index}" + ), + ] + } + + #[test] + fn test_pci_bus_devices_root_ports() { + let test = |q35: bool, ostype: Option<&str>, hostpcie: &[u8], expected: &[[String; 2]]| { + let mut hw = hw_config(q35, None, 0, false); + hw.ostype = ostype.and_then(|ostype| ostype.parse().ok()); + hw.hostpcie_devices = hostpcie + .iter() + .map(|&index| (index, HostpcieDevice::default())) + .collect(); + let expected: Vec = expected.iter().flatten().cloned().collect(); + let res = pci_bus_devices(&hw).expect("valid machine"); + // only look at the root ports, the rest is tested separately + let res: Vec = res + .chunks(2) + .skip(generated_chipset(&hw)) + .filter(|device| device[1].starts_with("pcie-root-port")) + .flatten() + .cloned() + .collect(); + assert_eq!(res, expected, "for {hostpcie:?}"); + }; + + // the first four root ports are part of the q35 chipset + test(true, None, &[0, 3], &[]); + test(true, None, &[4], &[root_port(5, "10.0")]); + test( + true, + None, + &[0, 12, 4], + &[root_port(5, "10.0"), root_port(13, "11.0")], + ); + // function 0 of a used slot has to exist for guests to see the others + test( + true, + None, + &[5, 7], + &[ + root_port(5, "10.0"), + root_port(6, "10.1"), + root_port(8, "10.3"), + ], + ); + test( + true, + None, + &[14], + &[root_port(13, "11.0"), root_port(15, "11.2")], + ); + + // windows 7 has its devices directly on the root bus + test(true, Some("win7"), &[5], &[]); + test(false, None, &[5], &[]); + } + + /// Parses QEMU device options into the id, the driver and the remaining + /// properties, with the address normalized to slot and function. + fn normalize_device( + id: &str, + driver: &str, + props: &mut dyn Iterator, + ) -> (String, String, BTreeMap) { + let mut props: BTreeMap = props.collect(); + if let Some(addr) = props.get_mut("addr") { + let addr_str = addr.trim_start_matches("0x"); + let (slot, function) = addr_str.split_once('.').unwrap_or((addr_str, "0")); + let slot = u8::from_str_radix(slot, 16).expect("valid slot"); + *addr = format!("{slot:02x}.{function}"); + } + (id.to_string(), driver.to_string(), props) + } + + /// Parses the device sections of a QEMU config file. + fn parse_qemu_cfg(cfg: &str) -> Vec<(String, String, BTreeMap)> { + let mut devices = Vec::new(); + let mut current: Option<(String, Vec<(String, String)>)> = None; + let mut finish = |current: Option<(String, Vec<(String, String)>)>| { + if let Some((id, mut props)) = current { + let pos = props.iter().position(|(k, _)| k == "driver").unwrap(); + let (_, driver) = props.remove(pos); + devices.push(normalize_device(&id, &driver, &mut props.into_iter())); + } + }; + for line in cfg.lines().map(str::trim) { + if line.is_empty() || line.starts_with('#') { + continue; + } + if let Some(id) = line.strip_prefix("[device \"") { + finish(current.take()); + current = Some((id.trim_end_matches("\"]").to_string(), Vec::new())); + } else if let Some((key, value)) = line.split_once('=') { + let value = value.trim().trim_matches('"').to_string(); + current + .as_mut() + .unwrap() + .1 + .push((key.trim().to_string(), value)); + } + } + finish(current); + devices + } + + #[test] + /// The q35 chipset devices have to match what the machine config file of + /// qemu-server created before, as they are part of the guest's hardware. + fn test_q35_chipset_matches_cfg() { + let mut hw = hw_config(true, None, 0, false); + hw.machine_version = (11, 1, Some(1)); + let res = pci_bus_devices(&hw).expect("valid machine"); + let generated: Vec<_> = res + .chunks(2) + .take(Q35_CHIPSET_DEVICES.len()) + .map(|device| { + assert_eq!(device[0], "-device"); + let mut parts = device[1].split(','); + let driver = parts.next().unwrap(); + let mut props = parts.map(|part| { + let (key, value) = part.split_once('=').unwrap(); + (key.to_string(), value.to_string()) + }); + let id = props.next().unwrap(); + assert_eq!(id.0, "id"); + normalize_device(&id.1, driver, &mut props) + }) + .collect(); + let expected = parse_qemu_cfg(PVE_Q35_4_0_CFG); + assert_eq!(expected.len(), Q35_CHIPSET_DEVICES.len()); + assert_eq!(generated, expected); + } + + /// Copy of qemu-server's `pve-q35-4.0.cfg`. + const PVE_Q35_4_0_CFG: &str = include_str!(concat!( + env!("CARGO_MANIFEST_DIR"), + "/test-data/pve-q35-4.0.cfg" + )); } diff --git a/pve-qemu-server-pci/src/lib.rs b/pve-qemu-server-pci/src/lib.rs index fc982ce..a5d82a4 100644 --- a/pve-qemu-server-pci/src/lib.rs +++ b/pve-qemu-server-pci/src/lib.rs @@ -11,7 +11,7 @@ pub mod constants; mod hardware_config; -pub use hardware_config::HardwareConfig; +pub use hardware_config::{HardwareConfig, HostpcieDevice}; mod types; pub use types::*; diff --git a/pve-qemu-server-pci/src/types/layout.rs b/pve-qemu-server-pci/src/types/layout.rs index 889b961..91b1d78 100644 --- a/pve-qemu-server-pci/src/types/layout.rs +++ b/pve-qemu-server-pci/src/types/layout.rs @@ -1,4 +1,4 @@ -use std::collections::VecDeque; +use std::collections::{HashSet, VecDeque}; use crate::constants::{FUNCTIONS_PER_DEVICE, SLOTS_PER_BUS, USABLE_SLOTS_PER_BUS}; use crate::{Bus, Device, LayoutError, PciAddress, PveBridge}; @@ -48,6 +48,46 @@ pub struct Layout<'a> { } impl Layout<'_> { + /// Returns the root ports `devices` are attached to in layout order, plus + /// the one on function 0 of each used slot, as guests only look for further + /// functions if function 0 exists. + pub fn root_ports(&self, devices: &[Device]) -> Vec { + let attached: HashSet = devices + .iter() + .filter_map(|device| self.find_device(device).ok()) + .collect(); + let is_used = |port: &RootPort| attached.contains(&PciAddress::new(port.id, 0, 0)); + + let ports: Vec = self + .iter() + .filter_map(|used| match used.device { + #[expect(deprecated)] + Device::RootPort(id @ (Bus::RootPort(index) | Bus::LegacyRootPort(index))) => { + Some(RootPort { + addr: used.addr, + id, + index, + }) + } + _ => None, + }) + .collect(); + let used_slots: HashSet<(Bus, u8)> = ports + .iter() + .filter(|port| is_used(port)) + .map(|port| (port.addr.bus, port.addr.device)) + .collect(); + + ports + .into_iter() + .filter(|port| { + is_used(port) + || (port.addr.function == 0 + && used_slots.contains(&(port.addr.bus, port.addr.device))) + }) + .collect() + } + /// Looks up the address of a device in this layout. /// /// The first match wins, so the order of the layout is significant: a @@ -68,6 +108,27 @@ impl Layout<'_> { } } +/// A PCIe root port of a layout. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct RootPort { + pub addr: PciAddress, + /// The bus the root port forms. + pub id: Bus, + /// The number of the root port, also used as port and chassis number. + pub index: u8, +} + +impl RootPort { + /// Renders the QEMU `-device` argument for this root port. + pub fn to_qemu_device(&self) -> String { + let RootPort { addr, id, index } = self; + format!( + "pcie-root-port,id={id},addr={:02x}.{},x-speed=16,x-width=32,multifunction=on,bus={},port={index},chassis={index}", + addr.device, addr.function, addr.bus, + ) + } +} + /// The address a layout assigns to a device. #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub struct AddressAssignment { diff --git a/pve-qemu-server-pci/src/types/mod.rs b/pve-qemu-server-pci/src/types/mod.rs index 3847745..1605066 100644 --- a/pve-qemu-server-pci/src/types/mod.rs +++ b/pve-qemu-server-pci/src/types/mod.rs @@ -8,4 +8,4 @@ mod pci_address; pub use pci_address::PciAddress; mod layout; -pub use layout::{AddressAssignment, AddressAssignments, Function, Layout, Slot}; +pub use layout::{AddressAssignment, AddressAssignments, Function, Layout, RootPort, Slot}; diff --git a/pve-qemu-server-pci/test-data/pve-q35-4.0.cfg b/pve-qemu-server-pci/test-data/pve-q35-4.0.cfg new file mode 100644 index 0000000..c931417 --- /dev/null +++ b/pve-qemu-server-pci/test-data/pve-q35-4.0.cfg @@ -0,0 +1,163 @@ +[device "ehci"] + driver = "ich9-usb-ehci1" + multifunction = "on" + bus = "pcie.0" + addr = "1d.7" + +[device "uhci-1"] + driver = "ich9-usb-uhci1" + multifunction = "on" + bus = "pcie.0" + addr = "1d.0" + masterbus = "ehci.0" + firstport = "0" + +[device "uhci-2"] + driver = "ich9-usb-uhci2" + multifunction = "on" + bus = "pcie.0" + addr = "1d.1" + masterbus = "ehci.0" + firstport = "2" + +[device "uhci-3"] + driver = "ich9-usb-uhci3" + multifunction = "on" + bus = "pcie.0" + addr = "1d.2" + masterbus = "ehci.0" + firstport = "4" + +[device "ehci-2"] + driver = "ich9-usb-ehci2" + multifunction = "on" + bus = "pcie.0" + addr = "1a.7" + +[device "uhci-4"] + driver = "ich9-usb-uhci4" + multifunction = "on" + bus = "pcie.0" + addr = "1a.0" + masterbus = "ehci-2.0" + firstport = "0" + +[device "uhci-5"] + driver = "ich9-usb-uhci5" + multifunction = "on" + bus = "pcie.0" + addr = "1a.1" + masterbus = "ehci-2.0" + firstport = "2" + +[device "uhci-6"] + driver = "ich9-usb-uhci6" + multifunction = "on" + bus = "pcie.0" + addr = "1a.2" + masterbus = "ehci-2.0" + firstport = "4" + +# FIXME: Remove this audio0 device at the next possible time +# see: https://pve.proxmox.com/pipermail/pve-devel/2019-July/038417.html +# https://pve.proxmox.com/pipermail/pve-devel/2019-July/038428.html +[device "audio0"] + driver = "ich9-intel-hda" + bus = "pcie.0" + addr = "1b.0" + + +[device "ich9-pcie-port-1"] + driver = "pcie-root-port" + x-speed = "16" + x-width = "32" + multifunction = "on" + bus = "pcie.0" + addr = "1c.0" + port = "1" + chassis = "1" + +[device "ich9-pcie-port-2"] + driver = "pcie-root-port" + x-speed = "16" + x-width = "32" + multifunction = "on" + bus = "pcie.0" + addr = "1c.1" + port = "2" + chassis = "2" + +[device "ich9-pcie-port-3"] + driver = "pcie-root-port" + x-speed = "16" + x-width = "32" + multifunction = "on" + bus = "pcie.0" + addr = "1c.2" + port = "3" + chassis = "3" + +[device "ich9-pcie-port-4"] + driver = "pcie-root-port" + x-speed = "16" + x-width = "32" + multifunction = "on" + bus = "pcie.0" + addr = "1c.3" + port = "4" + chassis = "4" + +## +# Example PCIe switch with two downstream ports +# +#[device "pcie-switch-upstream-port-1"] +# driver = "x3130-upstream" +# bus = "ich9-pcie-port-4" +# addr = "00.0" +# +#[device "pcie-switch-downstream-port-1-1"] +# driver = "xio3130-downstream" +# multifunction = "on" +# bus = "pcie-switch-upstream-port-1" +# addr = "00.0" +# port = "1" +# chassis = "5" +# +#[device "pcie-switch-downstream-port-1-2"] +# driver = "xio3130-downstream" +# multifunction = "on" +# bus = "pcie-switch-upstream-port-1" +# addr = "00.1" +# port = "1" +# chassis = "6" + + + +[device "pcidmi"] + driver = "i82801b11-bridge" + bus = "pcie.0" + addr = "1e.0" + +[device "pci.0"] + driver = "pci-bridge" + bus = "pcidmi" + addr = "1.0" + chassis_nr = "1" + +[device "pci.1"] + driver = "pci-bridge" + bus = "pcidmi" + addr = "2.0" + chassis_nr = "2" + +[device "pci.2"] + driver = "pci-bridge" + bus = "pcidmi" + addr = "3.0" + chassis_nr = "3" + +[device "pci.3"] + driver = "pci-bridge" + bus = "pcidmi" + addr = "4.0" + chassis_nr = "4" -- 2.47.3