|
@@ -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 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)
|
|
|
|
|
|
-static ssize_t show_iommu_group(struct device *dev,
|
|
|
- struct device_attribute *attr, char *buf)
|
|
|
+#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)
|
|
|
{
|
|
|
- unsigned int groupid;
|
|
|
+ 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);
|
|
|
|
|
|
- if (iommu_device_group(dev, &groupid) == 0)
|
|
|
- return device_create_file(dev, &dev_attr_iommu_group);
|
|
|
+ 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);
|
|
|
|
|
|
-static int remove_iommu_group(struct device *dev)
|
|
|
+/**
|
|
|
+ * 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;
|
|
|
|
|
|
- if (iommu_device_group(dev, &groupid) == 0)
|
|
|
- device_remove_file(dev, &dev_attr_iommu_group);
|
|
|
+ ret = sysfs_create_link(&dev->kobj, &group->kobj, "iommu_group");
|
|
|
+ if (ret) {
|
|
|
+ kfree(device);
|
|
|
+ return ret;
|
|
|
+ }
|
|
|
+
|
|
|
+ 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);
|
|
|
|
|
|
-static int iommu_device_notifier(struct notifier_block *nb,
|
|
|
- unsigned long action, void *data)
|
|
|
+/**
|
|
|
+ * 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 add_iommu_group(struct device *dev, void *data)
|
|
|
+{
|
|
|
+ struct iommu_ops *ops = data;
|
|
|
+
|
|
|
+ if (!ops->add_device)
|
|
|
+ return -ENODEV;
|
|
|
+
|
|
|
+ WARN_ON(dev->iommu_group);
|
|
|
+
|
|
|
+ ops->add_device(dev);
|
|
|
+
|
|
|
+ return 0;
|
|
|
+}
|
|
|
+
|
|
|
+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;
|
|
|
+ }
|
|
|
+ }
|
|
|
|
|
|
- if (action == BUS_NOTIFY_ADD_DEVICE)
|
|
|
- return add_iommu_group(dev, NULL);
|
|
|
- else if (action == BUS_NOTIFY_DEL_DEVICE)
|
|
|
- return remove_iommu_group(dev);
|
|
|
+ /*
|
|
|
+ * 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 (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,15 @@ 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)
|
|
|
{
|
|
|
- if (iommu_present(dev->bus) && dev->bus->iommu_ops->device_group)
|
|
|
- return dev->bus->iommu_ops->device_group(dev, groupid);
|
|
|
+ iommu_group_kset = kset_create_and_add("iommu_groups",
|
|
|
+ NULL, kernel_kobj);
|
|
|
+ ida_init(&iommu_group_ida);
|
|
|
+ mutex_init(&iommu_group_mutex);
|
|
|
|
|
|
- return -ENODEV;
|
|
|
+ BUG_ON(!iommu_group_kset);
|
|
|
+
|
|
|
+ return 0;
|
|
|
}
|
|
|
-EXPORT_SYMBOL_GPL(iommu_device_group);
|
|
|
+subsys_initcall(iommu_init);
|