I'm wondering if there is a way to use a USB Modem (of Huawei E1550 species) on OS X in a generic manner, like a generic USB modem. The problem is that the OS won't recognise it as a legitimate CDC ACM device (which it is, but it doesn't advertise being such).
On Linux, the dongle is automatically recognised as a USB serial device.
There are reasons I don't want neither ZTE nor Huawei vendor drivers on any of my machines: these folks write stuff that barely works (my last experience with ZTE drivers involved spamming my logs, hogging the RAM, and me being unable to disable that), their drivers are not forward-compatible, they neglect to sync them with newer OS version, and they generally intend to distribute those drivers with carrier bloatware.
Apple's developer documentation writes in a vague way that a codeless kext can be written to force a specific driver on a matching device, or prevent a generic driver to load for a matching device. In fact, most searches on codeless kexts yield questions like "how do I prevent my serial dongle to be recognised as a modem?", while what I want is totally opposite!
My tries at writing such a codeless kext so far ended in a fiasco — kextutil tells the kext is loaded successfully, however it's not there, and so is AppleCDCACM.kext (which has the IOClass I want to use).
JFYI, the lsusb information as gathered from Linux:
Bus 004 Device 005: ID 12d1:1001 Huawei Technologies Co., Ltd. E169/E620/E800 HSDPA Modem Device Descriptor: bLength 18 bDescriptorType 1 bcdUSB 2.00 bDeviceClass 0 bDeviceSubClass 0 bDeviceProtocol 0 bMaxPacketSize0 64 idVendor 0x12d1 Huawei Technologies Co., Ltd. idProduct 0x1001 E169/E620/E800 HSDPA Modem bcdDevice 0.00 iManufacturer 2 HUAWEI Technology iProduct 1 HUAWEI Mobile iSerial 0 bNumConfigurations 1 Configuration Descriptor: bLength 9 bDescriptorType 2 wTotalLength 85 bNumInterfaces 3 bConfigurationValue 1 iConfiguration 3 Qualcomm Configuration bmAttributes 0xe0 Self Powered Remote Wakeup MaxPower 500mA Interface Descriptor: bLength 9 bDescriptorType 4 bInterfaceNumber 0 bAlternateSetting 0 bNumEndpoints 3 bInterfaceClass 255 Vendor Specific Class bInterfaceSubClass 255 Vendor Specific Subclass bInterfaceProtocol 255 Vendor Specific Protocol iInterface 0 Endpoint Descriptor: bLength 7 bDescriptorType 5 bEndpointAddress 0x81 EP 1 IN bmAttributes 3 Transfer Type Interrupt Synch Type None Usage Type Data wMaxPacketSize 0x0040 1x 64 bytes bInterval 5 Endpoint Descriptor: bLength 7 bDescriptorType 5 bEndpointAddress 0x82 EP 2 IN bmAttributes 2 Transfer Type Bulk Synch Type None Usage Type Data wMaxPacketSize 0x0200 1x 512 bytes bInterval 32 Endpoint Descriptor: bLength 7 bDescriptorType 5 bEndpointAddress 0x01 EP 1 OUT bmAttributes 2 Transfer Type Bulk Synch Type None Usage Type Data wMaxPacketSize 0x0200 1x 512 bytes bInterval 32 Interface Descriptor: bLength 9 bDescriptorType 4 bInterfaceNumber 1 bAlternateSetting 0 bNumEndpoints 2 bInterfaceClass 255 Vendor Specific Class bInterfaceSubClass 255 Vendor Specific Subclass bInterfaceProtocol 255 Vendor Specific Protocol iInterface 0 Endpoint Descriptor: bLength 7 bDescriptorType 5 bEndpointAddress 0x83 EP 3 IN bmAttributes 2 Transfer Type Bulk Synch Type None Usage Type Data wMaxPacketSize 0x0200 1x 512 bytes bInterval 32 Endpoint Descriptor: bLength 7 bDescriptorType 5 bEndpointAddress 0x02 EP 2 OUT bmAttributes 2 Transfer Type Bulk Synch Type None Usage Type Data wMaxPacketSize 0x0200 1x 512 bytes bInterval 32 Interface Descriptor: bLength 9 bDescriptorType 4 bInterfaceNumber 2 bAlternateSetting 0 bNumEndpoints 2 bInterfaceClass 255 Vendor Specific Class bInterfaceSubClass 255 Vendor Specific Subclass bInterfaceProtocol 255 Vendor Specific Protocol iInterface 0 Endpoint Descriptor: bLength 7 bDescriptorType 5 bEndpointAddress 0x84 EP 4 IN bmAttributes 2 Transfer Type Bulk Synch Type None Usage Type Data wMaxPacketSize 0x0200 1x 512 bytes bInterval 32 Endpoint Descriptor: bLength 7 bDescriptorType 5 bEndpointAddress 0x03 EP 3 OUT bmAttributes 2 Transfer Type Bulk Synch Type None Usage Type Data wMaxPacketSize 0x0200 1x 512 bytes bInterval 32 Device Qualifier (for other device speed): bLength 10 bDescriptorType 6 bcdUSB 2.00 bDeviceClass 0 bDeviceSubClass 0 bDeviceProtocol 0 bMaxPacketSize0 64 bNumConfigurations 1 can't get debug descriptor: Resource temporarily unavailable Device Status: 0x0001 Self Powered
The interface I want to hook up is interface 0 (modem).
Here's how ioreg
sees it:
| | | | | +-o HUAWEI Mobile@1d110000 | | | | | +-o AppleUSBHostLegacyClient | | | | | +-o AppleUSBHostCompositeDevice | | | | | +-o IOUSBHostInterface@0 | | | | | | { | | | | | | "USBPortType" = 0 | | | | | | "IOCFPlugInTypes" = {"2d9786c6-9ef3-11d4-ad51-000a27052861"="IOUSBFamily.kext/Contents/PlugIns/IOUSBLib.bundle"} | | | | | | "bcdDevice" = 0 | | | | | | "USBSpeed" = 3 | | | | | | "idProduct" = 4097 | | | | | | "bConfigurationValue" = 1 | | | | | | "bInterfaceSubClass" = 255 | | | | | | "locationID" = 487653376 | | | | | | "IOGeneralInterest" = "IOCommand is not serializable" | | | | | | "IOClassNameOverride" = "IOUSBInterface" | | | | | | "AppleUSBAlternateServiceRegistryID" = 4294969542 | | | | | | "idVendor" = 4817 | | | | | | "bInterfaceProtocol" = 255 | | | | | | "bAlternateSetting" = 0 | | | | | | "bInterfaceNumber" = 0 | | | | | | "bInterfaceClass" = 255 | | | | | | } | | | | | | | | | | | +-o IOUSBHostInterface@1 | | | | | | { | | | | | | "USBPortType" = 0 | | | | | | "IOCFPlugInTypes" = {"2d9786c6-9ef3-11d4-ad51-000a27052861"="IOUSBFamily.kext/Contents/PlugIns/IOUSBLib.bundle"} | | | | | | "bcdDevice" = 0 | | | | | | "USBSpeed" = 3 | | | | | | "idProduct" = 4097 | | | | | | "bConfigurationValue" = 1 | | | | | | "bInterfaceSubClass" = 255 | | | | | | "locationID" = 487653376 | | | | | | "IOGeneralInterest" = "IOCommand is not serializable" | | | | | | "IOClassNameOverride" = "IOUSBInterface" | | | | | | "AppleUSBAlternateServiceRegistryID" = 4294969544 | | | | | | "idVendor" = 4817 | | | | | | "bInterfaceProtocol" = 255 | | | | | | "bAlternateSetting" = 0 | | | | | | "bInterfaceNumber" = 1 | | | | | | "bInterfaceClass" = 255 | | | | | | } | | | | | | | | | | | +-o IOUSBHostInterface@2 | | | | | { | | | | | "USBPortType" = 0 | | | | | "IOCFPlugInTypes" = {"2d9786c6-9ef3-11d4-ad51-000a27052861"="IOUSBFamily.kext/Contents/PlugIns/IOUSBLib.bundle"} | | | | | "bcdDevice" = 0 | | | | | "USBSpeed" = 3 | | | | | "idProduct" = 4097 | | | | | "bConfigurationValue" = 1 | | | | | "bInterfaceSubClass" = 255 | | | | | "locationID" = 487653376 | | | | | "IOGeneralInterest" = "IOCommand is not serializable" | | | | | "IOClassNameOverride" = "IOUSBInterface" | | | | | "AppleUSBAlternateServiceRegistryID" = 4294969546 | | | | | "idVendor" = 4817 | | | | | "bInterfaceProtocol" = 255 | | | | | "bAlternateSetting" = 0 | | | | | "bInterfaceNumber" = 2 | | | | | "bInterfaceClass" = 255 | | | | | } | | | | |
So far, the Info.plist looks like this:
<?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>BuildMachineOSBuild</key>
<string>15C27b</string>
<key>CFBundleDevelopmentRegion</key>
<string>English</string>
<key>CFBundleIdentifier</key>
<string>name.fedevych.GenericWWAN</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>GenericWWAN</string>
<key>CFBundlePackageType</key>
<string>KEXT</string>
<key>CFBundleShortVersionString</key>
<string>99.0.0</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleSupportedPlatforms</key>
<array>
<string>MacOSX</string>
</array>
<key>CFBundleVersion</key>
<string>99.0.0</string>
<key>IOKitPersonalities</key>
<dict>
<key>HUAWEI_0x12d11001_Control</key>
<dict>
<key>CFBundleIdentifier</key>
<string>com.apple.driver.usb.cdc.acm</string>
<key>IOClass</key>
<string>AppleUSBACMControl</string>
<key>IOProviderClass</key>
<string>IOUSBHostInterface</string>
<key>idVendor</key>
<integer>4817</integer>
<key>idProduct</key>
<integer>4097</integer>
<key>bcdDevice</key>
<integer>0</integer>
<key>bInterfaceNumber</key>
<integer>0</integer>
<key>bConfigurationValue</key>
<integer>1</integer>
<key>IOMatchCategory</key>
<string>com.apple.driver.AppleUSBACMControl</string>
</dict>
<key>HUAWEI_0x12d11001_Data</key>
<dict>
<key>CFBundleIdentifier</key>
<string>com.apple.driver.usb.cdc.acm</string>
<key>IOClass</key>
<string>AppleUSBACMData</string>
<key>IOProviderClass</key>
<string>IOUSBHostInterface</string>
<key>idVendor</key>
<integer>4817</integer>
<key>idProduct</key>
<integer>4097</integer>
<key>bcdDevice</key>
<integer>0</integer>
<key>bInterfaceNumber</key>
<integer>0</integer>
<key>bConfigurationValue</key>
<integer>1</integer>
</dict>
</dict>
<key>OSBundleLibraries</key>
<dict/>
</dict>
</plist>
Any pointers?
So, I can't guarantee you success with your project - I have no idea if your modem will work with the standard CDC driver. But I can hopefully help you find out. There are 5 parts to this really:
I/O Kit USB matching
Apple's Q&A QA1076 is the best resource on the topic. I'd recommend being as specific as possible. So in your case, for example:
IOProviderClass: IOUSBInterface
idVendor: 0x12d1
idProduct: 0x1001
bInterfaceNumber: 0
bConfigurationValue: 1
I hope I got that right. You should be able to confirm by running ioreg -c IOUSBInterface
in the Terminal while the device is plugged in. This will show you all USB interfaces' properties in the system in OSX's format, and you should be able to pick out the one for your modem and make sure those properties match the ones I've given.
Driver class and bundle ID
In addition to the matching criteria, your I/O Kit personality dictionary in the info.plist needs to specify the (C++) class of which an instance should be created to drive the matched device, along with the bundle ID of the kext that contains it.
The problem now is, what driver to use. I don't know much about CDC-ACM devices, but as far as I can tell, they consist of a control and a data interface. The drivers for this appear to be
/System/Library/Extensions/IOUSBFamily.kext/Contents/PlugIns/AppleUSBCDCACMControl.kext
/System/Library/Extensions/IOUSBFamily.kext/Contents/PlugIns/AppleUSBCDCACMData.kext
respectively, on OSX. Each of these seem to assume they have full control over a USB interface - it seems that most devices split the control and data endpoints over 2 separate interfaces. In your case, you have only one. Technically, multiple drivers can match a service, by using different match categories. But I can't guarantee that the CDC-ACM data and control drivers will be happy with this. You can try…
To do so, you'll actually need two I/O kit personality dictionaries, with the same matching criteria, but one of them should have a non-default IOMatchCategory
, e.g. the relevant driver bundle ID.
So try something like this:
<key>IOKitPersonalities</key>
<dict>
<key>AppleUSBCDCACMControl</key>
<dict>
<key>CFBundleIdentifier</key>
<string>com.apple.driver.AppleUSBCDCACMControl</string>
<key>IOClass</key>
<string>AppleUSBCDCACMControl</string>
<key>IOProviderClass</key>
<string>IOUSBInterface</string>
<key>idVendor</key>
<integer>4817</integer>
<key>idProduct</key>
<integer>4097</integer>
<key>bInterfaceNumber</key>
<integer>0</integer>
<key>bConfigurationValue</key>
<integer>1</integer>
<key>IOMatchCategory</key>
<string>com.apple.driver.AppleUSBCDCACMControl</string>
</dict>
<key>AppleUSBCDCACMData</key>
<dict>
<key>CFBundleIdentifier</key>
<string>com.apple.driver.AppleUSBCDCACMData</string>
<key>IOClass</key>
<string>AppleUSBCDCACMData</string>
<key>IOProviderClass</key>
<string>IOUSBInterface</string>
<key>IOUserClientClass</key>
<string>AppleUSBCDCACMDataUserClient</string>
<key>InputBuffers</key>
<integer>8</integer>
<key>OutputBuffers</key>
<integer>16</integer>
<key>idVendor</key>
<integer>4817</integer>
<key>idProduct</key>
<integer>4097</integer>
<key>bInterfaceNumber</key>
<integer>0</integer>
<key>bConfigurationValue</key>
<integer>1</integer>
</dict>
</dict>
Codeless kext info.plist
As you already have a kext that kextutil appears to like, you've probably got this right already: codeless kexts must:
OSBundleLibraries
property.CFBundleExecutable
property.Kext signing
On OSX 10.10 and 10.11, all kexts, including codeless ones, must be signed. On 10.9 they can be unsigned if they reside in /System/Library/Extensions
, and on 10.10 they can be unsigned if the kernel option kext-dev-mode=1
is set. You need a special kext signing addition to your Developer Id certificate from Apple in order to sign kexts.
Loading and Debugging
Kexts in /System/Library/Extensions (writable until 10.10) or /Library/Extensions (10.9+) will be matched automatically on boot or device hot-plug. They can also be loaded manually from outside those locations with kextutil
.
Loaded kext binaries are listed in kextstat
's output. Codeless kexts don't have binaries and thus don't show up. The bundle containing the driver code, as specified in the I/O Kit Personality's CFBundleIdentifier will show up if it is active, however.
To see what is happening with your specific device, use the ioreg
command line tool or the GUI tools IORegistryExplorer (from Apple's Hardware I/O Tools bundle) or IOJones (open source).
Final thoughts
Given the CDC-ACM Control/Data split, you might not be able to get things working with just a codeless kext. You may need to look deeper into how Apple's drivers work and possibly override some of their behaviour in your own kext so that they find the correct end points. Good luck!