blob: f88abefceb7a7cad5132c6e5f8e94f9b44745cd8 [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>
#if defined(CONFIG_IOMMU)
#include <sel4utils/iommu_dma.h>
#include <sel4utils/vspace.h>
#include <sel4utils/vspace_internal.h>
#include <stdlib.h>
#include <vka/capops.h>
#include <string.h>
#include <utils/zf_log.h>
typedef struct dma_man {
vka_t vka;
vspace_t vspace;
int num_iospaces;
vspace_t *iospaces;
sel4utils_alloc_data_t *iospace_data;
} dma_man_t;
static void unmap_range(dma_man_t *dma, uintptr_t addr, size_t size)
{
uintptr_t start = ROUND_DOWN(addr, PAGE_SIZE_4K);
uintptr_t end = addr + size;
for (uintptr_t addr = start; addr < end; addr += PAGE_SIZE_4K) {
for (int i = 0; i < dma->num_iospaces; i++) {
uintptr_t *cookie = (uintptr_t *)vspace_get_cookie(dma->iospaces + i, (void *)addr);
assert(cookie);
(*cookie)--;
if (*cookie == 0) {
seL4_CPtr page = vspace_get_cap(dma->iospaces + i, (void *)addr);
cspacepath_t page_path;
assert(page);
vspace_unmap_pages(dma->iospaces + i, (void *)addr, 1, seL4_PageBits, NULL);
vka_cspace_make_path(&dma->vka, page, &page_path);
vka_cnode_delete(&page_path);
vka_cspace_free(&dma->vka, page);
free(cookie);
}
}
}
}
int sel4utils_iommu_dma_alloc_iospace(void *cookie, void *vaddr, size_t size)
{
dma_man_t *dma = (dma_man_t *)cookie;
int error;
/* for each page duplicate and map it into all the iospaces */
uintptr_t start = ROUND_DOWN((uintptr_t)vaddr, PAGE_SIZE_4K);
uintptr_t end = (uintptr_t)vaddr + size;
seL4_CPtr last_page = 0;
for (uintptr_t addr = start; addr < end; addr += PAGE_SIZE_4K) {
cspacepath_t page_path;
seL4_CPtr page = vspace_get_cap(&dma->vspace, (void *)addr);
if (!page) {
ZF_LOGE("Failed to retrieve frame cap for malloc region. "
"Is your malloc backed by the correct vspace? "
"If you allocated your own buffer, does the dma manager's vspace "
"know about the caps to the frames that back the buffer?");
unmap_range(dma, start, addr + 1);
return -1;
}
if (page == last_page) {
ZF_LOGE("Found the same frame two pages in a row. We only support 4K mappings");
unmap_range(dma, start, addr + 1);
return -1;
}
last_page = page;
vka_cspace_make_path(&dma->vka, page, &page_path);
/* work out the size of this page */
for (int i = 0; i < dma->num_iospaces; i++) {
/* see if its already mapped */
uintptr_t *cookie = (uintptr_t *)vspace_get_cookie(dma->iospaces + i, (void *)addr);
if (cookie) {
/* increment the counter */
(*cookie)++;
} else {
cspacepath_t copy_path;
/* allocate slot for the cap */
error = vka_cspace_alloc_path(&dma->vka, &copy_path);
if (error) {
ZF_LOGE("Failed to allocate slot");
unmap_range(dma, start, addr + 1);
return -1;
}
/* copy the cap */
error = vka_cnode_copy(&copy_path, &page_path, seL4_AllRights);
if (error) {
ZF_LOGE("Failed to copy frame cap");
vka_cspace_free(&dma->vka, copy_path.capPtr);
unmap_range(dma, start, addr + 1);
return -1;
}
/* now map it in */
reservation_t res = vspace_reserve_range_at(dma->iospaces + i, (void *)addr, PAGE_SIZE_4K, seL4_AllRights, 1);
if (!res.res) {
ZF_LOGE("Failed to create a reservation");
vka_cnode_delete(&copy_path);
vka_cspace_free(&dma->vka, copy_path.capPtr);
unmap_range(dma, start, addr + 1);
return -1;
}
cookie = malloc(sizeof(*cookie));
if (!cookie) {
ZF_LOGE("Failed to malloc %zu bytes", sizeof(*cookie));
vspace_free_reservation(dma->iospaces + i, res);
vka_cnode_delete(&copy_path);
vka_cspace_free(&dma->vka, copy_path.capPtr);
unmap_range(dma, start, addr + 1);
return -1;
}
*cookie = 1;
error = vspace_map_pages_at_vaddr(dma->iospaces + i, &copy_path.capPtr, (uintptr_t *)&cookie, (void *)addr, 1,
seL4_PageBits, res);
if (error) {
ZF_LOGE("Failed to map frame into iospace");
free(cookie);
vspace_free_reservation(dma->iospaces + i, res);
vka_cnode_delete(&copy_path);
vka_cspace_free(&dma->vka, copy_path.capPtr);
unmap_range(dma, start, addr + 1);
return -1;
}
vspace_free_reservation(dma->iospaces + i, res);
}
}
}
return 0;
}
static void *dma_alloc(void *cookie, size_t size, int align, int cached, ps_mem_flags_t flags)
{
int error;
if (cached || flags != PS_MEM_NORMAL) {
/* Going to ignore flags */
void *ret;
error = posix_memalign(&ret, align, size);
if (error) {
return NULL;
}
error = sel4utils_iommu_dma_alloc_iospace(cookie, ret, size);
if (error) {
free(ret);
return NULL;
}
return ret;
} else {
/* do not support uncached memory */
ZF_LOGE("Only support cached normal memory");
return NULL;
}
}
static void dma_free(void *cookie, void *addr, size_t size)
{
dma_man_t *dma = cookie;
unmap_range(dma, (uintptr_t)addr, size);
free(addr);
}
static uintptr_t dma_pin(void *cookie, void *addr, size_t size)
{
return (uintptr_t)addr;
}
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)
{
/* I have no way of knowing what this function should do on an architecture
* that is both non cache coherent with respect to DMA, and has an IOMMU.
* When there is a working implementation of an arm IOMMU this function
* could be implemented */
#ifdef CONFIG_ARCH_ARM
assert(!"not implemented");
#endif
}
int sel4utils_make_iommu_dma_alloc(vka_t *vka, vspace_t *vspace, ps_dma_man_t *dma_man, unsigned int num_iospaces,
seL4_CPtr *iospaces)
{
dma_man_t *dma = calloc(1, sizeof(*dma));
if (!dma) {
return -1;
}
dma->num_iospaces = num_iospaces;
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;
dma->iospaces = malloc(sizeof(vspace_t) * num_iospaces);
if (!dma->iospaces) {
goto error;
}
dma->iospace_data = malloc(sizeof(sel4utils_alloc_data_t) * num_iospaces);
if (!dma->iospace_data) {
goto error;
}
for (unsigned int i = 0; i < num_iospaces; i++) {
int err = sel4utils_get_vspace_with_map(&dma->vspace, dma->iospaces + i, dma->iospace_data + i, &dma->vka, iospaces[i],
NULL, NULL, sel4utils_map_page_iommu);
if (err) {
for (unsigned int j = 0; j < i; j++) {
vspace_tear_down(dma->iospaces + i, &dma->vka);
}
goto error;
}
}
return 0;
error:
if (dma->iospace_data) {
free(dma->iospace_data);
}
if (dma->iospaces) {
free(dma->iospaces);
}
if (dma) {
free(dma);
}
return -1;
}
#endif /* CONFIG_IOMMU */