* [PATCH v3 00/21] arm64: Preemptible this_cpu_*() operations
@ 2026-09-04 16:17 Mark Rutland
2026-09-04 16:17 ` [PATCH v3 03/21] arm64: percpu: Fix LSE operations on {8,16}-bit types Mark Rutland
` (20 more replies)
0 siblings, 21 replies; 23+ messages in thread
From: Mark Rutland @ 2026-09-04 16:17 UTC (permalink / raw)
To: linux-arm-kernel
Cc: mark.rutland, vladimir.murzin, ryan.roberts, usama.anjum, peterz,
catalin.marinas, david.laight.linux, stable, ruanjinjie,
james.morse, yang, cl, maz, david, ljs, will, ardb
This series reworks arm64's this_cpu_*() operations such that they do
not need to disable preemption. The scheme is described in detail in patch 13,
and is similar to the approach Heiko Carstens applied to s390 [1], which
inspired this series.
This scheme should build and function in all kernel configurations (e.g.
regardless of KPTI, SW PAN, CNP, VA BITS), and has no dependency on new
architectural features. The fixup logic should "just work" with kprobes,
etc, and I don't expect that this will need to become more complicated.
The patches are organised as follows:
* Patches 1 to 3 are preparatory fixes for latent issues which were
found by inspection. These will need to be backported to stable, and
have appropriate Fixes tags.
* Patches 4 to 7 are preparatory improvements to code generation issues
found by inspection during development. These aren't strictly related
to the PCPU GPR scheme, and it would make sense to queue these even if
we don't go ahead with the rest of the series.
* Patches 8 to 13 are preparatory work for the PCPU GPR scheme.
* Patch 14 implements the core of the PCPU GPR scheme, with all the
necessary exception handling logic, and the addition of helpers to
begin/end a PCPU GPR critical section.
* Patches 15 to 20 convert this_cpu_*() operations over to the PCPU GPR
scheme. These changes have been made over several patches to aid
review and bisection (if necessary).
* Patch 21 removes code made redundant by earlier patches.
I've given this build-testing (with GCC and clang) and some light boot
testing.
Since v1 [2]:
* Implement necessary SDEI entry logic.
* Drop RFC tag, as I believe this is now functionally complete.
* Add patches to fix latent bugs and poor codegen (per David, and myself)
* Make gpr-num handling consistent with asm-extable.h
* Assert PCPU GPRs are distinct at compile time (per Vladimir)
* Add comments to document PCPU GPR helpers.
* Improve commit messages.
* Add tags from v1.
Since v2 [3]:
* Rebase to v7.3-rc1.
* Fix SDEI register restoration.
* Avoid -Woverflow warnings.
* Add preparatory fix for {8,16}-bit types.
* Add tags from v2.
Thanks,
Mark.
[1] https://lore.kernel.org/linux-s390/20260526055702.1429061-1-hca@linux.ibm.com/
[2] https://lore.kernel.org/linux-arm-kernel/20260728123859.2911495-1-mark.rutland@arm.com/
[3] https://lore.kernel.org/linux-arm-kernel/20260804170503.3513916-1-mark.rutland@arm.com/
Mark Rutland (21):
arm64: percpu: Fix this_cpu_write() casting
arm64: percpu: Fix this_cpu_and() mask generation
arm64: percpu: Fix LSE operations on {8,16}-bit types
arm64: cmpxchg: LL/SC: Avoid redundant extension
arm64: cmpxchg128: LSE: Remove redundant operands
arm64: preempt: Simplify and optimize __preempt_count_dec_and_test()
arm64: preempt: Treat should_resched() as unlikely
arm64: ptrace: Always inline pt_regs_[read,write}_reg()
arm64: percpu: Factor out percpu offset asm
arm64: gpr-num: Add wxN aliases for wN registers
arm64: gpr-num: add __GPR_NUM() helper
arm64: entry: sdei: Restore all clobberable GPRs
arm64: entry: sdei: Make 'tsk' available
arm64: percpu: Add infrastructure for preemptible this_cpu_*() ops
arm64: percpu: Implement preemptible read/write ops
arm64: percpu: Implement preemptible void RMW ops
arm64: percpu: Implement preemptible return RMW ops
arm64: percpu: Implement preemptible XCHG ops
arm64: percpu: Implement preemptible CMPXCHG ops
arm64: percpu: Implement preemptible CMPXCHG128 ops
arm64: percpu: Remove _pcp_protect*() wrappers
arch/arm64/include/asm/atomic_ll_sc.h | 27 +-
arch/arm64/include/asm/atomic_lse.h | 4 +-
arch/arm64/include/asm/gpr-num.h | 9 +
arch/arm64/include/asm/percpu.h | 462 ++++++++++++++++++++------
arch/arm64/include/asm/preempt.h | 29 +-
arch/arm64/include/asm/ptrace.h | 12 +-
arch/arm64/include/asm/thread_info.h | 1 +
arch/arm64/include/asm/xwreg.h | 16 +
arch/arm64/kernel/asm-offsets.c | 2 +
arch/arm64/kernel/entry-common.c | 38 +++
arch/arm64/kernel/entry.S | 41 ++-
11 files changed, 497 insertions(+), 144 deletions(-)
create mode 100644 arch/arm64/include/asm/xwreg.h
--
2.30.2
^ permalink raw reply [flat|nested] 23+ messages in thread
* [PATCH v3 03/21] arm64: percpu: Fix LSE operations on {8,16}-bit types
2026-09-04 16:17 [PATCH v3 00/21] arm64: Preemptible this_cpu_*() operations Mark Rutland
@ 2026-09-04 16:17 ` Mark Rutland
2026-09-04 16:17 ` [PATCH v3 05/21] arm64: cmpxchg128: LSE: Remove redundant operands Mark Rutland
` (19 subsequent siblings)
20 siblings, 0 replies; 23+ messages in thread
From: Mark Rutland @ 2026-09-04 16:17 UTC (permalink / raw)
To: linux-arm-kernel
Cc: mark.rutland, vladimir.murzin, usama.anjum, peterz,
catalin.marinas, david.laight.linux, stable, ruanjinjie,
james.morse, yang, cl, maz, will, ardb
The assembly for __percpu_##name##_case_##sz() and
__percpu_##name##_return_case_##sz() doesn't use the 'sfx' macro
argument to form the LSE instruction. Without 'sfx', a W register
argument will imply a 32-bit memory location, and consequently
{8,16}-bit ops will erroneously read and write 32 bits of memory when
the LSE instruction is used.
Fix this by appending 'sfx' to 'op_lse' to LSE instruction. It is not
necessary (and not valid) to append 'sfx' to 'op_llsc', as 'op_llsc' is
a register-register operation which does not access memory (and does not
take a size suffix).
Test case:
| void outline_this_cpu_add_u8(u8 __percpu *p, u8 v)
| {
| this_cpu_add(*p, v);
| }
Generated inline code (same before and after this patch):
| <outline_this_cpu_add_u8>:
| paciasp
| stp x29, x30, [sp, #-16]!
| mrs x2, sp_el0
| mov x29, sp
| ldr w3, [x2, #8]
| add w3, w3, #0x1
| str w3, [x2, #8]
| mrs x3, tpidr_el1
| add x0, x0, x3
| 1: ldxrb w5, [x0] // <----------------
| add w5, w5, w1 // LL/SC alternative
| stxrb w4, w5, [x0] // instructions here
| cbnz w4, 1b // <----------------
| ldr x0, [x2, #8]
| sub x0, x0, #0x1
| str w0, [x2, #8]
| cbz x0, 2f
| ldr x0, [x2, #8]
| cbnz x0, 3f
| 2: bl preempt_schedule_notrace
| 3: ldp x29, x30, [sp], #16
| autiasp
| ret
Generated LSE alternative before this patch:
| ldadd w1, w5, [x0] // 32-bit LDADD
| nop
| nop
| nop
Generated LSE alternative after this patch:
| ldaddb w1, w5, [x0] // 8-bit LDADDB
| nop
| nop
| nop
Fixes: 959bf2fd03b5 ("arm64: percpu: Rewrite per-cpu ops to allow use of LSE atomics")
Signed-off-by: Mark Rutland <mark.rutland@arm.com>
Cc: Ada Couprie Diaz <ada.coupriediaz@arm.com>
Cc: Ard Biesheuvel <ardb@kernel.org>
Cc: Catalin Marinas <catalin.marinas@arm.com>
Cc: James Morse <james.morse@arm.com>
Cc: Jinjie Ruan <ruanjinjie@huawei.com>
Cc: Marc Zyngier <maz@kernel.org>
Cc: Peter Zijlstra <peterz@infradead.org>
Cc: Vladimir Murzin <vladimir.murzin@arm.com>
Cc: Will Deacon <will@kernel.org>
Cc: Yang Shi <yang@os.amperecomputing.com>
Cc: stable@vger.kernel.org
---
arch/arm64/include/asm/percpu.h | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/arch/arm64/include/asm/percpu.h b/arch/arm64/include/asm/percpu.h
index 31193bcf89a2b..8cf4068ce1b56 100644
--- a/arch/arm64/include/asm/percpu.h
+++ b/arch/arm64/include/asm/percpu.h
@@ -77,7 +77,7 @@ __percpu_##name##_case_##sz(void *ptr, unsigned long val) \
" stxr" #sfx "\t%w[loop], %" #w "[tmp], %[ptr]\n" \
" cbnz %w[loop], 1b", \
/* LSE atomics */ \
- #op_lse "\t%" #w "[val], %" #w "[tmp], %[ptr]\n" \
+ #op_lse #sfx "\t%" #w "[val], %" #w "[tmp], %[ptr]\n" \
__nops(3)) \
: [loop] "=&r" (loop), [tmp] "=&r" (tmp), \
[ptr] "+Q"(*(u##sz *)ptr) \
@@ -98,7 +98,7 @@ __percpu_##name##_return_case_##sz(void *ptr, unsigned long val) \
" stxr" #sfx "\t%w[loop], %" #w "[ret], %[ptr]\n" \
" cbnz %w[loop], 1b", \
/* LSE atomics */ \
- #op_lse "\t%" #w "[val], %" #w "[ret], %[ptr]\n" \
+ #op_lse #sfx "\t%" #w "[val], %" #w "[ret], %[ptr]\n" \
#op_llsc "\t%" #w "[ret], %" #w "[ret], %" #w "[val]\n" \
__nops(2)) \
: [loop] "=&r" (loop), [ret] "=&r" (ret), \
--
2.30.2
^ permalink raw reply related [flat|nested] 23+ messages in thread
* [PATCH v3 05/21] arm64: cmpxchg128: LSE: Remove redundant operands
2026-09-04 16:17 [PATCH v3 00/21] arm64: Preemptible this_cpu_*() operations Mark Rutland
2026-09-04 16:17 ` [PATCH v3 03/21] arm64: percpu: Fix LSE operations on {8,16}-bit types Mark Rutland
@ 2026-09-04 16:17 ` Mark Rutland
2026-09-04 16:17 ` [PATCH v3 07/21] arm64: preempt: Treat should_resched() as unlikely Mark Rutland
` (18 subsequent siblings)
20 siblings, 0 replies; 23+ messages in thread
From: Mark Rutland @ 2026-09-04 16:17 UTC (permalink / raw)
To: linux-arm-kernel
Cc: mark.rutland, vladimir.murzin, usama.anjum, peterz,
catalin.marinas, david.laight.linux, stable, ruanjinjie,
james.morse, yang, cl, maz, will, ardb
The LSE assembly for cmpxhg128*() has redundant operands which cause
unnecessary register pressure. These operands can be removed without
adverse effects, as described below.
It is necessary to force specific registers for the 64-bit halves of
'old' and 'new', as the encoding of CASP[A][L] requires that these
halves are allocated into even/odd register pairs, and contemporary
versions of LLVM don't support a mechanism to allocate a value into an
even/odd register pair for inline assembly. We manually allocate these
into x0/x1/x2/x3.
It is not necessary to force the address into a specific register, as
the encoding of CASP[A][L] can accept this in any GPR or SP. Hence, it
is not necessary to manually allocate the address into x4 for the
'[ptr]' operand. The assembly uses the '[v]' operand, and contemporary
compilers happen to allocate '[v]' into a separate register from
'[ptr]', meaning that '[ptr]' only serves to create register pressure.
It is not necessary to allocate the '[oldval1]' and '[oldval2]'
operands, as these are not used by the assembly. The assembly uses the
'[old1]' and '[old2]' operands for both input and output. The
'[oldval1]' and '[oldval2]' operands only serve to create register
pressure.
Remove the redundant operands. This saves on register pressure, as
demonstrated by the test case below. There's still some unfortunate
register shuffling due to the manual allocation of 'old' and 'new', but
this should be less prominent within a larger function.
Test case:
| u128 outline_cmpxchg128(u128 *p, u128 old, u128 new)
| {
| return cmpxchg128(p, old, new);
| }
Generated code before this patch:
| <outline_cmpxchg128>:
| mov x6, x0
| mov x1, x3
| mov x0, x2
| b 1f
| mov x2, x4
| mov x3, x5
| mov x4, x6
| mov x5, x0
| mov x7, x1
| caspal x0, x1, x2, x3, [x6]
| ret
| 1: prfm pstl1strm, [x6]
| 2: ldxp x8, x7, [x6]
| cmp x8, x0
| ccmp x7, x3, #0x0, eq
| b.ne 3f
| stlxp w2, x4, x5, [x6]
| cbnz w2, 2b
| dmb ish
| 3: mov x0, x8
| mov x1, x7
| ret
Generated code after this patch:
| <outline_cmpxchg128>:
| mov x6, x0
| mov x0, x2
| b 1f
| mov x1, x3
| mov x2, x4
| mov x3, x5
| caspal x0, x1, x2, x3, [x6]
| ret
| 1: prfm pstl1strm, [x6]
| 2: ldxp x8, x7, [x6]
| cmp x8, x0
| ccmp x7, x3, #0x0, eq
| 3f
| stlxp w2, x4, x5, [x6]
| cbnz w2, 2b
| dmb ish
| 3: mov x0, x8
| mov x1, x7
| ret
Signed-off-by: Mark Rutland <mark.rutland@arm.com>
Tested-by: Muhammad Usama Anjum <usama.anjum@arm.com>
Cc: Ada Couprie Diaz <ada.coupriediaz@arm.com>
Cc: Ard Biesheuvel <ardb@kernel.org>
Cc: Catalin Marinas <catalin.marinas@arm.com>
Cc: James Morse <james.morse@arm.com>
Cc: Jinjie Ruan <ruanjinjie@huawei.com>
Cc: Marc Zyngier <maz@kernel.org>
Cc: Peter Zijlstra <peterz@infradead.org>
Cc: Vladimir Murzin <vladimir.murzin@arm.com>
Cc: Will Deacon <will@kernel.org>
Cc: Yang Shi <yang@os.amperecomputing.com>
---
arch/arm64/include/asm/atomic_lse.h | 4 +---
1 file changed, 1 insertion(+), 3 deletions(-)
diff --git a/arch/arm64/include/asm/atomic_lse.h b/arch/arm64/include/asm/atomic_lse.h
index afad1849c4cf5..d588af0565331 100644
--- a/arch/arm64/include/asm/atomic_lse.h
+++ b/arch/arm64/include/asm/atomic_lse.h
@@ -291,15 +291,13 @@ __lse__cmpxchg128##name(volatile u128 *ptr, u128 old, u128 new) \
register unsigned long x1 asm ("x1") = o.high; \
register unsigned long x2 asm ("x2") = n.low; \
register unsigned long x3 asm ("x3") = n.high; \
- register unsigned long x4 asm ("x4") = (unsigned long)ptr; \
\
asm volatile( \
__LSE_PREAMBLE \
" casp" #mb "\t%[old1], %[old2], %[new1], %[new2], %[v]\n"\
: [old1] "+&r" (x0), [old2] "+&r" (x1), \
[v] "+Q" (*(u128 *)ptr) \
- : [new1] "r" (x2), [new2] "r" (x3), [ptr] "r" (x4), \
- [oldval1] "r" (o.low), [oldval2] "r" (o.high) \
+ : [new1] "r" (x2), [new2] "r" (x3) \
: cl); \
\
r.low = x0; r.high = x1; \
--
2.30.2
^ permalink raw reply related [flat|nested] 23+ messages in thread
* [PATCH v3 07/21] arm64: preempt: Treat should_resched() as unlikely
2026-09-04 16:17 [PATCH v3 00/21] arm64: Preemptible this_cpu_*() operations Mark Rutland
2026-09-04 16:17 ` [PATCH v3 03/21] arm64: percpu: Fix LSE operations on {8,16}-bit types Mark Rutland
2026-09-04 16:17 ` [PATCH v3 05/21] arm64: cmpxchg128: LSE: Remove redundant operands Mark Rutland
@ 2026-09-04 16:17 ` Mark Rutland
2026-09-04 16:17 ` [PATCH v3 08/21] arm64: ptrace: Always inline pt_regs_[read,write}_reg() Mark Rutland
` (17 subsequent siblings)
20 siblings, 0 replies; 23+ messages in thread
From: Mark Rutland @ 2026-09-04 16:17 UTC (permalink / raw)
To: linux-arm-kernel
Cc: mark.rutland, vladimir.murzin, usama.anjum, peterz,
catalin.marinas, david.laight.linux, stable, ruanjinjie,
james.morse, yang, cl, maz, will, ardb
The arm64 implementation of should_resched() doesn't treat the need to
resched as unlikely, while all other implementations do, e.g.
* asm-generic since:
bdb43806589096ac ("sched: Extract the basic add/sub preempt_count modifiers")
* x86 since:
c2daa3bed53a8117 ("sched, x86: Provide a per-cpu preempt_count implementation")
* s390 since:
c360192bf4a8dc72 ("s390/preempt: move preempt_count to the lowcore")
Do the same for arm64.
Signed-off-by: Mark Rutland <mark.rutland@arm.com>
Reviewed-by: Jinjie Ruan <ruanjinjie@huawei.com>
Tested-by: Muhammad Usama Anjum <usama.anjum@arm.com>
Cc: Ada Couprie Diaz <ada.coupriediaz@arm.com>
Cc: Ard Biesheuvel <ardb@kernel.org>
Cc: Catalin Marinas <catalin.marinas@arm.com>
Cc: James Morse <james.morse@arm.com>
Cc: Marc Zyngier <maz@kernel.org>
Cc: Peter Zijlstra <peterz@infradead.org>
Cc: Vladimir Murzin <vladimir.murzin@arm.com>
Cc: Will Deacon <will@kernel.org>
Cc: Yang Shi <yang@os.amperecomputing.com>
---
arch/arm64/include/asm/preempt.h | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/arch/arm64/include/asm/preempt.h b/arch/arm64/include/asm/preempt.h
index 92ae2eb95a738..0a99ef9b59609 100644
--- a/arch/arm64/include/asm/preempt.h
+++ b/arch/arm64/include/asm/preempt.h
@@ -78,7 +78,7 @@ static inline int __preempt_count_sub_return(int val)
static inline bool should_resched(int preempt_offset)
{
u64 pc = READ_ONCE(current_thread_info()->preempt_count);
- return pc == preempt_offset;
+ return unlikely(pc == preempt_offset);
}
static inline bool __preempt_count_dec_and_test(void)
--
2.30.2
^ permalink raw reply related [flat|nested] 23+ messages in thread
* [PATCH v3 08/21] arm64: ptrace: Always inline pt_regs_[read,write}_reg()
2026-09-04 16:17 [PATCH v3 00/21] arm64: Preemptible this_cpu_*() operations Mark Rutland
` (2 preceding siblings ...)
2026-09-04 16:17 ` [PATCH v3 07/21] arm64: preempt: Treat should_resched() as unlikely Mark Rutland
@ 2026-09-04 16:17 ` Mark Rutland
2026-09-04 16:17 ` [PATCH v3 09/21] arm64: percpu: Factor out percpu offset asm Mark Rutland
` (16 subsequent siblings)
20 siblings, 0 replies; 23+ messages in thread
From: Mark Rutland @ 2026-09-04 16:17 UTC (permalink / raw)
To: linux-arm-kernel
Cc: mark.rutland, vladimir.murzin, usama.anjum, peterz,
catalin.marinas, david.laight.linux, stable, ruanjinjie,
james.morse, yang, cl, maz, will, ardb
In subsequent patches we'll want to use pt_regs_read_reg() and
pt_regs_write_reg() in noinstr code.
To ensure noinstr safety, both functions must be inlined into their
caller. As both functions are marked as 'inline' rather than
'__always_inline', that's not strictly guaranteed, and the compiler is
permitted to generate out-of-line copies.
Mark both functions as '__always_inline' to ensure this.
There should be no functional change as a result of this patch.
Signed-off-by: Mark Rutland <mark.rutland@arm.com>
Reviewed-by: Jinjie Ruan <ruanjinjie@huawei.com>
Tested-by: Muhammad Usama Anjum <usama.anjum@arm.com>
Cc: Ada Couprie Diaz <ada.coupriediaz@arm.com>
Cc: Ard Biesheuvel <ardb@kernel.org>
Cc: Catalin Marinas <catalin.marinas@arm.com>
Cc: James Morse <james.morse@arm.com>
Cc: Marc Zyngier <maz@kernel.org>
Cc: Peter Zijlstra <peterz@infradead.org>
Cc: Vladimir Murzin <vladimir.murzin@arm.com>
Cc: Will Deacon <will@kernel.org>
Cc: Yang Shi <yang@os.amperecomputing.com>
---
arch/arm64/include/asm/ptrace.h | 7 ++++---
1 file changed, 4 insertions(+), 3 deletions(-)
diff --git a/arch/arm64/include/asm/ptrace.h b/arch/arm64/include/asm/ptrace.h
index f7dc5fb9427d6..f635c3453a858 100644
--- a/arch/arm64/include/asm/ptrace.h
+++ b/arch/arm64/include/asm/ptrace.h
@@ -266,7 +266,8 @@ static inline u64 regs_get_register(struct pt_regs *regs, unsigned int offset)
* Read a register given an architectural register index r.
* This handles the common case where 31 means XZR, not SP.
*/
-static inline unsigned long pt_regs_read_reg(const struct pt_regs *regs, int r)
+static __always_inline unsigned long
+pt_regs_read_reg(const struct pt_regs *regs, int r)
{
return (r == 31) ? 0 : regs->regs[r];
}
@@ -275,8 +276,8 @@ static inline unsigned long pt_regs_read_reg(const struct pt_regs *regs, int r)
* Write a register given an architectural register index r.
* This handles the common case where 31 means XZR, not SP.
*/
-static inline void pt_regs_write_reg(struct pt_regs *regs, int r,
- unsigned long val)
+static __always_inline void
+pt_regs_write_reg(struct pt_regs *regs, int r, unsigned long val)
{
if (r != 31)
regs->regs[r] = val;
--
2.30.2
^ permalink raw reply related [flat|nested] 23+ messages in thread
* [PATCH v3 09/21] arm64: percpu: Factor out percpu offset asm
2026-09-04 16:17 [PATCH v3 00/21] arm64: Preemptible this_cpu_*() operations Mark Rutland
` (3 preceding siblings ...)
2026-09-04 16:17 ` [PATCH v3 08/21] arm64: ptrace: Always inline pt_regs_[read,write}_reg() Mark Rutland
@ 2026-09-04 16:17 ` Mark Rutland
2026-09-04 16:17 ` [PATCH v3 10/21] arm64: gpr-num: Add wxN aliases for wN registers Mark Rutland
` (15 subsequent siblings)
20 siblings, 0 replies; 23+ messages in thread
From: Mark Rutland @ 2026-09-04 16:17 UTC (permalink / raw)
To: linux-arm-kernel
Cc: mark.rutland, vladimir.murzin, usama.anjum, peterz,
catalin.marinas, david.laight.linux, stable, ruanjinjie,
james.morse, yang, cl, maz, will, ardb
Depending on whether the kernel runs at EL1 or EL2, the percpu offset is
stored in either TPIDR_EL1 or TPIDR_EL2, and __kern_my_cpu_offset() uses
an ALTERNATIVE() sequence to read the relevant TPIDR_ELx.
In subsequent patches we'll need to read the percpu offset in other
assembly sequences. Factor the ALTERNATIVE sequence out of
__kern_my_cpu_offset() into a new __KERN_ASM_CPU_OFFSET() macro so that
we can share the code sequence.
There should be no functional change as a result of this patch.
Signed-off-by: Mark Rutland <mark.rutland@arm.com>
Reviewed-by: Jinjie Ruan <ruanjinjie@huawei.com>
Tested-by: Muhammad Usama Anjum <usama.anjum@arm.com>
Cc: Ada Couprie Diaz <ada.coupriediaz@arm.com>
Cc: Ard Biesheuvel <ardb@kernel.org>
Cc: Catalin Marinas <catalin.marinas@arm.com>
Cc: James Morse <james.morse@arm.com>
Cc: Marc Zyngier <maz@kernel.org>
Cc: Peter Zijlstra <peterz@infradead.org>
Cc: Vladimir Murzin <vladimir.murzin@arm.com>
Cc: Will Deacon <will@kernel.org>
Cc: Yang Shi <yang@os.amperecomputing.com>
---
arch/arm64/include/asm/percpu.h | 15 ++++++++++-----
1 file changed, 10 insertions(+), 5 deletions(-)
diff --git a/arch/arm64/include/asm/percpu.h b/arch/arm64/include/asm/percpu.h
index 8cf4068ce1b56..746d2a56f48bb 100644
--- a/arch/arm64/include/asm/percpu.h
+++ b/arch/arm64/include/asm/percpu.h
@@ -29,6 +29,11 @@ static inline unsigned long __hyp_my_cpu_offset(void)
return read_sysreg(tpidr_el2);
}
+#define __KERN_ASM_CPU_OFFSET(dst) \
+ ALTERNATIVE("mrs " dst ", tpidr_el1", \
+ "mrs " dst ", tpidr_el2", \
+ ARM64_HAS_VIRT_HOST_EXTN)
+
static inline unsigned long __kern_my_cpu_offset(void)
{
unsigned long off;
@@ -37,11 +42,11 @@ static inline unsigned long __kern_my_cpu_offset(void)
* We want to allow caching the value, so avoid using volatile and
* instead use a fake stack read to hazard against barrier().
*/
- asm(ALTERNATIVE("mrs %0, tpidr_el1",
- "mrs %0, tpidr_el2",
- ARM64_HAS_VIRT_HOST_EXTN)
- : "=r" (off) :
- "Q" (*(const unsigned long *)current_stack_pointer));
+ asm(
+ __KERN_ASM_CPU_OFFSET("%0")
+ : "=r" (off)
+ : "Q" (*(const unsigned long *)current_stack_pointer)
+ );
return off;
}
--
2.30.2
^ permalink raw reply related [flat|nested] 23+ messages in thread
* [PATCH v3 10/21] arm64: gpr-num: Add wxN aliases for wN registers
2026-09-04 16:17 [PATCH v3 00/21] arm64: Preemptible this_cpu_*() operations Mark Rutland
` (4 preceding siblings ...)
2026-09-04 16:17 ` [PATCH v3 09/21] arm64: percpu: Factor out percpu offset asm Mark Rutland
@ 2026-09-04 16:17 ` Mark Rutland
2026-09-04 16:17 ` [PATCH v3 12/21] arm64: entry: sdei: Restore all clobberable GPRs Mark Rutland
` (14 subsequent siblings)
20 siblings, 0 replies; 23+ messages in thread
From: Mark Rutland @ 2026-09-04 16:17 UTC (permalink / raw)
To: linux-arm-kernel
Cc: mark.rutland, vladimir.murzin, usama.anjum, peterz,
catalin.marinas, david.laight.linux, stable, ruanjinjie,
james.morse, yang, cl, maz, will, ardb
Some A64 instructions require a W register name for specific register
arguments, and will not accept an X register name. For example:
STRH <Wt>, [<Xn|SP>, (<Wm>|<Xm>){, <extend> {<amount>}}]
This is painful when writing macros for inline assembly, especially
where a single register is used as both an X register and a W register.
We solved this for plain assembly by adding a wxN alias for each xN
register in commit:
4c4dcd3541f83d21 ("arm64: assembler: introduce wxN aliases for wN registers")
Add the same facility for inline assembly, with a new
__DEFINE_ASM_GPR_ALIASES macro.
As with the other macros in gpr-num.h, different escaping is necessary
for inline vs non-inline assembly, so we can't share a single definition
that we stringify().
Signed-off-by: Mark Rutland <mark.rutland@arm.com>
Tested-by: Muhammad Usama Anjum <usama.anjum@arm.com>
Cc: Ada Couprie Diaz <ada.coupriediaz@arm.com>
Cc: Ard Biesheuvel <ardb@kernel.org>
Cc: Catalin Marinas <catalin.marinas@arm.com>
Cc: James Morse <james.morse@arm.com>
Cc: Jinjie Ruan <ruanjinjie@huawei.com>
Cc: Marc Zyngier <maz@kernel.org>
Cc: Peter Zijlstra <peterz@infradead.org>
Cc: Vladimir Murzin <vladimir.murzin@arm.com>
Cc: Will Deacon <will@kernel.org>
Cc: Yang Shi <yang@os.amperecomputing.com>
---
arch/arm64/include/asm/gpr-num.h | 5 +++++
1 file changed, 5 insertions(+)
diff --git a/arch/arm64/include/asm/gpr-num.h b/arch/arm64/include/asm/gpr-num.h
index a114e4f8209b0..240cd25d55c5a 100644
--- a/arch/arm64/include/asm/gpr-num.h
+++ b/arch/arm64/include/asm/gpr-num.h
@@ -21,6 +21,11 @@
" .equ .L__gpr_num_xzr, 31\n" \
" .equ .L__gpr_num_wzr, 31\n"
+#define __DEFINE_ASM_GPR_ALIASES \
+" .irp n,0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30\n" \
+" wx\\n .req w\\n\n" \
+" .endr\n"
+
#endif /* __ASSEMBLER__ */
#endif /* __ASM_GPR_NUM_H */
--
2.30.2
^ permalink raw reply related [flat|nested] 23+ messages in thread
* [PATCH v3 12/21] arm64: entry: sdei: Restore all clobberable GPRs
2026-09-04 16:17 [PATCH v3 00/21] arm64: Preemptible this_cpu_*() operations Mark Rutland
` (5 preceding siblings ...)
2026-09-04 16:17 ` [PATCH v3 10/21] arm64: gpr-num: Add wxN aliases for wN registers Mark Rutland
@ 2026-09-04 16:17 ` Mark Rutland
2026-09-04 16:17 ` [PATCH v3 14/21] arm64: percpu: Add infrastructure for preemptible this_cpu_*() ops Mark Rutland
` (13 subsequent siblings)
20 siblings, 0 replies; 23+ messages in thread
From: Mark Rutland @ 2026-09-04 16:17 UTC (permalink / raw)
To: linux-arm-kernel
Cc: mark.rutland, vladimir.murzin, usama.anjum, peterz,
catalin.marinas, david.laight.linux, stable, ruanjinjie,
james.morse, yang, cl, maz, will, ardb
Currently __sdei_asm_handler() avoids restoring x20 to x27, as these are
not be clobbered by existing code. This is correct today, but it makes
it awkward to rework __sdei_asm_handler(). Given the rarity of SDEI
events, restoring x20 to x27 should not have a measurable impact.
Restore x20 to x27 when returning from an SDEI event handler. This will
free up those GPRs for usage in __sdei_asm_handler() prior to return.
Signed-off-by: Mark Rutland <mark.rutland@arm.com>
Tested-by: Muhammad Usama Anjum <usama.anjum@arm.com>
Cc: Ada Couprie Diaz <ada.coupriediaz@arm.com>
Cc: Ard Biesheuvel <ardb@kernel.org>
Cc: Catalin Marinas <catalin.marinas@arm.com>
Cc: James Morse <james.morse@arm.com>
Cc: Jinjie Ruan <ruanjinjie@huawei.com>
Cc: Marc Zyngier <maz@kernel.org>
Cc: Peter Zijlstra <peterz@infradead.org>
Cc: Vladimir Murzin <vladimir.murzin@arm.com>
Cc: Will Deacon <will@kernel.org>
Cc: Yang Shi <yang@os.amperecomputing.com>
---
arch/arm64/kernel/entry.S | 8 ++++++--
1 file changed, 6 insertions(+), 2 deletions(-)
diff --git a/arch/arm64/kernel/entry.S b/arch/arm64/kernel/entry.S
index f63049ac32dc6..a6ec5c5a2d29c 100644
--- a/arch/arm64/kernel/entry.S
+++ b/arch/arm64/kernel/entry.S
@@ -1044,10 +1044,14 @@ SYM_CODE_START(__sdei_asm_handler)
bl __sdei_handler
msr sp_el0, x28
- /* restore regs >x17 that we clobbered */
+ /* restore regs >x17 that firmware won't restore */
mov x4, x19 // keep x4 for __sdei_asm_exit_trampoline
- ldp x28, x29, [x4, #SDEI_EVENT_INTREGS + 16 * 14]
ldp x18, x19, [x4, #SDEI_EVENT_INTREGS + 16 * 9]
+ ldp x20, x21, [x4, #SDEI_EVENT_INTREGS + 16 * 10]
+ ldp x22, x23, [x4, #SDEI_EVENT_INTREGS + 16 * 11]
+ ldp x24, x25, [x4, #SDEI_EVENT_INTREGS + 16 * 12]
+ ldp x26, x27, [x4, #SDEI_EVENT_INTREGS + 16 * 13]
+ ldp x28, x29, [x4, #SDEI_EVENT_INTREGS + 16 * 14]
ldp lr, x1, [x4, #SDEI_EVENT_INTREGS + S_LR]
mov sp, x1
--
2.30.2
^ permalink raw reply related [flat|nested] 23+ messages in thread
* [PATCH v3 14/21] arm64: percpu: Add infrastructure for preemptible this_cpu_*() ops
2026-09-04 16:17 [PATCH v3 00/21] arm64: Preemptible this_cpu_*() operations Mark Rutland
` (6 preceding siblings ...)
2026-09-04 16:17 ` [PATCH v3 12/21] arm64: entry: sdei: Restore all clobberable GPRs Mark Rutland
@ 2026-09-04 16:17 ` Mark Rutland
2026-09-04 16:17 ` [PATCH v3 15/21] arm64: percpu: Implement preemptible read/write ops Mark Rutland
` (12 subsequent siblings)
20 siblings, 0 replies; 23+ messages in thread
From: Mark Rutland @ 2026-09-04 16:17 UTC (permalink / raw)
To: linux-arm-kernel
Cc: mark.rutland, vladimir.murzin, usama.anjum, peterz,
catalin.marinas, david.laight.linux, stable, ruanjinjie,
james.morse, yang, cl, maz, will, ardb
Currently arm64's this_cpu_*() ops transiently disable preemption in
order to guarantee that the address generation and memory access(es)
occur on the same CPU.
Transiently disabling preemption can be expensive. When re-enabling
preemption it is necessary to make a conditional function call to
preempt_schedule[_notrace]() in order to handle the rare case that the
task needs to be rescheduled. The potential function call has a number
of negative effects on code generation (e.g. due to the need to create a
stack frame and spill registers), and the conditionality can result in
poor code generation and/or poor branch prediction.
This patch adds infrastructure for a scheme where this_cpu_*() ops do
not need to transiently disable preemption, avoiding the negative
impacts described above. Individual operations will be converted in
subsequent patches.
Each operation registers a critical section during which the exception
return code will adjust the offset and addresses if preemption occurs
mid-sequence. The critical section is registered/unregistered with a
small prologue and epilogue which encodes three distinct GPRRs (<pcp>,
<off>, <addr>) into a new thread_info::pcp_gprs field:
// Prologue. Enable fixups for <off> and <addr>.
mrs <tsk>, sp_el0
mov <tmp>, #__VAL_PCPU_GPRS(<pcp>, <off>, <addr>)
strh <tmp>, [<tsk>, #TSK_TI_PCPU_GPRS]
// Generate cpu-specific address
mrs <off>, TPIDR_ELx
add <addr>, <pcp>, <off>
// Perform access sequence
ldr <val>, [<addr>]
// Epilogue. Disable fixups
strh wzr, [<tsk>, #TSK_TI_PCPU_GPRS]
If an exception is taken from within the critical section, the exception
return code will adjust <off> to be the current CPU's offset, and will
adjust <addr> to be (<pcp> + <off>). Distinct registers are used for
<pcp>, <off>, and <addr>, so that the fixup can be applied safely at any
point during the critical section.
To ensure that this_cpu_*() operations within exception handlers work
correctly and do not corrupt state, thread_info::pcpu_gprs is saved
into a new pt_regs::pcpu_gprs field upon exception entry, and restored
upon exception return.
Looking at a simple this_cpu_operation:
| void outline_this_cpu_add_u64(u64 __percpu *p, u64 v)
| {
| this_cpu_add(*p, v);
| }
Atop v7.2-rc4, with GCC 15.2.0 and defconfig, this is compiled as:
| <outline_this_cpu_add_u64>:
| paciasp
| stp x29, x30, [sp, #-16]!
| mrs x2, sp_el0
| mov x29, sp
| ldr w3, [x2, #8]
| add w3, w3, #0x1
| str w3, [x2, #8]
| mrs x3, tpidr_el1
| add x0, x0, x3
| 1: ldxr x5, [x0]
| add x5, x5, x1
| stxr w4, x5, [x0]
| cbnz w4, 1b
| ldr x0, [x2, #8]
| sub x0, x0, #0x1
| str w0, [x2, #8]
| cbz x0, 2f
| ldr x0, [x2, #8]
| cbnz x0, 3f
| 2: bl preempt_schedule_notrace
| 3: ldp x29, x30, [sp], #16
| autiasp
| ret
With the scheme added in this patch, this can be compiled as:
| <outline_this_cpu_add_u64>:
| mrs x2, sp_el0
| mov x4, #0xc80 // __VAL_PCPU_GPRS(x0, x4, x3)
| strh w4, [x2, #20]
| mrs x4, tpidr_el1
| add x3, x0, x4
| 1: ldxr x6, [x3]
| add x6, x6, x1
| stxr w5, x6, [x3]
| cbnz w5, 1b
| strh wzr, [x2, #20]
| ret
Signed-off-by: Mark Rutland <mark.rutland@arm.com>
Tested-by: Muhammad Usama Anjum <usama.anjum@arm.com>
Cc: Ada Couprie Diaz <ada.coupriediaz@arm.com>
Cc: Ard Biesheuvel <ardb@kernel.org>
Cc: Catalin Marinas <catalin.marinas@arm.com>
Cc: James Morse <james.morse@arm.com>
Cc: Jinjie Ruan <ruanjinjie@huawei.com>
Cc: Marc Zyngier <maz@kernel.org>
Cc: Peter Zijlstra <peterz@infradead.org>
Cc: Vladimir Murzin <vladimir.murzin@arm.com>
Cc: Will Deacon <will@kernel.org>
Cc: Yang Shi <yang@os.amperecomputing.com>
---
arch/arm64/include/asm/percpu.h | 71 ++++++++++++++++++++++++++++
arch/arm64/include/asm/ptrace.h | 5 ++
arch/arm64/include/asm/thread_info.h | 1 +
arch/arm64/kernel/asm-offsets.c | 2 +
arch/arm64/kernel/entry-common.c | 38 +++++++++++++++
arch/arm64/kernel/entry.S | 22 +++++++++
6 files changed, 139 insertions(+)
diff --git a/arch/arm64/include/asm/percpu.h b/arch/arm64/include/asm/percpu.h
index 746d2a56f48bb..03fa916030fd1 100644
--- a/arch/arm64/include/asm/percpu.h
+++ b/arch/arm64/include/asm/percpu.h
@@ -5,10 +5,13 @@
#ifndef __ASM_PERCPU_H
#define __ASM_PERCPU_H
+#include <linux/bits.h>
#include <linux/preempt.h>
+#include <linux/stringify.h>
#include <asm/alternative.h>
#include <asm/cmpxchg.h>
+#include <asm/gpr-num.h>
#include <asm/stack_pointer.h>
#include <asm/sysreg.h>
@@ -51,6 +54,74 @@ static inline unsigned long __kern_my_cpu_offset(void)
return off;
}
+#define PCPU_GPR_PCP_SHIFT 0
+#define PCPU_GPR_PCP GENMASK(4, 0)
+#define PCPU_GPR_OFF_SHIFT 5
+#define PCPU_GPR_OFF GENMASK(9, 5)
+#define PCPU_GPR_ADDR_SHIFT 10
+#define PCPU_GPR_ADDR GENMASK(14, 10)
+
+#define __VAL_PCPU_GPRS(pcp, off, addr) \
+ "(" \
+ "(" __GPR_NUM(pcp) " << " __stringify(PCPU_GPR_PCP_SHIFT) ") | " \
+ "(" __GPR_NUM(off) " << " __stringify(PCPU_GPR_OFF_SHIFT) ") | " \
+ "(" __GPR_NUM(addr) " << " __stringify(PCPU_GPR_ADDR_SHIFT) ")" \
+ ")"
+
+#define __ASSERT_PCPU_GPRS_DISTINCT(pcp, off, addr) \
+ ".if (" \
+ "(" __GPR_NUM(pcp) " == " __GPR_NUM(off) ") || " \
+ "(" __GPR_NUM(pcp) " == " __GPR_NUM(addr) ") || " \
+ "(" __GPR_NUM(off) " == " __GPR_NUM(addr) ")" \
+ " )\n" \
+ ".error \"PCPU GPRS overlap: {" pcp "," off "," addr "}\"\n" \
+ ".endif\n"
+
+#define ____PCPU_GPRS_BEGIN(gprs, pcp, off, addr) \
+ "// ____PCPU_GPRS_BEGIN(" gprs ", " pcp ", " off ", " addr")\n" \
+ __DEFINE_ASM_GPR_NUMS \
+ __DEFINE_ASM_GPR_ALIASES \
+ __ASSERT_PCPU_GPRS_DISTINCT(pcp, off, addr) \
+ " mov w" off ", #" __VAL_PCPU_GPRS(pcp, off, addr) "\n" \
+ " strh w" off ", " gprs "\n" \
+ __KERN_ASM_CPU_OFFSET(off) "\n"
+
+/*
+ * Begin a PCPU GPR critical section which requires <addr> (and <off>).
+ *
+ * At the start of the critical section, and upon any (preemptible) exception
+ * until __PCPU_GPRS_END():
+ * - <off> will be set to the current CPU's percpu offset.
+ * - <addr> will be set to <pcp> + <off>.
+ *
+ * The <pcp>, <off>, and <addr> registers must be distinct GPRs.
+ *
+ * <gprs> must be '¤t_thread_info()->gprs', as a memory operand which can
+ * be written both at the start and end of the critical section (e.g. using
+ * "=Qo" constraints).
+ */
+#define __PCPU_GPRS_BEGIN(gprs, pcp, off, addr) \
+ ____PCPU_GPRS_BEGIN(gprs, pcp, off, addr) \
+ " add " addr ", " pcp ", " off "\n"
+
+/*
+ * Begin a PCPU GPR critical section which only requires <off> and does not
+ * require <addr>.
+ *
+ * This is only for operations that can use register-offset addressing,
+ * e.g. STR <Xt>, [<Xn>, <Xm].
+ *
+ * All other details are the same as __PCPU_GPRS_BEGIN().
+ */
+#define __PCPU_GPRS_BEGIN_OFFSET(gprs, pcp, off) \
+ ____PCPU_GPRS_BEGIN(gprs, pcp, off, "xzr")
+
+/*
+ * End a PCU GPR critical section.
+ */
+#define __PCPU_GPRS_END(gprs) \
+ " strh wzr, " gprs "\n"
+
#ifdef __KVM_NVHE_HYPERVISOR__
#define __my_cpu_offset __hyp_my_cpu_offset()
#else
diff --git a/arch/arm64/include/asm/ptrace.h b/arch/arm64/include/asm/ptrace.h
index f635c3453a858..25c70374cb7b4 100644
--- a/arch/arm64/include/asm/ptrace.h
+++ b/arch/arm64/include/asm/ptrace.h
@@ -167,6 +167,11 @@ struct pt_regs {
u64 sdei_ttbr1;
struct frame_record_meta stackframe;
+
+ u16 pcpu_gprs;
+ u16 __unused1;
+ u32 __unused2;
+ u64 __unused3;
};
/* For correct stack alignment, pt_regs has to be a multiple of 16 bytes. */
diff --git a/arch/arm64/include/asm/thread_info.h b/arch/arm64/include/asm/thread_info.h
index 5d7fe3e153c85..6db5fa72211d1 100644
--- a/arch/arm64/include/asm/thread_info.h
+++ b/arch/arm64/include/asm/thread_info.h
@@ -46,6 +46,7 @@ struct thread_info {
u64 mpam_partid_pmg;
#endif
u32 cpu;
+ u16 pcpu_gprs;
};
#define thread_saved_pc(tsk) \
diff --git a/arch/arm64/kernel/asm-offsets.c b/arch/arm64/kernel/asm-offsets.c
index 9c853ed3ceab8..9d603317eeb99 100644
--- a/arch/arm64/kernel/asm-offsets.c
+++ b/arch/arm64/kernel/asm-offsets.c
@@ -36,6 +36,7 @@ int main(void)
DEFINE(TSK_TI_SCS_BASE, offsetof(struct task_struct, thread_info.scs_base));
DEFINE(TSK_TI_SCS_SP, offsetof(struct task_struct, thread_info.scs_sp));
#endif
+ DEFINE(TSK_TI_PCPU_GPRS, offsetof(struct task_struct, thread_info.pcpu_gprs));
DEFINE(TSK_STACK, offsetof(struct task_struct, stack));
#ifdef CONFIG_STACKPROTECTOR
DEFINE(TSK_STACK_CANARY, offsetof(struct task_struct, stack_canary));
@@ -78,6 +79,7 @@ int main(void)
DEFINE(S_PMR, offsetof(struct pt_regs, pmr));
DEFINE(S_STACKFRAME, offsetof(struct pt_regs, stackframe));
DEFINE(S_STACKFRAME_TYPE, offsetof(struct pt_regs, stackframe.type));
+ DEFINE(S_PCPU_GPRS, offsetof(struct pt_regs, pcpu_gprs));
DEFINE(PT_REGS_SIZE, sizeof(struct pt_regs));
BLANK();
#ifdef CONFIG_DYNAMIC_FTRACE_WITH_ARGS
diff --git a/arch/arm64/kernel/entry-common.c b/arch/arm64/kernel/entry-common.c
index 72c03ccea59fe..11d3f888fcd87 100644
--- a/arch/arm64/kernel/entry-common.c
+++ b/arch/arm64/kernel/entry-common.c
@@ -25,12 +25,49 @@
#include <asm/irq_regs.h>
#include <asm/kprobes.h>
#include <asm/mmu.h>
+#include <asm/percpu.h>
#include <asm/processor.h>
#include <asm/sdei.h>
#include <asm/stacktrace.h>
#include <asm/sysreg.h>
#include <asm/system_misc.h>
+/*
+ * Where the context being returned to had an active percpu GPR critical
+ * section, ensure that the offset and address GPRs are updated to match the
+ * current CPU.
+ *
+ * For simplicity we always update the GPRs when a critical section is active
+ * and preemption was *possible*, regardless of whether preemption actually
+ * occurred. Where preemption did not occur, the updates are redundant but not
+ * harmful.
+ */
+static __always_inline void irqentry_exit_pcpu_adjust(struct pt_regs *regs)
+{
+ int reg_pcp, reg_off, reg_addr;
+ unsigned long pcp, off, addr;
+ u16 gprs = regs->pcpu_gprs;
+
+ /*
+ * Zero means no active PCPU GPRs. As the PCPU GPRs must be distinct,
+ * a PCPU critical section cannot possibly use {x0,x0,x0}.
+ */
+ if (likely(!gprs))
+ return;
+
+ reg_pcp = FIELD_GET(PCPU_GPR_PCP, gprs);
+ reg_off = FIELD_GET(PCPU_GPR_OFF, gprs);
+ reg_addr = FIELD_GET(PCPU_GPR_ADDR, gprs);
+
+ pcp = pt_regs_read_reg(regs, reg_pcp);
+
+ off = __kern_my_cpu_offset();
+ pt_regs_write_reg(regs, reg_off, off);
+
+ addr = pcp + off;
+ pt_regs_write_reg(regs, reg_addr, addr);
+}
+
/*
* Handle IRQ/context state management when entering from kernel mode.
* Before this function is called it is not safe to call regular kernel code,
@@ -56,6 +93,7 @@ static void noinstr __arm64_exit_to_kernel_mode(struct pt_regs *regs,
irqentry_state_t state)
{
local_daif_mask();
+ irqentry_exit_pcpu_adjust(regs);
mte_check_tfsr_exit();
irqentry_exit_to_kernel_mode_after_preempt(regs, state);
}
diff --git a/arch/arm64/kernel/entry.S b/arch/arm64/kernel/entry.S
index 0902c1bd9dd3d..8d17f61d1b5b2 100644
--- a/arch/arm64/kernel/entry.S
+++ b/arch/arm64/kernel/entry.S
@@ -194,6 +194,17 @@ alternative_cb_end
#endif
.endm
+ .macro pcpu_gprs_entry, tsk:req, regs:req, tmp:req
+ ldrh w\tmp, [\tsk, #TSK_TI_PCPU_GPRS]
+ strh w\tmp, [\regs, #S_PCPU_GPRS]
+ strh wzr, [\tsk, #TSK_TI_PCPU_GPRS]
+ .endm
+
+ .macro pcpu_gprs_exit, tsk:req, regs:req, tmp:req
+ ldrh w\tmp, [\regs, #S_PCPU_GPRS]
+ strh w\tmp, [\tsk, #TSK_TI_PCPU_GPRS]
+ .endm
+
.macro kernel_entry, el, regsize = 64
.if \el == 0
alternative_insn nop, SET_PSTATE_DIT(1), ARM64_HAS_DIT
@@ -277,6 +288,7 @@ alternative_else_nop_endif
.else
add x21, sp, #PT_REGS_SIZE
get_current_task tsk
+ pcpu_gprs_entry tsk, sp, x0
.endif /* \el == 0 */
mrs x22, elr_el1
mrs x23, spsr_el1
@@ -333,6 +345,10 @@ alternative_else_nop_endif
.endm
.macro kernel_exit, el
+ .if \el != 0
+ pcpu_gprs_exit tsk, sp, x0
+ .endif
+
#ifdef CONFIG_ARM64_PSEUDO_NMI
alternative_if_not ARM64_HAS_GIC_PRIO_MASKING
b .Lskip_pmr_restore\@
@@ -1040,10 +1056,16 @@ SYM_CODE_START(__sdei_asm_handler)
stp x29, x4, [sp, #-16]!
mov x29, sp
+ add x16, x19, #SDEI_EVENT_INTREGS
+ pcpu_gprs_entry tsk, x16, x17
+
add x0, x19, #SDEI_EVENT_INTREGS
mov x1, x19
bl __sdei_handler
+ add x16, x19, #SDEI_EVENT_INTREGS
+ pcpu_gprs_exit tsk, x16, x17
+
msr sp_el0, x20
/* restore regs >x17 that firmware won't restore */
mov x4, x19 // keep x4 for __sdei_asm_exit_trampoline
--
2.30.2
^ permalink raw reply related [flat|nested] 23+ messages in thread
* [PATCH v3 15/21] arm64: percpu: Implement preemptible read/write ops
2026-09-04 16:17 [PATCH v3 00/21] arm64: Preemptible this_cpu_*() operations Mark Rutland
` (7 preceding siblings ...)
2026-09-04 16:17 ` [PATCH v3 14/21] arm64: percpu: Add infrastructure for preemptible this_cpu_*() ops Mark Rutland
@ 2026-09-04 16:17 ` Mark Rutland
2026-09-04 16:17 ` [PATCH v3 16/21] arm64: percpu: Implement preemptible void RMW ops Mark Rutland
` (11 subsequent siblings)
20 siblings, 0 replies; 23+ messages in thread
From: Mark Rutland @ 2026-09-04 16:17 UTC (permalink / raw)
To: linux-arm-kernel
Cc: mark.rutland, vladimir.murzin, usama.anjum, peterz,
catalin.marinas, david.laight.linux, stable, ruanjinjie,
james.morse, yang, cl, maz, will, ardb
Use the PCPU GPR infrastructure to implement preemptible this_cpu_read()
and this_cpu_write().
This change means that this_cpu_read() will always use a plain LDR, even
in LTO configurations where READ_ONCE() will use LDA[P]R. Using plain
LDR is preferable, given that the rationale for using LDA[P]R in
READ_ONCE() was to retain address dependencies against values written by
other CPUs, which isn't expected usage for this_cpu_read(). Using plain
LDR will enforce fewer ordering constraints.
Test case:
| void outline_this_cpu_write_u64(u64 __percpu *p, u64 v)
| {
| this_cpu_write(*p, v);
| }
Generated code before this patch (v7.2-rc4):
| <outline_this_cpu_write_u64>:
| paciasp
| stp x29, x30, [sp, #-16]!
| mrs x2, sp_el0
| mov x29, sp
| ldr w3, [x2, #8]
| add w3, w3, #0x1
| str w3, [x2, #8]
| mrs x3, tpidr_el1
| str x1, [x0, x3]
| ldr x0, [x2, #8]
| sub x0, x0, #0x1
| str w0, [x2, #8]
| cbz x0, 1f
| ldr x0, [x2, #8]
| cbnz x0, 2f
| 1: bl preempt_schedule_notrace
| 2: ldp x29, x30, [sp], #16
| autiasp
| ret
Generated code after this patch:
| <outline_this_cpu_write_u64>:
| mrs x2, sp_el0
| mov w3, #0x7c60
| strh w3, [x2, #20]
| mrs x3, tpidr_el1
| str x1, [x0, x3]
| strh wzr, [x2, #20]
| ret
Signed-off-by: Mark Rutland <mark.rutland@arm.com>
Tested-by: Muhammad Usama Anjum <usama.anjum@arm.com>
Cc: Ada Couprie Diaz <ada.coupriediaz@arm.com>
Cc: Ard Biesheuvel <ardb@kernel.org>
Cc: Catalin Marinas <catalin.marinas@arm.com>
Cc: James Morse <james.morse@arm.com>
Cc: Jinjie Ruan <ruanjinjie@huawei.com>
Cc: Marc Zyngier <maz@kernel.org>
Cc: Peter Zijlstra <peterz@infradead.org>
Cc: Vladimir Murzin <vladimir.murzin@arm.com>
Cc: Will Deacon <will@kernel.org>
Cc: Yang Shi <yang@os.amperecomputing.com>
---
arch/arm64/include/asm/percpu.h | 79 ++++++++++++++++++++++++---------
1 file changed, 58 insertions(+), 21 deletions(-)
diff --git a/arch/arm64/include/asm/percpu.h b/arch/arm64/include/asm/percpu.h
index 03fa916030fd1..984f69846cc09 100644
--- a/arch/arm64/include/asm/percpu.h
+++ b/arch/arm64/include/asm/percpu.h
@@ -128,15 +128,42 @@ static inline unsigned long __kern_my_cpu_offset(void)
#define __my_cpu_offset __kern_my_cpu_offset()
#endif
-#define PERCPU_RW_OPS(sz) \
-static inline unsigned long __percpu_read_##sz(void *ptr) \
-{ \
- return READ_ONCE(*(u##sz *)ptr); \
-} \
- \
-static inline void __percpu_write_##sz(void *ptr, unsigned long val) \
-{ \
- WRITE_ONCE(*(u##sz *)ptr, (u##sz)val); \
+#define PERCPU_RW_OPS(w, sfx, sz) \
+static inline unsigned long __percpu_read_##sz(void __percpu *pcp) \
+{ \
+ u16 *gprs = ¤t_thread_info()->pcpu_gprs; \
+ unsigned long off; \
+ unsigned long val; \
+ \
+ asm volatile( \
+ __PCPU_GPRS_BEGIN_OFFSET("%[gprs]", "%[pcp]", "%[off]") \
+ " ldr" #sfx "\t%" #w "[val], [%[pcp], %[off]]\n" \
+ __PCPU_GPRS_END("%[gprs]") \
+ : [gprs] "=Qo" (*gprs), \
+ [off] "=&r" (off), \
+ [val] "=&r" (val) \
+ : [pcp] "r" (pcp) \
+ : "memory" \
+ ); \
+ \
+ return val; \
+} \
+ \
+static inline void __percpu_write_##sz(void __percpu *pcp, unsigned long val) \
+{ \
+ u16 *gprs = ¤t_thread_info()->pcpu_gprs; \
+ unsigned long off; \
+ \
+ asm volatile( \
+ __PCPU_GPRS_BEGIN_OFFSET("%[gprs]", "%[pcp]", "%[off]") \
+ " str" #sfx "\t%" #w "[val], [%[pcp], %[off]]\n" \
+ __PCPU_GPRS_END("%[gprs]") \
+ : [gprs] "=Qo" (*gprs), \
+ [off] "=&r" (off) \
+ : [pcp] "r" (pcp), \
+ [val] "r" ((u##sz)val) \
+ : "memory" \
+ ); \
}
#define __PERCPU_OP_CASE(w, sfx, name, sz, op_llsc, op_lse) \
@@ -196,10 +223,10 @@ __percpu_##name##_return_case_##sz(void *ptr, unsigned long val) \
__PERCPU_RET_OP_CASE(w, , name, 32, op_llsc, op_lse) \
__PERCPU_RET_OP_CASE( , , name, 64, op_llsc, op_lse)
-PERCPU_RW_OPS(8)
-PERCPU_RW_OPS(16)
-PERCPU_RW_OPS(32)
-PERCPU_RW_OPS(64)
+PERCPU_RW_OPS(w, b, 8)
+PERCPU_RW_OPS(w, h, 16)
+PERCPU_RW_OPS(w, , 32)
+PERCPU_RW_OPS( , , 64)
/*
* Use value-returning atomics for CPU-local ops as they are more likely
@@ -245,23 +272,33 @@ PERCPU_RET_OP(add, add, ldadd)
__retval; \
})
+#define _pcp_wrap(op, pcp, ...) \
+({ \
+ op(&(pcp), __VA_ARGS__); \
+})
+
+#define _pcp_wrap_return(op, pcp, args...) \
+({ \
+ (typeof(pcp))op(&(pcp), ##args); \
+})
+
#define this_cpu_read_1(pcp) \
- _pcp_protect_return(__percpu_read_8, pcp)
+ _pcp_wrap_return(__percpu_read_8, pcp)
#define this_cpu_read_2(pcp) \
- _pcp_protect_return(__percpu_read_16, pcp)
+ _pcp_wrap_return(__percpu_read_16, pcp)
#define this_cpu_read_4(pcp) \
- _pcp_protect_return(__percpu_read_32, pcp)
+ _pcp_wrap_return(__percpu_read_32, pcp)
#define this_cpu_read_8(pcp) \
- _pcp_protect_return(__percpu_read_64, pcp)
+ _pcp_wrap_return(__percpu_read_64, pcp)
#define this_cpu_write_1(pcp, val) \
- _pcp_protect(__percpu_write_8, pcp, (unsigned long)(val))
+ _pcp_wrap(__percpu_write_8, pcp, (unsigned long)(val))
#define this_cpu_write_2(pcp, val) \
- _pcp_protect(__percpu_write_16, pcp, (unsigned long)(val))
+ _pcp_wrap(__percpu_write_16, pcp, (unsigned long)(val))
#define this_cpu_write_4(pcp, val) \
- _pcp_protect(__percpu_write_32, pcp, (unsigned long)(val))
+ _pcp_wrap(__percpu_write_32, pcp, (unsigned long)(val))
#define this_cpu_write_8(pcp, val) \
- _pcp_protect(__percpu_write_64, pcp, (unsigned long)(val))
+ _pcp_wrap(__percpu_write_64, pcp, (unsigned long)(val))
#define this_cpu_add_1(pcp, val) \
_pcp_protect(__percpu_add_case_8, pcp, val)
--
2.30.2
^ permalink raw reply related [flat|nested] 23+ messages in thread
* [PATCH v3 16/21] arm64: percpu: Implement preemptible void RMW ops
2026-09-04 16:17 [PATCH v3 00/21] arm64: Preemptible this_cpu_*() operations Mark Rutland
` (8 preceding siblings ...)
2026-09-04 16:17 ` [PATCH v3 15/21] arm64: percpu: Implement preemptible read/write ops Mark Rutland
@ 2026-09-04 16:17 ` Mark Rutland
2026-09-05 9:03 ` David Laight
2026-09-04 16:17 ` [PATCH v3 17/21] arm64: percpu: Implement preemptible return " Mark Rutland
` (10 subsequent siblings)
20 siblings, 1 reply; 23+ messages in thread
From: Mark Rutland @ 2026-09-04 16:17 UTC (permalink / raw)
To: linux-arm-kernel
Cc: mark.rutland, vladimir.murzin, usama.anjum, peterz,
catalin.marinas, david.laight.linux, stable, ruanjinjie,
james.morse, yang, cl, maz, will, ardb
Use the PCPU GPR infrastructure to implement all of the RMW ops which do
not return a value.
Test case:
| void outline_this_cpu_add_u64(u64 __percpu *p, u64 v)
| {
| this_cpu_add(*p, v);
| }
Generated code before this patch (v7.2-rc4):
| <outline_this_cpu_add_u64>:
| paciasp
| stp x29, x30, [sp, #-16]!
| mrs x2, sp_el0
| mov x29, sp
| ldr w3, [x2, #8]
| add w3, w3, #0x1
| str w3, [x2, #8]
| mrs x3, tpidr_el1
| add x0, x0, x3
| 1: ldxr x5, [x0]
| add x5, x5, x1
| stxr w4, x5, [x0]
| cbnz w4, 1b
| ldr x0, [x2, #8]
| sub x0, x0, #0x1
| str w0, [x2, #8]
| cbz x0, 2f
| ldr x0, [x2, #8]
| cbnz x0, 3f
| 2: bl preempt_schedule_notrace
| 3: ldp x29, x30, [sp], #16
| autiasp
| ret
Generated code after this patch:
| <outline_this_cpu_add_u64>:
| mrs x2, sp_el0
| mov w4, #0xc80
| strh w4, [x2, #20]
| mrs x4, tpidr_el1
| add x3, x0, x4
| 1: ldxr x6, [x3]
| add x6, x6, x1
| stxr w5, x6, [x3]
| cbnz w5, 1b
| strh wzr, [x2, #20]
| ret
Signed-off-by: Mark Rutland <mark.rutland@arm.com>
Tested-by: Muhammad Usama Anjum <usama.anjum@arm.com>
Cc: Ada Couprie Diaz <ada.coupriediaz@arm.com>
Cc: Ard Biesheuvel <ardb@kernel.org>
Cc: Catalin Marinas <catalin.marinas@arm.com>
Cc: James Morse <james.morse@arm.com>
Cc: Jinjie Ruan <ruanjinjie@huawei.com>
Cc: Marc Zyngier <maz@kernel.org>
Cc: Peter Zijlstra <peterz@infradead.org>
Cc: Vladimir Murzin <vladimir.murzin@arm.com>
Cc: Will Deacon <will@kernel.org>
Cc: Yang Shi <yang@os.amperecomputing.com>
---
arch/arm64/include/asm/percpu.h | 52 ++++++++++++++++++++-------------
1 file changed, 32 insertions(+), 20 deletions(-)
diff --git a/arch/arm64/include/asm/percpu.h b/arch/arm64/include/asm/percpu.h
index 984f69846cc09..504a7fefa2015 100644
--- a/arch/arm64/include/asm/percpu.h
+++ b/arch/arm64/include/asm/percpu.h
@@ -168,23 +168,35 @@ static inline void __percpu_write_##sz(void __percpu *pcp, unsigned long val) \
#define __PERCPU_OP_CASE(w, sfx, name, sz, op_llsc, op_lse) \
static inline void \
-__percpu_##name##_case_##sz(void *ptr, unsigned long val) \
+__percpu_##name##_case_##sz(void __percpu *pcp, unsigned long val) \
{ \
+ u16 *gprs = ¤t_thread_info()->pcpu_gprs; \
+ unsigned long addr; \
+ unsigned long off; \
unsigned int loop; \
u##sz tmp; \
\
- asm volatile (ARM64_LSE_ATOMIC_INSN( \
+ asm volatile ( \
+ __PCPU_GPRS_BEGIN("%[gprs]", "%[pcp]", "%[off]", "%[addr]") \
+ ARM64_LSE_ATOMIC_INSN( \
/* LL/SC */ \
- "1: ldxr" #sfx "\t%" #w "[tmp], %[ptr]\n" \
+ "1: ldxr" #sfx "\t%" #w "[tmp], [%[addr]]\n" \
#op_llsc "\t%" #w "[tmp], %" #w "[tmp], %" #w "[val]\n" \
- " stxr" #sfx "\t%w[loop], %" #w "[tmp], %[ptr]\n" \
+ " stxr" #sfx "\t%w[loop], %" #w "[tmp], [%[addr]]\n" \
" cbnz %w[loop], 1b", \
/* LSE atomics */ \
- #op_lse #sfx "\t%" #w "[val], %" #w "[tmp], %[ptr]\n" \
+ #op_lse #sfx "\t%" #w "[val], %" #w "[tmp], [%[addr]]\n"\
__nops(3)) \
- : [loop] "=&r" (loop), [tmp] "=&r" (tmp), \
- [ptr] "+Q"(*(u##sz *)ptr) \
- : [val] "r" ((u##sz)(val))); \
+ __PCPU_GPRS_END("%[gprs]") \
+ : [gprs] "=Qo" (*gprs), \
+ [addr] "=&r" (addr), \
+ [off] "=&r" (off), \
+ [loop] "=&r" (loop), \
+ [tmp] "=&r" (tmp) \
+ : [pcp] "r" (pcp), \
+ [val] "r" ((u##sz)(val)) \
+ : "memory" \
+ ); \
}
#define __PERCPU_RET_OP_CASE(w, sfx, name, sz, op_llsc, op_lse) \
@@ -301,13 +313,13 @@ PERCPU_RET_OP(add, add, ldadd)
_pcp_wrap(__percpu_write_64, pcp, (unsigned long)(val))
#define this_cpu_add_1(pcp, val) \
- _pcp_protect(__percpu_add_case_8, pcp, val)
+ _pcp_wrap(__percpu_add_case_8, pcp, val)
#define this_cpu_add_2(pcp, val) \
- _pcp_protect(__percpu_add_case_16, pcp, val)
+ _pcp_wrap(__percpu_add_case_16, pcp, val)
#define this_cpu_add_4(pcp, val) \
- _pcp_protect(__percpu_add_case_32, pcp, val)
+ _pcp_wrap(__percpu_add_case_32, pcp, val)
#define this_cpu_add_8(pcp, val) \
- _pcp_protect(__percpu_add_case_64, pcp, val)
+ _pcp_wrap(__percpu_add_case_64, pcp, val)
#define this_cpu_add_return_1(pcp, val) \
_pcp_protect_return(__percpu_add_return_case_8, pcp, val)
@@ -319,22 +331,22 @@ PERCPU_RET_OP(add, add, ldadd)
_pcp_protect_return(__percpu_add_return_case_64, pcp, val)
#define this_cpu_and_1(pcp, val) \
- _pcp_protect(__percpu_andnot_case_8, pcp, ~(u8)(val))
+ _pcp_wrap(__percpu_andnot_case_8, pcp, ~(u8)(val))
#define this_cpu_and_2(pcp, val) \
- _pcp_protect(__percpu_andnot_case_16, pcp, ~(u16)(val))
+ _pcp_wrap(__percpu_andnot_case_16, pcp, ~(u16)(val))
#define this_cpu_and_4(pcp, val) \
- _pcp_protect(__percpu_andnot_case_32, pcp, ~(u32)(val))
+ _pcp_wrap(__percpu_andnot_case_32, pcp, ~(u32)(val))
#define this_cpu_and_8(pcp, val) \
- _pcp_protect(__percpu_andnot_case_64, pcp, ~(u64)(val))
+ _pcp_wrap(__percpu_andnot_case_64, pcp, ~(u64)(val))
#define this_cpu_or_1(pcp, val) \
- _pcp_protect(__percpu_or_case_8, pcp, val)
+ _pcp_wrap(__percpu_or_case_8, pcp, val)
#define this_cpu_or_2(pcp, val) \
- _pcp_protect(__percpu_or_case_16, pcp, val)
+ _pcp_wrap(__percpu_or_case_16, pcp, val)
#define this_cpu_or_4(pcp, val) \
- _pcp_protect(__percpu_or_case_32, pcp, val)
+ _pcp_wrap(__percpu_or_case_32, pcp, val)
#define this_cpu_or_8(pcp, val) \
- _pcp_protect(__percpu_or_case_64, pcp, val)
+ _pcp_wrap(__percpu_or_case_64, pcp, val)
#define this_cpu_xchg_1(pcp, val) \
_pcp_protect_return(xchg_relaxed, pcp, val)
--
2.30.2
^ permalink raw reply related [flat|nested] 23+ messages in thread
* [PATCH v3 17/21] arm64: percpu: Implement preemptible return RMW ops
2026-09-04 16:17 [PATCH v3 00/21] arm64: Preemptible this_cpu_*() operations Mark Rutland
` (9 preceding siblings ...)
2026-09-04 16:17 ` [PATCH v3 16/21] arm64: percpu: Implement preemptible void RMW ops Mark Rutland
@ 2026-09-04 16:17 ` Mark Rutland
2026-09-04 16:17 ` [PATCH v3 18/21] arm64: percpu: Implement preemptible XCHG ops Mark Rutland
` (9 subsequent siblings)
20 siblings, 0 replies; 23+ messages in thread
From: Mark Rutland @ 2026-09-04 16:17 UTC (permalink / raw)
To: linux-arm-kernel
Cc: mark.rutland, vladimir.murzin, usama.anjum, peterz,
catalin.marinas, david.laight.linux, stable, ruanjinjie,
james.morse, yang, cl, maz, will, ardb
Use the PCPU GPR infrastructure to implement all of the RMW ops which
return a value.
Test case:
| u64 outline_this_cpu_add_return_u64(u64 __percpu *p, u64 v)
| {
| return this_cpu_add_return(*p, v);
| }
Generated code before this patch (v7.2-rc4):
| <outline_this_cpu_add_return_u64>:
| paciasp
| stp x29, x30, [sp, #-32]!
| mrs x3, sp_el0
| mov x29, sp
| ldr w2, [x3, #8]
| add w2, w2, #0x1
| str w2, [x3, #8]
| mov x2, x0
| mrs x4, tpidr_el1
| add x2, x2, x4
| 1: ldxr x0, [x2]
| add x0, x0, x1
| stxr w5, x0, [x2]
| cbnz w5, 1b
| ldr x1, [x3, #8]
| sub x1, x1, #0x1
| str w1, [x3, #8]
| cbz x1, 2f
| ldr x1, [x3, #8]
| cbnz x1, 3f
| 2: str x0, [sp, #24]
| bl 0 <preempt_schedule_notrace>
| ldr x0, [sp, #24]
| 3: ldp x29, x30, [sp], #32
| autiasp
| ret
Generated code after this patch:
| <outline_this_cpu_add_return_u64>:
| mrs x3, sp_el0
| mov w5, #0x10a0
| strh w5, [x3, #20]
| mrs x5, tpidr_el1
| add x4, x0, x5
| 1: ldxr x2, [x4]
| add x2, x2, x1
| stxr w6, x2, [x4]
| cbnz w6, 1b
| strh wzr, [x3, #20]
| mov x0, x2
| ret
Signed-off-by: Mark Rutland <mark.rutland@arm.com>
Tested-by: Muhammad Usama Anjum <usama.anjum@arm.com>
Cc: Ada Couprie Diaz <ada.coupriediaz@arm.com>
Cc: Ard Biesheuvel <ardb@kernel.org>
Cc: Catalin Marinas <catalin.marinas@arm.com>
Cc: James Morse <james.morse@arm.com>
Cc: Jinjie Ruan <ruanjinjie@huawei.com>
Cc: Marc Zyngier <maz@kernel.org>
Cc: Peter Zijlstra <peterz@infradead.org>
Cc: Vladimir Murzin <vladimir.murzin@arm.com>
Cc: Will Deacon <will@kernel.org>
Cc: Yang Shi <yang@os.amperecomputing.com>
---
arch/arm64/include/asm/percpu.h | 36 ++++++++++++++++++++++-----------
1 file changed, 24 insertions(+), 12 deletions(-)
diff --git a/arch/arm64/include/asm/percpu.h b/arch/arm64/include/asm/percpu.h
index 504a7fefa2015..9fe44d04c871b 100644
--- a/arch/arm64/include/asm/percpu.h
+++ b/arch/arm64/include/asm/percpu.h
@@ -201,24 +201,36 @@ __percpu_##name##_case_##sz(void __percpu *pcp, unsigned long val) \
#define __PERCPU_RET_OP_CASE(w, sfx, name, sz, op_llsc, op_lse) \
static inline u##sz \
-__percpu_##name##_return_case_##sz(void *ptr, unsigned long val) \
+__percpu_##name##_return_case_##sz(void __percpu *pcp, unsigned long val) \
{ \
+ u16 *gprs = ¤t_thread_info()->pcpu_gprs; \
+ unsigned long addr; \
+ unsigned long off; \
unsigned int loop; \
u##sz ret; \
\
- asm volatile (ARM64_LSE_ATOMIC_INSN( \
+ asm volatile ( \
+ __PCPU_GPRS_BEGIN("%[gprs]", "%[pcp]", "%[off]", "%[addr]") \
+ ARM64_LSE_ATOMIC_INSN( \
/* LL/SC */ \
- "1: ldxr" #sfx "\t%" #w "[ret], %[ptr]\n" \
+ "1: ldxr" #sfx "\t%" #w "[ret], [%[addr]]\n" \
#op_llsc "\t%" #w "[ret], %" #w "[ret], %" #w "[val]\n" \
- " stxr" #sfx "\t%w[loop], %" #w "[ret], %[ptr]\n" \
+ " stxr" #sfx "\t%w[loop], %" #w "[ret], [%[addr]]\n" \
" cbnz %w[loop], 1b", \
/* LSE atomics */ \
- #op_lse #sfx "\t%" #w "[val], %" #w "[ret], %[ptr]\n" \
+ #op_lse #sfx "\t%" #w "[val], %" #w "[ret], [%[addr]]\n"\
#op_llsc "\t%" #w "[ret], %" #w "[ret], %" #w "[val]\n" \
__nops(2)) \
- : [loop] "=&r" (loop), [ret] "=&r" (ret), \
- [ptr] "+Q"(*(u##sz *)ptr) \
- : [val] "r" ((u##sz)(val))); \
+ __PCPU_GPRS_END("%[gprs]") \
+ : [gprs] "=Qo" (*gprs), \
+ [addr] "=&r" (addr), \
+ [off] "=&r" (off), \
+ [loop] "=&r" (loop), \
+ [ret] "=&r" (ret) \
+ : [pcp] "r" (pcp), \
+ [val] "r" ((u##sz)(val)) \
+ : "memory" \
+ ); \
\
return ret; \
}
@@ -322,13 +334,13 @@ PERCPU_RET_OP(add, add, ldadd)
_pcp_wrap(__percpu_add_case_64, pcp, val)
#define this_cpu_add_return_1(pcp, val) \
- _pcp_protect_return(__percpu_add_return_case_8, pcp, val)
+ _pcp_wrap_return(__percpu_add_return_case_8, pcp, val)
#define this_cpu_add_return_2(pcp, val) \
- _pcp_protect_return(__percpu_add_return_case_16, pcp, val)
+ _pcp_wrap_return(__percpu_add_return_case_16, pcp, val)
#define this_cpu_add_return_4(pcp, val) \
- _pcp_protect_return(__percpu_add_return_case_32, pcp, val)
+ _pcp_wrap_return(__percpu_add_return_case_32, pcp, val)
#define this_cpu_add_return_8(pcp, val) \
- _pcp_protect_return(__percpu_add_return_case_64, pcp, val)
+ _pcp_wrap_return(__percpu_add_return_case_64, pcp, val)
#define this_cpu_and_1(pcp, val) \
_pcp_wrap(__percpu_andnot_case_8, pcp, ~(u8)(val))
--
2.30.2
^ permalink raw reply related [flat|nested] 23+ messages in thread
* [PATCH v3 18/21] arm64: percpu: Implement preemptible XCHG ops
2026-09-04 16:17 [PATCH v3 00/21] arm64: Preemptible this_cpu_*() operations Mark Rutland
` (10 preceding siblings ...)
2026-09-04 16:17 ` [PATCH v3 17/21] arm64: percpu: Implement preemptible return " Mark Rutland
@ 2026-09-04 16:17 ` Mark Rutland
2026-09-04 16:17 ` [PATCH v3 19/21] arm64: percpu: Implement preemptible CMPXCHG ops Mark Rutland
` (8 subsequent siblings)
20 siblings, 0 replies; 23+ messages in thread
From: Mark Rutland @ 2026-09-04 16:17 UTC (permalink / raw)
To: linux-arm-kernel
Cc: mark.rutland, vladimir.murzin, usama.anjum, peterz,
catalin.marinas, david.laight.linux, stable, ruanjinjie,
james.morse, yang, cl, maz, will, ardb
Use the PCPU GPR infrastructure to implement all of the xchg ops
Test case:
| u64 outline_xchg_name(u64 *p, u64 v)
| {
| return xchg(p, v);
| }
Generated code before this patch (v7.2-rc4):
| <outline_this_cpu_xchg_u64>:
| paciasp
| stp x29, x30, [sp, #-32]!
| mrs x3, sp_el0
| mov x29, sp
| ldr w2, [x3, #8]
| add w2, w2, #0x1
| str w2, [x3, #8]
| mov x2, x0
| mrs x4, tpidr_el1
| add x2, x2, x4
| prfm pstl1strm, [x2]
| 1: ldxr x0, [x2]
| stxr w5, x1, [x2]
| cbnz w5, 1b
| ldr x1, [x3, #8]
| sub x1, x1, #0x1
| str w1, [x3, #8]
| cbz x1, 2f
| ldr x1, [x3, #8]
| cbnz x1, 3f
| 2: str x0, [sp, #24]
| bl preempt_schedule_notrace
| ldr x0, [sp, #24]
| 3: ldp x29, x30, [sp], #32
| autiasp
| ret
Generated code after this patch:
| <outline_this_cpu_xchg_u64>:
| mrs x3, sp_el0
| mov w5, #0x10a0
| strh w5, [x3, #20]
| mrs x5, tpidr_el1
| add x4, x0, x5
| prfm pstl1strm, [x4]
| 1: ldxr x2, [x4]
| stxr w6, x1, [x4]
| cbnz w6, 1b
| strh wzr, [x3, #20]
| mov x0, x2
| ret
Signed-off-by: Mark Rutland <mark.rutland@arm.com>
Tested-by: Muhammad Usama Anjum <usama.anjum@arm.com>
Cc: Ada Couprie Diaz <ada.coupriediaz@arm.com>
Cc: Ard Biesheuvel <ardb@kernel.org>
Cc: Catalin Marinas <catalin.marinas@arm.com>
Cc: James Morse <james.morse@arm.com>
Cc: Jinjie Ruan <ruanjinjie@huawei.com>
Cc: Marc Zyngier <maz@kernel.org>
Cc: Peter Zijlstra <peterz@infradead.org>
Cc: Vladimir Murzin <vladimir.murzin@arm.com>
Cc: Will Deacon <will@kernel.org>
Cc: Yang Shi <yang@os.amperecomputing.com>
---
arch/arm64/include/asm/percpu.h | 57 ++++++++++++++++++++++++++++++---
1 file changed, 53 insertions(+), 4 deletions(-)
diff --git a/arch/arm64/include/asm/percpu.h b/arch/arm64/include/asm/percpu.h
index 9fe44d04c871b..820340c61d70f 100644
--- a/arch/arm64/include/asm/percpu.h
+++ b/arch/arm64/include/asm/percpu.h
@@ -269,6 +269,50 @@ PERCPU_RET_OP(add, add, ldadd)
#undef PERCPU_OP
#undef PERCPU_RET_OP
+#define PERCPU_XCHG_OP(w, sfx, sz) \
+static inline unsigned long \
+__percpu_xchg_case_##sz(void __percpu *pcp, u##sz val) \
+{ \
+ u16 *gprs = ¤t_thread_info()->pcpu_gprs; \
+ unsigned long addr; \
+ unsigned long off; \
+ unsigned int loop; \
+ unsigned long ret; \
+ \
+ asm volatile ( \
+ __PCPU_GPRS_BEGIN("%[gprs]", "%[pcp]", "%[off]", "%[addr]") \
+ ARM64_LSE_ATOMIC_INSN( \
+ /* LL/SC */ \
+ " prfm pstl1strm, [%[addr]]\n" \
+ "1: ldxr" #sfx "\t%" #w "[ret], [%[addr]]\n" \
+ " stxr" #sfx "\t%w[loop], %" #w "[val], [%[addr]]\n" \
+ " cbnz %w[loop], 1b\n" \
+ , \
+ /* LSE atomics */ \
+ " swp" #sfx "\t%" #w "[val], %" #w "[ret], [%[addr]]\n" \
+ __nops(3) \
+ ) \
+ __PCPU_GPRS_END("%[gprs]") \
+ : [gprs] "=Qo" (*gprs), \
+ [addr] "=&r" (addr), \
+ [off] "=&r" (off), \
+ [loop] "=&r" (loop), \
+ [ret] "=&r" (ret) \
+ : [pcp] "r" (pcp), \
+ [val] "r" (val) \
+ : "memory" \
+ ); \
+ \
+ return ret; \
+}
+
+PERCPU_XCHG_OP(w, b, 8)
+PERCPU_XCHG_OP(w, h, 16)
+PERCPU_XCHG_OP(w, , 32)
+PERCPU_XCHG_OP(x, , 64)
+
+#undef PERCPU_XCHG_OP
+
/*
* It would be nice to avoid the conditional call into the scheduler when
* re-enabling preemption for preemptible kernels, but doing that in a way
@@ -306,6 +350,11 @@ PERCPU_RET_OP(add, add, ldadd)
(typeof(pcp))op(&(pcp), ##args); \
})
+#define _pcp_wrap_xchg(op, pcp, val) \
+({ \
+ (typeof(pcp))op(&(pcp), (unsigned long)(val)); \
+})
+
#define this_cpu_read_1(pcp) \
_pcp_wrap_return(__percpu_read_8, pcp)
#define this_cpu_read_2(pcp) \
@@ -361,13 +410,13 @@ PERCPU_RET_OP(add, add, ldadd)
_pcp_wrap(__percpu_or_case_64, pcp, val)
#define this_cpu_xchg_1(pcp, val) \
- _pcp_protect_return(xchg_relaxed, pcp, val)
+ _pcp_wrap_xchg(__percpu_xchg_case_8, pcp, val)
#define this_cpu_xchg_2(pcp, val) \
- _pcp_protect_return(xchg_relaxed, pcp, val)
+ _pcp_wrap_xchg(__percpu_xchg_case_16, pcp, val)
#define this_cpu_xchg_4(pcp, val) \
- _pcp_protect_return(xchg_relaxed, pcp, val)
+ _pcp_wrap_xchg(__percpu_xchg_case_32, pcp, val)
#define this_cpu_xchg_8(pcp, val) \
- _pcp_protect_return(xchg_relaxed, pcp, val)
+ _pcp_wrap_xchg(__percpu_xchg_case_64, pcp, val)
#define this_cpu_cmpxchg_1(pcp, o, n) \
_pcp_protect_return(cmpxchg_relaxed, pcp, o, n)
--
2.30.2
^ permalink raw reply related [flat|nested] 23+ messages in thread
* [PATCH v3 19/21] arm64: percpu: Implement preemptible CMPXCHG ops
2026-09-04 16:17 [PATCH v3 00/21] arm64: Preemptible this_cpu_*() operations Mark Rutland
` (11 preceding siblings ...)
2026-09-04 16:17 ` [PATCH v3 18/21] arm64: percpu: Implement preemptible XCHG ops Mark Rutland
@ 2026-09-04 16:17 ` Mark Rutland
2026-09-04 16:17 ` [PATCH v3 20/21] arm64: percpu: Implement preemptible CMPXCHG128 ops Mark Rutland
` (7 subsequent siblings)
20 siblings, 0 replies; 23+ messages in thread
From: Mark Rutland @ 2026-09-04 16:17 UTC (permalink / raw)
To: linux-arm-kernel
Cc: mark.rutland, vladimir.murzin, usama.anjum, peterz,
catalin.marinas, david.laight.linux, stable, ruanjinjie,
james.morse, yang, cl, maz, will, ardb
Use the PCPU GPR infrastructure to implement all of the {8,16,32,64}-bit
cmpxchg ops.
Note that before this patch, the LL/SC and LSE implementation was chosen
with an alternative branch. After this patch the implementations are
patched inline, matching the style of the other percpu ops.
Test case:
| u64 outline_this_cpu_cmpxchg_u64(u64 __percpu *p, u64 o, u64 n)
| {
| return this_cpu_cmpxchg(*p, o, n);
| }
Generated code before this patch (v7.2-rc4):
| <outline_this_cpu_cmpxchg_u64>:
| paciasp
| stp x29, x30, [sp, #-32]!
| mrs x4, sp_el0
| mov x29, sp
| ldr w3, [x4, #8]
| add w3, w3, #0x1
| str w3, [x4, #8]
| mrs x3, tpidr_el1
| add x3, x0, x3
| b 4f // alternative branch
| cas x1, x2, [x3]
| mov x0, x1
| 1: mrs x2, sp_el0
| ldr x1, [x2, #8]
| sub x1, x1, #0x1
| str w1, [x2, #8]
| cbz x1, 2f
| ldr x1, [x2, #8]
| cbnz x1, 3f
| 2: str x0, [sp, #24]
| bl preempt_schedule_notrace
| ldr x0, [sp, #24]
| 3: ldp x29, x30, [sp], #32
| autiasp
| ret
| 4: prfm pstl1strm, [x3]
| 5: ldxr x0, [x3]
| eor x4, x0, x1
| cbnz x4, 6f
| stxr w4, x2, [x3]
| cbnz w4, 5b
| 6: b 1b
Generated code after this patch:
| <outline_this_cpu_cmpxchg_u64>:
| mrs x4, sp_el0
| mov w6, #0x14c0
| strh w6, [x4, #20]
| mrs x6, tpidr_el1
| add x5, x0, x6
| prfm pstl1strm, [x5]
| 1: ldxr x3, [x5]
| eor x7, x3, x1
| cbnz x7, 2f
| stxr w7, x2, [x5]
| cbnz w7, 1b
| 2: strh wzr, [x4, #20]
| mov x0, x3
| ret
Signed-off-by: Mark Rutland <mark.rutland@arm.com>
Tested-by: Muhammad Usama Anjum <usama.anjum@arm.com>
Cc: Ada Couprie Diaz <ada.coupriediaz@arm.com>
Cc: Ard Biesheuvel <ardb@kernel.org>
Cc: Catalin Marinas <catalin.marinas@arm.com>
Cc: James Morse <james.morse@arm.com>
Cc: Jinjie Ruan <ruanjinjie@huawei.com>
Cc: Marc Zyngier <maz@kernel.org>
Cc: Peter Zijlstra <peterz@infradead.org>
Cc: Vladimir Murzin <vladimir.murzin@arm.com>
Cc: Will Deacon <will@kernel.org>
Cc: Yang Shi <yang@os.amperecomputing.com>
---
arch/arm64/include/asm/percpu.h | 69 +++++++++++++++++++++++++++++++--
1 file changed, 65 insertions(+), 4 deletions(-)
diff --git a/arch/arm64/include/asm/percpu.h b/arch/arm64/include/asm/percpu.h
index 820340c61d70f..f19a82437b3fd 100644
--- a/arch/arm64/include/asm/percpu.h
+++ b/arch/arm64/include/asm/percpu.h
@@ -306,12 +306,67 @@ __percpu_xchg_case_##sz(void __percpu *pcp, u##sz val) \
return ret; \
}
+#define PERCPU_CMPXCHG_OP(w, sfx, sz) \
+static inline unsigned long \
+__percpu_cmpxchg_case_##sz(void __percpu *pcp, \
+ u##sz old, \
+ u##sz new) \
+{ \
+ /* \
+ * Sub-word sizes require zero extension so that EOR+CBNZ don't \
+ * consume non-zero upper bits of the register containing "old".\
+ */ \
+ xwreg_t(w) cmpval = xwreg_zero_extend(old, w, sz); \
+ u16 *gprs = ¤t_thread_info()->pcpu_gprs; \
+ unsigned long addr; \
+ unsigned long off; \
+ unsigned long tmp; \
+ unsigned long oldval; \
+ \
+ asm volatile ( \
+ __PCPU_GPRS_BEGIN("%[gprs]", "%[pcp]", "%[off]", "%[addr]") \
+ ARM64_LSE_ATOMIC_INSN( \
+ /* LL/SC */ \
+ " prfm pstl1strm, [%[addr]]\n" \
+ "1: ldxr" #sfx "\t%" #w "[oldval], [%[addr]]\n" \
+ " eor %" #w "[tmp], %" #w "[oldval], %" #w "[old]\n" \
+ " cbnz %" #w "[tmp], 2f\n" \
+ " stxr" #sfx "\t%w[tmp], %" #w "[new], [%[addr]]\n" \
+ " cbnz %w[tmp], 1b\n" \
+ "2:\n" \
+ , \
+ /* LSE atomics */ \
+ " mov %" #w "[oldval], %" #w "[old]\n" \
+ " cas" #sfx "\t%" #w "[oldval], %" #w "[new], [%[addr]]\n"\
+ __nops(4) \
+ ) \
+ __PCPU_GPRS_END("%[gprs]") \
+ : [gprs] "=Qo" (*gprs), \
+ [addr] "=&r" (addr), \
+ [off] "=&r" (off), \
+ [tmp] "=&r" (tmp), \
+ [oldval] "=&r" (oldval) \
+ : [pcp] "r" (pcp), \
+ [old] "r" (cmpval), \
+ [new] "r" (new) \
+ : "memory" \
+ ); \
+ \
+ return oldval; \
+}
+
PERCPU_XCHG_OP(w, b, 8)
PERCPU_XCHG_OP(w, h, 16)
PERCPU_XCHG_OP(w, , 32)
PERCPU_XCHG_OP(x, , 64)
+PERCPU_CMPXCHG_OP(w, b, 8)
+PERCPU_CMPXCHG_OP(w, h, 16)
+PERCPU_CMPXCHG_OP(w, , 32)
+PERCPU_CMPXCHG_OP(x, , 64)
+
#undef PERCPU_XCHG_OP
+#undef PERCPU_CMPXCHG_OP
/*
* It would be nice to avoid the conditional call into the scheduler when
@@ -355,6 +410,12 @@ PERCPU_XCHG_OP(x, , 64)
(typeof(pcp))op(&(pcp), (unsigned long)(val)); \
})
+#define _pcp_wrap_cmpxchg(op, pcp, old, new) \
+({ \
+ (typeof(pcp))op(&(pcp), (unsigned long)(old), \
+ (unsigned long)(new)); \
+})
+
#define this_cpu_read_1(pcp) \
_pcp_wrap_return(__percpu_read_8, pcp)
#define this_cpu_read_2(pcp) \
@@ -419,13 +480,13 @@ PERCPU_XCHG_OP(x, , 64)
_pcp_wrap_xchg(__percpu_xchg_case_64, pcp, val)
#define this_cpu_cmpxchg_1(pcp, o, n) \
- _pcp_protect_return(cmpxchg_relaxed, pcp, o, n)
+ _pcp_wrap_cmpxchg(__percpu_cmpxchg_case_8, pcp, o, n)
#define this_cpu_cmpxchg_2(pcp, o, n) \
- _pcp_protect_return(cmpxchg_relaxed, pcp, o, n)
+ _pcp_wrap_cmpxchg(__percpu_cmpxchg_case_16, pcp, o, n)
#define this_cpu_cmpxchg_4(pcp, o, n) \
- _pcp_protect_return(cmpxchg_relaxed, pcp, o, n)
+ _pcp_wrap_cmpxchg(__percpu_cmpxchg_case_32, pcp, o, n)
#define this_cpu_cmpxchg_8(pcp, o, n) \
- _pcp_protect_return(cmpxchg_relaxed, pcp, o, n)
+ _pcp_wrap_cmpxchg(__percpu_cmpxchg_case_64, pcp, o, n)
#define this_cpu_cmpxchg64(pcp, o, n) this_cpu_cmpxchg_8(pcp, o, n)
--
2.30.2
^ permalink raw reply related [flat|nested] 23+ messages in thread
* [PATCH v3 20/21] arm64: percpu: Implement preemptible CMPXCHG128 ops
2026-09-04 16:17 [PATCH v3 00/21] arm64: Preemptible this_cpu_*() operations Mark Rutland
` (12 preceding siblings ...)
2026-09-04 16:17 ` [PATCH v3 19/21] arm64: percpu: Implement preemptible CMPXCHG ops Mark Rutland
@ 2026-09-04 16:17 ` Mark Rutland
2026-09-04 16:17 ` [PATCH v3 21/21] arm64: percpu: Remove _pcp_protect*() wrappers Mark Rutland
` (6 subsequent siblings)
20 siblings, 0 replies; 23+ messages in thread
From: Mark Rutland @ 2026-09-04 16:17 UTC (permalink / raw)
To: linux-arm-kernel
Cc: mark.rutland, vladimir.murzin, usama.anjum, peterz,
catalin.marinas, david.laight.linux, stable, ruanjinjie,
james.morse, yang, cl, maz, will, ardb
Use the PCPU GPR infrastructure to implement this_cpu_cmpxchg128().
Note that before this patch, the LL/SC and LSE implementation was chosen
with an alternative branch. After this patch the implementations are
patched inline, matching the style of the other percpu ops.
The sub-optimal register shuffling before and after this patch occurs
because we hard-code specific registers, as LLVM (currently) lacks a way
to place a 128-bit type in a compiler-allocated even-odd pair of
registers for inline assembly. We should be able to improve that in
future, given compiler support.
Test case:
| u128 outline_this_cpu_cmpxchg128(u128 __percpu *p, u128 old, u128 new)
| {
| return this_cpu_cmpxchg128(*p, old, new);
| }
Generated code before this patch (v7.2-rc4):
| <outline_this_cpu_cmpxchg128>:
| paciasp
| stp x29, x30, [sp, #-32]!
| mov x6, x0
| mov x0, x2
| mov x29, sp
| mov x1, x3
| mrs x2, sp_el0
| ldr w7, [x2, #8]
| add w7, w7, #0x1
| str w7, [x2, #8]
| mrs x2, tpidr_el1
| add x6, x6, x2
| b 4f
| mov x2, x4
| mov x3, x5
| mov x4, x6
| mov x5, x0
| mov x7, x1
| casp x0, x1, x2, x3, [x6]
| 1: mrs x3, sp_el0
| ldr x2, [x3, #8]
| sub x2, x2, #0x1
| str w2, [x3, #8]
| cbz x2, 2f
| ldr x2, [x3, #8]
| cbnz x2, 3f
| 2: stp x0, x1, [sp, #16]
| bl preempt_schedule_notrace
| ldp x0, x1, [sp, #16]
| 3: ldp x29, x30, [sp], #32
| autiasp
| ret
| 4: prfm pstl1strm, [x6]
| 5: ldxp x8, x7, [x6]
| cmp x8, x0
| ccmp x7, x3, #0x0, eq
| b.ne 6f
| stxp w2, x4, x5, [x6]
| cbnz w2, 5b
| 6: mov x0, x8
| mov x1, x7
| b 1b
Generated code after this patch:
| <outline_this_cpu_cmpxchg128>:
| mov x6, x0
| mov x1, x3
| mov x0, x2
| mov x3, x5
| mrs x7, sp_el0
| mov x2, x4
| mov w5, #0x10a6
| strh w5, [x7, #20]
| mrs x5, tpidr_el1
| add x4, x6, x5
| prfm pstl1strm, [x4]
| 1: ldxp x9, x10, [x4]
| cmp x9, x0
| ccmp x10, x1, #0x0, eq
| b.ne 2f
| stxp w8, x2, x3, [x4]
| cbnz w8, 1b
| 2: strh wzr, [x7, #20]
| mov x0, x9
| mov x1, x10
| ret
Signed-off-by: Mark Rutland <mark.rutland@arm.com>
Tested-by: Muhammad Usama Anjum <usama.anjum@arm.com>
Cc: Ada Couprie Diaz <ada.coupriediaz@arm.com>
Cc: Ard Biesheuvel <ardb@kernel.org>
Cc: Catalin Marinas <catalin.marinas@arm.com>
Cc: James Morse <james.morse@arm.com>
Cc: Jinjie Ruan <ruanjinjie@huawei.com>
Cc: Marc Zyngier <maz@kernel.org>
Cc: Peter Zijlstra <peterz@infradead.org>
Cc: Vladimir Murzin <vladimir.murzin@arm.com>
Cc: Will Deacon <will@kernel.org>
Cc: Yang Shi <yang@os.amperecomputing.com>
---
arch/arm64/include/asm/percpu.h | 70 +++++++++++++++++++++++++++------
1 file changed, 57 insertions(+), 13 deletions(-)
diff --git a/arch/arm64/include/asm/percpu.h b/arch/arm64/include/asm/percpu.h
index f19a82437b3fd..5cd98e20e825d 100644
--- a/arch/arm64/include/asm/percpu.h
+++ b/arch/arm64/include/asm/percpu.h
@@ -490,19 +490,63 @@ PERCPU_CMPXCHG_OP(x, , 64)
#define this_cpu_cmpxchg64(pcp, o, n) this_cpu_cmpxchg_8(pcp, o, n)
-#define this_cpu_cmpxchg128(pcp, o, n) \
-({ \
- typedef typeof(pcp) pcp_op_T__; \
- u128 old__, new__, ret__; \
- pcp_op_T__ *ptr__; \
- old__ = o; \
- new__ = n; \
- preempt_disable_notrace(); \
- ptr__ = raw_cpu_ptr(&(pcp)); \
- ret__ = cmpxchg128_local((void *)ptr__, old__, new__); \
- preempt_enable_notrace(); \
- ret__; \
-})
+static inline u128
+__percpu_cmpxchg_128(void __percpu *pcp, u128 old, u128 new)
+{
+ u16 *gprs = ¤t_thread_info()->pcpu_gprs;
+ unsigned long addr;
+ unsigned long off;
+ union __u128_halves r, o = { .full = (old) },
+ n = { .full = (new) };
+ register unsigned long ol asm ("x0") = o.low;
+ register unsigned long oh asm ("x1") = o.high;
+ register unsigned long nl asm ("x2") = n.low;
+ register unsigned long nh asm ("x3") = n.high;
+ unsigned long rl, rh;
+ unsigned long tmp;
+
+ asm volatile (
+ __PCPU_GPRS_BEGIN("%[gprs]", "%[pcp]", "%[off]", "%[addr]")
+ ARM64_LSE_ATOMIC_INSN(
+ /* LL/SC */
+ " prfm pstl1strm, [%[addr]]\n"
+ "1: ldxp %[rl], %[rh], [%[addr]]\n"
+ " cmp %[rl], %[ol]\n"
+ " ccmp %[rh], %[oh], 0, eq\n"
+ " b.ne 2f\n"
+ " stxp %w[tmp], %[nl], %[nh], [%[addr]]\n"
+ " cbnz %w[tmp], 1b\n"
+ "2:\n"
+ ,
+ /* LSE atomics */
+ " casp %[ol], %[oh], %[nl], %[nh], [%[addr]]\n"
+ " mov %[rl], %[ol]\n"
+ " mov %[rh], %[oh]\n"
+ __nops(4)
+ )
+ __PCPU_GPRS_END("%[gprs]")
+ : [gprs] "=Qo" (*gprs),
+ [addr] "=&r" (addr),
+ [off] "=&r" (off),
+ [tmp] "=&r" (tmp),
+ [ol] "+&r" (ol),
+ [oh] "+&r" (oh),
+ [rl] "=&r" (rl),
+ [rh] "=&r" (rh)
+ : [pcp] "r" (pcp),
+ [nl] "r" (nl),
+ [nh] "r" (nh)
+ : "memory", "cc"
+ );
+
+ r.low = rl;
+ r.high = rh;
+
+ return r.full;
+}
+
+#define this_cpu_cmpxchg128(pcp, o, n) \
+ _pcp_wrap_return(__percpu_cmpxchg_128, pcp, o, n)
#ifdef __KVM_NVHE_HYPERVISOR__
extern unsigned long __hyp_per_cpu_offset(unsigned int cpu);
--
2.30.2
^ permalink raw reply related [flat|nested] 23+ messages in thread
* [PATCH v3 21/21] arm64: percpu: Remove _pcp_protect*() wrappers
2026-09-04 16:17 [PATCH v3 00/21] arm64: Preemptible this_cpu_*() operations Mark Rutland
` (13 preceding siblings ...)
2026-09-04 16:17 ` [PATCH v3 20/21] arm64: percpu: Implement preemptible CMPXCHG128 ops Mark Rutland
@ 2026-09-04 16:17 ` Mark Rutland
2026-09-04 16:54 ` [PATCH v3 01/21] arm64: percpu: Fix this_cpu_write() casting Mark Rutland
` (5 subsequent siblings)
20 siblings, 0 replies; 23+ messages in thread
From: Mark Rutland @ 2026-09-04 16:17 UTC (permalink / raw)
To: linux-arm-kernel
Cc: mark.rutland, vladimir.murzin, usama.anjum, peterz,
catalin.marinas, david.laight.linux, stable, ruanjinjie,
james.morse, yang, cl, maz, will, ardb
Now that all this_cpu_*() operations are preemptible, there are no users
of _pcp_protect() or _pcp_protect_return().
Remove them both, along with the associated comment regarding
preemption.
Signed-off-by: Mark Rutland <mark.rutland@arm.com>
Tested-by: Muhammad Usama Anjum <usama.anjum@arm.com>
Cc: Ada Couprie Diaz <ada.coupriediaz@arm.com>
Cc: Ard Biesheuvel <ardb@kernel.org>
Cc: Catalin Marinas <catalin.marinas@arm.com>
Cc: James Morse <james.morse@arm.com>
Cc: Jinjie Ruan <ruanjinjie@huawei.com>
Cc: Marc Zyngier <maz@kernel.org>
Cc: Peter Zijlstra <peterz@infradead.org>
Cc: Vladimir Murzin <vladimir.murzin@arm.com>
Cc: Will Deacon <will@kernel.org>
Cc: Yang Shi <yang@os.amperecomputing.com>
---
arch/arm64/include/asm/percpu.h | 27 ---------------------------
1 file changed, 27 deletions(-)
diff --git a/arch/arm64/include/asm/percpu.h b/arch/arm64/include/asm/percpu.h
index 5cd98e20e825d..6ec6e212932d3 100644
--- a/arch/arm64/include/asm/percpu.h
+++ b/arch/arm64/include/asm/percpu.h
@@ -368,33 +368,6 @@ PERCPU_CMPXCHG_OP(x, , 64)
#undef PERCPU_XCHG_OP
#undef PERCPU_CMPXCHG_OP
-/*
- * It would be nice to avoid the conditional call into the scheduler when
- * re-enabling preemption for preemptible kernels, but doing that in a way
- * which builds inside a module would mean messing directly with the preempt
- * count. If you do this, peterz and tglx will hunt you down.
- *
- * Not to mention it'll break the actual preemption model for missing a
- * preemption point when TIF_NEED_RESCHED gets set while preemption is
- * disabled.
- */
-
-#define _pcp_protect(op, pcp, ...) \
-({ \
- preempt_disable_notrace(); \
- op(raw_cpu_ptr(&(pcp)), __VA_ARGS__); \
- preempt_enable_notrace(); \
-})
-
-#define _pcp_protect_return(op, pcp, args...) \
-({ \
- typeof(pcp) __retval; \
- preempt_disable_notrace(); \
- __retval = (typeof(pcp))op(raw_cpu_ptr(&(pcp)), ##args); \
- preempt_enable_notrace(); \
- __retval; \
-})
-
#define _pcp_wrap(op, pcp, ...) \
({ \
op(&(pcp), __VA_ARGS__); \
--
2.30.2
^ permalink raw reply related [flat|nested] 23+ messages in thread
* [PATCH v3 01/21] arm64: percpu: Fix this_cpu_write() casting
2026-09-04 16:17 [PATCH v3 00/21] arm64: Preemptible this_cpu_*() operations Mark Rutland
` (14 preceding siblings ...)
2026-09-04 16:17 ` [PATCH v3 21/21] arm64: percpu: Remove _pcp_protect*() wrappers Mark Rutland
@ 2026-09-04 16:54 ` Mark Rutland
2026-09-04 16:56 ` [PATCH v3 02/21] arm64: percpu: Fix this_cpu_and() mask generation Mark Rutland
` (4 subsequent siblings)
20 siblings, 0 replies; 23+ messages in thread
From: Mark Rutland @ 2026-09-04 16:54 UTC (permalink / raw)
To: linux-arm-kernel
Cc: mark.rutland, vladimir.murzin, usama.anjum, peterz,
catalin.marinas, david.laight.linux, stable, ruanjinjie,
james.morse, yang, cl, maz, will, ardb
The arm64 implementation of this_cpu_write() casts 'val' to unsigned
long. This is necessary to handle cases where 'val' is a pointer type,
and to avoid spurious compiler warnings for the (unreachable!) cases
where the pointer type would be cast to a smaller integer type.
Unfortunately, the cast is applied to 'val' rather than '(val)', which
won't always generate the expected value when 'val' is an expression.
For example, for this_cpu_write(pcp, zero - 1), where 'pcp' is a u64 and
'zero' is a u32:
* 'zero' ===> (u32) 0x00000000
* 'zero - 1' ===> (u32) 0xffffffff
* '(unsigned long)zero - 1' ===> (u64) 0xffffffffffffffff
* '(unsigned long)(zero - 1)' ===> (u64) 0x00000000ffffffff
Fix this by adding brackets around 'val'
The bug described above can be seen from the disassembly of the
following test code:
| void this_cpu_write_zero_minus_1(u64 __percpu *pcp)
| {
| u32 zero = 0;
| this_cpu_write(*pcp, zero - 1);
| }
|
| void this_cpu_write_zero_minus_1_brackets(u64 __percpu *pcp)
| {
| u32 zero = 0;
| this_cpu_write(*pcp, (zero - 1));
| }
Generated code before this patch:
| <this_cpu_write_zero_minus_1>:
| paciasp
| stp x29, x30, [sp, #-16]!
| mrs x1, sp_el0
| mov x29, sp
| ldr w2, [x1, #8]
| add w2, w2, #0x1
| str w2, [x1, #8]
| mov x3, #0xffffffffffffffff
| mrs x2, tpidr_el1
| str x3, [x0, x2]
| ldr x0, [x1, #8]
| add x0, x0, x3
| str w0, [x1, #8]
| cbz x0, 1f
| ldr x0, [x1, #8]
| cbnz x0, 2f
| 1: bl preempt_schedule_notrace
| 2: ldp x29, x30, [sp], #16
| autiasp
| ret
|
| <this_cpu_write_zero_minus_1_brackets>:
| paciasp
| stp x29, x30, [sp, #-16]!
| mrs x1, sp_el0
| mov x29, sp
| ldr w2, [x1, #8]
| add w2, w2, #0x1
| str w2, [x1, #8]
| mov x3, #0xffffffff
| mrs x2, tpidr_el1
| str x3, [x0, x2]
| ldr x0, [x1, #8]
| sub x0, x0, #0x1
| str w0, [x1, #8]
| cbz x0, 1f
| ldr x0, [x1, #8]
| cbnz x0, 2f
| 1: bl preempt_schedule_notrace
| 2: ldp x29, x30, [sp], #16
| autiasp
| ret
Generated code after this patch:
| <this_cpu_write_zero_minus_1>:
| paciasp
| stp x29, x30, [sp, #-16]!
| mrs x1, sp_el0
| mov x29, sp
| ldr w2, [x1, #8]
| add w2, w2, #0x1
| str w2, [x1, #8]
| mov x3, #0xffffffff
| mrs x2, tpidr_el1
| str x3, [x0, x2]
| ldr x0, [x1, #8]
| sub x0, x0, #0x1
| str w0, [x1, #8]
| cbz x0, 1f
| ldr x0, [x1, #8]
| cbnz x0, 2f
| 1: bl preempt_schedule_notrace
| 2: ldp x29, x30, [sp], #16
| autiasp
| ret
|
| <this_cpu_write_zero_minus_1_brackets>:
| b this_cpu_write_zero_minus_1
Fixes: 959bf2fd03b5 ("arm64: percpu: Rewrite per-cpu ops to allow use of LSE atomics")
Reported-by: David Laight <david.laight.linux@gmail.com>
Signed-off-by: Mark Rutland <mark.rutland@arm.com>
Reviewed-by: David Laight <david.laight.linux@gmail.com>
Reviewed-by: Jinjie Ruan <ruanjinjie@huawei.com>
Tested-by: Muhammad Usama Anjum <usama.anjum@arm.com>
Acked-by: Christopher Lameter (Ampere) <cl@gentwo.org>
Cc: Ada Couprie Diaz <ada.coupriediaz@arm.com>
Cc: Ard Biesheuvel <ardb@kernel.org>
Cc: Catalin Marinas <catalin.marinas@arm.com>
Cc: James Morse <james.morse@arm.com>
Cc: Marc Zyngier <maz@kernel.org>
Cc: Peter Zijlstra <peterz@infradead.org>
Cc: Vladimir Murzin <vladimir.murzin@arm.com>
Cc: Will Deacon <will@kernel.org>
Cc: Yang Shi <yang@os.amperecomputing.com>
Cc: stable@vger.kernel.org
---
arch/arm64/include/asm/percpu.h | 8 ++++----
1 file changed, 4 insertions(+), 4 deletions(-)
diff --git a/arch/arm64/include/asm/percpu.h b/arch/arm64/include/asm/percpu.h
index b57b2bb009677..63bbfd4944a37 100644
--- a/arch/arm64/include/asm/percpu.h
+++ b/arch/arm64/include/asm/percpu.h
@@ -179,13 +179,13 @@ PERCPU_RET_OP(add, add, ldadd)
_pcp_protect_return(__percpu_read_64, pcp)
#define this_cpu_write_1(pcp, val) \
- _pcp_protect(__percpu_write_8, pcp, (unsigned long)val)
+ _pcp_protect(__percpu_write_8, pcp, (unsigned long)(val))
#define this_cpu_write_2(pcp, val) \
- _pcp_protect(__percpu_write_16, pcp, (unsigned long)val)
+ _pcp_protect(__percpu_write_16, pcp, (unsigned long)(val))
#define this_cpu_write_4(pcp, val) \
- _pcp_protect(__percpu_write_32, pcp, (unsigned long)val)
+ _pcp_protect(__percpu_write_32, pcp, (unsigned long)(val))
#define this_cpu_write_8(pcp, val) \
- _pcp_protect(__percpu_write_64, pcp, (unsigned long)val)
+ _pcp_protect(__percpu_write_64, pcp, (unsigned long)(val))
#define this_cpu_add_1(pcp, val) \
_pcp_protect(__percpu_add_case_8, pcp, val)
--
2.30.2
^ permalink raw reply related [flat|nested] 23+ messages in thread
* [PATCH v3 02/21] arm64: percpu: Fix this_cpu_and() mask generation
2026-09-04 16:17 [PATCH v3 00/21] arm64: Preemptible this_cpu_*() operations Mark Rutland
` (15 preceding siblings ...)
2026-09-04 16:54 ` [PATCH v3 01/21] arm64: percpu: Fix this_cpu_write() casting Mark Rutland
@ 2026-09-04 16:56 ` Mark Rutland
2026-09-04 16:56 ` [PATCH v3 04/21] arm64: cmpxchg: LL/SC: Avoid redundant extension Mark Rutland
` (3 subsequent siblings)
20 siblings, 0 replies; 23+ messages in thread
From: Mark Rutland @ 2026-09-04 16:56 UTC (permalink / raw)
To: linux-arm-kernel
Cc: mark.rutland, vladimir.murzin, usama.anjum, peterz,
catalin.marinas, david.laight.linux, stable, ruanjinjie,
james.morse, yang, cl, maz, will, ardb
The arm64 implementation of this_cpu_and(pcp, val) is built in terms of
ANDNOT operations, which requires the 'val' argument to be bitwise
negated. The bitwise negation is not implemented correctly, with two
bugs described below.
(1) The bitwise negation is performed as '~val' rather than '~(val)'.
This won't always generate the expected value when 'val' is an
expression.
For example, for this_cpu_and(pcp, 1 - 1):
* 'val' is '1 - 1' ===> (int) 0x00000000
* '~val' is '~1 - 1' ===> (int) 0xfffffffd
* '~(val)' is '~(1 - 1)' ===> (int) 0xffffffff
... and thus bit[1] of 'pcp' would be preserved unexpectedly by the
ANDNOT operation.
(2) The bitwise negation is performed on 'val' before it has been cast
to (at least) the width of 'pcp'. This won't always generate the
expected value for the upper bits.
For example, for this_cpu_and(pcp, zero), where 'pcp' is a u64 and
'zero' is a u32:
* 'zero' ===> (u32) 0x00000000
* '~(zero)' ===> (u32) 0xffffffff
* '(u64)~(zero)' ===> (u64) 0x00000000ffffffff
* '~((u64)(zero))' ===> (u64) 0xffffffffffffffff
... and thus bits[63:32] of 'pcp' would be preserved unexpectedly by
the ANDNOT operation.
Fix these issues by adding brackets around 'val', and by casting 'val'
to an appropriately-sized type before bitwise negation.
The bugs described above can be seen from the disassembly of the
following test code:
| void this_cpu_and_u64_zero(u64 __percpu *pcp)
| {
| u64 zero = 0;
| this_cpu_and(*pcp, zero);
| }
|
| void this_cpu_and_u32_zero(u64 __percpu *pcp)
| {
| u32 zero = 0;
| this_cpu_and(*pcp, zero);
| }
|
| void this_cpu_and_expr_zero(u64 __percpu *pcp)
| {
| this_cpu_and(*pcp, 1 - 1);
| }
|
| void this_cpu_and_expr_zero_brackets(u64 __percpu *pcp)
| {
| this_cpu_and(*pcp, (1 - 1));
| }
Before this patch:
| <this_cpu_and_u64_zero>:
| paciasp
| stp x29, x30, [sp, #-16]!
| mrs x1, sp_el0
| mov x29, sp
| ldr w2, [x1, #8]
| add w2, w2, #0x1
| str w2, [x1, #8]
| mov x3, #0xffffffffffffffff // #-1
| mrs x2, tpidr_el1
| add x0, x0, x2
| 1: ldxr x5, [x0]
| bic x5, x5, x3
| stxr w4, x5, [x0]
| cbnz w4, 1b
| ldr x0, [x1, #8]
| add x0, x0, x3
| str w0, [x1, #8]
| cbz x0, 2f
| ldr x0, [x1, #8]
| cbnz x0, 3f
| 2: bl preempt_schedule_notrace
| 3: ldp x29, x30, [sp], #16
| autiasp
| ret
|
| <this_cpu_and_u32_zero>:
| paciasp
| stp x29, x30, [sp, #-16]!
| mrs x1, sp_el0
| mov x29, sp
| ldr w2, [x1, #8]
| add w2, w2, #0x1
| str w2, [x1, #8]
| mov x3, #0xffffffff // #4294967295
| mrs x2, tpidr_el1
| add x0, x0, x2
| 1: ldxr x5, [x0]
| bic x5, x5, x3
| stxr w4, x5, [x0]
| cbnz w4, 1b
| ldr x0, [x1, #8]
| sub x0, x0, #0x1
| str w0, [x1, #8]
| cbz x0, 2f
| ldr x0, [x1, #8]
| cbnz x0, 3f
| 2: bl preempt_schedule_notrace
| 3: ldp x29, x30, [sp], #16
| autiasp
| ret
|
| <this_cpu_and_expr_zero>:
| paciasp
| stp x29, x30, [sp, #-16]!
| mrs x1, sp_el0
| mov x29, sp
| ldr w2, [x1, #8]
| add w2, w2, #0x1
| str w2, [x1, #8]
| mov x3, #0xfffffffffffffffd // #-3
| mrs x2, tpidr_el1
| add x0, x0, x2
| 1: ldxr x5, [x0]
| bic x5, x5, x3
| stxr w4, x5, [x0]
| cbnz w4, 1b
| ldr x0, [x1, #8]
| sub x0, x0, #0x1
| str w0, [x1, #8]
| cbz x0, 2f
| ldr x0, [x1, #8]
| cbnz x0, 3f
| 2: bl preempt_schedule_notrace
| 3: ldp x29, x30, [sp], #16
| autiasp
| ret
After this patch:
| <this_cpu_and_u64_zero>:
| paciasp
| stp x29, x30, [sp, #-16]!
| mrs x1, sp_el0
| mov x29, sp
| ldr w2, [x1, #8]
| add w2, w2, #0x1
| str w2, [x1, #8]
| mov x3, #0xffffffffffffffff // #-1
| mrs x2, tpidr_el1
| add x0, x0, x2
| 1: ldxr x5, [x0]
| bic x5, x5, x3
| stxr w4, x5, [x0]
| cbnz w4, 1b
| ldr x0, [x1, #8]
| add x0, x0, x3
| str w0, [x1, #8]
| cbz x0, 2f
| ldr x0, [x1, #8]
| cbnz x0, 3f
| 2: bl 0 <preempt_schedule_notrace>
| 3: ldp x29, x30, [sp], #16
| autiasp
| ret
|
| <this_cpu_and_u32_zero>:
| b this_cpu_and_u64_zero
|
| <this_cpu_and_expr_zero>:
| b this_cpu_and_u64_zero
|
| <this_cpu_and_expr_zero_brackets>:
| b this_cpu_and_u64_zero
Fixes: 959bf2fd03b5 ("arm64: percpu: Rewrite per-cpu ops to allow use of LSE atomics")
Signed-off-by: Mark Rutland <mark.rutland@arm.com>
Reviewed-by: Jinjie Ruan <ruanjinjie@huawei.com>
Tested-by: Muhammad Usama Anjum <usama.anjum@arm.com>
Acked-by: Christopher Lameter (Ampere) <cl@gentwo.org>
Cc: Ada Couprie Diaz <ada.coupriediaz@arm.com>
Cc: Ard Biesheuvel <ardb@kernel.org>
Cc: Catalin Marinas <catalin.marinas@arm.com>
Cc: James Morse <james.morse@arm.com>
Cc: Marc Zyngier <maz@kernel.org>
Cc: Peter Zijlstra <peterz@infradead.org>
Cc: Vladimir Murzin <vladimir.murzin@arm.com>
Cc: Will Deacon <will@kernel.org>
Cc: Yang Shi <yang@os.amperecomputing.com>
Cc: stable@vger.kernel.org
---
arch/arm64/include/asm/percpu.h | 8 ++++----
1 file changed, 4 insertions(+), 4 deletions(-)
diff --git a/arch/arm64/include/asm/percpu.h b/arch/arm64/include/asm/percpu.h
index 63bbfd4944a37..31193bcf89a2b 100644
--- a/arch/arm64/include/asm/percpu.h
+++ b/arch/arm64/include/asm/percpu.h
@@ -206,13 +206,13 @@ PERCPU_RET_OP(add, add, ldadd)
_pcp_protect_return(__percpu_add_return_case_64, pcp, val)
#define this_cpu_and_1(pcp, val) \
- _pcp_protect(__percpu_andnot_case_8, pcp, ~val)
+ _pcp_protect(__percpu_andnot_case_8, pcp, ~(u8)(val))
#define this_cpu_and_2(pcp, val) \
- _pcp_protect(__percpu_andnot_case_16, pcp, ~val)
+ _pcp_protect(__percpu_andnot_case_16, pcp, ~(u16)(val))
#define this_cpu_and_4(pcp, val) \
- _pcp_protect(__percpu_andnot_case_32, pcp, ~val)
+ _pcp_protect(__percpu_andnot_case_32, pcp, ~(u32)(val))
#define this_cpu_and_8(pcp, val) \
- _pcp_protect(__percpu_andnot_case_64, pcp, ~val)
+ _pcp_protect(__percpu_andnot_case_64, pcp, ~(u64)(val))
#define this_cpu_or_1(pcp, val) \
_pcp_protect(__percpu_or_case_8, pcp, val)
--
2.30.2
^ permalink raw reply related [flat|nested] 23+ messages in thread
* [PATCH v3 04/21] arm64: cmpxchg: LL/SC: Avoid redundant extension
2026-09-04 16:17 [PATCH v3 00/21] arm64: Preemptible this_cpu_*() operations Mark Rutland
` (16 preceding siblings ...)
2026-09-04 16:56 ` [PATCH v3 02/21] arm64: percpu: Fix this_cpu_and() mask generation Mark Rutland
@ 2026-09-04 16:56 ` Mark Rutland
2026-09-04 16:57 ` [PATCH v3 06/21] arm64: preempt: Simplify and optimize __preempt_count_dec_and_test() Mark Rutland
` (2 subsequent siblings)
20 siblings, 0 replies; 23+ messages in thread
From: Mark Rutland @ 2026-09-04 16:56 UTC (permalink / raw)
To: linux-arm-kernel
Cc: mark.rutland, vladimir.murzin, usama.anjum, peterz,
catalin.marinas, david.laight.linux, stable, ruanjinjie,
james.morse, yang, cl, maz, will, ardb
The __ll_sc__cmpxchg_case_##name##sz() template always takes 'old' as an
unsigned long. When 'sz' is less than 32 bits, the 'old' is explicitly
truncated so that old[31:sz-1] is zero, which is necessary so that the
W-register EOR+CBNZ sequence doesn't fail spuriously.
For 32-bit types specifically, no extension is necessary, but as 'old'
is 64 bits, the caller must extend 'old' to 64 bits. This leads to a
redundant instruction (typically a MOV) to extend the value, as can be
seen from disassembly of the test case below.
Avoid this by always taking 'old' as a 'u##sz' type, and explicitly
zero-extend this to a 64-bit or 32-bit type matching the X or W register
used by the assembly. To make this work, the table of cases now
explicitly lists 'x' for the cases where an X register is used.
For 32-bit types this removes the redundant instruction. For 64-bit
types there is no truncation, and hence no change. For {16,8}-bit types,
the existing zero-extension to 64-bit is equivalent to the new
zero-extension to 32-bit, and compilers happen to use the same
instructions for this.
I've placed the logic for zero extension into a new <asm/xwreg.h> header
as it will be used by other logic in subsequent patches.
Test case:
| u32 outline_cmpxchg_u32(u32 *p, u32 o, u32 n)
| {
| return cmpxchg(p, o, n);
| }
Before this patch:
| <outline_cmpxchg_u32>:
| b 1f
| casal w1, w2, [x0]
| mov w0, w1
| ret
| 1: mov w3, w1
| prfm pstl1strm, [x0]
| 2: ldxr w1, [x0]
| eor w4, w1, w3
| cbnz w4, 3f
| stlxr w4, w2, [x0]
| cbnz w4, 2b
| dmb ish
| 3: mov w0, w1
| ret
After this patch:
| <outline_cmpxchg_u32>:
| b 1f
| casal w1, w2, [x0]
| mov w0, w1
| ret
| 1: prfm pstl1strm, [x0]
| 2: ldxr w3, [x0]
| eor w4, w3, w1
| cbnz w4, 3f
| stlxr w4, w2, [x0]
| cbnz w4, 2b
| dmb ish
| 3: mov w0, w3
| ret
Signed-off-by: Mark Rutland <mark.rutland@arm.com>
Tested-by: Muhammad Usama Anjum <usama.anjum@arm.com>
Cc: Ada Couprie Diaz <ada.coupriediaz@arm.com>
Cc: Ard Biesheuvel <ardb@kernel.org>
Cc: Catalin Marinas <catalin.marinas@arm.com>
Cc: James Morse <james.morse@arm.com>
Cc: Jinjie Ruan <ruanjinjie@huawei.com>
Cc: Marc Zyngier <maz@kernel.org>
Cc: Peter Zijlstra <peterz@infradead.org>
Cc: Vladimir Murzin <vladimir.murzin@arm.com>
Cc: Will Deacon <will@kernel.org>
Cc: Yang Shi <yang@os.amperecomputing.com>
---
arch/arm64/include/asm/atomic_ll_sc.h | 27 +++++++++++++--------------
arch/arm64/include/asm/xwreg.h | 16 ++++++++++++++++
2 files changed, 29 insertions(+), 14 deletions(-)
create mode 100644 arch/arm64/include/asm/xwreg.h
diff --git a/arch/arm64/include/asm/atomic_ll_sc.h b/arch/arm64/include/asm/atomic_ll_sc.h
index 89d2ba2723590..160c1251f0ec9 100644
--- a/arch/arm64/include/asm/atomic_ll_sc.h
+++ b/arch/arm64/include/asm/atomic_ll_sc.h
@@ -11,6 +11,7 @@
#define __ASM_ATOMIC_LL_SC_H
#include <linux/stringify.h>
+#include <asm/xwreg.h>
#ifndef CONFIG_CC_HAS_K_CONSTRAINT
#define K
@@ -239,20 +240,17 @@ __ll_sc_atomic64_dec_if_positive(atomic64_t *v)
#define __CMPXCHG_CASE(w, sfx, name, sz, mb, acq, rel, cl, constraint) \
static __always_inline u##sz \
__ll_sc__cmpxchg_case_##name##sz(volatile void *ptr, \
- unsigned long old, \
+ u##sz old, \
u##sz new) \
{ \
+ /* \
+ * Sub-word sizes require zero extension so that EOR+CBNZ won't \
+ * consume non-zero upper bits of the register containing "old".\
+ */ \
+ xwreg_t(w) cmpval = xwreg_zero_extend(old, w, sz); \
unsigned long tmp; \
u##sz oldval; \
\
- /* \
- * Sub-word sizes require explicit casting so that the compare \
- * part of the cmpxchg doesn't end up interpreting non-zero \
- * upper bits of the register containing "old". \
- */ \
- if (sz < 32) \
- old = (u##sz)old; \
- \
asm volatile( \
" prfm pstl1strm, %[v]\n" \
"1: ld" #acq "xr" #sfx "\t%" #w "[oldval], %[v]\n" \
@@ -264,7 +262,8 @@ __ll_sc__cmpxchg_case_##name##sz(volatile void *ptr, \
"2:" \
: [tmp] "=&r" (tmp), [oldval] "=&r" (oldval), \
[v] "+Q" (*(u##sz *)ptr) \
- : [old] __stringify(constraint) "r" (old), [new] "r" (new) \
+ : [old] __stringify(constraint) "r" (cmpval), \
+ [new] "r" (new) \
: cl); \
\
return oldval; \
@@ -278,19 +277,19 @@ __ll_sc__cmpxchg_case_##name##sz(volatile void *ptr, \
__CMPXCHG_CASE(w, b, , 8, , , , , K)
__CMPXCHG_CASE(w, h, , 16, , , , , K)
__CMPXCHG_CASE(w, , , 32, , , , , K)
-__CMPXCHG_CASE( , , , 64, , , , , L)
+__CMPXCHG_CASE(x, , , 64, , , , , L)
__CMPXCHG_CASE(w, b, acq_, 8, , a, , "memory", K)
__CMPXCHG_CASE(w, h, acq_, 16, , a, , "memory", K)
__CMPXCHG_CASE(w, , acq_, 32, , a, , "memory", K)
-__CMPXCHG_CASE( , , acq_, 64, , a, , "memory", L)
+__CMPXCHG_CASE(x, , acq_, 64, , a, , "memory", L)
__CMPXCHG_CASE(w, b, rel_, 8, , , l, "memory", K)
__CMPXCHG_CASE(w, h, rel_, 16, , , l, "memory", K)
__CMPXCHG_CASE(w, , rel_, 32, , , l, "memory", K)
-__CMPXCHG_CASE( , , rel_, 64, , , l, "memory", L)
+__CMPXCHG_CASE(x, , rel_, 64, , , l, "memory", L)
__CMPXCHG_CASE(w, b, mb_, 8, dmb ish, , l, "memory", K)
__CMPXCHG_CASE(w, h, mb_, 16, dmb ish, , l, "memory", K)
__CMPXCHG_CASE(w, , mb_, 32, dmb ish, , l, "memory", K)
-__CMPXCHG_CASE( , , mb_, 64, dmb ish, , l, "memory", L)
+__CMPXCHG_CASE(x, , mb_, 64, dmb ish, , l, "memory", L)
#undef __CMPXCHG_CASE
diff --git a/arch/arm64/include/asm/xwreg.h b/arch/arm64/include/asm/xwreg.h
new file mode 100644
index 0000000000000..d55e3ac68f7eb
--- /dev/null
+++ b/arch/arm64/include/asm/xwreg.h
@@ -0,0 +1,16 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+#ifndef __ASM_XWREG_H
+#define __ASM_XWREG_H
+
+#include <asm/types.h>
+
+#define __xwreg_t_x u64
+#define __xwreg_t_w u32
+#define xwreg_t(xw) __xwreg_t_##xw
+
+/*
+ * Zero extend 'v' from 'sz' bits (8/16/32/64) to fill an X or W register.
+ */
+#define xwreg_zero_extend(v, xw, sz) ((xwreg_t(xw))(u##sz)(v))
+
+#endif /* __ASM_XWREG_H */
--
2.30.2
^ permalink raw reply related [flat|nested] 23+ messages in thread
* [PATCH v3 06/21] arm64: preempt: Simplify and optimize __preempt_count_dec_and_test()
2026-09-04 16:17 [PATCH v3 00/21] arm64: Preemptible this_cpu_*() operations Mark Rutland
` (17 preceding siblings ...)
2026-09-04 16:56 ` [PATCH v3 04/21] arm64: cmpxchg: LL/SC: Avoid redundant extension Mark Rutland
@ 2026-09-04 16:57 ` Mark Rutland
2026-09-04 16:59 ` [PATCH v3 13/21] arm64: entry: sdei: Make 'tsk' available Mark Rutland
2026-09-04 17:43 ` [PATCH v3 00/21] arm64: Preemptible this_cpu_*() operations Mark Rutland
20 siblings, 0 replies; 23+ messages in thread
From: Mark Rutland @ 2026-09-04 16:57 UTC (permalink / raw)
To: linux-arm-kernel
Cc: mark.rutland, vladimir.murzin, usama.anjum, peterz,
catalin.marinas, david.laight.linux, stable, ruanjinjie,
james.morse, yang, cl, maz, will, ardb
In arm64's __preempt_count_dec_and_test(), the final conditional load of
'ti->preempt_count' is always executed in the common case. The
conditional load leads to unfortunate code generation for
preempt_enable[_notrace](), and it would be better to unconditionally
load 'ti->preempt_count', as described below.
On arm64, struct thread_info contains the following union:
| union {
| u64 preempt_count;
| struct {
| u32 count;
| u32 need_resched;
| } preempt;
| };
Note: 'need_resched' is encoded so that '0' means a reschedule is
needed, and '1' means a reschedule is NOT needed.
The core logic of __preempt_count_dec_and_test() is:
| static inline bool __preempt_count_dec_and_test(void)
| {
| struct thread_info *ti = current_thread_info();
| u64 pc = READ_ONCE(ti->preempt_count);
|
| WRITE_ONCE(ti->preempt.count, --pc);
|
| return !pc || !READ_ONCE(ti->preempt_count);
| }
The '!pc' condition can only be true when both:
* The initial value of 'need_resched' was 0, meaning that a reschedule
is needed. This should be rare.
* The initial value of 'count' was exactly 1. This cannot be true for a
nested preempt_disable() ... preempt_enable() sequence.
Hence in common cases, '!pc' will be false, and it's necessary to
execute the final READ_ONCE(ti->preempt_count).
This results in a conditional branch in the common case, as can be seen
when __preempt_count_dec_and_test() is outlined:
| <outline___preempt_count_dec_and_test>:
| mrs x2, sp_el0
| ldr x1, [x2, #8]
| mov w0, #0x1
| sub x1, x1, #0x1
| str w1, [x2, #8]
| cbz x1, 1f
| ldr x0, [x2, #8]
| cmp x0, #0x0
| cset w0, eq // eq = none
| 1: ret
It would be better to avoid the special case for 'pc == 0', and to
always load 'ti->preempt_count' after decrementing 'ti->preempt.count'.
For the common cases this will remove a conditional branch. For the rare
cases where preemption is needed initially, this only adds a single
load, whose cost should be dominated by other factors.
Remove the special case for 'pc == 0', and always load the combined
'ti->preempt_count' after decrementing 'ti->preempt.count'.
The removal of the conditional branch helps with code generation, as the
compiler can more easily move a dependent slow path out-of-line, as
demonstrated with the following compiled with GCC 15.2.0:
| void outline_preempt_enable_notrace(void)
| {
| preempt_enable_notrace();
| }
Before this patch:
| <outline_preempt_enable_notrace>:
| mrs x1, sp_el0
| ldr x0, [x1, #8]
| sub x0, x0, #0x1
| str w0, [x1, #8]
| cbz x0, 1f
| ldr x0, [x1, #8]
| cbnz x0, 2f
| 1: paciasp
| stp x29, x30, [sp, #-16]!
| mov x29, sp
| bl preempt_schedule_notrace
| ldp x29, x30, [sp], #16
| autiasp
| ret
| 2: ret
After this patch:
| <outline_preempt_enable_notrace>:
| mrs x0, sp_el0
| ldr w1, [x0, #8]
| sub w1, w1, #0x1
| str w1, [x0, #8]
| ldr x0, [x0, #8]
| cbz x0, 1f
| ret
| 1: paciasp
| stp x29, x30, [sp, #-16]!
| mov x29, sp
| bl preempt_schedule_notrace
| ldp x29, x30, [sp], #16
| autiasp
| ret
Signed-off-by: Mark Rutland <mark.rutland@arm.com>
Reviewed-by: Jinjie Ruan <ruanjinjie@huawei.com>
Tested-by: Muhammad Usama Anjum <usama.anjum@arm.com>
Cc: Ada Couprie Diaz <ada.coupriediaz@arm.com>
Cc: Ard Biesheuvel <ardb@kernel.org>
Cc: Catalin Marinas <catalin.marinas@arm.com>
Cc: James Morse <james.morse@arm.com>
Cc: Marc Zyngier <maz@kernel.org>
Cc: Peter Zijlstra <peterz@infradead.org>
Cc: Vladimir Murzin <vladimir.murzin@arm.com>
Cc: Will Deacon <will@kernel.org>
Cc: Yang Shi <yang@os.amperecomputing.com>
---
arch/arm64/include/asm/preempt.h | 29 +++++++++++------------------
1 file changed, 11 insertions(+), 18 deletions(-)
diff --git a/arch/arm64/include/asm/preempt.h b/arch/arm64/include/asm/preempt.h
index 9ecc2766a9f21..92ae2eb95a738 100644
--- a/arch/arm64/include/asm/preempt.h
+++ b/arch/arm64/include/asm/preempt.h
@@ -75,30 +75,23 @@ static inline int __preempt_count_sub_return(int val)
return pc;
}
-static inline bool __preempt_count_dec_and_test(void)
-{
- struct thread_info *ti = current_thread_info();
- u64 pc = READ_ONCE(ti->preempt_count);
-
- /* Update only the count field, leaving need_resched unchanged */
- WRITE_ONCE(ti->preempt.count, --pc);
-
- /*
- * If we wrote back all zeroes, then we're preemptible and in
- * need of a reschedule. Otherwise, we need to reload the
- * preempt_count in case the need_resched flag was cleared by an
- * interrupt occurring between the non-atomic READ_ONCE/WRITE_ONCE
- * pair.
- */
- return !pc || !READ_ONCE(ti->preempt_count);
-}
-
static inline bool should_resched(int preempt_offset)
{
u64 pc = READ_ONCE(current_thread_info()->preempt_count);
return pc == preempt_offset;
}
+static inline bool __preempt_count_dec_and_test(void)
+{
+ /*
+ * We must load the combined 'prempt_count' after decrementing
+ * 'preempt.count' as an interrupt could modify 'need_resched' before
+ * __preempt_count_sub() writes back to 'preempt.count'.
+ */
+ __preempt_count_sub(1);
+ return should_resched(0);
+}
+
#ifdef CONFIG_PREEMPTION
void preempt_schedule(void);
--
2.30.2
^ permalink raw reply related [flat|nested] 23+ messages in thread
* [PATCH v3 13/21] arm64: entry: sdei: Make 'tsk' available
2026-09-04 16:17 [PATCH v3 00/21] arm64: Preemptible this_cpu_*() operations Mark Rutland
` (18 preceding siblings ...)
2026-09-04 16:57 ` [PATCH v3 06/21] arm64: preempt: Simplify and optimize __preempt_count_dec_and_test() Mark Rutland
@ 2026-09-04 16:59 ` Mark Rutland
2026-09-04 17:43 ` [PATCH v3 00/21] arm64: Preemptible this_cpu_*() operations Mark Rutland
20 siblings, 0 replies; 23+ messages in thread
From: Mark Rutland @ 2026-09-04 16:59 UTC (permalink / raw)
To: linux-arm-kernel
Cc: mark.rutland, vladimir.murzin, usama.anjum, peterz,
catalin.marinas, david.laight.linux, stable, ruanjinjie,
james.morse, yang, cl, maz, will, ardb
For regular entry/exit sequences, we use 'tsk' (x28) to hold the current
task pointer, allowing this to be used by various assembly macros. The
SDEI entry/exit sequence uses x28 to hold the value of the interrupted
sp_el0, and doesn't retain the current task pointer in a register.
These differences makes it awkward to share assembly macros across
regular entry/exit and SDEI entry/exit, and risk surprises.
Align the SDEI entry/exit sequence with regular entry/exit, keeping the
current task pointer in 'tsk', and preserving the interrupted sp_el0 in
another (callee-saved) register. I've used x20 as x19 was already in
used for the relevant sdei_registered_event, and these are the
lowest-numbered registers above x18 (which we must preserve for shadow
call stack).
Signed-off-by: Mark Rutland <mark.rutland@arm.com>
Tested-by: Muhammad Usama Anjum <usama.anjum@arm.com>
Cc: Ada Couprie Diaz <ada.coupriediaz@arm.com>
Cc: Ard Biesheuvel <ardb@kernel.org>
Cc: Catalin Marinas <catalin.marinas@arm.com>
Cc: James Morse <james.morse@arm.com>
Cc: Jinjie Ruan <ruanjinjie@huawei.com>
Cc: Marc Zyngier <maz@kernel.org>
Cc: Peter Zijlstra <peterz@infradead.org>
Cc: Vladimir Murzin <vladimir.murzin@arm.com>
Cc: Will Deacon <will@kernel.org>
Cc: Yang Shi <yang@os.amperecomputing.com>
---
arch/arm64/kernel/entry.S | 11 ++++++-----
1 file changed, 6 insertions(+), 5 deletions(-)
diff --git a/arch/arm64/kernel/entry.S b/arch/arm64/kernel/entry.S
index a6ec5c5a2d29c..0902c1bd9dd3d 100644
--- a/arch/arm64/kernel/entry.S
+++ b/arch/arm64/kernel/entry.S
@@ -1023,11 +1023,12 @@ SYM_CODE_START(__sdei_asm_handler)
/*
* We may have interrupted userspace, or a guest, or exit-from or
- * return-to either of these. We can't trust sp_el0, restore it.
+ * return-to either of these. Preserve the interrupted sp_el0, and
+ * initialize sp_el0 to the current task.
*/
- mrs x28, sp_el0
- ldr_this_cpu dst=x0, sym=__entry_task, tmp=x1
- msr sp_el0, x0
+ mrs x20, sp_el0
+ ldr_this_cpu dst=tsk, sym=__entry_task, tmp=x1
+ msr sp_el0, tsk
/* If we interrupted the kernel point to the previous stack/frame. */
and x0, x3, #0xc
@@ -1043,7 +1044,7 @@ SYM_CODE_START(__sdei_asm_handler)
mov x1, x19
bl __sdei_handler
- msr sp_el0, x28
+ msr sp_el0, x20
/* restore regs >x17 that firmware won't restore */
mov x4, x19 // keep x4 for __sdei_asm_exit_trampoline
ldp x18, x19, [x4, #SDEI_EVENT_INTREGS + 16 * 9]
--
2.30.2
^ permalink raw reply related [flat|nested] 23+ messages in thread
* Re: [PATCH v3 00/21] arm64: Preemptible this_cpu_*() operations
2026-09-04 16:17 [PATCH v3 00/21] arm64: Preemptible this_cpu_*() operations Mark Rutland
` (19 preceding siblings ...)
2026-09-04 16:59 ` [PATCH v3 13/21] arm64: entry: sdei: Make 'tsk' available Mark Rutland
@ 2026-09-04 17:43 ` Mark Rutland
20 siblings, 0 replies; 23+ messages in thread
From: Mark Rutland @ 2026-09-04 17:43 UTC (permalink / raw)
To: linux-arm-kernel
Cc: vladimir.murzin, ryan.roberts, usama.anjum, peterz,
catalin.marinas, david.laight.linux, stable, ruanjinjie,
james.morse, yang, cl, maz, david, ljs, will, ardb
On Fri, Sep 04, 2026 at 05:17:37PM +0100, Mark Rutland wrote:
> This series reworks arm64's this_cpu_*() operations such that they do
> not need to disable preemption.
Please ignore this posting for now. I'll spin a RESEND or v4 next week.
At least one patch didn't make it to the list, and some CCs have been
dropped.
Mark.
^ permalink raw reply [flat|nested] 23+ messages in thread
* Re: [PATCH v3 16/21] arm64: percpu: Implement preemptible void RMW ops
2026-09-04 16:17 ` [PATCH v3 16/21] arm64: percpu: Implement preemptible void RMW ops Mark Rutland
@ 2026-09-05 9:03 ` David Laight
0 siblings, 0 replies; 23+ messages in thread
From: David Laight @ 2026-09-05 9:03 UTC (permalink / raw)
To: Mark Rutland
Cc: vladimir.murzin, usama.anjum, peterz, catalin.marinas, ruanjinjie,
stable, james.morse, yang, cl, maz, will, ardb, linux-arm-kernel
On Fri, 4 Sep 2026 17:17:53 +0100
Mark Rutland <mark.rutland@arm.com> wrote:
> Use the PCPU GPR infrastructure to implement all of the RMW ops which do
> not return a value.
...
> #define __PERCPU_OP_CASE(w, sfx, name, sz, op_llsc, op_lse) \
Isn't this definition exactly the same as __PERCPU_RET_OP_CASE() except
for s/tmp/ret/ and the 'return ret'?
Since the compiler will just ignore the unwanted return value
there is not point replicating the definition.
David
^ permalink raw reply [flat|nested] 23+ messages in thread
end of thread, other threads:[~2026-09-05 9:03 UTC | newest]
Thread overview: 23+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-09-04 16:17 [PATCH v3 00/21] arm64: Preemptible this_cpu_*() operations Mark Rutland
2026-09-04 16:17 ` [PATCH v3 03/21] arm64: percpu: Fix LSE operations on {8,16}-bit types Mark Rutland
2026-09-04 16:17 ` [PATCH v3 05/21] arm64: cmpxchg128: LSE: Remove redundant operands Mark Rutland
2026-09-04 16:17 ` [PATCH v3 07/21] arm64: preempt: Treat should_resched() as unlikely Mark Rutland
2026-09-04 16:17 ` [PATCH v3 08/21] arm64: ptrace: Always inline pt_regs_[read,write}_reg() Mark Rutland
2026-09-04 16:17 ` [PATCH v3 09/21] arm64: percpu: Factor out percpu offset asm Mark Rutland
2026-09-04 16:17 ` [PATCH v3 10/21] arm64: gpr-num: Add wxN aliases for wN registers Mark Rutland
2026-09-04 16:17 ` [PATCH v3 12/21] arm64: entry: sdei: Restore all clobberable GPRs Mark Rutland
2026-09-04 16:17 ` [PATCH v3 14/21] arm64: percpu: Add infrastructure for preemptible this_cpu_*() ops Mark Rutland
2026-09-04 16:17 ` [PATCH v3 15/21] arm64: percpu: Implement preemptible read/write ops Mark Rutland
2026-09-04 16:17 ` [PATCH v3 16/21] arm64: percpu: Implement preemptible void RMW ops Mark Rutland
2026-09-05 9:03 ` David Laight
2026-09-04 16:17 ` [PATCH v3 17/21] arm64: percpu: Implement preemptible return " Mark Rutland
2026-09-04 16:17 ` [PATCH v3 18/21] arm64: percpu: Implement preemptible XCHG ops Mark Rutland
2026-09-04 16:17 ` [PATCH v3 19/21] arm64: percpu: Implement preemptible CMPXCHG ops Mark Rutland
2026-09-04 16:17 ` [PATCH v3 20/21] arm64: percpu: Implement preemptible CMPXCHG128 ops Mark Rutland
2026-09-04 16:17 ` [PATCH v3 21/21] arm64: percpu: Remove _pcp_protect*() wrappers Mark Rutland
2026-09-04 16:54 ` [PATCH v3 01/21] arm64: percpu: Fix this_cpu_write() casting Mark Rutland
2026-09-04 16:56 ` [PATCH v3 02/21] arm64: percpu: Fix this_cpu_and() mask generation Mark Rutland
2026-09-04 16:56 ` [PATCH v3 04/21] arm64: cmpxchg: LL/SC: Avoid redundant extension Mark Rutland
2026-09-04 16:57 ` [PATCH v3 06/21] arm64: preempt: Simplify and optimize __preempt_count_dec_and_test() Mark Rutland
2026-09-04 16:59 ` [PATCH v3 13/21] arm64: entry: sdei: Make 'tsk' available Mark Rutland
2026-09-04 17:43 ` [PATCH v3 00/21] arm64: Preemptible this_cpu_*() operations Mark Rutland
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox