Merge branch 'bugfix/xmc_overerase_v3.3' into 'release/v3.3'

bootloader: add xmc spi_flash startup flow to improve reliability (v3.3)

See merge request espressif/esp-idf!14780
This commit is contained in:
Michael (XIAO Xufeng) 2022-01-02 16:09:15 +00:00
commit 9fc4057e9e
5 changed files with 237 additions and 21 deletions

View File

@ -233,6 +233,15 @@ menu "Bootloader config"
It allow to test anti-rollback implemention without permanent write eFuse bits. It allow to test anti-rollback implemention without permanent write eFuse bits.
In partition table should be exist this partition `emul_efuse, data, 5, , 0x2000`. In partition table should be exist this partition `emul_efuse, data, 5, , 0x2000`.
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 endmenu # Bootloader

View File

@ -32,6 +32,22 @@ void bootloader_enable_qio_mode(void);
*/ */
uint32_t bootloader_read_flash_id(); uint32_t bootloader_read_flash_id();
/**
* @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 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 #ifdef __cplusplus
} }
#endif #endif

View File

@ -145,6 +145,14 @@ static esp_err_t bootloader_main()
#endif #endif
bootloader_clock_configure(); bootloader_clock_configure();
uart_console_configure(); uart_console_configure();
/* Check and run XMC startup flow. The function depends on the clock and console, so it's run right after these stuff
* are initialized. */
if (bootloader_flash_xmc_startup() != ESP_OK) {
ESP_LOGE(TAG, "failed when running XMC startup flow, reboot!");
return ESP_FAIL;
}
wdt_reset_check(); wdt_reset_check();
ESP_LOGI(TAG, "ESP-IDF %s 2nd stage bootloader", IDF_VER); ESP_LOGI(TAG, "ESP-IDF %s 2nd stage bootloader", IDF_VER);

View File

@ -14,9 +14,11 @@
#include <stddef.h> #include <stddef.h>
#include <stdint.h> #include <stdint.h>
#include "bootloader_flash_config.h" #include "bootloader_flash_config.h"
#include "esp_attr.h"
#include "flash_qio_mode.h" #include "flash_qio_mode.h"
#include "esp_log.h" #include "esp_log.h"
#include "esp_err.h" #include "esp_err.h"
#include "rom/ets_sys.h"
#include "rom/spi_flash.h" #include "rom/spi_flash.h"
#include "rom/efuse.h" #include "rom/efuse.h"
#include "soc/spi_struct.h" #include "soc/spi_struct.h"
@ -37,8 +39,12 @@
#define CMD_RDSR 0x05 #define CMD_RDSR 0x05
#define CMD_RDSR2 0x35 /* Not all SPI flash uses this command */ #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_OTPEN 0x3A /* Enable OTP mode, not all SPI flash uses this command */
#define CMD_RDSFDP 0x5A /* Read the SFDP of the flash */
static const char *TAG = "qio_mode"; #define BYTESHIFT(VAR, IDX) (((VAR) >> ((IDX) * 8)) & 0xFF)
static DRAM_ATTR char TAG[] = "qio_mode";
typedef unsigned (*read_status_fn_t)(); typedef unsigned (*read_status_fn_t)();
typedef void (*write_status_fn_t)(unsigned); typedef void (*write_status_fn_t)(unsigned);
@ -116,7 +122,7 @@ static uint32_t execute_flash_command(uint8_t command, uint32_t mosi_data, uint8
/* dummy_len_plus values defined in ROM for SPI flash configuration */ /* dummy_len_plus values defined in ROM for SPI flash configuration */
extern uint8_t g_rom_spiflash_dummy_len_plus[]; extern uint8_t g_rom_spiflash_dummy_len_plus[];
uint32_t bootloader_read_flash_id() uint32_t IRAM_ATTR bootloader_read_flash_id(void)
{ {
uint32_t id = execute_flash_command(CMD_RDID, 0, 0, 24); uint32_t id = execute_flash_command(CMD_RDID, 0, 0, 24);
id = ((id & 0xff) << 16) | ((id >> 16) & 0xff) | (id & 0xff00); id = ((id & 0xff) << 16) | ((id >> 16) & 0xff) | (id & 0xff00);
@ -251,37 +257,193 @@ static void write_status_8b_xmc25qu64a(unsigned new_status)
execute_flash_command(CMD_WRDI, 0, 0, 0); /* Exit OTP mode */ execute_flash_command(CMD_WRDI, 0, 0, 0); /* Exit OTP mode */
} }
static uint32_t 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; uint32_t old_ctrl_reg = SPIFLASH.ctrl.val;
uint32_t old_user_reg = SPIFLASH.user.val;
uint32_t old_user1_reg = SPIFLASH.user1.val;
SPIFLASH.ctrl.val = SPI_WP_REG_M; // keep WP high while idle, otherwise leave DIO mode SPIFLASH.ctrl.val = SPI_WP_REG_M; // keep WP high while idle, otherwise leave DIO mode
SPIFLASH.user.usr_dummy = 0; //command phase
SPIFLASH.user.usr_addr = 0;
SPIFLASH.user.usr_command = 1; SPIFLASH.user.usr_command = 1;
SPIFLASH.user2.usr_command_bitlen = 7; SPIFLASH.user2.usr_command_bitlen = 7;
SPIFLASH.user2.usr_command_value = command; SPIFLASH.user2.usr_command_value = command;
SPIFLASH.user.usr_miso = miso_len > 0; //addr phase
SPIFLASH.miso_dlen.usr_miso_dbitlen = miso_len ? (miso_len - 1) : 0; SPIFLASH.user.usr_addr = addr_len > 0;
SPIFLASH.user1.usr_addr_bitlen = addr_len - 1;
SPIFLASH.addr = (addr_len > 0)? (address << (32-addr_len)) : 0;
//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; SPIFLASH.user.usr_mosi = mosi_len > 0;
SPIFLASH.mosi_dlen.usr_mosi_dbitlen = mosi_len ? (mosi_len - 1) : 0; SPIFLASH.mosi_dlen.usr_mosi_dbitlen = mosi_len ? (mosi_len - 1) : 0;
SPIFLASH.data_buf[0] = mosi_data; SPIFLASH.data_buf[0] = mosi_data;
//input data
if (g_rom_spiflash_dummy_len_plus[1]) { SPIFLASH.user.usr_miso = miso_len > 0;
/* When flash pins are mapped via GPIO matrix, need a dummy cycle before reading via MISO */ SPIFLASH.miso_dlen.usr_miso_dbitlen = miso_len ? (miso_len - 1) : 0;
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;
}
}
SPIFLASH.cmd.usr = 1; SPIFLASH.cmd.usr = 1;
while(SPIFLASH.cmd.usr != 0) while(SPIFLASH.cmd.usr != 0)
{ } { }
SPIFLASH.ctrl.val = old_ctrl_reg; SPIFLASH.ctrl.val = old_ctrl_reg;
return SPIFLASH.data_buf[0]; SPIFLASH.user.val = old_user_reg;
SPIFLASH.user1.val = old_user1_reg;
uint32_t ret = SPIFLASH.data_buf[0];
if (miso_len < 32) {
//set unused bits to 0
ret &= ~(UINT32_MAX << miso_len);
}
return ret;
} }
static uint32_t IRAM_ATTR 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);
}
/*******************************************************************************
* 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
#define BOOTLOADER_FLASH_LOG(level, ...) ESP_DRAM_LOG##level(TAG, ##__VA_ARGS__)
#define ESP_DRAM_LOGE( tag, format, ... ) ESP_DRAM_LOG_IMPL(tag, format, ESP_LOG_ERROR, E, ##__VA_ARGS__)
#define ESP_DRAM_LOGW( tag, format, ... ) ESP_DRAM_LOG_IMPL(tag, format, ESP_LOG_WARN, E, ##__VA_ARGS__)
#define ESP_DRAM_LOGI( tag, format, ... ) ESP_DRAM_LOG_IMPL(tag, format, ESP_LOG_INFO, E, ##__VA_ARGS__)
#define ESP_DRAM_LOGD( tag, format, ... ) ESP_DRAM_LOG_IMPL(tag, format, ESP_LOG_DEBUG, E, ##__VA_ARGS__)
#define ESP_DRAM_LOGV( tag, format, ... ) ESP_DRAM_LOG_IMPL(tag, format, ESP_LOG_VERBOSE, E, ##__VA_ARGS__)
#define ESP_DRAM_LOG_IMPL(tag, format, log_level, log_tag_letter, ...) do { \
if (LOG_LOCAL_LEVEL >= (log_level)) { \
ets_printf(DRAM_STR(#log_tag_letter " %s: " format "\n"), tag, ##__VA_ARGS__); \
}} while(0)
#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
execute_flash_command(0xB9, 0, 0, 0);
// Enter UDPD
execute_flash_command(0x79, 0, 0, 0);
// Exit UDPD
execute_flash_command(0xFF, 0, 0, 0);
// Delay tXUDPD
ets_delay_us(2000);
// Release Power-down
execute_flash_command(0xAB, 0, 0, 0);
ets_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

View File

@ -9,6 +9,8 @@
#include "driver/timer.h" #include "driver/timer.h"
#include "esp_intr_alloc.h" #include "esp_intr_alloc.h"
#include "test_utils.h" #include "test_utils.h"
#include "../include_bootloader/flash_qio_mode.h" //for bootloader_flash_xmc_startup
struct flash_test_ctx { struct flash_test_ctx {
uint32_t offset; uint32_t offset;
@ -226,3 +228,22 @@ TEST_CASE("spi_flash deadlock with high priority busy-waiting task", "[spi_flash
TEST_ASSERT_EQUAL_INT(uxTaskPriorityGet(NULL), UNITY_FREERTOS_PRIORITY); TEST_ASSERT_EQUAL_INT(uxTaskPriorityGet(NULL), UNITY_FREERTOS_PRIORITY);
} }
#endif // portNUM_PROCESSORS > 1 #endif // portNUM_PROCESSORS > 1
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();
}