From 80a931a87f79a89846682d319378fbdbe45591db Mon Sep 17 00:00:00 2001 From: Carter Li Date: Fri, 25 Sep 2026 08:46:13 +0800 Subject: [PATCH] CPU (Linux): fixes Snapdragon X2 series model detection Fixes #2611 --- CHANGELOG.md | 1 + CMakeLists.txt | 12 ++++ src/detection/cpu/cpu_linux.c | 104 ++++++++++++++++++++-------------- tests/cpu.c | 47 +++++++++++++++ 4 files changed, 123 insertions(+), 41 deletions(-) create mode 100644 tests/cpu.c diff --git a/CHANGELOG.md b/CHANGELOG.md index fe2226976..f98286dfb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -46,6 +46,7 @@ Features: * Lua format scripting is now considered stable. Bugfixes: +* Fixed Snapdragon X2 series model detection on Linux. (#2611, CPU / Linux) * Fixed image logo caching, padding, and positioning issues, including an iTerm display bug. (Logo) * Fixed several Windows issues, including Windows Terminal font detection, redirected `--gen-config` output, and gamepad battery reporting for DualShock 4 and Switch controllers. (#2573, TerminalFont; Gamepad; General) * Fixed formatting and display issues, including ANSI-aware truncation, date/time formatting, and bright key/title colors. (Format / DateTime / Display) diff --git a/CMakeLists.txt b/CMakeLists.txt index a7a47a243..1ed6ba835 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -2253,6 +2253,15 @@ if (BUILD_TESTS) PRIVATE libfastfetch ) + if (CMAKE_SYSTEM_NAME STREQUAL "Linux") + add_executable(fastfetch-test-cpu + tests/cpu.c + ) + target_link_libraries(fastfetch-test-cpu + PRIVATE libfastfetch + ) + endif() + add_executable(fastfetch-test-strbuf tests/strbuf.c ) @@ -2366,6 +2375,9 @@ if (BUILD_TESTS) ) enable_testing() + if (CMAKE_SYSTEM_NAME STREQUAL "Linux") + add_test(NAME test-cpu COMMAND fastfetch-test-cpu) + endif() add_test(NAME test-strbuf COMMAND fastfetch-test-strbuf) add_test(NAME test-list COMMAND fastfetch-test-list) add_test(NAME test-format COMMAND fastfetch-test-format) diff --git a/src/detection/cpu/cpu_linux.c b/src/detection/cpu/cpu_linux.c index 3cfac542d..4786874dd 100644 --- a/src/detection/cpu/cpu_linux.c +++ b/src/detection/cpu/cpu_linux.c @@ -706,6 +706,68 @@ static bool detectFrequency(FFCPUResult* cpu, const FFCPUOptions* options) { return true; } +// Also used in tests/cpu.c +bool ffCPUQualcommSnapdragonToName(FFstrbuf* name, const char* model) { + // X2 device trees use platform codenames rather than exact retail part + // numbers. qcom,glymur is used by X2 Elite Extreme boards, qcom,mahua by + // X2 Elite boards, and qcom,kalambo by X2 Plus boards. Do not append a + // part number from /sys/bus/soc/devices/soc0/soc_id: it contains + // SMEM_HW_SW_BUILD_ID.id (the platform ID), 662 is unnamed in the public + // qcom,ids.h, and one compatible can cover multiple retail SKUs. + // https://github.com/fastfetch-cli/fastfetch/issues/2611 + if (ffStrEquals(model, "glymur")) { + ffStrbufSetS(name, "Qualcomm Snapdragon X2 Elite Extreme"); + return true; + } + if (ffStrEquals(model, "mahua")) { + ffStrbufSetS(name, "Qualcomm Snapdragon X2 Elite"); + return true; + } + if (ffStrEquals(model, "kalambo")) { + ffStrbufSetS(name, "Qualcomm Snapdragon X2 Plus"); + return true; + } + + // SoC models are enumerated in the form of `x[e|p]`, e.g. `x1e80100`, + // which is marketed as `X1E-80-100`, where `e` stands for Elite and `p` for Plus. + // https://en.wikipedia.org/wiki/List_of_Qualcomm_Snapdragon_systems_on_chips#Snapdragon_X_series + const char* prefix; + uint32_t prefixLen; + + if (ffStrStartsWith(model, "x1e")) { + prefix = "Qualcomm Snapdragon X Elite"; + prefixLen = 3; + } else if (ffStrStartsWith(model, "x1p")) { + prefix = "Qualcomm Snapdragon X Plus"; + prefixLen = 3; + } else if (ffStrStartsWith(model, "x2e")) { + // Only X2E-94-100 and X2E-96-100 are branded as Extreme + bool extreme = ffStrStartsWith(model, "x2e94") || ffStrStartsWith(model, "x2e96"); + prefix = extreme ? "Qualcomm Snapdragon X2 Elite Extreme" : "Qualcomm Snapdragon X2 Elite"; + prefixLen = 3; + } else if (ffStrStartsWith(model, "x2p")) { + prefix = "Qualcomm Snapdragon X2 Plus"; + prefixLen = 3; + } else if (ffStrStartsWith(model, "x1")) { + prefix = "Qualcomm Snapdragon X"; + prefixLen = 2; + } else { + return false; + } + + uint32_t length = (uint32_t) strlen(model); + bool splitCode = length - prefixLen == 5; + + ffStrbufSetF(name, "%s X", prefix); + for (uint32_t i = 1; i < length; ++i) { + if (i == prefixLen || (splitCode && i == length - 3)) { + ffStrbufAppendC(name, '-'); + } + ffStrbufAppendC(name, (char) toupper(model[i])); + } + return true; +} + #if __i386__ || __x86_64__ [[maybe_unused]] static uint16_t getPackageCount(FFstrbuf* cpuinfo) { @@ -840,46 +902,6 @@ static const char* detectPhysicalCores(FFCPUResult* cpu) { return nullptr; } -[[maybe_unused]] static bool detectSnapdragonX(FFstrbuf* name, const char* model) { - // SoC models are enumerated in the form of `x[e|p]`, e.g. `x1e80100`, - // which is marketed as `X1E-80-100`, where `e` stands for Elite and `p` for Plus. - // https://en.wikipedia.org/wiki/List_of_Qualcomm_Snapdragon_systems_on_chips#Snapdragon_X_series - const char* prefix; - uint32_t prefixLen; - - if (ffStrStartsWith(model, "x1e")) { - prefix = "Qualcomm Snapdragon X Elite"; - prefixLen = 3; - } else if (ffStrStartsWith(model, "x1p")) { - prefix = "Qualcomm Snapdragon X Plus"; - prefixLen = 3; - } else if (ffStrStartsWith(model, "x2e")) { - // Only X2E-94-100 and X2E-96-100 are branded as Extreme - prefix = model[3] == '9' ? "Qualcomm Snapdragon X2 Elite Extreme" : "Qualcomm Snapdragon X2 Elite"; - prefixLen = 3; - } else if (ffStrStartsWith(model, "x2p")) { - prefix = "Qualcomm Snapdragon X2 Plus"; - prefixLen = 3; - } else if (ffStrStartsWith(model, "x1")) { - prefix = "Qualcomm Snapdragon X"; - prefixLen = 2; - } else { - return false; - } - - uint32_t length = (uint32_t) strlen(model); - bool splitCode = length - prefixLen == 5; - - ffStrbufSetF(name, "%s X", prefix); - for (uint32_t i = 1; i < length; ++i) { - if (i == prefixLen || (splitCode && i == length - 3)) { - ffStrbufAppendC(name, '-'); - } - ffStrbufAppendC(name, (char) toupper(model[i])); - } - return true; -} - [[maybe_unused]] static void parseIsa(FFstrbuf* cpuIsa) { // Always use the last part of the ISA string. Ref: #590 #1204 ffStrbufSubstrAfterLastC(cpuIsa, ' '); @@ -982,7 +1004,7 @@ static const char* detectPhysicalCores(FFCPUResult* cpu) { #endif else if (ffStrEquals(vendor, "qcom")) { // https://elixir.bootlin.com/linux/latest/source/arch/arm64/boot/dts/qcom - if (!detectSnapdragonX(&cpu->name, model)) { + if (!ffCPUQualcommSnapdragonToName(&cpu->name, model)) { if (ffStrStartsWith(model, "sc")) { const char* code = model + 2; uint32_t deviceId = (uint32_t) strtoul(code, nullptr, 10); diff --git a/tests/cpu.c b/tests/cpu.c new file mode 100644 index 000000000..180380ca2 --- /dev/null +++ b/tests/cpu.c @@ -0,0 +1,47 @@ +#include "common/FFstrbuf.h" +#include "common/textModifier.h" + +#include +#include + +bool ffCPUQualcommSnapdragonToName(FFstrbuf* name, const char* model); + + +static void verifyName(const char* model, bool expectedResult, const char* expected) { + FF_STRBUF_AUTO_DESTROY result = ffStrbufCreateS("unchanged"); + const bool returned = ffCPUQualcommSnapdragonToName(&result, model); + + if (returned != expectedResult || !ffStrbufEqualS(&result, expected)) { + fprintf(stderr, FASTFETCH_TEXT_MODIFIER_ERROR + "ffCPUQualcommSnapdragonToName(\"%s\"): expected %s \"%s\", got %s \"%s\"\n" + FASTFETCH_TEXT_MODIFIER_RESET, + model, expectedResult ? "true" : "false", expected, + returned ? "true" : "false", result.chars); + exit(1); + } +} + +#define VERIFY_NAME(model, expectedResult, expected) \ + verifyName((model), (expectedResult), (expected)) + +int main(void) { + // Existing X1 and direct X2 model names + VERIFY_NAME("x1e80100", true, "Qualcomm Snapdragon X Elite X1E-80-100"); + VERIFY_NAME("x1p42100", true, "Qualcomm Snapdragon X Plus X1P-42-100"); + VERIFY_NAME("x2e88100", true, "Qualcomm Snapdragon X2 Elite X2E-88-100"); + VERIFY_NAME("x2e90100", true, "Qualcomm Snapdragon X2 Elite X2E-90-100"); + VERIFY_NAME("x2e94100", true, "Qualcomm Snapdragon X2 Elite Extreme X2E-94-100"); + VERIFY_NAME("x2e96100", true, "Qualcomm Snapdragon X2 Elite Extreme X2E-96-100"); + VERIFY_NAME("x2p42100", true, "Qualcomm Snapdragon X2 Plus X2P-42-100"); + + // X2 platform codenames identify the marketing family, not an exact SKU + VERIFY_NAME("glymur", true, "Qualcomm Snapdragon X2 Elite Extreme"); + VERIFY_NAME("mahua", true, "Qualcomm Snapdragon X2 Elite"); + VERIFY_NAME("kalambo", true, "Qualcomm Snapdragon X2 Plus"); + + // Other Qualcomm platform names are handled by the caller + VERIFY_NAME("sc8280", false, "unchanged"); + + puts("\033[32mAll tests passed!" FASTFETCH_TEXT_MODIFIER_RESET); + return 0; +}