i.MX8MP 的 Cortex-M7 初始化要同时处理三类状态:本核的 MPU/cache、SoC 的资源权限,以及多个主控共享的时钟和电源。某个 SDK 例程能在评估板上启动,并不表示它可以在 Linux 已运行后原样再次执行。
本文对照 NXP MCUXpresso SDK MCUX_2.16.000 的 EVK 板级代码、MIMX8ML8 驱动与 RM Rev.3,说明原工程中的 FL_BOARD_InitMemory()、FL_BOARD_RdcInit()、FL_BOARD_BootClockRUN() 和微秒延时。FL_ 前缀属于项目包装;NXP 对应函数使用 BOARD_ 前缀,调用处必须与实际定义一致。
1. MPU 配置实际改变了什么
跳转到“1. MPU 配置实际改变了什么”BOARD_InitMemory() 读取链接符号,关闭已启用的 I-cache/D-cache,禁用 MPU,配置区域后重新启用 MPU 和缓存。这是启动阶段的板级策略。在线重配时,还涉及其他执行上下文、已有脏数据、DMA 和对端访问,不能仅复制这几次开关调用。
ARM_MPU_RASR() 的参数依次是 XN、访问权限 AP、TEX、S、C、B、禁用子区域掩码 SRD 和 SIZE。区域大小为 字节,最小 32 字节;基址须按区域大小对齐。相互覆盖时,编号较高的有效区域优先。
SDK 的区域表按闭区间展开如下。KiB、MiB、GiB 都按 1024 进制计算。
| 区域 | 起止地址 | 大小 | 类型及用途 |
|---|---|---|---|
| 0 | 0x00000000…0x3fffffff | 1 GiB | Device、不可执行、不可缓存;低地址兜底 |
| 1 | 0x00000000…0x0001ffff | 128 KiB | ITCM/TCML;Normal、不可缓存、可执行 |
| 2 | 0x08000000…0x0fffffff | 128 MiB | QSPI 映射;Normal、可缓存、可执行 |
| 3 | 0x20000000…0x2001ffff | 128 KiB | DTCM/TCMU;Normal、不可缓存、可执行 |
| 4 | 0x40000000…0x7fffffff | 1 GiB | DDR;Normal、不可缓存、可执行 |
| 5 | 0x80000000…0xbfffffff | 1 GiB | DDR;Normal、不可缓存、可执行 |
| 6 | 由链接符号决定 | 2 的幂 | 覆盖其中的程序/数据区,设为 Normal、可缓存、可执行 |
原注释中部分终点多写了 1:例如 0x20000000 开始的 128 KiB 结束于 0x2001ffff,不能把 0x20020000 包含在区域内。区域 0 的编码为 TEX=0,C=0,B=1,对应 shareable Device;虽然 SDK 注释写着 not shareable、参数 S 为 0,但这一组合的 Device 共享属性由类型编码决定,不能单看 S 位。
常见属性组合如下,x 表示该组合下这个位不决定表中属性:
| TEX | S | C | B | 类型与缓存策略 |
|---|---|---|---|---|
| 0 | x | 0 | 0 | Strongly ordered,shareable |
| 0 | x | 0 | 1 | Device,shareable |
| 0 | 0/1 | 1 | 0 | Normal,write-through、no write allocate;S 选择共享属性 |
| 0 | 0/1 | 1 | 1 | Normal,write-back、no write allocate;S 选择共享属性 |
| 1 | 0/1 | 0 | 0 | Normal,内外层均 non-cacheable;S 选择共享属性 |
| 1 | 0/1 | 1 | 1 | Normal,write-back、read/write allocate;S 选择共享属性 |
| 2 | x | 0 | 0 | Device,non-shareable |
这些区域均使用 ARM_MPU_AP_FULL,因此这份配置主要表达内存类型与缓存策略,不能描述成已经实现任务隔离。XN=0 允许取指,也不等于整个地址范围都有实际存储器。
启用时的 PRIVDEFENA 允许特权访问在未命中区域时使用默认内存映射;HFNMIENA 让 MPU 在 HardFault、NMI 和 FAULTMASK 场景下继续工作。二者不替代区域本身的权限设计。
链接符号、Linux 保留区和 MPU 区域是三回事
跳转到“链接符号、Linux 保留区和 MPU 区域是三回事”链接脚本把 __CACHE_REGION_START、__CACHE_REGION_SIZE 定义为绝对符号。SDK 用数组形式声明,再取符号的地址值;不是去这个地址读取一个 uint32_t 变量。实际值应从所用链接脚本和最终 map 文件确认。
此版本的 DDR 链接脚本把前 4 MiB 的程序/数据区设为可缓存,后续区域放置 NonCacheable 段;TCM 和 flash 链接脚本则把 __CACHE_REGION_SIZE 设为 0。它们是示例布局,必须与具体固件和 Linux 的 reserved-memory 一致。
SDK 注释提到 0x80000000 开始的 16 MiB Linux 保留区;区域 5 为方便对齐实际覆盖 1 GiB。设置 MPU 属性既不会让 Linux 保留这 1 GiB,也不会改变 A53 页表或使 DMA 自动一致。把变量放进 .NonCacheable 一类段,仅决定链接位置,仍需对应的 MPU 属性与对端映射。
用显式检查替代仅依赖 assert
跳转到“用显式检查替代仅依赖 assert”原算法通过右移计算对数,再用 assert 检查。发布构建可能关闭 assert;size=1 会跳过 i!=0 分支,1 << 31 还涉及有符号左移问题。大小为 0 时也应明确清除区域 6,防止重复调用时保留上一轮配置。
下面只计算 32 字节至 2 GiB 区域的 SIZE 字段,不读写 MPU。4 GiB 无法放入本接口的 uint32_t size,不在接口范围内。调用者仍需确认该区完全位于已分配的实际 RAM,并且不会覆盖共享区。
#include <stdbool.h>#include <stddef.h>#include <stdint.h>
bool m7_mpu_size_field(uint32_t base, uint32_t size, uint32_t *field){ if (field == NULL || size < 32U || (size & (size - 1U)) != 0U || (base & (size - 1U)) != 0U) { return false; } uint32_t exponent = 0U; for (uint32_t value = size; value > 1U; value >>= 1U) { ++exponent; } *field = exponent - 1U; return true;}在已停用 MPU/cache 的启动配置阶段,先禁用区域 6,再处理 size==0;非零时调用检查函数,失败应终止启动并报告错误,成功才设置该区域。不要把失败变成“继续使用默认属性”。配置完成后使用 CMSIS 的 MPU/cache 接口完成必要的屏障和启用步骤。共享缓冲的所有权、clean/invalidate 顺序另见缓存一致性说明。
2. RDC 域分配与访问策略
跳转到“2. RDC 域分配与访问策略”RDC 把总线主控分配到域,并为外设或内存区域设置各域访问策略。i.MX8MP 支持四个域;EVK 的 BOARD_DOMAIN_ID=1 是板级选择,不能据此认为所有固件启动时都已属于域 1。
SDK 的 BOARD_RdcInit() 做三件事:读取当前主控的域号,查询该域对 RDC 外设本身的权限,在具有写权限时把 M7 指派给 BOARD_DOMAIN_ID。RDC_GetPeriphAccessPolicy() 返回对应域的两位权限:
| 值 | 权限 |
|---|---|
| 0 | 无访问权限 |
| 1 | 可写 |
| 2 | 可读 |
| 3 | 可读写 |
因此原来的 0x1U & policy 检查的是写权限,不是笼统地“有权限”。指派 M7 到域 1,并不会自动给域 1 开放 UART/PWM,也不会把域 0 从这些外设排除。外设 PDAP、内存区域、锁定位及其他主控仍须由整体启动方案配置。
如果 A53/U-Boot 已完成正确分配,这一步可能不需要再次写入;如果没有写权限,SDK 分支会跳过分配。随后直接宣称“现在 M7 必在域 1”并不严谨,应读取当前域并核对所需资源策略。单核启动与 A53 先启动的区别在于已经建立的系统状态,不在于函数名称。
3. 域门控不能代替时钟所有权
跳转到“3. 域门控不能代替时钟所有权”RDC 域号也影响 CCM 门控依赖。CCGR 不是“每个域一位”,而是四个两位字段,位移为 0、4、8、12。RM 规定主控写入时仅更新自己所属域的字段,其他域字段写入被忽略。
kCLOCK_ClockNeededAll 的常量覆盖四个字段,但上述硬件规则仍生效。它表示当前域在所定义模式下需要该时钟,不等于“强制所有核永久打开”,也不保证 A53 不能修改共享 PLL、root、复位或电源域。
原 BOARD_RdcInit() 涉及的门控应按用途理解:
| 门控 | 原例程用途 |
|---|---|
| IOMUX、IPMUX1/2/3 | 引脚及外设互连访问 |
| QSPI | 仅在 FLASH_TARGET 构建下启用 |
| SYSTEM PLL1/2/3 | 为相关时钟源建立当前域需求 |
| AUDIO PLL1/2、VIDEO PLL1 | 为音频/视频相关时钟源建立当前域需求 |
共享外设由 M7 使用时,Linux 还需在设备树、驱动、时钟和电源管理中遵守同一所有权安排。仅把某个 Linux 节点禁用,也不自动完成其所有上游时钟的跨核协调。
4. 逐项理解 BootClockRUN
跳转到“4. 逐项理解 BootClockRUN”原代码来自板级示例,主要动作如下。下表频率依赖 SYSTEM PLL1 实际为 800 MHz、示例 PLL 参数与电源/速度等级允许该配置。
| 步骤 | 示例动作 | 需要核对的条件 |
|---|---|---|
| 1 | AHB、M7 root 临时切到 24 MHz | 这是共享系统变更,不能默认对 Linux 无影响 |
| 2 | 保留已注释的 SYSTEM PLL2/3 初始化 | ROM/U-Boot 已使用这些 PLL 时,重复配置可能使 SoC 失去时钟 |
| 3 | AUDIO PLL1/2 配置为约 393.216/361.2672 MHz | 音频 PLL 也可能正被 A53 或其他外设使用 |
| 4 | M7 root 选择 PLL1,PRE/POST 均为 1 | 目标为 800 MHz;须满足器件工作点约束 |
| 5 | AHB 选择 PLL1/6,PRE/POST 均为 1 | 约 133.333 MHz;固定除以 6 已包含在 MUX 输入中 |
| 6 | Audio AHB 选择 PLL1,POST=2 | 400 MHz,不是 AUDIO PLL1 输出 |
| 7 | UART4 选择 PLL1/10,PRE/POST 均为 1 | 80 MHz 是 UART 输入时钟,串口波特率仍由 UART 分频决定 |
| 8 | 打开 RDC、OCRAM、Audio 以及 SIM、Debug、DRAM 等门控 | 服务于例程的工作与低功耗假设,不表示每个应用都需全部启用 |
| 9 | 请求 AUDIOMIX 上电并完成 NoC 握手 | 核对 GPC 所有权和现有电源状态,等待应有失败处理 |
| 10 | 配置 AUDIOMIX 内的 SAI PLL | 此 SDK 版本先检查 CLKEN1.PLL;原笔记直接调用遗漏了条件 |
| 11 | SystemCoreClockUpdate() | 更新软件记录的内核频率,不改变外设时钟,也不会测量晶振 |
CLOCK_SetRootDivider() 的两个参数是实际除数,分别 1~8、1~64;对应 getter 也已返回实际除数。PLL 的 postDiv 编码则表示 ,不能把两种参数含义混用。
分数 PLL 使用:
示例 AUDIO PLL1 参数为 mainDiv=262,dsm=9437,preDiv=2,postDiv=3;AUDIO PLL2 与 SAI PLL 为 361,17511,3,3,参考均为 24 MHz。由于分数系数与晶振误差,393.216 MHz 和 361.2672 MHz 是目标/近似值。前者除以 8192 对应 48 kHz,后者对应 44.1 kHz,实际音频链仍需要 SAI 和 codec 的完整时钟配置。
原注释中的可选 QSPI“400 MHz”也只是 PLL1/2 的算术值,不能据此设置 flash 总线:应分别核对该 root、控制器、flash、板级布线及数据采样的限值。
AUDIOMIX 上电的四步含义
跳转到“AUDIOMIX 上电的四步含义”例程先在 PGC_CPU_M7_MAPPING 建立映射,再设置 PU_PGC_SW_PUP_REQ 的 AUDIOMIX 请求并等待自清;然后设置 PU_PWRHSK 中的 NoC 电源请求信号,等待对应 ACK。GPC 负责电源序列,CCM 门控负责时钟,这两层不能互相替代。
原例程使用无期限轮询。产品代码需要限定等待并输出故障位置;超时后不能继续访问未上电模块。Linux 运行期间重新映射电源域、请求上电或改 SAI PLL,须先协调双方所有权。不能把 SYSTEM PLL 列为“不可动”,却把 Audio PLL、AHB 或 AUDIOMIX 列为“随时可改”。
启动顺序与项目函数名
跳转到“启动顺序与项目函数名”原工程意图是先初始化内存属性与资源域,再准备引脚和时钟,最后启用调试串口。具体启动代码还需保证向量表、栈和 C 运行时已经就绪,以及访问 RDC/CCM 等模块所需的基础时钟和权限已经可用。
FL_BOARD_BootClockRUN() 与 BOARD_BootClockRUN() 是两个不同的 C 标识符。若实现采用前者,调用处也要调用前者,或明确提供包装。不能只改注释,就声称调用到了已经修改过的函数。
5. 微秒延时的计数边界
跳转到“5. 微秒延时的计数边界”SDK_DelayAtLeastUs() 是忙等:占用当前核等待,不会让出 RTOS 任务。未启用 SDK_DELAY_USE_DWT 时,该版本使用按内核类型估算循环次数的汇编循环;指令吞吐、缓存和总线状态都会影响结果。启用 DWT 后,用 CYCCNT 计数更可解释,但仍不等于精确的引脚定时。
SDK 用 USEC_TO_COUNT(us,hz) 计算周期数,并断言结果不超过 UINT32_MAX。1 GHz 下原注释的最大整数微秒数 4294967 μs,约 4.295 s 是这个转换上限,不能写成任意长延时。关闭 assert 也不会放宽计数器范围。
此版本 DWT 分支将当前计数加到目标周期数上,跨界时减去 UINT32_MAX。32 位计数器的模数实际是 ,因此这个减法存在一个计数的边界偏差。源码还设置 DEMCR.TRCENA、检查 NOCYCCNT 并使能 CYCCNTENA;这说明计数器的可用性是前提,不能只有循环比较。
无符号差值 (uint32_t)(now-start) 可以处理单次回绕。下面把周期上限限制为 INT32_MAX,并用向上取整避免非整数周期被截短;只有轮询足够频繁、计数器持续运行且频率稳定时,才可据此判断经过了足够周期。中断、抢占和调试暂停都可能延长实际等待;超过计数器可辨别范围的停顿会丢失经过了几圈的信息。
#include <stdbool.h>#include <stddef.h>#include <stdint.h>#include <limits.h>#include "fsl_device_registers.h"
bool m7_delay_cycle_count(uint32_t us, uint32_t hz, uint32_t *cycles){ if (cycles == NULL || hz == 0U) { return false; } uint64_t count = ((uint64_t)us * hz + 999999U) / 1000000U; if (count > INT32_MAX) { return false; } *cycles = (uint32_t)count; return true;}
bool m7_delay_us_checked(uint32_t us, uint32_t hz, uint32_t poll_limit){ uint32_t cycles; if (!m7_delay_cycle_count(us, hz, &cycles)) { return false; } if (cycles == 0U) { return true; } if (poll_limit == 0U || (DWT->CTRL & DWT_CTRL_NOCYCCNT_Msk) != 0U) { return false; } CoreDebug->DEMCR |= CoreDebug_DEMCR_TRCENA_Msk; DWT->CTRL |= DWT_CTRL_CYCCNTENA_Msk; uint32_t start = DWT->CYCCNT; while (poll_limit-- != 0U) { if ((uint32_t)(DWT->CYCCNT - start) >= cycles) { return true; } } return false;}poll_limit 限制循环次数,避免 CYCCNT 停止时一直等待;它不是微秒超时。返回 false 时可能已经等待了一部分时间,调用者必须处理失败。函数没有把 CYCCNT 清零,以免破坏其他使用它的测量。已有性能统计、调试器和电源管理仍需统一管理 DWT。
在 800 MHz 下,这个接口因半范围限制最多接受 2684354 μs;实际应只用于短时等待,并确保任意两次有效采样间隔小于半个回绕周期。周期性触发 ADC、精确脉宽和长时任务等待应分别采用硬件定时器/PWM、硬件事件链或 RTOS 时间接口。
参考资料
跳转到“参考资料”- NXP《i.MX 8M Plus Applications Processor Reference Manual》,Rev.3,CCM、RDC、GPC 与 Cortex-M7 相关章节。
- NXP EVK 内存属性与 RDC 初始化
- NXP EVK 完整时钟配置与 PLL 参数
- NXP DDR 链接脚本
- NXP ARM 公共驱动中的 DWT 与延时实现
- Arm CMSIS 5.9.0 的 Armv7 MPU 接口