/* * SPDX-License-Identifier: MIT * * Copyright (C) 2013-2024 OpenMV, LLC. * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. * * A simple dynamic memory allocator for DMA buffers, that can allocate memory * in multiple domains based on the board configuration. */ #include #include #include #include "py/runtime.h" #include "py/mphal.h" #include "dma_alloc.h" #include "omv_boardconfig.h" #include "omv_common.h" #if OMV_DMA_ALLOC typedef union block block_t; union block { struct { uint32_t used; // Set to 1/true if the block is used. uint32_t size; // Size of the memory block in bytes. void *periph; // The peripheral that owns this block. block_t *next; // Pointer to the the next block in chain. }; // Make sure that blocks are always aligned to 32 bytes. uint8_t alignment[32]; };// Actual block memory starts right after the block header. // Note block sizes can Not be adjusted after the first allocation. // When a block is allocated the first time, the block size is fixed to the requested // memory size, when the block is free'd and reused again the same block size is used. // Use multiples of the following min size when allocating blocks to avoid fragmentation. #define MIN_BLOCK_SIZE (128) // Get pointer to the block's memory. #define BLOCK_TO_PTR(block) \ ((void *) (block + 1)) // Get pointer to the block's header from memory pointer. #define PTR_TO_BLOCK(ptr) \ (((block_t *) ptr) - 1) // Initialize the first block in a block chain. #define INIT_BLOCK_CHAIN(D) \ ({ \ block_t *block = (block_t *) DMA_MEMORY_##D; \ block->used = false; \ block->size = OMV_DMA_ALLOC_ ##D ##_SIZE - sizeof(block_t); \ block->next = NULL; \ block->periph = NULL; \ }) #if defined(OMV_DMA_ALLOC_D1_SIZE) static uint8_t OMV_ATTR_SECTION(OMV_ATTR_ALIGNED(DMA_MEMORY_D1[OMV_DMA_ALLOC_D1_SIZE], 16), ".d1_dma_buffer"); #endif #if defined(OMV_DMA_ALLOC_D2_SIZE) static uint8_t OMV_ATTR_SECTION(OMV_ATTR_ALIGNED(DMA_MEMORY_D2[OMV_DMA_ALLOC_D2_SIZE], 16), ".d2_dma_buffer"); #endif #if defined(OMV_DMA_ALLOC_D3_SIZE) static uint8_t OMV_ATTR_SECTION(OMV_ATTR_ALIGNED(DMA_MEMORY_D3[OMV_DMA_ALLOC_D3_SIZE], 16), ".d3_dma_buffer"); #endif NORETURN static void dma_alloc_fail(uint32_t size) { mp_raise_msg_varg(&mp_type_MemoryError, MP_ERROR_TEXT("DMA buffer allocation failed while allocating %u bytes"), (uint) size); } void dma_alloc_init0() { // Initialize the first block in each DMA buffer. #if defined(OMV_DMA_ALLOC_D1_SIZE) INIT_BLOCK_CHAIN(D1); #endif #if defined(OMV_DMA_ALLOC_D2_SIZE) INIT_BLOCK_CHAIN(D2); #endif #if defined(OMV_DMA_ALLOC_D3_SIZE) INIT_BLOCK_CHAIN(D3); #endif } static int periph_to_domain(void *periph) { // Mask the lower 16 bits to get the bus base address uint32_t base = ((uint32_t) periph) & 0xFFFF0000; switch (base) { #if defined(OMV_DMA_ALLOC_D1_SIZE) case D1_APB1PERIPH_BASE: case D1_AHB1PERIPH_BASE: return 1; #endif #if defined(OMV_DMA_ALLOC_D2_SIZE) case D2_APB1PERIPH_BASE: case D2_APB2PERIPH_BASE: case D2_AHB1PERIPH_BASE: case D2_AHB2PERIPH_BASE: return 2; #endif #if defined(OMV_DMA_ALLOC_D3_SIZE) case D3_APB1PERIPH_BASE: case D3_AHB1PERIPH_BASE: return 3; #endif default: return -1; } } static void block_chain_from_domain(void *periph, void **start, void **end) { // Find the domain this peripheral belongs to. int domain = periph_to_domain(periph); switch (domain) { #if defined(OMV_DMA_ALLOC_D1_SIZE) case 1: *start = DMA_MEMORY_D1; *end = (DMA_MEMORY_D1 + OMV_DMA_ALLOC_D1_SIZE); break; #endif #if defined(OMV_DMA_ALLOC_D2_SIZE) case 2: *start = DMA_MEMORY_D2; *end = (DMA_MEMORY_D2 + OMV_DMA_ALLOC_D2_SIZE); break; #endif #if defined(OMV_DMA_ALLOC_D3_SIZE) case 3: *start = DMA_MEMORY_D3; *end = (DMA_MEMORY_D3 + OMV_DMA_ALLOC_D3_SIZE); break; #endif default: mp_raise_msg_varg(&mp_type_MemoryError, MP_ERROR_TEXT("DMA buffer allocation failed. No DMA buffer reserved for peripheral 0x%x"), (uint) periph); break; } } void *dma_alloc(uint32_t size, void *periph) { if (size == 0) { return NULL; } // Round up the alloc memory size to multiples of the minimum block size. size = ((size + MIN_BLOCK_SIZE - 1) / MIN_BLOCK_SIZE) * MIN_BLOCK_SIZE; // Find the start and end of the block chain this peripheral belongs to. void *block_chain_start, *block_chain_end; block_chain_from_domain(periph, &block_chain_start, &block_chain_end); block_t *block; for (block = block_chain_start; block != NULL; block = block->next) { if (!block->used && block->size >= size) { // Found a free block that's large enough. break; } if (block->next == NULL) { // last block // Allocate a new block if there's enough memory left. void *block_end = BLOCK_TO_PTR(block) + block->size; uint32_t freemem = block_chain_end - block_end; if (freemem >= (size + sizeof(block_t))) { block_t *new_block = (block_t *) block_end; new_block->next = NULL; // This is the new last block. block->next = new_block; // Link new block to the old one. block = new_block; // Return the new block. break; } } } if (block == NULL) { dma_alloc_fail(size); } block->used = true; // Mark block as used. block->periph = periph; if (block->next == NULL) { // Adjust/set the block size only if it's newly allocated or if it's the first and only // block in the chain, otherwise if it's a free'd/reused block and the size is adjusted, // the block will shrink and it will Not be possible to increase the block size again. block->size = size; } #ifndef NDEBUG printf("alloc DMA buffer at 0x%p size: %lu next in chain: 0x%p for periph 0x%p in domain %d\n", block, block->size, block->next, block->periph, periph_to_domain(block->periph)); #endif return BLOCK_TO_PTR(block); } void dma_alloc_free(void *ptr) { if (ptr != NULL) { block_t *block = PTR_TO_BLOCK(ptr); #ifndef NDEBUG printf("free DMA buffer at 0x%p size: %lu next in chain: 0x%p for periph 0x%p in domain %d\n", block, block->size, block->next, block->periph, periph_to_domain(block->periph)); #endif // Just mark the block as unused. block->used = false; block->periph = NULL; } } #endif