blob: 9b1a195149713075d3470f804f237c6884a54887 [file] [log] [blame]
Fuad Tabba5c738432019-12-02 11:02:42 +00001/*
2 * Copyright 2019 The Hafnium Authors.
3 *
Andrew Walbrane959ec12020-06-17 15:01:09 +01004 * 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.
Fuad Tabba5c738432019-12-02 11:02:42 +00007 */
8
9#include "hf/vcpu.h"
10
Olivier Depreze6f7b9d2021-02-01 11:55:48 +010011#include "hf/arch/cpu.h"
12
Fuad Tabba5c738432019-12-02 11:02:42 +000013#include "hf/check.h"
14#include "hf/dlog.h"
15#include "hf/std.h"
16#include "hf/vm.h"
17
Olivier Deprez181074b2023-02-02 14:53:23 +010018static struct vcpu *boot_vcpu;
19
J-Alves7ac49052022-02-08 17:20:53 +000020/** GP register to be used to pass the current vCPU ID, at core bring up. */
21#define PHYS_CORE_IDX_GP_REG 4
22
Fuad Tabba5c738432019-12-02 11:02:42 +000023/**
24 * Locks the given vCPU and updates `locked` to hold the newly locked vCPU.
25 */
26struct vcpu_locked vcpu_lock(struct vcpu *vcpu)
27{
28 struct vcpu_locked locked = {
29 .vcpu = vcpu,
30 };
31
32 sl_lock(&vcpu->lock);
33
34 return locked;
35}
36
37/**
Olivier Deprez0b6f10a2020-08-05 18:21:33 +020038 * Locks two vCPUs ensuring that the locking order is according to the locks'
39 * addresses.
40 */
41struct two_vcpu_locked vcpu_lock_both(struct vcpu *vcpu1, struct vcpu *vcpu2)
42{
43 struct two_vcpu_locked dual_lock;
44
45 sl_lock_both(&vcpu1->lock, &vcpu2->lock);
46 dual_lock.vcpu1.vcpu = vcpu1;
47 dual_lock.vcpu2.vcpu = vcpu2;
48
49 return dual_lock;
50}
51
52/**
Fuad Tabba5c738432019-12-02 11:02:42 +000053 * Unlocks a vCPU previously locked with vpu_lock, and updates `locked` to
54 * reflect the fact that the vCPU is no longer locked.
55 */
56void vcpu_unlock(struct vcpu_locked *locked)
57{
58 sl_unlock(&locked->vcpu->lock);
59 locked->vcpu = NULL;
60}
61
62void vcpu_init(struct vcpu *vcpu, struct vm *vm)
63{
64 memset_s(vcpu, sizeof(*vcpu), 0, sizeof(*vcpu));
65 sl_init(&vcpu->lock);
66 vcpu->regs_available = true;
67 vcpu->vm = vm;
68 vcpu->state = VCPU_STATE_OFF;
Olivier Deprezee9d6a92019-11-26 09:14:11 +000069 vcpu->direct_request_origin_vm_id = HF_INVALID_VM_ID;
Olivier Deprezb2808332023-02-02 15:25:40 +010070 vcpu->rt_model = RTM_SP_INIT;
Olivier Deprez181074b2023-02-02 14:53:23 +010071 vcpu->next_boot = NULL;
Fuad Tabba5c738432019-12-02 11:02:42 +000072}
73
74/**
75 * Initialise the registers for the given vCPU and set the state to
Madhukar Pappireddyb11e0d12021-08-02 19:44:35 -050076 * VCPU_STATE_WAITING. The caller must hold the vCPU lock while calling this.
Fuad Tabba5c738432019-12-02 11:02:42 +000077 */
78void vcpu_on(struct vcpu_locked vcpu, ipaddr_t entry, uintreg_t arg)
79{
80 arch_regs_set_pc_arg(&vcpu.vcpu->regs, entry, arg);
Madhukar Pappireddyb11e0d12021-08-02 19:44:35 -050081 vcpu.vcpu->state = VCPU_STATE_WAITING;
Fuad Tabba5c738432019-12-02 11:02:42 +000082}
83
Andrew Walbranb5ab43c2020-04-30 11:32:54 +010084ffa_vcpu_index_t vcpu_index(const struct vcpu *vcpu)
Fuad Tabba5c738432019-12-02 11:02:42 +000085{
86 size_t index = vcpu - vcpu->vm->vcpus;
87
88 CHECK(index < UINT16_MAX);
89 return index;
90}
91
92/**
93 * Check whether the given vcpu_state is an off state, for the purpose of
Madhukar Pappireddyb11e0d12021-08-02 19:44:35 -050094 * turning vCPUs on and off. Note that Aborted still counts as ON for the
95 * purposes of PSCI, because according to the PSCI specification (section
Olivier Depreze7eb1682022-03-16 17:09:03 +010096 * 5.7.1) a core is only considered to be off if it has been turned off
97 * with a CPU_OFF call or hasn't yet been turned on with a CPU_ON call.
Fuad Tabba5c738432019-12-02 11:02:42 +000098 */
99bool vcpu_is_off(struct vcpu_locked vcpu)
100{
Madhukar Pappireddyb11e0d12021-08-02 19:44:35 -0500101 return (vcpu.vcpu->state == VCPU_STATE_OFF);
Fuad Tabba5c738432019-12-02 11:02:42 +0000102}
103
104/**
105 * Starts a vCPU of a secondary VM.
106 *
107 * Returns true if the secondary was reset and started, or false if it was
108 * already on and so nothing was done.
109 */
Max Shvetsov40108e72020-08-27 12:39:50 +0100110bool vcpu_secondary_reset_and_start(struct vcpu_locked vcpu_locked,
111 ipaddr_t entry, uintreg_t arg)
Fuad Tabba5c738432019-12-02 11:02:42 +0000112{
Max Shvetsov40108e72020-08-27 12:39:50 +0100113 struct vm *vm = vcpu_locked.vcpu->vm;
Fuad Tabba5c738432019-12-02 11:02:42 +0000114 bool vcpu_was_off;
115
116 CHECK(vm->id != HF_PRIMARY_VM_ID);
117
Fuad Tabba5c738432019-12-02 11:02:42 +0000118 vcpu_was_off = vcpu_is_off(vcpu_locked);
119 if (vcpu_was_off) {
120 /*
121 * Set vCPU registers to a clean state ready for boot. As this
122 * is a secondary which can migrate between pCPUs, the ID of the
123 * vCPU is defined as the index and does not match the ID of the
124 * pCPU it is running on.
125 */
Max Shvetsov40108e72020-08-27 12:39:50 +0100126 arch_regs_reset(vcpu_locked.vcpu);
Fuad Tabba5c738432019-12-02 11:02:42 +0000127 vcpu_on(vcpu_locked, entry, arg);
128 }
Fuad Tabba5c738432019-12-02 11:02:42 +0000129
130 return vcpu_was_off;
131}
132
133/**
134 * Handles a page fault. It does so by determining if it's a legitimate or
135 * spurious fault, and recovering from the latter.
136 *
Fuad Tabbaed294af2019-12-20 10:43:01 +0000137 * Returns true if the caller should resume the current vCPU, or false if its VM
Fuad Tabba5c738432019-12-02 11:02:42 +0000138 * should be aborted.
139 */
140bool vcpu_handle_page_fault(const struct vcpu *current,
141 struct vcpu_fault_info *f)
142{
143 struct vm *vm = current->vm;
144 uint32_t mode;
145 uint32_t mask = f->mode | MM_MODE_INVALID;
146 bool resume;
Raghu Krishnamurthy785d52f2021-02-13 00:02:40 -0800147 struct vm_locked locked_vm;
Fuad Tabba5c738432019-12-02 11:02:42 +0000148
Raghu Krishnamurthy785d52f2021-02-13 00:02:40 -0800149 locked_vm = vm_lock(vm);
Fuad Tabba5c738432019-12-02 11:02:42 +0000150 /*
151 * Check if this is a legitimate fault, i.e., if the page table doesn't
152 * allow the access attempted by the VM.
153 *
154 * Otherwise, this is a spurious fault, likely because another CPU is
155 * updating the page table. It is responsible for issuing global TLB
156 * invalidations while holding the VM lock, so we don't need to do
157 * anything else to recover from it. (Acquiring/releasing the lock
158 * ensured that the invalidations have completed.)
159 */
Raghu Krishnamurthyb5775d22021-02-26 18:54:40 -0800160 if (!locked_vm.vm->el0_partition) {
161 resume = vm_mem_get_mode(locked_vm, f->ipaddr,
162 ipa_add(f->ipaddr, 1), &mode) &&
163 (mode & mask) == f->mode;
164 } else {
165 /*
166 * For EL0 partitions we need to get the mode for the faulting
167 * vaddr.
168 */
169 resume =
170 vm_mem_get_mode(locked_vm, ipa_init(va_addr(f->vaddr)),
171 ipa_add(ipa_init(va_addr(f->vaddr)), 1),
172 &mode) &&
173 (mode & mask) == f->mode;
Raghu Krishnamurthyf16b2ce2021-11-02 07:48:38 -0700174
175 /*
176 * For EL0 partitions, if there is an instruction abort and the
177 * mode of the page is RWX, we don't resume since Hafnium does
178 * not allow write and executable pages.
179 */
180 if ((f->mode == MM_MODE_X) &&
181 ((mode & MM_MODE_W) == MM_MODE_W)) {
182 resume = false;
183 }
Raghu Krishnamurthyb5775d22021-02-26 18:54:40 -0800184 }
Fuad Tabba5c738432019-12-02 11:02:42 +0000185
Raghu Krishnamurthy785d52f2021-02-13 00:02:40 -0800186 vm_unlock(&locked_vm);
Fuad Tabba5c738432019-12-02 11:02:42 +0000187
188 if (!resume) {
Andrew Walbran17eebf92020-02-05 16:35:49 +0000189 dlog_warning(
Raghu Krishnamurthyb5775d22021-02-26 18:54:40 -0800190 "Stage-%d page fault: pc=%#x, vmid=%#x, vcpu=%u, "
191 "vaddr=%#x, ipaddr=%#x, mode=%#x %#x\n",
192 current->vm->el0_partition ? 1 : 2, f->pc, vm->id,
193 vcpu_index(current), f->vaddr, f->ipaddr, f->mode,
194 mode);
Fuad Tabba5c738432019-12-02 11:02:42 +0000195 }
196
197 return resume;
198}
Olivier Deprez2ebae3a2020-06-11 16:34:30 +0200199
J-Alves7ac49052022-02-08 17:20:53 +0000200void vcpu_set_phys_core_idx(struct vcpu *vcpu)
201{
202 arch_regs_set_gp_reg(&vcpu->regs, cpu_index(vcpu->cpu),
203 PHYS_CORE_IDX_GP_REG);
204}
Olivier Deprez181074b2023-02-02 14:53:23 +0100205
206/**
Olivier Deprez632249e2022-09-26 09:18:31 +0200207 * Sets the designated GP register through which the vCPU expects to receive the
208 * boot info's address.
209 */
210void vcpu_set_boot_info_gp_reg(struct vcpu *vcpu)
211{
212 struct vm *vm = vcpu->vm;
213 uint32_t gp_register_num = vm->boot_info.gp_register_num;
214
215 if (vm->boot_info.blob_addr.ipa != 0U) {
216 arch_regs_set_gp_reg(&vcpu->regs,
217 ipa_addr(vm->boot_info.blob_addr),
218 gp_register_num);
219 }
220}
221
222/**
Olivier Deprez181074b2023-02-02 14:53:23 +0100223 * Gets the first partition to boot, according to Boot Protocol from FFA spec.
224 */
225struct vcpu *vcpu_get_boot_vcpu(void)
226{
227 return boot_vcpu;
228}
229
230/**
231 * Insert in boot list, sorted by `boot_order` parameter in the vm structure
232 * and rooted in `first_boot_vm`.
233 */
234void vcpu_update_boot(struct vcpu *vcpu)
235{
236 struct vcpu *current = NULL;
237 struct vcpu *previous = NULL;
238
239 if (boot_vcpu == NULL) {
240 boot_vcpu = vcpu;
241 return;
242 }
243
244 current = boot_vcpu;
245
246 while (current != NULL &&
247 current->vm->boot_order <= vcpu->vm->boot_order) {
248 previous = current;
249 current = current->next_boot;
250 }
251
252 if (previous != NULL) {
253 previous->next_boot = vcpu;
254 } else {
255 boot_vcpu = vcpu;
256 }
257
258 vcpu->next_boot = current;
259}
J-Alves12cedae2023-08-04 14:37:37 +0100260
261void vcpu_set_running(struct vcpu_locked target_locked, struct ffa_value args)
262{
263 struct vcpu *target_vcpu = target_locked.vcpu;
264
265 CHECK(target_vcpu->regs_available);
266 arch_regs_set_retval(&target_vcpu->regs, args);
267
268 /* Mark the registers as unavailable now. */
269 target_vcpu->regs_available = false;
270
271 /* We are about to resume target vCPU. */
272 target_vcpu->state = VCPU_STATE_RUNNING;
273}
274
275/**
276 * Saves the current interrupt priority.
277 */
278void vcpu_save_interrupt_priority(struct vcpu_locked vcpu_locked,
279 uint8_t priority)
280{
281 vcpu_locked.vcpu->priority_mask = priority;
282}
283
284/**
285 * It injects a virtual interrupt in the vcpu if is enabled and is not pending.
286 */
287void vcpu_interrupt_inject(struct vcpu_locked target_locked, uint32_t intid)
288{
289 struct vcpu *target_vcpu = target_locked.vcpu;
290 struct interrupts *interrupts = &target_vcpu->interrupts;
291
292 /*
293 * We only need to change state and (maybe) trigger a virtual interrupt
294 * if it is enabled and was not previously pending. Otherwise we can
295 * skip everything except setting the pending bit.
296 */
297 if (!(vcpu_is_virt_interrupt_enabled(interrupts, intid) &&
298 !vcpu_is_virt_interrupt_pending(interrupts, intid))) {
299 goto out;
300 }
301
302 /* Increment the count. */
303 vcpu_interrupt_count_increment(target_locked, interrupts, intid);
304
305 /*
306 * Only need to update state if there was not already an
307 * interrupt enabled and pending.
308 */
309 if (vcpu_interrupt_count_get(target_locked) != 1) {
310 goto out;
311 }
312
313out:
314 /* Either way, make it pending. */
315 vcpu_virt_interrupt_set_pending(interrupts, intid);
316}
317
318void vcpu_set_processing_interrupt(struct vcpu_locked vcpu_locked,
319 uint32_t intid, struct vcpu *preempted)
320{
321 struct vcpu *target_vcpu = vcpu_locked.vcpu;
322
323 target_vcpu->preempted_vcpu = preempted;
324 target_vcpu->processing_secure_interrupt = true;
325 target_vcpu->current_sec_interrupt_id = intid;
326}
327
328void vcpu_enter_secure_interrupt_rtm(struct vcpu_locked vcpu_locked)
329{
330 struct vcpu *target_vcpu = vcpu_locked.vcpu;
331
332 assert(target_vcpu->scheduling_mode == NONE);
333 assert(target_vcpu->call_chain.prev_node == NULL);
334 assert(target_vcpu->call_chain.next_node == NULL);
335 assert(target_vcpu->rt_model == RTM_NONE);
336
337 target_vcpu->scheduling_mode = SPMC_MODE;
338 target_vcpu->rt_model = RTM_SEC_INTERRUPT;
339}