IOHIDT8027USBSessionFilter.cpp [plain text]
#include <sys/time.h>
#include <IOKit/IOKitLib.h>
#include <IOKit/hid/IOHIDPrivateKeys.h>
#include <IOKit/hid/IOHIDEventSystemKeys.h>
#include <IOKit/hid/IOHIDUsageTables.h>
#include <IOKit/hid/IOHIDService.h>
#include "IOHIDT8027USBSessionFilter.hpp"
#include "IOHIDDebug.h"
#define DEBUG_ASSERT_MESSAGE(name, assertion, label, message, file, line, value) \
os_log_error(_HIDLog(), "AssertMacros: %s, %s", assertion, (message!=0) ? message : "");
#include <AssertMacros.h>
#define kAssertionTimeoutKey "T8027USBAssertionTimeout"
#define kSetAssertionKey "T8027USBSetAssertion"
CFStringRef const IOHIDT8027USBSessionFilter::ASSERTION_NAME = CFSTR("IOHIDT8027USBAssertion");
#define kIOHIDT8027USBSessionFilterFactory CFUUIDGetConstantUUIDWithBytes(kCFAllocatorSystemDefault, 0xc8, 0x52, 0xD4, 0xBF, 0x2C, 0x29, 0x4D, 0xCB, 0xBB, 0x7E, 0x88, 0x54, 0x8F, 0x66, 0xAA, 0x41)
extern "C" void * IOHIDT8027USBSessionFilterFactory(CFAllocatorRef allocator, CFUUIDRef typeUUID);
void *IOHIDT8027USBSessionFilterFactory(CFAllocatorRef allocator __unused, CFUUIDRef typeUUID)
{
if (CFEqual(typeUUID, kIOHIDSessionFilterPlugInTypeID)) {
void *alctr = CFAllocatorAllocate(kCFAllocatorDefault, sizeof(IOHIDT8027USBSessionFilter), 0);
return new(alctr) IOHIDT8027USBSessionFilter(kIOHIDT8027USBSessionFilterFactory);
}
return NULL;
}
IOHIDSessionFilterPlugInInterface IOHIDT8027USBSessionFilter::sIOHIDT8027USBSessionFilterFtbl =
{
NULL,
IOHIDT8027USBSessionFilter::_QueryInterface,
IOHIDT8027USBSessionFilter::_AddRef,
IOHIDT8027USBSessionFilter::_Release,
IOHIDT8027USBSessionFilter::_filter,
NULL,
NULL,
NULL,
NULL,
NULL,
NULL,
IOHIDT8027USBSessionFilter::_registerService,
IOHIDT8027USBSessionFilter::_unregisterService,
IOHIDT8027USBSessionFilter::_scheduleWithDispatchQueue,
IOHIDT8027USBSessionFilter::_unscheduleFromDispatchQueue,
IOHIDT8027USBSessionFilter::_getPropertyForClient,
IOHIDT8027USBSessionFilter::_setPropertyForClient,
};
IOHIDT8027USBSessionFilter::IOHIDT8027USBSessionFilter(CFUUIDRef factoryID) :
_sessionInterface(&sIOHIDT8027USBSessionFilterFtbl),
_factoryID( static_cast<CFUUIDRef>( CFRetain(factoryID) ) ),
_refCount(1),
_port(NULL),
_iterator(IO_OBJECT_NULL),
_timer(0),
_assertionTimeout(DEFAULT_ASSERTION_TIMEOUT),
_assertionID(0),
_hasT8027USB(false),
_asserting(false)
{
CFPlugInAddInstanceForFactory( factoryID );
}
IOHIDT8027USBSessionFilter::~IOHIDT8027USBSessionFilter()
{
CFPlugInRemoveInstanceForFactory( _factoryID );
CFRelease( _factoryID );
}
HRESULT IOHIDT8027USBSessionFilter::_QueryInterface(void *self, REFIID iid, LPVOID *ppv)
{
return static_cast<IOHIDT8027USBSessionFilter *>(self)->QueryInterface(iid, ppv);
}
HRESULT IOHIDT8027USBSessionFilter::QueryInterface( REFIID iid, LPVOID *ppv )
{
CFUUIDRef interfaceID = CFUUIDCreateFromUUIDBytes( NULL, iid );
if (CFEqual(interfaceID, kIOHIDSimpleSessionFilterPlugInInterfaceID) || CFEqual(interfaceID, kIOHIDSessionFilterPlugInInterfaceID)) {
AddRef();
*ppv = this;
CFRelease(interfaceID);
return S_OK;
}
if (CFEqual(interfaceID, IUnknownUUID)) {
AddRef();
*ppv = this;
CFRelease(interfaceID);
return S_OK;
}
*ppv = NULL;
CFRelease( interfaceID );
return E_NOINTERFACE;
}
ULONG IOHIDT8027USBSessionFilter::_AddRef(void *self)
{
return static_cast<IOHIDT8027USBSessionFilter *>(self)->AddRef();
}
ULONG IOHIDT8027USBSessionFilter::AddRef()
{
_refCount += 1;
return _refCount;
}
ULONG IOHIDT8027USBSessionFilter::_Release(void *self)
{
return static_cast<IOHIDT8027USBSessionFilter *>(self)->Release();
}
ULONG IOHIDT8027USBSessionFilter::Release()
{
_refCount -= 1;
if (_refCount == 0) {
delete this;
return 0;
}
return _refCount;
}
void IOHIDT8027USBSessionFilter::_registerService(void * self, IOHIDServiceRef service)
{
static_cast<IOHIDT8027USBSessionFilter *>(self)->registerService(service);
}
void IOHIDT8027USBSessionFilter::registerService(IOHIDServiceRef service)
{
CFStringRefWrap transportString = CFStringRefWrap(kIOHIDTransportKey);
CFTypeRef prop;
require(_usbHIDServices.Reference() && transportString.Reference(), exit);
require_quiet(IOHIDServiceConformsTo(service, kHIDPage_GenericDesktop, kHIDUsage_GD_Keyboard) ||
IOHIDServiceConformsTo(service, kHIDPage_GenericDesktop, kHIDUsage_GD_Keypad) ||
IOHIDServiceConformsTo(service, kHIDPage_Consumer, kHIDUsage_Csmr_ConsumerControl),
exit);
prop = IOHIDServiceGetProperty(service, transportString);
require(prop && CFGetTypeID(prop) == CFStringGetTypeID(), exit);
require_quiet(CFEqual(prop, CFSTR(kIOHIDTransportUSBValue)), exit);
_usbHIDServices.SetValue(service);
if (_hasT8027USB) {
CFStringRef detail = CFSTR("");
CFStringRef product = CFSTR("");
uint64_t senderID = 0;
prop = IOHIDServiceGetProperty(service, CFSTR(kIOHIDProductKey));
if (prop && CFGetTypeID(prop) == CFStringGetTypeID()) {
product = (CFStringRef)prop;
}
prop = IOHIDServiceGetRegistryID(service);
if (prop && CFGetTypeID(prop) == CFNumberGetTypeID()) {
CFNumberGetValue((CFNumberRef)prop, kCFNumberSInt64Type, &senderID);
}
detail = CFStringCreateWithFormat(kCFAllocatorDefault, NULL, CFSTR("RegID:0x%llx %@"), senderID, product);
os_log(_HIDLog(), "Creating T8027USB assertion for %@", detail);
preventIdleSleepAssertion(detail);
if (detail) {
CFRelease(detail);
}
}
exit:
return;
}
void IOHIDT8027USBSessionFilter::_unregisterService(void * self, IOHIDServiceRef service)
{
static_cast<IOHIDT8027USBSessionFilter *>(self)->unregisterService(service);
}
void IOHIDT8027USBSessionFilter::unregisterService(IOHIDServiceRef service)
{
require(_usbHIDServices.Reference(), exit);
if (_usbHIDServices.ContainValue(service)) {
_usbHIDServices.RemoveValue(service);
if (_usbHIDServices.Count() == 0) {
os_log(_HIDLog(), "Removing T8027USB assertion");
releaseIdleSleepAssertion();
}
}
exit:
return;
}
void IOHIDT8027USBSessionFilter::_scheduleWithDispatchQueue(void * self, dispatch_queue_t queue)
{
static_cast<IOHIDT8027USBSessionFilter *>(self)->scheduleWithDispatchQueue(queue);
}
void IOHIDT8027USBSessionFilter::scheduleWithDispatchQueue(dispatch_queue_t queue)
{
kern_return_t result;
_queue = queue;
_port = IONotificationPortCreate(kIOMasterPortDefault);
require(_port, exit);
result =
IOServiceAddMatchingNotification(_port,
kIOFirstPublishNotification,
IOServiceNameMatching("usb-device,t8027"),
&_serviceNotificationCallback,
this,
&_iterator);
require_noerr_action(result,
exit,
os_log_error(_HIDLog(), "%s adding matching notification 0x%x", __PRETTY_FUNCTION__, (int)result));
require(_iterator, exit);
IONotificationPortSetDispatchQueue(_port, _queue);
serviceNotificationCallback(_iterator);
exit:
return;
}
void IOHIDT8027USBSessionFilter::_serviceNotificationCallback (void * refcon, io_iterator_t iterator)
{
static_cast<IOHIDT8027USBSessionFilter *>(refcon)->serviceNotificationCallback(iterator);
}
void IOHIDT8027USBSessionFilter::serviceNotificationCallback(io_iterator_t iterator)
{
io_service_t service = IOIteratorNext(iterator);
require(service != IO_OBJECT_NULL, exit);
_hasT8027USB = true;
exit:
return;
}
void IOHIDT8027USBSessionFilter::_unscheduleFromDispatchQueue(void * self, dispatch_queue_t queue)
{
static_cast<IOHIDT8027USBSessionFilter *>(self)->unscheduleFromDispatchQueue(queue);
}
void IOHIDT8027USBSessionFilter::unscheduleFromDispatchQueue(dispatch_queue_t queue __unused)
{
if (_assertionID) {
releaseIdleSleepAssertion();
}
if (_iterator) {
IOObjectRelease(_iterator);
_iterator = IO_OBJECT_NULL;
}
if (_port) {
IONotificationPortDestroy(_port);
_port = NULL;
}
}
IOHIDEventRef IOHIDT8027USBSessionFilter::_filter(void * self, IOHIDServiceRef sender, IOHIDEventRef event)
{
return static_cast<IOHIDT8027USBSessionFilter *>(self)->filter(sender, event);
}
IOHIDEventRef IOHIDT8027USBSessionFilter::filter(IOHIDServiceRef sender, IOHIDEventRef event)
{
require(_usbHIDServices.Reference(), exit);
require_quiet(event && sender, exit);
require_quiet(IOHIDEventConformsTo(event, kIOHIDEventTypeKeyboard), exit);
require_quiet(IOHIDEventGetIntegerValue(event, kIOHIDEventFieldKeyboardDown), exit);
require_quiet(_usbHIDServices.ContainValue(sender), exit);
if (_assertionID) {
os_log_info(_HIDLog(), "T8027USB HID activity");
preventIdleSleepAssertion();
}
exit:
return event;
}
CFTypeRef IOHIDT8027USBSessionFilter::_getPropertyForClient (void * self, CFStringRef key, CFTypeRef client)
{
return static_cast<IOHIDT8027USBSessionFilter *>(self)->getPropertyForClient(key,client);
}
CFTypeRef IOHIDT8027USBSessionFilter::getPropertyForClient(CFStringRef key, CFTypeRef client __unused)
{
CFTypeRef result = NULL;
if (CFEqual(key, CFSTR(kIOHIDSessionFilterDebugKey))) {
CFMutableDictionaryRefWrap serializer;
serialize(serializer);
if (serializer) {
result = CFRetain(serializer.Reference());
}
}
return result;
}
void IOHIDT8027USBSessionFilter::_setPropertyForClient (void * self, CFStringRef key, CFTypeRef property, CFTypeRef client)
{
static_cast<IOHIDT8027USBSessionFilter *>(self)->setPropertyForClient(key, property, client);
}
void IOHIDT8027USBSessionFilter::setPropertyForClient (CFStringRef key, CFTypeRef property, CFTypeRef client __unused)
{
if (CFEqual(key, CFSTR(kAssertionTimeoutKey))) {
uint64_t timeout;
os_log(_HIDLog(), "Setting T8027 USB assertion timeout from %llu to %@", _assertionTimeout, (property ? property : CFSTR("")));
require(property && CFGetTypeID(property) == CFNumberGetTypeID(), exit);
CFNumberGetValue((CFNumberRef)property, kCFNumberSInt64Type, &timeout);
_assertionTimeout = timeout;
} else if (CFEqual(key, CFSTR(kSetAssertionKey))) {
os_log(_HIDLog(), "Setting T8027 USB assertion state %@", (property ? property : CFSTR("")));
if (property == kCFBooleanTrue) {
preventIdleSleepAssertion(CFSTR("SetProperty"));
} else {
if (_assertionID) {
releaseIdleSleepAssertion();
}
}
}
exit:
return;
}
void IOHIDT8027USBSessionFilter::serialize (CFMutableDictionaryRef dict) const {
CFMutableDictionaryRefWrap serializer (dict);
serializer.SetValueForKey(CFSTR("Class"), CFSTR("IOHIDT8027USBSessionFilter"));
serializer.SetValueForKey(CFSTR("HasT8027USB"), (_hasT8027USB ? kCFBooleanTrue : kCFBooleanFalse));
serializer.SetValueForKey(CFSTR("AssertionTimeout"), CFNumberRefWrap(_assertionTimeout));
serializer.SetValueForKey(CFSTR("Asserting"), (_asserting ? kCFBooleanTrue : kCFBooleanFalse));
serializer.SetValueForKey(CFSTR("USBHIDServiceCount"), _usbHIDServices.Count());
if (_assertionID) {
CFDictionaryRef pmDict = IOPMAssertionCopyProperties(_assertionID);
if (pmDict) {
serializer.SetValueForKey(CFSTR("AssertionProperties"), pmDict);
CFRelease(pmDict);
}
}
}
void IOHIDT8027USBSessionFilter::preventIdleSleepAssertion(CFStringRef detail)
{
CFNumberRef num = NULL;
uint32_t on = kIOPMAssertionLevelOn;
IOReturn status;
if (_assertionID == 0) {
require(initTimer(), exit);
status = IOPMAssertionCreateWithDescription(kIOPMAssertPreventUserIdleSystemSleep,
IOHIDT8027USBSessionFilter::ASSERTION_NAME,
(detail ? detail : NULL),
NULL, NULL,
0.0, NULL,
&_assertionID);
require_noerr_action(status,
exit,
os_log_error(_HIDLog(), "%s error creating assertion 0x%x", __PRETTY_FUNCTION__, (int)status));
} else {
num = CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &on);
require(num, exit);
status = IOPMAssertionSetProperty(_assertionID, kIOPMAssertionLevelKey, num);
require_noerr_action(status,
exit,
os_log_error(_HIDLog(), "%s error turning on assertion 0x%x", __PRETTY_FUNCTION__, (int)status));
}
dispatch_source_set_timer(_timer,
dispatch_time(DISPATCH_TIME_NOW, _assertionTimeout * NSEC_PER_SEC),
DISPATCH_TIME_FOREVER,
0);
_asserting = true;
exit:
if (num) {
CFRelease(num);
}
return;
}
void IOHIDT8027USBSessionFilter::releaseIdleSleepAssertion()
{
IOReturn status;
if (_timer) {
dispatch_source_cancel(_timer);
_timer = NULL;
}
status = IOPMAssertionRelease(_assertionID);
require_noerr_action(status,
exit,
os_log_error(_HIDLog(), "%s error releasing assertion 0x%x", __PRETTY_FUNCTION__, (int)status));
exit:
_assertionID = 0;
_asserting = false;
return;
}
bool IOHIDT8027USBSessionFilter::initTimer()
{
dispatch_source_t timer = NULL;
bool ret = false;
require_action_quiet(!_timer, exit, ret = true);
require(_queue, exit);
timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, DISPATCH_TIMER_STRICT, _queue);
require(timer, exit);
dispatch_source_set_event_handler(timer, ^{
timerHandler();
});
dispatch_source_set_cancel_handler(timer, ^(void) {
dispatch_release(timer);
});
dispatch_source_set_timer(timer, DISPATCH_TIME_FOREVER, 0, 0);
dispatch_activate(timer);
_timer = timer;
ret = true;
exit:
if (!ret) {
os_log_error(_HIDLog(), "%s error", __PRETTY_FUNCTION__);
}
return ret;
}
void IOHIDT8027USBSessionFilter::timerHandler()
{
CFNumberRef num = NULL;
uint32_t off = kIOPMAssertionLevelOff;
IOReturn status;
require(_assertionID != 0, exit);
os_log(_HIDLog(), "T8027USB HID assertion timeout");
num = CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &off);
require(num, exit);
status = IOPMAssertionSetProperty(_assertionID, kIOPMAssertionLevelKey, num);
require_noerr_action(status,
exit,
os_log_error(_HIDLog(), "%s error turning off assertion 0x%x", __PRETTY_FUNCTION__, (int)status));
_asserting = false;
exit:
if (num) {
CFRelease(num);
}
return;
}