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

news/2024/5/17 17:36:04 标签: linux, USB, hub, 枚举, root 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设备。

<a class=USB_Hub_Architecture" />

Simple_<a class=USB_Topology" />

<a class=USB_Hub_结构图" />

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));

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_driverhub设备或驱动注册时都会调用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处理。

ROOTHUB初始化流程

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事件,处理流程和上面一样。

轮询<a class=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
      1. 在通用的USB设备驱动中,首先会选择配置,某些USB设备有多个配置,因此需要选择其中一个用于工作。
      2. 接着设置配置,包括为USB设备分配带宽、设置配置等。
      3. 最后遍历USB设备的所有接口,调用usb_device_match为接口匹配驱动(USB驱动和USB接口对应,此时的匹配类型为usb_if_device_type),匹配成功后接口驱动的probe函数被调用。

<a class=USB设备枚举过程" />

参考资料

  1. eXtensible Host Controller Interface for Universal Serial Bus
  2. Universal Serial Bus 3.2 Specification
  3. Universal Serial Bus Specification Revision 2.0

http://www.niftyadmin.cn/n/5217824.html

相关文章

【虚拟机Ubuntu 18.04配置网络】

虚拟机Ubuntu 18.04配置网络 配置网络连接方式,查看自己网关 修改主机名 修改系统配置1. 配置网络连接方式,查看自己网关 选择虚拟机镜像设置网络连接模式,可以选择桥接或者NAT连接(我这里选择是NAT连接) 确定自己网关&#xff0c;可以在虚拟机 -》 编辑 -》虚拟网络编辑器…

Python文件路径常用操作

1 文件路径 在进行数据处理时&#xff0c;经常要用代码去读文件里的数据&#xff0c;那么首先就得知道这个文件的文件路径。文件路径简单地说就是文件的存放位置。文件路径分为两块&#xff1a;文件夹路径和文件名&#xff0c;文件名又分为文件基本名和扩展名。 举例说明&…

4面试题--数据库(补充)

隔离性问题 若不考虑隔离性则会出现以下问题 1. 脏读&#xff1a;指⼀个事务在处理数据的过程中&#xff0c;读取到另⼀个 未提交 事务的数据 2. 不可重复读&#xff1a;指对于数据库中的某个数据&#xff08;同⼀个数据项&#xff09;&#xff0c;⼀个事务内的多次查询却…

处理分类问题的不平衡数据的 5 种技术

一、介绍 分类问题在机器学习领域很常见。正如我们所知&#xff0c;在分类问题中&#xff0c;我们试图通过研究输入数据或预测变量来预测类标签&#xff0c;其中目标或输出变量本质上是分类变量。 如果您已经处理过分类问题&#xff0c;那么您一定遇到过以下情况&#xff1a;其…

华为OD机试 - 围棋的气(Java JS Python C)

题目描述 围棋棋盘由纵横各19条线垂直相交组成,棋盘上一共19 x 19 = 361 个交点,对弈双方一方执白棋,一方执黑棋,落子时只能将棋子置于交点上。 “气”是围棋中很重要的一个概念,某个棋子有几口气,是指其上下左右方向四个相邻的交叉点中,有几个交叉点没有棋子,由此可…

yo!这里是异常相关介绍

目录 前言 异常的概念 异常的抛出与捕获 捕获过程 重新抛出 规范 异常体系 自定义 标准库 异常的优缺点 后记 前言 对于程序运行时发生的错误&#xff0c;比如内存错误、除0错误等类型&#xff0c;你会如何处理&#xff1f;是使用assert终止程序或是使用exit返回错误…

【古诗生成AI实战】之四——模型包装器与模型的训练

在上一篇博客中&#xff0c;我们已经利用任务加载器task成功地从数据集文件中加载了文本数据&#xff0c;并通过预处理器processor构建了词典和编码器。在这一过程中&#xff0c;我们还完成了词向量的提取。 接下来的步骤涉及到定义模型、加载数据&#xff0c;并开始训练过程。…

antDesignPro a-table样式二次封装

antDesignPro是跟element-ui类似的一个样式框架&#xff0c;其本身就是一个完整的后台系统&#xff0c;风格样式都很统一。我使用的是antd pro vue&#xff0c;版本是1.7.8。公司要求使用这个框架&#xff0c;但是UI又有自己的一套设计。这就导致我需要对部分组件进行一定的个性…