Use IOKit to access usb in MacOS

Hi, I am trying to develop MacOS application which will be connecting to USB devices and should be available in AppStore. So it must be Sandbox and probably I've to use permission com.apple.security.device.usb.

I've following requirements:

  • I need to detect USB devices with file system
  • I need to have ability to upload & download files from this device
  • I need to read device serial number

I wonder if I can use IOKit for this and it will be compliant with AppStore rules or not?

Answered by DTS Engineer in 815138022

Maybe one more question. I was able to get RawUsbDevice using IOServiceAddMatchingNotification and now have basic stuff like VendorID, ProductID, SerialNo etc. How to get bsd name for this object or even mount point like /Volumes/Kindle/ in Sandboxed app?

Basically, this depends on what "direction" you want to go. Two approaches broad approaches, either going "up" from your io_object_t or "down" from the volume layer*:

*Note that the directionality here is entirely made up. I've always envisioned the IORegistry that grows "up" toward user space, but an argument can be made that it grows "down" from the root node.

1) Working from the USB object "up" to user space.

  1. Start with your io_object_t you got from the IOServiceAddMatchingNotification.

  2. Call IOServiceWaitQuiet to allow the driver stack to "stabilize". You can be notified of a USB device before the storage stack has actually loaded, so this ensure that process has occurred.

  3. Move "up" the registry to find the nodes you need. What you're looking is all "IOMedia" objects with a property of "Leaf = true". Use IORegistryEntryCreateIterator(::::) with "kIORegistryIterateRecursively" to get a child iterator, then iterate all nodes looking for those two criteria.

  4. That list of object will be the ones that are "eligible" for mounting. You can actually get the BSD name from the registry properties, but what I actually suggest is that you pass those IOMedia objects into DADiskCreateFromIOMedia().

  5. DiskArb is what handles automounting on macOS and, what makes it useful to you is that it's neatly bridges between IOKit (io_object_t's), BSD (dev nodes), and mounts (mount points). Given a DADisk, you can find out anything you want to know by grabbing it's description dictionary and looking at the key you want. Most of those properties are available through other APIs, but DiskArb is the one API that covers "all" of them.

2) Working "down" from DiskArb into the kernel.

  1. DiskArb can be used to monitor mounts and you can also create a DADisk from a mountpoint or a BSD name. With a DADisk as a starting point.

  2. Use DADiskCopyIOMedia() to retrieve the IOMedia object for that disk.

  3. Use IORegistryEntryCreateIterator(::::) with "kIORegistryIterateRecursively" and "kIORegistryIterateParents" to get a parent iterator, then iterate all all nodes looking for those two criteria.

  4. Iterate the parent iterator looking for the "your" USB io_object_t.

All of that can sound a bit overwhelming but it's not as difficult as it sounds once you get your head around it. Also, it can seem like iterating the registry like this would be a performance issue but that isn't really the case. The entire IORegistry basically ends up living in wired kernel memory so, in practice, it's MUCH faster to manipulate than you might think.

__
Kevin Elliott
DTS Engineer, CoreOS/Hardware

Hi, I am trying to develop MacOS application which will be connecting to USB devices and should be available in AppStore. So it must be Sandbox and probably I've to use permission com.apple.security.device.usb.

I think this is a case where I need a much better understanding of what you're actually trying to do and why.

The problem with what you're asking here:

I've following requirements:

I need to detect USB devices with file system I need to have ability to upload & download files from this device I need to read device serial number

...is that it sounds like what you'd like to do is attach a USB device to your mac and transfer (up or down) a file(s) to it without directly over USB, without using any "higher level" API.

If that's the case then:

  • In theory, it's entirely possible. Strictly speaking, the IOUSBHost framework will allow you to exchange USB traffic with device. Exchanging USB traffic is all any USB device does so, sure, it's possible.

  • In practice, it's somewhere between "an enormous pain" and "wildly impractical", depending on exactly how narrow what you're trying to do is and what code you already have. I'm happy to outline what's required, but it's directly equivalent to creating an entire mass storage stack from "scratch".

I wonder if I can use IOKit for this and it will be compliant with AppStore rules or not?

Again, this really depends on what you're actually trying to do. I can think of many approaches that would be App Store compatible. However, the problem wouldn't be getting them on the store, it would be getting them to work at all.

__
Kevin Elliott
DTS Engineer, CoreOS/Hardware

Ok so here is the whole idea.

Right now we have working code for that solution, but it isn't code compliant with AppStore

Here are some specs:

  • MacOS app available in AppStore - our current code is working without sandbox
  • App will be connecting with Kindle devices. Amazon currently is using both approaches - file system (older devices), MTP (new devices)
  • App needs to detect Kindle device - both types as described above
  • I have PoC for MTP solution using libmtp library, now looking for a solution for a generic Kindle
  • I need to read attached device serial number to identify it in our system
  • Ability to download/upload files from specific directory on device

So in general app is:

  • Listening if some usb devices are connecting
  • Checking if it is old type Kindle device using FS or new one using MTP
  • Reading serial number to identify device
  • Copying/deleting/reading files from documents

I've seen that there is for instance app called Commander One in AppStore which is working with USB and have most of the functions above. Only reading serial number is missing there.

I saw they are using IOKit somehow, but I'm not sure it is used for this. For generic Kindle, right now we are using system_profiler SPUSBDataType -xml for detection

I would be happy to hear if you have a recommendation for APIs that are compatible with AppStore's policy.

Accepted Answer

I saw they are using IOKit somehow, but I'm not sure it is used for this.

So, the first thing to understand here is that IOKit is basically THE API here. Other frameworks are often built on top/around it (more on that shortly), but IOKit is the core of how hardware is discovered and accessed from user space.

Case in point:

For generic Kindle, right now we are using system_profiler SPUSBDataType -xml for detection

Basically "all" of the hardware specific information you see in system_profiler was actually pulled from the IORegistry using IOKit. A few things I'd recommend when you're getting started with IOKit:

  1. Take a close look at the documents "IOKit Fundamentals". This is document is the "base" reference for how IOKit (the kernel API) is structured and functions and will help you understand what you're seeing on a "real world" system.

  2. Download and experiment with "IORegistryExplorer.app", which you'll find in side the "Additional Tools for Xcode <version>". Note that while that download is versioned to Xcode, you don't really need to worry about staying "current". IORegistryExplorer.app itself changes VERY little- the app itself is 24+ years old but is only at v3.0.2 and I don't think it's been updated since 2013. In any case, what IORegistryExplorer.app actually shows you is the system "IORegistry" structure, which is the core of how IOKit hardware discovery and communication works. Note that the data you're actually seeing in SPUSBDataType call came from the IORegistry data store.

  3. Read "Accessing Hardware From Applications", which is the reference for using the IOKit (user space) API to interact with hardware. It describes how you'll actually discover hardware and retrieve information about it.

I would be happy to hear if you have a recommendation for APIs

In terms of the API side of this, the basic approach would be:

  1. Use IOKit to discover existing devices and monitor for new ones, then determine whether your app should interact with them based on the devices properties. You can find some basic code in this post showing what this looks like. Note that it is ONLY a simple starting point. For example, it uses "IOServiceGetMatchingServices" (which searches for registry) but an app would use "IOServiceAddMatchingNotification" (which finds current services and then monitors for new ones).

  2. Based on the serial number (retrieved in #1), you'd then either use file systems access or device access.

  3. The file system access operates entirely outside of IOKit. IOKit (and other APIs) can be used to determine what mount point corresponds to a particular hardware device but all of the actual "access" would be handled just like any other file access.

  4. For MTP access, the IOKit object from #1 would then be used to directly communicate with the device through the IOUSBHost framework. Note that while the framework uses the term "host-mode user space drivers", that is a very fancy way of saying "an app that directly controls device".

that are compatible with AppStore's policy.

In terms of sandbox compatibility, I think the "com.apple.security.device.usb" entitlement is the main one you'd need. What's allowed on the App Store is ultimately determined by App Review, but I don't think any thing I've described above would be an issue on the App Store.

__
Kevin Elliott
DTS Engineer, CoreOS/Hardware

Thank you very much - very helpful :) I've already made some progress yesterday in terms of discovering devices. I'll go further with this.

Maybe one more question. I was able to get RawUsbDevice using IOServiceAddMatchingNotification and now have basic stuff like VendorID, ProductID, SerialNo etc.

How to get bsd name for this object or even mount point like /Volumes/Kindle/ in Sandboxed app?

Earlier I was able to read bsd name using system profiler, but this particular data is not available when Sandbox is used.

I can read mount point using mountedVolumeURLsIncludingResourceValuesForKeys on NSFileManager but can't match mount point to specific RawUsbDevice gathered from IOKIt.

Maybe one more question. I was able to get RawUsbDevice using IOServiceAddMatchingNotification and now have basic stuff like VendorID, ProductID, SerialNo etc. How to get bsd name for this object or even mount point like /Volumes/Kindle/ in Sandboxed app?

Basically, this depends on what "direction" you want to go. Two approaches broad approaches, either going "up" from your io_object_t or "down" from the volume layer*:

*Note that the directionality here is entirely made up. I've always envisioned the IORegistry that grows "up" toward user space, but an argument can be made that it grows "down" from the root node.

1) Working from the USB object "up" to user space.

  1. Start with your io_object_t you got from the IOServiceAddMatchingNotification.

  2. Call IOServiceWaitQuiet to allow the driver stack to "stabilize". You can be notified of a USB device before the storage stack has actually loaded, so this ensure that process has occurred.

  3. Move "up" the registry to find the nodes you need. What you're looking is all "IOMedia" objects with a property of "Leaf = true". Use IORegistryEntryCreateIterator(::::) with "kIORegistryIterateRecursively" to get a child iterator, then iterate all nodes looking for those two criteria.

  4. That list of object will be the ones that are "eligible" for mounting. You can actually get the BSD name from the registry properties, but what I actually suggest is that you pass those IOMedia objects into DADiskCreateFromIOMedia().

  5. DiskArb is what handles automounting on macOS and, what makes it useful to you is that it's neatly bridges between IOKit (io_object_t's), BSD (dev nodes), and mounts (mount points). Given a DADisk, you can find out anything you want to know by grabbing it's description dictionary and looking at the key you want. Most of those properties are available through other APIs, but DiskArb is the one API that covers "all" of them.

2) Working "down" from DiskArb into the kernel.

  1. DiskArb can be used to monitor mounts and you can also create a DADisk from a mountpoint or a BSD name. With a DADisk as a starting point.

  2. Use DADiskCopyIOMedia() to retrieve the IOMedia object for that disk.

  3. Use IORegistryEntryCreateIterator(::::) with "kIORegistryIterateRecursively" and "kIORegistryIterateParents" to get a parent iterator, then iterate all all nodes looking for those two criteria.

  4. Iterate the parent iterator looking for the "your" USB io_object_t.

All of that can sound a bit overwhelming but it's not as difficult as it sounds once you get your head around it. Also, it can seem like iterating the registry like this would be a performance issue but that isn't really the case. The entire IORegistry basically ends up living in wired kernel memory so, in practice, it's MUCH faster to manipulate than you might think.

__
Kevin Elliott
DTS Engineer, CoreOS/Hardware

Use IOKit to access usb in MacOS
 
 
Q