diff options
Diffstat (limited to 'arch/avr32/mm/fault.c')
-rw-r--r-- | arch/avr32/mm/fault.c | 315 |
1 files changed, 315 insertions, 0 deletions
diff --git a/arch/avr32/mm/fault.c b/arch/avr32/mm/fault.c new file mode 100644 index 000000000000..678557260a35 --- /dev/null +++ b/arch/avr32/mm/fault.c | |||
@@ -0,0 +1,315 @@ | |||
1 | /* | ||
2 | * Copyright (C) 2004-2006 Atmel Corporation | ||
3 | * | ||
4 | * Based on linux/arch/sh/mm/fault.c: | ||
5 | * Copyright (C) 1999 Niibe Yutaka | ||
6 | * | ||
7 | * This program is free software; you can redistribute it and/or modify | ||
8 | * it under the terms of the GNU General Public License version 2 as | ||
9 | * published by the Free Software Foundation. | ||
10 | */ | ||
11 | |||
12 | #include <linux/mm.h> | ||
13 | #include <linux/module.h> | ||
14 | #include <linux/pagemap.h> | ||
15 | |||
16 | #include <asm/kdebug.h> | ||
17 | #include <asm/mmu_context.h> | ||
18 | #include <asm/sysreg.h> | ||
19 | #include <asm/uaccess.h> | ||
20 | #include <asm/tlb.h> | ||
21 | |||
22 | #ifdef DEBUG | ||
23 | static void dump_code(unsigned long pc) | ||
24 | { | ||
25 | char *p = (char *)pc; | ||
26 | char val; | ||
27 | int i; | ||
28 | |||
29 | |||
30 | printk(KERN_DEBUG "Code:"); | ||
31 | for (i = 0; i < 16; i++) { | ||
32 | if (__get_user(val, p + i)) | ||
33 | break; | ||
34 | printk(" %02x", val); | ||
35 | } | ||
36 | printk("\n"); | ||
37 | } | ||
38 | #endif | ||
39 | |||
40 | #ifdef CONFIG_KPROBES | ||
41 | ATOMIC_NOTIFIER_HEAD(notify_page_fault_chain); | ||
42 | |||
43 | /* Hook to register for page fault notifications */ | ||
44 | int register_page_fault_notifier(struct notifier_block *nb) | ||
45 | { | ||
46 | return atomic_notifier_chain_register(¬ify_page_fault_chain, nb); | ||
47 | } | ||
48 | |||
49 | int unregister_page_fault_notifier(struct notifier_block *nb) | ||
50 | { | ||
51 | return atomic_notifier_chain_unregister(¬ify_page_fault_chain, nb); | ||
52 | } | ||
53 | |||
54 | static inline int notify_page_fault(enum die_val val, struct pt_regs *regs, | ||
55 | int trap, int sig) | ||
56 | { | ||
57 | struct die_args args = { | ||
58 | .regs = regs, | ||
59 | .trapnr = trap, | ||
60 | }; | ||
61 | return atomic_notifier_call_chain(¬ify_page_fault_chain, val, &args); | ||
62 | } | ||
63 | #else | ||
64 | static inline int notify_page_fault(enum die_val val, struct pt_regs *regs, | ||
65 | int trap, int sig) | ||
66 | { | ||
67 | return NOTIFY_DONE; | ||
68 | } | ||
69 | #endif | ||
70 | |||
71 | /* | ||
72 | * This routine handles page faults. It determines the address and the | ||
73 | * problem, and then passes it off to one of the appropriate routines. | ||
74 | * | ||
75 | * ecr is the Exception Cause Register. Possible values are: | ||
76 | * 5: Page not found (instruction access) | ||
77 | * 6: Protection fault (instruction access) | ||
78 | * 12: Page not found (read access) | ||
79 | * 13: Page not found (write access) | ||
80 | * 14: Protection fault (read access) | ||
81 | * 15: Protection fault (write access) | ||
82 | */ | ||
83 | asmlinkage void do_page_fault(unsigned long ecr, struct pt_regs *regs) | ||
84 | { | ||
85 | struct task_struct *tsk; | ||
86 | struct mm_struct *mm; | ||
87 | struct vm_area_struct *vma; | ||
88 | const struct exception_table_entry *fixup; | ||
89 | unsigned long address; | ||
90 | unsigned long page; | ||
91 | int writeaccess = 0; | ||
92 | |||
93 | if (notify_page_fault(DIE_PAGE_FAULT, regs, | ||
94 | ecr, SIGSEGV) == NOTIFY_STOP) | ||
95 | return; | ||
96 | |||
97 | address = sysreg_read(TLBEAR); | ||
98 | |||
99 | tsk = current; | ||
100 | mm = tsk->mm; | ||
101 | |||
102 | /* | ||
103 | * If we're in an interrupt or have no user context, we must | ||
104 | * not take the fault... | ||
105 | */ | ||
106 | if (in_atomic() || !mm || regs->sr & SYSREG_BIT(GM)) | ||
107 | goto no_context; | ||
108 | |||
109 | local_irq_enable(); | ||
110 | |||
111 | down_read(&mm->mmap_sem); | ||
112 | |||
113 | vma = find_vma(mm, address); | ||
114 | if (!vma) | ||
115 | goto bad_area; | ||
116 | if (vma->vm_start <= address) | ||
117 | goto good_area; | ||
118 | if (!(vma->vm_flags & VM_GROWSDOWN)) | ||
119 | goto bad_area; | ||
120 | if (expand_stack(vma, address)) | ||
121 | goto bad_area; | ||
122 | |||
123 | /* | ||
124 | * Ok, we have a good vm_area for this memory access, so we | ||
125 | * can handle it... | ||
126 | */ | ||
127 | good_area: | ||
128 | //pr_debug("good area: vm_flags = 0x%lx\n", vma->vm_flags); | ||
129 | switch (ecr) { | ||
130 | case ECR_PROTECTION_X: | ||
131 | case ECR_TLB_MISS_X: | ||
132 | if (!(vma->vm_flags & VM_EXEC)) | ||
133 | goto bad_area; | ||
134 | break; | ||
135 | case ECR_PROTECTION_R: | ||
136 | case ECR_TLB_MISS_R: | ||
137 | if (!(vma->vm_flags & (VM_READ | VM_WRITE | VM_EXEC))) | ||
138 | goto bad_area; | ||
139 | break; | ||
140 | case ECR_PROTECTION_W: | ||
141 | case ECR_TLB_MISS_W: | ||
142 | if (!(vma->vm_flags & VM_WRITE)) | ||
143 | goto bad_area; | ||
144 | writeaccess = 1; | ||
145 | break; | ||
146 | default: | ||
147 | panic("Unhandled case %lu in do_page_fault!", ecr); | ||
148 | } | ||
149 | |||
150 | /* | ||
151 | * If for any reason at all we couldn't handle the fault, make | ||
152 | * sure we exit gracefully rather than endlessly redo the | ||
153 | * fault. | ||
154 | */ | ||
155 | survive: | ||
156 | switch (handle_mm_fault(mm, vma, address, writeaccess)) { | ||
157 | case VM_FAULT_MINOR: | ||
158 | tsk->min_flt++; | ||
159 | break; | ||
160 | case VM_FAULT_MAJOR: | ||
161 | tsk->maj_flt++; | ||
162 | break; | ||
163 | case VM_FAULT_SIGBUS: | ||
164 | goto do_sigbus; | ||
165 | case VM_FAULT_OOM: | ||
166 | goto out_of_memory; | ||
167 | default: | ||
168 | BUG(); | ||
169 | } | ||
170 | |||
171 | up_read(&mm->mmap_sem); | ||
172 | return; | ||
173 | |||
174 | /* | ||
175 | * Something tried to access memory that isn't in our memory | ||
176 | * map. Fix it, but check if it's kernel or user first... | ||
177 | */ | ||
178 | bad_area: | ||
179 | pr_debug("Bad area [%s:%u]: addr %08lx, ecr %lu\n", | ||
180 | tsk->comm, tsk->pid, address, ecr); | ||
181 | |||
182 | up_read(&mm->mmap_sem); | ||
183 | |||
184 | if (user_mode(regs)) { | ||
185 | /* Hmm...we have to pass address and ecr somehow... */ | ||
186 | /* tsk->thread.address = address; | ||
187 | tsk->thread.error_code = ecr; */ | ||
188 | #ifdef DEBUG | ||
189 | show_regs(regs); | ||
190 | dump_code(regs->pc); | ||
191 | |||
192 | page = sysreg_read(PTBR); | ||
193 | printk("ptbr = %08lx", page); | ||
194 | if (page) { | ||
195 | page = ((unsigned long *)page)[address >> 22]; | ||
196 | printk(" pgd = %08lx", page); | ||
197 | if (page & _PAGE_PRESENT) { | ||
198 | page &= PAGE_MASK; | ||
199 | address &= 0x003ff000; | ||
200 | page = ((unsigned long *)__va(page))[address >> PAGE_SHIFT]; | ||
201 | printk(" pte = %08lx\n", page); | ||
202 | } | ||
203 | } | ||
204 | #endif | ||
205 | pr_debug("Sending SIGSEGV to PID %d...\n", | ||
206 | tsk->pid); | ||
207 | force_sig(SIGSEGV, tsk); | ||
208 | return; | ||
209 | } | ||
210 | |||
211 | no_context: | ||
212 | pr_debug("No context\n"); | ||
213 | |||
214 | /* Are we prepared to handle this kernel fault? */ | ||
215 | fixup = search_exception_tables(regs->pc); | ||
216 | if (fixup) { | ||
217 | regs->pc = fixup->fixup; | ||
218 | pr_debug("Found fixup at %08lx\n", fixup->fixup); | ||
219 | return; | ||
220 | } | ||
221 | |||
222 | /* | ||
223 | * Oops. The kernel tried to access some bad page. We'll have | ||
224 | * to terminate things with extreme prejudice. | ||
225 | */ | ||
226 | if (address < PAGE_SIZE) | ||
227 | printk(KERN_ALERT | ||
228 | "Unable to handle kernel NULL pointer dereference"); | ||
229 | else | ||
230 | printk(KERN_ALERT | ||
231 | "Unable to handle kernel paging request"); | ||
232 | printk(" at virtual address %08lx\n", address); | ||
233 | printk(KERN_ALERT "pc = %08lx\n", regs->pc); | ||
234 | |||
235 | page = sysreg_read(PTBR); | ||
236 | printk(KERN_ALERT "ptbr = %08lx", page); | ||
237 | if (page) { | ||
238 | page = ((unsigned long *)page)[address >> 22]; | ||
239 | printk(" pgd = %08lx", page); | ||
240 | if (page & _PAGE_PRESENT) { | ||
241 | page &= PAGE_MASK; | ||
242 | address &= 0x003ff000; | ||
243 | page = ((unsigned long *)__va(page))[address >> PAGE_SHIFT]; | ||
244 | printk(" pte = %08lx\n", page); | ||
245 | } | ||
246 | } | ||
247 | die("\nOops", regs, ecr); | ||
248 | do_exit(SIGKILL); | ||
249 | |||
250 | /* | ||
251 | * We ran out of memory, or some other thing happened to us | ||
252 | * that made us unable to handle the page fault gracefully. | ||
253 | */ | ||
254 | out_of_memory: | ||
255 | printk("Out of memory\n"); | ||
256 | up_read(&mm->mmap_sem); | ||
257 | if (current->pid == 1) { | ||
258 | yield(); | ||
259 | down_read(&mm->mmap_sem); | ||
260 | goto survive; | ||
261 | } | ||
262 | printk("VM: Killing process %s\n", tsk->comm); | ||
263 | if (user_mode(regs)) | ||
264 | do_exit(SIGKILL); | ||
265 | goto no_context; | ||
266 | |||
267 | do_sigbus: | ||
268 | up_read(&mm->mmap_sem); | ||
269 | |||
270 | /* | ||
271 | * Send a sigbus, regardless of whether we were in kernel or | ||
272 | * user mode. | ||
273 | */ | ||
274 | /* address, error_code, trap_no, ... */ | ||
275 | #ifdef DEBUG | ||
276 | show_regs(regs); | ||
277 | dump_code(regs->pc); | ||
278 | #endif | ||
279 | pr_debug("Sending SIGBUS to PID %d...\n", tsk->pid); | ||
280 | force_sig(SIGBUS, tsk); | ||
281 | |||
282 | /* Kernel mode? Handle exceptions or die */ | ||
283 | if (!user_mode(regs)) | ||
284 | goto no_context; | ||
285 | } | ||
286 | |||
287 | asmlinkage void do_bus_error(unsigned long addr, int write_access, | ||
288 | struct pt_regs *regs) | ||
289 | { | ||
290 | printk(KERN_ALERT | ||
291 | "Bus error at physical address 0x%08lx (%s access)\n", | ||
292 | addr, write_access ? "write" : "read"); | ||
293 | printk(KERN_INFO "DTLB dump:\n"); | ||
294 | dump_dtlb(); | ||
295 | die("Bus Error", regs, write_access); | ||
296 | do_exit(SIGKILL); | ||
297 | } | ||
298 | |||
299 | /* | ||
300 | * This functionality is currently not possible to implement because | ||
301 | * we're using segmentation to ensure a fixed mapping of the kernel | ||
302 | * virtual address space. | ||
303 | * | ||
304 | * It would be possible to implement this, but it would require us to | ||
305 | * disable segmentation at startup and load the kernel mappings into | ||
306 | * the TLB like any other pages. There will be lots of trickery to | ||
307 | * avoid recursive invocation of the TLB miss handler, though... | ||
308 | */ | ||
309 | #ifdef CONFIG_DEBUG_PAGEALLOC | ||
310 | void kernel_map_pages(struct page *page, int numpages, int enable) | ||
311 | { | ||
312 | |||
313 | } | ||
314 | EXPORT_SYMBOL(kernel_map_pages); | ||
315 | #endif | ||