]> xenbits.xensource.com Git - people/dariof/xen.git/commitdiff
RCU: let the RCU idle timer handler run
authorDario Faggioli <dario.faggioli@citrix.com>
Mon, 9 Oct 2017 11:22:07 +0000 (13:22 +0200)
committerJan Beulich <jbeulich@suse.com>
Mon, 9 Oct 2017 11:22:07 +0000 (13:22 +0200)
If stop_timer() is called between when the RCU
idle timer's interrupt arrives (and TIMER_SOFTIRQ is
raised) and when softirqs are checked and handled, the
timer is deactivated, and the handler never runs.

This happens to the RCU idle timer because stop_timer()
is called on it during the wakeup from idle (e.g., C-states,
on x86) path.

To fix that, we avoid calling stop_timer(), in case we see
that the timer itself is:
- still active,
- expired (i.e., it's expiry time is in the past).
In fact, that indicates (for this particular timer) that
it has fired, and we are just about to handle the TIMER_SOFTIRQ
(which will perform the timer deactivation and run its handler).

Signed-off-by: Dario Faggioli <dario.faggioli@citrix.com>
Reviewed-by: Jan Beulich <jbeulich@suse.com>
xen/common/rcupdate.c
xen/common/timer.c
xen/include/xen/timer.h

index 871936f11b8a94329f87b914dc1e3ed6ecb35e47..252e01b9c07fdc192322ad4f7b3da69993c0a349 100644 (file)
@@ -465,7 +465,24 @@ void rcu_idle_timer_stop()
         return;
 
     rdp->idle_timer_active = false;
-    stop_timer(&rdp->idle_timer);
+
+    /*
+     * In general, as the CPU is becoming active again, we don't need the
+     * idle timer, and so we want to stop it.
+     *
+     * However, in case we are here because idle_timer has (just) fired and
+     * has woken up the CPU, we skip stop_timer() now. In fact, when a CPU
+     * wakes up from idle, this code always runs before do_softirq() has the
+     * chance to check and deal with TIMER_SOFTIRQ. And if we stop the timer
+     * now, the TIMER_SOFTIRQ handler will see it as inactive, and will not
+     * call rcu_idle_timer_handler().
+     *
+     * Therefore, if we see that the timer is expired already, we leave it
+     * alone. The TIMER_SOFTIRQ handler will then run the timer routine, and
+     * deactivate it.
+     */
+    if ( !timer_is_expired(&rdp->idle_timer) )
+        stop_timer(&rdp->idle_timer);
 }
 
 static void rcu_idle_timer_handler(void* data)
index d9ff66951344fe20fcabb0571d143b7fd5cbcb58..376581bd54530dc4b3ec06eb9b0fedabc97c7415 100644 (file)
@@ -331,6 +331,20 @@ void stop_timer(struct timer *timer)
     timer_unlock_irqrestore(timer, flags);
 }
 
+bool timer_expires_before(struct timer *timer, s_time_t t)
+{
+    unsigned long flags;
+    bool ret;
+
+    if ( !timer_lock_irqsave(timer, flags) )
+        return false;
+
+    ret = active_timer(timer) && timer->expires <= t;
+
+    timer_unlock_irqrestore(timer, flags);
+
+    return ret;
+}
 
 void migrate_timer(struct timer *timer, unsigned int new_cpu)
 {
index 9531800defef7cd0de9f58fee848dd9cd7fa1f9b..4513260b0d8603cec8b269ea028a59ec0e1e54b6 100644 (file)
@@ -70,6 +70,11 @@ void set_timer(struct timer *timer, s_time_t expires);
  */
 void stop_timer(struct timer *timer);
 
+/* True if a timer is active, and its expiry time is earlier than t. */
+bool timer_expires_before(struct timer *timer, s_time_t t);
+
+#define timer_is_expired(t) timer_expires_before(t, NOW())
+
 /* Migrate a timer to a different CPU. The timer may be currently active. */
 void migrate_timer(struct timer *timer, unsigned int new_cpu);