sparc64: Fix top-level fault handling bugs.

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>
This commit is contained in:
David S. Miller 2014-04-28 23:52:11 -07:00
parent d037d16372
commit 70ffc6ebae

View File

@ -96,38 +96,51 @@ static unsigned int get_user_insn(unsigned long tpc)
pte_t *ptep, pte; pte_t *ptep, pte;
unsigned long pa; unsigned long pa;
u32 insn = 0; u32 insn = 0;
unsigned long pstate;
if (pgd_none(*pgdp)) if (pgd_none(*pgdp) || unlikely(pgd_bad(*pgdp)))
goto outret; goto out;
pudp = pud_offset(pgdp, tpc); pudp = pud_offset(pgdp, tpc);
if (pud_none(*pudp)) if (pud_none(*pudp) || unlikely(pud_bad(*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))
goto out; goto out;
pa = (pte_pfn(pte) << PAGE_SHIFT); /* This disables preemption for us as well. */
pa += (tpc & ~PAGE_MASK); local_irq_disable();
/* Use phys bypass so we don't pollute dtlb/dcache. */ pmdp = pmd_offset(pudp, tpc);
__asm__ __volatile__("lduwa [%1] %2, %0" if (pmd_none(*pmdp) || unlikely(pmd_bad(*pmdp)))
: "=r" (insn) goto out_irq_enable;
: "r" (pa), "i" (ASI_PHYS_USE_EC));
#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: out:
pte_unmap(ptep);
__asm__ __volatile__("wrpr %0, 0x0, %%pstate" : : "r" (pstate));
outret:
return insn; return insn;
} }
@ -153,7 +166,8 @@ show_signal_msg(struct pt_regs *regs, int sig, int code,
} }
static void do_fault_siginfo(int code, int sig, struct pt_regs *regs, 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; unsigned long addr;
siginfo_t info; siginfo_t info;
@ -161,10 +175,18 @@ static void do_fault_siginfo(int code, int sig, struct pt_regs *regs,
info.si_code = code; info.si_code = code;
info.si_signo = sig; info.si_signo = sig;
info.si_errno = 0; info.si_errno = 0;
if (fault_code & FAULT_CODE_ITLB) if (fault_code & FAULT_CODE_ITLB) {
addr = regs->tpc; addr = regs->tpc;
else } else {
addr = compute_effective_address(regs, insn, 0); /* 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_addr = (void __user *) addr;
info.si_trapno = 0; info.si_trapno = 0;
@ -239,7 +261,7 @@ static void __kprobes do_kernel_fault(struct pt_regs *regs, int si_code,
/* The si_code was set to make clear whether /* The si_code was set to make clear whether
* this was a SEGV_MAPERR or SEGV_ACCERR fault. * 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; return;
} }
@ -525,7 +547,7 @@ do_sigbus:
* Send a sigbus, regardless of whether we were in kernel * Send a sigbus, regardless of whether we were in kernel
* or user mode. * 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 */ /* Kernel mode? Handle exceptions or die */
if (regs->tstate & TSTATE_PRIV) if (regs->tstate & TSTATE_PRIV)