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
12typedef unsigned int u32;
13typedef unsigned long long u64;
14
15char *def_csv = "/usr/share/misc/cpuid.csv";
16char *user_csv;
17
18
19/* Cover both single-bit flag and multiple-bits fields */
20struct 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 */
30struct reg_desc {
31	/* number of valid entries */
32	int nr;
33	struct bits_desc descs[32];
34};
35
36enum cpuid_reg {
37	R_EAX = 0,
38	R_EBX,
39	R_ECX,
40	R_EDX,
41	NR_REGS
42};
43
44static const char * const reg_names[] = {
45	"EAX", "EBX", "ECX", "EDX",
46};
47
48struct 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) */
56struct 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
65struct 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 */
77struct cpuid_range *leafs_basic, *leafs_ext;
78
79static int num_leafs;
80static bool is_amd;
81static bool show_details;
82static bool show_raw;
83static bool show_flags_only = true;
84static u32 user_index = 0xFFFFFFFF;
85static u32 user_sub = 0xFFFFFFFF;
86static int flines;
87
88static 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
99static 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
124static 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 */
139static 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
182static 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
208struct 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 */
291static 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
391err_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 */
398static 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 */
435static 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
476static 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
498static 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
509static 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
517static 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
532static 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
570static 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
587static 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
601static 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
613static 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 */
663int 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