version 1.1.1.1, 2008/06/03 10:38:46 |
version 1.1.1.1.2.1, 2008/08/13 17:12:32 |
|
|
*/ |
*/ |
|
|
/* |
/* |
* vm_nommu.c - virtual memory functions for no MMU systems |
* vm_nommu.c - virtual memory alloctor for no MMU systems |
*/ |
*/ |
|
|
/* |
/* |
|
|
#include <sched.h> |
#include <sched.h> |
#include <vm.h> |
#include <vm.h> |
|
|
#ifdef CONFIG_VMTRACE |
|
static void vm_error(const char *, int); |
|
#define LOG(x) printk x |
|
#define CHK(fn,x) do { if (x) vm_error(fn, x); } while (0) |
|
#else |
|
#define LOG(x) |
|
#define CHK(fn,x) |
|
#endif |
|
|
|
/* forward declarations */ |
/* forward declarations */ |
static struct region *region_create(struct region *, u_long, size_t); |
static struct region *region_create(struct region *, void *, size_t); |
static void region_delete(struct region *, struct region *); |
static void region_delete(struct region *, struct region *); |
static struct region *region_find(struct region *, u_long, size_t); |
static struct region *region_find(struct region *, void *, size_t); |
static void region_free(struct region *, struct region *); |
static void region_free(struct region *, struct region *); |
static void region_init(struct region *); |
static void region_init(struct region *); |
static int do_allocate(vm_map_t, void **, size_t, int); |
static int do_allocate(vm_map_t, void **, size_t, int); |
|
|
static int do_attribute(vm_map_t, void *, int); |
static int do_attribute(vm_map_t, void *, int); |
static int do_map(vm_map_t, void *, size_t, void **); |
static int do_map(vm_map_t, void *, size_t, void **); |
|
|
|
|
/* vm mapping for kernel task */ |
/* vm mapping for kernel task */ |
static struct vm_map kern_map; |
static struct vm_map kern_map; |
|
|
/** |
/** |
* vm_allocate - allocate zero-filled memory for specified address |
* vm_allocate - allocate zero-filled memory for specified address |
* @task: task id to allocate memory |
|
* @addr: required address. set an allocated address in return. |
|
* @size: allocation size |
|
* @anywhere: if it is true, the "addr" argument will be ignored. |
|
* In this case, the address of free space will be found |
|
* automatically. |
|
* |
* |
* The allocated area has writable, user-access attribute by default. |
* If "anywhere" argument is true, the "addr" argument will be |
* The "addr" and "size" argument will be adjusted to page boundary. |
* ignored. In this case, the address of free space will be |
|
* found automatically. |
|
* |
|
* The allocated area has writable, user-access attribute by |
|
* default. The "addr" and "size" argument will be adjusted |
|
* to page boundary. |
*/ |
*/ |
int |
int |
vm_allocate(task_t task, void **addr, size_t size, int anywhere) |
vm_allocate(task_t task, void **addr, size_t size, int anywhere) |
|
|
int err; |
int err; |
void *uaddr; |
void *uaddr; |
|
|
LOG(("vm_aloc: task=%s addr=%x size=%x anywhere=%d\n", |
|
task->name ? task->name : "no name", *addr, size, anywhere)); |
|
|
|
sched_lock(); |
sched_lock(); |
|
|
if (!task_valid(task)) { |
if (!task_valid(task)) { |
err = ESRCH; |
err = ESRCH; |
} else if (task != cur_task() && !task_capable(CAP_MEMORY)) { |
goto out; |
|
} |
|
if (task != cur_task() && !task_capable(CAP_MEMORY)) { |
err = EPERM; |
err = EPERM; |
} else if (umem_copyin(addr, &uaddr, sizeof(void *))) { |
goto out; |
|
} |
|
if (umem_copyin(addr, &uaddr, sizeof(void *))) { |
err = EFAULT; |
err = EFAULT; |
} else if (anywhere == 0 && !user_area(*addr)) { |
goto out; |
|
} |
|
if (anywhere == 0 && !user_area(*addr)) { |
err = EACCES; |
err = EACCES; |
} else { |
goto out; |
err = do_allocate(task->map, &uaddr, size, anywhere); |
|
if (err == 0) { |
|
if (umem_copyout(&uaddr, addr, sizeof(void *))) |
|
err = EFAULT; |
|
} |
|
} |
} |
|
|
|
err = do_allocate(task->map, &uaddr, size, anywhere); |
|
if (err == 0) { |
|
if (umem_copyout(&uaddr, addr, sizeof(void *))) |
|
err = EFAULT; |
|
} |
|
out: |
sched_unlock(); |
sched_unlock(); |
CHK("vm_allocate", err); |
|
return err; |
return err; |
} |
} |
|
|
|
|
do_allocate(vm_map_t map, void **addr, size_t size, int anywhere) |
do_allocate(vm_map_t map, void **addr, size_t size, int anywhere) |
{ |
{ |
struct region *reg; |
struct region *reg; |
u_long start, end; |
char *start, *end; |
|
|
if (size == 0) |
if (size == 0) |
return EINVAL; |
return EINVAL; |
|
|
*/ |
*/ |
if (anywhere) { |
if (anywhere) { |
size = (size_t)PAGE_ALIGN(size); |
size = (size_t)PAGE_ALIGN(size); |
if ((start = (u_long)page_alloc(size)) == 0) |
if ((start = page_alloc(size)) == 0) |
return ENOMEM; |
return ENOMEM; |
} else { |
} else { |
start = PAGE_TRUNC(*addr); |
start = (char *)PAGE_TRUNC(*addr); |
end = PAGE_ALIGN(start + size); |
end = (char *)PAGE_ALIGN(start + size); |
size = (size_t)(end - start); |
size = (size_t)(end - start); |
|
|
if (page_reserve((void *)start, size)) |
if (page_reserve(start, size)) |
return EINVAL; |
return EINVAL; |
} |
} |
reg = region_create(&map->head, start, size); |
reg = region_create(&map->head, start, size); |
if (reg == NULL) { |
if (reg == NULL) { |
page_free((void *)start, size); |
page_free(start, size); |
return ENOMEM; |
return ENOMEM; |
} |
} |
reg->flags = REG_READ | REG_WRITE; |
reg->flags = REG_READ | REG_WRITE; |
|
|
/* Zero fill */ |
/* Zero fill */ |
memset((void *)start, 0, size); |
memset(start, 0, size); |
*addr = (void *)reg->addr; |
*addr = reg->addr; |
return 0; |
return 0; |
} |
} |
|
|
|
|
* Deallocate memory region for specified address. |
* Deallocate memory region for specified address. |
* |
* |
* The "addr" argument points to a memory region previously |
* The "addr" argument points to a memory region previously |
* allocated through a call to vm_allocate() or vm_map(). The number |
* allocated through a call to vm_allocate() or vm_map(). The |
* of bytes freed is the number of bytes of the allocated region. |
* number of bytes freed is the number of bytes of the |
* If one of the region of previous and next are free, it combines |
* allocated region. If one of the region of previous and |
* with them, and larger free region is created. |
* next are free, it combines with them, and larger free |
|
* region is created. |
*/ |
*/ |
int |
int |
vm_free(task_t task, void *addr) |
vm_free(task_t task, void *addr) |
{ |
{ |
int err; |
int err; |
|
|
LOG(("vm_free: task=%s addr=%x\n", |
|
task->name ? task->name : "no name", addr)); |
|
|
|
sched_lock(); |
sched_lock(); |
if (!task_valid(task)) { |
if (!task_valid(task)) { |
err = ESRCH; |
err = ESRCH; |
} else if (task != cur_task() && !task_capable(CAP_MEMORY)) { |
goto out; |
|
} |
|
if (task != cur_task() && !task_capable(CAP_MEMORY)) { |
err = EPERM; |
err = EPERM; |
} else if (!user_area(addr)) { |
goto out; |
|
} |
|
if (!user_area(addr)) { |
err = EFAULT; |
err = EFAULT; |
} else { |
goto out; |
err = do_free(task->map, addr); |
|
} |
} |
|
|
|
err = do_free(task->map, addr); |
|
out: |
sched_unlock(); |
sched_unlock(); |
CHK("vm_free", err); |
|
return err; |
return err; |
} |
} |
|
|
|
|
/* |
/* |
* Find the target region. |
* Find the target region. |
*/ |
*/ |
reg = region_find(&map->head, (u_long)addr, 1); |
reg = region_find(&map->head, addr, 1); |
if (reg == NULL || reg->addr != (u_long)addr || |
if (reg == NULL || reg->addr != addr || (reg->flags & REG_FREE)) |
(reg->flags & REG_FREE)) |
|
return EINVAL; /* not allocated */ |
return EINVAL; /* not allocated */ |
|
|
/* |
/* |
* Free pages if it is not shared and mapped. |
* Free pages if it is not shared and mapped. |
*/ |
*/ |
if (!(reg->flags & REG_SHARED) && !(reg->flags & REG_MAPPED)) |
if (!(reg->flags & REG_SHARED) && !(reg->flags & REG_MAPPED)) |
page_free((void *)reg->addr, reg->size); |
page_free(reg->addr, reg->size); |
|
|
region_free(&map->head, reg); |
region_free(&map->head, reg); |
return 0; |
return 0; |
|
|
/* |
/* |
* Change attribute of specified virtual address. |
* Change attribute of specified virtual address. |
* |
* |
* The "addr" argument points to a memory region previously allocated |
* The "addr" argument points to a memory region previously |
* through a call to vm_allocate(). The attribute type can be chosen |
* allocated through a call to vm_allocate(). The attribute |
* a combination of VMA_READ, VMA_WRITE. |
* type can be chosen a combination of VMA_READ, VMA_WRITE. |
* Note: VMA_EXEC is not supported, yet. |
* Note: VMA_EXEC is not supported, yet. |
*/ |
*/ |
int |
int |
|
|
{ |
{ |
int err; |
int err; |
|
|
LOG(("vm_attr: task=%s addr=%x attr=%x\n", |
|
task->name ? task->name : "no name", addr, attr)); |
|
|
|
sched_lock(); |
sched_lock(); |
if (attr == 0 || attr & ~(VMA_READ | VMA_WRITE)) { |
if (attr == 0 || attr & ~(VMA_READ | VMA_WRITE)) { |
err = EINVAL; |
err = EINVAL; |
} else if (!task_valid(task)) { |
goto out; |
|
} |
|
if (!task_valid(task)) { |
err = ESRCH; |
err = ESRCH; |
} else if (task != cur_task() && !task_capable(CAP_MEMORY)) { |
goto out; |
|
} |
|
if (task != cur_task() && !task_capable(CAP_MEMORY)) { |
err = EPERM; |
err = EPERM; |
} else if (!user_area(addr)) { |
goto out; |
|
} |
|
if (!user_area(addr)) { |
err = EFAULT; |
err = EFAULT; |
} else { |
goto out; |
err = do_attribute(task->map, addr, attr); |
|
} |
} |
|
|
|
err = do_attribute(task->map, addr, attr); |
|
out: |
sched_unlock(); |
sched_unlock(); |
CHK("vm_attribute", err); |
|
return err; |
return err; |
} |
} |
|
|
|
|
/* |
/* |
* Find the target region. |
* Find the target region. |
*/ |
*/ |
reg = region_find(&map->head, (u_long)addr, 1); |
reg = region_find(&map->head, addr, 1); |
if (reg == NULL || reg->addr != (u_long)addr || |
if (reg == NULL || reg->addr != addr || (reg->flags & REG_FREE)) { |
(reg->flags & REG_FREE)) { |
|
return EINVAL; /* not allocated */ |
return EINVAL; /* not allocated */ |
} |
} |
/* |
/* |
|
|
|
|
/** |
/** |
* vm_map - map another task's memory to current task. |
* vm_map - map another task's memory to current task. |
* @target: memory owner |
|
* @addr: target address |
|
* @size: map size |
|
* @alloc: map address returned |
|
* |
* |
* Note: This routine does not support mapping to the specific address. |
* Note: This routine does not support mapping to the specific |
|
* address. |
*/ |
*/ |
int |
int |
vm_map(task_t target, void *addr, size_t size, void **alloc) |
vm_map(task_t target, void *addr, size_t size, void **alloc) |
{ |
{ |
int err; |
int err; |
|
|
LOG(("vm_map : task=%s addr=%x size=%x\n", |
|
target->name ? target->name : "no name", addr, size)); |
|
|
|
sched_lock(); |
sched_lock(); |
if (!task_valid(target)) { |
if (!task_valid(target)) { |
err = ESRCH; |
err = ESRCH; |
} else if (target == cur_task()) { |
goto out; |
|
} |
|
if (target == cur_task()) { |
err = EINVAL; |
err = EINVAL; |
} else if (!task_capable(CAP_MEMORY)) { |
goto out; |
|
} |
|
if (!task_capable(CAP_MEMORY)) { |
err = EPERM; |
err = EPERM; |
} else if (!user_area(addr)) { |
goto out; |
|
} |
|
if (!user_area(addr)) { |
err = EFAULT; |
err = EFAULT; |
} else { |
goto out; |
err = do_map(target->map, addr, size, alloc); |
|
} |
} |
|
|
|
err = do_map(target->map, addr, size, alloc); |
|
out: |
sched_unlock(); |
sched_unlock(); |
CHK("vm_map", err); |
|
return err; |
return err; |
} |
} |
|
|
|
|
do_map(vm_map_t map, void *addr, size_t size, void **alloc) |
do_map(vm_map_t map, void *addr, size_t size, void **alloc) |
{ |
{ |
vm_map_t curmap; |
vm_map_t curmap; |
u_long start, end; |
task_t self; |
|
char *start, *end; |
struct region *reg, *tgt; |
struct region *reg, *tgt; |
void *tmp; |
void *tmp; |
|
|
|
|
if (umem_copyout(&tmp, alloc, sizeof(void *))) |
if (umem_copyout(&tmp, alloc, sizeof(void *))) |
return EFAULT; |
return EFAULT; |
|
|
start = PAGE_TRUNC(addr); |
start = (char *)PAGE_TRUNC(addr); |
end = PAGE_ALIGN((u_long)addr + size); |
end = (char *)PAGE_ALIGN((char *)addr + size); |
size = (size_t)(end - start); |
size = (size_t)(end - start); |
|
|
/* |
/* |
|
|
/* |
/* |
* Create new region to map |
* Create new region to map |
*/ |
*/ |
curmap = cur_task()->map; |
self = cur_task(); |
|
curmap = self->map; |
reg = region_create(&curmap->head, start, size); |
reg = region_create(&curmap->head, start, size); |
if (reg == NULL) |
if (reg == NULL) |
return ENOMEM; |
return ENOMEM; |
|
|
if ((map = kmem_alloc(sizeof(struct vm_map))) == NULL) |
if ((map = kmem_alloc(sizeof(struct vm_map))) == NULL) |
return NULL; |
return NULL; |
|
|
map->ref_count = 1; |
map->refcnt = 1; |
region_init(&map->head); |
region_init(&map->head); |
return map; |
return map; |
} |
} |
|
|
{ |
{ |
struct region *reg, *tmp; |
struct region *reg, *tmp; |
|
|
if (--map->ref_count >= 1) |
if (--map->refcnt >= 1) |
return; |
return; |
|
|
sched_lock(); |
sched_lock(); |
|
|
/* Free region if it is not shared and mapped */ |
/* Free region if it is not shared and mapped */ |
if (!(reg->flags & REG_SHARED) && |
if (!(reg->flags & REG_SHARED) && |
!(reg->flags & REG_MAPPED)) { |
!(reg->flags & REG_MAPPED)) { |
page_free((void *)reg->addr, reg->size); |
page_free(reg->addr, reg->size); |
} |
} |
} |
} |
tmp = reg; |
tmp = reg; |
|
|
vm_reference(vm_map_t map) |
vm_reference(vm_map_t map) |
{ |
{ |
|
|
map->ref_count++; |
map->refcnt++; |
return 0; |
return 0; |
} |
} |
|
|
|
|
void * |
void * |
vm_translate(void *addr, size_t size) |
vm_translate(void *addr, size_t size) |
{ |
{ |
|
|
return addr; |
return addr; |
} |
} |
|
|
/* |
/* |
* Check if specified access can be allowed. |
|
* return 0 on success, or EFAULT on failure. |
|
*/ |
|
int |
|
vm_access(void *addr, size_t size, int type) |
|
{ |
|
u_long start, end; |
|
int err; |
|
char tmp; |
|
|
|
ASSERT(size); |
|
start = (u_long)addr; |
|
end = (u_long)addr + size - 1; |
|
if ((err = umem_copyin((void *)start, &tmp, 1))) |
|
return EFAULT; |
|
if (type == VMA_WRITE) { |
|
if ((err = umem_copyout(&tmp, (void *)start, 1))) |
|
return EFAULT; |
|
} |
|
if ((err = umem_copyin((void *)end, &tmp, 1))) |
|
return EFAULT; |
|
if (type == VMA_WRITE) { |
|
if ((err = umem_copyout(&tmp, (void *)end, 1))) |
|
return EFAULT; |
|
} |
|
return 0; |
|
} |
|
|
|
/* |
|
* Reserve specific area for boot tasks. |
* Reserve specific area for boot tasks. |
*/ |
*/ |
static int |
static int |
do_reserve(vm_map_t map, void **addr, size_t size) |
do_reserve(vm_map_t map, void **addr, size_t size) |
{ |
{ |
struct region *reg; |
struct region *reg; |
u_long start, end; |
char *start, *end; |
|
|
if (size == 0) |
if (size == 0) |
return EINVAL; |
return EINVAL; |
|
|
start = PAGE_TRUNC(*addr); |
start = (char *)PAGE_TRUNC(*addr); |
end = PAGE_ALIGN(start + size); |
end = (char *)PAGE_ALIGN(start + size); |
size = (size_t)(end - start); |
size = (size_t)(end - start); |
|
|
reg = region_create(&map->head, start, size); |
reg = region_create(&map->head, start, size); |
if (reg == NULL) |
if (reg == NULL) |
return ENOMEM; |
return ENOMEM; |
reg->flags = REG_READ | REG_WRITE; |
reg->flags = REG_READ | REG_WRITE; |
*addr = (void *)reg->addr; |
*addr = reg->addr; |
return 0; |
return 0; |
} |
} |
|
|
|
|
* the proper address by a boot loader. |
* the proper address by a boot loader. |
*/ |
*/ |
int |
int |
vm_load(vm_map_t map, struct module *m, void **stack) |
vm_load(vm_map_t map, struct module *mod, void **stack) |
{ |
{ |
void *base; |
void *base; |
size_t size; |
size_t size; |
|
|
printk("Loading task:\'%s\'\n", m->name); |
DPRINTF(("Loading task:\'%s\'\n", mod->name)); |
|
|
/* |
/* |
* Reserve text & data area |
* Reserve text & data area |
*/ |
*/ |
base = (void *)m->text; |
base = (void *)mod->text; |
size = m->textsz + m->datasz + m->bsssz; |
size = mod->textsz + mod->datasz + mod->bsssz; |
if (do_reserve(map, &base, size)) |
if (do_reserve(map, &base, size)) |
return -1; |
return -1; |
if (m->bsssz != 0) |
if (mod->bsssz != 0) |
memset((void *)(m->data + m->datasz), 0, m->bsssz); |
memset((void *)(mod->data + mod->datasz), 0, mod->bsssz); |
|
|
/* |
/* |
* Create stack |
* Create stack |
|
|
* Returns region on success, or NULL on failure. |
* Returns region on success, or NULL on failure. |
*/ |
*/ |
static struct region * |
static struct region * |
region_create(struct region *prev, u_long addr, size_t size) |
region_create(struct region *prev, void *addr, size_t size) |
{ |
{ |
struct region *reg; |
struct region *reg; |
|
|
|
|
* Find the region at the specified area. |
* Find the region at the specified area. |
*/ |
*/ |
static struct region * |
static struct region * |
region_find(struct region *head, u_long addr, size_t size) |
region_find(struct region *head, void *addr, size_t size) |
{ |
{ |
struct region *reg; |
struct region *reg; |
|
|
reg = head; |
reg = head; |
do { |
do { |
if (reg->addr <= addr && |
if (reg->addr <= addr && |
reg->addr + reg->size >= addr + size) { |
(char *)reg->addr + reg->size >= (char *)addr + size) { |
return reg; |
return reg; |
} |
} |
reg = reg->next; |
reg = reg->next; |
|
|
|
|
reg->next = reg->prev = reg; |
reg->next = reg->prev = reg; |
reg->sh_next = reg->sh_prev = reg; |
reg->sh_next = reg->sh_prev = reg; |
reg->addr = 0; |
reg->addr = NULL; |
reg->size = 0; |
reg->size = 0; |
reg->flags = REG_FREE; |
reg->flags = REG_FREE; |
} |
} |
|
|
#if defined(DEBUG) && defined(CONFIG_KDUMP) |
#ifdef DEBUG |
void |
static void |
vm_dump_one(task_t task) |
vm_dump_one(task_t task) |
{ |
{ |
vm_map_t map; |
vm_map_t map; |
struct region *reg; |
struct region *reg; |
char flags[6]; |
char flags[6]; |
u_long total = 0; |
size_t total = 0; |
|
|
printk("task=%x map=%x name=%s\n", task, task->map, |
printf("task=%x map=%x name=%s\n", task, task->map, |
task->name ? task->name : "no name"); |
task->name ? task->name : "no name"); |
printk(" region virtual size flags\n"); |
printf(" region virtual size flags\n"); |
printk(" -------- -------- -------- -----\n"); |
printf(" -------- -------- -------- -----\n"); |
|
|
map = task->map; |
map = task->map; |
reg = &map->head; |
reg = &map->head; |
|
|
if (reg->flags & REG_MAPPED) |
if (reg->flags & REG_MAPPED) |
flags[4] = 'M'; |
flags[4] = 'M'; |
|
|
printk(" %08x %08x %08x %s\n", reg, |
printf(" %08x %08x %08x %s\n", reg, |
reg->addr, reg->size, flags); |
reg->addr, reg->size, flags); |
if ((reg->flags & REG_MAPPED) == 0) |
if ((reg->flags & REG_MAPPED) == 0) |
total += reg->size; |
total += reg->size; |
} |
} |
reg = reg->next; |
reg = reg->next; |
} while (reg != &map->head); /* Process all regions */ |
} while (reg != &map->head); /* Process all regions */ |
printk(" *total=%dK bytes\n\n", total / 1024); |
printf(" *total=%dK bytes\n\n", total / 1024); |
} |
} |
|
|
void |
void |
|
|
list_t n; |
list_t n; |
task_t task; |
task_t task; |
|
|
printk("\nVM dump:\n"); |
printf("\nVM dump:\n"); |
n = &kern_task.link; |
n = list_first(&kern_task.link); |
do { |
while (n != &kern_task.link) { |
task = list_entry(n, struct task, link); |
task = list_entry(n, struct task, link); |
ASSERT(task_valid(task)); |
|
vm_dump_one(task); |
vm_dump_one(task); |
n = list_next(n); |
n = list_next(n); |
} while (n != &kern_task.link); |
} |
} |
|
#endif |
|
|
|
|
|
#ifdef CONFIG_VMTRACE |
|
static void |
|
vm_error(const char *func, int err) |
|
{ |
|
printk("Error!!: %s returns err=%x\n", func, err); |
|
} |
} |
#endif |
#endif |
|
|