API GUIDE / SFC / MIDDLEWARE

SFC Middleware 接口

sfc mid api

本文面向 BSP 和应用开发者,说明如何通过 SFC 中间件访问 NOR/NAND Flash、如何通过 DFS 挂载文件系统,并提供公开 API 参考。内部控制器时序、CDT 表构造、NAND 器件表和 HAL 实现不在本文范围内。

API 头文件:

  • code/middleware/sfc/include/im_sfc_device.h
  • code/middleware/sfc/include/im_sfc.h
  • code/middleware/sfc/include/im_sfc_nor.h
  • code/middleware/sfc/include/im_sfc_nand.h

DFS 挂载相关接口来自 code/middleware/filesystem/include/filesystem_init.h 和 code/middleware/filesystem/include/dfs_fs.h,不属于 SFC 中间件 API。

同一构建只能选择 NOR 或 NAND 一种设备中间件。


1. 模块概览

1.1 软件分层

组件 作用
im_sfc Common SFC 控制器层,以及编译期选中的 NOR 或 NAND 中间件
im_mtd 把 NOR/NAND 分区发布为统一 MTD 对象
im_sfc_device 设备层,保存 BSP 配置,管理 NOR/NAND、MTD 和生命周期
dfs_mtd 把 MTD 分区注册为 DFS 设备,属于 DFS 侧组件
BSP board_sfc.c 描述板级 Flash 配置并注册设备,不直接访问 Flash

普通应用推荐使用 im_sfc_device:BSP 注册一次,DFS、日志、Shell 和应用共享同一个已启动设备。应用也可以绕过设备层,直接创建 NOR/NAND/Common SFC 对象,但需要自行管理初始化和生命周期。

1.2 运行时关系

BSP board_sfc.c
  -> im_sfc_device_register()

DFS file_system_init()                         [CONFIG_DFS]
  -> im_sfc_device_start()
     -> NOR/NAND 初始化
     -> MTD 挂接
  -> dfs_mtd 把分区注册为 DFS 设备
  -> dfs_init()
  -> 自动挂载 / 应用调用 mount_fs()

应用或 Shell
  -> im_sfc_device_start()
  -> im_sfc_device_nor_get() / nand_get() / mtd_get()
  -> NOR/NAND/MTD API
  -> 文件路径访问(DFS 已挂载时)

设备层只负责启动 SFC 并发布 MTD;DFS 设备注册和文件系统挂载由 DFS 完成,不由 im_sfc_device 完成。

1.3 头文件选择

使用方式 包含
通过设备层访问 <im_sfc_device.h>
直接使用 NOR <im_sfc_nor.h>
直接使用 NAND <im_sfc_nand.h>
直接使用 Common SFC 事务 <im_sfc.h>

im_sfc_nor.h 和 im_sfc_nand.h 已包含 im_sfc.h,不要重复依赖内部头文件。

1.4 使用约定

  • 返回 error_code_t 的接口以 IM_OK 表示成功。失败时不要使用本次调用写出的数据,除非接口另有说明。
  • timeout_ms == 0 使用默认超时 3000 ms。NOR 多块擦除、NAND 多次状态等待可能累计多个超时周期。
  • IM_ERR_TIMEOUT 不表示已经发出的 Flash 命令被撤销。编程、擦除或页读可能仍在器件内执行。
  • NOR 使用 Flash 字节地址。NAND 使用 block / page / column,长度和列地址的单位都是字节,列范围包含 spare 区。
  • name[32] 等定长字符串不保证以 NUL 结尾,打印或比较时按字段长度处理。
  • 初始化、读写、擦除、时钟切换和 XIP 操作都是同步阻塞接口,只能在任务上下文调用。
  • NOR/NAND 高层会串行化同一设备上的完整 Flash 操作;Common SFC 对象不串行化并发调用。
  • NOR/NAND 高层按每个数据 phase 的长度自动选择数据通路:达到 1024 字节使用 DMA,否则使用 CPU FIFO;不满足 DMA 对齐要求的缓冲区由高层自动 bounce。
  • 直接调用 Common 事务接口并指定 DMA 时,对齐、物理连续和 cache 维护由调用方负责。
  • 多页写入、多块擦除不是事务,失败时已完成的部分不会自动回滚。

2. 典型流程

2.1 Kconfig 配置

SFC 的配置项位于 code/middleware/sfc/Kconfig,配置菜单路径为 Middleware -> SFC Middleware。选择前需要先满足两个依赖:

  • Drivers -> G32S10 Drivers -> SFC driver:对应 CONFIG_DRIVER_SFC=y。
  • OS -> Build OS API:对应 CONFIG_OS_API=y,默认已启用。

依赖不满足时,SFC middleware 选项不可见。启用 CONFIG_MIDDLEWARE_SFC 后,继续选择 NOR 或 NAND;二者是同一个 choice,只能启用一个,默认选择 NOR。

配置项 默认值 说明
CONFIG_MIDDLEWARE_SFC n 启用 SFC 中间件
CONFIG_MIDDLEWARE_SFC_NOR y 编译 NOR 中间件,与 NAND 二选一
CONFIG_MIDDLEWARE_SFC_NAND n 编译 NAND 中间件,与 NOR 二选一
CONFIG_MIDDLEWARE_SFC_NOR_PARAM_OFFSET 0x6800 NOR Burner 参数块在 Flash 中的偏移
CONFIG_MIDDLEWARE_SFC_NAND_PARAM_OFFSET 0x6800 NAND Burner 参数块在 Flash 中的偏移
CONFIG_MIDDLEWARE_SFC_NAND_SKIP_BADBLOCK n NAND 跳过坏块检查

CONFIG_MIDDLEWARE_SFC_NOR_PARAM_OFFSET 只会在选择 NOR 后出现;NAND 偏移和跳过坏块选项只会在选择 NAND 后出现。CONFIG_MIDDLEWARE_SFC_NAND_SKIP_BADBLOCK 只适用于已知良品或测试场景,不建议在量产配置中启用。

使用 menuconfig 选择

在 SDK 工程根目录执行:

source tools/imde/env.sh
imde list-defconfigs
imde use-defconfig g32s10 g32s10_v1_mini_defconfig --reconfigure
imde menuconfig --configure-if-needed

第一次选择板级配置时使用上面的 use-defconfig。如果工程中已有 .config,不要再次执行 use-defconfig,否则会覆盖当前配置;直接运行 imde menuconfig --configure-if-needed。

在 menuconfig 中依次完成:

  1. 在 Drivers -> G32S10 Drivers 中启用 SFC driver。
  2. 在 OS 中确认 Build OS API 已启用。
  3. 在 Middleware -> SFC Middleware 中启用 SFC middleware。
  4. 在 SFC device type 中选择 NOR Flash 或 NAND Flash。
  5. 按实际板级烧录布局设置参数块偏移,然后保存并退出。

menuconfig 检测到 .config 变化后会自动同步生成配置并重新执行 CMake configure,随后直接构建:

imde build

defconfig 关键项

NOR 配置至少应包含:

CONFIG_DRIVER_SFC=y
CONFIG_OS_API=y
CONFIG_MIDDLEWARE_SFC=y
CONFIG_MIDDLEWARE_SFC_NOR=y
# CONFIG_MIDDLEWARE_SFC_NAND is not set
CONFIG_MIDDLEWARE_SFC_NOR_PARAM_OFFSET=0x6800

NAND 配置至少应包含:

CONFIG_DRIVER_SFC=y
CONFIG_OS_API=y
CONFIG_MIDDLEWARE_SFC=y
# CONFIG_MIDDLEWARE_SFC_NOR is not set
CONFIG_MIDDLEWARE_SFC_NAND=y
CONFIG_MIDDLEWARE_SFC_NAND_PARAM_OFFSET=0x6800
# CONFIG_MIDDLEWARE_SFC_NAND_SKIP_BADBLOCK is not set

批量维护时可以直接修改 defconfig,然后重新选择并配置:

imde use-defconfig <source> <config> --reconfigure
imde build

参数块偏移

NOR/NAND 初始化时从参数块偏移处读取 Burner 写入的 Flash 参数。0x6800 是 SDK 默认值,不是分区起点,也不应仅根据 Flash 容量修改。

修改该值时必须同步更新 Burner/烧录工具中的参数块位置,否则设备初始化会读取错误数据。BSP 的 im_sfc_device_config_t.param_offset 为 0 时沿用 Kconfig 配置;非 0 时由 BSP 值覆盖。

配置保存与启动方式

CONFIG_DFS 与 SFC 配置相互独立。同时启用 SFC 和 DFS(Middleware -> Filesystem -> Device filesystem)时,file_system_init() 会启动 SFC 并注册 MTD 分区;只使用裸 Flash 接口时不需要启用 DFS,应用在使用前调用 im_sfc_device_start(),已有自动启动路径时除外。启动方式的差异见 2.4 节,文件系统配置和挂载见 2.5 节。

BSP 组件应显式依赖 im_sfc_device。启用 CONFIG_DFS 时,DFS 组件也需要依赖 im_sfc_device,以便通过设备层启动 Flash。

2.2 BSP 注册设备

BSP 提供一份板级配置并注册到设备层。注册阶段只复制配置,不访问 Flash。

#include <im_sfc_device.h>

static const im_sfc_device_config_t s_board_sfc_config = {
    .board = {
        .sample_delay_init = 4U,
        .sample_delay_work = 4U,
        .ce_inactive_level = HAL_SFC_LEVEL_HIGH,
        .hold_inactive_level = HAL_SFC_LEVEL_HIGH,
        .wp_inactive_level = HAL_SFC_LEVEL_HIGH,
    },
    .clock_rate_hz = 0U,
    .param_offset = 0U,
};

error_code_t board_sfc_register_devices(void)
{
    error_code_t ret;

    ret = im_sfc_device_register(IM_SFC_DEVICE_ID_DEFAULT,
                                 &s_board_sfc_config);
    return (ret == IM_ERR_ALREADY_REGISTERED) ? IM_OK : ret;
}

启用 CONFIG_MIDDLEWARE_SFC 后,core_v1_early_init() 会调用当前 BSP 的 board_sfc_register_devices()。

2.3 启动并访问设备

DFS 会自动启动设备。没有 DFS、也没有其他自动启动路径时,应用在使用 Flash 前调用一次 im_sfc_device_start():

#include <im_sfc_device.h>

static error_code_t app_read(uint32_t address, uint8_t *buffer, uint32_t length)
{
    im_sfc_nor_t *nor;
    error_code_t ret;

    ret = im_sfc_device_start(IM_SFC_DEVICE_ID_DEFAULT);
    if (ret != IM_OK)
        return ret;

    nor = im_sfc_device_nor_get(IM_SFC_DEVICE_ID_DEFAULT);
    if (nor == NULL)
        return IM_ERR_NOT_READY;

    return im_sfc_nor_read(nor, address, length, buffer, 0U);
}

NAND 构建把 im_sfc_device_nor_get() 和 im_sfc_nor_read() 换成对应的 NAND 接口。

设备层规则:

  • device_id 使用 IM_SFC_DEVICE_ID_DEFAULT。
  • im_sfc_device_start() 可重复调用,已启动时返回 IM_OK。
  • im_sfc_device_nor_get() / nand_get() / mtd_get() 返回借用指针,不能释放。
  • 不要再对借用对象调用 init();由设备层统一启动和停止。
  • im_sfc_device_stop() 后指针立即失效,再次启动后要重新获取。

2.4 自动启动与手动启动

场景 启动方式
启用 CONFIG_DFS file_system_init() 启动设备、注册 DFS 设备,并执行已配置的自动挂载
Flash raw 日志 日志后端首次访问时调用 im_sfc_device_start()
Shell/USB 使用前调用 im_sfc_device_start()
普通应用 没有自动启动路径时自行调用 im_sfc_device_start()
直接使用 NOR/NAND/Common 调用方自行初始化,不经过设备层

如果 DFS 已挂载分区,不要直接 im_sfc_device_stop()。必须先卸载文件系统并停止所有访问者。stop() 不会注销 dfs_mtd 创建的 DFS 设备。

file_system_init() 初始化失败后(例如 SFC 启动、DFS 设备注册或 rootfs 挂载失败),会进入固定的 FAILED 状态;后续调用直接返回同一错误,不会自动重试。应用应在处理故障后重新初始化文件系统。

2.5 DFS 文件系统挂载

SFC 只负责把 Flash 分区发布为 MTD。应用要按文件路径访问 Flash,还需要启用 DFS,并把 MTD 分区注册为 DFS 设备后挂载文件系统:

挂载会把一个 DFS 设备(分区)绑定到目录;挂载完成后访问 /data/... 就是访问该分区中的文件系统。

挂载前提是 Burner 参数块中已经有合法分区;SFC/MTD 不创建分区,分区名就是 DFS 设备名。

file_system_init()
  -> im_sfc_device_start()
  -> MTD 分区
  -> dfs_mtd 注册 DFS 设备(设备名 = 分区名)
  -> dfs_init()
  -> 已配置的自动挂载

应用(未自动挂载时)
  -> im_dfs_mount_point_create()
  -> mount_fs()
  -> /<分区名>/...

NOR 使用 FAT 文件系统,DFS 后端名为 "elm";NAND 使用 YAFFS2,文件系统类型名为 "yaffs"。不要用 "elm" 挂载 NAND 分区。配置入口为 Middleware -> Filesystem:先启用 Device filesystem,再在 Filesystem Types 中选择对应后端。

相关 DFS Kconfig 配置项:

配置项 说明
CONFIG_DFS 启用 DFS;依赖 CONFIG_OS_API
CONFIG_DFS_ELMFAT NOR 使用的 ELM FatFs 后端
CONFIG_DFS_YAFFS NAND 使用的 YAFFS2 后端;依赖 CONFIG_MIDDLEWARE_SFC_NAND
CONFIG_DFS_ELM_MAX_SECTOR_SIZE FatFs 最大扇区大小,默认 8192;按 NOR 最大擦除块设置,例如 4 KiB 器件用 4096,32 KiB 器件用 32768
CONFIG_DFS_ROOT_DEVICE_RAMDISK / 使用 RAM disk,掉电不保留;NOR 的启动挂载默认使用该方式
CONFIG_DFS_ROOT_DEVICE_FLASH / 挂载 NOR 参数块中名为 rootfs 的 FAT 分区;需启用 ELMFAT,且仅适用于 NOR
CONFIG_DFS_ELMFAT_MOUNT_PARTITIONn / ..._NAME RAM disk 根模式下,启动时把指定 NOR 分区挂载到 /<分区名>
CONFIG_DFS_YAFFS_MOUNT_PARTITIONn / ..._NAME 启动时把指定 NAND 分区挂载到 /<分区名>
CONFIG_DFS_MOUNT_AUTO_FORMAT 卷没有可用文件系统时允许自动格式化;默认关闭

开机自动挂载

NOR 使用 RAM disk 作为根目录,并把 data 分区自动挂载到 /data:

CONFIG_DFS=y
CONFIG_DFS_ELMFAT=y
CONFIG_DFS_ELM_MAX_SECTOR_SIZE=32768
CONFIG_DFS_ROOT_DEVICE_RAMDISK=y
CONFIG_DFS_ELMFAT_MOUNT_PARTITION0=y
CONFIG_DFS_ELMFAT_MOUNT_PARTITION0_NAME="data"

NAND 自动挂载 YAFFS2 分区:

CONFIG_DFS=y
CONFIG_DFS_YAFFS=y
CONFIG_DFS_YAFFS_MOUNT_PARTITION0=y
CONFIG_DFS_YAFFS_MOUNT_PARTITION0_NAME="data"

自动挂载规则:

  • PARTITIONn 中的 n 是挂载槽位 0~7,不是参数块中的分区序号。
  • ..._NAME 必须与 Burner 参数块中的分区名完全一致,挂载点固定为 /<分区名>。
  • NOR 选择 CONFIG_DFS_ROOT_DEVICE_FLASH 时,只自动挂载名为 rootfs 的根分区;其他 NOR 分区需要手动挂载。rootfs 不存在,或默认关闭自动格式化时 rootfs 未格式化,都会使 file_system_init() 失败。
  • NAND 不要求提供 rootfs 分区;只启用 YAFFS2 时,根目录由 root-stub 提供,NAND 分区仍可自动或手动挂载到 /<分区名>。
  • 自动挂载不会默认格式化卷。卷没有文件系统时会挂载失败;此时先执行一次 dfs_mkfs();启用 CONFIG_DFS_SHELL_COMMANDS 时也可使用 Shell mkfs <fs> <device>(如 NOR 的 mkfs elm data、NAND 的 mkfs yaffs data),格式化会清除分区数据。
  • 普通分区自动挂载失败只打印日志,file_system_init() 仍可能返回 IM_OK;rootfs 根分区挂载失败会使初始化返回错误。应检查日志,并在启用 CONFIG_DFS_SHELL_COMMANDS 时使用 df 确认挂载结果。

手动挂载

未配置自动挂载,或需要挂载其他分区时,先创建挂载点目录,再调用 mount_fs():

#include <im_config.h>
#include <im_log.h>
#include <filesystem_init.h>
#include <dfs_device.h>
#include <dfs_fs.h>

#define APP_FS_DEVICE "data"
#define APP_FS_MOUNT  "/data"

#if defined(CONFIG_MIDDLEWARE_SFC_NOR)
#define APP_FS_TYPE "elm"
#elif defined(CONFIG_MIDDLEWARE_SFC_NAND)
#define APP_FS_TYPE "yaffs"
#endif

error_code_t app_mount_data(void)
{
    error_code_t ret;
    int status;

    ret = file_system_init();
    if (ret != IM_OK)
        return ret;

    ret = im_dfs_mount_point_create(APP_FS_MOUNT);
    if ((ret != IM_OK) && (ret != IM_ERR_ALREADY_EXISTS))
        return ret;

    status = mount_fs(APP_FS_MOUNT, APP_FS_DEVICE, APP_FS_TYPE,
                      0U, NULL, FORMAT_FILESYSTEM_TYPE_NO_FORMAT);
    if (status != 0) {
        IM_LOGE("mount %s on %s failed errno=%d",
                APP_FS_DEVICE, APP_FS_MOUNT, im_dfs_get_errno());
        return IM_ERR_IO;
    }

    return IM_OK;
}

说明:

  • 当前固件启动流程已在 CONFIG_DFS 下调用 file_system_init();成功后的重复调用返回 IM_OK,失败状态不会自动重试。自定义启动流程必须自行调用。
  • 该示例适用于未自动挂载 data 的场景;同一分区不要重复挂载。
  • mount_fs() 使用 FORMAT_FILESYSTEM_TYPE_NO_FORMAT 时只做挂载,不会格式化。传入 FORMAT_FILESYSTEM_TYPE_FAT(NOR)或 FORMAT_FILESYSTEM_TYPE_GENERIC(NAND),并开启 CONFIG_DFS_MOUNT_AUTO_FORMAT 后,才会在卷不可用时自动格式化并重试。
  • 首次使用或文件系统损坏时,先卸载文件系统并关闭所有访问者,再调用 dfs_mkfs(APP_FS_TYPE, APP_FS_DEVICE)。格式化会清除该分区全部内容。
  • 如果绕过 file_system_init() 自行启动 SFC,必须按顺序调用 dfs_device_init(),把 MTD 分区注册成 DFS 设备(NOR 使用 im_mtd_block_devices_register(),NAND 使用 im_mtd_nand_devices_register()),再调用 dfs_init() 并准备根目录/挂载点;否则 mount_fs() 找不到设备或无法解析路径。

挂载成功后,/data 下的文件由 DFS 转发到 Flash。应用可以使用 <dfs_posix.h> 提供的 open() / read() / write() / stat(),也可以使用标准 fopen() / fread() / fwrite()。

卸载时先关闭文件和其他访问者,再执行:

if (dfs_unmount(APP_FS_MOUNT) != 0)
    IM_LOGE("unmount %s failed", APP_FS_MOUNT);
(void)im_dfs_mount_point_remove(APP_FS_MOUNT);

必须卸载所有基于 SFC 分区的文件系统并停止访问后,才能调用 im_sfc_device_stop()。

2.6 直接初始化 NOR/NAND

绕过设备层时,NOR/NAND 对象由调用方分配,并且必须零初始化:

im_sfc_nor_t nor;
im_sfc_nor_config_t config;
error_code_t ret;

memset(&nor, 0, sizeof(nor));
ret = im_sfc_nor_config_default(&config);
if (ret == IM_OK)
    ret = im_sfc_nor_init(&nor, &config);
if (ret == IM_OK)
    ret = im_sfc_nor_read(&nor, address, length, buffer, 0U);

(void)im_sfc_nor_deinit(&nor);

直接使用低层对象时,应用自行负责时钟、并发和生命周期;不会自动获得设备层的板级配置、MTD 挂接或 DFS 注册。


3. 配置和关键类型

3.1 状态枚举

typedef enum {
    IM_SFC_STATE_UNINITIALIZED = 0U,
    IM_SFC_STATE_READY = 1U,
    IM_SFC_STATE_BUSY = 2U,
    IM_SFC_STATE_WORKING = 3U,
    IM_SFC_STATE_ERROR = 4U,
    IM_SFC_STATE_XIP = 5U,
} im_sfc_state_t;

typedef enum {
    IM_SFC_NOR_STATE_UNINITIALIZED = 0U,
    IM_SFC_NOR_STATE_READY = 1U,
    IM_SFC_NOR_STATE_BUSY = 2U,
    IM_SFC_NOR_STATE_ERROR = 3U,
    IM_SFC_NOR_STATE_XIP = 4U,
} im_sfc_nor_state_t;

typedef enum {
    IM_SFC_NAND_STATE_UNINITIALIZED = 0U,
    IM_SFC_NAND_STATE_READY = 1U,
    IM_SFC_NAND_STATE_BUSY = 2U,
    IM_SFC_NAND_STATE_ERROR = 3U,
} im_sfc_nand_state_t;

应用通常只需判断对象是否为 READY。出现 ERROR 时调用对应的 deinit(),成功后再重新初始化。XIP 状态下不能执行普通读写。

恢复建议:

  • ERROR:先 deinit(),再重新初始化。deinit() 失败时不要继续使用对象。
  • XIP:普通读写擦除返回 IM_ERR_BUSY。NOR 应通过 im_sfc_nor_xip_disable() 退出。
  • stop() 或 deinit() 失败可能只完成部分清理,不要继续使用借用指针或器件快照。

3.2 设备层配置

typedef struct {
    uint8_t sample_delay_init;
    uint8_t sample_delay_work;
    hal_sfc_level_t ce_inactive_level;
    hal_sfc_level_t hold_inactive_level;
    hal_sfc_level_t wp_inactive_level;
} im_sfc_board_config_t;

typedef struct {
    im_sfc_board_config_t board;
    uint32_t clock_rate_hz;
    uint32_t param_offset;
} im_sfc_device_config_t;

im_sfc_device_config_default() 的默认值:

字段 默认值 说明
sample_delay_init 4 初始化时钟下的采样延时
sample_delay_work 4 工作时钟下的采样延时
ce_inactive_level / hold_inactive_level / wp_inactive_level HAL_SFC_LEVEL_HIGH 引脚空闲电平
clock_rate_hz 0 使用 NOR/NAND 默认工作时钟(100 MHz)
param_offset 0 使用 Kconfig 中的参数块偏移

clock_rate_hz 和 param_offset 的 0 都是“使用下层默认值”的哨兵。设备层无法表达“不切换时钟”或“参数块位于偏移 0”;需要这两种行为时,请直接使用 NOR/NAND 层。

im_sfc_device_register() 会校验采样延时和三个空闲电平,并在注册时复制配置。注册完成后没有注销或重新配置接口;需要变更配置时必须重新上电或重新构建。

3.3 Common SFC 配置

typedef struct {
    hal_sfc_device_config_t device_config;
    hal_sfc_clock_config_t clock;
    uint16_t poll_time;
    bool use_irq;
} im_sfc_config_t;

普通应用不需要手工填写该结构。使用设备层时由 BSP 配置覆盖板级相关字段;直接使用 Common SFC 时,先调用 im_sfc_config_default(),再只修改需要的字段。

默认值:

字段 默认值
device_config SDK 通用 SFC 配置,采样延时 4,CE/HOLD/WP 空闲高电平
clock hal_sfc_clock_default_get(),默认 50 MHz
poll_time 0
use_irq FreeRTOS 构建为 true,其他构建为 false

注意:

  • use_irq = true 需要平台支持 IRQ;CONFIG_IRQ_SIMPLE_DISPATCH 构建中必须设为 false。
  • im_sfc_deinit() 不关闭 SFC 时钟,时钟生命周期由调用方管理。
  • poll_time 和 device_config 属于高级配置,取值范围见 hal_sfc.h。

3.4 时钟和 XIP 配置

typedef struct {
    uint32_t hold_ns;
    uint32_t setup_ns;
    uint32_t deselect_ns;
    uint8_t sample_delay;
} im_sfc_clock_timing_t;

typedef struct {
    hal_sfc_direct_phase_config_t read_phase;
    uint8_t mode_bits;
    uint8_t preset_cdt_index;
    uint16_t exit_command;
} im_sfc_xip_config_t;
字段 说明
read_phase 连续读命令、地址宽度、dummy 和数据传输模式
mode_bits 连续读模式位,默认 0xA0
preset_cdt_index 重入 XIP 时使用的 preset CDT 槽位,不能为 0
exit_command 退出连续读的命令,默认 0xFF;需要两字节命令时设为 0xFFFF

im_sfc_clock_timing_t 中的 hold/setup/deselect 单位为 ns;sample_delay 是新时钟下使用的采样延时编码。

普通 NOR XIP 使用 im_sfc_nor_xip_enable(nor, NULL) 即可,默认命令为 0xEB、4 个 dummy、quad I/O。只有器件需要不同读命令或退出命令时,才需要自行填写 im_sfc_xip_config_t。

直接使用 Common XIP API 时,除了调用 im_sfc_xip_config_default(),还必须填写有效的 read_phase.phase.address_width 和 preset_cdt_index。NOR 包装层会自动补齐这两个字段。

3.5 NOR 配置和快照

typedef struct {
    im_sfc_config_t sfc_config;
    uint32_t clock_rate_hz;
    uint32_t param_offset;
    uint8_t sample_delay_work;
} im_sfc_nor_config_t;

im_sfc_nor_config_default() 默认使用 100 MHz 工作时钟、Kconfig 参数块偏移和采样延时 4。

配置字段:

字段 说明
sfc_config 传给内部 Common SFC 的配置
clock_rate_hz 匹配器件后的工作时钟;0 表示保持初始化时钟
param_offset NOR Burner 参数块在 Flash 中的偏移
sample_delay_work 工作时钟下的采样延时;0 使用默认值 4,超过 HAL 上限时初始化返回 IM_ERR_INVALID_PARAM

初始化成功后,可以从 im_sfc_nor_t 读取以下只读快照:

字段 说明
flash_info(im_sfc_nor_flash_info_t) 器件命令、时序、容量、页大小和擦除大小
partitions(im_sfc_nor_partitions_t) Burner 参数块中的分区表
chip_id 读到的 24 位 JEDEC ID
quad_enabled 是否使用 quad 命令
address_4byte 是否进入 4-byte 地址模式

常用 flash_info 字段:

字段 说明
chip_size Flash 总容量,单位字节
page_size 编程页大小
erase_size 擦除粒度,不一定等于 4 KiB
id 参数块声明的 JEDEC ID

NOR 没有内置器件表。im_sfc_nor_init() 从 param_offset 读取 im_sfc_nor_burner_params_t,其内容依次为 magic、version、flash_info、partitions 和 private_data。初始化要求:

  • magic 为 IM_SFC_NOR_MAGIC,version 为 IM_SFC_NOR_VERSION。
  • flash_info.id 等于实际读到的 JEDEC ID。
  • param_offset + sizeof(im_sfc_nor_burner_params_t) 不超过 16 MiB。
  • flash_info 中的读、写、擦除和 busy 轮询命令完整有效。
  • 分区数量不超过 10;分区越界、重叠和对齐由烧录工具或上层存储代码验证。

3.6 NAND 配置和快照

typedef struct {
    im_sfc_config_t sfc_config;
    uint32_t clock_rate_hz;
    uint32_t param_offset;
    uint8_t sample_delay_work;
} im_sfc_nand_config_t;

im_sfc_nand_config_default() 默认使用 100 MHz 工作时钟、Kconfig 参数块偏移和采样延时 4。

im_sfc_nand_config_t 的字段含义与 NOR 相同。clock_rate_hz = 0 保持初始化时钟,param_offset 指向 NAND Burner 参数块。

初始化成功后,可以从 im_sfc_nand_t 读取以下只读快照:

字段 说明
flash_info(im_sfc_nand_flash_info_t) 匹配到的内置器件表项
partitions(im_sfc_nand_partitions_t) Burner 参数块中的分区表,读取失败时全 0
chip_id 厂商 ID 和器件 ID 的组合
quad_enabled 是否使用 quad 读写命令
ecc_status 最近一次读操作的原始 ECC 状态

常用 flash_info 字段:

字段 说明
page_size / spare_size 每页数据区和 spare 区大小
pages_per_block 每块页数
block_count 总块数

NAND 器件参数来自内置器件表。Burner 参数块只提供分区信息;参数块读取失败或格式非法时只打印警告,初始化仍可成功,此时分区表为全 0。NAND 层不检查分区是否越界、重叠或按块对齐,使用前必须由上层验证。

im_sfc_nand_burner_params_t 由 magic、partition_num 和最多 10 个分区项组成。合法参数块要求 magic 为 IM_SFC_NAND_MAGIC,partition_num 在 0~10 之间。

直接使用 NOR/NAND 层时,clock_rate_hz = 0 表示保持初始化时钟;通过设备层时该字段为 0 表示使用 100 MHz 默认工作时钟。

3.7 分区和 ECC 类型

NOR/NAND 分区表最多包含 10 项(im_sfc_nor_partitions_t / im_sfc_nand_partitions_t),每项(im_sfc_nor_partition_t / im_sfc_nand_partition_t)包含:

char name[32];
uint32_t size;
uint32_t offset;
uint32_t mask_flags;
uint32_t manager_mode;

name 不保证以 NUL 结尾,打印或比较时按最多 32 字节处理。mask_flags 和 manager_mode 是上层元数据,SFC 和 MTD 分区构造不解释。

NAND ECC 状态快照:

typedef struct {
    uint8_t status;
    uint8_t count;
} im_sfc_nand_ecc_status_t;

中间件只返回原始、按器件定义的 ECC 状态,不判断是否可纠正,也不自动纠正数据。

3.8 常用常量

宏 值 说明
IM_SFC_DEFAULT_TIMEOUT_MS 3000 默认事务超时
IM_SFC_DEFAULT_SAMPLE_DELAY 4 默认采样延时编码
IM_SFC_CLOCK_MAX_QUAD_HZ 104 MHz quad 模式时钟上限
IM_SFC_CLOCK_MAX_STANDARD_HZ 50 MHz 标准 SPI 时钟上限
IM_SFC_XIP_DEFAULT_COMMAND 0xEB 默认 XIP 读命令
IM_SFC_XIP_DEFAULT_DUMMY_CYCLES 4 默认 XIP dummy cycles
IM_SFC_NOR_DEFAULT_CLOCK_HZ 100 MHz NOR 默认工作时钟
IM_SFC_NAND_DEFAULT_CLOCK_HZ 100 MHz NAND 默认工作时钟
IM_SFC_DEVICE_ID_DEFAULT 0 默认设备 ID

3.9 MTD/DFS 边界

  • 设备层启动时会把 NOR/NAND 挂接到内部 MTD,并可通过 im_sfc_device_mtd_get() 取得借用指针。
  • dfs_mtd 把 NOR 分区注册为 DFS block 设备,把 NAND 分区注册为 DFS MTD 设备;设备名等于 Burner 参数块中的分区名。
  • 自动挂载、手动挂载、格式化与卸载流程见 2.5 节;是否挂载由应用和 DFS 配置决定。
  • MTD 挂接时会跳过空名、越界、未按擦除块/block 对齐或与其他分区重叠的项,这些分区不会出现在 DFS 中。
  • MTD 分区表在挂接时生成,之后只读。
  • im_sfc_device_stop() 只解挂 MTD,不注销 DFS 设备,也不卸载文件系统。

3.10 公开类型索引

普通应用通常只需要本文已展开的配置、状态和快照类型。以下类型由中间件或 Burner 工具填充,应用不应改写:

类型 用途
im_sfc_dma_segment_t 描述符 DMA 的单个数据段
im_sfc_data_buffers_t Common 事务的缓冲区或 DMA 段
im_sfc_cdt_transfer_t CDT 事务请求
im_sfc_direct_transfer_t direct 事务请求
im_sfc_transfer_context_t 内部事务上下文,不要读写
im_sfc_dma_descriptor_t 不透明 DMA 描述符,不要分配或修改
im_sfc_nor_cmd_info_t / im_sfc_nor_st_info_t NOR 命令和状态寄存器命令描述
im_sfc_nor_flash_info_t NOR 器件参数
im_sfc_nor_partition_t / im_sfc_nor_partitions_t NOR 分区项和分区表
im_sfc_nor_private_data_t NOR Burner 私有参数
im_sfc_nor_burner_params_t NOR Burner 参数块
im_sfc_nand_feature_t / im_sfc_nand_ecc_field_t NAND feature 和 ECC 字段描述
im_sfc_nand_ecc_layout_t NAND ECC 状态布局
im_sfc_nand_flash_info_t NAND 内置器件表项
im_sfc_nand_partition_t / im_sfc_nand_partitions_t NAND 分区项和分区表
im_sfc_nand_burner_params_t NAND Burner 参数块

Burner 参数块、命令描述和器件描述必须保持头文件定义的二进制布局,不要重排字段或使用 #pragma pack 改变布局。NOR/NAND 的底层命令码和 CDT 槽位定义在对应头文件中,扩展驱动时再查阅。

3.11 常见错误码

错误码 常见原因
IM_ERR_INVALID_PARAM 空指针、长度为零、地址或长度越界、擦除未对齐、NAND 跨页、DMA 缓冲区不合格
IM_ERR_NOT_READY 对象未初始化、设备未启动、systick 未初始化
IM_ERR_TIMEOUT 等待事务完成或 Flash 状态超时
IM_ERR_BUSY 对象正在操作、处于 XIP,或控制器尚未释放
IM_ERR_NO_MEMORY DMA 描述符、bounce buffer 或临时缓冲区分配失败
IM_ERR_NOT_FOUND 设备未注册,或 NOR 参数块中的器件 ID 与实际 ID 不一致
IM_ERR_ALREADY_EXISTS NOR/NAND 对象已经初始化
IM_ERR_UNSUPPORTED 当前器件或构建不支持该功能
IM_ERR_ALREADY_REGISTERED 同一设备 ID 已经注册
IM_ERR_IO FIFO 搬运不完整、NAND 编程/擦除失败、数据校验失败
IM_ERR_NO_DEVICE 设备 ID 非法、NOR 参数块非法,或 NAND 器件不在内置表
IM_ERR_INVALID_STATE 对象处于 ERROR,或当前状态不允许该操作

调用方应按“非 IM_OK 即失败”处理,并保留原始错误码用于日志。硬件错误码可能由 HAL、CPM、IRQ 或缓存层返回。

3.11.1 常见问题定位

现象 优先检查
NOR 初始化返回 IM_ERR_NO_DEVICE 参数块偏移、magic、version、分区布局,以及参数块是否位于前 16 MiB
NOR 初始化返回 IM_ERR_NOT_FOUND 参数块中的 flash_info.id 与实际 JEDEC ID 是否一致
NAND 初始化返回 IM_ERR_NO_DEVICE 芯片 ID 是否在内置器件表中
读数据不一致 地址和长度是否越界、写入前是否擦除、采样延时和时钟是否正确、XIP cache 是否已失效
NAND 读返回 IM_ERR_IO NAND OIP/P_FAIL/E_FAIL 状态、ECC 状态和坏块情况
XIP 读校验失败 读命令、dummy、mode bits、地址宽度、窗口映射和 cache 属性
file_system_init() 成功但看不到 /data 是否启用自动挂载、..._NAME 是否与参数块分区名一致;NOR 使用 Flash 根分区时是否只配置了 rootfs
mount_fs() 失败 分区设备是否存在、分区是否按擦除块/block 对齐、elm/yaffs 是否与 Flash 类型匹配、卷是否已格式化、DFS_ELM_MAX_SECTOR_SIZE 是否覆盖 NOR 擦除块
DFS 场景停止设备失败或挂载异常 是否先卸载文件系统,以及是否仍有其他访问者持有 MTD/DFS 设备

4. API 参考

4.1 API 总览

im_sfc_device.h

API 用途
im_sfc_device_config_default() 填充设备层默认配置
im_sfc_device_config_get() 读取已注册设备的配置副本
im_sfc_device_register() 注册一个板级 SFC 设备
im_sfc_device_start() 启动设备并初始化 NOR/NAND、挂接 MTD
im_sfc_device_stop() 解挂 MTD 并反初始化 NOR/NAND
im_sfc_device_nor_get() 取得内部 NOR 对象的借用指针
im_sfc_device_nand_get() 取得内部 NAND 对象的借用指针
im_sfc_device_mtd_get() 取得内部 MTD 对象的借用指针

im_sfc.h

API 用途
im_sfc_config_default() 填充 Common SFC 默认配置
im_sfc_init() 初始化控制器、时钟、DMA 资源和可选 IRQ
im_sfc_clock_config() 切换总线时钟并应用 CE/采样时序
im_sfc_cdt_entry_config() 配置一个 CDT 表项
im_sfc_cdt_transfer() 执行一条 CDT 命令链
im_sfc_direct_transfer() 使用临时 phase 表执行一次事务
im_sfc_dma_buffer_alignment_is_valid() 检查缓冲区是否满足 DMA 对齐要求
im_sfc_data_path_select() 按数据长度选择 DMA 或 CPU FIFO
im_sfc_dma_bounce_resize() 扩容内部 bounce buffer
im_sfc_xip_config_default() 填充默认 XIP 配置
im_sfc_xip_enable() 进入控制器 XIP 连续读模式
im_sfc_xip_disable() 退出 XIP 模式并恢复普通事务
im_sfc_deinit() 释放 DMA/IRQ 资源并软复位控制器
im_sfc_get_state() 读取控制器状态快照

im_sfc_nor.h

API 用途
im_sfc_nor_config_default() 填充 NOR 默认配置
im_sfc_nor_init() 初始化 NOR 和内部 Common SFC
im_sfc_nor_deinit() 反初始化 NOR 和内部 Common SFC
im_sfc_nor_clock_config() 切换 NOR 工作时钟
im_sfc_nor_get_state() 读取 NOR 状态快照
im_sfc_nor_read() 从 Flash 字节地址读取数据
im_sfc_nor_write() 向 NOR 写入数据
im_sfc_nor_erase() 按擦除大小擦除 NOR
im_sfc_nor_xip_enable() 进入 NOR XIP 连续读模式
im_sfc_nor_xip_disable() 退出 NOR XIP 并恢复普通命令模式

im_sfc_nand.h

API 用途
im_sfc_nand_config_default() 填充 NAND 默认配置
im_sfc_nand_init() 初始化 NAND 和内部 Common SFC
im_sfc_nand_deinit() 反初始化 NAND 和内部 Common SFC
im_sfc_nand_clock_config() 切换 NAND 工作时钟
im_sfc_nand_get_state() 读取 NAND 状态快照
im_sfc_nand_read() 从一页的指定列读取数据
im_sfc_nand_write() 向一页的指定列写入数据
im_sfc_nand_erase() 擦除一个 block
im_sfc_nand_is_bad_block() 检查出厂坏块标记
im_sfc_nand_get_ecc_status() 读取最近一次页读锁存的 ECC 状态

4.2 通用约定

  • 返回 error_code_t 的接口以 IM_OK 表示成功,其他值表示失败。
  • timeout_ms == 0 使用 IM_SFC_DEFAULT_TIMEOUT_MS,当前为 3000 ms。
  • 所有 Flash 操作都是同步阻塞接口,只能在任务上下文调用。
  • 所有输出参数必须指向有效内存;允许为 NULL 的接口会单独说明。
  • NOR/NAND/Common 对象由调用方分配时,必须先零初始化。
  • 设备层返回的 NOR/NAND/MTD 指针是借用指针,不能释放,也不能跨 stop() / 重新 start() 使用。

4.3 设备层 API

当前只有一个设备槽位,device_id 必须为 IM_SFC_DEVICE_ID_DEFAULT;其他 ID 返回 IM_ERR_NO_DEVICE。

im_sfc_device_config_default()

error_code_t im_sfc_device_config_default(im_sfc_device_config_t *config_out);

填充设备层默认配置。config_out 不能为 NULL。返回 IM_OK 或 IM_ERR_INVALID_PARAM。

im_sfc_device_config_get()

error_code_t im_sfc_device_config_get(uint32_t device_id,
                                      im_sfc_device_config_t *config_out);

读取已注册设备的配置副本,不启动设备,也不访问硬件。config_out == NULL 或设备 ID 非法返回 IM_ERR_INVALID_PARAM / IM_ERR_NO_DEVICE;尚未注册返回 IM_ERR_NOT_FOUND。

im_sfc_device_register()

error_code_t im_sfc_device_register(
    uint32_t device_id,
    const im_sfc_device_config_t *config);

注册一个板级 SFC 设备,只复制配置,不访问 Flash。device_id 使用 IM_SFC_DEVICE_ID_DEFAULT。config == NULL 或字段非法返回 IM_ERR_INVALID_PARAM;同一 ID 已注册返回 IM_ERR_ALREADY_REGISTERED。

im_sfc_device_start()

error_code_t im_sfc_device_start(uint32_t device_id);

启动已注册设备:应用板级配置,初始化 NOR/NAND,并挂接 MTD。已启动时重复调用返回 IM_OK。未注册返回 IM_ERR_NOT_FOUND;启动失败时返回底层第一个错误,并清理本次资源,后续可以重试。

im_sfc_device_stop()

error_code_t im_sfc_device_stop(uint32_t device_id);

解挂 MTD,并反初始化 NOR/NAND。已停止时重复调用返回 IM_OK。停止后设备仍保持注册状态,可以再次启动。失败时返回清理过程中的第一个错误,并可能只完成部分清理,此时不要继续使用借用指针。该接口不会注销 DFS 设备,也不会卸载文件系统。

im_sfc_device_nor_get()

im_sfc_nor_t *im_sfc_device_nor_get(uint32_t device_id);

NOR 构建可用。设备处于 READY 时返回内部 NOR 对象,否则返回 NULL。

im_sfc_device_nand_get()

im_sfc_nand_t *im_sfc_device_nand_get(uint32_t device_id);

NAND 构建可用。设备处于 READY 时返回内部 NAND 对象,否则返回 NULL。

im_sfc_device_mtd_get()

im_mtd_t *im_sfc_device_mtd_get(uint32_t device_id);

设备处于 READY 时返回内部 MTD 对象,否则返回 NULL。MTD 对象只在当前一次启动生命周期内有效。

4.4 Common SFC API

Common SFC 接口主要用于自定义命令、DMA 和控制器级 XIP。普通 NOR/NAND 应用优先使用高层 API。Common SFC 对象不串行化并发调用;NOR/NAND 高层会串行化各自的完整 Flash 操作,直接使用内嵌 sfc 时需要外部加锁。

Common API 的状态错误约定:未初始化返回 IM_ERR_NOT_READY,对象处于 ERROR 返回 IM_ERR_INVALID_STATE,正在执行事务或处于 XIP 返回 IM_ERR_BUSY,等待硬件超时返回 IM_ERR_TIMEOUT。

im_sfc_config_default()

error_code_t im_sfc_config_default(im_sfc_config_t *config_out);

填充 Common SFC 默认配置。config_out == NULL 时返回 IM_ERR_INVALID_PARAM。

im_sfc_init()

error_code_t im_sfc_init(im_sfc_t *sfc, const im_sfc_config_t *config);

初始化控制器、时钟、DMA 资源和可选 IRQ。sfc 必须零初始化,config 的内容会被复制。成功后状态为 READY。

重复初始化前必须先成功 im_sfc_deinit()。常见错误包括 IM_ERR_INVALID_PARAM、IM_ERR_INVALID_STATE、IM_ERR_BUSY、IM_ERR_NO_MEMORY 和 IRQ/CPM 错误。CONFIG_IRQ_SIMPLE_DISPATCH 构建中 use_irq 必须为 false。

im_sfc_clock_config()

error_code_t im_sfc_clock_config(im_sfc_t *sfc,
                                 uint32_t rate_hz,
                                 const im_sfc_clock_timing_t *timing,
                                 uint32_t *actual_hz_out);

切换 SFC 总线时钟并应用 CE/采样时序。rate_hz == 0 时不修改 CPM 频率;timing == NULL 时保持当前 CE/采样编码。actual_hz_out 可为 NULL。普通 NOR/NAND 应优先使用各自的 clock_config()。

im_sfc_cdt_entry_config()

error_code_t im_sfc_cdt_entry_config(
    im_sfc_t *sfc,
    uint8_t entry_index,
    const hal_sfc_cdt_entry_config_t *config);

配置一个 CDT 表项。NOR/NAND 中间件会占用各自的 CDT 槽位,应用不要覆盖;自定义命令应使用空闲槽位并自行保证串行化。

im_sfc_cdt_transfer()

error_code_t im_sfc_cdt_transfer(im_sfc_t *sfc,
                                 const im_sfc_cdt_transfer_t *transfer,
                                 uint32_t timeout_ms);

执行一条 CDT 命令链。数据 phase 根据方向使用 tx_buffer 或 rx_buffer,也可以按 dma_segments 使用描述符 DMA。调用期间缓冲区必须由 SFC 独占。

execution.data_enabled == false 时不搬运数据,buffers 应保持全 0。

DMA 缓冲区要求:

  • 直接 DMA:缓冲区物理连续,起始地址按 cache line 对齐,长度为 cache line 的整数倍。
  • 描述符 DMA:tx_buffer / rx_buffer 为空,dma_segments 非空;1~64 个物理连续段,每段按 cache line 对齐且长度为整数倍,data_transfer.transfer_length 填 0,由中间件按段长求和。
  • 非描述符模式:dma_segments == NULL,段数为 0。
  • 普通 NOR/NAND 高层接口会自动处理不满足对齐要求的缓冲区。

im_sfc_direct_transfer()

error_code_t im_sfc_direct_transfer(im_sfc_t *sfc,
                                    const im_sfc_direct_transfer_t *transfer,
                                    uint32_t timeout_ms);

使用请求中临时提供的 phase 表执行一次事务,不依赖 CDT 中预存的命令链。缓冲区和 DMA 规则与 im_sfc_cdt_transfer() 相同。

im_sfc_dma_buffer_alignment_is_valid()

bool im_sfc_dma_buffer_alignment_is_valid(const void *buffer, uint32_t length);

检查缓冲区是否按 cache line 对齐且长度为 cache line 的整数倍。返回 true 表示可以直接交给 DMA。

im_sfc_data_path_select()

hal_sfc_operation_mode_t im_sfc_data_path_select(uint32_t length);

按长度选择数据通路:长度大于等于 1024 字节返回 DMA,否则返回 CPU FIFO。普通应用不需要调用。

im_sfc_dma_bounce_resize()

error_code_t im_sfc_dma_bounce_resize(im_sfc_t *sfc, uint32_t min_size);

扩容内部 bounce buffer。该接口主要供 NAND 层在识别器件后使用,普通应用不需要调用。

im_sfc_xip_config_default()

error_code_t im_sfc_xip_config_default(im_sfc_xip_config_t *config_out);

填充默认 XIP 配置。config_out == NULL 时返回 IM_ERR_INVALID_PARAM。直接使用 Common XIP 时,调用方还必须填写 read_phase.phase.address_width 和 preset_cdt_index。

im_sfc_xip_enable()

error_code_t im_sfc_xip_enable(im_sfc_t *sfc, const im_sfc_xip_config_t *config);

让控制器进入 XIP 连续读模式。sfc 必须处于 READY。成功后状态为 XIP,普通事务接口返回 IM_ERR_BUSY。NOR 应用建议使用 im_sfc_nor_xip_enable()。

失败时不要调用 xip_disable() 做回滚;对象通常已回到 READY 或进入 ERROR,应按 im_sfc_get_state() 清理。

im_sfc_xip_disable()

error_code_t im_sfc_xip_disable(im_sfc_t *sfc, uint32_t timeout_ms);

退出控制器 XIP 模式并回到 READY。该函数必须在非 XIP 内存中执行。失败时可能保持 XIP 或进入 ERROR;应根据 im_sfc_get_state() 的结果重试或调用 im_sfc_deinit()。

im_sfc_deinit()

error_code_t im_sfc_deinit(im_sfc_t *sfc);

释放 DMA/IRQ 资源并软复位控制器。可以从 ERROR 状态重试。若对象仍在 XIP,应先通过 NOR 接口发送 Flash XIP 退出命令;Common 层 deinit() 不会发送该退出帧。deinit() 不关闭 SFC 时钟。

im_sfc_get_state()

im_sfc_state_t im_sfc_get_state(const im_sfc_t *sfc);

返回当前状态快照;sfc == NULL 时返回 IM_SFC_STATE_UNINITIALIZED。

4.5 NOR API

NOR 读、写、擦除要求对象处于 READY。未初始化返回 IM_ERR_NOT_READY,ERROR 返回 IM_ERR_INVALID_STATE,对象忙或处于 XIP 返回 IM_ERR_BUSY。

im_sfc_nor_config_default()

error_code_t im_sfc_nor_config_default(im_sfc_nor_config_t *config_out);

填充 NOR 默认配置:100 MHz、Kconfig 参数块偏移、采样延时 4。config_out 不能为 NULL。

im_sfc_nor_init()

error_code_t im_sfc_nor_init(im_sfc_nor_t *nor,
                             const im_sfc_nor_config_t *config);

初始化 NOR 和内部 Common SFC,读取 Burner 参数块,校验 JEDEC ID,设置工作时钟,并进入 quad/4-byte 模式。成功后状态为 READY。

常见错误:参数非法返回 IM_ERR_INVALID_PARAM;参数块 magic/version 错误返回 IM_ERR_NO_DEVICE;器件 ID 不匹配返回 IM_ERR_NOT_FOUND;已初始化返回 IM_ERR_ALREADY_EXISTS。

im_sfc_nor_deinit()

error_code_t im_sfc_nor_deinit(im_sfc_nor_t *nor);

反初始化 NOR 和内部 Common SFC。可以从 XIP 状态强制退出。Flash 器件本身保持当前模式,不会自动恢复到标准 SPI 模式。

im_sfc_nor_clock_config()

error_code_t im_sfc_nor_clock_config(im_sfc_nor_t *nor,
                                     uint32_t clock_rate_hz,
                                     uint8_t sample_delay,
                                     uint32_t *actual_hz_out);

切换 NOR 工作时钟。频率会按当前器件配置截断到允许上限,actual_hz_out 返回实际生效频率,可为 NULL。clock_rate_hz == 0 表示保持当前频率。

im_sfc_nor_get_state()

im_sfc_nor_state_t im_sfc_nor_get_state(const im_sfc_nor_t *nor);

返回 NOR 状态快照;nor == NULL 时返回 IM_SFC_NOR_STATE_UNINITIALIZED。

im_sfc_nor_read()

error_code_t im_sfc_nor_read(im_sfc_nor_t *nor,
                             uint32_t address,
                             uint32_t length,
                             uint8_t *buffer,
                             uint32_t timeout_ms);

从 Flash 字节地址读取数据。address + length 不能超过 flash_info.chip_size。接口根据当前模式自动选择 quad 或标准读命令,并自动处理 DMA 对齐。

length 必须非零,buffer 必须有效。

im_sfc_nor_write()

error_code_t im_sfc_nor_write(im_sfc_nor_t *nor,
                              uint32_t address,
                              uint32_t length,
                              const uint8_t *buffer,
                              uint32_t timeout_ms);

向 NOR 写入数据。单次页编程不会跨页,但一次调用可以跨多个页。写入前必须擦除目标范围。整次调用不是原子操作,中途失败时前面的页可能已经写入。

length 必须非零,buffer 必须有效;timeout_ms 约束每一页的编程等待。

im_sfc_nor_erase()

error_code_t im_sfc_nor_erase(im_sfc_nor_t *nor,
                              uint32_t address,
                              uint32_t length,
                              uint32_t timeout_ms);

按 flash_info.erase_size 擦除 NOR。address 和 length 都必须按擦除大小对齐。timeout_ms 约束每个擦除块,多块擦除可能等待多个超时周期。中途失败时不回滚已擦除的块。

length 必须非零且为擦除大小的整数倍;不支持通过该接口执行整片擦除。

im_sfc_nor_xip_enable()

error_code_t im_sfc_nor_xip_enable(im_sfc_nor_t *nor,
                                   const im_sfc_xip_config_t *config);

进入 NOR XIP 连续读模式。config == NULL 时固定使用 0xEB、4 个 dummy、quad I/O,不检查 quad_enabled;非 quad 或特殊器件必须传入显式配置。

成功只说明控制器侧已进入 XIP。调用方必须读取 XIP 窗口并校验数据,失败时立即退出 XIP。

enable 失败时 NOR 对象不会停留在 XIP,不要用 disable() 回滚;应先按 im_sfc_nor_get_state() 清理,再重新初始化或重新上电。

im_sfc_nor_xip_disable()

error_code_t im_sfc_nor_xip_disable(im_sfc_nor_t *nor, uint32_t timeout_ms);

发送退出帧并恢复普通命令模式。必须从非 XIP 内存调用,且不能再从 XIP 窗口取指或访问数据。返回失败时按 im_sfc_nor_get_state() 处理:仍在 XIP 可以重试,进入 ERROR 后调用 im_sfc_nor_deinit()。

4.6 NAND API

NAND 读、写、擦除要求对象处于 READY。未初始化返回 IM_ERR_NOT_READY,ERROR 返回 IM_ERR_INVALID_STATE,对象忙返回 IM_ERR_BUSY。

im_sfc_nand_config_default()

error_code_t im_sfc_nand_config_default(im_sfc_nand_config_t *config_out);

填充 NAND 默认配置:100 MHz、Kconfig 参数块偏移、采样延时 4。config_out 不能为 NULL。

im_sfc_nand_init()

error_code_t im_sfc_nand_init(im_sfc_nand_t *nand,
                              const im_sfc_nand_config_t *config);

初始化 NAND 和内部 Common SFC:复位、读取 ID、匹配内置器件表、配置时钟、QE/ECC_EN,并尝试读取分区参数块。成功后状态为 READY。

器件不在内置表中时返回 IM_ERR_NO_DEVICE;参数非法返回 IM_ERR_INVALID_PARAM;已初始化返回 IM_ERR_ALREADY_EXISTS。当前只支持单 die 器件。

im_sfc_nand_deinit()

error_code_t im_sfc_nand_deinit(im_sfc_nand_t *nand);

反初始化 NAND 和内部 Common SFC。可以从 ERROR 状态调用。失败时按返回值重试,不要继续使用对象。

im_sfc_nand_clock_config()

error_code_t im_sfc_nand_clock_config(im_sfc_nand_t *nand,
                                      uint32_t clock_rate_hz,
                                      uint8_t sample_delay,
                                      uint32_t *actual_hz_out);

切换 NAND 工作时钟。频率会按当前器件配置截断到允许上限,actual_hz_out 可为 NULL。clock_rate_hz == 0 表示保持当前频率。

im_sfc_nand_get_state()

im_sfc_nand_state_t im_sfc_nand_get_state(const im_sfc_nand_t *nand);

返回 NAND 状态快照;nand == NULL 时返回 IM_SFC_NAND_STATE_UNINITIALIZED。

im_sfc_nand_read()

error_code_t im_sfc_nand_read(im_sfc_nand_t *nand,
                              uint32_t block,
                              uint32_t page,
                              uint32_t column,
                              uint8_t *buffer,
                              uint32_t length,
                              uint32_t timeout_ms);

从一页的指定列读取数据。必须满足:

  • block < flash_info.block_count
  • page < flash_info.pages_per_block
  • column + length <= flash_info.page_size + flash_info.spare_size

一次调用不能跨页。数据读取完成后,中间件会尝试锁存本次原始 ECC 状态;不自动纠正数据。

length 必须非零,buffer 必须有效;timeout_ms 只约束本次页读主流程,后续 ECC 状态读取使用默认超时。

im_sfc_nand_write()

error_code_t im_sfc_nand_write(im_sfc_nand_t *nand,
                               uint32_t block,
                               uint32_t page,
                               uint32_t column,
                               const uint8_t *buffer,
                               uint32_t length,
                               uint32_t timeout_ms);

向一页的指定列写入数据,约束与 im_sfc_nand_read() 相同。接口不执行 read-modify-write;写部分页时,调用方必须自行保留未写区域,或确保目标页已经按预期擦除。

length 必须非零,buffer 必须有效;timeout_ms 约束本次页编程主流程。

im_sfc_nand_erase()

error_code_t im_sfc_nand_erase(im_sfc_nand_t *nand,
                               uint32_t block,
                               uint32_t timeout_ms);

擦除一个 block。擦除会破坏出厂坏块标记;需要保留坏块信息时,应在擦除前调用 im_sfc_nand_is_bad_block() 并自行记录。

block 必须小于 flash_info.block_count;timeout_ms 约束擦除主流程,后续状态确认使用默认超时。

im_sfc_nand_is_bad_block()

error_code_t im_sfc_nand_is_bad_block(im_sfc_nand_t *nand,
                                      uint32_t block,
                                      bool *bad_out);

检查出厂坏块标记,结果写入 bad_out。block 必须小于 flash_info.block_count。中间件检查 page 0 的第一个 spare 字节,读取失败时回退到 page 1;任何非 0xFF 值都视为坏块。打开 CONFIG_MIDDLEWARE_SFC_NAND_SKIP_BADBLOCK 后不访问硬件,始终报告为好块。

im_sfc_nand_get_ecc_status()

error_code_t im_sfc_nand_get_ecc_status(im_sfc_nand_t *nand,
                                        im_sfc_nand_ecc_status_t *status_out);

返回最近一次 NAND 读操作锁存的原始 ECC 状态,不访问 Flash。器件不支持 ECC 状态寄存器时返回 IM_ERR_UNSUPPORTED。状态字段的语义由具体器件定义。


5. 典型完整示例

文件系统挂载和文件访问见 2.5 节;本节示例均为裸 Flash 访问。

5.1 NOR 读写擦除

以下示例只示意调用顺序。DEMO_FLASH_ADDR 必须是产品预留区域,不能覆盖 SPL、参数块、固件或文件系统。

#include <im_sfc_nor.h>
#include <string.h>

#define DEMO_FLASH_ADDR (0x100000U)
#define DEMO_LENGTH     (256U)

static im_sfc_nor_t s_nor;

static error_code_t nor_demo(void)
{
    im_sfc_nor_config_t config;
    uint8_t pattern[DEMO_LENGTH];
    uint8_t verify[DEMO_LENGTH];
    uint32_t erase_size;
    uint32_t address = DEMO_FLASH_ADDR;
    error_code_t ret;

    memset(&s_nor, 0, sizeof(s_nor));

    ret = im_sfc_nor_config_default(&config);
    if (ret != IM_OK)
        return ret;

    ret = im_sfc_nor_init(&s_nor, &config);
    if (ret != IM_OK)
        return ret;

    erase_size = s_nor.flash_info.erase_size;
    if ((erase_size == 0U) || (DEMO_LENGTH > erase_size)) {
        ret = IM_ERR_INVALID_PARAM;
        goto out;
    }
    address -= address % erase_size;

    memset(pattern, 0x5A, sizeof(pattern));

    ret = im_sfc_nor_erase(&s_nor, address, erase_size, 0U);
    if (ret != IM_OK)
        goto out;

    ret = im_sfc_nor_write(&s_nor, address, sizeof(pattern), pattern, 0U);
    if (ret != IM_OK)
        goto out;

    memset(verify, 0, sizeof(verify));
    ret = im_sfc_nor_read(&s_nor, address, sizeof(verify), verify, 0U);
    if ((ret == IM_OK) && (memcmp(pattern, verify, sizeof(pattern)) != 0))
        ret = IM_ERR_IO;

out:
    (void)im_sfc_nor_deinit(&s_nor);
    return ret;
}

5.2 NAND 页读写

NAND 示例只使用一个已擦除的安全测试 block。实际产品必须结合坏块管理和文件系统分区策略。

#include <im_sfc_nand.h>
#include <string.h>

#define DEMO_NAND_BLOCK (16U)
#define DEMO_NAND_PAGE  (0U)
#define DEMO_NAND_LEN   (64U)

static im_sfc_nand_t s_nand;

static error_code_t nand_demo(void)
{
    im_sfc_nand_config_t config;
    im_sfc_nand_ecc_status_t ecc;
    uint8_t pattern[DEMO_NAND_LEN];
    uint8_t verify[DEMO_NAND_LEN];
    bool bad = false;
    error_code_t ret;

    memset(&s_nand, 0, sizeof(s_nand));

    ret = im_sfc_nand_config_default(&config);
    if (ret != IM_OK)
        return ret;

    ret = im_sfc_nand_init(&s_nand, &config);
    if (ret != IM_OK)
        return ret;

    if (DEMO_NAND_BLOCK >= s_nand.flash_info.block_count) {
        ret = IM_ERR_INVALID_PARAM;
        goto out;
    }

    ret = im_sfc_nand_is_bad_block(&s_nand, DEMO_NAND_BLOCK, &bad);
    if (ret != IM_OK)
        goto out;
    if (bad) {
        ret = IM_ERR_UNSUPPORTED;
        goto out;
    }

    memset(pattern, 0x3C, sizeof(pattern));

    ret = im_sfc_nand_erase(&s_nand, DEMO_NAND_BLOCK, 0U);
    if (ret != IM_OK)
        goto out;

    ret = im_sfc_nand_write(&s_nand, DEMO_NAND_BLOCK, DEMO_NAND_PAGE,
                            0U, pattern, sizeof(pattern), 0U);
    if (ret != IM_OK)
        goto out;

    memset(verify, 0, sizeof(verify));
    ret = im_sfc_nand_read(&s_nand, DEMO_NAND_BLOCK, DEMO_NAND_PAGE,
                           0U, verify, sizeof(verify), 0U);
    if (ret != IM_OK)
        goto out;

    if (memcmp(pattern, verify, sizeof(pattern)) != 0) {
        ret = IM_ERR_IO;
        goto out;
    }

    ret = im_sfc_nand_get_ecc_status(&s_nand, &ecc);
    if (ret == IM_ERR_UNSUPPORTED)
        ret = IM_OK;

out:
    (void)im_sfc_nand_deinit(&s_nand);
    return ret;
}

5.3 NOR XIP 读取

假设 s_nor 已通过 im_sfc_nor_init() 初始化。示例使用 0x16000000 作为窗口基址;产品必须使用自己的地址映射,并确认 cache 属性。

推荐执行顺序:

  1. 停止所有 Flash 访问、DMA 和中断处理。
  2. 确保后续代码不从待使用的 XIP 窗口取指。
  3. 用普通读接口取得参考数据。
  4. 调用 im_sfc_nor_xip_enable()。
  5. 失效 XIP 窗口对应的 D-cache,读取窗口并校验数据。
  6. 从非 XIP 内存调用 im_sfc_nor_xip_disable(),再用普通读接口复读校验。
#include <im_cache.h>
#include <im_sfc_nor.h>
#include <string.h>

#define XIP_WINDOW_BASE  (0x16000000UL)
#define XIP_FLASH_OFFSET (0x400000UL)
#define XIP_TEST_LENGTH  (64U)

static error_code_t xip_demo(void)
{
    const volatile uint8_t *window;
    uint8_t expected[XIP_TEST_LENGTH];
    uint8_t observed[XIP_TEST_LENGTH];
    error_code_t ret;
    error_code_t verify_ret;

    window = (const volatile uint8_t *)(XIP_WINDOW_BASE + XIP_FLASH_OFFSET);

    ret = im_sfc_nor_read(&s_nor, XIP_FLASH_OFFSET,
                          sizeof(expected), expected, 0U);
    if (ret != IM_OK)
        return ret;

    ret = im_sfc_nor_xip_enable(&s_nor, NULL);
    if (ret != IM_OK)
        return ret;

    sys_cache_data_invalidate_by_addr((const void *)(uintptr_t)window,
                                     sizeof(observed));
    memcpy(observed, (const void *)(uintptr_t)window, sizeof(observed));
    verify_ret = memcmp(expected, observed, sizeof(observed)) == 0 ?
                 IM_OK : IM_ERR_IO;

    ret = im_sfc_nor_xip_disable(&s_nor, 0U);
    if (ret != IM_OK)
        return ret;

    return verify_ret;
}

XIP 期间不能执行普通 NOR 读写擦除,也不能从正在禁用的 XIP 窗口取指。


6. 使用约束和检查清单

6.1 关键约束

  • NOR 和 NAND 不能在同一构建中同时启用。
  • 所有 Flash 操作只能在任务上下文调用,不能从 ISR 调用。
  • NOR 擦除必须按 flash_info.erase_size 对齐;NAND 以整个 block 擦除。
  • NOR 写入前必须先擦除。多页写入和多块擦除都不是原子操作。
  • NAND 一次读或写不能跨页;部分页写入不做 read-modify-write。
  • NAND 擦除会破坏出厂坏块标记,需要时先读取并保存坏块信息。
  • NOR 参数块必须位于前 16 MiB,且 magic、version、命令表、时序、容量和器件 ID 必须完整有效。
  • SFC 不检查分区越界、重叠和对齐;MTD 会跳过明显非法的项,应用仍需校验完整分区布局。
  • NOR 文件系统使用 elm,NAND 文件系统使用 yaffs;DFS 设备名必须与参数块分区名一致。
  • dfs_mkfs() 和自动格式化都会清除分区内容;量产流程不要依赖自动格式化。
  • XIP 只适用于 NOR。进入后必须读回校验,退出时必须在非 XIP 内存执行。
  • DFS 挂载期间不要停止设备。停止前先卸载文件系统并确认没有其他访问者。
  • 不要长期缓存设备层返回的 NOR/NAND/MTD 指针。

6.2 首次集成检查

  • 已启用 CONFIG_DRIVER_SFC、CONFIG_OS_API,并选择 CONFIG_MIDDLEWARE_SFC 及 NOR/NAND 之一。
  • BSP 已提供合法的 sample_delay_*、CE/HOLD/WP 空闲电平和时钟配置。
  • board_sfc_register_devices() 调用 im_sfc_device_register(),并接受 IM_ERR_ALREADY_REGISTERED。
  • NOR Burner 参数块位于前 16 MiB,且与烧录工具、分区布局一致。
  • NAND 器件 ID 在内置表中;需要分区时参数块格式正确。
  • DFS 场景已确认 file_system_init()、DFS 设备注册和文件系统挂载成功;无 DFS 场景已自行调用 im_sfc_device_start()。
  • DFS 后端、根目录模式和分区名正确;NOR 使用 elm,NAND 使用 yaffs。
  • 首次挂载前已格式化分区,或明确启用了自动格式化并接受数据被清除。
  • 已检查 im_sfc_device_nor_get() / nand_get() / mtd_get() 的返回值和生命周期。
  • 读、写、擦除只在预留测试区域进行。
  • NOR 擦写后已回读校验;NAND 已检查坏块和 ECC 状态。
  • 使用 XIP 时已确认窗口地址、cache 属性、退出命令和回读校验。
  • 关机或重新初始化前已关闭文件、卸载文件系统,并按层级执行 stop()/deinit()。