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

Leendert van Doorn
2009-01-04T04:12:46Z

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

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 #2: pci-rom-bar.patch --]
[-- Type: application/octet-stream, Size: 25528 bytes --]

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 */