Cleaned up the way ifdefs are handled
[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         if((flags & MAP_FIXED) && PGOFF(addr)) {
33                 printk("[kernel] mmap() page align your addr.\n");
34                 return (void*SAFE)TC(-1);
35         }
36
37         struct file* file = NULL;
38         if(fd != -1)
39         {
40                 file = file_open_from_fd(p,fd);
41                 if(!file)
42                         return (void*)-1;
43         }
44
45         void* result = do_mmap(p,addr,len,prot,flags,file,offset);
46
47         if(file)
48                 file_decref(file);
49
50         return result;
51 }
52
53 void *do_mmap(struct proc *p, uintptr_t addr, size_t len, int prot, int flags,
54               struct file* file, size_t offset)
55 {
56         // TODO: grab the appropriate mm_lock
57         spin_lock_irqsave(&p->proc_lock);
58         void* ret = __do_mmap(p,addr,len,prot,flags,file,offset);
59         spin_unlock_irqsave(&p->proc_lock);
60         return ret;
61 }
62
63 void *__do_mmap(struct proc *p, uintptr_t addr, size_t len, int prot, int flags,
64                 struct file* file, size_t offset)
65 {
66         int num_pages = ROUNDUP(len, PGSIZE) / PGSIZE;
67
68 #ifndef __CONFIG_DEMAND_PAGING__
69         flags |= MAP_POPULATE;
70 #endif
71         
72         if(!(flags & MAP_FIXED))
73         {
74                 addr = (uintptr_t)get_free_va_range(p->env_pgdir,addr,len);
75                 if(!addr)
76                         goto mmap_abort;
77
78                 assert(!PGOFF(addr));
79                 assert(addr + num_pages*PGSIZE <= USTACKBOT);
80         }
81
82         // get a list of pfault_info_t's and pte's a priori,
83         // because if their allocation fails, we could end up
84         // in an inconsistent state
85
86         pfault_info_t** pfis = kmalloc(sizeof(pfault_info_t*)*num_pages,0);
87         pte_t** ptes = kmalloc(sizeof(pte_t*)*num_pages,0);
88         if(!pfis || !ptes)
89         {
90                 kfree(ptes);
91                 kfree(pfis);
92                 goto mmap_abort;
93         }
94
95         for(int i = 0; i < num_pages; i++)
96         {
97                 pfis[i] = pfault_info_alloc(file);
98                 ptes[i] = pgdir_walk(p->env_pgdir,(char*)addr+i*PGSIZE,1);
99
100                 // cleanup allocated pfault_info_t's on allocation failure
101                 if(!pfis[i] || !ptes[i])
102                 {
103                         int free_until = pfis[i] ? i+1 : i;
104                         for(int j = 0; j < free_until; j++)
105                                 pfault_info_free(pfis[j]);
106
107                         kfree(ptes);
108                         kfree(pfis);
109                         goto mmap_abort;
110                 }
111         }
112
113         // make the lazy mapping finally
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]->prot = prot;
130                 *ptes[i] = PFAULT_INFO2PTE(pfis[i]);
131         }
132
133         kfree(ptes);
134         kfree(pfis);
135
136         // fault in pages now if MAP_POPULATE.  die on failure.
137         if(flags & MAP_POPULATE)
138                 for(int i = 0; i < num_pages; i++)
139                         if(__handle_page_fault(p,addr+i*PGSIZE,PROT_READ))
140                                 proc_destroy(p);
141
142         return (void*SAFE)TC(addr);
143
144         // TODO: if there's a failure, we should go back through the addr+len range
145         // and dealloc everything.  or at least define what we want to do if we run
146         // out of memory.
147         mmap_abort:
148                 // not a kernel problem, like if they ask to mmap a mapped location.
149                 printk("[kernel] mmap() aborted!\n");
150                 // mmap's semantics.  we need a better error propagation system
151                 return (void*SAFE)TC(-1); // this is also ridiculous
152 }
153
154 int mprotect(struct proc* p, void* addr, size_t len, int prot)
155 {
156         printd("mprotect(addr %x, len %x, prot %x)\n",addr,len,prot);
157         if((uintptr_t)addr % PGSIZE || (len == 0 && (prot & PROT_UNMAP)))
158         {
159                 set_errno(current_tf,EINVAL);
160                 return -1;
161         }
162
163         // overflow of end is handled in the for loop's parameters
164         char* end = ROUNDUP((char*)addr+len,PGSIZE);
165         if(addr >= (void*)UTOP || end > (char*)UTOP)
166         {
167                 set_errno(current_tf, (prot & PROT_UNMAP) ? EINVAL : ENOMEM);
168                 return -1;
169         }
170
171         spin_lock_irqsave(&p->proc_lock);
172         int ret = __mprotect(p,addr,len,prot);
173         spin_unlock_irqsave(&p->proc_lock);
174
175         return ret;
176 }
177
178 int __mprotect(struct proc* p, void* addr, size_t len, int prot)
179 {
180         int newperm = (prot & PROT_WRITE) ? PTE_USER_RW :
181                       (prot & (PROT_READ|PROT_EXEC)) ? PTE_USER_RO : 0;
182
183         char* end = ROUNDUP((char*)addr+len,PGSIZE);
184         for(char* a = (char*)addr; a < end; a += PGSIZE)
185         {
186                 pte_t* pte = pgdir_walk(p->env_pgdir,a,0);
187
188                 // unmapped page? error out, behavior undefined (per POSIX)
189                 if(!pte || PAGE_UNMAPPED(*pte))
190                 {
191                         set_errno(current_tf,ENOMEM);
192                         return -1;
193                 }
194                 // common case: the page is present
195                 else if(PAGE_PRESENT(*pte))
196                 {
197                         // TODO: do munmap() in munmap(), instead of mprotect()
198                         if(prot & PROT_UNMAP)
199                         {
200                                 page_t* page = ppn2page(PTE2PPN(*pte));
201                                 *pte = 0;
202                                 page_decref(page);
203                         }
204                         else
205                         {
206                                 *pte = (*pte & ~PTE_PERM) | newperm;
207                         }
208                 }
209                 // or, the page might be mapped, but not yet faulted-in
210                 else // PAGE_PAGED_OUT(*pte)
211                 {
212                         if(prot & PROT_UNMAP)
213                         {
214                                 pfault_info_free(PTE2PFAULT_INFO(*pte));
215                                 *pte = 0;
216                         }
217                         else
218                                 PTE2PFAULT_INFO(*pte)->prot = prot;
219                 }
220         }
221
222         //TODO: TLB shootdown - needs to be process wide
223         tlbflush();
224         return 0;
225 }
226
227 int munmap(struct proc* p, void* addr, size_t len)
228 {
229         return mprotect(p, addr, len, PROT_UNMAP);
230 }
231
232 int __munmap(struct proc* p, void* addr, size_t len)
233 {
234         return __mprotect(p, addr, len, PROT_UNMAP);
235 }
236
237 int handle_page_fault(struct proc* p, uintptr_t va, int prot)
238 {
239         va = ROUNDDOWN(va,PGSIZE);
240
241         if(prot != PROT_READ && prot != PROT_WRITE && prot != PROT_EXEC)
242                 panic("bad prot!");
243
244         spin_lock_irqsave(&p->proc_lock);
245         int ret = __handle_page_fault(p,va,prot);
246         spin_unlock_irqsave(&p->proc_lock);
247         return ret;
248 }
249         
250 int __handle_page_fault(struct proc* p, uintptr_t va, int prot)
251 {
252         int ret = -1;
253         // find offending PTE
254         pte_t* ppte = pgdir_walk(p->env_pgdir,(void*)va,0);
255         // if PTE is NULL, this is a fault that should kill the process
256         if(!ppte)
257                 goto out;
258
259         pte_t pte = *ppte;
260
261         // if PTE is present, why did we fault?
262         if(PAGE_PRESENT(pte))
263         {
264                 int perm = pte & PTE_PERM;
265                 // a race is possible: the page might have been faulted in by
266                 // another core already, in which case we should just return.
267                 // otherwise, it's a fault that should kill the user
268                 switch(prot)
269                 {
270                         case PROT_READ:
271                         case PROT_EXEC:
272                                 if(perm == PTE_USER_RO || perm == PTE_USER_RW)
273                                         ret = 0;
274                                 goto out;
275                         case PROT_WRITE:
276                                 if(perm == PTE_USER_RW)
277                                         ret = 0;
278                                 goto out;
279                 }
280                 // can't get here
281         }
282
283         // if the page isn't present, kill the user
284         if(PAGE_UNMAPPED(pte))
285                 goto out;
286
287         // now, we know that PAGE_PAGED_OUT(pte) is true
288         pfault_info_t* info = PTE2PFAULT_INFO(pte);
289
290         // allocate a page; maybe zero-fill it
291         int zerofill = info->file == NULL;
292         page_t* a_page;
293         if(upage_alloc(p, &a_page, zerofill))
294                 goto out;
295
296         // if this isn't a zero-filled page, read it in from file
297         if(!zerofill)
298         {
299                 int read_len = file_read_page(info->file,page2pa(a_page),info->pgoff);
300                 if(read_len < 0)
301                 {
302                         page_free(a_page);
303                         goto out;
304                 }
305
306                 // if we read too much, zero that part out
307                 if(info->read_len < read_len)
308                         memset(page2kva(a_page)+info->read_len,0,read_len-info->read_len);
309
310                 // if this is an executable page, we might have to flush
311                 // the instruction cache if our HW requires it
312                 if(info->prot & PROT_EXEC)
313                         icache_flush_page((void*)va,page2kva(a_page));
314         }
315
316         int perm = (info->prot & PROT_WRITE) ? PTE_USER_RW :
317                    (info->prot & (PROT_READ|PROT_EXEC))  ? PTE_USER_RO : 0;
318
319         // update the page table
320         page_incref(a_page);
321         *ppte = PTE(page2ppn(a_page),PTE_P | perm);
322
323         pfault_info_free(info);
324         ret = 0;
325
326 out:
327         tlbflush();
328         return ret;
329 }
330
331 struct kmem_cache* pfault_info_cache;
332 void mmap_init(void)
333 {
334         pfault_info_cache = kmem_cache_create("pfault_info",
335                                               sizeof(pfault_info_t), 8, 0, 0, 0);
336 }
337
338 pfault_info_t* pfault_info_alloc(struct file* file)
339 {
340         if(file)
341                 file_incref(file);
342         return kmem_cache_alloc(pfault_info_cache,0);
343 }
344
345 void pfault_info_free(pfault_info_t* pfi)
346 {
347         if(pfi->file)
348                 file_decref(pfi->file);
349         kmem_cache_free(pfault_info_cache,pfi);
350 }
351