Search code examples
macosserial-portmodemcdckernel-extension

Codeless kext to force a specific driver (CDC-ACM)


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?


Solution

  • 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:

    1. The correct matching dictionary to match the device/interface in question.
    2. Setting up the correct class and bundle ID for the actual driver.
    3. Some extra points to watch out for in the info.plist of a codeless kext
    4. Kext signing.
    5. Loading/Debugging.

    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:

    • Have an empty OSBundleLibraries property.
    • Not have an 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!