Added icache flushing
[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         for(int i = 0; i < num_pages; i++)
113         {
114                 // free an old page that was present here
115                 if(PAGE_PRESENT(*ptes[i]))
116                         page_decref(ppn2page(PTE2PPN(*ptes[i])));
117                 // free the pfault_info for a page that wasn't faulted-in yet
118                 else if(PAGE_PAGED_OUT(*ptes[i]))
119                         pfault_info_free(PTE2PFAULT_INFO(*ptes[i]));
120
121                 pfis[i]->file = file;
122                 pfis[i]->pgoff = offset+i;
123                 pfis[i]->read_len = PGSIZE;
124                 // zero-fill end of last page
125                 if(i == num_pages-1 && len % PGSIZE)
126                         pfis[i]->read_len = len % PGSIZE;
127                 pfis[i]->prot = prot;
128                 *ptes[i] = PFAULT_INFO2PTE(pfis[i]);
129
130                 // uncomment the line below to simulate aggressive loading
131                 //assert(handle_page_fault(p,(char*)addr+i*PGSIZE,PROT_READ) == 0);
132         }
133
134         kfree(ptes);
135         kfree(pfis);
136
137         // TODO: release the appropriate mm_lock
138         spin_unlock_irqsave(&p->proc_lock);
139         return (void*SAFE)TC(addr);
140
141         // TODO: if there's a failure, we should go back through the addr+len range
142         // and dealloc everything.  or at least define what we want to do if we run
143         // out of memory.
144         mmap_abort:
145                 // TODO: release the appropriate mm_lock
146                 spin_unlock_irqsave(&p->proc_lock);
147                 // not a kernel problem, like if they ask to mmap a mapped location.
148                 printk("[kernel] mmap() aborted!\n");
149                 // mmap's semantics.  we need a better error propagation system
150                 return (void*SAFE)TC(-1); // this is also ridiculous
151 }
152
153 int mprotect(struct proc* p, void* addr, size_t len, int prot)
154 {
155         printd("mprotect(addr %x, len %x, prot %x)\n",addr,len,prot);
156         if((uintptr_t)addr % PGSIZE || (len == 0 && (prot & PROT_UNMAP)))
157         {
158                 set_errno(current_tf,EINVAL);
159                 return -1;
160         }
161
162         // overflow of end is handled in the for loop's parameters
163         char* end = ROUNDUP((char*)addr+len,PGSIZE);
164         if(addr >= (void*)UTOP || end > (char*)UTOP)
165         {
166                 set_errno(current_tf, (prot & PROT_UNMAP) ? EINVAL : ENOMEM);
167                 return -1;
168         }
169
170         spin_lock_irqsave(&p->proc_lock);
171
172         int newperm = (prot & PROT_WRITE) ? PTE_USER_RW :
173                       (prot & (PROT_READ|PROT_EXEC)) ? PTE_USER_RO : 0;
174
175         for(char* a = (char*)addr; a < end; a += PGSIZE)
176         {
177                 pte_t* pte = pgdir_walk(p->env_pgdir,a,0);
178
179                 // unmapped page? error out, behavior undefined (per POSIX)
180                 if(!pte || PAGE_UNMAPPED(*pte))
181                 {
182                         set_errno(current_tf,ENOMEM);
183                         return -1;
184                 }
185                 // common case: the page is present
186                 else if(PAGE_PRESENT(*pte))
187                 {
188                         // TODO: do munmap() in munmap(), instead of mprotect()
189                         if(prot & PROT_UNMAP)
190                         {
191                                 page_t* page = ppn2page(PTE2PPN(*pte));
192                                 *pte = 0;
193                                 page_decref(page);
194                         }
195                         else
196                         {
197                                 *pte = (*pte & ~PTE_PERM) | newperm;
198                         }
199                 }
200                 // or, the page might be mapped, but not yet faulted-in
201                 else // PAGE_PAGED_OUT(*pte)
202                 {
203                         if(prot & PROT_UNMAP)
204                         {
205                                 pfault_info_free(PTE2PFAULT_INFO(*pte));
206                                 *pte = 0;
207                         }
208                         else
209                                 PTE2PFAULT_INFO(*pte)->prot = prot;
210                 }
211         }
212
213         spin_unlock_irqsave(&p->proc_lock);
214
215         //TODO: TLB shootdown - needs to be process wide
216         tlbflush();
217         return 0;
218 }
219
220 int munmap(struct proc* p, void* addr, size_t len)
221 {
222         return mprotect(p, addr, len, PROT_UNMAP);
223 }
224
225 int handle_page_fault(struct proc* p, uintptr_t va, int prot)
226 {
227         int ret = -1;
228         va = ROUNDDOWN(va,PGSIZE);
229
230         if(prot != PROT_READ && prot != PROT_WRITE && prot != PROT_EXEC)
231                 panic("bad prot!");
232
233         //spin_lock_irqsave(&p->proc_lock);
234
235         /// find offending PTE
236         pte_t* ppte = pgdir_walk(p->env_pgdir,(void*)va,0);
237         // if PTE is NULL, this is a fault that should kill the process
238         if(!ppte)
239                 goto out;
240
241         pte_t pte = *ppte;
242
243         // if PTE is present, why did we fault?
244         if(PAGE_PRESENT(pte))
245         {
246                 // a race is possible: the page might have been faulted in by
247                 // another core already, in which case we should just return.
248                 // otherwise, it's a fault that should kill the user
249                 switch(prot)
250                 {
251                         case PROT_READ:
252                         case PROT_EXEC:
253                                 if(pte == PTE_USER_RO || pte == PTE_USER_RW)
254                                         ret = 0;
255                                 goto out;
256                         case PROT_WRITE:
257                                 if(pte == PTE_USER_RW)
258                                         ret = 0;
259                                 goto out;
260                 }
261                 // can't get here
262         }
263
264         // if the page isn't present, kill the user
265         if(PAGE_UNMAPPED(pte))
266                 goto out;
267
268         // now, we know that PAGE_PAGED_OUT(pte) is true
269         pfault_info_t* info = PTE2PFAULT_INFO(pte);
270
271         // allocate a page; maybe zero-fill it
272         int zerofill = info->file == NULL;
273         page_t* a_page;
274         if(upage_alloc(p, &a_page, zerofill))
275                 goto out;
276
277         // if this isn't a zero-filled page, read it in from file
278         if(!zerofill)
279         {
280                 int read_len = file_read_page(info->file,page2pa(a_page),info->pgoff);
281                 if(read_len < 0)
282                 {
283                         page_free(a_page);
284                         goto out;
285                 }
286
287                 // if we read too much, zero that part out
288                 if(info->read_len < read_len)
289                         memset(page2kva(a_page)+info->read_len,0,read_len-info->read_len);
290
291                 // if this is an executable page, we might have to flush
292                 // the instruction cache if our HW requires it
293                 if(info->prot & PROT_EXEC)
294                         icache_flush_page((void*)va,page2kva(a_page));
295         }
296
297         int perm = (info->prot & PROT_WRITE) ? PTE_USER_RW :
298                    (info->prot & (PROT_READ|PROT_EXEC))  ? PTE_USER_RO : 0;
299         // update the page table
300         if(page_insert(p->env_pgdir, a_page, (void*)va, perm))
301         {
302                 page_free(a_page);
303                 goto out;
304         }
305
306         pfault_info_free(info);
307         ret = 0;
308
309 out:
310         //spin_unlock_irqsave(&p->proc_lock);
311         tlbflush();
312         return ret;
313 }
314
315 struct kmem_cache* pfault_info_cache;
316 void mmap_init(void)
317 {
318         pfault_info_cache = kmem_cache_create("pfault_info",
319                                               sizeof(pfault_info_t), 8, 0, 0, 0);
320 }
321
322 pfault_info_t* pfault_info_alloc(struct file* file)
323 {
324         if(file)
325                 file_incref(file);
326         return kmem_cache_alloc(pfault_info_cache,0);
327 }
328
329 void pfault_info_free(pfault_info_t* pfi)
330 {
331         if(pfi->file)
332                 file_decref(pfi->file);
333         kmem_cache_free(pfault_info_cache,pfi);
334 }
335