| /* | |
| * 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 <sel4utils/page_dma.h> | |
| #include <vspace/vspace.h> | |
| #include <stdlib.h> | |
| #include <vka/capops.h> | |
| #include <vka/kobject_t.h> | |
| #include <utils/util.h> | |
| #include <string.h> | |
| #include <sel4utils/arch/cache.h> | |
| typedef struct dma_man { | |
| vka_t vka; | |
| vspace_t vspace; | |
| } dma_man_t; | |
| typedef struct dma_alloc { | |
| void *base; | |
| vka_object_t ut; | |
| uintptr_t paddr; | |
| } dma_alloc_t; | |
| static void dma_free(void *cookie, void *addr, size_t size) | |
| { | |
| dma_man_t *dma = cookie; | |
| dma_alloc_t *alloc = (dma_alloc_t *)vspace_get_cookie(&dma->vspace, addr); | |
| assert(alloc); | |
| assert(alloc->base == addr); | |
| int num_pages = BIT(alloc->ut.size_bits) / PAGE_SIZE_4K; | |
| for (int i = 0; i < num_pages; i++) { | |
| cspacepath_t path; | |
| seL4_CPtr frame = vspace_get_cap(&dma->vspace, addr + i * PAGE_SIZE_4K); | |
| vspace_unmap_pages(&dma->vspace, addr + i * PAGE_SIZE_4K, 1, PAGE_BITS_4K, NULL); | |
| vka_cspace_make_path(&dma->vka, frame, &path); | |
| vka_cnode_delete(&path); | |
| vka_cspace_free(&dma->vka, frame); | |
| } | |
| vka_free_object(&dma->vka, &alloc->ut); | |
| free(alloc); | |
| } | |
| static uintptr_t dma_pin(void *cookie, void *addr, size_t size) | |
| { | |
| dma_man_t *dma = cookie; | |
| dma_alloc_t *alloc = (dma_alloc_t *)vspace_get_cookie(&dma->vspace, addr); | |
| if (!alloc) { | |
| return 0; | |
| } | |
| uint32_t diff = addr - alloc->base; | |
| return alloc->paddr + diff; | |
| } | |
| static void *dma_alloc(void *cookie, size_t size, int align, int cached, ps_mem_flags_t flags) | |
| { | |
| dma_man_t *dma = cookie; | |
| cspacepath_t *frames = NULL; | |
| reservation_t res = {NULL}; | |
| dma_alloc_t *alloc = NULL; | |
| unsigned int num_frames = 0; | |
| void *base = NULL; | |
| /* We align to the 4K boundary, but do not support more */ | |
| if (align > PAGE_SIZE_4K) { | |
| return NULL; | |
| } | |
| /* Round up to the next page size */ | |
| size = ROUND_UP(size, PAGE_SIZE_4K); | |
| /* Then round up to the next power of 2 size. This is because untypeds are allocated | |
| * in powers of 2 */ | |
| size_t size_bits = LOG_BASE_2(size); | |
| if (BIT(size_bits) != size) { | |
| size_bits++; | |
| } | |
| size = BIT(size_bits); | |
| /* Allocate an untyped */ | |
| vka_object_t ut; | |
| int error = vka_alloc_untyped(&dma->vka, size_bits, &ut); | |
| if (error) { | |
| ZF_LOGE("Failed to allocate untyped of size %zu", size_bits); | |
| return NULL; | |
| } | |
| /* Get the physical address */ | |
| uintptr_t paddr = vka_utspace_paddr(&dma->vka, ut.ut, seL4_UntypedObject, size_bits); | |
| if (paddr == VKA_NO_PADDR) { | |
| ZF_LOGE("Allocated untyped has no physical address"); | |
| goto handle_error; | |
| } | |
| /* Allocate all the frames */ | |
| num_frames = size / PAGE_SIZE_4K; | |
| frames = calloc(num_frames, sizeof(cspacepath_t)); | |
| if (!frames) { | |
| goto handle_error; | |
| } | |
| for (unsigned i = 0; i < num_frames; i++) { | |
| error = vka_cspace_alloc_path(&dma->vka, &frames[i]); | |
| if (error) { | |
| goto handle_error; | |
| } | |
| error = seL4_Untyped_Retype(ut.cptr, kobject_get_type(KOBJECT_FRAME, PAGE_BITS_4K), size_bits, frames[i].root, | |
| frames[i].dest, frames[i].destDepth, frames[i].offset, 1); | |
| if (error != seL4_NoError) { | |
| goto handle_error; | |
| } | |
| } | |
| /* Grab a reservation */ | |
| res = vspace_reserve_range(&dma->vspace, size, seL4_AllRights, cached, &base); | |
| if (!res.res) { | |
| ZF_LOGE("Failed to reserve"); | |
| return NULL; | |
| } | |
| alloc = malloc(sizeof(*alloc)); | |
| if (alloc == NULL) { | |
| goto handle_error; | |
| } | |
| alloc->base = base; | |
| alloc->ut = ut; | |
| alloc->paddr = paddr; | |
| /* Map in all the pages */ | |
| for (unsigned i = 0; i < num_frames; i++) { | |
| error = vspace_map_pages_at_vaddr(&dma->vspace, &frames[i].capPtr, (uintptr_t *)&alloc, base + i * PAGE_SIZE_4K, 1, | |
| PAGE_BITS_4K, res); | |
| if (error) { | |
| goto handle_error; | |
| } | |
| } | |
| /* no longer need the reservation */ | |
| vspace_free_reservation(&dma->vspace, res); | |
| return base; | |
| handle_error: | |
| if (alloc) { | |
| free(alloc); | |
| } | |
| if (res.res) { | |
| vspace_unmap_pages(&dma->vspace, base, num_frames, PAGE_BITS_4K, NULL); | |
| vspace_free_reservation(&dma->vspace, res); | |
| } | |
| if (frames) { | |
| for (int i = 0; i < num_frames; i++) { | |
| if (frames[i].capPtr) { | |
| vka_cnode_delete(&frames[i]); | |
| vka_cspace_free(&dma->vka, frames[i].capPtr); | |
| } | |
| } | |
| free(frames); | |
| } | |
| vka_free_object(&dma->vka, &ut); | |
| return NULL; | |
| } | |
| static void dma_unpin(void *cookie, void *addr, size_t size) | |
| { | |
| } | |
| static void dma_cache_op(void *cookie, void *addr, size_t size, dma_cache_op_t op) | |
| { | |
| dma_man_t *dma = cookie; | |
| seL4_CPtr root = vspace_get_root(&dma->vspace); | |
| uintptr_t end = (uintptr_t)addr + size; | |
| uintptr_t cur = (uintptr_t)addr; | |
| while (cur < end) { | |
| uintptr_t top = ROUND_UP(cur + 1, PAGE_SIZE_4K); | |
| if (top > end) { | |
| top = end; | |
| } | |
| switch (op) { | |
| case DMA_CACHE_OP_CLEAN: | |
| seL4_ARCH_PageDirectory_Clean_Data(root, (seL4_Word)cur, (seL4_Word)top); | |
| break; | |
| case DMA_CACHE_OP_INVALIDATE: | |
| seL4_ARCH_PageDirectory_Invalidate_Data(root, (seL4_Word)cur, (seL4_Word)top); | |
| break; | |
| case DMA_CACHE_OP_CLEAN_INVALIDATE: | |
| seL4_ARCH_PageDirectory_CleanInvalidate_Data(root, (seL4_Word)cur, (seL4_Word)top); | |
| break; | |
| } | |
| cur = top; | |
| } | |
| } | |
| int sel4utils_new_page_dma_alloc(vka_t *vka, vspace_t *vspace, ps_dma_man_t *dma_man) | |
| { | |
| dma_man_t *dma = calloc(1, sizeof(*dma)); | |
| if (!dma) { | |
| return -1; | |
| } | |
| dma->vka = *vka; | |
| dma->vspace = *vspace; | |
| dma_man->cookie = dma; | |
| dma_man->dma_alloc_fn = dma_alloc; | |
| dma_man->dma_free_fn = dma_free; | |
| dma_man->dma_pin_fn = dma_pin; | |
| dma_man->dma_unpin_fn = dma_unpin; | |
| dma_man->dma_cache_op_fn = dma_cache_op; | |
| return 0; | |
| } |