Merge branch 'interrupt-rework' into prog-loader
[lunaix-os.git] / lunaix-os / kernel / mm / mmap.c
1 #include <lunaix/mm/mmap.h>
2 #include <lunaix/mm/pmm.h>
3 #include <lunaix/mm/valloc.h>
4 #include <lunaix/mm/vmm.h>
5 #include <lunaix/spike.h>
6
7 #include <lunaix/syscall.h>
8 #include <lunaix/syscall_utils.h>
9
10 // any size beyond this is bullshit
11 #define BS_SIZE (KERNEL_MM_BASE - UMMAP_START)
12
13 int
14 mem_has_overlap(vm_regions_t* regions, ptr_t start, size_t len)
15 {
16     ptr_t end = start + end - 1;
17     struct mm_region *pos, *n;
18     llist_for_each(pos, n, regions, head)
19     {
20         if (pos->end >= start && pos->start < start) {
21             return 1;
22         }
23
24         if (pos->end <= end && pos->start >= start) {
25             return 1;
26         }
27
28         if (pos->end >= end && pos->start < end) {
29             return 1;
30         }
31     }
32
33     return 0;
34 }
35
36 int
37 mem_map(void** addr_out,
38         struct mm_region** created,
39         void* addr,
40         struct v_file* file,
41         struct mmap_param* param)
42 {
43     assert_msg(addr, "addr can not be NULL");
44
45     ptr_t last_end = USER_START, found_loc = (ptr_t)addr;
46     struct mm_region *pos, *n;
47
48     vm_regions_t* vm_regions = &param->pvms->regions;
49
50     if ((param->flags & MAP_FIXED_NOREPLACE)) {
51         if (mem_has_overlap(vm_regions, found_loc, param->mlen)) {
52             return EEXIST;
53         }
54         goto found;
55     }
56
57     if ((param->flags & MAP_FIXED)) {
58         int status =
59           mem_unmap(param->vms_mnt, vm_regions, found_loc, param->mlen);
60         if (status) {
61             return status;
62         }
63         goto found;
64     }
65
66     llist_for_each(pos, n, vm_regions, head)
67     {
68         if (last_end < found_loc) {
69             size_t avail_space = pos->start - found_loc;
70             if ((int)avail_space > 0 && avail_space > param->mlen) {
71                 goto found;
72             }
73             found_loc = pos->end + PG_SIZE;
74         }
75
76         last_end = pos->end + PG_SIZE;
77     }
78
79     return ENOMEM;
80
81 found:
82     if (found_loc >= KERNEL_MM_BASE || found_loc < USER_START) {
83         return ENOMEM;
84     }
85
86     struct mm_region* region = region_create_range(
87       found_loc,
88       param->mlen,
89       ((param->proct | param->flags) & 0x3f) | (param->type & ~0xffff));
90
91     region->mfile = file;
92     region->foff = param->offset;
93     region->flen = param->flen;
94     region->proc_vms = param->pvms;
95
96     region_add(vm_regions, region);
97
98     u32_t attr = PG_ALLOW_USER;
99     if ((param->proct & REGION_WRITE)) {
100         attr |= PG_WRITE;
101     }
102
103     for (u32_t i = 0; i < param->mlen; i += PG_SIZE) {
104         vmm_set_mapping(param->vms_mnt, found_loc + i, 0, attr, 0);
105     }
106
107     if (file) {
108         vfs_ref_file(file);
109     }
110
111     if (addr_out) {
112         *addr_out = found_loc;
113     }
114     if (created) {
115         *created = region;
116     }
117     return 0;
118 }
119
120 void
121 mem_sync_pages(ptr_t mnt,
122                struct mm_region* region,
123                ptr_t start,
124                ptr_t length,
125                int options)
126 {
127     if (!region->mfile || !(region->attr & REGION_WSHARED)) {
128         return;
129     }
130
131     v_mapping mapping;
132     for (size_t i = 0; i < length; i += PG_SIZE) {
133         if (!vmm_lookupat(mnt, start + i, &mapping)) {
134             continue;
135         }
136
137         if (PG_IS_DIRTY(*mapping.pte)) {
138             size_t offset = mapping.va - region->start + region->foff;
139             struct v_inode* inode = region->mfile->inode;
140             region->mfile->ops->write_page(inode, mapping.va, PG_SIZE, offset);
141             *mapping.pte &= ~PG_DIRTY;
142             cpu_invplg(mapping.pte);
143         } else if ((options & MS_INVALIDATE)) {
144             goto invalidate;
145         }
146
147         if (options & MS_INVALIDATE_ALL) {
148             goto invalidate;
149         }
150
151         continue;
152
153     invalidate:
154         *mapping.pte &= ~PG_PRESENT;
155         pmm_free_page(KERNEL_PID, mapping.pa);
156         cpu_invplg(mapping.pte);
157     }
158 }
159
160 int
161 mem_msync(ptr_t mnt,
162           vm_regions_t* regions,
163           ptr_t addr,
164           size_t length,
165           int options)
166 {
167     struct mm_region* pos = list_entry(regions->next, struct mm_region, head);
168     while (length && (ptr_t)&pos->head != (ptr_t)regions) {
169         if (pos->end >= addr && pos->start <= addr) {
170             size_t l = MIN(length, pos->end - addr);
171             mem_sync_pages(mnt, pos, addr, l, options);
172
173             addr += l;
174             length -= l;
175         }
176         pos = list_entry(pos->head.next, struct mm_region, head);
177     }
178
179     if (length) {
180         return ENOMEM;
181     }
182
183     return 0;
184 }
185
186 void
187 mem_unmap_region(ptr_t mnt, struct mm_region* region)
188 {
189     size_t len = ROUNDUP(region->end - region->start, PG_SIZE);
190     mem_sync_pages(mnt, region, region->start, len, 0);
191
192     for (size_t i = region->start; i <= region->end; i += PG_SIZE) {
193         ptr_t pa = vmm_del_mapping(mnt, i);
194         if (pa) {
195             pmm_free_page(__current->pid, pa);
196         }
197     }
198     llist_delete(&region->head);
199     region_release(region);
200 }
201
202 int
203 mem_unmap(ptr_t mnt, vm_regions_t* regions, void* addr, size_t length)
204 {
205     length = ROUNDUP(length, PG_SIZE);
206     ptr_t cur_addr = PG_ALIGN(addr);
207     struct mm_region *pos, *n;
208
209     llist_for_each(pos, n, regions, head)
210     {
211         if (pos->start <= cur_addr && pos->end >= cur_addr) {
212             break;
213         }
214     }
215
216     while (&pos->head != regions && cur_addr > pos->start) {
217         u32_t l = pos->end - cur_addr;
218         pos->end = cur_addr;
219
220         if (l > length) {
221             // unmap cause discontinunity in a memory region -  do split
222             struct mm_region* region = valloc(sizeof(struct mm_region));
223             *region = *pos;
224             region->start = cur_addr + length;
225             llist_insert_after(&pos->head, &region->head);
226             l = length;
227         }
228
229         mem_sync_pages(mnt, pos, cur_addr, l, 0);
230
231         for (size_t i = 0; i < l; i += PG_SIZE) {
232             ptr_t pa = vmm_del_mapping(mnt, cur_addr + i);
233             if (pa) {
234                 pmm_free_page(pos->proc_vms->pid, pa);
235             }
236         }
237
238         n = container_of(pos->head.next, typeof(*pos), head);
239         if (pos->end == pos->start) {
240             llist_delete(&pos->head);
241             region_release(pos);
242         }
243
244         pos = n;
245         length -= l;
246         cur_addr += length;
247     }
248
249     return 0;
250 }
251
252 __DEFINE_LXSYSCALL3(void*, sys_mmap, void*, addr, size_t, length, va_list, lst)
253 {
254     int proct = va_arg(lst, int);
255     int fd = va_arg(lst, u32_t);
256     off_t offset = va_arg(lst, off_t);
257     int options = va_arg(lst, int);
258     int errno = 0;
259     void* result = (void*)-1;
260
261     if (!length || length > BS_SIZE || !PG_ALIGNED(addr)) {
262         errno = EINVAL;
263         goto done;
264     }
265
266     if (!addr) {
267         addr = UMMAP_START;
268     } else if (addr < UMMAP_START || addr + length >= UMMAP_END) {
269         errno = ENOMEM;
270         goto done;
271     }
272
273     struct v_fd* vfd;
274     if ((errno = vfs_getfd(fd, &vfd))) {
275         goto done;
276     }
277
278     struct v_file* file = vfd->file;
279
280     if (!(options & MAP_ANON)) {
281         if (!file->ops->read_page) {
282             errno = ENODEV;
283             goto done;
284         }
285     } else {
286         file = NULL;
287     }
288
289     struct mmap_param param = { .flags = options,
290                                 .mlen = ROUNDUP(length, PG_SIZE),
291                                 .offset = offset,
292                                 .type = REGION_TYPE_GENERAL,
293                                 .proct = proct,
294                                 .pvms = &__current->mm,
295                                 .vms_mnt = VMS_SELF };
296
297     errno = mem_map(&result, NULL, addr, file, &param);
298
299 done:
300     __current->k_status = errno;
301     return result;
302 }
303
304 __DEFINE_LXSYSCALL2(void, munmap, void*, addr, size_t, length)
305 {
306     return mem_unmap(VMS_SELF, &__current->mm.regions, addr, length);
307 }
308
309 __DEFINE_LXSYSCALL3(int, msync, void*, addr, size_t, length, int, flags)
310 {
311     if (!PG_ALIGNED(addr) || ((flags & MS_ASYNC) && (flags & MS_SYNC))) {
312         return DO_STATUS(EINVAL);
313     }
314
315     int status =
316       mem_msync(VMS_SELF, &__current->mm.regions, addr, length, flags);
317
318     return DO_STATUS(status);
319 }