Monitor/Sources/FloatingMonitor/SMCSensor.swift

260 lines
7.4 KiB
Swift
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import Foundation
import IOKit
// MARK: - SMC AppleSMC + IOKit CPU / GPU
// SMC AppleSMC kext SMCParamStruct
private struct SMCVersion {
var major: UInt8 = 0
var minor: UInt8 = 0
var build: UInt8 = 0
var reserved: UInt8 = 0
var release: UInt16 = 0
}
private struct SMCPLimitData {
var version: UInt16 = 0
var length: UInt16 = 0
var cpuPLimit: UInt32 = 0
var gpuPLimit: UInt32 = 0
var memPLimit: UInt32 = 0
}
private struct SMCKeyInfoData {
var dataSize: UInt32 = 0
var dataType: UInt32 = 0
var dataAttributes: UInt8 = 0
}
private typealias SMCBytes = (
UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8,
UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8,
UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8,
UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8
)
private struct SMCParamStruct {
var key: UInt32 = 0
var vers = SMCVersion()
var pLimitData = SMCPLimitData()
var keyInfo = SMCKeyInfoData()
var padding: UInt16 = 0 //
var result: UInt8 = 0
var status: UInt8 = 0
var data8: UInt8 = 0 // selector5=key, 9=key, 8=key
var data32: UInt32 = 0
var bytes: SMCBytes = (
0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0
)
}
// SMC
private let kSMCHandleYPCEvent: UInt32 = 2
private let kSMCReadKey: UInt8 = 5
private let kSMCGetKeyInfo: UInt8 = 9
private let kSMCGetKeyFromIndex: UInt8 = 8
private let kSMCSuccess: UInt8 = 0
/// actor SMC
final class SMCSensor {
static let shared = SMCSensor()
private var conn: io_connect_t = 0
private var cpuKeys: [String] = []
private var gpuKeys: [String] = []
private var scanned = false
private init() {}
deinit {
if conn != 0 { IOServiceClose(conn) }
}
// MARK: -
func readCPUTemperature() -> Double? {
return readMax(keys: cpuKeys)
}
func readGPUTemperature() -> Double? {
return readMax(keys: gpuKeys)
}
// MARK: -
private func readMax(keys: [String]) -> Double? {
guard openSMC() else { return nil }
if !scanned { scanKeys() }
var maxTemp: Double? = nil
for key in keys {
if let temp = readKey(key) {
maxTemp = max(maxTemp ?? temp, temp)
}
}
return maxTemp
}
private func openSMC() -> Bool {
if conn != 0 { return true }
let service = IOServiceGetMatchingService(
kIOMainPortDefault,
IOServiceMatching("AppleSMC")
)
guard service != 0 else { return false }
let status = IOServiceOpen(service, mach_task_self_, 0, &conn)
IOObjectRelease(service)
return status == KERN_SUCCESS && conn != 0
}
// MARK: - key
private func scanKeys() {
scanned = true
guard conn != 0 else { return }
// key
let totalKeys = readKeyCount()
guard totalKeys > 0 else { return }
var cpu: [String] = []
var gpu: [String] = []
for i in 0..<totalKeys {
guard let keyName = keyAtIndex(i),
let (dataType, _) = keyInfo(keyName) else { continue }
// sp78 key
let isTempType = (dataType == "flt " || dataType == "sp78")
guard isTempType else { continue }
if keyName.hasPrefix("Tp") {
cpu.append(keyName)
} else if keyName.hasPrefix("Tg") {
gpu.append(keyName)
}
}
if !cpu.isEmpty { cpuKeys = cpu }
if !gpu.isEmpty { gpuKeys = gpu }
}
// MARK: - SMC
private func readKeyCount() -> Int {
var input = SMCParamStruct()
input.key = fourCharCode("#KEY")
input.keyInfo.dataSize = 4
input.keyInfo.dataType = fourCharCode("ui32")
input.data8 = kSMCReadKey
guard let output = callSMC(&input),
output.result == kSMCSuccess else { return 0 }
let raw = UInt32(output.bytes.0) << 24
| UInt32(output.bytes.1) << 16
| UInt32(output.bytes.2) << 8
| UInt32(output.bytes.3)
return Int(raw)
}
private func keyAtIndex(_ index: Int) -> String? {
var input = SMCParamStruct()
input.data8 = kSMCGetKeyFromIndex
input.data32 = UInt32(index)
guard let output = callSMC(&input),
output.result == kSMCSuccess else { return nil }
return charCodeToString(output.key)
}
/// key (dataType, dataSize)
private func keyInfo(_ keyName: String) -> (String, UInt32)? {
var input = SMCParamStruct()
input.key = fourCharCode(keyName)
input.data8 = kSMCGetKeyInfo
guard let output = callSMC(&input),
output.result == kSMCSuccess else { return nil }
return (charCodeToString(output.keyInfo.dataType),
output.keyInfo.dataSize)
}
/// key flt / sp78
private func readKey(_ keyName: String) -> Double? {
guard let (dataType, dataSize) = keyInfo(keyName) else { return nil }
var input = SMCParamStruct()
input.key = fourCharCode(keyName)
input.keyInfo.dataSize = dataSize
input.keyInfo.dataType = fourCharCode(dataType)
input.data8 = kSMCReadKey
guard let output = callSMC(&input),
output.result == kSMCSuccess else { return nil }
switch dataType {
case "flt ":
// 32-bit IEEE 754 float
let raw = UInt32(output.bytes.0)
| (UInt32(output.bytes.1) << 8)
| (UInt32(output.bytes.2) << 16)
| (UInt32(output.bytes.3) << 24)
return Double(Float(bitPattern: raw))
case "sp78":
// 16-bit signed fixed-point
let sign = (output.bytes.0 & 0x80) == 0 ? 1.0 : -1.0
return sign * (Double(output.bytes.0 & 0x7F) + Double(output.bytes.1) / 256.0)
default:
return nil
}
}
/// SMCParamStruct AppleSMC
private func callSMC(_ input: inout SMCParamStruct) -> SMCParamStruct? {
var output = SMCParamStruct()
let size = MemoryLayout<SMCParamStruct>.stride // 80
var outputSize = size
let result = IOConnectCallStructMethod(
conn,
kSMCHandleYPCEvent,
&input, size,
&output, &outputSize
)
guard result == KERN_SUCCESS else { return nil }
return output
}
// MARK: -
private func fourCharCode(_ string: String) -> UInt32 {
var result: UInt32 = 0
for char in string.utf8 {
result = (result << 8) | UInt32(char)
}
return result
}
private func charCodeToString(_ code: UInt32) -> String {
let c1 = UInt8((code >> 24) & 0xFF)
let c2 = UInt8((code >> 16) & 0xFF)
let c3 = UInt8((code >> 8) & 0xFF)
let c4 = UInt8(code & 0xFF)
return String(UnicodeScalar(c1))
+ String(UnicodeScalar(c2))
+ String(UnicodeScalar(c3))
+ String(UnicodeScalar(c4))
}
}