Merge branches 'iommu/fixes', 'x86/amd', 'groups', 'arm/tegra' and 'api/domain-attr...
[GitHub/mt8127/android_kernel_alcatel_ttab.git] / drivers / iommu / iommu.c
index 8b9ded88e6f5322c18c02ec6e685b05d8decfd29..ddbdacad7768e4c530a2e12657dc953a763325cc 100644 (file)
 #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)
+
+#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 show_iommu_group(struct device *dev,
-                               struct device_attribute *attr, char *buf)
+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;
+
+       if (attr->store)
+               ret = attr->store(group, buf, count);
+       return ret;
+}
+
+static const struct sysfs_ops iommu_group_sysfs_ops = {
+       .show = iommu_group_attr_show,
+       .store = iommu_group_attr_store,
+};
 
-       return sprintf(buf, "%u", groupid);
+static int iommu_group_create_file(struct iommu_group *group,
+                                  struct iommu_group_attribute *attr)
+{
+       return sysfs_create_file(&group->kobj, &attr->attr);
 }
-static DEVICE_ATTR(iommu_group, S_IRUGO, show_iommu_group, NULL);
 
-static int add_iommu_group(struct device *dev, void *data)
+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))
 {
-       unsigned int groupid;
+       group->iommu_data = iommu_data;
+       group->iommu_data_release = release;
+}
+EXPORT_SYMBOL_GPL(iommu_group_set_iommudata);
 
-       if (iommu_device_group(dev, &groupid) == 0)
-               return device_create_file(dev, &dev_attr_iommu_group);
+/**
+ * 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)
+{
+       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;
+       }
+
+       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 (!ops->add_device)
+               return -ENODEV;
 
-       if (iommu_device_group(dev, &groupid) == 0)
-               device_remove_file(dev, &dev_attr_iommu_group);
+       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)
 {
-       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);
+
+       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)
+{
+       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);