@samitouri / QOSamiQemu / commits / 256229e8f9

vfio/igd: Clear saved BDSM in legacy VBIOS ROM at load time

IGD does not come with a ROM BAR [1], the ROM BAR read by default from kernel is actually the host VBIOS shadow RAM region that contains host modifications on boot. With AI-assisted reverse engineering on VBIOS binaries, it is observed that VBIOS saves BDSM register value on first access and uses saved value if present. When the image is executed in guest, since there is already a saved HPA in VBIOS, it keeps using that value instead of the GPA programmed by SeaBIOS in BDSM register in PCI config space, causing VBIOS to program GTT entries with wrong address, resulting in garbled output in BIOS POST and the error below detected by i915 driver. i915 0000:00:02.0: [drm] *ERROR* Initial plane programming using invalid range, dma_addr=0x00000000db200000 ((null) [0x00000000baf00000-0x00000000beefffff]) The previous solution, c4c45e943e51 ("vfio/pci: Intel graphics legacy mode assignment"), adjusts GTT entry addresses to (addr - host BDSM + guest BDSM) to workaround that. But it is removed in 5aed8b0f0be2 ("vfio/igd: Remove GTT write quirk in IO BAR 4") due to inconsistent values in MMIO BAR0 and IO BAR4. Since it was a value latched into the VBIOS that breaks virtualization (QEMU does not map the GTT at the same address in the VM), a ROM quirk clearing the saved value in VBIOS image is introduced. It searches the BDSM accessor routine by matching a 19-byte signature anchored on the unique `mov $0x105e,%ax` instruction, then locates the offset of saved BDSM and clears it. This makes the routine fall through to the PCI config read on the first call inside the guest. [1] 3.5.15, 4th Generation Intel Core Processor Family Datasheet Vol. 2 https://www.intel.com/content/dam/www/public/us/en/documents/datasheets/4th-gen-core-family-desktop-vol-2-datasheet.pdf Fixes: 5aed8b0f0be2 ("vfio/igd: Remove GTT write quirk in IO BAR 4") Resolves: https://gitlab.com/qemu-project/qemu/-/work_items/3093 Reported-by: K S Maan <kirandeepmaan45@gmail.com> Cc: qemu-stable@nongnu.org Signed-off-by: Tomita Moeko <tomitamoeko@gmail.com> Reviewed-by: Alex Williamson <alex@shazbot.org> Link: https://lore.kernel.org/qemu-devel/20260708103100.23127-1-tomitamoeko@gmail.com Signed-off-by: Cédric Le Goater <clg@redhat.com>

Tomita Moeko committed Jul 8, 2026 at 18:31 UTC 256229e8f9b950a41b01ecee30524e7bd3235ef7
6 files changed +131
hw/vfio/igd-stubs.c
+5
@@ -18,3 +18,8 @@ bool vfio_probe_igd_config_quirk(VFIOPCIDevice *vdev, Error **errp)
18 {
19 return true;
20 }
21 +
22 +void vfio_igd_legacy_rom_quirk(VFIOPCIDevice *vdev)
23 +{
24 + return;
25 +}
hw/vfio/igd.c
+115
@@ -724,3 +724,118 @@ bool vfio_probe_igd_config_quirk(VFIOPCIDevice *vdev, Error **errp)
724
725 return vfio_pci_igd_config_quirk(vdev, errp);
726 }
727 +
728 +/*
729 + * IGD ROM BAR read from kernel is actually the host VBIOS shadow RAM region,
730 + * which contains host modifications. In Gen 6-9 VBIOS, the routine below is
731 + * used to get BDSM value when programming the initial GTT.
732 + * xx xx xx xx v: .long ? # saved value
733 + * 66 53 push %ebx
734 + * 66 2e 83 3e xx xx 00 cmpl $0x0,%cs:v # is saved value empty?
735 + * 74 07 je 1f # if zero, go compute
736 + * 66 2e a1 xx xx mov %cs:v,%eax # else return saved value
737 + * eb 0f jmp 2f
738 + * b8 5e 10 1: mov $0x105e,%ax # dev 00:02.0, offset 5E
739 + * e8 xx xx call pci_read_cfg_word
740 + * 66 c1 e0 10 shl $0x10,%eax # left shift 16 bits
741 + * 66 2e a3 xx xx mov %eax,%cs:v # save the result
742 + * 66 5b 2: pop %ebx
743 + * c3 ret
744 + * When running the VBIOS in guest, saved value still reflects the host stolen
745 + * memory base address, which is not correct in guest. So we need to patch the
746 + * VBIOS to clear the saved value.
747 + *
748 + * The unique 19-byte starts at `cmpl $0,%cs:v` and ends at `mov $0x105e,%ax`
749 + * anchors the match to the routine. Both `cs:` displacements must reference
750 + * the same offset.
751 + */
752 +static int igd_vbios_find_saved_bdsm(const uint8_t *rom, size_t rom_size,
753 + uint16_t *bdsm_offset)
754 +{
755 + static const uint8_t start[] = { 0x66, 0x2e, 0x83, 0x3e };
756 + static const uint8_t middle[] = { 0x00, 0x74, 0x07, 0x66, 0x2e, 0xa1 };
757 + static const uint8_t end[] = { 0xeb, 0x0f, 0xb8, 0x5e, 0x10 };
758 + uint16_t val;
759 + size_t i;
760 + bool found = false;
761 +
762 + if (rom_size < 19) {
763 + return -ENOENT;
764 + }
765 +
766 + for (i = 0; i + 19 <= rom_size; i++) {
767 + if (memcmp(rom + i, start, sizeof(start)) != 0 ||
768 + memcmp(rom + i + 6, middle, sizeof(middle)) != 0 ||
769 + memcmp(rom + i + 14, end, sizeof(end)) != 0) {
770 + continue;
771 + }
772 +
773 + /* same saved value address? */
774 + if (rom[i + 4] != rom[i + 12] || rom[i + 5] != rom[i + 13]) {
775 + continue;
776 + }
777 +
778 + if (found) {
779 + return -EEXIST;
780 + }
781 +
782 + val = rom[i + 4] | ((uint16_t)rom[i + 5] << 8);
783 + if (val + sizeof(uint32_t) <= rom_size) {
784 + *bdsm_offset = val;
785 + found = true;
786 + }
787 + }
788 +
789 + if (!found) {
790 + return -ENOENT;
791 + }
792 +
793 + return 0;
794 +}
795 +
796 +void vfio_igd_legacy_rom_quirk(VFIOPCIDevice *vdev)
797 +{
798 + uint8_t *rom = vdev->rom;
799 + int gen;
800 + uint16_t pcir_offset;
801 + uint16_t bdsm_offset = 0;
802 + uint8_t checksum = 0;
803 + uint32_t i;
804 +
805 + if (!vfio_pci_is(vdev, PCI_VENDOR_ID_INTEL, PCI_ANY_ID) ||
806 + !vfio_is_vga(vdev) || !vdev->vga) {
807 + return;
808 + }
809 +
810 + /* Only Gen 6~9 devices have legacy VBIOS as Option ROM */
811 + gen = igd_gen(vdev);
812 + if (gen < 6 || gen > 9) {
813 + return;
814 + }
815 +
816 + if (pci_get_word(rom) != 0xaa55) {
817 + return;
818 + }
819 +
820 + /* Must be a legacy ROM */
821 + pcir_offset = pci_get_word(rom + 0x18);
822 + if (pcir_offset + 0x14 >= vdev->rom_size ||
823 + memcmp(rom + pcir_offset, "PCIR", 4) ||
824 + pci_get_byte(rom + pcir_offset + 0x14) != 0x00) {
825 + return;
826 + }
827 +
828 + /* Search and clear the saved BDSM value */
829 + if (igd_vbios_find_saved_bdsm(rom, vdev->rom_size, &bdsm_offset)) {
830 + return;
831 + }
832 + memset(rom + bdsm_offset, 0, sizeof(uint32_t));
833 +
834 + /* Recalculate checksum and patch it. */
835 + for (i = 0; i < vdev->rom_size; i++) {
836 + checksum += rom[i];
837 + }
838 + rom[6] -= checksum;
839 +
840 + trace_vfio_pci_igd_vbios_patched(vdev->vbasedev.name);
841 +}
hw/vfio/pci-quirks.c
+5
@@ -1592,3 +1592,8 @@ bool vfio_add_virt_caps(VFIOPCIDevice *vdev, Error **errp)
1592
1593 return true;
1594 }
1595 +
1596 +void vfio_rom_quirk_setup(VFIOPCIDevice *vdev)
1597 +{
1598 + vfio_igd_legacy_rom_quirk(vdev);
1599 +}
hw/vfio/pci.c
+2
@@ -1119,6 +1119,8 @@ static bool vfio_pci_load_rom(VFIOPCIDevice *vdev, Error **errp)
1119 }
1120 }
1121
1122 + vfio_rom_quirk_setup(vdev);
1123 +
1124 return true;
1125 }
1126
hw/vfio/pci.h
+3
@@ -252,10 +252,13 @@ void vfio_bar_quirk_exit(VFIOPCIDevice *vdev, int nr);
252 void vfio_bar_quirk_finalize(VFIOPCIDevice *vdev, int nr);
253 void vfio_setup_resetfn_quirk(VFIOPCIDevice *vdev);
254 bool vfio_add_virt_caps(VFIOPCIDevice *vdev, Error **errp);
255 +void vfio_rom_quirk_setup(VFIOPCIDevice *vdev);
256 void vfio_quirk_reset(VFIOPCIDevice *vdev);
257 VFIOQuirk *vfio_quirk_alloc(int nr_mem);
258 +
259 void vfio_probe_igd_bar0_quirk(VFIOPCIDevice *vdev, int nr);
260 bool vfio_probe_igd_config_quirk(VFIOPCIDevice *vdev, Error **errp);
261 +void vfio_igd_legacy_rom_quirk(VFIOPCIDevice *vdev);
262
263 extern const PropertyInfo qdev_prop_nv_gpudirect_clique;
264
hw/vfio/trace-events
+1
@@ -90,6 +90,7 @@ vfio_pci_igd_bar4_write(const char *name, uint32_t index, uint32_t data, uint32_
90 vfio_pci_igd_bdsm_enabled(const char *name, int size) "%s %dMB"
91 vfio_pci_igd_host_bridge_enabled(const char *name) "%s"
92 vfio_pci_igd_lpc_bridge_enabled(const char *name) "%s"
93 +vfio_pci_igd_vbios_patched(const char *name) "%s"
94
95 # listener.c
96 vfio_iommu_map_notify(const char *op, uint64_t iova_start, uint64_t iova_end) "iommu %s @ 0x%"PRIx64" - 0x%"PRIx64