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