feat(spi_flash): refactor customize spi flash example for overriding more contents

This commit is contained in:
C.S.M
2024-12-02 17:49:54 +08:00
parent af31ec11f1
commit 190732c461
16 changed files with 262 additions and 171 deletions

View File

@@ -4,9 +4,6 @@ examples/storage/custom_flash_driver:
depends_components: depends_components:
- spi_flash - spi_flash
- driver - driver
disable_test:
- if: IDF_TARGET not in ["esp32", "esp32c3"]
reason: only one target per arch needed
examples/storage/emmc: examples/storage/emmc:
depends_components: depends_components:

View File

@@ -5,4 +5,8 @@ cmake_minimum_required(VERSION 3.16)
include($ENV{IDF_PATH}/tools/cmake/project.cmake) include($ENV{IDF_PATH}/tools/cmake/project.cmake)
# "Trim" the build. Include the minimal set of components, main, and anything it depends on. # "Trim" the build. Include the minimal set of components, main, and anything it depends on.
idf_build_set_property(MINIMAL_BUILD ON) idf_build_set_property(MINIMAL_BUILD ON)
idf_build_set_property(BOOTLOADER_EXTRA_COMPONENT_DIRS "${CMAKE_CURRENT_LIST_DIR}/managed_components/
espressif__esp_flash_nor/" APPEND)
project(custom_flash_driver) project(custom_flash_driver)

View File

@@ -35,21 +35,39 @@ Below is short explanation of remaining files in the project folder.
├── example_test.py Python script used for automated example testing ├── example_test.py Python script used for automated example testing
├── main ├── main
│   ├── CMakeLists.txt │   ├── CMakeLists.txt
│   ├── component.mk Component make file
│   └── main.c │   └── main.c
├── components/custom_chip_driver ├── components/custom_chip_driver
│   ├── CMakeLists.txt │   ├── CMakeLists.txt
│   ├── component.mk Component make file
│   ├── linker.lf Linker script to put the customized chip driver into internal RAM │   ├── linker.lf Linker script to put the customized chip driver into internal RAM
│   ├── project_include.cmake Global cmake file to add dependency to spi_flash
│   ├── chip_drivers.c │   ├── chip_drivers.c
│   ── spi_flash_chip_eon.c │   ── idf_component.yml Component manager for flash driver component
├── Makefile Makefile used by legacy GNU Make ├── bootloader_components/bootloader_flash
│   ├── bootloader_flash_qio_custom.c
| ├── bootloader_flash_unlock_custom.c
│   ├── CMakeLists.txt
│   ├── idf_component.yml Component manager for flash driver component
└── README.md This is the file you are currently reading └── README.md This is the file you are currently reading
``` ```
For more information on structure and contents of ESP-IDF projects, please refer to Section [Build System](https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-guides/build-system.html) of the ESP-IDF Programming Guide. For more information on structure and contents of ESP-IDF projects, please refer to Section [Build System](https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-guides/build-system.html) of the ESP-IDF Programming Guide.
## How to implement your driver
Please read [instructions](https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/peripherals/spi_flash/spi_flash_override_driver.html)
## Example output
The example output will be valid when you switch on `CONFIG_BOOTLOADER_LOG_LEVEL_DEBUG` and see output in bootloader stage
```
I (30) boot: ESP-IDF v5.4-dev-2463-g0d6cf47e5e-dirty 2nd stage bootloader
...
I (45) boot: chip revision: v0.3
D (49) boot.esp32c3: Using overridden bootloader_flash_unlock
...
D (69) qio_mode: Using overridden bootloader_flash_qio
```
## Troubleshooting ## Troubleshooting
* Program upload failure * Program upload failure

View File

@@ -0,0 +1,10 @@
idf_component_register(SRCS "bootloader_flash_qio_custom.c"
"bootloader_flash_unlock_custom.c"
REQUIRES bootloader_support spi_flash
INCLUDE_DIRS ""
WHOLE_ARCHIVE
)
# The symbol should be linked to cover weak symbol. Please don't call the function directly.
target_link_libraries(${COMPONENT_LIB} INTERFACE "-u bootloader_flash_unlock")
target_link_libraries(${COMPONENT_LIB} INTERFACE "-u bootloader_flash_qe_support_list")

View File

@@ -0,0 +1,32 @@
/*
* SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Unlicense OR CC0-1.0
*/
#include "bootloader_flash_override.h"
#include "bootloader_flash_priv.h"
#include "bootloader_flash_custom.h"
#include "esp_rom_spiflash.h"
#include "esp_attr.h"
#include "esp_rom_sys.h"
// All used flash chips that need special QIO operations need to be added here. The simplest way is to copy all entries from bootloader_..c from ESP-IDF and add your own at the beginning.
const DRAM_ATTR bootloader_qio_info_t bootloader_flash_qe_support_list_user[] = {
/* Manufacturer, mfg_id, flash_id, id mask, Read Status, Write Status, QIE Bit */
{ "GD", 0xC8, 0x4000, 0xFFFF, bootloader_read_status_16b_rdsr_rdsr2, bootloader_write_status_16b_wrsr, 9 },
{ "EON", 0x1C, 0x7000, 0xFF00, bootloader_read_status_otp_mode_8b, bootloader_write_status_otp_mode_8b, 6 },
/* Final entry is default entry, if no other IDs have matched.
This approach works for chips including:
GigaDevice (mfg ID 0xC8, flash IDs including 4016),
FM25Q32 (QOUT mode only, mfg ID 0xA1, flash IDs including 4016)
BY25Q32 (mfg ID 0x68, flash IDs including 4016)
*/
{ NULL, 0xFF, 0xFFFF, 0xFFFF, bootloader_read_status_8b_rdsr2, bootloader_write_status_8b_wrsr2, 1 },
};
const DRAM_ATTR bootloader_qio_info_t* bootloader_flash_qe_support_list = bootloader_flash_qe_support_list_user;
uint8_t DRAM_ATTR bootloader_flash_qe_list_count = (sizeof(bootloader_flash_qe_support_list_user) / sizeof(bootloader_qio_info_t));

View File

@@ -0,0 +1,154 @@
/*
* SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Unlicense OR CC0-1.0
*/
#include "bootloader_flash_priv.h"
#include "esp_rom_spiflash.h"
#include "esp_rom_sys.h"
#include "esp_attr.h"
#include "esp_log.h"
/**
Overview
This implementation is for adding unlock support to a specific SPI flash chip in the ESP32 series chip bootloader. Some flash chips might lock the sector in bootloader when its restart in a error state, thus will cause cannot write flash sector after esp chip boot up. So, an unlock function in bootloader is necessary. The goal of this function is to clear all bits except QE bit(which usually indicated the flash can work in quad mode). The main function of interest is bootloader_flash_unlock, which is responsible for ensuring that the QE bit is set correctly based on the flash chip type.
Step-by-Step Implementation
1. Include Necessary Headers:
• bootloader_flash_priv.h: For private bootloader functions.
• esp_rom_spiflash.h: For ROM SPI flash functions.
• esp_rom_sys.h: For basic ROM system functions.
• esp_attr.h and esp_log.h: For attributes and logging.
... Add necessary header file according to your requirement.
2. Define Macros for Flash IDs and QE Bits:
• Use macros to define the manufacturer IDs and bit positions for the QE bit. This is important as different flash chips have different ways to set the QE bit.
3. Identify QE Bit Position:
• Write functions to identify the QE bit position based on the chips device ID. For example, is_qe_bit_in_bit6_at_reg1 checks if the QE bit is in bit 6 of status register 1, a common configuration for ISSI and MXIC chips. Same, `is_qe_bit_in_bit1_at_reg2` is for GD chips in following example.
4. Implement bootloader_flash_unlock Function:
• Step 1: Initialize registers and prepare to read the current status.
• Step 2: Determine the correct QE bit position using the chip ID. Depending on the chip type, different commands and bit positions are used.
• Step 3: Read the status registers and determine if the QE bit is already set. If not, it sets the bit.
• Step 4: If changes are required, write the new status back to the flash chip.
• Step 5: Ensure that write operations are completed successfully by waiting for the chip to be idle and disabling write operations.
5. Test and Validate:
• After implementing the custom support, you must test with your specific flash chip to ensure that the QE bit is set correctly and that the chip works as expected in Quad SPI mode.
Example Code Snippets:
*/
#define BYTESHIFT(VAR, IDX) (((VAR) >> ((IDX) * 8)) & 0xFF)
#define ISSI_ID 0x9D
#define MXIC_ID 0xC2
#define GD_Q_ID_HIGH 0xC8
#define GD_Q_ID_MID 0x40
#define GD_Q_ID_LOW 0x16
#define ESP_BOOTLOADER_SPIFLASH_QE_SR1_BIT6 (BIT6)
#define ESP_BOOTLOADER_SPIFLASH_QE_SR2_BIT1 (BIT1) // QE position when you write 8 bits(for SR2) at one time.
#define ESP_BOOTLOADER_SPIFLASH_QE_SR1_2BYTE_BIT9 (BIT9) // QE position when you write 16 bits at one time.
FORCE_INLINE_ATTR bool is_qe_bit_in_bit6_at_reg1(const esp_rom_spiflash_chip_t* chip)
{
bool ret = true;
switch (chip->device_id) {
/***ISSI series***/
case 0x9D4016:
case 0x9D4017:
break;
/***MXIC series***/
case 0xC22016:
case 0xC22017:
break;
default:
ret = false;
}
return ret;
}
FORCE_INLINE_ATTR bool is_qe_bit_in_bit1_at_reg2(const esp_rom_spiflash_chip_t* chip)
{
bool ret = true;
switch (chip->device_id) {
/****GD series***/
case 0xC84016:
case 0xC84017:
case 0xC84018:
break;
default:
ret = false;
}
return ret;
}
esp_err_t IRAM_ATTR bootloader_flash_unlock(void)
{
// At the beginning status == new_status == status_sr2 == new_status_sr2 == 0.
// If the register doesn't need to be updated, keep them the same (0), so that no command will be actually sent.
uint16_t status = 0; // status for SR1 or SR1+SR2 if writing SR with 01H + 2Bytes.
uint16_t new_status = 0;
uint8_t status_sr2 = 0; // status_sr2 for SR2.
uint8_t new_status_sr2 = 0;
uint8_t sr1_bit_num = 0;
esp_err_t err = ESP_OK;
esp_rom_spiflash_wait_idle(&g_rom_flashchip);
if (is_qe_bit_in_bit6_at_reg1(&g_rom_flashchip)) {
// Currently ISSI & MXIC share the same command and register layout, which is different from the default model.
// If any code here needs to be modified, check both chips.
status = bootloader_execute_flash_command(CMD_RDSR, 0, 0, 8);
/* Clear all bits in the mask.
(This is different from ROM esp_rom_spiflash_unlock, which keeps all bits as-is.)
*/
sr1_bit_num = 8;
new_status = status & ESP_BOOTLOADER_SPIFLASH_QE_SR1_BIT6;
} else if (is_qe_bit_in_bit1_at_reg2(&g_rom_flashchip)) {
/* The GD chips behaviour is to clear all bits in SR1 and clear bits in SR2 except QE bit.
Use 01H to write SR1 and 31H to write SR2.
*/
status = bootloader_execute_flash_command(CMD_RDSR, 0, 0, 8);
sr1_bit_num = 8;
new_status = 0;
status_sr2 = bootloader_execute_flash_command(CMD_RDSR2, 0, 0, 8);
new_status_sr2 = status_sr2 & ESP_BOOTLOADER_SPIFLASH_QE_SR2_BIT1;
} else {
/* For common behaviour, like XMC chips, Use 01H+2Bytes to write both SR1 and SR2*/
status = bootloader_execute_flash_command(CMD_RDSR, 0, 0, 8) | (bootloader_execute_flash_command(CMD_RDSR2, 0, 0, 8) << 8);
/* Clear all bits except QE, if it is set.
(This is different from ROM esp_rom_spiflash_unlock, which keeps all bits as-is.)
*/
sr1_bit_num = 16;
new_status = status & ESP_BOOTLOADER_SPIFLASH_QE_SR1_2BYTE_BIT9;
}
// When SR is written, set to true to indicate that WRDI need to be sent to ensure the protection is ON before return.
bool status_written = false;
// Skip if nothing needs to be changed. Meaningless writing to SR increases the risk during write and wastes time.
if (status != new_status) {
esp_rom_spiflash_wait_idle(&g_rom_flashchip);
bootloader_execute_flash_command(CMD_WREN, 0, 0, 0);
bootloader_execute_flash_command(CMD_WRSR, new_status, sr1_bit_num, 0);
status_written = true;
}
if (status_sr2 != new_status_sr2) {
esp_rom_spiflash_wait_idle(&g_rom_flashchip);
bootloader_execute_flash_command(CMD_WREN, 0, 0, 0);
bootloader_execute_flash_command(CMD_WRSR2, new_status_sr2, 8, 0);
status_written = true;
}
if (status_written) {
//Call esp_rom_spiflash_wait_idle to make sure previous WRSR is completed.
esp_rom_spiflash_wait_idle(&g_rom_flashchip);
bootloader_execute_flash_command(CMD_WRDI, 0, 0, 0);
}
return err;
}

View File

@@ -0,0 +1,3 @@
## IDF Component Manager Manifest File
dependencies:
esp_flash_nor: ">=0.0.3"

View File

@@ -1,4 +1,4 @@
idf_component_register(SRCS "chip_drivers.c" "spi_flash_chip_eon.c" idf_component_register(SRCS "chip_drivers.c"
PRIV_REQUIRES spi_flash PRIV_REQUIRES spi_flash
LDFRAGMENTS linker.lf LDFRAGMENTS linker.lf
INCLUDE_DIRS "") INCLUDE_DIRS "")

View File

@@ -1,3 +1,8 @@
/*
* SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Unlicense OR CC0-1.0
*/
/* Custom flash driver example /* Custom flash driver example
This example code is in the Public Domain (or CC0 licensed, at your option.) This example code is in the Public Domain (or CC0 licensed, at your option.)
@@ -14,8 +19,9 @@
#include "spi_flash_chip_gd.h" #include "spi_flash_chip_gd.h"
#include "spi_flash_chip_winbond.h" #include "spi_flash_chip_winbond.h"
#include "spi_flash_chip_boya.h" #include "spi_flash_chip_boya.h"
// `spi_flash_chip_custom.h` is the header for the structure of customize flash driver,
extern const spi_flash_chip_t esp_flash_chip_eon; // in this example is `esp_flash_chip_eon`.
#include "spi_flash_chip_custom.h"
//Override the default chip driver provided by the IDF, CONFIG_SPI_FLASH_OVERRIDE_CHIP_DRIVER_LIST should be set //Override the default chip driver provided by the IDF, CONFIG_SPI_FLASH_OVERRIDE_CHIP_DRIVER_LIST should be set
const spi_flash_chip_t *default_registered_chips[] = { const spi_flash_chip_t *default_registered_chips[] = {

View File

@@ -0,0 +1,3 @@
## IDF Component Manager Manifest File
dependencies:
esp_flash_nor: ">=0.0.3"

View File

@@ -2,4 +2,3 @@
archive: libcustom_chip_driver.a archive: libcustom_chip_driver.a
entries: entries:
chip_drivers (noflash) chip_drivers (noflash)
spi_flash_chip_eon (noflash)

View File

@@ -1,156 +0,0 @@
/* Custom chip driver example
This example code is in the Public Domain (or CC0 licensed, at your option.)
Unless required by applicable law or agreed to in writing, this
software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
CONDITIONS OF ANY KIND, either express or implied.
*/
#include <stdlib.h>
#include <string.h>
#include <sys/param.h> // For MIN/MAX
#include "esp_log.h"
#include "spi_flash_chip_generic.h"
#include "spi_flash/spi_flash_defs.h"
// Not for all the vendors
#define CMD_ENTER_OTP 0x3A
static const char chip_name[] = "eon";
/* Driver for EON flash chip */
esp_err_t spi_flash_chip_eon_probe(esp_flash_t *chip, uint32_t flash_id)
{
/* Check manufacturer and product IDs match our desired masks */
const uint8_t MFG_ID = 0x1C;
const uint16_t DEV_ID = 0x7000;
if (flash_id >> 16 != MFG_ID || (flash_id & 0xFF00) != DEV_ID) {
return ESP_ERR_NOT_FOUND;
}
return ESP_OK;
}
static esp_err_t spi_flash_chip_eon_enter_otp_mode(esp_flash_t* chip)
{
spi_flash_trans_t trans = {
.command = CMD_ENTER_OTP,
};
return chip->host->driver->common_command(chip->host, &trans);
}
static esp_err_t spi_flash_chip_eon_exit_otp_mode(esp_flash_t* chip)
{
spi_flash_trans_t trans = {
.command = CMD_WRDI,
};
return chip->host->driver->common_command(chip->host, &trans);
}
esp_err_t spi_flash_chip_eon_get_io_mode(esp_flash_t *chip, esp_flash_io_mode_t* out_io_mode)
{
esp_err_t ret;
ret = spi_flash_chip_eon_enter_otp_mode(chip);
if (ret != ESP_OK) {
return ret;
}
// On "eon" chips, this involves checking
// bit 1 (QE) of RDSR2 (35h) result
// (it works this way on GigaDevice & Fudan Micro chips, probably others...)
const uint8_t BIT_QE = 1 << 6;
uint32_t sr;
ret = spi_flash_common_read_status_8b_rdsr(chip, &sr);
if (ret == ESP_OK) {
*out_io_mode = ((sr & BIT_QE)? SPI_FLASH_QOUT: 0);
}
//unconditionally exit OTP mode
esp_err_t ret_exit = spi_flash_chip_eon_exit_otp_mode(chip);
if (ret != ESP_OK) {
return ret;
}
return ret_exit;
}
esp_err_t spi_flash_chip_eon_set_io_mode(esp_flash_t *chip)
{
if (!esp_flash_is_quad_mode(chip)) {
return ESP_OK;
}
esp_err_t ret;
ret = spi_flash_chip_eon_enter_otp_mode(chip);
if (ret != ESP_OK) {
return ret;
}
// On "eon" chips, this involves checking
// bit 6 (QE) of RDSR (05h) result
const uint32_t BIT_QE = 1 << 6;
ret = spi_flash_common_set_io_mode(chip,
spi_flash_common_write_status_8b_wrsr,
spi_flash_common_read_status_8b_rdsr,
BIT_QE);
//unconditionally exit OTP mode
esp_err_t ret_exit = spi_flash_chip_eon_exit_otp_mode(chip);
if (ret != ESP_OK) {
return ret;
}
return ret_exit;
}
esp_err_t spi_flash_chip_eon_suspend_cmd_conf(esp_flash_t *chip)
{
return ESP_ERR_NOT_SUPPORTED;
}
spi_flash_caps_t spi_flash_chip_eon_get_caps(esp_flash_t *chip)
{
spi_flash_caps_t caps_flags = 0;
// 32-bit-address flash is not supported
// flash-suspend is not supported
// flash read unique id is not supported.
return caps_flags;
}
// The issi chip can use the functions for generic chips except from set read mode and probe,
// So we only replace these two functions.
const spi_flash_chip_t esp_flash_chip_eon = {
.name = chip_name,
.timeout = &spi_flash_chip_generic_timeout,
.probe = spi_flash_chip_eon_probe,
.reset = spi_flash_chip_generic_reset,
.detect_size = spi_flash_chip_generic_detect_size,
.erase_chip = spi_flash_chip_generic_erase_chip,
.erase_sector = spi_flash_chip_generic_erase_sector,
.erase_block = spi_flash_chip_generic_erase_block,
.sector_size = 4 * 1024,
.block_erase_size = 64 * 1024,
.get_chip_write_protect = spi_flash_chip_generic_get_write_protect,
.set_chip_write_protect = spi_flash_chip_generic_set_write_protect,
.num_protectable_regions = 0,
.protectable_regions = NULL,
.get_protected_regions = NULL,
.set_protected_regions = NULL,
.read = spi_flash_chip_generic_read,
.write = spi_flash_chip_generic_write,
.program_page = spi_flash_chip_generic_page_program,
.page_size = 256,
.write_encrypted = spi_flash_chip_generic_write_encrypted,
.wait_idle = spi_flash_chip_generic_wait_idle,
.set_io_mode = spi_flash_chip_eon_set_io_mode,
.get_io_mode = spi_flash_chip_eon_get_io_mode,
.read_reg = spi_flash_chip_generic_read_reg,
.yield = spi_flash_chip_generic_yield,
.sus_setup = spi_flash_chip_eon_suspend_cmd_conf,
.get_chip_caps = spi_flash_chip_eon_get_caps,
};

View File

@@ -1,3 +1,3 @@
idf_component_register(SRCS "main.c" idf_component_register(SRCS "main.c"
PRIV_REQUIRES custom_chip_driver PRIV_REQUIRES espressif__esp_flash_nor custom_chip_driver
INCLUDE_DIRS "") INCLUDE_DIRS "")

View File

@@ -0,0 +1,18 @@
# SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD
# SPDX-License-Identifier: Unlicense OR CC0-1.0
import pytest
from pytest_embedded import Dut
@pytest.mark.supported_targets
@pytest.mark.generic
@pytest.mark.parametrize(
'config',
[
'default',
],
indirect=True,
)
def test_examples_custom_flash_driver(dut: Dut) -> None:
dut.expect(r'Using overridden bootloader_flash_unlock', timeout=20)
dut.expect(r'Using overridden bootloader_flash_qio, the list number is \d+', timeout=20)

View File

@@ -0,0 +1,5 @@
CONFIG_BOOTLOADER_LOG_LEVEL_DEBUG=y
CONFIG_BOOTLOADER_LOG_LEVEL=4
CONFIG_ESPTOOLPY_FLASHMODE_QIO=y
# Bootloader overlapped with partition table after we enabled LOGD for bootloader. Move the partition table a bit to fix the issue on CI.
CONFIG_PARTITION_TABLE_OFFSET=0x9000

View File

@@ -915,8 +915,6 @@ examples/protocols/sockets/udp_server/main/udp_server.c
examples/protocols/static_ip/main/static_ip_example_main.c examples/protocols/static_ip/main/static_ip_example_main.c
examples/provisioning/wifi_prov_mgr/main/app_main.c examples/provisioning/wifi_prov_mgr/main/app_main.c
examples/security/flash_encryption/main/flash_encrypt_main.c examples/security/flash_encryption/main/flash_encrypt_main.c
examples/storage/custom_flash_driver/components/custom_chip_driver/chip_drivers.c
examples/storage/custom_flash_driver/components/custom_chip_driver/spi_flash_chip_eon.c
examples/storage/custom_flash_driver/main/main.c examples/storage/custom_flash_driver/main/main.c
examples/storage/nvs_rw_blob/main/nvs_blob_example_main.c examples/storage/nvs_rw_blob/main/nvs_blob_example_main.c
examples/storage/nvs_rw_value/main/nvs_value_example_main.c examples/storage/nvs_rw_value/main/nvs_value_example_main.c