diff --git a/include/trace/events/irq.h b/include/trace/events/irq.h
index 837c1740d0d07..1b2e05aaf7763 100644
--- a/include/trace/events/irq.h
+++ b/include/trace/events/irq.h
@@ -207,6 +207,62 @@ DEFINE_EVENT(tasklet, tasklet_exit,
TP_ARGS(t, func)
);
+/*
+ * Following tracepoints are not exported in tracefs and provide hooking
+ * mechanisms only for testing and debugging purposes.
+ */
+
+/*
+ * hardirq_context_entry - generic hardirq state has been established
+ * @depth: hardirq nesting depth, including the context being entered
+ *
+ * This tracepoint is emitted after generic hardirq preempt-count, lockdep,
+ * timer, and accounting state has been established. It does not describe the
+ * architecture-specific interval before irq_enter() or irq_enter_rcu().
+ */
+DECLARE_TRACE(hardirq_context_entry,
+ TP_PROTO(unsigned int depth),
+ TP_ARGS(depth));
+
+/*
+ * hardirq_context_exit - generic hardirq state is about to be removed
+ * @depth: hardirq nesting depth, including the context being exited
+ *
+ * This tracepoint is emitted with interrupts disabled near the end of generic
+ * hardirq exit, after tick-exit work and before hardirq preempt-count state is
+ * removed. A late-work handoff may run after callbacks return. Hardirq
+ * accounting ends earlier.
+ */
+DECLARE_TRACE(hardirq_context_exit,
+ TP_PROTO(unsigned int depth),
+ TP_ARGS(depth));
+
+/*
+ * softirq_context_entry - serving-softirq state has been established
+ * @serving: whether the current task is serving a softirq
+ * @from_hardirq: whether hardirq state was replaced by serving-softirq state
+ *
+ * This tracepoint runs with local interrupts disabled after the serving bit is
+ * set. On PREEMPT_RT it includes preparation before handle_softirqs(), even
+ * if the pending-work check finds nothing. It does not mark an individual
+ * action or the start of CPU-time accounting.
+ */
+DECLARE_TRACE(softirq_context_entry,
+ TP_PROTO(bool serving, bool from_hardirq),
+ TP_ARGS(serving, from_hardirq));
+
+/*
+ * softirq_context_exit - serving-softirq state is about to be removed
+ * @serving: whether the current task is serving a softirq
+ * @from_hardirq: whether hardirq state will replace serving-softirq state
+ *
+ * This tracepoint runs with local interrupts disabled before the serving bit is
+ * cleared. On PREEMPT_RT this includes teardown after handle_softirqs().
+ */
+DECLARE_TRACE(softirq_context_exit,
+ TP_PROTO(bool serving, bool from_hardirq),
+ TP_ARGS(serving, from_hardirq));
+
#endif /* _TRACE_IRQ_H */
/* This part must be outside protection */
diff --git a/kernel/softirq.c b/kernel/softirq.c
index 7fc2200392cdf..0d5fe265e354b 100644
--- a/kernel/softirq.c
+++ b/kernel/softirq.c
@@ -178,7 +178,9 @@ bool local_bh_blocked(void)
void __local_bh_disable_ip(unsigned long ip, unsigned int cnt)
{
+ unsigned long trace_flags = 0;
unsigned long flags;
+ bool trace_serving;
int newcnt;
WARN_ON_ONCE(in_hardirq());@@ -200,6 +202,16 @@ void __local_bh_disable_ip(unsigned long ip, unsigned int cnt)
}
}
+ /*
+ * A serving interval starts here, before handle_softirqs(). When its
+ * entry is traced, keep IRQs and task switches from observing the bit
+ * before the event. Any blocking BH lock was acquired above.
+ */
+ trace_serving = (cnt & SOFTIRQ_OFFSET) &&
+ trace_softirq_context_entry_tp_enabled();
+ if (trace_serving)
+ local_irq_save(trace_flags);
+
/*
* Track the per CPU softirq disabled state. On RT this is per CPU
* state to allow preemption of bottom half disabled sections.
@@ -233,15 +245,27 @@ void __local_bh_disable_ip(unsigned long ip, unsigned int cnt)
raw_local_irq_restore(flags);
}
}
+
+ if (trace_serving) {
+ trace_softirq_context_entry_tp(!!in_serving_softirq(), false);
+ local_irq_restore(trace_flags);
+ }
}
EXPORT_SYMBOL(__local_bh_disable_ip);
static void __local_bh_enable(unsigned int cnt, bool unlock)
{
+ bool entering = (cnt & SOFTIRQ_OFFSET) && !in_serving_softirq();
+ bool trace_exiting = (cnt & SOFTIRQ_OFFSET) && !entering &&
+ trace_softirq_context_exit_tp_enabled();
unsigned long flags;
bool sirq_en = false;
int newcnt;
+ /* Callers already disabled IRQs around the counter and its events. */
+ if (trace_exiting)
+ trace_softirq_context_exit_tp(!!in_serving_softirq(), false);
+
if (IS_ENABLED(CONFIG_PREEMPT_RT_NEEDS_BH_LOCK)) {
DEBUG_LOCKS_WARN_ON(current->softirq_disable_cnt !=
this_cpu_read(softirq_ctrl.cnt));@@ -276,6 +300,18 @@ static void __local_bh_enable(unsigned int cnt, bool unlock)
WARN_ON_ONCE(current->softirq_disable_cnt < 0);
}
}
+
+ /*
+ * An exit probe can raise a softirq while serving state still suppresses
+ * the normal ksoftirqd wakeup. Hand it off once no outer BH-disable
+ * reference remains.
+ */
+ if (trace_exiting && sirq_en && unlikely(local_softirq_pending()))
+ wakeup_softirqd();
+
+ /* local_bh_enable() can turn BH-disabled state into serving state. */
+ if (entering)
+ trace_softirq_context_entry_tp(!!in_serving_softirq(), false);
}
void __local_bh_enable_ip(unsigned long ip, unsigned int cnt)
@@ -640,11 +676,15 @@ static void handle_softirqs(bool ksirqd)
*/
current->flags &= ~PF_MEMALLOC;
- pending = local_softirq_pending();
-
from_irq_exit = softirq_handle_begin();
in_hardirq = lockdep_softirq_start();
account_softirq_enter(current);
+ if (!IS_ENABLED(CONFIG_PREEMPT_RT))
+ trace_softirq_context_entry_tp(!!in_serving_softirq(),
+ from_irq_exit);
+
+ /* Include work raised by the context-entry event. */
+ pending = local_softirq_pending();
restart:
/* Reset the pending bitmask before enabling irqs */
@@ -692,6 +732,14 @@ static void handle_softirqs(bool ksirqd)
wakeup_softirqd();
}
+ if (!IS_ENABLED(CONFIG_PREEMPT_RT) &&
+ trace_softirq_context_exit_tp_enabled()) {
+ trace_softirq_context_exit_tp(!!in_serving_softirq(),
+ from_irq_exit);
+ /* The final pending-work decision above has already completed. */
+ if (unlikely(local_softirq_pending()))
+ wakeup_softirqd();
+ }
account_softirq_exit(current);
lockdep_softirq_end(in_hardirq);
softirq_handle_end(from_irq_exit);@@ -722,6 +770,7 @@ void irq_enter_rcu(void)
tick_irq_enter();
account_hardirq_enter(current);
+ trace_hardirq_context_entry_tp(hardirq_count() >> HARDIRQ_SHIFT);
}
/**
@@ -796,6 +845,36 @@ static inline void __irq_exit_rcu(void)
wake_timersd();
tick_irq_exit();
+ if (trace_hardirq_context_exit_tp_enabled()) {
+ bool ksoftirqd_wake_requested = false;
+ bool timersd_wake_requested = false;
+
+ trace_hardirq_context_exit_tp(hardirq_count() >> HARDIRQ_SHIFT);
+
+ /* Hand work raised by the final boundary event to its worker. */
+ if (hardirq_count() == HARDIRQ_OFFSET) {
+ if (irq_count() == HARDIRQ_OFFSET &&
+ unlikely(local_softirq_pending()) &&
+ should_wake_ksoftirqd()) {
+ wakeup_softirqd();
+ ksoftirqd_wake_requested = true;
+ }
+
+ if (IS_ENABLED(CONFIG_IRQ_FORCED_THREADING) &&
+ force_irqthreads() && local_timers_pending_force_th()) {
+ wake_timersd();
+ timersd_wake_requested = true;
+ }
+
+ /* The timersd wakeup can itself raise a softirq. */
+ if (timersd_wake_requested &&
+ !ksoftirqd_wake_requested &&
+ irq_count() == HARDIRQ_OFFSET &&
+ unlikely(local_softirq_pending()) &&
+ should_wake_ksoftirqd())
+ wakeup_softirqd();
+ }
+ }
preempt_count_sub(HARDIRQ_OFFSET);
}
--
2.39.5 (Apple Git-154)