/*
* arch/mips/kernel/traps.c
*
* This file is subject to the terms and conditions of the GNU General Public
* License. See the file "COPYING" in the main directory of this archive
* for more details.
*/
/*
* 'traps.c' handles hardware traps and faults after we have saved some
* state in 'asm.s'. Currently mostly a debugging-aid, will be extended
* to mainly kill the offending process (probably by giving it a signal,
* but possibly by killing it outright if necessary).
*
* FIXME: This is the place for a fpu emulator.
*/
#include <linux/head.h>
#include <linux/sched.h>
#include <linux/kernel.h>
#include <linux/signal.h>
#include <linux/string.h>
#include <linux/types.h>
#include <linux/errno.h>
#include <linux/ptrace.h>
#include <linux/timer.h>
#include <linux/mm.h>
#include <asm/vector.h>
#include <asm/page.h>
#include <asm/pgtable.h>
#include <asm/system.h>
#include <asm/segment.h>
#include <asm/io.h>
#include <asm/mipsregs.h>
#include <asm/bootinfo.h>
static inline void console_verbose(void)
{
extern int console_loglevel;
console_loglevel = 15;
}
/*
* Machine specific interrupt handlers
*/
extern asmlinkage void acer_pica_61_handle_int(void);
extern asmlinkage void decstation_handle_int(void);
extern asmlinkage void deskstation_rpc44_handle_int(void);
extern asmlinkage void deskstation_tyne_handle_int(void);
extern asmlinkage void mips_magnum_4000_handle_int(void);
extern asmlinkage void handle_mod(void);
extern asmlinkage void handle_tlbl(void);
extern asmlinkage void handle_tlbs(void);
extern asmlinkage void handle_adel(void);
extern asmlinkage void handle_ades(void);
extern asmlinkage void handle_ibe(void);
extern asmlinkage void handle_dbe(void);
extern asmlinkage void handle_sys(void);
extern asmlinkage void handle_bp(void);
extern asmlinkage void handle_ri(void);
extern asmlinkage void handle_cpu(void);
extern asmlinkage void handle_ov(void);
extern asmlinkage void handle_tr(void);
extern asmlinkage void handle_vcei(void);
extern asmlinkage void handle_fpe(void);
extern asmlinkage void handle_vced(void);
extern asmlinkage void handle_watch(void);
extern asmlinkage void handle_reserved(void);
static char *cpu_names[] = CPU_NAMES;
unsigned long page_colour_mask;
int kstack_depth_to_print = 24;
/*
* These constant is for searching for possible module text segments.
* MODULE_RANGE is a guess of how much space is likely to be vmalloced.
*/
#define MODULE_RANGE (8*1024*1024)
void die_if_kernel(char * str, struct pt_regs * regs, long err)
{
int i;
int *stack;
u32 *sp, *pc, addr, module_start, module_end;
extern char start_kernel, _etext;
if ((regs->cp0_status & (ST0_ERL|ST0_EXL)) == 0)
return;
sp = (u32 *)regs->reg29;
pc = (u32 *)regs->cp0_epc;
console_verbose();
printk("%s: %08lx\n", str, err );
show_regs(regs);
/*
* Dump the stack
*/
if (STACK_MAGIC != *(u32 *)current->kernel_stack_page)
printk("Corrupted stack page\n");
printk("Process %s (pid: %d, stackpage=%08lx)\nStack: ",
current->comm, current->pid, current->kernel_stack_page);
for(i=0;i<5;i++)
printk("%08x ", *sp++);
stack = (int *) sp;
for(i=0; i < kstack_depth_to_print; i++) {
if (((u32) stack & (PAGE_SIZE -1)) == 0)
break;
if (i && ((i % 8) == 0))
printk("\n ");
printk("%08lx ", get_user(stack++));
}
printk("\nCall Trace: ");
stack = (int *)sp;
i = 1;
module_start = VMALLOC_START;
module_end = module_start + MODULE_RANGE;
while (((u32)stack & (PAGE_SIZE -1)) != 0) {
addr = get_user(stack++);
/*
* If the address is either in the text segment of the
* kernel, or in the region which contains vmalloc'ed
* memory, it *may* be the address of a calling
* routine; if so, print it so that someone tracing
* down the cause of the crash will be able to figure
* out the call path that was taken.
*/
if (((addr >= (u32) &start_kernel) &&
(addr <= (u32) &_etext)) ||
((addr >= module_start) && (addr <= module_end))) {
if (i && ((i % 8) == 0))
printk("\n ");
printk("%08x ", addr);
i++;
}
}
printk("\nCode : ");
if ((!verify_area(VERIFY_READ, pc, 5 * sizeof(*pc)) ||
KSEGX(pc) == KSEG0 ||
KSEGX(pc) == KSEG1) &&
(((unsigned long) pc & 3) == 0))
{
for(i=0;i<5;i++)
printk("%08x ", *pc++);
printk("\n");
}
else
printk("(Bad address in epc)\n");
while(1);
do_exit(SIGSEGV);
}
static void
fix_ade(struct pt_regs *regs, int write)
{
printk("Received address error (ade%c)\n", write ? 's' : 'l');
panic("Fixing address errors not implemented yet");
}
void do_adel(struct pt_regs *regs)
{
if(current->tss.mflags & MF_FIXADE)
{
fix_ade(regs, 0);
return;
}
show_regs(regs);
while(1);
dump_tlb_nonwired();
send_sig(SIGSEGV, current, 1);
}
void do_ades(struct pt_regs *regs)
{
unsigned long pc = regs->cp0_epc;
int i;
if(current->tss.mflags & MF_FIXADE)
{
fix_ade(regs, 1);
return;
}
while(1);
for(i=0; i<NR_TASKS;i++)
if(task[i] && task[i]->pid >= 2)
{
printk("Process %d\n", task[i]->pid);
dump_list_process(task[i], pc);
}
show_regs(regs);
dump_tlb_nonwired();
send_sig(SIGSEGV, current, 1);
}
/*
* The ibe/dbe exceptions are signaled by onboard hardware and should get
* a board specific handlers to get maximum available information. Bus
* errors are always symptom of hardware malfunction or a kernel error.
*
* FIXME: Linux/68k sends a SIGSEGV for a buserror which seems to be wrong.
* This is certainly wrong. Actually, all hardware errors (ades,adel,ibe,dbe)
* are bus errors and therefor should send a SIGBUS! (Andy)
*/
void do_ibe(struct pt_regs *regs)
{
show_regs(regs);
while(1);
send_sig(SIGBUS, current, 1);
}
void do_dbe(struct pt_regs *regs)
{
show_regs(regs);
while(1);
send_sig(SIGBUS, current, 1);
}
void do_ov(struct pt_regs *regs)
{
show_regs(regs);
while(1);
send_sig(SIGFPE, current, 1);
}
void do_fpe(struct pt_regs *regs)
{
show_regs(regs);
while(1);
send_sig(SIGFPE, current, 1);
}
void do_bp(struct pt_regs *regs)
{
show_regs(regs);
while(1);
send_sig(SIGILL, current, 1);
}
void do_tr(struct pt_regs *regs)
{
show_regs(regs);
while(1);
send_sig(SIGILL, current, 1);
}
void do_ri(struct pt_regs *regs)
{
int i;
for(i=0; i<NR_TASKS;i++)
if(task[i] && task[i]->pid >= 2)
{
printk("Process %d\n", task[i]->pid);
dump_list_process(task[i], 0x7ffff000);
}
show_regs(regs);
while(1);
send_sig(SIGILL, current, 1);
}
void do_cpu(struct pt_regs *regs)
{
unsigned int cpid;
cpid = (regs->cp0_cause >> CAUSEB_CE) & 3;
switch(cpid)
{
case 1:
regs->cp0_status |= ST0_CU1;
break;
case 0:
/*
* CPU for cp0 can only happen in user mode
*/
case 2:
case 3:
send_sig(SIGILL, current, 1);
break;
}
}
void do_vcei(struct pt_regs *regs)
{
/*
* Only possible on R4[04]00[SM]C. No handler because
* I don't have such a cpu.
*/
panic("Caught VCEI exception - can't handle yet\n");
}
void do_vced(struct pt_regs *regs)
{
/*
* Only possible on R4[04]00[SM]C. No handler because
* I don't have such a cpu.
*/
panic("Caught VCED exception - can't handle yet\n");
}
void do_watch(struct pt_regs *regs)
{
panic("Caught WATCH exception - can't handle yet\n");
}
void do_reserved(struct pt_regs *regs)
{
/*
* Game over - no way to handle this if it ever occurs.
* Most probably caused by a new unknown cpu type or
* after another deadly hard/software error.
*/
panic("Caught reserved exception - can't handle.\n");
}
void trap_init(void)
{
unsigned long i;
void watch_set(unsigned long, unsigned long);
if(boot_info.machtype == MACH_MIPS_MAGNUM_4000)
EISA_bus = 1;
/*
* Setup default vectors
*/
for (i=0;i<=31;i++)
set_except_vector(i, handle_reserved);
/*
* Handling the following exceptions depends mostly of the cpu type
*/
switch(boot_info.cputype) {
/*
* The R10000 is in most aspects similar to the R4400. It however
* should get some special optimizations.
*/
case CPU_R10000:
write_32bit_cp0_register(CP0_FRAMEMASK, 0);
set_cp0_status(ST0_XX, ST0_XX);
page_colour_mask = 0x3000;
panic("CPU too expensive - making holiday in the ANDES!");
break;
case CPU_R4000MC:
case CPU_R4400MC:
case CPU_R4000SC:
case CPU_R4400SC:
/*
* Handlers not implemented yet. If should ever be used -
* otherwise it's a bug in the Linux/MIPS kernel, anyway.
*/
set_except_vector(14, handle_vcei);
set_except_vector(31, handle_vced);
case CPU_R4000PC:
case CPU_R4400PC:
case CPU_R4200:
/* case CPU_R4300: */
/*
* Use watch exception to trap on access to address zero
*/
set_except_vector(23, handle_watch);
watch_set(KSEG0, 3);
case CPU_R4600:
set_except_vector(1, handle_mod);
set_except_vector(2, handle_tlbl);
set_except_vector(3, handle_tlbs);
set_except_vector(4, handle_adel);
set_except_vector(5, handle_ades);
/*
* The following two are signaled by onboard hardware and
* should get board specific handlers to get maximum
* available information.
*/
set_except_vector(6, handle_ibe);
set_except_vector(7, handle_dbe);
set_except_vector(8, handle_sys);
set_except_vector(9, handle_bp);
set_except_vector(10, handle_ri);
set_except_vector(11, handle_cpu);
set_except_vector(12, handle_ov);
set_except_vector(13, handle_tr);
set_except_vector(15, handle_fpe);
/*
* Compute mask for page_colour(). This is based on the
* size of the data cache. Does the size of the icache
* need to be accounted for?
*/
i = read_32bit_cp0_register(CP0_CONFIG);
i = (i >> 26) & 7;
page_colour_mask = 1 << (12 + i);
break;
case CPU_R2000:
case CPU_R3000:
case CPU_R3000A:
case CPU_R3041:
case CPU_R3051:
case CPU_R3052:
case CPU_R3081:
case CPU_R3081E:
case CPU_R6000:
case CPU_R6000A:
case CPU_R8000:
printk("Detected unsupported CPU type %s.\n",
cpu_names[boot_info.cputype]);
panic("Can't handle CPU\n");
break;
case CPU_UNKNOWN:
default:
panic("Unknown CPU type");
}
/*
* The interrupt handler mostly depends of the board type.
*/
set_except_vector(0, feature->handle_int);
}