#include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "vm.h" #include "proto.h" #include "util.h" #include "glo.h" #include "region.h" #include "sanitycheck.h" #include "yieldedavl.h" #include "memlist.h" #include "memtype.h" /* LRU list. */ static yielded_t *lru_youngest = NULL, *lru_oldest = NULL; static int map_ph_writept(struct vmproc *vmp, struct vir_region *vr, struct phys_region *pr); static phys_bytes freeyieldednode(yielded_t *node, int freemem); static struct vir_region *map_copy_region(struct vmproc *vmp, struct vir_region *vr); #if SANITYCHECKS static void lrucheck(void); #endif /* hash table of yielded blocks */ #define YIELD_HASHSIZE 65536 static yielded_avl vm_yielded_blocks[YIELD_HASHSIZE]; static int avl_inited = 0; void map_region_init(void) { int h; assert(!avl_inited); for(h = 0; h < YIELD_HASHSIZE; h++) yielded_init(&vm_yielded_blocks[h]); avl_inited = 1; } static yielded_avl *get_yielded_avl(block_id_t id) { u32_t h; assert(avl_inited); hash_i_64(id.owner, id.id, h); h = h % YIELD_HASHSIZE; assert(h >= 0); assert(h < YIELD_HASHSIZE); return &vm_yielded_blocks[h]; } void map_printregion(struct vir_region *vr) { int i; struct phys_region *ph; printf("map_printmap: map_name: %s\n", vr->memtype->name); printf("\t%lx (len 0x%lx, %lukB), %p\n", vr->vaddr, vr->length, vr->length/1024, vr->memtype->name); printf("\t\tphysblocks:\n"); for(i = 0; i < vr->length/VM_PAGE_SIZE; i++) { if(!(ph=vr->physblocks[i])) continue; printf("\t\t@ %lx (refs %d): phys 0x%lx\n", (vr->vaddr + ph->offset), ph->ph->refcount, ph->ph->phys); } } struct phys_region *physblock_get(struct vir_region *region, vir_bytes offset) { int i; struct phys_region *foundregion; assert(!(offset % VM_PAGE_SIZE)); assert(offset >= 0 && offset < region->length); i = offset/VM_PAGE_SIZE; if((foundregion = region->physblocks[i])) assert(foundregion->offset == offset); return foundregion; } void physblock_set(struct vir_region *region, vir_bytes offset, struct phys_region *newphysr) { int i; assert(!(offset % VM_PAGE_SIZE)); assert(offset >= 0 && offset < region->length); i = offset/VM_PAGE_SIZE; if(newphysr) { assert(!region->physblocks[i]); assert(newphysr->offset == offset); } else { assert(region->physblocks[i]); } region->physblocks[i] = newphysr; } /*===========================================================================* * map_printmap * *===========================================================================*/ void map_printmap(vmp) struct vmproc *vmp; { struct vir_region *vr; region_iter iter; printf("memory regions in process %d:\n", vmp->vm_endpoint); region_start_iter_least(&vmp->vm_regions_avl, &iter); while((vr = region_get_iter(&iter))) { map_printregion(vr); region_incr_iter(&iter); } } static struct vir_region *getnextvr(struct vir_region *vr) { struct vir_region *nextvr; region_iter v_iter; SLABSANE(vr); region_start_iter(&vr->parent->vm_regions_avl, &v_iter, vr->vaddr, AVL_EQUAL); assert(region_get_iter(&v_iter)); assert(region_get_iter(&v_iter) == vr); region_incr_iter(&v_iter); nextvr = region_get_iter(&v_iter); if(!nextvr) return NULL; SLABSANE(nextvr); assert(vr->parent == nextvr->parent); assert(vr->vaddr < nextvr->vaddr); assert(vr->vaddr + vr->length <= nextvr->vaddr); return nextvr; } int pr_writable(struct vir_region *vr, struct phys_region *pr) { assert(vr->memtype->writable); return ((vr->flags & VR_WRITABLE) && vr->memtype->writable(pr)); } #if SANITYCHECKS /*===========================================================================* * map_sanitycheck_pt * *===========================================================================*/ static int map_sanitycheck_pt(struct vmproc *vmp, struct vir_region *vr, struct phys_region *pr) { struct phys_block *pb = pr->ph; int rw; int r; if(pr_writable(vr, pr)) rw = PTF_WRITE; else rw = PTF_READ; r = pt_writemap(vmp, &vmp->vm_pt, vr->vaddr + pr->offset, pb->phys, VM_PAGE_SIZE, PTF_PRESENT | PTF_USER | rw, WMF_VERIFY); if(r != OK) { printf("proc %d phys_region 0x%lx sanity check failed\n", vmp->vm_endpoint, pr->offset); map_printregion(vr); } return r; } /*===========================================================================* * map_sanitycheck * *===========================================================================*/ void map_sanitycheck(char *file, int line) { struct vmproc *vmp; lrucheck(); /* Macro for looping over all physical blocks of all regions of * all processes. */ #define ALLREGIONS(regioncode, physcode) \ for(vmp = vmproc; vmp < &vmproc[VMP_NR]; vmp++) { \ vir_bytes voffset; \ region_iter v_iter; \ struct vir_region *vr; \ if(!(vmp->vm_flags & VMF_INUSE)) \ continue; \ region_start_iter_least(&vmp->vm_regions_avl, &v_iter); \ while((vr = region_get_iter(&v_iter))) { \ struct phys_region *pr; \ regioncode; \ for(voffset = 0; voffset < vr->length; \ voffset += VM_PAGE_SIZE) { \ if(!(pr = physblock_get(vr, voffset))) \ continue; \ physcode; \ } \ region_incr_iter(&v_iter); \ } \ } #define MYSLABSANE(s) MYASSERT(slabsane_f(__FILE__, __LINE__, s, sizeof(*(s)))) /* Basic pointers check. */ ALLREGIONS(MYSLABSANE(vr),MYSLABSANE(pr); MYSLABSANE(pr->ph);MYSLABSANE(pr->parent)); ALLREGIONS(/* MYASSERT(vr->parent == vmp) */,MYASSERT(pr->parent == vr);); /* Do counting for consistency check. */ ALLREGIONS(;,USE(pr->ph, pr->ph->seencount = 0;);); ALLREGIONS(;,MYASSERT(pr->offset == voffset);); ALLREGIONS(;,USE(pr->ph, pr->ph->seencount++;); if(pr->ph->seencount == 1) { if(pr->parent->memtype->ev_sanitycheck) pr->parent->memtype->ev_sanitycheck(pr, file, line); } ); /* Do consistency check. */ ALLREGIONS({ struct vir_region *nextvr = getnextvr(vr); if(nextvr) { MYASSERT(vr->vaddr < nextvr->vaddr); MYASSERT(vr->vaddr + vr->length <= nextvr->vaddr); } } MYASSERT(!(vr->vaddr % VM_PAGE_SIZE));, if(pr->ph->refcount != pr->ph->seencount) { map_printmap(vmp); printf("ph in vr %p: 0x%lx refcount %u " "but seencount %u\n", vr, pr->offset, pr->ph->refcount, pr->ph->seencount); } { int n_others = 0; struct phys_region *others; if(pr->ph->refcount > 0) { MYASSERT(pr->ph->firstregion); if(pr->ph->refcount == 1) { MYASSERT(pr->ph->firstregion == pr); } } else { MYASSERT(!pr->ph->firstregion); } for(others = pr->ph->firstregion; others; others = others->next_ph_list) { MYSLABSANE(others); MYASSERT(others->ph == pr->ph); n_others++; } MYASSERT(pr->ph->refcount == n_others); } MYASSERT(pr->ph->refcount == pr->ph->seencount); MYASSERT(!(pr->offset % VM_PAGE_SIZE));); ALLREGIONS(,MYASSERT(map_sanitycheck_pt(vmp, vr, pr) == OK)); } #define LRUCHECK lrucheck() static void lrucheck(void) { yielded_t *list; /* list is empty and ok if both ends point to null. */ if(!lru_youngest && !lru_oldest) return; /* if not, both should point to something. */ SLABSANE(lru_youngest); SLABSANE(lru_oldest); assert(!lru_youngest->younger); assert(!lru_oldest->older); for(list = lru_youngest; list; list = list->older) { SLABSANE(list); if(list->younger) { SLABSANE(list->younger); assert(list->younger->older == list); } else assert(list == lru_youngest); if(list->older) { SLABSANE(list->older); assert(list->older->younger == list); } else assert(list == lru_oldest); } } void blockstats(void) { yielded_t *list; int blocks = 0; phys_bytes mem = 0; clock_t ticks; int s; s = getuptime(&ticks); assert(s == OK); LRUCHECK; for(list = lru_youngest; list; list = list->older) { mem += VM_PAGE_SIZE; blocks++; } if(blocks > 0) printf("%d blocks, %lukB; ", blocks, mem/1024); printmemstats(); } #else #define LRUCHECK #endif /*=========================================================================* * map_ph_writept * *=========================================================================*/ static int map_ph_writept(struct vmproc *vmp, struct vir_region *vr, struct phys_region *pr) { int flags = PTF_PRESENT | PTF_USER; struct phys_block *pb = pr->ph; assert(vr); assert(pr); assert(pb); assert(!(vr->vaddr % VM_PAGE_SIZE)); assert(!(pr->offset % VM_PAGE_SIZE)); assert(pb->refcount > 0); if(pr_writable(vr, pr)) flags |= PTF_WRITE; else flags |= PTF_READ; #if defined(__arm__) if (pb->phys >= 0x80000000 && pb->phys < (0xc0000000 - VM_PAGE_SIZE)) { // LSC Do this only for actual RAM flags |= ARM_VM_PTE_WT; } #endif if(pt_writemap(vmp, &vmp->vm_pt, vr->vaddr + pr->offset, pb->phys, VM_PAGE_SIZE, flags, #if SANITYCHECKS !pr->written ? 0 : #endif WMF_OVERWRITE) != OK) { printf("VM: map_writept: pt_writemap failed\n"); return ENOMEM; } #if SANITYCHECKS USE(pr, pr->written = 1;); #endif return OK; } #define SLOT_FAIL ((vir_bytes) -1) /*===========================================================================* * region_find_slot_range * *===========================================================================*/ static vir_bytes region_find_slot_range(struct vmproc *vmp, vir_bytes minv, vir_bytes maxv, vir_bytes length) { struct vir_region *lastregion; vir_bytes startv = 0; int foundflag = 0; region_iter iter; SANITYCHECK(SCL_FUNCTIONS); /* Length must be reasonable. */ assert(length > 0); /* Special case: allow caller to set maxv to 0 meaning 'I want * it to be mapped in right here.' */ if(maxv == 0) { maxv = minv + length; /* Sanity check. */ if(maxv <= minv) { printf("region_find_slot: minv 0x%lx and bytes 0x%lx\n", minv, length); return SLOT_FAIL; } } /* Basic input sanity checks. */ assert(!(length % VM_PAGE_SIZE)); if(minv >= maxv) { printf("VM: 1 minv: 0x%lx maxv: 0x%lx length: 0x%lx\n", minv, maxv, length); } assert(minv < maxv); if(minv + length > maxv) return SLOT_FAIL; #define FREEVRANGE_TRY(rangestart, rangeend) { \ vir_bytes frstart = (rangestart), frend = (rangeend); \ frstart = MAX(frstart, minv); \ frend = MIN(frend, maxv); \ if(frend > frstart && (frend - frstart) >= length) { \ startv = frend-length; \ foundflag = 1; \ } } #define FREEVRANGE(start, end) { \ assert(!foundflag); \ FREEVRANGE_TRY(((start)+VM_PAGE_SIZE), ((end)-VM_PAGE_SIZE)); \ if(!foundflag) { \ FREEVRANGE_TRY((start), (end)); \ } \ } /* find region after maxv. */ region_start_iter(&vmp->vm_regions_avl, &iter, maxv, AVL_GREATER_EQUAL); lastregion = region_get_iter(&iter); if(!lastregion) { /* This is the free virtual address space after the last region. */ region_start_iter(&vmp->vm_regions_avl, &iter, maxv, AVL_LESS); lastregion = region_get_iter(&iter); FREEVRANGE(lastregion ? lastregion->vaddr+lastregion->length : 0, VM_DATATOP); } if(!foundflag) { struct vir_region *vr; while((vr = region_get_iter(&iter)) && !foundflag) { struct vir_region *nextvr; region_decr_iter(&iter); nextvr = region_get_iter(&iter); FREEVRANGE(nextvr ? nextvr->vaddr+nextvr->length : 0, vr->vaddr); } } if(!foundflag) { return SLOT_FAIL; } /* However we got it, startv must be in the requested range. */ assert(startv >= minv); assert(startv < maxv); assert(startv + length <= maxv); /* remember this position as a hint for next time. */ vmp->vm_region_top = startv + length; return startv; } /*===========================================================================* * region_find_slot * *===========================================================================*/ static vir_bytes region_find_slot(struct vmproc *vmp, vir_bytes minv, vir_bytes maxv, vir_bytes length) { vir_bytes v, hint = vmp->vm_region_top; /* use the top of the last inserted region as a minv hint if * possible. remember that a zero maxv is a special case. */ if(maxv && hint < maxv && hint >= minv) { v = region_find_slot_range(vmp, minv, hint, length); if(v != SLOT_FAIL) return v; } return region_find_slot_range(vmp, minv, maxv, length); } static int phys_slot(vir_bytes len) { assert(!(len % VM_PAGE_SIZE)); return len / VM_PAGE_SIZE; } struct vir_region *region_new(struct vmproc *vmp, vir_bytes startv, vir_bytes length, int flags, mem_type_t *memtype) { struct vir_region *newregion; struct phys_region **physregions; static u32_t id; int slots = phys_slot(length); if(!(SLABALLOC(newregion))) { printf("vm: region_new: could not allocate\n"); return NULL; } /* Fill in node details. */ USE(newregion, memset(newregion, 0, sizeof(*newregion)); newregion->vaddr = startv; newregion->length = length; newregion->flags = flags; newregion->memtype = memtype; newregion->remaps = 0; newregion->id = id++; newregion->lower = newregion->higher = NULL; newregion->parent = vmp;); if(!(physregions = calloc(slots, sizeof(struct phys_region *)))) { printf("VM: region_new: allocating phys blocks failed\n"); SLABFREE(newregion); return NULL; } USE(newregion, newregion->physblocks = physregions;); return newregion; } /*===========================================================================* * map_page_region * *===========================================================================*/ struct vir_region *map_page_region(vmp, minv, maxv, length, flags, mapflags, memtype) struct vmproc *vmp; vir_bytes minv; vir_bytes maxv; vir_bytes length; u32_t flags; int mapflags; mem_type_t *memtype; { struct vir_region *newregion; vir_bytes startv; assert(!(length % VM_PAGE_SIZE)); SANITYCHECK(SCL_FUNCTIONS); startv = region_find_slot(vmp, minv, maxv, length); if (startv == SLOT_FAIL) return NULL; /* Now we want a new region. */ if(!(newregion = region_new(vmp, startv, length, flags, memtype))) { printf("VM: map_page_region: allocating region failed\n"); return NULL; } /* If a new event is specified, invoke it. */ if(newregion->memtype->ev_new) { if(newregion->memtype->ev_new(newregion) != OK) { /* ev_new will have freed and removed the region */ return NULL; } } if(mapflags & MF_PREALLOC) { if(map_handle_memory(vmp, newregion, 0, length, 1) != OK) { printf("VM: map_page_region: prealloc failed\n"); free(newregion->physblocks); USE(newregion, newregion->physblocks = NULL;); SLABFREE(newregion); return NULL; } } /* Pre-allocations should be uninitialized, but after that it's a * different story. */ USE(newregion, newregion->flags &= ~VR_UNINITIALIZED;); /* Link it. */ region_insert(&vmp->vm_regions_avl, newregion); #if SANITYCHECKS assert(startv == newregion->vaddr); { struct vir_region *nextvr; if((nextvr = getnextvr(newregion))) { assert(newregion->vaddr < nextvr->vaddr); } } #endif SANITYCHECK(SCL_FUNCTIONS); return newregion; } /*===========================================================================* * map_subfree * *===========================================================================*/ static int map_subfree(struct vir_region *region, vir_bytes start, vir_bytes len) { struct phys_region *pr; vir_bytes end = start+len; vir_bytes voffset; #if SANITYCHECKS SLABSANE(region); for(voffset = 0; voffset < phys_slot(region->length); voffset += VM_PAGE_SIZE) { struct phys_region *others; struct phys_block *pb; if(!(pr = physblock_get(region, voffset))) continue; pb = pr->ph; for(others = pb->firstregion; others; others = others->next_ph_list) { assert(others->ph == pb); } } #endif for(voffset = start; voffset < end; voffset+=VM_PAGE_SIZE) { if(!(pr = physblock_get(region, voffset))) continue; assert(pr->offset >= start); assert(pr->offset < end); pb_unreferenced(region, pr, 1); SLABFREE(pr); } return OK; } /*===========================================================================* * map_free * *===========================================================================*/ int map_free(struct vir_region *region) { int r; if((r=map_subfree(region, 0, region->length)) != OK) { printf("%d\n", __LINE__); return r; } if(region->memtype->ev_delete) region->memtype->ev_delete(region); free(region->physblocks); region->physblocks = NULL; SLABFREE(region); return OK; } /*===========================================================================* * yielded_block_cmp * *===========================================================================*/ int yielded_block_cmp(struct block_id *id1, struct block_id *id2) { if(id1->owner < id2->owner) return -1; if(id1->owner > id2->owner) return 1; return cmp64(id1->id, id2->id); } /*===========================================================================* * free_yielded_proc * *===========================================================================*/ static vir_bytes free_yielded_proc(struct vmproc *vmp) { vir_bytes total = 0; int h; SANITYCHECK(SCL_FUNCTIONS); /* Free associated regions. */ for(h = 0; h < YIELD_HASHSIZE && vmp->vm_yielded > 0; h++) { yielded_t *yb; yielded_iter iter; yielded_avl *avl = &vm_yielded_blocks[h]; yielded_start_iter_least(avl, &iter); while((yb = yielded_get_iter(&iter))) { yielded_t *next_yb; SLABSANE(yb); yielded_incr_iter(&iter); if(yb->id.owner != vmp->vm_endpoint) continue; next_yb = yielded_get_iter(&iter); total += freeyieldednode(yb, 1); /* the above removal invalidated our iter; restart it * for the node we want to start at. */ if(!next_yb) break; yielded_start_iter(avl, &iter, next_yb->id, AVL_EQUAL); assert(yielded_get_iter(&iter) == next_yb); } } return total; } static phys_bytes freeyieldednode(yielded_t *node, int freemem) { yielded_t *older, *younger, *removed; yielded_avl *avl; int p; SLABSANE(node); LRUCHECK; /* Update LRU. */ younger = node->younger; older = node->older; if(younger) { SLABSANE(younger); assert(younger->older == node); USE(younger, younger->older = node->older;); } else { assert(node == lru_youngest); lru_youngest = node->older; } if(older) { SLABSANE(older); assert(older->younger == node); USE(older, older->younger = node->younger;); } else { assert(node == lru_oldest); lru_oldest = node->younger; } LRUCHECK; /* Update AVL. */ if(vm_isokendpt(node->id.owner, &p) != OK) panic("out of date owner of yielded block %d", node->id.owner); avl = get_yielded_avl(node->id); removed = yielded_remove(avl, node->id); assert(removed == node); assert(vmproc[p].vm_yielded > 0); vmproc[p].vm_yielded--; /* Free associated memory if requested. */ if(freemem) { free_mem(ABS2CLICK(node->physaddr), node->pages); } /* Free node. */ SLABFREE(node); return VM_PAGE_SIZE; } /*========================================================================* * free_yielded * *========================================================================*/ vir_bytes free_yielded(vir_bytes max_bytes) { /* PRIVATE yielded_t *lru_youngest = NULL, *lru_oldest = NULL; */ vir_bytes freed = 0; int blocks = 0; while(freed < max_bytes && lru_oldest) { SLABSANE(lru_oldest); freed += freeyieldednode(lru_oldest, 1); blocks++; } return freed; } /*========================================================================* * map_free_proc * *========================================================================*/ int map_free_proc(vmp) struct vmproc *vmp; { struct vir_region *r; while((r = region_search_root(&vmp->vm_regions_avl))) { SANITYCHECK(SCL_DETAIL); #if SANITYCHECKS nocheck++; #endif region_remove(&vmp->vm_regions_avl, r->vaddr); /* For sanity checks. */ map_free(r); #if SANITYCHECKS nocheck--; #endif SANITYCHECK(SCL_DETAIL); } region_init(&vmp->vm_regions_avl); /* Free associated yielded blocks. */ free_yielded_proc(vmp); SANITYCHECK(SCL_FUNCTIONS); return OK; } /*===========================================================================* * map_lookup * *===========================================================================*/ struct vir_region *map_lookup(vmp, offset, physr) struct vmproc *vmp; vir_bytes offset; struct phys_region **physr; { struct vir_region *r; SANITYCHECK(SCL_FUNCTIONS); #if SANITYCHECKS if(!region_search_root(&vmp->vm_regions_avl)) panic("process has no regions: %d", vmp->vm_endpoint); #endif if((r = region_search(&vmp->vm_regions_avl, offset, AVL_LESS_EQUAL))) { vir_bytes ph; if(offset >= r->vaddr && offset < r->vaddr + r->length) { ph = offset - r->vaddr; if(physr) { *physr = physblock_get(r, ph); if(*physr) assert((*physr)->offset == ph); } return r; } } SANITYCHECK(SCL_FUNCTIONS); return NULL; } u32_t vrallocflags(u32_t flags) { u32_t allocflags = 0; if(flags & VR_PHYS64K) allocflags |= PAF_ALIGN64K; if(flags & VR_LOWER16MB) allocflags |= PAF_LOWER16MB; if(flags & VR_LOWER1MB) allocflags |= PAF_LOWER1MB; if(flags & VR_CONTIG) allocflags |= PAF_CONTIG; if(!(flags & VR_UNINITIALIZED)) allocflags |= PAF_CLEAR; return allocflags; } /*===========================================================================* * map_clone_ph_block * *===========================================================================*/ struct phys_region *map_clone_ph_block(vmp, region, ph) struct vmproc *vmp; struct vir_region *region; struct phys_region *ph; { vir_bytes offset; u32_t allocflags; phys_bytes physaddr; struct phys_region *newpr; int region_has_single_block; SANITYCHECK(SCL_FUNCTIONS); /* Warning: this function will free the passed * phys_region *ph and replace it (in the same offset) * with another! So both the pointer to it * and any iterators over the phys_regions in the vir_region * will be invalid on successful return. (Iterators over * the vir_region could be invalid on unsuccessful return too.) */ /* This is only to be done if there is more than one copy. */ assert(ph->ph->refcount > 1); /* This function takes a physical block, copies its contents * into newly allocated memory, and replaces the single physical * block by one or more physical blocks with refcount 1 with the * same contents as the original. In other words, a fragmentable * version of map_copy_ph_block(). */ /* Remember where and how much. */ offset = ph->offset; physaddr = ph->ph->phys; /* Now unlink the original physical block so we can replace * it with new ones. */ SLABSANE(ph); SLABSANE(ph->ph); assert(ph->ph->refcount > 1); pb_unreferenced(region, ph, 1); SLABFREE(ph); SANITYCHECK(SCL_DETAIL); /* Put new free memory in. */ allocflags = vrallocflags(region->flags | VR_UNINITIALIZED); region_has_single_block = (offset == 0 && region->length == VM_PAGE_SIZE); assert(region_has_single_block || !(allocflags & PAF_CONTIG)); assert(!(allocflags & PAF_CLEAR)); if(map_pf(vmp, region, offset, 1) != OK) { /* XXX original range now gone. */ printf("VM: map_clone_ph_block: map_pf failed.\n"); return NULL; } /* Copy the block to the new memory. * Can only fail if map_new_physblock didn't do what we asked. */ if(copy_abs2region(physaddr, region, offset, VM_PAGE_SIZE) != OK) panic("copy_abs2region failed, no good reason for that"); newpr = physblock_get(region, offset); assert(newpr); assert(newpr->offset == offset); SANITYCHECK(SCL_FUNCTIONS); return newpr; } /*===========================================================================* * map_pf * *===========================================================================*/ int map_pf(vmp, region, offset, write) struct vmproc *vmp; struct vir_region *region; vir_bytes offset; int write; { struct phys_region *ph; int r = OK; offset -= offset % VM_PAGE_SIZE; assert(offset >= 0); assert(offset < region->length); assert(!(region->vaddr % VM_PAGE_SIZE)); assert(!(write && !(region->flags & VR_WRITABLE))); SANITYCHECK(SCL_FUNCTIONS); if(!(ph = physblock_get(region, offset))) { struct phys_block *pb; /* New block. */ if(!(pb = pb_new(MAP_NONE))) { printf("map_pf: pb_new failed\n"); return ENOMEM; } if(!(ph = pb_reference(pb, offset, region))) { printf("map_pf: pb_reference failed\n"); pb_free(pb); return ENOMEM; } } assert(ph); assert(ph->ph); /* If we're writing and the block is already * writable, nothing to do. */ assert(region->memtype->writable); if(!write || !region->memtype->writable(ph)) { assert(region->memtype->ev_pagefault); assert(ph->ph); if((r = region->memtype->ev_pagefault(vmp, region, ph, write)) == SUSPEND) { panic("map_pf: memtype->ev_pagefault returned SUSPEND\n"); return SUSPEND; } if(r != OK) { printf("map_pf: memtype->ev_pagefault failed\n"); if(ph) pb_unreferenced(region, ph, 1); return r; } assert(ph); assert(ph->ph); assert(ph->ph->phys != MAP_NONE); } assert(ph->ph); assert(ph->ph->phys != MAP_NONE); if((r = map_ph_writept(vmp, region, ph)) != OK) { printf("map_pf: writept failed\n"); return r; } SANITYCHECK(SCL_FUNCTIONS); #if SANITYCHECKS if(OK != pt_checkrange(&vmp->vm_pt, region->vaddr+offset, VM_PAGE_SIZE, write)) { panic("map_pf: pt_checkrange failed: %d", r); } #endif return r; } int map_handle_memory(vmp, region, start_offset, length, write) struct vmproc *vmp; struct vir_region *region; vir_bytes start_offset; vir_bytes length; int write; { vir_bytes offset, lim; int r; assert(length > 0); lim = start_offset + length; assert(lim > start_offset); for(offset = start_offset; offset < lim; offset += VM_PAGE_SIZE) if((r = map_pf(vmp, region, offset, write)) != OK) return r; return OK; } /*===========================================================================* * map_pin_memory * *===========================================================================*/ int map_pin_memory(struct vmproc *vmp) { struct vir_region *vr; int r; region_iter iter; region_start_iter_least(&vmp->vm_regions_avl, &iter); /* Scan all memory regions. */ while((vr = region_get_iter(&iter))) { /* Make sure region is mapped to physical memory and writable.*/ r = map_handle_memory(vmp, vr, 0, vr->length, 1); if(r != OK) { panic("map_pin_memory: map_handle_memory failed: %d", r); } region_incr_iter(&iter); } return OK; } /*===========================================================================* * map_copy_region * *===========================================================================*/ static struct vir_region *map_copy_region(struct vmproc *vmp, struct vir_region *vr) { /* map_copy_region creates a complete copy of the vir_region * data structure, linking in the same phys_blocks directly, * but all in limbo, i.e., the caller has to link the vir_region * to a process. Therefore it doesn't increase the refcount in * the phys_block; the caller has to do this once it's linked. * The reason for this is to keep the sanity checks working * within this function. */ struct vir_region *newvr; struct phys_region *ph; int r; #if SANITYCHECKS int cr; cr = physregions(vr); #endif vir_bytes p; if(!(newvr = region_new(vr->parent, vr->vaddr, vr->length, vr->flags, vr->memtype))) return NULL; if(vr->memtype->ev_copy && (r=vr->memtype->ev_copy(vr, newvr)) != OK) { map_free(newvr); printf("VM: memtype-specific copy failed (%d)\n", r); return NULL; } for(p = 0; p < phys_slot(vr->length); p++) { if(!(ph = physblock_get(vr, p*VM_PAGE_SIZE))) continue; struct phys_region *newph = pb_reference(ph->ph, ph->offset, newvr); if(!newph) { map_free(newvr); return NULL; } #if SANITYCHECKS USE(newph, newph->written = 0;); assert(physregions(vr) == cr); #endif } #if SANITYCHECKS assert(physregions(vr) == physregions(newvr)); #endif return newvr; } /*===========================================================================* * copy_abs2region * *===========================================================================*/ int copy_abs2region(phys_bytes abs, struct vir_region *destregion, phys_bytes offset, phys_bytes len) { assert(destregion); assert(destregion->physblocks); while(len > 0) { phys_bytes sublen, suboffset; struct phys_region *ph; assert(destregion); assert(destregion->physblocks); if(!(ph = physblock_get(destregion, offset))) { printf("VM: copy_abs2region: no phys region found (1).\n"); return EFAULT; } assert(ph->offset <= offset); if(ph->offset+VM_PAGE_SIZE <= offset) { printf("VM: copy_abs2region: no phys region found (2).\n"); return EFAULT; } suboffset = offset - ph->offset; assert(suboffset < VM_PAGE_SIZE); sublen = len; if(sublen > VM_PAGE_SIZE - suboffset) sublen = VM_PAGE_SIZE - suboffset; assert(suboffset + sublen <= VM_PAGE_SIZE); if(ph->ph->refcount != 1) { printf("VM: copy_abs2region: refcount not 1.\n"); return EFAULT; } if(sys_abscopy(abs, ph->ph->phys + suboffset, sublen) != OK) { printf("VM: copy_abs2region: abscopy failed.\n"); return EFAULT; } abs += sublen; offset += sublen; len -= sublen; } return OK; } /*=========================================================================* * map_writept * *=========================================================================*/ int map_writept(struct vmproc *vmp) { struct vir_region *vr; struct phys_region *ph; int r; region_iter v_iter; region_start_iter_least(&vmp->vm_regions_avl, &v_iter); while((vr = region_get_iter(&v_iter))) { vir_bytes p; for(p = 0; p < vr->length; p += VM_PAGE_SIZE) { if(!(ph = physblock_get(vr, p))) continue; if((r=map_ph_writept(vmp, vr, ph)) != OK) { printf("VM: map_writept: failed\n"); return r; } } region_incr_iter(&v_iter); } return OK; } /*========================================================================* * map_proc_copy * *========================================================================*/ int map_proc_copy(dst, src) struct vmproc *dst; struct vmproc *src; { /* Copy all the memory regions from the src process to the dst process. */ region_init(&dst->vm_regions_avl); return map_proc_copy_from(dst, src, NULL); } /*========================================================================* * map_proc_copy_from * *========================================================================*/ int map_proc_copy_from(dst, src, start_src_vr) struct vmproc *dst; struct vmproc *src; struct vir_region *start_src_vr; { struct vir_region *vr; region_iter v_iter; if(!start_src_vr) start_src_vr = region_search_least(&src->vm_regions_avl); assert(start_src_vr); assert(start_src_vr->parent == src); region_start_iter(&src->vm_regions_avl, &v_iter, start_src_vr->vaddr, AVL_EQUAL); assert(region_get_iter(&v_iter) == start_src_vr); /* Copy source regions after the destination's last region (if any). */ SANITYCHECK(SCL_FUNCTIONS); while((vr = region_get_iter(&v_iter))) { struct vir_region *newvr; if(!(newvr = map_copy_region(dst, vr))) { map_free_proc(dst); return ENOMEM; } USE(newvr, newvr->parent = dst;); region_insert(&dst->vm_regions_avl, newvr); assert(vr->length == newvr->length); #if SANITYCHECKS { vir_bytes vaddr; struct phys_region *orig_ph, *new_ph; assert(vr->physblocks != newvr->physblocks); for(vaddr = 0; vaddr < vr->length; vaddr += VM_PAGE_SIZE) { orig_ph = physblock_get(vr, vaddr); new_ph = physblock_get(newvr, vaddr); if(!orig_ph) { assert(!new_ph); continue;} assert(new_ph); assert(orig_ph != new_ph); assert(orig_ph->ph == new_ph->ph); } } #endif region_incr_iter(&v_iter); } map_writept(src); map_writept(dst); SANITYCHECK(SCL_FUNCTIONS); return OK; } int map_region_extend_upto_v(struct vmproc *vmp, vir_bytes v) { vir_bytes offset = v; struct vir_region *vr, *nextvr; struct phys_region **newpr; int newslots, prevslots, addedslots; offset = roundup(offset, VM_PAGE_SIZE); if(!(vr = region_search(&vmp->vm_regions_avl, offset, AVL_LESS))) { printf("VM: nothing to extend\n"); return ENOMEM; } if(vr->vaddr + vr->length >= v) return OK; assert(vr->vaddr <= offset); newslots = phys_slot(offset - vr->vaddr); prevslots = phys_slot(vr->length); assert(newslots >= prevslots); addedslots = newslots - prevslots; if(!(newpr = realloc(vr->physblocks, newslots * sizeof(struct phys_region *)))) { printf("VM: map_region_extend_upto_v: realloc failed\n"); return ENOMEM; } vr->physblocks = newpr; memset(vr->physblocks + prevslots, 0, addedslots * sizeof(struct phys_region *)); if((nextvr = getnextvr(vr))) { assert(offset <= nextvr->vaddr); } if(nextvr && nextvr->vaddr < offset) { printf("VM: can't grow into next region\n"); return ENOMEM; } if(!vr->memtype->ev_resize) { printf("VM: can't resize this type of memory\n"); return ENOMEM; } return vr->memtype->ev_resize(vmp, vr, offset - vr->vaddr); } /*========================================================================* * map_unmap_region * *========================================================================*/ int map_unmap_region(struct vmproc *vmp, struct vir_region *r, vir_bytes offset, vir_bytes len) { /* Shrink the region by 'len' bytes, from the start. Unreference * memory it used to reference if any. */ vir_bytes regionstart; int freeslots = phys_slot(len); SANITYCHECK(SCL_FUNCTIONS); if(offset+len > r->length || (len % VM_PAGE_SIZE)) { printf("VM: bogus length 0x%lx\n", len); return EINVAL; } regionstart = r->vaddr + offset; /* unreference its memory */ map_subfree(r, offset, len); /* if unmap was at start/end of this region, it actually shrinks */ if(offset == 0) { struct phys_region *pr; vir_bytes voffset; int remslots; region_remove(&vmp->vm_regions_avl, r->vaddr); USE(r, r->vaddr += len; r->length -= len;); remslots = phys_slot(r->length); region_insert(&vmp->vm_regions_avl, r); /* vaddr has increased; to make all the phys_regions * point to the same addresses, make them shrink by the * same amount. */ for(voffset = offset; voffset < r->length; voffset += VM_PAGE_SIZE) { if(!(pr = physblock_get(r, voffset))) continue; assert(pr->offset >= offset); USE(pr, pr->offset -= len;); } if(remslots) memmove(r->physblocks, r->physblocks + freeslots, remslots * sizeof(struct phys_region *)); } else if(offset + len == r->length) { assert(len <= r->length); r->length -= len; } if(r->length == 0) { /* Whole region disappears. Unlink and free it. */ region_remove(&vmp->vm_regions_avl, r->vaddr); map_free(r); } SANITYCHECK(SCL_DETAIL); if(pt_writemap(vmp, &vmp->vm_pt, regionstart, MAP_NONE, len, 0, WMF_OVERWRITE) != OK) { printf("VM: map_unmap_region: pt_writemap failed\n"); return ENOMEM; } SANITYCHECK(SCL_FUNCTIONS); return OK; } /*========================================================================* * map_get_phys * *========================================================================*/ int map_get_phys(struct vmproc *vmp, vir_bytes addr, phys_bytes *r) { struct vir_region *vr; if (!(vr = map_lookup(vmp, addr, NULL)) || (vr->vaddr != addr)) return EINVAL; if (!vr->memtype->regionid) return EINVAL; if(r) *r = vr->memtype->regionid(vr); return OK; } /*========================================================================* * map_get_ref * *========================================================================*/ int map_get_ref(struct vmproc *vmp, vir_bytes addr, u8_t *cnt) { struct vir_region *vr; if (!(vr = map_lookup(vmp, addr, NULL)) || (vr->vaddr != addr) || !vr->memtype->refcount) return EINVAL; if (cnt) *cnt = vr->memtype->refcount(vr); return OK; } /*========================================================================* * get_stats_info * *========================================================================*/ void get_stats_info(struct vm_stats_info *vsi) { yielded_t *yb; vsi->vsi_cached = 0L; for(yb = lru_youngest; yb; yb = yb->older) vsi->vsi_cached++; } void get_usage_info_kernel(struct vm_usage_info *vui) { memset(vui, 0, sizeof(*vui)); vui->vui_total = kernel_boot_info.kernel_allocated_bytes + kernel_boot_info.kernel_allocated_bytes_dynamic; } static void get_usage_info_vm(struct vm_usage_info *vui) { memset(vui, 0, sizeof(*vui)); vui->vui_total = kernel_boot_info.vm_allocated_bytes + get_vm_self_pages() * VM_PAGE_SIZE; } /*========================================================================* * get_usage_info * *========================================================================*/ void get_usage_info(struct vmproc *vmp, struct vm_usage_info *vui) { struct vir_region *vr; struct phys_region *ph; region_iter v_iter; region_start_iter_least(&vmp->vm_regions_avl, &v_iter); vir_bytes voffset; memset(vui, 0, sizeof(*vui)); if(vmp->vm_endpoint == VM_PROC_NR) { get_usage_info_vm(vui); return; } if(vmp->vm_endpoint < 0) { get_usage_info_kernel(vui); return; } while((vr = region_get_iter(&v_iter))) { for(voffset = 0; voffset < vr->length; voffset += VM_PAGE_SIZE) { if(!(ph = physblock_get(vr, voffset))) continue; /* All present pages are counted towards the total. */ vui->vui_total += VM_PAGE_SIZE; if (ph->ph->refcount > 1) { /* Any page with a refcount > 1 is common. */ vui->vui_common += VM_PAGE_SIZE; /* Any common, non-COW page is shared. */ if (vr->flags & VR_SHARED) vui->vui_shared += VM_PAGE_SIZE; } } region_incr_iter(&v_iter); } } /*===========================================================================* * get_region_info * *===========================================================================*/ int get_region_info(struct vmproc *vmp, struct vm_region_info *vri, int max, vir_bytes *nextp) { struct vir_region *vr; vir_bytes next; int count; region_iter v_iter; next = *nextp; if (!max) return 0; region_start_iter(&vmp->vm_regions_avl, &v_iter, next, AVL_GREATER_EQUAL); if(!(vr = region_get_iter(&v_iter))) return 0; for(count = 0; (vr = region_get_iter(&v_iter)) && count < max; count++, vri++) { struct phys_region *ph1 = NULL, *ph2 = NULL; vir_bytes voffset; /* where to start on next iteration, regardless of what we find now */ next = vr->vaddr + vr->length; /* Report part of the region that's actually in use. */ /* Get first and last phys_regions, if any */ for(voffset = 0; voffset < vr->length; voffset += VM_PAGE_SIZE) { struct phys_region *ph; if(!(ph = physblock_get(vr, voffset))) continue; if(!ph1) ph1 = ph; ph2 = ph; } if(!ph1 || !ph2) { assert(!ph1 && !ph2); continue; } /* Report start+length of region starting from lowest use. */ vri->vri_addr = vr->vaddr + ph1->offset; vri->vri_prot = 0; vri->vri_length = ph2->offset + VM_PAGE_SIZE - ph1->offset; /* "AND" the provided protection with per-page protection. */ if (!(vr->flags & VR_WRITABLE)) vri->vri_prot &= ~PROT_WRITE; region_incr_iter(&v_iter); } *nextp = next; return count; } /*========================================================================* * regionprintstats * *========================================================================*/ void printregionstats(struct vmproc *vmp) { struct vir_region *vr; struct phys_region *pr; vir_bytes used = 0, weighted = 0; region_iter v_iter; region_start_iter_least(&vmp->vm_regions_avl, &v_iter); while((vr = region_get_iter(&v_iter))) { vir_bytes voffset; region_incr_iter(&v_iter); if(vr->flags & VR_DIRECT) continue; for(voffset = 0; voffset < vr->length; voffset+=VM_PAGE_SIZE) { if(!(pr = physblock_get(vr, voffset))) continue; used += VM_PAGE_SIZE; weighted += VM_PAGE_SIZE / pr->ph->refcount; } } printf("%6lukB %6lukB\n", used/1024, weighted/1024); return; } /*===========================================================================* * get_clean_phys_region * *===========================================================================*/ static struct phys_region * get_clean_phys_region(struct vmproc *vmp, vir_bytes vaddr, struct vir_region **ret_region) { struct vir_region *region; vir_bytes mapaddr; struct phys_region *ph; mapaddr = vaddr; if(!(region = map_lookup(vmp, mapaddr, &ph)) || !ph) { printf("VM: get_clean_phys_region: 0x%lx not found\n", vaddr); return NULL; } assert(mapaddr >= region->vaddr); assert(mapaddr < region->vaddr + region->length); /* If it's mapped more than once, make a copy. */ assert(ph->ph->refcount > 0); if(ph->ph->refcount > 1) { if(!(ph = map_clone_ph_block(vmp, region, ph))) { printf("VM: get_clean_phys_region: ph copy failed\n"); return NULL; } } assert(ph->ph->refcount == 1); *ret_region = region; return ph; } static int getblock(struct vmproc *vmp, u64_t id, vir_bytes vaddr, int pages) { yielded_t *yb; struct phys_region *ph; struct vir_region *region; yielded_avl *avl; block_id_t blockid; phys_bytes phaddr; int p; /* Try to get the yielded block */ blockid.owner = vmp->vm_endpoint; blockid.id = id; avl = get_yielded_avl(blockid); if(!(yb = yielded_search(avl, blockid, AVL_EQUAL))) { return ESRCH; } if(yb->pages != pages) { printf("VM: getblock: length mismatch (%d != %d)\n", pages, yb->pages); return EFAULT; } phaddr = yb->physaddr; for(p = 0; p < pages; p++) { /* Get the intended phys region, make sure refcount is 1. */ if(!(ph = get_clean_phys_region(vmp, vaddr, ®ion))) { printf("VM: getblock: not found for %d\n", vmp->vm_endpoint); return EINVAL; } assert(ph->ph->refcount == 1); /* Free the block that is currently there. */ free_mem(ABS2CLICK(ph->ph->phys), 1); /* Set the phys block to new addr and update pagetable. */ USE(ph->ph, ph->ph->phys = phaddr;); if(map_ph_writept(vmp, region, ph) != OK) { /* Presumably it was mapped, so there is no reason * updating should fail. */ panic("do_get_block: couldn't write pt"); } vaddr += VM_PAGE_SIZE; phaddr += VM_PAGE_SIZE; } /* Forget about the yielded block and free the struct. */ freeyieldednode(yb, 0); return OK; } static int yieldblock(struct vmproc *vmp, u64_t id, vir_bytes vaddr, yielded_t **retyb, int pages) { yielded_t *newyb; vir_bytes mem_clicks, v, p, new_phaddr; struct vir_region *region; struct phys_region *ph = NULL, *prev_ph = NULL, *first_ph = NULL; yielded_avl *avl; block_id_t blockid; /* Makes no sense if yielded block ID already exists, and * is likely a serious bug in the caller. */ blockid.id = id; blockid.owner = vmp->vm_endpoint; avl = get_yielded_avl(blockid); if(yielded_search(avl, blockid, AVL_EQUAL)) { printf("!"); return EINVAL; } if((vaddr % VM_PAGE_SIZE) || pages < 1) return EFAULT; v = vaddr; for(p = 0; p < pages; p++) { if(!(region = map_lookup(vmp, v, &ph)) || !ph) { printf("VM: do_yield_block: not found for %d\n", vmp->vm_endpoint); return EINVAL; } if(!(region->flags & VR_ANON)) { printf("VM: yieldblock: non-anon 0x%lx\n", v); return EFAULT; } if(ph->ph->refcount != 1) { printf("VM: do_yield_block: mapped not once for %d\n", vmp->vm_endpoint); return EFAULT; } if(prev_ph) { if(ph->ph->phys != prev_ph->ph->phys + VM_PAGE_SIZE) { printf("VM: physically discontiguous yield\n"); return EINVAL; } } prev_ph = ph; if(!first_ph) first_ph = ph; v += VM_PAGE_SIZE; } /* Make a new block to record the yielding in. */ if(!SLABALLOC(newyb)) { return ENOMEM; } assert(!(ph->ph->phys % VM_PAGE_SIZE)); if((mem_clicks = alloc_mem(pages, PAF_CLEAR)) == NO_MEM) { SLABFREE(newyb); return ENOMEM; } /* Update yielded block info. */ USE(newyb, newyb->id = blockid; newyb->physaddr = first_ph->ph->phys; newyb->pages = pages; newyb->younger = NULL;); new_phaddr = CLICK2ABS(mem_clicks); /* Set new phys block to new addr and update pagetable. */ v = vaddr; for(p = 0; p < pages; p++) { region = map_lookup(vmp, v, &ph); assert(region && ph); assert(ph->ph->refcount == 1); USE(ph->ph, ph->ph->phys = new_phaddr;); if(map_ph_writept(vmp, region, ph) != OK) { /* Presumably it was mapped, so there is no reason * updating should fail. */ panic("yield_block: couldn't write pt"); } v += VM_PAGE_SIZE; new_phaddr += VM_PAGE_SIZE; } /* Remember yielded block. */ yielded_insert(avl, newyb); vmp->vm_yielded++; /* Add to LRU list too. It's the youngest block. */ LRUCHECK; if(lru_youngest) { USE(lru_youngest, lru_youngest->younger = newyb;); } else { lru_oldest = newyb; } USE(newyb, newyb->older = lru_youngest;); lru_youngest = newyb; LRUCHECK; if(retyb) *retyb = newyb; return OK; } /*===========================================================================* * do_forgetblocks * *===========================================================================*/ int do_forgetblocks(message *m) { int n; struct vmproc *vmp; endpoint_t caller = m->m_source; if(vm_isokendpt(caller, &n) != OK) panic("do_yield_block: message from strange source: %d", m->m_source); vmp = &vmproc[n]; free_yielded_proc(vmp); return OK; } /*===========================================================================* * do_forgetblock * *===========================================================================*/ int do_forgetblock(message *m) { int n; struct vmproc *vmp; endpoint_t caller = m->m_source; yielded_t *yb; u64_t id; block_id_t blockid; yielded_avl *avl; if(vm_isokendpt(caller, &n) != OK) panic("do_yield_block: message from strange source: %d", m->m_source); vmp = &vmproc[n]; id = make64(m->VMFB_IDLO, m->VMFB_IDHI); blockid.id = id; blockid.owner = vmp->vm_endpoint; avl = get_yielded_avl(blockid); if((yb = yielded_search(avl, blockid, AVL_EQUAL))) { freeyieldednode(yb, 1); } return OK; } /*===========================================================================* * do_yieldblockgetblock * *===========================================================================*/ int do_yieldblockgetblock(message *m) { u64_t yieldid, getid; int n; endpoint_t caller = m->m_source; struct vmproc *vmp; yielded_t *yb = NULL; int r = ESRCH; int pages; if(vm_isokendpt(caller, &n) != OK) panic("do_yieldblockgetblock: message from strange source: %d", m->m_source); vmp = &vmproc[n]; pages = m->VMYBGB_LEN / VM_PAGE_SIZE; if((m->VMYBGB_LEN % VM_PAGE_SIZE) || pages < 1) { static int printed; if(!printed) { printed = 1; printf("vm: non-page-aligned or short block length\n"); } return EFAULT; } yieldid = make64(m->VMYBGB_YIELDIDLO, m->VMYBGB_YIELDIDHI); getid = make64(m->VMYBGB_GETIDLO, m->VMYBGB_GETIDHI); if(cmp64(yieldid, VM_BLOCKID_NONE) != 0) { /* A block was given to yield. */ yieldblock(vmp, yieldid, (vir_bytes) m->VMYBGB_VADDR, &yb, pages); } if(cmp64(getid, VM_BLOCKID_NONE) != 0) { /* A block was given to get. */ r = getblock(vmp, getid, (vir_bytes) m->VMYBGB_VADDR, pages); } return r; } void map_setparent(struct vmproc *vmp) { region_iter iter; struct vir_region *vr; region_start_iter_least(&vmp->vm_regions_avl, &iter); while((vr = region_get_iter(&iter))) { USE(vr, vr->parent = vmp;); region_incr_iter(&iter); } } int physregions(struct vir_region *vr) { int n = 0; vir_bytes voffset; for(voffset = 0; voffset < vr->length; voffset += VM_PAGE_SIZE) { if(physblock_get(vr, voffset)) n++; } return n; }