From 4f078c3beb960fe06f4d8b44b9c913e2aacf7625 Mon Sep 17 00:00:00 2001 From: Jos Dehaes Date: Wed, 13 Oct 2021 23:38:27 +0200 Subject: [PATCH] more temperature (M1 + intel) --- src/osx/btop_collect.cpp | 64 ++++++++++--- src/osx/sensors.cpp | 94 +++++++++--------- src/osx/smc.hpp | 199 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 298 insertions(+), 59 deletions(-) create mode 100644 src/osx/smc.hpp diff --git a/src/osx/btop_collect.cpp b/src/osx/btop_collect.cpp index 12ce949..ba37c3c 100644 --- a/src/osx/btop_collect.cpp +++ b/src/osx/btop_collect.cpp @@ -17,8 +17,6 @@ tab-size = 4 */ #include #include -#include -#include #include #include #include @@ -51,6 +49,7 @@ tab-size = 4 #include #include "sensors.hpp" +#include "smc.hpp" using std::clamp, std::string_literals::operator""s, std::cmp_equal, std::cmp_less, std::cmp_greater; using std::ifstream, std::numeric_limits, std::streamsize, std::round, std::max, std::min; @@ -107,7 +106,7 @@ namespace Shared { fs::path passwd_path; uint64_t totalMem; - long pageSize, clkTck, coreCount; + long pageSize, clkTck, coreCount, physicalCoreCount; int totalMem_len; void init() { @@ -119,6 +118,11 @@ namespace Shared { Logger::warning("Could not determine number of cores, defaulting to 1."); } + size_t physicalCoreCountSize = sizeof(physicalCoreCount); + if (sysctlbyname("hw.physicalcpu", &physicalCoreCount, &physicalCoreCountSize, NULL, 0) < 0) { + Logger::error("Could not get physical core count"); + } + pageSize = sysconf(_SC_PAGE_SIZE); if (pageSize <= 0) { pageSize = 4096; @@ -229,6 +233,20 @@ namespace Cpu { ThermalSensors sensors; if (sensors.getSensors().size() > 0) { got_sensors = true; + } else { + // try SMC (intel) + SMCConnection smcCon; + try { + long long t = smcCon.getTemp(-1); // check if we have package T + if (t > -1) { + got_sensors = true; + } else { + got_sensors = false; + } + } catch (std::runtime_error &e) { + // ignore, we don't have temp + got_sensors = false; + } } Logger::debug("got sensors:" + std::to_string(got_sensors)); } @@ -236,19 +254,37 @@ namespace Cpu { } void update_sensors() { - current_cpu.temp_max = 95; // we have no idea how to get the critical temp - ThermalSensors sensors; - auto sensor = sensors.getSensors(); + current_cpu.temp_max = 95; // we have no idea how to get the critical temp try { - current_cpu.temp.at(0).push_back((long long)sensor[0]); + ThermalSensors sensors; + auto sensor = sensors.getSensors(); + if (sensor.size() > 0) { + current_cpu.temp.at(0).push_back((long long)sensor[0]); - if (Config::getB("show_coretemp") and not cpu_temp_only) { - for (int core = 1; core <= Shared::coreCount; core++) { - long long temp = (long long) sensor[core]; - if (cmp_less(core, current_cpu.temp.size())) { - current_cpu.temp.at(core).push_back(temp); - if (current_cpu.temp.at(core).size() > 20) - current_cpu.temp.at(core).pop_front(); + if (Config::getB("show_coretemp") and not cpu_temp_only) { + for (int core = 1; core <= Shared::coreCount; core++) { + long long temp = (long long)sensor[core]; + if (cmp_less(core, current_cpu.temp.size())) { + current_cpu.temp.at(core).push_back(temp); + if (current_cpu.temp.at(core).size() > 20) + current_cpu.temp.at(core).pop_front(); + } + } + } + } else { + SMCConnection smcCon; + int threadsPerCore = Shared::coreCount / Shared::physicalCoreCount; + long long packageT = smcCon.getTemp(-1); // -1 returns package T + current_cpu.temp.at(0).push_back(packageT); + + if (Config::getB("show_coretemp") and not cpu_temp_only) { + for (int core = 0; core < Shared::coreCount; core++) { + long long temp = smcCon.getTemp(core / threadsPerCore); // same temp for all threads of same physical core + if (cmp_less(core + 1, current_cpu.temp.size())) { + current_cpu.temp.at(core + 1).push_back(temp); + if (current_cpu.temp.at(core + 1).size() > 20) + current_cpu.temp.at(core + 1).pop_front(); + } } } } diff --git a/src/osx/sensors.cpp b/src/osx/sensors.cpp index 8451bd1..38b466b 100644 --- a/src/osx/sensors.cpp +++ b/src/osx/sensors.cpp @@ -3,12 +3,11 @@ #include #include +#include #include #include #include -#include - extern "C" { typedef struct __IOHIDEvent *IOHIDEventRef; typedef struct __IOHIDServiceClient *IOHIDServiceClientRef; @@ -45,25 +44,28 @@ CFDictionaryRef matching(int page, int usage) { CFArrayRef getProductNames(CFDictionaryRef sensors) { IOHIDEventSystemClientRef system = IOHIDEventSystemClientCreate(kCFAllocatorDefault); // in CFBase.h = NULL - // ... this is the same as using kCFAllocatorDefault or the return value from CFAllocatorGetDefault() - IOHIDEventSystemClientSetMatching(system, sensors); - CFArrayRef matchingsrvs = IOHIDEventSystemClientCopyServices(system); // matchingsrvs = matching services + if (system) { + IOHIDEventSystemClientSetMatching(system, sensors); + CFArrayRef matchingsrvs = IOHIDEventSystemClientCopyServices(system); // matchingsrvs = matching services + if (matchingsrvs) { + long count = CFArrayGetCount(matchingsrvs); + CFMutableArrayRef array = CFArrayCreateMutable(kCFAllocatorDefault, 0, &kCFTypeArrayCallBacks); - long count = CFArrayGetCount(matchingsrvs); - CFMutableArrayRef array = CFArrayCreateMutable(kCFAllocatorDefault, 0, &kCFTypeArrayCallBacks); - - for (int i = 0; i < count; i++) { - IOHIDServiceClientRef sc = (IOHIDServiceClientRef)CFArrayGetValueAtIndex(matchingsrvs, i); - CFStringRef name = IOHIDServiceClientCopyProperty(sc, CFSTR("Product")); // here we use ...CopyProperty - if (name) { - CFArrayAppendValue(array, name); - } else { - CFArrayAppendValue(array, CFSTR("noname")); // @ gives a Ref like in "CFStringRef name" + for (int i = 0; i < count; i++) { + IOHIDServiceClientRef sc = (IOHIDServiceClientRef)CFArrayGetValueAtIndex(matchingsrvs, i); + CFStringRef name = IOHIDServiceClientCopyProperty(sc, CFSTR("Product")); // here we use ...CopyProperty + if (name) { + CFArrayAppendValue(array, name); + } else { + CFArrayAppendValue(array, CFSTR("noname")); // @ gives a Ref like in "CFStringRef name" + } + CFRelease(name); + } + return array; } - CFRelease(name); + CFRelease(system); } - CFRelease(system); - return array; + return nullptr; } CFArrayRef getThermalValues(CFDictionaryRef sensors) { @@ -99,35 +101,37 @@ unordered_flat_map Cpu::ThermalSensors::getSensors() { // thermalSensors's PrimaryUsagePage should be 0xff00 for M1 chip, instead of 0xff05 // can be checked by ioreg -lfx CFArrayRef thermalNames = getProductNames(thermalSensors); - CFArrayRef thermalValues = getThermalValues(thermalSensors); - long count = CFArrayGetCount(thermalNames); - for (int i = 0; i < count; i++) { - CFStringRef nameRef = (CFStringRef)CFArrayGetValueAtIndex(thermalNames, i); - char buf[200]; - CFStringGetCString(nameRef, buf, 200, kCFStringEncodingASCII); - std::string n(buf); - CFNumberRef value = (CFNumberRef)CFArrayGetValueAtIndex(thermalValues, i); - double temp = 0.0; - CFNumberGetValue(value, kCFNumberDoubleType, &temp); - if (n.starts_with("PMU tdie")) { - // Apple Silicon - std::string indexString = n.substr(8, 1); - int index = stoi(indexString); - cpuValues[index - 1] = temp; - } else if (n.starts_with("TC") && n[3] == 'c') { - // intel mac - std::string indexString = n.substr(2, 1); - int index = stoi(indexString); - cpuValues[index] = temp; - } else if (n == "TCAD") { - cpuValues[0] = temp; // package T for intel - } else if (n == "SOC MTR Temp Sensor0") { - cpuValues[0] = temp; // package T for Apple Silicon - } + if (thermalNames) { + CFArrayRef thermalValues = getThermalValues(thermalSensors); + long count = CFArrayGetCount(thermalNames); + for (int i = 0; i < count; i++) { + CFStringRef nameRef = (CFStringRef)CFArrayGetValueAtIndex(thermalNames, i); + char buf[200]; + CFStringGetCString(nameRef, buf, 200, kCFStringEncodingASCII); + std::string n(buf); + CFNumberRef value = (CFNumberRef)CFArrayGetValueAtIndex(thermalValues, i); + double temp = 0.0; + CFNumberGetValue(value, kCFNumberDoubleType, &temp); + if (n.starts_with("PMU tdie")) { + // Apple Silicon + std::string indexString = n.substr(8, 1); + int index = stoi(indexString); + cpuValues[index - 1] = temp; + } else if (n.starts_with("TC") && n[3] == 'c') { + // intel mac + std::string indexString = n.substr(2, 1); + int index = stoi(indexString); + cpuValues[index] = temp; + } else if (n == "TCAD") { + cpuValues[0] = temp; // package T for intel + } else if (n == "SOC MTR Temp Sensor0") { + cpuValues[0] = temp; // package T for Apple Silicon + } + } + CFRelease(thermalNames); + CFRelease(thermalValues); } CFRelease(thermalSensors); - CFRelease(thermalNames); - CFRelease(thermalValues); return cpuValues; } diff --git a/src/osx/smc.hpp b/src/osx/smc.hpp new file mode 100644 index 0000000..13070f2 --- /dev/null +++ b/src/osx/smc.hpp @@ -0,0 +1,199 @@ +#pragma once + +#include +#include +#include +#include +#include + +#define VERSION "0.01" + +#define KERNEL_INDEX_SMC 2 + +#define SMC_CMD_READ_BYTES 5 +#define SMC_CMD_WRITE_BYTES 6 +#define SMC_CMD_READ_INDEX 8 +#define SMC_CMD_READ_KEYINFO 9 +#define SMC_CMD_READ_PLIMIT 11 +#define SMC_CMD_READ_VERS 12 + +#define DATATYPE_FPE2 "fpe2" +#define DATATYPE_UINT8 "ui8 " +#define DATATYPE_UINT16 "ui16" +#define DATATYPE_UINT32 "ui32" +#define DATATYPE_SP78 "sp78" + +// key values +#define SMC_KEY_CPU_TEMP "TC0P" +#define SMC_KEY_CPU1_TEMP "TC1C" +#define SMC_KEY_CPU2_TEMP "TC2C" // etc +#define SMC_KEY_FAN0_RPM_CUR "F0Ac" + +typedef struct { + char major; + char minor; + char build; + char reserved[1]; + UInt16 release; +} SMCKeyData_vers_t; + +typedef struct { + UInt16 version; + UInt16 length; + UInt32 cpuPLimit; + UInt32 gpuPLimit; + UInt32 memPLimit; +} SMCKeyData_pLimitData_t; + +typedef struct { + UInt32 dataSize; + UInt32 dataType; + char dataAttributes; +} SMCKeyData_keyInfo_t; + +typedef char SMCBytes_t[32]; + +typedef struct { + UInt32 key; + SMCKeyData_vers_t vers; + SMCKeyData_pLimitData_t pLimitData; + SMCKeyData_keyInfo_t keyInfo; + char result; + char status; + char data8; + UInt32 data32; + SMCBytes_t bytes; +} SMCKeyData_t; + +typedef char UInt32Char_t[5]; + +typedef struct { + UInt32Char_t key; + UInt32 dataSize; + UInt32Char_t dataType; + SMCBytes_t bytes; +} SMCVal_t; + +namespace Cpu { + class SMCConnection { + io_connect_t conn; + kern_return_t result; + mach_port_t masterPort; + io_iterator_t iterator; + io_object_t device; + + public: + SMCConnection() { + IOMasterPort(MACH_PORT_NULL, &masterPort); + + CFMutableDictionaryRef matchingDictionary = IOServiceMatching("AppleSMC"); + result = IOServiceGetMatchingServices(masterPort, matchingDictionary, &iterator); + if (result != kIOReturnSuccess) { + throw std::runtime_error("failed to get AppleSMC"); + } + + device = IOIteratorNext(iterator); + IOObjectRelease(iterator); + if (device == 0) { + throw std::runtime_error("failed to get SMC device"); + } + + result = IOServiceOpen(device, mach_task_self(), 0, &conn); + IOObjectRelease(device); + if (result != kIOReturnSuccess) { + throw std::runtime_error("failed to get SMC connection"); + } + } + // core means physical core in SMC, while in core map it's cpu threads :-/ Only an issue on hackintosh? + // this means we can only get the T per physical core + // another issue with the SMC API is that the key is always 4 chars -> what with systems with more than 9 physical cores? + // no Mac models with more than 18 threads are released, so no problem so far + // according to VirtualSMC docs (hackintosh fake SMC) the enumeration follows with alphabetic chars - not implemented yet here (nor in VirtualSMC) + long long getTemp(int core) { + SMCVal_t val; + kern_return_t result; + char key[] = SMC_KEY_CPU_TEMP; + if (core >= 0) { + snprintf(key, 5, "TC%1dc", core); + } + result = SMCReadKey(key, &val); + if (result == kIOReturnSuccess) { + if (strcmp(val.dataType, DATATYPE_SP78) == 0) { + // convert sp78 value to temperature + int intValue = val.bytes[0] * 256 + (unsigned char)val.bytes[1]; + return static_cast(intValue / 256.0); + } + } + return -1; + } + virtual ~SMCConnection() { + IOServiceClose(conn); + } + private: + UInt32 _strtoul(char *str, int size, int base) { + UInt32 total = 0; + int i; + + for (i = 0; i < size; i++) { + if (base == 16) { + total += str[i] << (size - 1 - i) * 8; + } else { + total += (unsigned char)(str[i] << (size - 1 - i) * 8); + } + } + return total; + } + void _ultostr(char *str, UInt32 val) { + str[0] = '\0'; + sprintf(str, "%c%c%c%c", + (unsigned int)val >> 24, + (unsigned int)val >> 16, + (unsigned int)val >> 8, + (unsigned int)val); + } + + kern_return_t SMCCall(int index, SMCKeyData_t *inputStructure, SMCKeyData_t *outputStructure) { + size_t structureInputSize; + size_t structureOutputSize; + + structureInputSize = sizeof(SMCKeyData_t); + structureOutputSize = sizeof(SMCKeyData_t); + + return IOConnectCallStructMethod(conn, index, + // inputStructure + inputStructure, structureInputSize, + // ouputStructure + outputStructure, &structureOutputSize); + } + + kern_return_t SMCReadKey(UInt32Char_t key, SMCVal_t *val) { + kern_return_t result; + SMCKeyData_t inputStructure; + SMCKeyData_t outputStructure; + + memset(&inputStructure, 0, sizeof(SMCKeyData_t)); + memset(&outputStructure, 0, sizeof(SMCKeyData_t)); + memset(val, 0, sizeof(SMCVal_t)); + + inputStructure.key = _strtoul(key, 4, 16); + inputStructure.data8 = SMC_CMD_READ_KEYINFO; + + result = SMCCall(KERNEL_INDEX_SMC, &inputStructure, &outputStructure); + if (result != kIOReturnSuccess) + return result; + + val->dataSize = outputStructure.keyInfo.dataSize; + _ultostr(val->dataType, outputStructure.keyInfo.dataType); + inputStructure.keyInfo.dataSize = val->dataSize; + inputStructure.data8 = SMC_CMD_READ_BYTES; + + result = SMCCall(KERNEL_INDEX_SMC, &inputStructure, &outputStructure); + if (result != kIOReturnSuccess) + return result; + + memcpy(val->bytes, outputStructure.bytes, sizeof(outputStructure.bytes)); + + return kIOReturnSuccess; + } + }; +}