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