blob: c8b8a16244b6cb9bdb1af05188a8743282939eb8 [file] [log] [blame]
/*
* Copyright 2014, NICTA
*
* This software may be distributed and modified according to the terms of
* the BSD 2-Clause license. Note that NO WARRANTY is provided.
* See "LICENSE_BSD2.txt" for details.
*
* @TAG(NICTA_BSD)
*/
/* see sel4utils/vspace.h for details */
#include <autoconf.h>
#if defined CONFIG_LIB_SEL4_VKA && defined CONFIG_LIB_SEL4_VSPACE
#include <stdlib.h>
#include <string.h>
#include <sel4utils/vspace.h>
#include <sel4utils/vspace_internal.h>
#include <utils/util.h>
/* see comment in vspace_internal for why these checks exist */
compile_time_assert(pages_are_4k1, BOTTOM_LEVEL_BITS_OFFSET == seL4_PageBits);
compile_time_assert(pages_are_4k2, BIT(BOTTOM_LEVEL_BITS_OFFSET) == PAGE_SIZE_4K);
static int
create_level(vspace_t *vspace, void* vaddr)
{
sel4utils_alloc_data_t *data = get_alloc_data(vspace);
/* We need a level in the bootstrapper vspace */
if (data->bootstrap == NULL) {
return bootstrap_create_level(vspace, vaddr);
}
/* Otherwise we allocate our level out of the bootstrapper vspace -
* which is where bookkeeping is mapped */
bottom_level_t *bottom_level = vspace_new_pages(data->bootstrap, seL4_AllRights, 1, seL4_PageBits);
if (bottom_level == NULL) {
return -1;
}
memset(bottom_level, 0, BIT(seL4_PageBits));
data->top_level[TOP_LEVEL_INDEX(vaddr)] = bottom_level;
return 0;
}
int
assure_level(vspace_t *vspace, void *vaddr)
{
sel4utils_alloc_data_t *data = get_alloc_data(vspace);
assert(data->top_level != NULL);
assert(data->top_level != (void *) RESERVED);
if (data->top_level[TOP_LEVEL_INDEX(vaddr)] == NULL) {
int error = create_level(vspace, vaddr);
if (error) {
return -1;
}
}
return 0;
}
static int
check_empty_range(bottom_level_t *top_level[], void *vaddr, size_t num_pages, size_t size_bits)
{
int result = 1;
int num_4k_pages = BYTES_TO_4K_PAGES(num_pages * (1 << size_bits));
for (int i = 0; i < num_4k_pages && result; i++) {
/* a range is empty if it does not have a mapped cap and it isn't reserved */
result = is_available(top_level, vaddr + (i * PAGE_SIZE_4K))
&& !is_reserved(top_level, vaddr + (i * PAGE_SIZE_4K));
}
return result;
}
/* check a range of pages is all reserved */
static int
check_reserved_range(bottom_level_t *top_level[], void *vaddr, size_t num_pages, size_t size_bits)
{
int result = 1;
int num_4k_pages = BYTES_TO_4K_PAGES(num_pages * (1 << size_bits));
for (int i = 0; i < num_4k_pages && result; i++) {
/* a range is empty if it does not have a mapped cap and it isn't reserved */
result = is_reserved(top_level, vaddr + (i * PAGE_SIZE_4K));
}
return result;
}
/* check that vaddr is actually in the reservation */
static int
check_reservation_bounds(reservation_t *reservation, void *vaddr, size_t num_pages, size_t size_bits)
{
return (vaddr >= reservation->start) &&
(vaddr + (num_pages * (1 << size_bits))) <= reservation->end;
}
static int
check_reservation(bottom_level_t *top_level[], reservation_t *reservation, void *vaddr,
size_t num_pages, size_t size_bits)
{
return check_reservation_bounds(reservation, vaddr, num_pages, size_bits) &
check_reserved_range(top_level, vaddr, num_pages, size_bits);
}
static void
perform_reservation(vspace_t *vspace, reservation_t *reservation, void *vaddr, size_t bytes,
seL4_CapRights rights, int cacheable)
{
reservation->start = (void *) (seL4_Word)ROUND_DOWN((uint32_t) ((seL4_Word)vaddr), PAGE_SIZE_4K);
reservation->end = (void *) (seL4_Word)ROUND_UP((uint32_t) ((seL4_Word)vaddr) + bytes, PAGE_SIZE_4K);
reservation->rights = rights;
reservation->cacheable = cacheable;
int error = seL4_NoError;
void *v = reservation->start;
for (v = reservation->start; v < reservation->end &&
error == seL4_NoError; v += PAGE_SIZE_4K) {
error = reserve(vspace, v);
}
/* return the amount we sucessfully reserved */
reservation->end = v;
}
int
sel4utils_map_page_pd(vspace_t *vspace, seL4_CPtr cap, void *vaddr, seL4_CapRights rights,
int cacheable, size_t size_bits)
{
vka_object_t pagetable = {0};
sel4utils_alloc_data_t *data = get_alloc_data(vspace);
int error = sel4utils_map_page(data->vka, vspace->page_directory, cap, vaddr,
rights, cacheable, &pagetable);
if (error) {
/* everything has gone to hell. Do no clean up. */
LOG_ERROR("Error mapping pages, bailing: %d", error);
return -1;
}
if (pagetable.cptr != 0) {
vspace_maybe_call_allocated_object(vspace, pagetable);
}
pagetable.cptr = 0;
return seL4_NoError;
}
#ifdef CONFIG_VTX
int
sel4utils_map_page_ept(vspace_t *vspace, seL4_CPtr cap, void *vaddr, seL4_CapRights rights,
int cacheable, size_t size_bits)
{
struct sel4utils_alloc_data *data = get_alloc_data(vspace);
vka_object_t pagetable = {0};
vka_object_t pagedir = {0};
int error = sel4utils_map_ept_page(data->vka, vspace->page_directory, cap,
(seL4_Word) vaddr, rights, cacheable, size_bits, &pagetable, &pagedir);
if (error) {
LOG_ERROR("Error mapping pages, bailing\n");
return -1;
}
if (pagetable.cptr != 0) {
vspace_maybe_call_allocated_object(vspace, pagetable);
pagetable.cptr = 0;
}
if (pagedir.cptr != 0) {
vspace_maybe_call_allocated_object(vspace, pagedir);
pagedir.cptr = 0;
}
return seL4_NoError;
}
#endif /* CONFIG_VTX */
static int
map_page(vspace_t *vspace, seL4_CPtr cap, void *vaddr, seL4_CapRights rights,
int cacheable, size_t size_bits)
{
sel4utils_alloc_data_t *data = get_alloc_data(vspace);
return data->map_page(vspace, cap, vaddr, rights, cacheable, size_bits);
}
static void *
find_range(sel4utils_alloc_data_t *data, size_t num_pages, size_t size_bits)
{
/* look for a contiguous range that is free.
* We use first-fit with the optimisation that we store
* a pointer to the last thing we freed/allocated */
int num_4k_pages = BYTES_TO_4K_PAGES(num_pages * (1 << size_bits));
void *current = data->last_allocated;
size_t contiguous = 0;
void *start = data->last_allocated;
while (contiguous < num_4k_pages) {
if (is_available(data->top_level, current) && IS_ALIGNED((uint32_t)((seL4_Word)start), size_bits)) {
contiguous++;
} else {
start = current + PAGE_SIZE_4K;
contiguous = 0;
}
current += PAGE_SIZE_4K;
if (current >= (void *) KERNEL_RESERVED_START) {
LOG_ERROR("Out of virtual memory");
return NULL;
}
}
data->last_allocated = current;
return start;
}
static int
alloc_pages(vspace_t *vspace, seL4_CPtr pages[], size_t num_pages, size_t size_bits)
{
sel4utils_alloc_data_t *data = get_alloc_data(vspace);
for (int i = 0; i < num_pages; i++) {
vka_object_t object;
if (vka_alloc_frame(data->vka, size_bits, &object) != 0) {
/* abort! */
LOG_ERROR("Failed to allocate page");
return -1;
}
vspace_maybe_call_allocated_object(vspace, object);
pages[i] = object.cptr;
}
return 0;
}
static int
map_pages_at_vaddr(vspace_t *vspace, seL4_CPtr caps[], void *vaddr, size_t num_pages,
size_t size_bits, seL4_CapRights rights, int cacheable)
{
int error = seL4_NoError;
for (int i = 0; i < num_pages && error == seL4_NoError; i++) {
assert(caps[i] != 0);
error = map_page(vspace, caps[i], vaddr, rights, cacheable, size_bits);
if (error == seL4_NoError) {
error = update_entries(vspace, vaddr, caps[i], size_bits);
vaddr += (1 << size_bits);
}
}
return error;
}
static int
new_pages_at_vaddr(vspace_t *vspace, void *vaddr, size_t num_pages, size_t size_bits,
seL4_CapRights rights, int cacheable)
{
seL4_CPtr pages[num_pages];
int error = alloc_pages(vspace, pages, num_pages, size_bits);
if (error == 0) {
error = map_pages_at_vaddr(vspace, pages, vaddr, num_pages, size_bits, rights, cacheable);
}
return error;
}
/* VSPACE INTERFACE FUNCTIONS */
int
sel4utils_map_pages_at_vaddr(vspace_t *vspace, seL4_CPtr caps[], void *vaddr,
size_t num_pages, size_t size_bits, reservation_t *reservation)
{
sel4utils_alloc_data_t *data = get_alloc_data(vspace);
if (!check_reservation(data->top_level, reservation, vaddr, num_pages, size_bits)) {
return -1;
}
return map_pages_at_vaddr(vspace, caps, vaddr, num_pages, size_bits,
reservation->rights, reservation->cacheable);
}
seL4_CPtr
sel4utils_get_cap(vspace_t *vspace, void *vaddr)
{
sel4utils_alloc_data_t *data = get_alloc_data(vspace);
seL4_CPtr entry = get_entry(data->top_level, vaddr);
if (entry == RESERVED) {
entry = 0;
}
return entry;
}
void *
sel4utils_map_pages(vspace_t *vspace, seL4_CPtr caps[], seL4_CapRights rights,
size_t num_pages, size_t size_bits, int cacheable)
{
struct sel4utils_alloc_data *data = get_alloc_data(vspace);
void *vaddr = find_range(data, num_pages, size_bits);
if (vaddr == NULL) {
return NULL;
}
int error = map_pages_at_vaddr(vspace, caps, vaddr, num_pages, size_bits, rights,
cacheable);
if (error == seL4_NoError) {
return vaddr;
} else {
return NULL;
}
}
int sel4utils_unmap_reserved_pages(vspace_t *vspace, void *vaddr, size_t num_pages, size_t size_bits, reservation_t *reservation) {
sel4utils_alloc_data_t *data = get_alloc_data(vspace);
if (!check_reservation_bounds(reservation, vaddr, num_pages, size_bits)) {
printf("check reservation failed vaddr %p\n", vaddr);
return -1;
}
for (int i = 0; i < num_pages; i++) {
seL4_CPtr cap = sel4utils_get_cap(vspace, vaddr);
if (cap != 0) {
int error = seL4_ARCH_Page_Unmap(get_entry(data->top_level, vaddr));
if (error != seL4_NoError) {
LOG_ERROR("Failed to unmap page at vaddr %p", vaddr);
}
/*update entires*/
update_entries(vspace, vaddr, RESERVED, size_bits);
}
vaddr += (1 << size_bits);
}
return 0;
}
void
sel4utils_unmap_pages(vspace_t *vspace, void *vaddr, size_t num_pages, size_t size_bits)
{
sel4utils_alloc_data_t *data = get_alloc_data(vspace);
for (int i = 0; i < num_pages; i++) {
seL4_CPtr cap = sel4utils_get_cap(vspace, vaddr);
if (cap != 0) {
int error = seL4_ARCH_Page_Unmap(get_entry(data->top_level, vaddr));
if (error != seL4_NoError) {
LOG_ERROR("Failed to unmap page at vaddr %p", vaddr);
}
}
vaddr += (1 << size_bits);
}
}
int
sel4utils_new_pages_at_vaddr(vspace_t *vspace, void *vaddr, size_t num_pages,
size_t size_bits, reservation_t *reservation)
{
struct sel4utils_alloc_data *data = get_alloc_data(vspace);
if (!check_reservation(data->top_level, reservation, vaddr, num_pages, size_bits)) {
LOG_ERROR("Range for vaddr %p with "DFMT" 4k pages not reserved!", vaddr, num_pages);
return -1;
}
return new_pages_at_vaddr(vspace, vaddr, num_pages, size_bits,
reservation->rights, reservation->cacheable);
}
void *
sel4utils_new_pages(vspace_t *vspace, seL4_CapRights rights, size_t num_pages,
size_t size_bits)
{
struct sel4utils_alloc_data *data = get_alloc_data(vspace);
void *vaddr = find_range(data, num_pages, size_bits);
if (vaddr == NULL) {
return NULL;
}
int error = new_pages_at_vaddr(vspace, vaddr, num_pages, size_bits, rights, 1);
if (error == seL4_NoError) {
return vaddr;
} else {
return NULL;
}
}
void
sel4utils_free_pages(vspace_t *vspace, void *vaddr, size_t num_pages,
size_t size_bits)
{
/* first unmap the pages */
sel4utils_unmap_pages(vspace, vaddr, num_pages, size_bits);
/* now mark them all as free */
for (int i = 0; i < num_pages; i++) {
clear_entries(vspace, vaddr, size_bits);
vaddr += PAGE_SIZE_4K;
}
}
void *
sel4utils_new_stack(vspace_t *vspace)
{
/* this implementation allocates stacks with small pages. */
int num_pages = BYTES_TO_4K_PAGES(CONFIG_SEL4UTILS_STACK_SIZE);
struct sel4utils_alloc_data *data = get_alloc_data(vspace);
void *vaddr = find_range(data, num_pages + 1, seL4_PageBits);
/* reserve the first page as the guard */
int error = reserve(vspace, vaddr);
if (error) {
return NULL;
}
void *stack_bottom = vaddr + PAGE_SIZE_4K;
error = new_pages_at_vaddr(vspace, stack_bottom, num_pages, seL4_PageBits,
seL4_AllRights, 1);
/* abort */
if (error != seL4_NoError) {
clear(vspace, vaddr);
return NULL;
}
/* return a pointer to the TOP of the stack */
return stack_bottom + CONFIG_SEL4UTILS_STACK_SIZE;
}
void
sel4utils_free_stack(vspace_t *vspace, void *stack_top)
{
int num_pages = BYTES_TO_4K_PAGES(CONFIG_SEL4UTILS_STACK_SIZE);
sel4utils_free_pages(vspace, stack_top - CONFIG_SEL4UTILS_STACK_SIZE,
num_pages, seL4_PageBits);
/* unreserve the guard page */
clear(vspace, stack_top - CONFIG_SEL4UTILS_STACK_SIZE - PAGE_SIZE_4K);
}
void *
sel4utils_new_ipc_buffer(vspace_t *vspace, seL4_CPtr *page)
{
void *vaddr = sel4utils_new_pages(vspace, seL4_AllRights, 1, seL4_PageBits);
if (vaddr == NULL) {
LOG_ERROR("Failed to create ipc buffer");
return NULL;
}
*page = sel4utils_get_cap(vspace, vaddr);
return vaddr;
}
void
sel4utils_free_ipc_buffer(vspace_t *vspace, void *vaddr)
{
sel4utils_free_pages(vspace, vaddr, 1, seL4_PageBits);
}
int sel4utils_reserve_range_no_alloc(vspace_t *vspace, reservation_t *reservation, size_t size,
seL4_CapRights rights, int cacheable, void **result)
{
sel4utils_alloc_data_t *data = get_alloc_data(vspace);
void *vaddr = find_range(data, BYTES_TO_4K_PAGES(size), seL4_PageBits);
if (vaddr == NULL) {
return -1;
}
*result = vaddr;
perform_reservation(vspace, reservation, vaddr, size, rights, cacheable);
return 0;
}
reservation_t *
sel4utils_reserve_range(vspace_t *vspace, size_t size, seL4_CapRights rights,
int cacheable, void **result)
{
reservation_t *reservation = malloc(sizeof(struct reservation));
if (reservation == NULL) {
LOG_ERROR("Malloc failed");
return NULL;
}
int error = sel4utils_reserve_range_no_alloc(vspace, reservation, size, rights, cacheable, result);
if (error) {
free(reservation);
return NULL;
}
return reservation;
}
int sel4utils_reserve_range_at_no_alloc(vspace_t *vspace, reservation_t *reservation, void *vaddr,
size_t size, seL4_CapRights rights, int cacheable)
{
sel4utils_alloc_data_t *data = get_alloc_data(vspace);
if (!check_empty_range(data->top_level, vaddr, BYTES_TO_4K_PAGES(size),
seL4_PageBits)) {
LOG_ERROR("Range not available at %p, size 0x"XFMT"", vaddr, size);
return -1;
}
perform_reservation(vspace, reservation, vaddr, size, rights, cacheable);
return 0;
}
reservation_t *
sel4utils_reserve_range_at(vspace_t *vspace, void *vaddr, size_t size, seL4_CapRights
rights, int cacheable)
{
reservation_t *reservation = malloc(sizeof(struct reservation));
if (reservation == NULL) {
LOG_ERROR("Malloc failed");
return NULL;
}
int error = sel4utils_reserve_range_at_no_alloc(vspace, reservation, vaddr, size, rights, cacheable);
if (error) {
free(reservation);
return NULL;
}
return reservation;
}
void sel4utils_free_reservation_no_alloc(vspace_t *vspace, reservation_t *reservation)
{
sel4utils_alloc_data_t *data = get_alloc_data(vspace);
for (void *current = reservation->start; current < reservation->end; current += PAGE_SIZE_4K) {
if (get_entry(data->top_level, current) == RESERVED) {
clear(vspace, current);
}
}
}
void
sel4utils_free_reservation(vspace_t *vspace, reservation_t *reservation)
{
sel4utils_free_reservation_no_alloc(vspace, reservation);
free(reservation);
}
#endif /* CONFIG_LIB_SEL4_VKA && CONFIG_LIB_SEL4_VSPACE */