201 lines
5.0 KiB
C
201 lines
5.0 KiB
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 (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;
|
|
}
|