40、用户空间 USB 驱动程序全解析

用户空间 USB 驱动程序全解析

一、用户空间 USB 驱动概述

从用户体验的角度来看,需要内核驱动的应用程序会带来一些不便。安装内核驱动通常需要写入“Extensions”目录,这要求用户具备管理员权限,用户需要运行安装程序、输入管理员账户密码,甚至可能需要重启计算机才能开始使用应用程序。而如果应用程序不需要内核驱动,安装过程可以像从 Mac App Store 下载应用程序一样简单。

在某些情况下,原本需要内核驱动的应用程序可以在不编写运行于内核的代码的情况下实现。这样做的好处是无需安装内核驱动,用户安装应用程序时不需要管理员权限,而且代码中的错误最多只会导致应用程序崩溃,不会引发内核恐慌从而使整个系统崩溃。

不过,并非所有硬件设备都能通过用户空间驱动进行控制。例如,包含内存映射地址范围的设备以及会产生中断的设备都需要内核驱动,这意味着所有 PCI 和 Thunderbolt 设备都需要内核驱动的支持。但基于 USB 和 FireWire 的硬件设备是用户空间驱动的理想选择。

当然,也不是所有 USB 和 FireWire 设备都适合使用用户空间驱动。比如需要在 /dev 目录下创建设备接口文件的驱动(如串口驱动或存储设备驱动),以及可被多个应用程序同时使用或系统需要使用的设备(如音频驱动),应该编写为内核驱动。但这些情况属于少数,大多数需要自定义驱动的 USB 设备都可以由用户空间驱动控制。

二、背后的原理

用户空间驱动实际上也需要内核驱动的支持,IOUSBDevice 和 IOUSBInterface 对象为与 USB 设备进行交互提供了基础。为了让用户空间应用程序能够使用这些对象,IOUSBFamily 为内核中创建的每个 IOUSBDevice 和 IOUSBInterface 实例发布了用户客户端。

用户客户端非常通用,其作用仅仅是将 IOUSBDevice 和 IOUSBInterface 类的方法暴露给用户空间。例如,IOUSBDevice 类的用户客户端包含获取和设置设备活动配置、执行设备请求以及遍历设备接口等方法;IOUSBInterface 类的用户客户端包含向指定端点读写数据的方法。

应用程序无需直接调用用户客户端类的方法,I/O Kit 框架提供了一个名为 IOUSBLib 的高级 API 来控制硬件。用户空间 USB 驱动的分层结构如下:

graph LR
    A[应用层] --> B[IOUSBLib]
    B --> C[I/O Kit 框架]
    C --> D[Mac OS X 系统库]
    D --> E[USB 硬件]
三、IOUSBLib 框架

IOUSBLib 是用于编写 USB 设备用户空间驱动的库,它是 I/O Kit 框架的一部分。用户空间驱动的首要任务是监测其感兴趣的 USB 设备的连接情况。由于 USB 设备可以随时连接或断开,应用程序启动时不能保证所需设备一定存在,因此安装一个通知回调来监测设备的连接是个不错的选择。

创建匹配字典是定位设备的常用方法。对于 USB 设备,可以使用以下代码创建一个匹配字典:

matchingDictionary = IOServiceMatching(kIOUSBDeviceClassName);  // “IOUSBDevice”

但这个匹配字典过于宽泛,会匹配所有连接到计算机的 USB 设备,包括键盘和鼠标。通常应用程序只对特定的 USB 设备感兴趣,因此需要缩小匹配范围。可以通过包含 USB 设备的特定产品 ID 和供应商 ID 来实现,示例代码如下:

#include <IOKit/IOKitLib.h>
#include <IOKit/usb/IOUSBLib.h>
#include <CoreFoundation/CoreFoundation.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;
}

对于复合 USB 设备,驱动程序可能更倾向于匹配特定的接口而非整个 USB 设备。用户空间驱动可以通过为特定的 IOUSBInterface 对象实例创建匹配字典来实现这一点。

创建匹配字典后,应用程序可以使用该字典遍历所有符合其规格的内核对象,或者安装回调以在符合条件的内核对象出现时接收通知。以下是一个遍历所有符合匹配字典的内核设备的示例函数:

void    MyFindMatchingDevices (CFDictionaryRef matchingDictionary)
{
        io_iterator_t           iterator = 0;
        io_service_t            usbDeviceRef;
        kern_return_t           err;

        // Find all kernel objects that match the dictionary.
        err = IOServiceGetMatchingServices(kIOMasterPortDefault, matchingDictionary,  
                       &iterator);
        if (err == 0)
        {
                // Iterate over all matching kernel objects.
                while ((usbDeviceRef = IOIteratorNext(iterator)) != 0)
                {
                        IOUSBDeviceInterface300**       usbDevice;

                        // Create a driver for this device instance
                        usbDevice = MyStartDriver(usbDeviceRef);
                        IOObjectRelease(usbDeviceRef);
                }

                IOObjectRelease(iterator);
        }
}
四、实例化用户空间类

匹配字典的迭代器会返回多个 io_service_t 对象,每个对象代表一个内核对象。IOUSBLib 接口通过一个包装 io_service_t 对象的 C++ 类来实现。IOUSBDevice 类的用户空间等效类是 IOUSBDeviceInterface,IOUSBInterface 类的用户空间等效类是 IOUSBInterfaceInterface。

以下是一个从 io_service_t 实例化 IOUSBDeviceInterface 类的示例代码:

IOUSBDeviceInterface300**       MyStartDriver (io_service_t usbDeviceRef)
{
        SInt32                          score;
        IOCFPlugInInterface**           plugin;
        IOUSBDeviceInterface300**       usbDevice = NULL;
        kern_return_t                   err;

        err = IOCreatePlugInInterfaceForService(usbDeviceRef, kIOUSBDeviceUserClientTypeID,
                                                   kIOCFPlugInInterfaceID, &plugin, &score);
        if (err == 0)
        {
                err = (*plugin)->QueryInterface(plugin,  
                        CFUUIDGetUUIDBytes(kIOUSBDeviceInterfaceID300),
                        (LPVOID)&usbDevice);
                IODestroyPlugInInterface(plugin);
        }

        return usbDevice;
}

如果应用程序创建的匹配字典指定的是 IOUSBInterface,那么它接收到的每个 io_service_t 值将代表一个内核 IOUSBInterface 对象。在这种情况下,应用程序需要实例化 IOUSBInterfaceInterface 类,只需对上述代码中的部分参数进行修改即可。

无论实例化的是 IOUSBDeviceInterface 还是 IOUSBInterfaceInterface,代码结构都是相同的。首先调用 IOCreatePlugInInterfaceForService() 函数,该函数返回一个 IOCFPlugInInterface 类型的对象,此对象用于实例化用户空间 I/O Kit 类,完成实例化后即可释放。IOCFPlugInInterface 类包含一个 QueryInterface() 方法,应用程序使用该方法获取指向 IOUSBDeviceInterface 或 IOUSBInterfaceInterface 对象的指针。

IOUSBLib 支持类的版本控制,应用程序请求接口时需要指定所需类的版本。类的版本是类名的一部分,例如 IOUSBDeviceInterface300 表示 IOUSBFamily 3.0.0 版本中包含的 IOUSBDeviceInterface 类的版本。

需要注意的是,IOUSBLib 返回的是对象指针的指针,因此在调用对象的方法之前,保存接口的变量需要进行额外的解引用。而且,IOUSBLib 类的每个方法都需要将对象的引用作为第一个参数传递。

五、处理异步操作

IOUSBLib 类的许多方法执行的是异步操作。所有异步方法都接受两个参数:一个指向回调函数的指针和一个名为“refcon”的参数,应用程序可以通过该参数将任意上下文值传递给回调函数。回调函数的签名如下:

void    MyCallbackFunction (void* refcon, IOReturn result, void* arg0);

其中,refcon 是应用程序的任意上下文参数,result 报告操作的总体结果,arg0 由 IOUSBLib 提供,其值取决于执行的操作类型。

用户空间应用程序可以使用运行循环来同步来自 IOUSBLib 的完成回调与其他代码。应用程序需要为执行异步操作的 IOUSBLib 类创建一个运行循环源。IOUSBLib 类提供了带有“Create”和“Get”前缀的方法(如 CreateDeviceAsyncEventSource 和 GetDeviceAsyncEventSource),但“Get”方法只会返回之前通过“Create”方法初始化的对象。

以下是一个创建并安装用于接收来自 IOUSBDeviceInterface 类的异步通知的运行循环源的示例函数:

IOReturn   InstallRunLoopSourceForUSBDevice (IOUSBDeviceInterface300** usbDevice)
{
     CFRunLoopSourceRef         runLoopSource;
     IOReturn                   error;

     error = (*usbDevice)->CreateDeviceAsyncEventSource(usbDevice, &runLoopSource);
     if (error == kIOReturnSuccess)
           CFRunLoopAddSource(CFRunLoopGetCurrent(), runLoopSource, kCFRunLoopDefaultMode);

     return error;
}

应用程序可以将运行循环源安装在任意线程的运行循环中,包括应用程序主线程的运行循环。对象所有权规则遵循所有 Core Foundation 函数的惯例:如果应用程序通过“Create”方法获取对象,则拥有该对象并负责释放它;如果通过“Get”方法获取对象,则不拥有该对象的引用,若要保留该对象,需要先显式保留它。

六、IOUSBDeviceInterface 类

IOUSBDeviceInterface 类是一个用户空间类,提供与内核驱动使用的 IOUSBDevice 类等效的功能。虽然它提供的功能与内核对应类相似,但实现方式不同,因此基于 IOUSBDevice 类编写的内核 USB 驱动不能直接用于用户空间 USB 驱动。

应用程序获取对用户空间 IOUSBDeviceInterface 的引用后,首先需要配置 USB 硬件。这与内核驱动的配置步骤类似,应用程序需要通过调用 USBDeviceOpen() 方法获取对 USB 硬件的独占访问权,防止硬件配置被其他驱动更改:

error = (*usbDevice)->USBDeviceOpen(usbDevice);

如果该方法返回的错误代码为 kIOReturnSuccess,则应用程序已获得配置硬件的独占访问权;如果其他应用程序或驱动已经获得了独占访问权,调用 USBDeviceOpen() 将失败,错误代码为 kIOReturnExclusiveAccess,应用程序应中止对设备的所有进一步访问,并可能向用户报告错误。

当应用程序使用完设备后,应调用 USBDeviceClose() 方法释放对硬件的独占访问权:

error = (*usbDevice)->USBDeviceClose(usbDevice);

IOUSBDeviceInterface 类提供了一系列方法来访问 USB 设备描述符中的信息,以下是部分方法的总结:
|方法|功能|
| ---- | ---- |
|GetDeviceClass, GetDeviceSubClass, GetDeviceProtocol|从 USB 设备描述符中返回设备类、子类和协议|
|GetDeviceVendor|返回设备的 USB 供应商 ID|
|GetDeviceProduct|返回设备的 USB 产品 ID|
|GetDeviceReleaseNumber|返回设备的发布版本号|
|USBGetManufacturerStringIndex|返回设备制造商名称字符串的索引|
|USBGetProductStringIndex|返回设备产品名称字符串的索引|
|USBGetSerialNumberStringIndex|返回设备序列号字符串的索引|
|GetNumberOfConfigurations|返回设备在当前速度下支持的配置数量|

此外,还有一些方法允许应用程序读取与 USB 设备当前状态相关的动态属性,以及对 USB 设备进行重置和发送控制请求等操作。以下是一个使用 DeviceRequest 方法从 USB 字符串表中读取制造商字符串的示例函数:

#include <IOKit/usb/IOUSBLib.h>
#include <IOKit/usb/USBSpec.h>
#include <CoreFoundation/CoreFoundation.h>

IOReturn                PrintDeviceManufacturer (IOUSBDeviceInterface300** usbDevice)
{
        UInt8                   stringIndex;
        IOUSBDevRequest         devRequest;
        UInt8                   buffer[256];
        CFStringRef             manufString;
        IOReturn                error;

        // Get the index in the string table for the manufacturer.
        error = (*usbDevice)->USBGetManufacturerStringIndex(usbDevice, &stringIndex);
        if (error != kIOReturnSuccess)
                return error;

        // Perform a device request to read the string descriptor.
        devRequest.bmRequestType = USBmakebmRequestType(kUSBIn, kUSBStandard, kUSBDevice);
        devRequest.bRequest = kUSBRqGetDescriptor;
        devRequest.wValue = (kUSBStringDesc << 8) | stringIndex;
        devRequest.wIndex = 0x409;              // Language setting - specify US English
        devRequest.wLength = sizeof(buffer);
        devRequest.pData = &buffer[0];
        bzero(&buffer[0], sizeof(buffer));
        //
        error = (*usbDevice)->DeviceRequest(usbDevice, &devRequest);
        if (error != kIOReturnSuccess)
                return error;

        // Create a CFString representation of the returned data.
        int             strLength;
        strLength = buffer[0] - 2;              // First byte is length (in bytes)
        manufString = CFStringCreateWithBytes(kCFAllocatorDefault, &buffer[2], strLength,
                                                kCFStringEncodingUTF16LE, false);
        // Print the manufacturer string.
        CFShow(manufString);
        CFRelease(manufString);

        return error;
}
七、初始化 USB 设备

应用程序通常会采取以下步骤来初始化新连接到系统的 USB 设备:
1. 获取设备支持的配置数量。
2. 读取第一个配置的描述符。
3. 将第一个配置设置为活动配置。
4. 创建一个迭代器来遍历活动配置中的所有接口。
5. 遍历所有接口并进行相应的设置。

以下是一个示例初始化函数:

IOReturn         MyConfigureDevice (IOUSBDeviceInterface300** usbDevice)
{
        UInt8                            numConfigurations;
        IOUSBConfigurationDescriptorPtr  configDesc;
        IOUSBFindInterfaceRequest        interfaceRequest;
        io_iterator_t                    interfaceIterator;
        io_service_t                     usbInterfaceRef;
        IOReturn                         error;

        // Get the count of the device's configurations.
        error = (*usbDevice)->GetNumberOfConfigurations(usbDevice, &numConfigurations);
        if (error != kIOReturnSuccess)
                return error;
        // Ensure the device has at least one configuration
        if (numConfigurations == 0)
                return kIOReturnError;

        // Read the descriptor for the first configuration.
        error = (*usbDevice)->GetConfigurationDescriptorPtr(usbDevice, 0, &configDesc);
        if (error != kIOReturnSuccess)
                return error;

        // Make the first configuration the active configuration.
        error = (*usbDevice)->SetConfiguration(usbDevice, configDesc->bConfigurationValue);
        if (error != kIOReturnSuccess)
                return error;

        // Create an iterator over all interfaces in the active configuration.
        interfaceRequest.bInterfaceClass = kIOUSBFindInterfaceDontCare;
        interfaceRequest.bInterfaceSubClass = kIOUSBFindInterfaceDontCare;
        interfaceRequest.bInterfaceProtocol = kIOUSBFindInterfaceDontCare;
        interfaceRequest.bAlternateSetting = kIOUSBFindInterfaceDontCare;

        error = (*usbDevice)->CreateInterfaceIterator(usbDevice, &interfaceRequest,  
                     &interfaceIterator);
        if (error != kIOReturnSuccess)
                return error;

        // Iterate over all interfaces.
        while ((usbInterfaceRef = IOIteratorNext(interfaceIterator)) != 0)
        {
                MySetupInterface(usbInterfaceRef);
                IOObjectRelease(usbInterfaceRef);
        }
        IOObjectRelease(interfaceIterator);

        return kIOReturnSuccess;
}

这个函数首先将设备的活动配置设置为已知的配置(这里是设备的第一个配置),这是必要的步骤,因为设备在应用程序启动之前可能已被其他应用程序使用,其状态可能未知。通过上述步骤,应用程序可以有效地初始化新连接的 USB 设备并进行相应的配置和操作。

用户空间 USB 驱动程序全解析

八、IOUSBDeviceInterface 类的动态属性读取方法

IOUSBDeviceInterface 类还提供了一系列方法,用于读取与 USB 设备当前状态相关的动态属性,这些属性能够帮助开发者更好地了解设备的实时情况。以下是这些方法的详细介绍:
|方法|功能|
| ---- | ---- |
|GetDeviceAddress|返回 USB 设备的地址,该地址在其连接的总线上是唯一的|
|GetDeviceSpeed|返回设备的速度,可能的值包括 kUSBDeviceSpeedLow、kUSBDeviceSpeedFull 或 kUSBDeviceSpeedHigh|
|GetLocationID|返回一个 32 位的值,该值基于设备连接的 USB 集线器和端口,唯一标识系统上的 USB 设备。重启计算机后该 ID 不会改变,但如果设备连接到其他集线器或端口,该 ID 会改变|
|GetBusFrameNumber|返回设备所连接的 USB 总线的当前帧号,同时返回内核驱动处理请求时对应的系统主机时间,系统时间可能在返回的 USB 帧内的任何位置|
|GetBusFrameNumberWithTime|返回设备所连接的 USB 总线的当前帧号,并返回该帧开始时对应的系统主机时间,该方法在 IOUSBDeviceInterface 类的后续版本中引入,取代了 GetBusFrameNumber() 方法|
|GetBusMicroFrameNumber|返回设备所连接的 USB 总线的当前微帧号,同时返回内核驱动处理请求时对应的系统主机时间,其行为与 GetBusFrameNumber() 方法类似|

这些方法为开发者提供了丰富的设备状态信息,有助于在不同场景下对 USB 设备进行更精确的控制和管理。

九、IOUSBDeviceInterface 类的设备重置方法

为了应对设备出现异常或需要重新配置的情况,IOUSBDeviceInterface 类提供了一些设备重置方法,具体如下:
|方法|功能|
| ---- | ---- |
|ResetDevice|重置 USB 设备,使其回到未配置状态|
|USBDeviceReEnumerate|指示设备所连接的集线器重置该设备连接的端口,相当于将设备从 USB 端口断开并重新连接|
|USBDeviceSuspend|根据布尔参数的值,该方法可以暂停或恢复 USB 设备所连接的端口。如果暂停设备,所有未完成的对该设备的事务将被中止|
|USBDeviceAbortPipeZero|中止控制端点上的所有未完成事务|

这些重置方法为处理 USB 设备的异常情况提供了有效的手段,确保设备能够在出现问题时快速恢复正常。

十、IOUSBDeviceInterface 类的控制请求方法

在与 USB 设备进行交互时,常常需要向设备发送控制请求。IOUSBDeviceInterface 类提供了以下几种控制请求方法:
|方法|功能|
| ---- | ---- |
|DeviceRequest|这是一个同步方法,直到设备请求完成后才会返回。设备请求由内核 USB 驱动使用的相同 IOUSBDevRequest 结构描述|
|DeviceRequestTO|同样是同步方法,接受两个以毫秒为单位的超时值。该方法会在设备请求完成或指定的超时时间过去后返回(以先到者为准)。调用者需要提供两个超时值,因为设备在处理请求时可能会停止发送或接收数据,一个超时值指定自上次数据传输后等待的最长时间,另一个超时值指定控制请求从开始到完成允许的最长时间。如果任何一个指定的超时条件发生,控制请求将被中止|
|DeviceRequestAsync|是 DeviceRequest 方法的异步版本。该方法接受一个回调函数,I/O Kit 使用该回调函数在请求完成后通知调用者。需要注意的是,该方法返回 kIOReturnSuccess 并不表示请求已成功完成,而是表示操作已成功启动。操作的实际结果将通过回调函数返回,传递给完成回调的 arg0 参数的值表示从设备读取或写入设备的字节数|

以下是使用 DeviceRequest 方法的示例代码,用于从 USB 字符串表中读取制造商字符串:

#include <IOKit/usb/IOUSBLib.h>
#include <IOKit/usb/USBSpec.h>
#include <CoreFoundation/CoreFoundation.h>

IOReturn                PrintDeviceManufacturer (IOUSBDeviceInterface300** usbDevice)
{
        UInt8                   stringIndex;
        IOUSBDevRequest         devRequest;
        UInt8                   buffer[256];
        CFStringRef             manufString;
        IOReturn                error;

        // Get the index in the string table for the manufacturer.
        error = (*usbDevice)->USBGetManufacturerStringIndex(usbDevice, &stringIndex);
        if (error != kIOReturnSuccess)
                return error;

        // Perform a device request to read the string descriptor.
        devRequest.bmRequestType = USBmakebmRequestType(kUSBIn, kUSBStandard, kUSBDevice);
        devRequest.bRequest = kUSBRqGetDescriptor;
        devRequest.wValue = (kUSBStringDesc << 8) | stringIndex;
        devRequest.wIndex = 0x409;              // Language setting - specify US English
        devRequest.wLength = sizeof(buffer);
        devRequest.pData = &buffer[0];
        bzero(&buffer[0], sizeof(buffer));
        //
        error = (*usbDevice)->DeviceRequest(usbDevice, &devRequest);
        if (error != kIOReturnSuccess)
                return error;

        // Create a CFString representation of the returned data.
        int             strLength;
        strLength = buffer[0] - 2;              // First byte is length (in bytes)
        manufString = CFStringCreateWithBytes(kCFAllocatorDefault, &buffer[2], strLength,
                                                kCFStringEncodingUTF16LE, false);
        // Print the manufacturer string.
        CFShow(manufString);
        CFRelease(manufString);

        return error;
}

这个示例展示了如何通过 DeviceRequest 方法与 USB 设备进行交互,读取设备的制造商字符串信息。

十一、设备配置与接口迭代方法

IOUSBDeviceInterface 类提供了一些方法,用于检查和设置设备配置,以及迭代设备的接口。这些方法通常在应用程序首次检测到 USB 设备时调用,用于初始化设备。以下是这些方法的介绍:
|方法|功能|
| ---- | ---- |
|GetConfigurationDescriptionPtr|返回指定配置的描述符指针。需要注意的是,虽然调用者会收到一个指向 IOUSBConfigurationDescriptorPtr 结构的指针,但该缓冲区由 IOUSBDeviceInterface 对象拥有,调用者不应释放它|
|GetConfiguration|返回设备的活动配置编号。这里的编号不是配置的索引,而是活动配置描述中的 bConfigurationValue 的值|
|SetConfiguration|设置设备的活动配置,通过传递所需配置描述中的 bConfigurationValue 来指定配置|
|CreateInterfaceIterator|创建一个遍历设备接口的迭代器。与内核中的等效方法类似,调用者需要提供一个 IOUSBFindInterfaceRequest 结构,指定返回的接口必须匹配的属性|

以下是一个示例初始化函数,展示了如何使用这些方法来配置 USB 设备:

IOReturn         MyConfigureDevice (IOUSBDeviceInterface300** usbDevice)
{
        UInt8                            numConfigurations;
        IOUSBConfigurationDescriptorPtr  configDesc;
        IOUSBFindInterfaceRequest        interfaceRequest;
        io_iterator_t                    interfaceIterator;
        io_service_t                     usbInterfaceRef;
        IOReturn                         error;

        // Get the count of the device's configurations.
        error = (*usbDevice)->GetNumberOfConfigurations(usbDevice, &numConfigurations);
        if (error != kIOReturnSuccess)
                return error;
        // Ensure the device has at least one configuration
        if (numConfigurations == 0)
                return kIOReturnError;

        // Read the descriptor for the first configuration.
        error = (*usbDevice)->GetConfigurationDescriptorPtr(usbDevice, 0, &configDesc);
        if (error != kIOReturnSuccess)
                return error;

        // Make the first configuration the active configuration.
        error = (*usbDevice)->SetConfiguration(usbDevice, configDesc->bConfigurationValue);
        if (error != kIOReturnSuccess)
                return error;

        // Create an iterator over all interfaces in the active configuration.
        interfaceRequest.bInterfaceClass = kIOUSBFindInterfaceDontCare;
        interfaceRequest.bInterfaceSubClass = kIOUSBFindInterfaceDontCare;
        interfaceRequest.bInterfaceProtocol = kIOUSBFindInterfaceDontCare;
        interfaceRequest.bAlternateSetting = kIOUSBFindInterfaceDontCare;

        error = (*usbDevice)->CreateInterfaceIterator(usbDevice, &interfaceRequest,  
                     &interfaceIterator);
        if (error != kIOReturnSuccess)
                return error;

        // Iterate over all interfaces.
        while ((usbInterfaceRef = IOIteratorNext(interfaceIterator)) != 0)
        {
                MySetupInterface(usbInterfaceRef);
                IOObjectRelease(usbInterfaceRef);
        }
        IOObjectRelease(interfaceIterator);

        return kIOReturnSuccess;
}

该函数的执行流程如下:

graph TD
    A[获取设备配置数量] --> B{配置数量是否为 0}
    B -- 是 --> C[返回错误]
    B -- 否 --> D[读取第一个配置的描述符]
    D --> E[设置第一个配置为活动配置]
    E --> F[创建接口迭代器]
    F --> G[遍历所有接口]
    G --> H[设置接口]
    H --> I[释放接口引用]
    I --> J[释放迭代器]
    J --> K[返回成功]

通过上述步骤,应用程序可以将设备的活动配置设置为已知的配置,并对设备的各个接口进行初始化设置。

十二、总结

用户空间 USB 驱动程序为开发者提供了一种无需内核驱动即可控制 USB 设备的方式,大大简化了应用程序的安装和使用过程。通过使用 IOUSBLib 框架,开发者可以方便地实现对 USB 设备的监测、配置和交互。

在实际开发中,开发者需要根据具体的需求选择合适的方法和回调函数,处理好设备的连接、断开、配置和数据传输等操作。同时,要注意处理异步操作和异常情况,确保应用程序的稳定性和可靠性。

通过本文的介绍,相信开发者对用户空间 USB 驱动程序有了更深入的了解,能够在实际项目中更好地运用相关技术,实现对 USB 设备的高效控制和管理。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值