【TDA4系列】 IPC applications应用举例

原文

https://software-dl.ti.com/jacinto7/esd/processor-sdk-rtos-jacinto7/06_02_00_21/exports/docs/psdk_rtos_auto/docs/user_guide/developer_notes.html

正文


Jacinto 7 SoC 在一个 SoC 上有多个不同的 CPU。 例如 R5F、A72、C7x、C6x。 在这些 CPU 上运行的软件需要相互协作并实现用例。 协作方式称为处理器间通信或 IPC。 每个 CPU 和操作系统上都提供了 IPC 库,以允许更高级别的应用程序相互通信。

不同 CPU/OS 上的整体 IPC SW 堆栈如下表所示

IPC SW layerDescription
ApplicationApplication which sends/receives IPC messages
RPMSG CHAR[ONLY in Linux] User space API used by application to sends/receives IPC messages
RPMSGSW protocol and interface used to exchange messages between endpoints on a destination CPU
VRINGShared memory based SW queue which temporarily holds messages as they are exchanges between two CPUs
HW MailboxHardware mechnism used for interrupt notification between two CPUs
IPC软件层说明
应用发送/接收IPC消息的应用程序
RPMSG CHAR[仅在 Linux 中] 应用程序用于发送/接收 IPC 消息的用户空间 API
RPMSG用于在目标 CPU 上的端点之间交换消息的软件协议和接口
VRING基于共享内存的 SW 队列,当消息在两个 CPU 之间交换时临时保存消息
硬件邮箱用于两个 CPU 之间的中断通知的硬件机制

IPC 的主要软件组件是,

1、TI-RTOS的PDK IPC LLD驱动,包括RPMSG、VRING和HW Mailbox驱动。
2、Linux内核IPC驱动套件,由RPMSG CHAR、RPMSG、VRING和HW Mailbox驱动组成。

PDK IPC 库和 Linux 内核 IPC 驱动程序套件支持 J7ES SoC 中所有内核之间的通信。 PDK IPC 库与 TI-RTOS 应用程序链接,并支持与其他运行 TI-RTOS 的内核(如 R5FSS 内核和 C6x/C7x DSP 内核)进行基于消息的通信。 它还能够使用相同的 API 与运行 Linux 的 A72 内核进行通信。

Linux IPC 驱动程序作为内核的一部分运行在 A72 内核上,可以与 R5FSS 和 DSP 内核通信。

2. RPMSG and VRING 模式

RPMSG 是 Linux 和 TI-RTOS 使用的通用消息传递框架。 RPMSG 是一种基于端点的协议,其中服务器 CPU 可以运行一个服务来侦听专用端点处的传入消息,而所有其他 CPU 可以向该(服务器 CPU、服务端点)元组发送请求。 您可以将其类比为网络中的 UDP/IP 层,其中 CPU 名称类似于 IP 地址,端点类似于 UDP 端口号。

客户端 CPU / 任务在向服务器发送消息时还提供回复端点,以便服务器可以将其回复发送到客户端 CPU。

通过使用多个端点,可以在同一组 CPU 之间打开多个逻辑 IPC 通信通道。

虽然 RPMSG 是应用程序所见的 API 或协议,但在内部,IPC 驱动程序使用 VRING 在不同(RPMSG CPU、端点)元组之间实际传递消息。 RMSG 端点与 VRING 的关系如下图所示。

在这里插入图片描述Fig. 8.1 RPMSG and VRING

VRING 是一个共享内存段,位于一对 CPU 之间,用于保存两个 CPU 之间传递的消息。 下图显示了作为消息从发送方传递到接收方并再次返回的事件序列。

在这里插入图片描述
Fig. 8.2 RPMSG and VRING message exchange data flow

他的步骤顺序描述如下,

1、应用程序向给定目的地(CPU、端点)发送消息
2、消息首先从应用程序复制到两个CPU之间使用的VRING。 此后,IPC 驱动程序在硬件邮箱中发布 VRING ID。
3、这会触发目标 CPU 上的中断。 在目标 CPU 的 ISR 中,它提取 VRING ID,然后根据 VRING ID 检查该 VRING 中的任何消息
4、如果收到消息,它从VRING中提取消息并将其放入目标RPMSG端点队列中。 然后触发在此 RPMSG 端点上阻止的应用程序
5、然后应用程序处理接收到的消息,并使用相同的RPMSG 和VRING 机制在相反方向回复发送方CPU。

3. RPMSG CHAR模式

RPMSG CHAR 是一个用户空间 API,它提供对 Linux 中 RPMSG 内核驱动程序的访问。
RPMSG CHAR 为 linux 应用程序提供了一个文件 IO 接口来读写消息到不同的 CPU
TI-RTOS 上需要与 linux 通信的应用程序需要将其端点“通知”到 linux。 这将在 linux 上创建一个设备“/dev/rpmsgX”。
Linux 用户空间应用程序可以使用该设备向 RTOS 端的关联端点读取和写入消息。
通常的 linux 用户空间 API,如“select”,可用于等待来自多个 CPU 的多个端点。
提供了一个实用程序库“rpmsg_char_helper”来简化来自不同 CPU 的已宣布 RPMSG 端点的发现和初始化

4. Hardware Mailbox模式

  • 硬件邮箱主要用于提供具有小的 32 位有效负载的中断事件通知。
    • VRING 使用硬件邮箱在目标 CPU 上触发中断。
  • 每个邮箱包含 16 个单向 HW 队列,最多可连接 4 个通信用户或 CPU。
  • 每个邮箱队列一次最多可容纳 4 个字大小的邮件。
  • 每个通信用户都可以使用以下邮箱状态和中断事件通知
    • 每个邮箱的 Rx 的新消息状态事件(只要邮箱有消息就触发)
    • Tx 的 Not Full 状态事件(只要邮箱有 empy fifo 就会触发)
    • 每个邮箱的状态寄存器“邮箱已满”和“未读邮件数”
  • J721E SoC 有 12 个硬件邮箱实例。 即 12x 16 个硬件邮箱队列

下图显示了硬件邮箱的逻辑框图,
在这里插入图片描述
Fig. 8.3 Hardware mailbox

4.2. Typical usage of mailbox IP

  • 邮箱与“发送者”用户和“接收者”用户相关联
  • 发件人执行以下步骤序列,
    • 检查邮箱FIFO中是否有空间(读取MAILBOX_FIFOSTATUS_m)
    • 如果未满,则将消息写入邮箱并返回给呼叫者(写入 MAILBOX_message_m)
    • 如果满了,
      • 将消息存储在支持硬件邮箱 FIFO 的软件队列中
      • 为该邮箱启用 NotFull 事件(设置 MAILBOX_IRQENABLE_SET_u 中的相应位)
    • 中断时,
      • 清除IP内的中断触发源(清除MAILBOX_IRQSTATUS_CLR_u中的对应位)
      • 从软件队列中删除消息并写入邮箱(写入尽可能多的可用空槽)
  • 接收器执行以下步骤序列,
    • 在对应的用户中断配置寄存器中启用对应的邮箱‘NewMsg’事件
    • 中断时,
      • 读取一个或多个邮箱消息,直到清空到软件支持的队列中
      • 清除中断源
    • 处理收到的消息

4.3. Mailbox and VRING

邮箱本质上充当一个非常小的硬件队列,其中包含 VRING ID
VRING 是共享内存中的 SW 队列,保存两个 CPU 之间传递的实际消息。 当收到中断时,邮箱消息会告知从哪个 VRING 出列消息。

 VRING ID = 0 告诉查看从发送方到接收方的 VRING
 VRING ID = 1 告诉从接收者到发送者查看 VRING

在这里插入图片描述
Fig. 8.4 Mailbox and VRING

5. Hardware spinlock模式

有时需要在两个 CPU 之间对临界区进行互斥访问。 通常在同一 CPU 或 CPU 集群上运行的操作系统上,使用 SW 信号量或互斥函数进行互斥。 然而,每个 CPU 运行不同的操作系统实例,当两个 CPU 之间需要互斥时,这个函数将不起作用,比如 Linux 和 TI-RTOS。 在这种情况下,可以使用 HW 自旋锁。

Jacinto 7 SoC 的单个硬件自旋锁实例中有 256 个硬件自旋锁。

J721E SoC 有 1 个硬件自旋锁实例。

  • 硬件自旋锁不由 IPC 驱动程序使用,可以直接由应用程序使用。

可以在 SDK 中找到显示硬件自旋锁用法的示例代码,如下所示

SDK ComponentFile / FolderDescription
vision_appsvision_apps/utils/ipc/app_ipc_linux_hw_spinlock.cHW spinlock usage on Linux
vision_appsvision_apps/utils/ipc/app_ipc_sysbios.c [appIpcHwLockAcquire/appIpcHwLockRelease]HW spinlock usage on RTOS

1、当使用 HW 自旋锁时,CPU“旋转”等待锁被释放,因此应该在很短的时间内使用 HW 自旋锁
2、应注意确保 CPU 在不释放锁的情况下不会退出
3、获取锁时可以使用 SW 超时以确保 CPU 不会无限期旋转
4、还建议在获取 HW 锁之前获取本地 OS 锁(POSIX 信号量、pthread_mutex、TI-RTOS 信号量),以确保同一 CPU 上的任务/线程/进程不会在同一个 HW 自旋锁上旋转

6. 性能和延迟

6.1. 延迟

  • 延迟
    延迟是发送器 CPU 和接收器 CPU 速度的函数
    接收 CPU 的中断服务延迟
    发送方和接收方 CPU 上的内存访问延迟
  • 邮箱延迟
    邮箱是在 SoC 内存映射中映射为 MMR 的硬件外设; CPU 读取/写入这些 MMR 会有一些延迟。
  • 涉及两个 memcpy
    向 VRING 发送应用程序
    VRING 到接收器 RPMSG 端点本地队列
    memcpy 的大小等于消息负载大小(最大 512 B)
    为了减少延迟和/或发送更大的缓冲区,建议将指针/句柄/偏移量从 ION 堆传递到更大的共享内存

有关 Jaincto 7 SoC 的测量性能,请参阅 PDK 数据表、IPC 部分 [链接]。

6.2.性能

-邮箱硬件队列深度为 4 个元素

-发件人可以连续发送 4 条消息,之后发件人需要等待接收方从邮箱中“弹出”至少一条消息。

  • 等待是软件等待 - 不是 CPU/总线停顿。 SW 可以使用中断从等待中恢复或轮询队列以获得一个空闲元素。

  • 在用于 TI-RTOS 的 PDK IPC 驱动程序中,邮箱由接收器在 ISR 上下文本身中弹出。所以邮箱本身没有任何性能瓶颈。

  • 如果接收 CPU 出现瓶颈以致无法执行 ISR,则发送方将需要等待才能在邮箱中发布新消息。

  • IPC 驱动程序使用共享内存 (VRING) 中的 SW 队列扩展 HW 队列。

          默认情况下,RPmessage 中的 SW 队列长度为 256 深。
          VRING 最多可以传输一条 512 字节的消息。
          对于较大的数据,建议使用使用ION heaps的共享内存,并传输指针以实现更快的传输。
    

7.其他注意事项

8.5.7.1.安全

IPC 驱动程序本身不处理安全性。
如果消息安全很重要,应用程序应该加密和解密通过 IPC 传递的消息。

8.5.7.2.消息错误

IPC 驱动程序不对 CRC 或校验和等消息进行任何错误检查
如果消息完整性很重要,应用程序应该对通过 IPC 传递的消息进行 CRC 或校验和
对于 IPC (VRING) 使用的共享内存,可以在系统级别启用 ECC,以确保 IPC 使用的消息和数据结构不会因内存中的硬件错误而损坏。

8.5.7.3.安全

IPC RTOS SW 目前是使用 TI 基线质量流程开发的,但是有路线图采用 TI 功能安全 (FSQ) 流程来覆盖 SW 中的系统故障。
此外,对于 FFI(免于干扰),系统集成商需要遵循以下附加说明,
    RPMSG 端点队列是 CPU 本地的,因此通过使用 MMU/MPU 和防火墙,可以确保没有其他 CPU 或非 CPU 主机(如 DMA)可以访问或损坏另一个 CPU RPMSG 端点队列。
    一对 CPU 之间有一个单独的共享内存 VRING。设置 MMU/MPU 以仅访问给定 CPU 需要的 VRING。
        这将避免一个 CPU 无意中损坏另一个 CPU 的 VRING 区域
    防火墙 VRING 共享内存以确保非 CPU 主机(如 DMA)不会无意中损坏共享 VRING 内存。
    每个邮箱实例都有自己的防火墙,因此对于 ASIL 和 QM OS 之间的 FFI(比如 Linux 是 QM,TI-RTOS 是 ASIL),建议对邮箱进行分区,以便与 QM OS 的所有通信都在一组邮箱上,并且所有带有 ASIL 操作系统的 IPC 位于另一组邮箱上。
防火墙、MPU/MMU 设置必须从外部 IPC 驱动程序完成。
    VRING、RPMSG 端点内存由用户提供给 IPC 驱动程序,因此用户可以控制 MPU/MMU 和防火墙设置来保护这些内存。

10.编写IPC应用程序 on TI-RTOS (PDK IPC driver)

1. IPC setup code

在开始与其他内核的任何通信之前初始化 IPC 堆栈。 这通常可以通过

uint32_t selfProcId = IPC_MCU2_1;
uint32_t remoteProcList[] =
{
    IPC_MPU1_0, IPC_MCU1_0, IPC_MCU1_1, IPC_MCU2_0, IPC_MCU3_0, IPC_MCU3_1, IPC_C66X_1, IPC_C66X_2, IPC_C7X_1
};
uint32_t numRemoteProcs = sizeof(remoteProcList)/sizeof(uint32_t);

Ipc_init(NULL);
Ipc_mpSetConfig(selfProcId, numRemoteProcs, &remoteProcList[0]);

需要使用参与通信所需的所有内核(selfProcId 除外)的列表来调用 Ipc_mpSetConfig()。

如果需要与运行在A72核上的Linux通信,还必须加载资源表

Ipc_loadResourceTable((void*)&ti_ipc_remoteproc_ResourceTable);

请参阅 apps/basic_demos/app_tirtos/tirtos_linux/ipc_rsctable.h 中的示例资源表(仅使用一个跟踪条目和一个 vdev 条目)

IPC 堆栈设置后,内核需要初始化 virtio 或 VRING 层。 virtio 框架可以初始化为

vqParam.vqObjBaseAddr = (void*)&sysVqBuf[0];
vqParam.vqBufSize     = numRemoteProcs * Ipc_getVqObjMemoryRequiredPerCore();
vqParam.vringBaseAddr = (void*)VRING_BASE_ADDRESS;
vqParam.vringBufSize  = IPC_VRING_BUFFER_SIZE;
vqParam.timeoutCnt    = 100;
Ipc_initVirtIO(&vqParam);

注意:
Ipc_initVirtIO() 将在内部调用 Ipc_isRemoteReady() 并将仅为当前在线的内核初始化 virtio 层。 如果核心不在线,它会跳过与该核心的 virtio 配对。 当 TI-RTOS 内核提前启动并且 Linux remoteproc 和 virtio 驱动程序稍后初始化时,就会发生这种情况。

有两种可能的方法可以解决此问题:

1、在调用 Ipc_initVirtIO() 之前等待所有 Linux 内核准备就绪

for(i = 0; i < numRemoteProcs; i++)
        while(!Ipc_isRemoteReady(remoteProcList[i]))
                Task_sleep(1);

vqParam.vqObjBaseAddr = (void*)&sysVqBuf[0];
vqParam.vqBufSize     = numRemoteProcs * Ipc_getVqObjMemoryRequiredPerCore();
vqParam.vringBaseAddr = (void*)VRING_BASE_ADDRESS;
vqParam.vringBufSize  = IPC_VRING_BUFFER_SIZE;
vqParam.timeoutCnt    = 100;
Ipc_initVirtIO(&vqParam);

2、轮询 Linux 内核是否准备就绪,稍后将 virtio 与 Linux 内核配对

...
for(i = 0; i < numRemoteProcs; i++) {
        Task_Params_init(&params);
        params.arg0 = (UArg)remoteProcList[i];
        params.arg1 = (UArg)lateInitFlag[i];
        Task_create(rpmsg_vdevMonitorFxn, &params, NULL);
}
...

void rpmsg_vdevMonitorFxn(UArg arg0, UArg arg1)
{
        int32_t status;

        /* Is this a late init core? */
        if((uint32_t) arg1 == FALSE)
        {
                return;
        }

        /* Wait for remote core to be ready ... */
        while(!Ipc_isRemoteReady((uint32_t) arg0))
        {
                Task_sleep(1);
        }

        /* Create the VRing now ... */
        Ipc_lateVirtioCreate((uint32_t) arg0);
        ...

Linux在启动TI-RTOS内核时,会提前启动Linux virtio驱动,可以使用上面的选项1。

下一步是初始化 RPMSG 堆栈。 这可以通过

RPMessageParams_init(&cntrlParam);

cntrlParam.buf         = pCntrlBuf;
cntrlParam.bufSize     = rpmsgDataSize;
cntrlParam.stackBuffer = &pTaskBuf[index++ * IPC_TASK_STACKSIZE];
cntrlParam.stackSize   = IPC_TASK_STACKSIZE;
RPMessage_init(&cntrlParam);

对于需要后期 virtio 配对的内核,RPMSG 堆栈配对也应该在 rpmsg_vdevMonitorFxn() 中完成

...
Ipc_lateVirtioCreate((uint32_t) arg0);

RPMessage_lateInit((uint32_t) arg0);
...

1.2. IPC server (listening to service requests)

设置完成后,服务器需要打开一个端点并向所有其他核心宣布服务的存在。

RPMessageParams_init(&params);
params.requestedEndpt = ENDPOINT;
params.buf = buf;
params.bufSize = bufSize;

handle = RPMessage_create(&params, &myEndPt);
RPMessage_announce(RPMESSAGE_ALL, myEndPt, "rpmsg_chrdev");

选择 ENDPOINT 以便它对于给定的核心是唯一的。 但是,它可以在不同的核心上复制。 此外,您可以通过为每个服务分配不同的端点编号,在给定的核心上运行多个 rpmsg_chrdev 服务(每个服务都有不同的用户定义的服务功能)。

已对已完成 virtio 配对的所有内核进行通知。 如果与任何内核进行了后期 virtio 配对,则必须在 rpmsg_vdevMonitorFxn() 中向它们重新通知这些服务

...
RPMessage_lateInit((uint32_t) arg0);

RPMessage_announce((uint32_t) arg0, RecvEndPt, "rpmsg_chrdev");
...

服务端点打开后,服务器可以运行一个循环来监听传入端点的消息并调用服务函数。 服务功能负责在服务完成后将任何响应发送回发送方。

while(TRUE)
{
        RPMessage_recv(handle, (Ptr)msg, &msg_len, &remoteEndPt, &remoteProcId,
                                IPC_RPMESSAGE_TIMEOUT_FOREVER);

        ...
        /* do something with message */

        service_func(msg, msg_len, remoteEndPt, remoteProcId, resp, &resp_len);

        ...

        RPMessage_send(handle, remoteProcId, remoteEndPt, myEndPt, resp, resp_len);
}

3. IPC client (sending sevice requests)

客户端应用程序还需要运行类似于服务器应用程序的设置代码。 设置完成后,客户端需要找出给定核心上服务的远程端点

RPMessage_getRemoteEndPt(dstProc, "rpmsg_chrdev", &remoteProcId, &remoteEndPt, BIOS_WAIT_FOREVER);

一旦识别出远程端点,客户端应用程序就可以启动一个循环来发送消息并等待响应

while(TRUE)
{
        RPMessage_send(handle, dstProc, remoteEndPt, myEndPt, (Ptr)msg, msg_len);

/* wait a for a response message: */
RPMessage_recv(handle, (Ptr)resp, &resp_len, &remoteEndPt,
        &remoteProcId, IPC_RPMESSAGE_TIMEOUT_FOREVER);

11 Writing IPC Applications on Linux

在 linux 中,rpmsg 字符驱动程序由内核初始化,它为每个远程 rpmsg_chrdev 服务创建一个用户空间 /dev 条目。 开发人员可以利用“rpmsg_char_helper”库轻松连接到远程端点并开始通信。

rproc_device _t *dev = rproc_device_find_for_name("r5f-main-0-core-1");
rproc_char_device_t *cdev = rproc_device_find_chrdev_by_remote_port(dev, REMOTE_SERVICE_ENDPOINT);
rproc_char_endpt_t *ept = rproc_char_device_create_endpt(cdev, "local-endpoint-name", RPMSG_ADDR_ANY);
char *path = rproc_char_endpt_get_dev_name(ept);
int fd = open(path, O_RDWR);

经过这些步骤,应用程序就有了一个可以用来读写消息的fd

while(1) {
        write(fd, msg, msg_len);
        read(fd, resp, resp_len);
}
  • 7
    点赞
  • 40
    收藏
    觉得还不错? 一键收藏
  • 4
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值