AppleUserHIDDevice.cpp [plain text]
#include "AppleUserHIDDevice.h"
#include "IOHIDResourceUserClient.h"
#include "IOHIDKeys.h"
#include "IOHIDPrivateKeys.h"
#include "IOHIDParameter.h"
#include <IOKit/IOLib.h>
#include "IOHIDInterface.h"
#include <AssertMacros.h>
#include "IOHIDDebug.h"
#include <IOKit/IOCommand.h>
#include "IOHIDFamilyPrivate.h"
#include <IOKit/IOKitKeys.h>
#include <stdatomic.h>
#define kIOHIDDeviceDextEntitlement "com.apple.developer.driverkit.family.hid.device"
#define super IOHIDDevice
OSDefineMetaClassAndStructors(AppleUserHIDDevice, IOHIDDevice)
#pragma mark -
#pragma mark Methods
#define HIDDeviceLogFault(fmt, ...) HIDLogFault("%s:0x%llx " fmt "\n", getName(), getRegistryEntryID(), ##__VA_ARGS__)
#define HIDDeviceLogError(fmt, ...) HIDLogError("%s:0x%llx " fmt "\n", getName(), getRegistryEntryID(), ##__VA_ARGS__)
#define HIDDeviceLog(fmt, ...) HIDLog("%s:0x%llx " fmt "\n", getName(), getRegistryEntryID(), ##__VA_ARGS__)
#define HIDDeviceLogInfo(fmt, ...) HIDLogInfo("%s:0x%llx " fmt "\n", getName(), getRegistryEntryID(), ##__VA_ARGS__)
#define HIDDeviceLogDebug(fmt, ...) HIDLogDebug("%s:0x%llx " fmt "\n", getName(), getRegistryEntryID(), ##__VA_ARGS__)
#define _workLoop ivar->workLoop
#define _commandGate ivar->commandGate
#define _syncActions ivar->syncActions
#define _asyncActions ivar->asyncActions
#define _asyncActionsLock ivar->asyncActionsLock
#define _state ivar->state
#define _requestTimeout ivar->requestTimeout
#define _setReportCount ivar->setReportCount
#define _setReportFailCount ivar->setReportFailCount
#define _setReportTimeoutCount ivar->setReportTimeoutCount
#define _setReportTime ivar->setReportTime
#define _getReportCount ivar->getReportCount
#define _getReportFailCount ivar->getReportFailCount
#define _getReportTimeoutCount ivar->getReportTimeoutCount
#define _getReportTime ivar->getReportTime
#define _inputReportCount ivar->inputReportCount
#define _inputReportTime ivar->inputReportTime
#define kIOUserHIDDeviceTimeoutUS 5000000ULL
#define dispatch_workloop_sync_with_ret(b) \
_commandGate ? _commandGate->runActionBlock(^IOReturn{ \
if (isInactive()) { \
return kIOReturnOffline; \
}; \
b \
return kIOReturnSuccess; \
}) : kIOReturnOffline
enum {
kAppleUserHIDDeviceStateStarted = 1,
kAppleUserHIDDeviceStateStopped = 2,
kAppleUserHIDDeviceStateKRStart = 4,
kAppleUserHIDDeviceStateDKStart = 8
};
struct IOHIDReportCompletion {
IOMemoryDescriptor * report;
IOHIDCompletion completion;
IOReturn status;
bool releaseReport;
};
bool AppleUserHIDDevice::init (OSDictionary * dict)
{
bool ret = false;
require (super::init(dict), exit);
ivar = IONew(AppleUserHIDDevice::AppleUserHIDDevice_IVars, 1);
require (ivar, exit);
bzero(ivar, sizeof(AppleUserHIDDevice::AppleUserHIDDevice_IVars));
_syncActions = OSSet::withCapacity(2);
require_action (_syncActions, exit, HIDDeviceLogError("syncActions\n"));
_asyncActions = OSSet::withCapacity(1);
require_action (_asyncActions, exit, HIDDeviceLogError("asyncActions\n"));
_asyncActionsLock = IOLockAlloc();
require_action (_asyncActionsLock, exit, HIDDeviceLogError("asyncActionsLock\n"));
super::setProperty(kIOServiceDEXTEntitlementsKey, kIOHIDDeviceDextEntitlement);
super::setProperty(kIOHIDRegisterServiceKey, kOSBooleanFalse);
ret = true;
exit:
return ret;
}
IOService * AppleUserHIDDevice::probe(IOService *provider, SInt32 *score)
{
if (isSingleUser()) {
return NULL;
}
return super::probe(provider, score);
}
bool AppleUserHIDDevice::willTerminate( IOService *provider,
IOOptionBits options )
{
messageClients(kIOHIDDeviceWillTerminate);
return super::willTerminate(provider, options);
}
bool AppleUserHIDDevice::start(IOService * provider)
{
IOReturn ret = kIOReturnSuccess;
bool status = false;
bool dkStart = getProperty(kIOHIDDKStartKey) == kOSBooleanTrue;
HIDDeviceLog("start (state:0x%x)", _state);
bool krStart = ((atomic_fetch_or((_Atomic UInt32 *)&_state, kAppleUserHIDDeviceStateKRStart) & kAppleUserHIDDeviceStateKRStart) == 0);
if (dkStart &&
(atomic_fetch_or((_Atomic UInt32 *)&_state, kAppleUserHIDDeviceStateDKStart) & kAppleUserHIDDeviceStateDKStart)) {
HIDDeviceLogError("Attempt to do kernel start for device multiple times");
return kIOReturnError;
}
if (!dkStart) {
ret = Start(provider);
require_noerr_action(ret, exit, HIDDeviceLogError("IOHIDDevice::Start:0x%x\n", ret));
} else if (krStart) {
status = super::start(provider);
require_action(status, exit, HIDDeviceLogError("super::start:0x%x\n", ret));
} else {
return super::start(provider);
}
atomic_fetch_or((_Atomic UInt32 *)&_state, kAppleUserHIDDeviceStateStarted);
status = true;
exit:
if (!status) {
stop(provider);
}
return status;
}
void AppleUserHIDDevice::stop(IOService * provider)
{
_state |= kAppleUserHIDDeviceStateStopped;
super::stop(provider);
}
void AppleUserHIDDevice::free()
{
if (ivar) {
OSSafeReleaseNULL(_syncActions);
OSSafeReleaseNULL(_asyncActions);
OSSafeReleaseNULL(_commandGate);
OSSafeReleaseNULL(_workLoop);
if (_asyncActionsLock) {
IOLockFree(_asyncActionsLock);
}
IODelete(ivar, AppleUserHIDDevice::AppleUserHIDDevice_IVars, 1);
}
super::free();
}
bool AppleUserHIDDevice::handleStart( IOService * provider )
{
bool status = false;
OSSerializer * debugSerializer = NULL;
OSNumber * value = NULL;
status = super::handleStart(provider);
require_action(status, exit, HIDDeviceLogError("handleStart"));
_workLoop = getWorkLoop();
require_action (_workLoop, exit, HIDDeviceLogError("workLoop\n"));
_workLoop->retain();
_commandGate = IOCommandGate::commandGate(this);
require_action(_commandGate && _workLoop->addEventSource(_commandGate) == kIOReturnSuccess, exit, HIDDeviceLogError("_commandGate"));
_requestTimeout = kIOUserHIDDeviceTimeoutUS;
value = OSDynamicCast(OSNumber, getProperty(kIOHIDRequestTimeoutKey));
if (value) {
_requestTimeout = value->unsigned32BitValue();
}
debugSerializer = OSSerializer::forTarget(this,
OSMemberFunctionCast(OSSerializerCallback,
this,
&AppleUserHIDDevice::serializeDebugState));
require(debugSerializer, exit);
super::setProperty("DebugState", debugSerializer);
status = true;
exit:
OSSafeReleaseNULL(debugSerializer);
return status;
}
void AppleUserHIDDevice::handleStop( IOService * provider )
{
HIDDeviceLogInfo("handleStop src: %d srfc: %d srtc: %d srt: %llu grc: %d grfc: %d grtc: %d grt: %llu irc: %d irt: %llu",
_setReportCount, _setReportFailCount, _setReportTimeoutCount, _setReportTime,
_getReportCount, _getReportFailCount, _getReportTimeoutCount, _getReportTime,
_inputReportCount, _inputReportTime);
if (_workLoop) {
if ( _commandGate ) {
if (_syncActions) {
uint32_t count = _syncActions->getCount();
_syncActions->iterateObjects(^bool(OSObject *object) {
OSAction * action = OSDynamicCast(OSAction, object);
_commandGate->commandWakeup(action);
return false;
});
_syncActions->flushCollection();
while (count) {
HIDDeviceLogInfo("handleStop sleeping %d", count);
_commandGate->commandSleep(&_syncActions);
count--;
}
}
_workLoop->removeEventSource(_commandGate);
}
}
if (_asyncActions) {
OSSet * tmp = OSSet::withSet(_asyncActions);
if (tmp) {
tmp->iterateObjects(^bool(OSObject *object) {
OSAction * action = OSDynamicCast(OSAction, object);
completeReport(action, kIOReturnAborted, 0);
return false;
});
tmp->release();
}
}
super::handleStop(provider);
}
OSString * AppleUserHIDDevice::newTransportString() const
{
OSString * string = OSDynamicCast(OSString, getProperty (kIOHIDTransportKey));
if (!string) {
return NULL;
}
string->retain();
return string;
}
OSString * AppleUserHIDDevice::newManufacturerString() const
{
OSString * string = OSDynamicCast(OSString, getProperty(kIOHIDManufacturerKey));
if (!string) {
return NULL;
}
string->retain();
return string;
}
OSString * AppleUserHIDDevice::newProductString() const
{
OSString * string = OSDynamicCast(OSString, getProperty(kIOHIDProductKey));
if (!string) {
return NULL;
}
string->retain();
return string;
}
OSNumber * AppleUserHIDDevice::newVendorIDNumber() const
{
OSNumber * number = OSDynamicCast(OSNumber, getProperty(kIOHIDVendorIDKey));
if ( !number )
return NULL;
number->retain();
return number;
}
OSNumber * AppleUserHIDDevice::newProductIDNumber() const
{
OSNumber * number = OSDynamicCast(OSNumber, getProperty(kIOHIDProductIDKey));
if (!number) {
return NULL;
}
number->retain();
return number;
}
OSNumber * AppleUserHIDDevice::newVersionNumber() const
{
OSNumber * number = OSDynamicCast(OSNumber, getProperty(kIOHIDVersionNumberKey));
if (!number) {
return NULL;
}
number->retain();
return number;
}
OSString * AppleUserHIDDevice::newSerialNumberString() const
{
OSString * string = OSDynamicCast(OSString, getProperty(kIOHIDSerialNumberKey));
if (!string) {
return NULL;
}
string->retain();
return string;
}
OSNumber * AppleUserHIDDevice::newVendorIDSourceNumber() const
{
OSNumber * number = OSDynamicCast(OSNumber, getProperty(kIOHIDVendorIDSourceKey));
if ( !number )
return NULL;
number->retain();
return number;
}
OSNumber * AppleUserHIDDevice::newCountryCodeNumber() const
{
OSNumber * number = OSDynamicCast(OSNumber, getProperty(kIOHIDCountryCodeKey));
if (!number) {
return NULL;
}
number->retain();
return number;
}
OSNumber * AppleUserHIDDevice::newReportIntervalNumber() const
{
OSNumber * number = OSDynamicCast(OSNumber, getProperty(kIOHIDReportIntervalKey));
if ( !number ) {
number = IOHIDDevice::newReportIntervalNumber();
} else {
number->retain();
}
return number;
}
OSNumber * AppleUserHIDDevice::newLocationIDNumber() const
{
OSNumber * number = OSDynamicCast(OSNumber, getProperty(kIOHIDLocationIDKey));
if (!number) {
return NULL;
}
number->retain();
return number;
}
IOReturn AppleUserHIDDevice::newReportDescriptor(IOMemoryDescriptor ** descriptor ) const
{
OSData * data;
data = OSDynamicCast(OSData, getProperty(kIOHIDReportDescriptorKey));
if (!data) {
return kIOReturnError;
}
*descriptor = IOBufferMemoryDescriptor::withBytes(data->getBytesNoCopy(), data->getLength(), kIODirectionNone);
return kIOReturnSuccess;
}
bool AppleUserHIDDevice::setProperty(const OSSymbol * aKey, OSObject * anObject)
{
bool status = false;
OSSerialize * s = NULL;
OSDictionary * dict = NULL;
IOBufferMemoryDescriptor * md = NULL;
require (_state & kAppleUserHIDDeviceStateStarted, exit);
require (OSDynamicCast(IOCommand, anObject) == NULL, exit);
require_action (!isInactive(), exit, HIDDeviceLogError("inactive"));
dict = OSDictionary::withObjects((const OSObject **) &anObject, &aKey, 1);
s = OSSerialize::binaryWithCapacity(4096);
require_action (s, exit, HIDDeviceLogError("OSSerialize"));
s->setIndexed(true);
require_action (dict->serialize(s), exit, HIDDeviceLogError("serialize"));
require_action(s->text() && s->getLength(), exit, HIDDeviceLogError("serialize"));
md = IOBufferMemoryDescriptor::withOptions(kIODirectionOutIn |
kIOMemoryPageable |
kIOMemoryKernelUserShared,
s->getLength());
require_action (md, exit, HIDDeviceLogError("IOBufferMemoryDescriptor"));
bcopy(s->text(), md->getBytesNoCopy(), s->getLength());
_SetProperty(md);
exit:
status = super::setProperty(aKey, anObject);
OSSafeReleaseNULL(md);
OSSafeReleaseNULL(s);
OSSafeReleaseNULL(dict);
return status;
}
IOReturn AppleUserHIDDevice::setProperties(OSObject * properties)
{
IOReturn result = kIOReturnBadArgument;
OSDictionary * propertyDict = OSDynamicCast(OSDictionary, properties);
require_action (propertyDict, exit, HIDDeviceLogError("invalid object type"));
propertyDict->iterateObjects (^bool(const OSSymbol * key, OSObject * object) {
if (key->isEqualTo(kIOUserClientClassKey) ||
key->isEqualTo(kIOClassKey) ||
key->isEqualTo(kIOProviderClassKey) ||
key->isEqualTo(kIOKitDebugKey) ||
key->isEqualTo(kIOServiceDEXTEntitlementsKey)) {
HIDDeviceLogError("Ignore porperty for key");
return false;
}
super::setProperty(key, object);
return false;
});
result = kIOReturnSuccess;
exit:
return result;
}
IOReturn AppleUserHIDDevice::getReport(IOMemoryDescriptor * report,
IOHIDReportType reportType,
IOOptionBits options,
UInt32 completionTimeout,
IOHIDCompletion * completion)
{
IOReturn ret;
require_action (report, exit, ret = kIOReturnBadArgument);
require_action (isInactive() == false, exit, ret = kIOReturnOffline);
if (completion && completion->action) {
ret = processReport (kIOHIDReportCommandGetReport, report, reportType, options, completionTimeout, completion);
} else {
ret = dispatch_workloop_sync_with_ret({
return processReport (kIOHIDReportCommandGetReport, report, reportType, options, completionTimeout, completion);
});
}
exit:
return ret;
}
IOReturn AppleUserHIDDevice::setReport(IOMemoryDescriptor * report,
IOHIDReportType reportType,
IOOptionBits options,
UInt32 completionTimeout,
IOHIDCompletion * completion)
{
IOReturn ret = kIOReturnIOError;
bool prepared = false;
IOBufferMemoryDescriptor *memBuff = NULL;
require_action (report, exit, ret = kIOReturnBadArgument);
require_action (isInactive() == false, exit, ret = kIOReturnOffline);
if (OSDynamicCast(IOBufferMemoryDescriptor, report) == NULL || ((IOBufferMemoryDescriptor*)report)->getCapacity() < PAGE_SIZE) {
require_noerr_action(report->prepare(), exit, HIDDeviceLogError("Unable to prepare report for setReport."));
memBuff = IOBufferMemoryDescriptor::withOptions(kIODirectionOutIn |
kIOMemoryPageable |
kIOMemoryKernelUserShared,
report->getLength());
report->readBytes(0, memBuff->getBytesNoCopy(), report->getLength());
report->complete();
memBuff->setLength(report->getLength());
require_noerr_action(memBuff->prepare(), exit, HIDDeviceLogError("Unable to prepare temp report for setReport."));
prepared = true;
report = memBuff;
}
if (completion && completion->action) {
ret = processReport (kIOHIDReportCommandSetReport, report, reportType, options, completionTimeout, completion, memBuff != NULL);
} else {
ret = dispatch_workloop_sync_with_ret({
return processReport (kIOHIDReportCommandSetReport, report, reportType, options, completionTimeout, completion);
});
if (memBuff) {
memBuff->complete();
}
OSSafeReleaseNULL(memBuff);
}
exit:
if (ret != kIOReturnSuccess && memBuff) {
if (prepared) {
memBuff->complete();
}
OSSafeReleaseNULL(memBuff);
}
return ret;
}
IOReturn AppleUserHIDDevice::processReport(HIDReportCommandType command,
IOMemoryDescriptor * report,
IOHIDReportType reportType,
IOOptionBits options,
uint32_t completionTimeout,
IOHIDCompletion * completion,
bool releaseReport)
{
IOReturn ret = kIOReturnSuccess;
OSAction * action = NULL;
IOHIDReportCompletion * reportCompletion;
ret = CreateAction_CompleteReport(
sizeof (IOHIDReportCompletion),
&action);
require_noerr_action(ret, exit, HIDDeviceLogError("CreateAction_CompleteReport:%x", ret));
reportCompletion = (IOHIDReportCompletion *) action->GetReference();
reportCompletion->report = report;
reportCompletion->completion = completion ? *completion : (IOHIDCompletion){NULL, NULL, NULL};
reportCompletion->releaseReport = releaseReport;
if (completion) {
IOLockLock(_asyncActionsLock);
_asyncActions->setObject(action);
IOLockUnlock(_asyncActionsLock);
} else {
_syncActions->setObject(action);
}
action->SetAbortedHandler(^{
if (completion) {
completeReport(action, kIOReturnAborted, 0);
} else {
if (_commandGate) {
_commandGate->runActionBlock(^IOReturn{
if (_syncActions->containsObject(action)) {
reportCompletion->status = kIOReturnAborted;
HIDDeviceLogInfo("Action aborted %d %d", command, reportType);
_syncActions->removeObject(action);
_commandGate->commandWakeup(action);
}
return kIOReturnSuccess;
});
}
}
});
if (command == kIOHIDReportCommandGetReport) {
_getReportCount++;
_getReportTime = mach_continuous_time();
} else {
_setReportCount++;
_setReportTime = mach_continuous_time();
}
_ProcessReport(command, report, reportType, options, completionTimeout, action);
if (completion == NULL || completion->action == NULL) {
AbsoluteTime ts;
if (!completionTimeout) {
completionTimeout = _requestTimeout;
} else {
completionTimeout *= 1000;
}
clock_interval_to_deadline(completionTimeout, kMicrosecondScale, &ts);
ret = _commandGate->commandSleep((void *)action, ts, THREAD_ABORTSAFE);
if (ret == THREAD_AWAKENED) {
ret = reportCompletion->status;
} else if (ret == THREAD_TIMED_OUT) {
ret = kIOReturnTimeout;
} else {
ret = kIOReturnError;
}
if (gIOHIDFamilyDtraceDebug()) {
IOBufferMemoryDescriptor *bmd = OSDynamicCast(IOBufferMemoryDescriptor, report);
hid_trace(reportType == kIOHIDReportTypeInput ? kHIDTraceGetReport : kHIDTraceSetReport, (uintptr_t)getRegistryEntryID(), (uintptr_t)(options & 0xff), (uintptr_t)report->getLength(), bmd ? (uintptr_t)bmd->getBytesNoCopy() : NULL, (uintptr_t)mach_absolute_time());
}
if (ret) {
if (ret == kIOReturnTimeout) {
if (command == kIOHIDReportCommandGetReport) {
_getReportTimeoutCount++;
} else {
_setReportTimeoutCount++;
}
} else {
if (command == kIOHIDReportCommandGetReport) {
_getReportFailCount++;
} else {
_setReportFailCount++;
}
}
HIDDeviceLogError("ProcessReport:0x%x %d %d", ret, reportType, command);
}
if (_syncActions->containsObject(action)) {
_syncActions->removeObject(action);
}
if (isInactive()) {
_commandGate->commandWakeup(&_syncActions);
}
}
exit:
if (action) {
action->release();
}
return ret;
}
void AppleUserHIDDevice::completeReport(OSAction * action, IOReturn status, uint32_t actualByteCount)
{
bool async = false;
require_action (_state & kAppleUserHIDDeviceStateStarted, exit, HIDDeviceLogError("HID Device not ready (state:0x%x)", _state));
IOHIDReportCompletion *reportCompletion;
reportCompletion = (IOHIDReportCompletion *)action->GetReference();
IOLockLock(_asyncActionsLock);
async = _asyncActions->containsObject(action);
_asyncActions->removeObject(action);
IOLockUnlock(_asyncActionsLock);
if (async) {
if (actualByteCount <= reportCompletion->report->getLength()) {
if (reportCompletion->releaseReport) {
if (reportCompletion->report) {
reportCompletion->report->complete();
}
OSSafeReleaseNULL(reportCompletion->report);
}
reportCompletion->completion.action(reportCompletion->completion.target,
reportCompletion->completion.parameter,
status,
actualByteCount);
}
} else {
if (_commandGate) {
_commandGate->runActionBlock(^IOReturn{
if (_syncActions->containsObject(action)) {
reportCompletion->status = status;
_syncActions->removeObject(action);
_commandGate->commandWakeup((void *)action);
}
return kIOReturnSuccess;
});
}
}
exit:
return;
}
IOReturn AppleUserHIDDevice::handleReportWithTime(AbsoluteTime timeStamp,
IOMemoryDescriptor *report,
IOHIDReportType reportType,
IOOptionBits options)
{
IOReturn ret = kIOReturnNotReady;
require_action (_state & kAppleUserHIDDeviceStateStarted, exit, HIDDeviceLogError("HID Device not ready (state:0x%x)", _state));
_inputReportCount++;
_inputReportTime = mach_continuous_time();
ret = super::handleReportWithTime(timeStamp, report, reportType, options);
exit:
return ret;
}
#define SET_DICT_NUM(dict, key, val) do { \
if (val) { \
OSNumber *num = OSNumber::withNumber(val, 64); \
if (num) { \
dict->setObject(key, num); \
num->release(); \
} \
} \
} while (0);
static inline uint64_t calculateDelta(uint64_t a, uint64_t b)
{
uint64_t result = 0, deltaTime = 0;
if (!a) {
return 0;
}
deltaTime = AbsoluteTime_to_scalar(&b) - AbsoluteTime_to_scalar(&a);
absolutetime_to_nanoseconds(deltaTime, &result);
return result;
}
bool AppleUserHIDDevice::serializeDebugState(void *ref __unused,
OSSerialize *serializer)
{
bool result = false;
OSDictionary *dict = OSDictionary::withCapacity(4);
uint64_t now = mach_continuous_time();
require(dict, exit);
SET_DICT_NUM(dict, "SetReportCount", _setReportCount);
SET_DICT_NUM(dict, "SetReportFailCount", _setReportFailCount);
SET_DICT_NUM(dict, "SetReportTimeoutCount", _setReportTimeoutCount);
SET_DICT_NUM(dict, "SetReportTime", calculateDelta(_setReportTime, now));
SET_DICT_NUM(dict, "GetReportCount", _getReportCount);
SET_DICT_NUM(dict, "GetReportFailCount", _getReportFailCount);
SET_DICT_NUM(dict, "GetReportTimeoutCount", _getReportTimeoutCount);
SET_DICT_NUM(dict, "GetReportTime", calculateDelta(_getReportTime, now));
SET_DICT_NUM(dict, "InputReportCount", _inputReportCount);
SET_DICT_NUM(dict, "InputReportTime", calculateDelta(_inputReportTime, now));
result = dict->serialize(serializer);
OSSafeReleaseNULL(dict);
exit:
return result;
}