A Total Overhaul on the Lunaix's Virtual Memory Model (#26)
[lunaix-os.git] / lunaix-os / kernel / mm / fault.c
1 #include <lunaix/mm/fault.h>
2 #include <lunaix/mm/pmm.h>
3 #include <lunaix/mm/region.h>
4 #include <lunaix/mm/vmm.h>
5 #include <lunaix/sched.h>
6 #include <lunaix/signal.h>
7 #include <lunaix/status.h>
8 #include <lunaix/syslog.h>
9 #include <lunaix/trace.h>
10 #include <lunaix/pcontext.h>
11
12 #include <sys/mm/mm_defs.h>
13
14 #include <klibc/string.h>
15
16 LOG_MODULE("pf")
17
18 static void
19 __gather_memaccess_info(struct fault_context* context)
20 {
21     pte_t* ptep = (pte_t*)context->fault_va;
22     ptr_t mnt = ptep_vm_mnt(ptep);
23     ptr_t refva;
24     
25     context->mm = vmspace(__current);
26
27     if (mnt < VMS_MOUNT_1) {
28         refva = (ptr_t)ptep;
29         goto done;
30     }
31
32     context->ptep_fault = true;
33     context->remote_fault = (mnt != VMS_SELF);
34     
35     if (context->remote_fault && context->mm) {
36         context->mm = context->mm->guest_mm;
37         assert(context->mm);
38     }
39
40 #if LnT_ENABLED(1)
41     ptep = (pte_t*)page_addr(ptep_pfn(ptep));
42     mnt  = ptep_vm_mnt(ptep);
43     if (mnt < VMS_MOUNT_1) {
44         refva = (ptr_t)ptep;
45         goto done;
46     }
47 #endif
48
49 #if LnT_ENABLED(2)
50     ptep = (pte_t*)page_addr(ptep_pfn(ptep));
51     mnt  = ptep_vm_mnt(ptep);
52     if (mnt < VMS_MOUNT_1) {
53         refva = (ptr_t)ptep;
54         goto done;
55     }
56 #endif
57
58 #if LnT_ENABLED(3)
59     ptep = (pte_t*)page_addr(ptep_pfn(ptep));
60     mnt  = ptep_vm_mnt(ptep);
61     if (mnt < VMS_MOUNT_1) {
62         refva = (ptr_t)ptep;
63         goto done;
64     }
65 #endif
66
67     ptep = (pte_t*)page_addr(ptep_pfn(ptep));
68     mnt  = ptep_vm_mnt(ptep);
69     
70     assert(mnt < VMS_MOUNT_1);
71     refva = (ptr_t)ptep;
72
73 done:
74     context->fault_refva = refva;
75 }
76
77 static bool
78 __prepare_fault_context(struct fault_context* fault)
79 {
80     if (!__arch_prepare_fault_context(fault)) {
81         return false;
82     }
83
84     __gather_memaccess_info(fault);
85
86     pte_t* fault_ptep      = fault->fault_ptep;
87     ptr_t  fault_va        = fault->fault_va;
88     pte_t  fault_pte       = *fault_ptep;
89     bool   kernel_vmfault  = kernel_addr(fault_va);
90     bool   kernel_refaddr  = kernel_addr(fault->fault_refva);
91     
92     // for a ptep fault, the parent page tables should match the actual
93     //  accesser permission
94     if (kernel_refaddr) {
95         ptep_alloc_hierarchy(fault_ptep, fault_va, KERNEL_DATA);
96     } else {
97         ptep_alloc_hierarchy(fault_ptep, fault_va, USER_DATA);
98     }
99
100     fault->fault_pte = fault_pte;
101     
102     if (fault->ptep_fault && !kernel_refaddr) {
103         fault->resolving = pte_setprot(fault_pte, USER_DATA);
104     } else {
105         fault->resolving = pte_setprot(fault_pte, KERNEL_DATA);
106     }
107
108     fault->kernel_vmfault = kernel_vmfault;
109     fault->kernel_access  = kernel_context(fault->ictx);
110
111     return true;
112 }
113
114 static void
115 __handle_conflict_pte(struct fault_context* fault) 
116 {
117     pte_t pte = fault->fault_pte;
118     ptr_t fault_pa  = pte_paddr(pte);
119     if (!pte_allow_user(pte)) {
120         return;
121     }
122
123     assert(pte_iswprotect(pte));
124
125     if (writable_region(fault->vmr)) {
126         // normal page fault, do COW
127         // TODO makes `vmm_dup_page` arch-independent
128         ptr_t pa = (ptr_t)vmm_dup_page(fault_pa);
129
130         pmm_free_page(fault_pa);
131         pte_t new_pte = pte_setpaddr(pte, pa);
132         new_pte = pte_mkwritable(new_pte);
133
134         fault_resolved(fault, new_pte, NO_PREALLOC);
135     }
136
137     return;
138 }
139
140
141 static void
142 __handle_anon_region(struct fault_context* fault)
143 {
144     pte_t pte = fault->resolving;
145     pte_attr_t prot = region_pteprot(fault->vmr);
146     pte = pte_setprot(pte, prot);
147
148     fault_resolved(fault, pte, 0);
149 }
150
151
152 static void
153 __handle_named_region(struct fault_context* fault)
154 {
155     struct mm_region* vmr = fault->vmr;
156     struct v_file* file = vmr->mfile;
157
158     pte_t pte       = fault->resolving;
159     ptr_t fault_va  = va_align(fault->fault_va);
160
161     u32_t mseg_off  = (fault_va - vmr->start);
162     u32_t mfile_off = mseg_off + vmr->foff;
163
164     int errno = file->ops->read_page(file->inode, (void*)fault_va, mfile_off);
165     if (errno < 0) {
166         ERROR("fail to populate page (%d)", errno);
167         return;
168     }
169
170     pte_attr_t prot = region_pteprot(vmr);
171     pte = pte_setprot(pte, prot);
172
173     fault_resolved(fault, pte, 0);
174 }
175
176 static void
177 __handle_kernel_page(struct fault_context* fault)
178 {
179     // we must ensure only ptep fault is resolvable
180     if (fault->fault_va < VMS_MOUNT_1) {
181         return;
182     }
183     
184     fault_resolved(fault, fault->resolving, 0);
185     pmm_set_attr(fault->prealloc_pa, PP_FGPERSIST);
186 }
187
188
189 static void
190 fault_prealloc_page(struct fault_context* fault)
191 {
192     if (!pte_isnull(fault->fault_pte)) {
193         return;
194     }
195
196     pte_t pte;
197     
198     pte = vmm_alloc_page(fault->fault_ptep, fault->resolving);
199     if (pte_isnull(pte)) {
200         return;
201     }
202
203     fault->resolving = pte;
204     fault->prealloc_pa = pte_paddr(fault->resolving);
205
206     pmm_set_attr(fault->prealloc_pa, 0);
207     cpu_flush_page(fault->fault_va);
208 }
209
210
211 static void noret
212 __fail_to_resolve(struct fault_context* fault)
213 {
214     if (fault->prealloc_pa) {
215         pmm_free_page(fault->prealloc_pa);
216     }
217
218     ERROR("(pid: %d) Segmentation fault on %p (%p,e=0x%x)",
219           __current->pid,
220           fault->fault_va,
221           fault->fault_instn,
222           fault->fault_data);
223
224     trace_printstack_isr(fault->ictx);
225
226     if (fault->kernel_access) {
227         // if a page fault from kernel is not resolvable, then
228         //  something must be went south
229         FATAL("unresolvable page fault");
230         unreachable;
231     }
232
233     thread_setsignal(current_thread, _SIGSEGV);
234
235     schedule();
236     fail("Unexpected return from segfault");
237
238     unreachable;
239 }
240
241 static bool
242 __try_resolve_fault(struct fault_context* fault)
243 {
244     pte_t fault_pte = fault->fault_pte;
245     if (pte_isguardian(fault_pte)) {
246         ERROR("memory region over-running");
247         return false;
248     }
249
250     if (fault->kernel_vmfault && fault->kernel_access) {
251         __handle_kernel_page(fault);
252         goto done;
253     }
254
255     assert(fault->mm);
256     vm_regions_t* vmr = &fault->mm->regions;
257     fault->vmr = region_get(vmr, fault->fault_va);
258
259     if (!fault->vmr) {
260         return false;
261     }
262
263     if (pte_isloaded(fault_pte)) {
264         __handle_conflict_pte(fault);
265     }
266     else if (anon_region(fault->vmr)) {
267         __handle_anon_region(fault);
268     }
269     else if (fault->vmr->mfile) {
270         __handle_named_region(fault);
271     }
272     else {
273         // page not present, might be a chance to introduce swap file?
274         ERROR("WIP page fault route");
275     }
276     
277 done:
278     return !!(fault->resolve_type & RESOLVE_OK);
279 }
280
281 void
282 intr_routine_page_fault(const isr_param* param)
283 {
284     if (param->depth > 10) {
285         // Too many nested fault! we must messed up something
286         // XXX should we failed silently?
287         spin();
288     }
289
290     struct fault_context fault = { .ictx = param };
291
292     if (!__prepare_fault_context(&fault)) {
293         __fail_to_resolve(&fault);
294     }
295
296     fault_prealloc_page(&fault);
297
298     if (!__try_resolve_fault(&fault)) {
299         __fail_to_resolve(&fault);
300     }
301
302     if ((fault.resolve_type & NO_PREALLOC)) {
303         if (fault.prealloc_pa) {
304             pmm_free_page(fault.prealloc_pa);
305         }
306     }
307
308     set_pte(fault.fault_ptep, fault.resolving);
309
310     cpu_flush_page(fault.fault_va);
311     cpu_flush_page((ptr_t)fault.fault_ptep);
312 }