Commit | Line | Data |
---|---|---|
5f97f7f9 HS |
1 | /* |
2 | * Copyright (C) 2004-2006 Atmel Corporation | |
3 | * | |
4 | * This program is free software; you can redistribute it and/or modify | |
5 | * it under the terms of the GNU General Public License version 2 as | |
6 | * published by the Free Software Foundation. | |
7 | */ | |
8 | #undef DEBUG | |
9 | #include <linux/sched.h> | |
10 | #include <linux/init.h> | |
11 | #include <linux/module.h> | |
12 | #include <linux/kallsyms.h> | |
13 | #include <linux/notifier.h> | |
14 | ||
15 | #include <asm/traps.h> | |
16 | #include <asm/sysreg.h> | |
17 | #include <asm/addrspace.h> | |
18 | #include <asm/ocd.h> | |
19 | #include <asm/mmu_context.h> | |
20 | #include <asm/uaccess.h> | |
21 | ||
22 | static void dump_mem(const char *str, unsigned long bottom, unsigned long top) | |
23 | { | |
24 | unsigned long p; | |
25 | int i; | |
26 | ||
27 | printk("%s(0x%08lx to 0x%08lx)\n", str, bottom, top); | |
28 | ||
29 | for (p = bottom & ~31; p < top; ) { | |
30 | printk("%04lx: ", p & 0xffff); | |
31 | ||
32 | for (i = 0; i < 8; i++, p += 4) { | |
33 | unsigned int val; | |
34 | ||
35 | if (p < bottom || p >= top) | |
36 | printk(" "); | |
37 | else { | |
38 | if (__get_user(val, (unsigned int __user *)p)) { | |
39 | printk("\n"); | |
40 | goto out; | |
41 | } | |
42 | printk("%08x ", val); | |
43 | } | |
44 | } | |
45 | printk("\n"); | |
46 | } | |
47 | ||
48 | out: | |
49 | return; | |
50 | } | |
51 | ||
52 | #ifdef CONFIG_FRAME_POINTER | |
53 | static inline void __show_trace(struct task_struct *tsk, unsigned long *sp, | |
54 | struct pt_regs *regs) | |
55 | { | |
56 | unsigned long __user *fp; | |
57 | unsigned long __user *last_fp = NULL; | |
58 | ||
59 | if (regs) { | |
60 | fp = (unsigned long __user *)regs->r7; | |
61 | } else if (tsk == current) { | |
62 | register unsigned long __user *real_fp __asm__("r7"); | |
63 | fp = real_fp; | |
64 | } else { | |
65 | fp = (unsigned long __user *)tsk->thread.cpu_context.r7; | |
66 | } | |
67 | ||
68 | /* | |
69 | * Walk the stack until (a) we get an exception, (b) the frame | |
70 | * pointer becomes zero, or (c) the frame pointer gets stuck | |
71 | * at the same value. | |
72 | */ | |
73 | while (fp && fp != last_fp) { | |
74 | unsigned long lr, new_fp = 0; | |
75 | ||
76 | last_fp = fp; | |
77 | if (__get_user(lr, fp)) | |
78 | break; | |
79 | if (fp && __get_user(new_fp, fp + 1)) | |
80 | break; | |
81 | fp = (unsigned long __user *)new_fp; | |
82 | ||
83 | printk(" [<%08lx>] ", lr); | |
84 | print_symbol("%s\n", lr); | |
85 | } | |
86 | printk("\n"); | |
87 | } | |
88 | #else | |
89 | static inline void __show_trace(struct task_struct *tsk, unsigned long *sp, | |
90 | struct pt_regs *regs) | |
91 | { | |
92 | unsigned long addr; | |
93 | ||
94 | while (!kstack_end(sp)) { | |
95 | addr = *sp++; | |
96 | if (kernel_text_address(addr)) { | |
97 | printk(" [<%08lx>] ", addr); | |
98 | print_symbol("%s\n", addr); | |
99 | } | |
100 | } | |
101 | } | |
102 | #endif | |
103 | ||
104 | void show_trace(struct task_struct *tsk, unsigned long *sp, | |
105 | struct pt_regs *regs) | |
106 | { | |
107 | if (regs && | |
108 | (((regs->sr & MODE_MASK) == MODE_EXCEPTION) || | |
109 | ((regs->sr & MODE_MASK) == MODE_USER))) | |
110 | return; | |
111 | ||
112 | printk ("Call trace:"); | |
113 | #ifdef CONFIG_KALLSYMS | |
114 | printk("\n"); | |
115 | #endif | |
116 | ||
117 | __show_trace(tsk, sp, regs); | |
118 | printk("\n"); | |
119 | } | |
120 | ||
121 | void show_stack(struct task_struct *tsk, unsigned long *sp) | |
122 | { | |
123 | unsigned long stack; | |
124 | ||
125 | if (!tsk) | |
126 | tsk = current; | |
127 | if (sp == 0) { | |
128 | if (tsk == current) { | |
129 | register unsigned long *real_sp __asm__("sp"); | |
130 | sp = real_sp; | |
131 | } else { | |
132 | sp = (unsigned long *)tsk->thread.cpu_context.ksp; | |
133 | } | |
134 | } | |
135 | ||
136 | stack = (unsigned long)sp; | |
137 | dump_mem("Stack: ", stack, | |
138 | THREAD_SIZE + (unsigned long)tsk->thread_info); | |
139 | show_trace(tsk, sp, NULL); | |
140 | } | |
141 | ||
142 | void dump_stack(void) | |
143 | { | |
144 | show_stack(NULL, NULL); | |
145 | } | |
146 | EXPORT_SYMBOL(dump_stack); | |
147 | ||
148 | ATOMIC_NOTIFIER_HEAD(avr32_die_chain); | |
149 | ||
150 | int register_die_notifier(struct notifier_block *nb) | |
151 | { | |
152 | pr_debug("register_die_notifier: %p\n", nb); | |
153 | ||
154 | return atomic_notifier_chain_register(&avr32_die_chain, nb); | |
155 | } | |
156 | EXPORT_SYMBOL(register_die_notifier); | |
157 | ||
158 | int unregister_die_notifier(struct notifier_block *nb) | |
159 | { | |
160 | return atomic_notifier_chain_unregister(&avr32_die_chain, nb); | |
161 | } | |
162 | EXPORT_SYMBOL(unregister_die_notifier); | |
163 | ||
164 | static DEFINE_SPINLOCK(die_lock); | |
165 | ||
166 | void __die(const char *str, struct pt_regs *regs, unsigned long err, | |
167 | const char *file, const char *func, unsigned long line) | |
168 | { | |
169 | struct task_struct *tsk = current; | |
170 | static int die_counter; | |
171 | ||
172 | console_verbose(); | |
173 | spin_lock_irq(&die_lock); | |
174 | bust_spinlocks(1); | |
175 | ||
176 | printk(KERN_ALERT "%s", str); | |
177 | if (file && func) | |
178 | printk(" in %s:%s, line %ld", file, func, line); | |
179 | printk("[#%d]:\n", ++die_counter); | |
180 | print_modules(); | |
181 | show_regs(regs); | |
182 | printk("Process %s (pid: %d, stack limit = 0x%p)\n", | |
183 | tsk->comm, tsk->pid, tsk->thread_info + 1); | |
184 | ||
185 | if (!user_mode(regs) || in_interrupt()) { | |
186 | dump_mem("Stack: ", regs->sp, | |
187 | THREAD_SIZE + (unsigned long)tsk->thread_info); | |
188 | } | |
189 | ||
190 | bust_spinlocks(0); | |
191 | spin_unlock_irq(&die_lock); | |
192 | do_exit(SIGSEGV); | |
193 | } | |
194 | ||
195 | void __die_if_kernel(const char *str, struct pt_regs *regs, unsigned long err, | |
196 | const char *file, const char *func, unsigned long line) | |
197 | { | |
198 | if (!user_mode(regs)) | |
199 | __die(str, regs, err, file, func, line); | |
200 | } | |
201 | ||
202 | asmlinkage void do_nmi(unsigned long ecr, struct pt_regs *regs) | |
203 | { | |
204 | #ifdef CONFIG_SUBARCH_AVR32B | |
205 | /* | |
206 | * The exception entry always saves RSR_EX. For NMI, this is | |
207 | * wrong; it should be RSR_NMI | |
208 | */ | |
209 | regs->sr = sysreg_read(RSR_NMI); | |
210 | #endif | |
211 | ||
212 | printk("NMI taken!!!!\n"); | |
213 | die("NMI", regs, ecr); | |
214 | BUG(); | |
215 | } | |
216 | ||
217 | asmlinkage void do_critical_exception(unsigned long ecr, struct pt_regs *regs) | |
218 | { | |
219 | printk("Unable to handle critical exception %lu at pc = %08lx!\n", | |
220 | ecr, regs->pc); | |
221 | die("Oops", regs, ecr); | |
222 | BUG(); | |
223 | } | |
224 | ||
225 | asmlinkage void do_address_exception(unsigned long ecr, struct pt_regs *regs) | |
226 | { | |
227 | siginfo_t info; | |
228 | ||
229 | die_if_kernel("Oops: Address exception in kernel mode", regs, ecr); | |
230 | ||
231 | #ifdef DEBUG | |
232 | if (ecr == ECR_ADDR_ALIGN_X) | |
233 | pr_debug("Instruction Address Exception at pc = %08lx\n", | |
234 | regs->pc); | |
235 | else if (ecr == ECR_ADDR_ALIGN_R) | |
236 | pr_debug("Data Address Exception (Read) at pc = %08lx\n", | |
237 | regs->pc); | |
238 | else if (ecr == ECR_ADDR_ALIGN_W) | |
239 | pr_debug("Data Address Exception (Write) at pc = %08lx\n", | |
240 | regs->pc); | |
241 | else | |
242 | BUG(); | |
243 | ||
244 | show_regs(regs); | |
245 | #endif | |
246 | ||
247 | info.si_signo = SIGBUS; | |
248 | info.si_errno = 0; | |
249 | info.si_code = BUS_ADRALN; | |
250 | info.si_addr = (void __user *)regs->pc; | |
251 | ||
252 | force_sig_info(SIGBUS, &info, current); | |
253 | } | |
254 | ||
255 | /* This way of handling undefined instructions is stolen from ARM */ | |
256 | static LIST_HEAD(undef_hook); | |
257 | static spinlock_t undef_lock = SPIN_LOCK_UNLOCKED; | |
258 | ||
259 | void register_undef_hook(struct undef_hook *hook) | |
260 | { | |
261 | spin_lock_irq(&undef_lock); | |
262 | list_add(&hook->node, &undef_hook); | |
263 | spin_unlock_irq(&undef_lock); | |
264 | } | |
265 | ||
266 | void unregister_undef_hook(struct undef_hook *hook) | |
267 | { | |
268 | spin_lock_irq(&undef_lock); | |
269 | list_del(&hook->node); | |
270 | spin_unlock_irq(&undef_lock); | |
271 | } | |
272 | ||
273 | static int do_cop_absent(u32 insn) | |
274 | { | |
275 | int cop_nr; | |
276 | u32 cpucr; | |
277 | if ( (insn & 0xfdf00000) == 0xf1900000 ) | |
278 | /* LDC0 */ | |
279 | cop_nr = 0; | |
280 | else | |
281 | cop_nr = (insn >> 13) & 0x7; | |
282 | ||
283 | /* Try enabling the coprocessor */ | |
284 | cpucr = sysreg_read(CPUCR); | |
285 | cpucr |= (1 << (24 + cop_nr)); | |
286 | sysreg_write(CPUCR, cpucr); | |
287 | ||
288 | cpucr = sysreg_read(CPUCR); | |
289 | if ( !(cpucr & (1 << (24 + cop_nr))) ){ | |
290 | printk("Coprocessor #%i not found!\n", cop_nr); | |
291 | return -1; | |
292 | } | |
293 | ||
294 | return 0; | |
295 | } | |
296 | ||
297 | #ifdef CONFIG_BUG | |
298 | #ifdef CONFIG_DEBUG_BUGVERBOSE | |
299 | static inline void do_bug_verbose(struct pt_regs *regs, u32 insn) | |
300 | { | |
301 | char *file; | |
302 | u16 line; | |
303 | char c; | |
304 | ||
305 | if (__get_user(line, (u16 __user *)(regs->pc + 2))) | |
306 | return; | |
307 | if (__get_user(file, (char * __user *)(regs->pc + 4)) | |
308 | || (unsigned long)file < PAGE_OFFSET | |
309 | || __get_user(c, file)) | |
310 | file = "<bad filename>"; | |
311 | ||
312 | printk(KERN_ALERT "kernel BUG at %s:%d!\n", file, line); | |
313 | } | |
314 | #else | |
315 | static inline void do_bug_verbose(struct pt_regs *regs, u32 insn) | |
316 | { | |
317 | ||
318 | } | |
319 | #endif | |
320 | #endif | |
321 | ||
322 | asmlinkage void do_illegal_opcode(unsigned long ecr, struct pt_regs *regs) | |
323 | { | |
324 | u32 insn; | |
325 | struct undef_hook *hook; | |
326 | siginfo_t info; | |
327 | void __user *pc; | |
328 | ||
329 | if (!user_mode(regs)) | |
330 | goto kernel_trap; | |
331 | ||
332 | local_irq_enable(); | |
333 | ||
334 | pc = (void __user *)instruction_pointer(regs); | |
335 | if (__get_user(insn, (u32 __user *)pc)) | |
336 | goto invalid_area; | |
337 | ||
338 | if (ecr == ECR_COPROC_ABSENT) { | |
339 | if (do_cop_absent(insn) == 0) | |
340 | return; | |
341 | } | |
342 | ||
343 | spin_lock_irq(&undef_lock); | |
344 | list_for_each_entry(hook, &undef_hook, node) { | |
345 | if ((insn & hook->insn_mask) == hook->insn_val) { | |
346 | if (hook->fn(regs, insn) == 0) { | |
347 | spin_unlock_irq(&undef_lock); | |
348 | return; | |
349 | } | |
350 | } | |
351 | } | |
352 | spin_unlock_irq(&undef_lock); | |
353 | ||
354 | invalid_area: | |
355 | ||
356 | #ifdef DEBUG | |
357 | printk("Illegal instruction at pc = %08lx\n", regs->pc); | |
358 | if (regs->pc < TASK_SIZE) { | |
359 | unsigned long ptbr, pgd, pte, *p; | |
360 | ||
361 | ptbr = sysreg_read(PTBR); | |
362 | p = (unsigned long *)ptbr; | |
363 | pgd = p[regs->pc >> 22]; | |
364 | p = (unsigned long *)((pgd & 0x1ffff000) | 0x80000000); | |
365 | pte = p[(regs->pc >> 12) & 0x3ff]; | |
366 | printk("page table: 0x%08lx -> 0x%08lx -> 0x%08lx\n", ptbr, pgd, pte); | |
367 | } | |
368 | #endif | |
369 | ||
370 | info.si_signo = SIGILL; | |
371 | info.si_errno = 0; | |
372 | info.si_addr = (void __user *)regs->pc; | |
373 | switch (ecr) { | |
374 | case ECR_ILLEGAL_OPCODE: | |
375 | case ECR_UNIMPL_INSTRUCTION: | |
376 | info.si_code = ILL_ILLOPC; | |
377 | break; | |
378 | case ECR_PRIVILEGE_VIOLATION: | |
379 | info.si_code = ILL_PRVOPC; | |
380 | break; | |
381 | case ECR_COPROC_ABSENT: | |
382 | info.si_code = ILL_COPROC; | |
383 | break; | |
384 | default: | |
385 | BUG(); | |
386 | } | |
387 | ||
388 | force_sig_info(SIGILL, &info, current); | |
389 | return; | |
390 | ||
391 | kernel_trap: | |
392 | #ifdef CONFIG_BUG | |
393 | if (__kernel_text_address(instruction_pointer(regs))) { | |
394 | insn = *(u16 *)instruction_pointer(regs); | |
395 | if (insn == AVR32_BUG_OPCODE) { | |
396 | do_bug_verbose(regs, insn); | |
397 | die("Kernel BUG", regs, 0); | |
398 | return; | |
399 | } | |
400 | } | |
401 | #endif | |
402 | ||
403 | die("Oops: Illegal instruction in kernel code", regs, ecr); | |
404 | } | |
405 | ||
406 | asmlinkage void do_fpe(unsigned long ecr, struct pt_regs *regs) | |
407 | { | |
408 | siginfo_t info; | |
409 | ||
410 | printk("Floating-point exception at pc = %08lx\n", regs->pc); | |
411 | ||
412 | /* We have no FPU... */ | |
413 | info.si_signo = SIGILL; | |
414 | info.si_errno = 0; | |
415 | info.si_addr = (void __user *)regs->pc; | |
416 | info.si_code = ILL_COPROC; | |
417 | ||
418 | force_sig_info(SIGILL, &info, current); | |
419 | } | |
420 | ||
421 | ||
422 | void __init trap_init(void) | |
423 | { | |
424 | ||
425 | } |