| From foo@baz Thu Aug 7 22:33:35 PDT 2014 |
| From: "David S. Miller" <davem@davemloft.net> |
| Date: Mon, 28 Apr 2014 23:52:11 -0700 |
| Subject: sparc64: Fix top-level fault handling bugs. |
| |
| From: "David S. Miller" <davem@davemloft.net> |
| |
| [ Upstream commit 70ffc6ebaead783ac8dafb1e87df0039bb043596 ] |
| |
| Make get_user_insn() able to cope with huge PMDs. |
| |
| Next, make do_fault_siginfo() more robust when get_user_insn() can't |
| actually fetch the instruction. In particular, use the MMU announced |
| fault address when that happens, instead of calling |
| compute_effective_address() and computing garbage. |
| |
| Signed-off-by: David S. Miller <davem@davemloft.net> |
| Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org> |
| --- |
| arch/sparc/mm/fault_64.c | 84 +++++++++++++++++++++++++++++------------------ |
| 1 file changed, 53 insertions(+), 31 deletions(-) |
| |
| --- a/arch/sparc/mm/fault_64.c |
| +++ b/arch/sparc/mm/fault_64.c |
| @@ -95,38 +95,51 @@ static unsigned int get_user_insn(unsign |
| pte_t *ptep, pte; |
| unsigned long pa; |
| u32 insn = 0; |
| - unsigned long pstate; |
| |
| - if (pgd_none(*pgdp)) |
| - goto outret; |
| + if (pgd_none(*pgdp) || unlikely(pgd_bad(*pgdp))) |
| + goto out; |
| pudp = pud_offset(pgdp, tpc); |
| - if (pud_none(*pudp)) |
| - goto outret; |
| - pmdp = pmd_offset(pudp, tpc); |
| - if (pmd_none(*pmdp)) |
| - goto outret; |
| - |
| - /* This disables preemption for us as well. */ |
| - __asm__ __volatile__("rdpr %%pstate, %0" : "=r" (pstate)); |
| - __asm__ __volatile__("wrpr %0, %1, %%pstate" |
| - : : "r" (pstate), "i" (PSTATE_IE)); |
| - ptep = pte_offset_map(pmdp, tpc); |
| - pte = *ptep; |
| - if (!pte_present(pte)) |
| + if (pud_none(*pudp) || unlikely(pud_bad(*pudp))) |
| goto out; |
| |
| - pa = (pte_pfn(pte) << PAGE_SHIFT); |
| - pa += (tpc & ~PAGE_MASK); |
| + /* This disables preemption for us as well. */ |
| + local_irq_disable(); |
| |
| - /* Use phys bypass so we don't pollute dtlb/dcache. */ |
| - __asm__ __volatile__("lduwa [%1] %2, %0" |
| - : "=r" (insn) |
| - : "r" (pa), "i" (ASI_PHYS_USE_EC)); |
| + pmdp = pmd_offset(pudp, tpc); |
| + if (pmd_none(*pmdp) || unlikely(pmd_bad(*pmdp))) |
| + goto out_irq_enable; |
| |
| +#ifdef CONFIG_TRANSPARENT_HUGEPAGE |
| + if (pmd_trans_huge(*pmdp)) { |
| + if (pmd_trans_splitting(*pmdp)) |
| + goto out_irq_enable; |
| + |
| + pa = pmd_pfn(*pmdp) << PAGE_SHIFT; |
| + pa += tpc & ~HPAGE_MASK; |
| + |
| + /* Use phys bypass so we don't pollute dtlb/dcache. */ |
| + __asm__ __volatile__("lduwa [%1] %2, %0" |
| + : "=r" (insn) |
| + : "r" (pa), "i" (ASI_PHYS_USE_EC)); |
| + } else |
| +#endif |
| + { |
| + ptep = pte_offset_map(pmdp, tpc); |
| + pte = *ptep; |
| + if (pte_present(pte)) { |
| + pa = (pte_pfn(pte) << PAGE_SHIFT); |
| + pa += (tpc & ~PAGE_MASK); |
| + |
| + /* Use phys bypass so we don't pollute dtlb/dcache. */ |
| + __asm__ __volatile__("lduwa [%1] %2, %0" |
| + : "=r" (insn) |
| + : "r" (pa), "i" (ASI_PHYS_USE_EC)); |
| + } |
| + pte_unmap(ptep); |
| + } |
| +out_irq_enable: |
| + local_irq_enable(); |
| out: |
| - pte_unmap(ptep); |
| - __asm__ __volatile__("wrpr %0, 0x0, %%pstate" : : "r" (pstate)); |
| -outret: |
| return insn; |
| } |
| |
| @@ -154,7 +167,8 @@ show_signal_msg(struct pt_regs *regs, in |
| extern unsigned long compute_effective_address(struct pt_regs *, unsigned int, unsigned int); |
| |
| static void do_fault_siginfo(int code, int sig, struct pt_regs *regs, |
| - unsigned int insn, int fault_code) |
| + unsigned long fault_addr, unsigned int insn, |
| + int fault_code) |
| { |
| unsigned long addr; |
| siginfo_t info; |
| @@ -162,10 +176,18 @@ static void do_fault_siginfo(int code, i |
| info.si_code = code; |
| info.si_signo = sig; |
| info.si_errno = 0; |
| - if (fault_code & FAULT_CODE_ITLB) |
| + if (fault_code & FAULT_CODE_ITLB) { |
| addr = regs->tpc; |
| - else |
| - addr = compute_effective_address(regs, insn, 0); |
| + } else { |
| + /* If we were able to probe the faulting instruction, use it |
| + * to compute a precise fault address. Otherwise use the fault |
| + * time provided address which may only have page granularity. |
| + */ |
| + if (insn) |
| + addr = compute_effective_address(regs, insn, 0); |
| + else |
| + addr = fault_addr; |
| + } |
| info.si_addr = (void __user *) addr; |
| info.si_trapno = 0; |
| |
| @@ -240,7 +262,7 @@ static void __kprobes do_kernel_fault(st |
| /* The si_code was set to make clear whether |
| * this was a SEGV_MAPERR or SEGV_ACCERR fault. |
| */ |
| - do_fault_siginfo(si_code, SIGSEGV, regs, insn, fault_code); |
| + do_fault_siginfo(si_code, SIGSEGV, regs, address, insn, fault_code); |
| return; |
| } |
| |
| @@ -515,7 +537,7 @@ do_sigbus: |
| * Send a sigbus, regardless of whether we were in kernel |
| * or user mode. |
| */ |
| - do_fault_siginfo(BUS_ADRERR, SIGBUS, regs, insn, fault_code); |
| + do_fault_siginfo(BUS_ADRERR, SIGBUS, regs, address, insn, fault_code); |
| |
| /* Kernel mode? Handle exceptions or die */ |
| if (regs->tstate & TSTATE_PRIV) |