[PATCH] iommu: Add bus_unset_iommu()

From: Jean-Philippe Brucker
Date: Mon Nov 04 2019 - 09:52:36 EST


Let modular IOMMU drivers undo bus_set_iommu(). Keep track of bus
registrations with a list and refcount, and remove the iommu_ops from
the bus when there are no IOMMU providers anymore.

Signed-off-by: Jean-Philippe Brucker <jean-philippe@xxxxxxxxxx>
---
drivers/iommu/iommu.c | 101 ++++++++++++++++++++++++++++++++++--------
include/linux/iommu.h | 1 +
2 files changed, 84 insertions(+), 18 deletions(-)

diff --git a/drivers/iommu/iommu.c b/drivers/iommu/iommu.c
index 393a5376d7c6..f9bac5633f2a 100644
--- a/drivers/iommu/iommu.c
+++ b/drivers/iommu/iommu.c
@@ -31,6 +31,9 @@ static unsigned int iommu_def_domain_type __read_mostly;
static bool iommu_dma_strict __read_mostly = true;
static u32 iommu_cmd_line __read_mostly;

+static DEFINE_MUTEX(iommu_bus_notifiers_lock);
+static LIST_HEAD(iommu_bus_notifiers);
+
struct iommu_group {
struct kobject kobj;
struct kobject *devices_kobj;
@@ -58,6 +61,14 @@ struct iommu_group_attribute {
const char *buf, size_t count);
};

+struct iommu_bus_notifier {
+ struct notifier_block nb;
+ const struct iommu_ops *ops;
+ struct bus_type *bus;
+ struct list_head list;
+ refcount_t refs;
+};
+
static const char * const iommu_group_resv_type_string[] = {
[IOMMU_RESV_DIRECT] = "direct",
[IOMMU_RESV_DIRECT_RELAXABLE] = "direct-relaxable",
@@ -1494,15 +1505,29 @@ static int iommu_bus_notifier(struct notifier_block *nb,
static int iommu_bus_init(struct bus_type *bus, const struct iommu_ops *ops)
{
int err;
- struct notifier_block *nb;
+ struct iommu_bus_notifier *iommu_notifier;

- nb = kzalloc(sizeof(struct notifier_block), GFP_KERNEL);
- if (!nb)
- return -ENOMEM;
+ list_for_each_entry(iommu_notifier, &iommu_bus_notifiers, list) {
+ if (iommu_notifier->ops == ops && iommu_notifier->bus == bus) {
+ refcount_inc(&iommu_notifier->refs);
+ return 0;
+ }
+ }
+
+ bus->iommu_ops = ops;
+
+ iommu_notifier = kzalloc(sizeof(*iommu_notifier), GFP_KERNEL);
+ if (!iommu_notifier) {
+ err = -ENOMEM;
+ goto out_clear;
+ }

- nb->notifier_call = iommu_bus_notifier;
+ iommu_notifier->ops = ops;
+ iommu_notifier->bus = bus;
+ iommu_notifier->nb.notifier_call = iommu_bus_notifier;
+ refcount_set(&iommu_notifier->refs, 1);

- err = bus_register_notifier(bus, nb);
+ err = bus_register_notifier(bus, &iommu_notifier->nb);
if (err)
goto out_free;

@@ -1510,20 +1535,47 @@ static int iommu_bus_init(struct bus_type *bus, const struct iommu_ops *ops)
if (err)
goto out_err;

-
+ list_add(&iommu_notifier->list, &iommu_bus_notifiers);
return 0;

out_err:
/* Clean up */
bus_for_each_dev(bus, NULL, NULL, remove_iommu_group);
- bus_unregister_notifier(bus, nb);
-
+ bus_unregister_notifier(bus, &iommu_notifier->nb);
out_free:
- kfree(nb);
+ kfree(iommu_notifier);
+out_clear:
+ bus->iommu_ops = NULL;

return err;
}

+static int iommu_bus_remove(struct bus_type *bus, const struct iommu_ops *ops)
+{
+ struct iommu_bus_notifier *tmp;
+ struct iommu_bus_notifier *iommu_notifier = NULL;
+
+ list_for_each_entry(tmp, &iommu_bus_notifiers, list) {
+ if (tmp->ops == ops && tmp->bus == bus) {
+ iommu_notifier = tmp;
+ break;
+ }
+ }
+
+ if (!iommu_notifier)
+ return -ESRCH;
+
+ if (!refcount_dec_and_test(&iommu_notifier->refs))
+ return 0;
+
+ list_del(&iommu_notifier->list);
+ bus_for_each_dev(bus, NULL, NULL, remove_iommu_group);
+ bus_unregister_notifier(bus, &iommu_notifier->nb);
+ kfree(iommu_notifier);
+ bus->iommu_ops = NULL;
+ return 0;
+}
+
/**
* bus_set_iommu - set iommu-callbacks for the bus
* @bus: bus.
@@ -1541,20 +1593,33 @@ int bus_set_iommu(struct bus_type *bus, const struct iommu_ops *ops)
{
int err;

- if (bus->iommu_ops != NULL)
- return -EBUSY;
-
- bus->iommu_ops = ops;
-
/* Do IOMMU specific setup for this bus-type */
- err = iommu_bus_init(bus, ops);
- if (err)
- bus->iommu_ops = NULL;
+ mutex_lock(&iommu_bus_notifiers_lock);
+ if (bus->iommu_ops != NULL && bus->iommu_ops != ops)
+ err = -EBUSY;
+ else
+ err = iommu_bus_init(bus, ops);
+ mutex_unlock(&iommu_bus_notifiers_lock);

return err;
}
EXPORT_SYMBOL_GPL(bus_set_iommu);

+int bus_unset_iommu(struct bus_type *bus, const struct iommu_ops *ops)
+{
+ int err;
+
+ mutex_lock(&iommu_bus_notifiers_lock);
+ if (bus->iommu_ops != ops)
+ err = -EINVAL;
+ else
+ err = iommu_bus_remove(bus, ops);
+ mutex_unlock(&iommu_bus_notifiers_lock);
+
+ return err;
+}
+EXPORT_SYMBOL_GPL(bus_unset_iommu);
+
bool iommu_present(struct bus_type *bus)
{
return bus->iommu_ops != NULL;
diff --git a/include/linux/iommu.h b/include/linux/iommu.h
index 29bac5345563..15c9115e31ff 100644
--- a/include/linux/iommu.h
+++ b/include/linux/iommu.h
@@ -408,6 +408,7 @@ static inline void iommu_iotlb_gather_init(struct iommu_iotlb_gather *gather)
#define IOMMU_GROUP_NOTIFY_UNBOUND_DRIVER 6 /* Post Driver unbind */

extern int bus_set_iommu(struct bus_type *bus, const struct iommu_ops *ops);
+extern int bus_unset_iommu(struct bus_type *bus, const struct iommu_ops *ops);
extern bool iommu_present(struct bus_type *bus);
extern bool iommu_capable(struct bus_type *bus, enum iommu_cap cap);
extern struct iommu_domain *iommu_domain_alloc(struct bus_type *bus);
--
2.23.0


--UlVJffcvxoiEqYs2--