summaryrefslogtreecommitdiffstats
path: root/arch/powerpc/kernel/irq.c
diff options
context:
space:
mode:
authorHugh Dickins <hugh@veritas.com>2006-11-10 21:32:40 +0000
committerPaul Mackerras <paulus@samba.org>2006-12-04 20:39:20 +1100
commitef2b343e99e772e35f0f9d00f7db318b6629c16e (patch)
tree386f9b6b1376ab99a478ab5d394572fca720d0ef /arch/powerpc/kernel/irq.c
parent56291e19e37cf3bb8fc701ebf3aa8ffbf59f73ef (diff)
downloadlinux-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.c57
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();