| /* |
| * 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) |
| */ |
| |
| #include <autoconf.h> |
| |
| #if defined CONFIG_LIB_SEL4_VKA && defined CONFIG_LIB_SEL4_VSPACE |
| |
| #include <sel4utils/client_server_vspace.h> |
| #include <vka/capops.h> |
| #include <sel4utils/mapping.h> |
| #include <sel4utils/util.h> |
| #include <stdlib.h> |
| #include <sel4utils/vspace.h> |
| #include <sel4utils/vspace_internal.h> |
| |
| #define OFFSET(x) ((uintptr_t)(x) & MASK(BOTTOM_LEVEL_BITS_OFFSET) ) |
| #define ADDR(start, page) ( ((uintptr_t) start) + (page) * PAGE_SIZE_4K ) |
| |
| typedef struct client_server_vspace { |
| /* vspace of the server. we also use this to request memory for metadata */ |
| vspace_t *server; |
| /* vspace of the client we are proxying calls for */ |
| vspace_t *client; |
| vka_t *vka; |
| /* a fake sel4utils vspace that acts as a translation layer */ |
| sel4utils_alloc_data_t translation_data; |
| vspace_t translation; |
| } client_server_vspace_t; |
| |
| static void unmap(client_server_vspace_t *cs_vspace, void *caddr, size_t size_bits) |
| { |
| /* determine the virtual address of the start */ |
| void *saddr = (void*)get_cap(cs_vspace->translation_data.top_level, (uintptr_t) caddr); |
| assert(saddr); |
| /* get the cap from the server vspace */ |
| seL4_CPtr cap = vspace_get_cap(cs_vspace->server, saddr); |
| assert(cap); |
| /* unmap */ |
| vspace_unmap_pages(cs_vspace->server, saddr, 1, size_bits, VSPACE_PRESERVE); |
| /* delete and free the cap */ |
| cspacepath_t cap_path; |
| vka_cspace_make_path(cs_vspace->vka, cap, &cap_path); |
| vka_cnode_delete(&cap_path); |
| vka_cspace_free(cs_vspace->vka, cap); |
| /* remove our translation */ |
| clear_entries(&cs_vspace->translation, (uintptr_t) caddr, size_bits); |
| } |
| |
| static void unmap_many(client_server_vspace_t *cs_vspace, void *caddr, size_t num_pages, size_t size_bits) |
| { |
| int i; |
| for (i = 0; i < num_pages; i++) { |
| unmap(cs_vspace, caddr + BIT(size_bits) * i, size_bits); |
| } |
| } |
| |
| static int dup_and_map(client_server_vspace_t *cs_vspace, seL4_CPtr cap, void *caddr, size_t size_bits) |
| { |
| /* duplicate the cap */ |
| seL4_CPtr server_cap; |
| cspacepath_t cap_path, server_cap_path; |
| int error = vka_cspace_alloc(cs_vspace->vka, &server_cap); |
| if (error) { |
| LOG_ERROR("Failed to allocate cslot"); |
| return error; |
| } |
| vka_cspace_make_path(cs_vspace->vka, cap, &cap_path); |
| vka_cspace_make_path(cs_vspace->vka, server_cap, &server_cap_path); |
| error = vka_cnode_copy(&server_cap_path, &cap_path, seL4_AllRights); |
| if (error != seL4_NoError) { |
| LOG_ERROR("Error copying frame cap"); |
| vka_cspace_free(cs_vspace->vka, server_cap); |
| return -1; |
| } |
| /* map it into the server */ |
| void *saddr = vspace_map_pages(cs_vspace->server, &server_cap_path.capPtr, NULL, seL4_AllRights, 1, size_bits, 1); |
| if (!saddr) { |
| LOG_ERROR("Error mapping frame into server vspace"); |
| vka_cnode_delete(&server_cap_path); |
| vka_cspace_free(cs_vspace->vka, server_cap); |
| return -1; |
| } |
| /* update our translation */ |
| int i; |
| uint32_t pages = BYTES_TO_4K_PAGES(BIT(size_bits)); |
| for (i = 0; i < pages; i++) { |
| if (update_entry(&cs_vspace->translation, ADDR(caddr, i), (seL4_CPtr)ADDR(saddr, i), 0)) { |
| /* remove the entries we already put in. we can't call |
| * clear_entries because the portion to remove is not |
| * a power of two size */ |
| for (i--; i >= 0; i--) { |
| clear(&cs_vspace->translation, ADDR(caddr, i)); |
| } |
| vka_cnode_delete(&server_cap_path); |
| vka_cspace_free(cs_vspace->vka, server_cap); |
| return -1; |
| } |
| } |
| return 0; |
| } |
| |
| static int dup_and_map_many(client_server_vspace_t *cs_vspace, seL4_CPtr *caps, void *caddr, size_t num_pages, size_t size_bits) |
| { |
| int i; |
| for (i = 0; i < num_pages; i++) { |
| int error = dup_and_map(cs_vspace, caps[i], caddr + BIT(size_bits) * i, size_bits); |
| if (error) { |
| unmap_many(cs_vspace, caddr, i - 1, size_bits); |
| return error; |
| } |
| } |
| return 0; |
| } |
| |
| static int find_dup_and_map_many(client_server_vspace_t *cs_vspace, void *caddr, size_t num_pages, size_t size_bits) |
| { |
| int i; |
| seL4_CPtr caps[num_pages]; |
| for (i = 0; i < num_pages; i++) { |
| caps[i] = vspace_get_cap(cs_vspace->client, caddr + BIT(size_bits) * i); |
| assert(caps[i]); |
| } |
| return dup_and_map_many(cs_vspace, caps, caddr, num_pages, size_bits); |
| } |
| |
| static reservation_t cs_reserve_range_aligned(vspace_t *vspace, size_t size, size_t size_bits, seL4_CapRights rights, |
| int cacheable, void **result) |
| { |
| client_server_vspace_t *cs_vspace = (client_server_vspace_t*)vspace->data; |
| assert(cacheable); |
| get_alloc_data(cs_vspace->client)->page_directory = cs_vspace->translation_data.page_directory; |
| /* we are not interested in client reservations, just proxy */ |
| return vspace_reserve_range_aligned(cs_vspace->client, size, size_bits, rights, cacheable, result); |
| } |
| |
| static reservation_t cs_reserve_range_at(vspace_t *vspace, void *vaddr, size_t size, seL4_CapRights |
| rights, int cacheable) |
| { |
| client_server_vspace_t *cs_vspace = (client_server_vspace_t*)vspace->data; |
| assert(cacheable); |
| get_alloc_data(cs_vspace->client)->page_directory = cs_vspace->translation_data.page_directory; |
| /* we are not interested in client reservations, just proxy */ |
| return vspace_reserve_range_at(cs_vspace->client, vaddr, size, rights, cacheable); |
| } |
| |
| static void cs_free_reservation(vspace_t *vspace, reservation_t reservation) |
| { |
| client_server_vspace_t *cs_vspace = (client_server_vspace_t*)vspace->data; |
| get_alloc_data(cs_vspace->client)->page_directory = cs_vspace->translation_data.page_directory; |
| vspace_free_reservation(cs_vspace->client, reservation); |
| } |
| |
| static int cs_map_pages_at_vaddr(vspace_t *vspace, seL4_CPtr caps[], uint32_t cookies[], void *vaddr, |
| size_t num_pages, size_t size_bits, reservation_t reservation) |
| { |
| client_server_vspace_t *cs_vspace = (client_server_vspace_t*)vspace->data; |
| get_alloc_data(cs_vspace->client)->page_directory = cs_vspace->translation_data.page_directory; |
| assert(size_bits >= 12); |
| /* first map all the pages into the client */ |
| int result = vspace_map_pages_at_vaddr(cs_vspace->client, caps, cookies, vaddr, num_pages, size_bits, reservation); |
| if (result) { |
| LOG_ERROR("Error mapping pages into client vspace"); |
| /* some error, propagate up */ |
| return result; |
| } |
| /* now map them all into the server */ |
| result = dup_and_map_many(cs_vspace, caps, vaddr, num_pages, size_bits); |
| if (result) { |
| /* unmap */ |
| LOG_ERROR("Error mapping pages into server vspace"); |
| vspace_unmap_pages(cs_vspace->client, vaddr, num_pages, size_bits, VSPACE_PRESERVE); |
| return result; |
| } |
| return 0; |
| } |
| |
| static seL4_CPtr cs_get_cap(vspace_t *vspace, void *vaddr) |
| { |
| client_server_vspace_t *cs_vspace = (client_server_vspace_t*)vspace->data; |
| get_alloc_data(cs_vspace->client)->page_directory = cs_vspace->translation_data.page_directory; |
| /* return the client cap */ |
| return vspace_get_cap(cs_vspace->client, vaddr); |
| } |
| |
| static seL4_CPtr cs_get_root(vspace_t *vspace) |
| { |
| client_server_vspace_t *cs_vspace = (client_server_vspace_t*)vspace->data; |
| return cs_vspace->translation_data.page_directory; |
| } |
| |
| static void cs_unmap_pages(vspace_t *vspace, void *vaddr, size_t num_pages, size_t size_bits, vka_t *vka) |
| { |
| client_server_vspace_t *cs_vspace = (client_server_vspace_t*)vspace->data; |
| get_alloc_data(cs_vspace->client)->page_directory = cs_vspace->translation_data.page_directory; |
| /* remove our mappings */ |
| unmap_many(cs_vspace, vaddr, num_pages, size_bits); |
| /* unmap from the client */ |
| vspace_unmap_pages(cs_vspace->client, vaddr, num_pages, size_bits, vka); |
| } |
| |
| static int cs_new_pages_at_vaddr(vspace_t *vspace, void *vaddr, size_t num_pages, |
| size_t size_bits, reservation_t reservation) |
| { |
| client_server_vspace_t *cs_vspace = (client_server_vspace_t*)vspace->data; |
| get_alloc_data(cs_vspace->client)->page_directory = cs_vspace->translation_data.page_directory; |
| assert(size_bits >= 12); |
| /* create new pages in the client first */ |
| int result = vspace_new_pages_at_vaddr(cs_vspace->client, vaddr, num_pages, size_bits, reservation); |
| if (result) { |
| LOG_ERROR("Error creating pages in client vspace"); |
| return result; |
| } |
| result = find_dup_and_map_many(cs_vspace, vaddr, num_pages, size_bits); |
| if (result) { |
| LOG_ERROR("Error mapping new page in server vspace"); |
| assert(!"Cannot delete pages we created in client vspace"); |
| return -1; |
| } |
| return 0; |
| } |
| |
| int sel4utils_get_cs_vspace(vspace_t *vspace, vka_t *vka, vspace_t *server, vspace_t *client) |
| { |
| int error; |
| client_server_vspace_t *cs_vspace = malloc(sizeof(*cs_vspace)); |
| if (cs_vspace == NULL) { |
| LOG_ERROR("Failed to create translation vspace"); |
| return -1; |
| } |
| cs_vspace->server = server; |
| cs_vspace->client = client; |
| cs_vspace->vka = vka; |
| |
| error = sel4utils_get_vspace(server, &cs_vspace->translation, &cs_vspace->translation_data, |
| vka, 0, NULL, NULL); |
| if (error) { |
| LOG_ERROR("Failed to create translation vspace"); |
| free(cs_vspace); |
| return error; |
| } |
| |
| vspace->data = cs_vspace; |
| |
| vspace->new_pages_at_vaddr = cs_new_pages_at_vaddr; |
| |
| vspace->map_pages_at_vaddr = cs_map_pages_at_vaddr; |
| vspace->unmap_pages = cs_unmap_pages; |
| |
| vspace->reserve_range_aligned = cs_reserve_range_aligned; |
| vspace->reserve_range_at = cs_reserve_range_at; |
| vspace->free_reservation = cs_free_reservation; |
| |
| vspace->get_cap = cs_get_cap; |
| vspace->get_root = cs_get_root; |
| |
| vspace->allocated_object = NULL; |
| vspace->allocated_object_cookie = NULL; |
| |
| return 0; |
| } |
| |
| void *sel4utils_cs_vspace_translate(vspace_t *vspace, void *addr) |
| { |
| client_server_vspace_t *cs_vspace = (client_server_vspace_t*)vspace->data; |
| |
| seL4_CPtr translation_result = get_cap(cs_vspace->translation_data.top_level, (uintptr_t) addr); |
| |
| /* the translation will throw away any non aligned bits, if we got a successful |
| * translation then add the offset back onto the result */ |
| return translation_result == 0 ? NULL : (void *)(translation_result + OFFSET((uintptr_t) addr)); |
| } |
| |
| int sel4utils_cs_vspace_for_each(vspace_t *vspace, void *addr, uint32_t len, |
| int (*proc)(void* addr, uint32_t len, void *cookie), |
| void *cookie) |
| { |
| uintptr_t current_addr; |
| uintptr_t next_addr; |
| uintptr_t end_addr = (uintptr_t) addr + len; |
| for (current_addr = (uintptr_t) addr; current_addr < end_addr; current_addr = next_addr) { |
| uintptr_t current_aligned = PAGE_ALIGN_4K(current_addr); |
| uintptr_t next_page_start = current_aligned + PAGE_SIZE_4K; |
| next_addr = MIN(end_addr, next_page_start); |
| uintptr_t saddr = (uintptr_t) sel4utils_cs_vspace_translate(vspace, (void *) current_aligned); |
| if (!saddr) { |
| return -1; |
| } |
| int result = proc((void*)(saddr + (current_addr - current_aligned)), next_addr - current_addr, cookie); |
| if (result) { |
| return result; |
| } |
| } |
| return 0; |
| } |
| |
| int sel4utils_cs_vspace_set_root(vspace_t *vspace, seL4_CPtr page_directory) |
| { |
| assert(vspace); |
| |
| client_server_vspace_t *cs_vspace = (client_server_vspace_t*)vspace->data; |
| cs_vspace->translation_data.page_directory = page_directory; |
| |
| return 0; |
| } |
| |
| #endif |