Added page-coloring support to SPARC port
[akaros.git] / kern / src / pmap.c
1 /* See COPYRIGHT for copyright information. */
2
3 #include <arch/arch.h>
4 #include <arch/mmu.h>
5
6 #include <ros/error.h>
7
8 #include <kmalloc.h>
9 #include <atomic.h>
10 #include <string.h>
11 #include <assert.h>
12 #include <pmap.h>
13 #include <kclock.h>
14 #include <process.h>
15 #include <stdio.h>
16
17 static void *DANGEROUS user_mem_check_addr;
18
19 // --------------------------------------------------------------
20 // Tracking of physical pages.
21 // The 'pages' array has one 'page_t' entry per physical page.
22 // Pages are reference counted, and free pages are kept on a linked list.
23 // --------------------------------------------------------------
24   
25 // Initialize page structure and memory free list.
26 void page_init(void)
27 {
28         // First, make 'pages' point to an array of size 'npages' of
29         // type 'page_t'.
30         // The kernel uses this structure to keep track of physical pages;
31         // 'npages' equals the number of physical pages in memory.
32         // round up to the nearest page
33         size_t page_array_size = ROUNDUP(npages*sizeof(page_t), PGSIZE);
34         pages = (page_t*)boot_alloc(page_array_size, PGSIZE);
35         memset(pages, 0, page_array_size);
36
37         // Now initilaize everything so pages can start to be alloced and freed
38         // from the memory free list
39         page_alloc_init();
40 }
41
42 //
43 // Map the physical page 'pp' at virtual address 'va'.
44 // The permissions (the low 12 bits) of the page table
45 //  entry should be set to 'perm|PTE_P'.
46 //
47 // Details
48 //   - If there is already a page mapped at 'va', it is page_remove()d.
49 //   - If necessary, on demand, allocates a page table and inserts it into
50 //     'pgdir'.
51 //   - page_incref() should be called if the insertion succeeds.
52 //   - The TLB must be invalidated if a page was formerly present at 'va'.
53 //     (this is handled in page_remove)
54 //
55 // RETURNS: 
56 //   0 on success
57 //   -ENOMEM, if page table couldn't be allocated
58 //
59 // Hint: The TA solution is implemented using pgdir_walk, page_remove,
60 // and page2pa.
61 //
62 // No support for jumbos here.  will need to be careful of trying to insert
63 // regular pages into something that was already jumbo, and the overloading
64 // of the PTE_PS and PTE_PAT flags...
65 int
66 page_insert(pde_t *pgdir, page_t *pp, void *va, int perm) 
67 {
68         pte_t* pte = pgdir_walk(pgdir, va, 1);
69         if (!pte)
70                 return -ENOMEM;
71         // need to up the ref count in case pp is already mapped at va
72         // and we don't want to page_remove (which could free pp) and then 
73         // continue as if pp wasn't freed.  moral = up the ref asap
74         page_incref(pp);
75         if (*pte & PTE_P) {
76                 page_remove(pgdir, va);
77         }
78         *pte = PTE(page2ppn(pp), PTE_P | perm);
79         return 0;
80 }
81
82 //
83 // Map the physical page 'pp' at the first virtual address that is free 
84 // in the range 'vab' to 'vae'.
85 // The permissions (the low 12 bits) of the page table entry get set to 
86 // 'perm|PTE_P'.
87 //
88 // Details
89 //   - If there is no free entry in the range 'vab' to 'vae' this 
90 //     function returns -ENOMEM.
91 //   - If necessary, on demand, this function will allocate a page table 
92 //     and inserts it into 'pgdir'.
93 //   - page_incref() should be called if the insertion succeeds.
94 //
95 // RETURNS: 
96 //   NULL, if no free va in the range (vab, vae) could be found
97 //   va,   the virtual address where pp has been mapped in the 
98 //         range (vab, vae)
99 //
100 void* page_insert_in_range(pde_t *pgdir, page_t *pp, 
101                            void *vab, void *vae, int perm) 
102 {
103         pte_t* pte = NULL;
104         void*SNT new_va;
105         
106         for(new_va = vab; new_va <= vae; new_va+= PGSIZE) {
107                 pte = pgdir_walk(pgdir, new_va, 1);
108                 if(pte != NULL && !(*pte & PTE_P)) break;
109                 else pte = NULL;
110         }
111         if (!pte) return NULL;
112         *pte = page2pa(pp) | PTE_P | perm;
113         return TC(new_va); // trusted because mapping a page is like allocation
114 }
115
116 //
117 // Return the page mapped at virtual address 'va'.
118 // If pte_store is not zero, then we store in it the address
119 // of the pte for this page.  This is used by page_remove
120 // but should not be used by other callers.
121 //
122 // Return 0 if there is no page mapped at va.
123 //
124 // Hint: the TA solution uses pgdir_walk and pa2page.
125 //
126 // For jumbos, right now this returns the first Page* in the 4MB
127 page_t *page_lookup(pde_t *pgdir, void *va, pte_t **pte_store)
128 {
129         pte_t* pte = pgdir_walk(pgdir, va, 0);
130         if (!pte || !(*pte & PTE_P))
131                 return 0;
132         if (pte_store)
133                 *pte_store = pte;
134         return pa2page(PTE_ADDR(*pte));
135 }
136
137 //
138 // Unmaps the physical page at virtual address 'va'.
139 // If there is no physical page at that address, silently does nothing.
140 //
141 // Details:
142 //   - The ref count on the physical page should decrement.
143 //   - The physical page should be freed if the refcount reaches 0.
144 //   - The pg table entry corresponding to 'va' should be set to 0.
145 //     (if such a PTE exists)
146 //   - The TLB must be invalidated if you remove an entry from
147 //     the pg dir/pg table.
148 //
149 // Hint: The TA solution is implemented using page_lookup,
150 //      tlb_invalidate, and page_decref.
151 //
152 // This may be wonky wrt Jumbo pages and decref.  
153 void
154 page_remove(pde_t *pgdir, void *va)
155 {
156         pte_t* pte;
157         page_t *page;
158         page = page_lookup(pgdir, va, &pte);
159         if (!page)
160                 return;
161         *pte = 0;
162         tlb_invalidate(pgdir, va);
163         page_decref(page);
164 }
165
166 //
167 // Invalidate a TLB entry, but only if the page tables being
168 // edited are the ones currently in use by the processor.
169 //
170 // Need to sort this for cross core lovin'  TODO
171 void
172 tlb_invalidate(pde_t *pgdir, void *va)
173 {
174         // Flush the entry only if we're modifying the current address space.
175         // For now, there is only one address space, so always invalidate.
176         invlpg(va);
177 }
178
179 //
180 // Check that an environment is allowed to access the range of memory
181 // [va, va+len) with permissions 'perm | PTE_P'.
182 // Normally 'perm' will contain PTE_U at least, but this is not required.
183 // 'va' and 'len' need not be page-aligned; you must test every page that
184 // contains any of that range.  You will test either 'len/PGSIZE',
185 // 'len/PGSIZE + 1', or 'len/PGSIZE + 2' pages.
186 //
187 // A user program can access a virtual address if (1) the address is below
188 // ULIM, and (2) the page table gives it permission.  These are exactly
189 // the tests you should implement here.
190 //
191 // If there is an error, set the 'user_mem_check_addr' variable to the first
192 // erroneous virtual address.
193 //
194 // Returns 0 if the user program can access this range of addresses,
195 // and -EFAULT otherwise.
196 //
197 // Hint: The TA solution uses pgdir_walk.
198 //
199
200 // zra: I've modified the interface to these two functions so that Ivy can
201 // check that user pointers aren't dereferenced. User pointers get the
202 // DANGEROUS qualifier. After validation, these functions return a
203 // COUNT(len) pointer. user_mem_check now returns NULL on error instead of
204 // -EFAULT.
205
206 void *
207 user_mem_check(env_t *env, const void *DANGEROUS va, size_t len, int perm)
208 {
209         // TODO - will need to sort this out wrt page faulting / PTE_P
210         // also could be issues with sleeping and waking up to find pages
211         // are unmapped, though i think the lab ignores this since the 
212         // kernel is uninterruptible
213         void *DANGEROUS start, *DANGEROUS end;
214         size_t num_pages, i;
215         pte_t *pte;
216
217         perm |= PTE_P;
218         start = ROUNDDOWN((void*DANGEROUS)va, PGSIZE);
219         end = ROUNDUP((void*DANGEROUS)va + len, PGSIZE);
220         if (start >= end) {
221                 warn("Blimey!  Wrap around in VM range calculation!");  
222                 return NULL;
223         }
224         num_pages = PPN(end - start);
225         for (i = 0; i < num_pages; i++, start += PGSIZE) {
226                 pte = pgdir_walk(env->env_pgdir, start, 0);
227                 // ensures the bits we want on are turned on.  if not, error out
228                 if ( !pte || ((*pte & perm) != perm) ) {
229                         if (i == 0)
230                                 user_mem_check_addr = (void*DANGEROUS)va;
231                         else
232                                 user_mem_check_addr = start;
233                         return NULL;
234                 }
235         }
236         // this should never be needed, since the perms should catch it
237         if ((uintptr_t)end > ULIM) {
238                 warn ("I suck - Bug in user permission mappings!");
239                 return NULL;
240         }
241         return (void *COUNT(len))TC(va);
242 }
243
244 size_t
245 user_mem_strlcpy(env_t *env, char *dst, const char *DANGEROUS va,
246                  size_t len, int perm)
247 {
248         const char *DANGEROUS src = va;
249         char *NT COUNT(len-1) dst_in = dst;
250
251         if (len > 0) {
252                 while (1) {
253                         char *c;
254                         if (--len <= 0) break;
255                         c = user_mem_check(env, src, 1, perm);
256                         if (!c) break;
257                         if (*c == '\0') break;
258                         *dst++ = *c;
259                         src++;
260                 }
261                 *dst = '\0';
262         }
263
264         return dst - dst_in;
265 }
266
267 //
268 // Checks that environment 'env' is allowed to access the range
269 // of memory [va, va+len) with permissions 'perm | PTE_U'.
270 // If it can, then the function simply returns.
271 // If it cannot, 'env' is destroyed.
272 //
273 void *
274 user_mem_assert(env_t *env, const void *DANGEROUS va, size_t len, int perm)
275 {
276     void *COUNT(len) res = user_mem_check(env,va,len,perm | PTE_USER_RO);
277         if (!res) {
278                 cprintf("[%08x] user_mem_check assertion failure for "
279                         "va %08x\n", env->env_id, user_mem_check_addr);
280                 proc_destroy(env);      // may not return
281         return NULL;
282         }
283     return res;
284 }
285
286 // copies data from a user buffer to a kernel buffer.
287 // EFAULT if page not present, user lacks perms, or invalid addr.
288 error_t
289 memcpy_from_user(env_t* env, void* COUNT(len) dest,
290                  const void *DANGEROUS va, size_t len)
291 {
292         const void *DANGEROUS start, *DANGEROUS end;
293         size_t num_pages, i;
294         pte_t *pte;
295         uintptr_t perm = PTE_P | PTE_USER_RO;
296         size_t bytes_copied = 0;
297
298         static_assert(ULIM % PGSIZE == 0 && ULIM != 0); // prevent wrap-around
299
300         start = ROUNDDOWN(va, PGSIZE);
301         end = ROUNDUP(va + len, PGSIZE);
302
303         if(start >= (void*SNT)ULIM || end >= (void*SNT)ULIM)
304                 return -EFAULT;
305
306         num_pages = PPN(end - start);
307         for(i = 0; i < num_pages; i++)
308         {
309                 pte = pgdir_walk(env->env_pgdir, start+i*PGSIZE, 0);
310                 if(!pte || (*pte & perm) != perm)
311                         return -EFAULT;
312
313                 void*COUNT(PGSIZE) kpage = KADDR(PTE_ADDR(pte));
314                 void* src_start = i > 0 ? kpage : kpage+(va-start);
315                 void* dst_start = dest+bytes_copied;
316                 size_t copy_len = PGSIZE;
317                 if(i == 0)
318                         copy_len -= va-start;
319                 if(i == num_pages-1)
320                         copy_len -= end-(start+len);
321
322                 memcpy(dst_start,src_start,copy_len);
323                 bytes_copied += copy_len;
324         }
325
326         assert(bytes_copied == len);
327
328         return ESUCCESS;
329 }