1/*
2 *  (C) 2010,2011       Thomas Renninger <trenn@suse.de>, Novell Inc.
3 *
4 *  Licensed under the terms of the GNU GPL License version 2.
5 *
6 *  Based on SandyBridge monitor. Implements the new package C-states
7 *  (PC8, PC9, PC10) coming with a specific Haswell (family 0x45) CPU.
8 */
9
10#if defined(__i386__) || defined(__x86_64__)
11
12#include <stdio.h>
13#include <stdint.h>
14#include <stdlib.h>
15#include <string.h>
16
17#include "helpers/helpers.h"
18#include "idle_monitor/cpupower-monitor.h"
19
20#define MSR_PKG_C8_RESIDENCY           0x00000630
21#define MSR_PKG_C9_RESIDENCY           0x00000631
22#define MSR_PKG_C10_RESIDENCY          0x00000632
23
24#define MSR_TSC	0x10
25
26enum intel_hsw_ext_id { PC8 = 0, PC9, PC10, HSW_EXT_CSTATE_COUNT,
27			TSC = 0xFFFF };
28
29static int hsw_ext_get_count_percent(unsigned int self_id, double *percent,
30				 unsigned int cpu);
31
32static cstate_t hsw_ext_cstates[HSW_EXT_CSTATE_COUNT] = {
33	{
34		.name			= "PC8",
35		.desc			= N_("Processor Package C8"),
36		.id			= PC8,
37		.range			= RANGE_PACKAGE,
38		.get_count_percent	= hsw_ext_get_count_percent,
39	},
40	{
41		.name			= "PC9",
42		.desc			= N_("Processor Package C9"),
43		.desc			= N_("Processor Package C2"),
44		.id			= PC9,
45		.range			= RANGE_PACKAGE,
46		.get_count_percent	= hsw_ext_get_count_percent,
47	},
48	{
49		.name			= "PC10",
50		.desc			= N_("Processor Package C10"),
51		.id			= PC10,
52		.range			= RANGE_PACKAGE,
53		.get_count_percent	= hsw_ext_get_count_percent,
54	},
55};
56
57static unsigned long long tsc_at_measure_start;
58static unsigned long long tsc_at_measure_end;
59static unsigned long long *previous_count[HSW_EXT_CSTATE_COUNT];
60static unsigned long long *current_count[HSW_EXT_CSTATE_COUNT];
61/* valid flag for all CPUs. If a MSR read failed it will be zero */
62static int *is_valid;
63
64static int hsw_ext_get_count(enum intel_hsw_ext_id id, unsigned long long *val,
65			unsigned int cpu)
66{
67	int msr;
68
69	switch (id) {
70	case PC8:
71		msr = MSR_PKG_C8_RESIDENCY;
72		break;
73	case PC9:
74		msr = MSR_PKG_C9_RESIDENCY;
75		break;
76	case PC10:
77		msr = MSR_PKG_C10_RESIDENCY;
78		break;
79	case TSC:
80		msr = MSR_TSC;
81		break;
82	default:
83		return -1;
84	};
85	if (read_msr(cpu, msr, val))
86		return -1;
87	return 0;
88}
89
90static int hsw_ext_get_count_percent(unsigned int id, double *percent,
91				 unsigned int cpu)
92{
93	*percent = 0.0;
94
95	if (!is_valid[cpu])
96		return -1;
97
98	*percent = (100.0 *
99		(current_count[id][cpu] - previous_count[id][cpu])) /
100		(tsc_at_measure_end - tsc_at_measure_start);
101
102	dprint("%s: previous: %llu - current: %llu - (%u)\n",
103		hsw_ext_cstates[id].name, previous_count[id][cpu],
104		current_count[id][cpu], cpu);
105
106	dprint("%s: tsc_diff: %llu - count_diff: %llu - percent: %2.f (%u)\n",
107	       hsw_ext_cstates[id].name,
108	       (unsigned long long) tsc_at_measure_end - tsc_at_measure_start,
109	       current_count[id][cpu] - previous_count[id][cpu],
110	       *percent, cpu);
111
112	return 0;
113}
114
115static int hsw_ext_start(void)
116{
117	int num, cpu;
118	unsigned long long val;
119
120	for (num = 0; num < HSW_EXT_CSTATE_COUNT; num++) {
121		for (cpu = 0; cpu < cpu_count; cpu++) {
122			hsw_ext_get_count(num, &val, cpu);
123			previous_count[num][cpu] = val;
124		}
125	}
126	hsw_ext_get_count(TSC, &tsc_at_measure_start, 0);
127	return 0;
128}
129
130static int hsw_ext_stop(void)
131{
132	unsigned long long val;
133	int num, cpu;
134
135	hsw_ext_get_count(TSC, &tsc_at_measure_end, 0);
136
137	for (num = 0; num < HSW_EXT_CSTATE_COUNT; num++) {
138		for (cpu = 0; cpu < cpu_count; cpu++) {
139			is_valid[cpu] = !hsw_ext_get_count(num, &val, cpu);
140			current_count[num][cpu] = val;
141		}
142	}
143	return 0;
144}
145
146struct cpuidle_monitor intel_hsw_ext_monitor;
147
148static struct cpuidle_monitor *hsw_ext_register(void)
149{
150	int num;
151
152	if (cpupower_cpu_info.vendor != X86_VENDOR_INTEL
153	    || cpupower_cpu_info.family != 6)
154		return NULL;
155
156	switch (cpupower_cpu_info.model) {
157	case 0x45: /* HSW */
158		break;
159	default:
160		return NULL;
161	}
162
163	is_valid = calloc(cpu_count, sizeof(int));
164	for (num = 0; num < HSW_EXT_CSTATE_COUNT; num++) {
165		previous_count[num] = calloc(cpu_count,
166					sizeof(unsigned long long));
167		current_count[num]  = calloc(cpu_count,
168					sizeof(unsigned long long));
169	}
170	intel_hsw_ext_monitor.name_len = strlen(intel_hsw_ext_monitor.name);
171	return &intel_hsw_ext_monitor;
172}
173
174void hsw_ext_unregister(void)
175{
176	int num;
177	free(is_valid);
178	for (num = 0; num < HSW_EXT_CSTATE_COUNT; num++) {
179		free(previous_count[num]);
180		free(current_count[num]);
181	}
182}
183
184struct cpuidle_monitor intel_hsw_ext_monitor = {
185	.name			= "HaswellExtended",
186	.hw_states		= hsw_ext_cstates,
187	.hw_states_num		= HSW_EXT_CSTATE_COUNT,
188	.start			= hsw_ext_start,
189	.stop			= hsw_ext_stop,
190	.do_register		= hsw_ext_register,
191	.unregister		= hsw_ext_unregister,
192	.needs_root		= 1,
193	.overflow_s		= 922000000 /* 922337203 seconds TSC overflow
194					       at 20GHz */
195};
196#endif /* defined(__i386__) || defined(__x86_64__) */
197