电子科技大学-嵌入式linux设备驱动程序的开发.doc

上传人:豆**** 文档编号:28430140 上传时间:2022-07-28 格式:DOC 页数:75 大小:179KB
返回 下载 相关 举报
电子科技大学-嵌入式linux设备驱动程序的开发.doc_第1页
第1页 / 共75页
电子科技大学-嵌入式linux设备驱动程序的开发.doc_第2页
第2页 / 共75页
点击查看更多>>
资源描述

《电子科技大学-嵌入式linux设备驱动程序的开发.doc》由会员分享,可在线阅读,更多相关《电子科技大学-嵌入式linux设备驱动程序的开发.doc(75页珍藏版)》请在taowenge.com淘文阁网|工程机械CAD图纸|机械工程制图|CAD装配图下载|SolidWorks_CaTia_CAD_UG_PROE_设计图分享下载上搜索。

1、Four short words sum up what has lifted most successful individuals above the crowd: a little bit more.-author-date电子科技大学-嵌入式linux设备驱动程序的开发电子科技大学-嵌入式linux设备驱动程序的开发Linux将所有外部设备看成是一类特殊文件,称之为“设备文件”,如果说系统调用是Linux内核和应用程序之间的接口,那么设备驱动程序则可以看成是Linux内核与外部设备之间的接口。设备驱动程序向应用程序屏蔽了硬件在实现上的细节,使得应用程序可以像操作普通文件一样来操作外部设

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

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

4、(即已经加载了驱动程序)的硬件设备的主设备号可以从/proc/devices文件中得到。使用mknod命令可以创建指定类型的设备文件,同时为其分配相应的主设备号和次设备号。例如,下面的命令:rootgary root# mknod /dev/lp0 c 6 0将建立一个主设备号为6,次设备号为0的字符设备文件/dev/lp0。当应用程序对某个设备文件进行系统调用时,Linux内核会根据该设备文件的设备类型和主设备号调用相应的驱动程序,并从用户态进入到核心态,再由驱动程序判断该设备的次设备号,最终完成对相应硬件的操作。2. 设备驱动程序接口Linux中的I/O子系统向内核中的其他部分提供了一个统

5、一的标准设备接口,这是通过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 fil

6、e *, 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 (

7、*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) (s

8、truct 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内核将

9、通过file_operations结构访问驱动程序提供的函数。例如,当应用程序对设备文件执行读操作时,内核将调用file_operations结构中的read函数。2. 设备驱动程序模块 Linux下的设备驱动程序可以按照两种方式进行编译,一种是直接静态编译成内核的一部分,另一种则是编译成可以动态加载的模块。如果编译进内核的话,会增加内核的大小,还要改动内核的源文件,而且不能动态地卸载,不利于调试,所有推荐使用模块方式。 从本质上来讲,模块也是内核的一部分,它不同于普通的应用程序,不能调用位于用户态下的C或者C+库函数,而只能调用Linux内核提供的函数,在/proc/ksyms中可以查看到内

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

11、使用该模块的用户数。设备驱动程序结构了解设备驱动程序的基本结构(或者称为框架),对开发人员而言是非常重要的,Linux的设备驱动程序大致可以分为如下几个部分:驱动程序的注册与注销、设备的打开与释放、设备的读写操作、设备的控制操作、设备的中断和轮询处理。驱动程序的注册与注销 向系统增加一个驱动程序意味着要赋予它一个主设备号,这可以通过在驱动程序的初始化过程中调用register_chrdev( )或者register_blkdev( )来完成。而在关闭字符设备或者块设备时,则需要通过调用unregister_chrdev( )或unregister_blkdev( )从内核中注销设备,同时释放占

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

13、),它的作用正好与open( )相反,通常要完成下列工作: 1.使用计数减1。 2.释放在file-private_data中分配的内存。 3.如果使用计算为0,则关闭设备。 设备的读写操作 字符设备的读写操作相对比较简单,直接使用函数read( )和write( )就可以了。但如果是块设备的话,则需要调用函数block_read( )和block_write( )来进行数据读写,这两个函数将向设备请求表中增加读写请求,以便Linux内核可以对请求顺序进行优化。由于是对内存缓冲区而不是直接对设备进行操作的,因此能很大程度上加快读写速度。如果内存缓冲区中没有所要读入的数据,或者需要执行写操作将数

14、据写入设备,那么就要执行真正的数据传输,这是通过调用数据结构blk_dev_struct中的函数request_fn( )来完成的。 设备的控制操作 除了读写操作外,应用程序有时还需要对设备进行控制,这可以通过设备驱动程序中的函数ioctl( )来完成。ioctl( )的用法与具体设备密切关联,因此需要根据设备的实际情况进行具体分析。 设备的中断和轮询处理对于不支持中断的硬件设备,读写时需要轮流查询设备状态,以便决定是否继续进行数据传输。如果设备支持中断,则可以按中断方式进行操作。 三、PCI驱动程序实现1. 关键数据结构PCI设备上有三种地址空间:PCI的I/O空间、PCI的存储空间和PCI

15、的配置空间。CPU可以访问PCI设备上的所有地址空间,其中I/O空间和存储空间提供给设备驱动程序使用,而配置空间则由Linux内核中的PCI初始化代码使用。内核在启动时负责对所有PCI设备进行初始化,配置好所有的PCI设备,包括中断号以及I/O基址,并在文件/proc/pci中列出所有找到的PCI设备,以及这些设备的参数和属性。Linux驱动程序通常使用结构(struct)来表示一种设备,而结构体中的变量则代表某一具体设备,该变量存放了与该设备相关的所有信息。好的驱动程序都应该能驱动多个同种设备,每个设备之间用次设备号进行区分,如果采用结构数据来代表所有能由该驱动程序驱动的设备,那么就可以简单

16、地使用数组下标来表示次设备号。在PCI驱动程序中,下面几个关键数据结构起着非常核心的作用:pci_driver 这个数据结构在文件include/linux/pci.h里,这是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_d

17、ev *dev, const struct pci_device_id *id); void (*remove) (struct pci_dev *dev); int (*save_state) (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 这个数据结构也在文件i

18、nclude/linux/pci.h里,它详细描述了一个PCI设备几乎所有的硬件信息,包括厂商ID、设备ID、各种资源等:struct pci_dev struct list_head global_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; unsi

19、gned short subsystem_vendor; unsigned short subsystem_device; unsigned int class; u8 hdr_type; u8 rom_base_reg; struct pci_driver *driver; void *driver_data; u64 dma_mask; u32 current_state; unsigned short vendor_compatibleDEVICE_COUNT_COMPATIBLE; unsigned short device_compatibleDEVICE_COUNT_COMPATI

20、BLE; unsigned int irq; struct resource resourceDEVICE_COUNT_RESOURCE; struct resource dma_resourceDEVICE_COUNT_DMA; struct resource irq_resourceDEVICE_COUNT_IRQ; char name80; char slot_name8; int active; int ro; unsigned short regs; int (*prepare)(struct pci_dev *dev); int (*activate)(struct pci_dev

21、 *dev); int (*deactivate)(struct pci_dev *dev);2. 基本框架在用模块方式实现PCI设备驱动程序时,通常至少要实现以下几个部分:初始化设备模块、设备打开模块、数据读写和控制模块、中断处理模块、设备释放模块、设备卸载模块。下面给出一个典型的PCI设备驱动程序的基本框架,从中不难体会到这几个关键模块是如何组织起来的。/* 指明该驱动程序适用于哪一些PCI设备 */static struct pci_device_id demo_pci_tbl _initdata = PCI_VENDOR_ID_DEMO, PCI_DEVICE_ID_DEMO, PCI

22、_ANY_ID, PCI_ANY_ID, 0, 0, DEMO, 0,;/* 对特定PCI设备进行描述的数据结构 */struct demo_card unsigned int magic; /* 使用链表保存所有同类的PCI设备 */ struct demo_card *next; /* . */* 中断处理模块 */static void demo_interrupt(int irq, void *dev_id, struct pt_regs *regs) /* . */* 设备文件操作接口 */static struct file_operations demo_fops = owner

23、: THIS_MODULE, /* demo_fops所属的设备模块 */ read: demo_read, /* 读设备操作*/ write: demo_write, /* 写设备操作*/ ioctl: demo_ioctl, /* 控制设备操作*/ mmap: demo_mmap, /* 内存重映射操作*/ open: demo_open, /* 打开设备操作*/ release: demo_release /* 释放设备操作*/ /* . */;/* 设备模块信息 */static struct pci_driver demo_pci_driver = name: demo_MODULE

24、_NAME, /* 设备模块名称 */ id_table: demo_pci_tbl, /* 能够驱动的设备列表 */ probe: demo_probe, /* 查找并初始化设备 */ remove: demo_remove /* 卸载设备模块 */ /* . */;static int _init demo_init_module (void) /* . */static void _exit demo_cleanup_module (void) pci_unregister_driver(&demo_pci_driver);/* 加载驱动程序模块入口 */module_init(demo

25、_init_module);/* 卸载驱动程序模块入口 */module_exit(demo_cleanup_module);上面这段代码给出了一个典型的PCI设备驱动程序的框架,是一种相对固定的模式。需要注意的是,同加载和卸载模块相关的函数或数据结构都要在前面加上_init、_exit等标志符,以使同普通函数区分开来。构造出这样一个框架之后,接下去的工作就是如何完成框架内的各个功能模块了。3. 初始化设备模块在Linux系统下,想要完成对一个PCI设备的初始化,需要完成以下工作:检查PCI总线是否被Linux内核支持;检查设备是否插在总线插槽上,如果在的话则保存它所占用的插槽的位置等信息。读

26、出配置头中的信息提供给驱动程序使用。 当Linux内核启动并完成对所有PCI设备进行扫描、登录和分配资源等初始化操作的同时,会建立起系统中所有PCI设备的拓扑结构,此后当PCI驱动程序需要对设备进行初始化时,一般都会调用如下的代码:static int _init demo_init_module (void) /* 检查系统是否支持PCI总线 */ if (!pci_present() return -ENODEV; /* 注册硬件驱动程序 */ if (!pci_register_driver(&demo_pci_driver) pci_unregister_driver(&demo_pc

27、i_driver); return -ENODEV; /* . */ return 0;驱动程序首先调用函数pci_present( )检查PCI总线是否已经被Linux内核支持,如果系统支持PCI总线结构,这个函数的返回值为0,如果驱动程序在调用这个函数时得到了一个非0的返回值,那么驱动程序就必须得中止自己的任务了。在2.4以前的内核中,需要手工调用pci_find_device( )函数来查找PCI设备,但在2.4以后更好的办法是调用pci_register_driver( )函数来注册PCI设备的驱动程序,此时需要提供一个pci_driver结构,在该结构中给出的probe探测例程将负责

28、完成对硬件的检测工作。 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; /* 设备DMA标识 */ if (pci_set_dma_mask(pci_dev, DEMO_DMA_MASK) return -ENODEV; /* 在内核空间中动态申请内存 */ if (card = kmalloc(size

29、of(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, 1); card-pci_dev = pci_dev; card-pci_id = pci_id-device; card-irq = pci_dev-irq; card-next = devs; card-magic

30、 = DEMO_CARD_MAGIC; /* 设置成总线主DMA模式 */ pci_set_master(pci_dev); /* 申请I/O资源 */ request_region(card-iobase, 64, card_namespci_id-driver_data); return 0;4. 打开设备模块在这个模块里主要实现申请中断、检查读写模式以及申请对设备的控制权等。在申请控制权的时候,非阻塞方式遇忙返回,否则进程主动接受调度,进入睡眠状态,等待其它进程释放对设备的控制权。static int demo_open(struct inode *inode, struct file

31、*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) /* . */ /* 申请对设备的控制权 */ down(&card-open_sem); while(card-open_mode & file-f_mode) if (file-f_flags & O_NONBL

32、OCK) /* 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. 数据读写和控制信息模块PCI设备驱动程序可以通过demo_fops 结构中的函数demo_ioctl( ),向应用程序提供对硬件进行控制的接口。例如,通过它可以从I/O寄存器里读取一个

33、数据,并传送到用户空间里: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); /* 将读取的数据传输到用户空间 */ return 0; /* . */事实上,在demo_fops里还可以实现诸如demo_read( )、demo_mmap( )等操作,Linux内核源码中的d

34、river目录里提供了许多设备驱动程序的源代码,找那里可以找到类似的例子。在对资源的访问方式上,除了有I/O指令以外,还有对外设I/O内存的访问。对这些内存的操作一方面可以通过把I/O内存重新映射后作为普通内存进行操作,另一方面也可以通过总线主DMA(Bus Master DMA)的方式让设备把数据通过DMA传送到系统内存中。 6. 中断处理模块PC的中断资源比较有限,只有015的中断号,因此大部分外部设备都是以共享的形式申请中断号的。当中断发生的时候,中断处理程序首先负责对中断进行识别,然后再做进一步的处理。static void demo_interrupt(int irq, void *

35、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); return; /* not for us */ /* 告诉设备已经收到中断 */ outl(status & INT_MASK, card-iobase + GLOB

36、_STA); spin_unlock(&card-lock); /* 其它进一步的处理,如更新DMA缓冲区指针等 */7. 释放设备模块释放设备模块主要负责释放对设备的控制权,释放占用的内存和中断等,所做的事情正好与打开设备模块相反:static int demo_release(struct inode *inode, struct file *file) /* . */ /* 释放对设备的控制权 */ card-open_mode &= (FMODE_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);-

展开阅读全文
相关资源
相关搜索

当前位置:首页 > 教育专区 > 小学资料

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

工信部备案号:黑ICP备15003705号© 2020-2023 www.taowenge.com 淘文阁