ohmu
·
2026-10-15
1#include "gpu.h"
2#include "common/apple/cf_helpers.h"
3#include "common/apple/smc_temps.h"
4#include "common/debug.h"
5
6#include <IOKit/graphics/IOGraphicsLib.h>
7
8const char* ffGpuDetectMetal(FFlist* gpus);
9const char* ffGpuDetectDriverVersion(FFlist* gpus);
10
11static double detectGpuTemp(const FFstrbuf* gpuName) {
12 double result = 0;
13 const char* error = nullptr;
14
15 if (ffStrbufStartsWithS(gpuName, "Apple M")) {
16 switch (strtol(gpuName->chars + strlen("Apple M"), nullptr, 10)) {
17 case 0:
18 error = "Invalid Apple Silicon GPU";
19 break;
20 case 1:
21 error = ffDetectSmcTemps(FF_TEMP_GPU_M1X, &result);
22 break;
23 case 2:
24 error = ffDetectSmcTemps(FF_TEMP_GPU_M2X, &result);
25 break;
26 case 3:
27 error = ffDetectSmcTemps(FF_TEMP_GPU_M3X, &result);
28 break;
29 case 4:
30 error = ffDetectSmcTemps(FF_TEMP_GPU_M4X, &result);
31 break;
32 case 5:
33 error = ffDetectSmcTemps(FF_TEMP_GPU_M5X, &result);
34 break;
35 default:
36 error = "Unsupported Apple Silicon GPU";
37 break;
38 }
39 } else if (ffStrbufStartsWithS(gpuName, "Intel")) {
40 error = ffDetectSmcTemps(FF_TEMP_GPU_INTEL, &result);
41 } else if (ffStrbufStartsWithS(gpuName, "Radeon") || ffStrbufStartsWithS(gpuName, "AMD")) {
42 error = ffDetectSmcTemps(FF_TEMP_GPU_AMD, &result);
43 } else {
44 error = ffDetectSmcTemps(FF_TEMP_GPU_UNKNOWN, &result);
45 }
46
47 if (error) {
48 return FF_GPU_TEMP_UNSET;
49 }
50
51 return result;
52}
53
54#ifdef __aarch64__
55 #include <IOKit/IOKitLib.h>
56
57static const char* detectFrequency(FFGPUResult* gpu) {
58 // https://github.com/giampaolo/psutil/pull/2222/files
59
60 FF_IOOBJECT_AUTO_RELEASE io_registry_entry_t entryDevice = IOServiceGetMatchingService(MACH_PORT_NULL, IOServiceNameMatching("pmgr"));
61 if (!entryDevice) {
62 FF_DEBUG("IOServiceGetMatchingServices() failed");
63 return "IOServiceGetMatchingServices() failed";
64 }
65
66 if (!IOObjectConformsTo(entryDevice, "AppleARMIODevice")) {
67 FF_DEBUG("\"pmgr\" should conform to \"AppleARMIODevice\"");
68 return "\"pmgr\" should conform to \"AppleARMIODevice\"";
69 }
70
71 FF_CFTYPE_AUTO_RELEASE CFDataRef freqProperty = (CFDataRef) IORegistryEntryCreateCFProperty(entryDevice, CFSTR("voltage-states9-sram"), kCFAllocatorDefault, kNilOptions);
72 if (!freqProperty || CFGetTypeID(freqProperty) != CFDataGetTypeID()) {
73 FF_DEBUG("\"voltage-states9-sram\" in \"pmgr\" is not found");
74 return "\"voltage-states9-sram\" in \"pmgr\" is not found";
75 }
76
77 // voltage-states9-sram stores supported <frequency / voltage> pairs of gpu from the lowest to the highest
78 CFIndex propLength = CFDataGetLength(freqProperty);
79 if (propLength == 0 || propLength % (CFIndex) (sizeof(uint32_t) * 2) != 0) {
80 FF_DEBUG("Invalid \"voltage-states9-sram\" length");
81 return "Invalid \"voltage-states9-sram\" length";
82 }
83
84 uint32_t* pStart = (uint32_t*) CFDataGetBytePtr(freqProperty);
85 uint32_t pMax = *pStart;
86 for (CFIndex i = 2; i < propLength / (CFIndex) sizeof(uint32_t) && pStart[i] > 0; i += 2 /* skip voltage */) {
87 pMax = pMax > pStart[i] ? pMax : pStart[i];
88 }
89
90 if (pMax > 0) {
91 // While this is not necessary for now (seems), we add this logic just in case. See cpu_apple.c
92 if (pMax > 100000000) { // Assume that pMax is in Hz
93 gpu->frequency = pMax / 1000 / 1000;
94 } else { // Assume that pMax is in kHz
95 gpu->frequency = pMax / 1000;
96 }
97 }
98
99 return nullptr;
100}
101#endif
102
103const char* ffDetectGPUImpl(const FFGPUOptions* options, FFlist* gpus) {
104 FF_IOOBJECT_AUTO_RELEASE io_iterator_t iterator = IO_OBJECT_NULL;
105 {
106 CFMutableDictionaryRef matches = IOServiceMatching(kIOAcceleratorClassName);
107 CFDictionaryAddValue(matches, CFSTR("IOMatchCategory"), CFSTR(kIOAcceleratorClassName));
108 if (IOServiceGetMatchingServices(MACH_PORT_NULL, matches, &iterator) != kIOReturnSuccess) {
109 FF_DEBUG("IOServiceGetMatchingServices() failed");
110 return "IOServiceGetMatchingServices() failed";
111 }
112 }
113
114 io_registry_entry_t registryEntry;
115 while ((registryEntry = IOIteratorNext(iterator)) != IO_OBJECT_NULL) {
116 CFMutableDictionaryRef properties;
117 if (IORegistryEntryCreateCFProperties(registryEntry, &properties, kCFAllocatorDefault, kNilOptions) != kIOReturnSuccess) {
118 IOObjectRelease(registryEntry);
119 continue;
120 }
121
122 FFGPUResult* gpu = FF_LIST_ADD(FFGPUResult, *gpus);
123 gpu->index = FF_GPU_INDEX_UNSET;
124 ffStrbufInit(&gpu->memoryType);
125 gpu->dedicated.total = gpu->dedicated.used = gpu->shared.total = gpu->shared.used = FF_GPU_VMEM_SIZE_UNSET;
126 gpu->type = FF_GPU_TYPE_UNKNOWN;
127 gpu->frequency = FF_GPU_FREQUENCY_UNSET;
128 gpu->pcieSpeed = FF_GPU_PCIE_SPEED_UNSET;
129 IORegistryEntryGetRegistryEntryID(registryEntry, &gpu->deviceId);
130 ffStrbufInitStatic(&gpu->platformApi, "IOKit");
131
132 ffStrbufInit(&gpu->driver); // Ok for both Apple and Intel
133 ffCfDictGetString(properties, CFSTR("CFBundleIdentifier"), &gpu->driver);
134
135 if (ffCfDictGetInt(properties, CFSTR("gpu-core-count"), &gpu->coreCount) != nullptr) { // For Apple
136 gpu->coreCount = FF_GPU_CORE_COUNT_UNSET;
137 }
138
139 gpu->coreUsage = FF_GPU_CORE_USAGE_UNSET;
140 CFDictionaryRef perfStatistics = nullptr;
141 uint64_t vramUsed = 0, vramTotal = 0;
142 if (ffCfDictGetDict(properties, CFSTR("PerformanceStatistics"), &perfStatistics) == nullptr) {
143 int64_t utilization;
144 if (ffCfDictGetInt64(perfStatistics, CFSTR("Device Utilization %"), &utilization) == nullptr) {
145 gpu->coreUsage = (double) utilization;
146 } else if (ffCfDictGetInt64(perfStatistics, CFSTR("GPU Core Utilization"), &utilization) == nullptr) {
147 gpu->coreUsage = (double) utilization / 10000000.; // Nvidia?
148 }
149
150 if (ffCfDictGetInt64(perfStatistics, CFSTR("Alloc system memory"), (int64_t*) &vramTotal) == nullptr) {
151 if (ffCfDictGetInt64(perfStatistics, CFSTR("In use system memory"), (int64_t*) &vramUsed) != nullptr) {
152 vramTotal = 0;
153 }
154 } else if (ffCfDictGetInt64(perfStatistics, CFSTR("vramFreeBytes"), (int64_t*) &vramTotal) == nullptr) {
155 if (ffCfDictGetInt64(perfStatistics, CFSTR("vramUsedBytes"), (int64_t*) &vramUsed) == nullptr) {
156 vramTotal += vramUsed;
157 } else {
158 vramTotal = 0;
159 }
160 }
161 }
162
163 ffStrbufInit(&gpu->name);
164 // IOAccelerator returns model / vendor-id properties for Apple Silicon, but not for Intel Iris GPUs.
165 // Still needs testing for AMD's
166 if (ffCfDictGetString(properties, CFSTR("model"), &gpu->name) != nullptr) {
167 CFRelease(properties);
168 properties = nullptr;
169
170 FF_IOOBJECT_AUTO_RELEASE io_registry_entry_t parentEntry = 0;
171 if (IORegistryEntryGetParentEntry(registryEntry, kIOServicePlane, &parentEntry) != kIOReturnSuccess ||
172 IORegistryEntryCreateCFProperties(parentEntry, &properties, kCFAllocatorDefault, kNilOptions) != kIOReturnSuccess) {
173 IOObjectRelease(registryEntry);
174 continue;
175 }
176 ffCfDictGetString(properties, CFSTR("model"), &gpu->name);
177 }
178
179 ffStrbufInit(&gpu->vendor);
180 int vendorId;
181 if (ffCfDictGetInt(properties, CFSTR("vendor-id"), &vendorId) == nullptr) {
182 const char* vendorStr = ffGPUGetVendorString((unsigned) vendorId);
183 ffStrbufAppendS(&gpu->vendor, vendorStr);
184 if (vendorStr == FF_GPU_VENDOR_NAME_APPLE || vendorStr == FF_GPU_VENDOR_NAME_INTEL) {
185 gpu->type = FF_GPU_TYPE_INTEGRATED;
186 } else if (vendorStr == FF_GPU_VENDOR_NAME_NVIDIA || vendorStr == FF_GPU_VENDOR_NAME_AMD) {
187 gpu->type = FF_GPU_TYPE_DISCRETE;
188 }
189
190#ifdef __aarch64__
191 if (vendorStr == FF_GPU_VENDOR_NAME_APPLE) {
192 detectFrequency(gpu);
193 }
194#endif
195
196 if (gpu->type == FF_GPU_TYPE_INTEGRATED) {
197 gpu->shared.total = vramTotal;
198 gpu->shared.used = vramUsed;
199 } else if (gpu->type == FF_GPU_TYPE_DISCRETE) {
200 gpu->dedicated.total = vramTotal;
201 gpu->dedicated.used = vramUsed;
202 }
203 }
204
205 gpu->temperature = options->temp ? detectGpuTemp(&gpu->name) : FF_GPU_TEMP_UNSET;
206
207 CFRelease(properties);
208 IOObjectRelease(registryEntry);
209 }
210
211 ffGpuDetectMetal(gpus);
212 if (instance.config.general.detectVersion) {
213 ffGpuDetectDriverVersion(gpus);
214 }
215 return nullptr;
216}