mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
* [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®