1/**
2 * \file psci.c
3 * \brief
4 */
5
6
7/*
8 * Copyright (c) 2017 ETH Zurich.
9 * All rights reserved.
10 *
11 * This file is distributed under the terms in the attached LICENSE file.
12 * If you do not find this file, copies can be found by writing to:
13 * ETH Zurich D-INFK, Universitaetsstrasse 6, CH-8092 Zurich. Attn: Systems Group.
14 */
15
16#include <kernel.h>
17#include <stddef.h>
18#include <errno.h>
19
20#include <arch/arm/smc_hvc.h>
21
22#include <psci.h>
23
24
25static uint64_t psci_use_hvc = 0;
26
27static inline void psci_invoke(psci_fn_t fn, uintptr_t arg0, uintptr_t arg1,
28                               uintptr_t arg2, struct arm_smc_hvc_retval *ret_val)
29{
30    /*
31     * TODO: we need to distinguish between SMC and HVC instructions to
32     *       call the PSCI function
33     */
34    if (psci_use_hvc)
35        invoke_arm_hvc(fn, arg0, arg1, arg2, 0, 0, 0, 0, ret_val);
36    else
37        invoke_arm_smc(fn, arg0, arg1, arg2, 0, 0, 0, 0, ret_val);
38}
39
40
41static errval_t psci_error_to_barrelfish(struct arm_smc_hvc_retval retval)
42{
43    int32_t err = (int32_t)(retval.a0 & 0xffffffff);
44
45    if (err >= 0) {
46        return SYS_ERR_OK;
47    }
48
49    if (err == PSCI_ERRNO_NOT_SUPPORTED) {
50        return PSCI_ERR_NOT_SUPPORTED;
51    } else if (err == PSCI_ERRNO_INVALID_PARAMETER) {
52        return PSCI_ERR_INVALID_PARAMETER;
53    } else if (err == PSCI_ERRNO_DENIED) {
54        return PSCI_ERR_DENIED;
55    } else if (err == PSCI_ERRNO_ALREADY_ON) {
56        return PSCI_ERR_ALREADY_ON;
57    } else if (err == PSCI_ERRNO_ON_PENDING) {
58        return PSCI_ERR_ON_PENDING;
59    } else if (err == PSCI_ERRNO_INTERNAL_FAILURE) {
60        return PSCI_ERR_INTERNAL_FAILURE;
61    } else if (err == PSCI_ERRNO_NOT_PRESENT) {
62        return PSCI_ERR_NOT_PRESENT;
63    } else if (err == PSCI_ERRNO_DISABLED) {
64        return PSCI_ERR_DISABLED;
65    } else if (err == PSCI_ERRNO_INVALID_ADDRESS) {
66        return PSCI_ERR_INVALID_ADDRESS;
67    }
68
69    return PSCI_ERR_UNKNOWN_ERROR;
70}
71
72/**
73 * @brief Return the version of PSCI implemented.
74 *
75 * @param major returns the major version
76 * @param minor returns the minor version
77 *
78 * @return SYS_ERR_OK on success
79 *         PSCI_ERR_* on failure
80 */
81errval_t psci_version(uint16_t *major, uint16_t *minor)
82{
83    struct arm_smc_hvc_retval retval;
84    psci_invoke(PSCI_FN_VERSION, 0, 0, 0, &retval);
85
86    uint32_t version = (uint32_t)retval.a0;
87    if (major) {
88        *major = (version  >> 16) & 0xffff;
89    }
90    if (minor) {
91        *minor = version & 0xffff;
92    }
93
94    return SYS_ERR_OK;
95}
96
97/**
98 * @brief Suspend execution on a core or higher level topology node.
99 *
100 * @param power_state   the power state to
101 * @param entry_point   Physical address of the location to resume execution
102 * @param context_id    Value left in x0/r0 when resuming execution
103 *
104 * @return SYS_ERR_OK on success
105 *         PSCI_ERR_INVALID_PARAMTER
106 *         PSCI_ERR_INVALID_ADDRESS
107 *         PSCI_ERR_DENIED
108 *
109 * This call is intended for use in idle subsystems where the core is expected
110 * to return to execution through a wakeup event. See section 5.4.
111 */
112errval_t psci_cpu_suspend(uint32_t power_state, lpaddr_t entry_point,
113                          uintptr_t context_id)
114{
115    struct arm_smc_hvc_retval retval;
116    psci_invoke(PSCI_FN_CPU_SUSPEND, power_state, entry_point, context_id, &retval);
117
118    return psci_error_to_barrelfish(retval);
119}
120
121/**
122 * @brief Power down the calling core.
123 *
124 * @return PSCI_ERR_DENIED on failure
125 *         The call does not return on success
126 *
127 * This call is intended for use in hotplug. A core that is powered down by
128 * CPU_OFF can only be powered up again in response to a CPU_ON.
129 */
130errval_t psci_cpu_off(void)
131{
132    struct arm_smc_hvc_retval retval;
133    psci_invoke(PSCI_FN_CPU_OFF, 0, 0, 0, &retval);
134
135    return psci_error_to_barrelfish(retval);
136}
137
138/**
139 * @brief Power up a core.
140 *
141 * @param target_cpu    MPIDR of the target processors
142 * @param entry_point   Address at which the core must commence execution
143 * @param context_id    Value left in x0/r0 when resuming execution
144 *
145 * @return  SYS_ERR_OK on success
146 *          PSCI_ERR_INVALID_PARAMETERS
147 *          PSCI_ERR_INVALID_ADDRESS
148 *          PSCI_ERR_ALREADY_ON
149 *          PSCI_ERR_ON_PENDING
150 *          PSCI_ERR_INTERNAL_FAILURE
151 *
152 * This call is used to power up cores that either:
153 * - Have not yet been booted into the calling supervisory software.
154 * - Have been previously powered down with a CPU_OFF call.
155 */
156errval_t psci_cpu_on(uintptr_t target_cpu, lpaddr_t entry_point,
157                     uintptr_t context_id)
158{
159    struct arm_smc_hvc_retval retval;
160    psci_invoke(PSCI_FN_CPU_ON, target_cpu, entry_point, context_id, &retval);
161
162    return psci_error_to_barrelfish(retval);
163}
164
165/**
166 * @brief Places the core into an IMPLEMENTATION DEFINED low-power state
167 *
168 * @return on success, does not return
169 *         PSCI_ERR_NOT_SUPPORTED
170 *         PSCI_ERR_DENIED
171 */
172errval_t psci_cpu_freeze(void)
173{
174    struct arm_smc_hvc_retval retval;
175    psci_invoke(PSCI_FN_CPU_FREEZE, 0, 0, 0, &retval);
176
177    return psci_error_to_barrelfish(retval);
178}
179
180/**
181 * @brief Will place a core into an IMPLEMENTATION DEFINED low-power state
182 *
183 * @param entry_point_address   Address to be executed when waking up
184 * @param context_id            Context pointer to be left in x0/r0
185 *
186 * @return SYS_ERR_OK on success
187 *         PSCI_ERR_INVALID_ADDRESS
188 */
189errval_t psci_cpu_default_suspend(lpaddr_t entry_point_address,
190                                  uintptr_t context_id)
191{
192    struct arm_smc_hvc_retval retval;
193    psci_invoke(PSCI_FN_CPU_DEFAULT_SUSPEND64, entry_point_address, context_id,
194                0, &retval);
195
196    return psci_error_to_barrelfish(retval);
197}
198
199
200/**
201 * @brief Enable the caller to request status of an affinity instance
202 *
203 * @param target_affinity       MPDIR of the target cpu
204 * @param lowest_affinity_level Denotes the lowest valid affinity level field
205 * @param ret_info
206 *
207 * @return
208 */
209errval_t psci_affinity_info(uintptr_t target_affinity,
210                            uint32_t lowest_affinity_level,
211                            psci_affinity_t *ret_info)
212{
213    struct arm_smc_hvc_retval retval;
214    psci_invoke(PSCI_FN_AFFINITY_INFO64, target_affinity, lowest_affinity_level,
215                0, &retval);
216
217    errval_t err =  psci_error_to_barrelfish(retval);
218    if (err_is_ok(err) && ret_info) {
219        *ret_info = (psci_affinity_t)retval.a0;
220    }
221
222    return err;
223}
224
225/**
226 * @brief Optional. This is used to ask a uniprocessor Trusted OS to migrate
227 *        its context to a specific core.
228 *
229 * @param target_cpu    MPDIR of the target core
230 *
231 * @return SYS_ERR_OK on success
232 *         PSCI_ERR_NOT_SUPPORTED
233 *         PSCI_ERR_INVALID_PARAMETERS
234 *         PSCI_ERR_DENIED
235 *         PSCI_ERR_INTERNAL_FAILURE
236 *         PSCI_ERR_NOT_PRESENT
237 */
238errval_t psci_migrate(uintptr_t target_cpu)
239{
240    struct arm_smc_hvc_retval retval;
241    psci_invoke(PSCI_FN_MIGRATE64, target_cpu, 0, 0, &retval);
242
243    return psci_error_to_barrelfish(retval);
244}
245
246/**
247 * @brief Optional. This function allows a caller to identify the level of
248 *        multicore support present in the Trusted OS
249 *
250 * @param ret_migrate_type  Migration type capabilities
251 *
252 * @return SYS_ERR_OK on success
253 *         PSCI_ERR_NOT_SUPPORTED if the operation is not supported
254 */
255errval_t psci_migrate_info_type(psci_migrate_t *ret_migrate_type)
256{
257    struct arm_smc_hvc_retval retval;
258    psci_invoke(PSCI_FN_MIGRATE_INFO_TYPE, 0, 0, 0, &retval);
259
260    errval_t err = psci_error_to_barrelfish(retval);
261    if (err_is_ok(err) && ret_migrate_type) {
262        *ret_migrate_type = (psci_migrate_t)(retval.a0 & 0xffffffff);
263    }
264
265    return err;
266}
267
268
269/**
270 * @brief Optional. For a uniprocessor Trusted OS, this function returns the
271 *        current resident core
272 *
273 * @param ret_mpdir     MPIDR based value of core where the Trusted OS is resident
274 *
275 * @return  SYS_ERR_OK on success
276 *          PSCI_ERR_NOT_SUPPORTED if not supported
277 */
278errval_t psci_migrate_info_up_cpu(uintptr_t *ret_mpdir)
279{
280    struct arm_smc_hvc_retval retval;
281    psci_invoke(PSCI_FN_MIGRATE_INFO_UP_CPU64, 0, 0, 0, &retval);
282
283    errval_t err = psci_error_to_barrelfish(retval);
284    if (err_is_ok(err) && ret_mpdir) {
285        *ret_mpdir = retval.a0;
286    }
287
288    return err;
289}
290
291/**
292 * @brief Shutdown the system.
293 */
294void psci_system_off(void)
295{
296    struct arm_smc_hvc_retval retval;
297    psci_invoke(PSCI_FN_SYSTEM_OFF, 0, 0, 0, &retval);
298    panic("SHOULD NOT BE REACHED!\n");
299}
300
301/**
302 * @brief Reset the system.
303 */
304void psci_system_reset(void)
305{
306    struct arm_smc_hvc_retval retval;
307    psci_invoke(PSCI_FN_SYSTEM_REST, 0, 0, 0, &retval);
308    panic("SHOULD NOT BE REACHED!\n");
309}
310
311/**
312 * @brief Used to implement suspend to RAM. The semantics are equivalent to a
313 *        CPU_SUSPEND to the deepest low-power state.
314 *
315 * @param entry_point_address   physical address to be executed when returning
316 * @param context_id            context id to be stored in x0/r0
317 *
318 * @return does not retturn on success
319 *         PSCI_ERR_NOT_SUPPORTED
320 *         PSCI_ERR_INVALID_ADDRESS
321 *         PSCI_ERR_ALREADY_ON
322 */
323errval_t psci_system_suspend(lpaddr_t entry_point_address, uintptr_t context_id)
324{
325    struct arm_smc_hvc_retval retval;
326    psci_invoke(PSCI_FN_SYSTEM_SUPSEND64, entry_point_address, context_id, 0,
327                &retval);
328
329    return psci_error_to_barrelfish(retval);
330}
331
332/**
333 * @brief Query API to discover whether a specific PSCI function is implemented
334 *
335 * @param psci_fn_id            Function ID for a PSCI Function
336 * @param ret_feature_flags     Returns a set of feature flags of the function
337 *
338 * @return SYS_ERR_OK on success
339 *         PSCI_ERR_NOT_SUPPORTED if the function is not supported or invalid
340 */
341errval_t psci_features(uint32_t psci_fn_id, uint32_t *ret_feature_flags)
342{
343    struct arm_smc_hvc_retval retval;
344    psci_invoke(PSCI_FN_PSCI_FEATURES, psci_fn_id, 0, 0, &retval);
345
346    errval_t err = psci_error_to_barrelfish(retval);
347    if (err_is_ok(err) && ret_feature_flags) {
348        *ret_feature_flags = (uint32_t)(retval.a0 & 0x7ffffff);
349    }
350
351    return err;
352}
353
354
355/**
356 * @brief This API is intended to return the true HW state of a node in the
357 *        power domain topology of the system
358 *
359 * @param target_cpu    Target CPU MPDIR
360 * @param power_level   Power domain level for the node
361 * @param ret_node_hw   return the power node hw state
362 *
363 * @return  SYS_ERR_OK on success
364 *          PSCI_ERR_NOT_SUPPORTED
365 *          PSCI_ERR_INVALID_PARAMETERS
366 */
367errval_t psci_node_hw_state(uintptr_t target_cpu, uint32_t power_level,
368                            psci_node_hw_state_t *ret_node_hw)
369{
370    struct arm_smc_hvc_retval retval;
371    psci_invoke(PSCI_FN_NODE_HW_STATE64, target_cpu, power_level,
372                0, &retval);
373
374    errval_t err =  psci_error_to_barrelfish(retval);
375    if (err_is_ok(err) && ret_node_hw) {
376        *ret_node_hw = (psci_node_hw_state_t)retval.a0;
377    }
378
379    return err;
380}
381
382
383/**
384 * @brief This API allows setting the mode used by CPU_SUSPEND to coordinate
385 *        power states.
386 *
387 * @param mode  Mode to put the system in
388 *
389 * @return SYS_ERR_OK on success,
390 *         PSCI_ERR_NOT_SUPPORTED
391 *         PSCI_ERR_INVALID_PARAMETERS
392 *         PSCI_ERR_DENIED
393 */
394errval_t psci_set_suspend_mode(psci_suspend_mode_t mode)
395{
396    struct arm_smc_hvc_retval retval;
397    psci_invoke(PSCI_FN_PSCI_SET_SUSPEND_MODE, mode, 0, 0, &retval);
398
399    return psci_error_to_barrelfish(retval);
400}
401
402/**
403 * @brief Returns the amount of time the platform has spent in the given power
404 *        state since cold boot.
405 *
406 * @param target_cpu    target CPU MPDIR
407 * @param power_state   power state to query
408 * @param ret_residency Returns the amount of time, in microseconds, spent in state
409 *
410 * @return  SYS_ERR_OK
411 */
412errval_t psci_stat_residency(uintptr_t target_cpu, uint32_t power_state,
413                             uintptr_t *ret_residency)
414{
415    struct arm_smc_hvc_retval retval;
416    psci_invoke(PSCI_FN_PSCI_STAT_RESIDENCY64, target_cpu, power_state,
417            0, &retval);
418
419    errval_t err =  psci_error_to_barrelfish(retval);
420    if (err_is_ok(err) && ret_residency) {
421        *ret_residency = retval.a0;
422    }
423
424    return err;
425}
426
427/**
428 * @brief Return the number of times the platform has used the given power state
429 *        since cold boot.
430 *
431 * @param target_cpu    target CPU MPDIR
432 * @param power_state   power state to query
433 * @param count         returns the count
434 *
435 * @return SYS_ERR_OK
436 */
437errval_t psci_stat_count(uintptr_t target_cpu, uint32_t power_state,
438                         uintptr_t *count)
439{
440    struct arm_smc_hvc_retval retval;
441    psci_invoke(PSCI_FN_PSCI_STAT_COUNT64, target_cpu, power_state,
442            0, &retval);
443
444    errval_t err =  psci_error_to_barrelfish(retval);
445    if (err_is_ok(err) && count) {
446        *count = retval.a0;
447    }
448
449    return err;
450}
451
452/**
453 * Change the PSCI's conduit to use hvc instead of smc
454 * @param use_hvc True, if hvc should be used
455 */
456void psci_set_use_hvc(uint64_t use_hvc)
457{
458    psci_use_hvc = use_hvc;
459}
460