Mercurial > hg > Members > innparusu > xv6-rpi
diff src/vm.c @ 0:83c23a36980d
Init
author | Tatsuki IHA <e125716@ie.u-ryukyu.ac.jp> |
---|---|
date | Fri, 26 May 2017 23:11:05 +0900 |
parents | |
children |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/vm.c Fri May 26 23:11:05 2017 +0900 @@ -0,0 +1,447 @@ +#include "param.h" +#include "types.h" +#include "defs.h" +#include "arm.h" +#include "memlayout.h" +#include "mmu.h" +#include "proc.h" +#include "spinlock.h" +#include "elf.h" + +extern char data[]; // defined by kernel.ld +pde_t *kpgdir; // for use in scheduler() + +// Xv6 can only allocate memory in 4KB blocks. This is fine +// for x86. ARM's page table and page directory (for 28-bit +// user address) have a size of 1KB. kpt_alloc/free is used +// as a wrapper to support allocating page tables during boot +// (use the initial kernel map, and during runtime, use buddy +// memory allocator. +struct run { + struct run *next; +}; + +struct { + struct spinlock lock; + struct run *freelist; +} kpt_mem; + +void init_vmm (void) +{ + initlock(&kpt_mem.lock, "vm"); + kpt_mem.freelist = NULL; +} + +static void _kpt_free (char *v) +{ + struct run *r; + + r = (struct run*) v; + r->next = kpt_mem.freelist; + kpt_mem.freelist = r; +} + + +static void kpt_free (char *v) +{ + if (v >= (char*)P2V(INIT_KERNMAP)) { + kfree(v, PT_ORDER); + return; + } + + acquire(&kpt_mem.lock); + _kpt_free (v); + release(&kpt_mem.lock); +} + +// add some memory used for page tables (initialization code) +void kpt_freerange (uint32 low, uint32 hi) +{ + while (low < hi) { + _kpt_free ((char*)low); + low += PT_SZ; + } +} + +void* kpt_alloc (void) +{ + struct run *r; + + acquire(&kpt_mem.lock); + + if ((r = kpt_mem.freelist) != NULL ) { + kpt_mem.freelist = r->next; + } + + release(&kpt_mem.lock); + + // Allocate a PT page if no inital pages is available + if ((r == NULL) && ((r = kmalloc (PT_ORDER)) == NULL)) { + panic("oom: kpt_alloc"); + } + + memset(r, 0, PT_SZ); + return (char*) r; +} + +// Return the address of the PTE in page directory that corresponds to +// virtual address va. If alloc!=0, create any required page table pages. +static pte_t* walkpgdir (pde_t *pgdir, const void *va, int alloc) +{ + pde_t *pde; + pte_t *pgtab; + + // pgdir points to the page directory, get the page direcotry entry (pde) + pde = &pgdir[PDE_IDX(va)]; + + if (*pde & PE_TYPES) { + pgtab = (pte_t*) p2v(PT_ADDR(*pde)); + + } else { + if (!alloc || (pgtab = (pte_t*) kpt_alloc()) == 0) { + return 0; + } + + // Make sure all those PTE_P bits are zero. + memset(pgtab, 0, PT_SZ); + + // The permissions here are overly generous, but they can + // be further restricted by the permissions in the page table + // entries, if necessary. + *pde = v2p(pgtab) | UPDE_TYPE; + } + + return &pgtab[PTE_IDX(va)]; +} + +// Create PTEs for virtual addresses starting at va that refer to +// physical addresses starting at pa. va and size might not +// be page-aligned. +static int mappages (pde_t *pgdir, void *va, uint size, uint pa, int ap) +{ + char *a, *last; + pte_t *pte; + + a = (char*) align_dn(va, PTE_SZ); + last = (char*) align_dn((uint)va + size - 1, PTE_SZ); + + for (;;) { + if ((pte = walkpgdir(pgdir, a, 1)) == 0) { + return -1; + } + + if (*pte & PE_TYPES) { + panic("remap"); + } + + *pte = pa | ((ap & 0x3) << 4) | PE_CACHE | PE_BUF | PTE_TYPE; + + if (a == last) { + break; + } + + a += PTE_SZ; + pa += PTE_SZ; + } + + return 0; +} + +// flush all TLB +static void flush_tlb (void) +{ + uint val = 0; + asm("MCR p15, 0, %[r], c8, c7, 0" : :[r]"r" (val):); + + // invalid entire data and instruction cache + asm ("MCR p15,0,%[r],c7,c10,0": :[r]"r" (val):); + asm ("MCR p15,0,%[r],c7,c11,0": :[r]"r" (val):); +} + +// Switch to the user page table (TTBR0) +void switchuvm (struct proc *p) +{ + uint val; + + pushcli(); + + if (p->pgdir == 0) { + panic("switchuvm: no pgdir"); + } + + val = (uint) V2P(p->pgdir) | 0x00; + + asm("MCR p15, 0, %[v], c2, c0, 0": :[v]"r" (val):); + flush_tlb(); + + popcli(); +} + +// Load the initcode into address 0 of pgdir. sz must be less than a page. +void inituvm (pde_t *pgdir, char *init, uint sz) +{ + char *mem; + + if (sz >= PTE_SZ) { + panic("inituvm: more than a page"); + } + + mem = alloc_page(); + memset(mem, 0, PTE_SZ); + mappages(pgdir, 0, PTE_SZ, v2p(mem), AP_KU); + memmove(mem, init, sz); +} + +// Load a program segment into pgdir. addr must be page-aligned +// and the pages from addr to addr+sz must already be mapped. +int loaduvm (pde_t *pgdir, char *addr, struct inode *ip, uint offset, uint sz) +{ + uint i, pa, n; + pte_t *pte; + + if ((uint) addr % PTE_SZ != 0) { + panic("loaduvm: addr must be page aligned"); + } + + for (i = 0; i < sz; i += PTE_SZ) { + if ((pte = walkpgdir(pgdir, addr + i, 0)) == 0) { + panic("loaduvm: address should exist"); + } + + pa = PTE_ADDR(*pte); + + if (sz - i < PTE_SZ) { + n = sz - i; + } else { + n = PTE_SZ; + } + + if (readi(ip, p2v(pa), offset + i, n) != n) { + return -1; + } + } + + return 0; +} + +// Allocate page tables and physical memory to grow process from oldsz to +// newsz, which need not be page aligned. Returns new size or 0 on error. +int allocuvm (pde_t *pgdir, uint oldsz, uint newsz) +{ + char *mem; + uint a; + + if (newsz >= UADDR_SZ) { + return 0; + } + + if (newsz < oldsz) { + return oldsz; + } + + a = align_up(oldsz, PTE_SZ); + + for (; a < newsz; a += PTE_SZ) { + mem = alloc_page(); + + if (mem == 0) { + cprintf("allocuvm out of memory\n"); + deallocuvm(pgdir, newsz, oldsz); + return 0; + } + + memset(mem, 0, PTE_SZ); + mappages(pgdir, (char*) a, PTE_SZ, v2p(mem), AP_KU); + } + + return newsz; +} + +// Deallocate user pages to bring the process size from oldsz to +// newsz. oldsz and newsz need not be page-aligned, nor does newsz +// need to be less than oldsz. oldsz can be larger than the actual +// process size. Returns the new process size. +int deallocuvm (pde_t *pgdir, uint oldsz, uint newsz) +{ + pte_t *pte; + uint a; + uint pa; + + if (newsz >= oldsz) { + return oldsz; + } + + for (a = align_up(newsz, PTE_SZ); a < oldsz; a += PTE_SZ) { + pte = walkpgdir(pgdir, (char*) a, 0); + + if (!pte) { + // pte == 0 --> no page table for this entry + // round it up to the next page directory + a = align_up (a, PDE_SZ); + + } else if ((*pte & PE_TYPES) != 0) { + pa = PTE_ADDR(*pte); + + if (pa == 0) { + panic("deallocuvm"); + } + + free_page(p2v(pa)); + *pte = 0; + } + } + + return newsz; +} + +// Free a page table and all the physical memory pages +// in the user part. +void freevm (pde_t *pgdir) +{ + uint i; + char *v; + + if (pgdir == 0) { + panic("freevm: no pgdir"); + } + + // release the user space memroy, but not page tables + deallocuvm(pgdir, UADDR_SZ, 0); + + // release the page tables + for (i = 0; i < NUM_UPDE; i++) { + if (pgdir[i] & PE_TYPES) { + v = p2v(PT_ADDR(pgdir[i])); + kpt_free(v); + } + } + + kpt_free((char*) pgdir); +} + +// Clear PTE_U on a page. Used to create an inaccessible page beneath +// the user stack (to trap stack underflow). +void clearpteu (pde_t *pgdir, char *uva) +{ + pte_t *pte; + + pte = walkpgdir(pgdir, uva, 0); + if (pte == 0) { + panic("clearpteu"); + } + + // in ARM, we change the AP field (ap & 0x3) << 4) + *pte = (*pte & ~(0x03 << 4)) | AP_KO << 4; +} + +// Given a parent process's page table, create a copy +// of it for a child. +pde_t* copyuvm (pde_t *pgdir, uint sz) +{ + pde_t *d; + pte_t *pte; + uint pa, i, ap; + char *mem; + + // allocate a new first level page directory + d = kpt_alloc(); + if (d == NULL ) { + return NULL ; + } + + // copy the whole address space over (no COW) + for (i = 0; i < sz; i += PTE_SZ) { + if ((pte = walkpgdir(pgdir, (void *) i, 0)) == 0) { + panic("copyuvm: pte should exist"); + } + + if (!(*pte & PE_TYPES)) { + panic("copyuvm: page not present"); + } + + pa = PTE_ADDR (*pte); + ap = PTE_AP (*pte); + + if ((mem = alloc_page()) == 0) { + goto bad; + } + + memmove(mem, (char*) p2v(pa), PTE_SZ); + + if (mappages(d, (void*) i, PTE_SZ, v2p(mem), ap) < 0) { + goto bad; + } + } + return d; + +bad: freevm(d); + return 0; +} + +//PAGEBREAK! +// Map user virtual address to kernel address. +char* uva2ka (pde_t *pgdir, char *uva) +{ + pte_t *pte; + + pte = walkpgdir(pgdir, uva, 0); + + // make sure it exists + if ((*pte & PE_TYPES) == 0) { + return 0; + } + + // make sure it is a user page + if (PTE_AP(*pte) != AP_KU) { + return 0; + } + + return (char*) p2v(PTE_ADDR(*pte)); +} + +// Copy len bytes from p to user address va in page table pgdir. +// Most useful when pgdir is not the current page table. +// uva2ka ensures this only works for user pages. +int copyout (pde_t *pgdir, uint va, void *p, uint len) +{ + char *buf, *pa0; + uint n, va0; + + buf = (char*) p; + + while (len > 0) { + va0 = align_dn(va, PTE_SZ); + pa0 = uva2ka(pgdir, (char*) va0); + + if (pa0 == 0) { + return -1; + } + + n = PTE_SZ - (va - va0); + + if (n > len) { + n = len; + } + + memmove(pa0 + (va - va0), buf, n); + + len -= n; + buf += n; + va = va0 + PTE_SZ; + } + + return 0; +} + + +// 1:1 map the memory [phy_low, phy_hi] in kernel. We need to +// use 2-level mapping for this block of memory. The rumor has +// it that ARMv6's small brain cannot handle the case that memory +// be mapped in both 1-level page table and 2-level page. For +// initial kernel, we use 1MB mapping, other memory needs to be +// mapped as 4KB pages +void paging_init (uint phy_low, uint phy_hi) +{ + mappages (P2V(&_kernel_pgtbl), P2V(phy_low), phy_hi - phy_low, phy_low, AP_KU); + flush_tlb (); +}