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