NetBSD/sys/arch/arm/arm32/fault.c

682 lines
18 KiB
C

/* $NetBSD: fault.c,v 1.30 2003/05/21 18:04:43 thorpej Exp $ */
/*
* Copyright 2003 Wasabi Systems, Inc.
* All rights reserved.
*
* Written by Steve C. Woodford for Wasabi Systems, Inc.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. All advertising materials mentioning features or use of this software
* must display the following acknowledgement:
* This product includes software developed for the NetBSD Project by
* Wasabi Systems, Inc.
* 4. The name of Wasabi Systems, Inc. may not be used to endorse
* or promote products derived from this software without specific prior
* written permission.
*
* THIS SOFTWARE IS PROVIDED BY WASABI SYSTEMS, INC. ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL WASABI SYSTEMS, INC
* BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
/*
* Copyright (c) 1994-1997 Mark Brinicombe.
* Copyright (c) 1994 Brini.
* All rights reserved.
*
* This code is derived from software written for Brini by Mark Brinicombe
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. All advertising materials mentioning features or use of this software
* must display the following acknowledgement:
* This product includes software developed by Brini.
* 4. The name of the company nor the name of the author may be used to
* endorse or promote products derived from this software without specific
* prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY BRINI ``AS IS'' AND ANY EXPRESS OR IMPLIED
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
* IN NO EVENT SHALL BRINI OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
* INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*
* RiscBSD kernel project
*
* fault.c
*
* Fault handlers
*
* Created : 28/11/94
*/
#include "opt_ddb.h"
#include "opt_kgdb.h"
#include "opt_pmap_debug.h"
#include <sys/types.h>
__KERNEL_RCSID(0, "$NetBSD: fault.c,v 1.30 2003/05/21 18:04:43 thorpej Exp $");
#include <sys/param.h>
#include <sys/systm.h>
#include <sys/proc.h>
#include <sys/user.h>
#include <sys/kernel.h>
#include <uvm/uvm_extern.h>
#include <arm/cpuconf.h>
#include <machine/frame.h>
#include <arm/arm32/katelib.h>
#include <machine/cpu.h>
#include <machine/intr.h>
#if defined(DDB) || defined(KGDB)
#include <machine/db_machdep.h>
#ifdef KGDB
#include <sys/kgdb.h>
#endif
#if !defined(DDB)
#define kdb_trap kgdb_trap
#endif
#endif
#include <arch/arm/arm/disassem.h>
#include <arm/arm32/machdep.h>
extern char fusubailout[];
#ifdef DEBUG
int last_fault_code; /* For the benefit of pmap_fault_fixup() */
#endif
static void report_abort __P((const char *, u_int, u_int, u_int));
/* Abort code */
/* Define text descriptions of the different aborts */
static const char *aborts[16] = {
"Write buffer fault",
"Alignment fault",
"Write buffer fault",
"Alignment fault",
"Bus error (LF section)",
"Translation fault (section)",
"Bus error (page)",
"Translation fault (page)",
"Bus error (section)",
"Domain error (section)",
"Bus error (page)",
"Domain error (page)",
"Bus error trans (L1)",
"Permission error (section)",
"Bus error trans (L2)",
"Permission error (page)"
};
static void
report_abort(prefix, fault_status, fault_address, fault_pc)
const char *prefix;
u_int fault_status;
u_int fault_address;
u_int fault_pc;
{
#ifndef DEBUG
if (prefix == NULL) {
#endif
if (prefix)
printf("%s ", prefix);
printf("Data abort: '%s' status=%03x address=%08x PC=%08x\n",
aborts[fault_status & FAULT_TYPE_MASK],
fault_status & 0xfff, fault_address, fault_pc);
#ifndef DEBUG
}
#endif
}
static __volatile int data_abort_expected;
static __volatile int data_abort_received;
int
badaddr_read(void *addr, size_t size, void *rptr)
{
u_long rcpt;
int rv;
/* Tell the Data Abort handler that we're expecting one. */
data_abort_received = 0;
data_abort_expected = 1;
cpu_drain_writebuf();
/* Read from the test address. */
switch (size) {
case sizeof(uint8_t):
__asm __volatile("ldrb %0, [%1]"
: "=r" (rcpt)
: "r" (addr));
break;
case sizeof(uint16_t):
__asm __volatile("ldrh %0, [%1]"
: "=r" (rcpt)
: "r" (addr));
break;
case sizeof(uint32_t):
__asm __volatile("ldr %0, [%1]"
: "=r" (rcpt)
: "r" (addr));
break;
default:
data_abort_expected = 0;
panic("badaddr: invalid size (%lu)", (u_long) size);
}
/* Disallow further Data Aborts. */
data_abort_expected = 0;
rv = data_abort_received;
data_abort_received = 0;
/* Copy the data back if no fault occurred. */
if (rptr != NULL && rv == 0) {
switch (size) {
case sizeof(uint8_t):
*(uint8_t *) rptr = rcpt;
break;
case sizeof(uint16_t):
*(uint16_t *) rptr = rcpt;
break;
case sizeof(uint32_t):
*(uint32_t *) rptr = rcpt;
break;
}
}
/* Return true if the address was invalid. */
return (rv);
}
/*
* void data_abort_handler(trapframe_t *frame)
*
* Abort handler called when read/write occurs at an address of
* a non existent or restricted (access permissions) memory page.
* We first need to identify the type of page fault.
*/
#define TRAP_CODE ((fault_status & 0x0f) | (fault_address & 0xfffffff0))
/* Determine if we can recover from a fault */
#define IS_FATAL_FAULT(x) \
(((1 << (x)) & \
((1 << FAULT_WRTBUF_0) | (1 << FAULT_WRTBUF_1) | \
(1 << FAULT_BUSERR_0) | (1 << FAULT_BUSERR_1) | \
(1 << FAULT_BUSERR_2) | (1 << FAULT_BUSERR_3) | \
(1 << FAULT_BUSTRNL1) | (1 << FAULT_BUSTRNL2) | \
(1 << FAULT_ALIGN_0) | (1 << FAULT_ALIGN_1))) != 0)
void
data_abort_handler(frame)
trapframe_t *frame;
{
struct lwp *l;
struct proc *p;
struct pcb *pcb;
u_int fault_address;
u_int fault_status;
u_int fault_pc;
u_int fault_instruction;
int fault_code, fatal_fault;
int user;
int error;
int rv;
void *onfault;
vaddr_t va;
struct vmspace *vm;
struct vm_map *map;
vm_prot_t ftype;
extern struct vm_map *kernel_map;
/*
* If we were expecting a Data Abort, signal that we got
* one, adjust the PC to skip the faulting insn, and
* return.
*/
if (data_abort_expected) {
data_abort_received = 1;
frame->tf_pc += INSN_SIZE;
return;
}
/*
* Must get fault address and status from the CPU before
* re-enabling interrupts. (Interrupt handlers may take
* R/M emulation faults.)
*/
fault_address = cpu_faultaddress();
fault_status = cpu_faultstatus();
fault_pc = frame->tf_pc;
/*
* Enable IRQ's (disabled by CPU on abort) if trapframe
* shows they were enabled.
*/
if (!(frame->tf_spsr & I32_bit))
enable_interrupts(I32_bit);
#ifdef DEBUG
if ((GetCPSR() & PSR_MODE) != PSR_SVC32_MODE)
panic("data_abort_handler: not in SVC32 mode");
#endif
/* Update vmmeter statistics */
uvmexp.traps++;
/* Extract the fault code from the fault status */
fault_code = fault_status & FAULT_TYPE_MASK;
fatal_fault = IS_FATAL_FAULT(fault_code);
/* Get the current lwp structure or lwp0 if there is none */
l = curlwp == NULL ? &lwp0 : curlwp;
p = l->l_proc;
/*
* can't use curpcb, as it might be NULL; and we have p in
* a register anyway
*/
pcb = &l->l_addr->u_pcb;
/* fusubailout is used by [fs]uswintr to avoid page faulting */
if (pcb->pcb_onfault &&
(fatal_fault || pcb->pcb_onfault == fusubailout)) {
frame->tf_r0 = EFAULT;
copyfault:
#ifdef DEBUG
printf("Using pcb_onfault=%p addr=%08x st=%08x l=%p\n",
pcb->pcb_onfault, fault_address, fault_status, l);
#endif
frame->tf_pc = (u_int)pcb->pcb_onfault;
if ((frame->tf_spsr & PSR_MODE) == PSR_USR32_MODE)
panic("Yikes pcb_onfault=%p during USR mode fault",
pcb->pcb_onfault);
return;
}
/* More debug stuff */
fault_instruction = ReadWord(fault_pc);
#ifdef PMAP_DEBUG
if (pmap_debug_level >= 0) {
report_abort(NULL, fault_status, fault_address, fault_pc);
printf("Instruction @V%08x = %08x\n",
fault_pc, fault_instruction);
}
#endif
/* Call the cpu specific abort fixup routine */
error = cpu_dataabt_fixup(frame);
if (error == ABORT_FIXUP_RETURN)
return;
if (error == ABORT_FIXUP_FAILED) {
printf("pc = 0x%08x, opcode 0x%08x, insn = ", fault_pc, *((u_int *)fault_pc));
disassemble(fault_pc);
printf("data abort handler: fixup failed for this instruction\n");
}
#ifdef PMAP_DEBUG
if (pmap_debug_level >= 0)
printf("fault in process %p\n", p);
#endif
#ifdef DEBUG
/* Is this needed ? (XXXSCW: yes. can happen during boot ...) */
if (!cold && pcb != curpcb) {
printf("data_abort: Alert ! pcb(%p) != curpcb(%p)\n",
pcb, curpcb);
printf("data_abort: Alert ! proc(%p), curlwp(%p)\n",
p, curlwp);
}
#endif /* DEBUG */
/* Were we in user mode when the abort occurred ? */
if ((frame->tf_spsr & PSR_MODE) == PSR_USR32_MODE) {
/*
* Note that the fault was from USR mode.
*/
user = 1;
l->l_addr->u_pcb.pcb_tf = frame;
} else
user = 0;
/* check if this was a failed fixup */
if (error == ABORT_FIXUP_FAILED) {
if (user) {
trapsignal(l, SIGSEGV, TRAP_CODE);
userret(l);
return;
};
panic("Data abort fixup failed in kernel - we're dead");
};
/* Now act on the fault type */
if (fatal_fault) {
/*
* None of these faults should happen on a perfectly
* functioning system. They indicate either some gross
* problem with the kernel, or a hardware problem.
* In either case, stop.
*/
report_abort(NULL, fault_status, fault_address, fault_pc);
we_re_toast:
/*
* Were are dead, try and provide some debug
* information before dying.
*/
#if defined(DDB) || defined(KGDB)
printf("Unhandled trap (frame = %p)\n", frame);
report_abort(NULL, fault_status, fault_address, fault_pc);
kdb_trap(-1, frame);
return;
#else
panic("Unhandled trap (frame = %p)", frame);
#endif /* DDB || KGDB */
}
/*
* At this point, we're dealing with one of the following faults:
*
* FAULT_TRANS_P Page Translation Fault
* FAULT_PERM_P Page Permission Fault
* FAULT_TRANS_S Section Translation Fault
* FAULT_PERM_S Section Permission Fault
* FAULT_DOMAIN_P Page Domain Error Fault
* FAULT_DOMAIN_S Section Domain Error Fault
*
* Page/section translation/permission fault -- need to fault in
* the page.
*
* Page/section domain fault -- need to see if the L1 entry can
* be fixed up.
*/
vm = p->p_vmspace;
va = trunc_page((vaddr_t)fault_address);
#ifdef PMAP_DEBUG
if (pmap_debug_level >= 0)
printf("page fault: addr=V%08lx ", va);
#endif
/*
* It is only a kernel address space fault iff:
* 1. user == 0 and
* 2. pcb_onfault not set or
* 3. pcb_onfault set but supervisor space fault
* The last can occur during an exec() copyin where the
* argument space is lazy-allocated.
*/
if (!user &&
(va >= VM_MIN_KERNEL_ADDRESS || va < VM_MIN_ADDRESS)) {
/* Was the fault due to the FPE/IPKDB ? */
if ((frame->tf_spsr & PSR_MODE) == PSR_UND32_MODE) {
report_abort("UND32", fault_status,
fault_address, fault_pc);
trapsignal(l, SIGSEGV, TRAP_CODE);
/*
* Force exit via userret()
* This is necessary as the FPE is an extension
* to userland that actually runs in a
* priveledged mode but uses USR mode
* permissions for its accesses.
*/
userret(l);
return;
}
map = kernel_map;
} else
map = &vm->vm_map;
#ifdef PMAP_DEBUG
if (pmap_debug_level >= 0)
printf("vmmap=%p ", map);
#endif
if (map == NULL)
printf("No map for fault address va = 0x%08lx", va);
/*
* We need to know whether the page should be mapped
* as R or R/W. The MMU does not give us the info as
* to whether the fault was caused by a read or a write.
* This means we need to disassemble the instruction
* responsible and determine if it was a read or write
* instruction.
*/
/* STR instruction ? */
if ((fault_instruction & 0x0c100000) == 0x04000000)
ftype = VM_PROT_WRITE;
/* STM or CDT instruction ? */
else if ((fault_instruction & 0x0a100000) == 0x08000000)
ftype = VM_PROT_WRITE;
/* STRH, STRSH or STRSB instruction ? */
else if ((fault_instruction & 0x0e100090) == 0x00000090)
ftype = VM_PROT_WRITE;
/* SWP instruction ? */
else if ((fault_instruction & 0x0fb00ff0) == 0x01000090)
ftype = VM_PROT_READ | VM_PROT_WRITE;
else
ftype = VM_PROT_READ;
#ifdef PMAP_DEBUG
if (pmap_debug_level >= 0)
printf("fault protection = %d\n", ftype);
#endif
if (pmap_fault_fixup(map->pmap, va, ftype, user))
goto out;
if (current_intr_depth > 0) {
#if defined(DDB) || defined(KGDB)
printf("Non-emulated page fault with intr_depth > 0\n");
report_abort(NULL, fault_status, fault_address, fault_pc);
kdb_trap(-1, frame);
return;
#else
panic("Fault with intr_depth > 0");
#endif /* DDB */
}
onfault = pcb->pcb_onfault;
pcb->pcb_onfault = NULL;
rv = uvm_fault(map, va, 0, ftype);
pcb->pcb_onfault = onfault;
if (rv == 0) {
if (user != 0) /* Record any stack growth... */
uvm_grow(p, trunc_page(va));
goto out;
}
if (user == 0) {
if (pcb->pcb_onfault) {
frame->tf_r0 = rv;
goto copyfault;
}
printf("[u]vm_fault(%p, %lx, %x, 0) -> %x\n", map, va, ftype,
rv);
goto we_re_toast;
}
report_abort("", fault_status, fault_address, fault_pc);
if (rv == ENOMEM) {
printf("UVM: pid %d (%s), uid %d killed: "
"out of swap\n", p->p_pid, p->p_comm,
(p->p_cred && p->p_ucred) ? p->p_ucred->cr_uid : -1);
trapsignal(l, SIGKILL, TRAP_CODE);
} else
trapsignal(l, SIGSEGV, TRAP_CODE);
out:
/* Call userret() if it was a USR mode fault */
if (user)
userret(l);
}
/*
* void prefetch_abort_handler(trapframe_t *frame)
*
* Abort handler called when instruction execution occurs at
* a non existent or restricted (access permissions) memory page.
* If the address is invalid and we were in SVC mode then panic as
* the kernel should never prefetch abort.
* If the address is invalid and the page is mapped then the user process
* does no have read permission so send it a signal.
* Otherwise fault the page in and try again.
*/
void
prefetch_abort_handler(frame)
trapframe_t *frame;
{
struct lwp *l;
struct proc *p;
struct vm_map *map;
vaddr_t fault_pc, va;
int error;
/*
* Enable IRQ's (disabled by the abort) This always comes
* from user mode so we know interrupts were not disabled.
* But we check anyway.
*/
if (!(frame->tf_spsr & I32_bit))
enable_interrupts(I32_bit);
#ifdef DEBUG
if ((GetCPSR() & PSR_MODE) != PSR_SVC32_MODE)
panic("prefetch_abort_handler: not in SVC32 mode");
#endif
/* Update vmmeter statistics */
uvmexp.traps++;
/* Call the cpu specific abort fixup routine */
error = cpu_prefetchabt_fixup(frame);
if (error == ABORT_FIXUP_RETURN)
return;
if (error == ABORT_FIXUP_FAILED)
panic("prefetch abort fixup failed");
/* Get the current proc structure or proc0 if there is none */
if ((l = curlwp) == NULL) {
l = &lwp0;
#ifdef DEBUG
printf("Prefetch abort with curlwp == 0\n");
#endif
}
p = l->l_proc;
#ifdef PMAP_DEBUG
if (pmap_debug_level >= 0)
printf("prefetch fault in process %p %s\n", p, p->p_comm);
#endif
/* Get fault address */
fault_pc = frame->tf_pc;
va = trunc_page(fault_pc);
/* Was the prefectch abort from USR32 mode ? */
if ((frame->tf_spsr & PSR_MODE) == PSR_USR32_MODE) {
l->l_addr->u_pcb.pcb_tf = frame;
} else {
/*
* All the kernel code pages are loaded at boot time
* and do not get paged
*/
panic("Prefetch abort in non-USR mode (frame=%p PC=0x%08lx)",
frame, fault_pc);
}
map = &p->p_vmspace->vm_map;
#ifdef PMAP_DEBUG
if (pmap_debug_level >= 0)
printf("prefetch_abort: PC = %08lx\n", fault_pc);
#endif
/* Ok validate the address, can only execute in USER space */
if (fault_pc < VM_MIN_ADDRESS || fault_pc >= VM_MAXUSER_ADDRESS) {
#ifdef DEBUG
printf("prefetch: pc (%08lx) not in user process space\n",
fault_pc);
#endif
trapsignal(l, SIGSEGV, fault_pc);
userret(l);
return;
}
/*
* See if the pmap can handle this fault on its own...
*/
if (pmap_fault_fixup(map->pmap, va, VM_PROT_READ, 1))
goto out;
if (current_intr_depth > 0) {
#ifdef DDB
printf("Non-emulated prefetch abort with intr_depth > 0\n");
kdb_trap(-1, frame);
return;
#else
panic("Prefetch Abort with intr_depth > 0");
#endif
}
error = uvm_fault(map, va, 0, VM_PROT_READ);
if (error == 0)
goto out;
if (error == ENOMEM) {
printf("UVM: pid %d (%s), uid %d killed: "
"out of swap\n", p->p_pid, p->p_comm,
(p->p_cred && p->p_ucred) ? p->p_ucred->cr_uid : -1);
trapsignal(l, SIGKILL, fault_pc);
} else
trapsignal(l, SIGSEGV, fault_pc);
out:
userret(l);
}