/* * * @APPLE_LICENSE_HEADER_START@ * * Copyright (c) 1999-2003 Apple Computer, Inc. All Rights Reserved. * * This file contains Original Code and/or Modifications of Original Code * as defined in and that are subject to the Apple Public Source License * Version 2.0 (the 'License'). You may not use this file except in * compliance with the License. Please obtain a copy of the License at * http://www.opensource.apple.com/apsl/ and read it before using this * file. * * The Original Code and all software distributed under the License are * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. * Please see the License for the specific language governing rights and * limitations under the License. * * @APPLE_LICENSE_HEADER_END@ */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "IOHIDLibUserClient.h" #include "IOHIDDevice.h" #include "IOHIDEventQueue.h" __BEGIN_DECLS #include __END_DECLS #if TARGET_OS_EMBEDDED #include #define kIOHIDManagerUserAccessKeyboardEntitlement "com.apple.hid.manager.user-access-keyboard" #endif #define super IOUserClient struct AsyncParam { OSAsyncReference64 fAsyncRef; UInt32 fMax; IOMemoryDescriptor *fMem; IOHIDReportType reportType; uint32_t fAsyncCount; }; struct AsyncGateParam { OSAsyncReference asyncRef; IOHIDReportType reportType; UInt32 reportID; void * reportBuffer; UInt32 reportBufferSize; UInt32 completionTimeOutMS; }; OSDefineMetaClassAndStructors(IOHIDLibUserClient, IOUserClient); const IOExternalMethodDispatch IOHIDLibUserClient:: sMethods[kIOHIDLibUserClientNumCommands] = { { // kIOHIDLibUserClientDeviceIsValid (IOExternalMethodAction) &IOHIDLibUserClient::_deviceIsValid, 0, 0, 2, 0 }, { // kIOHIDLibUserClientOpen (IOExternalMethodAction) &IOHIDLibUserClient::_open, 1, 0, 0, 0 }, { // kIOHIDLibUserClientClose (IOExternalMethodAction) &IOHIDLibUserClient::_close, 0, 0, 0, 0 }, { // kIOHIDLibUserClientCreateQueue (IOExternalMethodAction) &IOHIDLibUserClient::_createQueue, 2, 0, 1, 0 }, { // kIOHIDLibUserClientDisposeQueue (IOExternalMethodAction) &IOHIDLibUserClient::_disposeQueue, 1, 0, 0, 0 }, { // kIOHIDLibUserClientAddElementToQueue (IOExternalMethodAction) &IOHIDLibUserClient::_addElementToQueue, 3, 0, 1, 0 }, { // kIOHIDLibUserClientRemoveElementFromQueue (IOExternalMethodAction) &IOHIDLibUserClient::_removeElementFromQueue, 2, 0, 1, 0 }, { // kIOHIDLibUserClientQueueHasElement (IOExternalMethodAction) &IOHIDLibUserClient::_queueHasElement, 2, 0, 1, 0 }, { // kIOHIDLibUserClientStartQueue (IOExternalMethodAction) &IOHIDLibUserClient::_startQueue, 1, 0, 0, 0 }, { // kIOHIDLibUserClientStopQueue (IOExternalMethodAction) &IOHIDLibUserClient::_stopQueue, 1, 0, 0, 0 }, { // kIOHIDLibUserClientUpdateElementValues (IOExternalMethodAction) &IOHIDLibUserClient::_updateElementValues, kIOUCVariableStructureSize, 0, 0, 0 }, { // kIOHIDLibUserClientPostElementValues (IOExternalMethodAction) &IOHIDLibUserClient::_postElementValues, kIOUCVariableStructureSize, 0, 0, 0 }, { // kIOHIDLibUserClientGetReport (IOExternalMethodAction) &IOHIDLibUserClient::_getReport, 3, 0, 0, kIOUCVariableStructureSize }, { // kIOHIDLibUserClientSetReport (IOExternalMethodAction) &IOHIDLibUserClient::_setReport, 3, kIOUCVariableStructureSize, 0, 0 }, { // kIOHIDLibUserClientGetElementCount (IOExternalMethodAction) &IOHIDLibUserClient::_getElementCount, 0, 0, 2, 0 }, { // kIOHIDLibUserClientGetElements (IOExternalMethodAction) &IOHIDLibUserClient::_getElements, 1, 0, 0, kIOUCVariableStructureSize }, // ASYNC METHODS { // kIOHIDLibUserClientSetQueueAsyncPort (IOExternalMethodAction) &IOHIDLibUserClient::_setQueueAsyncPort, 1, 0, 0, 0 } }; #define kIOHIDLibClientExtendedData "ExtendedData" static void deflate_vec(uint32_t *dp, uint32_t d, const uint64_t *sp, uint32_t s) { if (d > s) d = s; for (uint32_t i = 0; i < d; i++) dp[i] = (uint32_t) sp[i]; } bool IOHIDLibUserClient::initWithTask(task_t owningTask, void * /* security_id */, UInt32 /* type */) { if (!super::init()) return false; if (IOUserClient::clientHasPrivilege(owningTask, kIOClientPrivilegeAdministrator) != kIOReturnSuccess) { // Preparing for extended data. Set a temporary key. setProperty(kIOHIDLibClientExtendedData, true); } fClient = owningTask; task_reference (fClient); proc_t p = (proc_t)get_bsdtask_info(fClient); fPid = proc_pid(p); fQueueMap = OSArray::withCapacity(4); if (!fQueueMap) return false; return true; } IOReturn IOHIDLibUserClient::clientClose(void) { if ( !isInactive() && fGate ) { fGate->runAction(OSMemberFunctionCast(IOCommandGate::Action, this, &IOHIDLibUserClient::cleanupGated)); terminate(); } return kIOReturnSuccess; } void IOHIDLibUserClient::cleanupGated(void) { if (fClient) { task_deallocate(fClient); fClient = 0; } if (fNub) { // First clear any remaining queues setStateForQueues(kHIDQueueStateClear); // Have been started so we better detach // make sure device is closed (especially on crash) // note radar #2729708 for a more comprehensive fix // probably should also subclass clientDied for crash specific code fNub->close(this, fCachedOptionBits); } if ( fResourceNotification ) { fResourceNotification->remove(); fResourceNotification = 0; } if (fResourceES) { if ( fWL ) fWL->removeEventSource(fResourceES); fResourceES->release(); fResourceES = 0; } } bool IOHIDLibUserClient::start(IOService *provider) { OSDictionary *matching = NULL; if (!super::start(provider)) return false; fNub = OSDynamicCast(IOHIDDevice, provider); if (!fNub) return false; fNub->retain(); fWL = getWorkLoop(); if (!fWL) return false; fWL->retain(); OSNumber *primaryUsage = (OSNumber*)fNub->copyProperty(kIOHIDPrimaryUsageKey); OSNumber *primaryUsagePage = (OSNumber*)fNub->copyProperty(kIOHIDPrimaryUsagePageKey); if ((OSDynamicCast(OSNumber, primaryUsagePage) && (primaryUsagePage->unsigned32BitValue() == kHIDPage_GenericDesktop)) && (OSDynamicCast(OSNumber, primaryUsage) && ((primaryUsage->unsigned32BitValue() == kHIDUsage_GD_Keyboard) || (primaryUsage->unsigned32BitValue() == kHIDUsage_GD_Keypad)))) { fNubIsKeyboard = true; } OSSafeReleaseNULL(primaryUsage); OSSafeReleaseNULL(primaryUsagePage); IOCommandGate * cmdGate = IOCommandGate::commandGate(this); if (!cmdGate) goto ABORT_START; fWL->addEventSource(cmdGate); fGate = cmdGate; fResourceES = IOInterruptEventSource::interruptEventSource (this, OSMemberFunctionCast(IOInterruptEventSource::Action, this, &IOHIDLibUserClient::resourceNotificationGated)); if ( !fResourceES ) goto ABORT_START; fWL->addEventSource(fResourceES); // Get notified everytime Root properties change matching = serviceMatching("IOResources"); fResourceNotification = addMatchingNotification( gIOPublishNotification, matching, OSMemberFunctionCast(IOServiceMatchingNotificationHandler, this, &IOHIDLibUserClient::resourceNotification), this); matching->release(); matching = NULL; if ( !fResourceNotification ) goto ABORT_START; return true; ABORT_START: if (fResourceES) { fWL->removeEventSource(fResourceES); fResourceES->release(); fResourceES = 0; } if (fGate) { fWL->removeEventSource(fGate); fGate->release(); fGate = 0; } fWL->release(); fWL = 0; return false; } void IOHIDLibUserClient::stop(IOService *provider) { super::stop(provider); } bool IOHIDLibUserClient::resourceNotification(void * refcon __unused, IOService *service __unused, IONotifier *notifier __unused) { #pragma ignore(notifier) if (!isInactive() && fResourceES) fResourceES->interruptOccurred(0, 0, 0); return true; } void IOHIDLibUserClient::resourceNotificationGated() { IOReturn ret = kIOReturnSuccess; do { // We should force success on seize if ( kIOHIDOptionsTypeSeizeDevice & fCachedOptionBits ) break; #if !TARGET_OS_EMBEDDED OSData * data; IOService * service = getResourceService(); if ( !service ) { ret = kIOReturnError; break; } data = (OSData*)service->copyProperty(kIOConsoleUsersSeedKey); if ( !OSDynamicCast(OSData, data) || !data->getLength() || !data->getBytesNoCopy() ) { ret = kIOReturnError; OSSafeReleaseNULL(data); break; } UInt64 currentSeed = 0; switch ( data->getLength() ) { case sizeof(UInt8): currentSeed = *(UInt8*)(data->getBytesNoCopy()); break; case sizeof(UInt16): currentSeed = *(UInt16*)(data->getBytesNoCopy()); break; case sizeof(UInt32): currentSeed = *(UInt32*)(data->getBytesNoCopy()); break; case sizeof(UInt64): default: currentSeed = *(UInt64*)(data->getBytesNoCopy()); break; } OSSafeReleaseNULL(data); // We should return rather than break so that previous setting is retained if ( currentSeed == fCachedConsoleUsersSeed ) return; fCachedConsoleUsersSeed = currentSeed; #endif ret = clientHasPrivilege(fClient, kIOClientPrivilegeAdministrator); if (ret == kIOReturnSuccess) break; #if TARGET_OS_EMBEDDED if ( fNubIsKeyboard ) { bool result = false; IOReturn kr; proc_t process; process = (proc_t)get_bsdtask_info(fClient); if ( process ) { kr = AppleMobileFileIntegrity::AMFIEntitlementGetBool(process, kIOHIDManagerUserAccessKeyboardEntitlement, &result); if ( kr || !result ) { char name[255]; bzero(name, sizeof(name)); proc_name(fPid, name, sizeof(name)); IOLog("IOHIDLibUserClient: %s is not entitled\n", name); } if (kr == kIOReturnSuccess && result) { ret = kIOReturnSuccess; } } } else { ret = kIOReturnSuccess; } #else if ( fNubIsKeyboard ) { IOUCProcessToken token; token.token = fClient; token.pid = fPid; ret = clientHasPrivilege(&token, kIOClientPrivilegeSecureConsoleProcess); } else { ret = clientHasPrivilege(fClient, kIOClientPrivilegeConsoleUser); } #endif /* TARGET_OS_EMBEDDED */ } while (false); setValid(kIOReturnSuccess == ret); } typedef struct HIDCommandGateArgs { uint32_t selector; IOExternalMethodArguments * arguments; IOExternalMethodDispatch * dispatch; OSObject * target; void * reference; }HIDCommandGateArgs; IOReturn IOHIDLibUserClient::externalMethod( uint32_t selector, IOExternalMethodArguments * arguments, IOExternalMethodDispatch * dispatch, OSObject * target, void * reference) { IOReturn status = kIOReturnOffline; if (fGate) { HIDCommandGateArgs args; args.selector = selector; args.arguments = arguments; args.dispatch = dispatch; args.target = target; args.reference = reference; if (!isInactive()) status = fGate->runAction(OSMemberFunctionCast(IOCommandGate::Action, target, &IOHIDLibUserClient::externalMethodGated), (void *)&args); } return status; } IOReturn IOHIDLibUserClient::externalMethodGated(void * args) { HIDCommandGateArgs * cArgs = (HIDCommandGateArgs *)args; uint32_t selector = cArgs->selector; IOExternalMethodArguments * arguments = cArgs->arguments; IOExternalMethodDispatch * dispatch = cArgs->dispatch; OSObject * target = cArgs->target; void * reference = cArgs->reference; if (isInactive()) return kIOReturnOffline; if (selector < (uint32_t) kIOHIDLibUserClientNumCommands) { dispatch = (IOExternalMethodDispatch *) &sMethods[selector]; if (!target) target = this; } return super::externalMethod(selector, arguments, dispatch, target, reference); } IOReturn IOHIDLibUserClient::_setQueueAsyncPort(IOHIDLibUserClient * target, void * reference __unused, IOExternalMethodArguments * arguments) { return target->setQueueAsyncPort(target->getQueueForToken(arguments->scalarInput[0]), arguments->asyncWakePort); } IOReturn IOHIDLibUserClient::setQueueAsyncPort(IOHIDEventQueue * queue, mach_port_t port) { if ( !queue ) return kIOReturnBadArgument; queue->setNotificationPort(port); return kIOReturnSuccess; } IOReturn IOHIDLibUserClient::_open(IOHIDLibUserClient * target, void * reference __unused, IOExternalMethodArguments * arguments) { return target->open((IOOptionBits)arguments->scalarInput[0]); } IOReturn IOHIDLibUserClient::open(IOOptionBits options) { IOReturn ret = kIOReturnNotPrivileged; do { ret = clientHasPrivilege(fClient, kIOClientPrivilegeAdministrator); if ( ret == kIOReturnSuccess ) break; // RY: If this is a keyboard and the client is attempting to seize, // the client needs to be admin if ( !fNubIsKeyboard || ((options & kIOHIDOptionsTypeSeizeDevice) == 0) ) { #if TARGET_OS_EMBEDDED ret = kIOReturnSuccess; #else ret = clientHasPrivilege(fClient, kIOClientPrivilegeLocalUser); #endif } } while (false); if (ret != kIOReturnSuccess) return ret; if (!fNub) return kIOReturnOffline; if (!fNub->open(this, options)) return kIOReturnExclusiveAccess; fCachedOptionBits = options; fCachedConsoleUsersSeed = 0; resourceNotificationGated(); return kIOReturnSuccess; } IOReturn IOHIDLibUserClient::_close(IOHIDLibUserClient * target, void * reference __unused, IOExternalMethodArguments * arguments __unused) { return target->close(); } IOReturn IOHIDLibUserClient::close() { if (fNub) fNub->close(this, fCachedOptionBits); setValid(false); fCachedOptionBits = 0; // @@@ gvdl: release fWakePort leak them for the time being return kIOReturnSuccess; } bool IOHIDLibUserClient::didTerminate( IOService * provider, IOOptionBits options, bool * defer ) { if (fGate) { fGate->runAction(OSMemberFunctionCast(IOCommandGate::Action, this, &IOHIDLibUserClient::cleanupGated)); } return super::didTerminate(provider, options, defer); } void IOHIDLibUserClient::free() { OSSafeReleaseNULL(fQueueMap); OSSafeReleaseNULL(fNub); if (fResourceES) { if (fWL) fWL->removeEventSource(fResourceES); fResourceES->release(); fResourceES = 0; } if (fGate) { if (fWL) fWL->removeEventSource(fGate); fGate->release(); fGate = 0; } OSSafeReleaseNULL(fWL); if ( fValidMessage ) { IOFree(fValidMessage, sizeof (struct _notifyMsg)); fValidMessage = NULL; } if (fWakePort != MACH_PORT_NULL) { ipc_port_release_send(fWakePort); fWakePort = MACH_PORT_NULL; } if (fValidPort != MACH_PORT_NULL) { ipc_port_release_send(fValidPort); fValidPort = MACH_PORT_NULL; } super::free(); } IOReturn IOHIDLibUserClient::message(UInt32 type, IOService * provider, void * argument ) { if ( !isInactive() && fGate ) { fGate->runAction(OSMemberFunctionCast(IOCommandGate::Action, this, &IOHIDLibUserClient::messageGated), (void*)(intptr_t)type, provider, argument); } return super::message(type, provider, argument); } IOReturn IOHIDLibUserClient::messageGated(UInt32 type, IOService * provider __unused, void * argument ) { IOOptionBits options = (uintptr_t)argument; switch ( type ) { case kIOMessageServiceIsRequestingClose: if ((options & kIOHIDOptionsTypeSeizeDevice) && (options != fCachedOptionBits)) setValid(false); break; case kIOMessageServiceWasClosed: if ((options & kIOHIDOptionsTypeSeizeDevice) && (options != fCachedOptionBits)) { // instead of calling set valid, let's make sure we still have // permission through the resource notification fCachedConsoleUsersSeed = 0; resourceNotificationGated(); } break; }; return kIOReturnSuccess; } IOReturn IOHIDLibUserClient::registerNotificationPort(mach_port_t port, UInt32 type, UInt32 refCon) { if (fGate) { return fGate->runAction(OSMemberFunctionCast(IOCommandGate::Action, this, &IOHIDLibUserClient::registerNotificationPortGated), (void *)port, (void*)(intptr_t)type, (void*)(intptr_t)refCon); } else { return kIOReturnOffline; } } IOReturn IOHIDLibUserClient::registerNotificationPortGated(mach_port_t port, UInt32 type, UInt32 refCon __unused) { IOReturn kr = kIOReturnSuccess; switch ( type ) { case kIOHIDLibUserClientAsyncPortType: if (fWakePort != MACH_PORT_NULL) { ipc_port_release_send(fWakePort); fWakePort = MACH_PORT_NULL; } fWakePort = port; break; case kIOHIDLibUserClientDeviceValidPortType: if (fValidPort != MACH_PORT_NULL) { ipc_port_release_send(fValidPort); fValidPort = MACH_PORT_NULL; } fValidPort = port; static struct _notifyMsg init_msg = { { MACH_MSGH_BITS(MACH_MSG_TYPE_COPY_SEND, 0), sizeof (struct _notifyMsg), MACH_PORT_NULL, MACH_PORT_NULL, 0, 0 } }; if ( fValidMessage ) { IOFree(fValidMessage, sizeof (struct _notifyMsg)); fValidMessage = NULL; } if ( !fValidPort ) break; if ( !(fValidMessage = IOMalloc( sizeof(struct _notifyMsg))) ) { kr = kIOReturnNoMemory; break; } // Initialize the events available message. *((struct _notifyMsg *)fValidMessage) = init_msg; ((struct _notifyMsg *)fValidMessage)->h.msgh_remote_port = fValidPort; dispatchMessage(fValidMessage); break; default: kr = kIOReturnUnsupported; break; }; return kr; } void IOHIDLibUserClient::setValid(bool state) { if (fValid == state) return; if ( !state ) { // unmap this memory if (fNub && !isInactive()) { IOMemoryDescriptor * mem; IOMemoryMap * map; mem = fNub->getMemoryWithCurrentElementValues(); if ( mem ) { map = removeMappingForDescriptor(mem); if ( map ) map->release(); } } fGeneration++; } // set the queue states setStateForQueues(state ? kHIDQueueStateEnable : kHIDQueueStateDisable); // dispatch message dispatchMessage(fValidMessage); fValid = state; } IOReturn IOHIDLibUserClient::dispatchMessage(void * messageIn) { IOReturn ret = kIOReturnError; mach_msg_header_t * msgh = (mach_msg_header_t *)messageIn; if( msgh) { ret = mach_msg_send_from_kernel( msgh, msgh->msgh_size); switch ( ret ) { case MACH_SEND_TIMED_OUT:/* Already has a message posted */ case MACH_MSG_SUCCESS: /* Message is posted */ break; }; } return ret; } void IOHIDLibUserClient::setStateForQueues(UInt32 state, IOOptionBits options __unused) { for (u_int token = getNextTokenForToken(0); token != 0; token = getNextTokenForToken(token)) { // this cannot return a NULL queue because of the above code IOHIDEventQueue *queue = getQueueForToken(token); switch (state) { case kHIDQueueStateEnable: queue->enable(); break; case kHIDQueueStateDisable: queue->disable(); break; case kHIDQueueStateClear: fNub->stopEventDelivery(queue); break; } } } IOReturn IOHIDLibUserClient::clientMemoryForType ( UInt32 type, IOOptionBits * options, IOMemoryDescriptor ** memory ) { if (fGate) { return fGate->runAction(OSMemberFunctionCast(IOCommandGate::Action, this, &IOHIDLibUserClient::clientMemoryForTypeGated), (void*)(intptr_t)type, (void *)options, (void *)memory); } else { return kIOReturnOffline; } } IOReturn IOHIDLibUserClient::clientMemoryForTypeGated( UInt32 token, IOOptionBits * options, IOMemoryDescriptor ** memory ) { IOReturn ret = kIOReturnNoMemory; IOMemoryDescriptor *memoryToShare = NULL; IOHIDEventQueue *queue = NULL; // if the type is element values, then get that if (token == kIOHIDLibUserClientElementValuesType) { // if we can get an element values ptr if (fValid && fNub && !isInactive()) memoryToShare = fNub->getMemoryWithCurrentElementValues(); } // otherwise, the type is token else if (NULL != (queue = getQueueForToken(token))) { memoryToShare = queue->getMemoryDescriptor(); } // if we got some memory if (memoryToShare) { // Memory will be released by user client // when last map is destroyed. memoryToShare->retain(); ret = kIOReturnSuccess; } // set the result *options = 0; *memory = memoryToShare; return ret; } IOReturn IOHIDLibUserClient::_getElementCount(IOHIDLibUserClient * target, void * reference __unused, IOExternalMethodArguments * arguments) { return target->getElementCount(&(arguments->scalarOutput[0]), &(arguments->scalarOutput[1])); } IOReturn IOHIDLibUserClient::getElementCount(uint64_t * pOutElementCount, uint64_t * pOutReportElementCount) { uint32_t outElementCount, outReportElementCount; if (!pOutElementCount || !pOutReportElementCount) return kIOReturnBadArgument; getElements(kHIDElementType, (void *)NULL, &outElementCount); getElements(kHIDReportHandlerType, (void*)NULL, &outReportElementCount); *pOutElementCount = outElementCount / sizeof(IOHIDElementStruct); *pOutReportElementCount = outReportElementCount / sizeof(IOHIDElementStruct); return kIOReturnSuccess; } IOReturn IOHIDLibUserClient::_getElements(IOHIDLibUserClient * target, void * reference __unused, IOExternalMethodArguments * arguments) { if ( arguments->structureOutputDescriptor ) return target->getElements((uint32_t)arguments->scalarInput[0], arguments->structureOutputDescriptor, &(arguments->structureOutputDescriptorSize)); else return target->getElements((uint32_t)arguments->scalarInput[0], arguments->structureOutput, &(arguments->structureOutputSize)); } IOReturn IOHIDLibUserClient::getElements (uint32_t elementType, void *elementBuffer, uint32_t *elementBufferSize) { OSArray * array; uint32_t i, bi, count; IOHIDElementPrivate * element; IOHIDElementStruct * elementStruct; if (elementBuffer && elementBufferSize && !*elementBufferSize) return kIOReturnBadArgument; if (!fNub || isInactive()) return kIOReturnNotAttached; if ( elementType == kHIDElementType ) array = fNub->_reserved->hierarchElements; else array = fNub->_reserved->inputInterruptElementArray; if ( !array ) return kIOReturnError; if ( elementBuffer ) bzero(elementBuffer, *elementBufferSize); count = array->getCount(); bi = 0; for ( i=0; igetObject(i)); if (!element) continue; // Passing elementBuffer=0 means we are just attempting to get the count; elementStruct = elementBuffer ? &(((IOHIDElementStruct *)elementBuffer)[bi]) : 0; if ( element->fillElementStruct(elementStruct) ) bi++; } if (elementBufferSize) *elementBufferSize = bi * sizeof(IOHIDElementStruct); return kIOReturnSuccess; } IOReturn IOHIDLibUserClient::getElements(uint32_t elementType, IOMemoryDescriptor * mem, uint32_t *elementBufferSize) { IOReturn ret = kIOReturnNoMemory; if (!fNub || isInactive()) return kIOReturnNotAttached; ret = mem->prepare(); if(ret == kIOReturnSuccess) { void * elementData; uint32_t elementLength; elementLength = mem->getLength(); if ( elementLength ) { elementData = IOMalloc( elementLength ); if ( elementData ) { bzero(elementData, elementLength); ret = getElements(elementType, elementData, &elementLength); if ( elementBufferSize ) *elementBufferSize = elementLength; mem->writeBytes( 0, elementData, elementLength ); IOFree( elementData, elementLength ); } else ret = kIOReturnNoMemory; } else ret = kIOReturnBadArgument; mem->complete(); } return ret; } IOReturn IOHIDLibUserClient::_deviceIsValid(IOHIDLibUserClient * target, void * reference __unused, IOExternalMethodArguments * arguments) { IOReturn kr; bool status; uint64_t generation; kr = target->deviceIsValid(&status, &generation); arguments->scalarOutput[0] = status; arguments->scalarOutput[1] = generation; return kr; } IOReturn IOHIDLibUserClient::deviceIsValid(bool *status, uint64_t *generation) { if ( status ) *status = fValid; if ( generation ) *generation = fGeneration; return kIOReturnSuccess; } IOReturn IOHIDLibUserClient::_createQueue(IOHIDLibUserClient * target, void * reference __unused, IOExternalMethodArguments * arguments) { return target->createQueue((uint32_t)arguments->scalarInput[0], (uint32_t)arguments->scalarInput[1], &(arguments->scalarOutput[0])); } IOReturn IOHIDLibUserClient::createQueue(uint32_t flags, uint32_t depth, uint64_t * outQueue) { // create the queue (fudge it a bit bigger than requested) IOHIDEventQueue * eventQueue = IOHIDEventQueue::withEntries (depth+1, DEFAULT_HID_ENTRY_SIZE); if ( !eventQueue ) return kIOReturnNoMemory; eventQueue->setOptions(flags); if ( !fValid ) eventQueue->disable(); // add the queue to the map and set out queue *outQueue = (uint64_t)createTokenForQueue(eventQueue); eventQueue->release(); return kIOReturnSuccess; } IOReturn IOHIDLibUserClient::_disposeQueue(IOHIDLibUserClient * target, void * reference __unused, IOExternalMethodArguments * arguments) { return target->disposeQueue(target->getQueueForToken(arguments->scalarInput[0])); } IOReturn IOHIDLibUserClient::disposeQueue(IOHIDEventQueue * queue) { IOReturn ret = kIOReturnSuccess; // remove this queue from all elements that use it if (fNub && !isInactive()) ret = fNub->stopEventDelivery (queue); // remove the queue from the map removeQueueFromMap(queue); // This should really return an actual result return kIOReturnSuccess; } // Add an element to a queue IOReturn IOHIDLibUserClient::_addElementToQueue(IOHIDLibUserClient * target, void * reference __unused, IOExternalMethodArguments * arguments) { return target->addElementToQueue(target->getQueueForToken(arguments->scalarInput[0]), (IOHIDElementCookie)arguments->scalarInput[1], (uint32_t)arguments->scalarInput[2], &(arguments->scalarOutput[0])); } IOReturn IOHIDLibUserClient::addElementToQueue(IOHIDEventQueue * queue, IOHIDElementCookie elementCookie, uint32_t flags __unused, uint64_t *pSizeChange) { IOReturn ret = kIOReturnSuccess; UInt32 size = 0; size = (queue) ? queue->getEntrySize() : 0; // add the queue to the element's queues if (fNub && !isInactive()) ret = fNub->startEventDelivery (queue, elementCookie); *pSizeChange = (queue && (size != queue->getEntrySize())); return ret; } // remove an element from a queue IOReturn IOHIDLibUserClient::_removeElementFromQueue (IOHIDLibUserClient * target, void * reference __unused, IOExternalMethodArguments * arguments) { return target->removeElementFromQueue(target->getQueueForToken(arguments->scalarInput[0]), (IOHIDElementCookie)arguments->scalarInput[1], &(arguments->scalarOutput[0])); } IOReturn IOHIDLibUserClient::removeElementFromQueue (IOHIDEventQueue * queue, IOHIDElementCookie elementCookie, uint64_t *pSizeChange) { IOReturn ret = kIOReturnSuccess; UInt32 size = 0; size = (queue) ? queue->getEntrySize() : 0; // remove the queue from the element's queues if (fNub && !isInactive()) ret = fNub->stopEventDelivery (queue, elementCookie); *pSizeChange = (queue && (size != queue->getEntrySize())); return ret; } // Check to see if a queue has an element IOReturn IOHIDLibUserClient::_queueHasElement (IOHIDLibUserClient * target, void * reference __unused, IOExternalMethodArguments * arguments) { return target->queueHasElement(target->getQueueForToken(arguments->scalarInput[0]), (IOHIDElementCookie)arguments->scalarInput[1], &(arguments->scalarOutput[0])); } IOReturn IOHIDLibUserClient::queueHasElement (IOHIDEventQueue * queue, IOHIDElementCookie elementCookie, uint64_t * pHasElement) { IOReturn ret = kIOReturnSuccess; // check to see if that element is feeding that queue bool hasElement = false; if (fNub && !isInactive()) ret = fNub->checkEventDelivery (queue, elementCookie, &hasElement); // set return *pHasElement = hasElement; return ret; } // start a queue IOReturn IOHIDLibUserClient::_startQueue (IOHIDLibUserClient * target, void * reference __unused, IOExternalMethodArguments * arguments) { return target->startQueue(target->getQueueForToken(arguments->scalarInput[0])); } IOReturn IOHIDLibUserClient::startQueue (IOHIDEventQueue * queue) { // start the queue if (queue) { queue->start(); return kIOReturnSuccess; } return kIOReturnBadArgument; } // stop a queue IOReturn IOHIDLibUserClient::_stopQueue (IOHIDLibUserClient * target, void * reference __unused, IOExternalMethodArguments * arguments) { return target->stopQueue(target->getQueueForToken(arguments->scalarInput[0])); } IOReturn IOHIDLibUserClient::stopQueue (IOHIDEventQueue * queue) { // stop the queue if (queue) { queue->stop(); return kIOReturnSuccess; } return kIOReturnBadArgument; } // update the feature element value IOReturn IOHIDLibUserClient::_updateElementValues (IOHIDLibUserClient * target, void * reference __unused, IOExternalMethodArguments * arguments) { return target->updateElementValues(arguments->scalarInput, arguments->scalarInputCount); } IOReturn IOHIDLibUserClient::updateElementValues (const uint64_t * lCookies, uint32_t cookieCount) { IOReturn ret = kIOReturnError; if (fNub && !isInactive()) { uint32_t cookies[cookieCount]; deflate_vec(cookies, cookieCount, lCookies, cookieCount); ret = fNub->updateElementValues((IOHIDElementCookie *)cookies, cookieCount); } return ret; } // Set the element values IOReturn IOHIDLibUserClient::_postElementValues (IOHIDLibUserClient * target, void * reference __unused, IOExternalMethodArguments * arguments) { return target->postElementValues(arguments->scalarInput, arguments->scalarInputCount); } IOReturn IOHIDLibUserClient::postElementValues (const uint64_t * lCookies, uint32_t cookieCount) { IOReturn ret = kIOReturnError; if (fNub && !isInactive()) { uint32_t cookies[cookieCount]; deflate_vec(cookies, cookieCount, lCookies, cookieCount); ret = fNub->postElementValues((IOHIDElementCookie *)cookies, cookieCount); } return ret; } IOReturn IOHIDLibUserClient::_getReport(IOHIDLibUserClient * target, void * reference __unused, IOExternalMethodArguments * arguments) { if ( arguments->asyncWakePort ) { IOReturn ret; IOHIDCompletion tap; AsyncParam * pb = (AsyncParam *)IOMalloc(sizeof(AsyncParam)); if(!pb) return kIOReturnNoMemory; target->retain(); bcopy(arguments->asyncReference, pb->fAsyncRef, sizeof(OSAsyncReference64)); pb->fAsyncCount = arguments->asyncReferenceCount; tap.target = target; tap.action = OSMemberFunctionCast(IOHIDCompletionAction, target, &IOHIDLibUserClient::ReqComplete); tap.parameter = pb; if ( arguments->structureOutputDescriptor ) ret = target->getReport(arguments->structureOutputDescriptor, &(arguments->structureOutputDescriptorSize), (IOHIDReportType)arguments->scalarInput[0], (uint32_t)arguments->scalarInput[1], (uint32_t)arguments->scalarInput[2], &tap); else ret = target->getReport(arguments->structureOutput, &(arguments->structureOutputSize), (IOHIDReportType)arguments->scalarInput[0], (uint32_t)arguments->scalarInput[1], (uint32_t)arguments->scalarInput[2], &tap); if ( ret ) { if ( pb ) IOFree(pb, sizeof(*pb)); target->release(); } } if ( arguments->structureOutputDescriptor ) return target->getReport(arguments->structureOutputDescriptor, &(arguments->structureOutputDescriptorSize), (IOHIDReportType)arguments->scalarInput[0], (uint32_t)arguments->scalarInput[1]); else return target->getReport(arguments->structureOutput, &(arguments->structureOutputSize), (IOHIDReportType)arguments->scalarInput[0], (uint32_t)arguments->scalarInput[1]); } IOReturn IOHIDLibUserClient::getReport(void *reportBuffer, uint32_t *pOutsize, IOHIDReportType reportType, uint32_t reportID, uint32_t timeout, IOHIDCompletion * completion) { IOReturn ret = kIOReturnBadArgument; IOMemoryDescriptor * mem; // VTN3: Is there a real maximum report size? It looks like the current limit is around // 1024 bytes, but that will (or has) changed. 65536 is above every upper limit // I have seen by a few factors. if (*pOutsize > 0x10000) { IOLog("IOHIDLibUserClient::getReport called with an irrationally large output size: %lu\n", (long unsigned) *pOutsize); } else { mem = IOBufferMemoryDescriptor::withCapacity(*pOutsize, kIODirectionInOut); if(mem) { mem->prepare(kIODirectionInOut); ret = getReport(mem, pOutsize, reportType, reportID, timeout, completion); mem->readBytes(0, reportBuffer, *pOutsize); mem->complete(); mem->release(); } else { ret = kIOReturnNoMemory; } } return ret; } IOReturn IOHIDLibUserClient::getReport(IOMemoryDescriptor * mem, uint32_t * pOutsize, IOHIDReportType reportType, uint32_t reportID, uint32_t timeout, IOHIDCompletion * completion) { IOReturn ret = kIOReturnBadArgument; // VTN3: Is there a real maximum report size? It looks like the current limit is around // 1024 bytes, but that will (or has) changed. 65536 is above every upper limit // I have seen by a few factors. if (*pOutsize > 0x10000) { IOLog("IOHIDLibUserClient::getReport called with an irrationally large output size: %lu\n", (long unsigned) *pOutsize); } else if (fNub && !isInactive()) { ret = mem->prepare(); if(ret == kIOReturnSuccess) { if (completion) { AsyncParam * pb = (AsyncParam *)completion->parameter; pb->fMax = *pOutsize; pb->fMem = mem; pb->reportType = reportType; mem->retain(); ret = fNub->getReport(mem, reportType, reportID, timeout, completion); } else { ret = fNub->getReport(mem, reportType, reportID); // make sure the element values are updated. if (ret == kIOReturnSuccess) fNub->handleReport(mem, reportType, kIOHIDReportOptionNotInterrupt); *pOutsize = mem->getLength(); mem->complete(); } } } else { ret = kIOReturnNotAttached; } return ret; } IOReturn IOHIDLibUserClient::_setReport(IOHIDLibUserClient * target, void * reference __unused, IOExternalMethodArguments * arguments) { IOReturn ret = kIOReturnError; if ( arguments->asyncWakePort ) { IOHIDCompletion tap; AsyncParam * pb = (AsyncParam *)IOMalloc(sizeof(AsyncParam)); if(!pb) return kIOReturnNoMemory; target->retain(); bcopy(arguments->asyncReference, pb->fAsyncRef, sizeof(OSAsyncReference64)); pb->fAsyncCount = arguments->asyncReferenceCount; tap.target = target; tap.action = OSMemberFunctionCast(IOHIDCompletionAction, target, &IOHIDLibUserClient::ReqComplete); tap.parameter = pb; if ( arguments->structureInputDescriptor ) ret = target->setReport( arguments->structureInputDescriptor, (IOHIDReportType)arguments->scalarInput[0], (uint32_t)arguments->scalarInput[1],(uint32_t)arguments->scalarInput[2], &tap); else ret = target->setReport(arguments->structureInput, arguments->structureInputSize, (IOHIDReportType)arguments->scalarInput[0], (uint32_t)arguments->scalarInput[1], (uint32_t)arguments->scalarInput[2], &tap); if ( ret ) { if ( pb ) IOFree(pb, sizeof(*pb)); target->release(); } } else if ( arguments->structureInputDescriptor ) ret = target->setReport( arguments->structureInputDescriptor, (IOHIDReportType)arguments->scalarInput[0], (uint32_t)arguments->scalarInput[1]); else ret = target->setReport(arguments->structureInput, arguments->structureInputSize, (IOHIDReportType)arguments->scalarInput[0], (uint32_t)arguments->scalarInput[1]); return ret; } IOReturn IOHIDLibUserClient::setReport(const void *reportBuffer, uint32_t reportBufferSize, IOHIDReportType reportType, uint32_t reportID, uint32_t timeout, IOHIDCompletion * completion) { IOReturn ret; IOMemoryDescriptor * mem; mem = IOMemoryDescriptor::withAddress((void *)reportBuffer, reportBufferSize, kIODirectionOut); if(mem) { ret = setReport(mem, reportType, reportID, timeout, completion); mem->release(); } else ret = kIOReturnNoMemory; return ret; } IOReturn IOHIDLibUserClient::setReport(IOMemoryDescriptor * mem, IOHIDReportType reportType, uint32_t reportID, uint32_t timeout, IOHIDCompletion * completion) { IOReturn ret; if (fNub && !isInactive()) { ret = mem->prepare(); if(ret == kIOReturnSuccess) { OSArray *extended = (OSArray*)copyProperty(kIOHIDLibClientExtendedData); if (OSDynamicCast(OSArray, extended) && extended->getCount()) { OSCollectionIterator *itr = OSCollectionIterator::withCollection(extended); if (itr) { bool done = false; while (!done) { OSObject *obj; while (!done && (NULL != (obj = itr->getNextObject()))) { OSNumber *num = OSDynamicCast(OSNumber, obj); if (num) { uint8_t excludedReportID = (num->unsigned64BitValue() >> 16) & 0xff; uint8_t excludedReportType =(num->unsigned64BitValue() >> 24) & 0xff; if ((excludedReportID == reportID) && (excludedReportType == (reportType + 1))) { // Block IOLog("IOHIDLibUserClient::setReport %02x/%02x blocked due to lack of privileges\n", reportID, reportType); done = true; ret = kIOReturnNotPrivileged; } } } if (itr->isValid()) { // Do not block done = true; } else { // Someone changed the array. Check again. itr->reset(); } } itr->release(); } } OSSafeReleaseNULL(extended); if (ret == kIOReturnSuccess) { if ( completion ) { AsyncParam * pb = (AsyncParam *)completion->parameter; pb->fMax = mem->getLength(); pb->fMem = mem; pb->reportType = reportType; mem->retain(); ret = fNub->setReport(mem, reportType, reportID, timeout, completion); } else { ret = fNub->setReport(mem, reportType, reportID); // make sure the element values are updated. if (ret == kIOReturnSuccess) fNub->handleReport(mem, reportType, kIOHIDReportOptionNotInterrupt); mem->complete(); } } } } else ret = kIOReturnNotAttached; return ret; } void IOHIDLibUserClient::ReqComplete(void *param, IOReturn res, UInt32 remaining) { fGate->runAction(OSMemberFunctionCast(IOCommandGate::Action, this, &IOHIDLibUserClient::ReqCompleteGated), param, (void*)(intptr_t)res, (void*)(intptr_t)remaining); } IOReturn IOHIDLibUserClient::ReqCompleteGated(void *param, IOReturn res, UInt32 remaining) { io_user_reference_t args[1]; AsyncParam * pb = (AsyncParam *)param; if(res == kIOReturnSuccess) { args[0] = (io_user_reference_t)(pb->fMax - remaining); // make sure the element values are updated. if (fNub && !isInactive()) fNub->handleReport(pb->fMem, pb->reportType, kIOHIDReportOptionNotInterrupt); } else { args[0] = 0; } if (pb->fMem) { pb->fMem->complete(); pb->fMem->release(); } sendAsyncResult64(pb->fAsyncRef, res, args, 1); IOFree(pb, sizeof(*pb)); release(); return kIOReturnSuccess; } // This section is to track all user queues and hand out unique tokens for // particular queues. vtn3 // rdar://5957582 start enum { kIOHIDLibUserClientQueueTokenOffset = 200 }; u_int IOHIDLibUserClient::createTokenForQueue(IOHIDEventQueue *queue) { u_int index = 0; u_int result = 0; while (OSDynamicCast(IOHIDEventQueue, fQueueMap->getObject(index))) index++; if (index < (UINT_MAX - kIOHIDLibUserClientQueueTokenOffset)) { fQueueMap->setObject(index, queue); result = index + kIOHIDLibUserClientQueueTokenOffset; } else { IOLog("IOHIDLibUserClient::createTokenForQueue generated out-of-range index: %d\n", index); } return (result); } void IOHIDLibUserClient::removeQueueFromMap(IOHIDEventQueue *queue) { OSObject *obj = NULL; for (u_int index = 0; NULL != (obj = fQueueMap->getObject(index)); index++) if (obj == queue) { fQueueMap->replaceObject(index, kOSBooleanFalse); } } IOHIDEventQueue* IOHIDLibUserClient::getQueueForToken(u_int token) { IOHIDEventQueue *result = NULL; if (token >= kIOHIDLibUserClientQueueTokenOffset) { result = OSDynamicCast(IOHIDEventQueue, fQueueMap->getObject(token - kIOHIDLibUserClientQueueTokenOffset)); } else { IOLog("IOHIDLibUserClient::getQueueForToken received out-of-range token: %d\n", token); } return (result); } u_int IOHIDLibUserClient::getNextTokenForToken(u_int token) { u_int next_token = (token < kIOHIDLibUserClientQueueTokenOffset) ? kIOHIDLibUserClientQueueTokenOffset - 1 : token; IOHIDEventQueue *queue = NULL; do { queue = getQueueForToken(++next_token); } while ((next_token < fQueueMap->getCount() + kIOHIDLibUserClientQueueTokenOffset) && (queue == NULL)); if (next_token >= fQueueMap->getCount() + kIOHIDLibUserClientQueueTokenOffset) next_token = 0; return next_token; } // rdar://5957582 end bool IOHIDLibUserClient::attach(IOService * provider) { if (!super::attach(provider)) { return false; } if (provider && getProperty(kIOHIDLibClientExtendedData)) { // Check for extended data OSArray *extended = (OSArray*)provider->copyProperty(kIOHIDLibClientExtendedData, gIOServicePlane); if (OSDynamicCast(OSArray, extended) && extended->getCount()) { // Extended data found. Replace the temporary key. setProperty(kIOHIDLibClientExtendedData, extended); } else { // No extended data found. Remove the temporary key. removeProperty(kIOHIDLibClientExtendedData); } OSSafeReleaseNULL(extended); } return true; }