4. STM32MP双核启动与固件加载机制
STM32MP1 系列内部集成 Cortex-A7 与 Cortex-M4 两个异构内核。上电后,Cortex-A7 侧由 BootROM 引导,经 TF-A、U-Boot 加载 Linux 内核;而 Cortex-M4 侧并不参与这条主启动链,它的固件由 Linux 运行起来之后,通过 remoteproc 框架按需加载。理解这一"先 A 后 M、由 A 控 M"的时序关系,是掌握 OpenAMP 通信能否建立的前提——只有 M4 固件被正确加载并启动,RPMsg 通道才会出现。
一、Cortex-M 固件的三种加载方式
在 STM32MP 平台上,M4 固件进入内存并运行主要有三种途径,适用场景各不相同。
1. remoteproc 动态加载(最常用)。把编译好的固件(通常是 elf 或 stm32 格式)放到 Linux 根文件系统的 /lib/firmware/ 目录下,由内核的 remoteproc 驱动读取、解析并写入 M4 可访问的 RAM 区域,然后释放复位、启动内核。这种方式灵活,可在运行时更换固件,是 OpenAMP 开发的标准做法。
2. U-Boot 阶段预加载。在 U-Boot 中通过 ext4load 或 fatload 把固件搬到指定 DDR 地址,并配置 rproc 相关环境变量,让内核启动时直接接管已就位的固件。适合固件较大、希望缩短 Linux 启动后等待时间的场景。
3. 固化到 Flash 并由 M4 自启动。部分配置下 M4 可在 A7 之前独立运行,但此时无法使用依赖 Linux 侧 vring 的 RPMsg,一般只用于纯裸机控制任务。
三种方式中,只有前两种能与 OpenAMP 协同工作,因为它们最终都由 Linux 的 remoteproc 框架统一管理。
二、remoteproc 控制接口
remoteproc 把每个远程处理器抽象成一个设备,用户空间通过 sysfs 与之交互。STM32MP1 上 M4 对应的节点通常是 /sys/class/remoteproc/remoteproc0/,关键文件有三个:
state:只读,反映当前状态,取值为offline、running、suspended等。firmware:可写,指定要加载的固件文件名(相对/lib/firmware/)。name:只读,显示该 remoteproc 实例的名字,如m4。
控制动作通过向 state 写入字符串完成:写 start 触发加载并启动,写 stop 停止并释放资源。下面是一段典型的操作序列:
# 查看当前状态
cat /sys/class/remoteproc/remoteproc0/state
# offline
# 指定固件
echo openamp_cm4.elf > /sys/class/remoteproc/remoteproc0/firmware
# 启动
echo start > /sys/class/remoteproc/remoteproc0/state
# 再次查看,应变为 running
cat /sys/class/remoteproc/remoteproc0/state
# running
# 停止
echo stop > /sys/class/remoteproc/remoteproc0/state
启动成功后,内核日志(dmesg)会出现类似 remoteproc remoteproc0: powering up m4 与 remoteproc remoteproc0: remote processor m4 is now up 的信息。若固件里包含 RPMsg 资源表,还会看到 virtio 设备注册的打印,随后 /dev/ 下出现 ttyRPMSG0 之类的字符设备节点——这正是 OpenAMP 通道就绪的标志。
三、启动、停止与复位流程
一次完整的启动并非简单"写 start",其内部经历若干阶段,可用下面的信号链来理解:
需要特别注意的是"复位":remoteproc 没有单独的 reset 文件,复位等价于先 stop 再 start。这一过程会销毁原有的 vring 与 RPMsg 端点,A7 侧此前打开的 /dev/ttyRPMSG0 文件描述符随之失效,必须重新 open 才能继续通信。很多"通信突然中断"的问题,根源就是 M4 侧异常复位后 A7 侧没有重新建立通道。
四、设备树配置要点
remoteproc 能否找到 M4、能否正确划分内存,取决于设备树。核心节点是 m4_rproc,关键属性包括:
compatible = "st,stm32mp1-m4":匹配 STM32MP1 的 M4 remoteproc 驱动。memory-region:引用 reserved-memory 中为 M4 预留的区域,通常包含固件代码段、数据段和 vring 共享内存三部分。resets与clocks:分别指向 M4 的复位控制器和时钟,驱动靠它们完成释放复位与使能时钟。st,syscfg-*:用于配置 M4 的启动地址、向量表偏移等,必须与固件链接脚本一致。
一个简化的 reserved-memory 片段如下,注意 vring 区域必须与固件资源表中声明的地址严格对应,否则会出现"固件已 running 但 RPMsg 节点不出现"的现象:
reserved-memory {
#address-cells = <1>;
#size-cells = <1>;
ranges;
mcuram2: mcuram2@10000000 {
reg = <0x10000000 0x40000>;
no-map;
};
vdev0vring0: vdev0vring0@10040000 {
reg = <0x10040000 0x1000>;
no-map;
};
vdev0vring1: vdev0vring1@10041000 {
reg = <0x10041000 0x1000>;
no-map;
};
vdev0buffer: vdev0buffer@10042000 {
reg = <0x10042000 0x4000>;
no-map;
};
};
state 写入 start 后立即回到 offline,优先检查固件格式是否被驱动识别、memory-region 是否覆盖了固件实际加载地址;若 state 为 running 但无 rpmsg 节点,则重点核对 vring 地址与资源表是否一致。五、判断加载是否成功的依据
综合来看,一次成功的 M4 固件加载应同时满足:state 显示 running;dmesg 中出现 "remote processor m4 is now up";/dev/ 下出现对应的 rpmsg 字符设备;A7 侧打开该设备后能收到 M4 发来的首帧数据。四者缺一,说明加载链在某个环节中断,可依据上面的信号链逐段排查。掌握这条从设备树到 rpmsg 节点的完整路径,后续 OpenAMP 通信的调试就有了明确的定位坐标。