// // smc.swift // SMC // // Created by Serhiy Mytrovtsiy on 25/05/2021. // Using Swift 5.0. // Running on macOS 10.15. // // Copyright © 2021 Serhiy Mytrovtsiy. All rights reserved. // import Foundation import IOKit internal enum SMCDataType: String { case UI8 = "ui8 " case UI16 = "ui16" case UI32 = "ui32" case SP1E = "sp1e" case SP3C = "sp3c" case SP4B = "sp4b" case SP5A = "sp5a" case SPA5 = "spa5" case SP69 = "sp69" case SP78 = "sp78" case SP87 = "sp87" case SP96 = "sp96" case SPB4 = "spb4" case SPF0 = "spf0" case FLT = "flt " case FPE2 = "fpe2" case FP2E = "fp2e" case FDS = "{fds" } internal enum SMCKeys: UInt8 { case kernelIndex = 2 case readBytes = 5 case writeBytes = 6 case readIndex = 8 case readKeyInfo = 9 case readPLimit = 11 case readVers = 12 } public enum FanMode: Int, Codable { case automatic = 0 case forced = 1 } internal struct SMCKeyData_t { typealias SMCBytes_t = (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) struct vers_t { var major: CUnsignedChar = 0 var minor: CUnsignedChar = 0 var build: CUnsignedChar = 0 var reserved: CUnsignedChar = 0 var release: CUnsignedShort = 0 } struct LimitData_t { var version: UInt16 = 0 var length: UInt16 = 0 var cpuPLimit: UInt32 = 0 var gpuPLimit: UInt32 = 0 var memPLimit: UInt32 = 0 } struct keyInfo_t { var dataSize: IOByteCount32 = 0 var dataType: UInt32 = 0 var dataAttributes: UInt8 = 0 } var key: UInt32 = 0 var vers = vers_t() var pLimitData = LimitData_t() var keyInfo = keyInfo_t() var padding: UInt16 = 0 var result: UInt8 = 0 var status: UInt8 = 0 var data8: UInt8 = 0 var data32: UInt32 = 0 var bytes: SMCBytes_t = (UInt8(0), UInt8(0), UInt8(0), UInt8(0), UInt8(0), UInt8(0), UInt8(0), UInt8(0), UInt8(0), UInt8(0), UInt8(0), UInt8(0), UInt8(0), UInt8(0), UInt8(0), UInt8(0), UInt8(0), UInt8(0), UInt8(0), UInt8(0), UInt8(0), UInt8(0), UInt8(0), UInt8(0), UInt8(0), UInt8(0), UInt8(0), UInt8(0), UInt8(0), UInt8(0), UInt8(0), UInt8(0)) } internal struct SMCVal_t { var key: String var dataSize: UInt32 = 0 var dataType: String = "" var bytes: [UInt8] = Array(repeating: 0, count: 32) init(_ key: String) { self.key = key } } extension FourCharCode { init(fromString str: String) { precondition(str.count == 4) self = str.utf8.reduce(0) { sum, character in return sum << 8 | UInt32(character) } } func toString() -> String { return String(describing: UnicodeScalar(self >> 24 & 0xff)!) + String(describing: UnicodeScalar(self >> 16 & 0xff)!) + String(describing: UnicodeScalar(self >> 8 & 0xff)!) + String(describing: UnicodeScalar(self & 0xff)!) } } extension UInt16 { init(bytes: (UInt8, UInt8)) { self = UInt16(bytes.0) << 8 | UInt16(bytes.1) } } extension UInt32 { init(bytes: (UInt8, UInt8, UInt8, UInt8)) { self = UInt32(bytes.0) << 24 | UInt32(bytes.1) << 16 | UInt32(bytes.2) << 8 | UInt32(bytes.3) } } extension Int { init(fromFPE2 bytes: (UInt8, UInt8)) { self = (Int(bytes.0) << 6) + (Int(bytes.1) >> 2) } } extension Float { init?(_ bytes: [UInt8]) { self = bytes.withUnsafeBytes { return $0.load(fromByteOffset: 0, as: Self.self) } } var bytes: [UInt8] { withUnsafeBytes(of: self, Array.init) } } public class SMC { public static let shared = SMC() private var conn: io_connect_t = 0 public init() { var result: kern_return_t var iterator: io_iterator_t = 0 let device: io_object_t let matchingDictionary: CFMutableDictionary = IOServiceMatching("AppleSMC") result = IOServiceGetMatchingServices(kIOMasterPortDefault, matchingDictionary, &iterator) if result != kIOReturnSuccess { print("Error IOServiceGetMatchingServices(): " + (String(cString: mach_error_string(result), encoding: String.Encoding.ascii) ?? "unknown error")) return } device = IOIteratorNext(iterator) IOObjectRelease(iterator) if device == 0 { print("Error IOIteratorNext(): " + (String(cString: mach_error_string(result), encoding: String.Encoding.ascii) ?? "unknown error")) return } result = IOServiceOpen(device, mach_task_self_, 0, &conn) IOObjectRelease(device) if result != kIOReturnSuccess { print("Error IOServiceOpen(): " + (String(cString: mach_error_string(result), encoding: String.Encoding.ascii) ?? "unknown error")) return } } deinit { let result = self.close() if result != kIOReturnSuccess { print("error close smc connection: " + (String(cString: mach_error_string(result), encoding: String.Encoding.ascii) ?? "unknown error")) } } public func close() -> kern_return_t { return IOServiceClose(conn) } public func getValue(_ key: String) -> Double? { var result: kern_return_t = 0 var val: SMCVal_t = SMCVal_t(key) result = read(&val) if result != kIOReturnSuccess { print("Error read(\(key)): " + (String(cString: mach_error_string(result), encoding: String.Encoding.ascii) ?? "unknown error")) return nil } if val.dataSize > 0 { if val.bytes.first(where: { $0 != 0 }) == nil && val.key != "FS! " && val.key != "F0Md" && val.key != "F1Md" { return nil } switch val.dataType { case SMCDataType.UI8.rawValue: return Double(val.bytes[0]) case SMCDataType.UI16.rawValue: return Double(UInt16(bytes: (val.bytes[0], val.bytes[1]))) case SMCDataType.UI32.rawValue: return Double(UInt32(bytes: (val.bytes[0], val.bytes[1], val.bytes[2], val.bytes[3]))) case SMCDataType.SP1E.rawValue: let result: Double = Double(UInt16(val.bytes[0]) * 256 + UInt16(val.bytes[1])) return Double(result / 16384) case SMCDataType.SP3C.rawValue: let result: Double = Double(UInt16(val.bytes[0]) * 256 + UInt16(val.bytes[1])) return Double(result / 4096) case SMCDataType.SP4B.rawValue: let result: Double = Double(UInt16(val.bytes[0]) * 256 + UInt16(val.bytes[1])) return Double(result / 2048) case SMCDataType.SP5A.rawValue: let result: Double = Double(UInt16(val.bytes[0]) * 256 + UInt16(val.bytes[1])) return Double(result / 1024) case SMCDataType.SP69.rawValue: let result: Double = Double(UInt16(val.bytes[0]) * 256 + UInt16(val.bytes[1])) return Double(result / 512) case SMCDataType.SP78.rawValue: let intValue: Double = Double(Int(val.bytes[0]) * 256 + Int(val.bytes[1])) return Double(intValue / 256) case SMCDataType.SP87.rawValue: let intValue: Double = Double(Int(val.bytes[0]) * 256 + Int(val.bytes[1])) return Double(intValue / 128) case SMCDataType.SP96.rawValue: let intValue: Double = Double(Int(val.bytes[0]) * 256 + Int(val.bytes[1])) return Double(intValue / 64) case SMCDataType.SPA5.rawValue: let result: Double = Double(UInt16(val.bytes[0]) * 256 + UInt16(val.bytes[1])) return Double(result / 32) case SMCDataType.SPB4.rawValue: let intValue: Double = Double(Int(val.bytes[0]) * 256 + Int(val.bytes[1])) return Double(intValue / 16) case SMCDataType.SPF0.rawValue: let intValue: Double = Double(Int(val.bytes[0]) * 256 + Int(val.bytes[1])) return intValue case SMCDataType.FLT.rawValue: let value: Float? = Float(val.bytes) if value != nil { return Double(value!) } return nil case SMCDataType.FPE2.rawValue: return Double(Int(fromFPE2: (val.bytes[0], val.bytes[1]))) default: return nil } } return nil } public func getStringValue(_ key: String) -> String? { var result: kern_return_t = 0 var val: SMCVal_t = SMCVal_t(key) result = read(&val) if result != kIOReturnSuccess { print("Error read(): " + (String(cString: mach_error_string(result), encoding: String.Encoding.ascii) ?? "unknown error")) return nil } if val.dataSize > 0 { if val.bytes.first(where: { $0 != 0}) == nil { return nil } switch val.dataType { case SMCDataType.FDS.rawValue: let c1 = String(UnicodeScalar(val.bytes[4])) let c2 = String(UnicodeScalar(val.bytes[5])) let c3 = String(UnicodeScalar(val.bytes[6])) let c4 = String(UnicodeScalar(val.bytes[7])) let c5 = String(UnicodeScalar(val.bytes[8])) let c6 = String(UnicodeScalar(val.bytes[9])) let c7 = String(UnicodeScalar(val.bytes[10])) let c8 = String(UnicodeScalar(val.bytes[11])) let c9 = String(UnicodeScalar(val.bytes[12])) let c10 = String(UnicodeScalar(val.bytes[13])) let c11 = String(UnicodeScalar(val.bytes[14])) let c12 = String(UnicodeScalar(val.bytes[15])) return (c1 + c2 + c3 + c4 + c5 + c6 + c7 + c8 + c9 + c10 + c11 + c12).trimmingCharacters(in: .whitespaces) default: print("unsupported data type \(val.dataType) for key: \(key)") return nil } } return nil } public func getAllKeys() -> [String] { var list: [String] = [] let keysNum: Double? = self.getValue("#KEY") if keysNum == nil { print("ERROR no keys count found") return list } var result: kern_return_t = 0 var input: SMCKeyData_t = SMCKeyData_t() var output: SMCKeyData_t = SMCKeyData_t() for i in 0...Int(keysNum!) { input = SMCKeyData_t() output = SMCKeyData_t() input.data8 = SMCKeys.readIndex.rawValue input.data32 = UInt32(i) result = call(SMCKeys.kernelIndex.rawValue, input: &input, output: &output) if result != kIOReturnSuccess { continue } list.append(output.key.toString()) } return list } public func write(_ key: String, _ newValue: Int) -> kern_return_t { var value = SMCVal_t(key) value.dataSize = 2 value.bytes = [UInt8(newValue >> 6), UInt8((newValue << 2) ^ ((newValue >> 6) << 8)), UInt8(0), UInt8(0), UInt8(0), UInt8(0), UInt8(0), UInt8(0), UInt8(0), UInt8(0), UInt8(0), UInt8(0), UInt8(0), UInt8(0), UInt8(0), UInt8(0), UInt8(0), UInt8(0), UInt8(0), UInt8(0), UInt8(0), UInt8(0), UInt8(0), UInt8(0), UInt8(0), UInt8(0), UInt8(0), UInt8(0), UInt8(0), UInt8(0), UInt8(0), UInt8(0)] return write(value) } // MARK: - fans public func setFanMode(_ id: Int, mode: FanMode) { if self.getValue("F\(id)Md") != nil { var result: kern_return_t = 0 var value = SMCVal_t("F\(id)Md") result = read(&value) if result != kIOReturnSuccess { print("Error read fan mode: " + (String(cString: mach_error_string(result), encoding: String.Encoding.ascii) ?? "unknown error")) return } value.bytes = [UInt8(mode.rawValue), UInt8(0), UInt8(0), UInt8(0), UInt8(0), UInt8(0), UInt8(0), UInt8(0), UInt8(0), UInt8(0), UInt8(0), UInt8(0), UInt8(0), UInt8(0), UInt8(0), UInt8(0), UInt8(0), UInt8(0), UInt8(0), UInt8(0), UInt8(0), UInt8(0), UInt8(0), UInt8(0), UInt8(0), UInt8(0), UInt8(0), UInt8(0), UInt8(0), UInt8(0), UInt8(0), UInt8(0)] result = write(value) if result != kIOReturnSuccess { print("Error write: " + (String(cString: mach_error_string(result), encoding: String.Encoding.ascii) ?? "unknown error")) return } } let fansMode = Int(self.getValue("FS! ") ?? 0) var newMode: UInt8 = 0 if fansMode == 0 && id == 0 && mode == .forced { newMode = 1 } else if fansMode == 0 && id == 1 && mode == .forced { newMode = 2 } else if fansMode == 1 && id == 0 && mode == .automatic { newMode = 0 } else if fansMode == 1 && id == 1 && mode == .forced { newMode = 3 } else if fansMode == 2 && id == 1 && mode == .automatic { newMode = 0 } else if fansMode == 2 && id == 0 && mode == .forced { newMode = 3 } else if fansMode == 3 && id == 0 && mode == .automatic { newMode = 2 } else if fansMode == 3 && id == 1 && mode == .automatic { newMode = 1 } if fansMode == newMode { return } var result: kern_return_t = 0 var value = SMCVal_t("FS! ") result = read(&value) if result != kIOReturnSuccess { print("Error read fan mode: " + (String(cString: mach_error_string(result), encoding: String.Encoding.ascii) ?? "unknown error")) return } value.bytes = [0, newMode, UInt8(0), UInt8(0), UInt8(0), UInt8(0), UInt8(0), UInt8(0), UInt8(0), UInt8(0), UInt8(0), UInt8(0), UInt8(0), UInt8(0), UInt8(0), UInt8(0), UInt8(0), UInt8(0), UInt8(0), UInt8(0), UInt8(0), UInt8(0), UInt8(0), UInt8(0), UInt8(0), UInt8(0), UInt8(0), UInt8(0), UInt8(0), UInt8(0), UInt8(0), UInt8(0)] result = write(value) if result != kIOReturnSuccess { print("Error write: " + (String(cString: mach_error_string(result), encoding: String.Encoding.ascii) ?? "unknown error")) return } } public func setFanSpeed(_ id: Int, speed: Int) { let maxSpeed = Int(self.getValue("F\(id)Mx") ?? 4000) if speed > maxSpeed { print("new fan speed (\(speed)) is more than maximum speed (\(maxSpeed))") return } var result: kern_return_t = 0 var value = SMCVal_t("F\(id)Tg") result = read(&value) if result != kIOReturnSuccess { print("Error read fan value: " + (String(cString: mach_error_string(result), encoding: String.Encoding.ascii) ?? "unknown error")) return } if value.dataType == "flt " { let bytes = Float(speed).bytes value.bytes[0] = bytes[0] value.bytes[1] = bytes[1] value.bytes[2] = bytes[2] value.bytes[3] = bytes[3] } else if value.dataType == "fpe2" { value.bytes[0] = UInt8(speed >> 6) value.bytes[1] = UInt8((speed << 2) ^ ((speed >> 6) << 8)) value.bytes[2] = UInt8(0) value.bytes[3] = UInt8(0) } result = write(value) if result != kIOReturnSuccess { print("Error write: " + (String(cString: mach_error_string(result), encoding: String.Encoding.ascii) ?? "unknown error")) return } } public func resetFans() { var value = SMCVal_t("FS! ") value.dataSize = 2 let result = write(value) if result != kIOReturnSuccess { print("Error write: " + (String(cString: mach_error_string(result), encoding: String.Encoding.ascii) ?? "unknown error")) } } // MARK: - internal functions private func read(_ value: UnsafeMutablePointer) -> kern_return_t { var result: kern_return_t = 0 var input = SMCKeyData_t() var output = SMCKeyData_t() input.key = FourCharCode(fromString: value.pointee.key) input.data8 = SMCKeys.readKeyInfo.rawValue result = call(SMCKeys.kernelIndex.rawValue, input: &input, output: &output) if result != kIOReturnSuccess { return result } value.pointee.dataSize = UInt32(output.keyInfo.dataSize) value.pointee.dataType = output.keyInfo.dataType.toString() input.keyInfo.dataSize = output.keyInfo.dataSize input.data8 = SMCKeys.readBytes.rawValue result = call(SMCKeys.kernelIndex.rawValue, input: &input, output: &output) if result != kIOReturnSuccess { return result } memcpy(&value.pointee.bytes, &output.bytes, Int(value.pointee.dataSize)) return kIOReturnSuccess } private func write(_ value: SMCVal_t) -> kern_return_t { var input = SMCKeyData_t() var output = SMCKeyData_t() input.key = FourCharCode(fromString: value.key) input.data8 = SMCKeys.writeBytes.rawValue input.keyInfo.dataSize = IOByteCount32(value.dataSize) input.bytes = (value.bytes[0], value.bytes[1], value.bytes[2], value.bytes[3], value.bytes[4], value.bytes[5], value.bytes[6], value.bytes[7], value.bytes[8], value.bytes[9], value.bytes[10], value.bytes[11], value.bytes[12], value.bytes[13], value.bytes[14], value.bytes[15], value.bytes[16], value.bytes[17], value.bytes[18], value.bytes[19], value.bytes[20], value.bytes[21], value.bytes[22], value.bytes[23], value.bytes[24], value.bytes[25], value.bytes[26], value.bytes[27], value.bytes[28], value.bytes[29], value.bytes[30], value.bytes[31]) let result = self.call(SMCKeys.kernelIndex.rawValue, input: &input, output: &output) if result != kIOReturnSuccess { return result } return kIOReturnSuccess } private func call(_ index: UInt8, input: inout SMCKeyData_t, output: inout SMCKeyData_t) -> kern_return_t { let inputSize = MemoryLayout.stride var outputSize = MemoryLayout.stride return IOConnectCallStructMethod(conn, UInt32(index), &input, inputSize, &output, &outputSize) } }