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