|  | /* | 
|  | * Copyright 2017, Data61 | 
|  | * Commonwealth Scientific and Industrial Research Organisation (CSIRO) | 
|  | * ABN 41 687 119 230. | 
|  | * | 
|  | * 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(DATA61_BSD) | 
|  | */ | 
|  |  | 
|  | #include <autoconf.h> | 
|  | #include <sel4utils/gen_config.h> | 
|  |  | 
|  | #include <stdlib.h> | 
|  | #include <string.h> | 
|  | #include <vka/vka.h> | 
|  | #include <vka/capops.h> | 
|  | #include <sel4utils/vspace.h> | 
|  | #include <sel4utils/vspace_internal.h> | 
|  |  | 
|  | /* For the initial vspace, we must always guarantee we have virtual memory available | 
|  | * for each bottom level page table. Future vspaces can then use the initial vspace | 
|  | * to allocate bottom level page tables until memory runs out. | 
|  | * We have 1 + k + k^2 + ... + k^n number of intermediate paging structures. Where | 
|  | * k = VSPACE_LEVEL_SIZE and n = (VSPACE_NUM_LEVELS - 2) | 
|  | * We want to calculate this using a geometric sumation. Fortunately we know that | 
|  | * VSPACE_LEVEL_SIZE = 2^VSPACE_LEVEL_BITS so when calculating k^n we can simplify to | 
|  | * (2^VSPACE_LEVEL_BITS)^n = (2^(VSPACE_LEVEL_BITS * n)) = 1 << (VSPACE_LEVEL_BITS * n) */ | 
|  | #define NUM_MID_LEVEL_STRUCTURES ( (1 - BIT(VSPACE_LEVEL_BITS * (VSPACE_NUM_LEVELS - 1))) / (1 - BIT(VSPACE_LEVEL_BITS))) | 
|  | /* Number of bottom level structures is just the next term in the previous geometric | 
|  | * series, i.e. k^(n + 1) */ | 
|  | #define NUM_BOTTOM_LEVEL_STRUCTURES (BIT(VSPACE_LEVEL_BITS * (VSPACE_NUM_LEVELS - 1))) | 
|  | /* We need to reserve a range of virtual memory such that we have somewhere to put all of | 
|  | * our tables */ | 
|  | #define MID_LEVEL_STRUCTURES_SIZE (NUM_MID_LEVEL_STRUCTURES * sizeof(vspace_mid_level_t)) | 
|  | #define BOTTOM_LEVEL_STRUCTURES_SIZE (NUM_BOTTOM_LEVEL_STRUCTURES * sizeof(vspace_bottom_level_t)) | 
|  | #define VSPACE_RESERVE_SIZE (MID_LEVEL_STRUCTURES_SIZE + BOTTOM_LEVEL_STRUCTURES_SIZE + sizeof(vspace_mid_level_t)) | 
|  | #define VSPACE_RESERVE_START (KERNEL_RESERVED_START - VSPACE_RESERVE_SIZE) | 
|  |  | 
|  | static int common_init(vspace_t *vspace, vka_t *vka, seL4_CPtr vspace_root, | 
|  | vspace_allocated_object_fn allocated_object_fn, void *cookie) | 
|  | { | 
|  | sel4utils_alloc_data_t *data = get_alloc_data(vspace); | 
|  | data->vka = vka; | 
|  | data->last_allocated = 0x10000000; | 
|  | data->reservation_head = NULL; | 
|  | data->is_empty = false; | 
|  |  | 
|  | data->vspace_root = vspace_root; | 
|  | vspace->allocated_object = allocated_object_fn; | 
|  | vspace->allocated_object_cookie = cookie; | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static void common_init_post_bootstrap(vspace_t *vspace, sel4utils_map_page_fn map_page) | 
|  | { | 
|  | sel4utils_alloc_data_t *data = get_alloc_data(vspace); | 
|  | /* reserve the kernel region, we do this by marking the | 
|  | * top level entry as RESERVED */ | 
|  | if (!data->is_empty) { | 
|  | for (int i = TOP_LEVEL_INDEX(KERNEL_RESERVED_START); | 
|  | i < VSPACE_LEVEL_SIZE; i++) { | 
|  | data->top_level->table[i] = RESERVED; | 
|  | } | 
|  | } | 
|  |  | 
|  | data->map_page = map_page; | 
|  |  | 
|  | /* initialise the rest of the functions now that they are usable */ | 
|  | vspace->new_pages = sel4utils_new_pages; | 
|  | vspace->map_pages = sel4utils_map_pages; | 
|  | vspace->new_pages_at_vaddr = sel4utils_new_pages_at_vaddr; | 
|  | vspace->map_pages_at_vaddr = sel4utils_map_pages_at_vaddr; | 
|  | vspace->deferred_rights_map_pages_at_vaddr = sel4utils_deferred_rights_map_pages_at_vaddr; | 
|  | vspace->unmap_pages = sel4utils_unmap_pages; | 
|  |  | 
|  | vspace->reserve_range_aligned = sel4utils_reserve_range_aligned; | 
|  | vspace->reserve_range_at = sel4utils_reserve_range_at; | 
|  | vspace->reserve_deferred_rights_range_at = sel4utils_reserve_deferred_rights_range_at; | 
|  | vspace->free_reservation = sel4utils_free_reservation; | 
|  | vspace->free_reservation_by_vaddr = sel4utils_free_reservation_by_vaddr; | 
|  |  | 
|  | vspace->get_cap = sel4utils_get_cap; | 
|  | vspace->get_cookie = sel4utils_get_cookie; | 
|  | vspace->get_root = sel4utils_get_root; | 
|  |  | 
|  | vspace->tear_down = sel4utils_tear_down; | 
|  | vspace->share_mem_at_vaddr = sel4utils_share_mem_at_vaddr; | 
|  | } | 
|  |  | 
|  | static void *alloc_and_map(vspace_t *vspace, size_t size) | 
|  | { | 
|  | sel4utils_alloc_data_t *data = get_alloc_data(vspace); | 
|  | if ((size % PAGE_SIZE_4K) != 0) { | 
|  | ZF_LOGE("Object must be multiple of base page size"); | 
|  | return NULL; | 
|  | } | 
|  | if (data->next_bootstrap_vaddr) { | 
|  | void *first_addr = (void *)data->next_bootstrap_vaddr; | 
|  | while (size > 0) { | 
|  | void *vaddr = (void *)data->next_bootstrap_vaddr; | 
|  | vka_object_t frame; | 
|  | int error = vka_alloc_frame(data->vka, seL4_PageBits, &frame); | 
|  | if (error) { | 
|  | LOG_ERROR("Failed to allocate bootstrap frame, error: %d", error); | 
|  | return NULL; | 
|  | } | 
|  |  | 
|  | vka_object_t objects[VSPACE_MAP_PAGING_OBJECTS]; | 
|  | int num = VSPACE_MAP_PAGING_OBJECTS; | 
|  |  | 
|  | error = sel4utils_map_page(data->vka, data->vspace_root, frame.cptr, vaddr, | 
|  | seL4_AllRights, 1, objects, &num); | 
|  |  | 
|  | if (error) { | 
|  | vka_free_object(data->vka, &frame); | 
|  | LOG_ERROR("Failed to map bootstrap frame at %p, error: %d", vaddr, error); | 
|  | return NULL; | 
|  | } | 
|  |  | 
|  | /* Zero the memory */ | 
|  | memset(vaddr, 0, PAGE_SIZE_4K); | 
|  |  | 
|  | for (int i = 0; i < num; i++) { | 
|  | vspace_maybe_call_allocated_object(vspace, objects[i]); | 
|  | } | 
|  |  | 
|  | data->next_bootstrap_vaddr += PAGE_SIZE_4K; | 
|  | size -= PAGE_SIZE_4K; | 
|  | } | 
|  | return first_addr; | 
|  | } else { | 
|  | assert(!"not implemented"); | 
|  | } | 
|  | return NULL; | 
|  | } | 
|  |  | 
|  | static int reserve_range_bottom(vspace_t *vspace, vspace_bottom_level_t *level, uintptr_t start, uintptr_t end) | 
|  | { | 
|  | while (start < end) { | 
|  | int index = INDEX_FOR_LEVEL(start, 0); | 
|  | uintptr_t cap = level->cap[index]; | 
|  | switch (cap) { | 
|  | case RESERVED: | 
|  | /* nothing to be done */ | 
|  | break; | 
|  | case EMPTY: | 
|  | level->cap[index] = RESERVED; | 
|  | break; | 
|  | default: | 
|  | ZF_LOGE("Cannot reserve allocated region"); | 
|  | return -1; | 
|  | } | 
|  | start += BYTES_FOR_LEVEL(0); | 
|  | } | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static int reserve_range_mid(vspace_t *vspace, vspace_mid_level_t *level, int level_num, uintptr_t start, uintptr_t end) | 
|  | { | 
|  | /* walk entries at this level until we complete this range */ | 
|  | while (start < end) { | 
|  | int index = INDEX_FOR_LEVEL(start, level_num); | 
|  | /* align the start so we can check for alignment later */ | 
|  | uintptr_t aligned_start = start & ALIGN_FOR_LEVEL(level_num); | 
|  | /* calculate the start of the next index */ | 
|  | uintptr_t next_start = aligned_start + BYTES_FOR_LEVEL(level_num); | 
|  | int must_recurse = 0; | 
|  | if (next_start > end) { | 
|  | next_start = end; | 
|  | must_recurse = 1; | 
|  | } else if (start != aligned_start) { | 
|  | must_recurse = 1; | 
|  | } | 
|  | uintptr_t next_table = level->table[index]; | 
|  | if (next_table == EMPTY) { | 
|  | if (must_recurse) { | 
|  | /* allocate new level */ | 
|  | if (level_num == 1) { | 
|  | next_table = (uintptr_t)alloc_and_map(vspace, sizeof(vspace_bottom_level_t)); | 
|  | } else { | 
|  | next_table = (uintptr_t)alloc_and_map(vspace, sizeof(vspace_mid_level_t)); | 
|  | } | 
|  | if (next_table == EMPTY) { | 
|  | ZF_LOGE("Failed to allocate and map book keeping frames during bootstrapping"); | 
|  | return -1; | 
|  | } | 
|  | } else { | 
|  | next_table = RESERVED; | 
|  | } | 
|  | level->table[index] = next_table; | 
|  | } | 
|  | /* at this point table is either RESERVED or needs recursion */ | 
|  | if (next_table != RESERVED) { | 
|  | int error; | 
|  | if (level_num == 1) { | 
|  | error = reserve_range_bottom(vspace, (vspace_bottom_level_t *)next_table, start, next_start); | 
|  | } else { | 
|  | error = reserve_range_mid(vspace, (vspace_mid_level_t *)next_table, level_num - 1, start, next_start); | 
|  | } | 
|  | if (error) { | 
|  | return error; | 
|  | } | 
|  | } | 
|  | start = next_start; | 
|  | } | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static int reserve_range(vspace_t *vspace, uintptr_t start, uintptr_t end) | 
|  | { | 
|  | sel4utils_alloc_data_t *data = get_alloc_data(vspace); | 
|  | return reserve_range_mid(vspace, data->top_level, VSPACE_NUM_LEVELS - 1, start, end); | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Symbols in this function need to be provided by your | 
|  | * crt0.S or your linker script, such that we can figure out | 
|  | * what virtual addresses are taken up by the current task | 
|  | */ | 
|  | void sel4utils_get_image_region(uintptr_t *va_start, uintptr_t *va_end) | 
|  | { | 
|  | extern char __executable_start[]; | 
|  | extern char _end[]; | 
|  |  | 
|  | *va_start = (uintptr_t) __executable_start; | 
|  | *va_end = (uintptr_t) _end; | 
|  | *va_end = (uintptr_t) ROUND_UP(*va_end, PAGE_SIZE_4K); | 
|  | } | 
|  |  | 
|  | static int reserve_initial_task_regions(vspace_t *vspace, void *existing_frames[]) | 
|  | { | 
|  |  | 
|  | /* mark the code and data segments as used */ | 
|  | uintptr_t va_start, va_end; | 
|  |  | 
|  | sel4utils_get_image_region(&va_start, &va_end); | 
|  |  | 
|  | /* this is the scope of the virtual memory used by the image, including | 
|  | * data, text and stack */ | 
|  | if (reserve_range(vspace, va_start, va_end)) { | 
|  | ZF_LOGE("Error reserving code/data segment"); | 
|  | return -1; | 
|  | } | 
|  |  | 
|  | /* mark boot info as used */ | 
|  | if (existing_frames != NULL) { | 
|  | for (int i = 0; existing_frames[i] != NULL; i++) { | 
|  | if (reserve_range(vspace, (uintptr_t) existing_frames[i], (uintptr_t) existing_frames[i] | 
|  | + PAGE_SIZE_4K)) { | 
|  | ZF_LOGE("Error reserving frame at %p", existing_frames[i]); | 
|  | return -1; | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | /* What we need to do is bootstrap the book keeping information for our page tables. | 
|  | * The whole goal here is that at some point we need to allocate book keeping information | 
|  | * and put it somewhere. Putting it somewhere is fine, but we then need to make sure that | 
|  | * we track (i.e. mark as used) wherever we ended up putting it. In order to do this we | 
|  | * need to allocate memory to create structures to mark etc etc. To prevent this recursive | 
|  | * dependency we will mark, right now, as reserved a region large enough such that we could | 
|  | * allocate all possible book keeping tables from it */ | 
|  | static int bootstrap_page_table(vspace_t *vspace) | 
|  | { | 
|  | sel4utils_alloc_data_t *data = get_alloc_data(vspace); | 
|  |  | 
|  | data->next_bootstrap_vaddr = VSPACE_RESERVE_START; | 
|  | /* Allocate top level paging structure */ | 
|  | data->top_level = alloc_and_map(vspace, sizeof(vspace_mid_level_t)); | 
|  |  | 
|  | /* Try and mark reserved our entire reserve region */ | 
|  | if (reserve_range(vspace, VSPACE_RESERVE_START, VSPACE_RESERVE_START + VSPACE_RESERVE_SIZE)) { | 
|  | return -1; | 
|  | } | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | void *bootstrap_create_level(vspace_t *vspace, size_t size) | 
|  | { | 
|  | return alloc_and_map(vspace, size); | 
|  | } | 
|  |  | 
|  |  | 
|  | static int get_vspace_bootstrap(vspace_t *loader, vspace_t *new_vspace, sel4utils_alloc_data_t *data, | 
|  | sel4utils_map_page_fn map_page) | 
|  | { | 
|  | data->bootstrap = loader; | 
|  | /* create the top level page table from the loading vspace */ | 
|  | data->top_level = vspace_new_pages(loader, seL4_AllRights, sizeof(vspace_mid_level_t) / PAGE_SIZE_4K, seL4_PageBits); | 
|  | if (data->top_level == NULL) { | 
|  | return -1; | 
|  | } | 
|  | memset(data->top_level, 0, sizeof(vspace_mid_level_t)); | 
|  |  | 
|  | common_init_post_bootstrap(new_vspace, map_page); | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | /* Interface functions */ | 
|  | int sel4utils_get_vspace_with_map(vspace_t *loader, vspace_t *new_vspace, sel4utils_alloc_data_t *data, | 
|  | vka_t *vka, seL4_CPtr vspace_root, | 
|  | vspace_allocated_object_fn allocated_object_fn, void *allocated_object_cookie, sel4utils_map_page_fn map_page) | 
|  | { | 
|  | new_vspace->data = (void *) data; | 
|  |  | 
|  | if (common_init(new_vspace, vka, vspace_root, allocated_object_fn, allocated_object_cookie)) { | 
|  | return -1; | 
|  | } | 
|  |  | 
|  | return get_vspace_bootstrap(loader, new_vspace, data, map_page); | 
|  | } | 
|  |  | 
|  | int sel4utils_get_empty_vspace_with_map(vspace_t *loader, vspace_t *new_vspace, sel4utils_alloc_data_t *data, | 
|  | vka_t *vka, seL4_CPtr vspace_root, | 
|  | vspace_allocated_object_fn allocated_object_fn, void *allocated_object_cookie, sel4utils_map_page_fn map_page) | 
|  | { | 
|  |  | 
|  | new_vspace->data = (void *) data; | 
|  |  | 
|  | if (common_init(new_vspace, vka, vspace_root, allocated_object_fn, allocated_object_cookie)) { | 
|  | return -1; | 
|  | } | 
|  | data->is_empty = true; | 
|  |  | 
|  | return get_vspace_bootstrap(loader, new_vspace, data, map_page); | 
|  | } | 
|  |  | 
|  | int sel4utils_get_vspace(vspace_t *loader, vspace_t *new_vspace, sel4utils_alloc_data_t *data, | 
|  | vka_t *vka, seL4_CPtr vspace_root, | 
|  | vspace_allocated_object_fn allocated_object_fn, void *allocated_object_cookie) | 
|  | { | 
|  | return sel4utils_get_vspace_with_map(loader, new_vspace, data, vka, vspace_root, allocated_object_fn, | 
|  | allocated_object_cookie, sel4utils_map_page_pd); | 
|  | } | 
|  |  | 
|  | int sel4utils_get_empty_vspace(vspace_t *loader, vspace_t *new_vspace, sel4utils_alloc_data_t *data, | 
|  | vka_t *vka, seL4_CPtr vspace_root, | 
|  | vspace_allocated_object_fn allocated_object_fn, void *allocated_object_cookie) | 
|  | { | 
|  | new_vspace->data = (void *) data; | 
|  |  | 
|  | if (common_init(new_vspace, vka, vspace_root, allocated_object_fn, allocated_object_cookie)) { | 
|  | return -1; | 
|  | } | 
|  | data->is_empty = true; | 
|  |  | 
|  | return get_vspace_bootstrap(loader, new_vspace, data, sel4utils_map_page_pd); | 
|  | } | 
|  |  | 
|  |  | 
|  | #ifdef CONFIG_VTX | 
|  | int sel4utils_get_vspace_ept(vspace_t *loader, vspace_t *new_vspace, vka_t *vka, | 
|  | seL4_CPtr ept, vspace_allocated_object_fn allocated_object_fn, void *allocated_object_cookie) | 
|  | { | 
|  | sel4utils_alloc_data_t *data = malloc(sizeof(*data)); | 
|  | if (!data) { | 
|  | return -1; | 
|  | } | 
|  |  | 
|  | return sel4utils_get_vspace_with_map(loader, new_vspace, data, vka, ept, allocated_object_fn, allocated_object_cookie, | 
|  | sel4utils_map_page_ept); | 
|  | } | 
|  | #endif /* CONFIG_VTX */ | 
|  |  | 
|  | int sel4utils_bootstrap_vspace(vspace_t *vspace, sel4utils_alloc_data_t *data, | 
|  | seL4_CPtr vspace_root, vka_t *vka, | 
|  | vspace_allocated_object_fn allocated_object_fn, void *cookie, void *existing_frames[]) | 
|  | { | 
|  | vspace->data = (void *) data; | 
|  |  | 
|  | if (common_init(vspace, vka, vspace_root, allocated_object_fn, cookie)) { | 
|  | return -1; | 
|  | } | 
|  |  | 
|  | data->bootstrap = NULL; | 
|  |  | 
|  | if (bootstrap_page_table(vspace)) { | 
|  | return -1; | 
|  | } | 
|  |  | 
|  | if (reserve_initial_task_regions(vspace, existing_frames)) { | 
|  | return -1; | 
|  | } | 
|  |  | 
|  | common_init_post_bootstrap(vspace, sel4utils_map_page_pd); | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | int sel4utils_bootstrap_vspace_with_bootinfo(vspace_t *vspace, sel4utils_alloc_data_t *data, | 
|  | seL4_CPtr vspace_root, | 
|  | vka_t *vka, seL4_BootInfo *info, vspace_allocated_object_fn allocated_object_fn, | 
|  | void *allocated_object_cookie) | 
|  | { | 
|  | size_t extra_pages = BYTES_TO_4K_PAGES(info->extraLen); | 
|  | uintptr_t extra_base = (uintptr_t)info + PAGE_SIZE_4K; | 
|  | void *existing_frames[extra_pages + 3]; | 
|  | existing_frames[0] = info; | 
|  | /* We assume the IPC buffer is less than a page and fits into one page */ | 
|  | existing_frames[1] = (void *)(seL4_Word)ROUND_DOWN(((seL4_Word)(info->ipcBuffer)), PAGE_SIZE_4K); | 
|  | size_t i; | 
|  | for (i = 0; i < extra_pages; i++) { | 
|  | existing_frames[i + 2] = (void *)(extra_base + i * PAGE_SIZE_4K); | 
|  | } | 
|  | existing_frames[i + 2] = NULL; | 
|  |  | 
|  | return sel4utils_bootstrap_vspace(vspace, data, vspace_root, vka, allocated_object_fn, | 
|  | allocated_object_cookie, existing_frames); | 
|  | } | 
|  |  | 
|  | int sel4utils_bootstrap_clone_into_vspace(vspace_t *current, vspace_t *clone, reservation_t image) | 
|  | { | 
|  | sel4utils_res_t *res = reservation_to_res(image); | 
|  | seL4_CPtr slot; | 
|  | int error = vka_cspace_alloc(get_alloc_data(current)->vka, &slot); | 
|  |  | 
|  | if (error) { | 
|  | return -1; | 
|  | } | 
|  |  | 
|  | cspacepath_t dest; | 
|  | vka_cspace_make_path(get_alloc_data(current)->vka, slot, &dest); | 
|  |  | 
|  | for (uintptr_t page = res->start; page < res->end - 1; page += PAGE_SIZE_4K) { | 
|  | /* we don't know if the current vspace has caps to its mappings - | 
|  | * it probably doesn't. | 
|  | * | 
|  | * So we map the page in and copy the data across instead :( */ | 
|  |  | 
|  | /* create the page in the clone vspace */ | 
|  | error = vspace_new_pages_at_vaddr(clone, (void *) page, 1, seL4_PageBits, image); | 
|  | if (error) { | 
|  | /* vspace will be left inconsistent */ | 
|  | ZF_LOGE("Error %d while trying to map page at %"PRIuPTR, error, page); | 
|  | } | 
|  |  | 
|  | seL4_CPtr cap = vspace_get_cap(clone, (void *) page); | 
|  | /* copy the cap */ | 
|  | cspacepath_t src; | 
|  |  | 
|  | vka_cspace_make_path(get_alloc_data(clone)->vka, cap, &src); | 
|  | error = vka_cnode_copy(&dest, &src, seL4_AllRights); | 
|  | assert(error == 0); | 
|  |  | 
|  | /* map a copy of it the current vspace */ | 
|  | void *dest_addr = vspace_map_pages(current, &dest.capPtr, NULL, seL4_AllRights, | 
|  | 1, seL4_PageBits, 1); | 
|  | if (dest_addr == NULL) { | 
|  | /* vspace will be left inconsistent */ | 
|  | ZF_LOGE("Error! Vspace mapping failed, bailing\n"); | 
|  | return -1; | 
|  | } | 
|  |  | 
|  | /* copy the data */ | 
|  | memcpy(dest_addr, (void *) page, PAGE_SIZE_4K); | 
|  |  | 
|  | #ifdef CONFIG_ARCH_ARM | 
|  | seL4_ARM_Page_Unify_Instruction(dest.capPtr, 0, PAGE_SIZE_4K); | 
|  | seL4_ARM_Page_Unify_Instruction(cap, 0, PAGE_SIZE_4K); | 
|  | #endif /* CONFIG_ARCH_ARM */ | 
|  |  | 
|  | /* unmap our copy */ | 
|  | vspace_unmap_pages(current, dest_addr, 1, seL4_PageBits, VSPACE_PRESERVE); | 
|  | vka_cnode_delete(&dest); | 
|  | } | 
|  |  | 
|  | /* TODO swap out fault handler temporarily to ignore faults here */ | 
|  | vka_cspace_free(get_alloc_data(current)->vka, slot); | 
|  | return 0; | 
|  | } |