feat: msync(2)
[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 (2 << 30)
12
13 int
14 mem_map(void** addr_out,
15         ptr_t mnt,
16         vm_regions_t* regions,
17         void* addr,
18         struct v_file* file,
19         off_t offset,
20         size_t length,
21         u32_t proct,
22         u32_t options)
23 {
24     ptr_t last_end = USER_START;
25     struct mm_region *pos, *n;
26
27     if ((options & MAP_FIXED)) {
28         pos = region_get(regions, addr);
29         if (!pos) {
30             last_end = addr;
31             goto found;
32         }
33         return EEXIST;
34     }
35
36     llist_for_each(pos, n, regions, head)
37     {
38         if (pos->start - last_end >= length && last_end >= addr) {
39             goto found;
40         }
41         last_end = pos->end;
42     }
43
44     return ENOMEM;
45
46 found:
47     addr = last_end;
48
49     struct mm_region* region =
50       region_create_range(addr, length, proct | (options & 0x1f));
51     region->mfile = file;
52     region->offset = offset;
53
54     region_add(regions, region);
55
56     u32_t attr = PG_ALLOW_USER;
57     if ((proct & REGION_WRITE)) {
58         attr |= PG_WRITE;
59     }
60
61     for (u32_t i = 0; i < length; i += PG_SIZE) {
62         vmm_set_mapping(mnt, addr + i, 0, attr, 0);
63     }
64
65     *addr_out = addr;
66     return 0;
67 }
68
69 void
70 mem_sync_pages(ptr_t mnt,
71                struct mm_region* region,
72                ptr_t start,
73                ptr_t length,
74                int options)
75 {
76     if (!region->mfile || !(region->attr & REGION_WSHARED)) {
77         return;
78     }
79
80     v_mapping mapping;
81     for (size_t i = 0; i < length; i += PG_SIZE) {
82         if (!vmm_lookupat(mnt, start + i, &mapping)) {
83             continue;
84         }
85         if (PG_IS_DIRTY(*mapping.pte)) {
86             size_t offset = mapping.va - region->start + region->offset;
87             struct v_inode* inode = region->mfile->inode;
88             region->mfile->ops->write_page(inode, mapping.va, PG_SIZE, offset);
89             *mapping.pte &= ~PG_DIRTY;
90             cpu_invplg(mapping.va);
91         } else if ((options & MS_INVALIDATE)) {
92             *mapping.pte &= ~PG_PRESENT;
93             cpu_invplg(mapping.va);
94         }
95     }
96 }
97
98 int
99 mem_msync(ptr_t mnt,
100           vm_regions_t* regions,
101           ptr_t addr,
102           size_t length,
103           int options)
104 {
105     struct mm_region* pos = list_entry(regions->next, struct mm_region, head);
106     while (length && (ptr_t)&pos->head != (ptr_t)regions) {
107         if (pos->end >= addr && pos->start <= addr) {
108             size_t l = MIN(length, pos->end - addr);
109             mem_sync_pages(mnt, pos, addr, l, options);
110
111             addr += l;
112             length -= l;
113         }
114         pos = list_entry(pos->head.next, struct mm_region, head);
115     }
116
117     if (length) {
118         return ENOMEM;
119     }
120
121     return 0;
122 }
123
124 int
125 mem_unmap(ptr_t mnt, vm_regions_t* regions, void* addr, size_t length)
126 {
127     length = ROUNDUP(length, PG_SIZE);
128     ptr_t cur_addr = ROUNDDOWN((ptr_t)addr, PG_SIZE);
129     struct mm_region *pos, *n;
130
131     llist_for_each(pos, n, regions, head)
132     {
133         if (pos->start <= cur_addr) {
134             break;
135         }
136     }
137
138     while (&pos->head != regions && cur_addr > pos->start) {
139         u32_t l = pos->end - cur_addr;
140         pos->end = cur_addr;
141
142         if (l > length) {
143             // unmap cause discontinunity in a memory region -  do split
144             struct mm_region* region = valloc(sizeof(struct mm_region));
145             *region = *pos;
146             region->start = cur_addr + length;
147             llist_insert_after(&pos->head, &region->head);
148             l = length;
149         }
150
151         mem_sync_pages(mnt, pos, cur_addr, l, 0);
152
153         for (size_t i = 0; i < l; i += PG_SIZE) {
154             ptr_t pa = vmm_del_mapping(mnt, cur_addr + i);
155             if (pa) {
156                 pmm_free_page(__current->pid, pa);
157             }
158         }
159
160         n = container_of(pos->head.next, typeof(*pos), head);
161         if (pos->end == pos->start) {
162             llist_delete(&pos->head);
163             vfree(pos);
164         }
165
166         pos = n;
167         length -= l;
168         cur_addr += length;
169     }
170 }
171
172 __DEFINE_LXSYSCALL3(void*, sys_mmap, void*, addr, size_t, length, va_list, lst)
173 {
174     int proct = va_arg(lst, int);
175     int fd = va_arg(lst, u32_t);
176     off_t offset = va_arg(lst, off_t);
177     int options = va_arg(lst, int);
178     int errno = 0;
179     void* result = (void*)-1;
180
181     if (!length || length > BS_SIZE || !PG_ALIGNED(addr)) {
182         errno = EINVAL;
183         goto done;
184     }
185
186     struct v_fd* vfd;
187     if ((errno = vfs_getfd(fd, &vfd))) {
188         goto done;
189     }
190
191     struct v_file* file = vfd->file;
192
193     if (!(options & MAP_ANON)) {
194         if (!file->ops->read_page) {
195             errno = ENODEV;
196             goto done;
197         }
198     } else {
199         file = NULL;
200     }
201
202     length = ROUNDUP(length, PG_SIZE);
203
204     errno = mem_map(&result,
205                     VMS_SELF,
206                     &__current->mm.regions,
207                     addr,
208                     file,
209                     offset,
210                     length,
211                     proct,
212                     options);
213
214 done:
215     __current->k_status = errno;
216     return result;
217 }
218
219 __DEFINE_LXSYSCALL2(void, munmap, void*, addr, size_t, length)
220 {
221     return mem_unmap(VMS_SELF, &__current->mm.regions, addr, length);
222 }
223
224 __DEFINE_LXSYSCALL3(int, msync, void*, addr, size_t, length, int, flags)
225 {
226     if (!PG_ALIGNED(addr) || ((flags & MS_ASYNC) && (flags & MS_SYNC))) {
227         return DO_STATUS(EINVAL);
228     }
229
230     int status =
231       mem_msync(VMS_SELF, &__current->mm.regions, addr, length, flags);
232
233     return DO_STATUS(status);
234 }