查看: 2806|回复: 2

[分享] 利用i.MXRT1xxx系列内部DCP引擎计算Hash值时需特别处理L1 D-Cache

[复制链接]
  • TA的每日心情
    开心
    7 天前
  • 签到天数: 301 天

    连续签到: 2 天

    [LV.8]以坛为家I

    3868

    主题

    7472

    帖子

    0

    管理员

    Rank: 9Rank: 9Rank: 9

    积分
    39227
    最后登录
    2025-7-18
    发表于 2022-4-2 17:07:00 | 显示全部楼层 |阅读模式
    利用i.MXRT1xxx系列内部DCP引擎计算Hash值时需特别处理L1 D-Cache


           今天痞子衡给大家介绍的是利用i.MXRT1xxx系列内部DCP引擎计算Hash值时需特别处理L1 D-Cache。


      关于i.MXRT1xxx系列内部通用数据协处理器DCP模块,痞子衡之前写过一篇文章 《SNVS Master Key仅在i.MXRT10xx Hab关闭时才能用于DCP加解密》 介绍了DCP基本功能和AES加解密使用注意事项,实际上DCP模块除了对AES加解密算法支持外,还支持经典的Hash算法(SHA-1/SHA-256/CRC32)。


      痞子衡最近支持一个i.MXRT大客户,他们项目里使用了DCP做Hash运算,但会出现概率性Hash校验失败的情况(差不多运行50次,会失败1次),这是什么情况?


    一、客户项目基本情况
      先介绍下客户基本情况,他们项目使用的主芯片是i.MXRT1062,并且配置了外部串行Flash存储程序代码(XiP),以及外部SDRAM放置程序数据区(其实主要是做frameBuffer的,但也同时放置了.data段和STACK),项目基于的SDK版本是v2.6.2。


      项目中主要调用了 \SDK_2.6.2_EVK-MIMXRT1060\middleware\mbedtls\library\sha256.c 中的 mbedtls_sha256() 函数,这个函数其实是通过调用 \SDK_2.6.2_EVK-MIMXRT1060\middleware\mbedtls\port\ksdk\ksdk_mbedtls.c 里的一系列底层函数mbedtls_sha256_xx() 来进一步实现的。


      ksdk_mbedtls.c 文件是同时适用Kinetis/LPC/i.MXRT等系列MCU的,不同MCU上硬件引擎不同(比如有LTC/CAAM/CAU3/DCP/HashCrypt)。对于i.MXRT1xxx,硬件引擎就是DCP,这些 mbedtls_sha256_xx() 函数主要调用了 SDK 标准驱动 fsl_dcp.c 里的如下函数:
    1. status_t DCP_HASH_Init(DCP_Type *base, dcp_handle_t *handle, dcp_hash_ctx_t *ctx, dcp_hash_algo_t algo);
    2. status_t DCP_HASH_Update(DCP_Type *base, dcp_hash_ctx_t *ctx, const uint8_t *input, size_t inputSize);
    3. status_t DCP_HASH_Finish(DCP_Type *base, dcp_hash_ctx_t *ctx, uint8_t *output, size_t *outputSize);
    复制代码
    二、概率性失败情况分析
      既然是概率性失败的问题,那大概率和Cache处理有关了,我们需要检查下 fsl_dcp.c 驱动是否很好地处理了Cache。让我们打开 \SDK_2.6.2_EVK-MIMXRT1060\boards\evkmimxrt1060\driver_examples\dcp 例程先看一下,在 dcp.c 文件的 main() 函数里可以看到明显的提醒。如果项目里用到了SDRAM,必须将DCache关掉,说明 dcp 驱动并不支持在DCache使能下运行。但显然这个客户项目用到了SDRAM,后来跟客户确认,他们DCache一直是使能的,这显然是有问题的。
    1. int main(void)
    2. {
    3.     dcp_config_t dcpConfig;

    4.     /* Init hardware*/
    5.     BOARD_ConfigMPU();
    6.     BOARD_InitPins();
    7.     BOARD_BootClockRUN();
    8.     BOARD_InitDebugConsole();

    9.     /* Data cache must be temporarily disabled to be able to use sdram */
    10.     SCB_DisableDCache();

    11.     ...
    复制代码
    让我们再次回到SDK版本,在 恩智浦SDK下载主页 可以看到所有i.MXRT1060 SDK历史版本,v2.6.2是2019年7月发布的(这个版本里的dcp驱动版本是v2.1.1),是的,这个客户算是i.MXRT早期客户了。而现在最新的SDK版本已经是v2.9.3(dcp驱动已经升级到v2.1.6),时间快过去两年了,客户并没有实时更新SDK版本。


      早期的 dcp 驱动没有处理DCache,所以其必须在 DCache 关掉的情况下才能正常工作。从v2.1.5开始增加了对 DCache 的处理,这样 dcp 驱动就可以在 DCache 使能的情况下正常工作了。

    三、DCP驱动里是如何处理DCache的?
      现在让我们在SDK标准驱动 fsl_dcp.c 中看一下它到底是怎么增加对DCache处理的。


    3.1 DCP上下文buffer设置
      使用 dcp 驱动的第一步是DCP模块初始化,即DCP_Init()函数,这个函数会在DCP->CTRL寄存器里将模块全部的四通道都使能以及将上下文(Context)的缓存和通道自切换功能也都开启,其中关于上下文切换有一个重要的私有全局变量 s_dcpContextSwitchingBuffer,这个变量被放置到了NON-CACHE区域(驱动改进处一)。下述DCP->CONTEXT寄存器就是用来存储 s_dcpContextSwitchingBuffer 地址的。
    1. AT_NONCACHEABLE_SECTION_INIT(static dcp_context_t s_dcpContextSwitchingBuffer);

    2. void DCP_Init(DCP_Type *base, const dcp_config_t *config)
    3. {
    4.     // 代码省略...

    5.     /* use context switching buffer */
    6.     base->CONTEXT = (uint32_t)&s_dcpContextSwitchingBuffer;
    7. }
    复制代码
    3.2 DCP用户数据in/out buffer设置
      DCP 模块初始化完成后,就是调用 dcp 驱动里的DCP_HASH()函数进行Hash运算,这个函数参数里有两个用户Buffer,一个Input Buffer存放待计算的消息数据,另一个Output Buffer存放计算好的Hash值(SHA256是32bytes),这两个Buffer最好由用户处理放置在NON-CACHE区。
    1. /* Input data for DCP like input and output should be handled properly
    2. * when DCACHE is used (e.g. Clean&Invalidate, use non-cached memory)
    3. */
    4. AT_NONCACHEABLE_SECTION(static uint8_t s_outputSha256[32]);

    5. status_t calc_sha256(const uint8_t *messageBuf, uint32_t messageLen)
    6. {
    7.     size_t outLength = sizeof(s_outputSha256);
    8.     dcp_handle_t m_handle;
    9.     m_handle.channel    = kDCP_Channel0;
    10.     m_handle.keySlot    = kDCP_KeySlot0;
    11.     m_handle.swapConfig = kDCP_NoSwap;

    12.     memset(&s_outputSha256, 0, outLength);

    13.     return DCP_HASH(DCP, &m_handle, kDCP_Sha256, messageBuf, messageLen, s_outputSha256, &outLength);
    14. }
    复制代码
    3.3 DCP_HASH()相关代码中DCache处理
      DCP_HASH()函数运行过程中会一直用到一个非常关键的内部结构体 dcp_hash_ctx_internal_t,这个结构体大小为47 Words(包含128byte的待计算消息数据块blk、32bytes实时计算结果runningHash、及其他辅助变量成员)。
    1. /*! internal dcp_hash context structure */
    2. typedef struct _dcp_hash_ctx_internal
    3. {
    4.     dcp_hash_block_t blk;        /*!< memory buffer. only full blocks are written to DCP during hash updates */
    5.     size_t blksz;                /*!< number of valid bytes in memory buffer */
    6.     dcp_hash_algo_t algo;        /*!< selected algorithm from the set of supported algorithms */
    7.     dcp_hash_algo_state_t state; /*!< finite machine state of the hash software process */
    8.     uint32_t fullMessageSize;    /*!< track message size */
    9.     uint32_t ctrl0;              /*!< HASH_INIT and HASH_TERM flags */
    10.     uint32_t runningHash[9];     /*!< running hash. up to SHA-256 plus size, that is 36 bytes. */
    11.     dcp_handle_t *handle;
    12. } dcp_hash_ctx_internal_t;
    复制代码
     dcp 驱动直接定义了 dcp_hash_ctx_t 型局部变量hashCtx,hashCtx空间后续会被用作dcp_hash_ctx_internal_t。旧版本里DCP_HASH_CTX_SIZE值为58,新版本增加到64,这是为了后续L1DCACHE的LINE对齐(驱动改进处二)。
    1. /*! @brief DCP HASH Context size. */
    2. #define DCP_HASH_CTX_SIZE 64

    3. /*! @brief Storage type used to save hash context. */
    4. typedef struct _dcp_hash_ctx_t
    5. {
    6.     uint32_t x[DCP_HASH_CTX_SIZE];
    7. } dcp_hash_ctx_t;

    8. status_t DCP_HASH(DCP_Type *base, dcp_handle_t *handle, dcp_hash_algo_t algo, const uint8_t *input, size_t inputSize, uint8_t *output, size_t *outputSize)
    9. {
    10.     dcp_hash_ctx_t hashCtx = {0};
    11.     status_t status;

    12.     status = DCP_HASH_Init(base, handle, &hashCtx, algo);
    13.     status = DCP_HASH_Update(base, &hashCtx, input, inputSize);
    14.     status = DCP_HASH_Finish(base, &hashCtx, output, outputSize);
    15.     // ...
    16. }

    17. status_t DCP_HASH_Init/Update/Finish(...,dcp_hash_ctx_t *ctx,...)
    18. {
    19.     dcp_hash_ctx_internal_t *ctxInternal;
    20.     /* Align structure on DCACHE line*/
    21. #if defined(__DCACHE_PRESENT) && (__DCACHE_PRESENT == 1U) && defined(DCP_USE_DCACHE) && (DCP_USE_DCACHE == 1U)
    22.     ctxInternal = (dcp_hash_ctx_internal_t *)(uint32_t)((uint8_t *)ctx + FSL_FEATURE_L1DCACHE_LINESIZE_BYTE);
    23. #else
    24.     ctxInternal = (dcp_hash_ctx_internal_t *)(uint32_t)ctx;
    25. #endif

    26.     // 代码省略...
    27. }
    复制代码
     DCP_HASH()函数中启动DCP引擎去计算消息块数据前,都会调用 DCACHE_InvalidateByRange() 函数对 ctxInternal 所占空间做清理(驱动改进处三)。启动DCP引擎工作一次的函数是dcp_hash_update(),这个函数会利用 dcp_work_packet_t 型结构体变量,对于这个结构,代码中也同样做了L1DCACHE对齐处理(驱动改进处四):
    1. /*! @brief DCP's work packet. */
    2. typedef struct _dcp_work_packet
    3. {
    4.     uint32_t nextCmdAddress;
    5.     uint32_t control0;
    6.     uint32_t control1;
    7.     uint32_t sourceBufferAddress;
    8.     uint32_t destinationBufferAddress;
    9.     uint32_t bufferSize;
    10.     uint32_t payloadPointer;
    11.     uint32_t status;
    12. } dcp_work_packet_t;

    13. #if defined(__DCACHE_PRESENT) && (__DCACHE_PRESENT == 1U) && defined(DCP_USE_DCACHE) && (DCP_USE_DCACHE == 1U)
    14. static inline uint32_t *DCP_FindCacheLine(uint8_t *dcpWorkExt)
    15. {
    16.     while (0U != ((uint32_t)dcpWorkExt & ((uint32_t)FSL_FEATURE_L1DCACHE_LINESIZE_BYTE - 1U)))
    17.     {
    18.         dcpWorkExt++;
    19.     }
    20.     return (uint32_t *)(uint32_t)dcpWorkExt;
    21. }
    22. #endif

    23. static status_t dcp_hash_update(DCP_Type *base, dcp_hash_ctx_internal_t *ctxInternal, const uint8_t *msg, size_t size)
    24. {
    25.     status_t completionStatus = kStatus_Fail;

    26.     /* Use extended  DCACHE line size aligned structure */
    27. #if defined(__DCACHE_PRESENT) && (__DCACHE_PRESENT == 1U) && defined(DCP_USE_DCACHE) && (DCP_USE_DCACHE == 1U)
    28.     dcp_work_packet_t *dcpWork;
    29.     uint8_t dcpWorkExt[sizeof(dcp_work_packet_t) + FSL_FEATURE_L1DCACHE_LINESIZE_BYTE] = {0U};
    30.     dcpWork = (dcp_work_packet_t *)(uint32_t)DCP_FindCacheLine(dcpWorkExt);
    31. #else
    32.     dcp_work_packet_t dcpWorkPacket = {0};
    33.     dcp_work_packet_t *dcpWork      = &dcpWorkPacket;
    34. #endif

    35.     do
    36.     {
    37.         completionStatus = dcp_hash_update_non_blocking(base, ctxInternal, dcpWork, msg, size);
    38.     } while (completionStatus == (int32_t)kStatus_DCP_Again);

    39.     completionStatus = DCP_WaitForChannelComplete(base, ctxInternal->handle);

    40.     ctxInternal->ctrl0 = 0;
    41.     return (completionStatus);
    42. }
    复制代码
     至此,利用i.MXRT1xxx系列内部DCP引擎计算Hash值时需特别处理L1 D-Cache痞子衡便介绍完毕了,掌声在哪里~~~









    qiandao qiandao
    回复

    使用道具 举报

  • TA的每日心情
    开心
    2025-6-10 23:03
  • 签到天数: 1502 天

    连续签到: 1 天

    [LV.Master]伴坛终老

    97

    主题

    4688

    帖子

    12

    版主

    Rank: 7Rank: 7Rank: 7

    积分
    10080
    最后登录
    2025-7-2
    发表于 2022-4-2 17:34:17 | 显示全部楼层
    这么多的字。
    看来数据功能卸载,已经成为MCU硬件的发展的一个新方向
    该会员没有填写今日想说内容.
    回复 支持 反对

    使用道具 举报

  • TA的每日心情
    奋斗
    昨天 23:21
  • 签到天数: 1845 天

    连续签到: 5 天

    [LV.Master]伴坛终老

    203

    主题

    3万

    帖子

    64

    超级版主

    Rank: 8Rank: 8

    积分
    112619
    最后登录
    2025-7-17
    发表于 2022-4-2 17:50:39 | 显示全部楼层
    都用起缓存了
    该会员没有填写今日想说内容.
    回复 支持 反对

    使用道具 举报

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

    本版积分规则

    关闭

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

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

    GMT+8, 2025-7-18 20:50 , Processed in 0.084345 second(s), 21 queries , MemCache On.

    Powered by Discuz! X3.4

    Copyright © 2001-2024, Tencent Cloud.

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