public inbox for pve-devel@lists.proxmox.com
 help / color / mirror / Atom feed
* [PATCH qemu-server v3 0/8] pci: bridges: cleanup and hotplug fix
@ 2026-09-08 13:52 Dominik Csapak
  2026-09-08 13:52 ` [PATCH qemu-server v3 1/8] tests: cfg2cmd: add test for ivshmem with q35 Dominik Csapak
                   ` (7 more replies)
  0 siblings, 8 replies; 9+ messages in thread
From: Dominik Csapak @ 2026-09-08 13:52 UTC (permalink / raw)
  To: pve-devel

Main motivation for this series was a defect in how we handle
hotplugging certain devices:

When e.g. adding scsi15 on a vm with i440fx + lsi controller, we'd need
scsihw2 (since only 7 devices can live on scsihw0 and scsihw1
respectively) which sits on bus pci.4. This itself is not added by
default to the config, so we tried to hotplug the bridge. This works,
but without SHPC (which is neither the default, nor does it work
reliably in all guests) one cannot hot-plug anything into a hot-plugged
bridge. So when hotplugging scsi15 in this example, we'd hot-plug
pci.4, but adding scsihw2 would fail and leave the disk in 'pending'.

When now a live-migration is initiated, the already added bridge is
never recorded in the config, and the guest crashes on the remote
after it's resumed.

Since this is a rather narrow case, I'd opted for preventing
hot-plugging bridges completely.

This version adds a few more cleanup/maintenance patches and tests, but
they should be relatively self-explaining.

changes from v2:
* rebase on master
* improve wording and commit message/subject
* add new tests and factor out some changes
* rename qemu_add_pci_bridges to assert_pci_bridge_present and remove
  its now unneeded parameters (and the unnecessary variable in the
  vm_deviceplug)
* rename get_bridges to get_pci_bridges to make it clear its virtual
  hardware related
* rename get_bridge_for device to get_pci_bridge_for_device to be
  consistent
* new patch that increases the minimum machine version to 5.0
  (was already technically enforced by qemu, just not by our code)
* new patch to add pci.4 by default starting with machine version 11.1
* surely some things i forgot to write down...

changes from v1:
* updated a few callsites of methods which removed the bridges parameter
  which i originally forgot (used 'undef' as that parameter)
* added a fixes trailer for the hotplug commit
* use hostpci_fmt schema to check legacy-igd. parsing twice is not
  optimal, but it's only 16 lines and we can improve upon that later too

Dominik Csapak (8):
  tests: cfg2cmd: add test for ivshmem with q35
  tests: cfg2cmd: add q35 + win7 + hostpci test
  code cleanup: pci: don't export print_pcie_root_port
  code cleanup: hot-plug: simplify getting bridge for device
  hotplug: don't try to hotplug bridges
  cfg2cmd: reject guests with machine version below 5.0
  cfg2cmd: pci: add bridges to device list up front
  cfg2cmd: pci: add pci.4 by default starting with machine version 11.1

 src/PVE/QemuServer.pm                         | 134 ++++++------------
 src/PVE/QemuServer/Drive.pm                   |   2 +-
 src/PVE/QemuServer/DriveDevice.pm             |  23 ++-
 src/PVE/QemuServer/PCI.pm                     |  68 ++++++++-
 src/PVE/QemuServer/RNG.pm                     |   4 +-
 src/PVE/QemuServer/USB.pm                     |   8 +-
 src/test/cfg2cmd/efi-raw-old.conf             |   4 +-
 src/test/cfg2cmd/efi-raw-old.conf.cmd         |   4 +-
 src/test/cfg2cmd/linux-bridges-qemu-11.1.conf |  14 ++
 .../cfg2cmd/linux-bridges-qemu-11.1.conf.cmd  |  36 +++++
 .../old-unsupported-machine-version.conf      |   4 +
 src/test/cfg2cmd/pinned-version-pxe-pve.conf  |   2 +-
 .../cfg2cmd/pinned-version-pxe-pve.conf.cmd   |   2 +-
 src/test/cfg2cmd/pinned-version.conf          |   2 +-
 src/test/cfg2cmd/pinned-version.conf.cmd      |   4 +-
 src/test/cfg2cmd/q35-linux-ivshmem.conf       |  13 ++
 src/test/cfg2cmd/q35-linux-ivshmem.conf.cmd   |  35 +++++
 src/test/cfg2cmd/q35-usb13-error.conf         |   2 +-
 src/test/cfg2cmd/q35-usb2.conf                |   2 +-
 src/test/cfg2cmd/q35-usb2.conf.cmd            |   4 +-
 src/test/cfg2cmd/q35-usb3.conf                |   2 +-
 src/test/cfg2cmd/q35-usb3.conf.cmd            |   4 +-
 src/test/cfg2cmd/q35-win7-hostpci.conf        |  17 +++
 src/test/cfg2cmd/q35-win7-hostpci.conf.cmd    |  37 +++++
 src/test/cfg2cmd/spice-enhancments.conf       |   2 +-
 src/test/cfg2cmd/spice-enhancments.conf.cmd   |   4 +-
 ...ce-linux-4.1.conf => spice-linux-5.0.conf} |   2 +-
 ...-4.1.conf.cmd => spice-linux-5.0.conf.cmd} |   2 +-
 src/test/cfg2cmd/spice-usb3.conf              |   2 +-
 src/test/cfg2cmd/spice-usb3.conf.cmd          |   4 +-
 src/test/cfg2cmd/spice-win.conf               |   2 +-
 src/test/cfg2cmd/spice-win.conf.cmd           |   2 +-
 src/test/cfg2cmd/usb13-error.conf             |   2 +-
 33 files changed, 316 insertions(+), 133 deletions(-)
 create mode 100644 src/test/cfg2cmd/linux-bridges-qemu-11.1.conf
 create mode 100644 src/test/cfg2cmd/linux-bridges-qemu-11.1.conf.cmd
 create mode 100644 src/test/cfg2cmd/old-unsupported-machine-version.conf
 create mode 100644 src/test/cfg2cmd/q35-linux-ivshmem.conf
 create mode 100644 src/test/cfg2cmd/q35-linux-ivshmem.conf.cmd
 create mode 100644 src/test/cfg2cmd/q35-win7-hostpci.conf
 create mode 100644 src/test/cfg2cmd/q35-win7-hostpci.conf.cmd
 rename src/test/cfg2cmd/{spice-linux-4.1.conf => spice-linux-5.0.conf} (92%)
 rename src/test/cfg2cmd/{spice-linux-4.1.conf.cmd => spice-linux-5.0.conf.cmd} (97%)

-- 
2.47.3





^ permalink raw reply	[flat|nested] 9+ messages in thread

* [PATCH qemu-server v3 1/8] tests: cfg2cmd: add test for ivshmem with q35
  2026-09-08 13:52 [PATCH qemu-server v3 0/8] pci: bridges: cleanup and hotplug fix Dominik Csapak
@ 2026-09-08 13:52 ` Dominik Csapak
  2026-09-08 13:52 ` [PATCH qemu-server v3 2/8] tests: cfg2cmd: add q35 + win7 + hostpci test Dominik Csapak
                   ` (6 subsequent siblings)
  7 siblings, 0 replies; 9+ messages in thread
From: Dominik Csapak @ 2026-09-08 13:52 UTC (permalink / raw)
  To: pve-devel

this has an address on a pcie bus (vs pci for most other devices), so it
warrants a separate test.

Signed-off-by: Dominik Csapak <d.csapak@proxmox.com>
---
 src/test/cfg2cmd/q35-linux-ivshmem.conf     | 13 ++++++++
 src/test/cfg2cmd/q35-linux-ivshmem.conf.cmd | 35 +++++++++++++++++++++
 2 files changed, 48 insertions(+)
 create mode 100644 src/test/cfg2cmd/q35-linux-ivshmem.conf
 create mode 100644 src/test/cfg2cmd/q35-linux-ivshmem.conf.cmd

diff --git a/src/test/cfg2cmd/q35-linux-ivshmem.conf b/src/test/cfg2cmd/q35-linux-ivshmem.conf
new file mode 100644
index 00000000..6e007c12
--- /dev/null
+++ b/src/test/cfg2cmd/q35-linux-ivshmem.conf
@@ -0,0 +1,13 @@
+# TEST: Config with q35 and ivshmem
+bootdisk: scsi0
+cores: 1
+machine: q35
+memory: 512
+net0: virtio=2E:01:68:F9:9C:87,bridge=vmbr0
+numa: 1
+ostype: l26
+ivshmem: size=512
+scsihw: virtio-scsi-pci
+smbios1: uuid=3dd750ce-d910-44d0-9493-525c0be4e687
+sockets: 2
+vmgenid: 54d1c06c-8f5b-440f-b5b2-6eab1380e13d
diff --git a/src/test/cfg2cmd/q35-linux-ivshmem.conf.cmd b/src/test/cfg2cmd/q35-linux-ivshmem.conf.cmd
new file mode 100644
index 00000000..b4fb7f08
--- /dev/null
+++ b/src/test/cfg2cmd/q35-linux-ivshmem.conf.cmd
@@ -0,0 +1,35 @@
+/usr/bin/kvm \
+  -id 8006 \
+  -name vm8006 \
+  -no-shutdown \
+  -chardev 'socket,id=qmp,path=/var/run/qemu-server/8006.qmp,server=on,wait=off' \
+  -mon 'chardev=qmp,mode=control' \
+  -chardev 'socket,id=qmp-event,path=/var/run/qmeventd.sock,reconnect-ms=5000' \
+  -mon 'chardev=qmp-event,mode=control' \
+  -pidfile /var/run/qemu-server/8006.pid \
+  -daemonize \
+  -smbios 'type=1,uuid=3dd750ce-d910-44d0-9493-525c0be4e687' \
+  -global 'ICH9-LPC.acpi-pci-hotplug-with-bridge-support=off' \
+  -smp '2,sockets=2,cores=1,maxcpus=2' \
+  -nodefaults \
+  -boot 'menu=on,strict=on,reboot-timeout=1000,splash=/usr/share/qemu-server/bootsplash.jpg' \
+  -vnc 'unix:/var/run/qemu-server/8006.vnc,password=on' \
+  -cpu kvm64,enforce,+kvm_pv_eoi,+kvm_pv_unhalt,+lahf_lm,+sep \
+  -m 512 \
+  -object 'memory-backend-ram,id=ram-node0,size=256M' \
+  -numa 'node,nodeid=0,cpus=0,memdev=ram-node0' \
+  -object 'memory-backend-ram,id=ram-node1,size=256M' \
+  -numa 'node,nodeid=1,cpus=1,memdev=ram-node1' \
+  -global 'ICH9-LPC.disable_s3=1' \
+  -global 'ICH9-LPC.disable_s4=1' \
+  -readconfig /usr/share/qemu-server/pve-q35-4.0.cfg \
+  -device 'vmgenid,guid=54d1c06c-8f5b-440f-b5b2-6eab1380e13d' \
+  -device 'usb-tablet,id=tablet,bus=ehci.0,port=1' \
+  -device 'VGA,id=vga,bus=pcie.0,addr=0x1' \
+  -device 'virtio-balloon-pci,id=balloon0,bus=pci.0,addr=0x3,free-page-reporting=on' \
+  -iscsi 'initiator-name=iqn.1993-08.org.debian:01:aabbccddeeff' \
+  -netdev 'type=tap,id=net0,ifname=tap8006i0,script=/usr/libexec/qemu-server/pve-bridge,downscript=/usr/libexec/qemu-server/pve-bridgedown,vhost=on' \
+  -device 'virtio-net-pci,mac=2E:01:68:F9:9C:87,netdev=net0,bus=pci.0,addr=0x12,id=net0,rx_queue_size=1024,tx_queue_size=256,bootindex=300,host_mtu=1500,host_tunnel=off' \
+  -device 'ivshmem-plain,memdev=ivshmem,bus=pcie.0,addr=0x14,' \
+  -object 'memory-backend-file,id=ivshmem,share=on,mem-path=/dev/shm/pve-shm-8006,size=512M' \
+  -machine 'hpet=off,type=q35+pve0'
-- 
2.47.3





^ permalink raw reply related	[flat|nested] 9+ messages in thread

* [PATCH qemu-server v3 2/8] tests: cfg2cmd: add q35 + win7 + hostpci test
  2026-09-08 13:52 [PATCH qemu-server v3 0/8] pci: bridges: cleanup and hotplug fix Dominik Csapak
  2026-09-08 13:52 ` [PATCH qemu-server v3 1/8] tests: cfg2cmd: add test for ivshmem with q35 Dominik Csapak
@ 2026-09-08 13:52 ` Dominik Csapak
  2026-09-08 13:52 ` [PATCH qemu-server v3 3/8] code cleanup: pci: don't export print_pcie_root_port Dominik Csapak
                   ` (5 subsequent siblings)
  7 siblings, 0 replies; 9+ messages in thread
From: Dominik Csapak @ 2026-09-08 13:52 UTC (permalink / raw)
  To: pve-devel

For windows 7 and q35, the hostpci devices have different addresses on
the pcie bus, so test for that.

Signed-off-by: Dominik Csapak <d.csapak@proxmox.com>
---
 src/test/cfg2cmd/q35-win7-hostpci.conf     | 17 ++++++++++
 src/test/cfg2cmd/q35-win7-hostpci.conf.cmd | 37 ++++++++++++++++++++++
 2 files changed, 54 insertions(+)
 create mode 100644 src/test/cfg2cmd/q35-win7-hostpci.conf
 create mode 100644 src/test/cfg2cmd/q35-win7-hostpci.conf.cmd

diff --git a/src/test/cfg2cmd/q35-win7-hostpci.conf b/src/test/cfg2cmd/q35-win7-hostpci.conf
new file mode 100644
index 00000000..dac7d538
--- /dev/null
+++ b/src/test/cfg2cmd/q35-win7-hostpci.conf
@@ -0,0 +1,17 @@
+# TEST: Config with q35, hostpci passthrough as pcie & Windows7
+bios: ovmf
+bootdisk: scsi0
+cores: 1
+efidisk0: local:100/vm-100-disk-1.qcow2,size=128K
+hostpci0: f0:42.0
+hostpci1: f0:43.0,pcie=1
+hostpci4: 00:43.1,pcie=1
+machine: q35
+memory: 512
+net0: virtio=2E:01:68:F9:9C:87,bridge=vmbr0
+numa: 1
+ostype: win7
+scsihw: virtio-scsi-pci
+smbios1: uuid=3dd750ce-d910-44d0-9493-525c0be4e687
+sockets: 2
+vmgenid: 54d1c06c-8f5b-440f-b5b2-6eab1380e13d
diff --git a/src/test/cfg2cmd/q35-win7-hostpci.conf.cmd b/src/test/cfg2cmd/q35-win7-hostpci.conf.cmd
new file mode 100644
index 00000000..66f8aec5
--- /dev/null
+++ b/src/test/cfg2cmd/q35-win7-hostpci.conf.cmd
@@ -0,0 +1,37 @@
+/usr/bin/kvm \
+  -id 8006 \
+  -name vm8006 \
+  -no-shutdown \
+  -chardev 'socket,id=qmp,path=/var/run/qemu-server/8006.qmp,server=on,wait=off' \
+  -mon 'chardev=qmp,mode=control' \
+  -chardev 'socket,id=qmp-event,path=/var/run/qmeventd.sock,reconnect-ms=5000' \
+  -mon 'chardev=qmp-event,mode=control' \
+  -pidfile /var/run/qemu-server/8006.pid \
+  -daemonize \
+  -smbios 'type=1,uuid=3dd750ce-d910-44d0-9493-525c0be4e687' \
+  -drive 'if=pflash,unit=0,format=raw,readonly=on,file=/usr/share/pve-edk2-firmware//OVMF_CODE.fd' \
+  -drive 'if=pflash,unit=1,id=drive-efidisk0,format=qcow2,file=/var/lib/vz/images/100/vm-100-disk-1.qcow2' \
+  -smp '2,sockets=2,cores=1,maxcpus=2' \
+  -nodefaults \
+  -boot 'menu=on,strict=on,reboot-timeout=1000,splash=/usr/share/qemu-server/bootsplash.jpg' \
+  -vnc 'unix:/var/run/qemu-server/8006.vnc,password=on' \
+  -global 'kvm-pit.lost_tick_policy=discard' \
+  -cpu kvm64,enforce,+kvm_pv_eoi,+kvm_pv_unhalt,+lahf_lm,+sep \
+  -m 512 \
+  -object 'memory-backend-ram,id=ram-node0,size=256M' \
+  -numa 'node,nodeid=0,cpus=0,memdev=ram-node0' \
+  -object 'memory-backend-ram,id=ram-node1,size=256M' \
+  -numa 'node,nodeid=1,cpus=1,memdev=ram-node1' \
+  -readconfig /usr/share/qemu-server/pve-q35-4.0.cfg \
+  -device 'vmgenid,guid=54d1c06c-8f5b-440f-b5b2-6eab1380e13d' \
+  -device 'usb-tablet,id=tablet,bus=ehci.0,port=1' \
+  -device 'vfio-pci,host=0000:f0:42.0,id=hostpci0,bus=pci.0,addr=0x10' \
+  -device 'vfio-pci,host=0000:f0:43.0,id=hostpci1,bus=pcie.0,addr=0x11' \
+  -device 'vfio-pci,host=0000:00:43.1,id=hostpci4,bus=pcie.0,addr=0x9' \
+  -device 'VGA,id=vga,bus=pcie.0,addr=0x1' \
+  -device 'virtio-balloon-pci,id=balloon0,bus=pci.0,addr=0x3' \
+  -iscsi 'initiator-name=iqn.1993-08.org.debian:01:aabbccddeeff' \
+  -netdev 'type=tap,id=net0,ifname=tap8006i0,script=/usr/libexec/qemu-server/pve-bridge,downscript=/usr/libexec/qemu-server/pve-bridgedown,vhost=on' \
+  -device 'virtio-net-pci,mac=2E:01:68:F9:9C:87,netdev=net0,bus=pci.0,addr=0x12,id=net0,bootindex=300' \
+  -rtc 'driftfix=slew,base=localtime' \
+  -machine 'hpet=off,type=pc-q35-5.1+pve0'
-- 
2.47.3





^ permalink raw reply related	[flat|nested] 9+ messages in thread

* [PATCH qemu-server v3 3/8] code cleanup: pci: don't export print_pcie_root_port
  2026-09-08 13:52 [PATCH qemu-server v3 0/8] pci: bridges: cleanup and hotplug fix Dominik Csapak
  2026-09-08 13:52 ` [PATCH qemu-server v3 1/8] tests: cfg2cmd: add test for ivshmem with q35 Dominik Csapak
  2026-09-08 13:52 ` [PATCH qemu-server v3 2/8] tests: cfg2cmd: add q35 + win7 + hostpci test Dominik Csapak
@ 2026-09-08 13:52 ` Dominik Csapak
  2026-09-08 13:52 ` [PATCH qemu-server v3 4/8] code cleanup: hot-plug: simplify getting bridge for device Dominik Csapak
                   ` (4 subsequent siblings)
  7 siblings, 0 replies; 9+ messages in thread
From: Dominik Csapak @ 2026-09-08 13:52 UTC (permalink / raw)
  To: pve-devel

This is only used in the PCI module itself, so it can be private.

Signed-off-by: Dominik Csapak <d.csapak@proxmox.com>
---
 src/PVE/QemuServer.pm     | 2 +-
 src/PVE/QemuServer/PCI.pm | 3 +--
 2 files changed, 2 insertions(+), 3 deletions(-)

diff --git a/src/PVE/QemuServer.pm b/src/PVE/QemuServer.pm
index 149f17be..8c8d3df8 100644
--- a/src/PVE/QemuServer.pm
+++ b/src/PVE/QemuServer.pm
@@ -86,7 +86,7 @@ use PVE::QemuServer::MetaInfo;
 use PVE::QemuServer::Monitor qw(mon_cmd qmp_cmd vm_qmp_peer);
 use PVE::QemuServer::Network;
 use PVE::QemuServer::OVMF;
-use PVE::QemuServer::PCI qw(print_pci_addr print_pcie_addr print_pcie_root_port parse_hostpci);
+use PVE::QemuServer::PCI qw(print_pci_addr print_pcie_addr parse_hostpci);
 use PVE::QemuServer::PCI::Mdev;
 use PVE::QemuServer::QemuImage;
 use PVE::QemuServer::QMPHelpers
diff --git a/src/PVE/QemuServer/PCI.pm b/src/PVE/QemuServer/PCI.pm
index 0b67943c..bae46cb7 100644
--- a/src/PVE/QemuServer/PCI.pm
+++ b/src/PVE/QemuServer/PCI.pm
@@ -19,7 +19,6 @@ use base 'Exporter';
 our @EXPORT_OK = qw(
     print_pci_addr
     print_pcie_addr
-    print_pcie_root_port
     parse_hostpci
 );
 
@@ -372,7 +371,7 @@ sub print_pcie_addr {
 
 # Generates the device strings for additional pcie root ports. The first 4 pcie
 # root ports are defined in the pve-q35*.cfg files.
-sub print_pcie_root_port {
+my sub print_pcie_root_port {
     my ($i) = @_;
     my $res = '';
 
-- 
2.47.3





^ permalink raw reply related	[flat|nested] 9+ messages in thread

* [PATCH qemu-server v3 4/8] code cleanup: hot-plug: simplify getting bridge for device
  2026-09-08 13:52 [PATCH qemu-server v3 0/8] pci: bridges: cleanup and hotplug fix Dominik Csapak
                   ` (2 preceding siblings ...)
  2026-09-08 13:52 ` [PATCH qemu-server v3 3/8] code cleanup: pci: don't export print_pcie_root_port Dominik Csapak
@ 2026-09-08 13:52 ` Dominik Csapak
  2026-09-08 13:52 ` [PATCH qemu-server v3 5/8] hotplug: don't try to hotplug bridges Dominik Csapak
                   ` (3 subsequent siblings)
  7 siblings, 0 replies; 9+ messages in thread
From: Dominik Csapak @ 2026-09-08 13:52 UTC (permalink / raw)
  To: pve-devel

Instead of routing through `print_pci_addr` and checking which bridges
are needed, simply expose a new function that does exactly return which
`bus` (which translates to a bridge) is used for the device.

Signed-off-by: Dominik Csapak <d.csapak@proxmox.com>
---
 src/PVE/QemuServer.pm     | 10 +---------
 src/PVE/QemuServer/PCI.pm | 11 +++++++++++
 2 files changed, 12 insertions(+), 9 deletions(-)

diff --git a/src/PVE/QemuServer.pm b/src/PVE/QemuServer.pm
index 8c8d3df8..ce3eea4d 100644
--- a/src/PVE/QemuServer.pm
+++ b/src/PVE/QemuServer.pm
@@ -4218,15 +4218,7 @@ sub qemu_deletescsihw {
 sub qemu_add_pci_bridge {
     my ($storecfg, $conf, $vmid, $device, $arch, $machine_type) = @_;
 
-    my $bridges = {};
-
-    my $bridgeid;
-
-    print_pci_addr($device, $bridges, $arch);
-
-    while (my ($k, $v) = each %$bridges) {
-        $bridgeid = $k;
-    }
+    my $bridgeid = PVE::QemuServer::PCI::get_pci_bridge_for_device($device);
     return 1 if !defined($bridgeid) || $bridgeid < 1;
 
     my $bridge = "pci.$bridgeid";
diff --git a/src/PVE/QemuServer/PCI.pm b/src/PVE/QemuServer/PCI.pm
index bae46cb7..8069f104 100644
--- a/src/PVE/QemuServer/PCI.pm
+++ b/src/PVE/QemuServer/PCI.pm
@@ -291,6 +291,17 @@ my $get_addr_mapping_from_id = sub {
     return { bus => $d->{bus}, addr => sprintf("0x%x", $d->{addr}) };
 };
 
+sub get_pci_bridge_for_device {
+    my ($id) = @_;
+
+    my $map = get_pci_addr_map();
+    if (my $d = $get_addr_mapping_from_id->($map, $id)) {
+        return $d->{bus};
+    }
+
+    return;
+}
+
 sub print_pci_addr {
     my ($id, $bridges, $arch) = @_;
 
-- 
2.47.3





^ permalink raw reply related	[flat|nested] 9+ messages in thread

* [PATCH qemu-server v3 5/8] hotplug: don't try to hotplug bridges
  2026-09-08 13:52 [PATCH qemu-server v3 0/8] pci: bridges: cleanup and hotplug fix Dominik Csapak
                   ` (3 preceding siblings ...)
  2026-09-08 13:52 ` [PATCH qemu-server v3 4/8] code cleanup: hot-plug: simplify getting bridge for device Dominik Csapak
@ 2026-09-08 13:52 ` Dominik Csapak
  2026-09-08 13:52 ` [PATCH qemu-server v3 6/8] cfg2cmd: reject guests with machine version below 5.0 Dominik Csapak
                   ` (2 subsequent siblings)
  7 siblings, 0 replies; 9+ messages in thread
From: Dominik Csapak @ 2026-09-08 13:52 UTC (permalink / raw)
  To: pve-devel

While bridges can be hotplugged (on PCI on i440fx), no device can be
hotplugged in that afterwards. For that to work the SHPC option would
have to be enabled and the guest must support that. Since this is not
guaranteed to work and bridges don't show up in our config, this could
lead to bridges added that are not represented in the config.

To be on the safe side, simply don't allow hotplugging bridges at all.
Luckily, the only bridge we ever tried hotplugging (since machine
version 2.3) was pci.4 which only houses scsihw2/3/4 at the moment.

These are only used for scsiX where X > 13 and only if the scsihw is an
LSI controller, so not very likely to occur.

This fixes an issue where trying to hotplug a scsi disk with index >=14
on a i440fx machine with an LSI scsi controller would leave the bridge
around after failing to add the scsi controller, and the machine would
subsequently crash on live migration.

Fixes: 2513b862 (fix #2566: increase scsi limit to 31)
Signed-off-by: Dominik Csapak <d.csapak@proxmox.com>
---
 src/PVE/QemuServer.pm | 19 +++++--------------
 1 file changed, 5 insertions(+), 14 deletions(-)

diff --git a/src/PVE/QemuServer.pm b/src/PVE/QemuServer.pm
index ce3eea4d..fbb0a23e 100644
--- a/src/PVE/QemuServer.pm
+++ b/src/PVE/QemuServer.pm
@@ -3899,13 +3899,11 @@ sub vm_devices_list {
 sub vm_deviceplug {
     my ($storecfg, $conf, $vmid, $deviceid, $device, $arch, $machine_type) = @_;
 
-    my $q35 = PVE::QemuServer::Machine::machine_type_is_q35($conf);
-
     my $devices_list = vm_devices_list($vmid);
     return 1 if defined($devices_list->{$deviceid});
 
-    # add PCI bridge if we need it for the device
-    qemu_add_pci_bridge($storecfg, $conf, $vmid, $deviceid, $arch, $machine_type);
+    # we can't hotplug bridges, so check if the necessary one exists
+    assert_pci_bridge_present($vmid, $deviceid);
 
     if ($deviceid eq 'tablet') {
         qemu_deviceadd($vmid, print_tabletdevice_full($conf, $arch));
@@ -3992,13 +3990,6 @@ sub vm_deviceplug {
             warn $@ if $@;
             die $err;
         }
-    } elsif (!$q35 && $deviceid =~ m/^(pci\.)(\d+)$/) {
-        my $bridgeid = $2;
-        my $pciaddr = print_pci_addr($deviceid, undef, $arch);
-        my $devicefull = "pci-bridge,id=pci.$bridgeid,chassis_nr=$bridgeid$pciaddr";
-
-        qemu_deviceadd($vmid, $devicefull);
-        qemu_deviceaddverify($vmid, $deviceid);
     } else {
         die "can't hotplug device '$deviceid'\n";
     }
@@ -4215,8 +4206,8 @@ sub qemu_deletescsihw {
     return 1;
 }
 
-sub qemu_add_pci_bridge {
-    my ($storecfg, $conf, $vmid, $device, $arch, $machine_type) = @_;
+sub assert_pci_bridge_present {
+    my ($vmid, $device) = @_;
 
     my $bridgeid = PVE::QemuServer::PCI::get_pci_bridge_for_device($device);
     return 1 if !defined($bridgeid) || $bridgeid < 1;
@@ -4225,7 +4216,7 @@ sub qemu_add_pci_bridge {
     my $devices_list = vm_devices_list($vmid);
 
     if (!defined($devices_list->{$bridge})) {
-        vm_deviceplug($storecfg, $conf, $vmid, $bridge, $arch, $machine_type);
+        die "can't hotplug bridge necessary for '$device'\n";
     }
 
     return 1;
-- 
2.47.3





^ permalink raw reply related	[flat|nested] 9+ messages in thread

* [PATCH qemu-server v3 6/8] cfg2cmd: reject guests with machine version below 5.0
  2026-09-08 13:52 [PATCH qemu-server v3 0/8] pci: bridges: cleanup and hotplug fix Dominik Csapak
                   ` (4 preceding siblings ...)
  2026-09-08 13:52 ` [PATCH qemu-server v3 5/8] hotplug: don't try to hotplug bridges Dominik Csapak
@ 2026-09-08 13:52 ` Dominik Csapak
  2026-09-08 13:52 ` [PATCH qemu-server v3 7/8] cfg2cmd: pci: add bridges to device list up front Dominik Csapak
  2026-09-08 13:52 ` [PATCH qemu-server v3 8/8] cfg2cmd: pci: add pci.4 by default starting with machine version 11.1 Dominik Csapak
  7 siblings, 0 replies; 9+ messages in thread
From: Dominik Csapak @ 2026-09-08 13:52 UTC (permalink / raw)
  To: pve-devel

since commit
 c6b32373 (pbs-restore: set 'no-cache' on block devices backed by zfspool)

qemu-server depends on qemu 11, which dropped machine versions below
5.0[0]. This makes it impossible for us to start them, so reject them
early in `config_to_command`.

This means we have to adapt all tests that reference machine versions
before 5.0. Also add an extra test which tests the new error message.

0: https://wiki.qemu.org/ChangeLog/11.0

Signed-off-by: Dominik Csapak <d.csapak@proxmox.com>
---
 src/PVE/QemuServer.pm                                        | 5 +++++
 src/test/cfg2cmd/efi-raw-old.conf                            | 4 ++--
 src/test/cfg2cmd/efi-raw-old.conf.cmd                        | 4 ++--
 src/test/cfg2cmd/old-unsupported-machine-version.conf        | 4 ++++
 src/test/cfg2cmd/pinned-version-pxe-pve.conf                 | 2 +-
 src/test/cfg2cmd/pinned-version-pxe-pve.conf.cmd             | 2 +-
 src/test/cfg2cmd/pinned-version.conf                         | 2 +-
 src/test/cfg2cmd/pinned-version.conf.cmd                     | 4 ++--
 src/test/cfg2cmd/q35-usb13-error.conf                        | 2 +-
 src/test/cfg2cmd/q35-usb2.conf                               | 2 +-
 src/test/cfg2cmd/q35-usb2.conf.cmd                           | 4 ++--
 src/test/cfg2cmd/q35-usb3.conf                               | 2 +-
 src/test/cfg2cmd/q35-usb3.conf.cmd                           | 4 ++--
 src/test/cfg2cmd/spice-enhancments.conf                      | 2 +-
 src/test/cfg2cmd/spice-enhancments.conf.cmd                  | 4 ++--
 .../cfg2cmd/{spice-linux-4.1.conf => spice-linux-5.0.conf}   | 2 +-
 .../{spice-linux-4.1.conf.cmd => spice-linux-5.0.conf.cmd}   | 2 +-
 src/test/cfg2cmd/spice-usb3.conf                             | 2 +-
 src/test/cfg2cmd/spice-usb3.conf.cmd                         | 4 ++--
 src/test/cfg2cmd/spice-win.conf                              | 2 +-
 src/test/cfg2cmd/spice-win.conf.cmd                          | 2 +-
 src/test/cfg2cmd/usb13-error.conf                            | 2 +-
 22 files changed, 36 insertions(+), 27 deletions(-)
 create mode 100644 src/test/cfg2cmd/old-unsupported-machine-version.conf
 rename src/test/cfg2cmd/{spice-linux-4.1.conf => spice-linux-5.0.conf} (92%)
 rename src/test/cfg2cmd/{spice-linux-4.1.conf.cmd => spice-linux-5.0.conf.cmd} (97%)

diff --git a/src/PVE/QemuServer.pm b/src/PVE/QemuServer.pm
index fbb0a23e..08580fed 100644
--- a/src/PVE/QemuServer.pm
+++ b/src/PVE/QemuServer.pm
@@ -3192,6 +3192,11 @@ sub config_to_command {
             . " or enable in BIOS.\n";
     }
 
+    # check for the minimum required machine version explicitly
+    if (!$version_guard->(5, 0)) {
+        die "unsupported machine version '$machine_version'. At least version 5.0 is required\n";
+    }
+
     my $q35 = PVE::QemuServer::Machine::machine_type_is_q35($conf);
     my $hotplug_features =
         parse_hotplug_features(defined($conf->{hotplug}) ? $conf->{hotplug} : '1');
diff --git a/src/test/cfg2cmd/efi-raw-old.conf b/src/test/cfg2cmd/efi-raw-old.conf
index 621470ed..fd768d3f 100644
--- a/src/test/cfg2cmd/efi-raw-old.conf
+++ b/src/test/cfg2cmd/efi-raw-old.conf
@@ -1,5 +1,5 @@
-# TEST: Test raw efidisk size parameter on old version
+# TEST: Test raw efidisk size parameter on old (pre-blockdev) machine version
 smbios1: uuid=7b10d7af-b932-4c66-b2c3-3996152ec465
 bios: ovmf
-machine: pc-i440fx-4.1+pve0
+machine: pc-i440fx-5.0+pve0
 efidisk0: local:100/vm-100-disk-0.raw
diff --git a/src/test/cfg2cmd/efi-raw-old.conf.cmd b/src/test/cfg2cmd/efi-raw-old.conf.cmd
index 774607e6..d7bc32c6 100644
--- a/src/test/cfg2cmd/efi-raw-old.conf.cmd
+++ b/src/test/cfg2cmd/efi-raw-old.conf.cmd
@@ -10,7 +10,7 @@
   -daemonize \
   -smbios 'type=1,uuid=7b10d7af-b932-4c66-b2c3-3996152ec465' \
   -drive 'if=pflash,unit=0,format=raw,readonly=on,file=/usr/share/pve-edk2-firmware//OVMF_CODE.fd' \
-  -drive 'if=pflash,unit=1,id=drive-efidisk0,format=raw,file=/var/lib/vz/images/100/vm-100-disk-0.raw' \
+  -drive 'if=pflash,unit=1,id=drive-efidisk0,format=raw,file=/var/lib/vz/images/100/vm-100-disk-0.raw,size=131072' \
   -smp '1,sockets=1,cores=1,maxcpus=1' \
   -nodefaults \
   -boot 'menu=on,strict=on,reboot-timeout=1000,splash=/usr/share/qemu-server/bootsplash.jpg' \
@@ -24,4 +24,4 @@
   -device 'VGA,id=vga,bus=pci.0,addr=0x2' \
   -device 'virtio-balloon-pci,id=balloon0,bus=pci.0,addr=0x3' \
   -iscsi 'initiator-name=iqn.1993-08.org.debian:01:aabbccddeeff' \
-  -machine 'type=pc-i440fx-4.1+pve0'
+  -machine 'type=pc-i440fx-5.0+pve0'
diff --git a/src/test/cfg2cmd/old-unsupported-machine-version.conf b/src/test/cfg2cmd/old-unsupported-machine-version.conf
new file mode 100644
index 00000000..f2d3586d
--- /dev/null
+++ b/src/test/cfg2cmd/old-unsupported-machine-version.conf
@@ -0,0 +1,4 @@
+# TEST: Test machine version minimum requirement and expect fail on old version
+# EXPECT_ERROR: unsupported machine version '4.0'. At least version 5.0 is required
+smbios1: uuid=7b10d7af-b932-4c66-b2c3-3996152ec465
+machine: pc-i440fx-4.0
diff --git a/src/test/cfg2cmd/pinned-version-pxe-pve.conf b/src/test/cfg2cmd/pinned-version-pxe-pve.conf
index 36169d7b..21b92149 100644
--- a/src/test/cfg2cmd/pinned-version-pxe-pve.conf
+++ b/src/test/cfg2cmd/pinned-version-pxe-pve.conf
@@ -2,7 +2,7 @@
 bootdisk: scsi0
 cores: 3
 ide2: none,media=cdrom
-machine: pc-q35-4.1+pve2.pxe
+machine: pc-q35-5.0+pve0.pxe
 memory: 1024
 name: pinned
 net0: virtio=A2:C0:43:77:08:A1,bridge=vmbr0
diff --git a/src/test/cfg2cmd/pinned-version-pxe-pve.conf.cmd b/src/test/cfg2cmd/pinned-version-pxe-pve.conf.cmd
index 41360544..fe7f1a2e 100644
--- a/src/test/cfg2cmd/pinned-version-pxe-pve.conf.cmd
+++ b/src/test/cfg2cmd/pinned-version-pxe-pve.conf.cmd
@@ -30,4 +30,4 @@
   -device 'scsi-hd,bus=scsihw0.0,channel=0,scsi-id=0,lun=0,drive=drive-scsi0,id=scsi0,bootindex=100' \
   -netdev 'type=tap,id=net0,ifname=tap8006i0,script=/usr/libexec/qemu-server/pve-bridge,downscript=/usr/libexec/qemu-server/pve-bridgedown,vhost=on' \
   -device 'virtio-net-pci,mac=A2:C0:43:77:08:A1,netdev=net0,bus=pci.0,addr=0x12,id=net0,bootindex=300,romfile=pxe-virtio.rom' \
-  -machine 'type=pc-q35-4.1+pve2'
+  -machine 'type=pc-q35-5.0+pve0'
diff --git a/src/test/cfg2cmd/pinned-version.conf b/src/test/cfg2cmd/pinned-version.conf
index 61191836..39b5f0ed 100644
--- a/src/test/cfg2cmd/pinned-version.conf
+++ b/src/test/cfg2cmd/pinned-version.conf
@@ -2,7 +2,7 @@
 bootdisk: scsi0
 cores: 3
 ide2: none,media=cdrom
-machine: pc-q35-3.1
+machine: pc-q35-5.0
 memory: 1024
 name: pinned
 net0: virtio=A2:C0:43:77:08:A1,bridge=vmbr0
diff --git a/src/test/cfg2cmd/pinned-version.conf.cmd b/src/test/cfg2cmd/pinned-version.conf.cmd
index e26b35c6..3353da19 100644
--- a/src/test/cfg2cmd/pinned-version.conf.cmd
+++ b/src/test/cfg2cmd/pinned-version.conf.cmd
@@ -15,7 +15,7 @@
   -vnc 'unix:/var/run/qemu-server/8006.vnc,password=on' \
   -cpu kvm64,enforce,+kvm_pv_eoi,+kvm_pv_unhalt,+lahf_lm,+sep \
   -m 1024 \
-  -readconfig /usr/share/qemu-server/pve-q35.cfg \
+  -readconfig /usr/share/qemu-server/pve-q35-4.0.cfg \
   -device 'vmgenid,guid=bdd46b98-fefc-11e9-97b4-d72c378e0f96' \
   -device 'usb-tablet,id=tablet,bus=ehci.0,port=1' \
   -device 'VGA,id=vga,bus=pcie.0,addr=0x1' \
@@ -28,4 +28,4 @@
   -device 'scsi-hd,bus=scsihw0.0,channel=0,scsi-id=0,lun=0,drive=drive-scsi0,id=scsi0,bootindex=100' \
   -netdev 'type=tap,id=net0,ifname=tap8006i0,script=/usr/libexec/qemu-server/pve-bridge,downscript=/usr/libexec/qemu-server/pve-bridgedown,vhost=on' \
   -device 'virtio-net-pci,mac=A2:C0:43:77:08:A1,netdev=net0,bus=pci.0,addr=0x12,id=net0,bootindex=300' \
-  -machine 'type=pc-q35-3.1+pve0'
+  -machine 'type=pc-q35-5.0+pve0'
diff --git a/src/test/cfg2cmd/q35-usb13-error.conf b/src/test/cfg2cmd/q35-usb13-error.conf
index 4777902b..9c4ac55a 100644
--- a/src/test/cfg2cmd/q35-usb13-error.conf
+++ b/src/test/cfg2cmd/q35-usb13-error.conf
@@ -5,7 +5,7 @@ memory: 768
 name: q35-usb3-error
 net0: virtio=A2:C0:43:77:08:A1,bridge=vmbr0
 ostype: l26
-machine: pc-q35-4.0
+machine: pc-q35-5.0
 scsihw: virtio-scsi-pci
 smbios1: uuid=7b10d7af-b932-4c66-b2c3-3996152ec465
 vmgenid: c773c261-d800-4348-9f5d-167fadd53cf8
diff --git a/src/test/cfg2cmd/q35-usb2.conf b/src/test/cfg2cmd/q35-usb2.conf
index b106b9e8..31341886 100644
--- a/src/test/cfg2cmd/q35-usb2.conf
+++ b/src/test/cfg2cmd/q35-usb2.conf
@@ -4,7 +4,7 @@ memory: 768
 name: q35-usb2
 net0: virtio=A2:C0:43:77:08:A1,bridge=vmbr0
 ostype: l26
-machine: pc-q35-4.0
+machine: pc-q35-5.0
 scsihw: virtio-scsi-pci
 smbios1: uuid=7b10d7af-b932-4c66-b2c3-3996152ec465
 vmgenid: c773c261-d800-4348-9f5d-167fadd53cf8
diff --git a/src/test/cfg2cmd/q35-usb2.conf.cmd b/src/test/cfg2cmd/q35-usb2.conf.cmd
index 915e7008..6e082646 100644
--- a/src/test/cfg2cmd/q35-usb2.conf.cmd
+++ b/src/test/cfg2cmd/q35-usb2.conf.cmd
@@ -19,7 +19,7 @@
   -device 'vmgenid,guid=c773c261-d800-4348-9f5d-167fadd53cf8' \
   -chardev 'spicevmc,id=usbredirchardev1,name=usbredir' \
   -device 'usb-redir,chardev=usbredirchardev1,id=usbredirdev1,bus=ehci.0' \
-  -device 'qxl-vga,id=vga,bus=pcie.0,addr=0x1' \
+  -device 'qxl-vga,id=vga,max_outputs=4,bus=pcie.0,addr=0x1' \
   -device 'virtio-serial,id=spice,bus=pci.0,addr=0x9' \
   -chardev 'spicevmc,id=vdagent,name=vdagent' \
   -device 'virtserialport,chardev=vdagent,name=com.redhat.spice.0' \
@@ -28,4 +28,4 @@
   -iscsi 'initiator-name=iqn.1993-08.org.debian:01:aabbccddeeff' \
   -netdev 'type=tap,id=net0,ifname=tap8006i0,script=/usr/libexec/qemu-server/pve-bridge,downscript=/usr/libexec/qemu-server/pve-bridgedown,vhost=on' \
   -device 'virtio-net-pci,mac=A2:C0:43:77:08:A1,netdev=net0,bus=pci.0,addr=0x12,id=net0,bootindex=300' \
-  -machine 'type=pc-q35-4.0+pve0'
+  -machine 'type=pc-q35-5.0+pve0'
diff --git a/src/test/cfg2cmd/q35-usb3.conf b/src/test/cfg2cmd/q35-usb3.conf
index 3e405919..1d82efe3 100644
--- a/src/test/cfg2cmd/q35-usb3.conf
+++ b/src/test/cfg2cmd/q35-usb3.conf
@@ -4,7 +4,7 @@ memory: 768
 name: q35-usb3
 net0: virtio=A2:C0:43:77:08:A1,bridge=vmbr0
 ostype: l26
-machine: pc-q35-4.0
+machine: pc-q35-5.0
 scsihw: virtio-scsi-pci
 smbios1: uuid=7b10d7af-b932-4c66-b2c3-3996152ec465
 vmgenid: c773c261-d800-4348-9f5d-167fadd53cf8
diff --git a/src/test/cfg2cmd/q35-usb3.conf.cmd b/src/test/cfg2cmd/q35-usb3.conf.cmd
index 0fe6f8cb..b2b319d5 100644
--- a/src/test/cfg2cmd/q35-usb3.conf.cmd
+++ b/src/test/cfg2cmd/q35-usb3.conf.cmd
@@ -20,7 +20,7 @@
   -device 'nec-usb-xhci,id=xhci,bus=pci.1,addr=0x1b' \
   -chardev 'spicevmc,id=usbredirchardev1,name=usbredir' \
   -device 'usb-redir,chardev=usbredirchardev1,id=usbredirdev1,bus=xhci.0' \
-  -device 'qxl-vga,id=vga,bus=pcie.0,addr=0x1' \
+  -device 'qxl-vga,id=vga,max_outputs=4,bus=pcie.0,addr=0x1' \
   -device 'virtio-serial,id=spice,bus=pci.0,addr=0x9' \
   -chardev 'spicevmc,id=vdagent,name=vdagent' \
   -device 'virtserialport,chardev=vdagent,name=com.redhat.spice.0' \
@@ -29,4 +29,4 @@
   -iscsi 'initiator-name=iqn.1993-08.org.debian:01:aabbccddeeff' \
   -netdev 'type=tap,id=net0,ifname=tap8006i0,script=/usr/libexec/qemu-server/pve-bridge,downscript=/usr/libexec/qemu-server/pve-bridgedown,vhost=on' \
   -device 'virtio-net-pci,mac=A2:C0:43:77:08:A1,netdev=net0,bus=pci.0,addr=0x12,id=net0,bootindex=300' \
-  -machine 'type=pc-q35-4.0+pve0'
+  -machine 'type=pc-q35-5.0+pve0'
diff --git a/src/test/cfg2cmd/spice-enhancments.conf b/src/test/cfg2cmd/spice-enhancments.conf
index 895565c8..58f3b355 100644
--- a/src/test/cfg2cmd/spice-enhancments.conf
+++ b/src/test/cfg2cmd/spice-enhancments.conf
@@ -1,5 +1,5 @@
 # TEST: Test for SPICE enhancements
-machine: pc-i440fx-4.0
+machine: pc-i440fx-5.0
 smbios1: uuid=363a6126-5f48-43e1-811f-013294a946a0
 spice_enhancements: foldersharing=1,videostreaming=all
 vga: qxl
diff --git a/src/test/cfg2cmd/spice-enhancments.conf.cmd b/src/test/cfg2cmd/spice-enhancments.conf.cmd
index 6043488d..20049ca2 100644
--- a/src/test/cfg2cmd/spice-enhancments.conf.cmd
+++ b/src/test/cfg2cmd/spice-enhancments.conf.cmd
@@ -19,7 +19,7 @@
   -device 'pci-bridge,id=pci.2,chassis_nr=2,bus=pci.0,addr=0x1f' \
   -device 'vmgenid,guid=719b9591-1b0d-4e43-bca2-22b9ffbd3568' \
   -device 'piix3-usb-uhci,id=uhci,bus=pci.0,addr=0x1.0x2' \
-  -device 'qxl-vga,id=vga,bus=pci.0,addr=0x2' \
+  -device 'qxl-vga,id=vga,max_outputs=4,bus=pci.0,addr=0x2' \
   -device 'virtio-serial,id=spice,bus=pci.0,addr=0x9' \
   -chardev 'spicevmc,id=vdagent,name=vdagent' \
   -device 'virtserialport,chardev=vdagent,name=com.redhat.spice.0' \
@@ -28,4 +28,4 @@
   -spice 'tls-port=61000,addr=127.0.0.1,tls-ciphers=HIGH,seamless-migration=on,streaming-video=all' \
   -device 'virtio-balloon-pci,id=balloon0,bus=pci.0,addr=0x3' \
   -iscsi 'initiator-name=iqn.1993-08.org.debian:01:aabbccddeeff' \
-  -machine 'type=pc-i440fx-4.0+pve0'
+  -machine 'type=pc-i440fx-5.0+pve0'
diff --git a/src/test/cfg2cmd/spice-linux-4.1.conf b/src/test/cfg2cmd/spice-linux-5.0.conf
similarity index 92%
rename from src/test/cfg2cmd/spice-linux-4.1.conf
rename to src/test/cfg2cmd/spice-linux-5.0.conf
index e72cbe67..01c19a7d 100644
--- a/src/test/cfg2cmd/spice-linux-4.1.conf
+++ b/src/test/cfg2cmd/spice-linux-5.0.conf
@@ -1,6 +1,6 @@
 # TEST: Test for SPICE with SPICE with max_outputs
 cores: 2
-machine: pc-i440fx-4.1
+machine: pc-i440fx-5.0
 memory: 768
 name: spicelinux
 net0: virtio=A2:C0:43:67:08:A1,bridge=vmbr0
diff --git a/src/test/cfg2cmd/spice-linux-4.1.conf.cmd b/src/test/cfg2cmd/spice-linux-5.0.conf.cmd
similarity index 97%
rename from src/test/cfg2cmd/spice-linux-4.1.conf.cmd
rename to src/test/cfg2cmd/spice-linux-5.0.conf.cmd
index 2d2b8055..5f3a8a92 100644
--- a/src/test/cfg2cmd/spice-linux-4.1.conf.cmd
+++ b/src/test/cfg2cmd/spice-linux-5.0.conf.cmd
@@ -28,4 +28,4 @@
   -iscsi 'initiator-name=iqn.1993-08.org.debian:01:aabbccddeeff' \
   -netdev 'type=tap,id=net0,ifname=tap8006i0,script=/usr/libexec/qemu-server/pve-bridge,downscript=/usr/libexec/qemu-server/pve-bridgedown,vhost=on' \
   -device 'virtio-net-pci,mac=A2:C0:43:67:08:A1,netdev=net0,bus=pci.0,addr=0x12,id=net0,bootindex=300' \
-  -machine 'type=pc-i440fx-4.1+pve0'
+  -machine 'type=pc-i440fx-5.0+pve0'
diff --git a/src/test/cfg2cmd/spice-usb3.conf b/src/test/cfg2cmd/spice-usb3.conf
index 40d0dfb7..1242abfb 100644
--- a/src/test/cfg2cmd/spice-usb3.conf
+++ b/src/test/cfg2cmd/spice-usb3.conf
@@ -1,6 +1,6 @@
 # TEST: Test for SPICE with a USB3 based SPICE port added
 cores: 2
-machine: pc-i440fx-4.0
+machine: pc-i440fx-5.0
 memory: 768
 name: spiceusb3
 net0: virtio=A2:C0:43:77:08:A1,bridge=vmbr0
diff --git a/src/test/cfg2cmd/spice-usb3.conf.cmd b/src/test/cfg2cmd/spice-usb3.conf.cmd
index 2df589f4..9c7bafd0 100644
--- a/src/test/cfg2cmd/spice-usb3.conf.cmd
+++ b/src/test/cfg2cmd/spice-usb3.conf.cmd
@@ -22,7 +22,7 @@
   -device 'nec-usb-xhci,id=xhci,bus=pci.1,addr=0x1b' \
   -chardev 'spicevmc,id=usbredirchardev1,name=usbredir' \
   -device 'usb-redir,chardev=usbredirchardev1,id=usbredirdev1,bus=xhci.0' \
-  -device 'qxl-vga,id=vga,bus=pci.0,addr=0x2' \
+  -device 'qxl-vga,id=vga,max_outputs=4,bus=pci.0,addr=0x2' \
   -device 'virtio-serial,id=spice,bus=pci.0,addr=0x9' \
   -chardev 'spicevmc,id=vdagent,name=vdagent' \
   -device 'virtserialport,chardev=vdagent,name=com.redhat.spice.0' \
@@ -31,4 +31,4 @@
   -iscsi 'initiator-name=iqn.1993-08.org.debian:01:aabbccddeeff' \
   -netdev 'type=tap,id=net0,ifname=tap8006i0,script=/usr/libexec/qemu-server/pve-bridge,downscript=/usr/libexec/qemu-server/pve-bridgedown,vhost=on' \
   -device 'virtio-net-pci,mac=A2:C0:43:77:08:A1,netdev=net0,bus=pci.0,addr=0x12,id=net0,bootindex=300' \
-  -machine 'type=pc-i440fx-4.0+pve0'
+  -machine 'type=pc-i440fx-5.0+pve0'
diff --git a/src/test/cfg2cmd/spice-win.conf b/src/test/cfg2cmd/spice-win.conf
index 67fb7bc7..0b5ace01 100644
--- a/src/test/cfg2cmd/spice-win.conf
+++ b/src/test/cfg2cmd/spice-win.conf
@@ -1,6 +1,6 @@
 # TEST: Test for SPICE under Win10 with a USB3 based SPICE port added
 cores: 2
-machine: pc-i440fx-4.0
+machine: pc-i440fx-5.0
 memory: 768
 name: spiceusb3
 net0: virtio=A2:C0:43:77:08:A1,bridge=vmbr0
diff --git a/src/test/cfg2cmd/spice-win.conf.cmd b/src/test/cfg2cmd/spice-win.conf.cmd
index a2885b2e..50aa6b69 100644
--- a/src/test/cfg2cmd/spice-win.conf.cmd
+++ b/src/test/cfg2cmd/spice-win.conf.cmd
@@ -34,4 +34,4 @@
   -netdev 'type=tap,id=net0,ifname=tap8006i0,script=/usr/libexec/qemu-server/pve-bridge,downscript=/usr/libexec/qemu-server/pve-bridgedown,vhost=on' \
   -device 'virtio-net-pci,mac=A2:C0:43:77:08:A1,netdev=net0,bus=pci.0,addr=0x12,id=net0,bootindex=300' \
   -rtc 'driftfix=slew,base=localtime' \
-  -machine 'hpet=off,type=pc-i440fx-4.0+pve0'
+  -machine 'hpet=off,type=pc-i440fx-5.0+pve0'
diff --git a/src/test/cfg2cmd/usb13-error.conf b/src/test/cfg2cmd/usb13-error.conf
index 257be94f..acb0dd70 100644
--- a/src/test/cfg2cmd/usb13-error.conf
+++ b/src/test/cfg2cmd/usb13-error.conf
@@ -1,7 +1,7 @@
 # TEST: Test error for old machine type with newer usb config
 # EXPECT_ERROR: using usb13 is only possible with machine type >= 7.1 and ostype l26 or windows > 7
 cores: 2
-machine: pc-i440fx-4.0
+machine: pc-i440fx-5.0
 memory: 768
 name: q35-usb3-error
 net0: virtio=A2:C0:43:77:08:A1,bridge=vmbr0
-- 
2.47.3





^ permalink raw reply related	[flat|nested] 9+ messages in thread

* [PATCH qemu-server v3 7/8] cfg2cmd: pci: add bridges to device list up front
  2026-09-08 13:52 [PATCH qemu-server v3 0/8] pci: bridges: cleanup and hotplug fix Dominik Csapak
                   ` (5 preceding siblings ...)
  2026-09-08 13:52 ` [PATCH qemu-server v3 6/8] cfg2cmd: reject guests with machine version below 5.0 Dominik Csapak
@ 2026-09-08 13:52 ` Dominik Csapak
  2026-09-08 13:52 ` [PATCH qemu-server v3 8/8] cfg2cmd: pci: add pci.4 by default starting with machine version 11.1 Dominik Csapak
  7 siblings, 0 replies; 9+ messages in thread
From: Dominik Csapak @ 2026-09-08 13:52 UTC (permalink / raw)
  To: pve-devel

Since qemu machine version 2.3, we add the first two bridges by default
(3 in q35), so wiring the bridge adding logic through print_pci_addr was
only necessary in a few (easily checkable) circumstances.

So to not have to pass and collect the bridge devices throughout our
code, instead check those conditions directly and add them upfront.
(This also eliminates the need to insert the bridge devices at specific
point in the commandline via splice/unshift)

Signed-off-by: Dominik Csapak <d.csapak@proxmox.com>
---
 src/PVE/QemuServer.pm             | 100 ++++++++++--------------------
 src/PVE/QemuServer/Drive.pm       |   2 +-
 src/PVE/QemuServer/DriveDevice.pm |  23 ++++++-
 src/PVE/QemuServer/PCI.pm         |  54 ++++++++++++++--
 src/PVE/QemuServer/RNG.pm         |   4 +-
 src/PVE/QemuServer/USB.pm         |   8 +--
 6 files changed, 110 insertions(+), 81 deletions(-)

diff --git a/src/PVE/QemuServer.pm b/src/PVE/QemuServer.pm
index 08580fed..a5737f59 100644
--- a/src/PVE/QemuServer.pm
+++ b/src/PVE/QemuServer.pm
@@ -86,7 +86,7 @@ use PVE::QemuServer::MetaInfo;
 use PVE::QemuServer::Monitor qw(mon_cmd qmp_cmd vm_qmp_peer);
 use PVE::QemuServer::Network;
 use PVE::QemuServer::OVMF;
-use PVE::QemuServer::PCI qw(print_pci_addr print_pcie_addr parse_hostpci);
+use PVE::QemuServer::PCI qw(print_pci_addr print_pcie_addr parse_hostpci get_pci_bridges);
 use PVE::QemuServer::PCI::Mdev;
 use PVE::QemuServer::QemuImage;
 use PVE::QemuServer::QMPHelpers
@@ -1325,7 +1325,6 @@ sub print_netdevice_full {
         $conf,
         $net,
         $netid,
-        $bridges,
         $use_old_bios_files,
         $arch,
         $machine_version,
@@ -1337,7 +1336,7 @@ sub print_netdevice_full {
         $device = 'virtio-net-pci';
     }
 
-    my $pciaddr = print_pci_addr("$netid", $bridges, $arch);
+    my $pciaddr = print_pci_addr("$netid", $arch);
     my $tmpstr = "$device,mac=$net->{macaddr},netdev=$netid$pciaddr,id=$netid";
     if ($net->{queues} && $net->{queues} > 1 && $net->{model} eq 'virtio') {
         # Consider we have N queues, the number of vectors needed is 2 * N + 2, i.e., one per in
@@ -1497,7 +1496,7 @@ my sub map_vga_model {
 }
 
 sub print_vga_device {
-    my ($conf, $vga, $arch, $machine_version, $id, $qxlnum, $bridges) = @_;
+    my ($conf, $vga, $arch, $machine_version, $id, $qxlnum) = @_;
 
     my $type = map_vga_model($vga->{type}, $arch);
     my $vgamem_mb = $vga->{memory};
@@ -1546,7 +1545,7 @@ sub print_vga_device {
         # the first display uses pcie.0 bus on q35 machines
         $pciaddr = print_pcie_addr($vgaid);
     } else {
-        $pciaddr = print_pci_addr($vgaid, $bridges, $arch);
+        $pciaddr = print_pci_addr($vgaid, $arch);
     }
 
     if ($vga->{type} eq 'virtio-gl') {
@@ -3137,7 +3136,6 @@ sub config_to_command {
 
     my ($machineFlags, $rtcFlags) = ([], []);
     my $devices = [];
-    my $bridges = {};
     my $ostype = $conf->{ostype};
     my $winversion = windows_version($ostype);
     my $kvm = $conf->{kvm};
@@ -3290,13 +3288,17 @@ sub config_to_command {
         push @$cmd, $fixups->@*;
     }
 
+    my $max_scsihw = PVE::QemuServer::DriveDevice::get_max_scsihw_index($conf);
+    if (my $bridges = get_pci_bridges($conf, $arch, $q35, $max_scsihw)) {
+        push @$devices, $bridges->@*;
+    }
+
     if ($conf->{vmgenid}) {
         push @$devices, '-device', 'vmgenid,guid=' . $conf->{vmgenid};
     }
 
     # add usb controllers
-    my @usbcontrollers =
-        PVE::QemuServer::USB::get_usb_controllers($conf, $bridges, $arch, $machine_version);
+    my @usbcontrollers = PVE::QemuServer::USB::get_usb_controllers($conf, $arch, $machine_version);
     push @$devices, @usbcontrollers if @usbcontrollers;
 
     my ($vga, $qxlnum) = get_vga_properties($conf, $arch, $machine_version, $winversion);
@@ -3320,7 +3322,7 @@ sub config_to_command {
     # host pci device passthrough
     my ($kvm_off, $gpu_passthrough, $legacy_igd, $pci_devices) =
         PVE::QemuServer::PCI::print_hostpci_devices(
-            $vmid, $conf, $devices, $vga, $winversion, $bridges, $arch, $bootorder, $dry_run,
+            $vmid, $conf, $devices, $vga, $winversion, $arch, $bootorder, $dry_run,
         );
 
     # usb devices
@@ -3364,7 +3366,7 @@ sub config_to_command {
     }
 
     if (min_version($machine_version, 4, 0) && (my $audio = conf_has_audio($conf))) {
-        my $audiopciaddr = print_pci_addr("audio0", $bridges, $arch);
+        my $audiopciaddr = print_pci_addr("audio0", $arch);
         my $audio_devs = audio_devs($audio, $audiopciaddr, $machine_version);
         push @$devices, @$audio_devs;
     }
@@ -3409,7 +3411,7 @@ sub config_to_command {
 
     if ($vga->{type} && $vga->{type} !~ m/^serial\d+$/ && $vga->{type} ne 'none') {
         push @$devices, '-device',
-            print_vga_device($conf, $vga, $arch, $machine_version, undef, $qxlnum, $bridges);
+            print_vga_device($conf, $vga, $arch, $machine_version, undef, $qxlnum);
 
         push @$cmd, '-display', 'egl-headless,gl=core' if $vga->{type} eq 'virtio-gl'; # VIRGL
 
@@ -3469,7 +3471,7 @@ sub config_to_command {
         push @$devices, '-chardev', "socket,path=$qgasocket,server=on,wait=off,id=qga0";
 
         if (!$guest_agent->{type} || $guest_agent->{type} eq 'virtio') {
-            my $pciaddr = print_pci_addr("qga0", $bridges, $arch);
+            my $pciaddr = print_pci_addr("qga0", $arch);
             push @$devices, '-device', "virtio-serial,id=qga0$pciaddr";
             push @$devices, '-device', 'virtserialport,chardev=qga0,name=org.qemu.guest_agent.0';
         } elsif ($guest_agent->{type} eq 'isa') {
@@ -3480,7 +3482,7 @@ sub config_to_command {
     my $rng = $conf->{rng0} ? parse_rng($conf->{rng0}) : undef;
     if ($rng && $version_guard->(4, 1, 2)) {
         my $rng_object = print_rng_object_commandline('rng0', $rng);
-        my $rng_device = print_rng_device_commandline('rng0', $rng, $bridges, $arch);
+        my $rng_device = print_rng_device_commandline('rng0', $rng, $arch);
         push @$devices, '-object', $rng_object;
         push @$devices, '-device', $rng_device;
     }
@@ -3494,10 +3496,9 @@ sub config_to_command {
         if ($qxlnum > 1) {
             if ($winversion) {
                 for (my $i = 1; $i < $qxlnum; $i++) {
-                    push @$devices, '-device',
-                        print_vga_device(
-                            $conf, $vga, $arch, $machine_version, $i, $qxlnum, $bridges,
-                        );
+                    push @$devices, '-device', print_vga_device(
+                        $conf, $vga, $arch, $machine_version, $i, $qxlnum,
+                    );
                 }
             } else {
                 # assume other OS works like Linux
@@ -3511,7 +3512,7 @@ sub config_to_command {
             }
         }
 
-        my $pciaddr = print_pci_addr("spice", $bridges, $arch);
+        my $pciaddr = print_pci_addr("spice", $arch);
 
         push @$devices, '-device', "virtio-serial,id=spice$pciaddr";
         if ($vga->{'clipboard'} && $vga->{'clipboard'} eq 'vnc') {
@@ -3549,7 +3550,7 @@ sub config_to_command {
 
     # enable balloon by default, unless explicitly disabled
     if (!defined($conf->{balloon}) || $conf->{balloon}) {
-        my $pciaddr = print_pci_addr("balloon0", $bridges, $arch);
+        my $pciaddr = print_pci_addr("balloon0", $arch);
         my $ballooncmd = "virtio-balloon-pci,id=balloon0$pciaddr";
         $ballooncmd .= ",free-page-reporting=on" if min_version($machine_version, 6, 2);
         push @$devices, '-device', $ballooncmd;
@@ -3557,7 +3558,7 @@ sub config_to_command {
 
     if ($conf->{watchdog}) {
         my $wdopts = parse_watchdog($conf->{watchdog});
-        my $pciaddr = print_pci_addr("watchdog", $bridges, $arch);
+        my $pciaddr = print_pci_addr("watchdog", $arch);
         my $watchdog = $wdopts->{model} || 'i6300esb';
         push @$devices, '-device', "$watchdog$pciaddr";
         push @$devices, '-watchdog-action', $wdopts->{action} if $wdopts->{action};
@@ -3599,7 +3600,7 @@ sub config_to_command {
                     "scsi$drive->{index}: machine version 4.1~pve2 or higher is required to use more than 14 SCSI disks\n"
                     if $drive->{index} > 13 && !&$version_guard(4, 1, 2);
 
-                my $pciaddr = print_pci_addr("$controller_prefix$controller", $bridges, $arch);
+                my $pciaddr = print_pci_addr("$controller_prefix$controller", $arch);
                 my $scsihw_type =
                     $scsihw =~ m/^virtio-scsi-single/ ? "virtio-scsi-pci" : $scsihw;
 
@@ -3634,7 +3635,7 @@ sub config_to_command {
 
             if ($drive->{interface} eq 'sata') {
                 my $controller = int($drive->{index} / $PVE::QemuServer::Drive::MAX_SATA_DISKS);
-                my $pciaddr = print_pci_addr("ahci$controller", $bridges, $arch);
+                my $pciaddr = print_pci_addr("ahci$controller", $arch);
                 push @$devices, '-device', "ahci,id=ahci$controller,multifunction=on$pciaddr"
                     if !$ahcicontroller->{$controller};
                 $ahcicontroller->{$controller} = 1;
@@ -3678,10 +3679,9 @@ sub config_to_command {
                 push @$devices, '-drive', $drive_cmd;
             }
 
-            push @$devices, '-device',
-                print_drivedevice_full(
-                    $storecfg, $conf, $vmid, $drive, $bridges, $arch, $machine_type,
-                );
+            push @$devices, '-device', print_drivedevice_full(
+                $storecfg, $conf, $vmid, $drive, $arch, $machine_type,
+            );
         },
     );
 
@@ -3709,7 +3709,6 @@ sub config_to_command {
             $conf,
             $d,
             $netname,
-            $bridges,
             $use_old_bios_files,
             $arch,
             $machine_version,
@@ -3726,7 +3725,7 @@ sub config_to_command {
         if ($q35) {
             $bus = print_pcie_addr("ivshmem");
         } else {
-            $bus = print_pci_addr("ivshmem", $bridges, $arch);
+            $bus = print_pci_addr("ivshmem", $arch);
         }
 
         my $ivshmem_name = $ivshmem->{name} // $vmid;
@@ -3737,34 +3736,6 @@ sub config_to_command {
             "memory-backend-file,id=ivshmem,share=on,mem-path=$path" . ",size=$ivshmem->{size}M";
     }
 
-    # pci.4 is nested in pci.1
-    $bridges->{1} = 1 if $bridges->{4};
-
-    if (!$q35) { # add pci bridges
-        if (min_version($machine_version, 2, 3)) {
-            $bridges->{1} = 1;
-            $bridges->{2} = 1;
-        }
-        $bridges->{3} = 1 if $scsihw =~ m/^virtio-scsi-single/;
-    }
-
-    for my $k (sort { $b cmp $a } keys %$bridges) {
-        next if $q35 && $k < 4; # q35.cfg already includes bridges up to 3
-
-        my $k_name = $k;
-        if ($k == 2 && $legacy_igd) {
-            $k_name = "$k-igd";
-        }
-        my $pciaddr = print_pci_addr("pci.$k_name", undef, $arch);
-        my $devstr = "pci-bridge,id=pci.$k,chassis_nr=$k$pciaddr";
-
-        if ($q35) { # add after -readconfig pve-q35.cfg
-            splice @$devices, 2, 0, '-device', $devstr;
-        } else {
-            unshift @$devices, '-device', $devstr if $k > 0;
-        }
-    }
-
     if (!$kvm) {
         push @$machineFlags, 'accel=tcg';
     }
@@ -3928,7 +3899,7 @@ sub vm_deviceplug {
 
         qemu_driveadd($storecfg, $vmid, $device);
         my $devicefull =
-            print_drivedevice_full($storecfg, $conf, $vmid, $device, undef, $arch, $machine_type);
+            print_drivedevice_full($storecfg, $conf, $vmid, $device, $arch, $machine_type);
 
         qemu_deviceadd($vmid, $devicefull);
         eval { qemu_deviceaddverify($vmid, $deviceid); };
@@ -3939,7 +3910,7 @@ sub vm_deviceplug {
         }
     } elsif ($deviceid =~ m/^(virtioscsi|scsihw)(\d+)$/) {
         my $scsihw = defined($conf->{scsihw}) ? $conf->{scsihw} : "lsi";
-        my $pciaddr = print_pci_addr($deviceid, undef, $arch);
+        my $pciaddr = print_pci_addr($deviceid, $arch);
         my $scsihw_type = $scsihw eq 'virtio-scsi-single' ? "virtio-scsi-pci" : $scsihw;
 
         my $devicefull = "$scsihw_type,id=$deviceid$pciaddr";
@@ -3960,7 +3931,7 @@ sub vm_deviceplug {
         qemu_driveadd($storecfg, $vmid, $device);
 
         my $devicefull =
-            print_drivedevice_full($storecfg, $conf, $vmid, $device, undef, $arch, $machine_type);
+            print_drivedevice_full($storecfg, $conf, $vmid, $device, $arch, $machine_type);
         eval { qemu_deviceadd($vmid, $devicefull); };
         if (my $err = $@) {
             eval { qemu_drivedel($vmid, $deviceid); };
@@ -3976,14 +3947,7 @@ sub vm_deviceplug {
         ($use_old_bios_files, $machine_type) = qemu_use_old_bios_files($machine_type);
 
         my $netdevicefull = print_netdevice_full(
-            $vmid,
-            $conf,
-            $device,
-            $deviceid,
-            undef,
-            $use_old_bios_files,
-            $arch,
-            $machine_version,
+            $vmid, $conf, $device, $deviceid, $use_old_bios_files, $arch, $machine_version,
         );
         qemu_deviceadd($vmid, $netdevicefull);
         eval {
@@ -4274,7 +4238,7 @@ sub qemu_usb_hotplug {
     my $devicelist = vm_devices_list($vmid);
 
     if (!$devicelist->{xhci}) {
-        my $pciaddr = print_pci_addr("xhci", undef, $arch);
+        my $pciaddr = print_pci_addr("xhci", $arch);
         qemu_deviceadd($vmid, PVE::QemuServer::USB::print_qemu_xhci_controller($pciaddr));
         $added_xhci = 1;
     }
diff --git a/src/PVE/QemuServer/Drive.pm b/src/PVE/QemuServer/Drive.pm
index b80b7dbb..17c46f36 100644
--- a/src/PVE/QemuServer/Drive.pm
+++ b/src/PVE/QemuServer/Drive.pm
@@ -152,7 +152,7 @@ sub get_path_and_format {
 }
 
 my $MAX_IDE_DISKS = 4;
-my $MAX_SCSI_DISKS = 31;
+our $MAX_SCSI_DISKS = 31;
 my $MAX_VIRTIO_DISKS = 16;
 our $MAX_SATA_DISKS = 6;
 our $MAX_UNUSED_DISKS = 256;
diff --git a/src/PVE/QemuServer/DriveDevice.pm b/src/PVE/QemuServer/DriveDevice.pm
index 37b611f0..8974bae0 100644
--- a/src/PVE/QemuServer/DriveDevice.pm
+++ b/src/PVE/QemuServer/DriveDevice.pm
@@ -49,8 +49,27 @@ sub scsihw_infos {
     return ($maxdev, $controller, $controller_prefix);
 }
 
+# Gets the maximum scsihw index that will be used
+sub get_max_scsihw_index {
+    my ($conf) = @_;
+
+    my $max_index = 0;
+
+    for (my $i = 0; $i < $PVE::QemuServer::Drive::MAX_SCSI_DISKS; $i++) {
+        next if !defined($conf->{"scsi$i"});
+        my (undef, $index, $prefix) = scsihw_infos($conf->{scsihw}, $i);
+        last if $prefix ne 'scsihw'; # must be the same for all
+
+        if ($index > $max_index) {
+            $max_index = $index;
+        }
+    }
+
+    return $max_index;
+}
+
 sub print_drivedevice_full {
-    my ($storecfg, $conf, $vmid, $drive, $bridges, $arch, $machine_type) = @_;
+    my ($storecfg, $conf, $vmid, $drive, $arch, $machine_type) = @_;
 
     my $device = '';
     my $maxdev = 0;
@@ -61,7 +80,7 @@ sub print_drivedevice_full {
 
     my $drive_id = PVE::QemuServer::Drive::get_drive_id($drive);
     if ($drive->{interface} eq 'virtio') {
-        my $pciaddr = print_pci_addr("$drive_id", $bridges, $arch);
+        my $pciaddr = print_pci_addr("$drive_id", $arch);
         $device = 'virtio-blk-pci';
         # for the switch to -blockdev, there is no blockdev for 'none'
         if (!min_version($machine_version, 10, 0) || $drive->{file} ne 'none') {
diff --git a/src/PVE/QemuServer/PCI.pm b/src/PVE/QemuServer/PCI.pm
index 8069f104..2d10f2e1 100644
--- a/src/PVE/QemuServer/PCI.pm
+++ b/src/PVE/QemuServer/PCI.pm
@@ -20,6 +20,7 @@ our @EXPORT_OK = qw(
     print_pci_addr
     print_pcie_addr
     parse_hostpci
+    get_pci_bridges
 );
 
 our $MAX_HOSTPCI_DEVICES = 16;
@@ -303,7 +304,7 @@ sub get_pci_bridge_for_device {
 }
 
 sub print_pci_addr {
-    my ($id, $bridges, $arch) = @_;
+    my ($id, $arch) = @_;
 
     die "aarch64 cannot use IDE devices\n" if $arch eq 'aarch64' && $id =~ /^ide/;
 
@@ -316,7 +317,6 @@ sub print_pci_addr {
         my $busname = $arch eq 'aarch64' && $d->{bus} eq 0 ? 'pcie' : 'pci';
 
         $res = ",bus=$busname.$d->{bus},addr=$d->{addr}";
-        $bridges->{ $d->{bus} } = 1 if $bridges;
     }
 
     return $res;
@@ -627,7 +627,7 @@ sub choose_hostpci_devices {
 }
 
 sub print_hostpci_devices {
-    my ($vmid, $conf, $devices, $vga, $winversion, $bridges, $arch, $bootorder, $dry_run) = @_;
+    my ($vmid, $conf, $devices, $vga, $winversion, $arch, $bootorder, $dry_run) = @_;
 
     my $kvm_off = 0;
     my $gpu_passthrough = 0;
@@ -658,7 +658,7 @@ sub print_hostpci_devices {
             }
         } else {
             my $pci_name = $d->{'legacy-igd'} ? 'legacy-igd' : $id;
-            $pciaddr = print_pci_addr($pci_name, $bridges, $arch);
+            $pciaddr = print_pci_addr($pci_name, $arch);
         }
 
         my $num_devices = scalar($d->{ids}->@*);
@@ -881,4 +881,50 @@ sub reserve_pci_usage {
     die $@ if $@;
 }
 
+# Returns a list of bridge devices which are necessary for the remaining
+# devices.
+sub get_pci_bridges {
+    my ($conf, $arch, $q35, $max_scsihw) = @_;
+
+    my $bridges = {
+        # 0 => 1, always present
+        1 => 1,
+        2 => 1,
+    };
+
+    $bridges->{3} = 1 if ($conf->{scsihw} // '') =~ m/^virtio-scsi-single/;
+
+    # some scsi controllers can only have 7 scsi disks per controller,
+    # so scsi14 and upwards need scsihw2,3,4 which live on bridge 4
+    $bridges->{4} = 1 if $max_scsihw > 1;
+
+    # use cheap legacy igd check instead of a full parse_hostpci
+    my $legacy_igd = 0;
+    for (my $i = 0; $i < $MAX_HOSTPCI_DEVICES; $i++) {
+        next if !defined($conf->{"hostpci$i"});
+        my $res = PVE::JSONSchema::parse_property_string($hostpci_fmt, $conf->{"hostpci$i"});
+        next if !defined($res);
+        if ($res->{'legacy-igd'}) {
+            $legacy_igd = 1;
+            last;
+        }
+    }
+
+    my $devices = [];
+    for my $k (sort { $a <=> $b } keys %$bridges) {
+        next if $q35 && $k < 4; # q35.cfg already includes bridges up to 3
+
+        my $k_name = $k;
+        if ($k == 2 && $legacy_igd) {
+            $k_name = "$k-igd";
+        }
+        my $pciaddr = print_pci_addr("pci.$k_name", $arch);
+        my $devstr = "pci-bridge,id=pci.$k,chassis_nr=$k$pciaddr";
+
+        push @$devices, '-device', $devstr;
+    }
+
+    return $devices;
+}
+
 1;
diff --git a/src/PVE/QemuServer/RNG.pm b/src/PVE/QemuServer/RNG.pm
index eb477a5d..f854dd50 100644
--- a/src/PVE/QemuServer/RNG.pm
+++ b/src/PVE/QemuServer/RNG.pm
@@ -87,7 +87,7 @@ sub check_rng_source {
 }
 
 sub print_rng_device_commandline {
-    my ($id, $rng, $bridges, $arch) = @_;
+    my ($id, $rng, $arch) = @_;
 
     die "no rng device specified\n" if !$rng;
 
@@ -98,7 +98,7 @@ sub print_rng_device_commandline {
         $limiter_str = ",max-bytes=$max_bytes,period=$period";
     }
 
-    my $rng_addr = print_pci_addr($id, $bridges, $arch);
+    my $rng_addr = print_pci_addr($id, $arch);
 
     return "virtio-rng-pci,rng=$id$limiter_str$rng_addr";
 }
diff --git a/src/PVE/QemuServer/USB.pm b/src/PVE/QemuServer/USB.pm
index c9408a42..d75507e4 100644
--- a/src/PVE/QemuServer/USB.pm
+++ b/src/PVE/QemuServer/USB.pm
@@ -120,7 +120,7 @@ my sub assert_usb_index_is_useable {
 }
 
 sub get_usb_controllers {
-    my ($conf, $bridges, $arch, $machine_version) = @_;
+    my ($conf, $arch, $machine_version) = @_;
 
     my $devices = [];
     my $pciaddr = "";
@@ -134,10 +134,10 @@ sub get_usb_controllers {
     my $is_q35 = PVE::QemuServer::Machine::machine_type_is_q35($conf);
 
     if ($arch eq 'aarch64') {
-        $pciaddr = print_pci_addr('ehci', $bridges, $arch);
+        $pciaddr = print_pci_addr('ehci', $arch);
         push @$devices, '-device', "usb-ehci,id=ehci$pciaddr";
     } elsif (!$is_q35) {
-        $pciaddr = print_pci_addr("piix3", $bridges, $arch);
+        $pciaddr = print_pci_addr("piix3", $arch);
         push @$devices, '-device', "piix3-usb-uhci,id=uhci$pciaddr.0x2";
     }
 
@@ -157,7 +157,7 @@ sub get_usb_controllers {
         push @$devices, '-readconfig', '/usr/share/qemu-server/pve-usb.cfg';
     }
 
-    $pciaddr = print_pci_addr("xhci", $bridges, $arch);
+    $pciaddr = print_pci_addr("xhci", $arch);
     if ($use_qemu_xhci && $any_usb) {
         push @$devices, '-device', print_qemu_xhci_controller($pciaddr);
     } elsif ($use_usb3) {
-- 
2.47.3





^ permalink raw reply related	[flat|nested] 9+ messages in thread

* [PATCH qemu-server v3 8/8] cfg2cmd: pci: add pci.4 by default starting with machine version 11.1
  2026-09-08 13:52 [PATCH qemu-server v3 0/8] pci: bridges: cleanup and hotplug fix Dominik Csapak
                   ` (6 preceding siblings ...)
  2026-09-08 13:52 ` [PATCH qemu-server v3 7/8] cfg2cmd: pci: add bridges to device list up front Dominik Csapak
@ 2026-09-08 13:52 ` Dominik Csapak
  7 siblings, 0 replies; 9+ messages in thread
From: Dominik Csapak @ 2026-09-08 13:52 UTC (permalink / raw)
  To: pve-devel

Without that bridge, scsi14 and higher can't be hotplugged with lsi scsi
controllers (when the vm wasn't already started with such a disk), so
add it by default with the next machine version.

Add a test with a forced qemu version 11.1.

Signed-off-by: Dominik Csapak <d.csapak@proxmox.com>
---
 src/PVE/QemuServer.pm                         |  2 +-
 src/PVE/QemuServer/PCI.pm                     |  4 +--
 src/test/cfg2cmd/linux-bridges-qemu-11.1.conf | 14 ++++++++
 .../cfg2cmd/linux-bridges-qemu-11.1.conf.cmd  | 36 +++++++++++++++++++
 4 files changed, 53 insertions(+), 3 deletions(-)
 create mode 100644 src/test/cfg2cmd/linux-bridges-qemu-11.1.conf
 create mode 100644 src/test/cfg2cmd/linux-bridges-qemu-11.1.conf.cmd

diff --git a/src/PVE/QemuServer.pm b/src/PVE/QemuServer.pm
index a5737f59..0294a0b3 100644
--- a/src/PVE/QemuServer.pm
+++ b/src/PVE/QemuServer.pm
@@ -3289,7 +3289,7 @@ sub config_to_command {
     }
 
     my $max_scsihw = PVE::QemuServer::DriveDevice::get_max_scsihw_index($conf);
-    if (my $bridges = get_pci_bridges($conf, $arch, $q35, $max_scsihw)) {
+    if (my $bridges = get_pci_bridges($conf, $arch, $q35, $max_scsihw, $version_guard)) {
         push @$devices, $bridges->@*;
     }
 
diff --git a/src/PVE/QemuServer/PCI.pm b/src/PVE/QemuServer/PCI.pm
index 2d10f2e1..d7dc6121 100644
--- a/src/PVE/QemuServer/PCI.pm
+++ b/src/PVE/QemuServer/PCI.pm
@@ -884,7 +884,7 @@ sub reserve_pci_usage {
 # Returns a list of bridge devices which are necessary for the remaining
 # devices.
 sub get_pci_bridges {
-    my ($conf, $arch, $q35, $max_scsihw) = @_;
+    my ($conf, $arch, $q35, $max_scsihw, $version_guard) = @_;
 
     my $bridges = {
         # 0 => 1, always present
@@ -896,7 +896,7 @@ sub get_pci_bridges {
 
     # some scsi controllers can only have 7 scsi disks per controller,
     # so scsi14 and upwards need scsihw2,3,4 which live on bridge 4
-    $bridges->{4} = 1 if $max_scsihw > 1;
+    $bridges->{4} = 1 if $max_scsihw > 1 || $version_guard->(11, 1);
 
     # use cheap legacy igd check instead of a full parse_hostpci
     my $legacy_igd = 0;
diff --git a/src/test/cfg2cmd/linux-bridges-qemu-11.1.conf b/src/test/cfg2cmd/linux-bridges-qemu-11.1.conf
new file mode 100644
index 00000000..fa9cc76b
--- /dev/null
+++ b/src/test/cfg2cmd/linux-bridges-qemu-11.1.conf
@@ -0,0 +1,14 @@
+# TEST: Test that bridge pci.4 is added by default in qemu 11.1
+# QEMU_VERSION: 11.1.0
+bootdisk: scsi0
+cores: 3
+ide2: none,media=cdrom
+memory: 768
+name: simple
+net0: virtio=A2:C0:43:77:08:A0,bridge=vmbr0
+numa: 0
+ostype: l26
+scsi0: local:8006/vm-8006-disk-0.qcow2,discard=on,size=104858K
+smbios1: uuid=7b10d7af-b932-4c66-b2c3-3996152ec465
+sockets: 1
+vmgenid: c773c261-d800-4348-9f5d-167fadd53cf8
diff --git a/src/test/cfg2cmd/linux-bridges-qemu-11.1.conf.cmd b/src/test/cfg2cmd/linux-bridges-qemu-11.1.conf.cmd
new file mode 100644
index 00000000..2aebdffa
--- /dev/null
+++ b/src/test/cfg2cmd/linux-bridges-qemu-11.1.conf.cmd
@@ -0,0 +1,36 @@
+/usr/bin/kvm \
+  -id 8006 \
+  -name simple \
+  -no-shutdown \
+  -chardev 'socket,id=qmp,path=/var/run/qemu-server/8006.qmp,server=on,wait=off' \
+  -mon 'chardev=qmp,mode=control' \
+  -chardev 'socket,id=qmp-event,path=/var/run/qmeventd.sock,reconnect-ms=5000' \
+  -mon 'chardev=qmp-event,mode=control' \
+  -pidfile /var/run/qemu-server/8006.pid \
+  -daemonize \
+  -smbios 'type=1,uuid=7b10d7af-b932-4c66-b2c3-3996152ec465' \
+  -smp '3,sockets=1,cores=3,maxcpus=3' \
+  -nodefaults \
+  -boot 'menu=on,strict=on,reboot-timeout=1000,splash=/usr/share/qemu-server/bootsplash.jpg' \
+  -vnc 'unix:/var/run/qemu-server/8006.vnc,password=on' \
+  -cpu kvm64,enforce,+kvm_pv_eoi,+kvm_pv_unhalt,+lahf_lm,+sep \
+  -m 768 \
+  -object '{"id":"throttle-drive-scsi0","limits":{},"qom-type":"throttle-group"}' \
+  -global 'PIIX4_PM.disable_s3=1' \
+  -global 'PIIX4_PM.disable_s4=1' \
+  -device 'pci-bridge,id=pci.1,chassis_nr=1,bus=pci.0,addr=0x1e' \
+  -device 'pci-bridge,id=pci.2,chassis_nr=2,bus=pci.0,addr=0x1f' \
+  -device 'pci-bridge,id=pci.4,chassis_nr=4,bus=pci.1,addr=0x1c' \
+  -device 'vmgenid,guid=c773c261-d800-4348-9f5d-167fadd53cf8' \
+  -device 'piix3-usb-uhci,id=uhci,bus=pci.0,addr=0x1.0x2' \
+  -device 'usb-tablet,id=tablet,bus=uhci.0,port=1' \
+  -device 'VGA,id=vga,bus=pci.0,addr=0x2' \
+  -device 'virtio-balloon-pci,id=balloon0,bus=pci.0,addr=0x3,free-page-reporting=on' \
+  -iscsi 'initiator-name=iqn.1993-08.org.debian:01:aabbccddeeff' \
+  -device 'ide-cd,bus=ide.1,unit=0,id=ide2,bootindex=200' \
+  -device 'lsi,id=scsihw0,bus=pci.0,addr=0x5' \
+  -blockdev '{"detect-zeroes":"unmap","discard":"unmap","driver":"throttle","file":{"cache":{"direct":true,"no-flush":false},"detect-zeroes":"unmap","discard":"unmap","driver":"qcow2","file":{"aio":"io_uring","cache":{"direct":true,"no-flush":false},"detect-zeroes":"unmap","discard":"unmap","driver":"file","filename":"/var/lib/vz/images/8006/vm-8006-disk-0.qcow2","node-name":"ecd04be4259153b8293415fefa2a84c","read-only":false},"node-name":"fcd04be4259153b8293415fefa2a84c","read-only":false},"node-name":"drive-scsi0","read-only":false,"throttle-group":"throttle-drive-scsi0"}' \
+  -device 'scsi-hd,bus=scsihw0.0,scsi-id=0,drive=drive-scsi0,id=scsi0,device_id=drive-scsi0,bootindex=100,write-cache=on' \
+  -netdev 'type=tap,id=net0,ifname=tap8006i0,script=/usr/libexec/qemu-server/pve-bridge,downscript=/usr/libexec/qemu-server/pve-bridgedown,vhost=on' \
+  -device 'virtio-net-pci,mac=A2:C0:43:77:08:A0,netdev=net0,bus=pci.0,addr=0x12,id=net0,rx_queue_size=1024,tx_queue_size=256,bootindex=300,host_mtu=1500,host_tunnel=off' \
+  -machine 'hpet=off,type=pc+pve0'
-- 
2.47.3





^ permalink raw reply related	[flat|nested] 9+ messages in thread

end of thread, other threads:[~2026-09-08 13:58 UTC | newest]

Thread overview: 9+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-09-08 13:52 [PATCH qemu-server v3 0/8] pci: bridges: cleanup and hotplug fix Dominik Csapak
2026-09-08 13:52 ` [PATCH qemu-server v3 1/8] tests: cfg2cmd: add test for ivshmem with q35 Dominik Csapak
2026-09-08 13:52 ` [PATCH qemu-server v3 2/8] tests: cfg2cmd: add q35 + win7 + hostpci test Dominik Csapak
2026-09-08 13:52 ` [PATCH qemu-server v3 3/8] code cleanup: pci: don't export print_pcie_root_port Dominik Csapak
2026-09-08 13:52 ` [PATCH qemu-server v3 4/8] code cleanup: hot-plug: simplify getting bridge for device Dominik Csapak
2026-09-08 13:52 ` [PATCH qemu-server v3 5/8] hotplug: don't try to hotplug bridges Dominik Csapak
2026-09-08 13:52 ` [PATCH qemu-server v3 6/8] cfg2cmd: reject guests with machine version below 5.0 Dominik Csapak
2026-09-08 13:52 ` [PATCH qemu-server v3 7/8] cfg2cmd: pci: add bridges to device list up front Dominik Csapak
2026-09-08 13:52 ` [PATCH qemu-server v3 8/8] cfg2cmd: pci: add pci.4 by default starting with machine version 11.1 Dominik Csapak

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