基于RK3562浅谈多核异构与核间通信

一、何为多核异构(AMP)?

在软件上区分的话,多核处理器有三种运行模式:

  • AMP(非对称多处理)

  • SMP(对称多处理)

  • BMP(受约束多处理):BMP运行模式与 SMP类似,同样也是一个OS管理所有的核心,但开发者可以指定将某个任务仅在某个指定内核上执行。

1.1 多核异构AMP简介

多核异构系统是⼀种将同⼀颗 SoC 芯片中不同处理器核⼼分别独⽴运⾏不同平台的计算系统。同时支持SMP (Symmetric Multi-Processing) 对称多处理系统AMP (Asymmetric Multi-Processing) 非对称多处理系统。

多核异构系统将传统平台两套系统合二为一。在传统平台中,Linux系统和实时性系统往往是完全独立的两套系统,需要完整的两颗处理器和两套外围电路。而在多核异构系统中,通过合理的处理器核心、外设等资源划分,同⼀颗 SoC 芯片能够独立运行Linux系统和实时性系统,满足系统在软件功能和硬件外设的丰富性要求的同时,满⾜系统的实时性要求

多核异构系统应用于产品设计中,还具有明显的性价比优势和产品体积优势。目前已经广泛应用于电力、工控等行业应用和扫地机等消费级产品中。

1.2 AMP案例

二、AMP编译构建

编译环境均在docker环境下配置,读者自行修改路径

RK3562这款soc的CPU核心为四核A53+M0组合,其中AMP支持3AP+1AP、4AP+1MCU组合,其中MCU为cortex-M0核,主频200MHz。

用A53跑RTOS或者裸机太浪费了,于是笔者这篇博文仅介绍4AP+MCU的AMP组合,其中AP跑通用Linux,MCU跑RT-Thread。

详见RK官方文档:Rockchip_RK3562_Quick_Start_AMP_MCU_CN.pdf

2.1 MCU编译

2.1.1 配置编译环境

export RTT_ROOT=/workspace/rk3562j-amp/amp-sdk-v1.0/rtos
export BSP_ROOT=/workspace/rk3562j-amp/amp-sdk-v1.0/rtos/bsp/rockchip/rk3562-mcu
scons --menuconfig

在 RT-Thread 里“menuconfig 里打开串口”并不是把硬件外设时钟/引脚一并配好,而是只做两件事:
1.让 串口设备驱动源码 参与编译(drv_usart.c 会被加进工程)。
2.给该驱动注册一个 rt-thread 设备名(如 uart0、uart1),这样应用就能用 rt_device_find("uart1")、rt_kprintf() 等标准接口。
其余 硬件层面的使能——时钟、GPIO 复用、中断号——必须另外做

2.1.2 编译RTOS项目

提前下载好gcc-arm-none-eabi-10-2020-q4-major-x86_64-linux

cd /workspace/rk3562j-amp/amp-sdk-v1.0/rtos/bsp/rockchip/led_flash
scons -c
scons
打包成amp.img镜像
./mkimage.sh  

2.2 U-Boot编译

CONFIG_AMP=y
CONFIG_ROCKCHIP_AMP=y
在.config中添加上述两行或者make menuconfig选择AMP选项

export PATH=/workspace/rk3562j/rk3562-buildroot-2021.11-sdk-v1.0/prebuilts/gcc/linux-x86/aarch64/gcc-arm-10.3-2021.07-x86_64-aarch64-none-linux-gnu/bin:$PATH
make distclean  
make rk3562_defconfig   
./make.sh CROSS_COMPILE=aarch64-none-linux-gnu- rk3562 --spl-new  

2.3 Kernel编译

设备树的修改见下文,此次仅介绍编译流程

AMP-内核
cd ./rk3562j-amp/Kernel/
export PATH=/workspace/rk3562j/rk3562-buildroot-2021.11-sdk-v1.0/prebuilts/gcc/linux-x86/aarch64/gcc-arm-10.3-2021.07-x86_64-aarch64-none-linux-gnu/bin:$PATH
make CROSS_COMPILE=aarch64-none-linux-gnu- ARCH=arm64 distclean  
make -j8 CROSS_COMPILE=aarch64-none-linux-gnu- ARCH=arm64 rockchip_linux_defconfig   
使用RT-Linux的话:make -j8 CROSS_COMPILE=aarch64-none-linux-gnu- ARCH=arm64 rockchip_linux_defconfig rockchip_rt.config  
make -j8 CROSS_COMPILE=aarch64-none-linux-gnu- ARCH=arm64 tl3562-minievm-led-flash.img  

2.4 Buildroot编译(通用)

./build.sh tl3562_minievm_defconfig  
非必需:make menuconfig  
       make savedefconfig(执行如下命令,保存 Buildroot 配置项。配置会自动保存至"configs/rockchip_rk3562_defconfig"中。)
cd buildroot  
rm -r output  
./envsetup.sh rockchip_rk3562  
cd ..
./build.sh buildroot  

三、AMP相关的设备树修改

3.1 CPU核心的内存分配速览

参考创龙科技对板载DDR的划分。如果DDR内存紧张,其中预留的AMP框架的CPU0-3(A53)内存可去掉,扩充至其它模块。

3.2 Linux端资源配置

rk3562-amp.dtsi 是 Linux Kernel 用来集中管理多核异构系统的共享资源的设备树文件

AMP 驱动相关文件: <AMP_SDK>/kernel/drivers/soc/rockchip/rockchip_amp.c

3.2.1 Linux kernel内存资源

DRAM 是系统私有的运行内存。Linux Kernel 默认是将所有 DDR 资源都作为 DRAM 使用,因此多核异构系统中,需要在 Linux Kernel DTS 上,将其他系统的 DRAM 位置保留出来。

DDR内存分配提前规划好,避免冲突,可参考核心板厂家预设的DDR内存分配。

reserved-memory {
		#address-cells = <2>;
		#size-cells = <2>;
		ranges;

		/* remote amp core address */
		amp_shmem_reserved: amp-shmem@7800000 {
			reg = <0x0 0x7800000 0x0 0x300000>;
			no-map;
		};

		/* mcu address */
		mcu_reserved: mcu@7b00000 {
			reg = <0x0 0x7b00000 0x0 0x100000>;
			no-map;
		};

		rpmsg_reserved: rpmsg@7c00000 {
			reg = <0x0 0x07c00000 0x0 0x400000>;
			no-map;
		};

		rpmsg_dma_reserved: rpmsg-dma@8000000 {
			compatible = "shared-dma-pool";
			reg = <0x0 0x08000000 0x0 0x100000>;
			no-map;
		};
		amp_core0_mem_reserved: amp-core0-mem@9000000 {
			reg = <0x0 0x09000000 0x0 0x00800000>;
			no-map;
		};
		amp_core1_mem_reserved: amp-core1-mem@9800000 {
			reg = <0x0 0x09800000 0x0 0x00800000>;
			no-map;
		};
		amp_core2_mem_reserved: amp-core2-mem@a000000 {
			reg = <0x0 0x0a000000 0x0 0x00800000>;
			no-map;
		};
		amp_core3_mem_reserved: amp-core3-mem@a800000 {
			reg = <0x0 0x0a800000 0x0 0x00800000>;
			no-map;
		};
	};  

3.2.2 Linux Kernel 外设资源

Linux Kernel 默认将所有芯片资源都定义到了 DTS 中,因此,当 AMP 需要在 Linux Kernel 之外使用外设资源时,需要先在 DTS 中关闭对应的模块,将资源让给 AMP 的其他系统使用。

下面以 RK3562 EVB1 中,Linux Kernel 将 I2C1 资源转让给 RTOS 使用为例:

先找到 rk3562.dtsi 中,I2C1 的定义。

    i2c1: i2c@ffa00000 {
		compatible = "rockchip,rk3562-i2c", "rockchip,rk3399-i2c";
		reg = <0x0 0xffa00000 0x0 0x1000>;
		clocks = <&cru CLK_I2C1>, <&cru PCLK_I2C1>;
		clock-names = "i2c", "pclk";
		interrupts = <GIC_SPI 13 IRQ_TYPE_LEVEL_HIGH>;
		pinctrl-names = "default";
		pinctrl-0 = <&i2c1m0_xfer>;
		#address-cells = <1>;
		#size-cells = <0>;
		status = "disabled";
	};

先在最后的DTS中关闭i2c1
    @i2c1 {
		status = "disabled";
	};

从 DTS 中,I2C1 的资源配置中,可以看到 I2C1 需要用到:中断资源、引脚资源、时钟资源

3.2.2.1 中断配置 (AP+MCU不需要配置)

3AP+AP需要配置,原因是AP走的是GIC管理中断线,而MCU走的是NVIC。

3.2.2.2 引脚配置

3.2.2.3 时钟资源

3.3 MCU资源配置

<AMP_SDK>/rtos/bsp/rockchip/rk3562-mcu/Image/amp.its

该文件中的images {mcu {load =<0x07b00000>;表示将 RT-Thread MCU DRAM 设置在物理地址 0x07b00000。该地址与DTS的reserved-memory的相匹配。

3.3.1 MCU中断配置

与 MCU 直连的中断走 NVIC 的接口,其余的中断需要走 INTMUX 接口

<AMP_SDK>/hal/project/rk3562/src/test_demo.c

3.3.2 MCU引脚配置

// <AMP_SDK>/rtos/bsp/rockchip/rk3562-32/board/rk3562_evb1_lp4x/iomux.c
void i2c1_m0_iomux_config(void)
{
    HAL_PINCTRL_SetIOMUX(GPIO_BANK0,
    GPIO_PIN_B3 | GPIO_PIN_B4,
    PIN_CONFIG_MUX_FUNC1);
} 
void rt_hw_iomux_config(void)
{
    // ...  
    i2c1_m0_iomux_config();
    // ...
}  

3.3.3 MCU时钟配置

RT-Thread 中自带 CRU 模块,不需要再进行时钟开关配置。

scons menuconfig配置I2C1驱动参与编译后,至此,即可使用 RT-Thread 的 I2C 接口,对 I2C1 进行操作。

四、AMP启动流程

开发板重新上电启动,在 U-Boot 启动阶段将读取 amp.img 镜像文件,解析 amp.img中的配置信息(配置信息由 amp.its 配置文件保存在 amp.img),并根据配置信息加载 Baremetal或RT-Thread(RTOS)工程至指定内存地址,然后启动指定 CPU 运行程序。

所以对于AP+MCU的AMP架构来说,MCU固件会比Linux更早的运行。这么做的好处是增强了CPU的实时性,毕竟相比加载MCU固件,加载Linux kernel还是太慢了。

五、核间通信

参考博文(API):https://www.jianshu.com/p/c7cdad8273ed

5.1 通信方案

RK AMP 目前核间中断触发方式支持 Mailbox、软件中断,共享内存 Linux 仅支持 uncache,其余默认支持cacheable

Linux 下 Mailbox 中断的方式参考如下路径的代码: drivers/rpmsg/rockchip_rpmsg_mbox.c

Linux 下软件中断的方式参考如下路径的代码: drivers/rpmsg/rockchip_rpmsg_softirq.c

Bare-metal 下 RPMsg-Lite 参考如下路径的代码:

hal/middleware/rpmsg-lite/lib/rpmsg_lite/porting/platform/RKXX/rpmsg_platform.c

RTOS 下 RPMsg-Lite 参考如下路径的代码:

rtos/rockchip/common/drivers/rpmsglite/lib/rpmsg_lite/porting/platform/RKXX/rpmsg_platform.c

5.1.1 核间中断触发

5.1.1.1 Mailbox中断

使用 RK Mailbox 模块进行核间通信,在触发 Mailbox 中断的同时,可以传输一个 32 bit 的 Command 寄存器数据和一个 32 bit 的 Data 寄存器数据 。

可通过Data 寄存器数据读取四字节数据。

mailbox外设负责产生中断事件,GIC 负责把事件路由到 CPU 并管理优先级/嵌套。在 ISR 里必须先处理外设(情源),再处理中断控制器(放行)

5.1.1.2 软件触发中断

使用 GIC SPI 中断,即共享外设中断中的 reserved irq,通过主动 Send Pending 触发

5.1.1.3 GIC SGI中断

使用 GIC SGI,即软中断触发。由于 Linux SMP 占用了8个 non-secure SGI 中断号,而另外8个 secure的SGI 中断号需要特殊申请。因此,SGI 触发的方式常用于多个从核进行同步。

5.1.2 RPMsg协议方案

RPMsg 是在 VirtIo 上实现的一个消息传递机制,VirtIo 是一种用来实现虚拟化 IO 的通用架构,类似的虚拟网卡,虚拟磁盘等都是用这种技术。VritIo 中基于 VirtIo-Ring,通过共享内存实现数据的发送/接收,vring 是单向的,一个 vring 专用于发送数据到 Remote Core,另一个 vring 用于从 Remote Core 接收数据。

因此从整体框架上看,RPMsg 是由 Master Core 和 Remote Core 的核间中断,以及 vring0、vring1、vdev buffer 三段 Shared Memory 构成。

主-从核心通过中断和共享内存的方式进行通信,内存的管理由主核负责,在每个通信方向上都有 USED 和 AVAIL 两个缓冲区,这两个缓冲区可以按照 RPMsg 的消息格式分成一块一块,由这些内存块可以链接成一个环。

RPMsg 每次发送的最大数据长度取决于 payload 长度,这个长度在SDK中默认为 512 Bytes,由于 RPMsg还带有16 Bytes的数据头,因此一次性传输的最大数据量为 496 Bytes,详细可以参考drivers/rpmsg/virtio_rpmsg_bus.c

5.2 驱动注意点

内存的管理由 Master 负责,MCU 作为 Remote 端,从 vring 中取到的 buffer 地址为真实物理地址,所以在发送/接收时都需要对申请到地址进行偏移处理。

void *platform_patova(uint32_t addr)
{
#ifdef HAL_MCU_CORE
    addr -= RL_PHY_MCU_OFFSET;
#endif
    return ((void *)(char *)addr);
}  

Linux端的callback函数是在软中断上下文中执行,不要休眠,可再加上tasklet或者工作队列做延迟处理。

5.3 RPMsg编译配置

5.3.1 Linux端

Linux kernel编译配置
# 开启MailBox支持
CONFIG_MAILBOX=y
CONFIG_ROCKCHIP_MBOX=y
## MailBox 中断触发
CONFIG_RPMSG_ROCKCHIP_MBOX=y
CONFIG_RPMSG_VIRTIO=y  
开启TTY的支持(可选)  
CONFIG_RPMSG_TTY=y  

Linux rpmsg应用程序使用交叉编译,工具链使用buildroot构建的产物,例如
source /workspace/rk3562j/rk3562-buildroot-2021.11-sdk-v1.0/buildroot/output/rockchip_rk3562/host/environment-setup  

5.3.2 RTOS端

## 开启RPMsg-Lite 支持
CONFIG_RT_USING_RPMSG_LITE=y
## 开启Linux+RTT RPMsg
CONFIG_RT_USING_LINUX_RPMSG=y  

然后整体编译命令参考第二节,基于RK官方烧录器使用强制地址烧录方式烧录各个模块,也或者直接烧录update.img。

5.4 RPMSG使用示例

Kernel RPMSG 核间通信框架,底层适配使用 VirtIO 方案。主要驱动代码路径如下:

kernel/drivers/rpmsg/rpmsg_core.c
kernel/drivers/rpmsg/virtio_rpmsg_bus.c
kernel/drivers/rpmsg/rockchip_rpmsg_softirq.c
kernel/include/linux/rpmsg/rockchip_rpmsg.h  

5.4.1 Linux端有两种路线实现rpmsg

5.4.1.1 Linux驱动方法

static struct rpmsg_driver tty_drv = {
    .drv.name   = "rpmsg-tty",   // 必须 = MCU 端 EP_NAME
    .id_table   = NULL,          // 只按名字匹配
    .probe      = tty_probe,
    .remove     = tty_remove,
    .callback   = tty_cb,
};
module_rpmsg_driver(tty_drv);

5.4.1.2 Linux应用层访问设备节点方法(推荐,调试简单)

用 /dev/rpmsg_ctrl0 动态建端点
int fd = open("/dev/rpmsg0", O_RDWR);
write(fd, "hello", 5);             // 发给 MCU
read (fd, buf, sizeof(buf));       // 读回显
close(fd);

5.4.2 rpmsg示例程序逻辑

5.4.2.1 Linux端设备树修改

5.4.2.1.1 外设节点关闭

参考第三节

&i2c3 {
	status = "disabled";
};
&uart2 {
	status = "disabled";
};

5.4.2.1.2 配置 rockchip_amp 节点

参考第三节

&rockchip_amp {
	clocks = <&cru FCLK_BUS_CM0_CORE>, <&cru CLK_BUS_CM0_RTC>,
		<&cru PCLK_MAILBOX>, <&cru PCLK_INTC>,
		<&cru PCLK_TIMER>, <&cru CLK_TIMER4>, <&cru CLK_TIMER5>,
		<&cru SCLK_UART2>, <&cru PCLK_UART2>,
        <&cru CLK_I2C3>, <&cru PCLK_I2C3>;

	amp-irqs = /bits/ 64 <GIC_AMP_IRQ_CFG_ROUTE(147, 0xd0, CPU_GET_AFFINITY(3, 0))
							GIC_AMP_IRQ_CFG_ROUTE(64, 0xd0, CPU_GET_AFFINITY(3, 0))>;

	pinctrl-names = "default";
	pinctrl-0 = <&uart2m0_xfer>, <&i2c3m0_xfer>;
};

5.4.2.1.3 配置 rpmsg 节点

link-id 参数为运行案例程序 CPU 序号, 0x03 为 Cortex-A53(CPU3), 0x04 为 CortexM0(MCU)。

&rpmsg {
	/* CPU3: link-id 0x03; MCU: link-id 0x04; */
	rockchip,link-id = <0x04>;
};

5.4.2.2 rpmsg通信开发注意点

端点地址选择准则如下:

本地端点地址(RPMSG_MASTER_ADDR):由于 1024 及以下的端点地址被 Linux 内核使用,因此本地端点地址需指定大于 1024。

5.5 RPMSG常用API

5.5.1 rpmsg_ns_bind()

监听对方的ns_announce,对方不执行ns_announce就收不到信息

让当前核 登记一个全局回调,一旦对端(或本端)创建/销毁名为“xxx”的 RPMsg 端点(endpoint),内核就把这个消息广播过来,回调函数 rpmsg_ns_cb 会被立即调用,从而 动态发现对端服务。

就是 “订阅对端服务上线/下线通知” 的函数,让 RPMsg 通信从“写死地址”升级为“自动发现”。

5.5.2 rpmsg_lite_wait_for_link_up(info->instance)

是 RPMsg-Lite 协议栈里 “等待对端就绪” 的阻塞API,功能可一句话概括为:

让当前核(通常是 remote 核)休眠/阻塞,直到 Master 核(如 Linux 或 CM33)完成 RPMsg 初始化并把共享内存中的 link_state 置为 UP,即AP侧 virtio ready,函数才返回;返回前保证 msg 通道已打通,可安全创建 endpoint 并收发数据。

5.5.3 rpmsg_ns_announce()

MCU侧将新建的本地端点信息通知给AP侧,如果驱动中name匹配的话,AP侧动态创建设备节点/dev/rpmsg0,记录这个通道的信息;否则不会ap侧不会自动创建/dev/rpmsg0,仅仅只是记录下ns信息。

5.5.4 ioctl(fd, RPMSG _CREATE_EPT_IOCTL, &ept_info)

AP侧主动创建/dev/rpmsg0设备文件,创建本地端点。只要mcu侧的对应端点创建且链路up,不向mcu侧announce也能正常通讯。

5.6 RPMSG:MCU端程序

/**
 * Copyright (C) 2013 Guangzhou Tronlong Electronic Technology Co., Ltd. - www.tronlong.com
 *
 * @file main.c
 *
 * @brief Example application main file.
 * This application flash TL3562-EVM LED1, LED2.
 *
 * @author Tronlong <support@tronlong.com>
 *
 * @version V1.0
 *
 * @date 2024-04-19
 **/

#include <stdio.h>
#include <string.h>
#include <rtthread.h>
#include <rtdevice.h>
#include <rthw.h>
#include <hal_base.h>
#include "rpmsg_lite.h"
// #include "rpmsg_queue.h"
#include "rpmsg_ns.h"

/* CPU0 as master and MCU as remote */
#define MASTER_ID ((uint32_t)0)
#define REMOTE_ID ((uint32_t)4)

/*
 * Define remote endpoint id
 * Endpoint name "rpmsg_chrdev" is compatible to linux kernel rpmsg char driver.
 * Endpoint 0x4004 is compatible to rpmsg_echo linux demo.
 */
#define RPMSG_HAL_REMOTE_TEST_EPT      0x4004U
#define RPMSG_HAL_REMOTE_TEST_EPT_NAME "rpmsg_chrdev"

/*
 * Rpmsg share memory start address, end address.
 * These are defined in the linked script gcc_bus_m0.ld
 */
extern uint32_t __linux_share_rpmsg_start__[];
extern uint32_t __linux_share_rpmsg_end__[];

#define RPMSG_LINUX_MEM_BASE ((uint32_t)&__linux_share_rpmsg_start__)
#define RPMSG_LINUX_MEM_END  ((uint32_t)&__linux_share_rpmsg_end__)
#define RPMSG_LINUX_MEM_SIZE (2UL * RL_VRING_OVERHEAD)

struct rpmsg_info_t {
    struct rpmsg_lite_instance *instance;
    struct rpmsg_lite_endpoint *ept;
    uint32_t master_ept_id;
};

/*
 * rpmsg_share_mem_check() - Check rpmsg share memory
 * 
 * Returns: HAL_OK on success or a negative error code on failure.
 */
static HAL_Status rpmsg_share_mem_check(void)
{
    if ((RPMSG_LINUX_MEM_BASE + RPMSG_LINUX_MEM_SIZE) > RPMSG_LINUX_MEM_END)
        return HAL_ERROR;

    return HAL_OK;
}

/* New endpoint NS callback function */
void rpmsg_ns_cb(uint32_t new_ept, const char *new_ept_name, uint32_t flags, void *user_data)
{
    char ept_name[RL_NS_NAME_SIZE];

    strncpy(ept_name, new_ept_name, RL_NS_NAME_SIZE);
    printf("rpmsg remote: new_ept-0x%lx name-%s\n", new_ept, ept_name);
}

/* Rpmsg receive callback function */
static int32_t remote_ept_cb(void *payload, uint32_t payload_len, uint32_t src, void *priv)
{
    int ret;
    struct rpmsg_info_t *info = (struct rpmsg_info_t *)priv;

    info->master_ept_id = src;
    ret = rpmsg_lite_send(info->instance, info->ept, info->master_ept_id, payload, payload_len, RL_BLOCK);
    if (ret) {
        printf("rpmsg_lite_send error!\n");
    }

    return ret;
}

/* Init rpmsg demo communicating with Linux */
static void rpmsg_echo_init(void)
{
    uint32_t master_id, remote_id;
    struct rpmsg_info_t *info;
    uint32_t ept_flags;
    void *ns_cb_data;
    int ret;

    /* Check rpmsg share memory */
    ret = rpmsg_share_mem_check();
    if (ret != HAL_OK) {
        printf("Share memory size error!\n");
        goto main_out1;
    }

    /* CPU0 as master and MCU as remote */
    master_id = MASTER_ID;
    remote_id = REMOTE_ID;
    printf("rpmsg remote: remote core cpu_id-%ld\n", remote_id);
    printf("rpmsg remote: shmem_base-0x%lx shmem_end-0x%lx\n", RPMSG_LINUX_MEM_BASE, RPMSG_LINUX_MEM_END);

    info = malloc(sizeof(struct rpmsg_info_t));
    if (info == NULL) {
        printf("info malloc error!\n");
        goto main_out1;
    }

    /* Init rpmsg lite on the remote core */
    info->instance = rpmsg_lite_remote_init((void *)RPMSG_LINUX_MEM_BASE, RL_PLATFORM_SET_LINK_ID(master_id, remote_id), RL_NO_FLAGS);
    if (info->instance == RL_NULL) {
        printf("init rpmsg lite remote failed\n");
        goto main_out2;
    }

    /* Wait for master core link up, timeout: 10s */
    rpmsg_lite_wait_for_link_up(info->instance, 10000);
    printf("rpmsg remote: link up! link_id-0x%lx\n", info->instance->link_id);

    /* Registers application nameservice callback */
    rpmsg_ns_bind(info->instance, rpmsg_ns_cb, &ns_cb_data);

    /* Create a rpmsg lite endpoint */
    info->ept = rpmsg_lite_create_ept(info->instance, RPMSG_HAL_REMOTE_TEST_EPT, remote_ept_cb, info);
    if (info->ept == RL_NULL) {
        printf("Create a rpmsg lite endpoint failed\n");
        goto main_out2;
    }

    /* Announce the rpmsg lite endpoint name to master core */
    ept_flags = RL_NS_CREATE;
    rpmsg_ns_announce(info->instance, info->ept, RPMSG_HAL_REMOTE_TEST_EPT_NAME, ept_flags);

    return;

main_out2:
    free(info);

main_out1:
    return;
}

int main(void)
{
    printf("MCU RTOS - RPMSG ECHO\n");

    rpmsg_echo_init();

    return 0;
}

5.7 Linux端应用程序

#include <stdio.h>
#include <stdbool.h>
#include <stddef.h>
#include <errno.h>
#include <string.h>
#include <stdlib.h>
#include <dirent.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdint.h>
#include <pthread.h>
#include <signal.h>
#include <sys/ioctl.h>
#include <sys/stat.h>
#include <linux/rpmsg.h>

#define NUM_ITERATIONS (10)
/*
 * This message is from kernel/drivers/rpmsg/virtio_rpmsg_bus.c
 * Local addresses are dynamically allocated on-demand.
 * We do not dynamically assign addresses from the low 1024 range,
 * in order to reserve that address range for predefined services.
 */
#define RPMSG_MASTER_ADDR (0x1005)
/* Rpmsg remote addr is compatible to rpmsg_echo baremetal/rtos demo */
#define RPMSG_REMOTE_ADDR (0x4004)

static int g_efd = 0;

/* Returns a pointer to allocated memory, needs to be freed once done */
char *file_deref_link(char *fpath, char *link_name)
{
	char path[512] = { 0 };
	char rel_path[256] = { 0 };
	int n, nr;

	n = snprintf(path, 256, "%s/%s", fpath, link_name);
	if (n < 0 || n >= 256) {
		fprintf(stderr, "%s: could not create full path string\n",
			__func__);
		return NULL;
	}

	nr = readlink(path, rel_path, sizeof(rel_path));
	if (nr < 0) {
		fprintf(stderr, "%s: readlink failed for %s\n", __func__, path);
		return NULL;
	}

	if (n + nr >= 512) {
		fprintf(stderr, "%s: full relative path exceeds buffer size, n = %d nr = %d\n",
			__func__, n, nr);
		return NULL;
	}

	memset(path, 0, sizeof(path));
	sprintf(path, "%s/%s", fpath, rel_path);

	return realpath(path, NULL);
}

static int file_read_string(char *fpath, char *buf, int size)
{
	int fd, bytes;

	fd = open(fpath, O_RDONLY);
	if (fd < 0) {
		fprintf(stderr, "could not open %s: errno = %d\n",
			fpath, errno);
		return -errno;
	}

	bytes = read(fd, buf, size);
	close(fd);
	if (bytes <= 0) {
		fprintf(stderr, "could not read %s: errno = %d\n",
			fpath, errno);
		return -EIO;
	}
	if (bytes >= size) {
		fprintf(stderr, "%d bytes read from %s are larger than size %d\n",
			bytes, fpath, size);
		return -EIO;
	}

	/* suppress the newline */
	buf[bytes - 1] = '\0';

	return bytes;
}

static int file_read_value(char *fpath)
{
	char buf[32];
	int ret;

	ret = file_read_string(fpath, buf, sizeof(buf));
	if (ret < 0)
		return ret;

	return strtol(buf, NULL, 0);
}

static int rpmsg_char_get_local_endpt(void)
{
	char fpath[512] = { 0 };
	char rpmsg[16] = { 0 };
	char *rpath;

	sprintf(rpmsg, "rpmsg%u", 0);
	rpath = file_deref_link("/sys/class/rpmsg", rpmsg);
	if (!rpath) {
		fprintf(stderr, "%s: rpmsg0 realpath failed\n",
			__func__);
		return -ENOENT;
	}

	sprintf(fpath, "%s/src", rpath);
	free(rpath);

	return file_read_value(fpath);
}

/*
 * rpmsg_char_destroy_eptdev() - Destroy endpoint dev
 * 
 * Returns: 0 on success or a negative error code on failure.
 */
static int rpmsg_char_destroy_eptdev(int fd)
{
	int ret;

	ret = ioctl(fd, RPMSG_DESTROY_EPT_IOCTL, NULL);
	if (ret) {
		fprintf(stderr, "%s: could not destroy endpt %d\n",
			__func__, fd);
		return ret;
	}

	close(fd);
	return 0;
}

/*
 * rpmsg_char_open_eptdev() - Open endpoint dev
 * 
 * Returns: 0 on success or a negative error code on failure.
 */
static int rpmsg_char_open_eptdev(void)
{
	int efd, endpt;

	efd = open("/dev/rpmsg0", O_RDWR);
	if (efd < 0) {
		fprintf(stderr, "failed to open eptdev /dev/rpmsg0\n");
		return -errno;
	}

	/*
	 * Local endpoint is assigned after the open call.
	 * Determine if rpmsg endpoint dev is opening normally
	 * by checking local endpoint is assigned or not.
	 */
	endpt = rpmsg_char_get_local_endpt();
	if (endpt < 0) {
		rpmsg_char_destroy_eptdev(efd);
		fprintf(stdout, "%s: get_local_endpt failed %d \n",__func__, endpt);
		return endpt;
	}

	g_efd = efd;

	return 0;
}

/*
 * rpmsg_char_create_eptdev() - Create endpoint dev
 * @eptdev_name: endpoint name
 * @local_port: local endpoint
 * @remote_port: remote endpoint
 * 
 * Returns: 0 on success or a negative error code on failure.
 */
static int rpmsg_char_create_eptdev(char *eptdev_name,
				     int local_port, int remote_port)
{
	int fd, ret;
	struct rpmsg_endpoint_info ept_info = { 0 };

	fd = open("/dev/rpmsg_ctrl0", O_RDWR);
	if (fd < 0) {
		fprintf(stderr, "%s: could not open rpmsg_ctrl0 dev node\n", __func__);
		return fd;
	}

	ept_info.src = local_port;
	ept_info.dst = remote_port;
	sprintf(ept_info.name, "%s", eptdev_name);

	ret = ioctl(fd, RPMSG_CREATE_EPT_IOCTL, &ept_info);
	if (ret) {
		fprintf(stderr, "%s: ctrl_fd ioctl: %s\n", __func__,
			strerror(errno));
	}

	close(fd);

	return ret;
}

/*
 * send_msg() - Send rpmsg data
 * @fd: rpmsg endpoint dev fd
 * @msg: data
 * @len: data len
 * 
 * Returns: 0 on success or a negative error code on failure.
 */
int send_msg(int fd, char *msg, int len)
{
	int ret = 0;

	ret = write(fd, msg, len);
	if (ret < 0) {
		perror("Can't write to rpmsg endpt device\n");
		return -1;
	}

	return ret;
}

/*
 * recv_msg() - Receive rpmsg data
 * @fd: rpmsg endpoint dev fd
 * @msg: data buf
 * @len: receive data len
 * 
 * Returns: 0 on success or a negative error code on failure.
 */
int recv_msg(int fd, int len, char *reply_msg, int *reply_len)
{
	int ret = 0;

	/* Note: len should be max length of response expected */
	ret = read(fd, reply_msg, len);
	if (ret < 0) {
		perror("Can't read from rpmsg endpt device\n");
		return -1;
	} else {
		*reply_len = ret;
	}

	return 0;
}

void usage(void)
{
	printf("Usage: rpmsg_rw [-n <num_msgs>] \n");
	printf("\t\tDefaults: num_msgs: %d\n", NUM_ITERATIONS);
}

int main(int argc, char const *argv[])
{
	char packet_buf[512] = { 0 };
	int num_msgs = NUM_ITERATIONS;
	int ret, i, packet_len, c;

	while (1) {
		c = getopt(argc, (void *)argv, "n:h");
		if (c == -1)
			break;

		switch (c) {
		case 'n':
			num_msgs = atoi(optarg);
			break;
		case 'h':
		default:
			usage();
			exit(0);
		}
	}

	/* Create rpmsg endpoint dev */
	ret = rpmsg_char_create_eptdev("rpmsg-char-0", RPMSG_MASTER_ADDR, RPMSG_REMOTE_ADDR);
	if (ret) {
		fprintf(stderr, "%s: could not create end-point rpmsg-char-0\n", __func__);
		return ret;
	}

	/* Open rpmsg endpoint dev */
	ret = rpmsg_char_open_eptdev();
	if (ret) {
		fprintf(stderr, "%s: could not open end-point rpmsg-char-0\n", __func__);
		return ret;
	}

	/* 
	 * Send data to rpmsg remote core, then wait for data sending from rpmsg remote core,
	 * cycle num_msgs times.
	 */
	for (i = 0; i < num_msgs; i++) {
		memset(packet_buf, 0, sizeof(packet_buf));
		sprintf(packet_buf, "hello there %d!", i);
		packet_len = strlen(packet_buf);
		printf("Sending message #%d: %s\n", i, packet_buf);

		ret = send_msg(g_efd, (char *)packet_buf, packet_len);
		if (ret < 0) {
			printf("send_msg failed for iteration %d, ret = %d\n", i, ret);
			goto out;
		}
		if (ret != packet_len) {
			printf("bytes written does not match send request, ret = %d, packet_len = %d\n",
				i, ret);
		    goto out;
		}

		memset(packet_buf, 0, sizeof(packet_buf));
		printf("Receiving message #%d: ", i);
		ret = recv_msg(g_efd, 256, (char *)packet_buf, &packet_len);
		if (ret < 0) {
			printf("recv_msg failed for iteration %d, ret = %d\n", i, ret);
			goto out;
		}

		printf("%s\n", packet_buf);

		/*
		 * RK3562 Linux rpmsg is based on mailbox, RK3562 mailbox
		 * driver tx done status is polled by Linux hrtimer.
		 * tx poll period is 1ms, so need to sleep 5ms(slightly larger than)
		 * before sending another data.
		 */
		usleep(5000);
	}

out:
	ret = rpmsg_char_destroy_eptdev(g_efd);
	if (ret < 0)
		perror("Can't delete the endpoint device\n");

	return ret;
}

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

脆莓不想当 Linux 小白

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值