From c2c7983c93f5d86962318be7e7298f1bc3feb1a6 Mon Sep 17 00:00:00 2001 From: Pengpeng Hou Date: Fri, 3 Apr 2026 16:55:56 +0800 Subject: [PATCH 01/18] genirq/proc: Size interrupt directory names for 10-digit interrupt numbers /proc/irq// directory names are built in `char name[10]` buffers with `sprintf(name, "%u", irq)`. Ten-digit IRQ numbers already need 11 bytes including the trailing NUL, and current sparse-IRQ configurations allow interrupt numbers in that range. Size the temporary name buffer for the current decimal form and switch to bounded formatting when creating or removing the proc entry. Signed-off-by: Pengpeng Hou Signed-off-by: Thomas Gleixner Link: https://patch.msgid.link/20260404101001.1-genirq-proc-pengpeng@iscas.ac.cn --- kernel/irq/proc.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/kernel/irq/proc.c b/kernel/irq/proc.c index b0999a4f1f68..dfa0b0723642 100644 --- a/kernel/irq/proc.c +++ b/kernel/irq/proc.c @@ -10,6 +10,7 @@ #include #include #include +#include #include #include #include @@ -326,7 +327,7 @@ void register_handler_proc(unsigned int irq, struct irqaction *action) #undef MAX_NAMELEN -#define MAX_NAMELEN 10 +#define MAX_NAMELEN 11 void register_irq_proc(unsigned int irq, struct irq_desc *desc) { @@ -348,7 +349,7 @@ void register_irq_proc(unsigned int irq, struct irq_desc *desc) return; /* create /proc/irq/1234 */ - sprintf(name, "%u", irq); + snprintf(name, MAX_NAMELEN, "%u", irq); desc->dir = proc_mkdir(name, root_irq_dir); if (!desc->dir) return; @@ -401,7 +402,7 @@ void unregister_irq_proc(unsigned int irq, struct irq_desc *desc) #endif remove_proc_entry("spurious", desc->dir); - sprintf(name, "%u", irq); + snprintf(name, MAX_NAMELEN, "%u", irq); remove_proc_entry(name, root_irq_dir); } From 115bbf0c1b60cb7bed348c64694eb88e21e7d458 Mon Sep 17 00:00:00 2001 From: Dmitry Ilvokhin Date: Sun, 17 May 2026 22:01:33 +0200 Subject: [PATCH 02/18] x86/irq: Optimize interrupts decimals printing Monitoring tools periodically scan /proc/interrupts to export metrics as a timeseries for future analysis and investigation. In large fleets, /proc/interrupts is polled (often every few seconds) on every machine. The cumulative overhead adds up quickly across thousands of nodes, so reducing the cost of generating these stats does have a measurable operational impact. With the ongoing trend toward higher core counts per machine, this cost becomes even more noticeable over time, since interrupt counters are per-CPU. In Meta's fleet, we have observed this overhead at scale. Although a binary /proc interface would be a better long-term solution due to lower formatting (kernel side) and parsing (userspace side) overhead, the text interface will remain in use for some time, even if better solutions will be available. Optimizing the /proc/interrupts printing code is therefore still beneficial. Function seq_printf() supports rich format string for decimals printing, but it doesn't required for printing /proc/interrupts per CPU counters, seq_put_decimal_ull_width() function can be used instead to print per CPU counters, because very limited formatting is required for this case. Similar optimization idea is already used in show_interrupts(). As a side effect this aligns the x86 decriptions with the generic interrupts event descriptions. Performance counter stats (truncated) for 'sh -c cat /proc/interrupts Before: 3.42 msec task-clock # 0.802 CPUs utilized ( +- 0.05% ) 1 context-switches # 291.991 /sec ( +- 0.74% ) 0 cpu-migrations # 0.000 /sec 343 page-faults # 100.153 K/sec ( +- 0.01% ) 8,932,242 instructions # 1.66 insn per cycle ( +- 0.34% ) 5,374,427 cycles # 1.569 GHz ( +- 0.04% ) 1,483,154 branches # 433.068 M/sec ( +- 0.22% ) 28,768 branch-misses # 1.94% of all branches ( +- 0.31% ) 0.00427182 +- 0.00000215 seconds time elapsed ( +- 0.05% ) After: 2.39 msec task-clock # 0.796 CPUs utilized ( +- 0.06% ) 1 context-switches # 418.541 /sec ( +- 0.70% ) 0 cpu-migrations # 0.000 /sec 343 page-faults # 143.560 K/sec ( +- 0.01% ) 7,020,982 instructions # 1.30 insn per cycle ( +- 0.52% ) 5,397,266 cycles # 2.259 GHz ( +- 0.06% ) 1,569,648 branches # 656.962 M/sec ( +- 0.08% ) 25,419 branch-misses # 1.62% of all branches ( +- 0.72% ) 0.00299996 +- 0.00000206 seconds time elapsed ( +- 0.07% ) Relative speed up in time elapsed is around 29%. [ tglx: Fixed it up so it applies to current mainline ] Signed-off-by: Dmitry Ilvokhin Signed-off-by: Thomas Gleixner Tested-by: Michael Kelley Reviewed-by: Thomas Gleixner Reviewed-by: Radu Rendec Link: https://patch.msgid.link/aQj5mGZ6_BBlAm3B@shell.ilvokhin.com Link: https://patch.msgid.link/20260517194930.949709489@kernel.org --- arch/x86/kernel/irq.c | 112 ++++++++++++++++++++++-------------------- 1 file changed, 59 insertions(+), 53 deletions(-) diff --git a/arch/x86/kernel/irq.c b/arch/x86/kernel/irq.c index ec77be217eaf..963690b56f9f 100644 --- a/arch/x86/kernel/irq.c +++ b/arch/x86/kernel/irq.c @@ -62,6 +62,18 @@ void ack_bad_irq(unsigned int irq) apic_eoi(); } +/* + * A helper routine for putting space and decimal number without overhead + * from rich format of printf(). + */ +static void put_decimal(struct seq_file *p, unsigned long long num) +{ + const char *delimiter = " "; + unsigned int width = 10; + + seq_put_decimal_ull_width(p, delimiter, num, width); +} + #define irq_stats(x) (&per_cpu(irq_stat, x)) /* * /proc/interrupts printing for arch specific interrupts @@ -70,103 +82,101 @@ int arch_show_interrupts(struct seq_file *p, int prec) { int j; - seq_printf(p, "%*s: ", prec, "NMI"); + seq_printf(p, "%*s:", prec, "NMI"); for_each_online_cpu(j) - seq_printf(p, "%10u ", irq_stats(j)->__nmi_count); + put_decimal(p, irq_stats(j)->__nmi_count); seq_puts(p, " Non-maskable interrupts\n"); #ifdef CONFIG_X86_LOCAL_APIC - seq_printf(p, "%*s: ", prec, "LOC"); + seq_printf(p, "%*s:", prec, "LOC"); for_each_online_cpu(j) - seq_printf(p, "%10u ", irq_stats(j)->apic_timer_irqs); + put_decimal(p, irq_stats(j)->apic_timer_irqs); seq_puts(p, " Local timer interrupts\n"); - seq_printf(p, "%*s: ", prec, "SPU"); + seq_printf(p, "%*s:", prec, "SPU"); for_each_online_cpu(j) - seq_printf(p, "%10u ", irq_stats(j)->irq_spurious_count); + put_decimal(p, irq_stats(j)->irq_spurious_count); seq_puts(p, " Spurious interrupts\n"); - seq_printf(p, "%*s: ", prec, "PMI"); + seq_printf(p, "%*s:", prec, "PMI"); for_each_online_cpu(j) - seq_printf(p, "%10u ", irq_stats(j)->apic_perf_irqs); + put_decimal(p, irq_stats(j)->apic_perf_irqs); seq_puts(p, " Performance monitoring interrupts\n"); - seq_printf(p, "%*s: ", prec, "IWI"); + seq_printf(p, "%*s:", prec, "IWI"); for_each_online_cpu(j) - seq_printf(p, "%10u ", irq_stats(j)->apic_irq_work_irqs); + put_decimal(p, irq_stats(j)->apic_irq_work_irqs); seq_puts(p, " IRQ work interrupts\n"); - seq_printf(p, "%*s: ", prec, "RTR"); + seq_printf(p, "%*s:", prec, "RTR"); for_each_online_cpu(j) - seq_printf(p, "%10u ", irq_stats(j)->icr_read_retry_count); + put_decimal(p, irq_stats(j)->icr_read_retry_count); seq_puts(p, " APIC ICR read retries\n"); if (x86_platform_ipi_callback) { - seq_printf(p, "%*s: ", prec, "PLT"); + seq_printf(p, "%*s:", prec, "PLT"); for_each_online_cpu(j) - seq_printf(p, "%10u ", irq_stats(j)->x86_platform_ipis); + put_decimal(p, irq_stats(j)->x86_platform_ipis); seq_puts(p, " Platform interrupts\n"); } #endif #ifdef CONFIG_SMP - seq_printf(p, "%*s: ", prec, "RES"); + seq_printf(p, "%*s:", prec, "RES"); for_each_online_cpu(j) - seq_printf(p, "%10u ", irq_stats(j)->irq_resched_count); + put_decimal(p, irq_stats(j)->irq_resched_count); seq_puts(p, " Rescheduling interrupts\n"); - seq_printf(p, "%*s: ", prec, "CAL"); + seq_printf(p, "%*s:", prec, "CAL"); for_each_online_cpu(j) - seq_printf(p, "%10u ", irq_stats(j)->irq_call_count); + put_decimal(p, irq_stats(j)->irq_call_count); seq_puts(p, " Function call interrupts\n"); - seq_printf(p, "%*s: ", prec, "TLB"); + seq_printf(p, "%*s:", prec, "TLB"); for_each_online_cpu(j) - seq_printf(p, "%10u ", irq_stats(j)->irq_tlb_count); + put_decimal(p, irq_stats(j)->irq_tlb_count); seq_puts(p, " TLB shootdowns\n"); #endif #ifdef CONFIG_X86_THERMAL_VECTOR - seq_printf(p, "%*s: ", prec, "TRM"); + seq_printf(p, "%*s:", prec, "TRM"); for_each_online_cpu(j) - seq_printf(p, "%10u ", irq_stats(j)->irq_thermal_count); + put_decimal(p, irq_stats(j)->irq_thermal_count); seq_puts(p, " Thermal event interrupts\n"); #endif #ifdef CONFIG_X86_MCE_THRESHOLD - seq_printf(p, "%*s: ", prec, "THR"); + seq_printf(p, "%*s:", prec, "THR"); for_each_online_cpu(j) - seq_printf(p, "%10u ", irq_stats(j)->irq_threshold_count); + put_decimal(p, irq_stats(j)->irq_threshold_count); seq_puts(p, " Threshold APIC interrupts\n"); #endif #ifdef CONFIG_X86_MCE_AMD - seq_printf(p, "%*s: ", prec, "DFR"); + seq_printf(p, "%*s:", prec, "DFR"); for_each_online_cpu(j) - seq_printf(p, "%10u ", irq_stats(j)->irq_deferred_error_count); + put_decimal(p, irq_stats(j)->irq_deferred_error_count); seq_puts(p, " Deferred Error APIC interrupts\n"); #endif #ifdef CONFIG_X86_MCE - seq_printf(p, "%*s: ", prec, "MCE"); + seq_printf(p, "%*s:", prec, "MCE"); for_each_online_cpu(j) - seq_printf(p, "%10u ", per_cpu(mce_exception_count, j)); + put_decimal(p, per_cpu(mce_exception_count, j)); seq_puts(p, " Machine check exceptions\n"); - seq_printf(p, "%*s: ", prec, "MCP"); + seq_printf(p, "%*s:", prec, "MCP"); for_each_online_cpu(j) - seq_printf(p, "%10u ", per_cpu(mce_poll_count, j)); + put_decimal(p, per_cpu(mce_poll_count, j)); seq_puts(p, " Machine check polls\n"); #endif #ifdef CONFIG_X86_HV_CALLBACK_VECTOR if (test_bit(HYPERVISOR_CALLBACK_VECTOR, system_vectors)) { - seq_printf(p, "%*s: ", prec, "HYP"); + seq_printf(p, "%*s:", prec, "HYP"); for_each_online_cpu(j) - seq_printf(p, "%10u ", - irq_stats(j)->irq_hv_callback_count); + put_decimal(p, irq_stats(j)->irq_hv_callback_count); seq_puts(p, " Hypervisor callback interrupts\n"); } #endif #if IS_ENABLED(CONFIG_HYPERV) if (test_bit(HYPERV_REENLIGHTENMENT_VECTOR, system_vectors)) { - seq_printf(p, "%*s: ", prec, "HRE"); + seq_printf(p, "%*s:", prec, "HRE"); for_each_online_cpu(j) - seq_printf(p, "%10u ", - irq_stats(j)->irq_hv_reenlightenment_count); + put_decimal(p, + irq_stats(j)->irq_hv_reenlightenment_count); seq_puts(p, " Hyper-V reenlightenment interrupts\n"); } if (test_bit(HYPERV_STIMER0_VECTOR, system_vectors)) { - seq_printf(p, "%*s: ", prec, "HVS"); + seq_printf(p, "%*s:", prec, "HVS"); for_each_online_cpu(j) - seq_printf(p, "%10u ", - irq_stats(j)->hyperv_stimer0_count); + put_decimal(p, irq_stats(j)->hyperv_stimer0_count); seq_puts(p, " Hyper-V stimer0 interrupts\n"); } #endif @@ -175,35 +185,31 @@ int arch_show_interrupts(struct seq_file *p, int prec) seq_printf(p, "%*s: %10u\n", prec, "MIS", atomic_read(&irq_mis_count)); #endif #if IS_ENABLED(CONFIG_KVM) - seq_printf(p, "%*s: ", prec, "PIN"); + seq_printf(p, "%*s:", prec, "PIN"); for_each_online_cpu(j) - seq_printf(p, "%10u ", irq_stats(j)->kvm_posted_intr_ipis); + put_decimal(p, irq_stats(j)->kvm_posted_intr_ipis); seq_puts(p, " Posted-interrupt notification event\n"); - seq_printf(p, "%*s: ", prec, "NPI"); + seq_printf(p, "%*s:", prec, "NPI"); for_each_online_cpu(j) - seq_printf(p, "%10u ", - irq_stats(j)->kvm_posted_intr_nested_ipis); + put_decimal(p, irq_stats(j)->kvm_posted_intr_nested_ipis); seq_puts(p, " Nested posted-interrupt event\n"); - seq_printf(p, "%*s: ", prec, "PIW"); + seq_printf(p, "%*s:", prec, "PIW"); for_each_online_cpu(j) - seq_printf(p, "%10u ", - irq_stats(j)->kvm_posted_intr_wakeup_ipis); + put_decimal(p, irq_stats(j)->kvm_posted_intr_wakeup_ipis); seq_puts(p, " Posted-interrupt wakeup event\n"); #endif #ifdef CONFIG_GUEST_PERF_EVENTS - seq_printf(p, "%*s: ", prec, "VPMI"); + seq_printf(p, "%*s:", prec, "VPMI"); for_each_online_cpu(j) - seq_printf(p, "%10u ", - irq_stats(j)->perf_guest_mediated_pmis); + put_decimal(p, irq_stats(j)->perf_guest_mediated_pmis); seq_puts(p, " Perf Guest Mediated PMI\n"); #endif #ifdef CONFIG_X86_POSTED_MSI - seq_printf(p, "%*s: ", prec, "PMN"); + seq_printf(p, "%*s:", prec, "PMN"); for_each_online_cpu(j) - seq_printf(p, "%10u ", - irq_stats(j)->posted_msi_notification_count); + put_decimal(p, irq_stats(j)->posted_msi_notification_count); seq_puts(p, " Posted MSI notification event\n"); #endif return 0; From 95c33a64f203be444954a1e1d855a4820c4f0efa Mon Sep 17 00:00:00 2001 From: Thomas Gleixner Date: Sun, 17 May 2026 22:01:38 +0200 Subject: [PATCH 03/18] genirq/proc: Avoid formatting zero counts in /proc/interrupts A large portion of interrupt count entries are zero. There is no point in formatting the zero value as it is way cheeper to just emit a constant string. Collect the number of consecutive zero counts and emit them in one go before a non-zero count and at the end of the line. Signed-off-by: Thomas Gleixner Tested-by: Michael Kelley Reviewed-by: Dmitry Ilvokhin Reviewed-by: Radu Rendec Reviewed-by: Shrikanth Hegde Link: https://patch.msgid.link/20260517194931.034728540@kernel.org --- include/linux/interrupt.h | 1 + kernel/irq/proc.c | 43 ++++++++++++++++++++++++++++++++++----- 2 files changed, 39 insertions(+), 5 deletions(-) diff --git a/include/linux/interrupt.h b/include/linux/interrupt.h index 6cd26ffb0505..3bf969ad8fe0 100644 --- a/include/linux/interrupt.h +++ b/include/linux/interrupt.h @@ -864,6 +864,7 @@ static inline void init_irq_proc(void) struct seq_file; int show_interrupts(struct seq_file *p, void *v); int arch_show_interrupts(struct seq_file *p, int prec); +void irq_proc_emit_counts(struct seq_file *p, unsigned int __percpu *cnts); extern int early_irq_init(void); extern int arch_probe_nr_irqs(void); diff --git a/kernel/irq/proc.c b/kernel/irq/proc.c index dfa0b0723642..378b523a1b00 100644 --- a/kernel/irq/proc.c +++ b/kernel/irq/proc.c @@ -451,6 +451,43 @@ int __weak arch_show_interrupts(struct seq_file *p, int prec) # define ACTUAL_NR_IRQS irq_get_nr_irqs() #endif +/* Same as seq_put_decimal_ull_width(p, " ", cnt, 10) */ +#define ZSTR1 " 0" +#define ZSTR1_LEN (sizeof(ZSTR1) - 1) +#define ZSTR16 ZSTR1 ZSTR1 ZSTR1 ZSTR1 ZSTR1 ZSTR1 ZSTR1 ZSTR1 \ + ZSTR1 ZSTR1 ZSTR1 ZSTR1 ZSTR1 ZSTR1 ZSTR1 ZSTR1 +#define ZSTR256 ZSTR16 ZSTR16 ZSTR16 ZSTR16 ZSTR16 ZSTR16 ZSTR16 ZSTR16 \ + ZSTR16 ZSTR16 ZSTR16 ZSTR16 ZSTR16 ZSTR16 ZSTR16 ZSTR16 + +static inline void irq_proc_emit_zero_counts(struct seq_file *p, unsigned int zeros) +{ + if (!zeros) + return; + + for (unsigned int n = min(zeros, 256); n; zeros -= n, n = min(zeros, 256)) + seq_write(p, ZSTR256, n * ZSTR1_LEN); +} + +static inline unsigned int irq_proc_emit_count(struct seq_file *p, unsigned int cnt, + unsigned int zeros) +{ + if (!cnt) + return zeros + 1; + + irq_proc_emit_zero_counts(p, zeros); + seq_put_decimal_ull_width(p, " ", cnt, 10); + return 0; +} + +void irq_proc_emit_counts(struct seq_file *p, unsigned int __percpu *cnts) +{ + unsigned int cpu, zeros = 0; + + for_each_online_cpu(cpu) + zeros = irq_proc_emit_count(p, per_cpu(*cnts, cpu), zeros); + irq_proc_emit_zero_counts(p, zeros); +} + int show_interrupts(struct seq_file *p, void *v) { const unsigned int nr_irqs = irq_get_nr_irqs(); @@ -486,11 +523,7 @@ int show_interrupts(struct seq_file *p, void *v) return 0; seq_printf(p, "%*d:", prec, i); - for_each_online_cpu(j) { - unsigned int cnt = desc->kstat_irqs ? per_cpu(desc->kstat_irqs->cnt, j) : 0; - - seq_put_decimal_ull_width(p, " ", cnt, 10); - } + irq_proc_emit_counts(p, &desc->kstat_irqs->cnt); seq_putc(p, ' '); guard(raw_spinlock_irq)(&desc->lock); From 0179464391af9a01b911f441d2dda42ea253dfbd Mon Sep 17 00:00:00 2001 From: Thomas Gleixner Date: Sun, 17 May 2026 22:01:43 +0200 Subject: [PATCH 04/18] genirq/proc: Utilize irq_desc::tot_count to avoid evaluation Interrupts which are not marked per CPU increment not only the per CPU statistics, but also the accumulation counter irq_desc::tot_count. Change the counter to type unsigned long so it does not produce sporadic zeros due to wrap arounds on 64-bit machines and do a quick check for non per CPU interrupts. If the counter is zero, then simply emit a full set of zero strings. That spares the evaluation of the per CPU counters completely for interrupts with zero events. Signed-off-by: Thomas Gleixner Tested-by: Michael Kelley Reviewed-by: Dmitry Ilvokhin Reviewed-by: Radu Rendec Link: https://patch.msgid.link/20260517194931.115522199@kernel.org --- include/linux/irqdesc.h | 6 +++--- kernel/irq/proc.c | 11 ++++++++++- 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/include/linux/irqdesc.h b/include/linux/irqdesc.h index dae9a9b93665..1058786d76eb 100644 --- a/include/linux/irqdesc.h +++ b/include/linux/irqdesc.h @@ -52,8 +52,8 @@ struct irq_redirect { * @depth: disable-depth, for nested irq_disable() calls * @wake_depth: enable depth, for multiple irq_set_irq_wake() callers * @tot_count: stats field for non-percpu irqs - * @irq_count: stats field to detect stalled irqs * @last_unhandled: aging timer for unhandled count + * @irq_count: stats field to detect stalled irqs * @irqs_unhandled: stats field for spurious unhandled interrupts * @threads_handled: stats field for deferred spurious detection of threaded handlers * @threads_handled_last: comparator field for deferred spurious detection of threaded handlers @@ -87,9 +87,9 @@ struct irq_desc { unsigned int core_internal_state__do_not_mess_with_it; unsigned int depth; /* nested irq disables */ unsigned int wake_depth; /* nested wake enables */ - unsigned int tot_count; - unsigned int irq_count; /* For detecting broken IRQs */ + unsigned long tot_count; unsigned long last_unhandled; /* Aging timer for unhandled count */ + unsigned int irq_count; /* For detecting broken IRQs */ unsigned int irqs_unhandled; atomic_t threads_handled; int threads_handled_last; diff --git a/kernel/irq/proc.c b/kernel/irq/proc.c index 378b523a1b00..5a1805b0b8a9 100644 --- a/kernel/irq/proc.c +++ b/kernel/irq/proc.c @@ -523,7 +523,16 @@ int show_interrupts(struct seq_file *p, void *v) return 0; seq_printf(p, "%*d:", prec, i); - irq_proc_emit_counts(p, &desc->kstat_irqs->cnt); + + /* + * Always output per CPU interrupts. Output device interrupts only when + * desc::tot_count is not zero. + */ + if (irq_settings_is_per_cpu(desc) || irq_settings_is_per_cpu_devid(desc) || + data_race(desc->tot_count)) + irq_proc_emit_counts(p, &desc->kstat_irqs->cnt); + else + irq_proc_emit_zero_counts(p, num_online_cpus()); seq_putc(p, ' '); guard(raw_spinlock_irq)(&desc->lock); From 2b57c69917eeba3ee657f252257e37f31916ba2a Mon Sep 17 00:00:00 2001 From: Thomas Gleixner Date: Sun, 17 May 2026 22:01:48 +0200 Subject: [PATCH 05/18] x86/irq: Make irqstats array based Having the x86 specific interrupt statistics as a data structure with individual members instead of an array is just stupid as it requires endless copy and paste in arch_show_interrupts() and arch_irq_stat_cpu(), where the latter does not even take the latest interrupt additions into account. The resulting #ifdef orgy is just disgusting. Convert it to an array of counters, which does not make a difference in the actual interrupt hotpath increment as the array index is constant and therefore not any different than the member based access. But in arch_show_interrupts() and arch_irq_stat_cpu() this just turns into a loop, which reduces the text size by ~2k (~12%): text data bss dec hex filename 19643 15250 904 35797 8bd5 ../build/arch/x86/kernel/irq.o 17355 15250 904 33509 82e5 ../build/arch/x86/kernel/irq.o Adding a new vector or software counter only requires to update the table and everything just works. Using the core provided emit function which speeds up 0 outputs makes it significantly faster. Signed-off-by: Thomas Gleixner Tested-by: Michael Kelley Reviewed-by: Radu Rendec Link: https://patch.msgid.link/20260517194931.196070643@kernel.org --- arch/x86/events/amd/core.c | 2 +- arch/x86/events/amd/ibs.c | 2 +- arch/x86/events/core.c | 2 +- arch/x86/events/intel/core.c | 2 +- arch/x86/events/intel/knc.c | 2 +- arch/x86/events/intel/p4.c | 2 +- arch/x86/events/zhaoxin/core.c | 2 +- arch/x86/hyperv/hv_init.c | 2 +- arch/x86/include/asm/hardirq.h | 101 +++++----- arch/x86/include/asm/mce.h | 3 - arch/x86/kernel/apic/apic.c | 4 +- arch/x86/kernel/apic/ipi.c | 2 +- arch/x86/kernel/cpu/acrn.c | 2 +- arch/x86/kernel/cpu/mce/amd.c | 2 +- arch/x86/kernel/cpu/mce/core.c | 8 +- arch/x86/kernel/cpu/mce/threshold.c | 2 +- arch/x86/kernel/cpu/mshyperv.c | 4 +- arch/x86/kernel/irq.c | 283 +++++++++++----------------- arch/x86/kernel/irq_work.c | 2 +- arch/x86/kernel/kvm.c | 2 +- arch/x86/kernel/nmi.c | 4 +- arch/x86/kernel/smp.c | 6 +- arch/x86/mm/tlb.c | 2 +- arch/x86/xen/enlighten_hvm.c | 2 +- arch/x86/xen/enlighten_pv.c | 2 +- arch/x86/xen/smp.c | 6 +- arch/x86/xen/smp_pv.c | 2 +- 27 files changed, 198 insertions(+), 257 deletions(-) diff --git a/arch/x86/events/amd/core.c b/arch/x86/events/amd/core.c index 0c92ed5f464b..305774b67995 100644 --- a/arch/x86/events/amd/core.c +++ b/arch/x86/events/amd/core.c @@ -1032,7 +1032,7 @@ static int amd_pmu_v2_handle_irq(struct pt_regs *regs) * Unmasking the LVTPC is not required as the Mask (M) bit of the LVT * PMI entry is not set by the local APIC when a PMC overflow occurs */ - inc_irq_stat(apic_perf_irqs); + inc_perf_irq_stat(); done: cpuc->enabled = pmu_enabled; diff --git a/arch/x86/events/amd/ibs.c b/arch/x86/events/amd/ibs.c index e0bd5051db2a..ed18a6d7e1a8 100644 --- a/arch/x86/events/amd/ibs.c +++ b/arch/x86/events/amd/ibs.c @@ -1600,7 +1600,7 @@ perf_ibs_nmi_handler(unsigned int cmd, struct pt_regs *regs) handled += perf_ibs_handle_irq(&perf_ibs_op, regs); if (handled) - inc_irq_stat(apic_perf_irqs); + inc_perf_irq_stat(); perf_sample_event_took(sched_clock() - stamp); diff --git a/arch/x86/events/core.c b/arch/x86/events/core.c index 810ab21ffd99..244ba2018c12 100644 --- a/arch/x86/events/core.c +++ b/arch/x86/events/core.c @@ -1750,7 +1750,7 @@ int x86_pmu_handle_irq(struct pt_regs *regs) } if (handled) - inc_irq_stat(apic_perf_irqs); + inc_perf_irq_stat(); return handled; } diff --git a/arch/x86/events/intel/core.c b/arch/x86/events/intel/core.c index d9488ade0f8e..2d0802c590c5 100644 --- a/arch/x86/events/intel/core.c +++ b/arch/x86/events/intel/core.c @@ -3504,7 +3504,7 @@ static int handle_pmi_common(struct pt_regs *regs, u64 status) int bit; int handled = 0; - inc_irq_stat(apic_perf_irqs); + inc_perf_irq_stat(); /* * Ignore a range of extra bits in status that do not indicate diff --git a/arch/x86/events/intel/knc.c b/arch/x86/events/intel/knc.c index e614baf42926..e887adc108ac 100644 --- a/arch/x86/events/intel/knc.c +++ b/arch/x86/events/intel/knc.c @@ -238,7 +238,7 @@ static int knc_pmu_handle_irq(struct pt_regs *regs) goto done; } - inc_irq_stat(apic_perf_irqs); + inc_perf_irq_stat(); for_each_set_bit(bit, (unsigned long *)&status, X86_PMC_IDX_MAX) { struct perf_event *event = cpuc->events[bit]; diff --git a/arch/x86/events/intel/p4.c b/arch/x86/events/intel/p4.c index 02bfdb77158b..5368dc31787c 100644 --- a/arch/x86/events/intel/p4.c +++ b/arch/x86/events/intel/p4.c @@ -1077,7 +1077,7 @@ static int p4_pmu_handle_irq(struct pt_regs *regs) } if (handled) - inc_irq_stat(apic_perf_irqs); + inc_perf_irq_stat(); /* * When dealing with the unmasking of the LVTPC on P4 perf hw, it has diff --git a/arch/x86/events/zhaoxin/core.c b/arch/x86/events/zhaoxin/core.c index 4bdfcf091200..4bc177badac2 100644 --- a/arch/x86/events/zhaoxin/core.c +++ b/arch/x86/events/zhaoxin/core.c @@ -373,7 +373,7 @@ static int zhaoxin_pmu_handle_irq(struct pt_regs *regs) else zhaoxin_pmu_ack_status(status); - inc_irq_stat(apic_perf_irqs); + inc_perf_irq_stat(); /* * CondChgd bit 63 doesn't mean any overflow status. Ignore diff --git a/arch/x86/hyperv/hv_init.c b/arch/x86/hyperv/hv_init.c index 323adc93f2dc..55a8b6de2865 100644 --- a/arch/x86/hyperv/hv_init.c +++ b/arch/x86/hyperv/hv_init.c @@ -219,7 +219,7 @@ static inline bool hv_reenlightenment_available(void) DEFINE_IDTENTRY_SYSVEC(sysvec_hyperv_reenlightenment) { apic_eoi(); - inc_irq_stat(irq_hv_reenlightenment_count); + inc_irq_stat(HYPERV_REENLIGHTENMENT); schedule_delayed_work(&hv_reenlightenment_work, HZ/10); } diff --git a/arch/x86/include/asm/hardirq.h b/arch/x86/include/asm/hardirq.h index 9314642ae93c..423fa4d068a7 100644 --- a/arch/x86/include/asm/hardirq.h +++ b/arch/x86/include/asm/hardirq.h @@ -4,51 +4,60 @@ #include +enum irq_stat_counts { + IRQ_COUNT_NMI, +#ifdef CONFIG_X86_LOCAL_APIC + IRQ_COUNT_APIC_TIMER, + IRQ_COUNT_SPURIOUS, + IRQ_COUNT_APIC_PERF, + IRQ_COUNT_IRQ_WORK, + IRQ_COUNT_ICR_READ_RETRY, + IRQ_COUNT_X86_PLATFORM_IPI, +#endif +#ifdef CONFIG_SMP + IRQ_COUNT_RESCHEDULE, + IRQ_COUNT_CALL_FUNCTION, +#endif + IRQ_COUNT_TLB, +#ifdef CONFIG_X86_THERMAL_VECTOR + IRQ_COUNT_THERMAL_APIC, +#endif +#ifdef CONFIG_X86_MCE_THRESHOLD + IRQ_COUNT_THRESHOLD_APIC, +#endif +#ifdef CONFIG_X86_MCE_AMD + IRQ_COUNT_DEFERRED_ERROR, +#endif +#ifdef CONFIG_X86_MCE + IRQ_COUNT_MCE_EXCEPTION, + IRQ_COUNT_MCE_POLL, +#endif +#ifdef CONFIG_X86_HV_CALLBACK_VECTOR + IRQ_COUNT_HYPERVISOR_CALLBACK, +#endif +#if IS_ENABLED(CONFIG_HYPERV) + IRQ_COUNT_HYPERV_REENLIGHTENMENT, + IRQ_COUNT_HYPERV_STIMER0, +#endif +#if IS_ENABLED(CONFIG_KVM) + IRQ_COUNT_POSTED_INTR, + IRQ_COUNT_POSTED_INTR_NESTED, + IRQ_COUNT_POSTED_INTR_WAKEUP, +#endif +#ifdef CONFIG_GUEST_PERF_EVENTS + IRQ_COUNT_PERF_GUEST_MEDIATED_PMI, +#endif +#ifdef CONFIG_X86_POSTED_MSI + IRQ_COUNT_POSTED_MSI_NOTIFICATION, +#endif + IRQ_COUNT_MAX, +}; + typedef struct { #if IS_ENABLED(CONFIG_CPU_MITIGATIONS) && IS_ENABLED(CONFIG_KVM_INTEL) u8 kvm_cpu_l1tf_flush_l1d; #endif - unsigned int __nmi_count; /* arch dependent */ -#ifdef CONFIG_X86_LOCAL_APIC - unsigned int apic_timer_irqs; /* arch dependent */ - unsigned int irq_spurious_count; - unsigned int icr_read_retry_count; -#endif -#if IS_ENABLED(CONFIG_KVM) - unsigned int kvm_posted_intr_ipis; - unsigned int kvm_posted_intr_wakeup_ipis; - unsigned int kvm_posted_intr_nested_ipis; -#endif -#ifdef CONFIG_GUEST_PERF_EVENTS - unsigned int perf_guest_mediated_pmis; -#endif - unsigned int x86_platform_ipis; /* arch dependent */ - unsigned int apic_perf_irqs; - unsigned int apic_irq_work_irqs; -#ifdef CONFIG_SMP - unsigned int irq_resched_count; - unsigned int irq_call_count; -#endif - unsigned int irq_tlb_count; -#ifdef CONFIG_X86_THERMAL_VECTOR - unsigned int irq_thermal_count; -#endif -#ifdef CONFIG_X86_MCE_THRESHOLD - unsigned int irq_threshold_count; -#endif -#ifdef CONFIG_X86_MCE_AMD - unsigned int irq_deferred_error_count; -#endif -#ifdef CONFIG_X86_HV_CALLBACK_VECTOR - unsigned int irq_hv_callback_count; -#endif -#if IS_ENABLED(CONFIG_HYPERV) - unsigned int irq_hv_reenlightenment_count; - unsigned int hyperv_stimer0_count; -#endif -#ifdef CONFIG_X86_POSTED_MSI - unsigned int posted_msi_notification_count; -#endif + unsigned int counts[IRQ_COUNT_MAX]; } ____cacheline_aligned irq_cpustat_t; DECLARE_PER_CPU_SHARED_ALIGNED(irq_cpustat_t, irq_stat); @@ -58,15 +67,23 @@ DECLARE_PER_CPU_ALIGNED(struct pi_desc, posted_msi_pi_desc); #endif #define __ARCH_IRQ_STAT -#define inc_irq_stat(member) this_cpu_inc(irq_stat.member) +#define inc_irq_stat(index) this_cpu_inc(irq_stat.counts[IRQ_COUNT_##index]) + +#ifdef CONFIG_X86_LOCAL_APIC +#define inc_perf_irq_stat() inc_irq_stat(APIC_PERF) +#else +#define inc_perf_irq_stat() do { } while (0) +#endif extern void ack_bad_irq(unsigned int irq); +#ifdef CONFIG_PROC_FS extern u64 arch_irq_stat_cpu(unsigned int cpu); #define arch_irq_stat_cpu arch_irq_stat_cpu extern u64 arch_irq_stat(void); #define arch_irq_stat arch_irq_stat +#endif DECLARE_PER_CPU_CACHE_HOT(u16, __softirq_pending); #define local_softirq_pending_ref __softirq_pending diff --git a/arch/x86/include/asm/mce.h b/arch/x86/include/asm/mce.h index 0175d39a5856..e575b702063d 100644 --- a/arch/x86/include/asm/mce.h +++ b/arch/x86/include/asm/mce.h @@ -291,9 +291,6 @@ bool mce_is_memory_error(struct mce *m); bool mce_is_correctable(struct mce *m); bool mce_usable_address(struct mce *m); -DECLARE_PER_CPU(unsigned, mce_exception_count); -DECLARE_PER_CPU(unsigned, mce_poll_count); - typedef DECLARE_BITMAP(mce_banks_t, MAX_NR_BANKS); DECLARE_PER_CPU(mce_banks_t, mce_poll_banks); diff --git a/arch/x86/kernel/apic/apic.c b/arch/x86/kernel/apic/apic.c index 639904911444..6599e8004d94 100644 --- a/arch/x86/kernel/apic/apic.c +++ b/arch/x86/kernel/apic/apic.c @@ -1045,7 +1045,7 @@ static void local_apic_timer_interrupt(void) /* * the NMI deadlock-detector uses this. */ - inc_irq_stat(apic_timer_irqs); + inc_irq_stat(APIC_TIMER); evt->event_handler(evt); } @@ -2114,7 +2114,7 @@ static noinline void handle_spurious_interrupt(u8 vector) trace_spurious_apic_entry(vector); - inc_irq_stat(irq_spurious_count); + inc_irq_stat(SPURIOUS); /* * If this is a spurious interrupt then do not acknowledge diff --git a/arch/x86/kernel/apic/ipi.c b/arch/x86/kernel/apic/ipi.c index 98a57cb4aa86..3635c4d7b7f5 100644 --- a/arch/x86/kernel/apic/ipi.c +++ b/arch/x86/kernel/apic/ipi.c @@ -120,7 +120,7 @@ u32 apic_mem_wait_icr_idle_timeout(void) for (cnt = 0; cnt < 1000; cnt++) { if (!(apic_read(APIC_ICR) & APIC_ICR_BUSY)) return 0; - inc_irq_stat(icr_read_retry_count); + inc_irq_stat(ICR_READ_RETRY); udelay(100); } return APIC_ICR_BUSY; diff --git a/arch/x86/kernel/cpu/acrn.c b/arch/x86/kernel/cpu/acrn.c index 2c5b51aad91a..dc119af83524 100644 --- a/arch/x86/kernel/cpu/acrn.c +++ b/arch/x86/kernel/cpu/acrn.c @@ -52,7 +52,7 @@ DEFINE_IDTENTRY_SYSVEC(sysvec_acrn_hv_callback) * HYPERVISOR_CALLBACK_VECTOR. */ apic_eoi(); - inc_irq_stat(irq_hv_callback_count); + inc_irq_stat(HYPERVISOR_CALLBACK); if (acrn_intr_handler) acrn_intr_handler(); diff --git a/arch/x86/kernel/cpu/mce/amd.c b/arch/x86/kernel/cpu/mce/amd.c index 6605a0224659..222fa9cb181b 100644 --- a/arch/x86/kernel/cpu/mce/amd.c +++ b/arch/x86/kernel/cpu/mce/amd.c @@ -850,7 +850,7 @@ bool amd_mce_usable_address(struct mce *m) DEFINE_IDTENTRY_SYSVEC(sysvec_deferred_error) { trace_deferred_error_apic_entry(DEFERRED_ERROR_VECTOR); - inc_irq_stat(irq_deferred_error_count); + inc_irq_stat(DEFERRED_ERROR); deferred_error_int_vector(); trace_deferred_error_apic_exit(DEFERRED_ERROR_VECTOR); apic_eoi(); diff --git a/arch/x86/kernel/cpu/mce/core.c b/arch/x86/kernel/cpu/mce/core.c index 8dd424ac5de8..77cad8d57e16 100644 --- a/arch/x86/kernel/cpu/mce/core.c +++ b/arch/x86/kernel/cpu/mce/core.c @@ -67,8 +67,6 @@ static DEFINE_MUTEX(mce_sysfs_mutex); #define SPINUNIT 100 /* 100ns */ -DEFINE_PER_CPU(unsigned, mce_exception_count); - DEFINE_PER_CPU_READ_MOSTLY(unsigned int, mce_num_banks); DEFINE_PER_CPU_READ_MOSTLY(struct mce_bank[MAX_NR_BANKS], mce_banks_array); @@ -716,8 +714,6 @@ static noinstr void mce_read_aux(struct mce_hw_err *err, int i) } } -DEFINE_PER_CPU(unsigned, mce_poll_count); - /* * We have three scenarios for checking for Deferred errors: * @@ -820,7 +816,7 @@ void machine_check_poll(enum mcp_flags flags, mce_banks_t *b) struct mce *m; int i; - this_cpu_inc(mce_poll_count); + inc_irq_stat(MCE_POLL); mce_gather_info(&err, NULL); m = &err.m; @@ -1595,7 +1591,7 @@ noinstr void do_machine_check(struct pt_regs *regs) */ lmce = 1; - this_cpu_inc(mce_exception_count); + inc_irq_stat(MCE_EXCEPTION); mce_gather_info(&err, regs); m = &err.m; diff --git a/arch/x86/kernel/cpu/mce/threshold.c b/arch/x86/kernel/cpu/mce/threshold.c index 0d13c9ffcba0..6c370d5af5bd 100644 --- a/arch/x86/kernel/cpu/mce/threshold.c +++ b/arch/x86/kernel/cpu/mce/threshold.c @@ -37,7 +37,7 @@ void (*mce_threshold_vector)(void) = default_threshold_interrupt; DEFINE_IDTENTRY_SYSVEC(sysvec_threshold) { trace_threshold_apic_entry(THRESHOLD_APIC_VECTOR); - inc_irq_stat(irq_threshold_count); + inc_irq_stat(THRESHOLD_APIC); mce_threshold_vector(); trace_threshold_apic_exit(THRESHOLD_APIC_VECTOR); apic_eoi(); diff --git a/arch/x86/kernel/cpu/mshyperv.c b/arch/x86/kernel/cpu/mshyperv.c index b5b6a58b67b0..9381102e884a 100644 --- a/arch/x86/kernel/cpu/mshyperv.c +++ b/arch/x86/kernel/cpu/mshyperv.c @@ -154,7 +154,7 @@ DEFINE_IDTENTRY_SYSVEC(sysvec_hyperv_callback) { struct pt_regs *old_regs = set_irq_regs(regs); - inc_irq_stat(irq_hv_callback_count); + inc_irq_stat(HYPERVISOR_CALLBACK); if (mshv_handler) mshv_handler(); @@ -193,7 +193,7 @@ DEFINE_IDTENTRY_SYSVEC(sysvec_hyperv_stimer0) { struct pt_regs *old_regs = set_irq_regs(regs); - inc_irq_stat(hyperv_stimer0_count); + inc_irq_stat(HYPERV_STIMER0); if (hv_stimer0_handler) hv_stimer0_handler(); add_interrupt_randomness(HYPERV_STIMER0_VECTOR); diff --git a/arch/x86/kernel/irq.c b/arch/x86/kernel/irq.c index 963690b56f9f..decd08d3d315 100644 --- a/arch/x86/kernel/irq.c +++ b/arch/x86/kernel/irq.c @@ -62,156 +62,113 @@ void ack_bad_irq(unsigned int irq) apic_eoi(); } -/* - * A helper routine for putting space and decimal number without overhead - * from rich format of printf(). - */ -static void put_decimal(struct seq_file *p, unsigned long long num) +struct irq_stat_info { + unsigned int skip_vector; + const char *symbol; + const char *text; +}; + +#define ISS(idx, sym, txt) [IRQ_COUNT_##idx] = { .symbol = sym, .text = txt } + +#define ITS(idx, sym, txt) [IRQ_COUNT_##idx] = \ + { .skip_vector = idx## _VECTOR, .symbol = sym, .text = txt } + +static struct irq_stat_info irq_stat_info[IRQ_COUNT_MAX] __ro_after_init = { + ISS(NMI, "NMI", " Non-maskable interrupts\n"), +#ifdef CONFIG_X86_LOCAL_APIC + ISS(APIC_TIMER, "LOC", " Local timer interrupts\n"), + ISS(SPURIOUS, "SPU", " Spurious interrupts\n"), + ISS(APIC_PERF, "PMI", " Performance monitoring interrupts\n"), + ISS(IRQ_WORK, "IWI", " IRQ work interrupts\n"), + ISS(ICR_READ_RETRY, "RTR", " APIC ICR read retries\n"), + ISS(X86_PLATFORM_IPI, "PLT", " Platform interrupts\n"), +#endif +#ifdef CONFIG_SMP + ISS(RESCHEDULE, "RES", " Rescheduling interrupts\n"), + ISS(CALL_FUNCTION, "CAL", " Function call interrupts\n"), +#endif + ISS(TLB, "TLB", " TLB shootdowns\n"), +#ifdef CONFIG_X86_THERMAL_VECTOR + ISS(THERMAL_APIC, "TRM", " Thermal event interrupt\n"), +#endif +#ifdef CONFIG_X86_MCE_THRESHOLD + ISS(THRESHOLD_APIC, "THR", " Threshold APIC interrupts\n"), +#endif +#ifdef CONFIG_X86_MCE_AMD + ISS(DEFERRED_ERROR, "DFR", " Deferred Error APIC interrupts\n"), +#endif +#ifdef CONFIG_X86_MCE + ISS(MCE_EXCEPTION, "MCE", " Machine check exceptions\n"), + ISS(MCE_POLL, "MCP", " Machine check polls\n"), +#endif +#ifdef CONFIG_X86_HV_CALLBACK_VECTOR + ITS(HYPERVISOR_CALLBACK, "HYP", " Hypervisor callback interrupts\n"), +#endif +#if IS_ENABLED(CONFIG_HYPERV) + ITS(HYPERV_REENLIGHTENMENT, "HRE", " Hyper-V reenlightenment interrupts\n"), + ITS(HYPERV_STIMER0, "HVS", " Hyper-V stimer0 interrupts\n"), +#endif +#if IS_ENABLED(CONFIG_KVM) + ITS(POSTED_INTR, "PIN", " Posted-interrupt notification event\n"), + ITS(POSTED_INTR_NESTED, "NPI", " Nested posted-interrupt event\n"), + ITS(POSTED_INTR_WAKEUP, "PIW", " Posted-interrupt wakeup event\n"), +#endif +#ifdef CONFIG_GUEST_PERF_EVENTS + ISS(PERF_GUEST_MEDIATED_PMI, "VPMI", " Perf Guest Mediated PMI\n"), +#endif +#ifdef CONFIG_X86_POSTED_MSI + ISS(POSTED_MSI_NOTIFICATION, "PMN", " Posted MSI notification event\n"), +#endif +}; + +static int __init irq_init_stats(void) { - const char *delimiter = " "; - unsigned int width = 10; + struct irq_stat_info *info = irq_stat_info; - seq_put_decimal_ull_width(p, delimiter, num, width); + for (unsigned int i = 0; i < ARRAY_SIZE(irq_stat_info); i++, info++) { + if (info->skip_vector && test_bit(info->skip_vector, system_vectors)) + info->skip_vector = 0; + } + +#ifdef CONFIG_X86_LOCAL_APIC + if (!x86_platform_ipi_callback) + irq_stat_info[IRQ_COUNT_X86_PLATFORM_IPI].skip_vector = 1; +#endif + +#ifdef CONFIG_X86_POSTED_MSI + if (!posted_msi_enabled()) + irq_stat_info[IRQ_COUNT_POSTED_MSI_NOTIFICATION].skip_vector = 1; +#endif + +#ifdef CONFIG_X86_MCE_AMD + if (boot_cpu_data.x86_vendor != X86_VENDOR_AMD && + boot_cpu_data.x86_vendor != X86_VENDOR_HYGON) + irq_stat_info[IRQ_COUNT_DEFERRED_ERROR].skip_vector = 1; +#endif + return 0; } +late_initcall(irq_init_stats); -#define irq_stats(x) (&per_cpu(irq_stat, x)) +#ifdef CONFIG_PROC_FS /* * /proc/interrupts printing for arch specific interrupts */ int arch_show_interrupts(struct seq_file *p, int prec) { - int j; + const struct irq_stat_info *info = irq_stat_info; - seq_printf(p, "%*s:", prec, "NMI"); - for_each_online_cpu(j) - put_decimal(p, irq_stats(j)->__nmi_count); - seq_puts(p, " Non-maskable interrupts\n"); -#ifdef CONFIG_X86_LOCAL_APIC - seq_printf(p, "%*s:", prec, "LOC"); - for_each_online_cpu(j) - put_decimal(p, irq_stats(j)->apic_timer_irqs); - seq_puts(p, " Local timer interrupts\n"); + for (unsigned int i = 0; i < ARRAY_SIZE(irq_stat_info); i++, info++) { + if (info->skip_vector) + continue; - seq_printf(p, "%*s:", prec, "SPU"); - for_each_online_cpu(j) - put_decimal(p, irq_stats(j)->irq_spurious_count); - seq_puts(p, " Spurious interrupts\n"); - seq_printf(p, "%*s:", prec, "PMI"); - for_each_online_cpu(j) - put_decimal(p, irq_stats(j)->apic_perf_irqs); - seq_puts(p, " Performance monitoring interrupts\n"); - seq_printf(p, "%*s:", prec, "IWI"); - for_each_online_cpu(j) - put_decimal(p, irq_stats(j)->apic_irq_work_irqs); - seq_puts(p, " IRQ work interrupts\n"); - seq_printf(p, "%*s:", prec, "RTR"); - for_each_online_cpu(j) - put_decimal(p, irq_stats(j)->icr_read_retry_count); - seq_puts(p, " APIC ICR read retries\n"); - if (x86_platform_ipi_callback) { - seq_printf(p, "%*s:", prec, "PLT"); - for_each_online_cpu(j) - put_decimal(p, irq_stats(j)->x86_platform_ipis); - seq_puts(p, " Platform interrupts\n"); + seq_printf(p, "%*s:", prec, info->symbol); + irq_proc_emit_counts(p, &irq_stat.counts[i]); + seq_puts(p, info->text); } -#endif -#ifdef CONFIG_SMP - seq_printf(p, "%*s:", prec, "RES"); - for_each_online_cpu(j) - put_decimal(p, irq_stats(j)->irq_resched_count); - seq_puts(p, " Rescheduling interrupts\n"); - seq_printf(p, "%*s:", prec, "CAL"); - for_each_online_cpu(j) - put_decimal(p, irq_stats(j)->irq_call_count); - seq_puts(p, " Function call interrupts\n"); - seq_printf(p, "%*s:", prec, "TLB"); - for_each_online_cpu(j) - put_decimal(p, irq_stats(j)->irq_tlb_count); - seq_puts(p, " TLB shootdowns\n"); -#endif -#ifdef CONFIG_X86_THERMAL_VECTOR - seq_printf(p, "%*s:", prec, "TRM"); - for_each_online_cpu(j) - put_decimal(p, irq_stats(j)->irq_thermal_count); - seq_puts(p, " Thermal event interrupts\n"); -#endif -#ifdef CONFIG_X86_MCE_THRESHOLD - seq_printf(p, "%*s:", prec, "THR"); - for_each_online_cpu(j) - put_decimal(p, irq_stats(j)->irq_threshold_count); - seq_puts(p, " Threshold APIC interrupts\n"); -#endif -#ifdef CONFIG_X86_MCE_AMD - seq_printf(p, "%*s:", prec, "DFR"); - for_each_online_cpu(j) - put_decimal(p, irq_stats(j)->irq_deferred_error_count); - seq_puts(p, " Deferred Error APIC interrupts\n"); -#endif -#ifdef CONFIG_X86_MCE - seq_printf(p, "%*s:", prec, "MCE"); - for_each_online_cpu(j) - put_decimal(p, per_cpu(mce_exception_count, j)); - seq_puts(p, " Machine check exceptions\n"); - seq_printf(p, "%*s:", prec, "MCP"); - for_each_online_cpu(j) - put_decimal(p, per_cpu(mce_poll_count, j)); - seq_puts(p, " Machine check polls\n"); -#endif -#ifdef CONFIG_X86_HV_CALLBACK_VECTOR - if (test_bit(HYPERVISOR_CALLBACK_VECTOR, system_vectors)) { - seq_printf(p, "%*s:", prec, "HYP"); - for_each_online_cpu(j) - put_decimal(p, irq_stats(j)->irq_hv_callback_count); - seq_puts(p, " Hypervisor callback interrupts\n"); - } -#endif -#if IS_ENABLED(CONFIG_HYPERV) - if (test_bit(HYPERV_REENLIGHTENMENT_VECTOR, system_vectors)) { - seq_printf(p, "%*s:", prec, "HRE"); - for_each_online_cpu(j) - put_decimal(p, - irq_stats(j)->irq_hv_reenlightenment_count); - seq_puts(p, " Hyper-V reenlightenment interrupts\n"); - } - if (test_bit(HYPERV_STIMER0_VECTOR, system_vectors)) { - seq_printf(p, "%*s:", prec, "HVS"); - for_each_online_cpu(j) - put_decimal(p, irq_stats(j)->hyperv_stimer0_count); - seq_puts(p, " Hyper-V stimer0 interrupts\n"); - } -#endif + seq_printf(p, "%*s: %10u\n", prec, "ERR", atomic_read(&irq_err_count)); -#if defined(CONFIG_X86_IO_APIC) - seq_printf(p, "%*s: %10u\n", prec, "MIS", atomic_read(&irq_mis_count)); -#endif -#if IS_ENABLED(CONFIG_KVM) - seq_printf(p, "%*s:", prec, "PIN"); - for_each_online_cpu(j) - put_decimal(p, irq_stats(j)->kvm_posted_intr_ipis); - seq_puts(p, " Posted-interrupt notification event\n"); - - seq_printf(p, "%*s:", prec, "NPI"); - for_each_online_cpu(j) - put_decimal(p, irq_stats(j)->kvm_posted_intr_nested_ipis); - seq_puts(p, " Nested posted-interrupt event\n"); - - seq_printf(p, "%*s:", prec, "PIW"); - for_each_online_cpu(j) - put_decimal(p, irq_stats(j)->kvm_posted_intr_wakeup_ipis); - seq_puts(p, " Posted-interrupt wakeup event\n"); -#endif -#ifdef CONFIG_GUEST_PERF_EVENTS - seq_printf(p, "%*s:", prec, "VPMI"); - for_each_online_cpu(j) - put_decimal(p, irq_stats(j)->perf_guest_mediated_pmis); - seq_puts(p, " Perf Guest Mediated PMI\n"); -#endif -#ifdef CONFIG_X86_POSTED_MSI - seq_printf(p, "%*s:", prec, "PMN"); - for_each_online_cpu(j) - put_decimal(p, irq_stats(j)->posted_msi_notification_count); - seq_puts(p, " Posted MSI notification event\n"); -#endif + if (IS_ENABLED(CONFIG_X86_IO_APIC)) + seq_printf(p, "%*s: %10u\n", prec, "MIS", atomic_read(&irq_mis_count)); return 0; } @@ -220,38 +177,11 @@ int arch_show_interrupts(struct seq_file *p, int prec) */ u64 arch_irq_stat_cpu(unsigned int cpu) { - u64 sum = irq_stats(cpu)->__nmi_count; + irq_cpustat_t *p = per_cpu_ptr(&irq_stat, cpu); + u64 sum = 0; -#ifdef CONFIG_X86_LOCAL_APIC - sum += irq_stats(cpu)->apic_timer_irqs; - sum += irq_stats(cpu)->irq_spurious_count; - sum += irq_stats(cpu)->apic_perf_irqs; - sum += irq_stats(cpu)->apic_irq_work_irqs; - sum += irq_stats(cpu)->icr_read_retry_count; - if (x86_platform_ipi_callback) - sum += irq_stats(cpu)->x86_platform_ipis; -#endif -#ifdef CONFIG_SMP - sum += irq_stats(cpu)->irq_resched_count; - sum += irq_stats(cpu)->irq_call_count; -#endif -#ifdef CONFIG_X86_THERMAL_VECTOR - sum += irq_stats(cpu)->irq_thermal_count; -#endif -#ifdef CONFIG_X86_MCE_THRESHOLD - sum += irq_stats(cpu)->irq_threshold_count; -#endif -#ifdef CONFIG_X86_HV_CALLBACK_VECTOR - sum += irq_stats(cpu)->irq_hv_callback_count; -#endif -#if IS_ENABLED(CONFIG_HYPERV) - sum += irq_stats(cpu)->irq_hv_reenlightenment_count; - sum += irq_stats(cpu)->hyperv_stimer0_count; -#endif -#ifdef CONFIG_X86_MCE - sum += per_cpu(mce_exception_count, cpu); - sum += per_cpu(mce_poll_count, cpu); -#endif + for (unsigned int i = 0; i < ARRAY_SIZE(irq_stat_info); i++) + sum += p->counts[i]; return sum; } @@ -260,6 +190,7 @@ u64 arch_irq_stat(void) u64 sum = atomic_read(&irq_err_count); return sum; } +#endif /* CONFIG_PROC_FS */ static __always_inline void handle_irq(struct irq_desc *desc, struct pt_regs *regs) @@ -344,7 +275,7 @@ DEFINE_IDTENTRY_IRQ(common_interrupt) #ifdef CONFIG_X86_LOCAL_APIC /* Function pointer for generic interrupt vector handling */ -void (*x86_platform_ipi_callback)(void) = NULL; +void (*x86_platform_ipi_callback)(void) __ro_after_init = NULL; /* * Handler for X86_PLATFORM_IPI_VECTOR. */ @@ -354,7 +285,7 @@ DEFINE_IDTENTRY_SYSVEC(sysvec_x86_platform_ipi) apic_eoi(); trace_x86_platform_ipi_entry(X86_PLATFORM_IPI_VECTOR); - inc_irq_stat(x86_platform_ipis); + inc_irq_stat(X86_PLATFORM_IPI); if (x86_platform_ipi_callback) x86_platform_ipi_callback(); trace_x86_platform_ipi_exit(X86_PLATFORM_IPI_VECTOR); @@ -369,7 +300,7 @@ DEFINE_IDTENTRY_SYSVEC(sysvec_x86_platform_ipi) DEFINE_IDTENTRY_SYSVEC(sysvec_perf_guest_mediated_pmi_handler) { apic_eoi(); - inc_irq_stat(perf_guest_mediated_pmis); + inc_irq_stat(PERF_GUEST_MEDIATED_PMI); perf_guest_handle_mediated_pmi(); } #endif @@ -395,7 +326,7 @@ EXPORT_SYMBOL_FOR_KVM(kvm_set_posted_intr_wakeup_handler); DEFINE_IDTENTRY_SYSVEC_SIMPLE(sysvec_kvm_posted_intr_ipi) { apic_eoi(); - inc_irq_stat(kvm_posted_intr_ipis); + inc_irq_stat(POSTED_INTR); } /* @@ -404,7 +335,7 @@ DEFINE_IDTENTRY_SYSVEC_SIMPLE(sysvec_kvm_posted_intr_ipi) DEFINE_IDTENTRY_SYSVEC(sysvec_kvm_posted_intr_wakeup_ipi) { apic_eoi(); - inc_irq_stat(kvm_posted_intr_wakeup_ipis); + inc_irq_stat(POSTED_INTR_WAKEUP); kvm_posted_intr_wakeup_handler(); } @@ -414,7 +345,7 @@ DEFINE_IDTENTRY_SYSVEC(sysvec_kvm_posted_intr_wakeup_ipi) DEFINE_IDTENTRY_SYSVEC_SIMPLE(sysvec_kvm_posted_intr_nested_ipi) { apic_eoi(); - inc_irq_stat(kvm_posted_intr_nested_ipis); + inc_irq_stat(POSTED_INTR_NESTED); } #endif @@ -488,7 +419,7 @@ DEFINE_IDTENTRY_SYSVEC(sysvec_posted_msi_notification) /* Mark the handler active for intel_ack_posted_msi_irq() */ __this_cpu_write(posted_msi_handler_active, true); - inc_irq_stat(posted_msi_notification_count); + inc_irq_stat(POSTED_MSI_NOTIFICATION); irq_enter(); /* @@ -583,7 +514,7 @@ static void smp_thermal_vector(void) DEFINE_IDTENTRY_SYSVEC(sysvec_thermal) { trace_thermal_apic_entry(THERMAL_APIC_VECTOR); - inc_irq_stat(irq_thermal_count); + inc_irq_stat(THERMAL_APIC); smp_thermal_vector(); trace_thermal_apic_exit(THERMAL_APIC_VECTOR); apic_eoi(); diff --git a/arch/x86/kernel/irq_work.c b/arch/x86/kernel/irq_work.c index b0a24deab4a1..308c62411ff4 100644 --- a/arch/x86/kernel/irq_work.c +++ b/arch/x86/kernel/irq_work.c @@ -18,7 +18,7 @@ DEFINE_IDTENTRY_SYSVEC(sysvec_irq_work) { apic_eoi(); trace_irq_work_entry(IRQ_WORK_VECTOR); - inc_irq_stat(apic_irq_work_irqs); + inc_irq_stat(IRQ_WORK); irq_work_run(); trace_irq_work_exit(IRQ_WORK_VECTOR); } diff --git a/arch/x86/kernel/kvm.c b/arch/x86/kernel/kvm.c index 29226d112029..d1f3f320168c 100644 --- a/arch/x86/kernel/kvm.c +++ b/arch/x86/kernel/kvm.c @@ -304,7 +304,7 @@ DEFINE_IDTENTRY_SYSVEC(sysvec_kvm_asyncpf_interrupt) apic_eoi(); - inc_irq_stat(irq_hv_callback_count); + inc_irq_stat(HYPERVISOR_CALLBACK); if (__this_cpu_read(async_pf_enabled)) { token = __this_cpu_read(apf_reason.token); diff --git a/arch/x86/kernel/nmi.c b/arch/x86/kernel/nmi.c index 3d239ed12744..a7f56e7638d8 100644 --- a/arch/x86/kernel/nmi.c +++ b/arch/x86/kernel/nmi.c @@ -576,7 +576,7 @@ DEFINE_IDTENTRY_RAW(exc_nmi) irq_state = irqentry_nmi_enter(regs); - inc_irq_stat(__nmi_count); + inc_irq_stat(NMI); if (IS_ENABLED(CONFIG_NMI_CHECK_CPU) && ignore_nmis) { WRITE_ONCE(nsp->idt_ignored, nsp->idt_ignored + 1); @@ -725,7 +725,7 @@ DEFINE_FREDENTRY_NMI(exc_nmi) irq_state = irqentry_nmi_enter(regs); - inc_irq_stat(__nmi_count); + inc_irq_stat(NMI); default_do_nmi(regs); irqentry_nmi_exit(regs, irq_state); diff --git a/arch/x86/kernel/smp.c b/arch/x86/kernel/smp.c index cbf95fe2b207..985103cab16c 100644 --- a/arch/x86/kernel/smp.c +++ b/arch/x86/kernel/smp.c @@ -250,7 +250,7 @@ DEFINE_IDTENTRY_SYSVEC_SIMPLE(sysvec_reschedule_ipi) { apic_eoi(); trace_reschedule_entry(RESCHEDULE_VECTOR); - inc_irq_stat(irq_resched_count); + inc_irq_stat(RESCHEDULE); scheduler_ipi(); trace_reschedule_exit(RESCHEDULE_VECTOR); } @@ -259,7 +259,7 @@ DEFINE_IDTENTRY_SYSVEC(sysvec_call_function) { apic_eoi(); trace_call_function_entry(CALL_FUNCTION_VECTOR); - inc_irq_stat(irq_call_count); + inc_irq_stat(CALL_FUNCTION); generic_smp_call_function_interrupt(); trace_call_function_exit(CALL_FUNCTION_VECTOR); } @@ -268,7 +268,7 @@ DEFINE_IDTENTRY_SYSVEC(sysvec_call_function_single) { apic_eoi(); trace_call_function_single_entry(CALL_FUNCTION_SINGLE_VECTOR); - inc_irq_stat(irq_call_count); + inc_irq_stat(CALL_FUNCTION); generic_smp_call_function_single_interrupt(); trace_call_function_single_exit(CALL_FUNCTION_SINGLE_VECTOR); } diff --git a/arch/x86/mm/tlb.c b/arch/x86/mm/tlb.c index af43d177087e..4c045f844492 100644 --- a/arch/x86/mm/tlb.c +++ b/arch/x86/mm/tlb.c @@ -1123,7 +1123,7 @@ static void flush_tlb_func(void *info) VM_WARN_ON(!irqs_disabled()); if (!local) { - inc_irq_stat(irq_tlb_count); + inc_irq_stat(TLB); count_vm_tlb_event(NR_TLB_REMOTE_FLUSH_RECEIVED); } diff --git a/arch/x86/xen/enlighten_hvm.c b/arch/x86/xen/enlighten_hvm.c index 2f9fa27e5a3c..6c4eac4ff13a 100644 --- a/arch/x86/xen/enlighten_hvm.c +++ b/arch/x86/xen/enlighten_hvm.c @@ -125,7 +125,7 @@ DEFINE_IDTENTRY_SYSVEC(sysvec_xen_hvm_callback) if (xen_percpu_upcall) apic_eoi(); - inc_irq_stat(irq_hv_callback_count); + inc_irq_stat(HYPERVISOR_CALLBACK); xen_evtchn_do_upcall(); diff --git a/arch/x86/xen/enlighten_pv.c b/arch/x86/xen/enlighten_pv.c index ed2d7a3756ce..ea19428d5da0 100644 --- a/arch/x86/xen/enlighten_pv.c +++ b/arch/x86/xen/enlighten_pv.c @@ -728,7 +728,7 @@ static void __xen_pv_evtchn_do_upcall(struct pt_regs *regs) { struct pt_regs *old_regs = set_irq_regs(regs); - inc_irq_stat(irq_hv_callback_count); + inc_irq_stat(HYPERVISOR_CALLBACK); xen_evtchn_do_upcall(); diff --git a/arch/x86/xen/smp.c b/arch/x86/xen/smp.c index 05f92c812ac8..05ee0d3b0874 100644 --- a/arch/x86/xen/smp.c +++ b/arch/x86/xen/smp.c @@ -23,7 +23,7 @@ static irqreturn_t xen_call_function_single_interrupt(int irq, void *dev_id); */ static irqreturn_t xen_reschedule_interrupt(int irq, void *dev_id) { - inc_irq_stat(irq_resched_count); + inc_irq_stat(RESCHEDULE); scheduler_ipi(); return IRQ_HANDLED; @@ -254,7 +254,7 @@ void xen_send_IPI_allbutself(int vector) static irqreturn_t xen_call_function_interrupt(int irq, void *dev_id) { generic_smp_call_function_interrupt(); - inc_irq_stat(irq_call_count); + inc_irq_stat(CALL_FUNCTION); return IRQ_HANDLED; } @@ -262,7 +262,7 @@ static irqreturn_t xen_call_function_interrupt(int irq, void *dev_id) static irqreturn_t xen_call_function_single_interrupt(int irq, void *dev_id) { generic_smp_call_function_single_interrupt(); - inc_irq_stat(irq_call_count); + inc_irq_stat(CALL_FUNCTION); return IRQ_HANDLED; } diff --git a/arch/x86/xen/smp_pv.c b/arch/x86/xen/smp_pv.c index db9b8e222b38..c2812f8177bb 100644 --- a/arch/x86/xen/smp_pv.c +++ b/arch/x86/xen/smp_pv.c @@ -400,7 +400,7 @@ static void xen_pv_stop_other_cpus(int wait) static irqreturn_t xen_irq_work_interrupt(int irq, void *dev_id) { irq_work_run(); - inc_irq_stat(apic_irq_work_irqs); + inc_irq_stat(IRQ_WORK); return IRQ_HANDLED; } From 8713f2e596a134ed5c41e96bb9714251a5e6d56a Mon Sep 17 00:00:00 2001 From: Thomas Gleixner Date: Sun, 17 May 2026 22:01:54 +0200 Subject: [PATCH 06/18] x86/irq: Suppress unlikely interrupt stats by default Unlikely interrupt counters like the spurious vector and the synthetic APIC ICR read retry show up in /proc/interrupts with all counts 0 most of the time. As these are events which should never happen, suppress them by default and enable them for output when they actually happen. This requires a seperate bitmap as the description array is marked __ro_after_init. With that bitmap in place it becomes RO data. Signed-off-by: Thomas Gleixner Tested-by: Michael Kelley Reviewed-by: Radu Rendec Link: https://patch.msgid.link/20260517194931.276486277@kernel.org --- arch/x86/include/asm/hardirq.h | 1 + arch/x86/kernel/apic/apic.c | 2 +- arch/x86/kernel/apic/ipi.c | 2 +- arch/x86/kernel/irq.c | 38 +++++++++++++++++++++++++--------- 4 files changed, 31 insertions(+), 12 deletions(-) diff --git a/arch/x86/include/asm/hardirq.h b/arch/x86/include/asm/hardirq.h index 423fa4d068a7..6723b512c694 100644 --- a/arch/x86/include/asm/hardirq.h +++ b/arch/x86/include/asm/hardirq.h @@ -68,6 +68,7 @@ DECLARE_PER_CPU_ALIGNED(struct pi_desc, posted_msi_pi_desc); #define __ARCH_IRQ_STAT #define inc_irq_stat(index) this_cpu_inc(irq_stat.counts[IRQ_COUNT_##index]) +void irq_stat_inc_and_enable(enum irq_stat_counts which); #ifdef CONFIG_X86_LOCAL_APIC #define inc_perf_irq_stat() inc_irq_stat(APIC_PERF) diff --git a/arch/x86/kernel/apic/apic.c b/arch/x86/kernel/apic/apic.c index 6599e8004d94..e27c263d6c13 100644 --- a/arch/x86/kernel/apic/apic.c +++ b/arch/x86/kernel/apic/apic.c @@ -2114,7 +2114,7 @@ static noinline void handle_spurious_interrupt(u8 vector) trace_spurious_apic_entry(vector); - inc_irq_stat(SPURIOUS); + irq_stat_inc_and_enable(IRQ_COUNT_SPURIOUS); /* * If this is a spurious interrupt then do not acknowledge diff --git a/arch/x86/kernel/apic/ipi.c b/arch/x86/kernel/apic/ipi.c index 3635c4d7b7f5..c627bee3b14f 100644 --- a/arch/x86/kernel/apic/ipi.c +++ b/arch/x86/kernel/apic/ipi.c @@ -120,7 +120,7 @@ u32 apic_mem_wait_icr_idle_timeout(void) for (cnt = 0; cnt < 1000; cnt++) { if (!(apic_read(APIC_ICR) & APIC_ICR_BUSY)) return 0; - inc_irq_stat(ICR_READ_RETRY); + irq_stat_inc_and_enable(IRQ_COUNT_ICR_READ_RETRY); udelay(100); } return APIC_ICR_BUSY; diff --git a/arch/x86/kernel/irq.c b/arch/x86/kernel/irq.c index decd08d3d315..2d8ca4116610 100644 --- a/arch/x86/kernel/irq.c +++ b/arch/x86/kernel/irq.c @@ -68,19 +68,24 @@ struct irq_stat_info { const char *text; }; +#define DEFAULT_SUPPRESSED_VECTOR UINT_MAX + #define ISS(idx, sym, txt) [IRQ_COUNT_##idx] = { .symbol = sym, .text = txt } #define ITS(idx, sym, txt) [IRQ_COUNT_##idx] = \ { .skip_vector = idx## _VECTOR, .symbol = sym, .text = txt } -static struct irq_stat_info irq_stat_info[IRQ_COUNT_MAX] __ro_after_init = { +#define IDS(idx, sym, txt) [IRQ_COUNT_##idx] = \ + { .skip_vector = DEFAULT_SUPPRESSED_VECTOR, .symbol = sym, .text = txt } + +static const struct irq_stat_info irq_stat_info[IRQ_COUNT_MAX] = { ISS(NMI, "NMI", " Non-maskable interrupts\n"), #ifdef CONFIG_X86_LOCAL_APIC ISS(APIC_TIMER, "LOC", " Local timer interrupts\n"), - ISS(SPURIOUS, "SPU", " Spurious interrupts\n"), + IDS(SPURIOUS, "SPU", " Spurious interrupts\n"), ISS(APIC_PERF, "PMI", " Performance monitoring interrupts\n"), ISS(IRQ_WORK, "IWI", " IRQ work interrupts\n"), - ISS(ICR_READ_RETRY, "RTR", " APIC ICR read retries\n"), + IDS(ICR_READ_RETRY, "RTR", " APIC ICR read retries\n"), ISS(X86_PLATFORM_IPI, "PLT", " Platform interrupts\n"), #endif #ifdef CONFIG_SMP @@ -121,34 +126,47 @@ static struct irq_stat_info irq_stat_info[IRQ_COUNT_MAX] __ro_after_init = { #endif }; +static DECLARE_BITMAP(irq_stat_count_show, IRQ_COUNT_MAX) __read_mostly; + static int __init irq_init_stats(void) { - struct irq_stat_info *info = irq_stat_info; + const struct irq_stat_info *info = irq_stat_info; for (unsigned int i = 0; i < ARRAY_SIZE(irq_stat_info); i++, info++) { - if (info->skip_vector && test_bit(info->skip_vector, system_vectors)) - info->skip_vector = 0; + if (!info->skip_vector || (info->skip_vector != DEFAULT_SUPPRESSED_VECTOR && + test_bit(info->skip_vector, system_vectors))) + set_bit(i, irq_stat_count_show); } #ifdef CONFIG_X86_LOCAL_APIC if (!x86_platform_ipi_callback) - irq_stat_info[IRQ_COUNT_X86_PLATFORM_IPI].skip_vector = 1; + clear_bit(IRQ_COUNT_X86_PLATFORM_IPI, irq_stat_count_show); #endif #ifdef CONFIG_X86_POSTED_MSI if (!posted_msi_enabled()) - irq_stat_info[IRQ_COUNT_POSTED_MSI_NOTIFICATION].skip_vector = 1; + clear_bit(IRQ_COUNT_POSTED_MSI_NOTIFICATION, irq_stat_count_show); #endif #ifdef CONFIG_X86_MCE_AMD if (boot_cpu_data.x86_vendor != X86_VENDOR_AMD && boot_cpu_data.x86_vendor != X86_VENDOR_HYGON) - irq_stat_info[IRQ_COUNT_DEFERRED_ERROR].skip_vector = 1; + clear_bit(IRQ_COUNT_DEFERRED_ERROR, irq_stat_count_show); #endif return 0; } late_initcall(irq_init_stats); +/* + * Used for default disabled counters to increment the stats and to enable the + * entry for /proc/interrupts output. + */ +void irq_stat_inc_and_enable(enum irq_stat_counts which) +{ + this_cpu_inc(irq_stat.counts[which]); + set_bit(which, irq_stat_count_show); +} + #ifdef CONFIG_PROC_FS /* * /proc/interrupts printing for arch specific interrupts @@ -158,7 +176,7 @@ int arch_show_interrupts(struct seq_file *p, int prec) const struct irq_stat_info *info = irq_stat_info; for (unsigned int i = 0; i < ARRAY_SIZE(irq_stat_info); i++, info++) { - if (info->skip_vector) + if (!test_bit(i, irq_stat_count_show)) continue; seq_printf(p, "%*s:", prec, info->symbol); From d6b70b16b4e7035d230ef97ac6927f40e6aefcce Mon Sep 17 00:00:00 2001 From: Thomas Gleixner Date: Sun, 17 May 2026 22:01:59 +0200 Subject: [PATCH 07/18] x86/irq: Move IOAPIC misrouted and PIC/APIC error counts into irq_stats The special treatment of these counts is just adding extra code for no real value. The irq_stats mechanism allows to suppress output of counters, which should never happen by default and provides a mechanism to enable them for the rare case that they occur. Move the IOAPIC misrouted and the PIC/APIC error counts into irq_stats, mark them suppressed by default and update the sites which increment them. This changes the output format of 'ERR' and 'MIS' in case there are events to the regular per CPU display format and otherwise suppresses them completely. As a side effect this removes the arch_cpu_stat() mechanism from proc/stat which was only there to account for the error interrupts on x86 and missed to take the misrouted ones into account. Signed-off-by: Thomas Gleixner Tested-by: Michael Kelley Reviewed-by: Radu Rendec Link: https://patch.msgid.link/20260517194931.361942103@kernel.org --- arch/x86/include/asm/hardirq.h | 7 ++++--- arch/x86/include/asm/hw_irq.h | 4 ---- arch/x86/kernel/apic/apic.c | 2 +- arch/x86/kernel/apic/io_apic.c | 4 +--- arch/x86/kernel/i8259.c | 2 +- arch/x86/kernel/irq.c | 16 ++++------------ fs/proc/stat.c | 4 ---- 7 files changed, 11 insertions(+), 28 deletions(-) diff --git a/arch/x86/include/asm/hardirq.h b/arch/x86/include/asm/hardirq.h index 6723b512c694..dea60d66d976 100644 --- a/arch/x86/include/asm/hardirq.h +++ b/arch/x86/include/asm/hardirq.h @@ -49,6 +49,10 @@ enum irq_stat_counts { #endif #ifdef CONFIG_X86_POSTED_MSI IRQ_COUNT_POSTED_MSI_NOTIFICATION, +#endif + IRQ_COUNT_PIC_APIC_ERROR, +#ifdef CONFIG_X86_IO_APIC + IRQ_COUNT_IOAPIC_MISROUTED, #endif IRQ_COUNT_MAX, }; @@ -81,9 +85,6 @@ extern void ack_bad_irq(unsigned int irq); #ifdef CONFIG_PROC_FS extern u64 arch_irq_stat_cpu(unsigned int cpu); #define arch_irq_stat_cpu arch_irq_stat_cpu - -extern u64 arch_irq_stat(void); -#define arch_irq_stat arch_irq_stat #endif DECLARE_PER_CPU_CACHE_HOT(u16, __softirq_pending); diff --git a/arch/x86/include/asm/hw_irq.h b/arch/x86/include/asm/hw_irq.h index cbe19e669080..47727d0b540b 100644 --- a/arch/x86/include/asm/hw_irq.h +++ b/arch/x86/include/asm/hw_irq.h @@ -110,10 +110,6 @@ static inline void lock_vector_lock(void) {} static inline void unlock_vector_lock(void) {} #endif -/* Statistics */ -extern atomic_t irq_err_count; -extern atomic_t irq_mis_count; - extern void elcr_set_level_irq(unsigned int irq); extern char irq_entries_start[]; diff --git a/arch/x86/kernel/apic/apic.c b/arch/x86/kernel/apic/apic.c index e27c263d6c13..3eeebc2c5a1d 100644 --- a/arch/x86/kernel/apic/apic.c +++ b/arch/x86/kernel/apic/apic.c @@ -2186,7 +2186,7 @@ DEFINE_IDTENTRY_SYSVEC(sysvec_error_interrupt) apic_write(APIC_ESR, 0); v = apic_read(APIC_ESR); apic_eoi(); - atomic_inc(&irq_err_count); + irq_stat_inc_and_enable(IRQ_COUNT_PIC_APIC_ERROR); apic_pr_debug("APIC error on CPU%d: %02x", smp_processor_id(), v); diff --git a/arch/x86/kernel/apic/io_apic.c b/arch/x86/kernel/apic/io_apic.c index 352ed5558cbc..7d7175d01228 100644 --- a/arch/x86/kernel/apic/io_apic.c +++ b/arch/x86/kernel/apic/io_apic.c @@ -1575,8 +1575,6 @@ static unsigned int startup_ioapic_irq(struct irq_data *data) return was_pending; } -atomic_t irq_mis_count; - #ifdef CONFIG_GENERIC_PENDING_IRQ static bool io_apic_level_ack_pending(struct mp_chip_data *data) { @@ -1713,7 +1711,7 @@ static void ioapic_ack_level(struct irq_data *irq_data) * at the cpu. */ if (!(v & (1 << (i & 0x1f)))) { - atomic_inc(&irq_mis_count); + irq_stat_inc_and_enable(IRQ_COUNT_IOAPIC_MISROUTED); eoi_ioapic_pin(cfg->vector, irq_data->chip_data); } diff --git a/arch/x86/kernel/i8259.c b/arch/x86/kernel/i8259.c index f67063df6723..f7a86b94a0dd 100644 --- a/arch/x86/kernel/i8259.c +++ b/arch/x86/kernel/i8259.c @@ -214,7 +214,7 @@ static void mask_and_ack_8259A(struct irq_data *data) "spurious 8259A interrupt: IRQ%d.\n", irq); spurious_irq_mask |= irqmask; } - atomic_inc(&irq_err_count); + irq_stat_inc_and_enable(IRQ_COUNT_PIC_APIC_ERROR); /* * Theoretically we do not have to handle this IRQ, * but in Linux this does not cause problems and is diff --git a/arch/x86/kernel/irq.c b/arch/x86/kernel/irq.c index 2d8ca4116610..9d98c81fcd24 100644 --- a/arch/x86/kernel/irq.c +++ b/arch/x86/kernel/irq.c @@ -39,8 +39,6 @@ EXPORT_PER_CPU_SYMBOL(__softirq_pending); DEFINE_PER_CPU_CACHE_HOT(struct irq_stack *, hardirq_stack_ptr); -atomic_t irq_err_count; - /* * 'what should we do if we get a hw irq event on an illegal vector'. * each architecture has to answer this themselves. @@ -123,6 +121,10 @@ static const struct irq_stat_info irq_stat_info[IRQ_COUNT_MAX] = { #endif #ifdef CONFIG_X86_POSTED_MSI ISS(POSTED_MSI_NOTIFICATION, "PMN", " Posted MSI notification event\n"), +#endif + IDS(PIC_APIC_ERROR, "ERR", " PIC/APIC error interrupts\n"), +#ifdef CONFIG_X86_IO_APIC + IDS(IOAPIC_MISROUTED, "MIS", " Misrouted IO/APIC interrupts\n"), #endif }; @@ -183,10 +185,6 @@ int arch_show_interrupts(struct seq_file *p, int prec) irq_proc_emit_counts(p, &irq_stat.counts[i]); seq_puts(p, info->text); } - - seq_printf(p, "%*s: %10u\n", prec, "ERR", atomic_read(&irq_err_count)); - if (IS_ENABLED(CONFIG_X86_IO_APIC)) - seq_printf(p, "%*s: %10u\n", prec, "MIS", atomic_read(&irq_mis_count)); return 0; } @@ -202,12 +200,6 @@ u64 arch_irq_stat_cpu(unsigned int cpu) sum += p->counts[i]; return sum; } - -u64 arch_irq_stat(void) -{ - u64 sum = atomic_read(&irq_err_count); - return sum; -} #endif /* CONFIG_PROC_FS */ static __always_inline void handle_irq(struct irq_desc *desc, diff --git a/fs/proc/stat.c b/fs/proc/stat.c index 8b444e862319..20c3df9a9b80 100644 --- a/fs/proc/stat.c +++ b/fs/proc/stat.c @@ -18,9 +18,6 @@ #ifndef arch_irq_stat_cpu #define arch_irq_stat_cpu(cpu) 0 #endif -#ifndef arch_irq_stat -#define arch_irq_stat() 0 -#endif u64 get_idle_time(struct kernel_cpustat *kcs, int cpu) { @@ -122,7 +119,6 @@ static int show_stat(struct seq_file *p, void *v) sum_softirq += softirq_stat; } } - sum += arch_irq_stat(); seq_put_decimal_ull(p, "cpu ", nsec_to_clock_t(user)); seq_put_decimal_ull(p, " ", nsec_to_clock_t(nice)); From cca5e6fa791bdf84f716ae92604d8330a6b6411a Mon Sep 17 00:00:00 2001 From: Thomas Gleixner Date: Sun, 17 May 2026 22:02:04 +0200 Subject: [PATCH 08/18] scripts/gdb: Update x86 interrupts to the array based storage x86 changed the interrupt statistics from a struct with individual members to an counter array. It also provides a corresponding info array with the strings for prefix and description and an indicator to skip the entry. Update the already out of sync GDB script to use the counter and the info array, which keeps the GDB script in sync automatically. Signed-off-by: Thomas Gleixner Tested-by: Florian Fainelli Reviewed-by: Florian Fainelli Link: https://patch.msgid.link/20260517194931.442613033@kernel.org --- scripts/gdb/linux/interrupts.py | 71 +++++++++------------------------ 1 file changed, 18 insertions(+), 53 deletions(-) diff --git a/scripts/gdb/linux/interrupts.py b/scripts/gdb/linux/interrupts.py index f4f715a8f0e3..b794e013c360 100644 --- a/scripts/gdb/linux/interrupts.py +++ b/scripts/gdb/linux/interrupts.py @@ -48,7 +48,7 @@ def show_irq_desc(prec, irq): count = cpus.per_cpu(desc['kstat_irqs'], cpu)['cnt'] else: count = 0 - text += "%10u" % (count) + text += "%10u " % (count) name = "None" if desc['irq_data']['chip']: @@ -58,7 +58,7 @@ def show_irq_desc(prec, irq): else: name = "-" - text += " %8s" % (name) + text += " %-8s" % (name) if desc['irq_data']['domain']: text += " %*lu" % (prec, desc['irq_data']['hwirq']) @@ -97,64 +97,29 @@ def show_irq_err_count(prec): text += "%*s: %10u\n" % (prec, "ERR", cnt['counter']) return text -def x86_show_irqstat(prec, pfx, field, desc): - irq_stat = gdb.parse_and_eval("&irq_stat") +def x86_show_irqstat(prec, pfx, idx, desc): + irq_stat = gdb.parse_and_eval("&irq_stat.counts[%d]" %idx) text = "%*s: " % (prec, pfx) for cpu in cpus.each_online_cpu(): stat = cpus.per_cpu(irq_stat, cpu) - text += "%10u " % (stat[field]) - text += " %s\n" % (desc) - return text - -def x86_show_mce(prec, var, pfx, desc): - pvar = gdb.parse_and_eval(var) - text = "%*s: " % (prec, pfx) - for cpu in cpus.each_online_cpu(): - text += "%10u " % (cpus.per_cpu(pvar, cpu).dereference()) - text += " %s\n" % (desc) + text += "%10u " % (stat.dereference()) + text += desc return text def x86_show_interupts(prec): - text = x86_show_irqstat(prec, "NMI", '__nmi_count', 'Non-maskable interrupts') + info_type = gdb.lookup_type('struct irq_stat_info') + info = gdb.parse_and_eval('irq_stat_info') + bitmap = gdb.parse_and_eval('irq_stat_count_show') + bitsperlong = 8 * int(bitmap.type.target().sizeof) - if constants.LX_CONFIG_X86_LOCAL_APIC: - text += x86_show_irqstat(prec, "LOC", 'apic_timer_irqs', "Local timer interrupts") - text += x86_show_irqstat(prec, "SPU", 'irq_spurious_count', "Spurious interrupts") - text += x86_show_irqstat(prec, "PMI", 'apic_perf_irqs', "Performance monitoring interrupts") - text += x86_show_irqstat(prec, "IWI", 'apic_irq_work_irqs', "IRQ work interrupts") - text += x86_show_irqstat(prec, "RTR", 'icr_read_retry_count', "APIC ICR read retries") - if utils.gdb_eval_or_none("x86_platform_ipi_callback") is not None: - text += x86_show_irqstat(prec, "PLT", 'x86_platform_ipis', "Platform interrupts") - - if constants.LX_CONFIG_SMP: - text += x86_show_irqstat(prec, "RES", 'irq_resched_count', "Rescheduling interrupts") - text += x86_show_irqstat(prec, "CAL", 'irq_call_count', "Function call interrupts") - text += x86_show_irqstat(prec, "TLB", 'irq_tlb_count', "TLB shootdowns") - - if constants.LX_CONFIG_X86_THERMAL_VECTOR: - text += x86_show_irqstat(prec, "TRM", 'irq_thermal_count', "Thermal events interrupts") - - if constants.LX_CONFIG_X86_MCE_THRESHOLD: - text += x86_show_irqstat(prec, "THR", 'irq_threshold_count', "Threshold APIC interrupts") - - if constants.LX_CONFIG_X86_MCE_AMD: - text += x86_show_irqstat(prec, "DFR", 'irq_deferred_error_count', "Deferred Error APIC interrupts") - - if constants.LX_CONFIG_X86_MCE: - text += x86_show_mce(prec, "&mce_exception_count", "MCE", "Machine check exceptions") - text += x86_show_mce(prec, "&mce_poll_count", "MCP", "Machine check polls") - - text += show_irq_err_count(prec) - - if constants.LX_CONFIG_X86_IO_APIC: - cnt = utils.gdb_eval_or_none("irq_mis_count") - if cnt is not None: - text += "%*s: %10u\n" % (prec, "MIS", cnt['counter']) - - if constants.LX_CONFIG_KVM: - text += x86_show_irqstat(prec, "PIN", 'kvm_posted_intr_ipis', 'Posted-interrupt notification event') - text += x86_show_irqstat(prec, "NPI", 'kvm_posted_intr_nested_ipis', 'Nested posted-interrupt event') - text += x86_show_irqstat(prec, "PIW", 'kvm_posted_intr_wakeup_ipis', 'Posted-interrupt wakeup event') + text = "" + for idx in range(int(info.type.sizeof / info_type.sizeof)): + show = bitmap[int(idx / bitsperlong)] + if not show & 1 << int(idx % bitsperlong): + continue + pfx = info[idx]['symbol'].string() + desc = info[idx]['text'].string() + text += x86_show_irqstat(prec, pfx, idx, desc) return text From b99dc723b12ea587fc8b2e07bd8401433eec58d8 Mon Sep 17 00:00:00 2001 From: Thomas Gleixner Date: Sun, 17 May 2026 22:02:09 +0200 Subject: [PATCH 09/18] genirq: Expose nr_irqs in core code ... to avoid function calls in the core code to retrieve the maximum number of interrupts. Rename it to 'total_nr_irqs' as 'nr_irqs' is too generic and fix up the 'nr_irqs' reference in the related GDB script as well. Signed-off-by: Thomas Gleixner Tested-by: Michael Kelley Reviewed-by: Dmitry Ilvokhin Reviewed-by: Radu Rendec Reviewed-by: Shrikanth Hegde Link: https://patch.msgid.link/20260517194931.522168332@kernel.org --- kernel/irq/internals.h | 1 + kernel/irq/irqdesc.c | 28 ++++++++++++++-------------- kernel/irq/proc.c | 4 ++-- scripts/gdb/linux/interrupts.py | 2 +- 4 files changed, 18 insertions(+), 17 deletions(-) diff --git a/kernel/irq/internals.h b/kernel/irq/internals.h index 9412e57056f5..db359acdf27f 100644 --- a/kernel/irq/internals.h +++ b/kernel/irq/internals.h @@ -21,6 +21,7 @@ extern bool noirqdebug; extern int irq_poll_cpu; +extern unsigned int total_nr_irqs; extern struct irqaction chained_action; diff --git a/kernel/irq/irqdesc.c b/kernel/irq/irqdesc.c index 7173b8b634f2..a7ce1e8165a6 100644 --- a/kernel/irq/irqdesc.c +++ b/kernel/irq/irqdesc.c @@ -140,14 +140,14 @@ static void desc_set_defaults(unsigned int irq, struct irq_desc *desc, int node, desc_smp_init(desc, node, affinity); } -static unsigned int nr_irqs = NR_IRQS; +unsigned int total_nr_irqs __read_mostly = NR_IRQS; /** * irq_get_nr_irqs() - Number of interrupts supported by the system. */ unsigned int irq_get_nr_irqs(void) { - return nr_irqs; + return total_nr_irqs; } EXPORT_SYMBOL_GPL(irq_get_nr_irqs); @@ -159,7 +159,7 @@ EXPORT_SYMBOL_GPL(irq_get_nr_irqs); */ unsigned int irq_set_nr_irqs(unsigned int nr) { - nr_irqs = nr; + total_nr_irqs = nr; return nr; } @@ -187,9 +187,9 @@ static unsigned int irq_find_at_or_after(unsigned int offset) struct irq_desc *desc; guard(rcu)(); - desc = mt_find(&sparse_irqs, &index, nr_irqs); + desc = mt_find(&sparse_irqs, &index, total_nr_irqs); - return desc ? irq_desc_get_irq(desc) : nr_irqs; + return desc ? irq_desc_get_irq(desc) : total_nr_irqs; } static void irq_insert_desc(unsigned int irq, struct irq_desc *desc) @@ -543,7 +543,7 @@ static bool irq_expand_nr_irqs(unsigned int nr) { if (nr > MAX_SPARSE_IRQS) return false; - nr_irqs = nr; + total_nr_irqs = nr; return true; } @@ -557,16 +557,16 @@ int __init early_irq_init(void) /* Let arch update nr_irqs and return the nr of preallocated irqs */ initcnt = arch_probe_nr_irqs(); printk(KERN_INFO "NR_IRQS: %d, nr_irqs: %d, preallocated irqs: %d\n", - NR_IRQS, nr_irqs, initcnt); + NR_IRQS, total_nr_irqs, initcnt); - if (WARN_ON(nr_irqs > MAX_SPARSE_IRQS)) - nr_irqs = MAX_SPARSE_IRQS; + if (WARN_ON(total_nr_irqs > MAX_SPARSE_IRQS)) + total_nr_irqs = MAX_SPARSE_IRQS; if (WARN_ON(initcnt > MAX_SPARSE_IRQS)) initcnt = MAX_SPARSE_IRQS; - if (initcnt > nr_irqs) - nr_irqs = initcnt; + if (initcnt > total_nr_irqs) + total_nr_irqs = initcnt; for (i = 0; i < initcnt; i++) { desc = alloc_desc(i, node, 0, NULL, NULL); @@ -862,7 +862,7 @@ void irq_free_descs(unsigned int from, unsigned int cnt) { int i; - if (from >= nr_irqs || (from + cnt) > nr_irqs) + if (from >= total_nr_irqs || (from + cnt) > total_nr_irqs) return; guard(mutex)(&sparse_irq_lock); @@ -911,7 +911,7 @@ int __ref __irq_alloc_descs(int irq, unsigned int from, unsigned int cnt, int no if (irq >=0 && start != irq) return -EEXIST; - if (start + cnt > nr_irqs) { + if (start + cnt > total_nr_irqs) { if (!irq_expand_nr_irqs(start + cnt)) return -ENOMEM; } @@ -923,7 +923,7 @@ EXPORT_SYMBOL_GPL(__irq_alloc_descs); * irq_get_next_irq - get next allocated irq number * @offset: where to start the search * - * Returns next irq number after offset or nr_irqs if none is found. + * Returns next irq number after offset or total_nr_irqs if none is found. */ unsigned int irq_get_next_irq(unsigned int offset) { diff --git a/kernel/irq/proc.c b/kernel/irq/proc.c index 5a1805b0b8a9..d5b812680f5c 100644 --- a/kernel/irq/proc.c +++ b/kernel/irq/proc.c @@ -448,7 +448,7 @@ int __weak arch_show_interrupts(struct seq_file *p, int prec) } #ifndef ACTUAL_NR_IRQS -# define ACTUAL_NR_IRQS irq_get_nr_irqs() +# define ACTUAL_NR_IRQS total_nr_irqs #endif /* Same as seq_put_decimal_ull_width(p, " ", cnt, 10) */ @@ -490,7 +490,7 @@ void irq_proc_emit_counts(struct seq_file *p, unsigned int __percpu *cnts) int show_interrupts(struct seq_file *p, void *v) { - const unsigned int nr_irqs = irq_get_nr_irqs(); + const unsigned int nr_irqs = total_nr_irqs; static int prec; int i = *(loff_t *) v, j; diff --git a/scripts/gdb/linux/interrupts.py b/scripts/gdb/linux/interrupts.py index b794e013c360..a4e25d2bf123 100644 --- a/scripts/gdb/linux/interrupts.py +++ b/scripts/gdb/linux/interrupts.py @@ -174,7 +174,7 @@ class LxInterruptList(gdb.Command): super(LxInterruptList, self).__init__("lx-interruptlist", gdb.COMMAND_DATA) def invoke(self, arg, from_tty): - nr_irqs = gdb.parse_and_eval("nr_irqs") + nr_irqs = gdb.parse_and_eval("total_nr_irqs") prec = 3 j = 1000 while prec < 10 and j <= nr_irqs: From 3ba92f6a28203e30d0b2c7d75b59f48d5ff9fbcc Mon Sep 17 00:00:00 2001 From: Thomas Gleixner Date: Sun, 17 May 2026 22:02:14 +0200 Subject: [PATCH 10/18] genirq/manage: Make NMI cleanup RT safe Eventually blocking functions cannot be invoked with interrupts disabled and a raw spin lock held. Restructure the code so this happens outside of the descriptor lock held region. Signed-off-by: Thomas Gleixner Tested-by: Michael Kelley Link: https://patch.msgid.link/20260517194931.601972758@kernel.org --- kernel/irq/manage.c | 37 +++++++++++++++++++++---------------- 1 file changed, 21 insertions(+), 16 deletions(-) diff --git a/kernel/irq/manage.c b/kernel/irq/manage.c index 2e8072437826..8863b2d4b8ca 100644 --- a/kernel/irq/manage.c +++ b/kernel/irq/manage.c @@ -2026,24 +2026,30 @@ const void *free_irq(unsigned int irq, void *dev_id) } EXPORT_SYMBOL(free_irq); -/* This function must be called with desc->lock held */ static const void *__cleanup_nmi(unsigned int irq, struct irq_desc *desc) { + struct irqaction *action = NULL; const char *devname = NULL; - desc->istate &= ~IRQS_NMI; + scoped_guard(raw_spinlock_irqsave, &desc->lock) { + irq_nmi_teardown(desc); - if (!WARN_ON(desc->action == NULL)) { - irq_pm_remove_action(desc, desc->action); - devname = desc->action->name; - unregister_handler_proc(irq, desc->action); + desc->istate &= ~IRQS_NMI; - kfree(desc->action); + if (!WARN_ON(desc->action == NULL)) { + action = desc->action; + irq_pm_remove_action(desc, action); + devname = action->name; + } desc->action = NULL; + + irq_settings_clr_disable_unlazy(desc); + irq_shutdown_and_deactivate(desc); } - irq_settings_clr_disable_unlazy(desc); - irq_shutdown_and_deactivate(desc); + if (action) + unregister_handler_proc(irq, action); + kfree(action); irq_release_resources(desc); @@ -2067,8 +2073,6 @@ const void *free_nmi(unsigned int irq, void *dev_id) if (WARN_ON(desc->depth == 0)) disable_nmi_nosync(irq); - guard(raw_spinlock_irqsave)(&desc->lock); - irq_nmi_teardown(desc); return __cleanup_nmi(irq, desc); } @@ -2318,13 +2322,14 @@ int request_nmi(unsigned int irq, irq_handler_t handler, /* Setup NMI state */ desc->istate |= IRQS_NMI; retval = irq_nmi_setup(desc); - if (retval) { - __cleanup_nmi(irq, desc); - return -EINVAL; - } - return 0; } + if (retval) { + __cleanup_nmi(irq, desc); + return -EINVAL; + } + return 0; + err_irq_setup: irq_chip_pm_put(&desc->irq_data); err_out: From 4892e5e71ec9c942293cea7fd26e0c76179211ed Mon Sep 17 00:00:00 2001 From: Thomas Gleixner Date: Sun, 17 May 2026 22:02:19 +0200 Subject: [PATCH 11/18] genirq: Cache the condition for /proc/interrupts exposure show_interrupts() evaluates a boatload of conditions to establish whether it should expose an interrupt in /proc/interrupts or not. That can be simplified by caching the condition in an internal status flag, which is updated when one of the relevant inputs changes. The irq_desc::kstat_irq check is dropped because visible interrupt descriptors always have a valid pointer. As a result the number of instructions and branches for reading /proc/interrupts is reduced significantly. Signed-off-by: Thomas Gleixner Tested-by: Michael Kelley Reviewed-by: Dmitry Ilvokhin Reviewed-by: Radu Rendec Link: https://patch.msgid.link/20260517194931.680943749@kernel.org --- include/linux/irq.h | 1 + kernel/irq/chip.c | 2 ++ kernel/irq/internals.h | 2 ++ kernel/irq/manage.c | 8 +++++++- kernel/irq/proc.c | 15 +++++++++++---- kernel/irq/settings.h | 13 +++++++++++++ 6 files changed, 36 insertions(+), 5 deletions(-) diff --git a/include/linux/irq.h b/include/linux/irq.h index efa514ee562f..f485369b1b4f 100644 --- a/include/linux/irq.h +++ b/include/linux/irq.h @@ -103,6 +103,7 @@ enum { IRQ_DISABLE_UNLAZY = (1 << 19), IRQ_HIDDEN = (1 << 20), IRQ_NO_DEBUG = (1 << 21), + IRQ_RESERVED = (1 << 22), }; #define IRQF_MODIFY_MASK \ diff --git a/kernel/irq/chip.c b/kernel/irq/chip.c index 6c9b1dc4e7d4..2d283d1390dc 100644 --- a/kernel/irq/chip.c +++ b/kernel/irq/chip.c @@ -1007,6 +1007,7 @@ __irq_do_set_handler(struct irq_desc *desc, irq_flow_handler_t handle, WARN_ON(irq_chip_pm_get(irq_desc_get_irq_data(desc))); irq_activate_and_startup(desc, IRQ_RESEND); } + irq_proc_update_valid(desc); } void __irq_set_handler(unsigned int irq, irq_flow_handler_t handle, int is_chained, @@ -1067,6 +1068,7 @@ void irq_modify_status(unsigned int irq, unsigned long clr, unsigned long set) trigger = tmp; irqd_set(&desc->irq_data, trigger); + irq_proc_update_valid(desc); } } EXPORT_SYMBOL_GPL(irq_modify_status); diff --git a/kernel/irq/internals.h b/kernel/irq/internals.h index db359acdf27f..e5f3d310a825 100644 --- a/kernel/irq/internals.h +++ b/kernel/irq/internals.h @@ -123,6 +123,7 @@ extern void register_irq_proc(unsigned int irq, struct irq_desc *desc); extern void unregister_irq_proc(unsigned int irq, struct irq_desc *desc); extern void register_handler_proc(unsigned int irq, struct irqaction *action); extern void unregister_handler_proc(unsigned int irq, struct irqaction *action); +void irq_proc_update_valid(struct irq_desc *desc); #else static inline void register_irq_proc(unsigned int irq, struct irq_desc *desc) { } static inline void unregister_irq_proc(unsigned int irq, struct irq_desc *desc) { } @@ -130,6 +131,7 @@ static inline void register_handler_proc(unsigned int irq, struct irqaction *action) { } static inline void unregister_handler_proc(unsigned int irq, struct irqaction *action) { } +static inline void irq_proc_update_valid(struct irq_desc *desc) { } #endif extern bool irq_can_set_affinity_usr(unsigned int irq); diff --git a/kernel/irq/manage.c b/kernel/irq/manage.c index 8863b2d4b8ca..7eb07e3bdb4c 100644 --- a/kernel/irq/manage.c +++ b/kernel/irq/manage.c @@ -1802,6 +1802,7 @@ __setup_irq(unsigned int irq, struct irq_desc *desc, struct irqaction *new) __enable_irq(desc); } + irq_proc_update_valid(desc); raw_spin_unlock_irqrestore(&desc->lock, flags); chip_bus_sync_unlock(desc); mutex_unlock(&desc->request_mutex); @@ -1906,6 +1907,7 @@ static struct irqaction *__free_irq(struct irq_desc *desc, void *dev_id) desc->affinity_hint = NULL; #endif + irq_proc_update_valid(desc); raw_spin_unlock_irqrestore(&desc->lock, flags); /* * Drop bus_lock here so the changes which were done in the chip @@ -2047,6 +2049,8 @@ static const void *__cleanup_nmi(unsigned int irq, struct irq_desc *desc) irq_shutdown_and_deactivate(desc); } + irq_proc_update_valid(desc); + if (action) unregister_handler_proc(irq, action); kfree(action); @@ -2433,8 +2437,10 @@ static struct irqaction *__free_percpu_irq(unsigned int irq, void __percpu *dev_ *action_ptr = action->next; /* Demote from NMI if we killed the last action */ - if (!desc->action) + if (!desc->action) { desc->istate &= ~IRQS_NMI; + irq_proc_update_valid(desc); + } } unregister_handler_proc(irq, action); diff --git a/kernel/irq/proc.c b/kernel/irq/proc.c index d5b812680f5c..cc9c7c907dd2 100644 --- a/kernel/irq/proc.c +++ b/kernel/irq/proc.c @@ -440,6 +440,16 @@ void init_irq_proc(void) register_irq_proc(irq, desc); } +void irq_proc_update_valid(struct irq_desc *desc) +{ + u32 set = _IRQ_PROC_VALID; + + if (irq_settings_is_hidden(desc) || irq_desc_is_chained(desc) || !desc->action) + set = 0; + + irq_settings_update_proc_valid(desc, set); +} + #ifdef CONFIG_GENERIC_IRQ_SHOW int __weak arch_show_interrupts(struct seq_file *p, int prec) @@ -516,10 +526,7 @@ int show_interrupts(struct seq_file *p, void *v) guard(rcu)(); desc = irq_to_desc(i); - if (!desc || irq_settings_is_hidden(desc)) - return 0; - - if (!desc->action || irq_desc_is_chained(desc) || !desc->kstat_irqs) + if (!desc || !irq_settings_proc_valid(desc)) return 0; seq_printf(p, "%*d:", prec, i); diff --git a/kernel/irq/settings.h b/kernel/irq/settings.h index 00b3bd127692..0a0c027a5d34 100644 --- a/kernel/irq/settings.h +++ b/kernel/irq/settings.h @@ -18,6 +18,7 @@ enum { _IRQ_DISABLE_UNLAZY = IRQ_DISABLE_UNLAZY, _IRQ_HIDDEN = IRQ_HIDDEN, _IRQ_NO_DEBUG = IRQ_NO_DEBUG, + _IRQ_PROC_VALID = IRQ_RESERVED, _IRQF_MODIFY_MASK = IRQF_MODIFY_MASK, }; @@ -34,6 +35,7 @@ enum { #define IRQ_DISABLE_UNLAZY GOT_YOU_MORON #define IRQ_HIDDEN GOT_YOU_MORON #define IRQ_NO_DEBUG GOT_YOU_MORON +#define IRQ_RESERVED GOT_YOU_MORON #undef IRQF_MODIFY_MASK #define IRQF_MODIFY_MASK GOT_YOU_MORON @@ -180,3 +182,14 @@ static inline bool irq_settings_no_debug(struct irq_desc *desc) { return desc->status_use_accessors & _IRQ_NO_DEBUG; } + +static inline bool irq_settings_proc_valid(struct irq_desc *desc) +{ + return desc->status_use_accessors & _IRQ_PROC_VALID; +} + +static inline void irq_settings_update_proc_valid(struct irq_desc *desc, u32 set) +{ + desc->status_use_accessors &= ~_IRQ_PROC_VALID; + desc->status_use_accessors |= (set & _IRQ_PROC_VALID); +} From 2d62735f1d4a2832af367c9e3de04bfb280a945c Mon Sep 17 00:00:00 2001 From: Thomas Gleixner Date: Sun, 17 May 2026 22:02:24 +0200 Subject: [PATCH 12/18] genirq: Calculate precision only when required Calculating the precision of the interrupt number column on every initial show_interrupt() invocation is a pointless exercise as the underlying maximum number of interrupts rarely changes. Calculate it only when that number is modified and let show_interrupts() use the cached value. Signed-off-by: Thomas Gleixner Tested-by: Michael Kelley Reviewed-by: Dmitry Ilvokhin Reviewed-by: Radu Rendec Link: https://patch.msgid.link/20260517194931.760664517@kernel.org --- kernel/irq/internals.h | 6 ++++++ kernel/irq/irqdesc.c | 10 ++++++---- kernel/irq/proc.c | 33 +++++++++++++++++++++++---------- 3 files changed, 35 insertions(+), 14 deletions(-) diff --git a/kernel/irq/internals.h b/kernel/irq/internals.h index e5f3d310a825..be702dd64422 100644 --- a/kernel/irq/internals.h +++ b/kernel/irq/internals.h @@ -134,6 +134,12 @@ static inline void unregister_handler_proc(unsigned int irq, static inline void irq_proc_update_valid(struct irq_desc *desc) { } #endif +#if defined(CONFIG_PROC_FS) && defined(CONFIG_GENERIC_IRQ_SHOW) +void irq_proc_calc_prec(void); +#else +static inline void irq_proc_calc_prec(void) { } +#endif + extern bool irq_can_set_affinity_usr(unsigned int irq); extern int irq_do_set_affinity(struct irq_data *data, diff --git a/kernel/irq/irqdesc.c b/kernel/irq/irqdesc.c index a7ce1e8165a6..90595eefb477 100644 --- a/kernel/irq/irqdesc.c +++ b/kernel/irq/irqdesc.c @@ -157,13 +157,12 @@ EXPORT_SYMBOL_GPL(irq_get_nr_irqs); * * Return: @nr. */ -unsigned int irq_set_nr_irqs(unsigned int nr) +unsigned int __init irq_set_nr_irqs(unsigned int nr) { total_nr_irqs = nr; - + irq_proc_calc_prec(); return nr; } -EXPORT_SYMBOL_GPL(irq_set_nr_irqs); static DEFINE_MUTEX(sparse_irq_lock); static struct maple_tree sparse_irqs = MTREE_INIT_EXT(sparse_irqs, @@ -544,6 +543,7 @@ static bool irq_expand_nr_irqs(unsigned int nr) if (nr > MAX_SPARSE_IRQS) return false; total_nr_irqs = nr; + irq_proc_calc_prec(); return true; } @@ -572,6 +572,7 @@ int __init early_irq_init(void) desc = alloc_desc(i, node, 0, NULL, NULL); irq_insert_desc(i, desc); } + irq_proc_calc_prec(); return arch_early_irq_init(); } @@ -592,7 +593,7 @@ int __init early_irq_init(void) init_irq_default_affinity(); - printk(KERN_INFO "NR_IRQS: %d\n", NR_IRQS); + pr_info("NR_IRQS: %d\n", NR_IRQS); count = ARRAY_SIZE(irq_desc); @@ -602,6 +603,7 @@ int __init early_irq_init(void) goto __free_desc_res; } + irq_proc_calc_prec(); return arch_early_irq_init(); __free_desc_res: diff --git a/kernel/irq/proc.c b/kernel/irq/proc.c index cc9c7c907dd2..c635fb62c783 100644 --- a/kernel/irq/proc.c +++ b/kernel/irq/proc.c @@ -457,10 +457,25 @@ int __weak arch_show_interrupts(struct seq_file *p, int prec) return 0; } +static struct irq_proc_constraints { + unsigned int num_prec; +} irq_proc_constraints __read_mostly = { + .num_prec = 3, +}; + #ifndef ACTUAL_NR_IRQS # define ACTUAL_NR_IRQS total_nr_irqs #endif +void irq_proc_calc_prec(void) +{ + unsigned int prec, n; + + for (prec = 3, n = 1000; prec < 10 && n <= total_nr_irqs; ++prec) + n *= 10; + WRITE_ONCE(irq_proc_constraints.num_prec, prec); +} + /* Same as seq_put_decimal_ull_width(p, " ", cnt, 10) */ #define ZSTR1 " 0" #define ZSTR1_LEN (sizeof(ZSTR1) - 1) @@ -500,9 +515,7 @@ void irq_proc_emit_counts(struct seq_file *p, unsigned int __percpu *cnts) int show_interrupts(struct seq_file *p, void *v) { - const unsigned int nr_irqs = total_nr_irqs; - static int prec; - + unsigned int prec = READ_ONCE(irq_proc_constraints.num_prec); int i = *(loff_t *) v, j; struct irqaction *action; struct irq_desc *desc; @@ -515,9 +528,6 @@ int show_interrupts(struct seq_file *p, void *v) /* print header and calculate the width of the first column */ if (i == 0) { - for (prec = 3, j = 1000; prec < 10 && j <= nr_irqs; ++prec) - j *= 10; - seq_printf(p, "%*s", prec + 8, ""); for_each_online_cpu(j) seq_printf(p, "CPU%-8d", j); @@ -553,13 +563,16 @@ int show_interrupts(struct seq_file *p, void *v) } else { seq_printf(p, "%8s", "None"); } + + seq_putc(p, ' '); if (desc->irq_data.domain) - seq_printf(p, " %*lu", prec, desc->irq_data.hwirq); + seq_put_decimal_ull_width(p, "", desc->irq_data.hwirq, prec); else seq_printf(p, " %*s", prec, ""); -#ifdef CONFIG_GENERIC_IRQ_SHOW_LEVEL - seq_printf(p, " %-8s", irqd_is_level_type(&desc->irq_data) ? "Level" : "Edge"); -#endif + + if (IS_ENABLED(CONFIG_GENERIC_IRQ_SHOW_LEVEL)) + seq_printf(p, " %-8s", irqd_is_level_type(&desc->irq_data) ? "Level" : "Edge"); + if (desc->name) seq_printf(p, "-%-8s", desc->name); From 34594da7650d3ea67f96c0f4034ff6b2453f67c3 Mon Sep 17 00:00:00 2001 From: Thomas Gleixner Date: Sun, 17 May 2026 22:02:29 +0200 Subject: [PATCH 13/18] genirq/proc: Increase default interrupt number precision to four Quite some architectures have four character wide acronyms for architecture specific interrupts like IPI, NMI, etc. The default precision of printing the Linux device interrupt numbers is three, which causes quite some code to play games with adding or omitting space after the acronym and the colon in order to keep the per CPU numbers properly aligned. Increase the default number precision to four in the core code and get rid of the space games all over the place. At the same time align all architecture specific descriptor texts left so that they show up in the same column as the interrupt chip names, which makes the output more uniform accross architectures. Fix up the GDB script to this new scheme as well. Signed-off-by: Thomas Gleixner Link: https://patch.msgid.link/20260517194931.839482411@kernel.org --- arch/alpha/kernel/irq.c | 8 ++++---- arch/arm/kernel/smp.c | 3 +-- arch/arm64/kernel/smp.c | 5 ++--- arch/loongarch/kernel/smp.c | 2 +- arch/riscv/kernel/smp.c | 3 +-- arch/sh/kernel/irq.c | 2 +- arch/sparc/kernel/irq_32.c | 12 ++++++------ arch/sparc/kernel/irq_64.c | 4 ++-- arch/um/kernel/irq.c | 4 ++-- arch/xtensa/kernel/irq.c | 2 +- kernel/irq/proc.c | 4 ++-- scripts/gdb/linux/interrupts.py | 16 ++++++---------- 12 files changed, 29 insertions(+), 36 deletions(-) diff --git a/arch/alpha/kernel/irq.c b/arch/alpha/kernel/irq.c index c67047c5d830..4a6a8b1d5a8b 100644 --- a/arch/alpha/kernel/irq.c +++ b/arch/alpha/kernel/irq.c @@ -72,16 +72,16 @@ int arch_show_interrupts(struct seq_file *p, int prec) int j; #ifdef CONFIG_SMP - seq_puts(p, "IPI: "); + seq_puts(p, " IPI: "); for_each_online_cpu(j) seq_printf(p, "%10lu ", cpu_data[j].ipi_count); seq_putc(p, '\n'); #endif - seq_puts(p, "PMI: "); + seq_puts(p, " PMI: "); for_each_online_cpu(j) seq_printf(p, "%10lu ", per_cpu(irq_pmi_count, j)); - seq_puts(p, " Performance Monitoring\n"); - seq_printf(p, "ERR: %10lu\n", irq_err_count); + seq_puts(p, " Performance Monitoring\n"); + seq_printf(p, " ERR: %10lu\n", irq_err_count); return 0; } diff --git a/arch/arm/kernel/smp.c b/arch/arm/kernel/smp.c index 4e8e89a26ca3..b5fb4697bc3f 100644 --- a/arch/arm/kernel/smp.c +++ b/arch/arm/kernel/smp.c @@ -551,8 +551,7 @@ void show_ipi_list(struct seq_file *p, int prec) if (!ipi_desc[i]) continue; - seq_printf(p, "%*s%u:%s", prec - 1, "IPI", i, - prec >= 4 ? " " : ""); + seq_printf(p, "%*s%u:", prec - 1, "IPI", i); for_each_online_cpu(cpu) seq_printf(p, "%10u ", irq_desc_kstat_cpu(ipi_desc[i], cpu)); diff --git a/arch/arm64/kernel/smp.c b/arch/arm64/kernel/smp.c index 1aa324104afb..1d0e0e6a5b92 100644 --- a/arch/arm64/kernel/smp.c +++ b/arch/arm64/kernel/smp.c @@ -833,11 +833,10 @@ int arch_show_interrupts(struct seq_file *p, int prec) unsigned int cpu, i; for (i = 0; i < MAX_IPI; i++) { - seq_printf(p, "%*s%u:%s", prec - 1, "IPI", i, - prec >= 4 ? " " : ""); + seq_printf(p, "%*s%u: ", prec - 1, "IPI", i); for_each_online_cpu(cpu) seq_printf(p, "%10u ", irq_desc_kstat_cpu(get_ipi_desc(cpu, i), cpu)); - seq_printf(p, " %s\n", ipi_types[i]); + seq_printf(p, " %s\n", ipi_types[i]); } seq_printf(p, "%*s: %10lu\n", prec, "Err", irq_err_count); diff --git a/arch/loongarch/kernel/smp.c b/arch/loongarch/kernel/smp.c index 64a048f1b880..50922610758b 100644 --- a/arch/loongarch/kernel/smp.c +++ b/arch/loongarch/kernel/smp.c @@ -88,7 +88,7 @@ void show_ipi_list(struct seq_file *p, int prec) unsigned int cpu, i; for (i = 0; i < NR_IPI; i++) { - seq_printf(p, "%*s%u:%s", prec - 1, "IPI", i, prec >= 4 ? " " : ""); + seq_printf(p, "%*s%u:", prec - 1, "IPI", i); for_each_online_cpu(cpu) seq_put_decimal_ull_width(p, " ", per_cpu(irq_stat, cpu).ipi_irqs[i], 10); seq_printf(p, " LoongArch %d %s\n", i + 1, ipi_types[i]); diff --git a/arch/riscv/kernel/smp.c b/arch/riscv/kernel/smp.c index 5ed5095320e6..fa66f9c97d74 100644 --- a/arch/riscv/kernel/smp.c +++ b/arch/riscv/kernel/smp.c @@ -226,8 +226,7 @@ void show_ipi_stats(struct seq_file *p, int prec) unsigned int cpu, i; for (i = 0; i < IPI_MAX; i++) { - seq_printf(p, "%*s%u:%s", prec - 1, "IPI", i, - prec >= 4 ? " " : ""); + seq_printf(p, "%*s%u:", prec - 1, "IPI", i); for_each_online_cpu(cpu) seq_printf(p, "%10u ", irq_desc_kstat_cpu(ipi_desc[i], cpu)); seq_printf(p, " %s\n", ipi_names[i]); diff --git a/arch/sh/kernel/irq.c b/arch/sh/kernel/irq.c index 9022d8af9d68..03c39b5da50f 100644 --- a/arch/sh/kernel/irq.c +++ b/arch/sh/kernel/irq.c @@ -46,7 +46,7 @@ int arch_show_interrupts(struct seq_file *p, int prec) seq_printf(p, "%*s:", prec, "NMI"); for_each_online_cpu(j) seq_put_decimal_ull_width(p, " ", per_cpu(irq_stat.__nmi_count, j), 10); - seq_printf(p, " Non-maskable interrupts\n"); + seq_printf(p, " Non-maskable interrupts\n"); seq_printf(p, "%*s: %10u\n", prec, "ERR", atomic_read(&irq_err_count)); diff --git a/arch/sparc/kernel/irq_32.c b/arch/sparc/kernel/irq_32.c index 5210991429d5..22db727652ba 100644 --- a/arch/sparc/kernel/irq_32.c +++ b/arch/sparc/kernel/irq_32.c @@ -199,19 +199,19 @@ int arch_show_interrupts(struct seq_file *p, int prec) int j; #ifdef CONFIG_SMP - seq_printf(p, "RES:"); + seq_printf(p, "%*s:", prec, "RES"); for_each_online_cpu(j) seq_put_decimal_ull_width(p, " ", cpu_data(j).irq_resched_count, 10); - seq_printf(p, " IPI rescheduling interrupts\n"); - seq_printf(p, "CAL:"); + seq_printf(p, " IPI rescheduling interrupts\n"); + seq_printf(p, "%*s:", prec, "CAL"); for_each_online_cpu(j) seq_put_decimal_ull_width(p, " ", cpu_data(j).irq_call_count, 10); - seq_printf(p, " IPI function call interrupts\n"); + seq_printf(p, " IPI function call interrupts\n"); #endif - seq_printf(p, "NMI:"); + seq_printf(p, "%*s:", prec, "NMI"); for_each_online_cpu(j) seq_put_decimal_ull_width(p, " ", cpu_data(j).counter, 10); - seq_printf(p, " Non-maskable interrupts\n"); + seq_printf(p, " Non-maskable interrupts\n"); return 0; } diff --git a/arch/sparc/kernel/irq_64.c b/arch/sparc/kernel/irq_64.c index c5466a9fd560..3f55c69d5f3b 100644 --- a/arch/sparc/kernel/irq_64.c +++ b/arch/sparc/kernel/irq_64.c @@ -303,10 +303,10 @@ int arch_show_interrupts(struct seq_file *p, int prec) { int j; - seq_printf(p, "NMI:"); + seq_printf(p, "%*s:", prec, "NMI"); for_each_online_cpu(j) seq_put_decimal_ull_width(p, " ", cpu_data(j).__nmi_count, 10); - seq_printf(p, " Non-maskable interrupts\n"); + seq_printf(p, " Non-maskable interrupts\n"); return 0; } diff --git a/arch/um/kernel/irq.c b/arch/um/kernel/irq.c index 5929d498b65f..ddfd6e9bd8c7 100644 --- a/arch/um/kernel/irq.c +++ b/arch/um/kernel/irq.c @@ -716,12 +716,12 @@ int arch_show_interrupts(struct seq_file *p, int prec) seq_printf(p, "%*s: ", prec, "RES"); for_each_online_cpu(cpu) seq_printf(p, "%10u ", irq_stats(cpu)->irq_resched_count); - seq_puts(p, " Rescheduling interrupts\n"); + seq_puts(p, " Rescheduling interrupts\n"); seq_printf(p, "%*s: ", prec, "CAL"); for_each_online_cpu(cpu) seq_printf(p, "%10u ", irq_stats(cpu)->irq_call_count); - seq_puts(p, " Function call interrupts\n"); + seq_puts(p, " Function call interrupts\n"); #endif return 0; diff --git a/arch/xtensa/kernel/irq.c b/arch/xtensa/kernel/irq.c index b1e410f6b5ab..6f01f530868b 100644 --- a/arch/xtensa/kernel/irq.c +++ b/arch/xtensa/kernel/irq.c @@ -59,7 +59,7 @@ int arch_show_interrupts(struct seq_file *p, int prec) seq_printf(p, "%*s:", prec, "NMI"); for_each_online_cpu(cpu) seq_printf(p, " %10lu", per_cpu(nmi_count, cpu)); - seq_puts(p, " Non-maskable interrupts\n"); + seq_puts(p, " Non-maskable interrupts\n"); #endif return 0; } diff --git a/kernel/irq/proc.c b/kernel/irq/proc.c index c635fb62c783..1cb47a8eab49 100644 --- a/kernel/irq/proc.c +++ b/kernel/irq/proc.c @@ -460,7 +460,7 @@ int __weak arch_show_interrupts(struct seq_file *p, int prec) static struct irq_proc_constraints { unsigned int num_prec; } irq_proc_constraints __read_mostly = { - .num_prec = 3, + .num_prec = 4, }; #ifndef ACTUAL_NR_IRQS @@ -471,7 +471,7 @@ void irq_proc_calc_prec(void) { unsigned int prec, n; - for (prec = 3, n = 1000; prec < 10 && n <= total_nr_irqs; ++prec) + for (prec = 4, n = 10000; prec < 10 && n <= total_nr_irqs; ++prec) n *= 10; WRITE_ONCE(irq_proc_constraints.num_prec, prec); } diff --git a/scripts/gdb/linux/interrupts.py b/scripts/gdb/linux/interrupts.py index a4e25d2bf123..e96734348f86 100644 --- a/scripts/gdb/linux/interrupts.py +++ b/scripts/gdb/linux/interrupts.py @@ -131,23 +131,19 @@ def arm_common_show_interrupts(prec): if nr_ipi is None or ipi_desc is None or ipi_types is None: return text - if prec >= 4: - sep = " " - else: - sep = "" - for ipi in range(nr_ipi): - text += "%*s%u:%s" % (prec - 1, "IPI", ipi, sep) + text += "%*s%u: " % (prec - 1, "IPI", ipi) desc = ipi_desc[ipi].cast(irq_desc_type.get_type().pointer()) if desc == 0: continue for cpu in cpus.each_online_cpu(): - text += "%10u" % (cpus.per_cpu(desc['kstat_irqs'], cpu)['cnt']) - text += " %s" % (ipi_types[ipi].string()) + text += "%10u " % (cpus.per_cpu(desc['kstat_irqs'], cpu)['cnt']) + text += "%s" % (ipi_types[ipi].string()) text += "\n" return text def aarch64_show_interrupts(prec): + # Does not work for ARM64 as "ipi_desc" is not available there text = arm_common_show_interrupts(prec) text += "%*s: %10lu\n" % (prec, "ERR", gdb.parse_and_eval("irq_err_count")) return text @@ -175,8 +171,8 @@ class LxInterruptList(gdb.Command): def invoke(self, arg, from_tty): nr_irqs = gdb.parse_and_eval("total_nr_irqs") - prec = 3 - j = 1000 + prec = 4 + j = 10000 while prec < 10 and j <= nr_irqs: prec += 1 j *= 10 From 1d9c4745bfb6773712751f5068c23ebad9cd30a0 Mon Sep 17 00:00:00 2001 From: Thomas Gleixner Date: Sun, 17 May 2026 22:02:34 +0200 Subject: [PATCH 14/18] genirq: Add rcuref count to struct irq_desc Prepare for a smarter iterator for /proc/interrupts so that the next interrupt descriptor can be cached after lookup. Signed-off-by: Thomas Gleixner Tested-by: Michael Kelley Reviewed-by: Dmitry Ilvokhin Link: https://patch.msgid.link/20260517194931.917415190@kernel.org --- include/linux/irqdesc.h | 2 ++ kernel/irq/internals.h | 17 ++++++++++++++++- kernel/irq/irqdesc.c | 21 +++++++++++++-------- 3 files changed, 31 insertions(+), 9 deletions(-) diff --git a/include/linux/irqdesc.h b/include/linux/irqdesc.h index 1058786d76eb..8080db17c1b1 100644 --- a/include/linux/irqdesc.h +++ b/include/linux/irqdesc.h @@ -70,6 +70,7 @@ struct irq_redirect { * IRQF_NO_SUSPEND set * @force_resume_depth: number of irqactions on a irq descriptor with * IRQF_FORCE_RESUME set + * @refcnt: Reference count mainly for /proc/interrupts * @rcu: rcu head for delayed free * @kobj: kobject used to represent this struct in sysfs * @request_mutex: mutex to protect request/free before locking desc->lock @@ -119,6 +120,7 @@ struct irq_desc { struct dentry *debugfs_file; const char *dev_name; #endif + rcuref_t refcnt; #ifdef CONFIG_SPARSE_IRQ struct rcu_head rcu; struct kobject kobj; diff --git a/kernel/irq/internals.h b/kernel/irq/internals.h index be702dd64422..bacbf4020242 100644 --- a/kernel/irq/internals.h +++ b/kernel/irq/internals.h @@ -9,6 +9,7 @@ #include #include #include +#include #include #ifdef CONFIG_SPARSE_IRQ @@ -101,9 +102,23 @@ extern void unmask_irq(struct irq_desc *desc); extern void unmask_threaded_irq(struct irq_desc *desc); #ifdef CONFIG_SPARSE_IRQ -static inline void irq_mark_irq(unsigned int irq) { } +static __always_inline void irq_mark_irq(unsigned int irq) { } +void irq_desc_free_rcu(struct irq_desc *desc); + +static __always_inline bool irq_desc_get_ref(struct irq_desc *desc) +{ + return rcuref_get(&desc->refcnt); +} + +static __always_inline void irq_desc_put_ref(struct irq_desc *desc) +{ + if (rcuref_put(&desc->refcnt)) + irq_desc_free_rcu(desc); +} #else extern void irq_mark_irq(unsigned int irq); +static __always_inline bool irq_desc_get_ref(struct irq_desc *desc) { return true; } +static __always_inline void irq_desc_put_ref(struct irq_desc *desc) { } #endif irqreturn_t __handle_irq_event_percpu(struct irq_desc *desc); diff --git a/kernel/irq/irqdesc.c b/kernel/irq/irqdesc.c index 90595eefb477..23f21763bfed 100644 --- a/kernel/irq/irqdesc.c +++ b/kernel/irq/irqdesc.c @@ -137,6 +137,7 @@ static void desc_set_defaults(unsigned int irq, struct irq_desc *desc, int node, desc->tot_count = 0; desc->name = NULL; desc->owner = owner; + rcuref_init(&desc->refcnt, 1); desc_smp_init(desc, node, affinity); } @@ -465,6 +466,17 @@ static void delayed_free_desc(struct rcu_head *rhp) kobject_put(&desc->kobj); } +void irq_desc_free_rcu(struct irq_desc *desc) +{ + /* + * We free the descriptor, masks and stat fields via RCU. That + * allows demultiplex interrupts to do rcu based management of + * the child interrupts. + * This also allows us to use rcu in kstat_irqs_usr(). + */ + call_rcu(&desc->rcu, delayed_free_desc); +} + static void free_desc(unsigned int irq) { struct irq_desc *desc = irq_to_desc(irq); @@ -483,14 +495,7 @@ static void free_desc(unsigned int irq) */ irq_sysfs_del(desc); delete_irq_desc(irq); - - /* - * We free the descriptor, masks and stat fields via RCU. That - * allows demultiplex interrupts to do rcu based management of - * the child interrupts. - * This also allows us to use rcu in kstat_irqs_usr(). - */ - call_rcu(&desc->rcu, delayed_free_desc); + irq_desc_put_ref(desc); } static int alloc_descs(unsigned int start, unsigned int cnt, int node, From 7603e0575d8a92318bd4695917fce7ec2c5825a1 Mon Sep 17 00:00:00 2001 From: Thomas Gleixner Date: Sun, 17 May 2026 22:02:39 +0200 Subject: [PATCH 15/18] genirq: Expose irq_find_desc_at_or_after() in core code ... in preparation for a smarter iterator for /proc/interrupts. Signed-off-by: Thomas Gleixner Tested-by: Michael Kelley Reviewed-by: Dmitry Ilvokhin Link: https://patch.msgid.link/20260517194932.005787611@kernel.org --- kernel/irq/internals.h | 2 ++ kernel/irq/irqdesc.c | 15 ++++++++------- 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/kernel/irq/internals.h b/kernel/irq/internals.h index bacbf4020242..37eec0337867 100644 --- a/kernel/irq/internals.h +++ b/kernel/irq/internals.h @@ -155,6 +155,8 @@ void irq_proc_calc_prec(void); static inline void irq_proc_calc_prec(void) { } #endif +struct irq_desc *irq_find_desc_at_or_after(unsigned int offset); + extern bool irq_can_set_affinity_usr(unsigned int irq); extern int irq_do_set_affinity(struct irq_data *data, diff --git a/kernel/irq/irqdesc.c b/kernel/irq/irqdesc.c index 23f21763bfed..80ef4e27dcf4 100644 --- a/kernel/irq/irqdesc.c +++ b/kernel/irq/irqdesc.c @@ -181,15 +181,12 @@ static int irq_find_free_area(unsigned int from, unsigned int cnt) return mas.index; } -static unsigned int irq_find_at_or_after(unsigned int offset) +struct irq_desc *irq_find_desc_at_or_after(unsigned int offset) { unsigned long index = offset; - struct irq_desc *desc; - guard(rcu)(); - desc = mt_find(&sparse_irqs, &index, total_nr_irqs); - - return desc ? irq_desc_get_irq(desc) : total_nr_irqs; + lockdep_assert_in_rcu_read_lock(); + return mt_find(&sparse_irqs, &index, total_nr_irqs); } static void irq_insert_desc(unsigned int irq, struct irq_desc *desc) @@ -934,7 +931,11 @@ EXPORT_SYMBOL_GPL(__irq_alloc_descs); */ unsigned int irq_get_next_irq(unsigned int offset) { - return irq_find_at_or_after(offset); + struct irq_desc *desc; + + guard(rcu)(); + desc = irq_find_desc_at_or_after(offset); + return desc ? irq_desc_get_irq(desc) : total_nr_irqs; } struct irq_desc *__irq_get_desc_lock(unsigned int irq, unsigned long *flags, bool bus, From 61b51a167c524b65a59b1342e70c2008d514a796 Mon Sep 17 00:00:00 2001 From: Thomas Gleixner Date: Sun, 17 May 2026 22:02:44 +0200 Subject: [PATCH 16/18] genirq/proc: Runtime size the chip name The chip name column in the /proc/interrupt output is 8 characters and right aligned, which causes visual clutter due to the fixed length and the alignment. Many interrupt chips, e.g. PCI/MSI[X] have way longer names. Update the length when a chip is assigned to an interrupt and utilize this information for the output. Align it left so all chip names start at the begin of the column. Update the GDB script as well and disentangle the header maze so it actually works with all .config combinations. Signed-off-by: Thomas Gleixner Tested-by: Michael Kelley Reviewed-by: Dmitry Ilvokhin Link: https://patch.msgid.link/20260517194932.085786035@kernel.org --- kernel/irq/chip.c | 6 +++-- kernel/irq/debugfs.h | 44 ++++++++++++++++++++++++++++++ kernel/irq/internals.h | 48 +++------------------------------ kernel/irq/irqdomain.c | 5 +++- kernel/irq/proc.c | 33 +++++++++++++++++++---- kernel/irq/proc.h | 13 +++++++++ scripts/gdb/linux/interrupts.py | 23 ++++++++++------ 7 files changed, 111 insertions(+), 61 deletions(-) create mode 100644 kernel/irq/debugfs.h create mode 100644 kernel/irq/proc.h diff --git a/kernel/irq/chip.c b/kernel/irq/chip.c index 2d283d1390dc..934777925f5c 100644 --- a/kernel/irq/chip.c +++ b/kernel/irq/chip.c @@ -47,9 +47,11 @@ int irq_set_chip(unsigned int irq, const struct irq_chip *chip) scoped_irqdesc->irq_data.chip = (struct irq_chip *)(chip ?: &no_irq_chip); ret = 0; } - /* For !CONFIG_SPARSE_IRQ make the irq show up in allocated_irqs. */ - if (!ret) + if (!ret) { + /* For !CONFIG_SPARSE_IRQ make the irq show up in allocated_irqs. */ irq_mark_irq(irq); + irq_proc_update_chip(chip); + } return ret; } EXPORT_SYMBOL(irq_set_chip); diff --git a/kernel/irq/debugfs.h b/kernel/irq/debugfs.h new file mode 100644 index 000000000000..8a9360d5fefb --- /dev/null +++ b/kernel/irq/debugfs.h @@ -0,0 +1,44 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef _KERNEL_IRQ_DEBUGFS_H +#define _KERNEL_IRQ_DEBUGFS_H + +#ifdef CONFIG_GENERIC_IRQ_DEBUGFS +#include + +struct irq_bit_descr { + unsigned int mask; + char *name; +}; + +#define BIT_MASK_DESCR(m) { .mask = m, .name = #m } + +void irq_debug_show_bits(struct seq_file *m, int ind, unsigned int state, + const struct irq_bit_descr *sd, int size); + +void irq_add_debugfs_entry(unsigned int irq, struct irq_desc *desc); +static inline void irq_remove_debugfs_entry(struct irq_desc *desc) +{ + debugfs_remove(desc->debugfs_file); + kfree(desc->dev_name); +} +void irq_debugfs_copy_devname(int irq, struct device *dev); +# ifdef CONFIG_IRQ_DOMAIN +void irq_domain_debugfs_init(struct dentry *root); +# else +static inline void irq_domain_debugfs_init(struct dentry *root) +{ +} +# endif +#else /* CONFIG_GENERIC_IRQ_DEBUGFS */ +static inline void irq_add_debugfs_entry(unsigned int irq, struct irq_desc *d) +{ +} +static inline void irq_remove_debugfs_entry(struct irq_desc *d) +{ +} +static inline void irq_debugfs_copy_devname(int irq, struct device *dev) +{ +} +#endif /* CONFIG_GENERIC_IRQ_DEBUGFS */ + +#endif diff --git a/kernel/irq/internals.h b/kernel/irq/internals.h index 37eec0337867..f9c099d45a64 100644 --- a/kernel/irq/internals.h +++ b/kernel/irq/internals.h @@ -12,6 +12,9 @@ #include #include +#include "debugfs.h" +#include "proc.h" + #ifdef CONFIG_SPARSE_IRQ # define MAX_SPARSE_IRQS INT_MAX #else @@ -149,12 +152,6 @@ static inline void unregister_handler_proc(unsigned int irq, static inline void irq_proc_update_valid(struct irq_desc *desc) { } #endif -#if defined(CONFIG_PROC_FS) && defined(CONFIG_GENERIC_IRQ_SHOW) -void irq_proc_calc_prec(void); -#else -static inline void irq_proc_calc_prec(void) { } -#endif - struct irq_desc *irq_find_desc_at_or_after(unsigned int offset); extern bool irq_can_set_affinity_usr(unsigned int irq); @@ -398,42 +395,3 @@ static inline struct irq_data *irqd_get_parent_data(struct irq_data *irqd) return NULL; #endif } - -#ifdef CONFIG_GENERIC_IRQ_DEBUGFS -#include - -struct irq_bit_descr { - unsigned int mask; - char *name; -}; - -#define BIT_MASK_DESCR(m) { .mask = m, .name = #m } - -void irq_debug_show_bits(struct seq_file *m, int ind, unsigned int state, - const struct irq_bit_descr *sd, int size); - -void irq_add_debugfs_entry(unsigned int irq, struct irq_desc *desc); -static inline void irq_remove_debugfs_entry(struct irq_desc *desc) -{ - debugfs_remove(desc->debugfs_file); - kfree(desc->dev_name); -} -void irq_debugfs_copy_devname(int irq, struct device *dev); -# ifdef CONFIG_IRQ_DOMAIN -void irq_domain_debugfs_init(struct dentry *root); -# else -static inline void irq_domain_debugfs_init(struct dentry *root) -{ -} -# endif -#else /* CONFIG_GENERIC_IRQ_DEBUGFS */ -static inline void irq_add_debugfs_entry(unsigned int irq, struct irq_desc *d) -{ -} -static inline void irq_remove_debugfs_entry(struct irq_desc *d) -{ -} -static inline void irq_debugfs_copy_devname(int irq, struct device *dev) -{ -} -#endif /* CONFIG_GENERIC_IRQ_DEBUGFS */ diff --git a/kernel/irq/irqdomain.c b/kernel/irq/irqdomain.c index cc93abf009e8..f15c9f1223bb 100644 --- a/kernel/irq/irqdomain.c +++ b/kernel/irq/irqdomain.c @@ -20,6 +20,8 @@ #include #include +#include "proc.h" + static LIST_HEAD(irq_domain_list); static DEFINE_MUTEX(irq_domain_mutex); @@ -1532,6 +1534,7 @@ int irq_domain_set_hwirq_and_chip(struct irq_domain *domain, unsigned int virq, irq_data->chip = (struct irq_chip *)(chip ? chip : &no_irq_chip); irq_data->chip_data = chip_data; + irq_proc_update_chip(chip); return 0; } EXPORT_SYMBOL_GPL(irq_domain_set_hwirq_and_chip); @@ -2081,7 +2084,7 @@ static void irq_domain_free_one_irq(struct irq_domain *domain, unsigned int virq #endif /* CONFIG_IRQ_DOMAIN_HIERARCHY */ #ifdef CONFIG_GENERIC_IRQ_DEBUGFS -#include "internals.h" +#include "debugfs.h" static struct dentry *domain_dir; diff --git a/kernel/irq/proc.c b/kernel/irq/proc.c index 1cb47a8eab49..9a968007eb2d 100644 --- a/kernel/irq/proc.c +++ b/kernel/irq/proc.c @@ -457,10 +457,14 @@ int __weak arch_show_interrupts(struct seq_file *p, int prec) return 0; } +static DEFINE_RAW_SPINLOCK(irq_proc_constraints_lock); + static struct irq_proc_constraints { unsigned int num_prec; + unsigned int chip_width; } irq_proc_constraints __read_mostly = { .num_prec = 4, + .chip_width = 8, }; #ifndef ACTUAL_NR_IRQS @@ -473,7 +477,23 @@ void irq_proc_calc_prec(void) for (prec = 4, n = 10000; prec < 10 && n <= total_nr_irqs; ++prec) n *= 10; - WRITE_ONCE(irq_proc_constraints.num_prec, prec); + + guard(raw_spinlock_irqsave)(&irq_proc_constraints_lock); + if (prec > irq_proc_constraints.num_prec) + WRITE_ONCE(irq_proc_constraints.num_prec, prec); +} + +void irq_proc_update_chip(const struct irq_chip *chip) +{ + unsigned int len = chip && chip->name ? strlen(chip->name) : 0; + + if (!len || len <= READ_ONCE(irq_proc_constraints.chip_width)) + return; + + /* Can be invoked from interrupt disabled contexts */ + guard(raw_spinlock_irqsave)(&irq_proc_constraints_lock); + if (len > irq_proc_constraints.chip_width) + WRITE_ONCE(irq_proc_constraints.chip_width, len); } /* Same as seq_put_decimal_ull_width(p, " ", cnt, 10) */ @@ -515,6 +535,7 @@ void irq_proc_emit_counts(struct seq_file *p, unsigned int __percpu *cnts) int show_interrupts(struct seq_file *p, void *v) { + unsigned int chip_width = READ_ONCE(irq_proc_constraints.chip_width); unsigned int prec = READ_ONCE(irq_proc_constraints.num_prec); int i = *(loff_t *) v, j; struct irqaction *action; @@ -550,18 +571,20 @@ int show_interrupts(struct seq_file *p, void *v) irq_proc_emit_counts(p, &desc->kstat_irqs->cnt); else irq_proc_emit_zero_counts(p, num_online_cpus()); - seq_putc(p, ' '); + + /* Enforce a visual gap */ + seq_write(p, " ", 2); guard(raw_spinlock_irq)(&desc->lock); if (desc->irq_data.chip) { if (desc->irq_data.chip->irq_print_chip) desc->irq_data.chip->irq_print_chip(&desc->irq_data, p); else if (desc->irq_data.chip->name) - seq_printf(p, "%8s", desc->irq_data.chip->name); + seq_printf(p, "%-*s", chip_width, desc->irq_data.chip->name); else - seq_printf(p, "%8s", "-"); + seq_printf(p, "%-*s", chip_width, "-"); } else { - seq_printf(p, "%8s", "None"); + seq_printf(p, "%-*s", chip_width, "None"); } seq_putc(p, ' '); diff --git a/kernel/irq/proc.h b/kernel/irq/proc.h new file mode 100644 index 000000000000..0631d57fbfb7 --- /dev/null +++ b/kernel/irq/proc.h @@ -0,0 +1,13 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef _KERNEL_IRQ_PROC_H +#define _KERNEL_IRQ_PROC_H + +#if defined(CONFIG_PROC_FS) && defined(CONFIG_GENERIC_IRQ_SHOW) +void irq_proc_calc_prec(void); +void irq_proc_update_chip(const struct irq_chip *chip); +#else +static inline void irq_proc_calc_prec(void) { } +static inline void irq_proc_update_chip(const struct irq_chip *chip) { } +#endif + +#endif diff --git a/scripts/gdb/linux/interrupts.py b/scripts/gdb/linux/interrupts.py index e96734348f86..a68ae91b4531 100644 --- a/scripts/gdb/linux/interrupts.py +++ b/scripts/gdb/linux/interrupts.py @@ -20,7 +20,7 @@ def irq_desc_is_chained(desc): def irqd_is_level(desc): return desc['irq_data']['common']['state_use_accessors'] & constants.LX_IRQD_LEVEL -def show_irq_desc(prec, irq): +def show_irq_desc(prec, chip_width, irq): text = "" desc = mapletree.mtree_load(gdb.parse_and_eval("&sparse_irqs"), irq) @@ -58,7 +58,7 @@ def show_irq_desc(prec, irq): else: name = "-" - text += " %-8s" % (name) + text += " %-*s" % (chip_width, name) if desc['irq_data']['domain']: text += " %*lu" % (prec, desc['irq_data']['hwirq']) @@ -171,11 +171,18 @@ class LxInterruptList(gdb.Command): def invoke(self, arg, from_tty): nr_irqs = gdb.parse_and_eval("total_nr_irqs") - prec = 4 - j = 10000 - while prec < 10 and j <= nr_irqs: - prec += 1 - j *= 10 + constr = utils.gdb_eval_or_none('irq_proc_constraints') + + if constr: + prec = int(constr['num_prec']) + chip_width = int(constr['chip_width']) + else: + prec = 4 + j = 10000 + while prec < 10 and j <= nr_irqs: + prec += 1 + j *= 10 + chip_width = 8 gdb.write("%*s" % (prec + 8, "")) for cpu in cpus.each_online_cpu(): @@ -186,7 +193,7 @@ class LxInterruptList(gdb.Command): raise gdb.GdbError("Unable to find the sparse IRQ tree, is CONFIG_SPARSE_IRQ enabled?") for irq in range(nr_irqs): - gdb.write(show_irq_desc(prec, irq)) + gdb.write(show_irq_desc(prec, chip_width, irq)) gdb.write(arch_show_interrupts(prec)) From 171cc0d9eed1cad5de7ce6a212efbeda390edb0f Mon Sep 17 00:00:00 2001 From: Thomas Gleixner Date: Sun, 17 May 2026 22:02:49 +0200 Subject: [PATCH 17/18] genirq/proc: Speed up /proc/interrupts iteration Reading /proc/interrupts iterates over the interrupt number space one by one and looks up the descriptors one by one. That's just a waste of time. When CONFIG_GENERIC_IRQ_SHOW is enabled this can utilize the maple tree and cache the descriptor pointer efficiently for the sequence file operations. Implement a CONFIG_GENERIC_IRQ_SHOW specific version in the core code and leave the fs/proc/ variant for the legacy architectures which ignore generic code. This reduces the time wasted for looking up the next record significantly. Signed-off-by: Thomas Gleixner Tested-by: Michael Kelley Reviewed-by: Dmitry Ilvokhin Link: https://patch.msgid.link/20260517194932.165280601@kernel.org --- fs/proc/Makefile | 4 +- kernel/irq/proc.c | 116 ++++++++++++++++++++++++++++++++++++---------- 2 files changed, 94 insertions(+), 26 deletions(-) diff --git a/fs/proc/Makefile b/fs/proc/Makefile index 7b4db9c56e6a..8bc615ff84e5 100644 --- a/fs/proc/Makefile +++ b/fs/proc/Makefile @@ -16,7 +16,9 @@ proc-y += cmdline.o proc-y += consoles.o proc-y += cpuinfo.o proc-y += devices.o -proc-y += interrupts.o +ifneq ($(CONFIG_GENERIC_IRQ_SHOW),y) +proc-y += interrupts.o +endif proc-y += loadavg.o proc-y += meminfo.o proc-y += stat.o diff --git a/kernel/irq/proc.c b/kernel/irq/proc.c index 9a968007eb2d..1b835725f7b1 100644 --- a/kernel/irq/proc.c +++ b/kernel/irq/proc.c @@ -452,6 +452,8 @@ void irq_proc_update_valid(struct irq_desc *desc) #ifdef CONFIG_GENERIC_IRQ_SHOW +#define ARCH_PROC_IRQDESC ((void *)0x00001111) + int __weak arch_show_interrupts(struct seq_file *p, int prec) { return 0; @@ -460,6 +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; unsigned int num_prec; unsigned int chip_width; } irq_proc_constraints __read_mostly = { @@ -533,34 +536,28 @@ void irq_proc_emit_counts(struct seq_file *p, unsigned int __percpu *cnts) irq_proc_emit_zero_counts(p, zeros); } -int show_interrupts(struct seq_file *p, void *v) +static int irq_seq_show(struct seq_file *p, void *v) { - unsigned int chip_width = READ_ONCE(irq_proc_constraints.chip_width); - unsigned int prec = READ_ONCE(irq_proc_constraints.num_prec); - int i = *(loff_t *) v, j; + struct irq_proc_constraints *constr = p->private; + struct irq_desc *desc = v; struct irqaction *action; - struct irq_desc *desc; - if (i > ACTUAL_NR_IRQS) - return 0; + /* Print header for the first interrupt? */ + if (constr->print_header) { + unsigned int cpu; - if (i == ACTUAL_NR_IRQS) - return arch_show_interrupts(p, prec); - - /* print header and calculate the width of the first column */ - if (i == 0) { - seq_printf(p, "%*s", prec + 8, ""); - for_each_online_cpu(j) - seq_printf(p, "CPU%-8d", j); + 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; } - guard(rcu)(); - desc = irq_to_desc(i); - if (!desc || !irq_settings_proc_valid(desc)) - return 0; + if (desc == ARCH_PROC_IRQDESC) + return arch_show_interrupts(p, constr->num_prec); - seq_printf(p, "%*d:", prec, i); + seq_put_decimal_ull_width(p, "", irq_desc_get_irq(desc), constr->num_prec); + seq_putc(p, ':'); /* * Always output per CPU interrupts. Output device interrupts only when @@ -580,18 +577,18 @@ int show_interrupts(struct seq_file *p, void *v) if (desc->irq_data.chip->irq_print_chip) desc->irq_data.chip->irq_print_chip(&desc->irq_data, p); else if (desc->irq_data.chip->name) - seq_printf(p, "%-*s", chip_width, desc->irq_data.chip->name); + seq_printf(p, "%-*s", constr->chip_width, desc->irq_data.chip->name); else - seq_printf(p, "%-*s", chip_width, "-"); + seq_printf(p, "%-*s", constr->chip_width, "-"); } else { - seq_printf(p, "%-*s", chip_width, "None"); + seq_printf(p, "%-*s", constr->chip_width, "None"); } seq_putc(p, ' '); if (desc->irq_data.domain) - seq_put_decimal_ull_width(p, "", desc->irq_data.hwirq, prec); + seq_put_decimal_ull_width(p, "", desc->irq_data.hwirq, constr->num_prec); else - seq_printf(p, " %*s", prec, ""); + seq_printf(p, " %*s", constr->num_prec, ""); if (IS_ENABLED(CONFIG_GENERIC_IRQ_SHOW_LEVEL)) seq_printf(p, " %-8s", irqd_is_level_type(&desc->irq_data) ? "Level" : "Edge"); @@ -609,4 +606,73 @@ int show_interrupts(struct seq_file *p, void *v) seq_putc(p, '\n'); return 0; } + +static void *irq_seq_next_desc(loff_t *pos) +{ + if (*pos > total_nr_irqs) + return NULL; + + guard(rcu)(); + for (;;) { + struct irq_desc *desc = irq_find_desc_at_or_after((unsigned int) *pos); + + if (desc) { + *pos = irq_desc_get_irq(desc); + /* + * If valid for output then try to acquire a reference + * count on the descriptor so that it can't be freed + * after dropping RCU read lock on return. + */ + if (irq_settings_proc_valid(desc) && irq_desc_get_ref(desc)) + return desc; + (*pos)++; + } else { + *pos = total_nr_irqs; + return ARCH_PROC_IRQDESC; + } + } +} + +static void *irq_seq_start(struct seq_file *f, loff_t *pos) +{ + if (!*pos) { + struct irq_proc_constraints *constr = f->private; + + 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); +} + +static void *irq_seq_next(struct seq_file *f, void *v, loff_t *pos) +{ + if (v && v != ARCH_PROC_IRQDESC) + irq_desc_put_ref(v); + + (*pos)++; + return irq_seq_next_desc(pos); +} + +static void irq_seq_stop(struct seq_file *f, void *v) +{ + if (v && v != ARCH_PROC_IRQDESC) + irq_desc_put_ref(v); +} + +static const struct seq_operations irq_seq_ops = { + .start = irq_seq_start, + .next = irq_seq_next, + .stop = irq_seq_stop, + .show = irq_seq_show, +}; + +static int __init irq_proc_init(void) +{ + proc_create_seq_private("interrupts", 0, NULL, &irq_seq_ops, + sizeof(irq_proc_constraints), NULL); + return 0; +} +fs_initcall(irq_proc_init); + #endif From 8f727615134abc6382f0ea07b90270d7bdde578f Mon Sep 17 00:00:00 2001 From: Thomas Gleixner Date: Sat, 13 Jun 2026 15:31:03 +0200 Subject: [PATCH 18/18] x86/irq: Add missing 's' back to thermal event printout The /proc/interrupt handling rework dropped a 's' in the thermal event printout, which breaks the thermal test in the Intel LKVS suite. Bring the important letter back. Fixes: 2b57c69917ee ("x86/irq: Make irqstats array based") Reported-by: kernel test robot Signed-off-by: Thomas Gleixner Closes: https://lore.kernel.org/oe-lkp/202606121325.97b29701-lkp@intel.com --- arch/x86/kernel/irq.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/arch/x86/kernel/irq.c b/arch/x86/kernel/irq.c index 9d98c81fcd24..30122f0b3af9 100644 --- a/arch/x86/kernel/irq.c +++ b/arch/x86/kernel/irq.c @@ -92,7 +92,7 @@ static const struct irq_stat_info irq_stat_info[IRQ_COUNT_MAX] = { #endif ISS(TLB, "TLB", " TLB shootdowns\n"), #ifdef CONFIG_X86_THERMAL_VECTOR - ISS(THERMAL_APIC, "TRM", " Thermal event interrupt\n"), + ISS(THERMAL_APIC, "TRM", " Thermal event interrupts\n"), #endif #ifdef CONFIG_X86_MCE_THRESHOLD ISS(THRESHOLD_APIC, "THR", " Threshold APIC interrupts\n"),