| /* |
| * 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; |
| } |