mirror of
https://github.com/FEX-Emu/linux.git
synced 2024-12-26 19:36:41 +00:00
9caebec7b8
Avoid the costly notifier list in the pagefault path and call the kprobes code directly. The same change went into the 2.6.22 cycle for powerpc, 2s390 and sparc64 already. Signed-off-by: Christoph Hellwig <hch@lst.de> Signed-off-by: Haavard Skinnemoen <hskinnemoen@atmel.com>
270 lines
6.2 KiB
C
270 lines
6.2 KiB
C
/*
|
|
* Copyright (C) 2004-2006 Atmel Corporation
|
|
*
|
|
* Based on linux/arch/sh/mm/fault.c:
|
|
* Copyright (C) 1999 Niibe Yutaka
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License version 2 as
|
|
* published by the Free Software Foundation.
|
|
*/
|
|
|
|
#include <linux/mm.h>
|
|
#include <linux/module.h>
|
|
#include <linux/pagemap.h>
|
|
#include <linux/kdebug.h>
|
|
#include <linux/kprobes.h>
|
|
|
|
#include <asm/mmu_context.h>
|
|
#include <asm/sysreg.h>
|
|
#include <asm/tlb.h>
|
|
#include <asm/uaccess.h>
|
|
|
|
#ifdef CONFIG_KPROBES
|
|
static inline int notify_page_fault(struct pt_regs *regs, int trap)
|
|
{
|
|
int ret = 0;
|
|
|
|
if (!user_mode(regs)) {
|
|
if (kprobe_running() && kprobe_fault_handler(regs, trap))
|
|
ret = 1;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
#else
|
|
static inline int notify_page_fault(struct pt_regs *regs, int trap)
|
|
{
|
|
return 0;
|
|
}
|
|
#endif
|
|
|
|
int exception_trace = 1;
|
|
|
|
/*
|
|
* This routine handles page faults. It determines the address and the
|
|
* problem, and then passes it off to one of the appropriate routines.
|
|
*
|
|
* ecr is the Exception Cause Register. Possible values are:
|
|
* 6: Protection fault (instruction access)
|
|
* 15: Protection fault (read access)
|
|
* 16: Protection fault (write access)
|
|
* 20: Page not found (instruction access)
|
|
* 24: Page not found (read access)
|
|
* 28: Page not found (write access)
|
|
*/
|
|
asmlinkage void do_page_fault(unsigned long ecr, struct pt_regs *regs)
|
|
{
|
|
struct task_struct *tsk;
|
|
struct mm_struct *mm;
|
|
struct vm_area_struct *vma;
|
|
const struct exception_table_entry *fixup;
|
|
unsigned long address;
|
|
unsigned long page;
|
|
int writeaccess;
|
|
long signr;
|
|
int code;
|
|
|
|
if (notify_page_fault(regs, ecr))
|
|
return;
|
|
|
|
address = sysreg_read(TLBEAR);
|
|
|
|
tsk = current;
|
|
mm = tsk->mm;
|
|
|
|
signr = SIGSEGV;
|
|
code = SEGV_MAPERR;
|
|
|
|
/*
|
|
* If we're in an interrupt or have no user context, we must
|
|
* not take the fault...
|
|
*/
|
|
if (in_atomic() || !mm || regs->sr & SYSREG_BIT(GM))
|
|
goto no_context;
|
|
|
|
local_irq_enable();
|
|
|
|
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:
|
|
code = SEGV_ACCERR;
|
|
writeaccess = 0;
|
|
|
|
switch (ecr) {
|
|
case ECR_PROTECTION_X:
|
|
case ECR_TLB_MISS_X:
|
|
if (!(vma->vm_flags & VM_EXEC))
|
|
goto bad_area;
|
|
break;
|
|
case ECR_PROTECTION_R:
|
|
case ECR_TLB_MISS_R:
|
|
if (!(vma->vm_flags & (VM_READ | VM_WRITE | VM_EXEC)))
|
|
goto bad_area;
|
|
break;
|
|
case ECR_PROTECTION_W:
|
|
case ECR_TLB_MISS_W:
|
|
if (!(vma->vm_flags & VM_WRITE))
|
|
goto bad_area;
|
|
writeaccess = 1;
|
|
break;
|
|
default:
|
|
panic("Unhandled case %lu in do_page_fault!", ecr);
|
|
}
|
|
|
|
/*
|
|
* If for any reason at all we couldn't handle the fault, make
|
|
* sure we exit gracefully rather than endlessly redo the
|
|
* fault.
|
|
*/
|
|
survive:
|
|
switch (handle_mm_fault(mm, vma, address, writeaccess)) {
|
|
case VM_FAULT_MINOR:
|
|
tsk->min_flt++;
|
|
break;
|
|
case VM_FAULT_MAJOR:
|
|
tsk->maj_flt++;
|
|
break;
|
|
case VM_FAULT_SIGBUS:
|
|
goto do_sigbus;
|
|
case VM_FAULT_OOM:
|
|
goto out_of_memory;
|
|
default:
|
|
BUG();
|
|
}
|
|
|
|
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)) {
|
|
if (exception_trace)
|
|
printk("%s%s[%d]: segfault at %08lx pc %08lx "
|
|
"sp %08lx ecr %lu\n",
|
|
is_init(tsk) ? KERN_EMERG : KERN_INFO,
|
|
tsk->comm, tsk->pid, address, regs->pc,
|
|
regs->sp, ecr);
|
|
_exception(SIGSEGV, regs, code, address);
|
|
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);
|
|
|
|
page = sysreg_read(PTBR);
|
|
printk(KERN_ALERT "ptbr = %08lx", page);
|
|
if (page) {
|
|
page = ((unsigned long *)page)[address >> 22];
|
|
printk(" pgd = %08lx", page);
|
|
if (page & _PAGE_PRESENT) {
|
|
page &= PAGE_MASK;
|
|
address &= 0x003ff000;
|
|
page = ((unsigned long *)__va(page))[address >> PAGE_SHIFT];
|
|
printk(" pte = %08lx", page);
|
|
}
|
|
}
|
|
printk("\n");
|
|
die("Kernel access of bad area", regs, signr);
|
|
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);
|
|
if (is_init(current)) {
|
|
yield();
|
|
down_read(&mm->mmap_sem);
|
|
goto survive;
|
|
}
|
|
printk("VM: Killing process %s\n", tsk->comm);
|
|
if (user_mode(regs))
|
|
do_exit(SIGKILL);
|
|
goto no_context;
|
|
|
|
do_sigbus:
|
|
up_read(&mm->mmap_sem);
|
|
|
|
/* Kernel mode? Handle exceptions or die */
|
|
signr = SIGBUS;
|
|
code = BUS_ADRERR;
|
|
if (!user_mode(regs))
|
|
goto no_context;
|
|
|
|
if (exception_trace)
|
|
printk("%s%s[%d]: bus error at %08lx pc %08lx "
|
|
"sp %08lx ecr %lu\n",
|
|
is_init(tsk) ? KERN_EMERG : KERN_INFO,
|
|
tsk->comm, tsk->pid, address, regs->pc,
|
|
regs->sp, ecr);
|
|
|
|
_exception(SIGBUS, regs, BUS_ADRERR, address);
|
|
}
|
|
|
|
asmlinkage void do_bus_error(unsigned long addr, int write_access,
|
|
struct pt_regs *regs)
|
|
{
|
|
printk(KERN_ALERT
|
|
"Bus error at physical address 0x%08lx (%s access)\n",
|
|
addr, write_access ? "write" : "read");
|
|
printk(KERN_INFO "DTLB dump:\n");
|
|
dump_dtlb();
|
|
die("Bus Error", regs, SIGKILL);
|
|
}
|
|
|
|
/*
|
|
* This functionality is currently not possible to implement because
|
|
* we're using segmentation to ensure a fixed mapping of the kernel
|
|
* virtual address space.
|
|
*
|
|
* It would be possible to implement this, but it would require us to
|
|
* disable segmentation at startup and load the kernel mappings into
|
|
* the TLB like any other pages. There will be lots of trickery to
|
|
* avoid recursive invocation of the TLB miss handler, though...
|
|
*/
|
|
#ifdef CONFIG_DEBUG_PAGEALLOC
|
|
void kernel_map_pages(struct page *page, int numpages, int enable)
|
|
{
|
|
|
|
}
|
|
EXPORT_SYMBOL(kernel_map_pages);
|
|
#endif
|