/*1* arch/score/mm/fault.c2*3* Score Processor version.4*5* Copyright (C) 2009 Sunplus Core Technology Co., Ltd.6* Lennox Wu <[email protected]>7* Chen Liqin <[email protected]>8*9* This program is free software; you can redistribute it and/or modify10* it under the terms of the GNU General Public License as published by11* the Free Software Foundation; either version 2 of the License, or12* (at your option) any later version.13*14* This program is distributed in the hope that it will be useful,15* but WITHOUT ANY WARRANTY; without even the implied warranty of16* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the17* GNU General Public License for more details.18*19* You should have received a copy of the GNU General Public License20* along with this program; if not, see the file COPYING, or write21* to the Free Software Foundation, Inc.,22* 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA23*/2425#include <linux/errno.h>26#include <linux/interrupt.h>27#include <linux/kernel.h>28#include <linux/mm.h>29#include <linux/mman.h>30#include <linux/module.h>31#include <linux/signal.h>32#include <linux/sched.h>33#include <linux/string.h>34#include <linux/types.h>35#include <linux/ptrace.h>3637/*38* This routine handles page faults. It determines the address,39* and the problem, and then passes it off to one of the appropriate40* routines.41*/42asmlinkage void do_page_fault(struct pt_regs *regs, unsigned long write,43unsigned long address)44{45struct vm_area_struct *vma = NULL;46struct task_struct *tsk = current;47struct mm_struct *mm = tsk->mm;48const int field = sizeof(unsigned long) * 2;49siginfo_t info;50int fault;5152info.si_code = SEGV_MAPERR;5354/*55* We fault-in kernel-space virtual memory on-demand. The56* 'reference' page table is init_mm.pgd.57*58* NOTE! We MUST NOT take any locks for this case. We may59* be in an interrupt or a critical region, and should60* only copy the information from the master page table,61* nothing more.62*/63if (unlikely(address >= VMALLOC_START && address <= VMALLOC_END))64goto vmalloc_fault;65#ifdef MODULE_START66if (unlikely(address >= MODULE_START && address < MODULE_END))67goto vmalloc_fault;68#endif6970/*71* If we're in an interrupt or have no user72* context, we must not take the fault..73*/74if (in_atomic() || !mm)75goto bad_area_nosemaphore;7677down_read(&mm->mmap_sem);78vma = find_vma(mm, address);79if (!vma)80goto bad_area;81if (vma->vm_start <= address)82goto good_area;83if (!(vma->vm_flags & VM_GROWSDOWN))84goto bad_area;85if (expand_stack(vma, address))86goto bad_area;87/*88* Ok, we have a good vm_area for this memory access, so89* we can handle it..90*/91good_area:92info.si_code = SEGV_ACCERR;9394if (write) {95if (!(vma->vm_flags & VM_WRITE))96goto bad_area;97} else {98if (!(vma->vm_flags & (VM_READ | VM_WRITE | VM_EXEC)))99goto bad_area;100}101102survive:103/*104* If for any reason at all we couldn't handle the fault,105* make sure we exit gracefully rather than endlessly redo106* the fault.107*/108fault = handle_mm_fault(mm, vma, address, write);109if (unlikely(fault & VM_FAULT_ERROR)) {110if (fault & VM_FAULT_OOM)111goto out_of_memory;112else if (fault & VM_FAULT_SIGBUS)113goto do_sigbus;114BUG();115}116if (fault & VM_FAULT_MAJOR)117tsk->maj_flt++;118else119tsk->min_flt++;120121up_read(&mm->mmap_sem);122return;123124/*125* Something tried to access memory that isn't in our memory map..126* Fix it, but check if it's kernel or user first..127*/128bad_area:129up_read(&mm->mmap_sem);130131bad_area_nosemaphore:132/* User mode accesses just cause a SIGSEGV */133if (user_mode(regs)) {134tsk->thread.cp0_badvaddr = address;135tsk->thread.error_code = write;136info.si_signo = SIGSEGV;137info.si_errno = 0;138/* info.si_code has been set above */139info.si_addr = (void __user *) address;140force_sig_info(SIGSEGV, &info, tsk);141return;142}143144no_context:145/* Are we prepared to handle this kernel fault? */146if (fixup_exception(regs)) {147current->thread.cp0_baduaddr = address;148return;149}150151/*152* Oops. The kernel tried to access some bad page. We'll have to153* terminate things with extreme prejudice.154*/155bust_spinlocks(1);156157printk(KERN_ALERT "CPU %d Unable to handle kernel paging request at "158"virtual address %0*lx, epc == %0*lx, ra == %0*lx\n",1590, field, address, field, regs->cp0_epc,160field, regs->regs[3]);161die("Oops", regs);162163/*164* We ran out of memory, or some other thing happened to us that made165* us unable to handle the page fault gracefully.166*/167out_of_memory:168up_read(&mm->mmap_sem);169if (is_global_init(tsk)) {170yield();171down_read(&mm->mmap_sem);172goto survive;173}174printk("VM: killing process %s\n", tsk->comm);175if (user_mode(regs))176do_group_exit(SIGKILL);177goto no_context;178179do_sigbus:180up_read(&mm->mmap_sem);181/* Kernel mode? Handle exceptions or die */182if (!user_mode(regs))183goto no_context;184else185/*186* Send a sigbus, regardless of whether we were in kernel187* or user mode.188*/189tsk->thread.cp0_badvaddr = address;190info.si_signo = SIGBUS;191info.si_errno = 0;192info.si_code = BUS_ADRERR;193info.si_addr = (void __user *) address;194force_sig_info(SIGBUS, &info, tsk);195return;196vmalloc_fault:197{198/*199* Synchronize this task's top level page-table200* with the 'reference' page table.201*202* Do _not_ use "tsk" here. We might be inside203* an interrupt in the middle of a task switch..204*/205int offset = __pgd_offset(address);206pgd_t *pgd, *pgd_k;207pud_t *pud, *pud_k;208pmd_t *pmd, *pmd_k;209pte_t *pte_k;210211pgd = (pgd_t *) pgd_current + offset;212pgd_k = init_mm.pgd + offset;213214if (!pgd_present(*pgd_k))215goto no_context;216set_pgd(pgd, *pgd_k);217218pud = pud_offset(pgd, address);219pud_k = pud_offset(pgd_k, address);220if (!pud_present(*pud_k))221goto no_context;222223pmd = pmd_offset(pud, address);224pmd_k = pmd_offset(pud_k, address);225if (!pmd_present(*pmd_k))226goto no_context;227set_pmd(pmd, *pmd_k);228229pte_k = pte_offset_kernel(pmd_k, address);230if (!pte_present(*pte_k))231goto no_context;232return;233}234}235236237