www.usr.com/support/gpl/USR9113_release1.0.tar.gz
[bcm963xx.git] / kernel / linux / arch / mips / mm / fault.c
1 /*\r
2  * This file is subject to the terms and conditions of the GNU General Public\r
3  * License.  See the file "COPYING" in the main directory of this archive\r
4  * for more details.\r
5  *\r
6  * Copyright (C) 1995 - 2000 by Ralf Baechle\r
7  */\r
8 #include <linux/signal.h>\r
9 #include <linux/sched.h>\r
10 #include <linux/interrupt.h>\r
11 #include <linux/kernel.h>\r
12 #include <linux/errno.h>\r
13 #include <linux/string.h>\r
14 #include <linux/types.h>\r
15 #include <linux/ptrace.h>\r
16 #include <linux/mman.h>\r
17 #include <linux/mm.h>\r
18 #include <linux/smp.h>\r
19 #include <linux/smp_lock.h>\r
20 #include <linux/vt_kern.h>              /* For unblank_screen() */\r
21 #include <linux/module.h>\r
22 \r
23 #include <asm/branch.h>\r
24 #include <asm/hardirq.h>\r
25 #include <asm/mmu_context.h>\r
26 #include <asm/system.h>\r
27 #include <asm/uaccess.h>\r
28 #include <asm/ptrace.h>\r
29 \r
30 /*\r
31  * This routine handles page faults.  It determines the address,\r
32  * and the problem, and then passes it off to one of the appropriate\r
33  * routines.\r
34  */\r
35 asmlinkage void do_page_fault(struct pt_regs *regs, unsigned long write,\r
36                               unsigned long address)\r
37 {\r
38         struct vm_area_struct * vma = NULL;\r
39         struct task_struct *tsk = current;\r
40         struct mm_struct *mm = tsk->mm;\r
41         const int field = sizeof(unsigned long) * 2;\r
42         siginfo_t info;\r
43 \r
44 #if 0\r
45         printk("Cpu%d[%s:%d:%0*lx:%ld:%0*lx]\n", smp_processor_id(),\r
46                current->comm, current->pid, field, address, write,\r
47                field, regs->cp0_epc);\r
48 #endif\r
49 \r
50         info.si_code = SEGV_MAPERR;\r
51 \r
52         /*\r
53          * We fault-in kernel-space virtual memory on-demand. The\r
54          * 'reference' page table is init_mm.pgd.\r
55          *\r
56          * NOTE! We MUST NOT take any locks for this case. We may\r
57          * be in an interrupt or a critical region, and should\r
58          * only copy the information from the master page table,\r
59          * nothing more.\r
60          */\r
61         if (unlikely(address >= VMALLOC_START))\r
62                 goto vmalloc_fault;\r
63 \r
64         /*\r
65          * If we're in an interrupt or have no user\r
66          * context, we must not take the fault..\r
67          */\r
68         if (in_atomic() || !mm)\r
69                 goto bad_area_nosemaphore;\r
70 \r
71         down_read(&mm->mmap_sem);\r
72         vma = find_vma(mm, address);\r
73         if (!vma)\r
74                 goto bad_area;\r
75         if (vma->vm_start <= address)\r
76                 goto good_area;\r
77         if (!(vma->vm_flags & VM_GROWSDOWN))\r
78                 goto bad_area;\r
79         if (expand_stack(vma, address))\r
80                 goto bad_area;\r
81 /*\r
82  * Ok, we have a good vm_area for this memory access, so\r
83  * we can handle it..\r
84  */\r
85 good_area:\r
86         info.si_code = SEGV_ACCERR;\r
87 \r
88         if (write) {\r
89                 if (!(vma->vm_flags & VM_WRITE))\r
90                         goto bad_area;\r
91         } else {\r
92                 if (!(vma->vm_flags & (VM_READ | VM_EXEC)))\r
93                         goto bad_area;\r
94         }\r
95 \r
96 survive:\r
97         /*\r
98          * If for any reason at all we couldn't handle the fault,\r
99          * make sure we exit gracefully rather than endlessly redo\r
100          * the fault.\r
101          */\r
102         switch (handle_mm_fault(mm, vma, address, write)) {\r
103         case VM_FAULT_MINOR:\r
104                 tsk->min_flt++;\r
105                 break;\r
106         case VM_FAULT_MAJOR:\r
107                 tsk->maj_flt++;\r
108                 break;\r
109         case VM_FAULT_SIGBUS:\r
110                 goto do_sigbus;\r
111         case VM_FAULT_OOM:\r
112                 goto out_of_memory;\r
113         default:\r
114                 BUG();\r
115         }\r
116 \r
117         up_read(&mm->mmap_sem);\r
118         return;\r
119 \r
120 /*\r
121  * Something tried to access memory that isn't in our memory map..\r
122  * Fix it, but check if it's kernel or user first..\r
123  */\r
124 bad_area:\r
125         up_read(&mm->mmap_sem);\r
126 \r
127 bad_area_nosemaphore:\r
128         /* User mode accesses just cause a SIGSEGV */\r
129         if (user_mode(regs)) {\r
130                 tsk->thread.cp0_badvaddr = address;\r
131                 tsk->thread.error_code = write;\r
132 #if 1\r
133                 printk("do_page_fault() #2: sending SIGSEGV to %s for "\r
134                        "invalid %s\n%0*lx (epc == %0*lx, ra == %0*lx)\n",\r
135                        tsk->comm,\r
136                        write ? "write access to" : "read access from",\r
137                        field, address,\r
138                        field, (unsigned long) regs->cp0_epc,\r
139                        field, (unsigned long) regs->regs[31]);\r
140 #endif\r
141                 info.si_signo = SIGSEGV;\r
142                 info.si_errno = 0;\r
143                 /* info.si_code has been set above */\r
144                 info.si_addr = (void *) address;\r
145                 force_sig_info(SIGSEGV, &info, tsk);\r
146                 return;\r
147         }\r
148 \r
149 no_context:\r
150         /* Are we prepared to handle this kernel fault?  */\r
151         if (fixup_exception(regs)) {\r
152                 current->thread.cp0_baduaddr = address;\r
153                 return;\r
154         }\r
155 \r
156         /*\r
157          * Oops. The kernel tried to access some bad page. We'll have to\r
158          * terminate things with extreme prejudice.\r
159          */\r
160 \r
161         bust_spinlocks(1);\r
162 \r
163         printk(KERN_ALERT "CPU %d Unable to handle kernel paging request at "\r
164                "virtual address %0*lx, epc == %0*lx, ra == %0*lx\n",\r
165                smp_processor_id(), field, address, field, regs->cp0_epc,\r
166                field,  regs->regs[31]);\r
167         die("Oops", regs);\r
168 \r
169 /*\r
170  * We ran out of memory, or some other thing happened to us that made\r
171  * us unable to handle the page fault gracefully.\r
172  */\r
173 out_of_memory:\r
174         up_read(&mm->mmap_sem);\r
175         if (tsk->pid == 1) {\r
176                 yield();\r
177                 down_read(&mm->mmap_sem);\r
178                 goto survive;\r
179         }\r
180         printk("VM: killing process %s\n", tsk->comm);\r
181         if (user_mode(regs))\r
182                 do_exit(SIGKILL);\r
183         goto no_context;\r
184 \r
185 do_sigbus:\r
186         up_read(&mm->mmap_sem);\r
187 \r
188         /* Kernel mode? Handle exceptions or die */\r
189         if (!user_mode(regs))\r
190                 goto no_context;\r
191 \r
192         /*\r
193          * Send a sigbus, regardless of whether we were in kernel\r
194          * or user mode.\r
195          */\r
196         tsk->thread.cp0_badvaddr = address;\r
197         info.si_signo = SIGBUS;\r
198         info.si_errno = 0;\r
199         info.si_code = BUS_ADRERR;\r
200         info.si_addr = (void *) address;\r
201         force_sig_info(SIGBUS, &info, tsk);\r
202 \r
203         return;\r
204 \r
205 vmalloc_fault:\r
206         {\r
207                 /*\r
208                  * Synchronize this task's top level page-table\r
209                  * with the 'reference' page table.\r
210                  *\r
211                  * Do _not_ use "tsk" here. We might be inside\r
212                  * an interrupt in the middle of a task switch..\r
213                  */\r
214                 int offset = __pgd_offset(address);\r
215                 pgd_t *pgd, *pgd_k;\r
216                 pmd_t *pmd, *pmd_k;\r
217                 pte_t *pte_k;\r
218 \r
219                 pgd = (pgd_t *) pgd_current[smp_processor_id()] + offset;\r
220                 pgd_k = init_mm.pgd + offset;\r
221 \r
222                 if (!pgd_present(*pgd_k))\r
223                         goto no_context;\r
224                 set_pgd(pgd, *pgd_k);\r
225 \r
226                 pmd = pmd_offset(pgd, address);\r
227                 pmd_k = pmd_offset(pgd_k, address);\r
228                 if (!pmd_present(*pmd_k))\r
229                         goto no_context;\r
230                 set_pmd(pmd, *pmd_k);\r
231 \r
232                 pte_k = pte_offset_kernel(pmd_k, address);\r
233                 if (!pte_present(*pte_k))\r
234                         goto no_context;\r
235                 return;\r
236         }\r
237 }\r