From 5437fcaabe1d4671e2dc5b90b7898c0bf698111b Mon Sep 17 00:00:00 2001 From: Jean-Philippe Brucker Date: Mon, 4 Nov 2019 15:52:36 +0100 Subject: [PATCH] iommu: Add bus_unset_iommu() 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 --- 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