动态枚举

动态枚举 是驱动程序在系统运行时检测和报告连接到系统的设备数量和类型的更改的能力。

如果连接到父设备的设备数量或类型取决于系统的配置,则总线驱动程序必须使用动态枚举。 其中一些设备可能始终连接到系统,有些设备可能在系统运行时插入并拔下。

例如,插入系统 PCI 总线的设备数量和类型依赖于系统,但它们是永久性的,除非用户关闭电源、打开案例,以及使用螺丝刀添加或删除设备。 另一方面,用户可以在系统运行时插入或拔下电缆来添加或删除 USB 设备。

动态子列表

通过框架,驱动程序通过提供框架子列表对象来支持动态枚举。 每个子列表对象都表示连接到父设备的子设备的列表。 父设备的总线驱动程序必须标识父设备的子设备,将其添加到父设备的子列表中,并为每个子设备创建一个物理设备 (PDO) 对象。

每次驱动程序创建表示设备的 FDO 的框架设备对象时,框架会为设备创建一个空的默认子列表。 驱动程序可以通过调用 WdfFdoGetDefaultChildList 获取设备默认子列表的句柄。 通常,如果要编写枚举设备子级的总线驱动程序,则驱动程序可以将子级添加到默认子列表中。 如果需要创建其他子列表,驱动程序可以调用 WdfChildListCreate

在驱动程序可以使用子列表之前,它必须通过初始化 WDF_CHILD_LIST_CONFIG 结构,将结构传递给 WdfFdoInitSetDefaultChildListConfig(对于默认子列表)或 WdfChildListCreate(对于其他子列表)来配置子列表对象。

动态子说明

每次总线驱动程序标识子设备时,都必须将子设备的说明添加到子列表中。 子 说明 由必需的标识 说明和 可选的地址 说明组成

标识说明 标识说明是一种结构,包含唯一标识驱动程序枚举的每个设备的信息。 驱动程序定义此结构,但其第一个成员 必须是WDF_CHILD_IDENTIFICATION_DESCRIPTION_HEADER结构

通常,标识说明包含设备的设备标识字符串(可能是序列号)以及有关设备在总线上的位置的信息,例如槽号。

驱动程序可以提供以下一组回调函数,使框架能够操作标识说明中的信息:

通常,如果驱动程序的标识说明结构包含指向动态分配的缓冲区的指针,则需要提供这些回调函数。 有关这些回调函数的用途详细信息,请参阅其参考页。

地址说明 地址说明是一种结构,包含驱动程序需要的信息,以便它可以访问其总线上的设备(如果设备接通电源时信息可能会更改)。 驱动程序定义此结构,但其第一个成员 必须是WDF_CHILD_ADDRESS_DESCRIPTION_HEADER结构

地址说明是可选的。 如果设备的地址信息在设备接通电源和拔下设备的时间之间不能更改,则设备的所有地址信息都可以存储在标识说明中。 例如,当设备接通电源时,USB 控制器会向设备分配地址,并且这些地址不会更改。

另一方面,某些总线使用可以更改的寻址信息。 例如,IEEE 1394总线使用"生成计数",即发生的总线重置数。 对设备的每个异步 I/O IEEE 1394必须包含生成计数。 由于此地址信息可能会更改,因此驱动程序必须存储在地址说明中。

驱动程序可以提供以下一组回调函数来操作地址说明中的信息:

通常,如果驱动程序的地址说明结构包含指向动态分配的缓冲区的指针,则需要提供这些回调函数。 有关这些回调函数的用途详细信息,请参阅其参考页。

将设备添加到动态子列表

当框架调用总线驱动程序的 EvtDriverDeviceAdd 回调函数时,回调函数必须调用 WdfDeviceCreate 为父设备(通常是总线适配器)创建 FDO。 有关创建 FDO 的信息,请参阅 在函数驱动程序中创建设备对象。 然后,驱动程序必须枚举父设备的子级,然后将子级添加到子列表中。

(可选)驱动程序可以调用 WdfDeviceSetBusInformationForChildren ,为框架提供有关总线的信息。 建议这样做,因为它使子设备和应用能够更轻松地识别总线。

若要将子级添加到子列表,驱动程序必须针对找到的每个子设备调用 WdfChildListAddOrUpdateChildDescriptionAsPresent 。 此调用通知框架驱动程序已发现连接到父设备的子设备。 当驱动程序调用 WdfChildListAddOrUpdateChildDescriptionAsPresent 时,它会提供标识说明和(可选)地址说明。

驱动程序调用 WdfChildListAddOrUpdateChildDescriptionAsPresent 报告新设备后,框架会通知 PnP 管理器新设备存在。 然后,PnP 管理器为新设备生成设备堆栈和驱动程序堆栈。 在此过程过程中,框架调用总线驱动程序的 EvtChildListCreateDevice 回调函数。 此回调函数必须调用 WdfDeviceCreate 以创建新设备的 PDO。

通常,多个子设备连接到父设备,因此总线驱动程序需要多次调用 WdfChildListAddOrUpdateChildDescriptionAsPresent 。 为此,最有效的方法如下:

  1. 调用 WdfChildListBeginScan

  2. 针对 每个子设备调用 WdfChildListAddOrUpdateChildDescriptionAsPresent

  3. 调用 WdfChildListEndScan

如果使用对 WdfChildListBeginScanWdfChildListEndScan 的调用来环绕驱动程序的动态枚举,则框架将存储对子列表的所有更改,并通知 PnP 管理器在驱动程序调用 WdfChildListEndScan 时所做的更改。 稍后,框架会为子列表中的每个设备调用总线驱动程序 的 EvtChildListCreateDevice 回调函数。 此回调函数调用 WdfDeviceCreate 为每个新设备创建 PDO。

当驱动程序调用 WdfChildListBeginScan 时,框架将以前报告的所有设备标记为不再存在。 因此,驱动程序必须为驱动程序可以检测到的所有子代调用 WdfChildListAddOrUpdateChildDescriptionAsPresent ,而不只是新发现的子代。 若要将单个子级添加到子列表,驱动程序可以调用 WdfChildListUpdateAllChildDescriptionsAsPresent ,而无需先调用 WdfChildListBeginScan

更新动态子列表

有两种常用方法来更新动态子列表中的信息:

  1. 当父设备收到指示子级到达或删除的中断时,如果设备已接通电源,驱动程序的 EvtInterruptDpc 回调函数将调用 WdfChildListAddOrUpdateChildDescriptionAsPresent ;如果设备已拔下,则调用 WdfChildListUpdateChildDescriptionAsMissing

  2. 驱动程序可以提供 EvtChildListScanForChildren 回调函数,每次父设备进入工作状态时,框架 (D0) 函数。 此回调函数应调用 WdfChildListBeginScanWdfChildListAddOrUpdateChildDescriptionAsPresent (或 WdfChildListUpdateAllChildDescriptionsAsPresent) 和 WdfChildListEndScan 来枚举所有子设备。

可以在驱动程序中同时使用其中一种或两种技术。

遍历动态子列表

如果希望驱动程序检查子列表的内容,可以使用以下方法之一遍历列表:

访问 PDO 的标识和地址说明

驱动程序可以调用以下方法来访问 PDO 的标识说明或地址说明:

处理重新枚举请求

支持动态枚举的基于框架的总线驱动程序可以接收请求,以通过 REENUMERATE_SELF_INTERFACE_STANDARD 接口 重新枚举 特定子设备。 有关详细信息,请参阅 处理枚举请求