Merge branch 'docs/spi_flash_auto_suspend_v4.3' into 'release/v4.3'

spi_flash: update docs after adding CONFIG_SPI_FLASH_AUTO_SUSPEND (v4.3)

See merge request espressif/esp-idf!12512
This commit is contained in:
Michael (XIAO Xufeng) 2021-02-26 08:53:48 +00:00
commit 24f3341a2d
8 changed files with 553 additions and 471 deletions

View File

@ -1,274 +1,3 @@
SPI Flash API
=============
See the spi_flash.rst in the programming guide folder for more details.
:link_to_translation:`zh_CN:[中文]`
Overview
--------
The spi_flash component contains API functions related to reading, writing,
erasing, memory mapping for data in the external flash. The spi_flash
component also has higher-level API functions which work with partitions
defined in the :doc:`partition table </api-guides/partition-tables>`.
Different from the API before IDF v4.0, the functionality of esp_flash_* APIs is not limited to
the "main" SPI flash chip (the same SPI flash chip from which program runs).
With different chip pointers, you can access to external flashes chips on not
only SPI0/1 but also HSPI/VSPI buses.
.. note::
Due to limitations of the cache, access to external flash is limited to `esp_flash_*` APIs through SPI1 only. It is not allowed to use mmap or encrypted operations to access the external flash.
.. note::
Flash APIs after IDF v4.0 are no longer *atomic*. A writing operation
during another on-going read operation, on the overlapped flash address,
may cause the return data from the read operation to be partly same as
before, and partly updated as new written.
Kconfig option :ref:`CONFIG_SPI_FLASH_USE_LEGACY_IMPL` can be used to switch
``spi_flash_*`` functions back to the implementation before IDF v4.0.
However, the code size may get bigger if you use the new API and the old API
the same time.
Encrypted reads and writes use the old implementation, even if
:ref:`CONFIG_SPI_FLASH_USE_LEGACY_IMPL` is not enabled. As such, encrypted
flash operations are only supported with the main flash chip (and not with
other flash chips, that is on SPI1 with different CS, or on other SPI buses). Reading through cache is
only supported on the main flash, which is determined by the HW.
Support for features of flash chips
-----------------------------------
Different chips need different supports, and we will progressively complete drivers for other types of chip in the future. We support the fast/slow read and Dual mode (DOUT/DIO) of almost all 24-bits address flash chips, because they don't need any vendor-specific commands to enable.
For Quad mode (QIO/QOUT) the following 24-bit address chip types are supported:
1. ISSI
2. GD
3. MXIC
4. FM
5. Winbond
6. XMC
We are continuing updating to support 32-bits address chips, here is the list of them:
1. W25Q256
Initializing a flash device
---------------------------
To use ``esp_flash_*`` APIs, you need to have a chip initialized on a certain
SPI bus.
1. Call :cpp:func:`spi_bus_initialize` to properly initialize an SPI bus.
This functions initialize the resources (I/O, DMA, interrupts) shared
among devices attached to this bus.
2. Call :cpp:func:`spi_bus_add_flash_device` to attach the flash device onto
the bus. This allocates memory, and fill the members for the
``esp_flash_t`` structure. The CS I/O is also initialized here.
3. Call :cpp:func:`esp_flash_init` to actually communicate with the chip.
This will also detect the chip type, and influence the following
operations.
.. note:: Multiple flash chips can be attached to the same bus now. However,
using ``esp_flash_*`` devices and ``spi_device_*`` devices on the
same SPI bus is not supported yet.
SPI flash access API
--------------------
This is the set of API functions for working with data in flash:
- :cpp:func:`esp_flash_read` reads data from flash to RAM
- :cpp:func:`esp_flash_write` writes data from RAM to flash
- :cpp:func:`esp_flash_erase_region` erases specific region of flash
- :cpp:func:`esp_flash_erase_chip` erases the whole flash
- :cpp:func:`esp_flash_get_chip_size` returns flash chip size, in bytes, as configured in menuconfig
Generally, try to avoid using the raw SPI flash functions to the "main" SPI
flash chip in favour of :ref:`partition-specific functions
<flash-partition-apis>`.
SPI Flash Size
--------------
The SPI flash size is configured by writing a field in the software bootloader image header, flashed at offset 0x1000.
By default, the SPI flash size is detected by esptool.py when this bootloader is written to flash, and the header is updated with the correct size. Alternatively, it is possible to generate a fixed flash size by setting :envvar:`CONFIG_ESPTOOLPY_FLASHSIZE` in project configuration.
If it is necessary to override the configured flash size at runtime, it is possible to set the ``chip_size`` member of the ``g_rom_flashchip`` structure. This size is used by ``esp_flash_*`` functions (in both software & ROM) to check the bounds.
Concurrency Constraints for flash on SPI1
-----------------------------------------
Because the SPI1 flash is also used for firmware execution via the instruction & data caches, these caches must be disabled while reading/writing/erasing. This means that both CPUs must be running code from IRAM and must only be reading data from DRAM while flash write operations occur.
If you use the API functions documented here, then these constraints are applied automatically and transparently. However, note that it will have some performance impact on other tasks in the system.
There are no such constraints and impacts for flash chips on other SPI buses than SPI0/1.
For differences between IRAM, DRAM, and flash cache, please refer to the :ref:`application memory layout <memory-layout>` documentation.
To avoid reading flash cache accidentally, when one CPU initiates a flash write or erase operation, the other CPU is put into a blocked state, and all non-IRAM-safe interrupts are disabled on both CPUs until the flash operation completes.
If one CPU initiates a flash write or erase operation, the other CPU is put into a blocked state to avoid reading flash cache accidentally. All interrupts not safe for IRAM are disabled on both CPUs until the flash operation completes.
Please also see :ref:`esp_flash_os_func`, :ref:`spi_bus_lock`.
.. _iram-safe-interrupt-handlers:
IRAM-Safe Interrupt Handlers
^^^^^^^^^^^^^^^^^^^^^^^^^^^^
If you have an interrupt handler that you want to execute while a flash operation is in progress (for example, for low latency operations), set the ``ESP_INTR_FLAG_IRAM`` flag when the :doc:`interrupt handler is registered </api-reference/system/intr_alloc>`.
You must ensure that all data and functions accessed by these interrupt handlers, including the ones that handlers call, are located in IRAM or DRAM.
Use the ``IRAM_ATTR`` attribute for functions::
#include "esp_attr.h"
void IRAM_ATTR gpio_isr_handler(void* arg)
{
// ...
}
Use the ``DRAM_ATTR`` and ``DRAM_STR`` attributes for constant data::
void IRAM_ATTR gpio_isr_handler(void* arg)
{
const static DRAM_ATTR uint8_t INDEX_DATA[] = { 45, 33, 12, 0 };
const static char *MSG = DRAM_STR("I am a string stored in RAM");
}
Note that knowing which data should be marked with ``DRAM_ATTR`` can be hard, the compiler will sometimes recognize that a variable or expression is constant (even if it is not marked ``const``) and optimize it into flash, unless it is marked with ``DRAM_ATTR``.
If a function or symbol is not correctly put into IRAM/DRAM, and the interrupt handler reads from the flash cache during a flash operation, it will cause a crash due to Illegal Instruction exception (for code which should be in IRAM) or garbage data to be read (for constant data which should be in DRAM).
.. _flash-partition-apis:
Partition table API
-------------------
ESP-IDF projects use a partition table to maintain information about various regions of SPI flash memory (bootloader, various application binaries, data, filesystems). More information on partition tables can be found :doc:`here </api-guides/partition-tables>`.
This component provides API functions to enumerate partitions found in the partition table and perform operations on them. These functions are declared in ``esp_partition.h``:
- :cpp:func:`esp_partition_find` checks a partition table for entries with specific type, returns an opaque iterator.
- :cpp:func:`esp_partition_get` returns a structure describing the partition for a given iterator.
- :cpp:func:`esp_partition_next` shifts the iterator to the next found partition.
- :cpp:func:`esp_partition_iterator_release` releases iterator returned by ``esp_partition_find``.
- :cpp:func:`esp_partition_find_first` - a convenience function which returns the structure describing the first partition found by ``esp_partition_find``.
- :cpp:func:`esp_partition_read`, :cpp:func:`esp_partition_write`, :cpp:func:`esp_partition_erase_range` are equivalent to :cpp:func:`spi_flash_read`, :cpp:func:`spi_flash_write`, :cpp:func:`spi_flash_erase_range`, but operate within partition boundaries.
.. note::
Application code should mostly use these ``esp_partition_*`` API functions instead of lower level ``esp_flash_*`` API functions. Partition table API functions do bounds checking and calculate correct offsets in flash, based on data stored in a partition table.
SPI Flash Encryption
--------------------
It is possible to encrypt the contents of SPI flash and have it transparently decrypted by hardware.
Refer to the :doc:`Flash Encryption documentation </security/flash-encryption>` for more details.
Memory mapping API
------------------
ESP32 features memory hardware which allows regions of flash memory to be mapped into instruction and data address spaces. This mapping works only for read operations. It is not possible to modify contents of flash memory by writing to a mapped memory region.
Mapping happens in 64KB pages. Memory mapping hardware can map up to four megabytes of flash into data address space and up to 16 megabytes of flash into instruction address space. See the technical reference manual for more details about memory mapping hardware.
Note that some 64KB pages are used to map the application itself into memory, so the actual number of available 64KB pages may be less.
Reading data from flash using a memory mapped region is the only way to decrypt contents of flash when :doc:`flash encryption </security/flash-encryption>` is enabled. Decryption is performed at the hardware level.
Memory mapping API are declared in ``esp_spi_flash.h`` and ``esp_partition.h``:
- :cpp:func:`spi_flash_mmap` maps a region of physical flash addresses into instruction space or data space of the CPU.
- :cpp:func:`spi_flash_munmap` unmaps previously mapped region.
- :cpp:func:`esp_partition_mmap` maps part of a partition into the instruction space or data space of the CPU.
Differences between :cpp:func:`spi_flash_mmap` and :cpp:func:`esp_partition_mmap` are as follows:
- :cpp:func:`spi_flash_mmap` must be given a 64KB aligned physical address.
- :cpp:func:`esp_partition_mmap` may be given any arbitrary offset within the partition, it will adjust the returned pointer to mapped memory as necessary
Note that since memory mapping happens in 64KB blocks, it may be possible to read data outside of the partition provided to ``esp_partition_mmap``.
.. note:: mmap is supported by cache, so it can only be used on main flash.
SPI Flash Implementation
------------------------
The ``esp_flash_t`` structure holds chip data as well as three important parts of this API:
1. The host driver, which provides the hardware support to access the chip;
2. The chip driver, which provides compatibility service to different chips;
3. The OS functions, provides support of some OS functions (e.g. lock, delay)
in different stages (1st/2st boot, or the app).
Host driver
^^^^^^^^^^^
The host driver relies on an interface (``spi_flash_host_driver_t``) defined
in the ``spi_flash_types.h`` (in the ``hal/include/hal`` folder). This
interface provides some common functions to communicate with the chip.
In other files of the SPI HAL, some of these functions are implemented with
existing ESP32 memory-spi functionalities. However due to the speed
limitations of ESP32, the HAL layer can't provide high-speed implementations
to some reading commands (So we didn't do it at all). The files
(``memspi_host_driver.h`` and ``.c``) implement the high-speed version of
these commands with the ``common_command`` function provided in the HAL, and
wrap these functions as ``spi_flash_host_driver_t`` for upper layer to use.
You can also implement your own host driver, even with the GPIO. As long as
all the functions in the ``spi_flash_host_driver_t`` are implemented, the
esp_flash API can access to the flash regardless of the low-level hardware.
Chip driver
^^^^^^^^^^^
The chip driver, defined in ``spi_flash_chip_driver.h``, wraps basic
functions provided by the host driver for the API layer to use.
Some operations need some commands to be sent first, or read some status
after. Some chips need different command or value, or need special
communication ways.
There is a type of chip called ``generic chip`` which stands for common
chips. Other special chip drivers can be developed on the base of the generic
chip.
The chip driver relies on the host driver.
.. _esp_flash_os_func:
OS functions
^^^^^^^^^^^^
Currently the OS function layer provides entries of a lock and delay.
The lock (see :ref:`spi_bus_lock`) is used to resolve the conflicts among the access of devices
on the same SPI bus, and the SPI Flash chip access. E.g.
1. On SPI1 bus, the cache (used to fetch the data (code) in the Flash and PSRAM) should be
disabled when the flash chip on the SPI0/1 is being accessed.
2. On the other buses, the flash driver needs to disable the ISR registered by SPI Master driver,
to avoid conflicts.
3. Some devices of SPI Master driver may requires to use the bus monopolized during a period.
(especially when the device doesn't have CS wire, or the wire is controlled by the software
like SDSPI driver).
The delay is used by some long operations which requires the master to wait
or polling periodically.
The top API wraps these the chip driver and OS functions into an entire
component, and also provides some argument checking.
The docs above is for the new SPI Flash API above 4.0, for legacy implementation (CONFIG_SPI_FLASH_USE_LEGACY_IMPL enabled), read README_legayc.rst in the same folder with this readme.

View File

@ -1,195 +0,0 @@
SPI Flash API
=================
:link_to_translation:`en:[English]`
概述
--------
SPI Flash 组件提供外部 flash 数据读取、写入、擦除和内存映射相关的 API 函数,同时也提供了更高层级的,面向分区的 API 函数(定义在 :doc:`分区表 </api-guides/partition-tables>` 中)。
与 ESP-IDF V4.0 之前的 API 不同,这一版 `esp_flash_*` API 功能并不局限于主 SPI Flash 芯片(即运行程序的 SPI Flash 芯片)。使用不同的芯片指针,您可以通过 SPI0/1 或 HSPI/VSPI 总线访问外部 flash。
.. note::
由于 cache 的限制,外部 flash 只能使用 `esp_flash_*` API 只能通过 SPI1 访问,而不允许使用 mmap 或加密操作访问。
.. note::
ESP-IDF V4.0 之后的 flash API 不再是原子的。因此,如果 flash 操作地址有重叠,且写操作与读操作同时执行,读操作可能会返回一部分写入之前的数据,返回一部分写入之后的数据。
Kconfig 选项 :ref:`CONFIG_SPI_FLASH_USE_LEGACY_IMPL` 可将 ``spi_flash_*`` 函数切换至 ESP-IDF V4.0 之前的实现。但是,如果同时使用新旧 API代码量可能会增多。
即便未启用 :ref:`CONFIG_SPI_FLASH_USE_LEGACY_IMPL`,加密读取和加密写入操作也均使用旧实现。因此,仅有主 flash 芯片支持加密操作,外接(经 SPI1 使用其他不同片选访问,或经其它 SPI 总线访问)的 flash 芯片则不支持加密操作。也仅有主 flash 支持从 cache 当中读取,因为这是由硬件决定的。
初始化 Flash 设备
---------------------------
在使用 ``esp_flash_*`` API 之前,您需要在 SPI 总线上初始化芯片。
1. 调用 :cpp:func:`spi_bus_initialize` 初始化 SPI 总线,此函数将初始化总线上设备间共享的资源,如 I/O、DMA 及中断等。
2. 调用 :cpp:func:`spi_bus_add_flash_device` 将 flash 设备连接到总线上。然后分配内存,填充 ``esp_flash_t`` 结构体,同时初始化 CS I/O。
3. 调用 :cpp:func:`esp_flash_init` 与芯片进行通信。后续操作会依据芯片类型不同而有差异。
.. note:: 目前,多个 flash 芯片可连接到同一总线。但尚不支持在同一个 SPI 总线上使用 ``esp_flash_*````spi_device_*`` 设备。
SPI Flash 访问 API
--------------------
如下所示为处理 flash 中数据的函数集:
- :cpp:func:`esp_flash_read`:将数据从 flash 读取到 RAM
- :cpp:func:`esp_flash_write`:将数据从 RAM 写入到 flash
- :cpp:func:`esp_flash_erase_region`:擦除 flash 中指定区域的数据;
- :cpp:func:`esp_flash_erase_chip`:擦除整个 flash
- :cpp:func:`esp_flash_get_chip_size`:返回 menuconfig 中设置的 flash 芯片容量(以字节为单位)。
一般来说,请尽量避免对主 SPI flash 芯片直接使用原始 SPI flash 函数,如需对主 SPI flash 芯片进行操作,请使用 :ref:`分区专用函数 <flash-partition-apis>`
SPI Flash 容量
--------------
SPI flash 容量存储于引导程序映像头部(烧录偏移量为 0x1000的一个字段。
默认情况下,引导程序写入 flash 时esptool.py 将引导程序写入 flash 时,会自动检测 SPI flash 容量,同时使用正确容量更新引导程序的头部。您也可以在工程配置中设置 :envvar:`CONFIG_ESPTOOLPY_FLASHSIZE`,生成固定的 flash 容量。
如需在运行时覆盖已配置的 flash 容量,请配置 ``g_rom_flashchip`` 结构中的 ``chip_size````esp_flash_*`` 函数使用此容量(于软件和 ROM 中)进行边界检查。
SPI1 Flash 并发约束
-----------------------------------------
由于 SPI1 flash 也被用于执行固件(通过指令 cache 或数据 cache ),因此在执行读取、写入及擦除操作时,必须禁用这些 cache。这意味着在执行 flash 写操作时,两个 CPU 必须从 IRAM 运行代码,且只能从 DRAM 中读取数据。
如果您使用本文档中 API 函数,上述限制将自动生效且透明(无需您额外关注),但这些限制可能会影响系统中的其他任务的性能。
除 SPI0/1 以外的 SPI 总线上的其它 flash 芯片则不受这种限制。
请参阅 :ref:`应用程序内存分布 <memory-layout>`,查看 IRAM、DRAM 和 flash cache 的区别。
为避免意外读取 flash cache一个 CPU 在启动 flash 写入或擦除操作时,另一个 CPU 将阻塞,并且在 flash 操作完成前,两个 CPU 上的所有的非 IRAM 安全的中断都会被禁用。
.. _iram-safe-interrupt-handlers:
IRAM 安全中断处理程序
^^^^^^^^^^^^^^^^^^^^^^^^^^^^
如果您需要在 flash 操作期间运行中断处理程序(比如低延迟操作),请在 :doc:`注册中断处理程序 </api-reference/system/intr_alloc>` 时设置 ``ESP_INTR_FLAG_IRAM``
请确保中断处理程序访问的所有数据和函数(包括其调用的数据和函数)都存储在 IRAM 或 DRAM 中。
为函数添加 ``IRAM_ATTR`` 属性::
#include "esp_attr.h"
void IRAM_ATTR gpio_isr_handler(void* arg)
{
// ...
}
为常量添加 ``DRAM_ATTR````DRAM_STR`` 属性::
void IRAM_ATTR gpio_isr_handler(void* arg)
{
const static DRAM_ATTR uint8_t INDEX_DATA[] = { 45, 33, 12, 0 };
const static char *MSG = DRAM_STR("I am a string stored in RAM");
}
辨别哪些数据应标记为 ``DRAM_ATTR`` 可能会比较困难,除非明确标记为 ``DRAM_ATTR``,否则编译器依然可能将某些变量或表达式当做常量(即便没有 ``const`` 标记),并将其放入 flash。
如果函数或符号未被正确放入 IRAM/DRAM 中,当中断处理程序在 flash 操作期间从 flash cache 中读取数据,则会产生非法指令异常(这是因为代码未被正确放入 IRAM或读取垃圾数据这是因为常数未被正确放入 DRAM而导致崩溃。
.. _flash-partition-apis:
分区表 API
-------------------
ESP-IDF 工程使用分区表保存 SPI flash 各区信息,包括引导程序、各种应用程序二进制文件、数据及文件系统等。请参考 :doc:`分区表 </api-guides/partition-tables>`,查看详细信息。
该组件在 ``esp_partition.h`` 中声明了一些 API 函数,用以枚举在分区表中找到的分区,并对这些分区执行操作:
- :cpp:func:`esp_partition_find`:在分区表中查找特定类型的条目,返回一个不透明迭代器;
- :cpp:func:`esp_partition_get`:返回一个结构,描述给定迭代器的分区;
- :cpp:func:`esp_partition_next`:将迭代器移至下一个找到的分区;
- :cpp:func:`esp_partition_iterator_release`:释放 ``esp_partition_find`` 中返回的迭代器;
- :cpp:func:`esp_partition_find_first`:返回一个结构,描述 ``esp_partition_find`` 中找到的第一个分区;
- :cpp:func:`esp_partition_read`:cpp:func:`esp_partition_write`:cpp:func:`esp_partition_erase_range` 在分区边界内执行,等同于 :cpp:func:`spi_flash_read`:cpp:func:`spi_flash_write`:cpp:func:`spi_flash_erase_range`
.. note::
请在应用程序代码中使用上述 ``esp_partition_*`` API 函数,而非低层级的 ``esp_flash_*`` API 函数。分区表 API 函数根据存储在分区表中的数据,进行边界检查并计算在 flash 中的正确偏移量。
SPI Flash 加密
--------------------
您可以对 SPI flash 内容进行加密,并在硬件层对其进行透明解密。
请参阅 :doc:`Flash 加密 </security/flash-encryption>`,查看详细信息。
内存映射 API
------------------
ESP32 内存硬件可以将 flash 部分区域映射到指令地址空间和数据地址空间,此映射仅用于读操作。不能通过写入 flash 映射的存储区域来改变 flash 中内容。
Flash 以 64 KB 页为单位进行地址映射。内存映射硬件最多可将 4 MB flash 映射到数据地址空间,将 16 MB flash 映射到指令地址空间。请参考《ESP32 技术参考手册》查看内存映射硬件的详细信息。
请注意,有些 64 KB 页还用于将应用程序映射到内存中,因此实际可用的 64 KB 页会更少一些。
:doc:`Flash 加密 </security/flash-encryption>` 启用时,使用内存映射区域从 flash 读取数据是解密 flash 的唯一方法,解密需在硬件层进行。
内存映射 API 在 ``esp_spi_flash.h````esp_partition.h`` 中声明:
- :cpp:func:`spi_flash_mmap`:将 flash 物理地址区域映射到 CPU 指令空间或数据空间;
- :cpp:func:`spi_flash_munmap`:取消上述区域的映射;
- :cpp:func:`esp_partition_mmap`:将分区的一部分映射至 CPU 指令空间或数据空间;
:cpp:func:`spi_flash_mmap`:cpp:func:`esp_partition_mmap` 的区别如下:
- :cpp:func:`spi_flash_mmap`:需要给定一个 64 KB 对齐的物理地址;
- :cpp:func:`esp_partition_mmap`:给定分区内任意偏移量即可,此函数根据需要将返回的指针调整至指向映射内存。
内存映射在 64 KB 块中进行,如果分区已传递给 ``esp_partition_mmap``,则可读取分区外数据。
.. note::
由于 mmap 是由 cache 支持的因此mmap 也仅能用在主 flash 上。
实现
--------------
``esp_flash_t`` 结构包含芯片数据和该 API 的三个重要部分:
1. 主机驱动,为访问芯片提供硬件支持;
2. 芯片驱动,为不同芯片提供兼容性服务;
3. OS 函数,在不同阶段(一级或二级 Boot 或者应用程序阶段)为部分 OS 函数提供支持(如一些锁、延迟)。
主机驱动
^^^^^^^^^^^^^^^
主机驱动依赖 ``hal/include/hal`` 文件夹下 ``spi_flash_types.h`` 定义的 ``spi_flash_host_driver_t`` 接口。该接口提供了一些与芯片通信常用的函数。
在 SPI HAL 文件中,有些函数是基于现有的 ESP32 memory-spi 来实现的。但是,由于 ESP32 速度限制HAL 层无法提供某些读命令的高速实现(所以这些命令根本没有在 HAL 的文件中被实现)。``memspi_host_driver.h````.c`` 文件使用 HAL 提供的 ``common_command`` 函数实现上述读命令的高速版本,并将所有它实现的及 HAL 函数封装为 ``spi_flash_host_driver_t`` 供更上层调用。
您也可以实现自己的主机驱动,甚至只通过简单的 GPIO。只要实现了 ``spi_flash_host_driver_t`` 中所有函数不管底层硬件是什么esp_flash API 都可以访问 flash。
芯片驱动
^^^^^^^^^^^
芯片驱动在 ``spi_flash_chip_driver.h`` 中进行定义,并将主机驱动提供的基本函数进行封装以供 API 层使用。
有些操作需在执行前先发送命令,或在执行后读取状态,因此有些芯片需要不同的命令或值以及通信方式。
``generic chip`` 芯片代表了常见的 flash 芯片,其他芯片驱动可以在通用芯片的基础上进行开发。
芯片驱动依赖主机驱动。
OS 函数
^^^^^^^^^^^^
OS 函数层提供访问锁和延迟的方法。
该锁定用于解决 SPI Flash 芯片访问和其他函数之间的冲突。例如,经 SPI0/1 访问 flash 芯片时,应当禁用 cache平时用于取代码和 PSRAM 数据)。另一种情况是,一些没有 CS 线或者 CS 线受软件控制的设备(如通过 SPI 接口的 SD 卡控制)需要在一段时间内独占总线。
延时则用于某些长时操作,需要主机处于等待状态或执行轮询。
顶层 API 将芯片驱动和 OS 函数封装成一个完整的组件,并提供参数检查。

View File

@ -0,0 +1,21 @@
.. _auto_suspend:
When auto suspend is enabled
----------------------------
When auto suspend is enabled, the cache will be kept enabled while accessing the SPI1 bus (e.g. erasing/writing/reading main flash). The hardware handles the arbitration between them.
If SPI1 operation is short (like reading operation), the CPU and the cache will wait until the SPI1 operation is done. However if it's an erasing, auto suspend will happen, interrupting the erasing, making the CPU able to read from cache in limited time.
This way some code/variables can be put into the flash/psram instead of IRAM/DRAM, while still able to be executed during flash erasing. This reduces the some usage of IRAM/DRAM.
Please note this feature has the overhead of the flash suspend/resume. The flash erasing can be extremely long if the erasing is interrupted too often. Use FreeRTOS task priorities to ensure that only real-time critical tasks are executed at higher priority than flash erase, to allow the flash erase to complete in reasonable time.
In other words, there are three kinds of code:
1. Critical code: inside IRAM/DRAM. This kind of code usually has high performance requirements, related to cache/flash/psram, or called very often.
2. Cached code: inside flash/psram. This kind of code has lower performance requirements or called less often. They will execute during erasing, with some overhead.
3. Low priority code: inside flash/psram and disabled during erasing. This kind of code should be forbidden from executed to avoid affecting the flash erasing, by setting a lower task priority than the erasing task.

View File

@ -1,4 +1,198 @@
.. include:: ../../../../components/spi_flash/README.rst
SPI Flash API
=============
:link_to_translation:`zh_CN:[中文]`
Overview
--------
The spi_flash component contains API functions related to reading, writing, erasing, memory mapping for data in the external flash. The spi_flash component also has higher-level API functions which work with partitions defined in the :doc:`partition table </api-guides/partition-tables>`.
Different from the API before IDF v4.0, the functionality of esp_flash_* APIs is not limited to the "main" SPI flash chip (the same SPI flash chip from which program runs). With different chip pointers, you can access to external flashes chips connected to not only SPI0/1 but also other SPI buses like SPI2.
.. note::
Instead of through the cache connected to the SPI0 peripheral, most `esp_flash_*` APIs go through other SPI peripherals like SPI1, SPI2, etc.. This makes them able to access to not only the main flash, but also external flash.
However due to limitations of the cache, operations through the cache are limited to the main flash. The address range limitation for these operations are also on the cache side. The cache is not able to access external flash chips or address range above its capabilities. These cache operations include: mmap, encrypted read/write, executing code or access to variables in the flash.
.. note::
Flash APIs after IDF v4.0 are no longer *atomic*. A writing operation during another on-going read operation, on the overlapped flash address, may cause the return data from the read operation to be partly same as before, and partly updated as new written.
Kconfig option :ref:`CONFIG_SPI_FLASH_USE_LEGACY_IMPL` can be used to switch ``spi_flash_*`` functions back to the implementation before IDF v4.0. However, the code size may get bigger if you use the new API and the old API the same time.
Encrypted reads and writes use the old implementation, even if :ref:`CONFIG_SPI_FLASH_USE_LEGACY_IMPL` is not enabled. As such, encrypted flash operations are only supported with the main flash chip (and not with other flash chips, that is on SPI1 with different CS, or on other SPI buses). Reading through cache is only supported on the main flash, which is determined by the HW.
Support for features of flash chips
-----------------------------------
Flash features of different vendors are operated in different ways and need special support. The fast/slow read and Dual mode (DOUT/DIO) of almost all 24-bits address flash chips are supported, because they don't need any vendor-specific commands.
The Quad mode (QIO/QOUT) the following chip types are supported:
1. ISSI
2. GD
3. MXIC
4. FM
5. Winbond
6. XMC
7. BOYA
The 32-bit address range of following chip type is supported:
1. W25Q256
Initializing a flash device
---------------------------
To use ``esp_flash_*`` APIs, you need to have a chip initialized on a certain SPI bus.
1. Call :cpp:func:`spi_bus_initialize` to properly initialize an SPI bus. This functions initialize the resources (I/O, DMA, interrupts) shared among devices attached to this bus.
2. Call :cpp:func:`spi_bus_add_flash_device` to attach the flash device onto the bus. This allocates memory, and fill the members for the ``esp_flash_t`` structure. The CS I/O is also initialized here.
3. Call :cpp:func:`esp_flash_init` to actually communicate with the chip. This will also detect the chip type, and influence the following operations.
.. note:: Multiple flash chips can be attached to the same bus now. However, using ``esp_flash_*`` devices and ``spi_device_*`` devices on the same SPI bus is not supported yet.
SPI flash access API
--------------------
This is the set of API functions for working with data in flash:
- :cpp:func:`esp_flash_read` reads data from flash to RAM
- :cpp:func:`esp_flash_write` writes data from RAM to flash
- :cpp:func:`esp_flash_erase_region` erases specific region of flash
- :cpp:func:`esp_flash_erase_chip` erases the whole flash
- :cpp:func:`esp_flash_get_chip_size` returns flash chip size, in bytes, as configured in menuconfig
Generally, try to avoid using the raw SPI flash functions to the "main" SPI flash chip in favour of :ref:`partition-specific functions <flash-partition-apis>`.
SPI Flash Size
--------------
The SPI flash size is configured by writing a field in the software bootloader image header, flashed at offset 0x1000.
By default, the SPI flash size is detected by esptool.py when this bootloader is written to flash, and the header is updated with the correct size. Alternatively, it is possible to generate a fixed flash size by setting :envvar:`CONFIG_ESPTOOLPY_FLASHSIZE` in project configuration.
If it is necessary to override the configured flash size at runtime, it is possible to set the ``chip_size`` member of the ``g_rom_flashchip`` structure. This size is used by ``esp_flash_*`` functions (in both software & ROM) to check the bounds.
Concurrency Constraints for flash on SPI1
-----------------------------------------
.. toctree::
:hidden:
spi_flash_concurrency
.. attention::
The SPI0/1 bus is shared between the instruction & data cache (for firmware execution) and the SPI1 peripheral (controlled by the drivers including this SPI Flash driver). Hence, calling SPI Flash API on SPI1 bus (including the main flash) will cause significant influence to the whole system. See :doc:`spi_flash_concurrency` for more details.
.. _flash-partition-apis:
Partition table API
-------------------
ESP-IDF projects use a partition table to maintain information about various regions of SPI flash memory (bootloader, various application binaries, data, filesystems). More information on partition tables can be found :doc:`here </api-guides/partition-tables>`.
This component provides API functions to enumerate partitions found in the partition table and perform operations on them. These functions are declared in ``esp_partition.h``:
- :cpp:func:`esp_partition_find` checks a partition table for entries with specific type, returns an opaque iterator.
- :cpp:func:`esp_partition_get` returns a structure describing the partition for a given iterator.
- :cpp:func:`esp_partition_next` shifts the iterator to the next found partition.
- :cpp:func:`esp_partition_iterator_release` releases iterator returned by ``esp_partition_find``.
- :cpp:func:`esp_partition_find_first` - a convenience function which returns the structure describing the first partition found by ``esp_partition_find``.
- :cpp:func:`esp_partition_read`, :cpp:func:`esp_partition_write`, :cpp:func:`esp_partition_erase_range` are equivalent to :cpp:func:`spi_flash_read`, :cpp:func:`spi_flash_write`, :cpp:func:`spi_flash_erase_range`, but operate within partition boundaries.
.. note::
Application code should mostly use these ``esp_partition_*`` API functions instead of lower level ``esp_flash_*`` API functions. Partition table API functions do bounds checking and calculate correct offsets in flash, based on data stored in a partition table.
SPI Flash Encryption
--------------------
It is possible to encrypt the contents of SPI flash and have it transparently decrypted by hardware.
Refer to the :doc:`Flash Encryption documentation </security/flash-encryption>` for more details.
Memory mapping API
------------------
{IDF_TARGET_CACHE_SIZE:default="64 KB"}
{IDF_TARGET_NAME} features memory hardware which allows regions of flash memory to be mapped into instruction and data address spaces. This mapping works only for read operations. It is not possible to modify contents of flash memory by writing to a mapped memory region.
Mapping happens in {IDF_TARGET_CACHE_SIZE} pages. Memory mapping hardware can map flash into the data address space and the instruction address space. See the technical reference manual for more details and limitations about memory mapping hardware.
Note that some pages are used to map the application itself into memory, so the actual number of available pages may be less than the capability of the hardware.
Reading data from flash using a memory mapped region is the only way to decrypt contents of flash when :doc:`flash encryption </security/flash-encryption>` is enabled. Decryption is performed at the hardware level.
Memory mapping API are declared in ``esp_spi_flash.h`` and ``esp_partition.h``:
- :cpp:func:`spi_flash_mmap` maps a region of physical flash addresses into instruction space or data space of the CPU.
- :cpp:func:`spi_flash_munmap` unmaps previously mapped region.
- :cpp:func:`esp_partition_mmap` maps part of a partition into the instruction space or data space of the CPU.
Differences between :cpp:func:`spi_flash_mmap` and :cpp:func:`esp_partition_mmap` are as follows:
- :cpp:func:`spi_flash_mmap` must be given a {IDF_TARGET_CACHE_SIZE} aligned physical address.
- :cpp:func:`esp_partition_mmap` may be given any arbitrary offset within the partition, it will adjust the returned pointer to mapped memory as necessary
Note that since memory mapping happens in pages, it may be possible to read data outside of the partition provided to ``esp_partition_mmap``, regardless of the partition boundary.
.. note:: mmap is supported by cache, so it can only be used on main flash.
SPI Flash Implementation
------------------------
The ``esp_flash_t`` structure holds chip data as well as three important parts of this API:
1. The host driver, which provides the hardware support to access the chip;
2. The chip driver, which provides compatibility service to different chips;
3. The OS functions, provides support of some OS functions (e.g. lock, delay) in different stages (1st/2st boot, or the app).
Host driver
^^^^^^^^^^^
The host driver relies on an interface (``spi_flash_host_driver_t``) defined in the ``spi_flash_types.h`` (in the ``hal/include/hal`` folder). This interface provides some common functions to communicate with the chip.
In other files of the SPI HAL, some of these functions are implemented with existing {IDF_TARGET_NAME} memory-spi functionalities. However due to the speed limitations of {IDF_TARGET_NAME}, the HAL layer can't provide high-speed implementations to some reading commands (So we didn't do it at all). The files (``memspi_host_driver.h`` and ``.c``) implement the high-speed version of these commands with the ``common_command`` function provided in the HAL, and wrap these functions as ``spi_flash_host_driver_t`` for upper layer to use.
You can also implement your own host driver, even with the GPIO. As long as all the functions in the ``spi_flash_host_driver_t`` are implemented, the esp_flash API can access to the flash regardless of the low-level hardware.
Chip driver
^^^^^^^^^^^
The chip driver, defined in ``spi_flash_chip_driver.h``, wraps basic functions provided by the host driver for the API layer to use.
Some operations need some commands to be sent first, or read some status after. Some chips need different command or value, or need special communication ways.
There is a type of chip called ``generic chip`` which stands for common chips. Other special chip drivers can be developed on the base of the generic chip.
The chip driver relies on the host driver.
.. _esp_flash_os_func:
OS functions
^^^^^^^^^^^^
Currently the OS function layer provides entries of a lock and delay.
The lock (see :ref:`spi_bus_lock`) is used to resolve the conflicts among the access of devices on the same SPI bus, and the SPI Flash chip access. E.g.
1. On SPI1 bus, the cache (used to fetch the data (code) in the Flash and PSRAM) should be disabled when the flash chip on the SPI0/1 is being accessed.
2. On the other buses, the flash driver needs to disable the ISR registered by SPI Master driver, to avoid conflicts.
3. Some devices of SPI Master driver may requires to use the bus monopolized during a period. (especially when the device doesn't have CS wire, or the wire is controlled by the software like SDSPI driver).
The delay is used by some long operations which requires the master to wait or polling periodically.
The top API wraps these the chip driver and OS functions into an entire component, and also provides some argument checking.
See also
--------
@ -17,7 +211,6 @@ In order to perform some flash operations, it is necessary to make sure that bot
- In a single-core setup, the SDK does it by disabling interrupts/scheduler before performing the flash operation.
- In a dual-core setup, this is slightly more complicated as the SDK needs to make sure that the other CPU is not running any code from flash.
When SPI flash API is called on CPU A (can be PRO or APP), start the spi_flash_op_block_func function on CPU B using the esp_ipc_call API. This API wakes up a high priority task on CPU B and tells it to execute a given function, in this case, spi_flash_op_block_func. This function disables cache on CPU B and signals that the cache is disabled by setting the s_flash_op_can_start flag. Then the task on CPU A disables cache as well and proceeds to execute flash operation.
While a flash operation is running, interrupts can still run on CPUs A and B. It is assumed that all interrupt code is placed into RAM. Once the interrupt allocation API is added, a flag should be added to request the interrupt to be disabled for the duration of a flash operations.

View File

@ -0,0 +1,73 @@
Concurrency Constraints for flash on SPI1
=========================================
The SPI0/1 bus is shared between the instruction & data cache (for firmware execution) and the SPI1 peripheral (controlled by the drivers including this SPI Flash driver). Hence, operations to SPI1 will cause significant influence to the whole system. This kind of operations include calling SPI Flash API or other drivers on SPI1 bus, any operations like read/write/erase or other user defined SPI operations, regardless to the main flash or other SPI slave devices.
.. only:: not esp32c3
On {IDF_TARGET_NAME}, these caches must be disabled while reading/writing/erasing.
.. only:: esp32c3
On {IDF_TARGET_NAME}, the config option :ref:`CONFIG_SPI_FLASH_AUTO_SUSPEND` (enabled by default) allows the cache to read flash & PSRAM concurrently with SPI1 operations. See :ref:`auto_suspend` for more details.
If this option is disabled, the caches must be disabled while reading/writing/erasing operations. There are some constraints using driver on the SPI1 bus, see :ref:`impact_disabled_cache`. This constraints will cause more IRAM/DRAM usages.
.. _impact_disabled_cache:
When the caches are disabled
----------------------------
This means that all CPUs must be running code from IRAM and must only be reading data from DRAM while flash write operations occur. If you use the API functions documented here, then the caches will be disabled automatically and transparently. However, note that it will have some performance impact on other tasks in the system.
There are no such constraints and impacts for flash chips on other SPI buses than SPI0/1.
For differences between IRAM, DRAM, and flash cache, please refer to the :ref:`application memory layout <memory-layout>` documentation.
.. only: not CONFIG_FREERTOS_UNICORE
To avoid reading flash cache accidentally, when one CPU initiates a flash write or erase operation, the other CPU is put into a blocked state, and all non-IRAM-safe interrupts are disabled on all CPUs until the flash operation completes.
See also :ref:`esp_flash_os_func`, :ref:`spi_bus_lock`.
.. _iram-safe-interrupt-handlers:
IRAM-Safe Interrupt Handlers
^^^^^^^^^^^^^^^^^^^^^^^^^^^^
If you have an interrupt handler that you want to execute while a flash operation is in progress (for example, for low latency operations), set the ``ESP_INTR_FLAG_IRAM`` flag when the :doc:`interrupt handler is registered </api-reference/system/intr_alloc>`.
You must ensure that all data and functions accessed by these interrupt handlers, including the ones that handlers call, are located in IRAM or DRAM. There are two approaches to do this:
Using Attribute Macros
""""""""""""""""""""""
Use the ``IRAM_ATTR`` attribute for functions::
#include "esp_attr.h"
void IRAM_ATTR gpio_isr_handler(void* arg)
{
// ...
}
Use the ``DRAM_ATTR`` and ``DRAM_STR`` attributes for constant data::
void IRAM_ATTR gpio_isr_handler(void* arg)
{
const static DRAM_ATTR uint8_t INDEX_DATA[] = { 45, 33, 12, 0 };
const static char *MSG = DRAM_STR("I am a string stored in RAM");
}
Note that knowing which data should be marked with ``DRAM_ATTR`` can be hard, the compiler will sometimes recognize that a variable or expression is constant (even if it is not marked ``const``) and optimize it into flash, unless it is marked with ``DRAM_ATTR``.
Using Linker Scripts
""""""""""""""""""""
See :doc:`/api-guides/linker-script-generation` for details.
If a function or symbol is not correctly put into IRAM/DRAM, and the interrupt handler reads from the flash cache during a flash operation, it will cause a crash due to Illegal Instruction exception (for code which should be in IRAM) or garbage data to be read (for constant data which should be in DRAM).
.. only:: esp32c3
.. include:: auto_suspend.inc

View File

@ -0,0 +1,19 @@
.. _auto_suspend:
当使能 flash 擦除的自动暂停
--------------------------------------
当使能 flash 擦除的自动暂停,访问 SPI1 时(如擦除、写入、读取主 flash cache 便无需被禁用。硬件会负责仲裁二者的访问。
SPI1 操作较短时(如读取操作), CPU cache 会等待直到 SPI1 的操作结束。然而对于一个擦除操作,自动暂停会发生并打断擦除操作,允许 CPU 能够在有限时间内从 cache 读取数据。
因此,部分的代码及变量便可以放入 flash / PSRAM 而非 IRAM / DRAM ,同时仍然能够在 flash 擦除期间被执行。这样就减少了 IRAM / DRAM 的消耗。
请注意这个功能会带来 flash 暂停及恢复时的额外开销。如果被频繁打断, flash 的擦除时间可能异常的长。为了确保 flash 擦除操作在一个合理的时间内完成,请调整 FreeRTOS 任务优先级,这样仅有那些高于擦除任务优先级的任务,会在擦除进行过程中会被执行。
换句话说,代码可以分为以下三类:
1. 关键代码:放置在 IRAM / DRAM 中。这类代码通常有较高的性能要求,与 cache / flash / PSRAM 相关,或者被频繁调用。
2. cache 访问的代码:放置在 flash / PSRAM中。这类代码的性能要求较低或者较少被调用。他们会在 flash 擦除的时候被执行,带来一定的开销。
3. 低优先级代码:放置在 flash / PSRAM 中,并且在 flash 擦除的期间被禁止运行。这类代码的任务优先级应被设置的低于擦除任务,从而避免影响 flash 擦除的速度。

View File

@ -1,4 +1,172 @@
.. include:: ../../../../components/spi_flash/README_CN.rst
SPI Flash API
=================
:link_to_translation:`en:[English]`
概述
--------
SPI Flash 组件提供外部 flash 数据读取、写入、擦除和内存映射相关的 API 函数,同时也提供了更高层级的,面向分区的 API 函数(定义在 :doc:`分区表 </api-guides/partition-tables>` 中)。
与 ESP-IDF V4.0 之前的 API 不同,这一版 `esp_flash_*` API 功能并不局限于主 SPI Flash 芯片(即运行程序的 SPI Flash 芯片)。使用不同的芯片指针,您可以通过 SPI0/1 或 HSPI/VSPI 总线访问外部 flash。
.. note::
大多数 `esp_flash_*` API 使用 SPI1SPI2 等外设而非通过 SPI0 上的 cache。这使得它们不仅能访问主 flash也能访问外部 flash 。
而由于 cache 的限制,所有经过 cache 的操作都只能对 flash 进行。这些操作的地址同样受到 cache 能力的限制。 Cache 无法访问外部 flash 或者高于它能力的地址段。这些 cache 操作包括mmap ,加密读写,执行代码或者访问在 flash 中的变量。
.. note::
ESP-IDF V4.0 之后的 flash API 不再是原子的。因此,如果 flash 操作地址有重叠,且写操作与读操作同时执行,读操作可能会返回一部分写入之前的数据,返回一部分写入之后的数据。
Kconfig 选项 :ref:`CONFIG_SPI_FLASH_USE_LEGACY_IMPL` 可将 ``spi_flash_*`` 函数切换至 ESP-IDF V4.0 之前的实现。但是,如果同时使用新旧 API代码量可能会增多。
即便未启用 :ref:`CONFIG_SPI_FLASH_USE_LEGACY_IMPL`,加密读取和加密写入操作也均使用旧实现。因此,仅有主 flash 芯片支持加密操作,外接(经 SPI1 使用其他不同片选访问,或经其它 SPI 总线访问)的 flash 芯片则不支持加密操作。也仅有主 flash 支持从 cache 当中读取,因为这是由硬件决定的。
初始化 Flash 设备
---------------------------
在使用 ``esp_flash_*`` API 之前,您需要在 SPI 总线上初始化芯片。
1. 调用 :cpp:func:`spi_bus_initialize` 初始化 SPI 总线,此函数将初始化总线上设备间共享的资源,如 I/O、DMA 及中断等。
2. 调用 :cpp:func:`spi_bus_add_flash_device` 将 flash 设备连接到总线上。然后分配内存,填充 ``esp_flash_t`` 结构体,同时初始化 CS I/O。
3. 调用 :cpp:func:`esp_flash_init` 与芯片进行通信。后续操作会依据芯片类型不同而有差异。
.. note:: 目前,多个 flash 芯片可连接到同一总线。但尚不支持在同一个 SPI 总线上使用 ``esp_flash_*````spi_device_*`` 设备。
SPI Flash 访问 API
--------------------
如下所示为处理 flash 中数据的函数集:
- :cpp:func:`esp_flash_read`:将数据从 flash 读取到 RAM
- :cpp:func:`esp_flash_write`:将数据从 RAM 写入到 flash
- :cpp:func:`esp_flash_erase_region`:擦除 flash 中指定区域的数据;
- :cpp:func:`esp_flash_erase_chip`:擦除整个 flash
- :cpp:func:`esp_flash_get_chip_size`:返回 menuconfig 中设置的 flash 芯片容量(以字节为单位)。
一般来说,请尽量避免对主 SPI flash 芯片直接使用原始 SPI flash 函数,如需对主 SPI flash 芯片进行操作,请使用 :ref:`分区专用函数 <flash-partition-apis>`
SPI Flash 容量
--------------
SPI flash 容量存储于引导程序映像头部(烧录偏移量为 0x1000的一个字段。
默认情况下,引导程序写入 flash 时esptool.py 将引导程序写入 flash 时,会自动检测 SPI flash 容量,同时使用正确容量更新引导程序的头部。您也可以在工程配置中设置 :envvar:`CONFIG_ESPTOOLPY_FLASHSIZE`,生成固定的 flash 容量。
如需在运行时覆盖已配置的 flash 容量,请配置 ``g_rom_flashchip`` 结构中的 ``chip_size````esp_flash_*`` 函数使用此容量(于软件和 ROM 中)进行边界检查。
SPI1 Flash 并发约束
-----------------------------------------
.. toctree::
:hidden:
spi_flash_concurrency
.. attention::
指令/数据 cache用以执行固件与 SPI1 外设(由像 SPI Flash 驱动一样的驱动程序控制)共享 SPI0/1 总线。因此,在 SPI1 总线上调用 SPI Flash API (包括访问主 flash )会对整个系统造成显著的影响。更多细节,参见 :doc:`spi_flash_concurrency`
.. _flash-partition-apis:
分区表 API
-------------------
ESP-IDF 工程使用分区表保存 SPI flash 各区信息,包括引导程序、各种应用程序二进制文件、数据及文件系统等。请参考 :doc:`分区表 </api-guides/partition-tables>`,查看详细信息。
该组件在 ``esp_partition.h`` 中声明了一些 API 函数,用以枚举在分区表中找到的分区,并对这些分区执行操作:
- :cpp:func:`esp_partition_find`:在分区表中查找特定类型的条目,返回一个不透明迭代器;
- :cpp:func:`esp_partition_get`:返回一个结构,描述给定迭代器的分区;
- :cpp:func:`esp_partition_next`:将迭代器移至下一个找到的分区;
- :cpp:func:`esp_partition_iterator_release`:释放 ``esp_partition_find`` 中返回的迭代器;
- :cpp:func:`esp_partition_find_first`:返回一个结构,描述 ``esp_partition_find`` 中找到的第一个分区;
- :cpp:func:`esp_partition_read`:cpp:func:`esp_partition_write`:cpp:func:`esp_partition_erase_range` 在分区边界内执行,等同于 :cpp:func:`spi_flash_read`:cpp:func:`spi_flash_write`:cpp:func:`spi_flash_erase_range`
.. note::
请在应用程序代码中使用上述 ``esp_partition_*`` API 函数,而非低层级的 ``esp_flash_*`` API 函数。分区表 API 函数根据存储在分区表中的数据,进行边界检查并计算在 flash 中的正确偏移量。
SPI Flash 加密
--------------------
您可以对 SPI flash 内容进行加密,并在硬件层对其进行透明解密。
请参阅 :doc:`Flash 加密 </security/flash-encryption>`,查看详细信息。
内存映射 API
------------------
{IDF_TARGET_CACHE_SIZE:default="64 KB"}
{IDF_TARGET_NAME} 内存硬件可以将 flash 部分区域映射到指令地址空间和数据地址空间,此映射仅用于读操作。不能通过写入 flash 映射的存储区域来改变 flash 中内容。
Flash 以 {IDF_TARGET_CACHE_SIZE} 页为单位进行地址映射。内存映射硬件既可将 flash 映射到数据地址空间,也能映射到指令地址空间。请参考《 {IDF_TARGET_NAME} 技术参考手册》查看内存映射硬件的详细信息及有关限制。
请注意,有些页被用于将应用程序映射到内存中,因此实际可用的页会少于硬件提供的总数。
:doc:`Flash 加密 </security/flash-encryption>` 启用时,使用内存映射区域从 flash 读取数据是解密 flash 的唯一方法,解密需在硬件层进行。
内存映射 API 在 ``esp_spi_flash.h````esp_partition.h`` 中声明:
- :cpp:func:`spi_flash_mmap`:将 flash 物理地址区域映射到 CPU 指令空间或数据空间;
- :cpp:func:`spi_flash_munmap`:取消上述区域的映射;
- :cpp:func:`esp_partition_mmap`:将分区的一部分映射至 CPU 指令空间或数据空间;
:cpp:func:`spi_flash_mmap`:cpp:func:`esp_partition_mmap` 的区别如下:
- :cpp:func:`spi_flash_mmap`:需要给定一个 {IDF_TARGET_CACHE_SIZE} 对齐的物理地址;
- :cpp:func:`esp_partition_mmap`:给定分区内任意偏移量即可,此函数根据需要将返回的指针调整至指向映射内存。
内存映射以页为单位,即使传递给 ``esp_partition_mmap`` 的是一个分区,分区外的数据也是也是可以被读取到的,不会受到分区边界的影响。
.. note::
由于 mmap 是由 cache 支持的因此mmap 也仅能用在主 flash 上。
实现
--------------
``esp_flash_t`` 结构包含芯片数据和该 API 的三个重要部分:
1. 主机驱动,为访问芯片提供硬件支持;
2. 芯片驱动,为不同芯片提供兼容性服务;
3. OS 函数,在不同阶段(一级或二级 Boot 或者应用程序阶段)为部分 OS 函数提供支持(如一些锁、延迟)。
主机驱动
^^^^^^^^^^^^^^^
主机驱动依赖 ``hal/include/hal`` 文件夹下 ``spi_flash_types.h`` 定义的 ``spi_flash_host_driver_t`` 接口。该接口提供了一些与芯片通信常用的函数。
在 SPI HAL 文件中,有些函数是基于现有的 {IDF_TARGET_NAME} memory-spi 来实现的。但是,由于 {IDF_TARGET_NAME} 速度限制HAL 层无法提供某些读命令的高速实现(所以这些命令根本没有在 HAL 的文件中被实现)。``memspi_host_driver.h````.c`` 文件使用 HAL 提供的 ``common_command`` 函数实现上述读命令的高速版本,并将所有它实现的及 HAL 函数封装为 ``spi_flash_host_driver_t`` 供更上层调用。
您也可以实现自己的主机驱动,甚至只通过简单的 GPIO。只要实现了 ``spi_flash_host_driver_t`` 中所有函数不管底层硬件是什么esp_flash API 都可以访问 flash。
芯片驱动
^^^^^^^^^^^
芯片驱动在 ``spi_flash_chip_driver.h`` 中进行定义,并将主机驱动提供的基本函数进行封装以供 API 层使用。
有些操作需在执行前先发送命令,或在执行后读取状态,因此有些芯片需要不同的命令或值以及通信方式。
``generic chip`` 芯片代表了常见的 flash 芯片,其他芯片驱动可以在通用芯片的基础上进行开发。
芯片驱动依赖主机驱动。
.. _esp_flash_os_func:
OS 函数
^^^^^^^^^^^^
OS 函数层提供访问锁和延迟的方法。
该锁定用于解决 SPI Flash 芯片访问和其他函数之间的冲突。例如,经 SPI0/1 访问 flash 芯片时,应当禁用 cache平时用于取代码和 PSRAM 数据)。另一种情况是,一些没有 CS 线或者 CS 线受软件控制的设备(如通过 SPI 接口的 SD 卡控制)需要在一段时间内独占总线。
延时则用于某些长时操作,需要主机处于等待状态或执行轮询。
顶层 API 将芯片驱动和 OS 函数封装成一个完整的组件,并提供参数检查。
另请参考
------------

View File

@ -0,0 +1,74 @@
SPI1 Flash 并发约束
=========================================
指令/数据 cache (用以执行固件)与 SPI1 外设(由像 SPI Flash 驱动一样的驱动程序控制)共享 SPI0/1 总线。因此,对 SPI1 外设的操作会对整个系统造成显著的影响。这类操作包括调用 SPI Flash API 或者其他 SPI1 总线上的驱动,任何 flash 操作(如读取、写入、擦除)或者其他用户定义的 SPI 操作,无论是对主 flash 或者其他各类的 SPI 从机。
.. only:: not esp32c3
在 {IDF_TARGET_NAME} 上, flash 读取/写入/擦除时 cache 必须被禁用。
.. only:: esp32c3
在 {IDF_TARGET_NAME} 上,默认启用的配置选项 :ref:`CONFIG_SPI_FLASH_AUTO_SUSPEND` 允许 flash / PSRAM 的 cache 访问和 SPI1 的操作存并发地执行。更多详情,参见 :ref:`auto_suspend`
然而当该选项被禁用时,读取/写入/擦除 flash 时, cache 必须被禁用。使用驱动访问 SPI1 的相关约束参见 :ref:`impact_disabled_cache` 。这些约束会带来更多的 IRAM / DRAM 消耗。
.. _impact_disabled_cache:
当 cache 被禁用时
----------------------------
这意味着当 flash 擦写操作发生时,所有的 CPU 都只能执行 IRAM 中的代码,而且必须从 DRAM 中读取数据。如果您使用本文档中 API 函数,上述限制将自动生效且透明(无需您额外关注),但这些限制可能会影响系统中的其他任务的性能。
除 SPI0/1 以外的 SPI 总线上的其它 flash 芯片则不受这种限制。
请参阅 :ref:`应用程序内存分布 <memory-layout>`,查看 IRAM、DRAM 和 flash cache 的区别。
.. only: not CONFIG_FREERTOS_UNICORE
为避免意外读取 flash cache一个 CPU 在启动 flash 写入或擦除操作时,另一个 CPU 将阻塞,并且在 flash 操作完成前,所有 CPU 上,所有的非 IRAM 安全的中断都会被禁用。
另请参阅 :ref:`esp_flash_os_func`:ref:`spi_bus_lock`
.. _iram-safe-interrupt-handlers:
IRAM 安全中断处理程序
^^^^^^^^^^^^^^^^^^^^^^^^^^^^
如果您需要在 flash 操作期间运行中断处理程序(比如低延迟操作),请在 :doc:`注册中断处理程序 </api-reference/system/intr_alloc>` 时设置 ``ESP_INTR_FLAG_IRAM``
请确保中断处理程序访问的所有数据和函数(包括其调用的数据和函数)都存储在 IRAM 或 DRAM 中。有两种方法可供使用:
使用属性宏
""""""""""""""""""""
为函数添加 ``IRAM_ATTR`` 属性::
#include "esp_attr.h"
void IRAM_ATTR gpio_isr_handler(void* arg)
{
// ...
}
为常量添加 ``DRAM_ATTR````DRAM_STR`` 属性::
void IRAM_ATTR gpio_isr_handler(void* arg)
{
const static DRAM_ATTR uint8_t INDEX_DATA[] = { 45, 33, 12, 0 };
const static char *MSG = DRAM_STR("I am a string stored in RAM");
}
辨别哪些数据应标记为 ``DRAM_ATTR`` 可能会比较困难,除非明确标记为 ``DRAM_ATTR``,否则编译器依然可能将某些变量或表达式当做常量(即便没有 ``const`` 标记),并将其放入 flash。
使用链接脚本
""""""""""""""""""""""
参见 :doc:`/api-guides/linker-script-generation`
如果函数或符号未被正确放入 IRAM/DRAM 中,当中断处理程序在 flash 操作期间从 flash cache 中读取数据,则会产生非法指令异常(这是因为代码未被正确放入 IRAM或读取垃圾数据这是因为常数未被正确放入 DRAM而导致崩溃。
.. only:: esp32c3
.. include:: auto_suspend.inc