使用IOKit/IOUSBLib无法访问USB设备的问题求助
问题描述
我正在调试BUSYLIGHT OMEGA USB设备,采用IOKit框架实现访问。参考资料整理出以下步骤:
- 用IOServiceMatching结合vendorID和productID搜索设备
- 通过io_iterator_t查找目标设备
- 创建IOUSBDeviceInterface300设备驱动实例
- 创建IOUSBInterfaceInterface实例以读写设备数据
知晓这是在用户空间创建类内核对象以规避内核级问题,参考《OS X and iOS Kernel Programming》第15章编写代码。代码执行到标注“ERROR OCCURS ON THE NEXT LINE”的DeviceRequest调用时出错,错误码为e00002c2,调试时还出现EXC_BAD_ACCESS(code=257)内存访问错误,推测是权限或非法内存访问问题。研究过权限授权(entitlements)但不知如何配置以获取设备访问权限,现寻求错误原因分析及权限配置方法。
附完整代码
#include <CoreFoundation/CoreFoundation.h> #include <IOKit/IOKitLib.h> #include <IOKit/IOCFPlugIn.h> #include <IOKit/usb/IOUSBLib.h> #include <IOKit/usb/USBSpec.h> CFDictionaryRef MyCreateUSBMatchingDictionary(SInt32 idVendor, SInt32 idProduct) { CFMutableDictionaryRef matchingDictionary = NULL; CFNumberRef numberRef; // Create a matching dictionary for IOUSBDevice matchingDictionary = IOServiceMatching(kIOUSBDeviceClassName); if (matchingDictionary == NULL) goto bail; // Add the USB Vendor ID to the matching dictionary numberRef = CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt32Type, &idVendor); if (numberRef == NULL) goto bail; CFDictionaryAddValue(matchingDictionary, CFSTR(kUSBVendorID), numberRef); CFRelease(numberRef); // Add the USB Product ID to the matching dictionary numberRef = CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt32Type, &idProduct); if (numberRef == NULL) goto bail; CFDictionaryAddValue(matchingDictionary, CFSTR(kUSBProductID), numberRef); CFRelease(numberRef); // Success - return the dictionary to the caller return matchingDictionary; bail: // Failure - release resources and return NULL if (matchingDictionary != NULL) CFRelease(matchingDictionary); return NULL; } IOUSBDeviceInterface300** MyStartDriver(io_service_t usbDeviceRef) { SInt32 score; IOCFPlugInInterface** plugin; IOUSBDeviceInterface300** usbDevice = NULL; kern_return_t err1, err2; err1 = IOCreatePlugInInterfaceForService(usbDeviceRef, kIOUSBDeviceUserClientTypeID, kIOCFPlugInInterfaceID, &plugin, &score); if (err1 == 0) { err1 = (*plugin)->QueryInterface(plugin, CFUUIDGetUUIDBytes(kIOUSBDeviceInterfaceID300), (LPVOID*)&usbDevice); // NOTE: In the code this is stated as "LPVOIDE" without the astrix err2 = (*usbDevice)->USBDeviceOpen(usbDevice); printf("Error code when opening the usbDevice: %d\n", err2); UInt8 stringIndex; err2 = (*usbDevice)->USBGetManufacturerStringIndex(usbDevice, &stringIndex); printf("Error code when accessing the mfg string index: %d\n", err2); IOUSBDevRequest devRequest; UInt8 buffer[256]; devRequest.bmRequestType = USBmakebmRequestType(kUSBIn, kUSBStandard, kUSBDevice); devRequest.bRequest = kUSBRqGetDescriptor; devRequest.wValue = (kUSBStringDesc << 8) | stringIndex; devRequest.wIndex = 0x409; devRequest.wLength = sizeof(buffer); devRequest.pData = &buffer[0]; bzero(&buffer[0], sizeof(buffer)); // ERROR OCCURS ON THE NEXT LINE i.e. "(*usbDevice)->DeviceRequest(&usbDevice, &devRequest)" err2 = (*usbDevice)->DeviceRequest(&usbDevice, &devRequest); printf("Error code when making device request: %x", err2); IODestroyPlugInInterface(plugin); } return usbDevice; } IOUSBDeviceInterface300 ** MyCreateInterfaceClass(io_service_t usbInterfaceRef) { SInt32 score; IOCFPlugInInterface** plugin; IOUSBDeviceInterface300** usbInterface = NULL; kern_return_t err; err = IOCreatePlugInInterfaceForService(usbInterfaceRef, kIOUSBInterfaceUserClientTypeID, kIOCFPlugInInterfaceID, &plugin, &score); if (err == 0) { err = (*plugin)->QueryInterface(plugin, CFUUIDGetUUIDBytes(kIOUSBInterfaceInterfaceID300), (LPVOID*)&usbInterface); IODestroyPlugInInterface(plugin); } printf("Error code when opening the usbinterface: %x", err); return usbInterface; } void MyFindMatchingDevices (CFDictionaryRef matchingDictionary) { io_iterator_t iterator = 0; io_service_t usbDeviceRef; kern_return_t err; // Find all kernel objects that match the directory err = IOServiceGetMatchingServices(kIOMasterPortDefault, matchingDictionary, &iterator); if (err == 0) { // Iterate over all matching kernel objects while ((usbDeviceRef = IOIteratorNext(iterator)) !=0 ) { IOUSBDeviceInterface300** usbDevice; IOUSBDeviceInterface300** usbInterface; // Create a driver for this device instance usbDevice = MyStartDriver(usbDeviceRef); //usbInterface = MyCreateInterfaceClass(usbDeviceRef); IOObjectRelease(iterator); } } } int main() { CFDictionaryRef myDict = MyCreateUSBMatchingDictionary(10171, 15311); if (myDict == NULL) { printf("Could not find matching dictionary item -- stopping"); exit(0); } MyFindMatchingDevices(myDict); return 0; }
ioreg设备信息
执行ioreg -p IOUSB -w0 -l得到设备信息:
+-o BUSYLIGHT OMEGA@00110000 <class IOUSBHostDevice, id 0x10008951b, registered, matched, active, busy 0 (38 ms), retain 31> { "sessionID" = 23078049098302 "USBSpeed" = 1 "idProduct" = 15311 "iManufacturer" = 1 "bDeviceClass" = 0 "IOPowerManagement" = {"PowerOverrideOn"=Yes,"DevicePowerState"=2,"CurrentPowerState"=2,"CapabilityFlags"=32768,"MaxPowerState"=2,"DriverPowerState"=0} "bcdDevice" = 256 "bMaxPacketSize0" = 8 "iProduct" = 2 "iSerialNumber" = 3 "bNumConfigurations" = 1 "UsbDeviceSignature" = <bb27cf3b000139323230393246463030313032344646303346464646464632373032464646463330303246464646000000030000> "USB Product Name" = "BUSYLIGHT OMEGA" "locationID" = 1114112 "bDeviceSubClass" = 0 "bcdUSB" = 512 "kUSBSerialNumberString" = "922092FF001024FF03FFFFFF2702FFFF3002FFFF" "USB Address" = 4 "IOCFPlugInTypes" = {"9dc7b780-9ec0-11d4-a54f-000a27052861"="IOUSBHostFamily.kext/Contents/PlugIns/IOUSBLib.bundle"} "kUSBCurrentConfiguration" = 1 "bDeviceProtocol" = 0 "USBPortType" = 0 "IOServiceDEXTEntitlements" = (("com.apple.developer.driverkit.transport.usb")) "USB Vendor Name" = "PLENOM A/S" "Device Speed" = 1 "idVendor" = 10171 "kUSBProductString" = "BUSYLIGHT OMEGA" "USB Serial Number" = "922092FF001024FF03FFFFFF2702FFFF3002FFFF" "IOGeneralInterest" = "IOCommand is not serializable" "kUSBAddress" = 4 "kUSBVendorString" = "PLENOM A/S" }
错误原因分析
1. 内存访问错误的直接原因
代码中DeviceRequest调用的参数错误:
err2 = (*usbDevice)->DeviceRequest(&usbDevice, &devRequest);
IOUSBDeviceInterface的DeviceRequest方法第一个参数应为接口指针本身,而非指针的地址。正确调用方式:
err2 = (*usbDevice)->DeviceRequest(usbDevice, &devRequest);
传递&usbDevice会导致函数访问非法内存地址,直接触发EXC_BAD_ACCESS(code=257)错误。
2. 权限错误的深层原因
错误码e00002c2对应kIOReturnNotAuthorized,说明应用无足够权限访问USB设备。从ioreg输出的IOServiceDEXTEntitlements字段可知,设备需要特定权限,且macOS对用户空间USB访问有严格管控,必须通过正确的entitlements配置获取授权。
权限配置方法
1. 创建Entitlements文件
在Xcode项目中添加新的Property List文件(命名为MyApp.entitlements),添加以下内容:
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> <plist version="1.0"> <dict> <key>com.apple.security.device.usb</key> <array> <dict> <key>idVendor</key> <integer>10171</integer> <key>idProduct</key> <integer>15311</integer> </dict> </array> </dict> </plist>
该权限允许应用访问指定vendorID和productID的USB设备。
2. 配置Xcode项目
- 打开项目的
Signing & Capabilities标签页 - 点击
+ Capability,搜索并添加USB权限 - 在
USB权限模块中,点击+按钮,输入设备的vendorID(10171)和productID(15311) - 确保项目的
Code Signing Entitlements指向创建的MyApp.entitlements文件
3. 代码额外修正点
MyFindMatchingDevices函数中,IOObjectRelease(iterator)需放在循环外,否则第一次循环后迭代器被释放,后续循环出错:
void MyFindMatchingDevices (CFDictionaryRef matchingDictionary) { io_iterator_t iterator = 0; io_service_t usbDeviceRef; kern_return_t err; err = IOServiceGetMatchingServices(kIOMasterPortDefault, matchingDictionary, &iterator); if (err == 0) { while ((usbDeviceRef = IOIteratorNext(iterator)) !=0 ) { IOUSBDeviceInterface300** usbDevice; usbDevice = MyStartDriver(usbDeviceRef); // 使用完设备接口后释放资源 if (usbDevice) { (*usbDevice)->USBDeviceClose(usbDevice); IOObjectRelease(*usbDevice); } IOObjectRelease(usbDeviceRef); // 释放单个设备引用 } IOObjectRelease(iterator); // 循环结束后释放迭代器 } }
- 使用完
IOUSBDeviceInterface后,必须调用USBDeviceClose和IOObjectRelease释放资源,避免内存泄漏。
内容的提问来源于stack exchange,提问作者dshenoy

