概述
设备驱动在本质上就是一种软件程序,上层软件可以在不用了解硬件特性的情况下,通过设备驱动提供的接口和计算机硬件进行通信。
为方便我们加入各种驱动以支持不同硬件,内核抽象出了很多层次结构,这些层次结构是设备驱动的上层。它们抽象出各种驱动接口,相应驱动只需要
填写相应的回调函数,就能很容易把新的驱动加入到Linux内核。
一般来说,设备驱动可以分为下面3类
(1)块设备驱动(block device driver)
块设备的读写都有缓存支持,并且块设备必须能够随机存取。块设备驱动的I/O操作都是通过系统大小缓存来实现的。块设备驱动主要用于磁盘驱动器。
(2)字符设备驱动
字符设备的I/O操作没有通过缓存,字符设备操作以字节为基础,但不是说一次只能执行一个字节操作。例如对于字符设备我们可以通过mmap一次进行
大量数据交换。字符设备实现比较简单和灵活,很多驱动都用的是字符设备。
(3)网络设备驱动
网络设备在Linux里做专门的处理。Linux的网络系统主要是基于BSD UNIX的Socket机制。网络设备驱动为网络操作提供接口,管理网络数据的接送和收发。
为了屏蔽网络环境中物理网络设备的多样性,Linux对所有的物理设备进行抽象并定义一个统一的概念,称之为接口。所有对网络硬件的访问都是通过接口
进行的,接口对上层协议提供一致化的操作集合来处理基本数据的发送和接收,对下层屏蔽硬件差异。它与字符设备及块设备不同之处其一就是网络接口不
存在于Linux的设备文件系统/dev/中。
除了这3类设备驱动,还有一种就是虚拟设备驱动。这种驱动不控制具体硬件。它用来模拟一种硬件功能,特别是在虚拟化环境中。与字符设备和块设备驱动
一样,虚拟设备驱动利用了设备驱动接口。不一样在于虚拟设备驱动不对具体硬件进行操作。
模块机制与"Hello World"
设备模型
设备模型支持如下功能:
电源管理。与用户空间通信。支持热插拔设备。设备分类。对象生命周期
设备模型的经济基础
设备模型可以粗略划分为两个层次:总线、设备、驱动3个数据结构构成了设备模型的上层建筑,通常开发设备驱动时接触的就是这3个概念;kobject、kset、
kobj_type这3个数据结构组成了设备模型的经济基础,经济基础决定了上层建筑,它们也决定了总线、设备和驱动的组织关系。
1.kobject结构
kobject是组成设备模型的最小单元,也是构成设备模型的核心结构。在内核文档中有一个专门的文件Documentation\kobject.txt进行介绍。
设备模型的首要任务在于把设备连在一起,形成一个清晰明朗的树状结构。对于设备而言,虽然设备的种类千奇百怪,但是有一些属性是大家都要有的,
比如引用计数、设备上锁、设备名称、用来形成树状结构的链表指针等。于是人们把这些公共的东西都放在一个数据结构里,便于管理,这个数据结构就叫做
kobject。
kobject,顾名思义是kernel对象,这是用C语言实现了面向对象的继承。kobject就类似抽象的基类,本身没有什么特殊作用。它只被嵌入到更大的对象中,
比如bus、devices、drivers等数据结构,这些容器就是通过kobject连接起来了,形成了一个树状结构。而且给定kobject,我们可以通过一个叫container_of
的宏,来反查它所在的数据结构。
2.kobj_type 结构
ktype是具有相同操作的kobject的集合。它负责管理这一类kobject在sysfs下的操作(show,store)。
3.kset结构
kset,顾名思义,乃是kobject的集合。kobject通过kset组织成层次化的结构,kset是具有相同类型的kobject的集合。kobject相当于叶子节点,kset类似于内节点,
两者连接形成了一个树状结构。
kobj_type结构关注的是对象的类型,而kset结构关心的是对象的集合,可认为kset是kobject的顶层容器类。每个kset在内部包含自己的kobject,并可以用多种处理
kobject的方法处理kset。kset总是在sysfs中出现。一旦设置了kset并把它添加到系统中,将在sysfs中创建一个目录。kobject不必在sysfs中表示,但kset中的每一个
kobject成员都在sysfs中得到表述。
增加kobject到kset中去,通常是在kobject创建时完成,其过程分为3个步骤。
(1)完成kobject的初始化,特别注意entry、parent的初始化。
(2)把kobject的kset成员指向目标kset。
(3)将kobject传递给kobject_add函数。
kset最重要的是建立上层(subsystem)和下层的(kobject)的关联性。kobject也会利用它分辨自己是属于哪一个类型,然后在/sys下建立正确的目录位置。而kset的
优先权比较高,kobject会利用自己的kobject结构中的kset找到自己所属的kset,并把kobj_type指定成该kset下的kobj_type。除非没有定义kset,才会用kobj_type
来建立关系。kobject通过kset组织成层次化的结构。kset是具有相同类型的kobject的集合,在内核中用kset数据结构表示。
设备模型的上层建筑
设备模型的上层建筑由建筑、设备、驱动这3个数据结构构成,设备模型表示了它们之间的连接关系。
总线是处理器和一个或多个设备之间的通道。在设备模型里面,所有的设备都通过总线连接。总线可以是物理存在的,也可以是虚拟的。即使有些设备并没有连接到
一根物理上的总线上,我们也会为其设置一个内部的、虚拟的“平台”总线,来维持总线、设备、驱动的关系。内核中对应的结构为struct bus_type。
设备是连接到某条物理或者虚拟总线上的对象。可能是真正物理对象,也可能是虚拟对象。
驱动是用来和设备通信的软件程序。驱动可以从设备中获取数据,也可以把相应数据发给设备进行处理。驱动一般都是和具体操作系统以及具体硬件相关的。内核
中对应的结构为struct device_driver。
类(Class)与类设备(class_device)
类是一个设备的高层视图,它抽象出了底层的实现细节,从而允许用户空间使用设备所提供的功能,而不用关心设备是如何连接和工作的。类成员通常由上层代码所
控制,而无需驱动的明确支持。但有些情况下驱动也需要直接处理类。
几乎所有的类都显示在/sys/class目录中。处于历史的原因,有一个例外:块设备显示在/sys/block目录中。在许多情况,类子系统是向用户空间导出信息的最好方法。
当类子系统创建一个类时,它将完全拥有这个类,根本不用担心哪个模块拥有那些属性,而且信息的表示也比较友好。
为了管理类,驱动程序核心导出了一些接口,其目的之一是提供包含设备号的属性以便自动创建设备节点,所以udev的使用离不开类。类函数和结构与设备模型的
其他部分遵循相同的模式,所以真正的概念是很少的。
sysfs文件系统
内核系统Documentation\filesystems\sysfs.txt中写道:“sysfs是一个虚拟文件系统(类似proc文件系统),用于将系统中的设备组成层次结构,并向用户模式程序
提供详细的内核数据结构信息。”
sysfs提供两部分功能:一个就是把驱动和设备相应的信息提供给用户空间,另一个就是提供接口给用户空间来驱动和设备进行配置。
sysfs与/sys
sysfs文件系统为我们提供了kobject对象层次结构的视图,它被用来表示kobject。kobject的属性以及kobject之间的相互关系,帮助用户可以用一个简单文件
系统的方式来观察系统中各种设备的拓扑结构。借助属性对象,kobject可用导出文件的方式,将内核变量提供给用户读取或写入。
sysfs目录项sysfs_dirent
sysfs的文件系统的所读写的信息是存放在kobject当中,那么dentry是如何与kobject联系起来的呢?是通过sysfs_dirent。
dirent=directory_entry(目录实体)。每一个dentry对应了一个dirent结构,dentry->d_fsdata是一个void的指针,由它指向不同的文件系统所特有dirent结构,
在sysfs中,则指向sysfs_dirent。
sysfs目录和属性
内核提供了一些接口给我们来添加和删除kobject,从而在sysfs目录下建立和删除相应的目录。kobject在sysfs中的目录位置取决于kobject在对象层次结构
中的位置。如果kobject的父指针被设置,那么在sysfs中kobject将被映射为其父目录下的子目录。如果parent没有设备,那么kobject将被映射到kset->kobj
中的子目录。两者都未设置,映射为sysfs下的根级目录。
USB子系统与USB驱动
USB协议基础
这棵USB大树主要包括了USB连接,USB Host Controller(USB主机控制器)和USB Device(USB设备)三个部分。而USB设备还包括了Hub和功能设备
USB驱动程序
USB驱动可以分为主机端驱动和设备端驱动。
1.USB主机端驱动
USB主机端驱动可以分为主机控制器和主机端设备驱动。主机控制器驱动主要是和主机控制器打交道,完成对硬件的操作。设备驱动是针对具体设备的驱动
程序。比如对于USB耳机,在主机端就要有设备驱动程序来驱动。这里我们只介绍设备驱动程序。
对USB驱动程序来说,最重要的一个概念就是urb。
一个urb包含了完成一个USB传输需要的相关信息。
执行urb是一个异步的过程,也就是usb_submit_urb是把当前请求的urb放到一个请求队列,然后马上返回。
放到请求队列的urb能随时通过调用usb_unlink_urb取消此次请求。
每个urb包括一个完成回调函数。当URB请求成功完成或者被取消后,此回调函数都会被调用。
设备的每个端点都支持请求队列。请求队列会在上一个请求完成之后开始下一个请求,这样可以最大限度地利用USB带宽。
另一个重要的概念就是管道。一个USB管道是驱动程序的一个数据缓冲区与一个外设端点的连接,它代表了一种在两者之间移动数据的能力。一旦设备被配置,
管道就存在了。该变量的初始化必须由USB主机端设备驱动程序来完成。
一个urb的典型生命周期如下。
由USB设备驱动创建。
安排给一个特定USB设备的特定端点。
由USB设备驱动程序提交给USB核心。
由USB核心提交到设备连接的主机控制器驱动。
被USB主机控制器处理,通过USB总线传送到设备。
当urb完成,USB主机控制器驱动通过complete回调函数通知USB设备驱动。当我们想完成USB主机和USB设备之间通信的时候,通常我们要先创建一个urb,然后
给该urb赋值。usb_alloc_urb函数为创建urb的接口。
2.USB设备端驱动程序
USB设备端驱动包括外设控制器驱动和USB器件驱动。
USB器件驱动主要是针对嵌入式应用。嵌入式设备可以和PC或者其他形式的主机用USB接口进行通信。此时设备端的USB驱动我们可以称为器件驱动。在USB
协议中,主机端USB设备驱动是主,设备端USB器件驱动是从。
从软件层次结构看来,设备端的USB驱动可以分为3层。USB控制器驱动程序、USB器件驱动程序和更上层接口。
USB控制器驱动程序直接和硬件打交道,通过寄存器、FIFO、DMA、中断等来完成USB数据交换。我们又把设备端的USB控制器称为外设控制器。外设控制器
驱动程序中会提供很多接口给USB器件驱动程序,具体接口在include/linux/usb_gadget.h文件中定义。同时USB控制器驱动程序又会通过一些回调函数来和USB
器件驱动程序完成交互。
USB器件驱动程序是USB控制器驱动程序的上层。USB器件驱动程序主要负责:
通过ep0处理建立连接请求。
返回设备的配置和各种描述。
设备或者重新设置配置和可选接口设置,包括使能和配置端点。
处理各种事件,包括USB suspend/resume,远程唤醒,断开和主机连接。
处理IN/OUT传输。
除了USB控制器驱动程序和USB器件驱动程序,USB设备端驱动还要和更上层的Linux各种框架打交道。我们把这层称为更上层。这层是用来处理由器件驱动程序
传过来的数据,或者发数据给器件驱动程序来处理。
需要注意的是对于设备端驱动来说,没有类似于USB core的中间层。主机端需要USB core这层来抽象各种接口,使我们更容易来写主机端驱动。设备端驱动不需要
这种中间层。