一、何为多核异构(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;
}
716

被折叠的 条评论
为什么被折叠?



