Commit e9e2cdb4 authored by Thomas Gleixner's avatar Thomas Gleixner Committed by Linus Torvalds
Browse files

[PATCH] clockevents: i386 drivers



Add clockevent drivers for i386: lapic (local) and PIT/HPET (global).  Update
the timer IRQ to call into the PIT/HPET driver's event handler and the
lapic-timer IRQ to call into the lapic clockevent driver.  The assignement of
timer functionality is delegated to the core framework code and replaces the
compile and runtime evalution in do_timer_interrupt_hook()

Use the clockevents broadcast support and implement the lapic_broadcast
function for ACPI.

No changes to existing functionality.

[ kdump fix from Vivek Goyal <vgoyal@in.ibm.com> ]
[ fixes based on review feedback from Arjan van de Ven <arjan@infradead.org> ]
Cleanups-from: Adrian Bunk <bunk@stusta.de>
Build-fixes-from: Andrew Morton <akpm@osdl.org>
Signed-off-by: default avatarThomas Gleixner <tglx@linutronix.de>
Signed-off-by: default avatarIngo Molnar <mingo@elte.hu>
Cc: john stultz <johnstul@us.ibm.com>
Cc: Roman Zippel <zippel@linux-m68k.org>
Cc: Andi Kleen <ak@suse.de>
Signed-off-by: default avatarAndrew Morton <akpm@linux-foundation.org>
Signed-off-by: default avatarLinus Torvalds <torvalds@linux-foundation.org>
parent 79bf2bb3
......@@ -22,6 +22,14 @@ config CLOCKSOURCE_WATCHDOG
bool
default y
config GENERIC_CLOCKEVENTS
bool
default y
config GENERIC_CLOCKEVENTS_BROADCAST
bool
default y
config LOCKDEP_SUPPORT
bool
default y
......
......@@ -32,7 +32,6 @@ obj-$(CONFIG_KPROBES) += kprobes.o
obj-$(CONFIG_MODULES) += module.o
obj-y += sysenter.o vsyscall.o
obj-$(CONFIG_ACPI_SRAT) += srat.o
obj-$(CONFIG_HPET_TIMER) += time_hpet.o
obj-$(CONFIG_EFI) += efi.o efi_stub.o
obj-$(CONFIG_DOUBLEFAULT) += doublefault.o
obj-$(CONFIG_VM86) += vm86.o
......
......@@ -25,6 +25,7 @@
#include <linux/kernel_stat.h>
#include <linux/sysdev.h>
#include <linux/cpu.h>
#include <linux/clockchips.h>
#include <linux/module.h>
#include <asm/atomic.h>
......@@ -51,12 +52,6 @@
# error SPURIOUS_APIC_VECTOR definition error
#endif
/*
* cpu_mask that denotes the CPUs that needs timer interrupt coming in as
* IPIs in place of local APIC timers
*/
static cpumask_t timer_bcast_ipi;
/*
* Knob to control our willingness to enable the local APIC.
*
......@@ -64,16 +59,38 @@ static cpumask_t timer_bcast_ipi;
*/
static int enable_local_apic __initdata = 0;
/* Enable local APIC timer for highres/dyntick on UP */
static int enable_local_apic_timer __initdata = 0;
/*
* Debug level, exported for io_apic.c
*/
int apic_verbosity;
static void apic_pm_activate(void);
static unsigned int calibration_result;
static int lapic_next_event(unsigned long delta,
struct clock_event_device *evt);
static void lapic_timer_setup(enum clock_event_mode mode,
struct clock_event_device *evt);
static void lapic_timer_broadcast(cpumask_t mask);
static void apic_pm_activate(void);
/* Using APIC to generate smp_local_timer_interrupt? */
int using_apic_timer __read_mostly = 0;
/*
* The local apic timer can be used for any function which is CPU local.
*/
static struct clock_event_device lapic_clockevent = {
.name = "lapic",
.features = CLOCK_EVT_FEAT_PERIODIC | CLOCK_EVT_FEAT_ONESHOT
| CLOCK_EVT_FEAT_C3STOP,
.shift = 32,
.set_mode = lapic_timer_setup,
.set_next_event = lapic_next_event,
.broadcast = lapic_timer_broadcast,
.rating = 100,
.irq = -1,
};
static DEFINE_PER_CPU(struct clock_event_device, lapic_events);
/* Local APIC was disabled by the BIOS and enabled by the kernel */
static int enabled_via_apicbase;
......@@ -151,6 +168,11 @@ int lapic_get_maxlvt(void)
* closely follows bus clocks.
*/
/*
* FIXME: Move this to i8253.h. There is no need to keep the access to
* the PIT scattered all around the place -tglx
*/
/*
* The timer chip is already set up at HZ interrupts per second here,
* but we do not accept timer interrupts yet. We only allow the BP
......@@ -209,16 +231,17 @@ void (*wait_timer_tick)(void) __devinitdata = wait_8254_wraparound;
#define APIC_DIVISOR 16
static void __setup_APIC_LVTT(unsigned int clocks)
static void __setup_APIC_LVTT(unsigned int clocks, int oneshot, int irqen)
{
unsigned int lvtt_value, tmp_value;
int cpu = smp_processor_id();
lvtt_value = APIC_LVT_TIMER_PERIODIC | LOCAL_TIMER_VECTOR;
lvtt_value = LOCAL_TIMER_VECTOR;
if (!oneshot)
lvtt_value |= APIC_LVT_TIMER_PERIODIC;
if (!lapic_is_integrated())
lvtt_value |= SET_APIC_TIMER_BASE(APIC_TIMER_BASE_DIV);
if (cpu_isset(cpu, timer_bcast_ipi))
if (!irqen)
lvtt_value |= APIC_LVT_MASKED;
apic_write_around(APIC_LVTT, lvtt_value);
......@@ -231,31 +254,80 @@ static void __setup_APIC_LVTT(unsigned int clocks)
& ~(APIC_TDR_DIV_1 | APIC_TDR_DIV_TMBASE))
| APIC_TDR_DIV_16);
apic_write_around(APIC_TMICT, clocks/APIC_DIVISOR);
if (!oneshot)
apic_write_around(APIC_TMICT, clocks/APIC_DIVISOR);
}
/*
* Program the next event, relative to now
*/
static int lapic_next_event(unsigned long delta,
struct clock_event_device *evt)
{
apic_write_around(APIC_TMICT, delta);
return 0;
}
static void __devinit setup_APIC_timer(unsigned int clocks)
/*
* Setup the lapic timer in periodic or oneshot mode
*/
static void lapic_timer_setup(enum clock_event_mode mode,
struct clock_event_device *evt)
{
unsigned long flags;
unsigned int v;
local_irq_save(flags);
/*
* Wait for IRQ0's slice:
*/
wait_timer_tick();
__setup_APIC_LVTT(clocks);
switch (mode) {
case CLOCK_EVT_MODE_PERIODIC:
case CLOCK_EVT_MODE_ONESHOT:
__setup_APIC_LVTT(calibration_result,
mode != CLOCK_EVT_MODE_PERIODIC, 1);
break;
case CLOCK_EVT_MODE_UNUSED:
case CLOCK_EVT_MODE_SHUTDOWN:
v = apic_read(APIC_LVTT);
v |= (APIC_LVT_MASKED | LOCAL_TIMER_VECTOR);
apic_write_around(APIC_LVTT, v);
break;
}
local_irq_restore(flags);
}
/*
* Local APIC timer broadcast function
*/
static void lapic_timer_broadcast(cpumask_t mask)
{
#ifdef CONFIG_SMP
send_IPI_mask(mask, LOCAL_TIMER_VECTOR);
#endif
}
/*
* Setup the local APIC timer for this CPU. Copy the initilized values
* of the boot CPU and register the clock event in the framework.
*/
static void __devinit setup_APIC_timer(void)
{
struct clock_event_device *levt = &__get_cpu_var(lapic_events);
memcpy(levt, &lapic_clockevent, sizeof(*levt));
levt->cpumask = cpumask_of_cpu(smp_processor_id());
clockevents_register_device(levt);
}
/*
* In this function we calibrate APIC bus clocks to the external
* timer. Unfortunately we cannot use jiffies and the timer irq
* to calibrate, since some later bootup code depends on getting
* the first irq? Ugh.
*
* TODO: Fix this rather than saying "Ugh" -tglx
*
* We want to do the calibration only once since we
* want to have local timer irqs syncron. CPUs connected
* by the same APIC bus have the very same bus frequency.
......@@ -278,7 +350,7 @@ static int __init calibrate_APIC_clock(void)
* value into the APIC clock, we just want to get the
* counter running for calibration.
*/
__setup_APIC_LVTT(1000000000);
__setup_APIC_LVTT(1000000000, 0, 0);
/*
* The timer chip counts down to zero. Let's wait
......@@ -315,6 +387,17 @@ static int __init calibrate_APIC_clock(void)
result = (tt1-tt2)*APIC_DIVISOR/LOOPS;
/* Calculate the scaled math multiplication factor */
lapic_clockevent.mult = div_sc(tt1-tt2, TICK_NSEC * LOOPS, 32);
lapic_clockevent.max_delta_ns =
clockevent_delta2ns(0x7FFFFF, &lapic_clockevent);
lapic_clockevent.min_delta_ns =
clockevent_delta2ns(0xF, &lapic_clockevent);
apic_printk(APIC_VERBOSE, "..... tt1-tt2 %ld\n", tt1 - tt2);
apic_printk(APIC_VERBOSE, "..... mult: %ld\n", lapic_clockevent.mult);
apic_printk(APIC_VERBOSE, "..... calibration result: %ld\n", result);
if (cpu_has_tsc)
apic_printk(APIC_VERBOSE, "..... CPU clock speed is "
"%ld.%04ld MHz.\n",
......@@ -329,13 +412,10 @@ static int __init calibrate_APIC_clock(void)
return result;
}
static unsigned int calibration_result;
void __init setup_boot_APIC_clock(void)
{
unsigned long flags;
apic_printk(APIC_VERBOSE, "Using local APIC timer interrupts.\n");
using_apic_timer = 1;
local_irq_save(flags);
......@@ -343,97 +423,47 @@ void __init setup_boot_APIC_clock(void)
/*
* Now set up the timer for real.
*/
setup_APIC_timer(calibration_result);
setup_APIC_timer();
local_irq_restore(flags);
}
void __devinit setup_secondary_APIC_clock(void)
{
setup_APIC_timer(calibration_result);
}
void disable_APIC_timer(void)
{
if (using_apic_timer) {
unsigned long v;
v = apic_read(APIC_LVTT);
/*
* When an illegal vector value (0-15) is written to an LVT
* entry and delivery mode is Fixed, the APIC may signal an
* illegal vector error, with out regard to whether the mask
* bit is set or whether an interrupt is actually seen on
* input.
*
* Boot sequence might call this function when the LVTT has
* '0' vector value. So make sure vector field is set to
* valid value.
*/
v |= (APIC_LVT_MASKED | LOCAL_TIMER_VECTOR);
apic_write_around(APIC_LVTT, v);
}
}
void enable_APIC_timer(void)
{
int cpu = smp_processor_id();
if (using_apic_timer && !cpu_isset(cpu, timer_bcast_ipi)) {
unsigned long v;
v = apic_read(APIC_LVTT);
apic_write_around(APIC_LVTT, v & ~APIC_LVT_MASKED);
}
}
void switch_APIC_timer_to_ipi(void *cpumask)
{
cpumask_t mask = *(cpumask_t *)cpumask;
int cpu = smp_processor_id();
if (cpu_isset(cpu, mask) &&
!cpu_isset(cpu, timer_bcast_ipi)) {
disable_APIC_timer();
cpu_set(cpu, timer_bcast_ipi);
}
}
EXPORT_SYMBOL(switch_APIC_timer_to_ipi);
void switch_ipi_to_APIC_timer(void *cpumask)
{
cpumask_t mask = *(cpumask_t *)cpumask;
int cpu = smp_processor_id();
if (cpu_isset(cpu, mask) &&
cpu_isset(cpu, timer_bcast_ipi)) {
cpu_clear(cpu, timer_bcast_ipi);
enable_APIC_timer();
}
setup_APIC_timer();
}
EXPORT_SYMBOL(switch_ipi_to_APIC_timer);
/*
* Local timer interrupt handler. It does both profiling and
* process statistics/rescheduling.
* The guts of the apic timer interrupt
*/
inline void smp_local_timer_interrupt(void)
static void local_apic_timer_interrupt(void)
{
profile_tick(CPU_PROFILING);
#ifdef CONFIG_SMP
update_process_times(user_mode_vm(get_irq_regs()));
#endif
int cpu = smp_processor_id();
struct clock_event_device *evt = &per_cpu(lapic_events, cpu);
/*
* We take the 'long' return path, and there every subsystem
* grabs the apropriate locks (kernel lock/ irq lock).
* Normally we should not be here till LAPIC has been
* initialized but in some cases like kdump, its possible that
* there is a pending LAPIC timer interrupt from previous
* kernel's context and is delivered in new kernel the moment
* interrupts are enabled.
*
* we might want to decouple profiling from the 'long path',
* and do the profiling totally in assembly.
*
* Currently this isn't too much of an issue (performance wise),
* we can take more than 100K local irqs per second on a 100 MHz P5.
* Interrupts are enabled early and LAPIC is setup much later,
* hence its possible that when we get here evt->event_handler
* is NULL. Check for event_handler being NULL and discard
* the interrupt as spurious.
*/
if (!evt->event_handler) {
printk(KERN_WARNING
"Spurious LAPIC timer interrupt on cpu %d\n", cpu);
/* Switch it off */
lapic_timer_setup(CLOCK_EVT_MODE_SHUTDOWN, evt);
return;
}
per_cpu(irq_stat, cpu).apic_timer_irqs++;
evt->event_handler(evt);
}
/*
......@@ -445,15 +475,9 @@ inline void smp_local_timer_interrupt(void)
* interrupt as well. Thus we cannot inline the local irq ... ]
*/
fastcall void smp_apic_timer_interrupt(struct pt_regs *regs)
void fastcall smp_apic_timer_interrupt(struct pt_regs *regs)
{
struct pt_regs *old_regs = set_irq_regs(regs);
int cpu = smp_processor_id();
/*
* the NMI deadlock-detector uses this.
*/
per_cpu(irq_stat, cpu).apic_timer_irqs++;
/*
* NOTE! We'd better ACK the irq immediately,
......@@ -467,41 +491,10 @@ fastcall void smp_apic_timer_interrupt(struct pt_regs *regs)
*/
exit_idle();
irq_enter();
smp_local_timer_interrupt();
local_apic_timer_interrupt();
irq_exit();
set_irq_regs(old_regs);
}
#ifndef CONFIG_SMP
static void up_apic_timer_interrupt_call(void)
{
int cpu = smp_processor_id();
/*
* the NMI deadlock-detector uses this.
*/
per_cpu(irq_stat, cpu).apic_timer_irqs++;
smp_local_timer_interrupt();
}
#endif
void smp_send_timer_broadcast_ipi(void)
{
cpumask_t mask;
cpus_and(mask, cpu_online_map, timer_bcast_ipi);
if (!cpus_empty(mask)) {
#ifdef CONFIG_SMP
send_IPI_mask(mask, LOCAL_TIMER_VECTOR);
#else
/*
* We can directly call the apic timer interrupt handler
* in UP case. Minus all irq related functions
*/
up_apic_timer_interrupt_call();
#endif
}
set_irq_regs(old_regs);
}
int setup_profiling_timer(unsigned int multiplier)
......@@ -914,6 +907,11 @@ void __devinit setup_local_APIC(void)
printk(KERN_INFO "No ESR for 82489DX.\n");
}
/* Disable the local apic timer */
value = apic_read(APIC_LVTT);
value |= (APIC_LVT_MASKED | LOCAL_TIMER_VECTOR);
apic_write_around(APIC_LVTT, value);
setup_apic_nmi_watchdog(NULL);
apic_pm_activate();
}
......@@ -1128,6 +1126,13 @@ static int __init parse_nolapic(char *arg)
}
early_param("nolapic", parse_nolapic);
static int __init apic_enable_lapic_timer(char *str)
{
enable_local_apic_timer = 1;
return 0;
}
early_param("lapictimer", apic_enable_lapic_timer);
static int __init apic_set_verbosity(char *str)
{
if (strcmp("debug", str) == 0)
......@@ -1147,7 +1152,7 @@ __setup("apic=", apic_set_verbosity);
/*
* This interrupt should _never_ happen with our APIC/SMP architecture
*/
fastcall void smp_spurious_interrupt(struct pt_regs *regs)
void smp_spurious_interrupt(struct pt_regs *regs)
{
unsigned long v;
......@@ -1171,7 +1176,7 @@ fastcall void smp_spurious_interrupt(struct pt_regs *regs)
/*
* This interrupt should never happen with our APIC/SMP architecture
*/
fastcall void smp_error_interrupt(struct pt_regs *regs)
void smp_error_interrupt(struct pt_regs *regs)
{
unsigned long v, v1;
......
#include <linux/clocksource.h>
#include <linux/clockchips.h>
#include <linux/errno.h>
#include <linux/hpet.h>
#include <linux/init.h>
......@@ -6,17 +7,278 @@
#include <asm/hpet.h>
#include <asm/io.h>
extern struct clock_event_device *global_clock_event;
#define HPET_MASK CLOCKSOURCE_MASK(32)
#define HPET_SHIFT 22
/* FSEC = 10^-15 NSEC = 10^-9 */
#define FSEC_PER_NSEC 1000000
static void __iomem *hpet_ptr;
/*
* HPET address is set in acpi/boot.c, when an ACPI entry exists
*/
unsigned long hpet_address;
static void __iomem * hpet_virt_address;
static inline unsigned long hpet_readl(unsigned long a)
{
return readl(hpet_virt_address + a);
}
static inline void hpet_writel(unsigned long d, unsigned long a)
{
writel(d, hpet_virt_address + a);
}
/*
* HPET command line enable / disable
*/
static int boot_hpet_disable;
static int __init hpet_setup(char* str)
{
if (str) {
if (!strncmp("disable", str, 7))
boot_hpet_disable = 1;
}
return 1;
}
__setup("hpet=", hpet_setup);
static inline int is_hpet_capable(void)
{
return (!boot_hpet_disable && hpet_address);
}
/*
* HPET timer interrupt enable / disable
*/
static int hpet_legacy_int_enabled;
/**
* is_hpet_enabled - check whether the hpet timer interrupt is enabled
*/
int is_hpet_enabled(void)
{
return is_hpet_capable() && hpet_legacy_int_enabled;
}
/*
* When the hpet driver (/dev/hpet) is enabled, we need to reserve
* timer 0 and timer 1 in case of RTC emulation.
*/
#ifdef CONFIG_HPET
static void hpet_reserve_platform_timers(unsigned long id)
{
struct hpet __iomem *hpet = hpet_virt_address;
struct hpet_timer __iomem *timer = &hpet->hpet_timers[2];
unsigned int nrtimers, i;
struct hpet_data hd;
nrtimers = ((id & HPET_ID_NUMBER) >> HPET_ID_NUMBER_SHIFT) + 1;
memset(&hd, 0, sizeof (hd));
hd.hd_phys_address = hpet_address;
hd.hd_address = hpet_virt_address;
hd.hd_nirqs = nrtimers;
hd.hd_flags = HPET_DATA_PLATFORM;
hpet_reserve_timer(&hd, 0);
#ifdef CONFIG_HPET_EMULATE_RTC
hpet_reserve_timer(&hd, 1);
#endif
hd.hd_irq[0] = HPET_LEGACY_8254;
hd.hd_irq[1] = HPET_LEGACY_RTC;
for (i = 2; i < nrtimers; timer++, i++)
hd.hd_irq[i] = (timer->hpet_config & Tn_INT_ROUTE_CNF_MASK) >>
Tn_INT_ROUTE_CNF_SHIFT;
hpet_alloc(&hd);
}
#else
static void hpet_reserve_platform_timers(unsigned long id) { }
#endif
/*
* Common hpet info
*/
static unsigned long hpet_period;
static void hpet_set_mode(enum clock_event_mode mode,
struct clock_event_device *evt);
static int hpet_next_event(unsigned long delta,
struct clock_event_device *evt);
/*
* The hpet clock event device
*/
static struct clock_event_device hpet_clockevent = {
.name = "hpet",
.features = CLOCK_EVT_FEAT_PERIODIC | CLOCK_EVT_FEAT_ONESHOT,
.set_mode = hpet_set_mode,
.set_next_event = hpet_next_event,
.shift = 32,
.irq = 0,
};
static void hpet_start_counter(void)
{
unsigned long cfg = hpet_readl(HPET_CFG);
cfg &= ~HPET_CFG_ENABLE;
hpet_writel(cfg, HPET_CFG);
hpet_writel(0, HPET_COUNTER);
hpet_writel(0, HPET_COUNTER + 4);
cfg |= HPET_CFG_ENABLE;
hpet_writel(cfg, HPET_CFG);
}
static void hpet_enable_int(void)
{
unsigned long cfg = hpet_readl(HPET_CFG);
cfg |= HPET_CFG_LEGACY;
hpet_writel(cfg, HPET_CFG);
hpet_legacy_int_enabled = 1;
}
static void hpet_set_mode(enum clock_event_mode mode,
struct clock_event_device *evt)
{
unsigned long cfg, cmp, now;
uint64_t delta;
switch(mode) {
case CLOCK_EVT_MODE_PERIODIC:
delta = ((uint64_t)(NSEC_PER_SEC/HZ)) * hpet_clockevent.mult;
delta >>= hpet_clockevent.shift;
now = hpet_readl(HPET_COUNTER);
cmp = now + (unsigned long) delta