Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[v1.06][ARM] Add Windows on Arm support #273

Merged
merged 17 commits into from
Sep 10, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 20 additions & 5 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -70,12 +70,27 @@ $(error Aborting compilation)

OUTPUT=cpufetch
else
# Assume x86_64
arch := $(shell cc -dumpmachine)
arch := $(firstword $(subst -, ,$(arch)))

ifeq ($(arch), $(filter $(arch), x86_64 amd64 i386 i486 i586 i686))
SRC_DIR=src/x86/
SOURCE += $(COMMON_SRC) $(SRC_DIR)cpuid.c $(SRC_DIR)apic.c $(SRC_DIR)cpuid_asm.c $(SRC_DIR)uarch.c
HEADERS += $(COMMON_HDR) $(SRC_DIR)cpuid.h $(SRC_DIR)apic.h $(SRC_DIR)cpuid_asm.h $(SRC_DIR)uarch.h
CFLAGS += -DARCH_X86 -std=c99
else ifeq ($(arch), $(filter $(arch), arm aarch64_be aarch64 arm64 armv8b armv8l armv7l armv6l))
SRC_DIR=src/arm/
SOURCE += $(COMMON_SRC) $(SRC_DIR)midr.c $(SRC_DIR)uarch.c $(SRC_COMMON)soc.c $(SRC_DIR)soc.c $(SRC_COMMON)pci.c $(SRC_DIR)udev.c sve.o
HEADERS += $(COMMON_HDR) $(SRC_DIR)midr.h $(SRC_DIR)uarch.h $(SRC_COMMON)soc.h $(SRC_DIR)soc.h $(SRC_COMMON)pci.h $(SRC_DIR)udev.c $(SRC_DIR)socs.h
CFLAGS += -DARCH_ARM -std=c99
else
# Error lines should not be tabulated because Makefile complains about it
$(warning Unsupported arch detected: $(arch). See https://github.com/Dr-Noob/cpufetch#1-support)
$(warning If your architecture is supported but the compilation fails, please open an issue in https://github.com/Dr-Noob/cpufetch/issues)
$(error Aborting compilation)
endif

GIT_VERSION := ""
SRC_DIR=src/x86/
SOURCE += $(COMMON_SRC) $(SRC_DIR)cpuid.c $(SRC_DIR)apic.c $(SRC_DIR)cpuid_asm.c $(SRC_DIR)uarch.c
HEADERS += $(COMMON_HDR) $(SRC_DIR)cpuid.h $(SRC_DIR)apic.h $(SRC_DIR)cpuid_asm.h $(SRC_DIR)uarch.h
CFLAGS += -DARCH_X86 -std=c99
SANITY_FLAGS += -Wno-pedantic-ms-format
OUTPUT=cpufetch.exe
endif
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ cpufetch is a command-line tool written in C that displays the CPU information i
| OS | x86_64 / x86 | ARM | RISC-V | PowerPC |
|:-----------:|:------------------:|:------------------:|:------------------:|:------------------:|
| GNU / Linux | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: |
| Windows | :heavy_check_mark: | :x: | :x: | :x: |
| Windows | :heavy_check_mark: | :heavy_check_mark: | :x: | :x: |
| Android | :heavy_check_mark: | :heavy_check_mark: | :x: | :x: |
| macOS | :heavy_check_mark: | :heavy_check_mark: | :x: | :heavy_check_mark: |
| FreeBSD | :heavy_check_mark: | :x: | :x: | :x: |
Expand Down
162 changes: 162 additions & 0 deletions src/arm/midr.c
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,10 @@
#include "../common/freq.h"
#elif defined __APPLE__ || __MACH__
#include "../common/sysctl.h"
#elif defined _WIN32
#define WIN32_LEAN_AND_MEAN
#define NOMINMAX
#include <windows.h>
#endif

#include "../common/global.h"
Expand All @@ -21,6 +25,60 @@
#include "uarch.h"
#include "sve.h"


#if defined _WIN32
// Windows stores processor information in registery at:
// "HKEY_LOCAL_MACHINE\HARDWARE\DESCRIPTION\System\CentralProcessor"
// Within this directory, each core will get its own folder with
// registery entries named `CP ####` that map to ARM system registers.
// Ex. the MIDR register for core 0 is the `REG_QWORD` at:
// "HKEY_LOCAL_MACHINE\HARDWARE\DESCRIPTION\System\CentralProcessor\0\CP 4000"
// The name of these `CP ####`-registers follow their register ID encoding in hexadecimal
// (op0&1):op1:crn:crm:op2.
// More registers can be found here:
// https://developer.arm.com/documentation/ddi0601/2024-06/AArch64-Registers
// Some important ones:
// CP 4000: MIDR_EL1
// CP 4020: ID_AA64PFR0_EL1
// CP 4021: ID_AA64PFR1_EL1
// CP 4028: ID_AA64DFR0_EL1
// CP 4029: ID_AA64DFR1_EL1
// CP 402C: ID_AA64AFR0_EL1
// CP 402D: ID_AA64AFR1_EL1
// CP 4030: ID_AA64ISAR0_EL1
// CP 4031: ID_AA64ISAR1_EL1
// CP 4038: ID_AA64MMFR0_EL1
// CP 4039: ID_AA64MMFR1_EL1
// CP 403A: ID_AA64MMFR2_EL1

bool read_registry_hklm_int(char* path, char* name, void* value, bool is64) {
DWORD value_len;
int reg_type;
if (is64) {
value_len = sizeof(int64_t);
reg_type = RRF_RT_REG_QWORD;
}
else {
value_len = sizeof(int32_t);
reg_type = RRF_RT_REG_DWORD;
}

if(RegGetValueA(HKEY_LOCAL_MACHINE, path, name, reg_type, NULL, value, &value_len) != ERROR_SUCCESS) {
printBug("Error reading registry entry \"%s\\%s\"", path, name);
return false;
}
return true;
}

bool get_win32_core_info_int(uint32_t core_index, char* name, void* value, bool is64) {
// path + digits
uint32_t max_path_size = 45+3+1;
char* path = ecalloc(sizeof(char) * max_path_size, sizeof(char));
snprintf(path, max_path_size, "HARDWARE\\DESCRIPTION\\System\\CentralProcessor\\%u", core_index);
return read_registry_hklm_int(path, name, value, is64);
}
#endif

bool cores_are_equal(int c1pos, int c2pos, uint32_t* midr_array, int32_t* freq_array) {
return midr_array[c1pos] == midr_array[c2pos] && freq_array[c1pos] == freq_array[c2pos];
}
Expand Down Expand Up @@ -208,6 +266,46 @@ struct features* get_features_info(void) {
feat->NEON = true;
feat->SVE = false;
feat->SVE2 = false;
#elif defined _WIN32

// CP 4020 maps to the ID_AA64PFR0_EL1 register on Windows
// https://developer.arm.com/documentation/ddi0601/2024-06/AArch64-Registers/ID-AA64PFR0-EL1--AArch64-Processor-Feature-Register-0
int64_t pfr0 = 0;
if(!get_win32_core_info_int(0, "CP 4020", &pfr0, true)) {
printWarn("Unable to retrieve PFR0 via registry");
}
else {
// AdvSimd[23:20]
// -1: Not available
// 0: AdvSimd support
// 1: AdvSimd support + FP16
int8_t adv_simd = ((int64_t)(pfr0 << (60 - 20)) >> 60);
feat->NEON = (adv_simd >= 0);

// SVE[35:32]
feat->SVE = (pfr0 >> 32) & 0xF ? true : false;
}

// Windoes does not expose a registry entry for the ID_AA64ZFR0_EL1 register
// this would have mapped to "CP 4024".
feat->SVE2 = false;

// CP 4030 maps to the ID_AA64ISAR0_EL1 register on Windows
// https://developer.arm.com/documentation/ddi0601/2024-06/AArch64-Registers/ID-AA64ISAR0-EL1--AArch64-Instruction-Set-Attribute-Register-0
int64_t isar0 = 0;
if(!get_win32_core_info_int(0, "CP 4030", &isar0, true)) {
printWarn("Unable to retrieve ISAR0 via registry");
}
else {
// AES[7:4]
feat->AES = (isar0 >> 4) & 0xF ? true : false;
// SHA1[11:8]
feat->SHA1 = (isar0 >> 8) & 0xF ? true : false;
// SHA2[15:12]
feat->SHA2 = (isar0 >> 12) & 0xF ? true : false;
// CRC32[19:16]
feat->CRC32 = (isar0 >> 16) & 0xF ? true : false;
}
#endif // ifdef __linux__

if (feat->SVE || feat->SVE2) {
Expand Down Expand Up @@ -428,6 +526,68 @@ struct cpuInfo* get_cpu_info_mach(struct cpuInfo* cpu) {

return cpu;
}
#elif defined _WIN32
struct cpuInfo* get_cpu_info_windows(struct cpuInfo* cpu) {
init_cpu_info(cpu);

SYSTEM_INFO sys_info;
GetSystemInfo(&sys_info);
int ncores = sys_info.dwNumberOfProcessors;

uint32_t* midr_array = emalloc(sizeof(uint32_t) * ncores);
int32_t* freq_array = emalloc(sizeof(uint32_t) * ncores);
uint32_t* ids_array = emalloc(sizeof(uint32_t) * ncores);
for(int i=0; i < ncores; i++) {
// Cast from 64 to 32 bit to be able to re-use the pre-existing
// functions such as fill_ids_from_midr and cores_are_equal
int64_t midr_64;
if(!get_win32_core_info_int(i, "CP 4000", &midr_64, true)) {
return NULL;
}
midr_array[i] = midr_64;
if(!get_win32_core_info_int(i, "~MHz", &freq_array[i], false)) {
return NULL;
}
}

uint32_t sockets = fill_ids_from_midr(midr_array, freq_array, ids_array, ncores);

struct cpuInfo* ptr = cpu;
int midr_idx = 0;
int tmp_midr_idx = 0;
for(uint32_t i=0; i < sockets; i++) {
if(i > 0) {
ptr->next_cpu = emalloc(sizeof(struct cpuInfo));
ptr = ptr->next_cpu;
init_cpu_info(ptr);

tmp_midr_idx = midr_idx;
while(cores_are_equal(midr_idx, tmp_midr_idx, midr_array, freq_array)) tmp_midr_idx++;
midr_idx = tmp_midr_idx;
}

ptr->midr = midr_array[midr_idx];
ptr->arch = get_uarch_from_midr(ptr->midr, ptr);

ptr->feat = get_features_info();

ptr->freq = emalloc(sizeof(struct frequency));
ptr->freq->measured = false;
ptr->freq->base = freq_array[midr_idx];
ptr->freq->max = UNKNOWN_DATA;

ptr->cach = get_cache_info(ptr);
ptr->topo = get_topology_info(ptr, ptr->cach, midr_array, freq_array, i, ncores);
}

cpu->num_cpus = sockets;
cpu->hv = emalloc(sizeof(struct hypervisor));
cpu->hv->present = false;
cpu->soc = get_soc(cpu);
cpu->peak_performance = get_peak_performance(cpu);

return cpu;
}
#endif

struct cpuInfo* get_cpu_info(void) {
Expand All @@ -438,6 +598,8 @@ struct cpuInfo* get_cpu_info(void) {
return get_cpu_info_linux(cpu);
#elif defined __APPLE__ || __MACH__
return get_cpu_info_mach(cpu);
#elif defined _WIN32
return get_cpu_info_windows(cpu);
#endif
}

Expand Down
43 changes: 42 additions & 1 deletion src/arm/soc.c
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,28 @@
#include "../common/sysctl.h"
#endif

#if defined(_WIN32)
#define WIN32_LEAN_AND_MEAN
#define NOMINMAX
#include <windows.h>

// Gets a RRF_RT_REG_SZ-entry from the Windows registry, returning a newly allocated
// string and its length
bool read_registry_hklm_sz(char* path, char* value, char** string, LPDWORD length) {
// First call to RegGetValueA gets the length of the string and determines how much
// memory should be allocated for the new string
if(RegGetValueA(HKEY_LOCAL_MACHINE, path, value, RRF_RT_REG_SZ, NULL, NULL, length) != ERROR_SUCCESS) {
return false;
}
*string = ecalloc(*length, sizeof(char));
// Second call actually writes the string data
if(RegGetValueA(HKEY_LOCAL_MACHINE, path, value, RRF_RT_REG_SZ, NULL, *string, length) != ERROR_SUCCESS) {
return false;
}
return true;
}
#endif

#define NA -1
#define min(a,b) (((a)<(b))?(a):(b))
#define ARRAY_SIZE(arr) (sizeof(arr) / sizeof((arr)[0]))
Expand Down Expand Up @@ -1224,7 +1246,24 @@ struct system_on_chip* get_soc(struct cpuInfo* cpu) {
else {
return soc;
}
#endif // ifdef __linux__
#endif

#if defined _WIN32
// Use the first core to determine the SoC
char* processor_name_string = NULL;
unsigned long processor_name_string_len = 0;
if(!read_registry_hklm_sz("HARDWARE\\DESCRIPTION\\System\\CentralProcessor\\0", "ProcessorNameString", &processor_name_string, &processor_name_string_len)) {
printWarn("Failed to aquire SoC name from registery");
return soc;
}

soc->name = processor_name_string;
soc->raw_name = processor_name_string;
soc->vendor = try_match_soc_vendor_name(processor_name_string);
soc->model = SOC_MODEL_UNKNOWN;
soc->process = UNKNOWN;

#else

if(soc->model == SOC_MODEL_UNKNOWN) {
// raw_name might not be NULL, but if we were unable to find
Expand All @@ -1233,5 +1272,7 @@ struct system_on_chip* get_soc(struct cpuInfo* cpu) {
snprintf(soc->raw_name, strlen(STRING_UNKNOWN)+1, STRING_UNKNOWN);
}

#endif

return soc;
}
6 changes: 6 additions & 0 deletions src/common/printer.c
Original file line number Diff line number Diff line change
Expand Up @@ -885,7 +885,13 @@ bool print_cpufetch_arm(struct cpuInfo* cpu, STYLE s, struct color** cs, struct
char* soc_name = get_soc_name(cpu->soc);
char* features = get_str_features(cpu);
setAttribute(art, ATTRIBUTE_SOC, soc_name);

// Currently no reliable way to identify the specific SoC on Windows
// https://github.com/Dr-Noob/cpufetch/pull/273
// Hide manufacturing process
#if !defined(_WIN32)
setAttribute(art, ATTRIBUTE_TECHNOLOGY, manufacturing_process);
#endif

if(cpu->num_cpus == 1) {
char* uarch = get_str_uarch(cpu);
Expand Down
12 changes: 12 additions & 0 deletions src/common/soc.c
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,18 @@ void fill_soc(struct system_on_chip* soc, char* soc_name, SOC soc_model, int32_t
}
}

#ifdef _WIN32
VENDOR try_match_soc_vendor_name(char* vendor_name)
{
for(size_t i=1; i < sizeof(soc_trademark_string)/sizeof(soc_trademark_string[0]); i++) {
if(strstr(vendor_name, soc_trademark_string[i]) != NULL) {
return i;
}
}
return SOC_VENDOR_UNKNOWN;
}
#endif

bool match_soc(struct system_on_chip* soc, char* raw_name, char* expected_name, char* soc_name, SOC soc_model, int32_t process) {
int len1 = strlen(raw_name);
int len2 = strlen(expected_name);
Expand Down
3 changes: 3 additions & 0 deletions src/common/soc.h
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,9 @@ VENDOR get_soc_vendor(struct system_on_chip* soc);
bool match_soc(struct system_on_chip* soc, char* raw_name, char* expected_name, char* soc_name, SOC soc_model, int32_t process);
char* get_str_process(struct system_on_chip* soc);
void fill_soc(struct system_on_chip* soc, char* soc_name, SOC soc_model, int32_t process);
#ifdef _WIN32
VENDOR try_match_soc_vendor_name(char* vendor_name);
#endif

#define SOC_START if (false) {}
#define SOC_EQ(raw_name, expected_name, soc_name, soc_model, soc, process) \
Expand Down