xref: /openbmc/linux/tools/arch/x86/kcpuid/kcpuid.c (revision e5242c5f)
1 // SPDX-License-Identifier: GPL-2.0
2 #define _GNU_SOURCE
3 
4 #include <stdio.h>
5 #include <stdbool.h>
6 #include <stdlib.h>
7 #include <string.h>
8 #include <getopt.h>
9 
10 #define ARRAY_SIZE(x)	(sizeof(x) / sizeof((x)[0]))
11 #define min(a, b)	(((a) < (b)) ? (a) : (b))
12 
13 typedef unsigned int u32;
14 typedef unsigned long long u64;
15 
16 char *def_csv = "/usr/share/misc/cpuid.csv";
17 char *user_csv;
18 
19 
20 /* Cover both single-bit flag and multiple-bits fields */
21 struct bits_desc {
22 	/* start and end bits */
23 	int start, end;
24 	/* 0 or 1 for 1-bit flag */
25 	int value;
26 	char simp[32];
27 	char detail[256];
28 };
29 
30 /* descriptor info for eax/ebx/ecx/edx */
31 struct reg_desc {
32 	/* number of valid entries */
33 	int nr;
34 	struct bits_desc descs[32];
35 };
36 
37 enum cpuid_reg {
38 	R_EAX = 0,
39 	R_EBX,
40 	R_ECX,
41 	R_EDX,
42 	NR_REGS
43 };
44 
45 static const char * const reg_names[] = {
46 	"EAX", "EBX", "ECX", "EDX",
47 };
48 
49 struct subleaf {
50 	u32 index;
51 	u32 sub;
52 	u32 eax, ebx, ecx, edx;
53 	struct reg_desc info[NR_REGS];
54 };
55 
56 /* Represent one leaf (basic or extended) */
57 struct cpuid_func {
58 	/*
59 	 * Array of subleafs for this func, if there is no subleafs
60 	 * then the leafs[0] is the main leaf
61 	 */
62 	struct subleaf *leafs;
63 	int nr;
64 };
65 
66 struct cpuid_range {
67 	/* array of main leafs */
68 	struct cpuid_func *funcs;
69 	/* number of valid leafs */
70 	int nr;
71 	bool is_ext;
72 };
73 
74 /*
75  * basic:  basic functions range: [0... ]
76  * ext:    extended functions range: [0x80000000... ]
77  */
78 struct cpuid_range *leafs_basic, *leafs_ext;
79 
80 static int num_leafs;
81 static bool is_amd;
82 static bool show_details;
83 static bool show_raw;
84 static bool show_flags_only = true;
85 static u32 user_index = 0xFFFFFFFF;
86 static u32 user_sub = 0xFFFFFFFF;
87 static int flines;
88 
89 static inline void cpuid(u32 *eax, u32 *ebx, u32 *ecx, u32 *edx)
90 {
91 	/* ecx is often an input as well as an output. */
92 	asm volatile("cpuid"
93 	    : "=a" (*eax),
94 	      "=b" (*ebx),
95 	      "=c" (*ecx),
96 	      "=d" (*edx)
97 	    : "0" (*eax), "2" (*ecx));
98 }
99 
100 static inline bool has_subleafs(u32 f)
101 {
102 	if (f == 0x7 || f == 0xd)
103 		return true;
104 
105 	if (is_amd) {
106 		if (f == 0x8000001d)
107 			return true;
108 		return false;
109 	}
110 
111 	switch (f) {
112 	case 0x4:
113 	case 0xb:
114 	case 0xf:
115 	case 0x10:
116 	case 0x14:
117 	case 0x18:
118 	case 0x1f:
119 		return true;
120 	default:
121 		return false;
122 	}
123 }
124 
125 static void leaf_print_raw(struct subleaf *leaf)
126 {
127 	if (has_subleafs(leaf->index)) {
128 		if (leaf->sub == 0)
129 			printf("0x%08x: subleafs:\n", leaf->index);
130 
131 		printf(" %2d: EAX=0x%08x, EBX=0x%08x, ECX=0x%08x, EDX=0x%08x\n",
132 			leaf->sub, leaf->eax, leaf->ebx, leaf->ecx, leaf->edx);
133 	} else {
134 		printf("0x%08x: EAX=0x%08x, EBX=0x%08x, ECX=0x%08x, EDX=0x%08x\n",
135 			leaf->index, leaf->eax, leaf->ebx, leaf->ecx, leaf->edx);
136 	}
137 }
138 
139 /* Return true is the input eax/ebx/ecx/edx are all zero */
140 static bool cpuid_store(struct cpuid_range *range, u32 f, int subleaf,
141 			u32 a, u32 b, u32 c, u32 d)
142 {
143 	struct cpuid_func *func;
144 	struct subleaf *leaf;
145 	int s = 0;
146 
147 	if (a == 0 && b == 0 && c == 0 && d == 0)
148 		return true;
149 
150 	/*
151 	 * Cut off vendor-prefix from CPUID function as we're using it as an
152 	 * index into ->funcs.
153 	 */
154 	func = &range->funcs[f & 0xffff];
155 
156 	if (!func->leafs) {
157 		func->leafs = malloc(sizeof(struct subleaf));
158 		if (!func->leafs)
159 			perror("malloc func leaf");
160 
161 		func->nr = 1;
162 	} else {
163 		s = func->nr;
164 		func->leafs = realloc(func->leafs, (s + 1) * sizeof(*leaf));
165 		if (!func->leafs)
166 			perror("realloc f->leafs");
167 
168 		func->nr++;
169 	}
170 
171 	leaf = &func->leafs[s];
172 
173 	leaf->index = f;
174 	leaf->sub = subleaf;
175 	leaf->eax = a;
176 	leaf->ebx = b;
177 	leaf->ecx = c;
178 	leaf->edx = d;
179 
180 	return false;
181 }
182 
183 static void raw_dump_range(struct cpuid_range *range)
184 {
185 	u32 f;
186 	int i;
187 
188 	printf("%s Leafs :\n", range->is_ext ? "Extended" : "Basic");
189 	printf("================\n");
190 
191 	for (f = 0; (int)f < range->nr; f++) {
192 		struct cpuid_func *func = &range->funcs[f];
193 		u32 index = f;
194 
195 		if (range->is_ext)
196 			index += 0x80000000;
197 
198 		/* Skip leaf without valid items */
199 		if (!func->nr)
200 			continue;
201 
202 		/* First item is the main leaf, followed by all subleafs */
203 		for (i = 0; i < func->nr; i++)
204 			leaf_print_raw(&func->leafs[i]);
205 	}
206 }
207 
208 #define MAX_SUBLEAF_NUM		32
209 struct cpuid_range *setup_cpuid_range(u32 input_eax)
210 {
211 	u32 max_func, idx_func, subleaf, max_subleaf;
212 	u32 eax, ebx, ecx, edx, f = input_eax;
213 	struct cpuid_range *range;
214 	bool allzero;
215 
216 	eax = input_eax;
217 	ebx = ecx = edx = 0;
218 
219 	cpuid(&eax, &ebx, &ecx, &edx);
220 	max_func = eax;
221 	idx_func = (max_func & 0xffff) + 1;
222 
223 	range = malloc(sizeof(struct cpuid_range));
224 	if (!range)
225 		perror("malloc range");
226 
227 	if (input_eax & 0x80000000)
228 		range->is_ext = true;
229 	else
230 		range->is_ext = false;
231 
232 	range->funcs = malloc(sizeof(struct cpuid_func) * idx_func);
233 	if (!range->funcs)
234 		perror("malloc range->funcs");
235 
236 	range->nr = idx_func;
237 	memset(range->funcs, 0, sizeof(struct cpuid_func) * idx_func);
238 
239 	for (; f <= max_func; f++) {
240 		eax = f;
241 		subleaf = ecx = 0;
242 
243 		cpuid(&eax, &ebx, &ecx, &edx);
244 		allzero = cpuid_store(range, f, subleaf, eax, ebx, ecx, edx);
245 		if (allzero)
246 			continue;
247 		num_leafs++;
248 
249 		if (!has_subleafs(f))
250 			continue;
251 
252 		max_subleaf = MAX_SUBLEAF_NUM;
253 
254 		/*
255 		 * Some can provide the exact number of subleafs,
256 		 * others have to be tried (0xf)
257 		 */
258 		if (f == 0x7 || f == 0x14 || f == 0x17 || f == 0x18)
259 			max_subleaf = min((eax & 0xff) + 1, max_subleaf);
260 
261 		if (f == 0xb)
262 			max_subleaf = 2;
263 
264 		for (subleaf = 1; subleaf < max_subleaf; subleaf++) {
265 			eax = f;
266 			ecx = subleaf;
267 
268 			cpuid(&eax, &ebx, &ecx, &edx);
269 			allzero = cpuid_store(range, f, subleaf,
270 						eax, ebx, ecx, edx);
271 			if (allzero)
272 				continue;
273 			num_leafs++;
274 		}
275 
276 	}
277 
278 	return range;
279 }
280 
281 /*
282  * The basic row format for cpuid.csv  is
283  *	LEAF,SUBLEAF,register_name,bits,short name,long description
284  *
285  * like:
286  *	0,    0,  EAX,   31:0, max_basic_leafs,  Max input value for supported subleafs
287  *	1,    0,  ECX,      0, sse3,  Streaming SIMD Extensions 3(SSE3)
288  */
289 static int parse_line(char *line)
290 {
291 	char *str;
292 	int i;
293 	struct cpuid_range *range;
294 	struct cpuid_func *func;
295 	struct subleaf *leaf;
296 	u32 index;
297 	u32 sub;
298 	char buffer[512];
299 	char *buf;
300 	/*
301 	 * Tokens:
302 	 *  1. leaf
303 	 *  2. subleaf
304 	 *  3. register
305 	 *  4. bits
306 	 *  5. short name
307 	 *  6. long detail
308 	 */
309 	char *tokens[6];
310 	struct reg_desc *reg;
311 	struct bits_desc *bdesc;
312 	int reg_index;
313 	char *start, *end;
314 
315 	/* Skip comments and NULL line */
316 	if (line[0] == '#' || line[0] == '\n')
317 		return 0;
318 
319 	strncpy(buffer, line, 511);
320 	buffer[511] = 0;
321 	str = buffer;
322 	for (i = 0; i < 5; i++) {
323 		tokens[i] = strtok(str, ",");
324 		if (!tokens[i])
325 			goto err_exit;
326 		str = NULL;
327 	}
328 	tokens[5] = strtok(str, "\n");
329 	if (!tokens[5])
330 		goto err_exit;
331 
332 	/* index/main-leaf */
333 	index = strtoull(tokens[0], NULL, 0);
334 
335 	if (index & 0x80000000)
336 		range = leafs_ext;
337 	else
338 		range = leafs_basic;
339 
340 	index &= 0x7FFFFFFF;
341 	/* Skip line parsing for non-existing indexes */
342 	if ((int)index >= range->nr)
343 		return -1;
344 
345 	func = &range->funcs[index];
346 
347 	/* Return if the index has no valid item on this platform */
348 	if (!func->nr)
349 		return 0;
350 
351 	/* subleaf */
352 	sub = strtoul(tokens[1], NULL, 0);
353 	if ((int)sub > func->nr)
354 		return -1;
355 
356 	leaf = &func->leafs[sub];
357 	buf = tokens[2];
358 
359 	if (strcasestr(buf, "EAX"))
360 		reg_index = R_EAX;
361 	else if (strcasestr(buf, "EBX"))
362 		reg_index = R_EBX;
363 	else if (strcasestr(buf, "ECX"))
364 		reg_index = R_ECX;
365 	else if (strcasestr(buf, "EDX"))
366 		reg_index = R_EDX;
367 	else
368 		goto err_exit;
369 
370 	reg = &leaf->info[reg_index];
371 	bdesc = &reg->descs[reg->nr++];
372 
373 	/* bit flag or bits field */
374 	buf = tokens[3];
375 
376 	end = strtok(buf, ":");
377 	bdesc->end = strtoul(end, NULL, 0);
378 	bdesc->start = bdesc->end;
379 
380 	/* start != NULL means it is bit fields */
381 	start = strtok(NULL, ":");
382 	if (start)
383 		bdesc->start = strtoul(start, NULL, 0);
384 
385 	strcpy(bdesc->simp, tokens[4]);
386 	strcpy(bdesc->detail, tokens[5]);
387 	return 0;
388 
389 err_exit:
390 	printf("Warning: wrong line format:\n");
391 	printf("\tline[%d]: %s\n", flines, line);
392 	return -1;
393 }
394 
395 /* Parse csv file, and construct the array of all leafs and subleafs */
396 static void parse_text(void)
397 {
398 	FILE *file;
399 	char *filename, *line = NULL;
400 	size_t len = 0;
401 	int ret;
402 
403 	if (show_raw)
404 		return;
405 
406 	filename = user_csv ? user_csv : def_csv;
407 	file = fopen(filename, "r");
408 	if (!file) {
409 		/* Fallback to a csv in the same dir */
410 		file = fopen("./cpuid.csv", "r");
411 	}
412 
413 	if (!file) {
414 		printf("Fail to open '%s'\n", filename);
415 		return;
416 	}
417 
418 	while (1) {
419 		ret = getline(&line, &len, file);
420 		flines++;
421 		if (ret > 0)
422 			parse_line(line);
423 
424 		if (feof(file))
425 			break;
426 	}
427 
428 	fclose(file);
429 }
430 
431 
432 /* Decode every eax/ebx/ecx/edx */
433 static void decode_bits(u32 value, struct reg_desc *rdesc, enum cpuid_reg reg)
434 {
435 	struct bits_desc *bdesc;
436 	int start, end, i;
437 	u32 mask;
438 
439 	if (!rdesc->nr) {
440 		if (show_details)
441 			printf("\t %s: 0x%08x\n", reg_names[reg], value);
442 		return;
443 	}
444 
445 	for (i = 0; i < rdesc->nr; i++) {
446 		bdesc = &rdesc->descs[i];
447 
448 		start = bdesc->start;
449 		end = bdesc->end;
450 		if (start == end) {
451 			/* single bit flag */
452 			if (value & (1 << start))
453 				printf("\t%-20s %s%s\n",
454 					bdesc->simp,
455 					show_details ? "-" : "",
456 					show_details ? bdesc->detail : ""
457 					);
458 		} else {
459 			/* bit fields */
460 			if (show_flags_only)
461 				continue;
462 
463 			mask = ((u64)1 << (end - start + 1)) - 1;
464 			printf("\t%-20s\t: 0x%-8x\t%s%s\n",
465 					bdesc->simp,
466 					(value >> start) & mask,
467 					show_details ? "-" : "",
468 					show_details ? bdesc->detail : ""
469 					);
470 		}
471 	}
472 }
473 
474 static void show_leaf(struct subleaf *leaf)
475 {
476 	if (!leaf)
477 		return;
478 
479 	if (show_raw) {
480 		leaf_print_raw(leaf);
481 	} else {
482 		if (show_details)
483 			printf("CPUID_0x%x_ECX[0x%x]:\n",
484 				leaf->index, leaf->sub);
485 	}
486 
487 	decode_bits(leaf->eax, &leaf->info[R_EAX], R_EAX);
488 	decode_bits(leaf->ebx, &leaf->info[R_EBX], R_EBX);
489 	decode_bits(leaf->ecx, &leaf->info[R_ECX], R_ECX);
490 	decode_bits(leaf->edx, &leaf->info[R_EDX], R_EDX);
491 
492 	if (!show_raw && show_details)
493 		printf("\n");
494 }
495 
496 static void show_func(struct cpuid_func *func)
497 {
498 	int i;
499 
500 	if (!func)
501 		return;
502 
503 	for (i = 0; i < func->nr; i++)
504 		show_leaf(&func->leafs[i]);
505 }
506 
507 static void show_range(struct cpuid_range *range)
508 {
509 	int i;
510 
511 	for (i = 0; i < range->nr; i++)
512 		show_func(&range->funcs[i]);
513 }
514 
515 static inline struct cpuid_func *index_to_func(u32 index)
516 {
517 	struct cpuid_range *range;
518 	u32 func_idx;
519 
520 	range = (index & 0x80000000) ? leafs_ext : leafs_basic;
521 	func_idx = index & 0xffff;
522 
523 	if ((func_idx + 1) > (u32)range->nr) {
524 		printf("ERR: invalid input index (0x%x)\n", index);
525 		return NULL;
526 	}
527 	return &range->funcs[func_idx];
528 }
529 
530 static void show_info(void)
531 {
532 	struct cpuid_func *func;
533 
534 	if (show_raw) {
535 		/* Show all of the raw output of 'cpuid' instr */
536 		raw_dump_range(leafs_basic);
537 		raw_dump_range(leafs_ext);
538 		return;
539 	}
540 
541 	if (user_index != 0xFFFFFFFF) {
542 		/* Only show specific leaf/subleaf info */
543 		func = index_to_func(user_index);
544 		if (!func)
545 			return;
546 
547 		/* Dump the raw data also */
548 		show_raw = true;
549 
550 		if (user_sub != 0xFFFFFFFF) {
551 			if (user_sub + 1 <= (u32)func->nr) {
552 				show_leaf(&func->leafs[user_sub]);
553 				return;
554 			}
555 
556 			printf("ERR: invalid input subleaf (0x%x)\n", user_sub);
557 		}
558 
559 		show_func(func);
560 		return;
561 	}
562 
563 	printf("CPU features:\n=============\n\n");
564 	show_range(leafs_basic);
565 	show_range(leafs_ext);
566 }
567 
568 static void setup_platform_cpuid(void)
569 {
570 	 u32 eax, ebx, ecx, edx;
571 
572 	/* Check vendor */
573 	eax = ebx = ecx = edx = 0;
574 	cpuid(&eax, &ebx, &ecx, &edx);
575 
576 	/* "htuA" */
577 	if (ebx == 0x68747541)
578 		is_amd = true;
579 
580 	/* Setup leafs for the basic and extended range */
581 	leafs_basic = setup_cpuid_range(0x0);
582 	leafs_ext = setup_cpuid_range(0x80000000);
583 }
584 
585 static void usage(void)
586 {
587 	printf("kcpuid [-abdfhr] [-l leaf] [-s subleaf]\n"
588 		"\t-a|--all             Show both bit flags and complex bit fields info\n"
589 		"\t-b|--bitflags        Show boolean flags only\n"
590 		"\t-d|--detail          Show details of the flag/fields (default)\n"
591 		"\t-f|--flags           Specify the cpuid csv file\n"
592 		"\t-h|--help            Show usage info\n"
593 		"\t-l|--leaf=index      Specify the leaf you want to check\n"
594 		"\t-r|--raw             Show raw cpuid data\n"
595 		"\t-s|--subleaf=sub     Specify the subleaf you want to check\n"
596 	);
597 }
598 
599 static struct option opts[] = {
600 	{ "all", no_argument, NULL, 'a' },		/* show both bit flags and fields */
601 	{ "bitflags", no_argument, NULL, 'b' },		/* only show bit flags, default on */
602 	{ "detail", no_argument, NULL, 'd' },		/* show detail descriptions */
603 	{ "file", required_argument, NULL, 'f' },	/* use user's cpuid file */
604 	{ "help", no_argument, NULL, 'h'},		/* show usage */
605 	{ "leaf", required_argument, NULL, 'l'},	/* only check a specific leaf */
606 	{ "raw", no_argument, NULL, 'r'},		/* show raw CPUID leaf data */
607 	{ "subleaf", required_argument, NULL, 's'},	/* check a specific subleaf */
608 	{ NULL, 0, NULL, 0 }
609 };
610 
611 static int parse_options(int argc, char *argv[])
612 {
613 	int c;
614 
615 	while ((c = getopt_long(argc, argv, "abdf:hl:rs:",
616 					opts, NULL)) != -1)
617 		switch (c) {
618 		case 'a':
619 			show_flags_only = false;
620 			break;
621 		case 'b':
622 			show_flags_only = true;
623 			break;
624 		case 'd':
625 			show_details = true;
626 			break;
627 		case 'f':
628 			user_csv = optarg;
629 			break;
630 		case 'h':
631 			usage();
632 			exit(1);
633 			break;
634 		case 'l':
635 			/* main leaf */
636 			user_index = strtoul(optarg, NULL, 0);
637 			break;
638 		case 'r':
639 			show_raw = true;
640 			break;
641 		case 's':
642 			/* subleaf */
643 			user_sub = strtoul(optarg, NULL, 0);
644 			break;
645 		default:
646 			printf("%s: Invalid option '%c'\n", argv[0], optopt);
647 			return -1;
648 	}
649 
650 	return 0;
651 }
652 
653 /*
654  * Do 4 things in turn:
655  * 1. Parse user options
656  * 2. Parse and store all the CPUID leaf data supported on this platform
657  * 2. Parse the csv file, while skipping leafs which are not available
658  *    on this platform
659  * 3. Print leafs info based on user options
660  */
661 int main(int argc, char *argv[])
662 {
663 	if (parse_options(argc, argv))
664 		return -1;
665 
666 	/* Setup the cpuid leafs of current platform */
667 	setup_platform_cpuid();
668 
669 	/* Read and parse the 'cpuid.csv' */
670 	parse_text();
671 
672 	show_info();
673 	return 0;
674 }
675