-
-
Notifications
You must be signed in to change notification settings - Fork 441
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add cpufreq support on Darwin for Apple Silicon machines
- Loading branch information
Showing
7 changed files
with
307 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,176 @@ | ||
#include "darwin/CpuFreq.h" | ||
|
||
#ifdef CPUFREQ_SUPPORT | ||
|
||
#include <stdlib.h> | ||
#include <string.h> | ||
#include <stdint.h> | ||
|
||
/* Implementations */ | ||
int CpuFreq_init(const Machine* machine, CpuFreqData* data) { | ||
data->existingCPUs = machine->existingCPUs; | ||
data->cluster_type_per_cpu = xCalloc(data->existingCPUs, sizeof(uint32_t)); | ||
data->frequencies = xCalloc(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 = xCalloc(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 | ||
} | ||
/* Calculate average frequency based on residency and per-performance state frequency */ | ||
double average_freq = 0.0; | ||
int64_t total_residency = 0U; | ||
for (uint32_t i = 0U; i < state_count; i++) { | ||
const int64_t residency = IOReportStateGetResidency(ch, i); | ||
total_residency += residency; | ||
/* We count idle as the smallest frequency */ | ||
average_freq += residency * cpu_frequencies->frequencies[(i == 0) ? 0 : (i - 1)]; | ||
} | ||
average_freq /= total_residency; | ||
data->frequencies[cpu_i] = average_freq / 1000000; // Convert to MHz | ||
|
||
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,66 @@ | ||
#ifndef HEADER_CpuFreq | ||
#define HEADER_CpuFreq | ||
|
||
#include "Machine.h" | ||
|
||
#if (defined(HAVE_LIBIOREPORT) && defined(HTOP_AARCH64)) | ||
#define CPUFREQ_SUPPORT | ||
|
||
#include <IOKit/IOKitLib.h> | ||
#include <CoreFoundation/CoreFoundation.h> | ||
|
||
/* Private API definitions from libIOReport*/ | ||
enum { | ||
kIOReportIterOk = 0, | ||
}; | ||
typedef struct IOReportSubscriptionRef *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(const Machine* machine, CpuFreqData* data); | ||
void CpuFreq_update(CpuFreqData* data); | ||
void CpuFreq_cleanup(CpuFreqData* data); | ||
#endif | ||
|
||
#endif |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters