kern

view src/vm.c @ 29:8ea6debe4265

starting on the task switching implementation
author John Tsiombikas <nuclear@siggraph.org>
date Sat, 23 Apr 2011 00:51:18 +0300
parents 53588744382c
children 387078ef5c0d
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(struct mboot_info *mb)
57 {
58 uint32_t idmap_end;
60 /* initialize the physical memory map and allocator */
61 init_mem(mb);
63 /* setup the page tables */
64 pgdir = (uint32_t*)alloc_phys_page();
65 memset(pgdir, 0, PGSIZE);
66 set_pgdir_addr((uint32_t)pgdir);
68 /* map the video memory and kernel code 1-1 */
69 get_kernel_mem_range(0, &idmap_end);
70 map_mem_range(IDMAP_START, idmap_end - IDMAP_START, IDMAP_START, 0);
72 /* make the last page directory entry point to the page directory */
73 pgdir[1023] = ((uint32_t)pgdir & ADDR_PGENT_MASK) | PG_PRESENT;
74 pgdir = (uint32_t*)PGDIR_ADDR;
76 /* set the page fault handler */
77 interrupt(PAGEFAULT, pgfault);
79 /* we can enable paging now */
80 enable_paging();
82 /* initialize the virtual page allocator */
83 node_pool = 0;
85 first_node.start = ADDR_TO_PAGE(KMEM_START);
86 first_node.end = PAGE_COUNT;
87 first_node.next = 0;
88 pglist[MEM_KERNEL] = &first_node;
90 pglist[MEM_USER] = alloc_node();
91 pglist[MEM_USER]->start = 0;
92 pglist[MEM_USER]->end = ADDR_TO_PAGE(KMEM_START);
93 pglist[MEM_USER]->next = 0;
94 }
96 /* if ppage == -1 we allocate a physical page by calling alloc_phys_page */
97 int map_page(int vpage, int ppage, unsigned int attr)
98 {
99 uint32_t *pgtbl;
100 int diridx, pgidx, pgon, intr_state;
102 intr_state = get_intr_state();
103 disable_intr();
105 pgon = get_paging_status();
107 if(ppage < 0) {
108 uint32_t addr = alloc_phys_page();
109 if(!addr) {
110 set_intr_state(intr_state);
111 return -1;
112 }
113 ppage = ADDR_TO_PAGE(addr);
114 }
116 diridx = PAGE_TO_PGTBL(vpage);
117 pgidx = PAGE_TO_PGTBL_PG(vpage);
119 if(!(pgdir[diridx] & PG_PRESENT)) {
120 uint32_t addr = alloc_phys_page();
121 pgdir[diridx] = addr | (attr & ATTR_PGDIR_MASK) | PG_PRESENT;
123 pgtbl = pgon ? PGTBL(diridx) : (uint32_t*)addr;
124 memset(pgtbl, 0, PGSIZE);
125 } else {
126 if(pgon) {
127 pgtbl = PGTBL(diridx);
128 } else {
129 pgtbl = (uint32_t*)(pgdir[diridx] & ADDR_PGENT_MASK);
130 }
131 }
133 pgtbl[pgidx] = PAGE_TO_ADDR(ppage) | (attr & ATTR_PGTBL_MASK) | PG_PRESENT;
134 flush_tlb_page(vpage);
136 set_intr_state(intr_state);
137 return 0;
138 }
140 void unmap_page(int vpage)
141 {
142 uint32_t *pgtbl;
143 int diridx = PAGE_TO_PGTBL(vpage);
144 int pgidx = PAGE_TO_PGTBL_PG(vpage);
146 int intr_state = get_intr_state();
147 disable_intr();
149 if(!(pgdir[diridx] & PG_PRESENT)) {
150 goto err;
151 }
152 pgtbl = (uint32_t*)(pgdir[diridx] & ADDR_PGENT_MASK);
154 if(!(pgtbl[pgidx] & PG_PRESENT)) {
155 goto err;
156 }
157 pgtbl[pgidx] = 0;
158 flush_tlb_page(vpage);
160 if(0) {
161 err:
162 printf("unmap_page(%d): page already not mapped\n", vpage);
163 }
164 set_intr_state(intr_state);
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;
171 uint32_t paddr;
173 for(i=0; i<pgcount; i++) {
174 if(ppg_start < 0) {
175 if(!(paddr = alloc_phys_page())) {
176 return -1;
177 }
178 phys_pg = ADDR_TO_PAGE(paddr);
179 } else {
180 phys_pg = ppg_start + i;
181 }
183 map_page(vpg_start + i, phys_pg, attr);
184 }
185 return 0;
186 }
188 /* if paddr is 0, we allocate physical pages with alloc_phys_page() */
189 int map_mem_range(uint32_t vaddr, size_t sz, uint32_t paddr, unsigned int attr)
190 {
191 int vpg_start, ppg_start, num_pages;
193 if(!sz) return -1;
195 if(ADDR_TO_PGOFFS(paddr)) {
196 panic("map_mem_range called with unaligned physical address: %x\n", paddr);
197 }
199 vpg_start = ADDR_TO_PAGE(vaddr);
200 ppg_start = paddr > 0 ? ADDR_TO_PAGE(paddr) : -1;
201 num_pages = ADDR_TO_PAGE(sz) + 1;
203 return map_page_range(vpg_start, num_pages, ppg_start, attr);
204 }
206 uint32_t virt_to_phys(uint32_t vaddr)
207 {
208 uint32_t pgaddr, *pgtbl;
209 int diridx = ADDR_TO_PGTBL(vaddr);
210 int pgidx = ADDR_TO_PGTBL_PG(vaddr);
212 if(!(pgdir[diridx] & PG_PRESENT)) {
213 panic("virt_to_phys(%x): page table %d not present\n", vaddr, diridx);
214 }
215 pgtbl = (uint32_t*)(pgdir[diridx] & PGENT_ADDR_MASK);
217 if(!(pgtbl[pgidx] & PG_PRESENT)) {
218 panic("virt_to_phys(%x): page %d not present\n", vaddr, ADDR_TO_PAGE(vaddr));
219 }
220 pgaddr = pgtbl[pgidx] & PGENT_ADDR_MASK;
222 return pgaddr | ADDR_TO_PGOFFS(vaddr);
223 }
225 /* allocate a contiguous block of virtual memory pages along with
226 * backing physical memory for them, and update the page table.
227 */
228 int pgalloc(int num, int area)
229 {
230 int intr_state, ret = -1;
231 struct page_range *node, *prev, dummy;
233 intr_state = get_intr_state();
234 disable_intr();
236 dummy.next = pglist[area];
237 node = pglist[area];
238 prev = &dummy;
240 while(node) {
241 if(node->end - node->start >= num) {
242 ret = node->start;
243 node->start += num;
245 if(node->start == node->end) {
246 prev->next = node->next;
247 node->next = 0;
249 if(node == pglist[area]) {
250 pglist[area] = 0;
251 }
252 free_node(node);
253 }
254 break;
255 }
257 prev = node;
258 node = node->next;
259 }
261 if(ret >= 0) {
262 /* allocate physical storage and map */
263 if(map_page_range(ret, num, -1, 0) == -1) {
264 ret = -1;
265 }
266 }
268 set_intr_state(intr_state);
269 return ret;
270 }
272 void pgfree(int start, int num)
273 {
274 int area, end, intr_state;
275 struct page_range *node, *new, *prev, *next;
277 intr_state = get_intr_state();
278 disable_intr();
280 if(!(new = alloc_node())) {
281 panic("pgfree: can't allocate new page_range node to add the freed pages\n");
282 }
283 new->start = start;
284 end = new->end = start + num;
286 area = PAGE_TO_ADDR(start) >= KMEM_START ? MEM_KERNEL : MEM_USER;
288 if(!pglist[area] || pglist[area]->start > start) {
289 next = new->next = pglist[area];
290 pglist[area] = new;
291 prev = 0;
293 } else {
295 prev = 0;
296 node = pglist[area];
297 next = node ? node->next : 0;
299 while(node) {
300 if(!next || next->start > start) {
301 /* place here, after node */
302 new->next = next;
303 node->next = new;
304 prev = node; /* needed by coalesce after the loop */
305 break;
306 }
308 prev = node;
309 node = next;
310 next = node ? node->next : 0;
311 }
312 }
314 coalesce(prev, new, next);
315 set_intr_state(intr_state);
316 }
318 static void coalesce(struct page_range *low, struct page_range *mid, struct page_range *high)
319 {
320 if(high) {
321 if(mid->end == high->start) {
322 mid->end = high->end;
323 mid->next = high->next;
324 free_node(high);
325 }
326 }
328 if(low) {
329 if(low->end == mid->start) {
330 low->end += mid->end;
331 low->next = mid->next;
332 free_node(mid);
333 }
334 }
335 }
337 static void pgfault(int inum, uint32_t err)
338 {
339 printf("~~~~ PAGE FAULT ~~~~\n");
341 printf("fault address: %x\n", get_fault_addr());
343 if(err & PG_PRESENT) {
344 if(err & 8) {
345 printf("reserved bit set in some paging structure\n");
346 } else {
347 printf("%s protection violation ", (err & PG_WRITABLE) ? "write" : "read");
348 printf("in %s mode\n", err & PG_USER ? "user" : "kernel");
349 }
350 } else {
351 printf("page not present\n");
352 }
354 panic("unhandled page fault\n");
355 }
357 /* --- page range list node management --- */
358 #define NODES_IN_PAGE (PGSIZE / sizeof(struct page_range))
360 static struct page_range *alloc_node(void)
361 {
362 struct page_range *node;
363 int pg, i;
365 if(node_pool) {
366 node = node_pool;
367 node_pool = node_pool->next;
368 printf("alloc_node -> %x\n", (unsigned int)node);
369 return node;
370 }
372 /* no node structures in the pool, we need to allocate a new page,
373 * split it up into node structures, add them in the pool, and
374 * allocate one of them.
375 */
376 if(!(pg = pgalloc(1, MEM_KERNEL))) {
377 panic("ran out of physical memory while allocating VM range structures\n");
378 }
379 node_pool = (struct page_range*)PAGE_TO_ADDR(pg);
381 /* link them up, skip the first as we'll just allocate it anyway */
382 for(i=2; i<NODES_IN_PAGE; i++) {
383 node_pool[i - 1].next = node_pool + i;
384 }
385 node_pool[NODES_IN_PAGE - 1].next = 0;
387 /* grab the first and return it */
388 node = node_pool++;
389 printf("alloc_node -> %x\n", (unsigned int)node);
390 return node;
391 }
393 static void free_node(struct page_range *node)
394 {
395 node->next = node_pool;
396 node_pool = node;
397 printf("free_node\n");
398 }
401 void dbg_print_vm(int area)
402 {
403 struct page_range *node;
404 int last, intr_state;
406 intr_state = get_intr_state();
407 disable_intr();
409 node = pglist[area];
410 last = area == MEM_USER ? 0 : ADDR_TO_PAGE(KMEM_START);
412 printf("%s vm space\n", area == MEM_USER ? "user" : "kernel");
414 while(node) {
415 if(node->start > last) {
416 printf(" vm-used: %x -> %x\n", PAGE_TO_ADDR(last), PAGE_TO_ADDR(node->start));
417 }
419 printf(" vm-free: %x -> ", PAGE_TO_ADDR(node->start));
420 if(node->end >= PAGE_COUNT) {
421 printf("END\n");
422 } else {
423 printf("%x\n", PAGE_TO_ADDR(node->end));
424 }
426 last = node->end;
427 node = node->next;
428 }
430 set_intr_state(intr_state);
431 }