notifier: Add blocking/atomic_notifier_chain_register_unique_prio()

Add variant of blocking/atomic_notifier_chain_register() functions that
allow registration of a notifier only if it has unique priority, otherwise
-EBUSY error code is returned by the new functions.

Reviewed-by: Michał Mirosław <mirq-linux@rere.qmqm.pl>
Signed-off-by: Dmitry Osipenko <dmitry.osipenko@collabora.com>
Signed-off-by: Rafael J. Wysocki <rafael.j.wysocki@intel.com>
This commit is contained in:
Dmitry Osipenko 2022-05-10 02:32:10 +03:00 committed by Rafael J. Wysocki
parent 13dfd97a34
commit c82f898d87
2 changed files with 74 additions and 19 deletions

View File

@ -150,6 +150,11 @@ extern int raw_notifier_chain_register(struct raw_notifier_head *nh,
extern int srcu_notifier_chain_register(struct srcu_notifier_head *nh, extern int srcu_notifier_chain_register(struct srcu_notifier_head *nh,
struct notifier_block *nb); struct notifier_block *nb);
extern int atomic_notifier_chain_register_unique_prio(
struct atomic_notifier_head *nh, struct notifier_block *nb);
extern int blocking_notifier_chain_register_unique_prio(
struct blocking_notifier_head *nh, struct notifier_block *nb);
extern int atomic_notifier_chain_unregister(struct atomic_notifier_head *nh, extern int atomic_notifier_chain_unregister(struct atomic_notifier_head *nh,
struct notifier_block *nb); struct notifier_block *nb);
extern int blocking_notifier_chain_unregister(struct blocking_notifier_head *nh, extern int blocking_notifier_chain_unregister(struct blocking_notifier_head *nh,

View File

@ -20,7 +20,8 @@ BLOCKING_NOTIFIER_HEAD(reboot_notifier_list);
*/ */
static int notifier_chain_register(struct notifier_block **nl, static int notifier_chain_register(struct notifier_block **nl,
struct notifier_block *n) struct notifier_block *n,
bool unique_priority)
{ {
while ((*nl) != NULL) { while ((*nl) != NULL) {
if (unlikely((*nl) == n)) { if (unlikely((*nl) == n)) {
@ -30,6 +31,8 @@ static int notifier_chain_register(struct notifier_block **nl,
} }
if (n->priority > (*nl)->priority) if (n->priority > (*nl)->priority)
break; break;
if (n->priority == (*nl)->priority && unique_priority)
return -EBUSY;
nl = &((*nl)->next); nl = &((*nl)->next);
} }
n->next = *nl; n->next = *nl;
@ -144,12 +147,35 @@ int atomic_notifier_chain_register(struct atomic_notifier_head *nh,
int ret; int ret;
spin_lock_irqsave(&nh->lock, flags); spin_lock_irqsave(&nh->lock, flags);
ret = notifier_chain_register(&nh->head, n); ret = notifier_chain_register(&nh->head, n, false);
spin_unlock_irqrestore(&nh->lock, flags); spin_unlock_irqrestore(&nh->lock, flags);
return ret; return ret;
} }
EXPORT_SYMBOL_GPL(atomic_notifier_chain_register); EXPORT_SYMBOL_GPL(atomic_notifier_chain_register);
/**
* atomic_notifier_chain_register_unique_prio - Add notifier to an atomic notifier chain
* @nh: Pointer to head of the atomic notifier chain
* @n: New entry in notifier chain
*
* Adds a notifier to an atomic notifier chain if there is no other
* notifier registered using the same priority.
*
* Returns 0 on success, %-EEXIST or %-EBUSY on error.
*/
int atomic_notifier_chain_register_unique_prio(struct atomic_notifier_head *nh,
struct notifier_block *n)
{
unsigned long flags;
int ret;
spin_lock_irqsave(&nh->lock, flags);
ret = notifier_chain_register(&nh->head, n, true);
spin_unlock_irqrestore(&nh->lock, flags);
return ret;
}
EXPORT_SYMBOL_GPL(atomic_notifier_chain_register_unique_prio);
/** /**
* atomic_notifier_chain_unregister - Remove notifier from an atomic notifier chain * atomic_notifier_chain_unregister - Remove notifier from an atomic notifier chain
* @nh: Pointer to head of the atomic notifier chain * @nh: Pointer to head of the atomic notifier chain
@ -222,6 +248,26 @@ bool atomic_notifier_call_chain_is_empty(struct atomic_notifier_head *nh)
* synchronized by an rwsem. * synchronized by an rwsem.
*/ */
static int __blocking_notifier_chain_register(struct blocking_notifier_head *nh,
struct notifier_block *n,
bool unique_priority)
{
int ret;
/*
* This code gets used during boot-up, when task switching is
* not yet working and interrupts must remain disabled. At
* such times we must not call down_write().
*/
if (unlikely(system_state == SYSTEM_BOOTING))
return notifier_chain_register(&nh->head, n, unique_priority);
down_write(&nh->rwsem);
ret = notifier_chain_register(&nh->head, n, unique_priority);
up_write(&nh->rwsem);
return ret;
}
/** /**
* blocking_notifier_chain_register - Add notifier to a blocking notifier chain * blocking_notifier_chain_register - Add notifier to a blocking notifier chain
* @nh: Pointer to head of the blocking notifier chain * @nh: Pointer to head of the blocking notifier chain
@ -235,23 +281,27 @@ bool atomic_notifier_call_chain_is_empty(struct atomic_notifier_head *nh)
int blocking_notifier_chain_register(struct blocking_notifier_head *nh, int blocking_notifier_chain_register(struct blocking_notifier_head *nh,
struct notifier_block *n) struct notifier_block *n)
{ {
int ret; return __blocking_notifier_chain_register(nh, n, false);
/*
* This code gets used during boot-up, when task switching is
* not yet working and interrupts must remain disabled. At
* such times we must not call down_write().
*/
if (unlikely(system_state == SYSTEM_BOOTING))
return notifier_chain_register(&nh->head, n);
down_write(&nh->rwsem);
ret = notifier_chain_register(&nh->head, n);
up_write(&nh->rwsem);
return ret;
} }
EXPORT_SYMBOL_GPL(blocking_notifier_chain_register); EXPORT_SYMBOL_GPL(blocking_notifier_chain_register);
/**
* blocking_notifier_chain_register_unique_prio - Add notifier to a blocking notifier chain
* @nh: Pointer to head of the blocking notifier chain
* @n: New entry in notifier chain
*
* Adds a notifier to an blocking notifier chain if there is no other
* notifier registered using the same priority.
*
* Returns 0 on success, %-EEXIST or %-EBUSY on error.
*/
int blocking_notifier_chain_register_unique_prio(struct blocking_notifier_head *nh,
struct notifier_block *n)
{
return __blocking_notifier_chain_register(nh, n, true);
}
EXPORT_SYMBOL_GPL(blocking_notifier_chain_register_unique_prio);
/** /**
* blocking_notifier_chain_unregister - Remove notifier from a blocking notifier chain * blocking_notifier_chain_unregister - Remove notifier from a blocking notifier chain
* @nh: Pointer to head of the blocking notifier chain * @nh: Pointer to head of the blocking notifier chain
@ -354,7 +404,7 @@ EXPORT_SYMBOL_GPL(blocking_notifier_call_chain);
int raw_notifier_chain_register(struct raw_notifier_head *nh, int raw_notifier_chain_register(struct raw_notifier_head *nh,
struct notifier_block *n) struct notifier_block *n)
{ {
return notifier_chain_register(&nh->head, n); return notifier_chain_register(&nh->head, n, false);
} }
EXPORT_SYMBOL_GPL(raw_notifier_chain_register); EXPORT_SYMBOL_GPL(raw_notifier_chain_register);
@ -433,10 +483,10 @@ int srcu_notifier_chain_register(struct srcu_notifier_head *nh,
* such times we must not call mutex_lock(). * such times we must not call mutex_lock().
*/ */
if (unlikely(system_state == SYSTEM_BOOTING)) if (unlikely(system_state == SYSTEM_BOOTING))
return notifier_chain_register(&nh->head, n); return notifier_chain_register(&nh->head, n, false);
mutex_lock(&nh->mutex); mutex_lock(&nh->mutex);
ret = notifier_chain_register(&nh->head, n); ret = notifier_chain_register(&nh->head, n, false);
mutex_unlock(&nh->mutex); mutex_unlock(&nh->mutex);
return ret; return ret;
} }