xref: /rk3399_ARM-atf/lib/psci/psci_stat.c (revision fabd0a864a09ab6024db2cac0a4929b443a72640)
1 /*
2  * Copyright (c) 2016, ARM Limited and Contributors. All rights reserved.
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions are met:
6  *
7  * Redistributions of source code must retain the above copyright notice, this
8  * list of conditions and the following disclaimer.
9  *
10  * Redistributions in binary form must reproduce the above copyright notice,
11  * this list of conditions and the following disclaimer in the documentation
12  * and/or other materials provided with the distribution.
13  *
14  * Neither the name of ARM nor the names of its contributors may be used
15  * to endorse or promote products derived from this software without specific
16  * prior written permission.
17  *
18  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
19  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
20  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
21  * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
22  * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
23  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
24  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
25  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
26  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
27  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
28  * POSSIBILITY OF SUCH DAMAGE.
29  */
30 
31 #include <assert.h>
32 #include <debug.h>
33 #include <platform.h>
34 #include <platform_def.h>
35 #include "psci_private.h"
36 
37 #ifndef PLAT_MAX_PWR_LVL_STATES
38 #define PLAT_MAX_PWR_LVL_STATES 2
39 #endif
40 
41 /* Ticks elapsed in one second by a signal of 1 MHz */
42 #define MHZ_TICKS_PER_SEC 1000000
43 
44 /* Following structure is used for PSCI STAT */
45 typedef struct psci_stat {
46 	u_register_t residency;
47 	u_register_t count;
48 } psci_stat_t;
49 
50 /*
51  * Following is used to keep track of the last cpu
52  * that goes to power down in non cpu power domains.
53  */
54 static int last_cpu_in_non_cpu_pd[PSCI_NUM_NON_CPU_PWR_DOMAINS] = {-1};
55 
56 /*
57  * Following are used to store PSCI STAT values for
58  * CPU and non CPU power domains.
59  */
60 static psci_stat_t psci_cpu_stat[PLATFORM_CORE_COUNT]
61 				[PLAT_MAX_PWR_LVL_STATES];
62 static psci_stat_t psci_non_cpu_stat[PSCI_NUM_NON_CPU_PWR_DOMAINS]
63 				[PLAT_MAX_PWR_LVL_STATES];
64 
65 /* Register PMF PSCI service */
66 PMF_REGISTER_SERVICE(psci_svc, PMF_PSCI_STAT_SVC_ID,
67 	 PSCI_STAT_TOTAL_IDS, PMF_STORE_ENABLE)
68 
69 /* The divisor to use to convert raw timestamp into microseconds */
70 u_register_t residency_div;
71 
72 /*
73  * This macro calculates the stats residency in microseconds,
74  * taking in account the wrap around condition.
75  */
76 #define calc_stat_residency(_pwrupts, _pwrdnts, _res)		\
77 	do {							\
78 		if (_pwrupts < _pwrdnts)			\
79 			_res = UINT64_MAX - _pwrdnts + _pwrupts;\
80 		else						\
81 			_res = _pwrupts - _pwrdnts;		\
82 		/* Convert timestamp into microseconds */	\
83 		_res = _res/residency_div;			\
84 	} while (0)
85 
86 /*
87  * This functions returns the index into the `psci_stat_t` array given the
88  * local power state and power domain level. If the platform implements the
89  * `get_pwr_lvl_state_idx` pm hook, then that will be used to return the index.
90  */
91 static int get_stat_idx(plat_local_state_t local_state, int pwr_lvl)
92 {
93 	int idx;
94 
95 	if (psci_plat_pm_ops->get_pwr_lvl_state_idx == NULL) {
96 		assert(PLAT_MAX_PWR_LVL_STATES == 2);
97 		if (is_local_state_retn(local_state))
98 			return 0;
99 
100 		assert(is_local_state_off(local_state));
101 		return 1;
102 	}
103 
104 	idx = psci_plat_pm_ops->get_pwr_lvl_state_idx(local_state, pwr_lvl);
105 	assert((idx >= 0) && (idx < PLAT_MAX_PWR_LVL_STATES));
106 	return idx;
107 }
108 
109 /*******************************************************************************
110  * This function is passed the target local power states for each power
111  * domain (state_info) between the current CPU domain and its ancestors until
112  * the target power level (end_pwrlvl).
113  *
114  * Then, for each level (apart from the CPU level) until the 'end_pwrlvl', it
115  * updates the `last_cpu_in_non_cpu_pd[]` with last power down cpu id.
116  *
117  * This function will only be invoked with data cache enabled and while
118  * powering down a core.
119  ******************************************************************************/
120 void psci_stats_update_pwr_down(unsigned int end_pwrlvl,
121 			const psci_power_state_t *state_info)
122 {
123 	int lvl, parent_idx, cpu_idx = plat_my_core_pos();
124 
125 	assert(end_pwrlvl <= PLAT_MAX_PWR_LVL);
126 	assert(state_info);
127 
128 	parent_idx = psci_cpu_pd_nodes[cpu_idx].parent_node;
129 
130 	for (lvl = PSCI_CPU_PWR_LVL + 1; lvl <= end_pwrlvl; lvl++) {
131 
132 		/* Break early if the target power state is RUN */
133 		if (is_local_state_run(state_info->pwr_domain_state[lvl]))
134 			break;
135 
136 		/*
137 		 * The power domain is entering a low power state, so this is
138 		 * the last CPU for this power domain
139 		 */
140 		last_cpu_in_non_cpu_pd[parent_idx] = cpu_idx;
141 
142 		parent_idx = psci_non_cpu_pd_nodes[parent_idx].parent_node;
143 	}
144 
145 }
146 
147 /*******************************************************************************
148  * This function updates the PSCI STATS(residency time and count) for CPU
149  * and NON-CPU power domains.
150  * It is called with caches enabled and locks acquired(for NON-CPU domain)
151  ******************************************************************************/
152 void psci_stats_update_pwr_up(unsigned int end_pwrlvl,
153 			const psci_power_state_t *state_info,
154 			unsigned int flags)
155 {
156 	int parent_idx, cpu_idx = plat_my_core_pos();
157 	int lvl, stat_idx;
158 	plat_local_state_t local_state;
159 	unsigned long long pwrup_ts = 0, pwrdn_ts = 0;
160 	u_register_t residency;
161 
162 	assert(end_pwrlvl <= PLAT_MAX_PWR_LVL);
163 	assert(state_info);
164 
165 	/* Initialize the residency divisor if not already initialized */
166 	if (!residency_div) {
167 		/* Pre-calculate divisor so that it can be directly used to
168 		   convert time-stamp into microseconds */
169 		residency_div = read_cntfrq_el0() / MHZ_TICKS_PER_SEC;
170 		assert(residency_div);
171 	}
172 
173 	/* Get power down time-stamp for current CPU */
174 	PMF_GET_TIMESTAMP_BY_INDEX(psci_svc, PSCI_STAT_ID_ENTER_LOW_PWR,
175 			cpu_idx, flags, pwrdn_ts);
176 
177 	/* In the case of 1st power on just return */
178 	if (!pwrdn_ts)
179 		return;
180 
181 	/* Get power up time-stamp for current CPU */
182 	PMF_GET_TIMESTAMP_BY_INDEX(psci_svc, PSCI_STAT_ID_EXIT_LOW_PWR,
183 			cpu_idx, flags, pwrup_ts);
184 
185 	/* Get the index into the stats array */
186 	local_state = state_info->pwr_domain_state[PSCI_CPU_PWR_LVL];
187 	stat_idx = get_stat_idx(local_state, PSCI_CPU_PWR_LVL);
188 
189 	/* Calculate stats residency */
190 	calc_stat_residency(pwrup_ts, pwrdn_ts, residency);
191 
192 	/* Update CPU stats. */
193 	psci_cpu_stat[cpu_idx][stat_idx].residency += residency;
194 	psci_cpu_stat[cpu_idx][stat_idx].count++;
195 
196 	/*
197 	 * Check what power domains above CPU were off
198 	 * prior to this CPU powering on.
199 	 */
200 	parent_idx = psci_cpu_pd_nodes[cpu_idx].parent_node;
201 	for (lvl = PSCI_CPU_PWR_LVL + 1; lvl <= end_pwrlvl; lvl++) {
202 		local_state = state_info->pwr_domain_state[lvl];
203 		if (is_local_state_run(local_state)) {
204 			/* Break early */
205 			break;
206 		}
207 
208 		assert(last_cpu_in_non_cpu_pd[parent_idx] != -1);
209 
210 		/* Get power down time-stamp for last CPU */
211 		PMF_GET_TIMESTAMP_BY_INDEX(psci_svc, PSCI_STAT_ID_ENTER_LOW_PWR,
212 				last_cpu_in_non_cpu_pd[parent_idx],
213 				flags, pwrdn_ts);
214 
215 		/* Initialize back to reset value */
216 		last_cpu_in_non_cpu_pd[parent_idx] = -1;
217 
218 		/* Get the index into the stats array */
219 		stat_idx = get_stat_idx(local_state, lvl);
220 
221 		/* Calculate stats residency */
222 		calc_stat_residency(pwrup_ts, pwrdn_ts, residency);
223 
224 		/* Update non cpu stats */
225 		psci_non_cpu_stat[parent_idx][stat_idx].residency += residency;
226 		psci_non_cpu_stat[parent_idx][stat_idx].count++;
227 
228 		parent_idx = psci_non_cpu_pd_nodes[parent_idx].parent_node;
229 	}
230 
231 }
232 
233 /*******************************************************************************
234  * This function returns the appropriate count and residency time of the
235  * local state for the highest power level expressed in the `power_state`
236  * for the node represented by `target_cpu`.
237  ******************************************************************************/
238 int psci_get_stat(u_register_t target_cpu, unsigned int power_state,
239 			 psci_stat_t *psci_stat)
240 {
241 	int rc, pwrlvl, lvl, parent_idx, stat_idx, target_idx;
242 	psci_power_state_t state_info = { {PSCI_LOCAL_STATE_RUN} };
243 	plat_local_state_t local_state;
244 
245 	/* Validate the target_cpu parameter and determine the cpu index */
246 	target_idx = plat_core_pos_by_mpidr(target_cpu);
247 	if (target_idx == -1)
248 		return PSCI_E_INVALID_PARAMS;
249 
250 	/* Validate the power_state parameter */
251 	if (!psci_plat_pm_ops->translate_power_state_by_mpidr)
252 		rc = psci_validate_power_state(power_state, &state_info);
253 	else
254 		rc = psci_plat_pm_ops->translate_power_state_by_mpidr(
255 				target_cpu, power_state, &state_info);
256 
257 	if (rc != PSCI_E_SUCCESS)
258 		return PSCI_E_INVALID_PARAMS;
259 
260 	/* Find the highest power level */
261 	pwrlvl = psci_find_target_suspend_lvl(&state_info);
262 	if (pwrlvl == PSCI_INVALID_PWR_LVL) {
263 		ERROR("Invalid target power level for PSCI statistics operation\n");
264 		panic();
265 	}
266 
267 	/* Get the index into the stats array */
268 	local_state = state_info.pwr_domain_state[pwrlvl];
269 	stat_idx = get_stat_idx(local_state, pwrlvl);
270 
271 	if (pwrlvl > PSCI_CPU_PWR_LVL) {
272 		/* Get the power domain index */
273 		parent_idx = psci_cpu_pd_nodes[target_idx].parent_node;
274 		for (lvl = PSCI_CPU_PWR_LVL + 1; lvl < pwrlvl; lvl++)
275 			parent_idx = psci_non_cpu_pd_nodes[parent_idx].parent_node;
276 
277 		/* Get the non cpu power domain stats */
278 		*psci_stat = psci_non_cpu_stat[parent_idx][stat_idx];
279 	} else {
280 		/* Get the cpu power domain stats */
281 		*psci_stat = psci_cpu_stat[target_idx][stat_idx];
282 	}
283 
284 	return PSCI_E_SUCCESS;
285 }
286 
287 /* This is the top level function for PSCI_STAT_RESIDENCY SMC. */
288 u_register_t psci_stat_residency(u_register_t target_cpu,
289 		unsigned int power_state)
290 {
291 	psci_stat_t psci_stat;
292 
293 	int rc = psci_get_stat(target_cpu, power_state, &psci_stat);
294 	if (rc == PSCI_E_SUCCESS)
295 		return psci_stat.residency;
296 	else
297 		return 0;
298 }
299 
300 /* This is the top level function for PSCI_STAT_COUNT SMC. */
301 u_register_t psci_stat_count(u_register_t target_cpu,
302 	unsigned int power_state)
303 {
304 	psci_stat_t psci_stat;
305 
306 	int rc = psci_get_stat(target_cpu, power_state, &psci_stat);
307 	if (rc == PSCI_E_SUCCESS)
308 		return psci_stat.count;
309 	else
310 		return 0;
311 }
312