esp-idf-hal icon indicating copy to clipboard operation
esp-idf-hal copied to clipboard

How do I put RMT Encoder in IRAM?

Open faern opened this issue 2 years ago • 7 comments
trafficstars

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?

faern avatar Jul 24 '23 09:07 faern

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.

dacut avatar Jul 27 '23 18:07 dacut

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.

MabezDev avatar Jul 27 '23 19:07 MabezDev

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:

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.

dacut avatar Jul 27 '23 20:07 dacut

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.

MabezDev avatar Jul 27 '23 20:07 MabezDev

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?

faern avatar Jul 28 '23 10:07 faern

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.

MabezDev avatar Jul 28 '23 10:07 MabezDev

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.

dacut avatar Aug 06 '23 23:08 dacut

Original question was answered. Please create a new separate issue for enhancement proposals etc.

Vollbrecht avatar Jun 21 '24 09:06 Vollbrecht