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