嵌入式驱动程序

上传人:jin****ng 文档编号:122917375 上传时间:2022-07-21 格式:DOC 页数:18 大小:210.50KB
收藏 版权申诉 举报 下载
嵌入式驱动程序_第1页
第1页 / 共18页
嵌入式驱动程序_第2页
第2页 / 共18页
嵌入式驱动程序_第3页
第3页 / 共18页
资源描述:

《嵌入式驱动程序》由会员分享,可在线阅读,更多相关《嵌入式驱动程序(18页珍藏版)》请在装配图网上搜索。

1、Linux下PCI设备驱动程序开发一、pci总线系统体系结构pci是外围设备互连(Peripheral Component Interconnect)的简称,作为一 种通用的总线接口标准,它在目前的计算机系统中得到了非常广泛的应用 PCI 提供了一 组完整的总线接口规范,其目的是描述如何将计算机系统中的外围设备以一种结构化和 可控化的方式连接在一起,同时它还刻画了外围设备在连接时的电气特性和行为规约, 并且详细定义了计算机系统中的各个不同部件之间应该如何正确地进行交互。无论是在基于Intel芯片的PC机中,或是在基于Alpha芯片的工作站上,pci毫无 疑问都是目前使用最广泛的一种总线接口标准

2、。同旧式的ISA总线不同,pci将计算机系 统中的总线子系统与存储子系统完全地分开,CPU通过一块称为pci桥(pci-Bridge) 的设备来完成同总线子系统的交互,如图1 所示。图1 pci子系统的体系结构由于使用了更高的时钟频率,因此PCI总线能够获得比ISA总线更好的整体性能。 PCI总线的时钟频率一般在25MHz到33MHz范围内,有些甚至能够达到66MHz或者 133MHz,而在64位系统中则最高能达到266MHz。尽管目前pci设备大多采用32 位数据总线,但pci规范中已经给出了 64位的扩展实现,从而使pci总线能够更好地实 现平台无关性,现在pci总线已经能够用于IA-32

3、、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主线)连

4、接在一 起,通常pci总线1称为pci-pci桥的下游(downstream),而pci总线0则称为pci-pci 桥的上游(upstream)。图中连接到从pci总线上的是SCSI卡和以太网卡。为了兼容 旧的ISA总线标准pci总线还可以通过pci-ISA桥来连接ISA总线,从而能够支持以前 的ISA设备。图中ISA总线上连接着一个多功能I/O控制器,用于控制键盘、鼠标和 软驱。PCI 系统示意图图2在此我只对PCI总线系统体系结构作了概括性介绍,如果读者想进一步了解,DavidA Rusling 在 The Linux Kernel 中对 Linux 的 PCI 子系统有比较详细的介绍。二

5、、Linux驱动程序框架Linux 将所有外部设备看成是一类特殊文件,称之为“设备文件”,如果说系统调用 是 Linux 内核和应用程序之间的接口,那么设备驱动程序则可以看成是 Linux 内核与外 部设备之间的接口。设备驱动程序向应用程序屏蔽了硬件在实现上的细节,使得应用程 序可以像操作普通文件一样来操作外部设备。1. 字符设备和块设备Linux抽象了对硬件的处理,所有的硬件设备都可以像普通文件一样来看待:它们 可以使用和操作文件相同的、标准的系统调用接口来完成打开、关闭、读写和 I/O 控制 操作,而驱动程序的主要任务也就是要实现这些系统调用函数。Linux系统中的所有硬 件设备都使用一个

6、特殊的设备文件来表示,例如,系统中的第一个IDE硬盘使用 /dev/hda 表示。每个设备文件对应有两个设备号:一个是主设备号,标识该设备的种 类,也标识了该设备所使用的驱动程序;另一个是次设备号,标识使用同一设备驱动程 序的不同硬件设备。设备文件的主设备号必须与设备驱动程序在登录该设备时申请的主 设备号一致,否则用户进程将无法访问到设备驱动程序。在 Linux 操作系统下有两类主要的设备文件:一类是字符设备,另一类则是块设备。 字符设备是以字节为单位逐个进行I/O操作的设备,在对字符设备发出读写请求时,实 际的硬件I/O紧接着就发生了,一般来说字符设备中的缓存是可有可无的,而且也不支 持随机

7、访问。块设备则是利用一块系统内存作为缓冲区,当用户进程对设备进行读写请 求时,驱动程序先查看缓冲区中的内容,如果缓冲区中的数据能满足用户的要求就返回 相应的数据,否则就调用相应的请求函数来进行实际的I/O操作。块设备主要是针对磁 盘等慢速设备设计的,其目的是避免耗费过多的CPU时间来等待操作的完成。一般说 来, PCI 卡通常都属于字符设备。所有已经注册(即已经加载了驱动程序)的硬件设备的主设备号可以从 /proc/devices文件中得到。使用mknod命令可以创建指定类型的设备文件,同时为 其分配相应的主设备号和次设备号。例如,下面的命令:rootgary root# mknod /dev

8、/lp0 c 6 0将建立一个主设备号为6,次设备号为0的字符设备文件/dev/lpO。当应用程序 对某个设备文件进行系统调用时, Linux 内核会根据该设备文件的设备类型和主设备号 调用相应的驱动程序,并从用户态进入到核心态,再由驱动程序判断该设备的次设备号, 最终完成对相应硬件的操作。2. 设备驱动程序接口Linux中的I/O子系统向内核中的其他部分提供了一个统一的标准设备接口,这是 通过include/linux/fs.h中的数据结构file_operations来完成的:struct file_operations struct module *owner;loff_t (*llse

9、ek) (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 *,

10、 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, st

11、ruct 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

12、_t *, int);unsigned long (*get_unmapped_area)(struct file *, unsigned long, unsigned long, unsigned long, unsigned long);当应用程序对设备文件进行诸如open、close、read、write等操作时,Linux内 核将通过file_operatio ns结构访问驱动程序提供的函数。例如,当应用程序对设备文 件执行读操作时,内核将调用file_operations结构中的read函数。2. 设备驱动程序模块Linux下的设备驱动程序可以按照两种方式进行编译,一种是直接静态编译

13、成内核 的一部分,另一种则是编译成可以动态加载的模块。如果编译进内核的话,会增加内核 的大小,还要改动内核的源文件,而且不能动态地卸载,不利于调试,所有推荐使用模 块方式。从本质上来讲,模块也是内核的一部分,它不同于普通的应用程序,不能调用位于 用户态下的C或者C+库函数,而只能调用Linux内核提供的函数,在/proc/ksyms 中可以查看到内核提供的所有函数。在以模块方式编写驱动程序时,要实现两个必不可少的函数ini t_module()和 clea nu p_module(),而且至少要包含 vli nu x/krer nel.h 和 vli nu x/module.h 两 个头文件。

14、在用gcc编译内核模块时,需要加上-DMODULE -D_KERNEL_ -DLINUX这几个参数,编译生成的模块(一般为o文件)可以使用命令insmod载入 Linux内核,从而成为内核的一个组成部分,此时内核会调用模块中的函数 init_module( )。当不需要该模块时,可以使用 rmmod 命令进行卸载,此进内核会 调用模块中的函数cleanup_module()。任何时候都可以使用命令来lsmod查看目前 已经加载的模块以及正在使用该模块的用户数。3. 设备驱动程序结构了解设备驱动程序的基本结构(或者称为框架),对开发人员而言是非常重要的, Linux的设备驱动程序大致可以分为如下

15、几个部分:驱动程序的注册与注销、设备的打 开与释放、设备的读写操作、设备的控制操作、设备的中断和轮询处理。驱动程序的注册与注销向系统增加一个驱动程序意味着要赋予它一个主设备号,这可以通过在驱动程序的 初始化过程中调用 register_chrdev( )或者 register_blkdev( )来完成。而在关闭字 符设备或者块设备时,则需要通过调用 unregister_chrdev( )或 unregister_blkdev( )从内核中注销设备,同时释放占用的主设备号。设备的打开与释放打开设备是通过调用file_operations结构中的函数open()来完成的,它是驱动 程序用来为今后

16、的操作完成初始化准备工作的。在大部分驱动程序中, open( )通常需 要完成下列工作:检查设备相关错误,如设备尚未准备好等。如果是第一次打开,则初始化硬件设备。识别次设备号,如果有必要则更新读写操作的当前位置指针f_ops。分配和填写要放在file-private_data里的数据结构。使用计数增1。释放设备是通过调用 file_operations 结构中的函数 release( )来完成的,这个设 备方法有时也被称为close(),它的作用正好与open()相反,通常要完成下列工作:使用计数减1。释放在file-private_data中分配的内存。如果使用计算为0则关闭设备。设备的读写

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

18、备进行控制,这可以通过设备驱动程序 中的函数ioctl()来完成。ioctl()的用法与具体设备密切关联,因此需要根据设备的实 际情况进行具体分析。设备的中断和轮询处理对于不支持中断的硬件设备,读写时需要轮流查询设备状态,以便决定是否继续进 行数据传输。如果设备支持中断,则可以按中断方式进行操作。三、pci驱动程序实现1. 关键数据结构PCI设备上有三种地址空间:PCI的I/O空间、PCI的存储空间和PCI的配置空间。CPU可以访问PCI设备上的所有地址空间,其中I/O空间和存储空间提供给设备驱动 程序使用,而配置空间则由Linux内核中的PCI初始化代码使用。内核在启动时负责对 所有PCI设

19、备进行初始化,配置好所有的PCI设备,包括中断号以及I/O基址,并在文 件/proc/pci中列出所有找到的pci设备,以及这些设备的参数和属性。Linux驱动程序通常使用结构(Struct)来表示一种设备,而结构体中的变量则代 表某一具体设备,该变量存放了与该设备相关的所有信息。好的驱动程序都应该能驱动 多个同种设备,每个设备之间用次设备号进行区分,如果采用结构数据来代表所有能由 该驱动程序驱动的设备,那么就可以简单地使用数组下标来表示次设备号。在 PCI 驱动程序中,下面几个关键数据结构起着非常核心的作用:pci_driver这个数据结构在文件include/linux/pci.h里,这是

20、Linux内核版本2.4之后为新 型的pci设备驱动程序所添加的,其中最主要的是用于识别设备的id_table结构,以 及用于检测设备的函数probe()和卸载设备的函数remove():struct pci_driver struct list_head node; char *name;const struct pci_device_id *id_table;int (*probe) (struct pci_dev *dev, const struct pci_device_id *id);void (*remove) (struct pci_dev *dev);int (*save_st

21、ate) (struct pci_dev *dev, u32 state);int (*suspend)(struct pci_dev *dev, u32 state);int (*resume) (struct pci_dev *dev);int (*enable_wake) (struct pci_dev *dev, u32 state, int enable);pci_dev这个数据结构也在文件include/linux/pci.h里,它详细描述了一个pci设备几乎 所有的硬件信息,包括厂商ID、设备ID、各种资源等:struct pci_dev struct list_head glo

22、bal_list;struct list_head bus_list;struct pci_bus *bus;struct pci_bus *subordinate;void*sysdata;struct proc_dir_entry *procent;unsigned int devfn;unsigned short vendor;unsigned short device;unsigned short subsystem_vendor;unsigned short subsystem_device;unsigned int class;u8hdr_type;u8rom_base_reg;s

23、truct pci_driver *driver;void*driver_data;u64dma_mask;u32current_state;unsigned short vendor_compatibleDEVIcE_cOUNT_cOMpATIBLEunsigned short device_compatibleDEVIcE_cOUNT_cOMpATIBLE;unsigned int irq;struct resource resourceDEVIcE_cOUNT_RESOURcEstruct resource dma_resourceDEVIcE_cOUNT_DMA;struct reso

24、urce irq_resourceDEVICE_COUNT_IRQ;charname80;charslot_name8;int active;int ro;unsigned short regs;int (*prepare)(struct pci_dev *dev);int (*activate)(struct pci_dev *dev);int (*deactivate)(struct pci_dev *dev);2. 基本框架在用模块方式实现PCI设备驱动程序时,通常至少要实现以下几个部分:初始化设 备模块、设备打开模块、数据读写和控制模块、中断处理模块、设备释放模块、设备卸 载模块。下面

25、给出一个典型的PCI设备驱动程序的基本框架,从中不难体会到这几个关 键模块是如何组织起来的。/*指明该驱动程序适用于哪一些PCI设备*/static struct pci_device_id demo_pci_tbl _initdata = PCI_VENDOR_ID_DEMO, PCI_DEVICE_ID_DEMO,PCI_ANY_ID, PCI_ANY_ID, 0, 0, DEMO,0,;/*对特定PCI设备进行描述的数据结构*/ struct demo_card unsigned int magic;/*使用链表保存所有同类的PCI设备*/ struct demo_card *next;

26、/* . */* 中断处理模块 */static void demo_interrupt(int irq, void *dev_id, struct pt_regs *regs)/* . */* 设备文件操作接口 */static struct file_operations demo_fops = owner:THIS_MODULE, /* demo_fops 所属的设备模块 */read:demo_read,/* 读设备操作*/write:demo_write, /* 写设备操作*/ioctl:demo_ioctl, /* 控制设备操作*/mmap:demo_mmap, /* 内存重映射操

27、作*/open:demo_open, /* 打开设备操作*/release:demo_release/* 释放设备操作*/* . */;/* 设备模块信息 */static struct pci_driver demo_pci_driver = name:demo_MODULE_NAME, /* 设备模块名称 */id_table:demo_pci_tbl, /* 能够驱动的设备列表 */probe:demo_probe, /* 查找并初始化设备 */remove:demo_remove /* 卸载设备模块 */;static int _init demo_init_module (void)

28、/* . */static void _exit demo_cleanup_module (void)pci_unregister_driver(&demo_pci_driver);/* 加载驱动程序模块入口 */module_init(demo_init_module);/* 卸载驱动程序模块入口 */module_exit(demo_cleanup_module)上面这段代码给出了一个典型的PCI设备驱动程序的框架,是一种相对固定的模式。 需要注意的是,同加载和卸载模块相关的函数或数据结构都要在前面加上_init、 _exit等标志符,以使同普通函数区分开来。构造出这样一个框架之后,接下去

29、的工 作就是如何完成框架内的各个功能模块了。3. 初始化设备模块在Linux系统下,想要完成对一个PCI设备的初始化,需要完成以下工作:检查PCI总线是否被Linux内核支持;检查设备是否插在总线插槽上,如果在的话则保存它所占用的插槽的位置等信息。 读出配置头中的信息提供给驱动程序使用。当Linux内核启动并完成对所有PCI设备进行扫描、登录和分配资源等初始化操作 的同时,会建立起系统中所有PCI设备的拓扑结构,此后当PCI驱动程序需要对设备进 行初始化时,一般都会调用如下的代码:static int _init demo_init_module (void)/*检查系统是否支持PCI总线*/

30、if (!pci_present()return -ENODEV;/* 注册硬件驱动程序 */if (!pci_register_driver(&demo_pci_driver) pci_unregister_driver(&demo_pci_driver);return -ENODEV;/* . */return 0;驱动程序首先调用函数pci_present()检查PCI总线是否已经被Linux内核支持, 如果系统支持PCI总线结构,这个函数的返回值为0如果驱动程序在调用这个函数时 得到了一个非0 的返回值,那么驱动程序就必须得中止自己的任务了。在2.4以前的 内核中,需要手工调用pci_

31、find_device()函数来查找pci设备,但在2.4以后更好 的办法是调用pci_register_driver()函数来注册pci设备的驱动程序,此时需要提供 一个pci_driver结构,在该结构中给出的probe探测例程将负责完成对硬件的检测工 作。static int _init demo_probe(struct pci_dev *pci_dev, const struct pci_device_id *pci_id)struct demo_card *card;/* 启动 PCI 设备 */if (pci_enable_device(pci_dev)return -EIO;/

32、* 设备 DMA 标识 */if (pci_set_dma_mask(pci_dev, DEMO_DMA_MASK) return -ENODEV;/* 在内核空间中动态申请内存 */if (card = kmalloc(sizeof(struct demo_card), GFP_KERNEL) = NULL) printk(KERN_ERR pci_demo: out of memoryn); return -ENOMEM;memset(card, 0, sizeof(*card);/*读取PCI配置信息*/card-iobase = pci_resource_start (pci_dev,

33、 1);card-pci_dev = pci_dev;card-pci_id = pci_id-device;card-irq = pci_dev-irq;card-next = devs;card-magic = DEMO_CARD_MAGIC;/*设置成总线主DMA模式*/pci_set_master(pci_dev);/*申请I/O资源*/request_region(card-iobase, 64, card_namespci_id-driver_data);return 0;4. 打开设备模块在这个模块里主要实现申请中断、检查读写模式以及申请对设备的控制权等。在申 请控制权的时候,非

34、阻塞方式遇忙返回,否则进程主动接受调度,进入睡眠状态,等待 其它进程释放对设备的控制权。static int demo_open(struct inode *inode, struct file *file)/* 申请中断,注册中断处理程序 */ request_irq(card-irq, &demo_interrupt, SA_SHIRQ, card_namespci_id-driver_data, card) /* 检查读写模式 */if(file-f_mode & FMODE_READ) /* . */if(file-f_mode & FMODE_WRITE) /* . */* 申请对设

35、备的控制权 */down(&card-open_sem); while(card-open_mode & file-f_mode) if (file-f_flags & O_NONBLOCK) /* NONBLOCK 模式,返回-EBUSY */ up(&card-open_sem);return -EBUSY; else /* 等待调度,获得控制权 */ card-open_mode |= f_mode & (FMODE_READ | FMODE_WRITE);up(&card-open_sem);/* 设备打开计数增1 */MOD_INC_USE_COUNT;/* . */5. 数据读写和

36、控制信息模块pci设备驱动程序可以通过demo_fops结构中的函数demo_ioctl(),向应用程 序提供对硬件进行控制的接口。例如,通过它可以从I/O寄存器里读取一个数据,并传 送到用户空间里:static int demo_ioctl(struct inode *inode, struct file *file, unsigned int cmd, unsigned long arg)/* . */switch(cmd) case DEMO_RDATA:/* 从 I/O 端口读取 4 字节的数据 */val = inl(card-iobae + 0x10);/* 将读取的数据传输到用户

37、空间 */ return 0;/* . */事实上,在demo_fops里还可以实现诸如demo_read( )、demo_mmap() 等操作Linux内核源码中的driver目录里提供了许多设备驱动程序的源代码,找那里 可以找到类似的例子。在对资源的访问方式上,除了有I/O指令以外,还有对外设I/O 内存的访问。对这些内存的操作一方面可以通过把I/O内存重新映射后作为普通内存进行操作,另一方面也可以通过总线主DMA (Bus Master DMA)的方式让设备把数据 通过DMA传送到系统内存中。6. 中断处理模块PC 的中断资源比较有限,只有015的中断号,因此大部分外部设备都是以共享 的

38、形式申请中断号的。当中断发生的时候,中断处理程序首先负责对中断进行识别,然 后再做进一步的处理。static void demo_interrupt(int irq, void *dev_id, struct pt_regs *regs)struct demo_card *card =(struct demo_card *)dev_id;u32 status;spin_lock(&card-lock);/* 识别中断 */status = inl(card-iobase + GLOB_STA); if(!(status & INT_MASK)spin_unlock(&card-lock);re

39、turn; /* not for us */* 告诉设备已经收到中断 */outl(status & INT_MASK, card-iobase + GLOB_STA); spin_unlock(&card-lock);/*其它进一步的处理,如更新DMA缓冲区指针等*/7. 释放设备模块释放设备模块主要负责释放对设备的控制权,释放占用的内存和中断等,所做的事情正好与打开设备模块相反:static int demo_release(struct inode *inode, struct file *file)/* . */* 释放对设备的控制权 */card-open_mode &= (FMOD

40、E_READ | FMODE_WRITE);/* 唤醒其它等待获取控制权的进程 */wake_up(&card-open_wait);up(&card-open_sem);/* 释放中断 */free_irq(card-irq, card);/* 设备打开计数增1 */MOD_DEC_USE_COUNT;/* . */8. 卸载设备模块 卸载设备模块与初始化设备模块是相对应的,实现起来相对比较简单,主要是调用函数pci_unregister_driver()从Linux内核中注销设备驱动程序:static void _exit demo_cleanup_module (void)pci_unregister_driver(&demo_pci_driver);四、小结PCI总线不仅是目前应用广泛的计算机总线标准,而且是一种兼容性最强、功能最全 的计算机总线。而Linux作为一种新的操作系统,其发展前景是无法估量的,同时也为 PCI总线与各种新型设备互连成为可能。由于Linux源码开放,因此给连接到PCI总线上 的任何设备编写驱动程序变得相对容易。本文介绍如何编译Linux下的PCI驱动程序, 针对的内核版本是2.4。

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

copyright@ 2023-2025  zhuangpeitu.com 装配图网版权所有   联系电话:18123376007

备案号:ICP2024067431-1 川公网安备51140202000466号


本站为文档C2C交易模式,即用户上传的文档直接被用户下载,本站只是中间服务平台,本站所有文档下载所得的收益归上传人(含作者)所有。装配图网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对上载内容本身不做任何修改或编辑。若文档所含内容侵犯了您的版权或隐私,请立即通知装配图网,我们立即给予删除!