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