飞腾派部署openAMP(二) | 基于 Phytium-openAMP框架从核 GPIO 驱动
文章来源:郭霞
1. 项目简介
飞腾派开发板搭载异构系统级芯片(SoC),基于 ARMV8 架构,集成四核处理器(2×FTC664@1.8GHz + 2×FTC310@1.5GHz)。非对称多处理(AMP)系统要求支持同一芯片上多操作系统环境并行运行,OpenAMP 通过标准化共享内存方案,为异构多处理应用提供多核并行性利用能力,简化 SoC 协同机制。
《飞腾派部署 openAMP》系列将分两期发布,采用 “主核 Linux 系统 + 从核裸机系统” 的 openAMP 拓扑架构:
u 第一期《PIOSv2.1 部署与 AMP 启动》:阐述飞腾派基础操作系统 Phytium-Pi-OSv2.1(简称 PIOSv2.1)的部署流程,以及 Phytium-openAMP 的集成方法,实现主从核协同的 AMP 拓扑。
u 第二期《基于 Phytium-openAMP框架从核 GPIO 驱动》:基于首期构建的 OpenAMP 环境,实现并演示从核通过 OpenAMP 机制实现 GPIO 外设驱动的应用实例,具体以驱动飞腾派开发板上的LED20灯为例。
2. 项目准备
硬件规格:飞腾派
软件要求:开发板已经部署支持openAMP的PIOSV2.1
3. LED20硬件原理
(1) LED20在开发板上的位置
(2) LED20的硬件原理图
(3) 根据原理图,LED20连接到E37引脚,该引脚对应微控制器的GPIO1_8。因此,需要配置GPIO1_8为输出功能(Output Mode)以驱动LED20。本文我们利用Phytium-Standalone-SDK来实现。
4. Phytium-Standalone-SDK
(1) 基本介绍
a)项目地址
https://gitee.com/phytium_embedded/phytium-standalone-sdk
b) 项目基本功能介绍
本项目发布了 Phytium 系列 CPU 的 嵌入式软件开源开发工具包,包括板级支持包、第三方开源中间件、交叉编译构建工具、及其 Baremetal 参考例程,在支持多平台裸机应用开发的基础上,能够为多种RTOS提供外设驱动和配置构建工具。
(2) Phytium-Standalone-SDK源代码目录
example 目录:包含5大类案例。包括外设应用peripherals、网络应用network、多媒体应用media、存储类应用storage和其他系统类应用system。每一个example中包含了用于编译的makefile和用于配置的sdkconfig、Kconfig文件,用户基于这些文件可以直接编译生成可执行文件,并上板跑测这些example示例。本文使用example/system/amp/openamp_for_linux案例。
drivers目录:在本文中我们用到的driver模块,主要有drivers/iomux/fiopad/以及drivers/pin/fgpio/两个模块。
third-party目录phytium-opemAMP依赖第三方库openamp和libmetal。
(3) IOMUX 模块
1) IOMUX简介
飞腾派CPU支持 IO PAD 复用,用户可以通过配置控制寄存器来完成复用、上下拉电阻、驱动能力的调整以及延迟的选择。
2) 相关的API函数
a)FIOMuxInit初始化函数
函数原型:_WEAK void FIOMuxInit(void)
函数位置:phytium-standalone-sdk/board/firefly/fio_mux.c
函数功能:调用FIOPadCfgInitialize函数初始化IOPAD模块。
FIOPadCfgInitialize函数在drivers/iomux/fiopad/fiopad.c中实现。
b)FIOPadSetGpioMux初始化函数
函数原型:void FIOPadSetGpioMux(u32 gpio_id, u32 pin_id)
函数位置:phytium-standalone-sdk/board/firefly/fio_mux.c
函数功能:调用FIOPadSetFunc函数设置指定的gpio_id的pin_id的GPIO功能。
FIOPadSetFunc函数在drivers/iomux/fiopad/fiopad.c中实现。
函数参数:
u32 gpio_id:取值范围FGPIO_CTRL_0~FGPIO_CTRL_5
u32 pin_id:0-16
(4) GPIO 模块
1) GPIO模块简介
飞腾派CPU飞腾派集成 6 个 GPIO 控制器,每个控制器16位信号,共提供 96 位 GPIO 信号。
2) 相关的API函数
a)FGpioLookupConfig
函数原型:const FGpioConfig *FGpioLookupConfig(u32 gpio_id)
函数位置:phytium-standalone-sdk/drivers/pin/fgpio/fgpio.c
函数功能:根据gpio_id获取指定gpio引脚的默认配置。
函数参数:gpio_id=gpio_ctrl*16+pin_id,比如gpio1_8,它的gpio_id=1*16+8=24
SDK提供了宏FGPIO_ID(ctrl, pin) 计算gpio_id。
FGpioConfig结构
typedef struct{
u32 id; /* GPIO标号,0 ~ FGPIO_NUM */
u32 ctrl; /* GPIO所属的控制器,0 ~ FGPIO_CTRL_NUM */
u32 port; /* GPIO所属的Port, Port A, B */
u32 pin; /* GPIO的引脚号,0 ~ FGPIO_PIN_NUM */
uintptr base_addr; /* GPIO控制器基地址 */
u32 irq_num; /* GPIO中断号,如果不支持中断,置位为 0 */
u32 irq_priority; /* 中断优先级 */
u32 cap; /* GPIO引脚能力集 */
} FGpioConfig; /* GPIO引脚配置 */
b)FGpioCfgInitializex
函数原型:FError FGpioCfgInitialize(FGpio *const pin, const FGpioConfig *const config)
函数位置:phytium-standalone-sdk/drivers/pin/fgpio/fgpio.c
函数功能:根据FGpioConfig结构的默认配置config,初始化GPIO引脚实例pin
函数参数:
const FGpioConfig *const config,默认参数,通过FGpioLookupConfig获取的
FGpio *const pin,初始化后的参数。
FGpio的结构:
typedef struct
{
FGpioConfig config;
u32 is_ready;
FGpioInterruptCallback irq_cb; /* 中断回调函数 */
void *irq_cb_params; /* 中断回调函数的入参 */
} FGpio; /* GPIO引脚实例 */
c)FGpioSetDirection
函数原型:void FGpioSetDirection(FGpio *const pin, FGpioDirection dir)
函数位置:phytium-standalone-sdk/drivers/pin/fgpio/fgpio.c
函数功能:设置GPIO引脚的输入输出方向
函数参数:
FGpio *const pin,初始化引脚的参数,通过FGpioCfgInitialize函数初始化。
FGpioDirection dir,初始化后的参数。
typedef enum
{
FGPIO_DIR_INPUT = 0, /* 输入 */
FGPIO_DIR_OUTPUT /* 输出 */
} FGpioDirection; /* GPIO引脚的输入输出方向 */
d)FGpioSetOutputValue
函数原型:FError FGpioSetOutputValue(FGpio *const pin, const FGpioVal output)
函数位置:phytium-standalone-sdk/drivers/pin/fgpio/fgpio.c
函数功能:设置GPIO引脚的输出值
函数参数:
FGpio *const pin,初始化引脚的参数,通过FGpioCfgInitialize函数初始化。
const FGpioVal output,初始化后的参数。
typedef enum
{
FGPIO_PIN_LOW = 0,/* 低电平 */
FGPIO_PIN_HIGH/* 高电平 */
} FGpioVal; /* GPIO引脚电平类型 */
e)返回值FError
FGPIO_SUCCESS : success
FGPIO_ERR_INVALID_PARA : invalid input parameters
FGPIO_ERR_INVALID_STATE : invalid state
(5) openamp_for_linux案例
1) openamp_for_linux案例介绍
openamp_for_linux案例位于phytium-standalone/example/system/amp/openamp_for_linux ,基于开源openamp项目 OpenAMP。案例提供了PHYTIUMPI 裸机与 linux 系统之间的测试例程,通过使用rpmsg来实现核间通信的应用程序。openamp_for_linux案例为从机程序,linux 中的程序为主核程序,其目标是主核主动发送数据之后,从机程序会将收到的数据重新发生给主核。
2) openamp_for_linux目录
3) openamp_for_linux的主要函数
a)main
函数原型:int main(void)
函数位置:phytium-standalone/example/system/amp/openamp_for_linux/main.c
函数功能:从核函数的入口函数,调用slave00_rpmsg_echo_process函数初始化从核。
b)slave00_rpmsg_echo_process
函数原型:int slave00_rpmsg_echo_process(void)
函数位置:phytium-standalone/example/system/amp/openamp_for_linux/src/slaver_00_example.c
函数功能:运行在从核(如FreeRTOS或裸机环境)上的核心任务,负责实现数据回显(Echo)功能。,主要负责数据接收与回传以及通信生命周期管理。主要调用的实现函数是FRpmsgEchoApp。
c)FRpmsgEchoApp
函数原型:static int FRpmsgEchoApp(struct rpmsg_device *rdev, void *priv)
函数位置:phytium-standalone/example/system/amp/openamp_for_linux/src/slaver_00_example.c
函数功能:
。初始化 RPMSG 通信:负责配置和初始化 RPMSG 通道,建立处理器间(通常是主机与从机)的通信链路,包括设置端点(endpoint)、注册回调函数等。
。消息收发处理:实现核心的 "回声" 功能 —— 接收来自通信对端(如主机)的消息后,将消息原样返回给发送方,以此验证通信链路的有效性。
。通信管理:可能包含对消息队列、缓冲区的管理,以及处理通信过程中的异常情况(如消息超时、传输错误等)。
。演示 RPMSG 机制:作为示例应用,它直观展示了 RPMSG 的基本使用流程,包括消息接收、处理和发送的完整闭环,帮助开发者理解跨处理器通信的实现方式。
d)rpmsg_endpoint_cb
函数原型:static int rpmsg_endpoint_cb(struct rpmsg_endpoint *ept, void *data, size_t len, uint32_t src, void *priv)
函数位置:phytium-standalone/example/system/amp/openamp_for_linux/src/slaver_00_example.c
函数功能:RPMSG 通信中消息接收与处理的核心回调入口,是实现处理器间数据交互的关键环节
函数参数:其中参数分别代表:RPMSG 端点、接收的数据、数据长度、消息源地址、私有数据。
4) openamp_for_linux数据流
5. OpenAMP从核驱动LED20案例
(1) openamp_for_linux工程配置
工程目录:phytium-pi-os/output/build/phytium-standalone-openamp-v1.0/example/system/amp/openamp_for_linux
配置文件:configs/phytiumpi_aarch64_firefly_openamp_core0.config
增加如下内容:
CONFIG_USE_GPIO=y
CONFIG_ENABLE_FGPIO=y
CONFIG_LOG_VERBOS=y
(2) 增加LED20控制的新文件openamp_for_linux/src/led20set.c
#include <stdio.h>
#include "sdkconfig.h"
#include "fdebug.h"
/*common*/
#include "fio.h"
#include"ftypes.h"
#include"fkernel.h"
#include"fsleep.h"
#include "fassert.h"
/*drivers/iomux/fiopad*/
#include "fiopad.h"
/*drivers/pin/fgpio*/
#include "fgpio_hw.h"
#include "fgpio.h"
/*board/firefly*/
#include "fio_mux.h"
#include "slaver_00_example.h"
#include "fgpio.h"
int initFLag = 0;
u32 gpIO1_8_FGpio_id ;
const FGpioConfig *gpI01_8_FGpioconfig = NULL;
FGpio gpI01_8_FGpio;
void led20Set(int flag )
{
if(0 == initFLag)
{
gpIO1_8_FGpio_id = FGPIO_ID(FGPIO_CTRL_1,FGPIO_PIN_8);
gpI01_8_FGpioconfig =FGpioLookupConfig(gpIO1_8_FGpio_id);
if(NULL == gpI01_8_FGpioconfig)
{
SLAVE_DEBUG_E("led20Set():FGpioLookupConfig error\r\n");
return ;
}
memset(&gpI01_8_FGpio,0,sizeof(gpI01_8_FGpio ));
if(FGPIO_SUCCESS != FGpioCfgInitialize(&gpI01_8_FGpio,gpI01_8_FGpioconfig))
{
SLAVE_DEBUG_E("led20Set():FGpioCfgInitialize() error\r\n");
return ;
}
FIOMuxInit();
FIOPadSetGpioMux(FGPIO_CTRL_1,FGPIO_PIN_8);
FGpioSetDirection(&gpI01_8_FGpio,FGPIO_DIR_OUTPUT);
initFLag = 1;
}
if( 0 == flag )
{
SLAVE_DEBUG_I("set led20 low n");
FGpioSetOutputValue(&gpI01_8_FGpio,FGPIO_PIN_LOW);
}
else if( 1 == flag)
{
SLAVE_DEBUG_I("set led20 high\r\n");
FGpioSetOutputValue(&gpI01_8_FGpio,FGPIO_PIN_HIGH);
}
return;
}
(3) 修改回调函数rpmsg_endpoint_cb,执行LED20的控制命令
static int rpmsg_endpoint_cb(struct rpmsg_endpoint *ept, void *data, size_t len, uint32_t src, void *priv)
{
SLAVE_DEBUG_W("[%s:%d] start %s %s\r\n",__FUNCTION__,__LINE__,__DATE__,__TIME__);
(void)priv;
(void)src;
int ret;
int i = 0;
(void)priv;
SLAVE_DEBUG_I("src:0x%x",src);
ept->dest_addr = src;
ret = parse_protocol_data((char *)data, len, &protocol_data);
if(ret != 0)
{
SLAVE_DEBUG_E("parse protocol data error,ret:%d",ret);
return RPMSG_SUCCESS;/* 瑙f瀽澶辫触锛屽拷鐣ユ暟鎹?*/
}
SLAVE_DEBUG_I("command:0x%x,length:%d.",protocol_data.command,protocol_data.length);
switch (protocol_data.command)
{
case DEVICE_CORE_START:
{
break;
}
case DEVICE_CORE_SHUTDOWN:
{
shutdown_req = 1;
break;
}
case DEVICE_CORE_CHECK:
{
/* Send temp_data back to master */
ret = rpmsg_send(ept, &protocol_data, len);
if (ret < 0)
{
SLAVE_DEBUG_E("rpmsg_send failed.\r\n");
return ret;
}
break;
}
#if 1//for LED20
case LED_ON:
SLAVE_DEBUG_I("rpmsg_endpoint_cb:LED_ON");
led20Set(1);
break;
case LED_OFF:
SLAVE_DEBUG_I("rpmsg_endpoint_cb:LED_OFF");
led20Set(0);
break;
#endif
default:
break;
}
return RPMSG_SUCCESS;
}
(4) 编译从核程序
phytium-pi-os# make phytium-standalone-rebuild
生成的elf文件的位置:
example/system/amp/openamp_for_linux/phytiumpi_aarch64_firefly_openamp_core0.elf
Note:注意需要在交叉编译环境的phytium-pi-os目录下编译。
(5) 拷贝到开发板上
开发板文件位置:/lib/firmware
文件名需要同设备树指定的文件一致,openamp_core0.elf
6. 验证和测试
(1) 主机程序,修改rpmsg-demo-single.c控制LED20,并在开发板上编译
#include <stdio.h>
#include <string.h>
#include <stdint.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <unistd.h>
#include <poll.h>
#include <linux/rpmsg.h>
#include <errno.h>
#define MAX_DATA_LENGTH 256
#define DEVICE_CORE_START 0x0001U
#define DEVICE_CORE_STOP 0x0002U
#define DEVICE_CORE_CHECK 0x0003U
#if 1//for LED20
#define LED_ON 0x004U
#define LED_OFF 0X005U
#define LED_RUNNING 0x006U
#endif
......
int main(int argc, char **argv)
{
int ctrl_fd, rpmsg_fd, ret;
int leng;
struct rpmsg_endpoint_info eptinfo;
struct pollfd fds;
char *buff;
data_packet test_data;
data_packet test_data_r;
char buff_r[MAX_DATA_LENGTH];
printf("argc: %d\n", argc);
for (int i = 0; i < argc; i++) {
printf("Argument %d: %s\n", i, argv[i]);
}
test_data.command = DEVICE_CORE_CHECK;
buff = test_data.data;
ctrl_fd = open("/dev/rpmsg_ctrl0", O_RDWR);
if (ctrl_fd < 0) {
perror("open rpmsg_ctrl0 failed.\n");
return -1;
}
memcpy(eptinfo.name, "xxxx", 32);
eptinfo.src = 0;
eptinfo.dst = 0;
ret = ioctl(ctrl_fd, RPMSG_CREATE_EPT_IOCTL, eptinfo);
if (ret != 0) {
perror("ioctl RPMSG_CREATE_EPT_IOCTL failed.\n");
goto err0;
}
rpmsg_fd = open("/dev/rpmsg0", O_RDWR);
if (rpmsg_fd < 0) {
perror("open rpmsg0 failed.\n");
goto err1;
}
#if 1//for LED20
if (argc > 2 && strcmp(argv[1], "led") == 0) {
if (strcmp(argv[2], "0") == 0) {
printf("led off \n");
test_data.command = LED_OFF;
test_data.length = 1;
ret = write_full(rpmsg_fd, &test_data, sizeof(data_packet));
if (ret < 0) {
perror("write_full failed.\n");
}
}
else if (strcmp(argv[2], "1") == 0) {
printf("led on 0\n");
test_data.command = LED_ON;
test_data.length = 1;
ret = write_full(rpmsg_fd, &test_data, sizeof(data_packet));
if (ret < 0) {
perror("write_full failed.\n");
}
}
else {
printf("stop para err!\n");
}
#endif
goto err1;
}
在开发板编译用户测试程序:
gcc rpmsg-demo-single.c -o rpmsg-demo-single
(2) 启动远程服务器
启动远程服务器 echo start > /sys/class/remoteproc/remoteproc0/state
绑定驱动名字 echo rpmsg_chrdev > /sys/bus/rpmsg/devices/virtio0.rpmsg-openamp-demo-channel.-1.0/driver_override
(3) 从核关闭led20
./rpmsg-demo-single led 0
(4) 从核打开led20
./rpmsg-demo-single led 1
7.参考资料链接
飞腾派开发板资料下载www.iceasy.com/cloud/Phytium