1、Linux设备驱动设备驱动广州嵌入式软件公共技术支持中心梁老师2007年07月设备驱动概述设备驱动概述l操作系统是通过各种驱动程序来驾驭硬件设备,它为操作系统是通过各种驱动程序来驾驭硬件设备,它为用户屏蔽了各种各样的设备用户屏蔽了各种各样的设备,硬件设备的抽象。硬件设备的抽象。l设备驱动程序设备驱动程序:处理和管理硬件控制器的软件。处理和管理硬件控制器的软件。l设备驱动程序是操作系统内核和机器硬件之间的接口。设备驱动程序是操作系统内核和机器硬件之间的接口。设备驱动概述设备驱动概述l设备由两部分组成,一个是被称为控制器的电器部分,设备由两部分组成,一个是被称为控制器的电器部分,另一个是机械部分。
2、另一个是机械部分。l一组寄存器组被赋予到各个控制器。一组寄存器组被赋予到各个控制器。I/O端口包含端口包含4组寄组寄存器,即状态寄存器,控制寄存器,数据输入寄存器,存器,即状态寄存器,控制寄存器,数据输入寄存器,数据输出寄存器。数据输出寄存器。状态寄存器拥有可以被状态寄存器拥有可以被CPU读取的读取的(状态状态)位,用来位,用来 指示当前命令是否执行完毕,或者字节是否可以被指示当前命令是否执行完毕,或者字节是否可以被读出或写入,以及任何错误提示。读出或写入,以及任何错误提示。控制寄存器则用于启动一条命令(指令)或者改变控制寄存器则用于启动一条命令(指令)或者改变设备的设备的(工作工作)模式。模
3、式。数据输入寄存器用于获取输入的数据。数据输入寄存器用于获取输入的数据。数据输出寄存器则向数据输出寄存器则向CPU发送结果。发送结果。l处理器和设备之间的基本界面是控制和状态寄存器。处理器和设备之间的基本界面是控制和状态寄存器。设备驱动概述设备驱动概述l寄存器拥有在寄存器拥有在I/O空间明确定义的地址范围。空间明确定义的地址范围。l通常这些地址在启动时被分配。通常这些地址在启动时被分配。如果设备是静态加载的如果设备是静态加载的,各个设备的地址范围可能被预各个设备的地址范围可能被预分配。这意味内核包含了已存在设备的驱动分配。这意味内核包含了已存在设备的驱动 程序。通程序。通过运行过运行“cat/
4、proc/ioports”命令检查其所使用的地命令检查其所使用的地址范围。第一列输出显示了端口的范围而第二列则是址范围。第一列输出显示了端口的范围而第二列则是拥用这些端口的设备。拥用这些端口的设备。设备驱动概述设备驱动概述l设备驱动的概念是非常抽象的并且处于一台计算设备驱动的概念是非常抽象的并且处于一台计算上所运行软件的最低层。上所运行软件的最低层。l由于直接到设备的硬件特性的限由于直接到设备的硬件特性的限 制。每个设备驱制。每个设备驱动都只管理一种单一类型的设备。动都只管理一种单一类型的设备。l如果一个应用如果一个应用 程序向设备提出(操作)要求。内程序向设备提出(操作)要求。内核会联系到对
5、应的设备驱动,设备驱动接着向特核会联系到对应的设备驱动,设备驱动接着向特定的设备发出命令。定的设备发出命令。l设备驱设备驱 动是一个函数集合:包含了许多调用入口,动是一个函数集合:包含了许多调用入口,类似于类似于open,close,read,write,ioctl,llseek 等。等。设备驱动概述设备驱动概述lLinuxLinux操作系统把设备操作系统把设备纳入文件系统的范畴纳入文件系统的范畴来管理。来管理。l文件操作是对设备操作的组织和抽象。设备操作则是文件操作是对设备操作的组织和抽象。设备操作则是对文件操作的最终实现。对文件操作的最终实现。l每个设备都对应一个文件名每个设备都对应一个文
6、件名,在内核中也就对应一个,在内核中也就对应一个索引节点。索引节点。l对文件操作的系统调用大都适用于设备文件。对文件操作的系统调用大都适用于设备文件。l从应用程序的角度看,设备文件逻辑上的空间是一个从应用程序的角度看,设备文件逻辑上的空间是一个线性空间(起始地址为线性空间(起始地址为0 0,每读取一个字节加,每读取一个字节加1 1)。从)。从这个逻辑空间到具体设备物理空间(如磁盘的磁道、这个逻辑空间到具体设备物理空间(如磁盘的磁道、扇区)的映射则是由内核提供,并被划分为扇区)的映射则是由内核提供,并被划分为文件操作文件操作和和设备驱动设备驱动两个层次。两个层次。设备驱动概述设备驱动概述lLin
7、uxLinux将设备分成两大类。将设备分成两大类。一类像键盘那样以字符(字节)为单位,逐个字符一类像键盘那样以字符(字节)为单位,逐个字符进行输入输出的设备,称为进行输入输出的设备,称为字符设备字符设备。一类是像磁盘那样以块或扇区为单位,成块进行输一类是像磁盘那样以块或扇区为单位,成块进行输入输出的设备,称为入输出的设备,称为块设备块设备。文件系统通常都建立在块设备上。文件系统通常都建立在块设备上。设备驱动概述设备驱动概述l文件操作和设备驱动是对一个具体的设备操作的不同层文件操作和设备驱动是对一个具体的设备操作的不同层次。从这种观点出发,从概念上可以把一个系统划分为次。从这种观点出发,从概念上
8、可以把一个系统划分为应用、文件系统和设备驱动三个层次。应用、文件系统和设备驱动三个层次。将请求加入请求队列 请求提交操作readsys_readfile-f_op-readdo_generic_file_read用户空间函数内核系统调用文件系统读操作通用文件系统读操作readsys_readfile-f_op-readdo_generic_file_read用户空间函数内核系统调用文件系统读操作submit_bhsubmit_bhadd_requstadd_requst设备驱动概述设备驱动概述设备驱动概述设备驱动概述l要使一项设备可以被应用程序访问,首先要在系统中要使一项设备可以被应用程序访问
9、,首先要在系统中建立一个代表此设备的设备文件,这是通过系统调用建立一个代表此设备的设备文件,这是通过系统调用mknode()mknode()实现的。此外,更重要的是在设备驱动层要实现的。此外,更重要的是在设备驱动层要有这种设备的驱动程序。有这种设备的驱动程序。设备驱动概述设备驱动概述l设备文件:设备文件:任何设备都被当作路径任何设备都被当作路径/dev 的设备文件处理,并通的设备文件处理,并通过这些设备文件提供访问硬件的方法。过这些设备文件提供访问硬件的方法。每个设备文件除了设备名外,还有类型、主设备号、每个设备文件除了设备名外,还有类型、主设备号、次设备号这三个属性。次设备号这三个属性。设备
10、文件是通过设备文件是通过mknodmknod系统调用创建的。其原型为:系统调用创建的。其原型为:mknod(const char mknod(const char*filename,int mode,dev_t filename,int mode,dev_t dev)dev)mknod/dev/led0 c 253 0 mknod/dev/led0 c 253 0 设备驱动概述设备驱动概述l主设备号和次设备号:主设备号和次设备号:主设备号标识设备对应的驱动程序。一般主设备号标识设备对应的驱动程序。一般“一个主一个主设备号对应一个驱动程序设备号对应一个驱动程序”次设备号用于确定设备文件所指的设备
11、。次设备号用于确定设备文件所指的设备。可通过可通过ls l“设备文件名设备文件名”命令查看设备的主次设命令查看设备的主次设备号,以及设备的类型。备号,以及设备的类型。设备驱动概述设备驱动概述l主设备号和次设备号的内部表达:主设备号和次设备号的内部表达:Dev_t类型用于保存设备号,称为设备编号。类型用于保存设备号,称为设备编号。/linux/types.h文件中定义。文件中定义。目前设备编号目前设备编号dev_t是一个是一个32位的整数,其中位的整数,其中12位位表示主设备号,表示主设备号,20位表示次设备号。位表示次设备号。通过设备编号获取主次设备号:通过设备编号获取主次设备号:MAJOR(
12、dev_t dev);MINOR(dev_t dev);通过主次设备号合成设备编号:通过主次设备号合成设备编号:MKDEV(int major,int minor);Dev_t格式以后可能会发生变化,但只要使用这些格式以后可能会发生变化,但只要使用这些宏,就可保证设备驱动程序的正确性。宏,就可保证设备驱动程序的正确性。一些重要的数据结构一些重要的数据结构l大部分驱动程序涉及三个重要的内核数据结构:大部分驱动程序涉及三个重要的内核数据结构:文件操作文件操作file_operations结构体结构体文件对象文件对象file结构体结构体索引节点索引节点inode结构体结构体一些重要的数据结构一些重要
13、的数据结构l文件操作结构体文件操作结构体file_operations结构体结构体file_operations在头文件在头文件 linux/fs.h中定义,中定义,用来存储驱动内核模块提供的用来存储驱动内核模块提供的对设备进行各种操作对设备进行各种操作的函数的指针的函数的指针。结构体的每个域都对应着驱动模块用来处理某个被结构体的每个域都对应着驱动模块用来处理某个被请求的事务的函数的地址。请求的事务的函数的地址。struct file_operations struct module*owner;loff_t(*llseek)(struct file*,loff_t,int);ssize_t(
14、*read)(struct file*,char _user*,size_t,loff_t*);ssize_t(*write)(struct file*,const char _user*,size_t,loff_t*);。一些重要的数据结构一些重要的数据结构lfile_operations重要的成员重要的成员Struct module*owner,指向拥有该结构体的模,指向拥有该结构体的模块的指针。块的指针。方法方法llseek用来修改文件的当前读写位置,把新位用来修改文件的当前读写位置,把新位置作为返回值返回。置作为返回值返回。方法方法read用来从设备中读取数据。非负返回值表示用来从设备
15、中读取数据。非负返回值表示成功读取的直接数。成功读取的直接数。方法方法write向设备发送数据。向设备发送数据。方法方法ioctl提供一种执行设备特定命令的方法。提供一种执行设备特定命令的方法。一些重要的数据结构一些重要的数据结构lfile_operations重要的成员重要的成员驱动内核模块是不需要实现每个函数的。相对应的驱动内核模块是不需要实现每个函数的。相对应的file_operations的项就为的项就为 NULL。Gcc的语法扩展,使得可以定义该结构体:的语法扩展,使得可以定义该结构体:struct file_operations fops=read:device_read,writ
16、e:device_write,open:device_open,release:device_release;这种语法清晰,没有显示声明的结构体成员都被这种语法清晰,没有显示声明的结构体成员都被gcc初始化为初始化为NULL。一些重要的数据结构一些重要的数据结构lfile_operations重要的成员重要的成员标准标准C的标记化结构体的初始化方法:的标记化结构体的初始化方法:struct file_operations fops=.read=device_read,.write=device_write,.open=device_open,.release=device_release;推荐
17、使用该方法,提高移植性,方法允许对结构体成员进行重推荐使用该方法,提高移植性,方法允许对结构体成员进行重新排列。新排列。没有显示声明的结构体成员同样都被没有显示声明的结构体成员同样都被gcc初始化为初始化为NULL。指向结构体指向结构体file_operations的指针通常命名为的指针通常命名为fops。一些重要的数据结构一些重要的数据结构l文件对象文件对象file结构体结构体文件对象文件对象file代表着一个打开的文件代表着一个打开的文件。进程通过文进程通过文件描述符件描述符fdfd与已打开文件的与已打开文件的filefile结构相联系。进程结构相联系。进程通过它对文件的线性逻辑空间进行操
18、作。例如:通过它对文件的线性逻辑空间进行操作。例如:file-f_op-read();file-f_op-read();Struct file Struct file 在在中定义。中定义。指向结构体指向结构体struct file的指针通常命名为的指针通常命名为filp,或者,或者file。建议使用文件指针。建议使用文件指针filp。一些重要的数据结构一些重要的数据结构l文件对象文件对象file结构体的成员结构体的成员Struct file_operations*f_op;与文件相关的操作结构体指针。与文件相关的操作与文件相关的操作结构体指针。与文件相关的操作是在打开文件的时候确定下来的,也就
19、是确定该指是在打开文件的时候确定下来的,也就是确定该指针的值。可在需要的时候,改变指针所指向的文件针的值。可在需要的时候,改变指针所指向的文件操作结构体。用操作结构体。用C语言实现面向对象编程的方法重语言实现面向对象编程的方法重载。载。其他成员可先忽略,后面具体实例分析。因为设备其他成员可先忽略,后面具体实例分析。因为设备驱动模块并不自己直接填充结构体驱动模块并不自己直接填充结构体 file,只是使用,只是使用file中的数据。中的数据。一些重要的数据结构一些重要的数据结构l索引节点索引节点inode结构结构文件打开,在内存建立副本后,由文件打开,在内存建立副本后,由唯一的索引节点唯一的索引节
20、点inode描述。描述。与与file结构不同。结构不同。file结构是进程使用的结构,进程每打开一个文结构是进程使用的结构,进程每打开一个文件,就建立一个件,就建立一个file结构。不同的进程打开同一结构。不同的进程打开同一个文件,建立不同的个文件,建立不同的file结构。结构。Inode结构是内核使用的结构,文件在内存建立结构是内核使用的结构,文件在内存建立副本,就建立一个副本,就建立一个inode结构来描述。结构来描述。一个文件一个文件在内存里面只有一个在内存里面只有一个inode结构对应。结构对应。一些重要的数据结构一些重要的数据结构l索引节点索引节点inode结构结构Inode结构包含
21、大量描述文件信息的成员变量。结构包含大量描述文件信息的成员变量。但是对于描述设备文件的但是对于描述设备文件的inode,跟设备驱动有关,跟设备驱动有关的成员只有两个。的成员只有两个。Dev_t i_rdev;包含真正的设备编号。包含真正的设备编号。Struct cdev*i_cdev;指向指向cdev结构体的指针。结构体的指针。cdev是表示字符设备的内核数据结构。是表示字符设备的内核数据结构。从从inode中获得主设备号和次设备号的宏:中获得主设备号和次设备号的宏:Unsigned int iminor(struct inode*inode);Unsigned int imajor(stru
22、ct inode*inode);驱动程序中的内存分配驱动程序中的内存分配l在在Linux内核模式下,不能使用用户态的内核模式下,不能使用用户态的malloc()和和free()函数申请和释放内存。函数申请和释放内存。l内核编程最常用的内存申请和释放函数为内核编程最常用的内存申请和释放函数为kmalloc()和和kfree(),其原型为:,其原型为:include/linux/kernel.hvoid void*kmalloc(unsigned int len,int priority);kmalloc(unsigned int len,int priority);void kfree(void
23、 void kfree(void*_ptr);_ptr);priority参数参数:通常设置为通常设置为GFP_KERNEL,可能会引起睡眠,可能会引起睡眠.如果在中断服务程序里申请内存则要用如果在中断服务程序里申请内存则要用GFP_ATOMIC参数,参数,在中断中是不允许睡眠的。在中断中是不允许睡眠的。初始化和卸载函数初始化和卸载函数l驱动程序是内核的一部分,因此我们需要给其添驱动程序是内核的一部分,因此我们需要给其添加模块初始化函数,该函数用来完成对所控设备加模块初始化函数,该函数用来完成对所控设备的初始化工作,并调用的初始化工作,并调用register_chrdev()函数函数注册字符设
24、备注册字符设备.int register_chrdev(unsigned int major,const char*name,struct file_operations*fops);major 是给定的主设备号。为0代表什么?name 是驱动的名字(将出现在/proc/devices),fops 是设备驱动的file_operations 结构。register_chrdev 将给设备分配 0-255 的次设备号,并且为每一个建立一个缺省的 cdev 结构。l与模块初始化函数对应的就是模块卸载函数,需与模块初始化函数对应的就是模块卸载函数,需要调用要调用register_chrdev()的的
25、反函数反函数设备操作函数集的定义设备操作函数集的定义lfile_operations结构体,驱动程序只是利用其中结构体,驱动程序只是利用其中的一部分。的一部分。l对于字符设备来说,要提供的主要入口有:对于字符设备来说,要提供的主要入口有:open()、release()、read()、write()、ioctl()等。等。设备操作函数集的定义设备操作函数集的定义lopen()函数函数对设备特殊文件进行对设备特殊文件进行open()系统调用时,将调用驱动系统调用时,将调用驱动程序的程序的open()函数:函数:int(int(*open)(struct inode open)(struct in
26、ode*,struct file ,struct file*););参数参数inode为设备特殊文件的为设备特殊文件的inode(索引结点索引结点)结结构的指针,构的指针,参数参数file是指向这一设备的文件结构的指针。是指向这一设备的文件结构的指针。open()的主要任务是确定硬件处在就绪状态、验证次的主要任务是确定硬件处在就绪状态、验证次设备号的合法性设备号的合法性(次设备号可以用次设备号可以用MINOR(inode-i-rdev)取得取得)、控制使用设备的进程数、根据执行情况、控制使用设备的进程数、根据执行情况返回状态码返回状态码(0表示成功,负数表示存在错误表示成功,负数表示存在错误)
27、等;等;设备操作函数集的定义设备操作函数集的定义lrelease()函数函数当最后一个打开设备的用户进程执行当最后一个打开设备的用户进程执行close()系统调系统调用时,内核将调用驱动程序的用时,内核将调用驱动程序的release()函数:函数:void(void(*release)(struct inode release)(struct inode*,struct file ,struct file*););release 函数的主要任务是清理未结束的输入函数的主要任务是清理未结束的输入/输出操输出操作、释放资源、用户自定义排他标志的复位等作、释放资源、用户自定义排他标志的复位等.设备操
28、作函数集的定义设备操作函数集的定义lread()函数函数lRead的任务的任务,就是从设备拷贝数据到用户空间。就是从设备拷贝数据到用户空间。当对设备特殊文件进行当对设备特殊文件进行read()系统调用时,将调用驱系统调用时,将调用驱动程序动程序read()函数:函数:ssize_t read(struct file*filp,char _user*buff,size_t count,loff_t*offp);filp 是文件对象指针,count 是请求的传输数据大小.buff 参数对write来说是指向持有被写入数据的缓存,对read则是放入新数据的空缓存.offp 是指向一个“long of
29、fset type”的指针,它指出用户正在存取的文件位置.返回值是“signed size type”类型;设备操作函数集的定义设备操作函数集的定义lwrite()函数函数Write的任务,则从用户空间拷贝数据到设备。的任务,则从用户空间拷贝数据到设备。当设备特殊文件进行当设备特殊文件进行write()系统调用时,将调用驱系统调用时,将调用驱动程序的动程序的write()函数:函数:ssize_t write(struct file*filp,const char _user *buff,size_t count,loff_t*offp);filp 是文件对象指针,count 是请求的传输数据
30、大小.buff 参数对write来说是指向持有被写入数据的缓存,对read则是放入新数据的空缓存.offp 是指向一个“long offset type”的指针,它指出用户正在存取的文件位置.返回值是“signed size type”类型;设备操作函数集的定义设备操作函数集的定义lread 和和 write 方法的方法的 buff 参数是用户空间指针,参数是用户空间指针,不能被内核代码直接解引用。不能被内核代码直接解引用。_user字符串只是字符串只是形式上的说明,表明是用户空间地址。形式上的说明,表明是用户空间地址。l驱动必须能够存取用户空间缓存以完成它的工作。驱动必须能够存取用户空间缓存
31、以完成它的工作。内核如何解决这个问题?内核如何解决这个问题?为安全起见,内核提供专用的函数来完成对用户空间为安全起见,内核提供专用的函数来完成对用户空间的存取。这些专用函数在的存取。这些专用函数在中声明。unsigned long copy_to_user(void _user*to,const void*from,unsigned long count);unsigned long copy_from_user(void*to,const void _user*from,unsigned long count);大多数读写函数都会调用这两个函数,用于跟应用程序空间交流大多数读写函数都会调用这
32、两个函数,用于跟应用程序空间交流信息。信息。Read和和Write方法方法l典型的典型的Read函数对参数的使用。函数对参数的使用。S3C2410 的I/O 介绍lS3C2410 有117 个复用功能输入输出端口引脚,这些引脚是:lPortA(GPA):32 个输入/输出端口lPortB(GPB):11 个输入/输出端口lPortC(GPC):16 个输入/输出端口lPortD(GPD):16 个输入/输出端口lPortE(GPE):16 个输入/输出端口lPortF(GPF):8 个输入/输出端口lPortG(GPG):16 个输入/输出端口lPortH(GPH):11 个输入/输出端口S3
33、C2410 的I/O 介绍l端口控制说明l端口配置寄存器(GPACONGPHCON)大部分的引脚是复用的,所以必须对于每个引脚要求定义一个功能,端口配置寄存器定义了每个引脚的功能。l端口数据寄存器(GPADATGPHDAT)如果端口配置成输出端口,数据能够被写到端口数据寄存器的对应位,然后通过管脚输出。如果端口配置成输入端口,能从端口数据寄存器对应的位中读出管脚上的电平l端口上拉寄存器(GPBUPGPHUP)端口上拉寄存器控制着每个端口组的上拉寄存器的使能或禁止,当对应位为0,这个引脚的上拉寄存器是允许的,当为1 时,上拉寄存器是禁止的。MIZI提供的提供的S3C2410.Hl使用一个使用一个
34、32位的数来表示端口的使用情况。位的数来表示端口的使用情况。l模式模式|上拉上拉|端口端口|端口引脚端口引脚lMODE|PULLUP|PORT|OFSl不需要自己手动组合,通过宏定义以及不需要自己手动组合,通过宏定义以及SHIFT和和MASK组合。见程序组合。见程序MIZI提供的提供的S3C2410.Hl端口的表示端口的表示l#define PORTA_OFS0l#define PORTB_OFS1l#define PORTC_OFS2l#define PORTD_OFS3l#define PORTE_OFS4l#define PORTF_OFS5l#define PORTG_OFS6l#de
35、fine PORTH_OFS7MIZI提供的提供的S3C2410.Hl端口引脚的表示端口引脚的表示l#define GPIO_A0MAKE_GPIO_NUM(PORTA_OFS,0)l#define GPIO_A1MAKE_GPIO_NUM(PORTA_OFS,1)l#define GPIO_A2MAKE_GPIO_NUM(PORTA_OFS,2)l#define GPIO_A3MAKE_GPIO_NUM(PORTA_OFS,3)l。l#define MAKE_GPIO_NUM(p,o)(p GPIO_PORT_SHIFTT)|(o GPIO_OFS_SHIFT)MIZI提供的提供的S3C24
36、10.Hlset_gpio_ctrl(x)l功能:配置端口引脚的功能,设置功能:配置端口引脚的功能,设置IO口控制寄存口控制寄存器和上拉寄存器器和上拉寄存器l用法:用法:set_gpio_ctrl(模式模式|上拉上拉?|IO脚脚)l模式模式|是否上拉是否上拉|IO脚脚,在,在S3C2410.h中都有其定义中都有其定义好的名字。好的名字。l set_gpio_ctrl(GPIO_E11|GPIO_PULLUP_DIS|GPIO_MODE_OUT);MIZI提供的提供的S3C2410.Hlwrite_gpio_bit(x,v)l功能:把端口功能:把端口 对应的端口数据寄存器对应的端口数据寄存器x位
37、设置为位设置为vlwrite_gpio_bit(GPIO_E11,0);lread_gpio_bit(x)l功能:把端口数据寄存器功能:把端口数据寄存器x位的状态位的状态 读入,函数读入,函数返回值既是其状态返回值既是其状态lread_gpio_bit(GPIO_G11);MIZI提供的提供的S3C2410.Hlwrite_gpio_reg(x,v)l功能:把端口数据寄存器功能:把端口数据寄存器x 设置为设置为v lread_gpio_reg(x)l功能:读取端口数据寄存器功能:读取端口数据寄存器x,函数返回值既是,函数返回值既是其数据其数据 l按驱动的框架写好驱动,实现初始化、卸载函数,按驱
38、动的框架写好驱动,实现初始化、卸载函数,以及以及file_opertation操作集。操作集。l对于对于led使用的使用的gpio函数,内核没有输出作为公函数,内核没有输出作为公开符号,所以需要手动修改内核代码,代码放在开符号,所以需要手动修改内核代码,代码放在arch/arm/march-s3c2410/gpio.c,输出符号:,输出符号:EXPORT_SYMBOL(s3c2410_gpio_setpin);EXPORT_SYMBOL(s3c2410_gpio_cfgpin);EXPORT_SYMBOL(s3c2410_gpio_pullup);l编写驱动模块的编写驱动模块的Makefile
39、。l交叉编译驱动模块交叉编译驱动模块lNFS加载驱动模块,进行测试。加载驱动模块,进行测试。嵌入式键盘驱动广州嵌入式软件公共技术支持中心广州嵌入式软件公共技术支持中心梁老师2007年7月注册中断服务例程注册中断服务例程 l中断线是一个宝贵且常常有限的资源中断线是一个宝贵且常常有限的资源,特别当它特别当它们只有们只有1515个时。内核维护一个中断线的注册表。个时。内核维护一个中断线的注册表。l要使用中断线,就要进行中断线的申请,也就是要使用中断线,就要进行中断线的申请,也就是IRQ(Interrupt ReQuirement)IRQ(Interrupt ReQuirement),因此我们也常把,
40、因此我们也常把申请一条中断线称为申请一个申请一条中断线称为申请一个IRQIRQ或者是申请一或者是申请一个中断号。个中断号。lIRQIRQ线是从线是从0 0开始顺序编号的;第一条开始顺序编号的;第一条IRQIRQ线通常线通常表示成表示成IRQ0IRQ0。IRQnIRQn的缺省向量是的缺省向量是n+32n+32。注册中断服务例程注册中断服务例程 l并不是每个设备都可以向中断线上发中断信号的,并不是每个设备都可以向中断线上发中断信号的,只有对某一条确定的中断线拥有了控制权,才可只有对某一条确定的中断线拥有了控制权,才可以向这条中断线上发送信号。以向这条中断线上发送信号。l计算机的外部设备越来越多,所
41、以计算机的外部设备越来越多,所以15条中断线已条中断线已经不够用了,中断线是非常宝贵的资源。经不够用了,中断线是非常宝贵的资源。l只有当设备需要中断的时候才申请占用一个只有当设备需要中断的时候才申请占用一个IRQ,或者是在申请或者是在申请IRQ时采用共享中断的方式,这样时采用共享中断的方式,这样可以让更多的设备使用中断。可以让更多的设备使用中断。注册中断服务例程注册中断服务例程 l在在 实现中断注册接口实现中断注册接口:int request_irq(unsigned int irq,irqreturn_t(*handler)(int,void*,struct pt_regs*),unsign
42、ed long flags,const char*dev_name,void*dev_id);void free_irq(unsigned int irq,void*dev_id);lrequest_irq 的返回值是的返回值是 0 指示申请成功,为负指示申请成功,为负值时表示错误码。函数返回值时表示错误码。函数返回-EBUSY 表示已经有表示已经有另一个驱动占用了所要申请的中断线。另一个驱动占用了所要申请的中断线。注册中断服务例程注册中断服务例程 lrequest_irq的参数说明:的参数说明:unsigned int irq,要申请的中断号。要申请的中断号。对某些设备,如传统对某些设备,如
43、传统PC设备上的系统时钟或键盘,设备上的系统时钟或键盘,这个值通常是预先确定的。这个值通常是预先确定的。而对于大多数其它设备来说,这个值要么可以通过而对于大多数其它设备来说,这个值要么可以通过探测获取,要么可以动态确定。探测获取,要么可以动态确定。irqreturn_t(*handler)(int,void*,struct pt_regs*),要安装的中断处理函数指针。后面介绍。注册中断服务例程注册中断服务例程 lrequest_irq的参数说明:的参数说明:unsigned long flags,与中断管理相关的位掩码选项。const char*dev_name,用在/proc/interr
44、upts 中显示中断的拥有者。void*dev_id这个指针用于共享的中断线。做为驱动程序的私有数据区(可用来识别那个设备产生的中断)。不使用共享中断线方式时,可设置为NULL。注册中断服务例程注册中断服务例程 lflags参数的详细说明:参数的详细说明:Flags的每个位有不同含义SA_INTERRUPT当该位被设置时,表示这是一个“快速”中断。快速中断处理例程运行时,屏蔽中断。SA_SHIRQ这个位表示中断可以在设备间共享。proc 文件系统中的中断信息文件系统中的中断信息l/proc/interrupts反映系统的中断信息反映系统的中断信息第一列是 IRQ 号给出每个中断线发生中断的次数
45、。给出每个中断线发生中断的次数。给出处理中断的可编程中断控制器。给出处理中断的可编程中断控制器。给出在该中断号上注册中断处理例程的设备名称。给出在该中断号上注册中断处理例程的设备名称。实现中断处理例程实现中断处理例程l首先中断处理例程也是普通的首先中断处理例程也是普通的C程序。程序。l特别之处:特别之处:在中断时间内运行,不能向用户空间发送或者接收数在中断时间内运行,不能向用户空间发送或者接收数据。据。不能做任何导致休眠的操作。不能做任何导致休眠的操作。不能调用不能调用schedule函数。函数。无论快速还是慢速中断处理例程,都应该设计成执行无论快速还是慢速中断处理例程,都应该设计成执行时间尽
46、可能短。时间尽可能短。实现中断处理例程实现中断处理例程l中断处理函数的参数和返回值中断处理函数的参数和返回值irqreturn_t(*handler)(int,void*,struct pt_regs*)irqreturn_t short_interrupt(int irq,void*dev_id,struct pt_regs*regs)Irq 中断号中断号Dev_id 驱动程序可用的数据区,通常可传递指向描述设备的数驱动程序可用的数据区,通常可传递指向描述设备的数据结构指针。据结构指针。struct pt_regs*regs,保存了处理器进入中断代码之前的,保存了处理器进入中断代码之前的cp
47、u寄存器的值。一般驱动可不要。寄存器的值。一般驱动可不要。矩阵式键盘原理矩阵式键盘原理l矩阵式键盘一般适用于按键数量较多的场合,它由行线矩阵式键盘一般适用于按键数量较多的场合,它由行线和列线组成,按键位于行、列的交叉点上。和列线组成,按键位于行、列的交叉点上。l如图所示,一个如图所示,一个44的行、列结构可以构成一个有的行、列结构可以构成一个有16个个按键的键盘。按键的键盘。矩阵式键盘原理矩阵式键盘原理l按键设置在行、列交叉点上,行、列分别连接到按键设置在行、列交叉点上,行、列分别连接到按键开关的两端。行线通过上拉电阻接到十按键开关的两端。行线通过上拉电阻接到十5 V上。上。l平时无按键动作时
48、,行线处于高电平状态平时无按键动作时,行线处于高电平状态;而当有而当有健按下时,行线电平状态将由通过此按键的列线健按下时,行线电平状态将由通过此按键的列线电平决定电平决定:列线电平如果为低,行线电平为低列线电平如果为低,行线电平为低;列列线电平如果为高,则行线电平亦为高。这一点是线电平如果为高,则行线电平亦为高。这一点是识别矩阵式键盘是否被按下的关键所在。识别矩阵式键盘是否被按下的关键所在。矩阵式键盘原理矩阵式键盘原理l矩阵键盘按键的识别方法分两步进行矩阵键盘按键的识别方法分两步进行:l识别键盘哪一行的键被按下。让所有列线均为识别键盘哪一行的键被按下。让所有列线均为低电平,检查各行线电平是否为
49、低。如果有行线低电平,检查各行线电平是否为低。如果有行线为低,则说明该行有键被按下,否则说明无键被为低,则说明该行有键被按下,否则说明无键被按下。按下。l如果某行有键被按下,识别键盘哪一列的键被如果某行有键被按下,识别键盘哪一列的键被按下按下(亦称之为扫描法亦称之为扫描法)。逐列置低电平,并置其。逐列置低电平,并置其余各列为高电平余各列为高电平.检查各行线电平的变化。如果行检查各行线电平的变化。如果行电平变为低电平,则可确定此行此列交叉点处按电平变为低电平,则可确定此行此列交叉点处按键被按下。键被按下。键盘的硬件实现键盘的硬件实现l4X4 矩阵键盘矩阵键盘l四个输入引脚:四个输入引脚:lEIN
50、T0-(GPF0 )-INPUTl EINT2-(GPF2 )-INPUTl EINT11-(GPG3 )-INPUTl EINT19-(GPG11)-INPUT l四个输出引脚:四个输出引脚:lKEYSCAN0-(GPE11)-OUTPUTl KEYSCAN1-(GPG6 )-OUTPUTl KEYSCAN2-(GPE13)-OUTPUTl KEYSCAN3-(GPG2 )-OUTPUT键盘的驱动实现l引入结构体引入结构体key_info对按键进行描述对按键进行描述lstatic struct key_info lint irq_no;/外部中断号外部中断号lunsigned int gpi
侵权处理QQ:3464097650--上传资料QQ:3464097650
【声明】本站为“文档C2C交易模式”,即用户上传的文档直接卖给(下载)用户,本站只是网络空间服务平台,本站所有原创文档下载所得归上传人所有,如您发现上传作品侵犯了您的版权,请立刻联系我们并提供证据,我们将在3个工作日内予以改正。