From 0842eda3997a562ac9078b685acc90992cf10b54 Mon Sep 17 00:00:00 2001 From: Han Gao Date: Sun, 6 Sep 2026 03:21:41 +0800 Subject: [PATCH] CPU: show heterogeneous RISC-V microarchitectures Read all per-CPU uarch descriptions even when a SoC name is available. Append a counted breakdown for heterogeneous systems while preserving the existing display for homogeneous or incomplete descriptions. Distinguish equal-frequency implementations such as A210 C908 and C920 without inferring performance/efficiency classes from their names. Keep frequency reporting and the existing coreTypes interface unchanged. Add tests for mixed, interleaved, homogeneous and malformed CPU descriptions, including an unterminated final line and inconsistent online CPU counts. Signed-off-by: Han Gao --- CMakeLists.txt | 3 ++ src/detection/cpu/cpu_linux.c | 15 +++++++ src/detection/cpu/cpu_riscv.h | 79 +++++++++++++++++++++++++++++++++++ tests/cpu-riscv.c | 42 +++++++++++++++++++ 4 files changed, 139 insertions(+) create mode 100644 src/detection/cpu/cpu_riscv.h create mode 100644 tests/cpu-riscv.c diff --git a/CMakeLists.txt b/CMakeLists.txt index 5d52ecd8e4..9a816da622 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -2130,6 +2130,8 @@ endif() ################### if (BUILD_TESTS) + add_executable(fastfetch-test-cpu-riscv tests/cpu-riscv.c) + target_link_libraries(fastfetch-test-cpu-riscv PRIVATE libfastfetch) add_executable(fastfetch-test-strbuf tests/strbuf.c ) @@ -2173,6 +2175,7 @@ if (BUILD_TESTS) ) enable_testing() + add_test(NAME test-cpu-riscv COMMAND fastfetch-test-cpu-riscv) 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 4a338ea4e8..d634411f3b 100644 --- a/src/detection/cpu/cpu_linux.c +++ b/src/detection/cpu/cpu_linux.c @@ -14,6 +14,10 @@ #define FF_CPUINFO_PATH "/proc/cpuinfo" +#if __riscv__ || __riscv + #include "cpu_riscv.h" +#endif + static double readTempFile(int dfd, const char* filename, FFstrbuf* buffer) { if (filename ? !ffReadFileBufferRelative(dfd, filename, buffer) : !ffReadFDBuffer(dfd, buffer)) { return FF_CPU_TEMP_UNSET; @@ -1047,6 +1051,17 @@ static const char* detectPhysicalCores(FFCPUResult* cpu) { detectFrequency(cpu, options); + #if __riscv__ || __riscv + // A detected SoC name must not hide a heterogeneous core topology. + // Frequency-based core counts can merge distinct uarchs on RISC-V. + { + FF_STRBUF_AUTO_DESTROY cpuinfo = ffStrbufCreateA(PROC_FILE_BUFFSIZ); + if (ffReadFileBuffer(FF_CPUINFO_PATH, &cpuinfo)) { + ffCPUDetectRiscvUarch(&cpuinfo, cpu->coresOnline, &cpu->name); + } + } + #endif + if (cpu->name.length == 0) { FF_STRBUF_AUTO_DESTROY cpuinfo = ffStrbufCreateA(PROC_FILE_BUFFSIZ); if (!ffReadFileBuffer(FF_CPUINFO_PATH, &cpuinfo) || cpuinfo.length == 0) { diff --git a/src/detection/cpu/cpu_riscv.h b/src/detection/cpu/cpu_riscv.h new file mode 100644 index 0000000000..6e8244bdf2 --- /dev/null +++ b/src/detection/cpu/cpu_riscv.h @@ -0,0 +1,79 @@ +#pragma once + +#include "common/properties.h" + +// Unlike frequency-based core grouping, uarch also distinguishes different +// implementations running at the same frequency. Keep the kernel's names: +// a microarchitecture name alone does not establish a P/E classification. +static bool ffCPUDetectRiscvUarch(FFstrbuf* cpuinfo, uint16_t coresOnline, FFstrbuf* name) { + typedef struct UarchCount { + FFstrbuf name; + uint32_t count; + } UarchCount; + + FF_LIST_AUTO_DESTROY groups = ffListCreate(); + FF_STRBUF_AUTO_DESTROY value = ffStrbufCreate(); + uint32_t processors = 0; + uint32_t descriptions = 0; + bool haveUarch = false; + bool valid = true; + char* line = NULL; + size_t length = 0; + + while (ffStrbufGetline(&line, &length, cpuinfo)) { + ffStrbufClear(&value); + if (ffParsePropLine(line, "processor :", &value)) { + if (processors > 0 && !haveUarch) { + valid = false; + } + ++processors; + haveUarch = false; + } else if (ffParsePropLine(line, "uarch :", &value)) { + if (processors == 0 || haveUarch || value.length == 0) { + valid = false; + continue; + } + haveUarch = true; + ++descriptions; + + UarchCount* group = NULL; + FF_LIST_FOR_EACH (UarchCount, candidate, groups) { + if (ffStrbufEqual(&candidate->name, &value)) { + group = candidate; + break; + } + } + if (!group) { + group = FF_LIST_ADD(UarchCount, groups); + ffStrbufInitCopy(&group->name, &value); + group->count = 0; + } + ++group->count; + } + } + + // /proc/cpuinfo describes online CPUs. Do not publish a partial breakdown + // if descriptions are missing or CPU hotplug changed the snapshot. + bool heterogeneous = valid && haveUarch && groups.length > 1 && + processors == coresOnline && descriptions == coresOnline; + if (heterogeneous) { + bool haveSocName = name->length > 0; + if (haveSocName) { + ffStrbufAppendS(name, " ("); + } + FF_LIST_FOR_EACH (UarchCount, group, groups) { + if (group != ffListGet(&groups, sizeof(UarchCount), 0)) { + ffStrbufAppendS(name, " + "); + } + ffStrbufAppendF(name, "%u x %s", group->count, group->name.chars); + } + if (haveSocName) { + ffStrbufAppendC(name, ')'); + } + } + + FF_LIST_FOR_EACH (UarchCount, group, groups) { + ffStrbufDestroy(&group->name); + } + return heterogeneous; +} diff --git a/tests/cpu-riscv.c b/tests/cpu-riscv.c new file mode 100644 index 0000000000..9b1eb46ba5 --- /dev/null +++ b/tests/cpu-riscv.c @@ -0,0 +1,42 @@ +#include "detection/cpu/cpu_riscv.h" + +#include + +static void verify(const char* input, uint16_t online, const char* soc, const char* expected, bool changed) { + FF_STRBUF_AUTO_DESTROY cpuinfo = ffStrbufCreateS(input); + FF_STRBUF_AUTO_DESTROY name = ffStrbufCreateS(soc); + bool result = ffCPUDetectRiscvUarch(&cpuinfo, online, &name); + if (result != changed || !ffStrbufEqualS(&name, expected) || !ffStrbufEqualS(&cpuinfo, input)) { + fprintf(stderr, "Expected '%s' (%d), got '%s' (%d)\n", expected, changed, name.chars, result); + exit(1); + } +} + +int main(void) { + // Interleaved IDs must be grouped by uarch, not adjacency or frequency. + const char* mixed = + "processor\t: 0\nuarch\t: thead,c908\n\n" + "processor\t: 4\nuarch\t: thead,c920\n\n" + "processor\t: 1\nuarch\t: thead,c908\n\n" + "processor\t: 5\nuarch\t: thead,c920\n\n" + "processor\t: 2\nuarch\t: thead,c908\n\n" + "processor\t: 6\nuarch\t: thead,c920\n\n" + "processor\t: 3\nuarch\t: thead,c908\n\n" + "processor\t: 7\nuarch\t: thead,c920\n"; + verify(mixed, 8, "a210", "a210 (4 x thead,c908 + 4 x thead,c920)", true); + verify(mixed, 8, "", "4 x thead,c908 + 4 x thead,c920", true); + verify(mixed, 7, "a210", "a210", false); + verify("processor : 0\nuarch : vendor,small\n\nprocessor : 7\nuarch : vendor,large", + 2, "SoC", "SoC (1 x vendor,small + 1 x vendor,large)", true); + verify("processor : 0\nuarch : thead,c908\n\nprocessor : 1\nuarch : thead,c908\n", + 2, "SoC", "SoC", false); + verify("processor : 0\nisa : rv64imafdc\n", 1, "SoC", "SoC", false); + verify("processor : 0\nuarch : vendor,a\n\nprocessor : 1\n\nprocessor : 2\nuarch : vendor,b\n", + 3, "SoC", "SoC", false); + verify("processor : 0\nuarch : vendor,a\nuarch : vendor,b\n", + 1, "SoC", "SoC", false); + verify("processor : 0\nuarch : \n", 1, "SoC", "SoC", false); + verify("", 0, "SoC", "SoC", false); + puts("RISC-V uarch tests passed"); + return 0; +}