osx CPU freq on apple silicon

This commit is contained in:
Jos Dehaes 2023-12-20 16:07:31 +01:00
parent a2325371d4
commit d273f21917
4 changed files with 331 additions and 8 deletions

View file

@ -152,7 +152,7 @@ else ifeq ($(PLATFORM_LC),freebsd)
else ifeq ($(PLATFORM_LC),macos)
PLATFORM_DIR := osx
THREADS := $(shell sysctl -n hw.ncpu || echo 1)
override ADDFLAGS += -framework IOKit -framework CoreFoundation -Wno-format-truncation
override ADDFLAGS += -framework IOKit -framework CoreFoundation -lIOReport -Wno-format-truncation
SU_GROUP := wheel
else
$(error $(shell printf "\033[1;91mERROR: \033[97mUnsupported platform ($(PLATFORM))\033[0m"))
@ -205,6 +205,9 @@ SOURCES += $(sort $(shell find $(SRCDIR)/$(PLATFORM_DIR) -type f -name *.$(SRCEX
SOURCE_COUNT := $(words $(SOURCES))
OBJECTS := $(patsubst $(SRCDIR)/%,$(BUILDDIR)/%,$(SOURCES:.$(SRCEXT)=.$(OBJEXT)))
ifeq ($(PLATFORM_LC),macos)
OBJECTS += $(BUILDDIR)/osx/CpuFreq.o
endif
ifeq ($(shell find $(BUILDDIR) -type f -newermt "$(DATESTAMP)" -name *.o >/dev/null 2>&1; echo $$?),0)
ifneq ($(wildcard $(BUILDDIR)/.*),)
@ -378,5 +381,12 @@ $(BUILDDIR)/%.$(OBJEXT): $(SRCDIR)/%.$(SRCEXT) | rocm_smi directories
@$(CXX) $(CXXFLAGS) $(INC) -MMD -c -o $@ $< || exit 1
@printf "\033[1;92m$$($(PROGRESS))$(P)\033[10D\033[5C-> \033[1;37m$@ \033[100D\033[38C\033[1;93m(\033[1;97m$$(du -ah $@ | cut -f1)iB\033[1;93m) \033[92m(\033[97m$$($(DATE_CMD) -d @$$(expr $$($(DATE_CMD) +%s 2>/dev/null || echo "0") - $${TSTAMP} 2>/dev/null) -u +%Mm:%Ss 2>/dev/null | sed 's/^00m://' || echo '')\033[92m)\033[0m\n"
ifeq ($(PLATFORM_LC),macos)
.ONESHELL:
$(BUILDDIR)/osx/CpuFreq.o: $(SRCDIR)/osx/CpuFreq.c
gcc -c -o $@ $(SRCDIR)/osx/CpuFreq.c
endif
#? Non-File Targets
.PHONY: all msg help pre

200
src/osx/CpuFreq.c Normal file
View file

@ -0,0 +1,200 @@
#include <Availability.h>
#if __MAC_OS_X_VERSION_MIN_REQUIRED > 101504 && defined (__clang__) && defined (__aarch64__)
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include "CpuFreq.h"
#if __MAC_OS_X_VERSION_MIN_REQUIRED < 120000
#define kIOMainPortDefault kIOMasterPortDefault
#endif
/* Implementations */
int CpuFreq_init(CpuFreqData *data) {
data->cluster_type_per_cpu = (unsigned int *)malloc(data->existingCPUs * sizeof(uint32_t));
data->frequencies = (double *)malloc(data->existingCPUs * sizeof(double));
/* Determine the cluster type for all CPUs */
char buf[128];
for (uint32_t num_cpus = 0U; num_cpus < data->existingCPUs; num_cpus++) {
snprintf(buf, sizeof(buf), "IODeviceTree:/cpus/cpu%u", num_cpus);
io_registry_entry_t cpu_io_registry_entry =
IORegistryEntryFromPath(kIOMainPortDefault, buf);
if (cpu_io_registry_entry == MACH_PORT_NULL) {
return -1;
}
CFDataRef cluster_type_ref = (CFDataRef)IORegistryEntryCreateCFProperty(
cpu_io_registry_entry, CFSTR("cluster-type"), kCFAllocatorDefault, 0U);
if (cluster_type_ref == NULL) {
IOObjectRelease(cpu_io_registry_entry);
return -1;
}
if (CFDataGetLength(cluster_type_ref) != 2) {
CFRelease(cluster_type_ref);
IOObjectRelease(cpu_io_registry_entry);
return -1;
}
UniChar cluster_type_char;
CFDataGetBytes(cluster_type_ref, CFRangeMake(0, 2),
(uint8_t *)&cluster_type_char);
CFRelease(cluster_type_ref);
uint32_t cluster_type = 0U;
switch (cluster_type_char) {
case L'E':
cluster_type = 0U;
break;
case L'P':
cluster_type = 1U;
break;
default:
/* Unknown cluster type */
IOObjectRelease(cpu_io_registry_entry);
return -1;
}
data->cluster_type_per_cpu[num_cpus] = cluster_type;
IOObjectRelease(cpu_io_registry_entry);
}
/*
* Determine frequencies for per-cluster-type performance states
* Frequencies for the "E" cluster type are stored in voltage-states1,
* frequencies for the "P" cluster type are stored in voltage-states5.
* This seems to be hardcoded.
*/
const CFStringRef voltage_states_key_per_cluster[CPUFREQ_NUM_CLUSTER_TYPES] =
{CFSTR("voltage-states1"), CFSTR("voltage-states5")};
io_registry_entry_t pmgr_registry_entry =
IORegistryEntryFromPath(kIOMainPortDefault, "IODeviceTree:/arm-io/pmgr");
if (pmgr_registry_entry == MACH_PORT_NULL) {
return -1;
}
for (size_t i = 0U; i < CPUFREQ_NUM_CLUSTER_TYPES; i++) {
CFDataRef voltage_states_ref = (CFDataRef)IORegistryEntryCreateCFProperty(
pmgr_registry_entry, voltage_states_key_per_cluster[i],
kCFAllocatorDefault, 0U);
if (voltage_states_ref == NULL) {
IOObjectRelease(pmgr_registry_entry);
return -1;
}
CpuFreqPowerStateFrequencies *cluster_frequencies =
&data->cpu_frequencies_per_cluster_type[i];
cluster_frequencies->num_frequencies =
CFDataGetLength(voltage_states_ref) / 8;
cluster_frequencies->frequencies =
(double *)malloc(cluster_frequencies->num_frequencies * sizeof(double));
const uint8_t *voltage_states_data = CFDataGetBytePtr(voltage_states_ref);
for (size_t j = 0U; j < cluster_frequencies->num_frequencies; j++) {
uint32_t freq_value;
memcpy(&freq_value, voltage_states_data + j * 8, 4);
cluster_frequencies->frequencies[j] = (65536000.0 / freq_value) * 1000000;
}
CFRelease(voltage_states_ref);
}
IOObjectRelease(pmgr_registry_entry);
/* Create subscription for CPU performance states */
CFMutableDictionaryRef channels = IOReportCopyChannelsInGroup(
CFSTR("CPU Stats"), CFSTR("CPU Core Performance States"), NULL, NULL);
if (channels == NULL) {
return -1;
}
data->subscribed_channels = NULL;
data->subscription = IOReportCreateSubscription(
NULL, channels, &data->subscribed_channels, 0U, NULL);
CFRelease(channels);
if (data->subscription == NULL) {
return -1;
}
data->prev_samples = NULL;
return 0;
}
void CpuFreq_update(CpuFreqData *data) {
CFDictionaryRef samples = IOReportCreateSamples(
data->subscription, data->subscribed_channels, NULL);
if (samples == NULL) {
return;
}
if (data->prev_samples == NULL) {
data->prev_samples = samples;
return;
}
/* Residency is cumulative, we have to diff two samples to get a current view
*/
CFDictionaryRef samples_delta =
IOReportCreateSamplesDelta(data->prev_samples, samples, NULL);
/* Iterate through performance state residencies. Index 0 is the idle
* residency, index 1-n is the per-performance state residency. */
__block uint32_t cpu_i = 0U;
IOReportIterate(samples_delta, ^(IOReportSampleRef ch) {
if (cpu_i >= data->existingCPUs) {
/* The report contains more CPUs than we know about. This should not
* happen. */
return kIOReportIterOk; // TODO: find way to possibly stop iteration early
// on error
}
const CpuFreqPowerStateFrequencies *cpu_frequencies =
&data->cpu_frequencies_per_cluster_type
[data->cluster_type_per_cpu[cpu_i]];
uint32_t state_count = IOReportStateGetCount(ch);
if (state_count != cpu_frequencies->num_frequencies + 1) {
/* The number of reported states does not match the number of previously
* queried frequencies. This should not happen. */
return kIOReportIterOk; // TODO: find way to possibly stop iteration early
// on error
}
double freq = 0.0;
double highest_residency = 0.0;
uint32_t highest_i = 0U;
for (uint32_t i = 0U; i < state_count; i++) {
const int64_t residency = IOReportStateGetResidency(ch, i);
if (residency > highest_residency) {
highest_i = i;
highest_residency = residency;
}
}
freq = cpu_frequencies->frequencies[highest_i == 0 ? 0 : highest_i - 1];
data->frequencies[cpu_i] = round(freq);
cpu_i++;
return kIOReportIterOk;
});
CFRelease(samples_delta);
CFRelease(data->prev_samples);
data->prev_samples = samples;
}
void CpuFreq_cleanup(CpuFreqData *data) {
if (data->subscription != NULL) {
CFRelease(data->subscription);
}
if (data->subscribed_channels != NULL) {
CFRelease(data->subscribed_channels);
}
if (data->prev_samples != NULL) {
CFRelease(data->prev_samples);
}
free(data->cluster_type_per_cpu);
free(data->frequencies);
for (size_t i = 0U; i < CPUFREQ_NUM_CLUSTER_TYPES; i++) {
free(data->cpu_frequencies_per_cluster_type[i].frequencies);
}
}
#endif

77
src/osx/CpuFreq.h Normal file
View file

@ -0,0 +1,77 @@
#ifndef HEADER_CpuFreq
#define HEADER_CpuFreq
#include <Availability.h>
#if __MAC_OS_X_VERSION_MIN_REQUIRED > 101504 && defined(__clang__) && defined (__aarch64__)
#include <CoreFoundation/CoreFoundation.h>
#include <IOKit/IOKitLib.h>
#include <IOKit/IOTypes.h>
/* Private API definitions from libIOReport*/
enum {
kIOReportIterOk = 0,
};
typedef struct IOReportSubscription *IOReportSubscriptionRef;
typedef CFDictionaryRef IOReportSampleRef;
typedef CFDictionaryRef IOReportChannelRef;
typedef int (^io_report_iterate_callback_t)(IOReportSampleRef ch);
extern void IOReportIterate(CFDictionaryRef samples,
io_report_iterate_callback_t callback);
extern CFMutableDictionaryRef
IOReportCopyChannelsInGroup(CFStringRef, CFStringRef, void *, void *);
extern IOReportSubscriptionRef
IOReportCreateSubscription(void *a, CFMutableDictionaryRef desiredChannels,
CFMutableDictionaryRef *subbedChannels,
uint64_t channel_id, CFTypeRef b);
extern CFDictionaryRef
IOReportCreateSamples(IOReportSubscriptionRef iorsub,
CFMutableDictionaryRef subbedChannels, CFTypeRef a);
extern uint32_t IOReportStateGetCount(IOReportChannelRef ch);
extern uint64_t IOReportStateGetResidency(IOReportChannelRef ch,
uint32_t index);
extern CFDictionaryRef IOReportCreateSamplesDelta(CFDictionaryRef prev,
CFDictionaryRef current,
CFTypeRef a);
/* Definitions */
typedef struct {
uint32_t num_frequencies;
double *frequencies;
} CpuFreqPowerStateFrequencies;
/*
* Seems to be hardcoded for now on all Apple Silicon platforms, no way to get
* it dynamically. Current cluster types are "E" for efficiency cores and "P"
* for performance cores.
*/
#define CPUFREQ_NUM_CLUSTER_TYPES 2
typedef struct {
/* Number of CPUs */
unsigned int existingCPUs;
/* existingCPUs records, containing which CPU belongs to which cluster type
* ("E": 0, "P": 1) */
uint32_t *cluster_type_per_cpu;
/* Frequencies for all power states per cluster type */
CpuFreqPowerStateFrequencies
cpu_frequencies_per_cluster_type[CPUFREQ_NUM_CLUSTER_TYPES];
/* IOReport subscription handlers */
IOReportSubscriptionRef subscription;
CFMutableDictionaryRef subscribed_channels;
/* Last IOReport sample */
CFDictionaryRef prev_samples;
/* existingCPUs records, containing last determined frequency per CPU in MHz
*/
double *frequencies;
} CpuFreqData;
int CpuFreq_init(CpuFreqData *data);
void CpuFreq_update(CpuFreqData *data);
void CpuFreq_cleanup(CpuFreqData *data);
#endif
#endif

View file

@ -50,14 +50,22 @@ tab-size = 4
#include <fstream>
#include <numeric>
#include <ranges>
#include <regex>
#include <string>
#include "../btop_config.hpp"
#include "../btop_shared.hpp"
#include "../btop_tools.hpp"
#if __MAC_OS_X_VERSION_MIN_REQUIRED < 120000
#define IOMainPort IOMasterPort
#endif
#if __MAC_OS_X_VERSION_MIN_REQUIRED > 101504
#ifdef __clang__
extern "C" {
#include "CpuFreq.h"
}
#endif
#include "sensors.hpp"
#endif
#include "smc.hpp"
@ -191,6 +199,9 @@ namespace Cpu {
bool has_battery = true;
bool macM1 = false;
tuple<int, long, string> current_bat;
#if __MAC_OS_X_VERSION_MIN_REQUIRED > 101504 && defined(__clang__) && defined (__aarch64__)
CpuFreqData data;
#endif
const array<string, 10> time_names = {"user", "nice", "system", "idle"};
@ -253,13 +264,15 @@ namespace Cpu {
Logger::debug("get_sensors(): show_coretemp=" + std::to_string(Config::getB("show_coretemp")) + " check_temp=" + std::to_string(Config::getB("check_temp")));
got_sensors = false;
if (Config::getB("show_coretemp") and Config::getB("check_temp")) {
#if __MAC_OS_X_VERSION_MIN_REQUIRED > 101504
#if __MAC_OS_X_VERSION_MIN_REQUIRED > 101504 && defined(__clang__) && defined (__aarch64__)
ThermalSensors sensors;
if (sensors.getSensors() > 0) {
Logger::debug("M1 sensors found");
got_sensors = true;
cpu_temp_only = true;
macM1 = true;
data.existingCPUs = Shared::coreCount;
CpuFreq_init(&data);
} else {
#endif
// try SMC (intel)
@ -286,7 +299,7 @@ namespace Cpu {
// ignore, we don't have temp
got_sensors = false;
}
#if __MAC_OS_X_VERSION_MIN_REQUIRED > 101504
#if __MAC_OS_X_VERSION_MIN_REQUIRED > 101504 && defined(__clang__) && defined (__aarch64__)
}
#endif
}
@ -297,7 +310,7 @@ namespace Cpu {
current_cpu.temp_max = 95; // we have no idea how to get the critical temp
try {
if (macM1) {
#if __MAC_OS_X_VERSION_MIN_REQUIRED > 101504
#if __MAC_OS_X_VERSION_MIN_REQUIRED > 101504 && defined(__clang__) && defined (__aarch64__)
ThermalSensors sensors;
current_cpu.temp.at(0).push_back(sensors.getSensors());
if (current_cpu.temp.at(0).size() > 20)
@ -324,16 +337,39 @@ namespace Cpu {
}
}
#if __MAC_OS_X_VERSION_MIN_REQUIRED > 101504 && defined(__clang__) && defined (__aarch64__)
uint32_t get_m1_cpuHz() {
uint32_t freq = 0;
CpuFreq_update(&data);
double highest = 0;
for (unsigned int i = 0; i < data.existingCPUs; i++) {
if (data.frequencies[i] > highest)
highest = data.frequencies[i];
}
return freq = (uint32_t)highest;
}
#endif
string get_cpuHz() {
unsigned int freq = 1;
uint32_t freq = 1;
size_t size = sizeof(freq);
int mib[] = {CTL_HW, HW_CPU_FREQ};
if (sysctl(mib, 2, &freq, &size, nullptr, 0) < 0) {
Logger::debug("sysctl failed");
// this fails on Apple Silicon macs. Apparently you're not allowed to know
return "";
#if __MAC_OS_X_VERSION_MIN_REQUIRED > 101504 && defined(__clang__) && defined (__aarch64__)
freq = get_m1_cpuHz();
if (freq == 0) {
// give up
#endif
return "";
#if __MAC_OS_X_VERSION_MIN_REQUIRED > 101504 && defined(__clang__) && defined (__aarch64__)
}
#endif
}
Logger::debug("freq: " + std::to_string(freq));
return std::to_string(freq / 1000.0 / 1000.0 / 1000.0).substr(0, 3);
}
@ -604,7 +640,7 @@ namespace Mem {
io_iterator_t drive_list;
mach_port_t libtop_master_port;
if (IOMasterPort(bootstrap_port, &libtop_master_port)) {
if (IOMainPort(bootstrap_port, &libtop_master_port)) {
Logger::error("errot getting master port");
return;
}