684 lines · c
1// SPDX-License-Identifier: GPL-2.02#define _GNU_SOURCE3 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 13typedef unsigned int u32;14typedef unsigned long long u64;15 16char *def_csv = "/usr/share/misc/cpuid.csv";17char *user_csv;18 19 20/* Cover both single-bit flag and multiple-bits fields */21struct 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 */31struct reg_desc {32 /* number of valid entries */33 int nr;34 struct bits_desc descs[32];35};36 37enum cpuid_reg {38 R_EAX = 0,39 R_EBX,40 R_ECX,41 R_EDX,42 NR_REGS43};44 45static const char * const reg_names[] = {46 "EAX", "EBX", "ECX", "EDX",47};48 49struct 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) */57struct cpuid_func {58 /*59 * Array of subleafs for this func, if there is no subleafs60 * then the leafs[0] is the main leaf61 */62 struct subleaf *leafs;63 int nr;64};65 66struct 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 */78struct cpuid_range *leafs_basic, *leafs_ext;79 80static bool is_amd;81static bool show_details;82static bool show_raw;83static bool show_flags_only = true;84static u32 user_index = 0xFFFFFFFF;85static u32 user_sub = 0xFFFFFFFF;86static int flines;87 88static 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 99static 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 114static 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 */129static 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 an141 * 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 172static 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 64198struct 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 else219 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 is277 * LEAF,SUBLEAF,register_name,bits,short name,long description278 *279 * like:280 * 0, 0, EAX, 31:0, max_basic_leafs, Max input value for supported subleafs281 * 1, 0, ECX, 0, sse3, Streaming SIMD Extensions 3(SSE3)282 */283static 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. leaf297 * 2. subleaf298 * 3. register299 * 4. bits300 * 5. short name301 * 6. long detail302 */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 else334 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 else376 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 397err_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 */404static 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 */441static 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 483static 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 505static 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 516static 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 524static 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 539static 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 577static 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 594static 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 608static 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 620static 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 options665 * 2. Parse and store all the CPUID leaf data supported on this platform666 * 2. Parse the csv file, while skipping leafs which are not available667 * on this platform668 * 3. Print leafs info based on user options669 */670int 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