windows 驱动实例分析系列: libusb驱动分析-驱动层源码篇(二) 📅 发布时间:2026/9/2 8:19:08 👁 浏览次数: libusb-win32 内核驱动源码分析第二篇IOCTL 派发、USB 传输引擎与配置管理1. 概述本篇深入分析驱动处理用户态请求的核心机制聚焦于ioctl.cIOCTL 派发器、transfer.c传输执行引擎、set_configuration.c/get_descriptor.c配置与描述符管理以及各类标准/厂商请求处理模块。用户态 DLL 通过DeviceIoControl发送各种 IOCTL 码驱动在dispatch_ioctl中解析并路由到对应的内核函数。数据传输BULK/INT/ISO是其中最复杂的部分涉及 URB 构建、MDL 管理、传输拆分和完成例程链式调用。2. IOCTL 派发器dispatch_ioctl位于ioctl.cdispatch_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若不是则发出警告但仍允许执行。确定urbFunctionURB_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→ 填充驱动版本信息扩展 IOCTLLIBUSB_IOCTL_GET_DEVICE_PROPERTY、GET_CUSTOM_REG_PROPERTY、GET_OBJECT_NAME等。所有处理都包裹在remove_lock_acquire/remove_lock_release中确保设备移除过程中不会处理新请求。3. 核心传输引擎transfer.ctransfer.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根据传输类型创建不同大小的 URBBULK/INTERRUPTsizeof(struct _URB_BULK_OR_INTERRUPT_TRANSFER)。设置 PipeHandle、TransferFlags、TransferBufferLength 和 TransferBufferMDL。ISOCHRONOUSsizeof(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若有则放弃拆分避免乱序。分配子 MDLIoAllocateMdlIoBuildPartialMdl指向原始缓冲区的后续偏移。复用原有 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/WRITElibusbK 兼容接口。它构建一个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_WRITE64KB但后续update_pipe_info会根据设备速度重新计算更合理的值。提交 URBcall_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 新的带宽分配规则ControlSuperSpeed → 64KB其他 → 4KB。Interrupt固定 4MB。IsochronousHighSpeed → 1024 * MaxPacketSize其他 → 256 * MaxPacketSize。BulkSuperSpeed → 32MBHighSpeed → 4MB其他 → 256KB。确保maxTransferSize是maxPacketSize的整数倍向下取整。4.3 接口声明claim_interface.c/release_interface.cclaim_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_endpointreset_endpoint.c发送URB_FUNCTION_RESET_PIPE清除端点上的 halt 条件重置数据翻转位。失败时返回相应错误码。6.2abort_endpointabort_endpoint.c发送URB_FUNCTION_ABORT_PIPE取消该端点上所有未完成的 URB。这在用户态调用usb_cancel_async或超时取消时被触发。7. 标准与厂商请求处理这些模块相对标准化直接构建对应 URB 并提交get_statusget_status.cURB_FUNCTION_GET_STATUS_FROM_DEVICE/INTERFACE/ENDPOINT/OTHER。set_feature/clear_featureset_feature.c/clear_feature.cURB_FUNCTION_SET/CLEAR_FEATURE_TO_DEVICE/INTERFACE/ENDPOINT/OTHER。vendor_class_requestvendor_request.c处理 USB_TYPE_VENDOR 或 USB_TYPE_CLASS 请求。根据 recipient 选择 URB_FUNCTION_VENDOR/CLASS_DEVICE/INTERFACE/ENDPOINT/OTHER。若 recipient 为保留值则按 Kevin Timmerman 补丁的方式处理视为 Vendor Device。set_descriptorset_descriptor.cURB_FUNCTION_SET_DESCRIPTOR_TO_DEVICE/INTERFACE/ENDPOINT。8. 接口备用设置管理set_interface.c/get_interface.cset_interface分配URB_FUNCTION_SELECT_INTERFACEURB设置 ConfigurationHandle、InterfaceNumber、AlternateSetting并更新管道信息update_pipe_info。get_interface发送URB_FUNCTION_GET_INTERFACE获取当前备用设置。这两者均有_ex版本支持通过索引查找接口增强与 libusbK 的兼容性。9. 设备复位reset_device.creset_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 功能实现简洁高效。后续文档将深入分析注册表读取、设备添加与过滤驱动逻辑以及调试与错误处理等支撑模块。