diff mbox

[1/2] pci: Automatically patch PCI vendor id and device id in PCI ROM

Message ID 1287522501-8091-1-git-send-email-weil@mail.berlios.de
State New
Headers show

Commit Message

Stefan Weil Oct. 19, 2010, 9:08 p.m. UTC
PCI devices with different vendor or device ids sometimes share
the same rom code. Only the ids and the checksum

Comments

Gerd Hoffmann Oct. 20, 2010, 7:19 a.m. UTC | #1
Hi,

> The i825xx ethernet controller family is a typical example
> which is implemented in hw/eepro100.c. It uses at least
> 3 different device ids, so normally 3 boot roms would be needed.

Does this actually work now with the etherboot roms?

cheers,
   Gerd
Stefan Weil Oct. 20, 2010, 8:30 p.m. UTC | #2
Am 20.10.2010 09:19, schrieb Gerd Hoffmann:
>   Hi,
>
>> The i825xx ethernet controller family is a typical example
>> which is implemented in hw/eepro100.c. It uses at least
>> 3 different device ids, so normally 3 boot roms would be needed.
>
> Does this actually work now with the etherboot roms?
>
> cheers,
>   Gerd


Yes. I tested these two cases:

qemu -L pc-bios -boot n -netdev user,id=internet \
     -device i82801,netdev=internet,romfile=gpxe-eepro100-80861209.rom

=> no bootable device

qemu -L pc-bios -boot n -netdev user,id=internet \
     -device i82801,netdev=internet

=> device gets ip address via dhcp

The same works with i82559c or other eepro100 variants, too.

Cheers,
Stefan
Michael S. Tsirkin Nov. 22, 2010, 6:29 a.m. UTC | #3
On Tue, Oct 19, 2010 at 11:08:21PM +0200, Stefan Weil wrote:
> PCI devices with different vendor or device ids sometimes share
> the same rom code. Only the ids and the checksum
> differs in a boot rom for such devices.
> 
> The i825xx ethernet controller family is a typical example
> which is implemented in hw/eepro100.c. It uses at least
> 3 different device ids, so normally 3 boot roms would be needed.
> 
> By automatically patching vendor id and device id (and the checksum)
> in qemu, all emulated family members can share the same boot rom.
> 
> VGA bios roms are another example with different vendor and device ids.
> 
> Only qemu's built-in default rom files will be patched.
> 
> v2:
>     * Patch also the vendor id (and remove the sanity check for vendor id).
> 
> v3:
>     * Don't patch a rom file when its name was set by the user.
>       Thus we avoid modifications of unknown rom data.
> 
> Cc: Gerd Hoffmann <kraxel@redhat.com>
> Cc: Markus Armbruster <armbru@redhat.com>
> Cc: Michael S. Tsirkin <mst@redhat.com>
> Signed-off-by: Stefan Weil <weil@mail.berlios.de>

Looks safe enough for me.
Applied.

> ---
>  hw/pci.c |   73 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---
>  1 files changed, 69 insertions(+), 4 deletions(-)
> 
> diff --git a/hw/pci.c b/hw/pci.c
> index 1280d4d..74cbea5 100644
> --- a/hw/pci.c
> +++ b/hw/pci.c
> @@ -78,7 +78,7 @@ static struct BusInfo pci_bus_info = {
>  
>  static void pci_update_mappings(PCIDevice *d);
>  static void pci_set_irq(void *opaque, int irq_num, int level);
> -static int pci_add_option_rom(PCIDevice *pdev);
> +static int pci_add_option_rom(PCIDevice *pdev, bool is_default_rom);
>  static void pci_del_option_rom(PCIDevice *pdev);
>  
>  static uint16_t pci_default_sub_vendor_id = PCI_SUBVENDOR_ID_REDHAT_QUMRANET;
> @@ -1672,6 +1672,7 @@ static int pci_qdev_init(DeviceState *qdev, DeviceInfo *base)
>      PCIDeviceInfo *info = container_of(base, PCIDeviceInfo, qdev);
>      PCIBus *bus;
>      int devfn, rc;
> +    bool is_default_rom;
>  
>      /* initialize cap_present for pci_is_express() and pci_config_size() */
>      if (info->is_express) {
> @@ -1692,9 +1693,12 @@ static int pci_qdev_init(DeviceState *qdev, DeviceInfo *base)
>      }
>  
>      /* rom loading */
> -    if (pci_dev->romfile == NULL && info->romfile != NULL)
> +    is_default_rom = false;
> +    if (pci_dev->romfile == NULL && info->romfile != NULL) {
>          pci_dev->romfile = qemu_strdup(info->romfile);
> -    pci_add_option_rom(pci_dev);
> +        is_default_rom = true;
> +    }
> +    pci_add_option_rom(pci_dev, is_default_rom);
>  
>      if (qdev->hotplugged) {
>          rc = bus->hotplug(bus->hotplug_qdev, pci_dev, 1);
> @@ -1797,8 +1801,64 @@ static void pci_map_option_rom(PCIDevice *pdev, int region_num, pcibus_t addr, p
>      cpu_register_physical_memory(addr, size, pdev->rom_offset);
>  }
>  
> +/* 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, int size)
> +{
> +    uint16_t vendor_id;
> +    uint16_t device_id;
> +    uint16_t rom_vendor_id;
> +    uint16_t rom_device_id;
> +    uint16_t rom_magic;
> +    uint16_t pcir_offset;
> +    uint8_t checksum;
> +
> +    /* Words in rom data are little endian (like in PCI configuration),
> +       so they can be read / written with pci_get_word / pci_set_word. */
> +
> +    /* Only a valid rom will be patched. */
> +    rom_magic = pci_get_word(ptr);
> +    if (rom_magic != 0xaa55) {
> +        PCI_DPRINTF("Bad ROM magic %04x\n", rom_magic);
> +        return;
> +    }
> +    pcir_offset = pci_get_word(ptr + 0x18);
> +    if (pcir_offset + 8 >= size || memcmp(ptr + pcir_offset, "PCIR", 4)) {
> +        PCI_DPRINTF("Bad PCIR offset 0x%x or signature\n", pcir_offset);
> +        return;
> +    }
> +
> +    vendor_id = pci_get_word(pdev->config + PCI_VENDOR_ID);
> +    device_id = pci_get_word(pdev->config + PCI_DEVICE_ID);
> +    rom_vendor_id = pci_get_word(ptr + pcir_offset + 4);
> +    rom_device_id = pci_get_word(ptr + pcir_offset + 6);
> +
> +    PCI_DPRINTF("%s: ROM id %04x%04x / PCI id %04x%04x\n", pdev->romfile,
> +                vendor_id, device_id, rom_vendor_id, rom_device_id);
> +
> +    checksum = ptr[6];
> +
> +    if (vendor_id != rom_vendor_id) {
> +        /* Patch vendor id and checksum (at offset 6 for etherboot roms). */
> +        checksum += (uint8_t)rom_vendor_id + (uint8_t)(rom_vendor_id >> 8);
> +        checksum -= (uint8_t)vendor_id + (uint8_t)(vendor_id >> 8);
> +        PCI_DPRINTF("ROM checksum %02x / %02x\n", ptr[6], checksum);
> +        ptr[6] = checksum;
> +        pci_set_word(ptr + pcir_offset + 4, vendor_id);
> +    }
> +
> +    if (device_id != rom_device_id) {
> +        /* Patch device id and checksum (at offset 6 for etherboot roms). */
> +        checksum += (uint8_t)rom_device_id + (uint8_t)(rom_device_id >> 8);
> +        checksum -= (uint8_t)device_id + (uint8_t)(device_id >> 8);
> +        PCI_DPRINTF("ROM checksum %02x / %02x\n", ptr[6], checksum);
> +        ptr[6] = checksum;
> +        pci_set_word(ptr + pcir_offset + 6, device_id);
> +    }
> +}
> +
>  /* Add an option rom for the device */
> -static int pci_add_option_rom(PCIDevice *pdev)
> +static int pci_add_option_rom(PCIDevice *pdev, bool is_default_rom)
>  {
>      int size;
>      char *path;
> @@ -1849,6 +1909,11 @@ static int pci_add_option_rom(PCIDevice *pdev)
>      load_image(path, ptr);
>      qemu_free(path);
>  
> +    if (is_default_rom) {
> +        /* Only the default rom images will be patched (if needed). */
> +        pci_patch_ids(pdev, ptr, size);
> +    }
> +
>      pci_register_bar(pdev, PCI_ROM_SLOT, size,
>                       0, pci_map_option_rom);
>  
> -- 
> 1.7.1
diff mbox

Patch

differs in a boot rom for such devices.

The i825xx ethernet controller family is a typical example
which is implemented in hw/eepro100.c. It uses at least
3 different device ids, so normally 3 boot roms would be needed.

By automatically patching vendor id and device id (and the checksum)
in qemu, all emulated family members can share the same boot rom.

VGA bios roms are another example with different vendor and device ids.

Only qemu's built-in default rom files will be patched.

v2:
    * Patch also the vendor id (and remove the sanity check for vendor id).

v3:
    * Don't patch a rom file when its name was set by the user.
      Thus we avoid modifications of unknown rom data.

Cc: Gerd Hoffmann <kraxel@redhat.com>
Cc: Markus Armbruster <armbru@redhat.com>
Cc: Michael S. Tsirkin <mst@redhat.com>
Signed-off-by: Stefan Weil <weil@mail.berlios.de>
---
 hw/pci.c |   73 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---
 1 files changed, 69 insertions(+), 4 deletions(-)

diff --git a/hw/pci.c b/hw/pci.c
index 1280d4d..74cbea5 100644
--- a/hw/pci.c
+++ b/hw/pci.c
@@ -78,7 +78,7 @@  static struct BusInfo pci_bus_info = {
 
 static void pci_update_mappings(PCIDevice *d);
 static void pci_set_irq(void *opaque, int irq_num, int level);
-static int pci_add_option_rom(PCIDevice *pdev);
+static int pci_add_option_rom(PCIDevice *pdev, bool is_default_rom);
 static void pci_del_option_rom(PCIDevice *pdev);
 
 static uint16_t pci_default_sub_vendor_id = PCI_SUBVENDOR_ID_REDHAT_QUMRANET;
@@ -1672,6 +1672,7 @@  static int pci_qdev_init(DeviceState *qdev, DeviceInfo *base)
     PCIDeviceInfo *info = container_of(base, PCIDeviceInfo, qdev);
     PCIBus *bus;
     int devfn, rc;
+    bool is_default_rom;
 
     /* initialize cap_present for pci_is_express() and pci_config_size() */
     if (info->is_express) {
@@ -1692,9 +1693,12 @@  static int pci_qdev_init(DeviceState *qdev, DeviceInfo *base)
     }
 
     /* rom loading */
-    if (pci_dev->romfile == NULL && info->romfile != NULL)
+    is_default_rom = false;
+    if (pci_dev->romfile == NULL && info->romfile != NULL) {
         pci_dev->romfile = qemu_strdup(info->romfile);
-    pci_add_option_rom(pci_dev);
+        is_default_rom = true;
+    }
+    pci_add_option_rom(pci_dev, is_default_rom);
 
     if (qdev->hotplugged) {
         rc = bus->hotplug(bus->hotplug_qdev, pci_dev, 1);
@@ -1797,8 +1801,64 @@  static void pci_map_option_rom(PCIDevice *pdev, int region_num, pcibus_t addr, p
     cpu_register_physical_memory(addr, size, pdev->rom_offset);
 }
 
+/* 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, int size)
+{
+    uint16_t vendor_id;
+    uint16_t device_id;
+    uint16_t rom_vendor_id;
+    uint16_t rom_device_id;
+    uint16_t rom_magic;
+    uint16_t pcir_offset;
+    uint8_t checksum;
+
+    /* Words in rom data are little endian (like in PCI configuration),
+       so they can be read / written with pci_get_word / pci_set_word. */
+
+    /* Only a valid rom will be patched. */
+    rom_magic = pci_get_word(ptr);
+    if (rom_magic != 0xaa55) {
+        PCI_DPRINTF("Bad ROM magic %04x\n", rom_magic);
+        return;
+    }
+    pcir_offset = pci_get_word(ptr + 0x18);
+    if (pcir_offset + 8 >= size || memcmp(ptr + pcir_offset, "PCIR", 4)) {
+        PCI_DPRINTF("Bad PCIR offset 0x%x or signature\n", pcir_offset);
+        return;
+    }
+
+    vendor_id = pci_get_word(pdev->config + PCI_VENDOR_ID);
+    device_id = pci_get_word(pdev->config + PCI_DEVICE_ID);
+    rom_vendor_id = pci_get_word(ptr + pcir_offset + 4);
+    rom_device_id = pci_get_word(ptr + pcir_offset + 6);
+
+    PCI_DPRINTF("%s: ROM id %04x%04x / PCI id %04x%04x\n", pdev->romfile,
+                vendor_id, device_id, rom_vendor_id, rom_device_id);
+
+    checksum = ptr[6];
+
+    if (vendor_id != rom_vendor_id) {
+        /* Patch vendor id and checksum (at offset 6 for etherboot roms). */
+        checksum += (uint8_t)rom_vendor_id + (uint8_t)(rom_vendor_id >> 8);
+        checksum -= (uint8_t)vendor_id + (uint8_t)(vendor_id >> 8);
+        PCI_DPRINTF("ROM checksum %02x / %02x\n", ptr[6], checksum);
+        ptr[6] = checksum;
+        pci_set_word(ptr + pcir_offset + 4, vendor_id);
+    }
+
+    if (device_id != rom_device_id) {
+        /* Patch device id and checksum (at offset 6 for etherboot roms). */
+        checksum += (uint8_t)rom_device_id + (uint8_t)(rom_device_id >> 8);
+        checksum -= (uint8_t)device_id + (uint8_t)(device_id >> 8);
+        PCI_DPRINTF("ROM checksum %02x / %02x\n", ptr[6], checksum);
+        ptr[6] = checksum;
+        pci_set_word(ptr + pcir_offset + 6, device_id);
+    }
+}
+
 /* Add an option rom for the device */
-static int pci_add_option_rom(PCIDevice *pdev)
+static int pci_add_option_rom(PCIDevice *pdev, bool is_default_rom)
 {
     int size;
     char *path;
@@ -1849,6 +1909,11 @@  static int pci_add_option_rom(PCIDevice *pdev)
     load_image(path, ptr);
     qemu_free(path);
 
+    if (is_default_rom) {
+        /* Only the default rom images will be patched (if needed). */
+        pci_patch_ids(pdev, ptr, size);
+    }
+
     pci_register_bar(pdev, PCI_ROM_SLOT, size,
                      0, pci_map_option_rom);