/* * Copyright (c) 1998-2023 Apple Inc. All rights reserved. * * @APPLE_OSREFERENCE_LICENSE_HEADER_START@ * * 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. The rights granted to you under the License * may not be used to create, or enable the creation or redistribution of, * unlawful or unlicensed copies of an Apple operating system, or to * circumvent, violate, or enable the circumvention or violation of, any * terms of an Apple operating system software license agreement. * * 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_OSREFERENCE_LICENSE_HEADER_END@ */ #include #include #include #include #include #include "../Kernel/IOServicePrivate.h" #include #if CONFIG_EXCLAVES #include #include #define EXLOG(x...) do { \ if (kIOLogExclaves & gIOKitDebug) \ IOLog(x); \ } while (false) /* Global IOExclaveProxyState lookup table */ OSDictionary *gExclaveProxyStates; IORecursiveLock *gExclaveProxyStateLock; const OSSymbol *gDARTMapperFunctionSetActive; /* IOExclaveProxyState */ class IOExclaveWorkLoopAperture { public: IOWorkLoop *workLoop; void closeGate() { workLoop->closeGate(); } void openGate() { workLoop->openGate(); } }; #endif /* CONFIG_EXCLAVES */ struct IOService::IOExclaveProxyState { IOService *service; uint64_t mach_endpoint; #if CONFIG_EXCLAVES tb_endpoint_t tb_endpoint; ioservice_ioserviceconcrete client; // ExclaveDriverKit related state uint64_t edk_mach_endpoint; tb_endpoint_t edk_tb_endpoint; ioservice_ioserviceprivate edk_client; OSDictionary *exclave_interrupts; IOLock *exclave_interrupts_lock; OSDictionary *exclave_timers; IOLock *exclave_timers_lock; uint32_t nextExclaveTimerId; // TODO: implement properly once ExclaveAperture removed IOExclaveWorkLoopAperture *ewla; IOLock * exclaveAsyncNotificationEventSourcesLock; OSArray *exclaveAsyncNotificationEventSources; // ANE specific upcalls ANEUpcallSetPowerStateHandler aneSetPowerStateUpcallHandler; ANEUpcallWorkHandler aneWorkSubmitUpcallHandler; ANEUpcallWorkHandler aneWorkBeginUpcallHandler; ANEUpcallWorkHandler aneWorkEndUpcallHandler; #endif /* CONFIG_EXCLAVES */ }; #if CONFIG_EXCLAVES class IOExclaveProxyStateWrapper : public OSObject { OSDeclareFinalStructors(IOExclaveProxyStateWrapper); public: IOService::IOExclaveProxyState *proxyState; }; OSDefineMetaClassAndFinalStructors(IOExclaveProxyStateWrapper, OSObject); extern "C" kern_return_t exclaves_driver_service_lookup(const char *service_name, uint64_t *endpoint); #endif /* CONFIG_EXCLAVES */ bool IOService::exclaveStart(IOService * provider, IOExclaveProxyState ** pRef) { IOExclaveProxyState * ref; ref = NULL; #if CONFIG_EXCLAVES int err = 1; do { char key[16]; uint64_t serviceID; uint64_t mach_endpoint = 0; uint64_t edk_mach_endpoint = 0; tb_error_t tberr; tb_endpoint_t tb_endpoint; tb_endpoint_t edk_tb_endpoint; ioservice_ioserviceconcrete client; ioservice_ioserviceprivate edk_client; OSObject * prop; OSData * data; IOWorkLoop * wl; IOExclaveProxyStateWrapper * wrapper; bool result; // exit early if Exclaves are not available if (exclaves_get_status() == EXCLAVES_STATUS_NOT_SUPPORTED) { break; } prop = provider->copyProperty("exclave-endpoint"); if ((data = OSDynamicCast(OSData, prop))) { mach_endpoint = ((uint32_t *)data->getBytesNoCopy())[0]; } OSSafeReleaseNULL(prop); prop = provider->copyProperty("exclave-service"); if ((data = OSDynamicCast(OSData, prop))) { const char *exclave_service = (const char *) data->getBytesNoCopy(); if (exclave_service[data->getLength() - 1] != '\0') { IOLog("%s: %s-0x%qx exclave-service property is invalid\n", __func__, provider->getName(), provider->getRegistryEntryID()); OSSafeReleaseNULL(prop); break; } if (exclaves_driver_service_lookup(exclave_service, &mach_endpoint) != KERN_SUCCESS) { IOLog("%s: %s-0x%qx could not find exclave-service %s\n", __func__, provider->getName(), provider->getRegistryEntryID(), exclave_service); OSSafeReleaseNULL(prop); break; } } OSSafeReleaseNULL(prop); prop = provider->copyProperty("exclave-edk-endpoint"); if ((data = OSDynamicCast(OSData, prop))) { edk_mach_endpoint = ((uint32_t *)data->getBytesNoCopy())[0]; } OSSafeReleaseNULL(prop); prop = provider->copyProperty("exclave-edk-service"); if ((data = OSDynamicCast(OSData, prop))) { const char *exclave_edk_service = (const char *) data->getBytesNoCopy(); if (exclave_edk_service[data->getLength() - 1] != '\0') { IOLog("%s: %s-0x%qx exclave-edk-service property is invalid\n", __func__, provider->getName(), provider->getRegistryEntryID()); OSSafeReleaseNULL(prop); break; } if (exclaves_driver_service_lookup(exclave_edk_service, &edk_mach_endpoint) != KERN_SUCCESS) { IOLog("%s: %s-0x%qx could not find exclave-edk-service %s\n", __func__, provider->getName(), provider->getRegistryEntryID(), exclave_edk_service); OSSafeReleaseNULL(prop); break; } } OSSafeReleaseNULL(prop); // Initialize IOServiceConcrete endpoint tb_endpoint = tb_endpoint_create_with_value(TB_TRANSPORT_TYPE_XNU, mach_endpoint, TB_ENDPOINT_OPTIONS_NONE); assert(NULL != tb_endpoint); if (NULL == tb_endpoint) { break; } tberr = ioservice_ioserviceconcrete_init(&client, tb_endpoint); assert(TB_ERROR_SUCCESS == tberr); if (TB_ERROR_SUCCESS != tberr) { break; } // Initialize IOServicePrivate endpoint edk_tb_endpoint = tb_endpoint_create_with_value(TB_TRANSPORT_TYPE_XNU, edk_mach_endpoint, TB_ENDPOINT_OPTIONS_NONE); assert(NULL != edk_tb_endpoint); if (NULL == edk_tb_endpoint) { printf("%s: ERROR: Failed to create endpoint\n", __func__); break; } tberr = ioservice_ioserviceprivate_init(&edk_client, edk_tb_endpoint); assert(TB_ERROR_SUCCESS == tberr); if (TB_ERROR_SUCCESS != tberr) { printf("%s: ERROR: Failed to init IOServicePrivate\n", __func__); break; } ref = IONewZero(IOExclaveProxyState, 1); if (!ref) { break; } ref->service = this; ref->mach_endpoint = mach_endpoint; ref->tb_endpoint = tb_endpoint; ref->client = client; ref->edk_mach_endpoint = edk_mach_endpoint; ref->edk_tb_endpoint = edk_tb_endpoint; ref->edk_client = edk_client; ref->exclave_interrupts = OSDictionary::withCapacity(1); ref->exclave_interrupts_lock = IOLockAlloc(); ref->exclave_timers = OSDictionary::withCapacity(1); ref->exclave_timers_lock = IOLockAlloc(); ref->exclaveAsyncNotificationEventSourcesLock = IOLockAlloc(); // TODO: remove once workloop aperture workaround removed wl = getWorkLoop(); if (!wl) { printf("%s ERROR: getWorkLoop failed\n", __func__); break; } ref->ewla = IONew(IOExclaveWorkLoopAperture, 1); if (!ref->ewla) { printf("%s ERROR: exclaveWorkLoopAperture init failed\n", __func__); break; } ref->ewla->workLoop = wl; // Add proxy state to global lookup table serviceID = getRegistryEntryID(); snprintf(key, sizeof(key), "%llu", serviceID); wrapper = OSTypeAlloc(IOExclaveProxyStateWrapper); wrapper->proxyState = ref; IORecursiveLockLock(gExclaveProxyStateLock); gExclaveProxyStates->setObject(key, wrapper); IORecursiveLockUnlock(gExclaveProxyStateLock); // Start() called after lookup table registration in case upcalls are made during exclave start(). // Use registry ID as exclave's upcall identifer tberr = ioservice_ioserviceprivate_startprivate(&edk_client, serviceID, &result); if (TB_ERROR_SUCCESS != tberr || !result) { printf("%s ERROR: Failed StartPrivate\n", __func__); // Deregister from lookup table if start() fails IORecursiveLockLock(gExclaveProxyStateLock); gExclaveProxyStates->removeObject(key); IORecursiveLockUnlock(gExclaveProxyStateLock); wrapper->release(); break; } err = 0; } while (false); if (err) { if (ref) { OSSafeReleaseNULL(ref->exclave_interrupts); if (ref->exclave_interrupts_lock) { IOLockFree(ref->exclave_interrupts_lock); ref->exclave_interrupts_lock = NULL; } OSSafeReleaseNULL(ref->exclave_timers); if (ref->exclave_timers_lock) { IOLockFree(ref->exclave_timers_lock); ref->exclave_timers_lock = NULL; } if (ref->exclaveAsyncNotificationEventSourcesLock) { IOLockFree(ref->exclaveAsyncNotificationEventSourcesLock); ref->exclaveAsyncNotificationEventSourcesLock = NULL; } if (ref->ewla) { IODelete(ref->ewla, IOExclaveWorkLoopAperture, 1); ref->ewla = NULL; } IODelete(ref, IOExclaveProxyState, 1); ref = NULL; } } #endif /* CONFIG_EXCLAVES */ if (!ref) { return false; } *pRef = ref; return true; } uint64_t IOService::exclaveEndpoint(IOExclaveProxyState * pRef) { return pRef->mach_endpoint; } bool IOExclaveProxy::start(IOService * provider) { bool ok; ok = exclaveStart(provider, &exclaveState); return ok; } /* Exclave upcall handlers */ #if CONFIG_EXCLAVES static IOService::IOExclaveProxyState * getProxyStateFromRegistryID(uint64_t id) { OSObject *obj = NULL; IOExclaveProxyStateWrapper *wrapper = NULL; char key[15]; snprintf(key, sizeof(key), "%llu", id); IORecursiveLockLock(gExclaveProxyStateLock); obj = gExclaveProxyStates->getObject(key); IORecursiveLockUnlock(gExclaveProxyStateLock); if (!obj) { printf("%s ERROR: failed to find proxy state\n", __func__); return NULL; } wrapper = OSDynamicCast(IOExclaveProxyStateWrapper, obj); if (!wrapper) { printf("%s ERROR: failed to cast IOExclaveProxyStateWrapper\n", __func__); return NULL; } if (!wrapper->proxyState) { printf("%s ERROR: IOExclaveProxyStateWrapper contains NULL proxy state\n", __func__); return NULL; } return wrapper->proxyState; } bool IOExclaveInterruptUpcallHandler(uint64_t id, IOExclaveInterruptUpcallArgs *args) { assert(args); IOService::IOExclaveProxyState *ref = getProxyStateFromRegistryID(id); if (!ref || !args) { return false; } ref->service->retain(); bool res; switch (args->type) { case kIOExclaveInterruptUpcallTypeRegister: // Register interrupt res = ref->service->exclaveRegisterInterrupt(ref, args->index, args->data.register_args.test_irq); break; case kIOExclaveInterruptUpcallTypeRemove: // Remove interrupt res = ref->service->exclaveRemoveInterrupt(ref, args->index); break; case kIOExclaveInterruptUpcallTypeEnable: // Enable/disable interrupt res = ref->service->exclaveEnableInterrupt(ref, args->index, args->data.enable_args.enable); break; default: res = false; printf("%s ERROR: invalid upcall type\n", __func__); } if (!res) { printf("%s ERROR: upcall handler type %d failed\n", __func__, args->type); ref->service->release(); return false; } ref->service->release(); return true; } bool IOExclaveTimerUpcallHandler(uint64_t id, IOExclaveTimerUpcallArgs *args) { assert(args); IOService::IOExclaveProxyState *ref = getProxyStateFromRegistryID(id); if (!ref || !args) { return false; } ref->service->retain(); bool res; uint32_t timer_id = args->timer_id; switch (args->type) { case kIOExclaveTimerUpcallTypeRegister: // Register timer res = ref->service->exclaveRegisterTimer(ref, &args->timer_id); break; case kIOExclaveTimerUpcallTypeRemove: // Remove timer res = ref->service->exclaveRemoveTimer(ref, timer_id); break; case kIOExclaveTimerUpcallTypeEnable: { // Enable/disable timer bool enable = args->data.enable_args.enable; res = ref->service->exclaveEnableTimer(ref, timer_id, enable); break; } case kIOExclaveTimerUpcallTypeSetTimeout: { // Set timeout uint32_t options = args->data.set_timeout_args.clock_continuous ? kIOTimeOptionsContinuous : 0; AbsoluteTime duration = args->data.set_timeout_args.duration; kern_return_t *kr = &args->data.set_timeout_args.kr; res = ref->service->exclaveTimerSetTimeout(ref, timer_id, options, duration, 0, kr); break; } case kIOExclaveTimerUpcallTypeCancelTimeout: // Cancel timeout res = ref->service->exclaveTimerCancelTimeout(ref, timer_id); break; default: res = false; printf("%s ERROR: invalid upcall type\n", __func__); } if (!res) { printf("%s ERROR: upcall handler type %d failed\n", __func__, args->type); ref->service->release(); return false; } ref->service->release(); return true; } bool IOExclaveLockWorkloop(uint64_t id, bool lock) { IOService::IOExclaveProxyState *ref = getProxyStateFromRegistryID(id); if (!ref) { return false; } // Lock or unlock workloop if (lock) { ref->ewla->closeGate(); EXLOG("%s locked workloop\n", __func__); } else { ref->ewla->openGate(); EXLOG("%s unlocked workloop\n", __func__); } return true; } static void getExclaveInterruptKey(int index, char *key, size_t size) { snprintf(key, size, "%d", index); } static IOInterruptEventSource * copyExclaveInterruptEventSource(IOService::IOExclaveProxyState * pRef, int index) { OSObject *obj; IOInterruptEventSource *ies; char irqKey[5]; if (!pRef) { return NULL; } getExclaveInterruptKey(index, irqKey, sizeof(irqKey)); IOLockAssert(pRef->exclave_interrupts_lock, kIOLockAssertOwned); obj = pRef->exclave_interrupts->getObject(irqKey); if (!obj) { return NULL; } ies = OSDynamicCast(IOInterruptEventSource, obj); if (ies) { ies->retain(); } return ies; } // TODO: Remove after testing void IOExclaveTestSignalInterrupt(thread_call_param_t arg0, __unused thread_call_param_t arg1) { EXLOG("%s called\n", __func__); // Unpackage params struct IOExclaveTestSignalInterruptParam *params = (struct IOExclaveTestSignalInterruptParam *) arg0; if (params->id == -1 || params->index == -1) { printf("%s: ERROR: id and irq index not initialized\n", __func__); return; } uint64_t id = params->id; int index = (int) params->index; IOService::IOExclaveProxyState *ref = getProxyStateFromRegistryID(id); if (!ref) { return; } ref->service->retain(); // Get interrupt char irqKey[5]; getExclaveInterruptKey(index, irqKey, sizeof(irqKey)); OSObject *obj2 = ref->exclave_interrupts->getObject(irqKey); if (!obj2) { printf("%s: ERROR: failed to get ies\n", __func__); ref->service->release(); return; } IOInterruptEventSource *ies = OSDynamicCast(IOInterruptEventSource, obj2); if (!ies) { printf("%s: ERROR: failed to cast ies\n", __func__); ref->service->release(); return; } // Signal interrupt ies->interruptOccurred(NULL, NULL, 1); ref->service->release(); } bool IOExclaveAsyncNotificationUpcallHandler(uint64_t id, struct IOExclaveAsyncNotificationUpcallArgs *args) { IOService::IOExclaveProxyState *ref = getProxyStateFromRegistryID(id); bool ret = false; if (!ref) { return false; } switch (args->type) { case AsyncNotificationUpcallTypeSignal: ret = ref->service->exclaveAsyncNotificationSignal(ref, args->notificationID) == kIOReturnSuccess; break; default: ret = false; break; } return ret; } bool IOExclaveMapperOperationUpcallHandler(uint64_t id, IOExclaveMapperOperationUpcallArgs *args) { assert(args); IOService *provider = NULL; bool res = false; IOService::IOExclaveProxyState *ref = getProxyStateFromRegistryID(id); if (!ref) { return false; } provider = ref->service->getProvider(); IOMapper *mapper = IOMapper::copyMapperForDeviceWithIndex(provider, (unsigned int)(args->mapperIndex)); if (!mapper) { goto finish; } switch (args->type) { case MapperActivate: res = kIOReturnSuccess == mapper->callPlatformFunction(gDARTMapperFunctionSetActive, false, (void *)(true), (void *)(false), NULL, NULL); break; case MapperDeactivate: res = kIOReturnSuccess == mapper->callPlatformFunction(gDARTMapperFunctionSetActive, false, (void *)(false), (void *)(false), NULL, NULL); break; default: break; } finish: return res; } bool IOExclaveANEUpcallHandler(uint64_t id, struct IOExclaveANEUpcallArgs *args, bool *result) { IOService::IOExclaveProxyState *ref = getProxyStateFromRegistryID(id); bool ret = false; bool _result = false; if (!ref || !args) { return false; } switch (args->type) { case kIOExclaveANEUpcallTypeSetPowerState: if (ref->aneSetPowerStateUpcallHandler) { _result = (ref->aneSetPowerStateUpcallHandler)( args->setpowerstate_args.desired_state ); ret = true; } else { printf("%s: no handler for upcall %d registered\n", __func__, (int)args->type); } break; case kIOExclaveANEUpcallTypeWorkSubmit: if (ref->aneWorkSubmitUpcallHandler) { _result = (ref->aneWorkSubmitUpcallHandler)( args->work_args.arg0, args->work_args.arg1, args->work_args.arg2 ); ret = true; } else { printf("%s: no handler for upcall %d registered\n", __func__, (int)args->type); } break; case kIOExclaveANEUpcallTypeWorkBegin: if (ref->aneWorkBeginUpcallHandler) { _result = (ref->aneWorkBeginUpcallHandler)( args->work_args.arg0, args->work_args.arg1, args->work_args.arg2 ); ret = true; } else { printf("%s: no handler for upcall %d registered\n", __func__, (int)args->type); } break; case kIOExclaveANEUpcallTypeWorkEnd: if (ref->aneWorkEndUpcallHandler) { _result = (ref->aneWorkEndUpcallHandler)( args->work_args.arg0, args->work_args.arg1, args->work_args.arg2 ); ret = true; } else { printf("%s: no handler for upcall %d registered\n", __func__, (int)args->type); } break; default: ret = false; break; } if (result) { *result = _result; } return ret; } IOReturn IOExclaveLPWUpcallHandler(struct IOExclaveLPWUpcallArgs *args) { IOReturn ret; if (!args) { return kIOReturnBadArgument; } IOPMrootDomain *rootDomain = IOService::getPMRootDomain(); if (!rootDomain) { return kIOReturnInternalError; } switch (args->type) { case kIOExclaveLPWUpcallTypeCreateAssertion: return IOExclaveLPWCreateAssertion(&args->data.createassertion.id_out, "Exclave LPW assertion"); case kIOExclaveLPWUpcallTypeReleaseAssertion: return IOExclaveLPWReleaseAssertion(args->data.releaseassertion.id); case kIOExclaveLPWUpcallTypeRequestRunMode: ret = rootDomain->requestRunMode(args->data.requestrunmode.runmode_mask); break; default: return kIOReturnBadArgument; } return ret; } IOReturn IOExclaveLPWCreateAssertion(uint64_t *id_out, const char *desc) { IOPMDriverAssertionID assertionID; IOPMrootDomain *rootDomain = IOService::getPMRootDomain(); if (!rootDomain) { return kIOReturnInternalError; } assertionID = rootDomain->createPMAssertion( kIOPMDriverAssertionCPUBit | kIOPMDriverAssertionForceWakeupBit, kIOPMDriverAssertionLevelOn, rootDomain, desc); if (assertionID == 0) { return kIOReturnInternalError; } *id_out = (uint64_t) assertionID; return kIOReturnSuccess; } IOReturn IOExclaveLPWReleaseAssertion(uint64_t id) { IOPMrootDomain *rootDomain = IOService::getPMRootDomain(); if (!rootDomain) { return kIOReturnInternalError; } return rootDomain->releasePMAssertion(id); } /* IOService exclave methods */ #endif /* CONFIG_EXCLAVES */ bool IOService::exclaveRegisterInterrupt(IOExclaveProxyState * pRef, int index, bool noProvider = false) { #if CONFIG_EXCLAVES IOInterruptEventSource *ies = NULL; IOInterruptEventSource::Action action; IOWorkLoop *wl; char irqKey[5]; assert(getWorkLoop()); if (!pRef) { return false; } action = OSMemberFunctionCast(IOInterruptEventSource::Action, this, &IOService::exclaveInterruptOccurred); ies = IOInterruptEventSource::interruptEventSource(this, action, noProvider ? nullptr : getProvider(), index); if (!ies) { return false; } wl = getWorkLoop(); if (!wl) { ies->release(); return false; } if (wl->addEventSource(ies) != kIOReturnSuccess) { ies->release(); return false; } // Register IOIES in exclave proxy state getExclaveInterruptKey(index, irqKey, sizeof(irqKey)); IOLockLock(pRef->exclave_interrupts_lock); pRef->exclave_interrupts->setObject(irqKey, ies); IOLockUnlock(pRef->exclave_interrupts_lock); OSSafeReleaseNULL(ies); EXLOG("%s: IRQ %d register success!\n", __func__, index); return true; #else /* CONFIG_EXCLAVES */ return false; #endif /* CONFIG_EXCLAVES */ } bool IOService::exclaveRemoveInterrupt(IOExclaveProxyState * pRef, int index) { #if CONFIG_EXCLAVES IOInterruptEventSource *ies; IOWorkLoop *wl; char irqKey[5]; assert(getWorkLoop()); if (!pRef) { return false; } getExclaveInterruptKey(index, irqKey, sizeof(irqKey)); IOLockLock(pRef->exclave_interrupts_lock); ies = copyExclaveInterruptEventSource(pRef, index); if (!ies) { IOLockUnlock(pRef->exclave_interrupts_lock); OSSafeReleaseNULL(ies); return false; } pRef->exclave_interrupts->removeObject(irqKey); IOLockUnlock(pRef->exclave_interrupts_lock); wl = getWorkLoop(); if (!wl) { OSSafeReleaseNULL(ies); return false; } wl->removeEventSource(ies); OSSafeReleaseNULL(ies); EXLOG("%s: IRQ %d removed successfully\n", __func__, index); return true; #else /* CONFIG_EXCLAVES */ return false; #endif /* CONFIG_EXCLAVES */ } bool IOService::exclaveEnableInterrupt(IOExclaveProxyState * pRef, int index, bool enable) { #if CONFIG_EXCLAVES IOInterruptEventSource *ies; assert(getWorkLoop()); if (!pRef) { return false; } IOLockLock(pRef->exclave_interrupts_lock); ies = copyExclaveInterruptEventSource(pRef, index); if (!ies) { IOLockUnlock(pRef->exclave_interrupts_lock); OSSafeReleaseNULL(ies); return false; } if (enable) { ies->enable(); } else { ies->disable(); } IOLockUnlock(pRef->exclave_interrupts_lock); OSSafeReleaseNULL(ies); EXLOG("%s: IRQ %s success!\n", __func__, enable ? "enable" : "disable"); return true; #else /* CONFIG_EXCLAVES */ return false; #endif /* CONFIG_EXCLAVES */ } void IOService::exclaveInterruptOccurred(IOInterruptEventSource *eventSource, int count) { #if CONFIG_EXCLAVES tb_error_t tberr; IOService::IOExclaveProxyState *ref; if (!eventSource) { printf("%s ERROR: IOInterruptEventSource is null\n", __func__); return; } EXLOG("%s id 0x%llx (irq index %d)\n", __func__, getRegistryEntryID(), eventSource->getIntIndex()); ref = getProxyStateFromRegistryID(getRegistryEntryID()); if (!ref) { printf("%s ERROR: failed to get IOExclaveProxyState\n", __func__); return; } tberr = ioservice_ioserviceprivate_interruptoccurredprivate(&ref->edk_client, eventSource->getIntIndex(), count); assert(TB_ERROR_SUCCESS == tberr); if (TB_ERROR_SUCCESS != tberr) { printf("%s ERROR: tightbeam call failed\n", __func__); return; } #endif /* CONFIG_EXCLAVES */ } #if CONFIG_EXCLAVES static void getExclaveTimerKey(uint32_t timer_id, char *key, size_t size) { snprintf(key, size, "%d", timer_id); } static IOTimerEventSource * copyExclaveTimerEventSource(IOService::IOExclaveProxyState * pRef, uint32_t timer_id) { OSObject *obj; IOTimerEventSource *tes; char timerKey[5]; if (!pRef) { return NULL; } getExclaveTimerKey(timer_id, timerKey, sizeof(timerKey)); IOLockAssert(pRef->exclave_timers_lock, kIOLockAssertOwned); obj = pRef->exclave_timers->getObject(timerKey); if (!obj) { return NULL; } tes = OSDynamicCast(IOTimerEventSource, obj); if (tes) { tes->retain(); } return tes; } #endif /* CONFIG_EXCLAVES */ bool IOService::exclaveRegisterTimer(IOExclaveProxyState * pRef, uint32_t *timer_id) { #if CONFIG_EXCLAVES IOTimerEventSource *tes = NULL; IOTimerEventSource::Action action; IOWorkLoop *wl; char timerKey[5]; assert(getWorkLoop()); if (!pRef || !timer_id) { return false; } action = OSMemberFunctionCast(IOTimerEventSource::Action, this, &IOService::exclaveTimerFired); tes = IOTimerEventSource::timerEventSource(this, action); if (!tes) { return false; } wl = getWorkLoop(); if (!wl) { tes->release(); return false; } if (wl->addEventSource(tes) != kIOReturnSuccess) { tes->release(); return false; } // Register IOTES in exclave proxy state IOLockLock(pRef->exclave_timers_lock); *timer_id = pRef->nextExclaveTimerId++; getExclaveTimerKey(*timer_id, timerKey, sizeof(timerKey)); pRef->exclave_timers->setObject(timerKey, tes); IOLockUnlock(pRef->exclave_timers_lock); OSSafeReleaseNULL(tes); EXLOG("%s: timer %u register success!\n", __func__, *timer_id); return true; #else /* CONFIG_EXCLAVES */ return false; #endif /* CONFIG_EXCLAVES */ } bool IOService::exclaveRemoveTimer(IOExclaveProxyState * pRef, uint32_t timer_id) { #if CONFIG_EXCLAVES IOTimerEventSource *tes; IOWorkLoop *wl; char timerKey[5]; assert(getWorkLoop()); if (!pRef) { return false; } getExclaveTimerKey(timer_id, timerKey, sizeof(timerKey)); IOLockLock(pRef->exclave_timers_lock); tes = copyExclaveTimerEventSource(pRef, timer_id); if (!tes) { IOLockUnlock(pRef->exclave_timers_lock); OSSafeReleaseNULL(tes); return false; } pRef->exclave_timers->removeObject(timerKey); IOLockUnlock(pRef->exclave_timers_lock); wl = getWorkLoop(); if (!wl) { OSSafeReleaseNULL(tes); return false; } wl->removeEventSource(tes); OSSafeReleaseNULL(tes); EXLOG("%s: timer %u removed successfully\n", __func__, timer_id); return true; #else /* CONFIG_EXCLAVES */ return false; #endif /* CONFIG_EXCLAVES */ } bool IOService::exclaveEnableTimer(IOExclaveProxyState * pRef, uint32_t timer_id, bool enable) { #if CONFIG_EXCLAVES IOTimerEventSource *tes; assert(getWorkLoop()); if (!pRef) { return false; } IOLockLock(pRef->exclave_timers_lock); tes = copyExclaveTimerEventSource(pRef, timer_id); if (!tes) { IOLockUnlock(pRef->exclave_timers_lock); OSSafeReleaseNULL(tes); return false; } if (enable) { tes->enable(); } else { tes->disable(); } IOLockUnlock(pRef->exclave_timers_lock); OSSafeReleaseNULL(tes); EXLOG("%s: timer %u %s success\n", __func__, timer_id, enable ? "enable" : "disable"); return true; #else /* CONFIG_EXCLAVES */ return false; #endif /* CONFIG_EXCLAVES */ } bool IOService::exclaveTimerSetTimeout(IOExclaveProxyState * pRef, uint32_t timer_id, uint32_t options, AbsoluteTime interval, AbsoluteTime leeway, kern_return_t *kr) { #if CONFIG_EXCLAVES IOTimerEventSource *tes; assert(getWorkLoop()); if (!pRef || !kr) { return false; } IOLockLock(pRef->exclave_timers_lock); tes = copyExclaveTimerEventSource(pRef, timer_id); if (!tes) { IOLockUnlock(pRef->exclave_timers_lock); OSSafeReleaseNULL(tes); return false; } *kr = tes->setTimeout(options, interval, leeway); IOLockUnlock(pRef->exclave_timers_lock); OSSafeReleaseNULL(tes); EXLOG("%s: timer %u setTimeout completed (kr %d)\n", __func__, timer_id, *kr); return true; #else /* CONFIG_EXCLAVES */ return false; #endif /* CONFIG_EXCLAVES */ } bool IOService::exclaveTimerCancelTimeout(IOExclaveProxyState * pRef, uint32_t timer_id) { #if CONFIG_EXCLAVES IOTimerEventSource *tes; assert(getWorkLoop()); if (!pRef) { return false; } IOLockLock(pRef->exclave_timers_lock); tes = copyExclaveTimerEventSource(pRef, timer_id); if (!tes) { IOLockUnlock(pRef->exclave_timers_lock); OSSafeReleaseNULL(tes); return false; } tes->cancelTimeout(); IOLockUnlock(pRef->exclave_timers_lock); OSSafeReleaseNULL(tes); EXLOG("%s: timer %u setTimeout success\n", __func__, timer_id); return true; #else /* CONFIG_EXCLAVES */ return false; #endif /* CONFIG_EXCLAVES */ } void IOService::exclaveTimerFired(IOTimerEventSource *eventSource) { #if CONFIG_EXCLAVES tb_error_t tberr; IOService::IOExclaveProxyState *ref; __block bool found = false; __block uint32_t timer_id; if (!eventSource) { printf("%s ERROR: IOTimerEventSource is null\n", __func__); return; } ref = getProxyStateFromRegistryID(getRegistryEntryID()); if (!ref) { printf("%s ERROR: failed to get IOExclaveProxyState\n", __func__); return; } // Find timer ID IOLockLock(ref->exclave_timers_lock); ref->exclave_timers->iterateObjects(^bool (const OSSymbol * id, OSObject * obj) { if (obj == eventSource) { found = true; const char *key = id->getCStringNoCopy(); timer_id = (uint32_t) strtol(key, NULL, 0); return true; } return false; }); IOLockUnlock(ref->exclave_timers_lock); if (!found) { printf("%s ERROR: Could not find timer ID\n", __func__); return; } EXLOG("%s id 0x%llx (timer_id %u)\n", __func__, getRegistryEntryID(), timer_id); tberr = ioservice_ioserviceprivate_timerfiredprivate(&ref->edk_client, timer_id); assert(TB_ERROR_SUCCESS == tberr); if (TB_ERROR_SUCCESS != tberr) { printf("%s ERROR: tightbeam call failed\n", __func__); return; } #endif /* CONFIG_EXCLAVES */ } kern_return_t IOService::exclaveAsyncNotificationRegister(IOExclaveProxyState * pRef, IOInterruptEventSource *notification, uint32_t *notificationID) { #if CONFIG_EXCLAVES kern_return_t ret; if (notification == NULL) { return kIOReturnBadArgument; } IOLockLock(pRef->exclaveAsyncNotificationEventSourcesLock); if (!pRef->exclaveAsyncNotificationEventSources) { pRef->exclaveAsyncNotificationEventSources = OSArray::withCapacity(1); } if (pRef->exclaveAsyncNotificationEventSources) { *notificationID = (uint32_t) pRef->exclaveAsyncNotificationEventSources->getCount(); pRef->exclaveAsyncNotificationEventSources->setObject(notification); ret = kIOReturnSuccess; } else { ret = kIOReturnNoMemory; } IOLockUnlock(pRef->exclaveAsyncNotificationEventSourcesLock); return ret; #else #pragma unused(pRef, notification, notificationID) return kIOReturnUnsupported; #endif /* CONFIG_EXCLAVES*/ } kern_return_t IOService::exclaveAsyncNotificationSignal(IOExclaveProxyState * pRef, uint32_t notificationID) { #if CONFIG_EXCLAVES kern_return_t ret; IOInterruptEventSource *event; IOLockLock(pRef->exclaveAsyncNotificationEventSourcesLock); if (pRef->exclaveAsyncNotificationEventSources && (event = OSDynamicCast(IOInterruptEventSource, pRef->exclaveAsyncNotificationEventSources->getObject((unsigned int)notificationID)))) { event->interruptOccurred(NULL, NULL, 0); ret = kIOReturnSuccess; } else { ret = kIOReturnError; } IOLockUnlock(pRef->exclaveAsyncNotificationEventSourcesLock); return ret; #else #pragma unused(pRef, notificationID) return kIOReturnUnsupported; #endif /* CONFIG_EXCLAVES */ } #if CONFIG_EXCLAVES void exclaves_wait_for_cpu_init() { OSDictionary *match_dict = IOService::resourceMatching(gIOAllCPUInitializedKey); IOService *match = IOService::waitForMatchingService(match_dict); match_dict->release(); match->release(); } /* ANE Upcalls */ static kern_return_t exclaveRegisterANEUpcallHelper(IOService::IOExclaveProxyState * pRef, IOExclaveANEUpcallType type, IOWorkLoop *wl, void *block) { void __block * _block = block; if (!_block) { return kIOReturnBadArgument; } if (!pRef) { Block_release(_block); return kIOReturnBadArgument; } if (wl != NULL) { return wl->runActionBlock(^{ switch (type) { case kIOExclaveANEUpcallTypeSetPowerState: if (pRef->aneSetPowerStateUpcallHandler) { Block_release(pRef->aneSetPowerStateUpcallHandler); } pRef->aneSetPowerStateUpcallHandler = (ANEUpcallSetPowerStateHandler) _block; break; case kIOExclaveANEUpcallTypeWorkSubmit: if (pRef->aneWorkSubmitUpcallHandler) { Block_release(pRef->aneWorkSubmitUpcallHandler); } pRef->aneWorkSubmitUpcallHandler = (ANEUpcallWorkHandler) _block; break; case kIOExclaveANEUpcallTypeWorkBegin: if (pRef->aneWorkBeginUpcallHandler) { Block_release(pRef->aneWorkBeginUpcallHandler); } pRef->aneWorkBeginUpcallHandler = (ANEUpcallWorkHandler) _block; break; case kIOExclaveANEUpcallTypeWorkEnd: if (pRef->aneWorkEndUpcallHandler) { Block_release(pRef->aneWorkEndUpcallHandler); } pRef->aneWorkEndUpcallHandler = (ANEUpcallWorkHandler) _block; break; default: Block_release(_block); return kIOReturnBadArgument; } return kIOReturnSuccess; }); } else { Block_release(_block); return kIOReturnError; } } #endif /* CONFIG_EXCLAVES */ kern_return_t IOService::exclaveRegisterANEUpcallSetPowerState(IOExclaveProxyState * pRef, ANEUpcallSetPowerStateHandler handler) { #if CONFIG_EXCLAVES return exclaveRegisterANEUpcallHelper(pRef, kIOExclaveANEUpcallTypeSetPowerState, getWorkLoop(), Block_copy(handler)); #else #pragma unused(pRef, handler) return kIOReturnUnsupported; #endif /* CONFIG_EXCLAVES*/ } kern_return_t IOService::exclaveRegisterANEUpcallWorkSubmit(IOExclaveProxyState * pRef, ANEUpcallWorkHandler handler) { #if CONFIG_EXCLAVES return exclaveRegisterANEUpcallHelper(pRef, kIOExclaveANEUpcallTypeWorkSubmit, getWorkLoop(), Block_copy(handler)); #else #pragma unused(pRef, handler) return kIOReturnUnsupported; #endif /* CONFIG_EXCLAVES*/ } kern_return_t IOService::exclaveRegisterANEUpcallWorkBegin(IOExclaveProxyState * pRef, ANEUpcallWorkHandler handler) { #if CONFIG_EXCLAVES return exclaveRegisterANEUpcallHelper(pRef, kIOExclaveANEUpcallTypeWorkBegin, getWorkLoop(), Block_copy(handler)); #else #pragma unused(pRef, handler) return kIOReturnUnsupported; #endif /* CONFIG_EXCLAVES*/ } kern_return_t IOService::exclaveRegisterANEUpcallWorkEnd(IOExclaveProxyState * pRef, ANEUpcallWorkHandler handler) { #if CONFIG_EXCLAVES return exclaveRegisterANEUpcallHelper(pRef, kIOExclaveANEUpcallTypeWorkEnd, getWorkLoop(), Block_copy(handler)); #else #pragma unused(pRef, handler) return kIOReturnUnsupported; #endif /* CONFIG_EXCLAVES*/ }