diff options
author | Viresh Kumar <viresh.kumar@linaro.org> | 2015-01-29 13:45:52 +0530 |
---|---|---|
committer | Gary S. Robertson <gary.robertson@linaro.org> | 2015-07-22 16:17:11 -0500 |
commit | 60a3281ced1f2ece96266343b585ac83861566a3 (patch) | |
tree | 21379abb35cf7da55796fb7e6c8fe04142b9c465 | |
parent | 7df2229c060b080dcc0db923bbaac4011f36bcd7 (diff) |
clockevents: Introduce CLOCK_EVT_MODE_ONESHOT_STOPPED mode
When no timers/hrtimers are pending, the expiry time is set to a special value:
'KTIME_MAX'. This normally happens with NO_HZ_{IDLE|FULL} in both LOWRES/HIGHRES
modes.
When 'expiry == KTIME_MAX', we either cancel the 'tick-sched' hrtimer
(NOHZ_MODE_HIGHRES) or skip reprogramming clockevent device (NOHZ_MODE_LOWRES).
But, the clockevent device is already reprogrammed from the tick-handler for
next tick.
As the clock event device is programmed in ONESHOT mode it will atleast fire one
more time (unnecessarily). Timers on many implementations (like arm_arch_timer,
powerpc, etc.) only support PERIODIC mode and their drivers emulate ONESHOT over
that. Which means that on these platforms we will get spurious interrupts at
last programmed interval rate, normally tick rate.
In order to avoid spurious interrupts/wakeups, the clockevent device should be
stopped or its interrupts should be masked.
A simple (yet hacky) solution to get this fixed could be: update
hrtimer_force_reprogram() to always reprogram clockevent device and update
clockevent drivers to STOP generating events (or delay it to max time) when
'expires' is set to KTIME_MAX. But the drawback here is that every clockevent
driver has to be hacked for this particular case and its very easy for new ones
to miss this.
However, Thomas suggested to add an optional mode ONESHOT_STOPPED to solve this
problem: lkml.org/lkml/2014/5/9/508.
This patch adds support for ONESHOT_STOPPED mode in clockevents core. It will
only be available to drivers that implement the mode-specific set-mode callbacks
instead of the legacy ->set_mode() callback.
Signed-off-by: Viresh Kumar <viresh.kumar@linaro.org>
-rw-r--r-- | include/linux/clockchips.h | 5 | ||||
-rw-r--r-- | kernel/time/clockevents.c | 13 | ||||
-rw-r--r-- | kernel/time/timer_list.c | 6 |
3 files changed, 22 insertions, 2 deletions
diff --git a/include/linux/clockchips.h b/include/linux/clockchips.h index 59af26b54d15..fcc570d5fe07 100644 --- a/include/linux/clockchips.h +++ b/include/linux/clockchips.h @@ -41,6 +41,7 @@ enum clock_event_mode { CLOCK_EVT_MODE_RESUME, /* Legacy ->set_mode() callback doesn't support below modes */ + CLOCK_EVT_MODE_ONESHOT_STOPPED, }; /* @@ -86,6 +87,7 @@ enum clock_event_mode { * @set_mode: legacy set mode function, only for modes <= CLOCK_EVT_MODE_RESUME. * @set_mode_periodic: switch mode to periodic, if !set_mode * @set_mode_oneshot: switch mode to oneshot, if !set_mode + * @set_mode_stop_oneshot: stop oneshot mode, if !set_mode * @set_mode_shutdown: switch mode to shutdown, if !set_mode * @set_mode_resume: resume clkevt device, if !set_mode * @broadcast: function to broadcast events @@ -118,12 +120,13 @@ struct clock_event_device { * Mode transition callback(s): Only one of the two groups should be * defined: * - set_mode(), only for modes <= CLOCK_EVT_MODE_RESUME. - * - set_mode_{shutdown|periodic|oneshot|resume}(). + * - set_mode_{shutdown|periodic|oneshot|stop_oneshot|resume}(). */ void (*set_mode)(enum clock_event_mode mode, struct clock_event_device *); int (*set_mode_periodic)(struct clock_event_device *); int (*set_mode_oneshot)(struct clock_event_device *); + int (*set_mode_stop_oneshot)(struct clock_event_device *); int (*set_mode_shutdown)(struct clock_event_device *); int (*set_mode_resume)(struct clock_event_device *); diff --git a/kernel/time/clockevents.c b/kernel/time/clockevents.c index 489642b08d64..808ae0902379 100644 --- a/kernel/time/clockevents.c +++ b/kernel/time/clockevents.c @@ -133,6 +133,16 @@ static int __clockevents_set_mode(struct clock_event_device *dev, return -ENOSYS; return dev->set_mode_oneshot(dev); + case CLOCK_EVT_MODE_ONESHOT_STOPPED: + /* Core internal bug */ + WARN_ONCE(dev->mode != CLOCK_EVT_MODE_ONESHOT, + "Current mode: %d\n", dev->mode); + + if (dev->set_mode_stop_oneshot) + return dev->set_mode_stop_oneshot(dev); + else + return -ENOSYS; + case CLOCK_EVT_MODE_RESUME: /* Optional callback */ if (dev->set_mode_resume) @@ -433,7 +443,8 @@ static int clockevents_sanity_check(struct clock_event_device *dev) if (dev->set_mode) { /* We shouldn't be supporting new modes now */ WARN_ON(dev->set_mode_periodic || dev->set_mode_oneshot || - dev->set_mode_shutdown || dev->set_mode_resume); + dev->set_mode_shutdown || dev->set_mode_resume || + dev->set_mode_stop_oneshot); return 0; } diff --git a/kernel/time/timer_list.c b/kernel/time/timer_list.c index 2cfd19485824..ee3ff65b679d 100644 --- a/kernel/time/timer_list.c +++ b/kernel/time/timer_list.c @@ -251,6 +251,12 @@ print_tickdevice(struct seq_file *m, struct tick_device *td, int cpu) SEQ_printf(m, "\n"); } + if (dev->set_mode_stop_oneshot) { + SEQ_printf(m, " stop_oneshot: "); + print_name_offset(m, dev->set_mode_stop_oneshot); + SEQ_printf(m, "\n"); + } + if (dev->set_mode_resume) { SEQ_printf(m, " resume: "); print_name_offset(m, dev->set_mode_resume); |