串行总线驱动程序层

串行总线驱动程序基于 ACPI 创建的 PDO 加载,可以查询和访问系统资源(例如 GPIO 和 I2C 控制器)来执行信号控制。

电源控制的示例机制

基于 USB 的蓝牙提供了一种内置机制,用于支持睡眠和唤醒的带内信号。 但是,在 SoC 平台中,可以使用各种控制器让支持电源控制的机制更为灵活(且可自定义)。

下面是一个演示空闲和唤醒信号的示例实现:

  • GPIO 中断 HOST_WAKE 线路 - 当需要唤醒主机以从远程设备(例如远程连接)处理请求时,蓝牙控制器会发出信号
  • GPIO 信号线 BT_ENABLE 线 - 由总线驱动程序设置,并在无线电处于活动状态(D0 中的核心堆栈)时断言,或在蓝牙核心堆栈检测到空闲状态(进入 D2)时断言。

这两条 GPIO 线路采用系统资源的形式,在驱动程序加载过程中作为常规中断和新的 GPIO 信号报告给串行总线驱动程序。 其互连属性由系统集成商和蓝牙芯片供应商 (IHV) 在 ACPI 表中定义。 串行总线驱动程序可以查询和缓存这些相关控制器的连接 ID,以便打开和访问其资源。

启动以启用空闲

串行总线驱动程序需要执行以下任务以支持 S0 系统电源状态中的空闲:

  1. 报告 PnP 和电源管理功能;作为集成设备,其子 PDO 的可移动标志应设置为 WdfFalse。
  2. 报告它可以支持其函数驱动程序空闲(蓝牙核心驱动程序)。
  3. 处理唤醒和唤醒信号的布防和撤防操作。
  4. 接收设备电源状态通知,并将 I/O 完成与当前设备电源状态同步。

电源管理功能

总线驱动程序创建的子 PDO 会设置电源功能以启用空闲状态支持。 子 PDO 由电源管理器管理,包括用于指示以下各项的设置:

  • 其支持 D2 设备状态的功能。
  • 其进入空闲状态和从 D2 唤醒的功能。
  • 其系统状态到设备状态的映射,并与蓝牙核心驱动程序同步。
WDF_DEVICE_POWER_CAPABILITIES_INIT(&PowerCaps);
…
PowerCaps.DeviceD1 = WdfFalse;
PowerCaps.DeviceD2 = WdfTrue;
…
PowerCaps.DeviceWake = PowerDeviceD2;

PowerCaps.DeviceState[PowerSystemWorking]   = PowerDeviceD0;
PowerCaps.DeviceState[PowerSystemSleeping1] = PowerDeviceD2;
PowerCaps.DeviceState[PowerSystemSleeping2] = PowerDeviceD2;
PowerCaps.DeviceState[PowerSystemSleeping3] = PowerDeviceD2;
PowerCaps.DeviceState[PowerSystemHibernate] = PowerDeviceD2;
PowerCaps.DeviceState[PowerSystemShutdown]  = PowerDeviceD3;
..
WdfDeviceSetPowerCapabilities(ChildDevice, &PowerCaps);

子 PDO 会创建一个 WDF 队列,用于从蓝牙核心驱动程序接收 IOCTL(I/O 控制)请求。 在启动设备之前,此类请求确实会查询接口版本和静态功能;因此,此队列不能由电源管理。

QueueConfig.PowerManaged = WdfFalse;

QueueConfig.EvtIoDeviceControl = PdoIoQuDeviceControl;

Status = WdfIoQueueCreate(ChildDevice, 
                          &QueueConfig, 
                          WDF_NO_OBJECT_ATTRIBUTES,
                          &Queue);

针对空闲功能的蓝牙传输特定查询

除了报告电源管理功能(如上一节突出显示),子 PDO 还会响应蓝牙核心驱动程序的查询,了解其进入空闲状态的功能。 为了支持 S0 中的空闲状态,将设置以下标志:

FdoExtension->BthXCaps.IsDeviceIdleCapable = TRUE;

唤醒的布防和撤防

空闲支持要求是能够从远程蓝牙设备接收唤醒请求。 此类唤醒请求的设置涉及对唤醒进行布防。 蓝牙函数的 PDO 可以注册以接收回调来完成布防/撤防操作:

WDF_PDO_EVENT_CALLBACKS_INIT(&Callbacks);

// Receive this callback to arming the device for wake
Callbacks.EvtDeviceEnableWakeAtBus  = PdoDevEnableWakeAtBus;

// Receive this callback to disarming the device for wake
Callbacks.EvtDeviceDisableWakeAtBus = PdoDevDisableWakeAtBus;

WdfPdoInitSetEventCallbacks(DeviceInit, &Callbacks);

如果支持上述机制,蓝牙核心驱动程序就可以启用空闲和唤醒支持。

设备电源状态通知

子 PDO 注册以接收用以进入和退出 D0 的回调,以便收到设备电源状态转换通知。 当前设备电源状态用于同步 IO 完成 - 也就是说,正常 IO 完成只能在 D0 中完成。

    //
    // Register to receive device power state change notification
    //
    WDF_PNPPOWER_EVENT_CALLBACKS_INIT(&PnpPowerCallbacks);  

    PnpPowerCallbacks.EvtDeviceD0Entry = PdoDevD0Entry;
    PnpPowerCallbacks.EvtDeviceD0Exit  = PdoDevD0Exit; 

    
    WdfDeviceInitSetPnpPowerEventCallbacks(DeviceInit, 
                                           &PnpPowerCallbacks);

为唤醒做好准备

进入空闲状态之前,串行总线驱动程序将接收回调 EvtDeviceEnableWakeAtBus,以便为唤醒做好准备。

为唤醒做好准备的机制特定于 SoC 平台的供应商,因此超出了本部分的讨论范围。 但是,Windows 预期总线驱动程序将准备好接收唤醒信号,并且会有回调函数(例如 ISR)来处理此类信号。

进入空闲

蓝牙核心驱动程序会启用基于时间的空闲检测机制。 满足空闲要求后,核心驱动程序会开始启动堆栈以进入空闲状态。 它会调用 PoRequestPowerIrp,将电源连同完成函数设置为进入 D2。 在总线驱动程序完成 IRP 后,将调用此完成函数。 此时即完成到 D2 的转换。

在转换为空闲状态时,蓝牙核心驱动程序会取消所有挂起的读取请求,并在恢复为活动状态时重启这些请求。 需要空电源托管队列才能使串行总线驱动程序本身进入空闲状态。

除了空闲超时之外,蓝牙核心驱动程序在进入空闲状态之前会需要考虑许多不同的情况,例如:

  • 等待已发出的 HCI 命令完成。 注意:蓝牙核心驱动程序在完成之前不会进入空闲状态。
  • 所有连接的设备都处于嗅探模式。

在此空闲状态下,多功能控制器可以通过蓝牙函数来限制其电源,但必须继续提供电源才能保持其易失性设置和配置。 然后,它可以依赖其唤醒机制将堆栈唤醒回活动 (D0) 状态,之后 I/O 通信可以恢复。

从睡眠状态唤醒

虽然蓝牙函数已与一个或多个设备配对并且处于睡眠状态,但其无线电会定期扫描其配对设备发出的请求。 当配对设备启动请求并由蓝牙无线电接收时,恢复为活动状态的过程将开始。 设备堆栈恢复为活动 (D0) 状态后,驱动程序可以开始为此远程请求提供服务。

此远程请求由总线驱动程序中的唤醒信号处理函数处理,如上一部分所述。 此唤醒信号处理函数应确保 PDO 的设备状态确实处于 D2 状态,然后调用 WdfDeviceIndicateWakeStatus(PDO, 成功状态)以通知 KMDF 完成 W/W(等待唤醒)IRP。 此时,可以调用此 W/W IRP 的完成函数并由其发起方(蓝牙核心驱动程序和电源策略所有者)进行处理。

W/W IRP 的完成会触发蓝牙核心驱动程序启动到 D0 的转换。 它会请求具有完成函数的 PoRequestPowerIrp 将设备电源状态设置为 D0。

在恢复为活动 D0 状态之前,串行总线驱动程序可能会收到通知 EvtDeviceDisableWakeAtBus 以禁用唤醒 - 这将完成撤消EvtDeviceEnableWakeAtBus 之前执行的操作的过程。

蓝牙驱动程序堆栈恢复为 D0 后,串行总线驱动程序就可以完成远程设备请求。

某些事件需要在串行总线驱动程序中同步,例如:

  • 进入 D2 时,应已存在挂起的 W/W Irp。 这是在为唤醒做好准备以接收唤醒信号时进行,而不是在接收 W/W Irp 时进行。 唤醒信号仅在 D2 状态下可操作。
  • 如果在进入 D2 时有数据到达(要形成数据包),并且队列中没有挂起的读取请求,则总线驱动程序可以缓存传入数据并进入 D2。 然后,它可以完成 W/W Irp(成功)将系统唤醒回 D0 以重新发送并完成读取请求。
  • Bthport 会取消所有挂起的读取请求,并在进入 D2 之前等待完成。 同时,串行总线驱动程序可能已收到完整的 HCI 数据包,并已取消排队读取请求以返回此 HCI 数据包。 串行总线驱动程序应完成此请求,然后将启动以进入 D2。

由主机端蓝牙应用程序启动的操作还可以从空闲状态唤醒堆栈。 在这种情况下,只需要设备电源状态转换,并且此操作由蓝牙核心驱动程序启动。

为了减少加电时间,串行总线驱动程序中的回调函数(例如 EnterD0 和唤醒)不应标记为可分页。

用于表达空闲/唤醒、布防/撤防和设备电源状态转换的流程图

下面是一个简化的流程图,用于演示空闲和唤醒支持的典型序列和逻辑。 此逻辑跨越许多驱动程序和线程,并且存在不会表示的异常以及极端情况(例如,主机端的应用程序也可以从空闲状态唤醒堆栈)。

说明了空闲和唤醒支持的蓝牙设备电源状态转换的流程图。

总线驱动程序自己的电源管理

串行总线驱动程序是其层的函数驱动程序 (FD) 和电源策略所有者 (PPO)。 因此,它需要处理自己的电源管理。 在其所有子级都进入较低的设备电源状态后,它可以自行进入较低的电源状态。 当它准备好进入这个较低电源状态时,它可以取消对 UART 控制器驱动程序的所有挂起的 I/O 请求 - 这允许 UART 驱动程序也进入较低的电源状态。 但是,当 UART 驱动程序电源状态恢复为活动状态时,该驱动程序应保留并还原其设备设置(包括波特率)。