API REFERENCE / SFC / XIP USAGE

SFC XIP使用

本文说明 G32S10 平台上 SFC NOR Flash 的 XIP(eXecute In Place,原地执行)机制、SDK 接口分层、进入与退出流程、只读数据访问、代码原地执行,以及退出 XIP 后执行 Flash 擦写的方法。

CONCEPT

XIP 介绍

XIP 是指 CPU 不把 Flash 内容搬运到 RAM,而是通过 SFC 控制器的 XIP 地址窗口直接读取 NOR Flash,必要时直接从 Flash 取指执行。

XIP 窗口地址

G32S10 的 XIP 窗口基址为:

#define XIP_IOBASE 0x16000000UL

窗口地址与 Flash 偏移的对应关系为:

XIP 窗口地址 = XIP_IOBASE + Flash 字节偏移

例如,Flash 偏移 0x200000 对应的窗口地址为:

0x16000000 + 0x00200000 = 0x16200000

相比普通 im_sfc_nor_read(),XIP 访问具有以下特点:

  • 访问由 CPU 的 AHB 总线直接发起,不需要 CPU 逐条搬运数据。

  • 适合频繁读取的代码、常量表和大块只读数据。

  • 进入 XIP 后,普通 NOR 读、写、擦除接口都会返回 IM_ERR_BUSY。

  • XIP 只能读,不能通过 XIP 窗口写或擦除 Flash。

LAYERS

SDK 中的三个 XIP 层次

当前 SDK 中存在三个不同层次的 XIP 配置与接口,使用时应明确区分。

层次配置或接口作用
SPL 启动层CONFIG_XIP_ENABLE决定 SPL 的链接地址和启动头 XIP 标志,属于启动镜像构建配置。
运行期中间件im_sfc_nor_xip_enable() / im_sfc_nor_xip_disable()应用运行后控制 SFC 控制器进入或退出 XIP。
底层 HALhal_sfc_xip_mode_config() 等仅供驱动或调试使用,普通应用不建议直接调用。
注意:运行期 XIP 要求应用代码运行在 SRAM/PSRAM 中,但不要求应用侧打开 CONFIG_XIP_ENABLE。不要把 SPL 的 XIP 启动配置与运行期的 im_sfc_nor_xip_*() 接口混为一谈。

READ PHASE

当前代码支持的 XIP 读相位

当前代码支持以下两种 XIP 读相位配置:

参数Quad I/ODual I/O
连续读命令0xEB0xBB
Dummy cycles40
线型HAL_SFC_TRANSFER_QUAD_IO_SPIHAL_SFC_TRANSFER_DUAL_IO_SPI
Mode bits0xA00xA0
地址字段3 字节 Flash 地址 + 1 字节 mode byte同左
AHB 访问默认 incr8;可按需使用 single同左
注意:以上参数只对当前器件 ZB25VQ128DSJG 可以直接使用。更换 Flash 后,必须根据其数据手册或 SFDP 重新确认命令、dummy、mode bits、线型和地址宽度。

WORKFLOW

推荐操作顺序

  1. 初始化 SFC/NOR。

  2. 按 Flash 参数配置 XIP 读相位。

  3. 调用 im_sfc_nor_xip_enable() 进入 XIP。

  4. 通过 XIP 窗口读取数据或执行代码。

  5. 停止所有 XIP 窗口访问。

  6. 调用 im_sfc_nor_xip_disable() 退出 XIP,并确认 NOR 状态恢复为 READY。

API

XIP 相关 API

API 名称声明文件作用
im_sfc_nor_xip_enable()code/middleware/sfc/include/im_sfc_nor.h通过 NOR 中间件进入 XIP,成功后 NOR 状态变为 XIP。
im_sfc_nor_xip_disable()code/middleware/sfc/include/im_sfc_nor.h退出 XIP,成功后 NOR 状态恢复为 READY。
im_sfc_nor_get_state()code/middleware/sfc/include/im_sfc_nor.h查询 NOR 当前运行状态,包括 READY、BUSY、XIP 和 ERROR。
im_sfc_xip_config_default()code/middleware/sfc/include/im_sfc.h填充默认 XIP 配置,默认使用 0xEB Quad I/O 读相位。
im_sfc_xip_enable()code/middleware/sfc/include/im_sfc.h在 SFC 中间件层配置并进入 XIP。
im_sfc_xip_disable()code/middleware/sfc/include/im_sfc.h在 SFC 中间件层退出 XIP,恢复普通命令模式。
hal_sfc_xip_mode_config()code/include/drivers/g32s10/sfc/hal_sfc.hHAL 层设置 mode bits,并开启或关闭 XIP_EN。
hal_sfc_xip_cpu_burst_config()code/include/drivers/g32s10/sfc/hal_sfc.h切换 XIP 使用期间和普通模式下的 CPU AHB burst 策略。
hal_sfc_is_xip_enabled()code/include/drivers/g32s10/sfc/hal_sfc.h查询控制器 XIP_EN 的实际状态。

表中路径为 API 的声明文件;实现分别位于 code/middleware/sfc/src/im_sfc_nor.c、code/middleware/sfc/src/im_sfc.c 和 code/drivers/g32s10/sfc/hal_sfc.c。

普通应用优先使用 im_sfc_nor_xip_*() 接口;im_sfc_xip_*() 和 hal_sfc_*() 主要供中间件、驱动或调试使用。

ENABLE

如何开启 XIP

前置条件

  1. 板卡使用 SFC NOR Flash,且 Flash 支持所选的连续读命令。

  2. SPL 能从 Flash 正常启动,应用代码当前运行在 SRAM/PSRAM,而不是正在被操作的 XIP 区域。

  3. SFC 驱动、SFC 中间件和 NOR 中间件已经启用。

  4. 板级 SFC 设备已经注册,且 im_sfc_device_start() 初始化成功。

  5. Flash 0x6800 偏移处的 Burner 参数块有效,参数块中的 JEDEC ID、命令、页大小、擦除大小与实物一致。

  6. 进入 XIP 前没有任何任务、DMA 或中断正在访问同一片 Flash。

  7. 准备 XIP 访问的 Flash 数据或代码。

关键配置项如下:

CONFIG_DRIVER_SFC=y
CONFIG_DRIVER_CPM=y
CONFIG_MIDDLEWARE_SFC=y
CONFIG_MIDDLEWARE_SFC_NOR=y
CONFIG_MIDDLEWARE_SFC_NOR_PARAM_OFFSET=0x6800

板级注册代码位于:

code/bsp/g32s10/g32s103_sk_slt/board_sfc.c
code/bsp/g32s10/g32s107_sk_full_8910/board_sfc.c
code/bsp/g32s10/g32s107_sk_slt/board_sfc.c

初始化顺序为:

board_sfc_register_devices();
im_sfc_device_start(IM_SFC_DEVICE_ID_DEFAULT);
im_sfc_nor_t *nor = im_sfc_device_nor_get(IM_SFC_DEVICE_ID_DEFAULT);

进入 XIP 的伪代码

应用代码应优先使用 NOR 中间件接口,不要直接操作 HAL 寄存器。以下伪代码描述进入 XIP 的完整流程,接口名来自 SDK,但不代表可以直接编译的完整程序。

/* 伪代码:在应用代码中进入 XIP */
function enter_xip():
    初始化 SFC/NOR
    if 初始化失败:
        return 错误码

    nor = 获取默认 NOR 设备对象
    if nor 无效:
        return IM_ERR_NOT_READY

    result = im_sfc_xip_config_default(&xip_config)
    if result != IM_OK:
        return result

    /* 以下参数适用于当前器件 ZB25VQ128DSJG;更换 Flash 后按数据手册修改 */
    xip_config.连续读命令       = 0xEB
    xip_config.dummy_cycles = 4
    xip_config.线型           = Quad I/O
    xip_config.mode_bits    = 0xA0

    /* 由应用保证:其它任务、DMA 和中断已停止访问 Flash */
    暂停其它 Flash 访问

    result = im_sfc_nor_xip_enable(nor, xip_config)
    if result != IM_OK:
        恢复普通 Flash 访问
        return result

    /* 控制器已进入 XIP,后续只通过 XIP 窗口访问 Flash */
    return IM_OK
  • im_sfc_nor_xip_enable() 返回 IM_OK 表示控制器侧已经进入 XIP;窗口访问是否正常仍取决于读相位、地址映射和 Cache 配置。

  • address_width 和 preset_cdt_index 由 im_sfc_nor_xip_enable() 自动补齐,应用不需要手工设置。

  • 进入 XIP 后,直接通过 XIP 窗口读取已烧录的数据或执行已链接到该窗口的代码。

  • 如果窗口访问异常,应马上调用 im_sfc_nor_xip_disable() 退出,再检查读相位、地址和 Cache,而不是继续访问。

  • 不显式指定配置时,接口使用默认 0xEB + 4 dummy + Quad I/O 配置,但不会检查当前 Flash 是否已经打开 Quad 模式。

USE

进入 XIP 后如何使用

当前支持只读数据访问和在 XIP 窗口内执行代码两种方式。

只读数据访问

适用于数据只读、不需要修改 Flash,也不需要从 XIP 窗口取指的场景,例如读取常量表、字库、图片或算法参数。

/* 伪代码:XIP 已进入,通过窗口读取一段只读数据 */
error_code_t xip_read_blob(uint32_t flash_offset,
                           uint8_t *dst,
                           uint32_t length)
{
    const volatile uint32_t *window;
    uint32_t done;
    uint32_t word;

    if (im_sfc_nor_get_state(s_nor) != IM_SFC_NOR_STATE_XIP) {
        return IM_ERR_INVALID_STATE;
    }

    if ((dst == NULL) ||
        (length == 0U) ||
        ((flash_offset % 4U) != 0U) ||
        ((length % 4U) != 0U)) {
        return IM_ERR_INVALID_PARAM;
    }

    window = (const volatile uint32_t *)(XIP_IOBASE + flash_offset);
    im_cache_data_invalidate_by_addr((const void *)window, length);

    for (done = 0U; done < length; done += 4U) {
        word = window[done / 4U];
        dst[done + 0U] = (uint8_t)(word >> 0U);
        dst[done + 1U] = (uint8_t)(word >> 8U);
        dst[done + 2U] = (uint8_t)(word >> 16U);
        dst[done + 3U] = (uint8_t)(word >> 24U);
    }

    return IM_OK;
}
  • CPU 直接执行 load 指令读取 XIP 窗口,不需要调用 im_sfc_nor_read()。

  • 读取前应失效对应 D-cache,避免读到进入 XIP 前缓存的旧数据。

  • 示例要求 flash_offset 和 length 都按 4 字节对齐;非对齐长度应先补齐边界,或改用带完整边界检查的字节读取实现。

  • 建议按块读取;XIP 窗口访问本身没有硬件超时,应用应根据业务规模控制单次读取长度。

  • 如果数据需要更新,必须先退出 XIP,在普通模式下擦除、写入并重新进入 XIP,不能直接写 XIP 窗口。

  • 该方向可以长时间驻留,但驻留期间不能同时进行普通 Flash 读写、文件系统操作或固件更新。

代码原地执行

适用于目标函数已经烧写到 NOR,并且链接地址、XIP 窗口地址和函数入口一致;执行期间不需要更新 Flash 的场景。

/* 伪代码:XIP 已进入,调用者运行在 SRAM/PSRAM */
typedef int32_t (*xip_function_t)(uint32_t argument);

error_code_t xip_call_function(uint32_t code_flash_offset,
                               uint32_t code_size,
                               uint32_t entry_offset,
                               int32_t *result_out)
{
    uint8_t *window;
    xip_function_t entry;

    if (im_sfc_nor_get_state(s_nor) != IM_SFC_NOR_STATE_XIP) {
        return IM_ERR_INVALID_STATE;
    }

    if ((result_out == NULL) ||
        (code_size == 0U) ||
        (entry_offset >= code_size)) {
        return IM_ERR_INVALID_PARAM;
    }

    window = (uint8_t *)(XIP_IOBASE + code_flash_offset);

    /* 清除可能残留的旧 Cache,并同步指令流 */
    im_cache_data_invalidate_by_addr(window, code_size);
    im_cache_inst_invalidate_by_addr(window, code_size);
    v1_cpu_fence_i();

    /* 通过普通 C 函数调用进入 XIP 取指;参数、栈和返回地址位于 RAM */
    entry = (xip_function_t)(window + entry_offset);
    *result_out = entry(XIP_DEMO_ARGUMENT);

    return IM_OK;
}
  • XIP 代码必须是只读代码,不能在执行期间擦写自身。

  • 调用者、栈、返回地址、异常向量和可能发生的中断处理程序都必须位于 SRAM/PSRAM,不能位于 XIP 窗口。

  • Flash 内容在 Cache 填充后被重新擦写时,I-cache 可能继续提供旧指令,D-cache 可能提供代码中的旧常量。重新进入 XIP 不会自动清除这些旧副本。

  • 首次冷启动且目标 Flash 区域从未被缓存或改写时,通常不需要维护 Cache;只要发生过擦除、烧写,或无法确认启动阶段是否留下旧 Cache,就应在首次执行目标代码前维护一次。

  • im_cache_inst_invalidate_by_addr() 内部已经执行 fence.i;伪代码显式调用 v1_cpu_fence_i() 是为了强调顺序。

  • entry_offset 是函数入口相对 code_flash_offset 的偏移;函数、只读数据和重定位结果都必须按 XIP 窗口地址链接。

  • XIP 函数不能调用 im_sfc_nor_read()、im_sfc_nor_write()、im_sfc_nor_erase()、DFS/Shell Flash 命令等普通 Flash 接口。

  • XIP 函数必须返回 RAM 中的调用者后才能执行退出流程;不要在 XIP 函数内部关闭 XIP。

  • 部署前应确认链接脚本、ABI、MPU/PMA、中断返回和异常路径满足从 XIP 执行的要求。

共同约束:XIP 期间不要调用 im_sfc_nor_read()、im_sfc_nor_write()、im_sfc_nor_erase(),这些接口会返回 IM_ERR_BUSY。不要通过 Shell 执行 sfc_nor_* 原始读写命令,也不要让文件系统或日志任务访问同一片 Flash。XIP 窗口访问不受 SFC 中间件互斥锁保护,使用期间必须确保没有其它 Flash 访问者。

DISABLE

如何退出 XIP

退出 XIP 前必须停止所有 XIP 窗口访问。调用退出流程的应用代码、调用链、栈和中断处理程序必须位于 SRAM/PSRAM,退出函数本身不能从正在关闭的 XIP 窗口取指。

/* 伪代码:调用者、栈和中断处理程序均位于 SRAM/PSRAM */
error_code_t app_xip_exit(uint32_t timeout_ms)
{
    error_code_t ret;

    if (im_sfc_nor_get_state(s_nor) != IM_SFC_NOR_STATE_XIP) {
        return IM_ERR_INVALID_STATE;
    }

    /* 调用前应已停止所有任务、DMA 和中断对 XIP 窗口的访问 */
    ret = im_sfc_nor_xip_disable(s_nor, timeout_ms);
    if (ret != IM_OK) {
        return ret;
    }

    if (im_sfc_nor_get_state(s_nor) != IM_SFC_NOR_STATE_READY) {
        return IM_ERR_INVALID_STATE;
    }

    return IM_OK;
}

退出失败后,应先调用 im_sfc_nor_get_state() 查看状态。如果状态仍是 XIP,可以在确认没有 XIP 访问后重试退出;如果状态已经变成 ERROR,调用 im_sfc_nor_deinit() 后重新初始化,必要时复位或重新上电。不要忽略退出失败继续写 Flash,否则 Flash 可能仍处于连续读模式,后续普通命令读写都会异常。

WRITE BACK

退出 XIP 后重新写入 Flash

退出 XIP 并确认 NOR 状态为 READY 后,应用可以重新使用普通读、写、擦除接口访问 Flash。

/* 伪代码:退出 XIP 后向 Flash 写入业务数据 */
error_code_t flash_write_after_xip(uint32_t flash_offset,
                                   uint32_t length,
                                   const uint8_t *data)
{
    error_code_t ret;
    uint32_t erase_size;

    if (im_sfc_nor_get_state(s_nor) != IM_SFC_NOR_STATE_READY) {
        return IM_ERR_INVALID_STATE;
    }

    erase_size = s_nor->flash_info.erase_size;
    if ((erase_size == 0U) ||
        (data == NULL) ||
        (length == 0U) ||
        (length > erase_size) ||
        ((flash_offset % erase_size) != 0U)) {
        return IM_ERR_INVALID_PARAM;
    }

    /* 1. 擦除目标区域 */
    ret = im_sfc_nor_erase(s_nor, flash_offset, erase_size, 0U);
    if (ret != IM_OK) {
        return ret;
    }

    /* 2. 使用普通命令模式写入数据 */
    ret = im_sfc_nor_write(s_nor, flash_offset, length, data, 0U);
    if (ret != IM_OK) {
        return ret;
    }

    return IM_OK;
}

如果业务需要读取回数据,写入成功后调用 im_sfc_nor_read() 读取同一区域;读取前按需维护 D-cache。

写回后再次进入 XIP

  1. 对目标地址范围执行 D-cache clean/invalidate。

  2. 如果目标区域包含代码,再对同一范围执行 I-cache invalidate。

  3. 重新调用 im_sfc_nor_xip_enable()。

  4. 通过 XIP 窗口访问更新后的数据或代码。

中间件在成功退出一次 XIP 后会记录 re-entry 状态,下一次进入时会使用预置 CDT 槽重新建立连续读模式。

使用 Shell 写 Flash

当前 Shell 提供以下 NOR 原始访问命令:

sfc_nor_cpu_read  <address> <length> <1|4>
sfc_nor_cpu_write <address> <length> <1|4> [byte ...]
sfc_nor_dma_read  <address> <length> <1|4>
sfc_nor_dma_write <address> <length> <1|4> [byte ...]
  • 这些命令只能在 READY 状态执行,XIP 状态下会被拒绝。

  • Shell 当前没有 NOR 擦除命令,sfc_nor_*_write 只是页编程;写入前必须由应用、文件系统或烧录工具完成擦除。

  • 原始命令会保护已经挂载的文件系统分区,分区内数据不应绕过文件系统直接写入。

  • 批量操作时应使用项目规划的保留区域,并避开 SPL、参数区和已挂载的文件系统分区。

CAUTIONS

使用注意事项

操作安全

  • 退出 XIP 的函数、其调用链、栈和中断处理代码都必须在 SRAM/PSRAM 中执行,不能位于正在关闭的 XIP 窗口。

  • 进入或退出 XIP 前停止所有 Flash 访问者,包括文件系统、日志任务、DMA 和中断。

  • XIP 窗口访问绕过中间件锁,应用必须自行保证互斥。

  • 不要依赖复位来退出 XIP。Flash 可能在复位后仍保持连续读状态,必须执行正确的退出帧。

  • 擦写过程中不要断电,否则目标块可能处于不稳定状态。

Flash 与板级参数

  • XIP 仅适用于 NOR Flash,不适用于 NAND。

  • 读命令、dummy cycles、mode bits、地址宽度和线型必须与实物 Flash 匹配。

  • 0xA0 mode bits、0xEB + 4 dummy 和 0xBB + 0 dummy 只对当前器件有直接参考意义。

  • 新 Flash 首次使用时,应先用 Burner 生成正确的参数区,不能直接套用其它型号的参数。

  • 采样延时和 SFC 工作时钟应在实际板卡上确认;时序不合适时可能表现为偶发读错、首字节错位或读取卡死。

  • 当前 XIP 窗口基址来自 code/soc/g32s10/include/base.h 中的 XIP_IOBASE,不要把普通 SFC 寄存器地址当成 XIP 窗口。

存储区域保护

当前镜像的 SPL 占用 Flash 起始 32 KB 区域,参数块位于 0x6800,也在这个 32 KB 块内。因此:

  • 不要在 0x000000 开始的第一个块上执行 32 KB 块擦除。

  • 不要覆盖 0x6800 参数块。

  • 不要擦除正在运行的代码或正在使用的文件系统分区。

  • 应用数据应放在项目规划的保留区域,不要占用 SPL、参数区和已挂载的文件系统分区。

Cache 与并发

  • XIP 数据读取前应失效对应 D-cache。

  • Flash 内容更新后,再次通过 XIP 读取前应失效对应 D-cache。

  • 若 XIP 区域包含指令,还必须失效对应 I-cache。

  • 只做 D-cache 维护而不做 I-cache 维护,可能出现“数据已经更新,但 CPU 仍执行旧指令”的问题。

TROUBLESHOOTING

常见问题及处理

现象常见原因处理建议
im_sfc_device_start() 返回 IM_ERR_NO_DEVICE参数区 magic/version 无效,或参数区未烧录。用 Burner 重新生成参数区;不要手工清零 0x6800 参数块。
初始化返回 IM_ERR_NOT_FOUND参数块中的 JEDEC ID 与实物不一致。核对 Flash 型号、ID 和参数块。
进入 XIP 返回 IM_ERR_NOT_READYSFC/NOR 未初始化;无 IRQ 且系统 tick 未初始化。先调用 im_sfc_device_start();确认系统 tick 工作。
进入 XIP 返回 IM_ERR_BUSY当前有普通 Flash 事务,或已经处于 XIP。停止其它 Flash 访问;用 im_sfc_nor_get_state() 检查状态。
进入 XIP 返回 IM_ERR_INVALID_STATENOR 中间件已经进入 ERROR 状态。停止访问后执行 im_sfc_nor_deinit(),并重新初始化或复位。
接口返回 IM_OK,但窗口数据错误命令、dummy、mode bits、地址宽度、线型、QE 位或 Cache 不匹配。对照 Flash 数据手册检查参数;读取前失效 D-cache,并以普通读数据做基准。
窗口读出全 0x00 或全 0xFF窗口地址错误、Flash 未进入连续读、SFC 时序错误或总线异常。检查 XIP_IOBASE + offset;确认 XIP_EN=1,并检查采样延时和硬件连接。
Dual I/O 数据整体错位Dual 读使用了错误的 dummy;当前 0xBB 应为 0 dummy。按 SFDP 或数据手册重新确认 dummy 和 mode bits。
读 XIP 窗口时程序卡死总线上无法完成访问,CPU 停在 load 指令。软件超时无法处理此类死锁;断电重启,并逐项核对窗口配置。
退出 XIP 失败或超时仍有代码或中断从 XIP 取指,仍有窗口访问,或控制器异常。把退出流程放到 SRAM/PSRAM;停止所有访问;必要时复位或重新上电。
退出后普通读仍失败Flash 没有真正退出连续读模式,或退出命令/mode byte 不匹配。检查 exit_command 和器件退出时序;断电恢复。
擦除后回读不是全 0xFFWP# 保护、状态寄存器写保护、地址错误或 Flash 仍 busy。检查写保护;确认地址和容量;等待 busy 结束。
页编程失败或数据错误写入前未擦除、跨页方式错误,或目标位于挂载分区。先擦除;使用 im_sfc_nor_write();卸载文件系统后再写。
写回后再次进入 XIP 仍读到旧数据D-cache 未维护,或旧指令仍在 I-cache。对目标范围 clean/invalidate D-cache;代码区域同时失效 I-cache。
Shell 中找不到 sfc_nor_* 命令固件未启用 SFC/NOR 中间件,或构建成 NAND 版本。确认 CONFIG_MIDDLEWARE_SFC=y、CONFIG_MIDDLEWARE_SFC_NOR=y。
Shell 写 Flash 提示 XIP 状态当前仍处于 XIP,且 Shell 没有 XIP 退出命令。通过应用代码、应用入口或调试环境调用 im_sfc_nor_xip_disable(),确认状态为 READY 后再写。
擦除后板子无法启动擦除了 SPL 首 32 KB 或 0x6800 参数区。使用完整镜像重新烧录;应用数据改放项目预留区。

SOURCE

相关代码位置

内容路径
运行期 XIP APIcode/middleware/sfc/include/im_sfc_nor.h
code/middleware/sfc/src/im_sfc_nor.c
Common SFC 的 XIP 控制器流程与退出帧code/middleware/sfc/src/im_sfc.c
底层 HAL 与寄存器定义code/drivers/g32s10/sfc/hal_sfc.c
code/include/drivers/g32s10/sfc/hal_sfc.h
code/include/drivers/g32s10/sfc/ll_sfc.h
XIP 地址窗口code/soc/g32s10/include/base.h
SFC Shell 原始读写命令code/middleware/shell/cmds/drivers/im_shell_cmd_sfc.c
SPL XIP 启动配置spl/Kconfig
spl/arch/riscv/cpu/g32s10-spl-xip.ld
spl/arch/riscv/cpu/g32s10-spl-xip-sram.ld