All of lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH] arm64/kernel: Expose the running CPU of a native task in TPIDRRO_EL0
@ 2026-08-15 15:19 Guilherme G. Piccoli
  2026-08-15 16:52 ` Bradley Morgan
  2026-08-16  9:51 ` Will Deacon
  0 siblings, 2 replies; 4+ messages in thread
From: Guilherme G. Piccoli @ 2026-08-15 15:19 UTC (permalink / raw)
  To: linux-arm-kernel
  Cc: catalin.marinas, will, mark.rutland, robin.murphy, gshan,
	kernel-dev, kernel, Guilherme G. Piccoli, Mark Brown, Ryan Houdek

The register TPIDRRO_EL0 has 2 meanings / usages today, depending on
the mode userspace is running:

- For native/arm64 mode, this register is always 0 from the userspace
  perspective. In the kernel, it is used during ventry (entry.S) as
  a scratch register, related to KPTI (stashes x30 in fact).

- For 32-bit compat tasks, this register holds the TLS userspace pointer,
  and is not used for KPTI or anything else in entry.S. This is ABI and
  userspace can count on having a valid TLS pointer there.

Happens that other operating systems follow a similar approach than 32-bit
compat mode, even for arm64: in both Windows[0] and MacOS (xnu/darwin)[1],
this register is used to hold the running CPU of the current task, hence
offering a very fast / non-syscall way for a given task to figure its
running CPU id.

Given that we are zeroing this register on Linux for arm64 native tasks,
we hereby propose the change of doing the same as other OSes. The use
cases for this are related to gaming on Linux. We have mainly two
distinct, yet related, cases for this change:

- Windows can run natively on arm64. So, arm64 native applications could
  run on Linux through the usage of Wine. But...these applications easily
  could attempt a read to this register to perform operations related to
  their running CPUs. As Wine cannot ever trap a register read, these
  apps would be deemed broken / incompatible with Linux.

- Emulation SW like FEX would greatly benefit from this feature. They
  could use this register to compose the emulation of the x86 RDTSCP
  instruction for example.

Not only the specific cases above, but this is useful for regular native
arm64 applications as a way of fast introspection, more or less like
vDSO getcpu() approach but with way less code and supported by the ARM
specification[2].

Care is taken on the implementation to avoid messing with the 32-bit
compat case. This was tested both for native 64-bit applications and
32-bit compat TLS reads in a rpi3 HW.

[0] https://learn.microsoft.com/en-us/cpp/build/arm64-windows-abi-conventions?view=msvc-170#system-registers
[1] https://github.com/apple-oss-distributions/xnu/blob/main/osfmk/arm64/cswitch.s#L170 (set_thread_registers macro)
[2] https://support.arm.com/documentation/ddi0601/2026-06/AArch64-Registers/TPIDRRO-EL0--EL0-Read-Only-Software-Thread-ID-Register

Assisted-by: LLM
Cc: Mark Brown <broonie@kernel.org>
Cc: Ryan Houdek <sonicadvance1@gmail.com>
Signed-off-by: Guilherme G. Piccoli <gpiccoli@igalia.com>
---


Hi folks, thanks in advance for your reviews and suggestions!

I would like to specially highlight the very interesting work from Mark Brown
adding a vDSO getcpu() to Linux/arm64, which seemed quite complete at V3:
https://lore.kernel.org/r/20200819121318.52158-1-broonie@kernel.org/

At the same time that vDSO is pretty nifty (and common) as a means of
optimizing the applications' getcpu() operation, I think worth to clarify this
is not exactly the goal of this patch. In other words, the patch does indeed
benefit that use case, but our main concern is the compatibility of Windows
ARM64 native applications running through Wine on Linux, and the potential
of x86 direct emulation on ARM64 (or other architecture combinations). The
goal **is** to have the register TPIDRRO_EL0 holding the current task's CPU.

Some prior discussion about this is at MLs too, from 2020, but led to nowhere:
https://lore.kernel.org/r/20200601070311.GA8601@willie-the-truck/

I would like to also mention that I'm in no way an expert on arm64 architecture,
hence I relied in studying the code and "pair-programming" with LLM in order
to validate my ideas and patches. Hence, I appreciate extra care on review
since I might be missing some relevant corner case, and if that's the case,
my apologies in advance for the extra burden.
Cheers,

Guilherme


 arch/arm64/kernel/entry.S   | 19 +++++++++++++++++++
 arch/arm64/kernel/process.c |  8 ++++++--
 2 files changed, 25 insertions(+), 2 deletions(-)

diff --git a/arch/arm64/kernel/entry.S b/arch/arm64/kernel/entry.S
index e0db14e9c843..fe42dfc4f4f1 100644
--- a/arch/arm64/kernel/entry.S
+++ b/arch/arm64/kernel/entry.S
@@ -409,6 +409,25 @@ alternative_else_nop_endif
 
 	msr	elr_el1, x21			// set up the return data
 	msr	spsr_el1, x22
+
+	/*
+	 * Save the CPU number into tpidrro_el0 before returning to EL0:
+	 *
+	 * For native tasks, we stash the running CPU of the task in the
+	 * tpidrro_el0 register; at this moment, tsk is valid and we're
+	 * almost ready to get back to EL0. For the 32-bit compat tasks,
+	 * tpidrro_el0 carries the TLS pointer and must survive every kernel
+	 * exit intact - so we test TIF_32BIT and skip the write accordingly.
+	 */
+
+	.if	\el == 0
+	ldr	w0, [tsk, #TSK_TI_FLAGS]
+	tbnz	w0, #TIF_32BIT, .Lskip_tpidrro_\@
+	ldr	w0, [tsk, #TSK_TI_CPU]
+	msr	tpidrro_el0, x0
+.Lskip_tpidrro_\@:
+	.endif
+
 	ldp	x0, x1, [sp, #16 * 0]
 	ldp	x2, x3, [sp, #16 * 1]
 	ldp	x4, x5, [sp, #16 * 2]
diff --git a/arch/arm64/kernel/process.c b/arch/arm64/kernel/process.c
index 581f80e9b9b7..80c2d9cdedd8 100644
--- a/arch/arm64/kernel/process.c
+++ b/arch/arm64/kernel/process.c
@@ -533,8 +533,12 @@ static void tls_thread_switch(struct task_struct *next)
 
 	if (is_compat_thread(task_thread_info(next)))
 		write_sysreg(next->thread.uw.tp_value, tpidrro_el0);
-	else
-		write_sysreg(0, tpidrro_el0);
+
+	/*
+	 * About the native/64-bit case: notice we don't need to update
+	 * tpidrro_el0 here, since it is refreshed at every EL1->EL0
+	 * transition at kernel exit.
+	 */
 
 	write_sysreg(*task_user_tls(next), tpidr_el0);
 	if (system_supports_tpidr2())
-- 
2.50.1



^ permalink raw reply related	[flat|nested] 4+ messages in thread

* Re: [PATCH] arm64/kernel: Expose the running CPU of a native task in TPIDRRO_EL0
  2026-08-15 15:19 [PATCH] arm64/kernel: Expose the running CPU of a native task in TPIDRRO_EL0 Guilherme G. Piccoli
@ 2026-08-15 16:52 ` Bradley Morgan
  2026-08-16  9:51 ` Will Deacon
  1 sibling, 0 replies; 4+ messages in thread
From: Bradley Morgan @ 2026-08-15 16:52 UTC (permalink / raw)
  To: gpiccoli
  Cc: broonie, catalin.marinas, gshan, kernel-dev, kernel,
	linux-arm-kernel, mark.rutland, robin.murphy, sonicadvance1, will

Hi, we meet again, :)

Well, I looked at this assembly code until I was blind,heh, and I
couldnt particularly find anything bad about it, well, I know I'll
probably get these arm pros saying I'm dumb or something for not 
noticing a mistake, but I don't see any mistakes asm side.


For process.c, I really really like your comment, it's, straight
to the point, did your LLM write this, or did you write it?

For your patch;

Reviewed-by: Bradley Morgan <include@grrlz.net>


Note: I hope I don't get plonked, I couldnt find anything wrong, but who knows
Thanks!


^ permalink raw reply	[flat|nested] 4+ messages in thread

* Re: [PATCH] arm64/kernel: Expose the running CPU of a native task in TPIDRRO_EL0
  2026-08-15 15:19 [PATCH] arm64/kernel: Expose the running CPU of a native task in TPIDRRO_EL0 Guilherme G. Piccoli
  2026-08-15 16:52 ` Bradley Morgan
@ 2026-08-16  9:51 ` Will Deacon
  2026-08-17  8:51   ` Catalin Marinas
  1 sibling, 1 reply; 4+ messages in thread
From: Will Deacon @ 2026-08-16  9:51 UTC (permalink / raw)
  To: Guilherme G. Piccoli
  Cc: linux-arm-kernel, catalin.marinas, mark.rutland, robin.murphy,
	gshan, kernel-dev, kernel, Mark Brown, Ryan Houdek

On Sat, Aug 15, 2026 at 12:19:32PM -0300, Guilherme G. Piccoli wrote:
> The register TPIDRRO_EL0 has 2 meanings / usages today, depending on
> the mode userspace is running:
> 
> - For native/arm64 mode, this register is always 0 from the userspace
>   perspective. In the kernel, it is used during ventry (entry.S) as
>   a scratch register, related to KPTI (stashes x30 in fact).
> 
> - For 32-bit compat tasks, this register holds the TLS userspace pointer,
>   and is not used for KPTI or anything else in entry.S. This is ABI and
>   userspace can count on having a valid TLS pointer there.
> 
> Happens that other operating systems follow a similar approach than 32-bit
> compat mode, even for arm64: in both Windows[0] and MacOS (xnu/darwin)[1],
> this register is used to hold the running CPU of the current task, hence
> offering a very fast / non-syscall way for a given task to figure its
> running CPU id.

I don't think we should allocate TPIDRRO_EL0 for this as it precludes us
using it for anything else in the future. If you want the current CPU,
then I think you're better off using either rseq (which I thought could
do this) or looking at reviving the vDSO work you mention (but that has
unresolved limitations iirc).

Will


^ permalink raw reply	[flat|nested] 4+ messages in thread

* Re: [PATCH] arm64/kernel: Expose the running CPU of a native task in TPIDRRO_EL0
  2026-08-16  9:51 ` Will Deacon
@ 2026-08-17  8:51   ` Catalin Marinas
  0 siblings, 0 replies; 4+ messages in thread
From: Catalin Marinas @ 2026-08-17  8:51 UTC (permalink / raw)
  To: Will Deacon
  Cc: Guilherme G. Piccoli, linux-arm-kernel, mark.rutland,
	robin.murphy, gshan, kernel-dev, kernel, Mark Brown, Ryan Houdek

On Sun, Aug 16, 2026 at 10:51:12AM +0100, Will Deacon wrote:
> On Sat, Aug 15, 2026 at 12:19:32PM -0300, Guilherme G. Piccoli wrote:
> > The register TPIDRRO_EL0 has 2 meanings / usages today, depending on
> > the mode userspace is running:
> > 
> > - For native/arm64 mode, this register is always 0 from the userspace
> >   perspective. In the kernel, it is used during ventry (entry.S) as
> >   a scratch register, related to KPTI (stashes x30 in fact).
> > 
> > - For 32-bit compat tasks, this register holds the TLS userspace pointer,
> >   and is not used for KPTI or anything else in entry.S. This is ABI and
> >   userspace can count on having a valid TLS pointer there.
> > 
> > Happens that other operating systems follow a similar approach than 32-bit
> > compat mode, even for arm64: in both Windows[0] and MacOS (xnu/darwin)[1],
> > this register is used to hold the running CPU of the current task, hence
> > offering a very fast / non-syscall way for a given task to figure its
> > running CPU id.
> 
> I don't think we should allocate TPIDRRO_EL0 for this as it precludes us
> using it for anything else in the future. If you want the current CPU,
> then I think you're better off using either rseq (which I thought could
> do this) or looking at reviving the vDSO work you mention (but that has
> unresolved limitations iirc).

Indeed. IIRC glibc already implements getcpu() using rseq.

-- 
Catalin


^ permalink raw reply	[flat|nested] 4+ messages in thread

end of thread, other threads:[~2026-08-17  8:51 UTC | newest]

Thread overview: 4+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-08-15 15:19 [PATCH] arm64/kernel: Expose the running CPU of a native task in TPIDRRO_EL0 Guilherme G. Piccoli
2026-08-15 16:52 ` Bradley Morgan
2026-08-16  9:51 ` Will Deacon
2026-08-17  8:51   ` Catalin Marinas

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.