xtensa/esp32: Add high memory support to work with PSRAM

This commit is contained in:
Alan C. Assis 2020-11-11 16:22:31 -03:00 committed by Abdelatif Guettouche
parent cbd4e90781
commit f09d103528
7 changed files with 1268 additions and 0 deletions

View File

@ -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"

View File

@ -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)

View File

@ -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 <stdlib.h>
#include <debug.h>
#include <nuttx/kmalloc.h>
#include <nuttx/himem/himem.h>
#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;
}

View File

@ -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 <stddef.h>
#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 */

View File

@ -61,6 +61,7 @@
#include <syslog.h>
#include <sys/errno.h>
#include <nuttx/himem/himem.h>
#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 */

View File

@ -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 */

173
include/nuttx/himem/himem.h Normal file
View File

@ -0,0 +1,173 @@
/****************************************************************************
* include/nuttx/himem/himem.h
*
****************************************************************************/
#ifndef __INCLUDE_NUTTX_HIMEM_HIMEM_H
#define __INCLUDE_NUTTX_HIMEM_HIMEM_H
/****************************************************************************
* Included Files
****************************************************************************/
#include <nuttx/config.h>
#include <nuttx/fs/ioctl.h>
#include <signal.h>
#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 */