kern
view src/vm.c @ 47:f65b348780e3
continuing with the process implementation. not done yet, panics.
author | John Tsiombikas <nuclear@member.fsf.org> |
---|---|
date | Thu, 28 Jul 2011 05:43:04 +0300 |
parents | b8f02479e3f4 |
children | 4c9c16754b59 |
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 IDMAP_START 0xa0000
13 #define PGDIR_ADDR 0xfffff000
14 #define PGTBL_BASE (0xffffffff - 4096 * 1024 + 1)
15 #define PGTBL(x) ((uint32_t*)(PGTBL_BASE + PGSIZE * (x)))
17 #define ATTR_PGDIR_MASK 0x3f
18 #define ATTR_PGTBL_MASK 0x1ff
19 #define ADDR_PGENT_MASK 0xfffff000
21 #define PAGEFAULT 14
24 struct page_range {
25 int start, end;
26 struct page_range *next;
27 };
29 /* defined in vm-asm.S */
30 void enable_paging(void);
31 void disable_paging(void);
32 int get_paging_status(void);
33 void set_pgdir_addr(uint32_t addr);
34 void flush_tlb(void);
35 void flush_tlb_addr(uint32_t addr);
36 #define flush_tlb_page(p) flush_tlb_addr(PAGE_TO_ADDR(p))
37 uint32_t get_fault_addr(void);
39 static void coalesce(struct page_range *low, struct page_range *mid, struct page_range *high);
40 static void pgfault(int inum, uint32_t err);
41 static struct page_range *alloc_node(void);
42 static void free_node(struct page_range *node);
44 /* page directory */
45 static uint32_t *pgdir;
47 /* 2 lists of free ranges, for kernel memory and user memory */
48 static struct page_range *pglist[2];
49 /* list of free page_range structures to be used in the lists */
50 static struct page_range *node_pool;
51 /* the first page range for the whole kernel address space, to get things started */
52 static struct page_range first_node;
55 void init_vm(void)
56 {
57 uint32_t idmap_end;
58 int i, kmem_start_pg, pgtbl_base_pg;
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 kmem_start_pg = ADDR_TO_PAGE(KMEM_START);
83 pgtbl_base_pg = ADDR_TO_PAGE(PGTBL_BASE);
85 first_node.start = kmem_start_pg;
86 first_node.end = pgtbl_base_pg;
87 first_node.next = 0;
88 pglist[MEM_KERNEL] = &first_node;
90 pglist[MEM_USER] = alloc_node();
91 pglist[MEM_USER]->start = ADDR_TO_PAGE(idmap_end);
92 pglist[MEM_USER]->end = kmem_start_pg;
93 pglist[MEM_USER]->next = 0;
95 /* temporaroly map something into every 1024th page of the kernel address
96 * space to force pre-allocation of all the kernel page-tables
97 */
98 for(i=kmem_start_pg; i<pgtbl_base_pg; i+=1024) {
99 /* if there's already something mapped here, leave it alone */
100 if(virt_to_phys_page(i) == -1) {
101 map_page(i, 0, 0);
102 unmap_page(i);
103 }
104 }
105 }
107 /* if ppage == -1 we allocate a physical page by calling alloc_phys_page */
108 int map_page(int vpage, int ppage, unsigned int attr)
109 {
110 uint32_t *pgtbl;
111 int diridx, pgidx, pgon, intr_state;
113 intr_state = get_intr_state();
114 disable_intr();
116 pgon = get_paging_status();
118 if(ppage < 0) {
119 uint32_t addr = alloc_phys_page();
120 if(!addr) {
121 set_intr_state(intr_state);
122 return -1;
123 }
124 ppage = ADDR_TO_PAGE(addr);
125 }
127 diridx = PAGE_TO_PGTBL(vpage);
128 pgidx = PAGE_TO_PGTBL_PG(vpage);
130 if(!(pgdir[diridx] & PG_PRESENT)) {
131 uint32_t addr = alloc_phys_page();
132 pgdir[diridx] = addr | (attr & ATTR_PGDIR_MASK) | PG_PRESENT;
134 pgtbl = pgon ? PGTBL(diridx) : (uint32_t*)addr;
135 memset(pgtbl, 0, PGSIZE);
136 } else {
137 if(pgon) {
138 pgtbl = PGTBL(diridx);
139 } else {
140 pgtbl = (uint32_t*)(pgdir[diridx] & ADDR_PGENT_MASK);
141 }
142 }
144 pgtbl[pgidx] = PAGE_TO_ADDR(ppage) | (attr & ATTR_PGTBL_MASK) | PG_PRESENT;
145 flush_tlb_page(vpage);
147 set_intr_state(intr_state);
148 return 0;
149 }
151 int unmap_page(int vpage)
152 {
153 uint32_t *pgtbl;
154 int res = 0;
155 int diridx = PAGE_TO_PGTBL(vpage);
156 int pgidx = PAGE_TO_PGTBL_PG(vpage);
158 int intr_state = get_intr_state();
159 disable_intr();
161 if(!(pgdir[diridx] & PG_PRESENT)) {
162 goto err;
163 }
164 pgtbl = PGTBL(diridx);
166 if(!(pgtbl[pgidx] & PG_PRESENT)) {
167 goto err;
168 }
169 pgtbl[pgidx] = 0;
170 flush_tlb_page(vpage);
172 if(0) {
173 err:
174 printf("unmap_page(%d): page already not mapped\n", vpage);
175 res = -1;
176 }
177 set_intr_state(intr_state);
178 return res;
179 }
181 /* if ppg_start is -1, we allocate physical pages to map with alloc_phys_page() */
182 int map_page_range(int vpg_start, int pgcount, int ppg_start, unsigned int attr)
183 {
184 int i, phys_pg;
186 for(i=0; i<pgcount; i++) {
187 phys_pg = ppg_start < 0 ? -1 : ppg_start + i;
188 map_page(vpg_start + i, phys_pg, attr);
189 }
190 return 0;
191 }
193 int unmap_page_range(int vpg_start, int pgcount)
194 {
195 int i, res = 0;
197 for(i=0; i<pgcount; i++) {
198 if(unmap_page(vpg_start + i) == -1) {
199 res = -1;
200 }
201 }
202 return res;
203 }
205 /* if paddr is 0, we allocate physical pages with alloc_phys_page() */
206 int map_mem_range(uint32_t vaddr, size_t sz, uint32_t paddr, unsigned int attr)
207 {
208 int vpg_start, ppg_start, num_pages;
210 if(!sz) return -1;
212 if(ADDR_TO_PGOFFS(paddr)) {
213 panic("map_mem_range called with unaligned physical address: %x\n", paddr);
214 }
216 vpg_start = ADDR_TO_PAGE(vaddr);
217 ppg_start = paddr > 0 ? ADDR_TO_PAGE(paddr) : -1;
218 num_pages = ADDR_TO_PAGE(sz) + 1;
220 return map_page_range(vpg_start, num_pages, ppg_start, attr);
221 }
223 uint32_t virt_to_phys(uint32_t vaddr)
224 {
225 int pg;
226 uint32_t pgaddr;
228 if((pg = virt_to_phys_page(ADDR_TO_PAGE(vaddr))) == -1) {
229 return 0;
230 }
231 pgaddr = PAGE_TO_ADDR(pg);
233 return pgaddr | ADDR_TO_PGOFFS(vaddr);
234 }
236 int virt_to_phys_page(int vpg)
237 {
238 uint32_t pgaddr, *pgtbl;
239 int diridx, pgidx;
241 if(vpg < 0 || vpg >= PAGE_COUNT) {
242 return -1;
243 }
245 diridx = PAGE_TO_PGTBL(vpg);
246 pgidx = PAGE_TO_PGTBL_PG(vpg);
248 if(!(pgdir[diridx] & PG_PRESENT)) {
249 return -1;
250 }
251 pgtbl = PGTBL(diridx);
253 if(!(pgtbl[pgidx] & PG_PRESENT)) {
254 return -1;
255 }
256 pgaddr = pgtbl[pgidx] & PGENT_ADDR_MASK;
257 return ADDR_TO_PAGE(pgaddr);
258 }
260 /* allocate a contiguous block of virtual memory pages along with
261 * backing physical memory for them, and update the page table.
262 */
263 int pgalloc(int num, int area)
264 {
265 int intr_state, ret = -1;
266 struct page_range *node, *prev, dummy;
267 unsigned int attr = 0; /* TODO */
269 intr_state = get_intr_state();
270 disable_intr();
272 dummy.next = pglist[area];
273 node = pglist[area];
274 prev = &dummy;
276 while(node) {
277 if(node->end - node->start >= num) {
278 ret = node->start;
279 node->start += num;
281 if(node->start == node->end) {
282 prev->next = node->next;
283 node->next = 0;
285 if(node == pglist[area]) {
286 pglist[area] = 0;
287 }
288 free_node(node);
289 }
290 break;
291 }
293 prev = node;
294 node = node->next;
295 }
297 if(ret >= 0) {
298 /* allocate physical storage and map */
299 if(map_page_range(ret, num, -1, attr) == -1) {
300 ret = -1;
301 }
302 }
304 set_intr_state(intr_state);
305 return ret;
306 }
308 int pgalloc_vrange(int start, int num)
309 {
310 struct page_range *node, *prev, dummy;
311 int area, intr_state, ret = -1;
312 unsigned int attr = 0; /* TODO */
314 area = (start >= ADDR_TO_PAGE(KMEM_START)) ? MEM_KERNEL : MEM_USER;
315 if(area == MEM_USER && start + num > ADDR_TO_PAGE(KMEM_START)) {
316 printf("pgalloc_vrange: invalid range request crossing user/kernel split\n");
317 return -1;
318 }
320 intr_state = get_intr_state();
321 disable_intr();
323 dummy.next = pglist[area];
324 node = pglist[area];
325 prev = &dummy;
327 /* check to see if the requested VM range is available */
328 node = pglist[area];
329 while(node) {
330 if(start >= node->start && start + num <= node->end) {
331 ret = node->start;
332 node->start += num;
334 if(node->start == node->end) {
335 prev->next = node->next;
336 node->next = 0;
338 if(node == pglist[area]) {
339 pglist[area] = 0;
340 }
341 free_node(node);
342 }
343 break;
344 }
346 prev = node;
347 node = node->next;
348 }
350 if(ret >= 0) {
351 /* allocate physical storage and map */
352 if(map_page_range(ret, num, -1, attr) == -1) {
353 ret = -1;
354 }
355 }
357 set_intr_state(intr_state);
358 return ret;
359 }
361 void pgfree(int start, int num)
362 {
363 int i, area, intr_state;
364 struct page_range *node, *new, *prev, *next;
366 intr_state = get_intr_state();
367 disable_intr();
369 for(i=0; i<num; i++) {
370 int phys_pg = virt_to_phys_page(start + i);
371 if(phys_pg != -1) {
372 free_phys_page(phys_pg);
373 }
374 }
376 if(!(new = alloc_node())) {
377 panic("pgfree: can't allocate new page_range node to add the freed pages\n");
378 }
379 new->start = start;
380 new->end = start + num;
382 area = PAGE_TO_ADDR(start) >= KMEM_START ? MEM_KERNEL : MEM_USER;
384 if(!pglist[area] || pglist[area]->start > start) {
385 next = new->next = pglist[area];
386 pglist[area] = new;
387 prev = 0;
389 } else {
391 prev = 0;
392 node = pglist[area];
393 next = node ? node->next : 0;
395 while(node) {
396 if(!next || next->start > start) {
397 /* place here, after node */
398 new->next = next;
399 node->next = new;
400 prev = node; /* needed by coalesce after the loop */
401 break;
402 }
404 prev = node;
405 node = next;
406 next = node ? node->next : 0;
407 }
408 }
410 coalesce(prev, new, next);
411 set_intr_state(intr_state);
412 }
414 static void coalesce(struct page_range *low, struct page_range *mid, struct page_range *high)
415 {
416 if(high) {
417 if(mid->end == high->start) {
418 mid->end = high->end;
419 mid->next = high->next;
420 free_node(high);
421 }
422 }
424 if(low) {
425 if(low->end == mid->start) {
426 low->end += mid->end;
427 low->next = mid->next;
428 free_node(mid);
429 }
430 }
431 }
433 static void pgfault(int inum, uint32_t err)
434 {
435 printf("~~~~ PAGE FAULT ~~~~\n");
437 printf("fault address: %x\n", get_fault_addr());
439 if(err & PG_PRESENT) {
440 if(err & 8) {
441 printf("reserved bit set in some paging structure\n");
442 } else {
443 printf("%s protection violation ", (err & PG_WRITABLE) ? "write" : "read");
444 printf("in %s mode\n", err & PG_USER ? "user" : "kernel");
445 }
446 } else {
447 printf("page not present\n");
448 }
450 panic("unhandled page fault\n");
451 }
453 /* --- page range list node management --- */
454 #define NODES_IN_PAGE (PGSIZE / sizeof(struct page_range))
456 static struct page_range *alloc_node(void)
457 {
458 struct page_range *node;
459 int pg, i;
461 if(node_pool) {
462 node = node_pool;
463 node_pool = node_pool->next;
464 /*printf("alloc_node -> %x\n", (unsigned int)node);*/
465 return node;
466 }
468 /* no node structures in the pool, we need to allocate a new page,
469 * split it up into node structures, add them in the pool, and
470 * allocate one of them.
471 */
472 if(!(pg = pgalloc(1, MEM_KERNEL))) {
473 panic("ran out of physical memory while allocating VM range structures\n");
474 }
475 node_pool = (struct page_range*)PAGE_TO_ADDR(pg);
477 /* link them up, skip the first as we'll just allocate it anyway */
478 for(i=2; i<NODES_IN_PAGE; i++) {
479 node_pool[i - 1].next = node_pool + i;
480 }
481 node_pool[NODES_IN_PAGE - 1].next = 0;
483 /* grab the first and return it */
484 node = node_pool++;
485 /*printf("alloc_node -> %x\n", (unsigned int)node);*/
486 return node;
487 }
489 static void free_node(struct page_range *node)
490 {
491 node->next = node_pool;
492 node_pool = node;
493 /*printf("free_node\n");*/
494 }
497 /* clone_vm makes a copy of the current page tables, thus duplicating the
498 * virtual address space.
499 *
500 * For the kernel part of the address space (last 256 page directory entries)
501 * we don't want to diplicate the page tables, just point all page directory
502 * entries to the same set of page tables.
503 *
504 * Returns the physical address of the new page directory.
505 */
506 uint32_t clone_vm(void)
507 {
508 int i, dirpg, tblpg, kmem_start_pg;
509 uint32_t paddr;
510 uint32_t *ndir, *ntbl;
512 /* allocate the new page directory */
513 if((dirpg = pgalloc(1, MEM_KERNEL)) == -1) {
514 panic("clone_vmem: failed to allocate page directory page\n");
515 }
516 ndir = (uint32_t*)PAGE_TO_ADDR(dirpg);
518 /* allocate a virtual page for temporarily mapping all new
519 * page tables while we populate them.
520 */
521 if((tblpg = pgalloc(1, MEM_KERNEL)) == -1) {
522 panic("clone_vmem: failed to allocate page table page\n");
523 }
524 ntbl = (uint32_t*)PAGE_TO_ADDR(tblpg);
526 /* we will allocate physical pages and map them to this virtual page
527 * as needed in the loop below.
528 */
529 free_phys_page(virt_to_phys(tblpg));
531 kmem_start_pg = ADDR_TO_PAGE(KMEM_START);
533 /* user space */
534 for(i=0; i<kmem_start_pg; i++) {
535 if(pgdir[i] & PG_PRESENT) {
536 paddr = alloc_phys_page();
537 map_page(tblpg, ADDR_TO_PAGE(paddr), 0);
539 /* copy the page table */
540 memcpy(ntbl, PGTBL(i), PGSIZE);
542 /* set the new page directory entry */
543 ndir[i] = paddr | (pgdir[i] & PGOFFS_MASK);
544 } else {
545 ndir[i] = 0;
546 }
547 }
549 /* kernel space */
550 for(i=kmem_start_pg; i<1024; i++) {
551 ndir[i] = *PGTBL(i);
552 }
554 paddr = virt_to_phys(dirpg);
556 /* unmap before freeing to avoid deallocating the physical pages */
557 unmap_page(dirpg);
558 unmap_page(tblpg);
560 pgfree(dirpg, 1);
561 pgfree(tblpg, 1);
563 return paddr;
564 }
567 void dbg_print_vm(int area)
568 {
569 struct page_range *node;
570 int last, intr_state;
572 intr_state = get_intr_state();
573 disable_intr();
575 node = pglist[area];
576 last = area == MEM_USER ? 0 : ADDR_TO_PAGE(KMEM_START);
578 printf("%s vm space\n", area == MEM_USER ? "user" : "kernel");
580 while(node) {
581 if(node->start > last) {
582 printf(" vm-used: %x -> %x\n", PAGE_TO_ADDR(last), PAGE_TO_ADDR(node->start));
583 }
585 printf(" vm-free: %x -> ", PAGE_TO_ADDR(node->start));
586 if(node->end >= PAGE_COUNT) {
587 printf("END\n");
588 } else {
589 printf("%x\n", PAGE_TO_ADDR(node->end));
590 }
592 last = node->end;
593 node = node->next;
594 }
596 set_intr_state(intr_state);
597 }