Making Unsupported USB Devices a Reality on the Macintosh

Got a USB device that you'd like to use on your Macintosh? Vendor won't provide a driver for it? That's a shame but there is an alternative. If you have a basic understanding of the C language, it's possible to write an OS X driver or application to access it.

Document Status Last Update
Ancient History 9/02/06

Background

To write my OS X based USB application, I had to dig thru Apple's esoteric documentation, and scour the web and newsgroups for hours to get enough knowledge to make the project successful. I thought it would be good to write some notes.

This document is written for developers and tinkerers that have some knowledge of the C programming language but do not necessarily have any USB knowledge or any Macintosh API knowledge. I will attempt to cover all aspects of developing a USB based solution from start to finish. The following will be covered:

  • Types of USB devices
  • Figuring out what kind of device you have
  • USB and Apple's HID Manager
  • Uncovering the USB interface with Prober
  • USB basics
  • HID manager function calls
  • Successful steps
  • Connecting to the device
  • Reading the device
  • Writing to the device
  • Sample code that has been compiled and works on OS X.3
  • Types of Devices

    In this document, we will split USB devices into two classes: HID and legacy. HID stands for Human Interface Device and on OS X, these devices are managed by the HID manager. The HID manager will swipe any HID compliant device as soon as it is plugged in. That doesn't mean you can use the device though, the HID manager probably won't know what to do with it. That's where this page comes in. Here I will explain how to programatically access the HID manager, as well as providing some tips on troubleshooting.

    Which type do I have?

    To find out if you have an HID device, simply plug it in. Open the System Profiler (available thru the About This Mac option on the menu bar). The device should show up under the USB list like the pic below. If it doesn't show up, you probably have a legacy device. You'll need to write a device driver, called a kernel extension or KEXT, to access the device. But that's a topic for another day. Today's focus is HID compliant devices.

    Apple's API

    Apple has provided an API, an Application Program Interface, thru which you can access the HID manager to access HID devices. Arguably there are some advantages of using an API:

  • There's no kernel extension to load
  • It's less likely to break when you upgrade the OS
  • You won't get a cute little sad Mac face when you boot your machine
  • A lot of the grunt work is done for you
  • As USB evolves, so does the HID manager (hopefully)
  • The methods are documented (at least there is some documentation)
  • Pipes and Interfaces

    Before we discuss the details, we need to know a little bit about how USB works. Every USB device has a control pipe, sometimes called a message pipe or a default pipe. There's also an interface, as well as an element, also known as an endpoint.

    The control or default pipe is used to talk to the device and pass control and status info between the Macintosh and the device. When the device powers up, the control pipe is selected per the USB specification. The default address is x0000.

    An element, or endpoint, is the source of data on a device that is read/written to. Each element has an address and stores a piece of data. Every USB device has a different number of elements so you'll need to figure out how many yours has what type of data they accept. More on that later.

    The interface pipe is used to access the data elements on the device. Summing it up:

  • To connect to the device, you'll use the control pipe
  • To get data from the device, or send data to the device, you'll use the interface pipe

  • I see you

    To read/write from your device, you'll need to know what it looks like e.g. what interfaces it exposes, how many elements there are, etc. To do that, there is a nifty tool that comes with every Macintosh called USB Prober. You'll find it under Macintosh HD->Developer->Applications->Tools->USB. Simply plug in your device and fire it up. You should be able to locate your device in the list and get an idea of the physical attributes. Here are some pics of the probe in action on a PicKit2:


    This pic is hard to see so I dumped the output to a text file. Let's look over it.

    
    Composite device: "PICkit 2 Microcontroller Programmer"
        Device Descriptor   
            Descriptor Version Number:   0x0200
            Device VendorID/ProductID:   0x04D8/0x0033   (Microchip Technology Inc.)
            Manufacturer String:   1 "Microchip Technology Inc."
            Product String:   2 "PICkit 2 Microcontroller Programmer"
    
    

    In this part you can see some important info. For one thing, we found our device - a PicKit 2 in this example. Secondly, we can identify the description, vendorID, productID, and product string. You will use these in your program to find the device.

    
    "PICkit 2 Microcontroller Programmer"
            Total Length of Descriptor:   41
            Number of Interfaces:   1
            Configuration Value:   1
            Attributes:   0x80 (bus-powered)
            MaxPower:   100 ma
            Interface #0 - HID   
                Alternate Setting   0
                Number of Endpoints   2
    
    

    Here we discover that there is one interface, it is indeed an HID device, it can provide or sink at least 100 milliamps of current, and it has two endpoints. BTW, an LED or Light Emitting Diode consumes about 20mA of current so 100mA is not much. If your port is limited to 100mA it will shut down if you try to sink more than that. That means you don't want to use it to jump start your car. However, if your port can provide 500mA then your USB device will probably sink more than 100mA.
    
    Endpoint 0x81 - Interrupt Input   
                    Attributes:   0x81  (IN)
                    Attributes:   0x03  (Interrupt)
                    Max Packet Size:   64
                    Polling Interval:   1 ms
                Endpoint 0x01 - Interrupt Output   
                    Attributes:   0x01  (OUT)
                    Attributes:   0x03  (Interrupt)
                    Max Packet Size:   64
                    Polling Interval:   1 ms
    
    

    Continuing we discover that the two endpoints have a 64 byte packet size. That means the USB controller in the host will split the transaction into 64-byte chunks on the bus. There is one input endpoint and one output endpoint. That means you can read from and write to the device. The device can be polled every 1 millisecond.

    The API methods

    Now that you have an idea of the makeup of your device, it's time to discuss how to actually connect to it. There are several methods to do this, but the way I would like to explore is the one that I have working which consists of these steps:

  • Find the device
  • Create asynchronous interface to the device
  • Create an asynchronous device callback
  • Create a timer
  • Start the timer
  • Write to the device
  • Read from the device
  • Stop the timer
  • Close the device
  • Free the device
  • Here are the methods that will be used:

    Task Method(s)
    Find the device HIDBuildDeviceList
    HIDCountDevices
    HIDGetFirstDevice
    HIDGetNextDevice
    Create asynchronous interface createAsyncPort
    Setup asynchronous callback CFRunLoopSourceCreate
    createAsyncEventSource
    CFRunLoopAddSource
    setInterruptReportHandlerCallback
    Setup timer CFRunLoopGetCurrent
    CFRunLoopTimerCreate
    CFRunLoopAddTimer
    Start timer CFRunLoopRun
    Write to device HIDSetReport
    Read device TimerCallback*
    callback_rpt*
    Stop timer CFRunLoopStop
    Release device HIDCloseReleaseInterface

    * Custom functions

    Problems and Warnings

    1. You may be tempted to just use the getReport function of the API. Sadly, this does not work as you might expect. The result will be you wasting a lot of time.

    A getReport() will be sent as a request on the default (control) pipe. If your reports are coming through the interrupt IN pipe you will need to use the HID Manager to get the desired elements. There is currently no way to get the raw report.

    See the link below for more details.

    http://lists.apple.com/archives/usb/2003/Feb/msg00114.html

    2. Once a device has been swiped by the HID manager, it cannot be released thru a release request even if the force option is specified.

    22699 people have wished they had a driver since 1/06.