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。 |
| 底层 HAL | hal_sfc_xip_mode_config() 等 | 仅供驱动或调试使用,普通应用不建议直接调用。 |
CONFIG_XIP_ENABLE。不要把 SPL 的 XIP 启动配置与运行期的 im_sfc_nor_xip_*() 接口混为一谈。READ PHASE
当前代码支持的 XIP 读相位
当前代码支持以下两种 XIP 读相位配置:
| 参数 | Quad I/O | Dual I/O |
|---|---|---|
| 连续读命令 | 0xEB | 0xBB |
| Dummy cycles | 4 | 0 |
| 线型 | HAL_SFC_TRANSFER_QUAD_IO_SPI | HAL_SFC_TRANSFER_DUAL_IO_SPI |
| Mode bits | 0xA0 | 0xA0 |
| 地址字段 | 3 字节 Flash 地址 + 1 字节 mode byte | 同左 |
| AHB 访问 | 默认 incr8;可按需使用 single | 同左 |
WORKFLOW
推荐操作顺序
初始化 SFC/NOR。
按 Flash 参数配置 XIP 读相位。
调用
im_sfc_nor_xip_enable()进入 XIP。通过 XIP 窗口读取数据或执行代码。
停止所有 XIP 窗口访问。
调用
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.h | HAL 层设置 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
前置条件
板卡使用 SFC NOR Flash,且 Flash 支持所选的连续读命令。
SPL 能从 Flash 正常启动,应用代码当前运行在 SRAM/PSRAM,而不是正在被操作的 XIP 区域。
SFC 驱动、SFC 中间件和 NOR 中间件已经启用。
板级 SFC 设备已经注册,且
im_sfc_device_start()初始化成功。Flash
0x6800偏移处的 Burner 参数块有效,参数块中的 JEDEC ID、命令、页大小、擦除大小与实物一致。进入 XIP 前没有任何任务、DMA 或中断正在访问同一片 Flash。
准备 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 执行的要求。
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
对目标地址范围执行 D-cache clean/invalidate。
如果目标区域包含代码,再对同一范围执行 I-cache invalidate。
重新调用
im_sfc_nor_xip_enable()。通过 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 匹配。
0xA0mode 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_READY | SFC/NOR 未初始化;无 IRQ 且系统 tick 未初始化。 | 先调用 im_sfc_device_start();确认系统 tick 工作。 |
进入 XIP 返回 IM_ERR_BUSY | 当前有普通 Flash 事务,或已经处于 XIP。 | 停止其它 Flash 访问;用 im_sfc_nor_get_state() 检查状态。 |
进入 XIP 返回 IM_ERR_INVALID_STATE | NOR 中间件已经进入 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 和器件退出时序;断电恢复。 |
擦除后回读不是全 0xFF | WP# 保护、状态寄存器写保护、地址错误或 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 API | code/middleware/sfc/include/im_sfc_nor.hcode/middleware/sfc/src/im_sfc_nor.c |
| Common SFC 的 XIP 控制器流程与退出帧 | code/middleware/sfc/src/im_sfc.c |
| 底层 HAL 与寄存器定义 | code/drivers/g32s10/sfc/hal_sfc.ccode/include/drivers/g32s10/sfc/hal_sfc.hcode/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/Kconfigspl/arch/riscv/cpu/g32s10-spl-xip.ldspl/arch/riscv/cpu/g32s10-spl-xip-sram.ld |