+//*********************************************************************************
+// PMHaltWorker Class
+//
+//*********************************************************************************
+
+static unsigned int gPMHaltBusyCount;
+static unsigned int gPMHaltIdleCount;
+static int gPMHaltDepth;
+static unsigned long gPMHaltEvent;
+static IOLock * gPMHaltLock = 0;
+static OSArray * gPMHaltArray = 0;
+static const OSSymbol * gPMHaltClientAcknowledgeKey = 0;
+
+PMHaltWorker * PMHaltWorker::worker( void )
+{
+ PMHaltWorker * me;
+ IOThread thread;
+
+ do {
+ me = OSTypeAlloc( PMHaltWorker );
+ if (!me || !me->init())
+ break;
+
+ me->lock = IOLockAlloc();
+ if (!me->lock)
+ break;
+
+ DEBUG_LOG("PMHaltWorker %p\n", me);
+ me->retain(); // thread holds extra retain
+ thread = IOCreateThread( &PMHaltWorker::main, me );
+ if (!thread)
+ {
+ me->release();
+ break;
+ }
+ return me;
+
+ } while (false);
+
+ if (me) me->release();
+ return 0;
+}
+
+void PMHaltWorker::free( void )
+{
+ DEBUG_LOG("PMHaltWorker free %p\n", this);
+ if (lock)
+ {
+ IOLockFree(lock);
+ lock = 0;
+ }
+ return OSObject::free();
+}
+
+void PMHaltWorker::main( void * arg )
+{
+ PMHaltWorker * me = (PMHaltWorker *) arg;
+
+ IOLockLock( gPMHaltLock );
+ gPMHaltBusyCount++;
+ me->depth = gPMHaltDepth;
+ IOLockUnlock( gPMHaltLock );
+
+ while (me->depth >= 0)
+ {
+ PMHaltWorker::work( me );
+
+ IOLockLock( gPMHaltLock );
+ if (++gPMHaltIdleCount >= gPMHaltBusyCount)
+ {
+ // This is the last thread to finish work on this level,
+ // inform everyone to start working on next lower level.
+ gPMHaltDepth--;
+ me->depth = gPMHaltDepth;
+ gPMHaltIdleCount = 0;
+ thread_wakeup((event_t) &gPMHaltIdleCount);
+ }
+ else
+ {
+ // One or more threads are still working on this level,
+ // this thread must wait.
+ me->depth = gPMHaltDepth - 1;
+ do {
+ IOLockSleep(gPMHaltLock, &gPMHaltIdleCount, THREAD_UNINT);
+ } while (me->depth != gPMHaltDepth);
+ }
+ IOLockUnlock( gPMHaltLock );
+ }
+
+ // No more work to do, terminate thread
+ DEBUG_LOG("All done for worker: %p (visits = %u)\n", me, me->visits);
+ thread_wakeup( &gPMHaltDepth );
+ me->release();
+}
+
+void PMHaltWorker::work( PMHaltWorker * me )
+{
+ IOService * service;
+ OSSet * inner;
+ AbsoluteTime startTime;
+ UInt32 deltaTime;
+ bool timeout;
+
+ while (true)
+ {
+ service = 0;
+ timeout = false;
+
+ // Claim an unit of work from the shared pool
+ IOLockLock( gPMHaltLock );
+ inner = (OSSet *)gPMHaltArray->getObject(me->depth);
+ if (inner)
+ {
+ service = (IOService *)inner->getAnyObject();
+ if (service)
+ {
+ service->retain();
+ inner->removeObject(service);
+ }
+ }
+ IOLockUnlock( gPMHaltLock );
+ if (!service)
+ break; // no more work at this depth
+
+ clock_get_uptime(&startTime);
+
+ if (!service->isInactive() &&
+ service->setProperty(gPMHaltClientAcknowledgeKey, me))
+ {
+ IOLockLock(me->lock);
+ me->startTime = startTime;
+ me->service = service;
+ me->timeout = false;
+ IOLockUnlock(me->lock);
+
+ service->systemWillShutdown( gPMHaltEvent );
+
+ // Wait for driver acknowledgement
+ IOLockLock(me->lock);
+ while (service->getProperty(gPMHaltClientAcknowledgeKey))
+ {
+ IOLockSleep(me->lock, me, THREAD_UNINT);
+ }
+ me->service = 0;
+ timeout = me->timeout;
+ IOLockUnlock(me->lock);
+ }
+
+ deltaTime = computeDeltaTimeMS(&startTime);
+ if ((deltaTime > kPMHaltTimeoutMS) || timeout ||
+ (gIOKitDebug & kIOLogDebugPower))
+ {
+ HaltRestartLog("%s driver %s (%p) took %lu ms\n",
+ (gPMHaltEvent == kIOMessageSystemWillPowerOff) ?
+ "PowerOff" : "Restart",
+ service->getName(), service,
+ deltaTime );
+ }
+
+ service->release();
+ me->visits++;
+ }
+}
+
+void PMHaltWorker::checkTimeout( PMHaltWorker * me, AbsoluteTime * now )
+{
+ UInt64 nano;
+ AbsoluteTime startTime;
+ AbsoluteTime endTime;
+
+ endTime = *now;
+
+ IOLockLock(me->lock);
+ if (me->service && !me->timeout)
+ {
+ startTime = me->startTime;
+ nano = 0;
+ if (CMP_ABSOLUTETIME(&endTime, &startTime) > 0)
+ {
+ SUB_ABSOLUTETIME(&endTime, &startTime);
+ absolutetime_to_nanoseconds(endTime, &nano);
+ }
+ if (nano > 3000000000ULL)
+ {
+ me->timeout = true;
+ HaltRestartLog("%s still waiting on %s\n",
+ (gPMHaltEvent == kIOMessageSystemWillPowerOff) ?
+ "PowerOff" : "Restart",
+ me->service->getName());
+ }
+ }
+ IOLockUnlock(me->lock);
+}
+
+//*********************************************************************************
+// acknowledgeSystemWillShutdown
+//
+// Acknowledgement from drivers that they have prepared for shutdown/restart.
+//*********************************************************************************
+
+void IOPMrootDomain::acknowledgeSystemWillShutdown( IOService * from )
+{
+ PMHaltWorker * worker;
+ OSObject * prop;
+
+ if (!from)
+ return;
+
+ //DEBUG_LOG("%s acknowledged\n", from->getName());
+ prop = from->copyProperty( gPMHaltClientAcknowledgeKey );
+ if (prop)
+ {
+ worker = (PMHaltWorker *) prop;
+ IOLockLock(worker->lock);
+ from->removeProperty( gPMHaltClientAcknowledgeKey );
+ thread_wakeup((event_t) worker);
+ IOLockUnlock(worker->lock);
+ worker->release();
+ }
+ else
+ {
+ DEBUG_LOG("%s acknowledged without worker property\n",
+ from->getName());
+ }
+}
+
+//*********************************************************************************
+// notifySystemShutdown
+//
+// Notify all objects in PM tree that system will shutdown or restart
+//*********************************************************************************
+
+static void
+notifySystemShutdown( IOService * root, unsigned long event )
+{
+#define PLACEHOLDER ((OSSet *)gPMHaltArray)
+ IORegistryIterator * iter;
+ IORegistryEntry * entry;
+ IOService * node;
+ OSSet * inner;
+ PMHaltWorker * workers[kPMHaltMaxWorkers];
+ AbsoluteTime deadline;
+ unsigned int totalNodes = 0;
+ unsigned int depth;
+ unsigned int rootDepth;
+ unsigned int numWorkers;
+ unsigned int count;
+ int waitResult;
+ void * baseFunc;
+ bool ok;
+
+ DEBUG_LOG("%s event = %lx\n", __FUNCTION__, event);
+
+ baseFunc = OSMemberFunctionCast(void *, root, &IOService::systemWillShutdown);
+
+ // Iterate the entire PM tree starting from root
+
+ rootDepth = root->getDepth( gIOPowerPlane );
+ if (!rootDepth) goto done;
+
+ // debug - for repeated test runs
+ while (PMHaltWorker::metaClass->getInstanceCount())
+ IOSleep(1);
+
+ if (!gPMHaltArray)
+ {
+ gPMHaltArray = OSArray::withCapacity(40);
+ if (!gPMHaltArray) goto done;
+ }
+ else // debug
+ gPMHaltArray->flushCollection();
+
+ if (!gPMHaltLock)
+ {
+ gPMHaltLock = IOLockAlloc();
+ if (!gPMHaltLock) goto done;
+ }
+
+ if (!gPMHaltClientAcknowledgeKey)
+ {
+ gPMHaltClientAcknowledgeKey =
+ OSSymbol::withCStringNoCopy("PMShutdown");
+ if (!gPMHaltClientAcknowledgeKey) goto done;
+ }
+
+ gPMHaltEvent = event;
+
+ // Depth-first walk of PM plane
+
+ iter = IORegistryIterator::iterateOver(
+ root, gIOPowerPlane, kIORegistryIterateRecursively);
+
+ if (iter)
+ {
+ while ((entry = iter->getNextObject()))
+ {
+ node = OSDynamicCast(IOService, entry);
+ if (!node)
+ continue;
+
+ if (baseFunc ==
+ OSMemberFunctionCast(void *, node, &IOService::systemWillShutdown))
+ continue;
+
+ depth = node->getDepth( gIOPowerPlane );
+ if (depth <= rootDepth)
+ continue;
+
+ ok = false;
+
+ // adjust to zero based depth
+ depth -= (rootDepth + 1);
+
+ // gPMHaltArray is an array of containers, each container
+ // refers to nodes with the same depth.
+
+ count = gPMHaltArray->getCount();
+ while (depth >= count)
+ {
+ // expand array and insert placeholders
+ gPMHaltArray->setObject(PLACEHOLDER);
+ count++;
+ }
+ count = gPMHaltArray->getCount();
+ if (depth < count)
+ {
+ inner = (OSSet *)gPMHaltArray->getObject(depth);
+ if (inner == PLACEHOLDER)
+ {
+ inner = OSSet::withCapacity(40);
+ if (inner)
+ {
+ gPMHaltArray->replaceObject(depth, inner);
+ inner->release();
+ }
+ }
+
+ // PM nodes that appear more than once in the tree will have
+ // the same depth, OSSet will refuse to add the node twice.
+ if (inner)
+ ok = inner->setObject(node);
+ }
+ if (!ok)
+ DEBUG_LOG("Skipped PM node %s\n", node->getName());
+ }
+ iter->release();
+ }
+
+ // debug only
+ for (int i = 0; (inner = (OSSet *)gPMHaltArray->getObject(i)); i++)
+ {
+ count = 0;
+ if (inner != PLACEHOLDER)
+ count = inner->getCount();
+ DEBUG_LOG("Nodes at depth %u = %u\n", i, count);
+ }
+
+ // strip placeholders (not all depths are populated)
+ numWorkers = 0;
+ for (int i = 0; (inner = (OSSet *)gPMHaltArray->getObject(i)); )
+ {
+ if (inner == PLACEHOLDER)
+ {
+ gPMHaltArray->removeObject(i);
+ continue;
+ }
+ count = inner->getCount();
+ if (count > numWorkers)
+ numWorkers = count;
+ totalNodes += count;
+ i++;
+ }
+
+ if (gPMHaltArray->getCount() == 0 || !numWorkers)
+ goto done;
+
+ gPMHaltBusyCount = 0;
+ gPMHaltIdleCount = 0;
+ gPMHaltDepth = gPMHaltArray->getCount() - 1;
+
+ // Create multiple workers (and threads)
+
+ if (numWorkers > kPMHaltMaxWorkers)
+ numWorkers = kPMHaltMaxWorkers;
+
+ DEBUG_LOG("PM nodes = %u, maxDepth = %u, workers = %u\n",
+ totalNodes, gPMHaltArray->getCount(), numWorkers);
+
+ for (unsigned int i = 0; i < numWorkers; i++)
+ workers[i] = PMHaltWorker::worker();
+
+ // Wait for workers to exhaust all available work
+
+ IOLockLock(gPMHaltLock);
+ while (gPMHaltDepth >= 0)
+ {
+ clock_interval_to_deadline(1000, kMillisecondScale, &deadline);
+
+ waitResult = IOLockSleepDeadline(
+ gPMHaltLock, &gPMHaltDepth, deadline, THREAD_UNINT);
+ if (THREAD_TIMED_OUT == waitResult)
+ {
+ AbsoluteTime now;
+ clock_get_uptime(&now);
+
+ IOLockUnlock(gPMHaltLock);
+ for (unsigned int i = 0 ; i < numWorkers; i++)
+ {
+ if (workers[i])
+ PMHaltWorker::checkTimeout(workers[i], &now);
+ }
+ IOLockLock(gPMHaltLock);
+ }
+ }
+ IOLockUnlock(gPMHaltLock);
+
+ // Release all workers
+
+ for (unsigned int i = 0; i < numWorkers; i++)
+ {
+ if (workers[i])
+ workers[i]->release();
+ // worker also retained by it's own thread
+ }
+
+done:
+ DEBUG_LOG("%s done\n", __FUNCTION__);
+ return;
+}
+
+#if DEBUG_TEST
+// debug - exercise notifySystemShutdown()
+bool IOPMrootDomain::serializeProperties( OSSerialize * s ) const
+{
+ IOPMrootDomain * root = (IOPMrootDomain *) this;
+ notifySystemShutdown( root, kIOMessageSystemWillPowerOff );
+ return( super::serializeProperties(s) );
+}
+#endif
+