PCI驱动编程基本框架_第1页
PCI驱动编程基本框架_第2页
PCI驱动编程基本框架_第3页
PCI驱动编程基本框架_第4页
PCI驱动编程基本框架_第5页
已阅读5页,还剩11页未读 继续免费阅读

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

Linux将所有外部设备看成是一类特殊文件,称之为“设备文件”,如果说系统调用是Linux内核和应用程序之间的接口,那么设备驱动程序则可以看成是Linux内核与外部设备之间的接口。设备驱动程序向应用程序屏蔽了硬件在实现上的细节,使得应用程序可以像操作普通文件一样来操作外部设备。字符设备和块设备Linux抽象了对硬件的处理,所有的硬件设备都可以像普通文件一样来看待:它们可以使用和操作文件相同的、标准的系统调用接口来完成打开、关闭、读写和I/O控制操作,而驱动程序的主要任务也就是要实现这些系统调用函数。Linux系统中的所有硬件设备都使用一个特殊的设备文件来表示,例如,系统中的第一个IDE硬盘使用/dev/hda表示。每个设备文件对应有两个设备号:一个是主设备号,标识该设备的种类,也标识了该设备所使用的驱动程序;另一个是次设备号,标识使用同一设备驱动程序的不同硬件设备。设备文件的主设备号必须与设备驱动程序在登录该设备时申请的主设备号一致,否则用户进程将无法访问到设备驱动程序。在Linux操作系统下有两类主要的设备文件:一类是字符设备,另一类则是块设备。字符设备是以字节为单位逐个进行I/O操作的设备,在对字符设备发出读写请求时,实际的硬件I/O紧接着就发生了,一般来说字符设备中的缓存是可有可无的,而且也不支持随机访问。块设备则是利用一块系统内存作为缓冲区,当用户进程对设备进行读写请求时,驱动程序先查看缓冲区中的内容,如果缓冲区中的数据能满足用户的要求就返回相应的数据,否则就调用相应的请求函数来进行实际的I/O操作。块设备主要是针对磁盘等慢速设备设计的,其目的是避免耗费过多的CPU时间来等待操作的完成。一般说来,PCI卡通常都属于字符设备。设备驱动程序接口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__user*,size_t,loff_t*);ssize_t(*write)(structfile*,constchar__user*,size_t,loff_t*);

ssize_t(*aio_read)(structkiocb*,conststructiovec*,unsignedlong,loff_t);ssize_t(*aio_write)(structkiocb*,conststructiovec*,unsignedlong,loff_t);int(*readdir)(structfile*,void*,filldir_t);unsignedint(*poll)(structfile*,structpoll_table_struct*);long(*unlocked_ioctl)(structfile*,unsignedint,unsignedlong);long(*compat_ioctl)(structfile*,unsignedint,unsignedlong);int(*mmap)(structfile*,structvm_area_struct*);int(*open)(structinode*,structfile*);int(*flush)(structfile*,fl_owner_tid);int(*release)(structinode*,structfile*);int(*fsync)(structfile*,loff_t,loff_t,intdatasync);int(*aio_fsync)(structkiocb*,intdatasync);int(*fasync)(int,structfile*,int);int(*lock)(structfile*,int,structfile_lock*);ssize_t(*sendpage)(structfile*,structpage*,int,size_t,loff_t*,int);unsignedlong(*get_unmapped_area)(structfile*,unsignedlong,unsignedlong,unsignedlong,unsignedlong);int(*check_flags)(int);int(*flock)(structfile*,int,structfile_lock*);ssize_t(*splice_write)(structpipe_inode_info*,structfile*,loff_t*,size_t,unsignedint);ssize_t(*splice_read)(structfile*,loff_t*,structpipe_inode_info*,size_t,unsignedint);int(*setlease)(structfile*,long,structfile_lock**);long(*fallocate)(structfile*file,intmode,loff_toffset,loff_tlen);};当应用程序对设备文件进行诸如open、close、read、write等操作时,Linux内核将通过file_operations结构访问驱动程序提供的函数。例如,当应用程序对设备文件执行读操作时,内核将调用file_operations结构中的read函数。设备驱动程序模块Linux下的设备驱动程序可以按照两种方式进行编译,一种是直接静态编译成内核的一部分,另一种则是编译成可以动态加载的模块。如果编译进内核的话,会增加内核的大小,

还要改动内核的源文件,而且不能动态地卸载,不利于调试,所有推荐使用模块方式。从本质上来讲,模块也是内核的一部分,它不同于普通的应用程序,不能调用位于用户态下的C或者C++库函数,而只能调用Linux内核提供的函数,在/proc/ksyms中可以查看到内核提供的所有函数。在以模块方式编写驱动程序时,要实现两个必不可少的函数init_module()和cleanup_module(),而且至少要包含和两个头文件。一般使用LDD3例程中使用的makefile作为基本的版本,稍作改变之后用来编译驱动,编译生成的模块(一般为.ko文件)可以使用命令insmod载入Linux内核,从而成为内核的一个组成部分,此时内核会调用模块中的函数init_module()。当不需要该模块时,可以使用rmmod命令进行卸载,此进内核会调用模块中的函数cleanup_module()。任何时候都可以使用命令来lsmod查看目前已经加载的模块以及正在使用该模块的用户数。设备驱动程序结构了解设备驱动程序的基本结构(或者称为框架),对开发人员而言是非常重要的,Linux的设备驱动程序大致可以分为如下几个部分:驱动程序的注册与注销、设备的打开与释放、设备的读写操作、设备的控制操作、设备的中断和轮询处理。驱动程序的注册与注销向系统增加一个驱动程序意味着要赋予它一个主设备号,这可以通过在驱动程序的初始化过程中调用register_chrdev()或者register_blkdev()来完成。而在关闭字符设备或者块设备时,则需要通过调用unregister_chrdev(咸unregister_blkdev()从内核中注销设备,同时释放占用的主设备号。但是现在程序员都倾向于动态创建设备号和设备结点,动态创建设备号和设备结点需要几个指定的函数,具体可以参见“Linux字符驱动中动态分配设备号与动态生成设备节点”。设备的打开与释放打开设备是通过调用file_operations结构中的函数open()来完成的,它是驱动程序用来为今后的操作完成初始化准备工作的。在大部分驱动程序中,open()通常需要完成下列工作:1•检查设备相关错误,如设备尚未准备好等。2•如果是第一次打开,则初始化硬件设备。3•识别次设备号,如果有必要则更新读写操作的当前位置指针f_ops。4.分配和填写要放在file->private_data里的数据结构。5•使用计数增1。释放设备是通过调用file_operations结构中的函数release()来完成的,这个设备方法有时也被称为close(),它的作用正好与open()相反,通常要完成下列工作:1•使用计数减1。2.释放在file->private_data中分配的内存。3•如果使用计算为0则关闭设备。设备的读写操作字符设备的读写操作相对比较简单,直接使用函数read()和write()就可以了。但如果是块设备的话,则需要调用函数block_read()和block_write()来进行数据读写,这两个函数将向设备请求表中增加读写请求,以便Linux内核可以对请求顺序进行优化。由于是对内存缓冲区而不是直接对设备进行操作的,因此能很大程度上加快读写速度。如果内存缓冲区中没有所要读入的数据,或者需要执行写操作将数据写入设备,那么就要执行真正的数据传输,这是通过调用数据结构blk_dev_struct中的函数request_fn()来完成的。设备的控制操作除了读写操作外,应用程序有时还需要对设备进行控制,这可以通过设备驱动程序中的函数ioctl()来完成,ioctl系统调用有下面的原型:intioctl(intfd,unsignedlongcmd,…),第一个参数是文件描述符,第二个参数是具体的命令,一般使用宏定义来确定,第三个参数一般是传递给驱动中处理设备控制操作函数的参数。ioctl()的用法与具体设备密切关联,因此需要根据设备的实际情况进行具体分析。设备的中断和轮询处理对于不支持中断的硬件设备,读写时需要轮流查询设备状态,以便决定是否继续进行数据传输。如果设备支持中断,则可以按中断方式进行操作。

基本框架在用模块方式实现PCI设备驱动程序时,通常至少要实现以下几个部分:初始化设备模块、设备打开模块、数据读写和控制模块、中断处理模块、设备释放模块、设备卸载模块。下面给出一个典型的PCI设备驱动程序的基本框架,从中不难体会到这几个关键模块是如何组织起来的。/*指明该驱动程序适用于哪一些PCI设备*/staticstructpci_device_idmy_pci_tbl[]__initdata={{PCI_VENDOR_ID,PCI_DEVICE_ID,PCI_ANY_ID,PCI_ANY_ID,O,0,0},{0,}};/*对特定PCI设备进行描述的数据结构*/structdevice_private{}/*中断处理模块*/staticirqreturn_tdevice_interrupt(intirq,void*dev_id){/*...*/}/*设备文件操作接口*/staticstructfile_operationsdevice_fops={owner:THIS_MODULE,/*demo_fops所属的设备模块*/read:device_read,/*读设备操作*/write:device_write,/*写设备操作*/ioctl:device_ioctl,/*控制设备操作*/mmap:device_mmap,/*内存重映射操作*/open:device_open,/*打开设备操作*/release:device_release/*释放设备操作*//*...*/};/*设备模块信息*/staticstructpci_drivermy_pci_driver={

name:DEVICE_MODULE_NAME,/*设备模块名称*/id_table:device_pci_tbl,/*能够驱动的设备列表*/probe:device_probe,/*查找并初始化设备*/remove:device_remove/*卸载设备模块*//*...*/};staticint__initinit_module(void){/*...*/}staticvoid__exitcleanup_module(void){pci_unregister_driver(&my_pci_driver);}/*加载驱动程序模块入口*/module_init(init_module);/*卸载驱动程序模块入口*/module_exit(cleanup_module);上面这段代码给出了一个典型的PCI设备驱动程序的框架,是一种相对固定的模式。需要注意的是,同加载和卸载模块相关的函数或数据结构都要在前面加上__init、__exit等标志符,以使同普通函数区分开来。构造出这样一个框架之后,接下去的工作就是如何完成框架内的各个功能模块了。针对相应设备定义描述该PCI设备的数据结构:structdevice_privatestructdevice_private/*注册字符驱动和发现PCI设备的时候使用*/structpci_dev*my_pdev;〃structcdevmy_cdev;〃dev_tmy_dev;atomic_tcreated;/*用于获取PCI设备配置空间的基本信息*/unsignedlongmmio_addr;unsignedlongregs_len;intirq;//中断号

/*用于保存分配给PCI设备的内存空间的信息*/dma_addr_trx_dma_addrp;dma_addr_ttx_dma_addrp;/*基本的同步手段*/spinlock_tlock_send;spinlock_tlock_rev;/*保存内存空间转换后的地址信息*/void__iomem*ioaddr;unsignedlongvirts_addr;intopen_flag//设备打开标记};初始化设备模块:name:id_table:等staticstructpci_drivermy_pci_driver={DRV_NAME,//驱动的名字,一般是一个宏定义my_pci_tbl,};初始化设备模块:name:id_table:等staticstructpci_drivermy_pci_driver={DRV_NAME,//驱动的名字,一般是一个宏定义my_pci_tbl,//包含了相关物理PCI设备的基本信息,vendorID,deviceID};probe:remove:pci_probe,//用于发现PCI设备__devexit_p(pci_remove),//PCI设备的移除//my_pci_tbl其实是一个structpci_device结构,该结构可以有很多项,每一项代表一个设备//该结构可以包含很多项,每一项表明使用该结构的驱动支持的设备//注意:需要以一个空的项结尾,也就是:{0,}staticstructpci_device_idmy_pci_tbl[]__initdata={{vendor_id,device_id,PCI_ANY_ID,PCI_ANY_ID,O,0,0},{0,}};staticint__initinit_module(void){intresult;printk(KERN_INFO"my_pci_driverbuilton%s,%s\n",_DATE_,_TIME_);result=pci_register_driver(&my_pci_driver);〃注册设备驱动if(result)returnresult;return0;}卸载设备模块:staticvoid__devexitmy_pci_remove(structpci_dev*pci_dev){structdevice_private*private;private=(structdevice_private*)pci_get_drvdata(pci_dev);printk("FCswitch->irq=%d\n",private->irq);//register_w32是封装的宏,便于直接操作//#defineregister_w32(reg,val32)iowrite32((val32),device_private->ioaddr+(reg))//这里的作用是关中断,硬件复位

register_w32(IntrMask,0x00000001);register_w32(Reg_reset,0x00000001);//移除动态创建的设备号和设备device_destroy(device_class,device->my_dev);class_destroy(device_class);cdev_del(&private->my_cdev);unregister_chrdev_region(priv->my_dev」);〃清理用于映射到用户空间的内存页面for(private->virts_addr=(unsignedlong)private->rx_buf_virts;private->virts_addr<(unsignedlong)private->rx_buf_virts+BUF_SIZE;private->virts_addr+=PAGE_SIZE){ClearPageReserved(virt_to_page(FCswitch->virts_addr));}//释放分配的内存空间pci_free_consistent(private->my_pdev,BUF_SIZE,private->rx_buf_virts,private->rx_dma_addrp);free_irq(private->irq,private);iounmap(private->ioaddr);pci_release_regions(pci_dev);kfree(private);pci_set_drvdata(pci_dev,NULL);pci_disable_device(pci_dev);}//总之模块卸载函数的职责就是释放一切分配过的资源,根据自己代码的需要进行具体的操作PCI设备的探测(probe):

staticint__devinitpci_probe(structpci_dev*pci_dev,conststructpci_device_id*pci_id){unsignedlongmmio_start;unsignedlongmmio_end;unsignedlongmmio_flags;unsignedlongmmio_len;void__iomem*ioaddrl=NULL;structdevice_private*private;intresult;printk("probefunctionisrunning'n");/*启动PCI设备*/if(pci_enable_device(pci_dev)){printk(KERN_ERR"%s:cannotenabledevice\n",pci_name(pci_dev));return-ENODEV;}printk("enabledevice\n");/*在内核空间中动态申请内存*/if((private=kmalloc(sizeof(structdevice_private),GFP_KERNEL))==NULL){printk(KERN_ERR"pci_demo:outofmemory'n");return-ENOMEM;}memset(private,0,sizeof(*private));private->my_pdev=pci_dev;mmio_start=pci_resource_start(pci_dev,0);mmio_end=pci_resource_end(pci_dev,0);mmio_flags=pci_resource_flags(pci_dev,0);mmio_len=pci_resource_len(pci_dev,0);printk("mmio_startis0x%0x\n",(unsignedint)mmio_start);printk("mmio_lenis0x%0x\n",(unsignedint)mmio_len);if(!(mmio_flags&IORESOURCE_MEM)){printk(KERN_ERR"cannotfindproperPCIdevicebaseaddress,aborting.'n');result=-ENODEV;gotoerr_out;}

/*对PCI区进行标记,标记该区域已经分配出去*/result=pci_request_regions(pci_dev,DEVICE_NAME);if(result)gotoerr_out;/*设置成总线主DMA模式*/pci_set_master(pci_dev);/*ioremap重映射一个物理地址范围到处理器的虚拟地址空间,使它对内核可用.*/ioaddrl=ioremap(mmio_start,mmio」en);if(ioaddrl==NULL){printk(KERN_ERR"%s:cannotremapmmio,aborting\n",pci_name(pci_dev));result=-EIO;gotoerr_out;}printk("ioaddr1=0x%0x\n",(unsignedint)ioaddr1);private->ioaddr=ioaddr1;private->mmio_addr=mmio_start;private->regs」en=mmio」en;private->irq=pci_dev->irq;printk("irqis%d\n",pci_dev->irq);/*初始化自旋锁*/spin_lock_init(&private->lock_send);spin」ock_init(&private->lock_rev);if(my_register_chrdev(private))//注:这里的注册字符设备,类似于前面的文章中介绍过的动态创建设备号和动态生成设备结点{printk("chrdevregisterfail\n");gotoerr_out;}〃下面这两个函数根据具体的硬件来处理,主要就是内存分配、对硬件进行初始化设置等device_init_buf(xx_device)〃这个函数主要进行内存分配,内存映射,获取中断device_hw_start(xx_device)〃这个函数主要是往寄存器中写一些值,复位硬件,开中

断,打开DMA等〃把设备指针地址放入PCI设备中的设备指针中,便于后面调用pci_get_drvdatapci_set_drvdata(pci_dev,FCswitch);return0;err_out:printk("errorprocess*");resource_cleanup_dev(FCswitch);〃如果出现任何问题,释放已经分配了的资源returnresult;}//probe函数的作用就是启动pci设备,读取配置空间信息,进行相应的初始化中断处理:〃中断处理,主要就是读取中断寄存器,然后调用中断处理函数来处理中断的下半部分,一般通过tasklet或者workqueue来实现注意:由于使用request_irq获得的中断是共享中断,因此在中断处理函数的上半部需要区分是不是该设备发出的中断,这就需要读取中断状态寄存器的值来判断,如果不是该设备发起的中断则返回IRQ_NONE奄staticirqreturn_tdevice_interrupt(intirq,void*dev_id)if(READ(IntMask)==0x00000001){returnIRQ_NONE;}WRITE(IntMask,0x00000001);tasklet_schedule(&my_tasklet);//需要先申明tasklet并关联处理函数

returnIRQ_HANDLED;//声明taskletstaticvoidmy_tasklet_process(unsignedlongunused);DECLARE_TASKLET(my_tasklet,my_tasklet_process,(unsignedlong)&private);〃第三个参数为传递给my_tasklet_process函数的参数设备驱动的接口:staticstructfile_operationsdevice_fops={owner:THIS_MODULE,open:device_open,//打开设备ioctl:device_ioctl,〃设备控制操作mmap:device_mmap,〃内存重映射操作release:device_release,〃释放设备打开设备:open方法提供给驱动来做任何的初始化来准备后续的操作.open方法的原型是:int(*open)(structinode*inode,structfile*filp);inode参数有我们需要的信息,以它的i_cdev成员的形式,里面包含我们之前建立的cdev结构.唯一的问题是通常我们不想要cdev结构本身,我们需要的是包含cdev结构的device_private结构.龍staticintdevice_open(structinode*inode,structfile*filp){structdevice_private*private;private=container_of(inode->i_cdev,structdevice_private,my_cdev);

filp->private_data=private;private->open_flag++;try_module_get(THIS_MODULE);return0;}释放设备:release方法的角色是open的反面,设备方法应当进行下面的任务:•释放open分配在filp->private_data中的任何东西•在最后的close关闭设备staticintFCswitch_release(structinode*inode,structfile*filp){structdevice_private*private=filp->private_data;private->open_flag--;module_put(THIS_MODULE);printk("pcideviceclosesuccess*");return0;}设备控制操作:PCI设备驱动程序可以通过device_fops

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

评论

0/150

提交评论