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