espressif / esp-idf

Espressif IoT Development Framework. Official development framework for Espressif SoCs.
Apache License 2.0
13.65k stars 7.29k forks source link

QIO/QOUT selectable in menuconfig, yet ignored during binary creation (IDFGH-8035) #9542

Open kriegste opened 2 years ago

kriegste commented 2 years ago

Environment

Problem Description and Steps to reproduce

In menuconfig "Serial flasher config / Flash SPI mode" it says "Mode the flash chip is flashed in, as well as the default mode for the binary to run in.". When I choose "QIO" (or "QOUT") and compile the project the resulting binary's header is wrong: The "spi_mode" field of esp_image_header_t is ESP_IMAGE_SPIMODEDIO.

After selecting "QIO" in menuconfig the file sdkconfig contains these lines:

CONFIG_ESPTOOLPY_FLASHMODE_QIO=y
# CONFIG_ESPTOOLPY_FLASHMODE_QOUT is not set
# CONFIG_ESPTOOLPY_FLASHMODE_DIO is not set
# CONFIG_ESPTOOLPY_FLASHMODE_DOUT is not set
CONFIG_ESPTOOLPY_FLASHMODE="dio"

I think this is where the problem takes its course. Interestingly, when I manually edit sdkconfig and set CONFIG_ESPTOOLPY_FLASHMODE="qio", during the build process it is reset to "dio". The resulting binary still runs in "dio" mode.

Expected Behavior

The created binary has the selected spi_mode set in its header and also runs in the selected spi_mode (after being flashed OTA).

Code to reproduce this issue

You can use any example project.

igrr commented 2 years ago

Hi @kriegste,

Unfortunately the case you describe (bootloader & app initially built with DIO mode, later app updated to QIO mode) is not supported.

The switch from DIO to QIO mode currently can only be done by the 2nd stage bootloader. CONFIG_ESPTOOLPY_FLASHMODE_QIO option is used in the 2nd stage bootloader to control whether the code paths related to the switch from DIO to QIO mode are included. If the bootloader was not initially built with QIO mode support, there is no provision to enable QIO mode later at run time.

(Unlike the QIO flash mode, flash frequency is recorded in the application binary image header and can be upgraded this way, including via OTA.)

kriegste commented 2 years ago

But also the bootloader binary is never built in QIO/QOUT mode, even when selected. That is all the more unfortunate when it impedes any correction.

The perfect solution would be a bootloader which automatically detects the fastest mode.

igrr commented 2 years ago

The 2nd stage bootloader binary header is indeed not set to QIO/QOUT mode, since the ROM bootloader doesn't have all the information required to enable QIO mode for every type of flash. The ROM bootloader loads the 2nd stage bootloader using DIO mode, then the 2nd stage bootloader (if built with the CONFIG_ESPTOOLPY_FLASHMODE_QIO option) configures the flash chip and enables the QIO mode.

The perfect solution would be a bootloader which automatically detects the fastest mode.

The main reason why we don't do this is that in most cases the hardware design isn't an unknown variable when the bootloader is flashed into the product. Therefore the bootloader can be built for the specific flash chip and module/board design. Any code that performs run-time detection would add complexity, which we are trying to avoid in the 2nd stage bootloader since it isn't updatable in the field.

Upgrading flash frequency from 40 to 80 MHz is the only exception here. Sometimes at the board production time there isn't enough test data to say whether 80 MHz flash frequency will work reliably. In this case there is an option to do the production run with the 40 MHz setting and later update the app with 80 MHz setting in the header.

kriegste commented 2 years ago

Is the app binary header ever set to QIO or is this information stored elsewhere? I have never been able to build an app with a QIO header. If this is true, how can I (the app) find out which flash mode is currently being used?

igrr commented 2 years ago

The app binary header is never set to QIO at the moment. The information whether QIO mode is enabled is stored in the 2nd stage bootloader itself, in the form of code paths which get conditionally compiled. For example, https://github.com/espressif/esp-idf/blob/fde4afc67a2035a1f461dc511cd85dde1ef90368/components/bootloader_support/src/esp32s3/bootloader_esp32s3.c#L220-L222 Additionally, in a few places in the app itself we have to use compile-time checks of CONFIG_ESPTOOLPY_FLASHMODE_QIO, so the app binary does depend on the selected read mode, just not via its header.

To check the currently used flash read mode you can use esp_flash_default_chip->read_mode (requires #include "esp_flash.h").

kriegste commented 2 years ago

Thanks for the thorough explanations! So my app might already run in QIO mode after all...

In any case, you really need to fix the documentation and mention these kind of surprises. https://docs.espressif.com/projects/esp-idf/en/v4.4.2/esp32/api-guides/bootloader.html#spi-flash-configuration

The First stage bootloader in ROM reads the Second stage bootloader header information from flash and uses this infomation to load the rest of the Second stage bootloader from flash. However, at this time the system clock speed is lower than configured and not all flash modes are supported. When the Second stage bootloader then runs, it will reconfigure the flash using values read from the currently selected app binary’s header (and NOT from the Second stage bootloader header). This allows an OTA update to change the SPI flash settings in use.

igrr commented 2 years ago

Thanks for noticing this! Indeed this paragraph was written with the reference to flash frequency and flash size in mind. We'll correct it to say that flash mode cannot be changed that way.

kriegste commented 2 years ago

And also the documentation parts where the app header format is mentioned. esp_image_header_t has the esp_image_spi_mode_t field and one can easily think that this can become ESP_IMAGE_SPI_MODE_QIO.

kriegste commented 2 years ago

Is there a more direct way to get the currently used SPI flash frequency? Just like I can obtain the mode using the mentioned esp_flash_default_chip->read_mode. Or is reading the header the only way?

readmodifywrite commented 2 years ago

@igrr I'd like to add a bit to the confusion here.

I'm in a similar situation, bootloader is set for DIO and 40 MHz. My bootloader is build against IDF 3.3.2.

What I've noticed is that my application, build against IDF 4.4.2 and still reporting DIO mode, is running the flash extremely slow. For example, a start up CRC check of the firmware image (approx 800,000 bytes) is taking 16 seconds.

Then I change to QIO mode in the IDF and reflash the app. The bootloader trace logs still report DIO mode (since the booltoader didn't change, this is correct, only the app was reflashed), while the application trace logs report QIO. But the real kicker? That CRC check now takes 600 ms. More than 25 times faster. This improvement is not limited to the CRC check, all of my file system scan operations are now 25x faster. This is so bizarre that I've tried it multiple times just to confirm this is the case.

I can also note that the bootloader performs a similar CRC check and is also extremely fast, < 1 second. This is DIO on IDF 3.3.2. So, maybe there's a new bug here in 4.x somehow? I probably would have noticed 16 second scan times back when I was using 3.3.x on my application.

I just filed #9901 to try to sort out my confusion of what the correct settings are supposed to be for the WROOM module. But clearly something else is going on too. I can file this as a new issue if you prefer, but this issue seems related and furthermore, my experience differs completely from the assertions being made here. Not only can you change the flash mode to QIO in the app regardless of the bootloader configuration, it makes a completely unexplainable and shocking difference in performance.

What I'm hoping is that I can leave my 2nd stage bootloaders alone and update my apps to correct this performance issue. #9901 is my attempt to make sure all modules have the same flash chip, and therefore, support the same features (QIO).

tve commented 10 months ago

@kriegste : have you found a way to get the flash speed at run-time?

kriegste commented 10 months ago

No. I gave up. Instead, I measure how long it takes loading my config from flash. Looking at the times (250-400us) I can tell if everything is as it should be or not.

tve commented 10 months ago

Thanks for the confirmation. Bummer. I resorted to duplicating a couple of the #ifdefs :

    // flash speed and read mode diagnostic, see esp_flash_spi_init.c for ifdefs...
    // https://github.com/espressif/esp-idf/issues/9542
    bool quad = esp_flash_is_quad_mode(esp_flash_default_chip);
#ifdef CONFIG_ESPTOOLPY_FLASHFREQ_80M
    uint8_t speed = 80;
#elif defined(CONFIG_ESPTOOLPY_FLASHFREQ_40M)
    uint8_t speed = 40;
#else
    uint8_t speed = 0;
#endif
    printf("Flash speed: %dMhz, read mode: %s\n", speed, quad ? "quad" : "dual");

Definitely suboptimal...