2026-03-13 19:44:50 +00:00
|
|
|
#include <kernel/address-space.h>
|
|
|
|
|
#include <kernel/iovec.h>
|
|
|
|
|
#include <kernel/libc/stdio.h>
|
|
|
|
|
#include <kernel/object.h>
|
|
|
|
|
#include <kernel/panic.h>
|
|
|
|
|
#include <kernel/printk.h>
|
|
|
|
|
#include <kernel/util.h>
|
|
|
|
|
#include <kernel/vm-object.h>
|
|
|
|
|
#include <mango/status.h>
|
|
|
|
|
|
|
|
|
|
/*** STATIC DATA + MACROS *****************************************************/
|
|
|
|
|
|
|
|
|
|
#define ADDRESS_SPACE_CAST(p) \
|
|
|
|
|
OBJECT_C_CAST(struct address_space, s_base, &address_space_type, p)
|
|
|
|
|
#define INVALID_OFFSET ((off_t) - 1)
|
|
|
|
|
|
|
|
|
|
enum get_entry_flags {
|
|
|
|
|
GET_ENTRY_EXACT = 0,
|
|
|
|
|
GET_ENTRY_CLOSEST_LEFT,
|
|
|
|
|
GET_ENTRY_CLOSEST_RIGHT,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/* iterates over a range of mapped virtual memory in a region, and provides
|
|
|
|
|
* a moving buffer through which the memory can be accessed */
|
|
|
|
|
struct vm_iterator {
|
|
|
|
|
struct address_space *it_region;
|
|
|
|
|
struct vm_area *it_mapping;
|
|
|
|
|
virt_addr_t it_base;
|
|
|
|
|
vm_prot_t it_prot;
|
|
|
|
|
void *it_buf;
|
|
|
|
|
size_t it_max;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/* iterates over the areas in an address space */
|
|
|
|
|
struct area_iterator {
|
|
|
|
|
struct address_space *it_root;
|
|
|
|
|
struct vm_area *it_area;
|
|
|
|
|
virt_addr_t it_search_base, it_search_limit;
|
|
|
|
|
virt_addr_t it_base, it_limit;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
enum search_direction {
|
|
|
|
|
SEARCH_LEFT,
|
|
|
|
|
SEARCH_RIGHT,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
static kern_status_t address_space_object_destroy(struct object *obj);
|
|
|
|
|
|
|
|
|
|
static struct object_type address_space_type = {
|
|
|
|
|
.ob_name = "address-space",
|
|
|
|
|
.ob_size = sizeof(struct address_space),
|
|
|
|
|
.ob_header_offset = offsetof(struct address_space, s_base),
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
static struct vm_cache vm_area_cache = {
|
|
|
|
|
.c_name = "vm-area",
|
|
|
|
|
.c_obj_size = sizeof(struct vm_area),
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/*** INTERNAL UTILITY FUNCTION ************************************************/
|
|
|
|
|
|
|
|
|
|
/* this function must be called with `parent` locked */
|
|
|
|
|
static void put_entry(struct btree *tree, struct vm_area *child)
|
|
|
|
|
{
|
|
|
|
|
struct btree_node *cur = tree->b_root;
|
|
|
|
|
if (!cur) {
|
|
|
|
|
tree->b_root = &child->vma_node;
|
|
|
|
|
btree_insert_fixup(tree, &child->vma_node);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
while (cur) {
|
|
|
|
|
struct vm_area *cur_entry
|
|
|
|
|
= BTREE_CONTAINER(struct vm_area, vma_node, cur);
|
|
|
|
|
|
|
|
|
|
struct btree_node *next = NULL;
|
|
|
|
|
|
|
|
|
|
if (child->vma_limit < cur_entry->vma_base) {
|
|
|
|
|
next = btree_left(cur);
|
|
|
|
|
} else if (child->vma_base > cur_entry->vma_limit) {
|
|
|
|
|
next = btree_right(cur);
|
|
|
|
|
} else {
|
|
|
|
|
panic("tried to add an overlapping entry [%zx-%zx] to "
|
|
|
|
|
"vm-region (overlaps [%zx-%zx])",
|
|
|
|
|
child->vma_base,
|
|
|
|
|
child->vma_limit,
|
|
|
|
|
cur_entry->vma_base,
|
|
|
|
|
cur_entry->vma_limit);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (next) {
|
|
|
|
|
cur = next;
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (child->vma_limit < cur_entry->vma_base) {
|
|
|
|
|
btree_put_left(cur, &child->vma_node);
|
|
|
|
|
} else {
|
|
|
|
|
btree_put_right(cur, &child->vma_node);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
btree_insert_fixup(tree, &child->vma_node);
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static struct vm_area *get_entry(
|
|
|
|
|
struct address_space *region,
|
|
|
|
|
virt_addr_t address,
|
|
|
|
|
enum get_entry_flags flags)
|
|
|
|
|
{
|
|
|
|
|
/* `x` must be to the left of `y` */
|
|
|
|
|
#define LEFT_DIFF(x, y) ((y) ? ((y)->vma_base - (x)) : ((size_t)-1))
|
|
|
|
|
/* `x` must be to the right of `y` */
|
|
|
|
|
#define RIGHT_DIFF(x, y) ((y) ? ((y)->vma_limit - (x)) : ((size_t)-1))
|
|
|
|
|
|
|
|
|
|
struct btree_node *cur = region->s_mappings.b_root;
|
|
|
|
|
if (!cur) {
|
|
|
|
|
return NULL;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
struct vm_area *result = NULL;
|
|
|
|
|
struct vm_area *closest_left = NULL;
|
|
|
|
|
struct vm_area *closest_right = NULL;
|
|
|
|
|
|
|
|
|
|
while (cur) {
|
|
|
|
|
struct vm_area *child
|
|
|
|
|
= BTREE_CONTAINER(struct vm_area, vma_node, cur);
|
|
|
|
|
|
|
|
|
|
struct btree_node *next = NULL;
|
|
|
|
|
|
|
|
|
|
if (address < child->vma_base) {
|
|
|
|
|
next = btree_left(cur);
|
|
|
|
|
if (LEFT_DIFF(address, child)
|
|
|
|
|
< LEFT_DIFF(address, closest_left)) {
|
|
|
|
|
closest_left = child;
|
|
|
|
|
}
|
|
|
|
|
} else if (address > child->vma_limit) {
|
|
|
|
|
next = btree_right(cur);
|
|
|
|
|
if (RIGHT_DIFF(address, child)
|
|
|
|
|
< RIGHT_DIFF(address, closest_right)) {
|
|
|
|
|
closest_right = child;
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
result = child;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
cur = next;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (result) {
|
|
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (flags & GET_ENTRY_CLOSEST_LEFT) {
|
|
|
|
|
return closest_left;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (flags & GET_ENTRY_CLOSEST_RIGHT) {
|
|
|
|
|
return closest_right;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return NULL;
|
|
|
|
|
#undef LEFT_DIFF
|
|
|
|
|
#undef RIGHT_DIFF
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* does not consider reserved areas! */
|
|
|
|
|
static bool is_area_free(
|
|
|
|
|
const struct address_space *space,
|
|
|
|
|
virt_addr_t base,
|
|
|
|
|
size_t len)
|
|
|
|
|
{
|
|
|
|
|
virt_addr_t limit = base + len - 1;
|
|
|
|
|
|
|
|
|
|
if (base < space->s_base_address) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (base + limit > space->s_limit_address) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
struct btree_node *cur = space->s_mappings.b_root;
|
|
|
|
|
if (!cur) {
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
while (cur) {
|
|
|
|
|
struct vm_area *cur_area
|
|
|
|
|
= BTREE_CONTAINER(struct vm_area, vma_node, cur);
|
|
|
|
|
|
|
|
|
|
if (base >= cur_area->vma_base && base <= cur_area->vma_limit) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (limit >= cur_area->vma_base
|
|
|
|
|
&& limit <= cur_area->vma_limit) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-14 22:39:14 +00:00
|
|
|
if (base < cur_area->vma_base && limit > cur_area->vma_limit) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-13 19:44:50 +00:00
|
|
|
if (base > cur_area->vma_limit) {
|
|
|
|
|
cur = btree_right(cur);
|
|
|
|
|
} else if (limit < cur_area->vma_base) {
|
|
|
|
|
cur = btree_left(cur);
|
|
|
|
|
} else {
|
|
|
|
|
/* what */
|
2026-03-14 22:39:14 +00:00
|
|
|
panic("unhandled case in is_area_free. base=%zx, "
|
|
|
|
|
"len=%zx, "
|
|
|
|
|
"limit=%zx, cur_area=[%zx-%zx]",
|
|
|
|
|
base,
|
|
|
|
|
len,
|
|
|
|
|
limit,
|
|
|
|
|
cur_area->vma_base,
|
|
|
|
|
cur_area->vma_limit);
|
2026-03-13 19:44:50 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* ONLY considers reserved areas! */
|
|
|
|
|
static bool is_area_reserved(
|
|
|
|
|
const struct address_space *space,
|
|
|
|
|
virt_addr_t base,
|
|
|
|
|
size_t len)
|
|
|
|
|
{
|
|
|
|
|
virt_addr_t limit = base + len - 1;
|
|
|
|
|
|
|
|
|
|
if (base < space->s_base_address) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (base + limit > space->s_limit_address) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
struct btree_node *cur = space->s_reserved.b_root;
|
|
|
|
|
if (!cur) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
while (cur) {
|
|
|
|
|
struct vm_area *cur_area
|
|
|
|
|
= BTREE_CONTAINER(struct vm_area, vma_node, cur);
|
|
|
|
|
|
|
|
|
|
if (base >= cur_area->vma_base && base <= cur_area->vma_limit) {
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (limit >= cur_area->vma_base
|
|
|
|
|
&& limit <= cur_area->vma_limit) {
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-14 22:39:14 +00:00
|
|
|
if (base < cur_area->vma_base && limit > cur_area->vma_limit) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-13 19:44:50 +00:00
|
|
|
if (base > cur_area->vma_limit) {
|
|
|
|
|
cur = btree_right(cur);
|
|
|
|
|
} else if (limit < cur_area->vma_base) {
|
|
|
|
|
cur = btree_left(cur);
|
|
|
|
|
} else {
|
|
|
|
|
/* what */
|
2026-03-14 22:39:14 +00:00
|
|
|
panic("unhandled case in is_area_reserved. base=%zx, "
|
|
|
|
|
"len=%zx, "
|
|
|
|
|
"limit=%zx, cur_area=[%zx-%zx]",
|
|
|
|
|
base,
|
|
|
|
|
len,
|
|
|
|
|
limit,
|
|
|
|
|
cur_area->vma_base,
|
|
|
|
|
cur_area->vma_limit);
|
2026-03-13 19:44:50 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static virt_addr_t generate_address(
|
|
|
|
|
virt_addr_t lower_bound,
|
|
|
|
|
virt_addr_t upper_bound)
|
|
|
|
|
{
|
|
|
|
|
virt_addr_t result = 0;
|
|
|
|
|
fill_random(&result, sizeof result);
|
|
|
|
|
|
|
|
|
|
virt_addr_t mask = upper_bound;
|
|
|
|
|
|
|
|
|
|
result += lower_bound;
|
|
|
|
|
result &= mask;
|
|
|
|
|
result &= ~VM_PAGE_MASK;
|
|
|
|
|
|
|
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static virt_addr_t find_free_area(
|
|
|
|
|
struct address_space *space,
|
|
|
|
|
size_t target_length)
|
|
|
|
|
{
|
|
|
|
|
virt_addr_t search_limit = space->s_base_address << 16;
|
|
|
|
|
|
|
|
|
|
int attempt = 0;
|
|
|
|
|
while (1) {
|
|
|
|
|
virt_addr_t base = generate_address(
|
|
|
|
|
space->s_base_address,
|
|
|
|
|
search_limit - 1);
|
|
|
|
|
bool ok = true;
|
|
|
|
|
|
|
|
|
|
if (is_area_reserved(space, base, target_length)) {
|
|
|
|
|
ok = false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (ok && !is_area_free(space, base, target_length)) {
|
|
|
|
|
ok = false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (ok) {
|
|
|
|
|
return base;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
attempt++;
|
|
|
|
|
if (attempt >= 3) {
|
|
|
|
|
search_limit <<= 4;
|
|
|
|
|
attempt = 0;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* this function should be called with `region` locked */
|
|
|
|
|
static void vm_iterator_begin(
|
|
|
|
|
struct vm_iterator *it,
|
|
|
|
|
struct address_space *region,
|
|
|
|
|
virt_addr_t base,
|
|
|
|
|
vm_prot_t prot)
|
|
|
|
|
{
|
|
|
|
|
memset(it, 0x0, sizeof *it);
|
|
|
|
|
it->it_base = base;
|
|
|
|
|
it->it_region = region;
|
|
|
|
|
it->it_prot = prot;
|
|
|
|
|
|
|
|
|
|
it->it_mapping = get_entry(region, base, GET_ENTRY_EXACT);
|
|
|
|
|
if (!it->it_mapping) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ((it->it_mapping->vma_prot & prot) != prot) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
off_t object_offset = base - it->it_mapping->vma_base
|
|
|
|
|
+ it->it_mapping->vma_object_offset;
|
|
|
|
|
struct vm_page *pg = NULL;
|
2026-03-14 22:39:14 +00:00
|
|
|
enum vm_object_flags flags = 0;
|
2026-03-13 19:44:50 +00:00
|
|
|
if (prot & VM_PROT_WRITE) {
|
2026-03-14 22:39:14 +00:00
|
|
|
flags |= VMO_ALLOCATE_MISSING_PAGE;
|
2026-03-13 19:44:50 +00:00
|
|
|
}
|
|
|
|
|
|
2026-03-14 22:39:14 +00:00
|
|
|
pg = vm_object_get_page(
|
|
|
|
|
it->it_mapping->vma_object,
|
|
|
|
|
object_offset,
|
|
|
|
|
flags,
|
|
|
|
|
NULL);
|
|
|
|
|
|
2026-03-13 19:44:50 +00:00
|
|
|
if (!pg) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void *buffer_base = vm_page_get_vaddr(pg);
|
|
|
|
|
phys_addr_t pg_addr = vm_page_get_paddr(pg);
|
|
|
|
|
size_t buffer_size = vm_page_get_size_bytes(pg);
|
|
|
|
|
|
|
|
|
|
while (1) {
|
|
|
|
|
struct btree_node *next_node = btree_next(&pg->p_bnode);
|
|
|
|
|
struct vm_page *next
|
|
|
|
|
= BTREE_CONTAINER(struct vm_page, p_bnode, next_node);
|
|
|
|
|
if (!next) {
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
phys_addr_t next_addr = vm_page_get_paddr(next);
|
|
|
|
|
if (pg_addr + vm_page_get_size_bytes(pg) != next_addr) {
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pg = next;
|
|
|
|
|
pg_addr = next_addr;
|
|
|
|
|
buffer_size += vm_page_get_size_bytes(next);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
it->it_buf = (char *)buffer_base + (object_offset & VM_PAGE_MASK);
|
|
|
|
|
it->it_max = buffer_size - (object_offset & VM_PAGE_MASK);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static kern_status_t vm_iterator_seek(struct vm_iterator *it, size_t nr_bytes)
|
|
|
|
|
{
|
|
|
|
|
if (nr_bytes < it->it_max) {
|
|
|
|
|
it->it_base += nr_bytes;
|
|
|
|
|
it->it_buf = (char *)it->it_buf + nr_bytes;
|
|
|
|
|
it->it_max -= nr_bytes;
|
|
|
|
|
return KERN_OK;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
it->it_base += nr_bytes;
|
|
|
|
|
|
|
|
|
|
struct vm_area *next_mapping
|
|
|
|
|
= get_entry(it->it_region, it->it_base, GET_ENTRY_EXACT);
|
|
|
|
|
if (!next_mapping) {
|
|
|
|
|
it->it_buf = NULL;
|
|
|
|
|
it->it_max = 0;
|
|
|
|
|
return KERN_MEMORY_FAULT;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ((next_mapping->vma_prot & it->it_prot) != it->it_prot) {
|
|
|
|
|
it->it_buf = NULL;
|
|
|
|
|
it->it_max = 0;
|
|
|
|
|
return KERN_MEMORY_FAULT;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
off_t object_offset = it->it_base - it->it_mapping->vma_base
|
|
|
|
|
+ it->it_mapping->vma_object_offset;
|
|
|
|
|
|
|
|
|
|
struct vm_page *pg = NULL;
|
2026-03-14 22:39:14 +00:00
|
|
|
enum vm_object_flags flags = 0;
|
2026-03-13 19:44:50 +00:00
|
|
|
if (it->it_prot & VM_PROT_WRITE) {
|
2026-03-14 22:39:14 +00:00
|
|
|
flags |= VMO_ALLOCATE_MISSING_PAGE;
|
2026-03-13 19:44:50 +00:00
|
|
|
}
|
|
|
|
|
|
2026-03-14 22:39:14 +00:00
|
|
|
pg = vm_object_get_page(
|
|
|
|
|
it->it_mapping->vma_object,
|
|
|
|
|
object_offset,
|
|
|
|
|
flags,
|
|
|
|
|
NULL);
|
|
|
|
|
|
2026-03-13 19:44:50 +00:00
|
|
|
if (!pg) {
|
|
|
|
|
return KERN_NO_MEMORY;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void *buffer_base = vm_page_get_vaddr(pg);
|
|
|
|
|
phys_addr_t pg_addr = vm_page_get_paddr(pg);
|
|
|
|
|
size_t buffer_size = vm_page_get_size_bytes(pg);
|
|
|
|
|
|
|
|
|
|
while (1) {
|
|
|
|
|
struct btree_node *next_node = btree_next(&pg->p_bnode);
|
|
|
|
|
struct vm_page *next
|
|
|
|
|
= BTREE_CONTAINER(struct vm_page, p_bnode, next_node);
|
|
|
|
|
if (!next) {
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
phys_addr_t next_addr = vm_page_get_paddr(next);
|
|
|
|
|
if (pg_addr + vm_page_get_size_bytes(pg) != next_addr) {
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pg = next;
|
|
|
|
|
pg_addr = next_addr;
|
|
|
|
|
buffer_size += vm_page_get_size_bytes(next);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
it->it_buf = (char *)buffer_base + (object_offset & VM_PAGE_MASK);
|
|
|
|
|
it->it_max = buffer_size;
|
|
|
|
|
return KERN_OK;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void vm_iterator_finish(struct vm_iterator *it)
|
|
|
|
|
{
|
|
|
|
|
memset(it, 0x0, sizeof *it);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* this function must be called with `space` locked */
|
|
|
|
|
static void area_iterator_begin(
|
|
|
|
|
struct area_iterator *it,
|
|
|
|
|
struct address_space *space,
|
|
|
|
|
virt_addr_t base,
|
|
|
|
|
virt_addr_t limit)
|
|
|
|
|
{
|
|
|
|
|
memset(it, 0x0, sizeof *it);
|
|
|
|
|
|
|
|
|
|
struct vm_area *area = get_entry(space, base, GET_ENTRY_CLOSEST_RIGHT);
|
|
|
|
|
if (!area) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (area->vma_base > limit) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
it->it_search_base = base;
|
|
|
|
|
it->it_search_limit = limit;
|
|
|
|
|
it->it_root = space;
|
|
|
|
|
it->it_area = area;
|
|
|
|
|
it->it_base = area->vma_base;
|
|
|
|
|
it->it_limit = area->vma_base;
|
|
|
|
|
|
|
|
|
|
if (it->it_base < base) {
|
|
|
|
|
it->it_base = base;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (it->it_limit > limit) {
|
|
|
|
|
it->it_limit = limit;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void area_iterator_finish(struct area_iterator *it)
|
|
|
|
|
{
|
|
|
|
|
memset(it, 0x0, sizeof *it);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static kern_status_t area_iterator_move_next(struct area_iterator *it)
|
|
|
|
|
{
|
|
|
|
|
if (!it->it_root || !it->it_area) {
|
|
|
|
|
return KERN_NO_ENTRY;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
struct btree_node *next = btree_next(&it->it_area->vma_node);
|
|
|
|
|
if (!next) {
|
|
|
|
|
goto end;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
struct vm_area *area = BTREE_CONTAINER(struct vm_area, vma_node, next);
|
|
|
|
|
if (!area) {
|
|
|
|
|
goto end;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (area->vma_base > it->it_search_limit) {
|
|
|
|
|
goto end;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
it->it_area = area;
|
|
|
|
|
it->it_base = area->vma_base;
|
|
|
|
|
it->it_limit = area->vma_base;
|
|
|
|
|
|
|
|
|
|
if (it->it_base < it->it_search_base) {
|
|
|
|
|
it->it_base = it->it_search_base;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (it->it_limit > it->it_search_limit) {
|
|
|
|
|
it->it_limit = it->it_search_limit;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return KERN_OK;
|
|
|
|
|
|
|
|
|
|
end:
|
|
|
|
|
memset(it, 0x0, sizeof *it);
|
|
|
|
|
return KERN_NO_ENTRY;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void area_iterator_erase(struct area_iterator *it)
|
|
|
|
|
{
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/*** PUBLIC API ***************************************************************/
|
|
|
|
|
|
|
|
|
|
kern_status_t address_space_type_init(void)
|
|
|
|
|
{
|
|
|
|
|
vm_cache_init(&vm_area_cache);
|
|
|
|
|
return object_type_register(&address_space_type);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
struct address_space *address_space_cast(struct object *obj)
|
|
|
|
|
{
|
|
|
|
|
return ADDRESS_SPACE_CAST(obj);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* this function should be called with `parent` locked (if parent is
|
|
|
|
|
* non-NULL)
|
|
|
|
|
*/
|
|
|
|
|
kern_status_t address_space_create(
|
|
|
|
|
virt_addr_t base,
|
|
|
|
|
virt_addr_t limit,
|
|
|
|
|
struct address_space **out)
|
|
|
|
|
{
|
|
|
|
|
if (!base || !limit || limit <= base) {
|
|
|
|
|
return KERN_INVALID_ARGUMENT;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ((base & VM_PAGE_MASK) || ((limit + 1) & VM_PAGE_MASK)) {
|
|
|
|
|
return KERN_INVALID_ARGUMENT;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
struct object *region_object = object_create(&address_space_type);
|
|
|
|
|
if (!region_object) {
|
|
|
|
|
return KERN_NO_MEMORY;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
struct address_space *space = ADDRESS_SPACE_CAST(region_object);
|
|
|
|
|
|
|
|
|
|
space->s_base_address = base;
|
|
|
|
|
space->s_limit_address = limit;
|
|
|
|
|
#ifdef TRACE
|
|
|
|
|
tracek("creating address space at [%llx-%llx]", base, limit);
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
*out = space;
|
|
|
|
|
return KERN_OK;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
kern_status_t address_space_map(
|
|
|
|
|
struct address_space *root,
|
|
|
|
|
virt_addr_t map_address,
|
|
|
|
|
struct vm_object *object,
|
|
|
|
|
off_t object_offset,
|
|
|
|
|
size_t length,
|
|
|
|
|
vm_prot_t prot,
|
|
|
|
|
virt_addr_t *out)
|
|
|
|
|
{
|
|
|
|
|
if (object_offset & VM_PAGE_MASK) {
|
|
|
|
|
object_offset &= ~VM_PAGE_MASK;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (length & VM_PAGE_MASK) {
|
|
|
|
|
length &= ~VM_PAGE_MASK;
|
|
|
|
|
length += VM_PAGE_SIZE;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (map_address != MAP_ADDRESS_ANY && (map_address & VM_PAGE_MASK)) {
|
|
|
|
|
map_address &= ~VM_PAGE_MASK;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
tracek("address_space_map(%zx, %zx)", map_address, length);
|
|
|
|
|
|
|
|
|
|
if (!root || !object) {
|
|
|
|
|
tracek("null pointer");
|
|
|
|
|
return KERN_INVALID_ARGUMENT;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ((prot & object->vo_prot) != prot) {
|
|
|
|
|
tracek("protection error");
|
|
|
|
|
return KERN_INVALID_ARGUMENT;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!length || object_offset + length > object->vo_size) {
|
|
|
|
|
tracek("length exceeds object bounds");
|
|
|
|
|
return KERN_INVALID_ARGUMENT;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (map_address == MAP_ADDRESS_ANY) {
|
|
|
|
|
map_address = find_free_area(root, length);
|
|
|
|
|
|
|
|
|
|
if (map_address == MAP_ADDRESS_INVALID) {
|
|
|
|
|
tracek("no virtual memory available");
|
|
|
|
|
return KERN_NO_MEMORY;
|
|
|
|
|
}
|
|
|
|
|
} else if (!is_area_free(root, map_address, length)) {
|
|
|
|
|
tracek("area already in use");
|
|
|
|
|
return KERN_INVALID_ARGUMENT;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
struct vm_area *area = vm_cache_alloc(&vm_area_cache, VM_NORMAL);
|
|
|
|
|
if (!area) {
|
|
|
|
|
return KERN_NO_MEMORY;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
object_ref(&object->vo_base);
|
2026-03-15 14:40:24 +00:00
|
|
|
area->vma_space = root;
|
2026-03-13 19:44:50 +00:00
|
|
|
area->vma_object = object;
|
|
|
|
|
area->vma_prot = prot;
|
|
|
|
|
area->vma_object_offset = object_offset;
|
|
|
|
|
area->vma_base = map_address;
|
|
|
|
|
area->vma_limit = map_address + length - 1;
|
|
|
|
|
|
|
|
|
|
#ifdef TRACE
|
|
|
|
|
tracek("mapping %s at [%llx-%llx]",
|
|
|
|
|
object->vo_name,
|
|
|
|
|
area->vma_base,
|
|
|
|
|
area->vma_base + length);
|
|
|
|
|
#endif
|
|
|
|
|
put_entry(&root->s_mappings, area);
|
|
|
|
|
|
|
|
|
|
unsigned long lock_flags;
|
|
|
|
|
vm_object_lock_irqsave(object, &lock_flags);
|
|
|
|
|
queue_push_back(&object->vo_mappings, &area->vma_object_entry);
|
|
|
|
|
vm_object_unlock_irqrestore(object, lock_flags);
|
|
|
|
|
|
|
|
|
|
if (out) {
|
|
|
|
|
*out = map_address;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return KERN_OK;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* unmap some pages in the middle of an area, splitting it into two
|
|
|
|
|
* separate mappings */
|
|
|
|
|
static kern_status_t split_area(
|
|
|
|
|
struct vm_area *mapping,
|
|
|
|
|
struct address_space *root,
|
|
|
|
|
virt_addr_t unmap_base,
|
|
|
|
|
virt_addr_t unmap_limit)
|
|
|
|
|
{
|
|
|
|
|
struct vm_area *left = mapping;
|
|
|
|
|
struct vm_area *right = vm_cache_alloc(&vm_area_cache, VM_NORMAL);
|
|
|
|
|
if (!right) {
|
|
|
|
|
return KERN_NO_MEMORY;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
virt_addr_t left_base = mapping->vma_base;
|
|
|
|
|
virt_addr_t right_base = unmap_limit;
|
|
|
|
|
off_t left_object_offset = mapping->vma_object_offset;
|
|
|
|
|
size_t left_length = unmap_base - mapping->vma_base;
|
|
|
|
|
size_t right_length = mapping->vma_limit - unmap_limit;
|
|
|
|
|
off_t right_object_offset = mapping->vma_limit - right_length;
|
|
|
|
|
|
|
|
|
|
tracek("mapping=[%zx-%zx]->[%zx-%zx]",
|
|
|
|
|
mapping->vma_base,
|
|
|
|
|
mapping->vma_limit,
|
|
|
|
|
mapping->vma_object_offset,
|
|
|
|
|
mapping->vma_object_offset
|
|
|
|
|
+ (mapping->vma_limit - mapping->vma_base));
|
|
|
|
|
tracek("left=[%zx-%zx]->[%zx-%zx], right=[%zx-%zx]->[%zx-%zx]",
|
|
|
|
|
left_base,
|
|
|
|
|
left_base + left_length,
|
|
|
|
|
left_object_offset,
|
|
|
|
|
left_object_offset + left_length,
|
|
|
|
|
right_base,
|
|
|
|
|
right_base + right_length,
|
|
|
|
|
right_object_offset,
|
|
|
|
|
right_object_offset + right_length);
|
|
|
|
|
|
|
|
|
|
left->vma_object_offset = left_object_offset;
|
|
|
|
|
left->vma_base = left_base;
|
|
|
|
|
left->vma_limit = left_base + left_length - 1;
|
|
|
|
|
|
2026-03-15 14:40:24 +00:00
|
|
|
right->vma_space = left->vma_space;
|
2026-03-13 19:44:50 +00:00
|
|
|
right->vma_object = left->vma_object;
|
|
|
|
|
right->vma_prot = left->vma_prot;
|
|
|
|
|
right->vma_object_offset = right_object_offset;
|
|
|
|
|
right->vma_base = right_base;
|
|
|
|
|
right->vma_limit = right_base + right_length - 1;
|
|
|
|
|
|
|
|
|
|
if (!mapping->vma_object) {
|
|
|
|
|
put_entry(&root->s_reserved, right);
|
|
|
|
|
/* just a reservation, no page tables to update */
|
|
|
|
|
return KERN_OK;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
put_entry(&root->s_mappings, right);
|
|
|
|
|
for (size_t i = unmap_base; i < unmap_limit; i += VM_PAGE_SIZE) {
|
|
|
|
|
tracek("unmapping %zx", i);
|
|
|
|
|
pmap_remove(root->s_pmap, i);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return KERN_OK;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* unmap some pages from the left-side of a mapping to somewhere in the
|
|
|
|
|
* middle. */
|
|
|
|
|
static kern_status_t left_reduce_area(
|
|
|
|
|
struct vm_area *mapping,
|
|
|
|
|
struct address_space *root,
|
|
|
|
|
virt_addr_t unmap_base,
|
|
|
|
|
virt_addr_t unmap_limit)
|
|
|
|
|
{
|
|
|
|
|
tracek("left reduce mapping [%zx-%zx] subtract [%zx-%zx]",
|
|
|
|
|
mapping->vma_base,
|
|
|
|
|
mapping->vma_limit,
|
|
|
|
|
unmap_base,
|
|
|
|
|
unmap_limit);
|
|
|
|
|
|
|
|
|
|
virt_addr_t base = mapping->vma_base;
|
|
|
|
|
virt_addr_t limit = unmap_limit;
|
|
|
|
|
size_t length = limit - base + 1;
|
|
|
|
|
|
|
|
|
|
mapping->vma_base += length;
|
|
|
|
|
mapping->vma_object_offset += length;
|
|
|
|
|
|
|
|
|
|
if (!mapping->vma_object) {
|
|
|
|
|
/* just a reservation, no page tables to update */
|
|
|
|
|
tracek(" unreserving %zx-%zx (%zx bytes)",
|
|
|
|
|
base,
|
|
|
|
|
base + length,
|
|
|
|
|
length);
|
|
|
|
|
return KERN_OK;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
tracek(" unmapping %zx-%zx (%zx bytes)", base, base + length, length);
|
|
|
|
|
for (size_t i = base; i < limit; i += VM_PAGE_SIZE) {
|
|
|
|
|
pmap_remove(root->s_pmap, i);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return KERN_OK;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* unmap some pages from the middle of a mapping to the right-side. */
|
|
|
|
|
static kern_status_t right_reduce_area(
|
|
|
|
|
struct vm_area *mapping,
|
|
|
|
|
struct address_space *root,
|
|
|
|
|
virt_addr_t unmap_base,
|
|
|
|
|
virt_addr_t unmap_limit)
|
|
|
|
|
{
|
|
|
|
|
/* unmap_base falls somwwhere between mapping_offset and
|
|
|
|
|
* mapping_offset+length */
|
|
|
|
|
tracek("right reduce mapping [%zx-%zx] subtract [%zx-%zx]",
|
|
|
|
|
mapping->vma_base,
|
|
|
|
|
mapping->vma_limit,
|
|
|
|
|
unmap_base,
|
|
|
|
|
unmap_limit);
|
|
|
|
|
|
|
|
|
|
virt_addr_t base = unmap_base;
|
|
|
|
|
virt_addr_t limit = mapping->vma_limit;
|
|
|
|
|
size_t length = limit - base + 1;
|
|
|
|
|
mapping->vma_limit -= length;
|
|
|
|
|
|
|
|
|
|
if (!mapping->vma_object) {
|
|
|
|
|
/* just a reservation, no page tables to update */
|
|
|
|
|
tracek(" unreserving %zx-%zx (%zx bytes)",
|
|
|
|
|
base,
|
|
|
|
|
base + length,
|
|
|
|
|
length);
|
|
|
|
|
return KERN_OK;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
tracek(" unmapping %zx-%zx (%zx bytes)", base, base + length, length);
|
|
|
|
|
for (size_t i = base; i < limit; i += VM_PAGE_SIZE) {
|
|
|
|
|
pmap_remove(root->s_pmap, i);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return KERN_OK;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* completely unmap and delete an entire mapping */
|
|
|
|
|
static kern_status_t delete_area(
|
|
|
|
|
struct vm_area *mapping,
|
|
|
|
|
struct address_space *root)
|
|
|
|
|
{
|
|
|
|
|
if (!mapping->vma_object) {
|
|
|
|
|
/* just a reservation, no page tables to update */
|
|
|
|
|
return KERN_OK;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
tracek("delete mapping [%zx-%zx]",
|
|
|
|
|
mapping->vma_base,
|
|
|
|
|
mapping->vma_limit);
|
|
|
|
|
|
|
|
|
|
for (size_t i = mapping->vma_base; i < mapping->vma_limit;
|
|
|
|
|
i += VM_PAGE_SIZE) {
|
|
|
|
|
pmap_remove(root->s_pmap, i);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
struct vm_object *object = mapping->vma_object;
|
|
|
|
|
unsigned long flags;
|
|
|
|
|
vm_object_lock_irqsave(mapping->vma_object, &flags);
|
|
|
|
|
queue_delete(
|
|
|
|
|
&mapping->vma_object->vo_mappings,
|
|
|
|
|
&mapping->vma_object_entry);
|
|
|
|
|
mapping->vma_object = NULL;
|
|
|
|
|
vm_object_unlock_irqrestore(mapping->vma_object, flags);
|
|
|
|
|
object_unref(&object->vo_base);
|
|
|
|
|
|
|
|
|
|
/* don't actually delete the mapping yet. that will be done by
|
|
|
|
|
* address_space_unmap */
|
|
|
|
|
|
|
|
|
|
return KERN_OK;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
kern_status_t address_space_unmap(
|
|
|
|
|
struct address_space *region,
|
|
|
|
|
virt_addr_t unmap_base,
|
|
|
|
|
size_t unmap_length)
|
|
|
|
|
{
|
|
|
|
|
if (unmap_length == 0) {
|
|
|
|
|
return KERN_OK;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (unmap_base & VM_PAGE_MASK) {
|
|
|
|
|
unmap_base &= ~VM_PAGE_MASK;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (unmap_length & VM_PAGE_MASK) {
|
|
|
|
|
unmap_length &= VM_PAGE_MASK;
|
|
|
|
|
unmap_length += VM_PAGE_SIZE;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
kern_status_t status = KERN_OK;
|
|
|
|
|
struct area_iterator it;
|
|
|
|
|
virt_addr_t unmap_limit = unmap_base + unmap_length - 1;
|
|
|
|
|
tracek("unmapping %zx-%zx", unmap_base, unmap_limit);
|
|
|
|
|
|
|
|
|
|
area_iterator_begin(&it, region, unmap_base, unmap_limit);
|
|
|
|
|
while (it.it_area) {
|
|
|
|
|
struct vm_area *area = it.it_area;
|
|
|
|
|
virt_addr_t area_base = area->vma_base;
|
|
|
|
|
virt_addr_t area_limit = area->vma_limit;
|
|
|
|
|
|
|
|
|
|
bool split
|
|
|
|
|
= (area_base > unmap_base && area_limit < unmap_limit);
|
|
|
|
|
bool delete
|
|
|
|
|
= (area_base <= unmap_base
|
|
|
|
|
&& area_limit >= unmap_limit);
|
|
|
|
|
bool left_reduce
|
|
|
|
|
= (unmap_base <= area_base && unmap_limit < area_limit);
|
|
|
|
|
bool right_reduce
|
|
|
|
|
= (unmap_base > area_base && unmap_limit >= area_limit);
|
|
|
|
|
|
|
|
|
|
if (split) {
|
|
|
|
|
status = split_area(
|
|
|
|
|
area,
|
|
|
|
|
region,
|
|
|
|
|
unmap_base,
|
|
|
|
|
unmap_limit);
|
|
|
|
|
delete = true;
|
|
|
|
|
} else if (delete) {
|
|
|
|
|
status = delete_area(area, region);
|
|
|
|
|
} else if (left_reduce) {
|
|
|
|
|
status = left_reduce_area(
|
|
|
|
|
area,
|
|
|
|
|
region,
|
|
|
|
|
unmap_base,
|
|
|
|
|
unmap_limit);
|
|
|
|
|
} else if (right_reduce) {
|
|
|
|
|
status = right_reduce_area(
|
|
|
|
|
area,
|
|
|
|
|
region,
|
|
|
|
|
unmap_base,
|
|
|
|
|
unmap_limit);
|
|
|
|
|
} else {
|
|
|
|
|
panic("don't know what to do with this "
|
|
|
|
|
"mapping");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (delete) {
|
|
|
|
|
area_iterator_erase(&it);
|
|
|
|
|
} else {
|
|
|
|
|
area_iterator_move_next(&it);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (status != KERN_OK) {
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
area_iterator_finish(&it);
|
|
|
|
|
|
|
|
|
|
return status;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
kern_status_t address_space_reserve(
|
|
|
|
|
struct address_space *space,
|
|
|
|
|
virt_addr_t base,
|
|
|
|
|
size_t length,
|
|
|
|
|
virt_addr_t *out)
|
|
|
|
|
{
|
|
|
|
|
if (length & VM_PAGE_MASK) {
|
|
|
|
|
length &= ~VM_PAGE_MASK;
|
|
|
|
|
length += VM_PAGE_SIZE;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (base != MAP_ADDRESS_ANY && (base & VM_PAGE_MASK)) {
|
|
|
|
|
base &= ~VM_PAGE_MASK;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!space) {
|
|
|
|
|
return KERN_INVALID_ARGUMENT;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!length || base + length > space->s_limit_address) {
|
|
|
|
|
return KERN_INVALID_ARGUMENT;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (base == MAP_ADDRESS_ANY) {
|
|
|
|
|
base = find_free_area(space, length);
|
|
|
|
|
|
|
|
|
|
if (base == MAP_ADDRESS_INVALID) {
|
|
|
|
|
return KERN_NO_MEMORY;
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
if (is_area_reserved(space, base, length)) {
|
|
|
|
|
/* for now, don't figure out overlapping reservations */
|
|
|
|
|
return KERN_INVALID_ARGUMENT;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!is_area_free(space, base, length)) {
|
|
|
|
|
return KERN_INVALID_ARGUMENT;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
struct vm_area *area = vm_cache_alloc(&vm_area_cache, VM_NORMAL);
|
|
|
|
|
if (!area) {
|
|
|
|
|
return KERN_NO_MEMORY;
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-15 14:40:24 +00:00
|
|
|
area->vma_space = space;
|
2026-03-13 19:44:50 +00:00
|
|
|
area->vma_base = base;
|
|
|
|
|
area->vma_limit = base + length - 1;
|
|
|
|
|
|
|
|
|
|
#ifdef TRACE
|
|
|
|
|
tracek("reservation at [%llx-%llx]", area->vma_base, area->vma_limit);
|
|
|
|
|
#endif
|
|
|
|
|
put_entry(&space->s_reserved, area);
|
|
|
|
|
|
|
|
|
|
if (out) {
|
|
|
|
|
*out = base;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return KERN_OK;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
kern_status_t address_space_release(
|
|
|
|
|
struct address_space *space,
|
|
|
|
|
virt_addr_t release_base,
|
|
|
|
|
size_t release_length)
|
|
|
|
|
{
|
|
|
|
|
if ((release_base & VM_PAGE_MASK) || (release_length & VM_PAGE_MASK)) {
|
|
|
|
|
return KERN_INVALID_ARGUMENT;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
kern_status_t status = KERN_OK;
|
|
|
|
|
struct area_iterator it;
|
|
|
|
|
virt_addr_t release_limit = release_base + release_length - 1;
|
|
|
|
|
tracek("unreserving %zx-%zx", release_base, release_limit);
|
|
|
|
|
|
|
|
|
|
area_iterator_begin(&it, space, release_base, release_limit);
|
|
|
|
|
while (it.it_area) {
|
|
|
|
|
struct vm_area *area = it.it_area;
|
|
|
|
|
virt_addr_t area_base = area->vma_base;
|
|
|
|
|
virt_addr_t area_limit = area->vma_limit;
|
|
|
|
|
|
|
|
|
|
bool split
|
|
|
|
|
= (area_base > release_base
|
|
|
|
|
&& area_limit < release_limit);
|
|
|
|
|
bool delete
|
|
|
|
|
= (area_base <= release_base
|
|
|
|
|
&& area_limit >= release_limit);
|
|
|
|
|
bool left_reduce
|
|
|
|
|
= (release_base <= area_base
|
|
|
|
|
&& release_limit < area_limit);
|
|
|
|
|
bool right_reduce
|
|
|
|
|
= (release_base > area_base
|
|
|
|
|
&& release_limit >= area_limit);
|
|
|
|
|
|
|
|
|
|
if (split) {
|
|
|
|
|
status = split_area(
|
|
|
|
|
area,
|
|
|
|
|
space,
|
|
|
|
|
release_base,
|
|
|
|
|
release_limit);
|
|
|
|
|
delete = true;
|
|
|
|
|
} else if (delete) {
|
|
|
|
|
status = delete_area(area, space);
|
|
|
|
|
} else if (left_reduce) {
|
|
|
|
|
status = left_reduce_area(
|
|
|
|
|
area,
|
|
|
|
|
space,
|
|
|
|
|
release_base,
|
|
|
|
|
release_limit);
|
|
|
|
|
} else if (right_reduce) {
|
|
|
|
|
status = right_reduce_area(
|
|
|
|
|
area,
|
|
|
|
|
space,
|
|
|
|
|
release_base,
|
|
|
|
|
release_limit);
|
|
|
|
|
} else {
|
|
|
|
|
panic("don't know what to do with this "
|
|
|
|
|
"mapping");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (delete) {
|
|
|
|
|
area_iterator_erase(&it);
|
|
|
|
|
} else {
|
|
|
|
|
area_iterator_move_next(&it);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (status != KERN_OK) {
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
area_iterator_finish(&it);
|
|
|
|
|
|
|
|
|
|
return status;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool address_space_validate_access(
|
|
|
|
|
struct address_space *region,
|
|
|
|
|
virt_addr_t ptr,
|
|
|
|
|
size_t len,
|
|
|
|
|
vm_prot_t prot)
|
|
|
|
|
{
|
|
|
|
|
if (len == 0) {
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (ptr < region->s_base_address) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (ptr + len > region->s_limit_address) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
virt_addr_t base = ptr & ~VM_PAGE_MASK;
|
|
|
|
|
virt_addr_t limit = (ptr + len) - 1;
|
|
|
|
|
if ((limit + 1) & VM_PAGE_MASK) {
|
|
|
|
|
limit &= ~VM_PAGE_MASK;
|
|
|
|
|
limit += VM_PAGE_SIZE;
|
|
|
|
|
limit -= 1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* TODO improve this to not require a per-page loop */
|
|
|
|
|
for (virt_addr_t i = base; i < limit;) {
|
|
|
|
|
struct vm_area *area = get_entry(region, i, GET_ENTRY_EXACT);
|
|
|
|
|
if (!area) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ((area->vma_prot & prot) != prot) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
i = area->vma_limit;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-14 22:39:14 +00:00
|
|
|
static kern_status_t request_missing_page(
|
|
|
|
|
struct address_space *region,
|
|
|
|
|
virt_addr_t addr,
|
|
|
|
|
off_t object_offset,
|
|
|
|
|
struct vm_object *object,
|
|
|
|
|
vm_prot_t prot,
|
|
|
|
|
enum pmap_fault_flags flags,
|
|
|
|
|
unsigned long *irq_flags)
|
|
|
|
|
{
|
|
|
|
|
/* here:
|
|
|
|
|
* `region` is locked.
|
|
|
|
|
* `object` is unlocked.
|
|
|
|
|
* `irq_flags` must be restored when `region` is unlocked.
|
|
|
|
|
* the relevant page in `object` may or may not be committed.
|
|
|
|
|
* if it isn't, it needs to be requested.
|
|
|
|
|
*/
|
|
|
|
|
vm_object_lock(object);
|
|
|
|
|
address_space_unlock(region);
|
|
|
|
|
|
|
|
|
|
struct vm_page *pg = vm_object_get_page(
|
|
|
|
|
object,
|
|
|
|
|
object_offset,
|
|
|
|
|
VMO_ALLOCATE_MISSING_PAGE | VMO_REQUEST_MISSING_PAGE,
|
|
|
|
|
irq_flags);
|
|
|
|
|
if (!pg) {
|
|
|
|
|
vm_object_unlock_irqrestore(object, *irq_flags);
|
2026-03-15 14:40:24 +00:00
|
|
|
printk("page request for %zx failed", addr);
|
2026-03-14 22:39:14 +00:00
|
|
|
return KERN_FATAL_ERROR;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* now: `region` is unlocked, and `object` is locked */
|
|
|
|
|
|
|
|
|
|
kern_status_t status = pmap_add(
|
|
|
|
|
region->s_pmap,
|
|
|
|
|
addr,
|
|
|
|
|
vm_page_get_pfn(pg),
|
|
|
|
|
prot,
|
|
|
|
|
PMAP_NORMAL);
|
|
|
|
|
|
|
|
|
|
vm_object_unlock_irqrestore(object, *irq_flags);
|
|
|
|
|
return status;
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-13 19:44:50 +00:00
|
|
|
/* this function must be called with `region` locked */
|
|
|
|
|
kern_status_t address_space_demand_map(
|
|
|
|
|
struct address_space *region,
|
|
|
|
|
virt_addr_t addr,
|
|
|
|
|
enum pmap_fault_flags flags)
|
|
|
|
|
{
|
|
|
|
|
addr &= ~VM_PAGE_MASK;
|
|
|
|
|
if (addr < region->s_base_address || addr > region->s_limit_address) {
|
|
|
|
|
return KERN_NO_ENTRY;
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-14 22:39:14 +00:00
|
|
|
unsigned long irq_flags;
|
|
|
|
|
address_space_lock_irqsave(region, &irq_flags);
|
|
|
|
|
|
2026-03-13 19:44:50 +00:00
|
|
|
struct vm_area *area = get_entry(region, addr, GET_ENTRY_EXACT);
|
2026-03-14 22:39:14 +00:00
|
|
|
if (!area || !area->vma_object) {
|
|
|
|
|
address_space_unlock_irqrestore(region, irq_flags);
|
2026-03-13 19:44:50 +00:00
|
|
|
return KERN_NO_ENTRY;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
off_t object_offset = addr - area->vma_base + area->vma_object_offset;
|
2026-03-14 22:39:14 +00:00
|
|
|
if (area->vma_object->vo_ctrl) {
|
|
|
|
|
return request_missing_page(
|
|
|
|
|
region,
|
|
|
|
|
addr,
|
|
|
|
|
object_offset,
|
|
|
|
|
area->vma_object,
|
|
|
|
|
area->vma_prot,
|
|
|
|
|
flags,
|
|
|
|
|
&irq_flags);
|
|
|
|
|
}
|
2026-03-13 19:44:50 +00:00
|
|
|
|
|
|
|
|
#if 0
|
|
|
|
|
tracek("vm: tried to access vm-object %s at offset=%05llx",
|
|
|
|
|
area->vma_object->vo_name,
|
|
|
|
|
object_offset);
|
|
|
|
|
#endif
|
|
|
|
|
|
2026-03-14 22:39:14 +00:00
|
|
|
/* simple case: this vm-object is not attached to a controller */
|
|
|
|
|
vm_object_lock(area->vma_object);
|
|
|
|
|
struct vm_page *pg = vm_object_get_page(
|
2026-03-13 19:44:50 +00:00
|
|
|
area->vma_object,
|
|
|
|
|
object_offset,
|
2026-03-14 22:39:14 +00:00
|
|
|
VMO_ALLOCATE_MISSING_PAGE,
|
|
|
|
|
NULL);
|
2026-03-15 14:40:24 +00:00
|
|
|
// tracek("vm: mapping %07llx -> %10llx", vm_page_get_paddr(pg), addr);
|
|
|
|
|
|
|
|
|
|
if (!pg) {
|
|
|
|
|
return KERN_FATAL_ERROR;
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-14 22:39:14 +00:00
|
|
|
kern_status_t status = pmap_add(
|
2026-03-13 19:44:50 +00:00
|
|
|
region->s_pmap,
|
|
|
|
|
addr,
|
|
|
|
|
vm_page_get_pfn(pg),
|
|
|
|
|
area->vma_prot,
|
|
|
|
|
PMAP_NORMAL);
|
2026-03-14 22:39:14 +00:00
|
|
|
|
|
|
|
|
vm_object_unlock(area->vma_object);
|
|
|
|
|
address_space_unlock_irqrestore(region, irq_flags);
|
|
|
|
|
return status;
|
2026-03-13 19:44:50 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
virt_addr_t address_space_get_base_address(const struct address_space *region)
|
|
|
|
|
{
|
|
|
|
|
return region->s_base_address;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
kern_status_t address_space_read(
|
|
|
|
|
struct address_space *src_region,
|
|
|
|
|
virt_addr_t src_ptr,
|
|
|
|
|
size_t count,
|
|
|
|
|
void *destp,
|
|
|
|
|
size_t *nr_read)
|
|
|
|
|
{
|
|
|
|
|
struct vm_iterator src;
|
|
|
|
|
char *dest = destp;
|
|
|
|
|
|
|
|
|
|
vm_iterator_begin(
|
|
|
|
|
&src,
|
|
|
|
|
src_region,
|
|
|
|
|
src_ptr,
|
|
|
|
|
VM_PROT_READ | VM_PROT_USER);
|
|
|
|
|
|
|
|
|
|
kern_status_t status = KERN_OK;
|
|
|
|
|
size_t r = 0;
|
|
|
|
|
|
|
|
|
|
while (r < count && src.it_max) {
|
|
|
|
|
size_t remaining = count - r;
|
|
|
|
|
size_t to_move = MIN(src.it_max, remaining);
|
|
|
|
|
memmove(dest, src.it_buf, to_move);
|
|
|
|
|
|
|
|
|
|
status = vm_iterator_seek(&src, to_move);
|
|
|
|
|
if (status != KERN_OK) {
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
r += to_move;
|
|
|
|
|
dest += to_move;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
vm_iterator_finish(&src);
|
|
|
|
|
|
|
|
|
|
if (nr_read) {
|
|
|
|
|
*nr_read = r;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return status;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
kern_status_t address_space_write(
|
|
|
|
|
struct address_space *dst_region,
|
|
|
|
|
virt_addr_t dst_ptr,
|
|
|
|
|
size_t count,
|
|
|
|
|
const void *srcp,
|
|
|
|
|
size_t *nr_written)
|
|
|
|
|
{
|
|
|
|
|
struct vm_iterator dst;
|
|
|
|
|
const char *src = srcp;
|
|
|
|
|
|
|
|
|
|
vm_iterator_begin(
|
|
|
|
|
&dst,
|
|
|
|
|
dst_region,
|
|
|
|
|
dst_ptr,
|
|
|
|
|
VM_PROT_WRITE | VM_PROT_USER);
|
|
|
|
|
|
|
|
|
|
kern_status_t status = KERN_OK;
|
|
|
|
|
size_t r = 0;
|
|
|
|
|
|
|
|
|
|
while (r < count && dst.it_max) {
|
|
|
|
|
size_t remaining = count - r;
|
|
|
|
|
size_t to_move = MIN(dst.it_max, remaining);
|
|
|
|
|
memmove(dst.it_buf, src, to_move);
|
|
|
|
|
|
|
|
|
|
status = vm_iterator_seek(&dst, to_move);
|
|
|
|
|
if (status != KERN_OK) {
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
r += to_move;
|
|
|
|
|
src += to_move;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
vm_iterator_finish(&dst);
|
|
|
|
|
|
|
|
|
|
if (nr_written) {
|
|
|
|
|
*nr_written = r;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return status;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
kern_status_t address_space_memmove(
|
|
|
|
|
struct address_space *dest_region,
|
|
|
|
|
virt_addr_t dest_ptr,
|
|
|
|
|
struct address_space *src_region,
|
|
|
|
|
virt_addr_t src_ptr,
|
|
|
|
|
size_t count,
|
|
|
|
|
size_t *nr_moved)
|
|
|
|
|
{
|
|
|
|
|
struct vm_iterator src, dest;
|
|
|
|
|
vm_iterator_begin(
|
|
|
|
|
&src,
|
|
|
|
|
src_region,
|
|
|
|
|
src_ptr,
|
|
|
|
|
VM_PROT_READ | VM_PROT_USER);
|
|
|
|
|
vm_iterator_begin(
|
|
|
|
|
&dest,
|
|
|
|
|
dest_region,
|
|
|
|
|
dest_ptr,
|
|
|
|
|
VM_PROT_WRITE | VM_PROT_USER);
|
|
|
|
|
|
|
|
|
|
kern_status_t status = KERN_OK;
|
|
|
|
|
size_t r = 0;
|
|
|
|
|
|
|
|
|
|
while (count && src.it_max && dest.it_max) {
|
|
|
|
|
size_t to_move = MIN(MIN(src.it_max, dest.it_max), count);
|
|
|
|
|
memmove(dest.it_buf, src.it_buf, to_move);
|
|
|
|
|
|
|
|
|
|
status = vm_iterator_seek(&src, to_move);
|
|
|
|
|
if (status != KERN_OK) {
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
status = vm_iterator_seek(&dest, to_move);
|
|
|
|
|
if (status != KERN_OK) {
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
count -= to_move;
|
|
|
|
|
r += to_move;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
vm_iterator_finish(&src);
|
|
|
|
|
vm_iterator_finish(&dest);
|
|
|
|
|
|
|
|
|
|
if (nr_moved) {
|
|
|
|
|
*nr_moved = r;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return status;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
extern kern_status_t address_space_memmove_v(
|
|
|
|
|
struct address_space *dest_region,
|
|
|
|
|
size_t dest_offset,
|
|
|
|
|
const kern_iovec_t *dest_vecs,
|
|
|
|
|
size_t nr_dest_vecs,
|
|
|
|
|
struct address_space *src_region,
|
|
|
|
|
size_t src_offset,
|
|
|
|
|
const kern_iovec_t *src_vecs,
|
|
|
|
|
size_t nr_src_vecs,
|
|
|
|
|
size_t bytes_to_move,
|
|
|
|
|
size_t *nr_bytes_moved)
|
|
|
|
|
{
|
|
|
|
|
struct iovec_iterator src, dest;
|
|
|
|
|
iovec_iterator_begin_user(&src, src_region, src_vecs, nr_src_vecs);
|
|
|
|
|
iovec_iterator_begin_user(&dest, dest_region, dest_vecs, nr_dest_vecs);
|
|
|
|
|
|
|
|
|
|
iovec_iterator_seek(&src, src_offset);
|
|
|
|
|
iovec_iterator_seek(&dest, dest_offset);
|
|
|
|
|
|
|
|
|
|
size_t moved = 0;
|
|
|
|
|
while (bytes_to_move && src.it_len && dest.it_len) {
|
|
|
|
|
size_t to_move
|
|
|
|
|
= MIN(MIN(src.it_len, dest.it_len), bytes_to_move);
|
|
|
|
|
|
|
|
|
|
kern_status_t status = address_space_memmove(
|
|
|
|
|
dest_region,
|
|
|
|
|
dest.it_base,
|
|
|
|
|
src_region,
|
|
|
|
|
src.it_base,
|
|
|
|
|
to_move,
|
|
|
|
|
NULL);
|
|
|
|
|
if (status != KERN_OK) {
|
|
|
|
|
return status;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
iovec_iterator_seek(&src, to_move);
|
|
|
|
|
iovec_iterator_seek(&dest, to_move);
|
|
|
|
|
bytes_to_move -= to_move;
|
|
|
|
|
moved += to_move;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (nr_bytes_moved) {
|
|
|
|
|
*nr_bytes_moved = moved;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return KERN_OK;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#ifdef TRACE
|
|
|
|
|
void address_space_dump(struct address_space *region)
|
|
|
|
|
{
|
|
|
|
|
struct btree_node *cur = btree_first(®ion->s_mappings);
|
|
|
|
|
while (cur) {
|
|
|
|
|
struct vm_area *area
|
|
|
|
|
= BTREE_CONTAINER(struct vm_area, vma_node, cur);
|
|
|
|
|
|
|
|
|
|
tracek("+mapping [%zx-%zx] %s",
|
|
|
|
|
area->vma_base,
|
|
|
|
|
area->vma_limit,
|
|
|
|
|
area->vma_object->vo_name);
|
|
|
|
|
|
|
|
|
|
cur = btree_next(cur);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
#endif
|