summaryrefslogtreecommitdiffstats
path: root/arch/arm/kernel/traps.c
blob: 222fd6b17d6fa0a39f9df7b3d12fe0bebf3e50e1 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
/*
 *  linux/arch/arm/kernel/traps.c
 *
 *  Copyright (C) 1995, 1996 Russell King
 *  Fragments that appear the same as linux/arch/i386/kernel/traps.c (C) Linus Torvalds
 */

/*
 * 'traps.c' handles hardware exceptions after we have saved some state in
 * 'linux/arch/arm/lib/traps.S'.  Mostly a debugging aid, but will probably
 * kill the offending process.
 */
#include <linux/config.h>
#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/signal.h>
#include <linux/sched.h>
#include <linux/mm.h>

#include <asm/system.h>
#include <asm/uaccess.h>
#include <asm/io.h>
#include <asm/spinlock.h>
#include <asm/atomic.h>
#include <asm/pgtable.h>

extern void fpe_save(struct fp_soft_struct *);
extern void fpe_restore(struct fp_soft_struct *);
extern void die_if_kernel(char *str, struct pt_regs *regs, int err, int ret);
extern void c_backtrace (unsigned long fp, int pmode);
extern int ptrace_cancel_bpt (struct task_struct *);

char *processor_modes[]=
{ "USER_26", "FIQ_26" , "IRQ_26" , "SVC_26" , "UK4_26" , "UK5_26" , "UK6_26" , "UK7_26" ,
  "UK8_26" , "UK9_26" , "UK10_26", "UK11_26", "UK12_26", "UK13_26", "UK14_26", "UK15_26",
  "USER_32", "FIQ_32" , "IRQ_32" , "SVC_32" , "UK4_32" , "UK5_32" , "UK6_32" , "ABT_32" ,
  "UK8_32" , "UK9_32" , "UK10_32", "UND_32" , "UK12_32", "UK13_32", "UK14_32", "SYS_32"
};

static char *handler[]= { "prefetch abort", "data abort", "address exception", "interrupt" };

static inline void console_verbose(void)
{
	extern int console_loglevel;
	console_loglevel = 15;
}

int kstack_depth_to_print = 200;

static int verify_stack_pointer (unsigned long stackptr, int size)
{
#if defined(CONFIG_CPU_ARM2) || defined(CONFIG_CPU_ARM3)
	if (stackptr < 0x02048000 || stackptr + size > 0x03000000)
        	return -EFAULT;
#else
	if (stackptr < 0xc0000000 || stackptr + size > (unsigned long)high_memory)
		return -EFAULT;
#endif
		return 0;
}

/*
 * Dump out the contents of some memory nicely...
 */
void dump_mem(unsigned long bottom, unsigned long top)
{
	unsigned long p = bottom & ~31;
	int i;

	for (p = bottom & ~31; p < top;) {
		printk("%08lx: ", p);

		for (i = 0; i < 8; i++, p += 4) {
			if (p < bottom || p >= top)
				printk("         ");
			else
				printk("%08lx ", *(unsigned long *)p);
			if (i == 3)
				printk(" ");
		}
		printk ("\n");
	}
}

/*
 * These constants are for searching for possible module text
 * segments.  VMALLOC_OFFSET comes from mm/vmalloc.c; MODULE_RANGE is
 * a guess of how much space is likely to be vmalloced.
 */
#define VMALLOC_OFFSET (8*1024*1024)
#define MODULE_RANGE (8*1024*1024)

static void dump_instr (unsigned long pc)
{
	unsigned long module_start, module_end;
	int pmin = -2, pmax = 3, ok = 0;
	extern char start_kernel, _etext;

	module_start = VMALLOC_START;
	module_end   = module_start + MODULE_RANGE;

	if ((pc >= (unsigned long) &start_kernel) &&
	    (pc <= (unsigned long) &_etext)) {
		if (pc + pmin < (unsigned long) &start_kernel)
			pmin = ((unsigned long) &start_kernel) - pc;
		if (pc + pmax > (unsigned long) &_etext)
			pmax = ((unsigned long) &_etext) - pc;
		ok = 1;
	} else if (pc >= module_start && pc <= module_end) {
		if (pc + pmin < module_start)
			pmin = module_start - pc;
		if (pc + pmax > module_end)
			pmax = module_end - pc;
		ok = 1;
	}
	printk ("Code: ");
	if (ok) {
		int i;
		for (i = pmin; i < pmax; i++)
			printk("%08lx ", ((unsigned long *)pc)[i]);
		printk ("\n");
	} else
		printk ("pc not in code space\n");
}	

/*
 * This function is protected against kernel-mode re-entrancy.  If it
 * is re-entered it will hang the system since we can't guarantee in
 * this case that any of the functions that it calls are safe any more.
 * Even the panic function could be a problem, but we'll give it a go.
 */
void die_if_kernel(char *str, struct pt_regs *regs, int err, int ret)
{
	static int died = 0;
	unsigned long cstack, sstack, frameptr;
	
	if (user_mode(regs))
    		return;

	switch (died) {
	case 2:
		while (1);
	case 1:
		died ++;
		panic ("die_if_kernel re-entered.  Major kernel corruption.  Please reboot me!");
		break;
	case 0:
		died ++;
		break;
	}

	console_verbose();
	printk("Internal error: %s: %x\n", str, err);
	printk("CPU: %d", smp_processor_id());
	show_regs(regs);
	printk("Process %s (pid: %d, stackpage=%08lx)\n",
		current->comm, current->pid, 4096+(unsigned long)current);

	cstack = (unsigned long)(regs + 1);
	sstack = 4096+(unsigned long)current;

	printk("Stack: ");
	if (verify_stack_pointer(cstack, 4))
		printk("invalid kernel stack pointer %08lx", cstack);
	else if(cstack > sstack + 4096)
		printk("(sp overflow)");
	else if(cstack < sstack)
		printk("(sp underflow)");
	printk("\n");

	dump_mem(cstack, sstack + 4096);

	frameptr = regs->ARM_fp;
	if (frameptr) {
		if (verify_stack_pointer (frameptr, 4))
			printk ("Backtrace: invalid frame pointer\n");
		else {
			printk("Backtrace: \n");
			c_backtrace (frameptr, processor_mode(regs));
		}
	}

	dump_instr(instruction_pointer(regs));
	died = 0;
	if (ret != -1)
		do_exit (ret);
	else {
		cli ();
		while (1);
	}
}

void bad_user_access_alignment (const void *ptr)
{
	void *pc;
	__asm__("mov %0, lr\n": "=r" (pc));
	printk (KERN_ERR "bad_user_access_alignment called: ptr = %p, pc = %p\n", ptr, pc);
	current->tss.error_code = 0;
	current->tss.trap_no = 11;
	force_sig (SIGBUS, current);
/*	die_if_kernel("Oops - bad user access alignment", regs, mode, SIGBUS);*/
}

asmlinkage void do_undefinstr (int address, struct pt_regs *regs, int mode)
{
	current->tss.error_code = 0;
	current->tss.trap_no = 6;
	force_sig (SIGILL, current);
	die_if_kernel("Oops - undefined instruction", regs, mode, SIGILL);
}

asmlinkage void do_excpt (int address, struct pt_regs *regs, int mode)
{
	current->tss.error_code = 0;
	current->tss.trap_no = 11;
	force_sig (SIGBUS, current);
	die_if_kernel("Oops - address exception", regs, mode, SIGBUS);
}

asmlinkage void do_unexp_fiq (struct pt_regs *regs)
{
#ifndef CONFIG_IGNORE_FIQ
	printk ("Hmm.  Unexpected FIQ received, but trying to continue\n");
	printk ("You may have a hardware problem...\n");
#endif
}

asmlinkage void bad_mode(struct pt_regs *regs, int reason, int proc_mode)
{
	printk (KERN_CRIT "Bad mode in %s handler detected: mode %s\n",
		handler[reason],
		processor_modes[proc_mode]);
	die_if_kernel ("Oops", regs, 0, -1);
}

/*
 * 'math_state_restore()' saves the current math information in the
 * old math state array, and gets the new ones from the current task.
 *
 * We no longer save/restore the math state on every context switch
 * any more.  We only do this now if it actually gets used.
 */
asmlinkage void math_state_restore (void)
{
	if (last_task_used_math == current)
		return;
	if (last_task_used_math)
		/*
		 * Save current fp state into last_task_used_math->tss.fpe_save
		 */
		fpe_save (&last_task_used_math->tss.fpstate.soft);
	last_task_used_math = current;
	if (current->used_math) {
	    	/*
    		 * Restore current fp state from current->tss.fpe_save
	    	 */
    		fpe_restore (&current->tss.fpstate.soft);
	} else {
    		/*
	    	 * initialise fp state
    		 */
	    	fpe_restore (&init_task.tss.fpstate.soft);
	    	current->used_math = 1;
	}
}

asmlinkage void arm_syscall (int no, struct pt_regs *regs)
{
	switch (no) {
	case 0: /* branch through 0 */
		printk ("[%d] %s: branch through zero\n", current->pid, current->comm);
		force_sig (SIGILL, current);
		if (user_mode(regs)) {
			show_regs (regs);
			c_backtrace (regs->ARM_fp, processor_mode(regs));
		}
		die_if_kernel ("Oops", regs, 0, SIGILL);
		break;

	case 1: /* SWI_BREAK_POINT */
		regs->ARM_pc -= 4; /* Decrement PC by one instruction */
		ptrace_cancel_bpt (current);
		force_sig (SIGTRAP, current);
		break;

	default:
		printk ("[%d] %s: arm syscall %d\n", current->pid, current->comm, no);
		force_sig (SIGILL, current);
		if (user_mode(regs)) {
			show_regs (regs);
			c_backtrace (regs->ARM_fp, processor_mode(regs));
		}
		die_if_kernel ("Oops", regs, no, SIGILL);
		break;
	}
}

asmlinkage void deferred(int n, struct pt_regs *regs)
{
	printk ("[%d] %s: old system call %X\n", current->pid, current->comm, n);
	show_regs (regs);
	force_sig (SIGILL, current);
}

asmlinkage void arm_malalignedptr(const char *str, void *pc, volatile void *ptr)
{
	printk ("Mal-aligned pointer in %s: %p (PC=%p)\n", str, ptr, pc);
}

asmlinkage void arm_invalidptr (const char *function, int size)
{
	printk ("Invalid pointer size in %s (PC=%p) size %d\n",
		function, __builtin_return_address(0), size);
}