Fork me on GitHub

USB总线-Linux内核USB3.0 Hub驱动分析(十四)

1.概述

USB Hub提供了连接USB主机和USB设备的电气接口。USB Hub拥有一个上行口,至少一个下行口,上行口连接上一级的Hub的下行口或者USB主机,连接主机的为Root Hub,下行口连接下一级Hub的上行口或者USB设备。经过Hub的扩展,一个USB主机可以和多个USB设备通信。USB Hub有如下特性:

  1. 良好的扩展性
  2. 电源管理
  3. 探测设备连接和断开连接
  4. 总线错误探测和修复
  5. 支持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驱动匹配和初始化过程如下图所示,主要的工作流程有:

  1. 当注册root hub时,会调用device_add注册设备。内核调用usb_hub_init函数初始化hub,此时会调用usb_register_driver注册hub驱动hub_driver。hub设备或驱动注册时都会调用usb_device_match匹配到对方,若匹配成功,则hub_probe函数被调用,进入hub的初始化流程。
  2. hub_probe函数中做了两件事,第一件是初始化轮询hub所用的工作队列和定时器,hub_event比较重要,用于处理USB设备连接、断开、低功耗模式等,后续会介绍,第二件是配置hub,具体的工作如下:
    1. 获取hub描述符,hub有专用的描述。
    2. 初始化用于事务分割的工作队列。主要是将USB2.0事物转换成USB1.0或USB1.1。
    3. 通过请求获取hub的信息和状态。
    4. 分配hub中断传输的urb,用以查询hub状态。
    5. 填充hub中断传输的urb,回调函数为hub_irq
    6. 给hub的每个port创建usb_port数据结构。
    7. 使能hub,主要是给port上电,检查port状态、提交之前设置的中断传输urb,用于查询hub的状态;最后调度处理hub事件的工作队列,当有事件发生时会调用hub_event处理。

4.枚举设备

USB主机通过hub感知USB设备的连接、断开、状态变化等事件。当事件发生会产生xHCI中断,在中断处理函数中处理USB设备事件,具体的流程如下:

  1. 产生中断后会最终调用到handle_port_status函数处理hub port口事件,没事件发生则直接退出,有事件发生,且status_urb空闲,则走下面的流程。

    1. 设置HCD_FLAG_POLL_RH标志,调用usb_hcd_poll_rh_status函数开始轮询hub状态。
    2. 调用xHCI驱动的xhci_hub_status_data函数查询root hub每个port的状态,若有变化,则会反汇事件数据的长度。port的状态由port_开头的宏定义定义。
    3. 清除轮询hub的标志,将查询hub状态的status_urb从对应的端点队中移除,然后调用usb_hcd_giveback_urb,最终通过调度tasklet处理hub事件。详细的处理过程后续介绍。
  2. 当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设备的过程。主要的工作有:

  1. 通过usb_hcd_poll_rh_status函数轮询到了hub的port上有事件发生,最终通过调用hub_irq函数处理这些事件。
  2. hub_irq函数做了两件事,第一件是重新提交查询hub状态的urb,即设置hcd->status_urb = urb,当下一次处理hub事件时,又会调用到hub_irq;第二件事是调度工作队列处理hub事件。
  3. hub事件通过hub_event函数处理,该函数会遍历每个port,当port上有USB设备连接时,调用hub_port_connect处理。
  4. 首先为设备分配usb_device数据结构,接着设置USB设备号,然后初始化设备,包括复位设备并获取设备速度、设置设备地址、获取设备描述符。
  5. 枚举USB设备和匹配USB设备驱动在usb_new_device函数中完成,主要的工作如下:
    1. 获取USB设备的配置、接口、端点等描述符,若开启了相关选项,则内核会打印USB设备的详细信息。
    2. 通过usb_device_match匹配设备驱动,即usb_device_type类型,最终会调用到内核提供的通用的USB设备驱动usb_generic_driver_probe
    3. 最后遍历USB设备的所有接口,调用usb_device_match为接口匹配驱动(USB驱动和USB接口对应,此时的匹配类型为usb_if_device_type),匹配成功后接口驱动的probe函数被调用。

posted @ 2024-09-14 22:27  yooooooo  阅读(2)  评论(0编辑  收藏  举报