1 #include <stdbool.h>
2 #include <stddef.h>
3 #include <stdint.h>
4 #include <stdlib.h>
5 #include <string.h>
6
7 #include <arm/linux/api.h>
8 #include <arm/midr.h>
9 #include <cpuinfo/log.h>
10 #include <linux/api.h>
11
12 /*
13 * Size, in chars, of the on-stack buffer used for parsing lines of
14 * /proc/cpuinfo. This is also the limit on the length of a single line.
15 */
16 #define BUFFER_SIZE 1024
17
parse_processor_number(const char * processor_start,const char * processor_end)18 static uint32_t parse_processor_number(const char* processor_start, const char* processor_end) {
19 const size_t processor_length = (size_t)(processor_end - processor_start);
20
21 if (processor_length == 0) {
22 cpuinfo_log_warning("Processor number in /proc/cpuinfo is ignored: string is empty");
23 return 0;
24 }
25
26 uint32_t processor_number = 0;
27 for (const char* digit_ptr = processor_start; digit_ptr != processor_end; digit_ptr++) {
28 const uint32_t digit = (uint32_t)(*digit_ptr - '0');
29 if (digit > 10) {
30 cpuinfo_log_warning(
31 "non-decimal suffix %.*s in /proc/cpuinfo processor number is ignored",
32 (int)(processor_end - digit_ptr),
33 digit_ptr);
34 break;
35 }
36
37 processor_number = processor_number * 10 + digit;
38 }
39
40 return processor_number;
41 }
42
43 /*
44 * Full list of ARM features reported in /proc/cpuinfo:
45 *
46 * * swp - support for SWP instruction (deprecated in ARMv7, can be removed
47 *in future)
48 * * half - support for half-word loads and stores. These instruction are
49 *part of ARMv4, so no need to check it on supported CPUs.
50 * * thumb - support for 16-bit Thumb instruction set. Note that BX
51 *instruction is detected by ARMv4T architecture, not by this flag.
52 * * 26bit - old CPUs merged 26-bit PC and program status register (flags)
53 *into 32-bit PC and had special instructions for working with packed PC. Now it
54 *is all deprecated.
55 * * fastmult - most old ARM CPUs could only compute 2 bits of
56 *multiplication result per clock cycle, but CPUs with M suffix (e.g. ARM7TDMI)
57 *could compute 4 bits per cycle. Of course, now it makes no sense.
58 * * fpa - floating point accelerator available. On original ARM ABI all
59 *floating-point operations generated FPA instructions. If FPA was not
60 *available, these instructions generated "illegal operation" interrupts, and
61 *the OS processed them by emulating the FPA instructions. Debian used this ABI
62 *before it switched to EABI. Now FPA is deprecated.
63 * * vfp - vector floating point instructions. Available on most modern
64 *CPUs (as part of VFPv3). Required by Android ARMv7A ABI and by Ubuntu on ARM.
65 * Note: there is no flag for VFPv2.
66 * * edsp - V5E instructions: saturating add/sub and 16-bit x 16-bit ->
67 *32/64-bit multiplications. Required on Android, supported by all CPUs in
68 *production.
69 * * java - Jazelle extension. Supported on most CPUs.
70 * * iwmmxt - Intel/Marvell Wireless MMX instructions. 64-bit integer SIMD.
71 * Supported on XScale (Since PXA270) and Sheeva (PJ1, PJ4)
72 *architectures. Note that there is no flag for WMMX2 instructions.
73 * * crunch - Maverick Crunch instructions. Junk.
74 * * thumbee - ThumbEE instructions. Almost no documentation is available.
75 * * neon - NEON instructions (aka Advanced SIMD). MVFR1 register gives
76 *more fine-grained information on particular supported features, but the Linux
77 *kernel exports only a single flag for all of them. According to ARMv7A docs it
78 *also implies the availability of VFPv3 (with 32 double-precision registers
79 *d0-d31).
80 * * vfpv3 - VFPv3 instructions. Available on most modern CPUs. Augment
81 *VFPv2 by conversion to/from integers and load constant instructions. Required
82 *by Android ARMv7A ABI and by Ubuntu on ARM.
83 * * vfpv3d16 - VFPv3 instructions with only 16 double-precision registers
84 *(d0-d15).
85 * * tls - software thread ID registers.
86 * Used by kernel (and likely libc) for efficient implementation of
87 *TLS.
88 * * vfpv4 - fused multiply-add instructions.
89 * * idiva - DIV instructions available in ARM mode.
90 * * idivt - DIV instructions available in Thumb mode.
91 * * vfpd32 - VFP (of any version) with 32 double-precision registers d0-d31.
92 * * lpae - Large Physical Address Extension (physical address up to 40 bits).
93 * * evtstrm - generation of Event Stream by timer.
94 * * aes - AES instructions.
95 * * pmull - Polinomial Multiplication instructions.
96 * * sha1 - SHA1 instructions.
97 * * sha2 - SHA2 instructions.
98 * * crc32 - CRC32 instructions.
99 *
100 * /proc/cpuinfo on ARM is populated in file arch/arm/kernel/setup.c in
101 *Linux kernel Note that some devices may use patched Linux kernels with
102 *different feature names. However, the names above were checked on a large
103 *number of /proc/cpuinfo listings.
104 */
parse_features(const char * features_start,const char * features_end,struct cpuinfo_arm_linux_processor processor[restrict static1])105 static void parse_features(
106 const char* features_start,
107 const char* features_end,
108 struct cpuinfo_arm_linux_processor processor[restrict static 1]) {
109 const char* feature_start = features_start;
110 const char* feature_end;
111
112 /* Mark the features as valid */
113 processor->flags |= CPUINFO_ARM_LINUX_VALID_FEATURES | CPUINFO_ARM_LINUX_VALID_PROCESSOR;
114
115 do {
116 feature_end = feature_start + 1;
117 for (; feature_end != features_end; feature_end++) {
118 if (*feature_end == ' ') {
119 break;
120 }
121 }
122 const size_t feature_length = (size_t)(feature_end - feature_start);
123
124 switch (feature_length) {
125 case 2:
126 if (memcmp(feature_start, "fp", feature_length) == 0) {
127 #if CPUINFO_ARCH_ARM64
128 processor->features |= CPUINFO_ARM_LINUX_FEATURE_FP;
129 #endif
130 #if CPUINFO_ARCH_ARM
131 } else if (memcmp(feature_start, "wp", feature_length) == 0) {
132 /*
133 * Some AArch64 kernels, including the
134 * one on Nexus 5X, erroneously report
135 * "swp" as "wp" to AArch32 programs
136 */
137 processor->features |= CPUINFO_ARM_LINUX_FEATURE_SWP;
138 #endif
139 } else {
140 goto unexpected;
141 }
142 break;
143 case 3:
144 if (memcmp(feature_start, "aes", feature_length) == 0) {
145 #if CPUINFO_ARCH_ARM
146 processor->features2 |= CPUINFO_ARM_LINUX_FEATURE2_AES;
147 #elif CPUINFO_ARCH_ARM64
148 processor->features |= CPUINFO_ARM_LINUX_FEATURE_AES;
149 #endif
150 #if CPUINFO_ARCH_ARM
151 } else if (memcmp(feature_start, "swp", feature_length) == 0) {
152 processor->features |= CPUINFO_ARM_LINUX_FEATURE_SWP;
153 } else if (memcmp(feature_start, "fpa", feature_length) == 0) {
154 processor->features |= CPUINFO_ARM_LINUX_FEATURE_FPA;
155 } else if (memcmp(feature_start, "vfp", feature_length) == 0) {
156 processor->features |= CPUINFO_ARM_LINUX_FEATURE_VFP;
157 } else if (memcmp(feature_start, "tls", feature_length) == 0) {
158 processor->features |= CPUINFO_ARM_LINUX_FEATURE_TLS;
159 #endif /* CPUINFO_ARCH_ARM */
160 } else {
161 goto unexpected;
162 }
163 break;
164 case 4:
165 if (memcmp(feature_start, "sha1", feature_length) == 0) {
166 #if CPUINFO_ARCH_ARM
167 processor->features2 |= CPUINFO_ARM_LINUX_FEATURE2_SHA1;
168 #elif CPUINFO_ARCH_ARM64
169 processor->features |= CPUINFO_ARM_LINUX_FEATURE_SHA1;
170 #endif
171 } else if (memcmp(feature_start, "sha2", feature_length) == 0) {
172 #if CPUINFO_ARCH_ARM
173 processor->features2 |= CPUINFO_ARM_LINUX_FEATURE2_SHA2;
174 #elif CPUINFO_ARCH_ARM64
175 processor->features |= CPUINFO_ARM_LINUX_FEATURE_SHA2;
176 #endif
177 } else if (memcmp(feature_start, "fphp", feature_length) == 0) {
178 #if CPUINFO_ARCH_ARM64
179 processor->features |= CPUINFO_ARM_LINUX_FEATURE_FPHP;
180 #endif
181 } else if (memcmp(feature_start, "fcma", feature_length) == 0) {
182 #if CPUINFO_ARCH_ARM64
183 processor->features |= CPUINFO_ARM_LINUX_FEATURE_FCMA;
184 #endif
185 } else if (memcmp(feature_start, "i8mm", feature_length) == 0) {
186 #if CPUINFO_ARCH_ARM64
187 processor->features2 |= CPUINFO_ARM_LINUX_FEATURE2_I8MM;
188 #endif
189 #if CPUINFO_ARCH_ARM
190 } else if (memcmp(feature_start, "half", feature_length) == 0) {
191 processor->features |= CPUINFO_ARM_LINUX_FEATURE_HALF;
192 } else if (memcmp(feature_start, "edsp", feature_length) == 0) {
193 processor->features |= CPUINFO_ARM_LINUX_FEATURE_EDSP;
194 } else if (memcmp(feature_start, "java", feature_length) == 0) {
195 processor->features |= CPUINFO_ARM_LINUX_FEATURE_JAVA;
196 } else if (memcmp(feature_start, "neon", feature_length) == 0) {
197 processor->features |= CPUINFO_ARM_LINUX_FEATURE_NEON;
198 } else if (memcmp(feature_start, "lpae", feature_length) == 0) {
199 processor->features |= CPUINFO_ARM_LINUX_FEATURE_LPAE;
200 } else if (memcmp(feature_start, "tlsi", feature_length) == 0) {
201 /*
202 * Some AArch64 kernels, including the
203 * one on Nexus 5X, erroneously report
204 * "tls" as "tlsi" to AArch32 programs
205 */
206 processor->features |= CPUINFO_ARM_LINUX_FEATURE_TLS;
207 #endif /* CPUINFO_ARCH_ARM */
208 } else {
209 goto unexpected;
210 }
211 break;
212 case 5:
213 if (memcmp(feature_start, "pmull", feature_length) == 0) {
214 #if CPUINFO_ARCH_ARM
215 processor->features2 |= CPUINFO_ARM_LINUX_FEATURE2_PMULL;
216 #elif CPUINFO_ARCH_ARM64
217 processor->features |= CPUINFO_ARM_LINUX_FEATURE_PMULL;
218 #endif
219 } else if (memcmp(feature_start, "crc32", feature_length) == 0) {
220 #if CPUINFO_ARCH_ARM
221 processor->features2 |= CPUINFO_ARM_LINUX_FEATURE2_CRC32;
222 #elif CPUINFO_ARCH_ARM64
223 processor->features |= CPUINFO_ARM_LINUX_FEATURE_CRC32;
224 #endif
225 } else if (memcmp(feature_start, "asimd", feature_length) == 0) {
226 #if CPUINFO_ARCH_ARM64
227 processor->features |= CPUINFO_ARM_LINUX_FEATURE_ASIMD;
228 #endif
229 } else if (memcmp(feature_start, "cpuid", feature_length) == 0) {
230 #if CPUINFO_ARCH_ARM64
231 processor->features |= CPUINFO_ARM_LINUX_FEATURE_CPUID;
232 #endif
233 } else if (memcmp(feature_start, "jscvt", feature_length) == 0) {
234 #if CPUINFO_ARCH_ARM64
235 processor->features |= CPUINFO_ARM_LINUX_FEATURE_JSCVT;
236 #endif
237 } else if (memcmp(feature_start, "lrcpc", feature_length) == 0) {
238 #if CPUINFO_ARCH_ARM64
239 processor->features |= CPUINFO_ARM_LINUX_FEATURE_LRCPC;
240 #endif
241 #if CPUINFO_ARCH_ARM
242 } else if (memcmp(feature_start, "thumb", feature_length) == 0) {
243 processor->features |= CPUINFO_ARM_LINUX_FEATURE_THUMB;
244 } else if (memcmp(feature_start, "26bit", feature_length) == 0) {
245 processor->features |= CPUINFO_ARM_LINUX_FEATURE_26BIT;
246 } else if (memcmp(feature_start, "vfpv3", feature_length) == 0) {
247 processor->features |= CPUINFO_ARM_LINUX_FEATURE_VFPV3;
248 } else if (memcmp(feature_start, "vfpv4", feature_length) == 0) {
249 processor->features |= CPUINFO_ARM_LINUX_FEATURE_VFPV4;
250 } else if (memcmp(feature_start, "idiva", feature_length) == 0) {
251 processor->features |= CPUINFO_ARM_LINUX_FEATURE_IDIVA;
252 } else if (memcmp(feature_start, "idivt", feature_length) == 0) {
253 processor->features |= CPUINFO_ARM_LINUX_FEATURE_IDIVT;
254 #endif /* CPUINFO_ARCH_ARM */
255 } else {
256 goto unexpected;
257 }
258 break;
259 #if CPUINFO_ARCH_ARM
260 case 6:
261 if (memcmp(feature_start, "iwmmxt", feature_length) == 0) {
262 processor->features |= CPUINFO_ARM_LINUX_FEATURE_IWMMXT;
263 } else if (memcmp(feature_start, "crunch", feature_length) == 0) {
264 processor->features |= CPUINFO_ARM_LINUX_FEATURE_CRUNCH;
265 } else if (memcmp(feature_start, "vfpd32", feature_length) == 0) {
266 processor->features |= CPUINFO_ARM_LINUX_FEATURE_VFPD32;
267 } else {
268 goto unexpected;
269 }
270 break;
271 #endif /* CPUINFO_ARCH_ARM */
272 case 7:
273 if (memcmp(feature_start, "evtstrm", feature_length) == 0) {
274 processor->features |= CPUINFO_ARM_LINUX_FEATURE_EVTSTRM;
275 } else if (memcmp(feature_start, "atomics", feature_length) == 0) {
276 #if CPUINFO_ARCH_ARM64
277 processor->features |= CPUINFO_ARM_LINUX_FEATURE_ATOMICS;
278 #endif
279 } else if (memcmp(feature_start, "asimdhp", feature_length) == 0) {
280 #if CPUINFO_ARCH_ARM64
281 processor->features |= CPUINFO_ARM_LINUX_FEATURE_ASIMDHP;
282 #endif
283 #if CPUINFO_ARCH_ARM
284 } else if (memcmp(feature_start, "thumbee", feature_length) == 0) {
285 processor->features |= CPUINFO_ARM_LINUX_FEATURE_THUMBEE;
286 #endif /* CPUINFO_ARCH_ARM */
287 } else {
288 goto unexpected;
289 }
290 break;
291 case 8:
292 if (memcmp(feature_start, "asimdrdm", feature_length) == 0) {
293 #if CPUINFO_ARCH_ARM64
294 processor->features |= CPUINFO_ARM_LINUX_FEATURE_ASIMDRDM;
295 #endif
296 } else if (memcmp(feature_start, "asimdfhm", feature_length) == 0) {
297 #if CPUINFO_ARCH_ARM64
298 processor->features |= CPUINFO_ARM_LINUX_FEATURE_ASIMDFHM;
299 #endif
300 #if CPUINFO_ARCH_ARM
301 } else if (memcmp(feature_start, "fastmult", feature_length) == 0) {
302 processor->features |= CPUINFO_ARM_LINUX_FEATURE_FASTMULT;
303 } else if (memcmp(feature_start, "vfpv3d16", feature_length) == 0) {
304 processor->features |= CPUINFO_ARM_LINUX_FEATURE_VFPV3D16;
305 #endif /* CPUINFO_ARCH_ARM */
306 } else {
307 goto unexpected;
308 }
309 break;
310 default:
311 unexpected:
312 cpuinfo_log_warning(
313 "unexpected /proc/cpuinfo feature \"%.*s\" is ignored",
314 (int)feature_length,
315 feature_start);
316 break;
317 }
318 feature_start = feature_end;
319 for (; feature_start != features_end; feature_start++) {
320 if (*feature_start != ' ') {
321 break;
322 }
323 }
324 } while (feature_start != feature_end);
325 }
326
parse_cpu_architecture(const char * cpu_architecture_start,const char * cpu_architecture_end,struct cpuinfo_arm_linux_processor processor[restrict static1])327 static void parse_cpu_architecture(
328 const char* cpu_architecture_start,
329 const char* cpu_architecture_end,
330 struct cpuinfo_arm_linux_processor processor[restrict static 1]) {
331 const size_t cpu_architecture_length = (size_t)(cpu_architecture_end - cpu_architecture_start);
332 /* Early AArch64 kernels report "CPU architecture: AArch64" instead of a
333 * numeric value 8 */
334 if (cpu_architecture_length == 7) {
335 if (memcmp(cpu_architecture_start, "AArch64", cpu_architecture_length) == 0) {
336 processor->midr = midr_set_architecture(processor->midr, UINT32_C(0xF));
337 processor->architecture_version = 8;
338 processor->flags |= CPUINFO_ARM_LINUX_VALID_ARCHITECTURE | CPUINFO_ARM_LINUX_VALID_PROCESSOR;
339 return;
340 }
341 }
342
343 uint32_t architecture = 0;
344 const char* cpu_architecture_ptr = cpu_architecture_start;
345 for (; cpu_architecture_ptr != cpu_architecture_end; cpu_architecture_ptr++) {
346 const uint32_t digit = (*cpu_architecture_ptr) - '0';
347
348 /* Verify that CPU architecture is a decimal number */
349 if (digit >= 10) {
350 break;
351 }
352
353 architecture = architecture * 10 + digit;
354 }
355
356 if (cpu_architecture_ptr == cpu_architecture_start) {
357 cpuinfo_log_warning(
358 "CPU architecture %.*s in /proc/cpuinfo is ignored due to non-digit at the beginning of the string",
359 (int)cpu_architecture_length,
360 cpu_architecture_start);
361 } else {
362 if (architecture != 0) {
363 processor->architecture_version = architecture;
364 processor->flags |= CPUINFO_ARM_LINUX_VALID_ARCHITECTURE | CPUINFO_ARM_LINUX_VALID_PROCESSOR;
365
366 for (; cpu_architecture_ptr != cpu_architecture_end; cpu_architecture_ptr++) {
367 const char feature = *cpu_architecture_ptr;
368 switch (feature) {
369 #if CPUINFO_ARCH_ARM
370 case 'T':
371 processor->architecture_flags |= CPUINFO_ARM_LINUX_ARCH_T;
372 break;
373 case 'E':
374 processor->architecture_flags |= CPUINFO_ARM_LINUX_ARCH_E;
375 break;
376 case 'J':
377 processor->architecture_flags |= CPUINFO_ARM_LINUX_ARCH_J;
378 break;
379 #endif /* CPUINFO_ARCH_ARM */
380 case ' ':
381 case '\t':
382 /* Ignore whitespace at the end
383 */
384 break;
385 default:
386 cpuinfo_log_warning(
387 "skipped unknown architectural feature '%c' for ARMv%" PRIu32,
388 feature,
389 architecture);
390 break;
391 }
392 }
393 } else {
394 cpuinfo_log_warning(
395 "CPU architecture %.*s in /proc/cpuinfo is ignored due to invalid value (0)",
396 (int)cpu_architecture_length,
397 cpu_architecture_start);
398 }
399 }
400
401 uint32_t midr_architecture = UINT32_C(0xF);
402 #if CPUINFO_ARCH_ARM
403 switch (processor->architecture_version) {
404 case 6:
405 midr_architecture = UINT32_C(0x7); /* ARMv6 */
406 break;
407 case 5:
408 if ((processor->architecture_flags & CPUINFO_ARM_LINUX_ARCH_TEJ) ==
409 CPUINFO_ARM_LINUX_ARCH_TEJ) {
410 midr_architecture = UINT32_C(0x6); /* ARMv5TEJ */
411 } else if (
412 (processor->architecture_flags & CPUINFO_ARM_LINUX_ARCH_TE) ==
413 CPUINFO_ARM_LINUX_ARCH_TE) {
414 midr_architecture = UINT32_C(0x5); /* ARMv5TE */
415 } else {
416 midr_architecture = UINT32_C(0x4); /* ARMv5T */
417 }
418 break;
419 }
420 #endif
421 processor->midr = midr_set_architecture(processor->midr, midr_architecture);
422 }
423
parse_cpu_part(const char * cpu_part_start,const char * cpu_part_end,struct cpuinfo_arm_linux_processor processor[restrict static1])424 static void parse_cpu_part(
425 const char* cpu_part_start,
426 const char* cpu_part_end,
427 struct cpuinfo_arm_linux_processor processor[restrict static 1]) {
428 const size_t cpu_part_length = (size_t)(cpu_part_end - cpu_part_start);
429
430 /*
431 * CPU part should contain hex prefix (0x) and one to three hex digits.
432 * I have never seen less than three digits as a value of this field,
433 * but I don't think it is impossible to see such values in future.
434 * Value can not contain more than three hex digits since
435 * Main ID Register (MIDR) assigns only a 12-bit value for CPU part.
436 */
437 if (cpu_part_length < 3 || cpu_part_length > 5) {
438 cpuinfo_log_warning(
439 "CPU part %.*s in /proc/cpuinfo is ignored due to unexpected length (%zu)",
440 (int)cpu_part_length,
441 cpu_part_start,
442 cpu_part_length);
443 return;
444 }
445
446 /* Verify the presence of hex prefix */
447 if (cpu_part_start[0] != '0' || cpu_part_start[1] != 'x') {
448 cpuinfo_log_warning(
449 "CPU part %.*s in /proc/cpuinfo is ignored due to lack of 0x prefix",
450 (int)cpu_part_length,
451 cpu_part_start);
452 return;
453 }
454
455 /* Verify that characters after hex prefix are hexadecimal digits and
456 * decode them */
457 uint32_t cpu_part = 0;
458 for (const char* digit_ptr = cpu_part_start + 2; digit_ptr != cpu_part_end; digit_ptr++) {
459 const char digit_char = *digit_ptr;
460 uint32_t digit;
461 if (digit_char >= '0' && digit_char <= '9') {
462 digit = digit_char - '0';
463 } else if ((uint32_t)(digit_char - 'A') < 6) {
464 digit = 10 + (digit_char - 'A');
465 } else if ((uint32_t)(digit_char - 'a') < 6) {
466 digit = 10 + (digit_char - 'a');
467 } else {
468 cpuinfo_log_warning(
469 "CPU part %.*s in /proc/cpuinfo is ignored due to unexpected non-hex character %c at offset %zu",
470 (int)cpu_part_length,
471 cpu_part_start,
472 digit_char,
473 (size_t)(digit_ptr - cpu_part_start));
474 return;
475 }
476 cpu_part = cpu_part * 16 + digit;
477 }
478
479 processor->midr = midr_set_part(processor->midr, cpu_part);
480 processor->flags |= CPUINFO_ARM_LINUX_VALID_PART | CPUINFO_ARM_LINUX_VALID_PROCESSOR;
481 }
482
parse_cpu_implementer(const char * cpu_implementer_start,const char * cpu_implementer_end,struct cpuinfo_arm_linux_processor processor[restrict static1])483 static void parse_cpu_implementer(
484 const char* cpu_implementer_start,
485 const char* cpu_implementer_end,
486 struct cpuinfo_arm_linux_processor processor[restrict static 1]) {
487 const size_t cpu_implementer_length = cpu_implementer_end - cpu_implementer_start;
488
489 /*
490 * Value should contain hex prefix (0x) and one or two hex digits.
491 * I have never seen single hex digit as a value of this field,
492 * but I don't think it is impossible in future.
493 * Value can not contain more than two hex digits since
494 * Main ID Register (MIDR) assigns only an 8-bit value for CPU
495 * implementer.
496 */
497 switch (cpu_implementer_length) {
498 case 3:
499 case 4:
500 break;
501 default:
502 cpuinfo_log_warning(
503 "CPU implementer %.*s in /proc/cpuinfo is ignored due to unexpected length (%zu)",
504 (int)cpu_implementer_length,
505 cpu_implementer_start,
506 cpu_implementer_length);
507 return;
508 }
509
510 /* Verify the presence of hex prefix */
511 if (cpu_implementer_start[0] != '0' || cpu_implementer_start[1] != 'x') {
512 cpuinfo_log_warning(
513 "CPU implementer %.*s in /proc/cpuinfo is ignored due to lack of 0x prefix",
514 (int)cpu_implementer_length,
515 cpu_implementer_start);
516 return;
517 }
518
519 /* Verify that characters after hex prefix are hexadecimal digits and
520 * decode them */
521 uint32_t cpu_implementer = 0;
522 for (const char* digit_ptr = cpu_implementer_start + 2; digit_ptr != cpu_implementer_end; digit_ptr++) {
523 const char digit_char = *digit_ptr;
524 uint32_t digit;
525 if (digit_char >= '0' && digit_char <= '9') {
526 digit = digit_char - '0';
527 } else if ((uint32_t)(digit_char - 'A') < 6) {
528 digit = 10 + (digit_char - 'A');
529 } else if ((uint32_t)(digit_char - 'a') < 6) {
530 digit = 10 + (digit_char - 'a');
531 } else {
532 cpuinfo_log_warning(
533 "CPU implementer %.*s in /proc/cpuinfo is ignored due to unexpected non-hex character '%c' at offset %zu",
534 (int)cpu_implementer_length,
535 cpu_implementer_start,
536 digit_char,
537 (size_t)(digit_ptr - cpu_implementer_start));
538 return;
539 }
540 cpu_implementer = cpu_implementer * 16 + digit;
541 }
542
543 processor->midr = midr_set_implementer(processor->midr, cpu_implementer);
544 processor->flags |= CPUINFO_ARM_LINUX_VALID_IMPLEMENTER | CPUINFO_ARM_LINUX_VALID_PROCESSOR;
545 }
546
parse_cpu_variant(const char * cpu_variant_start,const char * cpu_variant_end,struct cpuinfo_arm_linux_processor processor[restrict static1])547 static void parse_cpu_variant(
548 const char* cpu_variant_start,
549 const char* cpu_variant_end,
550 struct cpuinfo_arm_linux_processor processor[restrict static 1]) {
551 const size_t cpu_variant_length = cpu_variant_end - cpu_variant_start;
552
553 /*
554 * Value should contain hex prefix (0x) and one hex digit.
555 * Value can not contain more than one hex digits since
556 * Main ID Register (MIDR) assigns only a 4-bit value for CPU variant.
557 */
558 if (cpu_variant_length != 3) {
559 cpuinfo_log_warning(
560 "CPU variant %.*s in /proc/cpuinfo is ignored due to unexpected length (%zu)",
561 (int)cpu_variant_length,
562 cpu_variant_start,
563 cpu_variant_length);
564 return;
565 }
566
567 /* Skip if there is no hex prefix (0x) */
568 if (cpu_variant_start[0] != '0' || cpu_variant_start[1] != 'x') {
569 cpuinfo_log_warning(
570 "CPU variant %.*s in /proc/cpuinfo is ignored due to lack of 0x prefix",
571 (int)cpu_variant_length,
572 cpu_variant_start);
573 return;
574 }
575
576 /* Check if the value after hex prefix is indeed a hex digit and decode
577 * it. */
578 const char digit_char = cpu_variant_start[2];
579 uint32_t cpu_variant;
580 if ((uint32_t)(digit_char - '0') < 10) {
581 cpu_variant = (uint32_t)(digit_char - '0');
582 } else if ((uint32_t)(digit_char - 'A') < 6) {
583 cpu_variant = 10 + (uint32_t)(digit_char - 'A');
584 } else if ((uint32_t)(digit_char - 'a') < 6) {
585 cpu_variant = 10 + (uint32_t)(digit_char - 'a');
586 } else {
587 cpuinfo_log_warning(
588 "CPU variant %.*s in /proc/cpuinfo is ignored due to unexpected non-hex character '%c'",
589 (int)cpu_variant_length,
590 cpu_variant_start,
591 digit_char);
592 return;
593 }
594
595 processor->midr = midr_set_variant(processor->midr, cpu_variant);
596 processor->flags |= CPUINFO_ARM_LINUX_VALID_VARIANT | CPUINFO_ARM_LINUX_VALID_PROCESSOR;
597 }
598
parse_cpu_revision(const char * cpu_revision_start,const char * cpu_revision_end,struct cpuinfo_arm_linux_processor processor[restrict static1])599 static void parse_cpu_revision(
600 const char* cpu_revision_start,
601 const char* cpu_revision_end,
602 struct cpuinfo_arm_linux_processor processor[restrict static 1]) {
603 uint32_t cpu_revision = 0;
604 for (const char* digit_ptr = cpu_revision_start; digit_ptr != cpu_revision_end; digit_ptr++) {
605 const uint32_t digit = (uint32_t)(*digit_ptr - '0');
606
607 /* Verify that the character in CPU revision is a decimal digit
608 */
609 if (digit >= 10) {
610 cpuinfo_log_warning(
611 "CPU revision %.*s in /proc/cpuinfo is ignored due to unexpected non-digit character '%c' at offset %zu",
612 (int)(cpu_revision_end - cpu_revision_start),
613 cpu_revision_start,
614 *digit_ptr,
615 (size_t)(digit_ptr - cpu_revision_start));
616 return;
617 }
618
619 cpu_revision = cpu_revision * 10 + digit;
620 }
621
622 processor->midr = midr_set_revision(processor->midr, cpu_revision);
623 processor->flags |= CPUINFO_ARM_LINUX_VALID_REVISION | CPUINFO_ARM_LINUX_VALID_PROCESSOR;
624 }
625
626 #if CPUINFO_ARCH_ARM
627 /*
628 * Decode one of the cache-related numbers reported by Linux kernel
629 * for pre-ARMv7 architecture.
630 * An example cache-related information in /proc/cpuinfo:
631 *
632 * I size : 32768
633 * I assoc : 4
634 * I line length : 32
635 * I sets : 256
636 * D size : 16384
637 * D assoc : 4
638 * D line length : 32
639 * D sets : 128
640 *
641 */
parse_cache_number(const char * number_start,const char * number_end,const char * number_name,uint32_t number_ptr[restrict static1],uint32_t flags[restrict static1],uint32_t number_mask)642 static void parse_cache_number(
643 const char* number_start,
644 const char* number_end,
645 const char* number_name,
646 uint32_t number_ptr[restrict static 1],
647 uint32_t flags[restrict static 1],
648 uint32_t number_mask) {
649 uint32_t number = 0;
650 for (const char* digit_ptr = number_start; digit_ptr != number_end; digit_ptr++) {
651 const uint32_t digit = *digit_ptr - '0';
652 if (digit >= 10) {
653 cpuinfo_log_warning(
654 "%s %.*s in /proc/cpuinfo is ignored due to unexpected non-digit character '%c' at offset %zu",
655 number_name,
656 (int)(number_end - number_start),
657 number_start,
658 *digit_ptr,
659 (size_t)(digit_ptr - number_start));
660 return;
661 }
662
663 number = number * 10 + digit;
664 }
665
666 if (number == 0) {
667 cpuinfo_log_warning(
668 "%s %.*s in /proc/cpuinfo is ignored due to invalid value of zero reported by the kernel",
669 number_name,
670 (int)(number_end - number_start),
671 number_start);
672 }
673
674 /* If the number specifies a cache line size, verify that is a
675 * reasonable power of 2 */
676 if (number_mask & CPUINFO_ARM_LINUX_VALID_CACHE_LINE) {
677 switch (number) {
678 case 16:
679 case 32:
680 case 64:
681 case 128:
682 break;
683 default:
684 cpuinfo_log_warning(
685 "invalid %s %.*s is ignored: a value of 16, 32, 64, or 128 expected",
686 number_name,
687 (int)(number_end - number_start),
688 number_start);
689 }
690 }
691
692 *number_ptr = number;
693 *flags |= number_mask | CPUINFO_ARM_LINUX_VALID_PROCESSOR;
694 }
695 #endif /* CPUINFO_ARCH_ARM */
696
697 struct proc_cpuinfo_parser_state {
698 char* hardware;
699 char* revision;
700 uint32_t processor_index;
701 uint32_t max_processors_count;
702 struct cpuinfo_arm_linux_processor* processors;
703 struct cpuinfo_arm_linux_processor dummy_processor;
704 };
705
706 /*
707 * Decode a single line of /proc/cpuinfo information.
708 * Lines have format <words-with-spaces>[ ]*:[ ]<space-separated words>
709 * An example of /proc/cpuinfo (from Pandaboard-ES):
710 *
711 * Processor : ARMv7 Processor rev 10 (v7l)
712 * processor : 0
713 * BogoMIPS : 1392.74
714 *
715 * processor : 1
716 * BogoMIPS : 1363.33
717 *
718 * Features : swp half thumb fastmult vfp edsp thumbee neon
719 *vfpv3 CPU implementer : 0x41 CPU architecture: 7 CPU variant : 0x2 CPU
720 *part : 0xc09 CPU revision : 10
721 *
722 * Hardware : OMAP4 Panda board
723 * Revision : 0020
724 * Serial : 0000000000000000
725 */
parse_line(const char * line_start,const char * line_end,struct proc_cpuinfo_parser_state state[restrict static1],uint64_t line_number)726 static bool parse_line(
727 const char* line_start,
728 const char* line_end,
729 struct proc_cpuinfo_parser_state state[restrict static 1],
730 uint64_t line_number) {
731 /* Empty line. Skip. */
732 if (line_start == line_end) {
733 return true;
734 }
735
736 /* Search for ':' on the line. */
737 const char* separator = line_start;
738 for (; separator != line_end; separator++) {
739 if (*separator == ':') {
740 break;
741 }
742 }
743 /* Skip line if no ':' separator was found. */
744 if (separator == line_end) {
745 cpuinfo_log_debug(
746 "Line %.*s in /proc/cpuinfo is ignored: key/value separator ':' not found",
747 (int)(line_end - line_start),
748 line_start);
749 return true;
750 }
751
752 /* Skip trailing spaces in key part. */
753 const char* key_end = separator;
754 for (; key_end != line_start; key_end--) {
755 if (key_end[-1] != ' ' && key_end[-1] != '\t') {
756 break;
757 }
758 }
759 /* Skip line if key contains nothing but spaces. */
760 if (key_end == line_start) {
761 cpuinfo_log_debug(
762 "Line %.*s in /proc/cpuinfo is ignored: key contains only spaces",
763 (int)(line_end - line_start),
764 line_start);
765 return true;
766 }
767
768 /* Skip leading spaces in value part. */
769 const char* value_start = separator + 1;
770 for (; value_start != line_end; value_start++) {
771 if (*value_start != ' ') {
772 break;
773 }
774 }
775 /* Value part contains nothing but spaces. Skip line. */
776 if (value_start == line_end) {
777 cpuinfo_log_debug(
778 "Line %.*s in /proc/cpuinfo is ignored: value contains only spaces",
779 (int)(line_end - line_start),
780 line_start);
781 return true;
782 }
783
784 /* Skip trailing spaces in value part (if any) */
785 const char* value_end = line_end;
786 for (; value_end != value_start; value_end--) {
787 if (value_end[-1] != ' ') {
788 break;
789 }
790 }
791
792 const uint32_t processor_index = state->processor_index;
793 const uint32_t max_processors_count = state->max_processors_count;
794 struct cpuinfo_arm_linux_processor* processors = state->processors;
795 struct cpuinfo_arm_linux_processor* processor = &state->dummy_processor;
796 if (processor_index < max_processors_count) {
797 processor = &processors[processor_index];
798 }
799
800 const size_t key_length = key_end - line_start;
801 switch (key_length) {
802 case 6:
803 if (memcmp(line_start, "Serial", key_length) == 0) {
804 /* Usually contains just zeros, useless */
805 #if CPUINFO_ARCH_ARM
806 } else if (memcmp(line_start, "I size", key_length) == 0) {
807 parse_cache_number(
808 value_start,
809 value_end,
810 "instruction cache size",
811 &processor->proc_cpuinfo_cache.i_size,
812 &processor->flags,
813 CPUINFO_ARM_LINUX_VALID_ICACHE_SIZE);
814 } else if (memcmp(line_start, "I sets", key_length) == 0) {
815 parse_cache_number(
816 value_start,
817 value_end,
818 "instruction cache sets",
819 &processor->proc_cpuinfo_cache.i_sets,
820 &processor->flags,
821 CPUINFO_ARM_LINUX_VALID_ICACHE_SETS);
822 } else if (memcmp(line_start, "D size", key_length) == 0) {
823 parse_cache_number(
824 value_start,
825 value_end,
826 "data cache size",
827 &processor->proc_cpuinfo_cache.d_size,
828 &processor->flags,
829 CPUINFO_ARM_LINUX_VALID_DCACHE_SIZE);
830 } else if (memcmp(line_start, "D sets", key_length) == 0) {
831 parse_cache_number(
832 value_start,
833 value_end,
834 "data cache sets",
835 &processor->proc_cpuinfo_cache.d_sets,
836 &processor->flags,
837 CPUINFO_ARM_LINUX_VALID_DCACHE_SETS);
838 #endif /* CPUINFO_ARCH_ARM */
839 } else {
840 goto unknown;
841 }
842 break;
843 #if CPUINFO_ARCH_ARM
844 case 7:
845 if (memcmp(line_start, "I assoc", key_length) == 0) {
846 parse_cache_number(
847 value_start,
848 value_end,
849 "instruction cache associativity",
850 &processor->proc_cpuinfo_cache.i_assoc,
851 &processor->flags,
852 CPUINFO_ARM_LINUX_VALID_ICACHE_WAYS);
853 } else if (memcmp(line_start, "D assoc", key_length) == 0) {
854 parse_cache_number(
855 value_start,
856 value_end,
857 "data cache associativity",
858 &processor->proc_cpuinfo_cache.d_assoc,
859 &processor->flags,
860 CPUINFO_ARM_LINUX_VALID_DCACHE_WAYS);
861 } else {
862 goto unknown;
863 }
864 break;
865 #endif /* CPUINFO_ARCH_ARM */
866 case 8:
867 if (memcmp(line_start, "CPU part", key_length) == 0) {
868 parse_cpu_part(value_start, value_end, processor);
869 } else if (memcmp(line_start, "Features", key_length) == 0) {
870 parse_features(value_start, value_end, processor);
871 } else if (memcmp(line_start, "BogoMIPS", key_length) == 0) {
872 /* BogoMIPS is useless, don't parse */
873 } else if (memcmp(line_start, "Hardware", key_length) == 0) {
874 size_t value_length = value_end - value_start;
875 if (value_length > CPUINFO_HARDWARE_VALUE_MAX) {
876 cpuinfo_log_warning(
877 "length of Hardware value \"%.*s\" in /proc/cpuinfo exceeds limit (%d): truncating to the limit",
878 (int)value_length,
879 value_start,
880 CPUINFO_HARDWARE_VALUE_MAX);
881 value_length = CPUINFO_HARDWARE_VALUE_MAX;
882 } else {
883 state->hardware[value_length] = '\0';
884 }
885 memcpy(state->hardware, value_start, value_length);
886 cpuinfo_log_debug(
887 "parsed /proc/cpuinfo Hardware = \"%.*s\"", (int)value_length, value_start);
888 } else if (memcmp(line_start, "Revision", key_length) == 0) {
889 size_t value_length = value_end - value_start;
890 if (value_length > CPUINFO_REVISION_VALUE_MAX) {
891 cpuinfo_log_warning(
892 "length of Revision value \"%.*s\" in /proc/cpuinfo exceeds limit (%d): truncating to the limit",
893 (int)value_length,
894 value_start,
895 CPUINFO_REVISION_VALUE_MAX);
896 value_length = CPUINFO_REVISION_VALUE_MAX;
897 } else {
898 state->revision[value_length] = '\0';
899 }
900 memcpy(state->revision, value_start, value_length);
901 cpuinfo_log_debug(
902 "parsed /proc/cpuinfo Revision = \"%.*s\"", (int)value_length, value_start);
903 } else {
904 goto unknown;
905 }
906 break;
907 case 9:
908 if (memcmp(line_start, "processor", key_length) == 0) {
909 const uint32_t new_processor_index = parse_processor_number(value_start, value_end);
910 if (new_processor_index < processor_index) {
911 /* Strange: decreasing processor number
912 */
913 cpuinfo_log_warning(
914 "unexpectedly low processor number %" PRIu32
915 " following processor %" PRIu32 " in /proc/cpuinfo",
916 new_processor_index,
917 processor_index);
918 } else if (new_processor_index > processor_index + 1) {
919 /* Strange, but common: skipped
920 * processor $(processor_index + 1) */
921 cpuinfo_log_warning(
922 "unexpectedly high processor number %" PRIu32
923 " following processor %" PRIu32 " in /proc/cpuinfo",
924 new_processor_index,
925 processor_index);
926 }
927 if (new_processor_index < max_processors_count) {
928 /* Record that the processor was
929 * mentioned in /proc/cpuinfo */
930 processors[new_processor_index].flags |= CPUINFO_ARM_LINUX_VALID_PROCESSOR;
931 } else {
932 /* Log and ignore processor */
933 cpuinfo_log_warning(
934 "processor %" PRIu32
935 " in /proc/cpuinfo is ignored: index exceeds system limit %" PRIu32,
936 new_processor_index,
937 max_processors_count - 1);
938 }
939 state->processor_index = new_processor_index;
940 return true;
941 } else if (memcmp(line_start, "Processor", key_length) == 0) {
942 /* TODO: parse to fix misreported architecture,
943 * similar to Android's cpufeatures */
944 } else {
945 goto unknown;
946 }
947 break;
948 case 11:
949 if (memcmp(line_start, "CPU variant", key_length) == 0) {
950 parse_cpu_variant(value_start, value_end, processor);
951 } else {
952 goto unknown;
953 }
954 break;
955 case 12:
956 if (memcmp(line_start, "CPU revision", key_length) == 0) {
957 parse_cpu_revision(value_start, value_end, processor);
958 } else {
959 goto unknown;
960 }
961 break;
962 #if CPUINFO_ARCH_ARM
963 case 13:
964 if (memcmp(line_start, "I line length", key_length) == 0) {
965 parse_cache_number(
966 value_start,
967 value_end,
968 "instruction cache line size",
969 &processor->proc_cpuinfo_cache.i_line_length,
970 &processor->flags,
971 CPUINFO_ARM_LINUX_VALID_ICACHE_LINE);
972 } else if (memcmp(line_start, "D line length", key_length) == 0) {
973 parse_cache_number(
974 value_start,
975 value_end,
976 "data cache line size",
977 &processor->proc_cpuinfo_cache.d_line_length,
978 &processor->flags,
979 CPUINFO_ARM_LINUX_VALID_DCACHE_LINE);
980 } else {
981 goto unknown;
982 }
983 break;
984 #endif /* CPUINFO_ARCH_ARM */
985 case 15:
986 if (memcmp(line_start, "CPU implementer", key_length) == 0) {
987 parse_cpu_implementer(value_start, value_end, processor);
988 } else if (memcmp(line_start, "CPU implementor", key_length) == 0) {
989 parse_cpu_implementer(value_start, value_end, processor);
990 } else {
991 goto unknown;
992 }
993 break;
994 case 16:
995 if (memcmp(line_start, "CPU architecture", key_length) == 0) {
996 parse_cpu_architecture(value_start, value_end, processor);
997 } else {
998 goto unknown;
999 }
1000 break;
1001 default:
1002 unknown:
1003 cpuinfo_log_debug("unknown /proc/cpuinfo key: %.*s", (int)key_length, line_start);
1004 }
1005 return true;
1006 }
1007
cpuinfo_arm_linux_parse_proc_cpuinfo(char hardware[restrict static CPUINFO_HARDWARE_VALUE_MAX],char revision[restrict static CPUINFO_REVISION_VALUE_MAX],uint32_t max_processors_count,struct cpuinfo_arm_linux_processor processors[restrict static max_processors_count])1008 bool cpuinfo_arm_linux_parse_proc_cpuinfo(
1009 char hardware[restrict static CPUINFO_HARDWARE_VALUE_MAX],
1010 char revision[restrict static CPUINFO_REVISION_VALUE_MAX],
1011 uint32_t max_processors_count,
1012 struct cpuinfo_arm_linux_processor processors[restrict static max_processors_count]) {
1013 hardware[0] = '\0';
1014 struct proc_cpuinfo_parser_state state = {
1015 .hardware = hardware,
1016 .revision = revision,
1017 .processor_index = 0,
1018 .max_processors_count = max_processors_count,
1019 .processors = processors,
1020 };
1021 return cpuinfo_linux_parse_multiline_file(
1022 "/proc/cpuinfo", BUFFER_SIZE, (cpuinfo_line_callback)parse_line, &state);
1023 }
1024