动态枚举

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

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

例如,插入系统 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;如果设备已拔出,则调用 WdfChildListUpdateChildDescriptionAsMissing

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

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

遍历动态子列表

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

访问 PDO 的标识和地址说明

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

处理重新枚举请求

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