virtualized on proxmox¶
no longer used
truenas1 runs on bare metal now: see hardware and base install.
for about a year truenas1 was a VM on pve-nas1, a single Proxmox host that was never a member of the cluster. every drive it used reached it by PCIe passthrough. ZFS in the VM talked to real controllers and real disks, with no virtual disk anywhere in the storage path.
pve-nas1 is an AMD Epyc 9115 (Turin) on an ASRock Rack GENOAD8UD-2T/X550. the bulk storage hangs off MCIO connectors, and how those enumerate shapes a lot of what follows.
this is the configuration as it was, and what i would start from if i virtualized it again. devices came and went over that year, so the tables show the layout, not a parts list.
key principle¶
anything the VM uses must be bound to vfio-pci on the host before any host
driver can claim it, and anything the host uses must never appear in the vfio
list. getting this wrong fails in one of two ways:
- the host claims the device:
nvmeorahcigrabs the drive at boot, passthrough fails, and the VM starts without it - the host enumerates the pool, which is far worse. proxmox runs ZFS itself, and it will enumerate any pool it can see on a passed-through disk unless that device ID is kept away from it. two kernels acting on one pool can lose it, and nothing warns you first
the second happens even when the device is assigned to a VM and excluded from the host. ZFS can claim those drives early in boot, long before anything to do with virtualization has started.
keeping the two apart takes four mechanisms:
| what it does | |
|---|---|
blacklist |
stops the host loading a driver it has no use for at all |
softdep |
for drivers the host does need, loads vfio-pci ahead of them |
ids= |
names the devices vfio-pci should claim |
| udev rules | pin awkward devices by address or ID, whatever else happens |
without the softdep lines, the ids= line is only a race, and vfio-pci has to
win it. a device already bound to vfio-pci has no storage driver behind it, so
the host cannot see a pool on it.
never leave a pool exported across a host reboot
never export a pool in the TrueNAS VM and then reboot the host without importing it again in the VM first. nothing claims an exported pool, and in that state the host will take it.
what was on the bus¶
this table lists everything pve-nas1 could see, in bus order, and what each device was bound to. four devices stayed with the host, and the rest went to the VM.
| BDF | vendor:device | device | bound to |
|---|---|---|---|
01:00.0 |
1e60:2864 |
Hailo-8 AI processor | vfio-pci |
02:00.0 |
2646:5024 |
Kingston DC2000B NVMe | vfio-pci |
03:00.0 |
2646:5024 |
Kingston DC2000B NVMe | vfio-pci |
21:00.0 |
10de:2bb1 |
NVIDIA RTX PRO 6000 Blackwell | vfio-pci |
42:00.0 |
1022:7901 |
AMD FCH SATA (AHCI), MCIO connector A | vfio-pci |
42:00.1 |
1022:7901 |
AMD FCH SATA (AHCI), MCIO connector B | vfio-pci |
83:00.0 |
1cc1:8201 |
ADATA XPG SX8200 Pro NVMe | vfio-pci |
84:00.0 |
c0a9:5428 |
Crucial T710 NVMe, 4 TB | vfio-pci |
a1:00.0 |
8086:2700 |
Intel Optane SSD 900P | vfio-pci |
a3:00.0 |
8086:2700 |
Intel Optane SSD 900P | vfio-pci |
a5:00.0 |
8086:2700 |
Intel Optane SSD 900P | vfio-pci |
a7:00.0 |
8086:2700 |
Intel Optane SSD 900P | vfio-pci |
a9:00.0 |
not recorded | Intel X550 ethernet | ixgbe, host |
a9:00.1 |
not recorded | Intel X550 ethernet | ixgbe, host |
ab:00.0 |
not recorded | ASPEED BMC graphics | ast, host |
c1:00.0 |
1344:51c0 |
Micron 7400 PRO NVMe | nvme, host |
c2:00.0 |
1344:51c0 |
Micron 7400 PRO NVMe | nvme, host |
c3:00.0 |
15b3:1015 |
Mellanox ConnectX-4 Lx, port 0 | mlx5_core, host |
c3:00.1 |
15b3:1015 |
Mellanox ConnectX-4 Lx, port 1 | mlx5_core, host |
e1:00.0 |
1bb1:5018 |
Seagate E18 NVMe | vfio-pci |
e2:00.0 |
1bb1:5018 |
Seagate E18 NVMe | vfio-pci |
e3:00.0 |
1bb1:5018 |
Seagate E18 NVMe | vfio-pci |
e4:00.0 |
1bb1:5018 |
Seagate E18 NVMe | vfio-pci |
e6:00.0 |
1022:7901 |
AMD FCH SATA, phantom | vfio-pci |
e6:00.1 |
1022:7901 |
AMD FCH SATA, phantom | vfio-pci |
- the four Optanes are the SLOG devices. the four Seagates and the disks behind the two MCIO connectors are the bulk storage
e6:00.0/.1are phantom. the chiplet topology exposes them but the board has no MCIO wiring behind them, so they get bound along with the real pair and that is harmless- the vendor:device IDs are dependable, and most of the addresses are not. the
two SATA controllers sit on the FCH at
42:00.0/.1and the GPU stayed at21:00.0for the whole year, but anything on an NVMe carrier moved more than once. a carrier reorder put the Hailo on a different bus, and adding the second boot NVMe pushed the Mellanox fromc2toc3 - everything below binds by ID where it can, and by address only for the two controllers that never move
1. what stays on the host¶
four devices stay on their normal drivers: the two Micron 7400 PROs that are the
host's own rpool boot mirror, the Mellanox that is its data NIC, the X550 that
is its management NIC, and the ASPEED BMC console.
their vendor:device IDs are deliberately absent from the vfio list in step 2:
1344:51c0, the Micron 7400 PRO. putting this on vfio takes the host's boot pool away from it15b3:1015, the Mellanox. putting this on vfio takes the host off the network. that is the usual ConnectX-4 Lx ID. i did not read it off this card, so confirm it withlspci -nn -s c3:before trusting it
everything else on the bus goes to the VM.
2. the modprobe rules¶
/etc/modprobe.d/vfio-pci.conf does three jobs. i keep them in one file, so
that when this goes wrong it can all be read in one place:
# 1. drivers the host never needs, keep them out entirely
blacklist nouveau
blacklist nvidia
blacklist nvidia_drm
blacklist nvidia_modeset
blacklist hailo_pci
# 2. drivers the host does need, just not first
softdep nvme pre: vfio-pci
softdep ahci pre: vfio-pci
softdep hailo_pci pre: vfio-pci
# 3. what vfio-pci should claim
options vfio-pci ids=2646:5024,8086:2700,1cc1:8201,1bb1:5018,1e60:2864,1022:7901,10de:2bb1,10de:22e8,c0a9:5428
blacklist a driver the host never needs, softdep one it does:
- the host has no use for the GPU's and the Hailo's drivers, so they are blacklisted outright
nvmeandahciare different. the host boots off NVMe, so it needsnvmeloaded. a blanketblacklist nvmewould take the host's own boot pool out along with everything else- for those two,
softdep nvme pre: vfio-pcimeans "before you loadnvme, loadvfio-pcifirst". vfio-pci claims everything inids=before the storage driver enumerates anything. the softdep sets the load order and excludes nothing. almost every guide leaves this line out hailo_pcigets both, because it costs nothing and that card caused enough trouble already (step 7)
do not use the same NVMe device ID for the host's boot volume and anything you
pass through. the ids= line cannot tell two identical drives apart, so if the
boot pool shares an ID with a passthrough drive you cannot have one without the
other. this is why the host boots off Micron 7400 PROs and nothing else on the
box is a Micron 7400 PRO.
what each ID catches:
| ID | catches |
|---|---|
2646:5024 |
both Kingston DC2000B NVMe (02:00.0, 03:00.0) |
8086:2700 |
all four Intel Optane 900P (a1, a3, a5, a7) |
1cc1:8201 |
ADATA SX8200 Pro (83:00.0) |
1bb1:5018 |
all four Seagate E18 (e1 to e4) |
1e60:2864 |
Hailo-8 (01:00.0) |
1022:7901 |
all four AMD FCH SATA controllers (42:00.0/.1, e6:00.0/.1) |
10de:2bb1 |
NVIDIA RTX PRO 6000 Blackwell (21:00.0) |
10de:22e8 |
Blackwell HDA audio. a no-op while the card is in compute mode, kept so flipping it back to display mode needs no edit |
c0a9:5428 |
Crucial T710 NVMe (84:00.0) |
- binding by vendor:device covers every identical drive with one ID, so adding another Optane needs no edit and a bus renumber breaks nothing
i made the original list with a script, because reading lspci by hand and
missing one device can cost you a pool. it takes the boot drives out of
zpool status for rpool or boot-pool, excludes those, and prints every
remaining NVMe and SATA device as a vendor:device pair:
scyto/virtio-fs-detection-and-exlusion.
that repo also has an initramfs hook that binds by address at boot. i don't use it: its list of BDFs is hard-coded, and after a card goes in and the bus renumbers, it binds the wrong device. the modprobe and udev binding here replaced it. the generator script is the fastest way to get a correct ID list on a rebuilt host.
3. pin the SATA controllers by address as well¶
/etc/udev/rules.d/99-vfio-udev.rules:
ACTION=="add", SUBSYSTEM=="pci", KERNELS=="0000:42:00.0", ATTR{driver_override}="vfio-pci"
ACTION=="add", SUBSYSTEM=="pci", KERNELS=="0000:42:00.0", RUN+="/bin/sh -c 'modprobe vfio-pci; echo 0000:42:00.0 > /sys/bus/pci/drivers/vfio-pci/bind'"
ACTION=="add", SUBSYSTEM=="pci", KERNELS=="0000:42:00.1", ATTR{driver_override}="vfio-pci"
ACTION=="add", SUBSYSTEM=="pci", KERNELS=="0000:42:00.1", RUN+="/bin/sh -c 'modprobe vfio-pci; echo 0000:42:00.1 > /sys/bus/pci/drivers/vfio-pci/bind'"
these rules pin the two motherboard MCIO connectors, where the spinning disks live. they are the only devices whose address i hardcode, because they are on the FCH and have never moved.
- a third layer on top of
1022:7901and theahcisoftdep in step 2. these two controllers carry the bulk pool, so i want them bound even if something interferes with the modprobe binding - note the rules bind rather than unbind. if
ahcihas already claimed the controller by the time udev runs, the bind fails, which is why the load ordering in step 2 still matters driver_overridesets the only driver the device will ever accept, and theRUN+=line binds it straight away
4. load vfio early, then apply¶
everything above configures vfio-pci, and none of it loads it. a softdep on a
module the initramfs does not carry does nothing, so list the modules
explicitly.
/etc/initramfs-tools/modules:
then rebuild the initramfs and reload the rules:
reboot for the modprobe changes to take effect.
- it has to be this file. modules listed here go into the initramfs and load
early in boot. on a host that boots off NVMe that is the only stage that
matters, because
nvmehas loaded long before the real root is up - the Proxmox wiki points at
/etc/modules-load.d/vfio.confinstead. systemd reads that in the real root, which is too late to win the race - do not assume the module is already in the image.
MODULES=most, the default, covers filesystem, ata, sata, scsi and usb drivers, andvfio-pciis none of those - older guides, mine included, list a fourth module here:
vfio_virqfd. it was folded into the vfio core in kernel 6.2 and no longer exists, so drop it
5. the VM itself¶
this is VM 100 as it stood. the only virtual disk was the one TrueNAS booted from:
| machine | q35 |
| BIOS | ovmf, with an EFI disk, pre-enrolled-keys=1 |
| CPU | 32 cores, 1 socket, type host, numa: 0 |
| memory | 131072, ballooning down to 65536 |
| guest agent | on |
| boot order | scsi0;ide2 |
| SCSI controller | virtio-scsi-single |
boot disk (scsi0) |
local-zfs:vm-100-disk-1,cache=writeback,discard=on,iothread=1,size=64G,ssd=1 |
network (net0) |
virtio, on vmbr0, firewall on |
| TPM | v2.0 |
| RNG | source=/dev/urandom |
| startup | up=60 |
q35because passthrough wants a PCIe machine, not the old i440fx PCI one- CPU type
host, not a model. ZFS wants the real instruction set - the 64 GB boot disk lived on the host's
local-zfs, so it was the one part of the NAS the host did own. everything holding data was passed through - ballooning on a ZFS box is a matter of taste. the 64 GiB floor keeps ARC from being squeezed too far
6. the hostpci map¶
one hostpci entry per passed-through device:
| slot | configuration |
|---|---|
hostpci0 |
0000:42:00.0,pcie=1,rombar=0 |
hostpci1 |
0000:42:00.1,pcie=1,rombar=0 |
hostpci2 |
0000:21:00.0,pcie=1,x-vga=0 |
hostpci3 |
0000:a1:00.0,pcie=1,rombar=0 |
hostpci4 |
0000:a3:00.0,pcie=1,rombar=0 |
hostpci5 |
0000:a5:00.0,pcie=1,rombar=0 |
hostpci6 |
0000:a7:00.0,pcie=1,rombar=0 |
hostpci7 |
0000:83:00.0,pcie=1,rombar=0 |
hostpci8 |
0000:84:00.0,pcie=1,rombar=0 |
hostpci9 |
0000:e1:00.0,pcie=1,rombar=0 |
hostpci10 |
0000:e2:00.0,pcie=1,rombar=0 |
hostpci11 |
0000:e3:00.0,pcie=1,rombar=0 |
hostpci12 |
0000:e4:00.0,pcie=1,rombar=0 |
hostpci13 |
0000:02:00.0,pcie=1,rombar=0 |
hostpci14 |
0000:03:00.0,pcie=1,rombar=0 |
rombar=0everywhere. none of these need their option ROM exposed and leaving it on can stall the VM at bootx-vga=0on the GPU because it is a compute card, not the VM's console
two things do not fit the hostpciN form and live on an args: line in
100.conf instead:
args: -set device.hostpci0.x-msix-relocation=bar5 -set device.hostpci1.x-msix-relocation=bar5 -device pcie-root-port,id=pcie_hailo,addr=12.0,bus=pcie.0,chassis=20,hotplug=off -device vfio-pci,host=0000:01:00.0,bus=pcie_hailo,addr=0x0
-
x-msix-relocation=bar5moves the MSI-X tables on the two SATA controllers. without it the VM will not start, and logs this:it appears to be common to EPYC boards of this generation, not an ASRock quirk, so expect it on anything similar
-
the Hailo-8 gets its own explicit
pcie-root-portwithhotplug=offin place of ahostpciNslot, see step 7
pass the two SATA controllers as separate devices, 42:00.0 and 42:00.1.
selecting 42:00 with "All Functions" ticked does not work on this board.
the slot numbers above are from one moment. the bus renumbered more than once
over the year, as carriers were reordered and cards came and went, and the
hostpciN block had to be rewritten to match each time.
this block and the host= in args: are the only things a renumber breaks,
because they name addresses. the modprobe rules and the Hailo udev rule name
IDs, so they carry over untouched.
a duplicate hostpciN key drops a device without an error
i once ended up with two hostpci14 lines after a renumber. nothing
complains: the file parses, the VM starts, and one of the two devices is
not there. if a drive goes missing after you edit the passthrough block,
count the keys before you suspect the hardware.
7. the Hailo-8, FLR and bus resets¶
the Hailo is the one device that does not sit in a hostpciN slot. it needs its
own root port, a slot and chassis chosen by hand, and FLR turned off by a udev
rule.
a function level reset makes the Hailo drop off the bus and re-enumerate. the
hotplug event that follows panics the host at BIOS level, so Proxmox's ordinary
hostpci path can't be used for it. it gets the explicit pcie-root-port with
hotplug=off from the args: line above instead. when that works, the link
event is logged and ignored:
choose the slot and chassis by hand. Proxmox generates a root port for each
hostpciN, and as more devices went in those grew into chassis 5 to 16.
slot=10 and chassis=10 collided with them and the VM crashed.
addr=12.0, chassis=20 sits clear of them.
even with hotplug off, the VM took about two and a half minutes to start, with this twice in the kernel log:
each attempt waits 65 seconds before falling back to a secondary bus reset on the parent port. the fix is to stop offering FLR:
cat /sys/bus/pci/devices/0000:01:00.0/reset_method # flr bus cxl_bus
echo bus > /sys/bus/pci/devices/0000:01:00.0/reset_method
vfio then goes straight to the bus reset and the VM starts promptly. cxl_bus in
that list is irrelevant for this card.
that write does not persist, so a udev rule sets it. key the rule on the vendor and device ID. a rule keyed on the BDF broke when i reshuffled the NVMe carrier and everything renumbered, and the delay came straight back.
/etc/udev/rules.d/99-hailo-reset.rules:
# Hailo-8 (1e60:2864): use bus reset instead of FLR
ACTION=="add|change", SUBSYSTEM=="pci", ATTR{vendor}=="0x1e60", ATTR{device}=="0x2864", ATTR{reset_method}="bus"
check it without rebooting:
- without
udevadm settlethe read races udev, and the first one still showsflr bus cxl_bus. read it twice before believing it
what virtualizing costs¶
it costs little until you have to write to a device's own flash.
card firmware cannot be flashed from inside the VM. vfio passes MMIO through but drops QSPI writes without an error, so the flash tool reports success and the version never changes. i hit this on the MemryX MX3, and its installer now refuses to try from a guest at all:
ERROR: this TrueNAS is a VM (kvm). MX3 firmware CANNOT be flashed from a
passthrough guest — VFIO silently drops the QSPI writes.
the fix is to do it on the host:
- shut the VM down
-
unbind the card from vfio on the host, with the address from
lspci -d 1fe9: -
run the vendor's flash tool on the host
- full power cycle, not a reboot. the card only reads its flash at power-on
the same applies to any card whose firmware updater writes to onboard flash. check the version before you assume an in-guest update worked, because the failure gives no error.
sources¶
- pve-nas1 PCIe device inventory and passthrough configuration, my own reference snapshot, where the tables come from
- Passthrough of MCIO based SATA controller not working, the Proxmox forum thread where i worked out the MSI-X relocation and the load ordering
- scyto/virtio-fs-detection-and-exlusion, the ID generator script, and the initramfs approach i abandoned
This page started as a gist: the original, with its comments