|
@@ -28,249 +28,6 @@
|
|
|
#include <asm/pgalloc.h>
|
|
|
#include <asm/mmu_context.h>
|
|
|
|
|
|
-static pte_t *lookup_pte(struct mm_struct *mm, unsigned long address)
|
|
|
-{
|
|
|
- pgd_t *dir;
|
|
|
- pud_t *pud;
|
|
|
- pmd_t *pmd;
|
|
|
- pte_t *pte;
|
|
|
- pte_t entry;
|
|
|
-
|
|
|
- dir = pgd_offset(mm, address);
|
|
|
- if (pgd_none(*dir))
|
|
|
- return NULL;
|
|
|
-
|
|
|
- pud = pud_offset(dir, address);
|
|
|
- if (pud_none(*pud))
|
|
|
- return NULL;
|
|
|
-
|
|
|
- pmd = pmd_offset(pud, address);
|
|
|
- if (pmd_none(*pmd))
|
|
|
- return NULL;
|
|
|
-
|
|
|
- pte = pte_offset_kernel(pmd, address);
|
|
|
- entry = *pte;
|
|
|
- if (pte_none(entry) || !pte_present(entry))
|
|
|
- return NULL;
|
|
|
-
|
|
|
- return pte;
|
|
|
-}
|
|
|
-
|
|
|
-/*
|
|
|
- * This routine handles page faults. It determines the address,
|
|
|
- * and the problem, and then passes it off to one of the appropriate
|
|
|
- * routines.
|
|
|
- */
|
|
|
-asmlinkage void do_page_fault(struct pt_regs *regs, unsigned long error_code,
|
|
|
- unsigned long address)
|
|
|
-{
|
|
|
- struct task_struct *tsk;
|
|
|
- struct mm_struct *mm;
|
|
|
- struct vm_area_struct * vma;
|
|
|
- const struct exception_table_entry *fixup;
|
|
|
- int write = error_code & FAULT_CODE_WRITE;
|
|
|
- int textaccess = error_code & FAULT_CODE_ITLB;
|
|
|
- unsigned int flags = (FAULT_FLAG_ALLOW_RETRY | FAULT_FLAG_KILLABLE |
|
|
|
- (write ? FAULT_FLAG_WRITE : 0));
|
|
|
- pte_t *pte;
|
|
|
- int fault;
|
|
|
-
|
|
|
- /* SIM
|
|
|
- * Note this is now called with interrupts still disabled
|
|
|
- * This is to cope with being called for a missing IO port
|
|
|
- * address with interrupts disabled. This should be fixed as
|
|
|
- * soon as we have a better 'fast path' miss handler.
|
|
|
- *
|
|
|
- * Plus take care how you try and debug this stuff.
|
|
|
- * For example, writing debug data to a port which you
|
|
|
- * have just faulted on is not going to work.
|
|
|
- */
|
|
|
-
|
|
|
- tsk = current;
|
|
|
- mm = tsk->mm;
|
|
|
-
|
|
|
- /* Not an IO address, so reenable interrupts */
|
|
|
- local_irq_enable();
|
|
|
-
|
|
|
- perf_sw_event(PERF_COUNT_SW_PAGE_FAULTS, 1, regs, address);
|
|
|
-
|
|
|
- /*
|
|
|
- * If we're in an interrupt or have no user
|
|
|
- * context, we must not take the fault..
|
|
|
- */
|
|
|
- if (in_atomic() || !mm)
|
|
|
- goto no_context;
|
|
|
-
|
|
|
-retry:
|
|
|
- /* TLB misses upon some cache flushes get done under cli() */
|
|
|
- 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:
|
|
|
- if (textaccess) {
|
|
|
- if (!(vma->vm_flags & VM_EXEC))
|
|
|
- goto bad_area;
|
|
|
- } else {
|
|
|
- if (write) {
|
|
|
- if (!(vma->vm_flags & VM_WRITE))
|
|
|
- goto bad_area;
|
|
|
- } else {
|
|
|
- if (!(vma->vm_flags & VM_READ))
|
|
|
- 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.
|
|
|
- */
|
|
|
- fault = handle_mm_fault(mm, vma, address, flags);
|
|
|
-
|
|
|
- if ((fault & VM_FAULT_RETRY) && fatal_signal_pending(current))
|
|
|
- return;
|
|
|
-
|
|
|
- if (unlikely(fault & VM_FAULT_ERROR)) {
|
|
|
- if (fault & VM_FAULT_OOM)
|
|
|
- goto out_of_memory;
|
|
|
- else if (fault & VM_FAULT_SIGBUS)
|
|
|
- goto do_sigbus;
|
|
|
- BUG();
|
|
|
- }
|
|
|
-
|
|
|
- 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, address);
|
|
|
- } else {
|
|
|
- tsk->min_flt++;
|
|
|
- perf_sw_event(PERF_COUNT_SW_PAGE_FAULTS_MIN, 1,
|
|
|
- regs, address);
|
|
|
- }
|
|
|
-
|
|
|
- if (fault & VM_FAULT_RETRY) {
|
|
|
- flags &= ~FAULT_FLAG_ALLOW_RETRY;
|
|
|
-
|
|
|
- /*
|
|
|
- * 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;
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- /* If we get here, the page fault has been handled. Do the TLB refill
|
|
|
- now from the newly-setup PTE, to avoid having to fault again right
|
|
|
- away on the same instruction. */
|
|
|
- pte = lookup_pte (mm, address);
|
|
|
- if (!pte) {
|
|
|
- /* From empirical evidence, we can get here, due to
|
|
|
- !pte_present(pte). (e.g. if a swap-in occurs, and the page
|
|
|
- is swapped back out again before the process that wanted it
|
|
|
- gets rescheduled?) */
|
|
|
- goto no_pte;
|
|
|
- }
|
|
|
-
|
|
|
- __do_tlb_refill(address, textaccess, pte);
|
|
|
-
|
|
|
-no_pte:
|
|
|
-
|
|
|
- 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)) {
|
|
|
- static int count=0;
|
|
|
- siginfo_t info;
|
|
|
- if (count < 4) {
|
|
|
- /* This is really to help debug faults when starting
|
|
|
- * usermode, so only need a few */
|
|
|
- count++;
|
|
|
- printk("user mode bad_area address=%08lx pid=%d (%s) pc=%08lx\n",
|
|
|
- address, task_pid_nr(current), current->comm,
|
|
|
- (unsigned long) regs->pc);
|
|
|
- }
|
|
|
- if (is_global_init(tsk)) {
|
|
|
- panic("INIT had user mode bad_area\n");
|
|
|
- }
|
|
|
- tsk->thread.address = address;
|
|
|
- info.si_signo = SIGSEGV;
|
|
|
- info.si_errno = 0;
|
|
|
- info.si_addr = (void *) address;
|
|
|
- force_sig_info(SIGSEGV, &info, tsk);
|
|
|
- return;
|
|
|
- }
|
|
|
-
|
|
|
-no_context:
|
|
|
- /* Are we prepared to handle this kernel fault? */
|
|
|
- fixup = search_exception_tables(regs->pc);
|
|
|
- if (fixup) {
|
|
|
- regs->pc = fixup->fixup;
|
|
|
- return;
|
|
|
- }
|
|
|
-
|
|
|
-/*
|
|
|
- * Oops. The kernel tried to access some bad page. We'll have to
|
|
|
- * terminate things with extreme prejudice.
|
|
|
- *
|
|
|
- */
|
|
|
- if (address < PAGE_SIZE)
|
|
|
- printk(KERN_ALERT "Unable to handle kernel NULL pointer dereference");
|
|
|
- else
|
|
|
- printk(KERN_ALERT "Unable to handle kernel paging request");
|
|
|
- printk(" at virtual address %08lx\n", address);
|
|
|
- printk(KERN_ALERT "pc = %08Lx%08Lx\n", regs->pc >> 32, regs->pc & 0xffffffff);
|
|
|
- die("Oops", regs, error_code);
|
|
|
- do_exit(SIGKILL);
|
|
|
-
|
|
|
-/*
|
|
|
- * 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);
|
|
|
- if (!user_mode(regs))
|
|
|
- goto no_context;
|
|
|
- pagefault_out_of_memory();
|
|
|
- return;
|
|
|
-
|
|
|
-do_sigbus:
|
|
|
- printk("fault:Do sigbus\n");
|
|
|
- up_read(&mm->mmap_sem);
|
|
|
-
|
|
|
- /*
|
|
|
- * Send a sigbus, regardless of whether we were in kernel
|
|
|
- * or user mode.
|
|
|
- */
|
|
|
- tsk->thread.address = address;
|
|
|
- force_sig(SIGBUS, tsk);
|
|
|
-
|
|
|
- /* Kernel mode? Handle exceptions or die */
|
|
|
- if (!user_mode(regs))
|
|
|
- goto no_context;
|
|
|
-}
|
|
|
-
|
|
|
void local_flush_tlb_one(unsigned long asid, unsigned long page)
|
|
|
{
|
|
|
unsigned long long match, pteh=0, lpage;
|