kern

annotate src/vm.c @ 25:9939a6d7a45a

protected critical sections in VM and the physical memory manager by disabling interrupts
author John Tsiombikas <nuclear@member.fsf.org>
date Wed, 06 Apr 2011 07:42:44 +0300
parents 53588744382c
children 387078ef5c0d
rev   line source
nuclear@17 1 #include <stdio.h>
nuclear@17 2 #include <string.h>
nuclear@17 3 #include <inttypes.h>
nuclear@17 4 #include "vm.h"
nuclear@17 5 #include <stdio.h>
nuclear@17 6 #include "intr.h"
nuclear@17 7 #include "mem.h"
nuclear@17 8 #include "panic.h"
nuclear@17 9
nuclear@17 10
nuclear@17 11 #define KMEM_START 0xc0000000
nuclear@17 12 #define IDMAP_START 0xa0000
nuclear@17 13
nuclear@24 14 #define PGDIR_ADDR 0xfffff000
nuclear@24 15 #define PGTBL_BASE (0xffffffff - 4096 * 1024 + 1)
nuclear@24 16 #define PGTBL(x) ((uint32_t*)(PGTBL_BASE + PGSIZE * (x)))
nuclear@24 17
nuclear@17 18 #define ATTR_PGDIR_MASK 0x3f
nuclear@17 19 #define ATTR_PGTBL_MASK 0x1ff
nuclear@17 20 #define ADDR_PGENT_MASK 0xfffff000
nuclear@17 21
nuclear@17 22 #define PAGEFAULT 14
nuclear@17 23
nuclear@22 24
nuclear@22 25 struct page_range {
nuclear@22 26 int start, end;
nuclear@22 27 struct page_range *next;
nuclear@22 28 };
nuclear@22 29
nuclear@22 30 /* defined in vm-asm.S */
nuclear@22 31 void enable_paging(void);
nuclear@23 32 void disable_paging(void);
nuclear@23 33 int get_paging_status(void);
nuclear@22 34 void set_pgdir_addr(uint32_t addr);
nuclear@23 35 void flush_tlb(void);
nuclear@23 36 void flush_tlb_addr(uint32_t addr);
nuclear@23 37 #define flush_tlb_page(p) flush_tlb_addr(PAGE_TO_ADDR(p))
nuclear@22 38 uint32_t get_fault_addr(void);
nuclear@22 39
nuclear@23 40 static void coalesce(struct page_range *low, struct page_range *mid, struct page_range *high);
nuclear@22 41 static void pgfault(int inum, uint32_t err);
nuclear@22 42 static struct page_range *alloc_node(void);
nuclear@22 43 static void free_node(struct page_range *node);
nuclear@22 44
nuclear@22 45 /* page directory */
nuclear@22 46 static uint32_t *pgdir;
nuclear@22 47
nuclear@22 48 /* 2 lists of free ranges, for kernel memory and user memory */
nuclear@22 49 static struct page_range *pglist[2];
nuclear@22 50 /* list of free page_range structures to be used in the lists */
nuclear@22 51 static struct page_range *node_pool;
nuclear@23 52 /* the first page range for the whole kernel address space, to get things started */
nuclear@23 53 static struct page_range first_node;
nuclear@22 54
nuclear@22 55
nuclear@17 56 void init_vm(struct mboot_info *mb)
nuclear@17 57 {
nuclear@19 58 uint32_t idmap_end;
nuclear@19 59
nuclear@23 60 /* initialize the physical memory map and allocator */
nuclear@17 61 init_mem(mb);
nuclear@17 62
nuclear@23 63 /* setup the page tables */
nuclear@18 64 pgdir = (uint32_t*)alloc_phys_page();
nuclear@23 65 memset(pgdir, 0, PGSIZE);
nuclear@24 66 set_pgdir_addr((uint32_t)pgdir);
nuclear@17 67
nuclear@17 68 /* map the video memory and kernel code 1-1 */
nuclear@19 69 get_kernel_mem_range(0, &idmap_end);
nuclear@19 70 map_mem_range(IDMAP_START, idmap_end - IDMAP_START, IDMAP_START, 0);
nuclear@17 71
nuclear@24 72 /* make the last page directory entry point to the page directory */
nuclear@24 73 pgdir[1023] = ((uint32_t)pgdir & ADDR_PGENT_MASK) | PG_PRESENT;
nuclear@24 74 pgdir = (uint32_t*)PGDIR_ADDR;
nuclear@24 75
nuclear@23 76 /* set the page fault handler */
nuclear@17 77 interrupt(PAGEFAULT, pgfault);
nuclear@17 78
nuclear@23 79 /* we can enable paging now */
nuclear@17 80 enable_paging();
nuclear@23 81
nuclear@23 82 /* initialize the virtual page allocator */
nuclear@23 83 node_pool = 0;
nuclear@23 84
nuclear@23 85 first_node.start = ADDR_TO_PAGE(KMEM_START);
nuclear@23 86 first_node.end = PAGE_COUNT;
nuclear@23 87 first_node.next = 0;
nuclear@23 88 pglist[MEM_KERNEL] = &first_node;
nuclear@23 89
nuclear@23 90 pglist[MEM_USER] = alloc_node();
nuclear@23 91 pglist[MEM_USER]->start = 0;
nuclear@23 92 pglist[MEM_USER]->end = ADDR_TO_PAGE(KMEM_START);
nuclear@23 93 pglist[MEM_USER]->next = 0;
nuclear@17 94 }
nuclear@17 95
nuclear@23 96 /* if ppage == -1 we allocate a physical page by calling alloc_phys_page */
nuclear@23 97 int map_page(int vpage, int ppage, unsigned int attr)
nuclear@17 98 {
nuclear@17 99 uint32_t *pgtbl;
nuclear@25 100 int diridx, pgidx, pgon, intr_state;
nuclear@25 101
nuclear@25 102 intr_state = get_intr_state();
nuclear@25 103 disable_intr();
nuclear@23 104
nuclear@23 105 pgon = get_paging_status();
nuclear@23 106
nuclear@23 107 if(ppage < 0) {
nuclear@23 108 uint32_t addr = alloc_phys_page();
nuclear@23 109 if(!addr) {
nuclear@25 110 set_intr_state(intr_state);
nuclear@23 111 return -1;
nuclear@23 112 }
nuclear@23 113 ppage = ADDR_TO_PAGE(addr);
nuclear@23 114 }
nuclear@23 115
nuclear@23 116 diridx = PAGE_TO_PGTBL(vpage);
nuclear@23 117 pgidx = PAGE_TO_PGTBL_PG(vpage);
nuclear@17 118
nuclear@17 119 if(!(pgdir[diridx] & PG_PRESENT)) {
nuclear@17 120 uint32_t addr = alloc_phys_page();
nuclear@24 121 pgdir[diridx] = addr | (attr & ATTR_PGDIR_MASK) | PG_PRESENT;
nuclear@24 122
nuclear@24 123 pgtbl = pgon ? PGTBL(diridx) : (uint32_t*)addr;
nuclear@18 124 memset(pgtbl, 0, PGSIZE);
nuclear@17 125 } else {
nuclear@24 126 if(pgon) {
nuclear@24 127 pgtbl = PGTBL(diridx);
nuclear@24 128 } else {
nuclear@24 129 pgtbl = (uint32_t*)(pgdir[diridx] & ADDR_PGENT_MASK);
nuclear@24 130 }
nuclear@17 131 }
nuclear@17 132
nuclear@17 133 pgtbl[pgidx] = PAGE_TO_ADDR(ppage) | (attr & ATTR_PGTBL_MASK) | PG_PRESENT;
nuclear@23 134 flush_tlb_page(vpage);
nuclear@23 135
nuclear@25 136 set_intr_state(intr_state);
nuclear@23 137 return 0;
nuclear@17 138 }
nuclear@17 139
nuclear@17 140 void unmap_page(int vpage)
nuclear@17 141 {
nuclear@17 142 uint32_t *pgtbl;
nuclear@17 143 int diridx = PAGE_TO_PGTBL(vpage);
nuclear@17 144 int pgidx = PAGE_TO_PGTBL_PG(vpage);
nuclear@17 145
nuclear@25 146 int intr_state = get_intr_state();
nuclear@25 147 disable_intr();
nuclear@25 148
nuclear@17 149 if(!(pgdir[diridx] & PG_PRESENT)) {
nuclear@17 150 goto err;
nuclear@17 151 }
nuclear@17 152 pgtbl = (uint32_t*)(pgdir[diridx] & ADDR_PGENT_MASK);
nuclear@17 153
nuclear@17 154 if(!(pgtbl[pgidx] & PG_PRESENT)) {
nuclear@17 155 goto err;
nuclear@17 156 }
nuclear@17 157 pgtbl[pgidx] = 0;
nuclear@23 158 flush_tlb_page(vpage);
nuclear@17 159
nuclear@25 160 if(0) {
nuclear@17 161 err:
nuclear@25 162 printf("unmap_page(%d): page already not mapped\n", vpage);
nuclear@25 163 }
nuclear@25 164 set_intr_state(intr_state);
nuclear@17 165 }
nuclear@17 166
nuclear@22 167 /* if ppg_start is -1, we allocate physical pages to map with alloc_phys_page() */
nuclear@23 168 int map_page_range(int vpg_start, int pgcount, int ppg_start, unsigned int attr)
nuclear@17 169 {
nuclear@23 170 int i, phys_pg;
nuclear@23 171 uint32_t paddr;
nuclear@17 172
nuclear@17 173 for(i=0; i<pgcount; i++) {
nuclear@23 174 if(ppg_start < 0) {
nuclear@23 175 if(!(paddr = alloc_phys_page())) {
nuclear@23 176 return -1;
nuclear@23 177 }
nuclear@23 178 phys_pg = ADDR_TO_PAGE(paddr);
nuclear@23 179 } else {
nuclear@23 180 phys_pg = ppg_start + i;
nuclear@23 181 }
nuclear@22 182
nuclear@23 183 map_page(vpg_start + i, phys_pg, attr);
nuclear@17 184 }
nuclear@23 185 return 0;
nuclear@17 186 }
nuclear@17 187
nuclear@23 188 /* if paddr is 0, we allocate physical pages with alloc_phys_page() */
nuclear@23 189 int map_mem_range(uint32_t vaddr, size_t sz, uint32_t paddr, unsigned int attr)
nuclear@17 190 {
nuclear@17 191 int vpg_start, ppg_start, num_pages;
nuclear@17 192
nuclear@23 193 if(!sz) return -1;
nuclear@17 194
nuclear@17 195 if(ADDR_TO_PGOFFS(paddr)) {
nuclear@17 196 panic("map_mem_range called with unaligned physical address: %x\n", paddr);
nuclear@17 197 }
nuclear@17 198
nuclear@17 199 vpg_start = ADDR_TO_PAGE(vaddr);
nuclear@23 200 ppg_start = paddr > 0 ? ADDR_TO_PAGE(paddr) : -1;
nuclear@17 201 num_pages = ADDR_TO_PAGE(sz) + 1;
nuclear@17 202
nuclear@23 203 return map_page_range(vpg_start, num_pages, ppg_start, attr);
nuclear@17 204 }
nuclear@17 205
nuclear@18 206 uint32_t virt_to_phys(uint32_t vaddr)
nuclear@18 207 {
nuclear@18 208 uint32_t pgaddr, *pgtbl;
nuclear@18 209 int diridx = ADDR_TO_PGTBL(vaddr);
nuclear@18 210 int pgidx = ADDR_TO_PGTBL_PG(vaddr);
nuclear@18 211
nuclear@18 212 if(!(pgdir[diridx] & PG_PRESENT)) {
nuclear@18 213 panic("virt_to_phys(%x): page table %d not present\n", vaddr, diridx);
nuclear@18 214 }
nuclear@18 215 pgtbl = (uint32_t*)(pgdir[diridx] & PGENT_ADDR_MASK);
nuclear@18 216
nuclear@18 217 if(!(pgtbl[pgidx] & PG_PRESENT)) {
nuclear@18 218 panic("virt_to_phys(%x): page %d not present\n", vaddr, ADDR_TO_PAGE(vaddr));
nuclear@18 219 }
nuclear@18 220 pgaddr = pgtbl[pgidx] & PGENT_ADDR_MASK;
nuclear@18 221
nuclear@18 222 return pgaddr | ADDR_TO_PGOFFS(vaddr);
nuclear@18 223 }
nuclear@18 224
nuclear@22 225 /* allocate a contiguous block of virtual memory pages along with
nuclear@22 226 * backing physical memory for them, and update the page table.
nuclear@22 227 */
nuclear@22 228 int pgalloc(int num, int area)
nuclear@22 229 {
nuclear@25 230 int intr_state, ret = -1;
nuclear@22 231 struct page_range *node, *prev, dummy;
nuclear@22 232
nuclear@25 233 intr_state = get_intr_state();
nuclear@25 234 disable_intr();
nuclear@25 235
nuclear@22 236 dummy.next = pglist[area];
nuclear@22 237 node = pglist[area];
nuclear@22 238 prev = &dummy;
nuclear@22 239
nuclear@22 240 while(node) {
nuclear@22 241 if(node->end - node->start >= num) {
nuclear@22 242 ret = node->start;
nuclear@22 243 node->start += num;
nuclear@22 244
nuclear@22 245 if(node->start == node->end) {
nuclear@22 246 prev->next = node->next;
nuclear@22 247 node->next = 0;
nuclear@22 248
nuclear@22 249 if(node == pglist[area]) {
nuclear@22 250 pglist[area] = 0;
nuclear@22 251 }
nuclear@22 252 free_node(node);
nuclear@22 253 }
nuclear@22 254 break;
nuclear@22 255 }
nuclear@22 256
nuclear@22 257 prev = node;
nuclear@22 258 node = node->next;
nuclear@22 259 }
nuclear@22 260
nuclear@22 261 if(ret >= 0) {
nuclear@23 262 /* allocate physical storage and map */
nuclear@23 263 if(map_page_range(ret, num, -1, 0) == -1) {
nuclear@23 264 ret = -1;
nuclear@23 265 }
nuclear@22 266 }
nuclear@22 267
nuclear@25 268 set_intr_state(intr_state);
nuclear@22 269 return ret;
nuclear@22 270 }
nuclear@22 271
nuclear@22 272 void pgfree(int start, int num)
nuclear@22 273 {
nuclear@25 274 int area, end, intr_state;
nuclear@23 275 struct page_range *node, *new, *prev, *next;
nuclear@23 276
nuclear@25 277 intr_state = get_intr_state();
nuclear@25 278 disable_intr();
nuclear@25 279
nuclear@23 280 if(!(new = alloc_node())) {
nuclear@23 281 panic("pgfree: can't allocate new page_range node to add the freed pages\n");
nuclear@23 282 }
nuclear@23 283 new->start = start;
nuclear@23 284 end = new->end = start + num;
nuclear@23 285
nuclear@23 286 area = PAGE_TO_ADDR(start) >= KMEM_START ? MEM_KERNEL : MEM_USER;
nuclear@23 287
nuclear@23 288 if(!pglist[area] || pglist[area]->start > start) {
nuclear@23 289 next = new->next = pglist[area];
nuclear@23 290 pglist[area] = new;
nuclear@23 291 prev = 0;
nuclear@23 292
nuclear@23 293 } else {
nuclear@23 294
nuclear@23 295 prev = 0;
nuclear@23 296 node = pglist[area];
nuclear@23 297 next = node ? node->next : 0;
nuclear@23 298
nuclear@23 299 while(node) {
nuclear@23 300 if(!next || next->start > start) {
nuclear@23 301 /* place here, after node */
nuclear@23 302 new->next = next;
nuclear@23 303 node->next = new;
nuclear@23 304 prev = node; /* needed by coalesce after the loop */
nuclear@23 305 break;
nuclear@23 306 }
nuclear@23 307
nuclear@23 308 prev = node;
nuclear@23 309 node = next;
nuclear@23 310 next = node ? node->next : 0;
nuclear@23 311 }
nuclear@23 312 }
nuclear@23 313
nuclear@23 314 coalesce(prev, new, next);
nuclear@25 315 set_intr_state(intr_state);
nuclear@23 316 }
nuclear@23 317
nuclear@23 318 static void coalesce(struct page_range *low, struct page_range *mid, struct page_range *high)
nuclear@23 319 {
nuclear@23 320 if(high) {
nuclear@23 321 if(mid->end == high->start) {
nuclear@23 322 mid->end = high->end;
nuclear@23 323 mid->next = high->next;
nuclear@23 324 free_node(high);
nuclear@23 325 }
nuclear@23 326 }
nuclear@23 327
nuclear@23 328 if(low) {
nuclear@23 329 if(low->end == mid->start) {
nuclear@23 330 low->end += mid->end;
nuclear@23 331 low->next = mid->next;
nuclear@23 332 free_node(mid);
nuclear@23 333 }
nuclear@23 334 }
nuclear@22 335 }
nuclear@22 336
nuclear@17 337 static void pgfault(int inum, uint32_t err)
nuclear@17 338 {
nuclear@17 339 printf("~~~~ PAGE FAULT ~~~~\n");
nuclear@17 340
nuclear@17 341 printf("fault address: %x\n", get_fault_addr());
nuclear@17 342
nuclear@17 343 if(err & PG_PRESENT) {
nuclear@17 344 if(err & 8) {
nuclear@17 345 printf("reserved bit set in some paging structure\n");
nuclear@17 346 } else {
nuclear@17 347 printf("%s protection violation ", (err & PG_WRITABLE) ? "write" : "read");
nuclear@17 348 printf("in %s mode\n", err & PG_USER ? "user" : "kernel");
nuclear@17 349 }
nuclear@17 350 } else {
nuclear@17 351 printf("page not present\n");
nuclear@17 352 }
nuclear@19 353
nuclear@19 354 panic("unhandled page fault\n");
nuclear@17 355 }
nuclear@22 356
nuclear@22 357 /* --- page range list node management --- */
nuclear@23 358 #define NODES_IN_PAGE (PGSIZE / sizeof(struct page_range))
nuclear@23 359
nuclear@22 360 static struct page_range *alloc_node(void)
nuclear@22 361 {
nuclear@22 362 struct page_range *node;
nuclear@23 363 int pg, i;
nuclear@22 364
nuclear@22 365 if(node_pool) {
nuclear@22 366 node = node_pool;
nuclear@22 367 node_pool = node_pool->next;
nuclear@23 368 printf("alloc_node -> %x\n", (unsigned int)node);
nuclear@22 369 return node;
nuclear@22 370 }
nuclear@22 371
nuclear@23 372 /* no node structures in the pool, we need to allocate a new page,
nuclear@23 373 * split it up into node structures, add them in the pool, and
nuclear@23 374 * allocate one of them.
nuclear@22 375 */
nuclear@23 376 if(!(pg = pgalloc(1, MEM_KERNEL))) {
nuclear@22 377 panic("ran out of physical memory while allocating VM range structures\n");
nuclear@22 378 }
nuclear@23 379 node_pool = (struct page_range*)PAGE_TO_ADDR(pg);
nuclear@22 380
nuclear@23 381 /* link them up, skip the first as we'll just allocate it anyway */
nuclear@23 382 for(i=2; i<NODES_IN_PAGE; i++) {
nuclear@23 383 node_pool[i - 1].next = node_pool + i;
nuclear@23 384 }
nuclear@23 385 node_pool[NODES_IN_PAGE - 1].next = 0;
nuclear@23 386
nuclear@23 387 /* grab the first and return it */
nuclear@23 388 node = node_pool++;
nuclear@23 389 printf("alloc_node -> %x\n", (unsigned int)node);
nuclear@23 390 return node;
nuclear@22 391 }
nuclear@22 392
nuclear@22 393 static void free_node(struct page_range *node)
nuclear@22 394 {
nuclear@22 395 node->next = node_pool;
nuclear@22 396 node_pool = node;
nuclear@23 397 printf("free_node\n");
nuclear@22 398 }
nuclear@23 399
nuclear@23 400
nuclear@23 401 void dbg_print_vm(int area)
nuclear@23 402 {
nuclear@25 403 struct page_range *node;
nuclear@25 404 int last, intr_state;
nuclear@25 405
nuclear@25 406 intr_state = get_intr_state();
nuclear@25 407 disable_intr();
nuclear@25 408
nuclear@25 409 node = pglist[area];
nuclear@25 410 last = area == MEM_USER ? 0 : ADDR_TO_PAGE(KMEM_START);
nuclear@23 411
nuclear@23 412 printf("%s vm space\n", area == MEM_USER ? "user" : "kernel");
nuclear@23 413
nuclear@23 414 while(node) {
nuclear@23 415 if(node->start > last) {
nuclear@23 416 printf(" vm-used: %x -> %x\n", PAGE_TO_ADDR(last), PAGE_TO_ADDR(node->start));
nuclear@23 417 }
nuclear@23 418
nuclear@23 419 printf(" vm-free: %x -> ", PAGE_TO_ADDR(node->start));
nuclear@23 420 if(node->end >= PAGE_COUNT) {
nuclear@23 421 printf("END\n");
nuclear@23 422 } else {
nuclear@23 423 printf("%x\n", PAGE_TO_ADDR(node->end));
nuclear@23 424 }
nuclear@23 425
nuclear@23 426 last = node->end;
nuclear@23 427 node = node->next;
nuclear@23 428 }
nuclear@25 429
nuclear@25 430 set_intr_state(intr_state);
nuclear@23 431 }