public inbox for linux-kernel@vger.kernel.org
 help / color / mirror / Atom feed
* [patch 00/14] genirq: Improve /proc/interrupts for real and add a binary interface
@ 2026-03-04 18:55 Thomas Gleixner
  2026-03-04 18:55 ` [patch 01/14] x86/irq: Optimize interrupts decimals printing Thomas Gleixner
                   ` (13 more replies)
  0 siblings, 14 replies; 24+ messages in thread
From: Thomas Gleixner @ 2026-03-04 18:55 UTC (permalink / raw)
  To: LKML; +Cc: x86, Dmitry Ilvokhin, Neil Horman

I started to look into this a few days ago due to the recent patch reminder
from Dmitry:

  https://lore.kernel.org/aQj5mGZ6_BBlAm3B@shell.ilvokhin.com

Let me start with the history or /proc/interrupts. I've been observing for
more than two decades that developers micro enhance performance of
/proc/interrupts readout and stop right there after gaining $N% in a
particular part of the code instead of actually looking at the bigger
picture. I understand that people have time constraints, but it's amazing
how much low hanging fruit has been left on the table due to that.

Not to mention the more than a decade old talking about a new binary
interface which addresses the underlying problem of the unfortunately
unchangeable /proc/interrupts ABI. Just for giggles, the patch above
mentions it even in the change log:

   "Although a binary /proc interface would be a better long-term solution
    due to lower formatting (kernel side) and parsing (user-space 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."


Coming back to the above referenced patch which triggered me to actually
look into that. The patch achieves an impressive readout time improvement
of ~19% on my 256 CPUs test machine with a trivial C code test case which
essentially does:

	fd = open("/proc/interrupts");

	for (i = 0; i < 1000; i++) {
	    t0 = now();
	    read_all_data(fd);
	    deltas[i] = now() - t0;
	    lseek(fd, 0, SEEK_SET);
	}

	print_mean_and_rel_stddev(deltas);

I wrote that trivial test because the numbers provided in the patch above
are based on 'perf stat -r 1000 cat /proc/interrupts >/dev/null', which is
taking all the irrelevant setup and tear-down costs of 'cat' into
account. That makes it tedious to observe the actual problems via perf
[stat|top] because the setup/teardown overhead stats obfuscate the output.

For completeness sake the numbers observed with that very same perf command
line are provided below for reference. They pretty much confirm the
findings of the narrowed down micro benchmark.

Let's take a look at the resulting numbers:

Patch                                            t mean  rel. stddev   delta base  delta prev
     Baseline v7.0-rc1                      1310.363 us     +/-1.81% 
 1   x86/irq: Optimize interrupts decimal   1059.238 us     +/-1.76%       -19%      -19%

Impressive, but not so impressive when looking at perf top output and
addressing all the other offenders one by one:

Patch                                            t mean  rel. stddev   delta base  delta prev
     Baseline v7.0-rc1                      1310.363 us     +/-1.81% 
 1   x86/irq: Optimize interrupts decimal   1059.238 us     +/-1.76%       -19%      -19%
 3   genirq/proc: Utilize irq_desc::tot_c    652.365 us     +/-0.81%       -50%      -38%
 4   x86/irq: Make irqstats array based      605.326 us     +/-1.63%       -54%      - 7%
 7   genirq: Calculate precision only whe    575.973 us     +/-1.12%       -56%      - 5%
10   genirq/proc: Speed up /proc/interrup    209.907 us     +/-1.84%       -84%      -64%

Now let's look how I got there by simply running the microbenchmark with
infinite loops and using perf top to analyze the hotspots.

Baseline

  20.19%  [k] mtree_load
  19.34%  [k] num_to_str
  10.12%  [k] number
   9.18%  [k] vsnprintf
   7.55%  [k] seq_put_decimal_ull_width
   6.16%  [k] format_decode
   5.65%  [k] show_interrupts
   4.88%  [k] _find_next_bit
   3.46%  [k] seq_read_iter
   3.29%  [k] seq_printf
   1.58%  [k] memcpy_orig
   1.50%  [k] __rcu_read_lock
   1.26%  [k] arch_show_interrupts
   1.05%  [k] __rcu_read_unlock
   1.04%  [k] int_seq_next
   0.94%  [k] rep_movs_alternative
   0.82%  [k] irq_to_desc
   0.56%  [k] irq_get_nr_irqs

It's interesting to me that vsnprintf() was the first item to look at.

 1   x86/irq: Optimize interrupts decimal printing

  29.94%  [k] num_to_str
  25.16%  [k] mtree_load
  12.48%  [k] seq_put_decimal_ull_width
   7.05%  [k] show_interrupts
   6.53%  [k] _find_next_bit
   4.48%  [k] seq_read_iter
   1.87%  [k] __rcu_read_lock
   1.84%  [k] arch_show_interrupts
   1.60%  [k] vsnprintf
   1.32%  [k] __rcu_read_unlock
   1.18%  [k] rep_movs_alternative
   1.16%  [k] int_seq_next
   1.01%  [k] format_decode
   0.98%  [k] irq_to_desc
   0.65%  [k] irq_get_nr_irqs

So Dmitry's patch removed the vsnprintf() overhead and made num_to_str()
more prominent.

That number is insanely high. So I analyzed /proc/interrupt output which
gave an easy way out. There is a large number of interrupts with all but
one CPU having zero counts. That's normal for interrupt managed multiqueue
devices. Also low frequency interrupts tend to stay on their initial
affinity and are not moved around by balancers.

With single CPU effective affinity targets (x86 and other architectures)
the majority of lines have just _one_ non zero entry, unless the balancer
or admin changed the affinity.

So it was pretty obvious to use a fixed string and write it directly
instead of repeating the string conversion of zero over and over.

 3   genirq/proc: Utilize irq_desc::tot_count to avoid evaluation

  41.04%  [kernel]          [k] mtree_load
  10.92%  [kernel]          [k] num_to_str
   5.97%  [kernel]          [k] show_interrupts
   5.18%  [kernel]          [k] _find_next_bit
   4.86%  [kernel]          [k] seq_put_decimal_ull_width
   4.65%  [kernel]          [k] seq_read_iter
   3.02%  [kernel]          [k] __rcu_read_lock
   2.84%  [kernel]          [k] arch_show_interrupts
   2.71%  [kernel]          [k] irq_proc_emit_counts
   2.69%  [kernel]          [k] memcpy_orig
   2.32%  [kernel]          [k] vsnprintf
   2.01%  [kernel]          [k] __rcu_read_unlock
   1.79%  [kernel]          [k] int_seq_next
   1.71%  [kernel]          [k] format_decode
   1.62%  [kernel]          [k] irq_to_desc
   1.47%  [kernel]          [k] rep_movs_alternative
   1.13%  [kernel]          [k] irq_get_nr_irqs

num_to_str() has lost the pole position and got replaced by mtree_load()
again. mtree_load() is used to get the interrupt descriptors, but there is
something seriously wrong with the high CPU usage. Though I decided to look
at that later.

When fixing up Dmitry's patch I noticed the way how x86 manages the
architecture specific statistics is suboptimal. x86 holds the per interrupt
counters in struct members and therefore requires an #ifdeffed series of
copied code per counter to emit them. The struct member based counters are
also in the way of implementing a binary interface without adding more
architecture specific duplicated code.

The same can be achieved with an array of counters. That's not changing the
actual code in the interrupt hotpath, which increments the counter, as the
array indices are constant so the compiler still calculates the offset from
the per CPU data pointer as before. This also fixes the out of sync
arch_show_interrupts() and arch_irq_stat_cpu() implementations as they now
use the same table.

 4   x86/irq: Make irqstats array based

  43.99%  [kernel]          [k] mtree_load
   9.16%  [kernel]          [k] irq_proc_emit_counts
   6.41%  [kernel]          [k] show_interrupts
   5.86%  [kernel]          [k] _find_next_bit
   4.91%  [kernel]          [k] seq_read_iter
   3.98%  [kernel]          [k] memcpy_orig
   3.28%  [kernel]          [k] __rcu_read_lock
   2.95%  [kernel]          [k] num_to_str
   2.28%  [kernel]          [k] vsnprintf
   2.06%  [kernel]          [k] __rcu_read_unlock
   2.02%  [kernel]          [k] rep_movs_alternative
   1.99%  [kernel]          [k] int_seq_next
   1.96%  [kernel]          [k] format_decode
   1.69%  [kernel]          [k] irq_to_desc
   1.58%  [kernel]          [k] seq_put_decimal_ull_width
   1.20%  [kernel]          [k] irq_get_nr_irqs

num_to_str() got further demoted because x86 now uses
irq_proc_emit_counts() with the optimized 0 output as well and
arch_show_interrupts() is off the radar because it only contains a trivial
loop.

This reduces text size by ~2k, which obviously reduces the I-cache foot
print. The loop overhead is completely irrelevant compared to the actual
costs of doing the for_each_online_cpu() loop once per interrupt and in
each iteration access memory in the worst possible pattern especially when
reading counters from remote nodes. There is not much which can be done
about that. I tried to copy all per CPU counters into local data storage
first, but that just trades one memory/cacheline massacre against another
for no gain.

So back to mtree_load(). That high CPU usage doesn't make any sense because
the whole point of sparse interrupts and the underlying maple tree is to
have quick iterations through the maple tree to skip holes. So much for the
theory.

fs/proc/interrupts got never updated and still iterates over the possible
interrupt number space one by one thereby defeating the whole purpose of
the maple tree. On the test machine that's almost 1000 lookups of interrupt
descriptor, while only 153 are in use and exist in the maple tree.

The trivial fix would have been to use the proper iterator in
fs/proc/interrupts, but that would need some investigation vs. the
architectures which do not use the generic version of show_interrupts().
That can be done by those people if they actually care.

Aside of that it still would touch the maple tree twice for each interrupt.
First for finding the next number and then to actually load the interrupt
descriptor. This can be done smarter by retrieving the next descriptor
right away and storing it in the *v data pointer of the seq_file ops
instead of using the pointer to store the number.

But doing that required some preparatory changes and while thinking about
them I noticed a few obvious improvements to avoid doing the same thing
over and over for no reason and to reduce the number of conditional
branches in show_interrupts()

 7   genirq: Calculate precision only when required

  46.41%  [k] mtree_load
  10.42%  [k] irq_proc_emit_counts
   5.86%  [k] show_interrupts
   5.11%  [k] seq_read_iter
   5.05%  [k] _find_next_bit
   3.56%  [k] memcpy_orig
   3.23%  [k] num_to_str
   2.51%  [k] __rcu_read_unlock
   2.27%  [k] vsnprintf
   2.23%  [k] __rcu_read_lock
   2.00%  [k] rep_movs_alternative
   1.90%  [k] int_seq_next
   1.81%  [k] seq_put_decimal_ull_width
   1.58%  [k] format_decode
   1.09%  [k] number
   0.91%  [k] string
   0.76%  [k] put_dec_trunc8
   0.67%  [k] seq_printf
   0.60%  [k] irq_to_desc
   0.46%  [k] irq_get_nr_irqs

show_interrupts() became slightly less expensive. The mtree_load()
leader position stays obviously the same.

With that addressed adding optimized seq_file code into the interrupt core
became feasible. All what it still required was to provide a fast refcount
mechanism so that the pointer can safely be stored in the seq_file iterator
and cannot be freed between seq_next() and seq_show(). I pondered briefly
to use the existing kobject in the interrupt descriptor, but that uses a
refcount_t underneath which is way more expensive than rcuref_t and
requires function calls. As the descriptors are RCU managed rcuref_t is the
obvious choice.

10   genirq/proc: Speed up /proc/interrupts iteration

  26.77%  [k] irq_proc_emit_counts
  15.90%  [k] _find_next_bit
  10.56%  [k] memcpy_orig
   9.82%  [k] num_to_str
   5.68%  [k] rep_movs_alternative
   5.34%  [k] vsnprintf
   4.76%  [k] seq_put_decimal_ull_width
   4.01%  [k] format_decode
   3.17%  [k] mt_find
   2.51%  [k] string
   2.24%  [k] number
   1.52%  [k] put_dec_trunc8
   1.44%  [k] seq_printf
   0.87%  [k] irq_seq_show

mt_find() is the one retrieving the next descriptor and the numbers are now
where one would expect them to be.

I might have missed some low hanging fruit there as well. If you find
it, you are entitled to keep it and fix it yourself. :)

That said, let's talk about the previously mentioned optimized binary
interface. As I'm not interested in reading "it would be better" over and
over for another ten years, I sat down and implemented a straight forward
interface which provides a variable record sized binary dump of the
relevant statistics separated into three files:

   1) device interrupts
   2) per CPU interrupts (irq descriptor based)
   3) architecture specific interrupts

The separation is done because for interrupt balancing purposes #1 is the
interesting part as #2 and #3 cannot be influenced by modifying
affinities. They can be monitored of course, but that's a different class
of events.

This interface comes with the following semantics:

   - each record starts with a pair of 'interrupt number' and 'number of
     entries'

   - each entry is a pair of 'cpu number' and 'interrupt counts'

   - records are only emitted for interrupts which have a total non-zero
     event count as that's what matters for observation.

   - records are only emitted if the counter(s) for the effective affinity
     mask CPU target(s) are non zero.

     Emitting counts from a previous affinity setting is irrelevant and can
     be easily cached by the monitoring application if needed.

   - completely lockless vs. the interrupt descriptor

     The readout does not touch the interrupt descriptor lock so it does
     not interfere with concurrent high frequency interrupts at all except
     for the memory access to the counter which can't obviously be avoided.

   - contrary to the non-sensical seq_file seek, which handles
     /proc/interrupts, allow only seeking back to the origin.

     /proc/interrupts is also a non constant record file and the seq_file
     lseek() implementation does therefore not guarantee a consistent
     lseek() at all, except for seek(0, SEEK_SET).

Reading those files with the same 1000 loops test takes on average:

     ~ 8 us for the device interrupts
     ~37 us for x86 architecture interrupts
     ---
     ~45 us total

Compared to the fully applied /proc/interrupt enhancements:

      209us vs. 45us =~ -79%	 --> ~4.5X

The comparison to the baseline v7.0-rc1 is:

     1310us vs. 45us =~ -96%	 --> ~29X

For completeness sake the perf top list of the endless read/lseek loop
accessing /proc/irq/device_stats:

  70.52%  [kernel]          [k] mt_find
   6.36%  [kernel]          [k] _copy_to_iter
   5.09%  [kernel]          [k] irq_stats_read
   3.77%  [kernel]          [k] irq_find_desc_at_or_after
   2.09%  [kernel]          [k] __rcu_read_lock
   2.04%  [kernel]          [k] __rcu_read_unlock
   1.67%  [kernel]          [k] __check_object_size
   1.07%  [kernel]          [k] __virt_addr_valid
   0.82%  [kernel]          [k] entry_SYSCALL_64
   0.81%  [kernel]          [k] vfs_read

Here mt_find() dominates rightfully because the decision to output data is
trivial as all interrupts are single CPU target so there is only one
counter per interrupt to access. If non-zero the store and the output is
just vanishing in the noise compared to the actual lookup costs. Based on
the 8us average that means:

    5.641 us   mt_find()
    0.509 us   copy_to_iter()
    0.407 us   irq_stats_read()
    0.302 us   irq_find_desc_at_or_after

mt_find() and irq_find_desc_at_or_after() belong together so we have:

    5.943 us   lookup()
    0.509 us   copy_to_iter()	// Copies data to user space
    0.407 us   irq_stats_read() // Counter analysis

Broken down to a per interrupt average with 153 requested interrupts on
that machine this means:

       38.8 ns   lookup time
        3.3 ns   copy to user
	2.7 ns	 analayis

And the same for the endless read/lseek loop accessing
/proc/irq/arch_stats:

  57.82%  [k] _find_next_bit
  37.20%  [k] irq_arch_stats_read
   1.06%  [k] _copy_to_iter
   1.03%  [k] rep_movs_alternative
   0.73%  [k] irq_stat_copy_to_iter
   0.27%  [k] vfs_read

The most expensive part of the latter are the for_each_online_cpu() loops
and the sub-optimal memory access patterns as explained above.

So with 37us readout time for 256 CPUs and 19 x86 architecture specific
counters this gives:

  21.393 us  for_each_online_cpu()
  13.764 us  irq_arch_stats_read()  // Counter analysis

That means an average per counter:

    1.12 us for_each_online_cpu()
    0.72 us irq_arch_stats_read()

In theory we could optimize the for_each_online_cpu() overhead, but that
creates a lot of inlined code for a meager 5% performance improvement over
the out of line version. Not really worth it.

Note that the /proc/interrupt numbers were taken on a mostly idle system
with almost zero propability to hit irq_desc::lock contention.

All test results are obviously skewed due to the repetitive invocations,
which prime the cache. But cache cold tests with no repeats are resulting
in roughly the same performance difference ratio for all scenarios.

A quick python hack computing the total number of interrupts from the
optimized /proc/interrupts and from the new binary interfaces yields:

      /proc/interrupts optimized	/proc/irq/[device+arch]_stats

      6.957 ms				0.394 ms	-94% (~17X)

which covers both the costs of reading and computing. The read advantage of
the binary interface is ~4.5X (see above), so the compute advantage of
avoiding the text parsing and not looking at pointless numbers amounts to
~3.7X which is unsurprisingly in the expected ballpark.

The last four patches related to the binary interface need obviously some
thought vs. the interface and are therefore marked RFC.

The series applies on top of v7.0-rc1 and is also available via git:

    git://git.kernel.org/pub/scm/linux/kernel/git/tglx/devel.git irq/core

Thanks

	tglx
---
   It's not that I'm so smart, it's just that I stay with problems longer.
      - Albert Einstein 
---
'perf stat -r -r 1000 cat /proc/interrupts' data series

Baseline v7.0-rc1

 Performance counter stats for 'cat /proc/interrupts' (1000 runs):

              2.55 msec task-clock                       #    0.830 CPUs utilized               ( +-  0.09% )
                 0      context-switches                 #    0.000 /sec                      
                 0      cpu-migrations                   #    0.000 /sec                      
                94      page-faults                      #   36.877 K/sec                       ( +-  0.05% )
         5,072,386      cycles                           #    1.990 GHz                         ( +-  0.08% )
           357,009      stalled-cycles-frontend          #    7.04% frontend cycles idle        ( +-  0.29% )
        17,197,829      instructions                     #    3.39  insn per cycle            
                                                  #    0.02  stalled cycles per insn     ( +-  0.01% )
         3,323,704      branches                         #    1.304 G/sec                       ( +-  0.01% )
            13,773      branch-misses                    #    0.41% of all branches             ( +-  0.09% )

        0.00307221 +- 0.00000369 seconds time elapsed  ( +-  0.12% )

x86/irq: Optimize interrupts decimals printing

 Performance counter stats for 'cat /proc/interrupts' (1000 runs):

              2.10 msec task-clock                       #    0.809 CPUs utilized               ( +-  0.18% )
                 0      context-switches                 #    0.000 /sec                      
                 0      cpu-migrations                   #    0.000 /sec                      
                94      page-faults                      #   44.720 K/sec                       ( +-  0.05% )
         4,179,092      cycles                           #    1.988 GHz                         ( +-  0.13% )
           360,135      stalled-cycles-frontend          #    8.62% frontend cycles idle        ( +-  0.35% )
        13,174,711      instructions                     #    3.15  insn per cycle            
                                                  #    0.03  stalled cycles per insn     ( +-  0.02% )
         2,596,179      branches                         #    1.235 G/sec                       ( +-  0.02% )
            13,793      branch-misses                    #    0.53% of all branches             ( +-  0.09% )

        0.00259694 +- 0.00000597 seconds time elapsed  ( +-  0.23% )

genirq/proc: Utilize irq_desc::tot_count to avoid evaluation

 Performance counter stats for 'cat /proc/interrupts' (1000 runs):

              1.51 msec task-clock                       #    0.753 CPUs utilized               ( +-  0.23% )
                 0      context-switches                 #    0.000 /sec                      
                 0      cpu-migrations                   #    0.000 /sec                      
                94      page-faults                      #   62.425 K/sec                       ( +-  0.05% )
         2,989,307      cycles                           #    1.985 GHz                         ( +-  0.18% )
           331,156      stalled-cycles-frontend          #   11.08% frontend cycles idle        ( +-  0.55% )
         7,564,373      instructions                     #    2.53  insn per cycle            
                                                  #    0.04  stalled cycles per insn     ( +-  0.03% )
         1,554,530      branches                         #    1.032 G/sec                       ( +-  0.03% )
            13,531      branch-misses                    #    0.87% of all branches             ( +-  0.10% )

        0.00199971 +- 0.00000644 seconds time elapsed  ( +-  0.32% )

x86/irq: Make irqstats array based

 Performance counter stats for 'cat /proc/interrupts' (1000 runs):

              1.46 msec task-clock                       #    0.730 CPUs utilized               ( +-  0.17% )
                 0      context-switches                 #    0.000 /sec                      
                 0      cpu-migrations                   #    0.000 /sec                      
                95      page-faults                      #   65.261 K/sec                       ( +-  0.05% )
         2,891,394      cycles                           #    1.986 GHz                         ( +-  0.16% )
           335,074      stalled-cycles-frontend          #   11.59% frontend cycles idle        ( +-  0.40% )
         6,461,160      instructions                     #    2.23  insn per cycle            
                                                  #    0.05  stalled cycles per insn     ( +-  0.04% )
         1,370,320      branches                         #  941.353 M/sec                       ( +-  0.03% )
            13,409      branch-misses                    #    0.98% of all branches             ( +-  0.10% )

        0.00199471 +- 0.00000400 seconds time elapsed  ( +-  0.20% )

genirq: Calculate precision only when required

 Performance counter stats for 'cat /proc/interrupts' (1000 runs):

              1.42 msec task-clock                       #    0.732 CPUs utilized               ( +-  0.23% )
                 0      context-switches                 #    0.000 /sec                      
                 0      cpu-migrations                   #    0.000 /sec                      
                95      page-faults                      #   67.004 K/sec                       ( +-  0.05% )
         2,817,515      cycles                           #    1.987 GHz                         ( +-  0.23% )
           327,877      stalled-cycles-frontend          #   11.64% frontend cycles idle        ( +-  0.73% )
         6,391,647      instructions                     #    2.27  insn per cycle            
                                                  #    0.05  stalled cycles per insn     ( +-  0.04% )
         1,348,883      branches                         #  951.380 M/sec                       ( +-  0.03% )
            13,483      branch-misses                    #    1.00% of all branches             ( +-  0.09% )

        0.00193706 +- 0.00000522 seconds time elapsed  ( +-  0.27% )

genirq/proc: Speed up /proc/interrupts iteration

 Performance counter stats for 'cat /proc/interrupts' (1000 runs):

              1.05 msec task-clock                       #    0.671 CPUs utilized               ( +-  0.23% )
                 0      context-switches                 #    0.000 /sec                      
                 0      cpu-migrations                   #    0.000 /sec                      
                95      page-faults                      #   90.552 K/sec                       ( +-  0.05% )
         2,077,859      cycles                           #    1.981 GHz                         ( +-  0.18% )
           313,913      stalled-cycles-frontend          #   15.11% frontend cycles idle        ( +-  0.35% )
         3,891,909      instructions                     #    1.87  insn per cycle            
                                                  #    0.08  stalled cycles per insn     ( +-  0.06% )
           744,475      branches                         #  709.616 M/sec                       ( +-  0.06% )
            12,962      branch-misses                    #    1.74% of all branches             ( +-  0.10% )

        0.00156440 +- 0.00000414 seconds time elapsed  ( +-  0.26% )

---
 arch/x86/Kconfig                    |    1 
 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      |   76 ++--
 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               |  225 ++++----------
 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 
 fs/proc/Makefile                    |    4 
 include/linux/interrupt.h           |    1 
 include/linux/irq.h                 |   18 +
 include/linux/irqdesc.h             |    8 
 include/uapi/linux/irqstats.h       |   27 +
 kernel/irq/Kconfig                  |    6 
 kernel/irq/chip.c                   |    2 
 kernel/irq/internals.h              |   24 +
 kernel/irq/irqdesc.c                |   67 ++--
 kernel/irq/manage.c                 |   16 -
 kernel/irq/proc.c                   |  556 +++++++++++++++++++++++++++++++++---
 kernel/irq/settings.h               |   14 
 40 files changed, 815 insertions(+), 301 deletions(-)


^ permalink raw reply	[flat|nested] 24+ messages in thread

* [patch 01/14] x86/irq: Optimize interrupts decimals printing
  2026-03-04 18:55 [patch 00/14] genirq: Improve /proc/interrupts for real and add a binary interface Thomas Gleixner
@ 2026-03-04 18:55 ` Thomas Gleixner
  2026-03-04 18:55 ` [patch 02/14] genirq/proc: Avoid formatting zero counts in /proc/interrupts Thomas Gleixner
                   ` (12 subsequent siblings)
  13 siblings, 0 replies; 24+ messages in thread
From: Thomas Gleixner @ 2026-03-04 18:55 UTC (permalink / raw)
  To: LKML; +Cc: x86, Dmitry Ilvokhin, Neil Horman

From: Dmitry Ilvokhin <d@ilvokhin.com>

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().

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 <d@ilvokhin.com>
Signed-off-by: Thomas Gleixner <tglx@kernel.org>
Link: https://patch.msgid.link/aQj5mGZ6_BBlAm3B@shell.ilvokhin.com

---
Changes v2:
- Expanded commit message: add more rationale for the proposed change.
- Renamed helper put_spaced_decimal() -> put_decimal() primarely to make
  checkpatch.pl --strict pass.

 arch/x86/kernel/irq.c |  112 ++++++++++++++++++++++++++------------------------
 1 file changed, 59 insertions(+), 53 deletions(-)
--- 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
 {
 	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
 	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;


^ permalink raw reply	[flat|nested] 24+ messages in thread

* [patch 02/14] genirq/proc: Avoid formatting zero counts in /proc/interrupts
  2026-03-04 18:55 [patch 00/14] genirq: Improve /proc/interrupts for real and add a binary interface Thomas Gleixner
  2026-03-04 18:55 ` [patch 01/14] x86/irq: Optimize interrupts decimals printing Thomas Gleixner
@ 2026-03-04 18:55 ` Thomas Gleixner
  2026-03-09 15:59   ` Dmitry Ilvokhin
  2026-03-04 18:55 ` [patch 03/14] genirq/proc: Utilize irq_desc::tot_count to avoid evaluation Thomas Gleixner
                   ` (11 subsequent siblings)
  13 siblings, 1 reply; 24+ messages in thread
From: Thomas Gleixner @ 2026-03-04 18:55 UTC (permalink / raw)
  To: LKML; +Cc: x86, Dmitry Ilvokhin, Neil Horman

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 <tglx@kernel.org>
---
 include/linux/interrupt.h |    1 +
 kernel/irq/proc.c         |   42 +++++++++++++++++++++++++++++++++++++-----
 2 files changed, 38 insertions(+), 5 deletions(-)

--- 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);
--- a/kernel/irq/proc.c
+++ b/kernel/irq/proc.c
@@ -450,6 +450,42 @@ int __weak arch_show_interrupts(struct s
 # define ACTUAL_NR_IRQS irq_get_nr_irqs()
 #endif
 
+#define ZSTR1 "          0"
+#define ZSTR1_LEN	11
+#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();
@@ -485,11 +521,7 @@ int show_interrupts(struct seq_file *p,
 		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);


^ permalink raw reply	[flat|nested] 24+ messages in thread

* [patch 03/14] genirq/proc: Utilize irq_desc::tot_count to avoid evaluation
  2026-03-04 18:55 [patch 00/14] genirq: Improve /proc/interrupts for real and add a binary interface Thomas Gleixner
  2026-03-04 18:55 ` [patch 01/14] x86/irq: Optimize interrupts decimals printing Thomas Gleixner
  2026-03-04 18:55 ` [patch 02/14] genirq/proc: Avoid formatting zero counts in /proc/interrupts Thomas Gleixner
@ 2026-03-04 18:55 ` Thomas Gleixner
  2026-03-09 16:04   ` Dmitry Ilvokhin
  2026-03-04 18:55 ` [patch 04/14] x86/irq: Make irqstats array based Thomas Gleixner
                   ` (10 subsequent siblings)
  13 siblings, 1 reply; 24+ messages in thread
From: Thomas Gleixner @ 2026-03-04 18:55 UTC (permalink / raw)
  To: LKML; +Cc: x86, Dmitry Ilvokhin, Neil Horman

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 <tglx@kernel.org>
---
 include/linux/irqdesc.h |    6 +++---
 kernel/irq/proc.c       |   11 ++++++++++-
 2 files changed, 13 insertions(+), 4 deletions(-)

--- 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;
--- a/kernel/irq/proc.c
+++ b/kernel/irq/proc.c
@@ -521,7 +521,16 @@ int show_interrupts(struct seq_file *p,
 		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);


^ permalink raw reply	[flat|nested] 24+ messages in thread

* [patch 04/14] x86/irq: Make irqstats array based
  2026-03-04 18:55 [patch 00/14] genirq: Improve /proc/interrupts for real and add a binary interface Thomas Gleixner
                   ` (2 preceding siblings ...)
  2026-03-04 18:55 ` [patch 03/14] genirq/proc: Utilize irq_desc::tot_count to avoid evaluation Thomas Gleixner
@ 2026-03-04 18:55 ` Thomas Gleixner
  2026-03-04 22:18   ` Michael Kelley
  2026-03-09 18:12   ` Dmitry Ilvokhin
  2026-03-04 18:55 ` [patch 05/14] genirq: Expose nr_irqs in core code Thomas Gleixner
                   ` (9 subsequent siblings)
  13 siblings, 2 replies; 24+ messages in thread
From: Thomas Gleixner @ 2026-03-04 18:55 UTC (permalink / raw)
  To: LKML; +Cc: x86, Dmitry Ilvokhin, Neil Horman

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 <tglx@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      |   69 ++++++----
 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               |  227 ++++++++++--------------------------
 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, 135 insertions(+), 232 deletions(-)

--- 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
 	 * 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_irq_stat(APIC_PERF);
 
 done:
 	cpuc->enabled = pmu_enabled;
--- a/arch/x86/events/amd/ibs.c
+++ b/arch/x86/events/amd/ibs.c
@@ -1403,7 +1403,7 @@ perf_ibs_nmi_handler(unsigned int cmd, s
 	handled += perf_ibs_handle_irq(&perf_ibs_op, regs);
 
 	if (handled)
-		inc_irq_stat(apic_perf_irqs);
+		inc_irq_stat(APIC_PERF);
 
 	perf_sample_event_took(sched_clock() - stamp);
 
--- a/arch/x86/events/core.c
+++ b/arch/x86/events/core.c
@@ -1747,7 +1747,7 @@ int x86_pmu_handle_irq(struct pt_regs *r
 	}
 
 	if (handled)
-		inc_irq_stat(apic_perf_irqs);
+		inc_irq_stat(APIC_PERF);
 
 	return handled;
 }
--- 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_r
 	int bit;
 	int handled = 0;
 
-	inc_irq_stat(apic_perf_irqs);
+	inc_irq_stat(APIC_PERF);
 
 	/*
 	 * Ignore a range of extra bits in status that do not indicate
--- 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_
 		goto done;
 	}
 
-	inc_irq_stat(apic_perf_irqs);
+	inc_irq_stat(APIC_PERF);
 
 	for_each_set_bit(bit, (unsigned long *)&status, X86_PMC_IDX_MAX) {
 		struct perf_event *event = cpuc->events[bit];
--- 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_r
 	}
 
 	if (handled)
-		inc_irq_stat(apic_perf_irqs);
+		inc_irq_stat(APIC_PERF);
 
 	/*
 	 * When dealing with the unmasking of the LVTPC on P4 perf hw, it has
--- 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
 	else
 		zhaoxin_pmu_ack_status(status);
 
-	inc_irq_stat(apic_perf_irqs);
+	inc_irq_stat(APIC_PERF);
 
 	/*
 	 * CondChgd bit 63 doesn't mean any overflow status. Ignore
--- a/arch/x86/hyperv/hv_init.c
+++ b/arch/x86/hyperv/hv_init.c
@@ -219,7 +219,7 @@ static inline bool hv_reenlightenment_av
 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);
 }
 
--- a/arch/x86/include/asm/hardirq.h
+++ b/arch/x86/include/asm/hardirq.h
@@ -4,51 +4,60 @@
 
 #include <linux/threads.h>
 
-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 */
+enum {
+	IRQ_COUNT_NMI,
 #ifdef CONFIG_X86_LOCAL_APIC
-	unsigned int apic_timer_irqs;	/* arch dependent */
-	unsigned int irq_spurious_count;
-	unsigned int icr_read_retry_count;
+	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
-#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;
+	IRQ_COUNT_RESCHEDULE,
+	IRQ_COUNT_CALL_FUNCTION,
+	IRQ_COUNT_TLB,
 #endif
-	unsigned int irq_tlb_count;
 #ifdef CONFIG_X86_THERMAL_VECTOR
-	unsigned int irq_thermal_count;
+	IRQ_COUNT_THERMAL_APIC,
 #endif
 #ifdef CONFIG_X86_MCE_THRESHOLD
-	unsigned int irq_threshold_count;
+	IRQ_COUNT_THRESHOLD_APIC,
 #endif
 #ifdef CONFIG_X86_MCE_AMD
-	unsigned int irq_deferred_error_count;
+	IRQ_COUNT_DEFERRED_ERROR,
+#endif
+#ifdef CONFIG_X86_MCE
+	IRQ_COUNT_MCE_EXCEPTION,
+	IRQ_COUNT_MCE_POLL,
 #endif
 #ifdef CONFIG_X86_HV_CALLBACK_VECTOR
-	unsigned int irq_hv_callback_count;
+	IRQ_COUNT_HYPERVISOR_CALLBACK,
 #endif
 #if IS_ENABLED(CONFIG_HYPERV)
-	unsigned int irq_hv_reenlightenment_count;
-	unsigned int hyperv_stimer0_count;
+	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
-	unsigned int posted_msi_notification_count;
+	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 counts[IRQ_COUNT_MAX];
 } ____cacheline_aligned irq_cpustat_t;
 
 DECLARE_PER_CPU_SHARED_ALIGNED(irq_cpustat_t, irq_stat);
@@ -58,7 +67,7 @@ DECLARE_PER_CPU_ALIGNED(struct 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])
 
 extern void ack_bad_irq(unsigned int irq);
 
--- 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);
 
--- a/arch/x86/kernel/apic/apic.c
+++ b/arch/x86/kernel/apic/apic.c
@@ -1040,7 +1040,7 @@ static void local_apic_timer_interrupt(v
 	/*
 	 * the NMI deadlock-detector uses this.
 	 */
-	inc_irq_stat(apic_timer_irqs);
+	inc_irq_stat(APIC_TIMER);
 
 	evt->event_handler(evt);
 }
@@ -2108,7 +2108,7 @@ static noinline void handle_spurious_int
 
 	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
--- 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;
--- a/arch/x86/kernel/cpu/acrn.c
+++ b/arch/x86/kernel/cpu/acrn.c
@@ -52,7 +52,7 @@ DEFINE_IDTENTRY_SYSVEC(sysvec_acrn_hv_ca
 	 * HYPERVISOR_CALLBACK_VECTOR.
 	 */
 	apic_eoi();
-	inc_irq_stat(irq_hv_callback_count);
+	inc_irq_stat(HYPERVISOR_CALLBACK);
 
 	if (acrn_intr_handler)
 		acrn_intr_handler();
--- a/arch/x86/kernel/cpu/mce/amd.c
+++ b/arch/x86/kernel/cpu/mce/amd.c
@@ -840,7 +840,7 @@ bool amd_mce_usable_address(struct mce *
 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();
--- 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
 	}
 }
 
-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 f
 	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_
 	 */
 	lmce = 1;
 
-	this_cpu_inc(mce_exception_count);
+	inc_irq_stat(MCE_EXCEPTION);
 
 	mce_gather_info(&err, regs);
 	m = &err.m;
--- 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) = def
 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();
--- a/arch/x86/kernel/cpu/mshyperv.c
+++ b/arch/x86/kernel/cpu/mshyperv.c
@@ -154,7 +154,7 @@ DEFINE_IDTENTRY_SYSVEC(sysvec_hyperv_cal
 {
 	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();
 
@@ -191,7 +191,7 @@ DEFINE_IDTENTRY_SYSVEC(sysvec_hyperv_sti
 {
 	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);
--- a/arch/x86/kernel/irq.c
+++ b/arch/x86/kernel/irq.c
@@ -62,156 +62,84 @@ 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;
+struct irq_stat_info {
+	unsigned int	test_vector;
+	const char	*symbol;
+	const char	*text;
+};
 
-	seq_put_decimal_ull_width(p, delimiter, num, width);
-}
+#define ISS(idx, sym, txt) [IRQ_COUNT_##idx] = { .symbol = sym, .text = txt }
 
-#define irq_stats(x)		(&per_cpu(irq_stat, x))
-/*
- * /proc/interrupts printing for arch specific interrupts
- */
-int arch_show_interrupts(struct seq_file *p, int prec)
-{
-	int j;
+#define ITS(idx, sym, txt) [IRQ_COUNT_##idx] =				\
+	{ .test_vector = idx## _VECTOR, .symbol = sym, .text = txt }
 
-	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");
+static const struct irq_stat_info irq_stat_info[IRQ_COUNT_MAX] = {
+	ISS(NMI,			"NMI", " 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");
-
-	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");
-	}
+	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
-	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");
+	ISS(RESCHEDULE,			"RES", " Rescheduling interrupts\n"),
+	ISS(CALL_FUNCTION,		"CAL", " Function call interrupts\n"),
+	ISS(TLB,			"TLB", " 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");
+	ISS(THERMAL_APIC,		"TRM", " Thermal event interrupt\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");
+	ISS(THRESHOLD_APIC,		"THR", " 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");
+	ISS(DEFERRED_ERROR,		"DFR", " 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");
+	ISS(MCE_EXCEPTION,		"MCE", " Machine check exceptions\n"),
+	ISS(MCE_POLL,			"MCP", " 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");
-	}
+	ITS(HYPERVISOR_CALLBACK,	"HYP", " 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));
+	ITS(HYPERV_REENLIGHTMENT,	"HRE", " Hyper-V reenlightment interrupts\n"),
+	ITS(HYPERV_STIMER0,		"HVS", " Hyper-V stimer0 interrupts\n"),
 #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");
+	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
-	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");
+	ISS(PERF_GUEST_MEDIATED_PMI,	"VPMI", " 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");
+	ISS(POSTED_MSI_NOTIFICATION,	"PMN", " Posted MSI notification event\n"),
 #endif
+};
+
+/*
+ * /proc/interrupts printing for arch specific interrupts
+ */
+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->test_vector && !test_bit(info->test_vector, system_vectors))
+			continue;
+
+		seq_printf(p, "%*s:", prec, info->symbol);
+		irq_proc_emit_counts(p, &irq_stat.counts[i]);
+		seq_puts(p, info->text);
+	}
+
+	seq_printf(p, "ERR: %10u\n", (unsigned int) atomic_read(&irq_err_count));
+	if (IS_ENABLED(CONFIG_X86_IO_APIC))
+		seq_printf(p, "MIS: %10u\n", (unsigned int) atomic_read(&irq_mis_count));
 	return 0;
 }
 
@@ -220,38 +148,11 @@ int arch_show_interrupts(struct seq_file
  */
 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;
 }
 
@@ -354,7 +255,7 @@ DEFINE_IDTENTRY_SYSVEC(sysvec_x86_platfo
 
 	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 +270,7 @@ DEFINE_IDTENTRY_SYSVEC(sysvec_x86_platfo
 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 +296,7 @@ EXPORT_SYMBOL_FOR_KVM(kvm_set_posted_int
 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 +305,7 @@ DEFINE_IDTENTRY_SYSVEC_SIMPLE(sysvec_kvm
 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 +315,7 @@ DEFINE_IDTENTRY_SYSVEC(sysvec_kvm_posted
 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 +389,7 @@ DEFINE_IDTENTRY_SYSVEC(sysvec_posted_msi
 
 	/* 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 +484,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();
--- 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);
 }
--- a/arch/x86/kernel/kvm.c
+++ b/arch/x86/kernel/kvm.c
@@ -310,7 +310,7 @@ DEFINE_IDTENTRY_SYSVEC(sysvec_kvm_asyncp
 
 	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);
--- 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);
--- a/arch/x86/kernel/smp.c
+++ b/arch/x86/kernel/smp.c
@@ -249,7 +249,7 @@ DEFINE_IDTENTRY_SYSVEC_SIMPLE(sysvec_res
 {
 	apic_eoi();
 	trace_reschedule_entry(RESCHEDULE_VECTOR);
-	inc_irq_stat(irq_resched_count);
+	inc_irq_stat(RESCHEDULE);
 	scheduler_ipi();
 	trace_reschedule_exit(RESCHEDULE_VECTOR);
 }
@@ -258,7 +258,7 @@ DEFINE_IDTENTRY_SYSVEC(sysvec_call_funct
 {
 	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);
 }
@@ -267,7 +267,7 @@ DEFINE_IDTENTRY_SYSVEC(sysvec_call_funct
 {
 	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);
 }
--- a/arch/x86/mm/tlb.c
+++ b/arch/x86/mm/tlb.c
@@ -1144,7 +1144,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);
 	}
 
--- a/arch/x86/xen/enlighten_hvm.c
+++ b/arch/x86/xen/enlighten_hvm.c
@@ -125,7 +125,7 @@ DEFINE_IDTENTRY_SYSVEC(sysvec_xen_hvm_ca
 	if (xen_percpu_upcall)
 		apic_eoi();
 
-	inc_irq_stat(irq_hv_callback_count);
+	inc_irq_stat(HYPERVISOR_CALLBACK);
 
 	xen_evtchn_do_upcall();
 
--- 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(st
 {
 	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();
 
--- a/arch/x86/xen/smp.c
+++ b/arch/x86/xen/smp.c
@@ -23,7 +23,7 @@ static irqreturn_t xen_call_function_sin
  */
 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_int
 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;
 }
--- 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 w
 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;
 }


^ permalink raw reply	[flat|nested] 24+ messages in thread

* [patch 05/14] genirq: Expose nr_irqs in core code
  2026-03-04 18:55 [patch 00/14] genirq: Improve /proc/interrupts for real and add a binary interface Thomas Gleixner
                   ` (3 preceding siblings ...)
  2026-03-04 18:55 ` [patch 04/14] x86/irq: Make irqstats array based Thomas Gleixner
@ 2026-03-04 18:55 ` Thomas Gleixner
  2026-03-09 18:26   ` Dmitry Ilvokhin
  2026-03-04 18:55 ` [patch 06/14] genirq: Cache the condition for /proc/interrupts exposure Thomas Gleixner
                   ` (8 subsequent siblings)
  13 siblings, 1 reply; 24+ messages in thread
From: Thomas Gleixner @ 2026-03-04 18:55 UTC (permalink / raw)
  To: LKML; +Cc: x86, Dmitry Ilvokhin, Neil Horman

... to avoid function calls in the core code to retrieve the maximum number
of interrupts.

Signed-off-by: Thomas Gleixner <tglx@kernel.org>
---
 kernel/irq/internals.h |    1 +
 kernel/irq/irqdesc.c   |   28 ++++++++++++++--------------
 kernel/irq/proc.c      |    2 +-
 3 files changed, 16 insertions(+), 15 deletions(-)

--- 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;
 
--- a/kernel/irq/irqdesc.c
+++ b/kernel/irq/irqdesc.c
@@ -140,14 +140,14 @@ static void desc_set_defaults(unsigned i
 	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
 	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
 {
 	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, u
 {
 	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, uns
 	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)
 {
--- a/kernel/irq/proc.c
+++ b/kernel/irq/proc.c
@@ -447,7 +447,7 @@ int __weak arch_show_interrupts(struct s
 }
 
 #ifndef ACTUAL_NR_IRQS
-# define ACTUAL_NR_IRQS irq_get_nr_irqs()
+# define ACTUAL_NR_IRQS total_nr_irqs
 #endif
 
 #define ZSTR1 "          0"


^ permalink raw reply	[flat|nested] 24+ messages in thread

* [patch 06/14] genirq: Cache the condition for /proc/interrupts exposure
  2026-03-04 18:55 [patch 00/14] genirq: Improve /proc/interrupts for real and add a binary interface Thomas Gleixner
                   ` (4 preceding siblings ...)
  2026-03-04 18:55 ` [patch 05/14] genirq: Expose nr_irqs in core code Thomas Gleixner
@ 2026-03-04 18:55 ` Thomas Gleixner
  2026-03-16 18:46   ` Dmitry Ilvokhin
  2026-03-04 18:55 ` [patch 07/14] genirq: Calculate precision only when required Thomas Gleixner
                   ` (7 subsequent siblings)
  13 siblings, 1 reply; 24+ messages in thread
From: Thomas Gleixner @ 2026-03-04 18:55 UTC (permalink / raw)
  To: LKML; +Cc: x86, Dmitry Ilvokhin, Neil Horman

show_interrupts() evaluates a boatload of conditions to establish whether
exposing 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.

As a result the number of instructions and branches for reading
/proc/interrupts is reduced significantly.

Signed-off-by: Thomas Gleixner <tglx@kernel.org>
---
 include/linux/irq.h    |    1 +
 kernel/irq/chip.c      |    2 ++
 kernel/irq/internals.h |    2 ++
 kernel/irq/manage.c    |    2 ++
 kernel/irq/proc.c      |   16 ++++++++++++----
 kernel/irq/settings.h  |   14 ++++++++++++++
 6 files changed, 33 insertions(+), 4 deletions(-)

--- a/include/linux/irq.h
+++ b/include/linux/irq.h
@@ -99,6 +99,7 @@ enum {
 	IRQ_DISABLE_UNLAZY	= (1 << 19),
 	IRQ_HIDDEN		= (1 << 20),
 	IRQ_NO_DEBUG		= (1 << 21),
+	IRQF_RESERVED		= (1 << 22),
 };
 
 #define IRQF_MODIFY_MASK	\
--- a/kernel/irq/chip.c
+++ b/kernel/irq/chip.c
@@ -1004,6 +1004,7 @@ static void
 		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,
@@ -1064,6 +1065,7 @@ void irq_modify_status(unsigned int irq,
 			trigger = tmp;
 
 		irqd_set(&desc->irq_data, trigger);
+		irq_proc_update_valid(desc);
 	}
 }
 EXPORT_SYMBOL_GPL(irq_modify_status);
--- a/kernel/irq/internals.h
+++ b/kernel/irq/internals.h
@@ -123,6 +123,7 @@ extern void register_irq_proc(unsigned i
 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
 					 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);
--- a/kernel/irq/manage.c
+++ b/kernel/irq/manage.c
@@ -1802,6 +1802,7 @@ static int
 		__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(stru
 		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
--- a/kernel/irq/proc.c
+++ b/kernel/irq/proc.c
@@ -439,6 +439,17 @@ 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) || !desc->action ||
+	    irq_desc_is_chained(desc) || !desc->kstat_irqs)
+		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)
@@ -514,10 +525,7 @@ int show_interrupts(struct seq_file *p,
 
 	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);
--- a/kernel/irq/settings.h
+++ b/kernel/irq/settings.h
@@ -37,6 +37,9 @@ enum {
 #undef IRQF_MODIFY_MASK
 #define IRQF_MODIFY_MASK	GOT_YOU_MORON
 
+#define _IRQ_PROC_VALID		IRQF_RESERVED
+#undef IRQF_RESERVED
+
 static inline void
 irq_settings_clr_and_set(struct irq_desc *desc, u32 clr, u32 set)
 {
@@ -180,3 +183,14 @@ static inline bool irq_settings_no_debug
 {
 	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);
+}


^ permalink raw reply	[flat|nested] 24+ messages in thread

* [patch 07/14] genirq: Calculate precision only when required
  2026-03-04 18:55 [patch 00/14] genirq: Improve /proc/interrupts for real and add a binary interface Thomas Gleixner
                   ` (5 preceding siblings ...)
  2026-03-04 18:55 ` [patch 06/14] genirq: Cache the condition for /proc/interrupts exposure Thomas Gleixner
@ 2026-03-04 18:55 ` Thomas Gleixner
  2026-03-16 18:57   ` Dmitry Ilvokhin
  2026-03-04 18:56 ` [patch 08/14] genirq: Add rcuref count to struct irq_desc Thomas Gleixner
                   ` (6 subsequent siblings)
  13 siblings, 1 reply; 24+ messages in thread
From: Thomas Gleixner @ 2026-03-04 18:55 UTC (permalink / raw)
  To: LKML; +Cc: x86, Dmitry Ilvokhin, Neil Horman

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 <tglx@kernel.org>
---
 kernel/irq/internals.h |    2 ++
 kernel/irq/irqdesc.c   |   10 ++++++----
 kernel/irq/proc.c      |   28 +++++++++++++++++++---------
 3 files changed, 27 insertions(+), 13 deletions(-)

--- a/kernel/irq/internals.h
+++ b/kernel/irq/internals.h
@@ -124,6 +124,7 @@ extern void unregister_irq_proc(unsigned
 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);
+void irq_proc_calc_prec(void);
 #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) { }
@@ -132,6 +133,7 @@ static inline void register_handler_proc
 static inline void unregister_handler_proc(unsigned int irq,
 					   struct irqaction *action) { }
 static inline void irq_proc_update_valid(struct irq_desc *desc) { }
+static inline void irq_proc_calc_prec(void) { }
 #endif
 
 extern bool irq_can_set_affinity_usr(unsigned int irq);
--- 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
 	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:
--- a/kernel/irq/proc.c
+++ b/kernel/irq/proc.c
@@ -457,10 +457,21 @@ int __weak arch_show_interrupts(struct s
 	return 0;
 }
 
+static int irq_num_prec __read_mostly = 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_num_prec, prec);
+}
+
 #define ZSTR1 "          0"
 #define ZSTR1_LEN	11
 #define ZSTR16		ZSTR1 ZSTR1 ZSTR1 ZSTR1 ZSTR1 ZSTR1 ZSTR1 ZSTR1 \
@@ -499,8 +510,7 @@ void irq_proc_emit_counts(struct seq_fil
 
 int show_interrupts(struct seq_file *p, void *v)
 {
-	const unsigned int nr_irqs = irq_get_nr_irqs();
-	static int prec;
+	int prec = READ_ONCE(irq_num_prec);
 
 	int i = *(loff_t *) v, j;
 	struct irqaction *action;
@@ -514,9 +524,6 @@ int show_interrupts(struct seq_file *p,
 
 	/* 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);
@@ -552,13 +559,16 @@ int show_interrupts(struct seq_file *p,
 	} 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);
 


^ permalink raw reply	[flat|nested] 24+ messages in thread

* [patch 08/14] genirq: Add rcuref count to struct irq_desc
  2026-03-04 18:55 [patch 00/14] genirq: Improve /proc/interrupts for real and add a binary interface Thomas Gleixner
                   ` (6 preceding siblings ...)
  2026-03-04 18:55 ` [patch 07/14] genirq: Calculate precision only when required Thomas Gleixner
@ 2026-03-04 18:56 ` Thomas Gleixner
  2026-03-04 18:56 ` [patch 09/14] genirq: Expose irq_find_desc_at_or_after() in core code Thomas Gleixner
                   ` (5 subsequent siblings)
  13 siblings, 0 replies; 24+ messages in thread
From: Thomas Gleixner @ 2026-03-04 18:56 UTC (permalink / raw)
  To: LKML; +Cc: x86, Dmitry Ilvokhin, Neil Horman

Prepare for a smarter iterator for /proc/interrupts so that the next
interrupt descriptor can be cached after lookup.

Signed-off-by: Thomas Gleixner <tglx@kernel.org>
---
 include/linux/irqdesc.h |    2 ++
 kernel/irq/internals.h  |   17 ++++++++++++++++-
 kernel/irq/irqdesc.c    |   21 +++++++++++++--------
 3 files changed, 31 insertions(+), 9 deletions(-)

--- 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;
--- a/kernel/irq/internals.h
+++ b/kernel/irq/internals.h
@@ -9,6 +9,7 @@
 #include <linux/irqdesc.h>
 #include <linux/kernel_stat.h>
 #include <linux/pm_runtime.h>
+#include <linux/rcuref.h>
 #include <linux/sched/clock.h>
 
 #ifdef CONFIG_SPARSE_IRQ
@@ -101,9 +102,23 @@ extern void unmask_irq(struct irq_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);
--- a/kernel/irq/irqdesc.c
+++ b/kernel/irq/irqdesc.c
@@ -137,6 +137,7 @@ static void desc_set_defaults(unsigned i
 	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
 	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,


^ permalink raw reply	[flat|nested] 24+ messages in thread

* [patch 09/14] genirq: Expose irq_find_desc_at_or_after() in core code
  2026-03-04 18:55 [patch 00/14] genirq: Improve /proc/interrupts for real and add a binary interface Thomas Gleixner
                   ` (7 preceding siblings ...)
  2026-03-04 18:56 ` [patch 08/14] genirq: Add rcuref count to struct irq_desc Thomas Gleixner
@ 2026-03-04 18:56 ` Thomas Gleixner
  2026-03-04 18:56 ` [patch 10/14] genirq/proc: Speed up /proc/interrupts iteration Thomas Gleixner
                   ` (4 subsequent siblings)
  13 siblings, 0 replies; 24+ messages in thread
From: Thomas Gleixner @ 2026-03-04 18:56 UTC (permalink / raw)
  To: LKML; +Cc: x86, Dmitry Ilvokhin, Neil Horman

--- in preparation for a smarter iterator for /proc/interrupts.

Signed-off-by: Thomas Gleixner <tglx@kernel.org>
---
 kernel/irq/internals.h |    2 ++
 kernel/irq/irqdesc.c   |   12 +++++-------
 2 files changed, 7 insertions(+), 7 deletions(-)

--- a/kernel/irq/internals.h
+++ b/kernel/irq/internals.h
@@ -151,6 +151,8 @@ static inline void irq_proc_update_valid
 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,
--- a/kernel/irq/irqdesc.c
+++ b/kernel/irq/irqdesc.c
@@ -181,15 +181,11 @@ static int irq_find_free_area(unsigned i
 	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;
+	return mt_find(&sparse_irqs, &index, total_nr_irqs);
 }
 
 static void irq_insert_desc(unsigned int irq, struct irq_desc *desc)
@@ -934,7 +930,9 @@ 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 = 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,


^ permalink raw reply	[flat|nested] 24+ messages in thread

* [patch 10/14] genirq/proc: Speed up /proc/interrupts iteration
  2026-03-04 18:55 [patch 00/14] genirq: Improve /proc/interrupts for real and add a binary interface Thomas Gleixner
                   ` (8 preceding siblings ...)
  2026-03-04 18:56 ` [patch 09/14] genirq: Expose irq_find_desc_at_or_after() in core code Thomas Gleixner
@ 2026-03-04 18:56 ` Thomas Gleixner
  2026-03-04 18:56 ` [patch 11/14] [RFC] genirq: Cache target CPU for single CPU affinities Thomas Gleixner
                   ` (3 subsequent siblings)
  13 siblings, 0 replies; 24+ messages in thread
From: Thomas Gleixner @ 2026-03-04 18:56 UTC (permalink / raw)
  To: LKML; +Cc: x86, Dmitry Ilvokhin, Neil Horman

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 <tglx@kernel.org>
---
 fs/proc/Makefile  |    4 +-
 kernel/irq/proc.c |   99 +++++++++++++++++++++++++++++++++++++++++++-----------
 2 files changed, 83 insertions(+), 20 deletions(-)

--- 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
--- a/kernel/irq/proc.c
+++ b/kernel/irq/proc.c
@@ -452,6 +452,8 @@ void irq_proc_update_valid(struct irq_de
 
 #ifdef CONFIG_GENERIC_IRQ_SHOW
 
+#define ARCH_PROC_IRQDESC ((void *)0x00001111)
+
 int __weak arch_show_interrupts(struct seq_file *p, int prec)
 {
 	return 0;
@@ -508,34 +510,29 @@ void irq_proc_emit_counts(struct seq_fil
 	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)
 {
-	int prec = READ_ONCE(irq_num_prec);
-
-	int i = *(loff_t *) v, j;
+	int prec = (int)(unsigned long)p->private;
+	struct irq_desc *desc = v;
 	struct irqaction *action;
-	struct irq_desc *desc;
-
-	if (i > ACTUAL_NR_IRQS)
-		return 0;
 
-	if (i == ACTUAL_NR_IRQS)
+	if (desc == ARCH_PROC_IRQDESC)
 		return arch_show_interrupts(p, prec);
 
-	/* print header and calculate the width of the first column */
-	if (i == 0) {
+	/* print header for the first interrupt indicated by !p>private */
+	if (!prec) {
+		unsigned int cpu;
+
+		prec = READ_ONCE(irq_num_prec);
 		seq_printf(p, "%*s", prec + 8, "");
-		for_each_online_cpu(j)
-			seq_printf(p, "CPU%-8d", j);
+		for_each_online_cpu(cpu)
+			seq_printf(p, "CPU%-8d", cpu);
 		seq_putc(p, '\n');
+		p->private = (void *)(unsigned long)prec;
 	}
 
-	guard(rcu)();
-	desc = irq_to_desc(i);
-	if (!desc || !irq_settings_proc_valid(desc))
-		return 0;
-
-	seq_printf(p, "%*d:", prec, i);
+	seq_put_decimal_ull_width(p, "", irq_desc_get_irq(desc), prec);
+	seq_putc(p, ':');
 
 	/*
 	 * Always output per CPU interrupts. Output device interrupts only when
@@ -582,4 +579,68 @@ int show_interrupts(struct seq_file *p,
 	seq_putc(p, '\n');
 	return 0;
 }
+
+static void *irq_seq_next_desc(loff_t *pos)
+{
+	struct irq_desc *desc;
+
+	if (*pos > total_nr_irqs)
+		return NULL;
+
+	guard(rcu)();
+	for (;;) {
+		desc = irq_find_desc_at_or_after((unsigned int) *pos);
+		if (desc) {
+			*pos = irq_desc_get_irq(desc);
+			/*
+			 * If valid for output 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)
+		f->private = NULL;
+	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("interrupts", 0, NULL, &irq_seq_ops);
+	return 0;
+}
+fs_initcall(irq_proc_init);
+
 #endif


^ permalink raw reply	[flat|nested] 24+ messages in thread

* [patch 11/14] [RFC] genirq: Cache target CPU for single CPU affinities
  2026-03-04 18:55 [patch 00/14] genirq: Improve /proc/interrupts for real and add a binary interface Thomas Gleixner
                   ` (9 preceding siblings ...)
  2026-03-04 18:56 ` [patch 10/14] genirq/proc: Speed up /proc/interrupts iteration Thomas Gleixner
@ 2026-03-04 18:56 ` Thomas Gleixner
  2026-03-04 18:56 ` [patch 12/14] [RFC] genirq/proc: Provide binary statistic interface Thomas Gleixner
                   ` (2 subsequent siblings)
  13 siblings, 0 replies; 24+ messages in thread
From: Thomas Gleixner @ 2026-03-04 18:56 UTC (permalink / raw)
  To: LKML; +Cc: x86, Dmitry Ilvokhin, Neil Horman

Some places can be optimized by caching the target CPU for single CPU
affinities. That avoids finding the single CPU in the effective affinity
mask. Provide infrastructure for that.

Signed-off-by: Thomas Gleixner <tglx@kernel.org>
---
 include/linux/irq.h |   17 +++++++++++++++++
 kernel/irq/manage.c |   14 ++++++++++----
 2 files changed, 27 insertions(+), 4 deletions(-)

--- a/include/linux/irq.h
+++ b/include/linux/irq.h
@@ -140,6 +140,8 @@ struct irq_domain;
  * @effective_affinity:	The effective IRQ affinity on SMP as some irq
  *			chips do not allow multi CPU destinations.
  *			A subset of @affinity.
+ * @target_cpu:		The target CPU when @effective_affinity contains
+ *			only a single CPU, IRQ_TARGET_MULTI_CPU otherwise
  * @msi_desc:		MSI descriptor
  * @ipi_offset:		Offset of first IPI target cpu in @affinity. Optional.
  */
@@ -155,6 +157,7 @@ struct irq_common_data {
 #endif
 #ifdef CONFIG_GENERIC_IRQ_EFFECTIVE_AFF_MASK
 	cpumask_var_t		effective_affinity;
+	unsigned int		target_cpu;
 #endif
 #ifdef CONFIG_GENERIC_IRQ_IPI
 	unsigned int		ipi_offset;
@@ -903,6 +906,8 @@ static inline const struct cpumask *irq_
 	return d ? irq_data_get_affinity_mask(d) : NULL;
 }
 
+#define IRQ_TARGET_MULTI_CPU	UINT_MAX
+
 #ifdef CONFIG_GENERIC_IRQ_EFFECTIVE_AFF_MASK
 static inline
 const struct cpumask *irq_data_get_effective_affinity_mask(struct irq_data *d)
@@ -914,6 +919,14 @@ static inline void irq_data_update_effec
 {
 	cpumask_copy(d->common->effective_affinity, m);
 }
+static inline unsigned int irq_data_get_single_target(struct irq_data *d)
+{
+	return d->common->target_cpu;
+}
+static inline void irq_data_set_single_target(struct irq_data *d, unsigned int cpu)
+{
+	d->common->target_cpu = cpu;
+}
 #else
 static inline void irq_data_update_effective_affinity(struct irq_data *d,
 						      const struct cpumask *m)
@@ -924,6 +937,10 @@ const struct cpumask *irq_data_get_effec
 {
 	return irq_data_get_affinity_mask(d);
 }
+static inline unsigned int irq_data_get_single_target(struct irq_data *d)
+{
+	return IRQ_TARGET_MULTI_CPU;
+}
 #endif
 
 static inline
--- a/kernel/irq/manage.c
+++ b/kernel/irq/manage.c
@@ -217,11 +217,17 @@ static void irq_validate_effective_affin
 {
 	const struct cpumask *m = irq_data_get_effective_affinity_mask(data);
 	struct irq_chip *chip = irq_data_get_irq_chip(data);
+	unsigned int target = IRQ_TARGET_MULTI_CPU;
 
-	if (!cpumask_empty(m))
-		return;
-	pr_warn_once("irq_chip %s did not update eff. affinity mask of irq %u\n",
-		     chip->name, data->irq);
+	switch (cpumask_weight(m)) {
+	case 0:
+		pr_warn_once("irq_chip %s did not update eff. affinity mask of irq %u\n",
+			     chip->name, data->irq);
+		break;
+	case 1:
+		target = cpumask_first(m);
+	}
+	irq_data_set_single_target(data, target);
 }
 #else
 static inline void irq_validate_effective_affinity(struct irq_data *data) { }


^ permalink raw reply	[flat|nested] 24+ messages in thread

* [patch 12/14] [RFC] genirq/proc: Provide binary statistic interface
  2026-03-04 18:55 [patch 00/14] genirq: Improve /proc/interrupts for real and add a binary interface Thomas Gleixner
                   ` (10 preceding siblings ...)
  2026-03-04 18:56 ` [patch 11/14] [RFC] genirq: Cache target CPU for single CPU affinities Thomas Gleixner
@ 2026-03-04 18:56 ` Thomas Gleixner
  2026-03-04 18:56 ` [patch 13/14] [RFC] genirq/proc: Provide architecture specific binary statistics Thomas Gleixner
  2026-03-04 18:56 ` [patch 14/14] [RFC] x86/irq: Hook up architecture specific stats Thomas Gleixner
  13 siblings, 0 replies; 24+ messages in thread
From: Thomas Gleixner @ 2026-03-04 18:56 UTC (permalink / raw)
  To: LKML; +Cc: x86, Dmitry Ilvokhin, Neil Horman

/proc/interrupts is expensive to evaluate for monitoring because:

  - it is text based and contains a lot of information which is not
    relevant for interrupt frequency analysis. Due to the extra information
    like chip name, hardware interrupt number, interrupt action names, it
    has to take the interrupt descriptor lock to output those items into
    the seq_file buffer. That obviously interferes with high frequency
    interrupt workloads.

  - it contains both device interrupts, per CPU and architecture specific
    interrupt counters without being able to look at them separately. The
    file is seekable by some definition of seekable as the position can
    change when interrupts are requested or freed, so the data has to be
    read completely to get a coherent picture.

  - it emits records for requested interrupts even if their interrupt count
    is zero.

  - it always prints the per CPU counters even if all but one of them are
    zero.

  - converting numbers to text and then parsing the text back to numbers in
    user space is a pretty wasteful exercise

Provide a new interface which addresses the above pain points:

  1) The interface is binary and emits variable length records per
     interrupt. Each record starts with a header containing the interrupt
     number and the number of data entries following the header. The data
     entries consist of a CPU number and count pair.

  2) Interrupts with a total count of zero are skipped and produce no
     output at all.

  3) Interrupts which have a single CPU affinity either due to a restricted
     affinity mask or due to the underlying interrupt chip restricting a
     mask to a single CPU target emit only one data entry.

     That means they are not emitting the stale counts on previous target
     CPUs but they are not really interesting for interrupt frequency
     analysis as they are not changing and therefore pointless for
     accounting.

  4) The interface separates device interrupts, per CPU interrupts and
     architecture specific interrupts.

     Per CPU and architecture specific interrupts can only be monitored,
     while device interrupts can also be steered by changing the affinity
     unless they are affinity managed by the kernel.

     Per CPU interrupts are only available on architectures, e.g. ARM64,
     which use the regular interrupt descriptor mechanism for per CPU
     interrupt handling.

     Architectures which have their own mechanics, e.g. x86, do not enable
     and provide the per CPU interface as those interrupts are covered by
     the architecture specific accounting.

  5) The readout is fully lockless so it does not interfere with concurrent
     interrupt handling.

  6) Seek is restricted to seek(fd, 0, SEEK_SET) as that's the only
     operation which makes sense due to the variable record length and the
     dynamics of interrupt request/free operations which influence the
     position of the records in the output. For all other seek()
     invocations return the current file position, which makes e.g. python
     happy as an error code causes the file open checks to mark the
     resulting file object non-seekable.

Implement support for /proc/irq/device_stats and /proc/irq/percpu_stats.

The support for architecture specific interrupt statistics is added in a
separate step.

Reading /proc/irq/device_stats on a 256 CPU x86 machine with 83 requested
interrupts produces 13 records due to skipping zero count interrupts. It
results in 13 * 16 = 208 bytes of data as all device interrupts on x86 are
single CPU targeted. That readout takes ~8us time in the kernel, while the
full /proc/interrupts readout takes about 360us.

Signed-off-by: Thomas Gleixner <tglx@kernel.org>
---
 include/uapi/linux/irqstats.h |   27 +++
 kernel/irq/Kconfig            |    3 
 kernel/irq/proc.c             |  314 ++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 344 insertions(+)

--- /dev/null
+++ b/include/uapi/linux/irqstats.h
@@ -0,0 +1,27 @@
+/* SPDX-License-Identifier: GPL-2.0-only WITH Linux-syscall-note */
+#ifndef LINUX_UAPI_IRQSTATS_H
+#define LINUX_UAPI_IRQSTATS_H
+
+/**
+ * irq_proc_stat_cpu - Data record for /proc/irq/stats
+ * @cpu:	The CPU associated to @cnt
+ * @cnt:	The count assiciated to @cpu
+ */
+struct irq_proc_stat_cpu {
+	unsigned int	cpu;
+	unsigned int	cnt;
+};
+
+/**
+ * irq_proc_stat_data - Data header for /proc/irq/stats
+ * @irqnr:	The interrupt number
+ * @entries:	The number of records (max. nr_cpu_ids)
+ * @pcpu:	Runtime sized array of per CPU stat records
+ */
+struct irq_proc_stat_data {
+	unsigned int			irqnr;
+	unsigned int			entries;
+	struct irq_proc_stat_cpu	pcpu[];
+};
+
+#endif
--- a/kernel/irq/Kconfig
+++ b/kernel/irq/Kconfig
@@ -18,6 +18,9 @@ config GENERIC_IRQ_SHOW
 config GENERIC_IRQ_SHOW_LEVEL
        bool
 
+config GENERIC_IRQ_STATS_PERCPU
+       bool
+
 # Supports effective affinity mask
 config GENERIC_IRQ_EFFECTIVE_AFF_MASK
        depends on SMP
--- a/kernel/irq/proc.c
+++ b/kernel/irq/proc.c
@@ -13,6 +13,8 @@
 #include <linux/kernel_stat.h>
 #include <linux/mutex.h>
 #include <linux/string.h>
+#include <linux/uio.h>
+#include <uapi/linux/irqstats.h>
 
 #include "internals.h"
 
@@ -636,9 +638,321 @@ static const struct seq_operations irq_s
 	.show  = irq_seq_show,
 };
 
+/*
+ * /proc/irq/stats related code
+ *
+ * /proc/irq/stats provides variable record sized statistics for device
+ * interrupts.
+ */
+struct irq_proc_stat {
+	unsigned int			irqnr;
+	bool				percpu;
+	bool				first;
+	size_t				from;
+	size_t				count;
+	loff_t				read_pos;
+	struct irq_desc			*desc;
+	struct irq_proc_stat_data	*data;
+};
+
+static inline bool irq_stat_valid_irq(struct irq_proc_stat *s)
+{
+	struct irq_desc *desc = s->desc;
+
+	/* Check for general validity */
+	if (!irq_settings_proc_valid(desc))
+		return false;
+
+	if (!s->percpu) {
+		/*
+		 * Device interrupts update desc::tot_count. Per CPU
+		 * interrupts are not touching that fields due to the
+		 * obvious concurrency issues.  For device interrupts it's
+		 * therefore sufficient to evaluate desc::tot_count.
+		 */
+		if (!data_race(desc->tot_count))
+			return false;
+	} else {
+		/*
+		 * Per CPU interrupts are marked accordingly in the
+		 * settings.
+		 */
+		if (!irq_settings_is_per_cpu(desc) && !irq_settings_is_per_cpu_devid(desc))
+			return false;
+	}
+
+	/* Try to get a reference to prevent freeing before it's evaluated */
+	return irq_desc_get_ref(desc);
+}
+
+static inline bool irq_stat_find_irq(struct irq_proc_stat *s)
+{
+	/* Loop until a valid interrupt is found */
+	guard(rcu)();
+	for (;; s->irqnr++) {
+		s->desc = irq_find_desc_at_or_after(s->irqnr);
+		/* NULL means there is no interrupt anymore in the maple tree */
+		if (!s->desc) {
+			s->irqnr = total_nr_irqs;
+			return false;
+		}
+
+		/* Save the interrupt number for the next search */
+		s->irqnr = irq_desc_get_irq(s->desc);
+
+		if (irq_stat_valid_irq(s))
+			return true;
+	}
+}
+
+static inline void irq_stat_next_irq(struct irq_proc_stat *s)
+{
+	s->irqnr++;
+	irq_stat_find_irq(s);
+}
+
+static void irq_dev_stat_update_one(struct irq_proc_stat *s)
+{
+	struct irq_proc_stat_data *d = s->data;
+	struct irq_desc *desc = s->desc;
+	struct irq_data *irqd;
+	unsigned int cpu;
+
+	/*
+	 * Optimize for single CPU target affinities. Otherwise walk the
+	 * effective affinity mask, which falls back to the real affinity
+	 * mask if the architecture does not support effective affinity
+	 * masks. Bad luck...
+	 */
+	irqd = irq_desc_get_irq_data(desc);
+	cpu = irq_data_get_single_target(irqd);
+	if (cpu < nr_cpu_ids) {
+		struct irq_proc_stat_cpu pcpu = {
+			.cpu = cpu,
+			.cnt = data_race(per_cpu(desc->kstat_irqs->cnt, cpu)),
+		};
+
+		if (pcpu.cnt)
+			d->pcpu[d->entries++] = pcpu;
+	} else {
+		const struct cpumask *m = irq_data_get_effective_affinity_mask(irqd);
+
+		for_each_cpu(cpu, m) {
+			struct irq_proc_stat_cpu pcpu = {
+				.cpu = cpu,
+				.cnt = data_race(per_cpu(desc->kstat_irqs->cnt, cpu)),
+			};
+
+			if (pcpu.cnt)
+				d->pcpu[d->entries++] = pcpu;
+		}
+	}
+}
+
+static void irq_percpu_stat_update_one(struct irq_proc_stat *s)
+{
+	struct irq_proc_stat_data *d = s->data;
+	struct irq_desc *desc = s->desc;
+	unsigned int cpu;
+
+	for_each_online_cpu(cpu) {
+		struct irq_proc_stat_cpu pcpu = {
+			.cpu = cpu,
+			.cnt = data_race(per_cpu(desc->kstat_irqs->cnt, cpu)),
+		};
+
+		if (pcpu.cnt)
+			d->pcpu[d->entries++] = pcpu;
+	}
+}
+
+static bool irq_stat_update_one(struct irq_proc_stat *s)
+{
+	struct irq_proc_stat_data *d = s->data;
+
+	if (IS_ENABLED(CONFIG_GENERIC_IRQ_PERCPU_STATS) && s->percpu)
+		irq_percpu_stat_update_one(s);
+	else
+		irq_dev_stat_update_one(s);
+
+	/* Only output data if there is an actual count */
+	if (d->entries) {
+		d->irqnr = s->irqnr;
+		s->count = sizeof(*d) + d->entries * sizeof(*d->pcpu);
+	}
+
+	/* Drop the reference count which got acquired in irq_stat_find_irq() */
+	irq_desc_put_ref(s->desc);
+	s->desc = NULL;
+	return !!s->count;
+}
+
+static __always_inline bool irq_stat_next_data(struct irq_proc_stat *s)
+{
+	/*
+	 * On the first read or after a lseek(fd, 0, SEEK_SET) find the
+	 * first interrupt. Otherwise find the next one.
+	 */
+	if (unlikely(s->first)) {
+		s->irqnr = 0;
+		s->first = false;
+		irq_stat_find_irq(s);
+	} else {
+		irq_stat_next_irq(s);
+	}
+
+	/* Repeat until an interrupt with non-zero counts is found */
+	for (; s->desc; irq_stat_next_irq(s)) {
+		if (irq_stat_update_one(s))
+			return true;
+	}
+	return false;
+}
+
+static size_t irq_stat_copy_to_iter(struct irq_proc_stat *s, struct iov_iter *iter)
+{
+	size_t n = copy_to_iter(((char *)s->data) + s->from, s->count, iter);
+
+	s->count -= n;
+	s->from += n;
+	return n;
+}
+
+/* Force inline as otherwise next() becomes a indirect call */
+static __always_inline ssize_t __irq_stats_read(struct kiocb *iocb, struct iov_iter *iter,
+						bool (*next)(struct irq_proc_stat *))
+{
+	struct irq_proc_stat *s = iocb->ki_filp->private_data;
+	size_t copied = 0;
+
+	/* Real seek is not supported. See irq_stat_lseek() */
+	if (WARN_ON_ONCE(iocb->ki_pos != s->read_pos))
+		goto done;
+
+	if (s->count)
+		copied += irq_stat_copy_to_iter(s, iter);
+
+	for (; !s->count;) {
+		s->count = s->from = 0;
+		s->data->entries = 0;
+
+		if (!next(s))
+			goto done;
+		copied += irq_stat_copy_to_iter(s, iter);
+	}
+
+	if (!copied)
+		return -EFAULT;
+done:
+	iocb->ki_pos += copied;
+	s->read_pos += copied;
+	return copied;
+}
+
+static ssize_t irq_stats_read(struct kiocb *iocb, struct iov_iter *iter)
+{
+	return __irq_stats_read(iocb, iter, irq_stat_next_data);
+}
+
+static loff_t irq_stats_llseek(struct file *filp, loff_t offset, int whence)
+{
+	struct irq_proc_stat *s = filp->private_data;
+	loff_t ret;
+
+	/*
+	 * As this is a variable record interface and the actual use case is to
+	 * get a full snapshot of the active interrupts, there is no point in
+	 * trying to be fully seekable. Just support rewind to the beginning of
+	 * the data set. For all other operations return the current position
+	 * which makes e.g. python happy.
+	 */
+	if (whence != SEEK_SET || offset)
+		return noop_llseek(filp, offset, whence);
+
+	ret = default_llseek(filp, 0, SEEK_SET);
+	if (ret < 0)
+		return ret;
+
+	/* Reset the position, drop any leftovers and indicate to start over */
+	s->read_pos = 0;
+	s->count = 0;
+	s->first = true;
+	return 0;
+}
+
+static int __irq_stats_open(struct inode *inode, struct file *filp, bool percpu)
+{
+	struct irq_proc_stat *s = kzalloc_obj(*s);
+
+	if (!s)
+		return -ENOMEM;
+
+	s->data	= kzalloc_flex(*s->data, pcpu, num_possible_cpus());
+	if (!s->data) {
+		kfree(s);
+		return -ENOMEM;
+	}
+
+	s->first = true;
+	s->percpu = percpu;
+	filp->private_data = s;
+	return 0;
+}
+
+static int irq_stats_open(struct inode *inode, struct file *filp)
+{
+	return __irq_stats_open(inode, filp, false);
+}
+
+static int irq_stats_release(struct inode *inode, struct file *filp)
+{
+	struct irq_proc_stat *s = filp->private_data;
+
+	if (s) {
+		kfree(s->data);
+		kfree(s);
+	}
+	return 0;
+}
+
+static const struct proc_ops irq_dev_stat_ops = {
+	.proc_flags	= PROC_ENTRY_PERMANENT,
+	.proc_open	= irq_stats_open,
+	.proc_release	= irq_stats_release,
+	.proc_read_iter	= irq_stats_read,
+	.proc_lseek	= irq_stats_llseek,
+};
+
+#ifdef CONFIG_GENERIC_IRQ_STATS_PERCPU
+static int irq_pcp_stats_open(struct inode *inode, struct file *filp)
+{
+	return __irq_stats_open(inode, filp, true);
+}
+
+static const struct proc_ops irq_pcp_stat_ops = {
+	.proc_flags	= PROC_ENTRY_PERMANENT,
+	.proc_open	= irq_pcp_stats_open,
+	.proc_release	= irq_stats_release,
+	.proc_read_iter	= irq_stats_read,
+	.proc_lseek	= irq_stats_llseek,
+};
+
+static __init void irq_pcp_stats_init(void)
+{
+	proc_create("percpu_stats", 0, root_irq_dir, &irq_pcp_stat_ops);
+}
+#else  /* CONFIG_GENERIC_IRQ_STATS_PERCPU */
+static inline void irq_pcp_stats_init(void) { }
+#endif /* !CONFIG_GENERIC_IRQ_STATS_PERCPU */
+
 static int __init irq_proc_init(void)
 {
 	proc_create_seq("interrupts", 0, NULL, &irq_seq_ops);
+	if (!root_irq_dir)
+		return 0;
+
+	proc_create("device_stats", 0, root_irq_dir, &irq_dev_stat_ops);
+	irq_pcp_stats_init();
 	return 0;
 }
 fs_initcall(irq_proc_init);


^ permalink raw reply	[flat|nested] 24+ messages in thread

* [patch 13/14] [RFC] genirq/proc: Provide architecture specific binary statistics
  2026-03-04 18:55 [patch 00/14] genirq: Improve /proc/interrupts for real and add a binary interface Thomas Gleixner
                   ` (11 preceding siblings ...)
  2026-03-04 18:56 ` [patch 12/14] [RFC] genirq/proc: Provide binary statistic interface Thomas Gleixner
@ 2026-03-04 18:56 ` Thomas Gleixner
  2026-03-04 18:56 ` [patch 14/14] [RFC] x86/irq: Hook up architecture specific stats Thomas Gleixner
  13 siblings, 0 replies; 24+ messages in thread
From: Thomas Gleixner @ 2026-03-04 18:56 UTC (permalink / raw)
  To: LKML; +Cc: x86, Dmitry Ilvokhin, Neil Horman

Provide a binary statistics interface similar to the per device and per CPU
interfaces to access the architecture specific interrupt statistics.

The architecture has to select it in Kconfig and provide an accessor to the
per CPU interrupt information and the number of architecture specific
entries.

The entries are ordered by a numerical index starting from 0, which
corresponds to the ordering of those interrupts in /proc/interrupt. The
output format is the same as for the per device and per CPU interfaces and
only contains entries which have an interrupt count > 0.

Reading the architecture specific counters of a 256 CPU x86 system takes
36us kernel time for 6 interrupts with non-zero counts and produces about
10k of data.

Signed-off-by: Thomas Gleixner <tglx@kernel.org>
---
 kernel/irq/Kconfig |    3 ++
 kernel/irq/proc.c  |   56 +++++++++++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 59 insertions(+)

--- a/kernel/irq/Kconfig
+++ b/kernel/irq/Kconfig
@@ -18,6 +18,9 @@ config GENERIC_IRQ_SHOW
 config GENERIC_IRQ_SHOW_LEVEL
        bool
 
+config GENERIC_IRQ_STATS_ARCH
+       bool
+
 config GENERIC_IRQ_STATS_PERCPU
        bool
 
--- a/kernel/irq/proc.c
+++ b/kernel/irq/proc.c
@@ -945,6 +945,61 @@ static __init void irq_pcp_stats_init(vo
 static inline void irq_pcp_stats_init(void) { }
 #endif /* !CONFIG_GENERIC_IRQ_STATS_PERCPU */
 
+#ifdef CONFIG_GENERIC_IRQ_STATS_ARCH
+static inline void arch_stat_update_one(struct irq_proc_stat *s)
+{
+	struct irq_proc_stat_data *d = s->data;
+	unsigned int cpu, idx = s->irqnr;
+
+	for_each_online_cpu(cpu) {
+		struct irq_proc_stat_cpu pcpu = {
+			.cpu = cpu,
+			.cnt = arch_get_irq_stat(cpu, idx),
+		};
+
+		if (pcpu.cnt)
+			d->pcpu[d->entries++] = pcpu;
+	}
+
+	if (d->entries) {
+		d->irqnr = idx;
+		s->count = sizeof(*d) + d->entries * sizeof(*d->pcpu);
+	}
+}
+
+static __always_inline bool arch_stat_next_data(struct irq_proc_stat *s)
+{
+	if (unlikely(s->first)) {
+		s->irqnr = 0;
+		s->first = false;
+	}
+
+	for(; !s->count && s->irqnr < ARCH_IRQ_STATS_NUM_IRQS; s->irqnr++)
+		arch_stat_update_one(s);
+	return !!s->count;
+}
+
+static ssize_t irq_arch_stats_read(struct kiocb *iocb, struct iov_iter *iter)
+{
+	return __irq_stats_read(iocb, iter, arch_stat_next_data);
+}
+
+static const struct proc_ops irq_arch_stat_ops = {
+	.proc_flags	= PROC_ENTRY_PERMANENT,
+	.proc_open	= irq_stats_open,
+	.proc_release	= irq_stats_release,
+	.proc_read_iter	= irq_arch_stats_read,
+	.proc_lseek	= irq_stats_llseek,
+};
+
+static __init void irq_arch_stats_init(void)
+{
+	proc_create("arch_stats", 0, root_irq_dir, &irq_arch_stat_ops);
+}
+#else  /* CONFIG_GENERIC_IRQ_STATS_ARCH */
+static inline void irq_arch_stats_init(void) { }
+#endif /* !CONFIG_GENERIC_IRQ_STATS_ARCH */
+
 static int __init irq_proc_init(void)
 {
 	proc_create_seq("interrupts", 0, NULL, &irq_seq_ops);
@@ -953,6 +1008,7 @@ static int __init irq_proc_init(void)
 
 	proc_create("device_stats", 0, root_irq_dir, &irq_dev_stat_ops);
 	irq_pcp_stats_init();
+	irq_arch_stats_init();
 	return 0;
 }
 fs_initcall(irq_proc_init);


^ permalink raw reply	[flat|nested] 24+ messages in thread

* [patch 14/14] [RFC] x86/irq: Hook up architecture specific stats
  2026-03-04 18:55 [patch 00/14] genirq: Improve /proc/interrupts for real and add a binary interface Thomas Gleixner
                   ` (12 preceding siblings ...)
  2026-03-04 18:56 ` [patch 13/14] [RFC] genirq/proc: Provide architecture specific binary statistics Thomas Gleixner
@ 2026-03-04 18:56 ` Thomas Gleixner
  13 siblings, 0 replies; 24+ messages in thread
From: Thomas Gleixner @ 2026-03-04 18:56 UTC (permalink / raw)
  To: LKML; +Cc: x86, Dmitry Ilvokhin, Neil Horman

Enable the binary statistics interface for architecture specific
interrupts.

Signed-off-by: Thomas Gleixner <tglx@kernel.org>
---
 arch/x86/Kconfig               |    1 +
 arch/x86/include/asm/hardirq.h |    7 +++++++
 2 files changed, 8 insertions(+)

--- a/arch/x86/Kconfig
+++ b/arch/x86/Kconfig
@@ -177,6 +177,7 @@ config X86
 	select GENERIC_IRQ_PROBE
 	select GENERIC_IRQ_RESERVATION_MODE
 	select GENERIC_IRQ_SHOW
+	select GENERIC_IRQ_STATS_ARCH
 	select GENERIC_PENDING_IRQ		if SMP
 	select GENERIC_SMP_IDLE_THREAD
 	select GENERIC_TIME_VSYSCALL
--- a/arch/x86/include/asm/hardirq.h
+++ b/arch/x86/include/asm/hardirq.h
@@ -65,7 +65,14 @@ DECLARE_PER_CPU_SHARED_ALIGNED(irq_cpust
 #ifdef CONFIG_X86_POSTED_MSI
 DECLARE_PER_CPU_ALIGNED(struct pi_desc, posted_msi_pi_desc);
 #endif
+
 #define __ARCH_IRQ_STAT
+#define ARCH_IRQ_STATS_NUM_IRQS		IRQ_COUNT_MAX
+
+static inline unsigned int arch_get_irq_stat(unsigned int cpu, unsigned int idx)
+{
+	return data_race(per_cpu_ptr(&irq_stat, cpu)->counts[idx]);
+}
 
 #define inc_irq_stat(index)	this_cpu_inc(irq_stat.counts[IRQ_COUNT_##index])
 


^ permalink raw reply	[flat|nested] 24+ messages in thread

* RE: [patch 04/14] x86/irq: Make irqstats array based
  2026-03-04 18:55 ` [patch 04/14] x86/irq: Make irqstats array based Thomas Gleixner
@ 2026-03-04 22:18   ` Michael Kelley
  2026-03-05 15:52     ` Thomas Gleixner
  2026-03-09 18:12   ` Dmitry Ilvokhin
  1 sibling, 1 reply; 24+ messages in thread
From: Michael Kelley @ 2026-03-04 22:18 UTC (permalink / raw)
  To: Thomas Gleixner, LKML; +Cc: x86@kernel.org, Dmitry Ilvokhin, Neil Horman

From: Thomas Gleixner <tglx@kernel.org> Sent: Wednesday, March 4, 2026 10:56 AM
> 
> 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 <tglx@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      |   69 ++++++----
>  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               |  227 ++++++++++--------------------------
>  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, 135 insertions(+), 232 deletions(-)
> 

[snip]

> --- a/arch/x86/kernel/cpu/mshyperv.c
> +++ b/arch/x86/kernel/cpu/mshyperv.c
> @@ -154,7 +154,7 @@ DEFINE_IDTENTRY_SYSVEC(sysvec_hyperv_cal
>  {
>  	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();
> 
> @@ -191,7 +191,7 @@ DEFINE_IDTENTRY_SYSVEC(sysvec_hyperv_sti
>  {
>  	struct pt_regs *old_regs = set_irq_regs(regs);
> 
> -	inc_irq_stat(hyperv_stimer0_count);
> +	inc_irq_stat(HYPERV_STIMER0)

Missing semicolon at the end of this line causes a compile error.

>  	if (hv_stimer0_handler)
>  		hv_stimer0_handler();
>  	add_interrupt_randomness(HYPERV_STIMER0_VECTOR);
> --- a/arch/x86/kernel/irq.c
> +++ b/arch/x86/kernel/irq.c
> @@ -62,156 +62,84 @@ 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;
> +struct irq_stat_info {
> +	unsigned int	test_vector;
> +	const char	*symbol;
> +	const char	*text;
> +};
> 
> -	seq_put_decimal_ull_width(p, delimiter, num, width);
> -}
> +#define ISS(idx, sym, txt) [IRQ_COUNT_##idx] = { .symbol = sym, .text = txt }
> 
> -#define irq_stats(x)		(&per_cpu(irq_stat, x))
> -/*
> - * /proc/interrupts printing for arch specific interrupts
> - */
> -int arch_show_interrupts(struct seq_file *p, int prec)
> -{
> -	int j;
> +#define ITS(idx, sym, txt) [IRQ_COUNT_##idx] =				\
> +	{ .test_vector = idx## _VECTOR, .symbol = sym, .text = txt }
> 
> -	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");
> +static const struct irq_stat_info irq_stat_info[IRQ_COUNT_MAX] = {
> +	ISS(NMI,			"NMI", " 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");
> -
> -	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");
> -	}
> +	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
> -	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");
> +	ISS(RESCHEDULE,			"RES", " Rescheduling interrupts\n"),
> +	ISS(CALL_FUNCTION,		"CAL", " Function call interrupts\n"),
> +	ISS(TLB,			"TLB", " 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");
> +	ISS(THERMAL_APIC,		"TRM", " Thermal event interrupt\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");
> +	ISS(THRESHOLD_APIC,		"THR", " 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");
> +	ISS(DEFERRED_ERROR,		"DFR", " 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");
> +	ISS(MCE_EXCEPTION,		"MCE", " Machine check exceptions\n"),
> +	ISS(MCE_POLL,			"MCP", " 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");
> -	}
> +	ITS(HYPERVISOR_CALLBACK,	"HYP", " 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));
> +	ITS(HYPERV_REENLIGHTMENT,	"HRE", " Hyper-V reenlightment interrupts\n"),

s/HYPERV_REENLIGHTMENT/HYPERV_REENLIGHTENMENT/   (causes compile error)
s/reenlightment/reenlightenment/

> +	ITS(HYPERV_STIMER0,		"HVS", " Hyper-V stimer0 interrupts\n"),
>  #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");
> +	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
> -	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");
> +	ISS(PERF_GUEST_MEDIATED_PMI,	"VPMI", " 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");
> +	ISS(POSTED_MSI_NOTIFICATION,	"PMN", " Posted MSI notification event\n"),
>  #endif
> +};

^ permalink raw reply	[flat|nested] 24+ messages in thread

* RE: [patch 04/14] x86/irq: Make irqstats array based
  2026-03-04 22:18   ` Michael Kelley
@ 2026-03-05 15:52     ` Thomas Gleixner
  0 siblings, 0 replies; 24+ messages in thread
From: Thomas Gleixner @ 2026-03-05 15:52 UTC (permalink / raw)
  To: Michael Kelley, LKML; +Cc: x86@kernel.org, Dmitry Ilvokhin, Neil Horman

On Wed, Mar 04 2026 at 22:18, Michael Kelley wrote:
> From: Thomas Gleixner <tglx@kernel.org> Sent: Wednesday, March 4, 2026 10:56 AM
>> -	inc_irq_stat(hyperv_stimer0_count);
>> +	inc_irq_stat(HYPERV_STIMER0)
>
> Missing semicolon at the end of this line causes a compile error.

>> -	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));
>> +	ITS(HYPERV_REENLIGHTMENT,	"HRE", " Hyper-V reenlightment interrupts\n"),
>
> s/HYPERV_REENLIGHTMENT/HYPERV_REENLIGHTENMENT/   (causes compile error)
> s/reenlightment/reenlightenment/

Grmbl. Why did neither me nor the robots catch that before I posted?

^ permalink raw reply	[flat|nested] 24+ messages in thread

* Re: [patch 02/14] genirq/proc: Avoid formatting zero counts in /proc/interrupts
  2026-03-04 18:55 ` [patch 02/14] genirq/proc: Avoid formatting zero counts in /proc/interrupts Thomas Gleixner
@ 2026-03-09 15:59   ` Dmitry Ilvokhin
  0 siblings, 0 replies; 24+ messages in thread
From: Dmitry Ilvokhin @ 2026-03-09 15:59 UTC (permalink / raw)
  To: Thomas Gleixner; +Cc: LKML, x86, Neil Horman

On Wed, Mar 04, 2026 at 07:55:36PM +0100, Thomas Gleixner wrote:
> 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 <tglx@kernel.org>
> ---
>  include/linux/interrupt.h |    1 +
>  kernel/irq/proc.c         |   42 +++++++++++++++++++++++++++++++++++++-----
>  2 files changed, 38 insertions(+), 5 deletions(-)
> 
> --- 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);
> --- a/kernel/irq/proc.c
> +++ b/kernel/irq/proc.c
> @@ -450,6 +450,42 @@ int __weak arch_show_interrupts(struct s
>  # define ACTUAL_NR_IRQS irq_get_nr_irqs()
>  #endif
>  
> +#define ZSTR1 "          0"
> +#define ZSTR1_LEN	11

Nit: ZSTR1_LEN could be derived from ZSTR1 to avoid a silent mismatch
if the string is ever changed (which is unlikely, but still).

#define ZSTR1_LEN	(sizeof(ZSTR1) - 1)

Reviewed-by: Dmitry Ilvokhin <d@ilvokhin.com>

^ permalink raw reply	[flat|nested] 24+ messages in thread

* Re: [patch 03/14] genirq/proc: Utilize irq_desc::tot_count to avoid evaluation
  2026-03-04 18:55 ` [patch 03/14] genirq/proc: Utilize irq_desc::tot_count to avoid evaluation Thomas Gleixner
@ 2026-03-09 16:04   ` Dmitry Ilvokhin
  0 siblings, 0 replies; 24+ messages in thread
From: Dmitry Ilvokhin @ 2026-03-09 16:04 UTC (permalink / raw)
  To: Thomas Gleixner; +Cc: LKML, x86, Neil Horman

On Wed, Mar 04, 2026 at 07:55:40PM +0100, Thomas Gleixner wrote:
> 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 <tglx@kernel.org>

Reviewed-by: Dmitry Ilvokhin <d@ilvokhin.com>

^ permalink raw reply	[flat|nested] 24+ messages in thread

* Re: [patch 04/14] x86/irq: Make irqstats array based
  2026-03-04 18:55 ` [patch 04/14] x86/irq: Make irqstats array based Thomas Gleixner
  2026-03-04 22:18   ` Michael Kelley
@ 2026-03-09 18:12   ` Dmitry Ilvokhin
  2026-03-10 10:15     ` Thomas Gleixner
  1 sibling, 1 reply; 24+ messages in thread
From: Dmitry Ilvokhin @ 2026-03-09 18:12 UTC (permalink / raw)
  To: Thomas Gleixner; +Cc: LKML, x86, Neil Horman

On Wed, Mar 04, 2026 at 07:55:45PM +0100, Thomas Gleixner wrote:
> 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 <tglx@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      |   69 ++++++----
>  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               |  227 ++++++++++--------------------------
>  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, 135 insertions(+), 232 deletions(-)
> 

[...]

> -	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");
> -	}
> +	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"),

The old code only showed "PLT" when x86_platform_ipi_callback was set,
but with ISS() this is now unconditional.

Is this intentional?

[...]

> -	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));
> +	ITS(HYPERV_REENLIGHTMENT,	"HRE", " Hyper-V reenlightment interrupts\n"),

HYPERV_REENLIGHTMENT doesn't match the enum (HYPERV_REENLIGHTENMENT).
This should break the build with CONFIG_HYPERV=y. There is also a same
typo in text description.

> +	seq_printf(p, "ERR: %10u\n", (unsigned int) atomic_read(&irq_err_count));
> +	if (IS_ENABLED(CONFIG_X86_IO_APIC))
> +		seq_printf(p, "MIS: %10u\n", (unsigned int) atomic_read(&irq_mis_count));

This drops the prec-based alignment for ERR and MIS.

^ permalink raw reply	[flat|nested] 24+ messages in thread

* Re: [patch 05/14] genirq: Expose nr_irqs in core code
  2026-03-04 18:55 ` [patch 05/14] genirq: Expose nr_irqs in core code Thomas Gleixner
@ 2026-03-09 18:26   ` Dmitry Ilvokhin
  0 siblings, 0 replies; 24+ messages in thread
From: Dmitry Ilvokhin @ 2026-03-09 18:26 UTC (permalink / raw)
  To: Thomas Gleixner; +Cc: LKML, x86, Neil Horman

On Wed, Mar 04, 2026 at 07:55:49PM +0100, Thomas Gleixner wrote:
> ... to avoid function calls in the core code to retrieve the maximum number
> of interrupts.
> 
> Signed-off-by: Thomas Gleixner <tglx@kernel.org>

Reviewed-by: Dmitry Ilvokhin <d@ilvokhin.com>

^ permalink raw reply	[flat|nested] 24+ messages in thread

* Re: [patch 04/14] x86/irq: Make irqstats array based
  2026-03-09 18:12   ` Dmitry Ilvokhin
@ 2026-03-10 10:15     ` Thomas Gleixner
  0 siblings, 0 replies; 24+ messages in thread
From: Thomas Gleixner @ 2026-03-10 10:15 UTC (permalink / raw)
  To: Dmitry Ilvokhin; +Cc: LKML, x86, Neil Horman

On Mon, Mar 09 2026 at 18:12, Dmitry Ilvokhin wrote:
> On Wed, Mar 04, 2026 at 07:55:45PM +0100, Thomas Gleixner wrote:
>> +	ISS(ICR_READ_RETRY,		"RTR", " APIC ICR read retries\n"),
>> +	ISS(X86_PLATFORM_IPI,		"PLT", " Platform interrupts\n"),
>
> The old code only showed "PLT" when x86_platform_ipi_callback was set,
> but with ISS() this is now unconditional.
>
> Is this intentional?

Yes because I didn't want to add yet another conditional of dubious value.

> [...]
>
>> -	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));
>> +	ITS(HYPERV_REENLIGHTMENT,	"HRE", " Hyper-V reenlightment interrupts\n"),
>
> HYPERV_REENLIGHTMENT doesn't match the enum (HYPERV_REENLIGHTENMENT).
> This should break the build with CONFIG_HYPERV=y. There is also a same
> typo in text description.

Michael pointed that out already. Fixed locally.

>> +	seq_printf(p, "ERR: %10u\n", (unsigned int) atomic_read(&irq_err_count));
>> +	if (IS_ENABLED(CONFIG_X86_IO_APIC))
>> +		seq_printf(p, "MIS: %10u\n", (unsigned int) atomic_read(&irq_mis_count));
>
> This drops the prec-based alignment for ERR and MIS.

Indeed.

^ permalink raw reply	[flat|nested] 24+ messages in thread

* Re: [patch 06/14] genirq: Cache the condition for /proc/interrupts exposure
  2026-03-04 18:55 ` [patch 06/14] genirq: Cache the condition for /proc/interrupts exposure Thomas Gleixner
@ 2026-03-16 18:46   ` Dmitry Ilvokhin
  0 siblings, 0 replies; 24+ messages in thread
From: Dmitry Ilvokhin @ 2026-03-16 18:46 UTC (permalink / raw)
  To: Thomas Gleixner; +Cc: LKML, x86, Neil Horman

On Wed, Mar 04, 2026 at 07:55:53PM +0100, Thomas Gleixner wrote:
> show_interrupts() evaluates a boatload of conditions to establish whether
> exposing 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.
> 
> As a result the number of instructions and branches for reading
> /proc/interrupts is reduced significantly.
> 
> Signed-off-by: Thomas Gleixner <tglx@kernel.org>
> ---
>  include/linux/irq.h    |    1 +
>  kernel/irq/chip.c      |    2 ++
>  kernel/irq/internals.h |    2 ++
>  kernel/irq/manage.c    |    2 ++
>  kernel/irq/proc.c      |   16 ++++++++++++----
>  kernel/irq/settings.h  |   14 ++++++++++++++
>  6 files changed, 33 insertions(+), 4 deletions(-)
> 
> --- a/include/linux/irq.h
> +++ b/include/linux/irq.h
> @@ -99,6 +99,7 @@ enum {
>  	IRQ_DISABLE_UNLAZY	= (1 << 19),
>  	IRQ_HIDDEN		= (1 << 20),
>  	IRQ_NO_DEBUG		= (1 << 21),
> +	IRQF_RESERVED		= (1 << 22),
>  };

nit: IRQF_ prefix doesn't match the IRQ_ convention used by the other
entries in the enum.

[...]

> --- a/kernel/irq/settings.h
> +++ b/kernel/irq/settings.h
> @@ -37,6 +37,9 @@ enum {
>  #undef IRQF_MODIFY_MASK
>  #define IRQF_MODIFY_MASK	GOT_YOU_MORON
>  
> +#define _IRQ_PROC_VALID		IRQF_RESERVED
> +#undef IRQF_RESERVED

The #undef IRQF_RESERVED is a no-op since IRQF_RESERVED is an enum
constant, not a macro. Probably, need to follow the same pattern as
values above to shadow with GOT_YOU_MORON.

Other than that:

Reviewed-by: Dmitry Ilvokhin <d@ilvokhin.com>

^ permalink raw reply	[flat|nested] 24+ messages in thread

* Re: [patch 07/14] genirq: Calculate precision only when required
  2026-03-04 18:55 ` [patch 07/14] genirq: Calculate precision only when required Thomas Gleixner
@ 2026-03-16 18:57   ` Dmitry Ilvokhin
  0 siblings, 0 replies; 24+ messages in thread
From: Dmitry Ilvokhin @ 2026-03-16 18:57 UTC (permalink / raw)
  To: Thomas Gleixner; +Cc: LKML, x86, Neil Horman

On Wed, Mar 04, 2026 at 07:55:58PM +0100, Thomas Gleixner wrote:
> 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 <tglx@kernel.org>

Reviewed-by: Dmitry Ilvokhin <d@ilvokhin.com>

^ permalink raw reply	[flat|nested] 24+ messages in thread

end of thread, other threads:[~2026-03-16 18:57 UTC | newest]

Thread overview: 24+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-03-04 18:55 [patch 00/14] genirq: Improve /proc/interrupts for real and add a binary interface Thomas Gleixner
2026-03-04 18:55 ` [patch 01/14] x86/irq: Optimize interrupts decimals printing Thomas Gleixner
2026-03-04 18:55 ` [patch 02/14] genirq/proc: Avoid formatting zero counts in /proc/interrupts Thomas Gleixner
2026-03-09 15:59   ` Dmitry Ilvokhin
2026-03-04 18:55 ` [patch 03/14] genirq/proc: Utilize irq_desc::tot_count to avoid evaluation Thomas Gleixner
2026-03-09 16:04   ` Dmitry Ilvokhin
2026-03-04 18:55 ` [patch 04/14] x86/irq: Make irqstats array based Thomas Gleixner
2026-03-04 22:18   ` Michael Kelley
2026-03-05 15:52     ` Thomas Gleixner
2026-03-09 18:12   ` Dmitry Ilvokhin
2026-03-10 10:15     ` Thomas Gleixner
2026-03-04 18:55 ` [patch 05/14] genirq: Expose nr_irqs in core code Thomas Gleixner
2026-03-09 18:26   ` Dmitry Ilvokhin
2026-03-04 18:55 ` [patch 06/14] genirq: Cache the condition for /proc/interrupts exposure Thomas Gleixner
2026-03-16 18:46   ` Dmitry Ilvokhin
2026-03-04 18:55 ` [patch 07/14] genirq: Calculate precision only when required Thomas Gleixner
2026-03-16 18:57   ` Dmitry Ilvokhin
2026-03-04 18:56 ` [patch 08/14] genirq: Add rcuref count to struct irq_desc Thomas Gleixner
2026-03-04 18:56 ` [patch 09/14] genirq: Expose irq_find_desc_at_or_after() in core code Thomas Gleixner
2026-03-04 18:56 ` [patch 10/14] genirq/proc: Speed up /proc/interrupts iteration Thomas Gleixner
2026-03-04 18:56 ` [patch 11/14] [RFC] genirq: Cache target CPU for single CPU affinities Thomas Gleixner
2026-03-04 18:56 ` [patch 12/14] [RFC] genirq/proc: Provide binary statistic interface Thomas Gleixner
2026-03-04 18:56 ` [patch 13/14] [RFC] genirq/proc: Provide architecture specific binary statistics Thomas Gleixner
2026-03-04 18:56 ` [patch 14/14] [RFC] x86/irq: Hook up architecture specific stats Thomas Gleixner

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox