blob: 72eb4359318032f939e06db0ff5279a52573013d [file] [log] [blame]
// 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 (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;
}