The last post ended on one detection out of 85 still standing. It wants a display that can report a gamma ramp, and a headless guest does not have one. The fix I wrote down was to pass a physical GPU through, and the reason I did not was that my only card does real work on Linux.

Except the machine has two. The 7950X carries a Raphael iGPU that has never drawn a pixel, because the desktop runs on the 7900XTX. Give the integrated one to the VM, keep the discrete one for Linux, and nothing has to be given up.

Can you hand an APU's integrated GPU to a Windows guest?

Short answer: yes, and it needs a firmware file your motherboard vendor never meant you to extract. It also has a failure mode a discrete card does not have. The GPU goes to the guest, the processor that loads its firmware stays on the host, and when those two fall out of step only a host reboot puts them back.

Code 43, and the firmware nobody ships#

The passthrough itself is ordinary. Bind 1002:164e and its audio function to vfio-pci, hand them to the guest, install the AMD driver. Windows names the device correctly, the driver loads, and the device fails to start. Code 43.

This is not the reset bug. It is a missing GOP.

A discrete card carries its video BIOS on the card, so OVMF reads it and knows how to bring the display up. An integrated GPU carries nothing. Its init code lives in the motherboard firmware, which the guest never sees, and Raphael has no readable PCI option ROM to fall back on. So the guest has a GPU it cannot initialise.

You have to supply both halves yourself.

The vBIOS is the easy one - amdgpu keeps a copy and exposes it, 44544 bytes straight out of debugfs:

/sys/kernel/debug/dri/0/amdgpu_vbios

The GOP driver comes out of your own BIOS image. It is a PE32+ EFI driver sitting under a known section GUID, about 83 KB:

D151D96B-90F0-4603-A1FD-C2F2FD6CF374

Take the DXE one. There is a second, smaller AMD GOP in there at roughly 41 KB which is the PEI build, and it will not work as an option ROM. Wrap the DXE driver in a PCI expansion ROM stamped with the card's own IDs (-f 0x1002 -i 0x164e), then attach the vBIOS to the GPU function and the GOP ROM to the audio function, and pass both as one multifunction device on the same guest slot.

ConfigManagerErrorCode goes to 0 and the card is a real GPU in the guest. The whole fight was over 83 KB of firmware that ships in your board and nowhere else.

The reset bug is not the reset bug#

Everyone who passes an AMD card through knows the reset bug: the VM stops, the card is left in a state the host driver cannot re-grab, and you reboot. On an APU it looks identical and it is a different problem.

APU package
  +---------------------------------------------------------+
  |  0f:00.0  iGPU            -> vfio-pci  -> GUEST          |
  |  0f:00.1  iGPU audio      -> vfio-pci  -> GUEST          |
  |  0f:00.2  PSP             -> ccp       -> HOST  (fTPM)   |
  |  0f:00.3  USB             -> xhci      -> HOST           |
  |  0f:00.4  USB             -> xhci      -> HOST           |
  +---------------------------------------------------------+

  guest driver needs firmware
        |
        v
  asks the PSP  --> which the guest does not own, and never will
        |
        +-- transaction goes wrong -> ring stops answering
                                   -> nothing on the host clears it

The PSP loads GPU firmware. It is also the platform security processor that backs the host's fTPM, so it is not a GPU device you can simply pass along with the card. It has to stay on the host.

So the firmware path is cut in half by the VM boundary. Every time the guest driver needs a firmware load - init, a recovery after a timeout, some mode sets - the request reaches for a processor that belongs to somebody else. Usually it works. When it does not, the PSP's ring stops answering and stays that way:

psp reg (0x16080) wait timed out, mask: 8000ffff, read: 80020115 exp: 80000000
PSP create ring failed!
PSP firmware loading failed
hw_init of IP block <psp> failed -22
Fatal error during GPU init

That is what a wedged APU iGPU looks like from the host, and read: 80020115 came back byte for byte on every attempt afterwards.

What actually wedges it, and what clears it#

Two things wedge it. An ungraceful stop, which I expected. And a display-topology change while the guest is running, which I did not - I disabled a virtual display adapter on a healthy card and it went straight to Code 43.

It is worth reading that failure properly before trying to fix it, because I did not. The wait is mask: 8000ffff, exp: 80000000, so only bit 31 and the low 16 bits are being checked, and bit 31 is set in every single reading. The PSP is not hung and it is not ignoring the driver. It completes the command and answers with an error code in the low bits.

The card is not failing to talk to its firmware processor. Its firmware processor is saying no.

That changes what a fix would have to be, and it kills most of them:

LeverResult
Guest disable/enablepreventive only, does nothing to a wedged ring
amdgpu MODE2, the automatic choiceresets the GPU core, never the PSP
amdgpu MODE1, forcedthe only thing that reaches it - logs GPU psp mode1 reset, moves the error from 0115 to 000a, never to ready
reset_method 0, 1, 4isn't supported, using AUTO instead. Raphael has two reset methods and that is all
Re-init the PSP's own driverbrings tee and psp back for the fTPM, does not touch the GPU ring
Same, with ccp unbound entirelyrules out driver contention. Identical error
PCI secondary bus reset of the bridgeresets PSP, fTPM and both USB controllers together, crashed the host, did not clear the ring
Host rebootworks

The fTPM answered TPM2 commands normally through every one of those, which is the tell. The PSP is perfectly healthy as a platform device. Only the ring the GPU needs is unusable, and the most likely reason is the one thing a reboot fixes and a reset does not: a registration the guest never tore down when it died.

The host cycle is the interesting failure. Binding the iGPU to amdgpu makes the host driver re-initialise the PSP, and from a wedged state that init does not fail politely - it takes the machine down. The thing you reach for to avoid a reboot is the thing that reboots you.

So the guest-side fix is worth having, as long as you are honest about what it is. A scheduled task disables the iGPU when Windows starts shutting down, and a second one cycles it at boot. That keeps the card quiesced across a clean restart so the PSP never wedges. It is prevention, not repair.

Seeing a guest with no monitor#

Nothing is plugged into the iGPU, so there is no display to capture. A virtual display driver fills that in. The current one is IddCx and user-mode, which means Secure Boot's kernel-signing rules do not apply to it, but its catalog is signed by a publisher Windows does not trust by default, so pnputil refuses it with 0xE0000242 until you import the signer into the machine's trusted root and publisher stores. After that it is a root-enumerated device and needs devcon to create, not pnputil.

Sunshine captures that display and encodes it on the iGPU's own AMF encoder, H.264 and HEVC. No AV1 on Raphael. Moonlight on the host picks it up.

Do not use RDP for this. RDP reconnects to the auto-login console session and forces the display hand-off from the virtual display to its own, which bugchecks the AMD driver with 0xA0000001 and takes the guest with it.

The detection is still standing#

The check I started all of this for reads the primary display's COLORMGMTCAPS and looks for one bit, CM_GAMMA_RAMP. A virtual display reports zero.

I assumed that was an EDID problem, so I built a proper one - real gamma value, sRGB chromaticity, a native timing - and fed it to the virtual display. Still zero.

It was never an EDID problem. CM_GAMMA_RAMP says the display adapter has a programmable gamma LUT in hardware. EDID describes the monitor. You cannot hand a software adapter a hardware capability by describing a monitor at it, no matter how convincing the monitor is.

Which leaves the hardware answer: a dummy plug, so the real GPU drives a real display. I put one in and the iGPU picked it up immediately as a 1080p panel, which is the correct display for the check to pass. Then the display layout changed, and the PSP wedged.

What I got wrong#

Three, and the first two cost real time.

I wrote down the host amdgpu cycle as the safe recovery. It had worked twice, so I treated it as the fallback. It works while the PSP is clean and takes the host down once it is not, and I established which by taking the host down.

I treated the guest-side disable/enable as a cure. It is prevention. Disabling the card before the guest powers off keeps the PSP clean for next time; running it against an already-wedged card changes nothing. I spent a while re-running a fix against a state it was never able to touch.

The bus reset was reasoning without a blast radius. If the card will not reset, reset the bridge above it. Correct, and the bridge also carries the fTPM and both USB controllers, so the reset went through the host's security processor on the way. It crashed the machine and the PSP came back wedged anyway.

Rule of thumb: if a device's firmware lives on a processor you did not pass through, you do not own that device's reset.

Where I landed#

A discrete card carries its own firmware processor, which is why discrete passthrough is boring and this was not. Nothing here is a configuration mistake to be tuned out - the split is structural, and it will still be there on the next kernel.

The integrated GPU passes through, runs games, and encodes its own stream. I would not put anything I cared about behind it. The detection I started chasing is still standing, and it is standing for a better reason than before: not because the guest cannot answer, but because answering it needs a display the card can only provide while it is healthy.

Source: git.archworks.co/sandwich/vfio-native.