1 /*
2 * libsmc fork for The Onion Box
3 * Copyright (C) Ralph Wetzel
4 * License MIT
5 * www.theonionbox.com
6 *
7 * Original disclaimer follows!
8
9 /*
10 * Apple System Management Controller (SMC) API from user space for Intel based
11 * Macs. Works by talking to the AppleSMC.kext (kernel extension), the driver
12 * for the SMC.
13 *
14 * smc.c
15 * libsmc
16 *
17 * Copyright (C) 2014 beltex <https://github.com/beltex>
18 *
19 * Based off of fork from:
20 * osx-cpu-temp <https://github.com/lavoiesl/osx-cpu-temp>
21 *
22 * With credits to:
23 *
24 * Copyright (C) 2006 devnull
25 * Apple System Management Control (SMC) Tool
26 *
27 * Copyright (C) 2006 Hendrik Holtmann
28 * smcFanControl <https://github.com/hholtmann/smcFanControl>
29 *
30 * This program is free software; you can redistribute it and/or modify
31 * it under the terms of the GNU General Public License as published by
32 * the Free Software Foundation; either version 2 of the License, or
33 * (at your option) any later version.
34 *
35 * This program is distributed in the hope that it will be useful,
36 * but WITHOUT ANY WARRANTY; without even the implied warranty of
37 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
38 * GNU General Public License for more details.
39 *
40 * You should have received a copy of the GNU General Public License along
41 * with this program; if not, write to the Free Software Foundation, Inc.,
42 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
43 */
44
45 #include <stdio.h>
46 #include <string.h>
47 #include "../include/smc.h"
48
49
50 //------------------------------------------------------------------------------
51 // MARK: MACROS
52 //------------------------------------------------------------------------------
53
54
55 /**
56 Name of the SMC IOService as seen in the IORegistry. You can view it either via
57 command line with ioreg or through the IORegistryExplorer app (found on Apple's
58 developer site - Hardware IO Tools for Xcode)
59 */
60 #define IOSERVICE_SMC "AppleSMC"
61
62
63 /**
64 IOService for getting machine model name
65 */
66 #define IOSERVICE_MODEL "IOPlatformExpertDevice"
67
68
69 /**
70 SMC data types - 4 byte multi-character constants
71
72 Sources: See TMP SMC keys in smc.h
73
74 http://stackoverflow.com/questions/22160746/fpe2-and-sp78-data-types
75 */
76 #define DATA_TYPE_UINT8 "ui8 "
77 #define DATA_TYPE_UINT16 "ui16"
78 #define DATA_TYPE_UINT32 "ui32"
79 #define DATA_TYPE_FLAG "flag"
80 #define DATA_TYPE_FPE2 "fpe2"
81 #define DATA_TYPE_SFDS "{fds"
82 #define DATA_TYPE_SP78 "sp78"
83
84
85 //------------------------------------------------------------------------------
86 // MARK: GLOBAL VARS
87 //------------------------------------------------------------------------------
88
89
90 /**
91 Our connection to the SMC
92 */
93 static io_connect_t conn;
94
95
96 /**
97 Number of characters in an SMC key
98 */
99 static const int SMC_KEY_SIZE = 4;
100
101
102 /**
103 Number of characters in a data type "key" returned from the SMC. See data type
104 macros.
105 */
106 static const int DATA_TYPE_SIZE = 4;
107
108
109 //------------------------------------------------------------------------------
110 // MARK: ENUMS
111 //------------------------------------------------------------------------------
112
113
114 /**
115 Defined by AppleSMC.kext. See SMCParamStruct.
116
117 These are SMC specific return codes
118 */
119 typedef enum {
120 kSMCSuccess = 0,
121 kSMCError = 1,
122 kSMCKeyNotFound = 0x84
123 } kSMC_t;
124
125
126 /**
127 Defined by AppleSMC.kext. See SMCParamStruct.
128
129 Function selectors. Used to tell the SMC which function inside it to call.
130 */
131 typedef enum {
132 kSMCUserClientOpen = 0,
133 kSMCUserClientClose = 1,
134 kSMCHandleYPCEvent = 2,
135 kSMCReadKey = 5,
136 kSMCWriteKey = 6,
137 kSMCGetKeyCount = 7,
138 kSMCGetKeyFromIndex = 8,
139 kSMCGetKeyInfo = 9
140 } selector_t;
141
142
143 //------------------------------------------------------------------------------
144 // MARK: STRUCTS
145 //------------------------------------------------------------------------------
146
147
148 /**
149 Defined by AppleSMC.kext. See SMCParamStruct.
150 */
151 typedef struct {
152 unsigned char major;
153 unsigned char minor;
154 unsigned char build;
155 unsigned char reserved;
156 unsigned short release;
157 } SMCVersion;
158
159
160 /**
161 Defined by AppleSMC.kext. See SMCParamStruct.
162 */
163 typedef struct {
164 uint16_t version;
165 uint16_t length;
166 uint32_t cpuPLimit;
167 uint32_t gpuPLimit;
168 uint32_t memPLimit;
169 } SMCPLimitData;
170
171
172 /**
173 Defined by AppleSMC.kext. See SMCParamStruct.
174
175 - dataSize : How many values written to SMCParamStruct.bytes
176 - dataType : Type of data written to SMCParamStruct.bytes. This lets us know how
177 to interpret it (translate it to human readable)
178 */
179 typedef struct {
180 IOByteCount dataSize;
181 uint32_t dataType;
182 uint8_t dataAttributes;
183 } SMCKeyInfoData;
184
185
186 /**
187 Defined by AppleSMC.kext.
188
189 This is the predefined struct that must be passed to communicate with the
190 AppleSMC driver. While the driver is closed source, the definition of this
191 struct happened to appear in the Apple PowerManagement project at around
192 version 211, and soon after disappeared. It can be seen in the PrivateLib.c
193 file under pmconfigd.
194
195 https://www.opensource.apple.com/source/PowerManagement/PowerManagement-211/
196 */
197 typedef struct {
198 uint32_t key;
199 SMCVersion vers;
200 SMCPLimitData pLimitData;
201 SMCKeyInfoData keyInfo;
202 uint8_t result;
203 uint8_t status;
204 uint8_t data8;
205 uint32_t data32;
206 uint8_t bytes[32];
207 } SMCParamStruct;
208
209
210 /**
211 Used for returning data from the SMC.
212 */
213 typedef struct {
214 uint8_t data[32];
215 uint32_t dataType;
216 uint32_t dataSize;
217 kSMC_t kSMC;
218 } smc_return_t;
219
220
221 //------------------------------------------------------------------------------
222 // MARK: HELPERS - TYPE CONVERSION
223 //------------------------------------------------------------------------------
224
225
226 /**
227 Convert data from SMC of fpe2 type to human readable.
228
229 :param: data Data from the SMC to be converted. Assumed data size of 2.
230 :returns: Converted data
231 */
from_fpe2(uint8_t data[32])232 static unsigned int from_fpe2(uint8_t data[32])
233 {
234 unsigned int ans = 0;
235
236 // Data type for fan calls - fpe2
237 // This is assumend to mean floating point, with 2 exponent bits
238 // http://stackoverflow.com/questions/22160746/fpe2-and-sp78-data-types
239 ans += data[0] << 6;
240 ans += data[1] << 2;
241
242 return ans;
243 }
244
245
246 /**
247 Convert to fpe2 data type to be passed to SMC.
248
249 :param: val Value to convert
250 :param: data Pointer to data array to place result
251 */
to_fpe2(unsigned int val,uint8_t * data)252 static void to_fpe2(unsigned int val, uint8_t *data)
253 {
254 data[0] = val >> 6;
255 data[1] = (val << 2) ^ (data[0] << 8);
256 }
257
258
259 /**
260 Convert SMC key to uint32_t. This must be done to pass it to the SMC.
261
262 :param: key The SMC key to convert
263 :returns: uint32_t translation.
264 Returns zero if key is not 4 characters in length.
265 */
to_uint32_t(char * key)266 static uint32_t to_uint32_t(char *key)
267 {
268 uint32_t ans = 0;
269 uint32_t shift = 24;
270
271 // SMC key is expected to be 4 bytes - thus 4 chars
272 if (strlen(key) != SMC_KEY_SIZE) {
273 return 0;
274 }
275
276 for (int i = 0; i < SMC_KEY_SIZE; i++) {
277 ans += key[i] << shift;
278 shift -= 8;
279 }
280
281 return ans;
282 }
283
284
285 /**
286 For converting the dataType return from the SMC to human readable 4 byte
287 multi-character constant.
288 */
to_string(uint32_t val,char * dataType)289 static void to_string(uint32_t val, char *dataType)
290 {
291 int shift = 24;
292
293 for (int i = 0; i < DATA_TYPE_SIZE; i++) {
294 // To get each char, we shift it into the lower 8 bits, and then & by
295 // 255 to insolate it
296 dataType[i] = (val >> shift) & 0xff;
297 shift -= 8;
298 }
299 }
300
301
302 //------------------------------------------------------------------------------
303 // MARK: HELPERS - TMP CONVERSION
304 //------------------------------------------------------------------------------
305
306
307 /**
308 Celsius to Fahrenheit
309 */
to_fahrenheit(double tmp)310 static double to_fahrenheit(double tmp)
311 {
312 // http://en.wikipedia.org/wiki/Fahrenheit#Definition_and_conversions
313 return (tmp * 1.8) + 32;
314 }
315
316
317 /**
318 Celsius to Kelvin
319 */
to_kelvin(double tmp)320 static double to_kelvin(double tmp)
321 {
322 // http://en.wikipedia.org/wiki/Kelvin
323 return tmp + 273.15;
324 }
325
326
327 //------------------------------------------------------------------------------
328 // MARK: "PRIVATE" FUNCTIONS
329 //------------------------------------------------------------------------------
330
331
332 /**
333 Make a call to the SMC
334
335 :param: inputStruct Struct that holds data telling the SMC what you want
336 :param: outputStruct Struct holding the SMC's response
337 :returns: I/O Kit return code
338 */
call_smc_conn(io_connect_t con,SMCParamStruct * inputStruct,SMCParamStruct * outputStruct)339 static kern_return_t call_smc_conn(io_connect_t con,
340 SMCParamStruct *inputStruct,
341 SMCParamStruct *outputStruct)
342 {
343 kern_return_t result;
344 size_t inputStructCnt = sizeof(SMCParamStruct);
345 size_t outputStructCnt = sizeof(SMCParamStruct);
346
347 result = IOConnectCallStructMethod(con, kSMCHandleYPCEvent,
348 inputStruct,
349 inputStructCnt,
350 outputStruct,
351 &outputStructCnt);
352
353 if (result != kIOReturnSuccess) {
354 // IOReturn error code lookup. See "Accessing Hardware From Applications
355 // -> Handling Errors" Apple doc
356 result = err_get_code(result);
357 }
358
359 return result;
360 }
361
call_smc(SMCParamStruct * inputStruct,SMCParamStruct * outputStruct)362 static kern_return_t call_smc(SMCParamStruct *inputStruct,
363 SMCParamStruct *outputStruct)
364 {
365 return call_smc_conn(conn, inputStruct, outputStruct);
366 }
367
368 /**
369 Read data from the SMC
370
371 :param: key The SMC key
372 */
read_smc_conn(io_connect_t con,char * key,smc_return_t * result_smc)373 static kern_return_t read_smc_conn(io_connect_t con, char *key, smc_return_t *result_smc)
374 {
375 kern_return_t result;
376 SMCParamStruct inputStruct;
377 SMCParamStruct outputStruct;
378
379 memset(&inputStruct, 0, sizeof(SMCParamStruct));
380 memset(&outputStruct, 0, sizeof(SMCParamStruct));
381 memset(result_smc, 0, sizeof(smc_return_t));
382
383 // First call to AppleSMC - get key info
384 inputStruct.key = to_uint32_t(key);
385 inputStruct.data8 = kSMCGetKeyInfo;
386
387 result = call_smc_conn(con, &inputStruct, &outputStruct);
388 result_smc->kSMC = outputStruct.result;
389
390 if (result != kIOReturnSuccess || outputStruct.result != kSMCSuccess) {
391 return result;
392 }
393
394 // Store data for return
395 result_smc->dataSize = outputStruct.keyInfo.dataSize;
396 result_smc->dataType = outputStruct.keyInfo.dataType;
397
398
399 // Second call to AppleSMC - now we can get the data
400 inputStruct.keyInfo.dataSize = outputStruct.keyInfo.dataSize;
401 inputStruct.data8 = kSMCReadKey;
402
403 result = call_smc_conn(con, &inputStruct, &outputStruct);
404 result_smc->kSMC = outputStruct.result;
405
406 if (result != kIOReturnSuccess || outputStruct.result != kSMCSuccess) {
407 return result;
408 }
409
410 memcpy(result_smc->data, outputStruct.bytes, sizeof(outputStruct.bytes));
411
412 return result;
413 }
414
read_smc(char * key,smc_return_t * result_smc)415 static kern_return_t read_smc(char *key, smc_return_t *result_smc)
416 {
417 return read_smc_conn(conn, key, result_smc);
418 }
419
420 /**
421 Write data to the SMC.
422
423 :returns: IOReturn IOKit return code
424 */
write_smc_conn(io_connect_t con,char * key,smc_return_t * result_smc)425 static kern_return_t write_smc_conn(io_connect_t con, char *key, smc_return_t *result_smc)
426 {
427 kern_return_t result;
428 SMCParamStruct inputStruct;
429 SMCParamStruct outputStruct;
430
431 memset(&inputStruct, 0, sizeof(SMCParamStruct));
432 memset(&outputStruct, 0, sizeof(SMCParamStruct));
433
434 // First call to AppleSMC - get key info
435 inputStruct.key = to_uint32_t(key);
436 inputStruct.data8 = kSMCGetKeyInfo;
437
438 result = call_smc_conn(con, &inputStruct, &outputStruct);
439 result_smc->kSMC = outputStruct.result;
440
441 if (result != kIOReturnSuccess || outputStruct.result != kSMCSuccess) {
442 return result;
443 }
444
445 // Check data is correct
446 if (result_smc->dataSize != outputStruct.keyInfo.dataSize ||
447 result_smc->dataType != outputStruct.keyInfo.dataType) {
448 return kIOReturnBadArgument;
449 }
450
451 // Second call to AppleSMC - now we can write the data
452 inputStruct.data8 = kSMCWriteKey;
453 inputStruct.keyInfo.dataSize = outputStruct.keyInfo.dataSize;
454
455 // Set data to write
456 memcpy(inputStruct.bytes, result_smc->data, sizeof(result_smc->data));
457
458 result = call_smc_conn(con, &inputStruct, &outputStruct);
459 result_smc->kSMC = outputStruct.result;
460
461 return result;
462 }
463
write_smc(char * key,smc_return_t * result_smc)464 static kern_return_t write_smc(char *key, smc_return_t *result_smc)
465 {
466 return write_smc_conn(conn, key, result_smc);
467 }
468
469 /**
470 Get the model name of the machine.
471 */
get_machine_model(io_name_t model)472 static kern_return_t get_machine_model(io_name_t model)
473 {
474 io_service_t service;
475 kern_return_t result;
476
477 service = IOServiceGetMatchingService(kIOMasterPortDefault,
478 IOServiceMatching(IOSERVICE_MODEL));
479
480 if (service == 0) {
481 printf("ERROR: %s NOT FOUND\n", IOSERVICE_MODEL);
482 return kIOReturnError;
483 }
484
485 // Get the model name
486 result = IORegistryEntryGetName(service, model);
487 IOObjectRelease(service);
488
489 return result;
490 }
491
492
493 //------------------------------------------------------------------------------
494 // MARK: "PUBLIC" FUNCTIONS
495 //------------------------------------------------------------------------------
496
497
open_smc_conn(io_connect_t * con)498 kern_return_t open_smc_conn(io_connect_t *con)
499 {
500 kern_return_t result;
501 io_service_t service;
502
503 service = IOServiceGetMatchingService(kIOMasterPortDefault,
504 IOServiceMatching(IOSERVICE_SMC));
505
506 if (service == 0) {
507 // NOTE: IOServiceMatching documents 0 on failure
508 printf("ERROR: %s NOT FOUND\n", IOSERVICE_SMC);
509 return kIOReturnError;
510 }
511
512 result = IOServiceOpen(service, mach_task_self(), 0, con);
513 IOObjectRelease(service);
514
515 return result;
516 }
517
open_smc(void)518 kern_return_t open_smc(void)
519 {
520 return open_smc_conn(&conn);
521 }
522
523
close_smc_conn(io_connect_t con)524 kern_return_t close_smc_conn(io_connect_t con)
525 {
526 return IOServiceClose(con);
527 }
528
close_smc(void)529 kern_return_t close_smc(void)
530 {
531 return close_smc_conn(conn);
532 }
533
534
is_key_valid_conn(io_connect_t con,char * key)535 bool is_key_valid_conn(io_connect_t con, char *key)
536 {
537 bool ans = false;
538 kern_return_t result;
539 smc_return_t result_smc;
540
541 if (strlen(key) != SMC_KEY_SIZE) {
542 printf("ERROR: Invalid key size - must be 4 chars\n");
543 return ans;
544 }
545
546 // Try a read and see if it succeeds
547 result = read_smc_conn(con, key, &result_smc);
548
549 if (result == kIOReturnSuccess && result_smc.kSMC == kSMCSuccess) {
550 ans = true;
551 }
552
553 return ans;
554 }
555
is_key_valid(char * key)556 bool is_key_valid(char *key)
557 {
558 return is_key_valid_conn(conn, key);
559 }
560
561
get_tmp_conn(io_connect_t con,char * key,tmp_unit_t unit)562 double get_tmp_conn(io_connect_t con, char *key, tmp_unit_t unit)
563 {
564 kern_return_t result;
565 smc_return_t result_smc;
566
567 result = read_smc_conn(con, key, &result_smc);
568
569 if (!(result == kIOReturnSuccess &&
570 result_smc.dataSize == 2 &&
571 result_smc.dataType == to_uint32_t(DATA_TYPE_SP78))) {
572 // Error
573 return 0.0;
574 }
575
576 // TODO: Create from_sp78() convert function
577 double tmp = result_smc.data[0];
578
579 switch (unit) {
580 case CELSIUS:
581 break;
582 case FAHRENHEIT:
583 tmp = to_fahrenheit(tmp);
584 break;
585 case KELVIN:
586 tmp = to_kelvin(tmp);
587 break;
588 }
589
590 return tmp;
591 }
592
get_tmp(char * key,tmp_unit_t unit)593 double get_tmp(char *key, tmp_unit_t unit)
594 {
595 return get_tmp_conn(conn, key, unit);
596 }
597
598
is_battery_powered(void)599 bool is_battery_powered(void)
600 {
601 kern_return_t result;
602 smc_return_t result_smc;
603
604 result = read_smc(BATT_PWR, &result_smc);
605
606 if (!(result == kIOReturnSuccess &&
607 result_smc.dataSize == 1 &&
608 result_smc.dataType == to_uint32_t(DATA_TYPE_FLAG))) {
609 // Error
610 return false;
611 }
612
613 return result_smc.data[0];
614 }
615
616
is_optical_disk_drive_full(void)617 bool is_optical_disk_drive_full(void)
618 {
619 kern_return_t result;
620 smc_return_t result_smc;
621
622 result = read_smc(ODD_FULL, &result_smc);
623
624 if (!(result == kIOReturnSuccess &&
625 result_smc.dataSize == 1 &&
626 result_smc.dataType == to_uint32_t(DATA_TYPE_FLAG))) {
627 // Error
628 return false;
629 }
630
631 return result_smc.data[0];
632 }
633
634
635 //------------------------------------------------------------------------------
636 // MARK: FAN FUNCTIONS
637 //------------------------------------------------------------------------------
638
639
get_fan_name(unsigned int fan_num,fan_name_t name)640 bool get_fan_name(unsigned int fan_num, fan_name_t name)
641 {
642 char key[5];
643 kern_return_t result;
644 smc_return_t result_smc;
645
646 sprintf(key, "F%dID", fan_num);
647 result = read_smc(key, &result_smc);
648
649 if (!(result == kIOReturnSuccess &&
650 result_smc.dataSize == 16 &&
651 result_smc.dataType == to_uint32_t(DATA_TYPE_SFDS))) {
652 return false;
653 }
654
655
656 /*
657 We know the data size is 16 bytes and the type is "{fds", a custom
658 struct defined by the AppleSMC.kext. See TMP enum sources for the
659 struct.
660
661 The last 12 bytes contain the name of the fan, an array of chars, hence
662 the loop range.
663 */
664 int index = 0;
665 for (int i = 4; i < 16; i++) {
666 // Check if at the end (name may not be full 12 bytes)
667 // Could check for 0 (null), but instead we check for 32 (space). This
668 // is a hack to remove whitespace. :)
669 if (result_smc.data[i] == 32) {
670 break;
671 }
672
673 name[index] = result_smc.data[i];
674 index++;
675 }
676
677 return true;
678 }
679
680
get_num_fans(void)681 int get_num_fans(void)
682 {
683 kern_return_t result;
684 smc_return_t result_smc;
685
686 result = read_smc(NUM_FANS, &result_smc);
687
688 if (!(result == kIOReturnSuccess &&
689 result_smc.dataSize == 1 &&
690 result_smc.dataType == to_uint32_t(DATA_TYPE_UINT8))) {
691 // Error
692 return -1;
693 }
694
695 return result_smc.data[0];
696 }
697
698
get_fan_rpm(unsigned int fan_num)699 unsigned int get_fan_rpm(unsigned int fan_num)
700 {
701 char key[5];
702 kern_return_t result;
703 smc_return_t result_smc;
704
705 sprintf(key, "F%dAc", fan_num);
706 result = read_smc(key, &result_smc);
707
708 if (!(result == kIOReturnSuccess &&
709 result_smc.dataSize == 2 &&
710 result_smc.dataType == to_uint32_t(DATA_TYPE_FPE2))) {
711 // Error
712 return 0;
713 }
714
715 return from_fpe2(result_smc.data);
716 }
717
718
set_fan_min_rpm(unsigned int fan_num,unsigned int rpm,bool auth)719 bool set_fan_min_rpm(unsigned int fan_num, unsigned int rpm, bool auth)
720 {
721 // TODO: Add rpm val safety check
722 char key[5];
723 bool ans = false;
724 kern_return_t result;
725 smc_return_t result_smc;
726
727 memset(&result_smc, 0, sizeof(smc_return_t));
728
729 // TODO: Don't use magic number
730 result_smc.dataSize = 2;
731 result_smc.dataType = to_uint32_t(DATA_TYPE_FPE2);
732 to_fpe2(rpm, result_smc.data);
733
734 sprintf(key, "F%dMn", fan_num);
735 result = write_smc(key, &result_smc);
736
737 if (result == kIOReturnSuccess && result_smc.kSMC == kSMCSuccess) {
738 ans = true;
739 }
740
741 return ans;
742 }
743