From dd40123129bc5670ae081c31d519907f0125e4db Mon Sep 17 00:00:00 2001 From: "Michael (XIAO Xufeng)" Date: Sun, 1 Aug 2021 14:23:36 +0800 Subject: [PATCH] bootloader: add xmc spi_flash startup flow to improve reliability --- components/bootloader/Kconfig.projbuild | 9 + .../include/bootloader_flash.h | 15 ++ .../bootloader_flash_priv.h | 10 + .../bootloader_support/src/bootloader_flash.c | 197 ++++++++++++++++-- .../src/esp32/bootloader_esp32.c | 5 + .../src/esp32c3/bootloader_esp32c3.c | 5 + .../src/esp32h2/bootloader_esp32h2.c | 5 + .../src/esp32s2/bootloader_esp32s2.c | 5 + .../src/esp32s3/bootloader_esp32s3.c | 5 + .../bootloader_support/src/flash_qio_mode.c | 8 - components/spi_flash/test/test_spi_flash.c | 20 ++ 11 files changed, 254 insertions(+), 30 deletions(-) diff --git a/components/bootloader/Kconfig.projbuild b/components/bootloader/Kconfig.projbuild index 15f6183c4c..d6bd8b30cb 100644 --- a/components/bootloader/Kconfig.projbuild +++ b/components/bootloader/Kconfig.projbuild @@ -386,6 +386,15 @@ menu "Bootloader config" in this area of memory, you can increase it. It must be a multiple of 4 bytes. This area (rtc_retain_mem_t) is reserved and has access from the bootloader and an application. + config BOOTLOADER_FLASH_XMC_SUPPORT + bool "Enable the support for flash chips of XMC (READ HELP FIRST)" + default y + help + Perform the startup flow recommended by XMC. Please consult XMC for the details of this flow. + XMC chips will be forbidden to be used, when this option is disabled. + + DON'T DISABLE THIS UNLESS YOU KNOW WHAT YOU ARE DOING. + endmenu # Bootloader diff --git a/components/bootloader_support/include/bootloader_flash.h b/components/bootloader_support/include/bootloader_flash.h index 61afc2a337..49e457f372 100644 --- a/components/bootloader_support/include/bootloader_flash.h +++ b/components/bootloader_support/include/bootloader_flash.h @@ -14,6 +14,14 @@ extern "C" { #endif +/** + * @brief Read flash ID by sending RDID command (0x9F) + * @return flash raw ID + * mfg_id = (ID >> 16) & 0xFF; + flash_id = ID & 0xffff; + */ +uint32_t bootloader_read_flash_id(void); + #if SOC_CACHE_SUPPORT_WRAP /** * @brief Set the burst mode setting command for specified wrap mode. @@ -32,6 +40,13 @@ esp_err_t bootloader_flash_wrap_set(spi_flash_wrap_mode_t mode); */ esp_err_t bootloader_flash_unlock(void); +/** + * @brief Startup flow recommended by XMC. Call at startup before any erase/write operation. + * + * @return ESP_OK When startup successfully, otherwise ESP_FAIL (indiciating you should reboot before erase/write). + */ +esp_err_t bootloader_flash_xmc_startup(void); + #ifdef __cplusplus } #endif diff --git a/components/bootloader_support/include_bootloader/bootloader_flash_priv.h b/components/bootloader_support/include_bootloader/bootloader_flash_priv.h index 7d703b84fa..d898cae124 100644 --- a/components/bootloader_support/include_bootloader/bootloader_flash_priv.h +++ b/components/bootloader_support/include_bootloader/bootloader_flash_priv.h @@ -29,6 +29,7 @@ #define CMD_RDSR 0x05 #define CMD_RDSR2 0x35 /* Not all SPI flash uses this command */ #define CMD_OTPEN 0x3A /* Enable OTP mode, not all SPI flash uses this command */ +#define CMD_RDSFDP 0x5A /* Read the SFDP of the flash */ #define CMD_WRAP 0x77 /* Set burst with wrap command */ #define CMD_RESUME 0x7A /* Resume command to clear flash suspend bit */ @@ -156,6 +157,15 @@ static inline uint32_t bootloader_cache_pages_to_map(uint32_t size, uint32_t vad */ uint32_t bootloader_execute_flash_command(uint8_t command, uint32_t mosi_data, uint8_t mosi_len, uint8_t miso_len); +/** + * @brief Read the SFDP of the flash + * + * @param sfdp_addr Address of the parameter to read + * @param miso_byte_num Bytes to read + * @return The read SFDP, little endian, 4 bytes at most + */ +uint32_t bootloader_flash_read_sfdp(uint32_t sfdp_addr, unsigned int miso_byte_num); + /** * @brief Enable the flash write protect (WEL bit). */ diff --git a/components/bootloader_support/src/bootloader_flash.c b/components/bootloader_support/src/bootloader_flash.c index 8119bdfa1f..708e7dcbf5 100644 --- a/components/bootloader_support/src/bootloader_flash.c +++ b/components/bootloader_support/src/bootloader_flash.c @@ -122,7 +122,7 @@ esp_err_t bootloader_flash_erase_range(uint32_t start_addr, uint32_t size) return spi_flash_erase_range(start_addr, size); } -#else +#else //BOOTLOADER_BUILD /* Bootloader version, uses ROM functions only */ #if CONFIG_IDF_TARGET_ESP32 #include "esp32/rom/spi_flash.h" @@ -481,7 +481,8 @@ esp_err_t bootloader_flash_erase_range(uint32_t start_addr, uint32_t size) return spi_to_esp_err(rc); } -#endif +#endif // BOOTLOADER_BUILD + FORCE_INLINE_ATTR bool is_issi_chip(const esp_rom_spiflash_chip_t* chip) { @@ -563,29 +564,47 @@ esp_err_t IRAM_ATTR __attribute__((weak)) bootloader_flash_unlock(void) return err; } +/* dummy_len_plus values defined in ROM for SPI flash configuration */ #ifndef g_rom_spiflash_dummy_len_plus // ESP32-C3 uses a macro to access ROM data here extern uint8_t g_rom_spiflash_dummy_len_plus[]; #endif -uint32_t IRAM_ATTR bootloader_execute_flash_command(uint8_t command, uint32_t mosi_data, uint8_t mosi_len, uint8_t miso_len) +IRAM_ATTR static uint32_t bootloader_flash_execute_command_common( + uint8_t command, + uint32_t addr_len, uint32_t address, + uint8_t dummy_len, + uint8_t mosi_len, uint32_t mosi_data, + uint8_t miso_len) { + assert(mosi_len <= 32); + assert(miso_len <= 32); uint32_t old_ctrl_reg = SPIFLASH.ctrl.val; #if CONFIG_IDF_TARGET_ESP32 SPIFLASH.ctrl.val = SPI_WP_REG_M; // keep WP high while idle, otherwise leave DIO mode #else SPIFLASH.ctrl.val = SPI_MEM_WP_REG_M; // keep WP high while idle, otherwise leave DIO mode #endif - SPIFLASH.user.usr_dummy = 0; - SPIFLASH.user.usr_addr = 0; + //command phase SPIFLASH.user.usr_command = 1; SPIFLASH.user2.usr_command_bitlen = 7; - SPIFLASH.user2.usr_command_value = command; - SPIFLASH.user.usr_miso = miso_len > 0; + //addr phase + SPIFLASH.user.usr_addr = addr_len > 0; + SPIFLASH.user1.usr_addr_bitlen = addr_len - 1; #if CONFIG_IDF_TARGET_ESP32 - SPIFLASH.miso_dlen.usr_miso_dbitlen = miso_len ? (miso_len - 1) : 0; + SPIFLASH.addr = (addr_len > 0)? (address << (32-addr_len)) : 0; #else - SPIFLASH.miso_dlen.usr_miso_bit_len = miso_len ? (miso_len - 1) : 0; + SPIFLASH.addr = address; #endif + //dummy phase + if (miso_len > 0) { + uint32_t total_dummy = dummy_len + g_rom_spiflash_dummy_len_plus[1]; + SPIFLASH.user.usr_dummy = total_dummy > 0; + SPIFLASH.user1.usr_dummy_cyclelen = total_dummy - 1; + } else { + SPIFLASH.user.usr_dummy = 0; + SPIFLASH.user1.usr_dummy_cyclelen = 0; + } + //output data SPIFLASH.user.usr_mosi = mosi_len > 0; #if CONFIG_IDF_TARGET_ESP32 SPIFLASH.mosi_dlen.usr_mosi_dbitlen = mosi_len ? (mosi_len - 1) : 0; @@ -593,24 +612,50 @@ uint32_t IRAM_ATTR bootloader_execute_flash_command(uint8_t command, uint32_t mo SPIFLASH.mosi_dlen.usr_mosi_bit_len = mosi_len ? (mosi_len - 1) : 0; #endif SPIFLASH.data_buf[0] = mosi_data; - - if (g_rom_spiflash_dummy_len_plus[1]) { - /* When flash pins are mapped via GPIO matrix, need a dummy cycle before reading via MISO */ - if (miso_len > 0) { - SPIFLASH.user.usr_dummy = 1; - SPIFLASH.user1.usr_dummy_cyclelen = g_rom_spiflash_dummy_len_plus[1] - 1; - } else { - SPIFLASH.user.usr_dummy = 0; - SPIFLASH.user1.usr_dummy_cyclelen = 0; - } - } + //input data + SPIFLASH.user.usr_miso = miso_len > 0; +#if CONFIG_IDF_TARGET_ESP32 + SPIFLASH.miso_dlen.usr_miso_dbitlen = miso_len ? (miso_len - 1) : 0; +#else + SPIFLASH.miso_dlen.usr_miso_bit_len = miso_len ? (miso_len - 1) : 0; +#endif SPIFLASH.cmd.usr = 1; while (SPIFLASH.cmd.usr != 0) { } - SPIFLASH.ctrl.val = old_ctrl_reg; - return SPIFLASH.data_buf[0]; + + uint32_t ret = SPIFLASH.data_buf[0]; + if (miso_len < 32) { + //set unused bits to 0 + ret &= ~(UINT32_MAX << miso_len); + } + return ret; +} + +uint32_t IRAM_ATTR bootloader_execute_flash_command(uint8_t command, uint32_t mosi_data, uint8_t mosi_len, uint8_t miso_len) +{ + const uint8_t addr_len = 0; + const uint8_t address = 0; + const uint8_t dummy_len = 0; + + return bootloader_flash_execute_command_common(command, addr_len, address, + dummy_len, mosi_len, mosi_data, miso_len); +} + +// cmd(0x5A) + 24bit address + 8 cycles dummy +uint32_t IRAM_ATTR bootloader_flash_read_sfdp(uint32_t sfdp_addr, unsigned int miso_byte_num) +{ + assert(miso_byte_num <= 4); + const uint8_t command = CMD_RDSFDP; + const uint8_t addr_len = 24; + const uint8_t dummy_len = 8; + const uint8_t mosi_len = 0; + const uint32_t mosi_data = 0; + const uint8_t miso_len = miso_byte_num * 8; + + return bootloader_flash_execute_command_common(command, addr_len, sfdp_addr, + dummy_len, mosi_len, mosi_data, miso_len); } void bootloader_enable_wp(void) @@ -618,6 +663,13 @@ void bootloader_enable_wp(void) bootloader_execute_flash_command(CMD_WRDI, 0, 0, 0); /* Exit OTP mode */ } +uint32_t IRAM_ATTR bootloader_read_flash_id(void) +{ + uint32_t id = bootloader_execute_flash_command(CMD_RDID, 0, 0, 24); + id = ((id & 0xff) << 16) | ((id >> 16) & 0xff) | (id & 0xff00); + return id; +} + #if SOC_CACHE_SUPPORT_WRAP esp_err_t bootloader_flash_wrap_set(spi_flash_wrap_mode_t mode) { @@ -649,3 +701,104 @@ esp_err_t bootloader_flash_wrap_set(spi_flash_wrap_mode_t mode) return ESP_OK; } #endif //SOC_CACHE_SUPPORT_WRAP + +/******************************************************************************* + * XMC startup flow + ******************************************************************************/ + +#define XMC_SUPPORT CONFIG_BOOTLOADER_FLASH_XMC_SUPPORT +#define XMC_VENDOR_ID 0x20 + +#if BOOTLOADER_BUILD +#define BOOTLOADER_FLASH_LOG(level, ...) ESP_LOG##level(TAG, ##__VA_ARGS__) +#else +static DRAM_ATTR char bootloader_flash_tag[] = "bootloader_flash"; +#define BOOTLOADER_FLASH_LOG(level, ...) ESP_DRAM_LOG##level(bootloader_flash_tag, ##__VA_ARGS__) +#endif + +#if XMC_SUPPORT +//strictly check the model +static IRAM_ATTR bool is_xmc_chip_strict(uint32_t rdid) +{ + uint32_t vendor_id = BYTESHIFT(rdid, 2); + uint32_t mfid = BYTESHIFT(rdid, 1); + uint32_t cpid = BYTESHIFT(rdid, 0); + + if (vendor_id != XMC_VENDOR_ID) { + return false; + } + + bool matched = false; + if (mfid == 0x40) { + if (cpid >= 0x13 && cpid <= 0x20) { + matched = true; + } + } else if (mfid == 0x41) { + if (cpid >= 0x17 && cpid <= 0x20) { + matched = true; + } + } else if (mfid == 0x50) { + if (cpid >= 0x15 && cpid <= 0x16) { + matched = true; + } + } + return matched; +} + +esp_err_t IRAM_ATTR bootloader_flash_xmc_startup(void) +{ + // If the RDID value is a valid XMC one, may skip the flow + const bool fast_check = true; + if (fast_check && is_xmc_chip_strict(g_rom_flashchip.device_id)) { + BOOTLOADER_FLASH_LOG(D, "XMC chip detected by RDID (%08X), skip.", g_rom_flashchip.device_id); + return ESP_OK; + } + + // Check the Manufacturer ID in SFDP registers (JEDEC standard). If not XMC chip, no need to run the flow + const int sfdp_mfid_addr = 0x10; + uint8_t mf_id = (bootloader_flash_read_sfdp(sfdp_mfid_addr, 1) & 0xff); + if (mf_id != XMC_VENDOR_ID) { + BOOTLOADER_FLASH_LOG(D, "non-XMC chip detected by SFDP Read (%02X), skip.", mf_id); + return ESP_OK; + } + + BOOTLOADER_FLASH_LOG(I, "XM25QHxxC startup flow"); + // Enter DPD + bootloader_execute_flash_command(0xB9, 0, 0, 0); + // Enter UDPD + bootloader_execute_flash_command(0x79, 0, 0, 0); + // Exit UDPD + bootloader_execute_flash_command(0xFF, 0, 0, 0); + // Delay tXUDPD + esp_rom_delay_us(2000); + // Release Power-down + bootloader_execute_flash_command(0xAB, 0, 0, 0); + esp_rom_delay_us(20); + // Read flash ID and check again + g_rom_flashchip.device_id = bootloader_read_flash_id(); + if (!is_xmc_chip_strict(g_rom_flashchip.device_id)) { + BOOTLOADER_FLASH_LOG(E, "XMC flash startup fail"); + return ESP_FAIL; + } + + return ESP_OK; +} + +#else +//only compare the vendor id +static IRAM_ATTR bool is_xmc_chip(uint32_t rdid) +{ + uint32_t vendor_id = (rdid >> 16) & 0xFF; + return (vendor_id == XMC_VENDOR_ID); +} + +esp_err_t IRAM_ATTR bootloader_flash_xmc_startup(void) +{ + if (is_xmc_chip(g_rom_flashchip.device_id)) { + BOOTLOADER_FLASH_LOG(E, "XMC chip detected (%08X) while support disabled.", g_rom_flashchip.device_id); + return ESP_FAIL; + } + return ESP_OK; +} + +#endif //XMC_SUPPORT diff --git a/components/bootloader_support/src/esp32/bootloader_esp32.c b/components/bootloader_support/src/esp32/bootloader_esp32.c index f74d114006..358e398fe5 100644 --- a/components/bootloader_support/src/esp32/bootloader_esp32.c +++ b/components/bootloader_support/src/esp32/bootloader_esp32.c @@ -387,6 +387,11 @@ esp_err_t bootloader_init(void) bootloader_print_banner(); // update flash ID bootloader_flash_update_id(); + // Check and run XMC startup flow + if ((ret = bootloader_flash_xmc_startup()) != ESP_OK) { + ESP_LOGE(TAG, "failed when running XMC startup flow, reboot!"); + goto err; + } // read bootloader header if ((ret = bootloader_read_bootloader_header()) != ESP_OK) { goto err; diff --git a/components/bootloader_support/src/esp32c3/bootloader_esp32c3.c b/components/bootloader_support/src/esp32c3/bootloader_esp32c3.c index 7f033c0560..7f022f6560 100644 --- a/components/bootloader_support/src/esp32c3/bootloader_esp32c3.c +++ b/components/bootloader_support/src/esp32c3/bootloader_esp32c3.c @@ -309,6 +309,11 @@ esp_err_t bootloader_init(void) bootloader_print_banner(); // update flash ID bootloader_flash_update_id(); + // Check and run XMC startup flow + if ((ret = bootloader_flash_xmc_startup()) != ESP_OK) { + ESP_LOGE(TAG, "failed when running XMC startup flow, reboot!"); + goto err; + } // read bootloader header if ((ret = bootloader_read_bootloader_header()) != ESP_OK) { goto err; diff --git a/components/bootloader_support/src/esp32h2/bootloader_esp32h2.c b/components/bootloader_support/src/esp32h2/bootloader_esp32h2.c index d763b685da..075bd6a8c4 100644 --- a/components/bootloader_support/src/esp32h2/bootloader_esp32h2.c +++ b/components/bootloader_support/src/esp32h2/bootloader_esp32h2.c @@ -301,6 +301,11 @@ esp_err_t bootloader_init(void) bootloader_print_banner(); // update flash ID bootloader_flash_update_id(); + // Check and run XMC startup flow + if ((ret = bootloader_flash_xmc_startup()) != ESP_OK) { + ESP_LOGE(TAG, "failed when running XMC startup flow, reboot!"); + goto err; + } // read bootloader header if ((ret = bootloader_read_bootloader_header()) != ESP_OK) { goto err; diff --git a/components/bootloader_support/src/esp32s2/bootloader_esp32s2.c b/components/bootloader_support/src/esp32s2/bootloader_esp32s2.c index 032221b2b1..177ce835c1 100644 --- a/components/bootloader_support/src/esp32s2/bootloader_esp32s2.c +++ b/components/bootloader_support/src/esp32s2/bootloader_esp32s2.c @@ -307,6 +307,11 @@ esp_err_t bootloader_init(void) bootloader_print_banner(); // update flash ID bootloader_flash_update_id(); + // Check and run XMC startup flow + if ((ret = bootloader_flash_xmc_startup()) != ESP_OK) { + ESP_LOGE(TAG, "failed when running XMC startup flow, reboot!"); + goto err; + } // read bootloader header if ((ret = bootloader_read_bootloader_header()) != ESP_OK) { goto err; diff --git a/components/bootloader_support/src/esp32s3/bootloader_esp32s3.c b/components/bootloader_support/src/esp32s3/bootloader_esp32s3.c index 1bb984fd21..e3a1bbf07a 100644 --- a/components/bootloader_support/src/esp32s3/bootloader_esp32s3.c +++ b/components/bootloader_support/src/esp32s3/bootloader_esp32s3.c @@ -328,6 +328,11 @@ esp_err_t bootloader_init(void) bootloader_print_banner(); // update flash ID bootloader_flash_update_id(); + // Check and run XMC startup flow + if ((ret = bootloader_flash_xmc_startup()) != ESP_OK) { + ESP_LOGE(TAG, "failed when running XMC startup flow, reboot!"); + goto err; + } // read bootloader header if ((ret = bootloader_read_bootloader_header()) != ESP_OK) { goto err; diff --git a/components/bootloader_support/src/flash_qio_mode.c b/components/bootloader_support/src/flash_qio_mode.c index 83b9377f52..0447627ab9 100644 --- a/components/bootloader_support/src/flash_qio_mode.c +++ b/components/bootloader_support/src/flash_qio_mode.c @@ -105,14 +105,6 @@ static esp_err_t enable_qio_mode(read_status_fn_t read_status_fn, The command passed here is always the on-the-wire command given to the SPI flash unit. */ -/* dummy_len_plus values defined in ROM for SPI flash configuration */ -uint32_t bootloader_read_flash_id(void) -{ - uint32_t id = bootloader_execute_flash_command(CMD_RDID, 0, 0, 24); - id = ((id & 0xff) << 16) | ((id >> 16) & 0xff) | (id & 0xff00); - return id; -} - void bootloader_enable_qio_mode(void) { uint32_t raw_flash_id; diff --git a/components/spi_flash/test/test_spi_flash.c b/components/spi_flash/test/test_spi_flash.c index 3cbca1f790..a3672eee3b 100644 --- a/components/spi_flash/test/test_spi_flash.c +++ b/components/spi_flash/test/test_spi_flash.c @@ -15,6 +15,8 @@ #include "esp_rom_sys.h" #include "esp_timer.h" +#include "bootloader_flash.h" //for bootloader_flash_xmc_startup + #include "sdkconfig.h" #if CONFIG_IDF_TARGET_ESP32 #include "esp32/rom/spi_flash.h" @@ -427,3 +429,21 @@ TEST_CASE("rom unlock will not erase QE bit", "[spi_flash]") TEST_ASSERT(status & 0x40); } #endif + +static IRAM_ATTR NOINLINE_ATTR void test_xmc_startup(void) +{ + extern void spi_flash_disable_interrupts_caches_and_other_cpu(void); + extern void spi_flash_enable_interrupts_caches_and_other_cpu(void); + esp_err_t ret = ESP_OK; + + spi_flash_disable_interrupts_caches_and_other_cpu(); + ret = bootloader_flash_xmc_startup(); + spi_flash_enable_interrupts_caches_and_other_cpu(); + + TEST_ASSERT_EQUAL(ESP_OK, ret); +} + +TEST_CASE("bootloader_flash_xmc_startup can be called when cache disabled", "[spi_flash]") +{ + test_xmc_startup(); +}