[PATCH v8 6/6] Add common functions for SET_IRQS and GET_REGION_INFO ioctls

Kirti Wankhede
2016-10-10T20:27:57Z

Add common functions for SET_IRQS and to add capability buffer for
GET_REGION_INFO ioctls

Signed-off-by: Kirti Wankhede <kwankhede@nvidia.com>
Signed-off-by: Neo Jia <cjia@nvidia.com>
Change-Id: Id9e976a2c08b9b2b37da77dac4365ae8f6024b4a
---
drivers/vfio/pci/vfio_pci.c | 103 +++++++++++++++------------------------
drivers/vfio/vfio.c | 116 ++++++++++++++++++++++++++++++++++++++++++++
include/linux/vfio.h | 7 +++
3 files changed, 162 insertions(+), 64 deletions(-)

diff --git a/drivers/vfio/pci/vfio_pci.c b/drivers/vfio/pci/vfio_pci.c
index 188b1ff03f5f..f312cbb0eebc 100644
--- a/drivers/vfio/pci/vfio_pci.c
+++ b/drivers/vfio/pci/vfio_pci.c
@@ -478,12 +478,12 @@ static int vfio_pci_for_each_slot_or_bus(struct pci_dev *pdev,
}

static int msix_sparse_mmap_cap(struct vfio_pci_device *vdev,
+ struct vfio_region_info *info,
struct vfio_info_cap *caps)
{
- struct vfio_info_cap_header *header;
struct vfio_region_info_cap_sparse_mmap *sparse;
size_t end, size;
- int nr_areas = 2, i = 0;
+ int nr_areas = 2, i = 0, ret;

end = pci_resource_len(vdev->pdev, vdev->msix_bar);

@@ -494,13 +494,10 @@ static int msix_sparse_mmap_cap(struct vfio_pci_device *vdev,

size = sizeof(*sparse) + (nr_areas * sizeof(*sparse->areas));

- header = vfio_info_cap_add(caps, size,
- VFIO_REGION_INFO_CAP_SPARSE_MMAP, 1);
- if (IS_ERR(header))
- return PTR_ERR(header);
+ sparse = kzalloc(size, GFP_KERNEL);
+ if (!sparse)
+ return -ENOMEM;

- sparse = container_of(header,
- struct vfio_region_info_cap_sparse_mmap, header);
sparse->nr_areas = nr_areas;

if (vdev->msix_offset & PAGE_MASK) {
@@ -516,24 +513,14 @@ static int msix_sparse_mmap_cap(struct vfio_pci_device *vdev,
i++;
}

- return 0;
-}
-
-static int region_type_cap(struct vfio_pci_device *vdev,
- struct vfio_info_cap *caps,
- unsigned int type, unsigned int subtype)
-{
- struct vfio_info_cap_header *header;
- struct vfio_region_info_cap_type *cap;
+ info->flags |= VFIO_REGION_INFO_FLAG_CAPS;

- header = vfio_info_cap_add(caps, sizeof(*cap),
- VFIO_REGION_INFO_CAP_TYPE, 1);
- if (IS_ERR(header))
- return PTR_ERR(header);
+ ret = vfio_info_add_capability(info, caps,
+ VFIO_REGION_INFO_CAP_SPARSE_MMAP, sparse);
+ kfree(sparse);

- cap = container_of(header, struct vfio_region_info_cap_type, header);
- cap->type = type;
- cap->subtype = subtype;
+ if (ret)
+ return ret;

return 0;
}
@@ -628,7 +615,8 @@ static long vfio_pci_ioctl(void *device_data,
IORESOURCE_MEM && info.size >= PAGE_SIZE) {
info.flags |= VFIO_REGION_INFO_FLAG_MMAP;
if (info.index == vdev->msix_bar) {
- ret = msix_sparse_mmap_cap(vdev, &caps);
+ ret = msix_sparse_mmap_cap(vdev, &info,
+ &caps);
if (ret)
return ret;
}
@@ -676,6 +664,9 @@ static long vfio_pci_ioctl(void *device_data,

break;
default:
+ {
+ struct vfio_region_info_cap_type cap_type;
+
if (info.index >=
VFIO_PCI_NUM_REGIONS + vdev->num_regions)
return -EINVAL;
@@ -684,29 +675,26 @@ static long vfio_pci_ioctl(void *device_data,

info.offset = VFIO_PCI_INDEX_TO_OFFSET(info.index);
info.size = vdev->region[i].size;
- info.flags = vdev->region[i].flags;
+ info.flags = vdev->region[i].flags |
+ VFIO_REGION_INFO_FLAG_CAPS;

- ret = region_type_cap(vdev, &caps,
- vdev->region[i].type,
- vdev->region[i].subtype);
+ cap_type.type = vdev->region[i].type;
+ cap_type.subtype = vdev->region[i].subtype;
+
+ ret = vfio_info_add_capability(&info, &caps,
+ VFIO_REGION_INFO_CAP_TYPE,
+ &cap_type);
if (ret)
return ret;
+
+ }
}

- if (caps.size) {
- info.flags |= VFIO_REGION_INFO_FLAG_CAPS;
- if (info.argsz < sizeof(info) + caps.size) {
- info.argsz = sizeof(info) + caps.size;
- info.cap_offset = 0;
- } else {
- vfio_info_cap_shift(&caps, sizeof(info));
- if (copy_to_user((void __user *)arg +
- sizeof(info), caps.buf,
- caps.size)) {
- kfree(caps.buf);
- return -EFAULT;
- }
- info.cap_offset = sizeof(info);
+ if (info.cap_offset) {
+ if (copy_to_user((void __user *)arg + info.cap_offset,
+ caps.buf, caps.size)) {
+ kfree(caps.buf);
+ return -EFAULT;
}

kfree(caps.buf);
@@ -754,35 +742,22 @@ static long vfio_pci_ioctl(void *device_data,
} else if (cmd == VFIO_DEVICE_SET_IRQS) {
struct vfio_irq_set hdr;
u8 *data = NULL;
- int ret = 0;
+ int max, ret = 0, data_size = 0;

minsz = offsetofend(struct vfio_irq_set, count);

if (copy_from_user(&hdr, (void __user *)arg, minsz))
return -EFAULT;

- if (hdr.argsz < minsz || hdr.index >= VFIO_PCI_NUM_IRQS ||
- hdr.flags & ~(VFIO_IRQ_SET_DATA_TYPE_MASK |
- VFIO_IRQ_SET_ACTION_TYPE_MASK))
- return -EINVAL;
-
- if (!(hdr.flags & VFIO_IRQ_SET_DATA_NONE)) {
- size_t size;
- int max = vfio_pci_get_irq_count(vdev, hdr.index);
+ max = vfio_pci_get_irq_count(vdev, hdr.index);

- if (hdr.flags & VFIO_IRQ_SET_DATA_BOOL)
- size = sizeof(uint8_t);
- else if (hdr.flags & VFIO_IRQ_SET_DATA_EVENTFD)
- size = sizeof(int32_t);
- else
- return -EINVAL;
-
- if (hdr.argsz - minsz < hdr.count * size ||
- hdr.start >= max || hdr.start + hdr.count > max)
- return -EINVAL;
+ ret = vfio_set_irqs_validate_and_prepare(&hdr, max, &data_size);
+ if (ret)
+ return ret;

+ if (data_size) {
data = memdup_user((void __user *)(arg + minsz),
- hdr.count * size);
+ data_size);
if (IS_ERR(data))
return PTR_ERR(data);
}
@@ -790,7 +765,7 @@ static long vfio_pci_ioctl(void *device_data,
mutex_lock(&vdev->igate);

ret = vfio_pci_set_irqs_ioctl(vdev, hdr.flags, hdr.index,
- hdr.start, hdr.count, data);
+ hdr.start, hdr.count, data);

mutex_unlock(&vdev->igate);
kfree(data);
diff --git a/drivers/vfio/vfio.c b/drivers/vfio/vfio.c
index e3e342861e04..0185d5fb2c85 100644
--- a/drivers/vfio/vfio.c
+++ b/drivers/vfio/vfio.c
@@ -1782,6 +1782,122 @@ void vfio_info_cap_shift(struct vfio_info_cap *caps, size_t offset)
}
EXPORT_SYMBOL_GPL(vfio_info_cap_shift);

+static int sparse_mmap_cap(struct vfio_info_cap *caps, void *cap_type)
+{
+ struct vfio_info_cap_header *header;
+ struct vfio_region_info_cap_sparse_mmap *sparse_cap, *sparse = cap_type;
+ size_t size;
+
+ size = sizeof(*sparse) + sparse->nr_areas * sizeof(*sparse->areas);
+ header = vfio_info_cap_add(caps, size,
+ VFIO_REGION_INFO_CAP_SPARSE_MMAP, 1);
+ if (IS_ERR(header))
+ return PTR_ERR(header);
+
+ sparse_cap = container_of(header,
+ struct vfio_region_info_cap_sparse_mmap, header);
+ sparse_cap->nr_areas = sparse->nr_areas;
+ memcpy(sparse_cap->areas, sparse->areas,
+ sparse->nr_areas * sizeof(*sparse->areas));
+ return 0;
+}
+
+static int region_type_cap(struct vfio_info_cap *caps, void *cap_type)
+{
+ struct vfio_info_cap_header *header;
+ struct vfio_region_info_cap_type *type_cap, *cap = cap_type;
+
+ header = vfio_info_cap_add(caps, sizeof(*cap),
+ VFIO_REGION_INFO_CAP_TYPE, 1);
+ if (IS_ERR(header))
+ return PTR_ERR(header);
+
+ type_cap = container_of(header, struct vfio_region_info_cap_type,
+ header);
+ type_cap->type = cap->type;
+ type_cap->subtype = cap->subtype;
+ return 0;
+}
+
+int vfio_info_add_capability(struct vfio_region_info *info,
+ struct vfio_info_cap *caps,
+ int cap_type_id,
+ void *cap_type)
+{
+ int ret;
+
+ if (!(info->flags & VFIO_REGION_INFO_FLAG_CAPS) || !cap_type)
+ return 0;
+
+ switch (cap_type_id) {
+ case VFIO_REGION_INFO_CAP_SPARSE_MMAP:
+ ret = sparse_mmap_cap(caps, cap_type);
+ if (ret)
+ return ret;
+ break;
+
+ case VFIO_REGION_INFO_CAP_TYPE:
+ ret = region_type_cap(caps, cap_type);
+ if (ret)
+ return ret;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ if (caps->size) {
+ if (info->argsz < sizeof(*info) + caps->size) {
+ info->argsz = sizeof(*info) + caps->size;
+ info->cap_offset = 0;
+ } else {
+ vfio_info_cap_shift(caps, sizeof(*info));
+ info->cap_offset = sizeof(*info);
+ }
+ }
+ return 0;
+}
+EXPORT_SYMBOL(vfio_info_add_capability);
+
+int vfio_set_irqs_validate_and_prepare(struct vfio_irq_set *hdr, int num_irqs,
+ int *data_size)
+{
+ unsigned long minsz;
+
+ minsz = offsetofend(struct vfio_irq_set, count);
+
+ if ((hdr->argsz < minsz) || (hdr->index >= VFIO_PCI_NUM_IRQS) ||
+ (hdr->flags & ~(VFIO_IRQ_SET_DATA_TYPE_MASK |
+ VFIO_IRQ_SET_ACTION_TYPE_MASK)))
+ return -EINVAL;
+
+ if (data_size)
+ *data_size = 0;
+
+ if (!(hdr->flags & VFIO_IRQ_SET_DATA_NONE)) {
+ size_t size;
+
+ if (hdr->flags & VFIO_IRQ_SET_DATA_BOOL)
+ size = sizeof(uint8_t);
+ else if (hdr->flags & VFIO_IRQ_SET_DATA_EVENTFD)
+ size = sizeof(int32_t);
+ else
+ return -EINVAL;
+
+ if ((hdr->argsz - minsz < hdr->count * size) ||
+ (hdr->start >= num_irqs) ||
+ (hdr->start + hdr->count > num_irqs))
+ return -EINVAL;
+
+ if (!data_size)
+ return -EINVAL;
+
+ *data_size = hdr->count * size;
+ }
+
+ return 0;
+}
+EXPORT_SYMBOL(vfio_set_irqs_validate_and_prepare);
+
static struct vfio_group *vfio_group_from_dev(struct device *dev)
{
struct vfio_device *device;
diff --git a/include/linux/vfio.h b/include/linux/vfio.h
index 0bd25ba6223d..5641dab72ded 100644
--- a/include/linux/vfio.h
+++ b/include/linux/vfio.h
@@ -108,6 +108,13 @@ extern struct vfio_info_cap_header *vfio_info_cap_add(
struct vfio_info_cap *caps, size_t size, u16 id, u16 version);
extern void vfio_info_cap_shift(struct vfio_info_cap *caps, size_t offset);

+extern int vfio_info_add_capability(struct vfio_region_info *info,
+ struct vfio_info_cap *caps,
+ int cap_type_id, void *cap_type);
+
+extern int vfio_set_irqs_validate_and_prepare(struct vfio_irq_set *hdr,
+ int num_irqs, int *data_size);
+
struct pci_dev;
#ifdef CONFIG_EEH
extern void vfio_spapr_pci_eeh_open(struct pci_dev *pdev);
--
2.7.0