public inbox for pve-devel@lists.proxmox.com
 help / color / mirror / Atom feed
From: Dominik Csapak <d.csapak@proxmox.com>
To: Elias Huhsovitz <e.huhsovitz@proxmox.com>, pve-devel@lists.proxmox.com
Subject: Re: [PATCH pve-qemu-server-rs 2/9] pci: add machine abstraction and PCI bridge generation
Date: Mon, 28 Sep 2026 16:04:26 +0200	[thread overview]
Message-ID: <bc261479-d42e-4131-a808-e61ad01c6a68@proxmox.com> (raw)
In-Reply-To: <DLQYRIN8PHM3.9NP2SN1WA6HE@proxmox.com>



On 9/28/26 2:55 PM, Elias Huhsovitz wrote:
> Comments inline.
> 
> On Tue Sep 22, 2026 at 12:55 PM CEST, Dominik Csapak wrote:
>> Besides the address of a single device, callers also need the list of
>> bridge devices a guest has to be started with. Which bridges those are
>> depends on more than one property of the guest config, so pass them in
>> as a `Machine` struct rather than as a growing list of parameters. It
>> can be extended with the layout variant and other guest wide settings
>> later on, without touching every call site again.
>>
>> The bridge list itself follows the Perl implementation: q35 machines
>> already define the bridges up to three in their machine definition,
>> pci.3 is only needed for virtio-scsi-single, and pci.4 only once a
>> SCSI controller above index one exists or the machine version is new
>> enough to always include it.
>>
>> Signed-off-by: Dominik Csapak <d.csapak@proxmox.com>
>> ---
> 
> [snip]
> 
>> +/// Renders the bridge devices a guest needs, see
>> +/// [`crate::get_pci_bridges`].
>> +pub(crate) fn get_pci_bridges(machine: &Machine) -> Vec<String> {
> 
> nit: naming: If you remove the prefix from the print_ functions, i would
> also remove the prefix here for consistency. i.e., `pci_bridges`.
> 
>> +    // at most four bridges, each contributing a flag and its value
>> +    let mut res = Vec::with_capacity(8);
>> +
>> +    // 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 = machine.version_at_least(11, 1, 0) || machine.max_scsihw > 1;
>> +
>> +    for i in 1..=4 {
>> +        // q35.cfg already includes the bridges up to three
>> +        if i < 4 && machine.q35 {
>> +            continue;
>> +        }
>> +
>> +        if i == 3 && !machine.virtio_scsi_single() {
>> +            continue;
>> +        }
>> +
>> +        if i == 4 && !include_pci4 {
>> +            continue;
>> +        }
>> +
>> +        let id = if i == 2 && machine.legacy_igd {
>> +            format!("pci.{i}-igd")
>> +        } else {
>> +            format!("pci.{i}")
>> +        };
>> +
>> +        let addr = match print_pci_addr(&id, machine.arch) {
>> +            Ok(addr) => addr,
>> +            Err(err) => {
>> +                log::warn!("could not find address for bridge {id}: {err}");
>> +                continue;
>> +            }
> 
> Consider `log::error!` instead of `log::warn!`.
> 
> IMO a missing bridge is a cause for concern and should be reflected with
> an error.

i think returning an error here would even be better. we already
have to touch the perl side, so an additional eval { }
shouldn't hurt (or we simply die there; it shouldn't be possible
anyway?)

> 
>> +        };
>> +        res.push("-device".to_string());
>> +        res.push(format!("pci-bridge,id=pci.{i},chassis_nr={i}{addr}"));
>> +    }
>> +
>> +    res
>> +}
>> +
> 
> [snip]
> 
>>   }
>> diff --git a/pve-qemu-server-pci/src/lib.rs b/pve-qemu-server-pci/src/lib.rs
>> index 5e8b8a7..7d0bd92 100644
>> --- a/pve-qemu-server-pci/src/lib.rs
>> +++ b/pve-qemu-server-pci/src/lib.rs
>> @@ -10,6 +10,9 @@
>>   
>>   pub mod constants;
>>   
>> +mod machine;
>> +pub use machine::Machine;
>> +
>>   mod types;
>>   use pve_api_types::ClusterResourceHostArch as Arch;
>>   pub use types::*;
>> @@ -42,3 +45,18 @@ pub fn print_pcie_addr(id: &str) -> Result<String, PciConfigError> {
>>   pub fn print_pcie_root_port(index: u8) -> Result<String, PciConfigError> {
>>       layout::legacy::print_pcie_root_port(index)
>>   }
>> +
>> +/// Returns the number of the PCI bridge `id` sits on, or `None` if it is on
>> +/// the root bus or not a known device.
>> +pub fn get_pci_bridge_for_device(id: &str) -> Option<u8> {
>> +    layout::legacy::get_pci_bridge_for_device(id)
>> +}
>> +
>> +/// Returns the QEMU arguments for all PCI bridges `machine` needs, as
>> +/// alternating `-device` flags and their values.
>> +///
>> +/// Bridges whose address cannot be determined are skipped with a warning
>> +/// rather than failing the whole guest.
>> +pub fn get_pci_bridges(machine: &Machine) -> Vec<String> {
>> +    layout::legacy::get_pci_bridges(machine)
>> +}
>> diff --git a/pve-qemu-server-pci/src/machine.rs b/pve-qemu-server-pci/src/machine.rs
>> new file mode 100644
>> index 0000000..10ee9db
>> --- /dev/null
>> +++ b/pve-qemu-server-pci/src/machine.rs
>> @@ -0,0 +1,99 @@
>> +use pve_api_types::{ClusterResourceHostArch as Arch, QemuConfigOstype, QemuConfigScsihw};
>> +
>> +/// The parts of a guest configuration that influence which PCI layout is used
>> +/// and which quirks have to be applied to it.
>> +///
>> +/// This is deliberately not the full guest config: it only carries what the
>> +/// address generation needs, so that callers can build it from whatever
>> +/// configuration representation they have.
>> +#[derive(Clone, Debug)]
>> +pub struct Machine {
> 
> nit naming: Machine seems a bit strange to me here, since we a passing
> the relevant guest config. So why not just call it `LayoutContext`, or
> `GuestConfig | `GuestLayoutConfig`.
> 

yeah, machine was not a very well chosen name :P
I'll think about what to name it, thanks for the suggestions!

>> +    pub arch: Arch,
>> +    pub q35: bool,
> 
> Since q35 is a boolean, it is possible to configure:
> Machine { arch: Arch::Aarch64, q35: true }
> 
> What about making the chipset part of the Arch enum like this:
> 
> pub enum X86Chipset {
>      I440fx,
>      Q35,
> }
> 
> pub enum Arch {
>      X8664(X86Chipset),
>      Aarch64,
> }
> 
> This would allow us to remove the q35 boolean value.
> 
> Or if you want to keep a field here, what about a replacing q35 with an
> enum `machine_type` with 3 staes (Q35, I440fx, Virt)

since i want to match (and at some point use) the pve config fields here
having an enum for the 3 machine types makes most sense IMO

> 
>> +    pub scsihw: Option<QemuConfigScsihw>,
>> +    /// Highest `scsihw` controller index in use. Controllers from index 2 on
>> +    /// live on `pci.4`, so this decides whether that bridge is needed.
>> +    pub max_scsihw: u8,
>> +    pub legacy_igd: bool,
>> +    pub ostype: Option<QemuConfigOstype>,
>> +    /// QEMU machine version as `(major, minor, pve)`, where the PVE specific
>> +    /// revision is optional.
>> +    pub version: (u16, u16, Option<u16>),
> 
> nit: naming: Consequently we should rename `version` -> `machine_version`
> 
>> +}
>> +
>> +impl Machine {
>> +    /// Builds a [`Machine`] from the raw configuration values.
>> +    ///
>> +    /// Unparsable `arch`, `scsihw` and `ostype` values are not an error here:
>> +    /// `arch` falls back to x86_64 and the other two to `None`, which is the
>> +    /// same defaulting the Perl implementation does.
>> +    #[allow(clippy::too_many_arguments)]
>> +    pub fn new(
>> +        arch: &str,
>> +        q35: bool,
>> +        scsihw: Option<String>,
>> +        max_scsihw: u8,
>> +        legacy_igd: bool,
>> +        ostype: Option<String>,
>> +        major: u16,
>> +        minor: u16,
>> +        pve: Option<u16>,
>> +    ) -> Self {
>> +        let arch = arch.parse().unwrap_or(Arch::X8664);
>> +        let scsihw = scsihw.and_then(|hw| hw.parse().ok());
>> +        let ostype = ostype.and_then(|ostype| ostype.parse().ok());
>> +        Self {
>> +            arch,
>> +            q35,
>> +            scsihw,
>> +            max_scsihw,
>> +            legacy_igd,
>> +            ostype,
>> +            version: (major, minor, pve),
>> +        }
>> +    }
>> +
>> +    /// Whether the guest's root bus is PCIe. True for q35 machines and for
>> +    /// aarch64, which only has a PCIe host bridge.
>> +    pub fn is_pcie(&self) -> bool {
>> +        self.q35 || self.arch == Arch::Aarch64
>> +    }
>> +
>> +    /// Whether each SCSI disk gets its own controller, which needs the extra
>> +    /// `pci.3` bridge.
>> +    pub fn virtio_scsi_single(&self) -> bool {
>> +        self.scsihw == Some(QemuConfigScsihw::VirtioScsiSingle)
>> +    }
>> +
>> +    /// Whether the machine version is at least `major.minor.pve`.
>> +    ///
>> +    /// A machine without a PVE revision counts as being at least any requested
>> +    /// one, since the plain QEMU version already implies all PVE changes made
>> +    /// for it.
>> +    pub fn version_at_least(&self, major: u16, minor: u16, pve: u16) -> bool {
>> +        if self.version.0 > major {
>> +            return true;
>> +        }
>> +        if self.version.0 < major {
>> +            return false;
>> +        }
>> +
>> +        if self.version.1 > minor {
>> +            return true;
>> +        }
>> +        if self.version.1 < minor {
>> +            return false;
>> +        }
>> +
>> +        if let Some(our_pve) = self.version.2 {
>> +            if our_pve > pve {
>> +                return true;
>> +            }
>> +            if our_pve < pve {
>> +                return false;
>> +            }
>> +        }
>> +
>> +        true
>> +    }
> 
> IMO we can just let rusts tuple compare logic handle this:
> 
> pub fn version_at_least(&self, major: u16, minor: u16, pve: u16) -> bool {
>      let (our_major, our_minor, our_pve) = self.version;
> 	(our_major, our_minor, our_pve.unwrap_or(u16::MAX)) >= (major, minor, pve)
> }

true. This is a thing that should probably live in a helper crate
somewhere. I guess well reuse this kind of version comparison more than 
once.

> 
>> +}
> 





  reply	other threads:[~2026-09-28 14:04 UTC|newest]

Thread overview: 19+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-09-22 10:55 [RFC proxmox-perl-rs/qemu-server/qemu-server-rs 0/9] pci-handling rewrite (part 1) Dominik Csapak
2026-09-22 10:55 ` [PATCH pve-qemu-server-rs 1/9] add pve-qemu-server-pci crate for guest PCI address generation Dominik Csapak
2026-09-28 12:53   ` Elias Huhsovitz
2026-09-28 14:00     ` Dominik Csapak
2026-09-22 10:55 ` [PATCH pve-qemu-server-rs 2/9] pci: add machine abstraction and PCI bridge generation Dominik Csapak
2026-09-28 12:55   ` Elias Huhsovitz
2026-09-28 14:04     ` Dominik Csapak [this message]
2026-09-22 10:55 ` [PATCH pve-qemu-server-rs 3/9] pci: layout: add v2 PCI and PCIe layouts Dominik Csapak
2026-09-28 12:55   ` Elias Huhsovitz
2026-09-28 14:05     ` Dominik Csapak
2026-09-22 10:55 ` [PATCH pve-qemu-server-rs 4/9] fixup! add pve-qemu-server-pci crate for guest PCI address generation Dominik Csapak
2026-09-22 10:55 ` [PATCH proxmox-perl-rs 5/9] pve: add bindings for `pve-qemu-server-pci` crate Dominik Csapak
2026-09-28 12:56   ` Elias Huhsovitz
2026-09-28 14:06     ` Dominik Csapak
2026-09-22 10:55 ` [PATCH proxmox-perl-rs 6/9] pve: pci bindings: add bindings for the `Machine` struct Dominik Csapak
2026-09-22 10:55 ` [PATCH qemu-server 7/9] pci: use PVE::RS::PCI bindings Dominik Csapak
2026-09-22 10:55 ` [PATCH qemu-server 8/9] helpers: factor out the version parts parsing Dominik Csapak
2026-09-22 10:55 ` [PATCH qemu-server 9/9] pci: bridges: use the rust `Machine` struct to pass parameters Dominik Csapak
2026-09-22 11:06 ` [RFC proxmox-perl-rs/qemu-server/qemu-server-rs 0/9] pci-handling rewrite (part 1) Dominik Csapak

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=bc261479-d42e-4131-a808-e61ad01c6a68@proxmox.com \
    --to=d.csapak@proxmox.com \
    --cc=e.huhsovitz@proxmox.com \
    --cc=pve-devel@lists.proxmox.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox
Service provided by Proxmox Server Solutions GmbH | Privacy | Legal