root/tools/power/cpupower/utils/helpers/cpuid.c
// SPDX-License-Identifier: GPL-2.0
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>

#include "helpers/helpers.h"

static const char *cpu_vendor_table[X86_VENDOR_MAX] = {
        "Unknown", "GenuineIntel", "AuthenticAMD", "HygonGenuine",
};

#if defined(__i386__) || defined(__x86_64__)

/* from gcc */
#include <cpuid.h>

/*
 * CPUID functions returning a single datum
 *
 * Define unsigned int cpuid_e[abcd]x(unsigned int op)
 */
#define cpuid_func(reg)                                 \
        unsigned int cpuid_##reg(unsigned int op)       \
        {                                               \
        unsigned int eax, ebx, ecx, edx;                \
        __cpuid(op, eax, ebx, ecx, edx);                \
        return reg;                                     \
        }
cpuid_func(eax);
cpuid_func(ebx);
cpuid_func(ecx);
cpuid_func(edx);

#endif /* defined(__i386__) || defined(__x86_64__) */

/* get_cpu_info
 *
 * Extract CPU vendor, family, model, stepping info from /proc/cpuinfo
 *
 * Returns 0 on success or a negativ error code
 *
 * TBD: Should there be a cpuid alternative for this if /proc is not mounted?
 */
int get_cpu_info(struct cpupower_cpu_info *cpu_info)
{
        FILE *fp;
        char value[64];
        unsigned int proc, x;
        unsigned int unknown = 0xffffff;
        unsigned int cpuid_level, ext_cpuid_level;

        int ret = -EINVAL;

        cpu_info->vendor                = X86_VENDOR_UNKNOWN;
        cpu_info->family                = unknown;
        cpu_info->model                 = unknown;
        cpu_info->stepping              = unknown;
        cpu_info->caps                  = 0;

        fp = fopen("/proc/cpuinfo", "r");
        if (!fp)
                return -EIO;

        while (!feof(fp)) {
                if (!fgets(value, 64, fp))
                        continue;
                value[63 - 1] = '\0';

                if (!strncmp(value, "processor\t: ", 12))
                        sscanf(value, "processor\t: %u", &proc);

                if (proc != (unsigned int)base_cpu)
                        continue;

                /* Get CPU vendor */
                if (!strncmp(value, "vendor_id", 9)) {
                        for (x = 1; x < X86_VENDOR_MAX; x++) {
                                if (strstr(value, cpu_vendor_table[x]))
                                        cpu_info->vendor = x;
                        }
                /* Get CPU family, etc. */
                } else if (!strncmp(value, "cpu family\t: ", 13)) {
                        sscanf(value, "cpu family\t: %u",
                               &cpu_info->family);
                } else if (!strncmp(value, "model\t\t: ", 9)) {
                        sscanf(value, "model\t\t: %u",
                               &cpu_info->model);
                } else if (!strncmp(value, "stepping\t: ", 10)) {
                        sscanf(value, "stepping\t: %u",
                               &cpu_info->stepping);

                        /* Exit -> all values must have been set */
                        if (cpu_info->vendor == X86_VENDOR_UNKNOWN ||
                            cpu_info->family == unknown ||
                            cpu_info->model == unknown ||
                            cpu_info->stepping == unknown) {
                                ret = -EINVAL;
                                goto out;
                        }

                        ret = 0;
                        goto out;
                }
        }
        ret = -ENODEV;
out:
        fclose(fp);
        /* Get some useful CPU capabilities from cpuid */
        if (cpu_info->vendor != X86_VENDOR_AMD &&
            cpu_info->vendor != X86_VENDOR_HYGON &&
            cpu_info->vendor != X86_VENDOR_INTEL)
                return ret;

        cpuid_level     = cpuid_eax(0);
        ext_cpuid_level = cpuid_eax(0x80000000);

        /* Invariant TSC */
        if (ext_cpuid_level >= 0x80000007 &&
            (cpuid_edx(0x80000007) & (1 << 8)))
                cpu_info->caps |= CPUPOWER_CAP_INV_TSC;

        /* Aperf/Mperf registers support */
        if (cpuid_level >= 6 && (cpuid_ecx(6) & 0x1))
                cpu_info->caps |= CPUPOWER_CAP_APERF;

        /* AMD or Hygon Boost state enable/disable register */
        if (cpu_info->vendor == X86_VENDOR_AMD ||
            cpu_info->vendor == X86_VENDOR_HYGON) {
                if (ext_cpuid_level >= 0x80000007) {
                        if (cpuid_edx(0x80000007) & (1 << 9)) {
                                cpu_info->caps |= CPUPOWER_CAP_AMD_CPB;

                                if (cpu_info->family >= 0x17)
                                        cpu_info->caps |= CPUPOWER_CAP_AMD_CPB_MSR;
                        }

                        if ((cpuid_edx(0x80000007) & (1 << 7)) &&
                            cpu_info->family != 0x14) {
                                /* HW pstate was not implemented in family 0x14 */
                                cpu_info->caps |= CPUPOWER_CAP_AMD_HW_PSTATE;

                                if (cpu_info->family >= 0x17)
                                        cpu_info->caps |= CPUPOWER_CAP_AMD_PSTATEDEF;
                        }
                }

                if (ext_cpuid_level >= 0x80000008 &&
                    cpuid_ebx(0x80000008) & (1 << 4))
                        cpu_info->caps |= CPUPOWER_CAP_AMD_RDPRU;

                if (cpupower_amd_pstate_enabled()) {
                        cpu_info->caps |= CPUPOWER_CAP_AMD_PSTATE;

                        /*
                         * If AMD P-State is enabled, the firmware will treat
                         * AMD P-State function as high priority.
                         */
                        cpu_info->caps &= ~CPUPOWER_CAP_AMD_CPB;
                        cpu_info->caps &= ~CPUPOWER_CAP_AMD_CPB_MSR;
                        cpu_info->caps &= ~CPUPOWER_CAP_AMD_HW_PSTATE;
                        cpu_info->caps &= ~CPUPOWER_CAP_AMD_PSTATEDEF;
                }
        }

        if (cpu_info->vendor == X86_VENDOR_INTEL) {
                if (cpuid_level >= 6 &&
                    (cpuid_eax(6) & (1 << 1)))
                        cpu_info->caps |= CPUPOWER_CAP_INTEL_IDA;
        }

        if (cpu_info->vendor == X86_VENDOR_INTEL) {
                /* Intel's perf-bias MSR support */
                if (cpuid_level >= 6 && (cpuid_ecx(6) & (1 << 3)))
                        cpu_info->caps |= CPUPOWER_CAP_PERF_BIAS;

                /* Intel's Turbo Ratio Limit support */
                if (cpu_info->family == 6) {
                        switch (cpu_info->model) {
                        case 0x1A:      /* Core i7, Xeon 5500 series
                                         * Bloomfield, Gainstown NHM-EP
                                         */
                        case 0x1E:      /* Core i7 and i5 Processor
                                         * Clarksfield, Lynnfield, Jasper Forest
                                         */
                        case 0x1F:      /* Core i7 and i5 Processor - Nehalem */
                        case 0x25:      /* Westmere Client
                                         * Clarkdale, Arrandale
                                         */
                        case 0x2C:      /* Westmere EP - Gulftown */
                                cpu_info->caps |= CPUPOWER_CAP_HAS_TURBO_RATIO;
                                break;
                        case 0x2A:      /* SNB */
                        case 0x2D:      /* SNB Xeon */
                        case 0x3A:      /* IVB */
                        case 0x3E:      /* IVB Xeon */
                                cpu_info->caps |= CPUPOWER_CAP_HAS_TURBO_RATIO;
                                cpu_info->caps |= CPUPOWER_CAP_IS_SNB;
                                break;
                        case 0x2E:      /* Nehalem-EX Xeon - Beckton */
                        case 0x2F:      /* Westmere-EX Xeon - Eagleton */
                        default:
                                break;
                        }
                }
        }

        /*      printf("ID: %u - Extid: 0x%x - Caps: 0x%llx\n",
                cpuid_level, ext_cpuid_level, cpu_info->caps);
        */
        return ret;
}