| Karl Meakin | 9724b36 | 2024-10-15 14:35:02 +0100 | [diff] [blame] | 1 | /* |
| 2 | * Copyright 2024 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 | |
| Karl Meakin | 902af08 | 2024-11-28 14:58:38 +0000 | [diff] [blame] | 9 | #include "hf/ffa/indirect_messaging.h" |
| Karl Meakin | 9724b36 | 2024-10-15 14:35:02 +0100 | [diff] [blame] | 10 | |
| 11 | #include "hf/arch/other_world.h" |
| 12 | |
| 13 | #include "hf/api.h" |
| Karl Meakin | d8c9fff | 2025-02-10 12:26:48 +0000 | [diff] [blame] | 14 | #include "hf/ffa/init.h" |
| Karl Meakin | 9724b36 | 2024-10-15 14:35:02 +0100 | [diff] [blame] | 15 | #include "hf/ffa_internal.h" |
| 16 | #include "hf/vm.h" |
| 17 | |
| Karl Meakin | 117c808 | 2024-12-04 16:03:28 +0000 | [diff] [blame] | 18 | bool ffa_indirect_msg_is_supported(struct vm_locked sender_locked, |
| 19 | struct vm_locked receiver_locked) |
| Karl Meakin | 9724b36 | 2024-10-15 14:35:02 +0100 | [diff] [blame] | 20 | { |
| 21 | (void)sender_locked; |
| 22 | (void)receiver_locked; |
| 23 | |
| 24 | /* |
| 25 | * Hypervisor is only for testing purposes, always allow indirect |
| 26 | * messages from VM. |
| 27 | */ |
| 28 | return true; |
| 29 | } |
| 30 | |
| Karl Meakin | 117c808 | 2024-12-04 16:03:28 +0000 | [diff] [blame] | 31 | bool ffa_indirect_msg_send2_forward(ffa_id_t receiver_vm_id, |
| 32 | ffa_id_t sender_vm_id, |
| 33 | struct ffa_value *ret) |
| Karl Meakin | 9724b36 | 2024-10-15 14:35:02 +0100 | [diff] [blame] | 34 | { |
| 35 | /* FFA_MSG_SEND2 is forwarded to SPMC when the receiver is an SP. */ |
| Karl Meakin | 940a8d7 | 2024-10-15 14:13:30 +0100 | [diff] [blame] | 36 | if (vm_id_is_current_world(receiver_vm_id)) { |
| 37 | return false; |
| Karl Meakin | 9724b36 | 2024-10-15 14:35:02 +0100 | [diff] [blame] | 38 | } |
| 39 | |
| Karl Meakin | 940a8d7 | 2024-10-15 14:13:30 +0100 | [diff] [blame] | 40 | /* |
| 41 | * Set the sender in arg1 to allow the SPMC to retrieve |
| 42 | * VM's TX buffer to copy in SP's RX buffer. |
| 43 | */ |
| 44 | *ret = arch_other_world_call((struct ffa_value){ |
| 45 | .func = FFA_MSG_SEND2_32, |
| 46 | .arg1 = sender_vm_id << 16, |
| 47 | }); |
| 48 | |
| 49 | if (ffa_func_id(*ret) != FFA_SUCCESS_32) { |
| 50 | dlog_verbose( |
| 51 | "Failed forwarding FFA_MSG_SEND2_32 to the " |
| 52 | "SPMC, got error %s (%d).\n", |
| 53 | ffa_error_name(ffa_error_code(*ret)), |
| 54 | ffa_error_code(*ret)); |
| 55 | } |
| 56 | |
| 57 | return true; |
| Karl Meakin | 9724b36 | 2024-10-15 14:35:02 +0100 | [diff] [blame] | 58 | } |
| 59 | |
| 60 | /** |
| 61 | * Checks whether the vCPU's attempt to wait for a message has already been |
| 62 | * interrupted or whether it is allowed to block. |
| 63 | */ |
| Karl Meakin | 117c808 | 2024-12-04 16:03:28 +0000 | [diff] [blame] | 64 | static bool ffa_indirect_msg_recv_block_interrupted( |
| Karl Meakin | 9724b36 | 2024-10-15 14:35:02 +0100 | [diff] [blame] | 65 | struct vcpu_locked current_locked) |
| 66 | { |
| 67 | bool interrupted; |
| 68 | |
| 69 | /* |
| 70 | * Don't block if there are enabled and pending interrupts, to match |
| 71 | * behaviour of wait_for_interrupt. |
| 72 | */ |
| Daniel Boulby | d21e9b3 | 2025-02-13 15:53:21 +0000 | [diff] [blame^] | 73 | interrupted = (vcpu_virt_interrupt_count_get(current_locked) > 0); |
| Karl Meakin | 9724b36 | 2024-10-15 14:35:02 +0100 | [diff] [blame] | 74 | |
| 75 | return interrupted; |
| 76 | } |
| 77 | |
| 78 | /** |
| 79 | * Returns true if there is something in the return code, either a v1.0 |
| 80 | * FFA_MSG_SEND, or an FFA_ERROR. |
| 81 | */ |
| Karl Meakin | ca38ef9 | 2025-02-13 14:20:23 +0000 | [diff] [blame] | 82 | static bool ffa_indirect_msg_return_pending_messages(struct vm_locked vm_locked, |
| 83 | struct ffa_value *ret) |
| Karl Meakin | 9724b36 | 2024-10-15 14:35:02 +0100 | [diff] [blame] | 84 | { |
| 85 | /* Return pending messages without blocking. */ |
| 86 | if (vm_locked.vm->mailbox.state == MAILBOX_STATE_FULL) { |
| 87 | *ret = ffa_msg_recv_return(vm_locked.vm); |
| 88 | if (ret->func == FFA_MSG_SEND_32) { |
| 89 | vm_locked.vm->mailbox.state = MAILBOX_STATE_EMPTY; |
| 90 | } |
| 91 | return true; |
| 92 | } |
| 93 | |
| 94 | return false; |
| 95 | } |
| 96 | |
| 97 | /** |
| 98 | * Receives a message from the mailbox. If one isn't available, this function |
| 99 | * can optionally block the caller until one becomes available. |
| 100 | * |
| 101 | * No new messages can be received until the mailbox has been cleared. |
| 102 | */ |
| Karl Meakin | 117c808 | 2024-12-04 16:03:28 +0000 | [diff] [blame] | 103 | struct ffa_value ffa_indirect_msg_recv(bool block, |
| 104 | struct vcpu_locked current_locked, |
| 105 | struct vcpu **next) |
| Karl Meakin | 9724b36 | 2024-10-15 14:35:02 +0100 | [diff] [blame] | 106 | { |
| Karl Meakin | 9724b36 | 2024-10-15 14:35:02 +0100 | [diff] [blame] | 107 | struct vm *vm = current_locked.vcpu->vm; |
| 108 | struct vcpu *current = current_locked.vcpu; |
| 109 | struct vm_locked vm_locked; |
| 110 | struct ffa_value return_code; |
| 111 | |
| 112 | /* |
| 113 | * The primary VM will receive messages as a status code from running |
| 114 | * vCPUs and must not call this function. |
| 115 | */ |
| 116 | if (vm_is_primary(vm)) { |
| 117 | return ffa_error(FFA_NOT_SUPPORTED); |
| 118 | } |
| 119 | |
| 120 | /* |
| 121 | * Deny if vCPU is executing in context of an FFA_MSG_SEND_DIRECT_REQ |
| 122 | * invocation. |
| 123 | */ |
| Karl Meakin | 940a8d7 | 2024-10-15 14:13:30 +0100 | [diff] [blame] | 124 | if (is_ffa_direct_msg_request_ongoing(current_locked)) { |
| Karl Meakin | 9724b36 | 2024-10-15 14:35:02 +0100 | [diff] [blame] | 125 | return ffa_error(FFA_DENIED); |
| 126 | } |
| 127 | |
| 128 | vcpu_unlock(¤t_locked); |
| 129 | vm_locked = vm_lock(vm); |
| 130 | current_locked = vcpu_lock(current); |
| 131 | |
| Karl Meakin | ca38ef9 | 2025-02-13 14:20:23 +0000 | [diff] [blame] | 132 | if (ffa_indirect_msg_return_pending_messages(vm_locked, &return_code)) { |
| Karl Meakin | 9724b36 | 2024-10-15 14:35:02 +0100 | [diff] [blame] | 133 | goto out; |
| 134 | } |
| 135 | |
| 136 | /* No pending message so fail if not allowed to block. */ |
| 137 | if (!block) { |
| 138 | return_code = ffa_error(FFA_RETRY); |
| 139 | goto out; |
| 140 | } |
| 141 | |
| 142 | /* |
| 143 | * From this point onward this call can only be interrupted or a message |
| 144 | * received. If a message is received the return value will be set at |
| 145 | * that time to FFA_SUCCESS. |
| 146 | */ |
| 147 | return_code = ffa_error(FFA_INTERRUPTED); |
| Karl Meakin | 117c808 | 2024-12-04 16:03:28 +0000 | [diff] [blame] | 148 | if (ffa_indirect_msg_recv_block_interrupted(current_locked)) { |
| Karl Meakin | 9724b36 | 2024-10-15 14:35:02 +0100 | [diff] [blame] | 149 | goto out; |
| 150 | } |
| 151 | |
| 152 | { |
| 153 | /* Switch back to primary VM to block. */ |
| 154 | struct ffa_value run_return = { |
| 155 | .func = FFA_MSG_WAIT_32, |
| 156 | .arg1 = ffa_vm_vcpu(vm->id, |
| 157 | vcpu_index(current_locked.vcpu)), |
| 158 | }; |
| 159 | |
| 160 | *next = api_switch_to_primary(current_locked, run_return, |
| 161 | VCPU_STATE_WAITING); |
| 162 | } |
| 163 | out: |
| 164 | vm_unlock(&vm_locked); |
| 165 | |
| 166 | return return_code; |
| 167 | } |
| Karl Meakin | 2ae1abb | 2025-01-31 13:09:35 +0000 | [diff] [blame] | 168 | |
| 169 | /** |
| 170 | * Notifies the `to` VM about the message currently in its mailbox, possibly |
| 171 | * with the help of the primary VM. |
| 172 | */ |
| 173 | static struct ffa_value deliver_msg(struct vm_locked to, ffa_id_t from_id, |
| 174 | struct vcpu_locked current_locked, |
| 175 | struct vcpu **next) |
| 176 | { |
| 177 | struct ffa_value ret = (struct ffa_value){.func = FFA_SUCCESS_32}; |
| 178 | struct ffa_value primary_ret = { |
| 179 | .func = FFA_MSG_SEND_32, |
| 180 | .arg1 = ((uint32_t)from_id << 16) | to.vm->id, |
| 181 | }; |
| 182 | |
| 183 | /* Messages for the primary VM are delivered directly. */ |
| 184 | if (vm_is_primary(to.vm)) { |
| 185 | /* |
| 186 | * Only tell the primary VM the size and other details if the |
| 187 | * message is for it, to avoid leaking data about messages for |
| 188 | * other VMs. |
| 189 | */ |
| 190 | primary_ret = ffa_msg_recv_return(to.vm); |
| 191 | |
| 192 | *next = api_switch_to_primary(current_locked, primary_ret, |
| 193 | VCPU_STATE_BLOCKED); |
| 194 | return ret; |
| 195 | } |
| 196 | |
| 197 | to.vm->mailbox.state = MAILBOX_STATE_FULL; |
| 198 | |
| 199 | /* Messages for the TEE are sent on via the dispatcher. */ |
| 200 | if (to.vm->id == HF_TEE_VM_ID) { |
| 201 | struct ffa_value call = ffa_msg_recv_return(to.vm); |
| 202 | |
| 203 | ret = arch_other_world_call(call); |
| 204 | /* |
| 205 | * After the call to the TEE completes it must have finished |
| 206 | * reading its RX buffer, so it is ready for another message. |
| 207 | */ |
| 208 | to.vm->mailbox.state = MAILBOX_STATE_EMPTY; |
| 209 | /* |
| 210 | * Don't return to the primary VM in this case, as the TEE is |
| 211 | * not (yet) scheduled via FF-A. |
| 212 | */ |
| 213 | return ret; |
| 214 | } |
| 215 | |
| 216 | /* Return to the primary VM directly or with a switch. */ |
| 217 | if (from_id != HF_PRIMARY_VM_ID) { |
| 218 | *next = api_switch_to_primary(current_locked, primary_ret, |
| 219 | VCPU_STATE_BLOCKED); |
| 220 | } |
| 221 | |
| 222 | return ret; |
| 223 | } |
| 224 | |
| 225 | /* |
| 226 | * Copies data from the sender's send buffer to the recipient's receive buffer |
| 227 | * and notifies the recipient. |
| 228 | * |
| 229 | * If the recipient's receive buffer is busy, it can optionally register the |
| 230 | * caller to be notified when the recipient's receive buffer becomes available. |
| 231 | */ |
| 232 | struct ffa_value ffa_indirect_msg_send(ffa_id_t sender_vm_id, |
| 233 | ffa_id_t receiver_vm_id, uint32_t size, |
| 234 | struct vcpu *current, struct vcpu **next) |
| 235 | { |
| 236 | struct vm *from = current->vm; |
| 237 | struct vm *to; |
| 238 | struct vm_locked to_locked; |
| 239 | const void *from_msg; |
| 240 | struct ffa_value ret; |
| 241 | struct vcpu_locked current_locked; |
| 242 | bool is_direct_request_ongoing; |
| 243 | |
| 244 | /* Ensure sender VM ID corresponds to the current VM. */ |
| 245 | if (sender_vm_id != from->id) { |
| 246 | return ffa_error(FFA_INVALID_PARAMETERS); |
| 247 | } |
| 248 | |
| 249 | /* Disallow reflexive requests as this suggests an error in the VM. */ |
| 250 | if (receiver_vm_id == from->id) { |
| 251 | return ffa_error(FFA_INVALID_PARAMETERS); |
| 252 | } |
| 253 | |
| 254 | /* Limit the size of transfer. */ |
| 255 | if (size > FFA_MSG_PAYLOAD_MAX) { |
| 256 | return ffa_error(FFA_INVALID_PARAMETERS); |
| 257 | } |
| 258 | |
| 259 | /* Ensure the receiver VM exists. */ |
| 260 | to = vm_find(receiver_vm_id); |
| 261 | if (to == NULL) { |
| 262 | return ffa_error(FFA_INVALID_PARAMETERS); |
| 263 | } |
| 264 | |
| 265 | /* |
| 266 | * Deny if vCPU is executing in context of an FFA_MSG_SEND_DIRECT_REQ |
| 267 | * invocation. |
| 268 | */ |
| 269 | current_locked = vcpu_lock(current); |
| 270 | is_direct_request_ongoing = |
| 271 | is_ffa_direct_msg_request_ongoing(current_locked); |
| 272 | |
| 273 | if (is_direct_request_ongoing) { |
| 274 | ret = ffa_error(FFA_DENIED); |
| 275 | goto out_current; |
| 276 | } |
| 277 | |
| 278 | /* |
| 279 | * Check that the sender has configured its send buffer. If the tx |
| 280 | * mailbox at from_msg is configured (i.e. from_msg != NULL) then it can |
| 281 | * be safely accessed after releasing the lock since the tx mailbox |
| 282 | * address can only be configured once. |
| 283 | * A VM's lock must be acquired before any of its vCPU's lock. Hence, |
| 284 | * unlock current vCPU and acquire it immediately after its VM's lock. |
| 285 | */ |
| 286 | vcpu_unlock(¤t_locked); |
| 287 | sl_lock(&from->lock); |
| 288 | current_locked = vcpu_lock(current); |
| 289 | from_msg = from->mailbox.send; |
| 290 | sl_unlock(&from->lock); |
| 291 | |
| 292 | if (from_msg == NULL) { |
| 293 | ret = ffa_error(FFA_INVALID_PARAMETERS); |
| 294 | goto out_current; |
| 295 | } |
| 296 | |
| 297 | to_locked = vm_lock(to); |
| 298 | |
| 299 | if (vm_is_mailbox_busy(to_locked)) { |
| 300 | ret = ffa_error(FFA_BUSY); |
| 301 | goto out; |
| 302 | } |
| 303 | |
| 304 | /* Copy data. */ |
| 305 | memcpy_s(to->mailbox.recv, FFA_MSG_PAYLOAD_MAX, from_msg, size); |
| 306 | to->mailbox.recv_size = size; |
| 307 | to->mailbox.recv_sender = sender_vm_id; |
| 308 | to->mailbox.recv_func = FFA_MSG_SEND_32; |
| 309 | to->mailbox.state = MAILBOX_STATE_FULL; |
| 310 | ret = deliver_msg(to_locked, sender_vm_id, current_locked, next); |
| 311 | |
| 312 | out: |
| 313 | vm_unlock(&to_locked); |
| 314 | |
| 315 | out_current: |
| 316 | vcpu_unlock(¤t_locked); |
| 317 | |
| 318 | return ret; |
| 319 | } |