From 3e8503afcc66749d05f9e4e618a8b2f81af0f510 Mon Sep 17 00:00:00 2001 From: Rob Gardner Date: Fri, 9 Jun 2017 00:36:24 -0400 Subject: [PATCH] sparc64: Set valid bytes of misaligned no-fault loads If a misaligned no-fault load (ldm* from ASI 0x82, primary no fault) crosses a page boundary, and one of the pages causes an MMU miss that cannot be resolved, then the kernel must load bytes from the valid page into the high (or low) bytes of the destination register, and must load zeros into the low (or high) bytes of the register. Orabug: 25766652 Signed-off-by: Rob Gardner Reviewed-by: Steve Sistare steven.sistare@oracle.com Reviewed-by: Anthony Yznaga Signed-off-by: Allen Pais --- arch/sparc/include/asm/adi_64.h | 29 +++++++++++ arch/sparc/include/asm/setup.h | 1 + arch/sparc/kernel/unaligned_64.c | 85 ++++++++++++++++++++++++++++++++ arch/sparc/mm/fault_64.c | 8 +++ 4 files changed, 123 insertions(+) diff --git a/arch/sparc/include/asm/adi_64.h b/arch/sparc/include/asm/adi_64.h index cd2ce353de9e..7fbe92a04495 100644 --- a/arch/sparc/include/asm/adi_64.h +++ b/arch/sparc/include/asm/adi_64.h @@ -43,6 +43,35 @@ static inline unsigned long adi_nbits(void) return adi_state.caps.nbits; } +static inline unsigned long adi_normalize(long addr) +{ + return addr << adi_nbits() >> adi_nbits(); +} + +static inline unsigned long adi_pstate_disable(void) +{ + unsigned long saved_pstate; + + __asm__ __volatile__( + "rdpr %%pstate, %0 \n\t" + "andn %0, %1, %%g1 \n\t" + "wrpr %%g1, %%pstate \n\t" + : "=&r" (saved_pstate) + : "i" (PSTATE_MCDE) + : "g1"); + + return saved_pstate; +} + +static inline void adi_pstate_restore(unsigned long saved_pstate) +{ + __asm__ __volatile__( + "wrpr %0, %%pstate \n\t" + : + : "r" (saved_pstate) + : ); +} + #endif /* __ASSEMBLY__ */ #endif /* !(__ASM_SPARC64_ADI_H) */ diff --git a/arch/sparc/include/asm/setup.h b/arch/sparc/include/asm/setup.h index 7cf712fa37a6..54f5f94800a6 100644 --- a/arch/sparc/include/asm/setup.h +++ b/arch/sparc/include/asm/setup.h @@ -53,6 +53,7 @@ void __init start_early_boot(void); /* unaligned_64.c */ int handle_ldf_stq(u32 insn, struct pt_regs *regs); void handle_ld_nf(u32 insn, struct pt_regs *regs); +int handle_ldm_nf(u32 insn, struct pt_regs *regs); /* init_64.c */ extern atomic_t dcpage_flushes; diff --git a/arch/sparc/kernel/unaligned_64.c b/arch/sparc/kernel/unaligned_64.c index c89371481ae2..e301ba5dd444 100644 --- a/arch/sparc/kernel/unaligned_64.c +++ b/arch/sparc/kernel/unaligned_64.c @@ -592,6 +592,91 @@ void handle_ld_nf(u32 insn, struct pt_regs *regs) advance(regs); } +int handle_ldm_nf(u32 insn, struct pt_regs *regs) +{ + int i, sign_extend_bits = 0, bytes_needed; + unsigned long *uregp, saved_pstate = 0; + int rd = ((insn >> 25) & 0x1f); + int opm = ((insn >> 10) & 0x7); + long val, addr; + bool is_adi; + + if (regs->tstate & TSTATE_PRIV) + return 0; + + perf_sw_event(PERF_COUNT_SW_EMULATION_FAULTS, 1, regs, 0); + + switch (opm) { + case 0: + sign_extend_bits = 48; + /* fall through */ + case 1: + bytes_needed = 2; + break; + case 2: + sign_extend_bits = 32; + /* fall through */ + case 3: + bytes_needed = 4; + break; + case 5: + sign_extend_bits = 0; + bytes_needed = 8; + break; + default: + return 0; + } + + /* + * The LDM* instructions only have a 10 bit immediate so + * it might look like a bug to use compute_effective_address() + * here. But it's ok because LDM*A cannot have any immediate + * offset, so the effective address is always correctly + * calculated using only the registers. + */ + addr = compute_effective_address(regs, insn, rd); + + /* + * ADI logic here: Since we are emulating a no-fault load, + * we also want to emulate the h/w behavior, which is to actually + * suppress ADI-related faults. So if the application has + * ADI enabled, then let's disable MCD here before attempting + * the access, and use a normalized address as well. + * This is all to ensure that the only potential faults + * are MMU related. + */ + if ((is_adi = (current->mm && current->mm->context.adi))) { + saved_pstate = adi_pstate_disable(); + addr = adi_normalize(addr); + } + + for (i = 0, val = 0; i < bytes_needed; i++) { + unsigned char v; + + if (get_user(v, (unsigned char __user *) (addr+i))) + v = 0; + val = (val << 8) | v; + } + + if (is_adi) + adi_pstate_restore(saved_pstate); + + if (sign_extend_bits) + val = val << sign_extend_bits >> sign_extend_bits; + + maybe_flush_windows(0, 0, rd, 0); + uregp = fetch_reg_addr(rd, regs); + if (rd < 16) + uregp[0] = val; + else if (test_thread_64bit_stack(regs->u_regs[UREG_FP])) + put_user(val, (long __user *) uregp); + else + put_user(val, (int __user *) uregp); + + advance(regs); + return 1; +} + void handle_lddfmna(struct pt_regs *regs, unsigned long sfar, unsigned long sfsr) { enum ctx_state prev_state = exception_enter(); diff --git a/arch/sparc/mm/fault_64.c b/arch/sparc/mm/fault_64.c index 02cbe9512131..f9a7b843a4e2 100644 --- a/arch/sparc/mm/fault_64.c +++ b/arch/sparc/mm/fault_64.c @@ -248,6 +248,14 @@ static void __kprobes do_kernel_fault(struct pt_regs *regs, int si_code, } return; } + /* M8 misaligned load alternate space; OSA 2017, sec 7.89 */ + if ((insn & + ((3<<30) | (0x3f<<19) | (1<<13) | (1<<9))) == + ((3<<30) | (0x31<<19) | (1<<9))) { + asi = (regs->tstate >> 24); + if ((asi & 0xf2) == 0x82 && handle_ldm_nf(insn, regs)) + return; + } } /* Is this in ex_table? */ -- 2.50.1