// SPDX-License-Identifier: GPL-2.0 #define _GNU_SOURCE #include <stdio.h> #include <stdbool.h> #include <stdlib.h> #include <string.h> #include <getopt.h> #define ARRAY_SIZE(x) (sizeof(x) / sizeof((x)[0])) typedef unsigned int u32; typedef unsigned long long u64; char *def_csv = "/usr/share/misc/cpuid.csv"; char *user_csv; /* Cover both single-bit flag and multiple-bits fields */ struct bits_desc { /* start and end bits */ int start, end; /* 0 or 1 for 1-bit flag */ int value; char simp[32]; char detail[256]; }; /* descriptor info for eax/ebx/ecx/edx */ struct reg_desc { /* number of valid entries */ int nr; struct bits_desc descs[32]; }; enum cpuid_reg { R_EAX = 0, R_EBX, R_ECX, R_EDX, NR_REGS }; static const char * const reg_names[] = { "EAX", "EBX", "ECX", "EDX", }; struct subleaf { u32 index; u32 sub; u32 eax, ebx, ecx, edx; struct reg_desc info[NR_REGS]; }; /* Represent one leaf (basic or extended) */ struct cpuid_func { /* * Array of subleafs for this func, if there is no subleafs * then the leafs[0] is the main leaf */ struct subleaf *leafs; int nr; }; struct cpuid_range { /* array of main leafs */ struct cpuid_func *funcs; /* number of valid leafs */ int nr; bool is_ext; }; /* * basic: basic functions range: [0... ] * ext: extended functions range: [0x80000000... ] */ struct cpuid_range *leafs_basic, *leafs_ext; static int num_leafs; static bool is_amd; static bool show_details; static bool show_raw; static bool show_flags_only = true; static u32 user_index = 0xFFFFFFFF; static u32 user_sub = 0xFFFFFFFF; static int flines; static inline void cpuid(u32 *eax, u32 *ebx, u32 *ecx, u32 *edx) { /* ecx is often an input as well as an output. */ asm volatile("cpuid" : "=a" (*eax), "=b" (*ebx), "=c" (*ecx), "=d" (*edx) : "0" (*eax), "2" (*ecx)); } static inline bool has_subleafs(u32 f) { if (f == 0x7 || f == 0xd) return true; if (is_amd) { if (f == 0x8000001d) return true; return false; } switch (f) { case 0x4: case 0xb: case 0xf: case 0x10: case 0x14: case 0x18: case 0x1f: return true; default: return false; } } static void leaf_print_raw(struct subleaf *leaf) { if (has_subleafs(leaf->index)) { if (leaf->sub == 0) printf("0x%08x: subleafs:\n", leaf->index); printf(" %2d: EAX=0x%08x, EBX=0x%08x, ECX=0x%08x, EDX=0x%08x\n", leaf->sub, leaf->eax, leaf->ebx, leaf->ecx, leaf->edx); } else { printf("0x%08x: EAX=0x%08x, EBX=0x%08x, ECX=0x%08x, EDX=0x%08x\n", leaf->index, leaf->eax, leaf->ebx, leaf->ecx, leaf->edx); } } /* Return true is the input eax/ebx/ecx/edx are all zero */ static bool cpuid_store(struct cpuid_range *range, u32 f, int subleaf, u32 a, u32 b, u32 c, u32 d) { struct cpuid_func *func; struct subleaf *leaf; int s = 0; if (a == 0 && b == 0 && c == 0 && d == 0) return true; /* * Cut off vendor-prefix from CPUID function as we're using it as an * index into ->funcs. */ func = &range->funcs[f & 0xffff]; if (!func->leafs) { func->leafs = malloc(sizeof(struct subleaf)); if (!func->leafs) perror("malloc func leaf"); func->nr = 1; } else { s = func->nr; func->leafs = realloc(func->leafs, (s + 1) * sizeof(*leaf)); if (!func->leafs) perror("realloc f->leafs"); func->nr++; } leaf = &func->leafs[s]; leaf->index = f; leaf->sub = subleaf; leaf->eax = a; leaf->ebx = b; leaf->ecx = c; leaf->edx = d; return false; } static void raw_dump_range(struct cpuid_range *range) { u32 f; int i; printf("%s Leafs :\n", range->is_ext ? "Extended" : "Basic"); printf("================\n"); for (f = 0; (int)f < range->nr; f++) { struct cpuid_func *func = &range->funcs[f]; u32 index = f; if (range->is_ext) index += 0x80000000; /* Skip leaf without valid items */ if (!func->nr) continue; /* First item is the main leaf, followed by all subleafs */ for (i = 0; i < func->nr; i++) leaf_print_raw(&func->leafs[i]); } } #define MAX_SUBLEAF_NUM 32 struct cpuid_range *setup_cpuid_range(u32 input_eax) { u32 max_func, idx_func; int subleaf; struct cpuid_range *range; u32 eax, ebx, ecx, edx; u32 f = input_eax; int max_subleaf; bool allzero; eax = input_eax; ebx = ecx = edx = 0; cpuid(&eax, &ebx, &ecx, &edx); max_func = eax; idx_func = (max_func & 0xffff) + 1; range = malloc(sizeof(struct cpuid_range)); if (!range) perror("malloc range"); if (input_eax & 0x80000000) range->is_ext = true; else range->is_ext = false; range->funcs = malloc(sizeof(struct cpuid_func) * idx_func); if (!range->funcs) perror("malloc range->funcs"); range->nr = idx_func; memset(range->funcs, 0, sizeof(struct cpuid_func) * idx_func); for (; f <= max_func; f++) { eax = f; subleaf = ecx = 0; cpuid(&eax, &ebx, &ecx, &edx); allzero = cpuid_store(range, f, subleaf, eax, ebx, ecx, edx); if (allzero) continue; num_leafs++; if (!has_subleafs(f)) continue; max_subleaf = MAX_SUBLEAF_NUM; /* * Some can provide the exact number of subleafs, * others have to be tried (0xf) */ if (f == 0x7 || f == 0x14 || f == 0x17 || f == 0x18) max_subleaf = (eax & 0xff) + 1; if (f == 0xb) max_subleaf = 2; for (subleaf = 1; subleaf < max_subleaf; subleaf++) { eax = f; ecx = subleaf; cpuid(&eax, &ebx, &ecx, &edx); allzero = cpuid_store(range, f, subleaf, eax, ebx, ecx, edx); if (allzero) continue; num_leafs++; } } return range; } /* * The basic row format for cpuid.csv is * LEAF,SUBLEAF,register_name,bits,short name,long description * * like: * 0, 0, EAX, 31:0, max_basic_leafs, Max input value for supported subleafs * 1, 0, ECX, 0, sse3, Streaming SIMD Extensions 3(SSE3) */ static int parse_line(char *line) { char *str; int i; struct cpuid_range *range; struct cpuid_func *func; struct subleaf *leaf; u32 index; u32 sub; char buffer[512]; char *buf; /* * Tokens: * 1. leaf * 2. subleaf * 3. register * 4. bits * 5. short name * 6. long detail */ char *tokens[6]; struct reg_desc *reg; struct bits_desc *bdesc; int reg_index; char *start, *end; /* Skip comments and NULL line */ if (line[0] == '#' || line[0] == '\n') return 0; strncpy(buffer, line, 511); buffer[511] = 0; str = buffer; for (i = 0; i < 5; i++) { tokens[i] = strtok(str, ","); if (!tokens[i]) goto err_exit; str = NULL; } tokens[5] = strtok(str, "\n"); if (!tokens[5]) goto err_exit; /* index/main-leaf */ index = strtoull(tokens[0], NULL, 0); if (index & 0x80000000) range = leafs_ext; else range = leafs_basic; index &= 0x7FFFFFFF; /* Skip line parsing for non-existing indexes */ if ((int)index >= range->nr) return -1; func = &range->funcs[index]; /* Return if the index has no valid item on this platform */ if (!func->nr) return 0; /* subleaf */ sub = strtoul(tokens[1], NULL, 0); if ((int)sub > func->nr) return -1; leaf = &func->leafs[sub]; buf = tokens[2]; if (strcasestr(buf, "EAX")) reg_index = R_EAX; else if (strcasestr(buf, "EBX")) reg_index = R_EBX; else if (strcasestr(buf, "ECX")) reg_index = R_ECX; else if (strcasestr(buf, "EDX")) reg_index = R_EDX; else goto err_exit; reg = &leaf->info[reg_index]; bdesc = ®->descs[reg->nr++]; /* bit flag or bits field */ buf = tokens[3]; end = strtok(buf, ":"); bdesc->end = strtoul(end, NULL, 0); bdesc->start = bdesc->end; /* start != NULL means it is bit fields */ start = strtok(NULL, ":"); if (start) bdesc->start = strtoul(start, NULL, 0); strcpy(bdesc->simp, tokens[4]); strcpy(bdesc->detail, tokens[5]); return 0; err_exit: printf("Warning: wrong line format:\n"); printf("\tline[%d]: %s\n", flines, line); return -1; } /* Parse csv file, and construct the array of all leafs and subleafs */ static void parse_text(void) { FILE *file; char *filename, *line = NULL; size_t len = 0; int ret; if (show_raw) return; filename = user_csv ? user_csv : def_csv; file = fopen(filename, "r"); if (!file) { /* Fallback to a csv in the same dir */ file = fopen("./cpuid.csv", "r"); } if (!file) { printf("Fail to open '%s'\n", filename); return; } while (1) { ret = getline(&line, &len, file); flines++; if (ret > 0) parse_line(line); if (feof(file)) break; } fclose(file); } /* Decode every eax/ebx/ecx/edx */ static void decode_bits(u32 value, struct reg_desc *rdesc, enum cpuid_reg reg) { struct bits_desc *bdesc; int start, end, i; u32 mask; if (!rdesc->nr) { if (show_details) printf("\t %s: 0x%08x\n", reg_names[reg], value); return; } for (i = 0; i < rdesc->nr; i++) { bdesc = &rdesc->descs[i]; start = bdesc->start; end = bdesc->end; if (start == end) { /* single bit flag */ if (value & (1 << start)) printf("\t%-20s %s%s\n", bdesc->simp, show_details ? "-" : "", show_details ? bdesc->detail : "" ); } else { /* bit fields */ if (show_flags_only) continue; mask = ((u64)1 << (end - start + 1)) - 1; printf("\t%-20s\t: 0x%-8x\t%s%s\n", bdesc->simp, (value >> start) & mask, show_details ? "-" : "", show_details ? bdesc->detail : "" ); } } } static void show_leaf(struct subleaf *leaf) { if (!leaf) return; if (show_raw) { leaf_print_raw(leaf); } else { if (show_details) printf("CPUID_0x%x_ECX[0x%x]:\n", leaf->index, leaf->sub); } decode_bits(leaf->eax, &leaf->info[R_EAX], R_EAX); decode_bits(leaf->ebx, &leaf->info[R_EBX], R_EBX); decode_bits(leaf->ecx, &leaf->info[R_ECX], R_ECX); decode_bits(leaf->edx, &leaf->info[R_EDX], R_EDX); if (!show_raw && show_details) printf("\n"); } static void show_func(struct cpuid_func *func) { int i; if (!func) return; for (i = 0; i < func->nr; i++) show_leaf(&func->leafs[i]); } static void show_range(struct cpuid_range *range) { int i; for (i = 0; i < range->nr; i++) show_func(&range->funcs[i]); } static inline struct cpuid_func *index_to_func(u32 index) { struct cpuid_range *range; u32 func_idx; range = (index & 0x80000000) ? leafs_ext : leafs_basic; func_idx = index & 0xffff; if ((func_idx + 1) > (u32)range->nr) { printf("ERR: invalid input index (0x%x)\n", index); return NULL; } return &range->funcs[func_idx]; } static void show_info(void) { struct cpuid_func *func; if (show_raw) { /* Show all of the raw output of 'cpuid' instr */ raw_dump_range(leafs_basic); raw_dump_range(leafs_ext); return; } if (user_index != 0xFFFFFFFF) { /* Only show specific leaf/subleaf info */ func = index_to_func(user_index); if (!func) return; /* Dump the raw data also */ show_raw = true; if (user_sub != 0xFFFFFFFF) { if (user_sub + 1 <= (u32)func->nr) { show_leaf(&func->leafs[user_sub]); return; } printf("ERR: invalid input subleaf (0x%x)\n", user_sub); } show_func(func); return; } printf("CPU features:\n=============\n\n"); show_range(leafs_basic); show_range(leafs_ext); } static void setup_platform_cpuid(void) { u32 eax, ebx, ecx, edx; /* Check vendor */ eax = ebx = ecx = edx = 0; cpuid(&eax, &ebx, &ecx, &edx); /* "htuA" */ if (ebx == 0x68747541) is_amd = true; /* Setup leafs for the basic and extended range */ leafs_basic = setup_cpuid_range(0x0); leafs_ext = setup_cpuid_range(0x80000000); } static void usage(void) { printf("kcpuid [-abdfhr] [-l leaf] [-s subleaf]\n" "\t-a|--all Show both bit flags and complex bit fields info\n" "\t-b|--bitflags Show boolean flags only\n" "\t-d|--detail Show details of the flag/fields (default)\n" "\t-f|--flags Specify the cpuid csv file\n" "\t-h|--help Show usage info\n" "\t-l|--leaf=index Specify the leaf you want to check\n" "\t-r|--raw Show raw cpuid data\n" "\t-s|--subleaf=sub Specify the subleaf you want to check\n" ); } static struct option opts[] = { { "all", no_argument, NULL, 'a' }, /* show both bit flags and fields */ { "bitflags", no_argument, NULL, 'b' }, /* only show bit flags, default on */ { "detail", no_argument, NULL, 'd' }, /* show detail descriptions */ { "file", required_argument, NULL, 'f' }, /* use user's cpuid file */ { "help", no_argument, NULL, 'h'}, /* show usage */ { "leaf", required_argument, NULL, 'l'}, /* only check a specific leaf */ { "raw", no_argument, NULL, 'r'}, /* show raw CPUID leaf data */ { "subleaf", required_argument, NULL, 's'}, /* check a specific subleaf */ { NULL, 0, NULL, 0 } }; static int parse_options(int argc, char *argv[]) { int c; while ((c = getopt_long(argc, argv, "abdf:hl:rs:", opts, NULL)) != -1) switch (c) { case 'a': show_flags_only = false; break; case 'b': show_flags_only = true; break; case 'd': show_details = true; break; case 'f': user_csv = optarg; break; case 'h': usage(); exit(1); break; case 'l': /* main leaf */ user_index = strtoul(optarg, NULL, 0); break; case 'r': show_raw = true; break; case 's': /* subleaf */ user_sub = strtoul(optarg, NULL, 0); break; default: printf("%s: Invalid option '%c'\n", argv[0], optopt); return -1; } return 0; } /* * Do 4 things in turn: * 1. Parse user options * 2. Parse and store all the CPUID leaf data supported on this platform * 2. Parse the csv file, while skipping leafs which are not available * on this platform * 3. Print leafs info based on user options */ int main(int argc, char *argv[]) { if (parse_options(argc, argv)) return -1; /* Setup the cpuid leafs of current platform */ setup_platform_cpuid(); /* Read and parse the 'cpuid.csv' */ parse_text(); show_info(); return 0; }