diff options
author | Ley Foon Tan <lftan@altera.com> | 2014-11-06 02:19:44 -0500 |
---|---|---|
committer | Ley Foon Tan <lftan@altera.com> | 2014-12-07 23:55:52 -0500 |
commit | 862674d4249c204ce58313ec4f59190abbc86b7e (patch) | |
tree | 0a36821c0671a2ff6f990c098628149a04cbb9ef /arch | |
parent | 6b8baec4d652df19be6e0997c50b9894e5ca30a8 (diff) |
nios2: MMU Fault handling
This patch adds support for the handling of the MMU faults (exception
entry code introduced by a previous patch, kernel/entry.S).
Signed-off-by: Ley Foon Tan <lftan@altera.com>
Diffstat (limited to 'arch')
-rw-r--r-- | arch/nios2/mm/extable.c | 25 | ||||
-rw-r--r-- | arch/nios2/mm/fault.c | 251 |
2 files changed, 276 insertions, 0 deletions
diff --git a/arch/nios2/mm/extable.c b/arch/nios2/mm/extable.c new file mode 100644 index 000000000000..4d2fc5a589d0 --- /dev/null +++ b/arch/nios2/mm/extable.c | |||
@@ -0,0 +1,25 @@ | |||
1 | /* | ||
2 | * Copyright (C) 2010, Tobias Klauser <tklauser@distanz.ch> | ||
3 | * Copyright (C) 2009, Wind River Systems Inc | ||
4 | * Implemented by fredrik.markstrom@gmail.com and ivarholmqvist@gmail.com | ||
5 | * | ||
6 | * This file is subject to the terms and conditions of the GNU General Public | ||
7 | * License. See the file "COPYING" in the main directory of this archive | ||
8 | * for more details. | ||
9 | */ | ||
10 | |||
11 | #include <linux/module.h> | ||
12 | #include <linux/uaccess.h> | ||
13 | |||
14 | int fixup_exception(struct pt_regs *regs) | ||
15 | { | ||
16 | const struct exception_table_entry *fixup; | ||
17 | |||
18 | fixup = search_exception_tables(regs->ea); | ||
19 | if (fixup) { | ||
20 | regs->ea = fixup->fixup; | ||
21 | return 1; | ||
22 | } | ||
23 | |||
24 | return 0; | ||
25 | } | ||
diff --git a/arch/nios2/mm/fault.c b/arch/nios2/mm/fault.c new file mode 100644 index 000000000000..15a0bb5fc06d --- /dev/null +++ b/arch/nios2/mm/fault.c | |||
@@ -0,0 +1,251 @@ | |||
1 | /* | ||
2 | * Copyright (C) 2009 Wind River Systems Inc | ||
3 | * Implemented by fredrik.markstrom@gmail.com and ivarholmqvist@gmail.com | ||
4 | * | ||
5 | * based on arch/mips/mm/fault.c which is: | ||
6 | * | ||
7 | * Copyright (C) 1995-2000 Ralf Baechle | ||
8 | * | ||
9 | * This file is subject to the terms and conditions of the GNU General Public | ||
10 | * License. See the file "COPYING" in the main directory of this archive | ||
11 | * for more details. | ||
12 | */ | ||
13 | |||
14 | #include <linux/signal.h> | ||
15 | #include <linux/sched.h> | ||
16 | #include <linux/interrupt.h> | ||
17 | #include <linux/kernel.h> | ||
18 | #include <linux/errno.h> | ||
19 | #include <linux/string.h> | ||
20 | #include <linux/types.h> | ||
21 | #include <linux/ptrace.h> | ||
22 | #include <linux/mman.h> | ||
23 | #include <linux/mm.h> | ||
24 | #include <linux/module.h> | ||
25 | #include <linux/uaccess.h> | ||
26 | #include <linux/ptrace.h> | ||
27 | |||
28 | #include <asm/mmu_context.h> | ||
29 | #include <asm/traps.h> | ||
30 | |||
31 | #define EXC_SUPERV_INSN_ACCESS 9 /* Supervisor only instruction address */ | ||
32 | #define EXC_SUPERV_DATA_ACCESS 11 /* Supervisor only data address */ | ||
33 | #define EXC_X_PROTECTION_FAULT 13 /* TLB permission violation (x) */ | ||
34 | #define EXC_R_PROTECTION_FAULT 14 /* TLB permission violation (r) */ | ||
35 | #define EXC_W_PROTECTION_FAULT 15 /* TLB permission violation (w) */ | ||
36 | |||
37 | /* | ||
38 | * This routine handles page faults. It determines the address, | ||
39 | * and the problem, and then passes it off to one of the appropriate | ||
40 | * routines. | ||
41 | */ | ||
42 | asmlinkage void do_page_fault(struct pt_regs *regs, unsigned long cause, | ||
43 | unsigned long address) | ||
44 | { | ||
45 | struct vm_area_struct *vma = NULL; | ||
46 | struct task_struct *tsk = current; | ||
47 | struct mm_struct *mm = tsk->mm; | ||
48 | int code = SEGV_MAPERR; | ||
49 | int fault; | ||
50 | unsigned int flags = 0; | ||
51 | |||
52 | cause >>= 2; | ||
53 | |||
54 | /* Restart the instruction */ | ||
55 | regs->ea -= 4; | ||
56 | |||
57 | /* | ||
58 | * We fault-in kernel-space virtual memory on-demand. The | ||
59 | * 'reference' page table is init_mm.pgd. | ||
60 | * | ||
61 | * NOTE! We MUST NOT take any locks for this case. We may | ||
62 | * be in an interrupt or a critical region, and should | ||
63 | * only copy the information from the master page table, | ||
64 | * nothing more. | ||
65 | */ | ||
66 | if (unlikely(address >= VMALLOC_START && address <= VMALLOC_END)) { | ||
67 | if (user_mode(regs)) | ||
68 | goto bad_area_nosemaphore; | ||
69 | else | ||
70 | goto vmalloc_fault; | ||
71 | } | ||
72 | |||
73 | if (unlikely(address >= TASK_SIZE)) | ||
74 | goto bad_area_nosemaphore; | ||
75 | |||
76 | /* | ||
77 | * If we're in an interrupt or have no user | ||
78 | * context, we must not take the fault.. | ||
79 | */ | ||
80 | if (in_atomic() || !mm) | ||
81 | goto bad_area_nosemaphore; | ||
82 | |||
83 | if (user_mode(regs)) | ||
84 | flags |= FAULT_FLAG_USER; | ||
85 | |||
86 | if (!down_read_trylock(&mm->mmap_sem)) { | ||
87 | if (!user_mode(regs) && !search_exception_tables(regs->ea)) | ||
88 | goto bad_area_nosemaphore; | ||
89 | down_read(&mm->mmap_sem); | ||
90 | } | ||
91 | |||
92 | vma = find_vma(mm, address); | ||
93 | if (!vma) | ||
94 | goto bad_area; | ||
95 | if (vma->vm_start <= address) | ||
96 | goto good_area; | ||
97 | if (!(vma->vm_flags & VM_GROWSDOWN)) | ||
98 | goto bad_area; | ||
99 | if (expand_stack(vma, address)) | ||
100 | goto bad_area; | ||
101 | /* | ||
102 | * Ok, we have a good vm_area for this memory access, so | ||
103 | * we can handle it.. | ||
104 | */ | ||
105 | good_area: | ||
106 | code = SEGV_ACCERR; | ||
107 | |||
108 | switch (cause) { | ||
109 | case EXC_SUPERV_INSN_ACCESS: | ||
110 | goto bad_area; | ||
111 | case EXC_SUPERV_DATA_ACCESS: | ||
112 | goto bad_area; | ||
113 | case EXC_X_PROTECTION_FAULT: | ||
114 | if (!(vma->vm_flags & VM_EXEC)) | ||
115 | goto bad_area; | ||
116 | break; | ||
117 | case EXC_R_PROTECTION_FAULT: | ||
118 | if (!(vma->vm_flags & VM_READ)) | ||
119 | goto bad_area; | ||
120 | break; | ||
121 | case EXC_W_PROTECTION_FAULT: | ||
122 | if (!(vma->vm_flags & VM_WRITE)) | ||
123 | goto bad_area; | ||
124 | flags = FAULT_FLAG_WRITE; | ||
125 | break; | ||
126 | } | ||
127 | |||
128 | survive: | ||
129 | /* | ||
130 | * If for any reason at all we couldn't handle the fault, | ||
131 | * make sure we exit gracefully rather than endlessly redo | ||
132 | * the fault. | ||
133 | */ | ||
134 | fault = handle_mm_fault(mm, vma, address, flags); | ||
135 | if (unlikely(fault & VM_FAULT_ERROR)) { | ||
136 | if (fault & VM_FAULT_OOM) | ||
137 | goto out_of_memory; | ||
138 | else if (fault & VM_FAULT_SIGBUS) | ||
139 | goto do_sigbus; | ||
140 | BUG(); | ||
141 | } | ||
142 | if (fault & VM_FAULT_MAJOR) | ||
143 | tsk->maj_flt++; | ||
144 | else | ||
145 | tsk->min_flt++; | ||
146 | |||
147 | up_read(&mm->mmap_sem); | ||
148 | return; | ||
149 | |||
150 | /* | ||
151 | * Something tried to access memory that isn't in our memory map.. | ||
152 | * Fix it, but check if it's kernel or user first.. | ||
153 | */ | ||
154 | bad_area: | ||
155 | up_read(&mm->mmap_sem); | ||
156 | |||
157 | bad_area_nosemaphore: | ||
158 | /* User mode accesses just cause a SIGSEGV */ | ||
159 | if (user_mode(regs)) { | ||
160 | pr_alert("%s: unhandled page fault (%d) at 0x%08lx, " | ||
161 | "cause %ld\n", current->comm, SIGSEGV, address, cause); | ||
162 | show_regs(regs); | ||
163 | _exception(SIGSEGV, regs, code, address); | ||
164 | return; | ||
165 | } | ||
166 | |||
167 | no_context: | ||
168 | /* Are we prepared to handle this kernel fault? */ | ||
169 | if (fixup_exception(regs)) | ||
170 | return; | ||
171 | |||
172 | /* | ||
173 | * Oops. The kernel tried to access some bad page. We'll have to | ||
174 | * terminate things with extreme prejudice. | ||
175 | */ | ||
176 | bust_spinlocks(1); | ||
177 | |||
178 | pr_alert("Unable to handle kernel %s at virtual address %08lx", | ||
179 | address < PAGE_SIZE ? "NULL pointer dereference" : | ||
180 | "paging request", address); | ||
181 | pr_alert("ea = %08lx, ra = %08lx, cause = %ld\n", regs->ea, regs->ra, | ||
182 | cause); | ||
183 | panic("Oops"); | ||
184 | return; | ||
185 | |||
186 | /* | ||
187 | * We ran out of memory, or some other thing happened to us that made | ||
188 | * us unable to handle the page fault gracefully. | ||
189 | */ | ||
190 | out_of_memory: | ||
191 | up_read(&mm->mmap_sem); | ||
192 | if (is_global_init(tsk)) { | ||
193 | yield(); | ||
194 | down_read(&mm->mmap_sem); | ||
195 | goto survive; | ||
196 | } | ||
197 | if (!user_mode(regs)) | ||
198 | goto no_context; | ||
199 | pagefault_out_of_memory(); | ||
200 | return; | ||
201 | |||
202 | do_sigbus: | ||
203 | up_read(&mm->mmap_sem); | ||
204 | |||
205 | /* Kernel mode? Handle exceptions or die */ | ||
206 | if (!user_mode(regs)) | ||
207 | goto no_context; | ||
208 | |||
209 | _exception(SIGBUS, regs, BUS_ADRERR, address); | ||
210 | return; | ||
211 | |||
212 | vmalloc_fault: | ||
213 | { | ||
214 | /* | ||
215 | * Synchronize this task's top level page-table | ||
216 | * with the 'reference' page table. | ||
217 | * | ||
218 | * Do _not_ use "tsk" here. We might be inside | ||
219 | * an interrupt in the middle of a task switch.. | ||
220 | */ | ||
221 | int offset = pgd_index(address); | ||
222 | pgd_t *pgd, *pgd_k; | ||
223 | pud_t *pud, *pud_k; | ||
224 | pmd_t *pmd, *pmd_k; | ||
225 | pte_t *pte_k; | ||
226 | |||
227 | pgd = pgd_current + offset; | ||
228 | pgd_k = init_mm.pgd + offset; | ||
229 | |||
230 | if (!pgd_present(*pgd_k)) | ||
231 | goto no_context; | ||
232 | set_pgd(pgd, *pgd_k); | ||
233 | |||
234 | pud = pud_offset(pgd, address); | ||
235 | pud_k = pud_offset(pgd_k, address); | ||
236 | if (!pud_present(*pud_k)) | ||
237 | goto no_context; | ||
238 | pmd = pmd_offset(pud, address); | ||
239 | pmd_k = pmd_offset(pud_k, address); | ||
240 | if (!pmd_present(*pmd_k)) | ||
241 | goto no_context; | ||
242 | set_pmd(pmd, *pmd_k); | ||
243 | |||
244 | pte_k = pte_offset_kernel(pmd_k, address); | ||
245 | if (!pte_present(*pte_k)) | ||
246 | goto no_context; | ||
247 | |||
248 | flush_tlb_one(address); | ||
249 | return; | ||
250 | } | ||
251 | } | ||