diff options
Diffstat (limited to 'src/detection/cpu')
| -rw-r--r-- | src/detection/cpu/cpu.c | 519 | ||||
| -rw-r--r-- | src/detection/cpu/cpu.h | 35 | ||||
| -rw-r--r-- | src/detection/cpu/cpu_apple.c | 158 | ||||
| -rw-r--r-- | src/detection/cpu/cpu_arm.h | 588 | ||||
| -rw-r--r-- | src/detection/cpu/cpu_bsd.c | 109 | ||||
| -rw-r--r-- | src/detection/cpu/cpu_haiku.c | 68 | ||||
| -rw-r--r-- | src/detection/cpu/cpu_linux.c | 1098 | ||||
| -rw-r--r-- | src/detection/cpu/cpu_nbsd.c | 109 | ||||
| -rw-r--r-- | src/detection/cpu/cpu_nosupport.c | 5 | ||||
| -rw-r--r-- | src/detection/cpu/cpu_obsd.c | 78 | ||||
| -rw-r--r-- | src/detection/cpu/cpu_sunos.c | 154 | ||||
| -rw-r--r-- | src/detection/cpu/cpu_windows.c | 320 |
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, ¤t, &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(¤tCPU); + CPU_SET(1, ¤tCPU); + CPU_SET(2, ¤tCPU); + cpuset_setaffinity(CPU_LEVEL_WHICH, CPU_WHICH_TID, -1, sizeof(cpuset_t), ¤tCPU); +#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; +} |