linux-user/mips/target_cpu.h | 1 + linux-user/syscall.c | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-)
MIPS glibc startup expects the thread pointer to be live in both
CP0_UserLocal and k0. linux-user already updates CP0_UserLocal for
cpu_set_tls() and TARGET_NR_set_thread_area, but it leaves gpr[26]
unchanged.
A guest can therefore successfully execute set_thread_area() and still
reach __pthread_initialize_minimal() with k0 == 0, faulting on the
first TLS-relative store.
Make cpu_set_tls() synchronize k0 with CP0_UserLocal, and route the
MIPS set_thread_area syscall through cpu_set_tls() so all guest TLS
updates follow the same path.
Signed-off-by: James Hilliard <james.hilliard1@gmail.com>
---
linux-user/mips/target_cpu.h | 1 +
linux-user/syscall.c | 2 +-
2 files changed, 2 insertions(+), 1 deletion(-)
diff --git a/linux-user/mips/target_cpu.h b/linux-user/mips/target_cpu.h
index c375616c55..0a000ac8f3 100644
--- a/linux-user/mips/target_cpu.h
+++ b/linux-user/mips/target_cpu.h
@@ -36,6 +36,7 @@ static inline void cpu_clone_regs_parent(CPUMIPSState *env, unsigned flags)
static inline void cpu_set_tls(CPUMIPSState *env, target_ulong newtls)
{
env->active_tc.CP0_UserLocal = newtls;
+ env->active_tc.gpr[26] = newtls;
}
static inline abi_ulong get_sp_from_cpustate(CPUMIPSState *state)
diff --git a/linux-user/syscall.c b/linux-user/syscall.c
index f4b74ad350..8e96cc26db 100644
--- a/linux-user/syscall.c
+++ b/linux-user/syscall.c
@@ -13216,7 +13216,7 @@ static abi_long do_syscall1(CPUArchState *cpu_env, int num, abi_long arg1,
#ifdef TARGET_NR_set_thread_area
case TARGET_NR_set_thread_area:
#if defined(TARGET_MIPS)
- cpu_env->active_tc.CP0_UserLocal = arg1;
+ cpu_set_tls(cpu_env, arg1);
return 0;
#elif defined(TARGET_I386) && defined(TARGET_ABI32)
return do_set_thread_area(cpu_env, arg1);
--
2.53.0
On Wed, 8 Apr 2026 at 19:43, James Hilliard <james.hilliard1@gmail.com> wrote: > > MIPS glibc startup expects the thread pointer to be live in both > CP0_UserLocal and k0. linux-user already updates CP0_UserLocal for > cpu_set_tls() and TARGET_NR_set_thread_area, but it leaves gpr[26] > unchanged. > > A guest can therefore successfully execute set_thread_area() and still > reach __pthread_initialize_minimal() with k0 == 0, faulting on the > first TLS-relative store. > > Make cpu_set_tls() synchronize k0 with CP0_UserLocal, and route the > MIPS set_thread_area syscall through cpu_set_tls() so all guest TLS > updates follow the same path. Where does the kernel do this? Looking at the mips set_thread_area() https://elixir.bootlin.com/linux/v6.19.11/source/arch/mips/kernel/syscall.c#L87 it looks like it sets CP0_UserLocal plus the tp_value field in the thread_info struct; and I can't immediately see anywhere where it would read either CP0_UserLocal or tp_value and put it into k0. I also couldn't find the MIPS glibc code that uses k0, but I didn't look very hard there as I don't know my way around the glibc sources -- do you have a pointer to it on that end? thanks -- PMM
On Thu, Apr 9, 2026 at 10:17 AM Peter Maydell <peter.maydell@linaro.org> wrote:
>
> On Wed, 8 Apr 2026 at 19:43, James Hilliard <james.hilliard1@gmail.com> wrote:
> >
> > MIPS glibc startup expects the thread pointer to be live in both
> > CP0_UserLocal and k0. linux-user already updates CP0_UserLocal for
> > cpu_set_tls() and TARGET_NR_set_thread_area, but it leaves gpr[26]
> > unchanged.
> >
> > A guest can therefore successfully execute set_thread_area() and still
> > reach __pthread_initialize_minimal() with k0 == 0, faulting on the
> > first TLS-relative store.
> >
> > Make cpu_set_tls() synchronize k0 with CP0_UserLocal, and route the
> > MIPS set_thread_area syscall through cpu_set_tls() so all guest TLS
> > updates follow the same path.
>
> Where does the kernel do this? Looking at the mips set_thread_area()
> https://elixir.bootlin.com/linux/v6.19.11/source/arch/mips/kernel/syscall.c#L87
> it looks like it sets CP0_UserLocal plus the tp_value field in the
> thread_info struct; and I can't immediately see anywhere where it
> would read either CP0_UserLocal or tp_value and put it into k0.
So this seems to be something used by the vendor SDK for Octeon1
but not Octeon2 and Octeon3 targets, kinda strange/annoying.
I also found this:
https://lists.gnu.org/archive/html/qemu-devel/2011-08/msg02026.html
It's in the Octeon vendor kernel here looks like:
https://github.com/MarvellEmbeddedProcessors/Octeon-Linux-kernel-4.14/blob/5c48e5031fbd86361e8185f6c530ffcfa5c9a60d/arch/mips/kernel/syscall.c#L94-L96
> I also couldn't find the MIPS glibc code that uses k0, but I
> didn't look very hard there as I don't know my way around the
> glibc sources -- do you have a pointer to it on that end?
From Octeon SDK glibc/ports/sysdeps/mips/nptl/tls.h:
/* Note: rd must be $v1 to be ABI-conformant. */
# if !_MIPS_ARCH_OCTEON
# define READ_THREAD_POINTER() \
({ void *__result; \
asm volatile (".set\tpush\n\t.set\tmips32r2\n\t" \
"rdhwr\t%0, $29\n\t.set\tpop" : "=v" (__result)); \
__result; })
# else /* OCTEON */
/* The Kernel stores the value of "rdhwr v1,$29" in k0 ($26) register. And
it is the Kernel's responsibility to always have the correct value in
k0. Replacing rdhwr instruction with k0, as this instruction needs to
be emulated by the Kernel. */
# define READ_THREAD_POINTER() ( { register void *__result asm
("$26"); __result; } )
# endif /* OCTEON */
From Octeon SDK glibc/ports/sysdeps/mips/tls-macros.h:
# if !_MIPS_ARCH_OCTEON
# define TLS_IE(x) \
({ void *__result, *__tmp; \
asm (".set push\n\t.set mips32r2\n\t" \
"rdhwr\t%0,$29\n\t.set pop" \
: "=v" (__result)); \
asm (LOAD_GP LW " $3,%%gottprel(" #x ")($28)\n\t" \
ADDU " %0,%0,$3" \
UNLOAD_GP \
: "+r" (__result), [tmp] "=&r" (__tmp) \
: : "$3"); \
__result; })
# define TLS_LE(x) \
({ void *__result; \
asm (".set push\n\t.set mips32r2\n\t" \
"rdhwr\t%0,$29\n\t.set pop" \
: "=v" (__result)); \
asm ("lui $3,%%tprel_hi(" #x ")\n\t" \
"addiu $3,$3,%%tprel_lo(" #x ")\n\t" \
ADDU " %0,%0,$3" \
: "+r" (__result) : : "$3"); \
__result; })
# else /* OCTEON */
/* On Octeon the kernel stores the value of the thread pointer in the k0 ($26)
register. */
# define TLS_IE(x) \
({ void *__result, *__tmp; \
asm (LOAD_GP LW " $3,%%gottprel(" #x ")($28)\n\t" \
ADDU " %0,$26,$3" \
UNLOAD_GP \
: "+r" (__result), [tmp] "=&r" (__tmp) \
: : "$3"); \
__result; })
# define TLS_LE(x) \
({ void *__result; \
asm ("lui $3,%%tprel_hi(" #x ")\n\t" \
"addiu $3,$3,%%tprel_lo(" #x ")\n\t" \
ADDU " %0,$26,$3" \
: "+r" (__result) : : "$3"); \
__result; })
# endif /* OCTEON */
>
> thanks
> -- PMM
On Thu, 9 Apr 2026 at 20:47, James Hilliard <james.hilliard1@gmail.com> wrote: > > On Thu, Apr 9, 2026 at 10:17 AM Peter Maydell <peter.maydell@linaro.org> wrote: > > > > On Wed, 8 Apr 2026 at 19:43, James Hilliard <james.hilliard1@gmail.com> wrote: > > > > > > MIPS glibc startup expects the thread pointer to be live in both > > > CP0_UserLocal and k0. linux-user already updates CP0_UserLocal for > > > cpu_set_tls() and TARGET_NR_set_thread_area, but it leaves gpr[26] > > > unchanged. > > > > > > A guest can therefore successfully execute set_thread_area() and still > > > reach __pthread_initialize_minimal() with k0 == 0, faulting on the > > > first TLS-relative store. > > > > > > Make cpu_set_tls() synchronize k0 with CP0_UserLocal, and route the > > > MIPS set_thread_area syscall through cpu_set_tls() so all guest TLS > > > updates follow the same path. > > > > Where does the kernel do this? Looking at the mips set_thread_area() > > https://elixir.bootlin.com/linux/v6.19.11/source/arch/mips/kernel/syscall.c#L87 > > it looks like it sets CP0_UserLocal plus the tp_value field in the > > thread_info struct; and I can't immediately see anywhere where it > > would read either CP0_UserLocal or tp_value and put it into k0. > > So this seems to be something used by the vendor SDK for Octeon1 > but not Octeon2 and Octeon3 targets, kinda strange/annoying. > > I also found this: > https://lists.gnu.org/archive/html/qemu-devel/2011-08/msg02026.html Ah, nice find. > It's in the Octeon vendor kernel here looks like: > https://github.com/MarvellEmbeddedProcessors/Octeon-Linux-kernel-4.14/blob/5c48e5031fbd86361e8185f6c530ffcfa5c9a60d/arch/mips/kernel/syscall.c#L94-L96 Mmm. We emulate the upstream kernel, not vendor forks, so we wouldn't want to set k0 here I think. -- PMM
On Thu, Apr 9, 2026 at 2:17 PM Peter Maydell <peter.maydell@linaro.org> wrote: > > On Thu, 9 Apr 2026 at 20:47, James Hilliard <james.hilliard1@gmail.com> wrote: > > > > On Thu, Apr 9, 2026 at 10:17 AM Peter Maydell <peter.maydell@linaro.org> wrote: > > > > > > On Wed, 8 Apr 2026 at 19:43, James Hilliard <james.hilliard1@gmail.com> wrote: > > > > > > > > MIPS glibc startup expects the thread pointer to be live in both > > > > CP0_UserLocal and k0. linux-user already updates CP0_UserLocal for > > > > cpu_set_tls() and TARGET_NR_set_thread_area, but it leaves gpr[26] > > > > unchanged. > > > > > > > > A guest can therefore successfully execute set_thread_area() and still > > > > reach __pthread_initialize_minimal() with k0 == 0, faulting on the > > > > first TLS-relative store. > > > > > > > > Make cpu_set_tls() synchronize k0 with CP0_UserLocal, and route the > > > > MIPS set_thread_area syscall through cpu_set_tls() so all guest TLS > > > > updates follow the same path. > > > > > > Where does the kernel do this? Looking at the mips set_thread_area() > > > https://elixir.bootlin.com/linux/v6.19.11/source/arch/mips/kernel/syscall.c#L87 > > > it looks like it sets CP0_UserLocal plus the tp_value field in the > > > thread_info struct; and I can't immediately see anywhere where it > > > would read either CP0_UserLocal or tp_value and put it into k0. > > > > So this seems to be something used by the vendor SDK for Octeon1 > > but not Octeon2 and Octeon3 targets, kinda strange/annoying. > > > > I also found this: > > https://lists.gnu.org/archive/html/qemu-devel/2011-08/msg02026.html > > Ah, nice find. > > > It's in the Octeon vendor kernel here looks like: > > https://github.com/MarvellEmbeddedProcessors/Octeon-Linux-kernel-4.14/blob/5c48e5031fbd86361e8185f6c530ffcfa5c9a60d/arch/mips/kernel/syscall.c#L94-L96 > > Mmm. We emulate the upstream kernel, not vendor forks, so > we wouldn't want to set k0 here I think. Yeah, I was trying to see if there was some sort of way to trivially identify binaries that require this k0 support and autoenable a quirk but I didn't find anything, maybe it would make sense to have a separate runtime target for enabling this k0 quirk since it seems a lot of octeon binaries require it? > > -- PMM
On Thu, 9 Apr 2026 at 22:00, James Hilliard <james.hilliard1@gmail.com> wrote: > > On Thu, Apr 9, 2026 at 2:17 PM Peter Maydell <peter.maydell@linaro.org> wrote: > > > > On Thu, 9 Apr 2026 at 20:47, James Hilliard <james.hilliard1@gmail.com> wrote: > > > So this seems to be something used by the vendor SDK for Octeon1 > > > but not Octeon2 and Octeon3 targets, kinda strange/annoying. > > > > > > I also found this: > > > https://lists.gnu.org/archive/html/qemu-devel/2011-08/msg02026.html > > > > Ah, nice find. > > > > > It's in the Octeon vendor kernel here looks like: > > > https://github.com/MarvellEmbeddedProcessors/Octeon-Linux-kernel-4.14/blob/5c48e5031fbd86361e8185f6c530ffcfa5c9a60d/arch/mips/kernel/syscall.c#L94-L96 > > > > Mmm. We emulate the upstream kernel, not vendor forks, so > > we wouldn't want to set k0 here I think. > > Yeah, I was trying to see if there was some sort of way to trivially > identify binaries that require this k0 support and autoenable a quirk > but I didn't find anything, maybe it would make sense to have a > separate runtime target for enabling this k0 quirk since it seems a > lot of octeon binaries require it? We didn't add this 15 years ago, and my feeling is we still don't want to, because we track the mainline kernel. If an actual host kernel doesn't have a quirk to let these binaries run, we don't need one either. -- PMM
On Fri, Apr 10, 2026 at 2:13 AM Peter Maydell <peter.maydell@linaro.org> wrote: > > On Thu, 9 Apr 2026 at 22:00, James Hilliard <james.hilliard1@gmail.com> wrote: > > > > On Thu, Apr 9, 2026 at 2:17 PM Peter Maydell <peter.maydell@linaro.org> wrote: > > > > > > On Thu, 9 Apr 2026 at 20:47, James Hilliard <james.hilliard1@gmail.com> wrote: > > > > So this seems to be something used by the vendor SDK for Octeon1 > > > > but not Octeon2 and Octeon3 targets, kinda strange/annoying. > > > > > > > > I also found this: > > > > https://lists.gnu.org/archive/html/qemu-devel/2011-08/msg02026.html > > > > > > Ah, nice find. > > > > > > > It's in the Octeon vendor kernel here looks like: > > > > https://github.com/MarvellEmbeddedProcessors/Octeon-Linux-kernel-4.14/blob/5c48e5031fbd86361e8185f6c530ffcfa5c9a60d/arch/mips/kernel/syscall.c#L94-L96 > > > > > > Mmm. We emulate the upstream kernel, not vendor forks, so > > > we wouldn't want to set k0 here I think. > > > > Yeah, I was trying to see if there was some sort of way to trivially > > identify binaries that require this k0 support and autoenable a quirk > > but I didn't find anything, maybe it would make sense to have a > > separate runtime target for enabling this k0 quirk since it seems a > > lot of octeon binaries require it? > > We didn't add this 15 years ago, and my feeling is we still don't > want to, because we track the mainline kernel. If an actual host > kernel doesn't have a quirk to let these binaries run, we don't > need one either. I'm wondering, is there any actual downside to enabling this quirk for Octeon 1 binaries? It wouldn't actually interfere with binaries that don't require the quirk right? > > -- PMM
On 9/4/26 23:00, James Hilliard wrote:
> On Thu, Apr 9, 2026 at 2:17 PM Peter Maydell <peter.maydell@linaro.org> wrote:
>>
>> On Thu, 9 Apr 2026 at 20:47, James Hilliard <james.hilliard1@gmail.com> wrote:
>>>
>>> On Thu, Apr 9, 2026 at 10:17 AM Peter Maydell <peter.maydell@linaro.org> wrote:
>>>>
>>>> On Wed, 8 Apr 2026 at 19:43, James Hilliard <james.hilliard1@gmail.com> wrote:
>>>>>
>>>>> MIPS glibc startup expects the thread pointer to be live in both
>>>>> CP0_UserLocal and k0. linux-user already updates CP0_UserLocal for
>>>>> cpu_set_tls() and TARGET_NR_set_thread_area, but it leaves gpr[26]
>>>>> unchanged.
>>>>>
>>>>> A guest can therefore successfully execute set_thread_area() and still
>>>>> reach __pthread_initialize_minimal() with k0 == 0, faulting on the
>>>>> first TLS-relative store.
>>>>>
>>>>> Make cpu_set_tls() synchronize k0 with CP0_UserLocal, and route the
>>>>> MIPS set_thread_area syscall through cpu_set_tls() so all guest TLS
>>>>> updates follow the same path.
>>>>
>>>> Where does the kernel do this? Looking at the mips set_thread_area()
>>>> https://elixir.bootlin.com/linux/v6.19.11/source/arch/mips/kernel/syscall.c#L87
>>>> it looks like it sets CP0_UserLocal plus the tp_value field in the
>>>> thread_info struct; and I can't immediately see anywhere where it
>>>> would read either CP0_UserLocal or tp_value and put it into k0.
>>>
>>> So this seems to be something used by the vendor SDK for Octeon1
>>> but not Octeon2 and Octeon3 targets, kinda strange/annoying.
>>>
>>> I also found this:
>>> https://lists.gnu.org/archive/html/qemu-devel/2011-08/msg02026.html
>>
>> Ah, nice find.
>>
>>> It's in the Octeon vendor kernel here looks like:
>>> https://github.com/MarvellEmbeddedProcessors/Octeon-Linux-kernel-4.14/blob/5c48e5031fbd86361e8185f6c530ffcfa5c9a60d/arch/mips/kernel/syscall.c#L94-L96
>>
>> Mmm. We emulate the upstream kernel, not vendor forks, so
>> we wouldn't want to set k0 here I think.
>
> Yeah, I was trying to see if there was some sort of way to trivially
> identify binaries that require this k0 support and autoenable a quirk
> but I didn't find anything, maybe it would make sense to have a
> separate runtime target for enabling this k0 quirk since it seems a
> lot of octeon binaries require it?
What about (untested):
-- >8 --
diff --git a/linux-user/mips/target_cpu.h b/linux-user/mips/target_cpu.h
index 0a000ac8f35..c4aa08df4a3 100644
--- a/linux-user/mips/target_cpu.h
+++ b/linux-user/mips/target_cpu.h
@@ -36,7 +36,9 @@ static inline void cpu_clone_regs_parent(CPUMIPSState
*env, unsigned flags)
static inline void cpu_set_tls(CPUMIPSState *env, target_ulong newtls)
{
env->active_tc.CP0_UserLocal = newtls;
- env->active_tc.gpr[26] = newtls;
+ if (env->insn_flags & INSN_OCTEON) { /* Octeon1 */
+ env->active_tc.gpr[26] = newtls;
+ }
}
---
On Thu, Apr 9, 2026 at 3:07 PM Philippe Mathieu-Daudé <philmd@linaro.org> wrote:
>
> On 9/4/26 23:00, James Hilliard wrote:
> > On Thu, Apr 9, 2026 at 2:17 PM Peter Maydell <peter.maydell@linaro.org> wrote:
> >>
> >> On Thu, 9 Apr 2026 at 20:47, James Hilliard <james.hilliard1@gmail.com> wrote:
> >>>
> >>> On Thu, Apr 9, 2026 at 10:17 AM Peter Maydell <peter.maydell@linaro.org> wrote:
> >>>>
> >>>> On Wed, 8 Apr 2026 at 19:43, James Hilliard <james.hilliard1@gmail.com> wrote:
> >>>>>
> >>>>> MIPS glibc startup expects the thread pointer to be live in both
> >>>>> CP0_UserLocal and k0. linux-user already updates CP0_UserLocal for
> >>>>> cpu_set_tls() and TARGET_NR_set_thread_area, but it leaves gpr[26]
> >>>>> unchanged.
> >>>>>
> >>>>> A guest can therefore successfully execute set_thread_area() and still
> >>>>> reach __pthread_initialize_minimal() with k0 == 0, faulting on the
> >>>>> first TLS-relative store.
> >>>>>
> >>>>> Make cpu_set_tls() synchronize k0 with CP0_UserLocal, and route the
> >>>>> MIPS set_thread_area syscall through cpu_set_tls() so all guest TLS
> >>>>> updates follow the same path.
> >>>>
> >>>> Where does the kernel do this? Looking at the mips set_thread_area()
> >>>> https://elixir.bootlin.com/linux/v6.19.11/source/arch/mips/kernel/syscall.c#L87
> >>>> it looks like it sets CP0_UserLocal plus the tp_value field in the
> >>>> thread_info struct; and I can't immediately see anywhere where it
> >>>> would read either CP0_UserLocal or tp_value and put it into k0.
> >>>
> >>> So this seems to be something used by the vendor SDK for Octeon1
> >>> but not Octeon2 and Octeon3 targets, kinda strange/annoying.
> >>>
> >>> I also found this:
> >>> https://lists.gnu.org/archive/html/qemu-devel/2011-08/msg02026.html
> >>
> >> Ah, nice find.
> >>
> >>> It's in the Octeon vendor kernel here looks like:
> >>> https://github.com/MarvellEmbeddedProcessors/Octeon-Linux-kernel-4.14/blob/5c48e5031fbd86361e8185f6c530ffcfa5c9a60d/arch/mips/kernel/syscall.c#L94-L96
> >>
> >> Mmm. We emulate the upstream kernel, not vendor forks, so
> >> we wouldn't want to set k0 here I think.
> >
> > Yeah, I was trying to see if there was some sort of way to trivially
> > identify binaries that require this k0 support and autoenable a quirk
> > but I didn't find anything, maybe it would make sense to have a
> > separate runtime target for enabling this k0 quirk since it seems a
> > lot of octeon binaries require it?
>
> What about (untested):
>
> -- >8 --
> diff --git a/linux-user/mips/target_cpu.h b/linux-user/mips/target_cpu.h
> index 0a000ac8f35..c4aa08df4a3 100644
> --- a/linux-user/mips/target_cpu.h
> +++ b/linux-user/mips/target_cpu.h
> @@ -36,7 +36,9 @@ static inline void cpu_clone_regs_parent(CPUMIPSState
> *env, unsigned flags)
> static inline void cpu_set_tls(CPUMIPSState *env, target_ulong newtls)
> {
> env->active_tc.CP0_UserLocal = newtls;
> - env->active_tc.gpr[26] = newtls;
> + if (env->insn_flags & INSN_OCTEON) { /* Octeon1 */
INST_OCTEON isn't OCTEON1 specific, but we could do something like this:
https://lore.kernel.org/qemu-devel/20260410020012.98778-1-james.hilliard1@gmail.com/
> + env->active_tc.gpr[26] = newtls;
> + }
> }
> ---
© 2016 - 2026 Red Hat, Inc.