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