Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 84237a82 authored by Alex Williamson's avatar Alex Williamson
Browse files

vfio-pci: Add support for VGA region access



PCI defines display class VGA regions at I/O port address 0x3b0, 0x3c0
and MMIO address 0xa0000.  As these are non-overlapping, we can ignore
the I/O port vs MMIO difference and expose them both in a single
region.  We make use of the VGA arbiter around each access to
configure chipset access as necessary.

Signed-off-by: default avatarAlex Williamson <alex.williamson@redhat.com>
parent 2dd11948
Loading
Loading
Loading
Loading
+10 −0
Original line number Diff line number Diff line
@@ -6,3 +6,13 @@ config VFIO_PCI
	  use of PCI drivers using the VFIO framework.

	  If you don't know what to do here, say N.

config VFIO_PCI_VGA
	bool "VFIO PCI support for VGA devices"
	depends on VFIO_PCI && X86 && VGA_ARB && EXPERIMENTAL
	help
	  Support for VGA extension to VFIO PCI.  This exposes an additional
	  region on VGA devices for accessing legacy VGA addresses used by
	  BIOS and generic video drivers.

	  If you don't know what to do here, say N.
+18 −0
Original line number Diff line number Diff line
@@ -84,6 +84,11 @@ static int vfio_pci_enable(struct vfio_pci_device *vdev)
	} else
		vdev->msix_bar = 0xFF;

#ifdef CONFIG_VFIO_PCI_VGA
	if ((pdev->class >> 8) == PCI_CLASS_DISPLAY_VGA)
		vdev->has_vga = true;
#endif

	return 0;
}

@@ -285,6 +290,16 @@ static long vfio_pci_ioctl(void *device_data,
			info.flags = VFIO_REGION_INFO_FLAG_READ;
			break;
		}
		case VFIO_PCI_VGA_REGION_INDEX:
			if (!vdev->has_vga)
				return -EINVAL;

			info.offset = VFIO_PCI_INDEX_TO_OFFSET(info.index);
			info.size = 0xc0000;
			info.flags = VFIO_REGION_INFO_FLAG_READ |
				     VFIO_REGION_INFO_FLAG_WRITE;

			break;
		default:
			return -EINVAL;
		}
@@ -386,6 +401,9 @@ static ssize_t vfio_pci_rw(void *device_data, char __user *buf,

	case VFIO_PCI_BAR0_REGION_INDEX ... VFIO_PCI_BAR5_REGION_INDEX:
		return vfio_pci_bar_rw(vdev, buf, count, ppos, iswrite);

	case VFIO_PCI_VGA_REGION_INDEX:
		return vfio_pci_vga_rw(vdev, buf, count, ppos, iswrite);
	}

	return -EINVAL;
+4 −0
Original line number Diff line number Diff line
@@ -53,6 +53,7 @@ struct vfio_pci_device {
	bool			reset_works;
	bool			extended_caps;
	bool			bardirty;
	bool			has_vga;
	struct pci_saved_state	*pci_saved_state;
	atomic_t		refcnt;
};
@@ -77,6 +78,9 @@ extern ssize_t vfio_pci_config_rw(struct vfio_pci_device *vdev,
extern ssize_t vfio_pci_bar_rw(struct vfio_pci_device *vdev, char __user *buf,
			       size_t count, loff_t *ppos, bool iswrite);

extern ssize_t vfio_pci_vga_rw(struct vfio_pci_device *vdev, char __user *buf,
			       size_t count, loff_t *ppos, bool iswrite);

extern int vfio_pci_init_perm_bits(void);
extern void vfio_pci_uninit_perm_bits(void);

+61 −0
Original line number Diff line number Diff line
@@ -17,6 +17,7 @@
#include <linux/pci.h>
#include <linux/uaccess.h>
#include <linux/io.h>
#include <linux/vgaarb.h>

#include "vfio_pci_private.h"

@@ -175,3 +176,63 @@ ssize_t vfio_pci_bar_rw(struct vfio_pci_device *vdev, char __user *buf,

	return done;
}

ssize_t vfio_pci_vga_rw(struct vfio_pci_device *vdev, char __user *buf,
			       size_t count, loff_t *ppos, bool iswrite)
{
	int ret;
	loff_t off, pos = *ppos & VFIO_PCI_OFFSET_MASK;
	void __iomem *iomem = NULL;
	unsigned int rsrc;
	bool is_ioport;
	ssize_t done;

	if (!vdev->has_vga)
		return -EINVAL;

	switch (pos) {
	case 0xa0000 ... 0xbffff:
		count = min(count, (size_t)(0xc0000 - pos));
		iomem = ioremap_nocache(0xa0000, 0xbffff - 0xa0000 + 1);
		off = pos - 0xa0000;
		rsrc = VGA_RSRC_LEGACY_MEM;
		is_ioport = false;
		break;
	case 0x3b0 ... 0x3bb:
		count = min(count, (size_t)(0x3bc - pos));
		iomem = ioport_map(0x3b0, 0x3bb - 0x3b0 + 1);
		off = pos - 0x3b0;
		rsrc = VGA_RSRC_LEGACY_IO;
		is_ioport = true;
		break;
	case 0x3c0 ... 0x3df:
		count = min(count, (size_t)(0x3e0 - pos));
		iomem = ioport_map(0x3c0, 0x3df - 0x3c0 + 1);
		off = pos - 0x3c0;
		rsrc = VGA_RSRC_LEGACY_IO;
		is_ioport = true;
		break;
	default:
		return -EINVAL;
	}

	if (!iomem)
		return -ENOMEM;

	ret = vga_get_interruptible(vdev->pdev, rsrc);
	if (ret) {
		is_ioport ? ioport_unmap(iomem) : iounmap(iomem);
		return ret;
	}

	done = do_io_rw(iomem, buf, off, count, 0, 0, iswrite);

	vga_put(vdev->pdev, rsrc);

	is_ioport ? ioport_unmap(iomem) : iounmap(iomem);

	if (done >= 0)
		*ppos += done;

	return done;
}
+9 −0
Original line number Diff line number Diff line
@@ -303,6 +303,15 @@ enum {
	VFIO_PCI_BAR5_REGION_INDEX,
	VFIO_PCI_ROM_REGION_INDEX,
	VFIO_PCI_CONFIG_REGION_INDEX,
	/*
	 * Expose VGA regions defined for PCI base class 03, subclass 00.
	 * This includes I/O port ranges 0x3b0 to 0x3bb and 0x3c0 to 0x3df
	 * as well as the MMIO range 0xa0000 to 0xbffff.  Each implemented
	 * range is found at it's identity mapped offset from the region
	 * offset, for example 0x3b0 is region_info.offset + 0x3b0.  Areas
	 * between described ranges are unimplemented.
	 */
	VFIO_PCI_VGA_REGION_INDEX,
	VFIO_PCI_NUM_REGIONS
};