Add a new test_dev domain op for driver to test the compatibility between
a domain and a device at the driver level, before calling into the actual
attachment/replacement of a domain. Support pasid for set_dev_pasid call.

Move existing core-level compatibility tests to a helper function. Invoke
it prior to:
 * __iommu_attach_device() or its wrapper __iommu_device_set_domain()
 * __iommu_set_group_pasid()

And keep them within the group->mutex, so drivers can simply move all the
sanity and compatibility tests from their attach_dev callbacks to the new
test_dev callbacks without concerning about a race condition.

This may be a public API someday for VFIO/IOMMUFD to run a list of attach
tests without doing any actual attachment, which may result in a list of
failed tests. So encourage drivers to avoid printks to prevent kernel log
spam.

Suggested-by: Jason Gunthorpe <[email protected]>
Signed-off-by: Nicolin Chen <[email protected]>
---
 include/linux/iommu.h |  17 +++++--
 drivers/iommu/iommu.c | 111 ++++++++++++++++++++++++++++++------------
 2 files changed, 93 insertions(+), 35 deletions(-)

diff --git a/include/linux/iommu.h b/include/linux/iommu.h
index 801b2bd9e8d49..2ec99502dc29c 100644
--- a/include/linux/iommu.h
+++ b/include/linux/iommu.h
@@ -714,7 +714,12 @@ struct iommu_ops {
 
 /**
  * struct iommu_domain_ops - domain specific operations
- * @attach_dev: attach an iommu domain to a device
+ * @test_dev: Test compatibility prior to an @attach_dev or @set_dev_pasid 
call.
+ *            A driver-level callback of this op should do a thorough sanity, 
to
+ *            make sure a device is compatible with the domain. So the 
following
+ *            @attach_dev and @set_dev_pasid functions would likely succeed 
with
+ *            only one exception due to a temporary failure like out of memory.
+ *            It's suggested to avoid the kernel prints in this op.
  *  Return:
  * * 0         - success
  * * EINVAL    - can indicate that device and domain are incompatible due to
@@ -722,11 +727,15 @@ struct iommu_ops {
  *               driver shouldn't log an error, since it is legitimate for a
  *               caller to test reuse of existing domains. Otherwise, it may
  *               still represent some other fundamental problem
- * * ENOMEM    - out of memory
- * * ENOSPC    - non-ENOMEM type of resource allocation failures
  * * EBUSY     - device is attached to a domain and cannot be changed
  * * ENODEV    - device specific errors, not able to be attached
  * * <others>  - treated as ENODEV by the caller. Use is discouraged
+ * @attach_dev: attach an iommu domain to a device
+ *  Return:
+ * * 0         - success
+ * * ENOMEM    - out of memory
+ * * ENOSPC    - non-ENOMEM type of resource allocation failures
+ * * <others>  - Use is discouraged
  * @set_dev_pasid: set or replace an iommu domain to a pasid of device. The 
pasid of
  *                 the device should be left in the old config in error case.
  * @map_pages: map a physically contiguous set of pages of the same size to
@@ -751,6 +760,8 @@ struct iommu_ops {
  * @free: Release the domain after use.
  */
 struct iommu_domain_ops {
+       int (*test_dev)(struct iommu_domain *domain, struct device *dev,
+                       ioasid_t pasid, struct iommu_domain *old);
        int (*attach_dev)(struct iommu_domain *domain, struct device *dev,
                          struct iommu_domain *old);
        int (*set_dev_pasid)(struct iommu_domain *domain, struct device *dev,
diff --git a/drivers/iommu/iommu.c b/drivers/iommu/iommu.c
index 170e522b5bda4..95f0e2898b6b5 100644
--- a/drivers/iommu/iommu.c
+++ b/drivers/iommu/iommu.c
@@ -99,6 +99,9 @@ static int bus_iommu_probe(const struct bus_type *bus);
 static int iommu_bus_notifier(struct notifier_block *nb,
                              unsigned long action, void *data);
 static void iommu_release_device(struct device *dev);
+static int __iommu_domain_test_device(struct iommu_domain *domain,
+                                     struct device *dev, ioasid_t pasid,
+                                     struct iommu_domain *old);
 static int __iommu_attach_device(struct iommu_domain *domain,
                                 struct device *dev, struct iommu_domain *old);
 static int __iommu_attach_group(struct iommu_domain *domain,
@@ -642,6 +645,10 @@ static int __iommu_probe_device(struct device *dev, struct 
list_head *group_list
        if (group->default_domain)
                iommu_create_device_direct_mappings(group->default_domain, dev);
        if (group->domain) {
+               ret = __iommu_domain_test_device(group->domain, dev,
+                                                IOMMU_NO_PASID, NULL);
+               if (ret)
+                       goto err_remove_gdev;
                ret = __iommu_device_set_domain(group, dev, group->domain, NULL,
                                                0);
                if (ret)
@@ -2185,6 +2192,8 @@ EXPORT_SYMBOL_GPL(iommu_attach_device);
 
 int iommu_deferred_attach(struct device *dev, struct iommu_domain *domain)
 {
+       int ret;
+
        /*
         * This is called on the dma mapping fast path so avoid locking. This is
         * racy, but we have an expectation that the driver will setup its DMAs
@@ -2195,6 +2204,10 @@ int iommu_deferred_attach(struct device *dev, struct 
iommu_domain *domain)
 
        guard(mutex)(&dev->iommu_group->mutex);
 
+       ret = __iommu_domain_test_device(domain, dev, IOMMU_NO_PASID, NULL);
+       if (ret)
+               return ret;
+
        return __iommu_attach_device(domain, dev, NULL);
 }
 
@@ -2262,6 +2275,53 @@ static bool domain_iommu_ops_compatible(const struct 
iommu_ops *ops,
        return false;
 }
 
+/*
+ * Test if a future attach for a domain to the device will always fail. This 
may
+ * indicate that the device and the domain are incompatible in some way. Actual
+ * attach may still fail for some temporary failure like out of memory.
+ *
+ * If pasid != IOMMU_NO_PASID, it is meant to test a future set_dev_pasid call.
+ */
+static int __iommu_domain_test_device(struct iommu_domain *domain,
+                                     struct device *dev, ioasid_t pasid,
+                                     struct iommu_domain *old)
+{
+       const struct iommu_ops *ops = dev_iommu_ops(dev);
+       struct iommu_group *group = dev->iommu_group;
+
+       lockdep_assert_held(&group->mutex);
+
+       if (!domain_iommu_ops_compatible(ops, domain))
+               return -EINVAL;
+
+       if (pasid != IOMMU_NO_PASID) {
+               struct group_device *gdev;
+
+               if (!domain->ops->set_dev_pasid || !ops->blocked_domain ||
+                   !ops->blocked_domain->ops->set_dev_pasid)
+                       return -EOPNOTSUPP;
+               /*
+                * Skip PASID validation for devices without PASID support
+                * (max_pasids = 0). These devices cannot issue transactions
+                * with PASID, so they don't affect group's PASID usage.
+                */
+               for_each_group_device(group, gdev) {
+                       if (gdev->dev->iommu->max_pasids > 0 &&
+                           pasid >= gdev->dev->iommu->max_pasids)
+                               return -EINVAL;
+               }
+       }
+
+       /*
+        * Domains that don't implement a test_dev callback function must have a
+        * simple domain attach behavior. The sanity above should be enough.
+        */
+       if (!domain->ops->test_dev)
+               return 0;
+
+       return domain->ops->test_dev(domain, dev, pasid, old);
+}
+
 static int __iommu_attach_group(struct iommu_domain *domain,
                                struct iommu_group *group)
 {
@@ -2272,8 +2332,7 @@ static int __iommu_attach_group(struct iommu_domain 
*domain,
                return -EBUSY;
 
        dev = iommu_group_first_dev(group);
-       if (!dev_has_iommu(dev) ||
-           !domain_iommu_ops_compatible(dev_iommu_ops(dev), domain))
+       if (!dev_has_iommu(dev))
                return -EINVAL;
 
        return __iommu_group_set_domain(group, domain);
@@ -2381,6 +2440,13 @@ static int __iommu_group_set_domain_internal(struct 
iommu_group *group,
        if (WARN_ON(!new_domain))
                return -EINVAL;
 
+       for_each_group_device(group, gdev) {
+               ret = __iommu_domain_test_device(new_domain, gdev->dev,
+                                                IOMMU_NO_PASID, group->domain);
+               if (ret)
+                       return ret;
+       }
+
        /*
         * Changing the domain is done by calling attach_dev() on the new
         * domain. This switch does not have to be atomic and DMA can be
@@ -3479,38 +3545,19 @@ int iommu_attach_device_pasid(struct iommu_domain 
*domain,
 {
        /* Caller must be a probed driver on dev */
        struct iommu_group *group = dev->iommu_group;
-       struct group_device *device;
-       const struct iommu_ops *ops;
        void *entry;
        int ret;
 
        if (!group)
                return -ENODEV;
-
-       ops = dev_iommu_ops(dev);
-
-       if (!domain->ops->set_dev_pasid ||
-           !ops->blocked_domain ||
-           !ops->blocked_domain->ops->set_dev_pasid)
-               return -EOPNOTSUPP;
-
-       if (!domain_iommu_ops_compatible(ops, domain) ||
-           pasid == IOMMU_NO_PASID)
+       if (pasid == IOMMU_NO_PASID)
                return -EINVAL;
 
        mutex_lock(&group->mutex);
-       for_each_group_device(group, device) {
-               /*
-                * Skip PASID validation for devices without PASID support
-                * (max_pasids = 0). These devices cannot issue transactions
-                * with PASID, so they don't affect group's PASID usage.
-                */
-               if ((device->dev->iommu->max_pasids > 0) &&
-                   (pasid >= device->dev->iommu->max_pasids)) {
-                       ret = -EINVAL;
-                       goto out_unlock;
-               }
-       }
+
+       ret = __iommu_domain_test_device(domain, dev, pasid, NULL);
+       if (ret)
+               goto out_unlock;
 
        entry = iommu_make_pasid_array_entry(domain, handle);
 
@@ -3573,12 +3620,7 @@ int iommu_replace_device_pasid(struct iommu_domain 
*domain,
 
        if (!group)
                return -ENODEV;
-
-       if (!domain->ops->set_dev_pasid)
-               return -EOPNOTSUPP;
-
-       if (!domain_iommu_ops_compatible(dev_iommu_ops(dev), domain) ||
-           pasid == IOMMU_NO_PASID || !handle)
+       if (pasid == IOMMU_NO_PASID || !handle)
                return -EINVAL;
 
        mutex_lock(&group->mutex);
@@ -3615,6 +3657,11 @@ int iommu_replace_device_pasid(struct iommu_domain 
*domain,
        ret = 0;
 
        if (curr_domain != domain) {
+               ret = __iommu_domain_test_device(domain, dev, pasid,
+                                                curr_domain);
+               if (ret)
+                       goto out_unlock;
+
                ret = __iommu_set_group_pasid(domain, group,
                                              pasid, curr_domain);
                if (ret)
-- 
2.43.0


Reply via email to