esp-idf-hal
esp-idf-hal copied to clipboard
How do I put RMT Encoder in IRAM?
In the official documentation for RMT, it is highly recommended to put the encoding function into IRAM:
The encoding function is running in the ISR context. To speed up the encoding session, it’s high recommend to put the encoding function into IRAM. This can also avoid the cache miss during encoding. -- https://docs.espressif.com/projects/esp-idf/en/v5.1/esp32/api-reference/peripherals/rmt.html
But I don't understand how to do this. Neither the esp_idf_hal::rmt module docs, nor anything in esp_idf_sys or the Rust on ESP Book mentions IRAM at all.
How can I move arbitrary Rust functions to IRAM? And will this be possible with the iterator approach that TxRmtDriver is taking?
I looked at this for I2S. The answer I came to is it's not terribly easy to do today.
The main problem I ran into was determining the size of a function at runtime. LLVM does emit this into the debug symbols. For example, this Rust code:
pub fn add_it(a: i32, b: i32) -> i32 {
a.wrapping_add(b)
}
Becomes this RISCV32 assembly (demangled):
.globl _add_it
.type _add_it, @function
_add_it:
add a0, a0, a1
ret
.Lfunc_end0:
.size _add_it, .Lfunc_end0-_add_it
Note the .size directive at the end. That .Lfunc_end0-_add_it calculation is what we need to determine the size of the function, but the .size directive only stores it into the symbol table.
Ideally, we'd get another symbol that does end up in the resulting object, like:
.globl _add_it_size
_add_it_size:
.dc.l .Lfunc_end0-_add_it
.size _add_it_size, 4
This requires hooking into LLVM somehow, though. Would likely require a patch to LLVM since this intermediate assembly is never actually emitted in the normal workflow, just the MIR.
Barring this, the main alternative I can think of is to define an external symbol that gets resolved later, scrape this out of the debug info using readelf, then set that external symbol in a higher level crate or post-build step.
So the CONFIG_RMT_ISR_IRAM_SAFE config option will handle the C side of putting the required methods in IRAM, so we then just have to worry about the rust side when we detect this option. This isn't trivial at all tbh, it's quite difficult to ensure that all the code/subroutine calls inside a function are all in IRAM too. In my experience its dependent on a number of things, including optimization levels as to whether the functions actually end up in IRAM.
Interesting... it looks like if you can get the function into a section whose name is in the form .iram1.#, there's some magic that copies that function into IRAM.
If I'm reading this correctly, CONFIG_RMT_ISR_IRAM_SAFE only allocates data structures in IRAM:
- By adding
MALLOC_CAP_8BITtoRMT_MEM_MALLOC_CAPS - By adding
ESP_INTR_FLAG_IRAMtoRMT_INTR_ALLOC_FLAG
Actually putting a function into IRAM is done by decorating it with IRAM_ATTR, e.g.:
static void IRAM_ATTR rmt_tx_mark_eof(rmt_tx_channel_t *tx_chan). IRAM_ATTR is defined in esp_common/include/esp_attr.h as:
#define IRAM_ATTR _SECTION_ATTR_IMPL(".iram1", __COUNTER__)
#define _SECTION_ATTR_IMPL(SECTION, COUNTER) __attribute__((section(SECTION "." _COUNTER_STRINGIFY(COUNTER))))
#define _COUNTER_STRINGIFY(COUNTER) #COUNTER
I can't find the magic that does the copying, though. Maybe it's in the ROM? @MabezDev, do you know where this magic happens? (grep didn't turn up anything other than linker scripts for me.)
Both C and Rust have the issue of calling from IRAM into non-IRAM; C is just a bit more predictable in its code generation here. The C code does take care not to accept callback parameters it can't verify as being IRAM-safe, e.g. in rmt_tx_register_event_callbacks.
The copying code will be buried deep in the startup code for esp-idf, but provided we also put our Rust functions there, i.e by using #[link_section = ".iram1"] then it should work, provided the code within that function is also in IRAM.
Great research both of you. I will experiment a bit with this. Do you know how to verify what functions are placed in IRAM for a given compiled firmware/app?
Do you know how to verify what functions are placed in IRAM for a given compiled firmware/app?
Yes, you can use nm to find function names and their address, I'm not sure what chip you're using but you'll need to ensure that the address of the function is in the IRAM address space.
Adding on to this, as a note for myself and future spelunkers:
The code that is responsible for moving the function into IRAM is in the second stage bootloader, in process_segment, whose signature is:
process_segment(int index, uint32_t flash_addr, esp_image_segment_header_t *header, bool silent, bool do_load, bootloader_sha256_handle_t sha_handle, uint32_t *checksum)
esp_image_segment_header_t just has a load address and a length:
typedef struct {
uint32_t load_addr; /*!< Address of segment */
uint32_t data_len; /*!< Length of data */
} esp_image_segment_header_t;
This calls process_segment_data, which bootloader_mmaps the flash data, memcpys it into the destination, then bootloader_munmaps it.
The segment headers are created by the flash utility (e.g. espflash's elf.rs for translating the ELF binary into the firmware format).
I'm a bit surprised with the amount of code around memory mapping into the flash. It handles virtual to physical address translation cases, which I didn't think were a thing on any ESP32 chips; I thought addresses were directly mapped, and the flash accessible by the SPI bus (with cache possibly mediating accesses).
If you're using ESP32-C3/-C6 direct boot (no second-stage bootloader, just ROM into your app code) or have a custom second-stage bootloader, you're responsible for doing all of this yourself.
Original question was answered. Please create a new separate issue for enhancement proposals etc.