分类: LINUX
2010-04-22 19:57:51
第20章 USB主机与设备驱动
在Linux系统中,提供了主机侧和设备侧视角的USB驱动框架,本章主要讲解从主机侧角度看到的USB主机控制器驱动和设备驱动。
20.1节给出了Linux系统中USB驱动的整体视图,讲解了Linux中主机侧和设备侧角度的USB驱动层次。
从主机侧的角度而言,需要编写的USB驱动程序包括主机控制器驱动和设备驱动两类,USB主机控制器驱动程序控制插入其中的USB设备,而USB设 备驱动程序控制该设备如何作为从设备与主机通信。本章20.2节分析了USB主机控制器驱动的结构并给出实例,20.3节讲解了USB设备驱动的结构及其 设备请求块处理过程,并分析了USB设备驱动的骨架程序,20.4节则给出了Linux设备驱动的实例。
20.1节与20.2~20.4节是整体与部分的关系,20.2节与20.3~20.4节是并列关系。
20.1 Linux USB驱动层次
20.1.1 主机侧与设备侧USB驱动
USB采用树形拓扑结构,主机侧和设备侧的USB控制器分别称为主机控制器(Host Controller)和USB设备控制器(UDC),每条总线上只有一个主机控制器,负责协调主机和设备间的通信,而设备不能主动向主机发送任何消息。 如图20.1所示,在Linux系统中,USB驱动可以从两个角度去观察,一个角度是主机侧,一个角度是设备侧。
图20.1 Linux USB 驱动总体结构 |
如图20.1的左侧所示,从主机侧的观念去看,在Linux驱动中,USB驱动处于最底层的是USB主机控制器硬件,在其之上运行的是USB主机控 制器驱动,主机控制器之上为USB核心层,再上层为USB设备驱动层(插入主机上的U盘、鼠标、USB转串口等设备驱动)。因此,在主机侧的层次结构中, 要实现的USB驱动包括两类:USB主机控制器驱动和USB设备驱动,前者控制插入其中的USB设备,后者控制USB设备如何与主机通信。Linux内核 USB核心负责USB驱动管理和协议处理的主要工作。主机控制器驱动和设备驱动之间的USB核心非常重要,其功能包括:通过定义一些数据结构、宏和功能函 数,向上为设备驱动提供编程接口,向下为USB主机控制器驱动提供编程接口;通过全局变量维护整个系统的USB设备信息;完成设备热插拔控制、总线数据传 输控制等。
如图20.1的右侧所示,Linux内核中USB设备侧驱动程序分为3个层次:UDC驱动程序、Gadget API和Gadget驱动程序。UDC驱动程序直接访问硬件,控制USB设备和主机间的底层通信,向上层提供与硬件相关操作的回调函数。当前Gadget API是UDC驱动程序回调函数的简单包装。Gadget驱动程序具体控制USB设备功能的实现,使设备表现出“网络连接”、“打印机”或“USB Mass Storage”等特性,它使用Gadget API控制UDC实现上述功能。Gadget API把下层的UDC驱动程序和上层的Gadget驱动程序隔离开,使得在Linux系统中编写USB设备侧驱动程序时能够把功能的实现和底层通信分离。
本章将重点讲解从主机侧角度看到的USB主机控制器驱动与USB设备驱动,关于设备侧的Linux驱动,将不会详细讲解。
在USB设备的逻辑组织中,包含设备、配置、接口和端点4个层次。
每个USB设备都提供了不同级别的配置信息,可以包含一个或多个配置,不同的配置使设备表现出不同的功能组合(在探测/连接期间需从其中选定一 个),配置由多个接口组成。
在USB协议中,接口由多个端点组成,代表一个基本的功能,是USB设备驱动程序控制的对象,一个功能复杂的USB设备可以具有多个接口。每个配置 中可以有多个接口,而设备接口是端点的汇集(collection)。例如USB扬声器可以包含一个音频接口以及对旋钮和按钮的接口。一个配置中的所有接 口可以同时有效,并可被不同的驱动程序连接。每个接口可以有备用接口,以提供不同质量的服务参数。
端点是USB通信的最基本形式,每一个USB设备接口在主机看来就是一个端点的集合。主机只能通过端点与设备进行通信,以使用设备的功能。在USB 系统中每一个端点都有惟一的地址,这是由设备地址和端点号给出的。每个端点都有一定的属性,其中包括传输方式、总线访问频率、带宽、端点号和数据包的最大 容量等。一个USB端点只能在一个方向承载数据,或者从主机到设备(称为输出端点),或者从设备到主机(称为输入端点),因此端点可看作一个单向的管道。 端点0通常为控制端点,用于设备初始化参数等。只要设备连接到USB上并且上电端点0就可以被访问。端点1、2等一般用作数据端点,存放主机与设备间往来 的数据。\
总体而言,USB设备非常复杂,由许多不同的逻辑单元组成,如图20.2所示,这些单元之间的关系如下:
图20.2 USB设备、配置、接口和端点 |
l 设备通常有一个或多个配置;
l 配置通常有一个或多个接口;
l 接口通常有一个或多个设置;
l 接口有零或多个端点。
这种层次化配置信息在设备中通过一组标准的描述符来描述,如下所示。
l 设备描述符:关于设备的通用信息,如供应商ID、产品ID和修订ID,支持的设备类、子类和适用的协议以及默认端点的最大包大小等。在 Linux内核中,USB设备用usb_device结构体来描述,USB设备描述符定义为usb_device_descriptor结构体,如代码清 单20.1所示。
代码清单20.1 usb_device_descriptor结构体
1 struct usb_device_descriptor |
l 配置描述符:此配置中的接口数、支持的挂起和恢复能力以及功率要求。USB配置在内核中使用usb_host_config结构体描述,USB 配置描述符定义为结构体usb_config_descriptor,如代码清单20.2所示。
代码清单20.2 usb_config_descriptor结构体
1 struct usb_config_descriptor |
l 接口描述符:接口类、子类和适用的协议,接口备用配置的数目和端点数目。USB接口在内核中使用usb_interface结构体描述,USB 接口描述符定义为结构体usb_interface_descriptor,如代码清单20.3所示。
代码清单20.3 usb_interface_descriptor结构体
1 struct usb_interface_descriptor |
l 端点描述符:端点地址、方向和类型,支持的最大包大小,如果是中断类型的端点则还包括轮询频率。在Linux内核中,USB端点使用 usb_host_endpoint结构体来描述,USB端点描述符定义为usb_endpoint_descriptor结构体,如代码清单20.4所 示。
代码清单20.4 usb_endpoint_descriptor结构体
1 struct usb_endpoint_descriptor |
l 字符串描述符:在其他描述符中会为某些字段提供字符串索引,它们可被用来检索描述性字符串,可以以多种语言形式提供。字符串描述符是可选的,有 的设备有,有的设备没有,字符串描述符对应于usb_string_descriptor结构体,如代码清单20.5所示。
代码清单20.5 usb_string_descriptor结构体
1 struct usb_string_descriptor |
例如,笔者在运行Linux 2.6.15.5的系统上插入一个SanDisk U盘后,通过lsusb命令得到这个U盘相关的描述符,从中可以显示这个U盘包含了一个设备描述符、一个配置描述符、一个接口描述符以及批量输入和批量输 出两个端点描述符。呈现出来的信息内容直接对应于usb_device_descriptor、usb_config_descriptor、 usb_interface_descriptor、usb_endpoint_descriptor、usb_string_descriptor结构 体,如下所示:
Bus 001 Device 004: ID 0781:5151 SanDisk Corp. |
20.2.1 USB主机驱动的整体结构
USB主机控制器有3种规格:OHCI (Open Host Controller Interface)、UHCI (Universal Host Controller Interface) 和EHCI (Enhanced Host Controller Interface)。OHCI驱动程序用来为非PC 系统上以及带有SiS和ALi芯片组的 PC 主板上的USB芯片提供支持。UHCI驱动程序多用来为大多数其他PC主板(包括 Intel和Via)上的USB芯片提供支持。EHCI由USB 2.0规范所提出,它兼容于OHCI 和UHCI。UHCI的硬件线路比OHCI简单,所以成本较低,但需要较复杂的驱动程序,CPU负荷稍重。本节将重点介绍嵌入式系统中常用的OHCI主机 控制器驱动。
1.主机控制器驱动
在Linux内核中,用usb_hcd结构体描述USB主机控制器驱动,它包含USB主机控制器的“家务”信息、硬件资源、状态描述和用于操作主机 控制器的hc_driver等,其定义如代码清单20.6所示。
代码清单20.6 usb_hcd结构体
1 struct usb_hcd |
usb_hcd中的hc_driver成员非常重要,它包含具体的用于操作主机控制器的钩子函数,其定义如代码清单20.7所示。
代码清单20.7 hc_driver结构体
1 struct hc_driver |
在Linux内核中,使用如下函数来创建HCD:
struct usb_hcd *usb_create_hcd (const struct hc_driver *driver, |
如下函数被用来增加和移除HCD:
int usb_add_hcd(struct usb_hcd *hcd, |
2.OHCI主机控制器驱动
OHCI HCD驱动属于HCD驱动的实例,它定义了一个ohci_hcd结构体,作为代码清单20.6给出的usb_hcd结构体的私有数据,这个结构体的定义如 代码清单20.8所示。
代码清单20.8 ohci_hcd结构体
1 struct ohci_hcd |
使用如下内联函数可实现usb_hcd和ohci_hcd的相互转换:
struct ohci_hcd *hcd_to_ohci (struct usb_hcd *hcd); |
从usb_hcd得到ohci_hcd只是取得“私有”数据,而从ohci_hcd得到usb_hcd则是通过container_of()从结构 体成员获得结构体指针。
使用如下函数可初始化OHCI主机控制器:
int ohci_init (struct ohci_hcd *ohci); |
如下函数分别用于开启、停止及复位OHCI控制器:
int ohci_run (struct ohci_hcd *ohci); |
OHCI主机控制器驱动的主机工作仍然是实现代码清单20.7给出的hc_driver结构体中的成员函数。
S3C2410内部集成了一个USB主机控制器,完全兼容OCHI 1.0、USB 1.1标准,支持低速和全速USB设备,从基地址0x49000000开始分别提供了OHCI的HcRevision、HcControl、 HcCommonStatus、HcInterruptStatus、HcInterruptEnable、HcInterruptDisable、 HcHCCA、HcPeriodCuttentED、HcControlHeadED、HcControlCurrentED、 HcBulkHeadED、HcBulk CurrentED、HcDoneHead、HcRmInterval、HcFmRemaining、HcFmNumber、 HcPeriodicStart、HcLSThreshold、HcRhDescriptorA、HcRhDescriptorB、 HcRhStatus、HcRhPortStatus1、HcRhPortStatus2寄存器。
S3C2410主机控制器驱动hc_driver结构体中的大多数成员函数都是通用的ohci_xxx()函数,而start()、 hub_status_data()、hub_control()函数则针对S3C2410而编写的,如代码清单20.9所示。
代码清单20.9 S3C2410主机控制器驱动的hc_driver结构体
1 static const struct hc_driver ohci_s3c2410_hc_driver = |
hc_driver的start()成员函数用于初始化OHCI并启动主机控制器,如代码清单20.10所示。
代码清单20.10 S3C2410主机控制器驱动的start()函数
1 static int ohci_s3c2410_start (struct usb_hcd *hcd) |
hc_driver的hub_control()成员函数ohci_s3c2410_hub_control()中的主体是调用通用的ohci_ hub_control()函数,hub_status_data()成员函数ohci_s3c2410_hub_status_data()的主体是调 用通用的ohci_hub_status_data()函数。
20.3.1 USB设备驱动整体结构
Linux系统实现了几类通用的USB设备驱动,划分为如下几个设备类。
l 音频设备类。
l 通信设备类。
l HID(人机接口)设备类。
l 显示设备类。
l 海量存储设备类。
l 电
源设备类。
l 打印设备类。
l 集线器设备类。
一般的通用的Linux设备(如U盘、USB鼠标、USB键盘等)都不需要工程师再编写驱动,需要编写的是特定厂商、特定芯片的驱动,而且往往也可 以参考内核中已提供的驱动的模板。
这里所说的USB设备驱动指的是从主机角度观察,怎样访问被插入的USB设备,而不是指USB设备内部本身运行的固件程序。USB设备内的固件称为 “设备用户固件”,“设备用户固件”完成设备内部的控制任务,并在USB传输中对接收到的设备请求做出解释并予以正确响应。
Linux内核为各类USB设备分配了相应的设备号,如ACM USB调制解调器的主设备号为166(默认设备名/dev/ttyACMn)、USB打印机的主设备号为180,次设备号为0~15(默认设备名/dev /lpn)、USB串口的主设备号为188(默认设备名/dev/ttyUSBn)等。
内核中提供了USB设备文件系统(usbdevfs,Linux 2.6改为usbfs,即USB文件系统),它和/proc类似,都是动态产生的。通过在/etc/fstab文件中添加如下一行:
none /proc/bus/usb usbfs defaults |
或者输入命令:
mount -t usbfs none /proc/bus/usb |
可以实现USB设备文件系统的挂载。
一个典型的/proc/bus/usb/devices文件的结构如下(笔者在VmWare上运行的Linux 2.6.15.5内核上的机器上插入了一个SanDisk U盘):T: Bus=01 Lev=00 Prnt=00 Port=00 Cnt=00 Dev#= 1 Spd=12 MxCh= 2 |
通过分析usbfs中记录的信息,可以得到系统中USB完整的信息,例如,usbview可以以图形化的方式显示系统中的USB设备。
当然,在编译Linux内核时,应该包括“USB device filesystem”,如图20.3所示。usbfs动态跟踪总线上插入和移除的设备,通过它可以查看系统中USB设备的信息,包括拓扑、带宽、设备描 述符信息、产品ID、字符串描述符、配置描述符、接口描述符、端点描述符等。
图20.3 USB设备文件系统编译 |
此外,在sysfs文件系统中,同样包含了USB相关信息的描述,但只限于接口级别。USB设备和USB接口在sysfs中均表示为单独的USB设 备,其目录命名规则如下:
根集线器-集线器端口号(-集线器端口号-...):配置.接口。
下面讲解/sys/bus/usb目录的树形结构实例,其中的多数文件都是到/sys/devices及/sys/drivers中相应文件的链 接。
usb |
正如tty_driver、pci_driver等,在Linux内核中,使用usb_driver结构体描述一个USB设备驱 动,usb_driver结构体的定义如代码清单20.11所示。
代码清单20.11 usb_driver结构体
1 struct usb_driver { |
在编写新的USB设备驱动时,主要应该完成的工作是probe()和disconnect()函数,即探测和断开函数,它们分别在设备被插入和拔出 的时候被调用,用于初始化和释放软硬件资源。对usb_driver的注册和注销通过这两个函数完成:
int usb_register(struct usb_driver *new_driver) |
usb_driver结构体中的id_table成员描述了这个USB驱动所支持的USB设备列表,它指向一个usb_device_id数 组,usb_device_id结构体用于包含USB设备的制造商ID、产品ID、产品版本、设备类、接口类等信息及其要匹配标志成员match- Flash(标明要与哪些成员匹配)。可以借助下面一组宏来生成usb_device_id结构体的实例:
USB_DEVICE(vendor, product) |
该宏根据制造商ID和产品ID生成一个usb_device_id结构体的实例,在数组中增加该元素将意味着该驱动可支持匹配制造商ID、产品ID 的设备。
USB_DEVICE_VER(vendor, product, lo, hi) |
该宏根据制造商ID、产品ID、产品版本的最小值和最大值生成一个usb_device_id结构体的实例,在数组中增加该元素将意味着该驱动可支 持匹配制造商ID、产品ID和lo~hi范围内版本的设备。
USB_DEVICE_INFO(class, subclass, protocol) |
该宏用于创建一个匹配设备指定类型的usb_device_id结构体实例。
USB_INTERFACE_INFO(class, subclass, protocol) |
该宏用于创建一个匹配接口指定类型的usb_device_id结构体实例。
代码清单20.12所示为两个用于描述某USB驱动所支持的USB设备的usb_device_id结构体数组实例。
代码清单20.12 usb_device_id结构体数组实例
1 /* 本驱动支持的USB设备列表 */ |
上述usb_driver结构体中的函数是USB设备驱动中USB相关的部分,而USB只是一个总线,真正的USB设备驱动的主体工作仍然是USB 设备本身所属类型的驱动,如字符设备、tty设备、块设备、输入设备等。因此USB设备驱动包含其作为总线上挂在设备的驱动和本身所属设备类型的驱动两部 分。
与platform_driver类似,usb_driver起到了“牵线”的作用,即在probe()里注册相应的字符、tty等设备,在 disconnect()注销相应的字符、tty等设备,而原先对设备的注册和注销一般直接发生在模块加载和卸载函数中。
尽管USB本身所属设备驱动的结构与其不挂在USB总线上时完全相同,但是在访问方式上却发生了很大的变化,例如,对于字符设备而言,尽管仍然是 write()、read()、ioctl()这些函数,但是在这些函数中,与USB设备通信时不再是I/O内存和I/O端口的访问,而贯穿始终的是称为 URB的USB请求块。
如图20.4所示,在这棵树里,我们把树根比作主机控制器,树叶比作具体的USB设备,树干和树枝就是USB总线。树叶本身与树枝通过 usb_driver连接,而树叶本身的驱动(读写、控制)则需要通过其树叶设备本身所属类设备驱动来完成。树根和树叶之间的“通信”依靠在树干和树枝里 “流淌”的URB来完成。
由此可见,usb_driver本身只是起到了找到USB设备、管理USB设备连接和断开的作用,也就是说,它是公司入口处的“打卡机”,可以获得 员工(USB设备)的上/下班情况。树叶和员工一样,可以是研发工程师也可以是销售工程师,而作为USB设备的树叶可以是字符树叶、网络树叶或块树叶,因 此必须实现相应设备类的驱动。
图20.4 USB设备驱动结构 |
1.urb结构体
USB请求块(USB request block,urb)是USB设备驱动中用来描述与USB设备通信所用的基本载体和核心数据结构,非常类似于网络设备驱动中的sk_buff结构体,是 USB主机与设备通信的“电波”。
代码清单20.13 urb结构体
1 struct urb |
当transfer_flags标志中的URB_NO_TRANSFER_DMA_MAP被置位时,USB核心将使用transfer_dma指向 的缓冲区而非transfer_buffer指向的缓冲区,意味着即将传输DMA缓冲区。
当transfer_flags标志中的URB_NO_SETUP_DMA_MAP被置位时,对于有DMA缓冲区的控制urb而言,USB核心将使 用setup_dma指向的缓冲区而非setup_packet指向的缓冲区。
2.urb处理流程
USB设备中的每个端点都处理一个urb队列,在队列被清空之前,一个urb的典型生命周期如下:
(1)被一个 USB 设备驱动创建。
创建urb结构体的函数为:
struct urb *usb_alloc_urb(int iso_packets, int mem_flags); |
iso_packets是这个urb应当包含的等时数据包的数目,若为0表示不创建等时数据包。 mem_flags参数是分配内存的标志,和kmalloc()函数的分配标志参数含义相同。如果分配成功,该函数返回一个urb结构体指针,否则返回 0。
urb结构体在驱动中不能静态创建,因为这可能破坏USB核心给urb使用的引用计数方法。
usb_alloc_urb()的“反函数”为:
void usb_free_urb(struct urb *urb); |
该函数用于释放由usb_alloc_urb()分配的urb结构体。
(2)初始化,被安排给一个特定USB设备的特定端点。
对于中断urb,使用usb_fill_int_urb()函数来初始化urb,如下所示:
void usb_fill_int_urb(struct urb *urb, struct usb_device *dev, |
urb参数指向要被初始化的urb的指针;dev指向这个urb要被发送到的USB设备;pipe是这个urb要被发送到的USB设备的特定端 点;transfer_buffer是指向发送数据或接收数据的缓冲区的指针,和urb一样,它也不能是静态缓冲区,必须使用kmalloc()来分 配;buffer_length是transfer_buffer指针所指向缓冲区的大小;complete指针指向当这个 urb完成时被调用的完成处理函数;context是完成处理函数的“上下文”;interval是这个urb应当被调度的间隔。
上述函数参数中的pipe使用usb_sndintpipe()或usb_rcvintpipe()创建。
对于批量urb,使用usb_fill_bulk_urb()函数来初始化urb,如下所示:
void usb_fill_bulk_urb(struct urb *urb, struct usb_device *dev, |
除了没有对应于调度间隔的interval参数以外,该函数的参数和usb_fill_int_urb()函数的参数含义相同。
上述函数参数中的pipe使用usb_sndbulkpipe()或者usb_rcvbulkpipe()函数来创建。
对于控制 urb,使用usb_fill_control_urb()函数来初始化urb,如下所示:
void usb_fill_control_urb(struct urb *urb, struct usb_device *dev, |
除了增加了新的setup_packet参数以外,该函数的参数和usb_fill_bulk_urb()函数的参数含义相同。 setup_packet参数指向即将被发送到端点的设置数据包。
上述函数参数中的pipe使用usb_sndctrlpipe()或usb_rcvictrlpipe()函数来创建。
等时urb没有像中断、控制和批量urb的初始化函数,我们只能手动地初始化urb,而后才能提交给USB核心。代码清单20.14给出了初始化等 时urb的例子,它来自drivers/usb/media/usbvideo.c文件。
代码清单20.14 初始化等时urb
1 for (i = 0; i < USBVIDEO_NUMSBUF; i++) |
(3)被USB设备驱动提交给USB 核心。
在完成第(1)、(2)步的创建和初始化urb后,urb便可以提交给USB核心,通过usb_submit_urb()函数来完成,如下所示:
int usb_submit_urb(struct urb *urb, int mem_flags); |
urb参数是指向urb的指针,mem_flags参数与传递给kmalloc()函数参数的意义相同,它用于告知USB核心如何在此时分配内存缓 冲区。
在提交urb到USB核心后,直到完成函数被调用之前,不要访问urb中的任何成员。
usb_submit_urb()在原子上下文和进程上下文中都可以被调用,mem_flags变量需根据调用环境进行相应的设置,如下所示。
l GFP_ATOMIC:在中断处理函数、底半部、tasklet、定时器处理函数以及urb完成函数中,在调用者持有自旋锁或者读写锁时以及当 驱动将current->state修改为非 TASK_ RUNNING时,应使用此标志。
l GFP_NOIO:在存储设备的块I/O和错误处理路径中,应使用此标志;
l GFP_KERNEL:如果没有任何理由使用GFP_ATOMIC和GFP_NOIO,就使用GFP_ KERNEL。
如果usb_submit_urb()调用成功,即urb的控制权被移交给USB核心,该函数返回0;否则,返回错误号。
(4)提交由USB核心指定的USB主机控制器驱动。
(5)被USB主机控制器处理,进行一次到USB设备的传送。
第(4)~(5)步由USB核心和主机控制器完成,不受USB设备驱动的控制。
(6)当urb完成,USB主机控制器驱动通知USB设备驱动。
在如下3种情况下,urb将结束,urb完成函数将被调用。
l urb 被成功发送给设备,并且设备返回正确的确认。如果urb->status为0,意味着对于一个输出urb,数据被成功发送;对于一个输入urb,请 求的数据被成功收到。
l 如果发送数据到设备或从设备接收数据时发生了错误,urb->status将记录错误值。
l urb 被从USB 核心“去除连接”,这发生在驱动通过usb_unlink_urb()或usb_kill_urb()函数取消urb,或urb虽已提交,而USB设备被 拔出的情况下。
usb_unlink_urb()和usb_kill_urb()这两个函数用于取消已提交的urb,其参数为要被取消的urb指针。对 usb_unlink_urb()而言,如果urb结构体中的URB_ASYNC_UNLINK(即异步unlink)的标志被置位,则对该urb的 usb_unlink_urb()调用将立即返回,具体的unlink动作将在后台进行。否则,此函数一直等到urb被解开链接或结束时才返回。 usb_kill_urb()会彻底终止urb的生命周期,它通常在设备的disconnect()函数中被调用。
当urb生命结束时(处理完成或被解除链接),通过urb结构体的status成员可以获知其原因,如0表示传输成功,-ENOENT表示被 usb_kill_urb()杀死,-ECONNRESET表示被usb_unlink_urb()杀死,-EPROTO表示传输中发生了 bitstuff错误或者硬件未能及时收到响应数据包,-ENODEV表示USB设备已被移除,-EXDEV表示等时传输仅完成了一部分等。
对以上urb的处理步骤进行一个总结,图20.5给出了一个urb的整个处理流程,虚线框的usb_unlink_urb()和 usb_kill_urb()并非一定会发生,它只是在urb正在被USB核心和主机控制器处理时,被驱动程序取消的情况下才发生。
3.简单的批量与控制URB
有时USB驱动程序只是从USB设备上接收或向USB设备发送一些简单的数据,这时候,没有必要将urb创建、初始化、提交、完成处理的整个流程走 一遍,而可以使用两个更简单的函数,如下所示。
(1)usb_bulk_msg()。
usb_bulk_msg()函数创建一个USB批量urb 并将它发送到特定设备,这个函数是同步的,它一直等待urb完成后才返回。usb_bulk_msg()函数的原型为:
int usb_bulk_msg(struct usb_device *usb_dev, unsigned int pipe, |
图20.5 urb处理流程 |
usb_dev参数为批量消息要发送的USB 设备的指针,pipe为批量消息要发送到的USB设备的端点,data参数为指向要发送或接收的数据缓冲区的指针,len参数为data参数所指向的缓冲 区的长度,actual_length用于返回实际发送或接收的字节数,timeout是发送超时,以jiffies为单位,0意味着永远等待。
如果函数调用成功,返回0;否则,返回1个负的错误值。
(2)usb_control_msg()函数。
usb_control_msg()函数与usb_bulk_msg()函数类似,不过它提供驱动发送和结束USB控制信息而非批量信息的能力,该 函数的原型为:
int usb_control_msg(struct usb_device *dev, unsigned int pipe, _ _u8 request, |
dev指向控制消息发往的USB设备,pipe是控制消息要发往的USB设备的端点,request是这个控制消息的USB请求 值,requesttype是这个控制消息的USB请求类型,value是这个控制消息的USB消息值,index是这个控制消息的USB消息索引 值,data指向要发送或接收的数据缓冲区,size是data参数所指向的缓冲区的大小,timeout是发送超时,以jiffies为单位,0意味着 永远等待。
参数request、requesttype、value和index与USB规范中定义的USB控制消息直接对应。
如果函数调用成功,该函数返回发送到设备或从设备接收到的字节数;否则,返回一个负的错误值。
对usb_bulk_msg()和usb_control_msg()函数的使用要特别慎重,由于它们是同步的,因此不能在中断上下文和持有自旋锁 的情况下使用。而且,该函数也不能被任何其他函数取消,因此,务必要使得驱动程序的disconnect()函数掌握足够的信息,以判断和等待该调用的结 束。
在USB设备驱动usb_driver结构体的探测函数中,应该完成如下工作。
l 探测设备的端点地址、缓冲区大小,初始化任何可能用于控制USB设备的数据结构。
l 把已初始化数据结构的指针保存到接口设备中。
usb_set_intfdata()函数可以设置usb_interface的私有数据,这个函数的原型为:
void usb_set_intfdata (struct usb_interface *intf, void *data); |
这个函数的“反函数”用于得到usb_interface的私有数据,其原型为:
void *usb_get_intfdata (struct usb_interface *intf); |
l 注册USB设备。
如果是简单的字符设备,调用usb_register_dev(),这个函数的原型为:
int usb_register_dev(struct usb_interface *intf, |
上述函数中第二个参数为usb_class_driver结构体,这个结构体的定义如代码清单20.15所示。
代码清单20.15 usb_class_driver结构体
1 struct usb_class_driver |
对于字符设备而言,usb_class_driver结构体的fops成员中的write()、read()、ioctl()等函数的地位完全等同 于本书第6章中的file_operations成员函数。
如果是其他类型的设备,如tty设备,则调用对应设备的注册函数。
在USB设备驱动usb_driver结构体的探测函数中,应该完成如下工作。
l 释放所有为设备分配的资源。
l 设置接口设备的数据指针为NULL。
l 注销USB设备。
对于字符设备,可以直接调用usb_register_dev()函数的“反函数”,如下所示:
void usb_deregister_dev(struct usb_interface *intf, |
对于其他类型的设备,如tty设备,则调用对应设备的注销函数。
对探测函数的调用发生在USB设备被安装且USB核心认为该驱动程序与安装的USB设备对应时(usb_driver的id_table成员在此时 发挥作用),而对断开函数的调用则发生在驱动因为种种原因不再控制该设备的时候。对这两个函数的调用都是在内核线程中进行的,因此,可以在其中进行任何可 能引起睡眠的操作。但是,由于USB核心对所有探测和断开函数的调用都发生在单一线程内,因此,太慢的probe()或disconnect()将拖延其 他USB设备的探测时间。
20.4.1 USB串口驱动
在Linux内核中,串口属于tty设备,对于一个USB串口设备而言,其驱动主要由两部分组成:usb_driver的成员函数和tty设备的 tty_operations结构体成员函数。
在USB串口设备驱动的模块加载函数中,将注册对应于USB串口的usb_driver,并初始化和注册tty驱动,如代码清单20.28所示。
代码清单20.28 USB串口设备驱动的模块加载函数
1 static int __init usb_serial_init(void) |
在USB串口设备驱动的模块卸载函数中,将注销对应于USB串口的usb_driver,并注销tty驱动,如代码清单20.29所示。
代码清单20.29 USB串口设备驱动的模块卸载函数
1 static void _ _exit usb_serial_exit(void) |
在usb_driver的探测成员函数usb_serial_probe()中,将初始化USB端点等信息,并通过 usb_set_intfdata()设置接口私有数据,它也将初始化urb。相反地,在断开成员函数usb_serial_disconnect()中 将设置接口私有数据为NULL,并释放引用计数。
USB串口驱动的tty_operations结构体实例serial_ops定义如代码清单20.30所示,它封装了USB串口设备驱动中的串口 驱动成分。
代码清单20.30 USB串口驱动的tty_operations结构体
1 static struct tty_operations serial_ops = |
在tty_operations的各write()、read()等成员函数中,将调用usb_serial_driver结构体中的相应函 数,usb_serial_driver结构体中封装了串口的各函数(读写、读写中断端点完成函数、读写批量端点完成函数等),其定义如代码清单 20.31所示。
代码清单20.31 usb_serial_driver结构体
1 struct usb_serial_driver |
文件drivers/usb/serial/Generic.c文件中提供了USB串口驱动的通用打开/关闭、写函数、批量urb完成函数,如
usb_serial_generic_write()、usb_serial_generic_write_bulk_callback()、
usb_serial_
generic_read_bulk_callback()等。代码清单20.32列举了通用写函数及输出批量urb完成
回调函数。
代码清单20.32 USB串口通用写函数及批量写urb完成函数
1 int usb_serial_generic_write(struct usb_serial_port *port, const unsigned char |
在Linux系统中,键盘被认定为标准输入设备,对于一个USB键盘而言,其驱动主要由两部分组成:usb_driver的成员函数和输入设备的打 开、关闭、中断处理等函数。
在USB键盘设备驱动的模块加载和卸载函数中,将分别注册和注销对应于USB键盘的usb_driver结构体usb_kbd_driver,代码 清单20.33所示为模块加载与卸载函数以及usb_kbd_driver结构体的定义。
代码清单20.33 USB键盘设备驱动的模块加载与卸载函数及usb_driver结构体
1 static int _ _init usb_kbd_init(void) |
在usb_driver的探测函数中,将进行input设备的初始化和注册,USB键盘要使用的中断urb和控制urb的初始化,并设置接口的私有 数据,如代码清单20.34所示。
代码清单20.34 USB键盘设备驱动的探测函数
1 static int usb_kbd_probe(struct usb_interface *iface, const struct |
在usb_driver的断开函数中,将设置接口私有数据为NULL、终止已提交的urb并注销输入设备,如代码清单20.35所示。
代码清单20.35 USB键盘设备驱动的断开函数
1 static void usb_kbd_disconnect(struct usb_interface *intf) |
在键盘中断处理函数,也就是中断urb的完成函数中,将会通过input_report_key()报告按键事件,通过input_sync()报 告同步事件,并发起一次新的控制urb传输,如代码清单20.36所示。
代码清单20.36 USB键盘设备驱动的中断urb完成函数
1 static void usb_kbd_irq(struct urb *urb, struct pt_regs *regs) |
USB驱动分为USB主机驱动和USB设备驱动,如果系统的USB主机控制器符合OHCI等标准,这主机驱动的绝大部分工作都可以沿用通用的代码。
对于一个USB设备而言,它至少具备两重身份:首先它是“USB”的,其次它是“自己”的。USB设备是“USB”的,指它挂接在USB总线上,其 必须完成usb_driver的初始化和注册;USB设备是“自己”的,意味着本身可能是一个字符设备、tty设备、网络设备等,因此,USB设备驱动中 也必须实现符合相应框架的代码。
USB设备驱动的自身设备驱动部分的读写等操作流程有其特殊性,即以URB来贯穿始终,一个URB的生命周期通常包含创建、初始化、提交,和被 USB核心及USB主机传递及完成后回调函数被调用的过程,当然,在URB被驱动提交后,也可以被取消。此外,简单的控制及批量消息传递可以用同步的 usb_bulk_msg()、usb_control_msg()函数完成。