summaryrefslogtreecommitdiff
path: root/kernel/vm.c
diff options
context:
space:
mode:
Diffstat (limited to 'kernel/vm.c')
-rw-r--r--kernel/vm.c585
1 files changed, 585 insertions, 0 deletions
diff --git a/kernel/vm.c b/kernel/vm.c
new file mode 100644
index 0000000..46c4eab
--- /dev/null
+++ b/kernel/vm.c
@@ -0,0 +1,585 @@
+/**
+** @file vm.c
+**
+** @author CSCI-452 class of 20245
+**
+** @brief Kernel VM support
+*/
+
+#define KERNEL_SRC
+
+#include <common.h>
+
+#include <vm.h>
+#include <vmtables.h>
+
+#include <kmem.h>
+#include <procs.h>
+#include <x86/arch.h>
+#include <x86/ops.h>
+
+/*
+** PRIVATE DEFINITIONS
+*/
+
+/*
+** PRIVATE DATA TYPES
+*/
+
+/*
+** PRIVATE GLOBAL VARIABLES
+*/
+
+/*
+** PUBLIC GLOBAL VARIABLES
+*/
+
+// created page directory for the kernel
+pde_t *kpdir;
+
+/*
+** PRIVATE FUNCTIONS
+*/
+
+/**
+** Name: vm_isr
+**
+** Description: Page fault handler
+**
+** @param vector Interrupt vector number
+** @param code Error code pushed onto the stack
+*/
+static void vm_isr( int vector, int code ) {
+
+ // get whatever information we can from the fault
+ pfec_t fault;
+ fault.u = (uint32_t) code;
+ uint32_t addr = r_cr2();
+
+ // report what we found
+ sprint( b256,
+ "** page fault @ 0x%08x %cP %c %cM %cRSV %c %cPK %cSS %cHLAT %cSGZ",
+ addr,
+ fault.s.p ? ' ' : '!',
+ fault.s.w ? 'W' : 'R',
+ fault.s.us ? 'U' : 'S',
+ fault.s.rsvd ? ' ' : '!',
+ fault.s.id ? 'I' : 'D',
+ fault.s.pk ? ' ' : '!',
+ fault.s.ss ? ' ' : '!',
+ fault.s.hlat ? ' ' : '!',
+ fault.s.sgz ? ' ' : '!'
+ );
+
+ // and give up
+ PANIC( 0, b256 );
+}
+
+/**
+** Name: uva2kva
+**
+** Convert a user VA into a kernel address
+*/
+__attribute__((__unused__))
+static void *uva2kva( pde_t *pdir, void *va ) {
+
+ // find the PMT entry for this address
+ pte_t *pte = vm_getpte( pdir, va, false );
+ if( pte == NULL ) {
+ return NULL;
+ }
+
+ // is this a valid address for the user?
+ if( IS_PRESENT(*pte) ) {
+ return 0;
+ }
+
+ if( IS_LARGE(*pte) ) {
+ return 0;
+ }
+
+ // get the physical address
+ uint32_t frame = *pte & FRAME_MASK; // keep the frame address
+ frame |= ((uint32_t) va) & PERM_MASK; // OR in the lower 12 bits
+
+ return (void *) frame;
+}
+
+
+/*
+** PUBLIC FUNCTIONS
+*/
+
+/**
+** Name: vm_init
+**
+** Description: Initialize the VM module
+*/
+void vm_init( void ) {
+
+#if TRACING_INIT
+ cio_puts( " VM" );
+#endif
+
+ // set up the kernel's page directory
+ kpdir = vm_mkkvm();
+ assert( kpdir != NULL );
+
+ // install the page fault handler
+ install_isr( VEC_PAGE_FAULT, vm_isr );
+}
+
+/**
+** Name: vm_pagedup
+**
+** Duplicate a page of memory
+**
+** @param old Pointer to the first byte of a page
+**
+** @return a pointer to the new, duplicate page, or NULL
+*/
+void *vm_pagedup( void *old ) {
+ void *new = (void *) km_page_alloc();
+ if( new != NULL ) {
+ memcpy( new, old, SZ_PAGE );
+ }
+ return new;
+}
+
+/**
+** Name: vm_ptdup
+**
+** Duplicate a page directory entry
+**
+** @param dst Pointer to where the duplicate should go
+** @param curr Pointer to the entry to be duplicated
+**
+** @return true on success, else false
+*/
+bool_t vm_ptdup( pde_t *dst, pde_t *curr ) {
+
+#if TRACING_VM
+ cio_printf( "vm_ptdup dst %08x curr %08x\n",
+ (uint32_t) dst, (uint32_t) curr );
+#endif
+ // simplest case
+ if( *curr == 0 ) {
+ *dst = 0;
+ return true;
+ }
+
+ // OK, we have an entry; allocate a page table
+ pte_t *pt = (pte_t *) km_page_alloc();
+ if( pt == NULL ) {
+ return false;
+ }
+
+ // pointer to the first PTE in the current table
+ pte_t *old = (pte_t *) (((uint32_t) *curr) & FRAME_MASK);
+ // pointer to the first PTE in the new table
+ pte_t *new = pt;
+
+ for( int i = 0 ; i < N_PTE; ++i ) {
+ if( IS_PRESENT(*old) ) {
+ *new = 0;
+ } else {
+ *new = *old;
+ }
+ ++old;
+ ++new;
+ }
+
+ // assign the page table into the new page directory
+ // upper 22 bits from 'pt', lower 12 from '*curr'
+ *dst = (pde_t) (
+ (((uint32_t)pt) & FRAME_MASK) |
+ (((uint32_t)(*curr)) & PERM_MASK )
+ );
+
+ return true;
+}
+
+/**
+** Name: vm_getpte
+**
+** Return the address of the PTE corresponding to the virtual address
+** 'va' within the address space controlled by 'pgdir'. If there is no
+** page table for that VA and 'alloc' is true, create the necessary
+** page table entries.
+**
+** @param pdir Pointer to the page directory to be searched
+** @param va The virtual address we're looking for
+** @param alloc Should we allocate a page table if there isn't one?
+**
+** @return A pointer to the page table entry for this VA, or NULL
+*/
+pte_t *vm_getpte( pde_t *pdir, const void *va, bool_t alloc ) {
+ pte_t *ptab;
+
+ // sanity check
+ assert1( pdir != NULL );
+
+ // get the PDIR entry for this virtual address
+ pde_t *pde = &pdir[ PDIX(va) ];
+
+ // is it already set up?
+ if( IS_PRESENT(*pde) ) {
+
+ // yes!
+ ptab = (pte_t*)P2V(PTE_ADDR(*pde));
+
+ } else {
+
+ // no - should we create it?
+ if( !alloc ) {
+ // nope, so just return
+ return NULL;
+ }
+
+ // yes - try to allocate a page table
+ ptab = (pte_t *) km_page_alloc();
+ if( ptab == NULL ) {
+ WARNING( "can't allocate page table" );
+ return NULL;
+ }
+
+ // who knows what was left in this page....
+ memclr( ptab, SZ_PAGE );
+
+ // add this to the page directory
+ //
+ // we set this up to allow general access; this could be
+ // controlled by setting access control in the page table
+ // entries, if necessary.
+ *pde = V2P(ptab) | PDE_P | PDE_RW;
+ }
+
+ // finally, return a pointer to the entry in the
+ // page table for this VA
+ return &ptab[ PTIX(va) ];
+}
+
+// Set up kernel part of a page table.
+pde_t *vm_mkkvm( void )
+{
+ mapping_t *k;
+
+ // allocate the page directory
+ pde_t *pdir = km_page_alloc();
+ if( pdir == NULL ) {
+ return NULL;
+ }
+
+ // clear it out to disable all the entries
+ memclr( pdir, SZ_PAGE );
+
+ // map in all the page ranges
+ k = kmap;
+ for( int i = 0; i < n_kmap; ++i, ++k ) {
+ int stat = vm_map( pdir, ((void *)k->va_start),
+ k->pa_end - k->pa_start,
+ k->pa_start, k->perm );
+ if( stat != SUCCESS ) {
+ vm_free( pdir );
+ return 0;
+ }
+ }
+
+ return pdir;
+}
+
+/*
+** Creates an initial user VM table hierarchy by copying the
+** system entries into a new page directory.
+**
+** @return a pointer to the new page directory, or NULL
+*/
+pde_t *vm_mkuvm( void ) {
+
+ // allocate the directory
+ pde_t *new = (pde_t *) km_page_alloc();
+ if( new == NULL ) {
+ return NULL;
+ }
+
+ // iterate through the kernel page directory
+ pde_t *curr = kpdir;
+ pde_t *dst = new;
+ for( int i = 0; i < N_PDE; ++i ) {
+
+ if( *curr != 0 ) {
+ // found an active one - duplicate it
+ if( !vm_ptdup(dst,curr) ) {
+ return NULL;
+ }
+ }
+
+ ++curr;
+ ++dst;
+ }
+
+ return new;
+
+}
+
+/**
+** Name: vm_set_kvm
+**
+** Switch the page table register to the kernel's page directory.
+*/
+void vm_set_kvm( void ) {
+ w_cr3( V2P(kpdir) ); // switch to the kernel page table
+}
+
+/**
+** Name: vm_set_uvm
+**
+** Switch the page table register to the page directory for a user process.
+**
+** @param p PCB of the process we're switching to
+*/
+void vm_set_uvm( pcb_t *p ) {
+ assert( p != NULL );
+ assert( p->pdir != NULL );
+
+ w_cr3( V2P(p->pdir) ); // switch to process's address space
+}
+
+/**
+** Name: vm_add
+**
+** Add pages to the page hierarchy for a process, copying data into
+** them if necessary.
+**
+** @param pdir Pointer to the page directory to modify
+** @param wr "Writable" flag for the PTE
+** @param sys "System" flag for the PTE
+** @param va Starting VA of the range
+** @param size Amount of physical memory to allocate (bytes)
+** @param data Pointer to data to copy, or NULL
+** @param bytes Number of bytes to copy
+**
+** @return status of the allocation attempt
+*/
+int vm_add( pde_t *pdir, bool_t wr, bool_t sys,
+ void *va, uint32_t size, char *data, uint32_t bytes ) {
+
+ // how many pages do we need?
+ uint_t npages = ((size & MOD4K_BITS) ? PGUP(size) : size) >> MOD4K_SHIFT;
+
+ // permission set for the PTEs
+ uint_t entrybase = PTE_P;
+ if( wr ) {
+ entrybase |= PTE_RW;
+ }
+ if( sys ) {
+ entrybase |= PTE_US;
+ }
+
+#if TRACING_VM
+ cio_printf( "vm_add: pdir %08x, %s, va %08x (%u, %u pgs)\n",
+ (uint32_t) pdir, wr ? "W" : "!W", (uint32_t) va, size );
+ cio_printf( " from %08x, %u bytes, perms %08x\n",
+ (uint32_t) data, bytes, entrybase );
+#endif
+
+ // iterate through the pages
+
+ for( int i = 0; i < npages; ++i ) {
+
+ // figure out where this page will go in the hierarchy
+ pte_t *pte = vm_getpte( pdir, va, true );
+ if( pte == NULL ) {
+ // TODO if i > 0, this isn't the first frame - is
+ // there anything to do about other frames?
+ // POSSIBLE MEMORY LEAK?
+ return E_NO_MEMORY;
+ }
+
+ // allocate the frame
+ void *page = km_page_alloc();
+ if( page == NULL ) {
+ // TODO same question here
+ return E_NO_MEMORY;
+ }
+
+ // clear it all out
+ memclr( page, SZ_PAGE );
+
+ // create the PTE for this frame
+ uint32_t entry = (uint32_t) (PTE_ADDR(page) | entrybase);
+ *pte = entry;
+
+ // copy data if we need to
+ if( data != NULL && bytes > 0 ) {
+ // how much to copy
+ uint_t num = bytes > SZ_PAGE ? SZ_PAGE : bytes;
+ // do it!
+ memcpy( (void *)page, (void *)data, num );
+ // adjust all the pointers
+ data += num; // where to continue
+ bytes -= num; // what's left to copy
+ }
+
+ // bump the virtual address
+ va += SZ_PAGE;
+ }
+
+ return SUCCESS;
+
+}
+
+/**
+** Name: vm_free
+**
+** Deallocate a page table hierarchy and all physical memory frames
+** in the user portion.
+**
+** @param pdir Pointer to the page directory
+*/
+void vm_free( pde_t *pdir ) {
+
+ // do we have anything to do?
+ if( pdir == NULL ) {
+ return;
+ }
+
+ // iterate through the page directory entries, freeing the
+ // PMTS and the frames they point to
+ pde_t *curr = pdir;
+ for( int i = 0; i < N_PDE; ++i ) {
+
+ // does this entry point to anything useful?
+ if( IS_PRESENT(*curr) ) {
+
+ // yes - get the PMT pointer
+ pte_t *pte = (pte_t *) PTE_ADDR(*curr);
+
+ // walk the PMT
+ for( int j = 0; j < N_PTE; ++j ) {
+ // does this entry point to a frame?
+ if( IS_PRESENT(*pte) ) {
+ // yes - free the frame
+ km_page_free( (void *) PTE_ADDR(*pte) );
+ // mark it so we don't get surprised
+ *pte = 0;
+ }
+ // move on
+ ++pte;
+ }
+ // now, free the PMT itself
+ km_page_free( (void *) PDE_ADDR(*curr) );
+ *curr = 0;
+ }
+
+ // move to the next entry
+ ++curr;
+ }
+
+ // finally, free the PDIR itself
+ km_page_free( (void *) pdir );
+}
+
+/*
+** Name: vm_map
+**
+** Create PTEs for virtual addresses starting at 'va' that refer to
+** physical addresses in the range [pa, pa+size-1]. We aren't guaranteed
+** that va is page-aligned.
+**
+** @param pdir Page directory for this address space
+** @param va The starting virtual address
+** @param size Length of the range to be mapped
+** @param pa The starting physical address
+** @param perm Permission bits for the PTEs
+*/
+int vm_map( pde_t *pdir, void *va, uint_t size, uint_t pa, int perm ) {
+ pte_t *pte;
+
+ // round the VA down to its page boundary
+ char *addr = (char*)PGDOWN((uint_t)va);
+
+ // round the end of the range down to its page boundary
+ char *last = (char*)PGDOWN(((uint_t)va) + size - 1);
+
+ for(;;) {
+
+ // get a pointer to the PTE for the current VA
+ if( (pte = vm_getpte(pdir, addr, 1)) == 0 ) {
+ // couldn't find it
+ return E_NO_PTE;
+ }
+
+ // if this entry has already been mapped, we're in trouble
+ if( IS_PRESENT(*pte) ) {
+ PANIC( 0, "mapping an already-mapped address" );
+ }
+
+ // ok, set the PTE as requested
+ *pte = pa | perm | PTE_P;
+
+ // are we done?
+ if( addr == last ) {
+ break;
+ }
+
+ // nope - move to the next page
+ addr += SZ_PAGE;
+ pa += SZ_PAGE;
+ }
+ return 0;
+}
+
+/**
+** Name: vm_uvmdup
+**
+** Create a duplicate of the user portio of an existing page table
+** hierarchy. We assume that the "new" page directory exists and
+** the system portions of it should not be touched.
+**
+** Note: we do not duplicate the frames in the hierarchy - we just
+** create a duplicate of the hierarchy itself. This means that we
+** now have two sets of page tables that refer to the same user-level
+** frames in memory.
+**
+** @param old Existing page directory
+** @param new New page directory
+**
+** @return status of the duplication attempt
+*/
+int vm_uvmdup( pde_t *old, pde_t *new ) {
+
+ if( old == NULL || new == NULL ) {
+ return E_BAD_PARAM;
+ }
+
+ // we only want to deal with the "user" half of the address space
+ for( int i = 0; i < (N_PDE >> 1); ++i ) {
+
+ // is this entry in use?
+ if( IS_PRESENT(*old) ) {
+
+ // yes. if it points to a 4MB page, we just copy it;
+ // otherwise, we must duplicate the next level PMT
+
+ *new = *old; // copy the entry
+
+ if( !IS_LARGE(*old) ) {
+
+ // it's a 4KB page, so we need to duplicate the PMT
+ pte_t *newpmt = (pte_t *) vm_pagedup( (void *) (*old & FRAME_MASK) );
+ if( newpmt == NULL ) {
+ return E_NO_MEMORY;
+ }
+
+ // create the new PDE entry by replacing the frame #
+ *new = (pde_t) (((uint32_t)newpmt) | PERMS(*old));
+ }
+ }
+
+ ++old;
+ ++new;
+ }
+
+ return SUCCESS;
+}