#include <linux/extable.h>
#include <linux/signal.h>
#include <linux/sched.h>
#include <linux/kernel.h>
#include <linux/errno.h>
#include <linux/string.h>
#include <linux/types.h>
#include <linux/ptrace.h>
#include <linux/mman.h>
#include <linux/mm.h>
#include <linux/interrupt.h>
#include <linux/perf_event.h>
#include <asm/page.h>
#include <asm/mmu.h>
#include <linux/mmu_context.h>
#include <linux/uaccess.h>
#include <asm/exceptions.h>
static unsigned long pte_misses;
static unsigned long pte_errors;
static int store_updates_sp(struct pt_regs *regs)
{
unsigned int inst;
if (get_user(inst, (unsigned int __user *)regs->pc))
return 0;
if (((inst >> 21) & 0x1f) != 1)
return 0;
if ((inst & 0xd0000000) == 0xd0000000)
return 1;
return 0;
}
void bad_page_fault(struct pt_regs *regs, unsigned long address, int sig)
{
const struct exception_table_entry *fixup;
fixup = search_exception_tables(regs->pc);
if (fixup) {
regs->pc = fixup->fixup;
return;
}
die("kernel access of bad area", regs, sig);
}
void do_page_fault(struct pt_regs *regs, unsigned long address,
unsigned long error_code)
{
struct vm_area_struct *vma;
struct mm_struct *mm = current->mm;
int code = SEGV_MAPERR;
int is_write = error_code & ESR_S;
vm_fault_t fault;
unsigned int flags = FAULT_FLAG_DEFAULT;
regs->ear = address;
regs->esr = error_code;
if (unlikely(kernel_mode(regs) && (address >= TASK_SIZE))) {
pr_warn("kernel task_size exceed");
_exception(SIGSEGV, regs, code, address);
}
if ((error_code & 0x13) == 0x13 || (error_code & 0x11) == 0x11)
is_write = 0;
if (unlikely(faulthandler_disabled() || !mm)) {
if (kernel_mode(regs))
goto bad_area_nosemaphore;
pr_emerg("Page fault in user mode with faulthandler_disabled(), mm = %p\n",
mm);
pr_emerg("r15 = %lx MSR = %lx\n",
regs->r15, regs->msr);
die("Weird page fault", regs, SIGSEGV);
}
if (user_mode(regs))
flags |= FAULT_FLAG_USER;
perf_sw_event(PERF_COUNT_SW_PAGE_FAULTS, 1, regs, address);
if (unlikely(!mmap_read_trylock(mm))) {
if (kernel_mode(regs) && !search_exception_tables(regs->pc))
goto bad_area_nosemaphore;
retry:
mmap_read_lock(mm);
}
vma = find_vma(mm, address);
if (unlikely(!vma))
goto bad_area;
if (vma->vm_start <= address)
goto good_area;
if (unlikely(!(vma->vm_flags & VM_GROWSDOWN)))
goto bad_area;
if (unlikely(!is_write))
goto bad_area;
if (unlikely(address + 0x100000 < vma->vm_end)) {
struct pt_regs *uregs = current->thread.regs;
if (uregs == NULL)
goto bad_area;
if (address + 2048 < uregs->r1
&& (kernel_mode(regs) || !store_updates_sp(regs)))
goto bad_area;
}
vma = expand_stack(mm, address);
if (!vma)
goto bad_area_nosemaphore;
good_area:
code = SEGV_ACCERR;
if (unlikely(is_write)) {
if (unlikely(!(vma->vm_flags & VM_WRITE)))
goto bad_area;
flags |= FAULT_FLAG_WRITE;
} else {
if (unlikely(error_code & 0x08000000))
goto bad_area;
if (unlikely(!(vma->vm_flags & (VM_READ | VM_EXEC))))
goto bad_area;
}
fault = handle_mm_fault(vma, address, flags, regs);
if (fault_signal_pending(fault, regs)) {
if (!user_mode(regs))
bad_page_fault(regs, address, SIGBUS);
return;
}
if (fault & VM_FAULT_COMPLETED)
return;
if (unlikely(fault & VM_FAULT_ERROR)) {
if (fault & VM_FAULT_OOM)
goto out_of_memory;
else if (fault & VM_FAULT_SIGSEGV)
goto bad_area;
else if (fault & VM_FAULT_SIGBUS)
goto do_sigbus;
BUG();
}
if (fault & VM_FAULT_RETRY) {
flags |= FAULT_FLAG_TRIED;
goto retry;
}
mmap_read_unlock(mm);
pte_misses++;
return;
bad_area:
mmap_read_unlock(mm);
bad_area_nosemaphore:
pte_errors++;
if (user_mode(regs)) {
_exception(SIGSEGV, regs, code, address);
return;
}
bad_page_fault(regs, address, SIGSEGV);
return;
out_of_memory:
mmap_read_unlock(mm);
if (!user_mode(regs))
bad_page_fault(regs, address, SIGKILL);
else
pagefault_out_of_memory();
return;
do_sigbus:
mmap_read_unlock(mm);
if (user_mode(regs)) {
force_sig_fault(SIGBUS, BUS_ADRERR, (void __user *)address);
return;
}
bad_page_fault(regs, address, SIGBUS);
}