[PATCH][v2] kvm-userspace: Load PCI option ROMs

Liu, Kechao
2008-12-29T03:51:19Z

[-- Attachment #1: Type: text/plain, Size: 8653 bytes --]

Hi Avi,

Thanks for your comments. I've updated the patch according to them.
Please review it. Thank you.

Load assigned devices' PCI option ROMs to the RAM of
guest OS. And pass the corresponding devfns to BIOS.

Signed-off-by: Kechao Liu <kechao.liu@intel.com>
---
bios/rombios.c | 20 +++++-
qemu/hw/device-assignment.c | 155 +++++++++++++++++++++++++++++++++++++++++++
qemu/hw/device-assignment.h | 1 +
qemu/hw/pc.c | 8 ++-
4 files changed, 178 insertions(+), 6 deletions(-)

diff --git a/bios/rombios.c b/bios/rombios.c
index b7a240f..e98d4ff 100644
--- a/bios/rombios.c
+++ b/bios/rombios.c
@@ -10253,18 +10253,30 @@ rom_scan_loop:
add al, #0x04
block_count_rounded:

- xor bx, bx ;; Restore DS back to 0000:
- mov ds, bx
push ax ;; Save AX
push di ;; Save DI
;; Push addr of ROM entry point
push cx ;; Push seg
push #0x0003 ;; Push offset

+ ;; Get the BDF into ax before invoking the option ROM
+ mov bl, [2]
+ mov al, bl
+ shr al, #7
+ cmp al, #1
+ jne fetch_bdf
+ mov ax, ds ;; Increment the DS since rom size larger than an segment
+ add ax, #0x1000
+ mov ds, ax
+fetch_bdf:
+ shl bx, #9
+ xor ax, ax
+ mov al, [bx]
+
;; Point ES:DI at "$PnP", which tells the ROM that we are a PnP BIOS.
;; That should stop it grabbing INT 19h; we will use its BEV instead.
- mov ax, #0xf000
- mov es, ax
+ mov bx, #0xf000
+ mov es, bx
lea di, pnp_string

mov bp, sp ;; Call ROM init routine using seg:off on stack
diff --git a/qemu/hw/device-assignment.c b/qemu/hw/device-assignment.c
index 7a66665..7f16970 100644
--- a/qemu/hw/device-assignment.c
+++ b/qemu/hw/device-assignment.c
@@ -678,3 +678,158 @@ void add_assigned_devices(PCIBus *bus, const char **devices, int n_devices)
}
}
}
+
+/* Option ROM header */
+struct option_rom_header {
+ uint8_t signature[2];
+ uint8_t rom_size;
+ uint32_t entry_point;
+ uint8_t reserved[17];
+ uint16_t pci_header_offset;
+ uint16_t expansion_header_offset;
+} __attribute__ ((packed));
+
+/* Option ROM PCI data structure */
+struct option_rom_pci_header {
+ uint8_t signature[4];
+ uint16_t vendor_id;
+ uint16_t device_id;
+ uint16_t vital_product_data_offset;
+ uint16_t structure_length;
+ uint8_t structure_revision;
+ uint8_t class_code[3];
+ uint16_t image_length;
+ uint16_t image_revision;
+ uint8_t code_type;
+ uint8_t indicator;
+ uint16_t reserved;
+} __attribute__ ((packed));
+
+/*
+ * Scan the list of Option ROMs at roms. If a suitable Option ROM is found,
+ * allocate a ram space and copy it there. Then return its size aligned to
+ * both 2KB and target page size.
+ */
+#define OPTION_ROM_ALIGN(x) (((x) + 2047) & ~2047)
+static int scan_option_rom(uint8_t devfn, void *roms, ram_addr_t offset)
+{
+ int i, size, total_size;
+ uint8_t csum;
+ ram_addr_t addr;
+ struct option_rom_header *rom;
+ struct option_rom_pci_header *pcih;
+
+ rom = roms;
+
+ for ( ; ; ) {
+ /* Invalid signature means we're out of option ROMs. */
+ if (strncmp((char *)rom->signature, "\x55\xaa", 2) ||
+ (rom->rom_size == 0))
+ break;
+
+ size = rom->rom_size * 512;
+ /* Invalid checksum means we're out of option ROMs. */
+ csum = 0;
+ for (i = 0; i < size; i++)
+ csum += ((uint8_t *)rom)[i];
+ if (csum != 0)
+ break;
+
+ /* Check the PCI header (if any) for a match. */
+ pcih = (struct option_rom_pci_header *)
+ ((char *)rom + rom->pci_header_offset);
+ if ((rom->pci_header_offset != 0) &&
+ !strncmp((char *)pcih->signature, "PCIR", 4))
+ goto found;
+
+ rom = (struct option_rom_header *)((char *)rom + size);
+ }
+
+ return 0;
+
+ found:
+ /* The size should be both 2K-aligned and page-aligned */
+ total_size = (TARGET_PAGE_SIZE < 2048)
+ ? OPTION_ROM_ALIGN(size + 1)
+ : TARGET_PAGE_ALIGN(size + 1);
+
+ /* Size of all available ram space is 0x10000 (0xd0000 to 0xe0000) */
+ if ((offset + total_size) > 0x10000u) {
+ fprintf(stderr, "Option ROM size %x exceeds available space\n", size);
+ return 0;
+ }
+
+ addr = qemu_ram_alloc(total_size);
+ cpu_register_physical_memory(0xd0000 + offset, total_size, addr | IO_MEM_ROM);
+
+ /* Write ROM data and devfn to phys_addr */
+ cpu_physical_memory_write_rom(0xd0000 + offset, rom, size);
+ cpu_physical_memory_write_rom(0xd0000 + offset + size, &devfn, 1);
+
+ return total_size;
+}
+
+/*
+ * Scan the assigned devices for the devices that have an option ROM, and then
+ * load the corresponding ROM data to RAM. If an error occurs while loading an
+ * option ROM, we just ignore that option ROM and continue with the next one.
+ */
+ram_addr_t assigned_dev_load_option_roms(ram_addr_t rom_base_offset)
+{
+ ram_addr_t offset = rom_base_offset;
+ AssignedDevInfo *adev;
+
+ LIST_FOREACH(adev, &adev_head, next) {
+ int size, len;
+ void *buf;
+ FILE *fp;
+ uint8_t i = 1;
+ char rom_file[64];
+
+ snprintf(rom_file, sizeof(rom_file),
+ "/sys/bus/pci/devices/0000:%02x:%02x.%01x/rom",
+ adev->bus, adev->dev, adev->func);
+
+ if (access(rom_file, F_OK))
+ continue;
+
+ /* Write something to the ROM file to enable it */
+ fp = fopen(rom_file, "wb");
+ if (fp == NULL)
+ continue;
+ len = fwrite(&i, 1, 1, fp);
+ fclose(fp);
+ if (len != 1)
+ continue;
+
+ /* The file has to be closed and reopened, otherwise it won't work */
+ fp = fopen(rom_file, "rb");
+ if (fp == NULL)
+ continue;
+
+ fseek(fp, 0, SEEK_END);
+ size = ftell(fp);
+ fseek(fp, 0, SEEK_SET);
+
+ buf = malloc(size);
+ if (buf == NULL) {
+ fclose(fp);
+ continue;
+ }
+
+ fread(buf, size, 1, fp);
+ if (!feof(fp) || ferror(fp)) {
+ free(buf);
+ fclose(fp);
+ continue;
+ }
+
+ /* Scan the buffer for suitable ROMs and increase the offset */
+ offset += scan_option_rom(adev->assigned_dev->dev.devfn, buf, offset);
+
+ free(buf);
+ fclose(fp);
+ }
+
+ return offset;
+}
diff --git a/qemu/hw/device-assignment.h b/qemu/hw/device-assignment.h
index c8c47d3..a565948 100644
--- a/qemu/hw/device-assignment.h
+++ b/qemu/hw/device-assignment.h
@@ -98,6 +98,7 @@ void free_assigned_device(AssignedDevInfo *adev);
PCIDevice *init_assigned_device(AssignedDevInfo *adev, PCIBus *bus);
AssignedDevInfo *add_assigned_device(const char *arg);
void add_assigned_devices(PCIBus *bus, const char **devices, int n_devices);
+ram_addr_t assigned_dev_load_option_roms(ram_addr_t rom_base_offset);

#define MAX_DEV_ASSIGN_CMDLINE 8

diff --git a/qemu/hw/pc.c b/qemu/hw/pc.c
index 2d7e7f0..c470646 100644
--- a/qemu/hw/pc.c
+++ b/qemu/hw/pc.c
@@ -810,6 +810,7 @@ static void pc_init1(ram_addr_t ram_size, int vga_ram_size,
ram_addr_t ram_addr, vga_ram_addr, bios_offset, vga_bios_offset;
ram_addr_t below_4g_mem_size, above_4g_mem_size = 0;
int bios_size, isa_bios_size, vga_bios_size, opt_rom_offset;
+ int pci_option_rom_offset;
PCIBus *pci_bus;
int piix3_devfn = -1;
CPUState *env;
@@ -972,6 +973,7 @@ static void pc_init1(ram_addr_t ram_size, int vga_ram_size,
option_rom_setup_reset(0xd0000 + offset, size);
offset += size;
}
+ pci_option_rom_offset = offset;
}

/* map all the bios at the top of memory */
@@ -1190,8 +1192,10 @@ static void pc_init1(ram_addr_t ram_size, int vga_ram_size,
virtio_balloon_init(pci_bus);

#ifdef USE_KVM_DEVICE_ASSIGNMENT
- if (kvm_enabled())
- add_assigned_devices(pci_bus, assigned_devices, assigned_devices_index);
+ if (kvm_enabled()) {
+ add_assigned_devices(pci_bus, assigned_devices, assigned_devices_index);
+ assigned_dev_load_option_roms(pci_option_rom_offset);
+ }
#endif /* USE_KVM_DEVICE_ASSIGNMENT */
}

--
1.6.0

Best Regards,
Liu, Kechao

[-- Attachment #2: 0001-kvm-userspace-Load-PCI-option-ROMs.patch --]
[-- Type: application/octet-stream, Size: 8455 bytes --]

From 3c54b381e4bd8874265ec9337cb4c507d4c8fc7a Mon Sep 17 00:00:00 2001
From: Kechao Liu <kechao.liu@intel.com>
Date: Mon, 29 Dec 2008 11:23:22 +0800
Subject: [PATCH] kvm-userspace: Load PCI option ROMs

Load assigned devices' PCI option ROMs to the RAM of
guest OS. And pass the corresponding devfns to BIOS.

Signed-off-by: Kechao Liu <kechao.liu@intel.com>
---
bios/rombios.c | 20 +++++-
qemu/hw/device-assignment.c | 155 +++++++++++++++++++++++++++++++++++++++++++
qemu/hw/device-assignment.h | 1 +
qemu/hw/pc.c | 8 ++-
4 files changed, 178 insertions(+), 6 deletions(-)

diff --git a/bios/rombios.c b/bios/rombios.c
index b7a240f..e98d4ff 100644
--- a/bios/rombios.c
+++ b/bios/rombios.c
@@ -10253,18 +10253,30 @@ rom_scan_loop:
add al, #0x04
block_count_rounded:

- xor bx, bx ;; Restore DS back to 0000:
- mov ds, bx
push ax ;; Save AX
push di ;; Save DI
;; Push addr of ROM entry point
push cx ;; Push seg
push #0x0003 ;; Push offset

+ ;; Get the BDF into ax before invoking the option ROM
+ mov bl, [2]
+ mov al, bl
+ shr al, #7
+ cmp al, #1
+ jne fetch_bdf
+ mov ax, ds ;; Increment the DS since rom size larger than an segment
+ add ax, #0x1000
+ mov ds, ax
+fetch_bdf:
+ shl bx, #9
+ xor ax, ax
+ mov al, [bx]
+
;; Point ES:DI at "$PnP", which tells the ROM that we are a PnP BIOS.
;; That should stop it grabbing INT 19h; we will use its BEV instead.
- mov ax, #0xf000
- mov es, ax
+ mov bx, #0xf000
+ mov es, bx
lea di, pnp_string

mov bp, sp ;; Call ROM init routine using seg:off on stack
diff --git a/qemu/hw/device-assignment.c b/qemu/hw/device-assignment.c
index 7a66665..7f16970 100644
--- a/qemu/hw/device-assignment.c
+++ b/qemu/hw/device-assignment.c
@@ -678,3 +678,158 @@ void add_assigned_devices(PCIBus *bus, const char **devices, int n_devices)
}
}
}
+
+/* Option ROM header */
+struct option_rom_header {
+ uint8_t signature[2];
+ uint8_t rom_size;
+ uint32_t entry_point;
+ uint8_t reserved[17];
+ uint16_t pci_header_offset;
+ uint16_t expansion_header_offset;
+} __attribute__ ((packed));
+
+/* Option ROM PCI data structure */
+struct option_rom_pci_header {
+ uint8_t signature[4];
+ uint16_t vendor_id;
+ uint16_t device_id;
+ uint16_t vital_product_data_offset;
+ uint16_t structure_length;
+ uint8_t structure_revision;
+ uint8_t class_code[3];
+ uint16_t image_length;
+ uint16_t image_revision;
+ uint8_t code_type;
+ uint8_t indicator;
+ uint16_t reserved;
+} __attribute__ ((packed));
+
+/*
+ * Scan the list of Option ROMs at roms. If a suitable Option ROM is found,
+ * allocate a ram space and copy it there. Then return its size aligned to
+ * both 2KB and target page size.
+ */
+#define OPTION_ROM_ALIGN(x) (((x) + 2047) & ~2047)
+static int scan_option_rom(uint8_t devfn, void *roms, ram_addr_t offset)
+{
+ int i, size, total_size;
+ uint8_t csum;
+ ram_addr_t addr;
+ struct option_rom_header *rom;
+ struct option_rom_pci_header *pcih;
+
+ rom = roms;
+
+ for ( ; ; ) {
+ /* Invalid signature means we're out of option ROMs. */
+ if (strncmp((char *)rom->signature, "\x55\xaa", 2) ||
+ (rom->rom_size == 0))
+ break;
+
+ size = rom->rom_size * 512;
+ /* Invalid checksum means we're out of option ROMs. */
+ csum = 0;
+ for (i = 0; i < size; i++)
+ csum += ((uint8_t *)rom)[i];
+ if (csum != 0)
+ break;
+
+ /* Check the PCI header (if any) for a match. */
+ pcih = (struct option_rom_pci_header *)
+ ((char *)rom + rom->pci_header_offset);
+ if ((rom->pci_header_offset != 0) &&
+ !strncmp((char *)pcih->signature, "PCIR", 4))
+ goto found;
+
+ rom = (struct option_rom_header *)((char *)rom + size);
+ }
+
+ return 0;
+
+ found:
+ /* The size should be both 2K-aligned and page-aligned */
+ total_size = (TARGET_PAGE_SIZE < 2048)
+ ? OPTION_ROM_ALIGN(size + 1)
+ : TARGET_PAGE_ALIGN(size + 1);
+
+ /* Size of all available ram space is 0x10000 (0xd0000 to 0xe0000) */
+ if ((offset + total_size) > 0x10000u) {
+ fprintf(stderr, "Option ROM size %x exceeds available space\n", size);
+ return 0;
+ }
+
+ addr = qemu_ram_alloc(total_size);
+ cpu_register_physical_memory(0xd0000 + offset, total_size, addr | IO_MEM_ROM);
+
+ /* Write ROM data and devfn to phys_addr */
+ cpu_physical_memory_write_rom(0xd0000 + offset, rom, size);
+ cpu_physical_memory_write_rom(0xd0000 + offset + size, &devfn, 1);
+
+ return total_size;
+}
+
+/*
+ * Scan the assigned devices for the devices that have an option ROM, and then
+ * load the corresponding ROM data to RAM. If an error occurs while loading an
+ * option ROM, we just ignore that option ROM and continue with the next one.
+ */
+ram_addr_t assigned_dev_load_option_roms(ram_addr_t rom_base_offset)
+{
+ ram_addr_t offset = rom_base_offset;
+ AssignedDevInfo *adev;
+
+ LIST_FOREACH(adev, &adev_head, next) {
+ int size, len;
+ void *buf;
+ FILE *fp;
+ uint8_t i = 1;
+ char rom_file[64];
+
+ snprintf(rom_file, sizeof(rom_file),
+ "/sys/bus/pci/devices/0000:%02x:%02x.%01x/rom",
+ adev->bus, adev->dev, adev->func);
+
+ if (access(rom_file, F_OK))
+ continue;
+
+ /* Write something to the ROM file to enable it */
+ fp = fopen(rom_file, "wb");
+ if (fp == NULL)
+ continue;
+ len = fwrite(&i, 1, 1, fp);
+ fclose(fp);
+ if (len != 1)
+ continue;
+
+ /* The file has to be closed and reopened, otherwise it won't work */
+ fp = fopen(rom_file, "rb");
+ if (fp == NULL)
+ continue;
+
+ fseek(fp, 0, SEEK_END);
+ size = ftell(fp);
+ fseek(fp, 0, SEEK_SET);
+
+ buf = malloc(size);
+ if (buf == NULL) {
+ fclose(fp);
+ continue;
+ }
+
+ fread(buf, size, 1, fp);
+ if (!feof(fp) || ferror(fp)) {
+ free(buf);
+ fclose(fp);
+ continue;
+ }
+
+ /* Scan the buffer for suitable ROMs and increase the offset */
+ offset += scan_option_rom(adev->assigned_dev->dev.devfn, buf, offset);
+
+ free(buf);
+ fclose(fp);
+ }
+
+ return offset;
+}
diff --git a/qemu/hw/device-assignment.h b/qemu/hw/device-assignment.h
index c8c47d3..a565948 100644
--- a/qemu/hw/device-assignment.h
+++ b/qemu/hw/device-assignment.h
@@ -98,6 +98,7 @@ void free_assigned_device(AssignedDevInfo *adev);
PCIDevice *init_assigned_device(AssignedDevInfo *adev, PCIBus *bus);
AssignedDevInfo *add_assigned_device(const char *arg);
void add_assigned_devices(PCIBus *bus, const char **devices, int n_devices);
+ram_addr_t assigned_dev_load_option_roms(ram_addr_t rom_base_offset);

#define MAX_DEV_ASSIGN_CMDLINE 8

diff --git a/qemu/hw/pc.c b/qemu/hw/pc.c
index 2d7e7f0..c470646 100644
--- a/qemu/hw/pc.c
+++ b/qemu/hw/pc.c
@@ -810,6 +810,7 @@ static void pc_init1(ram_addr_t ram_size, int vga_ram_size,
ram_addr_t ram_addr, vga_ram_addr, bios_offset, vga_bios_offset;
ram_addr_t below_4g_mem_size, above_4g_mem_size = 0;
int bios_size, isa_bios_size, vga_bios_size, opt_rom_offset;
+ int pci_option_rom_offset;
PCIBus *pci_bus;
int piix3_devfn = -1;
CPUState *env;
@@ -972,6 +973,7 @@ static void pc_init1(ram_addr_t ram_size, int vga_ram_size,
option_rom_setup_reset(0xd0000 + offset, size);
offset += size;
}
+ pci_option_rom_offset = offset;
}

/* map all the bios at the top of memory */
@@ -1190,8 +1192,10 @@ static void pc_init1(ram_addr_t ram_size, int vga_ram_size,
virtio_balloon_init(pci_bus);

#ifdef USE_KVM_DEVICE_ASSIGNMENT
- if (kvm_enabled())
- add_assigned_devices(pci_bus, assigned_devices, assigned_devices_index);
+ if (kvm_enabled()) {
+ add_assigned_devices(pci_bus, assigned_devices, assigned_devices_index);
+ assigned_dev_load_option_roms(pci_option_rom_offset);
+ }
#endif /* USE_KVM_DEVICE_ASSIGNMENT */
}

--
1.6.0