of_syscon_register_regmap() publishes an externally owned regmap in the global syscon list, but provides no way for a removable driver to withdraw it. Registering a devm-managed regmap from such a driver would therefore leave a stale pointer after unbind. Factor external registration through an internal helper and add devm_of_syscon_register_regmap(). The managed action removes the entry under the syscon list lock before later devres actions release the regmap. Hold a device-node reference for the lifetime of every externally registered entry so its lookup key also remains valid. Consumers of a removable provider remain responsible for ordering teardown, for example with a managed device link. Signed-off-by: James Hilliard --- drivers/mfd/syscon.c | 87 ++++++++++++++++++++++++++++++++++------------ include/linux/mfd/syscon.h | 12 +++++++ 2 files changed, 77 insertions(+), 22 deletions(-) diff --git a/drivers/mfd/syscon.c b/drivers/mfd/syscon.c index 21a7fcdd2737..448422fe4f21 100644 --- a/drivers/mfd/syscon.c +++ b/drivers/mfd/syscon.c @@ -10,6 +10,7 @@ #include #include +#include #include #include #include @@ -193,53 +194,95 @@ static struct regmap *device_node_get_regmap(struct device_node *np, return syscon->regmap; } -/** - * of_syscon_register_regmap() - Register regmap for specified device node - * @np: Device tree node - * @regmap: Pointer to regmap object - * - * Register an externally created regmap object with syscon for the specified - * device tree node. This regmap will then be returned to client drivers using - * the syscon_regmap_lookup_by_phandle() API. - * - * Return: 0 on success, negative error code on failure. - */ -int of_syscon_register_regmap(struct device_node *np, struct regmap *regmap) +static struct syscon * +of_syscon_register_regmap_internal(struct device_node *np, + struct regmap *regmap) { struct syscon *entry, *syscon = NULL; - int ret; if (!np || !regmap) - return -EINVAL; + return ERR_PTR(-EINVAL); syscon = kzalloc_obj(*syscon); if (!syscon) - return -ENOMEM; + return ERR_PTR(-ENOMEM); /* check if syscon entry already exists */ mutex_lock(&syscon_list_lock); list_for_each_entry(entry, &syscon_list, list) if (entry->np == np) { - ret = -EEXIST; - goto err_unlock; + mutex_unlock(&syscon_list_lock); + kfree(syscon); + return ERR_PTR(-EEXIST); } syscon->regmap = regmap; - syscon->np = np; + syscon->np = of_node_get(np); /* register the regmap in syscon list */ list_add_tail(&syscon->list, &syscon_list); mutex_unlock(&syscon_list_lock); - return 0; + return syscon; +} + +/** + * of_syscon_register_regmap() - Register regmap for specified device node + * @np: Device tree node + * @regmap: Pointer to regmap object + * + * Register an externally created regmap object with syscon for the specified + * device tree node. This regmap will then be returned to client drivers using + * the syscon_regmap_lookup_by_phandle() API. + * + * Return: 0 on success, negative error code on failure. + */ +int of_syscon_register_regmap(struct device_node *np, struct regmap *regmap) +{ + return PTR_ERR_OR_ZERO(of_syscon_register_regmap_internal(np, regmap)); +} +EXPORT_SYMBOL_GPL(of_syscon_register_regmap); + +static void devm_of_syscon_register_regmap_release(void *data) +{ + struct syscon *syscon = data; -err_unlock: + mutex_lock(&syscon_list_lock); + list_del(&syscon->list); mutex_unlock(&syscon_list_lock); + + of_node_put(syscon->np); kfree(syscon); - return ret; } -EXPORT_SYMBOL_GPL(of_syscon_register_regmap); + +/** + * devm_of_syscon_register_regmap() - Register a managed external syscon regmap + * @dev: Device that owns the regmap + * @np: Device tree node associated with the regmap + * @regmap: Pointer to the externally created regmap + * + * Register an externally created regmap object with syscon and remove it when + * @dev is unbound. Consumers must stop using the regmap before the provider is + * unbound, for example by establishing a managed device link to @dev. + * + * Return: 0 on success, negative error code on failure. + */ +int devm_of_syscon_register_regmap(struct device *dev, + struct device_node *np, + struct regmap *regmap) +{ + struct syscon *syscon; + + syscon = of_syscon_register_regmap_internal(np, regmap); + if (IS_ERR(syscon)) + return PTR_ERR(syscon); + + return devm_add_action_or_reset(dev, + devm_of_syscon_register_regmap_release, + syscon); +} +EXPORT_SYMBOL_GPL(devm_of_syscon_register_regmap); /** * device_node_to_regmap() - Get or create a regmap for specified device node diff --git a/include/linux/mfd/syscon.h b/include/linux/mfd/syscon.h index aad9c6b50463..3e804fc1b03e 100644 --- a/include/linux/mfd/syscon.h +++ b/include/linux/mfd/syscon.h @@ -15,6 +15,7 @@ #include struct device_node; +struct device; #ifdef CONFIG_MFD_SYSCON struct regmap *device_node_to_regmap(struct device_node *np); @@ -30,6 +31,9 @@ struct regmap *syscon_regmap_lookup_by_phandle_optional(struct device_node *np, const char *property); int of_syscon_register_regmap(struct device_node *np, struct regmap *regmap); +int devm_of_syscon_register_regmap(struct device *dev, + struct device_node *np, + struct regmap *regmap); #else static inline struct regmap *device_node_to_regmap(struct device_node *np) { @@ -75,6 +79,14 @@ static inline int of_syscon_register_regmap(struct device_node *np, return -EOPNOTSUPP; } +static inline int +devm_of_syscon_register_regmap(struct device *dev, + struct device_node *np, + struct regmap *regmap) +{ + return -EOPNOTSUPP; +} + #endif #endif /* __LINUX_MFD_SYSCON_H__ */ -- 2.53.0