VFIO for 3.9-rc1
- Fixes PCIe v1 extended capability support - Cleans up read/write access functions - Fix Removal test to properly wait until devices are unused - Enable pcieport driver usage for non-accessible devices w/in groups - Extensions for PCI VGA support -----BEGIN PGP SIGNATURE----- Version: GnuPG v1.4.13 (GNU/Linux) iQIcBAABAgAGBQJRKkm1AAoJECObm247sIsiFIsQAIcppX9JOJ9pTIxx59G8CN0V LXJbTMPn3PtuiiMvS/+XqVTrdHDayddjjnaIHVVnYoTe2bPreCPdG8ViN37XjGLS JbxfGsslR7ZUH7PuO8fQIj64tA7sDKsquqchy+RmjNJujygDd6HSCw/K4eRkCX3z nZmDIlBcnqPLQi5zmwirdfzR+J0nKWWKvvqNOGgNTYfWj8M1WGH2M4Fi0ohLtVR8 1wJpZjwVPpwgMgMOMLBlFurQoKlibOKghjoOeJoMsBZ2gBi4iPustOJc+bwbF9SQ IuYpU3qGBUKWF6lvTlTMkgvGKBSqNgJBcTSJo0IvQounX1tIVYvRa2Bc1pQ4c+5p rFaD0+MCvRiyCSY85ZqvK7V6hfY8zTrhDm0eqCcW2hyy5xFJOh9jRxdmvAHSIA/A PWSy3wuDQJu6uIY+QNvWlo/iF2I8gh6b46Deqer3OuSCl3O2mjUjiSSqqR3BnO9p EG7mFMbvnv9hj0QxS53eYSUk0VRfu1EgfTLclbvo33JsCoVSk1Fb5zxHo04KRLZa da2pVM/B1NWtfscfQSNKLUpQ2UEMjfP48NXdbQriNoTZJIFQ6Qptg1NeRHPDK11x 0nI7sPv6Qe09YNA7EO91PbphYbl7bE+tjeBFkpmWfY+gKZ8YqgilL/SfTUBkv/dU Ntb4IdOODxyBOLLKIFBd =S7w8 -----END PGP SIGNATURE----- Merge tag 'vfio-v3.9-rc1' of git://github.com/awilliam/linux-vfio Pull VFIO updates from Alex Williamson: - Fixes PCIe v1 extended capability support - Cleans up read/write access functions - Fix Removal test to properly wait until devices are unused - Enable pcieport driver usage for non-accessible devices w/in groups - Extensions for PCI VGA support * tag 'vfio-v3.9-rc1' of git://github.com/awilliam/linux-vfio: drivers/vfio: remove depends on CONFIG_EXPERIMENTAL vfio-pci: Add support for VGA region access vfio-pci: Manage user power state transitions vfio: whitelist pcieport vfio: Protect vfio_dev_present against device_del vfio-pci: Cleanup BAR access vfio-pci: Cleanup read/write functions vfio-pci: Enable PCIe extended capabilities on v1
This commit is contained in:
		
				commit
				
					
						515d01f772
					
				
			
		
					 7 changed files with 284 additions and 257 deletions
				
			
		|  | @ -6,3 +6,13 @@ config VFIO_PCI | ||||||
| 	  use of PCI drivers using the VFIO framework. | 	  use of PCI drivers using the VFIO framework. | ||||||
| 
 | 
 | ||||||
| 	  If you don't know what to do here, say N. | 	  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 | ||||||
|  | 	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. | ||||||
|  |  | ||||||
|  | @ -84,6 +84,11 @@ static int vfio_pci_enable(struct vfio_pci_device *vdev) | ||||||
| 	} else | 	} else | ||||||
| 		vdev->msix_bar = 0xFF; | 		vdev->msix_bar = 0xFF; | ||||||
| 
 | 
 | ||||||
|  | #ifdef CONFIG_VFIO_PCI_VGA | ||||||
|  | 	if ((pdev->class >> 8) == PCI_CLASS_DISPLAY_VGA) | ||||||
|  | 		vdev->has_vga = true; | ||||||
|  | #endif | ||||||
|  | 
 | ||||||
| 	return 0; | 	return 0; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | @ -285,6 +290,16 @@ static long vfio_pci_ioctl(void *device_data, | ||||||
| 			info.flags = VFIO_REGION_INFO_FLAG_READ; | 			info.flags = VFIO_REGION_INFO_FLAG_READ; | ||||||
| 			break; | 			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: | 		default: | ||||||
| 			return -EINVAL; | 			return -EINVAL; | ||||||
| 		} | 		} | ||||||
|  | @ -366,52 +381,50 @@ static long vfio_pci_ioctl(void *device_data, | ||||||
| 	return -ENOTTY; | 	return -ENOTTY; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| static ssize_t vfio_pci_read(void *device_data, char __user *buf, | static ssize_t vfio_pci_rw(void *device_data, char __user *buf, | ||||||
| 			     size_t count, loff_t *ppos) | 			   size_t count, loff_t *ppos, bool iswrite) | ||||||
| { | { | ||||||
| 	unsigned int index = VFIO_PCI_OFFSET_TO_INDEX(*ppos); | 	unsigned int index = VFIO_PCI_OFFSET_TO_INDEX(*ppos); | ||||||
| 	struct vfio_pci_device *vdev = device_data; | 	struct vfio_pci_device *vdev = device_data; | ||||||
| 	struct pci_dev *pdev = vdev->pdev; |  | ||||||
| 
 | 
 | ||||||
| 	if (index >= VFIO_PCI_NUM_REGIONS) | 	if (index >= VFIO_PCI_NUM_REGIONS) | ||||||
| 		return -EINVAL; | 		return -EINVAL; | ||||||
| 
 | 
 | ||||||
| 	if (index == VFIO_PCI_CONFIG_REGION_INDEX) | 	switch (index) { | ||||||
| 		return vfio_pci_config_readwrite(vdev, buf, count, ppos, false); | 	case VFIO_PCI_CONFIG_REGION_INDEX: | ||||||
| 	else if (index == VFIO_PCI_ROM_REGION_INDEX) | 		return vfio_pci_config_rw(vdev, buf, count, ppos, iswrite); | ||||||
| 		return vfio_pci_mem_readwrite(vdev, buf, count, ppos, false); | 
 | ||||||
| 	else if (pci_resource_flags(pdev, index) & IORESOURCE_IO) | 	case VFIO_PCI_ROM_REGION_INDEX: | ||||||
| 		return vfio_pci_io_readwrite(vdev, buf, count, ppos, false); | 		if (iswrite) | ||||||
| 	else if (pci_resource_flags(pdev, index) & IORESOURCE_MEM) | 			return -EINVAL; | ||||||
| 		return vfio_pci_mem_readwrite(vdev, buf, count, ppos, false); | 		return vfio_pci_bar_rw(vdev, buf, count, ppos, false); | ||||||
|  | 
 | ||||||
|  | 	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; | 	return -EINVAL; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | static ssize_t vfio_pci_read(void *device_data, char __user *buf, | ||||||
|  | 			     size_t count, loff_t *ppos) | ||||||
|  | { | ||||||
|  | 	if (!count) | ||||||
|  | 		return 0; | ||||||
|  | 
 | ||||||
|  | 	return vfio_pci_rw(device_data, buf, count, ppos, false); | ||||||
|  | } | ||||||
|  | 
 | ||||||
| static ssize_t vfio_pci_write(void *device_data, const char __user *buf, | static ssize_t vfio_pci_write(void *device_data, const char __user *buf, | ||||||
| 			      size_t count, loff_t *ppos) | 			      size_t count, loff_t *ppos) | ||||||
| { | { | ||||||
| 	unsigned int index = VFIO_PCI_OFFSET_TO_INDEX(*ppos); | 	if (!count) | ||||||
| 	struct vfio_pci_device *vdev = device_data; | 		return 0; | ||||||
| 	struct pci_dev *pdev = vdev->pdev; |  | ||||||
| 
 | 
 | ||||||
| 	if (index >= VFIO_PCI_NUM_REGIONS) | 	return vfio_pci_rw(device_data, (char __user *)buf, count, ppos, true); | ||||||
| 		return -EINVAL; |  | ||||||
| 
 |  | ||||||
| 	if (index == VFIO_PCI_CONFIG_REGION_INDEX) |  | ||||||
| 		return vfio_pci_config_readwrite(vdev, (char __user *)buf, |  | ||||||
| 						 count, ppos, true); |  | ||||||
| 	else if (index == VFIO_PCI_ROM_REGION_INDEX) |  | ||||||
| 		return -EINVAL; |  | ||||||
| 	else if (pci_resource_flags(pdev, index) & IORESOURCE_IO) |  | ||||||
| 		return vfio_pci_io_readwrite(vdev, (char __user *)buf, |  | ||||||
| 					     count, ppos, true); |  | ||||||
| 	else if (pci_resource_flags(pdev, index) & IORESOURCE_MEM) { |  | ||||||
| 		return vfio_pci_mem_readwrite(vdev, (char __user *)buf, |  | ||||||
| 					      count, ppos, true); |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	return -EINVAL; |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| static int vfio_pci_mmap(void *device_data, struct vm_area_struct *vma) | static int vfio_pci_mmap(void *device_data, struct vm_area_struct *vma) | ||||||
|  |  | ||||||
|  | @ -587,12 +587,46 @@ static int __init init_pci_cap_basic_perm(struct perm_bits *perm) | ||||||
| 	return 0; | 	return 0; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | static int vfio_pm_config_write(struct vfio_pci_device *vdev, int pos, | ||||||
|  | 				int count, struct perm_bits *perm, | ||||||
|  | 				int offset, __le32 val) | ||||||
|  | { | ||||||
|  | 	count = vfio_default_config_write(vdev, pos, count, perm, offset, val); | ||||||
|  | 	if (count < 0) | ||||||
|  | 		return count; | ||||||
|  | 
 | ||||||
|  | 	if (offset == PCI_PM_CTRL) { | ||||||
|  | 		pci_power_t state; | ||||||
|  | 
 | ||||||
|  | 		switch (le32_to_cpu(val) & PCI_PM_CTRL_STATE_MASK) { | ||||||
|  | 		case 0: | ||||||
|  | 			state = PCI_D0; | ||||||
|  | 			break; | ||||||
|  | 		case 1: | ||||||
|  | 			state = PCI_D1; | ||||||
|  | 			break; | ||||||
|  | 		case 2: | ||||||
|  | 			state = PCI_D2; | ||||||
|  | 			break; | ||||||
|  | 		case 3: | ||||||
|  | 			state = PCI_D3hot; | ||||||
|  | 			break; | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		pci_set_power_state(vdev->pdev, state); | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return count; | ||||||
|  | } | ||||||
|  | 
 | ||||||
| /* Permissions for the Power Management capability */ | /* Permissions for the Power Management capability */ | ||||||
| static int __init init_pci_cap_pm_perm(struct perm_bits *perm) | static int __init init_pci_cap_pm_perm(struct perm_bits *perm) | ||||||
| { | { | ||||||
| 	if (alloc_perm_bits(perm, pci_cap_length[PCI_CAP_ID_PM])) | 	if (alloc_perm_bits(perm, pci_cap_length[PCI_CAP_ID_PM])) | ||||||
| 		return -ENOMEM; | 		return -ENOMEM; | ||||||
| 
 | 
 | ||||||
|  | 	perm->writefn = vfio_pm_config_write; | ||||||
|  | 
 | ||||||
| 	/*
 | 	/*
 | ||||||
| 	 * We always virtualize the next field so we can remove | 	 * We always virtualize the next field so we can remove | ||||||
| 	 * capabilities from the chain if we want to. | 	 * capabilities from the chain if we want to. | ||||||
|  | @ -600,10 +634,11 @@ static int __init init_pci_cap_pm_perm(struct perm_bits *perm) | ||||||
| 	p_setb(perm, PCI_CAP_LIST_NEXT, (u8)ALL_VIRT, NO_WRITE); | 	p_setb(perm, PCI_CAP_LIST_NEXT, (u8)ALL_VIRT, NO_WRITE); | ||||||
| 
 | 
 | ||||||
| 	/*
 | 	/*
 | ||||||
| 	 * Power management is defined *per function*, | 	 * Power management is defined *per function*, so we can let | ||||||
| 	 * so we let the user write this | 	 * the user change power state, but we trap and initiate the | ||||||
|  | 	 * change ourselves, so the state bits are read-only. | ||||||
| 	 */ | 	 */ | ||||||
| 	p_setd(perm, PCI_PM_CTRL, NO_VIRT, ALL_WRITE); | 	p_setd(perm, PCI_PM_CTRL, NO_VIRT, ~PCI_PM_CTRL_STATE_MASK); | ||||||
| 	return 0; | 	return 0; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | @ -985,12 +1020,12 @@ static int vfio_cap_len(struct vfio_pci_device *vdev, u8 cap, u8 pos) | ||||||
| 		if (ret) | 		if (ret) | ||||||
| 			return pcibios_err_to_errno(ret); | 			return pcibios_err_to_errno(ret); | ||||||
| 
 | 
 | ||||||
|  | 		vdev->extended_caps = true; | ||||||
|  | 
 | ||||||
| 		if ((word & PCI_EXP_FLAGS_VERS) == 1) | 		if ((word & PCI_EXP_FLAGS_VERS) == 1) | ||||||
| 			return PCI_CAP_EXP_ENDPOINT_SIZEOF_V1; | 			return PCI_CAP_EXP_ENDPOINT_SIZEOF_V1; | ||||||
| 		else { | 		else | ||||||
| 			vdev->extended_caps = true; |  | ||||||
| 			return PCI_CAP_EXP_ENDPOINT_SIZEOF_V2; | 			return PCI_CAP_EXP_ENDPOINT_SIZEOF_V2; | ||||||
| 		} |  | ||||||
| 	case PCI_CAP_ID_HT: | 	case PCI_CAP_ID_HT: | ||||||
| 		ret = pci_read_config_byte(pdev, pos + 3, &byte); | 		ret = pci_read_config_byte(pdev, pos + 3, &byte); | ||||||
| 		if (ret) | 		if (ret) | ||||||
|  | @ -1501,9 +1536,8 @@ static ssize_t vfio_config_do_rw(struct vfio_pci_device *vdev, char __user *buf, | ||||||
| 	return ret; | 	return ret; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| ssize_t vfio_pci_config_readwrite(struct vfio_pci_device *vdev, | ssize_t vfio_pci_config_rw(struct vfio_pci_device *vdev, char __user *buf, | ||||||
| 				  char __user *buf, size_t count, | 			   size_t count, loff_t *ppos, bool iswrite) | ||||||
| 				  loff_t *ppos, bool iswrite) |  | ||||||
| { | { | ||||||
| 	size_t done = 0; | 	size_t done = 0; | ||||||
| 	int ret = 0; | 	int ret = 0; | ||||||
|  |  | ||||||
|  | @ -53,6 +53,7 @@ struct vfio_pci_device { | ||||||
| 	bool			reset_works; | 	bool			reset_works; | ||||||
| 	bool			extended_caps; | 	bool			extended_caps; | ||||||
| 	bool			bardirty; | 	bool			bardirty; | ||||||
|  | 	bool			has_vga; | ||||||
| 	struct pci_saved_state	*pci_saved_state; | 	struct pci_saved_state	*pci_saved_state; | ||||||
| 	atomic_t		refcnt; | 	atomic_t		refcnt; | ||||||
| }; | }; | ||||||
|  | @ -70,16 +71,16 @@ extern int vfio_pci_set_irqs_ioctl(struct vfio_pci_device *vdev, | ||||||
| 				   uint32_t flags, unsigned index, | 				   uint32_t flags, unsigned index, | ||||||
| 				   unsigned start, unsigned count, void *data); | 				   unsigned start, unsigned count, void *data); | ||||||
| 
 | 
 | ||||||
| extern ssize_t vfio_pci_config_readwrite(struct vfio_pci_device *vdev, | extern ssize_t vfio_pci_config_rw(struct vfio_pci_device *vdev, | ||||||
| 					 char __user *buf, size_t count, |  | ||||||
| 					 loff_t *ppos, bool iswrite); |  | ||||||
| extern ssize_t vfio_pci_mem_readwrite(struct vfio_pci_device *vdev, |  | ||||||
| 				      char __user *buf, size_t count, |  | ||||||
| 				      loff_t *ppos, bool iswrite); |  | ||||||
| extern ssize_t vfio_pci_io_readwrite(struct vfio_pci_device *vdev, |  | ||||||
| 				  char __user *buf, size_t count, | 				  char __user *buf, size_t count, | ||||||
| 				  loff_t *ppos, bool iswrite); | 				  loff_t *ppos, bool iswrite); | ||||||
| 
 | 
 | ||||||
|  | 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 int vfio_pci_init_perm_bits(void); | ||||||
| extern void vfio_pci_uninit_perm_bits(void); | extern void vfio_pci_uninit_perm_bits(void); | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -17,208 +17,61 @@ | ||||||
| #include <linux/pci.h> | #include <linux/pci.h> | ||||||
| #include <linux/uaccess.h> | #include <linux/uaccess.h> | ||||||
| #include <linux/io.h> | #include <linux/io.h> | ||||||
|  | #include <linux/vgaarb.h> | ||||||
| 
 | 
 | ||||||
| #include "vfio_pci_private.h" | #include "vfio_pci_private.h" | ||||||
| 
 | 
 | ||||||
| /* I/O Port BAR access */ |  | ||||||
| ssize_t vfio_pci_io_readwrite(struct vfio_pci_device *vdev, char __user *buf, |  | ||||||
| 			      size_t count, loff_t *ppos, bool iswrite) |  | ||||||
| { |  | ||||||
| 	struct pci_dev *pdev = vdev->pdev; |  | ||||||
| 	loff_t pos = *ppos & VFIO_PCI_OFFSET_MASK; |  | ||||||
| 	int bar = VFIO_PCI_OFFSET_TO_INDEX(*ppos); |  | ||||||
| 	void __iomem *io; |  | ||||||
| 	size_t done = 0; |  | ||||||
| 
 |  | ||||||
| 	if (!pci_resource_start(pdev, bar)) |  | ||||||
| 		return -EINVAL; |  | ||||||
| 
 |  | ||||||
| 	if (pos + count > pci_resource_len(pdev, bar)) |  | ||||||
| 		return -EINVAL; |  | ||||||
| 
 |  | ||||||
| 	if (!vdev->barmap[bar]) { |  | ||||||
| 		int ret; |  | ||||||
| 
 |  | ||||||
| 		ret = pci_request_selected_regions(pdev, 1 << bar, "vfio"); |  | ||||||
| 		if (ret) |  | ||||||
| 			return ret; |  | ||||||
| 
 |  | ||||||
| 		vdev->barmap[bar] = pci_iomap(pdev, bar, 0); |  | ||||||
| 
 |  | ||||||
| 		if (!vdev->barmap[bar]) { |  | ||||||
| 			pci_release_selected_regions(pdev, 1 << bar); |  | ||||||
| 			return -EINVAL; |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	io = vdev->barmap[bar]; |  | ||||||
| 
 |  | ||||||
| 	while (count) { |  | ||||||
| 		int filled; |  | ||||||
| 
 |  | ||||||
| 		if (count >= 3 && !(pos % 4)) { |  | ||||||
| 			__le32 val; |  | ||||||
| 
 |  | ||||||
| 			if (iswrite) { |  | ||||||
| 				if (copy_from_user(&val, buf, 4)) |  | ||||||
| 					return -EFAULT; |  | ||||||
| 
 |  | ||||||
| 				iowrite32(le32_to_cpu(val), io + pos); |  | ||||||
| 			} else { |  | ||||||
| 				val = cpu_to_le32(ioread32(io + pos)); |  | ||||||
| 
 |  | ||||||
| 				if (copy_to_user(buf, &val, 4)) |  | ||||||
| 					return -EFAULT; |  | ||||||
| 			} |  | ||||||
| 
 |  | ||||||
| 			filled = 4; |  | ||||||
| 
 |  | ||||||
| 		} else if ((pos % 2) == 0 && count >= 2) { |  | ||||||
| 			__le16 val; |  | ||||||
| 
 |  | ||||||
| 			if (iswrite) { |  | ||||||
| 				if (copy_from_user(&val, buf, 2)) |  | ||||||
| 					return -EFAULT; |  | ||||||
| 
 |  | ||||||
| 				iowrite16(le16_to_cpu(val), io + pos); |  | ||||||
| 			} else { |  | ||||||
| 				val = cpu_to_le16(ioread16(io + pos)); |  | ||||||
| 
 |  | ||||||
| 				if (copy_to_user(buf, &val, 2)) |  | ||||||
| 					return -EFAULT; |  | ||||||
| 			} |  | ||||||
| 
 |  | ||||||
| 			filled = 2; |  | ||||||
| 		} else { |  | ||||||
| 			u8 val; |  | ||||||
| 
 |  | ||||||
| 			if (iswrite) { |  | ||||||
| 				if (copy_from_user(&val, buf, 1)) |  | ||||||
| 					return -EFAULT; |  | ||||||
| 
 |  | ||||||
| 				iowrite8(val, io + pos); |  | ||||||
| 			} else { |  | ||||||
| 				val = ioread8(io + pos); |  | ||||||
| 
 |  | ||||||
| 				if (copy_to_user(buf, &val, 1)) |  | ||||||
| 					return -EFAULT; |  | ||||||
| 			} |  | ||||||
| 
 |  | ||||||
| 			filled = 1; |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		count -= filled; |  | ||||||
| 		done += filled; |  | ||||||
| 		buf += filled; |  | ||||||
| 		pos += filled; |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	*ppos += done; |  | ||||||
| 
 |  | ||||||
| 	return done; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| /*
 | /*
 | ||||||
|  * MMIO BAR access |  * Read or write from an __iomem region (MMIO or I/O port) with an excluded | ||||||
|  * We handle two excluded ranges here as well, if the user tries to read |  * range which is inaccessible.  The excluded range drops writes and fills | ||||||
|  * the ROM beyond what PCI tells us is available or the MSI-X table region, |  * reads with -1.  This is intended for handling MSI-X vector tables and | ||||||
|  * we return 0xFF and writes are dropped. |  * leftover space for ROM BARs. | ||||||
|  */ |  */ | ||||||
| ssize_t vfio_pci_mem_readwrite(struct vfio_pci_device *vdev, char __user *buf, | static ssize_t do_io_rw(void __iomem *io, char __user *buf, | ||||||
| 			       size_t count, loff_t *ppos, bool iswrite) | 			loff_t off, size_t count, size_t x_start, | ||||||
|  | 			size_t x_end, bool iswrite) | ||||||
| { | { | ||||||
| 	struct pci_dev *pdev = vdev->pdev; | 	ssize_t done = 0; | ||||||
| 	loff_t pos = *ppos & VFIO_PCI_OFFSET_MASK; |  | ||||||
| 	int bar = VFIO_PCI_OFFSET_TO_INDEX(*ppos); |  | ||||||
| 	void __iomem *io; |  | ||||||
| 	resource_size_t end; |  | ||||||
| 	size_t done = 0; |  | ||||||
| 	size_t x_start = 0, x_end = 0; /* excluded range */ |  | ||||||
| 
 |  | ||||||
| 	if (!pci_resource_start(pdev, bar)) |  | ||||||
| 		return -EINVAL; |  | ||||||
| 
 |  | ||||||
| 	end = pci_resource_len(pdev, bar); |  | ||||||
| 
 |  | ||||||
| 	if (pos > end) |  | ||||||
| 		return -EINVAL; |  | ||||||
| 
 |  | ||||||
| 	if (pos == end) |  | ||||||
| 		return 0; |  | ||||||
| 
 |  | ||||||
| 	if (pos + count > end) |  | ||||||
| 		count = end - pos; |  | ||||||
| 
 |  | ||||||
| 	if (bar == PCI_ROM_RESOURCE) { |  | ||||||
| 		io = pci_map_rom(pdev, &x_start); |  | ||||||
| 		x_end = end; |  | ||||||
| 	} else { |  | ||||||
| 		if (!vdev->barmap[bar]) { |  | ||||||
| 			int ret; |  | ||||||
| 
 |  | ||||||
| 			ret = pci_request_selected_regions(pdev, 1 << bar, |  | ||||||
| 							   "vfio"); |  | ||||||
| 			if (ret) |  | ||||||
| 				return ret; |  | ||||||
| 
 |  | ||||||
| 			vdev->barmap[bar] = pci_iomap(pdev, bar, 0); |  | ||||||
| 
 |  | ||||||
| 			if (!vdev->barmap[bar]) { |  | ||||||
| 				pci_release_selected_regions(pdev, 1 << bar); |  | ||||||
| 				return -EINVAL; |  | ||||||
| 			} |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		io = vdev->barmap[bar]; |  | ||||||
| 
 |  | ||||||
| 		if (bar == vdev->msix_bar) { |  | ||||||
| 			x_start = vdev->msix_offset; |  | ||||||
| 			x_end = vdev->msix_offset + vdev->msix_size; |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	if (!io) |  | ||||||
| 		return -EINVAL; |  | ||||||
| 
 | 
 | ||||||
| 	while (count) { | 	while (count) { | ||||||
| 		size_t fillable, filled; | 		size_t fillable, filled; | ||||||
| 
 | 
 | ||||||
| 		if (pos < x_start) | 		if (off < x_start) | ||||||
| 			fillable = x_start - pos; | 			fillable = min(count, (size_t)(x_start - off)); | ||||||
| 		else if (pos >= x_end) | 		else if (off >= x_end) | ||||||
| 			fillable = end - pos; | 			fillable = count; | ||||||
| 		else | 		else | ||||||
| 			fillable = 0; | 			fillable = 0; | ||||||
| 
 | 
 | ||||||
| 		if (fillable >= 4 && !(pos % 4) && (count >= 4)) { | 		if (fillable >= 4 && !(off % 4)) { | ||||||
| 			__le32 val; | 			__le32 val; | ||||||
| 
 | 
 | ||||||
| 			if (iswrite) { | 			if (iswrite) { | ||||||
| 				if (copy_from_user(&val, buf, 4)) | 				if (copy_from_user(&val, buf, 4)) | ||||||
| 					goto out; | 					return -EFAULT; | ||||||
| 
 | 
 | ||||||
| 				iowrite32(le32_to_cpu(val), io + pos); | 				iowrite32(le32_to_cpu(val), io + off); | ||||||
| 			} else { | 			} else { | ||||||
| 				val = cpu_to_le32(ioread32(io + pos)); | 				val = cpu_to_le32(ioread32(io + off)); | ||||||
| 
 | 
 | ||||||
| 				if (copy_to_user(buf, &val, 4)) | 				if (copy_to_user(buf, &val, 4)) | ||||||
| 					goto out; | 					return -EFAULT; | ||||||
| 			} | 			} | ||||||
| 
 | 
 | ||||||
| 			filled = 4; | 			filled = 4; | ||||||
| 		} else if (fillable >= 2 && !(pos % 2) && (count >= 2)) { | 		} else if (fillable >= 2 && !(off % 2)) { | ||||||
| 			__le16 val; | 			__le16 val; | ||||||
| 
 | 
 | ||||||
| 			if (iswrite) { | 			if (iswrite) { | ||||||
| 				if (copy_from_user(&val, buf, 2)) | 				if (copy_from_user(&val, buf, 2)) | ||||||
| 					goto out; | 					return -EFAULT; | ||||||
| 
 | 
 | ||||||
| 				iowrite16(le16_to_cpu(val), io + pos); | 				iowrite16(le16_to_cpu(val), io + off); | ||||||
| 			} else { | 			} else { | ||||||
| 				val = cpu_to_le16(ioread16(io + pos)); | 				val = cpu_to_le16(ioread16(io + off)); | ||||||
| 
 | 
 | ||||||
| 				if (copy_to_user(buf, &val, 2)) | 				if (copy_to_user(buf, &val, 2)) | ||||||
| 					goto out; | 					return -EFAULT; | ||||||
| 			} | 			} | ||||||
| 
 | 
 | ||||||
| 			filled = 2; | 			filled = 2; | ||||||
|  | @ -227,43 +80,159 @@ ssize_t vfio_pci_mem_readwrite(struct vfio_pci_device *vdev, char __user *buf, | ||||||
| 
 | 
 | ||||||
| 			if (iswrite) { | 			if (iswrite) { | ||||||
| 				if (copy_from_user(&val, buf, 1)) | 				if (copy_from_user(&val, buf, 1)) | ||||||
| 					goto out; | 					return -EFAULT; | ||||||
| 
 | 
 | ||||||
| 				iowrite8(val, io + pos); | 				iowrite8(val, io + off); | ||||||
| 			} else { | 			} else { | ||||||
| 				val = ioread8(io + pos); | 				val = ioread8(io + off); | ||||||
| 
 | 
 | ||||||
| 				if (copy_to_user(buf, &val, 1)) | 				if (copy_to_user(buf, &val, 1)) | ||||||
| 					goto out; | 					return -EFAULT; | ||||||
| 			} | 			} | ||||||
| 
 | 
 | ||||||
| 			filled = 1; | 			filled = 1; | ||||||
| 		} else { | 		} else { | ||||||
| 			/* Drop writes, fill reads with FF */ | 			/* Fill reads with -1, drop writes */ | ||||||
| 			filled = min((size_t)(x_end - pos), count); | 			filled = min(count, (size_t)(x_end - off)); | ||||||
| 			if (!iswrite) { | 			if (!iswrite) { | ||||||
| 				char val = 0xFF; | 				u8 val = 0xFF; | ||||||
| 				size_t i; | 				size_t i; | ||||||
| 
 | 
 | ||||||
| 				for (i = 0; i < filled; i++) { | 				for (i = 0; i < filled; i++) | ||||||
| 					if (put_user(val, buf + i)) | 					if (copy_to_user(buf + i, &val, 1)) | ||||||
| 						goto out; | 						return -EFAULT; | ||||||
| 			} | 			} | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		count -= filled; | 		count -= filled; | ||||||
| 		done += filled; | 		done += filled; | ||||||
|  | 		off += filled; | ||||||
| 		buf += filled; | 		buf += filled; | ||||||
| 		pos += filled; |  | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
|  | 	return done; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | ssize_t vfio_pci_bar_rw(struct vfio_pci_device *vdev, char __user *buf, | ||||||
|  | 			size_t count, loff_t *ppos, bool iswrite) | ||||||
|  | { | ||||||
|  | 	struct pci_dev *pdev = vdev->pdev; | ||||||
|  | 	loff_t pos = *ppos & VFIO_PCI_OFFSET_MASK; | ||||||
|  | 	int bar = VFIO_PCI_OFFSET_TO_INDEX(*ppos); | ||||||
|  | 	size_t x_start = 0, x_end = 0; | ||||||
|  | 	resource_size_t end; | ||||||
|  | 	void __iomem *io; | ||||||
|  | 	ssize_t done; | ||||||
|  | 
 | ||||||
|  | 	if (!pci_resource_start(pdev, bar)) | ||||||
|  | 		return -EINVAL; | ||||||
|  | 
 | ||||||
|  | 	end = pci_resource_len(pdev, bar); | ||||||
|  | 
 | ||||||
|  | 	if (pos >= end) | ||||||
|  | 		return -EINVAL; | ||||||
|  | 
 | ||||||
|  | 	count = min(count, (size_t)(end - pos)); | ||||||
|  | 
 | ||||||
|  | 	if (bar == PCI_ROM_RESOURCE) { | ||||||
|  | 		/*
 | ||||||
|  | 		 * The ROM can fill less space than the BAR, so we start the | ||||||
|  | 		 * excluded range at the end of the actual ROM.  This makes | ||||||
|  | 		 * filling large ROM BARs much faster. | ||||||
|  | 		 */ | ||||||
|  | 		io = pci_map_rom(pdev, &x_start); | ||||||
|  | 		if (!io) | ||||||
|  | 			return -ENOMEM; | ||||||
|  | 		x_end = end; | ||||||
|  | 	} else if (!vdev->barmap[bar]) { | ||||||
|  | 		int ret; | ||||||
|  | 
 | ||||||
|  | 		ret = pci_request_selected_regions(pdev, 1 << bar, "vfio"); | ||||||
|  | 		if (ret) | ||||||
|  | 			return ret; | ||||||
|  | 
 | ||||||
|  | 		io = pci_iomap(pdev, bar, 0); | ||||||
|  | 		if (!io) { | ||||||
|  | 			pci_release_selected_regions(pdev, 1 << bar); | ||||||
|  | 			return -ENOMEM; | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		vdev->barmap[bar] = io; | ||||||
|  | 	} else | ||||||
|  | 		io = vdev->barmap[bar]; | ||||||
|  | 
 | ||||||
|  | 	if (bar == vdev->msix_bar) { | ||||||
|  | 		x_start = vdev->msix_offset; | ||||||
|  | 		x_end = vdev->msix_offset + vdev->msix_size; | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	done = do_io_rw(io, buf, pos, count, x_start, x_end, iswrite); | ||||||
|  | 
 | ||||||
|  | 	if (done >= 0) | ||||||
| 		*ppos += done; | 		*ppos += done; | ||||||
| 
 | 
 | ||||||
| out: |  | ||||||
| 	if (bar == PCI_ROM_RESOURCE) | 	if (bar == PCI_ROM_RESOURCE) | ||||||
| 		pci_unmap_rom(pdev, io); | 		pci_unmap_rom(pdev, io); | ||||||
| 
 | 
 | ||||||
| 	return count ? -EFAULT : done; | 	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; | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -442,7 +442,7 @@ static struct vfio_device *vfio_group_get_device(struct vfio_group *group, | ||||||
|  * a device.  It's not always practical to leave a device within a group |  * a device.  It's not always practical to leave a device within a group | ||||||
|  * driverless as it could get re-bound to something unsafe. |  * driverless as it could get re-bound to something unsafe. | ||||||
|  */ |  */ | ||||||
| static const char * const vfio_driver_whitelist[] = { "pci-stub" }; | static const char * const vfio_driver_whitelist[] = { "pci-stub", "pcieport" }; | ||||||
| 
 | 
 | ||||||
| static bool vfio_whitelisted_driver(struct device_driver *drv) | static bool vfio_whitelisted_driver(struct device_driver *drv) | ||||||
| { | { | ||||||
|  | @ -642,33 +642,16 @@ int vfio_add_group_dev(struct device *dev, | ||||||
| } | } | ||||||
| EXPORT_SYMBOL_GPL(vfio_add_group_dev); | EXPORT_SYMBOL_GPL(vfio_add_group_dev); | ||||||
| 
 | 
 | ||||||
| /* Test whether a struct device is present in our tracking */ | /* Given a referenced group, check if it contains the device */ | ||||||
| static bool vfio_dev_present(struct device *dev) | static bool vfio_dev_present(struct vfio_group *group, struct device *dev) | ||||||
| { | { | ||||||
| 	struct iommu_group *iommu_group; |  | ||||||
| 	struct vfio_group *group; |  | ||||||
| 	struct vfio_device *device; | 	struct vfio_device *device; | ||||||
| 
 | 
 | ||||||
| 	iommu_group = iommu_group_get(dev); |  | ||||||
| 	if (!iommu_group) |  | ||||||
| 		return false; |  | ||||||
| 
 |  | ||||||
| 	group = vfio_group_get_from_iommu(iommu_group); |  | ||||||
| 	if (!group) { |  | ||||||
| 		iommu_group_put(iommu_group); |  | ||||||
| 		return false; |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	device = vfio_group_get_device(group, dev); | 	device = vfio_group_get_device(group, dev); | ||||||
| 	if (!device) { | 	if (!device) | ||||||
| 		vfio_group_put(group); |  | ||||||
| 		iommu_group_put(iommu_group); |  | ||||||
| 		return false; | 		return false; | ||||||
| 	} |  | ||||||
| 
 | 
 | ||||||
| 	vfio_device_put(device); | 	vfio_device_put(device); | ||||||
| 	vfio_group_put(group); |  | ||||||
| 	iommu_group_put(iommu_group); |  | ||||||
| 	return true; | 	return true; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | @ -682,10 +665,18 @@ void *vfio_del_group_dev(struct device *dev) | ||||||
| 	struct iommu_group *iommu_group = group->iommu_group; | 	struct iommu_group *iommu_group = group->iommu_group; | ||||||
| 	void *device_data = device->device_data; | 	void *device_data = device->device_data; | ||||||
| 
 | 
 | ||||||
|  | 	/*
 | ||||||
|  | 	 * The group exists so long as we have a device reference.  Get | ||||||
|  | 	 * a group reference and use it to scan for the device going away. | ||||||
|  | 	 */ | ||||||
|  | 	vfio_group_get(group); | ||||||
|  | 
 | ||||||
| 	vfio_device_put(device); | 	vfio_device_put(device); | ||||||
| 
 | 
 | ||||||
| 	/* TODO send a signal to encourage this to be released */ | 	/* TODO send a signal to encourage this to be released */ | ||||||
| 	wait_event(vfio.release_q, !vfio_dev_present(dev)); | 	wait_event(vfio.release_q, !vfio_dev_present(group, dev)); | ||||||
|  | 
 | ||||||
|  | 	vfio_group_put(group); | ||||||
| 
 | 
 | ||||||
| 	iommu_group_put(iommu_group); | 	iommu_group_put(iommu_group); | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -303,6 +303,15 @@ enum { | ||||||
| 	VFIO_PCI_BAR5_REGION_INDEX, | 	VFIO_PCI_BAR5_REGION_INDEX, | ||||||
| 	VFIO_PCI_ROM_REGION_INDEX, | 	VFIO_PCI_ROM_REGION_INDEX, | ||||||
| 	VFIO_PCI_CONFIG_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 | 	VFIO_PCI_NUM_REGIONS | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	 Linus Torvalds
				Linus Torvalds