2005-06-24 09:01:24 +04:00
// TODO VM_EXEC flag work-around, cache aliasing
/*
* arch / xtensa / mm / fault . c
*
* This file is subject to the terms and conditions of the GNU General Public
* License . See the file " COPYING " in the main directory of this archive
* for more details .
*
* Copyright ( C ) 2001 - 2005 Tensilica Inc .
*
* Chris Zankel < chris @ zankel . net >
* Joe Taylor < joe @ tensilica . com , joetylr @ yahoo . com >
*/
# include <linux/mm.h>
# include <linux/module.h>
2009-03-10 22:55:49 +03:00
# include <linux/hardirq.h>
2005-06-24 09:01:24 +04:00
# include <asm/mmu_context.h>
# include <asm/cacheflush.h>
# include <asm/hardirq.h>
# include <asm/uaccess.h>
# include <asm/system.h>
# include <asm/pgalloc.h>
2006-12-10 13:18:48 +03:00
unsigned long asid_cache = ASID_USER_FIRST ;
2005-06-24 09:01:24 +04:00
void bad_page_fault ( struct pt_regs * , unsigned long , int ) ;
2007-08-22 21:14:51 +04:00
# undef DEBUG_PAGE_FAULT
2005-06-24 09:01:24 +04:00
/*
* This routine handles page faults . It determines the address ,
* and the problem , and then passes it off to one of the appropriate
* routines .
*
* Note : does not handle Miss and MultiHit .
*/
void do_page_fault ( struct pt_regs * regs )
{
struct vm_area_struct * vma ;
struct mm_struct * mm = current - > mm ;
unsigned int exccause = regs - > exccause ;
unsigned int address = regs - > excvaddr ;
siginfo_t info ;
int is_write , is_exec ;
2007-07-19 12:47:05 +04:00
int fault ;
2005-06-24 09:01:24 +04:00
info . si_code = SEGV_MAPERR ;
/* We fault-in kernel-space virtual memory on-demand. The
* ' reference ' page table is init_mm . pgd .
*/
if ( address > = TASK_SIZE & & ! user_mode ( regs ) )
goto vmalloc_fault ;
/* If we're in an interrupt or have no user
* context , we must not take the fault . .
*/
if ( in_atomic ( ) | | ! mm ) {
bad_page_fault ( regs , address , SIGSEGV ) ;
return ;
}
2006-12-10 13:18:48 +03:00
is_write = ( exccause = = EXCCAUSE_STORE_CACHE_ATTRIBUTE ) ? 1 : 0 ;
is_exec = ( exccause = = EXCCAUSE_ITLB_PRIVILEGE | |
exccause = = EXCCAUSE_ITLB_MISS | |
exccause = = EXCCAUSE_FETCH_CACHE_ATTRIBUTE ) ? 1 : 0 ;
2005-06-24 09:01:24 +04:00
2007-08-22 21:14:51 +04:00
# ifdef DEBUG_PAGE_FAULT
2005-06-24 09:01:24 +04:00
printk ( " [%s:%d:%08x:%d:%08x:%s%s] \n " , current - > comm , current - > pid ,
address , exccause , regs - > pc , is_write ? " w " : " " , is_exec ? " x " : " " ) ;
# endif
down_read ( & mm - > mmap_sem ) ;
vma = find_vma ( mm , address ) ;
if ( ! vma )
goto bad_area ;
if ( vma - > vm_start < = address )
goto good_area ;
if ( ! ( vma - > vm_flags & VM_GROWSDOWN ) )
goto bad_area ;
if ( expand_stack ( vma , address ) )
goto bad_area ;
/* Ok, we have a good vm_area for this memory access, so
* we can handle it . .
*/
good_area :
info . si_code = SEGV_ACCERR ;
if ( is_write ) {
if ( ! ( vma - > vm_flags & VM_WRITE ) )
goto bad_area ;
} else if ( is_exec ) {
if ( ! ( vma - > vm_flags & VM_EXEC ) )
goto bad_area ;
} else /* Allow read even from write-only pages. */
if ( ! ( vma - > vm_flags & ( VM_READ | VM_WRITE ) ) )
goto bad_area ;
/* If for any reason at all we couldn't handle the fault,
* make sure we exit gracefully rather than endlessly redo
* the fault .
*/
survive :
2009-04-10 20:01:23 +04:00
fault = handle_mm_fault ( mm , vma , address , is_write ? FAULT_FLAG_WRITE : 0 ) ;
2007-07-19 12:47:05 +04:00
if ( unlikely ( fault & VM_FAULT_ERROR ) ) {
if ( fault & VM_FAULT_OOM )
goto out_of_memory ;
else if ( fault & VM_FAULT_SIGBUS )
goto do_sigbus ;
2005-06-24 09:01:24 +04:00
BUG ( ) ;
}
2007-07-19 12:47:05 +04:00
if ( fault & VM_FAULT_MAJOR )
current - > maj_flt + + ;
else
current - > min_flt + + ;
2005-06-24 09:01:24 +04:00
up_read ( & mm - > mmap_sem ) ;
return ;
/* Something tried to access memory that isn't in our memory map..
* Fix it , but check if it ' s kernel or user first . .
*/
bad_area :
up_read ( & mm - > mmap_sem ) ;
if ( user_mode ( regs ) ) {
current - > thread . bad_vaddr = address ;
current - > thread . error_code = is_write ;
info . si_signo = SIGSEGV ;
info . si_errno = 0 ;
/* info.si_code has been set above */
info . si_addr = ( void * ) address ;
force_sig_info ( SIGSEGV , & info , current ) ;
return ;
}
bad_page_fault ( regs , address , SIGSEGV ) ;
return ;
/* We ran out of memory, or some other thing happened to us that made
* us unable to handle the page fault gracefully .
*/
out_of_memory :
up_read ( & mm - > mmap_sem ) ;
2007-10-19 10:39:52 +04:00
if ( is_global_init ( current ) ) {
2005-06-24 09:01:24 +04:00
yield ( ) ;
down_read ( & mm - > mmap_sem ) ;
goto survive ;
}
printk ( " VM: killing process %s \n " , current - > comm ) ;
if ( user_mode ( regs ) )
2007-10-16 12:24:18 +04:00
do_group_exit ( SIGKILL ) ;
2005-06-24 09:01:24 +04:00
bad_page_fault ( regs , address , SIGKILL ) ;
return ;
do_sigbus :
up_read ( & mm - > mmap_sem ) ;
/* Send a sigbus, regardless of whether we were in kernel
* or user mode .
*/
current - > thread . bad_vaddr = address ;
info . si_code = SIGBUS ;
info . si_errno = 0 ;
info . si_code = BUS_ADRERR ;
info . si_addr = ( void * ) address ;
force_sig_info ( SIGBUS , & info , current ) ;
/* Kernel mode? Handle exceptions or die */
if ( ! user_mode ( regs ) )
bad_page_fault ( regs , address , SIGBUS ) ;
vmalloc_fault :
{
/* Synchronize this task's top level page-table
* with the ' reference ' page table .
*/
struct mm_struct * act_mm = current - > active_mm ;
int index = pgd_index ( address ) ;
pgd_t * pgd , * pgd_k ;
pmd_t * pmd , * pmd_k ;
pte_t * pte_k ;
if ( act_mm = = NULL )
goto bad_page_fault ;
pgd = act_mm - > pgd + index ;
pgd_k = init_mm . pgd + index ;
if ( ! pgd_present ( * pgd_k ) )
goto bad_page_fault ;
pgd_val ( * pgd ) = pgd_val ( * pgd_k ) ;
pmd = pmd_offset ( pgd , address ) ;
pmd_k = pmd_offset ( pgd_k , address ) ;
if ( ! pmd_present ( * pmd ) | | ! pmd_present ( * pmd_k ) )
goto bad_page_fault ;
pmd_val ( * pmd ) = pmd_val ( * pmd_k ) ;
pte_k = pte_offset_kernel ( pmd_k , address ) ;
if ( ! pte_present ( * pte_k ) )
goto bad_page_fault ;
return ;
}
bad_page_fault :
bad_page_fault ( regs , address , SIGKILL ) ;
return ;
}
void
bad_page_fault ( struct pt_regs * regs , unsigned long address , int sig )
{
extern void die ( const char * , struct pt_regs * , long ) ;
const struct exception_table_entry * entry ;
/* Are we prepared to handle this kernel fault? */
if ( ( entry = search_exception_tables ( regs - > pc ) ) ! = NULL ) {
2007-08-22 21:14:51 +04:00
# ifdef DEBUG_PAGE_FAULT
2005-06-24 09:01:24 +04:00
printk ( KERN_DEBUG " %s: Exception at pc=%#010lx (%lx) \n " ,
current - > comm , regs - > pc , entry - > fixup ) ;
# endif
current - > thread . bad_uaddr = address ;
regs - > pc = entry - > fixup ;
return ;
}
/* Oops. The kernel tried to access some bad page. We'll have to
* terminate things with extreme prejudice .
*/
printk ( KERN_ALERT " Unable to handle kernel paging request at virtual "
" address %08lx \n pc = %08lx, ra = %08lx \n " ,
address , regs - > pc , regs - > areg [ 0 ] ) ;
die ( " Oops " , regs , sig ) ;
do_exit ( sig ) ;
}