一、特性和运作模式
1. USB标准的四个版本:
(1)USB1.0版本:
USB总线协议的第一个版本。
(2)USB1.1版本:
此版本普及了USB总线协议,大多数硬件都采用了该版本的标准。
(3)USB2.0版本:
此版本提升了USB总线的最大传输数率,由USB1.1的12Mb/s提高到了480Mb/s。
(4)USB3.0版本:
此版本进一步提高了USB总线的传输速率,并且支持OTG功能。
2. USB标准的四种不同的传输模式:
(1)控制传输(control transfer)
控制传输涉及传输所需的控制信息,用于设备的初始配置。此类通信必须安全可靠,但只需要较窄的带宽。其
中通过预定义的令牌传输各种控制命令,USB标准定义了令牌的符号名称和语义,如GET_STATUS、
SET_INTERFACE等。在内核源代码这些令牌都在<usb.h>中声明为预处理器常数,其前缀为USQ_REQ_,以防止
名称冲突。标准强制要求了一个命令的最小集合,所有设备都必须支持这些命令。但厂商可以随意添加其他特定于
设备的命令,厂商提供的驱动程序必须能够理解/使用这些命令。
(2)块传输(bulk transfer)
块传输按数据包发送数据,可以占据总线的全部带宽。在这种模式下,数据传输的安全性由总线保证。换句话
说,发送的数据总是原样到达其目的地。扫描仪或大容量存储器之类的设备会使用这种模式。
(3)中断传输(interrupt transfer)
中断传输类似于块传输,但按一定的周期重复。驱动程序可以自由地定义周期长度(在一定的限度内)。网卡
和类似设备会优先选择使用这种传输模式。
(4)同步传输(isochronous transfer)
同步传输具有特殊作用,它是能够使用固定的预定义带宽的唯一方法(尽管不可靠)。在某些方面,这种模式
可以与网卡的数据报技术类比。在需要确保连续数据流,而能够容忍偶尔数据丢失的情况下,该传输模式是最适用
的。使用这种模式的一个主要的例子就是网络摄像头,该设备通过USB总线发送视频数据。
二、驱动程序的管理
1. 内核中按两个层次实现USB总线系统
(1)宿主机适配器的驱动程序必须是可用的。该适配器必须为USB链提供链接选项,并承担与终端设备的电子通
信。适配器自身必须连接到另一个系统总线(当前,有3种不同宿主机适配器类型,分别称之为OHCI、EHCI、
UHCI,这些涵盖了市售的所有控制器类型)。
(2)设备驱动程序与各个USB设备通信,并将设备的功能导出到内核的其他部分,进而到用户空间。这些驱动程序
与宿主机控制器通过一种标准化接口交互,因而控制器类型与USB驱动程序是不相关的。任何其他方法显然都是不
切实际的,因为需要为每个USB设备开发与宿主机控制器相关的驱动程序。
2. USB子系统的四项主要任务
(1)注册和管理现存的设备驱动程序
(2)为USB设备查找适当的驱动程序,以及初始化和配置
(3)在内核内存中表示设备树
(4)与设备通信(交换数据)
3. 数据结构
usb_driver是USB设备驱动程序和内核其余部分(特别是USB层)之间协作的起始点。
<usb.h>
struct usb_driver {
const char *name;
int (*probe) (struct usb_interface *intf,
const struct usb_device_id *id);
void (*disconnect) (struct usb_interface *intf);
int (*unlocked_ioctl) (struct usb_interface *intf, unsigned int code,
void *buf);
int (*suspend) (struct usb_interface *intf, pm_message_t message);
int (*resume) (struct usb_interface *intf);
int (*reset_resume)(struct usb_interface *intf);
int (*pre_reset)(struct usb_interface *intf);
int (*post_reset)(struct usb_interface *intf);
const struct usb_device_id *id_table;
struct usb_dynids dynids;
struct usbdrv_wrap drvwrap;
unsigned int no_dynamic_id:1;
unsigned int supports_autosuspend:1;
unsigned int soft_unbind:1;
};
name字段用于日常管理。name是驱动程序的名称,在内核中必须是唯一的(通常使用模块的文件名)。
在这里,通常嵌入的driver对象隐藏在另一个结构体中。
<usb.h>
struct usbdrv_wrap {
struct device_driver driver;
int for_devices;
};
上面这个数据结构使得可以区分接口驱动程序(for_devices为0)和设备驱动程序。
函数指针probe和disconnect很重要,二者与id_table共同构成了USB子系统热插拔能力的支柱。在宿主机适配器检测
到新设备插入时,即发起一个探测过程,以查找适当的设备驱动程序。
内核接下来遍历设备树的所有结点,确定是否有驱动程序与该设备相关。当然,这里预先假定了该设备尚未分配驱动程序。如果已经分配了驱动程序,则跳过该设备。
内核首先扫描驱动程序支持的所有设备列表,即id_table。在找到设备和表项的匹配之后,则调用特定于驱动程
序的probe函数,执行进一步的检查和初始化工作。如果设备ID和驱动程序提供的列表之间无法匹配,则内核不会调
用probe,而是跳到下一个驱动程序。
ID表由以下结构的几个实例组成,该结构通过几个ID描述了USB设备:
<mod_devicetable.h>
struct usb_device_id {
/* 针对哪些字段进行匹配 */
__u16 match_flags;
/* 用于特定产品的匹配,范围包含边界在内 */
__u16 idVendor;
__u16 idProduct;
__u16 bcdDevice_lo;
__u16 bcdDevice_hi;
/* 用于设备类别的匹配 */
__u8 bDeviceClass;
__u8 bDeviceSubClass;
__u8 bDeviceProtocol;
/* 用于接口类别的匹配 */
__u8 bInterfaceClass;
__u8 bInterfaceSubClass;
__u8 bInterfaceProtocol;
/* not matched against */
kernel_ulong_t driver_info;
};
match_flags用于指定将该结构的哪些字段与设备数据比较,为此定义了各种预处理器常数。
例如,USB_DEVICE_ID_MATCH_VENDOR表示检查idVendor字段,USB_DEVICE_ID_MATCH_DEV_PROTOCOL
指示内核检查bDeviceProtocol字段。
不仅在新设备添加到系统时,会建立驱动程序和设备之间的关联。在加载新驱动程序时,也会如此。起始点是
usb_register例程,在注册新USB驱动程序时必须调用它。
三、设备树的表示
下面的数据结构描述了USB设备树以及内核中各种设备的特征。
<usb.h>
struct usb_device {
int devnum; /* 在USB总线上的地址 */
char devpath[16]; /* 用于消息中:/port/port/... */
enum usb_device_state state; /* 已配置、未连接,等等 */
enum usb_device_speed speed; /* high/full/low speed (or error) */
unsigned int toggle[2]; /* 每个比特位表示一个终点(0表示接入,1表示断开) */
struct usb_device *parent; /* 所在的集线器,如果为根结点,则为NULL */
struct usb_bus *bus; /* 所在总线 */
struct usb_host_endpoint ep0;
struct device dev; /* 到通用设备模型的接口 */
struct usb_device_descriptor descriptor; /* 描述符 */
struct usb_host_bos *bos;
struct usb_host_config *config; /* 所有配置 */
struct usb_host_config *actconfig; /* 当前活动配置 */
struct usb_host_endpoint *ep_in[16];
struct usb_host_endpoint *ep_out[16];
char **rawdescriptors;
unsigned short bus_mA;
u8 portnum; /* 父结点端口号(从1开始) */
u8 level;
....
/* 静态字符串,来自设备 */
char *product; /* iProduct字符串,如果有的话 */
char *manufacturer; /* iManufacturer字符串,如果有的话 */
char *serial; /* iSerialNumber字符串,如果有的话 */
struct list_head filelist;
#ifdef CONFIG_USB_DEVICE_CLASS
struct device *usb_classdev;
#endif
#ifdef CONFIG_USB_DEVICEFS
struct dentry *usbfs_dentry;
#endif
int maxchild;
struct usb_device **children;
....
};
结构体成员变量说明:
a. devnum保存了该设备的唯一编号(在整个USB树中全局唯一)。
state和speed表示设备的状态(已连接、已配置,等等)和速度。
b. devpath指定了该设备在USB树的拓扑结构中的位置。
从根结点移动到保存在各个数组项中的设备,必须遍历所有集线器的端口号。
c. parent指向该设备附接的集线器的数据结构,而bus指向总线对应的数据结构。
两个字段提供了有关USB链拓扑结构的信息。
d. dev建立了与通用设备模型的关联。
e. descriptor将描述USB设备的特征数据集到一个数据结构中(包括厂商ID、产品ID、设备类别等信息)。
f. actconfig指向设备的当前配置,而congfig列出了所有可能的配置。
g. usbfs_entry用于连接到USB文件系统,通常装载在/proc/bus/usb中,提供从用户空间访问设备的入口。
h. product、manufacturer和serial指向一组ASCII字符串,分别是产品名称、生产商和设备序列号,
这些都由硬件自身提供。
i. 如果当前设备是集线器,还有两个相关的成员:
maxchild指定了集线器的端口数目(可以附接的设备数目),children是一个指针数组,包含了指向对应
usb_device实例的指针。这两个成员定义了USB树的拓扑结构。
总线链表中的各个元素,由以下数据结构表示:
<usb.h>
struct usb_bus {
struct device *controller; /* 主机端硬件控制器 */
int busnum; /* 总线编号(按注册顺序)*/
const char *bus_name; /* 稳定的id(PCI slot_name等) */
u8 uses_dma; /* 主机控制器是否使用DMA */
....
struct usb_devmap devmap; /* 设备地址分配位图 */
struct usb_device *root_hub; /* 根集线器 */
struct list_head bus_list; /* 用作总线链表的链表元素 */
....
#ifdef CONFIG_USB_DEVICEFS
struct dentry *usbfs_dentry; /* 总线在usbfs中的dentry项 */
#endif
};
该数据结构有两个成员可以唯一地标示该总线。busnum是一个整数,在总线注册时按顺序分配,bus_name是一
个指向短字符串的指针,其中保存了一个唯一的名称。controller是一个指向device实例的指针,对应于实现了该总线
的硬件设备。
不仅设备会出现在上文提到的USB文件系统中,总线也一样。因而usb_bus还必须包含一个指向dentry实例的指
针,用于简历与该虚拟文件系统的必要的关联。
数据结构中间有几个重要的成员,这些成员将各个可用的总线彼此连接起来,也连接了其上附接的设备。它们还
提供了一个到底层宿主机控制器的标准化连接,向USB层剩余的部分提供了控制器的抽象:
a. bus_list是一个链表元素,用于将所有usb_bus实例连接到一个链表中管理。
b. root_hub是一个指针,指向(虚拟)根集线器的数据结构,表示总线设备树的根结点。
c. devmap是一个位图,长度(最少)为128个比特位。它用于跟踪哪些USB编号已经分配,哪些仍然是空闲的。
每个USB设备在插入时都分配了一个唯一的整数编号,标准规定一个总线上最多可附接128个设备。
为与底层的硬件控制器通信,使用了USB请求块(USB request block, URB)。在与USB设备的所有可能形式的传输中,都使用URB交换数据。
drivers/usb/core/hcd.h
int usb_hcd_submit_urb(struct urb * urb, gfp_t mem_flags);