由于MP157是一款多核异构的芯片,其中既包含的高性能的A7核及实时性强的M4内核,那么这两种处理器在工作时,怎么互相协调配合呢? 这就涉及到了核间通信的概念了。
IPCC (inter-processor communication controller)用于处理器间的数据交换的通知。 它提供了一种非阻塞的信号机制,并提供原子的方式进行信号发布和信息检索。 注意,核间通信的共享内存缓冲区是在MCU的SRAM中分配的,它不是IPCC外设的一部分。
IPCC外设提供了硬件支持,来管理两个处理器实例之间的处理器间通信。每个处理器拥有特定的寄存器区域和中断。 有点像硬件信号量的功能。
IPCC提供了六个双向通道信号。每个通道分为两个子通道,每个子通道提供从“发送方”处理器到“接收方”处理器的单向信号:
P1_TO_P2子通道(从P1发到P2)
P2_TO_P1子通道(从P2发到P1)
子通道中包括如下功能:
一个标志位,用于标识通道正在被占用和空闲的两种状态,这个标志被“发送方”处理器设置为被占用,并被“接收方”处理器清除。
两个相关的中断(所有通道都共享):
RXO: RX通道被占用,连接到“接收器”处理器。
TXF: TX通道空闲,连接到“发送”处理器。
带多路复用的中断掩码功能。
IPCC支持以下信道的操作模式:
单工通信方式:
仅使用一个子信道。
单向消息:“发送者”处理器将通信数据发布到内存中后,它将通道状态标志设置为已占用。当消息被处理时,“接收者”处理器清除该标志。
半双工通讯方式:
仅使用一个子信道。
双向消息:“发送者”处理器将通信数据发布到内存中后,它将通道状态标志设置为已占用。当消息被处理并且响应在共享内存中可用时,“接收器”处理器将清除该标志。
全双工通讯方式:
子通道用于异步模式。
通过将子通道状态标志设置为占用,任何处理器都可以异步发布消息。当消息被处理时,“接收者”处理器清除该标志。可以将这种模式视为给定通道上两个单工模式的组合。
核间通信的模型如下:
IPCC作为核间通信的桥梁,它仅承担着通知的角色,负责消息的分发、中断的处理等。
实际上,IPCC外设这个角色只是多核异构核间通信中的一块,在我们使用多核异构核间通信时,往往不仅希望使用到核间的消息通知,还希望能在不同的核心中进行数据的交互(比如M4核进行实时的AD数据采集处理,完成后,M4核可通过异构的框架将数据呈递给A7核,A7核再进行更复杂的应用)。那么在这个需求的驱动下,就出现了一些框架相互配合使用的情况了,下面我们就给大家介绍这些内核框架。
远程处理器框架(RPROC、RemoteProc)允许不同的平台/体系结构控制(打开电源,加载固件,关闭电源)远程处理器,同时抽象出硬件差异。此外,它还提供监视和调试远程协处理器的服务。
以MP157为例,其RemoteProc可分为两块,分别是A7核端、M4核端:
remoteproc:这是远程处理器框架的通用部分(在MP157中为A7核端)。
它的作用是:
将ELF固件加载到远程处理器内存中。
解析固件资源表以设置关联的资源(例如IPC,内存分割和跟踪)。
控制远程处理器的执行(启动,停止…)。
提供监视和调试远程固件的服务。
stm32_rproc:这是远程处理器平台驱动程序(在MP157中为M4核端)。
它的作用是:
将stm32特定的功能(回调)注册到RPROC框架。
处理与远程处理器关联的平台资源(例如寄存器,看门狗,复位,时钟和存储器)。
通过邮箱框架将通知(通知)转发到远程处理器。
ST官方参考资料:
Linux RPMsg framework overview - stm32mpu
此小节为大家简述有关Linux RPMsg框架的内容。RPMsg框架是一个基于virtio的消息总线,它允许本地处理器与系统上可用的远程处理器通信。
此框架在多核异构中承担的角色如下图:
Linux RPMsg框架是在virtio框架顶层上实现的消息传送框架,其用于主机和远程处理器进行通信。它基于virtio vring,可通过共享内存向远程CPU发送消息或从远程CPU接收消息。
这些vring是单向的,一个vring专用于发送到远程处理器的消息,另一个vring用于从远程处理器接收的消息。此外,共享缓冲区需要在两个处理器都可见的内存空间中创建。
当新消息在共享缓冲区中等待时,会使用到另一个框架 Linux Mailbox framework
,该框架将用于通知对应的Core。
依靠这些框架,RPMsg框架实现了基于不同通道的通信。通道可被文本名称标识,并有一个本地(“源”)的RPMsg地址和一个远程(“目的”)的RPMsg地址。
在远程处理器端(MP157则为M4核),也必须使用RPMSG框架。RPMSG框架的实现存在几种解决方案,ST建议使用OpenAMP方案,并在SDK中给出了示例。
Github OpenAMP框架 .
简单来说,MP157的A7核与M4核,通过一个标准的RPMsg框架来建立起联系,完成数据传递。
具体原理可以参考:
RPMsg-Messaging-Protocol .
RPMsg-Communication-Flow .
Linux内核源码目录给出的rpmsg client的示例代码位置如下:
samples/rpmsg/rpmsg_client_sample.c
rpmsg框架Linux内核驱动源码位于:
drivers/rpmsg
ST官方参考资料:
Linux remoteproc framework overview - stm32mpu
此小节为大家简述有关Linux邮箱框架的内容。邮箱框架涉及异构多核系统的处理器间通信。
此框架的结构如下图:
邮箱框架被用于内核间进行消息或信号的交换,常用于主机和协处理器间。邮箱由以下模块组成:
一个邮箱控制器(mailbox controller),依赖于硬件平台实现,比如MP157的IPCC外设:
它负责配置和处理来自IPCC外围设备的IRQ。
它为邮箱客户端提供了通用API。
一个邮箱客户端(mailbox client),负责发送或接收消息。
关于此框架的权威描述,在内核文档中的如下目录:
Documentation/mailbox.txt
一般而言mailbox controller和client都由芯片厂商来负责实现,因为这依赖于外设。 我们更常关注的,则是mailbox client的创建和使用。
ST实现的mailbox client代码位置如下:
drivers/remoteproc/stm32_rproc.c
在内核中还给出了一份mailbox client的示例驱动代码, 代码通过debugfs子系统,将mailbox的操作暴露给了用户空间, 用户可以直接通过debugfs来使用mailbox,进行消息在不同内核中的传递。
mailbox框架的设备树描述可参考内核源码文档:
Documentation/devicetree/bindings/mailbox/mailbox.txt
一个简单的mailbox client设备树节点,可以参考内核源码目录:
Documentation/devicetree/bindings/mailbox/sti-mailbox.txt
内核源码目录给出的mailbox client的示例代码位置如下:
drivers/mailbox/mailbox-test.c
ST官方参考资料:
Linux Mailbox framework overview - stm32mpu
前面介绍了三个框架,它们并不是独立工作的,而是相互协调的,彼此关联。 我们可以通过两张图来查看它们之间的关系。
以RemoteProc框架为主视角出发:
可以理清三个框架的关系,RemoteProc可以说是骨架,关联到了RPMsg框架、Mailbox框架。
设备树节点位于arch/arm/boot/dts/stm32mp157c.dtsi
IPCC设备树节点
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
ipcc: mailbox@4c001000 { compatible = "st,stm32mp1-ipcc"; #mbox-cells = <1>; reg = <0x4c001000 0x400>; st,proc-id = <0>; interrupts-extended = <&intc GIC_SPI 100 IRQ_TYPE_LEVEL_HIGH>, <&intc GIC_SPI 101 IRQ_TYPE_LEVEL_HIGH>, <&exti 61 1>; interrupt-names = "rx", "tx", "wakeup"; clocks = <&rcc IPCC>; wakeup-source; power-domains = <&pd_core>; status = "disabled"; }; |
使用节点位于arch/arm/boot/dts/stm32mp157a-basic.dts
使用IPCC设备树节点
1 2 3 |
&ipcc { status = "okay"; }; |
设备树中的compatible = “st,stm32mp1-ipcc”属性,会匹配到 drivers/mailbox/stm32-ipcc.c
驱动程序,驱动程序中会创建一个mbox controller。
设备树节点位于arch/arm/boot/dts/stm32mp157c.dtsi
rproc设备树节点
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
m4_rproc: m4@0 { compatible = "st,stm32mp1-rproc"; #address-cells = <1>; #size-cells = <1>; ranges = <0x00000000 0x38000000 0x10000>, <0x30000000 0x30000000 0x60000>, <0x10000000 0x10000000 0x60000>; resets = <&rcc MCU_R>; reset-names = "mcu_rst"; st,syscfg-pdds = <&pwr 0x014 0x1>; st,syscfg-holdboot = <&rcc 0x10C 0x1>; st,syscfg-tz = <&rcc 0x000 0x1>; st,syscfg-rsc-tbl = <&tamp 0x144 0xFFFFFFFF>; status = "disabled"; m4_system_resources { compatible = "rproc-srm-core"; status = "disabled"; }; }; |
使用节点位于arch/arm/boot/dts/stm32mp157a-basic.dts
使用rproc设备树节点
1 2 3 4 5 6 7 8 9 10 11 12 |
&m4_rproc { memory-region = <&retram>, <&mcuram>, <&mcuram2>, <&vdev0vring0>, <&vdev0vring1>, <&vdev0buffer>; mboxes = <&ipcc 0>, <&ipcc 1>, <&ipcc 2>; mbox-names = "vq0", "vq1", "shutdown"; interrupt-parent = <&exti>; interrupts = <68 1>; interrupt-names = "wdg"; wakeup-source; recovery; status = "okay"; }; |
设备树中的compatible = “st,stm32mp1-rproc”属性,会匹配到 drivers/remoteproc/stm32_rproc.c
驱动程序,驱动程序中会创建一个mbox client,并基于RemoteProc、RPMsg框架与mbox client进行关联。
这里我们就简单讲解一下M4核端的代码和一些概念,更详细的内容则需大家自己研究了。
rpmsg框架下有通信端点的概念,数据在两个端点间传输。端点间的数据传输是rpmsg框架下数据传输最原始的形式, 我们可以在原始的数据传输形式上再做一层封装,抽象出一些特定类型的设备。
每个端点注册的底层实现,就是一个内核设备的注册(使用的是平台总线模型),故注册的端点设备, 可以利用到驱动的Probe功能(具体实现详见 drivers/rpmsg/rpmsg_core.c
300行后内容)。
在M4端,通过调用openamp库中的 OPENAMP_create_endpoint
函数,并在调用时指定参数name(即为设备名称), 即可在内核中注册一个对应的rpmsg框架平台设备,该设备最终可以通过name(设备名称)来匹配到相应的A7端内核驱动:
所以Linux rpmsg框架下使用平台总线模型与端点通讯的方式结合,给一些需要有特殊操作的自定设备, 提供了支持的可能。比如异构间的通讯,可以封装成串口通讯模型。
在我们提供的M4内核固件的代码中,注册了两种Linux内核自带的rpmsg框架下, 原生支持的设备模型,这两种设备类型是rpmsg-tty-channel、rpmsg-client-sample:
rpmsg-tty-channel: tty终端设备,对应内核驱动源码 drivers/rpmsg/rpmsg_tty.c
,此驱动模块默认被编译进内核。
rpmsg-client-sample: 框架原生的通讯方式测试设备(放在内核里作为演示该框架的Demo提供的),对应内核驱动源码 samples/rpmsg/rpmsg_client_sample.c
,此驱动默认被编译成模块, 并放置在文件系统 /lib/modules/4.19.94-stm-r1/kernel/samples/rpmsg/rpmsg_client_sample.ko
中, 当设备与驱动发生匹配时,系统会自动insmod该驱动模块。
还有一种字符设备模型,rpmsg_chrdev,源码位于 drivers/rpmsg/rpmsg_char.c
,我们的代码中未实验,可自行研究。
M4核的代码中,创建上述两个rpmsg设备的代码如下:
rpmsg-client:rpmsg-client设备的示例,使用的是rpmsg框架下原生的异构通讯方式,调用openamp的原生操作函数,比如OPENAMP_send、receive等即可通讯。 在注册设备时,传入的 RPMSG_SERVICE_NAME
即为 rpmsg-client-sample
,故会在Linux系统中注册一个名为rpmsg-client-sample的设备, 并且会自动匹配对应名称的内核模块(前面有述)。
rpmsg-tty:rpmsg-tty的示例,则在原生的通讯方式上,注册成了tty设备的模型,并将rpmsg的通讯封装成了tty的通讯形式, 更符合串口通信的操作、方便使用,比如在M4核端,就有VIRT_UART_Transmit的串口发送函数, rpmsg-tty设备注册的设备文件,会映射到A7核端的Linux文件系统下的 /dev/ttyRPMSGx
。
在M4核的代码中,还初始化了usart3作为M4内核的Log输出串口,我们可以通过串口模块接入开发板上的usart3,来查看M4内核输出的Log。 最终工程代码会被用于生成ELF固件,ELF固件即为程序,会运行在MP157的M4内核上。
综上,通过原生的rpmsg框架设备、 /dev/ttyRPMSGx
节点以及M4内核使用的usart3资源,我们就可以进行简单的实验了。 本实验的代码也比较简单,这里就讲解这么多。
由于多核异构的框架是与处理器的架构紧密联系在一起的,所以一般这些框架驱动会由芯片厂商为我们提供好。 野火MP157开发板默认开启了这些驱动支持,并且开启了对应的设备树,我们直接进行使用就可以了。
在前面我们提到了,M4内核要与A7内核通讯需要共用一个框架,那么M4内核的运行的程序里, 就需要有对应的框架代码,这个为大家提供的工程中已经包含。 最终我们将代码生成的ELF固件,通过A7内核的remoteproc子系统加载到M4内核上, 即可做好前期的准备工作。
生成ELF固件的工程代码位于 \linux_driver\framework_ipcc\STM32Cube_FW_MP1_V1.2.0\Projects\STM32MP157C-EV1\Applications\OpenAMP\OpenAMP_raw
目录下, 感兴趣可自行研究,工程可用MDK或CubeIDE打开(在工程目录中由对应文件夹)。
MP157-M4内核的使用可参考:
[野火]Cortex-M4内核开发实战指南-基于STM32MP157
重要
在M4核的代码中,还初始化了usart3,实验前请务必将usart3的设备树插件关闭。
M4核的固件我们已经成功编译并放在了/linux_driver/framework_ipcc目录下, 我们将M4核的固件 OpenAMP_raw_CM4.elf
上传至Linux文件系统的 /lib/firmware/
目录。此目录存放着Linux系统中会使用到的各种固件。
执行如下命令指定M4内核加载的固件,默认在root用户下操作:
# 进入remoteproc子系统目录 cd /sys/class/remoteproc/remoteproc0 # 导入M4内核固件名称 echo OpenAMP_raw_CM4.elf > firmware
在同一目录下,执行如下命令可启动停止M4内核:
# 启动M4内核 echo start > state # 停止M4内核 echo stop > state
启动M4内核后信息如下:
M4内核加载固件并启动后,在串口终端中打印出了一些信息,我们通过串口模块接入usart3引脚, 再打开串口调试助手设置波特率为115200,可以看到M4固件初始化的usart3作为串口printf出来的信息, 为 [INFO ]M4 send to A7 : hello world!
, 并且A7端的驱动也打印出了 rpmsg_client_sample virtio0.rpmsg-client-sample.-1.0: incoming msg 1 (src: 0x0)
说明M4核及A7核驱动正常工作了。
此外,输入lsmod,我们还可以看到演示设备创建后,对应被动态加载的驱动模块, rpmsg_client_sample
:
下面,我们进行第二个设备测试,通过前面现象中的LOG,我们可以看到被枚举出的tty设备节点 /dev/ttyRPMSG0
节点, 我们就通过该节点测试tty设备的功能,输入如下命令:
echo "hello M4 core , i'm A7!" > /dev/ttyRPMSG0
实验现象如下所示:
上图为A7通过虚拟的tty终端设备,转发到M4内核的消息内容, 最终通过M4核固件的串口Log功能打印出来对应信息。
自此,所有实验结束。