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!
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.