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