diff --git a/Makefile b/Makefile index 142a9b0..d07f036 100644 --- a/Makefile +++ b/Makefile @@ -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 diff --git a/README.md b/README.md index 806c32a..cd73344 100644 --- a/README.md +++ b/README.md @@ -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: | diff --git a/src/arm/midr.c b/src/arm/midr.c index c4d2e27..251471c 100644 --- a/src/arm/midr.c +++ b/src/arm/midr.c @@ -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 #endif #include "../common/global.h" @@ -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]; } @@ -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) { @@ -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) { @@ -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 } diff --git a/src/arm/soc.c b/src/arm/soc.c index 0cdde03..4a01653 100644 --- a/src/arm/soc.c +++ b/src/arm/soc.c @@ -14,6 +14,28 @@ #include "../common/sysctl.h" #endif +#if defined(_WIN32) + #define WIN32_LEAN_AND_MEAN + #define NOMINMAX + #include + +// 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])) @@ -1231,7 +1253,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 @@ -1240,5 +1279,7 @@ struct system_on_chip* get_soc(struct cpuInfo* cpu) { snprintf(soc->raw_name, strlen(STRING_UNKNOWN)+1, STRING_UNKNOWN); } +#endif + return soc; } diff --git a/src/common/printer.c b/src/common/printer.c index df73ad8..760a5f7 100644 --- a/src/common/printer.c +++ b/src/common/printer.c @@ -887,7 +887,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); diff --git a/src/common/soc.c b/src/common/soc.c index b7a8be3..5b11263 100644 --- a/src/common/soc.c +++ b/src/common/soc.c @@ -79,6 +79,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); diff --git a/src/common/soc.h b/src/common/soc.h index 97a2562..be9b795 100644 --- a/src/common/soc.h +++ b/src/common/soc.h @@ -51,6 +51,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) \