aboutsummaryrefslogtreecommitdiffstats
path: root/kernel
diff options
context:
space:
mode:
authorBalasubramani Vivekanandan <balasubramani_vivekanandan@mentor.com>2019-09-26 09:51:01 -0400
committerThomas Gleixner <tglx@linutronix.de>2019-09-27 08:45:55 -0400
commitb9023b91dd020ad7e093baa5122b6968c48cc9e0 (patch)
tree49ee5f67250052e0b76086993a68eb106dcae90d /kernel
parentda05b5ea12c1e50b2988a63470d6b69434796f8b (diff)
tick: broadcast-hrtimer: Fix a race in bc_set_next
When a cpu requests broadcasting, before starting the tick broadcast hrtimer, bc_set_next() checks if the timer callback (bc_handler) is active using hrtimer_try_to_cancel(). But hrtimer_try_to_cancel() does not provide the required synchronization when the callback is active on other core. The callback could have already executed tick_handle_oneshot_broadcast() and could have also returned. But still there is a small time window where the hrtimer_try_to_cancel() returns -1. In that case bc_set_next() returns without doing anything, but the next_event of the tick broadcast clock device is already set to a timeout value. In the race condition diagram below, CPU #1 is running the timer callback and CPU #2 is entering idle state and so calls bc_set_next(). In the worst case, the next_event will contain an expiry time, but the hrtimer will not be started which happens when the racing callback returns HRTIMER_NORESTART. The hrtimer might never recover if all further requests from the CPUs to subscribe to tick broadcast have timeout greater than the next_event of tick broadcast clock device. This leads to cascading of failures and finally noticed as rcu stall warnings Here is a depiction of the race condition CPU #1 (Running timer callback) CPU #2 (Enter idle and subscribe to tick broadcast) --------------------- --------------------- __run_hrtimer() tick_broadcast_enter() bc_handler() __tick_broadcast_oneshot_control() tick_handle_oneshot_broadcast() raw_spin_lock(&tick_broadcast_lock); dev->next_event = KTIME_MAX; //wait for tick_broadcast_lock //next_event for tick broadcast clock set to KTIME_MAX since no other cores subscribed to tick broadcasting raw_spin_unlock(&tick_broadcast_lock); if (dev->next_event == KTIME_MAX) return HRTIMER_NORESTART // callback function exits without restarting the hrtimer //tick_broadcast_lock acquired raw_spin_lock(&tick_broadcast_lock); tick_broadcast_set_event() clockevents_program_event() dev->next_event = expires; bc_set_next() hrtimer_try_to_cancel() //returns -1 since the timer callback is active. Exits without restarting the timer cpu_base->running = NULL; The comment that hrtimer cannot be armed from within the callback is wrong. It is fine to start the hrtimer from within the callback. Also it is safe to start the hrtimer from the enter/exit idle code while the broadcast handler is active. The enter/exit idle code and the broadcast handler are synchronized using tick_broadcast_lock. So there is no need for the existing try to cancel logic. All this can be removed which will eliminate the race condition as well. Fixes: 5d1638acb9f6 ("tick: Introduce hrtimer based broadcast") Originally-by: Thomas Gleixner <tglx@linutronix.de> Signed-off-by: Balasubramani Vivekanandan <balasubramani_vivekanandan@mentor.com> Signed-off-by: Thomas Gleixner <tglx@linutronix.de> Cc: stable@vger.kernel.org Link: https://lkml.kernel.org/r/20190926135101.12102-2-balasubramani_vivekanandan@mentor.com
Diffstat (limited to 'kernel')
-rw-r--r--kernel/time/tick-broadcast-hrtimer.c62
1 files changed, 29 insertions, 33 deletions
diff --git a/kernel/time/tick-broadcast-hrtimer.c b/kernel/time/tick-broadcast-hrtimer.c
index c1f5bb590b5e..b5a65e212df2 100644
--- a/kernel/time/tick-broadcast-hrtimer.c
+++ b/kernel/time/tick-broadcast-hrtimer.c
@@ -42,39 +42,39 @@ static int bc_shutdown(struct clock_event_device *evt)
42 */ 42 */
43static int bc_set_next(ktime_t expires, struct clock_event_device *bc) 43static int bc_set_next(ktime_t expires, struct clock_event_device *bc)
44{ 44{
45 int bc_moved;
46 /* 45 /*
47 * We try to cancel the timer first. If the callback is on 46 * This is called either from enter/exit idle code or from the
48 * flight on some other cpu then we let it handle it. If we 47 * broadcast handler. In all cases tick_broadcast_lock is held.
49 * were able to cancel the timer nothing can rearm it as we
50 * own broadcast_lock.
51 * 48 *
52 * However we can also be called from the event handler of 49 * hrtimer_cancel() cannot be called here neither from the
53 * ce_broadcast_hrtimer itself when it expires. We cannot 50 * broadcast handler nor from the enter/exit idle code. The idle
54 * restart the timer because we are in the callback, but we 51 * code can run into the problem described in bc_shutdown() and the
55 * can set the expiry time and let the callback return 52 * broadcast handler cannot wait for itself to complete for obvious
56 * HRTIMER_RESTART. 53 * reasons.
57 * 54 *
58 * Since we are in the idle loop at this point and because 55 * Each caller tries to arm the hrtimer on its own CPU, but if the
59 * hrtimer_{start/cancel} functions call into tracing, 56 * hrtimer callbback function is currently running, then
60 * calls to these functions must be bound within RCU_NONIDLE. 57 * hrtimer_start() cannot move it and the timer stays on the CPU on
58 * which it is assigned at the moment.
59 *
60 * As this can be called from idle code, the hrtimer_start()
61 * invocation has to be wrapped with RCU_NONIDLE() as
62 * hrtimer_start() can call into tracing.
61 */ 63 */
62 RCU_NONIDLE( 64 RCU_NONIDLE( {
63 { 65 hrtimer_start(&bctimer, expires, HRTIMER_MODE_ABS_PINNED_HARD);
64 bc_moved = hrtimer_try_to_cancel(&bctimer) >= 0; 66 /*
65 if (bc_moved) { 67 * The core tick broadcast mode expects bc->bound_on to be set
66 hrtimer_start(&bctimer, expires, 68 * correctly to prevent a CPU which has the broadcast hrtimer
67 HRTIMER_MODE_ABS_PINNED_HARD); 69 * armed from going deep idle.
68 } 70 *
69 } 71 * As tick_broadcast_lock is held, nothing can change the cpu
70 ); 72 * base which was just established in hrtimer_start() above. So
71 73 * the below access is safe even without holding the hrtimer
72 if (bc_moved) { 74 * base lock.
73 /* Bind the "device" to the cpu */ 75 */
74 bc->bound_on = smp_processor_id(); 76 bc->bound_on = bctimer.base->cpu_base->cpu;
75 } else if (bc->bound_on == smp_processor_id()) { 77 } );
76 hrtimer_set_expires(&bctimer, expires);
77 }
78 return 0; 78 return 0;
79} 79}
80 80
@@ -100,10 +100,6 @@ static enum hrtimer_restart bc_handler(struct hrtimer *t)
100{ 100{
101 ce_broadcast_hrtimer.event_handler(&ce_broadcast_hrtimer); 101 ce_broadcast_hrtimer.event_handler(&ce_broadcast_hrtimer);
102 102
103 if (clockevent_state_oneshot(&ce_broadcast_hrtimer))
104 if (ce_broadcast_hrtimer.next_event != KTIME_MAX)
105 return HRTIMER_RESTART;
106
107 return HRTIMER_NORESTART; 103 return HRTIMER_NORESTART;
108} 104}
109 105