Add the rt linux 4.1.3-rt3 as base
[kvmfornfv.git] / kernel / drivers / vfio / platform / vfio_platform_common.c
diff --git a/kernel/drivers/vfio/platform/vfio_platform_common.c b/kernel/drivers/vfio/platform/vfio_platform_common.c
new file mode 100644 (file)
index 0000000..abcff7a
--- /dev/null
@@ -0,0 +1,521 @@
+/*
+ * Copyright (C) 2013 - Virtual Open Systems
+ * Author: Antonios Motakis <a.motakis@virtualopensystems.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License, version 2, as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ */
+
+#include <linux/device.h>
+#include <linux/iommu.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/slab.h>
+#include <linux/types.h>
+#include <linux/uaccess.h>
+#include <linux/vfio.h>
+
+#include "vfio_platform_private.h"
+
+static DEFINE_MUTEX(driver_lock);
+
+static int vfio_platform_regions_init(struct vfio_platform_device *vdev)
+{
+       int cnt = 0, i;
+
+       while (vdev->get_resource(vdev, cnt))
+               cnt++;
+
+       vdev->regions = kcalloc(cnt, sizeof(struct vfio_platform_region),
+                               GFP_KERNEL);
+       if (!vdev->regions)
+               return -ENOMEM;
+
+       for (i = 0; i < cnt;  i++) {
+               struct resource *res =
+                       vdev->get_resource(vdev, i);
+
+               if (!res)
+                       goto err;
+
+               vdev->regions[i].addr = res->start;
+               vdev->regions[i].size = resource_size(res);
+               vdev->regions[i].flags = 0;
+
+               switch (resource_type(res)) {
+               case IORESOURCE_MEM:
+                       vdev->regions[i].type = VFIO_PLATFORM_REGION_TYPE_MMIO;
+                       vdev->regions[i].flags |= VFIO_REGION_INFO_FLAG_READ;
+                       if (!(res->flags & IORESOURCE_READONLY))
+                               vdev->regions[i].flags |=
+                                       VFIO_REGION_INFO_FLAG_WRITE;
+
+                       /*
+                        * Only regions addressed with PAGE granularity may be
+                        * MMAPed securely.
+                        */
+                       if (!(vdev->regions[i].addr & ~PAGE_MASK) &&
+                                       !(vdev->regions[i].size & ~PAGE_MASK))
+                               vdev->regions[i].flags |=
+                                       VFIO_REGION_INFO_FLAG_MMAP;
+
+                       break;
+               case IORESOURCE_IO:
+                       vdev->regions[i].type = VFIO_PLATFORM_REGION_TYPE_PIO;
+                       break;
+               default:
+                       goto err;
+               }
+       }
+
+       vdev->num_regions = cnt;
+
+       return 0;
+err:
+       kfree(vdev->regions);
+       return -EINVAL;
+}
+
+static void vfio_platform_regions_cleanup(struct vfio_platform_device *vdev)
+{
+       int i;
+
+       for (i = 0; i < vdev->num_regions; i++)
+               iounmap(vdev->regions[i].ioaddr);
+
+       vdev->num_regions = 0;
+       kfree(vdev->regions);
+}
+
+static void vfio_platform_release(void *device_data)
+{
+       struct vfio_platform_device *vdev = device_data;
+
+       mutex_lock(&driver_lock);
+
+       if (!(--vdev->refcnt)) {
+               vfio_platform_regions_cleanup(vdev);
+               vfio_platform_irq_cleanup(vdev);
+       }
+
+       mutex_unlock(&driver_lock);
+
+       module_put(THIS_MODULE);
+}
+
+static int vfio_platform_open(void *device_data)
+{
+       struct vfio_platform_device *vdev = device_data;
+       int ret;
+
+       if (!try_module_get(THIS_MODULE))
+               return -ENODEV;
+
+       mutex_lock(&driver_lock);
+
+       if (!vdev->refcnt) {
+               ret = vfio_platform_regions_init(vdev);
+               if (ret)
+                       goto err_reg;
+
+               ret = vfio_platform_irq_init(vdev);
+               if (ret)
+                       goto err_irq;
+       }
+
+       vdev->refcnt++;
+
+       mutex_unlock(&driver_lock);
+       return 0;
+
+err_irq:
+       vfio_platform_regions_cleanup(vdev);
+err_reg:
+       mutex_unlock(&driver_lock);
+       module_put(THIS_MODULE);
+       return ret;
+}
+
+static long vfio_platform_ioctl(void *device_data,
+                               unsigned int cmd, unsigned long arg)
+{
+       struct vfio_platform_device *vdev = device_data;
+       unsigned long minsz;
+
+       if (cmd == VFIO_DEVICE_GET_INFO) {
+               struct vfio_device_info info;
+
+               minsz = offsetofend(struct vfio_device_info, num_irqs);
+
+               if (copy_from_user(&info, (void __user *)arg, minsz))
+                       return -EFAULT;
+
+               if (info.argsz < minsz)
+                       return -EINVAL;
+
+               info.flags = vdev->flags;
+               info.num_regions = vdev->num_regions;
+               info.num_irqs = vdev->num_irqs;
+
+               return copy_to_user((void __user *)arg, &info, minsz);
+
+       } else if (cmd == VFIO_DEVICE_GET_REGION_INFO) {
+               struct vfio_region_info info;
+
+               minsz = offsetofend(struct vfio_region_info, offset);
+
+               if (copy_from_user(&info, (void __user *)arg, minsz))
+                       return -EFAULT;
+
+               if (info.argsz < minsz)
+                       return -EINVAL;
+
+               if (info.index >= vdev->num_regions)
+                       return -EINVAL;
+
+               /* map offset to the physical address  */
+               info.offset = VFIO_PLATFORM_INDEX_TO_OFFSET(info.index);
+               info.size = vdev->regions[info.index].size;
+               info.flags = vdev->regions[info.index].flags;
+
+               return copy_to_user((void __user *)arg, &info, minsz);
+
+       } else if (cmd == VFIO_DEVICE_GET_IRQ_INFO) {
+               struct vfio_irq_info info;
+
+               minsz = offsetofend(struct vfio_irq_info, count);
+
+               if (copy_from_user(&info, (void __user *)arg, minsz))
+                       return -EFAULT;
+
+               if (info.argsz < minsz)
+                       return -EINVAL;
+
+               if (info.index >= vdev->num_irqs)
+                       return -EINVAL;
+
+               info.flags = vdev->irqs[info.index].flags;
+               info.count = vdev->irqs[info.index].count;
+
+               return copy_to_user((void __user *)arg, &info, minsz);
+
+       } else if (cmd == VFIO_DEVICE_SET_IRQS) {
+               struct vfio_irq_set hdr;
+               u8 *data = NULL;
+               int ret = 0;
+
+               minsz = offsetofend(struct vfio_irq_set, count);
+
+               if (copy_from_user(&hdr, (void __user *)arg, minsz))
+                       return -EFAULT;
+
+               if (hdr.argsz < minsz)
+                       return -EINVAL;
+
+               if (hdr.index >= vdev->num_irqs)
+                       return -EINVAL;
+
+               if (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;
+
+                       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 < size)
+                               return -EINVAL;
+
+                       data = memdup_user((void __user *)(arg + minsz), size);
+                       if (IS_ERR(data))
+                               return PTR_ERR(data);
+               }
+
+               mutex_lock(&vdev->igate);
+
+               ret = vfio_platform_set_irqs_ioctl(vdev, hdr.flags, hdr.index,
+                                                  hdr.start, hdr.count, data);
+               mutex_unlock(&vdev->igate);
+               kfree(data);
+
+               return ret;
+
+       } else if (cmd == VFIO_DEVICE_RESET)
+               return -EINVAL;
+
+       return -ENOTTY;
+}
+
+static ssize_t vfio_platform_read_mmio(struct vfio_platform_region reg,
+                                      char __user *buf, size_t count,
+                                      loff_t off)
+{
+       unsigned int done = 0;
+
+       if (!reg.ioaddr) {
+               reg.ioaddr =
+                       ioremap_nocache(reg.addr, reg.size);
+
+               if (!reg.ioaddr)
+                       return -ENOMEM;
+       }
+
+       while (count) {
+               size_t filled;
+
+               if (count >= 4 && !(off % 4)) {
+                       u32 val;
+
+                       val = ioread32(reg.ioaddr + off);
+                       if (copy_to_user(buf, &val, 4))
+                               goto err;
+
+                       filled = 4;
+               } else if (count >= 2 && !(off % 2)) {
+                       u16 val;
+
+                       val = ioread16(reg.ioaddr + off);
+                       if (copy_to_user(buf, &val, 2))
+                               goto err;
+
+                       filled = 2;
+               } else {
+                       u8 val;
+
+                       val = ioread8(reg.ioaddr + off);
+                       if (copy_to_user(buf, &val, 1))
+                               goto err;
+
+                       filled = 1;
+               }
+
+
+               count -= filled;
+               done += filled;
+               off += filled;
+               buf += filled;
+       }
+
+       return done;
+err:
+       return -EFAULT;
+}
+
+static ssize_t vfio_platform_read(void *device_data, char __user *buf,
+                                 size_t count, loff_t *ppos)
+{
+       struct vfio_platform_device *vdev = device_data;
+       unsigned int index = VFIO_PLATFORM_OFFSET_TO_INDEX(*ppos);
+       loff_t off = *ppos & VFIO_PLATFORM_OFFSET_MASK;
+
+       if (index >= vdev->num_regions)
+               return -EINVAL;
+
+       if (!(vdev->regions[index].flags & VFIO_REGION_INFO_FLAG_READ))
+               return -EINVAL;
+
+       if (vdev->regions[index].type & VFIO_PLATFORM_REGION_TYPE_MMIO)
+               return vfio_platform_read_mmio(vdev->regions[index],
+                                                       buf, count, off);
+       else if (vdev->regions[index].type & VFIO_PLATFORM_REGION_TYPE_PIO)
+               return -EINVAL; /* not implemented */
+
+       return -EINVAL;
+}
+
+static ssize_t vfio_platform_write_mmio(struct vfio_platform_region reg,
+                                       const char __user *buf, size_t count,
+                                       loff_t off)
+{
+       unsigned int done = 0;
+
+       if (!reg.ioaddr) {
+               reg.ioaddr =
+                       ioremap_nocache(reg.addr, reg.size);
+
+               if (!reg.ioaddr)
+                       return -ENOMEM;
+       }
+
+       while (count) {
+               size_t filled;
+
+               if (count >= 4 && !(off % 4)) {
+                       u32 val;
+
+                       if (copy_from_user(&val, buf, 4))
+                               goto err;
+                       iowrite32(val, reg.ioaddr + off);
+
+                       filled = 4;
+               } else if (count >= 2 && !(off % 2)) {
+                       u16 val;
+
+                       if (copy_from_user(&val, buf, 2))
+                               goto err;
+                       iowrite16(val, reg.ioaddr + off);
+
+                       filled = 2;
+               } else {
+                       u8 val;
+
+                       if (copy_from_user(&val, buf, 1))
+                               goto err;
+                       iowrite8(val, reg.ioaddr + off);
+
+                       filled = 1;
+               }
+
+               count -= filled;
+               done += filled;
+               off += filled;
+               buf += filled;
+       }
+
+       return done;
+err:
+       return -EFAULT;
+}
+
+static ssize_t vfio_platform_write(void *device_data, const char __user *buf,
+                                  size_t count, loff_t *ppos)
+{
+       struct vfio_platform_device *vdev = device_data;
+       unsigned int index = VFIO_PLATFORM_OFFSET_TO_INDEX(*ppos);
+       loff_t off = *ppos & VFIO_PLATFORM_OFFSET_MASK;
+
+       if (index >= vdev->num_regions)
+               return -EINVAL;
+
+       if (!(vdev->regions[index].flags & VFIO_REGION_INFO_FLAG_WRITE))
+               return -EINVAL;
+
+       if (vdev->regions[index].type & VFIO_PLATFORM_REGION_TYPE_MMIO)
+               return vfio_platform_write_mmio(vdev->regions[index],
+                                                       buf, count, off);
+       else if (vdev->regions[index].type & VFIO_PLATFORM_REGION_TYPE_PIO)
+               return -EINVAL; /* not implemented */
+
+       return -EINVAL;
+}
+
+static int vfio_platform_mmap_mmio(struct vfio_platform_region region,
+                                  struct vm_area_struct *vma)
+{
+       u64 req_len, pgoff, req_start;
+
+       req_len = vma->vm_end - vma->vm_start;
+       pgoff = vma->vm_pgoff &
+               ((1U << (VFIO_PLATFORM_OFFSET_SHIFT - PAGE_SHIFT)) - 1);
+       req_start = pgoff << PAGE_SHIFT;
+
+       if (region.size < PAGE_SIZE || req_start + req_len > region.size)
+               return -EINVAL;
+
+       vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot);
+       vma->vm_pgoff = (region.addr >> PAGE_SHIFT) + pgoff;
+
+       return remap_pfn_range(vma, vma->vm_start, vma->vm_pgoff,
+                              req_len, vma->vm_page_prot);
+}
+
+static int vfio_platform_mmap(void *device_data, struct vm_area_struct *vma)
+{
+       struct vfio_platform_device *vdev = device_data;
+       unsigned int index;
+
+       index = vma->vm_pgoff >> (VFIO_PLATFORM_OFFSET_SHIFT - PAGE_SHIFT);
+
+       if (vma->vm_end < vma->vm_start)
+               return -EINVAL;
+       if (!(vma->vm_flags & VM_SHARED))
+               return -EINVAL;
+       if (index >= vdev->num_regions)
+               return -EINVAL;
+       if (vma->vm_start & ~PAGE_MASK)
+               return -EINVAL;
+       if (vma->vm_end & ~PAGE_MASK)
+               return -EINVAL;
+
+       if (!(vdev->regions[index].flags & VFIO_REGION_INFO_FLAG_MMAP))
+               return -EINVAL;
+
+       if (!(vdev->regions[index].flags & VFIO_REGION_INFO_FLAG_READ)
+                       && (vma->vm_flags & VM_READ))
+               return -EINVAL;
+
+       if (!(vdev->regions[index].flags & VFIO_REGION_INFO_FLAG_WRITE)
+                       && (vma->vm_flags & VM_WRITE))
+               return -EINVAL;
+
+       vma->vm_private_data = vdev;
+
+       if (vdev->regions[index].type & VFIO_PLATFORM_REGION_TYPE_MMIO)
+               return vfio_platform_mmap_mmio(vdev->regions[index], vma);
+
+       else if (vdev->regions[index].type & VFIO_PLATFORM_REGION_TYPE_PIO)
+               return -EINVAL; /* not implemented */
+
+       return -EINVAL;
+}
+
+static const struct vfio_device_ops vfio_platform_ops = {
+       .name           = "vfio-platform",
+       .open           = vfio_platform_open,
+       .release        = vfio_platform_release,
+       .ioctl          = vfio_platform_ioctl,
+       .read           = vfio_platform_read,
+       .write          = vfio_platform_write,
+       .mmap           = vfio_platform_mmap,
+};
+
+int vfio_platform_probe_common(struct vfio_platform_device *vdev,
+                              struct device *dev)
+{
+       struct iommu_group *group;
+       int ret;
+
+       if (!vdev)
+               return -EINVAL;
+
+       group = iommu_group_get(dev);
+       if (!group) {
+               pr_err("VFIO: No IOMMU group for device %s\n", vdev->name);
+               return -EINVAL;
+       }
+
+       ret = vfio_add_group_dev(dev, &vfio_platform_ops, vdev);
+       if (ret) {
+               iommu_group_put(group);
+               return ret;
+       }
+
+       mutex_init(&vdev->igate);
+
+       return 0;
+}
+EXPORT_SYMBOL_GPL(vfio_platform_probe_common);
+
+struct vfio_platform_device *vfio_platform_remove_common(struct device *dev)
+{
+       struct vfio_platform_device *vdev;
+
+       vdev = vfio_del_group_dev(dev);
+       if (vdev)
+               iommu_group_put(dev->iommu_group);
+
+       return vdev;
+}
+EXPORT_SYMBOL_GPL(vfio_platform_remove_common);