diff options
Diffstat (limited to 'drivers/iommu/iommu.c')
| -rw-r--r-- | drivers/iommu/iommu.c | 611 | 
1 files changed, 581 insertions, 30 deletions
diff --git a/drivers/iommu/iommu.c b/drivers/iommu/iommu.c index 8b9ded88e6f..ddbdacad776 100644 --- a/drivers/iommu/iommu.c +++ b/drivers/iommu/iommu.c @@ -26,60 +26,535 @@  #include <linux/slab.h>  #include <linux/errno.h>  #include <linux/iommu.h> +#include <linux/idr.h> +#include <linux/notifier.h> +#include <linux/err.h> -static ssize_t show_iommu_group(struct device *dev, -				struct device_attribute *attr, char *buf) +static struct kset *iommu_group_kset; +static struct ida iommu_group_ida; +static struct mutex iommu_group_mutex; + +struct iommu_group { +	struct kobject kobj; +	struct kobject *devices_kobj; +	struct list_head devices; +	struct mutex mutex; +	struct blocking_notifier_head notifier; +	void *iommu_data; +	void (*iommu_data_release)(void *iommu_data); +	char *name; +	int id; +}; + +struct iommu_device { +	struct list_head list; +	struct device *dev; +	char *name; +}; + +struct iommu_group_attribute { +	struct attribute attr; +	ssize_t (*show)(struct iommu_group *group, char *buf); +	ssize_t (*store)(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 =		\ +	__ATTR(_name, _mode, _show, _store) + +#define to_iommu_group_attr(_attr)	\ +	container_of(_attr, struct iommu_group_attribute, attr) +#define to_iommu_group(_kobj)		\ +	container_of(_kobj, struct iommu_group, kobj) + +static ssize_t iommu_group_attr_show(struct kobject *kobj, +				     struct attribute *__attr, char *buf)  { -	unsigned int groupid; +	struct iommu_group_attribute *attr = to_iommu_group_attr(__attr); +	struct iommu_group *group = to_iommu_group(kobj); +	ssize_t ret = -EIO; -	if (iommu_device_group(dev, &groupid)) -		return 0; +	if (attr->show) +		ret = attr->show(group, buf); +	return ret; +} + +static ssize_t iommu_group_attr_store(struct kobject *kobj, +				      struct attribute *__attr, +				      const char *buf, size_t count) +{ +	struct iommu_group_attribute *attr = to_iommu_group_attr(__attr); +	struct iommu_group *group = to_iommu_group(kobj); +	ssize_t ret = -EIO; -	return sprintf(buf, "%u", groupid); +	if (attr->store) +		ret = attr->store(group, buf, count); +	return ret;  } -static DEVICE_ATTR(iommu_group, S_IRUGO, show_iommu_group, NULL); -static int add_iommu_group(struct device *dev, void *data) +static const struct sysfs_ops iommu_group_sysfs_ops = { +	.show = iommu_group_attr_show, +	.store = iommu_group_attr_store, +}; + +static int iommu_group_create_file(struct iommu_group *group, +				   struct iommu_group_attribute *attr) +{ +	return sysfs_create_file(&group->kobj, &attr->attr); +} + +static void iommu_group_remove_file(struct iommu_group *group, +				    struct iommu_group_attribute *attr) +{ +	sysfs_remove_file(&group->kobj, &attr->attr); +} + +static ssize_t iommu_group_show_name(struct iommu_group *group, char *buf) +{ +	return sprintf(buf, "%s\n", group->name); +} + +static IOMMU_GROUP_ATTR(name, S_IRUGO, iommu_group_show_name, NULL); + +static void iommu_group_release(struct kobject *kobj) +{ +	struct iommu_group *group = to_iommu_group(kobj); + +	if (group->iommu_data_release) +		group->iommu_data_release(group->iommu_data); + +	mutex_lock(&iommu_group_mutex); +	ida_remove(&iommu_group_ida, group->id); +	mutex_unlock(&iommu_group_mutex); + +	kfree(group->name); +	kfree(group); +} + +static struct kobj_type iommu_group_ktype = { +	.sysfs_ops = &iommu_group_sysfs_ops, +	.release = iommu_group_release, +}; + +/** + * iommu_group_alloc - Allocate a new group + * @name: Optional name to associate with group, visible in sysfs + * + * This function is called by an iommu driver to allocate a new iommu + * group.  The iommu group represents the minimum granularity of the iommu. + * Upon successful return, the caller holds a reference to the supplied + * group in order to hold the group until devices are added.  Use + * iommu_group_put() to release this extra reference count, allowing the + * group to be automatically reclaimed once it has no devices or external + * references. + */ +struct iommu_group *iommu_group_alloc(void) +{ +	struct iommu_group *group; +	int ret; + +	group = kzalloc(sizeof(*group), GFP_KERNEL); +	if (!group) +		return ERR_PTR(-ENOMEM); + +	group->kobj.kset = iommu_group_kset; +	mutex_init(&group->mutex); +	INIT_LIST_HEAD(&group->devices); +	BLOCKING_INIT_NOTIFIER_HEAD(&group->notifier); + +	mutex_lock(&iommu_group_mutex); + +again: +	if (unlikely(0 == ida_pre_get(&iommu_group_ida, GFP_KERNEL))) { +		kfree(group); +		mutex_unlock(&iommu_group_mutex); +		return ERR_PTR(-ENOMEM); +	} + +	if (-EAGAIN == ida_get_new(&iommu_group_ida, &group->id)) +		goto again; + +	mutex_unlock(&iommu_group_mutex); + +	ret = kobject_init_and_add(&group->kobj, &iommu_group_ktype, +				   NULL, "%d", group->id); +	if (ret) { +		mutex_lock(&iommu_group_mutex); +		ida_remove(&iommu_group_ida, group->id); +		mutex_unlock(&iommu_group_mutex); +		kfree(group); +		return ERR_PTR(ret); +	} + +	group->devices_kobj = kobject_create_and_add("devices", &group->kobj); +	if (!group->devices_kobj) { +		kobject_put(&group->kobj); /* triggers .release & free */ +		return ERR_PTR(-ENOMEM); +	} + +	/* +	 * The devices_kobj holds a reference on the group kobject, so +	 * as long as that exists so will the group.  We can therefore +	 * use the devices_kobj for reference counting. +	 */ +	kobject_put(&group->kobj); + +	return group; +} +EXPORT_SYMBOL_GPL(iommu_group_alloc); + +/** + * iommu_group_get_iommudata - retrieve iommu_data registered for a group + * @group: the group + * + * iommu drivers can store data in the group for use when doing iommu + * operations.  This function provides a way to retrieve it.  Caller + * should hold a group reference. + */ +void *iommu_group_get_iommudata(struct iommu_group *group) +{ +	return group->iommu_data; +} +EXPORT_SYMBOL_GPL(iommu_group_get_iommudata); + +/** + * iommu_group_set_iommudata - set iommu_data for a group + * @group: the group + * @iommu_data: new data + * @release: release function for iommu_data + * + * iommu drivers can store data in the group for use when doing iommu + * operations.  This function provides a way to set the data after + * the group has been allocated.  Caller should hold a group reference. + */ +void iommu_group_set_iommudata(struct iommu_group *group, void *iommu_data, +			       void (*release)(void *iommu_data)) +{ +	group->iommu_data = iommu_data; +	group->iommu_data_release = release; +} +EXPORT_SYMBOL_GPL(iommu_group_set_iommudata); + +/** + * iommu_group_set_name - set name for a group + * @group: the group + * @name: name + * + * Allow iommu driver to set a name for a group.  When set it will + * appear in a name attribute file under the group in sysfs. + */ +int iommu_group_set_name(struct iommu_group *group, const char *name) +{ +	int ret; + +	if (group->name) { +		iommu_group_remove_file(group, &iommu_group_attr_name); +		kfree(group->name); +		group->name = NULL; +		if (!name) +			return 0; +	} + +	group->name = kstrdup(name, GFP_KERNEL); +	if (!group->name) +		return -ENOMEM; + +	ret = iommu_group_create_file(group, &iommu_group_attr_name); +	if (ret) { +		kfree(group->name); +		group->name = NULL; +		return ret; +	} + +	return 0; +} +EXPORT_SYMBOL_GPL(iommu_group_set_name); + +/** + * iommu_group_add_device - add a device to an iommu group + * @group: the group into which to add the device (reference should be held) + * @dev: the device + * + * This function is called by an iommu driver to add a device into a + * group.  Adding a device increments the group reference count. + */ +int iommu_group_add_device(struct iommu_group *group, struct device *dev)  { -	unsigned int groupid; +	int ret, i = 0; +	struct iommu_device *device; + +	device = kzalloc(sizeof(*device), GFP_KERNEL); +	if (!device) +		return -ENOMEM; + +	device->dev = dev; + +	ret = sysfs_create_link(&dev->kobj, &group->kobj, "iommu_group"); +	if (ret) { +		kfree(device); +		return ret; +	} -	if (iommu_device_group(dev, &groupid) == 0) -		return device_create_file(dev, &dev_attr_iommu_group); +	device->name = kasprintf(GFP_KERNEL, "%s", kobject_name(&dev->kobj)); +rename: +	if (!device->name) { +		sysfs_remove_link(&dev->kobj, "iommu_group"); +		kfree(device); +		return -ENOMEM; +	} + +	ret = sysfs_create_link_nowarn(group->devices_kobj, +				       &dev->kobj, device->name); +	if (ret) { +		kfree(device->name); +		if (ret == -EEXIST && i >= 0) { +			/* +			 * Account for the slim chance of collision +			 * and append an instance to the name. +			 */ +			device->name = kasprintf(GFP_KERNEL, "%s.%d", +						 kobject_name(&dev->kobj), i++); +			goto rename; +		} + +		sysfs_remove_link(&dev->kobj, "iommu_group"); +		kfree(device); +		return ret; +	} + +	kobject_get(group->devices_kobj); + +	dev->iommu_group = group; +	mutex_lock(&group->mutex); +	list_add_tail(&device->list, &group->devices); +	mutex_unlock(&group->mutex); + +	/* Notify any listeners about change to group. */ +	blocking_notifier_call_chain(&group->notifier, +				     IOMMU_GROUP_NOTIFY_ADD_DEVICE, dev);  	return 0;  } +EXPORT_SYMBOL_GPL(iommu_group_add_device); + +/** + * iommu_group_remove_device - remove a device from it's current group + * @dev: device to be removed + * + * This function is called by an iommu driver to remove the device from + * it's current group.  This decrements the iommu group reference count. + */ +void iommu_group_remove_device(struct device *dev) +{ +	struct iommu_group *group = dev->iommu_group; +	struct iommu_device *tmp_device, *device = NULL; + +	/* Pre-notify listeners that a device is being removed. */ +	blocking_notifier_call_chain(&group->notifier, +				     IOMMU_GROUP_NOTIFY_DEL_DEVICE, dev); + +	mutex_lock(&group->mutex); +	list_for_each_entry(tmp_device, &group->devices, list) { +		if (tmp_device->dev == dev) { +			device = tmp_device; +			list_del(&device->list); +			break; +		} +	} +	mutex_unlock(&group->mutex); + +	if (!device) +		return; + +	sysfs_remove_link(group->devices_kobj, device->name); +	sysfs_remove_link(&dev->kobj, "iommu_group"); + +	kfree(device->name); +	kfree(device); +	dev->iommu_group = NULL; +	kobject_put(group->devices_kobj); +} +EXPORT_SYMBOL_GPL(iommu_group_remove_device); + +/** + * iommu_group_for_each_dev - iterate over each device in the group + * @group: the group + * @data: caller opaque data to be passed to callback function + * @fn: caller supplied callback function + * + * This function is called by group users to iterate over group devices. + * Callers should hold a reference count to the group during callback. + * The group->mutex is held across callbacks, which will block calls to + * iommu_group_add/remove_device. + */ +int iommu_group_for_each_dev(struct iommu_group *group, void *data, +			     int (*fn)(struct device *, void *)) +{ +	struct iommu_device *device; +	int ret = 0; + +	mutex_lock(&group->mutex); +	list_for_each_entry(device, &group->devices, list) { +		ret = fn(device->dev, data); +		if (ret) +			break; +	} +	mutex_unlock(&group->mutex); +	return ret; +} +EXPORT_SYMBOL_GPL(iommu_group_for_each_dev); + +/** + * iommu_group_get - Return the group for a device and increment reference + * @dev: get the group that this device belongs to + * + * This function is called by iommu drivers and users to get the group + * for the specified device.  If found, the group is returned and the group + * reference in incremented, else NULL. + */ +struct iommu_group *iommu_group_get(struct device *dev) +{ +	struct iommu_group *group = dev->iommu_group; + +	if (group) +		kobject_get(group->devices_kobj); + +	return group; +} +EXPORT_SYMBOL_GPL(iommu_group_get); + +/** + * iommu_group_put - Decrement group reference + * @group: the group to use + * + * This function is called by iommu drivers and users to release the + * iommu group.  Once the reference count is zero, the group is released. + */ +void iommu_group_put(struct iommu_group *group) +{ +	if (group) +		kobject_put(group->devices_kobj); +} +EXPORT_SYMBOL_GPL(iommu_group_put); + +/** + * iommu_group_register_notifier - Register a notifier for group changes + * @group: the group to watch + * @nb: notifier block to signal + * + * This function allows iommu group users to track changes in a group. + * See include/linux/iommu.h for actions sent via this notifier.  Caller + * should hold a reference to the group throughout notifier registration. + */ +int iommu_group_register_notifier(struct iommu_group *group, +				  struct notifier_block *nb) +{ +	return blocking_notifier_chain_register(&group->notifier, nb); +} +EXPORT_SYMBOL_GPL(iommu_group_register_notifier); + +/** + * iommu_group_unregister_notifier - Unregister a notifier + * @group: the group to watch + * @nb: notifier block to signal + * + * Unregister a previously registered group notifier block. + */ +int iommu_group_unregister_notifier(struct iommu_group *group, +				    struct notifier_block *nb) +{ +	return blocking_notifier_chain_unregister(&group->notifier, nb); +} +EXPORT_SYMBOL_GPL(iommu_group_unregister_notifier); + +/** + * iommu_group_id - Return ID for a group + * @group: the group to ID + * + * Return the unique ID for the group matching the sysfs group number. + */ +int iommu_group_id(struct iommu_group *group) +{ +	return group->id; +} +EXPORT_SYMBOL_GPL(iommu_group_id); -static int remove_iommu_group(struct device *dev) +static int add_iommu_group(struct device *dev, void *data)  { -	unsigned int groupid; +	struct iommu_ops *ops = data; -	if (iommu_device_group(dev, &groupid) == 0) -		device_remove_file(dev, &dev_attr_iommu_group); +	if (!ops->add_device) +		return -ENODEV; + +	WARN_ON(dev->iommu_group); + +	ops->add_device(dev);  	return 0;  } -static int iommu_device_notifier(struct notifier_block *nb, -				 unsigned long action, void *data) +static int iommu_bus_notifier(struct notifier_block *nb, +			      unsigned long action, void *data)  {  	struct device *dev = data; +	struct iommu_ops *ops = dev->bus->iommu_ops; +	struct iommu_group *group; +	unsigned long group_action = 0; + +	/* +	 * ADD/DEL call into iommu driver ops if provided, which may +	 * result in ADD/DEL notifiers to group->notifier +	 */ +	if (action == BUS_NOTIFY_ADD_DEVICE) { +		if (ops->add_device) +			return ops->add_device(dev); +	} else if (action == BUS_NOTIFY_DEL_DEVICE) { +		if (ops->remove_device && dev->iommu_group) { +			ops->remove_device(dev); +			return 0; +		} +	} + +	/* +	 * Remaining BUS_NOTIFYs get filtered and republished to the +	 * group, if anyone is listening +	 */ +	group = iommu_group_get(dev); +	if (!group) +		return 0; + +	switch (action) { +	case BUS_NOTIFY_BIND_DRIVER: +		group_action = IOMMU_GROUP_NOTIFY_BIND_DRIVER; +		break; +	case BUS_NOTIFY_BOUND_DRIVER: +		group_action = IOMMU_GROUP_NOTIFY_BOUND_DRIVER; +		break; +	case BUS_NOTIFY_UNBIND_DRIVER: +		group_action = IOMMU_GROUP_NOTIFY_UNBIND_DRIVER; +		break; +	case BUS_NOTIFY_UNBOUND_DRIVER: +		group_action = IOMMU_GROUP_NOTIFY_UNBOUND_DRIVER; +		break; +	} -	if (action == BUS_NOTIFY_ADD_DEVICE) -		return add_iommu_group(dev, NULL); -	else if (action == BUS_NOTIFY_DEL_DEVICE) -		return remove_iommu_group(dev); +	if (group_action) +		blocking_notifier_call_chain(&group->notifier, +					     group_action, dev); +	iommu_group_put(group);  	return 0;  } -static struct notifier_block iommu_device_nb = { -	.notifier_call = iommu_device_notifier, +static struct notifier_block iommu_bus_nb = { +	.notifier_call = iommu_bus_notifier,  };  static void iommu_bus_init(struct bus_type *bus, struct iommu_ops *ops)  { -	bus_register_notifier(bus, &iommu_device_nb); -	bus_for_each_dev(bus, NULL, NULL, add_iommu_group); +	bus_register_notifier(bus, &iommu_bus_nb); +	bus_for_each_dev(bus, NULL, ops, add_iommu_group);  }  /** @@ -192,6 +667,45 @@ void iommu_detach_device(struct iommu_domain *domain, struct device *dev)  }  EXPORT_SYMBOL_GPL(iommu_detach_device); +/* + * IOMMU groups are really the natrual working unit of the IOMMU, but + * the IOMMU API works on domains and devices.  Bridge that gap by + * iterating over the devices in a group.  Ideally we'd have a single + * device which represents the requestor ID of the group, but we also + * allow IOMMU drivers to create policy defined minimum sets, where + * the physical hardware may be able to distiguish members, but we + * wish to group them at a higher level (ex. untrusted multi-function + * PCI devices).  Thus we attach each device. + */ +static int iommu_group_do_attach_device(struct device *dev, void *data) +{ +	struct iommu_domain *domain = data; + +	return iommu_attach_device(domain, dev); +} + +int iommu_attach_group(struct iommu_domain *domain, struct iommu_group *group) +{ +	return iommu_group_for_each_dev(group, domain, +					iommu_group_do_attach_device); +} +EXPORT_SYMBOL_GPL(iommu_attach_group); + +static int iommu_group_do_detach_device(struct device *dev, void *data) +{ +	struct iommu_domain *domain = data; + +	iommu_detach_device(domain, dev); + +	return 0; +} + +void iommu_detach_group(struct iommu_domain *domain, struct iommu_group *group) +{ +	iommu_group_for_each_dev(group, domain, iommu_group_do_detach_device); +} +EXPORT_SYMBOL_GPL(iommu_detach_group); +  phys_addr_t iommu_iova_to_phys(struct iommu_domain *domain,  			       unsigned long iova)  { @@ -336,11 +850,48 @@ size_t iommu_unmap(struct iommu_domain *domain, unsigned long iova, size_t size)  }  EXPORT_SYMBOL_GPL(iommu_unmap); -int iommu_device_group(struct device *dev, unsigned int *groupid) +static int __init iommu_init(void) +{ +	iommu_group_kset = kset_create_and_add("iommu_groups", +					       NULL, kernel_kobj); +	ida_init(&iommu_group_ida); +	mutex_init(&iommu_group_mutex); + +	BUG_ON(!iommu_group_kset); + +	return 0; +} +subsys_initcall(iommu_init); + +int iommu_domain_get_attr(struct iommu_domain *domain, +			  enum iommu_attr attr, void *data)  { -	if (iommu_present(dev->bus) && dev->bus->iommu_ops->device_group) -		return dev->bus->iommu_ops->device_group(dev, groupid); +	struct iommu_domain_geometry *geometry; +	int ret = 0; + +	switch (attr) { +	case DOMAIN_ATTR_GEOMETRY: +		geometry  = data; +		*geometry = domain->geometry; + +		break; +	default: +		if (!domain->ops->domain_get_attr) +			return -EINVAL; + +		ret = domain->ops->domain_get_attr(domain, attr, data); +	} + +	return ret; +} +EXPORT_SYMBOL_GPL(iommu_domain_get_attr); + +int iommu_domain_set_attr(struct iommu_domain *domain, +			  enum iommu_attr attr, void *data) +{ +	if (!domain->ops->domain_set_attr) +		return -EINVAL; -	return -ENODEV; +	return domain->ops->domain_set_attr(domain, attr, data);  } -EXPORT_SYMBOL_GPL(iommu_device_group); +EXPORT_SYMBOL_GPL(iommu_domain_set_attr);  |