Goto sanos source index
//
// vmm.c
//
// Virtual memory manager
//
// Copyright (C) 2002 Michael Ringgaard. All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions
// are met:
//
// 1. Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// 2. Redistributions in binary form must reproduce the above copyright
// notice, this list of conditions and the following disclaimer in the
// documentation and/or other materials provided with the distribution.
// 3. Neither the name of the project nor the names of its contributors
// may be used to endorse or promote products derived from this software
// without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
// OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
// HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
// LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
// OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
// SUCH DAMAGE.
//
#include <os/krnl.h>
#define VMAP_ENTRIES 1024
#define VMEM_START (64 * 1024)
struct rmap *vmap;
static int valid_range(void *addr, int size) {
int pages = PAGES(size);
if ((unsigned long) addr < VMEM_START) return 0;
if (KERNELSPACE((unsigned long) addr + pages * PAGESIZE)) return 0;
if (rmap_status(vmap, BTOP(addr), pages) != 1) return 0;
return 1;
}
static unsigned long pte_flags_from_protect(int protect) {
switch (protect) {
case PAGE_NOACCESS:
return 0;
case PAGE_READONLY:
case PAGE_EXECUTE:
case PAGE_EXECUTE_READ:
return PT_USER;
case PAGE_READWRITE:
case PAGE_EXECUTE_READWRITE:
return PT_USER | PT_WRITABLE;
case PAGE_READONLY | PAGE_GUARD:
case PAGE_EXECUTE | PAGE_GUARD:
case PAGE_EXECUTE_READ | PAGE_GUARD:
return PT_GUARD;
case PAGE_READWRITE | PAGE_GUARD:
case PAGE_EXECUTE_READWRITE | PAGE_GUARD:
return PT_GUARD | PT_WRITABLE;
}
return 0xFFFFFFFF;
}
static int free_filemap(struct filemap *fm) {
int rc;
hunprotect(fm->file);
rc = hfree(fm->file);
if (rc < 0) return rc;
rmap_free(vmap, BTOP(fm->addr), PAGES(fm->size));
hunprotect(fm->self);
rc = hfree(fm->self);
if (rc < 0) return rc;
return 0;
}
static int fetch_file_page(struct filemap *fm, void *addr) {
struct file *filp;
unsigned long pfn;
unsigned long pos;
int rc;
filp = (struct file *) olock(fm->file, OBJECT_FILE);
if (!filp) return -EBADF;
pfn = alloc_pageframe('FMAP');
if (pfn == 0xFFFFFFFF) {
orel(filp);
return -ENOMEM;
}
map_page(addr, pfn, PT_WRITABLE | PT_PRESENT);
pos = (char *) addr - fm->addr;
rc = pread(filp, addr, PAGESIZE, fm->offset + pos);
if (rc < 0) {
orel(filp);
unmap_page(addr);
free_pageframe(pfn);
return rc;
}
pfdb[pfn].owner = fm->self;
map_page(addr, pfn, fm->protect | PT_PRESENT);
orel(filp);
return 0;
}
static int save_file_page(struct filemap *fm, void *addr) {
struct file *filp;
unsigned long pos;
int rc;
filp = (struct file *) olock(fm->file, OBJECT_FILE);
if (!filp) return -EBADF;
pos = (char *) addr - fm->addr;
rc = pwrite(filp, addr, PAGESIZE, fm->offset + pos);
if (rc < 0) {
orel(filp);
return rc;
}
clear_dirty(addr);
orel(filp);
return 0;
}
void init_vmm() {
vmap = (struct rmap *) kmalloc(VMAP_ENTRIES * sizeof(struct rmap));
rmap_init(vmap, VMAP_ENTRIES);
rmap_free(vmap, BTOP(VMEM_START), BTOP(OSBASE - VMEM_START));
}
void *vmalloc(void *addr, unsigned long size, int type, int protect, unsigned long tag, int *rc) {
int pages = PAGES(size);
unsigned long flags = pte_flags_from_protect(protect);
int i;
if (rc) *rc = 0;
if (size == 0) {
if (rc) *rc = -EINVAL;
return NULL;
}
if ((type & MEM_COMMIT) != 0 && flags == 0xFFFFFFFF) {
if (rc) *rc = -EINVAL;
return NULL;
}
addr = (void *) PAGEADDR(addr);
if (!addr && (type & MEM_COMMIT) != 0) type |= MEM_RESERVE;
if (!tag) tag = 'VM';
if (type & MEM_RESERVE) {
if (addr == NULL) {
if (type & MEM_ALIGN64K) {
addr = (void *) PTOB(rmap_alloc_align(vmap, pages, 64 * 1024 / PAGESIZE));
} else {
addr = (void *) PTOB(rmap_alloc(vmap, pages));
}
if (addr == NULL) {
if (rc) *rc = -ENOMEM;
return NULL;
}
} else {
if (rmap_reserve(vmap, BTOP(addr), pages)) {
if (rc) *rc = -ENOMEM;
return NULL;
}
}
} else {
if (!valid_range(addr, size)) {
if (rc) *rc = -EFAULT;
return NULL;
}
}
if (type & MEM_COMMIT) {
char *vaddr;
unsigned long pfn;
vaddr = (char *) addr;
for (i = 0; i < pages; i++) {
if (page_mapped(vaddr)) {
set_page_flags(vaddr, flags | PT_PRESENT);
} else {
pfn = alloc_pageframe(tag);
if (pfn == 0xFFFFFFFF) {
if (rc) *rc = -ENOMEM;
return NULL;
}
map_page(vaddr, pfn, flags | PT_PRESENT);
memset(vaddr, 0, PAGESIZE);
}
vaddr += PAGESIZE;
}
}
return addr;
}
void *vmmap(void *addr, unsigned long size, int protect, struct file *filp, off64_t offset, int *rc) {
int pages = PAGES(size);
unsigned long flags = pte_flags_from_protect(protect);
struct filemap *fm;
int i;
char *vaddr;
if (rc) *rc = 0;
if (size == 0 || flags == 0xFFFFFFFF) {
if (rc) *rc = -EINVAL;
return NULL;
}
addr = (void *) PAGEADDR(addr);
if (addr == NULL) {
addr = (void *) PTOB(rmap_alloc(vmap, pages));
if (addr == NULL) {
if (rc) *rc = -ENOMEM;
return NULL;
}
} else {
if (rmap_reserve(vmap, BTOP(addr), pages)) {
if (rc) *rc = -ENOMEM;
return NULL;
}
}
fm = (struct filemap *) kmalloc(sizeof(struct filemap));
if (!fm) {
rmap_free(vmap, BTOP(addr), pages);
if (rc) *rc = -ENOMEM;
return NULL;
}
init_object(&fm->object, OBJECT_FILEMAP);
fm->self = halloc(&fm->object);
fm->file = halloc(&filp->iob.object);
if (fm->self < 0 || fm->file < 0) {
if (rc) *rc = -ENFILE;
return NULL;
}
hprotect(fm->self);
hprotect(fm->file);
fm->offset = offset;
fm->pages = pages;
fm->object.signaled = 1;
fm->addr = addr;
fm->size = size;
fm->protect = flags | PT_FILE;
vaddr = (char *) addr;
flags = (flags & ~PT_USER) | PT_FILE;
for (i = 0; i < pages; i++) {
map_page(vaddr, fm->self, flags);
vaddr += PAGESIZE;
}
return addr;
}
int vmsync(void *addr, unsigned long size) {
struct filemap *fm = NULL;
int pages = PAGES(size);
int i, rc;
char *vaddr;
if (size == 0) return 0;
addr = (void *) PAGEADDR(addr);
if (!valid_range(addr, size)) return -EINVAL;
vaddr = (char *) addr;
for (i = 0; i < pages; i++) {
if (page_directory_mapped(vaddr)) {
pte_t flags = get_page_flags(vaddr);
if ((flags & (PT_FILE | PT_PRESENT | PT_DIRTY)) == (PT_FILE | PT_PRESENT | PT_DIRTY)) {
unsigned long pfn = BTOP(virt2phys(vaddr));
struct filemap *newfm = (struct filemap *) hlookup(pfdb[pfn].owner);
if (newfm != fm) {
if (fm) {
rc = unlock_filemap(fm);
if (rc < 0) return rc;
}
fm = newfm;
rc = wait_for_object(fm, INFINITE);
if (rc < 0) return rc;
}
rc = save_file_page(fm, vaddr);
if (rc < 0) return rc;
}
}
vaddr += PAGESIZE;
}
if (fm) {
rc = unlock_filemap(fm);
if (rc < 0) return rc;
}
return 0;
}
int vmfree(void *addr, unsigned long size, int type) {
struct filemap *fm = NULL;
int pages = PAGES(size);
int i, rc;
char *vaddr;
if (size == 0) return 0;
addr = (void *) PAGEADDR(addr);
if (!valid_range(addr, size)) return -EINVAL;
if (type & (MEM_DECOMMIT | MEM_RELEASE)) {
vaddr = (char *) addr;
for (i = 0; i < pages; i++) {
if (page_directory_mapped(vaddr)) {
pte_t flags = get_page_flags(vaddr);
unsigned long pfn = BTOP(virt2phys(vaddr));
if (flags & PT_FILE) {
handle_t h = (flags & PT_PRESENT) ? pfdb[pfn].owner : pfn;
struct filemap *newfm = (struct filemap *) hlookup(h);
if (newfm != fm) {
if (fm) {
if (fm->pages == 0) {
rc = free_filemap(fm);
} else {
rc = unlock_filemap(fm);
}
if (rc < 0) return rc;
}
fm = newfm;
rc = wait_for_object(fm, INFINITE);
if (rc < 0) return rc;
}
fm->pages--;
unmap_page(vaddr);
if (flags & PT_PRESENT) free_pageframe(pfn);
} else if (flags & PT_PRESENT) {
unmap_page(vaddr);
free_pageframe(pfn);
}
}
vaddr += PAGESIZE;
}
}
if (fm) {
if (fm->pages == 0) {
rc = free_filemap(fm);
} else {
rc = unlock_filemap(fm);
}
if (rc < 0) return rc;
} else if (type & MEM_RELEASE) {
rmap_free(vmap, BTOP(addr), pages);
}
return 0;
}
void *vmrealloc(void *addr, unsigned long oldsize, unsigned long newsize, int type, int protect, unsigned long tag) {
return NULL;
}
int vmprotect(void *addr, unsigned long size, int protect) {
int pages = PAGES(size);
int i;
char *vaddr;
unsigned long flags;
if (size == 0) return 0;
addr = (void *) PAGEADDR(addr);
if (!valid_range(addr, size)) return -EINVAL;
flags = pte_flags_from_protect(protect);
if (flags == 0xFFFFFFFF) return -EINVAL;
vaddr = (char *) addr;
for (i = 0; i < pages; i++) {
if (page_mapped(vaddr)) {
set_page_flags(vaddr, (get_page_flags(vaddr) & ~PT_PROTECTMASK) | flags);
}
vaddr += PAGESIZE;
}
return 0;
}
int vmlock(void *addr, unsigned long size) {
return -ENOSYS;
}
int vmunlock(void *addr, unsigned long size) {
return -ENOSYS;
}
void *miomap(unsigned long addr, int size, int protect) {
char *vaddr;
int i;
unsigned long flags = pte_flags_from_protect(protect);
int pages = PAGES(size);
vaddr = (char *) PTOB(rmap_alloc(vmap, pages));
if (vaddr == NULL) return NULL;
for (i = 0; i < pages; i++) {
map_page(vaddr + PTOB(i), BTOP(addr) + i, flags | PT_PRESENT);
}
return vaddr;
}
void miounmap(void *addr, int size) {
int i;
int pages = PAGES(size);
for (i = 0; i < pages; i++) unmap_page((char *) addr + PTOB(i));
rmap_free(vmap, BTOP(addr), pages);
}
int guard_page_handler(void *addr) {
unsigned long pfn;
struct thread *t = self();
if (!t->tib) return -EFAULT;
if (addr < t->tib->stacklimit || addr >= t->tib->stacktop) return -EFAULT;
if (t->tib->stacklimit <= t->tib->stackbase) return -EFAULT;
pfn = alloc_pageframe('STK');
if (pfn == 0xFFFFFFFF) return -ENOMEM;
t->tib->stacklimit = (char *) t->tib->stacklimit - PAGESIZE;
map_page(t->tib->stacklimit, pfn, PT_GUARD | PT_WRITABLE | PT_PRESENT);
memset(t->tib->stacklimit, 0, PAGESIZE);
return 0;
}
int fetch_page(void *addr) {
struct filemap *fm;
int rc;
fm = (struct filemap *) olock(virt2pfn(addr), OBJECT_FILEMAP);
if (!fm) return -EBADF;
rc = wait_for_object(fm, INFINITE);
if (rc < 0) {
orel(fm);
return rc;
}
if (!page_mapped(addr)) {
rc = fetch_file_page(fm, addr);
if (rc < 0) {
unlock_filemap(fm);
orel(fm);
return rc;
}
}
rc = unlock_filemap(fm);
if (rc < 0) return rc;
orel(fm);
return 0;
}
int vmem_proc(struct proc_file *pf, void *arg) {
return list_memmap(pf, vmap, BTOP(VMEM_START));
}
int mem_sysinfo(struct meminfo *info) {
struct rmap *r;
struct rmap *rlim;
unsigned int free = 0;
rlim = &vmap[vmap->offset];
for (r = &vmap[1]; r <= rlim; r++) free += r->size;
info->physmem_total = totalmem * PAGESIZE;
info->physmem_avail = freemem * PAGESIZE;
info->virtmem_total = OSBASE - VMEM_START;
info->virtmem_avail = free * PAGESIZE;
info->pagesize = PAGESIZE;
return 0;
}