diff options
Diffstat (limited to 'arch/arm/mm/fault.c')
| -rw-r--r-- | arch/arm/mm/fault.c | 169 | 
1 files changed, 58 insertions, 111 deletions
diff --git a/arch/arm/mm/fault.c b/arch/arm/mm/fault.c index aa33949fef6..bb7eac381a8 100644 --- a/arch/arm/mm/fault.c +++ b/arch/arm/mm/fault.c @@ -27,19 +27,6 @@  #include "fault.h" -/* - * Fault status register encodings.  We steal bit 31 for our own purposes. - */ -#define FSR_LNX_PF		(1 << 31) -#define FSR_WRITE		(1 << 11) -#define FSR_FS4			(1 << 10) -#define FSR_FS3_0		(15) - -static inline int fsr_fs(unsigned int fsr) -{ -	return (fsr & FSR_FS3_0) | (fsr & FSR_FS4) >> 6; -} -  #ifdef CONFIG_MMU  #ifdef CONFIG_KPROBES @@ -123,8 +110,10 @@ void show_pte(struct mm_struct *mm, unsigned long addr)  		pte = pte_offset_map(pmd, addr);  		printk(", *pte=%08llx", (long long)pte_val(*pte)); +#ifndef CONFIG_ARM_LPAE  		printk(", *ppte=%08llx",  		       (long long)pte_val(pte[PTE_HWTABLE_PTRS])); +#endif  		pte_unmap(pte);  	} while(0); @@ -231,7 +220,7 @@ static inline bool access_error(unsigned int fsr, struct vm_area_struct *vma)  static int __kprobes  __do_page_fault(struct mm_struct *mm, unsigned long addr, unsigned int fsr, -		struct task_struct *tsk) +		unsigned int flags, struct task_struct *tsk)  {  	struct vm_area_struct *vma;  	int fault; @@ -253,18 +242,7 @@ good_area:  		goto out;  	} -	/* -	 * If for any reason at all we couldn't handle the fault, make -	 * sure we exit gracefully rather than endlessly redo the fault. -	 */ -	fault = handle_mm_fault(mm, vma, addr & PAGE_MASK, (fsr & FSR_WRITE) ? FAULT_FLAG_WRITE : 0); -	if (unlikely(fault & VM_FAULT_ERROR)) -		return fault; -	if (fault & VM_FAULT_MAJOR) -		tsk->maj_flt++; -	else -		tsk->min_flt++; -	return fault; +	return handle_mm_fault(mm, vma, addr & PAGE_MASK, flags);  check_stack:  	if (vma->vm_flags & VM_GROWSDOWN && !expand_stack(vma, addr)) @@ -279,6 +257,9 @@ do_page_fault(unsigned long addr, unsigned int fsr, struct pt_regs *regs)  	struct task_struct *tsk;  	struct mm_struct *mm;  	int fault, sig, code; +	int write = fsr & FSR_WRITE; +	unsigned int flags = FAULT_FLAG_ALLOW_RETRY | FAULT_FLAG_KILLABLE | +				(write ? FAULT_FLAG_WRITE : 0);  	if (notify_page_fault(regs, fsr))  		return 0; @@ -305,6 +286,7 @@ do_page_fault(unsigned long addr, unsigned int fsr, struct pt_regs *regs)  	if (!down_read_trylock(&mm->mmap_sem)) {  		if (!user_mode(regs) && !search_exception_tables(regs->ARM_pc))  			goto no_context; +retry:  		down_read(&mm->mmap_sem);  	} else {  		/* @@ -320,14 +302,41 @@ do_page_fault(unsigned long addr, unsigned int fsr, struct pt_regs *regs)  #endif  	} -	fault = __do_page_fault(mm, addr, fsr, tsk); -	up_read(&mm->mmap_sem); +	fault = __do_page_fault(mm, addr, fsr, flags, tsk); + +	/* If we need to retry but a fatal signal is pending, handle the +	 * signal first. We do not need to release the mmap_sem because +	 * it would already be released in __lock_page_or_retry in +	 * mm/filemap.c. */ +	if ((fault & VM_FAULT_RETRY) && fatal_signal_pending(current)) +		return 0; + +	/* +	 * Major/minor page fault accounting is only done on the +	 * initial attempt. If we go through a retry, it is extremely +	 * likely that the page will be found in page cache at that point. +	 */  	perf_sw_event(PERF_COUNT_SW_PAGE_FAULTS, 1, regs, addr); -	if (fault & VM_FAULT_MAJOR) -		perf_sw_event(PERF_COUNT_SW_PAGE_FAULTS_MAJ, 1, regs, addr); -	else if (fault & VM_FAULT_MINOR) -		perf_sw_event(PERF_COUNT_SW_PAGE_FAULTS_MIN, 1, regs, addr); +	if (flags & FAULT_FLAG_ALLOW_RETRY) { +		if (fault & VM_FAULT_MAJOR) { +			tsk->maj_flt++; +			perf_sw_event(PERF_COUNT_SW_PAGE_FAULTS_MAJ, 1, +					regs, addr); +		} else { +			tsk->min_flt++; +			perf_sw_event(PERF_COUNT_SW_PAGE_FAULTS_MIN, 1, +					regs, addr); +		} +		if (fault & VM_FAULT_RETRY) { +			/* Clear FAULT_FLAG_ALLOW_RETRY to avoid any risk +			* of starvation. */ +			flags &= ~FAULT_FLAG_ALLOW_RETRY; +			goto retry; +		} +	} + +	up_read(&mm->mmap_sem);  	/*  	 * Handle the "normal" case first - VM_FAULT_MAJOR / VM_FAULT_MINOR @@ -441,6 +450,12 @@ do_translation_fault(unsigned long addr, unsigned int fsr,  	pmd = pmd_offset(pud, addr);  	pmd_k = pmd_offset(pud_k, addr); +#ifdef CONFIG_ARM_LPAE +	/* +	 * Only one hardware entry per PMD with LPAE. +	 */ +	index = 0; +#else  	/*  	 * On ARM one Linux PGD entry contains two hardware entries (see page  	 * tables layout in pgtable.h). We normally guarantee that we always @@ -450,6 +465,7 @@ do_translation_fault(unsigned long addr, unsigned int fsr,  	 * for the first of pair.  	 */  	index = (addr >> SECTION_SHIFT) & 1; +#endif  	if (pmd_none(pmd_k[index]))  		goto bad_area; @@ -489,55 +505,20 @@ do_bad(unsigned long addr, unsigned int fsr, struct pt_regs *regs)  	return 1;  } -static struct fsr_info { +struct fsr_info {  	int	(*fn)(unsigned long addr, unsigned int fsr, struct pt_regs *regs);  	int	sig;  	int	code;  	const char *name; -} fsr_info[] = { -	/* -	 * The following are the standard ARMv3 and ARMv4 aborts.  ARMv5 -	 * defines these to be "precise" aborts. -	 */ -	{ do_bad,		SIGSEGV, 0,		"vector exception"		   }, -	{ do_bad,		SIGBUS,	 BUS_ADRALN,	"alignment exception"		   }, -	{ do_bad,		SIGKILL, 0,		"terminal exception"		   }, -	{ do_bad,		SIGBUS,	 BUS_ADRALN,	"alignment exception"		   }, -	{ do_bad,		SIGBUS,	 0,		"external abort on linefetch"	   }, -	{ do_translation_fault,	SIGSEGV, SEGV_MAPERR,	"section translation fault"	   }, -	{ do_bad,		SIGBUS,	 0,		"external abort on linefetch"	   }, -	{ do_page_fault,	SIGSEGV, SEGV_MAPERR,	"page translation fault"	   }, -	{ do_bad,		SIGBUS,	 0,		"external abort on non-linefetch"  }, -	{ do_bad,		SIGSEGV, SEGV_ACCERR,	"section domain fault"		   }, -	{ do_bad,		SIGBUS,	 0,		"external abort on non-linefetch"  }, -	{ do_bad,		SIGSEGV, SEGV_ACCERR,	"page domain fault"		   }, -	{ do_bad,		SIGBUS,	 0,		"external abort on translation"	   }, -	{ do_sect_fault,	SIGSEGV, SEGV_ACCERR,	"section permission fault"	   }, -	{ do_bad,		SIGBUS,	 0,		"external abort on translation"	   }, -	{ do_page_fault,	SIGSEGV, SEGV_ACCERR,	"page permission fault"		   }, -	/* -	 * The following are "imprecise" aborts, which are signalled by bit -	 * 10 of the FSR, and may not be recoverable.  These are only -	 * supported if the CPU abort handler supports bit 10. -	 */ -	{ do_bad,		SIGBUS,  0,		"unknown 16"			   }, -	{ do_bad,		SIGBUS,  0,		"unknown 17"			   }, -	{ do_bad,		SIGBUS,  0,		"unknown 18"			   }, -	{ do_bad,		SIGBUS,  0,		"unknown 19"			   }, -	{ do_bad,		SIGBUS,  0,		"lock abort"			   }, /* xscale */ -	{ do_bad,		SIGBUS,  0,		"unknown 21"			   }, -	{ do_bad,		SIGBUS,  BUS_OBJERR,	"imprecise external abort"	   }, /* xscale */ -	{ do_bad,		SIGBUS,  0,		"unknown 23"			   }, -	{ do_bad,		SIGBUS,  0,		"dcache parity error"		   }, /* xscale */ -	{ do_bad,		SIGBUS,  0,		"unknown 25"			   }, -	{ do_bad,		SIGBUS,  0,		"unknown 26"			   }, -	{ do_bad,		SIGBUS,  0,		"unknown 27"			   }, -	{ do_bad,		SIGBUS,  0,		"unknown 28"			   }, -	{ do_bad,		SIGBUS,  0,		"unknown 29"			   }, -	{ do_bad,		SIGBUS,  0,		"unknown 30"			   }, -	{ do_bad,		SIGBUS,  0,		"unknown 31"			   }  }; +/* FSR definition */ +#ifdef CONFIG_ARM_LPAE +#include "fsr-3level.c" +#else +#include "fsr-2level.c" +#endif +  void __init  hook_fault_code(int nr, int (*fn)(unsigned long, unsigned int, struct pt_regs *),  		int sig, int code, const char *name) @@ -573,42 +554,6 @@ do_DataAbort(unsigned long addr, unsigned int fsr, struct pt_regs *regs)  	arm_notify_die("", regs, &info, fsr, 0);  } - -static struct fsr_info ifsr_info[] = { -	{ do_bad,		SIGBUS,  0,		"unknown 0"			   }, -	{ do_bad,		SIGBUS,  0,		"unknown 1"			   }, -	{ do_bad,		SIGBUS,  0,		"debug event"			   }, -	{ do_bad,		SIGSEGV, SEGV_ACCERR,	"section access flag fault"	   }, -	{ do_bad,		SIGBUS,  0,		"unknown 4"			   }, -	{ do_translation_fault,	SIGSEGV, SEGV_MAPERR,	"section translation fault"	   }, -	{ do_bad,		SIGSEGV, SEGV_ACCERR,	"page access flag fault"	   }, -	{ do_page_fault,	SIGSEGV, SEGV_MAPERR,	"page translation fault"	   }, -	{ do_bad,		SIGBUS,	 0,		"external abort on non-linefetch"  }, -	{ do_bad,		SIGSEGV, SEGV_ACCERR,	"section domain fault"		   }, -	{ do_bad,		SIGBUS,  0,		"unknown 10"			   }, -	{ do_bad,		SIGSEGV, SEGV_ACCERR,	"page domain fault"		   }, -	{ do_bad,		SIGBUS,	 0,		"external abort on translation"	   }, -	{ do_sect_fault,	SIGSEGV, SEGV_ACCERR,	"section permission fault"	   }, -	{ do_bad,		SIGBUS,	 0,		"external abort on translation"	   }, -	{ do_page_fault,	SIGSEGV, SEGV_ACCERR,	"page permission fault"		   }, -	{ do_bad,		SIGBUS,  0,		"unknown 16"			   }, -	{ do_bad,		SIGBUS,  0,		"unknown 17"			   }, -	{ do_bad,		SIGBUS,  0,		"unknown 18"			   }, -	{ do_bad,		SIGBUS,  0,		"unknown 19"			   }, -	{ do_bad,		SIGBUS,  0,		"unknown 20"			   }, -	{ do_bad,		SIGBUS,  0,		"unknown 21"			   }, -	{ do_bad,		SIGBUS,  0,		"unknown 22"			   }, -	{ do_bad,		SIGBUS,  0,		"unknown 23"			   }, -	{ do_bad,		SIGBUS,  0,		"unknown 24"			   }, -	{ do_bad,		SIGBUS,  0,		"unknown 25"			   }, -	{ do_bad,		SIGBUS,  0,		"unknown 26"			   }, -	{ do_bad,		SIGBUS,  0,		"unknown 27"			   }, -	{ do_bad,		SIGBUS,  0,		"unknown 28"			   }, -	{ do_bad,		SIGBUS,  0,		"unknown 29"			   }, -	{ do_bad,		SIGBUS,  0,		"unknown 30"			   }, -	{ do_bad,		SIGBUS,  0,		"unknown 31"			   }, -}; -  void __init  hook_ifault_code(int nr, int (*fn)(unsigned long, unsigned int, struct pt_regs *),  		 int sig, int code, const char *name) @@ -641,6 +586,7 @@ do_PrefetchAbort(unsigned long addr, unsigned int ifsr, struct pt_regs *regs)  	arm_notify_die("", regs, &info, ifsr, 0);  } +#ifndef CONFIG_ARM_LPAE  static int __init exceptions_init(void)  {  	if (cpu_architecture() >= CPU_ARCH_ARMv6) { @@ -663,3 +609,4 @@ static int __init exceptions_init(void)  }  arch_initcall(exceptions_init); +#endif  |