From: Thomas Gleixner tglx@linutronix.de
mainline inclusion from mainline-v6.2-rc1 commit 0cc04e80458a822300b93f82ed861a513edde194 category: bugfix bugzilla: https://gitee.com/openeuler/kernel/issues/I7R8WG
Reference: https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?i...
--------------------------------
Tearing down timers which have circular dependencies to other functionality, e.g. workqueues, where the timer can schedule work and work can arm timers, is not trivial.
In those cases it is desired to shutdown the timer in a way which prevents rearming of the timer. The mechanism to do so is to set timer->function to NULL and use this as an indicator for the timer arming functions to ignore the (re)arm request.
Add a shutdown argument to the relevant internal functions which makes the actual deactivation code set timer->function to NULL which in turn prevents rearming of the timer.
Co-developed-by: Steven Rostedt rostedt@goodmis.org Signed-off-by: Steven Rostedt rostedt@goodmis.org Signed-off-by: Thomas Gleixner tglx@linutronix.de Tested-by: Guenter Roeck linux@roeck-us.net Reviewed-by: Jacob Keller jacob.e.keller@intel.com Reviewed-by: Anna-Maria Behnsen anna-maria@linutronix.de Link: https://lore.kernel.org/all/20220407161745.7d6754b3@gandalf.local.home Link: https://lore.kernel.org/all/20221110064101.429013735@goodmis.org Link: https://lore.kernel.org/r/20221123201625.253883224@linutronix.de
Conflict: kernel/time/timer.c
Signed-off-by: Yu Liao liaoyu15@huawei.com --- kernel/time/timer.c | 62 +++++++++++++++++++++++++++++++++++++++------ 1 file changed, 54 insertions(+), 8 deletions(-)
diff --git a/kernel/time/timer.c b/kernel/time/timer.c index 7461c38eb417..ab0fadb8c0b0 100644 --- a/kernel/time/timer.c +++ b/kernel/time/timer.c @@ -1233,12 +1233,19 @@ EXPORT_SYMBOL_GPL(add_timer_on); /** * __del_timer - Internal function: Deactivate a timer * @timer: The timer to be deactivated + * @shutdown: If true, this indicates that the timer is about to be + * shutdown permanently. + * + * If @shutdown is true then @timer->function is set to NULL under the + * timer base lock which prevents further rearming of the time. In that + * case any attempt to rearm @timer after this function returns will be + * silently ignored. * * Return: * * %0 - The timer was not pending * * %1 - The timer was pending and deactivated */ -static int __del_timer(struct timer_list *timer) +static int __del_timer(struct timer_list *timer, bool shutdown) { struct timer_base *base; unsigned long flags; @@ -1246,9 +1253,22 @@ static int __del_timer(struct timer_list *timer)
debug_assert_init(timer);
- if (timer_pending(timer)) { + /* + * If @shutdown is set then the lock has to be taken whether the + * timer is pending or not to protect against a concurrent rearm + * which might hit between the lockless pending check and the lock + * aquisition. By taking the lock it is ensured that such a newly + * enqueued timer is dequeued and cannot end up with + * timer->function == NULL in the expiry code. + * + * If timer->function is currently executed, then this makes sure + * that the callback cannot requeue the timer. + */ + if (timer_pending(timer) || shutdown) { base = lock_timer_base(timer, &flags); ret = detach_if_pending(timer, base, true); + if (shutdown) + timer->function = NULL; raw_spin_unlock_irqrestore(&base->lock, flags); }
@@ -1271,20 +1291,31 @@ static int __del_timer(struct timer_list *timer) */ int del_timer(struct timer_list *timer) { - return __del_timer(timer); + return __del_timer(timer, false); } EXPORT_SYMBOL(del_timer);
/** * __try_to_del_timer_sync - Internal function: Try to deactivate a timer * @timer: Timer to deactivate + * @shutdown: If true, this indicates that the timer is about to be + * shutdown permanently. + * + * If @shutdown is true then @timer->function is set to NULL under the + * timer base lock which prevents further rearming of the timer. Any + * attempt to rearm @timer after this function returns will be silently + * ignored. + * + * This function cannot guarantee that the timer cannot be rearmed + * right after dropping the base lock if @shutdown is false. That + * needs to be prevented by the calling code if necessary. * * Return: * * %0 - The timer was not pending * * %1 - The timer was pending and deactivated * * %-1 - The timer callback function is running on a different CPU */ -static int __try_to_del_timer_sync(struct timer_list *timer) +static int __try_to_del_timer_sync(struct timer_list *timer, bool shutdown) { struct timer_base *base; unsigned long flags; @@ -1296,6 +1327,8 @@ static int __try_to_del_timer_sync(struct timer_list *timer)
if (base->running_timer != timer) ret = detach_if_pending(timer, base, true); + if (shutdown) + timer->function = NULL;
raw_spin_unlock_irqrestore(&base->lock, flags);
@@ -1320,7 +1353,7 @@ static int __try_to_del_timer_sync(struct timer_list *timer) */ int try_to_del_timer_sync(struct timer_list *timer) { - return __try_to_del_timer_sync(timer); + return __try_to_del_timer_sync(timer, false); } EXPORT_SYMBOL(try_to_del_timer_sync);
@@ -1328,12 +1361,25 @@ EXPORT_SYMBOL(try_to_del_timer_sync); * __del_timer_sync - Internal function: Deactivate a timer and wait * for the handler to finish. * @timer: The timer to be deactivated + * @shutdown: If true, @timer->function will be set to NULL under the + * timer base lock which prevents rearming of @timer + * + * If @shutdown is not set the timer can be rearmed later. If the timer can + * be rearmed concurrently, i.e. after dropping the base lock then the + * return value is meaningless. + * + * If @shutdown is set then @timer->function is set to NULL under timer + * base lock which prevents rearming of the timer. Any attempt to rearm + * a shutdown timer is silently ignored. + * + * If the timer should be reused after shutdown it has to be initialized + * again. * * Return: * * %0 - The timer was not pending * * %1 - The timer was pending and deactivated */ -static int __del_timer_sync(struct timer_list *timer) +static int __del_timer_sync(struct timer_list *timer, bool shutdown) { #ifdef CONFIG_LOCKDEP unsigned long flags; @@ -1353,7 +1399,7 @@ static int __del_timer_sync(struct timer_list *timer) */ WARN_ON(in_irq() && !(timer->flags & TIMER_IRQSAFE)); for (;;) { - int ret = __try_to_del_timer_sync(timer); + int ret = __try_to_del_timer_sync(timer, shutdown); if (ret >= 0) return ret; cpu_relax(); @@ -1402,7 +1448,7 @@ static int __del_timer_sync(struct timer_list *timer) */ int del_timer_sync(struct timer_list *timer) { - return __del_timer_sync(timer); + return __del_timer_sync(timer, false); } EXPORT_SYMBOL(del_timer_sync); #endif