ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

ZYNQ MPSoC双核通信实战:OpenAMP配置与避坑指南

ZYNQ MPSoC双核通信实战:OpenAMP配置与避坑指南 1. 项目概述为什么要做ZYNQ MPSoC双核通信很多人第一次接触ZYNQ UltraScale MPSoC都会被它“多核异构”的标签吸引——4核Cortex-A53、2核Cortex-R5F、Mali GPU、FPGA逻辑听起来非常强大。可真到了项目里大多数人遇到的第一道坎不是逻辑设计而是“这么多核到底怎么让它们协作干活”我最初接触这块芯片时天真的想法是“A53上跑LinuxR5F上跑裸机程序两边通过共享内存互相传数据这不就行了”结果真动手才发现光是把R5F的程序跑起来Linux就崩溃了偶尔跑起来了两个核之间数据传着传着就丢了。折腾了一周最后发现全部问题都指向同一个东西——OpenAMP。如果你现在也卡在双核通信上或者正准备在MPSoC上做AMP架构开发这篇文章可以帮你少走很多弯路。文章里我不会只贴命令凡是涉及关键参数的地方都会把“为什么要这么配”讲清楚。工程上用到的环境是Vivado 2018.3 PetaLinux 2018.3对应的OpenAMP组件是Xilinx官方定制的2018.3版本这也是目前网上能找到教程最多、坑解决方案最全的一个组合。2. 方案选型为什么用OpenAMP而不是自己写共享内存2.1 OpenAMP到底是什么OpenAMPOpen Asymmetric Multi-Processing是一套开源的双核通信框架Xilinx在SDK和PetaLinux里做了深度定制让它能适配ZYNQ和MPSoC的硬件结构。它主要干了三件事第一件事是生命周期管理。A53核可以通过固件接口去控制R5F的启动、停止和复位这个在调试阶段特别重要——你可以像Linux管理进程一样把R5F上跑的程序拉起来或者杀掉。第二件事是 RPMsg 通信协议。RPMsg定义了一套基于共享内存中断的数据传输规范协议层帮你处理了缓冲区管理、消息边界、多端点通信这些麻烦事你只需要调接口收发数据。第三件事是共享内存管理。OpenAMP会在内存里提前规划好几个区域一部分放通信缓冲区vring一部分放实际数据还有一部分放消息状态。这些区域的物理地址在Linker Script里写死双方不能越界。自己写共享内存也不是不行我最早就是这么干的。简单场景下用几个寄存器加一块固定内存确实能跑。但一旦涉及多个消息类型、不定长数据、双核并发访问你很快就会陷入“锁竞争、数据覆盖、Cache一致性问题”的泥潭。OpenAMP把这些通用问题全部解决掉了你还自己造什么轮子。2.2 2018.3版本为什么是“黄金版本”实际上OpenAMP在Xilinx工具链里的集成度是逐步完善的2017.4版本的RPMsg功能还不完整2019.1之后的改动又比较大网上教程大多对不上号。2018.3正好处在一个“功能成熟、资料充足、坑基本都被踩平”的节点上。从芯片选型角度来说如果你用的是ZCU102、ZCU104这类官方开发板2018.3版本的PetaLinux BSP直接支持不用自己修改设备树。如果用的是自己画的板子参考官方BSP改起来也容易。还有个关键点是编译工具链。2018.3版本的PetaLinux自带的交叉编译工具链是GCC 7.3编译R5F裸机程序的SDK工具链是aarch64-none-elf这两个版本匹配度很好不会出现“新工具链编译老库导致ABI不兼容”的玄学问题。2.3 AMP架构下的任务划分思路用OpenAMP做双核通信核心前提是两个核跑不同的系统、干不同的活。以我实际项目为例A53核运行PetaLinux负责网络通信、文件系统、用户交互这些“重活”。R5F核运行裸机程序负责实时性要求高的数据采集和IO控制。两个核各有分工通过OpenAMP交换数据和命令。这个方案最大的优势在于实时性。A53上跑Linux中断响应和任务调度都有不确定性但R5F是裸机中断响应时间可以做到微秒级别。很多工业控制场景对实时性要求极高把控制任务全部放到R5F上A53只做“后台管理”这样既享受了Linux的生态又保证了实时性。3. 环境准备与工程搭建3.1 硬件平台和工具链版本我使用的硬件平台是ZCU102开发板。如果你们用的是其他MPSoC板卡操作流程完全一致只是设备树和引脚配置需要相应调整。工具链版本严格对应如下不建议混用Vivado 2018.3用于硬件工程创建和比特流生成PetaLinux 2018.3用于Linux系统构建、设备树生成、rootfs定制Xilinx SDK 2018.3用于R5F裸机程序开发有个容易踩的坑是安装路径。Vivado、SDK、PetaLinux三者的安装路径里不能有中文和空格否则后续编译一定会出问题。我第一次装在了Windows分区挂载的目录下结果PetaLinux构建到一半报错查了半天发现是路径里有个空格。3.2 Vivado硬件工程中的关键配置打开Vivado 2018.3新建工程选择ZCU102板卡。在Block Design里添加ZYNQ UltraScale MPSoC IP核需要修改以下几个关键配置。PS侧配置里需要启用RPUR5F的Core0和Core1两个核都设置为“Split Mode”拆分模式即每个核独立运行各自的程序。这里有个细节如果设置为Lock-Step模式两个R5F核会运行相同的程序并互相校验这种模式适合安全关键应用但OpenAMP双核通信需要的是两个核跑不同程序所以必须选Split Mode。内存配置方面我给R5F分配了DDR的一部分区域同时使能了TCMTightly Coupled Memory。TCM是R5F独有的紧耦合内存访问延迟极低适合放中断向量和关键数据。在实际分配中我把R5F的程序放在DDR中但把中断向量表放在TCM里这样中断响应速度会快很多。另外必须勾选“Enable RPMsg”相关选项。在Zynq UltraScale MPSoC IP核配置界面的“PS-PL Configuration”里确保有OpenAMP相关的硬件支持主要是中断信号的连接。还有一个硬件配置容易被忽略必须正确设置DDR的地址映射。ZCU102的DDR起始地址是0x00000000大小根据板卡实际DDR容量填写。如果地址映射错了后续Linux起来后发现R5F的程序加载不进去折腾半天。3.3 PetaLinux工程配置与设备树修改Vivado导出硬件后开始创建PetaLinux工程。petalinux-create -t project -n openamp_demo --template zynqMP cd openamp_demo petalinux-config --get-hw-description/path/to/hdf这里那个HDF文件是Vivado导出的硬件描述文件包含了整个硬件系统的配置信息。接下来需要默认配置PetaLinux让它支持OpenAMP。默认配置里OpenAMP相关功能可能没有完全启用需要手动确认。petalinux-config -c kernel在内核配置界面中需要确保以下选项已经启用Device Drivers - Mailbox Hardware Support - ZynqMP MailboxDevice Drivers - Remoteproc - ZynqMP remoteproc supportDevice Drivers - Rpmsg - Virtio RPMsg这三个配置缺一不可。Mailbox是硬件邮箱用于核间中断Remoteproc负责管理R5F生命周期Rpmsg则是实际的数据传输通道。设备树也需要重点修改。PetaLinux会自动生成一个默认设备树你需要检查或添加remoteproc节点。设备树文件路径在project-spec/meta-user/recipes-bsp/device-tree/files/system-user.dtsi。remoteproc0 { compatible xlnx,zynqmp-r5f; reg 0x0 0xff9a0000 0x0 0x10000; core_conf 0; #address-cells 2; #size-cells 2; ranges; memory-region rproc_0_reserved; mboxes ipi_mailbox_pmu 0, ipi_mailbox_pmu 1; mbox-names tx, rx; };memory-region指向一段预留的DDR内存这段内存在Linux里要被屏蔽掉防止系统把它分配给别的进程使用。这是OpenAMP通信的基础——两个核共享的缓冲区必须在物理地址上固定。检查设备树中是否有类似下面的内存预留节点reserved-memory { #address-cells 2; #size-cells 2; ranges; rproc_0_reserved: rproc3ed00000 { no-map; reg 0x0 0x3ed00000 0x0 0x1000000; }; };这段配置将0x3ed00000开始的16MB内存保留给R5F使用。no-map属性很关键它告诉Linux这块内存不要建立页表映射纯粹隔离出来。如果不加no-mapLinux的Cache一致性处理会干扰双核通信导致数据错乱。PetaLinux编译完成后把生成的BOOT.BIN、image.ub和rootfs烧到SD卡里Linux系统就能启动了。4. R5F端裸机程序和Linux端驱动的实现4.1 在SDK中创建R5F裸机工程打开Xilinx SDK 2018.3选择对应工程新建Application Project。处理器选择“psu_cortexr5_0”操作系统选择“standalone”。在模板选择界面直接选“OpenAMP Echo-Test”模板这是Xilinx官方提供的测试程序它会周期性地向Linux发送消息并接收Linux回传的消息。如果你不想用模板想自己从头写那么有几个核心初始化步骤是必须的#include openamp/open_amp.h #include rsc_table.h #include platform_info.h #define SHARED_MEM_PA 0x3ed00000 #define SHARED_MEM_SIZE 0x100000 #define IPI_IRQ_VECT_ID 0x9 static struct remoteproc *rproc; static struct rpmsg_device *rpdev;这里最关键的是rsc_table.h它定义了资源表包括共享内存地址、vring地址、中断号等信息。编译时这些值必须和设备树中配置的值完全一致否则通信一定失败。主函数核心逻辑如下int main() { struct rpmsg_endpoint *ept; int ret; platform_init(); rproc remoteproc_init(rproc_ops, NULL); rproc-bootaddr 0x3ed00000; ret remoteproc_start(rproc); if (ret) { xil_printf(Failed to start remoteproc\r\n); return -1; } rpdev rpmsg_virtio_create_rpmsg_device(rproc); ept rpmsg_create_ept(rpdev, NULL, 0, 0, service_cb, NULL); while (1) { // 主循环等待消息回调 platform_poll(); } return 0; }Linux端的驱动在PetaLinux启动后会自动加载。r5f remoteproc驱动会在Linux启动时尝试从共享内存地址加载R5F固件并触发R5F复位释放。4.2 Linux端应用程序如何与R5F通信Linux端通过OpenAMP的RPMsg接口可以非常方便地创建端点和收发消息。Xilinx提供了一个示例程序echo_test位于/usr/bin目录。正常启动流程是这样的Linux启动后确认remoteproc设备已经注册cat /sys/class/remoteproc/remoteproc0/name如果输出“zynqmp-r5f”说明驱动加载正常。在R5F端程序已经烧写到DDR指定地址的前提下Linux通过sysfs接口启动R5Fecho start /sys/class/remoteproc/remoteproc0/state运行echo_test程序echo_test -n 100这个命令会发送100条消息给R5FR5F收到后原样返回echo_test统计往返时延。我实测的数据是平均往返时延在6~10微秒左右对绝大多数工业场景来说完全够用。如果你想在Linux端自己写收发程序核心代码也很简洁#include openamp/rpmsg_client.h #include openamp/virtio_rpmsg_bus.h struct rpmsg_endpoint *ept; static void rpmsg_cb(struct rpmsg_endpoint *ept, void *data, size_t len, void *priv, unsigned long src) { // 处理R5F发来的消息 printf(Received: %s\r\n, (char *)data); } int main() { struct rpmsg_device *rpdev; int ret; rpdev platform_create_rpmsg_vdev(0, VIRTIO_DEV_RPMSG, NULL); ept rpmsg_create_ept(rpdev, app, RPMSG_ADDR_ANY, RPMSG_ADDR_ANY, rpmsg_cb, NULL); while (1) { // 发送消息给R5F rpmsg_send(ept, hello from linux, 17); sleep(1); } }4.3 共享内存地址和Cache一致性处理共享内存是OpenAMP通信的核心也是最容易出现问题的部分。R5F和A53访问同一块DDR物理内存但两个核的Cache策略不同很容易出现“数据写了但对方读不到”的情况。R5F端的内存访问默认是带Cache的所以在操作共享内存区域时必须调用Xilinx提供的Cache维护函数。最简单的办法是在数据读写前后显式地刷CacheXil_DCacheFlush(); // 写数据后调用确保数据写回DDR Xil_DCacheInvalidate(); // 读数据前调用确保读到的是DDR最新数据Linux端的open-amp库会自动处理Cache操作一般不需要手动干预。但如果是自己写的共享内存应用同样需要在合适的位置操作Cache。这里最容易犯的错误是把Cache操作完全关闭——虽然能通信但性能会断崖式下跌。正确做法是“按需维护Cache”只在共享内存区域操作前后做flush/invalidate。5. 常见问题与避坑指南5.1 R5F程序启动不了这是遇到最多的问题。R5F程序编译好了地址也写在Linker Script里了但Linux执行echo start state之后程序就是跑不起来。排查第一步看内核日志dmesg | tail -50如果看到“Failed to get memory region”或“Invalid resource table”之类的报错基本可以断定是设备树里的内存配置和R5F Linker Script不对齐。对照检查以下三个地址是否完全一致设备树里rproc_0_reserved的reg地址R5F工程Linker Script里的DDR段起始地址remoteproc节点的bootaddr这三个地址只要有一个不一致程序就起不来。我踩过最惨的一次坑是三个地址全部一致但Linker Script里TCM段和DDR段的地址重叠了程序链接时没报错运行后R5F直接陷入HardFault查了一整天。5.2 echo_test能通但自己的程序偶发丢数据echo_test正常不代表所有数据通信都可靠。我自己写了一个比较复杂的双向通信程序结果发现长时间运行后偶发丢包还出现过数据错位。排查方向有四个。第一检查缓冲区大小和消息长度。RPMsg单条消息的最大长度默认是496字节如果超过这个值消息会被拆分接收端需要做重组处理。OpenAMP框架内部其实做了分片但处理的不好就会丢数据。第二检查多端点冲突。OpenAMP支持多个端点同时通信但每个端点必须有独立的地址。如果两个端点地址冲突数据就会串。第三检查中断优先级。在R5F端IPI中断的优先级不能太低否则在高负载场景下中断会被屏蔽导致消息接收延迟。第四也是最隐蔽的一个问题——Cortex-R5F的浮点寄存器保存。如果你在R5F端的中断处理函数里使用了浮点运算必须在进入中断时保存FPU寄存器否则中断返回后浮点运算结果全部错乱。5.3 Linux启动阶段R5F被意外启动有些场景下R5F程序会在Linux启动过程中被自动加载执行导致两个核同时初始化造成资源冲突。原因在于PetaLinux的rootfs里有一个systemd服务会检查remoteproc状态并自动加载R5F固件。如果你不想要这个行为可以把这个服务禁用掉systemctl disable zynqmp-firmware或者在设备树中把remoteproc节点的status属性改成disabled需要时再手动启动。我习惯保留自动启动功能因为在生产环境里上电后直接让R5F程序自动跑起来往往比手动加载更可靠。5.4 常见问题速查表问题现象可能原因排查方向R5F程序启动不了内存地址不一致检查设备树、Linker Script、bootaddr三个地址echo_test不通IPI中断配置错误检查Mailbox设备树节点消息偶发丢失缓冲区不足或Cache一致性增大vring检查Cache操作数据错位端点地址冲突检查端点地址唯一性Linux卡死重启预留内存被系统占用检查DDR地址段是否与其他配置冲突5.5 项目实用建议如果你们项目打算用ZYNQ MPSoC做双核通信有几点经验可以提前参考。第一硬件设计阶段就要把DDR地址分配想清楚哪段给DDR、哪段给OCM、哪段给R5F都提前规划好。我在早期项目里就是没规划好结果R5F的程序和Linux的DMA缓冲区撞在一起两个都时好时坏排查起来极其痛苦。内存规划这东西宁可多花一天时间想清楚后面少受一周罪。第二R5F端尽量不要用浮点运算。Cortex-R5F虽然支持VFPv3但浮点运算的指令周期比定点运算长得多而且会引入FPU状态保存的麻烦。能用定点就用定点需要高精度时用Q格式或者直接上A53算。第三通信频率不要无限调高。OpenAMP的吞吐量上限受限于核间中断处理速度我曾经试着把通信频率调到10kHz以上结果就是R5F几乎一直停在中断处理上实时任务反而被耽误了。适当降低通信频率数据打包批量传输效果反而更好。第四长期运行的稳定性测试很重要。双核通信最常见的故障模式不是“立即崩溃”而是“跑几个小时/几天后偶发出错”。每次修改代码后都要做长时间压力测试至少跑到之前出错时间的两倍以上才能对稳定性有一点信心。我个人在实际操作中最深的体会是OpenAMP双核通信配置和代码其实都不复杂真正花时间的地方全在排查各种隐性问题上。地址错位、Cache不一致、中断优先级配置不当、浮点寄存器未保存……每一个问题都是那种“看代码完全没问题跑起来就是出bug”的类型。所以如果你也是刚开始接触建议严格按照官方例程先跑通一遍再逐步往里面加自己的逻辑不要一上来就搞复杂架构否则出了问题很难定位是框架的问题还是自己代码的问题。先跑通、再扩展、最后再优化这个顺序能帮你省下大量排查时间。
返回列表