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 | |
12 | typedef unsigned int u32; |
13 | typedef unsigned long long u64; |
14 | |
15 | char *def_csv = "/usr/share/misc/cpuid.csv" ; |
16 | char *user_csv; |
17 | |
18 | |
19 | /* Cover both single-bit flag and multiple-bits fields */ |
20 | struct 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 */ |
30 | struct reg_desc { |
31 | /* number of valid entries */ |
32 | int nr; |
33 | struct bits_desc descs[32]; |
34 | }; |
35 | |
36 | enum cpuid_reg { |
37 | R_EAX = 0, |
38 | R_EBX, |
39 | R_ECX, |
40 | R_EDX, |
41 | NR_REGS |
42 | }; |
43 | |
44 | static const char * const reg_names[] = { |
45 | "EAX" , "EBX" , "ECX" , "EDX" , |
46 | }; |
47 | |
48 | struct 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) */ |
56 | struct 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 | |
65 | struct 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 | */ |
77 | struct cpuid_range *leafs_basic, *leafs_ext; |
78 | |
79 | static int num_leafs; |
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 | |
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 | |
99 | static 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 | |
124 | static 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 */ |
139 | static 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 | |
182 | static 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(leaf: &func->leafs[i]); |
204 | } |
205 | } |
206 | |
207 | #define MAX_SUBLEAF_NUM 32 |
208 | struct 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: &eax, ebx: &ebx, ecx: &ecx, edx: &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: &eax, ebx: &ebx, ecx: &ecx, edx: &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: &eax, ebx: &ebx, ecx: &ecx, edx: &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 | */ |
291 | static 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 = ®->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 | |
391 | err_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 */ |
398 | static 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 */ |
435 | static 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 | |
476 | static 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(value: leaf->eax, rdesc: &leaf->info[R_EAX], reg: R_EAX); |
490 | decode_bits(value: leaf->ebx, rdesc: &leaf->info[R_EBX], reg: R_EBX); |
491 | decode_bits(value: leaf->ecx, rdesc: &leaf->info[R_ECX], reg: R_ECX); |
492 | decode_bits(value: leaf->edx, rdesc: &leaf->info[R_EDX], reg: R_EDX); |
493 | |
494 | if (!show_raw && show_details) |
495 | printf("\n" ); |
496 | } |
497 | |
498 | static 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(leaf: &func->leafs[i]); |
507 | } |
508 | |
509 | static void show_range(struct cpuid_range *range) |
510 | { |
511 | int i; |
512 | |
513 | for (i = 0; i < range->nr; i++) |
514 | show_func(func: &range->funcs[i]); |
515 | } |
516 | |
517 | static 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 | |
532 | static 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(range: leafs_basic); |
539 | raw_dump_range(range: leafs_ext); |
540 | return; |
541 | } |
542 | |
543 | if (user_index != 0xFFFFFFFF) { |
544 | /* Only show specific leaf/subleaf info */ |
545 | func = index_to_func(index: 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(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(range: leafs_basic); |
567 | show_range(range: leafs_ext); |
568 | } |
569 | |
570 | static 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: &eax, ebx: &ebx, ecx: &ecx, edx: &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 | |
587 | static 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 | |
601 | static 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 | |
613 | static 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 | */ |
663 | int 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 | |