Linux系统下PCI设备驱动程序的开发

时间:2011-08-24

  摘要:以一个具体的PCI设备的驱动开发过程为基础,总结了与PCI设备驱动开发的相关问题,详细阐述了基本开发步骤、具体实现、驱动程序内核块的加载以及用户进程和驱动程序的协同工作问题。

  1 Linux 系统下设备驱动的概念

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

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

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

  2  Linux系统PCI设备驱动程序的开发

  2.1 基本需求分析

  根据设备不同的用途,可以区分不同的PCI设备类型。基于这一设备类型,又可以分析出其他一些基本需求。从本文所使用的数据采集卡可知,其主要用途是用于采集和控制。根据工控过程的特点,需要PCI采集卡在每采样一个数据点时,就以中断的方式交给内核缓冲,再由用户程序适时取出使用。因此,将PCI采集卡作为一个字符设备来处理,并选择触发模式为内触发,数据传送模式为中断传送。本文就是基于这样的需求,按如下所述的步骤和具体实现过程,开发了其驱动程序。

  2.2 基本步骤

  (1)PCI设备文件的建立

  既然PCI设备被操作系统当作特殊的文件来看,就要有个文件名。因此,建立一设备文件的名字来代表硬件设备是开发驱动的步。按照习惯,设备文件都放在系统目录/dev下。一般在开发过程中,由于要经常查看设备文件的状态,而在这一目录下已经有很多设备文件,查看起来特别不方便,因此,可以自己在某个地方建立一个文件夹,将该设备文件放在该文件夹下。使用mknod命令可以建立设备特殊文件(注意:只有root账号的超级用户才能使用些命令),其格式示例为:

  $mknod  /subfolders/mydev/PCIdrv   c  254  0

  也就是用主设备号254(一般在Linux操作系统下设备文件的主设备号不会超过254,所以选用254,以确保该设备号是惟一的)和辅助设备号0在目录/subfolders/mydev下建立特殊设备文件PCIdrv。

  (2)file_operation数据结构

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

  struct file_operations {

  struct module *owner;

  loff_t (*llseek) (struct file *, loff_t, int);

  ssize_t (*read) (struct file *, char *, size_t, loff_t *);

  ssize_t (*write) (struct file *, const char *, size_t, loff_t *);

  int (*readdir) (struct file *, void *, filldir_t);

  unsigned int (*poll) (struct file *, struct poll_table_struct *);

  int (*ioctl) (struct inode *, struct file *, unsigned int, unsigned long);

  int (*mmap) (struct file *, struct vm_area_struct *);

  int (*open) (struct inode *, struct file *);

  int (*flush) (struct file *);

  int (*release) (struct inode *, struct file *);

  int (*fsync) (struct file *, struct dentry *, int datasync);

  int (*fasync) (int, struct file *, int);

  int (*lock) (struct file *, int, struct file_lock *);

  ssize_t (*readv) (struct file *, const struct iovec *, unsigned long, loff_t *);

  ssize_t (*writev) (struct file *, const struct iovec *, unsigned long, loff_t *);

  ssize_t (*sendpage) (struct file *, struct page *, int, size_t, loff_t *, int);

  unsigned long (*get_unmapped_area)(struct file *, unsigned long, unsigned long, unsigned long, unsigned long);

  };

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

  (3)编写驱动程序子函数

  file_operation的数据结构中所定义的子函数的集合构成了具体的设备驱动的执行体。因此编写驱动程序子函数是开发过程中为重要的一步,这些子函数要根据具体的需求来设计。工作中用到的PCI采集卡的主要功能函数有:A/D转换函数AD_INT_Start、AD_INT_Stop、AD_INT_Data,数字量采集与输出函数DI_Data、DO_Data,D/A转换函数AO_Data等。这里不予赘述。

  2.3 PCI采集卡驱动程序的具体实现

  (1)获取PCI采集卡的基本配置信息

  PCI采集卡的驱动程序主要是完成对采集卡的寄存器和PCI总线控制器的PCI配置空间的设置和读取,用以启动采集卡,并按照一定的方式进行采集、传送、停止等。若要对这些寄存器进行设置和读取,就要知道这些寄存器的BaseAddress和偏移值、中断号等相关配置信息。这是对硬件操作的步。Linux操作系统对PCI设备提供了大量的初始化函数(这一点不同于Dos和Windows)。因此,在系统启动时,这特定的初始化函数会被调用,用来检测系统中存在的所有的PCI设备,并填充PCI设备的配置空间。因此,在开发PCI设备驱动时,只要执行相关的系统调用(由系统提供),就可获得所需要的PCI设备信息。

  下面是几个常用的内核函数:

  ①pcibios_present( )

  ②int pcibios_find_device(int device_id,int vendor_id,

  int index,int*bus_number,int*device_function)

  ③read_config_byte,read_config_word,read_config_dword

  ④write_config_byte,write_config_word,write_config_dword

  函数①的功能是:返回一个布尔值表明所运行的计算机是否具有支持PCI设备的能力;函数②的功能是:返回设备在总线上的位置及函数指针bus_number、device_function;函数③、④的功能是:通过调用该类函数,设备驱动程序实现寄存器空间的访问,包括读和写。

  (2)内存操作

  设备驱动的子函数运行在内核态,在设备驱动程序中动态开辟和释放内存。用kmalloc或get_free_pages直接申请页而不是用malloc和free;释放内存用的是kfree或free_pages。kmalloc等函数返回的是物理地址,而malloc等返回的是线性地址,且kmalloc只能开辟128KB。

  很多硬件需要一块比较大的连续内存用作DMA传送,并且需要一直驻留在内存,不能被交换到磁盘文件中去。但是kmalloc多只能开辟128KB的内存,这可以通过牺牲一些系统内存的方法来解决。具体做法是:若机器是32MB的内存,在lilo.conf的启动参数中加上mem=30MB,这样Linux就认为此机器只有30MB的内存,剩下的2MB内存在vremap之后就可以为DMA所用。

  (3)Linux下的中断及中断处理

  Linux中的中断处理程序很有特色。它的一个中断处理程序可分为上半部(top half)和下半部(bottom half)二个部分。之所以会有上半部和下半部之分,完全是考虑到中断处理的效率。上半部的功能是“登记中断”,当一个中断发生时,它就把设备驱动程序中中断例程的下半部挂到该设备的下半部执行队列中去,然后就等待新的中断的到来。这样一来,上半部执行的速度就会很快,它就可以接受更多设备产生的中断。上半部之所以要快,是因为它是完全屏蔽中断的,如果不执行完,其他的中断就不能被及时处理,只能等到这个中断处理程序执行完毕以后。所以,要尽可能多地对设备产生的中断进行服务和处理,确保中断处理程序的速度。

  要使用一个中断,必须先向系统登记。实现系统中断登记的系统调用形式如下:

  int request_irq(unsigned int irq,void(*handle)(int,void*,struct pt_regs*),unsigned int long flags,const char*device);

  其中:irq是要申请的中断号,handle是中断处理函数指针,flags是中断标识,device是设备名。

  如果登记成功,则返回0。这时在/proc/interrupts文件中可以看到自己请求的中断。

  (4)内核态和用户态下数据交换问题

  用户进程在执行特定系统调用使用设备时,系统就从用户态进入内核态下运行,这时用户进程的环境仍然可用。但在内核缓冲区和用户进程缓冲区间进行数据交换时,必须要用内核提供的专门函数。主要有:put_user( ),get_user( ),copy_to_user( ),copy_from_user( )等。

  通过这些调用来使用用户缓冲时还要进行用户缓冲读写权限的检验,否则调用数据交换函数时会出错。检验函数为:int verify_area(int access,void*u_addr,unsigned long size)。

  3 将驱动程序嵌入内核

  由于驱动程序是在内核下运行,因此,要把编写的驱动程序嵌入内核。驱动程序可以按照二种方式编译,一种是编译进kernel,另一种是编译成模块(modules)。如果编译进内核的话,会增加内核的大小,还要改动内核的源文件,而且不能动态卸载,不利于调试。所以推荐使用模块方式。这种方式可以用insmod命令来加载模块和用rmmod来卸载模块。

  在用insmod命令将编译好的模块调入内核时,init_module 函数被调用。在这里,init_module只做了一件事,就是向系统的字符设备表登记了一个字符设备。register_chrdev需要三个参数:其一是希望获得的设备号,如果为0,系统将选择一个没有被占用的设备号返回;其二是设备文件名;其三是用来登记驱动程序实际执行操作的函数指针。如果登记成功,则返回设备的主设备号;若不成功,则返回一个负值。

  下面是用模块方法将驱动程序加载进内核时用的主要功能函数体示例,也就是当执行inmod 命令时执行的函数体。

  int init_module(void)

  {

  int result;

  result=register_chrdev(254,″PCItest″,&PCI_fops);

  if(result<0) {

  printk(KERN_INFO ″test:can′t get major number\n″);

  return result;

  }

  if(PCItest==0) PCItest=result;/*dynamic*/

  return 0;

  }

  同样可以用 rmmod 命令卸载模块:

  void cleanup_module(void)

  {

  unregister_chrdev(254,″PCItest″);

  }

  在用rmmod卸载模块时,cleanup_module函数被调用,它释放字符设备PCItest在系统字符设备表中占有的表项。

  4  结束语

  设计Linux设备驱动程序有一定的模式,遵循这个模式,将会大大减轻设计程序的工作量。本文总结了工作中对一种PCI采集卡的驱动开发过程。同网卡的驱动相比,PCI采集卡驱动的开发是一件相对简单的工作,但它们同属于PCI设备,具有类似之处。所以,PCI采集卡的驱动开发对设计复杂的网络驱动程序是很有帮助的。


  
上一篇:由UC384x系列构成的彩显开关电源的故障维修与电路分析
下一篇:浅谈园区网安全出口解决方案

免责声明: 凡注明来源本网的所有作品,均为本网合法拥有版权或有权使用的作品,欢迎转载,注明出处。非本网作品均来自互联网,转载目的在于传递更多信息,并不代表本网赞同其观点和对其真实性负责。

相关技术资料