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之前,我们先看一下什么是系统调用。

开源地址: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文件。

三、写在最后的话

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

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

四、参考资料

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

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

四、参考资料

https://zhuanlan.zhihu.com/p/478247461

  • 25
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 3
    评论
Linux 设备驱动开发详解 4.0》是由马上飞鱼科技有限公司的紫金福老师所著的一本经典的书籍,该书是目前国内关于 Linux 设备驱动开发方面的最好的教材之一。该书主要介绍了 Linux 设备驱动的基本知识和开发方法,对于想学习 Linux 设备驱动开发或者对于嵌入式系统开发有兴趣的工程师是非常有价值的一本入门书籍。 该书从 Linux 内核的基本架构入手,介绍了 Linux 设备驱动的编写和调试方法。该书结合了实战案例,分步骤地开展了驱动开发的实践,详细分析了 Linux 的 I/O 模型、中断和 DMA、并发性问题等。同时,还详细介绍了外部总线的驱动,如 SPI、I2C、PCI、USB 等,并给出了完整的 demo 程序和相应的测试方法。 另外,该书还介绍了常见的设备驱动,如字符设备驱动、块设备驱动和网络设备驱动的编写方法,并且还介绍了常见设备驱动的硬件架构和硬件接口,使得读者能够更加深入地理解设备驱动的本质。 除此之外,《Linux 设备驱动开发详解 4.0》还介绍了如何通过编写 Linux 独立模块来实现设备驱动,如何使用内核 API 接口和系统调用等技术,并且还引导读者了解一些不常见的驱动开发技术,如用户空间驱动程序和内核模块参数。 总之,《Linux 设备驱动开发详解 4.0》是一本非常实用的 Linux 设备驱动开发指南,它涵盖了 Linux 设备驱动开发的方方面面,包括理论知识和实际操作,对于想深入了解 Linux 设备驱动开发的工程师来说是不可多得的一本书。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

北京不北

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值