--- /dev/null
+/*
+ * Copyright (c) 2010 Apple Computer, 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 <sys/sysctl.h>
+#include <kern/host.h>
+
+#include <IOKit/system.h>
+#include <libkern/c++/OSKext.h>
+#include <libkern/OSAtomic.h>
+
+#include <IOKit/IOStatisticsPrivate.h>
+#include <IOKit/IOUserClient.h>
+#include <IOKit/IOEventSource.h>
+#include <IOKit/IOKitDebug.h>
+
+#if IOKITSTATS
+
+bool IOStatistics::enabled = false;
+
+uint32_t IOStatistics::sequenceID = 0;
+
+uint32_t IOStatistics::lastClassIndex = 0;
+uint32_t IOStatistics::lastKextIndex = 0;
+
+uint32_t IOStatistics::loadedKexts = 0;
+uint32_t IOStatistics::registeredClasses = 0;
+uint32_t IOStatistics::registeredCounters = 0;
+uint32_t IOStatistics::registeredWorkloops = 0;
+
+uint32_t IOStatistics::attachedEventSources = 0;
+
+IOWorkLoopDependency *IOStatistics::nextWorkLoopDependency = NULL;
+
+/* Logging */
+
+#define LOG_LEVEL 0
+
+#define LOG(level, format, ...) \
+do { \
+ if (level <= LOG_LEVEL) \
+ printf(format, ##__VA_ARGS__); \
+} while (0)
+
+/* Locks */
+
+IORWLock *IOStatistics::lock = NULL;
+
+/* Kext tree */
+
+KextNode *IOStatistics::kextHint = NULL;
+
+IOStatistics::KextTreeHead IOStatistics::kextHead = RB_INITIALIZER(&IOStatistics::kextHead);
+
+int IOStatistics::kextNodeCompare(KextNode *e1, KextNode *e2)
+{
+ if (e1->kext < e2->kext)
+ return -1;
+ else if (e1->kext > e2->kext)
+ return 1;
+ else
+ return 0;
+}
+
+RB_GENERATE(IOStatistics::KextTree, KextNode, link, kextNodeCompare);
+
+/* Kext tree ordered by address */
+
+IOStatistics::KextAddressTreeHead IOStatistics::kextAddressHead = RB_INITIALIZER(&IOStatistics::kextAddressHead);
+
+int IOStatistics::kextAddressNodeCompare(KextNode *e1, KextNode *e2)
+{
+ if (e1->address < e2->address)
+ return -1;
+ else if (e1->address > e2->address)
+ return 1;
+ else
+ return 0;
+}
+
+RB_GENERATE(IOStatistics::KextAddressTree, KextNode, addressLink, kextAddressNodeCompare);
+
+/* Class tree */
+
+IOStatistics::ClassTreeHead IOStatistics::classHead = RB_INITIALIZER(&IOStatistics::classHead);
+
+int IOStatistics::classNodeCompare(ClassNode *e1, ClassNode *e2) {
+ if (e1->metaClass < e2->metaClass)
+ return -1;
+ else if (e1->metaClass > e2->metaClass)
+ return 1;
+ else
+ return 0;
+}
+
+RB_GENERATE(IOStatistics::ClassTree, ClassNode, tLink, classNodeCompare);
+
+/* Workloop dependencies */
+
+int IOWorkLoopCounter::loadTagCompare(IOWorkLoopDependency *e1, IOWorkLoopDependency *e2) {
+ if (e1->loadTag < e2->loadTag)
+ return -1;
+ else if (e1->loadTag > e2->loadTag)
+ return 1;
+ else
+ return 0;
+}
+
+RB_GENERATE(IOWorkLoopCounter::DependencyTree, IOWorkLoopDependency, link, IOWorkLoopCounter::loadTagCompare);
+
+/* sysctl stuff */
+
+static int
+oid_sysctl(__unused struct sysctl_oid *oidp, __unused void *arg1, int arg2, struct sysctl_req *req)
+{
+ int error = EINVAL;
+ uint32_t request = arg2;
+
+ switch (request)
+ {
+ case kIOStatisticsGeneral:
+ error = IOStatistics::getStatistics(req);
+ break;
+ case kIOStatisticsWorkLoop:
+ error = IOStatistics::getWorkLoopStatistics(req);
+ break;
+ case kIOStatisticsUserClient:
+ error = IOStatistics::getUserClientStatistics(req);
+ break;
+ default:
+ break;
+ }
+
+ return error;
+}
+
+SYSCTL_NODE(_debug, OID_AUTO, iokit_statistics, CTLFLAG_RW | CTLFLAG_LOCKED, 0, "IOStatistics");
+
+static SYSCTL_PROC(_debug_iokit_statistics, OID_AUTO, general,
+ CTLTYPE_STRUCT | CTLFLAG_RD | CTLFLAG_NOAUTO | CTLFLAG_KERN | CTLFLAG_LOCKED,
+ 0, kIOStatisticsGeneral, oid_sysctl, "S", "");
+
+static SYSCTL_PROC(_debug_iokit_statistics, OID_AUTO, workloop,
+ CTLTYPE_STRUCT | CTLFLAG_RD | CTLFLAG_NOAUTO | CTLFLAG_KERN | CTLFLAG_LOCKED,
+ 0, kIOStatisticsWorkLoop, oid_sysctl, "S", "");
+
+static SYSCTL_PROC(_debug_iokit_statistics, OID_AUTO, userclient,
+ CTLTYPE_STRUCT | CTLFLAG_RW | CTLFLAG_NOAUTO | CTLFLAG_KERN | CTLFLAG_LOCKED,
+ 0, kIOStatisticsUserClient, oid_sysctl, "S", "");
+
+void IOStatistics::initialize()
+{
+ if (enabled) {
+ return;
+ }
+
+#if DEVELOPMENT || DEBUG
+ /* Always enabled in development and debug builds. */
+#else
+ /* Only enabled in release builds if the boot argument is set. */
+ if (!(kIOStatistics & gIOKitDebug)) {
+ return;
+ }
+#endif
+
+ sysctl_register_oid(&sysctl__debug_iokit_statistics_general);
+ sysctl_register_oid(&sysctl__debug_iokit_statistics_workloop);
+ sysctl_register_oid(&sysctl__debug_iokit_statistics_userclient);
+
+ lock = IORWLockAlloc();
+ if (!lock) {
+ return;
+ }
+
+ nextWorkLoopDependency = (IOWorkLoopDependency*)kalloc(sizeof(IOWorkLoopDependency));
+ if (!nextWorkLoopDependency) {
+ return;
+ }
+
+ enabled = true;
+}
+
+void IOStatistics::onKextLoad(OSKext *kext, kmod_info_t *kmod_info)
+{
+ KextNode *ke;
+
+ assert(kext && kmod_info);
+
+ if (!enabled) {
+ return;
+ }
+
+ LOG(1, "IOStatistics::onKextLoad: %s, tag %d, address 0x%llx, address end 0x%llx\n",
+ kext->getIdentifierCString(), kmod_info->id, (uint64_t)kmod_info->address, (uint64_t)(kmod_info->address + kmod_info->size));
+
+ ke = (KextNode *)kalloc(sizeof(KextNode));
+ if (!ke) {
+ return;
+ }
+
+ memset(ke, 0, sizeof(KextNode));
+
+ ke->kext = kext;
+ ke->loadTag = kmod_info->id;
+ ke->address = kmod_info->address;
+ ke->address_end = kmod_info->address + kmod_info->size;
+
+ SLIST_INIT(&ke->classList);
+ TAILQ_INIT(&ke->userClientCallList);
+
+ IORWLockWrite(lock);
+
+ RB_INSERT(KextTree, &kextHead, ke);
+ RB_INSERT(KextAddressTree, &kextAddressHead, ke);
+
+ sequenceID++;
+ loadedKexts++;
+ lastKextIndex++;
+
+ IORWLockUnlock(lock);
+}
+
+void IOStatistics::onKextUnload(OSKext *kext)
+{
+ KextNode sought, *found;
+
+ assert(kext);
+
+ if (!enabled) {
+ return;
+ }
+
+ LOG(1, "IOStatistics::onKextUnload: %s\n", kext->getIdentifierCString());
+
+ IORWLockWrite(lock);
+
+ sought.kext = kext;
+ found = RB_FIND(KextTree, &kextHead, &sought);
+ if (found) {
+ IOWorkLoopCounter *wlc;
+ IOUserClientProcessEntry *uce;
+
+ /* Free up the list of counters */
+ while ((wlc = SLIST_FIRST(&found->workLoopList))) {
+ SLIST_REMOVE_HEAD(&found->workLoopList, link);
+ kfree(wlc, sizeof(IOWorkLoopCounter));
+ }
+
+ /* Free up the user client list */
+ while ((uce = TAILQ_FIRST(&found->userClientCallList))) {
+ TAILQ_REMOVE(&found->userClientCallList, uce, link);
+ kfree(uce, sizeof(IOUserClientProcessEntry));
+ }
+
+ /* Remove from kext trees */
+ RB_REMOVE(KextTree, &kextHead, found);
+ RB_REMOVE(KextAddressTree, &kextAddressHead, found);
+
+ /*
+ * Clear a matching kextHint to avoid use after free in
+ * onClassAdded() for a class add after a KEXT unload.
+ */
+ if (found == kextHint) {
+ kextHint = NULL;
+ }
+
+ /* Finally, free the class node */
+ kfree(found, sizeof(KextNode));
+
+ sequenceID++;
+ loadedKexts--;
+ }
+ else {
+ panic("IOStatistics::onKextUnload: cannot find kext: %s", kext->getIdentifierCString());
+ }
+
+ IORWLockUnlock(lock);
+}
+
+void IOStatistics::onClassAdded(OSKext *parentKext, OSMetaClass *metaClass)
+{
+ ClassNode *ce;
+ KextNode soughtKext, *foundKext = NULL;
+
+ assert(parentKext && metaClass);
+
+ if (!enabled) {
+ return;
+ }
+
+ LOG(1, "IOStatistics::onClassAdded: %s\n", metaClass->getClassName());
+
+ ce = (ClassNode *)kalloc(sizeof(ClassNode));
+ if (!ce) {
+ return;
+ }
+
+ memset(ce, 0, sizeof(ClassNode));
+
+ IORWLockWrite(lock);
+
+ /* Hinted? */
+ if (kextHint && kextHint->kext == parentKext) {
+ foundKext = kextHint;
+ }
+ else {
+ soughtKext.kext = parentKext;
+ foundKext = RB_FIND(KextTree, &kextHead, &soughtKext);
+ }
+
+ if (foundKext) {
+ ClassNode soughtClass, *foundClass = NULL;
+ const OSMetaClass *superClass;
+
+ ce->metaClass = metaClass;
+ ce->classID = lastClassIndex++;
+ ce->parentKext = foundKext;
+
+ /* Has superclass? */
+ superClass = ce->metaClass->getSuperClass();
+ if (superClass) {
+ soughtClass.metaClass = superClass;
+ foundClass = RB_FIND(ClassTree, &classHead, &soughtClass);
+ }
+ ce->superClassID = foundClass ? foundClass->classID : (uint32_t)(-1);
+
+ SLIST_INIT(&ce->counterList);
+ SLIST_INIT(&ce->userClientList);
+
+ RB_INSERT(ClassTree, &classHead, ce);
+ SLIST_INSERT_HEAD(&foundKext->classList, ce, lLink);
+
+ foundKext->classes++;
+
+ kextHint = foundKext;
+
+ sequenceID++;
+ registeredClasses++;
+ }
+ else {
+ panic("IOStatistics::onClassAdded: cannot find parent kext: %s", parentKext->getIdentifierCString());
+ }
+
+ IORWLockUnlock(lock);
+}
+
+void IOStatistics::onClassRemoved(OSKext *parentKext, OSMetaClass *metaClass)
+{
+ ClassNode sought, *found;
+
+ assert(parentKext && metaClass);
+
+ if (!enabled) {
+ return;
+ }
+
+ LOG(1, "IOStatistics::onClassRemoved: %s\n", metaClass->getClassName());
+
+ IORWLockWrite(lock);
+
+ sought.metaClass = metaClass;
+ found = RB_FIND(ClassTree, &classHead, &sought);
+ if (found) {
+ IOEventSourceCounter *esc;
+ IOUserClientCounter *ucc;
+
+ /* Free up the list of counters */
+ while ((esc = SLIST_FIRST(&found->counterList))) {
+ SLIST_REMOVE_HEAD(&found->counterList, link);
+ kfree(esc, sizeof(IOEventSourceCounter));
+ }
+
+ /* Free up the user client list */
+ while ((ucc = SLIST_FIRST(&found->userClientList))) {
+ SLIST_REMOVE_HEAD(&found->userClientList, link);
+ kfree(ucc, sizeof(IOUserClientCounter));
+ }
+
+ /* Remove from class tree */
+ RB_REMOVE(ClassTree, &classHead, found);
+
+ /* Remove from parent */
+ SLIST_REMOVE(&found->parentKext->classList, found, ClassNode, lLink);
+
+ /* Finally, free the class node */
+ kfree(found, sizeof(ClassNode));
+
+ sequenceID++;
+ registeredClasses--;
+ }
+ else {
+ panic("IOStatistics::onClassRemoved: cannot find class: %s", metaClass->getClassName());
+ }
+
+ IORWLockUnlock(lock);
+}
+
+IOEventSourceCounter *IOStatistics::registerEventSource(OSObject *inOwner)
+{
+ IOEventSourceCounter *counter = NULL;
+ ClassNode sought, *found = NULL;
+ boolean_t createDummyCounter = FALSE;
+
+ assert(inOwner);
+
+ if (!enabled) {
+ return NULL;
+ }
+
+ counter = (IOEventSourceCounter*)kalloc(sizeof(IOEventSourceCounter));
+ if (!counter) {
+ return NULL;
+ }
+
+ memset(counter, 0, sizeof(IOEventSourceCounter));
+
+ IORWLockWrite(lock);
+
+ /* Workaround for <rdar://problem/7158117> - create a dummy counter when inOwner is bad.
+ * We use retainCount here as our best indication that the pointer is awry.
+ */
+ if (inOwner->retainCount > 0xFFFFFF) {
+ kprintf("IOStatistics::registerEventSource - bad metaclass %p\n", inOwner);
+ createDummyCounter = TRUE;
+ }
+ else {
+ sought.metaClass = inOwner->getMetaClass();
+ found = RB_FIND(ClassTree, &classHead, &sought);
+ }
+
+ if (found) {
+ counter->parentClass = found;
+ SLIST_INSERT_HEAD(&found->counterList, counter, link);
+ registeredCounters++;
+ }
+
+ if (!(createDummyCounter || found)) {
+ panic("IOStatistics::registerEventSource: cannot find parent class: %s", inOwner->getMetaClass()->getClassName());
+ }
+
+ IORWLockUnlock(lock);
+
+ return counter;
+}
+
+void IOStatistics::unregisterEventSource(IOEventSourceCounter *counter)
+{
+ if (!counter) {
+ return;
+ }
+
+ IORWLockWrite(lock);
+
+ if (counter->parentClass) {
+ SLIST_REMOVE(&counter->parentClass->counterList, counter, IOEventSourceCounter, link);
+ registeredCounters--;
+ }
+ kfree(counter, sizeof(IOEventSourceCounter));
+
+ IORWLockUnlock(lock);
+}
+
+IOWorkLoopCounter* IOStatistics::registerWorkLoop(IOWorkLoop *workLoop)
+{
+ IOWorkLoopCounter *counter = NULL;
+ KextNode *found;
+
+ assert(workLoop);
+
+ if (!enabled) {
+ return NULL;
+ }
+
+ counter = (IOWorkLoopCounter*)kalloc(sizeof(IOWorkLoopCounter));
+ if (!counter) {
+ return NULL;
+ }
+
+ memset(counter, 0, sizeof(IOWorkLoopCounter));
+
+ found = getKextNodeFromBacktrace(TRUE);
+ if (!found) {
+ panic("IOStatistics::registerWorkLoop: cannot find parent kext");
+ }
+
+ counter->parentKext = found;
+ counter->workLoop = workLoop;
+ RB_INIT(&counter->dependencyHead);
+ SLIST_INSERT_HEAD(&found->workLoopList, counter, link);
+ registeredWorkloops++;
+
+ releaseKextNode(found);
+
+ return counter;
+}
+
+void IOStatistics::unregisterWorkLoop(IOWorkLoopCounter *counter)
+{
+ if (!counter) {
+ return;
+ }
+
+ IORWLockWrite(lock);
+
+ SLIST_REMOVE(&counter->parentKext->workLoopList, counter, IOWorkLoopCounter, link);
+ kfree(counter, sizeof(IOWorkLoopCounter));
+ registeredWorkloops--;
+
+ IORWLockUnlock(lock);
+}
+
+IOUserClientCounter *IOStatistics::registerUserClient(IOUserClient *userClient)
+{
+ ClassNode sought, *found;
+ IOUserClientCounter *counter = NULL;
+
+ assert(userClient);
+
+ if (!enabled) {
+ return NULL;
+ }
+
+ counter = (IOUserClientCounter*)kalloc(sizeof(IOUserClientCounter));
+ if (!counter) {
+ return NULL;
+ }
+
+ memset(counter, 0, sizeof(IOUserClientCounter));
+
+ IORWLockWrite(lock);
+
+ sought.metaClass = userClient->getMetaClass();
+
+ found = RB_FIND(ClassTree, &classHead, &sought);
+ if (found) {
+ counter->parentClass = found;
+ SLIST_INSERT_HEAD(&found->userClientList, counter, link);
+ }
+ else {
+ panic("IOStatistics::registerUserClient: cannot find parent class: %s", sought.metaClass->getClassName());
+ }
+
+ IORWLockUnlock(lock);
+
+ return counter;
+}
+
+void IOStatistics::unregisterUserClient(IOUserClientCounter *counter)
+{
+ if (!counter) {
+ return;
+ }
+
+ IORWLockWrite(lock);
+
+ SLIST_REMOVE(&counter->parentClass->userClientList, counter, IOUserClientCounter, link);
+ kfree(counter, sizeof(IOUserClientCounter));
+
+ IORWLockUnlock(lock);
+}
+
+void IOStatistics::attachWorkLoopEventSource(IOWorkLoopCounter *wlc, IOEventSourceCounter *esc)
+{
+ if (!wlc) {
+ return;
+ }
+
+ IORWLockWrite(lock);
+
+ if (!nextWorkLoopDependency) {
+ return;
+ }
+
+ attachedEventSources++;
+ wlc->attachedEventSources++;
+
+ /* Track the kext dependency */
+ nextWorkLoopDependency->loadTag = esc->parentClass->parentKext->loadTag;
+ if (NULL == RB_INSERT(IOWorkLoopCounter::DependencyTree, &wlc->dependencyHead, nextWorkLoopDependency)) {
+ nextWorkLoopDependency = (IOWorkLoopDependency*)kalloc(sizeof(IOWorkLoopDependency));
+ }
+
+ IORWLockUnlock(lock);
+}
+
+void IOStatistics::detachWorkLoopEventSource(IOWorkLoopCounter *wlc, IOEventSourceCounter *esc)
+{
+ IOWorkLoopDependency sought, *found;
+
+ if (!wlc) {
+ return;
+ }
+
+ IORWLockWrite(lock);
+
+ attachedEventSources--;
+ wlc->attachedEventSources--;
+
+ sought.loadTag = esc->parentClass->parentKext->loadTag;
+
+ found = RB_FIND(IOWorkLoopCounter::DependencyTree, &wlc->dependencyHead, &sought);
+ if (found) {
+ RB_REMOVE(IOWorkLoopCounter::DependencyTree, &wlc->dependencyHead, found);
+ kfree(found, sizeof(IOWorkLoopDependency));
+ }
+
+ IORWLockUnlock(lock);
+}
+
+int IOStatistics::getStatistics(sysctl_req *req)
+{
+ int error;
+ uint32_t calculatedSize, size;
+ char *buffer, *ptr;
+ IOStatisticsHeader *header;
+
+ assert(IOStatistics::enabled && req);
+
+ IORWLockRead(IOStatistics::lock);
+
+ /* Work out how much we need to allocate. IOStatisticsKext is of variable size. */
+ calculatedSize = sizeof(IOStatisticsHeader) +
+ sizeof(IOStatisticsGlobal) +
+ (sizeof(IOStatisticsKext) * loadedKexts) + (sizeof(uint32_t) * registeredClasses) +
+ (sizeof(IOStatisticsMemory) * loadedKexts) +
+ (sizeof(IOStatisticsClass) * registeredClasses) +
+ (sizeof(IOStatisticsCounter) * registeredClasses) +
+ (sizeof(IOStatisticsKextIdentifier) * loadedKexts) +
+ (sizeof(IOStatisticsClassName) * registeredClasses);
+
+ /* Size request? */
+ if (req->oldptr == USER_ADDR_NULL) {
+ error = SYSCTL_OUT(req, NULL, calculatedSize);
+ goto exit;
+ }
+
+ /* Read only */
+ if (req->newptr != USER_ADDR_NULL) {
+ error = EPERM;
+ goto exit;
+ }
+
+ buffer = (char*)kalloc(calculatedSize);
+ if (!buffer) {
+ error = ENOMEM;
+ goto exit;
+ }
+
+ memset(buffer, 0, calculatedSize);
+
+ ptr = buffer;
+
+ header = (IOStatisticsHeader*)((void*)ptr);
+
+ header->sig = IOSTATISTICS_SIG;
+ header->ver = IOSTATISTICS_VER;
+
+ header->seq = sequenceID;
+
+ ptr += sizeof(IOStatisticsHeader);
+
+ /* Global data - seq, timers, interrupts, etc) */
+ header->globalStatsOffset = sizeof(IOStatisticsHeader);
+ size = copyGlobalStatistics((IOStatisticsGlobal*)((void*)ptr));
+ ptr += size;
+
+ /* Kext statistics */
+ header->kextStatsOffset = header->globalStatsOffset + size;
+ size = copyKextStatistics((IOStatisticsKext*)((void*)ptr));
+ ptr += size;
+
+ /* Memory allocation info */
+ header->memoryStatsOffset = header->kextStatsOffset + size;
+ size = copyMemoryStatistics((IOStatisticsMemory*)((void*)ptr));
+ ptr += size;
+
+ /* Class statistics */
+ header->classStatsOffset = header->memoryStatsOffset + size;
+ size = copyClassStatistics((IOStatisticsClass*)((void*)ptr));
+ ptr += size;
+
+ /* Dynamic class counter data */
+ header->counterStatsOffset = header->classStatsOffset + size;
+ size = copyCounterStatistics((IOStatisticsCounter*)((void*)ptr));
+ ptr += size;
+
+ /* Kext identifiers */
+ header->kextIdentifiersOffset = header->counterStatsOffset + size;
+ size = copyKextIdentifiers((IOStatisticsKextIdentifier*)((void*)ptr));
+ ptr += size;
+
+ /* Class names */
+ header->classNamesOffset = header->kextIdentifiersOffset + size;
+ size = copyClassNames((IOStatisticsClassName*)ptr);
+ ptr += size;
+
+ LOG(2, "IOStatistics::getStatistics - calculatedSize 0x%x, kexts 0x%x, classes 0x%x.\n",
+ calculatedSize, loadedKexts, registeredClasses);
+
+ assert( (uint32_t)(ptr - buffer) == calculatedSize );
+
+ error = SYSCTL_OUT(req, buffer, calculatedSize);
+
+ kfree(buffer, calculatedSize);
+
+exit:
+ IORWLockUnlock(IOStatistics::lock);
+ return error;
+}
+
+int IOStatistics::getWorkLoopStatistics(sysctl_req *req)
+{
+ int error;
+ uint32_t calculatedSize, size;
+ char *buffer;
+ IOStatisticsWorkLoopHeader *header;
+
+ assert(IOStatistics::enabled && req);
+
+ IORWLockRead(IOStatistics::lock);
+
+ /* Approximate how much we need to allocate (worse case estimate) */
+ calculatedSize = sizeof(IOStatisticsWorkLoop) * registeredWorkloops +
+ sizeof(uint32_t) * attachedEventSources;
+
+ /* Size request? */
+ if (req->oldptr == USER_ADDR_NULL) {
+ error = SYSCTL_OUT(req, NULL, calculatedSize);
+ goto exit;
+ }
+
+ /* Read only */
+ if (req->newptr != USER_ADDR_NULL) {
+ error = EPERM;
+ goto exit;
+ }
+
+ buffer = (char*)kalloc(calculatedSize);
+ if (!buffer) {
+ error = ENOMEM;
+ goto exit;
+ }
+
+ header = (IOStatisticsWorkLoopHeader*)((void*)buffer);
+
+ header->sig = IOSTATISTICS_SIG_WORKLOOP;
+ header->ver = IOSTATISTICS_VER;
+
+ header->seq = sequenceID;
+
+ header->workloopCount = registeredWorkloops;
+
+ size = copyWorkLoopStatistics(&header->workLoopStats);
+
+ LOG(2, "IOStatistics::getWorkLoopStatistics: calculatedSize %d, size %d\n", calculatedSize, size);
+
+ assert( size <= calculatedSize );
+
+ error = SYSCTL_OUT(req, buffer, size);
+
+ kfree(buffer, calculatedSize);
+
+exit:
+ IORWLockUnlock(IOStatistics::lock);
+ return error;
+}
+
+int IOStatistics::getUserClientStatistics(sysctl_req *req)
+{
+ int error;
+ uint32_t calculatedSize, size;
+ char *buffer;
+ uint32_t requestedLoadTag = 0;
+ IOStatisticsUserClientHeader *header;
+
+ assert(IOStatistics::enabled && req);
+
+ IORWLockRead(IOStatistics::lock);
+
+ /* Work out how much we need to allocate */
+ calculatedSize = sizeof(IOStatisticsUserClientHeader) +
+ sizeof(IOStatisticsUserClientCall) * IOKIT_STATISTICS_RECORDED_USERCLIENT_PROCS * loadedKexts;
+
+ /* Size request? */
+ if (req->oldptr == USER_ADDR_NULL) {
+ error = SYSCTL_OUT(req, NULL, calculatedSize);
+ goto exit;
+ }
+
+ /* Kext request (potentially) valid? */
+ if (!req->newptr || req->newlen < sizeof(requestedLoadTag)) {
+ error = EINVAL;
+ goto exit;
+ }
+
+ SYSCTL_IN(req, &requestedLoadTag, sizeof(requestedLoadTag));
+
+ LOG(2, "IOStatistics::getUserClientStatistics - requesting kext w/load tag: %d\n", requestedLoadTag);
+
+ buffer = (char*)kalloc(calculatedSize);
+ if (!buffer) {
+ error = ENOMEM;
+ goto exit;
+ }
+
+ header = (IOStatisticsUserClientHeader*)((void*)buffer);
+
+ header->sig = IOSTATISTICS_SIG_USERCLIENT;
+ header->ver = IOSTATISTICS_VER;
+
+ header->seq = sequenceID;
+
+ header->processes = 0;
+
+ size = copyUserClientStatistics(header, requestedLoadTag);
+
+ assert((sizeof(IOStatisticsUserClientHeader) + size) <= calculatedSize);
+
+ if (size) {
+ error = SYSCTL_OUT(req, buffer, sizeof(IOStatisticsUserClientHeader) + size);
+ }
+ else {
+ error = EINVAL;
+ }
+
+ kfree(buffer, calculatedSize);
+
+exit:
+ IORWLockUnlock(IOStatistics::lock);
+ return error;
+}
+
+uint32_t IOStatistics::copyGlobalStatistics(IOStatisticsGlobal *stats)
+{
+ stats->kextCount = loadedKexts;
+ stats->classCount = registeredClasses;
+ stats->workloops = registeredWorkloops;
+
+ return sizeof(IOStatisticsGlobal);
+}
+
+uint32_t IOStatistics::copyKextStatistics(IOStatisticsKext *stats)
+{
+ KextNode *ke;
+ ClassNode *ce;
+ uint32_t index = 0;
+
+ RB_FOREACH(ke, KextTree, &kextHead) {
+ stats->loadTag = ke->loadTag;
+ ke->kext->getSizeInfo(&stats->loadSize, &stats->wiredSize);
+
+ stats->classes = ke->classes;
+
+ /* Append indices of owned classes */
+ SLIST_FOREACH(ce, &ke->classList, lLink) {
+ stats->classIndexes[index++] = ce->classID;
+ }
+
+ stats = (IOStatisticsKext *)((void*)((char*)stats + sizeof(IOStatisticsKext) + (ke->classes * sizeof(uint32_t))));
+ }
+
+ return (sizeof(IOStatisticsKext) * loadedKexts + sizeof(uint32_t) * registeredClasses);
+}
+
+uint32_t IOStatistics::copyMemoryStatistics(IOStatisticsMemory *stats)
+{
+ KextNode *ke;
+
+ RB_FOREACH(ke, KextTree, &kextHead) {
+ stats->allocatedSize = ke->memoryCounters[kIOStatisticsMalloc];
+ stats->freedSize = ke->memoryCounters[kIOStatisticsFree];
+ stats->allocatedAlignedSize = ke->memoryCounters[kIOStatisticsMallocAligned];
+ stats->freedAlignedSize = ke->memoryCounters[kIOStatisticsFreeAligned];
+ stats->allocatedContiguousSize = ke->memoryCounters[kIOStatisticsMallocContiguous];
+ stats->freedContiguousSize = ke->memoryCounters[kIOStatisticsFreeContiguous];
+ stats->allocatedPageableSize = ke->memoryCounters[kIOStatisticsMallocPageable];
+ stats->freedPageableSize = ke->memoryCounters[kIOStatisticsFreePageable];
+ stats++;
+ }
+
+ return (sizeof(IOStatisticsMemory) * loadedKexts);
+}
+
+uint32_t IOStatistics::copyClassStatistics(IOStatisticsClass *stats)
+{
+ KextNode *ke;
+ ClassNode *ce;
+
+ RB_FOREACH(ke, KextTree, &kextHead) {
+ SLIST_FOREACH(ce, &ke->classList, lLink) {
+ stats->classID = ce->classID;
+ stats->superClassID = ce->superClassID;
+ stats->classSize = ce->metaClass->getClassSize();
+
+ stats++;
+ }
+ }
+
+ return sizeof(IOStatisticsClass) * registeredClasses;
+}
+
+uint32_t IOStatistics::copyCounterStatistics(IOStatisticsCounter *stats)
+{
+ KextNode *ke;
+ ClassNode *ce;
+
+ RB_FOREACH(ke, KextTree, &kextHead) {
+ SLIST_FOREACH(ce, &ke->classList, lLink) {
+ IOUserClientCounter *userClientCounter;
+ IOEventSourceCounter *counter;
+
+ stats->classID = ce->classID;
+ stats->classInstanceCount = ce->metaClass->getInstanceCount();
+
+ IOStatisticsUserClients *uc = &stats->userClientStatistics;
+
+ /* User client counters */
+ SLIST_FOREACH(userClientCounter, &ce->userClientList, link) {
+ uc->clientCalls += userClientCounter->clientCalls;
+ uc->created++;
+ }
+
+ IOStatisticsInterruptEventSources *iec = &stats->interruptEventSourceStatistics;
+ IOStatisticsInterruptEventSources *fiec = &stats->filterInterruptEventSourceStatistics;
+ IOStatisticsTimerEventSources *tec = &stats->timerEventSourceStatistics;
+ IOStatisticsCommandGates *cgc = &stats->commandGateStatistics;
+ IOStatisticsCommandQueues *cqc = &stats->commandQueueStatistics;
+ IOStatisticsDerivedEventSources *dec = &stats->derivedEventSourceStatistics;
+
+ /* Event source counters */
+ SLIST_FOREACH(counter, &ce->counterList, link) {
+ switch (counter->type) {
+ case kIOStatisticsInterruptEventSourceCounter:
+ iec->created++;
+ iec->produced += counter->u.interrupt.produced;
+ iec->checksForWork += counter->u.interrupt.checksForWork;
+ break;
+ case kIOStatisticsFilterInterruptEventSourceCounter:
+ fiec->created++;
+ fiec->produced += counter->u.filter.produced;
+ fiec->checksForWork += counter->u.filter.checksForWork;
+ break;
+ case kIOStatisticsTimerEventSourceCounter:
+ tec->created++;
+ tec->timeouts += counter->u.timer.timeouts;
+ tec->checksForWork += counter->u.timer.checksForWork;
+ tec->timeOnGate += counter->timeOnGate;
+ tec->closeGateCalls += counter->closeGateCalls;
+ tec->openGateCalls += counter->openGateCalls;
+ break;
+ case kIOStatisticsCommandGateCounter:
+ cgc->created++;
+ cgc->timeOnGate += counter->timeOnGate;
+ cgc->actionCalls += counter->u.commandGate.actionCalls;
+ break;
+ case kIOStatisticsCommandQueueCounter:
+ cqc->created++;
+ cqc->actionCalls += counter->u.commandQueue.actionCalls;
+ break;
+ case kIOStatisticsDerivedEventSourceCounter:
+ dec->created++;
+ dec->timeOnGate += counter->timeOnGate;
+ dec->closeGateCalls += counter->closeGateCalls;
+ dec->openGateCalls += counter->openGateCalls;
+ break;
+ default:
+ break;
+ }
+ }
+
+ stats++;
+ }
+ }
+
+ return sizeof(IOStatisticsCounter) * registeredClasses;
+}
+
+uint32_t IOStatistics::copyKextIdentifiers(IOStatisticsKextIdentifier *kextIDs)
+{
+ KextNode *ke;
+
+ RB_FOREACH(ke, KextTree, &kextHead) {
+ strncpy(kextIDs->identifier, ke->kext->getIdentifierCString(), kIOStatisticsDriverNameLength);
+ kextIDs++;
+ }
+
+ return (sizeof(IOStatisticsKextIdentifier) * loadedKexts);
+}
+
+uint32_t IOStatistics::copyClassNames(IOStatisticsClassName *classNames)
+{
+ KextNode *ke;
+ ClassNode *ce;
+
+ RB_FOREACH(ke, KextTree, &kextHead) {
+ SLIST_FOREACH(ce, &ke->classList, lLink) {
+ strncpy(classNames->name, ce->metaClass->getClassName(), kIOStatisticsClassNameLength);
+ classNames++;
+ }
+ }
+
+ return (sizeof(IOStatisticsClassName) * registeredClasses);
+}
+
+uint32_t IOStatistics::copyWorkLoopStatistics(IOStatisticsWorkLoop *stats)
+{
+ KextNode *ke;
+ IOWorkLoopCounter *wlc;
+ IOWorkLoopDependency *dependentNode;
+ uint32_t size, accumulatedSize = 0;
+
+ RB_FOREACH(ke, KextTree, &kextHead) {
+ SLIST_FOREACH(wlc, &ke->workLoopList, link) {
+ stats->kextLoadTag = ke->loadTag;
+ stats->attachedEventSources = wlc->attachedEventSources;
+ stats->timeOnGate = wlc->timeOnGate;
+ stats->dependentKexts = 0;
+ RB_FOREACH(dependentNode, IOWorkLoopCounter::DependencyTree, &wlc->dependencyHead) {
+ stats->dependentKextLoadTags[stats->dependentKexts] = dependentNode->loadTag;
+ stats->dependentKexts++;
+ }
+
+ size = sizeof(IOStatisticsWorkLoop) + (sizeof(uint32_t) * stats->dependentKexts);
+
+ accumulatedSize += size;
+ stats = (IOStatisticsWorkLoop*)((void*)((char*)stats + size));
+ }
+ }
+
+ return accumulatedSize;
+}
+
+uint32_t IOStatistics::copyUserClientStatistics(IOStatisticsUserClientHeader *stats, uint32_t loadTag)
+{
+ KextNode *sought, *found = NULL;
+ uint32_t procs = 0;
+ IOUserClientProcessEntry *processEntry;
+
+ RB_FOREACH(sought, KextTree, &kextHead) {
+ if (sought->loadTag == loadTag) {
+ found = sought;
+ break;
+ }
+ }
+
+ if (!found) {
+ return 0;
+ }
+
+ TAILQ_FOREACH(processEntry, &found->userClientCallList, link) {
+ strncpy(stats->userClientCalls[procs].processName, processEntry->processName, kIOStatisticsProcessNameLength);
+ stats->userClientCalls[procs].pid = processEntry->pid;
+ stats->userClientCalls[procs].calls = processEntry->calls;
+ stats->processes++;
+ procs++;
+ }
+
+ return sizeof(IOStatisticsUserClientCall) * stats->processes;
+}
+
+void IOStatistics::storeUserClientCallInfo(IOUserClient *userClient, IOUserClientCounter *counter)
+{
+ OSString *ossUserClientCreator = NULL;
+ int32_t pid = -1;
+ KextNode *parentKext;
+ IOUserClientProcessEntry *entry, *nextEntry, *prevEntry = NULL;
+ uint32_t count = 0;
+ const char *ptr = NULL;
+ OSObject *obj;
+
+ /* TODO: see if this can be more efficient */
+ obj = userClient->copyProperty("IOUserClientCreator",
+ gIOServicePlane,
+ kIORegistryIterateRecursively | kIORegistryIterateParents);
+
+ if (!obj)
+ goto err_nounlock;
+
+ ossUserClientCreator = OSDynamicCast(OSString, obj);
+
+ if (ossUserClientCreator) {
+ uint32_t len, lenIter = 0;
+
+ ptr = ossUserClientCreator->getCStringNoCopy();
+ len = ossUserClientCreator->getLength();
+
+ while ((*ptr != ' ') && (lenIter < len)) {
+ ptr++;
+ lenIter++;
+ }
+
+ if (lenIter < len) {
+ ptr++; // Skip the space
+ lenIter++;
+ pid = 0;
+ while ( (*ptr != ',') && (lenIter < len)) {
+ pid = pid*10 + (*ptr - '0');
+ ptr++;
+ lenIter++;
+ }
+
+ if(lenIter == len) {
+ pid = -1;
+ } else {
+ ptr += 2;
+ }
+ }
+ }
+
+ if (-1 == pid)
+ goto err_nounlock;
+
+ IORWLockWrite(lock);
+
+ parentKext = counter->parentClass->parentKext;
+
+ TAILQ_FOREACH(entry, &parentKext->userClientCallList, link) {
+ if (entry->pid == pid) {
+ /* Found, so increment count and move to the head */
+ entry->calls++;
+ if (count) {
+ TAILQ_REMOVE(&parentKext->userClientCallList, entry, link);
+ break;
+ }
+ else {
+ /* At the head already, so increment and return */
+ goto err_unlock;
+ }
+ }
+
+ count++;
+ }
+
+ if (!entry) {
+ if (count == IOKIT_STATISTICS_RECORDED_USERCLIENT_PROCS) {
+ /* Max elements hit, so reuse the last */
+ entry = TAILQ_LAST(&parentKext->userClientCallList, ProcessEntryList);
+ TAILQ_REMOVE(&parentKext->userClientCallList, entry, link);
+ }
+ else {
+ /* Otherwise, allocate a new entry */
+ entry = (IOUserClientProcessEntry*)kalloc(sizeof(IOUserClientProcessEntry));
+ if (!entry) {
+ IORWLockUnlock(lock);
+ return;
+ }
+ }
+
+ strncpy(entry->processName, ptr, kIOStatisticsProcessNameLength);
+ entry->pid = pid;
+ entry->calls = 1;
+ }
+
+ TAILQ_FOREACH(nextEntry, &parentKext->userClientCallList, link) {
+ if (nextEntry->calls <= entry->calls)
+ break;
+
+ prevEntry = nextEntry;
+ }
+
+ if (!prevEntry)
+ TAILQ_INSERT_HEAD(&parentKext->userClientCallList, entry, link);
+ else
+ TAILQ_INSERT_AFTER(&parentKext->userClientCallList, prevEntry, entry, link);
+
+err_unlock:
+ IORWLockUnlock(lock);
+
+err_nounlock:
+ if (obj)
+ obj->release();
+}
+
+void IOStatistics::countUserClientCall(IOUserClient *client) {
+ IOUserClient::ExpansionData *data;
+ IOUserClientCounter *counter;
+
+ /* Guard against an uninitialized client object - <rdar://problem/8577946> */
+ if (!(data = client->reserved)) {
+ return;
+ }
+
+ if ((counter = data->counter)) {
+ storeUserClientCallInfo(client, counter);
+ OSIncrementAtomic(&counter->clientCalls);
+ }
+}
+
+KextNode *IOStatistics::getKextNodeFromBacktrace(boolean_t write) {
+ const uint32_t btMin = 3;
+
+ void *bt[16];
+ unsigned btCount = sizeof(bt) / sizeof(bt[0]);
+ vm_offset_t *scanAddr = NULL;
+ uint32_t i;
+ KextNode *found = NULL, *ke = NULL;
+
+ btCount = OSBacktrace(bt, btCount);
+
+ if (write) {
+ IORWLockWrite(lock);
+ } else {
+ IORWLockRead(lock);
+ }
+
+ /* Ignore first levels */
+ scanAddr = (vm_offset_t *)&bt[btMin - 1];
+
+ for (i = 0; i < btCount; i++, scanAddr++) {
+ ke = RB_ROOT(&kextAddressHead);
+ while (ke) {
+ if (*scanAddr < ke->address) {
+ ke = RB_LEFT(ke, addressLink);
+ }
+ else {
+ if ((*scanAddr < ke->address_end) && (*scanAddr >= ke->address)) {
+ if (!ke->kext->isKernelComponent()) {
+ return ke;
+ } else {
+ found = ke;
+ }
+ }
+ ke = RB_RIGHT(ke, addressLink);
+ }
+ }
+ }
+
+ if (!found) {
+ IORWLockUnlock(lock);
+ }
+
+ return found;
+}
+
+void IOStatistics::releaseKextNode(KextNode *node) {
+#pragma unused(node)
+ IORWLockUnlock(lock);
+}
+
+/* IOLib allocations */
+void IOStatistics::countAlloc(uint32_t index, vm_size_t size) {
+ KextNode *ke;
+
+ if (!enabled) {
+ return;
+ }
+
+ ke = getKextNodeFromBacktrace(FALSE);
+ if (ke) {
+ OSAddAtomic(size, &ke->memoryCounters[index]);
+ releaseKextNode(ke);
+ }
+}
+
+#endif /* IOKITSTATS */