mirror of
https://github.com/torvalds/linux.git
synced 2024-12-22 10:56:40 +00:00
27ce4cb4a0
Impact: timer hang on CPU online observed on AMD C1E systems When a CPU is brought online then the broadcast machinery can be in the one shot state already. Check this and setup the timer device of the new CPU in one shot mode so the broadcast code can pick up the next_event value correctly. Another AMD C1E oddity, as we switch to broadcast immediately and not after the full bring up via the ACPI cpu idle code. Signed-off-by: Thomas Gleixner <tglx@linutronix.de>
395 lines
8.8 KiB
C
395 lines
8.8 KiB
C
/*
|
|
* linux/kernel/time/tick-common.c
|
|
*
|
|
* This file contains the base functions to manage periodic tick
|
|
* related events.
|
|
*
|
|
* Copyright(C) 2005-2006, Thomas Gleixner <tglx@linutronix.de>
|
|
* Copyright(C) 2005-2007, Red Hat, Inc., Ingo Molnar
|
|
* Copyright(C) 2006-2007, Timesys Corp., Thomas Gleixner
|
|
*
|
|
* This code is licenced under the GPL version 2. For details see
|
|
* kernel-base/COPYING.
|
|
*/
|
|
#include <linux/cpu.h>
|
|
#include <linux/err.h>
|
|
#include <linux/hrtimer.h>
|
|
#include <linux/interrupt.h>
|
|
#include <linux/percpu.h>
|
|
#include <linux/profile.h>
|
|
#include <linux/sched.h>
|
|
#include <linux/tick.h>
|
|
|
|
#include <asm/irq_regs.h>
|
|
|
|
#include "tick-internal.h"
|
|
|
|
/*
|
|
* Tick devices
|
|
*/
|
|
DEFINE_PER_CPU(struct tick_device, tick_cpu_device);
|
|
/*
|
|
* Tick next event: keeps track of the tick time
|
|
*/
|
|
ktime_t tick_next_period;
|
|
ktime_t tick_period;
|
|
int tick_do_timer_cpu __read_mostly = TICK_DO_TIMER_BOOT;
|
|
DEFINE_SPINLOCK(tick_device_lock);
|
|
|
|
/*
|
|
* Debugging: see timer_list.c
|
|
*/
|
|
struct tick_device *tick_get_device(int cpu)
|
|
{
|
|
return &per_cpu(tick_cpu_device, cpu);
|
|
}
|
|
|
|
/**
|
|
* tick_is_oneshot_available - check for a oneshot capable event device
|
|
*/
|
|
int tick_is_oneshot_available(void)
|
|
{
|
|
struct clock_event_device *dev = __get_cpu_var(tick_cpu_device).evtdev;
|
|
|
|
return dev && (dev->features & CLOCK_EVT_FEAT_ONESHOT);
|
|
}
|
|
|
|
/*
|
|
* Periodic tick
|
|
*/
|
|
static void tick_periodic(int cpu)
|
|
{
|
|
if (tick_do_timer_cpu == cpu) {
|
|
write_seqlock(&xtime_lock);
|
|
|
|
/* Keep track of the next tick event */
|
|
tick_next_period = ktime_add(tick_next_period, tick_period);
|
|
|
|
do_timer(1);
|
|
write_sequnlock(&xtime_lock);
|
|
}
|
|
|
|
update_process_times(user_mode(get_irq_regs()));
|
|
profile_tick(CPU_PROFILING);
|
|
}
|
|
|
|
/*
|
|
* Event handler for periodic ticks
|
|
*/
|
|
void tick_handle_periodic(struct clock_event_device *dev)
|
|
{
|
|
int cpu = smp_processor_id();
|
|
ktime_t next;
|
|
|
|
tick_periodic(cpu);
|
|
|
|
if (dev->mode != CLOCK_EVT_MODE_ONESHOT)
|
|
return;
|
|
/*
|
|
* Setup the next period for devices, which do not have
|
|
* periodic mode:
|
|
*/
|
|
next = ktime_add(dev->next_event, tick_period);
|
|
for (;;) {
|
|
if (!clockevents_program_event(dev, next, ktime_get()))
|
|
return;
|
|
tick_periodic(cpu);
|
|
next = ktime_add(next, tick_period);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Setup the device for a periodic tick
|
|
*/
|
|
void tick_setup_periodic(struct clock_event_device *dev, int broadcast)
|
|
{
|
|
tick_set_periodic_handler(dev, broadcast);
|
|
|
|
/* Broadcast setup ? */
|
|
if (!tick_device_is_functional(dev))
|
|
return;
|
|
|
|
if ((dev->features & CLOCK_EVT_FEAT_PERIODIC) &&
|
|
!tick_broadcast_oneshot_active()) {
|
|
clockevents_set_mode(dev, CLOCK_EVT_MODE_PERIODIC);
|
|
} else {
|
|
unsigned long seq;
|
|
ktime_t next;
|
|
|
|
do {
|
|
seq = read_seqbegin(&xtime_lock);
|
|
next = tick_next_period;
|
|
} while (read_seqretry(&xtime_lock, seq));
|
|
|
|
clockevents_set_mode(dev, CLOCK_EVT_MODE_ONESHOT);
|
|
|
|
for (;;) {
|
|
if (!clockevents_program_event(dev, next, ktime_get()))
|
|
return;
|
|
next = ktime_add(next, tick_period);
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Setup the tick device
|
|
*/
|
|
static void tick_setup_device(struct tick_device *td,
|
|
struct clock_event_device *newdev, int cpu,
|
|
const cpumask_t *cpumask)
|
|
{
|
|
ktime_t next_event;
|
|
void (*handler)(struct clock_event_device *) = NULL;
|
|
|
|
/*
|
|
* First device setup ?
|
|
*/
|
|
if (!td->evtdev) {
|
|
/*
|
|
* If no cpu took the do_timer update, assign it to
|
|
* this cpu:
|
|
*/
|
|
if (tick_do_timer_cpu == TICK_DO_TIMER_BOOT) {
|
|
tick_do_timer_cpu = cpu;
|
|
tick_next_period = ktime_get();
|
|
tick_period = ktime_set(0, NSEC_PER_SEC / HZ);
|
|
}
|
|
|
|
/*
|
|
* Startup in periodic mode first.
|
|
*/
|
|
td->mode = TICKDEV_MODE_PERIODIC;
|
|
} else {
|
|
handler = td->evtdev->event_handler;
|
|
next_event = td->evtdev->next_event;
|
|
td->evtdev->event_handler = clockevents_handle_noop;
|
|
}
|
|
|
|
td->evtdev = newdev;
|
|
|
|
/*
|
|
* When the device is not per cpu, pin the interrupt to the
|
|
* current cpu:
|
|
*/
|
|
if (!cpus_equal(newdev->cpumask, *cpumask))
|
|
irq_set_affinity(newdev->irq, *cpumask);
|
|
|
|
/*
|
|
* When global broadcasting is active, check if the current
|
|
* device is registered as a placeholder for broadcast mode.
|
|
* This allows us to handle this x86 misfeature in a generic
|
|
* way.
|
|
*/
|
|
if (tick_device_uses_broadcast(newdev, cpu))
|
|
return;
|
|
|
|
if (td->mode == TICKDEV_MODE_PERIODIC)
|
|
tick_setup_periodic(newdev, 0);
|
|
else
|
|
tick_setup_oneshot(newdev, handler, next_event);
|
|
}
|
|
|
|
/*
|
|
* Check, if the new registered device should be used.
|
|
*/
|
|
static int tick_check_new_device(struct clock_event_device *newdev)
|
|
{
|
|
struct clock_event_device *curdev;
|
|
struct tick_device *td;
|
|
int cpu, ret = NOTIFY_OK;
|
|
unsigned long flags;
|
|
|
|
spin_lock_irqsave(&tick_device_lock, flags);
|
|
|
|
cpu = smp_processor_id();
|
|
if (!cpu_isset(cpu, newdev->cpumask))
|
|
goto out_bc;
|
|
|
|
td = &per_cpu(tick_cpu_device, cpu);
|
|
curdev = td->evtdev;
|
|
|
|
/* cpu local device ? */
|
|
if (!cpus_equal(newdev->cpumask, cpumask_of_cpu(cpu))) {
|
|
|
|
/*
|
|
* If the cpu affinity of the device interrupt can not
|
|
* be set, ignore it.
|
|
*/
|
|
if (!irq_can_set_affinity(newdev->irq))
|
|
goto out_bc;
|
|
|
|
/*
|
|
* If we have a cpu local device already, do not replace it
|
|
* by a non cpu local device
|
|
*/
|
|
if (curdev && cpus_equal(curdev->cpumask, cpumask_of_cpu(cpu)))
|
|
goto out_bc;
|
|
}
|
|
|
|
/*
|
|
* If we have an active device, then check the rating and the oneshot
|
|
* feature.
|
|
*/
|
|
if (curdev) {
|
|
/*
|
|
* Prefer one shot capable devices !
|
|
*/
|
|
if ((curdev->features & CLOCK_EVT_FEAT_ONESHOT) &&
|
|
!(newdev->features & CLOCK_EVT_FEAT_ONESHOT))
|
|
goto out_bc;
|
|
/*
|
|
* Check the rating
|
|
*/
|
|
if (curdev->rating >= newdev->rating)
|
|
goto out_bc;
|
|
}
|
|
|
|
/*
|
|
* Replace the eventually existing device by the new
|
|
* device. If the current device is the broadcast device, do
|
|
* not give it back to the clockevents layer !
|
|
*/
|
|
if (tick_is_broadcast_device(curdev)) {
|
|
clockevents_shutdown(curdev);
|
|
curdev = NULL;
|
|
}
|
|
clockevents_exchange_device(curdev, newdev);
|
|
tick_setup_device(td, newdev, cpu, &cpumask_of_cpu(cpu));
|
|
if (newdev->features & CLOCK_EVT_FEAT_ONESHOT)
|
|
tick_oneshot_notify();
|
|
|
|
spin_unlock_irqrestore(&tick_device_lock, flags);
|
|
return NOTIFY_STOP;
|
|
|
|
out_bc:
|
|
/*
|
|
* Can the new device be used as a broadcast device ?
|
|
*/
|
|
if (tick_check_broadcast_device(newdev))
|
|
ret = NOTIFY_STOP;
|
|
|
|
spin_unlock_irqrestore(&tick_device_lock, flags);
|
|
|
|
return ret;
|
|
}
|
|
|
|
/*
|
|
* Shutdown an event device on a given cpu:
|
|
*
|
|
* This is called on a life CPU, when a CPU is dead. So we cannot
|
|
* access the hardware device itself.
|
|
* We just set the mode and remove it from the lists.
|
|
*/
|
|
static void tick_shutdown(unsigned int *cpup)
|
|
{
|
|
struct tick_device *td = &per_cpu(tick_cpu_device, *cpup);
|
|
struct clock_event_device *dev = td->evtdev;
|
|
unsigned long flags;
|
|
|
|
spin_lock_irqsave(&tick_device_lock, flags);
|
|
td->mode = TICKDEV_MODE_PERIODIC;
|
|
if (dev) {
|
|
/*
|
|
* Prevent that the clock events layer tries to call
|
|
* the set mode function!
|
|
*/
|
|
dev->mode = CLOCK_EVT_MODE_UNUSED;
|
|
clockevents_exchange_device(dev, NULL);
|
|
td->evtdev = NULL;
|
|
}
|
|
/* Transfer the do_timer job away from this cpu */
|
|
if (*cpup == tick_do_timer_cpu) {
|
|
int cpu = first_cpu(cpu_online_map);
|
|
|
|
tick_do_timer_cpu = (cpu != NR_CPUS) ? cpu :
|
|
TICK_DO_TIMER_NONE;
|
|
}
|
|
spin_unlock_irqrestore(&tick_device_lock, flags);
|
|
}
|
|
|
|
static void tick_suspend(void)
|
|
{
|
|
struct tick_device *td = &__get_cpu_var(tick_cpu_device);
|
|
unsigned long flags;
|
|
|
|
spin_lock_irqsave(&tick_device_lock, flags);
|
|
clockevents_shutdown(td->evtdev);
|
|
spin_unlock_irqrestore(&tick_device_lock, flags);
|
|
}
|
|
|
|
static void tick_resume(void)
|
|
{
|
|
struct tick_device *td = &__get_cpu_var(tick_cpu_device);
|
|
unsigned long flags;
|
|
int broadcast = tick_resume_broadcast();
|
|
|
|
spin_lock_irqsave(&tick_device_lock, flags);
|
|
clockevents_set_mode(td->evtdev, CLOCK_EVT_MODE_RESUME);
|
|
|
|
if (!broadcast) {
|
|
if (td->mode == TICKDEV_MODE_PERIODIC)
|
|
tick_setup_periodic(td->evtdev, 0);
|
|
else
|
|
tick_resume_oneshot();
|
|
}
|
|
spin_unlock_irqrestore(&tick_device_lock, flags);
|
|
}
|
|
|
|
/*
|
|
* Notification about clock event devices
|
|
*/
|
|
static int tick_notify(struct notifier_block *nb, unsigned long reason,
|
|
void *dev)
|
|
{
|
|
switch (reason) {
|
|
|
|
case CLOCK_EVT_NOTIFY_ADD:
|
|
return tick_check_new_device(dev);
|
|
|
|
case CLOCK_EVT_NOTIFY_BROADCAST_ON:
|
|
case CLOCK_EVT_NOTIFY_BROADCAST_OFF:
|
|
case CLOCK_EVT_NOTIFY_BROADCAST_FORCE:
|
|
tick_broadcast_on_off(reason, dev);
|
|
break;
|
|
|
|
case CLOCK_EVT_NOTIFY_BROADCAST_ENTER:
|
|
case CLOCK_EVT_NOTIFY_BROADCAST_EXIT:
|
|
tick_broadcast_oneshot_control(reason);
|
|
break;
|
|
|
|
case CLOCK_EVT_NOTIFY_CPU_DEAD:
|
|
tick_shutdown_broadcast_oneshot(dev);
|
|
tick_shutdown_broadcast(dev);
|
|
tick_shutdown(dev);
|
|
break;
|
|
|
|
case CLOCK_EVT_NOTIFY_SUSPEND:
|
|
tick_suspend();
|
|
tick_suspend_broadcast();
|
|
break;
|
|
|
|
case CLOCK_EVT_NOTIFY_RESUME:
|
|
tick_resume();
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return NOTIFY_OK;
|
|
}
|
|
|
|
static struct notifier_block tick_notifier = {
|
|
.notifier_call = tick_notify,
|
|
};
|
|
|
|
/**
|
|
* tick_init - initialize the tick control
|
|
*
|
|
* Register the notifier with the clockevents framework
|
|
*/
|
|
void __init tick_init(void)
|
|
{
|
|
clockevents_register_notifier(&tick_notifier);
|
|
}
|