Access USB device on Mac OS X using I/O Kit


This post shows how you can access a USB device from user space on Mac OS X. I used Xcode 4.3.2 on Mac OS X Lion (10.7.3) for testing the code.

Apple provides the USB Device Interface Guide that shows how USB devices can be accessed from user space. The book OS X and iOS Kernel Programming is also very helpful, see Chapter 15 – User-Space USB Drivers.

Create an Xcode console application. Add the following libraries to the default target.

  • IOKit.framework
  • CoreFoundation.framework

Xcode will have created main.c, add the following headers.

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

Let’s now implement the main function. Add the following variable declarations.

    CFMutableDictionaryRef matchingDictionary = NULL;
    SInt32 idVendor = 0x0000; // set vendor id
    SInt32 idProduct = 0x0000; // set product id
    io_iterator_t iterator = 0;
    io_service_t usbRef;
    SInt32 score;
    IOCFPlugInInterface** plugin;
    IOUSBDeviceInterface300** usbDevice = NULL;
    IOReturn ret;
    IOUSBConfigurationDescriptorPtr config;
    IOUSBFindInterfaceRequest interfaceRequest;
    IOUSBInterfaceInterface300** usbInterface;
    char out[] = { 0x00, 0x00 }; // set data to send
    char* in;
    UInt32 numBytes;

We now try to find the USB device using the vendor and product id.

    matchingDictionary = IOServiceMatching(kIOUSBDeviceClassName);
    CFDictionaryAddValue(matchingDictionary,
                         CFSTR(kUSBVendorID),
                         CFNumberCreate(kCFAllocatorDefault,
                                        kCFNumberSInt32Type, &idVendor));
    CFDictionaryAddValue(matchingDictionary,
                         CFSTR(kUSBProductID),
                         CFNumberCreate(kCFAllocatorDefault,
                                        kCFNumberSInt32Type, &idProduct));
    IOServiceGetMatchingServices(kIOMasterPortDefault,
                                 matchingDictionary, &iterator);
    usbRef = IOIteratorNext(iterator);
    if (usbRef == 0)
    {
        printf("Device not found\n");
        return -1;
    }
    IOObjectRelease(iterator);
    IOCreatePlugInInterfaceForService(usbRef, kIOUSBDeviceUserClientTypeID,
                                      kIOCFPlugInInterfaceID, &plugin, &score);
    IOObjectRelease(usbRef);
    (*plugin)->QueryInterface(plugin,
                              CFUUIDGetUUIDBytes(kIOUSBDeviceInterfaceID300),
                              (LPVOID)&usbDevice);
    (*plugin)->Release(plugin);

Now that we have found the device, we open the device and set the first configuration as active.

    ret = (*usbDevice)->USBDeviceOpen(usbDevice);
    if (ret == kIOReturnSuccess)
    {
        // set first configuration as active
        ret = (*usbDevice)->GetConfigurationDescriptorPtr(usbDevice, 0, &config);
        if (ret != kIOReturnSuccess)
        {
            printf("Could not set active configuration (error: %x)\n", ret);
            return -1;
        }
        (*usbDevice)->SetConfiguration(usbDevice, config->bConfigurationValue);
    }
    else if (ret == kIOReturnExclusiveAccess)
    {
        // this is not a problem as we can still do some things
    }
    else
    {
        printf("Could not open device (error: %x)\n", ret);
        return -1;
    }

Having done that, we need to find the interface we want to use for sending and receiving data. Our device is a USB RNDIS device that has two bulk data endpoints on the second interface (#1).

    interfaceRequest.bInterfaceClass = kIOUSBFindInterfaceDontCare;
    interfaceRequest.bInterfaceSubClass = kIOUSBFindInterfaceDontCare;
    interfaceRequest.bInterfaceProtocol = kIOUSBFindInterfaceDontCare;
    interfaceRequest.bAlternateSetting = kIOUSBFindInterfaceDontCare;
    (*usbDevice)->CreateInterfaceIterator(usbDevice,
                                          &interfaceRequest, &iterator);
    IOIteratorNext(iterator); // skip interface #0
    usbRef = IOIteratorNext(iterator);
    IOObjectRelease(iterator);
    IOCreatePlugInInterfaceForService(usbRef,
                                      kIOUSBInterfaceUserClientTypeID,
                                      kIOCFPlugInInterfaceID, &plugin, &score);
    IOObjectRelease(usbRef);
    (*plugin)->QueryInterface(plugin,
                              CFUUIDGetUUIDBytes(kIOUSBInterfaceInterfaceID300),
                              (LPVOID)&usbInterface);
    (*plugin)->Release(plugin);

Now that we have found the interface, let us open it.

    ret = (*usbInterface)->USBInterfaceOpen(usbInterface);
    if (ret != kIOReturnSuccess)
    {
        printf("Could not open interface (error: %x)\n", ret);
        return -1;
    }

Finally, we can send and receive some data. The pipe references are in the same order as the end points that appear in the Bus Probe tab of USB Prober. In our case, pipe 0 is the default control endpoint (does not have an endpoint descriptor and will not appear in USB Prober), pipe 1 is the bulk data output endpoint, and pipe 2 is the bulk data input endpoint.

For some reason, if we read less than 64 bytes I/O Kit returns a kIOReturnOverrun error. Coincidentally, the max packet size of the bulk input pipe of the device is also 64 bytes.

    // Send data through pipe 1
    (*usbInterface)->WritePipe(usbInterface, 1, out, sizeof(out));

    // Read data through pipe 2
    numBytes = 64;
    in = malloc(numBytes);
    ret = (*usbInterface)->ReadPipe(usbInterface, 2, in, &numBytes);
    if (ret == kIOReturnSuccess)
    {
        printf("Read %d bytes\n", numBytes);
    }
    else
    {
        printf("Read failed (error: %x)\n", ret);
    }

To wrap it all, we close the interface and device, and return from main.

    (*usbInterface)->USBInterfaceClose(usbInterface);
    (*usbDevice)->USBDeviceClose(usbDevice);

    return 0;

Found an issue? Leave a comment below.

55 thoughts on “Access USB device on Mac OS X using I/O Kit

  1. Hi, thanks for this but I’m getting an error in this part
    ret = (*usbInterface)->USBInterfaceOpen(usbInterface);
    The code compiles and build successfully but when I run I get this error in that line
    Thread 1: EXC_BAD_ACCESS(code=1, address=0x0)

    Can you help me with this?

    1. Hi, you might want to test whether usbInterface is NULL before using it. For instance:

      HRESULT result;
      result = (*plugin)->QueryInterface(plugin,
                                CFUUIDGetUUIDBytes(kIOUSBInterfaceInterfaceID300),
                                (LPVOID)&usbInterface);
      if (result || !usbInterface)
      {
        [print message and quit]
      }
      

      I don’t know why you cannot obtain an interface reference though.

  2. Hi, thanks for the code.
    It seems working on my environment(Mac OS 10.8.4, Xcode 4.6.3). Do you have any idea about its speed? Do you think it is appropriate to use this method for bulk data transfer, say, around 10Mbytes? Or should I create a in-kernel driver for such case?

    1. Hi, glad it works for you. I have tested it with a USB 1.1 peripheral, at data rates of under 1 Mbps. I suggest measuring the data rate under different conditions and see how it goes. I would expect it to be possible to get bulk data transfer rates of 10 MB/s with a USB 2.0 peripheral.

      1. Thanks for the reply. I will try to measure the speed when I get a USB device for the test. Your code becomes a good start for my purpose. Thanks a lot!

  3. Hi I am getting just the same error as Diffy Romano. I am new to (Objective)C and don’t get why this error occurs. I am using XCode 5.0.1 and OS 10.9. I understand that it is a memory / Pointer issue, but no idea how to solve that …

    Would be great to get some information about that.

    1. Have you tried different USB device? Any USB devices near you, if the devices fail at the same place or not? I didn’t have the same problem as yours, and I used a kind of rare USB device which I thought that Mac OS had not supported it.

  4. I get the same error like Diffy Romano. Has anyone found a solution? I tried different devices like iphone, usb devices, always same error.

    1. You might want to check the Configuration Descriptor you have set, how many interfaces it has, which interface you want to use, and use the appropriate number of IOIteratorNext calls to skip to the correct interface. USB Prober will show you all that information for a device, under the Bus Probe tab. The code in the post is meant for using bulk input and output endpoints of a particular USB RNDIS device.

  5. 2014-01-08 14:00:48.312 usb_write[1037:303] *** -[CFDictionary respondsToSelector:]: message sent to deallocated instance 0x100656fc0

    What’s the meaning ? Thanks to replay.

    1. I think you have the same problem as mine.
      which is “exclusive access to the device” – you should print out the IOreturn in English using a function
      you can found their meaning in

  6. does anybody know how to access USB RAW interface descriptor, such as HID RAW Descriptor ?
    the HID API on mac doesn’t not provide this :/
    and I need it… I have a really specific use and HID API doesn’t work for me so I have to go throught IOUSB but still the same … I can’t access RAW descriptor

  7. High Speed device @ 5 (0xFA120000): ……………………………………… Vendor-specific device: “USB-SPDIF”
    Port Information: 0x0018
    Not Captive
    External Device
    Connected
    Enabled
    Number Of Endpoints (includes EP0):
    Total Endpoints for Configuration 1 (unconfigured): 2
    Device Descriptor
    Descriptor Version Number: 0x0200
    Device Class: 255 (Vendor-specific)
    Device Subclass: 255 (Vendor-specific)
    Device Protocol: 255
    Device MaxPacketSize: 64
    Device VendorID/ProductID: 0x04B4/0x930B (Cypress Semiconductor)
    Device Version Number: 0x0001
    Number of Configurations: 1
    Manufacturer String: 1 “M2TECH ”
    Product String: 2 “USB-SPDIF”
    Serial Number String: 0 (none)
    Current configuration: 0 (unconfigured)
    Configuration Descriptor (current config)
    Length (and contents): 25
    Raw Descriptor (hex) 0000: 09 02 19 00 01 00 00 60 32 09 04 00 00 01 FF FF
    Raw Descriptor (hex) 0010: FF 00 07 05 02 02 00 02 00
    Number of Interfaces: 1
    Configuration Value: 0
    Attributes: 0x60 (self-powered, remote wakeup)
    MaxPower: 100 ma
    Interface #0 – Vendor-specific
    Alternate Setting 0
    Number of Endpoints 1
    Interface Class: 255 (Vendor-specific)
    Interface Subclass; 255 (Vendor-specific)
    Interface Protocol: 255
    Endpoint 0x02 – Bulk Output
    Address: 0x02 (OUT)
    Attributes: 0x02 (Bulk no synchronization data endpoint)
    Max Packet Size: 512
    Polling Interval: 0 ( Endpoint never NAKs)
    Device Qualifier Descriptor
    Descriptor Version Number: 0x0200
    Device Class 255 (Vendor-specific)
    Device Subclass 255 (Vendor-specific)
    Device Protocol 255
    Device MaxPacketSize: 64
    Number of Configurations: 1
    bReserved: 0
    Other Speed Configuration Descriptor

    This is my usb prober copy of specific device. How you set USB RNDIS device and how i can modify your code to view like USB Vendor specific ? Thanks to replay…… :)

    1. It looks like your device has only one configuration, and that has only one interface (#0). You should be able to communicate with the device after commenting out line 74:
      IOIteratorNext(iterator); // skip interface #0

      The portion of the code that reads data will probably fail, because the interface has only a bulk output endpoint.

      Please remember to set idVendor to 0x04B4 and idProduct to 0x930B.

      1. Thanks to reply. Can you tell me what libraries you have loaded into your project? I’m beginning to suspect that I’m using includes unsuitable for your project. I’m using: kernel.framework, security.framew, corefoundation.framew and IOKit.framew. Should I use other links? thanks.

      2. Just realized this, if you are running from user space, you probably shouldn’t link with kernel.framework, only IOKit.framework. If you are running from kernel space, it is the other way round.

      3. I performed the steps you suggested but still can not get the code to open the interface even though I know that only the interface 0 with bulk output.
        I tried your code with an iphone (changing vendor and product id) but the result is the same. The error control interface allows you to not crash but it does not solve the problem.

      4. Code works fine for me using iPhone’s vendor and product ID, and commenting out line 74. Based on USB Prober Bus Probe I’d say I am accessing the bulk output endpoint, of the first interface, of the first configuration descriptor.

  8. You’re right. I tried with the iphone, opened the interface 0, sent the data but reading it gave me error. I think it’s a matter of reading iphone.
    What I do not understand is because having the bulk output 0x2 on interface 0 i get error while opening the interface.

    1. Use the IORegistry tab (IOService Plane) of USB Prober to see if you’ve got a driver class servicing that interface. For instance, the code above prints the message: Could not open interface (code: e00002c5). You’ll not be able to use that interface until you disable/remove the driver.

      1. I do not want to bother you again, until now you have been so kind to give me assistance. The problem is always present. Using vendor/product id of iphone, interface opens but I do not go to write nor read. I believe this is due to Apple’s protections on their device or am I’m doing something wrong.

  9. I would love to know if this code works for you from user-space in 10.9.x or, more specifically, if it works with a 64-bit OS.

    I have been doing something similar in 10.9.1 (talking directly to an XBox 360 controller from an application) and my problem is that ReadPipeAsync, WritePipe and WritePipeAsync all fail (error codes are either “memory valid, but cannot be used in this way” or “unable to wire down memory”). I have double- and triple-checked that I am using the right pipe and have tried different approaches to the memory buffer used (stack-allocated – for WritePipe only obviously, statically allocated, malloc-ing a 4k block) to no avail.

    I am able to open the device, configure it and open the (single) interface and it has one input pipe and one output pipe as expected. The ReadPipe command works fine, so I was able to make input work by running that in a separate thread (since it blocks when the input is not changing).

    This does however, leave me with all the LEDs blinking on the controller (this starts after configuring it) as I am unable to write anything to the out pipe. I have not found any information on the web, except for one other person doing something similar (but with another device) in 2011 who had very similar problems when the OS was in 64-bit mode (which 10.9 always is) but which worked fine in 32-bit mode. Unfortunately he/she never got any replies to the posts…

    So at this point my only theory is that something is different in 64-bit and you seem to be my best bet at either proving or disproving that theory – oddly enough talking to USB devices directly from user-space does not appear to be a widespread hobby ;)

      1. That’s interesting – so it’s either me or the controller that is being dumb then (hopefully me, because then I might be able to do something about it). I’ll have to give it another try this weekend.

        Many thanks for the quick reply!

      2. Hi again,

        It was me (being dumb). After playing around with it some more this morning I discovered that I am able to write to the controller, but only immediately after configuring it – once I have read from it it I am no longer able to write to it. That’s still somewhat puzzling, but it solves my immediate issue so is good enough for me. I was also quite sure I had already tried reordering my operations, but clearly I had not.

        I also managed to dig up some archived email list where the other person I mentioned did resolve his/her issue – it turned out there was a codeless kext involved (to prevent the OS driver from grabbing the device in question) and that kext was not loaded in 64-bit. So not very similar to my issue at all – I guess I was just grasping at straws at that point.

        Thanks again for the information.

  10. Hi Devendra, I just checked with the USB Prober if other drivers were using my device, the result was negative. I work on iMac i5 and macbook pro late 2010 because I thought that there was something different in the handling of USB devices between two computers. Any advice?

  11. Hi Sir,
    i am new for mac development,i need your help,i follow all of your steps so i can interact with my usb device(Getting device name,vendors id etc are succeed) but i can’t read data from that device.The USB Device is not configured one,it have only one interface #0 and four end points, i tried all the endpoints to read data from that device but failed. when i try to read data using 2 end point it shows an error 02c2 (#define kIOReturnBadArgument iokit_common_err(0x2c2) // invalid argument ).i commenting out line 74 also.When i use 3 pipe the function never return to my program always shows the processing activity.

    my USB Device information is
    =======================

    High Speed device @ 3 (0x24100000): ……………………………………… Diagnostic Device/Unknown device from unknown vendor
    Port Information: 0x001a
    Not Captive
    Attached to Root Hub
    External Device
    Connected
    Enabled
    Number Of Endpoints (includes EP0):
    Total Endpoints for Configuration 1 (current): 5
    Device Descriptor
    Descriptor Version Number: 0x0100
    Device Class: 220 (Diagnostic Device)
    Device Subclass: 0 (Unknown)
    Device Protocol: 0 (Unknown)
    Device MaxPacketSize: 64
    Device VendorID/ProductID: 0x1BBA/0x0222 (unknown vendor)
    Device Version Number: 0x0100
    Number of Configurations: 1
    Manufacturer String: 0 (none)
    Product String: 0 (none)
    Serial Number String: 0 (none)
    Configuration Descriptor (current config)
    Length (and contents): 46
    Raw Descriptor (hex) 0000: 09 02 2E 00 01 01 00 E0 01 09 04 00 00 04 DC A0
    Raw Descriptor (hex) 0010: B0 00 07 05 81 03 40 00 0A 07 05 01 02 40 00 0A
    Raw Descriptor (hex) 0020: 07 05 82 02 00 02 0A 07 05 02 02 00 02 0A
    Number of Interfaces: 1
    Configuration Value: 1
    Attributes: 0xE0 (self-powered, remote wakeup)
    MaxPower: 2 ma
    Interface #0 – Diagnostic Device/Unknown
    Alternate Setting 0
    Number of Endpoints 4
    Interface Class: 220 (Diagnostic Device)
    Interface Subclass; 160 (Unknown)
    Interface Protocol: 176
    Endpoint 0x81 – Interrupt Input
    Address: 0x81 (IN)
    Attributes: 0x03 (Interrupt no synchronization data endpoint)
    Max Packet Size: 64
    Polling Interval: 10 ms
    Endpoint 0x01 – Bulk Output
    Address: 0x01 (OUT)
    Attributes: 0x02 (Bulk no synchronization data endpoint)
    Max Packet Size: 64
    Polling Interval: 10 ms
    Endpoint 0x82 – Bulk Input
    Address: 0x82 (IN)
    Attributes: 0x02 (Bulk no synchronization data endpoint)
    Max Packet Size: 512
    Polling Interval: 10 ms
    Endpoint 0x02 – Bulk Output
    Address: 0x02 (OUT)
    Attributes: 0x02 (Bulk no synchronization data endpoint)
    Max Packet Size: 512
    Polling Interval: 10 ms

    Please help me sir.

  12. Hi Devendra,
    Its worked after a small change in your code

    ret = (*usbInterface)->ReadPipe(usbDevice, 2, in, &numBytes);

    Look that First parameter…

    Now the readpipe work but i got empty data from that device.i am sure that device have data because through windows our team mates can read data…Any solution???

    1. What version of Mac OS X are you programming for?

      You may be using the wrong pipe. Read the pipe’s properties using GetPipeProperties (such as direction) before reading or writing to it.

          (*usbInterface)->GetPipeProperties(usbInterface, 2, &direction, &number, &transferType, &maxPacketSize, &interval);
          if (direction == kUSBOut)
          {
              // 2 is an output pipe
          }
      
      1. Hi,
        PipeRef 1: direction in, transfer type interrupt, maxPacketSize 64
        PipeRef 2: direction out, transfer type bulk, maxPacketSize 512
        PipeRef 3: direction in, transfer type bulk, maxPacketSize 512
        PipeRef 4: direction out, transfer type bulk, maxPacketSize 512

        This is my Device log through xcode…so i used 2 nd pipe.any wrong?

      1. Can’t see anything obviously wrong with your code.

        You’d probably want to establish a read loop to read data continuously. Alternative read methods such as ReadPipeAsync or ReadPipeTO (with a suitable timeout) make it easier to do so.

    1. It shouldn’t matter. Do you happen to have a USB protocol analyzer such as the TotalPhase Beagle? That should help you discover the problem.

  13. Hi all,

    I’m new to Mac Apps…is that possible to list or access files which is stored on the usb drive using IOKit? Please some one help me!

    Thanks

  14. Hi all,

    The OS X IOKit should serve as the solution to controlling USB devices including iPhone/Android. Is it possible to disable/enable a particular USB port that the phone is plugged in? By “Disable”, I mean to stop powering that port and accordingly disconnect the device. “Enable” means to power on that port to allow the device to reconnect automatically.

    Thanks.

  15. I am using Xcode 8.0 on Mac OS 10.11.6. I copied your codes and edited vendor ID and product ID, obtained from USB Bus Probe. The compiler doesn’t recognize (LPVOID) on line 45. It passed with (void ). However, it could find “usb interface”.
    (*plugin)->QueryInterface(plugin,
    CFUUIDGetUUIDBytes(kIOUSBDeviceInterfaceID300),
    (void
    )&usbDevice);
    (*plugin)->Release(plugin);

    HRESULT result;
    result = (*plugin)->QueryInterface(plugin,
                                       CFUUIDGetUUIDBytes(kIOUSBInterfaceInterfaceID300),
                                       (void**)&usbInterface);
    if (result || !usbInterface)
    {
        printf("usbInterface not found\n");
        return -1;
    }
    
  16. I am using Xcode 8.0 on Mac OS 10.11.6. I copied your codes and edited vendor ID and product ID, obtained from USB Bus Probe. The compiler doesn’t recognize (LPVOID) on line 45. It passed with (void **). However, it couldn’t find “usb interface”.

    (plugin)->QueryInterface(plugin,
    CFUUIDGetUUIDBytes(kIOUSBDeviceInterfaceID300),
    (void)&usbDevice);
    (
    plugin)->Release(plugin);

    HRESULT result;
    result = (*plugin)->QueryInterface(plugin,
    CFUUIDGetUUIDBytes(kIOUSBInterfaceInterfaceID300),
    (void**)&usbInterface);
    if (result || !usbInterface)
    {
    printf(“usbInterface not found\n”);
    return -1;
    }

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s