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 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 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 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 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 */ 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 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 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 */ 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 */ 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 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 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 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 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 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 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 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 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 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 */ 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