summaryrefslogtreecommitdiffstats
path: root/src/detection/cpu
diff options
context:
space:
mode:
Diffstat (limited to 'src/detection/cpu')
-rw-r--r--src/detection/cpu/cpu.c519
-rw-r--r--src/detection/cpu/cpu.h35
-rw-r--r--src/detection/cpu/cpu_apple.c158
-rw-r--r--src/detection/cpu/cpu_arm.h588
-rw-r--r--src/detection/cpu/cpu_bsd.c109
-rw-r--r--src/detection/cpu/cpu_haiku.c68
-rw-r--r--src/detection/cpu/cpu_linux.c1098
-rw-r--r--src/detection/cpu/cpu_nbsd.c109
-rw-r--r--src/detection/cpu/cpu_nosupport.c5
-rw-r--r--src/detection/cpu/cpu_obsd.c78
-rw-r--r--src/detection/cpu/cpu_sunos.c154
-rw-r--r--src/detection/cpu/cpu_windows.c320
12 files changed, 3241 insertions, 0 deletions
diff --git a/src/detection/cpu/cpu.c b/src/detection/cpu/cpu.c
new file mode 100644
index 0000000..aadcdc4
--- /dev/null
+++ b/src/detection/cpu/cpu.c
@@ -0,0 +1,519 @@
+#include "cpu.h"
+
+const char* ffDetectCPUImpl(const FFCPUOptions* options, FFCPUResult* cpu);
+
+const char* ffDetectCPU(const FFCPUOptions* options, FFCPUResult* cpu) {
+ const char* error = ffDetectCPUImpl(options, cpu);
+ if (error) {
+ return error;
+ }
+
+ const char* removeStrings[] = {
+ " CPU", " FPU", " APU", " Processor", " Dual-Core", " Quad-Core", " Six-Core", " Eight-Core", " Ten-Core", " 2-Core", " 4-Core", " 6-Core", " 8-Core", " 10-Core", " 12-Core", " 14-Core", " 16-Core"
+ };
+ ffStrbufRemoveStrings(&cpu->name, ARRAY_SIZE(removeStrings), removeStrings);
+ uint32_t radeonGraphics = ffStrbufFirstIndexS(&cpu->name, " w/ Radeon "); // w/ Radeon 780M Graphics
+ if (radeonGraphics >= cpu->name.length) {
+ radeonGraphics = ffStrbufFirstIndexS(&cpu->name, " with Radeon ");
+ }
+ if (radeonGraphics < cpu->name.length) {
+ ffStrbufSubstrBefore(&cpu->name, radeonGraphics);
+ }
+ ffStrbufSubstrBeforeFirstC(&cpu->name, '@'); // Cut the speed output in the name as we append our own
+ ffStrbufTrimRight(&cpu->name, ' '); // If we removed the @ in previous step there was most likely a space before it
+ ffStrbufRemoveDupWhitespaces(&cpu->name);
+ return NULL;
+}
+
+const char* ffCPUAppleCodeToName(uint32_t code) {
+ // https://github.com/AsahiLinux/docs/wiki/Codenames
+ switch (code) {
+ case 8103:
+ return "Apple M1";
+ case 6000:
+ return "Apple M1 Pro";
+ case 6001:
+ return "Apple M1 Max";
+ case 6002:
+ return "Apple M1 Ultra";
+ case 8112:
+ return "Apple M2";
+ case 6020:
+ return "Apple M2 Pro";
+ case 6021:
+ return "Apple M2 Max";
+ case 6022:
+ return "Apple M2 Ultra";
+ case 8122:
+ return "Apple M3";
+ case 6030:
+ return "Apple M3 Pro";
+ case 6031:
+ case 6034:
+ return "Apple M3 Max";
+ case 8132:
+ return "Apple M4";
+ case 6040:
+ return "Apple M4 Pro";
+ case 6041:
+ return "Apple M4 Max";
+ default:
+ return NULL;
+ }
+}
+
+const char* ffCPUQualcommCodeToName(uint32_t code) {
+ // https://github.com/AsahiLinux/docs/wiki/Codenames
+ switch (code) {
+ case 7180:
+ return "Qualcomm Snapdragon 7c";
+ case 7280:
+ return "Qualcomm Snapdragon 7c+ Gen 3";
+ case 8180:
+ return "Qualcomm Snapdragon 8cx Gen 2 5G";
+ case 8280:
+ return "Qualcomm Snapdragon 8cx Gen 3";
+ default:
+ return NULL;
+ }
+}
+
+#if defined(__x86_64__) || defined(__i386__)
+
+ #include <cpuid.h>
+
+void ffCPUDetectByCpuid(FFCPUResult* cpu) {
+ uint32_t eax = 0, ebx = 0, ecx = 0, edx = 0;
+ if (__get_cpuid(0x16, &eax, &ebx, &ecx, &edx)) {
+ // WARNING: CPUID may report frequencies of efficient cores
+ // cpuid returns 0 MHz when hypervisor is enabled
+ if (eax) {
+ cpu->frequencyBase = eax;
+ }
+ if (ebx) {
+ cpu->frequencyMax = ebx;
+ }
+ }
+
+ if (__get_cpuid(1, &eax, &ebx, &ecx, &edx)) {
+ // Feature tests (leaf1.ecx, leaf7.ebx)
+ bool sse2 = (ecx & bit_SSE2) != 0;
+ bool sse4_2 = (ecx & bit_SSE4_2) != 0;
+ bool pclmul = (ecx & bit_PCLMUL) != 0;
+ bool popcnt = (ecx & bit_POPCNT) != 0;
+ bool fma = (ecx & bit_FMA) != 0;
+ bool osxsave = (ecx & bit_OSXSAVE) != 0;
+
+ unsigned int eax7 = 0, ebx7 = 0, ecx7 = 0, edx7 = 0;
+ __get_cpuid_count(7, 0, &eax7, &ebx7, &ecx7, &edx7);
+
+ bool avx2 = (ebx7 & bit_AVX2) != 0;
+ bool bmi2 = (ebx7 & bit_BMI2) != 0;
+ bool avx512f = (ebx7 & bit_AVX512F) != 0;
+ bool avx512bw = (ebx7 & bit_AVX512BW) != 0;
+ bool avx512dq = (ebx7 & bit_AVX512DQ) != 0;
+
+ // OS support for AVX/AVX512: check XGETBV (requires OSXSAVE)
+ bool avx_os = false;
+ bool avx512_os = false;
+ if (osxsave) {
+ __asm__ __volatile__(
+ "xgetbv"
+ : "=a"(eax), "=d"(edx)
+ : "c"(0)
+ :);
+ uint64_t xcr0 = ((uint64_t) edx << 32) | eax;
+
+ // AVX requires XCR0[1:2] == 11b (XMM and YMM state)
+ avx_os = (xcr0 & 0x6ULL) == 0x6ULL;
+ // AVX512 requires XCR0[7,5,6] etc. common mask 0xE6 (bits 1,2,5,6,7)
+ avx512_os = (xcr0 & 0xE6ULL) == 0xE6ULL;
+ }
+
+ cpu->march = "unknown";
+ if (avx512f && avx512bw && avx512dq && avx512_os) {
+ cpu->march = "x86_64-v4";
+ } else if (avx2 && fma && bmi2 && avx_os) {
+ cpu->march = "x86_64-v3";
+ } else if (sse4_2 && popcnt && pclmul) {
+ cpu->march = "x86_64-v2";
+ } else if (sse2) {
+ cpu->march = "x86_64-v1";
+ }
+ }
+}
+
+#elif defined(__aarch64__)
+
+// This is not accurate because a lot of flags are optional from old versions
+// https://developer.arm.com/documentation/109697/2025_06/Feature-descriptions?lang=en
+// https://en.wikipedia.org/wiki/AArch64#ARM-A_(application_architecture)
+// Worth noting: Apple M1 is marked as ARMv8.5-A on Wikipedia, but it lacks BTI (mandatory in v8.5)
+
+ #ifdef __linux__
+ #include "common/io.h"
+ #include <elf.h>
+// #include <asm/hwcap.h>
+
+void ffCPUDetectByCpuid(FFCPUResult* cpu) {
+ char buf[PROC_FILE_BUFFSIZ];
+ ssize_t nRead = ffReadFileData("/proc/self/auxv", ARRAY_SIZE(buf), buf);
+
+ if (nRead < (ssize_t) sizeof(Elf64_auxv_t)) {
+ return;
+ }
+
+ uint64_t hwcap = 0, hwcap2 = 0;
+
+ for (Elf64_auxv_t* auxv = (Elf64_auxv_t*) buf; (char*) auxv < buf + nRead; ++auxv) {
+ if (auxv->a_type == AT_HWCAP) {
+ hwcap = auxv->a_un.a_val;
+ } else if (auxv->a_type == AT_HWCAP2) {
+ hwcap2 = auxv->a_un.a_val;
+ }
+ }
+
+ if (!hwcap) {
+ return;
+ }
+
+ cpu->march = "unknown";
+
+ // ARMv8-A
+ bool has_fp = (hwcap & (1 << 0) /* HWCAP_FP */) != 0;
+ bool has_asimd = (hwcap & (1 << 1) /* HWCAP_ASIMD */) != 0;
+
+ // ARMv8.1-A
+ bool has_atomics = (hwcap & (1 << 8) /* HWCAP_ATOMICS */) != 0; // optional from v8.0
+ bool has_crc32 = (hwcap & (1 << 7) /* HWCAP_CRC32 */) != 0; // optional from v8.0
+ bool has_asimdrdm = (hwcap & (1 << 12) /* HWCAP_ASIMDRDM */) != 0; // optional from v8.0
+
+ // ARMv8.2-A
+ bool has_fphp = (hwcap & (1 << 9) /* HWCAP_FPHP */) != 0; // optional
+ bool has_dcpop = (hwcap & (1 << 16) /* HWCAP_DCPOP */) != 0; // DC CVAP, optional from v8.1
+
+ // ARMv8.3-A
+ bool has_paca = (hwcap & (1 << 30) /* HWCAP_PACA */) != 0; // optional from v8.2
+ bool has_lrcpc = (hwcap & (1 << 15) /* HWCAP_LRCPC */) != 0; // optional from v8.2
+ bool has_fcma = (hwcap & (1 << 14) /* HWCAP_FCMA */) != 0; // optional from v8.2
+ bool has_jscvt = (hwcap & (1 << 13) /* HWCAP_JSCVT */) != 0; // optional from v8.2
+
+ // ARMv8.4-A
+ bool has_dit = (hwcap & (1 << 24) /* HWCAP_DIT */) != 0; // optional from v8.3
+ bool has_flagm = (hwcap & (1 << 27) /* HWCAP_FLAGM */) != 0; // optional from v8.1
+ bool has_ilrcpc = (hwcap & (1 << 26) /* HWCAP_ILRCPC */) != 0; // optional from v8.2
+
+ // ARMv8.5-A
+ bool has_bti = (hwcap2 & (1 << 17) /* HWCAP2_BTI */) != 0; // optional from v8.4
+ bool has_sb = (hwcap & (1 << 29) /* HWCAP_SB */) != 0; // optional from v8.0
+ bool has_dcpodp = (hwcap2 & (1 << 0) /* HWCAP2_DCPODP */) != 0; // optional from v8.1
+ bool has_flagm2 = (hwcap2 & (1 << 7) /* HWCAP2_FLAGM2 */) != 0; // optional from v8.4
+ bool has_frint = (hwcap2 & (1 << 8) /* HWCAP2_FRINT */) != 0; // optional from v8.4
+
+ // ARMv9.0-A
+ bool has_sve2 = (hwcap2 & (1 << 1) /* HWCAP2_SVE2 */) != 0;
+
+ // ARMv9.1-A
+ // ARMv8.6-A
+ bool has_bf16 = (hwcap2 & (1 << 14) /* HWCAP2_BF16 */) != 0; // optional from v8.2
+ bool has_i8mm = (hwcap2 & (1 << 13) /* HWCAP2_I8MM */) != 0; // optional from v8.1
+
+ // ARMv8.7-A
+ bool has_afp = (hwcap2 & (1 << 20) /* HWCAP2_AFP */) != 0; // optional from v8.6
+
+ // ARMv9.2-A
+ bool has_sme = (hwcap2 & (1 << 23) /* HWCAP2_SME */) != 0;
+
+ // ARMv9.3-A
+ bool has_sme2 = (hwcap2 & (1UL << 37) /* HWCAP2_SME2 */) != 0; // optional from v9.2
+
+ // ARMv8.8-A
+ bool has_mops = (hwcap2 & (1UL << 43) /* HWCAP2_MOPS */) != 0; // optional from v8.7
+
+ // ARMv8.9-A
+ bool has_cssc = (hwcap2 & (1UL << 34) /* HWCAP2_CSSC */) != 0; // optional from v8.7
+
+ // ARMv9.4-A
+ bool has_sme2p1 = (hwcap2 & (1UL << 38) /* HWCAP2_SME2P1 */) != 0; // optional from v9.2
+
+ // ARMv9.5-A
+ bool has_f8e4m3 = (hwcap2 & (1UL << 55) /* HWCAP2_F8E4M3 */) != 0; // optional from v9.2
+ bool has_f8e5m2 = (hwcap2 & (1UL << 56) /* HWCAP2_F8E5M2 */) != 0; // optional from v9.2
+
+ // ARMv9.6-A
+ bool has_cmpbr = (hwcap & (1UL << 33) /* HWCAP_CMPBR */) != 0; // optional from v9.5
+ bool has_fprcvt = (hwcap & (1UL << 34) /* HWCAP_FPRCVT */) != 0; // optional from v9.5
+
+ if (has_sve2 || has_sme) {
+ // ARMv9
+ if (has_cmpbr && has_fprcvt) {
+ cpu->march = "ARMv9.6-A";
+ } else if (has_f8e5m2 && has_f8e4m3) {
+ cpu->march = "ARMv9.5-A";
+ } else if (has_sme2p1) {
+ cpu->march = "ARMv9.4-A";
+ } else if (has_sme2) {
+ cpu->march = "ARMv9.3-A";
+ } else if (has_sme) {
+ cpu->march = "ARMv9.2-A";
+ } else if (has_i8mm && has_bf16) {
+ cpu->march = "ARMv9.1-A";
+ } else {
+ cpu->march = "ARMv9.0-A";
+ }
+ } else {
+ // ARMv8
+ if (has_cssc) {
+ cpu->march = "ARMv8.9-A";
+ } else if (has_mops) {
+ cpu->march = "ARMv8.8-A";
+ } else if (has_afp) {
+ cpu->march = "ARMv8.7-A";
+ } else if (has_i8mm && has_bf16) {
+ cpu->march = "ARMv8.6-A";
+ } else if (has_bti && has_sb && has_dcpodp && has_flagm2 && has_frint) {
+ cpu->march = "ARMv8.5-A";
+ } else if (has_dit && has_flagm && has_ilrcpc) {
+ cpu->march = "ARMv8.4-A";
+ } else if (has_paca && has_lrcpc && has_fcma && has_jscvt) {
+ cpu->march = "ARMv8.3-A";
+ } else if (has_fphp && has_dcpop) {
+ cpu->march = "ARMv8.2-A";
+ } else if (has_atomics && has_crc32 && has_asimdrdm) {
+ cpu->march = "ARMv8.1-A";
+ } else if (has_asimd && has_fp) {
+ cpu->march = "ARMv8-A";
+ }
+ }
+}
+ #elif __APPLE__
+ #include <sys/sysctl.h>
+// #include <arm/cpu_capabilities_public.h> // Not available in macOS 14-
+
+void ffCPUDetectByCpuid(FFCPUResult* cpu) {
+ uint64_t caps[2] = { 0 }; // 80-bit capability mask, split into two 64-bit values
+ size_t size = sizeof(caps);
+
+ if (sysctlbyname("hw.optional.arm.caps", caps, &size, NULL, 0) != 0) {
+ return;
+ }
+
+ // Helper macro to test bit in 80-bit capability mask
+ #define FF_HAS_CAP(bit) \
+ (((bit) < 64) ? ((caps[0] >> (bit)) & 1ULL) : ((caps[1] >> ((bit) - 64U)) & 1ULL))
+
+ cpu->march = "unknown";
+
+ // ARMv8-A
+ bool has_fp = FF_HAS_CAP(50); /* CAP_BIT_AdvSIMD_HPFPCvt */ // Full FP16 support (implies FP/ASIMD)
+ bool has_asimd = FF_HAS_CAP(49); /* CAP_BIT_AdvSIMD */ // Advanced SIMD (NEON)
+
+ // ARMv8.1-A
+ bool has_lse = FF_HAS_CAP(6); /* CAP_BIT_FEAT_LSE */ // Large System Extensions, optional in v8.0
+ bool has_crc32 = FF_HAS_CAP(51); /* CAP_BIT_FEAT_CRC32 */ // CRC32 instructions, optional in v8.0
+ bool has_rdm = FF_HAS_CAP(5); /* CAP_BIT_FEAT_RDM */ // AdvSIMD rounding double multiply accumulate, optional in v8.0
+
+ // ARMv8.2-A
+ bool has_fp16 = FF_HAS_CAP(34); /* CAP_BIT_FEAT_FP16 */ // Half-precision FP support, optional
+ bool has_dpb = FF_HAS_CAP(22); /* CAP_BIT_FEAT_DPB */ // DC CVAP, optional from v8.1
+
+ // ARMv8.3-A
+ bool has_pauth = FF_HAS_CAP(19); /* CAP_BIT_FEAT_PAuth */ // Pointer Authentication (PAC), optional from v8.2
+ bool has_lrcpc = FF_HAS_CAP(15); /* CAP_BIT_FEAT_LRCPC */ // LDAPR/LR with RCPC semantics, optional from v8.2
+ bool has_fcma = FF_HAS_CAP(17); /* CAP_BIT_FEAT_FCMA */ // Complex number multiply-add, optional from v8.2
+ bool has_jscvt = FF_HAS_CAP(18); /* CAP_BIT_FEAT_JSCVT */ // JavaScript-style conversion (FJCVTZS), optional from v8.2
+
+ // ARMv8.4-A
+ bool has_lse2 = FF_HAS_CAP(30); /* CAP_BIT_FEAT_LSE2 */ // Large System Extensions version 2, optional from v8.2
+ bool has_dit = FF_HAS_CAP(33); /* CAP_BIT_FEAT_DIT */ // Data Independent Timing, optional from v8.3
+ bool has_flagm = FF_HAS_CAP(0); /* CAP_BIT_FEAT_FlagM */ // Flag manipulation (FMOV/FCVT), optional from v8.1
+ bool has_lrcpc2 = FF_HAS_CAP(16); /* CAP_BIT_FEAT_LRCPC2 */ // Enhanced RCPC (LDAPUR/LDAPST), optional from v8.2
+
+ // ARMv8.5-A
+ bool has_bti = FF_HAS_CAP(36); /* CAP_BIT_FEAT_BTI */ // Branch Target Identification, optional from v8.4
+ bool has_sb = FF_HAS_CAP(13); /* CAP_BIT_FEAT_SB */ // Speculative Barrier, optional from v8.0
+ bool has_dpb2 = FF_HAS_CAP(23); /* CAP_BIT_FEAT_DPB2 */ // DC CVADP (DPB2), optional from v8.1
+ bool has_flagm2 = FF_HAS_CAP(1); /* CAP_BIT_FEAT_FlagM2 */ // Enhanced FlagM, optional from v8.4
+ bool has_frintts = FF_HAS_CAP(14); /* CAP_BIT_FEAT_FRINTTS */ // Floating-point to integer instructions, optional from v8.4
+
+ // ARMv9.0-A
+ bool has_sve2 = false; // Not exposed and not supported by Apple M4
+
+ // ARMv9.1-A
+ // ARMv8.6-A
+ bool has_bf16 = FF_HAS_CAP(24); /* CAP_BIT_FEAT_BF16 */ // Brain float16, optional from v8.2
+ bool has_i8mm = FF_HAS_CAP(25); /* CAP_BIT_FEAT_I8MM */ // Int8 Matrix Multiply, optional from v8.1
+
+ // ARMv8.7-A
+ bool has_afp = FF_HAS_CAP(29); /* CAP_BIT_FEAT_AFP */ // Alternate FP16 (FEXPA), optional from v8.6
+
+ // ARMv9.2-A
+ bool has_sme = FF_HAS_CAP(40); /* CAP_BIT_FEAT_SME */ // Scalable Matrix Extension, optional from v9.2
+
+ // ARMv9.3-A
+ bool has_sme2 = FF_HAS_CAP(41); /* CAP_BIT_FEAT_SME2 */ // SME2, optional from v9.2
+
+ // ARMv8.8-A
+ bool has_hbc = FF_HAS_CAP(64); /* CAP_BIT_FEAT_HBC */ // Hinted conditional branches, optional from v8.7
+
+ // ARMv8.9-A
+ bool has_cssc = FF_HAS_CAP(67); /* CAP_BIT_FEAT_CSSC */ // Common Short String Compare, optional from v8.7
+
+ // ARMv9.4-A+ are not exposed yet
+
+ if (has_sve2 || has_sme) {
+ // ARMv9 family
+ if (has_sme2) {
+ cpu->march = "ARMv9.3-A";
+ } else if (has_sme) {
+ cpu->march = "ARMv9.2-A";
+ } else if (has_i8mm && has_bf16) {
+ cpu->march = "ARMv9.1-A";
+ } else {
+ cpu->march = "ARMv9.0-A";
+ }
+ } else {
+ // ARMv8 family
+ if (has_cssc) {
+ cpu->march = "ARMv8.9-A";
+ } else if (has_hbc) {
+ cpu->march = "ARMv8.8-A";
+ } else if (has_afp) {
+ cpu->march = "ARMv8.7-A";
+ } else if (has_i8mm && has_bf16) {
+ cpu->march = "ARMv8.6-A";
+ } else if (has_bti && has_sb && has_dpb2 && has_flagm2 && has_frintts) {
+ cpu->march = "ARMv8.5-A";
+ } else if (has_lse2 && has_dit && has_flagm && has_lrcpc2) {
+ cpu->march = "ARMv8.4-A";
+ } else if (has_pauth && has_lrcpc && has_fcma && has_jscvt) {
+ cpu->march = "ARMv8.3-A";
+ } else if (has_fp16 && has_dpb) {
+ cpu->march = "ARMv8.2-A";
+ } else if (has_lse && has_crc32 && has_rdm) {
+ cpu->march = "ARMv8.1-A";
+ } else if (has_asimd && has_fp) {
+ cpu->march = "ARMv8-A";
+ }
+ }
+
+ #undef HAS_CAP
+}
+ #elif _WIN32
+ #include <processthreadsapi.h>
+
+ // Missing from winnt.h of MinGW-w64
+ #define PF_ARM_LSE2_AVAILABLE 62
+ #define PF_RESERVED_FEATURE 63
+ #define PF_ARM_SHA3_INSTRUCTIONS_AVAILABLE 64
+ #define PF_ARM_SHA512_INSTRUCTIONS_AVAILABLE 65
+ #define PF_ARM_V82_I8MM_INSTRUCTIONS_AVAILABLE 66
+ #define PF_ARM_V82_FP16_INSTRUCTIONS_AVAILABLE 67
+ #define PF_ARM_V86_BF16_INSTRUCTIONS_AVAILABLE 68
+ #define PF_ARM_V86_EBF16_INSTRUCTIONS_AVAILABLE 69
+ #define PF_ARM_SME_INSTRUCTIONS_AVAILABLE 70
+ #define PF_ARM_SME2_INSTRUCTIONS_AVAILABLE 71
+ #define PF_ARM_SME2_1_INSTRUCTIONS_AVAILABLE 72
+ #define PF_ARM_SME2_2_INSTRUCTIONS_AVAILABLE 73
+ #define PF_ARM_SME_AES_INSTRUCTIONS_AVAILABLE 74
+ #define PF_ARM_SME_SBITPERM_INSTRUCTIONS_AVAILABLE 75
+ #define PF_ARM_SME_SF8MM4_INSTRUCTIONS_AVAILABLE 76
+ #define PF_ARM_SME_SF8MM8_INSTRUCTIONS_AVAILABLE 77
+ #define PF_ARM_SME_SF8DP2_INSTRUCTIONS_AVAILABLE 78
+ #define PF_ARM_SME_SF8DP4_INSTRUCTIONS_AVAILABLE 79
+ #define PF_ARM_SME_SF8FMA_INSTRUCTIONS_AVAILABLE 80
+ #define PF_ARM_SME_F8F32_INSTRUCTIONS_AVAILABLE 81
+ #define PF_ARM_SME_F8F16_INSTRUCTIONS_AVAILABLE 82
+ #define PF_ARM_SME_F16F16_INSTRUCTIONS_AVAILABLE 83
+ #define PF_ARM_SME_B16B16_INSTRUCTIONS_AVAILABLE 84
+ #define PF_ARM_SME_F64F64_INSTRUCTIONS_AVAILABLE 85
+ #define PF_ARM_SME_I16I64_INSTRUCTIONS_AVAILABLE 86
+ #define PF_ARM_SME_LUTv2_INSTRUCTIONS_AVAILABLE 87
+ #define PF_ARM_SME_FA64_INSTRUCTIONS_AVAILABLE 88
+
+void ffCPUDetectByCpuid(FFCPUResult* cpu) {
+ // ARMv8-A
+ bool has_vfp = IsProcessorFeaturePresent(PF_ARM_VFP_32_REGISTERS_AVAILABLE); // Implies basic FP support
+ bool has_neon = IsProcessorFeaturePresent(PF_ARM_NEON_INSTRUCTIONS_AVAILABLE); // NEON (ASIMD)
+
+ // ARMv8.1-A
+ bool has_atomics = IsProcessorFeaturePresent(PF_ARM_V81_ATOMIC_INSTRUCTIONS_AVAILABLE); // LSE atomics
+ bool has_crc32 = IsProcessorFeaturePresent(PF_ARM_V8_CRC32_INSTRUCTIONS_AVAILABLE); // CRC32
+
+ // ARMv8.2-A
+ bool has_fp16 = IsProcessorFeaturePresent(PF_ARM_V82_FP16_INSTRUCTIONS_AVAILABLE); // Half-precision FP
+
+ // ARMv8.3-A
+ bool has_lrcpc = IsProcessorFeaturePresent(PF_ARM_V83_LRCPC_INSTRUCTIONS_AVAILABLE); // LDAPR/LR with RCPC semantics
+ bool has_jscvt = IsProcessorFeaturePresent(PF_ARM_V83_JSCVT_INSTRUCTIONS_AVAILABLE); // FJCVTZS
+
+ // ARMv8.4-A
+ // My CPU (Apple M1 Pro in VM) does support LSE2, but Windows doesn't detect it for some reason
+ bool has_lse2 = IsProcessorFeaturePresent(PF_ARM_LSE2_AVAILABLE); // Large System Extensions version 2, optional from v8.2
+ bool has_dp = IsProcessorFeaturePresent(PF_ARM_V82_DP_INSTRUCTIONS_AVAILABLE); // DotProd, optional from v8.1 (*)
+
+ // ARMv9.0-A
+ bool has_sve2 = IsProcessorFeaturePresent(PF_ARM_SVE2_INSTRUCTIONS_AVAILABLE); // SVE2
+
+ // ARMv9.1-A
+ // ARMv8.6-A
+ bool has_bf16 = IsProcessorFeaturePresent(PF_ARM_V86_BF16_INSTRUCTIONS_AVAILABLE); // BF16, optional from v8.2
+ bool has_i8mm = IsProcessorFeaturePresent(PF_ARM_V82_I8MM_INSTRUCTIONS_AVAILABLE); // Int8 matrix multiply, optional from v8.2
+
+ // ARMv8.7-A
+ bool has_ebf16 = IsProcessorFeaturePresent(PF_ARM_V86_EBF16_INSTRUCTIONS_AVAILABLE); // Extended BFloat16 behaviors, optional from v8.2
+
+ // ARMv9.2-A
+ bool has_sme = IsProcessorFeaturePresent(PF_ARM_SME_INSTRUCTIONS_AVAILABLE); // SME
+
+ // ARMv9.3-A
+ bool has_sme2 = IsProcessorFeaturePresent(PF_ARM_SME2_INSTRUCTIONS_AVAILABLE); // SME2
+
+ // ARMv9.4-A
+ bool has_sme2p1 = IsProcessorFeaturePresent(PF_ARM_SME2_1_INSTRUCTIONS_AVAILABLE); // SME2.1
+
+ if (has_sve2 || has_sme) {
+ // ARMv9 family
+ if (has_sme2p1) {
+ cpu->march = "ARMv9.4-A";
+ } else if (has_sme2) {
+ cpu->march = "ARMv9.3-A";
+ } else if (has_sme) {
+ cpu->march = "ARMv9.2-A";
+ } else if (has_i8mm && has_bf16) {
+ cpu->march = "ARMv9.1-A";
+ } else {
+ cpu->march = "ARMv9.0-A";
+ }
+ } else {
+ // ARMv8 family
+ if (has_ebf16) {
+ cpu->march = "ARMv8.7-A";
+ } else if (has_i8mm && has_bf16) {
+ cpu->march = "ARMv8.6-A";
+ } else if (has_dp && has_lse2) {
+ cpu->march = "ARMv8.4-A";
+ } else if (has_lrcpc && has_jscvt) {
+ cpu->march = "ARMv8.3-A";
+ } else if (has_fp16) {
+ cpu->march = "ARMv8.2-A";
+ } else if (has_atomics && has_crc32) {
+ cpu->march = "ARMv8.1-A";
+ } else if (has_neon && has_vfp) {
+ cpu->march = "ARMv8-A";
+ }
+ }
+}
+ #else
+void ffCPUDetectByCpuid(FF_A_UNUSED FFCPUResult* cpu) {
+ // Unsupported system
+}
+ #endif
+
+#else
+
+void ffCPUDetectByCpuid(FF_A_UNUSED FFCPUResult* cpu) {
+ // Unsupported architecture
+}
+
+#endif
diff --git a/src/detection/cpu/cpu.h b/src/detection/cpu/cpu.h
new file mode 100644
index 0000000..0f7eece
--- /dev/null
+++ b/src/detection/cpu/cpu.h
@@ -0,0 +1,35 @@
+#pragma once
+
+#include "fastfetch.h"
+#include "modules/cpu/option.h"
+
+#define FF_CPU_TEMP_UNSET (-DBL_MAX)
+
+typedef struct FFCPUCore {
+ uint32_t freq;
+ uint32_t count;
+} FFCPUCore;
+
+typedef struct FFCPUResult {
+ FFstrbuf name;
+ FFstrbuf vendor;
+ const char* march; // Microarchitecture
+
+ uint16_t packages;
+ uint16_t coresPhysical;
+ uint16_t coresLogical;
+ uint16_t coresOnline;
+ uint16_t numaNodes;
+
+ uint32_t frequencyBase; // GHz
+ uint32_t frequencyMax; // GHz
+
+ FFCPUCore coreTypes[16]; // number of P cores, E cores, etc.
+
+ double temperature;
+} FFCPUResult;
+
+const char* ffDetectCPU(const FFCPUOptions* options, FFCPUResult* cpu);
+const char* ffCPUAppleCodeToName(uint32_t code);
+const char* ffCPUQualcommCodeToName(uint32_t code);
+void ffCPUDetectByCpuid(FFCPUResult* cpu);
diff --git a/src/detection/cpu/cpu_apple.c b/src/detection/cpu/cpu_apple.c
new file mode 100644
index 0000000..f078b0c
--- /dev/null
+++ b/src/detection/cpu/cpu_apple.c
@@ -0,0 +1,158 @@
+#include "cpu.h"
+#include "common/sysctl.h"
+#include "common/apple/smc_temps.h"
+#include "common/strutil.h"
+
+static double detectCpuTemp(const FFCPUOptions* options, const FFstrbuf* cpuName) {
+ double result = 0;
+
+ const char* error = NULL;
+
+ if (options->tempSensor.length) {
+ error = ffDetectSmcSpecificTemp(options->tempSensor.chars, &result);
+ } else {
+ if (ffStrbufStartsWithS(cpuName, "Apple M")) {
+ switch (strtol(cpuName->chars + strlen("Apple M"), NULL, 10)) {
+ case 1:
+ error = ffDetectSmcTemps(FF_TEMP_CPU_M1X, &result);
+ break;
+ case 2:
+ error = ffDetectSmcTemps(FF_TEMP_CPU_M2X, &result);
+ break;
+ case 3:
+ error = ffDetectSmcTemps(FF_TEMP_CPU_M3X, &result);
+ break;
+ case 4:
+ error = ffDetectSmcTemps(FF_TEMP_CPU_M4X, &result);
+ break;
+ default:
+ error = "Unsupported Apple Silicon CPU";
+ }
+ } else { // PPC?
+ error = ffDetectSmcTemps(FF_TEMP_CPU_X64, &result);
+ }
+ }
+
+ if (error) {
+ return FF_CPU_TEMP_UNSET;
+ }
+
+ return result;
+}
+
+#ifdef __aarch64__
+ #include "common/apple/cf_helpers.h"
+
+ #include <IOKit/IOKitLib.h>
+
+static const char* detectFrequency(FFCPUResult* cpu) {
+ // https://github.com/giampaolo/psutil/pull/2222/files
+
+ FF_IOOBJECT_AUTO_RELEASE io_registry_entry_t entryDevice = IOServiceGetMatchingService(MACH_PORT_NULL, IOServiceNameMatching("pmgr"));
+ if (!entryDevice) {
+ return "IOServiceGetMatchingService() failed";
+ }
+
+ if (!IOObjectConformsTo(entryDevice, "AppleARMIODevice")) {
+ return "\"pmgr\" should conform to \"AppleARMIODevice\"";
+ }
+
+ FF_CFTYPE_AUTO_RELEASE CFDataRef freqProperty = (CFDataRef) IORegistryEntryCreateCFProperty(entryDevice, CFSTR("voltage-states5-sram"), kCFAllocatorDefault, kNilOptions);
+ if (!freqProperty || CFGetTypeID(freqProperty) != CFDataGetTypeID()) {
+ return "\"voltage-states5-sram\" in \"pmgr\" is not found";
+ }
+
+ // voltage-states5-sram stores supported <frequency / voltage> pairs of pcores from the lowest to the highest
+ // voltage-states1-sram stores ecores'
+ CFIndex propLength = CFDataGetLength(freqProperty);
+ if (propLength == 0 || propLength % (CFIndex) sizeof(uint32_t) * 2 != 0) {
+ return "Invalid \"voltage-states5-sram\" length";
+ }
+
+ uint32_t* pStart = (uint32_t*) CFDataGetBytePtr(freqProperty);
+ uint32_t pMax = *pStart;
+ for (CFIndex i = 2; i < propLength / (CFIndex) sizeof(uint32_t) && pStart[i] > 0; i += 2 /* skip voltage */) {
+ pMax = pMax > pStart[i] ? pMax : pStart[i];
+ }
+
+ if (pMax > 0) {
+ if (pMax > 100000000) { // Assume that pMax is in Hz, M1~M3
+ cpu->frequencyMax = pMax / 1000 / 1000;
+ } else { // Assume that pMax is in kHz, M4 and later (#1394)
+ cpu->frequencyMax = pMax / 1000;
+ }
+ }
+
+ return NULL;
+}
+#else
+static const char* detectFrequency(FFCPUResult* cpu) {
+ cpu->frequencyBase = (uint32_t) (ffSysctlGetInt64("hw.cpufrequency", 0) / 1000 / 1000);
+ cpu->frequencyMax = (uint32_t) (ffSysctlGetInt64("hw.cpufrequency_max", 0) / 1000 / 1000);
+ if (cpu->frequencyBase == 0) {
+ unsigned current = 0;
+ size_t size = sizeof(current);
+ if (sysctl((int[]) { CTL_HW, HW_CPU_FREQ }, 2, &current, &size, NULL, 0) == 0) {
+ cpu->frequencyBase = (uint32_t) (current / 1000 / 1000);
+ }
+ }
+ return NULL;
+}
+#endif
+
+static const char* detectCoreCount(FFCPUResult* cpu) {
+ uint32_t nPerfLevels = (uint32_t) ffSysctlGetInt("hw.nperflevels", 0);
+ if (nPerfLevels <= 0) {
+ return "sysctl(hw.nperflevels) failed";
+ }
+
+ char sysctlKey[] = "hw.perflevelN.logicalcpu";
+ if (nPerfLevels > ARRAY_SIZE(cpu->coreTypes)) {
+ nPerfLevels = ARRAY_SIZE(cpu->coreTypes);
+ }
+ for (uint32_t i = 0; i < nPerfLevels; ++i) {
+ sysctlKey[strlen("hw.perflevel")] = (char) ('0' + i);
+ cpu->coreTypes[i] = (FFCPUCore) {
+ .freq = nPerfLevels - i,
+ .count = (uint32_t) ffSysctlGetInt(sysctlKey, 0),
+ };
+ }
+ return NULL;
+}
+
+const char* ffDetectCPUImpl(const FFCPUOptions* options, FFCPUResult* cpu) {
+ if (ffSysctlGetString("machdep.cpu.brand_string", &cpu->name) != NULL) {
+ return "sysctlbyname(machdep.cpu.brand_string) failed";
+ }
+
+ ffSysctlGetString("machdep.cpu.vendor", &cpu->vendor);
+ cpu->packages = (uint16_t) ffSysctlGetInt("hw.packages", 1);
+ if (cpu->vendor.length == 0 && ffStrbufStartsWithS(&cpu->name, "Apple ")) {
+ ffStrbufAppendS(&cpu->vendor, "Apple");
+ }
+
+ cpu->coresPhysical = (uint16_t) ffSysctlGetInt("hw.physicalcpu_max", 1);
+ if (cpu->coresPhysical == 1) {
+ cpu->coresPhysical = (uint16_t) ffSysctlGetInt("hw.physicalcpu", 1);
+ }
+
+ cpu->coresLogical = (uint16_t) ffSysctlGetInt("hw.logicalcpu_max", 1);
+ if (cpu->coresLogical == 1) {
+ cpu->coresLogical = (uint16_t) ffSysctlGetInt("hw.ncpu", 1);
+ }
+
+ cpu->coresOnline = (uint16_t) ffSysctlGetInt("hw.logicalcpu", 1);
+ if (cpu->coresOnline == 1) {
+ cpu->coresOnline = (uint16_t) ffSysctlGetInt("hw.activecpu", 1);
+ }
+
+ ffCPUDetectByCpuid(cpu);
+ detectFrequency(cpu);
+ if (options->showPeCoreCount) {
+ detectCoreCount(cpu);
+ }
+
+ cpu->temperature = options->temp ? detectCpuTemp(options, &cpu->name) : FF_CPU_TEMP_UNSET;
+
+ return NULL;
+}
diff --git a/src/detection/cpu/cpu_arm.h b/src/detection/cpu/cpu_arm.h
new file mode 100644
index 0000000..571761a
--- /dev/null
+++ b/src/detection/cpu/cpu_arm.h
@@ -0,0 +1,588 @@
+#pragma once
+
+#include "fastfetch.h"
+
+// https://github.com/util-linux/util-linux/blob/master/sys-utils/lscpu-arm.c
+// We use the util-linux's data but not its code. Call me if it violates util-linux's GPL license.
+
+static const char* hwImplId2Vendor(uint32_t implId) {
+ switch (implId) {
+ case 0x41:
+ return "ARM";
+ case 0x42:
+ return "Broadcom";
+ case 0x43:
+ return "Cavium";
+ case 0x44:
+ return "DEC";
+ case 0x46:
+ return "FUJITSU";
+ case 0x48:
+ return "HiSilicon";
+ case 0x49:
+ return "Infineon";
+ case 0x4d:
+ return "Motorola";
+ case 0x4e:
+ return "NVIDIA";
+ case 0x50:
+ return "APM";
+ case 0x51:
+ return "Qualcomm";
+ case 0x53:
+ return "Samsung";
+ case 0x56:
+ return "Marvell";
+ case 0x61:
+ return "Apple";
+ case 0x66:
+ return "Faraday";
+ case 0x69:
+ return "Intel";
+ case 0x6D:
+ return "Microsoft";
+ case 0x70:
+ return "Phytium";
+ case 0xc0:
+ return "Ampere";
+ default:
+ return "Unknown";
+ }
+}
+
+static const char* armPartId2name(uint32_t partId) {
+ switch (partId) {
+ case 0x810:
+ return "ARM810";
+ case 0x920:
+ return "ARM920";
+ case 0x922:
+ return "ARM922";
+ case 0x926:
+ return "ARM926";
+ case 0x940:
+ return "ARM940";
+ case 0x946:
+ return "ARM946";
+ case 0x966:
+ return "ARM966";
+ case 0xa20:
+ return "ARM1020";
+ case 0xa22:
+ return "ARM1022";
+ case 0xa26:
+ return "ARM1026";
+ case 0xb02:
+ return "ARM11-MPCore";
+ case 0xb36:
+ return "ARM1136";
+ case 0xb56:
+ return "ARM1156";
+ case 0xb76:
+ return "ARM1176";
+ case 0xc05:
+ return "Cortex-A5";
+ case 0xc07:
+ return "Cortex-A7";
+ case 0xc08:
+ return "Cortex-A8";
+ case 0xc09:
+ return "Cortex-A9";
+ case 0xc0d:
+ return "Cortex-A17"; /* Originally A12 */
+ case 0xc0f:
+ return "Cortex-A15";
+ case 0xc0e:
+ return "Cortex-A17";
+ case 0xc14:
+ return "Cortex-R4";
+ case 0xc15:
+ return "Cortex-R5";
+ case 0xc17:
+ return "Cortex-R7";
+ case 0xc18:
+ return "Cortex-R8";
+ case 0xc20:
+ return "Cortex-M0";
+ case 0xc21:
+ return "Cortex-M1";
+ case 0xc23:
+ return "Cortex-M3";
+ case 0xc24:
+ return "Cortex-M4";
+ case 0xc27:
+ return "Cortex-M7";
+ case 0xc60:
+ return "Cortex-M0+";
+ case 0xd01:
+ return "Cortex-A32";
+ case 0xd02:
+ return "Cortex-A34";
+ case 0xd03:
+ return "Cortex-A53";
+ case 0xd04:
+ return "Cortex-A35";
+ case 0xd05:
+ return "Cortex-A55";
+ case 0xd06:
+ return "Cortex-A65";
+ case 0xd07:
+ return "Cortex-A57";
+ case 0xd08:
+ return "Cortex-A72";
+ case 0xd09:
+ return "Cortex-A73";
+ case 0xd0a:
+ return "Cortex-A75";
+ case 0xd0b:
+ return "Cortex-A76";
+ case 0xd0c:
+ return "Neoverse-N1";
+ case 0xd0d:
+ return "Cortex-A77";
+ case 0xd0e:
+ return "Cortex-A76AE";
+ case 0xd13:
+ return "Cortex-R52";
+ case 0xd14:
+ return "Cortex-R82AE";
+ case 0xd15:
+ return "Cortex-R82";
+ case 0xd16:
+ return "Cortex-R52+";
+ case 0xd20:
+ return "Cortex-M23";
+ case 0xd21:
+ return "Cortex-M33";
+ case 0xd24:
+ return "Cortex-M52";
+ case 0xd22:
+ return "Cortex-M55";
+ case 0xd23:
+ return "Cortex-M85";
+ case 0xd40:
+ return "Neoverse-V1";
+ case 0xd41:
+ return "Cortex-A78";
+ case 0xd42:
+ return "Cortex-A78AE";
+ case 0xd43:
+ return "Cortex-A65AE";
+ case 0xd44:
+ return "Cortex-X1";
+ case 0xd46:
+ return "Cortex-A510";
+ case 0xd47:
+ return "Cortex-A710";
+ case 0xd48:
+ return "Cortex-X2";
+ case 0xd49:
+ return "Neoverse-N2";
+ case 0xd4a:
+ return "Neoverse-E1";
+ case 0xd4b:
+ return "Cortex-A78C";
+ case 0xd4c:
+ return "Cortex-X1C";
+ case 0xd4d:
+ return "Cortex-A715";
+ case 0xd4e:
+ return "Cortex-X3";
+ case 0xd4f:
+ return "Neoverse-V2";
+ case 0xd80:
+ return "Cortex-A520";
+ case 0xd81:
+ return "Cortex-A720";
+ case 0xd82:
+ return "Cortex-X4";
+ case 0xd83:
+ return "Neoverse-V3AE";
+ case 0xd84:
+ return "Neoverse-V3";
+ case 0xd85:
+ return "Cortex-X925";
+ case 0xd87:
+ return "Cortex-A725";
+ case 0xd88:
+ return "Cortex-A520AE";
+ case 0xd89:
+ return "Cortex-A720AE";
+ case 0xd8a:
+ return "C1-Nano";
+ case 0xd8b:
+ return "C1-Pro";
+ case 0xd8c:
+ return "C1-Ultra";
+ case 0xd8e:
+ return "Neoverse-N3";
+ case 0xd8f:
+ return "Cortex-A320";
+ case 0xd90:
+ return "C1-Premium";
+ default:
+ return NULL;
+ }
+}
+
+static const char* brcmPartId2name(uint32_t partId) {
+ switch (partId) {
+ case 0x0f:
+ return "Brahma-B15";
+ case 0x100:
+ return "Brahma-B53";
+ case 0x516:
+ return "ThunderX2";
+ default:
+ return NULL;
+ }
+}
+
+static const char* decPartId2name(uint32_t partId) {
+ switch (partId) {
+ case 0xa10:
+ return "SA110";
+ case 0xa11:
+ return "SA1100";
+ default:
+ return NULL;
+ }
+}
+
+static const char* caviumPartId2name(uint32_t partId) {
+ switch (partId) {
+ case 0x0a0:
+ return "ThunderX";
+ case 0x0a1:
+ return "ThunderX-88XX";
+ case 0x0a2:
+ return "ThunderX-81XX";
+ case 0x0a3:
+ return "ThunderX-83XX";
+ case 0x0af:
+ return "ThunderX2-99xx";
+ case 0x0b0:
+ return "OcteonTX2";
+ case 0x0b1:
+ return "OcteonTX2-98XX";
+ case 0x0b2:
+ return "OcteonTX2-96XX";
+ case 0x0b3:
+ return "OcteonTX2-95XX";
+ case 0x0b4:
+ return "OcteonTX2-95XXN";
+ case 0x0b5:
+ return "OcteonTX2-95XXMM";
+ case 0x0b6:
+ return "OcteonTX2-95XXO";
+ case 0x0b8:
+ return "ThunderX3-T110";
+ default:
+ return NULL;
+ }
+}
+
+static const char* apmPartId2name(uint32_t partId) {
+ switch (partId) {
+ case 0x000:
+ return "X-Gene";
+ default:
+ return NULL;
+ }
+}
+
+static const char* qcomPartId2name(uint32_t partId) {
+ switch (partId) {
+ case 0x001:
+ return "Oryon 1";
+ case 0x002:
+ return "Oryon 2";
+ case 0x00f:
+ return "Scorpion";
+ case 0x02d:
+ return "Scorpion";
+ case 0x04d:
+ return "Krait";
+ case 0x06f:
+ return "Krait";
+ case 0x201:
+ return "Kryo";
+ case 0x205:
+ return "Kryo";
+ case 0x211:
+ return "Kryo";
+ case 0x800:
+ return "Falkor-V1/Kryo";
+ case 0x801:
+ return "Kryo-V2";
+ case 0x802:
+ return "Kryo-3XX-Gold";
+ case 0x803:
+ return "Kryo-3XX-Silver";
+ case 0x804:
+ return "Kryo-4XX-Gold";
+ case 0x805:
+ return "Kryo-4XX-Silver";
+ case 0xc00:
+ return "Falkor";
+ case 0xc01:
+ return "Saphira";
+ default:
+ return NULL;
+ }
+}
+
+static const char* samsungPartId2name(uint32_t partId) {
+ switch (partId) {
+ case 0x001:
+ return "Exynos-M1";
+ case 0x002:
+ return "Exynos-M3";
+ case 0x003:
+ return "Exynos-M4";
+ case 0x004:
+ return "Exynos-M5";
+ default:
+ return NULL;
+ }
+}
+
+static const char* nvidiaPartId2name(uint32_t partId) {
+ switch (partId) {
+ case 0x000:
+ return "Denver";
+ case 0x003:
+ return "Denver-2";
+ case 0x004:
+ return "Carmel";
+ case 0x010:
+ return "Olympus";
+ default:
+ return NULL;
+ }
+}
+
+static const char* marvellPartId2name(uint32_t partId) {
+ switch (partId) {
+ case 0x131:
+ return "Feroceon-88FR131";
+ case 0x581:
+ return "PJ4/PJ4b";
+ case 0x584:
+ return "PJ4B-MP";
+ default:
+ return NULL;
+ }
+}
+
+static const char* applePartId2name(uint32_t partId) {
+ switch (partId) {
+ case 0x000:
+ return "Swift";
+ case 0x001:
+ return "Cyclone";
+ case 0x002:
+ return "Typhoon";
+ case 0x003:
+ return "Typhoon/Capri";
+ case 0x004:
+ return "Twister";
+ case 0x005:
+ return "Twister/Elba/Malta";
+ case 0x006:
+ return "Hurricane";
+ case 0x007:
+ return "Hurricane/Myst";
+ case 0x008:
+ return "Monsoon";
+ case 0x009:
+ return "Mistral";
+ case 0x00b:
+ return "Vortex";
+ case 0x00c:
+ return "Tempest";
+ case 0x00f:
+ return "Tempest-M9";
+ case 0x010:
+ return "Vortex/Aruba";
+ case 0x011:
+ return "Tempest/Aruba";
+ case 0x012:
+ return "Lightning";
+ case 0x013:
+ return "Thunder";
+ case 0x020:
+ return "Icestorm-A14";
+ case 0x021:
+ return "Firestorm-A14";
+ case 0x022:
+ return "Icestorm-M1";
+ case 0x023:
+ return "Firestorm-M1";
+ case 0x024:
+ return "Icestorm-M1-Pro";
+ case 0x025:
+ return "Firestorm-M1-Pro";
+ case 0x026:
+ return "Thunder-M10";
+ case 0x028:
+ return "Icestorm-M1-Max";
+ case 0x029:
+ return "Firestorm-M1-Max";
+ case 0x030:
+ return "Blizzard-A15";
+ case 0x031:
+ return "Avalanche-A15";
+ case 0x032:
+ return "Blizzard-M2";
+ case 0x033:
+ return "Avalanche-M2";
+ case 0x034:
+ return "Blizzard-M2-Pro";
+ case 0x035:
+ return "Avalanche-M2-Pro";
+ case 0x036:
+ return "Sawtooth-A16";
+ case 0x037:
+ return "Everest-A16";
+ case 0x038:
+ return "Blizzard-M2-Max";
+ case 0x039:
+ return "Avalanche-M2-Max";
+ case 0x046:
+ return "Sawtooth-M11";
+ case 0x048:
+ return "Sawtooth-M3-Max";
+ case 0x049:
+ return "Everest-M3-Max";
+ default:
+ return NULL;
+ }
+}
+
+static const char* faradayPartId2name(uint32_t partId) {
+ switch (partId) {
+ case 0x526:
+ return "FA526";
+ case 0x626:
+ return "FA626";
+ default:
+ return NULL;
+ }
+}
+
+static const char* intelPartId2name(uint32_t partId) {
+ switch (partId) {
+ case 0x200:
+ return "i80200";
+ case 0x210:
+ return "PXA250A";
+ case 0x212:
+ return "PXA210A";
+ case 0x242:
+ return "i80321-400";
+ case 0x243:
+ return "i80321-600";
+ case 0x290:
+ return "PXA250B/PXA26x";
+ case 0x292:
+ return "PXA210B";
+ case 0x2c2:
+ return "i80321-400-B0";
+ case 0x2c3:
+ return "i80321-600-B0";
+ case 0x2d0:
+ return "PXA250C/PXA255/PXA26x";
+ case 0x2d2:
+ return "PXA210C";
+ case 0x411:
+ return "PXA27x";
+ case 0x41c:
+ return "IPX425-533";
+ case 0x41d:
+ return "IPX425-400";
+ case 0x41f:
+ return "IPX425-266";
+ case 0x682:
+ return "PXA32x";
+ case 0x683:
+ return "PXA930/PXA935";
+ case 0x688:
+ return "PXA30x";
+ case 0x689:
+ return "PXA31x";
+ case 0xb11:
+ return "SA1110";
+ case 0xc12:
+ return "IPX1200";
+ default:
+ return NULL;
+ }
+}
+
+static const char* fujitsuPartId2name(uint32_t partId) {
+ switch (partId) {
+ case 0x001:
+ return "A64FX";
+ case 0x003:
+ return "MONAKA";
+ default:
+ return NULL;
+ }
+}
+
+static const char* hisiPartId2name(uint32_t partId) {
+ switch (partId) {
+ case 0xd01:
+ return "TaiShan-v110"; /* used in Kunpeng-920 SoC */
+ case 0xd02:
+ return "TaiShan-v120"; /* used in Kirin 990A and 9000S SoCs */
+ case 0xd40:
+ return "Cortex-A76"; /* HiSilicon uses this ID though advertises A76 */
+ case 0xd41:
+ return "Cortex-A77"; /* HiSilicon uses this ID though advertises A77 */
+ default:
+ return NULL;
+ }
+}
+
+static const char* amperePartId2name(uint32_t partId) {
+ switch (partId) {
+ case 0xac3:
+ return "Ampere-1";
+ case 0xac4:
+ return "Ampere-1a";
+ default:
+ return NULL;
+ }
+}
+
+static const char* ftPartId2name(uint32_t partId) {
+ switch (partId) {
+ case 0x303:
+ return "FTC310";
+ case 0x660:
+ return "FTC660";
+ case 0x661:
+ return "FTC661";
+ case 0x662:
+ return "FTC662";
+ case 0x663:
+ return "FTC663";
+ case 0x664:
+ return "FTC664";
+ case 0x862:
+ return "FTC862";
+ default:
+ return NULL;
+ }
+}
+
+static const char* msPartId2name(uint32_t partId) {
+ switch (partId) {
+ case 0xd49:
+ return "Azure-Cobalt-100";
+ default:
+ return NULL;
+ }
+}
diff --git a/src/detection/cpu/cpu_bsd.c b/src/detection/cpu/cpu_bsd.c
new file mode 100644
index 0000000..4cca30e
--- /dev/null
+++ b/src/detection/cpu/cpu_bsd.c
@@ -0,0 +1,109 @@
+#include "cpu.h"
+#include "common/sysctl.h"
+#include "common/strutil.h"
+
+#include <sys/param.h>
+#if __has_include(<sys/cpuset.h>)
+ #include <sys/cpuset.h>
+ #define FF_HAVE_CPUSET 1
+#endif
+
+static const char* detectCpuTemp(const FFCPUOptions* options, double* current) {
+ int temp;
+ if (options->tempSensor.length > 0) {
+ temp = ffSysctlGetInt(options->tempSensor.chars, -999999);
+ if (temp == -999999) {
+ return "ffSysctlGetInt(options->tempSensor) failed";
+ }
+ } else {
+ temp = ffSysctlGetInt("dev.cpu.0.temperature", -999999);
+ if (temp == -999999) {
+ // Thermal zone temperature
+ temp = ffSysctlGetInt("hw.acpi.thermal.tz0.temperature", -999999);
+ if (temp == -999999) {
+ return "ffSysctlGetInt(\"dev.cpu.0.temperature\" or \"hw.acpi.thermal.tz0.temperature\") failed";
+ }
+ }
+ }
+
+ // In tenth of degrees Kelvin
+ *current = (double) temp / 10 - 273.15;
+ return NULL;
+}
+
+const char* ffDetectCPUImpl(const FFCPUOptions* options, FFCPUResult* cpu) {
+ if (ffSysctlGetString("hw.model", &cpu->name) != NULL) {
+ return "sysctlbyname(hw.model) failed";
+ }
+
+ cpu->coresLogical = (uint16_t) ffSysctlGetInt("hw.ncpu", 1);
+ cpu->coresPhysical = (uint16_t) ffSysctlGetInt("kern.smp.cores", 0);
+ cpu->coresOnline = (uint16_t) ffSysctlGetInt("kern.smp.cpus", cpu->coresLogical);
+
+ FF_STRBUF_AUTO_DESTROY buffer = ffStrbufCreate();
+ if (ffSysctlGetString("kern.sched.topology_spec", &buffer) == NULL && buffer.length > 0) {
+ // <groups>
+ // <group level="1" cache-level="3">
+ // <cpu count="4" mask="f,0,0,0">0, 1, 2, 3</cpu>
+ // <children>
+ // <group level="2" cache-level="2">
+ // <cpu count="2" mask="3,0,0,0">0, 1</cpu>
+ // <flags><flag name="THREAD">THREAD group</flag><flag name="SMT">SMT group</flag></flags>
+ // </group>
+ // <group level="2" cache-level="2">
+ // <cpu count="2" mask="c,0,0,0">2, 3</cpu>
+ // <flags><flag name="THREAD">THREAD group</flag><flag name="SMT">SMT group</flag></flags>
+ // </group>
+ // </children>
+ // </group>
+ // </groups>
+ for (char* p = buffer.chars; (p = strstr(p, "\n </group>\n")); ++p) {
+ cpu->packages++;
+ }
+ }
+
+#if FF_HAVE_CPUSET && (__x86_64__ || __i386__)
+ // Bind current process to the first two cores, which is *usually* a performance core
+ cpuset_t currentCPU;
+ CPU_ZERO(&currentCPU);
+ CPU_SET(1, &currentCPU);
+ CPU_SET(2, &currentCPU);
+ cpuset_setaffinity(CPU_LEVEL_WHICH, CPU_WHICH_TID, -1, sizeof(cpuset_t), &currentCPU);
+#endif
+
+ ffCPUDetectByCpuid(cpu);
+
+ uint32_t clockrate = (uint32_t) ffSysctlGetInt("hw.clockrate", 0);
+ if (clockrate > cpu->frequencyBase) {
+ cpu->frequencyBase = clockrate;
+ }
+
+ for (uint16_t i = 0; i < cpu->coresLogical; ++i) {
+ ffStrbufClear(&buffer);
+ char key[32];
+ snprintf(key, sizeof(key), "dev.cpu.%u.freq_levels", i);
+ if (ffSysctlGetString(key, &buffer) == NULL) {
+ if (buffer.length == 0) {
+ continue;
+ }
+
+ // MHz/Watts pairs like: 2501/32000 2187/27125 2000/24000
+ uint32_t fmax = (uint32_t) strtoul(buffer.chars, NULL, 10);
+ if (cpu->frequencyMax < fmax) {
+ cpu->frequencyMax = fmax;
+ }
+ } else {
+ break;
+ }
+ }
+
+ cpu->temperature = FF_CPU_TEMP_UNSET;
+
+ if (options->temp) {
+ detectCpuTemp(options, &cpu->temperature);
+ }
+
+ cpu->numaNodes = (uint16_t) ffSysctlGetInt("vm.ndomains", 0);
+
+ return NULL;
+}
diff --git a/src/detection/cpu/cpu_haiku.c b/src/detection/cpu/cpu_haiku.c
new file mode 100644
index 0000000..9a8acb5
--- /dev/null
+++ b/src/detection/cpu/cpu_haiku.c
@@ -0,0 +1,68 @@
+#include "cpu.h"
+#include "common/mallocHelper.h"
+
+#include <OS.h>
+#include <private/shared/cpu_type.h>
+
+const char* ffDetectCPUImpl(FF_A_UNUSED const FFCPUOptions* options, FFCPUResult* cpu) {
+ system_info sysInfo;
+ if (get_system_info(&sysInfo) != B_OK) {
+ return "get_system_info() failed";
+ }
+
+ uint32 topoNodeCount = 0;
+ get_cpu_topology_info(NULL, &topoNodeCount);
+ if (topoNodeCount == 0) {
+ return "get_cpu_topology_info(NULL) failed";
+ }
+
+ FF_AUTO_FREE cpu_topology_node_info* topology = malloc(sizeof(*topology) * topoNodeCount);
+ if (get_cpu_topology_info(topology, &topoNodeCount) != B_OK) {
+ return "get_cpu_topology_info(topology) failed";
+ }
+
+ enum cpu_platform platform = B_CPU_UNKNOWN;
+ enum cpu_vendor cpuVendor = B_CPU_VENDOR_UNKNOWN;
+ uint32 cpuModel = 0, frequency = 0;
+ uint16_t packages = 0, cores = 0;
+
+ for (uint32 i = 0; i < topoNodeCount; i++) {
+ switch (topology[i].type) {
+ case B_TOPOLOGY_ROOT:
+ platform = topology[i].data.root.platform;
+ break;
+
+ case B_TOPOLOGY_PACKAGE:
+ cpuVendor = topology[i].data.package.vendor;
+ ++packages;
+ break;
+
+ case B_TOPOLOGY_CORE:
+ cpuModel = topology[i].data.core.model;
+ uint32_t freq = (uint32_t) (topology[i].data.core.default_frequency / 1000000);
+ frequency = freq > frequency ? freq : frequency;
+ ++cores;
+ break;
+
+ default:
+ break;
+ }
+ }
+
+ const char* model = get_cpu_model_string(platform, cpuVendor, cpuModel);
+ if (model) {
+ ffStrbufSetS(&cpu->name, model);
+ } else {
+ ffStrbufSetF(&cpu->name, "(Unknown %" B_PRIx32 ")", cpuModel);
+ }
+ ffStrbufSetS(&cpu->vendor, get_cpu_vendor_string(cpuVendor));
+
+ ffCPUDetectByCpuid(cpu);
+ if (cpu->frequencyBase < frequency) {
+ cpu->frequencyBase = frequency;
+ }
+ cpu->packages = packages;
+ cpu->coresPhysical = cores;
+ cpu->coresOnline = cpu->coresLogical = (uint16_t) sysInfo.cpu_count;
+ return NULL;
+}
diff --git a/src/detection/cpu/cpu_linux.c b/src/detection/cpu/cpu_linux.c
new file mode 100644
index 0000000..b228d52
--- /dev/null
+++ b/src/detection/cpu/cpu_linux.c
@@ -0,0 +1,1098 @@
+#include "cpu.h"
+#include "common/io.h"
+#include "common/processing.h"
+#include "common/properties.h"
+#include "common/mallocHelper.h"
+#include "common/strutil.h"
+#include "common/path.h"
+
+#include <sys/sysinfo.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <dirent.h>
+#include <fcntl.h>
+
+#define FF_CPUINFO_PATH "/proc/cpuinfo"
+
+static double readTempFile(int dfd, const char* filename, FFstrbuf* buffer) {
+ if (filename ? !ffReadFileBufferRelative(dfd, filename, buffer) : !ffReadFDBuffer(dfd, buffer)) {
+ return FF_CPU_TEMP_UNSET;
+ }
+
+ double value = ffStrbufToDouble(buffer, FF_CPU_TEMP_UNSET); // millidegree Celsius
+ if (value == FF_CPU_TEMP_UNSET) {
+ return FF_CPU_TEMP_UNSET;
+ }
+
+ return value / 1000.;
+}
+
+static double parseTZDir(int dfd, FFstrbuf* buffer) {
+ if (!ffReadFileBufferRelative(dfd, "type", buffer)) {
+ return FF_CPU_TEMP_UNSET;
+ }
+
+ if (!ffStrbufStartsWithS(buffer, "cpu") &&
+ !ffStrbufStartsWithS(buffer, "soc") &&
+#if __x86_64__ || __i386__
+ !ffStrbufEqualS(buffer, "x86_pkg_temp") &&
+#endif
+ true)
+ return FF_CPU_TEMP_UNSET;
+
+ return readTempFile(dfd, "temp", buffer);
+}
+
+static double parseHwmonDir(int dfd, FFstrbuf* buffer) {
+ // https://www.kernel.org/doc/Documentation/hwmon/sysfs-interface
+ if (!ffReadFileBufferRelative(dfd, "name", buffer)) {
+ return FF_CPU_TEMP_UNSET;
+ }
+
+ ffStrbufTrimRightSpace(buffer);
+
+ if (
+ !ffStrbufContainS(buffer, "cpu") &&
+#if __x86_64__ || __i386__
+ !ffStrbufEqualS(buffer, "k10temp") && // AMD
+ !ffStrbufEqualS(buffer, "fam15h_power") && // AMD
+ !ffStrbufEqualS(buffer, "coretemp") && // Intel
+#endif
+ true)
+ return FF_CPU_TEMP_UNSET;
+
+ return readTempFile(dfd, "temp1_input", buffer);
+}
+
+static double detectCPUTemp(const FFCPUOptions* options) {
+ FF_STRBUF_AUTO_DESTROY buffer = ffStrbufCreate();
+
+ if (options->tempSensor.length > 0) {
+ FF_AUTO_CLOSE_FD int subfd = -1;
+ const char* fileName = NULL;
+ if (ffStrbufStartsWithS(&options->tempSensor, "hwmon") && ffCharIsDigit(options->tempSensor.chars[strlen("hwmon")])) {
+ FF_AUTO_CLOSE_FD int dfd = open("/sys/class/hwmon/", O_PATH | O_CLOEXEC);
+ subfd = openat(dfd, options->tempSensor.chars, O_RDONLY | O_DIRECTORY | O_CLOEXEC);
+ if (subfd >= 0) {
+ fileName = "temp1_input";
+ } else {
+ subfd = openat(dfd, options->tempSensor.chars, O_RDONLY | O_CLOEXEC);
+ }
+ } else if (ffStrbufStartsWithS(&options->tempSensor, "thermal_zone") && ffCharIsDigit(options->tempSensor.chars[strlen("thermal_zone")])) {
+ FF_AUTO_CLOSE_FD int dfd = open("/sys/class/thermal/", O_PATH | O_CLOEXEC);
+ subfd = openat(dfd, options->tempSensor.chars, O_RDONLY | O_DIRECTORY | O_CLOEXEC);
+ if (subfd >= 0) {
+ fileName = "temp";
+ } else {
+ subfd = openat(dfd, options->tempSensor.chars, O_RDONLY | O_CLOEXEC);
+ }
+ } else if (ffStrbufStartsWithS(&options->tempSensor, "cputemp.") && ffCharIsDigit(options->tempSensor.chars[strlen("cputemp.")])) {
+ FF_AUTO_CLOSE_FD int dfd = open("/sys/class/platform/", O_PATH | O_CLOEXEC);
+ subfd = openat(dfd, options->tempSensor.chars, O_RDONLY | O_DIRECTORY | O_CLOEXEC);
+ if (subfd >= 0) {
+ fileName = "temp1_input";
+ } else {
+ subfd = openat(dfd, options->tempSensor.chars, O_RDONLY | O_CLOEXEC);
+ }
+ } else if (ffIsAbsolutePath(options->tempSensor.chars)) {
+ subfd = open(options->tempSensor.chars, O_RDONLY | O_DIRECTORY | O_CLOEXEC);
+ if (subfd >= 0) {
+ fileName = "temp1_input";
+ } else {
+ subfd = open(options->tempSensor.chars, O_RDONLY | O_CLOEXEC);
+ }
+ }
+ if (subfd < 0) {
+ return FF_CPU_TEMP_UNSET;
+ }
+
+ return readTempFile(subfd, fileName, &buffer);
+ }
+
+ {
+ FF_AUTO_CLOSE_DIR DIR* dirp = opendir("/sys/class/hwmon/");
+ if (dirp) {
+ int dfd = dirfd(dirp);
+
+ struct dirent* entry;
+ while ((entry = readdir(dirp)) != NULL) {
+ if (entry->d_name[0] == '.') {
+ continue;
+ }
+
+ FF_AUTO_CLOSE_FD int subfd = openat(dfd, entry->d_name, O_RDONLY | O_DIRECTORY | O_CLOEXEC);
+ if (subfd < 0) {
+ continue;
+ }
+
+ double result = parseHwmonDir(subfd, &buffer);
+ if (result != FF_CPU_TEMP_UNSET) {
+ return result;
+ }
+ }
+ }
+ }
+ {
+ FF_AUTO_CLOSE_DIR DIR* dirp = opendir("/sys/class/thermal/");
+ if (dirp) {
+ int dfd = dirfd(dirp);
+ struct dirent* entry;
+ while ((entry = readdir(dirp)) != NULL) {
+ if (entry->d_name[0] == '.') {
+ continue;
+ }
+ if (!ffStrStartsWith(entry->d_name, "thermal_zone")) {
+ continue;
+ }
+
+ FF_AUTO_CLOSE_FD int subfd = openat(dfd, entry->d_name, O_RDONLY | O_DIRECTORY | O_CLOEXEC);
+ if (subfd < 0) {
+ continue;
+ }
+
+ double result = parseTZDir(subfd, &buffer);
+ if (result != FF_CPU_TEMP_UNSET) {
+ return result;
+ }
+ }
+ }
+ }
+ {
+ FF_AUTO_CLOSE_DIR DIR* dirp = opendir("/sys/devices/platform/");
+ if (dirp) {
+ int dfd = dirfd(dirp);
+ struct dirent* entry;
+ while ((entry = readdir(dirp)) != NULL) {
+ if (entry->d_name[0] == '.') {
+ continue;
+ }
+ if (!ffStrStartsWith(entry->d_name, "cputemp.")) {
+ continue;
+ }
+
+ FF_AUTO_CLOSE_FD int subfd = openat(dfd, entry->d_name, O_RDONLY | O_DIRECTORY | O_CLOEXEC);
+ if (subfd < 0) {
+ continue;
+ }
+
+ double result = parseHwmonDir(subfd, &buffer);
+ if (result != FF_CPU_TEMP_UNSET) {
+ return result;
+ }
+ }
+ }
+ }
+
+ return FF_CPU_TEMP_UNSET;
+}
+
+static void detectNumaNodes(FFCPUResult* cpu) {
+ FF_AUTO_CLOSE_DIR DIR* dir = opendir("/sys/devices/system/node/");
+ if (!dir) {
+ return;
+ }
+
+ struct dirent* entry;
+ while ((entry = readdir(dir)) != NULL) {
+ if (entry->d_type != DT_DIR && entry->d_type != DT_UNKNOWN) {
+ continue;
+ }
+ if (ffStrStartsWith(entry->d_name, "node") && ffCharIsDigit(entry->d_name[strlen("node")])) {
+ cpu->numaNodes++;
+ }
+ }
+}
+
+#ifdef __ANDROID__
+ #include "common/settings.h"
+
+static void detectQualcomm(FFCPUResult* cpu) {
+ // https://en.wikipedia.org/wiki/List_of_Qualcomm_Snapdragon_systems_on_chips
+
+ assert(cpu->name.length >= 2);
+ uint32_t code = (uint32_t) strtoul(cpu->name.chars + 2, NULL, 10);
+ const char* name = NULL;
+
+ switch (code) {
+ case 8845:
+ name = "8 Gen 5";
+ break; // ?
+ case 8850:
+ name = "8 Elite Gen 5";
+ break;
+ case 8735:
+ name = "8s Gen 4";
+ break;
+ case 8750:
+ name = "8 Elite";
+ break;
+ case 8635:
+ name = "8s Gen 3";
+ break;
+ case 8650:
+ name = "8 Gen 3";
+ break;
+ case 8550:
+ name = "8 Gen 2";
+ break;
+ case 8475:
+ name = "8+ Gen 1";
+ break;
+ case 8450:
+ name = "8 Gen 1";
+ break;
+ case 7750:
+ name = "7 Gen 4";
+ break;
+ case 7675:
+ name = "7+ Gen 3";
+ break;
+ case 7635:
+ name = "7s Gen 3";
+ break;
+ case 7550:
+ name = "7 Gen 3";
+ break;
+ case 7475:
+ name = "7+ Gen 2";
+ break;
+ case 7435:
+ name = "7s Gen 2";
+ break;
+ case 7450:
+ name = "7 Gen 1";
+ break;
+ case 6650:
+ name = "6 Gen 4";
+ break;
+ case 6375:
+ name = "6s Gen 3";
+ break;
+ case 6475:
+ name = "6 Gen 3";
+ break;
+ case 6115:
+ name = "6s Gen 1";
+ break;
+ case 6450:
+ name = "6 Gen 1";
+ break;
+ case 4635:
+ name = "4s Gen 2";
+ break;
+ case 4450:
+ name = "4 Gen 2";
+ break;
+ case 4375:
+ name = "4 Gen 1";
+ break;
+ }
+
+ if (name) {
+ char str[32];
+ ffStrCopy(str, cpu->name.chars, sizeof(str));
+ ffStrbufSetF(&cpu->name, "Qualcomm Snapdragon %s [%s]", name, str);
+ return;
+ }
+}
+
+static void detectMediaTek(FFCPUResult* cpu) {
+ // https://en.wikipedia.org/wiki/List_of_MediaTek_systems_on_chips
+
+ assert(cpu->name.length >= 2);
+ uint32_t code = (uint32_t) strtoul(cpu->name.chars + 2, NULL, 10);
+ const char* name = NULL;
+
+ switch (code) // The SOC code of MTK Dimensity series is full of mess
+ {
+ case 6993:
+ name = "9500";
+ break;
+ case 6991:
+ name = "9400";
+ break;
+ case 6989:
+ case 8796:
+ name = "9300";
+ break;
+ case 6985:
+ name = "9200";
+ break;
+ case 6983:
+ case 8798:
+ name = "9000";
+ break;
+
+ case 6899:
+ name = "8400";
+ break;
+ case 6897:
+ case 8792:
+ name = "8300";
+ break;
+ case 6896:
+ name = "8200";
+ break;
+ case 8795:
+ name = "8100";
+ break;
+ case 6895:
+ name = "8000";
+ break;
+ }
+
+ if (name) {
+ char str[32];
+ ffStrCopy(str, cpu->name.chars, sizeof(str));
+ ffStrbufSetF(&cpu->name, "MediaTek Dimensity %s [%s]", name, str);
+ return;
+ }
+}
+
+static void detectExynos(FFCPUResult* cpu) {
+ // https://en.wikipedia.org/wiki/Exynos
+
+ assert(cpu->name.length > 3);
+ uint32_t code = (uint32_t) strtoul(cpu->name.chars + 3, NULL, 10);
+ const char* name = NULL;
+
+ switch (code) {
+ case 9965:
+ name = "2600";
+ break;
+ case 9955:
+ name = "2500";
+ break;
+ case 9945:
+ name = "2400";
+ break;
+ // No 2300
+ case 9925:
+ name = "2200";
+ break;
+ case 9840:
+ name = "2100";
+ break;
+
+ case 8855:
+ name = "1580";
+ break;
+ case 8845:
+ name = "1480";
+ break;
+ case 8835:
+ name = "1380";
+ break;
+ case 8535:
+ name = "1330";
+ break;
+ case 8825:
+ name = "1280";
+ break;
+ case 9815:
+ name = "1080";
+ break;
+
+ case 9830:
+ name = "990";
+ break;
+ case 9630:
+ name = "980";
+ break;
+
+ case 8805:
+ name = "880";
+ break;
+ case 3830:
+ name = "850";
+ break;
+ }
+
+ if (name) {
+ char str[32];
+ ffStrCopy(str, cpu->name.chars, sizeof(str));
+ ffStrbufSetF(&cpu->name, "Samsung Exynos %s [%s]", name, str);
+ return;
+ }
+}
+
+static void detectAndroid(FFCPUResult* cpu) {
+ if (cpu->name.length == 0) {
+ if (ffSettingsGetAndroidProperty("ro.soc.model", &cpu->name)) {
+ ffStrbufClear(&cpu->vendor); // We usually detect the vendor of CPU core as ARM, but instead we want the vendor of SOC
+ }
+ }
+ if (cpu->vendor.length == 0) {
+ if (!ffSettingsGetAndroidProperty("ro.soc.manufacturer", &cpu->vendor)) {
+ if (!ffSettingsGetAndroidProperty("ro.product.product.manufacturer", &cpu->vendor)) {
+ if (!ffSettingsGetAndroidProperty("ro.product.vendor.manufacturer", &cpu->vendor)) {
+ if (ffSettingsGetAndroidProperty("ro.mediatek.platform", &cpu->name)) {
+ ffStrbufSetStatic(&cpu->vendor, "MediaTek");
+ }
+ }
+ }
+ }
+ }
+
+ if (ffStrbufEqualS(&cpu->vendor, "QTI")) {
+ ffStrbufSetStatic(&cpu->vendor, "Qualcomm");
+ } else if (ffStrbufIgnCaseEqualS(&cpu->vendor, "MediaTek")) { // sometimes "Mediatek"
+ ffStrbufSetStatic(&cpu->vendor, "MediaTek");
+ } else if (cpu->vendor.length > 0) {
+ cpu->vendor.chars[0] = (char) toupper(cpu->vendor.chars[0]);
+ }
+
+ if (ffStrbufEqualS(&cpu->vendor, "Qualcomm") && ffStrbufStartsWithS(&cpu->name, "SM")) {
+ detectQualcomm(cpu);
+ } else if (ffStrbufEqualS(&cpu->vendor, "MediaTek") && ffStrbufStartsWithS(&cpu->name, "MT")) {
+ detectMediaTek(cpu);
+ } else if (ffStrbufEqualS(&cpu->vendor, "Samsung") && ffStrbufStartsWithS(&cpu->name, "s5e")) {
+ cpu->name.chars[0] = 'S';
+ cpu->name.chars[2] = 'E';
+ detectExynos(cpu);
+ }
+}
+#endif
+
+#if __arm__ || __aarch64__
+ #include "cpu_arm.h"
+
+static void detectArmName(FFstrbuf* cpuinfo, FFCPUResult* cpu, uint32_t implId) {
+ char* line = NULL;
+ size_t len = 0;
+ uint32_t lastPartId = UINT32_MAX;
+ uint32_t num = 0;
+ while (ffStrbufGetline(&line, &len, cpuinfo)) {
+ if (!ffStrStartsWith(line, "CPU part\t: ")) {
+ continue;
+ }
+ uint32_t partId = (uint32_t) strtoul(line + strlen("CPU part\t: "), NULL, 16);
+ const char* name = NULL;
+ switch (implId) {
+ case 0x41:
+ name = armPartId2name(partId);
+ break;
+ case 0x42:
+ name = brcmPartId2name(partId);
+ break;
+ case 0x43:
+ name = caviumPartId2name(partId);
+ break;
+ case 0x44:
+ name = decPartId2name(partId);
+ break;
+ case 0x46:
+ name = fujitsuPartId2name(partId);
+ break;
+ case 0x48:
+ name = hisiPartId2name(partId);
+ break;
+ case 0x4e:
+ name = nvidiaPartId2name(partId);
+ break;
+ case 0x50:
+ name = apmPartId2name(partId);
+ break;
+ case 0x51:
+ name = qcomPartId2name(partId);
+ break;
+ case 0x53:
+ name = samsungPartId2name(partId);
+ break;
+ case 0x56:
+ name = marvellPartId2name(partId);
+ break;
+ case 0x61:
+ if (partId == 0) {
+ // https://github.com/Dr-Noob/cpufetch/issues/213#issuecomment-1927782105
+ ffStrbufSetStatic(&cpu->name, "Virtualized Apple Silicon");
+ ffStrbufGetlineRestore(&line, &len, cpuinfo);
+ return;
+ }
+ name = applePartId2name(partId);
+ break;
+ case 0x66:
+ name = faradayPartId2name(partId);
+ break;
+ case 0x69:
+ name = intelPartId2name(partId);
+ break;
+ case 0x6d:
+ name = msPartId2name(partId);
+ break;
+ case 0x70:
+ name = ftPartId2name(partId);
+ break;
+ case 0xc0:
+ name = amperePartId2name(partId);
+ break;
+ }
+ if (lastPartId != partId) {
+ if (lastPartId != UINT32_MAX) {
+ if (num > 1) {
+ ffStrbufAppendF(&cpu->name, "*%u", num);
+ }
+ ffStrbufAppendS(&cpu->name, " + ");
+ }
+ if (name) {
+ ffStrbufAppendS(&cpu->name, name);
+ } else if (partId) {
+ ffStrbufAppendF(&cpu->name, "%s-%X", cpu->vendor.chars, partId);
+ } else {
+ ffStrbufAppend(&cpu->name, &cpu->vendor);
+ }
+ lastPartId = partId;
+ num = 1;
+ } else {
+ ++num;
+ }
+ }
+ if (num > 1) {
+ ffStrbufAppendF(&cpu->name, "*%u", num);
+ }
+}
+#endif
+
+static const char* parseCpuInfo(
+ FFstrbuf* cpuinfo,
+ FFCPUResult* cpu,
+ FF_A_UNUSED FFstrbuf* physicalCoresBuffer,
+ FF_A_UNUSED FFstrbuf* cpuMHz,
+ FF_A_UNUSED FFstrbuf* cpuIsa,
+ FF_A_UNUSED FFstrbuf* cpuUarch,
+ FF_A_UNUSED FFstrbuf* cpuImplementer) {
+ char* line = NULL;
+ size_t len = 0;
+
+ while (ffStrbufGetline(&line, &len, cpuinfo)) {
+ // Stop after reasonable information is acquired
+ if ((*line == '\0' || *line == '\n') && cpu->name.length > 0) {
+ ffStrbufGetlineRestore(&line, &len, cpuinfo);
+ break;
+ }
+
+ (void) (
+// arm64 doesn't have "model name"; arm32 does have "model name" but its value is not useful.
+// "Hardware" should always be used in this case
+#if __x86_64__ || __i386__
+ (cpu->name.length == 0 && ffParsePropLine(line, "model name :", &cpu->name)) ||
+ (cpu->vendor.length == 0 && ffParsePropLine(line, "vendor_id :", &cpu->vendor)) ||
+ (physicalCoresBuffer->length == 0 && ffParsePropLine(line, "cpu cores :", physicalCoresBuffer)) ||
+ (cpuMHz->length == 0 && ffParsePropLine(line, "cpu MHz :", cpuMHz)) ||
+#elif __arm__ || __aarch64__
+ (cpuImplementer->length == 0 && ffParsePropLine(line, "CPU implementer :", cpuImplementer)) ||
+ (cpu->name.length == 0 && ffParsePropLine(line, "Hardware :", &cpu->name)) || // For Android devices
+#elif __powerpc__ || __powerpc
+ (cpuMHz->length == 0 && ffParsePropLine(line, "clock :", cpuMHz)) ||
+ (cpu->name.length == 0 && ffParsePropLine(line, "cpu :", &cpu->name)) ||
+#elif __mips__ || __mips
+ (cpu->name.length == 0 && ffParsePropLine(line, "cpu model :", &cpu->name)) ||
+#elif __loongarch__
+ (cpu->name.length == 0 && ffParsePropLine(line, "Model Name :", &cpu->name)) ||
+ (cpuMHz->length == 0 && ffParsePropLine(line, "CPU MHz :", cpuMHz)) ||
+#elif __riscv__ || __riscv
+ (cpuIsa->length == 0 && ffParsePropLine(line, "isa :", cpuIsa)) ||
+ (cpuUarch->length == 0 && ffParsePropLine(line, "uarch :", cpuUarch)) ||
+#elif __s390x__
+ (cpu->name.length == 0 && ffParsePropLine(line, "machine :", &cpu->name)) ||
+ (cpu->vendor.length == 0 && ffParsePropLine(line, "vendor_id :", &cpu->vendor)) ||
+ (cpuMHz->length == 0 && ffParsePropLine(line, "cpu MHz static :", cpuMHz)) ||
+#elif __ia64__
+ (cpu->name.length == 0 && ffParsePropLine(line, "model name :", &cpu->name)) ||
+ (cpu->vendor.length == 0 && ffParsePropLine(line, "vendor :", &cpu->vendor)) ||
+ (cpuMHz->length == 0 && ffParsePropLine(line, "cpu MHz :", cpuMHz)) ||
+#elif __hppa__
+ (cpu->name.length == 0 && ffParsePropLine(line, "cpu :", &cpu->name)) ||
+#elif __sh__
+ (cpu->name.length == 0 && ffParsePropLine(line, "cpu type :", &cpu->name)) ||
+#else
+ (cpu->name.length == 0 && ffParsePropLine(line, "model name :", &cpu->name)) ||
+ (cpu->name.length == 0 && ffParsePropLine(line, "model :", &cpu->name)) ||
+ (cpu->name.length == 0 && ffParsePropLine(line, "cpu model :", &cpu->name)) ||
+ (cpu->name.length == 0 && ffParsePropLine(line, "hardware :", &cpu->name)) ||
+ (cpu->name.length == 0 && ffParsePropLine(line, "processor :", &cpu->name)) ||
+#endif
+
+ false);
+ }
+
+ return NULL;
+}
+
+static uint32_t getFrequency(FFstrbuf* basePath, const char* cpuinfoFileName, const char* scalingFileName, FFstrbuf* buffer) {
+ uint32_t baseLen = basePath->length;
+ ffStrbufAppendS(basePath, cpuinfoFileName);
+ bool ok = ffReadFileBuffer(basePath->chars, buffer);
+ ffStrbufSubstrBefore(basePath, baseLen);
+ if (ok) {
+ return (uint32_t) (ffStrbufToUInt(buffer, 0) / 1000);
+ }
+
+ if (scalingFileName) {
+ ffStrbufAppendS(basePath, scalingFileName);
+ ok = ffReadFileBuffer(basePath->chars, buffer);
+ ffStrbufSubstrBefore(basePath, baseLen);
+ if (ok) {
+ return (uint32_t) (ffStrbufToUInt(buffer, 0) / 1000);
+ }
+ }
+
+ return 0;
+}
+
+static uint8_t getNumCores(FFstrbuf* basePath, FFstrbuf* buffer) {
+ uint32_t baseLen = basePath->length;
+ ffStrbufAppendS(basePath, "/affected_cpus");
+ bool ok = ffReadFileBuffer(basePath->chars, buffer);
+ ffStrbufSubstrBefore(basePath, baseLen);
+ if (ok) {
+ return (uint8_t) (ffStrbufCountC(buffer, ' ') + 1);
+ }
+
+ ffStrbufAppendS(basePath, "/related_cpus");
+ ok = ffReadFileBuffer(basePath->chars, buffer);
+ ffStrbufSubstrBefore(basePath, baseLen);
+ if (ok) {
+ return (uint8_t) (ffStrbufCountC(buffer, ' ') + 1);
+ }
+
+ return 0;
+}
+
+static bool detectFrequency(FFCPUResult* cpu, const FFCPUOptions* options) {
+ FF_STRBUF_AUTO_DESTROY path = ffStrbufCreateS("/sys/devices/system/cpu/cpufreq/");
+ FF_AUTO_CLOSE_DIR DIR* dir = opendir(path.chars);
+ if (!dir) {
+ return false;
+ }
+
+ FF_STRBUF_AUTO_DESTROY buffer = ffStrbufCreate();
+ uint32_t baseLen = path.length;
+
+ struct dirent* entry;
+ while ((entry = readdir(dir)) != NULL) {
+ if (ffStrStartsWith(entry->d_name, "policy") && ffCharIsDigit(entry->d_name[strlen("policy")])) {
+ ffStrbufAppendS(&path, entry->d_name);
+
+ uint32_t fmax = getFrequency(&path, "/cpuinfo_max_freq", "/scaling_max_freq", &buffer);
+ if (fmax == 0) {
+ continue;
+ }
+
+ if (cpu->frequencyMax >= fmax) {
+ if (!options->showPeCoreCount) {
+ ffStrbufSubstrBefore(&path, baseLen);
+ continue;
+ }
+ } else {
+ cpu->frequencyMax = fmax;
+ }
+
+ uint32_t fbase = getFrequency(&path, "/base_frequency", NULL, &buffer);
+ if (fbase > 0) {
+ cpu->frequencyBase = cpu->frequencyBase > fbase ? cpu->frequencyBase : fbase;
+ }
+
+ if (options->showPeCoreCount) {
+ uint32_t freq = fbase == 0 ? fmax : fbase; // seems base frequencies are more stable
+ uint32_t ifreq = 0;
+ while (cpu->coreTypes[ifreq].freq != freq && cpu->coreTypes[ifreq].freq > 0) {
+ ++ifreq;
+ }
+ if (cpu->coreTypes[ifreq].freq == 0) {
+ cpu->coreTypes[ifreq].freq = freq;
+ }
+ cpu->coreTypes[ifreq].count += getNumCores(&path, &buffer);
+ }
+ ffStrbufSubstrBefore(&path, baseLen);
+ }
+ }
+ return true;
+}
+
+#if __i386__ || __x86_64__
+
+FF_A_UNUSED static uint16_t getPackageCount(FFstrbuf* cpuinfo) {
+ const char* p = cpuinfo->chars;
+ uint64_t low = 0, high = 0;
+
+ while ((p = memmem(p, cpuinfo->length - (uint32_t) (p - cpuinfo->chars), "\nphysical id\t:", strlen("\nphysical id\t:")))) {
+ p += strlen("\nphysical id\t:");
+ char* pend;
+ unsigned long long id = strtoul(p, &pend, 10);
+ if (__builtin_expect(id > 64, false)) { // Do 129-socket boards exist?
+ high |= 1ULL << (id - 64);
+ } else {
+ low |= 1ULL << id;
+ }
+ p = pend;
+ }
+
+ return (uint16_t) (__builtin_popcountll(low) + __builtin_popcountll(high));
+}
+
+FF_A_UNUSED static const char* detectCPUX86(const FFCPUOptions* options, FFCPUResult* cpu) {
+ FF_STRBUF_AUTO_DESTROY cpuinfo = ffStrbufCreateA(PROC_FILE_BUFFSIZ);
+ if (!ffReadFileBuffer(FF_CPUINFO_PATH, &cpuinfo) || cpuinfo.length == 0) {
+ return "ffReadFileBuffer(\"" FF_CPUINFO_PATH "\") failed";
+ }
+
+ FF_STRBUF_AUTO_DESTROY physicalCoresBuffer = ffStrbufCreate();
+ FF_STRBUF_AUTO_DESTROY cpuMHz = ffStrbufCreate();
+ const char* error = parseCpuInfo(&cpuinfo, cpu, &physicalCoresBuffer, &cpuMHz, NULL, NULL, NULL);
+ if (error) {
+ return error;
+ }
+
+ cpu->coresLogical = (uint16_t) get_nprocs_conf();
+ cpu->coresOnline = (uint16_t) get_nprocs();
+ cpu->packages = getPackageCount(&cpuinfo);
+ cpu->coresPhysical = (uint16_t) ffStrbufToUInt(&physicalCoresBuffer, 0); // physical cores in single package
+ if (cpu->coresPhysical > 0 && cpu->packages > 1) {
+ cpu->coresPhysical *= cpu->packages;
+ }
+
+ // Ref https://github.com/fastfetch-cli/fastfetch/issues/1194#issuecomment-2295058252
+ ffCPUDetectByCpuid(cpu);
+ if (!detectFrequency(cpu, options) || cpu->frequencyBase == 0) {
+ cpu->frequencyBase = (uint32_t) ffStrbufToUInt(&cpuMHz, 0);
+ }
+
+ detectNumaNodes(cpu);
+
+ return NULL;
+}
+
+#else
+
+static const char* detectPhysicalCores(FFCPUResult* cpu) {
+ int dfd = open("/sys/devices/system/cpu/", O_RDONLY | O_DIRECTORY | O_CLOEXEC);
+ if (dfd < 0) {
+ return "open(\"/sys/devices/system/cpu/\") failed";
+ }
+
+ FF_AUTO_CLOSE_DIR DIR* dir = fdopendir(dfd);
+ if (!dir) {
+ return "fdopendir(dfd) failed";
+ }
+
+ uint64_t pkgLow = 0, pkgHigh = 0;
+
+ struct dirent* entry;
+ FF_LIST_AUTO_DESTROY cpuList = ffListCreate();
+ while ((entry = readdir(dir)) != NULL) {
+ if (entry->d_type != DT_DIR || !ffStrStartsWith(entry->d_name, "cpu") || !ffCharIsDigit(entry->d_name[strlen("cpu")])) {
+ continue;
+ }
+
+ FF_AUTO_CLOSE_FD int cpuxfd = openat(dirfd(dir), entry->d_name, O_RDONLY | O_DIRECTORY);
+ if (cpuxfd < 0) {
+ continue;
+ }
+
+ char buf[128];
+
+ // Check if the directory contains a file named "topology/physical_package_id"
+ // that lists the physical package id of the CPU.
+
+ ssize_t len = ffReadFileDataRelative(cpuxfd, "topology/physical_package_id", sizeof(buf) - 1, buf);
+ if (len > 0) {
+ buf[len] = '\0';
+ unsigned long long id = strtoul(buf, NULL, 10);
+ if (__builtin_expect(id > 64, false)) { // Do 129-socket boards exist?
+ pkgHigh |= 1ULL << (id - 64);
+ } else {
+ pkgLow |= 1ULL << id;
+ }
+ }
+
+ // Check if the directory contains a file named "topology/core_cpus_list"
+ // that lists the physical cores in the package.
+
+ len = ffReadFileDataRelative(cpuxfd, "topology/core_cpus_list", sizeof(buf) - 1, buf);
+ if (len > 0) {
+ buf[len] = '\0'; // low-high or low
+
+ for (const char* p = buf; *p;) {
+ char* pend;
+ uint32_t coreId = (uint32_t) strtoul(p, &pend, 10);
+ if (pend == p) {
+ break;
+ }
+
+ bool found = false;
+ FF_LIST_FOR_EACH (uint32_t, id, cpuList) {
+ if (*id == coreId) {
+ // This core is already counted
+ found = true;
+ break;
+ }
+ }
+ if (!found) {
+ *FF_LIST_ADD(uint32_t, cpuList) = coreId;
+ }
+
+ p = strchr(pend, ',');
+ if (!p) {
+ break;
+ }
+ ++p;
+ }
+ }
+ }
+
+ cpu->coresPhysical = (uint16_t) cpuList.length;
+ cpu->packages = (uint16_t) (__builtin_popcountll(pkgLow) + __builtin_popcountll(pkgHigh));
+ return NULL;
+}
+
+FF_A_UNUSED static void parseIsa(FFstrbuf* cpuIsa) {
+ // Always use the last part of the ISA string. Ref: #590 #1204
+ ffStrbufSubstrAfterLastC(cpuIsa, ' ');
+
+ if (ffStrbufStartsWithS(cpuIsa, "rv")) {
+ // RISC-V ISA string example: "rv64imafdch_zicsr_zifencei".
+ // The _z parts are not important for CPU showcasing, so we remove them.
+ if (ffStrbufContainC(cpuIsa, '_')) {
+ ffStrbufSubstrBeforeFirstC(cpuIsa, '_');
+ }
+ // Then we replace "imafd" with "g" since "g" is a shorthand.
+ if (ffStrbufContainS(cpuIsa, "imafd")) {
+ // Remove 4 of the 5 characters and replace the remaining one with "g".
+ ffStrbufRemoveSubstr(cpuIsa, 4, 8);
+ cpuIsa->chars[4] = 'g';
+ }
+ // The final ISA output of the above example is "rv64gch".
+ }
+}
+
+FF_A_UNUSED static void detectSocName(FFCPUResult* cpu) {
+ if (cpu->name.length > 0) {
+ return;
+ }
+
+ // [x-vendor,x-model\0]*N
+ char content[512];
+ ssize_t length = ffReadFileData("/sys/firmware/devicetree/base/compatible", ARRAY_SIZE(content), content);
+ if (length < 4) {
+ return; // v,m\0
+ }
+
+ if (content[length - 1] != '\0') {
+ return; // must end with \0
+ }
+
+ --length;
+
+ char* vendor = NULL;
+ char* model = NULL;
+
+ for (char* p; length > 0; length = p ? (ssize_t) (p - content) - 1 : 0) {
+ p = memrchr(content, '\0', (size_t) length);
+
+ vendor = p /* first entry */ ? p + 1 : content;
+
+ size_t partLen = (size_t) (length - (vendor - content));
+ if (partLen < 3) {
+ continue;
+ }
+
+ char* comma = memchr(vendor, ',', partLen);
+ if (!comma) {
+ continue;
+ }
+
+ size_t vendorLen = (size_t) (comma - vendor);
+ if (vendorLen == 0) {
+ continue;
+ }
+
+ model = comma + 1;
+ size_t modelLen = (size_t) (partLen - (size_t) (model - vendor));
+ if (modelLen == 0) {
+ continue;
+ }
+
+ if ((modelLen >= strlen("-platform") && ffStrEndsWith(model, "-platform")) ||
+ (modelLen >= strlen("-soc") && ffStrEndsWith(model, "-soc"))) {
+ continue;
+ }
+
+ *comma = '\0';
+ break;
+ }
+
+ if (!length) {
+ return;
+ }
+
+ if (false) {
+ }
+ #if __aarch64__
+ else if (ffStrEquals(vendor, "apple")) {
+ // https://elixir.bootlin.com/linux/v6.11/source/arch/arm64/boot/dts/apple
+ if (model[0] == 't') {
+ uint32_t deviceId = (uint32_t) strtoul(model + 1, NULL, 10);
+ ffStrbufSetStatic(&cpu->name, ffCPUAppleCodeToName(deviceId));
+
+ if (!cpu->name.length) {
+ ffStrbufSetS(&cpu->name, "Apple Silicon ");
+ ffStrbufAppendS(&cpu->name, model);
+ }
+ } else {
+ ffStrbufSetS(&cpu->name, model);
+ }
+
+ ffStrbufSetStatic(&cpu->vendor, "Apple");
+ }
+ #endif
+ else if (ffStrEquals(vendor, "qcom")) {
+ // https://elixir.bootlin.com/linux/v6.11/source/arch/arm64/boot/dts/qcom
+ if (ffStrStartsWith(model, "x")) {
+ ffStrbufSetS(&cpu->name, "Qualcomm Snapdragon X Elite ");
+ for (const char* p = model + 1; *p; ++p) {
+ ffStrbufAppendC(&cpu->name, (char) toupper(*p));
+ }
+ } else if (ffStrStartsWith(model, "sc")) {
+ const char* code = model + 2;
+ uint32_t deviceId = (uint32_t) strtoul(code, NULL, 10);
+ ffStrbufSetStatic(&cpu->name, ffCPUQualcommCodeToName(deviceId));
+ if (!cpu->name.length) {
+ ffStrbufAppendS(&cpu->name, "Qualcomm Snapdragon SC");
+ ffStrbufAppendS(&cpu->name, code);
+ }
+ } else {
+ ffStrbufSetS(&cpu->name, model);
+ }
+
+ ffStrbufSetStatic(&cpu->vendor, "Qualcomm");
+ } else if (ffStrEquals(vendor, "brcm")) {
+ // Raspberry Pi
+ ffStrbufSetStatic(&cpu->vendor, "Broadcom");
+ for (const char* p = model; *p; ++p) {
+ ffStrbufAppendC(&cpu->name, (char) toupper(*p));
+ }
+ } else if (ffStrEquals(vendor, "thead")) {
+ // Lichee Pi?
+ ffStrbufSetStatic(&cpu->vendor, "T-Head");
+ for (const char* p = model; *p; ++p) {
+ ffStrbufAppendC(&cpu->name, (char) toupper(*p));
+ }
+ } else {
+ ffStrbufSetS(&cpu->name, model);
+ ffStrbufSetS(&cpu->vendor, vendor);
+ cpu->vendor.chars[0] = (char) toupper(vendor[0]);
+ }
+}
+
+ #ifdef __loongarch__
+FF_A_UNUSED static uint16_t getLoongarchPropCount(FFstrbuf* cpuinfo, const char* key) {
+ const char* p = cpuinfo->chars;
+ uint64_t low = 0, high = 0;
+ uint32_t keylen = (uint32_t) strlen(key);
+
+ while ((p = memmem(p, cpuinfo->length - (uint32_t) (p - cpuinfo->chars), key, keylen))) {
+ p += keylen;
+ char* pend;
+ unsigned long id = strtoul(p, &pend, 10);
+ if (__builtin_expect(id > 64, false)) {
+ high |= 1UL << (id - 64);
+ } else {
+ low |= 1UL << id;
+ }
+ p = pend;
+ }
+
+ return (uint16_t) (__builtin_popcountll(low) + __builtin_popcountll(high));
+}
+ #endif
+
+FF_A_UNUSED static const char* detectCPUOthers(const FFCPUOptions* options, FFCPUResult* cpu) {
+ cpu->coresLogical = (uint16_t) get_nprocs_conf();
+ cpu->coresOnline = (uint16_t) get_nprocs();
+
+ #if __ANDROID__
+ detectAndroid(cpu);
+ #elif !__powerpc__ && !__powerpc
+ detectSocName(cpu);
+ #endif
+
+ detectFrequency(cpu, options);
+
+ if (cpu->name.length == 0) {
+ FF_STRBUF_AUTO_DESTROY cpuinfo = ffStrbufCreateA(PROC_FILE_BUFFSIZ);
+ if (!ffReadFileBuffer(FF_CPUINFO_PATH, &cpuinfo) || cpuinfo.length == 0) {
+ return "ffReadFileBuffer(\"" FF_CPUINFO_PATH "\") failed";
+ }
+
+ FF_STRBUF_AUTO_DESTROY cpuMHz = ffStrbufCreate();
+ FF_STRBUF_AUTO_DESTROY cpuIsa = ffStrbufCreate();
+ FF_STRBUF_AUTO_DESTROY cpuUarch = ffStrbufCreate();
+ FF_STRBUF_AUTO_DESTROY cpuImplementerStr = ffStrbufCreate();
+
+ const char* error = parseCpuInfo(&cpuinfo, cpu, NULL, &cpuMHz, &cpuIsa, &cpuUarch, &cpuImplementerStr);
+ if (error) {
+ return error;
+ }
+
+ if (cpu->frequencyBase == 0) {
+ cpu->frequencyBase = (uint32_t) ffStrbufToUInt(&cpuMHz, 0);
+ }
+
+ #if __arm__ || __aarch64__
+ uint32_t cpuImplementer = (uint32_t) strtoul(cpuImplementerStr.chars, NULL, 16);
+ ffStrbufSetStatic(&cpu->vendor, hwImplId2Vendor(cpuImplementer));
+
+ if (cpu->name.length == 0) {
+ detectArmName(&cpuinfo, cpu, cpuImplementer);
+ }
+ #elif __riscv__ || __riscv
+ if (cpu->name.length == 0) {
+ if (cpuUarch.length > 0) {
+ if (cpu->name.length > 0) {
+ ffStrbufAppendC(&cpu->name, ' ');
+ }
+ ffStrbufAppend(&cpu->name, &cpuUarch);
+ }
+
+ if (cpuIsa.length > 0) {
+ parseIsa(&cpuIsa);
+ if (cpu->name.length > 0) {
+ ffStrbufAppendC(&cpu->name, ' ');
+ }
+ ffStrbufAppend(&cpu->name, &cpuIsa);
+ }
+ }
+ #elif __loongarch__
+ cpu->packages = getLoongarchPropCount(&cpuinfo, "\npackage\t\t\t:");
+ cpu->coresPhysical = getLoongarchPropCount(&cpuinfo, "\ncore\t\t\t:");
+ if (cpu->packages > 1) {
+ cpu->coresPhysical *= cpu->packages;
+ }
+ #elif __s390x__
+ if (cpu->name.length) {
+ ffStrbufPrependS(&cpu->name, "Machine ");
+ }
+ #endif
+ }
+
+ if (cpu->coresPhysical == 0) {
+ detectPhysicalCores(cpu);
+ }
+
+ ffCPUDetectByCpuid(cpu);
+ detectNumaNodes(cpu);
+
+ return NULL;
+}
+#endif
+
+const char* ffDetectCPUImpl(const FFCPUOptions* options, FFCPUResult* cpu) {
+ cpu->temperature = options->temp ? detectCPUTemp(options) : FF_CPU_TEMP_UNSET;
+
+#if __x86_64__ || __i386__
+ return detectCPUX86(options, cpu);
+#else
+ return detectCPUOthers(options, cpu);
+#endif
+}
diff --git a/src/detection/cpu/cpu_nbsd.c b/src/detection/cpu/cpu_nbsd.c
new file mode 100644
index 0000000..9c88c0e
--- /dev/null
+++ b/src/detection/cpu/cpu_nbsd.c
@@ -0,0 +1,109 @@
+#include "cpu.h"
+#include "common/sysctl.h"
+#include "common/io.h"
+
+#include <sys/envsys.h>
+#include <prop/proplib.h>
+#include <paths.h>
+#include <time.h>
+#include <unistd.h>
+#include <fcntl.h>
+
+static void freePropDict(prop_dictionary_t* pdict) {
+ assert(pdict != NULL);
+ if (*pdict == NULL) {
+ return;
+ }
+ prop_object_release(*pdict);
+}
+
+static const char* detectCpuTemp(const FFCPUOptions* options, double* current) {
+ FF_AUTO_CLOSE_FD int fd = open(_PATH_SYSMON, O_RDONLY | O_CLOEXEC);
+ if (fd < 0) {
+ return "open(_PATH_SYSMON, O_RDONLY | O_CLOEXEC) failed";
+ }
+
+ FF_A_CLEANUP(freePropDict) prop_dictionary_t root = NULL;
+ if (prop_dictionary_recv_ioctl(fd, ENVSYS_GETDICTIONARY, &root) < 0) {
+ return "prop_dictionary_recv_ioctl(ENVSYS_GETDICTIONARY) failed";
+ }
+
+ prop_array_t array;
+
+ if (options->tempSensor.length > 0) {
+ array = prop_dictionary_get(root, options->tempSensor.chars);
+ if (!array) {
+ return "No temp data found in specified sensor";
+ }
+ } else {
+ array = prop_dictionary_get(root, "coretemp0");
+ if (!array) {
+ array = prop_dictionary_get(root, "amdzentemp0");
+ }
+ if (!array) {
+ array = prop_dictionary_get(root, "viac7temp0");
+ }
+ if (!array) {
+ array = prop_dictionary_get(root, "acpitz0"); // Thermal Zones
+ }
+ if (!array) {
+ return "No temp data found in root dictionary";
+ }
+ }
+
+ if (prop_array_count(array) != 2) {
+ return "Unexpected `xtemp0` data";
+ }
+
+ prop_dictionary_t dict = prop_array_get(array, 0);
+ if (prop_object_type(dict) != PROP_TYPE_DICTIONARY) {
+ return "Unexpected `xtemp0[0]`";
+ }
+
+ int temp = 0; // in µK
+ if (!prop_dictionary_get_int(dict, "cur-value", &temp)) {
+ return "Failed to get temperature";
+ }
+
+ *current = temp / 1e6 - 273.15;
+
+ return NULL;
+}
+
+const char* ffDetectCPUImpl(const FFCPUOptions* options, FFCPUResult* cpu) {
+ if (ffSysctlGetString("machdep.cpu_brand", &cpu->name) != NULL &&
+ ffSysctlGetString("machdep.dmi.processor-version", &cpu->name) != NULL &&
+ ffSysctlGetString("hw.cpu0.name", &cpu->name) != NULL &&
+ ffSysctlGetString("hw.model", &cpu->name) != NULL) {
+ ffStrbufSetS(&cpu->name, "Unknown CPU");
+ }
+
+ if (ffSysctlGetString("machdep.dmi.processor-vendor", &cpu->vendor) == NULL) {
+ ffStrbufTrimRightSpace(&cpu->vendor);
+ }
+
+ cpu->coresPhysical = (uint16_t) ffSysctlGetInt("hw.ncpu", 1);
+ cpu->coresLogical = cpu->coresPhysical;
+ cpu->coresOnline = (uint16_t) ffSysctlGetInt("hw.ncpuonline", cpu->coresLogical);
+
+ ffCPUDetectByCpuid(cpu);
+
+ uint32_t freq = (uint32_t) ffSysctlGetInt("machdep.cpu.frequency.target", 0);
+ if (freq == 0) {
+ freq = (uint32_t) (ffSysctlGetInt64("hw.cpu0.clock_frequency", 0) / 1000000);
+ }
+ if (freq == 0) {
+ freq = (uint32_t) ffSysctlGetInt("machdep.dmi.processor-frequency", 0);
+ }
+ if (freq > cpu->frequencyBase) {
+ cpu->frequencyBase = freq;
+ }
+
+ cpu->temperature = FF_CPU_TEMP_UNSET;
+
+ if (options->temp) {
+ detectCpuTemp(options, &cpu->temperature);
+ }
+
+ return NULL;
+}
diff --git a/src/detection/cpu/cpu_nosupport.c b/src/detection/cpu/cpu_nosupport.c
new file mode 100644
index 0000000..309883d
--- /dev/null
+++ b/src/detection/cpu/cpu_nosupport.c
@@ -0,0 +1,5 @@
+#include "cpu.h"
+
+const char* ffDetectCPUImpl(FF_A_UNUSED const FFCPUOptions* options, FF_A_UNUSED FFCPUResult* cpu) {
+ return "Not supported on this platform";
+}
diff --git a/src/detection/cpu/cpu_obsd.c b/src/detection/cpu/cpu_obsd.c
new file mode 100644
index 0000000..e5b8d2f
--- /dev/null
+++ b/src/detection/cpu/cpu_obsd.c
@@ -0,0 +1,78 @@
+#include "cpu.h"
+#include "common/sysctl.h"
+#include "common/strutil.h"
+
+#include <errno.h>
+#include <sys/time.h>
+#include <sys/sensors.h>
+
+static const char* detectCPUTemp(const FFCPUOptions* options, FFCPUResult* cpu) {
+ int mib[5] = { CTL_HW, HW_SENSORS, 0, SENSOR_TEMP, 0 };
+
+ for (mib[2] = 0; mib[2] < 1024; mib[2]++) {
+ struct sensordev sensordev;
+ size_t sdlen = sizeof(struct sensordev);
+ if (sysctl(mib, 3, &sensordev, &sdlen, NULL, 0) < 0) {
+ if (errno == ENOENT) {
+ break;
+ }
+ if (errno == ENXIO) {
+ continue;
+ }
+ return "sysctl(sensordev) failed";
+ }
+
+ if (options->tempSensor.length > 0) {
+ if (!ffStrbufEqualS(&options->tempSensor, sensordev.xname)) {
+ continue;
+ }
+ } else {
+ if (!ffStrStartsWith(sensordev.xname, "cpu")) {
+ continue;
+ }
+ }
+
+ for (mib[4] = 0; mib[4] < sensordev.maxnumt[SENSOR_TEMP]; mib[4]++) {
+ struct sensor sensor;
+ size_t slen = sizeof(struct sensor);
+ if (sysctl(mib, 5, &sensor, &slen, NULL, 0) < 0) {
+ if (errno != ENOENT) {
+ return "sysctl(sensor) failed";
+ }
+ continue;
+ }
+ if (sensor.flags & SENSOR_FINVALID) {
+ continue;
+ }
+
+ cpu->temperature = (double) (sensor.value - 273150000) / 1E6;
+ return NULL;
+ }
+ }
+
+ return "No sensor for CPU temp found";
+}
+
+const char* ffDetectCPUImpl(const FFCPUOptions* options, FFCPUResult* cpu) {
+ if (ffSysctlGetString(CTL_HW, HW_MODEL, &cpu->name)) {
+ return "sysctl(hw.model) failed";
+ }
+
+ cpu->coresPhysical = (uint16_t) ffSysctlGetInt(CTL_HW, HW_NCPU, 1);
+ cpu->coresLogical = cpu->coresPhysical;
+ cpu->coresOnline = (uint16_t) ffSysctlGetInt(CTL_HW, HW_NCPUONLINE, cpu->coresLogical);
+
+ ffCPUDetectByCpuid(cpu);
+
+ uint32_t cpuspeed = (uint32_t) ffSysctlGetInt(CTL_HW, HW_CPUSPEED, 0);
+ if (cpuspeed > cpu->frequencyBase) {
+ cpu->frequencyBase = cpuspeed;
+ }
+
+ cpu->temperature = FF_CPU_TEMP_UNSET;
+ if (options->temp) {
+ detectCPUTemp(options, cpu);
+ }
+
+ return NULL;
+}
diff --git a/src/detection/cpu/cpu_sunos.c b/src/detection/cpu/cpu_sunos.c
new file mode 100644
index 0000000..9b1cab5
--- /dev/null
+++ b/src/detection/cpu/cpu_sunos.c
@@ -0,0 +1,154 @@
+#include "cpu.h"
+#include "common/processing.h"
+#include "common/strutil.h"
+#include <kstat.h>
+
+static const char* detectCPUTempByKstat(const FFCPUOptions* options, kstat_ctl_t* kc, FFCPUResult* cpu) {
+ const char* possibleModules[] = { "temperature", "cpu_temp", "acpi_thermal", NULL };
+
+ if (options->tempSensor.length > 0) {
+ possibleModules[0] = options->tempSensor.chars;
+ possibleModules[1] = NULL;
+ }
+
+ for (int i = 0; possibleModules[i] != NULL; i++) {
+ kstat_t* ks = kstat_lookup(kc, possibleModules[i], -1, NULL);
+ if (ks && kstat_read(kc, ks, NULL) >= 0) {
+ kstat_named_t* kn = kstat_data_lookup(ks, "temperature");
+ if (kn) {
+ switch (kn->data_type) {
+ case KSTAT_DATA_INT32:
+ cpu->temperature = (float) kn->value.i32;
+ return NULL;
+ case KSTAT_DATA_UINT32:
+ cpu->temperature = (float) kn->value.ui32;
+ return NULL;
+ case KSTAT_DATA_FLOAT:
+ cpu->temperature = kn->value.f;
+ return NULL;
+ }
+ }
+ }
+ }
+
+ return "Failed to find CPU temperature using kstat";
+}
+
+static const char* detectCPUTempByIpmiTool(FFCPUResult* cpu) {
+ FF_STRBUF_AUTO_DESTROY buffer = ffStrbufCreate();
+ const char* error = ffProcessAppendStdOut(&buffer, (char* const[]) { "ipmitool", "-c", "sdr", "list", NULL });
+
+ if (error) {
+ return error;
+ }
+
+ char* line = NULL;
+ size_t len = 0;
+ while (ffStrbufGetline(&line, &len, &buffer)) {
+ if (sscanf(line, "CPU%*d Temp,%lf,degrees C,ok", &cpu->temperature) == 1) {
+ return NULL;
+ }
+ }
+
+ return "ipmitool sdr list failed to find CPU temperature";
+}
+
+static inline void kstatFreeWrap(kstat_ctl_t** pkc) {
+ assert(pkc);
+ if (*pkc) {
+ kstat_close(*pkc);
+ }
+}
+
+static inline uint16_t countTypeId(kstat_ctl_t* kc, const char* type) {
+ uint64_t low = 0, high = 0;
+ for (kstat_t* ksp = kc->kc_chain; ksp; ksp = ksp->ks_next) {
+ if (ffStrStartsWith(ksp->ks_module, "cpu_info")) {
+ if (kstat_read(kc, ksp, NULL) < 0) {
+ continue;
+ }
+
+ kstat_named_t* stat = kstat_data_lookup(ksp, type);
+ if (!stat) {
+ continue;
+ }
+
+ uint32_t id = 0;
+ switch (stat->data_type) {
+#ifdef _INT64_TYPE
+ case KSTAT_DATA_INT64:
+ case KSTAT_DATA_UINT64:
+ id = (uint32_t) stat->value.ui64;
+ break;
+#endif
+ case KSTAT_DATA_INT32:
+ case KSTAT_DATA_UINT32:
+ id = stat->value.ui32;
+ break;
+ default:
+ continue;
+ }
+ if (__builtin_expect(id > 64, false)) {
+ high |= 1ULL << (id - 64);
+ } else {
+ low |= 1ULL << id;
+ }
+ }
+ }
+ return (uint16_t) (__builtin_popcountll(low) + __builtin_popcountll(high));
+}
+
+const char* ffDetectCPUImpl(const FFCPUOptions* options, FFCPUResult* cpu) {
+ FF_A_CLEANUP(kstatFreeWrap) kstat_ctl_t* kc = kstat_open();
+ if (!kc) {
+ return "kstat_open() failed";
+ }
+
+ kstat_t* ks = kstat_lookup(kc, "cpu_info", -1, NULL);
+ if (!ks) {
+ return "kstat_lookup() failed";
+ }
+
+ if (kstat_read(kc, ks, NULL) < 0) {
+ return "kstat_read() failed";
+ }
+
+ {
+ kstat_named_t* kn = kstat_data_lookup(ks, "brand");
+ if (kn) {
+ ffStrbufSetNS(&cpu->name, KSTAT_NAMED_STR_BUFLEN(kn) - 1, KSTAT_NAMED_STR_PTR(kn));
+ }
+ }
+ {
+ kstat_named_t* kn = kstat_data_lookup(ks, "vendor_id");
+ if (kn) {
+ ffStrbufSetNS(&cpu->vendor, KSTAT_NAMED_STR_BUFLEN(kn) - 1, KSTAT_NAMED_STR_PTR(kn));
+ }
+ }
+ ffCPUDetectByCpuid(cpu);
+ {
+ kstat_named_t* kn = kstat_data_lookup(ks, "clock_MHz");
+ if (kn && kn->value.ui32 > cpu->frequencyBase) {
+ cpu->frequencyBase = kn->value.ui32;
+ }
+ }
+
+ ks = kstat_lookup(kc, "unix", -1, "system_misc");
+ if (ks && kstat_read(kc, ks, NULL) >= 0) {
+ kstat_named_t* kn = kstat_data_lookup(ks, "ncpus");
+ if (kn) {
+ cpu->coresLogical = cpu->coresOnline = (uint16_t) kn->value.ui32;
+ }
+ }
+
+ cpu->packages = countTypeId(kc, "chip_id");
+ cpu->coresPhysical = countTypeId(kc, "core_id");
+
+ if (options->temp) {
+ if (detectCPUTempByKstat(options, kc, cpu) != NULL) {
+ detectCPUTempByIpmiTool(cpu);
+ }
+ }
+
+ return NULL;
+}
diff --git a/src/detection/cpu/cpu_windows.c b/src/detection/cpu/cpu_windows.c
new file mode 100644
index 0000000..86edce9
--- /dev/null
+++ b/src/detection/cpu/cpu_windows.c
@@ -0,0 +1,320 @@
+#include "cpu.h"
+#include "common/windows/registry.h"
+#include "common/windows/nt.h"
+#include "common/mallocHelper.h"
+#include "common/smbios.h"
+
+#include <windows.h>
+#include "common/windows/perflib_.h"
+#include "common/windows/nt.h"
+#include <wchar.h>
+
+static inline void ffPerfCloseQueryHandle(HANDLE* phQuery) {
+ if (*phQuery != NULL) {
+ PerfCloseQueryHandle(*phQuery);
+ *phQuery = NULL;
+ }
+}
+
+const char* detectThermalTemp(const FFCPUOptions* options, double* result) {
+ struct FFPerfQuerySpec {
+ PERF_COUNTER_IDENTIFIER Identifier;
+ WCHAR Name[16];
+ } querySpec = {
+ .Identifier = {
+ // Thermal Zone Information
+ // HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Perflib\_V2Providers\{383487a6-3676-4870-a4e7-d45b30c35629}\{52bc5412-dac2-449c-8bc2-96443888fe6b}
+ .CounterSetGuid = { 0x52bc5412, 0xdac2, 0x449c, { 0x8b, 0xc2, 0x96, 0x44, 0x38, 0x88, 0xfe, 0x6b } },
+ .Size = sizeof(querySpec),
+ .CounterId = PERF_WILDCARD_COUNTER,
+ .InstanceId = PERF_WILDCARD_COUNTER,
+ },
+ .Name = L"\\_TZ.CPUZ", // The standard(?) instance name for CPU temperature in the thermal provider
+ };
+
+ if (options->tempSensor.length > 0) {
+ if (!NT_SUCCESS(RtlUTF8ToUnicodeN(querySpec.Name, (ULONG) sizeof(querySpec.Name), NULL, options->tempSensor.chars, (ULONG) options->tempSensor.length + 1))) {
+ return "Invalid temp sensor string";
+ }
+ }
+
+ DWORD dataSize = 0;
+ if (PerfEnumerateCounterSetInstances(NULL, &querySpec.Identifier.CounterSetGuid, NULL, 0, &dataSize) != ERROR_NOT_ENOUGH_MEMORY) {
+ return "PerfEnumerateCounterSetInstances() failed";
+ }
+
+ if (dataSize <= sizeof(PERF_INSTANCE_HEADER)) {
+ return "No `Thermal Zone Information` instances found";
+ }
+
+ {
+ FF_AUTO_FREE PERF_INSTANCE_HEADER* const pHead = malloc(dataSize);
+ if (PerfEnumerateCounterSetInstances(NULL, &querySpec.Identifier.CounterSetGuid, pHead, dataSize, &dataSize) != ERROR_SUCCESS) {
+ return "PerfEnumerateCounterSetInstances() failed to get instance headers";
+ }
+
+ PERF_INSTANCE_HEADER* pInstanceHeader = pHead;
+ while (1) {
+ const wchar_t* instanceName = (const wchar_t*) ((BYTE*) pInstanceHeader + sizeof(*pInstanceHeader));
+ if (wcscmp(instanceName, querySpec.Name) == 0) {
+ break;
+ }
+
+ dataSize -= pInstanceHeader->Size;
+ if (dataSize == 0) {
+ break;
+ }
+ pInstanceHeader = (PERF_INSTANCE_HEADER*) ((BYTE*) pInstanceHeader + pInstanceHeader->Size);
+ }
+
+ if (dataSize == 0) {
+ if (options->tempSensor.length > 0) {
+ return "Unable to find CPU sensor";
+ }
+
+ const wchar_t* instanceName = (const wchar_t*) ((BYTE*) pHead + sizeof(*pHead));
+ wcscpy(querySpec.Name, instanceName); // Use the first instance name if the specific one is not found
+ }
+ }
+
+ FF_A_CLEANUP(ffPerfCloseQueryHandle)
+ HANDLE hQuery = NULL;
+
+ if (PerfOpenQueryHandle(NULL, &hQuery) != ERROR_SUCCESS) {
+ return "PerfOpenQueryHandle() failed";
+ }
+
+ if (PerfAddCounters(hQuery, &querySpec.Identifier, sizeof(querySpec)) != ERROR_SUCCESS) {
+ return "PerfAddCounters() failed";
+ }
+
+ if (querySpec.Identifier.Status != ERROR_SUCCESS) {
+ return "PerfAddCounters() reports invalid identifier";
+ }
+
+ if (PerfQueryCounterData(hQuery, NULL, 0, &dataSize) != ERROR_NOT_ENOUGH_MEMORY) {
+ return "PerfQueryCounterData(NULL) failed";
+ }
+
+ if (dataSize <= sizeof(PERF_DATA_HEADER) + sizeof(PERF_COUNTER_HEADER)) { // PERF_ERROR_RETURN, should not happen
+ return "instance doesn't exist";
+ }
+
+ FF_AUTO_FREE PERF_DATA_HEADER* const pDataHeader = malloc(dataSize);
+
+ if (PerfQueryCounterData(hQuery, pDataHeader, dataSize, &dataSize) != ERROR_SUCCESS) {
+ return "PerfQueryCounterData(pDataHeader) failed";
+ }
+
+ PERF_COUNTER_HEADER* pCounterHeader = (PERF_COUNTER_HEADER*) (pDataHeader + 1);
+ if (pCounterHeader->dwType != PERF_MULTIPLE_COUNTERS) {
+ return "Invalid counter type";
+ }
+
+ PERF_MULTI_COUNTERS* pMultiCounters = (PERF_MULTI_COUNTERS*) (pCounterHeader + 1);
+ PERF_COUNTER_DATA* pCounterData = (PERF_COUNTER_DATA*) ((BYTE*) pMultiCounters + pMultiCounters->dwSize);
+
+ for (ULONG iCounter = 0; iCounter != pMultiCounters->dwCounters; iCounter++) {
+ if (pCounterData->dwDataSize == sizeof(int32_t)) {
+ DWORD* pCounterIds = (DWORD*) (pMultiCounters + 1);
+ int32_t value = *(int32_t*) (pCounterData + 1);
+ if (value == 0) {
+ return "Temperature data is zero";
+ }
+
+ switch (pCounterIds[iCounter]) {
+ case 0: // Temperature
+ *result = value - 273;
+ break;
+ case 3: // High Precision Temperature
+ *result = value / 10.0 - 273;
+ break;
+ }
+ }
+
+ pCounterData = (PERF_COUNTER_DATA*) ((BYTE*) pCounterData + pCounterData->dwSize);
+ }
+
+ return NULL;
+}
+
+// 7.5
+typedef struct FFSmbiosProcessorInfo {
+ FFSmbiosHeader Header;
+
+ uint8_t SocketDesignation; // string
+ uint8_t ProcessorType; // enum
+ uint8_t ProcessorFamily; // enum
+ uint8_t ProcessorManufacturer; // string
+ uint64_t ProcessorID; // varies
+ uint8_t ProcessorVersion; // string
+ uint8_t Voltage; // varies
+ uint16_t ExternalClock; // varies
+ uint16_t MaxSpeed; // varies
+ uint16_t CurrentSpeed; // varies
+ uint8_t Status; // varies
+ uint8_t ProcessorUpgrade; // enum
+
+ // 2.1+
+ uint16_t L1CacheHandle; // varies
+ uint16_t L2CacheHandle; // varies
+ uint16_t L3CacheHandle; // varies
+
+ // 2.3+
+ uint8_t SerialNumber; // string
+ uint8_t AssertTag; // string
+ uint8_t PartNumber; // string
+
+ // 2.5+
+ uint8_t CoreCount; // varies
+ uint8_t CoreEnabled; // varies
+ uint8_t ThreadCount; // varies
+ uint16_t ProcessorCharacteristics; // bit field
+
+ // 2.6+
+ uint16_t ProcessorFamily2; // enum
+
+ // 3.0+
+ uint16_t CoreCount2; // varies
+ uint16_t CoreEnabled2; // varies
+ uint16_t ThreadCount2; // varies
+
+ // 3.6+
+ uint16_t ThreadEnabled; // varies
+} FF_A_PACKED FFSmbiosProcessorInfo;
+
+static_assert(offsetof(FFSmbiosProcessorInfo, ThreadEnabled) == 0x30,
+ "FFSmbiosProcessorInfo: Wrong struct alignment");
+
+static const char* detectMaxSpeedBySmbios(FFCPUResult* cpu) {
+ const FFSmbiosHeaderTable* smbiosTable = ffGetSmbiosHeaderTable();
+ if (!smbiosTable) {
+ return "Failed to get SMBIOS data";
+ }
+
+ const FFSmbiosProcessorInfo* data = (const FFSmbiosProcessorInfo*) (*smbiosTable)[FF_SMBIOS_TYPE_PROCESSOR_INFO];
+
+ if (!data) {
+ return "Processor information is not found in SMBIOS data";
+ }
+
+ while (data->ProcessorType != 0x03 /*Central Processor*/ || (data->Status & 0b00000111) != 1 /*Enabled*/) {
+ data = (const FFSmbiosProcessorInfo*) ffSmbiosNextEntry(&data->Header);
+ if (data->Header.Type != FF_SMBIOS_TYPE_PROCESSOR_INFO) {
+ return "No active CPU is found in SMBIOS data";
+ }
+ }
+
+ uint32_t speed = data->MaxSpeed;
+ // Sometimes SMBIOS reports invalid value. We assume that max speed is small than 2x of base
+ if (speed < cpu->frequencyBase || speed > cpu->frequencyBase * 2) {
+ return "Possible invalid CPU max speed in SMBIOS data. See #800";
+ }
+
+ cpu->frequencyMax = speed;
+
+ return NULL;
+}
+
+static const char* detectNCores(FFCPUResult* cpu) {
+ LOGICAL_PROCESSOR_RELATIONSHIP lpr = RelationAll;
+ ULONG length = 0;
+ NtQuerySystemInformationEx(SystemLogicalProcessorAndGroupInformation, &lpr, sizeof(lpr), NULL, 0, &length);
+ if (length == 0) {
+ return "GetLogicalProcessorInformationEx(RelationAll, NULL, &length) failed";
+ }
+
+ SYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX* FF_AUTO_FREE
+ pProcessorInfo = (SYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX*) malloc(length);
+
+ if (!NT_SUCCESS(NtQuerySystemInformationEx(SystemLogicalProcessorAndGroupInformation, &lpr, sizeof(lpr), pProcessorInfo, length, &length))) {
+ return "GetLogicalProcessorInformationEx(RelationAll, pProcessorInfo, &length) failed";
+ }
+
+ for (
+ SYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX* ptr = pProcessorInfo;
+ (uint8_t*) ptr < ((uint8_t*) pProcessorInfo) + length;
+ ptr = (SYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX*) (((uint8_t*) ptr) + ptr->Size)) {
+ if (ptr->Relationship == RelationGroup) {
+ for (uint32_t index = 0; index < ptr->Group.ActiveGroupCount; ++index) {
+ cpu->coresOnline += ptr->Group.GroupInfo[index].ActiveProcessorCount;
+ cpu->coresLogical += ptr->Group.GroupInfo[index].MaximumProcessorCount;
+ }
+ } else if (ptr->Relationship == RelationProcessorCore) {
+ ++cpu->coresPhysical;
+ } else if (ptr->Relationship == RelationProcessorPackage) {
+ ++cpu->packages;
+ } else if (ptr->Relationship == RelationNumaNode) {
+ ++cpu->numaNodes;
+ }
+ }
+
+ return NULL;
+}
+
+static const char* detectByRegistry(FFCPUResult* cpu) {
+ FF_AUTO_CLOSE_FD HANDLE hKey = NULL;
+ if (!ffRegOpenKeyForRead(HKEY_LOCAL_MACHINE, L"HARDWARE\\DESCRIPTION\\System\\CentralProcessor\\0", &hKey, NULL)) {
+ return "ffRegOpenKeyForRead(HKEY_LOCAL_MACHINE, L\"HARDWARE\\DESCRIPTION\\System\\CentralProcessor\\0\", &hKey, NULL) failed";
+ }
+
+ if (ffRegReadValues(hKey, 3, (FFRegValueArg[]) {
+ FF_ARG(cpu->name, L"ProcessorNameString"),
+ FF_ARG(cpu->vendor, L"VendorIdentifier"),
+ FF_ARG(cpu->frequencyBase, L"~MHz"),
+ },
+ NULL)) {
+ ffStrbufTrimRightSpace(&cpu->vendor);
+ } else {
+ return "ffRegReadValues() failed for CPU registry key";
+ }
+
+ return NULL;
+}
+
+static const char* detectCoreTypes(FFCPUResult* cpu) {
+ FF_AUTO_FREE PROCESSOR_POWER_INFORMATION* pinfo = calloc(cpu->coresLogical, sizeof(PROCESSOR_POWER_INFORMATION));
+ if (!NT_SUCCESS(NtPowerInformation(ProcessorInformation, NULL, 0, pinfo, (ULONG) sizeof(PROCESSOR_POWER_INFORMATION) * cpu->coresLogical))) {
+ return "NtPowerInformation(ProcessorInformation, NULL, 0, pinfo, size) failed";
+ }
+
+ for (uint32_t icore = 0; icore < cpu->coresLogical && pinfo[icore].MhzLimit; ++icore) {
+ uint32_t ifreq = 0;
+ while (cpu->coreTypes[ifreq].freq != pinfo[icore].MhzLimit && cpu->coreTypes[ifreq].freq > 0) {
+ ++ifreq;
+ }
+ if (cpu->coreTypes[ifreq].freq == 0) {
+ cpu->coreTypes[ifreq].freq = pinfo[icore].MhzLimit;
+ }
+ ++cpu->coreTypes[ifreq].count;
+ }
+
+ if (cpu->frequencyBase == 0) {
+ cpu->frequencyBase = pinfo->MaxMhz;
+ }
+ return NULL;
+}
+
+const char* ffDetectCPUImpl(const FFCPUOptions* options, FFCPUResult* cpu) {
+ detectNCores(cpu);
+
+ const char* error = detectByRegistry(cpu);
+ if (error) {
+ return error;
+ }
+
+ ffCPUDetectByCpuid(cpu);
+ if (options->showPeCoreCount) {
+ detectCoreTypes(cpu);
+ }
+
+ if (cpu->frequencyMax == 0) {
+ detectMaxSpeedBySmbios(cpu);
+ }
+
+ if (options->temp) {
+ detectThermalTemp(options, &cpu->temperature);
+ }
+
+ return NULL;
+}