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 */