blob: 7a663312531ee0ca9670f8708ae4071be1974e5f [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
11#include "hf/check.h"
12#include "hf/dlog.h"
13#include "hf/std.h"
14#include "hf/vm.h"
15
16/**
17 * Locks the given vCPU and updates `locked` to hold the newly locked vCPU.
18 */
19struct vcpu_locked vcpu_lock(struct vcpu *vcpu)
20{
21 struct vcpu_locked locked = {
22 .vcpu = vcpu,
23 };
24
25 sl_lock(&vcpu->lock);
26
27 return locked;
28}
29
30/**
31 * Unlocks a vCPU previously locked with vpu_lock, and updates `locked` to
32 * reflect the fact that the vCPU is no longer locked.
33 */
34void vcpu_unlock(struct vcpu_locked *locked)
35{
36 sl_unlock(&locked->vcpu->lock);
37 locked->vcpu = NULL;
38}
39
40void vcpu_init(struct vcpu *vcpu, struct vm *vm)
41{
42 memset_s(vcpu, sizeof(*vcpu), 0, sizeof(*vcpu));
43 sl_init(&vcpu->lock);
44 vcpu->regs_available = true;
45 vcpu->vm = vm;
46 vcpu->state = VCPU_STATE_OFF;
47}
48
49/**
50 * Initialise the registers for the given vCPU and set the state to
51 * VCPU_STATE_READY. The caller must hold the vCPU lock while calling this.
52 */
53void vcpu_on(struct vcpu_locked vcpu, ipaddr_t entry, uintreg_t arg)
54{
55 arch_regs_set_pc_arg(&vcpu.vcpu->regs, entry, arg);
56 vcpu.vcpu->state = VCPU_STATE_READY;
57}
58
Andrew Walbranb5ab43c2020-04-30 11:32:54 +010059ffa_vcpu_index_t vcpu_index(const struct vcpu *vcpu)
Fuad Tabba5c738432019-12-02 11:02:42 +000060{
61 size_t index = vcpu - vcpu->vm->vcpus;
62
63 CHECK(index < UINT16_MAX);
64 return index;
65}
66
67/**
68 * Check whether the given vcpu_state is an off state, for the purpose of
69 * turning vCPUs on and off. Note that aborted still counts as on in this
70 * context.
71 */
72bool vcpu_is_off(struct vcpu_locked vcpu)
73{
74 switch (vcpu.vcpu->state) {
75 case VCPU_STATE_OFF:
76 return true;
77 case VCPU_STATE_READY:
78 case VCPU_STATE_RUNNING:
79 case VCPU_STATE_BLOCKED_MAILBOX:
80 case VCPU_STATE_BLOCKED_INTERRUPT:
81 case VCPU_STATE_ABORTED:
82 /*
83 * Aborted still counts as ON for the purposes of PSCI,
84 * because according to the PSCI specification (section
85 * 5.7.1) a core is only considered to be off if it has
86 * been turned off with a CPU_OFF call or hasn't yet
87 * been turned on with a CPU_ON call.
88 */
89 return false;
90 }
91}
92
93/**
94 * Starts a vCPU of a secondary VM.
95 *
96 * Returns true if the secondary was reset and started, or false if it was
97 * already on and so nothing was done.
98 */
99bool vcpu_secondary_reset_and_start(struct vcpu *vcpu, ipaddr_t entry,
100 uintreg_t arg)
101{
102 struct vcpu_locked vcpu_locked;
103 struct vm *vm = vcpu->vm;
104 bool vcpu_was_off;
105
106 CHECK(vm->id != HF_PRIMARY_VM_ID);
107
108 vcpu_locked = vcpu_lock(vcpu);
109 vcpu_was_off = vcpu_is_off(vcpu_locked);
110 if (vcpu_was_off) {
111 /*
112 * Set vCPU registers to a clean state ready for boot. As this
113 * is a secondary which can migrate between pCPUs, the ID of the
114 * vCPU is defined as the index and does not match the ID of the
115 * pCPU it is running on.
116 */
117 arch_regs_reset(vcpu);
118 vcpu_on(vcpu_locked, entry, arg);
119 }
120 vcpu_unlock(&vcpu_locked);
121
122 return vcpu_was_off;
123}
124
125/**
126 * Handles a page fault. It does so by determining if it's a legitimate or
127 * spurious fault, and recovering from the latter.
128 *
Fuad Tabbaed294af2019-12-20 10:43:01 +0000129 * Returns true if the caller should resume the current vCPU, or false if its VM
Fuad Tabba5c738432019-12-02 11:02:42 +0000130 * should be aborted.
131 */
132bool vcpu_handle_page_fault(const struct vcpu *current,
133 struct vcpu_fault_info *f)
134{
135 struct vm *vm = current->vm;
136 uint32_t mode;
137 uint32_t mask = f->mode | MM_MODE_INVALID;
138 bool resume;
139
140 sl_lock(&vm->lock);
141
142 /*
143 * Check if this is a legitimate fault, i.e., if the page table doesn't
144 * allow the access attempted by the VM.
145 *
146 * Otherwise, this is a spurious fault, likely because another CPU is
147 * updating the page table. It is responsible for issuing global TLB
148 * invalidations while holding the VM lock, so we don't need to do
149 * anything else to recover from it. (Acquiring/releasing the lock
150 * ensured that the invalidations have completed.)
151 */
152 resume = mm_vm_get_mode(&vm->ptable, f->ipaddr, ipa_add(f->ipaddr, 1),
153 &mode) &&
154 (mode & mask) == f->mode;
155
156 sl_unlock(&vm->lock);
157
158 if (!resume) {
Andrew Walbran17eebf92020-02-05 16:35:49 +0000159 dlog_warning(
160 "Stage-2 page fault: pc=%#x, vmid=%u, vcpu=%u, "
161 "vaddr=%#x, ipaddr=%#x, mode=%#x\n",
162 f->pc, vm->id, vcpu_index(current), f->vaddr, f->ipaddr,
163 f->mode);
Fuad Tabba5c738432019-12-02 11:02:42 +0000164 }
165
166 return resume;
167}