pci_patch_ids() only adjusts checksum based on the new IDs. For an
option ROM with invalid checksum, the patched one will still have
an invalid checksum. Always calculate the checksum and patch it if
necessary to ensure the option ROM is valid.

This is intended for fixing the romfile used in IGD passthrough as
multiple IGD devices share the same rom with possible non-matching
device ID, and its checksum is known to be bogus [1].

A helper function pci_rom_calculate_checksum() is added and exported
for reusing in IGD-specific quirk later.

[1] hw/vfio/pci.c:1090

Reported-by: K S Maan <[email protected]>
Signed-off-by: Tomita Moeko <[email protected]>
Tested-by: K S Maan <[email protected]>
---
 hw/pci/pci.c         | 22 +++++++++++++++++++++-
 include/hw/pci/pci.h |  2 ++
 2 files changed, 23 insertions(+), 1 deletion(-)

diff --git a/hw/pci/pci.c b/hw/pci/pci.c
index 4298adf5a0..043fef1954 100644
--- a/hw/pci/pci.c
+++ b/hw/pci/pci.c
@@ -2472,6 +2472,21 @@ static uint8_t pci_find_capability_at_offset(PCIDevice 
*pdev, uint8_t offset)
     return found;
 }
 
+uint8_t pci_rom_calculate_checksum(uint8_t *ptr, uint32_t size)
+{
+    uint8_t checksum = 0;
+    uint8_t orig_checksum = ptr[6];
+    uint32_t i;
+
+    ptr[6] = 0;
+    for (i = 0; i < size; i++) {
+        checksum += ptr[i];
+    }
+    ptr[6] = orig_checksum;
+
+    return -checksum;
+}
+
 /* Patch the PCI vendor and device ids in a PCI rom image if necessary.
    This is needed for an option rom which is used for more than one device. */
 static void pci_patch_ids(PCIDevice *pdev, uint8_t *ptr, uint32_t size)
@@ -2507,7 +2522,12 @@ static void pci_patch_ids(PCIDevice *pdev, uint8_t *ptr, 
uint32_t size)
     trace_pci_rom_and_pci_ids(pdev->romfile, vendor_id, device_id,
                               rom_vendor_id, rom_device_id);
 
-    checksum = ptr[6];
+    /* In case the checksum is bogus */
+    checksum = pci_rom_calculate_checksum(ptr, size);
+    if (ptr[6] != checksum) {
+        trace_pci_rom_checksum_change(ptr[6], checksum);
+        ptr[6] = checksum;
+    }
 
     if (vendor_id != rom_vendor_id) {
         /* Patch vendor id and checksum (at offset 6 for etherboot roms). */
diff --git a/include/hw/pci/pci.h b/include/hw/pci/pci.h
index 5b179091de..551ab16139 100644
--- a/include/hw/pci/pci.h
+++ b/include/hw/pci/pci.h
@@ -1103,4 +1103,6 @@ void pci_set_enabled(PCIDevice *pci_dev, bool state);
 void pci_set_power(PCIDevice *pci_dev, bool state);
 int pci_pm_init(PCIDevice *pci_dev, uint8_t offset, Error **errp);
 
+uint8_t pci_rom_calculate_checksum(uint8_t *ptr, uint32_t size);
+
 #endif
-- 
2.53.0


Reply via email to