• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 #include <stdbool.h>
2 #include <stddef.h>
3 #include <stdint.h>
4 #include <stdio.h>
5 #include <string.h>
6 
7 #include <cpuinfo.h>
8 #include <cpuinfo/common.h>
9 #include <x86/api.h>
10 
11 /* The state of the parser to be preserved between parsing different tokens. */
12 struct parser_state {
13 	/*
14 	 * Pointer to the start of the previous token if it is "model".
15 	 * NULL if previous token is not "model".
16 	 */
17 	char* context_model;
18 	/*
19 	 * Pointer to the start of the previous token if it is a
20 	 * single-uppercase-letter token. NULL if previous token is anything
21 	 * different.
22 	 */
23 	char* context_upper_letter;
24 	/*
25 	 * Pointer to the start of the previous token if it is "Dual".
26 	 * NULL if previous token is not "Dual".
27 	 */
28 	char* context_dual;
29 	/*
30 	 * Pointer to the start of the previous token if it is "Core",
31 	 * "Dual-Core", "QuadCore", etc. NULL if previous token is anything
32 	 * different.
33 	 */
34 	char* context_core;
35 	/*
36 	 * Pointer to the start of the previous token if it is "Eng" or
37 	 * "Engineering", etc. NULL if previous token is anything different.
38 	 */
39 	char* context_engineering;
40 	/*
41 	 * Pointer to the '@' symbol in the brand string (separates frequency
42 	 * specification). NULL if there is no '@' symbol.
43 	 */
44 	char* frequency_separator;
45 	/* Indicates whether the brand string (after transformations) contains
46 	 * frequency. */
47 	bool frequency_token;
48 	/* Indicates whether the processor is of Xeon family (contains "Xeon"
49 	 * substring). */
50 	bool xeon;
51 	/* Indicates whether the processor model number was already parsed. */
52 	bool parsed_model_number;
53 	/* Indicates whether the processor is an engineering sample (contains
54 	 * "Engineering Sample" or "Eng Sample" substrings). */
55 	bool engineering_sample;
56 };
57 
58 /** @brief	Resets information about the previous token. Keeps all other
59  * state information. */
reset_context(struct parser_state * state)60 static void reset_context(struct parser_state* state) {
61 	state->context_model = NULL;
62 	state->context_upper_letter = NULL;
63 	state->context_dual = NULL;
64 	state->context_core = NULL;
65 }
66 
67 /**
68  * @brief	Overwrites the supplied string with space characters if it
69  * exactly matches the given string.
70  * @param	string	The string to be compared against other string, and
71  * erased in case of matching.
72  * @param	length	The length of the two string to be compared against each
73  * other.
74  * @param	target	The string to compare against.
75  * @retval	true	If the two strings match and the first supplied string
76  * was erased (overwritten with space characters).
77  * @retval	false	If the two strings are different and the first supplied
78  * string remained unchanged.
79  */
erase_matching(char * string,size_t length,const char * target)80 static inline bool erase_matching(char* string, size_t length, const char* target) {
81 	const bool match = memcmp(string, target, length) == 0;
82 	if (match) {
83 		memset(string, ' ', length);
84 	}
85 	return match;
86 }
87 
88 /**
89  * @brief	Checks if the supplied ASCII character is an uppercase latin
90  * letter.
91  * @param	character	The character to analyse.
92  * @retval	true	If the supplied character is an uppercase latin letter
93  * ('A' to 'Z').
94  * @retval	false	If the supplied character is anything different.
95  */
is_upper_letter(char character)96 static inline bool is_upper_letter(char character) {
97 	return (uint32_t)(character - 'A') <= (uint32_t)('Z' - 'A');
98 }
99 
100 /**
101  * @brief	Checks if the supplied ASCII character is a digit.
102  * @param	character	The character to analyse.
103  * @retval	true	If the supplied character is a digit ('0' to '9').
104  * @retval	false	If the supplied character is anything different.
105  */
is_digit(char character)106 static inline bool is_digit(char character) {
107 	return (uint32_t)(character - '0') < UINT32_C(10);
108 }
109 
is_zero_number(const char * token_start,const char * token_end)110 static inline bool is_zero_number(const char* token_start, const char* token_end) {
111 	for (const char* char_ptr = token_start; char_ptr != token_end; char_ptr++) {
112 		if (*char_ptr != '0') {
113 			return false;
114 		}
115 	}
116 	return true;
117 }
118 
is_space(const char * token_start,const char * token_end)119 static inline bool is_space(const char* token_start, const char* token_end) {
120 	for (const char* char_ptr = token_start; char_ptr != token_end; char_ptr++) {
121 		if (*char_ptr != ' ') {
122 			return false;
123 		}
124 	}
125 	return true;
126 }
127 
is_number(const char * token_start,const char * token_end)128 static inline bool is_number(const char* token_start, const char* token_end) {
129 	for (const char* char_ptr = token_start; char_ptr != token_end; char_ptr++) {
130 		if (!is_digit(*char_ptr)) {
131 			return false;
132 		}
133 	}
134 	return true;
135 }
136 
is_model_number(const char * token_start,const char * token_end)137 static inline bool is_model_number(const char* token_start, const char* token_end) {
138 	for (const char* char_ptr = token_start + 1; char_ptr < token_end; char_ptr++) {
139 		if (is_digit(char_ptr[-1]) && is_digit(char_ptr[0])) {
140 			return true;
141 		}
142 	}
143 	return false;
144 }
145 
is_frequency(const char * token_start,const char * token_end)146 static inline bool is_frequency(const char* token_start, const char* token_end) {
147 	const size_t token_length = (size_t)(token_end - token_start);
148 	if (token_length > 3 && token_end[-2] == 'H' && token_end[-1] == 'z') {
149 		switch (token_end[-3]) {
150 			case 'K':
151 			case 'M':
152 			case 'G':
153 				return true;
154 		}
155 	}
156 	return false;
157 }
158 
159 /**
160  * @warning	Input and output tokens can overlap
161  */
move_token(const char * token_start,const char * token_end,char * output_ptr)162 static inline char* move_token(const char* token_start, const char* token_end, char* output_ptr) {
163 	const size_t token_length = (size_t)(token_end - token_start);
164 	memmove(output_ptr, token_start, token_length);
165 	return output_ptr + token_length;
166 }
167 
transform_token(char * token_start,char * token_end,struct parser_state * state)168 static bool transform_token(char* token_start, char* token_end, struct parser_state* state) {
169 	const struct parser_state previousState = *state;
170 	reset_context(state);
171 
172 	size_t token_length = (size_t)(token_end - token_start);
173 
174 	if (state->frequency_separator != NULL) {
175 		if (token_start > state->frequency_separator) {
176 			if (state->parsed_model_number) {
177 				memset(token_start, ' ', token_length);
178 			}
179 		}
180 	}
181 
182 	/* Early AMD and Cyrix processors have "tm" suffix for trademark, e.g.
183 	 *   "AMD-K6tm w/ multimedia extensions"
184 	 *   "Cyrix MediaGXtm MMXtm Enhanced"
185 	 */
186 	if (token_length > 2) {
187 		const char context_char = token_end[-3];
188 		if (is_digit(context_char) || is_upper_letter(context_char)) {
189 			if (erase_matching(token_end - 2, 2, "tm")) {
190 				token_end -= 2;
191 				token_length -= 2;
192 			}
193 		}
194 	}
195 	if (token_length > 4) {
196 		/* Some early AMD CPUs have "AMD-" at the beginning, e.g.
197 		 *   "AMD-K5(tm) Processor"
198 		 *   "AMD-K6tm w/ multimedia extensions"
199 		 *   "AMD-K6(tm) 3D+ Processor"
200 		 *   "AMD-K6(tm)-III Processor"
201 		 */
202 		if (erase_matching(token_start, 4, "AMD-")) {
203 			token_start += 4;
204 			token_length -= 4;
205 		}
206 	}
207 	switch (token_length) {
208 		case 1:
209 			/*
210 			 * On some Intel processors there is a space between the
211 			 * first letter of the name and the number after it,
212 			 * e.g. "Intel(R) Core(TM) i7 CPU X 990  @ 3.47GHz"
213 			 *   "Intel(R) Core(TM) CPU Q 820  @ 1.73GHz"
214 			 * We want to merge these parts together, in reverse
215 			 * order, i.e. "X 990"
216 			 * -> "990X", "820" -> "820Q"
217 			 */
218 			if (is_upper_letter(token_start[0])) {
219 				state->context_upper_letter = token_start;
220 				return true;
221 			}
222 			break;
223 		case 2:
224 			/* Erase everything after "w/" in "AMD-K6tm w/
225 			 * multimedia extensions" */
226 			if (erase_matching(token_start, token_length, "w/")) {
227 				return false;
228 			}
229 			/*
230 			 * Intel Xeon processors since Ivy Bridge use versions,
231 			 * e.g. "Intel Xeon E3-1230 v2" Some processor branch
232 			 * strings report them as "V<N>", others report as
233 			 * "v<N>". Normalize the former (upper-case) to the
234 			 * latter (lower-case) version
235 			 */
236 			if (token_start[0] == 'V' && is_digit(token_start[1])) {
237 				token_start[0] = 'v';
238 				return true;
239 			}
240 			break;
241 		case 3:
242 			/*
243 			 * Erase "CPU" in brand string on Intel processors, e.g.
244 			 *  "Intel(R) Core(TM) i5 CPU         650  @ 3.20GHz"
245 			 *  "Intel(R) Xeon(R) CPU           X3210  @ 2.13GHz"
246 			 *  "Intel(R) Atom(TM) CPU Z2760  @ 1.80GHz"
247 			 */
248 			if (erase_matching(token_start, token_length, "CPU")) {
249 				return true;
250 			}
251 			/*
252 			 * Erase everything after "SOC" on AMD System-on-Chips,
253 			 * e.g. "AMD GX-212JC SOC with Radeon(TM) R2E Graphics
254 			 * \0"
255 			 */
256 			if (erase_matching(token_start, token_length, "SOC")) {
257 				return false;
258 			}
259 			/*
260 			 * Erase "AMD" in brand string on AMD processors, e.g.
261 			 *  "AMD Athlon(tm) Processor"
262 			 *  "AMD Engineering Sample"
263 			 *  "Quad-Core AMD Opteron(tm) Processor 2344 HE"
264 			 */
265 			if (erase_matching(token_start, token_length, "AMD")) {
266 				return true;
267 			}
268 			/*
269 			 * Erase "VIA" in brand string on VIA processors, e.g.
270 			 *   "VIA C3 Ezra"
271 			 *   "VIA C7-M Processor 1200MHz"
272 			 *   "VIA Nano L3050@1800MHz"
273 			 */
274 			if (erase_matching(token_start, token_length, "VIA")) {
275 				return true;
276 			}
277 			/* Erase "IDT" in brand string on early Centaur
278 			 * processors, e.g. "IDT WinChip 2-3D" */
279 			if (erase_matching(token_start, token_length, "IDT")) {
280 				return true;
281 			}
282 			/*
283 			 * Erase everything starting with "MMX" in
284 			 * "Cyrix MediaGXtm MMXtm Enhanced" ("tm" suffix is
285 			 * removed by this point)
286 			 */
287 			if (erase_matching(token_start, token_length, "MMX")) {
288 				return false;
289 			}
290 			/*
291 			 * Erase everything starting with "APU" on AMD
292 			 * processors, e.g. "AMD A10-4600M APU with Radeon(tm)
293 			 * HD Graphics" "AMD A10-7850K APU with Radeon(TM) R7
294 			 * Graphics" "AMD A6-6310 APU with AMD Radeon R4
295 			 * Graphics"
296 			 */
297 			if (erase_matching(token_start, token_length, "APU")) {
298 				return false;
299 			}
300 			/*
301 			 * Remember to discard string if it contains "Eng
302 			 * Sample", e.g. "Eng Sample,
303 			 * ZD302046W4K43_36/30/20_2/8_A"
304 			 */
305 			if (memcmp(token_start, "Eng", token_length) == 0) {
306 				state->context_engineering = token_start;
307 			}
308 			break;
309 		case 4:
310 			/* Remember to erase "Dual Core" in "AMD Athlon(tm) 64
311 			 * X2 Dual Core Processor 3800+" */
312 			if (memcmp(token_start, "Dual", token_length) == 0) {
313 				state->context_dual = token_start;
314 			}
315 			/* Remember if the processor is on Xeon family */
316 			if (memcmp(token_start, "Xeon", token_length) == 0) {
317 				state->xeon = true;
318 			}
319 			/* Erase "Dual Core" in "AMD Athlon(tm) 64 X2 Dual Core
320 			 * Processor 3800+"
321 			 */
322 			if (previousState.context_dual != NULL) {
323 				if (memcmp(token_start, "Core", token_length) == 0) {
324 					memset(previousState.context_dual,
325 					       ' ',
326 					       (size_t)(token_end - previousState.context_dual));
327 					state->context_core = token_end;
328 					return true;
329 				}
330 			}
331 			break;
332 		case 5:
333 			/*
334 			 * Erase "Intel" in brand string on Intel processors,
335 			 * e.g. "Intel(R) Xeon(R) CPU X3210 @ 2.13GHz" "Intel(R)
336 			 * Atom(TM) CPU D2700 @ 2.13GHz" "Genuine Intel(R)
337 			 * processor 800MHz"
338 			 */
339 			if (erase_matching(token_start, token_length, "Intel")) {
340 				return true;
341 			}
342 			/*
343 			 * Erase "Cyrix" in brand string on Cyrix processors,
344 			 * e.g. "Cyrix MediaGXtm MMXtm Enhanced"
345 			 */
346 			if (erase_matching(token_start, token_length, "Cyrix")) {
347 				return true;
348 			}
349 			/*
350 			 * Erase everything following "Geode" (but not "Geode"
351 			 * token itself) on Geode processors, e.g. "Geode(TM)
352 			 * Integrated Processor by AMD PCS" "Geode(TM)
353 			 * Integrated Processor by National Semi"
354 			 */
355 			if (memcmp(token_start, "Geode", token_length) == 0) {
356 				return false;
357 			}
358 			/* Remember to erase "model unknown" in "AMD Processor
359 			 * model unknown" */
360 			if (memcmp(token_start, "model", token_length) == 0) {
361 				state->context_model = token_start;
362 				return true;
363 			}
364 			break;
365 		case 6:
366 			/*
367 			 * Erase everything starting with "Radeon" or "RADEON"
368 			 * on AMD APUs, e.g. "A8-7670K Radeon R7, 10 Compute
369 			 * Cores 4C+6G" "FX-8800P Radeon R7, 12 Compute Cores
370 			 * 4C+8G" "A12-9800 RADEON R7, 12 COMPUTE CORES 4C+8G"
371 			 *   "A9-9410 RADEON R5, 5 COMPUTE CORES 2C+3G"
372 			 */
373 			if (erase_matching(token_start, token_length, "Radeon") ||
374 			    erase_matching(token_start, token_length, "RADEON")) {
375 				return false;
376 			}
377 			/*
378 			 * Erase "Mobile" when it is not part of the processor
379 			 * name, e.g. in "AMD Turion(tm) X2 Ultra Dual-Core
380 			 * Mobile ZM-82"
381 			 */
382 			if (previousState.context_core != NULL) {
383 				if (erase_matching(token_start, token_length, "Mobile")) {
384 					return true;
385 				}
386 			}
387 			/* Erase "family" in "Intel(R) Pentium(R) III CPU family
388 			 * 1266MHz" */
389 			if (erase_matching(token_start, token_length, "family")) {
390 				return true;
391 			}
392 			/* Discard the string if it contains "Engineering
393 			 * Sample" */
394 			if (previousState.context_engineering != NULL) {
395 				if (memcmp(token_start, "Sample", token_length) == 0) {
396 					state->engineering_sample = true;
397 					return false;
398 				}
399 			}
400 			break;
401 		case 7:
402 			/*
403 			 * Erase "Geniune" in brand string on Intel engineering
404 			 * samples, e.g. "Genuine Intel(R) processor 800MHz"
405 			 *   "Genuine Intel(R) CPU @ 2.13GHz"
406 			 *   "Genuine Intel(R) CPU 0000 @ 1.73GHz"
407 			 */
408 			if (erase_matching(token_start, token_length, "Genuine")) {
409 				return true;
410 			}
411 			/*
412 			 * Erase "12-core" in brand string on AMD Threadripper,
413 			 * e.g. "AMD Ryzen Threadripper 1920X 12-Core Processor"
414 			 */
415 			if (erase_matching(token_start, token_length, "12-Core")) {
416 				return true;
417 			}
418 			/*
419 			 * Erase "16-core" in brand string on AMD Threadripper,
420 			 * e.g. "AMD Ryzen Threadripper 1950X 16-Core Processor"
421 			 */
422 			if (erase_matching(token_start, token_length, "16-Core")) {
423 				return true;
424 			}
425 			/* Erase "model unknown" in "AMD Processor model
426 			 * unknown" */
427 			if (previousState.context_model != NULL) {
428 				if (memcmp(token_start, "unknown", token_length) == 0) {
429 					memset(previousState.context_model,
430 					       ' ',
431 					       token_end - previousState.context_model);
432 					return true;
433 				}
434 			}
435 			/*
436 			 * Discard the string if it contains "Eng Sample:" or
437 			 * "Eng Sample," e.g. "AMD Eng Sample,
438 			 * ZD302046W4K43_36/30/20_2/8_A" "AMD Eng Sample:
439 			 * 2D3151A2M88E4_35/31_N"
440 			 */
441 			if (previousState.context_engineering != NULL) {
442 				if (memcmp(token_start, "Sample,", token_length) == 0 ||
443 				    memcmp(token_start, "Sample:", token_length) == 0) {
444 					state->engineering_sample = true;
445 					return false;
446 				}
447 			}
448 			break;
449 		case 8:
450 			/* Erase "QuadCore" in "VIA QuadCore L4700 @ 1.2+ GHz"
451 			 */
452 			if (erase_matching(token_start, token_length, "QuadCore")) {
453 				state->context_core = token_end;
454 				return true;
455 			}
456 			/* Erase "Six-Core" in "AMD FX(tm)-6100 Six-Core
457 			 * Processor" */
458 			if (erase_matching(token_start, token_length, "Six-Core")) {
459 				state->context_core = token_end;
460 				return true;
461 			}
462 			break;
463 		case 9:
464 			if (erase_matching(token_start, token_length, "Processor")) {
465 				return true;
466 			}
467 			if (erase_matching(token_start, token_length, "processor")) {
468 				return true;
469 			}
470 			/* Erase "Dual-Core" in "Pentium(R) Dual-Core CPU T4200
471 			 * @ 2.00GHz" */
472 			if (erase_matching(token_start, token_length, "Dual-Core")) {
473 				state->context_core = token_end;
474 				return true;
475 			}
476 			/* Erase "Quad-Core" in AMD processors, e.g.
477 			 *   "Quad-Core AMD Opteron(tm) Processor 2347 HE"
478 			 *   "AMD FX(tm)-4170 Quad-Core Processor"
479 			 */
480 			if (erase_matching(token_start, token_length, "Quad-Core")) {
481 				state->context_core = token_end;
482 				return true;
483 			}
484 			/* Erase "Transmeta" in brand string on Transmeta
485 			 * processors, e.g. "Transmeta(tm) Crusoe(tm) Processor
486 			 * TM5800" "Transmeta Efficeon(tm) Processor TM8000"
487 			 */
488 			if (erase_matching(token_start, token_length, "Transmeta")) {
489 				return true;
490 			}
491 			break;
492 		case 10:
493 			/*
494 			 * Erase "Eight-Core" in AMD processors, e.g.
495 			 *   "AMD FX(tm)-8150 Eight-Core Processor"
496 			 */
497 			if (erase_matching(token_start, token_length, "Eight-Core")) {
498 				state->context_core = token_end;
499 				return true;
500 			}
501 			break;
502 		case 11:
503 			/*
504 			 * Erase "Triple-Core" in AMD processors, e.g.
505 			 *   "AMD Phenom(tm) II N830 Triple-Core Processor"
506 			 *   "AMD Phenom(tm) 8650 Triple-Core Processor"
507 			 */
508 			if (erase_matching(token_start, token_length, "Triple-Core")) {
509 				state->context_core = token_end;
510 				return true;
511 			}
512 			/*
513 			 * Remember to discard string if it contains
514 			 * "Engineering Sample", e.g. "AMD Engineering Sample"
515 			 */
516 			if (memcmp(token_start, "Engineering", token_length) == 0) {
517 				state->context_engineering = token_start;
518 				return true;
519 			}
520 			break;
521 	}
522 	if (is_zero_number(token_start, token_end)) {
523 		memset(token_start, ' ', token_length);
524 		return true;
525 	}
526 	/* On some Intel processors the last letter of the name is put before
527 	 * the number, and an additional space it added, e.g. "Intel(R) Core(TM)
528 	 * i7 CPU X 990  @ 3.47GHz" "Intel(R) Core(TM) CPU Q 820  @ 1.73GHz"
529 	 * "Intel(R) Core(TM) i5 CPU M 480  @ 2.67GHz" We fix this issue, i.e.
530 	 * "X 990" -> "990X", "Q 820"
531 	 * -> "820Q"
532 	 */
533 	if (previousState.context_upper_letter != 0) {
534 		/* A single letter token followed by 2-to-5 digit letter is
535 		 * merged together
536 		 */
537 		switch (token_length) {
538 			case 2:
539 			case 3:
540 			case 4:
541 			case 5:
542 				if (is_number(token_start, token_end)) {
543 					/* Load the previous single-letter token
544 					 */
545 					const char letter = *previousState.context_upper_letter;
546 					/* Erase the previous single-letter
547 					 * token */
548 					*previousState.context_upper_letter = ' ';
549 					/* Move the current token one position
550 					 * to the left */
551 					move_token(token_start, token_end, token_start - 1);
552 					token_start -= 1;
553 					/*
554 					 * Add the letter on the end
555 					 * Note: accessing token_start[-1] is
556 					 * safe because this is not the first
557 					 * token
558 					 */
559 					token_end[-1] = letter;
560 				}
561 		}
562 	}
563 	if (state->frequency_separator != NULL) {
564 		if (is_model_number(token_start, token_end)) {
565 			state->parsed_model_number = true;
566 		}
567 	}
568 	if (is_frequency(token_start, token_end)) {
569 		state->frequency_token = true;
570 	}
571 	return true;
572 }
573 
cpuinfo_x86_normalize_brand_string(const char raw_name[48],char normalized_name[48])574 uint32_t cpuinfo_x86_normalize_brand_string(const char raw_name[48], char normalized_name[48]) {
575 	normalized_name[0] = '\0';
576 	char name[48];
577 	memcpy(name, raw_name, sizeof(name));
578 
579 	/*
580 	 * First find the end of the string
581 	 * Start search from the end because some brand strings contain zeroes
582 	 * in the middle
583 	 */
584 	char* name_end = &name[48];
585 	while (name_end[-1] == '\0') {
586 		/*
587 		 * Adject name_end by 1 position and check that we didn't reach
588 		 * the start of the brand string. This is possible if all
589 		 * characters are zero.
590 		 */
591 		if (--name_end == name) {
592 			/* All characters are zeros */
593 			return 0;
594 		}
595 	}
596 
597 	struct parser_state parser_state = {0};
598 
599 	/* Now unify all whitespace characters: replace tabs and '\0' with
600 	 * spaces */
601 	{
602 		bool inside_parentheses = false;
603 		for (char* char_ptr = name; char_ptr != name_end; char_ptr++) {
604 			switch (*char_ptr) {
605 				case '(':
606 					inside_parentheses = true;
607 					*char_ptr = ' ';
608 					break;
609 				case ')':
610 					inside_parentheses = false;
611 					*char_ptr = ' ';
612 					break;
613 				case '@':
614 					parser_state.frequency_separator = char_ptr;
615 				case '\0':
616 				case '\t':
617 					*char_ptr = ' ';
618 					break;
619 				default:
620 					if (inside_parentheses) {
621 						*char_ptr = ' ';
622 					}
623 			}
624 		}
625 	}
626 
627 	/* Iterate through all tokens and erase redundant parts */
628 	{
629 		bool is_token = false;
630 		char* token_start = NULL;
631 		for (char* char_ptr = name; char_ptr != name_end; char_ptr++) {
632 			if (*char_ptr == ' ') {
633 				if (is_token) {
634 					is_token = false;
635 					if (!transform_token(token_start, char_ptr, &parser_state)) {
636 						name_end = char_ptr;
637 						break;
638 					}
639 				}
640 			} else {
641 				if (!is_token) {
642 					is_token = true;
643 					token_start = char_ptr;
644 				}
645 			}
646 		}
647 		if (is_token) {
648 			transform_token(token_start, name_end, &parser_state);
649 		}
650 	}
651 
652 	/* If this is an engineering sample, return empty string */
653 	if (parser_state.engineering_sample) {
654 		return 0;
655 	}
656 
657 	/* Check if there is some string before the frequency separator. */
658 	if (parser_state.frequency_separator != NULL) {
659 		if (is_space(name, parser_state.frequency_separator)) {
660 			/* If only frequency is available, return empty string
661 			 */
662 			return 0;
663 		}
664 	}
665 
666 	/* Compact tokens: collapse multiple spacing into one */
667 	{
668 		char* output_ptr = normalized_name;
669 		char* token_start = NULL;
670 		bool is_token = false;
671 		bool previous_token_ends_with_dash = true;
672 		bool current_token_starts_with_dash = false;
673 		uint32_t token_count = 1;
674 		for (char* char_ptr = name; char_ptr != name_end; char_ptr++) {
675 			const char character = *char_ptr;
676 			if (character == ' ') {
677 				if (is_token) {
678 					is_token = false;
679 					if (!current_token_starts_with_dash && !previous_token_ends_with_dash) {
680 						token_count += 1;
681 						*output_ptr++ = ' ';
682 					}
683 					output_ptr = move_token(token_start, char_ptr, output_ptr);
684 					/* Note: char_ptr[-1] exists because
685 					 * there is a token before this space */
686 					previous_token_ends_with_dash = (char_ptr[-1] == '-');
687 				}
688 			} else {
689 				if (!is_token) {
690 					is_token = true;
691 					token_start = char_ptr;
692 					current_token_starts_with_dash = (character == '-');
693 				}
694 			}
695 		}
696 		if (is_token) {
697 			if (!current_token_starts_with_dash && !previous_token_ends_with_dash) {
698 				token_count += 1;
699 				*output_ptr++ = ' ';
700 			}
701 			output_ptr = move_token(token_start, name_end, output_ptr);
702 		}
703 		if (parser_state.frequency_token && token_count <= 1) {
704 			/* The only remaining part is frequency */
705 			normalized_name[0] = '\0';
706 			return 0;
707 		}
708 		if (output_ptr < &normalized_name[48]) {
709 			*output_ptr = '\0';
710 		} else {
711 			normalized_name[47] = '\0';
712 		}
713 		return (uint32_t)(output_ptr - normalized_name);
714 	}
715 }
716 
717 static const char* vendor_string_map[] = {
718 	[cpuinfo_vendor_intel] = "Intel",
719 	[cpuinfo_vendor_amd] = "AMD",
720 	[cpuinfo_vendor_via] = "VIA",
721 	[cpuinfo_vendor_hygon] = "Hygon",
722 	[cpuinfo_vendor_rdc] = "RDC",
723 	[cpuinfo_vendor_dmp] = "DM&P",
724 	[cpuinfo_vendor_transmeta] = "Transmeta",
725 	[cpuinfo_vendor_cyrix] = "Cyrix",
726 	[cpuinfo_vendor_rise] = "Rise",
727 	[cpuinfo_vendor_nsc] = "NSC",
728 	[cpuinfo_vendor_sis] = "SiS",
729 	[cpuinfo_vendor_nexgen] = "NexGen",
730 	[cpuinfo_vendor_umc] = "UMC",
731 };
732 
cpuinfo_x86_format_package_name(enum cpuinfo_vendor vendor,const char normalized_brand_string[48],char package_name[CPUINFO_PACKAGE_NAME_MAX])733 uint32_t cpuinfo_x86_format_package_name(
734 	enum cpuinfo_vendor vendor,
735 	const char normalized_brand_string[48],
736 	char package_name[CPUINFO_PACKAGE_NAME_MAX]) {
737 	if (normalized_brand_string[0] == '\0') {
738 		package_name[0] = '\0';
739 		return 0;
740 	}
741 
742 	const char* vendor_string = NULL;
743 	if ((uint32_t)vendor < (uint32_t)CPUINFO_COUNT_OF(vendor_string_map)) {
744 		vendor_string = vendor_string_map[(uint32_t)vendor];
745 	}
746 	if (vendor_string == NULL) {
747 		strncpy(package_name, normalized_brand_string, CPUINFO_PACKAGE_NAME_MAX);
748 		package_name[CPUINFO_PACKAGE_NAME_MAX - 1] = '\0';
749 		return 0;
750 	} else {
751 		snprintf(package_name, CPUINFO_PACKAGE_NAME_MAX, "%s %s", vendor_string, normalized_brand_string);
752 		return (uint32_t)strlen(vendor_string) + 1;
753 	}
754 }
755