From: "Cédric Le Goater" <clg@redhat.com>
To: qemu-devel@nongnu.org
Cc: "Tomita Moeko" <tomitamoeko@gmail.com>,
"K S Maan" <kirandeepmaan45@gmail.com>,
qemu-stable@nongnu.org, "Alex Williamson" <alex@shazbot.org>,
"Cédric Le Goater" <clg@redhat.com>
Subject: [PULL 01/13] vfio/igd: Clear saved BDSM in legacy VBIOS ROM at load time
Date: Tue, 21 Jul 2026 19:05:06 +0200 [thread overview]
Message-ID: <20260721170518.4160785-2-clg@redhat.com> (raw)
In-Reply-To: <20260721170518.4160785-1-clg@redhat.com>
From: Tomita Moeko <tomitamoeko@gmail.com>
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>
---
hw/vfio/pci.h | 3 ++
hw/vfio/igd-stubs.c | 5 ++
hw/vfio/igd.c | 115 +++++++++++++++++++++++++++++++++++++++++++
hw/vfio/pci-quirks.c | 5 ++
hw/vfio/pci.c | 2 +
hw/vfio/trace-events | 1 +
6 files changed, 131 insertions(+)
diff --git a/hw/vfio/pci.h b/hw/vfio/pci.h
index fe52e9df6e67707c9899d418b0261afeedf4aab2..c9ab9498708ec0343e206c7f343085b0be8a7a1e 100644
--- a/hw/vfio/pci.h
+++ b/hw/vfio/pci.h
@@ -252,10 +252,13 @@ void vfio_bar_quirk_exit(VFIOPCIDevice *vdev, int nr);
void vfio_bar_quirk_finalize(VFIOPCIDevice *vdev, int nr);
void vfio_setup_resetfn_quirk(VFIOPCIDevice *vdev);
bool vfio_add_virt_caps(VFIOPCIDevice *vdev, Error **errp);
+void vfio_rom_quirk_setup(VFIOPCIDevice *vdev);
void vfio_quirk_reset(VFIOPCIDevice *vdev);
VFIOQuirk *vfio_quirk_alloc(int nr_mem);
+
void vfio_probe_igd_bar0_quirk(VFIOPCIDevice *vdev, int nr);
bool vfio_probe_igd_config_quirk(VFIOPCIDevice *vdev, Error **errp);
+void vfio_igd_legacy_rom_quirk(VFIOPCIDevice *vdev);
extern const PropertyInfo qdev_prop_nv_gpudirect_clique;
diff --git a/hw/vfio/igd-stubs.c b/hw/vfio/igd-stubs.c
index f7687d909125c2233e76f7e6d03aa90f38c3b89f..5f60b24c8b46d94f0de2573aeb6b6c80690476cb 100644
--- a/hw/vfio/igd-stubs.c
+++ b/hw/vfio/igd-stubs.c
@@ -18,3 +18,8 @@ bool vfio_probe_igd_config_quirk(VFIOPCIDevice *vdev, Error **errp)
{
return true;
}
+
+void vfio_igd_legacy_rom_quirk(VFIOPCIDevice *vdev)
+{
+ return;
+}
diff --git a/hw/vfio/igd.c b/hw/vfio/igd.c
index e091f21b6a3aea0013e0b3094d2ca13588200c98..413a49aae9163c6500bc50f29e5454f698addaa4 100644
--- a/hw/vfio/igd.c
+++ b/hw/vfio/igd.c
@@ -724,3 +724,118 @@ bool vfio_probe_igd_config_quirk(VFIOPCIDevice *vdev, Error **errp)
return vfio_pci_igd_config_quirk(vdev, errp);
}
+
+/*
+ * IGD ROM BAR read from kernel is actually the host VBIOS shadow RAM region,
+ * which contains host modifications. In Gen 6-9 VBIOS, the routine below is
+ * used to get BDSM value when programming the initial GTT.
+ * xx xx xx xx v: .long ? # saved value
+ * 66 53 push %ebx
+ * 66 2e 83 3e xx xx 00 cmpl $0x0,%cs:v # is saved value empty?
+ * 74 07 je 1f # if zero, go compute
+ * 66 2e a1 xx xx mov %cs:v,%eax # else return saved value
+ * eb 0f jmp 2f
+ * b8 5e 10 1: mov $0x105e,%ax # dev 00:02.0, offset 5E
+ * e8 xx xx call pci_read_cfg_word
+ * 66 c1 e0 10 shl $0x10,%eax # left shift 16 bits
+ * 66 2e a3 xx xx mov %eax,%cs:v # save the result
+ * 66 5b 2: pop %ebx
+ * c3 ret
+ * When running the VBIOS in guest, saved value still reflects the host stolen
+ * memory base address, which is not correct in guest. So we need to patch the
+ * VBIOS to clear the saved value.
+ *
+ * The unique 19-byte starts at `cmpl $0,%cs:v` and ends at `mov $0x105e,%ax`
+ * anchors the match to the routine. Both `cs:` displacements must reference
+ * the same offset.
+ */
+static int igd_vbios_find_saved_bdsm(const uint8_t *rom, size_t rom_size,
+ uint16_t *bdsm_offset)
+{
+ static const uint8_t start[] = { 0x66, 0x2e, 0x83, 0x3e };
+ static const uint8_t middle[] = { 0x00, 0x74, 0x07, 0x66, 0x2e, 0xa1 };
+ static const uint8_t end[] = { 0xeb, 0x0f, 0xb8, 0x5e, 0x10 };
+ uint16_t val;
+ size_t i;
+ bool found = false;
+
+ if (rom_size < 19) {
+ return -ENOENT;
+ }
+
+ for (i = 0; i + 19 <= rom_size; i++) {
+ if (memcmp(rom + i, start, sizeof(start)) != 0 ||
+ memcmp(rom + i + 6, middle, sizeof(middle)) != 0 ||
+ memcmp(rom + i + 14, end, sizeof(end)) != 0) {
+ continue;
+ }
+
+ /* same saved value address? */
+ if (rom[i + 4] != rom[i + 12] || rom[i + 5] != rom[i + 13]) {
+ continue;
+ }
+
+ if (found) {
+ return -EEXIST;
+ }
+
+ val = rom[i + 4] | ((uint16_t)rom[i + 5] << 8);
+ if (val + sizeof(uint32_t) <= rom_size) {
+ *bdsm_offset = val;
+ found = true;
+ }
+ }
+
+ if (!found) {
+ return -ENOENT;
+ }
+
+ return 0;
+}
+
+void vfio_igd_legacy_rom_quirk(VFIOPCIDevice *vdev)
+{
+ uint8_t *rom = vdev->rom;
+ int gen;
+ uint16_t pcir_offset;
+ uint16_t bdsm_offset = 0;
+ uint8_t checksum = 0;
+ uint32_t i;
+
+ if (!vfio_pci_is(vdev, PCI_VENDOR_ID_INTEL, PCI_ANY_ID) ||
+ !vfio_is_vga(vdev) || !vdev->vga) {
+ return;
+ }
+
+ /* Only Gen 6~9 devices have legacy VBIOS as Option ROM */
+ gen = igd_gen(vdev);
+ if (gen < 6 || gen > 9) {
+ return;
+ }
+
+ if (pci_get_word(rom) != 0xaa55) {
+ return;
+ }
+
+ /* Must be a legacy ROM */
+ pcir_offset = pci_get_word(rom + 0x18);
+ if (pcir_offset + 0x14 >= vdev->rom_size ||
+ memcmp(rom + pcir_offset, "PCIR", 4) ||
+ pci_get_byte(rom + pcir_offset + 0x14) != 0x00) {
+ return;
+ }
+
+ /* Search and clear the saved BDSM value */
+ if (igd_vbios_find_saved_bdsm(rom, vdev->rom_size, &bdsm_offset)) {
+ return;
+ }
+ memset(rom + bdsm_offset, 0, sizeof(uint32_t));
+
+ /* Recalculate checksum and patch it. */
+ for (i = 0; i < vdev->rom_size; i++) {
+ checksum += rom[i];
+ }
+ rom[6] -= checksum;
+
+ trace_vfio_pci_igd_vbios_patched(vdev->vbasedev.name);
+}
diff --git a/hw/vfio/pci-quirks.c b/hw/vfio/pci-quirks.c
index bccf31751fd47387fd89110e27b695e5c822d5c4..c5b4f9091d498d49eb3371c0502e7ab6b7b76b30 100644
--- a/hw/vfio/pci-quirks.c
+++ b/hw/vfio/pci-quirks.c
@@ -1592,3 +1592,8 @@ bool vfio_add_virt_caps(VFIOPCIDevice *vdev, Error **errp)
return true;
}
+
+void vfio_rom_quirk_setup(VFIOPCIDevice *vdev)
+{
+ vfio_igd_legacy_rom_quirk(vdev);
+}
diff --git a/hw/vfio/pci.c b/hw/vfio/pci.c
index c204706e63045c930bda7977adc8034f907b6890..dcfc92aae1c78d3a803665bcaea1e83f66a6d030 100644
--- a/hw/vfio/pci.c
+++ b/hw/vfio/pci.c
@@ -1119,6 +1119,8 @@ static bool vfio_pci_load_rom(VFIOPCIDevice *vdev, Error **errp)
}
}
+ vfio_rom_quirk_setup(vdev);
+
return true;
}
diff --git a/hw/vfio/trace-events b/hw/vfio/trace-events
index f71d0bbc0a5440e4ccd374fac4733af08438e7be..bfdaf229e42b0d9d40e38c828c97cf47eb831a87 100644
--- a/hw/vfio/trace-events
+++ b/hw/vfio/trace-events
@@ -90,6 +90,7 @@ vfio_pci_igd_bar4_write(const char *name, uint32_t index, uint32_t data, uint32_
vfio_pci_igd_bdsm_enabled(const char *name, int size) "%s %dMB"
vfio_pci_igd_host_bridge_enabled(const char *name) "%s"
vfio_pci_igd_lpc_bridge_enabled(const char *name) "%s"
+vfio_pci_igd_vbios_patched(const char *name) "%s"
# listener.c
vfio_iommu_map_notify(const char *op, uint64_t iova_start, uint64_t iova_end) "iommu %s @ 0x%"PRIx64" - 0x%"PRIx64
--
2.55.0
next prev parent reply other threads:[~2026-07-21 17:07 UTC|newest]
Thread overview: 15+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-07-21 17:05 [PULL 00/13] vfio queue Cédric Le Goater
2026-07-21 17:05 ` Cédric Le Goater [this message]
2026-07-21 17:05 ` [PULL 02/13] vfio/region: Clarify dma-buf failure messages Cédric Le Goater
2026-07-21 17:05 ` [PULL 03/13] vfio/pci: don't narrow a failed config read to a plausible value Cédric Le Goater
2026-07-21 17:05 ` [PULL 04/13] vfio/pci: reject invalid PCI_INTERRUPT_PIN values Cédric Le Goater
2026-07-21 17:05 ` [PULL 05/13] vfio-user: vfio_user_get_region_info: prevent buffer overflow Cédric Le Goater
2026-07-21 17:05 ` [PULL 06/13] vfio-user: vfio_user_get_region_info: respect max_xfer_size Cédric Le Goater
2026-07-21 17:05 ` [PULL 07/13] vfio-user: vfio_user_get_region_info: reject unreasonably short struct Cédric Le Goater
2026-07-21 17:05 ` [PULL 08/13] vfio-user: vfio_user_get_region_info: prevent excessive malloc Cédric Le Goater
2026-07-21 17:05 ` [PULL 09/13] vfio-user: vfio_user_device_io_get_region_info: fix capability check Cédric Le Goater
2026-07-21 17:05 ` [PULL 10/13] vfio-user: vfio_user_device_io_device_feature: prevent buffer overflow Cédric Le Goater
2026-07-21 17:05 ` [PULL 11/13] vfio-user: vfio_user_device_io_device_feature: prevent excessive malloc Cédric Le Goater
2026-07-21 17:05 ` [PULL 12/13] vfio-user: vfio_user_device_io_set_irqs: prevent buffer overflow Cédric Le Goater
2026-07-21 17:05 ` [PULL 13/13] vfio-user: vfio_user_device_io_set_irqs: prevent excessive malloc Cédric Le Goater
2026-07-22 17:58 ` [PULL 00/13] vfio queue Stefan Hajnoczi
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20260721170518.4160785-2-clg@redhat.com \
--to=clg@redhat.com \
--cc=alex@shazbot.org \
--cc=kirandeepmaan45@gmail.com \
--cc=qemu-devel@nongnu.org \
--cc=qemu-stable@nongnu.org \
--cc=tomitamoeko@gmail.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.