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

[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
......
This diff is collapsed.
This diff is collapsed.
......@@ -2,7 +2,7 @@
* i8253.c 8253/PIT functions
*
*/
#include <linux/clocksource.h>
#include <linux/clockchips.h>
#include <linux/spinlock.h>
#include <linux/jiffies.h>
#include <linux/sysdev.h>
......@@ -19,17 +19,97 @@
DEFINE_SPINLOCK(i8253_lock);
EXPORT_SYMBOL(i8253_lock);
void setup_pit_timer(void)
/*
* HPET replaces the PIT, when enabled. So we need to know, which of
* the two timers is used
*/
struct clock_event_device *global_clock_event;
/*
* Initialize the PIT timer.
*
* This is also called after resume to bring the PIT into operation again.
*/
static void init_pit_timer(enum clock_event_mode mode,
struct clock_event_device *evt)
{
unsigned long flags;
spin_lock_irqsave(&i8253_lock, flags);
outb_p(0x34,PIT_MODE); /* binary, mode 2, LSB/MSB, ch 0 */
switch(mode) {
case CLOCK_EVT_MODE_PERIODIC:
/* binary, mode 2, LSB/MSB, ch 0 */
outb_p(0x34, PIT_MODE);
udelay(10);
outb_p(LATCH & 0xff , PIT_CH0); /* LSB */
udelay(10);
outb(LATCH >> 8 , PIT_CH0); /* MSB */
break;
case CLOCK_EVT_MODE_ONESHOT:
case CLOCK_EVT_MODE_SHUTDOWN:
case CLOCK_EVT_MODE_UNUSED:
/* One shot setup */
outb_p(0x38, PIT_MODE);
udelay(10);
break;
}
spin_unlock_irqrestore(&i8253_lock, flags);
}
/*
* Program the next event in oneshot mode
*
* Delta is given in PIT ticks
*/
static int pit_next_event(unsigned long delta, struct clock_event_device *evt)
{
unsigned long flags;
spin_lock_irqsave(&i8253_lock, flags);
outb_p(delta & 0xff , PIT_CH0); /* LSB */
outb(delta >> 8 , PIT_CH0); /* MSB */
spin_unlock_irqrestore(&i8253_lock, flags);
return 0;
}
/*
* On UP the PIT can serve all of the possible timer functions. On SMP systems
* it can be solely used for the global tick.
*
* The profiling and update capabilites are switched off once the local apic is
* registered. This mechanism replaces the previous #ifdef LOCAL_APIC -
* !using_apic_timer decisions in do_timer_interrupt_hook()
*/
struct clock_event_device pit_clockevent = {
.name = "pit",
.features = CLOCK_EVT_FEAT_PERIODIC | CLOCK_EVT_FEAT_ONESHOT,
.set_mode = init_pit_timer,
.set_next_event = pit_next_event,
.shift = 32,
.irq = 0,
};
/*
* Initialize the conversion factor and the min/max deltas of the clock event
* structure and register the clock event source with the framework.
*/
void __init setup_pit_timer(void)
{
/*
* Start pit with the boot cpu mask and make it global after the
* IO_APIC has been initialized.
*/
pit_clockevent.cpumask = cpumask_of_cpu(0);
pit_clockevent.mult = div_sc(CLOCK_TICK_RATE, NSEC_PER_SEC, 32);
pit_clockevent.max_delta_ns =
clockevent_delta2ns(0x7FFF, &pit_clockevent);
pit_clockevent.min_delta_ns =
clockevent_delta2ns(0xF, &pit_clockevent);
clockevents_register_device(&pit_clockevent);
global_clock_event = &pit_clockevent;
}
/*
......
......@@ -409,12 +409,6 @@ void __init native_init_IRQ(void)
*/
intr_init_hook();
/*
* Set the clock to HZ Hz, we already have a valid
* vector now:
*/
setup_pit_timer();
/*
* External FPU? Set up irq13 if so, for
* original braindamaged IBM FERR coupling.
......
......@@ -289,8 +289,6 @@ static void __cpuinit smp_callin(void)
*/
smp_store_cpu_info(cpuid);
disable_APIC_timer();
/*
* Allow the master to continue.
*/
......@@ -408,7 +406,6 @@ static void __cpuinit start_secondary(void *unused)
enable_NMI_through_LVT0(NULL);
enable_8259A_irq(0);
}
enable_APIC_timer();
/*
* low-memory mappings have been cleared, flush them from
* the local TLBs too.
......
......@@ -159,15 +159,6 @@ EXPORT_SYMBOL(profile_pc);
*/
irqreturn_t timer_interrupt(int irq, void *dev_id)
{
/*
* Here we are in the timer irq handler. We just have irqs locally
* disabled but we don't know if the timer_bh is running on the other
* CPU. We need to avoid to SMP race with it. NOTE: we don' t need
* the irq version of write_lock because as just said we have irq
* locally disabled. -arca
*/
write_seqlock(&xtime_lock);
#ifdef CONFIG_X86_IO_APIC
if (timer_ack) {
/*
......@@ -186,7 +177,6 @@ irqreturn_t timer_interrupt(int irq, void *dev_id)
do_timer_interrupt_hook();
if (MCA_bus) {
/* The PS/2 uses level-triggered interrupts. You can't
turn them off, nor would you want to (any attempt to
......@@ -201,13 +191,6 @@ irqreturn_t timer_interrupt(int irq, void *dev_id)
outb_p( irq_v|0x80, 0x61 ); /* reset the IRQ */
}
write_sequnlock(&xtime_lock);
#ifdef CONFIG_X86_LOCAL_APIC
if (using_apic_timer)
smp_send_timer_broadcast_ipi();
#endif
return IRQ_HANDLED;
}
......@@ -277,63 +260,16 @@ void notify_arch_cmos_timer(void)
mod_timer(&sync_cmos_timer, jiffies + 1);
}
static int timer_resume(struct sys_device *dev)
{
#ifdef CONFIG_HPET_TIMER
if (is_hpet_enabled())
hpet_reenable();
#endif
setup_pit_timer();
touch_softlockup_watchdog();
return 0;
}
static struct sysdev_class timer_sysclass = {
.resume = timer_resume,
set_kset_name("timer"),
};
/* XXX this driverfs stuff should probably go elsewhere later -john */
static struct sys_device device_timer = {
.id = 0,
.cls = &timer_sysclass,
};
static int time_init_device(void)
{
int error = sysdev_class_register(&timer_sysclass);
if (!error)
error = sysdev_register(&device_timer);
return error;
}
device_initcall(time_init_device);
#ifdef CONFIG_HPET_TIMER
extern void (*late_time_init)(void);
/* Duplicate of time_init() below, with hpet_enable part added */
static void __init hpet_time_init(void)
{
if ((hpet_enable() >= 0) && hpet_use_timer) {
printk("Using HPET for base-timer\n");
}
if (!hpet_enable())
setup_pit_timer();
do_time_init();
}
#endif
void __init time_init(void)
{
#ifdef CONFIG_HPET_TIMER
if (is_hpet_capable()) {
/*
* HPET initialization needs to do memory-mapped io. So, let
* us do a late initialization after mem_init().
*/
late_time_init = hpet_time_init;
return;
}
#endif
do_time_init();
}
This diff is collapsed.
......@@ -79,7 +79,12 @@ void __init trap_init_hook(void)
{
}
static struct irqaction irq0 = { timer_interrupt, IRQF_DISABLED, CPU_MASK_NONE, "timer", NULL, NULL};
static struct irqaction irq0 = {
.handler = timer_interrupt,
.flags = IRQF_DISABLED | IRQF_NOBALANCING,
.mask = CPU_MASK_NONE,
.name = "timer"
};
/**
* time_init_hook - do any specific initialisations for the system timer.
......@@ -90,6 +95,7 @@ static struct irqaction irq0 = { timer_interrupt, IRQF_DISABLED, CPU_MASK_NONE,
**/
void __init time_init_hook(void)
{
irq0.mask = cpumask_of_cpu(0);
setup_irq(0, &irq0);
}
......
......@@ -39,6 +39,7 @@
#include <linux/moduleparam.h>
#include <linux/sched.h> /* need_resched() */
#include <linux/latency.h>
#include <linux/clockchips.h>
/*
* Include the apic definitions for x86 to have the APIC timer related defines
......@@ -274,12 +275,40 @@ static void acpi_timer_check_state(int state, struct acpi_processor *pr,
static void acpi_propagate_timer_broadcast(struct acpi_processor *pr)
{
#ifdef CONFIG_GENERIC_CLOCKEVENTS
unsigned long reason;
reason = pr->power.timer_broadcast_on_state < INT_MAX ?
CLOCK_EVT_NOTIFY_BROADCAST_ON : CLOCK_EVT_NOTIFY_BROADCAST_OFF;
clockevents_notify(reason, &pr->id);
#else
cpumask_t mask = cpumask_of_cpu(pr->id);
if (pr->power.timer_broadcast_on_state < INT_MAX)
on_each_cpu(switch_APIC_timer_to_ipi, &mask, 1, 1);
else
on_each_cpu(switch_ipi_to_APIC_timer, &mask, 1, 1);
#endif
}
/* Power(C) State timer broadcast control */
static void acpi_state_timer_broadcast(struct acpi_processor *pr,
struct acpi_processor_cx *cx,
int broadcast)
{
#ifdef CONFIG_GENERIC_CLOCKEVENTS
int state = cx - pr->power.states;
if (state >= pr->power.timer_broadcast_on_state) {
unsigned long reason;
reason = broadcast ? CLOCK_EVT_NOTIFY_BROADCAST_ENTER :
CLOCK_EVT_NOTIFY_BROADCAST_EXIT;
clockevents_notify(reason, &pr->id);
}
#endif
}
#else
......@@ -287,6 +316,11 @@ static void acpi_propagate_timer_broadcast(struct acpi_processor *pr)
static void acpi_timer_check_state(int state, struct acpi_processor *pr,
struct acpi_processor_cx *cstate) { }
static void acpi_propagate_timer_broadcast(struct acpi_processor *pr) { }
static void acpi_state_timer_broadcast(struct acpi_processor *pr,
struct acpi_processor_cx *cx,
int broadcast)
{
}
#endif
......@@ -434,6 +468,7 @@ static void acpi_processor_idle(void)
/* Get start time (ticks) */
t1 = inl(acpi_gbl_FADT.xpm_timer_block.address);
/* Invoke C2 */
acpi_state_timer_broadcast(pr, cx, 1);
acpi_cstate_enter(cx);
/* Get end time (ticks) */
t2 = inl(acpi_gbl_FADT.xpm_timer_block.address);
......@@ -448,6 +483,7 @@ static void acpi_processor_idle(void)
/* Compute time (ticks) that we were actually asleep */
sleep_ticks =
ticks_elapsed(t1, t2) - cx->latency_ticks - C2_OVERHEAD;
acpi_state_timer_broadcast(pr, cx, 0);
break;
case ACPI_STATE_C3:
......@@ -469,6 +505,7 @@ static void acpi_processor_idle(void)
/* Get start time (ticks) */
t1 = inl(acpi_gbl_FADT.xpm_timer_block.address);
/* Invoke C3 */
acpi_state_timer_broadcast(pr, cx, 1);
acpi_cstate_enter(cx);
/* Get end time (ticks) */
t2 = inl(acpi_gbl_FADT.xpm_timer_block.address);
......@@ -488,6 +525,7 @@ static void acpi_processor_idle(void)
/* Compute time (ticks) that we were actually asleep */
sleep_ticks =
ticks_elapsed(t1, t2) - cx->latency_ticks - C3_OVERHEAD;
acpi_state_timer_broadcast(pr, cx, 0);
break;
default:
......
......@@ -113,14 +113,9 @@ extern void smp_local_timer_interrupt (void);
extern void setup_boot_APIC_clock (void);
extern void setup_secondary_APIC_clock (void);
extern int APIC_init_uniprocessor (void);
extern void disable_APIC_timer(void);
extern void enable_APIC_timer(void);
extern void enable_NMI_through_LVT0 (void * dummy);
void smp_send_timer_broadcast_ipi(void);
void switch_APIC_timer_to_ipi(void *cpumask);
void switch_ipi_to_APIC_timer(void *cpumask);
#define ARCH_APICTIMER_STOPS_ON_C3 1
extern int timer_over_8254;
......
......@@ -90,16 +90,19 @@
#define HPET_MIN_PERIOD (100000UL)
#define HPET_TICK_RATE (HZ * 100000UL)
extern unsigned long hpet_tick; /* hpet clks count per tick */
extern unsigned long hpet_address; /* hpet memory map physical address */
extern int hpet_use_timer;
extern int is_hpet_enabled(void);
#ifdef CONFIG_X86_64
extern unsigned long hpet_tick; /* hpet clks count per tick */
extern int hpet_use_timer;
extern int hpet_rtc_timer_init(void);
extern int hpet_enable(void);
extern int hpet_reenable(void);
extern int is_hpet_enabled(void);
extern int is_hpet_capable(void);
extern int hpet_readl(unsigned long a);
#else
extern int hpet_enable(void);
#endif
#ifdef CONFIG_HPET_EMULATE_RTC
extern int hpet_mask_rtc_irq_bit(unsigned long bit_mask);
......@@ -110,5 +113,10 @@ extern int hpet_rtc_dropped_irq(void);
extern int hpet_rtc_timer_init(void);
extern irqreturn_t hpet_rtc_interrupt(int irq, void *dev_id);
#endif /* CONFIG_HPET_EMULATE_RTC */
#else
static inline int hpet_enable(void) { return 0; }
#endif /* CONFIG_HPET_TIMER */
#endif /* _I386_HPET_H */
#ifndef __ASM_I8253_H__
#define __ASM_I8253_H__
#include <linux/clockchips.h>
extern spinlock_t i8253_lock;
extern struct clock_event_device *global_clock_event;
/**
* pit_interrupt_hook - hook into timer tick
* @regs: standard registers from interrupt
*
* Call the global clock event handler.
**/
static inline void pit_interrupt_hook(void)
{
global_clock_event->event_handler(global_clock_event);
}
#endif /* __ASM_I8253_H__ */
/* defines for inline arch setup functions */
#include <linux/clockchips.h>
#include <asm/apic.h>
#include <asm/i8259.h>
#include <asm/i8253.h>
/**
* do_timer_interrupt_hook - hook into timer tick
* @regs: standard registers from interrupt
*
* Description:
* This hook is called immediately after the timer interrupt is ack'd.
* It's primary purpose is to allow architectures that don't possess
* individual per CPU clocks (like the CPU APICs supply) to broadcast the
* timer interrupt as a means of triggering reschedules etc.
* Call the pit clock event handler. see asm/i8253.h
**/
static inline void do_timer_interrupt_hook(void)
{
do_timer(1);
#ifndef CONFIG_SMP
update_process_times(user_mode_vm(get_irq_regs()));
#endif
/*
* In the SMP case we use the local APIC timer interrupt to do the
* profiling, except when we simulate SMP mode on a uniprocessor
* system, in that case we have to call the local interrupt handler.
*/
#ifndef CONFIG_X86_LOCAL_APIC
profile_tick(CPU_PROFILING);
#else
if (!using_apic_timer)
smp_local_timer_interrupt();
#endif
}
/* you can safely undefine this if you don't have the Neptune chipset */
#define BUGGY_NEPTUN_TIMER
/**
* do_timer_overflow - process a detected timer overflow condition
* @count: hardware timer interrupt count on overflow
*
* Description:
* This call is invoked when the jiffies count has not incremented but
* the hardware timer interrupt has. It means that a timer tick interrupt
* came along while the previous one was pending, thus a tick was missed
**/
static inline int do_timer_overflow(int count)
{
int i;
spin_lock(&i8259A_lock);
/*
* This is tricky when I/O APICs are used;
* see do_timer_interrupt().
*/
i = inb(0x20);
spin_unlock(&i8259A_lock);
/* assumption about timer being IRQ0 */
if (i & 0x01) {
/*
* We cannot detect lost timer interrupts ...
* well, that's why we call them lost, don't we? :)
* [hmm, on the Pentium and Alpha we can ... sort of]
*/
count -= LATCH;
} else {
#ifdef BUGGY_NEPTUN_TIMER
/*
* for the Neptun bug we know that the 'latch'
* command doesn't latch the high and low value
* of the counter atomically. Thus we have to
* substract 256 from the counter
* ... funny, isnt it? :)
*/
count -= 256;
#else
printk("do_slow_gettimeoffset(): hardware timer problem?\n");
#endif
}
return count;
pit_interrupt_hook();
}
/* defines for inline arch setup functions */
#include <linux/clockchips.h>
#include <asm/voyager.h>
#include <asm/i8253.h>
/**
* do_timer_interrupt_hook - hook into timer tick
* @regs: standard registers from interrupt
*
* Call the pit clock event handler. see asm/i8253.h
**/
static inline void do_timer_interrupt_hook(void)
{
do_timer(1);
#ifndef CONFIG_SMP
update_process_times(user_mode_vm(irq_regs));
#endif
pit_interrupt_hook();
voyager_timer_interrupt();
}
static inline int do_timer_overflow(int count)
{
/* can't read the ISR, just assume 1 tick
overflow */
if(count > LATCH || count < 0) {
printk(KERN_ERR "VOYAGER PROBLEM: count is %d, latch is %d\n", count, LATCH);
count = LATCH;
}
count -= LATCH;
return count;
}
......@@ -23,7 +23,6 @@ extern struct mpc_config_intsrc mp_irqs [MAX_IRQ_SOURCES];
extern int mpc_default_type;
extern unsigned long mp_lapic_addr;
extern int pic_mode;
extern int using_apic_timer;
#ifdef CONFIG_ACPI
extern void mp_register_lapic (u8 id, u8 enabled);
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment