内核版本:linux-3.4.2
USB采用树形拓扑结构,主机侧和设备侧的USB控制器分别称为主机控制器(Host Controller)和USB设备控制器(UDC),每条总线上只有一个主机控制器,负责协调主机和设备间的通信,设备不能主动向主机发送任何消息。如图:
从主机侧去看,在Linux驱动中,处于USB驱动最底层的是USB主机控制器硬件,在其上的是USB主机控制器驱动,在主机控制器驱动上的为USB核心层,再上层为USB设备驱动层。因此,在主机侧的层级结构中,要实现的USB驱动包括:USB主机控制器驱动和USB设备驱动。
USB核心层向上为USB设备驱动提供编程接口,向下为USB主机控制器驱动提供编程接口,维护真个系统的USB设备信息,完成设备热插拔控制、总线数据传输控制等。
USB设备驱动负责驱动具体的设备,例如U盘,鼠标等设备。USB设备驱动既可以注册成某个类型的设备驱动,例如输入子系统,此时的主设备号依据具体的子系统而定;又可以作为一个独立的USB设备注册进系统,这时的主设备号是180(USB_MAJOR)。
USB主机控制器驱动的代码位于:driver/usb/host,根据具体的硬件实现一个该硬件对应的主机控制器驱动的文件。
USB核心层代码位于:driver/usb/core。
USB设备驱动代码依据具体的设备放在对应的目录下。
Linux内核中USB设备侧驱动程序分为3个层次:UDC驱动程序、Gadget Function API和Gadget Function驱动程序。UDC驱动程序直接访问硬件,控制USB设备和主机间的底层通信,向上层提供与硬件相关操作的回调函数。当前Gadget Function API是UDC驱动程序回调函数的简单包装。Gadget Function驱动程序具体控制USB设备功能的实现,使设备表现出“网络连接”、“打印机”或“USB Mass Storage”等特性,它使用Gadget Function API控制UDC实现上述功能。Gadget Function API把下层的UDC驱动程序和上层的Gadget Function驱动程序隔离开,使得在Linux系统中编写USB设备侧驱动程序时能够把功能的实现和底层通信分离。
在USB设备的逻辑组织中包含了设备、配置、接口、端点四个层次。
一个设备里包含了不同级别的配置,可以有一个或者多个配置。设备描述符描述了这个设备。
在linux中,结构体usb_device_descriptor
对应于协议中的设备描述符。
struct usb_device_descriptor {
__u8 bLength; /* 描述符长度 */
__u8 bDescriptorType; /* 描述符类型 设备描述符类型值是1 */
__le16 bcdUSB; /* USB版本号 */
__u8 bDeviceClass; /* USB分配的设备类code */
__u8 bDeviceSubClass; /* USB分配的子类code */
__u8 bDeviceProtocol; /* USB分配的协议code */
__u8 bMaxPacketSize0; /* 端点0最大包大小 */
__le16 idVendor; /* 厂商编号 */
__le16 idProduct; /* 产品编号 */
__le16 bcdDevice; /* 设备出厂编号 */
__u8 iManufacturer; /* 描述厂商字符串的索引 */
__u8 iProduct; /* 描述产品字符串的索引 */
__u8 iSerialNumber; /* 描述设备序列号字符串的索引 */
__u8 bNumConfigurations; /* 可能的配置数量 */
} __attribute__ ((packed));
一个USB设备具有一个或多个配置,不同的配置使设备表现出不同的功能组合。一个配置下有一个或多个接口。
在linux中,结构体usb_config_descriptor
对应于协议中的配置描述符。
struct usb_config_descriptor {
__u8 bLength; /* 描述符长度 */
__u8 bDescriptorType; /* 描述符类型 配置描述符类型值是2 */
__le16 wTotalLength; /* 配置下面所有描述符的长度,包括这个配置描述符 */
__u8 bNumInterfaces; /* 配置所支持的接口数 */
__u8 bConfigurationValue; /* 配置值 */
__u8 iConfiguration; /* 描述该配置的字符串的索引值 */
__u8 bmAttributes; /* 供电模式的选择 */
__u8 bMaxPower; /* 设备从总线提取的最大电流 */
} __attribute__ ((packed));
一个接口是由几个端点组成,代表了一个基本的功能,是USB设备驱动程序控制的对象。一个设备中的某个配置有多个接口表示多种功能,例如一个USB声卡有两个接口,一个用来播放声音,一个用来录音。每个接口都有一个或者多个设置,表示这个功能下的不同参数。
在linux中,结构体usb_interface_descriptor
对应于协议中的接口描述符。
struct usb_interface_descriptor {
__u8 bLength; /* 描述符长度 */
__u8 bDescriptorType; /* 描述符类型 接口描述符类型值是4 */
__u8 bInterfaceNumber; /* 接口的编号,相同的编号代表相同的功能接口 */
__u8 bAlternateSetting; /* 接口的设置的编号,同一个接口可以有一个或者多个设置代表该接口下不同的参数 */
__u8 bNumEndpoints; /* 该接口下的端点数,不包括端点0 */
__u8 bInterfaceClass; /* 接口类型 */
__u8 bInterfaceSubClass; /* 接口子类型 */
__u8 bInterfaceProtocol; /* 接口遵循的协议 */
__u8 iInterface; /* 描述该接口的字符串索引值 */
} __attribute__ ((packed));
端点是USB通信的最基本形式,每一个USB设备接口在主机看来就是一个端点的集合。在USB系统中每一个端点都有唯一的地址,这是由设备地址和端点号给出的。一个端点只能在一个方向传输数据,从主机到设备称为输出端点,从设备到主机称为输入端点,端点可以看作是一个单项的管道。
在linux中,结构体usb_interface_descriptor
对应于协议中的设备描述符。
struct usb_endpoint_descriptor {
__u8 bLength; /* 描述符长度 */
__u8 bDescriptorType; /* 描述符类型 端点描述符类型值是5 */
__u8 bEndpointAddress; /* 端点地址:0~3位是端点号,第7位是方向(0为输出,1为输入) */
__u8 bmAttributes; /* 端点属性:bit[0:1]的值为00表示控制,为01表示同步,为02表示批量,为03表示中断 */
__le16 wMaxPacketSize; /* 本端点接受或发送的最大信息包的大小 */
__u8 bInterval; /* 轮训数据传送端点的时间间隔,不同类型的端点代表了不同的含义 */
/* NOTE: these two are _only_ in audio endpoints. */
/* use USB_DT_ENDPOINT*_SIZE in bLength, not sizeof. */
__u8 bRefresh;
__u8 bSynchAddress;
} __attribute__ ((packed));
端点0是个是个特殊的端点,通常为控制端点,用于设备初始化参数等。只要设备连接到USB上并且上电,端点0就可以被访问。端点0的包大小在设备描述符中。
SOC上的USB主机控制器并没有像PC上的主机控制器是挂在PCI总线上,因此是作为一个platform设备存在,那么硬件部分就会是一个platform_device
的结构体,里面包含了主机控制器的中断号、控制器地址等信息,这是板级相关的内容,一般会包含在arch/arm/下板级相关的文件中。而主机控制器的驱动,则会是一个platform设备的驱动,是一个platform_driver
的结构体。所以当platform设备和platform驱动匹配到的时候,会执行platform中的probe函数,而一般在这个函数中应该去注册一个主机控制器的驱动。
每一个主机控制器都会集成一个root hub,而在USB核心层,每当添加一个主机控制器驱动的时候,也会向USB总线添加一个root hub的设备(这里所说的总线,是usb_bus_type
,属于设备模型的虚拟的总线,并不是物理上的一个USB总线,关于USB物理上的总线后面说),这个设备是一个usb_device
结构体的实例。
root hub设备是作为一个USB设备存在于USB系统中的,这个USB设备与驱动的匹配过程大概是这样的:
在usb子系统初始化的时候会注册一个hub的驱动(hub_driver
)和一个USB设备驱动(usb_generic_driver
),当root hub的对象注册到总线后首先会匹配到usb_generic_driver
驱动,这时会调用usb_generic_driver
中的probe函数,也就是generic_probe()
函数,在generic_probe()
函数中会配置root hub,然后再分配USB接口设备,将USB接口设备注册到总线,此时才会匹配到hub_driver
驱动,然后调用hub_driver
中的probe函数,也就是hub_probe()
函数,在hub_probe()
函数中会为hub的这个接口下的中断端点设置urb,并且会设置一个回调函数hub_irq()
。
关于这一段中platform的设备匹配和root hub和hub驱动的匹配过程大概如下图所示:
从上图可以看出,注册到总线的设备有两种类型,一种是struct usb_device
,另一种是struct usb_interface
, 这两个结构体都内嵌了struct device
结构体,那么两种设备是如何区分的。在usb_alloc_dev()
函数分配一个usb_device
结构体后,将成员device
中的type指针指向了 usb_device_type
变量;而在向总线添加usb_interface
的地方将usb_interface
中device
成员的type指针指向了usb_if_device_type
变量,而总线的match
函数就是通过这个来分辨这个设备是USB接口设备还是USB设备。
这里我将一个物理存在的USB设备称为USB设备,而USB接口设备是指逻辑上一个USB接口的设备,前面说接口的时候说过“一个接口代表了一个基本的功能”,这里的USB接口设备就是这个基本的功能设备。
上图中的hub_driver
类型是struct usb_driver
,usb_generic_driver
类型是struct usb_device_driver
,从匹配关系可以看出,USB设备匹配的是struct usb_device_driver
类型的驱动,USB接口设备匹配的是struct usb_driver
类型的驱动。这两个驱动中都内嵌了一个struct usbdrv_wrap
结构体,从名字看是个wrap层,这个结构体:
struct usbdrv_wrap {
struct device_driver driver;
int for_devices;
};
这里的for_devices
成员就是用来分辨USB设备驱动和USB接口驱动的,当for_devices
不是0的时候代表这是一个USB设备驱动,当for_devices
是0的时候代表这是一个USB接口驱动。
所以在USB子系统中有两种设备:USB设备 和 USB接口设备
有两种驱动:USB设备驱动 和 USB接口驱动
通过总线的match
函数,可以看出,USB设备只能和USB设备驱动匹配,而USB接口设备只能和USB接口驱动匹配。而USB设备驱动只有一个,那就是usb_generic_driver
,USB接口驱动是对应于设备的功能开发的不同的驱动程序。
USB物理设备插入及USB设备和USB接口设备的注册和匹配的过程如图所示:
如图所示,xxx_driver就是实现的接口功能的驱动,可以是一个输入设备驱动,也可以是一个U盘驱动,也可以是一个普通的USB字符设备。