SoC软件技术核间通信

admin 2024年6月6日20:48:47评论5 views字数 8303阅读27分40秒阅读模式

点击上方蓝字谈思实验室

获取更多汽车网络安全资讯

SoC软件技术核间通信
SoC软件技术核间通信

SoC就是System on Chip,一个芯片上集成了一个系统,一个系统往往有很多核,例如M核、A核、R核,以及异构的RISC-V核等,集成到一个芯片上的好处就是节省成本并且体积更小,能耗也更低,可谓是一举多得。但是多个核上的各种OS之间就需要进行通信,这也就是本文的主题:核间通信。核间通信也称为Mailbox,Mailbox技术是由软硬件协同实现的。

核间通信需要解决三个主要问题:

  1. 告诉对方数据放哪里(mailbox硬件或者共享内存实现)

  2. 怎么去通知对方取数据(中断,俗称doorbell)

  3. 数据的软件协议格式是什么

举个例子:引用ARM规范SCMI协议的核间通信例子:

SoC软件技术核间通信

inux和BL31都是运行在A核上的,SCP一般是M核,这其中就是核间通信。

01

数据放mailbox硬件实现

SoC软件技术核间通信

完全用硬件实现数据传输不使用共享内存,那么数据就需要放入硬件的data寄存器里面就可以了,然后触发对方的中断去接收处理。

比较经典的两个ARM自己的mailbox IP就是PL320和MHU。并且代表了两种技术路线:

  1. PL320老一些,两个核都连在PL320上的两个通道上,PL320支持很多通道,一对多的关系,成本高

  2. MHU更加灵活,只提供一个通道,那么就需要一个核一个MHU跟对方连,这样小巧灵活,避免浪费。另外MHU的DATA数据寄存器传输能力更强,基本不用共享内存参与,且可以与软件深度融合定制,效率更高。

1.1 PL320硬件手册

这里以ARM一个经典的Mailbox硬件PL320为例进行说明,怎么样去拿到一手资料,那必须是ARM官网。

首先进入ARM官网:https://developer.arm.com 搜索PL320:

SoC软件技术核间通信

点击下载pdf即可,下面的内容都是摘自英文pdf的翻译:

IPCM是一个高度可配置和可编程的模块。它有三个可配置的参数:

  1. 1-32个邮箱

  2. 0-7个数据寄存器,

  3. 1-32个中断。

SoC软件技术核间通信

核心0有一条消息要发送到核心1:

  • 核心0通过在邮箱源寄存器中设置位0来声明邮箱。

  • 然后,Core0在邮箱目标寄存器中设置位1,启用中断,并将消息编程到邮箱数据寄存器中。

  • 最后,核心0通过写入01向邮箱发送寄存器发送消息。这就生效了对核心1的中断。

当Core1被中断时:

  • 它会读取IPCMINT[1]的屏蔽中断状态寄存器,以确定哪个邮箱包含该邮件。

  • Core1读取该邮箱中的消息,然后清除该中断,并通过向邮箱发送寄存器写入10来断言确认中断。

  • 核心0被确认消息中断,从而完成操作。然后,核心0决定是保留邮箱以发送另一条邮件还是释放邮箱,并将其释放给系统中的其他核心使用它。

IPCM的集成:

SoC软件技术核间通信
  • AHB接口:AHB接口允许从系统总线访问到IPCM寄存器。

  • 邮箱和控制逻辑:邮箱和控制逻辑块包含所有的邮箱寄存器和控制逻辑。

  • 中断生成逻辑:中断生成逻辑块从所有IPCM邮箱的当前状态生成IPCM中断输出。

AHB(Advanced High-performance Bus)接口是一种在ARM处理器中使用的总线接口。它是一种高性能、低功耗的总线架构,用于连接处理器核心、内存、外设和其他系统组件。AHB接口支持高带宽、低延迟的数据传输,能够满足多种应用场景的需求。在ARM体系结构中,AHB接口被广泛应用于系统级总线连接和数据交换。

使用举例:

SoC软件技术核间通信

两个核、四个邮箱

  • Core0是源,使用channel 1,使用邮箱0发消息

  • Core1是目标,使用channel 2

SoC软件技术核间通信

时序图如上面所示,具体为:

1.Core0获得对邮箱0的控制,并通过在IPCM0SOURCE注册器中设置位0来将自己标识为源核心。

2.Core0通过在IPCM0MSTATUS寄存器中设置位0和1来实现对核心0和核心1的中断。

3.Core0通过在IPCM0DSTATUS寄存器中设置位1来定义目标核心。

4.Core0程序的数据有效负载,设置寄存器IPCM0DR0为DA7A0000。

5.Core0设置邮箱发送寄存器IPCM0SEND位0(就是0x01),以触发邮箱0中断到核心1。

6.Core1读取IPCMRIS1寄存器,以确定是哪个邮箱导致了中断。在本例中,只指示邮箱0。

7.Core1读取数据有效负载IPCM0DR0。

8.Core1可选择使用确认数据DA7A1111更新数据有效负载IPCM0DR0。

9.Core1清除位0,并在IPCM0SEND寄存器中设置位1,以清除其中断,并将手动确认中断提供回核心0。

10.Core0读取IPCMRIS0寄存器,以确定是哪个邮箱导致了中断。同样,只指示邮箱0。

11.Core0读取确认有效负载数据IPCM0DR0。

12.Core0清除邮箱发送寄存器IPCM0SEND中的位1,以清除其中断。

13.Core0通过清除IPCM0SOURCE寄存器来释放邮箱的所有权,进而清除IPCM0DSTATUS、IPCM0MSTATUS和IPCM0DR0寄存器。

注意:在步骤13中,通过不清除IPCM0源注册器,核心0可以保留邮箱以发送另一个数据消息。

寄存器空间:

SoC软件技术核间通信

1.2 PL320驱动(linux为例)

直接上github代码链接:

https://github.com/torvalds/linux/blob/master/drivers/mailbox/pl320-ipc.c

static int pl320_probe(struct amba_device *adev, const struct amba_id *id)

{
int ret;

ipc_base = ioremap(adev->res.start, resource_size(&adev->res));
if (ipc_base == NULL)
return -ENOMEM;

writel_relaxed(0, ipc_base + IPCMxSEND(IPC_TX_MBOX));

ipc_irq = adev->irq[0];
ret = request_irq(ipc_irq, ipc_handler, 0, dev_name(&adev->dev), NULL);
if (ret < 0)
goto err;

/* Init slow mailbox */
writel_relaxed(CHAN_MASK(A9_SOURCE),
ipc_base + IPCMxSOURCE(IPC_TX_MBOX));
writel_relaxed(CHAN_MASK(M3_SOURCE),
ipc_base + IPCMxDSET(IPC_TX_MBOX));
writel_relaxed(CHAN_MASK(M3_SOURCE) | CHAN_MASK(A9_SOURCE),
ipc_base + IPCMxMSET(IPC_TX_MBOX));

/* Init receive mailbox */
writel_relaxed(CHAN_MASK(M3_SOURCE),
ipc_base + IPCMxSOURCE(IPC_RX_MBOX));
writel_relaxed(CHAN_MASK(A9_SOURCE),
ipc_base + IPCMxDSET(IPC_RX_MBOX));
writel_relaxed(CHAN_MASK(M3_SOURCE) | CHAN_MASK(A9_SOURCE),
ipc_base + IPCMxMSET(IPC_RX_MBOX));

return 0;
err:
iounmap(ipc_base);
return ret;
}

request_irq(ipc_irq, ipc_handler 设置了中断,这跟硬件的连接有关系,PL320硬件跟A核连接上了那个中断引脚就是哪个,是硬件决定的。

初始化的时候会执行pl320_probe,要根据具体硬件的连接方案,软件是需要有改动的,linux的驱动也不是万能的,只是提供了个例子,具体怎么用还需要自己去配置,所以驱动工程师每个公司都会养几个。

发数据的时候会调用pl320_probe:

int pl320_ipc_transmit(u32 *data)
{
int ret;

mutex_lock(&ipc_m1_lock);

init_completion(&ipc_completion);
__ipc_send(IPC_TX_MBOX, data);
ret = wait_for_completion_timeout(&ipc_completion,
msecs_to_jiffies(1000));
if (ret == 0) {
ret = -ETIMEDOUT;
goto out;
}

ret = __ipc_rcv(IPC_TX_MBOX, data);
out:
mutex_unlock(&ipc_m1_lock);
return ret;
}

发生中断在

#define IPCMxDR(m, dr)(((m) * 0x40) + ((dr) * 4) + 0x024)
#define IPCMxSEND(m)(((m) * 0x40) + 0x020)

static void __ipc_send(int mbox, u32 *data)
{
int i;
for (i = 0; i < 7; i++)
writel_relaxed(data[i], ipc_base + IPCMxDR(mbox, i));
writel_relaxed(0x1, ipc_base + IPCMxSEND(mbox));
}
  • m是channel数,一个channel的寄存器偏移占0x40的偏移大小

  • 数据寄存器的偏移就是0x024。

  • dr就是8个32bit的数据的那一个,一个字节8bit,32bit就是4个字节

  • 0x20 IPCMxSEND就是触发了对方的中断

SoC软件技术核间通信

收数据的过程就是对方给自己一个中断,进入中断处理函数ipc_handler

#define IPCMMIS(irq)(((irq) * 8) + 0x800)

static irqreturn_t ipc_handler(int irq, void *dev)
{
u32 irq_stat;
u32 data[7];

irq_stat = readl_relaxed(ipc_base + IPCMMIS(1));
if (irq_stat & MBOX_MASK(IPC_TX_MBOX)) {
writel_relaxed(0, ipc_base + IPCMxSEND(IPC_TX_MBOX));
complete(&ipc_completion);
}
if (irq_stat & MBOX_MASK(IPC_RX_MBOX)) {
__ipc_rcv(IPC_RX_MBOX, data);
atomic_notifier_call_chain(&ipc_notifier, data[0], data + 1);
writel_relaxed(2, ipc_base + IPCMxSEND(IPC_RX_MBOX));
}

return IRQ_HANDLED;
}

按照上面硬件手册中的流程:

10.Core0读取IPCMRIS0寄存器,以确定是哪个邮箱导致了中断。同样,只指示邮箱0。

对应:readl_relaxed(ipc_base + IPCMMIS(1));

SoC软件技术核间通信
SoC软件技术核间通信

11.Core0读取确认有效负载数据IPCM0DR0。

对应:__ipc_rcv

static u32 __ipc_rcv(int mbox, u32 *data)
{
int i;
for (i = 0; i < 7; i++)
data[i] = readl_relaxed(ipc_base + IPCMxDR(mbox, i));
return data[1];
}

12.Core0清除邮箱发送寄存器IPCM0SEND中的位1,以清除其中断。

对应代码:writel_relaxed(2, ipc_base + IPCMxSEND(IPC_RX_MBOX));

13.Core0通过清除IPCM0SOURCE寄存器来释放邮箱的所有权,进而清除IPCM0DSTATUS、IPCM0MSTATUS和IPCM0DR0寄存器。

02

数据放共享内存实现

SoC软件技术核间通信
  1. A核先往某个指定共享内存空间buffer写数据,然后写入共享内存空间的地址信息到相应通道数据寄存器,mailbox触发中断给R核;

  2. M核(SCP)通过得到mailbox中断,获取共享内存相应offset,读取buffer数据;

  3. M核(SCP)通过mailbox触发中断通知A核接收消息完毕。

这里的Mailbox硬件如果使用的PL320的话,那么只使用了其中断功能,DR数据寄存器就闲置了。一般是需要传输的数据量比较大的时候,DR寄存不够用了,就必须使用共享内存了。

注意:使用共享内存的时候,双方要约定好共享内存的区域,或者动态的使用DR寄存器传输共享内存地址。

在drivers/mailbox/mailbox.c中,mailbox_send_message发消息的时候会调用mbox_send_message

mailbox_send_message
--》mbox_send_message
--》msg_submit

static void msg_submit(struct mbox_chan *chan)
{
data = chan->msg_data[idx];
if (chan->cl->tx_prepare)
chan->cl->tx_prepare(chan->cl, data);
err = chan->mbox->ops->send_data(chan, data);
}

tx_prepare-》shmem_tx_prepare会往共享内存里面存入数据,在drivers/firmware/arm_scmi/shmem.c中

void shmem_tx_prepare(struct scmi_shared_mem __iomem *shmem,
struct scmi_xfer *xfer)
{
spin_until_cond(ioread32(&shmem->channel_status) &
SCMI_SHMEM_CHAN_STAT_CHANNEL_FREE);
iowrite32(0x0, &shmem->channel_status);
iowrite32(xfer->hdr.poll_completion ? 0 : SCMI_SHMEM_FLAG_INTR_ENABLED,
&shmem->flags);
iowrite32(sizeof(shmem->msg_header) + xfer->tx.len, &shmem->length);
iowrite32(pack_scmi_header(&xfer->hdr), &shmem->msg_header);
pr_info("#### shmem_tx_prepare shmem->msg_header=0x%xn", shmem->msg_header);
if (xfer->tx.buf){
memcpy_toio(shmem->msg_payload, xfer->tx.buf, xfer->tx.len);
pr_info("#### shmem_tx_prepare shmem->msg_payload[0]=0x%xn", (int)shmem->msg_payload[0]);
}
}

发消息drivers/mailbox/pl320-ipc.c中pl320_mbox_send_data函数

static const struct mbox_chan_ops pl320_mbox_ops = {
.send_data = pl320_mbox_send_data,
};

ops->send_data-》pl320_mbox_send_data-》__ipc_send(pl320_id, ch, buf);会触发中断

static void __ipc_send(int pl320_id, int mbox, u32 *data)
{
ipc_base = get_ipc_base(pl320_id);

for (i = 0; i < MBOX_MSG_LEN; i++)
writel_relaxed(data[i], ipc_base + IPCMxDR(mbox, i));

if (mbox % 2 == 0)
writel_relaxed(0x1, ipc_base + IPCMxSEND(mbox));
else
writel_relaxed(0x2, ipc_base + IPCMxSEND(mbox));
}

收消息,drivers/mailbox/pl320-ipc.c中ipc_handler

        for (idx = 0; idx < MBOX_CHAN_MAX; idx++)
if (irq_stat & (1 << idx))
receive_flag |= channel_handler(mbox, idx);

channel_handler中会清中断

03

数据的软件协议格式

3.1 SCMI协议

内存管理协议

SoC软件技术核间通信
SoC软件技术核间通信

代码参考:

https://github.com/torvalds/linux/blob/4a4be1ad3a6efea16c56615f31117590fd881358/drivers/firmware/arm_scmi/shmem.c#L22

struct scmi_shared_mem {
__le32 reserved;
__le32 channel_status;
#define SCMI_SHMEM_CHAN_STAT_CHANNEL_ERRORBIT(1)
#define SCMI_SHMEM_CHAN_STAT_CHANNEL_FREEBIT(0)
__le32 reserved1[2];
__le32 flags;
#define SCMI_SHMEM_FLAG_INTR_ENABLEDBIT(0)
__le32 length;
__le32 msg_header;
u8 msg_payload[];
};

其中里面包含了scmi协议的内容,如下:

__le32 msg_header;
u8 msg_payload[];

3.2 RPMsg通信

SoC软件技术核间通信

Linux ® RPMsg框架是在 virtio 框架[1] [2]之上实现的消息传递机制,用于与远程处理器进行通信。它基于 virtio vring,通过共享内存向远程 CPU 发送消息或从远程CPU接收消息。

vring 是单向的,一个 vring 专用于向远程处理器发送消息,另一个 vring 用于从远程处理器接收消息。此外,在两个处理器都可见的内存空间中创建共享缓冲区。

  • remoteproc:remoteproc 框架允许不同的平台/架构控制(开机、加载固件、关机)远程处理器。此框架还为支持 RPMsg 协议的远程处理器添加了 rpmsg virtio 设备。有关此框架的更多详细信息,请参阅remote proc 框架[1]页面。

  • virtio:支持虚拟化的 VirtIO 框架。它基于共享环形缓冲区 (vring) 提供高效的传输层。有关此框架的更多详细信息,请参阅以下链接:

  • Virtio:Linux 的 I/O 虚拟化框架[1]

  • virtio 介绍 - SlideShare [2]

  • rpmsg:基于 virtio 的消息总线,允许内核驱动程序与系统上可用的远程处理器进行通信。它提供消息传递基础结构,方便客户端驱动程序编写有线协议消息。然后,客户端驱动程序可以根据需要公开适当的用户空间接口。

  • rpmsg_client_driver是实现与远程处理器关联的服务的客户端驱动程序。当远程处理器使用“新服务公告” RPMsg消息请求关联服务时,RPMsg框架会探测此驱动程序。

在Linux内核代码中,RPMsg的代码主要位于drivers/rpmsg/下,文件之间的主要关系如下图所示。一开始Linux中只使用VirtIO作为该协议传输层,后来又增加了Glink、SMD等,Glink和SMD主要用于高通平台。用户代码通过操纵rpmsg驱动,实现数据的收发操作。所有数据都在RPMsg总线上传递。

SoC软件技术核间通信
SoC软件技术核间通信

当主核需要和从核进行通信的时候可以分为四步,如上图所示:

  1. 主核先从USED中取得一块内存;

  2. 将消息按照消息协议填充;

  3. 将该内存链接到AVAIL换中;

  4. 触发中断,通知从核有消息处理。

RPMSG的报文格式:

SoC软件技术核间通信

该消息格式的定义位于drivers/rpmsg/virtio_rpmsg_bus.c中,具体定义如下。

struct rpmsg_hdr {
u32 src;
u32 dst;
u32 reserved;
u16 len;
u16 flags;
u8 data[];
} __packed;

参考:https://www.jianshu.com/p/c7cdad8273e

 end 

SoC软件技术核间通信

 专业社群 

SoC软件技术核间通信

 精品活动推荐 

SoC软件技术核间通信
SoC软件技术核间通信
SoC软件技术核间通信

原文始发于微信公众号(谈思实验室):SoC软件技术--核间通信

  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2024年6月6日20:48:47
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   SoC软件技术核间通信http://cn-sec.com/archives/2823704.html

发表评论

匿名网友 填写信息