View Javadoc
1   /*
2    * Copyright 2016-2023 The OSHI Project Contributors
3    * SPDX-License-Identifier: MIT
4    */
5   package oshi.util.platform.mac;
6   
7   import java.nio.ByteBuffer;
8   import java.nio.ByteOrder;
9   import java.util.Arrays;
10  import java.util.Locale;
11  import java.util.Map;
12  import java.util.concurrent.ConcurrentHashMap;
13  
14  import org.slf4j.Logger;
15  import org.slf4j.LoggerFactory;
16  
17  import com.sun.jna.NativeLong;
18  import com.sun.jna.platform.mac.IOKit.IOConnect;
19  import com.sun.jna.platform.mac.IOKit.IOService;
20  import com.sun.jna.platform.mac.IOKitUtil;
21  
22  import oshi.annotation.concurrent.ThreadSafe;
23  import oshi.jna.ByRef.CloseableNativeLongByReference;
24  import oshi.jna.ByRef.CloseablePointerByReference;
25  import oshi.jna.platform.mac.IOKit;
26  import oshi.jna.platform.mac.IOKit.SMCKeyData;
27  import oshi.jna.platform.mac.IOKit.SMCKeyDataKeyInfo;
28  import oshi.jna.platform.mac.IOKit.SMCVal;
29  import oshi.jna.platform.mac.SystemB;
30  import oshi.util.ParseUtil;
31  
32  /**
33   * Provides access to SMC calls on macOS
34   */
35  @ThreadSafe
36  public final class SmcUtil {
37  
38      private static final Logger LOG = LoggerFactory.getLogger(SmcUtil.class);
39  
40      private static final IOKit IO = IOKit.INSTANCE;
41  
42      /**
43       * Thread-safe map for caching info retrieved by a key necessary for subsequent calls.
44       */
45      private static Map<Integer, SMCKeyDataKeyInfo> keyInfoCache = new ConcurrentHashMap<>();
46  
47      /**
48       * Byte array used for matching return type
49       */
50      private static final byte[] DATATYPE_SP78 = ParseUtil.asciiStringToByteArray("sp78", 5);
51      private static final byte[] DATATYPE_FPE2 = ParseUtil.asciiStringToByteArray("fpe2", 5);
52      private static final byte[] DATATYPE_FLT = ParseUtil.asciiStringToByteArray("flt ", 5);
53  
54      public static final String SMC_KEY_FAN_NUM = "FNum";
55      public static final String SMC_KEY_FAN_SPEED = "F%dAc";
56      public static final String SMC_KEY_CPU_TEMP = "TC0P";
57      public static final String SMC_KEY_CPU_VOLTAGE = "VC0C";
58  
59      public static final byte SMC_CMD_READ_BYTES = 5;
60      public static final byte SMC_CMD_READ_KEYINFO = 9;
61      public static final int KERNEL_INDEX_SMC = 2;
62  
63      private SmcUtil() {
64      }
65  
66      /**
67       * Open a connection to SMC.
68       *
69       * @return The connection if successful, null if failure
70       */
71      public static IOConnect smcOpen() {
72          IOService smcService = IOKitUtil.getMatchingService("AppleSMC");
73          if (smcService != null) {
74              try (CloseablePointerByReference connPtr = new CloseablePointerByReference()) {
75                  int result = IO.IOServiceOpen(smcService, SystemB.INSTANCE.mach_task_self(), 0, connPtr);
76                  if (result == 0) {
77                      return new IOConnect(connPtr.getValue());
78                  } else if (LOG.isErrorEnabled()) {
79                      LOG.error(String.format(Locale.ROOT, "Unable to open connection to AppleSMC service. Error: 0x%08x",
80                              result));
81                  }
82              } finally {
83                  smcService.release();
84              }
85          } else {
86              LOG.error("Unable to locate AppleSMC service");
87          }
88          return null;
89      }
90  
91      /**
92       * Close connection to SMC.
93       *
94       * @param conn The connection
95       *
96       * @return 0 if successful, nonzero if failure
97       */
98      public static int smcClose(IOConnect conn) {
99          return IO.IOServiceClose(conn);
100     }
101 
102     /**
103      * Get a value from SMC which is in a floating point datatype (SP78, FPE2, FLT)
104      *
105      * @param conn The connection
106      * @param key  The key to retrieve
107      * @return Double representing the value
108      */
109     public static double smcGetFloat(IOConnect conn, String key) {
110         try (SMCVal val = new SMCVal()) {
111             int result = smcReadKey(conn, key, val);
112             if (result == 0 && val.dataSize > 0) {
113                 if (Arrays.equals(val.dataType, DATATYPE_SP78) && val.dataSize == 2) {
114                     // First bit is sign, next 7 bits are integer portion, last 8 bits are
115                     // fractional portion
116                     return val.bytes[0] + val.bytes[1] / 256d;
117                 } else if (Arrays.equals(val.dataType, DATATYPE_FPE2) && val.dataSize == 2) {
118                     // First E (14) bits are integer portion last 2 bits are fractional portion
119                     return ParseUtil.byteArrayToFloat(val.bytes, val.dataSize, 2);
120                 } else if (Arrays.equals(val.dataType, DATATYPE_FLT) && val.dataSize == 4) {
121                     // Standard 32-bit floating point
122                     return ByteBuffer.wrap(val.bytes).order(ByteOrder.LITTLE_ENDIAN).getFloat();
123                 }
124             }
125         }
126         // Read failed
127         return 0d;
128     }
129 
130     /**
131      * Get a 64-bit integer value from SMC
132      *
133      * @param conn The connection
134      * @param key  The key to retrieve
135      * @return Long representing the value
136      */
137     public static long smcGetLong(IOConnect conn, String key) {
138         try (SMCVal val = new SMCVal()) {
139             int result = smcReadKey(conn, key, val);
140             if (result == 0) {
141                 return ParseUtil.byteArrayToLong(val.bytes, val.dataSize);
142             }
143         }
144         // Read failed
145         return 0;
146     }
147 
148     /**
149      * Get cached keyInfo if it exists, or generate new keyInfo
150      *
151      * @param conn            The connection
152      * @param inputStructure  Key data input
153      * @param outputStructure Key data output
154      * @return 0 if successful, nonzero if failure
155      */
156     public static int smcGetKeyInfo(IOConnect conn, SMCKeyData inputStructure, SMCKeyData outputStructure) {
157         if (keyInfoCache.containsKey(inputStructure.key)) {
158             SMCKeyDataKeyInfo keyInfo = keyInfoCache.get(inputStructure.key);
159             outputStructure.keyInfo.dataSize = keyInfo.dataSize;
160             outputStructure.keyInfo.dataType = keyInfo.dataType;
161             outputStructure.keyInfo.dataAttributes = keyInfo.dataAttributes;
162         } else {
163             inputStructure.data8 = SMC_CMD_READ_KEYINFO;
164             int result = smcCall(conn, KERNEL_INDEX_SMC, inputStructure, outputStructure);
165             if (result != 0) {
166                 return result;
167             }
168             SMCKeyDataKeyInfo keyInfo = new SMCKeyDataKeyInfo();
169             keyInfo.dataSize = outputStructure.keyInfo.dataSize;
170             keyInfo.dataType = outputStructure.keyInfo.dataType;
171             keyInfo.dataAttributes = outputStructure.keyInfo.dataAttributes;
172             keyInfoCache.put(inputStructure.key, keyInfo);
173         }
174         return 0;
175     }
176 
177     /**
178      * Read a key from SMC
179      *
180      * @param conn The connection
181      * @param key  Key to read
182      * @param val  Structure to receive the result
183      * @return 0 if successful, nonzero if failure
184      */
185     public static int smcReadKey(IOConnect conn, String key, SMCVal val) {
186         try (SMCKeyData inputStructure = new SMCKeyData(); SMCKeyData outputStructure = new SMCKeyData()) {
187             inputStructure.key = (int) ParseUtil.strToLong(key, 4);
188             int result = smcGetKeyInfo(conn, inputStructure, outputStructure);
189             if (result == 0) {
190                 val.dataSize = outputStructure.keyInfo.dataSize;
191                 val.dataType = ParseUtil.longToByteArray(outputStructure.keyInfo.dataType, 4, 5);
192 
193                 inputStructure.keyInfo.dataSize = val.dataSize;
194                 inputStructure.data8 = SMC_CMD_READ_BYTES;
195 
196                 result = smcCall(conn, KERNEL_INDEX_SMC, inputStructure, outputStructure);
197                 if (result == 0) {
198                     System.arraycopy(outputStructure.bytes, 0, val.bytes, 0, val.bytes.length);
199                     return 0;
200                 }
201             }
202             return result;
203         }
204     }
205 
206     /**
207      * Call SMC
208      *
209      * @param conn            The connection
210      * @param index           Kernel index
211      * @param inputStructure  Key data input
212      * @param outputStructure Key data output
213      * @return 0 if successful, nonzero if failure
214      */
215     public static int smcCall(IOConnect conn, int index, SMCKeyData inputStructure, SMCKeyData outputStructure) {
216         try (CloseableNativeLongByReference size = new CloseableNativeLongByReference(
217                 new NativeLong(outputStructure.size()))) {
218             return IO.IOConnectCallStructMethod(conn, index, inputStructure, new NativeLong(inputStructure.size()),
219                     outputStructure, size);
220         }
221     }
222 }