* [RFC PATCH] ARM: v7m: entry: Convert IRQ handling to generic IRQ entry
@ 2026-10-20 13:50 Yoshifumi SHU
0 siblings, 0 replies; only message in thread
From: Yoshifumi SHU @ 2026-10-20 13:50 UTC (permalink / raw)
To: Linus Walleij, Russell King
Cc: Arnd Bergmann, Thomas Gleixner, Paul E . McKenney, Jinjie Ruan,
Oleg Nesterov, Hajime Tazaki, linux-arm-kernel, linux-kernel,
Yoshifumi SHU
The recent patch converting ARM to generic IRQ entry [1] leaves the V7M
path using architecture-specific entry due to its distinct exception
model. This RFC attempts to complete that conversion for V7M.
On the V7M architecture, the execution priority cannot be lowered
without an exception return. Therefore, an IRQ handler cannot directly
perform a context switch. irqentry_exit() may schedule, so it cannot run
entirely in the IRQ handler. This is uncommon in other architectures.
The existing V7M code defers exit work to a PendSV (Pendable Service
Call) handler, which runs at the lowest priority (matching the kernel's
priority). Furthermore, kernel preemption on exit to kernel mode is
unsupported; PendSV runs at the kernel's priority and does not preempt
the kernel.
Convert the V7M IRQ path to use generic IRQ entry while deferring exit
work to PendSV:
- IRQ from user mode: if exit work is pending, defer the exit to user
mode to the PendSV handler. Otherwise, exit directly in the IRQ
handler to avoid an unnecessary PendSV exception.
- IRQ from kernel mode: perform generic exit handling directly in the
IRQ handler, with rescheduling suppressed by
arch_irqentry_exit_need_resched().
- IRQ taken while exit to user mode is deferred: handle it as a nested
IRQ, i.e., as an IRQ from kernel mode.
Use the separate irqentry_{enter_from,exit_to}_user_mode() and
irqentry_{enter_from,exit_to}_kernel_mode() for these paths.
This keeps the existing V7M scheduling behavior while using generic IRQ
entry.
Tested on MPS3-AN547 (Cortex-M55) and EK-RA8P1 (Cortex-M85) hardware.
RSEQ selftests pass.
Link: https://lore.kernel.org/linux-arm-kernel/20260910-arm-generic-irq-entry-v7-1-v3-1-5fd90f6859e1@kernel.org/ # [1]
Signed-off-by: Yoshifumi SHU <envzhu@gmail.com>
---
This applies on top of v7.3-rc1 with [1] applied.
To avoid unnecessary PendSV exceptions, the bits 0..15 in the thread
information flag are checked to judge whether work won't be triggered.
But irqentry_exit_to_user_mode() actually checks
EXIT_TO_USER_MODE_WORK_IRQ, which is slightly different.
We have to be careful with this check because a judgment mismatch of
whether work is needed can lead to performing a schedule in the IRQ
handler, which is unacceptable here.
I believe checking bits 0..15 is preferable to directly seeing
EXIT_TO_USER_MODE_WORK_IRQ because:
- EXIT_TO_USER_MODE_WORK_IRQ seems not to be intended to be inspected
outside irqentry_exit(). No other architecture (needs to) sees this
flag. The macro was introduced recently (February 2026).
- Bits 0..15 cover EXIT_TO_USER_MODE_WORK_IRQ.
- The existing IRQ entry and ret_fast_syscall also check bits 0..15.
Modification candidates as separate series, found while testing. I can
submit these after this RFC discussion:
- A fix for the NVIC driver: The interrupt controller driver (irq-nvic.c)
sets incorrect registers for priority configurations (the actual
accessed address of NVIC_IPR is wrong). This can lead to nested IRQs in
some environments, which is not assumed in the current V7M
implementation.
- On V7M, ARM_cpsr holds xPSR, a different meaning from that of
Cortex-A. Specifically, user_mode() and regs_irqs_disabled() may be
changed according to V7M semantics. Currently, both give the right
answer as long as IRQs do not nest.
I have tested this RFC patch on MPS3-AN547 (Cortex-M55@32MHz) and
EK-RA8P1 (Cortex-M85@1GHz) hardware.
Also, I applied some additional patches:
- kselftests support on nommu recently submitted [2]
- BSP support for MPS3-AN547 and EK-RA8P1
- AT_RSEQ_FEATURE_SIZE and AT_RSEQ_ALIGN in the FDPIC ELF loader
- The fix for the NVIC driver mentioned above
RSEQ basic_test and param_test kselftests pass on both boards. Without
this RFC patch, under the same test setup, param_test with delay
injection fails on both boards (e.g., "param_test -T l -t 6 -r 1000 -4
10000").
For brief evaluation, the overhead is evaluated by manually
instrumenting the code.
To evaluate the entry part of the IRQ handler, v7m_irq_handler is
measured with irq_{enter,exit_rcu}() and handle_arch_irq() excluded.
For comparison, its counterparts are measured for [1] (before applying
this patch). Mean cycles over 5,000 samples are:
before after
MPS3-AN547 kernel idle loop 225.2 259.1 +15%
(32 MHz) user loop 146.5 106.9 -27%
user loop + 1 ms SIGALRM 125.0 94.2 -25%
EK-RA8P1 kernel idle loop 1230.0 1822.6 +48%
(1 GHz) user loop 657.2 473.2 -28%
user loop + 1 ms SIGALRM 703.9 555.1 -21%
"kernel idle loop" runs "sleep 10" repeatedly and is expected to take
IRQs mostly from kernel mode. "user loop" runs a busy loop in user space
and takes IRQs mostly from user mode. "user loop + 1ms SIGALRM" takes
IRQs mostly from user mode with exit work triggered.
"kernel idle loop" (IRQs from kernel mode) shows overhead increase while
the others (IRQs from user mode) show decreases.
I have not yet investigated the reasons for these performance
differences. But I believe they are negligible since the entry part,
measured above, is less than 3% of the entire IRQ handler, including
irq_{enter,exit_rcu}() and handle_arch_irq() (measured on EK-RA8P1).
LLM tools were used for discussion, testing, evaluation, and reviewing.
The code was written by me.
Link: https://lore.kernel.org/all/20261006085642.3852152-1-thehajime@gmail.com/ # [2]
Link: https://lore.kernel.org/all/CAD++jL=VJsnk-GzAvR4oR8oKJayQzq9Oes+ZW7K=iHGrQ1jh0w@mail.gmail.com/
Link: https://lore.kernel.org/linux-arm-kernel/ac3e3fce-b27a-4d17-9616-48a70ddaf28b@gmail.com/
---
arch/arm/include/asm/entry-common.h | 9 ++++
arch/arm/include/asm/entry.h | 4 ++
arch/arm/kernel/entry-v7m.S | 49 ++++----------------
arch/arm/kernel/entry.c | 72 +++++++++++++++++++++++++++++
4 files changed, 94 insertions(+), 40 deletions(-)
diff --git a/arch/arm/include/asm/entry-common.h b/arch/arm/include/asm/entry-common.h
index c017df5f39d5..35a4905e7aca 100644
--- a/arch/arm/include/asm/entry-common.h
+++ b/arch/arm/include/asm/entry-common.h
@@ -4,4 +4,13 @@
#include <asm/stacktrace.h>
+#ifdef CONFIG_CPU_V7M
+/* V7M never reschedules on exit to kernel mode */
+static inline bool arch_irqentry_exit_need_resched(void)
+{
+ return false;
+}
+#define arch_irqentry_exit_need_resched arch_irqentry_exit_need_resched
+#endif
+
#endif
diff --git a/arch/arm/include/asm/entry.h b/arch/arm/include/asm/entry.h
index 4a28e54b5bf3..a4a44bda7e99 100644
--- a/arch/arm/include/asm/entry.h
+++ b/arch/arm/include/asm/entry.h
@@ -8,5 +8,9 @@ void arm_rseq_user_enter(void);
void arm_rseq_user_exit(void);
void arm_irq_handler(struct pt_regs *regs, int mode);
void arm_fiq_handler(struct pt_regs *regs);
+#ifdef CONFIG_CPU_V7M
+void v7m_irq_handler(struct pt_regs *regs);
+void v7m_pendsv_handler(struct pt_regs *regs);
+#endif
#endif /* __ASM_ENTRY_H__ */
diff --git a/arch/arm/kernel/entry-v7m.S b/arch/arm/kernel/entry-v7m.S
index 49a3a34e2913..0d6f2eb99ca7 100644
--- a/arch/arm/kernel/entry-v7m.S
+++ b/arch/arm/kernel/entry-v7m.S
@@ -39,41 +39,11 @@ __irq_entry:
@
@ Invoke the IRQ handler
@
- mov r0, sp
- ldr_this_cpu sp, irq_stack_ptr, r1, r2
-
- @
- @ If we took the interrupt while running in the kernel, we may already
- @ be using the IRQ stack, so revert to the original value in that case.
- @
- subs r2, sp, r0 @ SP above bottom of IRQ stack?
- rsbscs r2, r2, #THREAD_SIZE @ ... and below the top?
- movcs sp, r0
-
- push {r0, lr} @ preserve LR and original SP
-
- @ routine called with r0 = struct pt_regs *
- bl generic_handle_arch_irq
-
+ mov r0, sp @ regs
+ push {r0, lr} @ preserve LR with 8-byte stack alignment
+ bl v7m_irq_handler
pop {r0, lr}
- mov sp, r0
-
- @
- @ Check for any pending work if returning to user
- @
- ldr r1, =BASEADDR_V7M_SCB
- ldr r0, [r1, V7M_SCB_ICSR]
- tst r0, V7M_SCB_ICSR_RETTOBASE
- beq 2f
-
- get_thread_info tsk
- ldr r2, [tsk, #TI_FLAGS]
- movs r2, r2, lsl #16
- beq 2f @ no work pending
- mov r0, #V7M_SCB_ICSR_PENDSVSET
- str r0, [r1, V7M_SCB_ICSR] @ raise PendSV
-2:
@ registers r0-r3 and r12 are automatically restored on exception
@ return. r4-r7 were not clobbered in v7m_exception_entry so for
@ correctness they don't need to be restored. So only r8-r11 must be
@@ -87,14 +57,13 @@ ENDPROC(__irq_entry)
__pendsv_entry:
v7m_exception_entry
- ldr r1, =BASEADDR_V7M_SCB
- mov r0, #V7M_SCB_ICSR_PENDSVCLR
- str r0, [r1, V7M_SCB_ICSR] @ clear PendSV
+ mov r0, sp @ regs
+ bl v7m_pendsv_handler
- @ execute the pending work, including reschedule
- get_thread_info tsk
- mov why, #0
- b v7m_ret_to_user_from_irq
+#ifdef CONFIG_KSTACK_ERASE
+ bl stackleak_erase_on_task_stack
+#endif
+ restore_user_regs fast = 0, offset = 0
ENDPROC(__pendsv_entry)
/*
diff --git a/arch/arm/kernel/entry.c b/arch/arm/kernel/entry.c
index 42d9301bdf37..988555eaaf07 100644
--- a/arch/arm/kernel/entry.c
+++ b/arch/arm/kernel/entry.c
@@ -6,6 +6,9 @@
#include <asm/entry.h>
#include <asm/stacktrace.h>
#include <asm/traps.h>
+#ifdef CONFIG_CPU_V7M
+#include <asm/v7m.h>
+#endif
#include "irq.h"
@@ -59,3 +62,72 @@ noinstr void arm_fiq_handler(struct pt_regs *regs)
irqentry_nmi_exit(regs, state);
}
+
+#ifdef CONFIG_CPU_V7M
+/*
+ * On V7M architecture, the execution priority cannot be lowered without an
+ * exception return. Therefore, an IRQ handler cannot directly perform a
+ * context switch. To fit generic IRQ entry into this hardware constraint:
+ *
+ * - IRQ from user mode: if exit work is pending, defer the exit to the PendSV
+ * handler. PendSV has the kernel's priority (that of SVCall), below all
+ * IRQs, so the PendSV handler runs after all pending IRQs. Otherwise exit to
+ * user mode directly.
+ * - IRQ from kernel mode: exit directly with rescheduling suppressed using
+ * arch_irqentry_exit_need_resched(). PendSV cannot preempt the kernel, so a
+ * reschedule cannot be deferred to the PendSV handler.
+ * - IRQ taken while an exit to user mode is deferred (i.e., while PendSV is
+ * pending): handle it as an IRQ from kernel mode. This IRQ is logically
+ * nested from an irqentry_{enter,exit}() perspective (see
+ * Documentation/core-api/entry.rst), even if its stacked frame is the user
+ * frame (tail-chaining, or late arrival).
+ */
+noinstr void v7m_irq_handler(struct pt_regs *regs)
+{
+ if (!user_mode(regs) || (readl_relaxed(BASEADDR_V7M_SCB + V7M_SCB_ICSR) &
+ V7M_SCB_ICSR_PENDSVSET)) {
+ /* IRQ from kernel mode, or IRQ taken while PendSV is pending */
+ irqentry_state_t state = irqentry_enter_from_kernel_mode(regs);
+
+ if (on_thread_stack())
+ call_on_irq_stack(handle_arm_irq, regs);
+ else
+ handle_arm_irq(regs);
+
+ /* arch_irqentry_exit_need_resched() prevents rescheduling here */
+ irqentry_exit_to_kernel_mode(regs, state);
+ } else {
+ irqentry_enter_from_user_mode(regs);
+
+ call_on_irq_stack(handle_arm_irq, regs);
+
+ /*
+ * Check TIF bits in 0..15 to judge whether exit work will be
+ * triggered, as tested in ret_fast_syscall.
+ */
+ if (read_thread_flags() & GENMASK(15, 0)) {
+ /*
+ * Raise PendSV exception to defer the exit to user
+ * mode to the PendSV handler.
+ */
+ writel_relaxed(V7M_SCB_ICSR_PENDSVSET, BASEADDR_V7M_SCB +
+ V7M_SCB_ICSR);
+ } else {
+ /*
+ * Fast path: Directly perform exit in the IRQ handler.
+ * Safe because work won't be triggered.
+ */
+ irqentry_exit_to_user_mode(regs);
+ }
+ }
+}
+
+noinstr void v7m_pendsv_handler(struct pt_regs *regs)
+{
+ /* Clear PendSV pending state */
+ writel_relaxed(V7M_SCB_ICSR_PENDSVCLR, BASEADDR_V7M_SCB + V7M_SCB_ICSR);
+
+ /* Call exit and perform the deferred exit work */
+ irqentry_exit_to_user_mode(regs);
+}
+#endif
base-commit: cee9395acd8043be0644b25c34bfa86623f2b935
prerequisite-patch-id: 664e34728c0570f7f3602a381394fef3da5dc6d5
--
2.53.0
^ permalink raw reply [flat|nested] only message in thread
only message in thread, other threads:[~2026-10-20 13:50 UTC | newest]
Thread overview: (only message) (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-10-20 13:50 [RFC PATCH] ARM: v7m: entry: Convert IRQ handling to generic IRQ entry Yoshifumi SHU
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox
all inboxes | Powered by JetHome®