第11章嵌入式Linux设备驱动程序ppt课件.ppt
1,第11章 嵌入式Linux设备驱动程序,2,11.4 Linux设备驱动程序开发,设备驱动程序开发流程(字符设备)模块化的驱动程序设计方式(字符设备)字符型设备驱动demo源程序分析LED驱动程序开发实例键盘驱动程序开发实例,3,11.4.1 设备驱动程序开发流程,定义主、次设备号,也可以动态获取。通过file_operations结构定义设备所需的文件操作:所定义的文件操作对应的file_operations结构体函数指针,指向相应的设备操作函数(设备驱动程序的各个函数)实现设备驱动初始化函数-申请中断(如果有)、注册设备和退出函数释放中断(如果有)、卸载设备。如果驱动程序采用模块方式,则要实现模块的初始化和退出函数。实现设备所需的文件操作:相应的设备操作函数(设备驱动程序的各个函数)实现实现中断服务程序(如果有)将驱动编译到内核。如果驱动程序采用模块方式,需先编译成模块,然后加载。,4,11.4.2 模块化的驱动程序设计方式,模块机制“module”可以根据需要在不重新编译内核的情况下,将编译好的模块动态的插入运行中的内核,或者从运行中内核中将内核已经存在的一个模块移走。这种机制可以动态加载设备驱动程序到内核,为驱动程序开发调试提供了很大的方便在调试的过程中一般使用模块动态加载的方式,它的调试效率较高。当驱动调试完成后,在发行的过程就集成进内核。但编译进内核是某些驱动运行的唯一方法。例如:console驱动,flash驱动和对至少一种文件系统的支持等等,5,11.4.2 模块化的驱动程序设计方式,用gcc编译成模块,要加上以下参数:-D_KERNEL_-DMODULE I$(KERNELDIR_INCLUDE)在内核运行时,可以通过lsmod 察看内核中已经动态加载的模块。而模块加载到内核和从内核中卸载可以通过以下命令实现,它们的操作对象xxxx是经过编译但没有链接的.ko 文件(实际上就是.o文件,在2.4版本及之前不用.ko文件,直接就沿用为.o文件)insmod xxxx 将编译的模块直接插入内核rmmod xxxx 将编译的模块从内核中卸载,6,11.4.2 模块化的驱动程序设计方式,以驱动程序源文件为demo.c为例 驱动程序的编译(生成模块)gcc c-D_KERNEL_-DMODULE-I/usr/src/linux-2.4/include demo.c-o demo.ko 加载驱动(模块):insmod demo.ko 卸载驱动(模块):rmmod demo.ko对应的模块化驱动程序编程中,设备驱动程序源程序中必须至少提供两个宏:module_init(驱动程序初始化函数名):初始化模块的宏,在模块加载时调用module_exit(驱动程序退出函数名):卸载模块的宏,在模块卸载时调用,7,11.4.2 模块化的驱动程序设计方式,例:一个简单的模块化设备驱动程序/*-mdemo.c-*/#define MODULE#include int init_module(void)printk(nhello,world!nn”);return 0;void cleanup_module(void)printk(n Bye Byenn);module_init(init_module);module_exit(cleanup_module);,8,11.4.2 模块化的驱动程序设计方式,首先通过下面命令来编译源文件mdemo.c:gcc-c-D_KERNEL_-DMODULE-o mdemo.ko mdemo.c在得到了mdemo.ko模块文件后,用insmod 命令把它动态加载到内核:#insmod mdemo.ko#hello world!#rmmod mdemo.ko#Bye Bye,9,驱动程序与应用程序的区别设备驱动程序在Linux内核中以模块形式出现,与应用程序的执行过程不同,模块通常只是预先向内核注册自己,当内核需要时才被调用执行。,11.4.2 模块化的驱动程序设计方式,10,驱动程序与应用程序的区别应用程序一般有一个main函数,从头到尾执行一个任务;驱动程序却不同,它没有main函数,因为它实际上只是在内核中可供调用的函数。应用程序可以和C函数库链接,因此可以包含标准的头文件,比如、等;在驱动程序中是不能使用标准C 库的,因此不能调用C库函数,只能调用内核函数。eg:比如输出打印函数只能使用内核的printk 函数,包含的头文件只能是内核的头文件,比如,11.4.2 模块化的驱动程序设计方式,11,假设有一个简单的虚拟字符型设备设备名为“demo”设备中只有一个20个字节的缓冲区drv_buf(位于内核空间),对该设备的读写操作就是对缓冲区drv_buf的操作由于没有实际的硬件设备,故不需要驱动程序的下半部分,更加不需要采用中断方式实现,11.4.3 字符型设备驱动demo源程序分析,12,demo设备的驱动程序结构,11.4.3 字符型设备驱动demo源程序分析,13,/*头文件*/#include#include#include/*printk()*/#include/*error codes*/#include/*定义主、次设备号*/#define DEVICE_NAME“demo”/*设备名*/#define demo_MINOR 0/*定义次设备号*/static int demo_MAJOR 0/*自动获取主设备号*/设备文件系统支持#ifdefCONFIG_DEVFS_FS staticdevfs_handle_t devfs_DbDemo_dir,devfs_DbDemoRaw;#endif/*定义虚拟字符型设备demo的内存数据结构*/#define MAX_BUF_LEN 20static char drv_bufMAX_BUF_LEN;/*处于内核空间*/,11.4.3 字符型设备驱动demo源程序分析,14,/*函数声明*/static int demo_init(void);/*声明初始化函数*/static void demo_exit(void);/*声明退出函数*/*设备操作函数(设备驱动程序的各个函数)声明*/static int demo_open(struct inode*inode,struct file*filp);static int demo_release(struct inode*inode,struct file*filp);static ssize_t demo_read(struct file*filp,char*buffer,size_t count,loff_t*ppos);static ssize_t demo_write(struct file*filp,const char*buffer,size_t count);static int demo_ioctl(struct inode*inode,struct file*file,unsigned int cmd,unsigned long arg);,11.4.3 字符型设备驱动demo源程序分析,15,/*全局变量定义*/*通过file_operations结构定义设备所需的文件操作:指向相应的设备操作函数(设备驱动程序的各个函数)*/static struct file_operations demo_fops=#if LINUX_KERNEL_VERSION=KERNEL_VERSION(2,4,0)owner:THIS_MODULE,#endifllseek:NULL,write:demo_write,read:demo_read,ioctl:demo_ioctl,open:demo_open,release:demo_release,;,11.4.3 字符型设备驱动demo源程序分析,16,/*实现初始化和退出函数*/static int demo_init(void)int result;result=register_chrdev(demo_MAJOR,DEVICE_NAME,11.4.3 字符型设备驱动demo源程序分析,demo_fops指向file_operations结构体变量,17,在LINUX 2.4内核中引入了设备文件系统(devfs),它是文件系统的一部分。有了设备文件系统的支持,可以在设备驱动程序中自动创建设备文件(节点文件)。以前的Linux版本需要手工创建设备文件先给register_chrdev()传递0主设备号,以动态分配获得可用的主设备号后可在devfs_register()中指定次设备号 devfs下,设备文件命名规则比以前的Linux版本也发生了变化,多创建了一层目录:主设备号建立一个设备目录,再将具体的设备实例建立在此目录下 Eg:串口0 设备为:/dev/tts/0,11.4.3 字符型设备驱动demo源程序分析,18,11.4.3 字符型设备驱动demo源程序分析,devfs相关函数devfs_mk_dir 函数,创建一个名为demo的设备文件目录(/dev/demo),并返回一个带有目录结构的数据结构变量devfs_DbDemo_dir。将该变量作为下一步devfs_register 函数的参数。该参数在调用设备文件系统注册清除函数devfs_unregister 时也要作为参数传入devfs_register 函数在刚才创建的demo目录下再创建一个名为0的设备文件(节点文件)。该函数的参数中,DEVFS_FL_DEFAULT 为该函数的标志选项,demo_MAJOR为注册字符设备时系统自动分配的主设备号,demo_MINOR为次设备号,S_IFCHR|S_IRUSR|S_IWUSR 为默认的文件模式,&s3c2410_fops 为传入内核的demo设备file_operations 变量。该函数返回一个devfs_handle_t 数据结构的变量devfs_DbDemoRaw。这在调用设备文件系统注册清除函数devfs_unregister 时也要作为参数传入,19,/*实现退出函数*/static void demo_exit(void)/删除设备文件(节点文件)#ifdefCONFIG_DEVFS_FS devfs_unregister(devfs_DbDemoRaw);devfs_unregister(devfs_DbDemo_dir);#endif unregister_chrdev(demo_MAJOR,DEVICE_NAME);printk(DEVICE_NAME“unloadedn);/*实现模块初始化和退出函数*/module_init(demo_init);module_exit(demo_exit);,11.4.3 字符型设备驱动demo源程序分析,20,/*实现设备所需的文件操作:相应的设备操作函数(设备驱动程序的各个函数)实现*/static int demo_open(struct inode*inode,struct file*filp)MOD_INC_USE_COUNT;/维护递增计数器,2.6不再支持/you can insert your code hereprintk(device open sucess!n);return 0;static int demo_release(struct inode*inode,struct file*filp)MOD_DEC_USE_COUNT;/维护递增计数器,2.6不再支持/you can insert your code hereprintk(device releasen);return 0;,11.4.3 字符型设备驱动demo源程序分析,21,打开设备open,通常情况下完成如下工作:检查设备如果设备是首次打开,则对其进行初始化,为后续的设备操作做准备分配用于存放设备相关数据结构的内存空间一般会递增使用计数,防止文件关闭前模块被卸载(Linux 2.6不再需要)识别次设备号,11.4.3 字符型设备驱动demo源程序分析,释放设备release,与open正好相反释放由open分配的内存空间使用计数减一(Linux 2.6不再需要)在最后一次关闭操作时关闭设备,22,static ssize_t demo_read(struct file*filp,char*buffer,size_t count,loff_t*ppos)if(count MAX_BUF_LEN)count=MAX_BUF_LEN;/you can insert your code herecopy_to_user(buffer,drv_buf,count);printk(user read data from drivern);return count;static ssize_t demo_write(struct file*filp,const char*buffer,size_t count)/you can insert your code herecopy_from_user(drv_buf,buffer,count);printk(user write data to drivern);/your code herereturn count;,11.4.3 字符型设备驱动demo源程序分析,用户空间,内核空间,/*实现设备所需的文件操作:相应的设备操作函数(设备驱动程序的各个函数)实现*/,23,读写设备(1/3):,read函数将数据从内核空间拷贝到应用程序用户空间,write函数则将数据从用户空间拷贝到内核空间ssize_t read(struct file*filp,char*buff,size_t count,loff_t*offp);ssize_t write(struct file*filp,const char*buff,size_t count,loff_t*offp);其中,filp是文件指针count是请求传输的数据长度buff是指向用户空间的缓存区,这个缓存区或者是一个存放从内核读入数据的空缓冲区,或者保存要写入到内核的数据loffp是一个指向“long offset type(长偏移量类型)”的指针,指明用户在文件中进行存取操作的位置,11.4.3 字符型设备驱动demo源程序分析,24,读写设备(2/3):,问题:buff为用户空间的地址指针,处于内核空间的设备驱动程序不能直接访问其中的内容原因:用户空间的指针可能是无效的,该地址可能根本就无法映射到内核空间用户空间的内存可以被换出,因此可能会出现页面失效的问题从安全角度考虑解决办法:使用内核函数进行数据拷贝,11.4.3 字符型设备驱动demo源程序分析,25,内核数据拷贝函数unsigned long copy_to_user(void*to,const void*from,unsigned long count);unsigned long copy_from_user(void*to,const void*from,unsigned long count);其中:to表示数据目的缓冲区 from表示数据源缓冲区 count表示数据长度返回值:成功,返回数据长度 失败,返回EFAULT这两个函数不仅要拷贝数据,还要检查指针有效性,读写设备(3/3):,11.4.3 字符型设备驱动demo源程序分析,26,static int demo_ioctl(struct inode*inode,struct file*file,unsigned int cmd,unsigned long arg)/you can insert your code hereprintk(ioctl runingn);switch(cmd)case 1:printk(runing command 1 n);break;case 2:printk(runing command 2 n);break;default:printk(error cmd numbern);break;return 0;,11.4.3 字符型设备驱动demo源程序分析,/*实现设备所需的文件操作:相应的设备操作函数(设备驱动程序的各个函数)实现*/,27,ioctl函数:int(*ioctl)(struct inode*inode,struct file*filp,unsigned int cmd,unsigned long arg);其中,cmd是用户传递给驱动程序的命令,arg为该命令的参数,读写以外的I/O控制操作函数:,11.4.3 字符型设备驱动demo源程序分析,28,#include#include#include#include main()int fd,num;char buf10;/打开demo设备 fd=open(/dev/demo/0,O_RDWR,S_IRUSR|S_IWUSR);if(fd!=-1)/初次读demo read(fd,buf,10);printf(The demo is%sn,buf);,应用程序:用于测试驱动程序(1/3),11.4.3 字符型设备驱动demo源程序分析,29,/写demo printf(Please input the string written to demon);scanf(%s,应用程序:用于测试驱动程序(2/3),11.4.3 字符型设备驱动demo源程序分析,30,运行结果:,应用程序:用于测试驱动程序(3/3),11.4.3 字符型设备驱动demo源程序分析,31,11.4.4 LED驱动程序开发实例LED驱动工作原理LED驱动程序设计,32,LED驱动工作原理,MCU芯片S3C2410两个GPIO引脚GPIO_G12和GPIO_G13与两个LED(红、绿)相连,驱动双色LEDLED设备驱动程序控制GPIO_G12和GPIO_G13两个I/O引脚的电平,就可以实现双色LED显示,33,驱动程序开发所要了解的硬件知识,设备硬件这么复杂,驱动程序开发人员需要全部了解硬件实现细节吗?不需要全部了解那应该了解哪些?驱动程序开发人员不用去关心设备硬件的详细实现细节,只要关心:设备硬件大致的实现原理、控制设备硬件执行的方式:写入相关的设备内部寄存器(如数据寄存器、控制寄存器),或采用设备厂商提供相关函数硬件执行结果返回的方式:读取相关的设备内部寄存器(如数据寄存器、状态寄存器),或采用设备厂商提供相关函数注意:设备可能是MCU/SoC芯片外部连接的芯片,也可能是MCU/SoC芯片内部的设备(如刚才提到的GPIO),34,LED驱动程序设计,/头文件#include#include/它定义了模块的 API、类型和宏,所有内核 模块都必须包含这个头文件#include#include/头文件:module_init、module_exit等宏定义#include#include#include/调试程序#undefDEBUG/#defineDEBUG#ifdefDEBUG#defineDPRINTK(x.)printk(s3c2410-led:x)#else#defineDPRINTK(x.)#end,35,LED驱动程序设计,/定义设备名、主次设备号#defineDEVICE_NAME“led”#defineDbLedRAW_MINOR 1staticintDbLedMajor=0;/自动获得主设备号/定义设备相关的数据结构、宏等#define LEDGreenGPIO_G12#define LEDRedGPIO_G13static unsigned int LED=LEDGreen,LEDRed#define NumberOfLed(sizeof(LED)/sizeof(*LED)/计算LED数组大小staticcharledstatus=3;/设置LED状态/00 红绿都暗;01 红亮;10 绿亮;11 红绿都亮,显橙色/设备文件系统支持#ifdefCONFIG_DEVFS_FSstaticdevfs_handle_t devfs_DbLed_dir,devfs_DbLedraw;#endif,36,LED驱动程序设计,/设备驱动程序的各个函数声明staticints3c2410_DbLed_init(void);staticvoidUpdateled(void);/这是一个设备驱动程序各函数中要用到的一个函数staticvoids3c2410_DbLed_exit(void);staticints3c2410_DbLed_open(structinode*inode,structfile*filp);staticints3c2410_DbLed_release(structinode*inode,structfile*filp);staticssize_t s3c2410_DbLed_write(structfile*file,constchar*buffer,size_tcount,loff_t*ppos);staticssize_t s3c2410_DbLed_read(structfile*file,constchar*buffer,size_tcount,loff_t*ppos);/通过file_operations结构定义设备所需的文件操作:指向相应的设备操作函数(设备驱动程序的各个函数)staticstructfile_operations s3c2410_fops=owner:THIS_MODULE,open:s3c2410_DbLed_open,write:s3c2410_DbLed_write,read:s3c2410_DbLed_read,release:s3c2410_DbLed_release,;/初始化和退出函数的模块实现module_init(s3c2410_DbLed_init);module_exit(s3c2410_DbLed_exit);,37,LED驱动程序设计,staticints3c2410_DbLed_init(void)/初始化函数的实现 intret;/初始化硬件 for(i=0;iNumberOfLed;i+)set_gpio_ctrl(GPIO_MODE_OUT|GPIO_PULLUP_DIS|LEDi);/设置GPIO端口寄存器状态:将两个GPIO引脚设为输出且无上拉状态 Updateled();/注册字符设备驱动 ret=register_chrdev(DbLedMajor,DEVICE_NAME,38,LED驱动程序设计,staticvoidUpdateled(void)int I;for(i=0;iNumberOfLed;i+)if(ledstatus/设置GPIO引脚电平为0,使相应的LED灭,39,LED驱动程序设计,staticvoid_exit s3c2410_DbLed_exit(void)/退出函数实现#ifdefCONFIG_DEVFS_FS/删除设备文件(节点文件)devfs_unregister(devfs_DbLedraw);devfs_unregister(devfs_DbLed_dir);#endifunregister_chrdev(DbLedMajor,DEVICE_NAME);,40,LED驱动程序设计,staticints3c2410_DbLed_open(structinode*inode,structfile*filp)/open函数实现MOD_INC_USE_COUNT;/维护递增计数器,2.6不再支持 DPRINTK(openn);return0;staticints3c2410_DbLed_release(structinode*inode,structfile*filp)/release函数实现 MOD_DEC_USE_COUNT;/维护递增计数器,2.6不再支持DPRINTK(releasen);return0;,41,LED驱动程序设计,staticssize_t s3c2410_DbLed_write(structfile*file,constchar*buffer,size_tcount,loff_t*ppos/write函数实现 copy_from_user(,42,11.4.5 键盘驱动程序开发实例键盘驱动工作原理键盘驱动程序设计,43,键盘可分为独立式按键和行列式键盘两种独立式按键:比较简单S3C2410与按键接口的硬件电路当按键抬起时,通过MCU的上拉电阻 提供逻辑1;当按键按下时,MCU 的输入被拉低,得到逻辑0由于机械触点动作比处理器速度慢很多,因此会产生抖动信号,硬件上可以加高通滤波等,但软件上去抖则比较省成本:在判断有键按下后,用软件延时的方法(例如延时20ms),再判断按键状态,如果仍为有键按下,则确认为有键按下,否则当作按键抖动处理,键盘驱动工作原理,44,行列式(矩阵式)键盘的结构特点行列式(矩阵式)键盘适用于按键数量较多的场合,它由行线和列线组成,按键位于行、列的交叉点上例如一个33的行、列结构可以构成一个有9个按键的键盘同理一个44的行、列结构可以构成一个含有16个按键的键盘很明显,在按键数量较多的场合,行列式键盘与独立式按键盘相比,要节省很多的I/O引脚,键盘驱动工作原理,45,键盘驱动工作原理,独立式按键的键盘驱动设计原理MCU S3C2410芯片的GPIO端口PortF可以配置为外部中断EINT0-7,并且可以设置外部中断控制寄存器EXTINT为外部中断设置信号触发方法(低/高电平、上/下降沿、双沿触发等)一个按键和MCU S3C2410芯片的一个外部中断引脚EXTINT4-7相连,当按键按下时,引脚输入低电平,触发中断(执行按键中断服务程序),按键中断服务程序将按键的值存放在缓冲区,以备应用程序读取应用程序调用文件系统系统调用文件系统系统调用file_ operations中行各函数指针指向再重定向到设备驱动程序的各个函数(上半部分中实现)上半部分中的设备驱动程序相关函数从缓冲区读取按键值,46,键盘驱动程序设计,键盘设备的数据结构typedef unsigned char KBD_RETtypedef struct unsigned int kbdStatus;/按键当前状态KDB_RET bufMAX_KBD_BUF;/按键缓冲区:数组实现的循环队列unsigned int head,tail;/按键缓冲区头和尾wait_queue_head_t wq;/等待队列 Why?spinlock_t lock;/锁KBD_DEV;static KBD_DEV kbddev;,存放按键的值,缓冲区里不一定已经存放有按键值,47,键盘驱动程序设计,键盘设备数据结构KBD_DEV主要包括三个部分键盘当前状态kbdStatus:3种状态:抬起、按下、连击#define KEYSTATUS_UP 0#define KEYSTATUS_DOWN 1#define KEYSTATUS_DOWNX 2 按键缓冲区buf及其head,tail:当应用程序繁忙来不及处理按键动作时,缓冲区会把按键值暂时保存下来。这里定义的是一个循环队列缓冲区(环形数组)buf,使用如下宏来管理#define BUF_HEAD(kbddev.bufkbddev.head)/循环队列头#define BUF_TAIL(kbddev.bufkbddev.tail)/循环队列尾#define INCBUF(x,mod)(+(x)&(mod)1)/移动循环队列指针,mod必须是2的n次幂等待队列wq:读取按键的用户进程位于等待队列中,等待按键缓冲区有了按键值以后可以去读取。这是最常用的中断处理方法:当按键可以被读取时,位于等待队列中的用户进程可被中断唤醒interruptible_sleep_on(&(kbddev.wq)阻塞当前进程并使之进入等待队列wake_up_interruptible(&(kbddev.wq)唤醒等待队列中的进程,48,键盘驱动程序设计,键盘驱动程序(关键部分)/定义设备名、主次设备号(主设备自动获取)#define DEVICE_NAME“s3c2410-kbd”#define KBDRAW_MINOR 1/定义设备的数据结构(前页),49,键盘驱动程序设计,/与设备中断相关的函数/宏,可供设备驱动程序各函数调用/打开中断源,set_external_irq把连接4个按键的I/O引脚(中断源)设置为外部低电平触发中断方式,并开启内部上拉电阻#define IRQ_KBD(n)IRQ_EINT#n/如IRQ_KBD(4)为IRQ_EINT4#define KBD_OPEN_INT()do set_external_irq(IRQ_KBD(4),EXT_LOWLEVEL,GPIO_PULLUP_EN);set_external_irq(IRQ_KBD(5),EXT_LOWLEVEL,GPIO_PULLUP_EN);set_external_irq(IRQ_KBD(6),EXT_LOWLEVEL,GPIO_PULLUP_EN);set_external_irq(IRQ_KBD(7),EXT_LOWLEVEL,GPIO_PULLUP_EN);while(0)/关闭中断源#define KBD_CLOSE_INT()do set_gpio_ctrl(GPIO_MODE_IN|GPIO_PULLUP_EN|GPIO_F4);set_gpio_ctrl(GPIO_MODE_IN|GPIO_PULLUP_EN|GPIO_F5);set_gpio_ctrl(GPIO_MODE_IN|GPIO_PULLUP_EN|GPIO_F6);set_gpio_ctrl(GPIO_MODE_IN|GPIO_PULLUP_EN|GPIO_F7);while(0),50,键盘驱动程序设计,/判断是否有按键按下#define ISKBD_DOWN()(GPFDAT&(0 xf 4)&0 xf/按4、5、6、7键分别返回0001、0010、0100、1000/1 1 0 1 1 0 1 0(GPFDAT中的数据)/0 0 1 0 0 1 0 1(GPFDAT)/0 0 0 0 0 0 1 0(GPFDAT)4),GPIO PortF的数据寄存器,GPFDAT寄存器的地址,51,键盘驱动程序设计,/设备驱动程序的各个函数声明(s3c2410_kbd_open、s3c2410_kbd_read等,略)static void s3c2410_isr_kbd(int irq,void*dev_id,struct pt_regs*regs);/中断服务函数/设置file_operation:通过file_operations结构定义设备所需文件操作:指向相应的设备操作函数(设备驱动程序的各个函数)staticstructfile_operations s3c2410_fops=owner:THIS_MODULE,open:s3c2410_kbd_open,write:s3c2410_kbd_read,release:s3c2410_kbd_release;/初始化和退出函数的模块实现module_init(s3c2410_kbd_init);module_exit(s3c2410_kbd_exit);,52,键盘驱动程序设计,初始化函数 s3c2410_kbd_init()实现/打开中断源KBD_OPEN_INT()/初始化键盘设备数据结构,这块也可以放在open函数中kbddev.head=kbddev.tail=0;kbddev.kbdStatus=KEYSTATUS_UP;init_waitqueue_head(/函数返回值就是系统自动分配的主设备号,53,键盘驱动程序设计,/申请中断for(i=4;i8;i+)ret=request_irq(IRQ_KBD(i),s3c2410_isr_kbd,SA_INTERRUPT,DEVICE_NAME,(void*)i);if(ret)return ret;/创建设备文件系统节点(设备文件)#ifdefCONFIG_DEVFS_FSdevfs_kbd_dir=devfs_mk_dir(NULL,“keyboard,NULL);devfs_kbdraw=devfs_register(devfs_kbd_dir,0raw,DEVFS_FL_DEFAULT,kbdMajor,KBDRAW_MINOR,S_IFCHR|S_IRUSR|S_IWUSR,#endif,54,键盘驱动程序设计,退出函数 s3c2410_kbd_exit()实现/删除设备文件#ifdefCONFIG_DEVFS_FSdev_unregister(devfs_kbd_dir);dev_unregister(devfs_kbdraw);#endif/卸载设备unregister_chrdev(kbdMajor,DEVICE_NAME);/释放中断for(i=4;i8;i+)free_irq(IRQ_KBD(i),s3c2410_isr_kbd);/关闭中断源KBD_CLOSE_INT();,55,键盘驱动程序设计,s3c2410_kbd_open/release函数实现static int s3c2410_kbd_open(struct inode*inode,struct file*filp)MOD_INC_USE_COUNT;/Linux2.6不再需要memset(kbd_dev.buf,0,MAX_KEY_BUF);return 0;static int s3c2410_kbd_release(struct inode*inode,struct file*filp)MOD_DEC_USE_COUNT;/Linux2.6不再需要return 0;,56,键盘驱动程序设计,s3c2410_kbd_read()函数实现static ssize_t s3c2410_kbd_read(struct file*filp,char*buffer,size_t count,loff_t*ppos)KBD_RET kbd_retMAX_KBD_BUF;int xi;retry:if(kbddev.head!=kbddev.tail)/循环队列缓冲区中有数据for(xi=0;kbddev.head!=kbddev.tail;xi+)key_retxi=BUF_HEAD;/获取队头数据BUF_HEAD=0;/队头数据清零kbddev.head=INCBUF(kbddev.head,MAX_KBD_BUF);/移动循环队列队头指针copy_to_user(buffer,57,键盘驱动程序的中断服务函数3c2410_isr_kbd()实现static void s3c2410_isr_kbd(int irq,void*dev_id,struct pt_regs*regs)/you can insert your code herewhile(ISKBD_DOWN()if(ISKBD_DOWN()BUF_TAIL=Read_ExINT_Key();/读按键值添加到队尾kbddev.tail=INCBUF(kbddev.tail,MAX_KBD_BUF);/移动循环队列队尾指针wake_up_interruptible(,键盘驱动程序设计,