1 // Copyright (C) 2003 Dolphin Project.
2 
3 // This program is free software: you can redistribute it and/or modify
4 // it under the terms of the GNU General Public License as published by
5 // the Free Software Foundation, version 2.0.
6 
7 // This program is distributed in the hope that it will be useful,
8 // but WITHOUT ANY WARRANTY; without even the implied warranty of
9 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
10 // GNU General Public License 2.0 for more details.
11 
12 // A copy of the GPL 2.0 should have been included with the program.
13 // If not, see http://www.gnu.org/licenses/
14 
15 // Official SVN repository and contact information can be found at
16 // http://code.google.com/p/dolphin-emu/
17 
18 #include "ppsspp_config.h"
19 
20 #include <sstream>
21 
22 #if PPSSPP_PLATFORM(IOS) || PPSSPP_PLATFORM(MAC)
23 #include <sys/sysctl.h>
24 #endif
25 
26 
27 #if PPSSPP_ARCH(ARM) || PPSSPP_ARCH(ARM64)
28 
29 #include <ctype.h>
30 
31 #include "Common/Common.h"
32 #include "Common/CPUDetect.h"
33 #include "Common/StringUtils.h"
34 #include "Common/File/FileUtil.h"
35 #include "Common/Data/Encoding/Utf8.h"
36 
37 #if PPSSPP_PLATFORM(WINDOWS)
38 #if PPSSPP_PLATFORM(UWP)
39 // TODO: Maybe we can move the implementation here?
40 std::string GetCPUBrandString();
41 #else
42 // No CPUID on ARM, so we'll have to read the registry
43 #include <windows.h>
GetCPUBrandString()44 std::string GetCPUBrandString() {
45 	std::string cpu_string;
46 
47 	HKEY key;
48 	LSTATUS result = RegOpenKeyEx(HKEY_LOCAL_MACHINE, L"HARDWARE\\DESCRIPTION\\System\\CentralProcessor\\0", 0, KEY_READ, &key);
49 	if (result == ERROR_SUCCESS) {
50 		DWORD size = 0;
51 		DWORD type = REG_SZ;
52 		RegQueryValueEx(key, L"ProcessorNameString", NULL, &type, NULL, &size);
53 		LPBYTE buff = (LPBYTE)malloc(size);
54 		if (buff != NULL) {
55 			RegQueryValueEx(key, L"ProcessorNameString", NULL, &type, buff, &size);
56 			cpu_string = ConvertWStringToUTF8((wchar_t*)buff);
57 			free(buff);
58 		}
59 		RegCloseKey(key);
60 	}
61 
62 	if (cpu_string.empty())
63 		return "Unknown";
64 	else
65 		return cpu_string;
66 }
67 #endif
68 #endif
69 
70 // Only Linux platforms have /proc/cpuinfo
71 #if PPSSPP_PLATFORM(LINUX)
72 const char procfile[] = "/proc/cpuinfo";
73 // https://www.kernel.org/doc/Documentation/ABI/testing/sysfs-devices-system-cpu
74 const char syscpupresentfile[] = "/sys/devices/system/cpu/present";
75 
GetCPUString()76 std::string GetCPUString() {
77 	std::string procdata;
78 	bool readSuccess = File::ReadFileToString(true, Path(procfile), procdata);
79 	std::istringstream file(procdata);
80 	std::string cpu_string;
81 
82 	if (readSuccess) {
83 		std::string line, marker = "Hardware\t: ";
84 		while (std::getline(file, line)) {
85 			if (line.find(marker) != std::string::npos) {
86 				cpu_string = line.substr(marker.length());
87 			}
88 		}
89 	}
90 
91 	if (cpu_string.empty())
92 		cpu_string = "Unknown";
93 	else if (cpu_string.back() == '\n')
94 		cpu_string.pop_back(); // Drop the new-line character
95 
96 	return cpu_string;
97 }
98 
GetCPUBrandString()99 std::string GetCPUBrandString() {
100 	std::string procdata;
101 	bool readSuccess = File::ReadFileToString(true, Path(procfile), procdata);
102 	std::istringstream file(procdata);
103 	std::string brand_string;
104 
105 	if (readSuccess) {
106 		std::string line, marker = "Processor\t: ";
107 		while (std::getline(file, line)) {
108 			if (line.find(marker) != std::string::npos) {
109 				brand_string = line.substr(marker.length());
110 				if (brand_string.length() != 0 && !isdigit(brand_string[0])) {
111 					break;
112 				}
113 			}
114 		}
115 	}
116 
117 	if (brand_string.empty())
118 		brand_string = "Unknown";
119 	else if (brand_string.back() == '\n')
120 		brand_string.pop_back(); // Drop the new-line character
121 
122 	return brand_string;
123 }
124 
GetCPUImplementer()125 unsigned char GetCPUImplementer()
126 {
127 	std::string line, marker = "CPU implementer\t: ";
128 	unsigned char implementer = 0;
129 
130 	std::string procdata;
131 	if (!File::ReadFileToString(true, Path(procfile), procdata))
132 		return 0;
133 	std::istringstream file(procdata);
134 
135 	while (std::getline(file, line))
136 	{
137 		if (line.find(marker) != std::string::npos)
138 		{
139 			line = line.substr(marker.length());
140 			sscanf(line.c_str(), "0x%02hhx", &implementer);
141 			break;
142 		}
143 	}
144 
145 	return implementer;
146 }
147 
GetCPUPart()148 unsigned short GetCPUPart()
149 {
150 	std::string line, marker = "CPU part\t: ";
151 	unsigned short part = 0;
152 
153 	std::string procdata;
154 	if (!File::ReadFileToString(true, Path(procfile), procdata))
155 		return 0;
156 	std::istringstream file(procdata);
157 
158 	while (std::getline(file, line))
159 	{
160 		if (line.find(marker) != std::string::npos)
161 		{
162 			line = line.substr(marker.length());
163 			sscanf(line.c_str(), "0x%03hx", &part);
164 			break;
165 		}
166 	}
167 
168 	return part;
169 }
170 
CheckCPUFeature(const std::string & feature)171 bool CheckCPUFeature(const std::string& feature)
172 {
173 	std::string line, marker = "Features\t: ";
174 
175 	std::string procdata;
176 	if (!File::ReadFileToString(true, Path(procfile), procdata))
177 		return false;
178 	std::istringstream file(procdata);
179 	while (std::getline(file, line))
180 	{
181 		if (line.find(marker) != std::string::npos)
182 		{
183 			std::stringstream line_stream(line);
184 			std::string token;
185 			while (std::getline(line_stream, token, ' '))
186 			{
187 				if (token == feature)
188 					return true;
189 			}
190 		}
191 	}
192 
193 	return false;
194 }
195 
GetCoreCount()196 int GetCoreCount()
197 {
198 	std::string line, marker = "processor\t: ";
199 	int cores = 1;
200 
201 	std::string presentData;
202 	bool presentSuccess = File::ReadFileToString(true, Path(syscpupresentfile), presentData);
203 	std::istringstream presentFile(presentData);
204 
205 	if (presentSuccess) {
206 		int low, high, found;
207 		std::getline(presentFile, line);
208 		found = sscanf(line.c_str(), "%d-%d", &low, &high);
209 		if (found == 1)
210 			return 1;
211 		if (found == 2)
212 			return high - low + 1;
213 	}
214 
215 	std::string procdata;
216 	if (!File::ReadFileToString(true, Path(procfile), procdata))
217 		return 1;
218 	std::istringstream file(procdata);
219 
220 	while (std::getline(file, line))
221 	{
222 		if (line.find(marker) != std::string::npos)
223 			++cores;
224 	}
225 
226 	return cores;
227 }
228 #endif
229 
230 CPUInfo cpu_info;
231 
CPUInfo()232 CPUInfo::CPUInfo() {
233 	Detect();
234 }
235 
236 // Detects the various cpu features
Detect()237 void CPUInfo::Detect()
238 {
239 	// Set some defaults here
240 	HTT = false;
241 #if PPSSPP_ARCH(ARM64)
242 	OS64bit = true;
243 	CPU64bit = true;
244 	Mode64bit = true;
245 #else
246 	OS64bit = false;
247 	CPU64bit = false;
248 	Mode64bit = false;
249 #endif
250 	vendor = VENDOR_ARM;
251 	logical_cpu_count = 1;
252 
253 	// Get the information about the CPU
254 #if !PPSSPP_PLATFORM(LINUX)
255 	bool isVFP3 = false;
256 	bool isVFP4 = false;
257 #if PPSSPP_PLATFORM(IOS) || PPSSPP_PLATFORM(MAC)
258 #if PPSSPP_PLATFORM(IOS)
259 	isVFP3 = true;
260 	// Check for swift arch (VFP4)
261 #ifdef __ARM_ARCH_7S__
262 	isVFP4 = true;
263 #endif
264 #endif // PPSSPP_PLATFORM(IOS)
265 	size_t sz = 0x41; // char brand_string[0x41]
266 	if (sysctlbyname("machdep.cpu.brand_string", brand_string, &sz, nullptr, 0) != 0) {
267 		strcpy(brand_string, "Unknown");
268 	}
269 	int num = 0;
270 	sz = sizeof(num);
271 	if (sysctlbyname("hw.physicalcpu_max", &num, &sz, nullptr, 0) == 0) {
272 		num_cores = num;
273 		sz = sizeof(num);
274 		if (sysctlbyname("hw.logicalcpu_max", &num, &sz, nullptr, 0) == 0) {
275 			logical_cpu_count = num / num_cores;
276 		}
277 	}
278 #elif PPSSPP_PLATFORM(WINDOWS)
279 	truncate_cpy(brand_string, GetCPUBrandString().c_str());
280 	isVFP3 = true;
281 	isVFP4 = false;
282 	SYSTEM_INFO sysInfo;
283 	GetSystemInfo(&sysInfo);
284 	num_cores = sysInfo.dwNumberOfProcessors;
285 #else // !PPSSPP_PLATFORM(IOS) && !PPSSPP_PLATFORM(MAC) && !PPSSPP_PLATFORM(WINDOWS)
286 	strcpy(brand_string, "Unknown");
287 	num_cores = 1;
288 #endif
289 	truncate_cpy(cpu_string, brand_string);
290 	// Hardcode this for now
291 	bSwp = true;
292 	bHalf = true;
293 	bThumb = false;
294 	bFastMult = true;
295 	bVFP = true;
296 	bEDSP = true;
297 	bThumbEE = isVFP3;
298 	bNEON = isVFP3;
299 	bVFPv3 = isVFP3;
300 	bTLS = true;
301 	bVFPv4 = isVFP4;
302 	bIDIVa = isVFP4;
303 	bIDIVt = isVFP4;
304 	bFP = false;
305 	bASIMD = false;
306 #else // PPSSPP_PLATFORM(LINUX)
307 	truncate_cpy(cpu_string, GetCPUString().c_str());
308 	truncate_cpy(brand_string, GetCPUBrandString().c_str());
309 
310 	bSwp = CheckCPUFeature("swp");
311 	bHalf = CheckCPUFeature("half");
312 	bThumb = CheckCPUFeature("thumb");
313 	bFastMult = CheckCPUFeature("fastmult");
314 	bVFP = CheckCPUFeature("vfp");
315 	bEDSP = CheckCPUFeature("edsp");
316 	bThumbEE = CheckCPUFeature("thumbee");
317 	bNEON = CheckCPUFeature("neon");
318 	bVFPv3 = CheckCPUFeature("vfpv3");
319 	bTLS = CheckCPUFeature("tls");
320 	bVFPv4 = CheckCPUFeature("vfpv4");
321 	bIDIVa = CheckCPUFeature("idiva");
322 	bIDIVt = CheckCPUFeature("idivt");
323 	// Qualcomm Krait supports IDIVA but it doesn't report it. Check for krait (0x4D = Plus, 0x6F = Pro).
324 	unsigned short CPUPart = GetCPUPart();
325 	if (GetCPUImplementer() == 0x51 && (CPUPart == 0x4D || CPUPart == 0x6F))
326 		bIDIVa = bIDIVt = true;
327 	// These two require ARMv8 or higher
328 	bFP = CheckCPUFeature("fp");
329 	bASIMD = CheckCPUFeature("asimd");
330 	num_cores = GetCoreCount();
331 #endif
332 #if PPSSPP_ARCH(ARM64)
333 	// Whether the above detection failed or not, on ARM64 we do have ASIMD/NEON.
334 	bNEON = true;
335 	bASIMD = true;
336 #endif
337 }
338 
339 // Turn the cpu info into a string we can show
Summarize()340 std::string CPUInfo::Summarize()
341 {
342 	std::string sum;
343 	if (num_cores == 1)
344 		sum = StringFromFormat("%s, %d core", cpu_string, num_cores);
345 	else
346 		sum = StringFromFormat("%s, %d cores", cpu_string, num_cores);
347 	if (bSwp) sum += ", SWP";
348 	if (bHalf) sum += ", Half";
349 	if (bThumb) sum += ", Thumb";
350 	if (bFastMult) sum += ", FastMult";
351 	if (bEDSP) sum += ", EDSP";
352 	if (bThumbEE) sum += ", ThumbEE";
353 	if (bTLS) sum += ", TLS";
354 	if (bVFP) sum += ", VFP";
355 	if (bVFPv3) sum += ", VFPv3";
356 	if (bVFPv4) sum += ", VFPv4";
357 	if (bNEON) sum += ", NEON";
358 	if (bIDIVa) sum += ", IDIVa";
359 	if (bIDIVt) sum += ", IDIVt";
360 	if (CPU64bit) sum += ", 64-bit";
361 
362 	return sum;
363 }
364 
365 #endif // PPSSPP_ARCH(ARM) || PPSSPP_ARCH(ARM64)
366