master ohmu/fastfetch-openbsd / src / detection / cpu / cpu_linux.c
ohmu  ·  2026-10-15
   1#include "cpu.h"
   2#include "common/debug.h"
   3#include "common/io.h"
   4#include "common/processing.h"
   5#include "common/properties.h"
   6#include "common/mallocHelper.h"
   7#include "common/strutil.h"
   8#include "common/path.h"
   9
  10#include <sys/sysinfo.h>
  11#include <stdlib.h>
  12#include <string.h>
  13#include <unistd.h>
  14#include <dirent.h>
  15#include <fcntl.h>
  16
  17#define FF_CPUINFO_PATH "/proc/cpuinfo"
  18
  19static double readTempFile(int dfd, const char* filename, FFstrbuf* buffer) {
  20    if (filename ? !ffReadFileBufferRelative(dfd, filename, buffer) : !ffReadFDBuffer(dfd, buffer)) {
  21        return FF_CPU_TEMP_UNSET;
  22    }
  23
  24    double value = ffStrbufToDouble(buffer, FF_CPU_TEMP_UNSET); // millidegree Celsius
  25    if (value == FF_CPU_TEMP_UNSET) {
  26        return FF_CPU_TEMP_UNSET;
  27    }
  28
  29    return value / 1000.;
  30}
  31
  32static double parseTZDir(int dfd, FFstrbuf* buffer) {
  33    if (!ffReadFileBufferRelative(dfd, "type", buffer)) {
  34        return FF_CPU_TEMP_UNSET;
  35    }
  36
  37    if (!ffStrbufStartsWithS(buffer, "cpu") &&
  38        !ffStrbufStartsWithS(buffer, "soc") &&
  39#if __x86_64__ || __i386__
  40        !ffStrbufEqualS(buffer, "x86_pkg_temp") &&
  41#endif
  42        true)
  43        return FF_CPU_TEMP_UNSET;
  44
  45    return readTempFile(dfd, "temp", buffer);
  46}
  47
  48static double parseHwmonDir(int dfd, FFstrbuf* buffer) {
  49    // https://www.kernel.org/doc/Documentation/hwmon/sysfs-interface
  50    if (!ffReadFileBufferRelative(dfd, "name", buffer)) {
  51        return FF_CPU_TEMP_UNSET;
  52    }
  53
  54    ffStrbufTrimRightSpace(buffer);
  55
  56    if (
  57        !ffStrbufContainS(buffer, "cpu") &&
  58#if __x86_64__ || __i386__
  59        !ffStrbufEqualS(buffer, "k10temp") &&      // AMD
  60        !ffStrbufEqualS(buffer, "fam15h_power") && // AMD
  61        !ffStrbufEqualS(buffer, "coretemp") &&     // Intel
  62#endif
  63        true)
  64        return FF_CPU_TEMP_UNSET;
  65
  66    return readTempFile(dfd, "temp1_input", buffer);
  67}
  68
  69static double detectCPUTemp(const FFCPUOptions* options) {
  70    FF_STRBUF_AUTO_DESTROY buffer = ffStrbufCreate();
  71
  72    if (options->tempSensor.length > 0) {
  73        FF_AUTO_CLOSE_FD int subfd = -1;
  74        const char* fileName = nullptr;
  75        if (ffStrbufStartsWithS(&options->tempSensor, "hwmon") && ffCharIsDigit(options->tempSensor.chars[strlen("hwmon")])) {
  76            FF_AUTO_CLOSE_FD int dfd = open("/sys/class/hwmon/", O_PATH | O_CLOEXEC);
  77            subfd = openat(dfd, options->tempSensor.chars, O_RDONLY | O_DIRECTORY | O_CLOEXEC);
  78            if (subfd >= 0) {
  79                fileName = "temp1_input";
  80            } else {
  81                subfd = openat(dfd, options->tempSensor.chars, O_RDONLY | O_CLOEXEC);
  82            }
  83        } else if (ffStrbufStartsWithS(&options->tempSensor, "thermal_zone") && ffCharIsDigit(options->tempSensor.chars[strlen("thermal_zone")])) {
  84            FF_AUTO_CLOSE_FD int dfd = open("/sys/class/thermal/", O_PATH | O_CLOEXEC);
  85            subfd = openat(dfd, options->tempSensor.chars, O_RDONLY | O_DIRECTORY | O_CLOEXEC);
  86            if (subfd >= 0) {
  87                fileName = "temp";
  88            } else {
  89                subfd = openat(dfd, options->tempSensor.chars, O_RDONLY | O_CLOEXEC);
  90            }
  91        } else if (ffStrbufStartsWithS(&options->tempSensor, "cputemp.") && ffCharIsDigit(options->tempSensor.chars[strlen("cputemp.")])) {
  92            FF_AUTO_CLOSE_FD int dfd = open("/sys/class/platform/", O_PATH | O_CLOEXEC);
  93            subfd = openat(dfd, options->tempSensor.chars, O_RDONLY | O_DIRECTORY | O_CLOEXEC);
  94            if (subfd >= 0) {
  95                fileName = "temp1_input";
  96            } else {
  97                subfd = openat(dfd, options->tempSensor.chars, O_RDONLY | O_CLOEXEC);
  98            }
  99        } else if (ffIsAbsolutePath(options->tempSensor.chars)) {
 100            subfd = open(options->tempSensor.chars, O_RDONLY | O_DIRECTORY | O_CLOEXEC);
 101            if (subfd >= 0) {
 102                fileName = "temp1_input";
 103            } else {
 104                subfd = open(options->tempSensor.chars, O_RDONLY | O_CLOEXEC);
 105            }
 106        }
 107        if (subfd < 0) {
 108            return FF_CPU_TEMP_UNSET;
 109        }
 110
 111        return readTempFile(subfd, fileName, &buffer);
 112    }
 113
 114    {
 115        FF_AUTO_CLOSE_DIR DIR* dirp = opendir("/sys/class/hwmon/");
 116        if (dirp) {
 117            int dfd = dirfd(dirp);
 118
 119            struct dirent* entry;
 120            while ((entry = readdir(dirp)) != nullptr) {
 121                if (entry->d_name[0] == '.') {
 122                    continue;
 123                }
 124
 125                FF_AUTO_CLOSE_FD int subfd = openat(dfd, entry->d_name, O_RDONLY | O_DIRECTORY | O_CLOEXEC);
 126                if (subfd < 0) {
 127                    continue;
 128                }
 129
 130                double result = parseHwmonDir(subfd, &buffer);
 131                if (result != FF_CPU_TEMP_UNSET) {
 132                    return result;
 133                }
 134            }
 135        }
 136    }
 137    {
 138        FF_AUTO_CLOSE_DIR DIR* dirp = opendir("/sys/class/thermal/");
 139        if (dirp) {
 140            int dfd = dirfd(dirp);
 141            struct dirent* entry;
 142            while ((entry = readdir(dirp)) != nullptr) {
 143                if (entry->d_name[0] == '.') {
 144                    continue;
 145                }
 146                if (!ffStrStartsWith(entry->d_name, "thermal_zone")) {
 147                    continue;
 148                }
 149
 150                FF_AUTO_CLOSE_FD int subfd = openat(dfd, entry->d_name, O_RDONLY | O_DIRECTORY | O_CLOEXEC);
 151                if (subfd < 0) {
 152                    continue;
 153                }
 154
 155                double result = parseTZDir(subfd, &buffer);
 156                if (result != FF_CPU_TEMP_UNSET) {
 157                    return result;
 158                }
 159            }
 160        }
 161    }
 162    {
 163        FF_AUTO_CLOSE_DIR DIR* dirp = opendir("/sys/devices/platform/");
 164        if (dirp) {
 165            int dfd = dirfd(dirp);
 166            struct dirent* entry;
 167            while ((entry = readdir(dirp)) != nullptr) {
 168                if (entry->d_name[0] == '.') {
 169                    continue;
 170                }
 171                if (!ffStrStartsWith(entry->d_name, "cputemp.")) {
 172                    continue;
 173                }
 174
 175                FF_AUTO_CLOSE_FD int subfd = openat(dfd, entry->d_name, O_RDONLY | O_DIRECTORY | O_CLOEXEC);
 176                if (subfd < 0) {
 177                    continue;
 178                }
 179
 180                double result = parseHwmonDir(subfd, &buffer);
 181                if (result != FF_CPU_TEMP_UNSET) {
 182                    return result;
 183                }
 184            }
 185        }
 186    }
 187
 188    return FF_CPU_TEMP_UNSET;
 189}
 190
 191static void detectNumaNodes(FFCPUResult* cpu) {
 192    FF_AUTO_CLOSE_DIR DIR* dir = opendir("/sys/devices/system/node/");
 193    if (!dir) {
 194        return;
 195    }
 196
 197    struct dirent* entry;
 198    while ((entry = readdir(dir)) != nullptr) {
 199        if (entry->d_type != DT_DIR && entry->d_type != DT_UNKNOWN) {
 200            continue;
 201        }
 202        if (ffStrStartsWith(entry->d_name, "node") && ffCharIsDigit(entry->d_name[strlen("node")])) {
 203            cpu->numaNodes++;
 204        }
 205    }
 206}
 207
 208#ifdef __ANDROID__
 209    #include "common/settings.h"
 210
 211static void detectQualcomm(FFCPUResult* cpu) {
 212    // https://en.wikipedia.org/wiki/List_of_Qualcomm_Snapdragon_systems_on_chips
 213
 214    assert(cpu->name.length >= 2);
 215    uint32_t code = (uint32_t) strtoul(cpu->name.chars + 2, nullptr, 10);
 216    const char* name = nullptr;
 217
 218    switch (code) {
 219        case 8975:
 220            name = "8 Elite Extreme Gen 6";
 221            break;
 222        case 8950:
 223            name = "8 Elite Gen 6";
 224            break;
 225        case 8845:
 226            name = "8 Gen 5";
 227            break;
 228        case 8850:
 229            name = "8 Elite Gen 5";
 230            break;
 231        case 8735:
 232            name = "8s Gen 4";
 233            break;
 234        case 8750:
 235            name = "8 Elite";
 236            break;
 237        case 8635:
 238            name = "8s Gen 3";
 239            break;
 240        case 8650:
 241            name = "8 Gen 3";
 242            break;
 243        case 8550:
 244            name = "8 Gen 2";
 245            break;
 246        case 8475:
 247            name = "8+ Gen 1";
 248            break;
 249        case 8425:
 250            name = "8+ Gen 1 4G";
 251            break;
 252        case 8450:
 253            name = "8 Gen 1";
 254            break;
 255        case 7750:
 256            name = "7 Gen 4";
 257            break;
 258        case 7675:
 259            name = "7+ Gen 3";
 260            break;
 261        case 7635:
 262            name = "7s Gen 3";
 263            break;
 264        case 7550:
 265            name = "7 Gen 3";
 266            break;
 267        case 7475:
 268            name = "7+ Gen 2";
 269            break;
 270        case 7435:
 271            name = "7s Gen 2";
 272            break;
 273        case 7450:
 274            name = "7 Gen 1";
 275            break;
 276        case 6650:
 277            name = "6 Gen 4";
 278            break;
 279        case 6375:
 280            name = "6s Gen 3";
 281            break;
 282        case 6475:
 283            name = "6 Gen 3";
 284            break;
 285        case 6225:
 286            name = "6s 4G Gen 2";
 287            break;
 288        case 6115:
 289            name = "6s 4G Gen 1";
 290            break;
 291        case 6450:
 292            name = "6 Gen 1";
 293            break;
 294        case 4850:
 295            name = "4 Gen 5";
 296            break;
 297        case 4635:
 298            name = "4s Gen 2";
 299            break;
 300        case 4450:
 301            name = "4 Gen 2";
 302            break;
 303        case 4375:
 304            name = "4 Gen 1";
 305            break;
 306    }
 307
 308    if (name) {
 309        char str[32];
 310        ffStrCopy(str, cpu->name.chars, sizeof(str));
 311        ffStrbufSetF(&cpu->name, "Qualcomm Snapdragon %s [%s]", name, str);
 312        return;
 313    }
 314}
 315
 316static void detectMediaTek(FFCPUResult* cpu) {
 317    // https://en.wikipedia.org/wiki/List_of_MediaTek_systems_on_chips
 318
 319    assert(cpu->name.length >= 2);
 320    uint32_t code = (uint32_t) strtoul(cpu->name.chars + 2, nullptr, 10);
 321    const char* name = nullptr;
 322
 323    switch (code) // The SOC code of MTK Dimensity series is full of mess
 324    {
 325        case 6995:
 326            name = "9600 Pro";
 327            break;
 328        case 6993:
 329            name = "9500";
 330            break;
 331        case 6991:
 332            name = "9400";
 333            break;
 334        case 6989:
 335        case 8796:
 336            name = "9300";
 337            break;
 338        case 6985:
 339            name = "9200";
 340            break;
 341        case 6983:
 342        case 8798:
 343            name = "9000";
 344            break;
 345
 346        case 6899:
 347            name = "8400";
 348            break;
 349        case 6897:
 350        case 8792:
 351            name = "8300";
 352            break;
 353        case 6896:
 354            name = "8200";
 355            break;
 356        case 8795:
 357            name = "8100";
 358            break;
 359        case 6895:
 360            name = "8000";
 361            break;
 362    }
 363
 364    if (name) {
 365        char str[32];
 366        ffStrCopy(str, cpu->name.chars, sizeof(str));
 367        ffStrbufSetF(&cpu->name, "MediaTek Dimensity %s [%s]", name, str);
 368        return;
 369    }
 370}
 371
 372static void detectExynos(FFCPUResult* cpu) {
 373    // https://en.wikipedia.org/wiki/Exynos
 374
 375    assert(cpu->name.length > 3);
 376    uint32_t code = (uint32_t) strtoul(cpu->name.chars + 3, nullptr, 10);
 377    const char* name = nullptr;
 378
 379    switch (code) {
 380        case 9975:
 381            name = "2700";
 382            break;
 383        case 9965:
 384            name = "2600";
 385            break;
 386        case 9955:
 387            name = "2500";
 388            break;
 389        case 9945:
 390            name = "2400";
 391            break;
 392        // No 2300
 393        case 9925:
 394            name = "2200";
 395            break;
 396        case 9840:
 397            name = "2100";
 398            break;
 399
 400        case 8865:
 401            name = "1680";
 402            break;
 403        case 8855:
 404            name = "1580";
 405            break;
 406        case 8845:
 407            name = "1480";
 408            break;
 409        case 8835:
 410            name = "1380";
 411            break;
 412        case 8535:
 413            name = "1330";
 414            break;
 415        case 8825:
 416            name = "1280";
 417            break;
 418        case 9815:
 419            name = "1080";
 420            break;
 421
 422        case 9830:
 423            name = "990";
 424            break;
 425        case 9630:
 426            name = "980";
 427            break;
 428
 429        case 8805:
 430            name = "880";
 431            break;
 432        case 3830:
 433            name = "850";
 434            break;
 435    }
 436
 437    if (name) {
 438        char str[32];
 439        ffStrCopy(str, cpu->name.chars, sizeof(str));
 440        ffStrbufSetF(&cpu->name, "Samsung Exynos %s [%s]", name, str);
 441        return;
 442    }
 443}
 444
 445static void detectAndroid(FFCPUResult* cpu) {
 446    if (cpu->name.length == 0) {
 447        if (ffSettingsGetAndroidProperty("ro.soc.model", &cpu->name)) {
 448            ffStrbufClear(&cpu->vendor); // We usually detect the vendor of CPU core as ARM, but instead we want the vendor of SOC
 449        }
 450    }
 451    if (cpu->vendor.length == 0) {
 452        if (!ffSettingsGetAndroidProperty("ro.soc.manufacturer", &cpu->vendor)) {
 453            if (!ffSettingsGetAndroidProperty("ro.product.product.manufacturer", &cpu->vendor)) {
 454                if (!ffSettingsGetAndroidProperty("ro.product.vendor.manufacturer", &cpu->vendor)) {
 455                    if (ffSettingsGetAndroidProperty("ro.mediatek.platform", &cpu->name)) {
 456                        ffStrbufSetStatic(&cpu->vendor, "MediaTek");
 457                    }
 458                }
 459            }
 460        }
 461    }
 462
 463    if (ffStrbufEqualS(&cpu->vendor, "QTI")) {
 464        ffStrbufSetStatic(&cpu->vendor, "Qualcomm");
 465    } else if (ffStrbufIgnCaseEqualS(&cpu->vendor, "MediaTek")) { // sometimes "Mediatek"
 466        ffStrbufSetStatic(&cpu->vendor, "MediaTek");
 467    } else if (cpu->vendor.length > 0) {
 468        cpu->vendor.chars[0] = (char) toupper(cpu->vendor.chars[0]);
 469    }
 470
 471    if (ffStrbufEqualS(&cpu->vendor, "Qualcomm") && ffStrbufStartsWithS(&cpu->name, "SM")) {
 472        detectQualcomm(cpu);
 473    } else if (ffStrbufEqualS(&cpu->vendor, "MediaTek") && ffStrbufStartsWithS(&cpu->name, "MT")) {
 474        detectMediaTek(cpu);
 475    } else if (ffStrbufEqualS(&cpu->vendor, "Samsung") && ffStrbufStartsWithS(&cpu->name, "s5e")) {
 476        cpu->name.chars[0] = 'S';
 477        cpu->name.chars[2] = 'E';
 478        detectExynos(cpu);
 479    }
 480}
 481#endif
 482
 483#if __arm__ || __aarch64__
 484    #include "cpu_arm.h"
 485
 486static void detectArmName(FFstrbuf* cpuinfo, FFCPUResult* cpu, uint32_t implId) {
 487    char* line = nullptr;
 488    size_t len = 0;
 489    uint32_t lastPartId = UINT32_MAX;
 490    while (ffStrbufGetline(&line, &len, cpuinfo)) {
 491        if (!ffStrStartsWith(line, "CPU part\t: ")) {
 492            continue;
 493        }
 494        uint32_t partId = (uint32_t) strtoul(line + strlen("CPU part\t: "), nullptr, 16);
 495        const char* name = nullptr;
 496        switch (implId) {
 497            case 0x41:
 498                name = armPartId2name(partId);
 499                break;
 500            case 0x42:
 501                name = brcmPartId2name(partId);
 502                break;
 503            case 0x43:
 504                name = caviumPartId2name(partId);
 505                break;
 506            case 0x44:
 507                name = decPartId2name(partId);
 508                break;
 509            case 0x46:
 510                name = fujitsuPartId2name(partId);
 511                break;
 512            case 0x48:
 513                name = hisiPartId2name(partId);
 514                break;
 515            case 0x4e:
 516                name = nvidiaPartId2name(partId);
 517                break;
 518            case 0x50:
 519                name = apmPartId2name(partId);
 520                break;
 521            case 0x51:
 522                name = qcomPartId2name(partId);
 523                break;
 524            case 0x53:
 525                name = samsungPartId2name(partId);
 526                break;
 527            case 0x56:
 528                name = marvellPartId2name(partId);
 529                break;
 530            case 0x61:
 531                if (partId == 0) {
 532                    // https://github.com/Dr-Noob/cpufetch/issues/213#issuecomment-1927782105
 533                    ffStrbufSetStatic(&cpu->name, "Virtualized Apple Silicon");
 534                    ffStrbufGetlineRestore(&line, &len, cpuinfo);
 535                    return;
 536                }
 537                name = applePartId2name(partId);
 538                break;
 539            case 0x66:
 540                name = faradayPartId2name(partId);
 541                break;
 542            case 0x69:
 543                name = intelPartId2name(partId);
 544                break;
 545            case 0x6d:
 546                name = msPartId2name(partId);
 547                break;
 548            case 0x70:
 549                name = ftPartId2name(partId);
 550                break;
 551            case 0xc0:
 552                name = amperePartId2name(partId);
 553                break;
 554        }
 555        if (lastPartId != partId) {
 556            if (lastPartId != UINT32_MAX) {
 557                ffStrbufAppendC(&cpu->name, '+');
 558            }
 559            if (name) {
 560                ffStrbufAppendS(&cpu->name, name);
 561            } else if (partId) {
 562                ffStrbufAppendF(&cpu->name, "%s-%X", cpu->vendor.chars, partId);
 563            } else {
 564                ffStrbufAppend(&cpu->name, &cpu->vendor);
 565            }
 566            lastPartId = partId;
 567        }
 568    }
 569}
 570#endif
 571
 572static const char* parseCpuInfo(
 573    FFstrbuf* cpuinfo,
 574    FFCPUResult* cpu,
 575    [[maybe_unused]] FFstrbuf* physicalCoresBuffer,
 576    [[maybe_unused]] FFstrbuf* cpuMHz,
 577    [[maybe_unused]] FFstrbuf* cpuIsa,
 578    [[maybe_unused]] FFstrbuf* cpuUarch,
 579    [[maybe_unused]] FFstrbuf* cpuImplementer) {
 580    char* line = nullptr;
 581    size_t len = 0;
 582
 583    while (ffStrbufGetline(&line, &len, cpuinfo)) {
 584        // Stop after reasonable information is acquired
 585        if ((*line == '\0' || *line == '\n') && cpu->name.length > 0) {
 586            ffStrbufGetlineRestore(&line, &len, cpuinfo);
 587            break;
 588        }
 589
 590        (void) (
 591// arm64 doesn't have "model name"; arm32 does have "model name" but its value is not useful.
 592// "Hardware" should always be used in this case
 593#if __x86_64__ || __i386__
 594            (cpu->name.length == 0 && ffParsePropLine(line, "model name :", &cpu->name)) ||
 595            (cpu->vendor.length == 0 && ffParsePropLine(line, "vendor_id :", &cpu->vendor)) ||
 596            (physicalCoresBuffer->length == 0 && ffParsePropLine(line, "cpu cores :", physicalCoresBuffer)) ||
 597            (cpuMHz->length == 0 && ffParsePropLine(line, "cpu MHz :", cpuMHz)) ||
 598#elif __arm__ || __aarch64__
 599            (cpuImplementer->length == 0 && ffParsePropLine(line, "CPU implementer :", cpuImplementer)) ||
 600            (cpu->name.length == 0 && ffParsePropLine(line, "Hardware :", &cpu->name)) || // For Android devices
 601#elif __powerpc__ || __powerpc
 602            (cpuMHz->length == 0 && ffParsePropLine(line, "clock :", cpuMHz)) ||
 603            (cpu->name.length == 0 && ffParsePropLine(line, "cpu :", &cpu->name)) ||
 604#elif __mips__ || __mips
 605            (cpu->name.length == 0 && ffParsePropLine(line, "cpu model :", &cpu->name)) ||
 606#elif __loongarch__
 607            (cpu->name.length == 0 && ffParsePropLine(line, "Model Name :", &cpu->name)) ||
 608            (cpuMHz->length == 0 && ffParsePropLine(line, "CPU MHz :", cpuMHz)) ||
 609#elif __riscv__ || __riscv
 610            (cpuIsa->length == 0 && ffParsePropLine(line, "isa :", cpuIsa)) ||
 611            (cpuUarch->length == 0 && ffParsePropLine(line, "uarch :", cpuUarch)) ||
 612#elif __s390x__
 613            (cpu->name.length == 0 && ffParsePropLine(line, "machine :", &cpu->name)) ||
 614            (cpu->vendor.length == 0 && ffParsePropLine(line, "vendor_id :", &cpu->vendor)) ||
 615            (cpuMHz->length == 0 && ffParsePropLine(line, "cpu MHz static :", cpuMHz)) ||
 616#elif __ia64__
 617            (cpu->name.length == 0 && ffParsePropLine(line, "model name :", &cpu->name)) ||
 618            (cpu->vendor.length == 0 && ffParsePropLine(line, "vendor :", &cpu->vendor)) ||
 619            (cpuMHz->length == 0 && ffParsePropLine(line, "cpu MHz :", cpuMHz)) ||
 620#elif __hppa__
 621            (cpu->name.length == 0 && ffParsePropLine(line, "cpu :", &cpu->name)) ||
 622#elif __sh__
 623            (cpu->name.length == 0 && ffParsePropLine(line, "cpu type :", &cpu->name)) ||
 624#elif __sparc__ || __sparc
 625            (cpu->name.length == 0 && ffParsePropLine(line, "cpu :", &cpu->name)) ||
 626            (cpuMHz->length == 0 && ffParsePropLine(line, "Cpu0ClkTck :", cpuMHz)) ||
 627#else
 628            (cpu->name.length == 0 && ffParsePropLine(line, "model name :", &cpu->name)) ||
 629            (cpu->name.length == 0 && ffParsePropLine(line, "model :", &cpu->name)) ||
 630            (cpu->name.length == 0 && ffParsePropLine(line, "cpu model :", &cpu->name)) ||
 631            (cpu->name.length == 0 && ffParsePropLine(line, "hardware :", &cpu->name)) ||
 632            (cpu->name.length == 0 && ffParsePropLine(line, "processor :", &cpu->name)) ||
 633#endif
 634
 635            false);
 636    }
 637
 638    return nullptr;
 639}
 640
 641static uint32_t getFrequency(int policyFd, const char* cpuinfoFileName, const char* scalingFileName, FFstrbuf* buffer) {
 642    bool ok = ffReadFileBufferRelative(policyFd, cpuinfoFileName, buffer);
 643    if (ok) {
 644        return (uint32_t) (ffStrbufToUInt(buffer, 0) / 1000);
 645    }
 646
 647    if (scalingFileName) {
 648        ok = ffReadFileBufferRelative(policyFd, scalingFileName, buffer);
 649        if (ok) {
 650            return (uint32_t) (ffStrbufToUInt(buffer, 0) / 1000);
 651        }
 652    }
 653
 654    return 0;
 655}
 656
 657static bool detectFrequency(FFCPUResult* cpu, const FFCPUOptions* options) {
 658    const char* basePath = "/sys/devices/system/cpu/cpufreq/";
 659    FF_AUTO_CLOSE_DIR DIR* dir = opendir(basePath);
 660    if (!dir) {
 661        return false;
 662    }
 663    int freqFd = dirfd(dir);
 664
 665    FF_STRBUF_AUTO_DESTROY buffer = ffStrbufCreate();
 666
 667    struct dirent* entry;
 668    while ((entry = readdir(dir)) != nullptr) {
 669        if (ffStrStartsWith(entry->d_name, "policy") && ffCharIsDigit(entry->d_name[strlen("policy")])) {
 670            FF_AUTO_CLOSE_FD int policyFd = openat(freqFd, entry->d_name, O_RDONLY | O_DIRECTORY | O_CLOEXEC | O_PATH);
 671            if (policyFd < 0) {
 672                continue;
 673            }
 674
 675            uint32_t fmax = getFrequency(policyFd, "cpuinfo_max_freq", "scaling_max_freq", &buffer);
 676            if (fmax == 0) {
 677                continue;
 678            }
 679
 680            if (cpu->frequencyMax >= fmax) {
 681                if (!options->showPeCoreCount) {
 682                    continue;
 683                }
 684            } else {
 685                cpu->frequencyMax = fmax;
 686            }
 687
 688            uint32_t fbase = getFrequency(policyFd, "base_frequency", nullptr, &buffer);
 689            if (fbase > 0) {
 690                cpu->frequencyBase = cpu->frequencyBase > fbase ? cpu->frequencyBase : fbase;
 691            }
 692
 693            if (options->showPeCoreCount) {
 694                uint32_t freq = fbase == 0 ? fmax : fbase; // seems base frequencies are more stable
 695                uint32_t ifreq = 0;
 696                while (cpu->coreTypes[ifreq].freq != freq && cpu->coreTypes[ifreq].freq > 0) {
 697                    ++ifreq;
 698                }
 699                if (cpu->coreTypes[ifreq].freq == 0) {
 700                    cpu->coreTypes[ifreq].freq = freq;
 701                }
 702                if (ffReadFileBufferRelative(policyFd, "affected_cpus", &buffer)) {
 703                    cpu->coreTypes[ifreq].count += ffStrbufCountC(&buffer, ' ') + 1;
 704                }
 705            }
 706        }
 707    }
 708    return true;
 709}
 710
 711bool ffCPUQualcommSnapdragonToName(FFstrbuf* name, char* compatibles) {
 712    // While this also would be 555 below split it out as it's only seen on the devkit
 713    for (char* p = compatibles; *p; p += strlen(p) + 1) {
 714        if (strstr(p, "x1e001de") != nullptr) {
 715            ffStrbufSetS(name, "Qualcomm Snapdragon X Elite X1E-00-1DE");
 716            return true;
 717        }
 718    }
 719
 720    // https://github.com/tianocore/edk2-platforms/blob/9c2cdde/Silicon/Qualcomm/Common/QualcommCommonSiliconPkg/Include/ChipInfoDefs.h
 721    char content[16];
 722    ssize_t length = ffReadFileData("/sys/devices/soc0/soc_id", ARRAY_SIZE(content), content);
 723    if (length < 1) {
 724        return false;
 725    }
 726    if (content[length - 1] != '\n') {
 727        return false; // must end with \n
 728    }
 729
 730    unsigned long long soc_id = strtoull(content, nullptr, 10);
 731    const char* soc = nullptr;
 732    switch (soc_id) {
 733        // glymur
 734        case 662:
 735            soc = "X2 Elite Extreme X2E-94/96-100";
 736            break;
 737        case 698:
 738            soc = "X2 Elite X2E-88/90-100";
 739            break;
 740        // mahua
 741        case 699:
 742            soc = "X2 Elite X2E-80/84-100";
 743            break;
 744        case 693:
 745            soc = "X2 Elite X2E-78-100";
 746            break;
 747        case 728:
 748            soc = "X2 Plus X2P-64-100";
 749            break;
 750        // kalambo
 751        case 719:
 752            soc = "X2 Plus X2P-42-100";
 753            break;
 754        // hamoa
 755        case 555:
 756            soc = "X Elite X1E-78/80/84-100";
 757            break;
 758        case 615:
 759            soc = "X Plus X1P-64/66-100";
 760            break;
 761        case 616:
 762            soc = "X X1-26-101";
 763            break;
 764        // purwa
 765        case 635:
 766            soc = "X X1-26-100 / X Plus X1P-42/46-100";
 767            break;
 768    }
 769    if (soc) {
 770        ffStrbufSetS(name, "Qualcomm Snapdragon ");
 771        ffStrbufAppendS(name, soc);
 772        return true;
 773    }
 774
 775    return false;
 776}
 777
 778#if __i386__ || __x86_64__
 779
 780[[maybe_unused]] static uint16_t getPackageCount(FFstrbuf* cpuinfo) {
 781    const char* p = cpuinfo->chars;
 782    uint64_t low = 0, high = 0;
 783
 784    while ((p = memmem(p, cpuinfo->length - (uint32_t) (p - cpuinfo->chars), "\nphysical id\t:", strlen("\nphysical id\t:")))) {
 785        p += strlen("\nphysical id\t:");
 786        char* pend;
 787        unsigned long id = strtoul(p, &pend, 10);
 788        if (__builtin_expect(id > 64, false)) { // Do 129-socket boards exist?
 789            high |= 1ULL << (id - 64);
 790        } else {
 791            low |= 1ULL << id;
 792        }
 793        p = pend;
 794    }
 795
 796    return (uint16_t) (__builtin_popcountll(low) + __builtin_popcountll(high));
 797}
 798
 799[[maybe_unused]] static const char* detectCPUX86(const FFCPUOptions* options, FFCPUResult* cpu) {
 800    FF_STRBUF_AUTO_DESTROY cpuinfo = ffStrbufCreateA(PROC_FILE_BUFFSIZ);
 801    if (!ffReadFileBuffer(FF_CPUINFO_PATH, &cpuinfo) || cpuinfo.length == 0) {
 802        return "ffReadFileBuffer(\"" FF_CPUINFO_PATH "\") failed";
 803    }
 804
 805    FF_STRBUF_AUTO_DESTROY physicalCoresBuffer = ffStrbufCreate();
 806    FF_STRBUF_AUTO_DESTROY cpuMHz = ffStrbufCreate();
 807    const char* error = parseCpuInfo(&cpuinfo, cpu, &physicalCoresBuffer, &cpuMHz, nullptr, nullptr, nullptr);
 808    if (error) {
 809        return error;
 810    }
 811
 812    cpu->coresLogical = (uint16_t) get_nprocs_conf();
 813    cpu->coresOnline = (uint16_t) get_nprocs();
 814    cpu->packages = getPackageCount(&cpuinfo);
 815    cpu->coresPhysical = (uint16_t) ffStrbufToUInt(&physicalCoresBuffer, 0); // physical cores in single package
 816    if (cpu->coresPhysical > 0 && cpu->packages > 1) {
 817        cpu->coresPhysical *= cpu->packages;
 818    }
 819
 820    // Ref https://github.com/fastfetch-cli/fastfetch/issues/1194#issuecomment-2295058252
 821    ffCPUDetectByCpuid(cpu);
 822    if (!detectFrequency(cpu, options) || cpu->frequencyBase == 0) {
 823        cpu->frequencyBase = (uint32_t) ffStrbufToUInt(&cpuMHz, 0);
 824    }
 825
 826    detectNumaNodes(cpu);
 827
 828    return nullptr;
 829}
 830
 831#else
 832
 833static const char* detectPhysicalCores(FFCPUResult* cpu) {
 834    int dfd = open("/sys/devices/system/cpu/", O_RDONLY | O_DIRECTORY | O_CLOEXEC);
 835    if (dfd < 0) {
 836        FF_DEBUG("open(/sys/devices/system/cpu/) failed: %s", strerror(errno));
 837        return "open(\"/sys/devices/system/cpu/\") failed";
 838    }
 839
 840    FF_AUTO_CLOSE_DIR DIR* dir = fdopendir(dfd);
 841    if (!dir) {
 842        FF_DEBUG("fdopendir(/sys/devices/system/cpu/) failed: %s", strerror(errno));
 843        close(dfd);
 844        return "fdopendir(dfd) failed";
 845    }
 846
 847    uint64_t pkgLow = 0, pkgHigh = 0;
 848
 849    struct dirent* entry;
 850    FF_LIST_AUTO_DESTROY cpuList = ffListCreate();
 851    while ((entry = readdir(dir)) != nullptr) {
 852        if (entry->d_type != DT_DIR || !ffStrStartsWith(entry->d_name, "cpu") || !ffCharIsDigit(entry->d_name[strlen("cpu")])) {
 853            continue;
 854        }
 855
 856        FF_AUTO_CLOSE_FD int cpuxfd = openat(dirfd(dir), entry->d_name, O_RDONLY | O_DIRECTORY | O_CLOEXEC);
 857        if (cpuxfd < 0) {
 858            continue;
 859        }
 860
 861        char buf[128];
 862
 863        // Check if the directory contains a file named "topology/physical_package_id"
 864        // that lists the physical package id of the CPU.
 865
 866        ssize_t len = ffReadFileDataRelative(cpuxfd, "topology/physical_package_id", sizeof(buf) - 1, buf);
 867        if (len > 0) {
 868            buf[len] = '\0';
 869            unsigned long long id = strtoull(buf, nullptr, 10);
 870            if (__builtin_expect(id < 128, true)) {
 871                // Do 129-socket boards exist?
 872                if (__builtin_expect(id >= 64, false)) {
 873                    pkgHigh |= 1ULL << (id - 64);
 874                } else {
 875                    pkgLow |= 1ULL << id;
 876                }
 877            }
 878        }
 879
 880        // Check if the directory contains a file named "topology/core_cpus_list"
 881        // that lists the logical cores in the same physical core.
 882
 883        len = ffReadFileDataRelative(cpuxfd, "topology/core_cpus_list", sizeof(buf) - 1, buf);
 884        if (len > 0) {
 885            buf[len] = '\0'; // low[-high, low-high, ...]
 886
 887            char* pend;
 888            // We assume that the different physical cores exposes different logical core ids,
 889            // so that the first `low` is always different between different physical cores.
 890            uint32_t coreId = (uint32_t) strtoul(buf, &pend, 10);
 891            if (pend == buf) {
 892                break;
 893            }
 894
 895            bool found = false;
 896            FF_LIST_FOR_EACH (uint32_t, id, cpuList) {
 897                if (*id == coreId) {
 898                    // This core is already counted
 899                    found = true;
 900                    break;
 901                }
 902            }
 903            if (!found) {
 904                *FF_LIST_ADD(uint32_t, cpuList) = coreId;
 905            }
 906        }
 907    }
 908
 909    cpu->coresPhysical = (uint16_t) cpuList.length;
 910    cpu->packages = (uint16_t) (__builtin_popcountll(pkgLow) + __builtin_popcountll(pkgHigh));
 911    return nullptr;
 912}
 913
 914[[maybe_unused]] static void parseIsa(FFstrbuf* cpuIsa) {
 915    // Always use the last part of the ISA string. Ref: #590 #1204
 916    ffStrbufSubstrAfterLastC(cpuIsa, ' ');
 917
 918    if (ffStrbufStartsWithS(cpuIsa, "rv")) {
 919        // RISC-V ISA string example: "rv64imafdch_zicsr_zifencei".
 920        // The _z parts are not important for CPU showcasing, so we remove them.
 921        if (ffStrbufContainC(cpuIsa, '_')) {
 922            ffStrbufSubstrBeforeFirstC(cpuIsa, '_');
 923        }
 924        // Then we replace "imafd" with "g" since "g" is a shorthand.
 925        if (ffStrbufContainS(cpuIsa, "imafd")) {
 926            // Remove 4 of the 5 characters and replace the remaining one with "g".
 927            ffStrbufRemoveSubstr(cpuIsa, 4, 8);
 928            cpuIsa->chars[4] = 'g';
 929        }
 930        // The final ISA output of the above example is "rv64gch".
 931    }
 932}
 933
 934[[maybe_unused]] static void detectSocName(FFCPUResult* cpu) {
 935    if (cpu->name.length > 0) {
 936        return;
 937    }
 938
 939    // [x-vendor,x-model\0]*N
 940    char content[512];
 941    ssize_t length = ffReadFileData("/sys/firmware/devicetree/base/compatible", ARRAY_SIZE(content), content);
 942    if (length < 4) {
 943        return; // v,m\0
 944    }
 945
 946    if (content[length - 1] != '\0') {
 947        return; // must end with \0
 948    }
 949
 950    --length;
 951
 952    char* vendor = nullptr;
 953    char* model = nullptr;
 954
 955    for (char* p; length > 0; length = p ? (ssize_t) (p - content) - 1 : 0) {
 956        p = memrchr(content, '\0', (size_t) length);
 957
 958        vendor = p /* first entry */ ? p + 1 : content;
 959
 960        size_t partLen = (size_t) (length - (vendor - content));
 961        if (partLen < 3) {
 962            continue;
 963        }
 964
 965        char* comma = memchr(vendor, ',', partLen);
 966        if (!comma) {
 967            continue;
 968        }
 969
 970        size_t vendorLen = (size_t) (comma - vendor);
 971        if (vendorLen == 0) {
 972            continue;
 973        }
 974
 975        model = comma + 1;
 976        size_t modelLen = (size_t) (partLen - (size_t) (model - vendor));
 977        if (modelLen == 0) {
 978            continue;
 979        }
 980
 981        if ((modelLen >= strlen("-platform") && ffStrEndsWith(model, "-platform")) ||
 982            (modelLen >= strlen("-soc") && ffStrEndsWith(model, "-soc"))) {
 983            continue;
 984        }
 985
 986        *comma = '\0';
 987        break;
 988    }
 989
 990    if (!length) {
 991        return;
 992    }
 993
 994    if (false) {
 995    }
 996    #if __aarch64__
 997    else if (ffStrEquals(vendor, "apple")) {
 998        // https://elixir.bootlin.com/linux/v6.11/source/arch/arm64/boot/dts/apple
 999        if (model[0] == 't') {
1000            uint32_t deviceId = (uint32_t) strtoul(model + 1, nullptr, 10);
1001            ffStrbufSetStatic(&cpu->name, ffCPUAppleCodeToName(deviceId));
1002
1003            if (!cpu->name.length) {
1004                ffStrbufSetS(&cpu->name, "Apple Silicon ");
1005                ffStrbufAppendS(&cpu->name, model);
1006            }
1007        } else {
1008            ffStrbufSetS(&cpu->name, model);
1009        }
1010
1011        ffStrbufSetStatic(&cpu->vendor, "Apple");
1012    }
1013    #endif
1014    else if (ffStrEquals(vendor, "qcom")) {
1015        // https://elixir.bootlin.com/linux/latest/source/arch/arm64/boot/dts/qcom
1016        if (!ffCPUQualcommSnapdragonToName(&cpu->name, content)) {
1017            if (ffStrStartsWith(model, "sc")) {
1018                const char* code = model + 2;
1019                uint32_t deviceId = (uint32_t) strtoul(code, nullptr, 10);
1020                ffStrbufSetStatic(&cpu->name, ffCPUQualcommCodeToName(deviceId));
1021                if (!cpu->name.length) {
1022                    ffStrbufAppendS(&cpu->name, "Qualcomm Snapdragon SC");
1023                    ffStrbufAppendS(&cpu->name, code);
1024                }
1025            } else {
1026                ffStrbufSetS(&cpu->name, model);
1027            }
1028        }
1029
1030        ffStrbufSetStatic(&cpu->vendor, "Qualcomm");
1031    } else if (ffStrEquals(vendor, "brcm")) {
1032        // Raspberry Pi
1033        ffStrbufSetStatic(&cpu->vendor, "Broadcom");
1034        for (const char* p = model; *p; ++p) {
1035            ffStrbufAppendC(&cpu->name, (char) toupper(*p));
1036        }
1037    } else if (ffStrEquals(vendor, "thead")) {
1038        // Lichee Pi?
1039        ffStrbufSetStatic(&cpu->vendor, "T-Head");
1040        for (const char* p = model; *p; ++p) {
1041            ffStrbufAppendC(&cpu->name, (char) toupper(*p));
1042        }
1043    } else {
1044        ffStrbufSetS(&cpu->name, model);
1045        ffStrbufSetS(&cpu->vendor, vendor);
1046        cpu->vendor.chars[0] = (char) toupper(vendor[0]);
1047    }
1048}
1049
1050    #ifdef __loongarch__
1051[[maybe_unused]] static uint16_t getLoongarchPropCount(FFstrbuf* cpuinfo, const char* key) {
1052    const char* p = cpuinfo->chars;
1053    uint64_t low = 0, high = 0;
1054    uint32_t keylen = (uint32_t) strlen(key);
1055
1056    while ((p = memmem(p, cpuinfo->length - (uint32_t) (p - cpuinfo->chars), key, keylen))) {
1057        p += keylen;
1058        char* pend;
1059        unsigned long id = strtoul(p, &pend, 10);
1060        if (__builtin_expect(id > 64, false)) {
1061            high |= 1UL << (id - 64);
1062        } else {
1063            low |= 1UL << id;
1064        }
1065        p = pend;
1066    }
1067
1068    return (uint16_t) (__builtin_popcountll(low) + __builtin_popcountll(high));
1069}
1070    #endif
1071
1072[[maybe_unused]] static const char* detectCPUOthers(const FFCPUOptions* options, FFCPUResult* cpu) {
1073    cpu->coresLogical = (uint16_t) get_nprocs_conf();
1074    cpu->coresOnline = (uint16_t) get_nprocs();
1075
1076    #if __ANDROID__
1077    detectAndroid(cpu);
1078    #elif !__powerpc__ && !__powerpc
1079    detectSocName(cpu);
1080    #endif
1081
1082    detectFrequency(cpu, options);
1083
1084    if (cpu->name.length == 0) {
1085        FF_STRBUF_AUTO_DESTROY cpuinfo = ffStrbufCreateA(PROC_FILE_BUFFSIZ);
1086        if (!ffReadFileBuffer(FF_CPUINFO_PATH, &cpuinfo) || cpuinfo.length == 0) {
1087            return "ffReadFileBuffer(\"" FF_CPUINFO_PATH "\") failed";
1088        }
1089
1090        FF_STRBUF_AUTO_DESTROY cpuMHz = ffStrbufCreate();
1091        FF_STRBUF_AUTO_DESTROY cpuIsa = ffStrbufCreate();
1092        FF_STRBUF_AUTO_DESTROY cpuUarch = ffStrbufCreate();
1093        FF_STRBUF_AUTO_DESTROY cpuImplementerStr = ffStrbufCreate();
1094
1095        const char* error = parseCpuInfo(&cpuinfo, cpu, nullptr, &cpuMHz, &cpuIsa, &cpuUarch, &cpuImplementerStr);
1096        if (error) {
1097            return error;
1098        }
1099
1100        if (cpu->frequencyBase == 0) {
1101            cpu->frequencyBase = (uint32_t) ffStrbufToUInt(&cpuMHz, 0);
1102        }
1103
1104    #if __arm__ || __aarch64__
1105        uint32_t cpuImplementer = (uint32_t) strtoul(cpuImplementerStr.chars, nullptr, 16);
1106        ffStrbufSetStatic(&cpu->vendor, hwImplId2Vendor(cpuImplementer));
1107
1108        if (cpu->name.length == 0) {
1109            detectArmName(&cpuinfo, cpu, cpuImplementer);
1110        }
1111    #elif __riscv__ || __riscv
1112        if (cpu->name.length == 0) {
1113            if (cpuUarch.length > 0) {
1114                if (cpu->name.length > 0) {
1115                    ffStrbufAppendC(&cpu->name, ' ');
1116                }
1117                ffStrbufAppend(&cpu->name, &cpuUarch);
1118            }
1119
1120            if (cpuIsa.length > 0) {
1121                parseIsa(&cpuIsa);
1122                if (cpu->name.length > 0) {
1123                    ffStrbufAppendC(&cpu->name, ' ');
1124                }
1125                ffStrbufAppend(&cpu->name, &cpuIsa);
1126            }
1127        }
1128    #elif __loongarch__
1129        cpu->packages = getLoongarchPropCount(&cpuinfo, "\npackage\t\t\t:");
1130        cpu->coresPhysical = getLoongarchPropCount(&cpuinfo, "\ncore\t\t\t:");
1131        if (cpu->packages > 1) {
1132            cpu->coresPhysical *= cpu->packages;
1133        }
1134    #elif __s390x__
1135        if (cpu->name.length) {
1136            ffStrbufPrependS(&cpu->name, "Machine ");
1137        }
1138    #elif __sparc__ || __sparc
1139        // Cpu0ClkTck is in Hz, printed as "%016lx" by sparc64 and "%ld" by sparc32. A 32-bit userland can run
1140        // on a 64-bit kernel, so take the base from the width of the value rather than from our own bitness.
1141        cpu->frequencyBase = (uint32_t) (strtoull(cpuMHz.chars, nullptr, cpuMHz.length == 16 ? 16 : 10) / 1000000);
1142    #endif
1143    }
1144
1145    if (cpu->coresPhysical == 0) {
1146        detectPhysicalCores(cpu);
1147    }
1148
1149    ffCPUDetectByCpuid(cpu);
1150    detectNumaNodes(cpu);
1151
1152    return nullptr;
1153}
1154#endif
1155
1156const char* ffDetectCPUImpl(const FFCPUOptions* options, FFCPUResult* cpu) {
1157    cpu->temperature = options->temp ? detectCPUTemp(options) : FF_CPU_TEMP_UNSET;
1158
1159#if __x86_64__ || __i386__
1160    return detectCPUX86(options, cpu);
1161#else
1162    return detectCPUOthers(options, cpu);
1163#endif
1164}