162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0
262306a36Sopenharmony_ci#include <stdio.h>
362306a36Sopenharmony_ci#include <errno.h>
462306a36Sopenharmony_ci#include <string.h>
562306a36Sopenharmony_ci#include <unistd.h>
662306a36Sopenharmony_ci#include <stdlib.h>
762306a36Sopenharmony_ci
862306a36Sopenharmony_ci#include "helpers/helpers.h"
962306a36Sopenharmony_ci
1062306a36Sopenharmony_cistatic const char *cpu_vendor_table[X86_VENDOR_MAX] = {
1162306a36Sopenharmony_ci	"Unknown", "GenuineIntel", "AuthenticAMD", "HygonGenuine",
1262306a36Sopenharmony_ci};
1362306a36Sopenharmony_ci
1462306a36Sopenharmony_ci#if defined(__i386__) || defined(__x86_64__)
1562306a36Sopenharmony_ci
1662306a36Sopenharmony_ci/* from gcc */
1762306a36Sopenharmony_ci#include <cpuid.h>
1862306a36Sopenharmony_ci
1962306a36Sopenharmony_ci/*
2062306a36Sopenharmony_ci * CPUID functions returning a single datum
2162306a36Sopenharmony_ci *
2262306a36Sopenharmony_ci * Define unsigned int cpuid_e[abcd]x(unsigned int op)
2362306a36Sopenharmony_ci */
2462306a36Sopenharmony_ci#define cpuid_func(reg)					\
2562306a36Sopenharmony_ci	unsigned int cpuid_##reg(unsigned int op)	\
2662306a36Sopenharmony_ci	{						\
2762306a36Sopenharmony_ci	unsigned int eax, ebx, ecx, edx;		\
2862306a36Sopenharmony_ci	__cpuid(op, eax, ebx, ecx, edx);		\
2962306a36Sopenharmony_ci	return reg;					\
3062306a36Sopenharmony_ci	}
3162306a36Sopenharmony_cicpuid_func(eax);
3262306a36Sopenharmony_cicpuid_func(ebx);
3362306a36Sopenharmony_cicpuid_func(ecx);
3462306a36Sopenharmony_cicpuid_func(edx);
3562306a36Sopenharmony_ci
3662306a36Sopenharmony_ci#endif /* defined(__i386__) || defined(__x86_64__) */
3762306a36Sopenharmony_ci
3862306a36Sopenharmony_ci/* get_cpu_info
3962306a36Sopenharmony_ci *
4062306a36Sopenharmony_ci * Extract CPU vendor, family, model, stepping info from /proc/cpuinfo
4162306a36Sopenharmony_ci *
4262306a36Sopenharmony_ci * Returns 0 on success or a negativ error code
4362306a36Sopenharmony_ci *
4462306a36Sopenharmony_ci * TBD: Should there be a cpuid alternative for this if /proc is not mounted?
4562306a36Sopenharmony_ci */
4662306a36Sopenharmony_ciint get_cpu_info(struct cpupower_cpu_info *cpu_info)
4762306a36Sopenharmony_ci{
4862306a36Sopenharmony_ci	FILE *fp;
4962306a36Sopenharmony_ci	char value[64];
5062306a36Sopenharmony_ci	unsigned int proc, x;
5162306a36Sopenharmony_ci	unsigned int unknown = 0xffffff;
5262306a36Sopenharmony_ci	unsigned int cpuid_level, ext_cpuid_level;
5362306a36Sopenharmony_ci
5462306a36Sopenharmony_ci	int ret = -EINVAL;
5562306a36Sopenharmony_ci
5662306a36Sopenharmony_ci	cpu_info->vendor		= X86_VENDOR_UNKNOWN;
5762306a36Sopenharmony_ci	cpu_info->family		= unknown;
5862306a36Sopenharmony_ci	cpu_info->model			= unknown;
5962306a36Sopenharmony_ci	cpu_info->stepping		= unknown;
6062306a36Sopenharmony_ci	cpu_info->caps			= 0;
6162306a36Sopenharmony_ci
6262306a36Sopenharmony_ci	fp = fopen("/proc/cpuinfo", "r");
6362306a36Sopenharmony_ci	if (!fp)
6462306a36Sopenharmony_ci		return -EIO;
6562306a36Sopenharmony_ci
6662306a36Sopenharmony_ci	while (!feof(fp)) {
6762306a36Sopenharmony_ci		if (!fgets(value, 64, fp))
6862306a36Sopenharmony_ci			continue;
6962306a36Sopenharmony_ci		value[63 - 1] = '\0';
7062306a36Sopenharmony_ci
7162306a36Sopenharmony_ci		if (!strncmp(value, "processor\t: ", 12))
7262306a36Sopenharmony_ci			sscanf(value, "processor\t: %u", &proc);
7362306a36Sopenharmony_ci
7462306a36Sopenharmony_ci		if (proc != (unsigned int)base_cpu)
7562306a36Sopenharmony_ci			continue;
7662306a36Sopenharmony_ci
7762306a36Sopenharmony_ci		/* Get CPU vendor */
7862306a36Sopenharmony_ci		if (!strncmp(value, "vendor_id", 9)) {
7962306a36Sopenharmony_ci			for (x = 1; x < X86_VENDOR_MAX; x++) {
8062306a36Sopenharmony_ci				if (strstr(value, cpu_vendor_table[x]))
8162306a36Sopenharmony_ci					cpu_info->vendor = x;
8262306a36Sopenharmony_ci			}
8362306a36Sopenharmony_ci		/* Get CPU family, etc. */
8462306a36Sopenharmony_ci		} else if (!strncmp(value, "cpu family\t: ", 13)) {
8562306a36Sopenharmony_ci			sscanf(value, "cpu family\t: %u",
8662306a36Sopenharmony_ci			       &cpu_info->family);
8762306a36Sopenharmony_ci		} else if (!strncmp(value, "model\t\t: ", 9)) {
8862306a36Sopenharmony_ci			sscanf(value, "model\t\t: %u",
8962306a36Sopenharmony_ci			       &cpu_info->model);
9062306a36Sopenharmony_ci		} else if (!strncmp(value, "stepping\t: ", 10)) {
9162306a36Sopenharmony_ci			sscanf(value, "stepping\t: %u",
9262306a36Sopenharmony_ci			       &cpu_info->stepping);
9362306a36Sopenharmony_ci
9462306a36Sopenharmony_ci			/* Exit -> all values must have been set */
9562306a36Sopenharmony_ci			if (cpu_info->vendor == X86_VENDOR_UNKNOWN ||
9662306a36Sopenharmony_ci			    cpu_info->family == unknown ||
9762306a36Sopenharmony_ci			    cpu_info->model == unknown ||
9862306a36Sopenharmony_ci			    cpu_info->stepping == unknown) {
9962306a36Sopenharmony_ci				ret = -EINVAL;
10062306a36Sopenharmony_ci				goto out;
10162306a36Sopenharmony_ci			}
10262306a36Sopenharmony_ci
10362306a36Sopenharmony_ci			ret = 0;
10462306a36Sopenharmony_ci			goto out;
10562306a36Sopenharmony_ci		}
10662306a36Sopenharmony_ci	}
10762306a36Sopenharmony_ci	ret = -ENODEV;
10862306a36Sopenharmony_ciout:
10962306a36Sopenharmony_ci	fclose(fp);
11062306a36Sopenharmony_ci	/* Get some useful CPU capabilities from cpuid */
11162306a36Sopenharmony_ci	if (cpu_info->vendor != X86_VENDOR_AMD &&
11262306a36Sopenharmony_ci	    cpu_info->vendor != X86_VENDOR_HYGON &&
11362306a36Sopenharmony_ci	    cpu_info->vendor != X86_VENDOR_INTEL)
11462306a36Sopenharmony_ci		return ret;
11562306a36Sopenharmony_ci
11662306a36Sopenharmony_ci	cpuid_level	= cpuid_eax(0);
11762306a36Sopenharmony_ci	ext_cpuid_level	= cpuid_eax(0x80000000);
11862306a36Sopenharmony_ci
11962306a36Sopenharmony_ci	/* Invariant TSC */
12062306a36Sopenharmony_ci	if (ext_cpuid_level >= 0x80000007 &&
12162306a36Sopenharmony_ci	    (cpuid_edx(0x80000007) & (1 << 8)))
12262306a36Sopenharmony_ci		cpu_info->caps |= CPUPOWER_CAP_INV_TSC;
12362306a36Sopenharmony_ci
12462306a36Sopenharmony_ci	/* Aperf/Mperf registers support */
12562306a36Sopenharmony_ci	if (cpuid_level >= 6 && (cpuid_ecx(6) & 0x1))
12662306a36Sopenharmony_ci		cpu_info->caps |= CPUPOWER_CAP_APERF;
12762306a36Sopenharmony_ci
12862306a36Sopenharmony_ci	/* AMD or Hygon Boost state enable/disable register */
12962306a36Sopenharmony_ci	if (cpu_info->vendor == X86_VENDOR_AMD ||
13062306a36Sopenharmony_ci	    cpu_info->vendor == X86_VENDOR_HYGON) {
13162306a36Sopenharmony_ci		if (ext_cpuid_level >= 0x80000007) {
13262306a36Sopenharmony_ci			if (cpuid_edx(0x80000007) & (1 << 9)) {
13362306a36Sopenharmony_ci				cpu_info->caps |= CPUPOWER_CAP_AMD_CPB;
13462306a36Sopenharmony_ci
13562306a36Sopenharmony_ci				if (cpu_info->family >= 0x17)
13662306a36Sopenharmony_ci					cpu_info->caps |= CPUPOWER_CAP_AMD_CPB_MSR;
13762306a36Sopenharmony_ci			}
13862306a36Sopenharmony_ci
13962306a36Sopenharmony_ci			if ((cpuid_edx(0x80000007) & (1 << 7)) &&
14062306a36Sopenharmony_ci			    cpu_info->family != 0x14) {
14162306a36Sopenharmony_ci				/* HW pstate was not implemented in family 0x14 */
14262306a36Sopenharmony_ci				cpu_info->caps |= CPUPOWER_CAP_AMD_HW_PSTATE;
14362306a36Sopenharmony_ci
14462306a36Sopenharmony_ci				if (cpu_info->family >= 0x17)
14562306a36Sopenharmony_ci					cpu_info->caps |= CPUPOWER_CAP_AMD_PSTATEDEF;
14662306a36Sopenharmony_ci			}
14762306a36Sopenharmony_ci		}
14862306a36Sopenharmony_ci
14962306a36Sopenharmony_ci		if (ext_cpuid_level >= 0x80000008 &&
15062306a36Sopenharmony_ci		    cpuid_ebx(0x80000008) & (1 << 4))
15162306a36Sopenharmony_ci			cpu_info->caps |= CPUPOWER_CAP_AMD_RDPRU;
15262306a36Sopenharmony_ci
15362306a36Sopenharmony_ci		if (cpupower_amd_pstate_enabled()) {
15462306a36Sopenharmony_ci			cpu_info->caps |= CPUPOWER_CAP_AMD_PSTATE;
15562306a36Sopenharmony_ci
15662306a36Sopenharmony_ci			/*
15762306a36Sopenharmony_ci			 * If AMD P-State is enabled, the firmware will treat
15862306a36Sopenharmony_ci			 * AMD P-State function as high priority.
15962306a36Sopenharmony_ci			 */
16062306a36Sopenharmony_ci			cpu_info->caps &= ~CPUPOWER_CAP_AMD_CPB;
16162306a36Sopenharmony_ci			cpu_info->caps &= ~CPUPOWER_CAP_AMD_CPB_MSR;
16262306a36Sopenharmony_ci			cpu_info->caps &= ~CPUPOWER_CAP_AMD_HW_PSTATE;
16362306a36Sopenharmony_ci			cpu_info->caps &= ~CPUPOWER_CAP_AMD_PSTATEDEF;
16462306a36Sopenharmony_ci		}
16562306a36Sopenharmony_ci	}
16662306a36Sopenharmony_ci
16762306a36Sopenharmony_ci	if (cpu_info->vendor == X86_VENDOR_INTEL) {
16862306a36Sopenharmony_ci		if (cpuid_level >= 6 &&
16962306a36Sopenharmony_ci		    (cpuid_eax(6) & (1 << 1)))
17062306a36Sopenharmony_ci			cpu_info->caps |= CPUPOWER_CAP_INTEL_IDA;
17162306a36Sopenharmony_ci	}
17262306a36Sopenharmony_ci
17362306a36Sopenharmony_ci	if (cpu_info->vendor == X86_VENDOR_INTEL) {
17462306a36Sopenharmony_ci		/* Intel's perf-bias MSR support */
17562306a36Sopenharmony_ci		if (cpuid_level >= 6 && (cpuid_ecx(6) & (1 << 3)))
17662306a36Sopenharmony_ci			cpu_info->caps |= CPUPOWER_CAP_PERF_BIAS;
17762306a36Sopenharmony_ci
17862306a36Sopenharmony_ci		/* Intel's Turbo Ratio Limit support */
17962306a36Sopenharmony_ci		if (cpu_info->family == 6) {
18062306a36Sopenharmony_ci			switch (cpu_info->model) {
18162306a36Sopenharmony_ci			case 0x1A:	/* Core i7, Xeon 5500 series
18262306a36Sopenharmony_ci					 * Bloomfield, Gainstown NHM-EP
18362306a36Sopenharmony_ci					 */
18462306a36Sopenharmony_ci			case 0x1E:	/* Core i7 and i5 Processor
18562306a36Sopenharmony_ci					 * Clarksfield, Lynnfield, Jasper Forest
18662306a36Sopenharmony_ci					 */
18762306a36Sopenharmony_ci			case 0x1F:	/* Core i7 and i5 Processor - Nehalem */
18862306a36Sopenharmony_ci			case 0x25:	/* Westmere Client
18962306a36Sopenharmony_ci					 * Clarkdale, Arrandale
19062306a36Sopenharmony_ci					 */
19162306a36Sopenharmony_ci			case 0x2C:	/* Westmere EP - Gulftown */
19262306a36Sopenharmony_ci				cpu_info->caps |= CPUPOWER_CAP_HAS_TURBO_RATIO;
19362306a36Sopenharmony_ci				break;
19462306a36Sopenharmony_ci			case 0x2A:	/* SNB */
19562306a36Sopenharmony_ci			case 0x2D:	/* SNB Xeon */
19662306a36Sopenharmony_ci			case 0x3A:	/* IVB */
19762306a36Sopenharmony_ci			case 0x3E:	/* IVB Xeon */
19862306a36Sopenharmony_ci				cpu_info->caps |= CPUPOWER_CAP_HAS_TURBO_RATIO;
19962306a36Sopenharmony_ci				cpu_info->caps |= CPUPOWER_CAP_IS_SNB;
20062306a36Sopenharmony_ci				break;
20162306a36Sopenharmony_ci			case 0x2E:	/* Nehalem-EX Xeon - Beckton */
20262306a36Sopenharmony_ci			case 0x2F:	/* Westmere-EX Xeon - Eagleton */
20362306a36Sopenharmony_ci			default:
20462306a36Sopenharmony_ci				break;
20562306a36Sopenharmony_ci			}
20662306a36Sopenharmony_ci		}
20762306a36Sopenharmony_ci	}
20862306a36Sopenharmony_ci
20962306a36Sopenharmony_ci	/*	printf("ID: %u - Extid: 0x%x - Caps: 0x%llx\n",
21062306a36Sopenharmony_ci		cpuid_level, ext_cpuid_level, cpu_info->caps);
21162306a36Sopenharmony_ci	*/
21262306a36Sopenharmony_ci	return ret;
21362306a36Sopenharmony_ci}
214