aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorViresh Kumar <viresh.kumar@linaro.org>2015-01-29 13:45:52 +0530
committerGary S. Robertson <gary.robertson@linaro.org>2015-07-22 16:17:11 -0500
commit60a3281ced1f2ece96266343b585ac83861566a3 (patch)
tree21379abb35cf7da55796fb7e6c8fe04142b9c465
parent7df2229c060b080dcc0db923bbaac4011f36bcd7 (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.h5
-rw-r--r--kernel/time/clockevents.c13
-rw-r--r--kernel/time/timer_list.c6
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);