blob: 40fafd42d7b82eb38fada700ba5b76aa7a079603 [file] [log] [blame]
/*
* Copyright 2017, Data61, CSIRO (ABN 41 687 119 230)
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#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;
}