Skip to content

Commit

Permalink
[PATCH] i386: add idle notifier
Browse files Browse the repository at this point in the history
Add a notifier mechanism to the low level idle loop.  You can register a
callback function which gets invoked on entry and exit from the low level idle
loop.  The low level idle loop is defined as the polling loop, low-power call,
or the mwait instruction.  Interrupts processed by the idle thread are not
considered part of the low level loop.

The notifier can be used to measure precisely how much is spent in useless
execution (or low power mode).  The perfmon subsystem uses it to turn on/off
monitoring.

Signed-off-by: stephane eranian <eranian@hpl.hp.com>
Signed-off-by: Andrew Morton <akpm@osdl.org>
Signed-off-by: Andi Kleen <ak@suse.de>
  • Loading branch information
Stephane Eranian authored and Andi Kleen committed Feb 13, 2007
1 parent 86a9788 commit 2ff2d3d
Show file tree
Hide file tree
Showing 7 changed files with 85 additions and 1 deletion.
4 changes: 4 additions & 0 deletions arch/i386/kernel/apic.c
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
#include <asm/hpet.h>
#include <asm/i8253.h>
#include <asm/nmi.h>
#include <asm/idle.h>

#include <mach_apic.h>
#include <mach_apicdef.h>
Expand Down Expand Up @@ -1255,6 +1256,7 @@ fastcall void smp_apic_timer_interrupt(struct pt_regs *regs)
* Besides, if we don't timer interrupts ignore the global
* interrupt lock, which is the WrongThing (tm) to do.
*/
exit_idle();
irq_enter();
smp_local_timer_interrupt();
irq_exit();
Expand Down Expand Up @@ -1305,6 +1307,7 @@ fastcall void smp_spurious_interrupt(struct pt_regs *regs)
{
unsigned long v;

exit_idle();
irq_enter();
/*
* Check if this really is a spurious interrupt and ACK it
Expand All @@ -1329,6 +1332,7 @@ fastcall void smp_error_interrupt(struct pt_regs *regs)
{
unsigned long v, v1;

exit_idle();
irq_enter();
/* First tickle the hardware, only then report what went on. -- REW */
v = apic_read(APIC_ESR);
Expand Down
2 changes: 2 additions & 0 deletions arch/i386/kernel/cpu/mcheck/p4.c
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
#include <asm/system.h>
#include <asm/msr.h>
#include <asm/apic.h>
#include <asm/idle.h>

#include <asm/therm_throt.h>

Expand Down Expand Up @@ -59,6 +60,7 @@ static void (*vendor_thermal_interrupt)(struct pt_regs *regs) = unexpected_therm

fastcall void smp_thermal_interrupt(struct pt_regs *regs)
{
exit_idle();
irq_enter();
vendor_thermal_interrupt(regs);
irq_exit();
Expand Down
3 changes: 3 additions & 0 deletions arch/i386/kernel/irq.c
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@
#include <linux/cpu.h>
#include <linux/delay.h>

#include <asm/idle.h>

DEFINE_PER_CPU(irq_cpustat_t, irq_stat) ____cacheline_internodealigned_in_smp;
EXPORT_PER_CPU_SYMBOL(irq_stat);

Expand Down Expand Up @@ -61,6 +63,7 @@ fastcall unsigned int do_IRQ(struct pt_regs *regs)
union irq_ctx *curctx, *irqctx;
u32 *isp;
#endif
exit_idle();

if (unlikely((unsigned)irq >= NR_IRQS)) {
printk(KERN_EMERG "%s: cannot handle IRQ %d\n",
Expand Down
53 changes: 52 additions & 1 deletion arch/i386/kernel/process.c
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@
#include <asm/i387.h>
#include <asm/desc.h>
#include <asm/vm86.h>
#include <asm/idle.h>
#ifdef CONFIG_MATH_EMULATION
#include <asm/math_emu.h>
#endif
Expand Down Expand Up @@ -80,6 +81,42 @@ void (*pm_idle)(void);
EXPORT_SYMBOL(pm_idle);
static DEFINE_PER_CPU(unsigned int, cpu_idle_state);

static ATOMIC_NOTIFIER_HEAD(idle_notifier);

void idle_notifier_register(struct notifier_block *n)
{
atomic_notifier_chain_register(&idle_notifier, n);
}

void idle_notifier_unregister(struct notifier_block *n)
{
atomic_notifier_chain_unregister(&idle_notifier, n);
}

static DEFINE_PER_CPU(volatile unsigned long, idle_state);

void enter_idle(void)
{
/* needs to be atomic w.r.t. interrupts, not against other CPUs */
__set_bit(0, &__get_cpu_var(idle_state));
atomic_notifier_call_chain(&idle_notifier, IDLE_START, NULL);
}

static void __exit_idle(void)
{
/* needs to be atomic w.r.t. interrupts, not against other CPUs */
if (__test_and_clear_bit(0, &__get_cpu_var(idle_state)) == 0)
return;
atomic_notifier_call_chain(&idle_notifier, IDLE_END, NULL);
}

void exit_idle(void)
{
if (current->pid)
return;
__exit_idle();
}

void disable_hlt(void)
{
hlt_counter++;
Expand Down Expand Up @@ -130,6 +167,7 @@ EXPORT_SYMBOL(default_idle);
*/
static void poll_idle (void)
{
local_irq_enable();
cpu_relax();
}

Expand Down Expand Up @@ -189,7 +227,16 @@ void cpu_idle(void)
play_dead();

__get_cpu_var(irq_stat).idle_timestamp = jiffies;

/*
* Idle routines should keep interrupts disabled
* from here on, until they go to idle.
* Otherwise, idle callbacks can misfire.
*/
local_irq_disable();
enter_idle();
idle();
__exit_idle();
}
preempt_enable_no_resched();
schedule();
Expand Down Expand Up @@ -243,7 +290,11 @@ void mwait_idle_with_hints(unsigned long eax, unsigned long ecx)
__monitor((void *)&current_thread_info()->flags, 0, 0);
smp_mb();
if (!need_resched())
__mwait(eax, ecx);
__sti_mwait(eax, ecx);
else
local_irq_enable();
} else {
local_irq_enable();
}
}

Expand Down
2 changes: 2 additions & 0 deletions arch/i386/kernel/smp.c
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@

#include <asm/mtrr.h>
#include <asm/tlbflush.h>
#include <asm/idle.h>
#include <mach_apic.h>

/*
Expand Down Expand Up @@ -624,6 +625,7 @@ fastcall void smp_call_function_interrupt(struct pt_regs *regs)
/*
* At this point the info structure may be out of scope unless wait==1
*/
exit_idle();
irq_enter();
(*func)(info);
irq_exit();
Expand Down
14 changes: 14 additions & 0 deletions include/asm-i386/idle.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
#ifndef _ASM_I386_IDLE_H
#define _ASM_I386_IDLE_H 1

#define IDLE_START 1
#define IDLE_END 2

struct notifier_block;
void idle_notifier_register(struct notifier_block *n);
void idle_notifier_unregister(struct notifier_block *n);

void exit_idle(void);
void enter_idle(void);

#endif
8 changes: 8 additions & 0 deletions include/asm-i386/processor.h
Original file line number Diff line number Diff line change
Expand Up @@ -257,6 +257,14 @@ static inline void __mwait(unsigned long eax, unsigned long ecx)
: :"a" (eax), "c" (ecx));
}

static inline void __sti_mwait(unsigned long eax, unsigned long ecx)
{
/* "mwait %eax,%ecx;" */
asm volatile(
"sti; .byte 0x0f,0x01,0xc9;"
: :"a" (eax), "c" (ecx));
}

extern void mwait_idle_with_hints(unsigned long eax, unsigned long ecx);

/* from system description table in BIOS. Mostly for MCA use, but
Expand Down

0 comments on commit 2ff2d3d

Please sign in to comment.