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