linux设备驱动(精选10篇)
linux设备驱动 篇1
1 USB总线原理
USB协议是1994年底由康柏、IBM、英特尔等几家公司联合提出来的外部总线接口协议。USB就是英文中Universal Serial Bus (通用串行总线) 的缩写。USB总线具有其他总线所不具备的如:热插拔、数据传输可靠、扩展方便、成本低等一系列特点, 因此在嵌入式系统中被广泛使用。
一个USB系统一般是由一个USB主机控制器、一个或多个USB集线器和一个或多个USB设备节点组成。USB系统的物理连接具有层次性。USB总线连接USB设备和USB主机, 是一种星型拓扑结构。USB的拓扑结构如图1所示。
在一个USB系统传输数据的过程中有两个非常重要的概念, 就是USB传输模式和USB描述符。USB传输模式是指USB设备传输数据的形式。USB设备支持四种传输模式:控制传输模式、同步传输模式、中断传输模式和批量传输模式。控制传输模式是用来处理USB主端口到USB从端口的数据传输, 主要是设备控制指令、设备查询状态指令和确认指令。同步传输模式是指传输和时间关系密切的信息所使用的一种传输方式, 是一种周期的、连续的单向传输方式。中断传输模式这类传输模式主要用于传输非周期性的、自然发生的、数据量很小的信息, 这类数据传输的方向是从设备到主机, 适用于键盘、鼠标、操纵杆等设备上。最后一种是批量传输模式, 该模式适用于大量的、对时间没有要求的数据传输, 如U盘或者移动硬盘等设备。
USB设备在逻辑上分为几个层次, 分别是设备层 (Device) 、配置层 (Config) 、接口层 (Interface) 、端点层 (Endpoint) 。各个层次都有与之相对的描述符, 分别是设备描述符、配置描述符、接口描述符和端点描述符。
2 Linux下的USB驱动框架
USB设备的设备描述符在Linux系统中用usb_device_descriptor结构体表示, 它描述了USB设备的一般信息。配置描述符用usb_config_descriptor结构体表示, 它给出了USB设备的配置信息。接口驱动程序是在一个配置内给出一个接口信息, 它在Linux中由usb_interface_descriptor结构体表示。端口描述符被主机用来决定每个端口的带宽需求, 它在Linux系统中由usb_endpoint_descriptor结构体表示。
编写一个USB驱动程序, 是从usb_drive结构体开始的。Linux中模块加载函数调用usb_register () 和usb_unregister () 从而对usb_driver结构体进行加载与卸载。如果某个设备信息与该驱动中usb_device_id usb_mouse_id_table结构体的信息相一致, 则会调用usb_driver中探测成员函数probe () , 将初始化USB断点信息, 并对设备做一些初始化工作, 分配urb结构体, 准备数据传输。其urb处理大致框架结构如图2所示。
当鼠标设备在用户空间打开时, 将提交probe函数构建的urb请求块, urb将开始为传送数据而忙碌了。urb请求块就像一个装东西的“袋子”, USB驱动程序把“空袋子”提交给USB core, 然后再交给主控制器, 主控制器把数据放入这个“袋子”后再将装满数据的“袋子”通过USB core交还给USB驱动程序, 这样一次数据传输就完成了。
3 结束语
由于USB简单方便快捷等优点, 许多外接设备会越来越青睐USB接口, 这是一种发展的趋势。Linux系统具有开源、安全等特性, 用户也在急剧增加。届时, 会有越来越多的USB驱动加入Linux内核之中。
摘要:在现代生活中, 由于USB接口方便快捷, 人们对USB设备的依赖感也越来越强。许多操作系统中也都包含了大量USB设备驱动。Linux系统几乎支持各种各样的USB设备, 包括鼠标、键盘、摄像头、游戏杆等各种各样的USB设备。在主机控制器方面, Linux内核不仅支持USB1.1协议的UHCI与OHCI, 还支持USB2.0协议的EHCI。本文详细的描述了USB设备的框架结构以及Linux系统下的USB驱动的框架结构。
关键词:USB,设备驱动,Linux
参考文献
[1]Jonathan Corbet, Alessandro Rubini, Greg Kroah-Hartman等.LINUX设备驱动程序[M].北京:中国电力出版社, 2006.
[2]Universal Serial Bus Specification Compaq, Intel, Mi—crosoft, NEC Revision 1.1.September 23, 1998.
[3]温卡特斯瓦兰.精通Linux驱动程序开发[M].北京:人民邮电出版, 2009.
[4]胡晓军, 张爱成.USB接口卡发技术[M].西安:西安电子科技大学出社, 2005:15-17.
linux设备驱动 篇2
销毁类:参数为用struct class结构体定义的变量
void class_destroy(struct class *cls){ if ((cls == NULL) || (IS_ERR(cls))) return; class_unregister(cls);}销毁设备节点:
void device_destroy(struct class *class, dev_t devt){ struct device *dev = NULL; struct device *dev_tmp; down(&class->sem); list_for_each_entry(dev_tmp, &class->devices, node) { if (dev_tmp->devt == devt) { dev = dev_tmp; break; } } up(&class->sem); if (dev) device_unregister(dev);}
★ Win7系统提示“文件丢失”无法自动安装驱动怎么办
★ 简历的细节点
★ qq自动回复
★ 个性自动回复
★ 化工物流节点选址研究
★ 驱动高三考试作文
★ 任务驱动型作文
★ 任务型驱动满分范文
★ 二轮驱动玩具车作文
linux设备驱动 篇3
关键词: 触摸屏;input子系统;嵌入式Linux;驱动程序
中图分类号:TP316 文献标识码:A
引 言
触摸屏由于其友好的人机交互性、操作简单、输入响应速度快,大大简化了嵌入式系统的输入而被广泛应用于工业控制、仪器仪表、消费类电子产品等领域[1]。常见的触摸屏有电阻式触摸屏、电容式触摸屏、红外触摸屏以及表面声波式触摸屏四种虽然它们的材料不同,但实现原理都基本一致[8]。Linux操作系统由于开发源代码、内核稳定、易于移植、资源丰富、支持多种硬件平台、免费等优点,而被广泛应用嵌入式领域[5]。ARM处理器因其卓越的性能、良好的扩展性、丰富的文档资料以及丰富的嵌入式软件支持受到越来越多的设计者和学习者所喜爱,从而也使得以ARM处理器和Linux操作系统构造嵌入式系统成为嵌入式系统中的一个重要分支[9]。为了让触摸屏在嵌入式Linux系统中有效工作,需要有触摸屏驱动的支持,随着触摸屏在嵌入式Linux系统中的广泛使用,在触摸屏驱动的设计与研究方面做了大量工作,呈现出越来越多的文献。本文简要介绍了触摸屏的工作原理,详细分析了触摸屏驱动程序的实现框架,设计并实现了在S3C2440A上的触摸屏驱动程序,该驱动通过多种优化算法的结合,实现了较为精确的输入控制。
1 电阻式触摸屏的工作原理及S3C2440触摸屏接口
电阻式触摸屏基本上是薄膜加上玻璃的结构,薄膜和玻璃相邻的一面上均涂有ITO(纳米铟锡金属氧化物)涂层,ITO具有很好的导电性和透明性。当触摸操作时,薄膜下层的ITO会接触到玻璃上层的ITO,经由感应器传出相应的电信号,经过转换电路送到处理器,通过运算转化为屏幕上的X、Y值,从而完成点选的动作,并呈现在屏幕上。常见的电阻式触摸屏有四线触摸屏、五线触摸屏、七线触摸屏、八线触摸屏等,其中四线式最为基本,现以它为例简单介绍其工作原理。四线触摸屏包含两个阻性层,其中一层在屏幕的左右边缘各有一条垂直总线,另一层在屏幕的底部和顶部各有一条水平总线。为了在X轴方向进行测量,将左侧总线偏置为0V,右侧总线偏置为VREF。将顶部或底部总线连接到ADC,当顶层和底层相接触时即可作一次测量。为了在Y轴方向进行测量,将顶部总线偏置为VREF,底部总线偏置为0V。将ADC输入端接左侧总线或右侧总线,当顶层与底层相接触时即可对电压进行测量。对于四线触摸屏,最理想的连接方法是将偏置为VREF的总线接ADC的正参考输入端,并将设置为0V的总线接ADC的负参考输入端。
S3C2440的CMOS模数转换器可以接收8个通道的模拟信号的输入,并将其转换为10位的二进制数据。在2.5MHz的A/D转换时钟下,最大的转换速率可以达到500KSPS。
从图2可知,使用触摸屏时,引脚XP、XM、YP、YM被用于和触摸屏直接相连,剩下的AIN[3:0]可用于一般的ADC输入。当不使用触摸屏时,XP、XM、YP、YM也可以用于一般的ADC输入[5]。有两个中断信号:INT_ADC:表示A/D转换器转换已经结束;INT_TC:表示触摸屏被按下。S3C2440触摸屏控制器有ADC普通转换、独立X/Y轴坐标转换、自动X/Y轴坐标转换、中断等待、闲置五种模式。本文在测量X/Y坐标时,让控制器工作在自动X/Y轴坐标转换模式。该模式下将会自动进行X轴和Y轴的转换操作,随后产生相应的中断。
2 Linux触摸屏驱动设计与优化
2.1 Linux触摸屏驱动设计
Linux设备驱动程序是Linux内核和底层硬件之间的桥梁,为上层应用软件屏蔽了底层硬件的细节[8]。在Linux系统中,键盘、触摸屏、按键、鼠标等设备的驱动程序常推荐利用input输入子系统的接口函数来完成。input输入子系统向应用程序和底层驱动分别提供了统一的接口,input设备用input_dev结构体描述,使用input子系统实现输入设备驱动时,驱动的核心工作是向系统报告键盘、触摸屏、按键、鼠标等输入事件,不再关心文件操作接口,因为input子系统已经完成了文件操作接口,这样提高了驱动程序的通用性。通过input子系统,具体的输入设备驱动只需要完成如下工作[6]:
(1)在模块加载函数中,告知input子系统它可以报告的事件,注册输入设备;
(2)在键被按下/松开、触摸屏被触摸/松开/移动、鼠标被移动/单击/松开时,通过input_report _xxx()报告发生的事件及对应的键值/坐标等状态;
(3)在模块卸载函数中注销输入设备。
因此,本文的触摸屏驱动程序设计也是建立在input子系统之上。以下是基于input子系统的触摸屏驱动程序设计的主要步骤:
(1)在模块加载函数中完成的工作
(2)模块卸载函数中完成的工作
在驱动程序被卸载的时候,会调用该函数,在该函数中常常会完成加载函数相反的工作,如释放中断、释放内存、注销输入设备、删除定时器等。
(3)中断服务函数
2.2 Linux触摸屏驱动优化
通过实验在上报X/Y坐标的地方通过printk()将X/Y坐标(电压值)打印出来,发现即使在同一点点击触摸屏,打印出来的电压值变化也比较大,说明我们的驱动程序实现的精度还较低,为此,需要对其进行某些改进和优化。对于电阻式触摸屏,影响坐标值精度的原因主要有触摸屏自身电阻材料的非均匀性、A/D转换器的精度、触摸屏在按下和松开的抖动等。前两个问题属于器件本身的问题,无法消除,对于第三个问题,我们可以通过对驱动程序的改进和优化来减小误差。
① 首先通过设置adc_regs->adcdly=0xffff等待电压稳定之后再发出IRQ_TC中断;
② 在ADC转换结束后进入INT_ADC中断服务函数后通过adcdat0的第15位来判断触摸屏处于松开还是按下状态,如果是松开状态,则丢弃此次X/Y坐标采样值,而如果ADC结束后触摸屏仍然是按下状态才保留X/Y坐标采样值,并上报;
③ 在上一步的基础上,通过多次测量,然后通过中值滤波等到中间的X/Y坐标,然后上报该坐标,测量次数为奇数。
通过这样的处理,得到的X/Y坐标的误差就明显比较小。但该驱动仍然还不支持长按和滑动的处理。为了支持长按和滑动的处理,我们可以在驱动中增加一定时器,在加载函数中通过以下语句:
加一定时器,并在INT_ADC中断服务函数中,通过mod_timer(&ts_timer, jiffies + HZ/100);函数来启动该定时器,即长按和滑动过程中超过10ms,就会执行定时器处理函数ts_timer_function,进入该函数后,如果触摸屏仍然处于按下状态,则再次进入X/Y测量模式,并启动ADC。
3 Linux触摸屏驱动程序测试
为了测试本文设计的触摸屏驱动程序的正确性,我们在TQ2440硬件平台下,使用奇美3.5英寸液晶屏,以内核Linux-2.6.25.8做测试,在驱动程序的上报X/Y坐标的位置均添加pintk()打印语句把X/Y坐标打印出来。首先通过make menuconfig查看内核配置情况,确认内核中没有其它触摸屏驱动程序。如果有则应将其去掉,重新编译内核,并以该内核心重新启动开发板,然后为本驱动写一makefile文件,内容如下:
该驱动程序和makefile文件应处于同一目录下,然后执行make,如果编译成功,则会在该目录下生成一名为s3c2440_ts_drv.ko的内核模块文件,然后便可以通过网络或串口将该文件传至开发板的根文件系统的根目录下,并执行insmod s3c2440_ts_drv.ko。然后通过触摸笔在触摸屏的不同位置点击,就能通过串口打印出一系列X/Y坐标值。通过比较发现,在驱动改进及优化之后能处理长按和滑动,并且具有较高的精度。
4 结 论
本文在TQ2440上设计了基于input子系统的嵌入式Linux触摸屏驱动程序,并以内核linux-2.6.25.8做了实验测试。实验结果表明,该驱动具有处理长按和滑动的功能,并有较高的精度,为实际应用提供了参考价值。
参考文献
[1] 畅卫功,丁忠林. 嵌入式Linux系统中触摸屏驱动的研究[J]. 微计算机信息,2007,23(2):103-105.
[2] 李春萍,李颉思. 嵌入式Linux中对触摸屏驱动的设计[J]. 计算机工程与设计,2007,28(6):1387-1389.
[3] 朱伟胜,郝卫东 .嵌入式ARM下的触摸屏驱动系统设计[J]. 计算机系统应用,2010,19(9):248-251.
[4] 吕俊霞,杨俊成. 基于嵌入式方式Linux触摸屏驱动程序开发[J]. 核电子学与探测技术,2010,30(7):986-989.
[5] 韦东山. 嵌入式Linux应用程序开发完全手册[M]. 北京:人民邮电出版社,2008.
[6] 宋宝华. Linux设备驱动开发详解[M]. 北京:人民邮电出版社,2008.
[7] 印 盼,赵建军,袁宏攀. 基于TQ2440和Linux的触摸屏驱动研究[J]. 微型机与应用,2011,30(2):75-78.
[8] 於琪建,张海峰. Linux输入子系统在触摸屏驱动上的实现[J]. 机电工程,2009,26(3):32-35.
linux设备驱动 篇4
关键词:Linux,字符设备驱动,USB逻辑组织,USB设备驱动
Linux平台上的设备驱动程序采用模块化设计方式,使得硬件设备能响应各个内部编程接口[1,2]。这些编程接口使得设备对用户而言将变得透明,用户无需关注设备的工作细节,其只需通过系统调用接口即可访问硬件设备。在Linux平台下,将系统调用与设备具体操作相关联的组件即为驱动程序[3]。
某数据采集系统与计算机( PC) 之间的USB通信逻辑架构如图1 所示,PC通过USB接口实时获取数据采集系统所采集的多路图像数据,并存储在网络服务器硬盘上。系统要求该采集系统与PC之间数据传输速率不低于20 MB /s。一般USB设备驱动主要用于解决对设备存储区的访问、对设备进行简单的控制,对数据传输的实时性和带宽要求不高,本文的目的是解决Linux环境下USB设备实时、高速数据传输问题[4]。
1 字符设备驱动
Linux操作系统将外部设备分为3 种基本类型[5]:分别为字符设备,块设备和网络设备。本文所讨论的USB设备本身为简单设备,可归属为字符设备,关于字符设备驱动程序的结构如图2 所示。当驱动通过Insmod加载后,应用程序即可使用标准的系统调用接口实现对字符设备的操作。同时,应用程序还可使用C库函数进行编程; 出于代码可移植性的考虑,本文建议使用C库函数方式。
2 USB设备的逻辑组织
USB设备复杂,由众多不同的逻辑单元构成,具体如图3 所示[6]。
一个USB设备可包含一个或多个配置信息,而不同的配置则使USB设备具有不同的功能[7]; 配置亦由各接口所组成,一个配置中所包含的接口可同时有效,并可被不同的驱动程序所连接; 同样,接口亦由每个端点所组成,并代表着该USB设备对应的每个基本功能,是USB设备驱动程序所要控制的对象; 端点中则包含有该端点所属类型、地址、数据传输方向( 相对USB Host是输入或输出) 、数据传输方式( 同步、中断、批量、控制) 、数据包最大长度、轮询数据传输端点的时间间隔等USB通信时必须用到的信息。
3 USB设备驱动
3. 1 USB设备的注册与注销
在Linux内核中,USB设备的驱动将通过结构体usb_driver来进行描述。usb_driver中则包含着上文所述的所有描述符。对USB设备的注册和注销通过调用usb_register与usb_deregister两个函数完成[8,9]。
int usb_register( struct usb_driver * new driver) ; / /在module_init的usb_skel_init函数中完成
void usb_deregister( struct usb_driver * driver) ; / /在module_exit的usb_skel_exit函数中完成
结构体usb_driver中含有USB设备ID映射表,并定义了探测函数probe( ) 和断开函数disconnect( ) 。在USB设备驱动设计时,probe( ) 和disconnect( ) 将分别在USB设备被插入和移除时被自动调用。
3. 2 USB设备初始化
当USB设备被插入USB主机,且该USB设备被USB主机识别( 即该USB设备与上文已注册的USB设备相匹配) 时,则探测函数将自动被调用; 探测函数主要任务是分配资源,对USB设备进行初始化,获得USB设备的接口和端点信息,注册该USB设备本身所属设备。
本文所描述的USB设备为简单字符设备,在设计过程中将通过usb_register_dev( interface,&skel_class)函数来完成该USB设备驱动程序的注册。其中,interface为USB设备的接口描述符,skel_class中包含着设备名称和将系统调用与该驱动程序关联起来的关键数据结构file_operations; 而结构体file_operations中则包含着skel_open、skel_read( ) 、skel_ioctl( ) 等函数,这些函数的实现将是该USB设备驱动程序设计的关键部分,并且会在应用程序进行open( ) 、read( ) 、ioctl( ) 等系统调用时被最终调用。
3. 3 USB设备数据传输
skel_read( ) 主要用于将USB设备的数据搬移到Linux系统内核中,然后再将这些数据传递给用户。在该过程中,URB( USB Request Block,USB请求块) 发挥着重要作用。USB设备数据传输时,在一个USB端点所对应的URB队列被清空前,一个典型的URB处理过程如图4 所示。
基于URB的USB读取数据( skel_read( ) ) 关键代码如下[10]
结构体file_operations中定义的其它成员函数的实现与skel_read( ) 相类似。
3. 4 USB设备注销
当USB设备被移除或USB驱动程序无法继续控制该USB设备时,断开函数将自动被调用; 断开函数主要用于释放已经申请的资源,注销该USB设备本身所属设备。本文所描述的USB设备为简单字符设备,在设计过程中将通过调用usb_deregister_dev( ) 函数来完成该USB设备驱动程序的注销。
3. 5 其他
在使用USB时,若用户需要对USB设备进行一些简单的控制,则需要驱动程序提供相应的ioctl( ) 函数。当然,该控制功能可以通过上述URB方法实现,但大多数情况下,USB驱动程序仅是为了完成从USB设备接收或发送一些简单控制命令,这时则可通过usb_control_msg( ) 来快速实现该功能[11]。
4 测试验证
在完成驱动程序的设计编码工作后,通过make命令对该驱动进行编译,并将编译后的驱动程序通过insmod命令加载到Linux内核中。在Linux环境下开发该USB驱动的测试程序,通过在用户输入的时间内连从数据采集系统读取数据,累计读取的数据量,得到单位时间内的平均数据读取速度,具体如图5 所示。
5 结束语
linux设备驱动 篇5
一、PCI总线系统体系结构
PCI是外围设备互连(Peripheral Component Interconnect)的简称,作为一种通用的总线接口标准 ,它在目前的计算机系统中得到了非常广泛的应用。PCI提供了一组完整的总线接口规范,其目的是描述 如何将计算机系统中的外围设备以一种结构化和可控化的方式连接在一起,同时它还刻画了外围设备在连 接时的电气特性和行为规约,并且详细定义了计算机系统中的各个不同部件之间应该如何正确地进行交互 。
无论是在基于Intel芯片的PC机中,或是在基于Alpha芯片的工作站上,PCI毫无疑问都是目前使用最广 泛的一种总线接口标准。同旧式的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、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主线)连接在一起,通常PCI总线1称为PCI-PCI桥的下游(downstream),而PCI总线0 则称为 PCI-PCI桥的上游(upstream)。图中连接到从PCI总线上的是SCSI卡和以太网卡。为了兼容旧的ISA总线 标准,PCI总线还可以通过PCI-ISA桥来连接ISA总线,从而能够支持以前的ISA设备。图中ISA总线上连接 着一个多功能I/O控制器,用于控制键盘、鼠标和软驱。
图2 PCI系统示意图
在此我只对PCI总线系统体系结构作了概括性介绍,如果读者想进一步了解,David A Rusling在The Linux Kernel(tldp.org/LDP/tlk/dd/pci.html)中对Linux的PCI子系统有比较详细的介绍。
二、Linux驱动程序框架
Linux将所有外部设备看成是一类特殊文件,称之为“设备文件”,如果说系统调用是Linux内核和应 用程序之间的接口,那么设备驱动程序则可以看成是 Linux内核与外部设备之间的接口。设备驱动程序向 应用程序屏蔽了硬件在实现上的细节,使得应用程序可以像操作普通文件一样来操作外部设备。
1. 字符设备和块设备
Linux 抽象了对硬件的处理,所有的硬件设备都可以像普通文件一样来看待:它们可以使用和操作文 件相同的、标准的系统调用接口来完成打开、关闭、读写和I/O控制操作,而驱动程序的主要任务也就是 要实现这些系统调用函数。Linux系统中的所有硬件设备都使用一个特殊的设备文件来表示,例如,系统 中的第一个IDE 硬盘使用/dev/hda表示。每个设备文件对应有两个设备号:一个是主设备号,标识该设备 的种类,也标识了该设备所使用的驱动程序;另一个是次设备号,标识使用同一设备驱动程序的不同硬件 设备。设备文件的主设备号必须与设备驱动程序在登录该设备时申请的主设备号一致,否则用户进程将无 法访问到设备驱动程序。
在Linux操作系统下有两类主要的设备文件:一类是字符设备,另一类则是块设备。字符设备是以字节 为单位逐个进行 I/O操作的设备,在对字符设备发出读写请求时,实际的硬件I/O紧接着就发生了,一般 来说字符设备中的缓存是可有可无的,而且也不支持随机访问。块设备则是利用一块系统内存作为缓冲区 ,当用户进程对设备进行读写请求时,驱动程序先查看缓冲区中的内容,如果缓冲区中的数据能满足用户 的要求就返回相应的数据,否则就调用相应的请求函数来进行实际的I/O操作。块设备主要是针对磁盘等 慢速设备设计的,其目的是避免耗费过多的CPU时间来等待操作的完成。一般说来,PCI卡通常都属于字符 设备。
所有已经注册(即已经加载了驱动程序)的硬件设备的主设备号可以从/proc/devices文件中得到。使 用mknod命令可以创建指定类型的设备文件,同时为其分配相应的主设备号和次设备号。例如,下面的命 令:
[root@gary root]# mknod /dev/lp0 c 6 0
将建立一个主设备号为6,次设备号为0的字符设备文件/dev/lp0。当应用程序对某个设备文件进行系 统调用时,Linux内核会根据该设备文件的设备类型和主设备号调用相应的驱动程序,并从用户态进入到 核心态,再由驱动程序判断该设备的次设备号,最终完成对相应硬件的操作。
2. 设备驱动程序接口
Linux中的I/O子系统向内核中的其他部分提供了一个统一的标准设备接口,这是通过 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 file *, 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 (*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) (struct 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内核将通过 file_operations结构访问驱动程序提供的函数。例如,当应用程序对设备文件执行读操作时,内核将调 用file_operations结构中的read函数。
2. 设备驱动程序模块
Linux下的设备驱动程序可以按照两种方式进行编译,一种是直接静态编译成内核的一部分,另一种则 是编译成可以动态加载的模块。如果编译进内核的话,会增加内核的大小,还要改动内核的源文件,而且 不能动态地卸载,不利于调试,所有推荐使用模块方式。
从本质上来讲,模块也是内核的一部分,它不同于普通的应用程序,不能调用位于用户态下的C或者 C++库函数,而只能调用Linux内核提供的函数,在/proc/ksyms中可以查看到内核提供的所有函数。
在以模块方式编写驱动程序时,要实现两个必不可少的函数init_module( )和cleanup_module( ),而 且至少要包含和两个头文件。在用gcc编译内核模块时 ,需要加上-DMODULE -D__KERNEL__ -DLINUX这几个参数,编译生成的模块(一般为.o文件)可以使用命 令insmod载入Linux内核,从而成为内核的一个组成部分,此时内核会调用模块中的函数init_module( ) 。当不需要该模块时,可以使用rmmod命令进行卸载,此进内核会调用模块中的函数cleanup_module( )。 任何时候都可以使用命令来lsmod查看目前已经加载的模块以及正在使用该模块的用户数。
3. 设备驱动程序结构
了解设备驱动程序的基本结构(或者称为框架),对开发人员而言是非常重要的,Linux的设备驱动程 序大致可以分为如下几个部分:驱动程序的注册与注销、设备的打开与释放、设备的读写操作、设备的控 制操作、设备的中断和轮询处理。
驱动程序的注册与注销
向系统增加一个驱动程序意味着要赋予它一个主设备号,这可以通过在驱动程序的初始化过程中调用 register_chrdev( )或者register_blkdev( )来完成。而在关闭字符设备或者块设备时,则需要通过调用 unregister_chrdev( )或unregister_blkdev( )从内核中注销设备,同时释放占用的主设备号。
设备的打开与释放
打开设备是通过调用file_operations结构中的函数open( )来完成的,它是驱动程序用来为今后的操 作完成初始化准备工作的。在大部分驱动程序中,open( )通常需要完成下列工作:
检查设备相关错误,如设备尚未准备好等。
如果是第一次打开,则初始化硬件设备。
识别次设备号,如果有必要则更新读写操作的当前位置指针f_ops。
分配和填写要放在file->private_data里的数据结构。
使用计数增1。
释放设备是通过调用file_operations结构中的函数release( )来完成的,这个设备方法有时也被称为 close( ),它的作用正好与open( )相反,通常要完成下列工作:
使用计数减1。
释放在file->private_data中分配的内存。
如果使用计算为0,则关闭设备。
设备的读写操作
字符设备的读写操作相对比较简单,直接使用函数read( )和write( )就可以了。但如果是块设备的话 ,则需要调用函数block_read( )和block_write( )来进行数据读写,这两个函数将向设备请求表中增加 读写请求,以便Linux内核可以对请求顺序进行优化。由于是对内存缓冲区而不是直接对设备进行操作的 ,因此能很大程度上加快读写速度。如果内存缓冲区中没有所要读入的数据,或者需要执行写操作将数据 写入设备,那么就要执行真正的数据传输,这是通过调用数据结构blk_dev_struct中的函数request_fn( )来完成的。
设备的控制操作
除了读写操作外,应用程序有时还需要对设备进行控制,这可以通过设备驱动程序中的函数ioctl( ) 来完成。ioctl( )的用法与具体设备密切关联,因此需要根据设备的实际情况进行具体分析。
设备的中断和轮询处理
对于不支持中断的硬件设备,读写时需要轮流查询设备状态,以便决定是否继续进行数据传输。如果 设备支持中断,则可以按中断方式进行操作。
三、PCI驱动程序实现
1. 关键数据结构
PCI 设备上有三种地址空间:PCI的I/O空间、PCI的存储空间和PCI的配置空间。CPU可以访问PCI设备 上的所有地址空间,其中I/O空间和存储空间提供给设备驱动程序使用,而配置空间则由Linux内核中的 PCI初始化代码使用。内核在启动时负责对所有PCI设备进行初始化,配置好所有的PCI设备,包括中断号 以及I/O基址,并在文件/proc/pci中列出所有找到的PCI设备,以及这些设备的参数和属性。
Linux驱动程序通常使用结构(struct)来表示一种设备,而结构体中的变量则代表某一具体设备,该 变量存放了与该设备相关的所有信息。好的驱动程序都应该能驱动多个同种设备,每个设备之间用次设备 号进行区分,如果采用结构数据来代表所有能由该驱动程序驱动的设备,那么就可以简单地使用数组下标 来表示次设备号。
在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_dev *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
这个数据结构也在文件include/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; unsigned 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_compatible[DEVICE_COUNT_COMPATIBLE]; unsigned short device_compatible[DEVICE_COUNT_COMPATIBLE]; unsigned int irq; struct resource resource[DEVICE_COUNT_RESOURCE]; struct resource dma_resource[DEVICE_COUNT_DMA]; struct resource irq_resource[DEVICE_COUNT_IRQ]; char name[80]; char slot_name[8]; 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设备驱动程序时,通常至少要实现以下几个部分:初始化设备模块、设备打 开模块、数据读写和控制模块、中断处理模块、设备释放模块、设备卸载模块,
下面给出一个典型的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; /* ... */}/* 中断处理模块 */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, /* 内存重映射操作*/ 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){ /* ... */}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等标志符,以使同普通函数 区分开来。构造出这样一个框架之后,接下去的工作就是如何完成框架内的各个功能模块了。
3. 初始化设备模块
在Linux系统下,想要完成对一个PCI设备的初始化,需要完成以下工作:
检查PCI总线是否被Linux内核支持;
检查设备是否插在总线插槽上,如果在的话则保存它所占用的插槽的位置等信息。
读出配置头中的信息提供给驱动程序使用。
当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_pci_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探测例程将负责完成对硬件的检测工作。
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(sizeof(struct demo_card), GFP_KERNEL)) == NULL) { printk(KERN_ERR “pci_demo: out of memory ”); 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 = DEMO_CARD_MAGIC; /* 设置成总线主DMA模式 */ pci_set_master(pci_dev); /* 申请I/O资源 */ request_region(card->iobase, 64, card_names[pci_id->driver_data]); return 0;}
4. 打开设备模块
在这个模块里主要实现申请中断、检查读写模式以及申请对设备的控制权等。在申请控制权的时候, 非阻塞方式遇忙返回,否则进程主动接受调度,进入睡眠状态,等待其它进程释放对设备的控制权。
static int demo_open(struct inode *inode, struct file *file){ /* 申请中断,注册中断处理程序 */ request_irq(card->irq, &demo_interrupt, SA_SHIRQ, card_names[pci_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_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. 数据读写和控制信息模块
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);/* 将读取的数据传输到用户空间 */return 0; } /* ... */}
事实上,在demo_fops里还可以实现诸如demo_read( )、demo_mmap( )等操作,Linux内核源码中的 driver目录里提供了许多设备驱动程序的源代码,找那里可以找到类似的例子。在对资源的访问方式上, 除了有I/O指令以外,还有对外设I/O内存的访问。对这些内存的操作一方面可以通过把I/O内存重新映射 后作为普通内存进行操作,另一方面也可以通过总线主DMA(Bus Master DMA)的方式让设备把数据通过 DMA传送到系统内存中。
6. 中断处理模块
PC的中断资源比较有限,只有0~15的中断号,因此大部分外部设备都是以共享的形式申请中断号的。 当中断发生的时候,中断处理程序首先负责对中断进行识别,然后再做进一步的处理。
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); return; /* 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 &= (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);}
四、小结
linux设备驱动 篇6
通用串行总线(USB)是主机和外围设备之间的一种连接,它定义了一套任何特定类型的设备都可以遵循的标准,加之它具有良好的热插拔能力和较快的传输速度,因此,有越来越多的厂商的产品和设备都开始支持USB技术,使USB技术获得了广泛的应用。但Linux在硬件配置上还不能全部很好地支持USB设备,尤其是在嵌入式开发中,因此,研究USB驱动程序的结构和设备驱动程序的开发有着重要的现实意义。
1 USB接口规范及其设备驱动基础
1.1 USB总线简介
通用串行总线(USB)是主机和外围设备之间的一种连接方式,最初是为了代替一些低速总线而设计的,它以单一类型的总线连接许多不同类型的设备。它包括USB主控制器和根集线器,其中USB主控制器(host control)负责询问每一个USB设备是否有数据需要进行发送,同时提供主机与USB设备间的电气与协议互联,而根集线器则负责提供USB设备的连接点。USB接口有低速、全速和高速的数据传输三种方式。其中,前两种在USB1.1标准中予以定义,后面一种是在USB2.0中新引入的一种传输速率。
Linux内核支持两种类型的USB驱动程序:USB主机端驱动程序和USB设备端驱动程序。USB主机端驱动程序主要控制插入其中的USB设备,对其进行管理,而USB设备端驱动程序主要控制该设备如何作为一个USB设备来和主机通信。本文主要研究USB主机端驱动程序。
1.2 Linux下的设备表示和USB接口规范
1.2.1 Linux下的设备表示
Linux与windows不同,它把所有的设备都分为字符设备、块设备和网络设备等三种。确切的说,Linux操作系统是将所有的设备(而不仅是磁盘上的文件)全都都看成文件,都纳入文件系统的范畴,然后通过文件操作的界面进行操作。字符设备是指存取时没有利用缓存的设备(如鼠标、声卡等),其以字节为单位进行数据处理,通常只允许顺序访问,一般不利用缓存技术。块设备的读写则需要缓存技术来支持,它将块设备数据按可寻址的块为单位来进行处理,大多数块设备允许随机访问(如硬盘、光盘等)。传统方式的设备管理中,除了设备类型以外,Linux内核还需要一对主次设备号来唯一标示设备,主设备号(Major Number)相同的设备使用相同的驱动程序,而次设备号(Minor Number)则用来区分具体设备的实例。网络设备在Linux系统里需要做专门的处理。Linux网络系统主要是基于BSDunix的socket通信机制,Linux内核为所有与socket相关的操作提供一个统一的系统调用入口,内核中为socket设置的总入口为sys_socketcall(),其代码在net/socket.c中。
1.1.2 USB接口规范
从1994年USB实施者论坛(USB-IF)发布的USB0.7版本到现在的USB2.0和USB OTG(On-The-Go)规范,USB接口从功能上得到了不断的发展和完善。1995年底,Microsoft、Compaq等多家公司联合推出了USB OHCI(OPEN HOST CONTROL INTERFACE)标准。这是一个完全开放的规范标准,它定义了一系列的寄存器与许多相关的数据结构,用来统一USB主控制器和驱动程序接口。简单的说,操作系统只要具有USB OHCI标准规范的驱动程序,按照OHCI规范的USB主机控制器就可以应用在系统上。OHCI规范适用于USB1.1,其定义了两个主机控制器(HC)与主机控制器驱动(HCD)的通信通道,支持USB四种数据传输方式,并且根据数据传输的特点,将终端数据传输和等时数据传输归为同一类的周期性数据传输方式。
1.3 USB设备驱动基础
设备驱动程序是操作系统内核和机器硬件之间进行交互的接口,它为应用程序屏蔽了底层硬件的具体实现细节,我们开发的各种应用程序会把所有的硬件设备当作普通文件进行处理和相关操作。用户的操作通过一组标准化的调用执行,驱动程序则将这些调用映射到作用于实际硬件的设备的特有操作上,这个接口能够使驱动程序独立于内核的其他部分而建立,在必要的情况下可以在运行时“插入”内核,这种模块化的思想使我们编写Linux驱动程序时非常方便。设备驱动作为内核的一部分,它有非常重要的作用,主要完成下面一些功能:初始化系统设备;使设备进入运行状态和退出系统服务;在内核和具体设备间互相传送数据;实时检测和处理设备可能出现的各种错误。
2 USB设备驱动机制分析
在Linux环境下设计开发设备驱动程序,借助于Linux的强大功能、简单的操作和开放式源代码,可以很方便的编写设备驱动程序,但是这种驱动只能调用Linux内核所提供的函数来进行,而且涉及到和底层硬件进行交互,这样就会使系统容易崩溃,在工程实现上有很多弊端。一个完整的USB驱动程序必须要完成四方面工作:识别驱动程序所要支持的硬件设备、对USB驱动程序进行注册和注销、探测和断开及传输数据。
2.1 识别驱动程序所要支持的硬件设备
USB核心会借助一个列表来判断系统上的相关硬件设备应该使用哪一个驱动程序。这个列表是以一个结构体的形式存在的,它包含制造商的ID和设备号的ID。当我们安装硬件的制造商ID和设备号ID与所给列表信息相匹配时,硬件就可以使用这个驱动程序了。
2.2 对USB驱动程序进行注册和注销
所有的USB驱动程序在装载时都必须向USB系统进行注册,一个USB驱动程序由结构体usb_driver来描述,它会向USB核心来描述USB驱动程序,一般具有name、id_table、probe、disconnect等几个成员,来通知内核驱动程序的名称、硬件的匹配信息、标示列表以及当一个已有的匹配硬件进行连接或被移除时,应该调用系统的哪些函数。在USB驱动程序初始化和注销时,分别调用usb_register函数、usb_deregister函数来对USB驱动程序进行注册和注销。
2.3 探测和断开
当有一个USB设备连上总线时,usb_get_device_deseriptor函数会自动进行执行探测,从中取出该设备的描述符,并用结构体usb_device来保存配置和接口的相关信息。当USB系统核心确定有接口可以被该驱动程序处理时,探测函数probe会被调用。在探测函数中,USB驱动程序会调用函数usb_set_intfdata在接口中保存相应设备信息,然后再通过函数usb_register_dev在USB核心中注册硬件设备。当然,当一个USB接口从系统中被移除或者驱动程序将要从USB核心中卸载时,USB核心同样会通过断开函数disconnec来进行操作,然后,调用usb_deregister_dev函数来注销硬件设备。
2.4 传输数据
当驱动程序需要发送数据到USB设备或者要接收从USB设备发送来的数据时,通常情况下需要分配提交一个urb(Universa Request Block)来实现与设备间的数据传输。一个urb包括:USB设备信息、数据传输管道、数据传输缓冲区、数据传输缓冲区长度、回调函数、不同类型数据传输必需参数、数据传输过程控制参数等。在对urb初始化时可以指定回调函数,来报告urb传输的状态信息。考虑到内核空间与用户空间不能进行直接通信,而驱动程序和应用程序在分别从属于内核空间及用户空间,所以驱动程序就必须在内核空间分配一个缓冲区来作为中介以实现USB设备数据的读写,通过这个缓冲区,它一方面与硬件实现交互,另一方面利用函数copy_to_user或copy_from_user来实现与用户层的数据传递。
3 USB主设备驱动实现
3.1 USB驱动开发的一般流程
在嵌入式Linux系统中要实现USB主机接口,接口驱动程序需要完成三方面功能:USB主机控制器的初始化、配置USB设备并读取其信息然后给出判断、完成特定类USB类设备的操作命令。在操作系统环境下,驱动的开发相对简单一些,只需要通过对USB设备进行必要的配置,然后通过一个USB骨架就可以完成对USB驱动的开发。在Linux内核源代码driversusbusb-skeleton.c中提供了一个最为基础的USB驱动程序,我们称之为USB骨架。通过修改usb-skeleton.c,我们就可以方便地完成一个USB设备驱动的开发。Linux本身并不能支持生产厂商生产的各种各样的USB产品设备,我们要想这些USB设备正常的在Linux下工作,我们就需要为这些设备开发特定的驱动程序。开发USB驱动首先要在USB子系统中注册相关信息,然后把这些信息通过一个结构体传给USB子系统,这个结构体叫file_operations结构指针。它是设备驱动程序所提供的入口节点位置,其主要代码如下:
用户对设备进行访问时主要是通过这些函数来操作的,其含义如下:
open:打开USB设备,典型的用法为:open("devabc",flag);flag用来指定打开的参数,如可读写属性等;open函数会返回一个整数fd句柄,根据fad的值,来表示打开文件的正确与否,如果fad小于0,则表示打开错误。
read,write:设备读写函数,用法如下:read(int fd,char*bur,char lengh,……)。
ioctrl:设备控制函数,通过此函数用户可以对各类设备进行特殊控制。
一个设备驱动程序的设计开发实际上就是实现上述这四个函数和一个设备的初始化函数。这些函数在USB设备驱动程序中可以用usb_init(),usb_open(),usb_read(),usb_write(),usb_ioctrl()等来表示。最后用file_operations的结构体把用户级的open、read等函数与设备的usb_open(),usb_read()等函数联系起来,就可以实现USB设备驱动的开发。
3.2 USB驱动程序的注册和注销
一个USB设备驱动模块必须在USB子系统中注册两个入口点和名字,而对于那些特殊的USB设备则需要注册file_operations的结构体和一些次设备号。USB驱动程序首先需要向USB子系统进行登记注册,其注册结构为:
当USB设备连接到总线上被检测到时,就调用probe入口点。而设备驱动则需要为这个新的设备重新创建一个数据结构:void*probe(struct usb_device*dev,unsigned interface);当USB设备驱动程序被从USB核心移除或者设备从总线上取下时,程序需要调用另一个入口点:static void dabusb_disconnect(struct usb_device*usbdev.void*drv_context)
在probe函数里,需要找出中断读端点与中断写端点从而来建立它们之间的通信管道,并初始化dev的各成员变量,然后向系统内核注册硬件设备生成设备文件。其中需要动态分配两块内存来分别作为读操作和写操作时urb用的缓冲区:int_in_buffer及int_out_buffer,在硬件被移除或者设备驱动程序被卸载时可以调用delete函数来释放这两块缓冲区。根据设备制造商和设备号定义变量skel_table来创建匹配USB设备硬件的信息列表;定义usb_driver结构体变量skel_driver来描述设备驱动程序,并初始化其内相关成员name、id_tabe、probe、disconnect等。然后在初始化和退出函数usb_skel_init和usb_skel_exit中,通过分别调用函数usb_register和usb_deregister来实现设备注册和注销设备。
3.3 USB设备的打开和释放
当USB设备连上总线时,设备驱动程序需要调用文件操作集中的open()函数来打开设备,源代码见Linux内核目录driversusbusb-skeleton.c,主要代码如下:
当USB设备从总线上移除或设备驱动被卸载时,通过调用释放设备函数skel_release,其主要代码如下:
3.4 USB设备读数据函数
从USB设备上读取数据主要是靠读数据函数skel_read来实现,通过在内存中开辟一个缓冲区来保存设备数据,然后再把缓冲区中的数据拷贝到用户空间,从而实现设备数据读取。主要代码如下:
3.5 USB驱动程序的验证
Linux有一个很好的特性,其内核可以在运行时扩展,这就使得我们既可以把USB设备驱动直接编译到内核代码中,也可以用模块方式加载。考虑到我们调试的需要,可采用在驱动目录下,编写Make_file文件,通过make命令生成驱动模块,用insmod命令加载驱动模块的方式进行测试。以常见的USB设备u盘为例,配置好Linux内核,使其支持各种USB设备,然后通过mount命令挂载u盘,观察U盘能否成功挂接,从而判断开发的驱动正确与否。
4 结束语
功能完善的驱动是设备正常工作的前提,随着USB规范的普及和技术的提高,其传输速率越来越快,各个厂家生成的USB设备迅速增多,因此研究USB设备驱动的开发有着积极的意义。
参考文献
[1]毛德操,胡希明.Linux内核源代码情景分析[M].杭州:浙江大学出版社,2001.
[2]Jonathan Corbet,Alessandro Rubini,Greg Kroah-Hartman.Linux设备驱动程序[M].3版.魏永明,译.北京:中国电力出版社,2006.
[3]孙天泽,袁文菊,张海峰.嵌入式设计及Linux驱动开发指南——基于ARM9处理器[M].北京:电子工业出版社,2005.
[4]唐六华,王瑛.嵌入式Linux下USB主机设备驱动开发[J].计算机技术与发展,2009,19(9):182-184.
linux设备驱动 篇7
关键词:Linux,触摸屏,多点触摸,驱动
1 引言
多点触摸设备是指一种允许电脑使用者通过多个手指操作图形图像应用程序的交互系统设备。目前已经有很多基于多点触摸技术的应用, 例如手机、PDA、MID以及ATM机等设备。同时, 在嵌入式领域, Linux操作系统的开源代码授权模式和易于定制、易于剪裁和移植的特性, 使得Linux成为嵌入式开发平台和应用平台中非常受欢迎的操作系统。基于以上两点, 本文研究并实现了采用嵌入式Linux操作系统的触摸屏驱动的设计。
2 相关技术
2.1 多点触摸技术
多点触摸技术[1,2]目前有两种:多点触摸识别手势方向 (Multi-Touch Gesture) 和多点触摸识别手指位置 (Multi-Touch All-Point) , 其特点是:
(1) 多重触控是在同一显示界面上的多点或多用户的交互操作模式, 摒弃了键盘、鼠标的单点操作。
(2) 用户可通过双手进行单点触摸, 也可以以单击、双击、平移、按压、滚动以及旋转等不同手势触摸屏幕, 实现随心所欲地操控, 从而更好、更全面地了解对象的相关特征 (文字、录像、图片、卫片、三维模拟等信息) 。
(3) 可根据客户需求, 定制相应的触控板、触摸软件及多媒体系统;可与专业图形软件配合使用。
2.2 电容式触摸屏
电容式触摸屏是一块四层复合玻璃屏[3], 玻璃屏的内表面和夹层各涂有一层掺锡氧化铟 (Indium Tin Oxide, ITO) , 最外层是一薄层矽土玻璃保护层, 夹层ITO涂层作为工作面, 四个角上引出四个电极, 内层ITO为屏蔽层以保证良好的工作环境。当手指触摸在金属层上时, 由于人体电场作用, 用户和触摸屏表面形成一个耦合电容, 对于高频电流来说, 电容是直接导体, 于是手指从接触点吸走一个很小的电流。这个电流分别从触摸屏的四角上的电极中流出, 并且流经这四个电极的电流与手指到四角的距离成正比, 控制器通过对这四个电流比例的精确计算, 得出触摸点的位置。
3 多点触摸驱动的实现
3.1 硬件环境
(1) 触摸屏控制芯片Guitar
多点电容式触摸屏控制芯片Guitar采用投射式电容检测原理[4], 由15个驱动通道与10个感应通道组成触摸检测网络, 通过内置模拟放大电路、数字运算模块, 及高性能MPU得到实时准确的触摸信息, 并通过I2C传输给主控芯片。实现“所点即所得”的非凡用户体验。Guitar可同时识别5个触摸点位的实时准确位置、移动轨迹及触摸力度, 并可根据主控需要, 读取相应点数的触摸信息。
(2) RK2918处理器
RK2918是一款低功耗、高性能的移动电话、个人移动互联网设备以及其他应用的数字多媒体处理器的解决方案。它采用ARM Cortex A8内核, 处理器主频为1.2 GHz, 支持Neon协处理器和512 KB二极缓存, 并内置图形2D/3D加速:Open GL ES 2.0功能, 在3D效果方面相对同类产品有较大的提升, 三角形生产率最高支持40 M/s。具有高性能外部存储器接口, 要求具有较强的内存带宽承受能力, 还提供了一套完整的外围接口, 支持非常灵活的应用。
3.2 Linux I2C核心代码设计
在Linux系统中, I2C驱动由三部分组成[5,6,7,8], 即I2C核心、I2C总线驱动和I2C设备驱动。这三部分相互协作, 形成了非常通用、可适应性很强的I2C框架。
I2C核心提供了I2C总线驱动和设备驱动的注册、注销方法, I2C通信方法 (即“algorithm”) 上层的、与具体适配器无关的代码以及探测设备、检测设备地址的上层代码等。
I2C总线驱动是对I2C硬件体系结构中适配器端的实现, 适配器可由CPU控制, 甚至直接集成在CPU内部。I2C总线驱动主要包含了I2C适配器数据结构i2c_adapter、I2C适配器的algorithm数据结构i2c_algorithm和控制I2C适配器产生通信信号的函数。
I2C设备驱动是对I2C硬件体系结构中设备端的实现, 设备一般挂接在受CPU控制的I2C适配器上, 通过I2C适配器与CPU交换数据。I2C设备驱动主要包含了数据结构i2c_driver和i2c_client, 我们需要根据具体设备实现其中的成员函数。
上述代码第3行对应为SMBus传输函数指针, SMBus大部分基于I2C总线规范, SMBus不需增加额外引脚。与I2C总线相比, SMBus增加了一些新的功能特性, 在访问时序也有一定的差异。
3.3 数据结构间的关系
i2c_driver、i2c_client、i2c_adapter和i2c_algorithm这四个数据结构的作用及其相互间的关系分析如下。
(1) i2c_adapter与i2c_algorithm
i2c_adapter对应于物理上的一个适配器[9,10], 而i2c_algorithm对应一套通信方法。一个I2C适配器需要i2c_algorithm中提供的通信函数来控制适配器上产生特定的访问周期。缺少i2c_algorithm的i2c_adapter什么也做不了, 因此i2c_adapter中包含其使用的i2c_algorithm的指针。
i2c_algorithm中的关键函数master_xfer () 用于产生I2C访问周期需要的信号, 以i2c_msg为单位。
(2) i2c_driver与i2c_client
i2c_driver对应一套驱动方法, 是纯粹的用于辅助作用的数据结构, 它不对应于任何的物理实体。i2c_client对应于真实的物理设备, 每个I2C设备都需要一个i2c_client来描述。i2c_client一般被包含在i2c字符设备的私有信息结构体中。
i2c_driver与i2c_client发生关联的时刻在i2c_driver的attach_adapter () 函数被运行时。attach_adapter () 会探测物理设备, 当确定一个client存在时, 把该client使用的i2c_client数据结构的adapter指针指向对应的i2c_adapter, driver指针指向该i2c_driver, 并会调用i2c_adapter的client_register () 函数。相反的过程发生在i2c_driver的detach_client () 函数被调用的时候。
(3) i2c_adpater与i2c_client
i2c_adpater与i2c_client的关系与I2C硬件体系中适配器和设备的关系一致, 即i2c_client依附于i2c_adpater。由于一个适配器上可以连接多个I2C设备, 所以一个i2c_adpater也可以被多个i2c_client依附, i2c_adpater中包括依附于它的i2c_client的链表。
4 系统验证
验证的流程思想是当触摸笔按下的时候, 内核响应中断, 进入中断服务程序。在中断到来之后, 屏蔽中断, 以保证触摸屏被连续按下时, 不会连续地产生触发中断进入中断处理程序。在工作队列中设置一个函数, 连续监测从触摸屏取得的X和Y坐标值;如果触摸笔按下就读取X和Y坐标值到缓冲区中, 如果触摸笔抬起就在缓冲区中放入抬起信号, 并且在触摸笔抬起时退出中断处理。
本设计采用的内核版本是linux-2.6.25, 把内核程序以及编译链进行编译。图1a所示为整个硬件部分的连接图, 在LED显示屏上的是触摸屏。当在触摸屏上进行操作时, 显示屏上的圆圈代表的就是触摸点的位置 (见图1b) 。
与只能接受单点输入的触摸技术相比, 多点触摸技术允许用户在多个地方同时触摸显示屏, 而且能够对网页或图片进行旋转、缩小和放大等操作。
5 结语
本设计以I2C方式对多点触摸屏进行驱动, 通过嵌人式Linux将多点触摸输入方式应用到嵌入式应用系统中, 丰富了单一的键盘输入与单点输入方式, 减小了系统尺寸, 提高了系统的可靠性。它在传统单点触摸屏的基础上, 将使用者可操作的控制点扩展到多个, 使得使用者可以用双手同时进行多个控制点的定位和移动。这种力求更加体贴而时尚的功能和应用, 同时个性化的操作方式满足了当代人们对数码产品的高要求。
参考文献
[1]申伟杰, 彭楚武.嵌入式Linux中基于Qt/Embeded触摸屏驱动的设计[J].中国仪器仪表, 2006, 21 (07) :36-59.
[2]Pennock Jacob.A Survey of Input Sensing and Processing Techniques for Multi-Touch Systems[J].CDES, 2007 (02) :15-31.
[3]何小艇.电子系统设计[M].杭州:浙江大学出版社, 2008:10-61.
[4]刘淼.嵌入式系统接口设计与Linux驱动程序开发[M].北京:北京航空航天大学出版社, 2006:45-47.
[5]徐柳茂, 黄永强, 蒋念东, 等.嵌入式Linux中I2C驱动程序的应用设计[J].国外电子元器件2007, 11 (18) :171-173.
[6]陈莉君.Linux内核设计与实现[M].第2版.北京:机械工业出版社, 2006:59-95.
[7]周立功, 陈明计, 陈渝.ARM嵌入式Linux系统构建与驱动开发范例[M].北京:北京航空航天大学出版社, 2006:310-332.
[8]宋宝华.Linux设备驱动开发详解[M].北京:人民邮电出版社, 2008:120-123.
[9]田鹤, 陈剑波.Linux平台PCI卡驱动程序的设计与实现[J].计算机工程, 2001, 27 (12) :141-143.
linux设备驱动 篇8
PCI是一种广泛采用的总线标准, 它提供了许多优于其它总线标准 (如EISA) 的新特性, 目前已经成为计算机系统中应用最为广泛, 并且最为通用的总线标准。Linux的内核能较好地支持PCI总线, 本论述基于PLX公司推出的PCI总线接口芯片PCI9052, 设计开发在Linux2.6内核下的设备驱动程序
1 P CI总线系统体系结构
PCI是外围设备互连 (Peripheral Component Interconnect) 的简称, 作为一种通用的总线接口标准, 它在目前的计算机系统中得到了非常广泛的应用。PCI提供了一组完整的总线接口规范, 其目的是描述如何将计算机系统中的外围设备以一种结构化和可控化的方式连接在一起, 同时它还刻画了外围设备在连接时的电气特性和行为规约, 并且详细定义了计算机系统中的各个不同部件之间应该如何正确地进行交互。
无论是在基于Intel芯片的PC机中, 或是在基于Alpha芯片的工作站上, PCI毫无疑问都是目前使用最广泛的一种总线接口标准。同旧式的ISA总线不同, PCI将计算机系统中的总线子系统与存储子系统完全地分开, CPU通过一块称为PCI桥 (PCI-Bridge) 的设备来完成同总线子系统的交互, 见图1所示。
由于使用了更高的时钟频率, 因此PCI总线能够获得比ISA总线更好的整体性能。PCI总线的时钟频率一般在25MHz到33MHz范围内, 有些甚至能够达到66MHz或者133MHz, 而在64位系统中则最高能达到266MHz。尽管目前PCI设备大多采用32位数据总线, 但PCI规范中已经给出了64位的扩展实现, 从而使PCI总线能够更好地实现平台无关性, 现在PCI总线已经能够用于IA-32、Alpha、Power PC、SPARC64和IA-64等体系结构中。
PCI总线具有三个非常显著的优点, 使得它能够完成最终取代ISA总线这一历史使命:
(1) 在计算机和外设间传输数据时具有更好的性能;
(2) 能够尽量独立于具体的平台;
(3) 可以很方便地实现即插即用。
2 Linux驱动架构
Linux将所有外部设备看成是一类特殊文件, 称之为“设备文件”, 如果说系统调用是Linux内核和应用程序之间的接口, 那么设备驱动程序则可以看成是Linux内核与外部设备之间的接口。设备驱动程序向应用程序屏蔽了硬件在实现上的细节, 使得应用程序可以像操作普通文件一样来操作外部设备。
Linux抽象了对硬件的处理, 所有的硬件设备都可以像普通文件一样来看待:它们可以使用和操作文件相同的、标准的系统调用接口来完成打开、关闭、读写和I/O控制操作, 而驱动程序的主要任务也就是要实现这些系统调用函数。Linux系统中的所有硬件设备都使用一个特殊的设备文件来表示, 例如, 系统中的第一个IDE硬盘使用/dev/hda表示。每个设备文件对应有两个设备号:一个是主设备号, 标识该设备的种类, 也标识了该设备所使用的驱动程序;另一个是次设备号, 标识使用同一设备驱动程序的不同硬件设备。设备文件的主设备号必须与设备驱动程序在登录该设备时申请的主设备号一致, 否则用户进程将无法访问到设备驱动程序。
在Linux操作系统下有两类主要的设备文件:一类是字符设备, 另一类则是块设备。字符设备是以字节为单位逐个进行I/O操作的设备, 在对字符设备发出读写请求时, 实际的硬件I/O紧接着就发生了, 一般来说字符设备中的缓存是可有可无的, 而且也不支持随机访问。块设备则是利用一块系统内存作为缓冲区, 当用户进程对设备进行读写请求时, 驱动程序先查看缓冲区中的内容, 如果缓冲区中的数据能满足用户的要求就返回相应的数据, 否则就调用相应的请求函数来进行实际的I/O操作。块设备主要是针对磁盘等慢速设备设计的, 其目的是避免耗费过多的CPU时间来等待操作的完成。一般说来, PCI卡通常都属于字符设备。
所有已经注册 (即已经加载了驱动程序) 的硬件设备的主设备号可以从/proc/devices文件中得到。使用mknod命令可以创建指定类型的设备文件, 同时为其分配相应的主设备号和次设备号。例如, 下面的命令:
将建立一个主设备号为6, 次设备号为0的字符设备文件/dev/lp0。当应用程序对某个设备文件进行系统调用时, Linux内核会根据该设备文件的设备类型和主设备号调用相应的驱动程序, 并从用户态进入到核心态, 再由驱动程序判断该设备的次设备号, 最终完成对相应硬件的操作。
3 设备驱动程序结构
Linux的设备驱动程序大致可以分为如下几个部分:驱动程序的注册与注销、设备的打开与释放、设备的读写操作、设备的控制操作、设备的中断和轮询处理。
3.1 驱动程序的注册与注销
向系统增加一个驱动程序意味着要赋予它一个主设备号, 这可通过在驱动程序的初始化过程中调用register_chrdev () 或者register_blkdev () 来完成。而关闭字符设备或者块设备时, 则需调用unregister_chrdev () 或unregister_blkdev () 从内核中注销设备, 同时释放占用的主设备号。
3.2 设备的打开与释放
打开设备是通过调用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, 则关闭设备。
3.3 设备的读写操作
字符设备的读写操作相对较简单, 直接使用函数read () 和write () 就可以了。但如果是块设备的话, 则需调用函数block_read () 和block_write () 来进行数据读写, 这两个函数将向设备请求表中增加读写请求, 以便Linux内核可以对请求顺序进行优化。由于是对内存缓冲区而不是直接对设备进行操作的, 因此能很大程度上加快读写速度。如果内存缓冲区中没有所要读入的数据, 或者需要执行写操作将数据写入设备, 那么就要执行真正的数据传输, 这是通过调用数据结构blk_dev_struct中的函数request_fn () 来完成的。
3.4 设备的控制操作
除了读写操作外, 应用程序有时还需要对设备进行控制, 这可以通过设备驱动程序中的函数ioctl () 来完成。ioctl () 的用法与具体设备密切关联, 因此需要根据设备的实际情况进行具体分析。
3.5 设备的中断和轮询处理
对于不支持中断的硬件设备, 读写时需要轮流查询设备状态, 以便决定是否继续进行数据传输。如果设备支持中断, 则可以按中断方式进行操作。
4 P CI驱动程序实现
PCI设备上有三种地址空间:PCI的I/O空间、PCI的存储空间和PCI的配置空间。CPU可以访问PCI设备上的所有地址空间, 其中I/O空间和存储空间提供给设备驱动程序使用, 而配置空间则由Linux内核中的PCI初始化代码使用。内核在启动时负责对所有PCI设备进行初始化, 配置好所有的PCI设备, 包括中断号以及I/O基址, 并在文件/proc/pci中列出所有找到的PCI设备, 以及这些设备的参数和属性。
Linux驱动程序通常使用结构 (struct) 来表示一种设备, 而结构体中的变量则代表某一具体设备, 该变量存放了与该设备相关的所有信息。好的驱动程序都应该能驱动多个同种设备, 每个设备之间用次设备号进行区分, 如果采用结构数据来代表所有能由该驱动程序驱动的设备, 那么就可以简单地使用数组下标来表示次设备号。
4.1 关键数据结构
在PCI驱动程序中, 下面几个关键数据结构起着非常核心的作用:
这个数据结构在文件include/linux/pci.h里, 这是Linux内核版本2.4之后为新型的PCI设备驱动程序所添加的, 其中最主要的是用于识别设备的id_table结构, 以及用于检测设备的函数probe () 和卸载设备的函数remove () 。
这个数据结构也在文件include/linux/pci.h里, 它详细描述了一个PCI设备几乎所有的硬件信息, 包括厂商ID、设备ID、各种资源等。
Linux中的I/O子系统向内核中的其他部分提供了一个统一的标准设备接口, 这是通过include/linux/fs.h中的数据结构file_operations来完成的。
4.2 P CI驱动程序基本架构
在用模块方式实现PCI设备驱动程序时, 通常至少要实现以下几个部分:初始化设备模块、设备打开模块、数据读写和控制模块、中断处理模块、设备释放模块、设备卸载模块。
下面给出本驱动程序的基本架构:
4.3 P CI驱动程序加载过程
系统启动后, 检测外部设备都有哪些, 当检测到PCI设备时, 就按照E2PROM中的信息初始化设备。加载驱动程序后, 首先向内核注册设备模块, 为其分配设备号并注册对外的接口函数, 设计中根据需要只注册了四个接口 (见图2) :设备打开 (open) , 设备关闭 (close) , 设备读 (read) , 设备写 (write) 。
当有多个相同设备时, 主设备号相同, 可根据次设备号进行区分, 用户可在加载驱动模块时分配, 驱动程序中建立设备数组来进行相同设备的区分存储。
探测函数取得初始化信息, 进行地址空间的分配, 然后使用pci_enable_device函数启用卡。
Linux将所有的设备都看作文件形式, 驱动程序模块加载到内核中以后, 将设备和文件对应起来, 用户程序以访问文件的形式对设备进行操作。
卸载模块时, 只要将驱动程序中申请的空间和设备号全部注销掉就可以了。
5 结束语
PCI总线不仅是目前应用广泛的计算机总线标准, 而且是一种兼容性最强、功能最全的计算机总线。而Linux作为一种新的操作系统, 其发展前景是无法估量的, 同时也为PCI总线与各种新型设备互连成为可能。由于Linux源码开放, 因此给连接到PCI总线上的任何设备编写驱动程序变得相对容易。本论述介绍如何编写Linux下的PCI驱动程序, 针对的内核版本是2.6。
参考文献
[1]Jonathan Corber, Alessandro Rubini Greg Kroah-Hartman, Linux设备驱动程序[M].北京:中国电力出版社, 2005.
[2]Neil Matthew, Richard Stones.Linux程序设计 (第三版) [M].北京:人民邮电出版社, 2007.
linux设备驱动 篇9
Linux自出现以来,以其开放源代码、较好的网络性能、精简高效的内核、较高的可定制性、支持多种体系结构等特性,被广泛地应用于嵌入式领域。在嵌入式领域中,可靠性被提高到了最重要的位置。而驱动程序是影响系统可靠性的最大隐患。Stanford大学的一项针对Linux内核的研究显示驱动程序出现BUG的频率比内核中其它部分的代码高出2~7倍[1]。传统的驱动程序与操作系统内核同驻于系统地址空间,拥有内核的所有权限,一旦驱动程序出现BUG,可能导致整个系统崩溃。在提高系统可靠性方面,目前已有的研究都专注于通过把有错误的设备驱动程序与内核隔离[2]。然而,这些研究都忽略了驱动程序可靠性问题中最重要的一个方面:在内核态编程要比在用户态编程困难得多,由于难以调试,因此也更容易出现未知的BUG,导致系统可靠性下降。
本文基于ARM平台,设计并实现了DM9000网卡的用户态驱动程序。与传统的网卡驱动程序相比,本设计由于将大部分代码外移到用户空间,从而大大提高了系统可靠性,同时也有着良好的性能,并且与现有的Linux内核相兼容。
1 用户态驱动程序体系结构
用户态驱动意味着将驱动程序代码转移到用户态运行,从而达到与内核地址空间隔离,提高系统可靠性的目的。目前对用户态驱动的研究主要有微内核系统和微软的UMDF[3]。这两种方法都无法在嵌入式Linux系统中实现。Microdriver[4,5]试图在传统内核态驱动和用户态驱动之间进行折衷,将一部分代码留在内核态以获得性能及兼容性。
Microdriver是性能和可靠性的折中方案,它根据性能相关和优先级分离驱动程序代码。Microdriver将传统的内核态驱动程序依据性能相关与否以及优先级,分离为运行于内核态的k-driver和运行于用户态的u-driver两个部分。它的体系结构如图1所示。其中实线是性能攸关代码执行路径,虚线是性能无关代码执行路径。
设备驱动的内核部分k-driver包括驱动中性能攸关和经常使用的功能模块,如中断处理和一些性能有关操作(如发送接收网络报文),用户态部分u-driver用单独的进程实现,供内核态模块调用。这两个模块一起提供了完整的传统驱动程序的功能。Microdriver的最大特点是与传统操作系统的体系结构兼容。
2 DM9000网卡用户态驱动的实现
综合性能、兼容性等因素,这里借鉴Microdriver的设计思想来实现DM9000网卡在ARM平台的用户态驱动。
2.1 功能模块划分
本设计根据需要划分为k-driver、u-driver、k-mod、u-lib四个模块,对每个模块介绍如下:
(1) k-driver,包括性能相关的热点代码和数据通道,例如输入/输出、中断处理函数、一些高优先级函数(软中断、tasklet和工作队列)等。k-driver以Linux的可加载动态模块(Loadable Kernel Module)的方式运行于内核态,以保证高性能。
(2) u-driver,包括驱动程序中一些与性能无关的非关键操作以及一些优先级较低的代码,例如设备初始化、设备配置、设备控制、错误处理等。u-driver以一个普通进程的形式运行于用户态,以保证充分的故障隔离。
(3) k-mod,注册为一个设备驱动程序并实现了字符设备接口用来与u-lib通信。它实现的功能包括与u-driver进行通信、追踪k-driver与u-driver间共享的数据结构等。
(4) u-lib,实现为一个链接到u-driver的多线程库,它面向u-driver的接口与k-mod面向内核的接口类似。它实现的功能包括请求k-driver提供服务、执行来自k-mod的函数调用请求等。
2.2 I/O访问方式
用户态的u-driver需要能够直接访问DM9000的寄存器或I/O端口。在传统的X86架构处理器平台,存在着两个相关的系统调用:iopl()和ioperm(),通过这两个系统调用即可允许调用进程获得访问设备对应的I/O端口或寄存器的权限。但在嵌入式系统常用的ARM平台,不存在这两个系统调用,但是,可以将设备的寄存器或端口映射到某段物理内存空间上。/dev/mem是物理内存的全映像[6],可以用来访问物理内存,那么只需将DM9000网卡的寄存器或内存映射到物理地址空间,根据映射的地址,使用mmap()系统调用,u-driver可以直接访问和操作DM9000网卡的寄存器[7]。
2.3 通信方案
k-driver和u-driver必须协同工作才能完成传统的内核态驱动的功能。内核态和用户态的运行库提供了这一服务。运行于内核态的k-mod注册为一个设备驱动程序并实现了字符设备接口。运行于用户态的u-lib实现为一个链接到k-driver的多线程库。本设计采用ioctl()系统调用机制,实现k-mod和u-lib之间的通信。图2为本设计所采用的通信方案流程图,其中实线为数据路径,虚线为控制路径。
k-driver调用u-driver中的函数时,通过调用k-mod来传送请求给u-driver,u-driver预分配一个缓冲区来接收数据,缓冲区的大小设置为u-driver与k-driver能传输的数据的上限,接下来,k-mod激活在内核中等待的主线程,复制数据到u-driver地址空间中的缓冲区,并通知u-lib调用u-driver中正确的函数。收到来自k-driver的请求之后,u-lib发送请求给一个工作线程,并回复内核等待接下来的请求。
u-driver也可以向下调用,不论是调用k-driver中的函数还是内核中的其它函数,都可以通过ioctl()系统调用来实现,k-mod中的一个ioctl管理器收到请求并调用合适的内核函数。u-driver向下调用的请求主要有以下几种[8]:(1)VIRT2PHY,计算u-driver模块中虚拟地址对应的物理地址;(2)UP_INFO,向内核空间提交当前用户态驱动进程信息;(3)DMA_PHY,获取k-driver中DMA缓冲区的物理地址;(4)OPEN、CLOSE等系统调用。
将硬件产生的中断通知给用户程序,这里通过将中断映射到文件描述符来实现这一功能。在Linux系统的/proc文件系统下,每一个中断都对应一个目录[9],目录下包含一些属性文件。通过在中断号对应的目录下增加一个新的属性文件,用来标识是否产生了该中断号对应的中断。当u-driver对该文件执行read()操作时,内核在对应的信号量上执行down()操作,阻塞该read()操作直到中断的产生。硬件产生中断时,首先在内核中断处理函数中屏蔽该中断,然后增加中断映射的文件描述符中的计数,最后在对应的信号量上执行up()操作,使之前阻塞的read()操作返回,返回值为文件描述符中的中断计数,如果返回值大于0,说明产生了硬件中断,则转而去执行u-driver中定义的中断处理函数。
2.4 DMA缓冲区分配方案
为减少数据拷贝次数,u-driver要能够直接访问DMA缓冲区的内容,即实现用户态的DMA缓冲区[10]。这可以通过Linux内核的设备文件/dev/mem和mmap()系统调用来实现。通过open()系统调用打开/dev/mem文件,接着,把k-driver申请的内存的物理地址传送到用户空间,最后通过mmap()将这段物理内存映射到当前进程的地址空间。其中,分配共享内存的关键代码如下:
kaddr = kmalloc(size);
phy_addr = addr – PAGE_OFFSET;
fd = open(“/dev/mem”, O_RDWR);
uaddr = mmap(phy_addr, length, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
图3为本设计中所采用的DMA缓冲区分配方案的数据拷贝路径图,其中实线是本设计方案的数据拷贝路径,虚线是传统内核态驱动程序的数据拷贝路径。
3 测试
3.1 开发环境搭建
嵌入式开发一般采用宿主机/目标机模式。本设计的开发环境为:宿主机为PC机,网卡为Intel 82579LM千兆网卡,安装Debian 6.0系统,内核版本为2.6.32.5,目标机为S3C2440开发板,网卡为DM9000,在该目标机上移植了2.6.32.2版本的Linux内核,宿主机和目标机之间采用串口和以太网连接,便于调试。
3.2 测试方法
为了衡量用户态驱动对性能的影响,这里使用netperf工具集测试Linux内核自带驱动和本设计实现的用户态驱动程序的性能(吞吐量和CPU占用率)。在目标机分别安装两种驱动程序,在宿主机与目标机之间测试TCP吞吐量。所有的netperf测试都使用默认的接收和发送缓存大小,即接收为87,380字节,发送为16,384字节。测试界面如图4所示。
3.3 测试结果
网络吞吐量如表1所示,目标机作为client,宿主机运行netserver。
CPU占用率如表2所示,统计了作为client端的S3C2440 CPU的占用情况。
测试结果表明了本设计实现的用户态驱动程序对性能的影响十分微小,网络吞吐量与内核态驱动基本相当,CPU占用率方面略有上升,影响不大,达到了预期效果。
4 结束语
Linux作为目前在嵌入式领域使用最为广泛的操作系统,进一步提高其可靠性是目前的研究热点。本文从工程应用出发,研究并设计了ARM平台网卡的用户态驱动程序。实验证明本文设计的用户态驱动程序完全能满足性能要求,同时有效地减少了运行于内核态的驱动代码,大大提高了整个系统的可靠性。通过设计该驱动程序,总结出一种在嵌入式Linux操作系统下编写用户态驱动程序的方法,对类似的其他设备的用户态驱动开发有很大的借鉴意义。
摘要:为了进一步提高嵌入式Linux系统的可靠性,设计和实现了DM9000网卡在ARM平台的用户态驱动程序。通过将影响I/O性能的数据处理操作留在内核态全速运行,而将管理操作例如初始化和配置运行在速度相对较慢的用户态,从而获得了较高的性能和兼容性。实验结果证明,文中设计实现的用户态驱动程序能够满足实际应用的性能需求,同时降低了内核态的代码量,达到了提高整个系统可靠性的目的。
关键词:嵌入式系统,Linux,驱动程序,可靠性
参考文献
[1]颜跃进,秦莹,孔金珠,等.操作系统设备驱动可靠性研究综述[J].计算机工程与科学,2009,31(5):121-125.
[2]Swift M M,Bershad B N,Levy H M.Improving the reliability ofcommodity operating systems[J].ACM TOCS,2005,23(1).
[3]Microsoft.Architecture of the user-mode driver framework[Z].2006.Version 0.7.
[4]Ganapathy V,Renzelmann M J,Balakrishnan A,et al.The designand implementation of microdrivers[J].ASPLOS'08,ACM,2008:168-178.
[5]Ganapathy V,Balakrishnan A,Swift M M,et al.Microdrivers:ANew Architecture for Device Drivers[J].Proc.Of the 11th USE-NIX Workshop on Hot Topics in Operating Systems,USENIX Asso-ciation,2007:1-6.
[6]Corbet J,Rubini A,Kroah-Hartman G.Linux Device Driver[M].O'Reilly Media,2005.
[7]DM9000A Ethernet Controller with General Processor Interface DataSheet[M].Davicom semiconductor Inc,2005.
[8]刘军卫,李曦,陈香兰,等.用户态驱动框架的研究与实现[J].计算机系统应用,2011,20(11):67-71,90.
[9]Love R.Linux Kernel Development[M].机械工业出版社,2011.
linux设备驱动 篇10
随着电子技术的快速发展, 特别是大规模集成电路的产生而出现的微型机, 使现代科学研究得到了质的飞跃, 而嵌入式微控制器技术的出现则是给现代工业控制领域带来了一次新的技术革命。嵌入式Linux 在近几年发展迅速, 它没有昂贵的版权费, 完全开放源代码, 具有可裁减性与可移植性, 是开发嵌入式产品的优秀操作系统平台。设备驱动程序是Linux内核的重要组成部分, 运行在Linux内核底层, 在内核源代码中占有很大比例, 驱动程序开发逐渐成为嵌入式软件开发中一项重要的工作。应项目设计的需要, 我们完成了嵌入式Linux下的信号发生模块的驱动程序设计。
2 Linux下设备驱动程序
驱动程序从字面上可以理解为一类程序, 这类程序的目的一般是驱动硬件正常工作, 所以通常所说的驱动程序都是针对特定的硬件来编写的。Linux设备驱动程序是为特定的硬件提供给用户程序的一组标准化接口, 它隐藏了设备工作的细节。设备驱动程序从总体上看分为两部分:驱动程序与操作系统内核的接口、驱动程序与设备的接口。其代码结构大致可以分为如下几个部分:驱动程序的注册与注销、设备的打开与释放、设备的读写操作、设备的控制操作、设备的中断和轮询处理。Linux系统中, 用户对设备的操作需要Linux系统中虚拟文件系统的支持, 用户采用标准的文件操作访问设备, 虚拟文件系统将用户的这种文件访问转化成对驱动程序的调用。为了实现这种用户文件操作到设备操作的转换, 虚拟文件系统为设备驱动提供了一个标准化的文件操作实现接口, 并由file_operations结构定义。
Linux系统的设备分为三种类型, 即字符设备、块设备和网络设备。本文所介绍的信号发生模块驱动属于字符型设备驱动程序。在对字符设备发出读、写请求时, 实际的硬件I/O一般就紧接着发生了。应用程序可以用与存取文件相同的系统调用来打开、读写及关闭。字符设备驱动程序一般要包含open、close、read、write等几个系统调用。
Linux下, 设备文件使用主设备号和次设备号来标识。主设备号指明对应哪些设备驱动, 次设备号区分被一个设备驱动控制下的某个独立的设备。在启动设备驱动程序之前, 需要先注册到内核上, Linux下注册设备驱动程序就是在内核上注册file_operations结构体的变量。为了在内核上注册或注销file_operations结构体, 一般使用下面两个函数。注册函数定义为:
类似地, 字符设备注销函数定义为:
int unregister_chrdev (unsigned int major, const char *name)
其中, major是主设备号, name是设备名称, 设备是以文件的形式存在的, 设备注册时需要使用文件结构struct file_operations定义。
3 信号发生模块驱动程序设计
3.1 信号发生模块硬件电路
信号发生模块的核心器件是12位四路电压输出数模转换器DAC7725U。在计算机的测控系统中, 常要用到模拟输出, 数模转换器 (DAC) 就是一种将数字信号转换成模拟电信号的器件。DAC根据输入数据的格式一般分为并行和串行两种, 并行的DAC通常有8位、10位、12位和16位等。DAC7725U为并行的12位数模转换器, 输出电压范围为-10~+10 V, 可输出四路电压。它还支持双缓冲数据输入, 使数据输入更加快速。DAC7725U的运行是由主处理器OMAP-L137控制的, 它们之间的电路连接关系如图1所示。
在项目设计里需要产生一个能让整个系统稳定有序运行的控制信号, 这就需要由信号发生模块驱动程序来实现。当核心处理器OMAP-L137收到一个由键盘模块发送来的开始命令后, 系统软件会自动做出反应, 启动系统内的一个信号发生程序。该程序完成一系列必要的初始化动作后, 通过调用DAC7725U驱动的相关操作, 使DAC7725U芯片输出一个方波, 该方波的占空比和幅度都可通过应用程序根据算法需求加以控制调节。
3.2 信号发生模块驱动程序的设计与实现
信号发生电路主要是由数模转换芯片DAC7725U发出模拟信号, 控制激光器按照一定规律工作。因此, 要控制信号发生电路, 其实就是控制DAC7725U芯片。本驱动的功能就是要实现OMAP-L137主处理器对DAC7725U芯片的基本控制并为上层应用程序提供接口。
3.2.1 信号发生模块驱动程序的文件操作
在信号发生模块的驱动程序里, 对struct file_operation结构声明如下所示:
3.2.2 信号发生模块驱动程序的加载
驱动程序的加载方式有两种:一种是将其作为内核的一部分, 直接编译到内核中, 即静态编译;另外一种是单独作为一个模块编译, 在需要时再动态地把它加载入内核, 不需要时从内核中删除, 即动态连接。在这里我们采用加载模块的方式进行。
信号发生模块驱动程序的模块加载功能由模块加载函数和模块卸载函数来完成, 其中模块加载函数负责调用SM_init () 函数, 并根据返回值判断调用情况, 模块卸载函数则负责卸载信号发生模块的驱动程序, 并使D/A转换器输出最小值。代码如下:
SM_init () 在模块加载函数中被调用, 该函数主要完成信号发生模块驱动程序的系统注册、设备号申请、文件操作结构声明等工作。此外, 它还通过ioremap () 函数实现I/O地址的映射, 使嵌入式处理器可以方便地控制D/A转换器。
3.2.3 信号发生模块的写模块
由于信号发生模块的输入数据是从嵌入式处理器一端取得, 经过转换后输出模拟信号, 所以对应的驱动程序仅需实现写函数, 将需要转换的数字信号 (二进制码) 传入D/A转换器中。
3.2.4 信号发生模块的打开、退出模块
信号发生模块的打开、退出模块用于打开和关闭驱动程序。SM_open所做的工作非常简单, 就是增加模块使用的计数值;当调用信号发生模块的关闭函数时, 驱动程序向D/A转换设备发送数据0x0000, 使D/A转换输出最小值零, 并将模块使用计数值减一, 表明使用结束。
4 信号发生模块的测试程序设计
信号发生模块的驱动程序就是让嵌入式处理器将数字信号按照一定的时序规律发送给D/A转换器, 并得到正确的数/模转换结果。为了检验驱动程序的正确性, 就需要设计相应的测试程序。信号发生器的转换中经常使用方波信号和正弦波信号, 由于在工程项目中, 需要输出方波信号, 在本文中就以输出方波信号作为例子。其流程如图2所示:
其中, main () 函数首先调用open () 函数, 完成设备文件的打开, 然后调用信号发生模块的功能函数, 来完成用户期望D/A的操作, 在完成D/A转换功能后, 程序再返回该主函数关闭设备文件;功能函数sm_func () 是完成对方波生成函数的调用;功能打印函数主要负责实现人机交互功能, 通过打印功能选择界面, 让用户根据自己的实际需求选择D/A转换操作, 并调用对应的D/A转换函数;方波生成函数先定义方波信号的高电平二进制码0xfffe, 然后将该数字信号写入信号发生模块, 延时一段时间后, 定义方波信号的低电平0x0000, 再将数字信号写入信号发生模块, 周期性地重复此过程就可以在模拟输出端得到方波信号, 其代码如下:
运行测试程序, 连接示波器到信号发生电路的输出端, 可以看到所要求的方波波形 (如图3所示) , 通过结果分析, 完成了信号发生模块驱动程序的设计要求。
5 结束语
在编写完驱动程序后, 我们将该驱动程序以加载模块的方式进行编译, 在检查相关硬件完好后, 正确连接相关器件, 将编译好的信号发生模块的设备驱动下载到目标板上, 通过测试设备驱动的应用程序, 实现了设备的打开、关闭、内存映射和写等具体的操作。通过本次驱动程序的设计与实现, 完成了信号发生模块的输出, 编译此程序后在目标板上运行, 在示波器上显示输出了所需要的方波波形。这就完成了驱动程序的设计要求, 为接下来的开发奠定了基础。
通过本文, 我们了解了嵌入式Linux下的设备驱动程序设计的基本框架和步骤, 并学习了信号发生模块的设备驱动在Linux内核中的具体实现, 通过分析Linux下信号发生模块的驱动程序的设计与测试, 我们可以对Linux的驱动程序的结构、编写以及测试有一定的了解, 为以后更进一步地学习奠定了基础。
参考文献
[1]李亚峰, 欧文盛.ARM嵌入式Linux系统开发从入门到精通[M].北京:清华大学出版社, 2007.
[2]李新峰, 何广生, 赵秀文.基于ARM9的嵌入式Linux开发技术[M].北京:电子工业出版社, 2008.
[3]孙天泽, 袁文菊.嵌入式设计及Linux驱动开发指南[M].第2版.北京:电子工业出版社, 2007.
[4]冯国进.嵌入式Linux驱动程序设计从入门到精通[M].北京:清华大学出版社, 2008.