With this, some VGA cards can make it through VGA BIOS init, but I have yet to see one sync the monitor in VGA text mode. Only tested with -vga none. This adds a new option to vfio-pci, vga=on, which enables legacy VGA ranges. Signed-off-by: Alex Williamson <alex.williamson@xxxxxxxxxx> --- hw/vfio_pci.c | 173 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 172 insertions(+), 1 deletion(-) diff --git a/hw/vfio_pci.c b/hw/vfio_pci.c index 94c61ab..846e8de 100644 --- a/hw/vfio_pci.c +++ b/hw/vfio_pci.c @@ -59,6 +59,15 @@ typedef struct VFIOBAR { uint8_t nr; /* cache the BAR number for debug */ } VFIOBAR; +typedef struct VFIOLegacyIO { + off_t fd_offset; + int fd; + MemoryRegion mem; + off_t region_offset; + size_t size; + uint32_t flags; +} VFIOLegacyIO; + typedef struct VFIOINTx { bool pending; /* interrupt pending */ bool kvm_accel; /* set when QEMU bypass through KVM enabled */ @@ -126,10 +135,15 @@ typedef struct VFIODevice { int nr_vectors; /* Number of MSI/MSIX vectors currently in use */ int interrupt; /* Current interrupt type */ VFIOBAR bars[PCI_NUM_REGIONS - 1]; /* No ROM */ + VFIOLegacyIO vga[3]; /* 0xa0000, 0x3b0, 0x3c0 */ PCIHostDeviceAddress host; QLIST_ENTRY(VFIODevice) next; struct VFIOGroup *group; + uint32_t features; +#define VFIO_FEATURE_ENABLE_VGA_BIT 0 +#define VFIO_FEATURE_ENABLE_VGA (1 << VFIO_FEATURE_ENABLE_VGA_BIT) bool reset_works; + bool has_vga; } VFIODevice; typedef struct VFIOGroup { @@ -958,6 +972,87 @@ static const MemoryRegionOps vfio_bar_ops = { .endianness = DEVICE_LITTLE_ENDIAN, }; +static void vfio_legacy_write(void *opaque, hwaddr addr, + uint64_t data, unsigned size) +{ + VFIOLegacyIO *io = opaque; + union { + uint8_t byte; + uint16_t word; + uint32_t dword; + uint64_t qword; + } buf; + off_t offset = io->fd_offset + io->region_offset + addr; + + switch (size) { + case 1: + buf.byte = data; + break; + case 2: + buf.word = cpu_to_le16(data); + break; + case 4: + buf.dword = cpu_to_le32(data); + break; + default: + hw_error("vfio: unsupported write size, %d bytes\n", size); + break; + } + + if (pwrite(io->fd, &buf, size, offset) != size) { + error_report("%s(,0x%"HWADDR_PRIx", 0x%"PRIx64", %d) failed: %m\n", + __func__, io->region_offset + addr, data, size); + } + + DPRINTF("%s(0x%"HWADDR_PRIx", 0x%"PRIx64", %d)\n", + __func__, io->region_offset + addr, data, size); +} + +static uint64_t vfio_legacy_read(void *opaque, hwaddr addr, unsigned size) +{ + VFIOLegacyIO *io = opaque; + union { + uint8_t byte; + uint16_t word; + uint32_t dword; + uint64_t qword; + } buf; + uint64_t data = 0; + off_t offset = io->fd_offset + io->region_offset + addr; + + if (pread(io->fd, &buf, size, offset) != size) { + error_report("%s(,0x%"HWADDR_PRIx", %d) failed: %m\n", + __func__, io->region_offset + addr, size); + return (uint64_t)-1; + } + + switch (size) { + case 1: + data = buf.byte; + break; + case 2: + data = le16_to_cpu(buf.word); + break; + case 4: + data = le32_to_cpu(buf.dword); + break; + default: + hw_error("vfio: unsupported read size, %d bytes\n", size); + break; + } + + DPRINTF("%s(0x%"HWADDR_PRIx", %d) = 0x%"PRIx64"\n", + __func__, io->region_offset + addr, size, data); + + return data; +} + +static const MemoryRegionOps vfio_legacy_ops = { + .read = vfio_legacy_read, + .write = vfio_legacy_write, + .endianness = DEVICE_LITTLE_ENDIAN, +}; + /* * PCI config space */ @@ -1498,6 +1593,27 @@ static void vfio_map_bars(VFIODevice *vdev) for (i = 0; i < PCI_ROM_SLOT; i++) { vfio_map_bar(vdev, i); } + + if (vdev->has_vga && (vdev->features & VFIO_FEATURE_ENABLE_VGA)) { + memory_region_init_io(&vdev->vga[0].mem, &vfio_legacy_ops, + &vdev->vga[0], "vfio-vga-mmio@0xa0000", + 0xc0000 - 0xa0000); + memory_region_add_subregion_overlap(pci_address_space(&vdev->pdev), + 0xa0000, &vdev->vga[0].mem, 1); + memory_region_set_coalescing(&vdev->vga[0].mem); + + memory_region_init_io(&vdev->vga[1].mem, &vfio_legacy_ops, + &vdev->vga[1], "vfio-vga-io@0x3b0", + 0x3bc - 0x3b0); + memory_region_add_subregion_overlap(pci_address_space_io(&vdev->pdev), + 0x3b0, &vdev->vga[1].mem, 1); + + memory_region_init_io(&vdev->vga[2].mem, &vfio_legacy_ops, + &vdev->vga[2], "vfio-vga-io@0x3c0", + 0x3e0 - 0x3c0); + memory_region_add_subregion_overlap(pci_address_space_io(&vdev->pdev), + 0x3c0, &vdev->vga[2].mem, 1); + } } static void vfio_unmap_bars(VFIODevice *vdev) @@ -1507,6 +1623,20 @@ static void vfio_unmap_bars(VFIODevice *vdev) for (i = 0; i < PCI_ROM_SLOT; i++) { vfio_unmap_bar(vdev, i); } + + if (vdev->has_vga && (vdev->features & VFIO_FEATURE_ENABLE_VGA)) { + memory_region_del_subregion(pci_address_space(&vdev->pdev), + &vdev->vga[0].mem); + memory_region_destroy(&vdev->vga[0].mem); + + memory_region_del_subregion(pci_address_space_io(&vdev->pdev), + &vdev->vga[1].mem); + memory_region_destroy(&vdev->vga[1].mem); + + memory_region_del_subregion(pci_address_space_io(&vdev->pdev), + &vdev->vga[2].mem); + memory_region_destroy(&vdev->vga[2].mem); + } } /* @@ -1838,7 +1968,7 @@ static int vfio_get_device(VFIOGroup *group, const char *name, VFIODevice *vdev) error_report("Warning, device %s does not support reset\n", name); } - if (dev_info.num_regions != VFIO_PCI_NUM_REGIONS) { + if (dev_info.num_regions < VFIO_PCI_CONFIG_REGION_INDEX + 1) { error_report("vfio: unexpected number of io regions %u\n", dev_info.num_regions); goto error; @@ -1902,6 +2032,45 @@ static int vfio_get_device(VFIOGroup *group, const char *name, VFIODevice *vdev) vdev->config_size = reg_info.size; vdev->config_offset = reg_info.offset; + if (dev_info.num_regions > VFIO_PCI_CONFIG_REGION_INDEX + 1 && + dev_info.flags & VFIO_DEVICE_FLAGS_VGA) { + struct vfio_region_info mmio_info, io_info; + + mmio_info.argsz = io_info.argsz = sizeof(struct vfio_region_info); + mmio_info.index = VFIO_PCI_LEGACY_MMIO_REGION_INDEX; + io_info.index = VFIO_PCI_LEGACY_IOPORT_REGION_INDEX; + + ret = ioctl(vdev->fd, VFIO_DEVICE_GET_REGION_INFO, &mmio_info); + if (ret) { + error_report("vfio: Unable to access VGA MMIO resources: %m\n"); + ret = 0; + goto error; + } + + vdev->vga[0].flags = mmio_info.flags; + vdev->vga[0].size = mmio_info.size; + vdev->vga[0].fd_offset = mmio_info.offset; + vdev->vga[0].fd = vdev->fd; + vdev->vga[0].region_offset = 0xa0000; + + ret = ioctl(vdev->fd, VFIO_DEVICE_GET_REGION_INFO, &io_info); + if (ret) { + error_report("vfio: Unable to access VGA IOPORT resources: %m\n"); + ret = 0; + goto error; + } + + vdev->vga[1].flags = vdev->vga[2].flags = io_info.flags; + vdev->vga[1].size = vdev->vga[2].size = io_info.size; + vdev->vga[1].fd_offset = vdev->vga[2].fd_offset = io_info.offset; + vdev->vga[1].fd = vdev->vga[2].fd = vdev->fd; + + vdev->vga[1].region_offset = 0x3b0; + vdev->vga[2].region_offset = 0x3c0; + + vdev->has_vga = true; + } + error: if (ret) { QLIST_REMOVE(vdev, next); @@ -2096,6 +2265,8 @@ static Property vfio_pci_dev_properties[] = { DEFINE_PROP_PCI_HOST_DEVADDR("host", VFIODevice, host), DEFINE_PROP_UINT32("x-intx-mmap-timeout-ms", VFIODevice, intx.mmap_timeout, 1100), + DEFINE_PROP_BIT("vga", VFIODevice, features, + VFIO_FEATURE_ENABLE_VGA_BIT, false), /* * TODO - support passed fds... is this necessary? * DEFINE_PROP_STRING("vfiofd", VFIODevice, vfiofd_name), -- To unsubscribe from this list: send the line "unsubscribe kvm" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html