Currently, clockevents_program_event() - compares a given delta against ->min_delta_ns and ->max_delta_ns - and transforms the given delta value from ns to device cycles via ->mult.
Future changes introducing NTP time awareness into the clockevents core would possibly need to update ->mult, ->min_delta_ns and ->max_delta_ns from a different CPU than clockevents_program_event() runs on: the ->*_delta_ns values depend on ->mult. In order to guarantee atomicity between these updates, a seqlock would have to be introduced and acquired for reading in clockevents_program_event(). In order to avoid this, do the bounds checking in terms of the always invariant ->min_delta_ticks and ->max_delta_ticks values. The transition from ->max_delta_ns to ->max_delta_ticks is straight forward since they are 1:1. For ->min_delta_ns, the situation is slightly different as it can get larger than its initial value derived from ->min_delta_ticks for two reason: - ->min_delta_ns is enforced to be larger than 1us at initialization - and it can grow over time with CONFIG_GENERIC_CLOCKEVENTS_MIN_ADJUST, c.f. clockevents_increase_min_delta(). Introduce ->min_delta_ticks_adjusted always resembling the current value of ->min_delta_ns. The original ->min_delta_ticks must be kept for reconfiguration. The invariant ->min_delta_ticks <= ->min_delta_ticks_adjusted will always be guaranteed to hold. This will allow for non-atomic updates of ->mult and ->min_delta_ticks_adjusted -- as long as we stay within a device's allowed bounds, we don't care for small deviations. Make clockevents_program_event() use ->min_delta_ticks_adjusted and ->max_delta_ticks for the bounds enforcement on the delta value. Finally, slightly reorder the members of struct clock_event_device in order to keep the now often used ones close together. Also, since ->max_delta_ticks is being actively used now, remove the assertion that it is "stored for reconfiguration" from its docstring. Signed-off-by: Nicolai Stange <[email protected]> --- include/linux/clockchips.h | 14 ++++++++------ kernel/time/clockevents.c | 15 ++++++++++++--- 2 files changed, 20 insertions(+), 9 deletions(-) diff --git a/include/linux/clockchips.h b/include/linux/clockchips.h index a116926..8578e24 100644 --- a/include/linux/clockchips.h +++ b/include/linux/clockchips.h @@ -73,8 +73,8 @@ enum clock_event_state { * @set_next_event: set next event function using a clocksource delta * @set_next_ktime: set next event function using a direct ktime value * @next_event: local storage for the next event in oneshot mode - * @max_delta_ns: maximum delta value in ns - * @min_delta_ns: minimum delta value in ns + * @max_delta_ticks: maximum delta value in ticks + * @min_delta_ticks_adjusted: minimum delta value, increased as needed * @mult: nanosecond to cycles multiplier * @shift: nanoseconds to cycles divisor (power of two) * @state_use_accessors:current state of the device, assigned by the core code @@ -87,7 +87,8 @@ enum clock_event_state { * @tick_resume: resume clkevt device * @broadcast: function to broadcast events * @min_delta_ticks: minimum delta value in ticks stored for reconfiguration - * @max_delta_ticks: maximum delta value in ticks stored for reconfiguration + * @max_delta_ns: maximum delta value in ns + * @min_delta_ns: minimum delta value in ns * @name: ptr to clock event name * @rating: variable to rate clock event devices * @irq: IRQ number (only for non CPU local devices) @@ -101,8 +102,8 @@ struct clock_event_device { int (*set_next_event)(unsigned long evt, struct clock_event_device *); int (*set_next_ktime)(ktime_t expires, struct clock_event_device *); ktime_t next_event; - u64 max_delta_ns; - u64 min_delta_ns; + unsigned long max_delta_ticks; + unsigned long min_delta_ticks_adjusted; u32 mult; u32 shift; enum clock_event_state state_use_accessors; @@ -119,7 +120,8 @@ struct clock_event_device { void (*suspend)(struct clock_event_device *); void (*resume)(struct clock_event_device *); unsigned long min_delta_ticks; - unsigned long max_delta_ticks; + u64 max_delta_ns; + u64 min_delta_ns; const char *name; int rating; diff --git a/kernel/time/clockevents.c b/kernel/time/clockevents.c index f352f54..7832050 100644 --- a/kernel/time/clockevents.c +++ b/kernel/time/clockevents.c @@ -225,6 +225,11 @@ static int clockevents_increase_min_delta(struct clock_event_device *dev) if (dev->min_delta_ns > MIN_DELTA_LIMIT) dev->min_delta_ns = MIN_DELTA_LIMIT; + dev->min_delta_ticks_adjusted = (unsigned long)((dev->min_delta_ns * + dev->mult) >> dev->shift); + dev->min_delta_ticks_adjusted = max(dev->min_delta_ticks_adjusted, + dev->min_delta_ticks); + printk_deferred(KERN_WARNING "CE: %s increased min_delta_ns to %llu nsec\n", dev->name ? dev->name : "?", @@ -332,10 +337,10 @@ int clockevents_program_event(struct clock_event_device *dev, ktime_t expires, if (delta <= 0) return force ? clockevents_program_min_delta(dev) : -ETIME; - delta = min(delta, (int64_t) dev->max_delta_ns); - delta = max(delta, (int64_t) dev->min_delta_ns); - clc = ((unsigned long long) delta * dev->mult) >> dev->shift; + clc = min_t(unsigned long, clc, dev->max_delta_ticks); + clc = max_t(unsigned long, clc, dev->min_delta_ticks_adjusted); + rc = dev->set_next_event((unsigned long) clc, dev); return (rc && force) ? clockevents_program_min_delta(dev) : rc; @@ -453,6 +458,10 @@ static void __clockevents_update_bounds(struct clock_event_device *dev) */ dev->min_delta_ns = cev_delta2ns(dev->min_delta_ticks, dev, false); dev->max_delta_ns = cev_delta2ns(dev->max_delta_ticks, dev, true); + dev->min_delta_ticks_adjusted = (unsigned long)((dev->min_delta_ns * + dev->mult) >> dev->shift); + dev->min_delta_ticks_adjusted = max(dev->min_delta_ticks_adjusted, + dev->min_delta_ticks); } /** -- 2.9.2

