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