Unifying the Lunaix's Physical Memory Model (#28)
[lunaix-os.git] / lunaix-os / kernel / mm / mmap.c
1 #include <lunaix/mm/mmap.h>
2 #include <lunaix/mm/page.h>
3 #include <lunaix/mm/valloc.h>
4 #include <lunaix/spike.h>
5 #include <lunaix/syscall.h>
6 #include <lunaix/syscall_utils.h>
7
8 #include <sys/mm/mm_defs.h>
9
10 #include <usr/lunaix/mann_flags.h>
11
12 // any size beyond this is bullshit
13 #define BS_SIZE (KERNEL_RESIDENT - USR_MMAP)
14
15 int
16 mem_has_overlap(vm_regions_t* regions, ptr_t start, ptr_t end)
17 {
18     struct mm_region *pos, *n;
19     llist_for_each(pos, n, regions, head)
20     {
21         if (pos->end >= start && pos->start < start) {
22             return 1;
23         }
24
25         if (pos->end <= end && pos->start >= start) {
26             return 1;
27         }
28
29         if (pos->end >= end && pos->start < end) {
30             return 1;
31         }
32     }
33
34     return 0;
35 }
36
37 int
38 mem_adjust_inplace(vm_regions_t* regions,
39                    struct mm_region* region,
40                    ptr_t newend)
41 {
42     ssize_t len = newend - region->start;
43     if (len == 0) {
44         return 0;
45     }
46
47     if (len < 0) {
48         return EINVAL;
49     }
50
51     if (mem_has_overlap(regions, region->start, newend)) {
52         return ENOMEM;
53     }
54
55     region->end = newend;
56
57     return 0;
58 }
59
60 int 
61 mmap_user(void** addr_out,
62         struct mm_region** created,
63         ptr_t addr,
64         struct v_file* file,
65         struct mmap_param* param) 
66 {
67     param->range_end = KERNEL_RESIDENT;
68     param->range_start = USR_EXEC;
69
70     return mem_map(addr_out, created, addr, file, param);
71 }
72
73 static void
74 __remove_ranged_mappings(pte_t* ptep, size_t npages)
75 {
76     struct leaflet* leaflet;
77     pte_t pte; 
78     for (size_t i = 0, n = 0; i < npages; i++, ptep++) {
79         pte = pte_at(ptep);
80
81         set_pte(ptep, null_pte);
82         if (!pte_isloaded(pte)) {
83             continue;
84         }
85
86         leaflet = pte_leaflet_aligned(pte);
87         leaflet_return(leaflet);
88
89         n = ptep_unmap_leaflet(ptep, leaflet) - 1;
90         i += n;
91         ptep += n;
92     }
93 }
94
95 static ptr_t
96 __mem_find_slot_backward(struct mm_region* lead, struct mmap_param* param, struct mm_region* anchor)
97 {
98     ptr_t size = param->mlen;
99     struct mm_region *pos = anchor, 
100                      *n = next_region(pos);
101     while (pos != lead)
102     {
103         if (pos == lead) {
104             break;
105         }
106
107         ptr_t end = n->start;
108         if (n == lead) {
109             end = param->range_end;
110         }
111
112         if (end - pos->end >= size) {
113             return pos->end;
114         }
115
116         pos = n;
117         n = next_region(pos);
118     }
119     
120     return 0;
121 }
122
123 static ptr_t
124 __mem_find_slot_forward(struct mm_region* lead, struct mmap_param* param, struct mm_region* anchor)
125 {
126     ptr_t size = param->mlen;
127     struct mm_region *pos = anchor, 
128                      *prev = prev_region(pos);
129     while (lead != pos)
130     {
131         ptr_t end = prev->end;
132         if (prev == lead) {
133             end = param->range_start;
134         }
135
136         if (pos->start - end >= size) {
137             return pos->start - size;
138         }
139
140         pos = prev;
141         prev = prev_region(pos);
142     }
143
144     return 0;
145 }
146
147 static ptr_t
148 __mem_find_slot(vm_regions_t* lead, struct mmap_param* param, struct mm_region* anchor)
149 {
150     ptr_t result = 0;
151     struct mm_region* _lead = get_region(lead);
152     if ((result = __mem_find_slot_backward(_lead, param, anchor))) {
153         return result;
154     }
155
156     return __mem_find_slot_forward(_lead, param, anchor);
157 }
158
159 static struct mm_region*
160 __mem_find_nearest(vm_regions_t* lead, ptr_t addr)
161 {   
162     ptr_t min_dist = (ptr_t)-1;
163     struct mm_region *pos, *n, *min = NULL;
164     llist_for_each(pos, n, lead, head) {
165         if (region_contains(pos, addr)) {
166             return pos;
167         }
168
169         ptr_t dist = addr - pos->end;
170         if (addr < pos->start) {
171             dist = pos->start - addr;
172         }
173
174         if (dist < min_dist) {
175             min_dist = dist;
176             min = pos;
177         }
178     }
179
180     return min;
181 }
182
183 int
184 mem_map(void** addr_out,
185         struct mm_region** created,
186         ptr_t addr,
187         struct v_file* file,
188         struct mmap_param* param)
189 {
190     assert_msg(addr, "addr can not be NULL");
191
192     ptr_t last_end = USR_EXEC, found_loc = page_aligned(addr);
193     struct mm_region *pos, *n;
194
195     vm_regions_t* vm_regions = &param->pvms->regions;
196
197     if ((param->flags & MAP_FIXED_NOREPLACE)) {
198         if (mem_has_overlap(vm_regions, found_loc, param->mlen + found_loc)) {
199             return EEXIST;
200         }
201         goto found;
202     }
203
204     if ((param->flags & MAP_FIXED)) {
205         int status =
206           mem_unmap(param->vms_mnt, vm_regions, found_loc, param->mlen);
207         if (status) {
208             return status;
209         }
210         goto found;
211     }
212
213     if (llist_empty(vm_regions)) {
214         goto found;
215     }
216
217     struct mm_region* anchor = __mem_find_nearest(vm_regions, found_loc);
218     if ((found_loc = __mem_find_slot(vm_regions, param, anchor))) {
219         goto found;
220     }
221
222     return ENOMEM;
223
224 found:
225     if (found_loc >= param->range_end || found_loc < param->range_start) {
226         return ENOMEM;
227     }
228
229     struct mm_region* region = region_create_range(
230       found_loc,
231       param->mlen,
232       ((param->proct | param->flags) & 0x3f) | (param->type & ~0xffff));
233
234     region->mfile = file;
235     region->foff = param->offset;
236     region->proc_vms = param->pvms;
237
238     region_add(vm_regions, region);
239     
240     if (file) {
241         vfs_ref_file(file);
242     }
243
244     if (addr_out) {
245         *addr_out = (void*)found_loc;
246     }
247     if (created) {
248         *created = region;
249     }
250     return 0;
251 }
252
253 int
254 mem_remap(void** addr_out,
255           struct mm_region** remapped,
256           void* addr,
257           struct v_file* file,
258           struct mmap_param* param)
259 {
260     // TODO
261
262     return EINVAL;
263 }
264
265 void
266 mem_sync_pages(ptr_t mnt,
267                struct mm_region* region,
268                ptr_t start,
269                ptr_t length,
270                int options)
271 {
272     if (!region->mfile || !(region->attr & REGION_WSHARED)) {
273         return;
274     }
275     
276     pte_t* ptep = mkptep_va(mnt, start);
277     ptr_t va    = page_aligned(start);
278
279     for (; va < start + length; va += PAGE_SIZE, ptep++) {
280         pte_t pte = vmm_tryptep(ptep, LFT_SIZE);
281         if (pte_isnull(pte)) {
282             continue;
283         }
284
285         if (pte_dirty(pte)) {
286             size_t offset = va - region->start + region->foff;
287             struct v_inode* inode = region->mfile->inode;
288
289             region->mfile->ops->write_page(inode, (void*)va, offset);
290
291             set_pte(ptep, pte_mkclean(pte));
292             tlb_flush_vmr(region, va);
293             
294         } else if ((options & MS_INVALIDATE)) {
295             goto invalidate;
296         }
297
298         if (options & MS_INVALIDATE_ALL) {
299             goto invalidate;
300         }
301
302         continue;
303
304         // FIXME what if mem_sync range does not aligned with
305         //       a leaflet with order > 1
306     invalidate:
307         set_pte(ptep, null_pte);
308         leaflet_return(pte_leaflet(pte));
309         tlb_flush_vmr(region, va);
310     }
311 }
312
313 int
314 mem_msync(ptr_t mnt,
315           vm_regions_t* regions,
316           ptr_t addr,
317           size_t length,
318           int options)
319 {
320     struct mm_region* pos = list_entry(regions->next, struct mm_region, head);
321     while (length && (ptr_t)&pos->head != (ptr_t)regions) {
322         if (pos->end >= addr && pos->start <= addr) {
323             size_t l = MIN(length, pos->end - addr);
324             mem_sync_pages(mnt, pos, addr, l, options);
325
326             addr += l;
327             length -= l;
328         }
329         pos = list_entry(pos->head.next, struct mm_region, head);
330     }
331
332     if (length) {
333         return ENOMEM;
334     }
335
336     return 0;
337 }
338
339 void
340 mem_unmap_region(ptr_t mnt, struct mm_region* region)
341 {
342     if (!region) {
343         return;
344     }
345     
346     valloc_ensure_valid(region);
347     
348     pfn_t pglen = leaf_count(region->end - region->start);
349     mem_sync_pages(mnt, region, region->start, pglen * PAGE_SIZE, 0);
350
351     pte_t* ptep = mkptep_va(mnt, region->start);
352     __remove_ranged_mappings(ptep, pglen);
353
354     tlb_flush_vmr_all(region);
355     
356     llist_delete(&region->head);
357     region_release(region);
358 }
359
360 // Case: head inseted, tail inseted
361 #define CASE_HITI(vmr, addr, len)                                              \
362     ((vmr)->start <= (addr) && ((addr) + (len)) <= (vmr)->end)
363
364 // Case: head inseted, tail extruded
365 #define CASE_HITE(vmr, addr, len)                                              \
366     ((vmr)->start <= (addr) && ((addr) + (len)) > (vmr)->end)
367
368 // Case: head extruded, tail inseted
369 #define CASE_HETI(vmr, addr, len)                                              \
370     ((vmr)->start > (addr) && ((addr) + (len)) <= (vmr)->end)
371
372 // Case: head extruded, tail extruded
373 #define CASE_HETE(vmr, addr, len)                                              \
374     ((vmr)->start > (addr) && ((addr) + (len)) > (vmr)->end)
375
376 static void
377 __unmap_overlapped_cases(ptr_t mnt,
378                          struct mm_region* vmr,
379                          ptr_t* addr,
380                          size_t* length)
381 {
382     // seg start, umapped segement start
383     ptr_t seg_start = *addr, umps_start = 0;
384
385     // seg len, umapped segement len
386     size_t seg_len = *length, umps_len = 0;
387
388     size_t displ = 0, shrink = 0;
389
390     if (CASE_HITI(vmr, seg_start, seg_len)) {
391         size_t new_start = seg_start + seg_len;
392
393         // Require a split
394         if (new_start < vmr->end) {
395             struct mm_region* region = region_dup(vmr);
396             if (region->mfile) {
397                 size_t f_shifted = new_start - region->start;
398                 region->foff += f_shifted;
399             }
400             region->start = new_start;
401             llist_insert_after(&vmr->head, &region->head);
402         }
403
404         shrink = vmr->end - seg_start;
405         umps_len = shrink;
406         umps_start = seg_start;
407     } 
408     else if (CASE_HITE(vmr, seg_start, seg_len)) {
409         shrink = vmr->end - seg_start;
410         umps_len = shrink;
411         umps_start = seg_start;
412     } 
413     else if (CASE_HETI(vmr, seg_start, seg_len)) {
414         displ = seg_len - (vmr->start - seg_start);
415         umps_len = displ;
416         umps_start = vmr->start;
417     } 
418     else if (CASE_HETE(vmr, seg_start, seg_len)) {
419         shrink = vmr->end - vmr->start;
420         umps_len = shrink;
421         umps_start = vmr->start;
422     }
423
424     mem_sync_pages(mnt, vmr, vmr->start, umps_len, 0);
425
426     pte_t *ptep = mkptep_va(mnt, vmr->start);
427     __remove_ranged_mappings(ptep, leaf_count(umps_len));
428
429     tlb_flush_vmr_range(vmr, vmr->start, umps_len);
430
431     vmr->start += displ;
432     vmr->end -= shrink;
433
434     if (vmr->start >= vmr->end) {
435         llist_delete(&vmr->head);
436         region_release(vmr);
437     } else if (vmr->mfile) {
438         vmr->foff += displ;
439     }
440
441     *addr = umps_start + umps_len;
442
443     size_t ump_len = *addr - seg_start;
444     *length = MAX(seg_len, ump_len) - ump_len;
445 }
446
447 int
448 mem_unmap(ptr_t mnt, vm_regions_t* regions, ptr_t addr, size_t length)
449 {
450     length = ROUNDUP(length, PAGE_SIZE);
451     ptr_t cur_addr = page_aligned(addr);
452     struct mm_region *pos, *n;
453
454     llist_for_each(pos, n, regions, head)
455     {
456         u32_t l = pos->start - cur_addr;
457         if ((pos->start <= cur_addr && cur_addr < pos->end) || l <= length) {
458             break;
459         }
460     }
461
462     while (&pos->head != regions && length) {
463         n = container_of(pos->head.next, typeof(*pos), head);
464         __unmap_overlapped_cases(mnt, pos, &cur_addr, &length);
465
466         pos = n;
467     }
468
469     return 0;
470 }
471
472 __DEFINE_LXSYSCALL3(void*, sys_mmap, void*, addr, size_t, length, va_list, lst)
473 {
474     int proct = va_arg(lst, int);
475     int fd = va_arg(lst, u32_t);
476     off_t offset = va_arg(lst, off_t);
477     int options = va_arg(lst, int);
478     int errno = 0;
479     void* result = (void*)-1;
480
481     ptr_t addr_ptr = (ptr_t)addr;
482
483     if (!length || length > BS_SIZE || va_offset(addr_ptr)) {
484         errno = EINVAL;
485         goto done;
486     }
487
488     if (!addr_ptr) {
489         addr_ptr = USR_MMAP;
490     } else if (addr_ptr < USR_MMAP || addr_ptr + length >= USR_MMAP_END) {
491         if (!(options & (MAP_FIXED | MAP_FIXED_NOREPLACE))) {
492             errno = ENOMEM;
493             goto done;
494         }
495     }
496
497     struct v_file* file = NULL;
498
499     if (!(options & MAP_ANON)) {
500         struct v_fd* vfd;
501         if ((errno = vfs_getfd(fd, &vfd))) {
502             goto done;
503         }
504
505         file = vfd->file;
506         if (!file->ops->read_page) {
507             errno = ENODEV;
508             goto done;
509         }
510     }
511
512     struct mmap_param param = { .flags = options,
513                                 .mlen = ROUNDUP(length, PAGE_SIZE),
514                                 .offset = offset,
515                                 .type = REGION_TYPE_GENERAL,
516                                 .proct = proct,
517                                 .pvms = vmspace(__current),
518                                 .vms_mnt = VMS_SELF };
519
520     errno = mmap_user(&result, NULL, addr_ptr, file, &param);
521
522 done:
523     syscall_result(errno);
524     return result;
525 }
526
527 __DEFINE_LXSYSCALL2(int, munmap, void*, addr, size_t, length)
528 {
529     return mem_unmap(
530       VMS_SELF, vmregions(__current), (ptr_t)addr, length);
531 }
532
533 __DEFINE_LXSYSCALL3(int, msync, void*, addr, size_t, length, int, flags)
534 {
535     if (va_offset((ptr_t)addr) || ((flags & MS_ASYNC) && (flags & MS_SYNC))) {
536         return DO_STATUS(EINVAL);
537     }
538
539     int status = mem_msync(VMS_SELF,
540                            vmregions(__current),
541                            (ptr_t)addr,
542                            length,
543                            flags);
544
545     return DO_STATUS(status);
546 }