1/* 2 * IOHIDEventSystemStatistics.cpp 3 * IOHIDEventSystemPlugIns 4 * 5 * Created by Rob Yepez on 05/21/2013. 6 * Copyright 2013 Apple Inc. All rights reserved. 7 * 8 */ 9 10#include <new> 11#include <AggregateDictionary/ADClient.h> 12#include <CoreFoundation/CoreFoundation.h> 13#include <IOKit/hid/IOHIDSessionFilterPlugIn.h> 14#include <IOKit/hid/IOHIDEventSystemPrivate.h> 15#include <IOKit/hid/IOHIDEventTypes.h> 16#include <IOKit/hid/IOHIDEventData.h> 17#include <IOKit/hid/IOHIDSession.h> 18#include <IOKit/hid/IOHIDService.h> 19#include <IOKit/hid/IOHIDPrivateKeys.h> 20#include <IOKit/hid/IOHIDEventSystemKeys.h> 21#include <IOKit/hid/AppleHIDUsageTables.h> 22#include <IOKit/hid/IOHIDUsageTables.h> 23#include <IOKit/hid/IOHIDKeys.h> 24#include <notify.h> 25#include <pthread.h> 26#include <asl.h> 27#include <fcntl.h> 28#include <mach/mach.h> 29#include <mach/mach_time.h> 30#include "IOHIDEventSystemStatistics.h" 31 32 33#define kAggregateDictionaryKeyboardEnumerationCountKey "com.apple.iokit.hid.keyboard.enumerationCount" 34#define kAggregateDictionaryHomeButtonWakeCountKey "com.apple.iokit.hid.homeButton.wakeCount" 35#define kAggregateDictionaryPowerButtonWakeCountKey "com.apple.iokit.hid.powerButton.wakeCount" 36#define kAggregateDictionaryPowerButtonSleepCountKey "com.apple.iokit.hid.powerButton.sleepCount" 37 38#define kAggregateDictionaryPowerButtonPressedCountKey "com.apple.iokit.hid.powerButton.pressed" 39#define kAggregateDictionaryPowerButtonFilteredCountKey "com.apple.iokit.hid.powerButton.filtered" 40#define kAggregateDictionaryVolumeIncrementButtonPressedCountKey "com.apple.iokit.hid.volumeIncrementButton.pressed" 41#define kAggregateDictionaryVolumeIncrementButtonFilteredCountKey "com.apple.iokit.hid.volumeIncrementButton.filtered" 42#define kAggregateDictionaryVolumeDecrementButtonPressedCountKey "com.apple.iokit.hid.volumeDecrementButton.pressed" 43#define kAggregateDictionaryVolumeDecrementButtonFilteredCountKey "com.apple.iokit.hid.volumeDecrementButton.filtered" 44 45 46// 072BC077-E984-4C2A-BB72-D4769CE44FAF 47#define kIOHIDEventSystemStatisticsFactory CFUUIDGetConstantUUIDWithBytes(kCFAllocatorSystemDefault, 0x07, 0x2B, 0xC0, 0x77, 0xE9, 0x84, 0x4C, 0x2A, 0xBB, 0x72, 0xD4, 0x76, 0x9C, 0xE4, 0x4F, 0xAF) 48 49#define kStringLength 128 50 51extern "C" void * IOHIDEventSystemStatisticsFactory(CFAllocatorRef allocator, CFUUIDRef typeUUID); 52static mach_timebase_info_data_t sTimebaseInfo; 53 54static const char kButtonPower[] = "power/hold"; 55static const char kButtonVolumeIncrement[] = "volume_inc"; 56static const char kButtonVolumeDecrement[] = "volume_dec"; 57static const char kButtonMenu[] = "menu/home"; 58 59//------------------------------------------------------------------------------ 60// IOHIDEventSystemStatisticsFactory 61//------------------------------------------------------------------------------ 62// Implementation of the factory function for this type. 63void *IOHIDEventSystemStatisticsFactory(CFAllocatorRef allocator __unused, CFUUIDRef typeUUID) 64{ 65 // If correct type is being requested, allocate an 66 // instance of TestType and return the IUnknown interface. 67 if (CFEqual(typeUUID, kIOHIDSessionFilterPlugInTypeID)) { 68 void *p = CFAllocatorAllocate(kCFAllocatorDefault, sizeof(IOHIDEventSystemStatistics), 0); 69 return new(p) IOHIDEventSystemStatistics(kIOHIDEventSystemStatisticsFactory); 70 } 71 // If the requested type is incorrect, return NULL. 72 return NULL; 73} 74 75// The IOHIDEventSystemStatistics function table. 76IOHIDSessionFilterPlugInInterface IOHIDEventSystemStatistics::sIOHIDEventSystemStatisticsFtbl = 77{ 78 // Required padding for COM 79 NULL, 80 // These three are the required COM functions 81 IOHIDEventSystemStatistics::QueryInterface, 82 IOHIDEventSystemStatistics::AddRef, 83 IOHIDEventSystemStatistics::Release, 84 // IOHIDSimpleSessionFilterPlugInInterface functions 85 IOHIDEventSystemStatistics::filter, 86 NULL, 87 NULL, 88 // IOHIDSessionFilterPlugInInterface functions 89 IOHIDEventSystemStatistics::open, 90 IOHIDEventSystemStatistics::close, 91 NULL, 92 NULL, 93 IOHIDEventSystemStatistics::registerService, 94 NULL, 95 IOHIDEventSystemStatistics::scheduleWithDispatchQueue, 96 IOHIDEventSystemStatistics::unscheduleFromDispatchQueue, 97 NULL, 98 NULL, 99}; 100 101//------------------------------------------------------------------------------ 102// IOHIDEventSystemStatistics::IOHIDEventSystemStatistics 103//------------------------------------------------------------------------------ 104IOHIDEventSystemStatistics::IOHIDEventSystemStatistics(CFUUIDRef factoryID) 105: 106_sessionInterface(&sIOHIDEventSystemStatisticsFtbl), 107_factoryID( static_cast<CFUUIDRef>( CFRetain(factoryID) ) ), 108_refCount(1), 109_displayState(1), 110_displayToken(0), 111_pending_source(0), 112_dispatch_queue(0), 113_logButtonFiltering(false), 114_logStrings(NULL), 115_logfd(-1), 116_asl(NULL) 117{ 118 bzero(&_pending_buttons, sizeof(_pending_buttons)); 119 CFPlugInAddInstanceForFactory( factoryID ); 120} 121//------------------------------------------------------------------------------ 122// IOHIDEventSystemStatistics::IOHIDEventSystemStatistics 123//------------------------------------------------------------------------------ 124IOHIDEventSystemStatistics::~IOHIDEventSystemStatistics() 125{ 126 CFPlugInRemoveInstanceForFactory( _factoryID ); 127 CFRelease( _factoryID ); 128} 129//------------------------------------------------------------------------------ 130// IOHIDEventSystemStatistics::QueryInterface 131//------------------------------------------------------------------------------ 132HRESULT IOHIDEventSystemStatistics::QueryInterface( void *self, REFIID iid, LPVOID *ppv ) 133{ 134 return static_cast<IOHIDEventSystemStatistics *>(self)->QueryInterface(iid, ppv); 135} 136// Implementation of the IUnknown QueryInterface function. 137HRESULT IOHIDEventSystemStatistics::QueryInterface( REFIID iid, LPVOID *ppv ) 138{ 139 // Create a CoreFoundation UUIDRef for the requested interface. 140 CFUUIDRef interfaceID = CFUUIDCreateFromUUIDBytes( NULL, iid ); 141 // Test the requested ID against the valid interfaces. 142 if (CFEqual(interfaceID, kIOHIDSimpleSessionFilterPlugInInterfaceID) || CFEqual(interfaceID, kIOHIDSessionFilterPlugInInterfaceID)) { 143 AddRef(); 144 *ppv = this; 145 CFRelease(interfaceID); 146 return S_OK; 147 } 148 if (CFEqual(interfaceID, IUnknownUUID)) { 149 // If the IUnknown interface was requested, same as above. 150 AddRef(); 151 *ppv = this; 152 CFRelease(interfaceID); 153 return S_OK; 154 } 155 // Requested interface unknown, bail with error. 156 *ppv = NULL; 157 CFRelease( interfaceID ); 158 return E_NOINTERFACE; 159} 160//------------------------------------------------------------------------------ 161// IOHIDEventSystemStatistics::AddRef 162//------------------------------------------------------------------------------ 163ULONG IOHIDEventSystemStatistics::AddRef( void *self ) 164{ 165 return static_cast<IOHIDEventSystemStatistics *>(self)->AddRef(); 166} 167ULONG IOHIDEventSystemStatistics::AddRef() 168{ 169 _refCount += 1; 170 return _refCount; 171} 172//------------------------------------------------------------------------------ 173// IOHIDEventSystemStatistics::Release 174//------------------------------------------------------------------------------ 175ULONG IOHIDEventSystemStatistics::Release( void *self ) 176{ 177 return static_cast<IOHIDEventSystemStatistics *>(self)->Release(); 178} 179ULONG IOHIDEventSystemStatistics::Release() 180{ 181 _refCount -= 1; 182 if (_refCount == 0) { 183 delete this; 184 return 0; 185 } 186 return _refCount; 187} 188//------------------------------------------------------------------------------ 189// IOHIDEventSystemStatistics::open 190//------------------------------------------------------------------------------ 191boolean_t IOHIDEventSystemStatistics::open(void * self, IOHIDSessionRef session, IOOptionBits options) 192{ 193 return static_cast<IOHIDEventSystemStatistics *>(self)->open(session, options); 194} 195 196boolean_t IOHIDEventSystemStatistics::open(IOHIDSessionRef session, IOOptionBits options) 197{ 198 CFTypeRef bootArgs = nil; 199 io_registry_entry_t entry = IO_OBJECT_NULL; 200 201 (void)session; 202 (void)options; 203 204 entry = IORegistryEntryFromPath(kIOMasterPortDefault, "IODeviceTree:/options"); 205 if(entry){ 206 bootArgs = IORegistryEntryCreateCFProperty(entry, CFSTR("boot-args"), nil, 0); 207 if (bootArgs){ 208 if (CFGetTypeID(bootArgs) == CFStringGetTypeID()){ 209 CFRange findRange; 210 CFStringRef bootArgsString = (CFStringRef)bootArgs; 211 212 findRange = CFStringFind(bootArgsString, CFSTR("opposing-button-logging"), 0); 213 214 if (findRange.length != 0) 215 _logButtonFiltering = true; 216 } 217 CFRelease(bootArgs); 218 IOObjectRelease(entry); 219 } 220 } 221 222 if (_logButtonFiltering) { 223 _logStrings = CFArrayCreateMutable(kCFAllocatorDefault, 0, &kCFTypeArrayCallBacks); 224 _asl = asl_open("ButtonLogging", "Button Filtering Information", 0); 225 226 _logfd = ::open("/var/mobile/Library/Logs/button.log", O_CREAT | O_APPEND | O_RDWR, 0644); 227 228 if ((_logfd != -1) && (_asl != NULL)) 229 asl_add_log_file(_asl, _logfd); 230 } 231 232 return true; 233} 234 235//------------------------------------------------------------------------------ 236// IOHIDEventSystemStatistics::close 237//------------------------------------------------------------------------------ 238void IOHIDEventSystemStatistics::close(void * self, IOHIDSessionRef session, IOOptionBits options) 239{ 240 static_cast<IOHIDEventSystemStatistics *>(self)->close(session, options); 241} 242 243void IOHIDEventSystemStatistics::close(IOHIDSessionRef session, IOOptionBits options) 244{ 245 (void) session; 246 (void) options; 247 248 if (_logStrings) { 249 CFRelease(_logStrings); 250 _logStrings = NULL; 251 } 252 253 if (_asl) { 254 asl_close(_asl); 255 if (_logfd != -1) ::close(_logfd); 256 } 257} 258 259//------------------------------------------------------------------------------ 260// IOHIDEventSystemStatistics::registerService 261//------------------------------------------------------------------------------ 262void IOHIDEventSystemStatistics::registerService(void * self, IOHIDServiceRef service) 263{ 264 static_cast<IOHIDEventSystemStatistics *>(self)->registerService(service); 265} 266void IOHIDEventSystemStatistics::registerService(IOHIDServiceRef service) 267{ 268 if ( IOHIDServiceConformsTo(service, kHIDPage_GenericDesktop, kHIDUsage_GD_Keyboard) ) 269 { 270 ADClientAddValueForScalarKey(CFSTR(kAggregateDictionaryKeyboardEnumerationCountKey), 1); 271 } 272} 273 274//------------------------------------------------------------------------------ 275// IOHIDEventSystemStatistics::scheduleWithDispatchQueue 276//------------------------------------------------------------------------------ 277void IOHIDEventSystemStatistics::scheduleWithDispatchQueue(void * self, dispatch_queue_t queue) 278{ 279 static_cast<IOHIDEventSystemStatistics *>(self)->scheduleWithDispatchQueue(queue); 280} 281void IOHIDEventSystemStatistics::scheduleWithDispatchQueue(dispatch_queue_t queue) 282{ 283 _dispatch_queue = queue; 284 285 if ( _dispatch_queue ) { 286 _pending_source = dispatch_source_create(DISPATCH_SOURCE_TYPE_DATA_ADD, 0, 0, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0)); 287 dispatch_set_context(_pending_source, this); 288 dispatch_source_set_event_handler_f(_pending_source, IOHIDEventSystemStatistics::handlePendingStats); 289 dispatch_resume(_pending_source); 290 291 notify_register_dispatch( "com.apple.iokit.hid.displayStatus", &_displayToken,_dispatch_queue, ^(__unused int token){ 292 293 notify_get_state(_displayToken, &_displayState); 294 }); 295 } 296} 297 298//------------------------------------------------------------------------------ 299// IOHIDEventSystemStatistics::unscheduleFromDispatchQueue 300//------------------------------------------------------------------------------ 301void IOHIDEventSystemStatistics::unscheduleFromDispatchQueue(void * self, dispatch_queue_t queue) 302{ 303 static_cast<IOHIDEventSystemStatistics *>(self)->unscheduleFromDispatchQueue(queue); 304} 305void IOHIDEventSystemStatistics::unscheduleFromDispatchQueue(dispatch_queue_t queue) 306{ 307 if ( _dispatch_queue != queue ) 308 return; 309 310 if ( _pending_source ) { 311 dispatch_release(_pending_source); 312 _pending_source = NULL; 313 } 314} 315 316//------------------------------------------------------------------------------ 317// IOHIDEventSystemStatistics::handlePendingStats 318//------------------------------------------------------------------------------ 319void IOHIDEventSystemStatistics::handlePendingStats(void * self) 320{ 321 return static_cast<IOHIDEventSystemStatistics *>(self)->handlePendingStats(); 322} 323 324void IOHIDEventSystemStatistics::handlePendingStats() 325{ 326 __block Buttons buttons = {}; 327 __block CFArrayRef logStrings = NULL; 328 329 dispatch_sync(_dispatch_queue, ^{ 330 bcopy(&_pending_buttons, &buttons, sizeof(Buttons)); 331 bzero(&_pending_buttons, sizeof(Buttons)); 332 333 if (_logStrings) { 334 logStrings = CFArrayCreateCopy(kCFAllocatorDefault, _logStrings); 335 CFArrayRemoveAllValues(_logStrings); 336 } 337 }); 338 339 ADClientAddValueForScalarKey(CFSTR(kAggregateDictionaryHomeButtonWakeCountKey), buttons.home_wake); 340 ADClientAddValueForScalarKey(CFSTR(kAggregateDictionaryPowerButtonWakeCountKey), buttons.power_wake); 341 ADClientAddValueForScalarKey(CFSTR(kAggregateDictionaryPowerButtonSleepCountKey), buttons.power_sleep); 342 343 ADClientAddValueForScalarKey(CFSTR(kAggregateDictionaryPowerButtonPressedCountKey), buttons.power); 344 ADClientAddValueForScalarKey(CFSTR(kAggregateDictionaryPowerButtonFilteredCountKey), buttons.power_filtered); 345 346 ADClientAddValueForScalarKey(CFSTR(kAggregateDictionaryVolumeIncrementButtonPressedCountKey), buttons.volume_increment); 347 ADClientAddValueForScalarKey(CFSTR(kAggregateDictionaryVolumeIncrementButtonFilteredCountKey), buttons.volume_increment_filtered); 348 349 ADClientAddValueForScalarKey(CFSTR(kAggregateDictionaryVolumeDecrementButtonPressedCountKey), buttons.volume_decrement); 350 ADClientAddValueForScalarKey(CFSTR(kAggregateDictionaryVolumeDecrementButtonFilteredCountKey), buttons.volume_decrement_filtered); 351 352 if (logStrings) { 353 for (int i = 0; i < CFArrayGetCount(logStrings); i++) { 354 CFStringRef cfstr; 355 const char * cstr; 356 357 cfstr = (CFStringRef) CFArrayGetValueAtIndex(logStrings, i); 358 if (!cfstr) continue; 359 360 cstr = CFStringGetCStringPtr(cfstr, kCFStringEncodingUTF8); 361 if (!cstr) continue; 362 363 asl_log(_asl, NULL, ASL_LEVEL_NOTICE, "%s", cstr); 364 } 365 366 CFRelease(logStrings); 367 } 368} 369 370//------------------------------------------------------------------------------ 371// IOHIDEventSystemStatistics::filter 372//------------------------------------------------------------------------------ 373IOHIDEventRef IOHIDEventSystemStatistics::filter(void * self, IOHIDServiceRef sender, IOHIDEventRef event) 374{ 375 return static_cast<IOHIDEventSystemStatistics *>(self)->filter(sender, event); 376} 377IOHIDEventRef IOHIDEventSystemStatistics::filter(IOHIDServiceRef sender, IOHIDEventRef event) 378{ 379 const char * button; 380 uint64_t ts; 381 float secs; 382 383 (void) sender; 384 385 if ( _pending_source ) { 386 if ( event ) { 387 bool signal = false; 388 389 if ((IOHIDEventGetType(event) == kIOHIDEventTypeKeyboard) 390 && IOHIDEventGetIntegerValue(event, kIOHIDEventFieldKeyboardDown) 391 && (IOHIDEventGetIntegerValue(event, kIOHIDEventFieldKeyboardUsagePage) == kHIDPage_Consumer)) { 392 393 signal = true; 394 395 switch (IOHIDEventGetIntegerValue(event, kIOHIDEventFieldKeyboardUsage) ) { 396 case kHIDUsage_Csmr_Menu: 397 button = kButtonMenu; 398 if ( !_displayState ) 399 _pending_buttons.home_wake++; 400 break; 401 case kHIDUsage_Csmr_Power: 402 _pending_buttons.power++; 403 button = kButtonPower; 404 if ( !_displayState ) 405 _pending_buttons.power_wake++; 406 else 407 _pending_buttons.power_sleep++; 408 break; 409 case kHIDUsage_Csmr_VolumeDecrement: 410 _pending_buttons.volume_decrement++; 411 button = kButtonVolumeDecrement; 412 break; 413 case kHIDUsage_Csmr_VolumeIncrement: 414 _pending_buttons.volume_increment++; 415 button = kButtonVolumeIncrement; 416 break; 417 default: 418 signal = false; 419 break; 420 } 421 422 if (signal && _logButtonFiltering) { 423 if ( sTimebaseInfo.denom == 0 ) { 424 (void) mach_timebase_info(&sTimebaseInfo); 425 } 426 ts = IOHIDEventGetTimeStamp(event); 427 ts = ts * sTimebaseInfo.numer / sTimebaseInfo.denom; 428 secs = (float)ts / NSEC_PER_SEC; 429 430 CFStringRef str = CFStringCreateWithFormat(kCFAllocatorDefault, 431 0, 432 CFStringCreateWithCString(kCFAllocatorDefault, 433 "ts=%0.9f,action=down,button=%s", 434 kCFStringEncodingUTF8), 435 secs, 436 button ? button : "unknown"); 437 438 if (str) { 439 CFArrayAppendValue(_logStrings, str); 440 CFRelease(str); 441 } 442 } 443 } 444 else if ((IOHIDEventGetType(event) == kIOHIDEventTypeVendorDefined) 445 && (IOHIDEventGetIntegerValue(event, kIOHIDEventFieldVendorDefinedUsagePage) == kHIDPage_AppleVendorFilteredEvent) 446 && (IOHIDEventGetIntegerValue(event, kIOHIDEventFieldVendorDefinedUsage) == kIOHIDEventTypeKeyboard)) { 447 448 CFIndex dataLength = IOHIDEventGetIntegerValue(event, kIOHIDEventFieldVendorDefinedDataLength); 449 if ( dataLength >= sizeof(IOHIDKeyboardEventData)) { 450 IOHIDKeyboardEventData * data = (IOHIDKeyboardEventData*)IOHIDEventGetDataValue(event, kIOHIDEventFieldVendorDefinedData); 451 452 if ( data->usagePage == kHIDPage_Consumer && data->down ) { 453 454 signal = true; 455 456 switch ( data->usage ) { 457 case kHIDUsage_Csmr_Power: 458 _pending_buttons.power_filtered++; 459 button = kButtonPower; 460 break; 461 case kHIDUsage_Csmr_VolumeDecrement: 462 _pending_buttons.volume_decrement_filtered++; 463 button = kButtonVolumeDecrement; 464 break; 465 case kHIDUsage_Csmr_VolumeIncrement: 466 _pending_buttons.volume_increment_filtered++; 467 button = kButtonVolumeIncrement; 468 break; 469 default: 470 signal = false; 471 break; 472 } 473 } 474 475 if (signal && _logButtonFiltering) { 476 if ( sTimebaseInfo.denom == 0 ) { 477 (void) mach_timebase_info(&sTimebaseInfo); 478 } 479 480 ts = IOHIDEventGetTimeStamp(event); 481 ts = ts * sTimebaseInfo.numer / sTimebaseInfo.denom; 482 secs = (float)ts / NSEC_PER_SEC; 483 484 CFStringRef str = CFStringCreateWithFormat(kCFAllocatorDefault, 485 0, 486 CFStringCreateWithCString(kCFAllocatorDefault, 487 "ts=%0.9f,action=filtered,button=%s", 488 kCFStringEncodingUTF8) , 489 secs, 490 button ? button : "unknown"); 491 492 if (str) { 493 CFArrayAppendValue(_logStrings, str); 494 CFRelease(str); 495 } 496 } 497 } 498 } 499 500 if ( signal ) 501 dispatch_source_merge_data(_pending_source, 1); 502 } 503 504 } 505 506 return event; 507} 508