在IMX8MM上实现RPMSG通讯

一、rpmsg-lite与open-amp的区别

  笔者曾在zynq7020中使用过RPMSG,7020为双核A9,实现双核通讯时采用open-amp实现(基于vitis提供的库函数),而在IMX8MM这里我们实现的是四核A53与M4内核之间的通讯,使用rpmsg-lite而不是open-amp有以下好处:

        rpmsg-lite提供了代码大小的减少,API的简化和模块化的改进

        rpmsg-lite适用于较小的Cortex-M0+系统,而open-amp需要更多的内存和处理能力

        rpmsg-lite不依赖于virtio或remoteproc框架,可以与任何底层传输层一起使用

如果想要在M4核下实现open-amp,M4内核需具备以下条件

        M4核需要支持virtio和remoteproc框架,这些框架是open-amp的基础

        M4核需要有足够的内存和处理能力来运行open-amp库和应用程序 

        M4核需要与另一个核(例如Linux主机)建立可靠的通信通道

如果M4核不满足以上条件,可以考虑使用rmpsg-lite或其他轻量级的IPC方案,有些平台已经提供了在M4核上使用open-amp的示例代码,例如STM32MP12和i.MX 6SoloX3,用户可以参考这些示例代码来了解如何配置和加载M4核的固件,以及如何使用virtio设备进行跨核通信,另外还有RPMsg-TTY,RPMsg-TTY是一个在Linux内核中实现的虚拟TTY设备,它使用RPMSG协议与远程处理器进行通信。它可以通过/dev/ttyRPMSGx设备文件访问,并支持标准的TTY操作。

二、rmpsg-lite源码解析

  根据NXP官方提供的源码可在IAR上实现源码的编译,生成bin文件后可加载到linux系统上运行测试,下面一起看看rpmsg-lite的核心实现代码(具体实现过程可自行前往github上下载源码)

/*
 * Copyright (c) 2016, Freescale Semiconductor, Inc.
 * Copyright 2016-2020 NXP
 * All rights reserved.
 *
 * SPDX-License-Identifier: BSD-3-Clause
 */

#include 
#include 
#include 
#include "rpmsg_lite.h"
#include "rpmsg_queue.h"
#include "rpmsg_ns.h"
#include "pin_mux.h"
#include "clock_config.h"
#include "board.h"
#include "fsl_debug_console.h"
#include "FreeRTOS.h"
#include "task.h"

#include "fsl_uart.h"
#include "rsc_table.h"
/*******************************************************************************
 * Definitions
 ******************************************************************************/
#define RPMSG_LITE_LINK_ID            (RL_PLATFORM_IMX8MM_M4_USER_LINK_ID)
#define RPMSG_LITE_SHMEM_BASE         (VDEV0_VRING_BASE)
#define RPMSG_LITE_NS_ANNOUNCE_STRING "rpmsg-openamp-demo-channel"
#define RPMSG_LITE_MASTER_IS_LINUX

#define APP_DEBUG_UART_BAUDRATE (115200U) /* Debug console baud rate. */
#define APP_TASK_STACK_SIZE (256U)
#ifndef LOCAL_EPT_ADDR
#define LOCAL_EPT_ADDR (30U)
#endif
#define APP_RPMSG_READY_EVENT_DATA (1U)

typedef struct the_message
{
    uint32_t DATA;
} THE_MESSAGE, *THE_MESSAGE_PTR;

static volatile THE_MESSAGE msg = {0};
#ifdef RPMSG_LITE_MASTER_IS_LINUX
static char helloMsg[13];
#endif /* RPMSG_LITE_MASTER_IS_LINUX */

/*******************************************************************************
 * Prototypes
 ******************************************************************************/

/*******************************************************************************
 * Code
 ******************************************************************************/
static TaskHandle_t app_task_handle = NULL;/*任务句柄,空指针,freertos的任务句柄是一个指向任务控制块(TCB)的指针,它用于标识和管理任务。任务句柄有以下用途:
                                            创建、删除、挂起、恢复或改变任务的优先级时,需要传递任务句柄作为函数参数。
                                            查询或修改任务的状态或属性时,需要使用任务句柄作为函数返回值或参数。
                                            在中断服务程序(ISR)中发送信号给任务时,需要使用任务句柄作为函数参数。*/


//参考链接:https://community.nxp.com/t5/i-MX-Processors-Knowledge-Base/iMX8QXP-Use-RPMSG-to-wake-up-M4-and-A35/ta-p/1112941

static void app_nameservice_isr_cb(uint32_t new_ept, const char *new_ept_name, uint32_t flags, void *user_data)
{
}

#ifdef MCMGR_USED   /*MCMGR_USED是一个宏,用于指示FreeRTOS是否使用多核管理器(MCMGR)组件。MCMGR组件提供了一组API,用于管理和通信NXP微控制器上的多个核心。
                    它可以用于启动、停止、重置和同步核心,以及使用RPMsg-Lite发送和接收数据。MCMGR_USED宏可以在FreeRTOSConfig.h文件中定义,以启用或禁用MCMGR组件的使用。
                    如果它被定义为1,则MCMGR组件被FreeRTOS使用并在启动时初始化。如果它被定义为0或根本没有定义,则MCMGR组件不被FreeRTOS使用*/
/*!
 * @brief Application-specific implementation of the SystemInitHook() weak function.
 */
void SystemInitHook(void)
{
    /* Initialize MCMGR - low level multicore management library. Call this
       function as close to the reset entry as possible to allow CoreUp event
       triggering. The SystemInitHook() weak function overloading is used in this
       application. */
    (void)MCMGR_EarlyInit();
}
#endif /* MCMGR_USED */

static void app_task(void *param)
{
    volatile uint32_t remote_addr;
    struct rpmsg_lite_endpoint *volatile my_ept;
    volatile rpmsg_queue_handle my_queue;
    struct rpmsg_lite_instance *volatile my_rpmsg;
    volatile rpmsg_ns_handle ns_handle;

    /* Print the initial banner */
    (void)PRINTF("\r\nRPMSG Ping-Pong FreeRTOS RTOS API Demo...\r\n");

#ifdef MCMGR_USED
    uint32_t startupData;
    mcmgr_status_t status;

    /* Get the startup data */
    do
    {
        status = MCMGR_GetStartupData(&startupData);
    } while (status != kStatus_MCMGR_Success);

    my_rpmsg = rpmsg_lite_remote_init((void *)(char *)startupData, RPMSG_LITE_LINK_ID, RL_NO_FLAGS);

    /* Signal the other core we are ready by triggering the event and passing the APP_RPMSG_READY_EVENT_DATA */
    (void)MCMGR_TriggerEvent(kMCMGR_RemoteApplicationEvent, APP_RPMSG_READY_EVENT_DATA);
#else
    (void)PRINTF("RPMSG Share Base Addr is 0x%x\r\n", RPMSG_LITE_SHMEM_BASE);
    my_rpmsg = rpmsg_lite_remote_init((void *)RPMSG_LITE_SHMEM_BASE, RPMSG_LITE_LINK_ID, RL_NO_FLAGS);
    /*rpmsg_lite_remote_init是一个函数,它初始化了远程处理器上的RPMsg-Lite通信栈。它是RPMsg-Lite组件的一部分,
    RPMsg-Lite组件是远程处理器消息(RPMsg)协议的轻量级实现。RPMsg协议定义了一个标准化的二进制接口,用于在异构多核系统中实现多个核之间的通信。
     rpmsg_lite_remote_init函数接受三个参数:shmem_addr、link_id和init_flags。shmem_addr参数指定了用于通信的共享内存的基地址。
     link_id参数标识了哪个核与哪个核进行通信。init_flags参数控制一些初始化选项,比如是否使用静态或动态内存分配。
      rpmsg_lite_remote_init函数返回一个指向rpmsg_lite_instance结构体的指针,该结构体表示RPMsg-Lite组件的一个实例。
      这个指针可以用来创建端点并使用RPMsg-Lite API中的其他函数发送或接收消息。 rpmsg_lite_remote_init函数应该在每个核每个通信通道上只调用一次。
      它应该在两个核都初始化了它们的硬件和软件环境之后调用。*/
#endif /* MCMGR_USED */

    /*rpmsg_lite_is_link_up是一个函数,它检查RPMsg-Lite链接是否正常。如果链接正常,它返回RL_TRUE,否则返回RL_FALSE。当两个核都初始化了它们的RPMsg-Lite实例并准备好通信时,链接被认为是正常的。
    rpmsg_lite_is_link_up函数接受一个参数:rpmsg_lite_dev,它是一个指向rpmsg_lite_instance结构体的指针,该结构体表示RPMsg-Lite组件的一个实例。
    rpmsg_lite_is_link_up函数可以在使用RPMsg-Lite API中的其他函数发送或接收消息之前,轮询链接状态。*/
    while (0 == rpmsg_lite_is_link_up(my_rpmsg))
    {
    }
    (void)PRINTF("Link is up!\r\n");

    my_queue  = rpmsg_queue_create(my_rpmsg);
    /* ****************
    rpmsg_lite_create_ept是一个函数,它创建了一个RPMsg-Lite端点。端点是一个由本地地址标识的通信通道。端点可以用来使用RPMsg-Lite API中的其他函数发送或接收消息。
    rpmsg_lite_create_ept函数接受四个参数:rpmsg_lite_dev、addr、rx_cb、rx_cb_data。rpmsg_lite_dev参数是一个指向rpmsg_lite_instance结构体的指针,该结构体
    表示RPMsg-Lite组件的一个实例。addr参数指定了端点的本地地址。rx_cb参数是一个指向回调函数的指针,该回调函数将在这个端点收到消息时被调用。rx_cb_data参数是一个
    指向一些用户数据的指针,这些数据将被传递给回调函数。
        rpmsg_lite_create_ept函数返回一个指向rpmsg_lite_endpoint结构体的指针,该结构体表示RPMsg-Lite组件的一个端点。这个指针可以作为RPMsg-Lite API中其他函数的参数。
    rpmsg_lite_create_ept函数可以被多次调用,以创建不同地址和回调的不同端点。它还可以接受一个可选的最后一个参数,在RL_USE_STATIC_API选项被设置为1时,用来创建端点的内部
    上下文。如果没有设置这个选项,栈内部会调用env_alloc()来为它分配动态内存 */
    my_ept    = rpmsg_lite_create_ept(my_rpmsg, LOCAL_EPT_ADDR, rpmsg_queue_rx_cb, my_queue);
    /*rpmsg_ns_bind是一个函数,它将一个名称服务回调绑定到一个rpmsg设备上。当远程处理器上创建或销毁一个新的远程端点时,名称服务回调会被调用。回调可以用来创建或销毁与远程端点匹配的本地端点。
    rpmsg_ns_bind函数接受两个参数:rpdev和ns_cb。rpdev参数是一个指向rpmsg_device结构体的指针,该结构体表示一个RPMsg通道。ns_cb参数是一个指向回调函数的指针,该回调函数会被传入远程端点的名称、地址和标志。
    rpmsg_ns_bind函数在成功时返回0,在失败时返回错误码。
    rpmsg_ns_bind函数可以被需要根据远程处理器的动作动态创建或销毁端点的驱动程序使用。这只有在远程处理器具有VIRTIO_RPMSG_F_NS virtio设备特性位的情况下才可能。这个特性位意味着远程处理器支持在创建或销毁端点时发送名称服务通知。*/
    ns_handle = rpmsg_ns_bind(my_rpmsg, app_nameservice_isr_cb, ((void *)0));
    SDK_DelayAtLeastUs(1000000U, SDK_DEVICE_MAXIMUM_CPU_CLOCK_FREQUENCY);
    (void)rpmsg_ns_announce(my_rpmsg, my_ept, RPMSG_LITE_NS_ANNOUNCE_STRING, (uint32_t)RL_NS_CREATE);
    (void)PRINTF("Nameservice announce sent.\r\n");

#ifdef RPMSG_LITE_MASTER_IS_LINUX
    /* Wait Hello handshake message from Remote Core. */
    (void)rpmsg_queue_recv(my_rpmsg, my_queue, (uint32_t *)&remote_addr, helloMsg, sizeof(helloMsg), ((void *)0),//可自定义数据,例如代码段等核间通讯信息
                           RL_BLOCK);
#endif /* RPMSG_LITE_MASTER_IS_LINUX */


/*  pingpong机制是一种数据交换机制,我们可以不去等待接受模块(下级)处理结束,而是发送模块(上级)继续执行并将结果保存在ping路的缓存中,
    上级继续执行到一定时刻,下级模块处理完成将结果保存在pong路中),这样可以下级模块无需等待继续执行,上级也无需等待继续执行,转而将结果存储在ping路。
    这样便提高了处理效率。
*/
    while (msg.DATA <= 100U)
    {
        (void)PRINTF("Waiting for ping...\r\n");
        (void)rpmsg_queue_recv(my_rpmsg, my_queue, (uint32_t *)&remote_addr, (char *)&msg, sizeof(THE_MESSAGE),
                               ((void *)0), RL_BLOCK);
        msg.DATA++;
        (void)PRINTF("Sending pong...\r\n");
        (void)rpmsg_lite_send(my_rpmsg, my_ept, remote_addr, (char *)&msg, sizeof(THE_MESSAGE), RL_BLOCK);
    }

    (void)PRINTF("Ping pong done, deinitializing...\r\n");

    (void)rpmsg_lite_destroy_ept(my_rpmsg, my_ept);//删除端点
    my_ept = ((void *)0);
    (void)rpmsg_queue_destroy(my_rpmsg, my_queue);//删除接收数据的队列
    my_queue = ((void *)0);
    (void)rpmsg_ns_unbind(my_rpmsg, ns_handle);//取消绑定
    (void)rpmsg_lite_deinit(my_rpmsg);
    msg.DATA = 0U;

    (void)PRINTF("Looping forever...\r\n");

    /* End of the example */
    for (;;)
    {
    }
}

/*!
 * @brief Main function
 */
int main(void)
{
    /* Initialize standard SDK demo application pins */
    /* Board specific RDC settings */
    BOARD_RdcInit();

    BOARD_InitBootPins();
    BOARD_BootClockRUN();
    BOARD_InitDebugConsole();
    BOARD_InitMemory();

    copyResourceTable();

#ifdef MCMGR_USED
    /* Initialize MCMGR before calling its API */
    (void)MCMGR_Init();
#endif /* MCMGR_USED */
/*xTaskCreate创建一个新任务并将其添加到准备运行的任务列表中。 它需要指向任务入口函数的指针、任务的描述性名称、
堆栈深度、指向任务函数参数的指针、优先级值以及创建的 task1 的可选句柄。 xTaskCreate 只能用于创建可以不受限制地访问整个微控制器内存映射的任务。*/

/*freertos中的任务切换是通过使用一个定时器中断来实现的,每次定时器到期时,就会触发一次上下文切换1。上下文切换保存了当前运行任务的状态,
并恢复了下一个就绪任务的状态1。调度器根据任务的优先级和状态决定下一个要运行的任务,此处仅作演示,暂时不创建其他的任务,实际运用过程中可能存在多任务切换需求*/

    if (xTaskCreate(app_task, "APP_TASK", APP_TASK_STACK_SIZE, NULL, tskIDLE_PRIORITY + 1U, &app_task_handle) != pdPASS)//
    {
        (void)PRINTF("\r\nFailed to create application task\r\n");
        for (;;)
        {
        }
    }

    vTaskStartScheduler();

    (void)PRINTF("Failed to start FreeRTOS on core0.\r\n");
    for (;;)
    {
    }
}

三、编译及测试

在IMX8MM上实现RPMSG通讯_第1张图片 

你可能感兴趣的:(linux)