| /* |
| * 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> |
| |
| #include <simple/simple.h> |
| #include <sel4platsupport/io.h> |
| #ifdef ARCH_ARM |
| #include <platsupport/clock.h> |
| #include <platsupport/mux.h> |
| #endif |
| #include <utils/util.h> |
| #include <vspace/page.h> |
| |
| #include <vspace/vspace.h> |
| #include <vka/capops.h> |
| |
| #include <stdlib.h> |
| |
| typedef struct io_mapping { |
| /* 1 if we mapped this into the vspace ourselves, or 0 |
| * if simple just gave us the vaddr */ |
| int was_mapped; |
| /* address we returned to the user */ |
| void *returned_addr; |
| /* base address of the mapping with respect to the vspace */ |
| void *mapped_addr; |
| int num_pages; |
| int page_size; |
| seL4_CPtr *caps; |
| struct io_mapping *next, *prev; |
| } io_mapping_t; |
| |
| typedef struct sel4platsupport_io_mapper_cookie { |
| vspace_t vspace; |
| simple_t simple; |
| vka_t vka; |
| io_mapping_t *head; |
| } sel4platsupport_io_mapper_cookie_t; |
| |
| static io_mapping_t *new_unmapped_node(void *addr) |
| { |
| io_mapping_t *ret = (io_mapping_t*)malloc(sizeof(*ret)); |
| if (!ret) { |
| return NULL; |
| } |
| *ret = (io_mapping_t) { |
| .was_mapped = 0, |
| .returned_addr = addr, |
| .next = NULL, |
| .prev = NULL, |
| .caps = NULL, |
| .mapped_addr = NULL |
| }; |
| return ret; |
| } |
| |
| static void |
| _insert_node(sel4platsupport_io_mapper_cookie_t *io_mapper, io_mapping_t *node) |
| { |
| node->prev = NULL; |
| node->next = io_mapper->head; |
| if (io_mapper->head) { |
| io_mapper->head->prev = node; |
| } |
| io_mapper->head = node; |
| } |
| |
| static io_mapping_t * |
| _find_node(sel4platsupport_io_mapper_cookie_t *io_mapper, void *returned_addr) |
| { |
| io_mapping_t *current; |
| for (current = io_mapper->head; current; current = current->next) { |
| if (current->returned_addr == returned_addr) { |
| return current; |
| } |
| } |
| return NULL; |
| } |
| |
| static void |
| _remove_node(sel4platsupport_io_mapper_cookie_t *io_mapper, io_mapping_t *node) |
| { |
| if (node->prev) { |
| node->prev->next = node->next; |
| } else { |
| assert(io_mapper->head == node); |
| io_mapper->head = node->next; |
| } |
| if (node->next) { |
| node->next->prev = node->prev; |
| } |
| } |
| |
| static void |
| _free_node(io_mapping_t *node) |
| { |
| if (node->caps) { |
| free(node->caps); |
| } |
| free(node); |
| } |
| |
| static void |
| _remove_free_node(sel4platsupport_io_mapper_cookie_t *io_mapper, io_mapping_t *node) |
| { |
| _remove_node(io_mapper, node); |
| _free_node(node); |
| } |
| |
| static void * |
| sel4platsupport_map_paddr_with_page_size(sel4platsupport_io_mapper_cookie_t *io_mapper, uintptr_t paddr, size_t size, int page_size_bits, int cached) |
| { |
| |
| vka_t *vka = &io_mapper->vka; |
| vspace_t *vspace = &io_mapper->vspace; |
| simple_t *simple = &io_mapper->simple; |
| |
| /* search at start of page */ |
| int page_size = BIT(page_size_bits); |
| uintptr_t start = ROUND_DOWN(paddr, page_size); |
| uintptr_t offset = paddr - start; |
| size += offset; |
| |
| /* calculate number of pages */ |
| unsigned int num_pages = ROUND_UP(size, page_size) >> page_size_bits; |
| assert(num_pages << page_size_bits >= size); |
| |
| seL4_CPtr *frames = (seL4_CPtr*)malloc(sizeof(*frames) * num_pages); |
| if (!frames) { |
| ZF_LOGE("Failed to allocate array of size %zu", sizeof(*frames) * num_pages); |
| return NULL; |
| } |
| io_mapping_t *node = (io_mapping_t*)malloc(sizeof(*node)); |
| if (!node) { |
| ZF_LOGE("Failed to malloc of size %zu", sizeof(*node)); |
| free(frames); |
| return NULL; |
| } |
| |
| /* get all of the physical frame caps */ |
| for (unsigned int i = 0; i < num_pages; i++) { |
| /* allocate a cslot */ |
| int error = vka_cspace_alloc(vka, &frames[i]); |
| if (error) { |
| ZF_LOGE("cspace alloc failed"); |
| assert(error == 0); |
| /* we don't clean up as everything has gone to hell */ |
| return NULL; |
| } |
| |
| /* create a path */ |
| cspacepath_t path; |
| vka_cspace_make_path(vka, frames[i], &path); |
| |
| error = simple_get_frame_cap(simple, (void*)start + (i * page_size), page_size_bits, &path); |
| |
| if (error) { |
| /* free this slot, and then do general cleanup of the rest of the slots. |
| * this avoids a needless seL4_CNode_Delete of this slot, as there is no |
| * cap in it */ |
| vka_cspace_free(vka, frames[i]); |
| num_pages = i; |
| goto error; |
| } |
| |
| } |
| |
| /* Now map the frames in */ |
| void *vaddr = vspace_map_pages(vspace, frames, NULL, seL4_AllRights, num_pages, page_size_bits, cached); |
| if (vaddr) { |
| /* fill out and insert the node */ |
| *node = (io_mapping_t) { |
| .mapped_addr = vaddr, |
| .returned_addr = vaddr + offset, |
| .num_pages = num_pages, |
| .page_size = page_size_bits, |
| .caps = frames |
| }; |
| _insert_node(io_mapper, node); |
| return vaddr + offset; |
| } |
| error: |
| for (unsigned int i = 0; i < num_pages; i++) { |
| cspacepath_t path; |
| vka_cspace_make_path(vka, frames[i], &path); |
| vka_cnode_delete(&path); |
| vka_cspace_free(vka, frames[i]); |
| } |
| free(frames); |
| free(node); |
| return NULL; |
| } |
| |
| static void * |
| sel4platsupport_get_vaddr_with_page_size(sel4platsupport_io_mapper_cookie_t *io_mapper, uintptr_t paddr, size_t size, int page_size_bits) |
| { |
| simple_t *simple = &io_mapper->simple; |
| |
| /* search at start of page */ |
| int page_size = BIT(page_size_bits); |
| uintptr_t start = ROUND_DOWN(paddr, page_size); |
| uintptr_t offset = paddr - start; |
| size += offset; |
| |
| /* calculate number of pages */ |
| unsigned int num_pages = ROUND_UP(size, page_size) >> page_size_bits; |
| assert(num_pages << page_size_bits >= size); |
| |
| void *first_vaddr = simple_get_frame_vaddr(simple, (void*)start, page_size_bits); |
| if (!first_vaddr) { |
| return NULL; |
| } |
| for (unsigned int i = 1; i < num_pages; i++) { |
| void *vaddr = simple_get_frame_vaddr(simple, (void*)start + (i * page_size), page_size_bits); |
| if (first_vaddr + (i * page_size) != vaddr) { |
| return NULL; |
| } |
| } |
| io_mapping_t *node = new_unmapped_node(first_vaddr + offset); |
| if (!node) { |
| ZF_LOGE("Failed to allocate node to track mapping"); |
| return NULL; |
| } |
| _insert_node(io_mapper, node); |
| return first_vaddr + offset; |
| } |
| |
| static void * |
| sel4platsupport_map_paddr(void *cookie, uintptr_t paddr, size_t size, int cached, ps_mem_flags_t flags) |
| { |
| (void)flags; // we don't support these |
| /* The simple interface supports two ways of getting physical addresses. |
| * Unfortunately it tends that precisely one of them will actually be |
| * implemented. |
| * One gives us the cap and we have to map it in, the other gives us the |
| * mapped address. We try the getting the cap technique first as that gives |
| * us better control since we can ensure the correct caching policy. |
| * If that fails then we attempt to get the mappings and ensure that they |
| * are in contiguous virtual address if there is more than one. |
| * In both cases we will try and use the largest frame size possible */ |
| sel4platsupport_io_mapper_cookie_t* io_mapper = (sel4platsupport_io_mapper_cookie_t*)cookie; |
| |
| |
| int frame_size_index = 0; |
| /* find the largest reasonable frame size */ |
| while (frame_size_index + 1 < SEL4_NUM_PAGE_SIZES) { |
| if (size >> sel4_page_sizes[frame_size_index + 1] == 0) { |
| break; |
| } |
| frame_size_index++; |
| } |
| |
| /* try mapping in this and all smaller frame sizes until something works */ |
| for (int i = frame_size_index; i >= 0; i--) { |
| void *result = sel4platsupport_map_paddr_with_page_size(io_mapper, paddr, size, sel4_page_sizes[i], cached); |
| if (result) { |
| return result; |
| } |
| } |
| |
| /* try the get_frame_vaddr technique */ |
| for (int i = frame_size_index; i >= 0; i--) { |
| void *result = sel4platsupport_get_vaddr_with_page_size(io_mapper, paddr, size, sel4_page_sizes[i]); |
| if (result) { |
| return result; |
| } |
| } |
| |
| /* shit out of luck */ |
| ZF_LOGE("Failed to find a way to map address %p", (void *)paddr); |
| return NULL; |
| } |
| |
| static void |
| sel4platsupport_unmap_vaddr(void *cookie, void *vaddr, size_t size) |
| { |
| (void)size; |
| sel4platsupport_io_mapper_cookie_t* io_mapper = (sel4platsupport_io_mapper_cookie_t*)cookie; |
| |
| io_mapping_t *mapping; |
| |
| mapping = _find_node(io_mapper, vaddr); |
| if (!mapping) { |
| ZF_LOGF("Tried to unmap vaddr %p, which was never mapped in", vaddr); |
| return; |
| } |
| if (!mapping->was_mapped) { |
| /* this vaddr was given directly from simple, so nothing to unmap */ |
| _remove_free_node(io_mapper, mapping); |
| return; |
| } |
| |
| vspace_t *vspace = &io_mapper->vspace; |
| vka_t *vka = &io_mapper->vka; |
| |
| vspace_unmap_pages(vspace, mapping->mapped_addr, mapping->num_pages, mapping->page_size, |
| VSPACE_PRESERVE); |
| |
| for (int i = 0; i < mapping->num_pages; i++) { |
| cspacepath_t path; |
| vka_cspace_make_path(vka, mapping->caps[i], &path); |
| vka_cnode_delete(&path); |
| vka_cspace_free(vka, mapping->caps[i]); |
| } |
| |
| _remove_free_node(io_mapper, mapping); |
| } |
| |
| int |
| sel4platsupport_new_io_mapper(simple_t simple, vspace_t vspace, vka_t vka, ps_io_mapper_t *io_mapper) |
| { |
| sel4platsupport_io_mapper_cookie_t *cookie; |
| cookie = (sel4platsupport_io_mapper_cookie_t*)malloc(sizeof(*cookie)); |
| if (!cookie) { |
| ZF_LOGE("Failed to allocate %zu bytes", sizeof(*cookie)); |
| return -1; |
| } |
| *cookie = (sel4platsupport_io_mapper_cookie_t) { |
| .vspace = vspace, |
| .simple = simple, |
| .vka = vka |
| }; |
| *io_mapper = (ps_io_mapper_t) { |
| .cookie = cookie, |
| .io_map_fn = sel4platsupport_map_paddr, |
| .io_unmap_fn = sel4platsupport_unmap_vaddr |
| }; |
| return 0; |
| } |
| |
| int |
| sel4platsupport_new_io_ops(simple_t simple, vspace_t vspace, vka_t vka, ps_io_ops_t *io_ops) |
| { |
| int err; |
| err = sel4platsupport_new_io_mapper(simple, vspace, vka, &io_ops->io_mapper); |
| if (err) { |
| return err; |
| } |
| #ifdef ARCH_ARM |
| /* We don't consider these as failures */ |
| err = clock_sys_init(io_ops, &io_ops->clock_sys); |
| (void)err; |
| err = mux_sys_init(io_ops, &io_ops->mux_sys); |
| (void)err; |
| #endif |
| return 0; |
| } |
| |
| |