#include <AssertMacros.h>
#include <pthread.h>
#include <dispatch/dispatch.h>
#include <mach/mach.h>
#include <mach/mach_time.h>
#include <CoreFoundation/CFRuntime.h>
#include <CoreFoundation/CFBase.h>
#include <IOKit/IOCFPlugIn.h>
#include <IOKit/IOKitLib.h>
#include <IOKit/IOCFSerialize.h>
#include <IOKit/hid/IOHIDKeys.h>
#include <IOKit/hid/IOHIDResourceUserClient.h>
#include <IOKit/IODataQueueClient.h>
#include "IOHIDUserDevice.h"
#include "IOHIDDebugTrace.h"
#include <IOKit/IOKitLibPrivate.h>
#include <os/assumes.h>
#include <dispatch/private.h>
#include <os/state_private.h>
#include <mach/mach_time.h>
#if __has_include(<Rosetta/Rosetta.h>)
# include <Rosetta/Rosetta.h>
#endif
#include <IOKit/hid/IOHIDAnalytics.h>
#define IOHIDUDLogError(fmt, ...) os_log_error(_IOHIDLogCategory(kIOHIDLogCategoryUserDevice), "0x%llx: " fmt, device->regID, ##__VA_ARGS__)
#define IOHIDUDLog(fmt, ...) os_log(_IOHIDLogCategory(kIOHIDLogCategoryUserDevice), "0x%llx: " fmt, device->regID, ##__VA_ARGS__)
#define IOHIDUDLogInfo(fmt, ...) os_log_info(_IOHIDLogCategory(kIOHIDLogCategoryUserDevice), "0x%llx: " fmt, device->regID, ##__VA_ARGS__)
#define IOHIDUDLogDebug(fmt, ...) os_log_debug(_IOHIDLogCategory(kIOHIDLogCategoryUserDevice), "0x%llx: " fmt, device->regID, ##__VA_ARGS__)
IOHID_DYN_LINK_DYLIB(/usr/lib, Rosetta)
IOHID_DYN_LINK_FUNCTION(Rosetta, rosetta_is_current_process_translated, dyn_rosetta_is_current_process_translated, bool, false, (void), ())
IOHID_DYN_LINK_FUNCTION(Rosetta, rosetta_convert_to_rosetta_absolute_time, dyn_rosetta_convert_to_rosetta_absolute_time, uint64_t, system_time, (uint64_t system_time), (system_time))
IOHID_DYN_LINK_FUNCTION(Rosetta, rosetta_convert_to_system_absolute_time, dyn_rosetta_convert_to_system_absolute_time, uint64_t, rosetta_time, (uint64_t rosetta_time), (rosetta_time))
static IOHIDUserDeviceRef __IOHIDUserDeviceCreate(
CFAllocatorRef allocator,
CFAllocatorContext * context __unused,
IOOptionBits options);
static void __IOHIDUserDeviceExtRelease( CFTypeRef object );
static void __IOHIDUserDeviceIntRelease( CFTypeRef object );
static void __IOHIDUserDeviceRegister(void);
static void __IOHIDUserDeviceQueueCallback(CFMachPortRef port, void *msg, CFIndex size, void *info);
static void __IOHIDUserDeviceHandleReportAsyncCallback(void *refcon, IOReturn result);
static Boolean __IOHIDUserDeviceSetupAsyncSupport(IOHIDUserDeviceRef device);
static IOReturn __IOHIDUserDeviceStartDevice(IOHIDUserDeviceRef device, IOOptionBits options);
static void __IOHIDUserDeviceDestroyDevice(IOHIDUserDeviceRef device);
static CFStringRef __IOHIDUserDeviceCopyDebugDescription(CFTypeRef object);
static bool __IOHIDUserDeviceSetupAnalytics(IOHIDUserDeviceRef device);
static void __IOHIDUserDeviceUpdateUsageAnalytics(IOHIDUserDeviceRef device);
typedef struct __IOHIDUserDevice
{
IOHIDObjectBase hidBase;
io_service_t service;
io_connect_t connect;
io_service_t userDevice;
CFDictionaryRef properties;
IOOptionBits options;
os_state_handle_t stateHandler;
dispatch_queue_t stateQueue;
uint64_t queueCallbackTS;
uint64_t dequeueTS;
uint64_t regID;
CFRunLoopRef runLoop;
CFStringRef runLoopMode;
_Atomic uint32_t dispatchStateMask;
dispatch_queue_t dispatchQueue;
dispatch_mach_t dispatchMach;
dispatch_block_t cancelHandler;
struct {
CFMachPortRef port;
CFRunLoopSourceRef source;
IODataQueueMemory * data;
uint64_t size;
uint32_t lastTail;
CFTypeRef usageAnalytics;
} queue;
struct {
IONotificationPortRef port;
CFRunLoopSourceRef source;
IODataQueueMemory * data;
} async;
struct {
IOHIDUserDeviceReportCallback callback;
void * refcon;
} setReport, getReport;
struct {
IOHIDUserDeviceReportWithReturnLengthCallback callback;
void * refcon;
} getReportWithReturnLength;
IOHIDUserDeviceSetReportBlock setReportBlock;
IOHIDUserDeviceGetReportBlock getReportBlock;
struct {
uint32_t setreport;
uint32_t getreport;
uint32_t handlereport;
} statistics;
} __IOHIDUserDevice, *__IOHIDUserDeviceRef;
static const IOHIDObjectClass __IOHIDUserDeviceClass = {
{
_kCFRuntimeCustomRefCount, "IOHIDUserDevice", NULL, NULL, __IOHIDUserDeviceExtRelease, NULL, NULL, NULL, __IOHIDUserDeviceCopyDebugDescription, NULL, _IOHIDObjectExtRetainCount, NULL },
_IOHIDObjectIntRetainCount,
__IOHIDUserDeviceIntRelease
};
static pthread_once_t __deviceTypeInit = PTHREAD_ONCE_INIT;
static CFTypeID __kIOHIDUserDeviceTypeID = _kCFRuntimeNotATypeID;
static mach_port_t __masterPort = MACH_PORT_NULL;
typedef struct __IOHIDDeviceHandleReportAsyncContext {
IOHIDUserDeviceHandleReportAsyncCallback callback;
void * refcon;
} IOHIDDeviceHandleReportAsyncContext;
CFStringRef __IOHIDUserDeviceCopyDebugDescription(CFTypeRef object)
{
IOHIDUserDeviceRef device = (IOHIDUserDeviceRef) object;
return CFStringCreateWithFormat(CFGetAllocator(object), NULL, CFSTR("<IOHIDUserDeviceRef ref:%d/%d id:0x%llx stats:%d,%d,%d>"),
(int)device->hidBase.ref,
(int)device->hidBase.xref,
device->regID,
device->statistics.setreport,
device->statistics.getreport,
device->statistics.handlereport
);
}
void __IOHIDUserDeviceRegister(void)
{
IOMasterPort(bootstrap_port, &__masterPort);
__kIOHIDUserDeviceTypeID = _CFRuntimeRegisterClass(&__IOHIDUserDeviceClass.cfClass);
}
IOHIDUserDeviceRef __IOHIDUserDeviceCreate(
CFAllocatorRef allocator,
CFAllocatorContext * context __unused,
IOOptionBits options)
{
IOHIDUserDeviceRef device = NULL;
uint32_t size;
size = sizeof(__IOHIDUserDevice) - sizeof(CFRuntimeBase);
device = (IOHIDUserDeviceRef)_IOHIDObjectCreateInstance(allocator, IOHIDUserDeviceGetTypeID(), size, NULL);
if (!device)
return NULL;
device->options = options;
HIDDEBUGTRACE(kHID_UserDev_Create, device, 0, 0, 0);
return device;
}
void __IOHIDUserDeviceFinalizeStateHandler(void *context)
{
IOHIDUserDeviceRef device = (IOHIDUserDeviceRef)context;
_IOHIDObjectInternalRelease(device);
}
void __IOHIDUserDeviceDestroyDevice(IOHIDUserDeviceRef device)
{
IOHIDUDLog("Destroy: %@", device);
if (device->async.port) {
IONotificationPortDestroy(device->async.port);
device->async.port = NULL;
}
if (device->queue.data)
{
#if !__LP64__
vm_address_t mappedMem = (vm_address_t)device->queue.data;
#else
mach_vm_address_t mappedMem = (mach_vm_address_t)device->queue.data;
#endif
IOConnectUnmapMemory ( device->connect,
0,
mach_task_self(),
mappedMem);
device->queue.data = NULL;
}
if (device->connect) {
IOServiceClose(device->connect);
device->connect = 0;
}
if (device->service) {
IOObjectRelease(device->service);
device->service = 0;
}
}
void __IOHIDUserDeviceExtRelease( CFTypeRef object )
{
IOHIDUserDeviceRef device = (IOHIDUserDeviceRef)object;
HIDDEBUGTRACE(kHID_UserDev_Release, object, 0, 0, 0);
if (device->dispatchQueue) {
os_assert(device->dispatchStateMask == (kIOHIDDispatchStateActive | kIOHIDDispatchStateCancelled),
"Invalid dispatch state: 0x%x", device->dispatchStateMask);
}
if (device->stateHandler) {
os_state_remove_handler(device->stateHandler);
}
if (device->stateQueue) {
dispatch_set_context(device->stateQueue, device);
dispatch_set_finalizer_f(device->stateQueue, __IOHIDUserDeviceFinalizeStateHandler);
_IOHIDObjectInternalRetain(device);
dispatch_release(device->stateQueue);
}
if (device->queue.port) {
CFMachPortInvalidate(device->queue.port);
}
}
void __IOHIDUserDeviceIntRelease( CFTypeRef object )
{
IOHIDUserDeviceRef device = (IOHIDUserDeviceRef)object;
HIDDEBUGTRACE(kHID_UserDev_Release, object, 0, 0, 0);
__IOHIDUserDeviceDestroyDevice(device);
if (device->queue.source) {
CFRelease(device->queue.source);
device->queue.source = NULL;
}
if (device->queue.port) {
mach_port_mod_refs(mach_task_self(),
CFMachPortGetPort(device->queue.port),
MACH_PORT_RIGHT_RECEIVE,
-1);
CFRelease(device->queue.port);
device->queue.port = NULL;
}
if (device->properties) {
CFRelease(device->properties);
device->properties = NULL;
}
if (device->userDevice) {
IOObjectRelease(device->userDevice);
device->userDevice = 0;
}
if (device->queue.usageAnalytics) {
IOHIDAnalyticsEventCancel(device->queue.usageAnalytics);
CFRelease(device->queue.usageAnalytics);
device->queue.usageAnalytics = NULL;
}
if (device->setReportBlock) {
Block_release(device->setReportBlock);
}
if (device->getReportBlock) {
Block_release(device->getReportBlock);
}
}
io_service_t IOHIDUserDeviceCopyService(IOHIDUserDeviceRef device)
{
io_service_t service = IO_OBJECT_NULL;
if (device->userDevice) {
service = device->userDevice;
IOObjectRetain(service);
}
return service;
}
CFTypeID IOHIDUserDeviceGetTypeID(void)
{
if ( _kCFRuntimeNotATypeID == __kIOHIDUserDeviceTypeID )
pthread_once(&__deviceTypeInit, __IOHIDUserDeviceRegister);
return __kIOHIDUserDeviceTypeID;
}
IOReturn __IOHIDUserDeviceStartDevice(IOHIDUserDeviceRef device, IOOptionBits options)
{
CFDataRef data = NULL;
IOReturn kr;
uint64_t input = options;
data = IOCFSerialize(device->properties, 0);
require_action(data, error, kr=kIOReturnNoMemory);
kr = IOConnectCallMethod(device->connect, kIOHIDResourceDeviceUserClientMethodCreate, &input, 1, CFDataGetBytePtr(data), CFDataGetLength(data), NULL, NULL, NULL, NULL);
require_noerr(kr, error);
kr = IOConnectGetService(device->connect, &device->userDevice);
require_noerr(kr, error);
IORegistryEntryGetRegistryEntryID(device->userDevice, &device->regID);
IOHIDUDLog("Start: %@", device);
HIDDEBUGTRACE(kHID_UserDev_Start, device, options, device->regID, 0);
error:
if ( data ) {
CFRelease(data);
}
if (kr) {
IOHIDUDLogError("IOHIDUserDevice start failed:0x%x properties:%@", kr, device->properties);
}
return kr;
}
CFMutableDictionaryRef __IOHIDUserDeviceSerializeState(IOHIDUserDeviceRef device)
{
io_service_t service = IO_OBJECT_NULL;
uint64_t regID = 0;
CFMutableDictionaryRef state = NULL;
state = CFDictionaryCreateMutable(kCFAllocatorDefault,
0,
&kCFTypeDictionaryKeyCallBacks,
&kCFTypeDictionaryValueCallBacks);
require(state, exit);
service = IOHIDUserDeviceCopyService(device);
if (service) {
IORegistryEntryGetRegistryEntryID(service, ®ID);
}
CFDictionarySetValue(state, CFSTR("DispatchQueue"), device->dispatchQueue ? kCFBooleanTrue : kCFBooleanFalse);
CFDictionarySetValue(state, CFSTR("RunLoop"), device->runLoop ? kCFBooleanTrue : kCFBooleanFalse);
CFDictionarySetValue(state, CFSTR("Queue"), device->queue.data ? kCFBooleanTrue : kCFBooleanFalse);
CFDictionarySetValue(state, CFSTR("SetReportCallback"), device->setReport.callback ? kCFBooleanTrue : kCFBooleanFalse);
CFDictionarySetValue(state, CFSTR("GetReportCallback"), (device->getReport.callback || device->getReportWithReturnLength.callback) ? kCFBooleanTrue : kCFBooleanFalse);
_IOHIDDictionaryAddSInt64(state, CFSTR("RegistryID"), regID);
_IOHIDDictionaryAddSInt64(state, CFSTR("QueueCallbackTimestamp"), device->queueCallbackTS);
_IOHIDDictionaryAddSInt64(state, CFSTR("DequeueTimestamp"), device->dequeueTS);
_IOHIDDictionaryAddSInt64(state, CFSTR("SetReportCnt"), device->statistics.setreport);
_IOHIDDictionaryAddSInt64(state, CFSTR("GetReportCnt"), device->statistics.getreport);
_IOHIDDictionaryAddSInt64(state, CFSTR("HandleReportCnt"), device->statistics.handlereport);
exit:
if (service) {
IOObjectRelease(service);
}
return state;
}
os_state_data_t __IOHIDUserDeviceStateHandler(IOHIDUserDeviceRef device,
os_state_hints_t hints)
{
os_state_data_t stateData = NULL;
CFMutableDictionaryRef deviceState = NULL;
CFDataRef serializedDeviceState = NULL;
if (hints->osh_api != OS_STATE_API_FAULT &&
hints->osh_api != OS_STATE_API_REQUEST) {
return NULL;
}
deviceState = __IOHIDUserDeviceSerializeState(device);
require(deviceState, exit);
serializedDeviceState = CFPropertyListCreateData(kCFAllocatorDefault, deviceState, kCFPropertyListBinaryFormat_v1_0, 0, NULL);
require(serializedDeviceState, exit);
uint32_t serializedDeviceStateSize = (uint32_t)CFDataGetLength(serializedDeviceState);
stateData = calloc(1, OS_STATE_DATA_SIZE_NEEDED(serializedDeviceStateSize));
require(stateData, exit);
strlcpy(stateData->osd_title, "IOHIDUserDevice State", sizeof(stateData->osd_title));
stateData->osd_type = OS_STATE_DATA_SERIALIZED_NSCF_OBJECT;
stateData->osd_data_size = serializedDeviceStateSize;
CFDataGetBytes(serializedDeviceState, CFRangeMake(0, serializedDeviceStateSize), stateData->osd_data);
exit:
if (deviceState) {
CFRelease(deviceState);
}
if (serializedDeviceState) {
CFRelease(serializedDeviceState);
}
return stateData;
}
IOHIDUserDeviceRef IOHIDUserDeviceCreate(
CFAllocatorRef allocator,
CFDictionaryRef properties)
{
return IOHIDUserDeviceCreateWithOptions(allocator, properties, 0);
}
IOHIDUserDeviceRef IOHIDUserDeviceCreateWithProperties(CFAllocatorRef allocator,
CFDictionaryRef properties,
IOOptionBits options)
{
return IOHIDUserDeviceCreateWithOptions(allocator, properties, options);
}
IOHIDUserDeviceRef IOHIDUserDeviceCreateWithOptions(CFAllocatorRef allocator, CFDictionaryRef properties, IOOptionBits options)
{
IOHIDUserDeviceRef device = NULL;
IOHIDUserDeviceRef result = NULL;
kern_return_t kr;
require(properties, error);
device = __IOHIDUserDeviceCreate(allocator, NULL, options);
require(device, error);
device->properties = CFDictionaryCreateCopy(allocator, properties);
require(device->properties, error);
device->service = IOServiceGetMatchingService(__masterPort, IOServiceMatching("IOHIDResource"));
require_action (device->service, error, IOHIDUDLogError("IOHIDResource not found"));
kr = IOServiceOpen(device->service, mach_task_self(), kIOHIDResourceUserClientTypeDevice, &device->connect);
require_noerr_action(kr, error, IOHIDUDLogError("IOServiceOpen:0x%x", kr));
kr = __IOHIDUserDeviceStartDevice(device, device->options);
require_noerr_action(kr, error, IOHIDUDLogError("__IOHIDUserDeviceStartDevice:0x%x", kr));
device->stateQueue = dispatch_queue_create("IOHIDUserDeviceStateQueue", DISPATCH_QUEUE_SERIAL);
require(device->stateQueue, error);
device->stateHandler = os_state_add_handler(device->stateQueue,
^os_state_data_t(os_state_hints_t hints) {
return __IOHIDUserDeviceStateHandler(device, hints);
});
require(__IOHIDUserDeviceSetupAnalytics(device), error);
result = device;
CFRetain(result);
error:
if (device) {
CFRelease(device);
}
return result;
}
Boolean __IOHIDUserDeviceSetupAsyncSupport(IOHIDUserDeviceRef device)
{
Boolean result = false;
os_assert(!device->runLoop && !device->dispatchQueue, "Device already scheduled");
if ( !device->queue.data ) {
IOReturn ret;
#if !__LP64__
vm_address_t address = 0;
vm_size_t size = 0;
#else
mach_vm_address_t address = 0;
mach_vm_size_t size = 0;
#endif
ret = IOConnectMapMemory(device->connect, 0, mach_task_self(), &address, &size, kIOMapAnywhere);
require_noerr_action(ret, exit, result=false; IOHIDUDLogError("IOConnectMapMemory:0x%x", ret));
device->queue.data =(IODataQueueMemory * )address;
device->queue.size = (uint64_t)size;
}
if ( !device->queue.port ) {
mach_port_t port = IODataQueueAllocateNotificationPort();
if ( port != MACH_PORT_NULL ) {
CFMachPortContext context = {0, device, NULL, NULL, NULL};
device->queue.port = CFMachPortCreateWithPort(CFGetAllocator(device), port, __IOHIDUserDeviceQueueCallback, &context, FALSE);
}
}
require_action(device->queue.port, exit, result=false);
if ( !device->async.port ) {
device->async.port = IONotificationPortCreate(kIOMasterPortDefault);
}
require_action(device->async.port, exit, result=false);
result = true;
exit:
HIDDEBUGTRACE(kHID_UserDev_AsyncSupport, device, result, 0, 0);
return result;
}
void IOHIDUserDeviceScheduleWithRunLoop(IOHIDUserDeviceRef device, CFRunLoopRef runLoop, CFStringRef runLoopMode)
{
os_assert(__IOHIDUserDeviceSetupAsyncSupport(device));
device->runLoop = runLoop;
device->runLoopMode = runLoopMode;
if ( !device->queue.source ) {
device->queue.source = CFMachPortCreateRunLoopSource(CFGetAllocator(device), device->queue.port, 0);
if ( !device->queue.source )
return;
}
if ( !device->async.source ) {
device->async.source = IONotificationPortGetRunLoopSource(device->async.port);
if ( !device->async.source )
return;
}
CFRunLoopAddSource(runLoop, device->async.source, runLoopMode);
CFRunLoopAddSource(runLoop, device->queue.source, runLoopMode);
if (device->setReport.callback ||
device->getReport.callback ||
device->getReportWithReturnLength.callback) {
IOConnectSetNotificationPort(device->connect,
0,
CFMachPortGetPort(device->queue.port),
(uintptr_t)NULL);
}
if (device->options & kIOHIDUserDeviceCreateOptionStartWhenScheduled) {
IOConnectCallMethod(device->connect,
kIOHIDResourceDeviceUserClientMethodRegisterService,
NULL, 0, NULL, 0, NULL, NULL, NULL, NULL);
}
}
void IOHIDUserDeviceUnscheduleFromRunLoop(IOHIDUserDeviceRef device, CFRunLoopRef runLoop __unused, CFStringRef runLoopMode __unused)
{
HIDDEBUGTRACE(kHID_UserDev_Unschedule, device, 0, 0, 0);
os_assert(device->runLoop && !device->dispatchQueue,
"Unschedule failed queue: %p runLoop: %p", device->dispatchQueue, device->runLoop);
IOConnectSetNotificationPort(device->connect, 0, MACH_PORT_NULL, (uintptr_t)NULL);
CFRunLoopRemoveSource(device->runLoop, device->queue.source, device->runLoopMode);
CFRunLoopRemoveSource(device->runLoop, device->async.source, device->runLoopMode);
}
void IOHIDUserDeviceScheduleWithDispatchQueue(IOHIDUserDeviceRef device,
dispatch_queue_t queue)
{
IOHIDUserDeviceSetDispatchQueue(device, queue);
IOHIDUserDeviceActivate(device);
}
void IOHIDUserDeviceUnscheduleFromDispatchQueue(IOHIDUserDeviceRef device,
dispatch_queue_t queue __unused)
{
IOHIDUserDeviceCancel(device);
}
void IOHIDUserDeviceSetDispatchQueue(IOHIDUserDeviceRef device,
dispatch_queue_t queue)
{
HIDDEBUGTRACE(kHID_UserDev_ScheduleDispatch, device, 0, 0, 0);
os_assert(__IOHIDUserDeviceSetupAsyncSupport(device));
device->dispatchQueue = dispatch_queue_create_with_target("IOHIDUserDeviceDispatchQueue", DISPATCH_QUEUE_SERIAL, queue);
require(device->dispatchQueue, exit);
_IOHIDObjectInternalRetain(device);
device->dispatchMach = dispatch_mach_create("IOHIDDeviceDispatchMach", device->dispatchQueue,
^(dispatch_mach_reason_t reason,
dispatch_mach_msg_t message,
mach_error_t error __unused) {
switch (reason) {
case DISPATCH_MACH_MESSAGE_RECEIVED: {
size_t size = 0;
mach_msg_header_t *header = dispatch_mach_msg_get_msg(message, &size);
__IOHIDUserDeviceQueueCallback(device->queue.port, header, size, device);
break;
}
case DISPATCH_MACH_CANCELED: {
dispatch_release(device->dispatchMach);
device->dispatchMach = NULL;
__IOHIDUserDeviceDestroyDevice(device);
if (device->cancelHandler) {
(device->cancelHandler)();
Block_release(device->cancelHandler);
device->cancelHandler = NULL;
}
dispatch_release(device->dispatchQueue);
_IOHIDObjectInternalRelease(device);
break;
}
default:
break;
}
});
require_action(device->dispatchMach, exit, _IOHIDObjectInternalRelease(device));
exit:
return;
}
void IOHIDUserDeviceSetCancelHandler(IOHIDUserDeviceRef device, dispatch_block_t handler)
{
os_assert(!device->cancelHandler && handler);
device->cancelHandler = Block_copy(handler);
}
void IOHIDUserDeviceActivate(IOHIDUserDeviceRef device)
{
os_assert(device->dispatchQueue && !device->runLoop,
"Activate failed queue: %p runLoop: %p", device->dispatchQueue, device->runLoop);
if (atomic_fetch_or(&device->dispatchStateMask, kIOHIDDispatchStateActive) & kIOHIDDispatchStateActive) {
return;
}
IONotificationPortSetDispatchQueue(device->async.port, device->dispatchQueue);
if (device->setReport.callback ||
device->getReport.callback ||
device->getReportWithReturnLength.callback ||
device->setReportBlock ||
device->getReportBlock) {
IOConnectSetNotificationPort(device->connect,
0,
CFMachPortGetPort(device->queue.port),
(uintptr_t)NULL);
}
dispatch_mach_connect(device->dispatchMach, CFMachPortGetPort(device->queue.port), MACH_PORT_NULL, 0);
if (device->options & kIOHIDUserDeviceCreateOptionStartWhenScheduled) {
IOConnectCallMethod(device->connect,
kIOHIDResourceDeviceUserClientMethodRegisterService,
NULL, 0, NULL, 0, NULL, NULL, NULL, NULL);
}
}
void IOHIDUserDeviceCancel(IOHIDUserDeviceRef device)
{
HIDDEBUGTRACE(kHID_UserDev_UnscheduleDispatch, device, 0, 0, 0);
if (atomic_fetch_or(&device->dispatchStateMask, kIOHIDDispatchStateCancelled) & kIOHIDDispatchStateCancelled) {
return;
}
os_assert(device->dispatchQueue && !device->runLoop,
"Unschedule failed queue: %p runLoop: %p", device->dispatchQueue, device->runLoop);
IOConnectSetNotificationPort(device->connect,
0,
MACH_PORT_NULL,
(uintptr_t)NULL);
dispatch_mach_cancel(device->dispatchMach);
}
CFTypeRef IOHIDUserDeviceCopyProperty(IOHIDUserDeviceRef device,
CFStringRef key)
{
return IORegistryEntryCreateCFProperty(device->userDevice, key, kCFAllocatorDefault, 0);
}
Boolean IOHIDUserDeviceSetProperty(IOHIDUserDeviceRef device,
CFStringRef key,
CFTypeRef property)
{
return (IOConnectSetCFProperty(device->connect, key, property) == kIOReturnSuccess);
}
void IOHIDUserDeviceRegisterGetReportCallback(IOHIDUserDeviceRef device, IOHIDUserDeviceReportCallback callback, void * refcon)
{
device->getReport.callback = callback;
device->getReport.refcon = refcon;
if (device->getReport.callback && device->queue.port) {
IOConnectSetNotificationPort(device->connect,
0,
CFMachPortGetPort(device->queue.port),
(uintptr_t)NULL);
}
}
void IOHIDUserDeviceRegisterGetReportWithReturnLengthCallback(IOHIDUserDeviceRef device, IOHIDUserDeviceReportWithReturnLengthCallback callback, void * refcon)
{
device->getReportWithReturnLength.callback = callback;
device->getReportWithReturnLength.refcon = refcon;
if (device->getReportWithReturnLength.callback && device->queue.port) {
IOConnectSetNotificationPort(device->connect,
0,
CFMachPortGetPort(device->queue.port),
(uintptr_t)NULL);
}
}
void IOHIDUserDeviceRegisterSetReportCallback(IOHIDUserDeviceRef device, IOHIDUserDeviceReportCallback callback, void * refcon)
{
device->setReport.callback = callback;
device->setReport.refcon = refcon;
if (device->setReport.callback && device->queue.port) {
IOConnectSetNotificationPort(device->connect,
0,
CFMachPortGetPort(device->queue.port),
(uintptr_t)NULL);
}
}
void IOHIDUserDeviceRegisterGetReportBlock(IOHIDUserDeviceRef device,
IOHIDUserDeviceGetReportBlock block)
{
os_assert(!device->getReportBlock, "Get report block already set");
device->getReportBlock = Block_copy(block);
}
void IOHIDUserDeviceRegisterSetReportBlock(IOHIDUserDeviceRef device,
IOHIDUserDeviceSetReportBlock block)
{
os_assert(!device->setReportBlock, "Set report block already set");
device->setReportBlock = Block_copy(block);
}
#ifndef min
#define min(a, b) \
((a < b) ? a:b)
#endif
void __IOHIDUserDeviceQueueCallback(CFMachPortRef port __unused, void *msg __unused, CFIndex size __unused, void *info)
{
IOHIDUserDeviceRef device = (IOHIDUserDeviceRef)info;
kern_return_t kr;
HIDDEBUGTRACE(kHID_UserDev_QueueCallback, device, 0, 0, 0);
device->queueCallbackTS = mach_continuous_time();
if (!device->queue.data) {
return;
}
__IOHIDUserDeviceUpdateUsageAnalytics(device);
IODataQueueEntry * nextEntry;
uint32_t dataSize;
while ((nextEntry = IODataQueuePeek(device->queue.data))) {
IOHIDResourceDataQueueHeader * header = (IOHIDResourceDataQueueHeader*)&(nextEntry->data);
uint64_t response[kIOHIDResourceUserClientResponseIndexCount] = {kIOReturnUnsupported,header->token};
uint8_t * responseReport = NULL;
CFIndex responseLength = 0;
uint32_t reportID = header->reportID;
uint32_t reportFlags = header->reportFlags;
if ( header->direction == kIOHIDResourceReportDirectionOut ) {
CFIndex reportLength;
uint8_t * payload = ((uint8_t*)header)+sizeof(IOHIDResourceDataQueueHeader);
uint8_t * report = NULL;
if (reportFlags & kIOHIDResourceOOBReport) {
require_action(nextEntry->size >= sizeof(IOHIDResourceOOBReportInfo) + sizeof(IOHIDResourceDataQueueHeader),
exit,
IOHIDUDLogError("Packet size is to small for large report, but large report flag is set. reportFlags:%#x entrySize:%u",
reportFlags, (uint32_t)nextEntry->size));
IOHIDResourceOOBReportInfo *info = (void*)payload;
report = (uint8_t*)info->token;
reportLength = info->length;
} else {
report = payload;
reportLength = min(header->length, (nextEntry->size - sizeof(IOHIDResourceDataQueueHeader)));
}
++(device->statistics.setreport);
if ( device->setReport.callback ) {
HIDDEBUGTRACE(kHID_UserDev_SetReportCallback, device, 0, 0, 0);
response[kIOHIDResourceUserClientResponseIndexResult] = (*device->setReport.callback)(device->setReport.refcon, header->type, reportID, report, reportLength);
} else if (device->setReportBlock) {
response[kIOHIDResourceUserClientResponseIndexResult] = (device->setReportBlock)(header->type,
reportID,
report,
reportLength);
} else {
IOHIDUDLogInfo("set report not handled");
}
if (reportFlags & kIOHIDResourceOOBReport) {
uint64_t inputs[] = { ((IOHIDResourceOOBReportInfo*)payload)->token };
IOConnectCallMethod(device->connect, kIOHIDResourceDeviceUserClientMethodReleaseToken, inputs, 1, NULL, 0, NULL, NULL, NULL, NULL);
}
}
else if ( header->direction == kIOHIDResourceReportDirectionIn ) {
responseReport = (uint8_t *)malloc(header->length);
responseLength = header->length;
++(device->statistics.getreport);
if ( device->getReport.callback ) {
response[kIOHIDResourceUserClientResponseIndexResult] = (*device->getReport.callback)(device->getReport.refcon, header->type, reportID, responseReport, responseLength);
}
if ( device->getReportWithReturnLength.callback ) {
response[kIOHIDResourceUserClientResponseIndexResult] = (*device->getReportWithReturnLength.callback)(device->getReportWithReturnLength.refcon, header->type, reportID, responseReport, &responseLength);
} else if (device->getReportBlock) {
response[kIOHIDResourceUserClientResponseIndexResult] = (device->getReportBlock)(header->type,
reportID,
responseReport,
&responseLength);
}
if (!device->getReportWithReturnLength.callback &&
!device->getReport.callback &&
!device->getReportBlock) {
IOHIDUDLogInfo("get report not handled");
}
}
exit:
kr = IOConnectCallMethod(device->connect, kIOHIDResourceDeviceUserClientMethodPostReportResponse, response, sizeof(response)/sizeof(uint64_t), responseReport, responseLength, NULL, NULL, NULL, NULL);
if (kr) {
IOHIDUDLogError("kIOHIDResourceDeviceUserClientMethodPostReportResponse:%x", kr);
}
if ( responseReport )
free(responseReport);
dataSize = 0;
device->dequeueTS = mach_continuous_time();
IODataQueueDequeue(device->queue.data, NULL, &dataSize);
}
}
void __IOHIDUserDeviceHandleReportAsyncCallback(void *refcon, IOReturn result)
{
IOHIDDeviceHandleReportAsyncContext *pContext = (IOHIDDeviceHandleReportAsyncContext *)refcon;
HIDDEBUGTRACE(kHID_UserDev_HandleReportCallback, pContext, 0, 0, 0);
if (pContext->callback)
pContext->callback(pContext->refcon, result);
free(pContext);
}
IOReturn IOHIDUserDeviceHandleReportAsyncWithTimeStamp(IOHIDUserDeviceRef device, uint64_t timestamp, const uint8_t *report, CFIndex reportLength, IOHIDUserDeviceHandleReportAsyncCallback callback, void * refcon)
{
IOHIDDeviceHandleReportAsyncContext *pContext = malloc(sizeof(IOHIDDeviceHandleReportAsyncContext));
if (!pContext)
return kIOReturnNoMemory;
pContext->callback = callback;
pContext->refcon = refcon;
mach_port_t wakePort = MACH_PORT_NULL;
uint64_t asyncRef[kOSAsyncRef64Count];
wakePort = IONotificationPortGetMachPort(device->async.port);
asyncRef[kIOAsyncCalloutFuncIndex] = (uint64_t)(uintptr_t)__IOHIDUserDeviceHandleReportAsyncCallback;
asyncRef[kIOAsyncCalloutRefconIndex] = (uint64_t)(uintptr_t)pContext;
timestamp = dyn_rosetta_is_current_process_translated() ?
dyn_rosetta_convert_to_system_absolute_time(timestamp) : timestamp;
return IOConnectCallAsyncMethod(device->connect,
kIOHIDResourceDeviceUserClientMethodHandleReport, wakePort, asyncRef,
kOSAsyncRef64Count, ×tamp, 1, report, reportLength, NULL, NULL, NULL, NULL);
}
IOReturn IOHIDUserDeviceHandleReportWithTimeStamp(IOHIDUserDeviceRef device, uint64_t timestamp, const uint8_t * report, CFIndex reportLength)
{
IOReturn kr;
timestamp = dyn_rosetta_is_current_process_translated() ?
dyn_rosetta_convert_to_system_absolute_time(timestamp) : timestamp;
HIDDEBUGTRACE(kHID_UserDev_HandleReport, timestamp, device, reportLength, 0);
++(device->statistics.handlereport);
kr = IOConnectCallMethod(device->connect, kIOHIDResourceDeviceUserClientMethodHandleReport, ×tamp, 1, report, reportLength, NULL, NULL, NULL, NULL);
if (kr) {
IOHIDUDLogError("kIOHIDResourceDeviceUserClientMethodHandleReport:%x", kr);
}
return kr;
}
IOReturn IOHIDUserDeviceHandleReport(IOHIDUserDeviceRef device, const uint8_t * report, CFIndex reportLength)
{
return IOHIDUserDeviceHandleReportWithTimeStamp(device, mach_absolute_time(), report, reportLength);
}
IOReturn IOHIDUserDeviceHandleReportAsync(IOHIDUserDeviceRef device, const uint8_t * report, CFIndex reportLength, IOHIDUserDeviceHandleReportAsyncCallback callback, void * refcon)
{
return IOHIDUserDeviceHandleReportAsyncWithTimeStamp(device, mach_absolute_time(), report, reportLength, callback, refcon);
}
bool __IOHIDUserDeviceSetupAnalytics(IOHIDUserDeviceRef device)
{
bool result = false;
CFDictionaryRef eventDesc = NULL;
static CFStringRef keys[] = { CFSTR("staticSize"), CFSTR("queueType") };
CFTypeRef values[sizeof(keys)/sizeof(keys[0])] = {0};
IOHIDAnalyticsHistogramSegmentConfig analyticsConfig = {
.bucket_count = 8,
.bucket_width = 13,
.bucket_base = 0,
.value_normalizer = 1,
};
values[0] = CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt64Type, &device->queue.size);
values[1] = CFSTR("userDeviceQueue");
eventDesc = CFDictionaryCreate(kCFAllocatorDefault,
(const void **)keys,
(const void **)values,
sizeof(values)/sizeof(values[0]),
&kCFTypeDictionaryKeyCallBacks,
&kCFTypeDictionaryValueCallBacks);
require_action(eventDesc, exit, IOHIDUDLogError("Unable to create analytics description"));
device->queue.usageAnalytics = IOHIDAnalyticsHistogramEventCreate(CFSTR("com.apple.hid.queueUsage"), eventDesc, CFSTR("UsagePercent"), &analyticsConfig, 1);
require_action(device->queue.usageAnalytics, exit, IOHIDUDLogError("Unable to create queue analytics"));
IOHIDAnalyticsEventActivate(device->queue.usageAnalytics);
result = true;
exit:
if (eventDesc) {
CFRelease(eventDesc);
}
for (size_t i = 0; i < sizeof(keys)/sizeof(keys[0]); i++) {
if (values[i]) {
CFRelease(values[i]);
}
}
return result;
}
void __IOHIDUserDeviceUpdateUsageAnalytics(IOHIDUserDeviceRef device)
{
uint32_t head;
uint32_t tail;
uint64_t queueUsage;
require(device->queue.data, exit);
require(device->queue.usageAnalytics, exit);
head = (uint32_t)device->queue.data->head;
tail = (uint32_t)device->queue.data->tail;
if (tail == device->queue.lastTail) {
return;
}
if (head < tail) {
queueUsage = tail - head;
}
else {
queueUsage = device->queue.size - (head - tail);
}
queueUsage = (queueUsage * 100) / device->queue.size;
IOHIDAnalyticsHistogramEventSetIntegerValue(device->queue.usageAnalytics, queueUsage);
device->queue.lastTail = tail;
exit:
return;
}