root/tools/arch/x86/kcpuid/kcpuid.c
// SPDX-License-Identifier: GPL-2.0
#define _GNU_SOURCE

#include <cpuid.h>
#include <err.h>
#include <getopt.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define ARRAY_SIZE(x)   (sizeof(x) / sizeof((x)[0]))
#define min(a, b)       (((a) < (b)) ? (a) : (b))
#define __noreturn      __attribute__((__noreturn__))

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 output[NR_REGS];
        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;
};

enum range_index {
        RANGE_STD = 0,                  /* Standard */
        RANGE_EXT = 0x80000000,         /* Extended */
        RANGE_TSM = 0x80860000,         /* Transmeta */
        RANGE_CTR = 0xc0000000,         /* Centaur/Zhaoxin */
};

#define CPUID_INDEX_MASK                0xffff0000
#define CPUID_FUNCTION_MASK             (~CPUID_INDEX_MASK)

struct cpuid_range {
        /* array of main leafs */
        struct cpuid_func *funcs;
        /* number of valid leafs */
        int nr;
        enum range_index index;
};

static struct cpuid_range ranges[] = {
        {       .index          = RANGE_STD,    },
        {       .index          = RANGE_EXT,    },
        {       .index          = RANGE_TSM,    },
        {       .index          = RANGE_CTR,    },
};

static char *range_to_str(struct cpuid_range *range)
{
        switch (range->index) {
        case RANGE_STD:         return "Standard";
        case RANGE_EXT:         return "Extended";
        case RANGE_TSM:         return "Transmeta";
        case RANGE_CTR:         return "Centaur";
        default:                return NULL;
        }
}

#define __for_each_cpuid_range(range, __condition)                              \
        for (unsigned int i = 0;                                                \
             i < ARRAY_SIZE(ranges) && ((range) = &ranges[i]) && (__condition); \
             i++)

#define for_each_valid_cpuid_range(range)       __for_each_cpuid_range(range, (range)->nr != 0)
#define for_each_cpuid_range(range)             __for_each_cpuid_range(range, true)

struct cpuid_range *index_to_cpuid_range(u32 index)
{
        u32 func_idx = index & CPUID_FUNCTION_MASK;
        u32 range_idx = index & CPUID_INDEX_MASK;
        struct cpuid_range *range;

        for_each_valid_cpuid_range(range) {
                if (range->index == range_idx && (u32)range->nr > func_idx)
                        return range;
        }

        return NULL;
}

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;

/*
 * Force using <cpuid.h> __cpuid_count() instead of __cpuid(). The
 * latter leaves ECX uninitialized, which can break CPUID queries.
 */

#define cpuid(leaf, a, b, c, d)                         \
        __cpuid_count(leaf, 0, a, b, c, d)

#define cpuid_count(leaf, subleaf, a, b, c, d)          \
        __cpuid_count(leaf, subleaf, a, b, c, d)

static inline bool has_subleafs(u32 f)
{
        u32 with_subleaves[] = {
                0x4,  0x7,  0xb,  0xd,  0xf,  0x10, 0x12,
                0x14, 0x17, 0x18, 0x1b, 0x1d, 0x1f, 0x23,
                0x8000001d, 0x80000020, 0x80000026,
        };

        for (unsigned i = 0; i < ARRAY_SIZE(with_subleaves); i++)
                if (f == with_subleaves[i])
                        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->output[0], leaf->output[1], leaf->output[2], leaf->output[3]);
        } else {
                printf("0x%08x: EAX=0x%08x, EBX=0x%08x, ECX=0x%08x, EDX=0x%08x\n", leaf->index,
                       leaf->output[0], leaf->output[1], leaf->output[2], leaf->output[3]);
        }
}

/* 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 & CPUID_FUNCTION_MASK];

        if (!func->leafs) {
                func->leafs = malloc(sizeof(struct subleaf));
                if (!func->leafs)
                        err(EXIT_FAILURE, NULL);

                func->nr = 1;
        } else {
                s = func->nr;
                func->leafs = realloc(func->leafs, (s + 1) * sizeof(*leaf));
                if (!func->leafs)
                        err(EXIT_FAILURE, NULL);

                func->nr++;
        }

        leaf = &func->leafs[s];

        leaf->index = f;
        leaf->sub = subleaf;
        leaf->output[R_EAX] = a;
        leaf->output[R_EBX] = b;
        leaf->output[R_ECX] = c;
        leaf->output[R_EDX] = d;

        return false;
}

static void raw_dump_range(struct cpuid_range *range)
{
        printf("%s Leafs :\n", range_to_str(range));
        printf("================\n");

        for (u32 f = 0; (int)f < range->nr; f++) {
                struct cpuid_func *func = &range->funcs[f];

                /* Skip leaf without valid items */
                if (!func->nr)
                        continue;

                /* First item is the main leaf, followed by all subleafs */
                for (int i = 0; i < func->nr; i++)
                        leaf_print_raw(&func->leafs[i]);
        }
}

#define MAX_SUBLEAF_NUM         64
#define MAX_RANGE_INDEX_OFFSET  0xff
void setup_cpuid_range(struct cpuid_range *range)
{
        u32 max_func, range_funcs_sz;
        u32 eax, ebx, ecx, edx;

        cpuid(range->index, max_func, ebx, ecx, edx);

        /*
         * If the CPUID range's maximum function value is garbage, then it
         * is not recognized by this CPU.  Set the range's number of valid
         * leaves to zero so that for_each_valid_cpu_range() can ignore it.
         */
        if (max_func < range->index || max_func > (range->index + MAX_RANGE_INDEX_OFFSET)) {
                range->nr = 0;
                return;
        }

        range->nr = (max_func & CPUID_FUNCTION_MASK) + 1;
        range_funcs_sz = range->nr * sizeof(struct cpuid_func);

        range->funcs = malloc(range_funcs_sz);
        if (!range->funcs)
                err(EXIT_FAILURE, NULL);

        memset(range->funcs, 0, range_funcs_sz);

        for (u32 f = range->index; f <= max_func; f++) {
                u32 max_subleaf = MAX_SUBLEAF_NUM;
                bool allzero;

                cpuid(f, eax, ebx, ecx, edx);

                allzero = cpuid_store(range, f, 0, eax, ebx, ecx, edx);
                if (allzero)
                        continue;

                if (!has_subleafs(f))
                        continue;

                /*
                 * Some can provide the exact number of subleafs,
                 * others have to be tried (0xf)
                 */
                if (f == 0x7 || f == 0x14 || f == 0x17 || f == 0x18 || f == 0x1d)
                        max_subleaf = min((eax & 0xff) + 1, max_subleaf);
                if (f == 0xb)
                        max_subleaf = 2;
                if (f == 0x1f)
                        max_subleaf = 6;
                if (f == 0x23)
                        max_subleaf = 4;
                if (f == 0x80000020)
                        max_subleaf = 4;
                if (f == 0x80000026)
                        max_subleaf = 5;

                for (u32 subleaf = 1; subleaf < max_subleaf; subleaf++) {
                        cpuid_count(f, subleaf, eax, ebx, ecx, edx);

                        allzero = cpuid_store(range, f, subleaf, eax, ebx, ecx, edx);
                        if (allzero)
                                continue;
                }

        }
}

/*
 * 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 void parse_line(char *line)
{
        char *str;
        struct cpuid_range *range;
        struct cpuid_func *func;
        struct subleaf *leaf;
        u32 index;
        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;
        u32 subleaf_start, subleaf_end;
        unsigned bit_start, bit_end;

        /* Skip comments and NULL line */
        if (line[0] == '#' || line[0] == '\n')
                return;

        strncpy(buffer, line, 511);
        buffer[511] = 0;
        str = buffer;
        for (int 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);

        /*
         * Skip line parsing if the index is not covered by known-valid
         * CPUID ranges on this CPU.
         */
        range = index_to_cpuid_range(index);
        if (!range)
                return;

        /* Skip line parsing if the index CPUID output is all zero */
        index &= CPUID_FUNCTION_MASK;
        func = &range->funcs[index];
        if (!func->nr)
                return;

        /* subleaf */
        buf = tokens[1];
        end = strtok(buf, ":");
        start = strtok(NULL, ":");
        subleaf_end = strtoul(end, NULL, 0);

        /* A subleaf range is given? */
        if (start) {
                subleaf_start = strtoul(start, NULL, 0);
                subleaf_end = min(subleaf_end, (u32)(func->nr - 1));
                if (subleaf_start > subleaf_end)
                        return;
        } else {
                subleaf_start = subleaf_end;
                if (subleaf_start > (u32)(func->nr - 1))
                        return;
        }

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

        /* bit flag or bits field */
        buf = tokens[3];
        end = strtok(buf, ":");
        start = strtok(NULL, ":");
        bit_end = strtoul(end, NULL, 0);
        bit_start = (start) ? strtoul(start, NULL, 0) : bit_end;

        for (u32 sub = subleaf_start; sub <= subleaf_end; sub++) {
                leaf = &func->leafs[sub];
                reg = &leaf->info[reg_index];
                bdesc = &reg->descs[reg->nr++];

                bdesc->end = bit_end;
                bdesc->start = bit_start;
                strcpy(bdesc->simp, strtok(tokens[4], " \t"));
                strcpy(bdesc->detail, tokens[5]);
        }
        return;

err_exit:
        warnx("Wrong line format:\n"
              "\tline[%d]: %s", flines, line);
}

/* 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)
                err(EXIT_FAILURE, "%s", filename);

        while (1) {
                ret = getline(&line, &len, file);
                flines++;
                if (ret > 0)
                        parse_line(line);

                if (feof(file))
                        break;
        }

        fclose(file);
}

static void show_reg(const struct reg_desc *rdesc, u32 value)
{
        const struct bits_desc *bdesc;
        int start, end;
        u32 mask;

        for (int 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%s\n",
                                        bdesc->simp,
                                        show_flags_only ? "" : "\t\t\t",
                                        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_reg_header(bool has_entries, u32 leaf, u32 subleaf, const char *reg_name)
{
        if (show_details && has_entries)
                printf("CPUID_0x%x_%s[0x%x]:\n", leaf, reg_name, subleaf);
}

static void show_leaf(struct subleaf *leaf)
{
        if (show_raw)
                leaf_print_raw(leaf);

        for (int i = R_EAX; i < NR_REGS; i++) {
                show_reg_header((leaf->info[i].nr > 0), leaf->index, leaf->sub, reg_names[i]);
                show_reg(&leaf->info[i], leaf->output[i]);
        }

        if (!show_raw && show_details)
                printf("\n");
}

static void show_func(struct cpuid_func *func)
{
        for (int i = 0; i < func->nr; i++)
                show_leaf(&func->leafs[i]);
}

static void show_range(struct cpuid_range *range)
{
        for (int i = 0; i < range->nr; i++)
                show_func(&range->funcs[i]);
}

static inline struct cpuid_func *index_to_func(u32 index)
{
        u32 func_idx = index & CPUID_FUNCTION_MASK;
        struct cpuid_range *range;

        range = index_to_cpuid_range(index);
        if (!range)
                return NULL;

        return &range->funcs[func_idx];
}

static void show_info(void)
{
        struct cpuid_range *range;
        struct cpuid_func *func;

        if (show_raw) {
                /* Show all of the raw output of 'cpuid' instr */
                for_each_valid_cpuid_range(range)
                        raw_dump_range(range);
                return;
        }

        if (user_index != 0xFFFFFFFF) {
                /* Only show specific leaf/subleaf info */
                func = index_to_func(user_index);
                if (!func)
                        errx(EXIT_FAILURE, "Invalid input leaf (0x%x)", user_index);

                /* Dump the raw data also */
                show_raw = true;

                if (user_sub != 0xFFFFFFFF) {
                        if (user_sub + 1 > (u32)func->nr) {
                                errx(EXIT_FAILURE, "Leaf 0x%x has no valid subleaf = 0x%x",
                                     user_index, user_sub);
                        }

                        show_leaf(&func->leafs[user_sub]);
                        return;
                }

                show_func(func);
                return;
        }

        printf("CPU features:\n=============\n\n");
        for_each_valid_cpuid_range(range)
                show_range(range);
}

static void __noreturn usage(int exit_code)
{
        errx(exit_code, "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"
        );
}

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 void 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_SUCCESS);
                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:
                        usage(EXIT_FAILURE);
                }
}

/*
 * 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[])
{
        struct cpuid_range *range;

        parse_options(argc, argv);

        /* Setup the cpuid leafs of current platform */
        for_each_cpuid_range(range)
                setup_cpuid_range(range);

        /* Read and parse the 'cpuid.csv' */
        parse_text();

        show_info();
        return 0;
}