Created
July 4, 2026 18:21
-
-
Save andrew-kramer-inno/f7e8449b521b7435e91c92ae6b3ae6d8 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #import <Foundation/Foundation.h> | |
| #include <errno.h> | |
| #include <IOKit/IOKitLib.h> | |
| #include <libproc.h> | |
| #include <mach/mach.h> | |
| #include <mach/task_policy.h> | |
| #include <pthread.h> | |
| #include <stdarg.h> | |
| #include <stdbool.h> | |
| #include <stdint.h> | |
| #include <stdio.h> | |
| #include <stdlib.h> | |
| #include <string.h> | |
| #include <strings.h> | |
| #include <sys/resource.h> | |
| #include <sys/syscall.h> | |
| #include <time.h> | |
| #include <unistd.h> | |
| static const char* kRequestPath = "/tmp/power-daemon-override/request"; | |
| static const char* kLogPath = "/tmp/power-daemon-override/entitled-direct.log"; | |
| static const char* kPackageLaneSessionControlPath = "/tmp/power-daemon-override/package-lane-session-control"; | |
| static const char* kPackageLaneSessionStatusPath = "/tmp/power-daemon-override/package-lane-session-status"; | |
| #define MAX_SCALARS 16 | |
| #define MAX_STRUCT_BYTES 12288 | |
| #define MAX_LOGGED_STRUCT_BYTES MAX_STRUCT_BYTES | |
| #define PPM_SELECTOR_GET_CLIENT_STATE 0x1d | |
| #define PPM_SELECTOR_SET_DETAILED_THERMAL_BUDGETS 0x23 | |
| #define PPM_DETAILED_THERMAL_BUDGETS_BYTES 0x288 | |
| #define PPM_DETAILED_THERMAL_BUDGETS_COUNT_OFFSET 0x280 | |
| #define PPM_CLIENT_STATE_BYTES 0x640 | |
| #define PPM_CLIENT_STATE_DETAILED_BUDGET_COUNT_OFFSET 0x1b8 | |
| #define PPM_CLIENT_STATE_DETAILED_BUDGET_ENTRY_OFFSET 0x1c0 | |
| #define PPM_CLIENT_STATE_DETAILED_BUDGET_MAX 8 | |
| #define PPM_PACKAGE_CLIENT_ID 2 | |
| #define PPM_PACKAGE_SESSION_MIN_BUDGET 8000 | |
| #define PPM_PACKAGE_SESSION_MAX_BUDGET 255000 | |
| #define PPM_PACKAGE_SESSION_MAX_SECONDS 3600 | |
| #define PPM_PACKAGE_SESSION_MIN_POLL_MS 100 | |
| #define PPM_PACKAGE_SESSION_MAX_POLL_MS 5000 | |
| #define AGX_SELECTOR_CONSISTENT_PERF_STATE 0x109 | |
| #define AGX_SELECTOR_PERF_STATE 0x10d | |
| #ifndef PRIO_DARWIN_PROCESS | |
| #define PRIO_DARWIN_PROCESS 4 | |
| #endif | |
| #ifndef PRIO_DARWIN_GPU | |
| #define PRIO_DARWIN_GPU 5 | |
| #endif | |
| #ifndef PRIO_DARWIN_ROLE | |
| #define PRIO_DARWIN_ROLE 6 | |
| #endif | |
| #ifndef PROC_PIDCOALITIONINFO | |
| #define PROC_PIDCOALITIONINFO 20 | |
| #endif | |
| #ifndef SYS_coalition_policy_set | |
| #define SYS_coalition_policy_set 556 | |
| #endif | |
| #ifndef SYS_coalition_policy_get | |
| #define SYS_coalition_policy_get 557 | |
| #endif | |
| #define COALITION_TYPE_RESOURCE 0 | |
| #define COALITION_TYPE_JETSAM 1 | |
| #define COALITION_NUM_TYPES 2 | |
| #define COALITION_POLICY_SUPPRESS 1 | |
| #define COALITION_POLICY_SUPPRESS_NONE 0 | |
| #define COALITION_POLICY_SUPPRESS_DARWIN_BG 1 | |
| struct ProcPidCoalitionInfoPrivate | |
| { | |
| uint64_t CoalitionId[COALITION_NUM_TYPES]; | |
| uint64_t Reserved1; | |
| uint64_t Reserved2; | |
| uint64_t Reserved3; | |
| }; | |
| struct TaskSuppressionPolicyPrivate | |
| { | |
| integer_t Active; | |
| integer_t LowPriCpu; | |
| integer_t TimerThrottle; | |
| integer_t DiskThrottle; | |
| integer_t CpuLimit; | |
| integer_t Suspend; | |
| integer_t ThroughputQos; | |
| integer_t SuppressedCpu; | |
| integer_t BackgroundSockets; | |
| integer_t Reserved[7]; | |
| }; | |
| #define TASK_SUPPRESSION_POLICY_PRIVATE_COUNT ((mach_msg_type_number_t)(sizeof(struct TaskSuppressionPolicyPrivate) / sizeof(integer_t))) | |
| static const char* ReturnName(kern_return_t kr) | |
| { | |
| switch (kr) | |
| { | |
| case KERN_SUCCESS: return "KERN_SUCCESS"; | |
| case kIOReturnNotPrivileged: return "kIOReturnNotPrivileged"; | |
| case kIOReturnNotPermitted: return "kIOReturnNotPermitted"; | |
| case kIOReturnUnsupported: return "kIOReturnUnsupported"; | |
| case kIOReturnBadArgument: return "kIOReturnBadArgument"; | |
| case kIOReturnNoDevice: return "kIOReturnNoDevice"; | |
| case kIOReturnNoResources: return "kIOReturnNoResources"; | |
| default: return "other"; | |
| } | |
| } | |
| static void Logf(const char* Format, ...) | |
| { | |
| FILE* Log = fopen(kLogPath, "a"); | |
| if (!Log) | |
| { | |
| return; | |
| } | |
| time_t Now = time(NULL); | |
| fprintf(Log, "%lld ", (long long)Now); | |
| va_list Args; | |
| va_start(Args, Format); | |
| vfprintf(Log, Format, Args); | |
| va_end(Args); | |
| fprintf(Log, "\n"); | |
| fclose(Log); | |
| } | |
| static void ReadRequest(char* Buffer, size_t BufferSize) | |
| { | |
| if (BufferSize == 0) | |
| { | |
| return; | |
| } | |
| Buffer[0] = '\0'; | |
| FILE* Request = fopen(kRequestPath, "r"); | |
| if (!Request) | |
| { | |
| return; | |
| } | |
| size_t Len = fread(Buffer, 1, BufferSize - 1, Request); | |
| Buffer[Len] = '\0'; | |
| fclose(Request); | |
| for (size_t i = 0; Buffer[i]; ++i) | |
| { | |
| if (Buffer[i] == '\r' || Buffer[i] == '\n') | |
| { | |
| Buffer[i] = '\0'; | |
| break; | |
| } | |
| } | |
| } | |
| static size_t Tokenize(char* Buffer, char* Tokens[], size_t MaxTokens) | |
| { | |
| size_t Count = 0; | |
| char* Save = NULL; | |
| char* Token = strtok_r(Buffer, " \t", &Save); | |
| while (Token && Count < MaxTokens) | |
| { | |
| Tokens[Count++] = Token; | |
| Token = strtok_r(NULL, " \t", &Save); | |
| } | |
| return Count; | |
| } | |
| static uint64_t ParseU64(const char* Text) | |
| { | |
| return strtoull(Text, NULL, 0); | |
| } | |
| typedef struct NamedInt | |
| { | |
| const char* Name; | |
| int Value; | |
| } NamedInt; | |
| static const NamedInt kDarwinRoles[] = { | |
| {"default", 0}, | |
| {"ui_focal", 1}, | |
| {"uifocal", 1}, | |
| {"ui", 2}, | |
| {"non_ui", 3}, | |
| {"nonui", 3}, | |
| {"ui_non_focal", 4}, | |
| {"uinonfocal", 4}, | |
| {"tal_launch", 5}, | |
| {"bg", 6}, | |
| {"background", 6}, | |
| {"user_init", 7}, | |
| {NULL, 0}, | |
| }; | |
| static const NamedInt kGpuRoles[] = { | |
| {"unknown", 0}, | |
| {"allow", 1}, | |
| {"deny", 2}, | |
| {"background", 3}, | |
| {"utility", 4}, | |
| {"ui_non_focal", 5}, | |
| {"nonfocal", 5}, | |
| {"non_focal", 5}, | |
| {"ui", 6}, | |
| {"ui_focal", 7}, | |
| {"uifocal", 7}, | |
| {NULL, 0}, | |
| }; | |
| static bool ParseIntToken(const char* Text, int* OutValue) | |
| { | |
| char* End = NULL; | |
| errno = 0; | |
| long Value = strtol(Text, &End, 0); | |
| if (errno != 0 || End == Text || *End != '\0' || Value < INT32_MIN || Value > INT32_MAX) | |
| { | |
| return false; | |
| } | |
| *OutValue = (int)Value; | |
| return true; | |
| } | |
| static bool ParseNamedInt(const char* Text, const NamedInt* Values, int* OutValue) | |
| { | |
| if (ParseIntToken(Text, OutValue)) | |
| { | |
| return true; | |
| } | |
| for (size_t i = 0; Values[i].Name; ++i) | |
| { | |
| if (strcasecmp(Text, Values[i].Name) == 0) | |
| { | |
| *OutValue = Values[i].Value; | |
| return true; | |
| } | |
| } | |
| return false; | |
| } | |
| static const char* NamedIntName(const NamedInt* Values, int Value) | |
| { | |
| for (size_t i = 0; Values[i].Name; ++i) | |
| { | |
| if (Values[i].Value == Value) | |
| { | |
| return Values[i].Name; | |
| } | |
| } | |
| return "unknown-value"; | |
| } | |
| static int HexNibble(char C) | |
| { | |
| if (C >= '0' && C <= '9') | |
| { | |
| return C - '0'; | |
| } | |
| if (C >= 'a' && C <= 'f') | |
| { | |
| return C - 'a' + 10; | |
| } | |
| if (C >= 'A' && C <= 'F') | |
| { | |
| return C - 'A' + 10; | |
| } | |
| return -1; | |
| } | |
| static bool ParseHexBytes(const char* Hex, unsigned char* Output, size_t MaxOutput, size_t* OutputSize) | |
| { | |
| *OutputSize = 0; | |
| int High = -1; | |
| for (size_t i = 0; Hex[i]; ++i) | |
| { | |
| if (Hex[i] == ':' || Hex[i] == '-' || Hex[i] == '_' || Hex[i] == ' ') | |
| { | |
| continue; | |
| } | |
| int Value = HexNibble(Hex[i]); | |
| if (Value < 0) | |
| { | |
| return false; | |
| } | |
| if (High < 0) | |
| { | |
| High = Value; | |
| continue; | |
| } | |
| if (*OutputSize >= MaxOutput) | |
| { | |
| return false; | |
| } | |
| Output[(*OutputSize)++] = (unsigned char)((High << 4) | Value); | |
| High = -1; | |
| } | |
| return High < 0; | |
| } | |
| static const char* CoalitionSuppressName(int Value) | |
| { | |
| switch (Value) | |
| { | |
| case COALITION_POLICY_SUPPRESS_NONE: return "none"; | |
| case COALITION_POLICY_SUPPRESS_DARWIN_BG: return "darwin-bg"; | |
| default: return "unknown"; | |
| } | |
| } | |
| static bool ParseCoalitionSuppressValue(const char* Text, uint32_t* OutValue) | |
| { | |
| if (strcmp(Text, "none") == 0 || strcmp(Text, "NONE") == 0 || strcmp(Text, "0") == 0) | |
| { | |
| *OutValue = COALITION_POLICY_SUPPRESS_NONE; | |
| return true; | |
| } | |
| if (strcmp(Text, "darwin-bg") == 0 || strcmp(Text, "darwin_bg") == 0 || | |
| strcmp(Text, "bg") == 0 || strcmp(Text, "DARWIN_BG") == 0 || strcmp(Text, "1") == 0) | |
| { | |
| *OutValue = COALITION_POLICY_SUPPRESS_DARWIN_BG; | |
| return true; | |
| } | |
| return false; | |
| } | |
| static bool ReadCoalitionInfoForPid(pid_t Pid, struct ProcPidCoalitionInfoPrivate* Info) | |
| { | |
| memset(Info, 0, sizeof(*Info)); | |
| int Ret = proc_pidinfo(Pid, PROC_PIDCOALITIONINFO, 0, Info, sizeof(*Info)); | |
| if (Ret != (int)sizeof(*Info)) | |
| { | |
| int SavedErrno = errno; | |
| Logf("coalition pid=%d proc_pidinfo result=fail ret=%d errno=%d %s", | |
| Pid, | |
| Ret, | |
| SavedErrno, | |
| strerror(SavedErrno)); | |
| return false; | |
| } | |
| Logf("coalition pid=%d resource=%llu jetsam=%llu", | |
| Pid, | |
| (unsigned long long)Info->CoalitionId[COALITION_TYPE_RESOURCE], | |
| (unsigned long long)Info->CoalitionId[COALITION_TYPE_JETSAM]); | |
| return true; | |
| } | |
| static int CoalitionPolicyGet(uint64_t CoalitionId) | |
| { | |
| errno = 0; | |
| int Value = (int)syscall(SYS_coalition_policy_get, CoalitionId, COALITION_POLICY_SUPPRESS); | |
| int SavedErrno = errno; | |
| Logf("coalition=%llu op=get-suppress flavor=%u result=%s value=%d value-name=%s errno=%d %s", | |
| (unsigned long long)CoalitionId, | |
| COALITION_POLICY_SUPPRESS, | |
| Value >= 0 ? "ok" : "fail", | |
| Value, | |
| Value >= 0 ? CoalitionSuppressName(Value) : "none", | |
| Value >= 0 ? 0 : SavedErrno, | |
| Value >= 0 ? "none" : strerror(SavedErrno)); | |
| return Value; | |
| } | |
| static bool CoalitionPolicySet(uint64_t CoalitionId, uint32_t Value) | |
| { | |
| errno = 0; | |
| int Ret = (int)syscall(SYS_coalition_policy_set, CoalitionId, COALITION_POLICY_SUPPRESS, Value); | |
| int SavedErrno = errno; | |
| Logf("coalition=%llu op=set-suppress flavor=%u value=%u value-name=%s result=%s errno=%d %s", | |
| (unsigned long long)CoalitionId, | |
| COALITION_POLICY_SUPPRESS, | |
| Value, | |
| CoalitionSuppressName((int)Value), | |
| Ret == 0 ? "ok" : "fail", | |
| Ret == 0 ? 0 : SavedErrno, | |
| Ret == 0 ? "none" : strerror(SavedErrno)); | |
| return Ret == 0; | |
| } | |
| static bool ResolveJetsamCoalition(pid_t Pid, uint64_t* OutCoalition) | |
| { | |
| struct ProcPidCoalitionInfoPrivate Info; | |
| if (!ReadCoalitionInfoForPid(Pid, &Info)) | |
| { | |
| return false; | |
| } | |
| *OutCoalition = Info.CoalitionId[COALITION_TYPE_JETSAM]; | |
| return true; | |
| } | |
| static void CallCoalitionSuppressGet(pid_t Pid) | |
| { | |
| uint64_t Coalition = 0; | |
| if (!ResolveJetsamCoalition(Pid, &Coalition)) | |
| { | |
| return; | |
| } | |
| CoalitionPolicyGet(Coalition); | |
| } | |
| static void CallCoalitionSuppressSet(pid_t Pid, uint32_t Value) | |
| { | |
| uint64_t Coalition = 0; | |
| if (!ResolveJetsamCoalition(Pid, &Coalition)) | |
| { | |
| return; | |
| } | |
| CoalitionPolicyGet(Coalition); | |
| CoalitionPolicySet(Coalition, Value); | |
| CoalitionPolicyGet(Coalition); | |
| } | |
| static bool GetTaskPolicyPortForPid(pid_t Pid, mach_port_t* OutTask) | |
| { | |
| *OutTask = MACH_PORT_NULL; | |
| kern_return_t Kr = task_name_for_pid(mach_task_self(), Pid, OutTask); | |
| Logf("task-suppress pid=%d op=task_name_for_pid kr=0x%x %s task=0x%x", | |
| Pid, | |
| Kr, | |
| ReturnName(Kr), | |
| *OutTask); | |
| if (Kr == KERN_SUCCESS) | |
| { | |
| return true; | |
| } | |
| Kr = task_for_pid(mach_task_self(), Pid, OutTask); | |
| Logf("task-suppress pid=%d op=task_for_pid kr=0x%x %s task=0x%x", | |
| Pid, | |
| Kr, | |
| ReturnName(Kr), | |
| *OutTask); | |
| return Kr == KERN_SUCCESS; | |
| } | |
| static void FillTaskSuppressionPolicy(struct TaskSuppressionPolicyPrivate* Policy, bool Active) | |
| { | |
| memset(Policy, 0, sizeof(*Policy)); | |
| if (!Active) | |
| { | |
| return; | |
| } | |
| Policy->Active = 1; | |
| Policy->LowPriCpu = 1; | |
| Policy->TimerThrottle = LATENCY_QOS_TIER_5; | |
| Policy->DiskThrottle = 1; | |
| Policy->ThroughputQos = THROUGHPUT_QOS_TIER_5; | |
| Policy->BackgroundSockets = 1; | |
| } | |
| static void LogTaskSuppressionPolicy(const char* Label, const struct TaskSuppressionPolicyPrivate* Policy) | |
| { | |
| Logf("task-suppress %s active=%d lowpri-cpu=%d timer=0x%x disk=%d cpu-limit=%d suspend=%d throughput=0x%x suppressed-cpu=%d bg-sockets=%d", | |
| Label, | |
| Policy->Active, | |
| Policy->LowPriCpu, | |
| Policy->TimerThrottle, | |
| Policy->DiskThrottle, | |
| Policy->CpuLimit, | |
| Policy->Suspend, | |
| Policy->ThroughputQos, | |
| Policy->SuppressedCpu, | |
| Policy->BackgroundSockets); | |
| } | |
| static bool TaskSuppressionGet(pid_t Pid, struct TaskSuppressionPolicyPrivate* Policy) | |
| { | |
| mach_port_t Task = MACH_PORT_NULL; | |
| if (!GetTaskPolicyPortForPid(Pid, &Task)) | |
| { | |
| return false; | |
| } | |
| memset(Policy, 0, sizeof(*Policy)); | |
| mach_msg_type_number_t Count = TASK_SUPPRESSION_POLICY_PRIVATE_COUNT; | |
| boolean_t Defaulted = FALSE; | |
| kern_return_t Kr = task_policy_get(Task, | |
| TASK_SUPPRESSION_POLICY, | |
| (task_policy_t)Policy, | |
| &Count, | |
| &Defaulted); | |
| Logf("task-suppress pid=%d op=get flavor=%d kr=0x%x %s count=%u defaulted=%d", | |
| Pid, | |
| TASK_SUPPRESSION_POLICY, | |
| Kr, | |
| ReturnName(Kr), | |
| Count, | |
| Defaulted); | |
| if (Kr == KERN_SUCCESS) | |
| { | |
| LogTaskSuppressionPolicy("get", Policy); | |
| } | |
| mach_port_deallocate(mach_task_self(), Task); | |
| return Kr == KERN_SUCCESS; | |
| } | |
| static bool TaskSuppressionSet(pid_t Pid, bool Active) | |
| { | |
| mach_port_t Task = MACH_PORT_NULL; | |
| if (!GetTaskPolicyPortForPid(Pid, &Task)) | |
| { | |
| return false; | |
| } | |
| struct TaskSuppressionPolicyPrivate Policy; | |
| FillTaskSuppressionPolicy(&Policy, Active); | |
| LogTaskSuppressionPolicy(Active ? "set-active" : "set-inactive", &Policy); | |
| kern_return_t Kr = task_policy_set(Task, | |
| TASK_SUPPRESSION_POLICY, | |
| (task_policy_t)&Policy, | |
| TASK_SUPPRESSION_POLICY_PRIVATE_COUNT); | |
| Logf("task-suppress pid=%d op=set active=%d flavor=%d kr=0x%x %s count=%u", | |
| Pid, | |
| Active ? 1 : 0, | |
| TASK_SUPPRESSION_POLICY, | |
| Kr, | |
| ReturnName(Kr), | |
| TASK_SUPPRESSION_POLICY_PRIVATE_COUNT); | |
| mach_port_deallocate(mach_task_self(), Task); | |
| return Kr == KERN_SUCCESS; | |
| } | |
| static int EncodeLatencyQosTier(int Tier) | |
| { | |
| if (Tier <= 0) | |
| { | |
| return Tier == 0 ? LATENCY_QOS_TIER_0 : LATENCY_QOS_TIER_UNSPECIFIED; | |
| } | |
| if (Tier == 1) return LATENCY_QOS_TIER_1; | |
| if (Tier == 2) return LATENCY_QOS_TIER_2; | |
| if (Tier == 3) return LATENCY_QOS_TIER_3; | |
| if (Tier == 4) return LATENCY_QOS_TIER_4; | |
| return LATENCY_QOS_TIER_5; | |
| } | |
| static int EncodeThroughputQosTier(int Tier) | |
| { | |
| if (Tier <= 0) | |
| { | |
| return Tier == 0 ? THROUGHPUT_QOS_TIER_0 : THROUGHPUT_QOS_TIER_UNSPECIFIED; | |
| } | |
| if (Tier == 1) return THROUGHPUT_QOS_TIER_1; | |
| if (Tier == 2) return THROUGHPUT_QOS_TIER_2; | |
| if (Tier == 3) return THROUGHPUT_QOS_TIER_3; | |
| if (Tier == 4) return THROUGHPUT_QOS_TIER_4; | |
| return THROUGHPUT_QOS_TIER_5; | |
| } | |
| static bool ParseQosTierToken(const char* Text, int* OutTier) | |
| { | |
| if (strcmp(Text, "unspecified") == 0 || strcmp(Text, "none") == 0 || strcmp(Text, "clear") == 0) | |
| { | |
| *OutTier = -1; | |
| return true; | |
| } | |
| char* End = NULL; | |
| errno = 0; | |
| long Value = strtol(Text, &End, 0); | |
| if (errno != 0 || End == Text || *End != '\0' || Value < 0 || Value > 5) | |
| { | |
| return false; | |
| } | |
| *OutTier = (int)Value; | |
| return true; | |
| } | |
| static bool TaskQosGet(pid_t Pid, task_policy_flavor_t Flavor, const char* Label) | |
| { | |
| mach_port_t Task = MACH_PORT_NULL; | |
| if (!GetTaskPolicyPortForPid(Pid, &Task)) | |
| { | |
| return false; | |
| } | |
| struct task_qos_policy Policy; | |
| memset(&Policy, 0, sizeof(Policy)); | |
| mach_msg_type_number_t Count = TASK_QOS_POLICY_COUNT; | |
| boolean_t Defaulted = FALSE; | |
| kern_return_t Kr = task_policy_get(Task, | |
| Flavor, | |
| (task_policy_t)&Policy, | |
| &Count, | |
| &Defaulted); | |
| Logf("task-qos pid=%d op=get label=%s flavor=%d kr=0x%x %s count=%u defaulted=%d latency=0x%x throughput=0x%x", | |
| Pid, | |
| Label, | |
| Flavor, | |
| Kr, | |
| ReturnName(Kr), | |
| Count, | |
| Defaulted, | |
| Policy.task_latency_qos_tier, | |
| Policy.task_throughput_qos_tier); | |
| mach_port_deallocate(mach_task_self(), Task); | |
| return Kr == KERN_SUCCESS; | |
| } | |
| static bool TaskQosSet(pid_t Pid, task_policy_flavor_t Flavor, const char* Label, int LatencyTier, int ThroughputTier) | |
| { | |
| mach_port_t Task = MACH_PORT_NULL; | |
| if (!GetTaskPolicyPortForPid(Pid, &Task)) | |
| { | |
| return false; | |
| } | |
| struct task_qos_policy Policy; | |
| Policy.task_latency_qos_tier = EncodeLatencyQosTier(LatencyTier); | |
| Policy.task_throughput_qos_tier = EncodeThroughputQosTier(ThroughputTier); | |
| kern_return_t Kr = task_policy_set(Task, | |
| Flavor, | |
| (task_policy_t)&Policy, | |
| TASK_QOS_POLICY_COUNT); | |
| Logf("task-qos pid=%d op=set label=%s flavor=%d latency-tier=%d latency=0x%x throughput-tier=%d throughput=0x%x kr=0x%x %s", | |
| Pid, | |
| Label, | |
| Flavor, | |
| LatencyTier, | |
| Policy.task_latency_qos_tier, | |
| ThroughputTier, | |
| Policy.task_throughput_qos_tier, | |
| Kr, | |
| ReturnName(Kr)); | |
| mach_port_deallocate(mach_task_self(), Task); | |
| return Kr == KERN_SUCCESS; | |
| } | |
| static void CallTaskQosGet(pid_t Pid) | |
| { | |
| TaskQosGet(Pid, TASK_BASE_QOS_POLICY, "base"); | |
| TaskQosGet(Pid, TASK_OVERRIDE_QOS_POLICY, "override"); | |
| TaskQosGet(Pid, TASK_BASE_LATENCY_QOS_POLICY, "base-latency"); | |
| TaskQosGet(Pid, TASK_BASE_THROUGHPUT_QOS_POLICY, "base-throughput"); | |
| } | |
| static void CallTaskQosOverrideSet(pid_t Pid, int LatencyTier, int ThroughputTier) | |
| { | |
| TaskQosGet(Pid, TASK_OVERRIDE_QOS_POLICY, "override-before"); | |
| TaskQosSet(Pid, TASK_OVERRIDE_QOS_POLICY, "override", LatencyTier, ThroughputTier); | |
| TaskQosGet(Pid, TASK_OVERRIDE_QOS_POLICY, "override-after"); | |
| } | |
| static bool DarwinPriorityGet(pid_t Pid, int Which, const char* Label, const NamedInt* Names) | |
| { | |
| errno = 0; | |
| int Value = getpriority(Which, (id_t)Pid); | |
| int SavedErrno = errno; | |
| bool Ok = SavedErrno == 0; | |
| Logf("darwin-priority pid=%d op=get label=%s which=%d result=%s value=%d value-name=%s errno=%d %s", | |
| Pid, | |
| Label, | |
| Which, | |
| Ok ? "ok" : "fail", | |
| Value, | |
| Ok ? NamedIntName(Names, Value) : "unreadable", | |
| Ok ? 0 : SavedErrno, | |
| Ok ? "none" : strerror(SavedErrno)); | |
| return Ok; | |
| } | |
| static bool DarwinPrioritySet(pid_t Pid, int Which, const char* Label, const NamedInt* Names, int Value) | |
| { | |
| errno = 0; | |
| int Ret = setpriority(Which, (id_t)Pid, Value); | |
| int SavedErrno = errno; | |
| bool Ok = Ret == 0; | |
| Logf("darwin-priority pid=%d op=set label=%s which=%d value=%d value-name=%s result=%s errno=%d %s", | |
| Pid, | |
| Label, | |
| Which, | |
| Value, | |
| NamedIntName(Names, Value), | |
| Ok ? "ok" : "fail", | |
| Ok ? 0 : SavedErrno, | |
| Ok ? "none" : strerror(SavedErrno)); | |
| return Ok; | |
| } | |
| static void CallDarwinProcessClear(pid_t Pid) | |
| { | |
| errno = 0; | |
| int Ret = setpriority(PRIO_DARWIN_PROCESS, (id_t)Pid, 0); | |
| int SavedErrno = errno; | |
| Logf("darwin-priority pid=%d op=set label=process-clear which=%d value=0 result=%s errno=%d %s", | |
| Pid, | |
| PRIO_DARWIN_PROCESS, | |
| Ret == 0 ? "ok" : "fail", | |
| Ret == 0 ? 0 : SavedErrno, | |
| Ret == 0 ? "none" : strerror(SavedErrno)); | |
| } | |
| static void CallDarwinRoleGet(pid_t Pid) | |
| { | |
| DarwinPriorityGet(Pid, PRIO_DARWIN_ROLE, "darwin-role", kDarwinRoles); | |
| } | |
| static void CallDarwinRoleSet(pid_t Pid, int Value) | |
| { | |
| DarwinPriorityGet(Pid, PRIO_DARWIN_ROLE, "darwin-role-before", kDarwinRoles); | |
| DarwinPrioritySet(Pid, PRIO_DARWIN_ROLE, "darwin-role", kDarwinRoles, Value); | |
| DarwinPriorityGet(Pid, PRIO_DARWIN_ROLE, "darwin-role-after", kDarwinRoles); | |
| } | |
| static void CallGpuRoleGet(pid_t Pid) | |
| { | |
| DarwinPriorityGet(Pid, PRIO_DARWIN_GPU, "gpu-role", kGpuRoles); | |
| } | |
| static void CallGpuRoleSet(pid_t Pid, int Value) | |
| { | |
| DarwinPriorityGet(Pid, PRIO_DARWIN_GPU, "gpu-role-before", kGpuRoles); | |
| DarwinPrioritySet(Pid, PRIO_DARWIN_GPU, "gpu-role", kGpuRoles, Value); | |
| DarwinPriorityGet(Pid, PRIO_DARWIN_GPU, "gpu-role-after", kGpuRoles); | |
| } | |
| static void CallTaskSuppressGet(pid_t Pid) | |
| { | |
| struct TaskSuppressionPolicyPrivate Policy; | |
| TaskSuppressionGet(Pid, &Policy); | |
| } | |
| static void CallTaskSuppressSet(pid_t Pid, bool Active) | |
| { | |
| struct TaskSuppressionPolicyPrivate Policy; | |
| TaskSuppressionGet(Pid, &Policy); | |
| TaskSuppressionSet(Pid, Active); | |
| TaskSuppressionGet(Pid, &Policy); | |
| } | |
| static io_service_t GetService(const char* ClassName) | |
| { | |
| io_iterator_t Iterator = IO_OBJECT_NULL; | |
| kern_return_t kr = IOServiceGetMatchingServices(kIOMainPortDefault, IOServiceMatching(ClassName), &Iterator); | |
| if (kr != KERN_SUCCESS) | |
| { | |
| Logf("service=%s match kr=0x%x %s", ClassName, kr, ReturnName(kr)); | |
| return IO_OBJECT_NULL; | |
| } | |
| io_service_t Service = IOIteratorNext(Iterator); | |
| IOObjectRelease(Iterator); | |
| if (Service == IO_OBJECT_NULL) | |
| { | |
| Logf("service=%s not_found", ClassName); | |
| } | |
| return Service; | |
| } | |
| static bool OpenServiceType(const char* ClassName, uint32_t Type, io_connect_t* OutConnect) | |
| { | |
| *OutConnect = IO_OBJECT_NULL; | |
| io_service_t Service = GetService(ClassName); | |
| if (Service == IO_OBJECT_NULL) | |
| { | |
| return false; | |
| } | |
| kern_return_t kr = IOServiceOpen(Service, mach_task_self(), Type, OutConnect); | |
| IOObjectRelease(Service); | |
| Logf("open service=%s type=%u kr=0x%x %s connect=0x%x", ClassName, Type, kr, ReturnName(kr), *OutConnect); | |
| return kr == KERN_SUCCESS; | |
| } | |
| static void CallCLPCWorkload(double Watts, uint64_t Option) | |
| { | |
| io_connect_t Connect = IO_OBJECT_NULL; | |
| if (!OpenServiceType("AppleCLPC", 0, &Connect)) | |
| { | |
| return; | |
| } | |
| uint64_t Scalars[3] = { | |
| (uint64_t)(Watts * 1000.0), | |
| (Option == 2) ? 5 : 9, | |
| 2 | |
| }; | |
| kern_return_t kr = IOConnectCallMethod(Connect, 2, Scalars, 3, NULL, 0, NULL, NULL, NULL, NULL); | |
| Logf("call clpc-workload selector=2 watts=%.3f option=%llu scalars=(%llu,%llu,%llu) kr=0x%x %s", | |
| Watts, | |
| (unsigned long long)Option, | |
| (unsigned long long)Scalars[0], | |
| (unsigned long long)Scalars[1], | |
| (unsigned long long)Scalars[2], | |
| kr, | |
| ReturnName(kr)); | |
| IOServiceClose(Connect); | |
| } | |
| static void CallCLPCTarget(uint64_t Target, double Watts, uint64_t Reason) | |
| { | |
| io_connect_t Connect = IO_OBJECT_NULL; | |
| if (!OpenServiceType("AppleCLPC", 0, &Connect)) | |
| { | |
| return; | |
| } | |
| uint64_t Scalars[3] = { | |
| (uint64_t)(Watts * 1000.0), | |
| Target, | |
| Reason | |
| }; | |
| kern_return_t kr = IOConnectCallMethod(Connect, 8, Scalars, 3, NULL, 0, NULL, 0, NULL, 0); | |
| Logf("call clpc-target selector=8 target=%llu watts=%.3f reason=%llu scalars=(%llu,%llu,%llu) kr=0x%x %s", | |
| (unsigned long long)Target, | |
| Watts, | |
| (unsigned long long)Reason, | |
| (unsigned long long)Scalars[0], | |
| (unsigned long long)Scalars[1], | |
| (unsigned long long)Scalars[2], | |
| kr, | |
| ReturnName(kr)); | |
| IOServiceClose(Connect); | |
| } | |
| static void ScanPPMOpen(void) | |
| { | |
| for (uint32_t Type = 0; Type < 16; ++Type) | |
| { | |
| io_connect_t Connect = IO_OBJECT_NULL; | |
| bool Opened = OpenServiceType("ApplePassthroughPPM", Type, &Connect); | |
| if (Opened && Connect != IO_OBJECT_NULL) | |
| { | |
| IOServiceClose(Connect); | |
| } | |
| } | |
| } | |
| static void LogScalarArray(const char* Label, uint32_t Selector, const uint64_t* Values, uint32_t Count) | |
| { | |
| for (uint32_t i = 0; i < Count; ++i) | |
| { | |
| Logf("%s selector=%u index=%u value=0x%llx", | |
| Label, | |
| Selector, | |
| i, | |
| (unsigned long long)Values[i]); | |
| } | |
| } | |
| static void LogStructBytes(const char* Label, uint32_t Selector, const void* Data, size_t Size) | |
| { | |
| const unsigned char* Bytes = (const unsigned char*)Data; | |
| size_t Limit = Size < MAX_LOGGED_STRUCT_BYTES ? Size : MAX_LOGGED_STRUCT_BYTES; | |
| char Hex[(MAX_LOGGED_STRUCT_BYTES * 2) + 1]; | |
| for (size_t i = 0; i < Limit; ++i) | |
| { | |
| snprintf(&Hex[i * 2], sizeof(Hex) - (i * 2), "%02x", Bytes[i]); | |
| } | |
| Hex[Limit * 2] = '\0'; | |
| Logf("%s selector=%u bytes=%zu first%zu=%s", Label, Selector, Size, Limit, Hex); | |
| } | |
| static void CallGenericMethodOnConnection(const char* ServiceName, | |
| uint32_t OpenType, | |
| io_connect_t Connect, | |
| uint32_t Selector, | |
| const uint64_t* InputScalars, | |
| uint32_t InputScalarCount, | |
| const void* InputStruct, | |
| size_t InputStructSize, | |
| uint32_t RequestedOutputScalars, | |
| size_t RequestedOutputStructSize) | |
| { | |
| uint64_t OutputScalars[MAX_SCALARS] = {0}; | |
| unsigned char OutputStruct[MAX_STRUCT_BYTES] = {0}; | |
| uint32_t OutputScalarCount = RequestedOutputScalars > MAX_SCALARS ? MAX_SCALARS : RequestedOutputScalars; | |
| size_t OutputStructSize = RequestedOutputStructSize > MAX_STRUCT_BYTES ? MAX_STRUCT_BYTES : RequestedOutputStructSize; | |
| uint64_t* OutputScalarPtr = OutputScalarCount == 0 ? NULL : OutputScalars; | |
| uint32_t* OutputScalarCountPtr = OutputScalarCount == 0 ? NULL : &OutputScalarCount; | |
| void* OutputStructPtr = OutputStructSize == 0 ? NULL : OutputStruct; | |
| size_t* OutputStructSizePtr = OutputStructSize == 0 ? NULL : &OutputStructSize; | |
| kern_return_t kr = IOConnectCallMethod(Connect, | |
| Selector, | |
| InputScalarCount == 0 ? NULL : InputScalars, | |
| InputScalarCount, | |
| InputStructSize == 0 ? NULL : InputStruct, | |
| InputStructSize, | |
| OutputScalarPtr, | |
| OutputScalarCountPtr, | |
| OutputStructPtr, | |
| OutputStructSizePtr); | |
| Logf("call-method service=%s type=%u selector=%u input-scalars=%u input-struct=%zu requested-output-scalars=%u requested-output-struct=%zu kr=0x%x %s output-scalars=%u output-struct=%zu", | |
| ServiceName, | |
| OpenType, | |
| Selector, | |
| InputScalarCount, | |
| InputStructSize, | |
| RequestedOutputScalars, | |
| RequestedOutputStructSize, | |
| kr, | |
| ReturnName(kr), | |
| OutputScalarCount, | |
| OutputStructSize); | |
| LogScalarArray("call-method-input-scalar", Selector, InputScalars, InputScalarCount); | |
| if (kr == KERN_SUCCESS) | |
| { | |
| LogScalarArray("call-method-output-scalar", Selector, OutputScalars, OutputScalarCount); | |
| if (OutputStructSize > 0) | |
| { | |
| LogStructBytes("call-method-output-struct", Selector, OutputStruct, OutputStructSize); | |
| } | |
| } | |
| } | |
| static void CallGenericMethod(const char* ServiceName, | |
| uint32_t OpenType, | |
| uint32_t Selector, | |
| const uint64_t* InputScalars, | |
| uint32_t InputScalarCount, | |
| const void* InputStruct, | |
| size_t InputStructSize, | |
| uint32_t RequestedOutputScalars, | |
| size_t RequestedOutputStructSize) | |
| { | |
| io_connect_t Connect = IO_OBJECT_NULL; | |
| if (!OpenServiceType(ServiceName, OpenType, &Connect)) | |
| { | |
| return; | |
| } | |
| CallGenericMethodOnConnection(ServiceName, | |
| OpenType, | |
| Connect, | |
| Selector, | |
| InputScalars, | |
| InputScalarCount, | |
| InputStruct, | |
| InputStructSize, | |
| RequestedOutputScalars, | |
| RequestedOutputStructSize); | |
| IOServiceClose(Connect); | |
| } | |
| static void CallPPMSafeCPMSBatch(const uint64_t* Clients, uint32_t ClientCount) | |
| { | |
| static const uint64_t DefaultClients[] = {2, 5, 6, 7, 0}; | |
| if (ClientCount == 0) | |
| { | |
| Clients = DefaultClients; | |
| ClientCount = (uint32_t)(sizeof(DefaultClients) / sizeof(DefaultClients[0])); | |
| } | |
| io_connect_t Connect = IO_OBJECT_NULL; | |
| if (!OpenServiceType("ApplePassthroughPPM", 0, &Connect)) | |
| { | |
| return; | |
| } | |
| Logf("ppm-safe-cpms-batch begin clients=%u", ClientCount); | |
| for (uint32_t i = 0; i < ClientCount; ++i) | |
| { | |
| uint64_t InputScalars[1] = {Clients[i]}; | |
| Logf("ppm-safe-cpms-batch client=%llu selector=0x1d get-client-state", | |
| (unsigned long long)Clients[i]); | |
| CallGenericMethodOnConnection("ApplePassthroughPPM", 0, Connect, 0x1d, InputScalars, 1, NULL, 0, 0, 0x640); | |
| Logf("ppm-safe-cpms-batch client=%llu selector=0x27 get-default-power-profile", | |
| (unsigned long long)Clients[i]); | |
| CallGenericMethodOnConnection("ApplePassthroughPPM", 0, Connect, 0x27, InputScalars, 1, NULL, 0, 0, 0x24); | |
| Logf("ppm-safe-cpms-batch client=%llu selector=0x2d get-min-max-power-policy", | |
| (unsigned long long)Clients[i]); | |
| CallGenericMethodOnConnection("ApplePassthroughPPM", 0, Connect, 0x2d, InputScalars, 1, NULL, 0, 0, 0x28); | |
| } | |
| Logf("ppm-safe-cpms-batch selector=0x1e get-cpms-control-state"); | |
| CallGenericMethodOnConnection("ApplePassthroughPPM", 0, Connect, 0x1e, NULL, 0, NULL, 0, 0, 0x28c0); | |
| IOServiceClose(Connect); | |
| Logf("ppm-safe-cpms-batch end"); | |
| } | |
| static void BuildDetailedThermalBudgetPayload(unsigned char Output[PPM_DETAILED_THERMAL_BUDGETS_BYTES], | |
| uint8_t ClientId, | |
| uint32_t Budget, | |
| uint64_t Details) | |
| { | |
| uint32_t Count = 1; | |
| memset(Output, 0, PPM_DETAILED_THERMAL_BUDGETS_BYTES); | |
| Output[0] = ClientId; | |
| memcpy(Output + 4, &Budget, sizeof(Budget)); | |
| memcpy(Output + 8, &Details, sizeof(Details)); | |
| memcpy(Output + PPM_DETAILED_THERMAL_BUDGETS_COUNT_OFFSET, &Count, sizeof(Count)); | |
| } | |
| static bool ParseClientStateDetailedBudget(const unsigned char* State, | |
| size_t StateSize, | |
| uint8_t ClientId, | |
| uint64_t Details, | |
| uint32_t* OutBudget, | |
| uint32_t* OutCount) | |
| { | |
| if (StateSize < PPM_CLIENT_STATE_DETAILED_BUDGET_ENTRY_OFFSET) | |
| { | |
| return false; | |
| } | |
| uint32_t Count = 0; | |
| memcpy(&Count, State + PPM_CLIENT_STATE_DETAILED_BUDGET_COUNT_OFFSET, sizeof(Count)); | |
| if (OutCount) | |
| { | |
| *OutCount = Count; | |
| } | |
| if (Count > PPM_CLIENT_STATE_DETAILED_BUDGET_MAX) | |
| { | |
| return false; | |
| } | |
| for (uint32_t i = 0; i < Count; ++i) | |
| { | |
| size_t Offset = PPM_CLIENT_STATE_DETAILED_BUDGET_ENTRY_OFFSET + ((size_t)i * 16); | |
| if (Offset + 16 > StateSize) | |
| { | |
| return false; | |
| } | |
| uint32_t Budget = 0; | |
| uint64_t EntryDetails = 0; | |
| memcpy(&Budget, State + Offset + 4, sizeof(Budget)); | |
| memcpy(&EntryDetails, State + Offset + 8, sizeof(EntryDetails)); | |
| if (State[Offset] == ClientId && EntryDetails == Details) | |
| { | |
| if (OutBudget) | |
| { | |
| *OutBudget = Budget; | |
| } | |
| return true; | |
| } | |
| } | |
| return false; | |
| } | |
| static bool ReadPackageLaneBudgetOnConnection(io_connect_t Connect, uint64_t Details, uint32_t* OutBudget) | |
| { | |
| uint64_t InputScalars[1] = {PPM_PACKAGE_CLIENT_ID}; | |
| unsigned char OutputStruct[PPM_CLIENT_STATE_BYTES] = {0}; | |
| size_t OutputStructSize = sizeof(OutputStruct); | |
| kern_return_t kr = IOConnectCallMethod(Connect, | |
| PPM_SELECTOR_GET_CLIENT_STATE, | |
| InputScalars, | |
| 1, | |
| NULL, | |
| 0, | |
| NULL, | |
| NULL, | |
| OutputStruct, | |
| &OutputStructSize); | |
| if (kr != KERN_SUCCESS) | |
| { | |
| Logf("ppm-package-lane-readback selector=0x%x kr=0x%x %s output-struct=%zu", | |
| PPM_SELECTOR_GET_CLIENT_STATE, | |
| kr, | |
| ReturnName(kr), | |
| OutputStructSize); | |
| return false; | |
| } | |
| uint32_t Count = 0; | |
| uint32_t Budget = 0; | |
| bool Found = ParseClientStateDetailedBudget(OutputStruct, | |
| OutputStructSize, | |
| PPM_PACKAGE_CLIENT_ID, | |
| Details, | |
| &Budget, | |
| &Count); | |
| if (Found) | |
| { | |
| Logf("ppm-package-lane-readback client=%u details=0x%llx budget=%u count=%u", | |
| PPM_PACKAGE_CLIENT_ID, | |
| (unsigned long long)Details, | |
| Budget, | |
| Count); | |
| if (OutBudget) | |
| { | |
| *OutBudget = Budget; | |
| } | |
| } | |
| else | |
| { | |
| Logf("ppm-package-lane-readback client=%u details=0x%llx not_found count=%u output-struct=%zu", | |
| PPM_PACKAGE_CLIENT_ID, | |
| (unsigned long long)Details, | |
| Count, | |
| OutputStructSize); | |
| } | |
| return Found; | |
| } | |
| static kern_return_t SetPackageLaneBudgetOnConnection(io_connect_t Connect, uint32_t Budget, uint64_t Details) | |
| { | |
| unsigned char InputStruct[PPM_DETAILED_THERMAL_BUDGETS_BYTES]; | |
| BuildDetailedThermalBudgetPayload(InputStruct, PPM_PACKAGE_CLIENT_ID, Budget, Details); | |
| kern_return_t kr = IOConnectCallMethod(Connect, | |
| PPM_SELECTOR_SET_DETAILED_THERMAL_BUDGETS, | |
| NULL, | |
| 0, | |
| InputStruct, | |
| sizeof(InputStruct), | |
| NULL, | |
| NULL, | |
| NULL, | |
| NULL); | |
| Logf("ppm-package-lane-set selector=0x%x client=%u details=0x%llx budget=%u input-struct=%zu kr=0x%x %s", | |
| PPM_SELECTOR_SET_DETAILED_THERMAL_BUDGETS, | |
| PPM_PACKAGE_CLIENT_ID, | |
| (unsigned long long)Details, | |
| Budget, | |
| sizeof(InputStruct), | |
| kr, | |
| ReturnName(kr)); | |
| return kr; | |
| } | |
| static void CallPPMPackageLaneSetOnce(uint32_t Budget, uint64_t Details) | |
| { | |
| io_connect_t Connect = IO_OBJECT_NULL; | |
| if (!OpenServiceType("ApplePassthroughPPM", 0, &Connect)) | |
| { | |
| return; | |
| } | |
| Logf("ppm-package-lane-set-once begin client=%u details=0x%llx budget=%u", | |
| PPM_PACKAGE_CLIENT_ID, | |
| (unsigned long long)Details, | |
| Budget); | |
| ReadPackageLaneBudgetOnConnection(Connect, Details, NULL); | |
| SetPackageLaneBudgetOnConnection(Connect, Budget, Details); | |
| ReadPackageLaneBudgetOnConnection(Connect, Details, NULL); | |
| IOServiceClose(Connect); | |
| Logf("ppm-package-lane-set-once end"); | |
| } | |
| static void CallPPMPackageLaneCycle(uint32_t Budget, uint32_t HoldSeconds, uint64_t Details) | |
| { | |
| if (HoldSeconds < 1) | |
| { | |
| Logf("ppm-package-lane-cycle hold-seconds clamped from %u to 1", HoldSeconds); | |
| HoldSeconds = 1; | |
| } | |
| if (HoldSeconds > 60) | |
| { | |
| Logf("ppm-package-lane-cycle hold-seconds clamped from %u to 60", HoldSeconds); | |
| HoldSeconds = 60; | |
| } | |
| io_connect_t Connect = IO_OBJECT_NULL; | |
| if (!OpenServiceType("ApplePassthroughPPM", 0, &Connect)) | |
| { | |
| return; | |
| } | |
| uint32_t InitialBudget = 0; | |
| Logf("ppm-package-lane-cycle begin client=%u details=0x%llx budget=%u hold-seconds=%u", | |
| PPM_PACKAGE_CLIENT_ID, | |
| (unsigned long long)Details, | |
| Budget, | |
| HoldSeconds); | |
| if (!ReadPackageLaneBudgetOnConnection(Connect, Details, &InitialBudget)) | |
| { | |
| Logf("ppm-package-lane-cycle abort no-initial-readback"); | |
| IOServiceClose(Connect); | |
| Logf("ppm-package-lane-cycle end"); | |
| return; | |
| } | |
| kern_return_t SetKr = SetPackageLaneBudgetOnConnection(Connect, Budget, Details); | |
| ReadPackageLaneBudgetOnConnection(Connect, Details, NULL); | |
| if (SetKr == KERN_SUCCESS) | |
| { | |
| Logf("ppm-package-lane-cycle holding seconds=%u", HoldSeconds); | |
| sleep(HoldSeconds); | |
| ReadPackageLaneBudgetOnConnection(Connect, Details, NULL); | |
| } | |
| else | |
| { | |
| Logf("ppm-package-lane-cycle set failed; restore still attempted"); | |
| } | |
| Logf("ppm-package-lane-cycle restore-initial budget=%u", InitialBudget); | |
| SetPackageLaneBudgetOnConnection(Connect, InitialBudget, Details); | |
| ReadPackageLaneBudgetOnConnection(Connect, Details, NULL); | |
| IOServiceClose(Connect); | |
| Logf("ppm-package-lane-cycle end"); | |
| } | |
| typedef struct PackageLaneSessionControl | |
| { | |
| bool Stop; | |
| bool HasBudget; | |
| uint32_t Budget; | |
| uint32_t Seq; | |
| } PackageLaneSessionControl; | |
| typedef struct PackageLaneSessionArgs | |
| { | |
| uint32_t Budget; | |
| uint32_t MaxSeconds; | |
| uint32_t PollMs; | |
| uint64_t Details; | |
| } PackageLaneSessionArgs; | |
| static uint32_t ClampPackageLaneSessionBudget(uint32_t Budget) | |
| { | |
| if (Budget < PPM_PACKAGE_SESSION_MIN_BUDGET) | |
| { | |
| Logf("ppm-package-lane-session budget clamped from %u to %u", | |
| Budget, | |
| PPM_PACKAGE_SESSION_MIN_BUDGET); | |
| return PPM_PACKAGE_SESSION_MIN_BUDGET; | |
| } | |
| if (Budget > PPM_PACKAGE_SESSION_MAX_BUDGET) | |
| { | |
| Logf("ppm-package-lane-session budget clamped from %u to %u", | |
| Budget, | |
| PPM_PACKAGE_SESSION_MAX_BUDGET); | |
| return PPM_PACKAGE_SESSION_MAX_BUDGET; | |
| } | |
| return Budget; | |
| } | |
| static void WritePackageLaneSessionStatus(const char* Phase, | |
| bool Active, | |
| uint32_t InitialBudget, | |
| uint32_t TargetBudget, | |
| uint32_t ReadbackBudget, | |
| uint32_t ElapsedSeconds, | |
| uint32_t MaxSeconds, | |
| uint32_t SetCount, | |
| kern_return_t LastKr, | |
| const char* Reason) | |
| { | |
| char TmpPath[256]; | |
| snprintf(TmpPath, sizeof(TmpPath), "%s.tmp", kPackageLaneSessionStatusPath); | |
| FILE* Status = fopen(TmpPath, "w"); | |
| if (!Status) | |
| { | |
| Logf("ppm-package-lane-session status-open failed path=%s", TmpPath); | |
| return; | |
| } | |
| fprintf(Status, | |
| "{" | |
| "\"phase\":\"%s\"," | |
| "\"active\":%s," | |
| "\"pid\":%d," | |
| "\"initial_budget\":%u," | |
| "\"target_budget\":%u," | |
| "\"readback_budget\":%u," | |
| "\"elapsed_seconds\":%u," | |
| "\"max_seconds\":%u," | |
| "\"set_count\":%u," | |
| "\"last_kr\":%d," | |
| "\"last_kr_name\":\"%s\"," | |
| "\"reason\":\"%s\"," | |
| "\"updated_at\":%lld" | |
| "}\n", | |
| Phase, | |
| Active ? "true" : "false", | |
| getpid(), | |
| InitialBudget, | |
| TargetBudget, | |
| ReadbackBudget, | |
| ElapsedSeconds, | |
| MaxSeconds, | |
| SetCount, | |
| LastKr, | |
| ReturnName(LastKr), | |
| Reason ? Reason : "", | |
| (long long)time(NULL)); | |
| fclose(Status); | |
| if (rename(TmpPath, kPackageLaneSessionStatusPath) != 0) | |
| { | |
| Logf("ppm-package-lane-session status-rename failed"); | |
| unlink(TmpPath); | |
| } | |
| } | |
| static bool ReadPackageLaneSessionControl(PackageLaneSessionControl* OutControl) | |
| { | |
| memset(OutControl, 0, sizeof(*OutControl)); | |
| FILE* Control = fopen(kPackageLaneSessionControlPath, "r"); | |
| if (!Control) | |
| { | |
| return false; | |
| } | |
| char Line[128]; | |
| while (fgets(Line, sizeof(Line), Control)) | |
| { | |
| char Key[64] = {0}; | |
| unsigned long long Value = 0; | |
| if (sscanf(Line, "%63[^=]=%llu", Key, &Value) != 2) | |
| { | |
| continue; | |
| } | |
| if (strcmp(Key, "stop") == 0) | |
| { | |
| OutControl->Stop = Value != 0; | |
| } | |
| else if (strcmp(Key, "budget") == 0) | |
| { | |
| OutControl->HasBudget = true; | |
| OutControl->Budget = (uint32_t)Value; | |
| } | |
| else if (strcmp(Key, "seq") == 0) | |
| { | |
| OutControl->Seq = (uint32_t)Value; | |
| } | |
| } | |
| fclose(Control); | |
| return true; | |
| } | |
| static void* PackageLaneSessionThread(void* Context) | |
| { | |
| PackageLaneSessionArgs* Args = (PackageLaneSessionArgs*)Context; | |
| uint32_t TargetBudget = ClampPackageLaneSessionBudget(Args->Budget); | |
| uint32_t MaxSeconds = Args->MaxSeconds; | |
| uint32_t PollMs = Args->PollMs; | |
| uint64_t Details = Args->Details; | |
| free(Args); | |
| unlink(kPackageLaneSessionControlPath); | |
| if (MaxSeconds < 1) | |
| { | |
| Logf("ppm-package-lane-session max-seconds clamped from %u to 1", MaxSeconds); | |
| MaxSeconds = 1; | |
| } | |
| if (MaxSeconds > PPM_PACKAGE_SESSION_MAX_SECONDS) | |
| { | |
| Logf("ppm-package-lane-session max-seconds clamped from %u to %u", | |
| MaxSeconds, | |
| PPM_PACKAGE_SESSION_MAX_SECONDS); | |
| MaxSeconds = PPM_PACKAGE_SESSION_MAX_SECONDS; | |
| } | |
| if (PollMs < PPM_PACKAGE_SESSION_MIN_POLL_MS) | |
| { | |
| Logf("ppm-package-lane-session poll-ms clamped from %u to %u", | |
| PollMs, | |
| PPM_PACKAGE_SESSION_MIN_POLL_MS); | |
| PollMs = PPM_PACKAGE_SESSION_MIN_POLL_MS; | |
| } | |
| if (PollMs > PPM_PACKAGE_SESSION_MAX_POLL_MS) | |
| { | |
| Logf("ppm-package-lane-session poll-ms clamped from %u to %u", | |
| PollMs, | |
| PPM_PACKAGE_SESSION_MAX_POLL_MS); | |
| PollMs = PPM_PACKAGE_SESSION_MAX_POLL_MS; | |
| } | |
| io_connect_t Connect = IO_OBJECT_NULL; | |
| if (!OpenServiceType("ApplePassthroughPPM", 0, &Connect)) | |
| { | |
| Logf("ppm-package-lane-session open failed"); | |
| WritePackageLaneSessionStatus("ended", false, 0, TargetBudget, 0, 0, MaxSeconds, 0, kIOReturnNoDevice, "open-failed"); | |
| return NULL; | |
| } | |
| time_t Start = time(NULL); | |
| uint32_t InitialBudget = 0; | |
| uint32_t ReadbackBudget = 0; | |
| uint32_t SetCount = 0; | |
| uint32_t LastControlSeq = 0; | |
| uint32_t LastStatusSecond = UINT32_MAX; | |
| kern_return_t LastKr = KERN_SUCCESS; | |
| const char* EndReason = "timeout"; | |
| Logf("ppm-package-lane-session begin client=%u details=0x%llx budget=%u max-seconds=%u poll-ms=%u", | |
| PPM_PACKAGE_CLIENT_ID, | |
| (unsigned long long)Details, | |
| TargetBudget, | |
| MaxSeconds, | |
| PollMs); | |
| if (!ReadPackageLaneBudgetOnConnection(Connect, Details, &InitialBudget)) | |
| { | |
| Logf("ppm-package-lane-session abort no-initial-readback"); | |
| IOServiceClose(Connect); | |
| WritePackageLaneSessionStatus("ended", false, 0, TargetBudget, 0, 0, MaxSeconds, SetCount, kIOReturnNoDevice, "no-initial-readback"); | |
| Logf("ppm-package-lane-session end reason=no-initial-readback set-count=%u", SetCount); | |
| return NULL; | |
| } | |
| ReadbackBudget = InitialBudget; | |
| WritePackageLaneSessionStatus("starting", true, InitialBudget, TargetBudget, ReadbackBudget, 0, MaxSeconds, SetCount, LastKr, ""); | |
| LastKr = SetPackageLaneBudgetOnConnection(Connect, TargetBudget, Details); | |
| ++SetCount; | |
| ReadPackageLaneBudgetOnConnection(Connect, Details, &ReadbackBudget); | |
| WritePackageLaneSessionStatus("active", true, InitialBudget, TargetBudget, ReadbackBudget, 0, MaxSeconds, SetCount, LastKr, ""); | |
| for (;;) | |
| { | |
| time_t Now = time(NULL); | |
| uint32_t Elapsed = (uint32_t)(Now - Start); | |
| if (Elapsed >= MaxSeconds) | |
| { | |
| EndReason = "timeout"; | |
| break; | |
| } | |
| PackageLaneSessionControl Control; | |
| if (ReadPackageLaneSessionControl(&Control)) | |
| { | |
| if (Control.Stop) | |
| { | |
| EndReason = "stop-requested"; | |
| break; | |
| } | |
| if (Control.HasBudget && (Control.Seq != 0 || Control.Budget != TargetBudget)) | |
| { | |
| if (Control.Seq == 0 || Control.Seq != LastControlSeq) | |
| { | |
| TargetBudget = ClampPackageLaneSessionBudget(Control.Budget); | |
| LastControlSeq = Control.Seq; | |
| LastKr = SetPackageLaneBudgetOnConnection(Connect, TargetBudget, Details); | |
| ++SetCount; | |
| ReadPackageLaneBudgetOnConnection(Connect, Details, &ReadbackBudget); | |
| WritePackageLaneSessionStatus("active", true, InitialBudget, TargetBudget, ReadbackBudget, Elapsed, MaxSeconds, SetCount, LastKr, ""); | |
| } | |
| } | |
| } | |
| if (Elapsed != LastStatusSecond) | |
| { | |
| LastStatusSecond = Elapsed; | |
| ReadPackageLaneBudgetOnConnection(Connect, Details, &ReadbackBudget); | |
| WritePackageLaneSessionStatus("active", true, InitialBudget, TargetBudget, ReadbackBudget, Elapsed, MaxSeconds, SetCount, LastKr, ""); | |
| } | |
| usleep((useconds_t)PollMs * 1000); | |
| } | |
| uint32_t Elapsed = (uint32_t)(time(NULL) - Start); | |
| Logf("ppm-package-lane-session restore-initial budget=%u reason=%s", InitialBudget, EndReason); | |
| WritePackageLaneSessionStatus("restoring", true, InitialBudget, TargetBudget, ReadbackBudget, Elapsed, MaxSeconds, SetCount, LastKr, EndReason); | |
| LastKr = SetPackageLaneBudgetOnConnection(Connect, InitialBudget, Details); | |
| ++SetCount; | |
| ReadPackageLaneBudgetOnConnection(Connect, Details, &ReadbackBudget); | |
| IOServiceClose(Connect); | |
| WritePackageLaneSessionStatus("ended", false, InitialBudget, InitialBudget, ReadbackBudget, Elapsed, MaxSeconds, SetCount, LastKr, EndReason); | |
| Logf("ppm-package-lane-session end reason=%s set-count=%u", EndReason, SetCount); | |
| return NULL; | |
| } | |
| static void StartPPMPackageLaneSession(uint32_t Budget, uint32_t MaxSeconds, uint32_t PollMs, uint64_t Details) | |
| { | |
| PackageLaneSessionArgs* Args = (PackageLaneSessionArgs*)calloc(1, sizeof(*Args)); | |
| if (!Args) | |
| { | |
| Logf("ppm-package-lane-session allocation failed"); | |
| return; | |
| } | |
| Args->Budget = Budget; | |
| Args->MaxSeconds = MaxSeconds; | |
| Args->PollMs = PollMs; | |
| Args->Details = Details; | |
| pthread_t Thread; | |
| int rc = pthread_create(&Thread, NULL, PackageLaneSessionThread, Args); | |
| if (rc != 0) | |
| { | |
| Logf("ppm-package-lane-session pthread_create failed rc=%d", rc); | |
| free(Args); | |
| return; | |
| } | |
| pthread_detach(Thread); | |
| Logf("ppm-package-lane-session thread-started client=%u details=0x%llx budget=%u max-seconds=%u poll-ms=%u", | |
| PPM_PACKAGE_CLIENT_ID, | |
| (unsigned long long)Details, | |
| Budget, | |
| MaxSeconds, | |
| PollMs); | |
| } | |
| typedef struct PackageLanePinArgs | |
| { | |
| uint32_t Budget; | |
| uint32_t Seconds; | |
| uint32_t IntervalMs; | |
| uint64_t Details; | |
| } PackageLanePinArgs; | |
| static void* PackageLanePinThread(void* Context) | |
| { | |
| PackageLanePinArgs* Args = (PackageLanePinArgs*)Context; | |
| uint32_t Budget = Args->Budget; | |
| uint32_t Seconds = Args->Seconds; | |
| uint32_t IntervalMs = Args->IntervalMs; | |
| uint64_t Details = Args->Details; | |
| free(Args); | |
| io_connect_t Connect = IO_OBJECT_NULL; | |
| if (!OpenServiceType("ApplePassthroughPPM", 0, &Connect)) | |
| { | |
| Logf("ppm-package-lane-pin open failed"); | |
| return NULL; | |
| } | |
| time_t Start = time(NULL); | |
| uint32_t Iteration = 0; | |
| uint32_t InitialBudget = 0; | |
| bool HaveInitialBudget = false; | |
| Logf("ppm-package-lane-pin begin client=%u details=0x%llx budget=%u seconds=%u interval-ms=%u", | |
| PPM_PACKAGE_CLIENT_ID, | |
| (unsigned long long)Details, | |
| Budget, | |
| Seconds, | |
| IntervalMs); | |
| HaveInitialBudget = ReadPackageLaneBudgetOnConnection(Connect, Details, &InitialBudget); | |
| for (;;) | |
| { | |
| time_t Now = time(NULL); | |
| uint32_t Elapsed = (uint32_t)(Now - Start); | |
| if (Seconds != 0 && Elapsed >= Seconds) | |
| { | |
| break; | |
| } | |
| Logf("ppm-package-lane-pin iteration=%u elapsed=%u", Iteration, Elapsed); | |
| SetPackageLaneBudgetOnConnection(Connect, Budget, Details); | |
| ReadPackageLaneBudgetOnConnection(Connect, Details, NULL); | |
| ++Iteration; | |
| usleep((useconds_t)IntervalMs * 1000); | |
| } | |
| if (Seconds != 0 && HaveInitialBudget) | |
| { | |
| Logf("ppm-package-lane-pin restore-initial budget=%u", InitialBudget); | |
| SetPackageLaneBudgetOnConnection(Connect, InitialBudget, Details); | |
| } | |
| else if (Seconds != 0) | |
| { | |
| Logf("ppm-package-lane-pin restore-initial skipped; no initial readback"); | |
| } | |
| ReadPackageLaneBudgetOnConnection(Connect, Details, NULL); | |
| IOServiceClose(Connect); | |
| Logf("ppm-package-lane-pin end iterations=%u", Iteration); | |
| return NULL; | |
| } | |
| static void StartPPMPackageLanePin(uint32_t Budget, uint32_t Seconds, uint32_t IntervalMs, uint64_t Details) | |
| { | |
| if (IntervalMs < 100) | |
| { | |
| Logf("ppm-package-lane-pin interval-ms clamped from %u to 100", IntervalMs); | |
| IntervalMs = 100; | |
| } | |
| if (IntervalMs > 60000) | |
| { | |
| Logf("ppm-package-lane-pin interval-ms clamped from %u to 60000", IntervalMs); | |
| IntervalMs = 60000; | |
| } | |
| PackageLanePinArgs* Args = (PackageLanePinArgs*)calloc(1, sizeof(*Args)); | |
| if (!Args) | |
| { | |
| Logf("ppm-package-lane-pin allocation failed"); | |
| return; | |
| } | |
| Args->Budget = Budget; | |
| Args->Seconds = Seconds; | |
| Args->IntervalMs = IntervalMs; | |
| Args->Details = Details; | |
| pthread_t Thread; | |
| int rc = pthread_create(&Thread, NULL, PackageLanePinThread, Args); | |
| if (rc != 0) | |
| { | |
| Logf("ppm-package-lane-pin pthread_create failed rc=%d", rc); | |
| free(Args); | |
| return; | |
| } | |
| pthread_detach(Thread); | |
| Logf("ppm-package-lane-pin thread-started client=%u details=0x%llx budget=%u seconds=%u interval-ms=%u", | |
| PPM_PACKAGE_CLIENT_ID, | |
| (unsigned long long)Details, | |
| Budget, | |
| Seconds, | |
| IntervalMs); | |
| } | |
| static void CallGenericZeroStructMethod(const char* ServiceName, | |
| uint32_t OpenType, | |
| uint32_t Selector, | |
| const uint64_t* InputScalars, | |
| uint32_t InputScalarCount, | |
| size_t RequestedInputStructSize, | |
| uint32_t RequestedOutputScalars, | |
| size_t RequestedOutputStructSize, | |
| unsigned char FillByte) | |
| { | |
| unsigned char InputStruct[MAX_STRUCT_BYTES]; | |
| size_t InputStructSize = RequestedInputStructSize > MAX_STRUCT_BYTES ? MAX_STRUCT_BYTES : RequestedInputStructSize; | |
| memset(InputStruct, FillByte, InputStructSize); | |
| CallGenericMethod(ServiceName, | |
| OpenType, | |
| Selector, | |
| InputScalars, | |
| InputScalarCount, | |
| InputStructSize == 0 ? NULL : InputStruct, | |
| InputStructSize, | |
| RequestedOutputScalars, | |
| RequestedOutputStructSize); | |
| if (InputStructSize > 0) | |
| { | |
| LogStructBytes("call-method-input-struct", Selector, InputStruct, InputStructSize); | |
| } | |
| } | |
| static void CallStructZeroMethod(const char* ServiceName, | |
| uint32_t OpenType, | |
| uint32_t Selector, | |
| size_t RequestedInputStructSize, | |
| size_t RequestedOutputStructSize, | |
| unsigned char FillByte) | |
| { | |
| io_connect_t Connect = IO_OBJECT_NULL; | |
| if (!OpenServiceType(ServiceName, OpenType, &Connect)) | |
| { | |
| return; | |
| } | |
| unsigned char InputStruct[MAX_STRUCT_BYTES]; | |
| unsigned char OutputStruct[MAX_STRUCT_BYTES] = {0}; | |
| size_t InputStructSize = RequestedInputStructSize > MAX_STRUCT_BYTES ? MAX_STRUCT_BYTES : RequestedInputStructSize; | |
| size_t OutputStructSize = RequestedOutputStructSize > MAX_STRUCT_BYTES ? MAX_STRUCT_BYTES : RequestedOutputStructSize; | |
| memset(InputStruct, FillByte, InputStructSize); | |
| kern_return_t kr = IOConnectCallStructMethod(Connect, | |
| Selector, | |
| InputStructSize == 0 ? NULL : InputStruct, | |
| InputStructSize, | |
| OutputStructSize == 0 ? NULL : OutputStruct, | |
| OutputStructSize == 0 ? NULL : &OutputStructSize); | |
| Logf("call-struct-zero service=%s type=%u selector=%u input-struct=%zu requested-output-struct=%zu fill=0x%02x kr=0x%x %s output-struct=%zu", | |
| ServiceName, | |
| OpenType, | |
| Selector, | |
| RequestedInputStructSize, | |
| RequestedOutputStructSize, | |
| FillByte, | |
| kr, | |
| ReturnName(kr), | |
| OutputStructSize); | |
| if (InputStructSize > 0) | |
| { | |
| LogStructBytes("call-struct-zero-input", Selector, InputStruct, InputStructSize); | |
| } | |
| if (kr == KERN_SUCCESS && OutputStructSize > 0) | |
| { | |
| LogStructBytes("call-struct-zero-output", Selector, OutputStruct, OutputStructSize); | |
| } | |
| IOServiceClose(Connect); | |
| } | |
| static void ScanStructZeroMethods(const char* ServiceName, | |
| uint32_t OpenType, | |
| uint32_t StartSelector, | |
| uint32_t EndSelector, | |
| size_t InputStructSize, | |
| size_t OutputStructSize, | |
| unsigned char FillByte) | |
| { | |
| for (uint32_t Selector = StartSelector; Selector <= EndSelector; ++Selector) | |
| { | |
| CallStructZeroMethod(ServiceName, OpenType, Selector, InputStructSize, OutputStructSize, FillByte); | |
| } | |
| } | |
| static void ScanGenericZeroStructMethods(const char* ServiceName, | |
| uint32_t OpenType, | |
| uint32_t StartSelector, | |
| uint32_t EndSelector, | |
| const uint64_t* InputScalars, | |
| uint32_t InputScalarCount, | |
| size_t InputStructSize, | |
| uint32_t OutputScalarCount, | |
| size_t OutputStructSize, | |
| unsigned char FillByte) | |
| { | |
| for (uint32_t Selector = StartSelector; Selector <= EndSelector; ++Selector) | |
| { | |
| CallGenericZeroStructMethod(ServiceName, | |
| OpenType, | |
| Selector, | |
| InputScalars, | |
| InputScalarCount, | |
| InputStructSize, | |
| OutputScalarCount, | |
| OutputStructSize, | |
| FillByte); | |
| } | |
| } | |
| static void ScanScalarShapes(const char* ServiceName, | |
| uint32_t OpenType, | |
| uint32_t StartSelector, | |
| uint32_t EndSelector, | |
| uint32_t MaxInputScalars, | |
| uint32_t MaxOutputScalars, | |
| size_t OutputStructSize, | |
| uint64_t FillValue) | |
| { | |
| uint64_t InputScalars[MAX_SCALARS] = {0}; | |
| uint32_t InputLimit = MaxInputScalars > MAX_SCALARS ? MAX_SCALARS : MaxInputScalars; | |
| uint32_t OutputLimit = MaxOutputScalars > MAX_SCALARS ? MAX_SCALARS : MaxOutputScalars; | |
| for (uint32_t i = 0; i < MAX_SCALARS; ++i) | |
| { | |
| InputScalars[i] = FillValue; | |
| } | |
| for (uint32_t Selector = StartSelector; Selector <= EndSelector; ++Selector) | |
| { | |
| for (uint32_t InputCount = 0; InputCount <= InputLimit; ++InputCount) | |
| { | |
| for (uint32_t OutputCount = 0; OutputCount <= OutputLimit; ++OutputCount) | |
| { | |
| CallGenericMethod(ServiceName, | |
| OpenType, | |
| Selector, | |
| InputScalars, | |
| InputCount, | |
| NULL, | |
| 0, | |
| OutputCount, | |
| OutputStructSize); | |
| } | |
| } | |
| } | |
| } | |
| static void CallNoInputMethod(const char* ServiceName, uint32_t OpenType, uint32_t Selector) | |
| { | |
| io_connect_t Connect = IO_OBJECT_NULL; | |
| if (!OpenServiceType(ServiceName, OpenType, &Connect)) | |
| { | |
| return; | |
| } | |
| kern_return_t kr = IOConnectCallMethod(Connect, Selector, NULL, 0, NULL, 0, NULL, NULL, NULL, NULL); | |
| Logf("call-no-input service=%s type=%u selector=%u kr=0x%x %s", | |
| ServiceName, | |
| OpenType, | |
| Selector, | |
| kr, | |
| ReturnName(kr)); | |
| IOServiceClose(Connect); | |
| } | |
| static kern_return_t CallAGXPerfRecord(io_connect_t Connect, | |
| const char* Label, | |
| uint32_t Selector, | |
| const unsigned char Input[8]) | |
| { | |
| unsigned char Output[8] = {0}; | |
| size_t OutputSize = sizeof(Output); | |
| kern_return_t kr = IOConnectCallStructMethod(Connect, Selector, Input, 8, Output, &OutputSize); | |
| Logf("agx-perf-record label=%s selector=0x%x kr=0x%x %s output-struct=%zu", | |
| Label, | |
| Selector, | |
| kr, | |
| ReturnName(kr), | |
| OutputSize); | |
| LogStructBytes("agx-perf-input", Selector, Input, 8); | |
| if (kr == KERN_SUCCESS) | |
| { | |
| LogStructBytes("agx-perf-output", Selector, Output, OutputSize); | |
| } | |
| return kr; | |
| } | |
| static void CallAGXPerfAssertCycle(const char* ServiceName, uint32_t OpenType, uint32_t Level, uint32_t HoldSeconds) | |
| { | |
| if (Level < 1 || Level > 3) | |
| { | |
| Logf("agx-perf-assert-cycle invalid-level=%u", Level); | |
| return; | |
| } | |
| if (HoldSeconds > 30) | |
| { | |
| Logf("agx-perf-assert-cycle hold clamped from %u to 30", HoldSeconds); | |
| HoldSeconds = 30; | |
| } | |
| io_connect_t Connect = IO_OBJECT_NULL; | |
| if (!OpenServiceType(ServiceName, OpenType, &Connect)) | |
| { | |
| return; | |
| } | |
| unsigned char AssertRecord[8] = {1, (unsigned char)Level, 0, 0, 0, 0, 0, 0}; | |
| unsigned char ReleaseRecord[8] = {1, 4, 0, 0, 0, 0, 0, 0}; | |
| unsigned char PerfQuery[8] = {2, 0, 0, 0, 0, 0, 0, 0}; | |
| unsigned char ConsistentQuery[8] = {3, 0, 0, 0, 0, 0, 0, 0}; | |
| Logf("agx-perf-assert-cycle begin service=%s type=%u level=%u hold-seconds=%u", | |
| ServiceName, | |
| OpenType, | |
| Level, | |
| HoldSeconds); | |
| kern_return_t SetKr = CallAGXPerfRecord(Connect, "assert", AGX_SELECTOR_PERF_STATE, AssertRecord); | |
| if (SetKr == KERN_SUCCESS) | |
| { | |
| CallAGXPerfRecord(Connect, "perf-query-after-assert", AGX_SELECTOR_PERF_STATE, PerfQuery); | |
| CallAGXPerfRecord(Connect, "consistent-query-after-assert", AGX_SELECTOR_CONSISTENT_PERF_STATE, ConsistentQuery); | |
| Logf("agx-perf-assert-cycle holding seconds=%u", HoldSeconds); | |
| sleep(HoldSeconds); | |
| CallAGXPerfRecord(Connect, "release", AGX_SELECTOR_PERF_STATE, ReleaseRecord); | |
| CallAGXPerfRecord(Connect, "perf-query-after-release", AGX_SELECTOR_PERF_STATE, PerfQuery); | |
| CallAGXPerfRecord(Connect, "consistent-query-after-release", AGX_SELECTOR_CONSISTENT_PERF_STATE, ConsistentQuery); | |
| } | |
| else | |
| { | |
| Logf("agx-perf-assert-cycle set failed; release skipped"); | |
| } | |
| IOServiceClose(Connect); | |
| Logf("agx-perf-assert-cycle end"); | |
| } | |
| static void CallAGXPerfAssertHold(const char* ServiceName, uint32_t OpenType, uint32_t Level, uint32_t HoldSeconds) | |
| { | |
| if (Level < 1 || Level > 3) | |
| { | |
| Logf("agx-perf-assert-hold invalid-level=%u", Level); | |
| return; | |
| } | |
| io_connect_t Connect = IO_OBJECT_NULL; | |
| if (!OpenServiceType(ServiceName, OpenType, &Connect)) | |
| { | |
| return; | |
| } | |
| unsigned char AssertRecord[8] = {1, (unsigned char)Level, 0, 0, 0, 0, 0, 0}; | |
| unsigned char PerfQuery[8] = {2, 0, 0, 0, 0, 0, 0, 0}; | |
| unsigned char ConsistentQuery[8] = {3, 0, 0, 0, 0, 0, 0, 0}; | |
| Logf("agx-perf-assert-hold begin service=%s type=%u level=%u hold-seconds=%u pid=%d", | |
| ServiceName, | |
| OpenType, | |
| Level, | |
| HoldSeconds, | |
| getpid()); | |
| kern_return_t SetKr = CallAGXPerfRecord(Connect, "assert", AGX_SELECTOR_PERF_STATE, AssertRecord); | |
| if (SetKr == KERN_SUCCESS) | |
| { | |
| CallAGXPerfRecord(Connect, "perf-query-after-assert", AGX_SELECTOR_PERF_STATE, PerfQuery); | |
| CallAGXPerfRecord(Connect, "consistent-query-after-assert", AGX_SELECTOR_CONSISTENT_PERF_STATE, ConsistentQuery); | |
| Logf("agx-perf-assert-hold active; stop by terminating this launchd service"); | |
| if (HoldSeconds == 0) | |
| { | |
| for (;;) | |
| { | |
| sleep(3600); | |
| } | |
| } | |
| else | |
| { | |
| sleep(HoldSeconds); | |
| } | |
| } | |
| else | |
| { | |
| Logf("agx-perf-assert-hold set failed"); | |
| } | |
| IOServiceClose(Connect); | |
| Logf("agx-perf-assert-hold end"); | |
| } | |
| static void ScanNoInputMethods(const char* ServiceName, uint32_t OpenType, uint32_t MaxSelector) | |
| { | |
| for (uint32_t Selector = 0; Selector <= MaxSelector; ++Selector) | |
| { | |
| CallNoInputMethod(ServiceName, OpenType, Selector); | |
| } | |
| } | |
| static void ScanPPMNoInputAllTypes(uint32_t MaxType, uint32_t MaxSelector) | |
| { | |
| for (uint32_t Type = 0; Type <= MaxType; ++Type) | |
| { | |
| ScanNoInputMethods("ApplePassthroughPPM", Type, MaxSelector); | |
| } | |
| } | |
| __attribute__((constructor)) | |
| static void EntitledDirectEntry(void) | |
| { | |
| char Request[256]; | |
| ReadRequest(Request, sizeof(Request)); | |
| if (Request[0] == '\0') | |
| { | |
| Logf("loaded: no request"); | |
| return; | |
| } | |
| Logf("loaded request=\"%s\"", Request); | |
| char Command[64] = {0}; | |
| double Watts = 0.0; | |
| unsigned long long A = 0; | |
| unsigned long long B = 0; | |
| if (sscanf(Request, "%63s", Command) != 1) | |
| { | |
| Logf("parse failed"); | |
| return; | |
| } | |
| if (strcmp(Command, "clpc-workload") == 0) | |
| { | |
| unsigned long long Option = 0; | |
| if (sscanf(Request, "%*s %lf %llu", &Watts, &Option) != 2) | |
| { | |
| Logf("parse clpc-workload failed"); | |
| return; | |
| } | |
| CallCLPCWorkload(Watts, Option); | |
| return; | |
| } | |
| if (strcmp(Command, "clpc-target") == 0) | |
| { | |
| if (sscanf(Request, "%*s %llu %lf %llu", &A, &Watts, &B) != 3) | |
| { | |
| Logf("parse clpc-target failed"); | |
| return; | |
| } | |
| CallCLPCTarget(A, Watts, B); | |
| return; | |
| } | |
| if (strcmp(Command, "ppm-open-scan") == 0) | |
| { | |
| ScanPPMOpen(); | |
| return; | |
| } | |
| if (strcmp(Command, "ppm-safe-cpms-batch") == 0) | |
| { | |
| char ParseBuffer[256]; | |
| strncpy(ParseBuffer, Request, sizeof(ParseBuffer) - 1); | |
| ParseBuffer[sizeof(ParseBuffer) - 1] = '\0'; | |
| char* Tokens[20] = {0}; | |
| size_t Count = Tokenize(ParseBuffer, Tokens, sizeof(Tokens) / sizeof(Tokens[0])); | |
| uint64_t Clients[MAX_SCALARS] = {0}; | |
| uint32_t ClientCount = 0; | |
| for (size_t Arg = 1; Arg < Count && ClientCount < MAX_SCALARS; ++Arg) | |
| { | |
| Clients[ClientCount++] = ParseU64(Tokens[Arg]); | |
| } | |
| CallPPMSafeCPMSBatch(Clients, ClientCount); | |
| return; | |
| } | |
| if (strcmp(Command, "ppm-package-lane-set-once") == 0) | |
| { | |
| char ParseBuffer[256]; | |
| strncpy(ParseBuffer, Request, sizeof(ParseBuffer) - 1); | |
| ParseBuffer[sizeof(ParseBuffer) - 1] = '\0'; | |
| char* Tokens[4] = {0}; | |
| size_t Count = Tokenize(ParseBuffer, Tokens, sizeof(Tokens) / sizeof(Tokens[0])); | |
| if (Count < 2) | |
| { | |
| Logf("parse ppm-package-lane-set-once failed; expected: ppm-package-lane-set-once <budget> [details]"); | |
| return; | |
| } | |
| uint32_t Budget = (uint32_t)ParseU64(Tokens[1]); | |
| uint64_t Details = Count >= 3 ? ParseU64(Tokens[2]) : 0; | |
| CallPPMPackageLaneSetOnce(Budget, Details); | |
| return; | |
| } | |
| if (strcmp(Command, "ppm-package-lane-cycle") == 0) | |
| { | |
| char ParseBuffer[256]; | |
| strncpy(ParseBuffer, Request, sizeof(ParseBuffer) - 1); | |
| ParseBuffer[sizeof(ParseBuffer) - 1] = '\0'; | |
| char* Tokens[5] = {0}; | |
| size_t Count = Tokenize(ParseBuffer, Tokens, sizeof(Tokens) / sizeof(Tokens[0])); | |
| if (Count < 3) | |
| { | |
| Logf("parse ppm-package-lane-cycle failed; expected: ppm-package-lane-cycle <budget> <hold-seconds> [details]"); | |
| return; | |
| } | |
| uint32_t Budget = (uint32_t)ParseU64(Tokens[1]); | |
| uint32_t HoldSeconds = (uint32_t)ParseU64(Tokens[2]); | |
| uint64_t Details = Count >= 4 ? ParseU64(Tokens[3]) : 0; | |
| CallPPMPackageLaneCycle(Budget, HoldSeconds, Details); | |
| return; | |
| } | |
| if (strcmp(Command, "ppm-package-lane-session") == 0) | |
| { | |
| char ParseBuffer[256]; | |
| strncpy(ParseBuffer, Request, sizeof(ParseBuffer) - 1); | |
| ParseBuffer[sizeof(ParseBuffer) - 1] = '\0'; | |
| char* Tokens[6] = {0}; | |
| size_t Count = Tokenize(ParseBuffer, Tokens, sizeof(Tokens) / sizeof(Tokens[0])); | |
| if (Count < 4) | |
| { | |
| Logf("parse ppm-package-lane-session failed; expected: ppm-package-lane-session <budget> <max-seconds> <poll-ms> [details]"); | |
| return; | |
| } | |
| uint32_t Budget = (uint32_t)ParseU64(Tokens[1]); | |
| uint32_t MaxSeconds = (uint32_t)ParseU64(Tokens[2]); | |
| uint32_t PollMs = (uint32_t)ParseU64(Tokens[3]); | |
| uint64_t Details = Count >= 5 ? ParseU64(Tokens[4]) : 0; | |
| StartPPMPackageLaneSession(Budget, MaxSeconds, PollMs, Details); | |
| return; | |
| } | |
| if (strcmp(Command, "ppm-package-lane-pin") == 0) | |
| { | |
| char ParseBuffer[256]; | |
| strncpy(ParseBuffer, Request, sizeof(ParseBuffer) - 1); | |
| ParseBuffer[sizeof(ParseBuffer) - 1] = '\0'; | |
| char* Tokens[6] = {0}; | |
| size_t Count = Tokenize(ParseBuffer, Tokens, sizeof(Tokens) / sizeof(Tokens[0])); | |
| if (Count < 4) | |
| { | |
| Logf("parse ppm-package-lane-pin failed; expected: ppm-package-lane-pin <budget> <seconds> <interval-ms> [details]"); | |
| return; | |
| } | |
| uint32_t Budget = (uint32_t)ParseU64(Tokens[1]); | |
| uint32_t Seconds = (uint32_t)ParseU64(Tokens[2]); | |
| uint32_t IntervalMs = (uint32_t)ParseU64(Tokens[3]); | |
| uint64_t Details = Count >= 5 ? ParseU64(Tokens[4]) : 0; | |
| StartPPMPackageLanePin(Budget, Seconds, IntervalMs, Details); | |
| return; | |
| } | |
| if (strcmp(Command, "coalition-suppress-get") == 0) | |
| { | |
| char ParseBuffer[256]; | |
| strncpy(ParseBuffer, Request, sizeof(ParseBuffer) - 1); | |
| ParseBuffer[sizeof(ParseBuffer) - 1] = '\0'; | |
| char* Tokens[3] = {0}; | |
| size_t Count = Tokenize(ParseBuffer, Tokens, sizeof(Tokens) / sizeof(Tokens[0])); | |
| if (Count < 2) | |
| { | |
| Logf("parse coalition-suppress-get failed; expected: coalition-suppress-get <pid>"); | |
| return; | |
| } | |
| CallCoalitionSuppressGet((pid_t)ParseU64(Tokens[1])); | |
| return; | |
| } | |
| if (strcmp(Command, "coalition-suppress-set") == 0) | |
| { | |
| char ParseBuffer[256]; | |
| strncpy(ParseBuffer, Request, sizeof(ParseBuffer) - 1); | |
| ParseBuffer[sizeof(ParseBuffer) - 1] = '\0'; | |
| char* Tokens[4] = {0}; | |
| size_t Count = Tokenize(ParseBuffer, Tokens, sizeof(Tokens) / sizeof(Tokens[0])); | |
| uint32_t Value = 0; | |
| if (Count < 3 || !ParseCoalitionSuppressValue(Tokens[2], &Value)) | |
| { | |
| Logf("parse coalition-suppress-set failed; expected: coalition-suppress-set <pid> <none|darwin-bg>"); | |
| return; | |
| } | |
| CallCoalitionSuppressSet((pid_t)ParseU64(Tokens[1]), Value); | |
| return; | |
| } | |
| if (strcmp(Command, "task-suppress-get") == 0) | |
| { | |
| char ParseBuffer[256]; | |
| strncpy(ParseBuffer, Request, sizeof(ParseBuffer) - 1); | |
| ParseBuffer[sizeof(ParseBuffer) - 1] = '\0'; | |
| char* Tokens[3] = {0}; | |
| size_t Count = Tokenize(ParseBuffer, Tokens, sizeof(Tokens) / sizeof(Tokens[0])); | |
| if (Count < 2) | |
| { | |
| Logf("parse task-suppress-get failed; expected: task-suppress-get <pid>"); | |
| return; | |
| } | |
| CallTaskSuppressGet((pid_t)ParseU64(Tokens[1])); | |
| return; | |
| } | |
| if (strcmp(Command, "task-suppress-set") == 0) | |
| { | |
| char ParseBuffer[256]; | |
| strncpy(ParseBuffer, Request, sizeof(ParseBuffer) - 1); | |
| ParseBuffer[sizeof(ParseBuffer) - 1] = '\0'; | |
| char* Tokens[4] = {0}; | |
| size_t Count = Tokenize(ParseBuffer, Tokens, sizeof(Tokens) / sizeof(Tokens[0])); | |
| if (Count < 3) | |
| { | |
| Logf("parse task-suppress-set failed; expected: task-suppress-set <pid> <active|inactive>"); | |
| return; | |
| } | |
| bool Active = false; | |
| if (strcmp(Tokens[2], "active") == 0 || strcmp(Tokens[2], "on") == 0 || strcmp(Tokens[2], "1") == 0) | |
| { | |
| Active = true; | |
| } | |
| else if (strcmp(Tokens[2], "inactive") == 0 || strcmp(Tokens[2], "off") == 0 || strcmp(Tokens[2], "0") == 0) | |
| { | |
| Active = false; | |
| } | |
| else | |
| { | |
| Logf("parse task-suppress-set failed; invalid state=%s", Tokens[2]); | |
| return; | |
| } | |
| CallTaskSuppressSet((pid_t)ParseU64(Tokens[1]), Active); | |
| return; | |
| } | |
| if (strcmp(Command, "task-qos-get") == 0) | |
| { | |
| char ParseBuffer[256]; | |
| strncpy(ParseBuffer, Request, sizeof(ParseBuffer) - 1); | |
| ParseBuffer[sizeof(ParseBuffer) - 1] = '\0'; | |
| char* Tokens[3] = {0}; | |
| size_t Count = Tokenize(ParseBuffer, Tokens, sizeof(Tokens) / sizeof(Tokens[0])); | |
| if (Count < 2) | |
| { | |
| Logf("parse task-qos-get failed; expected: task-qos-get <pid>"); | |
| return; | |
| } | |
| CallTaskQosGet((pid_t)ParseU64(Tokens[1])); | |
| return; | |
| } | |
| if (strcmp(Command, "task-qos-override-set") == 0) | |
| { | |
| char ParseBuffer[256]; | |
| strncpy(ParseBuffer, Request, sizeof(ParseBuffer) - 1); | |
| ParseBuffer[sizeof(ParseBuffer) - 1] = '\0'; | |
| char* Tokens[5] = {0}; | |
| size_t Count = Tokenize(ParseBuffer, Tokens, sizeof(Tokens) / sizeof(Tokens[0])); | |
| int LatencyTier = -1; | |
| int ThroughputTier = -1; | |
| if (Count < 4 || | |
| !ParseQosTierToken(Tokens[2], &LatencyTier) || | |
| !ParseQosTierToken(Tokens[3], &ThroughputTier)) | |
| { | |
| Logf("parse task-qos-override-set failed; expected: task-qos-override-set <pid> <latency 0..5|none> <throughput 0..5|none>"); | |
| return; | |
| } | |
| CallTaskQosOverrideSet((pid_t)ParseU64(Tokens[1]), LatencyTier, ThroughputTier); | |
| return; | |
| } | |
| if (strcmp(Command, "darwin-process-clear") == 0) | |
| { | |
| char ParseBuffer[256]; | |
| strncpy(ParseBuffer, Request, sizeof(ParseBuffer) - 1); | |
| ParseBuffer[sizeof(ParseBuffer) - 1] = '\0'; | |
| char* Tokens[3] = {0}; | |
| size_t Count = Tokenize(ParseBuffer, Tokens, sizeof(Tokens) / sizeof(Tokens[0])); | |
| if (Count < 2) | |
| { | |
| Logf("parse darwin-process-clear failed; expected: darwin-process-clear <pid>"); | |
| return; | |
| } | |
| CallDarwinProcessClear((pid_t)ParseU64(Tokens[1])); | |
| return; | |
| } | |
| if (strcmp(Command, "darwin-role-get") == 0) | |
| { | |
| char ParseBuffer[256]; | |
| strncpy(ParseBuffer, Request, sizeof(ParseBuffer) - 1); | |
| ParseBuffer[sizeof(ParseBuffer) - 1] = '\0'; | |
| char* Tokens[3] = {0}; | |
| size_t Count = Tokenize(ParseBuffer, Tokens, sizeof(Tokens) / sizeof(Tokens[0])); | |
| if (Count < 2) | |
| { | |
| Logf("parse darwin-role-get failed; expected: darwin-role-get <pid>"); | |
| return; | |
| } | |
| CallDarwinRoleGet((pid_t)ParseU64(Tokens[1])); | |
| return; | |
| } | |
| if (strcmp(Command, "darwin-role-set") == 0) | |
| { | |
| char ParseBuffer[256]; | |
| strncpy(ParseBuffer, Request, sizeof(ParseBuffer) - 1); | |
| ParseBuffer[sizeof(ParseBuffer) - 1] = '\0'; | |
| char* Tokens[4] = {0}; | |
| size_t Count = Tokenize(ParseBuffer, Tokens, sizeof(Tokens) / sizeof(Tokens[0])); | |
| int Value = 0; | |
| if (Count < 3 || !ParseNamedInt(Tokens[2], kDarwinRoles, &Value)) | |
| { | |
| Logf("parse darwin-role-set failed; expected: darwin-role-set <pid> <default|ui_focal|ui|non_ui|ui_non_focal|tal_launch|bg|user_init|0..7>"); | |
| return; | |
| } | |
| CallDarwinRoleSet((pid_t)ParseU64(Tokens[1]), Value); | |
| return; | |
| } | |
| if (strcmp(Command, "gpu-role-get") == 0) | |
| { | |
| char ParseBuffer[256]; | |
| strncpy(ParseBuffer, Request, sizeof(ParseBuffer) - 1); | |
| ParseBuffer[sizeof(ParseBuffer) - 1] = '\0'; | |
| char* Tokens[3] = {0}; | |
| size_t Count = Tokenize(ParseBuffer, Tokens, sizeof(Tokens) / sizeof(Tokens[0])); | |
| if (Count < 2) | |
| { | |
| Logf("parse gpu-role-get failed; expected: gpu-role-get <pid>"); | |
| return; | |
| } | |
| CallGpuRoleGet((pid_t)ParseU64(Tokens[1])); | |
| return; | |
| } | |
| if (strcmp(Command, "gpu-role-set") == 0) | |
| { | |
| char ParseBuffer[256]; | |
| strncpy(ParseBuffer, Request, sizeof(ParseBuffer) - 1); | |
| ParseBuffer[sizeof(ParseBuffer) - 1] = '\0'; | |
| char* Tokens[4] = {0}; | |
| size_t Count = Tokenize(ParseBuffer, Tokens, sizeof(Tokens) / sizeof(Tokens[0])); | |
| int Value = 0; | |
| if (Count < 3 || !ParseNamedInt(Tokens[2], kGpuRoles, &Value)) | |
| { | |
| Logf("parse gpu-role-set failed; expected: gpu-role-set <pid> <unknown|allow|deny|background|utility|ui_non_focal|ui|ui_focal|0..7>"); | |
| return; | |
| } | |
| CallGpuRoleSet((pid_t)ParseU64(Tokens[1]), Value); | |
| return; | |
| } | |
| if (strcmp(Command, "ppm-noinput") == 0) | |
| { | |
| if (sscanf(Request, "%*s %llu %llu", &A, &B) != 2) | |
| { | |
| Logf("parse ppm-noinput failed; expected: ppm-noinput <type> <selector>"); | |
| return; | |
| } | |
| CallNoInputMethod("ApplePassthroughPPM", (uint32_t)A, (uint32_t)B); | |
| return; | |
| } | |
| if (strcmp(Command, "ppm-noinput-scan") == 0) | |
| { | |
| if (sscanf(Request, "%*s %llu %llu", &A, &B) != 2) | |
| { | |
| Logf("parse ppm-noinput-scan failed; expected: ppm-noinput-scan <type> <max-selector>"); | |
| return; | |
| } | |
| ScanNoInputMethods("ApplePassthroughPPM", (uint32_t)A, (uint32_t)B); | |
| return; | |
| } | |
| if (strcmp(Command, "ppm-noinput-scan-all") == 0) | |
| { | |
| if (sscanf(Request, "%*s %llu %llu", &A, &B) != 2) | |
| { | |
| Logf("parse ppm-noinput-scan-all failed; expected: ppm-noinput-scan-all <max-type> <max-selector>"); | |
| return; | |
| } | |
| ScanPPMNoInputAllTypes((uint32_t)A, (uint32_t)B); | |
| return; | |
| } | |
| if (strcmp(Command, "service-noinput") == 0) | |
| { | |
| char ServiceName[64] = {0}; | |
| if (sscanf(Request, "%*s %63s %llu %llu", ServiceName, &A, &B) != 3) | |
| { | |
| Logf("parse service-noinput failed; expected: service-noinput <service> <type> <selector>"); | |
| return; | |
| } | |
| CallNoInputMethod(ServiceName, (uint32_t)A, (uint32_t)B); | |
| return; | |
| } | |
| if (strcmp(Command, "service-noinput-scan") == 0) | |
| { | |
| char ServiceName[64] = {0}; | |
| if (sscanf(Request, "%*s %63s %llu %llu", ServiceName, &A, &B) != 3) | |
| { | |
| Logf("parse service-noinput-scan failed; expected: service-noinput-scan <service> <type> <max-selector>"); | |
| return; | |
| } | |
| ScanNoInputMethods(ServiceName, (uint32_t)A, (uint32_t)B); | |
| return; | |
| } | |
| if (strcmp(Command, "agx-perf-assert-cycle") == 0) | |
| { | |
| char ServiceName[64] = {0}; | |
| unsigned long long Type = 0; | |
| unsigned long long Level = 0; | |
| unsigned long long HoldSeconds = 0; | |
| if (sscanf(Request, "%*s %63s %llu %llu %llu", ServiceName, &Type, &Level, &HoldSeconds) != 4) | |
| { | |
| Logf("parse agx-perf-assert-cycle failed; expected: agx-perf-assert-cycle <service> <type> <level> <hold-seconds>"); | |
| return; | |
| } | |
| CallAGXPerfAssertCycle(ServiceName, (uint32_t)Type, (uint32_t)Level, (uint32_t)HoldSeconds); | |
| return; | |
| } | |
| if (strcmp(Command, "agx-perf-assert-hold") == 0) | |
| { | |
| char ServiceName[64] = {0}; | |
| unsigned long long Type = 0; | |
| unsigned long long Level = 0; | |
| unsigned long long HoldSeconds = 0; | |
| if (sscanf(Request, "%*s %63s %llu %llu %llu", ServiceName, &Type, &Level, &HoldSeconds) != 4) | |
| { | |
| Logf("parse agx-perf-assert-hold failed; expected: agx-perf-assert-hold <service> <type> <level> <hold-seconds>"); | |
| return; | |
| } | |
| CallAGXPerfAssertHold(ServiceName, (uint32_t)Type, (uint32_t)Level, (uint32_t)HoldSeconds); | |
| return; | |
| } | |
| if (strcmp(Command, "ppm-scalars") == 0 || strcmp(Command, "service-scalars") == 0) | |
| { | |
| char ParseBuffer[256]; | |
| strncpy(ParseBuffer, Request, sizeof(ParseBuffer) - 1); | |
| ParseBuffer[sizeof(ParseBuffer) - 1] = '\0'; | |
| char* Tokens[24] = {0}; | |
| size_t Count = Tokenize(ParseBuffer, Tokens, sizeof(Tokens) / sizeof(Tokens[0])); | |
| bool HasService = strcmp(Command, "service-scalars") == 0; | |
| size_t Base = HasService ? 6 : 5; | |
| if (Count < Base) | |
| { | |
| Logf("parse %s failed; expected: %s", | |
| Command, | |
| HasService ? "service-scalars <service> <type> <selector> <out-scalars> <out-struct-bytes> [scalar...]" | |
| : "ppm-scalars <type> <selector> <out-scalars> <out-struct-bytes> [scalar...]"); | |
| return; | |
| } | |
| const char* ServiceName = HasService ? Tokens[1] : "ApplePassthroughPPM"; | |
| size_t Arg = HasService ? 2 : 1; | |
| uint32_t OpenType = (uint32_t)ParseU64(Tokens[Arg++]); | |
| uint32_t Selector = (uint32_t)ParseU64(Tokens[Arg++]); | |
| uint32_t OutScalars = (uint32_t)ParseU64(Tokens[Arg++]); | |
| size_t OutStructBytes = (size_t)ParseU64(Tokens[Arg++]); | |
| uint64_t InputScalars[MAX_SCALARS] = {0}; | |
| uint32_t InputScalarCount = 0; | |
| while (Arg < Count && InputScalarCount < MAX_SCALARS) | |
| { | |
| InputScalars[InputScalarCount++] = ParseU64(Tokens[Arg++]); | |
| } | |
| CallGenericMethod(ServiceName, OpenType, Selector, InputScalars, InputScalarCount, NULL, 0, OutScalars, OutStructBytes); | |
| return; | |
| } | |
| if (strcmp(Command, "ppm-method-zero") == 0 || strcmp(Command, "service-method-zero") == 0) | |
| { | |
| char ParseBuffer[256]; | |
| strncpy(ParseBuffer, Request, sizeof(ParseBuffer) - 1); | |
| ParseBuffer[sizeof(ParseBuffer) - 1] = '\0'; | |
| char* Tokens[28] = {0}; | |
| size_t Count = Tokenize(ParseBuffer, Tokens, sizeof(Tokens) / sizeof(Tokens[0])); | |
| bool HasService = strcmp(Command, "service-method-zero") == 0; | |
| size_t Minimum = HasService ? 8 : 7; | |
| if (Count < Minimum) | |
| { | |
| Logf("parse %s failed; expected: %s", | |
| Command, | |
| HasService ? "service-method-zero <service> <type> <selector> <in-struct> <out-scalars> <out-struct> <fill-byte> [scalar...]" | |
| : "ppm-method-zero <type> <selector> <in-struct> <out-scalars> <out-struct> <fill-byte> [scalar...]"); | |
| return; | |
| } | |
| const char* ServiceName = HasService ? Tokens[1] : "ApplePassthroughPPM"; | |
| size_t Arg = HasService ? 2 : 1; | |
| uint32_t OpenType = (uint32_t)ParseU64(Tokens[Arg++]); | |
| uint32_t Selector = (uint32_t)ParseU64(Tokens[Arg++]); | |
| size_t InputBytes = (size_t)ParseU64(Tokens[Arg++]); | |
| uint32_t OutScalars = (uint32_t)ParseU64(Tokens[Arg++]); | |
| size_t OutStructBytes = (size_t)ParseU64(Tokens[Arg++]); | |
| unsigned char FillByte = (unsigned char)ParseU64(Tokens[Arg++]); | |
| uint64_t InputScalars[MAX_SCALARS] = {0}; | |
| uint32_t InputScalarCount = 0; | |
| while (Arg < Count && InputScalarCount < MAX_SCALARS) | |
| { | |
| InputScalars[InputScalarCount++] = ParseU64(Tokens[Arg++]); | |
| } | |
| CallGenericZeroStructMethod(ServiceName, | |
| OpenType, | |
| Selector, | |
| InputScalars, | |
| InputScalarCount, | |
| InputBytes, | |
| OutScalars, | |
| OutStructBytes, | |
| FillByte); | |
| return; | |
| } | |
| if (strcmp(Command, "ppm-method-hex") == 0 || strcmp(Command, "service-method-hex") == 0) | |
| { | |
| char ParseBuffer[256]; | |
| strncpy(ParseBuffer, Request, sizeof(ParseBuffer) - 1); | |
| ParseBuffer[sizeof(ParseBuffer) - 1] = '\0'; | |
| char* Tokens[16] = {0}; | |
| size_t Count = Tokenize(ParseBuffer, Tokens, sizeof(Tokens) / sizeof(Tokens[0])); | |
| bool HasService = strcmp(Command, "service-method-hex") == 0; | |
| size_t Minimum = HasService ? 7 : 6; | |
| if (Count < Minimum) | |
| { | |
| Logf("parse %s failed; expected: %s", | |
| Command, | |
| HasService ? "service-method-hex <service> <type> <selector> <out-scalars> <out-struct> <hex-struct>" | |
| : "ppm-method-hex <type> <selector> <out-scalars> <out-struct> <hex-struct>"); | |
| return; | |
| } | |
| const char* ServiceName = HasService ? Tokens[1] : "ApplePassthroughPPM"; | |
| size_t Arg = HasService ? 2 : 1; | |
| uint32_t OpenType = (uint32_t)ParseU64(Tokens[Arg++]); | |
| uint32_t Selector = (uint32_t)ParseU64(Tokens[Arg++]); | |
| uint32_t OutScalars = (uint32_t)ParseU64(Tokens[Arg++]); | |
| size_t OutStructBytes = (size_t)ParseU64(Tokens[Arg++]); | |
| unsigned char InputStruct[MAX_STRUCT_BYTES] = {0}; | |
| size_t InputStructSize = 0; | |
| if (!ParseHexBytes(Tokens[Arg], InputStruct, sizeof(InputStruct), &InputStructSize)) | |
| { | |
| Logf("parse %s failed; invalid hex struct", Command); | |
| return; | |
| } | |
| CallGenericMethod(ServiceName, | |
| OpenType, | |
| Selector, | |
| NULL, | |
| 0, | |
| InputStructSize == 0 ? NULL : InputStruct, | |
| InputStructSize, | |
| OutScalars, | |
| OutStructBytes); | |
| if (InputStructSize > 0) | |
| { | |
| LogStructBytes("call-method-input-struct", Selector, InputStruct, InputStructSize); | |
| } | |
| return; | |
| } | |
| if (strcmp(Command, "ppm-method-scan-zero") == 0 || strcmp(Command, "service-method-scan-zero") == 0) | |
| { | |
| char ParseBuffer[256]; | |
| strncpy(ParseBuffer, Request, sizeof(ParseBuffer) - 1); | |
| ParseBuffer[sizeof(ParseBuffer) - 1] = '\0'; | |
| char* Tokens[28] = {0}; | |
| size_t Count = Tokenize(ParseBuffer, Tokens, sizeof(Tokens) / sizeof(Tokens[0])); | |
| bool HasService = strcmp(Command, "service-method-scan-zero") == 0; | |
| size_t Minimum = HasService ? 9 : 8; | |
| if (Count < Minimum) | |
| { | |
| Logf("parse %s failed; expected: %s", | |
| Command, | |
| HasService ? "service-method-scan-zero <service> <type> <start-selector> <end-selector> <in-struct> <out-scalars> <out-struct> <fill-byte> [scalar...]" | |
| : "ppm-method-scan-zero <type> <start-selector> <end-selector> <in-struct> <out-scalars> <out-struct> <fill-byte> [scalar...]"); | |
| return; | |
| } | |
| const char* ServiceName = HasService ? Tokens[1] : "ApplePassthroughPPM"; | |
| size_t Arg = HasService ? 2 : 1; | |
| uint32_t OpenType = (uint32_t)ParseU64(Tokens[Arg++]); | |
| uint32_t StartSelector = (uint32_t)ParseU64(Tokens[Arg++]); | |
| uint32_t EndSelector = (uint32_t)ParseU64(Tokens[Arg++]); | |
| size_t InputBytes = (size_t)ParseU64(Tokens[Arg++]); | |
| uint32_t OutScalars = (uint32_t)ParseU64(Tokens[Arg++]); | |
| size_t OutStructBytes = (size_t)ParseU64(Tokens[Arg++]); | |
| unsigned char FillByte = (unsigned char)ParseU64(Tokens[Arg++]); | |
| uint64_t InputScalars[MAX_SCALARS] = {0}; | |
| uint32_t InputScalarCount = 0; | |
| while (Arg < Count && InputScalarCount < MAX_SCALARS) | |
| { | |
| InputScalars[InputScalarCount++] = ParseU64(Tokens[Arg++]); | |
| } | |
| ScanGenericZeroStructMethods(ServiceName, | |
| OpenType, | |
| StartSelector, | |
| EndSelector, | |
| InputScalars, | |
| InputScalarCount, | |
| InputBytes, | |
| OutScalars, | |
| OutStructBytes, | |
| FillByte); | |
| return; | |
| } | |
| if (strcmp(Command, "ppm-struct-zero") == 0 || strcmp(Command, "service-struct-zero") == 0) | |
| { | |
| char ParseBuffer[256]; | |
| strncpy(ParseBuffer, Request, sizeof(ParseBuffer) - 1); | |
| ParseBuffer[sizeof(ParseBuffer) - 1] = '\0'; | |
| char* Tokens[16] = {0}; | |
| size_t Count = Tokenize(ParseBuffer, Tokens, sizeof(Tokens) / sizeof(Tokens[0])); | |
| bool HasService = strcmp(Command, "service-struct-zero") == 0; | |
| size_t Minimum = HasService ? 6 : 5; | |
| if (Count < Minimum) | |
| { | |
| Logf("parse %s failed; expected: %s", | |
| Command, | |
| HasService ? "service-struct-zero <service> <type> <selector> <in-bytes> <out-bytes> [fill-byte]" | |
| : "ppm-struct-zero <type> <selector> <in-bytes> <out-bytes> [fill-byte]"); | |
| return; | |
| } | |
| const char* ServiceName = HasService ? Tokens[1] : "ApplePassthroughPPM"; | |
| size_t Arg = HasService ? 2 : 1; | |
| uint32_t OpenType = (uint32_t)ParseU64(Tokens[Arg++]); | |
| uint32_t Selector = (uint32_t)ParseU64(Tokens[Arg++]); | |
| size_t InputBytes = (size_t)ParseU64(Tokens[Arg++]); | |
| size_t OutputBytes = (size_t)ParseU64(Tokens[Arg++]); | |
| unsigned char FillByte = Arg < Count ? (unsigned char)ParseU64(Tokens[Arg]) : 0; | |
| CallStructZeroMethod(ServiceName, OpenType, Selector, InputBytes, OutputBytes, FillByte); | |
| return; | |
| } | |
| if (strcmp(Command, "ppm-struct-scan-zero") == 0 || strcmp(Command, "service-struct-scan-zero") == 0) | |
| { | |
| char ParseBuffer[256]; | |
| strncpy(ParseBuffer, Request, sizeof(ParseBuffer) - 1); | |
| ParseBuffer[sizeof(ParseBuffer) - 1] = '\0'; | |
| char* Tokens[16] = {0}; | |
| size_t Count = Tokenize(ParseBuffer, Tokens, sizeof(Tokens) / sizeof(Tokens[0])); | |
| bool HasService = strcmp(Command, "service-struct-scan-zero") == 0; | |
| size_t Minimum = HasService ? 7 : 6; | |
| if (Count < Minimum) | |
| { | |
| Logf("parse %s failed; expected: %s", | |
| Command, | |
| HasService ? "service-struct-scan-zero <service> <type> <start-selector> <end-selector> <in-bytes> <out-bytes> [fill-byte]" | |
| : "ppm-struct-scan-zero <type> <start-selector> <end-selector> <in-bytes> <out-bytes> [fill-byte]"); | |
| return; | |
| } | |
| const char* ServiceName = HasService ? Tokens[1] : "ApplePassthroughPPM"; | |
| size_t Arg = HasService ? 2 : 1; | |
| uint32_t OpenType = (uint32_t)ParseU64(Tokens[Arg++]); | |
| uint32_t StartSelector = (uint32_t)ParseU64(Tokens[Arg++]); | |
| uint32_t EndSelector = (uint32_t)ParseU64(Tokens[Arg++]); | |
| size_t InputBytes = (size_t)ParseU64(Tokens[Arg++]); | |
| size_t OutputBytes = (size_t)ParseU64(Tokens[Arg++]); | |
| unsigned char FillByte = Arg < Count ? (unsigned char)ParseU64(Tokens[Arg]) : 0; | |
| ScanStructZeroMethods(ServiceName, OpenType, StartSelector, EndSelector, InputBytes, OutputBytes, FillByte); | |
| return; | |
| } | |
| if (strcmp(Command, "ppm-scalar-shape-scan") == 0 || strcmp(Command, "service-scalar-shape-scan") == 0) | |
| { | |
| char ParseBuffer[256]; | |
| strncpy(ParseBuffer, Request, sizeof(ParseBuffer) - 1); | |
| ParseBuffer[sizeof(ParseBuffer) - 1] = '\0'; | |
| char* Tokens[16] = {0}; | |
| size_t Count = Tokenize(ParseBuffer, Tokens, sizeof(Tokens) / sizeof(Tokens[0])); | |
| bool HasService = strcmp(Command, "service-scalar-shape-scan") == 0; | |
| size_t Minimum = HasService ? 8 : 7; | |
| if (Count < Minimum) | |
| { | |
| Logf("parse %s failed; expected: %s", | |
| Command, | |
| HasService ? "service-scalar-shape-scan <service> <type> <start-selector> <end-selector> <max-in-scalars> <max-out-scalars> <out-struct-bytes> [fill]" | |
| : "ppm-scalar-shape-scan <type> <start-selector> <end-selector> <max-in-scalars> <max-out-scalars> <out-struct-bytes> [fill]"); | |
| return; | |
| } | |
| const char* ServiceName = HasService ? Tokens[1] : "ApplePassthroughPPM"; | |
| size_t Arg = HasService ? 2 : 1; | |
| uint32_t OpenType = (uint32_t)ParseU64(Tokens[Arg++]); | |
| uint32_t StartSelector = (uint32_t)ParseU64(Tokens[Arg++]); | |
| uint32_t EndSelector = (uint32_t)ParseU64(Tokens[Arg++]); | |
| uint32_t MaxInputScalars = (uint32_t)ParseU64(Tokens[Arg++]); | |
| uint32_t MaxOutputScalars = (uint32_t)ParseU64(Tokens[Arg++]); | |
| size_t OutputBytes = (size_t)ParseU64(Tokens[Arg++]); | |
| uint64_t FillValue = Arg < Count ? ParseU64(Tokens[Arg]) : 0; | |
| ScanScalarShapes(ServiceName, | |
| OpenType, | |
| StartSelector, | |
| EndSelector, | |
| MaxInputScalars, | |
| MaxOutputScalars, | |
| OutputBytes, | |
| FillValue); | |
| return; | |
| } | |
| if (strcmp(Command, "clear") == 0) | |
| { | |
| Logf("clear requested: no persistent direct state to clear"); | |
| return; | |
| } | |
| Logf("unknown command=\"%s\"", Command); | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment