diff --git a/arch/xtensa/src/esp32/Kconfig b/arch/xtensa/src/esp32/Kconfig index 0404ce84e5..dbb15b2244 100644 --- a/arch/xtensa/src/esp32/Kconfig +++ b/arch/xtensa/src/esp32/Kconfig @@ -591,6 +591,37 @@ config ESP32_SPIRAM_2T_MODE Applications will not be affected unless the use the esp_himem APIs, which are not supported in 2T mode. +config SPIRAM_BANKSWITCH_ENABLE + bool "Enable bank switching for >4MiB external RAM" + default y + help + The ESP32 only supports 4MiB of external RAM in its address + space. 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 esp_himem api to manage these + banks. + #Note that this is limited to 62 banks, as + #esp_spiram_writeback_cache needs some kind of mapping of + #some banks below that mark to work. We cannot at this + #moment guarantee this to exist when himem is enabled. + If spiram 2T mode is enabled, the size of 64Mbit psram will + be changed as 32Mbit, so himem will be unusable. + +config SPIRAM_BANKSWITCH_RESERVE + int "Amount of 32K pages to reserve for bank switching" + depends on SPIRAM_BANKSWITCH_ENABLE + default 8 + range 1 62 + help + Select the amount of banks reserved for bank switching. Note + that the amount of RAM allocatable with malloc will decrease + by 32K for each page reserved here. + 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 #SPI RAM Config menu "Ethernet configuration" diff --git a/arch/xtensa/src/esp32/Make.defs b/arch/xtensa/src/esp32/Make.defs index 5373f732d1..b60cd80cfd 100644 --- a/arch/xtensa/src/esp32/Make.defs +++ b/arch/xtensa/src/esp32/Make.defs @@ -121,6 +121,7 @@ endif ifeq ($(CONFIG_ESP32_SPIRAM),y) CHIP_CSRCS += esp32_spiram.c CHIP_CSRCS += esp32_psram.c +CHIP_CSRCS += esp32_himem.c endif ifeq ($(CONFIG_ESP32_EMAC),y) diff --git a/arch/xtensa/src/esp32/esp32_himem.c b/arch/xtensa/src/esp32/esp32_himem.c new file mode 100644 index 0000000000..f0ae9ccddf --- /dev/null +++ b/arch/xtensa/src/esp32/esp32_himem.c @@ -0,0 +1,855 @@ +/**************************************************************************** + * arch/xtensa/src/esp32/esp32_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 "esp32_spiram.h" +#include "esp32_himem.h" +#include "hardware/esp32_soc.h" + +/**************************************************************************** + * Pre-processor Definitions + ****************************************************************************/ + +/* So, why does the API look this way and is so inflexible to not allow any + * maps beyond the full 32K 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 addres 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 32K 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 32KB pages will be reserved for bank switch */ + +#if CONFIG_SPIRAM_BANKSWITCH_ENABLE +# define SPIRAM_BANKSWITCH_RESERVE CONFIG_SPIRAM_BANKSWITCH_RESERVE +#else +# define SPIRAM_BANKSWITCH_RESERVE 0 +#endif + +#define CACHE_BLOCKSIZE (32*1024) + +/* Start of the virtual address range reserved for himem use */ + +#define VIRT_HIMEM_RANGE_START (SOC_EXTRAM_DATA_LOW + \ + (128 - SPIRAM_BANKSWITCH_RESERVE) * \ + CACHE_BLOCKSIZE) + +/* Start MMU block reserved for himem use */ + +#define VIRT_HIMEM_RANGE_BLOCKSTART (128-SPIRAM_BANKSWITCH_RESERVE) + +/* Start physical block */ + +#define PHYS_HIMEM_BLOCKSTART (128 - SPIRAM_BANKSWITCH_RESERVE) + +#define HIMEM_CHECK(cond, str, err) if (cond) \ + do \ + { merr("%s: %s", __FUNCTION__, str); \ + return err; \ + } while(0) + +/* Character driver methods */ + +static int himem_open(FAR struct file *filep); +static int himem_close(FAR struct file *filep); +static ssize_t himem_read(FAR struct file *filep, FAR char *buffer, + size_t buflen); +static ssize_t himem_write(FAR struct file *filep, FAR const char *buffer, + size_t buflen); +static int himem_ioctl(FAR struct file *filep, int cmd, + unsigned long arg); + +/* This structure is used only for access control */ + +struct himem_access_s +{ + sem_t exclsem; /* Supports mutual exclusion */ +}; + +/* 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; + +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; + +/**************************************************************************** + * Private Data + ****************************************************************************/ + +static const struct file_operations g_himemfops = +{ + himem_open, + himem_close, + himem_read, + himem_write, + NULL, + himem_ioctl, + NULL +}; + +/**************************************************************************** + * Private functions + ****************************************************************************/ + +static inline int ramblock_idx_valid(int ramblock_idx) +{ + return (ramblock_idx >= 0 && ramblock_idx < g_ramblockcnt); +} + +static inline int rangeblock_idx_valid(int rangeblock_idx) +{ + return (rangeblock_idx >= 0 && rangeblock_idx < g_rangeblockcnt); +} + +static void set_bank(int virt_bank, int phys_bank, int ct) +{ + int r; + + r = cache_sram_mmu_set(0, 0, SOC_EXTRAM_DATA_LOW + CACHE_BLOCKSIZE * + virt_bank, phys_bank * CACHE_BLOCKSIZE, 32, ct); + DEBUGASSERT(r == 0); + r = cache_sram_mmu_set(1, 0, SOC_EXTRAM_DATA_LOW + CACHE_BLOCKSIZE * + virt_bank, phys_bank * CACHE_BLOCKSIZE, 32, ct); + DEBUGASSERT(r == 0); + + UNUSED(r); +} + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +size_t esp_himem_get_phys_size(void) +{ + int paddr_start = (4096 * 1024) - (CACHE_BLOCKSIZE * + SPIRAM_BANKSWITCH_RESERVE); + return esp_spiram_get_size() - paddr_start; +} + +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 += CACHE_BLOCKSIZE; + } + } + + return ret; +} + +size_t esp_himem_reserved_area_size(void) +{ + return CACHE_BLOCKSIZE * SPIRAM_BANKSWITCH_RESERVE; +} + +int esp_himem_init(void) +{ + FAR struct himem_access_s *priv; + int paddr_start = (4096 * 1024) - (CACHE_BLOCKSIZE * + SPIRAM_BANKSWITCH_RESERVE); + int paddr_end; + int maxram; + int ret; + + if (SPIRAM_BANKSWITCH_RESERVE == 0) + { + return -ENODEV; + } + + /* Allocate a new himem access instance */ + + priv = (FAR struct himem_access_s *) + kmm_zalloc(sizeof(struct himem_access_s)); + + if (!priv) + { + merr("ERROR: Failed to allocate device structure\n"); + return -ENOMEM; + } + + 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", (int) NULL); + + HIMEM_CHECK(g_range_descriptor != NULL, "already initialized", (int) NULL); + + /* need to have some reserved banks */ + + HIMEM_CHECK(SPIRAM_BANKSWITCH_RESERVE == 0, "No banks reserved for \ + himem", (int) NULL); + + /* 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 = ((paddr_end - paddr_start) / CACHE_BLOCKSIZE); + + /* Allocate data structures */ + + g_ram_descriptor = kmm_malloc(sizeof(ramblock_t) * g_ramblockcnt); + g_range_descriptor = kmm_malloc(sizeof(rangeblock_t) * \ + SPIRAM_BANKSWITCH_RESERVE); + + if (g_ram_descriptor == NULL || g_range_descriptor == NULL) + { + merr("Cannot allocate memory for meta info. Not initializing!"); + free(g_ram_descriptor); + free(g_range_descriptor); + return -ENOMEM; + } + + /* Register the character driver */ + + ret = register_driver("/dev/himem", &g_himemfops, 0666, priv); + if (ret < 0) + { + merr("ERROR: Failed to register driver: %d\n", ret); + kmm_free(priv); + } + + minfo("Initialized. Using last %d 32KB address blocks for bank \ + switching on %d KB of physical memory.\n", + SPIRAM_BANKSWITCH_RESERVE, (paddr_end - paddr_start) / 1024); + + return OK; +} + +/* Allocate count not-necessarily consecutive physical RAM blocks, return + * numbers in blocks[]. Return 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; + g_ram_descriptor[blocks_out[i]].is_mapped = false; + } + + return true; + } + else + { + /* Error allocating blocks */ + + return false; + } +} + +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 % CACHE_BLOCKSIZE != 0) + { + return -EINVAL; + } + + blocks = size / CACHE_BLOCKSIZE; + + 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(); + + ok = allocate_blocks(blocks, r->block); + + spin_unlock_irqrestore(spinlock_flags); + if (!ok) + { + goto nomem; + } + + r->block_ct = blocks; + *handle_out = r; + + return OK; + +nomem: + if (r) + { + free(r->block); + free(r); + } + + return -ENOMEM; +} + +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(); + for (i = 0; i < handle->block_ct; i++) + { + g_ram_descriptor[handle->block[i]].is_alloced = false; + } + + spin_unlock_irqrestore(spinlock_flags); + + /* Free handle */ + + free(handle->block); + free(handle); + return OK; +} + +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 % CACHE_BLOCKSIZE != 0, + "requested size not aligned to blocksize", + -EINVAL); + + blocks = size / CACHE_BLOCKSIZE; + + 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(); + + 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 */ + + free(r); + spin_unlock_irqrestore(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(spinlock_flags); + + /* All done. */ + + *handle_out = r; + return OK; +} + +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++) + { + assert(rangeblock_idx_valid(handle->block_start + i)); + + /* should be allocated, if handle is valid */ + + assert(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(); + + for (i = 0; i < handle->block_ct; i++) + { + g_range_descriptor[i + handle->block_start].is_alloced = 0; + } + + spin_unlock_irqrestore(spinlock_flags); + free(handle); + return OK; +} + +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 ram_block = ram_offset / CACHE_BLOCKSIZE; + int range_block = range_offset / CACHE_BLOCKSIZE; + int blockcount = len / CACHE_BLOCKSIZE; + + HIMEM_CHECK(g_ram_descriptor == NULL, "Himem not available!", + -EINVAL); + + /* Offsets and length must be block-aligned */ + + HIMEM_CHECK(ram_offset % CACHE_BLOCKSIZE != 0, + "ram offset not aligned to blocksize", -EINVAL); + + HIMEM_CHECK(range_offset % CACHE_BLOCKSIZE != 0, + "range not aligned to blocksize", -EINVAL); + + HIMEM_CHECK(len % CACHE_BLOCKSIZE != 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(); + + for (i = 0; i < blockcount; i++) + { + assert(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(spinlock_flags); + + for (i = 0; i < blockcount; i++) + { + set_bank(VIRT_HIMEM_RANGE_BLOCKSTART + range->block_start + i + + range_block, handle->block[i + ram_block] + + PHYS_HIMEM_BLOCKSTART, 1); + } + + /* Set out pointer */ + + *out_ptr = (void *)(VIRT_HIMEM_RANGE_START + + (range->block_start + range_offset) * CACHE_BLOCKSIZE); + + return OK; +} + +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 - VIRT_HIMEM_RANGE_START; + int range_block = (range_offset / CACHE_BLOCKSIZE) - range->block_start; + int blockcount = len / CACHE_BLOCKSIZE; + int i; + + HIMEM_CHECK(range_offset % CACHE_BLOCKSIZE != 0, + "range offset not block-aligned", -EINVAL); + + HIMEM_CHECK(len % CACHE_BLOCKSIZE != 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(); + + for (i = 0; i < blockcount; i++) + { + int ramblock = g_range_descriptor[range->block_start + i + + range_block].ram_block; + + assert(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(spinlock_flags); + return OK; +} + +/**************************************************************************** + * Name: himem_open + * + * Description: + * This function is called whenever the LM-75 device is opened. + * + ****************************************************************************/ + +static int himem_open(FAR struct file *filep) +{ + return OK; +} + +/**************************************************************************** + * Name: himem_close + * + * Description: + * This routine is called when the LM-75 device is closed. + * + ****************************************************************************/ + +static int himem_close(FAR struct file *filep) +{ + return OK; +} + +/**************************************************************************** + * Name: himem_read + ****************************************************************************/ + +static ssize_t himem_read(FAR struct file *filep, FAR char *buffer, + size_t buflen) +{ + return -ENOSYS; +} + +/**************************************************************************** + * Name: himem_write + ****************************************************************************/ + +static ssize_t himem_write(FAR struct file *filep, FAR const char *buffer, + size_t buflen) +{ + return -ENOSYS; +} + +/**************************************************************************** + * Name: himem_ioctl + ****************************************************************************/ + +static int himem_ioctl(FAR struct file *filep, int cmd, unsigned long arg) +{ + int ret = OK; + + switch (cmd) + { + /* Allocate the physical RAM blocks */ + + case HIMEMIOC_ALLOC_BLOCKS: + { + FAR struct esp_himem_par *param = + (FAR 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: + { + FAR struct esp_himem_par *param = + (FAR 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 maping range */ + + case HIMEMIOC_ALLOC_MAP_RANGE: + { + FAR struct esp_himem_par *param = + (FAR 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 maping range */ + + case HIMEMIOC_FREE_MAP_RANGE: + { + FAR struct esp_himem_par *param = + (FAR 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: + { + FAR struct esp_himem_par *param = + (FAR 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: + { + FAR struct esp_himem_par *param = + (FAR 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: + { + FAR struct esp_himem_par *param = + (FAR 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: + { + FAR struct esp_himem_par *param = + (FAR 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; +} + diff --git a/arch/xtensa/src/esp32/esp32_himem.h b/arch/xtensa/src/esp32/esp32_himem.h new file mode 100644 index 0000000000..99526a8c12 --- /dev/null +++ b/arch/xtensa/src/esp32/esp32_himem.h @@ -0,0 +1,193 @@ +/**************************************************************************** + * arch/xtensa/src/esp32/esp32_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_ESP32_ESP32_HIMEM_H +#define __ARCH_XTENSA_SRC_ESP32_ESP32_HIMEM_H + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include + +#ifdef __cplusplus +extern "C" +{ +#endif + +/* Indicates that a mapping will only be read from. Note that this is unused + * for now. + */ + +#define ESP_HIMEM_MAPFLAG_RO 1 + +/* Allocate a block in high memory + * + * params: + * 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 + * (32K). + * [out] handle_out Handle to be returned + * + * returns: + * - ESP_OK if succesful + * - ESP_ERR_NO_MEM if out of memory + * - ESP_ERR_INVALID_SIZE if size is not a multiple of 32K + */ + +int esp_himem_alloc(size_t size, esp_himem_handle_t *handle_out); + +/* 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. + * + * params: + * size Size of the range to be allocated. Note this needs to be a multiple + * of the external RAM mmu block size (32K). + * [out] handle_out Handle to be returned + * + * returns: + * - ESP_OK if succesful + * - ESP_ERR_NO_MEM if out of memory or address space + * - ESP_ERR_INVALID_SIZE if size is not a multiple of 32K + */ + +int esp_himem_alloc_map_range(size_t size, esp_himem_rangehandle_t + *handle_out); + +/* Description: Map a block of high memory into the CPUs address space + * + * This effectively makes the block available for read/write operations. + * + * Note: The region to be mapped needs to have offsets and sizes that are + * aligned to the SPI RAM MMU block size (32K) + * + * params: + * 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] out_ptr Pointer to variable to store resulting memory pointer in + * + * returns: + * - ESP_OK if the memory could be mapped + * - ESP_ERR_INVALID_ARG if offset, range or len aren't MMU-block-aligned + * (32K) + * - ESP_ERR_INVALID_SIZE if the offsets/lengths don't fit in the allocated + * memory or range + * - ESP_ERR_INVALID_STATE if a block in the selected ram offset/length is + * already mapped, or if a block in the selected range offset/length + * already has a mapping. + */ + +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); + +/* 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. + * + * params: + * handle Handle to the block of memory, as given by esp_himem_alloc + * + * returns: + * - ESP_OK if the memory is succesfully freed + * - ESP_ERR_INVALID_ARG if the handle still is (partially) mapped + */ + +int esp_himem_free(esp_himem_handle_t handle); + +/* 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. + * + * params: + * handle Handle to the range block, as given by esp_himem_alloc_map_range + * + * returns: + * - ESP_OK if the memory is succesfully freed + * - ESP_ERR_INVALID_ARG if the handle still is (partially) mapped to + */ + +int esp_himem_free_map_range(esp_himem_rangehandle_t handle); + +/* Description: Unmap a region + * + * params: + * 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 (32K) + * returns: + * - ESP_OK if the memory is succesfully unmapped, + * - ESP_ERR_INVALID_ARG if ptr or len are invalid. + */ + +int esp_himem_unmap(esp_himem_rangehandle_t range, void *ptr, + size_t len); + +/* Description: Get total amount of memory under control of himem API + * + * returns: + * Amount of memory, in bytes + */ + +size_t esp_himem_get_phys_size(void); + +/* Description: Get free amount of memory under control of himem API + * + * returns: + * Amount of free memory, in bytes + */ + +size_t esp_himem_get_free_size(void); + +/* Description: Get amount of SPI memory address space needed for + * bankswitching + * + * Note: This is also weakly defined in esp32/spiram.c and returns 0 there, + * so if no other function in this file is used, no memory is reserved. + * + * return: + * Amount of reserved area, in bytes + */ + +size_t esp_himem_reserved_area_size(void); + +#ifdef __cplusplus +} +#endif +#endif /* __ARCH_XTENSA_SRC_ESP32_ESP32_HIMEM_H */ diff --git a/boards/xtensa/esp32/esp32-core/src/esp32_bringup.c b/boards/xtensa/esp32/esp32-core/src/esp32_bringup.c index 867ac4aa02..de5327fe9a 100644 --- a/boards/xtensa/esp32/esp32-core/src/esp32_bringup.c +++ b/boards/xtensa/esp32/esp32-core/src/esp32_bringup.c @@ -61,6 +61,7 @@ #include #include +#include #include "esp32_procfs_imm.h" #include "esp32-core.h" @@ -142,6 +143,14 @@ int esp32_bringup(void) #endif +#if defined(CONFIG_ESP32_SPIRAM) + ret = esp_himem_init(); + if (ret < 0) + { + syslog(LOG_ERR, "ERROR: Failed to init HIMEM: %d\n", ret); + } +#endif + #ifdef CONFIG_FS_PROCFS /* Mount the procfs file system */ diff --git a/include/nuttx/fs/ioctl.h b/include/nuttx/fs/ioctl.h index 8526c9117b..0b83ecf029 100644 --- a/include/nuttx/fs/ioctl.h +++ b/include/nuttx/fs/ioctl.h @@ -100,6 +100,7 @@ #define _NOTECTLBASE (0x2c00) /* Note filter control ioctl commands*/ #define _NOTERAMBASE (0x2d00) /* Noteram device ioctl commands*/ #define _RCIOCBASE (0x2e00) /* Remote Control device ioctl commands */ +#define _HIMEMBASE (0x2f00) /* Himem device ioctl commands*/ #define _WLIOCBASE (0x8b00) /* Wireless modules ioctl network commands */ /* boardctl() commands share the same number space */ @@ -545,6 +546,11 @@ #define _RCIOCVALID(c) (_IOC_TYPE(c)==_RCIOCBASE) #define _RCIOC(nr) _IOC(_RCIOCBASE,nr) +/* Hime drivers **********************************************************/ + +#define _HIMEMIOCVALID(c) (_IOC_TYPE(c) == _HIMEMBASE) +#define _HIMEMIOC(nr) _IOC(_HIMEMBASE, nr) + /* Wireless driver network ioctl definitions ********************************/ /* (see nuttx/include/wireless/wireless.h */ diff --git a/include/nuttx/himem/himem.h b/include/nuttx/himem/himem.h new file mode 100644 index 0000000000..96e6569171 --- /dev/null +++ b/include/nuttx/himem/himem.h @@ -0,0 +1,173 @@ +/**************************************************************************** + * include/nuttx/himem/himem.h + * + ****************************************************************************/ + +#ifndef __INCLUDE_NUTTX_HIMEM_HIMEM_H +#define __INCLUDE_NUTTX_HIMEM_HIMEM_H + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include + +#include + +#ifdef CONFIG_ESP32_SPIRAM + +/**************************************************************************** + * Pre-processor Definitions + ****************************************************************************/ + +/* ESP32 MMU block size */ + +#define ESP_HIMEM_BLKSZ (0x8000) + +/* Command: HIMEMIOC_ALLOC_BLOCKS + * Description: Allocate a certain number of physical RAM blocks. + * Arguments: A structure containing the size of physical block to allocate + * and its esp_himem_handle_t. + * Return: Zero (OK) on success. Minus one will be returned on failure + * with the errno value set appropriately. + */ + +#define HIMEMIOC_ALLOC_BLOCKS _HIMEMIOC(0x0001) + +/* Command: HIMEMIOC_FREE_BLOCKS + * Description: Free a certain number of physical RAM blocks. + * Arguments: A structure containing the size of physical block to allocate + * and its esp_himem_handle_t. + * Return: Zero (OK) on success. Minus one will be returned on failure + * with the errno value set appropriately. + */ + +#define HIMEMIOC_FREE_BLOCKS _HIMEMIOC(0x0002) + +/* Command: HIMEMIOC_ALLOC_MAP_RANGE + * Description: Free the physical RAM blocks + * Arguments: A structure containing the block size and its + * esp_himem_rangehandle_t. + * Return: Zero (OK) on success. Minus one will be returned on failure + * with the errno value set appropriately. + */ + +#define HIMEMIOC_ALLOC_MAP_RANGE _HIMEMIOC(0x0003) + +/* Command: HIMEMIOC_FREE_MAP_RANGE + * Description: Maps the memory addresses to the physical psram range. + * Arguments: A structure containing the esp_himem_handle_t handle, the + * esp_himem_rangehandle_t, the ram offset, the range offset, + * the length, the memory flags and the output pointer. + * Return: Zero (OK) on success. Minus one will be returned on failure + * with the errno value set appropriately. + */ + +#define HIMEMIOC_FREE_MAP_RANGE _HIMEMIOC(0x0004) + +/* Command: HIMEMIOC_MAP + * Description: Maps the memory addresses to the physical psram range. + * Arguments: A structure containing the esp_himem_handle_t handle, the + * esp_himem_rangehandle_t, the ram offset, the range offset, + * the length, the memory flags and the output pointer. + * Return: Zero (OK) on success. Minus one will be returned on failure + * with the errno value set appropriately. + */ + +#define HIMEMIOC_MAP _HIMEMIOC(0x0005) + +/* Command: HIMEMIOC_UNMAP + * Description: Unmaps the memory addresses to the physical psram range. + * Arguments: A structure containing the esp_himem_rangehandle_t, the + * memory pointer and the memory length. + * the length, the memory flags and the output pointer. + * Return: Zero (OK) on success. Minus one will be returned on failure + * with the errno value set appropriately. + */ + +#define HIMEMIOC_UNMAP _HIMEMIOC(0x0006) + +/* Command: HIMEMIOC_GET_PHYS_SIZE + * Description: Get the size of physical external memory + * Arguments: None + * Return: Zero (OK) on success. Minus one will be returned on failure + * with the errno value set appropriately. + */ + +#define HIMEMIOC_GET_PHYS_SIZE _HIMEMIOC(0x0007) + +/* Command: HIMEMIOC_GET_FREE_SIZE + * Description: Get the amount of free memory + * Arguments: None + * Return: Zero (OK) on success. Minus one will be returned on failure + * with the errno value set appropriately. + */ + +#define HIMEMIOC_GET_FREE_SIZE _HIMEMIOC(0x0008) + +/**************************************************************************** + * Public Types + ****************************************************************************/ + +/* Handle for a window of address space */ + +typedef struct esp_himem_rangedata_t +{ + int block_ct; + int block_start; +} esp_himem_rangedata_t; + +/* Handle for a range of physical memory */ + +typedef struct esp_himem_ramdata_t +{ + int block_ct; + uint16_t *block; +} esp_himem_ramdata_t; + +/* Opaque pointers as handles for ram/range data */ + +typedef struct esp_himem_ramdata_t *esp_himem_handle_t; +typedef struct esp_himem_rangedata_t *esp_himem_rangehandle_t; + +/* Structs with the parameters passed to the IOCTLs */ + +struct esp_himem_par +{ + esp_himem_handle_t handle; + esp_himem_rangehandle_t range; + size_t ram_offset; + size_t range_offset; + size_t memfree; + size_t memcnt; + size_t len; + int flags; + uint32_t *ptr; +}; + +/**************************************************************************** + * Public Data + ****************************************************************************/ + +#ifdef __cplusplus +#define EXTERN extern "C" +extern "C" +{ +#else +#define EXTERN extern +#endif + +/**************************************************************************** + * Public Function Prototypes + ****************************************************************************/ + +int esp_himem_init(void); + +#undef EXTERN +#ifdef __cplusplus +} +#endif + +#endif /* CONFIG_ESP32_SPIRAM */ +#endif /* __INCLUDE_NUTTX_HIMEM_HIMEM_H */