IOHIDResourceUserClient.cpp [plain text]
#include <IOKit/IOLib.h>
#ifdef enqueue
#undef enqueue
#endif
#include "IOHIDResourceUserClient.h"
#define kHIDRTimeoutNS 1000000000
#define super IOUserClient
OSDefineMetaClassAndStructors( IOHIDResourceDeviceUserClient, IOUserClient )
const IOExternalMethodDispatch IOHIDResourceDeviceUserClient::_methods[kIOHIDResourceDeviceUserClientMethodCount] = {
{ (IOExternalMethodAction) &IOHIDResourceDeviceUserClient::_createDevice,
0, -1,
0, 0
},
{ (IOExternalMethodAction) &IOHIDResourceDeviceUserClient::_terminateDevice,
0, 0,
0, 0
},
{ (IOExternalMethodAction) &IOHIDResourceDeviceUserClient::_handleReport,
0, -1,
0, 0
},
{ (IOExternalMethodAction) &IOHIDResourceDeviceUserClient::_postReportResult,
kIOHIDResourceUserClientResponseIndexCount, -1,
0, 0
}
};
bool IOHIDResourceDeviceUserClient::initWithTask(task_t owningTask, void * security_id, UInt32 type)
{
#if !TARGET_OS_EMBEDDED
if (kIOReturnSuccess != clientHasPrivilege(owningTask, kIOClientPrivilegeAdministrator))
return false;
#endif
if (!super::initWithTask(owningTask, security_id, type)) {
IOLog("%s failed\n", __FUNCTION__);
return false;
}
_device = NULL;
_lock = IOLockAlloc();
_pending = OSSet::withCapacity(4);
return true;
}
bool IOHIDResourceDeviceUserClient::start(IOService * provider)
{
if (!super::start(provider)) {
IOLog("%s failed\n", __FUNCTION__);
return false;
}
_owner = (IOHIDResource *) provider;
_device = NULL;
return true;
}
void IOHIDResourceDeviceUserClient::stop(IOService * provider)
{
if ( _device )
_device->release();
super::stop(provider);
}
void IOHIDResourceDeviceUserClient::free()
{
if ( _queue )
_queue->release();
if ( _lock )
IOLockFree(_lock);
return super::free();
}
IOReturn IOHIDResourceDeviceUserClient::registerNotificationPort(mach_port_t port, UInt32 type __unused, io_user_reference_t refCon __unused)
{
if ( isInactive() )
return kIOReturnNoDevice;
_port = port;
_queue->setNotificationPort(port);
return kIOReturnSuccess;
}
IOReturn IOHIDResourceDeviceUserClient::clientMemoryForType(UInt32 type __unused, IOOptionBits * options, IOMemoryDescriptor ** memory )
{
IOReturn ret = kIOReturnNoMemory;
if ( isInactive() )
return kIOReturnNoDevice;
if ( !_queue ) {
UInt32 maxOutputReportSize = 0;
UInt32 maxFeatureReportSize = 0;
OSNumber * number;
number = (OSNumber*)_device->copyProperty(kIOHIDMaxOutputReportSizeKey);
if ( OSDynamicCast(OSNumber, number) )
maxOutputReportSize = number->unsigned32BitValue();
OSSafeReleaseNULL(number);
number = (OSNumber*)_device->copyProperty(kIOHIDMaxFeatureReportSizeKey);
if ( OSDynamicCast(OSNumber, number) )
maxFeatureReportSize = number->unsigned32BitValue();
OSSafeReleaseNULL(number);
_queue = IOHIDResourceQueue::withEntries(4, max(maxFeatureReportSize, maxOutputReportSize)+sizeof(IOHIDResourceDataQueueHeader));
}
if ( _queue ) {
IOMemoryDescriptor * memoryToShare = _queue->getMemoryDescriptor();
if (memoryToShare)
{
memoryToShare->retain();
ret = kIOReturnSuccess;
}
*options = 0;
*memory = memoryToShare;
}
return ret;
}
IOReturn IOHIDResourceDeviceUserClient::externalMethod(
uint32_t selector,
IOExternalMethodArguments * arguments,
IOExternalMethodDispatch * dispatch,
OSObject * target,
void * reference)
{
if ( isInactive() )
return kIOReturnNoDevice;
if (selector < (uint32_t) kIOHIDResourceDeviceUserClientMethodCount)
{
dispatch = (IOExternalMethodDispatch *) &_methods[selector];
if (!target)
target = this;
}
return super::externalMethod(selector, arguments, dispatch, target, reference);
}
IOMemoryDescriptor * IOHIDResourceDeviceUserClient::createMemoryDescriptorFromInputArguments(
IOExternalMethodArguments * arguments)
{
IOMemoryDescriptor * report = NULL;
if ( arguments->structureInputDescriptor ) {
report = arguments->structureInputDescriptor;
report->retain();
} else {
report = IOMemoryDescriptor::withAddress((void *)arguments->structureInput, arguments->structureInputSize, kIODirectionOut);
}
return report;
}
IOService * IOHIDResourceDeviceUserClient::getService(void)
{
return _owner;
}
IOReturn IOHIDResourceDeviceUserClient::clientClose(void)
{
cleanupPendingReports();
terminate();
return kIOReturnSuccess;
}
IOReturn IOHIDResourceDeviceUserClient::createDevice(
IOHIDResourceDeviceUserClient * target __unused,
void * reference __unused,
IOExternalMethodArguments * arguments)
{
if (_device == NULL) {
IOReturn ret;
IOMemoryDescriptor * propertiesDesc = NULL;
OSDictionary * properties = NULL;
propertiesDesc = createMemoryDescriptorFromInputArguments(arguments);
if ( !propertiesDesc ) {
IOLog("%s failed : could not create descriptor\n", __FUNCTION__);
return kIOReturnNoMemory;
}
ret = propertiesDesc->prepare();
if ( ret == kIOReturnSuccess ) {
void * propertiesData;
IOByteCount propertiesLength;
propertiesLength = propertiesDesc->getLength();
if ( propertiesLength ) {
propertiesData = IOMalloc(propertiesLength);
if ( propertiesData ) {
OSObject * object;
propertiesDesc->readBytes(0, propertiesData, propertiesLength);
object = OSUnserializeXML((const char *)propertiesData);
if (object) {
properties = OSDynamicCast(OSDictionary, object);
if( !properties )
object->release();
}
IOFree(propertiesData, propertiesLength);
}
}
propertiesDesc->complete();
}
propertiesDesc->release();
if ( properties ) {
_device = IOHIDUserDevice::withProperties(properties);
properties->release();
}
} else {
IOLog("%s failed : _device already exists\n", __FUNCTION__);
return kIOReturnInternalError;
}
if (_device == NULL) {
IOLog("%s failed : _device is NULL\n", __FUNCTION__);
return kIOReturnNoResources;
}
IOReturn ret = kIOReturnInternalError;
if (_device->attach(this) ) {
if ( _device->start(this) ) {
ret = kIOReturnSuccess;
} else {
IOLog("%s start failed\n", __FUNCTION__);
_device->detach(this);
}
} else {
IOLog("%s attach failed\n", __FUNCTION__);
}
if ( ret != kIOReturnSuccess ) {
_device->release();
_device = NULL;
}
return ret;
}
IOReturn IOHIDResourceDeviceUserClient::_createDevice(
IOHIDResourceDeviceUserClient * target,
void * reference,
IOExternalMethodArguments * arguments)
{
return target->createDevice(target, reference, arguments);
}
struct IOHIDResourceDeviceUserClientAsyncParamBlock {
OSAsyncReference64 fAsyncRef;
uint32_t fAsyncCount;
};
void IOHIDResourceDeviceUserClient::ReportComplete(void *param, IOReturn res, UInt32 remaining __unused)
{
IOHIDResourceDeviceUserClientAsyncParamBlock *pb = (IOHIDResourceDeviceUserClientAsyncParamBlock *)param;
io_user_reference_t args[1];
args[0] = 0;
sendAsyncResult64(pb->fAsyncRef, res, args, 0);
IOFree(pb, sizeof(*pb));
release();
}
IOReturn IOHIDResourceDeviceUserClient::handleReport(
IOHIDResourceDeviceUserClient * target,
void * reference __unused,
IOExternalMethodArguments * arguments)
{
if (_device == NULL) {
IOLog("%s failed : device is NULL\n", __FUNCTION__);
return kIOReturnNotOpen;
}
if (target != this) {
IOLog("%s failed : this is not target\n", __FUNCTION__);
return kIOReturnInternalError;
}
IOReturn ret;
IOMemoryDescriptor * report;
report = createMemoryDescriptorFromInputArguments(arguments);
if ( !report ) {
IOLog("%s failed : could not create descriptor\n", __FUNCTION__);
return kIOReturnNoMemory;
}
if ( !arguments->asyncWakePort ) {
ret = report->prepare();
if ( ret == kIOReturnSuccess ) {
ret = _device->handleReport(report);
report->complete();
}
report->release();
} else {
IOHIDCompletion tap;
IOHIDResourceDeviceUserClientAsyncParamBlock *pb =
(IOHIDResourceDeviceUserClientAsyncParamBlock *)IOMalloc(sizeof(IOHIDResourceDeviceUserClientAsyncParamBlock));
if (!pb) {
report->release();
return kIOReturnNoMemory; }
target->retain();
bcopy(arguments->asyncReference, pb->fAsyncRef, sizeof(OSAsyncReference64));
pb->fAsyncCount = arguments->asyncReferenceCount;
tap.target = target;
tap.action = OSMemberFunctionCast(IOHIDCompletionAction, target, &IOHIDResourceDeviceUserClient::ReportComplete);
tap.parameter = pb;
AbsoluteTime currentTime;
clock_get_uptime( ¤tTime );
ret = report->prepare();
if ( ret == kIOReturnSuccess ) {
ret = _device->handleReportWithTimeAsync(currentTime, report, kIOHIDReportTypeInput, 0, 0, &tap);
report->complete();
}
report->release();
if (ret != kIOReturnSuccess) {
IOFree(pb, sizeof(*pb));
target->release();
}
}
return ret;
}
IOReturn IOHIDResourceDeviceUserClient::_handleReport(IOHIDResourceDeviceUserClient *target,
void *reference,
IOExternalMethodArguments *arguments)
{
return target->handleReport(target, reference, arguments);
}
typedef struct {
IOReturn ret;
IOMemoryDescriptor * descriptor;
} __ReportResult;
IOReturn IOHIDResourceDeviceUserClient::getReport(IOMemoryDescriptor *report, IOHIDReportType reportType, IOOptionBits options)
{
IOHIDResourceDataQueueHeader header;
__ReportResult result;
IOReturn ret = kIOReturnNoMemory;
OSData * retData = NULL;
result.descriptor = report;
retData = OSData::withBytesNoCopy(&result, sizeof(__ReportResult));
if ( retData ) {
header.direction = kIOHIDResourceReportDirectionIn;
header.type = reportType;
header.reportID = options&0xff;
header.length = report->getLength();
header.token = (intptr_t)retData;
IOLockLock(_lock);
_pending->setObject(retData);
retData->release();
if ( _queue && _queue->enqueueReport(&header) ){
AbsoluteTime ts;
clock_interval_to_deadline(1, kHIDRTimeoutNS, &ts);
switch ( IOLockSleepDeadline(_lock, (void *)retData, ts, THREAD_ABORTSAFE) ) {
case THREAD_AWAKENED:
ret = result.ret;
break;
case THREAD_TIMED_OUT:
ret = kIOReturnTimeout;
break;
default:
ret = kIOReturnError;
break;
}
}
_pending->removeObject(retData);
IOLockUnlock(_lock);
}
return ret;
}
IOReturn IOHIDResourceDeviceUserClient::setReport(IOMemoryDescriptor *report, IOHIDReportType reportType, IOOptionBits options)
{
IOHIDResourceDataQueueHeader header;
__ReportResult result;
IOReturn ret = kIOReturnNoMemory;
OSData * retData = NULL;
bzero(&result, sizeof(result));
retData = OSData::withBytesNoCopy(&result, sizeof(result));
if ( retData ) {
header.direction = kIOHIDResourceReportDirectionOut;
header.type = reportType;
header.reportID = options&0xff;
header.length = report->getLength();
header.token = (intptr_t)retData;
IOLockLock(_lock);
_pending->setObject(retData);
retData->release();
if ( _queue && _queue->enqueueReport(&header, report) ) {
AbsoluteTime ts;
clock_interval_to_deadline(1, kHIDRTimeoutNS, &ts);
switch ( IOLockSleepDeadline(_lock, (void *)retData, ts, THREAD_ABORTSAFE) ) {
case THREAD_AWAKENED:
ret = result.ret;
break;
case THREAD_TIMED_OUT:
ret = kIOReturnTimeout;
break;
default:
ret = kIOReturnError;
break;
}
}
_pending->removeObject(retData);
IOLockUnlock(_lock);
}
return ret;
}
IOReturn IOHIDResourceDeviceUserClient::postReportResult(
IOHIDResourceDeviceUserClient * target __unused,
void * reference __unused,
IOExternalMethodArguments * arguments)
{
OSObject * tokenObj = (OSObject*)arguments->scalarInput[kIOHIDResourceUserClientResponseIndexToken];
IOLockLock(_lock);
if ( tokenObj && _pending->containsObject(tokenObj) ) {
OSData * data = OSDynamicCast(OSData, tokenObj);
if ( data ) {
__ReportResult * pResult = (__ReportResult*)data->getBytesNoCopy();
if ( pResult->descriptor && arguments->structureInput )
pResult->descriptor->writeBytes(0, arguments->structureInput, arguments->structureInputSize);
pResult->ret = arguments->scalarInput[kIOHIDResourceUserClientResponseIndexResult];
IOLockWakeup(_lock, (void *)data, false);
}
}
IOLockUnlock(_lock);
return kIOReturnSuccess;
}
IOReturn IOHIDResourceDeviceUserClient::_postReportResult(IOHIDResourceDeviceUserClient *target,
void *reference,
IOExternalMethodArguments *arguments)
{
return target->postReportResult(target, reference, arguments);
}
void IOHIDResourceDeviceUserClient::cleanupPendingReports()
{
OSCollectionIterator * iterator;
OSObject * object;
iterator = OSCollectionIterator::withCollection(_pending);
if ( !iterator )
return;
while ( (object = iterator->getNextObject()) )
IOLockWakeup(_lock, (void *)object, false);
iterator->release();
}
IOReturn IOHIDResourceDeviceUserClient::terminateDevice()
{
if (_device) {
_device->terminate();
_device->release();
}
_device = NULL;
return kIOReturnSuccess;
}
IOReturn IOHIDResourceDeviceUserClient::_terminateDevice(
IOHIDResourceDeviceUserClient *target,
void *reference __unused,
IOExternalMethodArguments *arguments __unused)
{
return target->terminateDevice();
}
#include <IOKit/IODataQueueShared.h>
OSDefineMetaClassAndStructors( IOHIDResourceQueue, IODataQueue )
IOHIDResourceQueue *IOHIDResourceQueue::withEntries(UInt32 numEntries, UInt32 entrySize)
{
IOHIDResourceQueue *dataQueue = new IOHIDResourceQueue;
if (dataQueue) {
if (!dataQueue->initWithEntries(numEntries, entrySize)) {
dataQueue->release();
dataQueue = 0;
}
}
return dataQueue;
}
void IOHIDResourceQueue::free()
{
if ( _descriptor )
{
_descriptor->release();
_descriptor = 0;
}
IODataQueue::free();
}
#define ALIGNED_DATA_SIZE(data_size,align_size) (data_size+(align_size-(data_size%align_size)))
Boolean IOHIDResourceQueue::enqueueReport(IOHIDResourceDataQueueHeader * header, IOMemoryDescriptor * report)
{
IOByteCount headerSize = sizeof(IOHIDResourceDataQueueHeader);
IOByteCount reportSize = report ? report->getLength() : 0;
IOByteCount dataSize = ALIGNED_DATA_SIZE(headerSize + reportSize, sizeof(uint32_t));
const UInt32 head = dataQueue->head; const UInt32 tail = dataQueue->tail;
const UInt32 entrySize = dataSize + DATA_QUEUE_ENTRY_HEADER_SIZE;
IODataQueueEntry * entry;
if ( tail >= head )
{
if ( (tail + entrySize) <= dataQueue->queueSize )
{
entry = (IODataQueueEntry *)((UInt8 *)dataQueue->queue + tail);
entry->size = dataSize;
bcopy(header, &entry->data, headerSize);
if ( report )
report->readBytes(0, ((UInt8*)&entry->data) + headerSize, reportSize);
dataQueue->tail += entrySize;
}
else if ( head > entrySize ) {
dataQueue->queue->size = dataSize;
if ( ( dataQueue->queueSize - tail ) >= DATA_QUEUE_ENTRY_HEADER_SIZE )
{
((IODataQueueEntry *)((UInt8 *)dataQueue->queue + tail))->size = dataSize;
}
bcopy(header, &dataQueue->queue->data, sizeof(IOHIDResourceDataQueueHeader));
if ( report )
report->readBytes(0, ((UInt8*)&dataQueue->queue->data) + headerSize, reportSize);
dataQueue->tail = entrySize;
}
else
{
return false; }
}
else
{
if ( (head - tail) > entrySize )
{
entry = (IODataQueueEntry *)((UInt8 *)dataQueue->queue + tail);
entry->size = dataSize;
bcopy(header, &entry->data, sizeof(IOHIDResourceDataQueueHeader));
if ( report )
report->readBytes(0, ((UInt8*)&entry->data) + headerSize, reportSize);
dataQueue->tail += entrySize;
}
else
{
return false; }
}
if ( ( head == tail ) || ( dataQueue->head == tail ) )
sendDataAvailableNotification();
return true;
}
void IOHIDResourceQueue::setNotificationPort(mach_port_t port)
{
IODataQueue::setNotificationPort(port);
if (dataQueue->head != dataQueue->tail)
sendDataAvailableNotification();
}
IOMemoryDescriptor * IOHIDResourceQueue::getMemoryDescriptor()
{
if (!_descriptor)
_descriptor = IODataQueue::getMemoryDescriptor();
return _descriptor;
}