Windows下USB堆栈如何枚举USB设备
在Windows系统下,当USB集线器通过集线器的中断端点指示端口连接状态更改时,USB端口的设备枚举开始。如果端口状态指示新连接的设备,USB集线器驱动程序将使用以下步骤序列来枚举设备:
- 端口稳定去抖动
- 第一次端口复位
- 第一次获取设备描述符
- 第二次端口复位
- 设置USB 地址SET_ADDRESS
- 第二次获取设备描述符
- 配置描述符请求
- MS OS 描述符查询
- 序列号字符串描述符查询
- MS OS 扩展配置描述符请求
- MS OS 容器ID描述符查询
- 语言ID查询(字符串索引为0)
- 产品ID字符串描述符查询
- 设备限定描述符查询
- 重复设备检测
- 向 PnP 管理器报告新设备
1.集线器端口稳定去抖
USB集线器驱动程序必须观察至少100ms的时间内没有集线器的端口连接改变(USB 2.0 规范、7.1.7.3、TATTDB)。如果集线器端口在200ms后仍未稳定,USB集线器驱动程序将禁用该端口并取消枚举。并且不会向 PnP 报告任何设备。
设备接入USB集线器端口后,必须有200ms的集线器端口稳定,即不发生改变。
TATTDB和TSIGATT的时间:
- ∆t1:这是集线器端口电源开关工作所需的时间量。集线器在集线器描述符中包含这一延迟时间,可通过向集线器控制器发出的请求读取该描述符。如果将设备插入非开关端口或已打开的端口,∆t1等于零。
- ∆t2 (TSIGATT):这是从设备的VBUS达到有效电平4.01V,设备开始信号连接的有效时间。∆t2表示设备内部电源稳定以及D+或D-在集线器处达到VIH(最小)所需的时间。对于所有集线器和设备实施,t2必须小于100毫秒。(该要求仅适用于设备从总线上取电的情况。)
- ∆t3 (TATTDB):这是由USB系统软件提供的最小持续时间为100 ms的去抖动时间。它确保了软件尝试重置连接的设备之前,电气和机械连接是稳定的。当USB系统软件收到连接检测通知时,间隔开始。如果存在断开连接,间隔将重新开始。去抖动间隔确保在向设备发送任何请求之前,设备的电源至少稳定100 ms。
- ∆t4 (T2SUSP):任何时候,当一个设备没有监察到总线活动时,它必须遵守进入暂停状态的规则
- ∆t5 (TDRST):集线器复位设备的时间
- t6 (TRSTRCY) :USB系统软件保证复位恢复时间至少为10毫秒。在重置恢复时间内,设备对寻址到默认设备地址的任何总线事务的响应未定义。
2.第一次端口复位
集线器端口去抖动成功完成,集线器驱动程序将向端口发出复位请求。在正常操作中,这将导致端口状态转换为连接和启用状态,并且设备本身将响应默认的 USB 地址 0。
所有 USB 设备的端口复位都通过基于每个主机控制器的“枚举锁”进行序列化,因为在任何时候只能使用默认 USB 地址 0 启用一个 USB 设备。集线器驱动程序将在发出第一个端口重置请求之前获取枚举锁,并在设备分配了非默认 USB 地址或枚举被取消时释放它。集线器驱动程序对端口重置请求的超时时间为5秒,以防它永远不会完成。
在等待第一个端口复位完成后,集线器驱动程序必须能够处理以下事件:
- 设备断开:枚举被取消。没有设备报告给 PnP。
- 过流变化:在这种情况下,通常会取消枚举,除非确定过电流是虚假的。没有设备报告给 PnP。
- 端口复位超时:在第一次重置请求超时的情况下,集线器驱动程序将通过返回到“第一端口重置”状态的开始来尝试重试枚举最多 3 次。每次重试之间会出现 500 毫秒的延迟,以允许设备稳定下来。如果在第 3 次重试时端口重置超时,则枚举将被取消,并向 PnP 报告“未知设备”。
如果端口重置请求成功完成,集线器驱动程序将根据当前端口状态进行如下操作:
- 设备断开:枚举被取消。没有设备报告给 PnP。
- 端口连接和禁用:端口重置完成被忽略。将允许重置超时运行,并且将根据需要重试端口重置。
- 端口连接和暂停:枚举被取消。没有设备报告给 PnP。
- 端口过流:端口重置完成被忽略。将允许重置超时运行,并且将根据需要重试端口重置。
- 端口已启用并已连接:这表明端口重置成功。集线器驱动程序延迟至少 10 毫秒以允许重置恢复(USB 2.0 规范、7.1.7.3、TRSTRCY)。集线器驱动程序移动到“第一个设备描述符请求”状态。
3.第一个获取设备描述符
USB 驱动程序堆栈发出对 USB 设备描述符的请求(描述符类型设备为 GET_DESCRIPTOR),使用默认的 USB 地址 0,低速设备最大数据包大小为 8 字节,全速和高速设备最大数据包大小为 64 字节设备。
此描述符请求仅用于确定默认控制端点的正确最大数据包大小,如偏移7处的设备描述符的 bMaxPacketSize0字段中指定的 那样 。 USB设备需要返回至少前8个字节的设备描述符。
当请求设备描述符时,集线器驱动程序将指定 64 字节的传输大小。这样做是因为如果请求大小较小,一些较旧的 USB 设备将表现不佳。我们还发现一些 USB 设备在返回设备描述符时会乱码,但仍会返回至少前 8 个字节的有效数据。因此,如果设备返回了至少 8 个字节的数据,则传输错误将被忽略。
如果对设备描述符的请求失败,集线器驱动程序将通过返回到“第一端口重置”状态来重试枚举最多 3 次。如果集线器驱动程序已经重试了 3 次枚举,则枚举将被取消并向 PnP 报告“未知设备”。
如果设备描述符请求成功,集线器驱动程序将移动到“第二次端口重复位步骤。默认端点的所有进一步控制传输将使用设备描述符中指定的最大数据包大小。
4.第二个端口重置
在 USB 的早期,如果某些 USB 设备没有为第一个请求返回完整的设备描述符,它们会被第二次请求设备描述符弄糊涂。为了允许这些设备成功枚举,有必要重置设备描述符的第一次和第二次请求之间的端口。
集线器驱动程序对第二个端口重置请求使用 5 秒超时,以防它永远不会完成。在等待第二次端口重置完成时,集线器驱动程序必须能够处理以下事件(这与第一次重置请求中的处理基本相同):
- 设备断开:枚举被取消。没有设备报告给 PnP。
- 过流变化:枚举被取消。没有设备报告给 PnP。
- 端口复位超时:在第二次重置请求超时的情况下,集线器驱动程序将尝试通过返回到“第一端口重置”状态的开头来重试枚举最多 3 次。如果在第 3 次重试时端口重置超时,则枚举将被取消,并向 PnP 报告“未知设备”。
如果端口重置请求成功完成,集线器驱动程序将根据当前端口状态进行如下操作:
- 设备断开:枚举被取消。没有设备报告给 PnP。
- 端口连接和禁用:端口重置完成被忽略。将允许重置超时运行,并且将根据需要重试端口重置。
- 端口连接和暂停:枚举被取消。没有设备报告给 PnP。
- 端口过流:端口重置完成被忽略。将允许重置超时运行,并且将根据需要重试端口重置。
- 端口已启用并已连接:这表明端口重置成功。集线器驱动程序延迟至少 10 毫秒以允许重置恢复(USB 2.0 规范、7.1.7.3、TRSTRCY)。集线器驱动程序移动到下一个枚举状态“设置 USB 地址”。如果必须至少重试一次枚举,集线器驱动程序将在成功重置端口后延迟 100 毫秒。
5.设置 USB 地址
USB 驱动程序堆栈分配一个唯一的(每个控制器)USB 设备地址并向设备发出 SET_ADDRESS 请求。如果 SET_ADDRESS 请求失败或超时,则取消枚举并向 PnP 报告“未知设备”。
如果 SET_ADDRESS 成功,集线器驱动程序将等待至少 10 毫秒以允许设备稳定,然后再进入“第二个设备描述符请求”状态。
6.第二个设备描述符请求
USB 驱动程序堆栈将发出对完整 USB 设备描述符(描述符类型设备为 GET_DESCRIPTOR)的第二个请求。如果请求失败或超时,端口将被禁用,并通过返回到“第一端口重置”状态来重试枚举。如果集线器驱动程序已重试枚举 3 次,则取消枚举并向 PnP 报告“未知设备”。
成功完成对设备描述符的第二次请求后,集线器驱动程序将验证设备描述符,如下所示:
- 所述 bLength 字段是等于或大于一个USB设备描述符的大小更大,如在USB 2.0规范定义的。
- 所述 bDescriptorType 字段等于描述符类型装置(1)。
如果验证失败,端口将被禁用,并通过返回到“第一端口重置”状态来重试枚举。如果集线器驱动程序已重试枚举 3 次,则取消枚举并向 PnP 报告“未知设备”。
成功验证设备描述符后,集线器驱动程序将缓存描述符,释放枚举锁,并进入“配置描述符请求”状态。
7.配置描述符请求
USB 驱动程序堆栈将发出对设备的 USB 配置描述符(描述符类型配置为 GET_DESCRIPTOR)的请求。出于兼容性原因,配置描述符请求将指定 255 字节的长度。
如果配置描述符请求完成并出现错误或超时,集线器驱动程序将禁用端口并通过返回“第一端口重置”状态重试枚举。如果集线器驱动程序已重试枚举 3 次,则取消枚举并向 PnP 报告“未知设备”。
如果配置描述符请求成功完成,集线器驱动程序将验证为请求返回的字节数是否大于或等于配置描述符的 wTotalLength 值。如果它不大于或等于 wTotalLength ,集线器驱动程序将重试一次配置描述符请求,以确保设备不会在描述符中返回无效数据。
成功完成配置描述符请求后,USB 驱动程序堆栈将验证描述符如下:
- 所述 bLength 字段是等于或大于一个USB配置描述符的大小更大,如在USB 2.0规范定义的。
- 所述 bDescriptorType 字段等于描述符类型配置(2)。
如果验证失败,集线器驱动程序将禁用端口并通过返回“第一端口重置”状态重试枚举。如果集线器驱动程序已重试枚举 3 次,则取消枚举并向 PnP 报告“未知设备”。
如果验证成功,配置描述符将被缓存,并且集线器驱动程序进入“MS OS 描述符查询”状态。
8.MS OS 描述符查询
Microsoft 定义了一组特定于供应商的 USB 描述符,称为 Microsoft OS Feature Descriptors ,在设备枚举时进行查询。
如果 USB 设备描述符的 bcdUSB 字段等于 0x0100 或 0x0110,集线器驱动程序将跳过对 MS OS 描述符的查询并进入“序列号字符串描述符查询”状态。
如果集线器驱动程序以前从未枚举过与被枚举设备具有相同 VID/PID/修订版的设备,它将查询该设备以获取 MS OS 字符串描述符(描述符类型 STRING 的 GET_DESCRIPTOR),它使用索引 0xEE。它将指定 0x00 的语言 ID。
如果设备返回 MS OS 描述符,集线器驱动程序将验证描述符如下:
- 该 MicrosoftString 字段必须等于“MSFT100”。
验证后,描述符的 bVendorCode 字段中的值 将按 VID/PID/修订版存储在注册表中,位于“osvc”注册表值中的 USBFLAGS 注册表子项下。具有相同 VID/PID/修订版的任何设备的后续枚举 将从该注册表值中读取 bVendorCode ,而不是查询设备。
集线器驱动程序然后将移动到“序列号字符串描述符查询”状态。
9.序列号字符串描述符查询
如果 USB 设备描述符报告非零序列号字符串索引,集线器驱动程序将使用美国英语语言 ID (0x409) 和序列号字符串索引查询序列号字符串描述符(GET_DESCRIPTOR for Descriptor Type STRING)。
集线器驱动程序对所有字符串描述符执行以下验证:
- 请求返回的字节数必须大于或等于 bLength 字段。
- 请求返回的字节数必须大于或等于 bLength 字段。
- bLength 字段必须大于 2 个字节。
- bDescriptorType 字段必须等于描述符类型 STRING (3)。
- bLength 字段必须是偶数,因为字符串是 Unicode 字符串。
如果此验证通过,集线器驱动程序将专门针对序列号字符串描述符执行以下验证:
- 该字符串是非 NULL。
- 字符串不超过 255 个字节。
- 描述符中返回的描述符类型是 STRING 类型。
- 字符串中的字节数是偶数,因为它是一个 Unicode 字符串。
- 该字符串不包含任何无效字符:
- 该字符的值必须大于或等于 0x20。
- 字符的值必须小于或等于 0x7F。
- 字符不能是逗号 (‘,’=0x2C)。
如果上述任一验证失败,序列号将被丢弃,否则将被缓存。
无论序列号查询和验证的结果如何,如果设备支持 MS OS 描述符,集线器驱动程序将进入“MS OS 扩展配置描述符请求”状态,否则将进入“语言 ID 查询”状态。
10.MS OS 扩展配置描述符请求
如果设备不是复合设备,集线器驱动程序将发出对 MS OS 扩展配置描述符的请求。
软件将通过指定等于描述符头的数据大小来发出对该描述符的初始请求。这将用于确定描述符的存在,并确定其大小。如果请求成功,集线器驱动程序将按如下方式验证标头:
- 返回的字节数必须等于头的定义大小。
- 二进制编码的十进制报头版本字段必须是 1.00。
- 标头的 wIndex 字段必须设置为 4。
- 标头的 bCount 字段必须非零。
- 头的 dwLength 字段必须等于头的大小,加上 bCount 字段的值乘以配置描述符的功能结构的大小。
一旦描述符头被验证,集线器驱动程序将使用由设备在描述符头中返回的整个描述符的大小重新发出对描述符的请求。
如果设备成功返回 MS OS 扩展配置描述符,集线器驱动程序将验证描述符如下:
- 将针对接口关联描述符和单接口函数描述的功能解析标准 USB 配置描述符,以确定设备上的功能总数。
将对 MS OS 扩展配置描述符标头执行以下验证:
- 头的 dwLength 值必须大于或等于头结构的大小。
- 头的 dwLength 值必须小于或等于头的大小加上 256 个扩展配置描述符函数。
- 标头的 dwLength 值必须小于或等于设备为描述符返回的字节数。
- 标头的 wIndex 值必须等于 4,即 MS 扩展配置描述符索引。
- 标头的 bCount 值必须小于或等于在 USB 配置描述符中找到的函数数。
- 头的 dwLength 值必须大于或等于头结构的大小加上头的 bCount 乘以扩展配置描述符功能结构的大小。
将对每个函数描述符执行以下验证:
- bFirstInterfaceNumber 小于等于 256。
- bFirstInterfaceNumber 对应于由接口关联描述符或设备的 USB 配置描述符中的单接口函数描述的函数的第一个接口。
- CompatibleID 字符串仅包含大写 ASCII 字符(“A”-“Z”)、数字(“0”-“9”)和/或下划线字符(“_”)。
- SubCompatibleID 字符串仅包含大写 ASCII 字符(“A”-“Z”)、数字(“0”-“9”)和/或下划线字符(“_”)。
- 功能描述符的数量必须等于标头中的 bCount 值。
软件将进入“MS OS Container ID Descriptor Query”状态。
11.MS OS 容器 ID 描述符查询
Windows 7 引入了 Container ID 的概念 ,它用于对属于物理设备的所有功能进行分组。有关 USB 如何生成容器 ID 的详细信息,请参阅 此 白皮书。
如果设备的 VID/PID/修订版先前已通过注册表被标记为不支持容器 ID 描述符,则集线器驱动程序将进入“语言 ID 查询”状态。
如果集线器描述符和 ACPI 命名空间都没有将设备描述为不可移动,并且设备支持 MS OS 描述符,则 USB 堆栈将查询设备的 MS OS 容器 ID,否则集线器驱动程序将移动到“语言” ID 查询”状态。
设备必须通过设置 MS OS 描述符的 bFlags 标志的第 1 位来指示对容器 ID 的支持。如果设置了该位,软件将发出对 MS OS Container ID Descriptor 标头的请求。设置数据包将指定 wIndex 为 6,并将设备指定为接收方。如果成功返回描述符,集线器驱动程序将验证描述符如下:
- 返回的字节数必须等于 MS OS Container ID Descriptor 标头的大小。
- MS OS Container ID Descriptor 标头的 bcdVersion 必须为 0x100。
- MS OS Container ID Descriptor 标头的 wIndex 必须为 6。
- MS OS Container ID Descriptor 头的 dwLength 必须等于 MS OS Container ID Descriptor 的大小。
如果标头成功验证,软件将发出对整个 MS 操作系统容器 ID 描述符的请求。设置数据包将再次指定 wIndex 为 6,并将设备作为接收方。
如果成功返回描述符,集线器驱动程序将验证描述符如下:
- 返回的字节数等于 MS OS Container ID Descriptor 的定义大小
- 容器 ID 并非全为零。
如果对容器 ID 描述符的请求因任何原因失败,将设置一个注册表值以指示集线器驱动程序应在将来枚举具有相同 VID/PID/修订版的设备时跳过此请求。然后,集线器驱动程序将通过返回到“第一端口重置”状态来禁用端口并重试枚举。如果集线器驱动程序已重试枚举 3 次,则取消枚举并向 PnP 报告“未知设备”。
12.语言 ID 查询
USB 集线器驱动程序将查询设备以获取支持的语言 ID 数组。集线器驱动程序将对索引 0 处的字符串描述符发出查询,该字符串描述符是设备支持的 2 字节 LANGID 代码数组,如 USB 2.0 规范的第 9.6.7 节中所定义。
集线器驱动程序将对语言 ID 字符串描述符执行标准字符串描述符验证,如之前“序列号字符串描述符查询”状态的描述中所述。如果发现字符串有效,集线器驱动程序将缓存字符串中返回的数据。然后它将移动到“产品 ID 字符串查询”状态。
13.产品ID字符串查询
如果 USB 设备描述符的 iProduct 字段非零,集线器驱动程序将使用 iProduct 字段中指定的字符串索引和 0x409 的英语语言 ID 发出对产品 ID 字符串的请求。
如果字符串描述符请求成功完成,集线器驱动程序将对产品 ID 字符串描述符执行标准字符串描述符验证。如果验证成功,集线器驱动程序将缓存该字符串。然后它将移动到“设备限定符描述符查询”状态。
14.设备限定符描述符查询
如果设备连接到 USB 1.1 集线器,以全速运行,并且其 USB 设备描述符 bcdUSB 字段大于或等于 0x200,则集线器驱动程序将为描述符类型 DEVICE_QUALIFIER (6) 发出 GET_DESCRIPTOR。
请求的成功完成表明设备可以支持 USB 2.0 高速操作。
15.重复设备检测
USB 驱动程序堆栈必须处理 EHCI 配套控制器设计的一个工件,当启用或禁用 USB 2.0 控制器时,设备可以在 USB 2.0 EHCI 主机控制器和配套 USB 1.1 控制器之间快速移动。这将创建一个场景,其中 USB 设备的新实例可以在其他主机控制器上的先前实例被报告为已删除到 PnP 管理器之前报告给 PnP 管理器。如果设备有序列号,这会导致错误检查,因为 PnP 管理器会看到两个设备节点报告相同的唯一实例 ID。如果在系统挂起时将 USB 设备移动到不同的端口,也会发生此行为。
USB 集线器驱动程序维护当前连接到系统并已报告给 PnP 管理器的所有 USB 设备的列表。如果当前枚举的设备具有序列号,软件将在此列表中搜索具有相同供应商 ID、产品 ID、修订号和序列号的任何设备。如果没有找到匹配的设备,集线器驱动程序将进入“向 PnP 管理器报告新设备”状态。
如果找到匹配的设备,集线器驱动程序将根据以下逻辑处理它:
如果匹配设备和枚举设备在同一端口上,则是设备快速分离/重新连接(可能是虚假连接更改)的情况。不采取任何措施,因为在将新实例报告为到达 PnP 管理器的同时,前一个实例将报告为已删除。
如果检测到匹配设备不再实际存在,软件将延迟 5 秒,等待匹配设备报告给 PnP 管理器已删除。如果匹配设备在延迟结束时未报告为已移除,则禁用端口并通过返回“第一端口重置”状态重试枚举。如果软件已经重试枚举 3 次,则禁用端口并取消枚举。不会向 PnP 管理器报告任何设备。
如果匹配的设备在物理上仍然存在,则是两个具有相同序列号的相同设备的情况。在这种情况下,新枚举设备的序列号将被丢弃,以防止 PnP 管理器触发错误检查。
16.向 PnP 管理器报告新设备
此时USB设备已成功枚举,集线器驱动程序将向PnP报告新设备。这涉及在处理 IRP_MN_QUERY_DEVICE_RELATIONS/BusRelations 时调用 IoInvalidateDeviceRelations 并报告新设备。