[PATCH v2 01/12] notifier: add device-managed registration APIs
Eliav Farber <[email protected]> Sun, 26 Jul 2026 04:56:55 +0000
| Newsgroups | org.kernel.vger.linux-pwm,org.kernel.vger.linux-acpi,org.kernel.vger.linux-gpio,org.kernel.vger.linux-iio,org.kernel.vger.linux-input,org.kernel.vger.linux-kernel,org.kernel.vger.platform-driver-x86 |
|---|---|
| Message-ID | <[email protected]> |
Add devm_atomic_notifier_chain_register(), devm_blocking_notifier_chain_register(), and devm_raw_notifier_chain_register() that automatically unregister the notifier when the device is unbound. Many drivers repeat the same boilerplate pattern: 1. Register the notifier with *_notifier_chain_register() 2. Check for error 3. Register a devm action to unregister on teardown 4. Implement a per-driver static unregister callback With the new devm_*_notifier_chain_register() APIs, this reduces to a single call with one error path, eliminating per-driver unregister callbacks entirely. The implementation follows the established devres pattern used by other device-managed kernel APIs. Signed-off-by: Eliav Farber <[email protected]> --- Changes in v2: - Drop 'extern' from new function prototypes (Bart Van Assche) - Fix kerneldoc to use 'Return:' format (Bart Van Assche) - Use <linux/device/devres.h> instead of <linux/device.h> (Andy Shevchenko) include/linux/notifier.h | 10 +++ kernel/notifier.c | 154 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 164 insertions(+) diff --git a/include/linux/notifier.h b/include/linux/notifier.h index 01b6c9d9956f..ccac9382478c 100644 --- a/include/linux/notifier.h +++ b/include/linux/notifier.h @@ -46,6 +46,7 @@ * often but notifier_blocks will seldom be removed. */ +struct device; struct notifier_block; typedef int (*notifier_fn_t)(struct notifier_block *nb, @@ -145,10 +146,19 @@ extern void srcu_init_notifier_head(struct srcu_notifier_head *nh); extern int atomic_notifier_chain_register(struct atomic_notifier_head *nh, struct notifier_block *nb); +int devm_atomic_notifier_chain_register(struct device *dev, + struct atomic_notifier_head *nh, + struct notifier_block *nb); extern int blocking_notifier_chain_register(struct blocking_notifier_head *nh, struct notifier_block *nb); +int devm_blocking_notifier_chain_register(struct device *dev, + struct blocking_notifier_head *nh, + struct notifier_block *nb); extern int raw_notifier_chain_register(struct raw_notifier_head *nh, struct notifier_block *nb); +int devm_raw_notifier_chain_register(struct device *dev, + struct raw_notifier_head *nh, + struct notifier_block *nb); extern int srcu_notifier_chain_register(struct srcu_notifier_head *nh, struct notifier_block *nb); diff --git a/kernel/notifier.c b/kernel/notifier.c index 2f9fe7c30287..d8c82b237586 100644 --- a/kernel/notifier.c +++ b/kernel/notifier.c @@ -1,4 +1,5 @@ // SPDX-License-Identifier: GPL-2.0-only +#include <linux/device/devres.h> #include <linux/kdebug.h> #include <linux/kprobes.h> #include <linux/export.h> @@ -197,6 +198,56 @@ int atomic_notifier_chain_unregister(struct atomic_notifier_head *nh, } EXPORT_SYMBOL_GPL(atomic_notifier_chain_unregister); +struct atomic_notifier_chain_devres { + struct atomic_notifier_head *nh; + struct notifier_block *nb; +}; + +static void devm_atomic_notifier_chain_unregister(struct device *dev, void *res) +{ + struct atomic_notifier_chain_devres *dr = res; + + atomic_notifier_chain_unregister(dr->nh, dr->nb); +} + +/** + * devm_atomic_notifier_chain_register - Device-managed atomic notifier registration + * @dev: Device to tie the notifier lifetime to + * @nh: Pointer to head of the atomic notifier chain + * @n: New entry in notifier chain + * + * Adds a notifier to an atomic notifier chain and registers a cleanup + * action to automatically unregister it when @dev is unbound. + * + * Return: + * 0 on success, negative errno on error. + */ +int devm_atomic_notifier_chain_register(struct device *dev, + struct atomic_notifier_head *nh, + struct notifier_block *n) +{ + struct atomic_notifier_chain_devres *dr; + int ret; + + dr = devres_alloc(devm_atomic_notifier_chain_unregister, + sizeof(*dr), GFP_KERNEL); + if (!dr) + return -ENOMEM; + + ret = atomic_notifier_chain_register(nh, n); + if (ret) { + devres_free(dr); + return ret; + } + + dr->nh = nh; + dr->nb = n; + devres_add(dev, dr); + + return 0; +} +EXPORT_SYMBOL_GPL(devm_atomic_notifier_chain_register); + /** * atomic_notifier_call_chain - Call functions in an atomic notifier chain * @nh: Pointer to head of the atomic notifier chain @@ -349,6 +400,58 @@ int blocking_notifier_call_chain_robust(struct blocking_notifier_head *nh, } EXPORT_SYMBOL_GPL(blocking_notifier_call_chain_robust); +struct blocking_notifier_chain_devres { + struct blocking_notifier_head *nh; + struct notifier_block *nb; +}; + +static void devm_blocking_notifier_chain_unregister(struct device *dev, + void *res) +{ + struct blocking_notifier_chain_devres *dr = res; + + blocking_notifier_chain_unregister(dr->nh, dr->nb); +} + +/** + * devm_blocking_notifier_chain_register - Device-managed blocking notifier registration + * @dev: Device to tie the notifier lifetime to + * @nh: Pointer to head of the blocking notifier chain + * @n: New entry in notifier chain + * + * Adds a notifier to a blocking notifier chain and registers a cleanup + * action to automatically unregister it when @dev is unbound. + * Must be called in process context. + * + * Return: + * 0 on success, negative errno on error. + */ +int devm_blocking_notifier_chain_register(struct device *dev, + struct blocking_notifier_head *nh, + struct notifier_block *n) +{ + struct blocking_notifier_chain_devres *dr; + int ret; + + dr = devres_alloc(devm_blocking_notifier_chain_unregister, + sizeof(*dr), GFP_KERNEL); + if (!dr) + return -ENOMEM; + + ret = blocking_notifier_chain_register(nh, n); + if (ret) { + devres_free(dr); + return ret; + } + + dr->nh = nh; + dr->nb = n; + devres_add(dev, dr); + + return 0; +} +EXPORT_SYMBOL_GPL(devm_blocking_notifier_chain_register); + /** * blocking_notifier_call_chain - Call functions in a blocking notifier chain * @nh: Pointer to head of the blocking notifier chain @@ -430,6 +533,57 @@ int raw_notifier_call_chain_robust(struct raw_notifier_head *nh, } EXPORT_SYMBOL_GPL(raw_notifier_call_chain_robust); +struct raw_notifier_chain_devres { + struct raw_notifier_head *nh; + struct notifier_block *nb; +}; + +static void devm_raw_notifier_chain_unregister(struct device *dev, void *res) +{ + struct raw_notifier_chain_devres *dr = res; + + raw_notifier_chain_unregister(dr->nh, dr->nb); +} + +/** + * devm_raw_notifier_chain_register - Device-managed raw notifier registration + * @dev: Device to tie the notifier lifetime to + * @nh: Pointer to head of the raw notifier chain + * @n: New entry in notifier chain + * + * Adds a notifier to a raw notifier chain and registers a cleanup + * action to automatically unregister it when @dev is unbound. + * All locking must be provided by the caller. + * + * Return: + * 0 on success, negative errno on error. + */ +int devm_raw_notifier_chain_register(struct device *dev, + struct raw_notifier_head *nh, + struct notifier_block *n) +{ + struct raw_notifier_chain_devres *dr; + int ret; + + dr = devres_alloc(devm_raw_notifier_chain_unregister, + sizeof(*dr), GFP_KERNEL); + if (!dr) + return -ENOMEM; + + ret = raw_notifier_chain_register(nh, n); + if (ret) { + devres_free(dr); + return ret; + } + + dr->nh = nh; + dr->nb = n; + devres_add(dev, dr); + + return 0; +} +EXPORT_SYMBOL_GPL(devm_raw_notifier_chain_register); + /** * raw_notifier_call_chain - Call functions in a raw notifier chain * @nh: Pointer to head of the raw notifier chain -- 2.47.3