• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 	char* revision;
635 	uint32_t processor_index;
636 	uint32_t max_processors_count;
637 	struct cpuinfo_arm_linux_processor* processors;
638 	struct cpuinfo_arm_linux_processor dummy_processor;
639 };
640 
641 /*
642  *	Decode a single line of /proc/cpuinfo information.
643  *	Lines have format <words-with-spaces>[ ]*:[ ]<space-separated words>
644  *	An example of /proc/cpuinfo (from Pandaboard-ES):
645  *
646  *		Processor       : ARMv7 Processor rev 10 (v7l)
647  *		processor       : 0
648  *		BogoMIPS        : 1392.74
649  *
650  *		processor       : 1
651  *		BogoMIPS        : 1363.33
652  *
653  *		Features        : swp half thumb fastmult vfp edsp thumbee neon vfpv3
654  *		CPU implementer : 0x41
655  *		CPU architecture: 7
656  *		CPU variant     : 0x2
657  *		CPU part        : 0xc09
658  *		CPU revision    : 10
659  *
660  *		Hardware        : OMAP4 Panda board
661  *		Revision        : 0020
662  *		Serial          : 0000000000000000
663  */
parse_line(const char * line_start,const char * line_end,struct proc_cpuinfo_parser_state state[restrict static1],uint64_t line_number)664 static bool parse_line(
665 	const char* line_start,
666 	const char* line_end,
667 	struct proc_cpuinfo_parser_state state[restrict static 1],
668 	uint64_t line_number)
669 {
670 	/* Empty line. Skip. */
671 	if (line_start == line_end) {
672 		return true;
673 	}
674 
675 	/* Search for ':' on the line. */
676 	const char* separator = line_start;
677 	for (; separator != line_end; separator++) {
678 		if (*separator == ':') {
679 			break;
680 		}
681 	}
682 	/* Skip line if no ':' separator was found. */
683 	if (separator == line_end) {
684 		cpuinfo_log_info("Line %.*s in /proc/cpuinfo is ignored: key/value separator ':' not found",
685 			(int) (line_end - line_start), line_start);
686 		return true;
687 	}
688 
689 	/* Skip trailing spaces in key part. */
690 	const char* key_end = separator;
691 	for (; key_end != line_start; key_end--) {
692 		if (key_end[-1] != ' ' && key_end[-1] != '\t') {
693 			break;
694 		}
695 	}
696 	/* Skip line if key contains nothing but spaces. */
697 	if (key_end == line_start) {
698 		cpuinfo_log_info("Line %.*s in /proc/cpuinfo is ignored: key contains only spaces",
699 			(int) (line_end - line_start), line_start);
700 		return true;
701 	}
702 
703 	/* Skip leading spaces in value part. */
704 	const char* value_start = separator + 1;
705 	for (; value_start != line_end; value_start++) {
706 		if (*value_start != ' ') {
707 			break;
708 		}
709 	}
710 	/* Value part contains nothing but spaces. Skip line. */
711 	if (value_start == line_end) {
712 		cpuinfo_log_info("Line %.*s in /proc/cpuinfo is ignored: value contains only spaces",
713 			(int) (line_end - line_start), line_start);
714 		return true;
715 	}
716 
717 	/* Skip trailing spaces in value part (if any) */
718 	const char* value_end = line_end;
719 	for (; value_end != value_start; value_end--) {
720 		if (value_end[-1] != ' ') {
721 			break;
722 		}
723 	}
724 
725 	const uint32_t processor_index      = state->processor_index;
726 	const uint32_t max_processors_count = state->max_processors_count;
727 	struct cpuinfo_arm_linux_processor* processors = state->processors;
728 	struct cpuinfo_arm_linux_processor* processor  = &state->dummy_processor;
729 	if (processor_index < max_processors_count) {
730 		processor = &processors[processor_index];
731 	}
732 
733 	const size_t key_length = key_end - line_start;
734 	switch (key_length) {
735 		case 6:
736 			if (memcmp(line_start, "Serial", key_length) == 0) {
737 				/* Usually contains just zeros, useless */
738 #if CPUINFO_ARCH_ARM
739 			} else if (memcmp(line_start, "I size", key_length) == 0) {
740 				parse_cache_number(value_start, value_end,
741 					"instruction cache size", &processor->proc_cpuinfo_cache.i_size,
742 					&processor->flags, CPUINFO_ARM_LINUX_VALID_ICACHE_SIZE);
743 			} else if (memcmp(line_start, "I sets", key_length) == 0) {
744 				parse_cache_number(value_start, value_end,
745 					"instruction cache sets", &processor->proc_cpuinfo_cache.i_sets,
746 					&processor->flags, CPUINFO_ARM_LINUX_VALID_ICACHE_SETS);
747 			} else if (memcmp(line_start, "D size", key_length) == 0) {
748 				parse_cache_number(value_start, value_end,
749 					"data cache size", &processor->proc_cpuinfo_cache.d_size,
750 					&processor->flags, CPUINFO_ARM_LINUX_VALID_DCACHE_SIZE);
751 			} else if (memcmp(line_start, "D sets", key_length) == 0) {
752 				parse_cache_number(value_start, value_end,
753 					"data cache sets", &processor->proc_cpuinfo_cache.d_sets,
754 					&processor->flags, CPUINFO_ARM_LINUX_VALID_DCACHE_SETS);
755 #endif /* CPUINFO_ARCH_ARM */
756 			} else {
757 				goto unknown;
758 			}
759 			break;
760 #if CPUINFO_ARCH_ARM
761 		case 7:
762 			if (memcmp(line_start, "I assoc", key_length) == 0) {
763 				parse_cache_number(value_start, value_end,
764 					"instruction cache associativity", &processor->proc_cpuinfo_cache.i_assoc,
765 					&processor->flags, CPUINFO_ARM_LINUX_VALID_ICACHE_WAYS);
766 			} else if (memcmp(line_start, "D assoc", key_length) == 0) {
767 				parse_cache_number(value_start, value_end,
768 					"data cache associativity", &processor->proc_cpuinfo_cache.d_assoc,
769 					&processor->flags, CPUINFO_ARM_LINUX_VALID_DCACHE_WAYS);
770 			} else {
771 				goto unknown;
772 			}
773 			break;
774 #endif /* CPUINFO_ARCH_ARM */
775 		case 8:
776 			if (memcmp(line_start, "CPU part", key_length) == 0) {
777 				parse_cpu_part(value_start, value_end, processor);
778 			} else if (memcmp(line_start, "Features", key_length) == 0) {
779 				parse_features(value_start, value_end, processor);
780 			} else if (memcmp(line_start, "BogoMIPS", key_length) == 0) {
781 				/* BogoMIPS is useless, don't parse */
782 			} else if (memcmp(line_start, "Hardware", key_length) == 0) {
783 				size_t value_length = value_end - value_start;
784 				if (value_length > CPUINFO_HARDWARE_VALUE_MAX) {
785 					cpuinfo_log_info(
786 						"length of Hardware value \"%.*s\" in /proc/cpuinfo exceeds limit (%d): truncating to the limit",
787 						(int) value_length, value_start, CPUINFO_HARDWARE_VALUE_MAX);
788 					value_length = CPUINFO_HARDWARE_VALUE_MAX;
789 				} else {
790 					state->hardware[value_length] = '\0';
791 				}
792 				memcpy(state->hardware, value_start, value_length);
793 				cpuinfo_log_debug("parsed /proc/cpuinfo Hardware = \"%.*s\"", (int) value_length, value_start);
794 			} else if (memcmp(line_start, "Revision", key_length) == 0) {
795 				size_t value_length = value_end - value_start;
796 				if (value_length > CPUINFO_REVISION_VALUE_MAX) {
797 					cpuinfo_log_info(
798 						"length of Revision value \"%.*s\" in /proc/cpuinfo exceeds limit (%d): truncating to the limit",
799 						(int) value_length, value_start, CPUINFO_REVISION_VALUE_MAX);
800 					value_length = CPUINFO_REVISION_VALUE_MAX;
801 				} else {
802 					state->revision[value_length] = '\0';
803 				}
804 				memcpy(state->revision, value_start, value_length);
805 				cpuinfo_log_debug("parsed /proc/cpuinfo Revision = \"%.*s\"", (int) value_length, value_start);
806 			} else {
807 				goto unknown;
808 			}
809 			break;
810 		case 9:
811 			if (memcmp(line_start, "processor", key_length) == 0) {
812 				const uint32_t new_processor_index = parse_processor_number(value_start, value_end);
813 				if (new_processor_index < processor_index) {
814 					/* Strange: decreasing processor number */
815 					cpuinfo_log_warning(
816 						"unexpectedly low processor number %"PRIu32" following processor %"PRIu32" in /proc/cpuinfo",
817 						new_processor_index, processor_index);
818 				} else if (new_processor_index > processor_index + 1) {
819 					/* Strange, but common: skipped processor $(processor_index + 1) */
820 					cpuinfo_log_info(
821 						"unexpectedly high processor number %"PRIu32" following processor %"PRIu32" in /proc/cpuinfo",
822 						new_processor_index, processor_index);
823 				}
824 				if (new_processor_index < max_processors_count) {
825 					/* Record that the processor was mentioned in /proc/cpuinfo */
826 					processors[new_processor_index].flags |= CPUINFO_ARM_LINUX_VALID_PROCESSOR;
827 				} else {
828 					/* Log and ignore processor */
829 					cpuinfo_log_warning("processor %"PRIu32" in /proc/cpuinfo is ignored: index exceeds system limit %"PRIu32,
830 						new_processor_index, max_processors_count - 1);
831 				}
832 				state->processor_index = new_processor_index;
833 				return true;
834 			} else if (memcmp(line_start, "Processor", key_length) == 0) {
835 				/* TODO: parse to fix misreported architecture, similar to Android's cpufeatures */
836 			} else {
837 				goto unknown;
838 			}
839 			break;
840 		case 11:
841 			if (memcmp(line_start, "CPU variant", key_length) == 0) {
842 				parse_cpu_variant(value_start, value_end, processor);
843 			} else {
844 				goto unknown;
845 			}
846 			break;
847 		case 12:
848 			if (memcmp(line_start, "CPU revision", key_length) == 0) {
849 				parse_cpu_revision(value_start, value_end, processor);
850 			} else {
851 				goto unknown;
852 			}
853 			break;
854 #if CPUINFO_ARCH_ARM
855 		case 13:
856 			if (memcmp(line_start, "I line length", key_length) == 0) {
857 				parse_cache_number(value_start, value_end,
858 					"instruction cache line size", &processor->proc_cpuinfo_cache.i_line_length,
859 					&processor->flags, CPUINFO_ARM_LINUX_VALID_ICACHE_LINE);
860 			} else if (memcmp(line_start, "D line length", key_length) == 0) {
861 				parse_cache_number(value_start, value_end,
862 					"data cache line size", &processor->proc_cpuinfo_cache.d_line_length,
863 					&processor->flags, CPUINFO_ARM_LINUX_VALID_DCACHE_LINE);
864 			} else {
865 				goto unknown;
866 			}
867 			break;
868 #endif /* CPUINFO_ARCH_ARM */
869 		case 15:
870 			if (memcmp(line_start, "CPU implementer", key_length) == 0) {
871 				parse_cpu_implementer(value_start, value_end, processor);
872 			} else if (memcmp(line_start, "CPU implementor", key_length) == 0) {
873 				parse_cpu_implementer(value_start, value_end, processor);
874 			} else {
875 				goto unknown;
876 			}
877 			break;
878 		case 16:
879 			if (memcmp(line_start, "CPU architecture", key_length) == 0) {
880 				parse_cpu_architecture(value_start, value_end, processor);
881 			} else {
882 				goto unknown;
883 			}
884 			break;
885 		default:
886 		unknown:
887 			cpuinfo_log_debug("unknown /proc/cpuinfo key: %.*s", (int) key_length, line_start);
888 
889 	}
890 	return true;
891 }
892 
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])893 bool cpuinfo_arm_linux_parse_proc_cpuinfo(
894 	char hardware[restrict static CPUINFO_HARDWARE_VALUE_MAX],
895 	char revision[restrict static CPUINFO_REVISION_VALUE_MAX],
896 	uint32_t max_processors_count,
897 	struct cpuinfo_arm_linux_processor processors[restrict static max_processors_count])
898 {
899 	struct proc_cpuinfo_parser_state state = {
900 		.hardware = hardware,
901 		.revision = revision,
902 		.processor_index = 0,
903 		.max_processors_count = max_processors_count,
904 		.processors = processors,
905 	};
906 	return cpuinfo_linux_parse_multiline_file("/proc/cpuinfo", BUFFER_SIZE,
907 		(cpuinfo_line_callback) parse_line, &state);
908 }
909