Commit fb02fbc1 authored by Thomas Gleixner's avatar Thomas Gleixner Committed by Thomas Gleixner

NOHZ: restart tick device from irq_enter()

We did not restart the tick device from irq_enter() to avoid double
reprogramming and extra events in the return immediate to idle case.

But long lasting softirqs can lead to a situation where jiffies become
stale:

idle()
  tick stopped (reprogrammed to next pending timer)
  halt()
   interrupt
     jiffies updated from irq_enter()
     interrupt handler
     softirq function 1 runs 20ms
     softirq function 2 arms a 10ms timer with a stale jiffies value
     jiffies updated from irq_exit()
     timer wheel has now an already expired timer
     (the one added in function 2)
     timer fires and timer softirq runs

This was discovered when debugging a timer problem which happend only
when the ath5k driver is active. The debugging proved that there is a
softirq function running for more than 20ms, which is a bug by itself.

To solve this we restart the tick timer right from irq_enter(), but do
not go through the other functions which are necessary to return from
idle when need_resched() is set.
Reported-by: default avatarElias Oltmanns <eo@nebensachen.de>
Signed-off-by: default avatarThomas Gleixner <tglx@linutronix.de>
Tested-by: default avatarElias Oltmanns <eo@nebensachen.de>
parent c34bec5a
...@@ -383,6 +383,19 @@ int tick_resume_broadcast_oneshot(struct clock_event_device *bc) ...@@ -383,6 +383,19 @@ int tick_resume_broadcast_oneshot(struct clock_event_device *bc)
return 0; return 0;
} }
/*
* Called from irq_enter() when idle was interrupted to reenable the
* per cpu device.
*/
void tick_check_oneshot_broadcast(int cpu)
{
if (cpu_isset(cpu, tick_broadcast_oneshot_mask)) {
struct tick_device *td = &per_cpu(tick_cpu_device, cpu);
clockevents_set_mode(td->evtdev, CLOCK_EVT_MODE_ONESHOT);
}
}
/* /*
* Handle oneshot mode broadcasting * Handle oneshot mode broadcasting
*/ */
......
...@@ -36,6 +36,7 @@ extern void tick_broadcast_switch_to_oneshot(void); ...@@ -36,6 +36,7 @@ extern void tick_broadcast_switch_to_oneshot(void);
extern void tick_shutdown_broadcast_oneshot(unsigned int *cpup); extern void tick_shutdown_broadcast_oneshot(unsigned int *cpup);
extern int tick_resume_broadcast_oneshot(struct clock_event_device *bc); extern int tick_resume_broadcast_oneshot(struct clock_event_device *bc);
extern int tick_broadcast_oneshot_active(void); extern int tick_broadcast_oneshot_active(void);
extern void tick_check_oneshot_broadcast(int cpu);
# else /* BROADCAST */ # else /* BROADCAST */
static inline void tick_broadcast_setup_oneshot(struct clock_event_device *bc) static inline void tick_broadcast_setup_oneshot(struct clock_event_device *bc)
{ {
...@@ -45,6 +46,7 @@ static inline void tick_broadcast_oneshot_control(unsigned long reason) { } ...@@ -45,6 +46,7 @@ static inline void tick_broadcast_oneshot_control(unsigned long reason) { }
static inline void tick_broadcast_switch_to_oneshot(void) { } static inline void tick_broadcast_switch_to_oneshot(void) { }
static inline void tick_shutdown_broadcast_oneshot(unsigned int *cpup) { } static inline void tick_shutdown_broadcast_oneshot(unsigned int *cpup) { }
static inline int tick_broadcast_oneshot_active(void) { return 0; } static inline int tick_broadcast_oneshot_active(void) { return 0; }
static inline void tick_check_oneshot_broadcast(int cpu) { }
# endif /* !BROADCAST */ # endif /* !BROADCAST */
#else /* !ONESHOT */ #else /* !ONESHOT */
......
...@@ -508,10 +508,6 @@ static void tick_nohz_handler(struct clock_event_device *dev) ...@@ -508,10 +508,6 @@ static void tick_nohz_handler(struct clock_event_device *dev)
update_process_times(user_mode(regs)); update_process_times(user_mode(regs));
profile_tick(CPU_PROFILING); profile_tick(CPU_PROFILING);
/* Do not restart, when we are in the idle loop */
if (ts->tick_stopped)
return;
while (tick_nohz_reprogram(ts, now)) { while (tick_nohz_reprogram(ts, now)) {
now = ktime_get(); now = ktime_get();
tick_do_update_jiffies64(now); tick_do_update_jiffies64(now);
...@@ -557,6 +553,27 @@ static void tick_nohz_switch_to_nohz(void) ...@@ -557,6 +553,27 @@ static void tick_nohz_switch_to_nohz(void)
smp_processor_id()); smp_processor_id());
} }
/*
* When NOHZ is enabled and the tick is stopped, we need to kick the
* tick timer from irq_enter() so that the jiffies update is kept
* alive during long running softirqs. That's ugly as hell, but
* correctness is key even if we need to fix the offending softirq in
* the first place.
*
* Note, this is different to tick_nohz_restart. We just kick the
* timer and do not touch the other magic bits which need to be done
* when idle is left.
*/
static void tick_nohz_kick_tick(int cpu)
{
struct tick_sched *ts = &per_cpu(tick_cpu_sched, cpu);
if (!ts->tick_stopped)
return;
tick_nohz_restart(ts, ktime_get());
}
#else #else
static inline void tick_nohz_switch_to_nohz(void) { } static inline void tick_nohz_switch_to_nohz(void) { }
...@@ -568,9 +585,11 @@ static inline void tick_nohz_switch_to_nohz(void) { } ...@@ -568,9 +585,11 @@ static inline void tick_nohz_switch_to_nohz(void) { }
*/ */
void tick_check_idle(int cpu) void tick_check_idle(int cpu)
{ {
tick_check_oneshot_broadcast(cpu);
#ifdef CONFIG_NO_HZ #ifdef CONFIG_NO_HZ
tick_nohz_stop_idle(cpu); tick_nohz_stop_idle(cpu);
tick_nohz_update_jiffies(); tick_nohz_update_jiffies();
tick_nohz_kick_tick(cpu);
#endif #endif
} }
...@@ -627,10 +646,6 @@ static enum hrtimer_restart tick_sched_timer(struct hrtimer *timer) ...@@ -627,10 +646,6 @@ static enum hrtimer_restart tick_sched_timer(struct hrtimer *timer)
profile_tick(CPU_PROFILING); profile_tick(CPU_PROFILING);
} }
/* Do not restart, when we are in the idle loop */
if (ts->tick_stopped)
return HRTIMER_NORESTART;
hrtimer_forward(timer, now, tick_period); hrtimer_forward(timer, now, tick_period);
return HRTIMER_RESTART; return HRTIMER_RESTART;
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment