493d26cf3fe0876df64b1d5594f64fcfda67bd01
[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 <asm/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->flen = param->flen;
236     region->foff = param->offset;
237     region->proc_vms = param->pvms;
238
239     region_add(vm_regions, region);
240     
241     if (file) {
242         vfs_ref_file(file);
243     }
244
245     if (addr_out) {
246         *addr_out = (void*)found_loc;
247     }
248     if (created) {
249         *created = region;
250     }
251     return 0;
252 }
253
254 int
255 mem_remap(void** addr_out,
256           struct mm_region** remapped,
257           void* addr,
258           struct v_file* file,
259           struct mmap_param* param)
260 {
261     // TODO
262
263     return EINVAL;
264 }
265
266 void
267 mem_sync_pages(ptr_t mnt,
268                struct mm_region* region,
269                ptr_t start,
270                ptr_t length,
271                int options)
272 {
273     if (!region->mfile || !(region->attr & REGION_WSHARED)) {
274         return;
275     }
276     
277     pte_t* ptep = mkptep_va(mnt, start);
278     ptr_t va    = page_aligned(start);
279
280     for (; va < start + length; va += PAGE_SIZE, ptep++) {
281         pte_t pte = vmm_tryptep(ptep, LFT_SIZE);
282         if (pte_isnull(pte)) {
283             continue;
284         }
285
286         if (pte_dirty(pte)) {
287             size_t offset = va - region->start + region->foff;
288             struct v_inode* inode = region->mfile->inode;
289
290             region->mfile->ops->write_page(inode, (void*)va, offset);
291
292             set_pte(ptep, pte_mkclean(pte));
293             tlb_flush_vmr(region, va);
294             
295         } else if ((options & MS_INVALIDATE)) {
296             goto invalidate;
297         }
298
299         if (options & MS_INVALIDATE_ALL) {
300             goto invalidate;
301         }
302
303         continue;
304
305         // FIXME what if mem_sync range does not aligned with
306         //       a leaflet with order > 1
307     invalidate:
308         set_pte(ptep, null_pte);
309         leaflet_return(pte_leaflet(pte));
310         tlb_flush_vmr(region, va);
311     }
312 }
313
314 int
315 mem_msync(ptr_t mnt,
316           vm_regions_t* regions,
317           ptr_t addr,
318           size_t length,
319           int options)
320 {
321     struct mm_region* pos = list_entry(regions->next, struct mm_region, head);
322     while (length && (ptr_t)&pos->head != (ptr_t)regions) {
323         if (pos->end >= addr && pos->start <= addr) {
324             size_t l = MIN(length, pos->end - addr);
325             mem_sync_pages(mnt, pos, addr, l, options);
326
327             addr += l;
328             length -= l;
329         }
330         pos = list_entry(pos->head.next, struct mm_region, head);
331     }
332
333     if (length) {
334         return ENOMEM;
335     }
336
337     return 0;
338 }
339
340 void
341 mem_unmap_region(ptr_t mnt, struct mm_region* region)
342 {
343     if (!region) {
344         return;
345     }
346     
347     valloc_ensure_valid(region);
348     
349     pfn_t pglen = leaf_count(region->end - region->start);
350     mem_sync_pages(mnt, region, region->start, pglen * PAGE_SIZE, 0);
351
352     pte_t* ptep = mkptep_va(mnt, region->start);
353     __remove_ranged_mappings(ptep, pglen);
354
355     tlb_flush_vmr_all(region);
356     
357     llist_delete(&region->head);
358     region_release(region);
359 }
360
361 // Case: head inseted, tail inseted
362 #define CASE_HITI(vmr, addr, len)                                              \
363     ((vmr)->start <= (addr) && ((addr) + (len)) <= (vmr)->end)
364
365 // Case: head inseted, tail extruded
366 #define CASE_HITE(vmr, addr, len)                                              \
367     ((vmr)->start <= (addr) && ((addr) + (len)) > (vmr)->end)
368
369 // Case: head extruded, tail inseted
370 #define CASE_HETI(vmr, addr, len)                                              \
371     ((vmr)->start > (addr) && ((addr) + (len)) <= (vmr)->end)
372
373 // Case: head extruded, tail extruded
374 #define CASE_HETE(vmr, addr, len)                                              \
375     ((vmr)->start > (addr) && ((addr) + (len)) > (vmr)->end)
376
377 static void
378 __unmap_overlapped_cases(ptr_t mnt,
379                          struct mm_region* vmr,
380                          ptr_t* addr,
381                          size_t* length)
382 {
383     // seg start, umapped segement start
384     ptr_t seg_start = *addr, umps_start = 0;
385
386     // seg len, umapped segement len
387     size_t seg_len = *length, umps_len = 0;
388
389     size_t displ = 0, shrink = 0;
390
391     if (CASE_HITI(vmr, seg_start, seg_len)) {
392         size_t new_start = seg_start + seg_len;
393
394         // Require a split
395         if (new_start < vmr->end) {
396             struct mm_region* region = region_dup(vmr);
397             if (region->mfile) {
398                 size_t f_shifted = new_start - region->start;
399                 region->foff += f_shifted;
400             }
401             region->start = new_start;
402             llist_insert_after(&vmr->head, &region->head);
403         }
404
405         shrink = vmr->end - seg_start;
406         umps_len = shrink;
407         umps_start = seg_start;
408     } 
409     else if (CASE_HITE(vmr, seg_start, seg_len)) {
410         shrink = vmr->end - seg_start;
411         umps_len = shrink;
412         umps_start = seg_start;
413     } 
414     else if (CASE_HETI(vmr, seg_start, seg_len)) {
415         displ = seg_len - (vmr->start - seg_start);
416         umps_len = displ;
417         umps_start = vmr->start;
418     } 
419     else if (CASE_HETE(vmr, seg_start, seg_len)) {
420         shrink = vmr->end - vmr->start;
421         umps_len = shrink;
422         umps_start = vmr->start;
423     }
424
425     mem_sync_pages(mnt, vmr, vmr->start, umps_len, 0);
426
427     pte_t *ptep = mkptep_va(mnt, vmr->start);
428     __remove_ranged_mappings(ptep, leaf_count(umps_len));
429
430     tlb_flush_vmr_range(vmr, vmr->start, umps_len);
431
432     vmr->start += displ;
433     vmr->end -= shrink;
434
435     if (vmr->start >= vmr->end) {
436         llist_delete(&vmr->head);
437         region_release(vmr);
438     } else if (vmr->mfile) {
439         vmr->foff += displ;
440     }
441
442     *addr = umps_start + umps_len;
443
444     size_t ump_len = *addr - seg_start;
445     *length = MAX(seg_len, ump_len) - ump_len;
446 }
447
448 int
449 mem_unmap(ptr_t mnt, vm_regions_t* regions, ptr_t addr, size_t length)
450 {
451     length = ROUNDUP(length, PAGE_SIZE);
452     ptr_t cur_addr = page_aligned(addr);
453     struct mm_region *pos, *n;
454
455     llist_for_each(pos, n, regions, head)
456     {
457         u32_t l = pos->start - cur_addr;
458         if ((pos->start <= cur_addr && cur_addr < pos->end) || l <= length) {
459             break;
460         }
461     }
462
463     size_t remaining = length;
464     while (&pos->head != regions && remaining) {
465         n = container_of(pos->head.next, typeof(*pos), head);
466         if (pos->start > cur_addr + length) {
467             break;
468         }
469
470         __unmap_overlapped_cases(mnt, pos, &cur_addr, &remaining);
471
472         pos = n;
473     }
474
475     return 0;
476 }
477
478 __DEFINE_LXSYSCALL1(void*, sys_mmap, struct usr_mmap_param*, mparam)
479 {
480     off_t offset;
481     size_t length;
482     int proct, fd, options;
483     int errno;
484     void* result;
485     ptr_t addr_ptr;
486
487     proct = mparam->proct;
488     fd = mparam->fd;
489     offset = mparam->offset;
490     options = mparam->flags;
491     addr_ptr = __ptr(mparam->addr);
492     length = mparam->length;
493
494     errno  = 0;
495     result = (void*)-1;
496
497     if (!length || length > BS_SIZE || va_offset(addr_ptr)) {
498         errno = EINVAL;
499         goto done;
500     }
501
502     if (!addr_ptr) {
503         addr_ptr = USR_MMAP;
504     } else if (addr_ptr < USR_MMAP || addr_ptr + length >= USR_MMAP_END) {
505         if (!(options & (MAP_FIXED | MAP_FIXED_NOREPLACE))) {
506             errno = ENOMEM;
507             goto done;
508         }
509     }
510
511     struct v_file* file = NULL;
512
513     if (!(options & MAP_ANON)) {
514         struct v_fd* vfd;
515         if ((errno = vfs_getfd(fd, &vfd))) {
516             goto done;
517         }
518
519         file = vfd->file;
520         if (!file->ops->read_page) {
521             errno = ENODEV;
522             goto done;
523         }
524     }
525
526     length = ROUNDUP(length, PAGE_SIZE);
527     struct mmap_param param = { .flags = options,
528                                 .mlen = length,
529                                 .flen = length,
530                                 .offset = offset,
531                                 .type = REGION_TYPE_GENERAL,
532                                 .proct = proct,
533                                 .pvms = vmspace(__current),
534                                 .vms_mnt = VMS_SELF };
535
536     errno = mmap_user(&result, NULL, addr_ptr, file, &param);
537
538 done:
539     syscall_result(errno);
540     return result;
541 }
542
543 __DEFINE_LXSYSCALL2(int, munmap, void*, addr, size_t, length)
544 {
545     return mem_unmap(
546       VMS_SELF, vmregions(__current), (ptr_t)addr, length);
547 }
548
549 __DEFINE_LXSYSCALL3(int, msync, void*, addr, size_t, length, int, flags)
550 {
551     if (va_offset((ptr_t)addr) || ((flags & MS_ASYNC) && (flags & MS_SYNC))) {
552         return DO_STATUS(EINVAL);
553     }
554
555     int status = mem_msync(VMS_SELF,
556                            vmregions(__current),
557                            (ptr_t)addr,
558                            length,
559                            flags);
560
561     return DO_STATUS(status);
562 }