| /* |
| * 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) |
| */ |
| /* Author: alex.kroh@nicta.com.au */ |
| |
| #include <dma/dma.h> |
| #include <assert.h> |
| #include <stdio.h> |
| #include <stdlib.h> |
| |
| #include <autoconf.h> |
| |
| //#define DMA_DEBUG |
| #ifdef DMA_DEBUG |
| #define dprintf(...) do{ \ |
| printf("DMA| "); \ |
| printf(__VA_ARGS__); \ |
| }while(0) |
| #else |
| #define dprintf(...) do{}while(0) |
| #endif |
| |
| #if defined(CONFIG_PLAT_IMX6) || defined(IMX6) |
| #define DMA_MINALIGN_BYTES 32 |
| #elif defined(CONFIG_PLAT_EXYNOS4) |
| #define DMA_MINALIGN_BYTES 32 |
| #elif defined(CONFIG_PLAT_EXYNOS5) |
| #define DMA_MINALIGN_BYTES 32 |
| #else |
| #warning Unknown platform. DMA alignment defaulting to 32 bytes. |
| #define DMA_MINALIGN_BYTES 32 |
| #endif |
| |
| #define _malloc malloc |
| #define _free free |
| |
| /* dma_mem_t flag bit that signals that the memory is in use */ |
| #define DMFLAG_ALLOCATED 1 |
| |
| /* Linked list of descriptors */ |
| struct dma_memd_node { |
| /* Description of this memory chunk */ |
| struct dma_mem_descriptor desc; |
| /* Number of frames in this region */ |
| int nframes; |
| /* Caps to the underlying frames */ |
| void **alloc_cookies; |
| /* Head of linked list of regions */ |
| dma_mem_t dma_mem_head; |
| /* Chain */ |
| struct dma_memd_node *next; |
| }; |
| |
| /* Linked list of memory regions */ |
| struct dma_mem { |
| /* Offset within the described memory */ |
| uintptr_t offset; |
| /* Flags */ |
| int flags; |
| /* Parent node */ |
| struct dma_memd_node *node; |
| /* Chain */ |
| dma_mem_t next; |
| }; |
| |
| struct dma_allocator { |
| dma_morecore_fn morecore; |
| struct dma_memd_node *head; |
| }; |
| |
| |
| /*** Helpers ***/ |
| |
| static inline int _is_free(dma_mem_t m) |
| { |
| return !(m->flags & DMFLAG_ALLOCATED); |
| } |
| |
| static inline size_t _node_size(struct dma_memd_node *n) |
| { |
| return n->nframes << n->desc.size_bits; |
| } |
| |
| static inline size_t _mem_size(dma_mem_t m) |
| { |
| struct dma_memd_node *n = m->node; |
| size_t s; |
| if (m->next == NULL) { |
| s = _node_size(n);; |
| } else { |
| s = m->next->offset; |
| } |
| return s -= m->offset; |
| } |
| |
| /* @pre the offset must be contained within the node provided node */ |
| static inline dma_mem_t _find_mem(struct dma_memd_node *n, uintptr_t offset) |
| { |
| dma_mem_t m; |
| assert(n); |
| assert(offset < _node_size(n)); |
| m = n->dma_mem_head; |
| while (m->next != NULL && offset >= m->next->offset) { |
| m = m->next; |
| } |
| return m; |
| } |
| |
| static void _mem_compact(dma_mem_t m) |
| { |
| while (m->next != NULL && _is_free(m->next)) { |
| dma_mem_t compact = m->next; |
| dprintf("Compacting:\n"); |
| m->next = compact->next; |
| _free(compact); |
| } |
| } |
| |
| /*** Debug ***/ |
| |
| static void print_dma_mem(dma_mem_t m, const char *prefix) |
| { |
| dprintf("%s{p0x%08x, v0x%08x, s0x%x %s}\n", prefix, |
| (uint32_t)dma_paddr(m), (uint32_t)dma_vaddr(m), _mem_size(m), |
| _is_free(m) ? "FREE" : "USED"); |
| } |
| |
| static void print_dma_node(struct dma_memd_node *n) |
| { |
| dma_mem_t m; |
| dprintf("NODE:\n"); |
| for (m = n->dma_mem_head; m != NULL; m = m->next) { |
| print_dma_mem(m, ">"); |
| } |
| } |
| |
| static void print_dma_allocator(struct dma_allocator *a) |
| { |
| struct dma_memd_node *n; |
| dprintf("ALLOC:\n"); |
| for (n = a->head; n != NULL; n = n->next) { |
| print_dma_node(n); |
| } |
| } |
| |
| /*** Interface ***/ |
| |
| |
| static struct dma_memd_node *do_dma_provide_mem(struct dma_allocator *allocator, |
| struct dma_mem_descriptor *dma_desc) |
| { |
| struct dma_memd_node *n; |
| dma_mem_t m; |
| |
| /* The memory size must be sane */ |
| assert(dma_desc->size_bits > 0 && dma_desc->size_bits < 32); |
| /* Allocate some objects */ |
| m = (dma_mem_t)_malloc(sizeof(*m)); |
| if (m == NULL) { |
| return NULL; |
| } |
| /* We do not attempt to tack this region onto an existing node */ |
| n = (struct dma_memd_node *)_malloc(sizeof(*n)); |
| if (n == NULL) { |
| _free(m); |
| return NULL; |
| } |
| /* Initialise free memory */ |
| m->offset = 0; |
| m->flags = 0; |
| m->next = NULL; |
| m->node = n; |
| /* Initialise the pool node */ |
| n->desc = *dma_desc; |
| n->dma_mem_head = m; |
| n->next = allocator->head; |
| n->nframes = 1; |
| n->alloc_cookies = _malloc(sizeof(*n->alloc_cookies) * n->nframes); |
| if (n->alloc_cookies == NULL) { |
| _free(n); |
| _free(m); |
| return NULL; |
| } |
| n->alloc_cookies[0] = dma_desc->alloc_cookie; |
| /* Add the node to the allocator */ |
| allocator->head = n; |
| |
| dprintf("DMA memory provided\n"); |
| print_dma_allocator(allocator); |
| return n; |
| } |
| |
| |
| |
| struct dma_allocator * |
| dma_allocator_init(dma_morecore_fn morecore) |
| { |
| struct dma_allocator *alloc; |
| alloc = (struct dma_allocator *)_malloc(sizeof(*alloc)); |
| if (alloc == NULL) { |
| return NULL; |
| } |
| alloc->morecore = morecore; |
| alloc->head = NULL; |
| return alloc; |
| } |
| |
| |
| int dma_provide_mem(struct dma_allocator *allocator, |
| struct dma_mem_descriptor dma_desc) |
| { |
| struct dma_memd_node *n; |
| n = do_dma_provide_mem(allocator, &dma_desc); |
| return n == NULL; |
| } |
| |
| static dma_mem_t dma_memd_alloc(struct dma_memd_node *n, size_t size, int align) |
| { |
| dma_mem_t m; |
| dprintf("Allocating 0x%x aligned to 0x%x\n", size, align); |
| for (m = n->dma_mem_head; m != NULL; m = m->next) { |
| if (_is_free(m)) { |
| size_t mem_size; |
| int mem_align; |
| paddr_t paddr; |
| |
| _mem_compact(m); |
| |
| /* Find the region size and alignment */ |
| paddr = dma_paddr(m); |
| mem_align = align - (paddr % align); |
| if (mem_align == align) { |
| mem_align = 0; |
| } |
| mem_size = _mem_size(m) - mem_align; |
| /* Check for overflow in subtraction and check our size */ |
| if (_mem_size(m) > mem_align && mem_size >= size) { |
| m->offset += mem_align; |
| /* Split off the free memory if possible */ |
| if (mem_size > size) { |
| dma_mem_t split; |
| split = (dma_mem_t)_malloc(sizeof(*split)); |
| if (split != NULL) { |
| split->offset = m->offset + size; |
| split->flags = m->flags; |
| split->next = m->next; |
| split->node = m->node; |
| |
| m->next = split; |
| } else { |
| /* Just use the over size region... */ |
| } |
| } |
| m->flags |= DMFLAG_ALLOCATED; |
| return m; |
| } |
| } |
| } |
| return NULL; |
| } |
| |
| vaddr_t dma_alloc(struct dma_allocator *allocator, size_t size, int align, |
| enum dma_flags flags, dma_mem_t *ret_mem) |
| { |
| int cached; |
| struct dma_memd_node *n; |
| assert(allocator); |
| |
| if (align < DMA_MINALIGN_BYTES) { |
| align = DMA_MINALIGN_BYTES; |
| } |
| /* TODO access patterns and cachability */ |
| cached = 0; |
| (void)flags; |
| |
| /* Cycle through nodes looking for free memory */ |
| for (n = allocator->head; n != NULL; n = n->next) { |
| dma_mem_t m; |
| /* TODO some tricky stuff first to see if an allocation from |
| * this node is appropriate (access patterns/cachability) */ |
| m = dma_memd_alloc(n, size, align); |
| if (m != NULL) { |
| dprintf("DMA mem allocated\n"); |
| print_dma_mem(m, "-"); |
| print_dma_allocator(allocator); |
| if (ret_mem) { |
| *ret_mem = m; |
| } |
| return dma_vaddr(m); |
| } |
| } |
| |
| /* Out of memory! Allocate more if we have the ability */ |
| if (allocator->morecore) { |
| struct dma_mem_descriptor dma_desc; |
| struct dma_memd_node *n; |
| dma_mem_t m; |
| int err; |
| dprintf("Morecore called for %d bytes\n", size); |
| /* Grab more core */ |
| err = allocator->morecore(size, cached, &dma_desc); |
| if (err) { |
| return NULL; |
| } |
| /* Add the memory to the allocator */ |
| n = do_dma_provide_mem(allocator, &dma_desc); |
| if (n == NULL) { |
| return NULL; |
| } |
| /* Perform the allocation */ |
| m = dma_memd_alloc(n, size, align); |
| assert(m); |
| if (m == NULL) { |
| return NULL; |
| } |
| /* Success */ |
| dprintf("DMA mem allocated\n"); |
| print_dma_mem(m, "-"); |
| print_dma_allocator(allocator); |
| if (ret_mem) { |
| *ret_mem = m; |
| } |
| return dma_vaddr(m); |
| } |
| dprintf("Failed to allocate DMA memory\n"); |
| return NULL; |
| } |
| |
| int dma_reclaim_mem(struct dma_allocator *allocator, |
| struct dma_mem_descriptor *dma_desc) |
| { |
| struct dma_memd_node *n; |
| struct dma_memd_node **nptr = &allocator->head; |
| for (n = allocator->head; n != NULL; n = n->next) { |
| dma_mem_t m = n->dma_mem_head; |
| _mem_compact(m); |
| if (_is_free(m) && !m->next) { |
| *dma_desc = n->desc; |
| /* Currently there is not support for compacted nodes */ |
| assert(n->nframes == 1); |
| dma_desc->alloc_cookie = n->alloc_cookies[0]; |
| /* Remove the node and free memory */ |
| *nptr = n->next; |
| _free(m); |
| _free(n->alloc_cookies); |
| _free(n); |
| return 0; |
| } |
| nptr = &n->next; |
| } |
| return -1; |
| } |
| |
| void dma_free(dma_mem_t m) |
| { |
| if (m) { |
| m->flags &= ~DMFLAG_ALLOCATED; |
| _mem_compact(m); |
| } |
| } |
| |
| /*** Address translation ***/ |
| |
| vaddr_t dma_vaddr(dma_mem_t m) |
| { |
| if (m) { |
| assert(m->node); |
| return (vaddr_t)((uintptr_t)m->node->desc.vaddr + m->offset); |
| } else { |
| return (vaddr_t)0; |
| } |
| } |
| |
| paddr_t dma_paddr(dma_mem_t m) |
| { |
| if (m) { |
| assert(m->node); |
| return m->node->desc.paddr + m->offset; |
| } else { |
| return (paddr_t)0; |
| } |
| } |
| |
| dma_mem_t dma_plookup(struct dma_allocator *dma_allocator, paddr_t paddr) |
| { |
| struct dma_memd_node *n; |
| uintptr_t offs; |
| /* Search the list for the associated node */ |
| for (n = dma_allocator->head; n != NULL; n = n->next) { |
| if (n->desc.paddr <= paddr && n->desc.paddr < _node_size(n)) { |
| break; |
| } |
| } |
| /* Search the mem list for the assocated dma_mem_t */ |
| if (n == NULL) { |
| return NULL; |
| } else { |
| offs = paddr - n->desc.paddr; |
| return _find_mem(n, offs); |
| } |
| } |
| |
| dma_mem_t dma_vlookup(struct dma_allocator *dma_allocator, vaddr_t vaddr) |
| { |
| struct dma_memd_node *n; |
| uintptr_t offs; |
| /* Search the list for the associated node */ |
| for (n = dma_allocator->head; n != NULL; n = n->next) { |
| uintptr_t vn = (uintptr_t)n->desc.vaddr; |
| uintptr_t vm = (uintptr_t)vaddr; |
| if (vn <= vm && vm < vn + _node_size(n)) { |
| break; |
| } |
| } |
| /* Search the mem list for the assocated dma_mem_t */ |
| if (n == NULL) { |
| return NULL; |
| } else { |
| offs = (uintptr_t)vaddr - (uintptr_t)n->desc.vaddr; |
| return _find_mem(n, offs); |
| } |
| } |
| |
| |
| |
| /*** Cache ops ***/ |
| |
| void dma_clean(dma_mem_t m, vaddr_t vstart, vaddr_t vend) |
| { |
| (void)m; |
| (void)vstart; |
| (void)vend; |
| } |
| |
| |
| void dma_invalidate(dma_mem_t m, vaddr_t vstart, vaddr_t vend) |
| { |
| (void)m; |
| (void)vstart; |
| (void)vend; |
| } |
| |
| void dma_cleaninvalidate(dma_mem_t m, vaddr_t vstart, vaddr_t vend) |
| { |
| (void)m; |
| (void)vstart; |
| (void)vend; |
| } |
| |
| /******** libplatsupport adapter ********/ |
| |
| static void *dma_dma_alloc(void *cookie, size_t size, int align, int cached, ps_mem_flags_t flags) |
| { |
| struct dma_allocator *dalloc; |
| vaddr_t vaddr; |
| enum dma_flags dma_flags; |
| assert(cookie); |
| dalloc = (struct dma_allocator *)cookie; |
| |
| if (cached) { |
| dma_flags = DMAF_COHERENT; |
| } else { |
| switch (flags) { |
| case PS_MEM_NORMAL: |
| dma_flags = DMAF_HRW; |
| break; |
| case PS_MEM_HR: |
| dma_flags = DMAF_HR; |
| break; |
| case PS_MEM_HW: |
| dma_flags = DMAF_HW; |
| break; |
| default: |
| dma_flags = DMAF_COHERENT; |
| } |
| } |
| |
| vaddr = dma_alloc(dalloc, size, align, dma_flags, NULL); |
| return vaddr; |
| } |
| |
| static void dma_dma_free(void *cookie, void *addr, size_t size) |
| { |
| struct dma_allocator *dalloc; |
| assert(cookie); |
| dalloc = (struct dma_allocator *)cookie; |
| dma_free(dma_vlookup(dalloc, addr)); |
| } |
| |
| |
| static uintptr_t dma_dma_pin(void *cookie, void *addr, size_t size) |
| { |
| struct dma_allocator *dalloc; |
| assert(cookie); |
| /* DMA memory is pinned when allocated */ |
| dalloc = (struct dma_allocator *)cookie; |
| return dma_paddr(dma_vlookup(dalloc, addr)); |
| } |
| |
| static void dma_dma_unpin(void *cookie UNUSED, void *addr UNUSED, size_t size UNUSED) |
| { |
| /* DMA memory is unpinned when freed */ |
| } |
| |
| |
| int dma_dmaman_init(dma_morecore_fn morecore, ps_dma_cache_op_fn_t cache_ops, |
| ps_dma_man_t *dma_man) |
| { |
| struct dma_allocator *dalloc; |
| assert(dma_man); |
| |
| dalloc = dma_allocator_init(morecore); |
| if (dalloc != NULL) { |
| dma_man->cookie = dalloc; |
| dma_man->dma_cache_op_fn = cache_ops; |
| dma_man->dma_alloc_fn = &dma_dma_alloc; |
| dma_man->dma_free_fn = &dma_dma_free; |
| dma_man->dma_pin_fn = &dma_dma_pin; |
| dma_man->dma_unpin_fn = &dma_dma_unpin; |
| return 0; |
| } else { |
| return -1; |
| } |
| } |
| |
| |