• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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