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