Added demand paging support
[akaros.git] / kern / src / mm.c
1 /*
2  * Copyright (c) 2009 The Regents of the University of California
3  * Barret Rhoden <brho@cs.berkeley.edu>
4  * See LICENSE for details.
5  *
6  */
7
8 #include <frontend.h>
9 #include <ros/common.h>
10 #include <ros/mman.h>
11 #include <pmap.h>
12 #include <mm.h>
13 #include <process.h>
14 #include <stdio.h>
15 #include <syscall.h>
16 #include <slab.h>
17 #include <kmalloc.h>
18
19 void *mmap(struct proc *p, uintptr_t addr, size_t len, int prot, int flags,
20            int fd, size_t offset)
21 {
22         printd("mmap(addr %x, len %x, prot %x, flags %x, fd %x, off %x)\n", addr,
23                len, prot, flags, fd, offset);
24         if (fd >= 0 && (flags & MAP_SHARED)) {
25                 printk("[kernel] mmap() for files requires !MAP_SHARED.\n");
26                 return (void*)-1;
27         }
28         if (fd >= 0 && (flags & MAP_ANON)) {
29                 printk("[kernel] mmap() with MAP_ANONYMOUS requires fd == -1.\n");
30                 return (void*)-1;
31         }
32
33         struct file* file = NULL;
34         if(fd != -1)
35         {
36                 file = file_open_from_fd(p,fd);
37                 if(!file)
38                         return (void*)-1;
39         }
40
41         void* result = do_mmap(p,addr,len,prot,flags,file,offset);
42
43         if(file)
44                 file_decref(file);
45
46         return result;
47 }
48
49 void *do_mmap(struct proc *p, uintptr_t addr, size_t len, int prot, int flags,
50               struct file* file, size_t offset)
51 {
52         /* TODO: make this work, instead of a ghetto hack
53          * Find a valid range, make sure it doesn't run into the kernel
54          * make sure there's enough memory (not exceeding quotas)
55          * allocate and map the pages, update appropriate structures (vm_region)
56          * return appropriate pointer
57          * Right now, all we can do is give them the range they ask for.
58          * (or try to find one on sparc) */
59
60         if((flags & MAP_FIXED) && PGOFF(addr)) {
61                 printk("[kernel] mmap() page align your addr.\n");
62                 return (void*SAFE)TC(-1);
63         }
64
65         // TODO: grab the appropriate mm_lock
66         spin_lock_irqsave(&p->proc_lock);
67
68         int num_pages = ROUNDUP(len, PGSIZE) / PGSIZE;
69
70         if(!(flags & MAP_FIXED))
71         {
72                 addr = (uintptr_t)get_free_va_range(p->env_pgdir,addr,len);
73                 if(!addr)
74                         goto mmap_abort;
75
76                 assert(!PGOFF(addr));
77                 assert(addr + num_pages*PGSIZE <= USTACKBOT);
78         }
79
80         // get a list of pfault_info_t's and pte's a priori,
81         // because if their allocation fails, we could end up
82         // in an inconsistent state
83
84         pfault_info_t** pfis = kmalloc(sizeof(pfault_info_t*)*num_pages,0);
85         pte_t** ptes = kmalloc(sizeof(pte_t*)*num_pages,0);
86         if(!pfis || !ptes)
87         {
88                 kfree(ptes);
89                 kfree(pfis);
90                 goto mmap_abort;
91         }
92
93         for(int i = 0; i < num_pages; i++)
94         {
95                 pfis[i] = pfault_info_alloc(file);
96                 ptes[i] = pgdir_walk(p->env_pgdir,(char*)addr+i*PGSIZE,1);
97
98                 // cleanup allocated pfault_info_t's on allocation failure
99                 if(!pfis[i] || !ptes[i])
100                 {
101                         int free_until = pfis[i] ? i+1 : i;
102                         for(int j = 0; j < free_until; j++)
103                                 pfault_info_free(pfis[j]);
104
105                         kfree(ptes);
106                         kfree(pfis);
107                         goto mmap_abort;
108                 }
109         }
110
111         // make the lazy mapping finally
112         int perm = (prot & PROT_WRITE) ? PTE_USER_RW :
113                    (prot & (PROT_READ|PROT_EXEC))  ? PTE_USER_RO : 0;
114         for(int i = 0; i < num_pages; i++)
115         {
116                 // free an old page that was present here
117                 if(PAGE_PRESENT(*ptes[i]))
118                         page_decref(ppn2page(PTE2PPN(*ptes[i])));
119                 // free the pfault_info for a page that wasn't faulted-in yet
120                 else if(PAGE_PAGED_OUT(*ptes[i]))
121                         pfault_info_free(PTE2PFAULT_INFO(*ptes[i]));
122
123                 pfis[i]->file = file;
124                 pfis[i]->pgoff = offset+i;
125                 pfis[i]->read_len = PGSIZE;
126                 // zero-fill end of last page
127                 if(i == num_pages-1 && len % PGSIZE)
128                         pfis[i]->read_len = len % PGSIZE;
129                 pfis[i]->perm = perm;
130                 *ptes[i] = PFAULT_INFO2PTE(pfis[i]);
131
132                 // uncomment the line below to simulate aggressive loading
133                 //assert(handle_page_fault(p,(char*)addr+i*PGSIZE,PROT_READ) == 0);
134         }
135
136         kfree(ptes);
137         kfree(pfis);
138
139         // TODO: release the appropriate mm_lock
140         spin_unlock_irqsave(&p->proc_lock);
141         return (void*SAFE)TC(addr);
142
143         // TODO: if there's a failure, we should go back through the addr+len range
144         // and dealloc everything.  or at least define what we want to do if we run
145         // out of memory.
146         mmap_abort:
147                 // TODO: release the appropriate mm_lock
148                 spin_unlock_irqsave(&p->proc_lock);
149                 // not a kernel problem, like if they ask to mmap a mapped location.
150                 printk("[kernel] mmap() aborted!\n");
151                 // mmap's semantics.  we need a better error propagation system
152                 return (void*SAFE)TC(-1); // this is also ridiculous
153 }
154
155 int mprotect(struct proc* p, void* addr, size_t len, int prot)
156 {
157         printd("mprotect(addr %x, len %x, prot %x)\n",addr,len,prot);
158         if((uintptr_t)addr % PGSIZE || (len == 0 && (prot & PROT_UNMAP)))
159         {
160                 set_errno(current_tf,EINVAL);
161                 return -1;
162         }
163
164         // overflow of end is handled in the for loop's parameters
165         char* end = ROUNDUP((char*)addr+len,PGSIZE);
166         if(addr >= (void*)UTOP || end > (char*)UTOP)
167         {
168                 set_errno(current_tf, (prot & PROT_UNMAP) ? EINVAL : ENOMEM);
169                 return -1;
170         }
171
172         spin_lock_irqsave(&p->proc_lock);
173
174         int newperm = (prot & PROT_WRITE) ? PTE_USER_RW :
175                       (prot & (PROT_READ|PROT_EXEC)) ? PTE_USER_RO : 0;
176
177         for(char* a = (char*)addr; a < end; a += PGSIZE)
178         {
179                 pte_t* pte = pgdir_walk(p->env_pgdir,a,0);
180
181                 // unmapped page? error out, behavior undefined (per POSIX)
182                 if(!pte || PAGE_UNMAPPED(*pte))
183                 {
184                         set_errno(current_tf,ENOMEM);
185                         return -1;
186                 }
187                 // common case: the page is present
188                 else if(PAGE_PRESENT(*pte))
189                 {
190                         // TODO: do munmap() in munmap(), instead of mprotect()
191                         if(prot & PROT_UNMAP)
192                         {
193                                 page_t* page = ppn2page(PTE2PPN(*pte));
194                                 *pte = 0;
195                                 page_decref(page);
196                         }
197                         else
198                         {
199                                 *pte = (*pte & ~PTE_PERM) | newperm;
200                         }
201                 }
202                 // or, the page might be mapped, but not yet faulted-in
203                 else // PAGE_PAGED_OUT(*pte)
204                 {
205                         if(prot & PROT_UNMAP)
206                         {
207                                 pfault_info_free(PTE2PFAULT_INFO(*pte));
208                                 *pte = 0;
209                         }
210                         else
211                                 PTE2PFAULT_INFO(*pte)->perm = newperm;
212                 }
213         }
214
215         spin_unlock_irqsave(&p->proc_lock);
216
217         //TODO: TLB shootdown - needs to be process wide
218         tlbflush();
219         return 0;
220 }
221
222 int munmap(struct proc* p, void* addr, size_t len)
223 {
224         return mprotect(p, addr, len, PROT_UNMAP);
225 }
226
227 int handle_page_fault(struct proc* p, uintptr_t va, int prot)
228 {
229         int ret = -1;
230         va = ROUNDDOWN(va,PGSIZE);
231
232         if(prot != PROT_READ && prot != PROT_WRITE && prot != PROT_EXEC)
233                 panic("bad prot!");
234
235         //spin_lock_irqsave(&p->proc_lock);
236
237         /// find offending PTE
238         pte_t* ppte = pgdir_walk(p->env_pgdir,(void*)va,0);
239         // if PTE is NULL, this is a fault that should kill the process
240         if(!ppte)
241                 goto out;
242
243         pte_t pte = *ppte;
244
245         // if PTE is present, why did we fault?
246         if(PAGE_PRESENT(pte))
247         {
248                 // a race is possible: the page might have been faulted in by
249                 // another core already, in which case we should just return.
250                 // otherwise, it's a fault that should kill the user
251                 switch(prot)
252                 {
253                         case PROT_READ:
254                         case PROT_EXEC:
255                                 if(pte == PTE_USER_RO || pte == PTE_USER_RW)
256                                         ret = 0;
257                                 goto out;
258                         case PROT_WRITE:
259                                 if(pte == PTE_USER_RW)
260                                         ret = 0;
261                                 goto out;
262                 }
263                 // can't get here
264         }
265
266         // if the page isn't present, kill the user
267         if(PAGE_UNMAPPED(pte))
268                 goto out;
269
270         // now, we know that PAGE_PAGED_OUT(pte) is true
271         pfault_info_t* info = PTE2PFAULT_INFO(pte);
272
273         // allocate a page; maybe zero-fill it
274         int zerofill = info->file == NULL;
275         page_t* a_page;
276         if(upage_alloc(p, &a_page, zerofill))
277                 goto out;
278
279         // if this isn't a zero-filled page, read it in from file
280         if(!zerofill)
281         {
282                 int read_len = file_read_page(info->file,page2pa(a_page),info->pgoff);
283                 if(read_len < 0)
284                 {
285                         page_free(a_page);
286                         goto out;
287                 }
288
289                 // if we read too much, zero that part out
290                 if(info->read_len < read_len)
291                         memset(page2kva(a_page)+info->read_len,0,read_len-info->read_len);
292         }
293
294         // update the page table
295         if(page_insert(p->env_pgdir, a_page, (void*)va, info->perm))
296         {
297                 page_free(a_page);
298                 goto out;
299         }
300
301         pfault_info_free(info);
302         ret = 0;
303
304 out:
305         //spin_unlock_irqsave(&p->proc_lock);
306         tlbflush();
307         return ret;
308 }
309
310 struct kmem_cache* pfault_info_cache;
311 void mmap_init(void)
312 {
313         pfault_info_cache = kmem_cache_create("pfault_info",
314                                               sizeof(pfault_info_t), 8, 0, 0, 0);
315 }
316
317 pfault_info_t* pfault_info_alloc(struct file* file)
318 {
319         if(file)
320                 file_incref(file);
321         return kmem_cache_alloc(pfault_info_cache,0);
322 }
323
324 void pfault_info_free(pfault_info_t* pfi)
325 {
326         if(pfi->file)
327                 file_decref(pfi->file);
328         kmem_cache_free(pfault_info_cache,pfi);
329 }
330