 kernel/softirq.c | 60 ++++++++++++++++++++++++++++++++++++++++++++------------
 1 file changed, 47 insertions(+), 13 deletions(-)

diff --git a/kernel/softirq.c b/kernel/softirq.c
index ed567babe789..3736b509cad5 100644
--- a/kernel/softirq.c
+++ b/kernel/softirq.c
@@ -320,20 +320,52 @@ void irq_enter(void)
 	__irq_enter();
 }
 
-static inline void invoke_softirq(void)
+#define preempt_value_in_interrupt(val) \
+	((val) & (HARDIRQ_MASK | SOFTIRQ_MASK | NMI_MASK))
+
+/*
+ * Invoce softirq's from irq_exit().
+ *
+ * Return the preempt offset: either IRQ_EXIT_OFFSET (if we
+ * did nothing to the preemption count) or SOFTIRQ_OFFSET (in
+ * case we did softirq processing and changed the preemption
+ * count to account for that).
+ */
+static inline int invoke_softirq(void)
 {
-	if (!force_irqthreads) {
+	int offset = IRQ_EXIT_OFFSET;
+	int count = preempt_count() - offset;
+
+	/* Can we run softirq's at all? We migth be nesting interrupts */
+	if (preempt_value_in_interrupt(count))
+		return offset;
+
+	/* Are there any pending? */
+	if (!local_softirq_pending())
+		return offset;
+
+	/* Do we force irq threads? If so, just wake up the daemon */
+	if (force_irqthreads) {
+		wakeup_softirqd();
+		return offset;
+	}
+
+	/*
+	 * Ok, do actual softirq handling!
+	 *
+	 * This downgrades us from hardirq context to softirq context.
+	 */
+	offset = SOFTIRQ_OFFSET;
+	preempt_count() = count + offset;
+
+	trace_softirqs_off(__builtin_return_address(0));
 #ifdef __ARCH_IRQ_EXIT_IRQS_DISABLED
-		__do_softirq();
+	__do_softirq();
 #else
-		do_softirq();
+	do_softirq();
 #endif
-	} else {
-		__local_bh_disable((unsigned long)__builtin_return_address(0),
-				SOFTIRQ_OFFSET);
-		wakeup_softirqd();
-		__local_bh_enable(SOFTIRQ_OFFSET);
-	}
+	trace_softirqs_on((unsigned long)__builtin_return_address(0));
+	return offset;
 }
 
 /*
@@ -341,11 +373,13 @@ static inline void invoke_softirq(void)
  */
 void irq_exit(void)
 {
+	int offset;
+
 	vtime_account_irq_exit(current);
 	trace_hardirq_exit();
-	sub_preempt_count(IRQ_EXIT_OFFSET);
-	if (!in_interrupt() && local_softirq_pending())
-		invoke_softirq();
+
+	offset = invoke_softirq();
+	sub_preempt_count(offset);
 
 #ifdef CONFIG_NO_HZ
 	/* Make sure that timer wheel updates are propagated */
