[PATCH RFC] genirq/chip: Add guard_chained_irq() cleanup helper

From: Peng Fan (OSS)

Date: Mon Oct 12 2026 - 22:04:26 EST


From: Peng Fan <peng.fan@xxxxxxx>

Chained interrupt handlers bracket their work with a
chained_irq_enter() / chained_irq_exit() pair. Because the exit call
has to be repeated on every return path of the handler, it is easy to
forget one when a new early-return is added, leaving the parent chip
masked or un-EOIed.

Provide a scope-based cleanup helper built on the DEFINE_CLASS()
infrastructure:

guard_chained_irq(chip, desc);

invokes chained_irq_enter() immediately and arranges for the matching
chained_irq_exit() to run automatically when the enclosing function
(or compound statement) scope ends, no matter which return path is
taken. This removes the need to spell out the exit call by hand.

The chained_irq_enter() call and the context struct are built directly
in the DEFINE_CLASS() constructor expression, so no separate helper
function is required.

Signed-off-by: Peng Fan <peng.fan@xxxxxxx>
---
Chained interrupt handlers bracket their work with a
chained_irq_enter()/chained_irq_exit() pair. The exit call must appear
on every return path, which is error-prone when early returns are added.

This patch adds a cleanup helper, guard_chained_irq(),
that calls chained_irq_enter() on construction and chained_irq_exit()
automatically at scope exit,

I not include scoped_guard helper in the patch, because almost all
chain_irq_exit() are put at end of a function, so guard helper is enough.

I have a local check that there are 38 places under drivers/irqchip/
could be converted to use guard_chained_irq() and a few goto-exit label
could also be dropped.

If this patch is welcomed, I will convert users under drivers/irqchip/ to
use it and post a formal patchset.

No functional changes.
---
include/linux/irqchip/chained_irq.h | 36 ++++++++++++++++++++++++++++++++++++
1 file changed, 36 insertions(+)

diff --git a/include/linux/irqchip/chained_irq.h b/include/linux/irqchip/chained_irq.h
index dd8b3c4766666..6a1a8e88c940e 100644
--- a/include/linux/irqchip/chained_irq.h
+++ b/include/linux/irqchip/chained_irq.h
@@ -7,6 +7,7 @@
#ifndef __IRQCHIP_CHAINED_IRQ_H
#define __IRQCHIP_CHAINED_IRQ_H

+#include <linux/cleanup.h>
#include <linux/irq.h>

/*
@@ -38,4 +39,39 @@ static inline void chained_irq_exit(struct irq_chip *chip,
chip->irq_unmask(&desc->irq_data);
}

+/*
+ * Scope-based variant of the chained_irq_enter()/chained_irq_exit() pair.
+ *
+ * guard_chained_irq() invokes chained_irq_enter() immediately and arranges
+ * for the matching chained_irq_exit() to run automatically when the
+ * enclosing function (or compound statement) scope ends, no matter which
+ * return path is taken. This removes the need to spell out chained_irq_exit()
+ * on every exit path of a chained handler.
+ *
+ * Usage:
+ *
+ * static void foo_irq_handler(struct irq_desc *desc)
+ * {
+ * struct irq_chip *chip = irq_desc_get_chip(desc);
+ *
+ * guard_chained_irq(chip, desc);
+ * ... handle the interrupt, return freely ...
+ * }
+ */
+
+/* Internal context for guard_chained_irq(); not for driver use. */
+struct __chained_irq_guard_ctx {
+ struct irq_chip *chip;
+ struct irq_desc *desc;
+};
+
+DEFINE_CLASS(chained_irq, struct __chained_irq_guard_ctx,
+ chained_irq_exit(_T.chip, _T.desc),
+ ({ struct __chained_irq_guard_ctx _T = { .chip = chip, .desc = desc };
+ chained_irq_enter(_T.chip, _T.desc); _T; }),
+ struct irq_chip *chip, struct irq_desc *desc)
+
+#define guard_chained_irq(chip, desc) \
+ guard(chained_irq)(chip, desc)
+
#endif /* __IRQCHIP_CHAINED_IRQ_H */

---
base-commit: becb95e45ef0dda9ffb35156d909d3da7a2789c3
change-id: 20261012-guard_chain_irq-92a89dbf5a19

Best regards,
--
Peng Fan <peng.fan@xxxxxxx>