# HG changeset patch # User Taylor R Campbell # Date 1788363676 0 # Wed Sep 02 15:41:16 2026 +0000 # Branch trunk # Node ID a9b10ae21d34ab10b5908ec626c96e552901f602 # Parent 4fbd288ac1da9e7d9592ddecf04f7513e244152f # EXP-Topic riastradh-pr60664-rdhwrtlbmiss WIP: mips: Emulate rdhwr $3,$29 with fewer temporary registers. This way, if there is a TLB exception when loading the user's instruction, there's no harm in having the TLB exception handler return straight to the user's instruction to retry it -- we haven't saved any registers in the kernel stack frame (previously, v0 and AT) that need to be restored before userland can continue. PR port-mips/60664: mips: rdhwr emulation corrupts v0 on nested TLB miss, random userland SIGSEGV diff -r 4fbd288ac1da -r a9b10ae21d34 sys/arch/mips/mips/mipsX_subr.S --- a/sys/arch/mips/mips/mipsX_subr.S Mon Aug 10 13:36:10 2026 +0000 +++ b/sys/arch/mips/mips/mipsX_subr.S Wed Sep 02 15:41:16 2026 +0000 @@ -1295,60 +1295,107 @@ 5: bne v0, s1, 5b END(MIPSX(kern_intr)) /* + * user_reserved_insn * + * Handle a reserved instruction exception. We implement a fast + * path for rdhwr $3,$29 or rdhwr v1,$29 (0x7c03e83b), the + * instruction in the ABI to read the thread-local storage + * pointer, whether or not the CPU supports it. Any other + * instructions, we handle by user_gen_exception. */ .p2align 5 NESTED_NOPROFILE(MIPSX(user_reserved_insn), CALLFRAME_SIZ, ra) .set noat .mask 0x80000000, -4 + KERN_ENTRY_ERRATA + /* - * Save a minimum of registers to see if this is rdhwr $3,$29 + * On entry: + * - EXL is set, + * - k0 is garbage, + * - k1 is curlwp, + * - EPC is the address of the reserved instruction, + * and all other general-purpose CPU registers are potentially + * used by the user program, so we would have to save and + * restore them in order to use them. + * + * We can reuse k1 at the cost of: + * + * lui k1,%hi(...) + * PTR_L k1,%lo(...)(k1) + * + * However, restoring saved registers is not trivial because of + * TLB exceptions. We have to load the instruction from EPC to + * discern whether it is rdhwr $3,$29 or something else. But + * the load may provoke a TLB refill/invalid exception. + * + * Since the EXL bit remains set, if this happens, the CPU will + * jump to the exception vector again with EPC _still pointing + * to the reserved instruction in userland_ -- which means we + * don't have an opportunity to restore any registers we saved! + * + * To avoid this, we would either have to use TLBP here to + * forestall TLB exceptions, or save enough state to clear the + * EXL bit so we can safely take TLB exceptions and return back + * to the kernel to restore the remaining state when done. All + * much messier! */ - KERN_ENTRY_ERRATA - /* K1 already has CURLWP */ - PTR_L k0, L_PCB(k1) # XXXuvm_lwp_getuarea - PTR_ADDU k0, USPACE - TF_SIZ - CALLFRAME_SIZ - - /* Need two working registers */ - REG_S AT, CALLFRAME_SIZ+TF_REG_AST(k0) - REG_S v0, CALLFRAME_SIZ+TF_REG_V0(k0) /* If this was in a branch delay slot, take the slow path. */ - mfc0 v0, MIPS_COP_0_CAUSE + _MFC0 k0, MIPS_COP_0_CAUSE + _MFC0 k1, MIPS_COP_0_EXC_PC MFC0_HAZARD - bltz v0, MIPSX(user_gen_exception_common) - nop + bltz k0, 1f - /* + /* * Get exception PC and fetch the instruction. We know we can do * this since the instruction actually got read. + * + * Except, of course, if we get a TLB exception. In that case, + * no big deal: the TLB exception handler will restart the user + * instruction, and we'll try user_reserved_insn again. */ - _MFC0 v0, MIPS_COP_0_EXC_PC - MFC0_HAZARD - INT_L AT, 0(v0) + INT_L k1, 0(k1) /* * Was this rdhwr $3,$29? */ - lui v0, %hi(0x7c03e83b) # 0x7c03e83b => rdhwr $3,$29 - addiu v0, %lo(0x7c03e83b) # or ... rdhwr v1,ulr - bne AT, v0, MIPSX(user_gen_exception_common) - nop + lui k0, %hi(0x7c03e83b) # 0x7c03e83b => rdhwr $3,$29 + addiu k0, %lo(0x7c03e83b) # or ... rdhwr v1,ulr + bne k0, k1, 1f + + /* + * We have now confirmed the instruction is rdhwr $3,$29, + * i.e., rdhwr v1,$29, so it is safe to use v1 now -- not + * that we have any use for v1 except to hold the result. + */ + + /* Load EPC into k0 again so we can advance to EPC+4. */ + _MFC0 k0, MIPS_COP_0_EXC_PC + MFC0_HAZARD + + /* Load curlwp into k1 again. */ + lui k1, %hi(CPUVAR(CURLWP)) + PTR_L k1, %lo(CPUVAR(CURLWP))(k1) /* * Advance the PC (don't want to restart at the rdhwr). */ - _MFC0 v0, MIPS_COP_0_EXC_PC - MFC0_HAZARD - PTR_ADDIU v0, 4 - _MTC0 v0, MIPS_COP_0_EXC_PC + PTR_ADDIU k0, 4 + _MTC0 k0, MIPS_COP_0_EXC_PC COP0_SYNC PTR_L v1, L_PRIVATE(k1) # rdhwr $3,$29 updates v1 - REG_L AT, CALLFRAME_SIZ+TF_REG_AST(k0)# restore reg - REG_L v0, CALLFRAME_SIZ+TF_REG_V0(k0) # restore reg eret + + /* + * Slow path. Load curlwp back into k1 and defer to + * user_gen_exception. + */ +1: lui k1, %hi(CPUVAR(CURLWP)) + b MIPSX(user_gen_exception) + PTR_L k1, %lo(CPUVAR(CURLWP))(k1) END(MIPSX(user_reserved_insn)) /* @@ -1372,7 +1419,6 @@ NESTED_NOPROFILE(MIPSX(user_gen_exceptio PTR_ADDU k0, USPACE - TF_SIZ - CALLFRAME_SIZ REG_S AT, CALLFRAME_SIZ+TF_REG_AST(k0) REG_S v0, CALLFRAME_SIZ+TF_REG_V0(k0) -MIPSX(user_gen_exception_common): REG_S v1, CALLFRAME_SIZ+TF_REG_V1(k0) mflo v0 REG_S a0, CALLFRAME_SIZ+TF_REG_A0(k0)