在Mac上读写USB(HID)中断端点 [英] Reading and writing to USB (HID) interrupt endpoints on Mac

查看:1525
本文介绍了在Mac上读写USB(HID)中断端点的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我试图与一个相当具体的USB设备通信,并开发Windows和Mac代码来做到这一点。



设备是具有HID接口(类别3)的USB设备,具有两个端点,一个中断输入和一个中断输出。设备的性质使得仅当从主机请求数据时才在输入端点上从设备发送数据:主机在其输入中断端点上发送其响应的数据。获取数据到设备(写)简单得多...



Windows的代码相当简单:我得到设备的句柄,然后调用ReadFile或WriteFile。显然,许多底层的异步行为是抽象出来的。它似乎工作得很好。



然而,在Mac,它是一个有点贴纸。我尝试了很多事情,没有一个是完全成功的,但这里是两个似乎最有希望的东西...



1)尝试访问通过IOUSBInterfaceInterface传递到设备(作为USB),迭代通过端点以确定输入和输出端点,并且(希望)使用ReadPipe和WritePipe进行通信。不幸的是,我无法打开界面一旦我有它,与返回值(kIOReturnExclusiveAccess),注意到已经有设备专门打开。我尝试使用IOUSBinterfaceInterface183,以便我可以调用USBInterfaceOpenSeize,但是导致相同的返回错误值。



--- update 7/30/2010 ---

显然,Apple IOUSBHIDDriver早期匹配设备,这可能阻止打开IOUSBInterfaceInterface。从一些挖掘看来,防止IOUSBHIDDriver匹配的常见方法是编写一个无代码的kext(内核扩展)与更高的探测得分。这将提前匹配,防止IOUSBHIDDriver打开设备,并且,理论上,应该允许我打开界面,直接写入和读取到端点。这是可以的,但我更喜欢不必在用户机器上安装额外的东西。如果任何人知道一个坚实的替代,我会感谢的信息。



2。)打开设备作为IOHIDDeviceInterface122(或更高版本)。为了读取,我设置了一个异步端口,当数据就绪时 - 当数据从输入中断端点上的设备发送时,要调用的事件源和回调方法。然而,要写入设备所需的数据 - 初始化响应我找不到一种方法。我被骗了。 setReport通常写入控制端点,加上我需要一个不期望任何直接响应,没有阻塞的写入。



我已经在线看过并且尝试了很多事情,但他们都没有给我成功。任何建议?我不能使用大多数Apple HIDManager代码,因为大部分是10.5+,我的应用程序必须在10.4上工作。

解决方案

p>我现在有一个工作的Mac驱动程序到USB设备,需要通过中断端点通信。这是我怎么做的:



最后,对我来说很好的方法是选项1(如上所述)。如上所述,我有问题打开设备的COM风格的IOUSBInterfaceInterface。随着时间的推移,这显然是由于HIDManager捕获设备。我无法从HIDManager捕获设备的控制一旦捕获(甚至USBInterfaceOpenSeize调用或USBDeviceOpenSeize调用将工作)。



要控制的设备我需要抓住它之前的HIDManager。解决方案是写一个无代码的kext(内核扩展)。 kext本质上是一个包,它位于System / Library / Extensions中,包含(通常)plist(属性列表)和(偶尔)内核级驱动程序等。在我的情况下,我只想要plist,它会给内核的指令在什么设备匹配。如果数据给出比HIDManager更高的探测得分,那么我基本上可以捕获设备,并使用用户空间驱动程序与它通信。



编写的kext plist,修改了一些项目特定的细节,如下:

 <?xml version = 1.0encoding =UTF-8?> 
<!DOCTYPE plist PUBLIC - // Apple // DTD PLIST 1.0 // ENhttp://www.apple.com/DTDs/PropertyList-1.0.dtd\">
< plist version =1.0>
< dict>
< key> OSBundleLibraries< / key>
< dict>
< key> com.apple.iokit.IOUSBFamily< / key>
< string> 1.8< / string>
< key> com.apple.kernel.libkern< / key>
< string> 6.0< / string>
< / dict>
< key> CFBundleDevelopmentRegion< / key>
< string>英语< / string>
< key> CFBundleGetInfoString< / key>
< string> Demi USB设备< / string>
< key> CFBundleIdentifier< / key>
< string> com.demiart.mydevice< / string>
< key> CFBundleInfoDictionaryVersion< / key>
< string> 6.0< / string>
< key> CFBundleName< / key>
< string> Demi USB设备< / string>
< key> CFBundlePackageType< / key>
< string> KEXT< / string>
< key> CFBundleSignature< / key>
< string> ????< / string>
< key> CFBundleVersion< / key>
< string> 1.0.0< / string>
< key> IOKit个人功能< / key>
< dict>
< key>设备驱动程序< / key>
< dict>
< key> CFBundleIdentifier< / key>
< string> com.apple.kernel.iokit< / string>
< key> IOClass< / key>
< string> IOService< / string>
< key> IOProviderClass< / key>
< string> IOUSBInterface< / string>
< key> idProduct< / key>
< integer> 12345< / integer>
< key> idVendor< / key>
< integer> 67890< / integer>
< key> bConfigurationValue< / key>
< integer> 1< / integer>
< key> bInterfaceNumber< / key>
< integer> 0< / integer>
< / dict>
< / dict>
< key> OSBundleRequired< / key>
< string> Local-Root< / string>
< / dict>
< / plist>

idVendor和idProduct值给出kext特异性并充分提高其探测得分。



为了使用kext,需要做以下事情(我的安装程序将为客户端做):


  1. 将所有者更改为root:wheel( sudo chown root:wheel DemiUSBDevice.kext

  2. 复制kext到扩展程序( sudo cp DemiUSBDevice.kext / System / Library / Extensions

  3. 调用 kextload 加载kext直接使用,无需重新启动( sudo kextload -vt /System/Library/Extensions/DemiUSBDevice.kext

  4. Extensions文件夹,以便下次重新启动时强制重建缓存( sudo touch / System / Library / Extensions

在这一点上,系统应该使用kext来保持HIDManager捕获我的设备。现在,该怎么办呢?如何写入和读取它?



以下是我的代码的一些简化的代码,减去任何错误处理,说明解决方案。在能够对设备做任何事情之前,应用程序需要知道设备何时附加(和分离)。注意,这只是为了说明的目的 - 一些变量是类级别,一些是全局的等。这里是初始化代码,设置attach / detach事件:

  #include< IOKit / IOKitLib.h> 
#include< IOKit / IOCFPlugIn.h>
#include< IOKit / usb / IOUSBLib.h>
#include< mach / mach.h>

#define DEMI_VENDOR_ID 12345
#define DEMI_PRODUCT_ID 67890

void DemiUSBDriver :: initialize(void)
{
IOReturn result;
Int32 vendor_id = DEMI_VENDOR_ID;
Int32 product_id = DEMI_PRODUCT_ID;
mach_port_t master_port;
CFMutableDictionaryRef matching_dict;
IONotificationPortRef notify_port;
CFRunLoopSourceRef run_loop_source;

//创建主端口
result = IOMasterPort(bootstrap_port,& master_port);

//为设备设置一个匹配的字典
matching_dict = IOServiceMatching(kIOUSBDeviceClassName);

//添加匹配参数
CFDictionarySetValue(matching_dict,CFSTR(kUSBVendorID),
CFNumberCreate(kCFAllocatorDefault,kCFNumberInt32Type,& vendor_id)
CFDictionarySetValue(matching_dict,CFSTR(kUSBProductID),
CFNumberCreate(kCFAllocatorDefault,kCFNumberInt32Type,& product_id));

//创建通知端口和事件源
notify_port = IONotificationPortCreate(master_port);
run_loop_source = IONotificationPortGetRunLoopSource(notify_port);
CFRunLoopAddSource(CFRunLoopGetCurrent(),run_loop_source,
kCFRunLoopDefaultMode);

//为辅助事件添加一个附加引用
// - each使用引用...
matching_dict =(CFMutableDictionaryRef)CFRetain(matching_dict);

//为分离事件添加通知回调
//注意:removed_iter是一个io_iterator_t,在其他地方声明
result = IOServiceAddMatchingNotification(notify_port,
kIOTerminatedNotification,matching_dict, device_detach_callback,
NULL,& removed_iter);

//调用回调以arm通知
device_detach_callback(NULL,removed_iter);

//为附加事件添加通知回调
//注意:added_iter是io_iterator_t,在其他地方声明
result = IOServiceAddMatchingNotification(notify_port,
kIOFirstMatchNotification,matching_dict, device_attach_callback,
NULL,& g_added_iter);
if(result)
{
throw Exception(无法添加附加通知回调。
}

//调用回调以arm通知
device_attach_callback(NULL,added_iter);

//'pump'运行循环来处理任何以前添加的设备
service();
}

在此初始化代码中有两种方法用作回调函数:device_detach_callback device_attach_callback(都以静态方法声明)。 device_detach_callback很简单:

  //实现
void DemiUSBDevice :: device_detach_callback(void * context,io_iterator_t iterator)
{
IOReturn result;
io_service_t obj;

while((obj = IOIteratorNext(iterator)))
{
//关闭与此服务/设备关联的所有开放资源...

//释放服务
result = IOObjectRelease(obj);
}
}

device_attach_callback是大多数魔法发生的地方。在我的代码中,我有这个分成多个方法,但在这里我将它作为一个大的整体方法...:

  void DemiUSBDevice :: device_attach_callback(void * context,
io_iterator_t iterator)
{
IOReturn result;
io_service_t usb_service;
IOCFPlugInInterface ** plugin;
HRESULT hres;
SInt32得分;
UInt16 vendor;
UInt16产品
IOUSBFindInterfaceRequest request;
io_iterator_t intf_iterator;
io_service_t usb_interface;

UInt8 interface_endpoint_count = 0;
UInt8 pipe_ref = 0xff;

UInt8 direction;
UInt8 number;
UInt8 transfer_type;
UInt16 max_packet_size;
UInt8 interval;

CFRunLoopSourceRef m_event_source;
CFRunLoopSourceRef compl_event_source;

IOUSBDeviceInterface245 ** dev = NULL;
IOUSBInterfaceInterface245 ** intf = NULL;

while((usb_service = IOIteratorNext(iterator)))
{
//创建中间插件
result = IOCreatePlugInInterfaceForService(usb_service,
kIOUSBDeviceUserClientTypeID, kIOCFPlugInInfacefaceID& plugin,
& score);

//获取设备接口
hres =(* plugin) - > QueryInterface(plugin,
CFUUIDGetUUIDBytes(kIOUSBDeviceInterfaceID245),(void **)& dev);

//释放插件 - 不再需要它
IODestroyPlugInInterface(plugin);

//正确性的双重检查id
result =(* dev) - > GetDeviceVendor(dev,& vendor);
result =(* dev) - > GetDeviceProduct(dev,& product);
if((vendor!= DEMI_VENDOR_ID)||(product!= DEMI_PRODUCT_ID))
{
continue;
}

//设置接口查找请求
request.bInterfaceClass = kIOUSBFindInterfaceDontCare;
request.bInterfaceSubClass = kIOUSBFindInterfaceDontCare;
request.bInterfaceProtocol = kIOUSBFindInterfaceDontCare;
request.bAlternateSetting = kIOUSBFindInterfaceDontCare;

result =(* dev) - > CreateInterfaceIterator(dev,& request,& intf_iterator);

while((usb_interface = IOIteratorNext(intf_iterator))
{
//创建中间插件
result = IOCreatePlugInInterfaceForService(usb_interface,
kIOUSBInterfaceUserClientTypeID,kIOCFPlugInInterfaceID ,& plugin,
& score);

//释放usb接口 - 不需要
result = IOObjectRelease(usb_interface);

//获取通用接口界面
hres =(* plugin) - > QueryInterface(plugin,CFUUIDGetUUIDBytes(
kIOUSBInterfaceInterfaceID245),(void **)& intf) ;

//释放插件接口
IODestroyPlugInInterface(plugin);

//尝试打开界面
result =(* intf) - > USBInterfaceOpen(intf);

//检查中断端点在这个接口上是否可用
//调用0xff invalid ...
m_input_pipe = 0xff; // UInt8,pipe from device to Mac
m_output_pipe = 0xff; // UInt8,pipe from Mac to device

result =(* intf) - > GetNumEndpoints(intf,& interface_endpoint_count);
if(!result)
{
//检查方向,类型等的端点
//注意pipe_ref == 0是控制端点想要它)
for(pipe_ref = 1; pipe_ref <= interface_endpoint_count; pipe_ref ++)
{
result =(* intf) - > GetPipeProperties(intf,pipe_ref,& direction,
& number,& transfer_type,& max_packet_size,& interval);
if(result)
{
break;
}

if(transfer_type == kUSBInterrupt)
{
if(direction == kUSBIn)
{
m_input_pipe = pipe_ref;
}
else if(direction == kUSBOut)
{
m_output_pipe = pipe_ref;
}
}
}
}

//设置异步完成通知
result =(* m_intf) - > CreateInterfaceAsyncEventSource m_intf,
& compl_event_source);
CFRunLoopAddSource(CFRunLoopGetCurrent(),compl_event_source,
kCFRunLoopDefaultMode);

break;
}

break;
}
}

此时,端点和一个打开的IOUSBInterfaceInterface到设备。可以通过调用以下方式来进行数据的异步写入:

  result =(intf) - > WritePipeAsync(intf,m_output_pipe ,
data,OUTPUT_DATA_BUF_SZ,device_write_completion,
NULL);

其中data是要写入的数据的char缓冲区,最终参数是要传递的可选上下文对象并且device_write_completion是一个具有以下一般形式的静态方法:

  void DemiUSBDevice :: device_write_completion(void * context, 
IOReturn结果,void * arg0)
{
// ...
}

$ b从中断端点读取的
$ b类似:

  result =(intf) - > ReadPipeAsync (intf,m_input_pipe,
data,INPUT_DATA_BUF_SZ,device_read_completion,
NULL);

其中device_read_completion的格式如下:

  void DemiUSBDevice :: device_read_completion(void * context,
IOReturn result,void * arg0)
{
// ...
请注意,为了接收这些回调,运行循环必须正在运行( //developer.apple.com/mac/library/documentation/CoreFoundation/Reference/CFRunLoopRef/Reference/reference.htmlrel =nofollow>请参阅此链接了解有关CFRunLoop的更多信息)。实现这一点的一种方法是在调用异步读或写方法之后调用 CFRunLoopRun(),此时主线程在运行循环运行时阻塞。处理回调之后,可以调用 CFRunLoopStop(CFRunLoopGetCurrent())停止运行循环并手动执行回主线程。



另一个选择(我在我的代码中)是将一个上下文对象(在下面的代码示例中名为request)传递到WritePipeAsync / ReadPipeAsync方法对象包含布尔完成标志(在本例中命名为is_done)。调用读/写方法后,不是调用 CFRunLoopRun(),可以执行类似以下内容:

  while(!(request-> is_done))
{
//运行1/10秒处理事件
Boolean returnAfterSourceHandled = false;
CFTimeInterval seconds = 0.1;
CFStringRef mode = kCFRunLoopDefaultMode;
CFRunLoopRunInMode(mode,seconds,returnAfterSourceHandled);
}

这有好处,如果你有其他线程使用运行循环不会过早退出应该另一个线程停止运行循环...



我希望这对人有帮助。我不得不从许多不完整的来源解决这个问题,这需要大量的工作,以获得良好运行...


I am attempting to communicate with a rather specific USB device and developing both Windows and Mac code to do so.

The device is a USB device with a HID interface (class 3) with two endpoints, an interrupt input and an interrupt output. The nature of the device is such that data is sent out from the device on the input endpoint only when data is requested from the host: the host sends it data which the device responds to on its input interrupt endpoint. Getting data to the device (a write) is much more simple...

The code for Windows is rather straight-forward: I get a handle to the device and then call either ReadFile or WriteFile. Apparently much of the underlying asynchronous behavior is abstracted out. It appears to work fine.

On Mac, however, it is a bit stickier. I have tried a number of things, none which have been fully successful, but here are the two things which seemed most promising...

1.) Attempt to get access to the device (as USB) via IOUSBInterfaceInterface, iterate through the endpoints to determine the input and output endpoints, and (hopefully) use ReadPipe and WritePipe to communicate. Unfortunately I am unable to open the interface once I have it, with the return value (kIOReturnExclusiveAccess) noting that something already has the device open exclusively. I have tried using IOUSBinterfaceInterface183, so that I could call USBInterfaceOpenSeize, but that results in the same return error value.

--- update 7/30/2010 ---
Apparently, the Apple IOUSBHIDDriver matches early to the device and this is what likely is preventing opening the IOUSBInterfaceInterface. From some digging about it seems that the common way to prevent the IOUSBHIDDriver from matching is to write a code-less kext (kernel extension) with a higher probe score. This would match early, preventing the IOUSBHIDDriver from opening the device, and should, in theory, permit me to open the interface and to write and read to endpoints directly. This is OK, but I would much prefer not having to install something additional on the user machine. If anyone knows of a solid alternative I would be thankful for the information.

2.) Open the device as an IOHIDDeviceInterface122 (or later). To read, I set up an async port, event source and callback method to be called when data is ready - when data is sent from the device on the input interrupt endpoint. However, to write the data — that the device needs — to initialize a response I can't find a way. I'm stumped. setReport typically writes to the control endpoint, plus I need a write that does not expect any direct response, no blocking.

I have looked around online and have tried many things, but none of them is giving me success. Any advice? I can not use much of the Apple HIDManager code since much of that is 10.5+ and my application must work on 10.4 as well.

解决方案

I have now a working Mac driver to a USB device that requires communication through interrupt endpoints. Here is how I did it:

Ultimately the method that worked well for me was option 1 (noted above). As noted, I was having issues opening the COM-style IOUSBInterfaceInterface to the device. It became clear over time that this was due to the HIDManager capturing the device. I was unable to wrest control of the device from the HIDManager once it was captured (not even the USBInterfaceOpenSeize call or the USBDeviceOpenSeize calls would work).

To take control of the device I needed to grab it before the HIDManager. The solution to this was to write a codeless kext (kernel extension). A kext is essentially a bundle that sits in System/Library/Extensions that contains (usually) a plist (property list) and (occasionally) a kernel-level driver, among other items. In my case I wanted only the plist, which would give the instructions to the kernel on what devices it matches. If the data gives a higher probe score than the HIDManager then I could essentially capture the device and use a user-space driver to communicate with it.

The kext plist written, with some project-specific details modified, is as follows:

<?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>OSBundleLibraries</key>
    <dict>
        <key>com.apple.iokit.IOUSBFamily</key>
        <string>1.8</string>
        <key>com.apple.kernel.libkern</key>
        <string>6.0</string>
    </dict>
    <key>CFBundleDevelopmentRegion</key>
    <string>English</string>
    <key>CFBundleGetInfoString</key>
    <string>Demi USB Device</string>
    <key>CFBundleIdentifier</key>
    <string>com.demiart.mydevice</string>
    <key>CFBundleInfoDictionaryVersion</key>
    <string>6.0</string>
    <key>CFBundleName</key>
    <string>Demi USB Device</string>
    <key>CFBundlePackageType</key>
    <string>KEXT</string>
    <key>CFBundleSignature</key>
    <string>????</string>
    <key>CFBundleVersion</key>
    <string>1.0.0</string>
    <key>IOKitPersonalities</key>
    <dict>
        <key>Device Driver</key>
        <dict>
            <key>CFBundleIdentifier</key>
            <string>com.apple.kernel.iokit</string>
            <key>IOClass</key>
            <string>IOService</string>
            <key>IOProviderClass</key>
            <string>IOUSBInterface</string>
            <key>idProduct</key>
            <integer>12345</integer>
            <key>idVendor</key>
            <integer>67890</integer>
            <key>bConfigurationValue</key>
            <integer>1</integer>
            <key>bInterfaceNumber</key>
            <integer>0</integer>
        </dict>
    </dict>
    <key>OSBundleRequired</key>
    <string>Local-Root</string>
</dict>
</plist>

The idVendor and idProduct values give the kext specificity and increase its probe score sufficiently.

In order to use the kext, the following things need to be done (which my installer will do for clients):

  1. Change the owner to root:wheel (sudo chown root:wheel DemiUSBDevice.kext)
  2. Copy the kext to Extensions (sudo cp DemiUSBDevice.kext /System/Library/Extensions)
  3. Call the kextload utility to load the kext for immediate use without restart (sudo kextload -vt /System/Library/Extensions/DemiUSBDevice.kext)
  4. Touch the Extensions folder so that the next restart will force a cache rebuild (sudo touch /System/Library/Extensions)

At this point the system should use the kext to keep the HIDManager from capturing my device. Now, what to do with it? How to write to and read from it?

Following are some simplified snippets of my code, minus any error handling, that illustrate the solution. Before being able to do anything with the device, the application needs to know when the device attaches (and detaches). Note that this is merely for purposes of illustration — some of the variables are class-level, some are global, etc. Here is the initialization code that sets the attach/detach events up:

#include <IOKit/IOKitLib.h>
#include <IOKit/IOCFPlugIn.h>
#include <IOKit/usb/IOUSBLib.h>
#include <mach/mach.h>

#define DEMI_VENDOR_ID 12345
#define DEMI_PRODUCT_ID 67890

void DemiUSBDriver::initialize(void)
{
    IOReturn                result;
    Int32                   vendor_id = DEMI_VENDOR_ID;
    Int32                   product_id = DEMI_PRODUCT_ID;
    mach_port_t             master_port;
    CFMutableDictionaryRef  matching_dict;
    IONotificationPortRef   notify_port;
    CFRunLoopSourceRef      run_loop_source;

    //create a master port
    result = IOMasterPort(bootstrap_port, &master_port);

    //set up a matching dictionary for the device
    matching_dict = IOServiceMatching(kIOUSBDeviceClassName);

    //add matching parameters
    CFDictionarySetValue(matching_dict, CFSTR(kUSBVendorID),
        CFNumberCreate(kCFAllocatorDefault, kCFNumberInt32Type, &vendor_id));
    CFDictionarySetValue(matching_dict, CFSTR(kUSBProductID),
        CFNumberCreate(kCFAllocatorDefault, kCFNumberInt32Type, &product_id));

    //create the notification port and event source
    notify_port = IONotificationPortCreate(master_port);
    run_loop_source = IONotificationPortGetRunLoopSource(notify_port);
    CFRunLoopAddSource(CFRunLoopGetCurrent(), run_loop_source, 
      kCFRunLoopDefaultMode);

    //add an additional reference for a secondary event 
    //  - each consumes a reference...
    matching_dict = (CFMutableDictionaryRef)CFRetain(matching_dict);

    //add a notification callback for detach event
    //NOTE: removed_iter is a io_iterator_t, declared elsewhere
    result = IOServiceAddMatchingNotification(notify_port, 
      kIOTerminatedNotification, matching_dict, device_detach_callback, 
      NULL, &removed_iter);

    //call the callback to 'arm' the notification
    device_detach_callback(NULL, removed_iter);

    //add a notification callback for attach event
    //NOTE: added_iter is a io_iterator_t, declared elsewhere
    result = IOServiceAddMatchingNotification(notify_port, 
      kIOFirstMatchNotification, matching_dict, device_attach_callback, 
      NULL, &g_added_iter);
    if (result)
    {
      throw Exception("Unable to add attach notification callback.");
    }

    //call the callback to 'arm' the notification
    device_attach_callback(NULL, added_iter);

    //'pump' the run loop to handle any previously added devices
    service();
}

There are two methods that are used as callbacks in this initialization code: device_detach_callback and device_attach_callback (both declared at static methods). device_detach_callback is straightforward:

//implementation
void DemiUSBDevice::device_detach_callback(void* context, io_iterator_t iterator)
{
    IOReturn       result;
    io_service_t   obj;

    while ((obj = IOIteratorNext(iterator)))
    {
        //close all open resources associated with this service/device...

        //release the service
        result = IOObjectRelease(obj);
    }
}

device_attach_callback is where most of the magic happens. In my code I have this broken into multiple methods, but here I'll present it as a big monolithic method...:

void DemiUSBDevice::device_attach_callback(void * context, 
    io_iterator_t iterator)
{
    IOReturn                   result;
    io_service_t           usb_service;
    IOCFPlugInInterface**      plugin;   
    HRESULT                    hres;
    SInt32                     score;
    UInt16                     vendor; 
    UInt16                     product;
    IOUSBFindInterfaceRequest  request;
    io_iterator_t              intf_iterator;
    io_service_t               usb_interface;

    UInt8                      interface_endpoint_count = 0;
    UInt8                      pipe_ref = 0xff;

    UInt8                      direction;
    UInt8                      number;
    UInt8                      transfer_type;
    UInt16                     max_packet_size;
    UInt8                      interval;

    CFRunLoopSourceRef         m_event_source;
    CFRunLoopSourceRef         compl_event_source;

    IOUSBDeviceInterface245** dev = NULL;
    IOUSBInterfaceInterface245** intf = NULL;

    while ((usb_service = IOIteratorNext(iterator)))
    {
      //create the intermediate plugin
      result = IOCreatePlugInInterfaceForService(usb_service, 
        kIOUSBDeviceUserClientTypeID, kIOCFPlugInInterfaceID, &plugin, 
        &score);

      //get the device interface
      hres = (*plugin)->QueryInterface(plugin, 
        CFUUIDGetUUIDBytes(kIOUSBDeviceInterfaceID245), (void**)&dev);

      //release the plugin - no further need for it
      IODestroyPlugInInterface(plugin);

      //double check ids for correctness
      result = (*dev)->GetDeviceVendor(dev, &vendor);
      result = (*dev)->GetDeviceProduct(dev, &product);
      if ((vendor != DEMI_VENDOR_ID) || (product != DEMI_PRODUCT_ID))
      {
        continue;
      }

      //set up interface find request
      request.bInterfaceClass     = kIOUSBFindInterfaceDontCare;
      request.bInterfaceSubClass  = kIOUSBFindInterfaceDontCare;
      request.bInterfaceProtocol  = kIOUSBFindInterfaceDontCare;
      request.bAlternateSetting   = kIOUSBFindInterfaceDontCare;

      result = (*dev)->CreateInterfaceIterator(dev, &request, &intf_iterator);

      while ((usb_interface = IOIteratorNext(intf_iterator)))
      {
        //create intermediate plugin
        result = IOCreatePlugInInterfaceForService(usb_interface, 
          kIOUSBInterfaceUserClientTypeID, kIOCFPlugInInterfaceID, &plugin, 
          &score);

        //release the usb interface - not needed
        result = IOObjectRelease(usb_interface);

        //get the general interface interface
        hres = (*plugin)->QueryInterface(plugin, CFUUIDGetUUIDBytes(
          kIOUSBInterfaceInterfaceID245), (void**)&intf);

        //release the plugin interface
        IODestroyPlugInInterface(plugin);

        //attempt to open the interface
        result = (*intf)->USBInterfaceOpen(intf);

        //check that the interrupt endpoints are available on this interface
        //calling 0xff invalid...
        m_input_pipe = 0xff;  //UInt8, pipe from device to Mac
        m_output_pipe = 0xff; //UInt8, pipe from Mac to device

        result = (*intf)->GetNumEndpoints(intf, &interface_endpoint_count);
        if (!result)
        {
          //check endpoints for direction, type, etc.
          //note that pipe_ref == 0 is the control endpoint (we don't want it)
          for (pipe_ref = 1; pipe_ref <= interface_endpoint_count; pipe_ref++)
          {
            result = (*intf)->GetPipeProperties(intf, pipe_ref, &direction,
              &number, &transfer_type, &max_packet_size, &interval);
            if (result)
            {
              break;
            }

            if (transfer_type == kUSBInterrupt)
            {
              if (direction == kUSBIn)
              {
                m_input_pipe = pipe_ref;
              }
              else if (direction == kUSBOut)
              {
                m_output_pipe = pipe_ref;
              }
            }
          }
        }

        //set up async completion notifications
        result = (*m_intf)->CreateInterfaceAsyncEventSource(m_intf, 
          &compl_event_source);
        CFRunLoopAddSource(CFRunLoopGetCurrent(), compl_event_source, 
          kCFRunLoopDefaultMode);

        break;
      }

      break;
    }
}

At this point we should have the numbers of the interrupt endpoints and an open IOUSBInterfaceInterface to the device. An asynchronous writing of data can be done by calling something like:

result = (intf)->WritePipeAsync(intf, m_output_pipe, 
          data, OUTPUT_DATA_BUF_SZ, device_write_completion, 
          NULL);

where data is a char buffer of data to write, the final parameter is an optional context object to pass into the callback, and device_write_completion is a static method with the following general form:

void DemiUSBDevice::device_write_completion(void* context, 
    IOReturn result, void* arg0)
{
  //...
}

reading from the interrupt endpoint is similar:

result = (intf)->ReadPipeAsync(intf, m_input_pipe, 
          data, INPUT_DATA_BUF_SZ, device_read_completion, 
          NULL);

where device_read_completion is of the following form:

void DemiUSBDevice::device_read_completion(void* context, 
    IOReturn result, void* arg0)
{
  //...
}

Note that to receive these callbacks the run loop must be running (see this link for more information about the CFRunLoop). One way to achieve this is to call CFRunLoopRun() after calling the async read or write methods at which point the main thread blocks while the run loop runs. After handling your callback you can call CFRunLoopStop(CFRunLoopGetCurrent()) to stop the run loop and hand execution back to the main thread.

Another alternative (which I do in my code) is to pass a context object (named 'request' in the following code sample) into the WritePipeAsync/ReadPipeAsync methods - this object contains a boolean completion flag (named 'is_done' in this example). After calling the read/write method, instead of calling CFRunLoopRun(), something like the following can be executed:

while (!(request->is_done))
{
  //run for 1/10 second to handle events
  Boolean returnAfterSourceHandled = false;
  CFTimeInterval seconds = 0.1;
  CFStringRef mode = kCFRunLoopDefaultMode;
  CFRunLoopRunInMode(mode, seconds, returnAfterSourceHandled);
}

This has the benefit that if you have other threads that use the run loop you won't prematurely exit should another thread stop the run loop...

I hope that this is helpful to people. I had to pull from many incomplete sources to solve this problem and this required considerable work to get running well...

这篇关于在Mac上读写USB(HID)中断端点的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆