master ohmu/fastfetch-openbsd / src / detection / gpu / gpu_apple.c
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}