查看: 912|回复: 1

基于SWD离线烧写OTP

[复制链接]
  • TA的每日心情
    开心
    2024-3-26 15:16
  • 签到天数: 266 天

    [LV.8]以坛为家I

    3299

    主题

    6546

    帖子

    0

    管理员

    Rank: 9Rank: 9Rank: 9

    积分
    32024
    最后登录
    2024-4-25
    发表于 2022-12-29 10:03:17 | 显示全部楼层 |阅读模式
    基于SWD离线烧写OTP


    嵌入式开发的最后阶段是要将成果产品化,要交付工厂量产。对于NXP i.MXRT系列的芯片来说,除了要交付给工厂项目固件外,还需要工厂写OTP区域来配置芯片启动方式或者开启芯片的安全功能。离线编程器烧写固件和OTP的方法有多种,如ISP,SWD或者Jtag等。


    今天就为大家介绍下,如何让离线编程器利用SWD接口烧写OTP。

    编写OTP烧写算法

    直接操作寄存器来写OTP的过程很繁琐。很多编程器借助Flash烧写算法来烧写Flash,我们也可以借助烧写Flash的方法来烧写OTP。
    本加油站曾经有一篇《编写Keil的自定义Flash烧写算法FLM》,在这篇文章中,作者介绍了如何利用Keil来编写Flash烧写算法。
    编写OTP烧写算法的方法和编写Flash烧写算法的方法一样,只需要复用接口函数Init和UnInit,新增接口OTPWrite。和Flash相关的操作函数可以直接删掉。OTP的烧写算法接口声明如下:
    1. int Init (unsigned long adr, unsigned long clk, unsigned long fnc);
    2. int UnInit (unsigned long fnc);
    3. int OTPWrite (unsigned long idx, unsigned long value);
    复制代码
    每个烧写算法接口函数都是对应OTP驱动函数的封装。下面是这3个接口函数的参考实现:
    1. int Init (unsigned long adr, unsigned long clk, unsigned long fnc) {
    2.     BOARD_BootClockRUN();
    3. #if (defined(FSL_FEATURE_OCOTP_HAS_TIMING_CTRL) && FSL_FEATURE_OCOTP_HAS_TIMING_CTRL)
    4.     OCOTP_Init(OCOTP, CLOCK_GetFreq(kCLOCK_IpgClk));
    5. #else
    6.     OCOTP_Init(OCOTP, 0U);
    7. #endif
    8.     return (0);              
    9. }

    10. int OTPWrite (unsigned long idx, unsigned long value) {
    11.     status_t status = kStatus_Success;
    12.     status = OCOTP_WriteFuseShadowRegister(OCOTP, idx, value);
    13.     return (kStatus_Success == status) ? (0) : (1);
    14. }

    15. int UnInit (unsigned long fnc) {
    16.     OCOTP_Deinit(OCOTP);
    17.     return (0);
    18. }
    复制代码
    以OCOTP_开头的函数是OTP driver接口。函数BOARD_BootClockRUN的具体实现请查看SDK内OTP driver的示例。

    提取算法代码

    Keil MDK生成的后缀为FLM的算法文件实质上是一段与地址无关的代码。对于下载器来说,一种简单的使用方法是把文件内的相关函数指令提取出来。这里需要用到开源项目pyocd里的python脚本FlashAlgo。该项目github地址为:


    https://github.com/pyocd/FlashAlgo


    FlashAlgo默认会从符号表里查找EraseSector等函数名。所以我们需要做一点改动,把flash相关操作的函数名换成OTP相关操作的函数名。


    文件flash_algo.py中集合REQUIRED_SYMBOLS改动如下:
    1. REQUIRED_SYMBOLS = set([
    2.         "Init",
    3.         "UnInit",
    4.         "OTPWrite", # EraseSector
    5.     ])
    复制代码
    文件generate_blobs.py中列表TEMPLATES改动如下:
    1. TEMPLATES = [
    2.     ("c_blob.tmpl", "c_blob.c"),
    3. ]
    复制代码
    c_blob.tmpl在文件夹templates中,将program_target_t改名为program_ocotp_t,并将其中和flash相关的Erase、Program等项删掉,新增OTPWrite,新增项如下:
    1. static const program_ocotp_t ocotp = {
    2. [...]
    3. {{'0x%08x' % (algo.symbols['OTPWrite'] + header_size + entry)}}, // OTPWrite
    4. [...]
    复制代码
    在执行脚本之前,先安装python依赖的第三方库,命令行如下:


      pip install requirements.txt


    执行脚本的命令行如下:


    python generate_blobs.py --blob_start 0x20000000 otp_prog.FLM


    这里的0x20000000是将要运行算法的目标RAM地址,读者可以根据芯片RAM位置配置。otp_prog.FLM为上一小节Keil MDK编译出来的FLM文件。c_blob.c是生成的包含烧写算法及相关信息的文件。

    烧写OTP

    要通过SWD烧写OTP,就需要实现一个SWD时序协议。ARM公司的开源项目DAPLINK实现了支持arm cortex系列MCU的SWD时序协议。具体的实现在文件swd_host.c内。


    该项目地址是https://github.com/armmbed/daplink


    这里我们使用swd_host.c中的接口来演示如何烧写OTP。和OTP相关的操作接口函数主要有两个:
    1. uint8_t swd_write_memory(uint32_t address, uint8_t *data, uint32_t size);

    2. uint32_t swd_flash_syscall_exec(const program_syscall_t *sysCallParam, uint32_t entry, uint32_t arg1, uint32_t arg2, uint32_t arg3, uint32_t arg4, flash_algo_return_t return_type);
    复制代码
    调用OTP算法需要的主要参数在文件c_blob.c中的结构体program_ocotp_t。我们仿照DAPLINK中program_target_t给出program_ocotp_t的声明:
    1. typedef struct {
    2.     const uint32_t  init;
    3.     const uint32_t  uninit;
    4.     const uint32_t  OCOTPWrite;
    5.     const program_syscall_t sys_call_s;
    6.     const uint32_t  program_buffer;
    7.     const uint32_t  algo_start;
    8.     const uint32_t  algo_size;
    9.     const uint32_t *algo_blob;
    10.     const uint32_t  program_buffer_size;
    11. } program_ocotp_t;
    复制代码
    调用swd_write_memory来下载烧写算法到目标地址的示例代码如下:


    swd_write_memory(ocotp.algo_start, (uint8_t *)ocotp.algo_blob, ocotp.algo_size);


    调用OTP模块初始化函数的示例代码如下:


    swd_flash_syscall_exec(&ocotp.sys_call_s, ocotp.init, 0, 0, 0, 0, FLASHALGO_RETURN_BOOL) ;


    调用OTP模块写fuse函数的示例代码如下:


    swd_flash_syscall_exec(&ocotp.sys_call_s, ocotp.OTPWrite, fuse_idx, fuse_value, 0, 0, FLASHALGO_RETURN_BOOL);

    到此,利用SWD烧写OTP就介绍完了。


    如果读者熟悉烧写flash的流程,会发现烧写OTP和烧写flash没有差别。


    OTP的值可以放在编程器的配置文件中,和固件存储在一起。要想保护OTP的值,可以将它们存储在硬件安全模块(HSM)中。笔者参与的基于RT1020的离线编程器采用了文章中描述的方法烧写OTP。该项目预计明年初会以应用笔记的方式发布在恩智浦官网。


    限于篇幅,脚本中有些和flash相关的域我没有删掉,相信聪明的读者可以自行完成。

    签到签到
    回复

    使用道具 举报

    该用户从未签到

    0

    主题

    3

    帖子

    0

    新手上路

    Rank: 1

    积分
    45
    最后登录
    2023-6-25
    发表于 2023-4-4 15:20:46 | 显示全部楼层
    本帖最后由 eefocus_3830488 于 2023-4-4 17:35 编辑

    很实用,厉害。
    回复 支持 反对

    使用道具 举报

    您需要登录后才可以回帖 注册/登录

    本版积分规则

    关闭

    站长推荐上一条 /4 下一条

    Archiver|手机版|小黑屋|恩智浦技术社区

    GMT+8, 2024-4-26 04:14 , Processed in 0.115990 second(s), 21 queries , MemCache On.

    Powered by Discuz! X3.4

    Copyright © 2001-2024, Tencent Cloud.

    快速回复 返回顶部 返回列表