diff options
Diffstat (limited to 'arch/sparc/mm/fault_64.c')
| -rw-r--r-- | arch/sparc/mm/fault_64.c | 179 | 
1 files changed, 108 insertions, 71 deletions
diff --git a/arch/sparc/mm/fault_64.c b/arch/sparc/mm/fault_64.c index f92ce56a8b2..587cd056512 100644 --- a/arch/sparc/mm/fault_64.c +++ b/arch/sparc/mm/fault_64.c @@ -21,6 +21,7 @@  #include <linux/kprobes.h>  #include <linux/kdebug.h>  #include <linux/percpu.h> +#include <linux/context_tracking.h>  #include <asm/page.h>  #include <asm/pgtable.h> @@ -31,6 +32,7 @@  #include <asm/lsu.h>  #include <asm/sections.h>  #include <asm/mmu_context.h> +#include <asm/setup.h>  int show_unhandled_signals = 1; @@ -95,38 +97,51 @@ 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; - -	/* 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); - -	/* 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)); +	/* This disables preemption for us as well. */ +	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)) { +			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;  } @@ -151,10 +166,9 @@ show_signal_msg(struct pt_regs *regs, int sig, int code,  	printk(KERN_CONT "\n");  } -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, 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 -		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; @@ -175,9 +197,6 @@ static void do_fault_siginfo(int code, int sig, struct pt_regs *regs,  	force_sig_info(sig, &info, current);  } -extern int handle_ldf_stq(u32, struct pt_regs *); -extern int handle_ld_nf(u32, struct pt_regs *); -  static unsigned int get_fault_insn(struct pt_regs *regs, unsigned int insn)  {  	if (!insn) { @@ -240,7 +259,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;  	} @@ -260,30 +279,20 @@ static void noinline __kprobes bogus_32bit_fault_tpc(struct pt_regs *regs)  	show_regs(regs);  } -static void noinline __kprobes bogus_32bit_fault_address(struct pt_regs *regs, -							 unsigned long addr) -{ -	static int times; - -	if (times++ < 10) -		printk(KERN_ERR "FAULT[%s:%d]: 32-bit process " -		       "reports 64-bit fault address [%lx]\n", -		       current->comm, current->pid, addr); -	show_regs(regs); -} -  asmlinkage void __kprobes do_sparc64_fault(struct pt_regs *regs)  { +	enum ctx_state prev_state = exception_enter();  	struct mm_struct *mm = current->mm;  	struct vm_area_struct *vma;  	unsigned int insn = 0;  	int si_code, fault_code, fault;  	unsigned long address, mm_rss; +	unsigned int flags = FAULT_FLAG_ALLOW_RETRY | FAULT_FLAG_KILLABLE;  	fault_code = get_thread_fault_code();  	if (notify_page_fault(regs)) -		return; +		goto exit_exception;  	si_code = SEGV_MAPERR;  	address = current_thread_info()->fault_address; @@ -299,10 +308,8 @@ asmlinkage void __kprobes do_sparc64_fault(struct pt_regs *regs)  				goto intr_or_no_mm;  			}  		} -		if (unlikely((address >> 32) != 0)) { -			bogus_32bit_fault_address(regs, address); +		if (unlikely((address >> 32) != 0))  			goto intr_or_no_mm; -		}  	}  	if (regs->tstate & TSTATE_PRIV) { @@ -314,9 +321,10 @@ asmlinkage void __kprobes do_sparc64_fault(struct pt_regs *regs)  			/* Valid, no problems... */  		} else {  			bad_kernel_pc(regs, address); -			return; +			goto exit_exception;  		} -	} +	} else +		flags |= FAULT_FLAG_USER;  	/*  	 * If we're in an interrupt or have no user @@ -325,7 +333,7 @@ asmlinkage void __kprobes do_sparc64_fault(struct pt_regs *regs)  	if (in_atomic() || !mm)  		goto intr_or_no_mm; -	perf_sw_event(PERF_COUNT_SW_PAGE_FAULTS, 1, 0, regs, address); +	perf_sw_event(PERF_COUNT_SW_PAGE_FAULTS, 1, regs, address);  	if (!down_read_trylock(&mm->mmap_sem)) {  		if ((regs->tstate & TSTATE_PRIV) && @@ -333,6 +341,8 @@ asmlinkage void __kprobes do_sparc64_fault(struct pt_regs *regs)  			insn = get_fault_insn(regs, insn);  			goto handle_kernel_fault;  		} + +retry:  		down_read(&mm->mmap_sem);  	} @@ -417,13 +427,19 @@ good_area:  		    vma->vm_file != NULL)  			set_thread_fault_code(fault_code |  					      FAULT_CODE_BLKCOMMIT); + +		flags |= FAULT_FLAG_WRITE;  	} else {  		/* Allow reads even for write-only mappings */  		if (!(vma->vm_flags & (VM_READ | VM_EXEC)))  			goto bad_area;  	} -	fault = handle_mm_fault(mm, vma, address, (fault_code & FAULT_CODE_WRITE) ? FAULT_FLAG_WRITE : 0); +	fault = handle_mm_fault(mm, vma, address, flags); + +	if ((fault & VM_FAULT_RETRY) && fatal_signal_pending(current)) +		goto exit_exception; +  	if (unlikely(fault & VM_FAULT_ERROR)) {  		if (fault & VM_FAULT_OOM)  			goto out_of_memory; @@ -431,30 +447,51 @@ good_area:  			goto do_sigbus;  		BUG();  	} -	if (fault & VM_FAULT_MAJOR) { -		current->maj_flt++; -		perf_sw_event(PERF_COUNT_SW_PAGE_FAULTS_MAJ, 1, 0, -			      regs, address); -	} else { -		current->min_flt++; -		perf_sw_event(PERF_COUNT_SW_PAGE_FAULTS_MIN, 1, 0, -			      regs, address); + +	if (flags & FAULT_FLAG_ALLOW_RETRY) { +		if (fault & VM_FAULT_MAJOR) { +			current->maj_flt++; +			perf_sw_event(PERF_COUNT_SW_PAGE_FAULTS_MAJ, +				      1, regs, address); +		} else { +			current->min_flt++; +			perf_sw_event(PERF_COUNT_SW_PAGE_FAULTS_MIN, +				      1, regs, address); +		} +		if (fault & VM_FAULT_RETRY) { +			flags &= ~FAULT_FLAG_ALLOW_RETRY; +			flags |= FAULT_FLAG_TRIED; + +			/* No need to up_read(&mm->mmap_sem) as we would +			 * have already released it in __lock_page_or_retry +			 * in mm/filemap.c. +			 */ + +			goto retry; +		}  	}  	up_read(&mm->mmap_sem);  	mm_rss = get_mm_rss(mm); -#ifdef CONFIG_HUGETLB_PAGE +#if defined(CONFIG_HUGETLB_PAGE) || defined(CONFIG_TRANSPARENT_HUGEPAGE)  	mm_rss -= (mm->context.huge_pte_count * (HPAGE_SIZE / PAGE_SIZE));  #endif  	if (unlikely(mm_rss >  		     mm->context.tsb_block[MM_TSB_BASE].tsb_rss_limit))  		tsb_grow(mm, MM_TSB_BASE, mm_rss); -#ifdef CONFIG_HUGETLB_PAGE +#if defined(CONFIG_HUGETLB_PAGE) || defined(CONFIG_TRANSPARENT_HUGEPAGE)  	mm_rss = mm->context.huge_pte_count;  	if (unlikely(mm_rss > -		     mm->context.tsb_block[MM_TSB_HUGE].tsb_rss_limit)) -		tsb_grow(mm, MM_TSB_HUGE, mm_rss); +		     mm->context.tsb_block[MM_TSB_HUGE].tsb_rss_limit)) { +		if (mm->context.tsb_block[MM_TSB_HUGE].tsb) +			tsb_grow(mm, MM_TSB_HUGE, mm_rss); +		else +			hugetlb_setup(regs); + +	}  #endif +exit_exception: +	exception_exit(prev_state);  	return;  	/* @@ -467,7 +504,7 @@ bad_area:  handle_kernel_fault:  	do_kernel_fault(regs, si_code, fault_code, insn, address); -	return; +	goto exit_exception;  /*   * We ran out of memory, or some other thing happened to us that made @@ -478,7 +515,7 @@ out_of_memory:  	up_read(&mm->mmap_sem);  	if (!(regs->tstate & TSTATE_PRIV)) {  		pagefault_out_of_memory(); -		return; +		goto exit_exception;  	}  	goto handle_kernel_fault; @@ -494,7 +531,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)  | 
