672 lines
18 KiB
C
672 lines
18 KiB
C
/*
|
|
* linux/arch/arm/mm/fault.c
|
|
*
|
|
* Copyright (C) 1995 Linus Torvalds
|
|
* Modifications for ARM processor (c) 1995-2004 Russell King
|
|
*
|
|
* 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/module.h>
|
|
#include <linux/signal.h>
|
|
#include <linux/mm.h>
|
|
#include <linux/hardirq.h>
|
|
#include <linux/init.h>
|
|
#include <linux/kprobes.h>
|
|
#include <linux/uaccess.h>
|
|
#include <linux/page-flags.h>
|
|
#include <linux/sched.h>
|
|
#include <linux/highmem.h>
|
|
|
|
#include <asm/system.h>
|
|
#include <asm/pgtable.h>
|
|
#include <asm/tlbflush.h>
|
|
#ifdef CONFIG_ARCH_MSM_SCORPION
|
|
#include <asm/io.h>
|
|
#include <mach/msm_iomap.h>
|
|
#endif
|
|
|
|
#include "fault.h"
|
|
|
|
/*
|
|
* Fault status register encodings. We steal bit 31 for our own purposes.
|
|
*/
|
|
#define FSR_LNX_PF (1 << 31)
|
|
#define FSR_WRITE (1 << 11)
|
|
#define FSR_FS4 (1 << 10)
|
|
#define FSR_FS3_0 (15)
|
|
|
|
static inline int fsr_fs(unsigned int fsr)
|
|
{
|
|
return (fsr & FSR_FS3_0) | (fsr & FSR_FS4) >> 6;
|
|
}
|
|
|
|
#ifdef CONFIG_MMU
|
|
|
|
#ifdef CONFIG_KPROBES
|
|
static inline int notify_page_fault(struct pt_regs *regs, unsigned int fsr)
|
|
{
|
|
int ret = 0;
|
|
|
|
if (!user_mode(regs)) {
|
|
/* kprobe_running() needs smp_processor_id() */
|
|
preempt_disable();
|
|
if (kprobe_running() && kprobe_fault_handler(regs, fsr))
|
|
ret = 1;
|
|
preempt_enable();
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
#else
|
|
static inline int notify_page_fault(struct pt_regs *regs, unsigned int fsr)
|
|
{
|
|
return 0;
|
|
}
|
|
#endif
|
|
|
|
/*
|
|
* This is useful to dump out the page tables associated with
|
|
* 'addr' in mm 'mm'.
|
|
*/
|
|
void show_pte(struct mm_struct *mm, unsigned long addr)
|
|
{
|
|
pgd_t *pgd;
|
|
|
|
if (!mm)
|
|
mm = &init_mm;
|
|
|
|
printk(KERN_ALERT "pgd = %p\n", mm->pgd);
|
|
pgd = pgd_offset(mm, addr);
|
|
printk(KERN_ALERT "[%08lx] *pgd=%08lx", addr, pgd_val(*pgd));
|
|
|
|
do {
|
|
pmd_t *pmd;
|
|
pte_t *pte;
|
|
|
|
if (pgd_none(*pgd))
|
|
break;
|
|
|
|
if (pgd_bad(*pgd)) {
|
|
printk("(bad)");
|
|
break;
|
|
}
|
|
|
|
pmd = pmd_offset(pgd, addr);
|
|
if (PTRS_PER_PMD != 1)
|
|
printk(", *pmd=%08lx", pmd_val(*pmd));
|
|
|
|
if (pmd_none(*pmd))
|
|
break;
|
|
|
|
if (pmd_bad(*pmd)) {
|
|
printk("(bad)");
|
|
break;
|
|
}
|
|
|
|
/* We must not map this if we have highmem enabled */
|
|
if (PageHighMem(pfn_to_page(pmd_val(*pmd) >> PAGE_SHIFT)))
|
|
break;
|
|
|
|
pte = pte_offset_map(pmd, addr);
|
|
printk(", *pte=%08lx", pte_val(*pte));
|
|
printk(", *ppte=%08lx", pte_val(pte[-PTRS_PER_PTE]));
|
|
pte_unmap(pte);
|
|
} while(0);
|
|
|
|
printk("\n");
|
|
}
|
|
#else /* CONFIG_MMU */
|
|
void show_pte(struct mm_struct *mm, unsigned long addr)
|
|
{ }
|
|
#endif /* CONFIG_MMU */
|
|
|
|
/*
|
|
* Oops. The kernel tried to access some page that wasn't present.
|
|
*/
|
|
static void
|
|
__do_kernel_fault(struct mm_struct *mm, unsigned long addr, unsigned int fsr,
|
|
struct pt_regs *regs)
|
|
{
|
|
/*
|
|
* Are we prepared to handle this kernel fault?
|
|
*/
|
|
if (fixup_exception(regs))
|
|
return;
|
|
|
|
/*
|
|
* No handler, we'll have to terminate things with extreme prejudice.
|
|
*/
|
|
bust_spinlocks(1);
|
|
printk(KERN_ALERT
|
|
"Unable to handle kernel %s at virtual address %08lx\n",
|
|
(addr < PAGE_SIZE) ? "NULL pointer dereference" :
|
|
"paging request", addr);
|
|
|
|
show_pte(mm, addr);
|
|
die("Oops", regs, fsr);
|
|
bust_spinlocks(0);
|
|
do_exit(SIGKILL);
|
|
}
|
|
|
|
/*
|
|
* Something tried to access memory that isn't in our memory map..
|
|
* User mode accesses just cause a SIGSEGV
|
|
*/
|
|
static void
|
|
__do_user_fault(struct task_struct *tsk, unsigned long addr,
|
|
unsigned int fsr, unsigned int sig, int code,
|
|
struct pt_regs *regs)
|
|
{
|
|
struct siginfo si;
|
|
|
|
#ifdef CONFIG_DEBUG_USER
|
|
if (user_debug & UDBG_SEGV) {
|
|
printk(KERN_DEBUG "%s: unhandled page fault (%d) at 0x%08lx, code 0x%03x\n",
|
|
tsk->comm, sig, addr, fsr);
|
|
show_pte(tsk->mm, addr);
|
|
show_regs(regs);
|
|
}
|
|
#endif
|
|
|
|
tsk->thread.address = addr;
|
|
tsk->thread.error_code = fsr;
|
|
tsk->thread.trap_no = 14;
|
|
si.si_signo = sig;
|
|
si.si_errno = 0;
|
|
si.si_code = code;
|
|
si.si_addr = (void __user *)addr;
|
|
force_sig_info(sig, &si, tsk);
|
|
}
|
|
|
|
void do_bad_area(unsigned long addr, unsigned int fsr, struct pt_regs *regs)
|
|
{
|
|
struct task_struct *tsk = current;
|
|
struct mm_struct *mm = tsk->active_mm;
|
|
|
|
/*
|
|
* If we are in kernel mode at this point, we
|
|
* have no context to handle this fault with.
|
|
*/
|
|
if (user_mode(regs))
|
|
__do_user_fault(tsk, addr, fsr, SIGSEGV, SEGV_MAPERR, regs);
|
|
else
|
|
__do_kernel_fault(mm, addr, fsr, regs);
|
|
}
|
|
|
|
#ifdef CONFIG_MMU
|
|
#define VM_FAULT_BADMAP 0x010000
|
|
#define VM_FAULT_BADACCESS 0x020000
|
|
|
|
/*
|
|
* Check that the permissions on the VMA allow for the fault which occurred.
|
|
* If we encountered a write fault, we must have write permission, otherwise
|
|
* we allow any permission.
|
|
*/
|
|
static inline bool access_error(unsigned int fsr, struct vm_area_struct *vma)
|
|
{
|
|
unsigned int mask = VM_READ | VM_WRITE | VM_EXEC;
|
|
|
|
if (fsr & FSR_WRITE)
|
|
mask = VM_WRITE;
|
|
if (fsr & FSR_LNX_PF)
|
|
mask = VM_EXEC;
|
|
|
|
return vma->vm_flags & mask ? false : true;
|
|
}
|
|
|
|
static int __kprobes
|
|
__do_page_fault(struct mm_struct *mm, unsigned long addr, unsigned int fsr,
|
|
struct task_struct *tsk)
|
|
{
|
|
struct vm_area_struct *vma;
|
|
int fault;
|
|
|
|
vma = find_vma(mm, addr);
|
|
fault = VM_FAULT_BADMAP;
|
|
if (unlikely(!vma))
|
|
goto out;
|
|
if (unlikely(vma->vm_start > addr))
|
|
goto check_stack;
|
|
|
|
/*
|
|
* Ok, we have a good vm_area for this
|
|
* memory access, so we can handle it.
|
|
*/
|
|
good_area:
|
|
if (access_error(fsr, vma)) {
|
|
fault = VM_FAULT_BADACCESS;
|
|
goto out;
|
|
}
|
|
|
|
/*
|
|
* 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, addr & PAGE_MASK, (fsr & FSR_WRITE) ? FAULT_FLAG_WRITE : 0);
|
|
if (unlikely(fault & VM_FAULT_ERROR))
|
|
return fault;
|
|
if (fault & VM_FAULT_MAJOR)
|
|
tsk->maj_flt++;
|
|
else
|
|
tsk->min_flt++;
|
|
return fault;
|
|
|
|
check_stack:
|
|
if (vma->vm_flags & VM_GROWSDOWN && !expand_stack(vma, addr))
|
|
goto good_area;
|
|
out:
|
|
return fault;
|
|
}
|
|
|
|
static int __kprobes
|
|
do_page_fault(unsigned long addr, unsigned int fsr, struct pt_regs *regs)
|
|
{
|
|
struct task_struct *tsk;
|
|
struct mm_struct *mm;
|
|
int fault, sig, code;
|
|
|
|
if (notify_page_fault(regs, fsr))
|
|
return 0;
|
|
|
|
tsk = current;
|
|
mm = tsk->mm;
|
|
|
|
/*
|
|
* If we're in an interrupt or have no user
|
|
* context, we must not take the fault..
|
|
*/
|
|
if (in_atomic() || !mm)
|
|
goto no_context;
|
|
|
|
/*
|
|
* As per x86, we may deadlock here. However, since the kernel only
|
|
* validly references user space from well defined areas of the code,
|
|
* we can bug out early if this is from code which shouldn't.
|
|
*/
|
|
if (!down_read_trylock(&mm->mmap_sem)) {
|
|
if (!user_mode(regs) && !search_exception_tables(regs->ARM_pc))
|
|
goto no_context;
|
|
down_read(&mm->mmap_sem);
|
|
} else {
|
|
/*
|
|
* The above down_read_trylock() might have succeeded in
|
|
* which case, we'll have missed the might_sleep() from
|
|
* down_read()
|
|
*/
|
|
might_sleep();
|
|
#ifdef CONFIG_DEBUG_VM
|
|
if (!user_mode(regs) &&
|
|
!search_exception_tables(regs->ARM_pc))
|
|
goto no_context;
|
|
#endif
|
|
}
|
|
|
|
fault = __do_page_fault(mm, addr, fsr, tsk);
|
|
up_read(&mm->mmap_sem);
|
|
|
|
/*
|
|
* Handle the "normal" case first - VM_FAULT_MAJOR / VM_FAULT_MINOR
|
|
*/
|
|
if (likely(!(fault & (VM_FAULT_ERROR | VM_FAULT_BADMAP | VM_FAULT_BADACCESS))))
|
|
return 0;
|
|
|
|
if (fault & VM_FAULT_OOM) {
|
|
/*
|
|
* We ran out of memory, call the OOM killer, and return to
|
|
* userspace (which will retry the fault, or kill us if we
|
|
* got oom-killed)
|
|
*/
|
|
pagefault_out_of_memory();
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* If we are in kernel mode at this point, we
|
|
* have no context to handle this fault with.
|
|
*/
|
|
if (!user_mode(regs))
|
|
goto no_context;
|
|
|
|
if (fault & VM_FAULT_SIGBUS) {
|
|
/*
|
|
* We had some memory, but were unable to
|
|
* successfully fix up this page fault.
|
|
*/
|
|
sig = SIGBUS;
|
|
code = BUS_ADRERR;
|
|
} else {
|
|
/*
|
|
* Something tried to access memory that
|
|
* isn't in our memory map..
|
|
*/
|
|
sig = SIGSEGV;
|
|
code = fault == VM_FAULT_BADACCESS ?
|
|
SEGV_ACCERR : SEGV_MAPERR;
|
|
}
|
|
|
|
__do_user_fault(tsk, addr, fsr, sig, code, regs);
|
|
return 0;
|
|
|
|
no_context:
|
|
__do_kernel_fault(mm, addr, fsr, regs);
|
|
return 0;
|
|
}
|
|
#else /* CONFIG_MMU */
|
|
static int
|
|
do_page_fault(unsigned long addr, unsigned int fsr, struct pt_regs *regs)
|
|
{
|
|
return 0;
|
|
}
|
|
#endif /* CONFIG_MMU */
|
|
|
|
/*
|
|
* First Level Translation Fault Handler
|
|
*
|
|
* We enter here because the first level page table doesn't contain
|
|
* a valid entry for the address.
|
|
*
|
|
* If the address is in kernel space (>= TASK_SIZE), then we are
|
|
* probably faulting in the vmalloc() area.
|
|
*
|
|
* If the init_task's first level page tables contains the relevant
|
|
* entry, we copy the it to this task. If not, we send the process
|
|
* a signal, fixup the exception, or oops the kernel.
|
|
*
|
|
* NOTE! We MUST NOT take any locks for this case. We may be in an
|
|
* interrupt or a critical region, and should only copy the information
|
|
* from the master page table, nothing more.
|
|
*/
|
|
#ifdef CONFIG_MMU
|
|
static int __kprobes
|
|
do_translation_fault(unsigned long addr, unsigned int fsr,
|
|
struct pt_regs *regs)
|
|
{
|
|
unsigned int index;
|
|
pgd_t *pgd, *pgd_k;
|
|
pmd_t *pmd, *pmd_k;
|
|
|
|
if (addr < TASK_SIZE)
|
|
return do_page_fault(addr, fsr, regs);
|
|
|
|
index = pgd_index(addr);
|
|
|
|
/*
|
|
* FIXME: CP15 C1 is write only on ARMv3 architectures.
|
|
*/
|
|
pgd = cpu_get_pgd() + index;
|
|
pgd_k = init_mm.pgd + index;
|
|
|
|
if (pgd_none(*pgd_k))
|
|
goto bad_area;
|
|
|
|
if (!pgd_present(*pgd))
|
|
set_pgd(pgd, *pgd_k);
|
|
|
|
pmd_k = pmd_offset(pgd_k, addr);
|
|
pmd = pmd_offset(pgd, addr);
|
|
|
|
if (pmd_none(*pmd_k))
|
|
goto bad_area;
|
|
|
|
copy_pmd(pmd, pmd_k);
|
|
return 0;
|
|
|
|
bad_area:
|
|
do_bad_area(addr, fsr, regs);
|
|
return 0;
|
|
}
|
|
#else /* CONFIG_MMU */
|
|
static int
|
|
do_translation_fault(unsigned long addr, unsigned int fsr,
|
|
struct pt_regs *regs)
|
|
{
|
|
return 0;
|
|
}
|
|
#endif /* CONFIG_MMU */
|
|
|
|
/*
|
|
* Some section permission faults need to be handled gracefully.
|
|
* They can happen due to a __{get,put}_user during an oops.
|
|
*/
|
|
static int
|
|
do_sect_fault(unsigned long addr, unsigned int fsr, struct pt_regs *regs)
|
|
{
|
|
do_bad_area(addr, fsr, regs);
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* This abort handler always returns "fault".
|
|
*/
|
|
static int
|
|
do_bad(unsigned long addr, unsigned int fsr, struct pt_regs *regs)
|
|
{
|
|
return 1;
|
|
}
|
|
|
|
#ifdef CONFIG_ARCH_MSM_SCORPION
|
|
#define __str(x) #x
|
|
#define MRC(x, v1, v2, v4, v5, v6) do { \
|
|
unsigned int __##x; \
|
|
asm("mrc " __str(v1) ", " __str(v2) ", %0, " __str(v4) ", " \
|
|
__str(v5) ", " __str(v6) "\n" \
|
|
: "=r" (__##x)); \
|
|
pr_info("%s: %s = 0x%.8x\n", __func__, #x, __##x); \
|
|
} while(0)
|
|
|
|
#define MSM_TCSR_SPARE2 (MSM_TCSR_BASE + 0x60)
|
|
|
|
#endif
|
|
|
|
static int
|
|
do_imprecise_ext(unsigned long addr, unsigned int fsr, struct pt_regs *regs)
|
|
{
|
|
#ifdef CONFIG_ARCH_MSM_SCORPION
|
|
unsigned int regval;
|
|
static unsigned char flush_toggle;
|
|
|
|
asm("mrc p15, 0, %0, c5, c1, 0\n" /* read adfsr for fault status */
|
|
: "=r" (regval));
|
|
|
|
pr_err("%s: ADFSR = 0x%.8x\n", __func__, regval);
|
|
|
|
#ifdef CONFIG_ARCH_QSD8X50
|
|
pr_err("PLL_CTL (0xA8800004)= 0x%.8x\n", readl(MSM_SCPLL_BASE + 0x04));
|
|
pr_err("PLL_CAL (0xA8800008)= 0x%.8x\n", readl(MSM_SCPLL_BASE + 0x08));
|
|
pr_err("PLL_FSM_CTL_INT (0xA880000C)= 0x%.8x\n", readl(MSM_SCPLL_BASE + 0x0C));
|
|
pr_err("PLL_FSM_CTL_EXT (0xA8800010)= 0x%.8x\n", readl(MSM_SCPLL_BASE + 0x10));
|
|
pr_err("PLL_FSM_CFG (0xA8800014)= 0x%.8x\n", readl(MSM_SCPLL_BASE + 0x14));
|
|
pr_err("PLL_STATUS (0xA8800018)= 0x%.8x\n", readl(MSM_SCPLL_BASE + 0x18));
|
|
pr_err("PLL_INTERNAL (0xA8800020)= 0x%.8x\n", readl(MSM_SCPLL_BASE + 0x20));
|
|
pr_err("PLL_CAL_VAL (0xA8800080)= 0x%.8x\n", readl(MSM_SCPLL_BASE + 0x80));
|
|
pr_err("PLL_LUT_A_10_13 (0xA8800084)= 0x%.8x\n", readl(MSM_SCPLL_BASE + 0x84));
|
|
pr_err("PLL_LUT_A_14_17 (0xA8800088)= 0x%.8x\n", readl(MSM_SCPLL_BASE + 0x88));
|
|
pr_err("PLL_LUT_A_18_21 (0xA880008C)= 0x%.8x\n", readl(MSM_SCPLL_BASE + 0x8C));
|
|
pr_err("PLL_LUT_A_22_25 (0xA8800090)= 0x%.8x\n", readl(MSM_SCPLL_BASE + 0x90));
|
|
pr_err("PLL_LUT_A_26_29 (0xA8800094)= 0x%.8x\n", readl(MSM_SCPLL_BASE + 0x94));
|
|
pr_err("PLL_LUT_A_30_33 (0xA8800098)= 0x%.8x\n", readl(MSM_SCPLL_BASE + 0x98));
|
|
pr_err("PLL_LUT_B_10_13 (0xA88000C4)= 0x%.8x\n", readl(MSM_SCPLL_BASE + 0xC4));
|
|
pr_err("PLL_LUT_B_14_17 (0xA88000C8)= 0x%.8x\n", readl(MSM_SCPLL_BASE + 0xC8));
|
|
pr_err("PLL_LUT_B_18_21 (0xA88000CC)= 0x%.8x\n", readl(MSM_SCPLL_BASE + 0xCC));
|
|
pr_err("PLL_LUT_B_22_25 (0xA88000D0)= 0x%.8x\n", readl(MSM_SCPLL_BASE + 0xD0));
|
|
pr_err("PLL_LUT_B_26_29 (0xA88000D4)= 0x%.8x\n", readl(MSM_SCPLL_BASE + 0xD4));
|
|
pr_err("PLL_LUT_B_30_33 (0xA88000D8)= 0x%.8x\n", readl(MSM_SCPLL_BASE + 0xD8));
|
|
#endif
|
|
|
|
if (regval == 0x2) {
|
|
/* Fault was caused by icache parity error. Alternate
|
|
* simply retrying the access and flushing the icache. */
|
|
flush_toggle ^= 1;
|
|
if (flush_toggle)
|
|
asm("mcr p15, 0, %0, c7, c5, 0\n"
|
|
:
|
|
: "r" (regval)); /* input value is ignored */
|
|
/* Clear fault in EFSR. */
|
|
asm("mcr p15, 7, %0, c15, c0, 1\n"
|
|
:
|
|
: "r" (regval));
|
|
/* Clear fault in ADFSR. */
|
|
regval = 0;
|
|
asm("mcr p15, 0, %0, c5, c1, 0\n"
|
|
:
|
|
: "r" (regval));
|
|
return 0;
|
|
} else {
|
|
MRC(DFSR, p15, 0, c5, c0, 0);
|
|
MRC(ACTLR, p15, 0, c1, c0, 1);
|
|
MRC(EFSR, p15, 7, c15, c0, 1);
|
|
MRC(L2SR, p15, 3, c15, c1, 0);
|
|
MRC(L2CR0, p15, 3, c15, c0, 1);
|
|
MRC(L2CPUESR, p15, 3, c15, c1, 1);
|
|
MRC(L2CPUCR, p15, 3, c15, c0, 2);
|
|
MRC(SPESR, p15, 1, c9, c7, 0);
|
|
MRC(SPCR, p15, 0, c9, c7, 0);
|
|
MRC(DMACHSR, p15, 1, c11, c0, 0);
|
|
MRC(DMACHESR, p15, 1, c11, c0, 1);
|
|
MRC(DMACHCR, p15, 0, c11, c0, 2);
|
|
pr_err("%s: TCSR_SPARE2 = 0x%.8x\n", __func__, readl(MSM_TCSR_SPARE2));
|
|
}
|
|
#endif
|
|
return 1;
|
|
}
|
|
|
|
static struct fsr_info {
|
|
int (*fn)(unsigned long addr, unsigned int fsr, struct pt_regs *regs);
|
|
int sig;
|
|
int code;
|
|
const char *name;
|
|
} fsr_info[] = {
|
|
/*
|
|
* The following are the standard ARMv3 and ARMv4 aborts. ARMv5
|
|
* defines these to be "precise" aborts.
|
|
*/
|
|
{ do_bad, SIGSEGV, 0, "vector exception" },
|
|
{ do_bad, SIGILL, BUS_ADRALN, "alignment exception" },
|
|
{ do_bad, SIGKILL, 0, "terminal exception" },
|
|
{ do_bad, SIGILL, BUS_ADRALN, "alignment exception" },
|
|
{ do_bad, SIGBUS, 0, "external abort on linefetch" },
|
|
{ do_translation_fault, SIGSEGV, SEGV_MAPERR, "section translation fault" },
|
|
{ do_bad, SIGBUS, 0, "external abort on linefetch" },
|
|
{ do_page_fault, SIGSEGV, SEGV_MAPERR, "page translation fault" },
|
|
{ do_bad, SIGBUS, 0, "external abort on non-linefetch" },
|
|
{ do_bad, SIGSEGV, SEGV_ACCERR, "section domain fault" },
|
|
{ do_bad, SIGBUS, 0, "external abort on non-linefetch" },
|
|
{ do_bad, SIGSEGV, SEGV_ACCERR, "page domain fault" },
|
|
{ do_bad, SIGBUS, 0, "external abort on translation" },
|
|
{ do_sect_fault, SIGSEGV, SEGV_ACCERR, "section permission fault" },
|
|
{ do_bad, SIGBUS, 0, "external abort on translation" },
|
|
{ do_page_fault, SIGSEGV, SEGV_ACCERR, "page permission fault" },
|
|
/*
|
|
* The following are "imprecise" aborts, which are signalled by bit
|
|
* 10 of the FSR, and may not be recoverable. These are only
|
|
* supported if the CPU abort handler supports bit 10.
|
|
*/
|
|
{ do_bad, SIGBUS, 0, "unknown 16" },
|
|
{ do_bad, SIGBUS, 0, "unknown 17" },
|
|
{ do_bad, SIGBUS, 0, "unknown 18" },
|
|
{ do_bad, SIGBUS, 0, "unknown 19" },
|
|
{ do_bad, SIGBUS, 0, "lock abort" }, /* xscale */
|
|
{ do_bad, SIGBUS, 0, "unknown 21" },
|
|
{ do_imprecise_ext, SIGBUS, BUS_OBJERR, "imprecise external abort" }, /* xscale */
|
|
{ do_bad, SIGBUS, 0, "unknown 23" },
|
|
{ do_bad, SIGBUS, 0, "dcache parity error" }, /* xscale */
|
|
{ do_bad, SIGBUS, 0, "unknown 25" },
|
|
{ do_bad, SIGBUS, 0, "unknown 26" },
|
|
{ do_bad, SIGBUS, 0, "unknown 27" },
|
|
{ do_bad, SIGBUS, 0, "unknown 28" },
|
|
{ do_bad, SIGBUS, 0, "unknown 29" },
|
|
{ do_bad, SIGBUS, 0, "unknown 30" },
|
|
{ do_bad, SIGBUS, 0, "unknown 31" }
|
|
};
|
|
|
|
void __init
|
|
hook_fault_code(int nr, int (*fn)(unsigned long, unsigned int, struct pt_regs *),
|
|
int sig, const char *name)
|
|
{
|
|
if (nr >= 0 && nr < ARRAY_SIZE(fsr_info)) {
|
|
fsr_info[nr].fn = fn;
|
|
fsr_info[nr].sig = sig;
|
|
fsr_info[nr].name = name;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Dispatch a data abort to the relevant handler.
|
|
*/
|
|
asmlinkage void __exception
|
|
do_DataAbort(unsigned long addr, unsigned int fsr, struct pt_regs *regs)
|
|
{
|
|
const struct fsr_info *inf = fsr_info + fsr_fs(fsr);
|
|
struct siginfo info;
|
|
|
|
if (!inf->fn(addr, fsr & ~FSR_LNX_PF, regs))
|
|
return;
|
|
|
|
printk(KERN_ALERT "Unhandled fault: %s (0x%03x) at 0x%08lx\n",
|
|
inf->name, fsr, addr);
|
|
|
|
info.si_signo = inf->sig;
|
|
info.si_errno = 0;
|
|
info.si_code = inf->code;
|
|
info.si_addr = (void __user *)addr;
|
|
arm_notify_die("", regs, &info, fsr, 0);
|
|
}
|
|
|
|
|
|
static struct fsr_info ifsr_info[] = {
|
|
{ do_bad, SIGBUS, 0, "unknown 0" },
|
|
{ do_bad, SIGBUS, 0, "unknown 1" },
|
|
{ do_bad, SIGBUS, 0, "debug event" },
|
|
{ do_bad, SIGSEGV, SEGV_ACCERR, "section access flag fault" },
|
|
{ do_bad, SIGBUS, 0, "unknown 4" },
|
|
{ do_translation_fault, SIGSEGV, SEGV_MAPERR, "section translation fault" },
|
|
{ do_bad, SIGSEGV, SEGV_ACCERR, "page access flag fault" },
|
|
{ do_page_fault, SIGSEGV, SEGV_MAPERR, "page translation fault" },
|
|
{ do_bad, SIGBUS, 0, "external abort on non-linefetch" },
|
|
{ do_bad, SIGSEGV, SEGV_ACCERR, "section domain fault" },
|
|
{ do_bad, SIGBUS, 0, "unknown 10" },
|
|
{ do_bad, SIGSEGV, SEGV_ACCERR, "page domain fault" },
|
|
{ do_bad, SIGBUS, 0, "external abort on translation" },
|
|
{ do_sect_fault, SIGSEGV, SEGV_ACCERR, "section permission fault" },
|
|
{ do_bad, SIGBUS, 0, "external abort on translation" },
|
|
{ do_page_fault, SIGSEGV, SEGV_ACCERR, "page permission fault" },
|
|
{ do_bad, SIGBUS, 0, "unknown 16" },
|
|
{ do_bad, SIGBUS, 0, "unknown 17" },
|
|
{ do_bad, SIGBUS, 0, "unknown 18" },
|
|
{ do_bad, SIGBUS, 0, "unknown 19" },
|
|
{ do_bad, SIGBUS, 0, "unknown 20" },
|
|
{ do_bad, SIGBUS, 0, "unknown 21" },
|
|
{ do_bad, SIGBUS, 0, "unknown 22" },
|
|
{ do_bad, SIGBUS, 0, "unknown 23" },
|
|
{ do_bad, SIGBUS, 0, "unknown 24" },
|
|
{ do_bad, SIGBUS, 0, "unknown 25" },
|
|
{ do_bad, SIGBUS, 0, "unknown 26" },
|
|
{ do_bad, SIGBUS, 0, "unknown 27" },
|
|
{ do_bad, SIGBUS, 0, "unknown 28" },
|
|
{ do_bad, SIGBUS, 0, "unknown 29" },
|
|
{ do_bad, SIGBUS, 0, "unknown 30" },
|
|
{ do_bad, SIGBUS, 0, "unknown 31" },
|
|
};
|
|
|
|
asmlinkage void __exception
|
|
do_PrefetchAbort(unsigned long addr, unsigned int ifsr, struct pt_regs *regs)
|
|
{
|
|
const struct fsr_info *inf = ifsr_info + fsr_fs(ifsr);
|
|
struct siginfo info;
|
|
|
|
if (!inf->fn(addr, ifsr | FSR_LNX_PF, regs))
|
|
return;
|
|
|
|
printk(KERN_ALERT "Unhandled prefetch abort: %s (0x%03x) at 0x%08lx\n",
|
|
inf->name, ifsr, addr);
|
|
|
|
info.si_signo = inf->sig;
|
|
info.si_errno = 0;
|
|
info.si_code = inf->code;
|
|
info.si_addr = (void __user *)addr;
|
|
arm_notify_die("", regs, &info, ifsr, 0);
|
|
}
|
|
|