macosiokitkernel-extension

How to register a key in the IORegistry and react to its change in the kext?


I am studying kext development. I'm trying to implement a simple "generic" kext that would publish a key in the IORegistry and react to its changes then. The class is quite trivial, it just registers with IOService::registerPowerDriver and publish a key. Reading some book, I've found following:

When a driver’s property is set from a user space application, the method setProperties() in the corresponding driver object is called with a parameter containing a dictionary of the properties that have been set.

So, I implemented it like that:

Header:

#define ENABLED_PROP_NAME "Enabled"

class GenericPower : public IOService
{
    OSDeclareDefaultStructors(GenericPower);

public:
    virtual bool init(OSDictionary* dict) override;
    virtual IOService* probe(IOService* provider, SInt32* score) override;
    virtual void free(void) override;
    virtual bool start(IOService* provider) override;
    virtual void stop(IOService * provider) override;
    virtual IOReturn setProperties(OSObject* properties) override;

protected:

    virtual IOReturn        powerStateWillChangeTo(IOPMPowerFlags capabilities, unsigned long stateNumber, IOService* whatDevice) override;
    virtual IOReturn        setPowerState(unsigned long powerStateOrdinal, IOService* whatDevice) override;
    virtual IOReturn        powerStateDidChangeTo(IOPMPowerFlags capabilities, unsigned long stateNumber, IOService* whatDevice) override;

    void updateProperties(void);

private:
    // Property to publish:
    bool isEnabled = true;
};

CPP:

#define super IOService

OSDefineMetaClassAndStructors(GenericPower, super);

bool GenericPower::init(OSDictionary* dict)
{
    if (!super::init(dict)) {
        IOLog("GenericPower: init failed\n");
        return (false);
    }
    IOLog("GenericPower: init\n");

    return (true);
}

void GenericPower::free(void)
{
    IOLog("GenericPower: free\n");

    super::free();
}

IOService* GenericPower::probe(IOService* provider, SInt32* score)
{
    IOLog("GenericPower: probe\n");

    return (super::probe(provider, score));
}


IOReturn GenericPower::setProperties(OSObject* properties)
{
    IOLog("GenericPower: setProperties\n");

    if (properties == nullptr) {
        IOLog("GenericPower: setProperties: no properties\n");
        return (kIOReturnUnsupported);
    }

    OSDictionary* propertiesDict;
    propertiesDict = OSDynamicCast(OSDictionary, properties);
    if (propertiesDict == nullptr) {
        IOLog("GenericPower: setProperties: cannot get properties\n");
        return (kIOReturnUnsupported);
    }
    OSObject * rawValue = propertiesDict->getObject(ENABLED_PROP_NAME);
    OSBoolean * boolValue = OSDynamicCast(OSBoolean, rawValue);
    if (boolValue == nullptr) {
        IOLog("GenericPower: setProperties: cannot get \"" ENABLED_PROP_NAME "\" property\n");
    } else {
        IOLog("GenericPower: setProperties: new value %s\n", boolValue->getValue() ? "Enabled" : "Disabled");
        isEnabled = boolValue->getValue();
    }

    IOLog("GenericPower: setProperties: \"" ENABLED_PROP_NAME "\" set to %s\n", isEnabled ? "ON" : "OFF");

    updateProperties();

    return (kIOReturnSuccess);
}

enum {
    kPowerStateOff,
    kPowerStateSleep,
    kPowerStateOn,
//
    kNumPowerStates
};

static IOPMPowerState powerStates[kNumPowerStates] = {
    {
        // Off
        kIOPMPowerStateVersion1,          // version
        0,   // capabilityFlags
        0,                      // outputPowerCharacter
        0,                      // inputPowerRequirement
        0, 0, 0, 0, 0, 0, 0, 0
    },
    {
        // Sleep
        kIOPMPowerStateVersion1,          // version
        kIOPMSleepCapability,   // capabilityFlags
        kIOPMSleep,                      // outputPowerCharacter
        kIOPMSleep,                      // inputPowerRequirement
        0, 0, 0, 0, 0, 0, 0, 0
    },
    {
        // On
        kIOPMPowerStateVersion1,          // version
        kIOPMPowerOn | kIOPMDeviceUsable,   // capabilityFlags
        kIOPMPowerOn,                      // outputPowerCharacter
        kIOPMPowerOn,                      // inputPowerRequirement
        0, 0, 0, 0, 0, 0, 0, 0
    },
};

bool GenericPower::start(IOService* provider)
{
    if (!super::start(provider)) {
        IOLog("GenericPower: start: IOService start error\n");
        return (false);
    }

    PMinit();
    provider->joinPMtree(this);
    registerPowerDriver(this, powerStates, kNumPowerStates);

    makeUsable();
    registerService();

    updateProperties();

    return (true);
}
void GenericPower::stop(IOService* provider)
{
    IOLog("GenericPower: stop. isEnabled=%d\n", (int)isEnabled);

    PMstop();
    super::stop(provider);
}

// /////////////////////////////////////////////////////////////

IOReturn GenericPower::powerStateWillChangeTo(IOPMPowerFlags capabilities, unsigned long stateNumber, IOService* whatDevice)
{
    IOLog("GenericPower: powerStateWillChangeTo %lu\n", stateNumber);
    return (kIOPMAckImplied);
}

IOReturn GenericPower::setPowerState(unsigned long powerStateOrdinal, IOService* whatDevice)
{
    IOLog("GenericPower: setPowerState %lu\n", powerStateOrdinal);

    return (kIOPMAckImplied);
}

IOReturn GenericPower::powerStateDidChangeTo(IOPMPowerFlags capabilities, unsigned long stateNumber, IOService* whatDevice)
{
    IOLog("GenericPower: powerStateDidChangeTo %lu\n", stateNumber);
    return (kIOPMAckImplied);

}
void GenericPower::updateProperties(void)
{
    // Properties init
    OSBoolean * osProp = OSBoolean::withBoolean(isEnabled);
    setProperty(ENABLED_PROP_NAME, osProp);
    OSSafeReleaseNULL(osProp);
}

But when I try to change this key in the ioregistryExplorer.app, I observe no call for IOService::setProperties in the log at all. Please help - how could I publish the key and react to its changes in the kext? Cannot find any relevant docs and/or examples.

Thank you!


Solution

  • I think the problem here is most likely that IORegistryExplorer.app doesn't support setting properties on I/O Registry entries. To verify if this is the case, I suggest writing a very small command line utility which locates your kext's service node, for example using IOServiceGetMatchingService(), and then sets the property using IORegistryEntrySetCFProperty(). You should then see the call to the setProperties() method happen.