Windows 操作系统中支持开发通用串行总线 (USB) 功能控制器驱动程序,该驱动程序与 Microsoft 提供的 USB 功能控制器扩展 (UFX) 进行通信。
Windows 为 Synopsys IP 的控制器硬件提供内置 USB 功能控制器驱动程序,例如 UfxSynopsys.sys。 它们通常需要平台级别的更改和验证,这些更改和验证通常由硬件合作伙伴或 OEM 在启动平台时执行。 此启动过程可能包括与 ACPI 集成,以通知系统驱动程序 USB 附加/分离事件,以及使用 Microsoft 提供的 HLK 测试执行其他验证。
若要编写自己的控制器驱动程序,需要:
- UFX (Ufx01000.sys) 作为 FDO 加载。 此驱动程序包含在 Windows 中;
- 链接到 Ufx01000.lib存根库。 存根库位于 WDK 中。 库转换由函数控制器驱动程序发出的调用,并将其传递到 UFX;
- 包括 WDK 中提供的 Ufxclient.h;
若要从用户模式发送请求,需要:
- GenericUSBFn.sys 作为 USB 函数类驱动程序加载。 此驱动程序包含在 Windows 中;
- 包括 WDK 中提供的 Genericusbfnioctl.h;
若要从 USB 类驱动程序发送请求,需要:
- UFX (Ufx01000.sys) 作为 FDO 加载。 此驱动程序包含在 Windows 中。
- 包括 WDK 中提供的 Usbfnioctl.h。
若要编写处理通过专有充电器充电的筛选器驱动程序,需要:
- UfxChipidea.sys 或 Ufxsynopsys.sys 作为客户端驱动程序加载到 UFX;
- 包括 WDK 中提供的 Ufxproprietarycharger.h;
编写控制器客户端驱动程序
介绍功能控制器客户端驱动程序在与 UFX) 的 USB 功能控制器扩展 (交互时执行的各种任务。 UFX 和客户端驱动程序使用导出方法和事件回调函数相互通信。 (名为 UfxDeviceXxx 或 UfxEndpointXxx) 的导出方法由 UFX 导出并由客户端驱动程序调用。 (名为 EVT_UFX_Xxx) 的回调函数在客户端驱动程序中实现,并由 UFX 调用。
UFX 以异步方式调用所有客户端驱动程序的回调函数,每个对象一次调用一个回调。 例如,有一个 USB 设备对象和三个端点对象。 一次可以调用最多四个回调函数 , 一个用于设备,每个端点一个回调函数。 对于每个回调方法,UFX 都会等待,直到客户端驱动程序调用 UfxDeviceEventComplete 以指示驱动程序已完成请求。 UFX 在等待这些导出时侦听的唯一其他导出方法是 UfxDeviceNotifyHardwareFailure。 许多客户端回调函数都是可选的。 所需函数如下所示:
- EVT_UFX_DEVICE_DEFAULT_ENDPOINT_ADD
- EVT_UFX_DEVICE_ENDPOINT_ADD
- EVT_UFX_DEVICE_HOST_CONNECT
- EVT_UFX_DEVICE_HOST_DISCONNECT
- EVT_UFX_DEVICE_ADDRESSED
初始化
初始化步骤如下:
- 当 Windows Driver Foundation (WDF) 调用客户端驱动程序的 EVT_WDF_DRIVER_DEVICE_ADD 回调实现时,函数控制器客户端驱动程序将启动初始化过程。 在该实现中,客户端驱动程序应调用 UfxFdoInit ,然后通过调用 WdfDeviceCreate 创建设备对象;
- 客户端驱动程序调用 UfxDeviceCreate 来创建 USB 设备对象并检索 UFXDEVICE 句柄;
- 客户端驱动程序调用 UfxDeviceNotifyHardwareReady 向 UFX 指示它现在可以调用客户端驱动程序的回调函数;
- UFX 执行初始化任务,例如:
- UFX 调用客户端驱动程序 的EVT_UFX_DEVICE_DEFAULT_ENDPOINT_ADD 实现来创建默认端点。
- UFX 为设备支持的接口创建子物理设备对象 (PDO) 。
- UFX 在发送 IOCTL_INTERNAL_USBFN_ACTIVATE_USB_BUS 请求时等待设备类驱动程序激活。 它还会等待客户端驱动程序调用指示设备已附加的 UfxDeviceNotifyAttach 。
类驱动程序通知
为了获得有关设置数据包和总线状态的通知,类驱动程序应发送 IOCTL_INTERNAL_USBFN_ACTIVATE_USB_BUS 请求。 UFX 将这些请求排队到特定于类驱动程序的通知队列中。 从客户端驱动程序收到有关总线事件的通知时,UFX 会从每个适当的队列弹出并完成请求。 为了防止类驱动程序丢失通知,UFX 为类驱动程序保留固定大小的通知队列。
设备附加和分离事件
UFX 假定设备分离,直到函数控制器客户端驱动程序调用 UfxDeviceNotifyAttach。
调用后,UFX 将设备状态设置为 USB 规范中定义的 “已开机 ”。 为了通知客户端驱动程序状态更改,UFX 调用客户端驱动程序的 EVT_UFX_DEVICE_USB_STATE_CHANGE 实现。
UFX 通知充电器驱动程序 (Cad.sys) 以帮助为设备充电。 UFX 还通过完成类驱动程序先前发送 IOCTL_INTERNAL_USBFN_BUS_EVENT_NOTIFICATION 请求来通知类驱动程序。
分离总线时,客户端驱动程序必须调用 UfxDeviceNotifyDetach 。 每次调用 UfxDeviceNotifyAttach 后,客户端只能调用 detach 一次。 在 UfxDeviceNotifyDetach 调用后,如果这不是接口 更改, 则 UFX 会调用EVT_UFX_DEVICE_HOST_DISCONNECT 。 UFX 然后继续执行所有清理任务,例如清除所有端点队列和启动默认端点队列。 UFX 调用EVT_UFX_DEVICE_USB_STATE_CHANGE 并通过完成 IOCTL_INTERNAL_USBFN_BUS_EVENT_NOTIFICATION 请求来通知类驱动程序。
硬件故障
如果发生硬件错误,客户端驱动程序应调用 UfxDeviceNotifyHardwareFailure。 作为响应,UFX 将拆毁设备堆栈,并可能尝试通过调用客户端驱动程序的 EVT_UFX_DEVICE_CONTROLLER_RESET从这种情况中恢复。 客户端应将控制器重置为其初始状态。 如果发生其他硬件故障,客户端应再次调用 UfxDeviceNotifyHardwareFailure。 在第二次调用时,UFX 将记录其状态和 bug 检查。
端口检测
端口检测由 UFX 执行。 它调用函数控制器客户端驱动程序的 EVT_UFX_DEVICE_PORT_DETECT 回调函数来确定设备所附加到的端口的类型。 客户端使用 USBFN_PORT_TYPE 中定义的端口类型之一调用 UfxDevicePortDetectComplete 或 UfxDevicePortDetectCompleteEx 进行响应。
如果客户端无法确定端口的类型,则客户端应报告 UsbfnUnknownPort。 如果端口未知或下游端口,则 UFX 会调用客户端驱动程序 的 EVT_UFX_DEVICE_HOST_CONNECT 函数。 UFX 侦听总线一段时间。 如果端口未知,但存在流量(如设置数据包),则 UFX 将采用 UsbfnStandardDownstreamPort。 否则,UFX 将端口分配为 UsbfnInvalidDedicatedChargingPort。 确定端口类型后,UFX 会通知 Cad.sys 并调用客户端驱动程序的 EVT_UFX_DEVICE_PORT_CHANGE 函数。 在 函数中,客户端驱动程序应更改硬件状态以匹配 UFX 端口类型。
端点创建
UFX 通过调用客户端驱动程序的 EVT_UFX_DEVICE_DEFAULT_ENDPOINT_ADD 创建默认端点 (端点 0) ,以便它可以 处理主机发送的设置数据包。 UFX 通过调用 EVT_UFX_DEVICE_ENDPOINT_ADD 创建其他端点。 UFX 仅在客户端驱动程序调用 UfxDeviceNotifyHardwareReady 后创建端点。 在这些回调函数中,客户端驱动程序应调用 UfxEndpointCreate 到端点对象并获取其 UFXENDPOINT 句柄。 UFX 将父级设置为与端点所属的接口关联的类驱动程序 PDO。 默认端点的父端点是 USB 设备对象。 端点包含两个框架队列对象:一个传输队列和一个命令队列,这两个对象仅当设备处于“已配置”状态 ((端点 0 除外)时才能访问,后者可在 UFX 调用 EVT_UFX_DEVICE_HOST_CONNECT) 后访问。
命令队列请求:
- IOCTL_INTERNAL_USBFN_GET_PIPE_STATE
- IOCTL_INTERNAL_USBFN_SET_PIPE_STATE
- IOCTL_INTERNAL_USBFN_DESCRIPTOR_UPDATE
传输队列请求:
- IOCTL_INTERNAL_USBFN_TRANSFER_IN
- IOCTL_INTERNAL_USBFN_TRANSFER_IN_APPEND_ZERO_PKT
- IOCTL_INTERNAL_USBFN_TRANSFER_OUT
- IOCTL_INTERNAL_USBFN_CONTROL_STATUS_HANDSHAKE_IN
- IOCTL_INTERNAL_USBFN_CONTROL_STATUS_HANDSHAKE_OUT
设备枚举
在 UFX 调用驱动程序的 EVT_UFX_DEVICE_HOST_CONNECT之前,客户端驱动程序不应允许连接到主机。 当客户端驱动程序调用 UfxDeviceNotifyReset 时,设备枚举开始。 在 默认 状态下,UFX 处理标准设置数据包。
重置
UFX 清除所有端点队列,并向客户端驱动程序发送 IOCTL_INTERNAL_USBFN_DESCRIPTOR_UPDATE 请求,以更新端点 0 的 wMaxPacketSize 。 UFX 启动默认端点的队列,并将状态设置为 Default。
Default
UFX 调用客户端驱动程序的 EVT_UFX_DEVICE_USB_STATE_CHANGE 函数。 它还通知类驱动程序状态。 在 UFX 收到SET_ADDRESS标准安装数据包后,UFX 会将状态设置为 “已寻址”。
解决
UFX 调用客户端驱动程序的 EVT_UFX_DEVICE_ADDRESSED 函数,以向客户端指示它应使用哪个地址。 - 如果地址为 0,UFX 会将状态重新设置为 Default ,并调用 EVT_UFX_DEVICE_USB_STATE_CHANGE 并通知类驱动程序。 收到SET_CONFIGURATION标准安装数据包时,UFX 将状态设置为 “已配置”。
已配置
如果所选配置为 0,则 UFX 将清除接口端点并将状态设置为“ 已寻址”。 UFX 向客户端驱动程序发送 IOCTL_INTERNAL_USBFN_DESCRIPTOR_UPDATE 请求,以更新接口端点的 wMaxPacketSize 。 UFX 确保所有接口端点队列已完成清除并启动接口端点队列。 如果端口类型不是 UsbfnStandardDownstreamPort 或 UsbfnChargingDownstreamPort,则 UFX 将端口类型更改为 UsbfnStandardDownstreamPort 并通知 Cad.sys;通过调用 EVT_UFX_DEVICE_PORT_CHANGE 和 EVT_UFX_DEVICE_USB_STATE_CHANGE 来更新状态的客户端驱动程序;已配置状态的类驱动程序。
标准控制传输
UFX 在调用 EVT_UFX_DEVICE_DEFAULT_ENDPOINT_ADD 后,可以随时处理默认端点上的控制传输,客户端驱动程序在其中使用 创建默认端点。 所有控制传输都以 8 字节设置数据包开头。 若要将设置数据包发送到主机,客户端驱动程序应调用 UfxEndpointNotifySetup。 标准控制传输由 UFX 完成。 如果存在与控件传输关联的数据,UFX 会根据需要从默认控制端点读取和写入。
非标准控制转移
如果 UFX 无法处理控制传输,则通过完成 IOCTL_INTERNAL_USBFN_BUS_EVENT_NOTIFICATION 请求将传输转发到相应的类驱动程序。 控制传输可以在端点描述符中定义为控制端点的任何端点上发生。 默认控制端点以外的终端点上的控制传输始终是非标准控制传输。 如果控制端点是默认控制端点,UFX 将通知类驱动程序安装数据包,这些数据包标记为类驱动程序的类请求。 如果控制端点属于接口,则 UFX 会通知与该接口关联的类驱动程序。 如有必要,类驱动程序应从控件端点进行读取和写入。
数据传输
数据传输由类驱动程序通过发送 IOCTL_INTERNAL_USBFN_TRANSFER_IN、 IOCTL_INTERNAL_USBFN_TRANSFER_IN_APPEND_ZERO_PKT或 IOCTL_INTERNAL_USBFN_TRANSFER_OUT 请求来启动。 验证每个请求后,UFX 将其转发到相应的端点队列,由客户端驱动程序处理。 客户端驱动程序应执行其他验证。 客户端驱动程序接收端点队列上的传输请求。 客户端驱动程序可以从此队列中检索尽可能多的请求,以最大程度地提高总线利用率。 客户端驱动程序应使用 STATUS_SUCCESS 完成成功的请求。 驱动程序应尽最大努力尝试取消请求,并在取消后使用STATUS_CANCELLED完成已取消的请求。 如果传递的参数无效,则客户端驱动程序使用 STATUS_INVALID_PARAMETER 完成请求。
控制传输
控制传输以 8 字节设置数据包开头。 若要将设置数据包发送到主机,客户端驱动程序应调用 UfxEndpointNotifySetup。 UFX 通过完成通知请求来通知类驱动程序非标准控制传输。 客户端和 UFX 都使用 IOCTL_INTERNAL_USBFN_TRANSFER_IN、 IOCTL_INTERNAL_USBFN_TRANSFER_IN_APPEND_ZERO_PKT 或 IOCTL_INTERNAL_USBFN_TRANSFER_OUT 从默认控制端点读取和写入。 但是,接口可以定义其他控制端点,只有相应的类驱动程序才能使用。 为了响应设置数据包,可以停止控制端点。 类驱动程序发送 IOCTL_INTERNAL_USBFN_SET_PIPE_STATE 请求来停止端点。 发送停止后,硬件或客户端驱动程序应立即恢复端点上的流量。 控制端点还可以 (ZLP) 发送和接收零长度数据包,而无需任何先前数据。 客户端驱动程序和 UFX 可以使用 IOCTL_INTERNAL_USBFN_CONTROL_STATUS_HANDSHAKE_IN 和 IOCTL_INTERNAL_USBFN_CONTROL_STATUS_HANDSHAKE_OUT 执行此操作。
批量传输和中断传输
批量传输保证数据传输,并用于发送大量数据。 可以使用 IOCTL_INTERNAL_USBFN_TRANSFER_IN、IOCTL_INTERNAL_USBFN_TRANSFER_IN_APPEND_ZERO_PKT 或 IOCTL_INTERNAL_USBFN_TRANSFER_OUT 在批量端点上发送传输。 批量端点可以像使用 IOCTL_INTERNAL_USBFN_SET_PIPE_STATE控制端点一样停止。 客户端驱动程序应发送 STALL 数据包以响应所有主机请求并保留 IOCTL 请求。 与控制端点不同,停止的批量端点将保持停止状态,直到显式清除停止状态。
中断传输 中断传输类似于批量传输,但有保证的延迟。 中断传输的接口与批量传输相同,但没有流式处理功能。
常时等量传输
客户端驱动程序不应支持此版本中的常时等量传输。
电源管理
客户端驱动程序拥有电源管理的所有方面。 由于回调函数是异步的,因此客户端驱动程序应在调用相应的事件完成导出函数(如 UfxDeviceEventComplete)之前恢复到适当的电源状态并完成请求。
如果 USBFN_DEVICE_STATE) 中定义的设备状态 (为 UsbfnDeviceStateSuspended 和 UsbfnDeviceStateAttached,并且未报告端口类型,则 UFX 将处于工作状态。 或者,UFX 报告了 USBFN_PORT_TYPE () UsbfnStandardDownstreamPort 或 UsbfnChargingDownstreamPort 中定义的端口类型。
UFX 通过调用 EVT_UFX_DEVICE_USB_STATE_CHANGE 或 EVT_UFX_DEVICE_PORT_CHANGE 实现进入和退出工作状态。 当客户端驱动程序调用 UfxDeviceEventComplete 时,到工作状态或从工作状态转换完成。
在“工作”状态下,UFX 可以调用任何回调。 虽然 UFX 不处于“工作”状态,但仅调用 EVT_UFX_DEVICE_USB_STATE_CHANGE 进入工作状态; 如果支持) ,EVT_UFX_DEVICE_REMOTE_WAKEUP_SIGNAL 在暂停 (期间发出远程唤醒。
设备挂起
当总线上没有流量 3 毫秒时,会发生设备挂起。 在这种情况下,客户端驱动程序必须通过调用 UfxDeviceNotifySuspend 和 UfxDeviceNotifyResume 来通知 UFX 检测到挂起和恢复。 接收这些调用后,UFX 会调用EVT_UFX_DEVICE_USB_STATE_CHANGE ,并通过完成 IOCTL_INTERNAL_USBFN_BUS_EVENT_NOTIFICATION 请求来通知类驱动程序。 如果设备支持远程唤醒并由主机启用,UFX 可能会在暂停时调用 调用EVT_UFX_DEVICE_USB_STATE_CHANGE 以发出远程唤醒信号。