#include "../IOGraphicsFamily/IOKit/graphics/IOGraphicsTypes.h"
#include <CoreFoundation/CoreFoundation.h>
#include <ApplicationServices/ApplicationServices.h>
#include <mach/mach_error.h>
#include <mach/mach_time.h>
#include <stdlib.h>
#include <stdint.h>
#include <stdio.h>
#include <getopt.h>
#include <memory.h>
#include <time.h>
#include <spawn.h>
#include <sys/wait.h>
#include "../IOGraphicsFamily/IOGraphicsDiagnose.h"
#include "../GTrace/GTrace.hpp"
#define kFILENAME_LENGTH 64
void print_usage( const char * name );
void agdcdiagnose(FILE * fOut);
void print_usage( const char * name )
{
fprintf(stdout, "%s options:\n", name);
fprintf(stdout, "\t--file | -f\n");
fprintf(stdout, "\t\tWrite report to file /tmp/com.apple.iokit.IOGraphics_YYYY_MM__DD_HH-MM-SS.txt (instead of stdout)\n");
fprintf(stdout, "\n");
fflush(stdout);
}
extern char **environ;
void agdcdiagnose(FILE * fOut)
{
pid_t pid = 0;
int status = -1;
char * const agdcArgs[] = {const_cast<char *>("/System/Library/Extensions/AppleGraphicsControl.kext/Contents/MacOS/AGDCDiagnose"),
const_cast<char *>("-a"),
nullptr};
if (NULL != fOut)
{
fprintf(fOut, "\n\n------------------------------------- AGDC REPORT -------------------------------------\n\n");
status = posix_spawn(&pid, agdcArgs[0], NULL, NULL, agdcArgs, environ);
if (0 == status)
{
(void)waitpid(pid, &status, 0);
}
else
{
fprintf(fOut, "\tAGDCDiagnose failed to launch\n");
}
fprintf(fOut, "\n---------------------------------------------------------------------------------------\n");
}
}
uint32_t sizeToLines( uint32_t bufSize )
{
uint32_t lines = 0;
if (bufSize > (kGTraceMaximumLineCount * sizeof(sGTrace)))
{
lines = kGTraceMaximumLineCount;
}
else if (bufSize >= sizeof(sGTrace))
{
lines = (1 << (32 - (__builtin_clz(bufSize) + 1))) / sizeof(sGTrace);
}
return (lines);
}
bool dumpTokenBuffer(FILE * fOut, uint32_t lastToken, uint32_t tokenLineCount, uint32_t tokenSize, uint64_t * tokenBuffer)
{
sGTrace * traceBuffer = reinterpret_cast<sGTrace *>(tokenBuffer);
uint32_t traceLines = 0;
uint32_t currentLine = 0;
bool bRet = false;
fprintf(fOut, "\n\t\tLast Token Recorded: ");
if (NULL != tokenBuffer)
{
if (tokenSize >= sizeof(sGTrace))
{
traceLines = sizeToLines(tokenSize);
if (0 != traceLines)
{
bRet = true;
lastToken--;
fprintf(fOut, "%u\n", lastToken);
fprintf(fOut, "\t\tToken Buffer Lines : %u\n", tokenLineCount);
fprintf(fOut, "\t\tToken Buffer Size : %u\n", tokenSize);
fprintf(fOut, "\t\tToken Buffer Data :\n");
if (lastToken > traceLines)
{
lastToken = traceLines;
}
for (currentLine = 0; currentLine < lastToken; currentLine++)
{
fprintf(fOut, "\t\tTkn: %04u\tTS: %llu\tLn: %u\tC: %#llx\tCTID: %u-%#llx\tOID: %#llx\tTag: %#llx\tA: %#llx-%#llx-%#llx-%#llx\n",
currentLine,
traceBuffer[currentLine].traceEntry.timestamp,
traceBuffer[currentLine].traceEntry.traceID.ID.line,
traceBuffer[currentLine].traceEntry.traceID.ID.component,
traceBuffer[currentLine].traceEntry.threadInfo.TI.cpu,
traceBuffer[currentLine].traceEntry.threadInfo.TI.threadID,
traceBuffer[currentLine].traceEntry.threadInfo.TI.registryID,
traceBuffer[currentLine].traceEntry.argsTag.TAG.u64,
traceBuffer[currentLine].traceEntry.args.ARGS.u64s[0],
traceBuffer[currentLine].traceEntry.args.ARGS.u64s[1],
traceBuffer[currentLine].traceEntry.args.ARGS.u64s[2],
traceBuffer[currentLine].traceEntry.args.ARGS.u64s[3]
);
}
fprintf(fOut, "\tTkn: %04u\tTS: %llu\tLn: %u\tC: %#llx\tCTID: %u-%#llx\tOID: %#llx\tTag: %#llx\tA: %#llx-%#llx-%#llx-%#llx\t<--\n",
currentLine,
traceBuffer[currentLine].traceEntry.timestamp,
traceBuffer[currentLine].traceEntry.traceID.ID.line,
traceBuffer[currentLine].traceEntry.traceID.ID.component,
traceBuffer[currentLine].traceEntry.threadInfo.TI.cpu,
traceBuffer[currentLine].traceEntry.threadInfo.TI.threadID,
traceBuffer[currentLine].traceEntry.threadInfo.TI.registryID,
traceBuffer[currentLine].traceEntry.argsTag.TAG.u64,
traceBuffer[currentLine].traceEntry.args.ARGS.u64s[0],
traceBuffer[currentLine].traceEntry.args.ARGS.u64s[1],
traceBuffer[currentLine].traceEntry.args.ARGS.u64s[2],
traceBuffer[currentLine].traceEntry.args.ARGS.u64s[3]
);
currentLine++;
for (; currentLine < traceLines; currentLine++)
{
fprintf(fOut, "\t\tTkn: %04u\tTS: %llu\tLn: %u\tC: %#llx\tCTID: %u-%#llx\tOID: %#llx\tTag: %#llx\tA: %#llx-%#llx-%#llx-%#llx\n",
currentLine,
traceBuffer[currentLine].traceEntry.timestamp,
traceBuffer[currentLine].traceEntry.traceID.ID.line,
traceBuffer[currentLine].traceEntry.traceID.ID.component,
traceBuffer[currentLine].traceEntry.threadInfo.TI.cpu,
traceBuffer[currentLine].traceEntry.threadInfo.TI.threadID,
traceBuffer[currentLine].traceEntry.threadInfo.TI.registryID,
traceBuffer[currentLine].traceEntry.argsTag.TAG.u64,
traceBuffer[currentLine].traceEntry.args.ARGS.u64s[0],
traceBuffer[currentLine].traceEntry.args.ARGS.u64s[1],
traceBuffer[currentLine].traceEntry.args.ARGS.u64s[2],
traceBuffer[currentLine].traceEntry.args.ARGS.u64s[3]
);
}
}
else
{
fprintf(fOut, "-3 (Token Data Count Invalid (%d vs %d))\n", traceLines, tokenSize);
}
}
else
{
fprintf(fOut, "-2 (No Token Data Recorded)\n");
}
}
else
{
fprintf(fOut, "-1 (No Token Data Recorded)\n");
}
fprintf(fOut, "\n");
return (bRet);
}
int main(int argc, char * argv[])
{
IOGDiagnose * report = NULL;
IOGReport * fbState = NULL;
FILE * fp = NULL;
FILE * fOut = NULL;
tm * timeinfo = NULL;
kern_return_t kr = kIOReturnNoMemory;
io_iterator_t iterator = IO_OBJECT_NULL;
io_service_t framebuffer = IO_OBJECT_NULL;
io_connect_t connect = IO_OBJECT_NULL;
uint64_t scalerParams[] = {sizeof(IOGDiagnose), IOGRAPHICS_DIAGNOSE_VERSION};
uint32_t scalerParamsCount = (sizeof(scalerParams) / sizeof(scalerParams[0]));
size_t reportLength = scalerParams[0];
uint32_t index = 0;
uint32_t mask = 0x80000000;
int exitValue = EXIT_SUCCESS;
time_t rawtime = 0;
char filename[kFILENAME_LENGTH] = {0};
bool bDumpToFile = false;
int optsIndex = 0;
int option = 0;
unsigned int groupIndex = 0;
unsigned int stampIndex = 0;
mach_timebase_info_data_t info = {0};
static struct option opts[] = {
"file", optional_argument, NULL, 'f',
NULL, no_argument, NULL, 0,
};
if (argc > 1)
{
option = getopt_long(argc, argv, "f", opts, &optsIndex);
if (-1 == option)
{
print_usage(argv[0]);
exit(EXIT_FAILURE);
}
do
{
switch (option)
{
case 'f':
{
bDumpToFile = true;
break;
}
default:
{
print_usage(argv[0]);
exit(EXIT_FAILURE);
}
}
option = getopt_long(argc, argv, "f", opts, &optsIndex);
} while (-1 != option);
}
report = (IOGDiagnose *)malloc(reportLength);
if (NULL != report)
{
bzero(report, reportLength);
kr = IOServiceGetMatchingServices(kIOMasterPortDefault, IOServiceMatching("IOFramebuffer"), &iterator);
if (kIOReturnSuccess != kr) {
fprintf(stderr, "IOG Framebuffer search failed %s(%#x)\n",
mach_error_string(kr), kr);
}
else
{
if (true == IOIteratorIsValid(iterator))
{
framebuffer = IOIteratorNext(iterator);
if (IO_OBJECT_NULL != framebuffer)
{
kr = IOServiceOpen(framebuffer, mach_task_self(), kIOFBDiagnoseConnectType, &connect);
if (kIOReturnSuccess != kr)
{
fprintf(stderr, "IOG Shared service failed %s(%#x)\n",
mach_error_string(kr), kr);
}
else
{
report->version = IOGRAPHICS_DIAGNOSE_VERSION;
report->tokenSize = sizeof(report->tokenBuffer);
kr = IOConnectCallMethod(connect, kIOGSharedInterface_IOGDiagnose,
scalerParams, scalerParamsCount, NULL, 0,
NULL, NULL, report, &reportLength);
if (kIOReturnSuccess != kr)
{
fprintf(stderr, "IOGDiagnose failed %s(%#x)\n",
mach_error_string(kr), kr);
}
(void)IOServiceClose(connect);
}
IOObjectRelease(framebuffer);
}
}
IOObjectRelease(iterator);
}
}
else
{
fprintf(stderr, "IOGDiagnose malloc failed %s(%#x)\n",
mach_error_string(kr), kr);
}
if (kIOReturnSuccess == kr)
{
time(&rawtime);
timeinfo = localtime(&rawtime);
strftime(filename, kFILENAME_LENGTH, "/tmp/com.apple.iokit.IOGraphics_%F_%H-%M-%S.txt", timeinfo);
if (true == bDumpToFile)
{
fp = fopen(filename, "w+");
}
if (NULL == fp)
{
fOut= stdout;
}
else
{
fOut = fp;
}
fprintf(fOut, "---------------------------------- IOGRAPHICS REPORT ----------------------------------\n\n");
strftime(filename, kFILENAME_LENGTH, "Report date: %F %H:%M:%S\n", timeinfo);
fprintf(fOut, "%s", filename);
if (kIOReturnSuccess == kr)
{
switch (report->version)
{
case 1:
case 2:
case 3:
{
fprintf(fOut, "Report version: %#llx\n", report->version);
fprintf(fOut, "Number of framebuffers: %llu\n\n", report->framebufferCount);
while(index < report->framebufferCount)
{
fbState = &(report->fbState[index]);
index++;
if (strlen(fbState->objectName))
{
fprintf(fOut, "\t%s::", fbState->objectName);
}
else
{
fprintf(fOut, "\tUNKNOWN::");
}
if (strlen(fbState->framebufferName))
{
fprintf(fOut, "%s", fbState->framebufferName);
}
else
{
fprintf(fOut, "IOFB?");
}
fprintf(fOut, " (%u %#llx)\n", fbState->dependentIndex, fbState->regID);
fprintf(fOut, "\t\tI-State : 0x%08x (b", fbState->stateBits);
for (mask = 0x80000000; 0 != mask; mask >>= 1)
{
fprintf(fOut, "%d", (fbState->stateBits & mask) ? 1 : 0);
}
fprintf(fOut, ")\n");
fprintf(fOut, "\t\tStates : OP:US:PS:CS:CC:CL:DS:MR:SG:WL:NA:SN:SS:SA:PP:MX:PPS: NOTIFIED : WSAA \n");
fprintf(fOut, "\t\tState Bits: %02d:%02d:%02d:%02d:%02d:%02d:%02d:%02d:%02d:%02d:%02d:%02d:%02d:%02d:%02d:%02d:0x%01x:0x%08x:0x%08x\n",
fbState->stateBits & kIOGReportState_Opened ? 1 : 0,
fbState->stateBits & kIOGReportState_Usable ? 1 : 0,
fbState->stateBits & kIOGReportState_Paging ? 1 : 0,
fbState->stateBits & kIOGReportState_Clamshell ? 1 : 0,
fbState->stateBits & kIOGReportState_ClamshellCurrent ? 1 : 0,
fbState->stateBits & kIOGReportState_ClamshellLast ? 1 : 0,
fbState->stateBits & kIOGReportState_SystemDark ? 1 : 0,
fbState->stateBits & kIOGReportState_Mirrored ? 1 : 0,
fbState->stateBits & kIOGReportState_SystemGated ? 1 : 0,
fbState->stateBits & kIOGReportState_WorkloopGated ? 1 : 0,
fbState->stateBits & kIOGReportState_NotificationActive ? 1 : 0,
fbState->stateBits & kIOGReportState_ServerNotified ? 1 : 0,
fbState->stateBits & kIOGReportState_ServerState ? 1 : 0,
fbState->stateBits & kIOGReportState_ServerPendingAck ? 1 : 0,
fbState->stateBits & kIOGReportState_PowerPendingChange ? 1 : 0,
fbState->stateBits & kIOGReportState_PowerPendingMuxChange ? 1 : 0,
fbState->pendingPowerState,
fbState->notificationGroup,
fbState->wsaaState );
fprintf(fOut, "\t\tA-State : 0x%08x (b", fbState->externalAPIState);
for (mask = 0x80000000; 0 != mask; mask >>= 1)
{
fprintf(fOut, "%d", (fbState->externalAPIState & mask) ? 1 : 0);
}
fprintf(fOut, ")\n");
if (report->version >= 2)
{
fprintf(fOut, "\t\tMode ID : %#x\n", fbState->lastSuccessfulMode);
}
fprintf(fOut, "\t\tSystem : %llu (%#llx) (%u)\n", fbState->systemOwner,
fbState->systemOwner,
fbState->systemGatedCount);
fprintf(fOut, "\t\tController: %llu (%#llx) (%u)\n", fbState->workloopOwner,
fbState->workloopOwner,
fbState->workloopGatedCount);
if (report->version >= 2)
{
mach_timebase_info(&info);
for (groupIndex = 0; groupIndex < IOGRAPHICS_MAXIMUM_REPORTS; groupIndex++)
{
if (0 != fbState->notifications[groupIndex].groupID)
{
fprintf(fOut, "\n\t\tGroup ID: %#llx\n", fbState->notifications[groupIndex].groupID - 1);
for (stampIndex = 0; stampIndex < IOGRAPHICS_MAXIMUM_REPORTS; stampIndex++)
{
if ((0 != fbState->notifications[groupIndex].stamp[stampIndex].lastEvent) ||
(0 != fbState->notifications[groupIndex].stamp[stampIndex].start) ||
(0 != fbState->notifications[groupIndex].stamp[stampIndex].end) ||
(0 != strlen(fbState->notifications[groupIndex].stamp[stampIndex].name)) )
{
fprintf(fOut, "\t\t\tComponent : %s\n", fbState->notifications[groupIndex].stamp[stampIndex].name);
fprintf(fOut, "\t\t\tLast Event : %u (%#x)\n",
fbState->notifications[groupIndex].stamp[stampIndex].lastEvent,
fbState->notifications[groupIndex].stamp[stampIndex].lastEvent);
fprintf(fOut, "\t\t\tStamp Start : %#llx\n", fbState->notifications[groupIndex].stamp[stampIndex].start);
fprintf(fOut, "\t\t\tStamp End : %#llx\n", fbState->notifications[groupIndex].stamp[stampIndex].end);
fprintf(fOut, "\t\t\tStamp Delta : ");
if (fbState->notifications[groupIndex].stamp[stampIndex].start <= fbState->notifications[groupIndex].stamp[stampIndex].end)
{
fprintf(fOut, "%llu ns\n", ((fbState->notifications[groupIndex].stamp[stampIndex].end - fbState->notifications[groupIndex].stamp[stampIndex].start) * static_cast<uint64_t>(info.numer)) / static_cast<uint64_t>(info.denom));
}
else
{
fprintf(fOut, "Notifier Active\n");
}
}
}
}
}
}
}
break;
}
default:
{
fprintf(fOut, "Unsupported report version: %#llx\n", report->version);
break;
}
}
if (report->version >= 3)
{
(void)dumpTokenBuffer(fOut, report->tokenLine, report->tokenLineCount, report->tokenSize, report->tokenBuffer);
}
}
else
{
fprintf(fOut, "Reporting error %#x encountered.\n", kr);
}
fprintf(fOut, "\n---------------------------------------------------------------------------------------\n");
fflush(fOut);
agdcdiagnose(fOut);
fflush(fOut);
if (NULL != fp)
{
fclose(fp);
}
}
else
{
exitValue = EXIT_FAILURE;
}
if (NULL != report)
{
free(report);
}
exit(exitValue);
}