8. 多核异构核间通信–ipcc

由于MP157是一款多核异构的芯片,其中既包含的高性能的A7核及实时性强的M4内核,那么这两种处理器在工作时,怎么互相协调配合呢? 这就涉及到了核间通信的概念了。

IPCC (inter-processor communication controller)用于处理器间的数据交换的通知。 它提供了一种非阻塞的信号机制,并提供原子的方式进行信号发布和信息检索。 注意,核间通信的共享内存缓冲区是在MCU的SRAM中分配的,它不是IPCC外设的一部分。

8.1. 外设简述

IPCC外设提供了硬件支持,来管理两个处理器实例之间的处理器间通信。每个处理器拥有特定的寄存器区域和中断。 有点像硬件信号量的功能。

IPCC提供了六个双向通道信号。每个通道分为两个子通道,每个子通道提供从“发送方”处理器到“接收方”处理器的单向信号:

  • P1_TO_P2子通道(从P1发到P2)

  • P2_TO_P1子通道(从P2发到P1)

子通道中包括如下功能:

  • 一个标志位,用于标识通道正在被占用和空闲的两种状态,这个标志被“发送方”处理器设置为被占用,并被“接收方”处理器清除。

  • 两个相关的中断(所有通道都共享):

    • RXO: RX通道被占用,连接到“接收器”处理器。

    • TXF: TX通道空闲,连接到“发送”处理器。

  • 带多路复用的中断掩码功能。

IPCC支持以下信道的操作模式:

  • 单工通信方式:

    • 仅使用一个子信道。

    • 单向消息:“发送者”处理器将通信数据发布到内存中后,它将通道状态标志设置为已占用。当消息被处理时,“接收者”处理器清除该标志。

  • 半双工通讯方式:

    • 仅使用一个子信道。

    • 双向消息:“发送者”处理器将通信数据发布到内存中后,它将通道状态标志设置为已占用。当消息被处理并且响应在共享内存中可用时,“接收器”处理器将清除该标志。

  • 全双工通讯方式:

    • 子通道用于异步模式。

    • 通过将子通道状态标志设置为占用,任何处理器都可以异步发布消息。当消息被处理时,“接收者”处理器清除该标志。可以将这种模式视为给定通道上两个单工模式的组合。

核间通信的模型如下:

broken

8.2. 框架简述

IPCC作为核间通信的桥梁,它仅承担着通知的角色,负责消息的分发、中断的处理等。

实际上,IPCC外设这个角色只是多核异构核间通信中的一块,在我们使用多核异构核间通信时,往往不仅希望使用到核间的消息通知,还希望能在不同的核心中进行数据的交互(比如M4核进行实时的AD数据采集处理,完成后,M4核可通过异构的框架将数据呈递给A7核,A7核再进行更复杂的应用)。那么在这个需求的驱动下,就出现了一些框架相互配合使用的情况了,下面我们就给大家介绍这些内核框架。

8.2.1. RemoteProc framework

远程处理器框架(RPROC、RemoteProc)允许不同的平台/体系结构控制(打开电源,加载固件,关闭电源)远程处理器,同时抽象出硬件差异。此外,它还提供监视和调试远程协处理器的服务。

以MP157为例,其RemoteProc可分为两块,分别是A7核端、M4核端:

remoteproc:这是远程处理器框架的通用部分(在MP157中为A7核端)。

它的作用是:

  • 将ELF固件加载到远程处理器内存中。

  • 解析固件资源表以设置关联的资源(例如IPC,内存分割和跟踪)。

  • 控制远程处理器的执行(启动,停止…)。

  • 提供监视和调试远程固件的服务。

stm32_rproc:这是远程处理器平台驱动程序(在MP157中为M4核端)。

它的作用是:

  • 将stm32特定的功能(回调)注册到RPROC框架。

  • 处理与远程处理器关联的平台资源(例如寄存器,看门狗,复位,时钟和存储器)。

  • 通过邮箱框架将通知(通知)转发到远程处理器。

ST官方参考资料:

https://wiki.stmicroelectronics.cn/stm32mpu/wiki/Linux_RPMsg_framework_overview

8.2.2. RPMsg framework

此小节为大家简述有关Linux RPMsg框架的内容。RPMsg框架是一个基于virtio的消息总线,它允许本地处理器与系统上可用的远程处理器通信。

此框架在多核异构中承担的角色如下图:

broken

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框架来建立起联系,完成数据传递。

broken

具体原理可以参考:

RPMsg-Messaging-Protocol .

RPMsg-Communication-Flow .

Linux内核源码目录给出的rpmsg client的示例代码位置如下:

samples/rpmsg/rpmsg_client_sample.c

rpmsg框架Linux内核驱动源码位于:

drivers/rpmsg

ST官方参考资料:

https://wiki.stmicroelectronics.cn/stm32mpu/wiki/Linux_remoteproc_framework_overview

8.2.3. Mailbox framework

此小节为大家简述有关Linux邮箱框架的内容。邮箱框架涉及异构多核系统的处理器间通信。

此框架的结构如下图:

broken

邮箱框架被用于内核间进行消息或信号的交换,常用于主机和协处理器间。邮箱由以下模块组成:

  • 一个邮箱控制器(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官方参考资料:

https://wiki.stmicroelectronics.cn/stm32mpu/wiki/Linux_Mailbox_framework_overview

8.2.4. 框架小结

前面介绍了三个框架,它们并不是独立工作的,而是相互协调的,彼此关联。 我们可以通过两张图来查看它们之间的关系。

以RemoteProc框架为主视角出发:

broken

可以理清三个框架的关系,RemoteProc可以说是骨架,关联到了RPMsg框架、Mailbox框架。

8.3. 设备树插件描述

8.3.1. IPCC设备树节点

设备树节点位于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。

8.3.2. A7<–>M4 rproc设备树节点

设备树节点位于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进行关联。

8.4. 实验代码简述

这里我们就简单讲解一下M4核端的代码和一些概念,更详细的内容则需大家自己研究了。

rpmsg框架下有通信端点的概念,数据在两个端点间传输。端点间的数据传输是rpmsg框架下数据传输最原始的形式, 我们可以在原始的数据传输形式上再做一层封装,抽象出一些特定类型的设备。

每个端点注册的底层实现,就是一个内核设备的注册(使用的是平台总线模型),故注册的端点设备, 可以利用到驱动的Probe功能(具体实现详见 drivers/rpmsg/rpmsg_core.c 300行后内容)。

在M4端,通过调用openamp库中的 OPENAMP_create_endpoint 函数,并在调用时指定参数name(即为设备名称), 即可在内核中注册一个对应的rpmsg框架平台设备,该设备最终可以通过name(设备名称)来匹配到相应的A7端内核驱动:

broken

所以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设备的代码如下:

broken
  • 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资源,我们就可以进行简单的实验了。 本实验的代码也比较简单,这里就讲解这么多。

8.5. 实验准备

由于多核异构的框架是与处理器的架构紧密联系在一起的,所以一般这些框架驱动会由芯片厂商为我们提供好。 野火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的设备树插件关闭。

8.6. 实验操作

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内核后信息如下:

broken

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

broken

下面,我们进行第二个设备测试,通过前面现象中的LOG,我们可以看到被枚举出的tty设备节点 /dev/ttyRPMSG0 节点, 我们就通过该节点测试tty设备的功能,输入如下命令:

echo "hello M4 core , i'm A7!" > /dev/ttyRPMSG0

实验现象如下所示:

broken

上图为A7通过虚拟的tty终端设备,转发到M4内核的消息内容, 最终通过M4核固件的串口Log功能打印出来对应信息。

自此,所有实验结束。