USB总线-Linux内核USB3.0 Hub驱动分析(十四)
1.概述
USB Hub提供了连接USB主机和USB设备的电气接口。USB Hub拥有一个上行口,至少一个下行口,上行口连接上一级的Hub的下行口或者USB主机,连接主机的为Root Hub,下行口连接下一级Hub的上行口或者USB设备。经过Hub的扩展,一个USB主机可以和多个USB设备通信。USB Hub有如下特性:
- 良好的扩展性
- 电源管理
- 探测设备连接和断开连接
- 总线错误探测和修复
- 支持USB1.0、USB1.1、USB2.0、USB3.2设备。
下图是USB Hub的架构图,拥有一个上行口,4个下行口,内部包含了USB3.2 Hub,USB2.0 Hub。USB2.0 Hub支持USB1.0、USB1.1、USB2.0设备,USB3.2 Hub支持USB3.2设备。
2.数据结构及接口
2.1.数据结构
Linux内核中使用struct usb_hub
结构体描述USB Hub,同时USB Hub也是一个USB设备,因此struct usb_hub
中的hdev
指向了描述USB Hub的struct usb_device
数据结构。
[drivers/usb/core/hub.h]
struct usb_hub {
struct device *intfdev; /* the "interface" device */
struct usb_device *hdev;
struct kref kref;
struct urb *urb; /* for interrupt polling pipe */
/* buffer for urb ... with extra space in case of babble */
u8 (*buffer)[8];
union {
struct usb_hub_status hub;
struct usb_port_status port;
} *status; /* buffer for status reports */
struct mutex status_mutex; /* for the status buffer */
int error; /* last reported error */
int nerrors; /* track consecutive errors */
unsigned long event_bits[1]; /* status change bitmask */
unsigned long change_bits[1]; /* ports with logical connect
status change */
unsigned long removed_bits[1]; /* ports with a "removed"
device present */
unsigned long wakeup_bits[1]; /* ports that have signaled
remote wakeup */
unsigned long power_bits[1]; /* ports that are powered */
unsigned long child_usage_bits[1]; /* ports powered on for
children */
unsigned long warm_reset_bits[1]; /* ports requesting warm
reset recovery */
#if USB_MAXCHILDREN > 31 /* 8*sizeof(unsigned long) - 1 */
#error event_bits[] is too short!
#endif
struct usb_hub_descriptor *descriptor; /* class descriptor */
struct usb_tt tt; /* Transaction Translator */
......
};
Linux内核中使用`struct usb_port`结构体描述Hub上的port。
```c
[drivers/usb/core/hub.h]
struct usb_port {
struct usb_device *child; // usb device attached to the port
struct device dev; // generic device interface
struct usb_dev_state *port_owner;
struct usb_port *peer; // related usb2 and usb3 ports (share the same connector)
struct dev_pm_qos_request *req;
enum usb_port_connect_type connect_type;
usb_port_location_t location;
struct mutex status_lock;
u32 over_current_count;
u8 portnum;
u32 quirks;
unsigned int is_superspeed:1;
unsigned int usb3_lpm_u1_permit:1;
unsigned int usb3_lpm_u2_permit:1;
};
Hub有专门的描述符,使用struct usb_hub_descriptor
描述。
[include/uapi/linux/usb/ch11.h]
/*
* Hub descriptor
* See USB 2.0 spec Table 11-13
*/
#define USB_DT_HUB (USB_TYPE_CLASS | 0x09)
#define USB_DT_SS_HUB (USB_TYPE_CLASS | 0x0a)
#define USB_DT_HUB_NONVAR_SIZE 7
#define USB_DT_SS_HUB_SIZE 12
/*
* Hub Device descriptor
* USB Hub class device protocols
* usb_device_descriptor中的bDeviceProtocol描述hub支持的最高速度
*/
#define USB_HUB_PR_FS 0 /* Full speed hub */
#define USB_HUB_PR_HS_NO_TT 0 /* Hi-speed hub without TT */
#define USB_HUB_PR_HS_SINGLE_TT 1 /* Hi-speed hub with single TT */
#define USB_HUB_PR_HS_MULTI_TT 2 /* Hi-speed hub with multiple TT */
#define USB_HUB_PR_SS 3 /* Super speed hub */
struct usb_hub_descriptor {
__u8 bDescLength;
__u8 bDescriptorType;
__u8 bNbrPorts; // number of ports on this hub
__le16 wHubCharacteristics; // Hub Charateristics
__u8 bPwrOn2PwrGood; // 设备完全打开所花费的时间(以 2 毫秒间隔)
__u8 bHubContrCurrent; // 集线器的控制器组件的最大电流要求
/* 2.0 and 3.0 hubs differ here */
union {
struct {
/* add 1 bit for hub status change; round to bytes */
__u8 DeviceRemovable[(USB_MAXCHILDREN + 1 + 7) / 8];
__u8 PortPwrCtrlMask[(USB_MAXCHILDREN + 1 + 7) / 8];
} __attribute__ ((packed)) hs;
struct {
__u8 bHubHdrDecLat;
__le16 wHubDelay;
__le16 DeviceRemovable;
} __attribute__ ((packed)) ss;
} u;
} __attribute__ ((packed));
Hub有专门的描述符,使用struct usb_hub_descriptor
描述。
[include/uapi/linux/usb/ch11.h]
/*
* Hub descriptor
* See USB 2.0 spec Table 11-13
*/
#define USB_DT_HUB (USB_TYPE_CLASS | 0x09)
#define USB_DT_SS_HUB (USB_TYPE_CLASS | 0x0a)
#define USB_DT_HUB_NONVAR_SIZE 7
#define USB_DT_SS_HUB_SIZE 12
/*
* Hub Device descriptor
* USB Hub class device protocols
* usb_device_descriptor中的bDeviceProtocol描述hub支持的最高速度
*/
#define USB_HUB_PR_FS 0 /* Full speed hub */
#define USB_HUB_PR_HS_NO_TT 0 /* Hi-speed hub without TT */
#define USB_HUB_PR_HS_SINGLE_TT 1 /* Hi-speed hub with single TT */
#define USB_HUB_PR_HS_MULTI_TT 2 /* Hi-speed hub with multiple TT */
#define USB_HUB_PR_SS 3 /* Super speed hub */
struct usb_hub_descriptor {
__u8 bDescLength;
__u8 bDescriptorType;
__u8 bNbrPorts; // number of ports on this hub
__le16 wHubCharacteristics; // Hub Charateristics
__u8 bPwrOn2PwrGood; // 设备完全打开所花费的时间(以 2 毫秒间隔)
__u8 bHubContrCurrent; // 集线器的控制器组件的最大电流要求
/* 2.0 and 3.0 hubs differ here */
union {
struct {
/* add 1 bit for hub status change; round to bytes */
__u8 DeviceRemovable[(USB_MAXCHILDREN + 1 + 7) / 8];
__u8 PortPwrCtrlMask[(USB_MAXCHILDREN + 1 + 7) / 8];
} __attribute__ ((packed)) hs;
struct {
__u8 bHubHdrDecLat;
__le16 wHubDelay;
__le16 DeviceRemovable;
} __attribute__ ((packed)) ss;
} u;
} __attribute__ ((packed));
2.2.接口
[drivers/usb/core/hub.h]
/* 创建port数据结构usb_port */
extern int usb_hub_create_port_device(struct usb_hub *hub,
int port1);
/* 释放port数据结构usb_port */
extern void usb_hub_remove_port_device(struct usb_hub *hub,
int port1);
/**
* call this function to control port's power via setting or
* clearing the port's PORT_POWER feature.
*/
extern int usb_hub_set_port_power(struct usb_device *hdev,
struct usb_hub *hub, int port1, bool set);
/* 清除port的特性 */
extern int usb_clear_port_feature(struct usb_device *hdev,
int port1, int feature);
/* 判断hub是否是superspeed hub */
static inline int hub_is_superspeed(struct usb_device *hdev)
{
return hdev->descriptor.bDeviceProtocol == USB_HUB_PR_SS;
}
/* 判断hub是否是superspeedplus hub */
static inline int hub_is_superspeedplus(struct usb_device *hdev)
{
return (hdev->descriptor.bDeviceProtocol == USB_HUB_PR_SS &&
le16_to_cpu(hdev->descriptor.bcdUSB) >= 0x0310 &&
hdev->bos->ssp_cap);
}
3.初始化
hub的驱动定义为hub_driver
,也是一个usb_driver
结构体。hub驱动的匹配方式由hub_id_table
定义,可以根据PID、VID、接口类型匹配。
[drivers/usb/core/hub.c]
static const struct usb_device_id hub_id_table[] = {
{ .match_flags = USB_DEVICE_ID_MATCH_VENDOR
| USB_DEVICE_ID_MATCH_PRODUCT
| USB_DEVICE_ID_MATCH_INT_CLASS,
.idVendor = USB_VENDOR_SMSC,
.idProduct = USB_PRODUCT_USB5534B,
.bInterfaceClass = USB_CLASS_HUB,
.driver_info = HUB_QUIRK_DISABLE_AUTOSUSPEND},
{ .match_flags = USB_DEVICE_ID_MATCH_VENDOR
| USB_DEVICE_ID_MATCH_PRODUCT,
.idVendor = USB_VENDOR_CYPRESS,
.idProduct = USB_PRODUCT_CY7C65632,
.driver_info = HUB_QUIRK_DISABLE_AUTOSUSPEND},
{ .match_flags = USB_DEVICE_ID_MATCH_VENDOR
| USB_DEVICE_ID_MATCH_INT_CLASS,
.idVendor = USB_VENDOR_GENESYS_LOGIC,
.bInterfaceClass = USB_CLASS_HUB,
.driver_info = HUB_QUIRK_CHECK_PORT_AUTOSUSPEND},
{ .match_flags = USB_DEVICE_ID_MATCH_DEV_CLASS,
.bDeviceClass = USB_CLASS_HUB},
{ .match_flags = USB_DEVICE_ID_MATCH_INT_CLASS,
.bInterfaceClass = USB_CLASS_HUB},
{ } /* Terminating entry */
};
MODULE_DEVICE_TABLE(usb, hub_id_table);
static struct usb_driver hub_driver = {
.name = "hub",
.probe = hub_probe,
.disconnect = hub_disconnect,
.suspend = hub_suspend,
.resume = hub_resume,
.reset_resume = hub_reset_resume,
.pre_reset = hub_pre_reset,
.post_reset = hub_post_reset,
.unlocked_ioctl = hub_ioctl,
.id_table = hub_id_table,
.supports_autosuspend = 1,
};
hub驱动匹配和初始化过程如下图所示,主要的工作流程有:
- 当注册root hub时,会调用device_add注册设备。内核调用usb_hub_init函数初始化hub,此时会调用usb_register_driver注册hub驱动hub_driver。hub设备或驱动注册时都会调用usb_device_match匹配到对方,若匹配成功,则hub_probe函数被调用,进入hub的初始化流程。
hub_probe
函数中做了两件事,第一件是初始化轮询hub所用的工作队列和定时器,hub_event
比较重要,用于处理USB设备连接、断开、低功耗模式等,后续会介绍,第二件是配置hub,具体的工作如下:- 获取hub描述符,hub有专用的描述。
- 初始化用于事务分割的工作队列。主要是将USB2.0事物转换成USB1.0或USB1.1。
- 通过请求获取hub的信息和状态。
- 分配hub中断传输的urb,用以查询hub状态。
- 填充hub中断传输的urb,回调函数为
hub_irq
。 - 给hub的每个port创建usb_port数据结构。
- 使能hub,主要是给port上电,检查port状态、提交之前设置的中断传输urb,用于查询hub的状态;最后调度处理hub事件的工作队列,当有事件发生时会调用
hub_event
处理。
4.枚举设备
USB主机通过hub感知USB设备的连接、断开、状态变化等事件。当事件发生会产生xHCI中断,在中断处理函数中处理USB设备事件,具体的流程如下:
-
产生中断后会最终调用到
handle_port_status
函数处理hub port口事件,没事件发生则直接退出,有事件发生,且status_urb
空闲,则走下面的流程。- 设置
HCD_FLAG_POLL_RH
标志,调用usb_hcd_poll_rh_status
函数开始轮询hub状态。 - 调用xHCI驱动的
xhci_hub_status_data
函数查询root hub每个port的状态,若有变化,则会反汇事件数据的长度。port的状态由port_开头的宏定义定义。 - 清除轮询hub的标志,将查询hub状态的
status_urb
从对应的端点队中移除,然后调用usb_hcd_giveback_urb
,最终通过调度tasklet处理hub事件。详细的处理过程后续介绍。
- 设置
-
当status_urb被占用时,即hcd->status_urb为NULL时,说明上一个hub事件还没处理完(tasklet没处理完),因此会设置HCD_FLAG_POLL_RH标志,调用mod_timer开启定时器,定时器到期后调用rh_timer_func处理hub事件,处理流程和上面一样。
xHCI驱动轮询的hub事件定义如下所示。root hub的port状态由一个32位的寄存器表示,地址保存在struct xhci_port
中的addr
变量中,事件的位域定义在xhci.h头文件中。
[drivers/usb/host/xhci.h]
/* true: port has an over-current condition */
#define PORT_OC (1 << 3)
/* true: connect status change */
#define PORT_CSC (1 << 17)
/* true: port enable change */
#define PORT_PEC (1 << 18)
/* true: warm reset for a USB 3.0 device is done. A "hot" reset puts the port
* into an enabled state, and the device into the default state. A "warm" reset
* also resets the link, forcing the device through the link training sequence.
* SW can also look at the Port Reset register to see when warm reset is done.
*/
#define PORT_WRC (1 << 19)
/* true: over-current change */
#define PORT_OCC (1 << 20)
/* true: reset change - 1 to 0 transition of PORT_RESET */
#define PORT_RC (1 << 21)
/* port link status change - set on some port link state transitions:
* Transition Reason
* ------------------------------------------------------------------------------
* - U3 to Resume Wakeup signaling from a device
* - Resume to Recovery to U0 USB 3.0 device resume
* - Resume to U0 USB 2.0 device resume
* - U3 to Recovery to U0 Software resume of USB 3.0 device complete
* - U3 to U0 Software resume of USB 2.0 device complete
* - U2 to U0 L1 resume of USB 2.1 device complete
* - U0 to U0 (???) L1 entry rejection by USB 2.1 device
* - U0 to disabled L1 entry error with USB 2.1 device
* - Any state to inactive Error on USB 3.0 port
*/
#define PORT_PLC (1 << 22)
/* port configure error change - port failed to configure its link partner */
#define PORT_CEC (1 << 23)
在众多事件中,枚举USB设备是最重要的,下图描述了USB主机枚举USB设备的过程。主要的工作有:
- 通过
usb_hcd_poll_rh_status
函数轮询到了hub的port上有事件发生,最终通过调用hub_irq
函数处理这些事件。 hub_irq
函数做了两件事,第一件是重新提交查询hub状态的urb,即设置hcd->status_urb = urb
,当下一次处理hub事件时,又会调用到hub_irq
;第二件事是调度工作队列处理hub事件。- hub事件通过
hub_event
函数处理,该函数会遍历每个port,当port上有USB设备连接时,调用hub_port_connect
处理。 - 首先为设备分配
usb_device
数据结构,接着设置USB设备号,然后初始化设备,包括复位设备并获取设备速度、设置设备地址、获取设备描述符。 - 枚举USB设备和匹配USB设备驱动在
usb_new_device
函数中完成,主要的工作如下:- 获取USB设备的配置、接口、端点等描述符,若开启了相关选项,则内核会打印USB设备的详细信息。
- 通过
usb_device_match
匹配设备驱动,即usb_device_type
类型,最终会调用到内核提供的通用的USB设备驱动usb_generic_driver_probe
。 - 最后遍历USB设备的所有接口,调用
usb_device_match
为接口匹配驱动(USB驱动和USB接口对应,此时的匹配类型为usb_if_device_type
),匹配成功后接口驱动的probe函数被调用。