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}