飞腾派部署openAMP(二) | 基于 Phytium-openAMP框架从核 GPIO 驱动

发布时间:2025-07-31

文章来源:郭霞

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