diff --git a/arch/xtensa/src/esp32s3/Kconfig b/arch/xtensa/src/esp32s3/Kconfig index e51a49fb23..a4a6f20605 100644 --- a/arch/xtensa/src/esp32s3/Kconfig +++ b/arch/xtensa/src/esp32s3/Kconfig @@ -919,6 +919,53 @@ config ESP32S3_RTC_HEAP select ARCH_HAVE_EXTRA_HEAPS default n +config ESP32S3_SPIRAM_MAP + bool "Remap the size and offset of SPIRAM virtual address" + depends on ESP32S3_SPIRAM + default n + +config ESP32S3_SPIRAM_VADDR_OFFSET + hex "Map virtual address offset" + default 0x30000 + depends on ESP32S3_SPIRAM_MAP + range 0 0x1FF0000 + +config ESP32S3_SPIRAM_VADDR_MAP_SIZE + hex "Map virtual address size" + default 0x400000 + depends on ESP32S3_SPIRAM_MAP + range 0x10000 0x2000000 + +config ESP32S3_SPIRAM_BANKSWITCH_ENABLE + bool "Enable bank switching for external RAM" + default n + depends on ESP32S3_SPIRAM_MAP + ---help--- + The MMU table of ESP32-S3 is up to 512, and the page size of each MMU + is 64KB. The MMU table can be used for mapping instructions and data. + External RAM mapped into data space in 64 KB blocks no larger than 32MB. + The hardware does support larger memories, but these have to be + bank-switched in and out of this address space. Enabling this allows + you to reserve some MMU pages for this, which allows the use of the + esp32s3_himem api to manage these + banks. + +config SPIRAM_BANKSWITCH_RESERVE + int "Amount of 64K pages to reserve for bank switching" + depends on ESP32S3_SPIRAM_BANKSWITCH_ENABLE + default 4 + range 1 256 + ---help--- + Select the amount of banks reserved for bank switching. Note + that the amount of RAM allocatable with malloc will decrease + by 64KB for each page reserved here. + Note that the amount of banks reserved is smaller than the number + of 64KB blocks of configured external RAM mapped to the data space. + Note that this reservation is only actually done if your + program actually uses the himem API. Without any himem + calls, the reservation is not done and the original amount + of memory will be available. + endmenu # Memory Configuration config ESP32S3_GPIO_IRQ diff --git a/arch/xtensa/src/esp32s3/Make.defs b/arch/xtensa/src/esp32s3/Make.defs index a0c90f09ee..a04e66ff6a 100644 --- a/arch/xtensa/src/esp32s3/Make.defs +++ b/arch/xtensa/src/esp32s3/Make.defs @@ -138,6 +138,7 @@ endif ifeq ($(CONFIG_ESP32S3_SPIRAM),y) CHIP_CSRCS += esp32s3_spiram.c +CHIP_CSRCS += esp32s3_himem.c ifeq ($(CONFIG_ESP32S3_SPIRAM_MODE_QUAD),y) CHIP_CSRCS += esp32s3_psram_quad.c diff --git a/arch/xtensa/src/esp32s3/esp32s3_allocateheap.c b/arch/xtensa/src/esp32s3/esp32s3_allocateheap.c index 05432454c0..2de7f36f5c 100644 --- a/arch/xtensa/src/esp32s3/esp32s3_allocateheap.c +++ b/arch/xtensa/src/esp32s3/esp32s3_allocateheap.c @@ -41,6 +41,7 @@ #include "hardware/esp32s3_rom_layout.h" #ifdef CONFIG_ESP32S3_SPIRAM # include "esp32s3_spiram.h" +# include "esp32s3_himem.h" #endif /**************************************************************************** @@ -101,7 +102,8 @@ void up_allocate_heap(void **heap_start, size_t *heap_size) # elif defined(CONFIG_BUILD_FLAT) # ifdef MM_USER_HEAP_EXTRAM ubase = (uintptr_t)esp_spiram_allocable_vaddr_start(); - utop = (uintptr_t)esp_spiram_allocable_vaddr_end(); + utop = (uintptr_t)(esp_spiram_allocable_vaddr_end() - + esp_himem_reserved_area_size()); # elif defined(MM_USER_HEAP_IRAM) ubase = (uintptr_t)_sheap + XTENSA_IMEM_REGION_SIZE; utop = (uintptr_t)HEAP_REGION1_END; @@ -220,7 +222,8 @@ void xtensa_add_region(void) #if defined(CONFIG_ESP32S3_SPIRAM_COMMON_HEAP) && !defined(MM_USER_HEAP_EXTRAM) start = (void *)esp_spiram_allocable_vaddr_start(); - end = (void *)esp_spiram_allocable_vaddr_end(); + end = (void *)(esp_spiram_allocable_vaddr_end() - + esp_himem_reserved_area_size()); size = (size_t)(end - start); #endif diff --git a/arch/xtensa/src/esp32s3/esp32s3_himem.c b/arch/xtensa/src/esp32s3/esp32s3_himem.c new file mode 100644 index 0000000000..e1855d3f6d --- /dev/null +++ b/arch/xtensa/src/esp32s3/esp32s3_himem.c @@ -0,0 +1,1122 @@ +/**************************************************************************** + * arch/xtensa/src/esp32s3/esp32s3_himem.c + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. The + * ASF licenses this file to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the + * License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + ****************************************************************************/ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include +#include +#include +#include +#include + +#include "xtensa.h" +#include "esp32s3_spiram.h" +#include "esp32s3_himem.h" +#include "hardware/esp32s3_soc.h" +#include "hardware/esp32s3_cache_memory.h" +#include "hardware/esp32s3_extmem.h" + +/**************************************************************************** + * Pre-processor Definitions + ****************************************************************************/ + +/* So, why does the API look this way and is so inflexible to not allow any + * maps beyond the full 64K chunks? Most of it has to do with the fact that + * the cache works on the *virtual* addresses What this comes down to is that + * while it's allowed to map a range of physical memory into the address + * space two times, there's no cache consistency between the two regions. + * + * This means that a write to region A may or may not show up, perhaps + * delayed, in region B, as it depends on the time that the writeback to SPI + * RAM is done on A and the time before the corresponding cache line is + * invalidated on B. Note that this goes for every 32-byte cache line: this + * implies that if a program writes to address X and Y within A, the write to + * Y may show up before the write to X does. + * + * It gets even worse when both A and B are written: theoretically, a write + * to a 32-byte cache line in A can be entirely undone because of a write to + * a different address in B that happens to be in the same 32-byte cache + * line. + * + * Because of these reasons, we do not allow double mappings at all. This, + * however, has other implications that make supporting ranges not really + * useful. Because the lack of double mappings, applications will need to do + * their own management of mapped regions, meaning they will normally map in + * and out blocks at a time anyway, as mapping more fluent regions would + * result in the chance of accidentally mapping two overlapping regions. As + * this is the case, to keep the code simple, at the moment we just force + * these blocks to be equal to the 64K MMU page size. The API itself does + * allow for more granular allocations, so if there's a pressing need for a + * more complex solution in the future, we can do this. + * + * Note: In the future, we can expand on this api to do a memcpy() between + * SPI RAM and (internal) memory using the SPI1 peripheral. This needs + * support for SPI1 to be in the SPI driver, however. + */ + +/* How many 64KB pages will be reserved for bank switch */ + +#ifdef CONFIG_ESP32S3_SPIRAM_BANKSWITCH_ENABLE +# define SPIRAM_BANKSWITCH_RESERVE CONFIG_SPIRAM_BANKSWITCH_RESERVE +#else +# define SPIRAM_BANKSWITCH_RESERVE 0 +#endif + +#define MMU_PAGE_SIZE 0x10000 +#define MMU_PAGE_TO_BYTES(page_num) ((page_num) * MMU_PAGE_SIZE) +#define BYTES_TO_MMU_PAGE(bytes) ((bytes) / MMU_PAGE_SIZE) + +#define HIMEM_CHECK(cond, str, err) if (cond) \ + do \ + { merr("%s: %s", __FUNCTION__, str); \ + return err; \ + } while(0) + +/**************************************************************************** + * Private Types + ****************************************************************************/ + +/* Metadata for a block of physical RAM */ + +typedef struct +{ + unsigned int is_alloced: 1; + unsigned int is_mapped: 1; +} ramblock_t; + +/* Metadata for a 32-K memory address range */ + +typedef struct +{ + unsigned int is_alloced: 1; + unsigned int is_mapped: 1; + unsigned int ram_block: 16; +} rangeblock_t; + +/**************************************************************************** + * ROM Function Prototypes + ****************************************************************************/ + +extern uint32_t cache_suspend_dcache(void); +extern void cache_resume_dcache(uint32_t val); +extern int cache_dbus_mmu_set(uint32_t ext_ram, uint32_t vaddr, + uint32_t paddr, uint32_t psize, + uint32_t num, uint32_t fixed); + +/**************************************************************************** + * Private Function Prototypes + ****************************************************************************/ + +static inline bool ramblock_idx_valid(int ramblock_idx); +static inline bool rangeblock_idx_valid(int rangeblock_idx); +static void set_bank(int virt_bank, int phys_bank, int ct); +static uint32_t esp_himem_get_range_start(void); +static uint32_t esp_himem_get_range_block(void); +static uint32_t esp_himem_get_phy_block(void); +static bool allocate_blocks(int count, uint16_t *blocks_out); + +/* Character driver methods */ + +static ssize_t himem_read(struct file *filep, char *buffer, + size_t buflen); +static ssize_t himem_write(struct file *filep, const char *buffer, + size_t buflen); +static int himem_ioctl(struct file *filep, int cmd, + unsigned long arg); + +/**************************************************************************** + * Private Data + ****************************************************************************/ + +static ramblock_t *g_ram_descriptor = NULL; +static rangeblock_t *g_range_descriptor = NULL; +static int g_ramblockcnt = 0; +static const int g_rangeblockcnt = SPIRAM_BANKSWITCH_RESERVE; + +/* Used by the spinlock */ + +irqstate_t spinlock_flags; + +static const struct file_operations g_himemfops = +{ + NULL, /* open */ + NULL, /* close */ + himem_read, /* read */ + himem_write, /* write */ + NULL, /* seek */ + himem_ioctl, /* ioctl */ +}; + +/**************************************************************************** + * Private functions + ****************************************************************************/ + +/**************************************************************************** + * Name: ramblock_idx_valid + * + * Description: + * Check whether RAM block is valid. + * + * Input Parameters: + * ramblock_idx - RAM block index. + * + * Returned Value: + * True if blocks can be allocated, false if not. + * + ****************************************************************************/ + +static inline bool ramblock_idx_valid(int ramblock_idx) +{ + return (ramblock_idx >= 0 && ramblock_idx < g_ramblockcnt); +} + +/**************************************************************************** + * Name: rangeblock_idx_valid + * + * Description: + * Check whether virtual block is valid. + * + * Input Parameters: + * rangeblock_idx - Range block index. + * + * Returned Value: + * True if blocks can be allocated, false if not. + * + ****************************************************************************/ + +static inline bool rangeblock_idx_valid(int rangeblock_idx) +{ + return (rangeblock_idx >= 0 && rangeblock_idx < g_rangeblockcnt); +} + +/**************************************************************************** + * Name: set_bank + * + * Description: + * Set DCache mmu mapping. + * + * Input Parameters: + * virt_bank - Beginning of the virtual bank + * phys_bank - Beginning of the physical bank + * ct - Number of banks + * + * Returned Value: + * None. + * + ****************************************************************************/ + +static void set_bank(int virt_bank, int phys_bank, int ct) +{ + uint32_t regval; + + /* Suspend DRAM Case during configuration */ + + cache_suspend_dcache(); + ASSERT(cache_dbus_mmu_set(MMU_ACCESS_SPIRAM, + SOC_EXTRAM_DATA_LOW + + MMU_PAGE_TO_BYTES(virt_bank), + MMU_PAGE_TO_BYTES(phys_bank), 64, ct, 0) == 0); + + regval = getreg32(EXTMEM_DCACHE_CTRL1_REG); + regval &= ~EXTMEM_DCACHE_SHUT_CORE0_BUS; + putreg32(regval, EXTMEM_DCACHE_CTRL1_REG); + +#if defined(CONFIG_SMP) + regval = getreg32(EXTMEM_DCACHE_CTRL1_REG); + regval &= ~EXTMEM_DCACHE_SHUT_CORE1_BUS; + putreg32(regval, EXTMEM_DCACHE_CTRL1_REG); +#endif + + cache_resume_dcache(0); +} + +/**************************************************************************** + * Name: esp_himem_get_range_start + * + * Description: + * Get the virtual address range reserved for himem use. + * + * Input Parameters: + * None. + * + * Returned Value: + * Range of reserved virtual address. + * + ****************************************************************************/ + +static uint32_t esp_himem_get_range_start(void) +{ + return (esp_spiram_allocable_vaddr_end() - + MMU_PAGE_TO_BYTES(SPIRAM_BANKSWITCH_RESERVE)); +} + +/**************************************************************************** + * Name: esp_himem_get_range_block + * + * Description: + * Get MMU blocks reserved for himem use. + * + * Input Parameters: + * None. + * + * Returned Value: + * Number of reserved MMU blocks. + * + ****************************************************************************/ + +static uint32_t esp_himem_get_range_block(void) +{ + return (BYTES_TO_MMU_PAGE(esp_spiram_allocable_vaddr_end() + - SOC_EXTRAM_DATA_LOW) + - SPIRAM_BANKSWITCH_RESERVE); +} + +/**************************************************************************** + * Name: esp_himem_get_phy_block + * + * Description: + * Get physical RAM blocks. + * + * Input Parameters: + * None. + * + * Returned Value: + * Physical RAM blocks. + * + ****************************************************************************/ + +static uint32_t esp_himem_get_phy_block(void) +{ + return (BYTES_TO_MMU_PAGE(esp_spiram_allocable_vaddr_end() + - esp_spiram_allocable_vaddr_start()) + - SPIRAM_BANKSWITCH_RESERVE); +} + +/**************************************************************************** + * Name: allocate_blocks + * + * Description: + * Allocate count not-necessarily consecutive physical RAM blocks. + * + * Input Parameters: + * count - Numbers of blocks + * blocks_out - Pointer numbers in blocks[] + * + * Returned Value: + * True if blocks can be allocated, false if not. + * + ****************************************************************************/ + +static bool allocate_blocks(int count, uint16_t *blocks_out) +{ + int n = 0; + int i; + + for (i = 0; i < g_ramblockcnt && n != count; i++) + { + if (!g_ram_descriptor[i].is_alloced) + { + blocks_out[n] = i; + n++; + } + } + + if (n == count) + { + /* All blocks could be allocated. Mark as in use. */ + + for (i = 0; i < count; i++) + { + g_ram_descriptor[blocks_out[i]].is_alloced = true; + DEBUGASSERT(g_ram_descriptor[blocks_out[i]].is_mapped == false); + } + + return true; + } + else + { + /* Error allocating blocks */ + + return false; + } +} + +/**************************************************************************** + * Name: himem_read + ****************************************************************************/ + +static ssize_t himem_read(struct file *filep, char *buffer, + size_t buflen) +{ + return -ENOSYS; +} + +/**************************************************************************** + * Name: himem_write + ****************************************************************************/ + +static ssize_t himem_write(struct file *filep, const char *buffer, + size_t buflen) +{ + return -ENOSYS; +} + +/**************************************************************************** + * Name: himem_ioctl + ****************************************************************************/ + +static int himem_ioctl(struct file *filep, int cmd, unsigned long arg) +{ + int ret = OK; + + switch (cmd) + { + /* Allocate the physical RAM blocks */ + + case HIMEMIOC_ALLOC_BLOCKS: + { + struct esp_himem_par *param = + (struct esp_himem_par *)((uintptr_t)arg); + + DEBUGASSERT(param != NULL); + + /* Allocate the memory we're going to check. */ + + ret = esp_himem_alloc(param->memfree, &(param->handle)); + if (ret < 0) + { + minfo("Error: esp_himem_alloc() failed!\n"); + return ret; + } + } + break; + + /* Free the physical RAM blocks */ + + case HIMEMIOC_FREE_BLOCKS: + { + struct esp_himem_par *param = + (struct esp_himem_par *)((uintptr_t)arg); + + DEBUGASSERT(param != NULL); + + ret = esp_himem_free(param->handle); + if (ret < 0) + { + minfo("Error: esp_himem_free() failed!\n"); + return ret; + } + } + break; + + /* Allocate the mapping range */ + + case HIMEMIOC_ALLOC_MAP_RANGE: + { + struct esp_himem_par *param = + (struct esp_himem_par *)((uintptr_t)arg); + + DEBUGASSERT(param != NULL); + + /* Allocate a block of address range */ + + ret = esp_himem_alloc_map_range(ESP_HIMEM_BLKSZ, &(param->range)); + if (ret < 0) + { + minfo("Error: esp_himem_alloc_map_range() failed!\n"); + return ret; + } + } + break; + + /* Free the mapping range */ + + case HIMEMIOC_FREE_MAP_RANGE: + { + struct esp_himem_par *param = + (struct esp_himem_par *)((uintptr_t)arg); + + DEBUGASSERT(param != NULL); + + ret = esp_himem_free_map_range(param->range); + if (ret < 0) + { + minfo("Error: esp_himem_free_map_range() failed!\n"); + return ret; + } + } + break; + + /* Map the himem blocks */ + + case HIMEMIOC_MAP: + { + struct esp_himem_par *param = + (struct esp_himem_par *)((uintptr_t)arg); + + DEBUGASSERT(param != NULL); + + ret = esp_himem_map(param->handle, + param->range, + param->ram_offset, + param->range_offset, + param->len, + param->flags, + (void **) &(param->ptr)); + if (ret < 0) + { + minfo("error: esp_himem_map() failed!\n"); + return ret; + } + } + break; + + /* Unmap the himem blocks */ + + case HIMEMIOC_UNMAP: + { + struct esp_himem_par *param = + (struct esp_himem_par *)((uintptr_t)arg); + + DEBUGASSERT(param != NULL); + + ret = esp_himem_unmap(param->range, + (void *) param->ptr, + param->len); + if (ret < 0) + { + minfo("error: esp_himem_unmap() failed!\n"); + return ret; + } + } + break; + + /* Get the physical external memory size */ + + case HIMEMIOC_GET_PHYS_SIZE: + { + struct esp_himem_par *param = + (struct esp_himem_par *)((uintptr_t)arg); + + DEBUGASSERT(param != NULL); + + param->memcnt = esp_himem_get_phys_size(); + } + break; + + /* Get the free memory size */ + + case HIMEMIOC_GET_FREE_SIZE: + { + struct esp_himem_par *param = + (struct esp_himem_par *)((uintptr_t)arg); + + DEBUGASSERT(param != NULL); + + param->memfree = esp_himem_get_free_size(); + } + break; + + default: + { + sninfo("Unrecognized cmd: %d\n", cmd); + ret = -ENOTTY; + } + break; + } + + return ret; +} + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +/**************************************************************************** + * Name: esp_himem_reserved_area_size + * + * Description: + * Get amount of SPI memory address space needed for bankswitching. + * + * Input Parameters: + * None. + * + * Returned Value: + * Amount of reserved area, in bytes. + * + ****************************************************************************/ + +size_t esp_himem_reserved_area_size(void) +{ + return MMU_PAGE_TO_BYTES(SPIRAM_BANKSWITCH_RESERVE); +} + +/**************************************************************************** + * Name: esp_himem_get_phys_size + * + * Description: + * Get total amount of memory under control of himem API. + * + * Input Parameters: + * None. + * + * Returned Value: + * Amount of memory, in bytes. + * + ****************************************************************************/ + +size_t esp_himem_get_phys_size(void) +{ + int paddr_start = esp_spiram_allocable_vaddr_end() + - esp_spiram_allocable_vaddr_start() + - MMU_PAGE_TO_BYTES(SPIRAM_BANKSWITCH_RESERVE); + return esp_spiram_get_size() - paddr_start; +} + +/**************************************************************************** + * Name: esp_himem_get_free_size + * + * Description: + * Get free amount of memory under control of himem API. + * + * Input Parameters: + * None. + * + * Returned Value: + * Amount of memory, in bytes. + * + ****************************************************************************/ + +size_t esp_himem_get_free_size(void) +{ + size_t ret = 0; + int i; + + for (i = 0; i < g_ramblockcnt; i++) + { + if (!g_ram_descriptor[i].is_alloced) + { + ret += MMU_PAGE_SIZE; + } + } + + return ret; +} + +/**************************************************************************** + * Name: esp_himem_alloc + * + * Description: + * Allocate a block in high memory. + * + * Input Parameters: + * size - Size of the to-be-allocated block, in bytes. Note that + * this needs to be a multiple of the external RAM mmu block + * size(64K). + * handle_out - Handle to be returned + * + * Returned Value: + * OK if success or a negative value if fail. + * + ****************************************************************************/ + +int esp_himem_alloc(size_t size, esp_himem_handle_t *handle_out) +{ + esp_himem_ramdata_t *r; + int blocks; + int ok; + + /* If the size is not multiple of BLOCKSIZE, there is an issue */ + + if (size % MMU_PAGE_SIZE != 0) + { + return -EINVAL; + } + + blocks = BYTES_TO_MMU_PAGE(size); + + r = kmm_malloc(sizeof(esp_himem_ramdata_t)); + if (!r) + { + goto nomem; + } + + r->block = kmm_malloc(sizeof(uint16_t) * blocks); + if (!r->block) + { + goto nomem; + } + + spinlock_flags = spin_lock_irqsave(NULL); + + ok = allocate_blocks(blocks, r->block); + + spin_unlock_irqrestore(NULL, spinlock_flags); + if (!ok) + { + goto nomem; + } + + r->block_ct = blocks; + *handle_out = r; + + return OK; + +nomem: + if (r) + { + kmm_free(r->block); + kmm_free(r); + } + + return -ENOMEM; +} + +/**************************************************************************** + * Name: esp_himem_free + * + * Description: + * Free a block of physical memory, this clears out the associated handle + * making the memory available for re-allocation again, this will only + * succeed if none of the memory blocks currently have a mapping. + * + * Input Parameters: + * handle - Handle to the block of memory, as given by esp_himem_alloc. + * + * Returned Value: + * OK if success or a negative value if fail. + * + ****************************************************************************/ + +int esp_himem_free(esp_himem_handle_t handle) +{ + int i; + + /* Check if any of the blocks is still mapped; fail if this is the case. */ + + for (i = 0; i < handle->block_ct; i++) + { + DEBUGASSERT(ramblock_idx_valid(handle->block[i])); + HIMEM_CHECK(g_ram_descriptor[handle->block[i]].is_mapped, + "block in range still mapped", -EINVAL); + } + + /* Mark blocks as free */ + + spinlock_flags = spin_lock_irqsave(NULL); + for (i = 0; i < handle->block_ct; i++) + { + g_ram_descriptor[handle->block[i]].is_alloced = false; + } + + spin_unlock_irqrestore(NULL, spinlock_flags); + + /* Free handle */ + + kmm_free(handle->block); + kmm_free(handle); + return OK; +} + +/**************************************************************************** + * Name: esp_himem_alloc_map_range + * + * Description: + * Allocate a memory region to map blocks into, this allocates a + * contiguous CPU memory region that can be used to map blocks of + * physical memory into. + * + * Input Parameters: + * size - Size of the range to be allocated. Note this needs to be a + * multiple of the external RAM mmu block size (64K). + * handle_out - Handle to be returned. + * + * Returned Value: + * OK if success or a negative value if fail. + * + ****************************************************************************/ + +int esp_himem_alloc_map_range(size_t size, esp_himem_rangehandle_t + *handle_out) +{ + esp_himem_rangedata_t *r; + int i; + int blocks; + int start_free; + + HIMEM_CHECK(g_ram_descriptor == NULL, "Himem not available!", + -EINVAL); + + HIMEM_CHECK(size % MMU_PAGE_SIZE != 0, + "requested size not aligned to blocksize", + -EINVAL); + + blocks = BYTES_TO_MMU_PAGE(size); + + r = kmm_malloc(sizeof(esp_himem_rangedata_t) * 1); + if (!r) + { + return -ENOMEM; + } + + r->block_ct = blocks; + r->block_start = -1; + + start_free = 0; + spinlock_flags = spin_lock_irqsave(NULL); + + for (i = 0; i < g_rangeblockcnt; i++) + { + if (g_range_descriptor[i].is_alloced) + { + start_free = i + 1; /* optimistically assume next block is free... */ + } + else + { + if (i - start_free == blocks - 1) + { + /* We found a span of blocks that's big enough to allocate + * the requested range in. + */ + + r->block_start = start_free; + break; + } + } + } + + if (r->block_start == -1) + { + /* Couldn't find enough free blocks */ + + kmm_free(r); + spin_unlock_irqrestore(NULL, spinlock_flags); + return -ENOMEM; + } + + /* Range is found. Mark the blocks as in use. */ + + for (i = 0; i < blocks; i++) + { + g_range_descriptor[r->block_start + i].is_alloced = 1; + } + + spin_unlock_irqrestore(NULL, spinlock_flags); + + /* All done. */ + + *handle_out = r; + return OK; +} + +/**************************************************************************** + * Name: esp_himem_free_map_range + * + * Description: + * Free a mapping range, this clears out the associated handle making the + * range available for re-allocation again, This will only succeed if none + * of the range blocks currently are used for a mapping. + * + * Input Parameters: + * handle - Handle to the range block, as given by + * esp_himem_alloc_map_range. + * + * Returned Value: + * OK if success or a negative value if fail. + * + ****************************************************************************/ + +int esp_himem_free_map_range(esp_himem_rangehandle_t handle) +{ + int i; + + /* Check if any of the blocks in the range have a mapping */ + + for (i = 0; i < handle->block_ct; i++) + { + DEBUGASSERT(rangeblock_idx_valid(handle->block_start + i)); + + /* should be allocated, if handle is valid */ + + DEBUGASSERT(g_range_descriptor[i + \ + handle->block_start].is_alloced == 1); + + HIMEM_CHECK(g_range_descriptor[i + handle->block_start].is_mapped, + "memory still mapped to range", -EINVAL); + } + + /* We should be good to free this. Mark blocks as free. */ + + spinlock_flags = spin_lock_irqsave(NULL); + + for (i = 0; i < handle->block_ct; i++) + { + g_range_descriptor[i + handle->block_start].is_alloced = 0; + } + + spin_unlock_irqrestore(NULL, spinlock_flags); + kmm_free(handle); + return OK; +} + +/**************************************************************************** + * Name: esp_himem_map + * + * Description: + * Map a block of high memory into the CPUs address space, this effectively + * makes the block available for read/write operations. + * + * Input Parameters: + * handle - Handle to the block of memory, as given by + * esp_himem_alloc + * range - Range handle to map the memory in + * ram_offset - Offset into the block of physical memory of the block to + * map + * range_offset - Offset into the address range where the block will be + * mapped + * len - Length of region to map + * flags - One of ESP_HIMEM_MAPFLAG_* + * out_ptr - Pointer to variable to store resulting memory pointer in + * + * Returned Value: + * OK if success or a negative value if fail. + * + ****************************************************************************/ + +int esp_himem_map(esp_himem_handle_t handle, + esp_himem_rangehandle_t range, + size_t ram_offset, + size_t range_offset, + size_t len, + int flags, + void **out_ptr) +{ + int i; + int virt_bank; + int phys_bank; + int ram_block = BYTES_TO_MMU_PAGE(ram_offset); + int range_block = BYTES_TO_MMU_PAGE(range_offset); + int blockcount = BYTES_TO_MMU_PAGE(len); + uint32_t himem_mmu_start = esp_himem_get_range_block(); + uint32_t himem_phy_start = esp_himem_get_phy_block(); + + HIMEM_CHECK(g_ram_descriptor == NULL, "Himem not available!", + -EINVAL); + + /* Offsets and length must be block-aligned */ + + HIMEM_CHECK(ram_offset % MMU_PAGE_SIZE != 0, + "ram offset not aligned to blocksize", -EINVAL); + + HIMEM_CHECK(range_offset % MMU_PAGE_SIZE != 0, + "range not aligned to blocksize", -EINVAL); + + HIMEM_CHECK(len % MMU_PAGE_SIZE != 0, + "length not aligned to blocksize", -EINVAL); + + /* ram and range should be within allocated range */ + + HIMEM_CHECK(ram_block + blockcount > handle->block_ct, + "args not in range of phys ram handle", -EINVAL); + + HIMEM_CHECK(range_block + blockcount > range->block_ct, + "args not in range of range handle", -EINVAL); + + /* Check if ram blocks aren't already mapped, and if memory range is + * unmapped. + */ + + for (i = 0; i < blockcount; i++) + { + HIMEM_CHECK(g_ram_descriptor[handle->block[i + ram_block]].is_mapped, + "ram already mapped", -EINVAL); + + HIMEM_CHECK(g_range_descriptor[range->block_start + i + + range_block].is_mapped, "range already mapped", + -EINVAL); + } + + /* Map and mark as mapped */ + + spinlock_flags = spin_lock_irqsave(NULL); + + for (i = 0; i < blockcount; i++) + { + DEBUGASSERT(ramblock_idx_valid(handle->block[i + ram_block])); + g_ram_descriptor[handle->block[i + ram_block]].is_mapped = 1; + g_range_descriptor[range->block_start + i + range_block].is_mapped = 1; + g_range_descriptor[range->block_start + i + range_block].ram_block = + handle->block[i + ram_block]; + } + + spin_unlock_irqrestore(NULL, spinlock_flags); + for (i = 0; i < blockcount; i++) + { + virt_bank = himem_mmu_start + range->block_start + i + range_block; + phys_bank = himem_phy_start + handle->block[i + ram_block]; + set_bank(virt_bank, phys_bank, 1); + } + + /* Set out pointer */ + + *out_ptr = (void *)(esp_himem_get_range_start() + + MMU_PAGE_TO_BYTES(range->block_start + range_offset)); + return OK; +} + +/**************************************************************************** + * Name: esp_himem_unmap + * + * Description: + * Unmap a region. + * + * Input Parameters: + * range - Range handle + * ptr - Pointer returned by esp_himem_map + * len - Length of the block to be unmapped, must be aligned to the + * SPI RAM MMU blocksize (64K) + * + * Returned Value: + * OK if success or a negative value if fail. + * + ****************************************************************************/ + +int esp_himem_unmap(esp_himem_rangehandle_t range, void *ptr, size_t len) +{ + /* Note: doesn't actually unmap, just clears cache and marks blocks as + * unmapped. + * Future optimization: could actually lazy-unmap here: essentially, do + * nothing and only clear the cache when we re-use the block for a + * different physical address. + */ + + int range_offset = (uint32_t)ptr - esp_himem_get_range_start(); + int range_block = BYTES_TO_MMU_PAGE(range_offset) - range->block_start; + int blockcount = BYTES_TO_MMU_PAGE(len); + int i; + + HIMEM_CHECK(range_offset % MMU_PAGE_SIZE != 0, + "range offset not block-aligned", -EINVAL); + + HIMEM_CHECK(len % MMU_PAGE_SIZE != 0, + "map length not block-aligned", -EINVAL); + + HIMEM_CHECK(range_block + blockcount > range->block_ct, + "range out of bounds for handle", -EINVAL); + + spinlock_flags = spin_lock_irqsave(NULL); + + for (i = 0; i < blockcount; i++) + { + int ramblock = g_range_descriptor[range->block_start + i + + range_block].ram_block; + + DEBUGASSERT(ramblock_idx_valid(ramblock)); + g_ram_descriptor[ramblock].is_mapped = 0; + g_range_descriptor[range->block_start + i + range_block].is_mapped = 0; + } + + esp_spiram_writeback_cache(); + spin_unlock_irqrestore(NULL, spinlock_flags); + return OK; +} + +/**************************************************************************** + * Name: esp_himem_init + * + * Description: + * Initialize Himem driver. + * + * Input Parameters: + * None. + * + * Returned Value: + * OK if success or a negative value if fail. + * + ****************************************************************************/ + +int esp_himem_init(void) +{ + int paddr_start = (esp_spiram_allocable_vaddr_end() - + esp_spiram_allocable_vaddr_start()) - + MMU_PAGE_TO_BYTES(SPIRAM_BANKSWITCH_RESERVE); + int paddr_end; + int maxram; + int ret; + + if (SPIRAM_BANKSWITCH_RESERVE == 0) + { + return -ENODEV; + } + + maxram = esp_spiram_get_size(); + + /* Catch double init */ + + /* Looks weird; last arg is empty so it expands to 'return ;' */ + + HIMEM_CHECK(g_ram_descriptor != NULL, "already initialized", 0); + + HIMEM_CHECK(g_range_descriptor != NULL, "already initialized", 0); + + /* need to have some reserved banks */ + + HIMEM_CHECK(SPIRAM_BANKSWITCH_RESERVE == 0, "No banks reserved for \ + himem", 0); + + /* Start and end of physical reserved memory. Note it starts slightly under + * the 4MiB mark as the reserved banks can't have an unity mapping to be + * used by malloc anymore; we treat them as himem instead. + */ + + paddr_end = maxram; + g_ramblockcnt = BYTES_TO_MMU_PAGE(paddr_end - paddr_start); + + /* Allocate data structures */ + + g_ram_descriptor = kmm_zalloc(sizeof(ramblock_t) * g_ramblockcnt); + g_range_descriptor = kmm_zalloc(sizeof(rangeblock_t) * \ + SPIRAM_BANKSWITCH_RESERVE); + + if (g_ram_descriptor == NULL || g_range_descriptor == NULL) + { + merr("Cannot allocate memory for meta info. Not initializing!"); + kmm_free(g_ram_descriptor); + kmm_free(g_range_descriptor); + return -ENOMEM; + } + + /* Register the character driver */ + + ret = register_driver("/dev/himem", &g_himemfops, 0666, NULL); + if (ret < 0) + { + merr("ERROR: Failed to register driver: %d\n", ret); + } + + minfo("Initialized. Using last %d 64KB address blocks for bank \ + switching on %d KB of physical memory.\n", + SPIRAM_BANKSWITCH_RESERVE, (paddr_end - paddr_start) / 1024); + + return OK; +} diff --git a/arch/xtensa/src/esp32s3/esp32s3_himem.h b/arch/xtensa/src/esp32s3/esp32s3_himem.h new file mode 100644 index 0000000000..f08c8707f4 --- /dev/null +++ b/arch/xtensa/src/esp32s3/esp32s3_himem.h @@ -0,0 +1,220 @@ +/**************************************************************************** + * arch/xtensa/src/esp32s3/esp32s3_himem.h + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. The + * ASF licenses this file to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the + * License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + ****************************************************************************/ + +#ifndef __ARCH_XTENSA_SRC_ESP32S3_ESP32S3_HIMEM_H +#define __ARCH_XTENSA_SRC_ESP32S3_ESP32S3_HIMEM_H + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include + +#include + +#ifdef __cplusplus +extern "C" +{ +#endif + +/**************************************************************************** + * Public Function Prototypes + ****************************************************************************/ + +/**************************************************************************** + * Name: esp_himem_reserved_area_size + * + * Description: + * Get amount of SPI memory address space needed for bankswitching. + * + * Input Parameters: + * None + * + * Returned Value: + * Amount of reserved area, in bytes. + * + ****************************************************************************/ + +size_t esp_himem_reserved_area_size(void); + +/**************************************************************************** + * Name: esp_himem_get_phys_size + * + * Description: + * Get total amount of memory under control of himem API. + * + * Input Parameters: + * None + * + * Returned Value: + * Amount of memory, in bytes. + * + ****************************************************************************/ + +size_t esp_himem_get_phys_size(void); + +/**************************************************************************** + * Name: esp_himem_get_free_size + * + * Description: + * Get free amount of memory under control of himem API. + * + * Input Parameters: + * None + * + * Returned Value: + * Amount of memory, in bytes. + * + ****************************************************************************/ + +size_t esp_himem_get_free_size(void); + +/**************************************************************************** + * Name: esp_himem_alloc + * + * Description: + * Allocate a block in high memory. + * + * Input Parameters: + * size - Size of the to-be-allocated block, in bytes. Note that + * this needs to be a multiple of the external RAM mmu block + * size(64K). + * handle_out - Handle to be returned + * + * Returned Value: + * OK if success or a negative value if fail. + * + ****************************************************************************/ + +int esp_himem_alloc(size_t size, esp_himem_handle_t *handle_out); + +/**************************************************************************** + * Name: esp_himem_free + * + * Description: + * Free a block of physical memory, this clears out the associated handle + * making the memory available for re-allocation again, this will only + * succeed if none of the memory blocks currently have a mapping. + * + * Input Parameters: + * handle - Handle to the block of memory, as given by esp_himem_alloc. + * + * Returned Value: + * OK if success or a negative value if fail. + * + ****************************************************************************/ + +int esp_himem_free(esp_himem_handle_t handle); + +/**************************************************************************** + * Name: esp_himem_alloc_map_range + * + * Description: + * Allocate a memory region to map blocks into, this allocates a + * contiguous CPU memory region that can be used to map blocks of + * physical memory into. + * + * Input Parameters: + * size - Size of the range to be allocated. Note this needs to be a + * multiple of the external RAM mmu block size (64K). + * handle_out - Handle to be returned + * + * Returned Value: + * OK if success or a negative value if fail. + * + ****************************************************************************/ + +int esp_himem_alloc_map_range(size_t size, esp_himem_rangehandle_t + *handle_out); + +/**************************************************************************** + * Name: esp_himem_free_map_range + * + * Description: + * Free a mapping range, this clears out the associated handle making the + * range available for re-allocation again, This will only succeed if none + * of the range blocks currently are used for a mapping. + * + * Input Parameters: + * handle - Handle to the range block, as given by + * esp_himem_alloc_map_range + * + * Returned Value: + * OK if success or a negative value if fail. + * + ****************************************************************************/ + +int esp_himem_free_map_range(esp_himem_rangehandle_t handle); + +/**************************************************************************** + * Name: esp_himem_map + * + * Description: + * Map a block of high memory into the CPUs address space, this effectively + * makes the block available for read/write operations. + * + * Input Parameters: + * handle - Handle to the block of memory, as given by + * esp_himem_alloc + * range - Range handle to map the memory in + * ram_offset - Offset into the block of physical memory of the block to + * map + * range_offset - Offset into the address range where the block will be + * mapped + * len - Length of region to map + * flags - One of ESP_HIMEM_MAPFLAG_* + * out_ptr - Pointer to variable to store resulting memory pointer in + * + * Returned Value: + * OK if success or a negative value if fail. + * + ****************************************************************************/ + +int esp_himem_map(esp_himem_handle_t handle, + esp_himem_rangehandle_t range, + size_t ram_offset, + size_t range_offset, + size_t len, + int flags, + void **out_ptr); + +/**************************************************************************** + * Name: esp_himem_unmap + * + * Description: + * Unmap a region. + * + * Input Parameters: + * range - Range handle + * ptr - Pointer returned by esp_himem_map + * len - Length of the block to be unmapped, must be aligned to the + * SPI RAM MMU blocksize (64K) + * + * Returned Value: + * OK if success or a negative value if fail. + * + ****************************************************************************/ + +int esp_himem_unmap(esp_himem_rangehandle_t range, void *ptr, size_t len); + +#ifdef __cplusplus +} +#endif +#endif /* __ARCH_XTENSA_SRC_ESP32S3_ESP32S3_HIMEM_H */ diff --git a/arch/xtensa/src/esp32s3/esp32s3_spiram.c b/arch/xtensa/src/esp32s3/esp32s3_spiram.c index dce575a635..fad579a396 100644 --- a/arch/xtensa/src/esp32s3/esp32s3_spiram.c +++ b/arch/xtensa/src/esp32s3/esp32s3_spiram.c @@ -33,6 +33,7 @@ #include #include #include +#include #include "xtensa.h" #include "xtensa_attr.h" @@ -53,6 +54,18 @@ # define PSRAM_SPEED PSRAM_CACHE_S80M #endif +#if CONFIG_ESP32S3_SPIRAM_VADDR_OFFSET +#define SPIRAM_VADDR_OFFSET CONFIG_ESP32S3_SPIRAM_VADDR_OFFSET +#else +#define SPIRAM_VADDR_OFFSET 0 +#endif + +#if CONFIG_ESP32S3_SPIRAM_VADDR_MAP_SIZE +#define SPIRAM_VADDR_MAP_SIZE CONFIG_ESP32S3_SPIRAM_VADDR_MAP_SIZE +#else +#define SPIRAM_VADDR_MAP_SIZE 0 +#endif + static bool g_spiram_inited; /* These variables are in bytes */ @@ -143,6 +156,8 @@ void IRAM_ATTR esp_spiram_init_cache(void) uint32_t regval; uint32_t psram_size; uint32_t mapped_vaddr_size; + uint32_t target_mapped_vaddr_start; + uint32_t target_mapped_vaddr_end; int ret = psram_get_available_size(&psram_size); if (ret != OK) @@ -151,9 +166,38 @@ void IRAM_ATTR esp_spiram_init_cache(void) } minfo("PSRAM available size = %d\n", psram_size); - mapped_vaddr_size = mmu_valid_space(&g_mapped_vaddr_start); - minfo("Virtual address size = %d\n", mapped_vaddr_size); + + if ((SPIRAM_VADDR_OFFSET + SPIRAM_VADDR_MAP_SIZE) > 0) + { + ASSERT(SPIRAM_VADDR_OFFSET % MMU_PAGE_SIZE == 0); + ASSERT(SPIRAM_VADDR_MAP_SIZE % MMU_PAGE_SIZE == 0); + target_mapped_vaddr_start = DRAM0_CACHE_ADDRESS_LOW + + SPIRAM_VADDR_OFFSET; + target_mapped_vaddr_end = target_mapped_vaddr_start + + SPIRAM_VADDR_MAP_SIZE; + if (target_mapped_vaddr_start < g_mapped_vaddr_start) + { + mwarn("Invalid target vaddr = 0x%x, change vaddr to: 0x%x\n", + target_mapped_vaddr_start, g_mapped_vaddr_start); + target_mapped_vaddr_start = g_mapped_vaddr_start; + } + + if (target_mapped_vaddr_end > + (g_mapped_vaddr_start + mapped_vaddr_size)) + { + mwarn("Invalid vaddr map size: 0x%x, change vaddr end: 0x%x\n", + SPIRAM_VADDR_MAP_SIZE, + g_mapped_vaddr_start + mapped_vaddr_size); + target_mapped_vaddr_end = g_mapped_vaddr_start + mapped_vaddr_size; + } + + ASSERT(target_mapped_vaddr_end > target_mapped_vaddr_start); + ASSERT(target_mapped_vaddr_end <= DRAM0_CACHE_ADDRESS_HIGH); + mapped_vaddr_size = target_mapped_vaddr_end - + target_mapped_vaddr_start; + g_mapped_vaddr_start = target_mapped_vaddr_start; + } if (mapped_vaddr_size < psram_size) { @@ -168,6 +212,10 @@ void IRAM_ATTR esp_spiram_init_cache(void) g_mapped_size = psram_size; } + minfo("Virtual address size = 0x%x, start: 0x%x, end: 0x%x\n", + mapped_vaddr_size, g_mapped_vaddr_start, + g_mapped_vaddr_start + g_mapped_size); + /* Suspend DRAM Case during configuration */ cache_suspend_dcache(); @@ -336,7 +384,7 @@ int IRAM_ATTR g_rodata_flash2spiram_offset(void) } #endif -int esp_spiram_init(void) +int IRAM_ATTR esp_spiram_init(void) { int r; uint32_t psram_physical_size = 0; diff --git a/boards/xtensa/esp32s3/esp32s3-devkit/src/esp32s3_bringup.c b/boards/xtensa/esp32s3/esp32s3-devkit/src/esp32s3_bringup.c index bd7675f178..23e8ed687f 100644 --- a/boards/xtensa/esp32s3/esp32s3-devkit/src/esp32s3_bringup.c +++ b/boards/xtensa/esp32s3/esp32s3-devkit/src/esp32s3_bringup.c @@ -37,6 +37,7 @@ #include #include +#include #ifdef CONFIG_ESP32S3_TIMER # include "esp32s3_board_tim.h" @@ -123,6 +124,15 @@ int esp32s3_bringup(void) bool i2s_enable_rx; #endif +#if defined(CONFIG_ESP32S3_SPIRAM) && \ + defined(CONFIG_ESP32S3_SPIRAM_BANKSWITCH_ENABLE) + ret = esp_himem_init(); + if (ret < 0) + { + syslog(LOG_ERR, "ERROR: Failed to init HIMEM: %d\n", ret); + } +#endif + #if defined(CONFIG_ESP32S3_EFUSE) ret = esp32s3_efuse_initialize("/dev/efuse"); if (ret < 0) diff --git a/include/nuttx/himem/himem.h b/include/nuttx/himem/himem.h index cb7ffa4f3f..f2f5d3bb56 100644 --- a/include/nuttx/himem/himem.h +++ b/include/nuttx/himem/himem.h @@ -29,15 +29,22 @@ #include -#ifdef CONFIG_ESP32_SPIRAM +#if defined(CONFIG_ESP32_SPIRAM) || defined(CONFIG_ESP32S3_SPIRAM) /**************************************************************************** * Pre-processor Definitions ****************************************************************************/ +#ifdef CONFIG_ESP32S3_SPIRAM +/* ESP32S3 MMU block size */ + +# define ESP_HIMEM_BLKSZ (0x10000) + +#else /* ESP32 MMU block size */ -#define ESP_HIMEM_BLKSZ (0x8000) +# define ESP_HIMEM_BLKSZ (0x8000) +#endif /* Command: HIMEMIOC_ALLOC_BLOCKS * Description: Allocate a certain number of physical RAM blocks.