From fbae907720afbae47162666b6b0aea974be80c07 Mon Sep 17 00:00:00 2001 From: Jos Dehaes Date: Fri, 15 Oct 2021 23:41:37 +0200 Subject: [PATCH] temperature sensors via IOKit --- src/osx/btop_collect.cpp | 167 ++++----------------------------------- src/osx/sensors.cpp | 123 ++++++++++++++++++++++++++++ src/osx/sensors.hpp | 8 ++ src/osx/smc.hpp | 69 ---------------- 4 files changed, 148 insertions(+), 219 deletions(-) create mode 100644 src/osx/sensors.cpp create mode 100644 src/osx/sensors.hpp delete mode 100644 src/osx/smc.hpp diff --git a/src/osx/btop_collect.cpp b/src/osx/btop_collect.cpp index c3da4e7..8ddf52e 100644 --- a/src/osx/btop_collect.cpp +++ b/src/osx/btop_collect.cpp @@ -50,7 +50,7 @@ tab-size = 4 #include #include -#include "smc.hpp" +#include "sensors.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 +107,7 @@ namespace Shared { fs::path passwd_path; uint64_t totalMem; - long pageSize, clkTck, coreCount, physicalCoreCount; + long pageSize, clkTck, coreCount; int totalMem_len; void init() { @@ -119,11 +119,6 @@ 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; @@ -227,159 +222,31 @@ namespace Cpu { return name; } - 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; - } - }; - bool get_sensors() { - 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("get_sensors"); + got_sensors = false; + ThermalSensors sensors; + if (sensors.getSensors().size() > 0) { + got_sensors = true; } + Logger::debug("got sensors:" + std::to_string(got_sensors)); return got_sensors; } void update_sensors() { current_cpu.temp_max = 95; // we have no idea how to get the critical temp - SMCConnection smcCon; - int threadsPerCore = Shared::coreCount / Shared::physicalCoreCount; + ThermalSensors sensors; + std::map sensor = sensors.getSensors(); try { - long long packageT = smcCon.getTemp(-1); // -1 returns package T - current_cpu.temp.at(0).push_back(packageT); + current_cpu.temp.at(0).push_back((long long)sensor[0]); 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(); + 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(); } } } diff --git a/src/osx/sensors.cpp b/src/osx/sensors.cpp new file mode 100644 index 0000000..1b65754 --- /dev/null +++ b/src/osx/sensors.cpp @@ -0,0 +1,123 @@ +#include "sensors.hpp" + +#include +#include + +#include +#include +#include + +extern "C" { +typedef struct __IOHIDEvent *IOHIDEventRef; +typedef struct __IOHIDServiceClient *IOHIDServiceClientRef; +#ifdef __LP64__ +typedef double IOHIDFloat; +#else +typedef float IOHIDFloat; +#endif + +#define IOHIDEventFieldBase(type) (type << 16) +#define kIOHIDEventTypeTemperature 15 + +IOHIDEventSystemClientRef IOHIDEventSystemClientCreate(CFAllocatorRef allocator); +int IOHIDEventSystemClientSetMatching(IOHIDEventSystemClientRef client, CFDictionaryRef match); +int IOHIDEventSystemClientSetMatchingMultiple(IOHIDEventSystemClientRef client, CFArrayRef match); +IOHIDEventRef IOHIDServiceClientCopyEvent(IOHIDServiceClientRef, int64_t, int32_t, int64_t); +CFStringRef IOHIDServiceClientCopyProperty(IOHIDServiceClientRef service, CFStringRef property); +IOHIDFloat IOHIDEventGetFloatValue(IOHIDEventRef event, int32_t field); + +// create a dict ref, like for temperature sensor {"PrimaryUsagePage":0xff00, "PrimaryUsage":0x5} +CFDictionaryRef matching(int page, int usage) { + CFNumberRef nums[2]; + CFStringRef keys[2]; + + keys[0] = CFStringCreateWithCString(0, "PrimaryUsagePage", 0); + keys[1] = CFStringCreateWithCString(0, "PrimaryUsage", 0); + nums[0] = CFNumberCreate(0, kCFNumberSInt32Type, &page); + nums[1] = CFNumberCreate(0, kCFNumberSInt32Type, &usage); + + CFDictionaryRef dict = CFDictionaryCreate(0, (const void **)keys, (const void **)nums, 2, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); + return dict; +} + +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 + + 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" + } + } + return array; +} + +CFArrayRef getThermalValues(CFDictionaryRef sensors) { + IOHIDEventSystemClientRef system = IOHIDEventSystemClientCreate(kCFAllocatorDefault); + IOHIDEventSystemClientSetMatching(system, sensors); + CFArrayRef matchingsrvs = IOHIDEventSystemClientCopyServices(system); + + long count = CFArrayGetCount(matchingsrvs); + CFMutableArrayRef array = CFArrayCreateMutable(kCFAllocatorDefault, 0, &kCFTypeArrayCallBacks); + + for (int i = 0; i < count; i++) { + IOHIDServiceClientRef sc = (IOHIDServiceClientRef)CFArrayGetValueAtIndex(matchingsrvs, i); + IOHIDEventRef event = IOHIDServiceClientCopyEvent(sc, kIOHIDEventTypeTemperature, 0, 0); // here we use ...CopyEvent + + CFNumberRef value; + if (event != 0) { + double temp = IOHIDEventGetFloatValue(event, IOHIDEventFieldBase(kIOHIDEventTypeTemperature)); + value = CFNumberCreate(kCFAllocatorDefault, kCFNumberDoubleType, &temp); + } else { + double temp = 0; + value = CFNumberCreate(kCFAllocatorDefault, kCFNumberDoubleType, &temp); + } + CFArrayAppendValue(array, value); + } + return array; +} +} +std::map Cpu::ThermalSensors::getSensors() { + std::map cpuValues; + CFDictionaryRef thermalSensors = matching(0xff00, 5); // 65280_10 = FF00_16 + // 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 + } + } + CFRelease(thermalValues); + return cpuValues; +} diff --git a/src/osx/sensors.hpp b/src/osx/sensors.hpp new file mode 100644 index 0000000..0cf7159 --- /dev/null +++ b/src/osx/sensors.hpp @@ -0,0 +1,8 @@ +#include + +namespace Cpu { + class ThermalSensors { + public: + std::map getSensors(); + }; +} // namespace Cpu diff --git a/src/osx/smc.hpp b/src/osx/smc.hpp deleted file mode 100644 index 0b851ad..0000000 --- a/src/osx/smc.hpp +++ /dev/null @@ -1,69 +0,0 @@ -#pragma once - -#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; \ No newline at end of file