iOS DriverKit中IOBufferMemoryDescriptor读取USB设备数据异常排查
DriverKit USB驱动ReadComplete获取全\0数据问题
我在iOS平台基于WWDC的USBApp示例代码开发USB设备驱动,驱动在设备连接时启动正常,readCompleted方法能被正常调用,但action->GetReference()仅获取到全\0字符。通过PyUSB验证,该设备在Mac上可正常返回1024字节的数据块。
相关代码定义与实现
Ivars定义
struct Mk1dDriver_IVars { IOUSBHostInterface *interface; IOUSBHostPipe *inPipe; OSAction *ioCompleteCallback; IOBufferMemoryDescriptor *inData; uint16_t maxPacketSize; };
Start方法实现
kern_return_t IMPL(Mk1dDriver, Start) { kern_return_t ret; IOUSBStandardEndpointDescriptors descriptors; ret = Start(provider, SUPERDISPATCH); __Require(kIOReturnSuccess == ret, Exit); ret = RegisterService(); if (ret != kIOReturnSuccess) { Log("Start() - Failed to register service with error: 0x%08x.", ret); goto Exit; } ivars->interface = OSDynamicCast(IOUSBHostInterface, provider); __Require_Action(NULL != ivars->interface, Exit, ret = kIOReturnNoDevice); ret = ivars->interface->Open(this, 0, NULL); __Require(kIOReturnSuccess == ret, Exit); ret = ivars->interface->CopyPipe(kMyEndpointAddress, &ivars->inPipe); __Require(kIOReturnSuccess == ret, Exit); ret = ivars->interface->CreateIOBuffer(kIOMemoryDirectionIn, 1024, &ivars->inData); __Require(kIOReturnSuccess == ret, Exit); ret = OSAction::Create(this, Mk1dDriver_ReadComplete_ID, IOUSBHostPipe_CompleteAsyncIO_ID, 0, &ivars->ioCompleteCallback); __Require(kIOReturnSuccess == ret, Exit); ret = ivars->inPipe->AsyncIO(ivars->inData, ivars->maxPacketSize, ivars->ioCompleteCallback, 0); __Require(kIOReturnSuccess == ret, Exit); os_log(OS_LOG_DEFAULT,"Finish"); // WWDC slides don't show the full function // i.e. this is still unfinished Exit: return ret; }
注:与苹果示例代码的唯一区别是将
CreateIOBuffer的capacity设为1024,否则会返回kIOReturnNoMemory错误。
ReadComplete方法实现
void IMPL(Mk1dDriver, ReadComplete) { char output[1024]; memcpy(action->GetReference(), &output, 1024); os_log(OS_LOG_DEFAULT,"ReadComplete"); }
问题分析与解决
核心错误点
- 内存拷贝方向完全错误:当前代码是把未初始化的
output栈数组(默认全\0)拷贝到action->GetReference()指向的内存,完全搞反了数据流向,正确逻辑应该是把设备返回的数据拷贝到output中。 - 对
action->GetReference()的作用误解:这个方法返回的是调用AsyncIO时传入的最后一个上下文参数(你这里传的是0),并不是设备返回数据的缓冲区指针,真正的接收数据存在ivars->inData里。 maxPacketSize未初始化:Ivars中定义的maxPacketSize没有赋值,调用AsyncIO时传入的是垃圾值,会导致读取长度异常。
修正步骤
1. 修复ReadComplete方法逻辑
修改方法,从inData缓冲区读取设备返回的数据,同时重新发起异步读取以保持数据接收循环:
void IMPL(Mk1dDriver, ReadComplete) { char output[1024] = {0}; uint32_t actualLength = 0; kern_return_t ret = ivars->inPipe->GetAsyncIOCompletionStatus(action, &actualLength, NULL); if (ret == kIOReturnSuccess && actualLength > 0) { void* buffer = ivars->inData->GetBytesNoCopy(); if (buffer != NULL) { memcpy(output, buffer, MIN(actualLength, 1024)); os_log(OS_LOG_DEFAULT, "Read %u bytes of data", actualLength); } } // 重新发起下一次异步读取 ivars->inPipe->AsyncIO(ivars->inData, ivars->maxPacketSize, ivars->ioCompleteCallback, 0); os_log(OS_LOG_DEFAULT,"ReadComplete"); }
2. 初始化maxPacketSize
在Start方法中,CopyPipe成功后添加代码,从端点描述符获取正确的最大包大小:
// 在CopyPipe成功后插入这段代码 ret = ivars->inPipe->GetEndpointDescriptor(&descriptors); __Require(kIOReturnSuccess == ret, Exit); ivars->maxPacketSize = descriptors.bMaxPacketSize;
3. 验证端点地址正确性
确认kMyEndpointAddress是正确的IN端点格式(0x80 | 端点号,比如端点1的IN地址为0x81),端点地址错误会导致无法读取有效数据。
额外说明
action->GetReference()是异步IO的上下文参数,若需要传递自定义数据,可以在AsyncIO最后一个参数传入指针(比如ivars),但它本身并不存储设备返回的数据。- 必须在
ReadComplete中重新调用AsyncIO,否则驱动只会执行一次读取操作,后续无法接收设备数据。
内容的提问来源于stack exchange,提问作者xarly
相关产品推荐
相关产品推荐

