summaryrefslogtreecommitdiff
path: root/drivers/iommu/iommu.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/iommu/iommu.c')
-rw-r--r--drivers/iommu/iommu.c230
1 files changed, 229 insertions, 1 deletions
diff --git a/drivers/iommu/iommu.c b/drivers/iommu/iommu.c
index 9a5ec1c7a8ac..b326389deea4 100644
--- a/drivers/iommu/iommu.c
+++ b/drivers/iommu/iommu.c
@@ -93,6 +93,8 @@ static void __iommu_detach_group(struct iommu_domain *domain,
static int iommu_create_device_direct_mappings(struct iommu_group *group,
struct device *dev);
static struct iommu_group *iommu_group_get_for_dev(struct device *dev);
+static ssize_t iommu_group_store_type(struct iommu_group *group,
+ const char *buf, size_t count);
#define IOMMU_GROUP_ATTR(_name, _mode, _show, _store) \
struct iommu_group_attribute iommu_group_attr_##_name = \
@@ -525,7 +527,8 @@ static IOMMU_GROUP_ATTR(name, S_IRUGO, iommu_group_show_name, NULL);
static IOMMU_GROUP_ATTR(reserved_regions, 0444,
iommu_group_show_resv_regions, NULL);
-static IOMMU_GROUP_ATTR(type, 0444, iommu_group_show_type, NULL);
+static IOMMU_GROUP_ATTR(type, 0644, iommu_group_show_type,
+ iommu_group_store_type);
static void iommu_group_release(struct kobject *kobj)
{
@@ -3027,3 +3030,228 @@ u32 iommu_sva_get_pasid(struct iommu_sva *handle)
return ops->sva_get_pasid(handle);
}
EXPORT_SYMBOL_GPL(iommu_sva_get_pasid);
+
+/*
+ * Changes the default domain of an iommu group that has *only* one device
+ *
+ * @group: The group for which the default domain should be changed
+ * @prev_dev: The device in the group (this is used to make sure that the device
+ * hasn't changed after the caller has called this function)
+ * @type: The type of the new default domain that gets associated with the group
+ *
+ * Returns 0 on success and error code on failure
+ *
+ * Note:
+ * 1. Presently, this function is called only when user requests to change the
+ * group's default domain type through /sys/kernel/iommu_groups/<grp_id>/type
+ * Please take a closer look if intended to use for other purposes.
+ */
+static int iommu_change_dev_def_domain(struct iommu_group *group,
+ struct device *prev_dev, int type)
+{
+ struct iommu_domain *prev_dom;
+ struct group_device *grp_dev;
+ int ret, dev_def_dom;
+ struct device *dev;
+
+ if (!group)
+ return -EINVAL;
+
+ mutex_lock(&group->mutex);
+
+ if (group->default_domain != group->domain) {
+ dev_err_ratelimited(prev_dev, "Group not assigned to default domain\n");
+ ret = -EBUSY;
+ goto out;
+ }
+
+ /*
+ * iommu group wasn't locked while acquiring device lock in
+ * iommu_group_store_type(). So, make sure that the device count hasn't
+ * changed while acquiring device lock.
+ *
+ * Changing default domain of an iommu group with two or more devices
+ * isn't supported because there could be a potential deadlock. Consider
+ * the following scenario. T1 is trying to acquire device locks of all
+ * the devices in the group and before it could acquire all of them,
+ * there could be another thread T2 (from different sub-system and use
+ * case) that has already acquired some of the device locks and might be
+ * waiting for T1 to release other device locks.
+ */
+ if (iommu_group_device_count(group) != 1) {
+ dev_err_ratelimited(prev_dev, "Cannot change default domain: Group has more than one device\n");
+ ret = -EINVAL;
+ goto out;
+ }
+
+ /* Since group has only one device */
+ grp_dev = list_first_entry(&group->devices, struct group_device, list);
+ dev = grp_dev->dev;
+
+ if (prev_dev != dev) {
+ dev_err_ratelimited(prev_dev, "Cannot change default domain: Device has been changed\n");
+ ret = -EBUSY;
+ goto out;
+ }
+
+ prev_dom = group->default_domain;
+ if (!prev_dom) {
+ ret = -EINVAL;
+ goto out;
+ }
+
+ dev_def_dom = iommu_get_def_domain_type(dev);
+ if (!type) {
+ /*
+ * If the user hasn't requested any specific type of domain and
+ * if the device supports both the domains, then default to the
+ * domain the device was booted with
+ */
+ type = dev_def_dom ? : iommu_def_domain_type;
+ } else if (dev_def_dom && type != dev_def_dom) {
+ dev_err_ratelimited(prev_dev, "Device cannot be in %s domain\n",
+ iommu_domain_type_str(type));
+ ret = -EINVAL;
+ goto out;
+ }
+
+ /*
+ * Switch to a new domain only if the requested domain type is different
+ * from the existing default domain type
+ */
+ if (prev_dom->type == type) {
+ ret = 0;
+ goto out;
+ }
+
+ /* Sets group->default_domain to the newly allocated domain */
+ ret = iommu_group_alloc_default_domain(dev->bus, group, type);
+ if (ret)
+ goto out;
+
+ ret = iommu_create_device_direct_mappings(group, dev);
+ if (ret)
+ goto free_new_domain;
+
+ ret = __iommu_attach_device(group->default_domain, dev);
+ if (ret)
+ goto free_new_domain;
+
+ group->domain = group->default_domain;
+
+ /*
+ * Release the mutex here because ops->probe_finalize() call-back of
+ * some vendor IOMMU drivers calls arm_iommu_attach_device() which
+ * in-turn might call back into IOMMU core code, where it tries to take
+ * group->mutex, resulting in a deadlock.
+ */
+ mutex_unlock(&group->mutex);
+
+ /* Make sure dma_ops is appropriatley set */
+ iommu_group_do_probe_finalize(dev, group->default_domain);
+ iommu_domain_free(prev_dom);
+ return 0;
+
+free_new_domain:
+ iommu_domain_free(group->default_domain);
+ group->default_domain = prev_dom;
+ group->domain = prev_dom;
+
+out:
+ mutex_unlock(&group->mutex);
+
+ return ret;
+}
+
+/*
+ * Changing the default domain through sysfs requires the users to ubind the
+ * drivers from the devices in the iommu group. Return failure if this doesn't
+ * meet.
+ *
+ * We need to consider the race between this and the device release path.
+ * device_lock(dev) is used here to guarantee that the device release path
+ * will not be entered at the same time.
+ */
+static ssize_t iommu_group_store_type(struct iommu_group *group,
+ const char *buf, size_t count)
+{
+ struct group_device *grp_dev;
+ struct device *dev;
+ int ret, req_type;
+
+ if (!capable(CAP_SYS_ADMIN) || !capable(CAP_SYS_RAWIO))
+ return -EACCES;
+
+ if (WARN_ON(!group))
+ return -EINVAL;
+
+ if (sysfs_streq(buf, "identity"))
+ req_type = IOMMU_DOMAIN_IDENTITY;
+ else if (sysfs_streq(buf, "DMA"))
+ req_type = IOMMU_DOMAIN_DMA;
+ else if (sysfs_streq(buf, "auto"))
+ req_type = 0;
+ else
+ return -EINVAL;
+
+ /*
+ * Lock/Unlock the group mutex here before device lock to
+ * 1. Make sure that the iommu group has only one device (this is a
+ * prerequisite for step 2)
+ * 2. Get struct *dev which is needed to lock device
+ */
+ mutex_lock(&group->mutex);
+ if (iommu_group_device_count(group) != 1) {
+ mutex_unlock(&group->mutex);
+ pr_err_ratelimited("Cannot change default domain: Group has more than one device\n");
+ return -EINVAL;
+ }
+
+ /* Since group has only one device */
+ grp_dev = list_first_entry(&group->devices, struct group_device, list);
+ dev = grp_dev->dev;
+ get_device(dev);
+
+ /*
+ * Don't hold the group mutex because taking group mutex first and then
+ * the device lock could potentially cause a deadlock as below. Assume
+ * two threads T1 and T2. T1 is trying to change default domain of an
+ * iommu group and T2 is trying to hot unplug a device or release [1] VF
+ * of a PCIe device which is in the same iommu group. T1 takes group
+ * mutex and before it could take device lock assume T2 has taken device
+ * lock and is yet to take group mutex. Now, both the threads will be
+ * waiting for the other thread to release lock. Below, lock order was
+ * suggested.
+ * device_lock(dev);
+ * mutex_lock(&group->mutex);
+ * iommu_change_dev_def_domain();
+ * mutex_unlock(&group->mutex);
+ * device_unlock(dev);
+ *
+ * [1] Typical device release path
+ * device_lock() from device/driver core code
+ * -> bus_notifier()
+ * -> iommu_bus_notifier()
+ * -> iommu_release_device()
+ * -> ops->release_device() vendor driver calls back iommu core code
+ * -> mutex_lock() from iommu core code
+ */
+ mutex_unlock(&group->mutex);
+
+ /* Check if the device in the group still has a driver bound to it */
+ device_lock(dev);
+ if (device_is_bound(dev)) {
+ pr_err_ratelimited("Device is still bound to driver\n");
+ ret = -EBUSY;
+ goto out;
+ }
+
+ ret = iommu_change_dev_def_domain(group, dev, req_type);
+ ret = ret ?: count;
+
+out:
+ device_unlock(dev);
+ put_device(dev);
+
+ return ret;
+}