Merge branch 'feat/esp_mmu_map_virt' into 'master'

feat(esp_mm): implement a mapping function to map at given virtual address

Closes IDF-15537 and IDF-6132

See merge request espressif/esp-idf!47219
This commit is contained in:
Omar Chebib
2026-04-27 13:44:52 +08:00
4 changed files with 192 additions and 17 deletions

View File

@@ -296,15 +296,57 @@ esp_err_t esp_mmu_map_get_max_consecutive_free_block_size(mmu_mem_caps_t caps, m
return ESP_OK;
}
static int32_t s_find_available_region(mem_region_t *mem_regions, uint32_t region_nums, size_t size, mmu_mem_caps_t caps, mmu_target_t target)
static bool s_is_vaddr_in_region(esp_vaddr_t vaddr, size_t size, mem_region_t *region)
{
return vaddr >= region->start && vaddr + size <= region->end;
}
static bool s_is_block_overlapped(esp_vaddr_t vaddr, size_t size, mem_block_t *block)
{
const esp_vaddr_t end = vaddr + size;
return (vaddr < block->laddr_end) && (end > block->laddr_start);
}
static bool s_is_range_available(esp_vaddr_t vaddr, size_t size, mem_region_t *region)
{
/* Browse all the allocated blocks in the region, looking for the one that contains our start address */
mem_block_t *mem_block = NULL;
TAILQ_FOREACH(mem_block, &region->mem_block_head, entries) {
/* Skip the dummy blocks */
if (mem_block->size == 0) {
continue;
}
if (s_is_block_overlapped(vaddr, size, mem_block)) {
return false;
}
/* Since the entries are sorted, we can stop searching if the start address of the next block is after the end address of our range */
if (mem_block->laddr_start >= vaddr + size) {
break;
}
}
/* If we didn't find any overlap, our range is available */
return true;
}
/* The virtual address is optional, passing 0 will search for the first available region. */
static int32_t s_find_available_region(mem_region_t *mem_regions, uint32_t region_nums,
esp_vaddr_t vaddr_start, size_t size,
mmu_mem_caps_t caps, mmu_target_t target)
{
const esp_vaddr_t linear_addr = vaddr_start & SOC_MMU_LINEAR_ADDR_MASK;
int32_t found_region_id = -1;
for (int i = 0; i < region_nums; i++) {
if (((mem_regions[i].caps & caps) == caps) && ((mem_regions[i].targets & target) == target)) {
if (mem_regions[i].max_slot_size >= size) {
/* Regardless of the virtual address, check if the region has the proper capabilities, has enough space and is the target */
if (((mem_regions[i].caps & caps) == caps) && ((mem_regions[i].targets & target) == target) && mem_regions[i].max_slot_size >= size) {
/* If a virtual address is specified and the region contains it, use this region if the range is available */
if (vaddr_start == 0) {
found_region_id = i;
break;
}
/* If the virtual address is specified, check if the region contains it */
if (s_is_vaddr_in_region(linear_addr, size, &mem_regions[i])) {
return s_is_range_available(linear_addr, size, &mem_regions[i]) ? i : -1;
}
}
}
return found_region_id;
@@ -318,7 +360,7 @@ esp_err_t esp_mmu_map_reserve_block_with_caps(size_t size, mmu_mem_caps_t caps,
size_t aligned_size = ALIGN_UP_BY(size, CONFIG_MMU_PAGE_SIZE);
uint32_t laddr = 0;
int32_t found_region_id = s_find_available_region(s_mmu_ctx.mem_regions, s_mmu_ctx.num_regions, aligned_size, caps, target);
int32_t found_region_id = s_find_available_region(s_mmu_ctx.mem_regions, s_mmu_ctx.num_regions, 0, aligned_size, caps, target);
if (found_region_id == -1) {
ESP_EARLY_LOGE(TAG, "no such vaddr range");
return ESP_ERR_NOT_FOUND;
@@ -461,26 +503,26 @@ static void IRAM_ATTR NOINLINE_ATTR s_do_mapping(mmu_target_t target, uint32_t v
ESP_EARLY_LOGV(TAG, "actual_mapped_len is 0x%"PRIx32, actual_mapped_len);
}
esp_err_t esp_mmu_map(esp_paddr_t paddr_start, size_t size, mmu_target_t target, mmu_mem_caps_t caps, int flags, void **out_ptr)
esp_err_t esp_mmu_map_virt(esp_vaddr_t vaddr_start, esp_paddr_t paddr_start, size_t size, mmu_target_t target, mmu_mem_caps_t caps, int flags, void **out_ptr)
{
esp_err_t ret = ESP_FAIL;
ESP_RETURN_ON_FALSE(out_ptr, ESP_ERR_INVALID_ARG, TAG, "null pointer");
mem_block_t *new_block = NULL;
#if !SOC_SPIRAM_SUPPORTED || CONFIG_IDF_TARGET_ESP32
ESP_RETURN_ON_FALSE(!(target & MMU_TARGET_PSRAM0), ESP_ERR_NOT_SUPPORTED, TAG, "PSRAM is not supported");
#endif
ESP_RETURN_ON_FALSE((paddr_start % CONFIG_MMU_PAGE_SIZE == 0), ESP_ERR_INVALID_ARG, TAG, "paddr must be rounded up to the nearest multiple of CONFIG_MMU_PAGE_SIZE");
ESP_RETURN_ON_FALSE((vaddr_start % CONFIG_MMU_PAGE_SIZE == 0), ESP_ERR_INVALID_ARG, TAG, "vaddr must be rounded up to the nearest multiple of CONFIG_MMU_PAGE_SIZE");
ESP_RETURN_ON_ERROR(s_mem_caps_check(caps), TAG, "invalid caps");
_lock_acquire(&s_mmu_ctx.mutex);
mem_block_t *dummy_head = NULL;
mem_block_t *dummy_tail = NULL;
size_t aligned_size = ALIGN_UP_BY(size, CONFIG_MMU_PAGE_SIZE);
int32_t found_region_id = s_find_available_region(s_mmu_ctx.mem_regions, s_mmu_ctx.num_regions, aligned_size, caps, target);
int32_t found_region_id = s_find_available_region(s_mmu_ctx.mem_regions, s_mmu_ctx.num_regions, vaddr_start, aligned_size, caps, target);
ESP_GOTO_ON_FALSE(found_region_id != -1, ESP_ERR_NOT_FOUND, err, TAG, "no such vaddr range");
//Now we're sure we can find an available block inside a certain region
mem_region_t *found_region = &s_mmu_ctx.mem_regions[found_region_id];
mem_block_t *new_block = NULL;
if (TAILQ_EMPTY(&found_region->mem_block_head)) {
dummy_head = (mem_block_t *)heap_caps_calloc(1, sizeof(mem_block_t), MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT);
@@ -536,7 +578,9 @@ esp_err_t esp_mmu_map(esp_paddr_t paddr_start, size_t size, mmu_target_t target,
* address.
*/
const uint32_t new_paddr_offset = paddr_start - mem_block->paddr_start;
*out_ptr = (void *)mem_block->vaddr_start + new_paddr_offset;
if (out_ptr) {
*out_ptr = (void *)mem_block->vaddr_start + new_paddr_offset;
}
ESP_LOGD(TAG, "paddr block is mapped already, vaddr_start: %p, size: 0x%x", (void *)mem_block->vaddr_start, mem_block->size);
ret = ESP_ERR_INVALID_STATE;
goto err;
@@ -558,16 +602,53 @@ esp_err_t esp_mmu_map(esp_paddr_t paddr_start, size_t size, mmu_target_t target,
size_t max_slot_len = 0;
mem_block_t *found_block = NULL; //This stands for the block we found, whose slot between its prior block is where we will insert the new block to
/* Convert virtual address to linear address if specified */
uint32_t requested_laddr_start = 0;
uint32_t requested_laddr_end = 0;
if (vaddr_start != 0) {
requested_laddr_start = mmu_ll_vaddr_to_laddr(vaddr_start);
requested_laddr_end = requested_laddr_start + aligned_size;
/* Verify the virtual address is valid for this region and target */
uint32_t vaddr_check = 0;
if (caps & MMU_MEM_CAP_EXEC) {
vaddr_check = mmu_ll_laddr_to_vaddr(requested_laddr_start, MMU_VADDR_INSTRUCTION, target);
} else {
vaddr_check = mmu_ll_laddr_to_vaddr(requested_laddr_start, MMU_VADDR_DATA, target);
}
ESP_GOTO_ON_FALSE(vaddr_check == vaddr_start, ESP_ERR_INVALID_ARG, err, TAG, "invalid virtual address for target and caps");
/* Verify the linear address is within the region */
ESP_GOTO_ON_FALSE(requested_laddr_start >= found_region->start && requested_laddr_end <= found_region->end,
ESP_ERR_INVALID_ARG, err, TAG, "virtual address range is outside the region");
ESP_GOTO_ON_FALSE(requested_laddr_start >= found_region->free_head,
ESP_ERR_INVALID_ARG, err, TAG, "virtual address range is referencing reserved memory");
}
TAILQ_FOREACH(mem_block, &found_region->mem_block_head, entries) {
slot_len = mem_block->laddr_start - last_end;
if (!found) {
if (slot_len >= aligned_size) {
/* Ignore dummy blocks and any block with size 0 */
if (!found && slot_len > 0) {
/* If we have no virtual address to map to, we need to find a block that has enough space */
if (vaddr_start == 0 && slot_len >= aligned_size) {
//Found it
found = true;
found_block = mem_block;
slot_len -= aligned_size;
new_block->laddr_start = last_end;
} else if (vaddr_start != 0) {
/* If we have a virtual address to map to, we need to make sure the range [last_end;mem_block->laddr_start[
* contains the range [requested_laddr_start;requested_laddr_end[ */
if (last_end <= requested_laddr_start && requested_laddr_end <= mem_block->laddr_start) {
found = true;
found_block = mem_block;
size_t left_len = requested_laddr_start - last_end;
size_t right_len = mem_block->laddr_start - requested_laddr_end;
slot_len = MAX(left_len, right_len);
new_block->laddr_start = requested_laddr_start;
} else if (last_end <= requested_laddr_start && requested_laddr_start < mem_block->laddr_end) {
/* Another block is already mapping part of our range, trigger an error */
ESP_GOTO_ON_FALSE(false, ESP_ERR_INVALID_ARG, err, TAG, "cannot map virtual address, range overlaps with existing mapping");
}
}
}
@@ -598,16 +679,23 @@ esp_err_t esp_mmu_map(esp_paddr_t paddr_start, size_t size, mmu_target_t target,
//do mapping
s_do_mapping(target, new_block->vaddr_start, paddr_start, aligned_size);
*out_ptr = (void *)new_block->vaddr_start;
if (out_ptr) {
*out_ptr = (void *)new_block->vaddr_start;
}
_lock_release(&s_mmu_ctx.mutex);
return ESP_OK;
err:
if (new_block) {
free(new_block);
}
if (dummy_tail) {
TAILQ_REMOVE(&found_region->mem_block_head, dummy_tail, entries);
free(dummy_tail);
}
if (dummy_head) {
TAILQ_REMOVE(&found_region->mem_block_head, dummy_head, entries);
free(dummy_head);
}
_lock_release(&s_mmu_ctx.mutex);
@@ -615,6 +703,12 @@ err:
return ret;
}
esp_err_t esp_mmu_map(esp_paddr_t paddr_start, size_t size, mmu_target_t target, mmu_mem_caps_t caps, int flags, void **out_ptr)
{
ESP_RETURN_ON_FALSE(out_ptr != NULL, ESP_ERR_INVALID_ARG, TAG, "null pointer");
return esp_mmu_map_virt(0, paddr_start, size, target, caps, flags, out_ptr);
}
#if SOC_MMU_PER_EXT_MEM_TARGET
FORCE_INLINE_ATTR void s_unmapping_operation(uint32_t vaddr_start, uint32_t size)
{

View File

@@ -1,5 +1,5 @@
/*
* SPDX-FileCopyrightText: 2022-2023 Espressif Systems (Shanghai) CO LTD
* SPDX-FileCopyrightText: 2022-2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
@@ -57,6 +57,11 @@ extern "C" {
*/
typedef uint32_t esp_paddr_t;
/**
* @brief Virtual memory type
*/
typedef uint32_t esp_vaddr_t;
/**
* @brief Map a physical memory block to external virtual address block, with given capabilities.
*
@@ -65,7 +70,7 @@ typedef uint32_t esp_paddr_t;
* @param[in] target Physical memory target you're going to map to, see `mmu_target_t`
* @param[in] caps Memory capabilities, see `mmu_mem_caps_t`
* @param[in] flags Mmap flags
* @param[out] out_ptr Start address of the mapped virtual memory
* @param[out] out_ptr Start address of the mapped virtual memory, must not be NULL
*
* @return
* - ESP_OK
@@ -83,6 +88,39 @@ typedef uint32_t esp_paddr_t;
*/
esp_err_t esp_mmu_map(esp_paddr_t paddr_start, size_t size, mmu_target_t target, mmu_mem_caps_t caps, int flags, void **out_ptr);
/**
* @brief Map a physical memory block to a given virtual address, with capabilities.
*
* @param[in] vaddr_start Start address of the virtual memory block to map to.
* - Set to 0 to let the driver choose any available virtual address automatically.
* - Set to a non-zero page-aligned address to request mapping at a specific virtual address.
* The address must not fall within an already mapped virtual address range and must fit
* entirely within an available virtual address region.
* @param[in] paddr_start Start address of the physical memory block, must be page-aligned
* @param[in] size Size to be mapped. Size will be rounded up by to the nearest multiple of MMU page size
* @param[in] target Physical memory target to map to, see `mmu_target_t`
* @param[in] caps Memory capabilities, see `mmu_mem_caps_t`
* @param[in] flags Mapping flags
* @param[out] out_ptr Start address of the mapped virtual memory, can be NULL
*
* @return
* - ESP_OK
* - ESP_ERR_INVALID_ARG: Invalid argument, see printed logs
* - ESP_ERR_NOT_SUPPORTED: Only on ESP32, PSRAM is not a supported physical memory target
* - ESP_ERR_NOT_FOUND: No enough size free block to use
* - ESP_ERR_NO_MEM: Out of memory, this API will allocate some heap memory for internal usage
* - ESP_ERR_INVALID_STATE: Paddr is mapped already. Only occurs when the to-be-mapped paddr block is totally
* enclosed by a previously mapped block (identical scenario behaves similarly).
* In this case, `out_ptr` (if not NULL) is set to the corresponding address within
* the *previously mapped* virtual block, regardless of the `vaddr_start` requested.
* new_block_start new_block_end
* |-------- New Block --------|
* |--------------- Block ---------------|
* block_start block_end
*
*/
esp_err_t esp_mmu_map_virt(esp_vaddr_t vaddr_start, esp_paddr_t paddr_start, size_t size, mmu_target_t target, mmu_mem_caps_t caps, int flags, void **out_ptr);
/**
* @brief Unmap a previously mapped virtual memory block
*

View File

@@ -1,5 +1,5 @@
/*
* SPDX-FileCopyrightText: 2022-2025 Espressif Systems (Shanghai) CO LTD
* SPDX-FileCopyrightText: 2022-2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
@@ -21,6 +21,7 @@
#include "esp_rom_sys.h"
#define TEST_BLOCK_SIZE CONFIG_MMU_PAGE_SIZE
#define ALIGN_DOWN(num, align) (((uint32_t)num) & ~((align) - 1))
const static char *TAG = "MMU_TEST";
@@ -54,6 +55,42 @@ TEST_CASE("Can dump mapped block stats", "[mmu]")
TEST_ESP_OK(esp_mmu_unmap(ptr2));
}
TEST_CASE("Can map partition to a given virtual address", "[mmu]")
{
const esp_partition_t *part = s_get_partition();
ESP_LOGI(TAG, "found partition '%s' at offset 0x%"PRIx32" with size 0x%"PRIx32, part->label, part->address, part->size);
/* Map in the middle of the address space, if the MMU page size is smaller than 64KB, not the whole range is accessible */
const uint32_t range_divider = (64 * 1024) / SOC_MMU_PAGE_SIZE;
const uint32_t virt_range_size = (SOC_DROM_HIGH - SOC_DROM_LOW) / range_divider;
const uint32_t vaddr = SOC_DROM_LOW + virt_range_size / 2;
ESP_LOGI(TAG, "mapping to virtual address 0x%x", vaddr);
void *ptr0 = NULL;
TEST_ESP_OK(esp_mmu_map_virt(vaddr, part->address, TEST_BLOCK_SIZE, MMU_TARGET_FLASH0, MMU_MEM_CAP_READ, 0, &ptr0));
TEST_ESP_OK(esp_mmu_unmap(ptr0));
TEST_ASSERT((uint32_t)ptr0 == vaddr);
}
/**
* When PSRAM XIP is enabled, the flash rodata is mapped to the PSRAM's MMU, so it will not be visible to
* the NOR flash MMU.
*/
#if !CONFIG_SPIRAM_XIP_FROM_PSRAM
TEST_CASE("Cannot map partition to a reserved addresses", "[mmu]")
{
const esp_partition_t *part = s_get_partition();
ESP_LOGI(TAG, "found partition '%s' at offset 0x%"PRIx32" with size 0x%"PRIx32, part->label, part->address, part->size);
/* Map in the address space of the flash ROM area, should fail */
void *ptr0 = NULL;
extern uint8_t _flash_rodata_start[];
const esp_vaddr_t addr = ALIGN_DOWN(_flash_rodata_start, CONFIG_MMU_PAGE_SIZE);
esp_err_t err = esp_mmu_map_virt(addr, part->address, TEST_BLOCK_SIZE, MMU_TARGET_FLASH0, MMU_MEM_CAP_READ, 0, &ptr0);
TEST_ESP_ERR(ESP_ERR_INVALID_ARG, err);
}
#endif
TEST_CASE("Can find paddr caps by any paddr offset", "[mmu]")
{
const esp_partition_t *part = s_get_partition();

View File

@@ -77,7 +77,7 @@ The virtual memory pool is made up with one or multiple virtual memory regions,
- A virtual memory block is a piece of virtual address range that is dynamically mapped.
- A slot is the virtual address range between two virtual memory blocks.
- A physical memory block is a piece of physical address range that is to-be-mapped or already mapped to a virtual memory block.
- Dynamical mapping is done by calling ``esp_mmap`` driver API :cpp:func:`esp_mmu_map`. This API maps the given physical memory block to a virtual memory block which is allocated by the ``esp_mmap`` driver.
- Dynamical mapping is done by calling ``esp_mmap`` driver API :cpp:func:`esp_mmu_map` or :cpp:func:`esp_mmu_map_virt`. These map the given physical memory block to a virtual memory block, either automatically chosen or requested by a specific virtual start address.
Relation Between Memory Blocks
@@ -120,7 +120,7 @@ Driver Behaviour
Memory Map
^^^^^^^^^^
You can call :cpp:func:`esp_mmu_map` to do a dynamical mapping. This API can allocate a certain size of virtual memory block according to the virtual memory capabilities you selected, then map this virtual memory block to the physical memory block as you requested. The ``esp_mmap`` driver supports mapping to one or more types of physical memory, so you should specify the physical memory target when mapping.
You can call :cpp:func:`esp_mmu_map` to do a dynamical mapping, or :cpp:func:`esp_mmu_map_virt` if you need a specific virtual start address. These APIs allocate, or place, a virtual memory block of the required size according to the virtual memory capabilities you selected, then map it to the physical memory block you requested. The ``esp_mmap`` driver supports mapping to one or more types of physical memory, so you should specify the physical memory target when mapping.
By default, physical memory blocks and virtual memory blocks are one-to-one mapped. This means, when calling :cpp:func:`esp_mmu_map`:
@@ -133,6 +133,12 @@ Specially, you can use :c:macro:`ESP_MMU_MMAP_FLAG_PADDR_SHARED`. This flag stan
* If it is the overlapped scenario, this API will allocate a new virtual memory block as requested, then map to the given physical memory block.
Mapping at a chosen virtual address
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
:cpp:func:`esp_mmu_map_virt` maps a physical block the same way as :cpp:func:`esp_mmu_map`, but you can pass the virtual start address as ``vaddr_start``. Passing a value of ``0`` for ``vaddr_start`` will let the driver allocate any suitable free block. With a non-zero ``vaddr_start`` value, the whole range must lie in an unmapped slot for the given capabilities and target, and the address must be valid for the MMU view implied by ``caps``.
Memory Unmap
^^^^^^^^^^^^