Getting SharC to check some locking
[akaros.git] / kern / src / pmap.c
1 /* See COPYRIGHT for copyright information. */
2
3 /** @file 
4  * This file is responsible for managing physical pages as they 
5  * are mapped into the page tables of a particular virtual address
6  * space.  The functions defined in this file operate on these
7  * page tables to insert and remove physical pages from them at 
8  * particular virtual addresses.
9  *
10  * @author Kevin Klues <klueska@cs.berkeley.edu>
11  * @author Barret Rhoden <brho@cs.berkeley.edu>
12  */
13
14 #ifdef __SHARC__
15 #pragma nosharc
16 #endif
17
18 #include <arch/arch.h>
19 #include <arch/mmu.h>
20
21 #include <ros/error.h>
22
23 #include <kmalloc.h>
24 #include <atomic.h>
25 #include <string.h>
26 #include <assert.h>
27 #include <pmap.h>
28 #include <kclock.h>
29 #include <process.h>
30 #include <stdio.h>
31
32 /**
33  * @brief Global variable used to store erroneous virtual addresses as the
34  *        result of a failed user_mem_check().
35  */
36 static void *DANGEROUS user_mem_check_addr;
37
38 volatile uint32_t vpt_lock = 0;
39 volatile uint32_t vpd_lock = 0;
40
41 /**
42  * @brief Initialize the array of physical pages and memory free list.
43  *
44  * The 'pages' array has one 'page_t' entry per physical page.
45  * Pages are reference counted, and free pages are kept on a linked list.
46  */
47 void page_init(void)
48 {
49         /*
50      * First, make 'pages' point to an array of size 'npages' of
51          * type 'page_t'.
52          * The kernel uses this structure to keep track of physical pages;
53          * 'npages' equals the number of physical pages in memory.
54          * round up to the nearest page
55          */
56         size_t page_array_size = ROUNDUP(npages*sizeof(page_t), PGSIZE);
57         pages = (page_t*)boot_alloc(page_array_size, PGSIZE);
58         memset(pages, 0, npages*sizeof(page_t));
59
60         /*
61      * Then initilaize everything so pages can start to be alloced and freed
62          * from the memory free list
63          */
64         page_alloc_init();
65 }
66
67 /** 
68  * @brief Map the physical page 'pp' into the virtual address 'va' in page
69  *        directory 'pgdir'
70  *
71  * Map the physical page 'pp' at virtual address 'va'.
72  * The permissions (the low 12 bits) of the page table
73  * entry should be set to 'perm|PTE_P'.
74  * 
75  * Details:
76  *   - If there is already a page mapped at 'va', it is page_remove()d.
77  *   - If necessary, on demand, allocates a page table and inserts it into 
78  *     'pgdir'.
79  *   - page_incref() should be called if the insertion succeeds. 
80  *   - The TLB must be invalidated if a page was formerly present at 'va'.
81  *     (this is handled in page_remove)
82  *
83  * No support for jumbos here.  We will need to be careful when trying to
84  * insert regular pages into something that was already jumbo.  We will
85  * also need to be careful with our overloading of the PTE_PS and 
86  * PTE_PAT flags...
87  *
88  * @param[in] pgdir the page directory to insert the page into
89  * @param[in] pp    a pointr to the page struct representing the
90  *                  physical page that should be inserted.
91  * @param[in] va    the virtual address where the page should be
92  *                  inserted.
93  * @param[in] perm  the permition bits with which to set up the 
94  *                  virtual mapping.
95  *
96  * @return ESUCCESS  on success
97  * @return -ENOMEM   if a page table could not be allocated
98  *                   into which the page should be inserted
99  *
100  */
101 int page_insert(pde_t *pgdir, page_t *pp, void *va, int perm) 
102 {
103         pte_t* pte = pgdir_walk(pgdir, va, 1);
104         if (!pte)
105                 return -ENOMEM;
106         // need to up the ref count in case pp is already mapped at va
107         // and we don't want to page_remove (which could free pp) and then 
108         // continue as if pp wasn't freed.  moral = up the ref asap
109         page_incref(pp);
110         if (*pte & PTE_P) {
111                 page_remove(pgdir, va);
112         }
113         *pte = PTE(page2ppn(pp), PTE_P | perm);
114         return 0;
115 }
116
117 /**
118  * @brief Map the physical page 'pp' at the first virtual address that is free 
119  * in the range 'vab' to 'vae' in page directory 'pgdir'.
120  *
121  * The permissions (the low 12 bits) of the page table entry get set to 
122  * 'perm|PTE_P'.
123  *
124  * Details:
125  *   - If there is no free entry in the range 'vab' to 'vae' this 
126  *     function returns NULL.
127  *   - If necessary, on demand, this function will allocate a page table 
128  *     and inserts it into 'pgdir'.
129  *   - page_incref() will be called if the insertion succeeds.
130  * 
131  * @param[in] pgdir the page directory to insert the page into
132  * @param[in] pp    a pointr to the page struct representing the
133  *                  physical page that should be inserted.
134  * @param[in] vab   the first virtual address in the range in which the 
135  *                  page can be inserted.
136  * @param[in] vae   the last virtual address in the range in which the 
137  *                  page can be inserted.
138  * @param[in] perm  the permition bits with which to set up the 
139  *                  virtual mapping.
140  *
141  * @return VA   the virtual address where pp has been mapped in the 
142  *              range (vab, vae)
143  * @return NULL no free va in the range (vab, vae) could be found
144  */
145 void* page_insert_in_range(pde_t *pgdir, page_t *pp, 
146                            void *vab, void *vae, int perm) 
147 {
148         pte_t* pte = NULL;
149         void*SNT new_va;
150         
151         for(new_va = vab; new_va <= vae; new_va+= PGSIZE) {
152                 pte = pgdir_walk(pgdir, new_va, 1);
153                 if(pte != NULL && !(*pte & PTE_P)) break;
154                 else pte = NULL;
155         }
156         if (!pte) return NULL;
157         *pte = page2pa(pp) | PTE_P | perm;
158         return TC(new_va); // trusted because mapping a page is like allocation
159 }
160
161 /**
162  * @brief Return the page mapped at virtual address 'va' in 
163  * page directory 'pgdir'.
164  *
165  * If pte_store is not NULL, then we store in it the address
166  * of the pte for this page.  This is used by page_remove
167  * but should not be used by other callers.
168  *
169  * For jumbos, right now this returns the first Page* in the 4MB range
170  *
171  * @param[in]  pgdir     the page directory from which we should do the lookup
172  * @param[in]  va        the virtual address of the page we are looking up
173  * @param[out] pte_store the address of the page table entry for the returned page
174  *
175  * @return PAGE the page mapped at virtual address 'va'
176  * @return NULL No mapping exists at virtual address 'va'   
177  */
178 page_t *page_lookup(pde_t *pgdir, void *va, pte_t **pte_store)
179 {
180         pte_t* pte = pgdir_walk(pgdir, va, 0);
181         if (!pte || !(*pte & PTE_P))
182                 return 0;
183         if (pte_store)
184                 *pte_store = pte;
185         return pa2page(PTE_ADDR(*pte));
186 }
187
188 /**
189  * @brief Unmaps the physical page at virtual address 'va' in page directory
190  * 'pgdir'.
191  *
192  * If there is no physical page at that address, this function silently 
193  * does nothing.
194  *
195  * Details:
196  *   - The ref count on the physical page is decrement when the page is removed
197  *   - The physical page is freed if the refcount reaches 0.
198  *   - The pg table entry corresponding to 'va' is set to 0.
199  *     (if such a PTE exists)
200  *   - The TLB is invalidated if an entry is removes from the pg dir/pg table.
201  *
202  * This may be wonky wrt Jumbo pages and decref.  
203  *
204  * @param pgdir the page directory from with the page sholuld be removed
205  * @param va    the virtual address at which the page we are trying to 
206  *              remove is mapped
207  */
208 void page_remove(pde_t *pgdir, void *va)
209 {
210         pte_t* pte;
211         page_t *page;
212         page = page_lookup(pgdir, va, &pte);
213         if (!page)
214                 return;
215         *pte = 0;
216         tlb_invalidate(pgdir, va);
217         page_decref(page);
218 }
219
220 /**
221  * @brief Invalidate a TLB entry, but only if the page tables being
222  * edited are the ones currently in use by the processor.
223  *
224  * TODO: Need to sort this for cross core lovin'
225  *
226  * @param pgdir the page directory assocaited with the tlb entry 
227  *              we are trying to invalidate
228  * @param va    the virtual address associated with the tlb entry
229  *              we are trying to invalidate
230  */
231 void tlb_invalidate(pde_t *pgdir, void *va)
232 {
233         // Flush the entry only if we're modifying the current address space.
234         // For now, there is only one address space, so always invalidate.
235         invlpg(va);
236 }
237
238 /**
239  * @brief Check that an environment is allowed to access the range of memory
240  * [va, va+len) with permissions 'perm | PTE_P'.
241  *
242  * Normally 'perm' will contain PTE_U at least, but this is not required.
243  * 'va' and 'len' need not be page-aligned;
244  *
245  * A user program can access a virtual address if:
246  *     -# the address is below ULIM
247  *     -# the page table gives it permission.  
248  *
249  * If there is an error, 'user_mem_check_addr' is set to the first
250  * erroneous virtual address.
251  *
252  * @param env  the environment associated with the user program trying to access
253  *             the virtual address range
254  * @param va   the first virtual address in the range
255  * @param len  the length of the virtual address range
256  * @param perm the permissions the user is trying to access the virtual address 
257  *             range with
258  *
259  * @return VA a pointer of type COUNT(len) to the address range
260  * @return NULL trying to access this range of virtual addresses is not allowed
261  */
262 void* user_mem_check(env_t *env, const void *DANGEROUS va, size_t len, int perm)
263 {
264         // TODO - will need to sort this out wrt page faulting / PTE_P
265         // also could be issues with sleeping and waking up to find pages
266         // are unmapped, though i think the lab ignores this since the 
267         // kernel is uninterruptible
268         void *DANGEROUS start, *DANGEROUS end;
269         size_t num_pages, i;
270         pte_t *pte;
271
272         perm |= PTE_P;
273         start = ROUNDDOWN((void*DANGEROUS)va, PGSIZE);
274         end = ROUNDUP((void*DANGEROUS)va + len, PGSIZE);
275         if (start >= end) {
276                 warn("Blimey!  Wrap around in VM range calculation!");  
277                 return NULL;
278         }
279         num_pages = PPN(end - start);
280         for (i = 0; i < num_pages; i++, start += PGSIZE) {
281                 pte = pgdir_walk(env->env_pgdir, start, 0);
282                 // ensures the bits we want on are turned on.  if not, error out
283                 if ( !pte || ((*pte & perm) != perm) ) {
284                         if (i == 0)
285                                 user_mem_check_addr = (void*DANGEROUS)va;
286                         else
287                                 user_mem_check_addr = start;
288                         return NULL;
289                 }
290         }
291         // this should never be needed, since the perms should catch it
292         if ((uintptr_t)end > ULIM) {
293                 warn ("I suck - Bug in user permission mappings!");
294                 return NULL;
295         }
296         return (void *COUNT(len))TC(va);
297 }
298
299 /**
300  * @brief Use the kernel to copy a string from a buffer stored in userspace
301  *        to a buffer stored elsewhere in the address space (potentially in 
302  *        memory only accessible by the kernel)
303  *
304  * @param env  the environment associated with the user program from which
305  *             the string is being copied
306  * @param dst  the destination of the buffer into which the string 
307  *             is being copied
308  * @param va   the start address of the buffer where the string resides
309  * @param len  the length of the buffer 
310  * @param perm the permissions with which the user is trying to access 
311  *             elements of the original buffer 
312  *
313  * @return LEN the length of the new buffer copied into 'dst'
314  */
315 size_t
316 user_mem_strlcpy(env_t *env, char *dst, const char *DANGEROUS va,
317                  size_t _len, int perm)
318 {
319         const char *DANGEROUS src = va;
320         size_t len = _len;
321         char *NT COUNT(_len-1) dst_in = dst;
322
323         if (len > 0) {
324                 while (1) {
325                         char *c;
326                         if (--len <= 0) break;
327                         c = user_mem_check(env, src, 1, perm);
328                         if (!c) break;
329                         if (*c == '\0') break;
330                         *dst++ = *c;
331                         src++;
332                 }
333                 *dst = '\0';
334         }
335
336         return dst - dst_in;
337 }
338
339 /**
340  * @brief Checks that environment 'env' is allowed to access the range
341  * of memory [va, va+len) with permissions 'perm | PTE_U'. Destroy 
342  * environment 'env' if the assertion fails.
343  *
344  * This function is identical to user_mem_assert() except that it has a side
345  * affect of destroying the environment 'env' if the memory check fails.
346  *
347  * @param env  the environment associated with the user program trying to access
348  *             the virtual address range
349  * @param va   the first virtual address in the range
350  * @param len  the length of the virtual address range
351  * @param perm the permissions the user is trying to access the virtual address 
352  *             range with
353  *
354  * @return VA a pointer of type COUNT(len) to the address range
355  * @return NULL trying to access this range of virtual addresses is not allowed
356  *              environment 'env' is destroyed
357  */
358 void *
359 user_mem_assert(env_t *env, const void *DANGEROUS va, size_t len, int perm)
360 {
361     void *COUNT(len) res = user_mem_check(env,va,len,perm | PTE_USER_RO);
362         if (!res) {
363                 cprintf("[%08x] user_mem_check assertion failure for "
364                         "va %08x\n", env->env_id, user_mem_check_addr);
365                 proc_destroy(env);      // may not return
366         return NULL;
367         }
368     return res;
369 }
370
371 /**
372  * @brief Copies data from a user buffer to a kernel buffer.
373  * 
374  * @param env  the environment associated with the user program
375  *             from which the buffer is being copied
376  * @param dest the destination address of the kernel buffer
377  * @param va   the address of the userspace buffer from which we are copying
378  * @param len  the length of the userspace buffer
379  *
380  * @return ESUCCESS on success
381  * @return -EFAULT  the page assocaited with 'va' is not present, the user 
382  *                  lacks the proper permissions, or there was an invalid 'va'
383  */
384 error_t memcpy_from_user(env_t* env, void* COUNT(len) dest,
385                  const void *DANGEROUS va, size_t len)
386 {
387         const void *DANGEROUS start, *DANGEROUS end;
388         size_t num_pages, i;
389         pte_t *pte;
390         uintptr_t perm = PTE_P | PTE_USER_RO;
391         size_t bytes_copied = 0;
392
393         static_assert(ULIM % PGSIZE == 0 && ULIM != 0); // prevent wrap-around
394
395         start = ROUNDDOWN(va, PGSIZE);
396         end = ROUNDUP(va + len, PGSIZE);
397
398         if(start >= (void*SNT)ULIM || end >= (void*SNT)ULIM)
399                 return -EFAULT;
400
401         num_pages = PPN(end - start);
402         for(i = 0; i < num_pages; i++)
403         {
404                 pte = pgdir_walk(env->env_pgdir, start+i*PGSIZE, 0);
405                 if(!pte || (*pte & perm) != perm)
406                         return -EFAULT;
407
408                 void*COUNT(PGSIZE) kpage = KADDR(PTE_ADDR(pte));
409                 void* src_start = i > 0 ? kpage : kpage+(va-start);
410                 void* dst_start = dest+bytes_copied;
411                 size_t copy_len = PGSIZE;
412                 if(i == 0)
413                         copy_len -= va-start;
414                 if(i == num_pages-1)
415                         copy_len -= end-(start+len);
416
417                 memcpy(dst_start,src_start,copy_len);
418                 bytes_copied += copy_len;
419         }
420
421         assert(bytes_copied == len);
422
423         return ESUCCESS;
424 }
425