IOKit and USB devices with swift 3


(Jerome Duquennoy) #1

Hi swift community,

I am trying to access an USB device in a swift project, with quite limited knowledge of IOKit and not a log of experience using unsafe pointers with swift.

My goal is to create a class that will listen to connections and disconnections of a device matching a productID and vendorID.
What I have done up to now is extensively based on that documentation (not swift oriented) :
https://developer.apple.com/library/content/documentation/DeviceDrivers/Conceptual/USBBook/USBDeviceInterfaces/USBDevInterfaces.html#//apple_ref/doc/uid/TP40002645-BBIEIEII

The result of my tests and trial can be found here : http://pastebin.com/rSqdFMwH
(sorry for the pastebin link, but the code is almost 300 lines, which is quite a lot for a mail)

This code compiles on Xcode 8, and the callbacks on plugging and unplugging the device works fine.
But it systematically crashes with an EXC_BAD_ACCESS on the first use of the IOUSBDeviceInterface when a device is detected (on line 143, when trying to open the device).

I guess to have such an error, I must be messing badly with the UnsaveMutablePointer.

Would anyone master IOKit and swift enough to spot my mistake ?
You can test it by executing that code in a sample project :

try USBDevice(vendorId: 0x04d8, productId: 0xf372)

where vendorId and productId should match one of your connected devices.

Also, there are very few exemples of swift code dealing with IOKit and USB, I would be glad to contribute by publishing that code if I can have it working !

Thanks for your help !

Jerome


(Quinn “The Eskimo!”) #2

Oh, fun times!

First things first, you don’t need IOMasterPort; use kIOMasterPortDefault instead.

Second, IOServiceMatching won’t return nil unless you run out of memory; you can just ignore that possibility.

Next, unless you absolutely have to, I’d avoid getting between IOServiceMatching and IOServiceAddMatchingNotification. These two have unusual memory management behaviour (IOServiceMatching returns a +1 reference and IOServiceAddMatchingNotification consumes that reference). It looks like the current SDK has the right annotations for this but it’s still easy to run into trouble.

IO_OBJECT_NULL rather than 0.

There’s two parts to your overall problem:

A. discovering devices

B. talking to devices

I’d separate these so you can debug them independently. Specifically, you can test B from a single matching service (IOServiceGetMatchingService) without having to deal with A at all. You can then generalise that to a list (using IOServiceGetMatchingServices) before moving on to tackle A.

As to your actual crash, you seem to be missing a level of indirection in `getDeviceInterface(forPluginInterface:)`. Consider this code:

200 private func getDeviceInterface(forPluginInterface pluginInterfacePtrP…
201 var deviceInterfaceRawPtr: UnsafeMutableRawPointer? = nil
202
203 let deviceInterfaceResult = pluginInterfacePtrPtr.pointee?.pointee.Q…
204
205
206 if (deviceInterfaceResult != kIOReturnSuccess) || (deviceInterfaceRa…
207 throw Error("Could not get device interface for plugin interface")…
208 }
209
210 return deviceInterfaceRawPtr!.assumingMemoryBound(to: IOUSBDeviceInt…
211 }

Expand line 210…211 to this:

210 let res = deviceInterfaceRawPtr!.assumingMemoryBound(to: IOUSBDevice…
211 return res
212 }

and set a breakpoint on 211. At the breakpoint `res` is gibberish.

(lldb) p res.pointee
(IOUSBDeviceInterface) $R1 = {
  _reserved = (_rawValue = 0x000000010916ea48 IOUSBDeviceClass::sUSBDevice…
  QueryInterface = 0x0000610000101830 -> 0x000000010916e250 IOUSBLib`vtabl…
  AddRef = 0x0000880300008703
  Release = nil
  …
}

Hint: the `Release` property should never be nil (-:

You need to add a level of indirection:

210 let res = deviceInterfaceRawPtr!.assumingMemoryBound(to:
        UnsafeMutablePointer<IOUSBDeviceInterface>.self
      )
211 return res.pointee
212 }

at which point your breakpoint on 211 which show sensible results:

(lldb) p res.pointee.pointee
(IOUSBDeviceInterface) $R1 = {
  _reserved = nil
  QueryInterface = 0x000000010a349cc0 IOUSBLib`IOUSBIUnknown::genericQueryInterface(void*, CFUUIDBytes, void**)
  AddRef = 0x000000010a349cd2 IOUSBLib`IOUSBIUnknown::genericAddRef(void*)
  Release = 0x000000010a349ce2 IOUSBLib`IOUSBIUnknown::genericRelease(void*)
  …
}

You can see this when you look at the equivalent code in C. For example, this line:

kr = (*privateDataRef->deviceInterface)->GetLocationID(privateDataRef->deviceInterface, &locationID);

from the USBPrivateDataSample.

<https://developer.apple.com/library/content/samplecode/USBPrivateDataSample/Listings/USBPrivateDataSample_c.html>

`privateDataRef` is indirected once with the `*` and again with the `->` (in C, `foo->bar` expands to `(*foo).bar`.

I recommend that you take a leaf out of the C’s book here and store the double indirected value. That is, have `getDeviceInterface(forPluginInterface:)` return `UnsafeMutablePointer<UnsafeMutablePointer<IOUSBDeviceInterface>>` rather than `UnsafeMutablePointer<IOUSBDeviceInterface>`.

                   * * *

This is all super unpleasant and I wish I had time to write up more detailed instructions. Hopefully this will get you unblocked.

Share and Enjoy

···

On 23 Sep 2016, at 17:46, Jérôme Duquennoy via swift-users <swift-users@swift.org> wrote:

I am trying to access an USB device in a swift project, with quite limited knowledge of IOKit and not a [lot] of experience using unsafe pointers with swift.

--
Quinn "The Eskimo!" <http://www.apple.com/developer/>
Apple Developer Relations, Developer Technical Support, Core OS/Hardware


(Quinn “The Eskimo!”) #3

And, and if you do get between these two it’s probably easiest to work in ‘CFDictionary space’ rather than try to treat this stuff as a Swift dictionary. This will help:

func CFDictionarySetValue(_ dict: CFMutableDictionary, _ key: String, _ valueObj: AnyObject) {
    let keyObj = key as NSString
    withExtendedLifetime(keyObj) {
        CFDictionarySetValue(dict, Unmanaged.passUnretained(keyObj).toOpaque(), Unmanaged.passUnretained(valueObj).toOpaque())
    }
}

Share and Enjoy

···

On 26 Sep 2016, at 10:48, Quinn The Eskimo! via swift-users <swift-users@swift.org> wrote:

Next, unless you absolutely have to, I’d avoid getting between IOServiceMatching and IOServiceAddMatchingNotification. These two have unusual memory management behaviour (IOServiceMatching returns a +1 reference and IOServiceAddMatchingNotification consumes that reference). It looks like the current SDK has the right annotations for this but it’s still easy to run into trouble.

--
Quinn "The Eskimo!" <http://www.apple.com/developer/>
Apple Developer Relations, Developer Technical Support, Core OS/Hardware