PCIe GPU Passthrough on a Dell Wyse 5070 Extended (AMD Embedded Radeon E9173)
A little while back I picked up two decommissioned Dell Wyse 5070s from work — the kind of thin clients that were never meant to do anything more exciting than run a remote desktop session. Turns out these two are the “Extended” chassis variant, and someone had them ordered from the factory with the optional dedicated GPU: an AMD Embedded Radeon E9173 module. No riser hacking required — the card was already sitting there. Naturally, that meant one of them was getting turned into a GPU-passthrough box, and after a frustrating detour through a very specific AMD reset quirk, it actually worked.
I didn’t write anything down while I was doing it (rookie mistake), so this post is reconstructed from my shell history after the fact. It’s as much a note to future-me as it is a guide, so treat the steps below as a solid starting point rather than gospel for your exact setup.
This post started as a reconstruction from shell history, then got filled in with the real BIOS options, grub config, and VM config pulled straight off the box — so what follows matches what’s actually running on
wyse2.
The build
A Dell Wyse 5070 Extended — the chassis variant with room for a factory add-in card — running Proxmox VE, with the dedicated GPU option Dell offered for it already installed. The goal: pass that GPU straight through to a VM instead of letting the Proxmox host touch it.
- Host CPU: Intel (the kernel cmdline uses
intel_iommu=on, so this is the Intel Celeron/Pentium variant of the 5070, not the AMD one) - GPU: an AMD Embedded Radeon E9173 — the factory-fit option for the
Extended chassis. It’s a GCN 4.0 “Lexa” chip with 2GB GDDR5 over a PCIe 3.0
x8 link, 35W TDP, no external power connector needed. PCI ID
1002:699fmatches this Lexa-family silicon, and its audio function1002:aae0rides along on the same card. - Hypervisor: Proxmox VE
Verify this: if you’re following along on different hardware, swap in your actual card’s PCI ID —
699fis specific to the Lexa/E9173 family.
Before you touch the OS: BIOS settings
None of this shows up in bash history, but it has to be right before any of the software steps below will work. Enter setup with F2 at boot, then head to the Virtualization support screen. Dell’s own manual for the 5070 lists exactly two options there: a “Virtualization” toggle for Intel Virtualization Technology (enabled by default), and a separate “VT for Direct I/O” option that lets a hypervisor use those extra Direct I/O capabilities — which is off by default.
- Virtualization → Enabled (this is Intel VT-x, and it’s already on out of the box)
- VT for Direct I/O → Enabled (this is VT-d — the one you actually have to flip yourself)
That second one is the setting the whole rest of this post depends on: no
VT-d, no DMAR ACPI table, no IOMMU groups, no passthrough.
Step 1 — Turn on IOMMU at the kernel level
1
nano /etc/default/grub
Here’s the actual line from /etc/default/grub:
1
GRUB_CMDLINE_LINUX_DEFAULT="quiet intel_iommu=on iommu=pt initcall_blacklist=sysfb_init pcie_acs_override=downstream,multifunction"
Three flags doing three different jobs:
intel_iommu=on iommu=pt— the baseline: turns on VT-d and puts the IOMMU in passthrough mode for everything that isn’t explicitly isolated.initcall_blacklist=sysfb_init— stops the kernel’s genericsysfb(simple-framebuffer) driver from registering a platform device against the GPU’s UEFI framebuffer during early boot. Without this,sysfb/simpledrmcan grab the E9173 beforevfio-pcigets a chance to, and no amount of blacklistingamdgpufixes that — it’s a different driver entirely.pcie_acs_override=downstream,multifunction— forces the kernel to treat each downstream port and multifunction device as its own isolation boundary. This one is a strong signal about the hardware: it means the 5070’s chipset was not exposing clean per-device IOMMU groups on its own, and the GPU’s group needed to be split apart from something else sharing it. See below.
1
2
update-grub
reboot
Confirm it actually took effect:
1
2
3
cat /proc/cmdline # should show intel_iommu=on iommu=pt
ls /sys/firmware/acpi/tables/DMAR # must exist — proves VT-d is on in BIOS
ls /sys/kernel/iommu_groups/ # must list numbered groups
If DMAR is missing, the BIOS setting didn’t take — go back and check VT-d
is actually enabled, not just the kernel flag.
Step 2 — Find the GPU and its IOMMU group
1
lspci -nn | grep 01:00
This is where 1002:699f (the E9173 GPU function) and 1002:aae0 (its
DisplayPort audio function) come from — both live on the same bus address
and need to move together.
1
2
GRP=$(readlink /sys/bus/pci/devices/0000:01:00.0/iommu_group)
ls /sys/kernel/iommu_groups/${GRP##*/}/devices/
On wyse2 that comes back with exactly one device:
1
2
3
4
root@wyse2:~# GRP=$(readlink /sys/bus/pci/devices/0000:01:00.0/iommu_group)
ls /sys/kernel/iommu_groups/${GRP##*/}/devices/
0000:01:00.0
root@wyse2:~#
That’s actually notable: normally, sibling functions of the same physical
card — here, the GPU at .0 and its HDMI/DP audio at .1 — land in the
same IOMMU group by default, because the PCIe spec has no way to prove
two functions behind the same device are isolated from each other without
ACS support. This board’s chipset doesn’t implement that, which is exactly
what the multifunction half of pcie_acs_override=downstream,multifunction
from Step 1 is for — it forces the kernel to treat sibling functions as
separately isolated. The result: 0000:01:00.0 gets its own single-device
group, and 0000:01:00.1 (the audio function) would show up in its own
separate one-device group too if you checked it the same way.
Worth knowing: ACS override is a well-known “trust it and move on” hack — the isolation is now assumed by the kernel, not proven by hardware. Fine for a homelab box you control end to end; not something to reach for on a shared or untrusted host.
Since the GPU and its audio ended up in different groups, they still need
to move as a pair for passthrough to make sense — which is exactly why the
VM config in Step 6 targets the whole slot (hostpci0: 0000:01:00,...,
with no .0/.1 suffix) rather than a single function: Proxmox/QEMU reads
that as “every function at this address,” regardless of which IOMMU group
each one individually landed in.
Step 3 — Bind the GPU to vfio-pci before anything else can claim it
1
2
3
4
echo "options vfio-pci ids=1002:699f,1002:aae0 disable_vga=1" > /etc/modprobe.d/vfio.conf
printf 'blacklist amdgpu\nblacklist radeon\n' >> /etc/modprobe.d/blacklist.conf
update-initramfs -u -k all
reboot
1
lspci -nnk -s 01:00
Check the “Kernel driver in use” line — it should say vfio-pci, not
amdgpu. Here’s what a clean bind looks like on wyse2 (one of the two
boxes):
1
2
3
4
5
6
7
8
9
root@wyse2:~# lspci -nnk -s 01:00
01:00.0 VGA compatible controller [0300]: Advanced Micro Devices, Inc. [AMD/ATI] Lexa PRO [Radeon 540/540X/550/550X / RX 540X/550/550X] [1002:699f] (rev 81)
Subsystem: Advanced Micro Devices, Inc. [AMD/ATI] Device [1002:ea73]
Kernel driver in use: vfio-pci
Kernel modules: amdgpu
01:00.1 Audio device [0403]: Advanced Micro Devices, Inc. [AMD/ATI] Baffin HDMI/DP Audio [Radeon RX 550 640SP / RX 560/560X] [1002:aae0]
Subsystem: Advanced Micro Devices, Inc. [AMD/ATI] Baffin HDMI/DP Audio [Radeon RX 550 640SP / RX 560/560X] [1002:aae0]
Kernel driver in use: vfio-pci
Kernel modules: snd_hda_intel
Both functions show Kernel driver in use: vfio-pci, with the host driver
(amdgpu / snd_hda_intel) demoted to just “available module” — that’s
exactly the state you want before handing the card to a VM.
Step 4 — Belt and suspenders: force load order with softdep
Blacklisting is sometimes not enough on its own (module autoloading can race during initramfs), so:
1
2
3
4
5
6
7
8
9
10
11
12
13
cat >> /etc/modules <<'EOF'
vfio
vfio_iommu_type1
vfio_pci
EOF
cat >> /etc/modprobe.d/vfio.conf <<'EOF'
softdep amdgpu pre: vfio-pci
softdep snd_hda_intel pre: vfio-pci
EOF
update-initramfs -u -k all
reboot
1
lspci -nnk -s 01:00 # re-confirm vfio-pci owns the card
Side quest: general Proxmox housekeeping
Not part of passthrough, but the same session also ran the standard community-scripts Proxmox post-install tuning script, and reclaimed the thin-pool storage into the root filesystem:
1
2
3
4
5
6
7
bash -c "$(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/tools/pve/post-pve-install.sh)"
reboot
pvesm remove local-lvm
lvremove /dev/pve/data
lvextend -l +100%FREE /dev/pve/root
resize2fs /dev/mapper/pve-root
Worth its own short post if you want to document it — I’ve left it out of the passthrough walkthrough below since it’s unrelated to the GPU.
Step 5 — The AMD reset bug, and why vendor-reset is non-negotiable
Here’s the quirk you half-remembered: many older AMD GPUs (the whole
Polaris/Lexa generation — which is exactly what a 699f card is — plus early
Vega) never got a working PCIe Function Level Reset implemented in
firmware. In practice that means:
- First VM boot after a host reboot: GPU passthrough works fine.
- Stop or reset the VM and start it again: the GPU is left in a broken state and either the VM hangs on boot or the guest never sees a working GPU — until you power-cycle the whole Proxmox host.
That matches the repeated qm start / qm restart / qm reboot / qm list
entries in the history around this point — that’s the reset bug in action,
not random flakiness.
gnif/vendor-reset is a small kernel module that ships hardware-specific reset sequences for exactly these GPUs, so the card comes back cleanly every time the VM restarts.
1
2
3
4
5
6
apt install pve-headers-$(uname -r)
apt install git dkms pve-headers
git clone https://github.com/gnif/vendor-reset.git
cd vendor-reset/
dkms install .
echo "vendor-reset" >> /etc/modules
Secure Boot + DKMS = you need to enroll a MOK key
Because this is a self-built, unsigned kernel module, a Secure-Boot-enabled host will refuse to load it until you enroll the DKMS signing key:
1
mokutil --import /var/lib/dkms/mok.pub
Note:
mokutil --importprompts for a one-time password right there in the terminal — you choose it yourself on the spot. Write it down, because you need it again on the next reboot, at the blue MOK Management screen (enroll → confirm → type that password), and it’s not a screen you can paste into. The command shows up twice in the log, which usually means the first enrollment didn’t stick — often because the reboot afterward got skipped, or the key changed after a DKMS rebuild. Iflsmod | grep vendor_resetcomes up empty after rebooting, redoing this is the first thing to try.
1
2
update-grub
reboot
At boot, go through the MOK enrollment screen, then confirm the module is actually loaded:
1
2
lsmod | grep vendor_reset
lspci -nnk -s 01:00 # driver should still read vfio-pci
Step 6 — Attach the GPU to the VM
Checking /etc/pve/qemu-server/101.conf confirms how this landed — either
set through the web UI or a qm set run outside this shell session:
1
2
3
4
5
6
7
8
9
10
11
12
13
agent: 1
balloon: 0
bios: ovmf
boot: order=scsi0;net0
cores: 4
cpu: x86-64-v2-AES
efidisk0: local:101/vm-101-disk-1.qcow2,efitype=4m,ms-cert=2023k,pre-enrolled-keys=1,size=528K
hostpci0: 0000:01:00,pcie=1,x-vga=1
machine: pc-q35-11.0
memory: 5120
ostype: win11
scsi0: local:101/vm-101-disk-0.qcow2,discard=on,iothread=1,size=90G
scsihw: virtio-scsi-single
The bits that matter for passthrough:
hostpci0: 0000:01:00,pcie=1,x-vga=1— the whole01:00device (both the GPU and audio function) passed through,pcie=1so it attaches as a real PCIe device instead of legacy PCI,x-vga=1marking it as the VM’s primary display.machine: pc-q35-11.0— Q35 chipset, required for PCIe passthrough (the older i440fx machine type doesn’t support it properly).bios: ovmf+efidisk0: ...,pre-enrolled-keys=1— UEFI firmware with Microsoft’s Secure Boot keys pre-enrolled at disk creation, paired withostype: win11— this is a Windows 11 guest, which requires Secure Boot to install and boot at all.
The repeated qm start 101 / qm restart 101 / qm reboot 101 attempts
right before the vendor-reset install, and the clean qm start 101 after
it, is a good before/after signature of the reset bug getting fixed.
Step 7 — Guest Secure Boot
1
qm enroll-efi-keys 101
The config already shows efidisk0: ...,pre-enrolled-keys=1, which normally
enrolls Microsoft’s default Secure Boot certificates automatically when the
EFI disk is created. Running qm enroll-efi-keys on top of that four times
in a row is harmless (it’s idempotent) — it more likely means the OVMF
NVRAM needed a manual nudge at some point while troubleshooting, possibly
after the EFI disk got recreated. Either way, this is the command to reach
for if a Windows 11 guest ever complains that Secure Boot isn’t satisfied.
Lessons learned
pcie_acs_overrideandinitcall_blacklist=sysfb_initare easy to forget about. They don’t show up in most “enable IOMMU” tutorials because plenty of boards don’t need them — but on hardware that doesn’t implement ACS on its downstream ports or between a card’s own sibling functions (like this one, where the GPU and its audio function only ended up isolated because of themultifunctionoverride), or with a UEFI GOP framebuffer thatsysfb/simpledrmgrabs beforevfio-pcican, passthrough silently fails without them, and the fix isn’t obvious from the symptom.- Blacklist and softdep. Blacklisting
amdgpu/radeonstops the host driver from loading later, but asoftdep ... pre: vfio-pciline is what actually wins the race during early boot. - Old AMD cards + passthrough = plan for
vendor-resetfrom day one. Anything Polaris/Lexa/early-Vega will hang on VM restart without it — it’s not an edge case, it’s the default behavior. - Secure Boot + DKMS means remembering a MOK password. Write down the
one you set when
mokutil --importasks for it; you’ll need it at the next boot, in a screen that doesn’t take pasted input. - Always re-check
lspci -nnk -s 01:00after every reboot during setup — it’s the fastest way to see whethervfio-pci,amdgpu, orvendor-resetcurrently owns the card.
Full command reference
Cleaned-up, step-ordered command log
```bash # --- IOMMU --- nano /etc/default/grub # GRUB_CMDLINE_LINUX_DEFAULT="quiet intel_iommu=on iommu=pt initcall_blacklist=sysfb_init pcie_acs_override=downstream,multifunction" update-grub reboot cat /proc/cmdline ls /sys/firmware/acpi/tables/DMAR ls /sys/kernel/iommu_groups/ # --- Identify GPU --- lspci -nn | grep 01:00 GRP=$(readlink /sys/bus/pci/devices/0000:01:00.0/iommu_group) ls /sys/kernel/iommu_groups/${GRP##*/}/devices/ # --- Bind to vfio-pci --- echo "options vfio-pci ids=1002:699f,1002:aae0 disable_vga=1" > /etc/modprobe.d/vfio.conf printf 'blacklist amdgpu\nblacklist radeon\n' >> /etc/modprobe.d/blacklist.conf update-initramfs -u -k all reboot lspci -nnk -s 01:00 # --- Force load order --- cat >> /etc/modules <<'EOF' vfio vfio_iommu_type1 vfio_pci EOF cat >> /etc/modprobe.d/vfio.conf <<'EOF' softdep amdgpu pre: vfio-pci softdep snd_hda_intel pre: vfio-pci EOF update-initramfs -u -k all reboot lspci -nnk -s 01:00 # --- vendor-reset for the AMD reset bug --- apt install pve-headers-$(uname -r) apt install git dkms pve-headers git clone https://github.com/gnif/vendor-reset.git cd vendor-reset/ dkms install . echo "vendor-reset" >> /etc/modules mokutil --import /var/lib/dkms/mok.pub update-grub reboot # enroll the MOK key at the blue screen lsmod | grep vendor_reset lspci -nnk -s 01:00 # --- Attach to VM and start --- qm set 101 -hostpci0 0000:01:00,pcie=1,x-vga=1 # or via web UI qm start 101 # --- Guest Secure Boot (if OVMF + Secure Boot in the VM) --- qm enroll-efi-keys 101 qm start 101 ```If you’re chasing the same AMD reset bug, the gnif/vendor-reset GitHub repo has an up-to-date list of which chips are supported — worth checking before you assume DKMS alone will fix it.