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}