IOHIDEventServiceClass.cpp [plain text]
#include "IOHIDEventServiceClass.h"
#include "IOHIDEventServiceUserClient.h"
#include "IOHIDEventData.h"
#include <IOKit/hid/IOHIDUsageTables.h>
#include <IOKit/hid/IOHIDServiceKeys.h>
#include <IOKit/hid/IOHIDKeys.h>
#include <IOKit/hid/AppleEmbeddedHIDKeys.h>
__BEGIN_DECLS
#include <asl.h>
#include <mach/mach.h>
#include <mach/mach_interface.h>
#include <IOKit/iokitmig.h>
#include <IOKit/IOMessage.h>
#include <mach/mach_time.h>
__END_DECLS
#define QUEUE_LOCK(service) pthread_mutex_lock(&service->_queueLock)
#define QUEUE_UNLOCK(service) pthread_mutex_unlock(&service->_queueLock)
#define QUEUE_WAIT(service) while (service->_queueBusy) { pthread_cond_wait(&service->_queueCondition, &service->_queueLock); }
#define QUEUE_SIGNAL(service) pthread_cond_signal(&service->_queueCondition)
static IOReturn MergeDictionaries(CFDictionaryRef srcDict, CFMutableDictionaryRef * pDstDict);
IOCFPlugInInterface IOHIDEventServiceClass::sIOCFPlugInInterfaceV1 =
{
0,
&IOHIDIUnknown::genericQueryInterface,
&IOHIDIUnknown::genericAddRef,
&IOHIDIUnknown::genericRelease,
1, 0, &IOHIDEventServiceClass::_probe,
&IOHIDEventServiceClass::_start,
&IOHIDEventServiceClass::_stop
};
IOHIDServiceInterface2 IOHIDEventServiceClass::sIOHIDServiceInterface2 =
{
0,
&IOHIDIUnknown::genericQueryInterface,
&IOHIDIUnknown::genericAddRef,
&IOHIDIUnknown::genericRelease,
&IOHIDEventServiceClass::_open,
&IOHIDEventServiceClass::_close,
&IOHIDEventServiceClass::_getProperty,
&IOHIDEventServiceClass::_setProperty,
&IOHIDEventServiceClass::_setEventCallback,
&IOHIDEventServiceClass::_scheduleWithRunLoop,
&IOHIDEventServiceClass::_unscheduleFromRunLoop,
&IOHIDEventServiceClass::_copyEvent,
&IOHIDEventServiceClass::_setElementValue
};
IOHIDEventServiceClass::IOHIDEventServiceClass() : IOHIDIUnknown(&sIOCFPlugInInterfaceV1)
{
_hidService.pseudoVTable = (IUnknownVTbl *) &sIOHIDServiceInterface2;
_hidService.obj = this;
_service = MACH_PORT_NULL;
_connect = MACH_PORT_NULL;
_isOpen = FALSE;
_asyncPort = MACH_PORT_NULL;
_asyncCFMachPort = NULL;
_asyncEventSource = NULL;
_serviceProperties = NULL;
_dynamicServiceProperties = NULL;
_servicePreferences = NULL;
_eventCallback = NULL;
_eventTarget = NULL;
_eventRefcon = NULL;
_queueMappedMemory = NULL;
_queueMappedMemorySize = 0;
_queueBusy = FALSE;
pthread_mutex_init(&_queueLock, NULL);
pthread_cond_init(&_queueCondition, NULL);
}
IOHIDEventServiceClass::~IOHIDEventServiceClass()
{
QUEUE_LOCK(this);
QUEUE_WAIT(this);
if (_queueMappedMemory)
{
#if !__LP64__
vm_address_t mappedMem = (vm_address_t)_queueMappedMemory;
#else
mach_vm_address_t mappedMem = (mach_vm_address_t)_queueMappedMemory;
#endif
IOConnectUnmapMemory ( _connect,
0,
mach_task_self(),
mappedMem);
_queueMappedMemory = NULL;
_queueMappedMemorySize = 0;
}
QUEUE_UNLOCK(this);
if (_connect) {
IOServiceClose(_connect);
_connect = MACH_PORT_NULL;
}
if (_service) {
IOObjectRelease(_service);
_service = MACH_PORT_NULL;
}
if (_serviceProperties) {
CFRelease(_serviceProperties);
_serviceProperties = NULL;
}
if (_dynamicServiceProperties) {
CFRelease(_dynamicServiceProperties);
_dynamicServiceProperties = NULL;
}
if (_servicePreferences) {
CFRelease(_servicePreferences);
_servicePreferences = NULL;
}
if (_asyncEventSource) {
CFRelease(_asyncEventSource);
_asyncEventSource = NULL;
}
if ( _asyncCFMachPort ) {
CFMachPortInvalidate(_asyncCFMachPort);
CFRelease(_asyncCFMachPort);
}
if ( _asyncPort ) {
mach_port_mod_refs(mach_task_self(), _asyncPort, MACH_PORT_RIGHT_RECEIVE, -1);
_asyncPort = MACH_PORT_NULL;
}
pthread_mutex_destroy(&_queueLock);
pthread_cond_destroy(&_queueCondition);
}
IOReturn IOHIDEventServiceClass::_probe(void *self, CFDictionaryRef propertyTable, io_service_t service, SInt32 *order)
{
return getThis(self)->probe(propertyTable, service, order);
}
IOReturn IOHIDEventServiceClass::_start(void *self, CFDictionaryRef propertyTable, io_service_t service)
{
return getThis(self)->start(propertyTable, service);
}
IOReturn IOHIDEventServiceClass::_stop(void *self)
{
return getThis(self)->stop();
}
boolean_t IOHIDEventServiceClass::_open(void * self, IOOptionBits options)
{
return getThis(self)->open(options);
}
void IOHIDEventServiceClass::_close(void * self, IOOptionBits options)
{
getThis(self)->close(options);
}
CFTypeRef IOHIDEventServiceClass::_getProperty(void * self, CFStringRef key)
{
return getThis(self)->getProperty(key);
}
boolean_t IOHIDEventServiceClass::_setProperty(void * self, CFStringRef key, CFTypeRef property)
{
return getThis(self)->setProperty(key, property);
}
IOHIDEventRef IOHIDEventServiceClass::_copyEvent(void *self, IOHIDEventType type, IOHIDEventRef matching, IOOptionBits options)
{
return getThis(self)->copyEvent(type, matching, options);
}
void IOHIDEventServiceClass::_setElementValue(void *self, uint32_t usagePage, uint32_t usage, uint32_t value)
{
return getThis(self)->setElementValue(usagePage, usage, value);
}
void IOHIDEventServiceClass::_setEventCallback(void * self, IOHIDServiceEventCallback callback, void * target, void * refcon)
{
getThis(self)->setEventCallback(callback, target, refcon);
}
void IOHIDEventServiceClass::_scheduleWithRunLoop(void *self, CFRunLoopRef runLoop, CFStringRef runLoopMode)
{
return getThis(self)->scheduleWithRunLoop(runLoop, runLoopMode);
}
void IOHIDEventServiceClass::_unscheduleFromRunLoop(void *self, CFRunLoopRef runLoop, CFStringRef runLoopMode)
{
return getThis(self)->unscheduleFromRunLoop(runLoop, runLoopMode);
}
void IOHIDEventServiceClass::_queueEventSourceCallback(
CFMachPortRef cfPort,
mach_msg_header_t * msg,
CFIndex size,
void * info)
{
IOHIDEventServiceClass *eventService = (IOHIDEventServiceClass *)info;
IOReturn ret = kIOReturnSuccess;
QUEUE_LOCK(eventService);
QUEUE_WAIT(eventService);
eventService->_queueBusy = TRUE;
do {
if ( !eventService->_queueMappedMemory )
break;
IODataQueueEntry * nextEntry;
uint32_t dataSize;
CFDataRef data;
while ((nextEntry = IODataQueuePeek(eventService->_queueMappedMemory))) {
if ( cfPort ) {
data = CFDataCreateWithBytesNoCopy(kCFAllocatorDefault, (const UInt8*)&(nextEntry->data), nextEntry->size, kCFAllocatorNull);
if (data) {
IOHIDEventRef event = IOHIDEventCreateWithData(kCFAllocatorDefault, data);
if ( event ) {
QUEUE_UNLOCK(eventService);
eventService->dispatchHIDEvent(event);
QUEUE_LOCK(eventService);
CFRelease(event);
}
CFRelease(data);
}
}
dataSize = 0;
IODataQueueDequeue(eventService->_queueMappedMemory, NULL, &dataSize);
}
} while ( 0 );
eventService->_queueBusy = FALSE;
QUEUE_UNLOCK(eventService);
QUEUE_SIGNAL(eventService);
}
void IOHIDEventServiceClass::dispatchHIDEvent(IOHIDEventRef event, IOOptionBits options)
{
if ( !_eventCallback )
return;
(*_eventCallback)(_eventTarget, _eventRefcon, (void *)&_hidService, event, options);
}
IOCFPlugInInterface ** IOHIDEventServiceClass::alloc()
{
IOHIDEventServiceClass * self = new IOHIDEventServiceClass;
return self ? (IOCFPlugInInterface **) &self->iunknown.pseudoVTable : NULL;
}
HRESULT IOHIDEventServiceClass::queryInterface(REFIID iid, void **ppv)
{
CFUUIDRef uuid = CFUUIDCreateFromUUIDBytes(NULL, iid);
HRESULT res = S_OK;
if (CFEqual(uuid, IUnknownUUID) || CFEqual(uuid, kIOCFPlugInInterfaceID))
{
*ppv = &iunknown;
addRef();
}
else if (CFEqual(uuid, kIOHIDServiceInterfaceID) || CFEqual(uuid, kIOHIDServiceInterface2ID))
{
*ppv = &_hidService;
addRef();
}
else {
*ppv = 0;
}
if (!*ppv)
res = E_NOINTERFACE;
CFRelease(uuid);
return res;
}
IOReturn IOHIDEventServiceClass::probe(CFDictionaryRef propertyTable, io_service_t service, SInt32 * order)
{
if (!service || !IOObjectConformsTo(service, "IOHIDEventService"))
return kIOReturnBadArgument;
return kIOReturnSuccess;
}
#define GET_AND_SET_SERVICE_PROPERTY(reg,regKey,dict,propKey) \
{ \
CFTypeRef typeRef = IORegistryEntryCreateCFProperty(reg,regKey, kCFAllocatorDefault, kNilOptions);\
if (typeRef) \
{ \
CFDictionarySetValue(dict,propKey,typeRef); \
CFRelease(typeRef); \
} \
}
#define GET_AND_SET_PROPERTY(prop,regKey,dict,propKey) \
{ \
CFTypeRef typeRef = CFDictionaryGetValue(prop,regKey); \
if (typeRef) \
CFDictionarySetValue(dict,propKey,typeRef); \
}
IOReturn IOHIDEventServiceClass::start(CFDictionaryRef propertyTable, io_service_t service)
{
IOReturn ret = kIOReturnError;
HRESULT plugInResult = S_OK;
SInt32 score = 0;
CFMutableDictionaryRef serviceProps = NULL;
do {
_service = service;
IOObjectRetain(_service);
_serviceProperties = CFDictionaryCreateMutable(kCFAllocatorDefault, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
if ( !_serviceProperties ) {
ret = kIOReturnNoMemory;
break;
}
IORegistryEntryCreateCFProperties(service, &serviceProps, kCFAllocatorDefault, 0);
if ( !serviceProps )
break;
GET_AND_SET_PROPERTY(serviceProps, CFSTR(kIOHIDTransportKey), _serviceProperties, CFSTR(kIOHIDServiceTransportKey));
GET_AND_SET_PROPERTY(serviceProps, CFSTR(kIOHIDVendorIDKey), _serviceProperties, CFSTR(kIOHIDServiceVendorIDKey));
GET_AND_SET_PROPERTY(serviceProps, CFSTR(kIOHIDVendorIDSourceKey), _serviceProperties, CFSTR(kIOHIDServiceVendorIDSourceKey));
GET_AND_SET_PROPERTY(serviceProps, CFSTR(kIOHIDProductIDKey), _serviceProperties, CFSTR(kIOHIDServiceProductIDKey));
GET_AND_SET_PROPERTY(serviceProps, CFSTR(kIOHIDVersionNumberKey), _serviceProperties, CFSTR(kIOHIDServiceVersionNumberKey));
GET_AND_SET_PROPERTY(serviceProps, CFSTR(kIOHIDManufacturerKey), _serviceProperties, CFSTR(kIOHIDServiceManufacturerKey));
GET_AND_SET_PROPERTY(serviceProps, CFSTR(kIOHIDProductKey), _serviceProperties, CFSTR(kIOHIDServiceProductKey));
GET_AND_SET_PROPERTY(serviceProps, CFSTR(kIOHIDSerialNumberKey), _serviceProperties, CFSTR(kIOHIDServiceSerialNumberKey));
GET_AND_SET_PROPERTY(serviceProps, CFSTR(kIOHIDCountryCodeKey), _serviceProperties, CFSTR(kIOHIDServiceCountryCodeKey));
GET_AND_SET_PROPERTY(serviceProps, CFSTR(kIOHIDLocationIDKey), _serviceProperties, CFSTR(kIOHIDServiceLocationIDKey));
GET_AND_SET_PROPERTY(serviceProps, CFSTR(kIOHIDPrimaryUsagePageKey), _serviceProperties, CFSTR(kIOHIDServicePrimaryUsagePageKey));
GET_AND_SET_PROPERTY(serviceProps, CFSTR(kIOHIDPrimaryUsageKey), _serviceProperties, CFSTR(kIOHIDServicePrimaryUsageKey));
GET_AND_SET_PROPERTY(serviceProps, CFSTR(kIOHIDDeviceUsagePairsKey), _serviceProperties, CFSTR(kIOHIDServiceDeviceUsagePairsKey));
CFRelease(serviceProps);
ret = IOServiceOpen(_service, mach_task_self(), 0, &_connect);
if (ret != kIOReturnSuccess || !_connect)
break;
QUEUE_LOCK(this);
#if !__LP64__
vm_address_t address = nil;
vm_size_t size = 0;
#else
mach_vm_address_t address = nil;
mach_vm_size_t size = 0;
#endif
ret = IOConnectMapMemory ( _connect,
0,
mach_task_self(),
&address,
&size,
kIOMapAnywhere );
if (ret != kIOReturnSuccess)
return false;
_queueMappedMemory = (IODataQueueMemory *) address;
_queueMappedMemorySize = size;
if ( !_queueMappedMemory || !_queueMappedMemorySize )
break;
_queueBusy = FALSE;
QUEUE_UNLOCK(this);
QUEUE_SIGNAL(this);
return kIOReturnSuccess;
} while (0);
if ( _service ) {
IOObjectRelease(_service);
_service = NULL;
}
return ret;
}
IOReturn IOHIDEventServiceClass::stop()
{
return kIOReturnSuccess;
}
boolean_t IOHIDEventServiceClass::open(IOOptionBits options)
{
uint32_t len = 0;
uint64_t input = options;
IOReturn kr;
bool ret = true;
QUEUE_LOCK(this);
if ( !_isOpen ) {
do {
kr = IOConnectCallScalarMethod(_connect, kIOHIDEventServiceUserClientOpen, &input, 1, 0, &len);;
if ( kr != kIOReturnSuccess ) {
ret = false;
break;
}
_isOpen = true;
} while ( 0 );
}
QUEUE_UNLOCK(this);
return ret;
}
void IOHIDEventServiceClass::close(IOOptionBits options)
{
uint32_t len = 0;
uint64_t input = options;
QUEUE_LOCK(this);
if ( _isOpen ) {
(void) IOConnectCallScalarMethod(_connect, kIOHIDEventServiceUserClientClose, &input, 1, 0, &len);
QUEUE_UNLOCK(this);
if ( _eventCallback )
_queueEventSourceCallback(NULL, NULL, 0, this);
QUEUE_LOCK(this);
_isOpen = false;
}
QUEUE_UNLOCK(this);
}
CFTypeRef IOHIDEventServiceClass::getProperty(CFStringRef key)
{
CFTypeRef value = CFDictionaryGetValue(_serviceProperties, key);
if ( !value ) {
if ( !_dynamicServiceProperties &&
!(_dynamicServiceProperties = CFDictionaryCreateMutable(kCFAllocatorDefault, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks)))
return NULL;
value = IORegistryEntryCreateCFProperty(_service, key, kCFAllocatorDefault, kNilOptions);
if (value)
{
CFDictionarySetValue(_dynamicServiceProperties,key,value);
CFRelease(value);
}
}
return value;
}
CFDictionaryRef IOHIDEventServiceClass::createFixedProperties(CFDictionaryRef floatProperties)
{
CFMutableDictionaryRef newProperties;
CFIndex count, index;
count = CFDictionaryGetCount(floatProperties);
if ( !count )
return NULL;
newProperties = CFDictionaryCreateMutable(kCFAllocatorDefault, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
if ( !newProperties )
return NULL;
CFTypeRef values[count];
CFTypeRef keys[count];
CFDictionaryGetKeysAndValues(floatProperties, keys, values);
for ( index=0; index<count; index++) {
CFTypeRef value = values[index];
CFTypeRef newValue = NULL;
if ( (CFNumberGetTypeID() == CFGetTypeID(value)) && CFNumberIsFloatType((CFNumberRef)value) ) {
double floatValue = 0.0;
IOFixed fixedValue = 0;
CFNumberGetValue((CFNumberRef)value, kCFNumberDoubleType, &floatValue);
fixedValue = floatValue * 65535;
value = newValue = CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt32Type, &fixedValue);
}
CFDictionarySetValue(newProperties, keys[index], value);
if ( newValue )
CFRelease(newValue);
}
return newProperties;
}
boolean_t IOHIDEventServiceClass::setProperty(CFStringRef key, CFTypeRef property)
{
CFDictionaryRef floatProperties = NULL;
boolean_t retVal;
if ( CFEqual(CFSTR(kIOHIDAccelerometerShakeKey), key) && (CFDictionaryGetTypeID() == CFGetTypeID(property)) ) {
property = floatProperties = createFixedProperties((CFDictionaryRef)property);
}
retVal = (IORegistryEntrySetCFProperty(_service, key, property) == kIOReturnSuccess);
if ( floatProperties )
CFRelease(floatProperties);
return retVal;
}
IOHIDEventRef IOHIDEventServiceClass::copyEvent(IOHIDEventType eventType, IOHIDEventRef matching, IOOptionBits options)
{
const UInt8 * inputData = NULL;
size_t inputDataSize = 0;
UInt8 * outputData = NULL;
size_t outputDataSize = 0;
size_t eventDataSize = 0;
CFDataRef fieldData = NULL;
CFMutableDataRef eventData = NULL;
IOHIDEventRef event = NULL;
IOReturn ret = kIOReturnSuccess;
if ( matching ) {
fieldData = IOHIDEventCreateData(kCFAllocatorDefault, matching);
if ( fieldData ) {
inputData = CFDataGetBytePtr(fieldData);
inputDataSize = CFDataGetLength(fieldData);
}
}
do {
uint64_t input[2];
input[0] = eventType;
input[1] = options;
IOHIDEventGetQueueElementSize(eventType, outputDataSize);
if ( !outputDataSize )
break;
eventData = CFDataCreateMutable(kCFAllocatorDefault, outputDataSize);
if ( !eventData )
break;
outputData = CFDataGetMutableBytePtr(eventData);
if ( !outputData )
break;
CFDataSetLength(eventData, outputDataSize);
bzero(outputData, outputDataSize);
ret = IOConnectCallMethod(_connect, kIOHIDEventServiceUserClientCopyEvent, input, 2, inputData, inputDataSize, NULL, NULL, outputData, &outputDataSize);
if ( ret != kIOReturnSuccess || !outputDataSize)
break;
CFDataSetLength(eventData, outputDataSize);
event = IOHIDEventCreateWithData(kCFAllocatorDefault, eventData);
} while ( 0 );
if ( fieldData )
CFRelease(fieldData);
if ( eventData )
CFRelease(eventData);
return event;
}
void IOHIDEventServiceClass::setElementValue(uint32_t usagePage, uint32_t usage, uint32_t value)
{
uint64_t input[3] = {usagePage, usage, value};
IOConnectCallMethod(_connect, kIOHIDEventServiceUserClientSetElementValue, input, 3, NULL, 0, NULL, NULL, NULL, NULL);
}
void IOHIDEventServiceClass::setEventCallback(IOHIDServiceEventCallback callback, void * target, void * refcon)
{
_eventCallback = callback;
_eventTarget = target;
_eventRefcon = refcon;
}
void IOHIDEventServiceClass::scheduleWithRunLoop(CFRunLoopRef runLoop, CFStringRef runLoopMode)
{
if ( !_asyncPort ) {
IOReturn ret = IOCreateReceivePort(kOSNotificationMessageID, &_asyncPort);
if (kIOReturnSuccess != ret || !_asyncPort)
return;
ret = IOConnectSetNotificationPort(_connect, 0, _asyncPort, NULL);
if ( kIOReturnSuccess != ret )
return;
}
if ( !_asyncCFMachPort ) {
CFMachPortContext context;
Boolean shouldFreeInfo = FALSE;
context.version = 1;
context.info = this;
context.retain = NULL;
context.release = NULL;
context.copyDescription = NULL;
_asyncCFMachPort = CFMachPortCreateWithPort(NULL, _asyncPort,
(CFMachPortCallBack) IOHIDEventServiceClass::_queueEventSourceCallback,
&context, &shouldFreeInfo);
if ( shouldFreeInfo ) {
asl_log(NULL, NULL, ASL_LEVEL_ERR, "%s received an unexpected reused CFMachPort", __func__);
}
if (!_asyncCFMachPort)
return;
}
if ( !_asyncEventSource ) {
_asyncEventSource = CFMachPortCreateRunLoopSource(NULL, _asyncCFMachPort, 0);
if ( !_asyncEventSource )
return;
}
CFRunLoopAddSource(runLoop, _asyncEventSource, runLoopMode);
if ( _queueMappedMemory )
CFRunLoopSourceSignal(_asyncEventSource);
}
void IOHIDEventServiceClass::unscheduleFromRunLoop(CFRunLoopRef runLoop, CFStringRef runLoopMode)
{
CFRunLoopRemoveSource(runLoop, _asyncEventSource, runLoopMode);
}
IOReturn MergeDictionaries(CFDictionaryRef srcDict, CFMutableDictionaryRef * pDstDict)
{
uint32_t count;
CFTypeRef * values;
CFStringRef * keys;
if ( !pDstDict || !srcDict || !(count = CFDictionaryGetCount(srcDict)))
return kIOReturnBadArgument;
if ( !*pDstDict ||
!(*pDstDict = CFDictionaryCreateMutable(kCFAllocatorDefault, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks)))
return kIOReturnNoMemory;
values = (CFTypeRef *)malloc(sizeof(CFTypeRef) * count);
keys = (CFStringRef *)malloc(sizeof(CFStringRef) * count);
for ( uint32_t i=0; i<count; i++)
CFDictionarySetValue(*pDstDict, keys[i], values[i]);
return kIOReturnSuccess;
}