master ohmu/fastfetch-openbsd / src / common / apple / smc_temps.c
ohmu  ·  2026-10-15
  1#include "smc_temps.h"
  2#include "common/apple/cf_helpers.h"
  3#include "common/debug.h"
  4#include "common/strutil.h"
  5
  6#include <stdint.h>
  7#include <IOKit/IOKitLib.h>
  8
  9static const char kSmcCmdReadBytes = 5;
 10static const char kSmcCmdReadKeyInfo = 9;
 11static const uint32_t kKernelIndexSmc = 2;
 12
 13typedef struct
 14{
 15    char major;
 16    char minor;
 17    char build;
 18    char reserved[1];
 19    uint16_t release;
 20} SmcKeyData_vers_t;
 21
 22typedef struct
 23{
 24    uint16_t version;
 25    uint16_t length;
 26    uint32_t cpuPLimit;
 27    uint32_t gpuPLimit;
 28    uint32_t memPLimit;
 29} SmcKeyData_pLimitData_t;
 30
 31typedef struct
 32{
 33    uint32_t dataSize;
 34    uint32_t dataType;
 35    char dataAttributes;
 36} SmcKeyData_keyInfo_t;
 37
 38typedef unsigned char SmcBytes_t[32];
 39
 40typedef struct
 41{
 42    uint32_t key;
 43    SmcKeyData_vers_t vers;
 44    SmcKeyData_pLimitData_t pLimitData;
 45    SmcKeyData_keyInfo_t keyInfo;
 46    char result;
 47    char status;
 48    char data8;
 49    uint32_t data32;
 50    SmcBytes_t bytes;
 51} SmcKeyData_t;
 52
 53typedef char UInt32Char_t[5];
 54
 55typedef struct
 56{
 57    UInt32Char_t key;
 58    uint32_t dataSize;
 59    UInt32Char_t dataType;
 60    SmcBytes_t bytes;
 61} SmcVal_t;
 62
 63static uint32_t smcStrtoul(const char* str, int size, int base) {
 64    uint32_t total = 0;
 65
 66    for (int i = 0; i < size; i++) {
 67        if (base == 16) {
 68            total += (uint32_t) (str[i] << (size - 1 - i) * 8);
 69        } else {
 70            total += (uint32_t) ((unsigned char) (str[i]) << (size - 1 - i) * 8);
 71        }
 72    }
 73    return total;
 74}
 75
 76static void smcUltostr(char* str, uint32_t val) {
 77    str[0] = (char) (val >> 24);
 78    str[1] = (char) (val >> 16);
 79    str[2] = (char) (val >> 8);
 80    str[3] = (char) val;
 81    str[4] = '\0';
 82}
 83
 84static const char* smcCall(io_connect_t conn, uint32_t selector, SmcKeyData_t* inputStructure, SmcKeyData_t* outputStructure) {
 85    size_t size = sizeof(SmcKeyData_t);
 86
 87    const IOReturn callStatus = IOConnectCallStructMethod(conn, selector, inputStructure, size, outputStructure, &size);
 88    if (callStatus != kIOReturnSuccess) {
 89        FF_DEBUG("IOConnectCallStructMethod(selector=%u, command=%u) failed: IOReturn 0x%08x",
 90            selector, (uint32_t) (unsigned char) inputStructure->data8, (unsigned) callStatus);
 91        return "IOConnectCallStructMethod(conn) failed";
 92    }
 93    return nullptr;
 94}
 95
 96// Provides key info, using a cache to dramatically improve the energy impact of smcFanControl
 97static const char* smcGetKeyInfo(io_connect_t conn, const uint32_t key, SmcKeyData_keyInfo_t* key_info) {
 98    SmcKeyData_t inputStructure = { 0 };
 99    SmcKeyData_t outputStructure = { 0 };
100
101    inputStructure.key = key;
102    inputStructure.data8 = kSmcCmdReadKeyInfo;
103
104    const char* error = smcCall(conn, kKernelIndexSmc, &inputStructure, &outputStructure);
105    if (error) {
106        return error;
107    }
108
109    *key_info = outputStructure.keyInfo;
110    return nullptr;
111}
112
113static const char* smcReadSmcVal(io_connect_t conn, const UInt32Char_t key, SmcVal_t* val) {
114    SmcKeyData_t inputStructure = { 0 };
115    SmcKeyData_t outputStructure = { 0 };
116
117    inputStructure.key = smcStrtoul(key, 4, 16);
118    strcpy(val->key, key);
119
120    const char* error = smcGetKeyInfo(conn, inputStructure.key, &outputStructure.keyInfo);
121    if (error) {
122        return error;
123    }
124
125    val->dataSize = outputStructure.keyInfo.dataSize;
126    smcUltostr(val->dataType, outputStructure.keyInfo.dataType);
127    inputStructure.keyInfo.dataSize = val->dataSize;
128    inputStructure.data8 = kSmcCmdReadBytes;
129
130    error = smcCall(conn, kKernelIndexSmc, &inputStructure, &outputStructure);
131    if (error) {
132        return error;
133    }
134
135    memcpy(val->bytes, outputStructure.bytes, sizeof(outputStructure.bytes));
136
137    return nullptr;
138}
139
140static const char* smcOpen(io_connect_t* conn) {
141    FF_IOOBJECT_AUTO_RELEASE io_object_t device = IOServiceGetMatchingService(MACH_PORT_NULL, IOServiceMatching("AppleSMC"));
142    if (!device) {
143        FF_DEBUG("IOKit has no AppleSMC service");
144        return "No SMC device found";
145    }
146
147    const IOReturn openStatus = IOServiceOpen(device, mach_task_self(), 0, conn);
148    if (openStatus != kIOReturnSuccess) {
149        FF_DEBUG("IOServiceOpen(AppleSMC) failed: IOReturn 0x%08x", (unsigned) openStatus);
150        return "IOServiceOpen() failed";
151    }
152
153    return nullptr;
154}
155
156static const char* smcReadValue(io_connect_t conn, const UInt32Char_t key, double* value) {
157    SmcVal_t val = { 0 };
158    const char* error = smcReadSmcVal(conn, key, &val);
159    if (error != nullptr) {
160        return error;
161    }
162    if (val.dataSize == 0) {
163        FF_DEBUG("The SMC key %s reported a data size of 0", key);
164        return "Empty SMC result";
165    }
166
167    switch (val.dataType[0]) {
168        case 'u': // unsigned integer types
169            if (val.dataType[1] == 'i') {
170                switch (val.dataSize) {
171                    case 1:
172                        *value = *(uint8_t*) (val.bytes);
173                        break;
174                    case 2:
175                        *value = ntohs(*(uint16_t*) (val.bytes));
176                        break;
177                    case 4:
178                        *value = ntohl(*(uint32_t*) (val.bytes));
179                        break;
180                    case 8:
181                        *value = (double) ntohll(*(uint64_t*) (val.bytes));
182                        break;
183                    default:
184                        FF_DEBUG("SMC key %s: unsigned type %s has an unsupported size of %u bytes",
185                            key, val.dataType, val.dataSize);
186                        return "Unsupported SMC unsigned integer data size";
187                }
188            } else {
189                FF_DEBUG("SMC key %s: unsupported unsigned type %s", key, val.dataType);
190                return "Unsupported SMC unsigned data type";
191            }
192            break;
193
194        case 'f': // floating point types
195            if (ffStrEquals(val.dataType, "flt ") && val.dataSize == 4) {
196                *value = *(float*) (val.bytes);
197            } else if (val.dataType[1] == 'p' && val.dataSize == 2) // fixed point types
198            {
199                if (ffStrEquals(val.dataType, "fp1f")) {
200                    *value = ntohs(*(uint16_t*) (val.bytes)) / 32768.0;
201                } else if (ffStrEquals(val.dataType, "fp4c")) {
202                    *value = ntohs(*(uint16_t*) (val.bytes)) / 4096.0;
203                } else if (ffStrEquals(val.dataType, "fp5b")) {
204                    *value = ntohs(*(uint16_t*) (val.bytes)) / 2048.0;
205                } else if (ffStrEquals(val.dataType, "fp6a")) {
206                    *value = ntohs(*(uint16_t*) (val.bytes)) / 1024.0;
207                } else if (ffStrEquals(val.dataType, "fp79")) {
208                    *value = ntohs(*(uint16_t*) (val.bytes)) / 512.0;
209                } else if (ffStrEquals(val.dataType, "fp88")) {
210                    *value = ntohs(*(uint16_t*) (val.bytes)) / 256.0;
211                } else if (ffStrEquals(val.dataType, "fpa6")) {
212                    *value = ntohs(*(uint16_t*) (val.bytes)) / 64.0;
213                } else if (ffStrEquals(val.dataType, "fpc4")) {
214                    *value = ntohs(*(uint16_t*) (val.bytes)) / 16.0;
215                } else if (ffStrEquals(val.dataType, "fpe2")) {
216                    *value = ntohs(*(uint16_t*) (val.bytes)) / 4.0;
217                } else {
218                    FF_DEBUG("SMC key %s: unsupported fixed point type %s of %u bytes",
219                        key, val.dataType, val.dataSize);
220                    return "Unsupported SMC floating point data type";
221                }
222            } else {
223                FF_DEBUG("SMC key %s: unsupported floating point type %s of %u bytes",
224                    key, val.dataType, val.dataSize);
225                return "Unsupported SMC floating point data type";
226            }
227            break;
228
229        case 's': // signed integer types
230            if (val.dataType[1] == 'i') {
231                switch (val.dataSize) {
232                    case 1:
233                        *value = *(int8_t*) (val.bytes);
234                        break;
235                    case 2:
236                        *value = ntohs(*(int16_t*) (val.bytes));
237                        break;
238                    case 4:
239                        *value = ntohl(*(int32_t*) (val.bytes));
240                        break;
241                    case 8:
242                        *value = (double) ntohll(*(int64_t*) (val.bytes));
243                        break;
244                    default:
245                        FF_DEBUG("SMC key %s: signed type %s has an unsupported size of %u bytes",
246                            key, val.dataType, val.dataSize);
247                        return "Unsupported SMC signed integer data size";
248                }
249            } else if (val.dataType[1] == 'p' && val.dataSize == 2) // signed fixed point types
250            {
251                if (ffStrEquals(val.dataType, "sp1e")) {
252                    *value = (int16_t) ntohs(*(int16_t*) (val.bytes)) / 16384.0;
253                } else if (ffStrEquals(val.dataType, "sp3c")) {
254                    *value = (int16_t) ntohs(*(int16_t*) (val.bytes)) / 4096.0;
255                } else if (ffStrEquals(val.dataType, "sp4b")) {
256                    *value = (int16_t) ntohs(*(int16_t*) (val.bytes)) / 2048.0;
257                } else if (ffStrEquals(val.dataType, "sp5a")) {
258                    *value = (int16_t) ntohs(*(int16_t*) (val.bytes)) / 1024.0;
259                } else if (ffStrEquals(val.dataType, "sp69")) {
260                    *value = (int16_t) ntohs(*(int16_t*) (val.bytes)) / 512.0;
261                } else if (ffStrEquals(val.dataType, "sp78")) {
262                    *value = (int16_t) ntohs(*(int16_t*) (val.bytes)) / 256.0;
263                } else if (ffStrEquals(val.dataType, "sp87")) {
264                    *value = (int16_t) ntohs(*(int16_t*) (val.bytes)) / 128.0;
265                } else if (ffStrEquals(val.dataType, "sp96")) {
266                    *value = (int16_t) ntohs(*(int16_t*) (val.bytes)) / 64.0;
267                } else if (ffStrEquals(val.dataType, "spb4")) {
268                    *value = (int16_t) ntohs(*(int16_t*) (val.bytes)) / 16.0;
269                } else if (ffStrEquals(val.dataType, "spf0")) {
270                    *value = (int16_t) ntohs(*(int16_t*) (val.bytes)) / 1.0;
271                } else {
272                    FF_DEBUG("SMC key %s: unsupported signed fixed point type %s of %u bytes",
273                        key, val.dataType, val.dataSize);
274                    return "Unsupported SMC signed integer data type";
275                }
276            } else {
277                FF_DEBUG("SMC key %s: unsupported signed type %s", key, val.dataType);
278                return "Unsupported SMC signed data type";
279            }
280            break;
281
282        case '{': // special types like pwm
283            if (ffStrEquals(val.dataType, "{pwm") && val.dataSize == 2) {
284                *value = (double) ntohs(*(uint16_t*) (val.bytes)) * 100 / 65536.0;
285            } else {
286                FF_DEBUG("SMC key %s: unsupported special type %s of %u bytes", key, val.dataType, val.dataSize);
287                return "Unsupported SMC special data type";
288            }
289            break;
290
291        default:
292            FF_DEBUG("SMC key %s: unknown data type '%c%c' (%s) of %u bytes",
293                key, val.dataType[0] ? val.dataType[0] : '?', val.dataType[1] ? val.dataType[1] : '?',
294                val.dataType, val.dataSize);
295            return "Unsupported SMC data type";
296    }
297    return nullptr;
298}
299
300static bool detectTemp(io_connect_t conn, const char* sensor, double* sum) {
301    double temp = 0;
302    const char* error = smcReadValue(conn, sensor, &temp);
303    if (error) {
304        return false;
305    }
306    // https://github.com/exelban/stats/blob/14e29c4d60229c363cca9c9d25c30c87b7870830/Modules/Sensors/readers.swift#L124
307    if (temp < 10 || temp > 120) {
308        return false;
309    }
310    *sum += temp;
311    return true;
312}
313
314static io_connect_t conn;
315
316const char* ffDetectSmcSpecificTemp(const char* sensor, double* result) {
317    if (!conn) {
318        if (smcOpen(&conn) != nullptr) {
319            conn = (io_connect_t) -1;
320        }
321    }
322    if (conn == (io_connect_t) -1) {
323        return "Could not open SMC connection";
324    }
325
326    if (!detectTemp(conn, sensor, result)) {
327        FF_DEBUG("The SMC sensor %s could not be read, or read a value outside 10..120", sensor);
328        return "Could not read SMC temperature";
329    }
330
331    return nullptr;
332}
333
334const char* ffDetectSmcTemps(enum FFTempType type, double* result) {
335    if (!conn) {
336        if (smcOpen(&conn) != nullptr) {
337            conn = (io_connect_t) -1;
338        }
339    }
340    if (conn == (io_connect_t) -1) {
341        return "Could not open SMC connection";
342    }
343
344    uint32_t count = 0;
345    *result = 0;
346
347    // https://github.com/exelban/stats/blob/master/Modules/Sensors/values.swift
348    switch (type) {
349        case FF_TEMP_CPU_X64:
350            count += detectTemp(conn, "TC0D", result); // CPU diode
351            count += detectTemp(conn, "TC0E", result); // CPU diode virtual
352            count += detectTemp(conn, "TC0F", result); // CPU diode filtered
353            count += detectTemp(conn, "TC0P", result); // CPU proximity
354            break;
355
356        case FF_TEMP_CPU_M1X:
357            count += detectTemp(conn, "Tp09", result); // CPU efficient core 1
358            count += detectTemp(conn, "Tp0T", result); // CPU efficient core 2
359
360            count += detectTemp(conn, "Tp01", result); // CPU performance core 1
361            count += detectTemp(conn, "Tp05", result); // CPU performance core 2
362            count += detectTemp(conn, "Tp0D", result); // CPU performance core 3
363            count += detectTemp(conn, "Tp0H", result); // CPU performance core 4
364            count += detectTemp(conn, "Tp0L", result); // CPU performance core 5
365            count += detectTemp(conn, "Tp0P", result); // CPU performance core 6
366            count += detectTemp(conn, "Tp0X", result); // CPU performance core 7
367            count += detectTemp(conn, "Tp0b", result); // CPU performance core 8
368            break;
369
370        case FF_TEMP_CPU_M2X:
371            count += detectTemp(conn, "Tp1h", result); // CPU efficiency core 1
372            count += detectTemp(conn, "Tp1t", result); // CPU efficiency core 2
373            count += detectTemp(conn, "Tp1p", result); // CPU efficiency core 3
374            count += detectTemp(conn, "Tp1l", result); // CPU efficiency core 4
375
376            count += detectTemp(conn, "Tp01", result); // CPU performance core 1
377            count += detectTemp(conn, "Tp05", result); // CPU performance core 2
378            count += detectTemp(conn, "Tp09", result); // CPU performance core 3
379            count += detectTemp(conn, "Tp0D", result); // CPU performance core 4
380            count += detectTemp(conn, "Tp0X", result); // CPU performance core 5
381            count += detectTemp(conn, "Tp0b", result); // CPU performance core 6
382            count += detectTemp(conn, "Tp0f", result); // CPU performance core 7
383            count += detectTemp(conn, "Tp0j", result); // CPU performance core 8
384            break;
385
386        case FF_TEMP_CPU_M3X:
387            count += detectTemp(conn, "Te05", result); // CPU efficiency core 1
388            count += detectTemp(conn, "Te0L", result); // CPU efficiency core 2
389            count += detectTemp(conn, "Te0P", result); // CPU efficiency core 3
390            count += detectTemp(conn, "Te0S", result); // CPU efficiency core 4
391            count += detectTemp(conn, "Tf04", result); // CPU performance core 1
392            count += detectTemp(conn, "Tf09", result); // CPU performance core 2
393            count += detectTemp(conn, "Tf0A", result); // CPU performance core 3
394            count += detectTemp(conn, "Tf0B", result); // CPU performance core 4
395            count += detectTemp(conn, "Tf0D", result); // CPU performance core 5
396            count += detectTemp(conn, "Tf0E", result); // CPU performance core 6
397            count += detectTemp(conn, "Tf44", result); // CPU performance core 7
398            count += detectTemp(conn, "Tf49", result); // CPU performance core 8
399            count += detectTemp(conn, "Tf4A", result); // CPU performance core 9
400            count += detectTemp(conn, "Tf4B", result); // CPU performance core 10
401            count += detectTemp(conn, "Tf4D", result); // CPU performance core 11
402            count += detectTemp(conn, "Tf4E", result); // CPU performance core 12
403            break;
404
405        case FF_TEMP_CPU_M4X:
406            count += detectTemp(conn, "Te05", result); // CPU efficiency core 1
407            count += detectTemp(conn, "Te0S", result); // CPU efficiency core 2
408            count += detectTemp(conn, "Te09", result); // CPU efficiency core 3
409            count += detectTemp(conn, "Te0H", result); // CPU efficiency core 4
410            count += detectTemp(conn, "Tp01", result); // CPU performance core 1
411            count += detectTemp(conn, "Tp05", result); // CPU performance core 2
412            count += detectTemp(conn, "Tp09", result); // CPU performance core 3
413            count += detectTemp(conn, "Tp0D", result); // CPU performance core 4
414            count += detectTemp(conn, "Tp0V", result); // CPU performance core 5
415            count += detectTemp(conn, "Tp0Y", result); // CPU performance core 6
416            count += detectTemp(conn, "Tp0b", result); // CPU performance core 7
417            count += detectTemp(conn, "Tp0e", result); // CPU performance core 8
418            break;
419
420        case FF_TEMP_CPU_M5X:
421            count += detectTemp(conn, "Tp00", result); // CPU super core 1
422            count += detectTemp(conn, "Tp04", result); // CPU super core 2
423            count += detectTemp(conn, "Tp08", result); // CPU super core 3
424            count += detectTemp(conn, "Tp0C", result); // CPU super core 4
425            count += detectTemp(conn, "Tp0G", result); // CPU super core 5
426            count += detectTemp(conn, "Tp0K", result); // CPU super core 6
427            count += detectTemp(conn, "Tp0O", result); // CPU performance core 1
428            count += detectTemp(conn, "Tp0R", result); // CPU performance core 2
429            count += detectTemp(conn, "Tp0U", result); // CPU performance core 3
430            count += detectTemp(conn, "Tp0X", result); // CPU performance core 4
431            count += detectTemp(conn, "Tp0a", result); // CPU performance core 5
432            count += detectTemp(conn, "Tp0d", result); // CPU performance core 6
433            count += detectTemp(conn, "Tp0g", result); // CPU performance core 7
434            count += detectTemp(conn, "Tp0j", result); // CPU performance core 8
435            count += detectTemp(conn, "Tp0m", result); // CPU performance core 9
436            count += detectTemp(conn, "Tp0p", result); // CPU performance core 10
437            count += detectTemp(conn, "Tp0u", result); // CPU performance core 11
438            count += detectTemp(conn, "Tp0y", result); // CPU performance core 12
439            break;
440
441        case FF_TEMP_GPU_INTEL:
442            count += detectTemp(conn, "TCGC", result); // GPU Intel Graphics
443            goto gpu_unknown;
444
445        case FF_TEMP_GPU_AMD:
446            count += detectTemp(conn, "TGDD", result); // GPU AMD Radeon
447            goto gpu_unknown;
448
449        case FF_TEMP_GPU_UNKNOWN: // Nvidia?
450        gpu_unknown:
451            count += detectTemp(conn, "TG0D", result); // GPU diode
452            count += detectTemp(conn, "TG0P", result); // GPU proximity
453            break;
454
455        case FF_TEMP_GPU_M1X:
456            count += detectTemp(conn, "Tg05", result); // GPU 1
457            count += detectTemp(conn, "Tg0D", result); // GPU 2
458            count += detectTemp(conn, "Tg0L", result); // GPU 3
459            count += detectTemp(conn, "Tg0T", result); // GPU 4
460            break;
461
462        case FF_TEMP_GPU_M2X:
463            count += detectTemp(conn, "Tg0f", result); // GPU 1
464            count += detectTemp(conn, "Tg0j", result); // GPU 2
465            break;
466
467        case FF_TEMP_GPU_M3X:
468            count += detectTemp(conn, "Tf14", result); // GPU 1
469            count += detectTemp(conn, "Tf18", result); // GPU 2
470            count += detectTemp(conn, "Tf19", result); // GPU 3
471            count += detectTemp(conn, "Tf1A", result); // GPU 4
472            count += detectTemp(conn, "Tf24", result); // GPU 5
473            count += detectTemp(conn, "Tf28", result); // GPU 6
474            count += detectTemp(conn, "Tf29", result); // GPU 7
475            count += detectTemp(conn, "Tf2A", result); // GPU 8
476            break;
477
478        case FF_TEMP_GPU_M4X:
479            count += detectTemp(conn, "Tg0G", result); // GPU 1 (Basic)
480            count += detectTemp(conn, "Tg0H", result); // GPU 2 (Basic)
481            count += detectTemp(conn, "Tg1U", result); // GPU 1 (Pro / Max)
482            count += detectTemp(conn, "Tg1k", result); // GPU 2 (Pro / Max)
483            count += detectTemp(conn, "Tg0K", result); // GPU 3
484            count += detectTemp(conn, "Tg0L", result); // GPU 4
485            count += detectTemp(conn, "Tg0d", result); // GPU 5
486            count += detectTemp(conn, "Tg0e", result); // GPU 6
487            count += detectTemp(conn, "Tg0j", result); // GPU 7
488            count += detectTemp(conn, "Tg0k", result); // GPU 8
489            break;
490
491        case FF_TEMP_GPU_M5X:
492            count += detectTemp(conn, "Tg0U", result); // GPU 1
493            count += detectTemp(conn, "Tg0X", result); // GPU 2
494            count += detectTemp(conn, "Tg0d", result); // GPU 3
495            count += detectTemp(conn, "Tg0g", result); // GPU 4
496            count += detectTemp(conn, "Tg0j", result); // GPU 5
497            count += detectTemp(conn, "Tg1Y", result); // GPU 6
498            count += detectTemp(conn, "Tg1c", result); // GPU 7
499            count += detectTemp(conn, "Tg1g", result); // GPU 8
500            break;
501
502        case FF_TEMP_BATTERY:
503            count += detectTemp(conn, "TB1T", result); // Battery
504            count += detectTemp(conn, "TB2T", result); // Battery
505            break;
506
507        case FF_TEMP_MEMORY:
508            count += detectTemp(conn, "Tm02", result); // Memory 1
509            count += detectTemp(conn, "Tm06", result); // Memory 2
510            count += detectTemp(conn, "Tm08", result); // Memory 3
511            count += detectTemp(conn, "Tm09", result); // Memory 4
512            count += detectTemp(conn, "Tm0p", result); // Memory Proximity 1 (M4)
513            count += detectTemp(conn, "Tm1p", result); // Memory Proximity 2 (M4)
514            count += detectTemp(conn, "Tm2p", result); // Memory Proximity 3 (M4)
515            break;
516    }
517
518    FF_DEBUG("None of the SMC sensors for temp type %d reported a usable reading", (int) type);
519    if (count == 0) {
520        return "No temperatures detected";
521    }
522
523    *result /= count;
524
525    return nullptr;
526}