sweep through entire page table to free up intermediate tables
[lunaix-os.git] / lunaix-os / kernel / mm / procvm.c
1 #include <lunaix/mm/procvm.h>
2 #include <lunaix/mm/valloc.h>
3 #include <lunaix/mm/region.h>
4 #include <lunaix/mm/page.h>
5 #include <lunaix/mm/mmap.h>
6 #include <lunaix/process.h>
7 #include <lunaix/syslog.h>
8
9 #include <asm/mm_defs.h>
10
11 #include <klibc/string.h>
12
13 #define alloc_pagetable_trace(ptep, pte, ord, level)                        \
14     ({                                                                      \
15         alloc_kpage_at(ptep, pte, ord);                                     \
16     })
17
18 #define free_pagetable_trace(ptep, pte, level)                              \
19     ({                                                                      \
20         struct leaflet* leaflet = pte_leaflet_aligned(pte);                 \
21         assert(leaflet_order(leaflet) == 0);                                \
22         leaflet_return(leaflet);                                            \
23         set_pte(ptep, null_pte);                                            \
24     })
25
26 struct proc_mm*
27 procvm_create(struct proc_info* proc) {
28     struct proc_mm* mm = vzalloc(sizeof(struct proc_mm));
29
30     assert(mm);
31
32     mm->heap = 0;
33     mm->proc = proc;
34
35     llist_init_head(&mm->regions);
36     return mm;
37 }
38
39 static inline unsigned int
40 __ptep_advancement(struct leaflet* leaflet, int level)
41 {
42     size_t shifts = MAX(MAX_LEVEL - level - 1, 1) * LEVEL_SHIFT;
43     return (1 << (leaflet_order(leaflet) % shifts)) - 1;
44 }
45
46 static inline int
47 __descend(ptr_t dest_mnt, ptr_t src_mnt, ptr_t va, bool alloc)
48 {
49     pte_t *dest, *src, pte;
50
51     int i = 0;
52     while (!pt_last_level(i))
53     {
54         dest = mklntep_va(i, dest_mnt, va);
55         src  = mklntep_va(i, src_mnt, va);
56         pte  = pte_at(src);
57
58         if (!pte_isloaded(pte) || pte_huge(pte)) {
59             break;
60         }
61
62         if (alloc && pte_isnull(pte_at(dest))) {
63             alloc_pagetable_trace(dest, pte, 0, i);
64         }
65
66         i++;
67     }
68
69     return i;
70 }
71
72 static inline void
73 copy_leaf(pte_t* dest, pte_t* src, pte_t pte, int level)
74 {
75     struct leaflet* leaflet;
76
77     set_pte(dest, pte);
78
79     if (!pte_isloaded(pte)) {
80         return;
81     }
82
83     leaflet = pte_leaflet(pte);
84     assert(leaflet_refcount(leaflet));
85     
86     if (leaflet_ppfn(leaflet) == pte_ppfn(pte)) {
87         leaflet_borrow(leaflet);
88     }
89 }
90
91 static inline void
92 copy_root(pte_t* dest, pte_t* src, pte_t pte, int level)
93 {
94     alloc_pagetable_trace(dest, pte, 0, level);
95 }
96
97 static void
98 vmrcpy(ptr_t dest_mnt, ptr_t src_mnt, struct mm_region* region)
99 {
100     pte_t *src, *dest;
101     ptr_t loc;
102     int level;
103     struct leaflet* leaflet;
104
105     loc  = region->start;
106     src  = mkptep_va(src_mnt, loc);
107     dest = mkptep_va(dest_mnt, loc);
108
109     level = __descend(dest_mnt, src_mnt, loc, true);
110
111     while (loc < region->end)
112     {
113         pte_t pte = *src;
114
115         if (pte_isnull(pte)) {
116             goto cont;
117         } 
118         
119         if (pt_last_level(level) || pte_huge(pte)) {
120             copy_leaf(dest, src, pte, level);
121             goto cont;
122         }
123         
124         if (!pt_last_level(level)) {
125             copy_root(dest, src, pte, level);
126
127             src = ptep_step_into(src);
128             dest = ptep_step_into(dest);
129             level++;
130
131             continue;
132         }
133         
134     cont:
135         loc += lnt_page_size(level);
136         while (ptep_vfn(src) == MAX_PTEN - 1) {
137             assert(level > 0);
138             src = ptep_step_out(src);
139             dest = ptep_step_out(dest);
140             level--;
141         }
142
143         src++;
144         dest++;
145     }
146 }
147
148 static void
149 vmrfree(ptr_t vm_mnt, struct mm_region* region)
150 {
151     pte_t *src, *end;
152     ptr_t loc;
153     int level;
154     struct leaflet* leaflet;
155
156     loc  = region->start;
157     src  = mkptep_va(vm_mnt, region->start);
158     end  = mkptep_va(vm_mnt, region->end);
159
160     level = __descend(vm_mnt, vm_mnt, loc, false);
161
162     while (src < end)
163     {
164         pte_t pte = *src;
165         ptr_t pa  = pte_paddr(pte);
166
167         if (pte_isnull(pte)) {
168             goto cont;
169         } 
170
171         if (!pt_last_level(level) && !pte_huge(pte)) {
172             src = ptep_step_into(src);
173             level++;
174
175             continue;
176         }
177
178         set_pte(src, null_pte);
179         
180         if (pte_isloaded(pte)) {
181             leaflet = pte_leaflet_aligned(pte);
182             leaflet_return(leaflet);
183
184             src += __ptep_advancement(leaflet, level);
185         }
186
187     cont:
188         while (ptep_vfn(src) == MAX_PTEN - 1) {
189             src = ptep_step_out(src);
190             free_pagetable_trace(src, pte_at(src), level);
191             
192             level--;
193         }
194
195         src++;
196     }
197 }
198
199 static void
200 vmscpy(struct proc_mm* dest_mm, struct proc_mm* src_mm)
201 {
202     // Build the self-reference on dest vms
203
204     /* 
205      *        -- What the heck are ptep_ssm and ptep_sms ? --
206      *      
207      *      ptep_dest point to the pagetable itself that is mounted
208      *          at dest_mnt (or simply mnt): 
209      *              mnt -> self -> self -> self -> L0TE@offset
210      * 
211      *      ptep_sms shallowed the recursion chain:
212      *              self -> mnt -> self -> self -> L0TE@self
213      * 
214      *      ptep_ssm shallowed the recursion chain:
215      *              self -> self -> mnt -> self -> L0TE@self
216      *      
217      *      Now, here is the problem, back to x86_32, the translation is 
218      *      a depth-3 recursion:
219      *              L0T -> LFT -> Page
220      *      
221      *      So ptep_ssm will terminate at mnt and give us a leaf
222      *      slot for allocate a fresh page table for mnt:
223      *              self -> self -> L0TE@mnt
224      * 
225      *      but in x86_64 translation has extra two more step:
226      *              L0T -> L1T -> L2T -> LFT -> Page
227      *      
228      *      So we must continue push down.... 
229      *      ptep_sssms shallowed the recursion chain:
230      *              self -> self -> self -> mnt  -> L0TE@self
231      * 
232      *      ptep_ssssm shallowed the recursion chain:
233      *              self -> self -> self -> self -> L0TE@mnt
234      * 
235      *      Note: PML4: 2 extra steps
236      *            PML5: 3 extra steps
237     */
238
239     ptr_t  dest_mnt, src_mnt;
240     
241     dest_mnt = dest_mm->vm_mnt;
242     assert(dest_mnt);
243
244     pte_t* ptep_ssm     = mkl0tep_va(VMS_SELF, dest_mnt);
245     pte_t* ptep_smx     = mkl1tep_va(VMS_SELF, dest_mnt);
246     pte_t  pte_sms      = mkpte_prot(KERNEL_PGTAB);
247
248     pte_sms = alloc_pagetable_trace(ptep_ssm, pte_sms, 0, 0);
249     set_pte(&ptep_smx[VMS_SELF_L0TI], pte_sms);
250     
251     tlb_flush_kernel((ptr_t)dest_mnt);
252
253     if (!src_mm) {
254         goto done;
255     }
256
257     src_mnt = src_mm->vm_mnt;
258
259     struct mm_region *pos, *n;
260     llist_for_each(pos, n, &src_mm->regions, head)
261     {
262         vmrcpy(dest_mnt, src_mnt, pos);
263     }
264
265 done:;
266     procvm_link_kernel(dest_mnt);
267     
268     dest_mm->vmroot = pte_paddr(pte_sms);
269 }
270
271 static void
272 __purge_vms_residual(struct proc_mm* mm, int level, ptr_t va)
273 {
274     pte_t *ptep, pte;
275     ptr_t _va;
276
277     if (level >= MAX_LEVEL) {
278         return;
279     }
280
281     ptep = mklntep_va(level, mm->vm_mnt, va);
282
283     for (unsigned i = 0; i < LEVEL_SIZE; i++, ptep++) 
284     {
285         pte = pte_at(ptep);
286         if (pte_isnull(pte) || !pte_isloaded(pte)) {
287             continue;
288         }
289
290         if (lntep_implie_vmnts(ptep, lnt_page_size(level))) {
291             continue;
292         }
293
294         _va = va + (i * lnt_page_size(level));
295         __purge_vms_residual(mm, level + 1, _va);
296         
297         set_pte(ptep, null_pte);
298         leaflet_return(pte_leaflet_aligned(pte));
299     }
300 }
301
302 static void
303 vmsfree(struct proc_mm* mm)
304 {
305     struct leaflet* leaflet;
306     struct mm_region *pos, *n;
307     ptr_t vm_mnt;
308     pte_t* ptep_self;
309     
310     vm_mnt    = mm->vm_mnt;
311     ptep_self = mkl0tep_va(vm_mnt, VMS_SELF);
312
313     // first pass: free region mappings
314     llist_for_each(pos, n, &mm->regions, head)
315     {
316         vmrfree(vm_mnt, pos);
317     }
318
319     procvm_unlink_kernel(vm_mnt);
320
321     // free up all allocated tables on intermediate levels
322     __purge_vms_residual(mm, 0, 0);
323
324     free_pagetable_trace(ptep_self, pte_at(ptep_self), 0);
325 }
326
327 static inline void
328 __attach_to_current_vms(struct proc_mm* guest_mm)
329 {
330     struct proc_mm* mm_current = vmspace(__current);
331     if (mm_current) {
332         assert(!mm_current->guest_mm);
333         mm_current->guest_mm = guest_mm;
334     }
335 }
336
337 static inline void
338 __detach_from_current_vms(struct proc_mm* guest_mm)
339 {
340     struct proc_mm* mm_current = vmspace(__current);
341     if (mm_current) {
342         assert(mm_current->guest_mm == guest_mm);
343         mm_current->guest_mm = NULL;
344     }
345 }
346
347 void
348 procvm_prune_vmr(ptr_t vm_mnt, struct mm_region* region)
349 {
350     vmrfree(vm_mnt, region);
351 }
352
353 void
354 procvm_dupvms_mount(struct proc_mm* mm) {
355     assert(__current);
356     assert(!mm->vm_mnt);
357
358     struct proc_mm* mm_current = vmspace(__current);
359     
360     __attach_to_current_vms(mm);
361    
362     mm->heap = mm_current->heap;
363     mm->vm_mnt = VMS_MOUNT_1;
364     
365     vmscpy(mm, mm_current);  
366     region_copy_mm(mm_current, mm);
367 }
368
369 void
370 procvm_mount(struct proc_mm* mm)
371 {
372     // if current mm is already active
373     if (active_vms(mm->vm_mnt)) {
374         return;
375     }
376     
377     // we are double mounting
378     assert(!mm->vm_mnt);
379     assert(mm->vmroot);
380
381     vms_mount(VMS_MOUNT_1, mm->vmroot);
382
383     __attach_to_current_vms(mm);
384
385     mm->vm_mnt = VMS_MOUNT_1;
386 }
387
388 void
389 procvm_unmount(struct proc_mm* mm)
390 {
391     if (active_vms(mm->vm_mnt)) {
392         return;
393     }
394     
395     assert(mm->vm_mnt);
396     vms_unmount(VMS_MOUNT_1);
397     
398     struct proc_mm* mm_current = vmspace(__current);
399     if (mm_current) {
400         mm_current->guest_mm = NULL;
401     }
402
403     mm->vm_mnt = 0;
404 }
405
406 void
407 procvm_initvms_mount(struct proc_mm* mm)
408 {
409     assert(!mm->vm_mnt);
410
411     __attach_to_current_vms(mm);
412
413     mm->vm_mnt = VMS_MOUNT_1;
414     vmscpy(mm, NULL);
415 }
416
417 void
418 procvm_unmount_release(struct proc_mm* mm) {
419     ptr_t vm_mnt = mm->vm_mnt;
420     struct mm_region *pos, *n;
421
422     llist_for_each(pos, n, &mm->regions, head)
423     {
424         mem_sync_pages(vm_mnt, pos, pos->start, pos->end - pos->start, 0);
425     }
426
427     vmsfree(mm);
428
429     llist_for_each(pos, n, &mm->regions, head)
430     {
431         region_release(pos);
432     }
433
434     vms_unmount(vm_mnt);
435     vfree(mm);
436
437     __detach_from_current_vms(mm);
438 }
439
440 void
441 procvm_mount_self(struct proc_mm* mm) 
442 {
443     assert(!mm->vm_mnt);
444
445     mm->vm_mnt = VMS_SELF;
446 }
447
448 void
449 procvm_unmount_self(struct proc_mm* mm)
450 {
451     assert(active_vms(mm->vm_mnt));
452
453     mm->vm_mnt = 0;
454 }
455
456 ptr_t
457 procvm_enter_remote(struct remote_vmctx* rvmctx, struct proc_mm* mm, 
458                     ptr_t remote_base, size_t size)
459 {
460     ptr_t vm_mnt = mm->vm_mnt;
461     assert(vm_mnt);
462     
463     pfn_t size_pn = pfn(size + PAGE_SIZE);
464     assert(size_pn < REMOTEVM_MAX_PAGES);
465
466     struct mm_region* region = region_get(&mm->regions, remote_base);
467     assert(region && region_contains(region, remote_base + size));
468
469     rvmctx->vms_mnt = vm_mnt;
470     rvmctx->page_cnt = size_pn;
471
472     remote_base = page_aligned(remote_base);
473     rvmctx->remote = remote_base;
474     rvmctx->local_mnt = PG_MOUNT_VAR;
475
476     pte_t* rptep = mkptep_va(vm_mnt, remote_base);
477     pte_t* lptep = mkptep_va(VMS_SELF, rvmctx->local_mnt);
478
479     pte_t pte, rpte = null_pte;
480     rpte = region_tweakpte(region, rpte);
481
482     for (size_t i = 0; i < size_pn; i++)
483     {
484         pte = vmm_tryptep(rptep, PAGE_SIZE);
485         if (pte_isloaded(pte)) {
486             set_pte(lptep, pte);
487             continue;
488         }
489
490         ptr_t pa = ppage_addr(pmm_alloc_normal(0));
491         set_pte(lptep, mkpte(pa, KERNEL_DATA));
492         set_pte(rptep, pte_setpaddr(rpte, pa));
493     }
494
495     return vm_mnt;
496     
497 }
498
499 int
500 procvm_copy_remote_transaction(struct remote_vmctx* rvmctx, 
501                    ptr_t remote_dest, void* local_src, size_t sz)
502 {
503     if (remote_dest < rvmctx->remote) {
504         return -1;
505     }
506
507     ptr_t offset = remote_dest - rvmctx->remote;
508     if (pfn(offset + sz) >= rvmctx->page_cnt) {
509         return -1;
510     }
511
512     memcpy((void*)(rvmctx->local_mnt + offset), local_src, sz);
513
514     return sz;
515 }
516
517 void
518 procvm_exit_remote(struct remote_vmctx* rvmctx)
519 {
520     pte_t* lptep = mkptep_va(VMS_SELF, rvmctx->local_mnt);
521     vmm_unset_ptes(lptep, rvmctx->page_cnt);
522 }