Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 70ffc6eb authored by David S. Miller's avatar David S. Miller
Browse files

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: default avatarDavid S. Miller <davem@davemloft.net>
parent d037d163
Loading
Loading
Loading
Loading
+52 −30
Original line number Diff line number Diff line
@@ -96,26 +96,38 @@ static unsigned int get_user_insn(unsigned long tpc)
	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;
	if (pud_none(*pudp) || unlikely(pud_bad(*pudp)))
		goto out;

	/* 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));
	local_irq_disable();

	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))
		goto out;

		if (pte_present(pte)) {
			pa  = (pte_pfn(pte) << PAGE_SHIFT);
			pa += (tpc & ~PAGE_MASK);

@@ -123,11 +135,12 @@ static unsigned int get_user_insn(unsigned long tpc)
			__asm__ __volatile__("lduwa [%1] %2, %0"
					     : "=r" (insn)
					     : "r" (pa), "i" (ASI_PHYS_USE_EC));

out:
		}
		pte_unmap(ptep);
	__asm__ __volatile__("wrpr %0, 0x0, %%pstate" : : "r" (pstate));
outret:
	}
out_irq_enable:
	local_irq_enable();
out:
	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,
			     unsigned int insn, int fault_code)
			     unsigned long fault_addr, unsigned int insn,
			     int fault_code)
{
	unsigned long addr;
	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_signo = sig;
	info.si_errno = 0;
	if (fault_code & FAULT_CODE_ITLB)
	if (fault_code & FAULT_CODE_ITLB) {
		addr = regs->tpc;
	else
	} 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;

@@ -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
		 * 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;
	}

@@ -525,7 +547,7 @@ asmlinkage void __kprobes do_sparc64_fault(struct pt_regs *regs)
	 * 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)