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

相关推荐
许野平1 小时前
Rust: 利用 chrono 库实现日期和字符串互相转换
开发语言·后端·rust·字符串·转换·日期·chrono
hjjdebug2 小时前
linux 下 signal() 函数的用法,信号类型在哪里定义的?
linux·signal
其乐无涯2 小时前
服务器技术(一)--Linux基础入门
linux·运维·服务器
Diamond技术流2 小时前
从0开始学习Linux——网络配置
linux·运维·网络·学习·安全·centos
斑布斑布2 小时前
【linux学习2】linux基本命令行操作总结
linux·运维·服务器·学习
Spring_java_gg2 小时前
如何抵御 Linux 服务器黑客威胁和攻击
linux·服务器·网络·安全·web安全
✿ ༺ ོIT技术༻2 小时前
Linux:认识文件系统
linux·运维·服务器
会掉头发3 小时前
Linux进程通信之共享内存
linux·运维·共享内存·进程通信
我言秋日胜春朝★3 小时前
【Linux】冯诺依曼体系、再谈操作系统
linux·运维·服务器