libusb-win32 内核驱动源码分析(第二篇):IOCTL 派发、USB 传输引擎与配置管理
1. 概述
本篇深入分析驱动处理用户态请求的核心机制,聚焦于ioctl.c(IOCTL 派发器)、transfer.c(传输执行引擎)、set_configuration.c/get_descriptor.c(配置与描述符管理)以及各类标准/厂商请求处理模块。
用户态 DLL 通过DeviceIoControl发送各种 IOCTL 码,驱动在dispatch_ioctl中解析并路由到对应的内核函数。数据传输(BULK/INT/ISO)是其中最复杂的部分,涉及 URB 构建、MDL 管理、传输拆分和完成例程链式调用。
2. IOCTL 派发器(dispatch_ioctl位于ioctl.c)
dispatch_ioctl是处理所有IRP_MJ_DEVICE_CONTROL的核心函数。它首先获取 IOCTL 码、输入/输出缓冲区长度、系统缓冲区指针以及 MDL 地址。
2.1 控制码分类
IOCTL 按数据传输方式分为两类:
- METHOD_IN_DIRECT / METHOD_OUT_DIRECT:用于大数据传输(BULK/INT/ISO 读写)。用户数据通过 MDL(内存描述符列表)传递,驱动直接操作 MDL,避免额外拷贝。
- METHOD_BUFFERED:用于小数据量的控制操作(配置、特征、描述符、厂商请求等)。输入/输出数据通过
irp->AssociatedIrp.SystemBuffer传递。
2.2 直接传输 IOCTL 处理
对于LIBUSB_IOCTL_INTERRUPT_OR_BULK_READ/WRITE和LIBUSB_IOCTL_ISOCHRONOUS_READ/WRITE,驱动执行以下步骤:
- 验证请求和 MDL 的有效性。
- 调用
get_pipe_info查找对应端点的管道信息(libusb_endpoint_t)。 - 检查端点类型是否匹配(BULK/INT 不能用于 ISO,反之亦然)。
- 检查传输缓冲区大小是否为端点最大包大小的整数倍(对于 ISO 和 BULK/INT,若不是则发出警告,但仍允许执行)。
- 确定
urbFunction(URB_FUNCTION_BULK_OR_INTERRUPT_TRANSFER或URB_FUNCTION_ISOCH_TRANSFER)和传输方向(USBD_TRANSFER_DIRECTION_IN/OUT)。 - 计算
maxTransferSize:若用户指定则使用用户值,否则使用管道默认值(pipe_info->maximum_transfer_size)。 - 调用
transfer函数执行实际传输。
宏TRANSFER_IOCTL_EXECUTE封装了对transfer的调用,并传递所有必要参数。
2.3 缓冲 IOCTL 处理
对于其他控制码,驱动直接从系统缓冲区读取libusb_request结构,根据 IOCTL 码调用对应的处理函数:
LIBUSB_IOCTL_SET_CONFIGURATION→set_configurationLIBUSB_IOCTL_GET_CACHED_CONFIGURATION→ 直接返回缓存的dev->config.valueLIBUSB_IOCTL_GET_CONFIGURATION→get_configurationLIBUSB_IOCTL_SET_INTERFACE/GET_INTERFACE→set_interface/get_interfaceLIBUSB_IOCTL_SET_FEATURE/CLEAR_FEATURE→set_feature/clear_featureLIBUSB_IOCTL_GET_STATUS→get_statusLIBUSB_IOCTL_GET_DESCRIPTOR/SET_DESCRIPTOR→get_descriptor/set_descriptorLIBUSB_IOCTL_VENDOR_READ/WRITE→vendor_class_requestLIBUSB_IOCTL_RESET_ENDPOINT/ABORT_ENDPOINT→reset_endpoint/abort_endpointLIBUSB_IOCTL_RESET_DEVICE/RESET_DEVICE_EX→reset_device/reset_device_exLIBUSB_IOCTL_CLAIM_INTERFACE/RELEASE_INTERFACE→claim_interface/release_interfaceLIBUSB_IOCTL_GET_VERSION→ 填充驱动版本信息- 扩展 IOCTL(
LIBUSB_IOCTL_GET_DEVICE_PROPERTY、GET_CUSTOM_REG_PROPERTY、GET_OBJECT_NAME)等。
所有处理都包裹在remove_lock_acquire/remove_lock_release中,确保设备移除过程中不会处理新请求。
3. 核心传输引擎(transfer.c)
transfer.c是驱动中最复杂的模块,负责 BULK、INTERRUPT 和 ISOCHRONOUS 传输的实际执行。
3.1 传输上下文与序列化
每个传输请求在驱动中创建一个context_t结构,包含:
urb:指向 URB 的指针。address:端点地址。sequence:全局递增的序列号(用于日志和顺序检查)。transferFlags/isoLatency:传输标志和等时延迟。totalLength/information:总长度和已传输字节数。maximum_packet_size/maxTransferSize:端点参数。mdlAddress/subMdl:原始 MDL 和子 MDL(用于拆分传输)。
驱动使用原子操作InterlockedIncrement生成序列号,并通过dev->pending_busy[endpoint->address]确保同一端点不会同时有多个传输在进行(防止乱序)。若检测到冲突,当前传输被中止。
3.2 URB 创建(create_urb)
根据传输类型创建不同大小的 URB:
- BULK/INTERRUPT:
sizeof(struct _URB_BULK_OR_INTERRUPT_TRANSFER)。设置 PipeHandle、TransferFlags、TransferBufferLength 和 TransferBufferMDL。 - ISOCHRONOUS:
sizeof(struct _URB_ISOCH_TRANSFER) + num_packets * sizeof(USBD_ISO_PACKET_DESCRIPTOR)。每个包描述符的 Offset 和 Length 根据packetSize计算。包数量不能超过 255。
3.3 传输拆分机制
transfer函数计算首次传输大小first_size = min(totalLength, maxTransferSize)。提交第一个 URB,并设置完成例程为transfer_complete。
在transfer_complete完成例程中:
- 检查传输是否成功且 USBD 状态成功。
- 更新
information累加已传输字节数。 - 计算剩余长度
totalLength -= transmitted。 - 关键逻辑:若传输成功且 URB 函数是
URB_FUNCTION_BULK_OR_INTERRUPT_TRANSFER,且transmitted等于maxTransferSize(即“满载”),且transmitted是maximum_packet_size的整数倍(表示可能还有更多数据),且totalLength > 0,则:- 检查是否有更新的请求已在该端点排队(通过比较
dev->pending_sequence与当前sequence),若有则放弃拆分,避免乱序。 - 分配子 MDL(
IoAllocateMdl+IoBuildPartialMdl),指向原始缓冲区的后续偏移。 - 复用原有 URB,更新 TransferBufferLength 和 TransferBufferMDL 为子 MDL。
- 调用
transfer_next再次提交(通过IoCallDriver)。 - 返回
STATUS_MORE_PROCESSING_REQUIRED,表示 IRP 尚未完成。
- 检查是否有更新的请求已在该端点排队(通过比较
- 若拆分完成或出现错误,则最终完成 IRP,释放 URB 和子 MDL,释放上下文,并调用
remove_lock_release。
此机制实现了大块传输的透明拆分,对用户态完全隐藏。
3.4 等时传输特殊处理(set_urb_transfer_flags)
对于URB_FUNCTION_ISOCH_TRANSFER,该函数:
- 保留方向位,清除其他标志。
- 若未设置
TRANSFER_FLAGS_SHORT_NOT_OK,则添加USBD_SHORT_TRANSFER_OK。 - 若设置了
TRANSFER_FLAGS_ISO_SET_START_FRAME,则调用get_current_frame获取当前帧号,并加上isoLatency作为起始帧。否则设置USBD_START_ISO_TRANSFER_ASAP。
3.5 控制传输(control_transfer)
专门处理LIBUSB_IOCTL_CONTROL_READ/WRITE(libusbK 兼容接口)。它构建一个URB_FUNCTION_CONTROL_TRANSFER类型的 URB,将 SetupPacket 填充为 8 字节(bmRequestType, bRequest, wValue, wIndex, wLength)。调用call_usbd_ex时不对超时做上限限制(即max_timeout = 0)。
4. 配置与接口管理
4.1 配置设置(set_configuration位于set_configuration.c)
设置配置是 USB 设备初始化的关键步骤:
- 快速路径:若请求的配置值与当前缓存值相同,直接返回
STATUS_SUCCESS。 - 若
configuration == 0(取消配置),发送URB_FUNCTION_SELECT_CONFIGURATION,清空管道信息。 - 若
configuration <= SET_CONFIG_ACTIVE_CONFIG,将其视为索引 -1(即第一个配置)。 - 调用
get_config_descriptor获取完整配置描述符(通过get_descriptor驱动内部调用)。 - 构建
USBD_INTERFACE_LIST_ENTRY数组:遍历配置描述符中的所有接口,调用find_interface_desc获取接口描述符(备用设置 0)。 - 调用
USBD_CreateConfigurationRequestEx创建选择配置请求 URB。 - 将 URB 中每个管道的
MaximumTransferSize设为LIBUSB_MAX_READ_WRITE(64KB),但后续update_pipe_info会根据设备速度重新计算更合理的值。 - 提交 URB(
call_usbd)。 - 若成功,更新
dev->config.handle,清空旧管道信息,对每个接口调用update_pipe_info解析管道句柄和参数,最后缓存配置描述符。
4.2 管道信息更新(update_pipe_info位于libusb_driver.c)
该函数根据 USBD 返回的USBD_INTERFACE_INFORMATION填充libusb_endpoint_t:
- 保存
PipeHandle、EndpointAddress、MaximumPacketSize、Interval、PipeType。 - 关键:根据设备速度计算
MaximumTransferSize(遵循 Windows 新的带宽分配规则):- Control:SuperSpeed → 64KB,其他 → 4KB。
- Interrupt:固定 4MB。
- Isochronous:HighSpeed → 1024 * MaxPacketSize,其他 → 256 * MaxPacketSize。
- Bulk:SuperSpeed → 32MB,HighSpeed → 4MB,其他 → 256KB。
- 确保
maxTransferSize是maxPacketSize的整数倍(向下取整)。
4.3 接口声明(claim_interface.c/release_interface.c)
claim_interface:检查接口是否有效、是否已被其他文件对象声明。若空闲,则将当前FILE_OBJECT绑定到dev->config.interfaces[interface].file_object。release_interface:解绑,前提是调用者持有该接口。release_all_interfaces:在IRP_MJ_CLOSE时调用,释放当前文件对象持有的所有接口。
这些函数还提供了_ex版本(claim_interface_ex等),支持通过接口索引而非编号查找,用于 libusbK 兼容性。
5. 描述符获取与缓存(get_descriptor.c)
为了减少不必要的控制传输,驱动实现了两级缓存:
5.1 设备描述符缓存
- 当首次收到
USB_DT_DEVICE类型的请求时(recipient == USB_RECIP_DEVICE,index == 0,langid == 0),驱动通过 URB 从设备读取描述符,并复制到dev->device_descriptor。 - 后续所有相同请求直接从缓存返回,不再产生设备 I/O。
5.2 配置描述符缓存
- 若请求
USB_DT_CONFIG类型,且dev->config.descriptor为 NULL 或dev->config.index != index:- 若
dev->config.descriptor为 NULL(即尚未缓存任何配置),且请求的长度足以包含完整描述符(wTotalLength),则驱动分配内核池,缓存整个配置描述符,并设置dev->config.value = 0(未配置状态)。 - 否则,通过 URB 从设备读取。
- 若
- 若请求的是当前已配置的活动配置(
dev->config.value > 0且index == dev->config.index),则直接从缓存的描述符返回数据。 - 特别说明:
set_configuration成功后,会通过UpdateContextConfigDescriptor宏将获取到的完整配置描述符缓存到dev->config.descriptor,并更新dev->config.value和dev->config.index。
5.3get_config_descriptor辅助函数
根据传入的value(正数表示 bConfigurationValue,负数表示索引)遍历设备描述符中的bNumConfigurations,调用get_descriptor获取对应的完整配置描述符。
6. 端点操作
6.1reset_endpoint(reset_endpoint.c)
发送URB_FUNCTION_RESET_PIPE,清除端点上的 halt 条件,重置数据翻转位。失败时返回相应错误码。
6.2abort_endpoint(abort_endpoint.c)
发送URB_FUNCTION_ABORT_PIPE,取消该端点上所有未完成的 URB。这在用户态调用usb_cancel_async或超时取消时被触发。
7. 标准与厂商请求处理
这些模块相对标准化,直接构建对应 URB 并提交:
get_status(get_status.c):URB_FUNCTION_GET_STATUS_FROM_DEVICE/INTERFACE/ENDPOINT/OTHER。set_feature/clear_feature(set_feature.c/clear_feature.c):URB_FUNCTION_SET/CLEAR_FEATURE_TO_DEVICE/INTERFACE/ENDPOINT/OTHER。vendor_class_request(vendor_request.c):处理 USB_TYPE_VENDOR 或 USB_TYPE_CLASS 请求。根据 recipient 选择 URB_FUNCTION_VENDOR/CLASS_DEVICE/INTERFACE/ENDPOINT/OTHER。若 recipient 为保留值,则按 Kevin Timmerman 补丁的方式处理(视为 Vendor Device)。set_descriptor(set_descriptor.c):URB_FUNCTION_SET_DESCRIPTOR_TO_DEVICE/INTERFACE/ENDPOINT。
8. 接口备用设置管理(set_interface.c/get_interface.c)
set_interface:分配URB_FUNCTION_SELECT_INTERFACEURB,设置 ConfigurationHandle、InterfaceNumber、AlternateSetting,并更新管道信息(update_pipe_info)。get_interface:发送URB_FUNCTION_GET_INTERFACE获取当前备用设置。
这两者均有_ex版本,支持通过索引查找接口,增强与 libusbK 的兼容性。
9. 设备复位(reset_device.c)
reset_device调用reset_device_ex(..., USB_RESET_TYPE_FULL_RESET)。reset_device_ex根据reset_type分别发送IOCTL_INTERNAL_USB_RESET_PORT和/或IOCTL_INTERNAL_USB_CYCLE_PORT。成功后清除缓存的配置描述符(UpdateContextConfigDescriptor(..., NULL, 0, 0, -1)),强制下次重新获取。
10. 总结
本篇覆盖了驱动处理用户态请求的核心业务逻辑:
dispatch_ioctl作为总入口,根据 IOCTL 码分发到特定处理函数。transfer.c实现了高性能的 BULK/INT/ISO 传输,支持自动拆分和链式完成,保证了大数据传输的可靠性。- 配置管理通过缓存和 USBD API 实现高效的配置切换和管道信息维护。
- 描述符缓存大幅减少了不必要的控制传输,提升了性能。
- 端点控制、标准请求、接口管理等模块均直接映射到对应的 URB 功能,实现简洁高效。
后续文档将深入分析注册表读取、设备添加与过滤驱动逻辑以及调试与错误处理等支撑模块。