核间通信

01、引  言

STM32 STM32MP15X 是基于 CM4+CA7 的异构 SOC,CM4 侧用于实时任务的处理, CA7 侧运行 Linux,负责更加复杂的计算和任务处理,CM4 侧采集到的数据通常会给到 CA7 去 做处理,因此必然需要核间通信的机制,核间通信需要硬件和软件的支持。

硬件:

1)IPCC: 负责产生中断信号 

2)共享内存:负责数据交互 

软件:

1)CM4 侧 OPenAMP 

2)CA7 Linux 侧 RPMsg framework(Mailbox,Remoteproc,RPMsg,VirtIO) 

对于核间通信的基本原理请阅读相关 wiki 站点文档。

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

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

本文将结合几个实际案例讨论核间通信可能遇到的问题

  • 如何增加 RPMsg buffer size 

  • 如何增加 RPMsg 通道数 

  • RPMsg 通信过程出现死锁 

  • 双核通信过程出现 No more buffer in queue,通信终止

02、如何增加 RPMsg buffer size

2.1. 默认配置 

首先我们来看一下当前代码里和 RPMSG 相关的默认配置,见如下代码

>CA7:virtio_rpmsg_bus.c 
#define MAX_RPMSG_NUM_BUFS (512) 
#define MAX_RPMSG_BUF_SIZE (512) 
CM4:rpmsg_virtio.h
#define RPMSG_BUFFER_SIZE (512)

Reserve memory 配置,参考如下 linux 设备树配置 

Stm32mp15xx-dkx.dtsi: 
vdev0vring0: vdev0vring0@10040000 
{ 
    compatible = "shared-dma-pool"; 
    reg = <0x10040000 0x1000>;
    no-map; 
}; 
vdev0vring1: vdev0vring1@10041000 
{ 
    compatible = "shared-dma-pool"; 
    reg = <0x10041000 0x1000>; 
    no-map; 
}; 
vdev0buffer: vdev0buffer@10042000 
{ 
    compatible = "shared-dma-pool"; 
    reg = <0x10042000 0x4000>; 
    no-map; 
};

通道数目:定义在 CM4 核的头文件中,这个值最终会传到 CA7 Linux 侧。

CM4: openamp_conf.h define VRING_NUM_BUFFS 16

从如上配置可以看到当前总共有 16 个 RPMSG 通道,每个通道的大小为 512 byte。

每次通信发送和接收的信息包含信息头和信息的实际内容两部分,其中 Vring 用来传输 Message header, Buffer pool 传输真正的数据。 

TX Vring :vdev0vring0,reserve 0x1000 即 4K byte, 

RX Vring :vdev0vring1,reserve 0x1000 即 4K byte 

Buffer pool :vdev0buffer, resserve 0x1000 即 16K byte 

2.1.1. 如何计算 vring size : 

如何计算 Vring 的 size(即 Message header): 

Linux 侧:Virtio_ring.h
static inline unsigned vring_size(unsigned int num, unsigned long align) 
{ 
    return ((sizeof(struct vring_desc) * num + sizeof(__virtio16) * (3 + num)
    + align - 1) & ~(align - 1)) 
    + sizeof(__virtio16) * 3 + sizeof(struct vring_used_elem) * num;
} 
CM4:Virtio_vring.h
static inline int vring_size(unsigned int num, unsigned long align) 

{ 
    int size; size = num * sizeof(struct vring_desc); 
    size += sizeof(struct vring_avail) + (num * sizeof(uint16_t)) + sizeof(uint16_t); 
    size = (size + align - 1) & ~(align - 1); 
    size += sizeof(struct vring_used) + 
    (num * sizeof(struct vrindg_used_elem)) + sizeof(uint16_t); 
    return size; 
}

根据上述方法计算出 16 通道下的 vring size 为 438byte,但是由于 linux 系统的中 reserve memory 的规则需要按 page(4096byte)对齐,因此 0x1000 是最小 reserve 的值。

2.1.2. 如何计算 vdev0buffer size 

vdev0buffer_size = buffer_size * number _of buffer * 2 因为 TX 和 RX 的通道是独立管理的,所以需要乘 2 可得出当前的 vdev0buffer_size=512*16*2 = 16K byte, 与上述配置是一致的。因此这里需要注意一点,RPMSG 通道数,也就是 Vring 的队 列数,也就是说 M4 和 A7 之间通讯的数据最大缓存队列。

基于当前的配置在实际发送数据时,单次只能发送 512byte 的数据,一旦超过则会被截断丢弃,因此如果在遇到这种单帧数据量超过 512 byte 的场景,又不想截断数据分批发送的话,那么可 以考虑将 buffer size 增大。

2.2. 如何将 Buffer size 改为 2048 byte 

首先根据公式 vdev0buffer_size = buffer_size * number _of buffer * 2 计算出所 vdev0buffer 需要的 reserve memory 为 2048*16*2 = 64KB,意味着仅仅是 vdev0buffer_size 就占满了整个MCUSRAM3,见下图
1.png

CM4 整个内存空间才 384KB(不计算 retention RAM 的情况下),因此这样的内存开销是巨大 的,可以考虑减少通道数目为 4,则根据 2048*4*2 所需的内存总量仍为 16KB,但是此时最多 只能创建 4 个 rpmsg 通道,对应为 4 个 rpmsg-tty 设备节点,同时 buffer pool 里总共只能存储 4 帧数据。

涉及修改如下 

通道 size : 

CA7:virtio_rpmsg_bus.c 

#define MAX_RPMSG_NUM_BUFS (512) 

#define MAX_RPMSG_BUF_SIZE (2048) 


CM4:rpmsg_virtio.h 

#define RPMSG_BUFFER_SIZE (2048) 

Reserve memory 配置保持不变,设备树示例: 

Stm32mp15xx-dkx.dtsi: 
vdev0vring0: vdev0vring0@10040000 
{ 
    compatible = "shared-dma-pool";    
    reg = <0x10040000 0x1000>; 
    no-map;
}; 
vdev0vring1: vdev0vring1@10041000 
{ 
    compatible = "shared-dma-pool"; 
    reg = <0x10041000 0x1000>; no-map; 
}; 
vdev0buffer: vdev0buffer@10042000 
{ 
    compatible = "shared-dma-pool"; 
    reg = <0x10042000 0x4000>; 
    no-map; 
};

通道数修改:

CM4: openamp_conf.h 

define VRING_NUM_BUFFS 4

03、如何将通道数增加为 64,且通道 size 改为 256byte 

通过前文的理解,我们已经知道如何计算 vdev0buffer 和 vdev0vring 的 size 了, 根据通道 vdev size 的计算公式 vdev0buffer_size = buffer_size * number_of buffer * 2 可计算所需的vdev0buffer_size=256 * 64*2=32768byte=0x8000 Vring size:16 通道,vring size 为 438byte, 那么可得 64 通道时的 vring_size 约为 1752byte=0x6db,  由于 reserve memory 按照 4K page size(0x1000)对齐,且 64 通道数的情况下所需要的 vring_size 0x6db 仍然小于 0x1000,vring size 的大小可保持不变。

修改如下:

CA7:virtio_rpmsg_bus.c 

#define MAX_RPMSG_NUM_BUFS (512) 

#define MAX_RPMSG_BUF_SIZE (256) 

CM4:rpmsg_virtio.h 

#define RPMSG_BUFFER_SIZE (256) 

Reserve memory 配置保持不变,设备树示例:

Stm32mp15xx-dkx.dtsi: 
vdev0vring0: vdev0vring0@10040000 
{ 
    compatible = "shared-dma-pool";
    reg = <0x10040000 0x1000>; 
    no-map; 
}; 

vdev0vring1: vdev0vring1@10041000 
{ 
    compatible = "shared-dma-pool";
    reg = <0x10041000 0x1000>; 
    no-map; 
}; 

vdev0buffer: vdev0buffer@10042000 
{ 
    compatible = "shared-dma-pool"; 
    reg = <0x10042000 0x8000>; 
    no-map; 
};

通道数修改:

CM4: openamp_conf.h 

define VRING_NUM_BUFFS 64 

04、RPMSG 通信死锁案例解析

问题现象是 RPMSG 通信过程中出现死锁,导致通信中断,系统重启 

4.1. 参考日志片段:

2.png

3.png

4.png

4.2. 解析 

从上述日志片段中可以看到有两种方式调用 virtio_rpmsg_trysend,分别是 

1),vfs_write 

2),__irq_svc 

即分别从用户空间和外设中断中触发 rpmsg 消息的发送, 问 题 发 生 时 , 首选用户空间触发的 virtio_rpmsg_trysend 正调用到 rpmsg_send_offchannel_raw,此时外设中断再次触发了 virtio_rpmsg_trysend,但是在执行 到 get_a_tx_buf 时 通过 mutex_lock 获 取互斥 锁 的 时 候 导 致 了 死锁 , 这 是 因 为 前 一 次 rpmsg_send_offchannel_raw 执行还未结束,互斥锁还未释放因此造成了死锁.因此针对这种情况,在触发 virtio_rpmsg_trysend 调用时,需要做好互斥。

5.png

该问题是由于业务程序设计不合理导致的,virtio_rpmsg_trysend 中使用了互斥锁,因此尽量不要在中断触发中调用 virtio_rpmsg_trysend,如果同时有用户程序在发起 RPMSG 的调用,由于中断不可预知的特点,中断在触发 virtio_rpmsg_trysend 调用时,只要频率足够快,是完全有可能和用户程序触发的 virtio_rpmsg_trysend 调用产生冲突的,又由于互斥锁的使用,因此这种情况下发生死锁是可以预见的,开发者需要了解这种情况,并调整 virtio_rpmsg_trysend 的调 用策略以避免竞争。

05、No more buffer in queue 

该问题表现为在 RPMSG 正常通信一段时间后,CA7 linux 内核不停打印日志 No more buffer  in queue,通信过程中断,此时即使重新加载 CM4 仍然不能恢复,只能重启系统,该问题仅在 OPENSTLINUX_V1.0 版本,即对应的内核 v4.19 版本中出现,在后续版本中已修复.

5.1. 参考日志

6.png

5.2. 分析 

上述日志是 linux 内核打印的日志,从仅有的日志中仅仅可以看当前的 buffer 队列中没有空闲的 buffer 了,为了进一步得到更详细的日志来分析,我们在 linux 中打开与协核 CM4 通信相关模 块的动态调试开关。

CA7 Linux 侧 

Board $> echo -n 'file stm32_rproc.c +p' > /sys/kernel/debug/dynamic_debug/control 

Board $> echo -n 'file remoteproc*.c +p' > /sys/kernel/debug/dynamic_debug/control 

Board $> echo -n 'file stm32_ipcc.c +p' > /sys/kernel/debug/dynamic_debug/control 

同时协核 CM4 侧也需要调整 log 等级为 LOGDEBUG, 以输出更多调试信息。

重新测试得到如下日志:

CA7 Linux 侧日志:

7.png
CM4 侧日志 :
8.png

正常通信时 ipcc tx 和 rx 的中断都是可以正常触发的,问题发生后,从 CA7 Linux 日志中可以看 到,只有 tx 中断,没有 rx 中断,从 M4 侧日志也可以看到中断触发不再更新,且上一次的中断 也没有被处理,而 CM4 和 CA7 通信的中断信号是由 IPCC 产生的,从这里基本可以得出结论, 通信不正常是由 IPCC 的状态异常导致的,重点检查中断处理相关代码,查看 CA7 Linux 代码 stm32_ipcc.c 中 的中断相关函数 stm32_ipcc_rx_irq 和 stm32_ipcc_tx_irq 时 发 现 使 用 stm32_ipcc_set_bits 做寄存器操作时均未做互斥处理,stm32_ipcc_set_bits 是更新 IPCC 寄存器的核心操作,在很多地方会被多次调用,因此如果不做互斥处理的话,IPCC 寄存器更新容易 发生紊乱,最后看到的现象就是如上日志所示, CM4 侧没有处理上一次中断没有被处理导致 CA7 侧无法再次收到 rx 中断,加上自旋锁保护后,即可解决该问题。

来源:STM32

免责声明:本文为转载文章,转载此文目的在于传递更多信息,版权归原作者所有。本文所用视频、图片、文字如涉及作品版权问题,请联系小编进行处理(联系邮箱:cathy@eetrend.com)。

围观 9
订阅 RSS - 核间通信