// SPDX-License-Identifier: GPL-2.0 #define _GNU_SOURCE #include #include #include #include #include #define ARRAY_SIZE(x) (sizeof(x) / sizeof((x)[0])) typedef unsigned int u32; typedef unsigned long long u64; char *def_csv = "/usr/share/hwdata/cpuid.csv"; char *user_csv; struct bits_desc { int start, end; /* start and end bits */ int value; /* 0 or 1 for 1-bit flag */ char simp[32]; char detail[256]; }; /* Descriptor info for eax/ebx/ecx/edx */ struct reg_desc { int nr; /* number of valid entries */ struct bits_desc descs[32]; }; enum { R_EAX = 0, R_EBX, R_ECX, R_EDX, NR_REGS }; struct subleaf { u32 index; u32 sub; u32 eax, ebx, ecx, edx; struct reg_desc info[NR_REGS]; /* eax, ebx, ecx, edx */ }; /* cpuid_func represents 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 leafs in this range */ struct cpuid_func *funcs; /* Number of valid leafs */ int nr; bool is_ext; }; /* * basic: basic functions started from 0 * ext: extended functions started from 0x80000000 */ struct cpuid_range *leafs_basic, *leafs_ext; static int num_leafs; static bool is_amd; static bool show_details; static bool show_all = true; static bool show_raw; static bool show_flags_only; 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; } if (f == 0x4 || f == 0xf || f == 0x10 || f == 0x14) return true; 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); } } static void 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; /* * 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(struct subleaf)); 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; } static void raw_dump_range(struct cpuid_range *range) { u32 f; int i; printf("\n%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; if (!func->nr) { printf("0x%08x: ...\n", f); } else { for (i = 0; i < func->nr; i++) leaf_print_raw(&func->leafs[i]); } } } 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; 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); cpuid_store(range, f, subleaf, eax, ebx, ecx, edx); num_leafs++; if (!has_subleafs(f)) continue; for (subleaf = 1; subleaf < 64; subleaf++) { eax = f; ecx = subleaf; cpuid(&eax, &ebx, &ecx, &edx); /* is subleaf valid? */ if (eax == 0 && ebx == 0 && ecx == 0 && edx == 0) continue; cpuid_store(range, f, subleaf, eax, ebx, ecx, edx); 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; printf("line: %s\n", line); /* 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; printf("token[%d]: %s\n", i, tokens[i]); } tokens[5] = strtok(str, "\n"); printf("token[5]: %s\n", tokens[5]); /* index/main-leaf */ index = strtoull(tokens[0], NULL, 0); if (index & 0x80000000) range = leafs_ext; else range = leafs_basic; index &= 0x7FFFFFFF; if ((int)index > range->nr) { printf("ERR: invalid index[0x%x] nr:%d\n", index, range->nr); return -1; } func = &range->funcs[index]; /* subleaf */ sub = strtoul(tokens[1], NULL, 0); if ((int)sub > func->nr) { printf("ERR: invalid subleaf[%d]\n", sub); 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); printf("end: %s, start:%s [%d:%d]\n", end, start, bdesc->end, bdesc->start); 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 text file, and construct the array of all CPUID leafs and subleafs */ static void parse_text(void) { FILE *file; char *filename, *line = NULL; size_t len = 0; int ret; filename = def_csv; if (show_raw) return; if (user_csv) filename = user_csv; //filename = user_csv ? user_csv : def_csv; file = fopen(filename, "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); } /* parse every eax/ebx/ecx/edx */ static void decode_bits(u32 value, struct reg_desc *rdesc) { struct bits_desc *bdesc; int start, end, i; u32 mask; 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; decode_bits(leaf->eax, &leaf->info[R_EAX]); decode_bits(leaf->ebx, &leaf->info[R_EBX]); decode_bits(leaf->ecx, &leaf->info[R_ECX]); decode_bits(leaf->edx, &leaf->info[R_EDX]); } 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; range = (index & 0x80000000) ? leafs_ext : leafs_basic; index &= 0x7FFFFFFF; if (((index & 0xFFFF) + 1) > (u32)range->nr) { printf("ERR: invalid input index (0x%x)\n", index); return NULL; } return &range->funcs[index]; } static void show_info(void) { struct cpuid_func *func; if (show_raw) { /* Show all of the raw output data of running cpuid */ raw_dump_range(leafs_basic); raw_dump_range(leafs_ext); return; } if (show_all) { show_range(leafs_basic); show_range(leafs_ext); return; } /* Show specific leaf/subleaf info */ func = index_to_func(user_index); if (!func) return; if (user_sub != 0xFFFFFFFF) { if (user_sub + 1 <= (u32)func->nr) { show_leaf(&func->leafs[user_sub]); return; } printf("ERR: invalid input index (0x%x)\n", user_sub); } show_func(func); } 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 by getting the base and extended range */ leafs_basic = setup_cpuid_range(0x0); leafs_ext = setup_cpuid_range(0x80000000); printf("This platform has %d CPUID leafs and subleafs.\n\n", num_leafs); } static void usage(void) { printf(" usage: kcpuid [-abdfhr] [-l leaf] [-s subleaf]\n" "\t-a|--all Show info of all CPUID leafs and subleafs(default on)\n" "\t-b|--bitflags Show boolean flags only\n" "\t-d|--detail Show details of the flag/fields\n" "\t-f|--flags Show boolean flags only\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" "\n" ); } struct option opts[] = { { "all", no_argument, NULL, 'a' }, /* show all leafs */ { "bitflags", no_argument, NULL, 'b' }, /* only show bit flags */ { "detail", no_argument, NULL, 'd' }, /* show detail descriptions, default no */ { "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_all = true; 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': user_index = strtoul(optarg, NULL, 0); show_all = false; break; case 'r': show_raw = true; break; case 's': /* parse_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 text file according, skip leafs which is not available * on this platform * 3. Print leafs info based on uers 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; }