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之前,我们先看一下什么是系统调用。
开源地址:https://github.com/KastnerRG/riffa
一、系统调用
1. 理论基础
探究系统调用,以ioctl为例子,通俗来讲,其实就是探究操作系统实现应用层的ioctl对应上特定驱动程序的ioctl的过程。
由于应用程序的ioctl处于用户空间,驱动程序的ioctl处于内核空间,所以这两者之间不属于简单的调用关系;另外,考虑到内核空间操作的安全性,系统调用过程大量的安全性处理,进而使得系统调用看起来十分复杂。
ioctl作用:应用层的ioctl函数传入的cmd和arg参数会直接传入驱动层的ioctl接口,在对应驱动文件会对相应的命令进行操作。对于传递的ioctl命令有一定的规范,具体可以参考:/include/asm/ioctl.h,/Documentation/ioctl-number.txt 这两个文件。
应用层和驱动程序联系如下:
最终ioctl是通过系统调用sys_ioctl软中断陷入到内核,通过系统中断号最终调用到内核态的ioctl函数。
2. 代码实例
构造ioctl命令linux已经给我们提供了宏命令:
_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) //应用从驱动中写数据
下面给出简单的实例用户态应用层代码:
//应用程序
#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);
}
驱动程序的代码:
//驱动程序
//假设该驱动程序建立了名为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代码分析
这里我们直接给出源代码:
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
// 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,
#define IOCTL_XXX _IOR(MAJOR_NUM, 5, XXX)
分析代码我们看到其实这些功能实现都是想组装一个IOCTRL结构,之后通过IOCTRL把这些参数传递给下层驱动进行控制。
这些参数应该是在每次读写开始的时候都要写到FPGA里面进行设置的。
另外,riffa.c是编译成静态库文件(.a),供大家调用的,其实使用的时候直接包含进来这个riffa.c的源文件也可以。
当然我们也可以使用动态调用.so文件。
四、参考资料
https://zhuanlan.zhihu.com/p/478247461
标签:MOTOR,fpga,int,ioctl,PCI,fd,io,Linux,详解 From: https://blog.51cto.com/u_16419576/9161221