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; +}