kern

view src/vm.c @ 43:5f6c5751ae05

- implemented clone_vmem
author John Tsiombikas <nuclear@member.fsf.org>
date Mon, 25 Jul 2011 11:29:02 +0300
parents 373a9f50b4e6
children b8f02479e3f4
line source
1 #include <stdio.h>
2 #include <string.h>
3 #include <inttypes.h>
4 #include "vm.h"
5 #include <stdio.h>
6 #include "intr.h"
7 #include "mem.h"
8 #include "panic.h"
11 #define KMEM_START 0xc0000000
12 #define IDMAP_START 0xa0000
14 #define PGDIR_ADDR 0xfffff000
15 #define PGTBL_BASE (0xffffffff - 4096 * 1024 + 1)
16 #define PGTBL(x) ((uint32_t*)(PGTBL_BASE + PGSIZE * (x)))
18 #define ATTR_PGDIR_MASK 0x3f
19 #define ATTR_PGTBL_MASK 0x1ff
20 #define ADDR_PGENT_MASK 0xfffff000
22 #define PAGEFAULT 14
25 struct page_range {
26 int start, end;
27 struct page_range *next;
28 };
30 /* defined in vm-asm.S */
31 void enable_paging(void);
32 void disable_paging(void);
33 int get_paging_status(void);
34 void set_pgdir_addr(uint32_t addr);
35 void flush_tlb(void);
36 void flush_tlb_addr(uint32_t addr);
37 #define flush_tlb_page(p) flush_tlb_addr(PAGE_TO_ADDR(p))
38 uint32_t get_fault_addr(void);
40 static void coalesce(struct page_range *low, struct page_range *mid, struct page_range *high);
41 static void pgfault(int inum, uint32_t err);
42 static struct page_range *alloc_node(void);
43 static void free_node(struct page_range *node);
45 /* page directory */
46 static uint32_t *pgdir;
48 /* 2 lists of free ranges, for kernel memory and user memory */
49 static struct page_range *pglist[2];
50 /* list of free page_range structures to be used in the lists */
51 static struct page_range *node_pool;
52 /* the first page range for the whole kernel address space, to get things started */
53 static struct page_range first_node;
56 void init_vm(void)
57 {
58 uint32_t idmap_end;
60 /* setup the page tables */
61 pgdir = (uint32_t*)alloc_phys_page();
62 memset(pgdir, 0, PGSIZE);
63 set_pgdir_addr((uint32_t)pgdir);
65 /* map the video memory and kernel code 1-1 */
66 get_kernel_mem_range(0, &idmap_end);
67 map_mem_range(IDMAP_START, idmap_end - IDMAP_START, IDMAP_START, 0);
69 /* make the last page directory entry point to the page directory */
70 pgdir[1023] = ((uint32_t)pgdir & ADDR_PGENT_MASK) | PG_PRESENT;
71 pgdir = (uint32_t*)PGDIR_ADDR;
73 /* set the page fault handler */
74 interrupt(PAGEFAULT, pgfault);
76 /* we can enable paging now */
77 enable_paging();
79 /* initialize the virtual page allocator */
80 node_pool = 0;
82 first_node.start = ADDR_TO_PAGE(KMEM_START);
83 first_node.end = ADDR_TO_PAGE(PGTBL_BASE);
84 first_node.next = 0;
85 pglist[MEM_KERNEL] = &first_node;
87 pglist[MEM_USER] = alloc_node();
88 pglist[MEM_USER]->start = ADDR_TO_PAGE(idmap_end);
89 pglist[MEM_USER]->end = ADDR_TO_PAGE(KMEM_START);
90 pglist[MEM_USER]->next = 0;
91 }
93 /* if ppage == -1 we allocate a physical page by calling alloc_phys_page */
94 int map_page(int vpage, int ppage, unsigned int attr)
95 {
96 uint32_t *pgtbl;
97 int diridx, pgidx, pgon, intr_state;
99 intr_state = get_intr_state();
100 disable_intr();
102 pgon = get_paging_status();
104 if(ppage < 0) {
105 uint32_t addr = alloc_phys_page();
106 if(!addr) {
107 set_intr_state(intr_state);
108 return -1;
109 }
110 ppage = ADDR_TO_PAGE(addr);
111 }
113 diridx = PAGE_TO_PGTBL(vpage);
114 pgidx = PAGE_TO_PGTBL_PG(vpage);
116 if(!(pgdir[diridx] & PG_PRESENT)) {
117 uint32_t addr = alloc_phys_page();
118 pgdir[diridx] = addr | (attr & ATTR_PGDIR_MASK) | PG_PRESENT;
120 pgtbl = pgon ? PGTBL(diridx) : (uint32_t*)addr;
121 memset(pgtbl, 0, PGSIZE);
122 } else {
123 if(pgon) {
124 pgtbl = PGTBL(diridx);
125 } else {
126 pgtbl = (uint32_t*)(pgdir[diridx] & ADDR_PGENT_MASK);
127 }
128 }
130 pgtbl[pgidx] = PAGE_TO_ADDR(ppage) | (attr & ATTR_PGTBL_MASK) | PG_PRESENT;
131 flush_tlb_page(vpage);
133 set_intr_state(intr_state);
134 return 0;
135 }
137 int unmap_page(int vpage)
138 {
139 uint32_t *pgtbl;
140 int res = 0;
141 int diridx = PAGE_TO_PGTBL(vpage);
142 int pgidx = PAGE_TO_PGTBL_PG(vpage);
144 int intr_state = get_intr_state();
145 disable_intr();
147 if(!(pgdir[diridx] & PG_PRESENT)) {
148 goto err;
149 }
150 pgtbl = PGTBL(diridx);
152 if(!(pgtbl[pgidx] & PG_PRESENT)) {
153 goto err;
154 }
155 pgtbl[pgidx] = 0;
156 flush_tlb_page(vpage);
158 if(0) {
159 err:
160 printf("unmap_page(%d): page already not mapped\n", vpage);
161 res = -1;
162 }
163 set_intr_state(intr_state);
164 return res;
165 }
167 /* if ppg_start is -1, we allocate physical pages to map with alloc_phys_page() */
168 int map_page_range(int vpg_start, int pgcount, int ppg_start, unsigned int attr)
169 {
170 int i, phys_pg;
172 for(i=0; i<pgcount; i++) {
173 phys_pg = ppg_start < 0 ? -1 : ppg_start + i;
174 map_page(vpg_start + i, phys_pg, attr);
175 }
176 return 0;
177 }
179 int unmap_page_range(int vpg_start, int pgcount)
180 {
181 int i, res = 0;
183 for(i=0; i<pgcount; i++) {
184 if(unmap_page(vpg_start + i) == -1) {
185 res = -1;
186 }
187 }
188 return res;
189 }
191 /* if paddr is 0, we allocate physical pages with alloc_phys_page() */
192 int map_mem_range(uint32_t vaddr, size_t sz, uint32_t paddr, unsigned int attr)
193 {
194 int vpg_start, ppg_start, num_pages;
196 if(!sz) return -1;
198 if(ADDR_TO_PGOFFS(paddr)) {
199 panic("map_mem_range called with unaligned physical address: %x\n", paddr);
200 }
202 vpg_start = ADDR_TO_PAGE(vaddr);
203 ppg_start = paddr > 0 ? ADDR_TO_PAGE(paddr) : -1;
204 num_pages = ADDR_TO_PAGE(sz) + 1;
206 return map_page_range(vpg_start, num_pages, ppg_start, attr);
207 }
209 uint32_t virt_to_phys(uint32_t vaddr)
210 {
211 int pg;
212 uint32_t pgaddr;
214 if((pg = virt_to_phys_page(ADDR_TO_PAGE(vaddr))) == -1) {
215 return 0;
216 }
217 pgaddr = PAGE_TO_ADDR(pg);
219 return pgaddr | ADDR_TO_PGOFFS(vaddr);
220 }
222 int virt_to_phys_page(int vpg)
223 {
224 uint32_t pgaddr, *pgtbl;
225 int diridx, pgidx;
227 if(vpg < 0 || vpg >= PAGE_COUNT) {
228 return -1;
229 }
231 diridx = PAGE_TO_PGTBL(vpg);
232 pgidx = PAGE_TO_PGTBL_PG(vpg);
234 if(!(pgdir[diridx] & PG_PRESENT)) {
235 return -1;
236 }
237 pgtbl = PGTBL(diridx);
239 if(!(pgtbl[pgidx] & PG_PRESENT)) {
240 return -1;
241 }
242 pgaddr = pgtbl[pgidx] & PGENT_ADDR_MASK;
243 return ADDR_TO_PAGE(pgaddr);
244 }
246 /* allocate a contiguous block of virtual memory pages along with
247 * backing physical memory for them, and update the page table.
248 */
249 int pgalloc(int num, int area)
250 {
251 int intr_state, ret = -1;
252 struct page_range *node, *prev, dummy;
254 intr_state = get_intr_state();
255 disable_intr();
257 dummy.next = pglist[area];
258 node = pglist[area];
259 prev = &dummy;
261 while(node) {
262 if(node->end - node->start >= num) {
263 ret = node->start;
264 node->start += num;
266 if(node->start == node->end) {
267 prev->next = node->next;
268 node->next = 0;
270 if(node == pglist[area]) {
271 pglist[area] = 0;
272 }
273 free_node(node);
274 }
275 break;
276 }
278 prev = node;
279 node = node->next;
280 }
282 if(ret >= 0) {
283 /* allocate physical storage and map */
284 if(map_page_range(ret, num, -1, 0) == -1) {
285 ret = -1;
286 }
287 }
289 set_intr_state(intr_state);
290 return ret;
291 }
293 void pgfree(int start, int num)
294 {
295 int i, area, intr_state;
296 struct page_range *node, *new, *prev, *next;
298 intr_state = get_intr_state();
299 disable_intr();
301 for(i=0; i<num; i++) {
302 int phys_pg = virt_to_phys_page(start + i);
303 if(phys_pg != -1) {
304 free_phys_page(phys_pg);
305 }
306 }
308 if(!(new = alloc_node())) {
309 panic("pgfree: can't allocate new page_range node to add the freed pages\n");
310 }
311 new->start = start;
312 new->end = start + num;
314 area = PAGE_TO_ADDR(start) >= KMEM_START ? MEM_KERNEL : MEM_USER;
316 if(!pglist[area] || pglist[area]->start > start) {
317 next = new->next = pglist[area];
318 pglist[area] = new;
319 prev = 0;
321 } else {
323 prev = 0;
324 node = pglist[area];
325 next = node ? node->next : 0;
327 while(node) {
328 if(!next || next->start > start) {
329 /* place here, after node */
330 new->next = next;
331 node->next = new;
332 prev = node; /* needed by coalesce after the loop */
333 break;
334 }
336 prev = node;
337 node = next;
338 next = node ? node->next : 0;
339 }
340 }
342 coalesce(prev, new, next);
343 set_intr_state(intr_state);
344 }
346 static void coalesce(struct page_range *low, struct page_range *mid, struct page_range *high)
347 {
348 if(high) {
349 if(mid->end == high->start) {
350 mid->end = high->end;
351 mid->next = high->next;
352 free_node(high);
353 }
354 }
356 if(low) {
357 if(low->end == mid->start) {
358 low->end += mid->end;
359 low->next = mid->next;
360 free_node(mid);
361 }
362 }
363 }
365 static void pgfault(int inum, uint32_t err)
366 {
367 printf("~~~~ PAGE FAULT ~~~~\n");
369 printf("fault address: %x\n", get_fault_addr());
371 if(err & PG_PRESENT) {
372 if(err & 8) {
373 printf("reserved bit set in some paging structure\n");
374 } else {
375 printf("%s protection violation ", (err & PG_WRITABLE) ? "write" : "read");
376 printf("in %s mode\n", err & PG_USER ? "user" : "kernel");
377 }
378 } else {
379 printf("page not present\n");
380 }
382 panic("unhandled page fault\n");
383 }
385 /* --- page range list node management --- */
386 #define NODES_IN_PAGE (PGSIZE / sizeof(struct page_range))
388 static struct page_range *alloc_node(void)
389 {
390 struct page_range *node;
391 int pg, i;
393 if(node_pool) {
394 node = node_pool;
395 node_pool = node_pool->next;
396 printf("alloc_node -> %x\n", (unsigned int)node);
397 return node;
398 }
400 /* no node structures in the pool, we need to allocate a new page,
401 * split it up into node structures, add them in the pool, and
402 * allocate one of them.
403 */
404 if(!(pg = pgalloc(1, MEM_KERNEL))) {
405 panic("ran out of physical memory while allocating VM range structures\n");
406 }
407 node_pool = (struct page_range*)PAGE_TO_ADDR(pg);
409 /* link them up, skip the first as we'll just allocate it anyway */
410 for(i=2; i<NODES_IN_PAGE; i++) {
411 node_pool[i - 1].next = node_pool + i;
412 }
413 node_pool[NODES_IN_PAGE - 1].next = 0;
415 /* grab the first and return it */
416 node = node_pool++;
417 printf("alloc_node -> %x\n", (unsigned int)node);
418 return node;
419 }
421 static void free_node(struct page_range *node)
422 {
423 node->next = node_pool;
424 node_pool = node;
425 printf("free_node\n");
426 }
429 /* clone_vmem makes a copy of the current page tables, thus duplicating
430 * the virtual address space.
431 *
432 * Returns the physical address of the new page directory.
433 */
434 uint32_t clone_vmem(void)
435 {
436 int i, dirpg, tblpg;
437 uint32_t paddr;
438 uint32_t *ndir, *ntbl;
440 if((dirpg = pgalloc(1, MEM_KERNEL)) == -1) {
441 panic("clone_vmem: failed to allocate page directory page\n");
442 }
443 ndir = (uint32_t*)PAGE_TO_ADDR(dirpg);
445 if((tblpg = pgalloc(1, MEM_KERNEL)) == -1) {
446 panic("clone_vmem: failed to allocate page table page\n");
447 }
448 ntbl = (uint32_t*)PAGE_TO_ADDR(tblpg);
450 /* we will allocate physical pages and map them to this virtual page
451 * as needed in the loop below.
452 */
453 free_phys_page(virt_to_phys(tblpg));
455 for(i=0; i<1024; i++) {
456 if(pgdir[i] & PG_PRESENT) {
457 paddr = alloc_phys_page();
458 map_page(tblpg, ADDR_TO_PAGE(paddr), 0);
460 /* copy the page table */
461 memcpy(ntbl, PGTBL(i), PGSIZE);
463 /* set the new page directory entry */
464 ndir[i] = paddr | (pgdir[i] & PGOFFS_MASK);
465 } else {
466 ndir[i] = 0;
467 }
468 }
470 paddr = virt_to_phys(dirpg);
472 /* unmap before freeing to avoid deallocating the physical pages */
473 unmap_page(dirpg);
474 unmap_page(tblpg);
476 pgfree(dirpg, 1);
477 pgfree(tblpg, 1);
479 return paddr;
480 }
483 void dbg_print_vm(int area)
484 {
485 struct page_range *node;
486 int last, intr_state;
488 intr_state = get_intr_state();
489 disable_intr();
491 node = pglist[area];
492 last = area == MEM_USER ? 0 : ADDR_TO_PAGE(KMEM_START);
494 printf("%s vm space\n", area == MEM_USER ? "user" : "kernel");
496 while(node) {
497 if(node->start > last) {
498 printf(" vm-used: %x -> %x\n", PAGE_TO_ADDR(last), PAGE_TO_ADDR(node->start));
499 }
501 printf(" vm-free: %x -> ", PAGE_TO_ADDR(node->start));
502 if(node->end >= PAGE_COUNT) {
503 printf("END\n");
504 } else {
505 printf("%x\n", PAGE_TO_ADDR(node->end));
506 }
508 last = node->end;
509 node = node->next;
510 }
512 set_intr_state(intr_state);
513 }