Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
qemu vfio-pci: Add support for VGA MMIO and I/O port access
Most VGA cards need some kind of quirk to fully operate since they
hide backdoors to get to other registers outside of PCI config space
within the registers, but this provides the base infrastructure.  If
we could identity map PCI resources for assigned devices we would need
a lot fewer quirks.

To enable this, use a kernel side vfio-pci driver that incorporates
VGA support (patches posted), and use the -vga none option and add the
x-vga=on option for the vfio-pci device.  The "x-" denotes this as an
experimental feature.  You may also need to use a cached copy of the
VGA BIOS for your device, passing it to vfio-pci using the romfile=
option.

Signed-off-by: Alex Williamson <alex.williamson@redhat.com>
  • Loading branch information
awilliam committed Jan 29, 2013
1 parent 820b8d9 commit ea2befa
Showing 1 changed file with 173 additions and 0 deletions.
173 changes: 173 additions & 0 deletions hw/vfio_pci.c
Expand Up @@ -59,6 +59,18 @@ typedef struct VFIOBAR {
uint8_t nr; /* cache the BAR number for debug */
} VFIOBAR;

typedef struct VFIOVGARegion {
MemoryRegion mem;
off_t offset;
int nr;
} VFIOVGARegion;

typedef struct VFIOVGA {
off_t fd_offset;
int fd;
VFIOVGARegion region[3];
} VFIOVGA;

typedef struct VFIOINTx {
bool pending; /* interrupt pending */
bool kvm_accel; /* set when QEMU bypass through KVM enabled */
Expand Down Expand Up @@ -126,10 +138,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 */
VFIOVGA vga; /* 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 {
Expand Down Expand Up @@ -957,6 +974,89 @@ static const MemoryRegionOps vfio_bar_ops = {
.endianness = DEVICE_LITTLE_ENDIAN,
};

static void vfio_vga_write(void *opaque, hwaddr addr,
uint64_t data, unsigned size)
{
VFIOVGARegion *region = opaque;
VFIOVGA *vga = container_of(region, VFIOVGA, region[region->nr]);
union {
uint8_t byte;
uint16_t word;
uint32_t dword;
uint64_t qword;
} buf;
off_t offset = vga->fd_offset + 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(vga->fd, &buf, size, offset) != size) {
error_report("%s(,0x%"HWADDR_PRIx", 0x%"PRIx64", %d) failed: %m\n",
__func__, region->offset + addr, data, size);
}

DPRINTF("%s(0x%"HWADDR_PRIx", 0x%"PRIx64", %d)\n",
__func__, region->offset + addr, data, size);
}

static uint64_t vfio_vga_read(void *opaque, hwaddr addr, unsigned size)
{
VFIOVGARegion *region = opaque;
VFIOVGA *vga = container_of(region, VFIOVGA, region[region->nr]);
union {
uint8_t byte;
uint16_t word;
uint32_t dword;
uint64_t qword;
} buf;
uint64_t data = 0;
off_t offset = vga->fd_offset + region->offset + addr;

if (pread(vga->fd, &buf, size, offset) != size) {
error_report("%s(,0x%"HWADDR_PRIx", %d) failed: %m\n",
__func__, 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__, region->offset + addr, size, data);

return data;
}

static const MemoryRegionOps vfio_vga_ops = {
.read = vfio_vga_read,
.write = vfio_vga_write,
.endianness = DEVICE_LITTLE_ENDIAN,
};

/*
* PCI config space
*/
Expand Down Expand Up @@ -1497,6 +1597,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) {
memory_region_init_io(&vdev->vga.region[0].mem, &vfio_vga_ops,
&vdev->vga.region[0], "vfio-vga-mmio@0xa0000",
0xbffff - 0xa0000 + 1);
memory_region_add_subregion_overlap(pci_address_space(&vdev->pdev),
0xa0000, &vdev->vga.region[0].mem,
1);

memory_region_init_io(&vdev->vga.region[1].mem, &vfio_vga_ops,
&vdev->vga.region[1], "vfio-vga-io@0x3b0",
0x3bb - 0x3b0 + 1);
memory_region_add_subregion_overlap(pci_address_space_io(&vdev->pdev),
0x3b0, &vdev->vga.region[1].mem, 1);

memory_region_init_io(&vdev->vga.region[2].mem, &vfio_vga_ops,
&vdev->vga.region[2], "vfio-vga-io@0x3c0",
0x3df - 0x3c0 + 1);
memory_region_add_subregion_overlap(pci_address_space_io(&vdev->pdev),
0x3c0, &vdev->vga.region[2].mem, 1);
}
}

static void vfio_unmap_bars(VFIODevice *vdev)
Expand All @@ -1506,6 +1627,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) {
memory_region_del_subregion(pci_address_space(&vdev->pdev),
&vdev->vga.region[0].mem);
memory_region_destroy(&vdev->vga.region[0].mem);

memory_region_del_subregion(pci_address_space_io(&vdev->pdev),
&vdev->vga.region[1].mem);
memory_region_destroy(&vdev->vga.region[1].mem);

memory_region_del_subregion(pci_address_space_io(&vdev->pdev),
&vdev->vga.region[2].mem);
memory_region_destroy(&vdev->vga.region[2].mem);
}
}

/*
Expand Down Expand Up @@ -1904,6 +2039,42 @@ static int vfio_get_device(VFIOGroup *group, const char *name, VFIODevice *vdev)
}
vdev->config_offset = reg_info.offset;

if ((vdev->features & VFIO_FEATURE_ENABLE_VGA) &&
dev_info.num_regions > VFIO_PCI_VGA_REGION_INDEX) {
struct vfio_region_info vga_info = {
.argsz = sizeof(vga_info),
.index = VFIO_PCI_VGA_REGION_INDEX,
};

ret = ioctl(vdev->fd, VFIO_DEVICE_GET_REGION_INFO, &vga_info);
if (ret) {
error_report(
"vfio: Device does not support requested feature x-vga\n");
goto error;
}

if (!(vga_info.flags & VFIO_REGION_INFO_FLAG_READ) ||
!(vga_info.flags & VFIO_REGION_INFO_FLAG_WRITE) ||
vga_info.size < 0xbffff + 1) {
error_report("vfio: Unexpected VGA info, flags 0x%lx, size 0x%lx\n",
(unsigned long)vga_info.flags,
(unsigned long)vga_info.size);
goto error;
}

vdev->vga.fd_offset = vga_info.offset;
vdev->vga.fd = vdev->fd;

vdev->vga.region[0].offset = 0xa0000;
vdev->vga.region[0].nr = 0;
vdev->vga.region[1].offset = 0x3b0;
vdev->vga.region[1].nr = 1;
vdev->vga.region[2].offset = 0x3c0;
vdev->vga.region[2].nr = 2;

vdev->has_vga = true;
}

error:
if (ret) {
QLIST_REMOVE(vdev, next);
Expand Down Expand Up @@ -2098,6 +2269,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("x-vga", VFIODevice, features,
VFIO_FEATURE_ENABLE_VGA_BIT, false),
/*
* TODO - support passed fds... is this necessary?
* DEFINE_PROP_STRING("vfiofd", VFIODevice, vfiofd_name),
Expand Down

0 comments on commit ea2befa

Please sign in to comment.