kernel/irq/proc.c | 24 +++++++++++++++++------- 1 file changed, 17 insertions(+), 7 deletions(-)
irq_seq_show() must be idempotent since it may be called multiple times
to render the same record, e.g. when seq_file's buffer is exceeded. It
emits the CPU header behind the one-shot print_header flag, which the
first pass clears, so the header is lost with the discarded buffer.
This can be seen on large systems, such as 8g.48xl (192 CPUs) and
8i.96xl (384 CPUs) AWS instance types:
$ head -1 /proc/interrupts
9: 0 0 ... 0 GICv3 25 Level vgic
Replace print_header with the position of the first record and include
the header whenever that record is emitted.
Fixes: 171cc0d9eed1 ("genirq/proc: Speed up /proc/interrupts iteration")
Signed-off-by: Haris Okanovic <harisokn@amazon.com>
---
kernel/irq/proc.c | 24 +++++++++++++++++-------
1 file changed, 17 insertions(+), 7 deletions(-)
diff --git a/kernel/irq/proc.c b/kernel/irq/proc.c
index 1b835725f7b1c..0c0df16de5816 100644
--- a/kernel/irq/proc.c
+++ b/kernel/irq/proc.c
@@ -462,7 +462,7 @@ int __weak arch_show_interrupts(struct seq_file *p, int prec)
static DEFINE_RAW_SPINLOCK(irq_proc_constraints_lock);
static struct irq_proc_constraints {
- bool print_header;
+ loff_t first_pos;
unsigned int num_prec;
unsigned int chip_width;
} irq_proc_constraints __read_mostly = {
@@ -543,14 +543,13 @@ static int irq_seq_show(struct seq_file *p, void *v)
struct irqaction *action;
/* Print header for the first interrupt? */
- if (constr->print_header) {
+ if (p->index == constr->first_pos) {
unsigned int cpu;
seq_printf(p, "%*s", constr->num_prec + 8, "");
for_each_online_cpu(cpu)
seq_printf(p, "CPU%-8d", cpu);
seq_putc(p, '\n');
- constr->print_header = false;
}
if (desc == ARCH_PROC_IRQDESC)
@@ -635,14 +634,25 @@ static void *irq_seq_next_desc(loff_t *pos)
static void *irq_seq_start(struct seq_file *f, loff_t *pos)
{
- if (!*pos) {
- struct irq_proc_constraints *constr = f->private;
+ struct irq_proc_constraints *constr = f->private;
+ bool first = !*pos;
+ void *ret;
+ if (first) {
constr->num_prec = READ_ONCE(irq_proc_constraints.num_prec);
constr->chip_width = READ_ONCE(irq_proc_constraints.chip_width);
- constr->print_header = true;
}
- return irq_seq_next_desc(pos);
+
+ ret = irq_seq_next_desc(pos);
+
+ /*
+ * Record position of the first output record so that irq_seq_show()
+ * knows where to write the header.
+ */
+ if (first)
+ constr->first_pos = *pos;
+
+ return ret;
}
static void *irq_seq_next(struct seq_file *f, void *v, loff_t *pos)
---
base-commit: 08df884136f1c1197bab2a27814404fd329d9aac
change-id: 20260911-dev-harisokn-proc-interrupts-fix-b4-7498f7e18db5
Best regards,
--
Haris Okanovic
AWS Graviton
On Fri, 2026-09-11 at 13:31 -0500, Haris Okanovic wrote:
> irq_seq_show() must be idempotent since it may be called multiple times
> to render the same record, e.g. when seq_file's buffer is exceeded. It
> emits the CPU header behind the one-shot print_header flag, which the
> first pass clears, so the header is lost with the discarded buffer.
>
> This can be seen on large systems, such as 8g.48xl (192 CPUs) and
> 8i.96xl (384 CPUs) AWS instance types:
>
> $ head -1 /proc/interrupts
> 9: 0 0 ... 0 GICv3 25 Level vgic
>
> Replace print_header with the position of the first record and include
> the header whenever that record is emitted.
>
> Fixes: 171cc0d9eed1 ("genirq/proc: Speed up /proc/interrupts iteration")
> Signed-off-by: Haris Okanovic <harisokn@amazon.com>
> ---
> kernel/irq/proc.c | 24 +++++++++++++++++-------
> 1 file changed, 17 insertions(+), 7 deletions(-)
>
> diff --git a/kernel/irq/proc.c b/kernel/irq/proc.c
> index 1b835725f7b1c..0c0df16de5816 100644
> --- a/kernel/irq/proc.c
> +++ b/kernel/irq/proc.c
> @@ -462,7 +462,7 @@ int __weak arch_show_interrupts(struct seq_file *p, int prec)
> static DEFINE_RAW_SPINLOCK(irq_proc_constraints_lock);
>
> static struct irq_proc_constraints {
> - bool print_header;
> + loff_t first_pos;
> unsigned int num_prec;
> unsigned int chip_width;
> } irq_proc_constraints __read_mostly = {
> @@ -543,14 +543,13 @@ static int irq_seq_show(struct seq_file *p, void *v)
> struct irqaction *action;
>
> /* Print header for the first interrupt? */
> - if (constr->print_header) {
> + if (p->index == constr->first_pos) {
> unsigned int cpu;
>
> seq_printf(p, "%*s", constr->num_prec + 8, "");
> for_each_online_cpu(cpu)
> seq_printf(p, "CPU%-8d", cpu);
> seq_putc(p, '\n');
> - constr->print_header = false;
> }
>
> if (desc == ARCH_PROC_IRQDESC)
> @@ -635,14 +634,25 @@ static void *irq_seq_next_desc(loff_t *pos)
>
> static void *irq_seq_start(struct seq_file *f, loff_t *pos)
> {
> - if (!*pos) {
> - struct irq_proc_constraints *constr = f->private;
> + struct irq_proc_constraints *constr = f->private;
> + bool first = !*pos;
> + void *ret;
>
> + if (first) {
> constr->num_prec = READ_ONCE(irq_proc_constraints.num_prec);
> constr->chip_width = READ_ONCE(irq_proc_constraints.chip_width);
> - constr->print_header = true;
> }
> - return irq_seq_next_desc(pos);
> +
> + ret = irq_seq_next_desc(pos);
> +
> + /*
> + * Record position of the first output record so that irq_seq_show()
> + * knows where to write the header.
> + */
> + if (first)
> + constr->first_pos = *pos;
> +
> + return ret;
> }
>
> static void *irq_seq_next(struct seq_file *f, void *v, loff_t *pos)
>
> ---
> base-commit: 08df884136f1c1197bab2a27814404fd329d9aac
> change-id: 20260911-dev-harisokn-proc-interrupts-fix-b4-7498f7e18db5
Since I don't have access to a machine with so many CPUs, I tested
locally in a regular VM and simulated the large number of CPUs by
adding artificial buffer writes to both the header part and the IRQ
counter part of irq_seq_show(). I could reproduce the problem and
validate the fix. Also, semantically the fix looks good to me.
Thanks for catching this and for the patch!
Reviewed-by: Radu Rendec <radu@rendec.net>
On Thu, 2026-09-17 at 22:55 -0400, Radu Rendec wrote: > Since I don't have access to a machine with so many CPUs, I tested > locally in a regular VM and simulated the large number of CPUs by > adding artificial buffer writes to both the header part and the IRQ > counter part of irq_seq_show(). I could reproduce the problem and > validate the fix. Also, semantically the fix looks good to me. > Thanks for catching this and for the patch! Thanks for looking at this, and for finding a way to reproduce it without the hardware. If a large machine would help in the future, let me know off-list and I'll see what I can arrange. -- Haris Okanovic AWS Graviton
Gentle ping on this one. Any concerns with the approach? We'd like to get this in before more distros pick up 171cc0d9eed1. Losing the CPU header is a user-visible format change that breaks tools parsing /proc/interrupts, e.g. aperf: https://github.com/aws/aperf Happy to respin if you'd prefer a different fix. Regards, -- Haris Okanovic AWS Graviton
© 2016 - 2026 Red Hat, Inc.