| From 30bfc2d6a1132a89a5f1c3b96c59cf3e4d076ea3 Mon Sep 17 00:00:00 2001 |
| From: Yao Zi <me@ziyao.cc> |
| Date: Thu, 5 Feb 2026 15:56:44 +0000 |
| Subject: MIPS: Work around LLVM bug when gp is used as global register variable |
| |
| From: Yao Zi <me@ziyao.cc> |
| |
| commit 30bfc2d6a1132a89a5f1c3b96c59cf3e4d076ea3 upstream. |
| |
| On MIPS, __current_thread_info is defined as global register variable |
| locating in $gp, and is simply assigned with new address during kernel |
| relocation. |
| |
| This however is broken with LLVM, which always restores $gp if it finds |
| $gp is clobbered in any form, including when intentionally through a |
| global register variable. This is against GCC's documentation[1], which |
| requires a callee-saved register used as global register variable not to |
| be restored if it's clobbered. |
| |
| As a result, $gp will continue to point to the unrelocated kernel after |
| the epilog of relocate_kernel(), leading to an early crash in init_idle, |
| |
| [ 0.000000] CPU 0 Unable to handle kernel paging request at virtual address 0000000000000000, epc == ffffffff81afada8, ra == ffffffff81afad90 |
| [ 0.000000] Oops[#1]: |
| [ 0.000000] CPU: 0 UID: 0 PID: 0 Comm: swapper Tainted: G W 6.19.0-rc5-00262-gd3eeb99bbc99-dirty #188 VOLUNTARY |
| [ 0.000000] Tainted: [W]=WARN |
| [ 0.000000] Hardware name: loongson,loongson64v-4core-virtio |
| [ 0.000000] $ 0 : 0000000000000000 0000000000000000 0000000000000001 0000000000000000 |
| [ 0.000000] $ 4 : ffffffff80b80ec0 ffffffff80b53d48 0000000000000000 00000000000f4240 |
| [ 0.000000] $ 8 : 0000000000000100 ffffffff81d82f80 ffffffff81d82f80 0000000000000001 |
| [ 0.000000] $12 : 0000000000000000 ffffffff81776f58 00000000000005da 0000000000000002 |
| [ 0.000000] $16 : ffffffff80b80e40 0000000000000000 ffffffff80b81614 9800000005dfbe80 |
| [ 0.000000] $20 : 00000000540000e0 ffffffff81980000 0000000000000000 ffffffff80f81c80 |
| [ 0.000000] $24 : 0000000000000a26 ffffffff8114fb90 |
| [ 0.000000] $28 : ffffffff80b50000 ffffffff80b53d40 0000000000000000 ffffffff81afad90 |
| [ 0.000000] Hi : 0000000000000000 |
| [ 0.000000] Lo : 0000000000000000 |
| [ 0.000000] epc : ffffffff81afada8 init_idle+0x130/0x270 |
| [ 0.000000] ra : ffffffff81afad90 init_idle+0x118/0x270 |
| [ 0.000000] Status: 540000e2 KX SX UX KERNEL EXL |
| [ 0.000000] Cause : 00000008 (ExcCode 02) |
| [ 0.000000] BadVA : 0000000000000000 |
| [ 0.000000] PrId : 00006305 (ICT Loongson-3) |
| [ 0.000000] Process swapper (pid: 0, threadinfo=(____ptrval____), task=(____ptrval____), tls=0000000000000000) |
| [ 0.000000] Stack : 9800000005dfbf00 ffffffff8178e950 0000000000000000 0000000000000000 |
| [ 0.000000] 0000000000000000 ffffffff81970000 000000000000003f ffffffff810a6528 |
| [ 0.000000] 0000000000000001 9800000005dfbe80 9800000005dfbf00 ffffffff81980000 |
| [ 0.000000] ffffffff810a6450 ffffffff81afb6c0 0000000000000000 ffffffff810a2258 |
| [ 0.000000] ffffffff81d82ec8 ffffffff8198d010 ffffffff81b67e80 ffffffff8197dd98 |
| [ 0.000000] ffffffff81d81c80 ffffffff81930000 0000000000000040 0000000000000000 |
| [ 0.000000] 0000000000000000 0000000000000000 0000000000000000 0000000000000000 |
| [ 0.000000] 0000000000000000 000000000000009e ffffffff9fc01000 0000000000000000 |
| [ 0.000000] 0000000000000000 0000000000000000 0000000000000000 0000000000000000 |
| [ 0.000000] 0000000000000000 ffffffff81ae86dc ffffffff81b3c741 0000000000000002 |
| [ 0.000000] ... |
| [ 0.000000] Call Trace: |
| [ 0.000000] [<ffffffff81afada8>] init_idle+0x130/0x270 |
| [ 0.000000] [<ffffffff81afb6c0>] sched_init+0x5c8/0x6c0 |
| [ 0.000000] [<ffffffff81ae86dc>] start_kernel+0x27c/0x7a8 |
| |
| This bug has been reported to LLVM[2] and affects version from (at |
| least) 18 to 21. Let's work around this by using inline assembly to |
| assign $gp before a fix is widely available. |
| |
| Cc: stable@vger.kernel.org |
| Link: https://gcc.gnu.org/onlinedocs/gcc-15.2.0/gcc/Global-Register-Variables.html # [1] |
| Link: https://github.com/llvm/llvm-project/issues/176546 # [2] |
| Signed-off-by: Yao Zi <me@ziyao.cc> |
| Acked-by: Nathan Chancellor <nathan@kernel.org> |
| Signed-off-by: Thomas Bogendoerfer <tsbogend@alpha.franken.de> |
| Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org> |
| --- |
| arch/mips/kernel/relocate.c | 13 +++++++++++++ |
| 1 file changed, 13 insertions(+) |
| |
| --- a/arch/mips/kernel/relocate.c |
| +++ b/arch/mips/kernel/relocate.c |
| @@ -420,7 +420,20 @@ void *__init relocate_kernel(void) |
| goto out; |
| |
| /* The current thread is now within the relocated image */ |
| +#ifndef CONFIG_CC_IS_CLANG |
| __current_thread_info = RELOCATED(&init_thread_union); |
| +#else |
| + /* |
| + * LLVM may wrongly restore $gp ($28) in epilog even if it's |
| + * intentionally modified. Work around this by using inline |
| + * assembly to assign $gp. $gp couldn't be listed as output or |
| + * clobber, or LLVM will still restore its original value. |
| + * See also LLVM upstream issue |
| + * https://github.com/llvm/llvm-project/issues/176546 |
| + */ |
| + asm volatile("move $28, %0" : : |
| + "r" (RELOCATED(&init_thread_union))); |
| +#endif |
| |
| /* Return the new kernel's entry point */ |
| kernel_entry = RELOCATED(start_kernel); |