On Thu, 4 Jun 2026 01:33:54 +0800
Tomita Moeko <[email protected]> wrote:
> diff --git a/hw/vfio/igd.c b/hw/vfio/igd.c
> index 17437ae18d..e00f6f8315 100644
> --- a/hw/vfio/igd.c
> +++ b/hw/vfio/igd.c
> @@ -739,3 +739,109 @@ 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.
> + */
Neat, cool quirk.
> +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 };
> + 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;
> + }
> +
> + *bdsm_offset = rom[i + 4] | ((uint16_t)rom[i + 5] << 8);
This needs a bounds check to make sure it's still within the ROM,
either here or before the memset().
> + found = true;
> + }
> +
> + if (!found) {
> + return -ENOENT;
> + }
> +
> + return 0;
> +}
> +
> +void vfio_probe_igd_legacy_rom_quirk(VFIOPCIDevice *vdev)
> +{
> + int ret, gen;
> + uint16_t pcir_offset, bdsm_offset = 0;
> + uint8_t checksum;
> +
> + 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(vdev->rom) != 0xaa55) {
> + return;
> + }
> +
> + /* Must be a legacy ROM */
> + pcir_offset = pci_get_word(vdev->rom + 0x18);
> + if (pcir_offset >= vdev->rom_size ||
This gates the below test to pcir_offset + 0x14, so should be:
if (pcir_offset + 0x14 >= vdev->rom_size ||
> + memcmp(vdev->rom + pcir_offset, "PCIR", 4) ||
> + pci_get_byte(vdev->rom + pcir_offset + 0x14) != 0x00) {
> + return;
> + }
> +
> + ret = igd_vbios_find_saved_bdsm(vdev->rom, vdev->rom_size, &bdsm_offset);
> + if (ret < 0) {
> + return;
> + }
> +
> + memset(vdev->rom + bdsm_offset, 0, sizeof(uint32_t));
> +
> + checksum = pci_rom_calculate_checksum(vdev->rom, vdev->rom_size);
> + ((uint8_t *)vdev->rom)[6] = checksum;
If updated per previous recommendation, this becomes:
((uint8_t *)vdev->rom)[6] -= checksum;
Thanks,
Alex