xref: /linux/tools/power/cpupower/utils/helpers/cpuid.c (revision 172cdcaefea5c297fdb3d20b7d5aff60ae4fbce6)
1 // SPDX-License-Identifier: GPL-2.0
2 #include <stdio.h>
3 #include <errno.h>
4 #include <string.h>
5 #include <unistd.h>
6 #include <stdlib.h>
7 
8 #include "helpers/helpers.h"
9 
10 static const char *cpu_vendor_table[X86_VENDOR_MAX] = {
11 	"Unknown", "GenuineIntel", "AuthenticAMD", "HygonGenuine",
12 };
13 
14 #if defined(__i386__) || defined(__x86_64__)
15 
16 /* from gcc */
17 #include <cpuid.h>
18 
19 /*
20  * CPUID functions returning a single datum
21  *
22  * Define unsigned int cpuid_e[abcd]x(unsigned int op)
23  */
24 #define cpuid_func(reg)					\
25 	unsigned int cpuid_##reg(unsigned int op)	\
26 	{						\
27 	unsigned int eax, ebx, ecx, edx;		\
28 	__cpuid(op, eax, ebx, ecx, edx);		\
29 	return reg;					\
30 	}
31 cpuid_func(eax);
32 cpuid_func(ebx);
33 cpuid_func(ecx);
34 cpuid_func(edx);
35 
36 #endif /* defined(__i386__) || defined(__x86_64__) */
37 
38 /* get_cpu_info
39  *
40  * Extract CPU vendor, family, model, stepping info from /proc/cpuinfo
41  *
42  * Returns 0 on success or a negativ error code
43  *
44  * TBD: Should there be a cpuid alternative for this if /proc is not mounted?
45  */
46 int get_cpu_info(struct cpupower_cpu_info *cpu_info)
47 {
48 	FILE *fp;
49 	char value[64];
50 	unsigned int proc, x;
51 	unsigned int unknown = 0xffffff;
52 	unsigned int cpuid_level, ext_cpuid_level;
53 
54 	int ret = -EINVAL;
55 
56 	cpu_info->vendor		= X86_VENDOR_UNKNOWN;
57 	cpu_info->family		= unknown;
58 	cpu_info->model			= unknown;
59 	cpu_info->stepping		= unknown;
60 	cpu_info->caps			= 0;
61 
62 	fp = fopen("/proc/cpuinfo", "r");
63 	if (!fp)
64 		return -EIO;
65 
66 	while (!feof(fp)) {
67 		if (!fgets(value, 64, fp))
68 			continue;
69 		value[63 - 1] = '\0';
70 
71 		if (!strncmp(value, "processor\t: ", 12))
72 			sscanf(value, "processor\t: %u", &proc);
73 
74 		if (proc != (unsigned int)base_cpu)
75 			continue;
76 
77 		/* Get CPU vendor */
78 		if (!strncmp(value, "vendor_id", 9)) {
79 			for (x = 1; x < X86_VENDOR_MAX; x++) {
80 				if (strstr(value, cpu_vendor_table[x]))
81 					cpu_info->vendor = x;
82 			}
83 		/* Get CPU family, etc. */
84 		} else if (!strncmp(value, "cpu family\t: ", 13)) {
85 			sscanf(value, "cpu family\t: %u",
86 			       &cpu_info->family);
87 		} else if (!strncmp(value, "model\t\t: ", 9)) {
88 			sscanf(value, "model\t\t: %u",
89 			       &cpu_info->model);
90 		} else if (!strncmp(value, "stepping\t: ", 10)) {
91 			sscanf(value, "stepping\t: %u",
92 			       &cpu_info->stepping);
93 
94 			/* Exit -> all values must have been set */
95 			if (cpu_info->vendor == X86_VENDOR_UNKNOWN ||
96 			    cpu_info->family == unknown ||
97 			    cpu_info->model == unknown ||
98 			    cpu_info->stepping == unknown) {
99 				ret = -EINVAL;
100 				goto out;
101 			}
102 
103 			ret = 0;
104 			goto out;
105 		}
106 	}
107 	ret = -ENODEV;
108 out:
109 	fclose(fp);
110 	/* Get some useful CPU capabilities from cpuid */
111 	if (cpu_info->vendor != X86_VENDOR_AMD &&
112 	    cpu_info->vendor != X86_VENDOR_HYGON &&
113 	    cpu_info->vendor != X86_VENDOR_INTEL)
114 		return ret;
115 
116 	cpuid_level	= cpuid_eax(0);
117 	ext_cpuid_level	= cpuid_eax(0x80000000);
118 
119 	/* Invariant TSC */
120 	if (ext_cpuid_level >= 0x80000007 &&
121 	    (cpuid_edx(0x80000007) & (1 << 8)))
122 		cpu_info->caps |= CPUPOWER_CAP_INV_TSC;
123 
124 	/* Aperf/Mperf registers support */
125 	if (cpuid_level >= 6 && (cpuid_ecx(6) & 0x1))
126 		cpu_info->caps |= CPUPOWER_CAP_APERF;
127 
128 	/* AMD or Hygon Boost state enable/disable register */
129 	if (cpu_info->vendor == X86_VENDOR_AMD ||
130 	    cpu_info->vendor == X86_VENDOR_HYGON) {
131 		if (ext_cpuid_level >= 0x80000007) {
132 			if (cpuid_edx(0x80000007) & (1 << 9)) {
133 				cpu_info->caps |= CPUPOWER_CAP_AMD_CPB;
134 
135 				if (cpu_info->family >= 0x17)
136 					cpu_info->caps |= CPUPOWER_CAP_AMD_CPB_MSR;
137 			}
138 
139 			if ((cpuid_edx(0x80000007) & (1 << 7)) &&
140 			    cpu_info->family != 0x14) {
141 				/* HW pstate was not implemented in family 0x14 */
142 				cpu_info->caps |= CPUPOWER_CAP_AMD_HW_PSTATE;
143 
144 				if (cpu_info->family >= 0x17)
145 					cpu_info->caps |= CPUPOWER_CAP_AMD_PSTATEDEF;
146 			}
147 		}
148 
149 		if (ext_cpuid_level >= 0x80000008 &&
150 		    cpuid_ebx(0x80000008) & (1 << 4))
151 			cpu_info->caps |= CPUPOWER_CAP_AMD_RDPRU;
152 	}
153 
154 	if (cpu_info->vendor == X86_VENDOR_INTEL) {
155 		if (cpuid_level >= 6 &&
156 		    (cpuid_eax(6) & (1 << 1)))
157 			cpu_info->caps |= CPUPOWER_CAP_INTEL_IDA;
158 	}
159 
160 	if (cpu_info->vendor == X86_VENDOR_INTEL) {
161 		/* Intel's perf-bias MSR support */
162 		if (cpuid_level >= 6 && (cpuid_ecx(6) & (1 << 3)))
163 			cpu_info->caps |= CPUPOWER_CAP_PERF_BIAS;
164 
165 		/* Intel's Turbo Ratio Limit support */
166 		if (cpu_info->family == 6) {
167 			switch (cpu_info->model) {
168 			case 0x1A:	/* Core i7, Xeon 5500 series
169 					 * Bloomfield, Gainstown NHM-EP
170 					 */
171 			case 0x1E:	/* Core i7 and i5 Processor
172 					 * Clarksfield, Lynnfield, Jasper Forest
173 					 */
174 			case 0x1F:	/* Core i7 and i5 Processor - Nehalem */
175 			case 0x25:	/* Westmere Client
176 					 * Clarkdale, Arrandale
177 					 */
178 			case 0x2C:	/* Westmere EP - Gulftown */
179 				cpu_info->caps |= CPUPOWER_CAP_HAS_TURBO_RATIO;
180 				break;
181 			case 0x2A:	/* SNB */
182 			case 0x2D:	/* SNB Xeon */
183 			case 0x3A:	/* IVB */
184 			case 0x3E:	/* IVB Xeon */
185 				cpu_info->caps |= CPUPOWER_CAP_HAS_TURBO_RATIO;
186 				cpu_info->caps |= CPUPOWER_CAP_IS_SNB;
187 				break;
188 			case 0x2E:	/* Nehalem-EX Xeon - Beckton */
189 			case 0x2F:	/* Westmere-EX Xeon - Eagleton */
190 			default:
191 				break;
192 			}
193 		}
194 	}
195 
196 	/*	printf("ID: %u - Extid: 0x%x - Caps: 0x%llx\n",
197 		cpuid_level, ext_cpuid_level, cpu_info->caps);
198 	*/
199 	return ret;
200 }
201