I've been experimenting with passthru graphics for KVM and for that I needed
to add PCI ROM support to QEMU. I went a slightly different route than
Haitoa, I enabled the BIOS to do the PCI ROM mapping and initialization and
added support for QEMU to fully implement the ROM BAR. This patch also uses
the host BAR addresses in the guest for direct assigned devices, because (as
Avi already pointed out) this is needed for more complex PCI cards such as
graphics adapters. My patch also allows you to optionally specify the ROM
image, which I found very useful for debugging.

My ROMBIOS patches are probably more invasive than necessary. I found it
cleaner (==easier to understand) to call the rombios from 32-bit mode than
leave some droppings (option rom address, bdf) in the BIOS extended data
segment. The other thing you notice is that I initialize the primary VGA ROM
last. I found that this was necessary when emulating dual headed displays
(one display on qemu's emulation window, the other the actual device).

I've attached my patches.

Note that I have not included the full graphics passthru patches yet. I have
that working for the xorg ati drivers (and ATI's ATOMBIOS) but not for the
closed sourced or Windows drivers yet. Let me know if you are interested in
those patches.

        Leendert


diff --git a/bios/rombios.c b/bios/rombios.c
index b7a240f..c8ef8ac 100644
--- a/bios/rombios.c
+++ b/bios/rombios.c
@@ -10130,7 +10130,7 @@ rombios32_real_mode:
   ret
 
 rombios32_gdt_48:
-  dw 0x30
+  dw 0x38
   dw rombios32_gdt
   dw 0x000f
 
@@ -10141,6 +10141,7 @@ rombios32_gdt:
   dw 0xffff, 0, 0x9300, 0x00cf ; 32 bit flat data segment (0x18)
   dw 0xffff, 0, 0x9b0f, 0x0000 ; 16 bit code segment base=0xf0000
limit=0xffff
   dw 0xffff, 0, 0x9300, 0x0000 ; 16 bit data segment base=0x0 limit=0xffff
+  dw 0xffff, 0, 0x9b0e, 0x0000 ; 16 bit code segment base=0xE0000
limit=0xffff
 #endif // BX_ROMBIOS32
 
 
@@ -10194,6 +10195,7 @@ no_serial:
   pop  dx
   ret
 
+#if !BX_ROMBIOS32
 rom_checksum:
   push ax
   push bx
@@ -10266,7 +10268,6 @@ block_count_rounded:
   mov  ax, #0xf000
   mov  es, ax
   lea  di, pnp_string
-
   mov  bp, sp   ;; Call ROM init routine using seg:off on stack
   db   0xff     ;; call_far ss:[bp+0]
   db   0x5e
@@ -10348,6 +10349,7 @@ rom_scan_increment:
   xor  ax, ax   ;; Restore DS back to 0000:
   mov  ds, ax
   ret
+#endif /* !BX_ROMBIOS32 */
 
 post_enable_cache:
   ;; enable cache
@@ -10652,12 +10654,6 @@ post_default_ints:
   ;; PIC
   call post_init_pic
 
-  mov  cx, #0xc000  ;; init vga bios
-  mov  ax, #0xc780
-  call rom_scan
-
-  call _print_bios_banner
-
 #if BX_ROMBIOS32
   call rombios32_init
 #else
@@ -10665,7 +10661,12 @@ post_default_ints:
   call pcibios_init_iomem_bases
   call pcibios_init_irqs
 #endif //BX_PCIBIOS
-#endif
+  mov  cx, #0xc000  ;; init vga bios
+  mov  ax, #0xc780
+  call rom_scan
+#endif //BX_ROMBIOS32
+
+  call _print_bios_banner
 
   ;;
   ;; Floppy setup
@@ -10698,9 +10699,11 @@ post_default_ints:
 
   call _init_boot_vectors
 
+#if !BX_ROMBIOS32
   mov  cx, #0xc800  ;; init option roms
   mov  ax, #0xe000
   call rom_scan
+#endif
 
 #if BX_ELTORITO_BOOT
   call _interactive_bootkey
diff --git a/bios/rombios32.c b/bios/rombios32.c
index 321563d..580aa17 100755
--- a/bios/rombios32.c
+++ b/bios/rombios32.c
@@ -1,4 +1,4 @@
-/////////////////////////////////////////////////////////////////////////
+////////////////////////////////////////////////////////////////////////
 // $Id: rombios32.c,v 1.11 2007/08/03 13:56:13 vruppert Exp $
 /////////////////////////////////////////////////////////////////////////
 //
@@ -679,6 +679,7 @@ typedef struct PCIDevice {
     int devfn;
 } PCIDevice;
 
+static int pci_bios_nvga = 0;
 static uint32_t pci_bios_io_addr;
 static uint32_t pci_bios_mem_addr;
 static uint32_t pci_bios_bigmem_addr;
@@ -736,7 +737,7 @@ static void pci_set_io_region_addr(PCIDevice *d, int
region_num, uint32_t addr)
     old_addr = pci_config_readl(d, ofs);
 
     pci_config_writel(d, ofs, addr);
-    BX_INFO("region %d: 0x%08x\n", region_num, addr);
+//  BX_INFO("region %d: 0x%08x\n", region_num, addr);
 
     /* enable memory mappings */
     cmd = pci_config_readw(d, PCI_COMMAND);
@@ -928,6 +929,7 @@ static void pci_bios_init_device(PCIDevice *d)
         }
         break;
     case 0x0300: /* Display controller - VGA compatible controller */
+       pci_bios_nvga++;
         if (vendor_id != 0x1234)
             goto default_map;
         /* VGA: map frame buffer to default Bochs VBE address */
@@ -953,18 +955,21 @@ static void pci_bios_init_device(PCIDevice *d)
     default_map:
         /* default memory mappings */
         for(i = 0; i < PCI_NUM_REGIONS; i++) {
+            uint32_t orig, addr, val, size;
             int ofs;
-            uint32_t val, size ;
 
             if (i == PCI_ROM_SLOT)
                 ofs = 0x30;
             else
                 ofs = 0x10 + i * 4;
+            orig = pci_config_readl(d, ofs);
             pci_config_writel(d, ofs, 0xffffffff);
             val = pci_config_readl(d, ofs);
             if (val != 0) {
                 size = (~(val & ~0xf)) + 1;
-                if (val & PCI_ADDRESS_SPACE_IO)
+               if (i == PCI_ROM_SLOT)
+                   paddr = &pci_bios_mem_addr;
+               else if (val & PCI_ADDRESS_SPACE_IO)
                     paddr = &pci_bios_io_addr;
                 else if (size >= 0x04000000)
                     paddr = &pci_bios_bigmem_addr;
@@ -972,7 +977,24 @@ static void pci_bios_init_device(PCIDevice *d)
                     paddr = &pci_bios_mem_addr;
                 *paddr = (*paddr + size - 1) & ~(size - 1);
                 pci_set_io_region_addr(d, i, *paddr);
+               BX_INFO("PCI: region %d: paddr 0x%x, size 0x%x\n",
+                       i, *paddr, size);
                 *paddr += size;
+
+               /*
+                * Some complex PCI devices (such as graphic controllers)
+                * keep lots of internal state, including their PCI mappings
+                * that were determined at initial PCI configuration.
+                * To make them work under QEMU/KVM, we preserve the host's
+                * PCI mappings in the guest.
+                * (we have to do this twice to ensure we update the
mappings)
+                */
+               addr = orig & ~(size - 1);
+               if (addr) {
+                    pci_set_io_region_addr(d, i, addr);
+                   BX_INFO("PCI: region %d: addr 0x%x, size 0x%x (host)\n",
+                       i, addr, size);
+               }
             }
         }
         break;
@@ -1033,6 +1055,310 @@ void pci_bios_init(void)
 }
 
 /****************************************************/
+/* Option ROM init */
+
+#define ROM_SIGNATURE  0xAA55
+#define PCI_SIGNATURE  0x52494350      /* "RICP" */
+
+#define        VGA_ROM         0xC0000
+#define        EXT_ROM_START   0xD0000
+#define        EXT_ROM_END     0xE0000
+
+#define        ROUND(v, n)     (((v) + (n) - 1) & ~((n) - 1))
+
+struct rom_header {
+    uint16_t     signature;
+    uint8_t      size;
+    uint8_t      init[3];
+    uint8_t      reserved[0x12];
+    uint16_t     data;
+};
+
+struct pci_header {
+    uint32_t     signature;
+    uint16_t     vendor;
+    uint16_t     device;
+    uint16_t     reserved_1;
+    uint16_t     dlen;
+    uint8_t      drevision;
+    uint8_t      class_lo;
+    uint16_t     class_hi;
+    uint16_t     ilen;
+    uint16_t     irevision;
+    uint8_t      type;
+    uint8_t      indicator;
+    uint16_t     reserved_2;
+};
+
+struct dtr {
+    unsigned short size;
+    unsigned long base __attribute__ ((packed));
+};
+
+struct dtr oldidtr;
+unsigned long stack;
+
+void rom_far_call(uint32_t seg, uint32_t eax)
+{
+       BX_INFO("ROM far call 0x%x:3; eax=0x%lx\n", seg, eax);
+
+       __asm__ __volatile__ (
+       "       .set RELOCBASE, 0xE0000 \n"     /* we are executing @0xE0000
*/
+       "       .set RELOCSEG, [RELOCBASE>>4]\n"
+       "       .set IPL_SEG, 0x9ff0    \n"
+       "       .set IPL_TABLE_ENTRIES, 8\n"
+       "       .set IPL_COUNT_OFFSET, 0x0080\n"
+       "       .set IPL_TYPE_BEV, 0x80\n"
+
+
+       /* save the world */
+       "       pushal                  \n"
+       "       mov     %esp, stack     \n"
+
+       /* get arguments */
+       "       movl    8(%ebp), %ecx   \n"     /* segment */
+       "       movl    12(%ebp), %eax  \n"     /* eax */
+
+       /* save idt */
+       "       sidt    oldidtr         \n"
+
+       /* get into real mode */
+       "       ljmp    $0x30, $1f-RELOCBASE\n"
+
+       /* initialized segment registers */
+       "       .code16                 \n"
+       "1:     mov     $0x28, %bx      \n"
+       "       mov     %bx, %ds        \n"
+       "       mov     %bx, %es        \n"
+       "       mov     %bx, %ss        \n"
+       "       mov     %bx, %fs        \n"
+       "       mov     %bx, %gs        \n"
+
+       /* turn off protected mode */
+       "       movl    %cr0, %ebx      \n"
+       "       andl    $~1, %ebx       \n"
+       "       movl    %ebx, %cr0      \n"
+
+       /* flush prefetch queue */
+       "       data32  ljmp $RELOCSEG, $(1f-RELOCBASE)\n"
+
+       /* setup 16-bit IDT */
+       "       .align  4               \n"
+       "idtr:  .word   1024-1          \n"
+       "       .long   0               \n"
+       "1:     addr32  lidt %cs:(idtr-RELOCBASE)\n"
+
+       /* zero the segment registers */
+       "       xor     %bx, %bx        \n"
+       "       mov     %bx, %ds        \n"
+       "       mov     %bx, %es        \n"
+       "       mov     %bx, %ss        \n"
+       "       mov     %bx, %fs        \n"
+       "       mov     %bx, %gs        \n"
+
+       /* setup a new stack */
+       "       movl    $0x1000, %ebx   \n"
+       "       movl    %ebx, %esp      \n"
+
+       /*
+        * 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.
+        */
+       "       movw    $RELOCBASE>>4, %bx\n"
+       "       mov     %bx, %es        \n"
+       "       movl    $pnp-RELOCBASE, %edi\n"
+
+       /* far call to extension ROM at %cx:3 */
+       "       push    %cx             \n"
+       "       push    $3              \n"
+       "       mov     %sp, %bp        \n"
+       "       .byte   0xFF, 0x5E, 0x00\n"     /* call far %ss:(%bp) */
+       "       cli                     \n"     /* just in case */
+
+       /*
+        * Look at the ROM's PnP Expansion header.  Properly, we're supposed
+        * to init all the ROMs and then go back and build an IPL table of
+        * all the bootable devices, but we can get away with one pass.
+        */
+       "       pop     %cx             \n"
+       "       pop     %cx             \n"     /* ROM segment */
+       "       mov     %cx, %ds        \n"
+
+       "       mov     $0x1A, %bx      \n"
+       "       movl    (%bx), %eax     \n"
+       "       movl    $pnp-RELOCBASE, %edi\n"
+       "       cmpl    %eax, (%edi)    \n"
+       "       je      1f              \n"
+       "       mov     0x1A(%bx), %ax  \n"
+       "       cmp     $0, %ax         \n"
+       "       je      1f              \n"
+
+       /*
+        * Found a device that thinks it can boot the system.
+        * Record its BEV and product name string.
+        */
+       "       mov     0x10(%bx), %di  \n"
+       "       mov     $IPL_SEG, %bx   \n"
+       "       mov     %bx, %ds        \n"
+       "       mov     IPL_COUNT_OFFSET, %bx\n"
+       "       cmpw    $IPL_TABLE_ENTRIES, %bx\n"
+       "       je      1f              \n"
+
+       "       shl     $4, %bx         \n"
+       "       movw    $IPL_TYPE_BEV, (%bx)\n"
+       "       mov     %ax, 4(%bx)     \n"
+       "       mov     %cx, 6(%bx)     \n"
+       "       cmp     $0, %di         \n"
+       "       je      2f              \n"
+
+       "       mov     %di, 8(%bx)     \n"
+       "       mov     %cx, 10(%bx)    \n"
+
+       "2:     shr     $4, %bx         \n"
+       "       inc     %bx             \n"
+       "       mov     %bx, IPL_COUNT_OFFSET\n"
+
+       "       jmp     1f              \n"
+       "pnp:   .asciz  \"$PnP\"        \n"
+
+       /* turn on protected mode */
+       "1:     movl    %cr0, %ebx      \n"
+       "       orl     $0x1, %ebx      \n"
+       "       movl    %ebx, %cr0      \n"
+
+       /* jump to 32-bit */
+       "       data32  ljmp $0x10, $1f \n"
+
+       "       .code32                 \n"
+       "1:     movw    $0x18, %bx      \n"
+       "       mov     %bx, %ds        \n"
+       "       mov     %bx, %es        \n"
+       "       mov     %bx, %ss        \n"
+       "       mov     %bx, %fs        \n"
+       "       mov     %bx, %gs        \n"
+
+       "       lidt    oldidtr         \n"
+       "       mov     stack, %esp     \n"
+       "       popal                   \n"
+       );
+}
+
+static int is_valid_rom(uint32_t rom_address)
+{
+    struct rom_header *rom_header;
+    unsigned char sum = 0, *rom_bytes;
+    unsigned int i;
+
+    rom_header = (struct rom_header *)rom_address;
+    if (rom_header->signature != ROM_SIGNATURE)
+       return 0;
+
+    rom_bytes = (unsigned char *)rom_address;
+    for (i = 0; i < rom_header->size * 512; i++)
+       sum += *(rom_bytes + i);
+    if (sum != 0)
+       return 0;
+
+    return 1;
+}
+
+static int is_valid_pci_rom(PCIDevice *d, uint32_t rom_address)
+{
+    int vendor_id, device_id;
+    struct pci_header *pci_header;
+    struct rom_header *rom_header;
+
+    if (!is_valid_rom(rom_address))
+       return 0;
+
+    rom_header = (struct rom_header *)rom_address;
+    pci_header = (struct pci_header *)((unsigned char *)rom_header
+                                 + rom_header->data);
+    if ( pci_header->signature != PCI_SIGNATURE)
+       return 0;
+
+    vendor_id = pci_config_readw(d, PCI_VENDOR_ID);
+    device_id = pci_config_readw(d, PCI_DEVICE_ID);
+    if (pci_header->vendor != vendor_id || pci_header->device != device_id)
+       return 0;
+
+    return 1;
+}
+
+static int pci_nvga = 0;
+static uint32_t pci_vga_devfn = 0;
+uint32_t ext_rom = EXT_ROM_START;
+uint32_t ext_rom_end = EXT_ROM_END;
+
+static void init_rom(PCIDevice *d)
+{
+    struct rom_header *rom_header;
+    struct pci_header *pci_header;
+    uint32_t rom_address;
+    int is_primary_vga = 0;
+    uint8_t *dest;
+    int class;
+
+    class = pci_config_readw(d, PCI_CLASS_DEVICE);
+
+    /* first VGA card found it the primary */
+    if (class == 0x300 && pci_nvga++ == 0)
+       is_primary_vga = 1;
+
+    rom_address = pci_config_readl(d, 0x30) & ~1;
+    if (rom_address == 0 || rom_address == ~0)
+       return;
+
+    BX_INFO("%x:%x: PCI option ROM address 0x%x\n",
+           d->bus, d->devfn, rom_address);
+    pci_config_writel(d, 0x30, rom_address | 1); /* enable rom */
+
+    if (!is_valid_pci_rom(d, rom_address))
+       goto bailout;
+
+    rom_header = (struct rom_header *)rom_address;
+    pci_header = (struct pci_header *)((unsigned char *)rom_header
+                                               + rom_header->data);
+
+    dest = is_primary_vga ? (uint8_t *)VGA_ROM : (uint8_t *)ext_rom;
+    BX_INFO("Copying PCI %04x:%04x ROM to 0x%x ... ",
+           pci_header->vendor, pci_header->device, dest);
+    memcpy(dest, (char *)rom_address, rom_header->size * 512);
+    BX_INFO("DONE\n");
+
+    if (!is_primary_vga) {
+       rom_far_call(ext_rom >> 4, (d->bus << 8) | d->devfn);
+       ext_rom += ROUND(rom_header->size * 512, 2048);
+    } else
+       pci_vga_devfn = (d->bus << 8) | d->devfn;
+
+bailout:
+    pci_config_writel(d, 0x30, rom_address); /* disable rom */
+}
+
+void option_rom_init(void)
+{
+    /* Execute any extension roms that were pre-loaded */
+    while (ext_rom < ext_rom_end) {
+       struct rom_header *rom_header = (struct rom_header *)ext_rom;
+
+       if (!is_valid_rom(ext_rom))
+           break;
+       rom_far_call(ext_rom >> 4, 0);
+
+       ext_rom += ROUND(rom_header->size * 512, 2048);
+    }
+
+    /* Execute any extension roms that came from PCI devices */
+    pci_nvga = 0;
+    pci_for_each_device(init_rom);
+
+    /* Initialize the primary VGA subsystem (last) */
+    if (is_valid_rom(VGA_ROM))
+       rom_far_call(VGA_ROM >> 4, pci_vga_devfn);
+}
+
+/****************************************************/
 /* Multi Processor table init */
 
 static void putb(uint8_t **pp, int val)
@@ -2257,4 +2583,6 @@ void rombios32_init(uint32_t *s3_resume_vector,
uint8_t *shutdown_flag)
             BX_PANIC("ebda_cur_addr overflow!\n");
 #endif
     }
+
+    option_rom_init();
 }
diff --git a/qemu/hw/device-assignment.c b/qemu/hw/device-assignment.c
index 7a66665..b6ba676 100644
--- a/qemu/hw/device-assignment.c
+++ b/qemu/hw/device-assignment.c
@@ -175,13 +175,33 @@ static void assigned_dev_iomem_map(PCIDevice *pci_dev,
int region_num,
     }
 }
 
+static void assigned_dev_ioperm(int start_port, int num, int turn_on)
+{
+    struct ioperm_data *data;
+    CPUState *env;
+
+    data = qemu_mallocz(sizeof(struct ioperm_data));
+    if (data == NULL) {
+        fprintf(stderr, "%s: Out of memory\n", __func__);
+        exit(1);
+    }
+
+    data->start_port = start_port;
+    data->num = num;
+    data->turn_on = 1;
+
+    kvm_add_ioperm_data(data);
+
+    for (env = first_cpu; env; env = env->next_cpu)
+        kvm_ioperm(env, data);
+}
+
 static void assigned_dev_ioport_map(PCIDevice *pci_dev, int region_num,
                                     uint32_t addr, uint32_t size, int type)
 {
     AssignedDevice *r_dev = (AssignedDevice *) pci_dev;
     AssignedDevRegion *region = &r_dev->v_addrs[region_num];
     int first_map = (region->e_size == 0);
-    CPUState *env;
 
     region->e_physbase = addr;
     region->e_size = size;
@@ -189,24 +209,8 @@ static void assigned_dev_ioport_map(PCIDevice *pci_dev,
int region_num,
     DEBUG("e_phys=0x%x r_baseport=%x type=0x%x len=%d region_num=%d \n",
           addr, region->u.r_baseport, type, size, region_num);
 
-    if (first_map) {
-       struct ioperm_data *data;
-
-       data = qemu_mallocz(sizeof(struct ioperm_data));
-       if (data == NULL) {
-           fprintf(stderr, "%s: Out of memory\n", __func__);
-           exit(1);
-       }
-
-       data->start_port = region->u.r_baseport;
-       data->num = region->r_size;
-       data->turn_on = 1;
-
-       kvm_add_ioperm_data(data);
-
-       for (env = first_cpu; env; env = env->next_cpu)
-           kvm_ioperm(env, data);
-    }
+    if (first_map)
+        assigned_dev_ioperm(region->u.r_baseport, region->r_size, 1);
 
     register_ioport_read(addr, size, 1, assigned_dev_ioport_readb,
                          (r_dev->v_addrs + region_num));
@@ -237,8 +241,8 @@ static void assigned_dev_pci_write_config(PCIDevice *d,
uint32_t address,
         /* Continue to program the card */
     }
 
-    if ((address >= 0x10 && address <= 0x24) || address == 0x34 ||
-        address == 0x3c || address == 0x3d) {
+    if ((address >= 0x10 && address <= 0x24) || address == 0x30 ||
+        address == 0x34 || address == 0x3c || address == 0x3d) {
         /* used for update-mappings (BAR emulation) */
         pci_default_write_config(d, address, val, len);
         return;
@@ -270,8 +274,8 @@ static uint32_t assigned_dev_pci_read_config(PCIDevice
*d, uint32_t address,
     int fd;
     ssize_t ret;
 
-    if ((address >= 0x10 && address <= 0x24) || address == 0x34 ||
-        address == 0x3c || address == 0x3d) {
+    if ((address >= 0x10 && address <= 0x24) || address == 0x30 ||
+        address == 0x34 || address == 0x3c || address == 0x3d) {
         val = pci_default_read_config(d, address, len);
         DEBUG("(%x.%x): address=%04x val=0x%08x len=%d\n",
               (d->devfn >> 3) & 0x1F, (d->devfn & 0x7), address, val, len);
@@ -309,6 +313,29 @@ do_log:
     return val;
 }
 
+#ifdef DEVICE_ASSIGNMENT_DEBUG
+void
+hexdump(uint8_t *data, int sz)
+{
+    uint8_t *d;
+    int i;
+
+    for (d = data; sz > 0; d += 16, sz -= 16) {
+        int n = sz > 16 ? 16 : sz;
+
+        printf("%08x: ", (unsigned) d);
+        for (i = 0; i < n; i++)
+            printf("%02x%c", d[i], i == 7 ? '-' : ' ');
+        for (; i < 16; i++)
+            printf("  %c", i == 7 ? '-' : ' ');
+        printf("   ");
+        for (i = 0; i < n; i++)
+            printf("%c", d[i] >= ' ' && d[i] <= '~' ? d[i] : '.');
+        printf("\n");
+    }
+}
+#endif /* DEVICE_ASSIGNMENT_DEBUG */
+
 static int assigned_dev_register_regions(PCIRegion *io_regions,
                                          unsigned long regions_num,
                                          AssignedDevice *pci_dev)
@@ -321,6 +348,78 @@ static int assigned_dev_register_regions(PCIRegion
*io_regions,
             continue;
         pci_dev->v_addrs[i].num = i;
 
+       /* handle ROM region */
+       if (i == PCI_ROM_SLOT) {
+           int t = cur_region->type & IORESOURCE_PREFETCH
+                   ? PCI_ADDRESS_SPACE_MEM_PREFETCH
+                   : PCI_ADDRESS_SPACE_MEM;
+           int nread, tread = 0;
+
+           /*
+            * Map physical memory. Ideally we mmap the rom image but
+            * since it may interfere with the device's operation (in
+            * case we are reading from a real device) we need to make
+            * a copy.
+            */
+           pci_dev->v_addrs[i].e_physbase = cur_region->base_addr;
+           pci_dev->v_addrs[i].u.r_virtbase =
+               qemu_vmalloc((cur_region->size + 0xFFF) & 0xFFFFF000);
+
+           if (pci_dev->v_addrs[i].u.r_virtbase == NULL) {
+               fprintf(stderr, "%s: Error: Couldn't malloc %d"
+                       " bytes!\n", __func__,
+                       (cur_region->size + 0xFFF) &
+                       0xFFFFF000);
+               return -1;
+           }
+
+           /* Linux quirk: Enable PCI ROM access */
+           if (!pci_dev->romfile) {
+               write(cur_region->resource_fd, "1\n", 2);
+               lseek(cur_region->resource_fd, 0L, SEEK_SET);
+           }
+
+           /* in option ROM */
+           do {
+               nread = read(cur_region->resource_fd,
+                            pci_dev->v_addrs[i].u.r_virtbase + tread,
+                            TARGET_PAGE_SIZE);
+               if (nread > 0)
+                   tread += nread;
+           } while (nread > 0 && tread < cur_region->size);
+
+           if (nread < 0) {
+               fprintf(stderr, "Error: Couldn't read ROM image\n");
+               return -1;
+           }
+
+           /* Linux quirk: Disable PCI ROM access */
+           if (!pci_dev->romfile) {
+               lseek(cur_region->resource_fd, 0L, SEEK_SET);
+               write(cur_region->resource_fd, "0\n", 2);
+           }
+
+#ifdef DEVICE_ASSIGNMENT_DEBUG
+           printf("PCI ROM:\n");
+            hexdump(pci_dev->v_addrs[i].u.r_virtbase, 64);
+#endif
+
+           pci_dev->v_addrs[i].r_size = cur_region->size;
+           pci_dev->v_addrs[i].e_size = 0;
+
+           /* add offset */
+           pci_dev->v_addrs[i].u.r_virtbase +=
+               (cur_region->base_addr & 0xFFF);
+
+           pci_register_io_region((PCIDevice *) pci_dev, i,
+                                  cur_region->size, t,
+                                  assigned_dev_iomem_map);
+
+           /* no need to tell the BIOS about the original ROM location */
+
+           continue;
+       }
+
         /* handle memory io regions */
         if (cur_region->type & IORESOURCE_MEM) {
             int t = cur_region->type & IORESOURCE_PREFETCH
@@ -352,8 +451,14 @@ static int assigned_dev_register_regions(PCIRegion
*io_regions,
             pci_register_io_region((PCIDevice *) pci_dev, i,
                                    cur_region->size, t,
                                    assigned_dev_iomem_map);
+
+            /* save original host mapping for BIOS (don't update mappings!)
*/
+            *(uint32_t *)(pci_dev->dev.config +  0x10 + i * 4) =
+                                       cpu_to_le32(cur_region->base_addr);
+
             continue;
         }
+
         /* handle port io regions */
         pci_dev->v_addrs[i].e_physbase = cur_region->base_addr;
         pci_dev->v_addrs[i].u.r_baseport = cur_region->base_addr;
@@ -364,6 +469,10 @@ static int assigned_dev_register_regions(PCIRegion
*io_regions,
                                cur_region->size, PCI_ADDRESS_SPACE_IO,
                                assigned_dev_ioport_map);
 
+        /* save original host mapping for BIOS (don't update mappings!) */
+        *(uint32_t *)(pci_dev->dev.config +  0x10 + i * 4) =
+                                       cpu_to_le32(cur_region->base_addr);
+
         /* not relevant for port io */
         pci_dev->v_addrs[i].memory_index = 0;
     }
@@ -423,10 +532,20 @@ again:
             continue;
         if (flags & IORESOURCE_MEM) {
             flags &= ~IORESOURCE_IO;
-           snprintf(name, sizeof(name), "%sresource%d", dir, r);
-            fd = open(name, O_RDWR);
-            if (fd == -1)
-                continue;       /* probably ROM */
+           if (r == PCI_ROM_SLOT && pci_dev->romfile) {
+               /* Use specified ROM instead of the card's */
+               strncpy(name, pci_dev->romfile, sizeof(name));
+           } else {
+               /* Use the card's resources */
+               if (r == PCI_ROM_SLOT)
+                   snprintf(name, sizeof(name), "%srom", dir);
+               else
+                   snprintf(name, sizeof(name), "%sresource%d", dir, r);
+           }
+           if ((fd = open(name, O_RDWR)) < 0) {
+                fprintf(stderr, "%s: %s: %m\n", __func__, name);
+                return 1;
+           }
             rp->resource_fd = fd;
         } else
             flags &= ~IORESOURCE_PREFETCH;
@@ -557,6 +676,7 @@ struct PCIDevice *init_assigned_device(AssignedDevInfo
*adev, PCIBus *bus)
 
     adev->assigned_dev = dev;
 
+    dev->romfile = adev->rom;
     if (get_real_device(dev, adev->bus, adev->dev, adev->func)) {
         fprintf(stderr, "%s: Error: Couldn't get real device (%s)!\n",
                 __func__, adev->name);
@@ -606,10 +726,11 @@ struct PCIDevice *init_assigned_device(AssignedDevInfo
*adev, PCIBus *bus)
 /*
  * Syntax to assign device:
  *
- * -pcidevice host=bus:dev.func[,dma=none][,name=Foo]
+ * -pcidevice host=bus:dev.func[,dma=none][,name=Foo][,rom=File]
  *
  * Example:
  * -pcidevice host=00:13.0,dma=pvdma
+ * -pcidevice host=01:00.0,rom=bios/RV610_B16905.bin
  *
  * dma can currently only be 'none' to disable iommu support.
  */
@@ -618,6 +739,7 @@ AssignedDevInfo *add_assigned_device(const char *arg)
     char *cp, *cp1;
     char device[8];
     char dma[6];
+    char rom[PATH_MAX];
     int r;
     AssignedDevInfo *adev;
 
@@ -636,6 +758,10 @@ AssignedDevInfo *add_assigned_device(const char *arg)
     if (r && !strncmp(dma, "none", 4))
         adev->disable_iommu = 1;
 #endif
+    r = get_param_value(rom, sizeof rom, "rom", arg);
+    if (r)
+       adev->rom = strdup(rom);
+
     cp = device;
     adev->bus = strtoul(cp, &cp1, 16);
     if (*cp1 != ':')
diff --git a/qemu/hw/device-assignment.h b/qemu/hw/device-assignment.h
index c8c47d3..3507be9 100644
--- a/qemu/hw/device-assignment.h
+++ b/qemu/hw/device-assignment.h
@@ -37,7 +37,7 @@
 #define PCI_DEVFN(slot, func)   ((((slot) & 0x1f) << 3) | ((func) & 0x07))
 
 /* The number of BARs in the config space header */
-#define MAX_IO_REGIONS (6)
+#define MAX_IO_REGIONS (7)
 
 typedef struct {
     int type;           /* Memory or port I/O */
@@ -80,6 +80,7 @@ typedef struct {
     unsigned char h_busnr;
     unsigned int h_devfn;
     int bound;
+    char *romfile;
 } AssignedDevice;
 
 typedef struct AssignedDevInfo AssignedDevInfo;
@@ -89,6 +90,7 @@ struct AssignedDevInfo {
     int bus;
     int dev;
     int func;
+    char *rom;
     AssignedDevice *assigned_dev;
     LIST_ENTRY(AssignedDevInfo) next;
     int disable_iommu;
diff --git a/qemu/hw/pc.c b/qemu/hw/pc.c
index 2d7e7f0..8451534 100644
--- a/qemu/hw/pc.c
+++ b/qemu/hw/pc.c
@@ -972,6 +972,18 @@ static void pc_init1(ram_addr_t ram_size, int
vga_ram_size,
             option_rom_setup_reset(0xd0000 + offset, size);
             offset += size;
         }
+
+        /* If we are using device assignment then map in the rest of the
+         * extension ROM space so PCI BIOS can use it to load ROMs into.
+         */
+        if (assigned_devices_index && offset < 0x10000) {
+           /* only 0xD0000..0xE0000 is available */
+            int size = 0x10000 - offset;
+            ram_addr_t option_rom_offset = qemu_ram_alloc(size);
+            cpu_register_physical_memory(0xd0000 + offset, size,
+                                        option_rom_offset /*| IO_MEM_ROM
*/);
+            option_rom_setup_reset(0xd0000 + offset, size);
+        }
     }
 
     /* map all the bios at the top of memory */

Attachment: pci-rom-bar.patch
Description: Binary data

Reply via email to