clockevents: Make minimum delay adjustments configurable
The automatic increase of the min_delta_ns of a clockevents device should be done in the clockevents code as the minimum delay is an attribute of the clockevents device. In addition not all architectures want the automatic adjustment, on a massively virtualized system it can happen that the programming of a clock event fails several times in a row because the virtual cpu has been rescheduled quickly enough. In that case the minimum delay will erroneously be increased with no way back. The new config symbol GENERIC_CLOCKEVENTS_MIN_ADJUST is used to enable the automatic adjustment. The config option is selected only for x86. Signed-off-by: Martin Schwidefsky <schwidefsky@de.ibm.com> Cc: john stultz <johnstul@us.ibm.com> Link: http://lkml.kernel.org/r/20110823133142.494157493@de.ibm.com Signed-off-by: Thomas Gleixner <tglx@linutronix.de>
This commit is contained in:
parent
29c158e81c
commit
d1748302f7
|
@ -68,6 +68,7 @@ config X86
|
||||||
select GENERIC_IRQ_PROBE
|
select GENERIC_IRQ_PROBE
|
||||||
select GENERIC_PENDING_IRQ if SMP
|
select GENERIC_PENDING_IRQ if SMP
|
||||||
select GENERIC_IRQ_SHOW
|
select GENERIC_IRQ_SHOW
|
||||||
|
select GENERIC_CLOCKEVENTS_MIN_ADJUST
|
||||||
select IRQ_FORCED_THREADING
|
select IRQ_FORCED_THREADING
|
||||||
select USE_GENERIC_SMP_HELPERS if SMP
|
select USE_GENERIC_SMP_HELPERS if SMP
|
||||||
select HAVE_BPF_JIT if (X86_64 && NET)
|
select HAVE_BPF_JIT if (X86_64 && NET)
|
||||||
|
|
|
@ -140,7 +140,7 @@ extern void clockevents_set_mode(struct clock_event_device *dev,
|
||||||
enum clock_event_mode mode);
|
enum clock_event_mode mode);
|
||||||
extern int clockevents_register_notifier(struct notifier_block *nb);
|
extern int clockevents_register_notifier(struct notifier_block *nb);
|
||||||
extern int clockevents_program_event(struct clock_event_device *dev,
|
extern int clockevents_program_event(struct clock_event_device *dev,
|
||||||
ktime_t expires, ktime_t now);
|
ktime_t expires, bool force);
|
||||||
|
|
||||||
extern void clockevents_handle_noop(struct clock_event_device *dev);
|
extern void clockevents_handle_noop(struct clock_event_device *dev);
|
||||||
|
|
||||||
|
|
|
@ -27,3 +27,5 @@ config GENERIC_CLOCKEVENTS_BUILD
|
||||||
default y
|
default y
|
||||||
depends on GENERIC_CLOCKEVENTS || GENERIC_CLOCKEVENTS_MIGR
|
depends on GENERIC_CLOCKEVENTS || GENERIC_CLOCKEVENTS_MIGR
|
||||||
|
|
||||||
|
config GENERIC_CLOCKEVENTS_MIN_ADJUST
|
||||||
|
bool
|
||||||
|
|
|
@ -94,42 +94,139 @@ void clockevents_shutdown(struct clock_event_device *dev)
|
||||||
dev->next_event.tv64 = KTIME_MAX;
|
dev->next_event.tv64 = KTIME_MAX;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ifdef CONFIG_GENERIC_CLOCKEVENTS_MIN_ADJUST
|
||||||
|
|
||||||
|
/* Limit min_delta to a jiffie */
|
||||||
|
#define MIN_DELTA_LIMIT (NSEC_PER_SEC / HZ)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* clockevents_increase_min_delta - raise minimum delta of a clock event device
|
||||||
|
* @dev: device to increase the minimum delta
|
||||||
|
*
|
||||||
|
* Returns 0 on success, -ETIME when the minimum delta reached the limit.
|
||||||
|
*/
|
||||||
|
static int clockevents_increase_min_delta(struct clock_event_device *dev)
|
||||||
|
{
|
||||||
|
/* Nothing to do if we already reached the limit */
|
||||||
|
if (dev->min_delta_ns >= MIN_DELTA_LIMIT) {
|
||||||
|
printk(KERN_WARNING "CE: Reprogramming failure. Giving up\n");
|
||||||
|
dev->next_event.tv64 = KTIME_MAX;
|
||||||
|
return -ETIME;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (dev->min_delta_ns < 5000)
|
||||||
|
dev->min_delta_ns = 5000;
|
||||||
|
else
|
||||||
|
dev->min_delta_ns += dev->min_delta_ns >> 1;
|
||||||
|
|
||||||
|
if (dev->min_delta_ns > MIN_DELTA_LIMIT)
|
||||||
|
dev->min_delta_ns = MIN_DELTA_LIMIT;
|
||||||
|
|
||||||
|
printk(KERN_WARNING "CE: %s increased min_delta_ns to %llu nsec\n",
|
||||||
|
dev->name ? dev->name : "?",
|
||||||
|
(unsigned long long) dev->min_delta_ns);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* clockevents_program_min_delta - Set clock event device to the minimum delay.
|
||||||
|
* @dev: device to program
|
||||||
|
*
|
||||||
|
* Returns 0 on success, -ETIME when the retry loop failed.
|
||||||
|
*/
|
||||||
|
static int clockevents_program_min_delta(struct clock_event_device *dev)
|
||||||
|
{
|
||||||
|
unsigned long long clc;
|
||||||
|
int64_t delta;
|
||||||
|
int i;
|
||||||
|
|
||||||
|
for (i = 0;;) {
|
||||||
|
delta = dev->min_delta_ns;
|
||||||
|
dev->next_event = ktime_add_ns(ktime_get(), delta);
|
||||||
|
|
||||||
|
if (dev->mode == CLOCK_EVT_MODE_SHUTDOWN)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
dev->retries++;
|
||||||
|
clc = ((unsigned long long) delta * dev->mult) >> dev->shift;
|
||||||
|
if (dev->set_next_event((unsigned long) clc, dev) == 0)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
if (++i > 2) {
|
||||||
|
/*
|
||||||
|
* We tried 3 times to program the device with the
|
||||||
|
* given min_delta_ns. Try to increase the minimum
|
||||||
|
* delta, if that fails as well get out of here.
|
||||||
|
*/
|
||||||
|
if (clockevents_increase_min_delta(dev))
|
||||||
|
return -ETIME;
|
||||||
|
i = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#else /* CONFIG_GENERIC_CLOCKEVENTS_MIN_ADJUST */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* clockevents_program_min_delta - Set clock event device to the minimum delay.
|
||||||
|
* @dev: device to program
|
||||||
|
*
|
||||||
|
* Returns 0 on success, -ETIME when the retry loop failed.
|
||||||
|
*/
|
||||||
|
static int clockevents_program_min_delta(struct clock_event_device *dev)
|
||||||
|
{
|
||||||
|
unsigned long long clc;
|
||||||
|
int64_t delta;
|
||||||
|
|
||||||
|
delta = dev->min_delta_ns;
|
||||||
|
dev->next_event = ktime_add_ns(ktime_get(), delta);
|
||||||
|
|
||||||
|
if (dev->mode == CLOCK_EVT_MODE_SHUTDOWN)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
dev->retries++;
|
||||||
|
clc = ((unsigned long long) delta * dev->mult) >> dev->shift;
|
||||||
|
return dev->set_next_event((unsigned long) clc, dev);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif /* CONFIG_GENERIC_CLOCKEVENTS_MIN_ADJUST */
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* clockevents_program_event - Reprogram the clock event device.
|
* clockevents_program_event - Reprogram the clock event device.
|
||||||
|
* @dev: device to program
|
||||||
* @expires: absolute expiry time (monotonic clock)
|
* @expires: absolute expiry time (monotonic clock)
|
||||||
|
* @force: program minimum delay if expires can not be set
|
||||||
*
|
*
|
||||||
* Returns 0 on success, -ETIME when the event is in the past.
|
* Returns 0 on success, -ETIME when the event is in the past.
|
||||||
*/
|
*/
|
||||||
int clockevents_program_event(struct clock_event_device *dev, ktime_t expires,
|
int clockevents_program_event(struct clock_event_device *dev, ktime_t expires,
|
||||||
ktime_t now)
|
bool force)
|
||||||
{
|
{
|
||||||
unsigned long long clc;
|
unsigned long long clc;
|
||||||
int64_t delta;
|
int64_t delta;
|
||||||
|
int rc;
|
||||||
|
|
||||||
if (unlikely(expires.tv64 < 0)) {
|
if (unlikely(expires.tv64 < 0)) {
|
||||||
WARN_ON_ONCE(1);
|
WARN_ON_ONCE(1);
|
||||||
return -ETIME;
|
return -ETIME;
|
||||||
}
|
}
|
||||||
|
|
||||||
delta = ktime_to_ns(ktime_sub(expires, now));
|
|
||||||
|
|
||||||
if (delta <= 0)
|
|
||||||
return -ETIME;
|
|
||||||
|
|
||||||
dev->next_event = expires;
|
dev->next_event = expires;
|
||||||
|
|
||||||
if (dev->mode == CLOCK_EVT_MODE_SHUTDOWN)
|
if (dev->mode == CLOCK_EVT_MODE_SHUTDOWN)
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
if (delta > dev->max_delta_ns)
|
delta = ktime_to_ns(ktime_sub(expires, ktime_get()));
|
||||||
delta = dev->max_delta_ns;
|
if (delta <= 0)
|
||||||
if (delta < dev->min_delta_ns)
|
return force ? clockevents_program_min_delta(dev) : -ETIME;
|
||||||
delta = dev->min_delta_ns;
|
|
||||||
|
|
||||||
clc = delta * dev->mult;
|
delta = min(delta, (int64_t) dev->max_delta_ns);
|
||||||
clc >>= dev->shift;
|
delta = max(delta, (int64_t) dev->min_delta_ns);
|
||||||
|
|
||||||
return dev->set_next_event((unsigned long) clc, dev);
|
clc = ((unsigned long long) delta * dev->mult) >> dev->shift;
|
||||||
|
rc = dev->set_next_event((unsigned long) clc, dev);
|
||||||
|
|
||||||
|
return (rc && force) ? clockevents_program_min_delta(dev) : rc;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -258,7 +355,7 @@ int clockevents_update_freq(struct clock_event_device *dev, u32 freq)
|
||||||
if (dev->mode != CLOCK_EVT_MODE_ONESHOT)
|
if (dev->mode != CLOCK_EVT_MODE_ONESHOT)
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
return clockevents_program_event(dev, dev->next_event, ktime_get());
|
return clockevents_program_event(dev, dev->next_event, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|
|
@ -194,7 +194,7 @@ static void tick_handle_periodic_broadcast(struct clock_event_device *dev)
|
||||||
for (next = dev->next_event; ;) {
|
for (next = dev->next_event; ;) {
|
||||||
next = ktime_add(next, tick_period);
|
next = ktime_add(next, tick_period);
|
||||||
|
|
||||||
if (!clockevents_program_event(dev, next, ktime_get()))
|
if (!clockevents_program_event(dev, next, false))
|
||||||
return;
|
return;
|
||||||
tick_do_periodic_broadcast();
|
tick_do_periodic_broadcast();
|
||||||
}
|
}
|
||||||
|
@ -373,7 +373,7 @@ static int tick_broadcast_set_event(ktime_t expires, int force)
|
||||||
{
|
{
|
||||||
struct clock_event_device *bc = tick_broadcast_device.evtdev;
|
struct clock_event_device *bc = tick_broadcast_device.evtdev;
|
||||||
|
|
||||||
return tick_dev_program_event(bc, expires, force);
|
return clockevents_program_event(bc, expires, force);
|
||||||
}
|
}
|
||||||
|
|
||||||
int tick_resume_broadcast_oneshot(struct clock_event_device *bc)
|
int tick_resume_broadcast_oneshot(struct clock_event_device *bc)
|
||||||
|
|
|
@ -94,7 +94,7 @@ void tick_handle_periodic(struct clock_event_device *dev)
|
||||||
*/
|
*/
|
||||||
next = ktime_add(dev->next_event, tick_period);
|
next = ktime_add(dev->next_event, tick_period);
|
||||||
for (;;) {
|
for (;;) {
|
||||||
if (!clockevents_program_event(dev, next, ktime_get()))
|
if (!clockevents_program_event(dev, next, false))
|
||||||
return;
|
return;
|
||||||
/*
|
/*
|
||||||
* Have to be careful here. If we're in oneshot mode,
|
* Have to be careful here. If we're in oneshot mode,
|
||||||
|
@ -137,7 +137,7 @@ void tick_setup_periodic(struct clock_event_device *dev, int broadcast)
|
||||||
clockevents_set_mode(dev, CLOCK_EVT_MODE_ONESHOT);
|
clockevents_set_mode(dev, CLOCK_EVT_MODE_ONESHOT);
|
||||||
|
|
||||||
for (;;) {
|
for (;;) {
|
||||||
if (!clockevents_program_event(dev, next, ktime_get()))
|
if (!clockevents_program_event(dev, next, false))
|
||||||
return;
|
return;
|
||||||
next = ktime_add(next, tick_period);
|
next = ktime_add(next, tick_period);
|
||||||
}
|
}
|
||||||
|
|
|
@ -26,8 +26,6 @@ extern void clockevents_shutdown(struct clock_event_device *dev);
|
||||||
extern void tick_setup_oneshot(struct clock_event_device *newdev,
|
extern void tick_setup_oneshot(struct clock_event_device *newdev,
|
||||||
void (*handler)(struct clock_event_device *),
|
void (*handler)(struct clock_event_device *),
|
||||||
ktime_t nextevt);
|
ktime_t nextevt);
|
||||||
extern int tick_dev_program_event(struct clock_event_device *dev,
|
|
||||||
ktime_t expires, int force);
|
|
||||||
extern int tick_program_event(ktime_t expires, int force);
|
extern int tick_program_event(ktime_t expires, int force);
|
||||||
extern void tick_oneshot_notify(void);
|
extern void tick_oneshot_notify(void);
|
||||||
extern int tick_switch_to_oneshot(void (*handler)(struct clock_event_device *));
|
extern int tick_switch_to_oneshot(void (*handler)(struct clock_event_device *));
|
||||||
|
|
|
@ -21,74 +21,6 @@
|
||||||
|
|
||||||
#include "tick-internal.h"
|
#include "tick-internal.h"
|
||||||
|
|
||||||
/* Limit min_delta to a jiffie */
|
|
||||||
#define MIN_DELTA_LIMIT (NSEC_PER_SEC / HZ)
|
|
||||||
|
|
||||||
static int tick_increase_min_delta(struct clock_event_device *dev)
|
|
||||||
{
|
|
||||||
/* Nothing to do if we already reached the limit */
|
|
||||||
if (dev->min_delta_ns >= MIN_DELTA_LIMIT)
|
|
||||||
return -ETIME;
|
|
||||||
|
|
||||||
if (dev->min_delta_ns < 5000)
|
|
||||||
dev->min_delta_ns = 5000;
|
|
||||||
else
|
|
||||||
dev->min_delta_ns += dev->min_delta_ns >> 1;
|
|
||||||
|
|
||||||
if (dev->min_delta_ns > MIN_DELTA_LIMIT)
|
|
||||||
dev->min_delta_ns = MIN_DELTA_LIMIT;
|
|
||||||
|
|
||||||
printk(KERN_WARNING "CE: %s increased min_delta_ns to %llu nsec\n",
|
|
||||||
dev->name ? dev->name : "?",
|
|
||||||
(unsigned long long) dev->min_delta_ns);
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* tick_program_event internal worker function
|
|
||||||
*/
|
|
||||||
int tick_dev_program_event(struct clock_event_device *dev, ktime_t expires,
|
|
||||||
int force)
|
|
||||||
{
|
|
||||||
ktime_t now = ktime_get();
|
|
||||||
int i;
|
|
||||||
|
|
||||||
for (i = 0;;) {
|
|
||||||
int ret = clockevents_program_event(dev, expires, now);
|
|
||||||
|
|
||||||
if (!ret || !force)
|
|
||||||
return ret;
|
|
||||||
|
|
||||||
dev->retries++;
|
|
||||||
/*
|
|
||||||
* We tried 3 times to program the device with the given
|
|
||||||
* min_delta_ns. If that's not working then we increase it
|
|
||||||
* and emit a warning.
|
|
||||||
*/
|
|
||||||
if (++i > 2) {
|
|
||||||
/* Increase the min. delta and try again */
|
|
||||||
if (tick_increase_min_delta(dev)) {
|
|
||||||
/*
|
|
||||||
* Get out of the loop if min_delta_ns
|
|
||||||
* hit the limit already. That's
|
|
||||||
* better than staying here forever.
|
|
||||||
*
|
|
||||||
* We clear next_event so we have a
|
|
||||||
* chance that the box survives.
|
|
||||||
*/
|
|
||||||
printk(KERN_WARNING
|
|
||||||
"CE: Reprogramming failure. Giving up\n");
|
|
||||||
dev->next_event.tv64 = KTIME_MAX;
|
|
||||||
return -ETIME;
|
|
||||||
}
|
|
||||||
i = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
now = ktime_get();
|
|
||||||
expires = ktime_add_ns(now, dev->min_delta_ns);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* tick_program_event
|
* tick_program_event
|
||||||
*/
|
*/
|
||||||
|
@ -96,7 +28,7 @@ int tick_program_event(ktime_t expires, int force)
|
||||||
{
|
{
|
||||||
struct clock_event_device *dev = __this_cpu_read(tick_cpu_device.evtdev);
|
struct clock_event_device *dev = __this_cpu_read(tick_cpu_device.evtdev);
|
||||||
|
|
||||||
return tick_dev_program_event(dev, expires, force);
|
return clockevents_program_event(dev, expires, force);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -104,11 +36,10 @@ int tick_program_event(ktime_t expires, int force)
|
||||||
*/
|
*/
|
||||||
void tick_resume_oneshot(void)
|
void tick_resume_oneshot(void)
|
||||||
{
|
{
|
||||||
struct tick_device *td = &__get_cpu_var(tick_cpu_device);
|
struct clock_event_device *dev = __this_cpu_read(tick_cpu_device.evtdev);
|
||||||
struct clock_event_device *dev = td->evtdev;
|
|
||||||
|
|
||||||
clockevents_set_mode(dev, CLOCK_EVT_MODE_ONESHOT);
|
clockevents_set_mode(dev, CLOCK_EVT_MODE_ONESHOT);
|
||||||
tick_program_event(ktime_get(), 1);
|
clockevents_program_event(dev, ktime_get(), true);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -120,7 +51,7 @@ void tick_setup_oneshot(struct clock_event_device *newdev,
|
||||||
{
|
{
|
||||||
newdev->event_handler = handler;
|
newdev->event_handler = handler;
|
||||||
clockevents_set_mode(newdev, CLOCK_EVT_MODE_ONESHOT);
|
clockevents_set_mode(newdev, CLOCK_EVT_MODE_ONESHOT);
|
||||||
tick_dev_program_event(newdev, next_event, 1);
|
clockevents_program_event(newdev, next_event, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
Loading…
Reference in New Issue