diff options
Diffstat (limited to 'arch/um/kernel/trap_kern.c')
-rw-r--r-- | arch/um/kernel/trap_kern.c | 251 |
1 files changed, 251 insertions, 0 deletions
diff --git a/arch/um/kernel/trap_kern.c b/arch/um/kernel/trap_kern.c new file mode 100644 index 000000000000..47e766e6ba10 --- /dev/null +++ b/arch/um/kernel/trap_kern.c | |||
@@ -0,0 +1,251 @@ | |||
1 | /* | ||
2 | * Copyright (C) 2000, 2001 Jeff Dike (jdike@karaya.com) | ||
3 | * Licensed under the GPL | ||
4 | */ | ||
5 | |||
6 | #include "linux/kernel.h" | ||
7 | #include "asm/errno.h" | ||
8 | #include "linux/sched.h" | ||
9 | #include "linux/mm.h" | ||
10 | #include "linux/spinlock.h" | ||
11 | #include "linux/config.h" | ||
12 | #include "linux/init.h" | ||
13 | #include "linux/ptrace.h" | ||
14 | #include "asm/semaphore.h" | ||
15 | #include "asm/pgtable.h" | ||
16 | #include "asm/pgalloc.h" | ||
17 | #include "asm/tlbflush.h" | ||
18 | #include "asm/a.out.h" | ||
19 | #include "asm/current.h" | ||
20 | #include "asm/irq.h" | ||
21 | #include "user_util.h" | ||
22 | #include "kern_util.h" | ||
23 | #include "kern.h" | ||
24 | #include "chan_kern.h" | ||
25 | #include "mconsole_kern.h" | ||
26 | #include "2_5compat.h" | ||
27 | #include "mem.h" | ||
28 | #include "mem_kern.h" | ||
29 | |||
30 | int handle_page_fault(unsigned long address, unsigned long ip, | ||
31 | int is_write, int is_user, int *code_out) | ||
32 | { | ||
33 | struct mm_struct *mm = current->mm; | ||
34 | struct vm_area_struct *vma; | ||
35 | pgd_t *pgd; | ||
36 | pud_t *pud; | ||
37 | pmd_t *pmd; | ||
38 | pte_t *pte; | ||
39 | unsigned long page; | ||
40 | int err = -EFAULT; | ||
41 | |||
42 | *code_out = SEGV_MAPERR; | ||
43 | down_read(&mm->mmap_sem); | ||
44 | vma = find_vma(mm, address); | ||
45 | if(!vma) | ||
46 | goto out; | ||
47 | else if(vma->vm_start <= address) | ||
48 | goto good_area; | ||
49 | else if(!(vma->vm_flags & VM_GROWSDOWN)) | ||
50 | goto out; | ||
51 | else if(!ARCH_IS_STACKGROW(address)) | ||
52 | goto out; | ||
53 | else if(expand_stack(vma, address)) | ||
54 | goto out; | ||
55 | |||
56 | good_area: | ||
57 | *code_out = SEGV_ACCERR; | ||
58 | if(is_write && !(vma->vm_flags & VM_WRITE)) | ||
59 | goto out; | ||
60 | page = address & PAGE_MASK; | ||
61 | pgd = pgd_offset(mm, page); | ||
62 | pud = pud_offset(pgd, page); | ||
63 | pmd = pmd_offset(pud, page); | ||
64 | do { | ||
65 | survive: | ||
66 | switch (handle_mm_fault(mm, vma, address, is_write)){ | ||
67 | case VM_FAULT_MINOR: | ||
68 | current->min_flt++; | ||
69 | break; | ||
70 | case VM_FAULT_MAJOR: | ||
71 | current->maj_flt++; | ||
72 | break; | ||
73 | case VM_FAULT_SIGBUS: | ||
74 | err = -EACCES; | ||
75 | goto out; | ||
76 | case VM_FAULT_OOM: | ||
77 | err = -ENOMEM; | ||
78 | goto out_of_memory; | ||
79 | default: | ||
80 | BUG(); | ||
81 | } | ||
82 | pgd = pgd_offset(mm, page); | ||
83 | pud = pud_offset(pgd, page); | ||
84 | pmd = pmd_offset(pud, page); | ||
85 | pte = pte_offset_kernel(pmd, page); | ||
86 | } while(!pte_present(*pte)); | ||
87 | err = 0; | ||
88 | *pte = pte_mkyoung(*pte); | ||
89 | if(pte_write(*pte)) *pte = pte_mkdirty(*pte); | ||
90 | flush_tlb_page(vma, page); | ||
91 | out: | ||
92 | up_read(&mm->mmap_sem); | ||
93 | return(err); | ||
94 | |||
95 | /* | ||
96 | * We ran out of memory, or some other thing happened to us that made | ||
97 | * us unable to handle the page fault gracefully. | ||
98 | */ | ||
99 | out_of_memory: | ||
100 | if (current->pid == 1) { | ||
101 | up_read(&mm->mmap_sem); | ||
102 | yield(); | ||
103 | down_read(&mm->mmap_sem); | ||
104 | goto survive; | ||
105 | } | ||
106 | goto out; | ||
107 | } | ||
108 | |||
109 | LIST_HEAD(physmem_remappers); | ||
110 | |||
111 | void register_remapper(struct remapper *info) | ||
112 | { | ||
113 | list_add(&info->list, &physmem_remappers); | ||
114 | } | ||
115 | |||
116 | static int check_remapped_addr(unsigned long address, int is_write) | ||
117 | { | ||
118 | struct remapper *remapper; | ||
119 | struct list_head *ele; | ||
120 | __u64 offset; | ||
121 | int fd; | ||
122 | |||
123 | fd = phys_mapping(__pa(address), &offset); | ||
124 | if(fd == -1) | ||
125 | return(0); | ||
126 | |||
127 | list_for_each(ele, &physmem_remappers){ | ||
128 | remapper = list_entry(ele, struct remapper, list); | ||
129 | if((*remapper->proc)(fd, address, is_write, offset)) | ||
130 | return(1); | ||
131 | } | ||
132 | |||
133 | return(0); | ||
134 | } | ||
135 | |||
136 | unsigned long segv(unsigned long address, unsigned long ip, int is_write, | ||
137 | int is_user, void *sc) | ||
138 | { | ||
139 | struct siginfo si; | ||
140 | void *catcher; | ||
141 | int err; | ||
142 | |||
143 | if(!is_user && (address >= start_vm) && (address < end_vm)){ | ||
144 | flush_tlb_kernel_vm(); | ||
145 | return(0); | ||
146 | } | ||
147 | else if(check_remapped_addr(address & PAGE_MASK, is_write)) | ||
148 | return(0); | ||
149 | else if(current->mm == NULL) | ||
150 | panic("Segfault with no mm"); | ||
151 | err = handle_page_fault(address, ip, is_write, is_user, &si.si_code); | ||
152 | |||
153 | catcher = current->thread.fault_catcher; | ||
154 | if(!err) | ||
155 | return(0); | ||
156 | else if(catcher != NULL){ | ||
157 | current->thread.fault_addr = (void *) address; | ||
158 | do_longjmp(catcher, 1); | ||
159 | } | ||
160 | else if(current->thread.fault_addr != NULL) | ||
161 | panic("fault_addr set but no fault catcher"); | ||
162 | else if(arch_fixup(ip, sc)) | ||
163 | return(0); | ||
164 | |||
165 | if(!is_user) | ||
166 | panic("Kernel mode fault at addr 0x%lx, ip 0x%lx", | ||
167 | address, ip); | ||
168 | |||
169 | if(err == -EACCES){ | ||
170 | si.si_signo = SIGBUS; | ||
171 | si.si_errno = 0; | ||
172 | si.si_code = BUS_ADRERR; | ||
173 | si.si_addr = (void *)address; | ||
174 | force_sig_info(SIGBUS, &si, current); | ||
175 | } | ||
176 | else if(err == -ENOMEM){ | ||
177 | printk("VM: killing process %s\n", current->comm); | ||
178 | do_exit(SIGKILL); | ||
179 | } | ||
180 | else { | ||
181 | si.si_signo = SIGSEGV; | ||
182 | si.si_addr = (void *) address; | ||
183 | current->thread.cr2 = address; | ||
184 | current->thread.err = is_write; | ||
185 | force_sig_info(SIGSEGV, &si, current); | ||
186 | } | ||
187 | return(0); | ||
188 | } | ||
189 | |||
190 | void bad_segv(unsigned long address, unsigned long ip, int is_write) | ||
191 | { | ||
192 | struct siginfo si; | ||
193 | |||
194 | si.si_signo = SIGSEGV; | ||
195 | si.si_code = SEGV_ACCERR; | ||
196 | si.si_addr = (void *) address; | ||
197 | current->thread.cr2 = address; | ||
198 | current->thread.err = is_write; | ||
199 | force_sig_info(SIGSEGV, &si, current); | ||
200 | } | ||
201 | |||
202 | void relay_signal(int sig, union uml_pt_regs *regs) | ||
203 | { | ||
204 | if(arch_handle_signal(sig, regs)) return; | ||
205 | if(!UPT_IS_USER(regs)) | ||
206 | panic("Kernel mode signal %d", sig); | ||
207 | force_sig(sig, current); | ||
208 | } | ||
209 | |||
210 | void bus_handler(int sig, union uml_pt_regs *regs) | ||
211 | { | ||
212 | if(current->thread.fault_catcher != NULL) | ||
213 | do_longjmp(current->thread.fault_catcher, 1); | ||
214 | else relay_signal(sig, regs); | ||
215 | } | ||
216 | |||
217 | void winch(int sig, union uml_pt_regs *regs) | ||
218 | { | ||
219 | do_IRQ(WINCH_IRQ, regs); | ||
220 | } | ||
221 | |||
222 | void trap_init(void) | ||
223 | { | ||
224 | } | ||
225 | |||
226 | DEFINE_SPINLOCK(trap_lock); | ||
227 | |||
228 | static int trap_index = 0; | ||
229 | |||
230 | int next_trap_index(int limit) | ||
231 | { | ||
232 | int ret; | ||
233 | |||
234 | spin_lock(&trap_lock); | ||
235 | ret = trap_index; | ||
236 | if(++trap_index == limit) | ||
237 | trap_index = 0; | ||
238 | spin_unlock(&trap_lock); | ||
239 | return(ret); | ||
240 | } | ||
241 | |||
242 | /* | ||
243 | * Overrides for Emacs so that we follow Linus's tabbing style. | ||
244 | * Emacs will notice this stuff at the end of the file and automatically | ||
245 | * adjust the settings for this buffer only. This must remain at the end | ||
246 | * of the file. | ||
247 | * --------------------------------------------------------------------------- | ||
248 | * Local variables: | ||
249 | * c-file-style: "linux" | ||
250 | * End: | ||
251 | */ | ||