- 驱动开发
- 硬件开发
【免费下载链接】DsHidMini
Virtual HID Mini-user-mode-driver for Sony DualShock 3 Controllers
本篇指南围绕 DsHidMini 项目的用户态 IPC SDK(Nefarius.DsHidMini.IPC)中的WDF_USB_CONTROL_SETUP_PACKET结构展开,说明它在内核驱动与用户态之间传递 USB 控制传输 setup 包(setup packet)时的内存布局、字段语义与典型应用场景。读完本文,你将掌握如何解读与构造这个 8 字节 USB 控制传输请求头,并能对照 driver/DsUsb.c 与 driver/Ds3.c 中的内核驱动调用链,理解 DsHidMini 在读取 DS3 主机蓝牙地址、执行自定义控制请求时底层的字段是如何逐位拼装的。
结构体概览:一个用户态的 USB 控制传输 setup 包
WDF_USB_CONTROL_SETUP_PACKET定义在命名空间Nefarius.DsHidMini.IPC.Models.Public下,用于描述一次 USB 控制传输(control transfer)的 setup packet。其声明位于 SDK/Nefarius.DsHidMini.IPC/Models/Public/UsbSetupPacket.cs:
[StructLayout(LayoutKind.Explicit)] [SuppressMessage("ReSharper", "InconsistentNaming")] public struct WDF_USB_CONTROL_SETUP_PACKET { [FieldOffset(0)] public PacketStruct Packet; [FieldOffset(0)] public GenericStruct Generic; }作为System.ValueType(值类型),它不参与托管堆分配,与内核驱动侧的原生WDF_USB_CONTROL_SETUP_PACKET(KMDF 框架提供、定义于wdfusb.h系列头文件)在语义上保持一致,目的是让用户态进程可以按相同的字节布局理解内核侧构造的 setup 包,或自行构造后通过 IPC 传递给驱动处理。
该类型被收录在 SDK 公共模型文档索引 SDK/Nefarius.DsHidMini.IPC/docs/index.md 的Namespace Nefarius.DsHidMini.IPC.Models.Public一节中,与PowerOffUsbResult、SetHostResult、Ds3LedEffect等公共模型并列,说明它是面向驱动外部调用者公开的数据契约之一。
内存布局:Union 式双视图设计
结构最核心的设计是union(联合体)式重叠布局:Packet与Generic两个字段都标注为[FieldOffset(0)],即它们从同一内存地址开始、共享同一块 8 字节存储,这与原生 WDF 头文件中WDF_USB_CONTROL_SETUP_PACKET的定义方式一致(原生定义同样包含Packet与Generic两个重叠成员)。
[StructLayout(LayoutKind.Sequential, Pack = 1)] public unsafe struct GenericStruct { public fixed byte Bytes[8]; }两个视图各自的用途:
- Packet 视图:把 8 字节按 USB 规范拆解为
bmRequestType(1 字节)、bRequest(1 字节)、wValue(2 字节)、wIndex(2 字节)、wLength(2 字节)五个逻辑字段,方便按名称读写; - Generic 视图:直接暴露
fixed byte Bytes[8]原始字节数组,用于需要按原始二进制内容整体访问、比较或序列化的场景(例如调试打印、按原样透传给设备)。
PacketStruct使用StructLayout(LayoutKind.Explicit, Pack = 1)并配合FieldOffset明确指定每个字段的字节偏移,确保无论运行在哪种体系结构下,内存布局都严格符合 USB 协议规定的字节顺序:
| 偏移量 | 字段 | 长度 | 说明 |
|---|---|---|---|
| 0 | bm(RequestStruct) | 1 字节 | 请求特性字节(位域:方向/类型/接收者) |
| 1 | bRequest | 1 字节 | 请求编号(如 GET_REPORT 等 HID 类请求) |
| 2 | wValue | 2 字节 | 请求附加值(小端序) |
| 4 | wIndex | 2 字节 | 索引值(小端序) |
| 6 | wLength | 2 字节 | 第二阶段传输的数据长度 |
Packet 字段逐项解读
bm:RequestStruct 位域拆分
USB 控制传输 setup 包的第一个字节bmRequestType是位掩码字段。该 SDK 将其建模为RequestStruct,通过属性逐位暴露四个子字段:
[StructLayout(LayoutKind.Sequential, Pack = 1)] public struct RequestStruct { private byte _byte; public byte Recipient // 最低 2 位(bit0-1) { get => (byte)(_byte & 0x03); set => _byte = (byte)((_byte & ~0x03) | (value & 0x03)); } public byte Reserved // 中间 3 位(bit2-4) { get => (byte)((_byte >> 2) & 0x07); set => _byte = (byte)((_byte & ~(0x07 << 2)) | ((value & 0x07) << 2)); } public byte Type // bit5-6 { get => (byte)((_byte >> 5) & 0x03); set => _byte = (byte)((_byte & ~(0x03 << 5)) | ((value & 0x03) << 5)); } public byte Dir // 最高位 bit7 { get => (byte)((_byte >> 7) & 0x01); set => _byte = (byte)((_byte & ~(0x01 << 7)) | ((value & 0x01) << 7)); } public byte Byte { get => _byte; set => _byte = value; } }各属性对应的位域语义(与 USB 2.0 规范中bmRequestType的定义一致):
Dir(bit7):数据传输方向,0表示主机到设备(Host-to-Device),1表示设备到主机(Device-to-Host);Type(bit6-5):请求类型,标准(Standard)/ 类(Class)/ 厂商(Vendor)/ 保留;Reserved(bit4-2):规范保留位,通常为 0,SDK 仍将其建模以便按位访问;Recipient(bit1-0):请求接收者,设备(Device)/ 接口(Interface)/ 端点(Endpoint)/ 其他。
注意Recipient的掩码是0x03(最低 2 位),虽然 USB 规范中接收者字段占据 bit4-0,但该 SDK 把 bit2-4 单独建模为Reserved,二者组合才构成完整的低 5 位。
bRequest:请求编号
[FieldOffset(1)] public byte bRequest;与bmRequestType组合使用,标识具体的请求操作。在 DsHidMini 驱动中,常见取值为 HID 类请求,例如GetReport(获取报告)——见下文驱动调用链一节。
wValue 与 wIndex:小端序 16 位值
wValue和wIndex在内部被建模为BytesStruct(LowByte/HiByte两个字节),并对外提供组合属性,按照Little-Endian(小端序)在高低字节之间换算:
[FieldOffset(2)] internal BytesStruct wValueBytes; [FieldOffset(4)] internal BytesStruct wIndexBytes; public ushort wValue { get => (ushort)((wValueBytes.HiByte << 8) | wValueBytes.LowByte); set { wValueBytes.LowByte = (byte)(value & 0xFF); wValueBytes.HiByte = (byte)((value >> 8) & 0xFF); } } public ushort wLength { // FieldOffset(6) 处直接声明为 ushort }也就是说,在 C# 中直接给wValue/wIndex赋值一个ushort,序列化到内存时会自动拆成低字节在前、高字节在后的 USB 线序;读取时则自动按同样的顺序重组,开发者无需手工做字节序转换。这一实现细节与 USB 设备端实际收发的字节序严格对应。
wLength:数据阶段长度
[FieldOffset(6)] public ushort wLength;指定控制传输数据阶段(data stage)期望传输的字节数。对于无数据阶段的控制传输(如仅设置类请求),该值为 0。
与内核驱动的对应关系:从 DsUsb.c 看真实调用链
该结构并非凭空设计——它在 DsHidMini 内核驱动中与 KMDF 的原生WDF_USB_CONTROL_SETUP_PACKET一一对应。驱动核心的 USB 控制请求封装位于 driver/DsUsb.c 的USB_SendControlRequest函数:
NTSTATUS USB_SendControlRequest( _In_ PDEVICE_CONTEXT Context, _In_ WDF_USB_BMREQUEST_DIRECTION Direction, _In_ WDF_USB_BMREQUEST_TYPE Type, _In_ BYTE Request, _In_ USHORT Value, _In_ USHORT Index, _Inout_ PVOID Buffer, _In_ ULONG BufferLength, _Out_opt_ PULONG BytesTransferred ) { NTSTATUS status; WDF_USB_CONTROL_SETUP_PACKET controlSetupPacket; WDF_REQUEST_SEND_OPTIONS sendOptions; WDF_MEMORY_DESCRIPTOR memDesc; ULONG bytesTransferred = 0; WDF_REQUEST_SEND_OPTIONS_INIT(&sendOptions, WDF_REQUEST_SEND_OPTION_TIMEOUT); WDF_REQUEST_SEND_OPTIONS_SET_TIMEOUT(&sendOptions, WDF_REL_TIMEOUT_IN_SEC(3)); switch (Type) { case BmRequestClass: WDF_USB_CONTROL_SETUP_PACKET_INIT_CLASS( &controlSetupPacket, Direction, BmRequestToInterface, Request, Value, Index ); break; default: return STATUS_INVALID_PARAMETER; } WDF_MEMORY_DESCRIPTOR_INIT_BUFFER(&memDesc, Buffer, BufferLength); if (!NT_SUCCESS(status = WdfUsbTargetDeviceSendControlTransferSynchronously( Context->Connection.Usb.UsbDevice, WDF_NO_HANDLE, &sendOptions, &controlSetupPacket, &memDesc, &bytesTransferred ))) { TraceError(TRACE_DSUSB, "WdfUsbTargetDeviceSendControlTransferSynchronously failed with status %!STATUS! (%d)", status, bytesTransferred); } ... }对照可见三层对应关系:
- 初始化宏:
WDF_USB_CONTROL_SETUP_PACKET_INIT_CLASS内部正是按 setup 包布局拼装bmRequestType(方向 + 类类型 + 接口接收者)、bRequest、wValue、wIndex四个成员,与 C# 侧PacketStruct的五个字段完全对应; - 传输 API:
WdfUsbTargetDeviceSendControlTransferSynchronously将 setup 包与内存描述符提交给 USB 目标设备,数据阶段缓冲区由WDF_MEMORY_DESCRIPTOR_INIT_BUFFER描述,其长度即对应wLength; - 超时保护:驱动统一为控制请求设置 3 秒超时(
WDF_REL_TIMEOUT_IN_SEC(3)),避免设备无响应时无限期阻塞。
从驱动实现可以推断:USB_SendControlRequest目前只接受类类型(BmRequestClass)请求,接收者为接口(BmRequestToInterface);如需厂商(Vendor)类型请求,需在驱动侧扩展。用户在 IPC 层构造 setup 包时也应遵循这一约束,否则驱动会返回STATUS_INVALID_PARAMETER。
实际应用场景:以读取 DS3 主机蓝牙地址为例
WDF_USB_CONTROL_SETUP_PACKET的典型用法可以从 driver/Ds3.c 中读取 DS3 手柄已配对主机蓝牙地址(Host BTH Address)的代码得到最直观的印证:
NTSTATUS DsUsb_Ds3RequestHostAddress(WDFDEVICE Device) { NTSTATUS status; const PDEVICE_CONTEXT pDevCtx = DeviceGetContext(Device); UCHAR controlTransferBuffer[CONTROL_TRANSFER_BUFFER_LENGTH]; if (NT_SUCCESS(status = USB_SendControlRequest( pDevCtx, BmRequestDeviceToHost, // Dir = 设备到主机 BmRequestClass, // Type = 类请求 GetReport, // bRequest = 获取报告 Ds3FeatureHostAddress, // wValue = Feature Report ID 0, // wIndex = 0 controlTransferBuffer, CONTROL_TRANSFER_BUFFER_LENGTH, NULL ))) { /* * NOTE: the first byte is 0x01 followed by a 0x00 and then * the host radio MAC address the device is currently paired to. */ RtlCopyMemory( &pDevCtx->HostAddress, &controlTransferBuffer[2], sizeof(BD_ADDR) ); } ... }对照WDF_USB_CONTROL_SETUP_PACKET的字段可以还原这个请求的完整语义:
bmRequestType:BmRequestDeviceToHost(bit7=1)+BmRequestClass(bit6-5)+BmRequestToInterface(接收者)——即Dir、Type、Recipient三个位域的组合;bRequest:GetReport(HID 类请求中的获取报告,编号为 0x01);wValue:Ds3FeatureHostAddress,Feature Report 的 ID,标识要读取的是"主机蓝牙地址"报告;wIndex:0;wLength/ 数据阶段:CONTROL_TRANSFER_BUFFER_LENGTH字节的输入缓冲区,设备返回的数据中偏移 2 处开始存放 6 字节的 BD_ADDR(主机蓝牙 MAC)。
类似的USB_SendControlRequest调用还出现在 driver/DsMotion.c(运动/陀螺仪相关控制请求)、driver/DsThirdPartyHid.c(第三方 HID 适配设备的控制请求)以及 driver/DsUsb.c 自身(如 slot 状态查询)中,说明该 setup 包布局贯穿了驱动内全部 USB 控制传输路径。这也解释了为何 SDK 要将WDF_USB_CONTROL_SETUP_PACKET作为公共模型暴露:用户态调用方若需理解或构造同类控制请求(例如调试诊断、自定义 report 交互),可以使用同一套字节布局与内核驱动对齐。
使用注意事项
结合源码实现,在使用该结构时有几点值得注意:
- 字节序由属性自动处理:
wValue/wIndex在 C# 中按主机字节序读写,内部自动映射为小端线序;不要重复手动交换字节,否则会出现高低字节颠倒; Recipient字段只覆盖 bit0-1:RequestStruct.Recipient的掩码是0x03,bit2-4 属于Reserved;构造标准/类请求时若需要接收者为接口(值为 1),直接设置Recipient = 1即可,因为BmRequestToInterface恰好落在低 2 位;- 与原生 KMDF 结构保持等价:本结构是 driver/DsUsb.c 中原生
WDF_USB_CONTROL_SETUP_PACKET的用户态镜像,二者都依赖Pack = 1的紧凑布局保证 8 字节无填充;任何对字段的增删都必须保持该字节偏移,否则 IPC 传输的二进制布局会错位; - 请求类型受驱动约束:当前驱动侧的
USB_SendControlRequest仅接受类类型请求,超出该范围的 setup 包会被拒绝;构造前应先确认目标请求是否属于驱动的既有调用路径。
小结
WDF_USB_CONTROL_SETUP_PACKET是 DsHidMini 在用户态与内核之间交换 USB 控制传输 setup 包的规范化数据契约:它以显式偏移的联合体布局精确还原了 USB 控制传输的 8 字节请求头,通过位域属性与自动字节序转换提供了安全的字段级读写入口,同时保留了Generic原始字节视图用于透传与调试。结合 driver/DsUsb.c 的USB_SendControlRequest与 driver/Ds3.c 的主机地址请求示例,可以完整理解从用户态模型到内核 KMDF API 再到 USB 设备端的完整数据通路,为基于该 SDK 开展控制请求的二次开发或诊断排查提供了准确的字段级参考。
- 驱动开发
- 硬件开发
【免费下载链接】DsHidMini
Virtual HID Mini-user-mode-driver for Sony DualShock 3 Controllers
相关推荐
DsHidMini `DS3_RAW_INPUT_REPORT` 详解:DualShock 3 原生输入报告的结构与 IPC 读取实战
DsHidMini DS3_RAW_INPUT_REPORT 详解:DualShock 3 原生输入报告的结构与 IPC 读取实战 导读 DS3_RAW_INP
驱动开发硬件开发TinyUSB USB传输类型详解:控制/批量/中断/等时传输
TinyUSB USB传输类型详解:控制/批量/中断/等时传输 引言:USB传输类型的核心挑战 在嵌入式系统开发中,你是否曾面临以下困境: 调试USB设备时数据
嵌入式驱动开发通信物联网快速上手 Claude Code Action:PR 自动审查指南
快速上手 Claude Code Action:PR 自动审查指南 每个 PR 都等着人来审,改完的代码却没人第一时间指出问题。把 Claude Code Ac
驱动开发硬件开发
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考