Name: Suspend And Resume APIC for x86 Author: Rusty Russell Status: Untested Depends: Hotcpu/atomic-cpudown-i386.patch.gz diff -urpN --exclude TAGS -X /home/rusty/devel/kernel/kernel-patches/current-dontdiff --minimal .22470-linux-2.6.5-rc2-bk4/arch/i386/kernel/apic.c .22470-linux-2.6.5-rc2-bk4.updated/arch/i386/kernel/apic.c --- .22470-linux-2.6.5-rc2-bk4/arch/i386/kernel/apic.c 2004-03-25 15:39:22.000000000 +1100 +++ .22470-linux-2.6.5-rc2-bk4.updated/arch/i386/kernel/apic.c 2004-03-25 16:35:35.000000000 +1100 @@ -470,13 +470,9 @@ void __init setup_local_APIC (void) apic_pm_activate(); } -#ifdef CONFIG_PM - -static struct { - /* 'active' is true if the local APIC was enabled by us and - not the BIOS; this signifies that we are also responsible - for disabling it before entering apm/acpi suspend */ - int active; +#if defined(CONFIG_PM) || defined(CONFIG_HOTPLUG_CPU) +struct apic_state +{ /* r/w apic fields */ unsigned int apic_id; unsigned int apic_taskpri; @@ -491,75 +487,95 @@ static struct { unsigned int apic_tmict; unsigned int apic_tdcr; unsigned int apic_thmr; -} apic_pm_state; +}; +static DEFINE_PER_CPU(struct apic_state, apic_state); -static int lapic_suspend(struct sys_device *dev, u32 state) +void apic_stop(void) { unsigned long flags; + struct apic_state *state; - if (!apic_pm_state.active) - return 0; - - apic_pm_state.apic_id = apic_read(APIC_ID); - apic_pm_state.apic_taskpri = apic_read(APIC_TASKPRI); - apic_pm_state.apic_ldr = apic_read(APIC_LDR); - apic_pm_state.apic_dfr = apic_read(APIC_DFR); - apic_pm_state.apic_spiv = apic_read(APIC_SPIV); - apic_pm_state.apic_lvtt = apic_read(APIC_LVTT); - apic_pm_state.apic_lvtpc = apic_read(APIC_LVTPC); - apic_pm_state.apic_lvt0 = apic_read(APIC_LVT0); - apic_pm_state.apic_lvt1 = apic_read(APIC_LVT1); - apic_pm_state.apic_lvterr = apic_read(APIC_LVTERR); - apic_pm_state.apic_tmict = apic_read(APIC_TMICT); - apic_pm_state.apic_tdcr = apic_read(APIC_TDCR); - apic_pm_state.apic_thmr = apic_read(APIC_LVTTHMR); + state = &get_cpu_var(apic_state); + state->apic_id = apic_read(APIC_ID); + state->apic_taskpri = apic_read(APIC_TASKPRI); + state->apic_ldr = apic_read(APIC_LDR); + state->apic_dfr = apic_read(APIC_DFR); + state->apic_spiv = apic_read(APIC_SPIV); + state->apic_lvtt = apic_read(APIC_LVTT); + state->apic_lvtpc = apic_read(APIC_LVTPC); + state->apic_lvt0 = apic_read(APIC_LVT0); + state->apic_lvt1 = apic_read(APIC_LVT1); + state->apic_lvterr = apic_read(APIC_LVTERR); + state->apic_tmict = apic_read(APIC_TMICT); + state->apic_tdcr = apic_read(APIC_TDCR); + state->apic_thmr = apic_read(APIC_LVTTHMR); local_irq_save(flags); disable_local_APIC(); local_irq_restore(flags); - return 0; + put_cpu_var(apic_state); } -static int lapic_resume(struct sys_device *dev) +void apic_start(void) { unsigned int l, h; unsigned long flags; - - if (!apic_pm_state.active) - return 0; + struct apic_state *state; local_irq_save(flags); - /* - * Make sure the APICBASE points to the right address - * - * FIXME! This will be wrong if we ever support suspend on - * SMP! We'll need to do this as part of the CPU restore! - */ + state = &__get_cpu_var(apic_state); + rdmsr(MSR_IA32_APICBASE, l, h); l &= ~MSR_IA32_APICBASE_BASE; l |= MSR_IA32_APICBASE_ENABLE | mp_lapic_addr; wrmsr(MSR_IA32_APICBASE, l, h); apic_write(APIC_LVTERR, ERROR_APIC_VECTOR | APIC_LVT_MASKED); - apic_write(APIC_ID, apic_pm_state.apic_id); - apic_write(APIC_DFR, apic_pm_state.apic_dfr); - apic_write(APIC_LDR, apic_pm_state.apic_ldr); - apic_write(APIC_TASKPRI, apic_pm_state.apic_taskpri); - apic_write(APIC_SPIV, apic_pm_state.apic_spiv); - apic_write(APIC_LVT0, apic_pm_state.apic_lvt0); - apic_write(APIC_LVT1, apic_pm_state.apic_lvt1); - apic_write(APIC_LVTTHMR, apic_pm_state.apic_thmr); - apic_write(APIC_LVTPC, apic_pm_state.apic_lvtpc); - apic_write(APIC_LVTT, apic_pm_state.apic_lvtt); - apic_write(APIC_TDCR, apic_pm_state.apic_tdcr); - apic_write(APIC_TMICT, apic_pm_state.apic_tmict); + apic_write(APIC_ID, state->apic_id); + apic_write(APIC_DFR, state->apic_dfr); + apic_write(APIC_LDR, state->apic_ldr); + apic_write(APIC_TASKPRI, state->apic_taskpri); + apic_write(APIC_SPIV, state->apic_spiv); + apic_write(APIC_LVT0, state->apic_lvt0); + apic_write(APIC_LVT1, state->apic_lvt1); + apic_write(APIC_LVTTHMR, state->apic_thmr); + apic_write(APIC_LVTPC, state->apic_lvtpc); + apic_write(APIC_LVTT, state->apic_lvtt); + apic_write(APIC_TDCR, state->apic_tdcr); + apic_write(APIC_TMICT, state->apic_tmict); apic_write(APIC_ESR, 0); apic_read(APIC_ESR); - apic_write(APIC_LVTERR, apic_pm_state.apic_lvterr); + apic_write(APIC_LVTERR, state->apic_lvterr); apic_write(APIC_ESR, 0); apic_read(APIC_ESR); local_irq_restore(flags); +} +#endif /* CONFIG_PM || CONFIG_HOTPLUG_CPU */ + +#ifdef CONFIG_PM +/* True if the local APIC was enabled by us and not the BIOS; this + signifies that we are also responsible for disabling it before + entering apm/acpi suspend */ +static DEFINE_PER_CPU(int, apic_state_pm_active); + +static int lapic_suspend(struct sys_device *dev, u32 state) +{ + /* Not SMP safe */ + if (!__get_cpu_var(apic_state_pm_active)) + return 0; + + apic_stop(); + return 0; +} + +static int lapic_resume(struct sys_device *dev) +{ + /* Not SMP safe */ + if (!__get_cpu_var(apic_state_pm_active)) + return 0; + + apic_start(); return 0; } @@ -577,7 +593,8 @@ static struct sys_device device_lapic = static void __init apic_pm_activate(void) { - apic_pm_state.active = 1; + /* Not SMP safe */ + __get_cpu_var(apic_state_pm_active) = 1; } static int __init init_lapic_sysfs(void) @@ -586,7 +603,7 @@ static int __init init_lapic_sysfs(void) if (!cpu_has_apic) return 0; - /* XXX: remove suspend/resume procs if !apic_pm_state.active? */ + /* XXX: remove suspend/resume procs if !apic_state_pm_active? */ error = sysdev_class_register(&lapic_sysclass); if (!error) @@ -1030,9 +1047,7 @@ inline void smp_local_timer_interrupt(st { int cpu = smp_processor_id(); - /* FIXME: Actually remove timer interrupt in __cpu_disable() --RR */ - if (cpu_is_offline(cpu)) - return; + WARN_ON(cpu_is_offline(cpu)); x86_do_profile(regs); diff -urpN --exclude TAGS -X /home/rusty/devel/kernel/kernel-patches/current-dontdiff --minimal .22470-linux-2.6.5-rc2-bk4/arch/i386/kernel/process.c .22470-linux-2.6.5-rc2-bk4.updated/arch/i386/kernel/process.c --- .22470-linux-2.6.5-rc2-bk4/arch/i386/kernel/process.c 2004-03-25 15:39:22.000000000 +1100 +++ .22470-linux-2.6.5-rc2-bk4.updated/arch/i386/kernel/process.c 2004-03-25 16:35:25.000000000 +1100 @@ -137,7 +137,7 @@ static void poll_idle (void) #ifdef CONFIG_HOTPLUG_CPU /* We don't actually take CPU down, just spin without interrupts. */ -static inline void play_dead(void) +static void play_dead(void) { /* Ack it */ __get_cpu_var(cpu_state) = CPU_DEAD; @@ -154,6 +154,7 @@ static inline void play_dead(void) local_irq_disable(); __flush_tlb_all(); cpu_set(smp_processor_id(), cpu_online_map); + apic_start(); local_irq_enable(); enable_timer_nmi_watchdog(); } diff -urpN --exclude TAGS -X /home/rusty/devel/kernel/kernel-patches/current-dontdiff --minimal .22470-linux-2.6.5-rc2-bk4/arch/i386/kernel/smpboot.c .22470-linux-2.6.5-rc2-bk4.updated/arch/i386/kernel/smpboot.c --- .22470-linux-2.6.5-rc2-bk4/arch/i386/kernel/smpboot.c 2004-03-25 15:39:22.000000000 +1100 +++ .22470-linux-2.6.5-rc2-bk4.updated/arch/i386/kernel/smpboot.c 2004-03-25 16:11:02.000000000 +1100 @@ -1173,6 +1173,7 @@ int __cpu_disable(void) return -EBUSY; fixup_irqs(); + apic_stop(); return 0; } diff -urpN --exclude TAGS -X /home/rusty/devel/kernel/kernel-patches/current-dontdiff --minimal .22470-linux-2.6.5-rc2-bk4/include/asm-i386/apic.h .22470-linux-2.6.5-rc2-bk4.updated/include/asm-i386/apic.h --- .22470-linux-2.6.5-rc2-bk4/include/asm-i386/apic.h 2004-02-18 23:54:32.000000000 +1100 +++ .22470-linux-2.6.5-rc2-bk4.updated/include/asm-i386/apic.h 2004-03-25 16:19:36.000000000 +1100 @@ -101,4 +101,7 @@ extern unsigned int nmi_watchdog; #endif /* CONFIG_X86_LOCAL_APIC */ +void apic_stop(void); +void apic_start(void); + #endif /* __ASM_APIC_H */