草庐IT

c++ - 在Mac上读写USB(HID)中断端点

coder 2023-05-31 原文

我正在尝试与相当特定的USB设备通信,并同时开发Windows和Mac代码。

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

Windows的代码非常简单:我得到了设备的句柄,然后调用ReadFile或WriteFile。显然,许多底层的异步行为已被抽象出来。它似乎工作正常。

但是,在Mac上,它有点粘性。我尝试了很多事情,没有一个完全成功,但是下面两件事似乎最有希望...

1.)尝试通过IOUSBInterfaceInterface访问设备(作为USB),遍历端点以确定输入和输出端点,并(希望)使用ReadPipe和WritePipe进行通信。不幸的是,一旦获得接口(interface),我将无法打开该接口(interface),返回值(kIOReturnExclusiveAccess)指出某些东西已经专门打开了该设备。我尝试使用IOUSBinterfaceInterface183,以便可以调用USBInterfaceOpenSeize,但这会导致相同的返回错误值。

-更新7/30/2010-
显然,Apple IOUSBHIDDriver与设备较早匹配,这很可能导致无法打开IOUSBInterfaceInterface。从某种程度上讲,防止IOUSBHIDDriver匹配的常见方法似乎是编写具有较高探针得分的无代码kext(内核扩展)。这将尽早实现,以防止IOUSBHIDDriver打开设备,并且从理论上讲,应该允许我打开接口(interface)并直接对端点进行读写。可以,但是我宁愿不必在用户计算机上安装其他东西。如果有人知道一个可靠的选择,我将感谢您提供的信息。

2.)作为IOHIDDeviceInterface122(或更高版本)打开设备。要进行读取,我设置了一个异步端口,事件源和回调方法,以便在数据准备就绪时(从输入中断端点上的设备发送数据时)调用该方法。但是,写数据(设备需要的数据)来初始化响应,我找不到办法。我很沮丧setReport通常会写到控制端点,再加上我需要的写法是不期望任何直接响应,不会阻塞。

我在网上四处张望,尝试了很多事情,但没有一个给我成功。有什么建议吗?我不能使用太多的Apple HIDManager代码,因为其中很多是10.5+,我的应用程序也必须在10.4上运行。

最佳答案

我现在有一个USB设备的工作Mac驱动程序,该设备需要通过中断端点进行通信。这是我的做法:
最终,最适合我的方法是选项1(上面已指出)。如前所述,我在打开COM样式的IOUSBInterfaceInterface到设备时遇到问题。随着时间的流逝,很明显这是由于HIDManager捕获了设备。捕获设备后,我无法从HIDManager夺取该设备的控制权(甚至USBInterfaceOpenSeize调用或USBDeviceOpenSeize调用都不起作用)。
要控制该设备,我需要在HIDManager之前先进行抓取。解决方案是编写一个无代码的kext(内核扩展)。 kext本质上是一个位于系统/库/扩展中的 bundle 软件,其中包含(通常)plist(属性列表)和(偶尔)内核级驱动程序,等等。在我的情况下,我只需要plist,它将向内核提供有关其匹配的设备的指令。如果数据提供的探针得分比HIDManager高,那么我基本上可以捕获该设备并使用用户空间驱动程序与其通信。
编写的kext plist修改了一些项目特定的详细信息,如下所示:

<?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>
idVendor和idProduct值赋予了kext特异性,并充分提高了其探针得分。
为了使用kext,需要完成以下操作(我的安装程序将对客户端执行此操作):
  • 将所有者更改为root:wheel(sudo chown root:wheel DemiUSBDevice.kext)
  • 将kext复制到扩展名(sudo cp DemiUSBDevice.kext /System/Library/Extensions)
  • 调用kextload实用程序以加载kext以立即使用,而无需重新启动(sudo kextload -vt /System/Library/Extensions/DemiUSBDevice.kext)
  • 触摸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;
        
        //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();
    }
    
    在此初始化代码中,有两种方法用作回调:device_detach_callback和device_attach_callback(均在静态方法中声明)。 device_detach_callback很简单:
    //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是大多数魔术发生的地方。在我的代码中,我将其分解为多种方法,但是在这里,我将其呈现为一个大型的整体方法...:
    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;
        }
    }
    
    此时,我们应该具有中断端点的编号和设备的开放IOUSBInterfaceInterface。可以通过调用以下内容来完成数据的异步写入:
    result = (intf)->WritePipeAsync(intf, m_output_pipe, 
              data, OUTPUT_DATA_BUF_SZ, device_write_completion, 
              NULL);
    
    其中data是要写入的数据的char缓冲区,final参数是要传递到回调中的可选上下文对象,device_write_completion是具有以下常规形式的静态方法:
    void DemiUSBDevice::device_write_completion(void* context, 
        IOReturn result, void* arg0)
    {
      //...
    }
    
    从中断端点读取的内容类似:
    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)
    {
      //...
    }
    
    请注意,要接收这些回调,必须运行run循环(see this link for more information about the CFRunLoop)。一种实现方法是在调用异步读取或写入方法后调用CFRunLoopRun(),此时运行循环运行时主线程会阻塞。处理完回调之后,您可以调用CFRunLoopStop(CFRunLoopGetCurrent())停止运行循环,并将执行移回主线程。
    另一种选择(我在我的代码中执行)是将上下文对象(在下面的代码示例中称为“request”)传递到WritePipeAsync/ReadPipeAsync方法中-该对象包含一个 bool 值完成标志(在本示例中为“is_done”) 。调用read/write方法后,无需执行CFRunLoopRun(),而是可以执行以下操作:
    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);
    }
    
    这样做的好处是,如果您有其他使用运行循环的线程,则在另一个线程停止运行循环的情况下,您不会过早退出...
    我希望这对人们有帮助。为了解决这个问题,我不得不从许多不完整的资源中寻求帮助,这需要大量的工作才能正常运行...

    关于c++ - 在Mac上读写USB(HID)中断端点,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/3368008/

    有关c++ - 在Mac上读写USB(HID)中断端点的更多相关文章

    1. ruby-on-rails - 如何优雅地重启 thin + nginx? - 2

      我的瘦服务器配置了nginx,我的ROR应用程序正在它们上运行。在我发布代码更新时运行thinrestart会给我的应用程序带来一些停机时间。我试图弄清楚如何优雅地重启正在运行的Thin实例,但找不到好的解决方案。有没有人能做到这一点? 最佳答案 #Restartjustthethinserverdescribedbythatconfigsudothin-C/etc/thin/mysite.ymlrestartNginx将继续运行并代理请求。如果您将Nginx设置为使用多个上游服务器,例如server{listen80;server

    2. ruby-on-rails - 如何在 mac os snow leopard 中升级 ruby​​ 和 rails - 2

      我想将我的MacSnowLeopardruby​​从1.8.7升级到1.9.1版本,有人知道轻松且最好的升级方法吗?因为我读了一些论坛/帖子/博客/讨论说覆盖苹果发布的ruby不好将Rails从版本2.2.2升级到2.3.8的最佳方法是什么?因为我找到的所有信息都仅适用于豹/老虎,而且很少有关于雪豹的复杂文章。他们还说覆盖apple提供的rails不好吗。谁能帮帮我?谢谢。 最佳答案 DanBenjamin有一些greatinstructionsforcompilingandinstallingRuby,RubyGemsandRai

    3. ruby - 在不使用 RVM 的情况下在 Mac 上卸载和升级 Ruby - 2

      我最近决定从我的系统中卸载RVM。在thispage提出的一些论点说服我:实际上,我的决定是,我根本不想担心Ruby的多个版本。我只想使用1.9.2-p290版本而不用担心其他任何事情。但是,当我在我的Mac上运行ruby--version时,它告诉我我的版本是1.8.7。我四处寻找如何简单地从我的Mac上卸载这个Ruby,但奇怪的是我没有找到任何东西。似乎唯一想卸载Ruby的人运行linux,而使用Mac的每个人都推荐RVM。如何从我的Mac上卸载Ruby1.8.7?我想升级到1.9.2-p290版本,并且我希望我的系统上只有一个版本。 最佳答案

    4. ruby - 使用 `+=` 和 `send` 方法 - 2

      如何将send与+=一起使用?a=20;a.send"+=",10undefinedmethod`+='for20:Fixnuma=20;a+=10=>30 最佳答案 恐怕你不能。+=不是方法,而是语法糖。参见http://www.ruby-doc.org/docs/ProgrammingRuby/html/tut_expressions.html它说Incommonwithmanyotherlanguages,Rubyhasasyntacticshortcut:a=a+2maybewrittenasa+=2.你能做的最好的事情是:

    5. ruby - ruby 乘法语句中星号中断语法前的空格 - 2

      在添加一些空格以使代码更具可读性时(与上面的代码对齐),我遇到了这个:classCdefx42endendm=C.new现在这将给出“错误数量的参数”:m.x*m.x这将给出“语法错误,意外的tSTAR,期待$end”:2/m.x*m.x这里的解析器到底发生了什么?我使用Ruby1.9.2和2.1.5进行了测试。 最佳答案 *用于运算符(42*42)和参数解包(myfun*[42,42])。当你这样做时:m.x*m.x2/m.x*m.xRuby将此解释为参数解包,而不是*运算符(即乘法)。如果您不熟悉它,参数解包(有时也称为“spl

    6. ruby - 如何计算 Liquid 中的变量 +1 - 2

      我对如何计算通过{%assignvar=0%}赋值的变量加一完全感到困惑。这应该是最简单的任务。到目前为止,这是我尝试过的:{%assignamount=0%}{%forvariantinproduct.variants%}{%assignamount=amount+1%}{%endfor%}Amount:{{amount}}结果总是0。也许我忽略了一些明显的东西。也许有更好的方法。我想要存档的只是获取运行的迭代次数。 最佳答案 因为{{incrementamount}}将输出您的变量值并且不会影响{%assign%}定义的变量,我

    7. ruby - 在 ASP 页面上 Mechanize 中断 - 2

      require'mechanize'agent=Mechanize.newlogin=agent.get('http://www.schoolnet.ch/DE/HomeDE.htm')agent.clicklogin.link_withtext:/Login/然后我得到Mechanize::UnsupportedSchemeError。 最佳答案 Mechanize不支持javascript但您可以将搜索字段添加到表单并为其分配搜索词并使用mechanize提交表单form=page.forms.firstform.add_fie

    8. ruby - 可以正常中断的来自 Rake 的长时间运行的 shell 命令? - 2

      在几个项目中,我希望有一个类似rakeserver的rake任务,它将通过任何需要的方式开始为该应用程序提供服务。这是一个示例:task:serverdo%x{bundleexecrackup-p1234}end这行得通,但是当我准备停止它时,按Ctrl+c并没有正常关闭;它中断了Rake任务本身,它说rakeaborted!并给出堆栈跟踪。在某些情况下,我必须执行Ctrl+c两次。我可能可以用Signal.trap写一些东西来更优雅地中断它。有没有更简单的方法? 最佳答案 trap('SIGINT'){puts"Yourmessa

    9. arrays - Ruby 数组 += vs 推送 - 2

      我有一个数组数组,想将元素附加到子数组。+=做我想做的,但我想了解为什么push不做。我期望的行为(并与+=一起工作):b=Array.new(3,[])b[0]+=["apple"]b[1]+=["orange"]b[2]+=["frog"]b=>[["苹果"],["橙子"],["Frog"]]通过推送,我将推送的元素附加到每个子数组(为什么?):a=Array.new(3,[])a[0].push("apple")a[1].push("orange")a[2].push("frog")a=>[[“苹果”、“橙子”、“Frog”]、[“苹果”、“橙子”、“Frog”]、[“苹果”、“

    10. ruby-on-rails - prawnto 显示新页面时不会中断的表格 - 2

      我有可变数量的表格和可变数量的行,我想让它们一个接一个地显示,但如果表格不适合当前页面,请将其放在下一页,然后继续。我已将表格放入事务中,以便我可以回滚然后打印它(如果高度适合当前页面),但我如何获得表格高度?我现在有这段代码pdf.transactiondopdf.table@data,:font_size=>12,:border_style=>:grid,:horizontal_padding=>10,:vertical_padding=>3,:border_width=>2,:position=>:left,:row_colors=>["FFFFFF","DDDDDD"]pdf.

    随机推荐