From 94235414a0568f260b60b89ec584ff4829d5e8c9 Mon Sep 17 00:00:00 2001 From: Lukas Beckmann Date: Sat, 22 Aug 2026 18:05:00 +0200 Subject: genirq: Discard cpumask of irqaffinity= boot parameter if parsing fails irq_affinity_setup() ignores the return value of cpulist_parse(). This can result in using a "half parsed" cpulist. E.g. on a system with 8 cores, the parameter "irqaffinity=0,1,6-10,4" would result in using only CPU 0 and 1, since the parsing fails at 6-10. Other boot parameters which take a cpulist (e.g. isolcpus=, workqueue.unbound_cpus=) already handle this by falling back to a default. Check the return value of cpulist_parse() and let init_irq_default_affinity() set the default in early_irq_init() later, if parsing fails. Signed-off-by: Lukas Beckmann Signed-off-by: Thomas Gleixner Reviewed-by: Radu Rendec Link: https://patch.msgid.link/20260822160502.4665-1-lbckmnn@mailbox.org --- kernel/irq/irqdesc.c | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) (limited to 'kernel') diff --git a/kernel/irq/irqdesc.c b/kernel/irq/irqdesc.c index 3a818f07a101..7cbcc3fb9e6d 100644 --- a/kernel/irq/irqdesc.c +++ b/kernel/irq/irqdesc.c @@ -28,7 +28,15 @@ static struct lock_class_key irq_desc_lock_class; static int __init irq_affinity_setup(char *str) { alloc_bootmem_cpumask_var(&irq_default_affinity); - cpulist_parse(str, irq_default_affinity); + if (cpulist_parse(str, irq_default_affinity) < 0) { + /* + * Clear the mask so that init_irq_default_affinity() + * defaults it later. + */ + cpumask_clear(irq_default_affinity); + pr_warn("irqaffinity: incorrect CPU range, using default\n"); + return 1; + } /* * Set at least the boot cpu. We don't want to end up with * bugreports caused by random commandline masks -- cgit v1.2.3 From a0e1fdb96578ea562a03c487f70673d228824d0a Mon Sep 17 00:00:00 2001 From: Enlin Mu Date: Tue, 25 Aug 2026 09:15:09 +0800 Subject: softirq: Remove redundant h->action from preempt_count mismatch error When a preempt_count mismatch is detected in handle_softirqs(), the error message currently prints the softirq action handler (h->action). However, as Thomas Gleixner pointed out, the action handler for a given softirq vector is always a known, fixed function (e.g., run_timer_softirq for TIMER_SOFTIRQ). Printing it provides zero diagnostic value. The real issue typically lies in the functions invoked *within* that handler, not the handler entry point itself. Therefore, as suggested by Sebastian Andrzej Siewior, remove h->action from the print statement entirely to clean up the error message. Suggested-by: Thomas Gleixner Suggested-by: Sebastian Andrzej Siewior Signed-off-by: Enlin Mu Signed-off-by: Thomas Gleixner Reviewed-by: Sebastian Andrzej Siewior Reviewed-by: Bradley Morgan Link: https://patch.msgid.link/20260825011509.8505-1-enlin.mu@linux.dev --- kernel/softirq.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'kernel') diff --git a/kernel/softirq.c b/kernel/softirq.c index 7980a4a232f9..800d81c57302 100644 --- a/kernel/softirq.c +++ b/kernel/softirq.c @@ -645,8 +645,8 @@ restart: h->action(); trace_softirq_exit(vec_nr); if (unlikely(prev_count != preempt_count())) { - pr_err("huh, entered softirq %u %s %p with preempt_count %08x, exited with %08x?\n", - vec_nr, softirq_to_name[vec_nr], h->action, + pr_err("huh, entered softirq %u %s with preempt_count %08x, exited with %08x?\n", + vec_nr, softirq_to_name[vec_nr], prev_count, preempt_count()); preempt_count_set(prev_count); } -- cgit v1.2.3 From feb5c9a2447a7d65a17d949c81c1f21864cde172 Mon Sep 17 00:00:00 2001 From: Karl Mehltretter Date: Sun, 11 Oct 2026 08:07:42 +0200 Subject: softirq: Preserve interrupt context on return from interrupt __irq_exit_rcu() removes HARDIRQ_OFFSET from preempt_count() before the interrupt exit work. On the outermost interrupt exit, with no NMI or softirq state remaining, this makes context checks report task context. The softirq-dispatch, timer-wakeup and tick-exit checks account for the early decrement, but other code reached in this window sees the wrong context: - ftrace, perf and the ring buffer record task context and use the task recursion and context slots. - KCSAN attributes accesses to the interrupted task. KMSAN uses and changes the interrupted task's KMSAN context. KCOV and the printk caller id see a task. - On PREEMPT_RT can_spin_trylock() and local_trylock() reject hard interrupt context to avoid interfering with PI when the interrupted task is blocked on a lock. Those checks do not reject calls made in this window. BPF programs attached to sched_waking or sched_wakeup can reach the trylock path through kmalloc_nolock(). - An oops kills the interrupted task instead of ending in "Fatal exception in interrupt". The early decrement predates git. No failure caused by this misclassification is known. lockdep is unaffected because lockdep_hardirq_exit() is the last operation in irq_exit(). Keep HARDIRQ_OFFSET until the end of __irq_exit_rcu(), including tick_irq_exit(). Softirq handlers must run in BH context, so replace HARDIRQ_OFFSET with SOFTIRQ_OFFSET while they run and restore it afterwards. Use one raw counter update for each transition to preserve the preemption disable location recorded by irq_enter_rcu(), and update lockdep explicitly. Detect IRQ exit with in_hardirq() in softirq_handle_begin() because __do_softirq() can be reached through do_softirq_own_stack() and cannot take an argument. Adjust the exit checks for HARDIRQ_OFFSET still being present. Use irq_count() == HARDIRQ_OFFSET for softirq dispatch, as in irq_enter_rcu(). Use hardirq_count() == HARDIRQ_OFFSET for the timer thread wakeup and tick_irq_exit(). NMI exits use a separate path. An IRQ nested in an NMI has an extra HARDIRQ_OFFSET from NMI entry, so it is excluded too. The timer thread check must ignore softirq_count() to allow wakeups when an IRQ interrupts softirq processing or a BH-disabled section. The timer thread handles all pending softirqs, including any raised during its wakeup. A softirq raised from a tracepoint on the final preempt_count_sub() waits for the next interrupt exit and can trigger NOHZ tick-stop warnings. Handling that instrumentation case does not justify a check on every interrupt exit. A tracepoint on tick_irq_exit() can also raise a softirq after the normal softirq handling has finished. The number of counter updates and interrupt time accounting are unchanged. The preemptoff tracer now reports the interrupt and subsequent softirq processing as one section. With nofuncgraph-irqs, function graph tracing also skips the IRQ exit work, including the __do_softirq() frame. Suggested-by: Peter Zijlstra Signed-off-by: Karl Mehltretter Signed-off-by: Thomas Gleixner Assisted-by: LLM Reviewed-by: Bradley Morgan Reviewed-by: Frederic Weisbecker Reviewed-by: Sebastian Andrzej Siewior Link: https://lore.kernel.org/r/20260813130826.GW687043@noisy.programming.kicks-ass.net Link: https://patch.msgid.link/20261011060742.30584-1-kmehltretter@gmail.com --- kernel/softirq.c | 60 ++++++++++++++++++++++++++++++++++++++++++++------------ 1 file changed, 47 insertions(+), 13 deletions(-) (limited to 'kernel') diff --git a/kernel/softirq.c b/kernel/softirq.c index c3729c5b284b..7fc2200392cd 100644 --- a/kernel/softirq.c +++ b/kernel/softirq.c @@ -350,8 +350,8 @@ static inline void ksoftirqd_run_end(void) local_irq_enable(); } -static inline void softirq_handle_begin(void) { } -static inline void softirq_handle_end(void) { } +static inline bool softirq_handle_begin(void) { return false; } +static inline void softirq_handle_end(bool from_irq_exit) { } static inline bool should_wake_ksoftirqd(void) { @@ -481,15 +481,38 @@ void __local_bh_enable_ip(unsigned long ip, unsigned int cnt) } EXPORT_SYMBOL(__local_bh_enable_ip); -static inline void softirq_handle_begin(void) +static inline bool softirq_handle_begin(void) { - __local_bh_disable_ip(_RET_IP_, SOFTIRQ_OFFSET); + if (!in_hardirq()) { + __local_bh_disable_ip(_RET_IP_, SOFTIRQ_OFFSET); + return false; + } + + /* + * Only reached from irq_exit(), with HARDIRQ_OFFSET still set. + * Replace it with SOFTIRQ_OFFSET before handle_softirqs() enables + * interrupts. Use the raw operation to preserve the preemption + * disable location recorded by irq_enter_rcu(), and update lockdep + * directly. + */ + __preempt_count_sub(HARDIRQ_OFFSET - SOFTIRQ_OFFSET); + lockdep_softirqs_off(_RET_IP_); + WARN_ON_ONCE(irq_count() != SOFTIRQ_OFFSET); + + return true; } -static inline void softirq_handle_end(void) +static inline void softirq_handle_end(bool from_irq_exit) { - __local_bh_enable(SOFTIRQ_OFFSET); - WARN_ON_ONCE(in_interrupt()); + if (!from_irq_exit) { + __local_bh_enable(SOFTIRQ_OFFSET); + WARN_ON_ONCE(in_interrupt()); + return; + } + + lockdep_softirqs_on(_RET_IP_); + __preempt_count_add(HARDIRQ_OFFSET - SOFTIRQ_OFFSET); + WARN_ON_ONCE(irq_count() != HARDIRQ_OFFSET); } static inline void ksoftirqd_run_begin(void) @@ -605,6 +628,7 @@ static void handle_softirqs(bool ksirqd) unsigned long old_flags = current->flags; int max_restart = MAX_SOFTIRQ_RESTART; struct softirq_action *h; + bool from_irq_exit; bool in_hardirq; __u32 pending; int softirq_bit; @@ -618,7 +642,7 @@ static void handle_softirqs(bool ksirqd) pending = local_softirq_pending(); - softirq_handle_begin(); + from_irq_exit = softirq_handle_begin(); in_hardirq = lockdep_softirq_start(); account_softirq_enter(current); @@ -670,7 +694,7 @@ restart: account_softirq_exit(current); lockdep_softirq_end(in_hardirq); - softirq_handle_end(); + softirq_handle_end(from_irq_exit); current_restore_flags(old_flags, PF_MEMALLOC); } @@ -716,7 +740,7 @@ static inline void tick_irq_exit(void) /* Make sure that timer wheel updates are propagated */ if ((sched_core_idle_cpu(cpu) && !need_resched()) || tick_nohz_full_cpu(cpu)) { - if (!in_hardirq()) + if (hardirq_count() == HARDIRQ_OFFSET) tick_nohz_irq_exit(); } #endif @@ -748,8 +772,12 @@ static inline void __irq_exit_rcu(void) lockdep_assert_irqs_disabled(); #endif account_hardirq_exit(current); - preempt_count_sub(HARDIRQ_OFFSET); - if (!in_interrupt() && local_softirq_pending()) { + + /* + * HARDIRQ_OFFSET is still set. Only the outermost interrupt handles + * softirqs, and only if it did not hit a softirq or BH disabled section. + */ + if (irq_count() == HARDIRQ_OFFSET && local_softirq_pending()) { /* * If we left hrtimers unarmed, make sure to arm them now, * before enabling interrupts to run softirq. @@ -758,11 +786,17 @@ static inline void __irq_exit_rcu(void) invoke_softirq(); } + /* + * Wake the timer thread even if the interrupt hit a softirq or a + * section with BHs disabled. Only the outermost interrupt can wake it. + */ if (IS_ENABLED(CONFIG_IRQ_FORCED_THREADING) && force_irqthreads() && - local_timers_pending_force_th() && !(in_nmi() | in_hardirq())) + local_timers_pending_force_th() && + hardirq_count() == HARDIRQ_OFFSET) wake_timersd(); tick_irq_exit(); + preempt_count_sub(HARDIRQ_OFFSET); } /** -- cgit v1.2.3 From d04adff6860587e843948b0c31174f8ae11b8dfe Mon Sep 17 00:00:00 2001 From: Richard Patel Date: Fri, 9 Oct 2026 14:58:41 +0000 Subject: genirq: Defer kfree(irqaction) until irq_thread_dtor() finishes When an interrupt thread exits abnormally via do_exit() (e.g. the interrupt handler oopses) without returning from irq_thread(), a concurrent call to free_irq() could free the irqaction before the irq_thread_dtor() exit task work runs. irq thread (oopsed) free_irq() ------------------- ---------- do_exit() exit_mm() complete_vfork_done() -------> kthread_stop() returns exit_files() ... kfree(action) exit_task_work() irq_thread_dtor() action->... <- use-after-free Track the pending exit work with the IRQTF_EXIT_WORK thread flag, and have __free_irq() wait for the bit to clear before kfree(). irq_thread_dtor() only runs on irq_thread abnormal exit. If that dtor also faults, free_irq() will hang indefinitely, but double IRQ thread faults require a reboot anyways. [ tglx: Add missing include ] Fixes: ed3e694d78cc ("move exit_task_work() past exit_files() et.al.") Signed-off-by: Richard Patel Signed-off-by: Thomas Gleixner Cc: stable@vger.kernel.org Link: https://patch.msgid.link/20261009145842.1616117-2-ripatel@wii.dev --- kernel/irq/internals.h | 2 ++ kernel/irq/manage.c | 26 ++++++++++++++++++++------ 2 files changed, 22 insertions(+), 6 deletions(-) (limited to 'kernel') diff --git a/kernel/irq/internals.h b/kernel/irq/internals.h index 0ce21dd45404..2c30e7537987 100644 --- a/kernel/irq/internals.h +++ b/kernel/irq/internals.h @@ -36,6 +36,7 @@ extern struct irqaction chained_action; * IRQTF_AFFINITY - irq thread is requested to adjust affinity * IRQTF_FORCED_THREAD - irq action is force threaded * IRQTF_READY - signals that irq thread is ready + * IRQTF_EXIT_WORK - irq thread exit task_work is pending */ enum { IRQTF_RUNTHREAD, @@ -43,6 +44,7 @@ enum { IRQTF_AFFINITY, IRQTF_FORCED_THREAD, IRQTF_READY, + IRQTF_EXIT_WORK, }; /* diff --git a/kernel/irq/manage.c b/kernel/irq/manage.c index 57eff26fa646..87a2476ea259 100644 --- a/kernel/irq/manage.c +++ b/kernel/irq/manage.c @@ -21,6 +21,7 @@ #include #include #include +#include #include "internals.h" @@ -1176,17 +1177,14 @@ void wake_threads_waitq(struct irq_desc *desc) wake_up(&desc->wait_for_threads); } -static void irq_thread_dtor(struct callback_head *unused) +static void __irq_thread_dtor(struct irqaction *action) { struct task_struct *tsk = current; struct irq_desc *desc; - struct irqaction *action; if (WARN_ON_ONCE(!(current->flags & PF_EXITING))) return; - action = kthread_data(tsk); - pr_err("exiting task \"%s\" (%d) is an active IRQ thread (irq %d)\n", tsk->comm, tsk->pid, action->irq); @@ -1203,6 +1201,14 @@ static void irq_thread_dtor(struct callback_head *unused) irq_finalize_oneshot(desc, action); } +static void irq_thread_dtor(struct callback_head *unused) +{ + struct irqaction *action = kthread_data(current); + + __irq_thread_dtor(action); + clear_and_wake_up_bit(IRQTF_EXIT_WORK, &action->thread_flags); +} + static void irq_wake_secondary(struct irq_desc *desc, struct irqaction *action) { struct irqaction *secondary = action->secondary; @@ -1263,6 +1269,7 @@ static int irq_thread(void *data) else handler_fn = irq_thread_fn; + set_bit(IRQTF_EXIT_WORK, &action->thread_flags); init_task_work(&on_exit_work, irq_thread_dtor); task_work_add(current, &on_exit_work, TWA_NONE); @@ -1283,6 +1290,7 @@ static int irq_thread(void *data) * oneshot mask bit can be set. */ task_work_cancel_func(current, irq_thread_dtor); + clear_and_wake_up_bit(IRQTF_EXIT_WORK, &action->thread_flags); return 0; } @@ -1853,6 +1861,12 @@ out_mput: return ret; } +static void irq_stop_thread(struct irqaction *action) +{ + kthread_stop_put(action->thread); + wait_on_bit(&action->thread_flags, IRQTF_EXIT_WORK, TASK_UNINTERRUPTIBLE); +} + /* * Internal function to unregister an irqaction - used to free * regular and special interrupts that are part of the architecture. @@ -1958,9 +1972,9 @@ static struct irqaction *__free_irq(struct irq_desc *desc, void *dev_id) * the same bit to a newly requested action. */ if (action->thread) { - kthread_stop_put(action->thread); + irq_stop_thread(action); if (action->secondary && action->secondary->thread) - kthread_stop_put(action->secondary->thread); + irq_stop_thread(action->secondary); } /* Last action releases resources */ -- cgit v1.2.3 From 89a9e003a2ef73f67dff03dfd59e0a5474b56b0b Mon Sep 17 00:00:00 2001 From: Richard Patel Date: Fri, 9 Oct 2026 14:58:42 +0000 Subject: genirq: Move interrupt thread exit_work object to irqaction irq_thread() registers irq_thread_dtor() as exit task_work using a stack-allocated callback_head. On normal interrupt thread exit, this work gets cancelled and never runs. On x86, an abnormal interrupt thread exit (e.g. handler oops) discards the stack, including the callback_head, while that callback_head is still pending. By the time exit_task_work() runs, the stack memory formerly backing callback_head is already clobbered. irq_thread() <-- on_exit_work allocated ** IRQ handler faults ** oops_end() rewind_stack_and_make_dead() <-- on_exit_work freed make_task_dead() do_exit(SIGSEGV) ... exit_shm() <-- on_exit_work clobbered ... exit_task_work() <-- stack use-after-free exit_shm()+0x30 <-- wild jump to return addr Thus, a benign threaded IRQ oops (e.g. a NULL pointer deref) is turned into a wild jump. This is caught by kernel IBT: ** first fault ** Oops: general protection fault, kernel NULL pointer dereference 0x3c0: 0000 [#1] SMP NOPTI ... ** second fault ** Missing ENDBR: exit_shm+0x30/0x1c0 kernel BUG at arch/x86/kernel/cet.c:133! ... ? task_work_run+0x5c/0x90 ? do_exit+0x209/0x460 ? rewind_stack_and_make_dead+0x16/0x20 ... note: irq/165-AudioDS[1427] exited with irqs disabled Fixing recursive fault but reboot is needed! Fix this by storing the callback_head in struct irqaction. The irqaction is only freed once irq_thread_dtor() finishes. Fixes: 2deb4be28077 ("x86/dumpstack: When OOPSing, rewind the stack before do_exit()") Signed-off-by: Richard Patel Signed-off-by: Thomas Gleixner Cc: stable@vger.kernel.org Link: https://patch.msgid.link/20261009145842.1616117-3-ripatel@wii.dev --- kernel/irq/manage.c | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) (limited to 'kernel') diff --git a/kernel/irq/manage.c b/kernel/irq/manage.c index 87a2476ea259..3b647d9b458a 100644 --- a/kernel/irq/manage.c +++ b/kernel/irq/manage.c @@ -1201,9 +1201,9 @@ static void __irq_thread_dtor(struct irqaction *action) irq_finalize_oneshot(desc, action); } -static void irq_thread_dtor(struct callback_head *unused) +static void irq_thread_dtor(struct callback_head *work) { - struct irqaction *action = kthread_data(current); + struct irqaction *action = container_of(work, struct irqaction, thread_dtor); __irq_thread_dtor(action); clear_and_wake_up_bit(IRQTF_EXIT_WORK, &action->thread_flags); @@ -1250,7 +1250,6 @@ static void wake_up_and_wait_for_irq_thread_ready(struct irq_desc *desc, */ static int irq_thread(void *data) { - struct callback_head on_exit_work; struct irqaction *action = data; struct irq_desc *desc = irq_to_desc(action->irq); irqreturn_t (*handler_fn)(struct irq_desc *desc, @@ -1270,8 +1269,8 @@ static int irq_thread(void *data) handler_fn = irq_thread_fn; set_bit(IRQTF_EXIT_WORK, &action->thread_flags); - init_task_work(&on_exit_work, irq_thread_dtor); - task_work_add(current, &on_exit_work, TWA_NONE); + init_task_work(&action->thread_dtor, irq_thread_dtor); + task_work_add(current, &action->thread_dtor, TWA_NONE); while (!irq_wait_for_interrupt(desc, action)) { irqreturn_t action_ret; -- cgit v1.2.3 From d2fa415329c138117c870069ebeeb9ac67caf5ef Mon Sep 17 00:00:00 2001 From: Hemanth Selam Date: Fri, 9 Oct 2026 12:18:07 +0530 Subject: genirq: Fix typos in comments Correct the spelling of "descriptor" and "preceded" in the kernel-doc for msi_insert_desc() and msi_next_desc(). Signed-off-by: Hemanth Selam Signed-off-by: Thomas Gleixner Reviewed-by: Radu Rendec Link: https://patch.msgid.link/20261009064807.70345-1-hemanth.selam@gmail.com --- kernel/irq/msi.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'kernel') diff --git a/kernel/irq/msi.c b/kernel/irq/msi.c index fb5f372215bf..b22d74496af1 100644 --- a/kernel/irq/msi.c +++ b/kernel/irq/msi.c @@ -142,7 +142,7 @@ fail: * insert it at @init_desc->msi_index * * @dev: Pointer to the device for which the descriptor is allocated - * @domid: The id of the interrupt domain to which the desriptor is added + * @domid: The id of the interrupt domain to which the descriptor is added * @init_desc: Pointer to an MSI descriptor to initialize the new descriptor * * Return: 0 on success or an appropriate failure code. @@ -416,7 +416,7 @@ EXPORT_SYMBOL_GPL(msi_domain_first_desc); * @domid: The id of the interrupt domain which should be walked. * @filter: Descriptor state filter * - * The first invocation of msi_next_desc() has to be preceeded by a + * The first invocation of msi_next_desc() has to be preceded by a * successful invocation of __msi_first_desc(). Consecutive invocations are * only valid if the previous one was successful. All these operations have * to be done within the same MSI mutex held region. -- cgit v1.2.3 From e74798f2f8e1562f2bbd2fed8d81929eeef39ffc Mon Sep 17 00:00:00 2001 From: Haris Okanovic Date: Fri, 11 Sep 2026 13:31:59 -0500 Subject: genirq/proc: Fix missing CPU header in /proc/interrupts irq_seq_show() must be idempotent since it may be called multiple times to render the same record, e.g. when seq_file's buffer is exceeded. It emits the CPU header behind the one-shot print_header flag, which the first pass clears, so the header is lost with the discarded buffer. This can be seen on large systems, such as 8g.48xl (192 CPUs) and 8i.96xl (384 CPUs) AWS instance types: $ head -1 /proc/interrupts 9: 0 0 ... 0 GICv3 25 Level vgic Replace print_header with the position of the first record and include the header whenever that record is emitted. Fixes: 171cc0d9eed1 ("genirq/proc: Speed up /proc/interrupts iteration") Signed-off-by: Haris Okanovic Signed-off-by: Thomas Gleixner Tested-by: Michael Kelley Reviewed-by: Radu Rendec Link: https://patch.msgid.link/20260911-dev-harisokn-proc-interrupts-fix-b4-v1-1-f9ff208efcfa@amazon.com --- kernel/irq/proc.c | 24 +++++++++++++++++------- 1 file changed, 17 insertions(+), 7 deletions(-) (limited to 'kernel') diff --git a/kernel/irq/proc.c b/kernel/irq/proc.c index 1b835725f7b1..0c0df16de581 100644 --- a/kernel/irq/proc.c +++ b/kernel/irq/proc.c @@ -462,7 +462,7 @@ int __weak arch_show_interrupts(struct seq_file *p, int prec) static DEFINE_RAW_SPINLOCK(irq_proc_constraints_lock); static struct irq_proc_constraints { - bool print_header; + loff_t first_pos; unsigned int num_prec; unsigned int chip_width; } irq_proc_constraints __read_mostly = { @@ -543,14 +543,13 @@ static int irq_seq_show(struct seq_file *p, void *v) struct irqaction *action; /* Print header for the first interrupt? */ - if (constr->print_header) { + if (p->index == constr->first_pos) { unsigned int cpu; seq_printf(p, "%*s", constr->num_prec + 8, ""); for_each_online_cpu(cpu) seq_printf(p, "CPU%-8d", cpu); seq_putc(p, '\n'); - constr->print_header = false; } if (desc == ARCH_PROC_IRQDESC) @@ -635,14 +634,25 @@ static void *irq_seq_next_desc(loff_t *pos) static void *irq_seq_start(struct seq_file *f, loff_t *pos) { - if (!*pos) { - struct irq_proc_constraints *constr = f->private; + struct irq_proc_constraints *constr = f->private; + bool first = !*pos; + void *ret; + if (first) { constr->num_prec = READ_ONCE(irq_proc_constraints.num_prec); constr->chip_width = READ_ONCE(irq_proc_constraints.chip_width); - constr->print_header = true; } - return irq_seq_next_desc(pos); + + ret = irq_seq_next_desc(pos); + + /* + * Record position of the first output record so that irq_seq_show() + * knows where to write the header. + */ + if (first) + constr->first_pos = *pos; + + return ret; } static void *irq_seq_next(struct seq_file *f, void *v, loff_t *pos) -- cgit v1.2.3