2019-06-04 10:11:33 +02:00
// SPDX-License-Identifier: GPL-2.0-only
2013-01-18 15:12:20 +05:30
/* Page Fault Handling for ARC (TLB Miss / ProtV)
*
* Copyright ( C ) 2004 , 2007 - 2010 , 2011 - 2012 Synopsys , Inc . ( www . synopsys . com )
*/
# include <linux/signal.h>
# include <linux/interrupt.h>
2017-02-08 18:51:30 +01:00
# include <linux/sched/signal.h>
2013-01-18 15:12:20 +05:30
# include <linux/errno.h>
# include <linux/ptrace.h>
# include <linux/uaccess.h>
# include <linux/kdebug.h>
2014-10-02 12:30:42 +05:30
# include <linux/perf_event.h>
2018-08-17 15:44:47 -07:00
# include <linux/mm_types.h>
2013-01-18 15:12:20 +05:30
# include <asm/pgalloc.h>
2013-05-14 13:28:17 +05:30
# include <asm/mmu.h>
2013-01-18 15:12:20 +05:30
2015-03-05 17:06:31 +05:30
/*
* kernel virtual address is required to implement vmalloc / pkmap / fixmap
* Refer to asm / processor . h for System Memory Map
*
* It simply copies the PMD entry ( pointer to 2 nd level page table or hugepage )
* from swapper pgdir to task pgdir . The 2 nd level table / page is thus shared
*/
noinline static int handle_kernel_vaddr_fault ( unsigned long address )
2013-01-18 15:12:20 +05:30
{
/*
* Synchronize this task ' s top level page - table
* with the ' reference ' page table .
*/
pgd_t * pgd , * pgd_k ;
2019-11-30 17:51:06 -08:00
p4d_t * p4d , * p4d_k ;
2013-01-18 15:12:20 +05:30
pud_t * pud , * pud_k ;
pmd_t * pmd , * pmd_k ;
2013-11-02 17:47:49 +05:30
pgd = pgd_offset_fast ( current - > active_mm , address ) ;
2013-01-18 15:12:20 +05:30
pgd_k = pgd_offset_k ( address ) ;
if ( ! pgd_present ( * pgd_k ) )
goto bad_area ;
2019-11-30 17:51:06 -08:00
p4d = p4d_offset ( pgd , address ) ;
p4d_k = p4d_offset ( pgd_k , address ) ;
if ( ! p4d_present ( * p4d_k ) )
goto bad_area ;
pud = pud_offset ( p4d , address ) ;
pud_k = pud_offset ( p4d_k , address ) ;
2013-01-18 15:12:20 +05:30
if ( ! pud_present ( * pud_k ) )
goto bad_area ;
pmd = pmd_offset ( pud , address ) ;
pmd_k = pmd_offset ( pud_k , address ) ;
if ( ! pmd_present ( * pmd_k ) )
goto bad_area ;
set_pmd ( pmd , * pmd_k ) ;
/* XXX: create the TLB entry here */
return 0 ;
bad_area :
return 1 ;
}
2013-09-18 16:25:40 +05:30
void do_page_fault ( unsigned long address , struct pt_regs * regs )
2013-01-18 15:12:20 +05:30
{
struct vm_area_struct * vma = NULL ;
struct task_struct * tsk = current ;
struct mm_struct * mm = tsk - > mm ;
2019-05-14 15:55:31 -07:00
int sig , si_code = SEGV_MAPERR ;
2019-05-14 14:25:54 -07:00
unsigned int write = 0 , exec = 0 , mask ;
2019-05-14 15:55:31 -07:00
vm_fault_t fault = VM_FAULT_SIGSEGV ; /* handle_mm_fault() output */
2019-05-14 14:35:45 -07:00
unsigned int flags ; /* handle_mm_fault() input */
2013-01-18 15:12:20 +05:30
/*
* NOTE ! We MUST NOT take any locks for this case . We may
* be in an interrupt or a critical region , and should
* only copy the information from the master page table ,
* nothing more .
*/
2019-05-13 20:28:00 +03:00
if ( address > = VMALLOC_START & & ! user_mode ( regs ) ) {
2019-05-14 16:07:24 -07:00
if ( unlikely ( handle_kernel_vaddr_fault ( address ) ) )
2019-05-13 20:28:00 +03:00
goto no_context ;
2013-01-18 15:12:20 +05:30
else
return ;
}
/*
* If we ' re in an interrupt or have no user
* context , we must not take the fault . .
*/
2015-05-11 17:52:11 +02:00
if ( faulthandler_disabled ( ) | | ! mm )
2013-01-18 15:12:20 +05:30
goto no_context ;
2019-05-14 14:25:54 -07:00
if ( regs - > ecr_cause & ECR_C_PROTV_STORE ) /* ST/EX */
write = 1 ;
else if ( ( regs - > ecr_vec = = ECR_V_PROTV ) & &
( regs - > ecr_cause = = ECR_C_PROTV_INST_FETCH ) )
exec = 1 ;
2020-04-01 21:08:37 -07:00
flags = FAULT_FLAG_DEFAULT ;
2013-09-12 15:13:39 -07:00
if ( user_mode ( regs ) )
flags | = FAULT_FLAG_USER ;
2019-05-14 14:25:54 -07:00
if ( write )
flags | = FAULT_FLAG_WRITE ;
2013-01-18 15:12:20 +05:30
retry :
down_read ( & mm - > mmap_sem ) ;
2019-05-14 14:22:47 -07:00
2013-01-18 15:12:20 +05:30
vma = find_vma ( mm , address ) ;
if ( ! vma )
goto bad_area ;
2019-05-14 14:22:47 -07:00
if ( unlikely ( address < vma - > vm_start ) ) {
if ( ! ( vma - > vm_flags & VM_GROWSDOWN ) | | expand_stack ( vma , address ) )
goto bad_area ;
}
2013-01-18 15:12:20 +05:30
/*
2019-05-14 14:25:54 -07:00
* vm_area is good , now check permissions for this memory access
2013-01-18 15:12:20 +05:30
*/
2019-05-14 14:25:54 -07:00
mask = VM_READ ;
if ( write )
mask = VM_WRITE ;
if ( exec )
mask = VM_EXEC ;
if ( ! ( vma - > vm_flags & mask ) ) {
si_code = SEGV_ACCERR ;
2013-01-18 15:12:20 +05:30
goto bad_area ;
}
2016-07-26 15:25:18 -07:00
fault = handle_mm_fault ( vma , address , flags ) ;
2013-01-18 15:12:20 +05:30
2020-04-01 21:08:14 -07:00
/* Quick path to respond to signals */
if ( fault_signal_pending ( fault , regs ) ) {
if ( ! user_mode ( regs ) )
goto no_context ;
return ;
}
2019-05-14 14:35:45 -07:00
/*
2020-04-01 21:08:14 -07:00
* Fault retry nuances , mmap_sem already relinquished by core mm
2019-05-14 14:35:45 -07:00
*/
2020-04-01 21:08:14 -07:00
if ( unlikely ( ( fault & VM_FAULT_RETRY ) & &
( flags & FAULT_FLAG_ALLOW_RETRY ) ) ) {
flags | = FAULT_FLAG_TRIED ;
goto retry ;
2013-01-18 15:12:20 +05:30
}
2019-05-14 16:28:30 -07:00
bad_area :
up_read ( & mm - > mmap_sem ) ;
2013-01-18 15:12:20 +05:30
/*
2019-05-14 14:35:45 -07:00
* Major / minor page fault accounting
* ( in case of retry we only land here once )
2013-01-18 15:12:20 +05:30
*/
2014-10-02 12:30:42 +05:30
perf_sw_event ( PERF_COUNT_SW_PAGE_FAULTS , 1 , regs , address ) ;
2013-01-18 15:12:20 +05:30
if ( likely ( ! ( fault & VM_FAULT_ERROR ) ) ) {
2019-05-14 14:35:45 -07:00
if ( fault & VM_FAULT_MAJOR ) {
tsk - > maj_flt + + ;
perf_sw_event ( PERF_COUNT_SW_PAGE_FAULTS_MAJ , 1 ,
regs , address ) ;
} else {
tsk - > min_flt + + ;
perf_sw_event ( PERF_COUNT_SW_PAGE_FAULTS_MIN , 1 ,
regs , address ) ;
2013-01-18 15:12:20 +05:30
}
2019-05-14 14:35:45 -07:00
/* Normal return path: fault Handled Gracefully */
2013-01-18 15:12:20 +05:30
return ;
}
2019-05-14 15:10:45 -07:00
if ( ! user_mode ( regs ) )
goto no_context ;
2013-01-18 15:12:20 +05:30
2019-05-14 15:55:31 -07:00
if ( fault & VM_FAULT_OOM ) {
2013-07-08 15:59:50 -07:00
pagefault_out_of_memory ( ) ;
return ;
}
2013-01-18 15:12:20 +05:30
2019-05-14 15:55:31 -07:00
if ( fault & VM_FAULT_SIGBUS ) {
sig = SIGBUS ;
si_code = BUS_ADRERR ;
}
else {
sig = SIGSEGV ;
}
2013-01-18 15:12:20 +05:30
tsk - > thread . fault_address = address ;
2019-07-16 15:07:51 -07:00
force_sig_fault ( sig , si_code , ( void __user * ) address ) ;
2019-05-14 14:45:44 -07:00
return ;
2013-01-18 15:12:20 +05:30
2019-05-14 14:45:44 -07:00
no_context :
if ( fixup_exception ( regs ) )
return ;
2013-01-18 15:12:20 +05:30
2019-05-14 14:45:44 -07:00
die ( " Oops " , regs , address ) ;
2013-01-18 15:12:20 +05:30
}