动态枚举

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

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

例如,插入系统 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 总线使用“代数”,即已发生的总线重置次数。 向 IEEE 1394 设备发送的每个异步 I/O 请求都必须包含生成计数。 由于此地址信息可能会更改,因此驱动程序必须将其存储在地址说明中。

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

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

将设备添加到动态子列表

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

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

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

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

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

  1. 调用 WdfChildListBeginScan

  2. 为每个子设备调用 WdfChildListAddOrUpdateChildDescriptionAsPresent

  3. 调用 WdfChildListEndScan

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

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

更新动态子列表

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

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

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

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

遍历动态子列表

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

访问 PDO 的标识和地址说明

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

处理重新枚举请求

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