1/*
2 * Copyright 2004-2013, Axel D��rfler, axeld@pinc-software.de.
3 * Copyright 2013, Pawe�� Dziepak, pdziepak@quarnos.org.
4 * Distributed under the terms of the MIT License.
5 */
6
7/* Taken from the Pulse application, and extended.
8 * It's used by Pulse, AboutHaiku, and sysinfo.
9 */
10
11#include <stdlib.h>
12#include <stdio.h>
13#include <strings.h>
14
15#include <OS.h>
16
17
18#ifdef __cplusplus
19extern "C" {
20#endif
21
22static const char* get_cpu_vendor_string(enum cpu_vendor cpuVendor);
23static const char* get_cpu_model_string(enum cpu_platform platform,
24	enum cpu_vendor cpuVendor, uint32 cpuModel);
25void get_cpu_type(char *vendorBuffer, size_t vendorSize,
26		char *modelBuffer, size_t modelSize);
27int32 get_rounded_cpu_speed(void);
28
29#ifdef __cplusplus
30}
31#endif
32
33
34#if defined(__i386__) || defined(__x86_64__)
35/*!	Tries to parse an Intel CPU ID string to match our usual naming scheme.
36	Note, this function is not thread safe, and must only be called once
37	at a time.
38*/
39static const char*
40parse_intel(const char* name)
41{
42	static char buffer[49];
43
44	// ignore initial spaces
45	int index = 0;
46	for (; name[index] != '\0'; index++) {
47		if (name[index] != ' ')
48			break;
49	}
50
51
52	// parse model
53	int outIndex = 0;
54	for (; name[index] != '\0'; index++) {
55		// ignore vendor
56		if (strncmp(&name[index], "Intel", 5) == 0) {
57			for (; name[index] != '\0'; index++) {
58				if (name[index] == ' ') {
59					index++;
60					break;
61				}
62			}
63		}
64		if (!strncmp(&name[index], "(R)", 3)) {
65			outIndex += strlcpy(&buffer[outIndex], "��",
66				sizeof(buffer) - outIndex);
67			index += 2;
68		} else if (!strncmp(&name[index], "(TM)", 4)) {
69			outIndex += strlcpy(&buffer[outIndex], "���",
70				sizeof(buffer) - outIndex);
71			index += 3;
72		} else if (!strncmp(&name[index], " CPU", 4)) {
73			// Cut out the CPU string
74			index += 3;
75		} else if (!strncmp(&name[index], " @", 2)) {
76			// Cut off the remainder
77			break;
78		} else
79			buffer[outIndex++] = name[index];
80	}
81
82	buffer[outIndex] = '\0';
83	return buffer;
84}
85
86
87static const char*
88parse_amd(const char* name)
89{
90	static char buffer[49];
91
92	// ignore initial spaces
93	int index = 0;
94	for (; name[index] != '\0'; index++) {
95		if (name[index] != ' ')
96			break;
97	}
98
99	// Keep an initial "mobile"
100	int outIndex = 0;
101	bool spaceWritten = false;
102	if (!strncasecmp(&name[index], "Mobile ", 7)) {
103		strcpy(buffer, "Mobile ");
104		spaceWritten = true;
105		outIndex += 7;
106		index += 7;
107	}
108
109	// parse model
110	for (; name[index] != '\0'; index++) {
111		if (!strncasecmp(&name[index], "(r)", 3)) {
112			outIndex += strlcpy(&buffer[outIndex], "��",
113				sizeof(buffer) - outIndex);
114			index += 2;
115		} else if (!strncasecmp(&name[index], "(tm)", 4)) {
116			outIndex += strlcpy(&buffer[outIndex], "���",
117				sizeof(buffer) - outIndex);
118			index += 3;
119		} else if (!strncmp(&name[index], "with ", 5)
120			|| !strncmp(&name[index], "/w", 2)) {
121			// Cut off the rest
122			break;
123		} else if (name[index] == '-') {
124			if (!spaceWritten)
125				buffer[outIndex++] = ' ';
126			spaceWritten = true;
127		} else {
128			const char* kWords[] = {
129				"Eight-core", "6-core", "Six-core", "Quad-core", "Dual-core",
130				"Dual core", "Processor", "APU", "AMD", "Intel", "Integrated",
131				"CyrixInstead", "Advanced Micro Devices", "Comb", "DualCore",
132				"Technology", "Mobile", "Triple-Core"
133			};
134			bool removed = false;
135			for (size_t i = 0; i < sizeof(kWords) / sizeof(kWords[0]); i++) {
136				size_t length = strlen(kWords[i]);
137				if (!strncasecmp(&name[index], kWords[i], length)) {
138					index += length - 1;
139					removed = true;
140					break;
141				}
142			}
143			if (removed)
144				continue;
145
146			if (name[index] == ' ') {
147				if (spaceWritten)
148					continue;
149				spaceWritten = true;
150			} else
151				spaceWritten = false;
152			buffer[outIndex++] = name[index];
153		}
154	}
155
156	// cut off trailing spaces
157	while (outIndex > 1 && buffer[outIndex - 1] == ' ')
158		outIndex--;
159
160	buffer[outIndex] = '\0';
161
162	// skip new initial spaces
163	for (outIndex = 0; buffer[outIndex] != '\0'; outIndex++) {
164		if (buffer[outIndex] != ' ')
165			break;
166	}
167	return buffer + outIndex;
168}
169#endif
170
171
172static const char*
173get_cpu_vendor_string(enum cpu_vendor cpuVendor)
174{
175	// Should match vendors in OS.h
176	static const char* vendorStrings[] = {
177		NULL, "AMD", "Cyrix", "IDT", "Intel", "National Semiconductor", "Rise",
178		"Transmeta", "VIA", "IBM", "Motorola", "NEC", "Hygon"
179	};
180
181	if ((size_t)cpuVendor >= sizeof(vendorStrings) / sizeof(const char*))
182		return NULL;
183	return vendorStrings[cpuVendor];
184}
185
186
187#if defined(__i386__) || defined(__x86_64__)
188/*! Parameter 'name' needs to point to an allocated array of 49 characters. */
189void
190get_cpuid_model_string(char *name)
191{
192	/* References:
193	 *
194	 * http://grafi.ii.pw.edu.pl/gbm/x86/cpuid.html
195	 * http://www.sandpile.org/ia32/cpuid.htm
196	 * http://www.amd.com/us-en/assets/content_type/
197	 *	white_papers_and_tech_docs/TN13.pdf (Duron erratum)
198	 */
199
200	cpuid_info baseInfo;
201	cpuid_info cpuInfo;
202	int32 maxStandardFunction, maxExtendedFunction = 0;
203
204	memset(name, 0, 49 * sizeof(char));
205
206	if (get_cpuid(&baseInfo, 0, 0) != B_OK) {
207		/* This CPU doesn't support cpuid. */
208		return;
209	}
210
211	maxStandardFunction = baseInfo.eax_0.max_eax;
212	if (maxStandardFunction >= 500) {
213		maxStandardFunction = 0;
214			/* Old Pentium sample chips have the CPU signature here. */
215	}
216
217	/* Extended cpuid */
218
219	get_cpuid(&cpuInfo, 0x80000000, 0);
220		/* hardcoded to CPU 0 */
221
222	/* Extended cpuid is only supported if max_eax is greater than the */
223	/* service id. */
224	if (cpuInfo.eax_0.max_eax > 0x80000000)
225		maxExtendedFunction = cpuInfo.eax_0.max_eax & 0xff;
226
227	if (maxExtendedFunction >= 4) {
228		int32 i;
229
230		for (i = 0; i < 3; i++) {
231			cpuid_info nameInfo;
232			get_cpuid(&nameInfo, 0x80000002 + i, 0);
233
234			memcpy(name, &nameInfo.regs.eax, 4);
235			memcpy(name + 4, &nameInfo.regs.ebx, 4);
236			memcpy(name + 8, &nameInfo.regs.ecx, 4);
237			memcpy(name + 12, &nameInfo.regs.edx, 4);
238			name += 16;
239		}
240	}
241}
242#endif	/* __i386__ || __x86_64__ */
243
244
245static const char*
246get_cpu_model_string(enum cpu_platform platform, enum cpu_vendor cpuVendor,
247	uint32 cpuModel)
248{
249#if defined(__i386__) || defined(__x86_64__)
250	char cpuidName[49];
251#endif
252
253	(void)cpuVendor;
254	(void)cpuModel;
255
256#if defined(__i386__) || defined(__x86_64__)
257	if (platform != B_CPU_x86 && platform != B_CPU_x86_64)
258		return NULL;
259
260	// XXX: This *really* isn't accurate. There is differing math
261	// based on the CPU vendor.. Don't use these numbers anywhere
262	// except "fast and dumb" identification of processor names.
263	//
264	// see cpuidtool.c to decode cpuid signatures (sysinfo) into a
265	// value for this function.
266	//
267	// sysinfo has code in it which obtains the proper fam/mod/step ids
268
269	uint16 family = ((cpuModel >> 8) & 0xf) | ((cpuModel >> 16) & 0xff0);
270	uint16 model = ((cpuModel >> 4) & 0xf) | ((cpuModel >> 12) & 0xf0);
271	uint8 stepping = cpuModel & 0xf;
272
273	if (cpuVendor == B_CPU_VENDOR_AMD) {
274		if (family == 5) {
275			if (model <= 3)
276				return "K5";
277			if (model <= 7)
278				return "K6";
279			if (model == 8)
280				return "K6-2";
281			if (model == 9 || model == 0xd)
282				return "K6-III";
283			if (model == 0xa)
284				return "Geode LX";
285		} else if (family == 6) {
286			if (model <= 2 || model == 4)
287				return "Athlon";
288			if (model == 3)
289				return "Duron";
290			if (model <= 8 || model == 0xa)
291				return "Athlon XP";
292		} else if (family == 0xf) {
293			if (model <= 4 || model == 7 || model == 8
294				|| (model >= 0xb && model <= 0xf) || model == 0x14
295				|| model == 0x18 || model == 0x1b || model == 0x1f
296				|| model == 0x23 || model == 0x2b
297				|| ((model & 0xf) == 0xf && model >= 0x2f && model <= 0x7e)) {
298				return "Athlon 64";
299			}
300			if (model == 5 || model == 0x15 || model == 0x21 || model == 0x25
301				|| model == 0x27) {
302				return "Opteron";
303			}
304			if (model == 0x1c || model == 0x2c || model == 0x7f)
305				return "Sempron 64";
306			if (model == 0x24 || model == 0x4c || model == 0x68)
307				return "Turion 64";
308		} else if (family == 0x1f) {
309			if (model == 2)
310				return "Phenom";
311			if ((model >= 4 && model <= 6) || model == 0xa) {
312				get_cpuid_model_string(cpuidName);
313				if (strcasestr(cpuidName, "Athlon") != NULL)
314					return "Athlon II";
315				return "Phenom II";
316			}
317		} else if (family == 0x3f)
318			return "A-Series";
319		else if (family == 0x5f) {
320			if (model == 1)
321				return "C-Series";
322			if (model == 2)
323				return "E-Series";
324		} else if (family == 0x6f) {
325			if (model == 1 || model == 2)
326				return "FX-Series";
327			if (model == 0x10 || model == 0x13)
328				return "A-Series";
329		}
330
331		// Fallback to manual parsing of the model string
332		get_cpuid_model_string(cpuidName);
333		return parse_amd(cpuidName);
334	}
335
336	if (cpuVendor == B_CPU_VENDOR_CYRIX) {
337		if (family == 5 && model == 4)
338			return "GXm";
339		if (family == 6)
340			return "6x86MX";
341		return NULL;
342	}
343
344	if (cpuVendor == B_CPU_VENDOR_INTEL) {
345		if (family == 5) {
346			if (model == 1 || model == 2)
347				return "Pentium";
348			if (model == 3 || model == 9)
349				return "Pentium OD";
350			if (model == 4 || model == 8)
351				return "Pentium MMX";
352		} else if (family == 6) {
353			if (model == 1)
354				return "Pentium Pro";
355			if (model == 3 || model == 5)
356				return "Pentium II";
357			if (model == 6)
358				return "Celeron";
359			if (model == 7 || model == 8 || model == 0xa || model == 0xb)
360				return "Pentium III";
361			if (model == 9 || model == 0xd) {
362				get_cpuid_model_string(cpuidName);
363				if (strcasestr(cpuidName, "Celeron") != NULL)
364					return "Pentium M Celeron";
365				return "Pentium M";
366			}
367			if (model == 0x1c || model == 0x26 || model == 0x36)
368				return "Atom";
369			if (model == 0xe) {
370				get_cpuid_model_string(cpuidName);
371				if (strcasestr(cpuidName, "Celeron") != NULL)
372					return "Core Celeron";
373				return "Core";
374			}
375			if (model == 0xf || model == 0x17) {
376                get_cpuid_model_string(cpuidName);
377				if (strcasestr(cpuidName, "Celeron") != NULL)
378					return "Core 2 Celeron";
379				if (strcasestr(cpuidName, "Xeon") != NULL)
380					return "Core 2 Xeon";
381				if (strcasestr(cpuidName, "Pentium") != NULL)
382					return "Pentium";
383				if (strcasestr(cpuidName, "Extreme") != NULL)
384					return "Core 2 Extreme";
385				return "Core 2";
386			}
387			if (model == 0x25) {
388				get_cpuid_model_string(cpuidName);
389				if (strcasestr(cpuidName, "i3") != NULL)
390					return "Core i3";
391				return "Core i5";
392			}
393			if (model == 0x1a || model == 0x1e) {
394				get_cpuid_model_string(cpuidName);
395				if (strcasestr(cpuidName, "Xeon") != NULL)
396					return "Core i7 Xeon";
397				return "Core i7";
398			}
399		} else if (family == 0xf) {
400			if (model <= 4) {
401				get_cpuid_model_string(cpuidName);
402				if (strcasestr(cpuidName, "Celeron") != NULL)
403					return "Pentium 4 Celeron";
404				if (strcasestr(cpuidName, "Xeon") != NULL)
405					return "Pentium 4 Xeon";
406				return "Pentium 4";
407			}
408		}
409
410		// Fallback to manual parsing of the model string
411		get_cpuid_model_string(cpuidName);
412		return parse_intel(cpuidName);
413	}
414
415	if (cpuVendor == B_CPU_VENDOR_NATIONAL_SEMICONDUCTOR) {
416		if (family == 5) {
417			if (model == 4)
418				return "Geode GX1";
419			if (model == 5)
420				return "Geode GX2";
421			return NULL;
422		}
423	}
424
425	if (cpuVendor == B_CPU_VENDOR_RISE) {
426		if (family == 5)
427			return "mP6";
428		return NULL;
429	}
430
431	if (cpuVendor == B_CPU_VENDOR_TRANSMETA) {
432		if (family == 5 && model == 4)
433			return "Crusoe";
434		if (family == 0xf && (model == 2 || model == 3))
435			return "Efficeon";
436		return NULL;
437	}
438
439	if (cpuVendor == B_CPU_VENDOR_VIA) {
440		if (family == 5) {
441			if (model == 4)
442				return "WinChip C6";
443			if (model == 8)
444				return "WinChip 2";
445			if (model == 9)
446				return "WinChip 3";
447			return NULL;
448		} else if (family == 6) {
449			if (model == 6)
450				return "C3 Samuel";
451			if (model == 7) {
452				if (stepping < 8)
453					return "C3 Eden/Samuel 2";
454				return "C3 Ezra";
455			}
456			if (model == 8)
457				return "C3 Ezra-T";
458			if (model == 9) {
459				if (stepping < 8)
460					return "C3 Nehemiah";
461				return "C3 Ezra-N";
462			}
463			if (model == 0xa || model == 0xd)
464				return "C7";
465			if (model == 0xf)
466				return "Nano";
467			return NULL;
468		}
469	}
470
471#endif
472
473	return NULL;
474}
475
476
477void
478get_cpu_type(char *vendorBuffer, size_t vendorSize, char *modelBuffer,
479	size_t modelSize)
480{
481	const char *vendor, *model;
482
483	uint32 topologyNodeCount = 0;
484	cpu_topology_node_info* topology = NULL;
485	get_cpu_topology_info(NULL, &topologyNodeCount);
486	if (topologyNodeCount != 0)
487		topology = (cpu_topology_node_info*)calloc(topologyNodeCount, sizeof(cpu_topology_node_info));
488	get_cpu_topology_info(topology, &topologyNodeCount);
489
490	enum cpu_platform platform = B_CPU_UNKNOWN;
491	enum cpu_vendor cpuVendor = B_CPU_VENDOR_UNKNOWN;
492	uint32 cpuModel = 0;
493	for (uint32 i = 0; i < topologyNodeCount; i++) {
494		switch (topology[i].type) {
495			case B_TOPOLOGY_ROOT:
496				platform = topology[i].data.root.platform;
497				break;
498
499			case B_TOPOLOGY_PACKAGE:
500				cpuVendor = topology[i].data.package.vendor;
501				break;
502
503			case B_TOPOLOGY_CORE:
504				cpuModel = topology[i].data.core.model;
505				break;
506
507			default:
508				break;
509		}
510	}
511	free(topology);
512
513	vendor = get_cpu_vendor_string(cpuVendor);
514	if (vendor == NULL)
515		vendor = "Unknown";
516
517	model = get_cpu_model_string(platform, cpuVendor, cpuModel);
518	if (model == NULL)
519		model = "Unknown";
520
521	strlcpy(vendorBuffer, vendor, vendorSize);
522	strlcpy(modelBuffer, model, modelSize);
523}
524
525
526int32
527get_rounded_cpu_speed(void)
528{
529	uint32 topologyNodeCount = 0;
530	cpu_topology_node_info* topology = NULL;
531	get_cpu_topology_info(NULL, &topologyNodeCount);
532	if (topologyNodeCount != 0)
533		topology = (cpu_topology_node_info*)calloc(topologyNodeCount, sizeof(cpu_topology_node_info));
534	get_cpu_topology_info(topology, &topologyNodeCount);
535
536	uint64 cpuFrequency = 0;
537	for (uint32 i = 0; i < topologyNodeCount; i++) {
538		if (topology[i].type == B_TOPOLOGY_CORE) {
539				cpuFrequency = topology[i].data.core.default_frequency;
540				break;
541		}
542	}
543	free(topology);
544
545	int target, frac, delta;
546	int freqs[] = { 100, 50, 25, 75, 33, 67, 20, 40, 60, 80, 10, 30, 70, 90 };
547	uint x;
548
549	target = cpuFrequency / 1000000;
550	frac = target % 100;
551	delta = -frac;
552
553	for (x = 0; x < sizeof(freqs) / sizeof(freqs[0]); x++) {
554		int ndelta = freqs[x] - frac;
555		if (abs(ndelta) < abs(delta))
556			delta = ndelta;
557	}
558	return target + delta;
559}
560
561