首页 技术文章

基于PCI总线的高速数据采集卡的linux驱动

2015-05-06 09:13:00 坤驰科技

一、PCI总线系统体系结构

PCI是外围设备互连(PeripheralComponentInterconnect)的简称,作为一种通用的总线接口标准,它在目前的计算机系统中得到了非常广泛的应用。PCI提供了一组完整的总线接口规范,其目的是描述如何将计算机系统中的外围设备以一种结构化和可控化的方式连接在一起,同时它还刻画了外围设备在连接时的电气特性和行为规约,并且详细定义了计算机系统中的各个不同部件之间应该如何正确地进行交互。

无论是在基于Intel芯片的PC机中,或是在基于Alpha芯片的工作站上,PCI毫无疑问都是目前使用广泛的一种总线接口标准。同旧式的ISA总线不同,PCI将计算机系统中的总线子系统与存储子系统完全地分开,CPU通过一块称为PCI桥(PCI-Bridge)的设备来完成同总线子系统的交互,如图1所示。



图1PCI子系统的体系结构


由于使用了更高的时钟频率,因此PCI总线能够获得比ISA总线更好的整体性能。PCI总线的时钟频率一般在25MHz到33MHz范围内,有些甚至能够达到66MHz或者133MHz,而在64位系统中则高能达到266MHz。尽管目前PCI设备大多采用32位数据总线,但PCI规范中已经给出了64位的扩展实现,从而使PCI总线能够更好地实现平台无关性,现在PCI总线已经能够用于IA-32、Alpha、PowerPC、SPARC64和IA-64等体系结构中。

PCI总线具有三个非常显著的优点,使得它能够完成终取代ISA总线这一历史使命:

在计算机和外设间传输数据时具有更好的性能;

能够尽量独立于具体的平台;

可以很方便地实现即插即用。

图2是一个典型的基于PCI总线的计算机系统逻辑示意图,系统的各个部分通过PCI总线和PCI-PCI桥连接在一起。从图中不难看出,CPU和RAM需要通过PCI桥连接到PCI总线0(即主PCI总线),而具有PCI接口的显卡则可以直接连接到主PCI总线上。PCI-PCI桥是一个特殊的PCI设备,它负责将PCI总线0和PCI总线1(即从PCI主线)连接在一起,通常PCI总线1称为PCI-PCI桥的下游(downstream),而PCI总线0则称为PCI-PCI桥的上游(upstream)。图中连接到从PCI总线上的是SCSI卡和以太网卡。为了兼容旧的ISA总线标准,PCI总线还可以通过PCI-ISA桥来连接ISA总线,从而能够支持以前的ISA设备。图中ISA总线上连接着一个多功能I/O控制器,用于控制键盘、鼠标和软驱。


图2PCI系统示意图



在此我只对PCI总线系统体系结构作了概括性介绍,如果读者想进一步了解,DavidARusling在TheLinuxKernel(http://tldp.org/LDP/tlk/dd/pci.html)中对Linux的PCI子系统有比较详细的介绍。

二、Linux驱动程序框架

Linux将所有外部设备看成是一类特殊文件,称之为“设备文件”,如果说系统调用是Linux内核和应用程序之间的接口,那么设备驱动程序则可以看成是Linux内核与外部设备之间的接口。设备驱动程序向应用程序屏蔽了硬件在实现上的细节,使得应用程序可以像操作普通文件一样来操作外部设备。

1.字符设备和块设备

Linux抽象了对硬件的处理,所有的硬件设备都可以像普通文件一样来看待:它们可以使用和操作文件相同的、标准的系统调用接口来完成打开、关闭、读写和I/O控制操作,而驱动程序的主要任务也就是要实现这些系统调用函数。Linux系统中的所有硬件设备都使用一个特殊的设备文件来表示,例如,系统中的个IDE硬盘使用/dev/hda表示。每个设备文件对应有两个设备号:一个是主设备号,标识该设备的种类,也标识了该设备所使用的驱动程序;另一个是次设备号,标识使用同一设备驱动程序的不同硬件设备。设备文件的主设备号必须与设备驱动程序在登录该设备时申请的主设备号一致,否则用户进程将无法访问到设备驱动程序。

在Linux操作系统下有两类主要的设备文件:一类是字符设备,另一类则是块设备。字符设备是以字节为单位逐个进行I/O操作的设备,在对字符设备发出读写请求时,实际的硬件I/O紧接着就发生了,一般来说字符设备中的缓存是可有可无的,而且也不支持随机访问。块设备则是利用一块系统内存作为缓冲区,当用户进程对设备进行读写请求时,驱动程序先查看缓冲区中的内容,如果缓冲区中的数据能满足用户的要求就返回相应的数据,否则就调用相应的请求函数来进行实际的I/O操作。块设备主要是针对磁盘等慢速设备设计的,其目的是避免耗费过多的CPU时间来等待操作的完成。一般说来,PCI卡通常都属于字符设备。

所有已经注册(即已经加载了驱动程序)的硬件设备的主设备号可以从/proc/devices文件中得到。使用mknod命令可以创建指定类型的设备文件,同时为其分配相应的主设备号和次设备号。例如,下面的命令:

[root@garyroot]#mknod/dev/lp0c60

将建立一个主设备号为6,次设备号为0的字符设备文件/dev/lp0。当应用程序对某个设备文件进行系统调用时,Linux内核会根据该设备文件的设备类型和主设备号调用相应的驱动程序,并从用户态进入到核心态,再由驱动程序判断该设备的次设备号,终完成对相应硬件的操作。

2.设备驱动程序接口

Linux中的I/O子系统向内核中的其他部分提供了一个统一的标准设备接口,这是通过include/linux/fs.h中的数据结构file_operations来完成的:

structfile_operations{

structmodule*owner;

loff_t(*llseek)(structfile*,loff_t,int);

ssize_t(*read)(structfile*,char*,size_t,loff_t*);

ssize_t(*write)(structfile*,constchar*,size_t,loff_t*);

int(*readdir)(structfile*,void*,filldir_t);

unsignedint(*poll)(structfile*,structpoll_table_struct*);

int(*ioctl)(structinode*,structfile*,unsignedint,unsignedlong);

int(*mmap)(structfile*,structvm_area_struct*);

int(*open)(structinode*,structfile*);

int(*flush)(structfile*);

int(*release)(structinode*,structfile*);

int(*fsync)(structfile*,structdentry*,intdatasync);

int(*fasync)(int,structfile*,int);

int(*lock)(structfile*,int,structfile_lock*);

ssize_t(*readv)(structfile*,conststructiovec*,unsignedlong,loff_t*);

ssize_t(*writev)(structfile*,conststructiovec*,unsignedlong,loff_t*);

ssize_t(*sendpage)(structfile*,structpage*,int,size_t,loff_t*,int);

unsignedlong(*get_unmapped_area)(structfile*,unsignedlong,

unsignedlong,unsignedlong,unsignedlong);

};

当应用程序对设备文件进行诸如open、close、read、write等操作时,Linux内核将通过file_operations结构访问驱动程序提供的函数。例如,当应用程序对设备文件执行读操作时,内核将调用file_operations结构中的read函数。

2.设备驱动程序模块

Linux下的设备驱动程序可以按照两种方式进行编译,一种是直接静态编译成内核的一部分,另一种则是编译成可以动态加载的模块。如果编译进内核的话,会增加内核的大小,还要改动内核的源文件,而且不能动态地卸载,不利于调试,所有推荐使用模块方式。

从本质上来讲,模块也是内核的一部分,它不同于普通的应用程序,不能调用位于用户态下的C或者C++库函数,而只能调用Linux内核提供的函数,在/proc/ksyms中可以查看到内核提供的所有函数。

在以模块方式编写驱动程序时,要实现两个必不可少的函数init_module()和cleanup_module(),而且至少要包含 两个头文件。在用gcc编译内核模块时,需要加上-DMODULE-D__KERNEL__-DLINUX这几个参数,编译生成的模块(一般为.o文件)可以使用命令insmod载入Linux内核,从而成为内核的一个组成部分,此时内核会调用模块中的函数init_module()。当不需要该模块时,可以使用rmmod命令进行卸载,此进内核会调用模块中的函数cleanup_module()。任何时候都可以使用命令来lsmod查看目前已经加载的模块以及正在使用该模块的用户数。

3.设备驱动程序结构

了解设备驱动程序的基本结构(或者称为框架),对开发人员而言是非常重要的,Linux的设备驱动程序大致可以分为如下几个部分:驱动程序的注册与注销、设备的打开与释放、设备的读写操作、设备的控制操作、设备的中断和轮询处理。

驱动程序的注册与注销

向系统增加一个驱动程序意味着要赋予它一个主设备号,这可以通过在驱动程序的初始化过程中调用register_chrdev()或者register_blkdev()来完成。而在关闭字符设备或者块设备时,则需要通过调用unregister_chrdev()或unregister_blkdev()从内核中注销设备,同时释放占用的主设备号。

设备的打开与释放

打开设备是通过调用file_operations结构中的函数open()来完成的,它是驱动程序用来为今后的操作完成初始化准备工作的。在大部分驱动程序中,open()通常需要完成下列工作:

检查设备相关错误,如设备尚未准备好等。

如果是次打开,则初始化硬件设备。

识别次设备号,如果有必要则更新读写操作的当前位置指针f_ops。

分配和填写要放在file->private_data里的数据结构。

使用计数增1。

释放设备是通过调用file_operations结构中的函数release()来完成的,这个设备方法有时也被称为close(),它的作用正好与open()相反,通常要完成下列工作:

使用计数减1。

释放在file->private_data中分配的内存。

如果使用计算为0,则关闭设备。

设备的读写操作

字符设备的读写操作相对比较简单,直接使用函数read()和write()就可以了。但如果是块设备的话,则需要调用函数block_read()和block_write()来进行数据读写,这两个函数将向设备请求表中增加读写请求,以便Linux内核可以对请求顺序进行优化。由于是对内存缓冲区而不是直接对设备进行操作的,因此能很大程度上加快读写速度。如果内存缓冲区中没有所要读入的数据,或者需要执行写操作将数据写入设备,那么就要执行真正的数据传输,这是通过调用数据结构blk_dev_struct中的函数request_fn()来完成的。

设备的控制操作

除了读写操作外,应用程序有时还需要对设备进行控制,这可以通过设备驱动程序中的函数ioctl()来完成。ioctl()的用法与具体设备密切关联,因此需要根据设备的实际情况进行具体分析。

设备的中断和轮询处理

对于不支持中断的硬件设备,读写时需要轮流查询设备状态,以便决定是否继续进行数据传输。如果设备支持中断,则可以按中断方式进行操作。

三、PCI驱动程序实现

1.基本框架

在实现PCI驱动程序时,通常要实现以下几个部分:初始化设备模块,打开设备模块,中断处理模块,读取寄存器模块,以及设备加载与卸载模块。

/*中断处理函数*/

voiddemo_irqhandler()

{

/*中断处理函数*/

}

/*寄存器读写操作*/

voidReadReg(u32dw_offset)

{

ret=readl(basevirt+dwoffset*4);

}

voidWriteReg(u32dw_offset,u32val)

{

writel(val,(basevirt+dw_offset*4));

}

intdemo_ioctl(structinode*inode,structfile*filp,unsignedcmd,unsignedlongreg)

{

switch(cmd)

{

case:

break;

}

}

/*设备操作接口*/

staticstructfile_operationsdemo_fops={

owner:THIS_MODULE,/*demo_fops所属的设备模块*/

read:demo_read,/*读设备操作*/

write:demo_write,/*写设备操作*/

open:demo_open,/*打开设备操作*/

release:demo_release,/*关闭设备操作*/

......

}

/*初始化设备*/

staticintdemo_init(void)

{

gdev=pci_find_device(VENDOR_ID,DEVICE_ID,gdev);/*不同版本的内核这个函数是不一样的*/

demo_basehdwr=pci_resource_start(gdev,bar);/*申请bar地址*/

......

demo_basehdwr=ioremap(demo_basehdwr,demo_basehdlen);/*映射bar地址*/

........

register_chrdev();

}

/*设备卸载模块*/

staticvoiddemo_exit(void)

{

/*释放内存,卸载设备*/

}

四、小结

PCI总线不仅是目前应用广泛的计算机总线标准,而且是一种兼容性强、功能全的计算机总线。而Linux作为一种新的操作系统,其发展前景是无法估量的,同时也为PCI总线与各种新型设备互连成为可能。由于Linux源码开放,因此给连接到PCI总线上的任何设备编写驱动程序变得相对容易。本文介绍如何编译Linux下的PCI驱动程序,针对的内核版本是2.4。

若您有高速数据采集板卡定制需求,请联系我们:400-000-4026

关于坤驰科技:
坤驰科技是专注于
高速数据采集与信号处理、高速数据存储与记录的高科技公司。坤驰科技为用户提供成熟的标准高速数据采集产品与技术,也可为用户提供定制化的高速信号采集解决方案。目前坤驰科技服务过的研究所、科研单位已达近百家。高速数据采集产品线涵盖PCIExpress、cPCI、PXIe、VPX、USB等总线,包含高速AD、DA平台、FPGA、DSP处理平台,SATA/SSD、Flash存储平台等。
全国咨询电话:400-000-4026

公司官方网站:http://www.queentest.cn

网友热评