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

Shan, Haitao
2009-01-04T04:59:15Z

Ah, your approach is much better than mine.
Please ignore ours.

Best Regards
Haitao Shan

Leendert van Doorn wrote:
> 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 */