1 /*
2 * Copyright 2019 The Hafnium Authors.
3 *
4 * Use of this source code is governed by a BSD-style
5 * license that can be found in the LICENSE file or at
6 * https://opensource.org/licenses/BSD-3-Clause.
7 */
8
9 #include "psci_handler.h"
10
11 #include <stdint.h>
12
13 #include "hf/arch/plat/psci.h"
14 #include "hf/arch/types.h"
15
16 #include "hf/api.h"
17 #include "hf/cpu.h"
18 #include "hf/dlog.h"
19 #include "hf/ffa.h"
20 #include "hf/panic.h"
21 #include "hf/vm.h"
22
23 #include "psci.h"
24
25 void cpu_entry(struct cpu *c);
26
27 /**
28 * Handles PSCI requests received via HVC or SMC instructions from the primary
29 * VM.
30 *
31 * A minimal PSCI 1.1 interface is offered which can make use of the
32 * implementation of PSCI in EL3 by acting as an adapter.
33 *
34 * Returns true if the request was a PSCI one, false otherwise.
35 */
psci_primary_vm_handler(struct vcpu * vcpu,uint32_t func,uintreg_t arg0,uintreg_t arg1,uintreg_t arg2,uintreg_t * ret)36 bool psci_primary_vm_handler(struct vcpu *vcpu, uint32_t func, uintreg_t arg0,
37 uintreg_t arg1, uintreg_t arg2, uintreg_t *ret)
38 {
39 struct cpu *c;
40 struct ffa_value smc_res;
41
42 /*
43 * If there's a problem with the EL3 PSCI, block standard secure service
44 * calls by marking them as unknown. Other calls will be allowed to pass
45 * through.
46 *
47 * This blocks more calls than just PSCI so it may need to be made more
48 * lenient in future.
49 */
50
51 if (plat_psci_version_get() == 0) {
52 *ret = SMCCC_ERROR_UNKNOWN;
53 return (func & SMCCC_SERVICE_CALL_MASK) ==
54 SMCCC_STANDARD_SECURE_SERVICE_CALL;
55 }
56
57 switch (func & ~SMCCC_CONVENTION_MASK) {
58 case PSCI_VERSION:
59 *ret = PSCI_VERSION_1_1;
60 break;
61
62 case PSCI_FEATURES:
63 switch (arg0 & ~SMCCC_CONVENTION_MASK) {
64 case SMCCC_VERSION_FUNC_ID:
65 *ret = SMCCC_VERSION_1_2;
66 break;
67
68 case PSCI_CPU_SUSPEND:
69 if (plat_psci_version_get() == PSCI_VERSION_0_2) {
70 /*
71 * PSCI 0.2 doesn't support PSCI_FEATURES so
72 * report PSCI 0.2 compatible features.
73 */
74 *ret = 0;
75 } else {
76 /* PSCI 1.x only defines two feature bits. */
77 smc_res = smc32(func, arg0, 0, 0, 0, 0, 0,
78 SMCCC_CALLER_HYPERVISOR);
79 *ret = smc_res.func & 0x3;
80 }
81 break;
82
83 case PSCI_VERSION:
84 case PSCI_FEATURES:
85 case PSCI_SYSTEM_OFF:
86 case PSCI_SYSTEM_RESET:
87 case PSCI_AFFINITY_INFO:
88 case PSCI_CPU_OFF:
89 case PSCI_CPU_ON:
90 /* These are supported without special features. */
91 *ret = 0;
92 break;
93
94 default:
95 /* Everything else is unsupported. */
96 *ret = PSCI_ERROR_NOT_SUPPORTED;
97 break;
98 }
99 break;
100
101 case PSCI_SYSTEM_OFF:
102 smc32(PSCI_SYSTEM_OFF, 0, 0, 0, 0, 0, 0,
103 SMCCC_CALLER_HYPERVISOR);
104 panic("System off failed");
105 break;
106
107 case PSCI_SYSTEM_RESET:
108 smc32(PSCI_SYSTEM_RESET, 0, 0, 0, 0, 0, 0,
109 SMCCC_CALLER_HYPERVISOR);
110 panic("System reset failed");
111 break;
112
113 case PSCI_AFFINITY_INFO:
114 c = cpu_find(arg0);
115 if (!c) {
116 *ret = PSCI_ERROR_INVALID_PARAMETERS;
117 break;
118 }
119
120 if (arg1 != 0) {
121 *ret = PSCI_ERROR_NOT_SUPPORTED;
122 break;
123 }
124
125 sl_lock(&c->lock);
126 if (c->is_on) {
127 *ret = PSCI_RETURN_ON;
128 } else {
129 *ret = PSCI_RETURN_OFF;
130 }
131 sl_unlock(&c->lock);
132 break;
133
134 case PSCI_CPU_SUSPEND: {
135 plat_psci_cpu_suspend(arg0);
136 /*
137 * Update vCPU state to wake from the provided entry point but
138 * if suspend returns, for example because it failed or was a
139 * standby power state, the SMC will return and the updated
140 * vCPU registers will be ignored.
141 */
142 arch_regs_set_pc_arg(&vcpu->regs, ipa_init(arg1), arg2);
143 smc_res = smc64(PSCI_CPU_SUSPEND, arg0, (uintreg_t)&cpu_entry,
144 (uintreg_t)vcpu->cpu, 0, 0, 0,
145 SMCCC_CALLER_HYPERVISOR);
146 *ret = smc_res.func;
147 break;
148 }
149
150 case PSCI_CPU_OFF:
151 cpu_off(vcpu->cpu);
152 smc32(PSCI_CPU_OFF, 0, 0, 0, 0, 0, 0, SMCCC_CALLER_HYPERVISOR);
153 panic("CPU off failed");
154 break;
155
156 case PSCI_CPU_ON:
157 c = cpu_find(arg0);
158 if (!c) {
159 *ret = PSCI_ERROR_INVALID_PARAMETERS;
160 break;
161 }
162
163 if (cpu_on(c, ipa_init(arg1), arg2)) {
164 *ret = PSCI_ERROR_ALREADY_ON;
165 break;
166 }
167
168 /*
169 * There's a race when turning a CPU on when it's in the
170 * process of turning off. We need to loop here while it is
171 * reported that the CPU is on (because it's about to turn
172 * itself off).
173 */
174 do {
175 smc_res = smc64(PSCI_CPU_ON, arg0,
176 (uintreg_t)&cpu_entry, (uintreg_t)c, 0,
177 0, 0, SMCCC_CALLER_HYPERVISOR);
178 *ret = smc_res.func;
179 } while (*ret == PSCI_ERROR_ALREADY_ON);
180
181 if (*ret != PSCI_RETURN_SUCCESS) {
182 cpu_off(c);
183 }
184 break;
185
186 case PSCI_MIGRATE:
187 case PSCI_MIGRATE_INFO_TYPE:
188 case PSCI_MIGRATE_INFO_UP_CPU:
189 case PSCI_CPU_FREEZE:
190 case PSCI_CPU_DEFAULT_SUSPEND:
191 case PSCI_NODE_HW_STATE:
192 case PSCI_SYSTEM_SUSPEND:
193 case PSCI_SET_SYSPEND_MODE:
194 case PSCI_STAT_RESIDENCY:
195 case PSCI_STAT_COUNT:
196 case PSCI_SYSTEM_RESET2:
197 case PSCI_MEM_PROTECT:
198 case PSCI_MEM_PROTECT_CHECK_RANGE:
199 /* Block all other known PSCI calls. */
200 *ret = PSCI_ERROR_NOT_SUPPORTED;
201 break;
202
203 default:
204 return false;
205 }
206
207 return true;
208 }
209
210 /**
211 * Convert a PSCI CPU / affinity ID for a secondary VM to the corresponding vCPU
212 * index.
213 */
vcpu_id_to_index(cpu_id_t vcpu_id)214 ffa_vcpu_index_t vcpu_id_to_index(cpu_id_t vcpu_id)
215 {
216 /* For now we use indices as IDs for the purposes of PSCI. */
217 return vcpu_id;
218 }
219
220 /**
221 * Handles PSCI requests received via HVC or SMC instructions from a secondary
222 * VM.
223 *
224 * A minimal PSCI 1.1 interface is offered which can start and stop vCPUs in
225 * collaboration with the scheduler in the primary VM.
226 *
227 * Returns true if the request was a PSCI one, false otherwise.
228 */
psci_secondary_vm_handler(struct vcpu * vcpu,uint32_t func,uintreg_t arg0,uintreg_t arg1,uintreg_t arg2,uintreg_t * ret,struct vcpu ** next)229 bool psci_secondary_vm_handler(struct vcpu *vcpu, uint32_t func, uintreg_t arg0,
230 uintreg_t arg1, uintreg_t arg2, uintreg_t *ret,
231 struct vcpu **next)
232 {
233 switch (func & ~SMCCC_CONVENTION_MASK) {
234 case PSCI_VERSION:
235 *ret = PSCI_VERSION_1_1;
236 break;
237
238 case PSCI_FEATURES:
239 switch (arg0 & ~SMCCC_CONVENTION_MASK) {
240 case PSCI_CPU_SUSPEND:
241 /*
242 * Does not offer OS-initiated mode but does use
243 * extended StateID Format.
244 */
245 *ret = 0x2;
246 break;
247
248 case PSCI_VERSION:
249 case PSCI_FEATURES:
250 case PSCI_AFFINITY_INFO:
251 case PSCI_CPU_OFF:
252 case PSCI_CPU_ON:
253 /* These are supported without special features. */
254 *ret = 0;
255 break;
256
257 default:
258 /* Everything else is unsupported. */
259 *ret = PSCI_ERROR_NOT_SUPPORTED;
260 break;
261 }
262 break;
263
264 case PSCI_AFFINITY_INFO: {
265 cpu_id_t target_affinity = arg0;
266 uint32_t lowest_affinity_level = arg1;
267 struct vm *vm = vcpu->vm;
268 struct vcpu_locked target_vcpu;
269 ffa_vcpu_index_t target_vcpu_index =
270 vcpu_id_to_index(target_affinity);
271
272 if (lowest_affinity_level != 0) {
273 /* Affinity levels greater than 0 not supported. */
274 *ret = PSCI_ERROR_INVALID_PARAMETERS;
275 break;
276 }
277
278 if (target_vcpu_index >= vm->vcpu_count) {
279 *ret = PSCI_ERROR_INVALID_PARAMETERS;
280 break;
281 }
282
283 target_vcpu = vcpu_lock(vm_get_vcpu(vm, target_vcpu_index));
284 *ret = vcpu_is_off(target_vcpu) ? PSCI_RETURN_OFF
285 : PSCI_RETURN_ON;
286 vcpu_unlock(&target_vcpu);
287 break;
288 }
289
290 case PSCI_CPU_SUSPEND: {
291 /*
292 * Downgrade suspend request to WFI and return SUCCESS, as
293 * allowed by the specification.
294 */
295 *next = api_wait_for_interrupt(vcpu);
296 *ret = PSCI_RETURN_SUCCESS;
297 break;
298 }
299
300 case PSCI_CPU_OFF:
301 /*
302 * Should never return to the caller, but in case it somehow
303 * does.
304 */
305 *ret = PSCI_ERROR_DENIED;
306 /* Tell the scheduler not to run the vCPU again. */
307 *next = api_vcpu_off(vcpu);
308 break;
309
310 case PSCI_CPU_ON: {
311 /* Parameter names as per PSCI specification. */
312 cpu_id_t target_cpu = arg0;
313 ipaddr_t entry_point_address = ipa_init(arg1);
314 uint64_t context_id = arg2;
315 ffa_vcpu_index_t target_vcpu_index =
316 vcpu_id_to_index(target_cpu);
317 struct vm *vm = vcpu->vm;
318 struct vcpu *target_vcpu;
319 struct vcpu_locked vcpu_locked;
320 bool vcpu_was_off;
321
322 if (target_vcpu_index >= vm->vcpu_count) {
323 *ret = PSCI_ERROR_INVALID_PARAMETERS;
324 break;
325 }
326
327 target_vcpu = vm_get_vcpu(vm, target_vcpu_index);
328 vcpu_locked = vcpu_lock(target_vcpu);
329 vcpu_was_off = vcpu_secondary_reset_and_start(
330 vcpu_locked, entry_point_address, context_id);
331 vcpu_unlock(&vcpu_locked);
332
333 if (vcpu_was_off) {
334 /*
335 * Tell the scheduler that it can start running the new
336 * vCPU now.
337 */
338 *next = api_wake_up(vcpu, target_vcpu);
339 *ret = PSCI_RETURN_SUCCESS;
340 } else {
341 *ret = PSCI_ERROR_ALREADY_ON;
342 }
343
344 break;
345 }
346
347 case PSCI_SYSTEM_OFF:
348 case PSCI_SYSTEM_RESET:
349 case PSCI_MIGRATE:
350 case PSCI_MIGRATE_INFO_TYPE:
351 case PSCI_MIGRATE_INFO_UP_CPU:
352 case PSCI_CPU_FREEZE:
353 case PSCI_CPU_DEFAULT_SUSPEND:
354 case PSCI_NODE_HW_STATE:
355 case PSCI_SYSTEM_SUSPEND:
356 case PSCI_SET_SYSPEND_MODE:
357 case PSCI_STAT_RESIDENCY:
358 case PSCI_STAT_COUNT:
359 case PSCI_SYSTEM_RESET2:
360 case PSCI_MEM_PROTECT:
361 case PSCI_MEM_PROTECT_CHECK_RANGE:
362 /* Block all other known PSCI calls. */
363 *ret = PSCI_ERROR_NOT_SUPPORTED;
364 break;
365
366 default:
367 return false;
368 }
369
370 return true;
371 }
372
373 /**
374 * Handles PSCI requests received via HVC or SMC instructions from a VM.
375 * Requests from primary and secondary VMs are dealt with differently.
376 *
377 * Returns true if the request was a PSCI one, false otherwise.
378 */
psci_handler(struct vcpu * vcpu,uint32_t func,uintreg_t arg0,uintreg_t arg1,uintreg_t arg2,uintreg_t * ret,struct vcpu ** next)379 bool psci_handler(struct vcpu *vcpu, uint32_t func, uintreg_t arg0,
380 uintreg_t arg1, uintreg_t arg2, uintreg_t *ret,
381 struct vcpu **next)
382 {
383 if (vcpu->vm->id == HF_PRIMARY_VM_ID) {
384 return psci_primary_vm_handler(vcpu, func, arg0, arg1, arg2,
385 ret);
386 }
387 return psci_secondary_vm_handler(vcpu, func, arg0, arg1, arg2, ret,
388 next);
389 }
390