Merge tag 'for-linus' of git://git.kernel.org/pub/scm/linux/kernel/git/rdma/rdma
[linux-2.6-microblaze.git] / drivers / iommu / iommu.c
index 0e4fbdc..8c470f4 100644 (file)
@@ -762,7 +762,7 @@ static int iommu_create_device_direct_mappings(struct iommu_group *group,
 
        }
 
-       iommu_flush_tlb_all(domain);
+       iommu_flush_iotlb_all(domain);
 
 out:
        iommu_put_resv_regions(dev, &mappings);
@@ -1961,25 +1961,188 @@ out_unlock:
 }
 EXPORT_SYMBOL_GPL(iommu_attach_device);
 
-int iommu_cache_invalidate(struct iommu_domain *domain, struct device *dev,
-                          struct iommu_cache_invalidate_info *inv_info)
+/*
+ * Check flags and other user provided data for valid combinations. We also
+ * make sure no reserved fields or unused flags are set. This is to ensure
+ * not breaking userspace in the future when these fields or flags are used.
+ */
+static int iommu_check_cache_invl_data(struct iommu_cache_invalidate_info *info)
 {
+       u32 mask;
+       int i;
+
+       if (info->version != IOMMU_CACHE_INVALIDATE_INFO_VERSION_1)
+               return -EINVAL;
+
+       mask = (1 << IOMMU_CACHE_INV_TYPE_NR) - 1;
+       if (info->cache & ~mask)
+               return -EINVAL;
+
+       if (info->granularity >= IOMMU_INV_GRANU_NR)
+               return -EINVAL;
+
+       switch (info->granularity) {
+       case IOMMU_INV_GRANU_ADDR:
+               if (info->cache & IOMMU_CACHE_INV_TYPE_PASID)
+                       return -EINVAL;
+
+               mask = IOMMU_INV_ADDR_FLAGS_PASID |
+                       IOMMU_INV_ADDR_FLAGS_ARCHID |
+                       IOMMU_INV_ADDR_FLAGS_LEAF;
+
+               if (info->granu.addr_info.flags & ~mask)
+                       return -EINVAL;
+               break;
+       case IOMMU_INV_GRANU_PASID:
+               mask = IOMMU_INV_PASID_FLAGS_PASID |
+                       IOMMU_INV_PASID_FLAGS_ARCHID;
+               if (info->granu.pasid_info.flags & ~mask)
+                       return -EINVAL;
+
+               break;
+       case IOMMU_INV_GRANU_DOMAIN:
+               if (info->cache & IOMMU_CACHE_INV_TYPE_DEV_IOTLB)
+                       return -EINVAL;
+               break;
+       default:
+               return -EINVAL;
+       }
+
+       /* Check reserved padding fields */
+       for (i = 0; i < sizeof(info->padding); i++) {
+               if (info->padding[i])
+                       return -EINVAL;
+       }
+
+       return 0;
+}
+
+int iommu_uapi_cache_invalidate(struct iommu_domain *domain, struct device *dev,
+                               void __user *uinfo)
+{
+       struct iommu_cache_invalidate_info inv_info = { 0 };
+       u32 minsz;
+       int ret;
+
        if (unlikely(!domain->ops->cache_invalidate))
                return -ENODEV;
 
-       return domain->ops->cache_invalidate(domain, dev, inv_info);
+       /*
+        * No new spaces can be added before the variable sized union, the
+        * minimum size is the offset to the union.
+        */
+       minsz = offsetof(struct iommu_cache_invalidate_info, granu);
+
+       /* Copy minsz from user to get flags and argsz */
+       if (copy_from_user(&inv_info, uinfo, minsz))
+               return -EFAULT;
+
+       /* Fields before the variable size union are mandatory */
+       if (inv_info.argsz < minsz)
+               return -EINVAL;
+
+       /* PASID and address granu require additional info beyond minsz */
+       if (inv_info.granularity == IOMMU_INV_GRANU_PASID &&
+           inv_info.argsz < offsetofend(struct iommu_cache_invalidate_info, granu.pasid_info))
+               return -EINVAL;
+
+       if (inv_info.granularity == IOMMU_INV_GRANU_ADDR &&
+           inv_info.argsz < offsetofend(struct iommu_cache_invalidate_info, granu.addr_info))
+               return -EINVAL;
+
+       /*
+        * User might be using a newer UAPI header which has a larger data
+        * size, we shall support the existing flags within the current
+        * size. Copy the remaining user data _after_ minsz but not more
+        * than the current kernel supported size.
+        */
+       if (copy_from_user((void *)&inv_info + minsz, uinfo + minsz,
+                          min_t(u32, inv_info.argsz, sizeof(inv_info)) - minsz))
+               return -EFAULT;
+
+       /* Now the argsz is validated, check the content */
+       ret = iommu_check_cache_invl_data(&inv_info);
+       if (ret)
+               return ret;
+
+       return domain->ops->cache_invalidate(domain, dev, &inv_info);
 }
-EXPORT_SYMBOL_GPL(iommu_cache_invalidate);
+EXPORT_SYMBOL_GPL(iommu_uapi_cache_invalidate);
 
-int iommu_sva_bind_gpasid(struct iommu_domain *domain,
-                          struct device *dev, struct iommu_gpasid_bind_data *data)
+static int iommu_check_bind_data(struct iommu_gpasid_bind_data *data)
 {
+       u32 mask;
+       int i;
+
+       if (data->version != IOMMU_GPASID_BIND_VERSION_1)
+               return -EINVAL;
+
+       /* Check the range of supported formats */
+       if (data->format >= IOMMU_PASID_FORMAT_LAST)
+               return -EINVAL;
+
+       /* Check all flags */
+       mask = IOMMU_SVA_GPASID_VAL;
+       if (data->flags & ~mask)
+               return -EINVAL;
+
+       /* Check reserved padding fields */
+       for (i = 0; i < sizeof(data->padding); i++) {
+               if (data->padding[i])
+                       return -EINVAL;
+       }
+
+       return 0;
+}
+
+static int iommu_sva_prepare_bind_data(void __user *udata,
+                                      struct iommu_gpasid_bind_data *data)
+{
+       u32 minsz;
+
+       /*
+        * No new spaces can be added before the variable sized union, the
+        * minimum size is the offset to the union.
+        */
+       minsz = offsetof(struct iommu_gpasid_bind_data, vendor);
+
+       /* Copy minsz from user to get flags and argsz */
+       if (copy_from_user(data, udata, minsz))
+               return -EFAULT;
+
+       /* Fields before the variable size union are mandatory */
+       if (data->argsz < minsz)
+               return -EINVAL;
+       /*
+        * User might be using a newer UAPI header, we shall let IOMMU vendor
+        * driver decide on what size it needs. Since the guest PASID bind data
+        * can be vendor specific, larger argsz could be the result of extension
+        * for one vendor but it should not affect another vendor.
+        * Copy the remaining user data _after_ minsz
+        */
+       if (copy_from_user((void *)data + minsz, udata + minsz,
+                          min_t(u32, data->argsz, sizeof(*data)) - minsz))
+               return -EFAULT;
+
+       return iommu_check_bind_data(data);
+}
+
+int iommu_uapi_sva_bind_gpasid(struct iommu_domain *domain, struct device *dev,
+                              void __user *udata)
+{
+       struct iommu_gpasid_bind_data data = { 0 };
+       int ret;
+
        if (unlikely(!domain->ops->sva_bind_gpasid))
                return -ENODEV;
 
-       return domain->ops->sva_bind_gpasid(domain, dev, data);
+       ret = iommu_sva_prepare_bind_data(udata, &data);
+       if (ret)
+               return ret;
+
+       return domain->ops->sva_bind_gpasid(domain, dev, &data);
 }
-EXPORT_SYMBOL_GPL(iommu_sva_bind_gpasid);
+EXPORT_SYMBOL_GPL(iommu_uapi_sva_bind_gpasid);
 
 int iommu_sva_unbind_gpasid(struct iommu_domain *domain, struct device *dev,
                             ioasid_t pasid)
@@ -1991,6 +2154,23 @@ int iommu_sva_unbind_gpasid(struct iommu_domain *domain, struct device *dev,
 }
 EXPORT_SYMBOL_GPL(iommu_sva_unbind_gpasid);
 
+int iommu_uapi_sva_unbind_gpasid(struct iommu_domain *domain, struct device *dev,
+                                void __user *udata)
+{
+       struct iommu_gpasid_bind_data data = { 0 };
+       int ret;
+
+       if (unlikely(!domain->ops->sva_bind_gpasid))
+               return -ENODEV;
+
+       ret = iommu_sva_prepare_bind_data(udata, &data);
+       if (ret)
+               return ret;
+
+       return iommu_sva_unbind_gpasid(domain, dev, data.hpasid);
+}
+EXPORT_SYMBOL_GPL(iommu_uapi_sva_unbind_gpasid);
+
 static void __iommu_detach_device(struct iommu_domain *domain,
                                  struct device *dev)
 {
@@ -2316,7 +2496,7 @@ size_t iommu_unmap(struct iommu_domain *domain,
 
        iommu_iotlb_gather_init(&iotlb_gather);
        ret = __iommu_unmap(domain, iova, size, &iotlb_gather);
-       iommu_tlb_sync(domain, &iotlb_gather);
+       iommu_iotlb_sync(domain, &iotlb_gather);
 
        return ret;
 }