Linux下PCI设备驱动开发详解(八)

Linux下PCI设备驱动开发详解(八)

RIFFA的Linux驱动文件夹下有6个C源码文件,riffa_driver.c、riffa_driver.h、circ_queue.c、circ_queue.h、riffa.c、riffa.h。

其中riffa.c和riffa.h不属于驱动源码,它们是系统函数调用驱动封装的一层接口,属于用户态应用程序的一部分。

在讲解riffa之前,我们先看一下什么是系统调用。

开源地址:github.com/KastnerRG/r...

一、系统调用

1. 理论基础

探究系统调用,以ioctl为例子,通俗来讲,其实就是探究操作系统实现应用层的ioctl对应上特定驱动程序的ioctl的过程。

由于应用程序的ioctl处于用户空间,驱动程序的ioctl处于内核空间,所以这两者之间不属于简单的调用关系;另外,考虑到内核空间操作的安全性,系统调用过程大量的安全性处理,进而使得系统调用看起来十分复杂。

ioctl作用:应用层的ioctl函数传入的cmd和arg参数会直接传入驱动层的ioctl接口,在对应驱动文件会对相应的命令进行操作。对于传递的ioctl命令有一定的规范,具体可以参考:/include/asm/ioctl.h,/Documentation/ioctl-number.txt 这两个文件。

应用层和驱动程序联系如下:

perl 复制代码
最终ioctl是通过系统调用sys_ioctl软中断陷入到内核,通过系统中断号最终调用到内核态的ioctl函数。

2. 代码实例

构造ioctl命令linux已经给我们提供了宏命令:

c 复制代码
_IO    an ioctl with no parameters
_IOW   an ioctl with write parameters (copy_from_user)
_IOR   an ioctl with read parameters  (copy_to_user)
_IOWR  an ioctl with both write and read parameters
相关参数:
/*
    type:    幻数(设备相关的一个字母,避免和内核冲突)
    nr:      命令编号
    datatype:数据类型
*/
_IO(type,nr)           //无参数的命令
_IOR(type,nr,datatype)  //应用从驱动中读数据
_IOW(type,nr,datatype)  //应用从驱动中写数据

下面给出简单的实例用户态应用层代码:

c 复制代码
//应用程序
 
#define MOTOR_CMD_BASE'M'  
#define SET_MOTOR_STEP _IOW(MOTOR_CMD_BASE, 1u, int)
#define GET_MOTOR_STEP _IOW(MOTOR_CMD_BASE, 2u, int)
 ...
    int step= 0;
    int value = 0;
    int fd = -1;
 
    fd = open("/dev/motor",O_RDWR);   
    if (fd== -1) {   
        printf("open device error!/n");   
        return -1;   
    }  
   ...  
    printf("Please input the motor step:/n"  
    scanf("%d",&step);    
    if(ioctl(fd, SET_MOTOR_STEP, &step)<0){  
          perror("ioctl error");  
        exit(1);  
    }  
 

驱动程序的代码:

c 复制代码
//驱动程序
//假设该驱动程序建立了名为motor的字符设备
 
#define MOTOR_CMD_BASE'M'  
#define SET_MOTOR_STEP _IOW(MOTOR_CMD_BASE, 1u, int)
#define GET_MOTOR_STEP _IOW(MOTOR_CMD_BASE, 2u, int)
 
 
int motor_ioctl(struct inode *inode,struct file *filp,unsigned int cmd,unsigned long arg)  
{
    int step=0;   
    int value = 0;
    switch (cmd) {  
        case SET_MOTOR_STEP :  
            if(copy_from_user(&step, (int*)arg, sizeof(int)))
                  return fail;
          //处理程序
            break;
        case GET_MOTOR_STEP :
            value = 100;
            if(copy_to_user((int*)arg, &value, sizeof(int)))
                return fail;
            break;
    ...
    }
}  

二、RIFFA代码分析

这里我们直接给出源代码:

c 复制代码
fpga_t * fpga_open(int id) 
{
	fpga_t * fpga;

	// Allocate space for the fpga_dev
	fpga = (fpga_t *)malloc(sizeof(fpga_t));
	if (fpga == NULL)
		return NULL;
	fpga->id = id;	

	// Open the device file.
	fpga->fd = open("/dev/" DEVICE_NAME, O_RDWR | O_SYNC);
	if (fpga->fd < 0) {
		free(fpga); 
		return NULL;
	}
	
	return fpga;
}

void fpga_close(fpga_t * fpga) 
{
	// Close the device file.
	close(fpga->fd);
	free(fpga);
}

int fpga_send(fpga_t * fpga, int chnl, void * data, int len, int destoff, 
	int last, long long timeout)
{
	fpga_chnl_io io;

	io.id = fpga->id;
	io.chnl = chnl;
	io.len = len;
	io.offset = destoff;
	io.last = last;
	io.timeout = timeout;
	io.data = (char *)data;

	return ioctl(fpga->fd, IOCTL_SEND, &io);
}

int fpga_recv(fpga_t * fpga, int chnl, void * data, int len, long long timeout)
{
	fpga_chnl_io io;

	io.id = fpga->id;
	io.chnl = chnl;
	io.len = len;
	io.timeout = timeout;
	io.data = (char *)data;

	return ioctl(fpga->fd, IOCTL_RECV, &io);
}

void fpga_reset(fpga_t * fpga)
{
	ioctl(fpga->fd, IOCTL_RESET, fpga->id);
}

int fpga_list(fpga_info_list * list) {
	int fd;
	int rc;

	fd = open("/dev/" DEVICE_NAME, O_RDWR | O_SYNC);
	if (fd < 0)
		return fd;
	rc = ioctl(fd, IOCTL_LIST, list);
	close(fd);
	return rc;
}

对应的ioctls

c 复制代码
// IOCTLs
#define IOCTL_SEND _IOW(MAJOR_NUM, 1, fpga_chnl_io *)
#define IOCTL_RECV _IOR(MAJOR_NUM, 2, fpga_chnl_io *)
#define IOCTL_LIST _IOR(MAJOR_NUM, 3, fpga_info_list *)
#define IOCTL_RESET _IOW(MAJOR_NUM, 4, int)

riffa.c代码定义了几个用户操作:打开、关闭、读、写、以及复位。其实都是通过ioctl实现的。如果我们后期扩展,想加上自己的函数,调用一个指定编号的ioctl,同时在驱动里面自己写好这个驱动,就可以实现我们想要的功能了。 eg,

c 复制代码
    #define IOCTL_XXX _IOR(MAJOR_NUM, 5, XXX)

分析代码我们看到其实这些功能实现都是想组装一个IOCTRL结构,之后通过IOCTRL把这些参数传递给下层驱动进行控制。

这些参数应该是在每次读写开始的时候都要写到FPGA里面进行设置的。

另外,riffa.c是编译成静态库文件(.a),供大家调用的,其实使用的时候直接包含进来这个riffa.c的源文件也可以。

当然我们也可以使用动态调用.so文件。

三、写在最后的话

css 复制代码
至此,Linux下PCI设备驱动开发详解(一)-(八),详细介绍了linux下驱动、内核、FPGA、用户态的理论知识,以及结合著名的开源RIFFA框架,分析了源代码的细节、框架、层次,做到了理论结合实践的最佳应用。

欢迎大家随时留言、沟通交流,详情+V:beijing_bubei(备注来意)

四、参考资料

zhuanlan.zhihu.com/p/478247461

相关推荐
白晨并不是很能熬夜15 小时前
【RPC】第 4 篇:服务发现 — Zookeeper + 缓存容错
java·后端·程序人生·缓存·zookeeper·rpc·服务发现
我这一拳20年的功力16 小时前
深入解析 XXL-JOB 核心原理:从 Quartz 到自研时间轮
后端
hahaha 1hhh16 小时前
中文乱码 ubuntu autodl
linux·运维·前端
MgArcher16 小时前
一个下划线表示“别动”,两个下划线表示“真别动”!Python属性访问控制,看懂这篇就够了
后端
计算机安禾16 小时前
【Linux从入门到精通】第37篇:NFS网络文件系统——无状态的数据共享
linux·网络·php
ltl16 小时前
【大模型基础设施工程】19:Agent 框架工程
后端
Leinwin16 小时前
Claude 四月宕机七次:从一次事故看企业级 AI 部署的容灾设计
后端·python·flask
是希燃亚16 小时前
hermes迁移手册,将hermes迁移到不同服务器~
后端·github
暴力求解16 小时前
Linux---保存信号
linux·运维·服务器·开发语言·操作系统
Bruce_Liuxiaowei16 小时前
CVE-2026-31431 (Copy Fail) 漏洞复现与验证记录
linux·安全·漏洞复现·cve-2026-31431