3. OTFDEC与Flash接口:OTFDEC如何连接Flash、地址映射与解密窗口

好,咱们接着往下聊。上一章我们把OTFDEC的密钥体系和配置讲清楚了,这一章我重点说说它跟Flash之间到底是怎么「勾搭」上的。说白了,OTFDEC不是凭空变出明文,它得知道从Flash的哪个位置读数据、解密后放到哪个地址让CPU去取。这个映射关系,就是本节的核心。

3.1 硬件连接:OTFDEC在总线上的位置

先看一张简化的框图(我这里用文字描述,你心里画一下):

CPU Core
    |
    v
AXI/AHB总线矩阵
    |
    v
OTFDEC (解密引擎)
    |
    v
Flash控制器
    |
    v
Flash存储阵列

OTFDEC插在CPU和Flash控制器之间。CPU发起的读请求,先经过OTFDEC,再到达Flash。OTFDEC拿到密文后,实时解密,把明文返回给CPU。整个过程对软件是透明的——你读一个地址,得到的就是解密后的数据。

关键点:OTFDEC工作在「线速」模式。它不会拖慢Flash的读取速度,因为解密流水线跟Flash的读取流水线是并行跑的。我在一个项目里实测过,开启OTFDEC后,CPU从加密区域读代码的速度几乎没变化。

3.2 地址映射:CPU看到的是「假地址」

嗯,这里有个容易搞混的概念。CPU访问Flash时,用的是系统地址空间里的地址。比如STM32L5,Flash的基地址是0x0800 0000。但OTFDEC内部维护了一套自己的地址映射规则。

具体来说:

  • CPU视角:访问0x0800 0000 + offset,得到的是解密后的数据
  • OTFDEC视角:它把CPU发来的地址,减去一个「基地址偏移」,得到Flash中的物理地址
  • Flash视角:它只认OTFDEC传过来的物理地址,返回密文数据

举个例子。假设你配置了OTFDEC的起始地址为0x0800 4000,那么:

CPU读 0x0800 4000 → OTFDEC计算偏移 = 0x0800 4000 - 0x0800 0000 = 0x4000
                  → OTFDEC从Flash物理地址0x4000处读取密文
                  → 解密后返回给CPU

这个映射关系,是通过OTFDEC的OTFDEC_FLASHADDR寄存器配置的。我个人习惯把起始地址对齐到4KB边界,这样不容易出问题。

3.3 解密窗口:不是整个Flash都能解密

OTFDEC不是对整个Flash进行解密。它只对「解密窗口」内的地址做解密操作。窗口之外的地址,数据直接透传——读什么就是什么。

解密窗口由两个参数定义:

参数 寄存器 说明
起始地址 OTFDEC_FLASHADDR 解密区域的起始物理地址(Flash内部地址)
区域大小 OTFDEC_FLASHSIZE 解密区域的大小,单位是字节

举个例子:

OTFDEC_FLASHADDR = 0x4000  // 从Flash物理地址0x4000开始
OTFDEC_FLASHSIZE = 0x4000  // 区域大小为16KB

解密窗口范围:Flash物理地址 0x4000 ~ 0x7FFF
CPU访问地址:0x0800 4000 ~ 0x0800 7FFF  → 自动解密
CPU访问地址:0x0800 0000 ~ 0x0800 3FFF  → 透传(不解密)
CPU访问地址:0x0800 8000 及以上          → 透传(不解密)

注意:解密窗口的起始地址和大小都必须对齐到4KB边界。我曾经因为没对齐,导致部分区域解密失败,查了半天才发现是配置问题。你想想看,如果起始地址是0x4100,那0x4000~0x40FF这段区域就尴尬了——它到底算不算解密区?硬件直接给你报错。

3.4 多区域支持:一个OTFDEC,多个解密窗口

STM32L5的OTFDEC支持最多4个独立的解密窗口。每个窗口可以配置不同的密钥和起始地址。这个设计很实用——比如你可以把Bootloader放在窗口0,用密钥A加密;把应用程序放在窗口1,用密钥B加密。

每个窗口的配置寄存器是独立的:

窗口0: OTFDEC_FLASHADDR0, OTFDEC_FLASHSIZE0, OTFDEC_KEYR0
窗口1: OTFDEC_FLASHADDR1, OTFDEC_FLASHSIZE1, OTFDEC_KEYR1
窗口2: OTFDEC_FLASHADDR2, OTFDEC_FLASHSIZE2, OTFDEC_KEYR2
窗口3: OTFDEC_FLASHADDR3, OTFDEC_FLASHSIZE3, OTFDEC_KEYR3

窗口之间不能重叠。如果两个窗口有重叠区域,硬件会怎么处理?嗯,我建议你绝对不要这么干。STM32的参考手册里写的是「未定义行为」,说白了就是可能解密出乱码,也可能直接挂掉。

3.5 实际项目中的配置流程

我一般按这个步骤来配置OTFDEC的地址映射:

  1. 规划分区:确定哪些Flash区域需要加密,哪些不需要。比如Bootloader通常不加密,应用程序加密。
  2. 计算地址:把加密区域的起始地址和大小算清楚,确保4KB对齐。
  3. 烧录密钥:在OTFDEC使能之前,先把密钥写入对应的KEYR寄存器。
  4. 配置窗口:设置FLASHADDR和FLASHSIZE寄存器。
  5. 使能OTFDEC:置位OTFDEC_CR寄存器中的EN位。
  6. 验证:从加密区域读几个字,跟预期明文对比。

小技巧:我习惯在使能OTFDEC之前,先读一遍加密区域的原始密文,保存下来。这样如果后面解密出问题,可以对比是加密过程错了,还是解密过程错了。这个习惯帮我省了不少调试时间。

3.6 避坑指南:地址映射的几个坑

讲几个我踩过的坑:

  • 坑一:CPU缓存导致读到的数据不对。OTFDEC解密后的数据会被CPU的缓存缓存起来。如果你在运行时修改了密钥或窗口配置,缓存里的数据还是旧的。解决办法是每次修改配置后,执行一次DCache和ICache的清理操作。
  • 坑二:DMA访问加密区域。DMA控制器访问Flash时,也会经过OTFDEC。但DMA没有缓存,所以每次读都是实时解密。这个没问题。但要注意,如果DMA和CPU同时访问同一个加密地址,可能会产生总线冲突。我建议DMA和CPU分时访问。
  • 坑三:调试器读加密区域。调试器(比如ST-Link)通过SWD接口读Flash时,不经过OTFDEC。所以你用调试器读到的永远是密文。我第一次遇到时还以为芯片坏了,后来才反应过来——调试器走的是另一条路。

3.7 小结

这一章我们聊了OTFDEC怎么跟Flash「对话」。核心就三点:

  • OTFDEC插在CPU和Flash之间,实时解密
  • 地址映射靠FLASHADDR和FLASHSIZE寄存器配置
  • 解密窗口必须4KB对齐,最多4个窗口

下一章我会讲OTFDEC的启动流程和初始化代码怎么写。到时候咱们手写一个完整的配置示例,把今天讲的这些寄存器都用上。