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