Search code examples
macosserial-portusbiokitdriverkit

Start returns 0xE00002C7 if my driver inherits IOUserUSBSerial


I am developing a DriverKit driver for single port USB-serial adapter. I have a simple driver code as following.

class MyDriver: public IOUserUSBSerial
{
public:
    virtual bool init() override;
    virtual kern_return_t Start(IOService *provider) override;
    virtual kern_return_t Stop(IOService *provider) override;
    virtual void free() override;
};
bool
MyDriver::init()
{
    Log("Entered %s", __FUNCTION__);
    return super::init();
}

void
MyDriver::free()
{
    Log("Entered %s", __FUNCTION__);
    super::free();
 }

kern_return_t
IMPL(MyDriver, Start)
{
    kern_return_t ret;
    Log("Entered %s", __FUNCTION__);
    
    ret = Start(provider, SUPERDISPATCH);
    
    if( ret!=kIOReturnSuccess ){
        Log("Start returns Error: 0x%X\n", ret);
        return ret;
    }
    
    ret = RegisterService();

    if( ret!=kIOReturnSuccess ){
        Log("IOReturn Error: 0x%X @ %d, %s\n", ret, __LINE__, __FUNCTION__);
        return ret;
    }

    Log("Hello MyDriver is started...");
    return ret;
}

kern_return_t
IMPL(MyDriver, Stop)
{
    kern_return_t ret = kIOReturnSuccess;
    
    Log("Entered %s", __FUNCTION__);
    
    ret = Stop(provider, SUPERDISPATCH);
    if( ret!=kIOReturnSuccess ){
        Log("IOReturn Error: 0x%X @ %d, %s\n", ret, __LINE__, __FUNCTION__);
    }

    return ret;
}

    <key>IOKitPersonalities</key>
    <dict>
        <key>MyDriver</key>
        <dict>
            <key>CFBundleIdentifier</key>
            <string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
            <key>CFBundleIdentifierKernel</key>
            <string>com.apple.driver.driverkit.serial</string>
            <key>IOClass</key>
            <string>IOUserSerial</string>
            <key>IOMatchCategory</key>
            <string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
            <key>IOProviderClass</key>
            <string>IOUSBHostDevice</string>
            <key>IOResourceMatch</key>
            <string>IOKit</string>
            <key>IOUserClass</key>
            <string>MyDriver</string>
            <key>IOUserServerName</key>
            <string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
            <key>idVendor</key>
            <integer>1234</integer>
            <key>idProduct</key>
            <integer>5678</integer>
            <key>IOTTYBaseName</key>
            <string>USB</string>
            <key>IOTTYSuffix</key>
            <string>4</string>
        </dict>
    </dict>

My USB device was detected by system, the init and Start were called successfully. However, while I call Start(provider, SUPERDISPATCH), it returns error code 0xE00002C7 (kIOReturnUnsupported).

I have referred this thread. If I change to inherit IOUserSerial class, The call to Start returns success. I guess the problem is caused by CFBundleIdentifierKernel but I can't find any information about that.

Could any expert kindly tell me why the Start returns error?

Can I ignore the call to super::Start?

Can I use IOUserSerial subclass if I prefer to develop some specific features?

I have tried to read all articles online and feel frustrated. IOUserUSBSerial example code is also not available online.


Solution

  • Disclaimer: I've not used IOUserUSBSerial myself yet, but I have years of experience with other parts of DriverKit and IOKit and the kernel before that. With Apple's documentation you always have to read between the lines to work out what it's implying even if it's not stating it explicitly.

    Main Question about IOUserUSBSerial:

    In short, I suspect the issue is this:

    You have a non class-compliant (non-ACM) device for which you'd like to develop a driver to make it act as a serial port to macOS. I believe IOUserUSBSerial is specifically for implementing vendor-specific behaviour on ACM devices, however.

    I deduce this from the phrase "This class automatically manages the serial communications with the device." How would IOUserUSBSerial know how to "automatically manage the serial communications" if your device implements a custom protocol? It must surely be implementing the ACM USB protocol.

    So the fact that your device is USB is irrelevant for choosing IOUserUSBSerial versus IOUserSerial, what actually matters is whether or not it's an ACM compliant device. I agree with you that the solution appears to be to use the lower-level IOUserSerial if your device is USB based but doesn't follow ACM.

    Further comments and answers to sub-questions:

               <key>IOMatchCategory</key>
               <string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
    

    Don't use IOMatchCategory when matching real hardware. (Except in very rare cases where you want multiple drivers attaching to the same node.)

    Can I ignore the call to super::Start?

    Generally: definitely not.

    I guess the problem is caused by CFBundleIdentifierKernel but I can't find any information about that.

    IOUserUSBSerial is a bit of a weird one because it seems to be implemented entirely in the framework, within the DriverKit runtime. Unlike most DriverKit classes, there is no kernel class backing it. So com.apple.driver.driverkit.serial actually appears to be the correct CFBundleIdentifierKernel, and IOUserSerial appears to be the correct IOClass, which is unusual.