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