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.