diff options
author | Hugh Dickins <hugh@veritas.com> | 2006-11-10 21:32:40 +0000 |
---|---|---|
committer | Paul Mackerras <paulus@samba.org> | 2006-12-04 20:39:20 +1100 |
commit | ef2b343e99e772e35f0f9d00f7db318b6629c16e (patch) | |
tree | 386f9b6b1376ab99a478ab5d394572fca720d0ef /arch/powerpc/kernel/irq.c | |
parent | 56291e19e37cf3bb8fc701ebf3aa8ffbf59f73ef (diff) | |
download | linux-ef2b343e99e772e35f0f9d00f7db318b6629c16e.tar.bz2 |
[POWERPC] Make soft_enabled irqs preempt safe
Rewrite local_get_flags and local_irq_disable to use r13 explicitly,
to avoid the risk that gcc will split get_paca()->soft_enabled into a
sequence unsafe against preemption. Similar care in local_irq_restore.
Signed-off-by: Hugh Dickins <hugh@veritas.com>
Signed-off-by: Paul Mackerras <paulus@samba.org>
Diffstat (limited to 'arch/powerpc/kernel/irq.c')
-rw-r--r-- | arch/powerpc/kernel/irq.c | 57 |
1 files changed, 52 insertions, 5 deletions
diff --git a/arch/powerpc/kernel/irq.c b/arch/powerpc/kernel/irq.c index e1936952017c..0bd8c7665834 100644 --- a/arch/powerpc/kernel/irq.c +++ b/arch/powerpc/kernel/irq.c @@ -97,22 +97,69 @@ EXPORT_SYMBOL(irq_desc); int distribute_irqs = 1; +static inline unsigned long get_hard_enabled(void) +{ + unsigned long enabled; + + __asm__ __volatile__("lbz %0,%1(13)" + : "=r" (enabled) : "i" (offsetof(struct paca_struct, hard_enabled))); + + return enabled; +} + +static inline void set_soft_enabled(unsigned long enable) +{ + __asm__ __volatile__("stb %0,%1(13)" + : : "r" (enable), "i" (offsetof(struct paca_struct, soft_enabled))); +} + void local_irq_restore(unsigned long en) { - get_paca()->soft_enabled = en; + /* + * get_paca()->soft_enabled = en; + * Is it ever valid to use local_irq_restore(0) when soft_enabled is 1? + * That was allowed before, and in such a case we do need to take care + * that gcc will set soft_enabled directly via r13, not choose to use + * an intermediate register, lest we're preempted to a different cpu. + */ + set_soft_enabled(en); if (!en) return; if (firmware_has_feature(FW_FEATURE_ISERIES)) { - if (get_paca()->lppaca_ptr->int_dword.any_int) + /* + * Do we need to disable preemption here? Not really: in the + * unlikely event that we're preempted to a different cpu in + * between getting r13, loading its lppaca_ptr, and loading + * its any_int, we might call iseries_handle_interrupts without + * an interrupt pending on the new cpu, but that's no disaster, + * is it? And the business of preempting us off the old cpu + * would itself involve a local_irq_restore which handles the + * interrupt to that cpu. + * + * But use "local_paca->lppaca_ptr" instead of "get_lppaca()" + * to avoid any preemption checking added into get_paca(). + */ + if (local_paca->lppaca_ptr->int_dword.any_int) iseries_handle_interrupts(); return; } - if (get_paca()->hard_enabled) + /* + * if (get_paca()->hard_enabled) return; + * But again we need to take care that gcc gets hard_enabled directly + * via r13, not choose to use an intermediate register, lest we're + * preempted to a different cpu in between the two instructions. + */ + if (get_hard_enabled()) return; - /* need to hard-enable interrupts here */ - get_paca()->hard_enabled = en; + + /* + * Need to hard-enable interrupts here. Since currently disabled, + * no need to take further asm precautions against preemption; but + * use local_paca instead of get_paca() to avoid preemption checking. + */ + local_paca->hard_enabled = en; if ((int)mfspr(SPRN_DEC) < 0) mtspr(SPRN_DEC, 1); hard_irq_enable(); |