bug fix - add fence to protect atomic operation
记录两个 bug,risc-v relaxed memory model 会导致 mcu 写寄存器延时,由此导致的问题;
原理可参考 note - risc-v memory model - usage of fence。
bug 1. bootloader jump failed
1.1 背景介绍
1.1.1 bootloader 方案
实现 bootloader + application,在 bootloader 中实现串口烧录和识别目标固件并跳转;
常规 bootloader 设计分两种:
- bootloader + application running area + application ota backup area
ota 即固件在线更新;
该方案每次实现 ota 时将固件更新到 ota backup area,重启后由 bootloader 从 backup area 拷贝固件至 running area 再运行,该方案为芯片设计限制,固件必须在指定位置运行。 - bootloader + application area A + application area B
该方案每次实现 ota 时将固件更新到非运行的另一块区域,即当前运行 A 则更新至 B,当前运行 B 则更新至 A,随后重启,通过 bootloader 识别高版本固件再指定跳转。
1.1.2 telink soc 多地址启动
由于 telink series soc 支持多地址启动,可采用上述方案 2,所以 bootloader 可直接跳转到目标地址,而无需将 ota 固件拷贝到指定运行地址。
soc 多地址启动通用原理:
soc支持 0/0x20000/0x40000/0x80000 地址启动,上电 soc 通过识别多地址位置对应的标志位(通常为固件中起始位置固定偏移处存有固定的启动标志位,如 telink 会在固件偏移 0x20 byte 的位置填充 “TLNK” 四个字符作为启动标志);
检查到对应启动标志位后,即默认当前偏移地址作为基地址,以 0x20000 为例,随即设置偏移地址寄存器(reg_multi_addr),即 soc 默认将 0x20000 视为 cpu 取址的 0 地址,举例:flash_addr = cpu_addr + reg_multi_addr; flash 取址会默认在 cpu 取址基础上加上对应的偏移地址;
1.2 问题现象
当固件 ota 完成后重启,bootloader检测到 0x20000 有新固件,跳转时出现异常,application 无法正常工作。
1.3 原因
首次上电有硬件检测有效固件位置,并设置偏移地址寄存器(reg_multi_addr),首次上电会检测到 0 地址的 bootloader 并运行,此时偏移地址寄存器(reg_multi_addr)默认为 0 地址;
当 bootloader jump 到固件位置时,由 bootloader 检测并设置偏移地址寄存器(reg_multi_addr)。
reg_multi_addr = addr;//line 1
jump;//line2
由于risc-v relaxed memory model 影响,line 1 设置但未实际写入寄存器,此时执行了 line 2;
下面分为两种 case:
- application firmware 位于 0x8000,硬件偏移地址为 0,软件偏移地址为8000(后续介绍),此时即便 line 1 未执行成功,reg_multi_addr 为上电检测到 bootloader 设置的初始值,还是为 0,所以不影响,当前 case 运行成功;
- application firmware 位于 0x28000,硬件偏移地址为 0,软件偏移地址为8000(由于烧录不同地址的固件需要保持一致,所以软件偏移地址固件,需要在基地址基础上偏移 0x8000),此时 line 1 未执行成功,reg_multi_addr 为上电检测到 bootloader 设置的初始值,还是为 0,但是软件 jump 到了 0x8000 的位置,导致取址出错;
2.1 如果 reg_multi_addr = 0,软件会取 flash 0x8000+reg_multi_addr = 0x8000 位置;
2.2 如果 reg_multi_addr = 0x20000,软件会取 flash 0x8000+reg_multi_addr = 0x28000 位置;
导致偏移地址寄存器未生效,bootloader jump 取地址错误,所以软件异常。
1.4 解决办法
reg_multi_addr = addr;//line 1
fence;
jump;//line2
在 jump 之前加入 fence,确保 reg_multi_addr 设置成功,偏移地址寄存器生效,随后 jump 取址即正常。