blob: 9907554a18daaa32a7bef060ba4d981d48c00212 [file] [log] [blame]
Jose Marinho75509b42019-04-09 09:34:59 +01001/*
2 * Copyright 2019 The Hafnium Authors.
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * https://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
Andrew Walbran475c1452020-02-07 13:22:22 +000017#include "hf/spci_memory.h"
18
Jose Marinho75509b42019-04-09 09:34:59 +010019#include "hf/api.h"
Jose Marinho09b1db82019-08-08 09:16:59 +010020#include "hf/check.h"
Jose Marinho75509b42019-04-09 09:34:59 +010021#include "hf/dlog.h"
Andrew Walbran475c1452020-02-07 13:22:22 +000022#include "hf/mpool.h"
Jose Marinho75509b42019-04-09 09:34:59 +010023#include "hf/spci_internal.h"
24#include "hf/std.h"
Andrew Scull3c257452019-11-26 13:32:50 +000025#include "hf/vm.h"
Jose Marinho75509b42019-04-09 09:34:59 +010026
Andrew Walbran475c1452020-02-07 13:22:22 +000027struct spci_mem_transitions {
28 uint32_t orig_from_mode;
29 uint32_t orig_to_mode;
30 uint32_t from_mode;
31 uint32_t to_mode;
32};
33
34/* TODO: Add device attributes: GRE, cacheability, shareability. */
35static inline uint32_t spci_memory_attrs_to_mode(uint16_t memory_attributes)
36{
37 uint32_t mode = 0;
38
39 switch (spci_get_memory_access_attr(memory_attributes)) {
40 case SPCI_MEMORY_RO_NX:
41 mode = MM_MODE_R;
42 break;
43 case SPCI_MEMORY_RO_X:
44 mode = MM_MODE_R | MM_MODE_X;
45 break;
46 case SPCI_MEMORY_RW_NX:
47 mode = MM_MODE_R | MM_MODE_W;
48 break;
49 case SPCI_MEMORY_RW_X:
50 mode = MM_MODE_R | MM_MODE_W | MM_MODE_X;
51 break;
52 }
53
54 return mode;
55}
56
Jose Marinho75509b42019-04-09 09:34:59 +010057/**
Jose Marinho75509b42019-04-09 09:34:59 +010058 * Obtain the next mode to apply to the two VMs.
59 *
Jose Marinho7fbbf2e2019-08-05 13:19:58 +010060 * Returns true iff a state transition was found.
Jose Marinho75509b42019-04-09 09:34:59 +010061 */
62static bool spci_msg_get_next_state(
63 const struct spci_mem_transitions *transitions,
64 uint32_t transition_count, uint32_t memory_to_attributes,
Andrew Walbran1281ed42019-10-22 17:23:40 +010065 uint32_t orig_from_mode, uint32_t orig_to_mode, uint32_t *from_mode,
66 uint32_t *to_mode)
Jose Marinho75509b42019-04-09 09:34:59 +010067{
68 const uint32_t state_mask =
69 MM_MODE_INVALID | MM_MODE_UNOWNED | MM_MODE_SHARED;
70 const uint32_t orig_from_state = orig_from_mode & state_mask;
71
72 for (uint32_t index = 0; index < transition_count; index++) {
73 uint32_t table_orig_from_mode =
74 transitions[index].orig_from_mode;
75 uint32_t table_orig_to_mode = transitions[index].orig_to_mode;
76
77 if (((orig_from_state) == table_orig_from_mode) &&
78 ((orig_to_mode & state_mask) == table_orig_to_mode)) {
79 *to_mode = transitions[index].to_mode |
80 memory_to_attributes;
Jose Marinho713f13a2019-05-21 11:54:16 +010081
82 *from_mode = transitions[index].from_mode |
83 (~state_mask & orig_from_mode);
Jose Marinho75509b42019-04-09 09:34:59 +010084
85 return true;
86 }
87 }
88 return false;
89}
90
91/**
92 * Verify that all pages have the same mode, that the starting mode
93 * constitutes a valid state and obtain the next mode to apply
94 * to the two VMs.
95 *
96 * Returns:
97 * The error code false indicates that:
98 * 1) a state transition was not found;
99 * 2) the pages being shared do not have the same mode within the <to>
Andrew Walbran475c1452020-02-07 13:22:22 +0000100 * or <from> VMs;
Jose Marinho75509b42019-04-09 09:34:59 +0100101 * 3) The beginning and end IPAs are not page aligned;
102 * 4) The requested share type was not handled.
103 * Success is indicated by true.
Jose Marinho75509b42019-04-09 09:34:59 +0100104 */
Andrew Walbranf4b51af2020-02-03 14:44:54 +0000105static bool spci_msg_check_transition(
106 struct vm *to, struct vm *from, uint32_t share_func,
107 uint32_t *orig_from_mode,
108 struct spci_memory_region_constituent *constituents,
109 uint32_t constituent_count, uint32_t memory_to_attributes,
110 uint32_t *from_mode, uint32_t *to_mode)
Jose Marinho75509b42019-04-09 09:34:59 +0100111{
Andrew Walbran1281ed42019-10-22 17:23:40 +0100112 uint32_t orig_to_mode;
Jose Marinho75509b42019-04-09 09:34:59 +0100113 const struct spci_mem_transitions *mem_transition_table;
114 uint32_t transition_table_size;
Jose Marinho7fbbf2e2019-08-05 13:19:58 +0100115 uint32_t i;
Jose Marinho75509b42019-04-09 09:34:59 +0100116
117 /*
118 * TODO: Transition table does not currently consider the multiple
119 * shared case.
120 */
121 static const struct spci_mem_transitions donate_transitions[] = {
122 {
123 /* 1) {O-EA, !O-NA} -> {!O-NA, O-EA} */
124 .orig_from_mode = 0,
125 .orig_to_mode = MM_MODE_INVALID | MM_MODE_UNOWNED,
126 .from_mode = MM_MODE_INVALID | MM_MODE_UNOWNED,
127 .to_mode = 0,
128 },
129 {
130 /* 2) {O-NA, !O-EA} -> {!O-NA, O-EA} */
131 .orig_from_mode = MM_MODE_INVALID,
132 .orig_to_mode = MM_MODE_UNOWNED,
133 .from_mode = MM_MODE_INVALID | MM_MODE_UNOWNED,
134 .to_mode = 0,
135 },
136 {
137 /* 3) {O-SA, !O-SA} -> {!O-NA, O-EA} */
138 .orig_from_mode = MM_MODE_SHARED,
139 .orig_to_mode = MM_MODE_UNOWNED | MM_MODE_SHARED,
140 .from_mode = MM_MODE_INVALID | MM_MODE_UNOWNED,
141 .to_mode = 0,
142 },
143 {
144 /*
145 * Duplicate of 1) in order to cater for an alternative
146 * representation of !O-NA:
147 * (INVALID | UNOWNED | SHARED) and (INVALID | UNOWNED)
148 * are both alternate representations of !O-NA.
149 */
150 /* 4) {O-EA, !O-NA} -> {!O-NA, O-EA} */
151 .orig_from_mode = 0,
152 .orig_to_mode = MM_MODE_INVALID | MM_MODE_UNOWNED |
153 MM_MODE_SHARED,
154 .from_mode = MM_MODE_INVALID | MM_MODE_UNOWNED |
155 MM_MODE_SHARED,
156 .to_mode = 0,
157 },
158 };
Jose Marinho56c25732019-05-20 09:48:53 +0100159
Jose Marinho713f13a2019-05-21 11:54:16 +0100160 static const uint32_t size_donate_transitions =
161 ARRAY_SIZE(donate_transitions);
162
Andrew Walbran648fc3e2019-10-22 16:23:05 +0100163 /*
164 * This data structure holds the allowed state transitions for the
165 * "lend" state machine. In this state machine the owner keeps ownership
166 * but loses access to the lent pages.
167 */
168 static const struct spci_mem_transitions lend_transitions[] = {
Jose Marinho56c25732019-05-20 09:48:53 +0100169 {
Andrew Walbran648fc3e2019-10-22 16:23:05 +0100170 /* 1) {O-EA, !O-NA} -> {O-NA, !O-EA} */
171 .orig_from_mode = 0,
172 .orig_to_mode = MM_MODE_INVALID | MM_MODE_UNOWNED |
173 MM_MODE_SHARED,
174 .from_mode = MM_MODE_INVALID,
175 .to_mode = MM_MODE_UNOWNED,
Jose Marinho56c25732019-05-20 09:48:53 +0100176 },
177 {
Andrew Walbran648fc3e2019-10-22 16:23:05 +0100178 /*
179 * Duplicate of 1) in order to cater for an alternative
180 * representation of !O-NA:
181 * (INVALID | UNOWNED | SHARED) and (INVALID | UNOWNED)
182 * are both alternate representations of !O-NA.
183 */
184 /* 2) {O-EA, !O-NA} -> {O-NA, !O-EA} */
185 .orig_from_mode = 0,
186 .orig_to_mode = MM_MODE_INVALID | MM_MODE_UNOWNED,
187 .from_mode = MM_MODE_INVALID,
188 .to_mode = MM_MODE_UNOWNED,
Jose Marinho56c25732019-05-20 09:48:53 +0100189 },
190 };
191
Andrew Walbran648fc3e2019-10-22 16:23:05 +0100192 static const uint32_t size_lend_transitions =
193 ARRAY_SIZE(lend_transitions);
Jose Marinho56c25732019-05-20 09:48:53 +0100194
Jose Marinho713f13a2019-05-21 11:54:16 +0100195 /*
Andrew Walbran648fc3e2019-10-22 16:23:05 +0100196 * This data structure holds the allowed state transitions for the
197 * "share" state machine. In this state machine the owner keeps the
198 * shared pages mapped on its stage2 table and keeps access as well.
Jose Marinho713f13a2019-05-21 11:54:16 +0100199 */
Andrew Walbran648fc3e2019-10-22 16:23:05 +0100200 static const struct spci_mem_transitions share_transitions[] = {
Jose Marinho713f13a2019-05-21 11:54:16 +0100201 {
202 /* 1) {O-EA, !O-NA} -> {O-SA, !O-SA} */
203 .orig_from_mode = 0,
204 .orig_to_mode = MM_MODE_INVALID | MM_MODE_UNOWNED |
205 MM_MODE_SHARED,
206 .from_mode = MM_MODE_SHARED,
207 .to_mode = MM_MODE_UNOWNED | MM_MODE_SHARED,
208 },
209 {
210 /*
211 * Duplicate of 1) in order to cater for an alternative
212 * representation of !O-NA:
213 * (INVALID | UNOWNED | SHARED) and (INVALID | UNOWNED)
214 * are both alternate representations of !O-NA.
215 */
216 /* 2) {O-EA, !O-NA} -> {O-SA, !O-SA} */
217 .orig_from_mode = 0,
218 .orig_to_mode = MM_MODE_INVALID | MM_MODE_UNOWNED,
219 .from_mode = MM_MODE_SHARED,
220 .to_mode = MM_MODE_UNOWNED | MM_MODE_SHARED,
221 },
222 };
223
Andrew Walbran648fc3e2019-10-22 16:23:05 +0100224 static const uint32_t size_share_transitions =
225 ARRAY_SIZE(share_transitions);
226
227 static const struct spci_mem_transitions relinquish_transitions[] = {
228 {
229 /* 1) {!O-EA, O-NA} -> {!O-NA, O-EA} */
230 .orig_from_mode = MM_MODE_UNOWNED,
231 .orig_to_mode = MM_MODE_INVALID,
232 .from_mode = MM_MODE_INVALID | MM_MODE_UNOWNED |
233 MM_MODE_SHARED,
234 .to_mode = 0,
235 },
236 {
237 /* 2) {!O-SA, O-SA} -> {!O-NA, O-EA} */
238 .orig_from_mode = MM_MODE_UNOWNED | MM_MODE_SHARED,
239 .orig_to_mode = MM_MODE_SHARED,
240 .from_mode = MM_MODE_INVALID | MM_MODE_UNOWNED |
241 MM_MODE_SHARED,
242 .to_mode = 0,
243 },
244 };
245
246 static const uint32_t size_relinquish_transitions =
247 ARRAY_SIZE(relinquish_transitions);
Jose Marinho75509b42019-04-09 09:34:59 +0100248
Andrew Walbranf4b51af2020-02-03 14:44:54 +0000249 if (constituent_count == 0) {
Jose Marinho7fbbf2e2019-08-05 13:19:58 +0100250 /*
251 * Fail if there are no constituents. Otherwise
252 * spci_msg_get_next_state would get an unitialised
253 * *orig_from_mode and orig_to_mode.
254 */
Jose Marinho75509b42019-04-09 09:34:59 +0100255 return false;
256 }
257
Andrew Walbranf4b51af2020-02-03 14:44:54 +0000258 for (i = 0; i < constituent_count; ++i) {
Andrew Walbrand88ee922020-01-15 18:13:21 +0000259 ipaddr_t begin =
260 ipa_init(spci_memory_region_constituent_get_address(
261 &constituents[i]));
Jose Marinho7fbbf2e2019-08-05 13:19:58 +0100262 size_t size = constituents[i].page_count * PAGE_SIZE;
263 ipaddr_t end = ipa_add(begin, size);
264 uint32_t current_from_mode;
265 uint32_t current_to_mode;
266
267 /* Fail if addresses are not page-aligned. */
268 if (!is_aligned(ipa_addr(begin), PAGE_SIZE) ||
269 !is_aligned(ipa_addr(end), PAGE_SIZE)) {
270 return false;
271 }
272
273 /*
274 * Ensure that this constituent memory range is all mapped with
275 * the same mode.
276 */
277 if (!mm_vm_get_mode(&from->ptable, begin, end,
278 &current_from_mode) ||
279 !mm_vm_get_mode(&to->ptable, begin, end,
280 &current_to_mode)) {
281 return false;
282 }
283
284 /*
285 * Ensure that all constituents are mapped with the same mode.
286 */
287 if (i == 0) {
288 *orig_from_mode = current_from_mode;
289 orig_to_mode = current_to_mode;
290 } else if (current_from_mode != *orig_from_mode ||
291 current_to_mode != orig_to_mode) {
292 return false;
293 }
Jose Marinho75509b42019-04-09 09:34:59 +0100294 }
295
Andrew Scullb5f49e02019-10-02 13:20:47 +0100296 /* Ensure the address range is normal memory and not a device. */
297 if (*orig_from_mode & MM_MODE_D) {
298 return false;
299 }
300
Andrew Walbrane7ad3c02019-12-24 17:03:04 +0000301 switch (share_func) {
302 case SPCI_MEM_DONATE_32:
Jose Marinho75509b42019-04-09 09:34:59 +0100303 mem_transition_table = donate_transitions;
304 transition_table_size = size_donate_transitions;
305 break;
306
Andrew Walbrane7ad3c02019-12-24 17:03:04 +0000307 case SPCI_MEM_LEND_32:
Andrew Walbran648fc3e2019-10-22 16:23:05 +0100308 mem_transition_table = lend_transitions;
309 transition_table_size = size_lend_transitions;
310 break;
311
Andrew Walbrane7ad3c02019-12-24 17:03:04 +0000312 case SPCI_MEM_SHARE_32:
Andrew Walbran648fc3e2019-10-22 16:23:05 +0100313 mem_transition_table = share_transitions;
314 transition_table_size = size_share_transitions;
315 break;
316
Andrew Walbrane7ad3c02019-12-24 17:03:04 +0000317 case HF_SPCI_MEM_RELINQUISH:
Jose Marinho56c25732019-05-20 09:48:53 +0100318 mem_transition_table = relinquish_transitions;
319 transition_table_size = size_relinquish_transitions;
320 break;
321
Jose Marinho75509b42019-04-09 09:34:59 +0100322 default:
323 return false;
324 }
325
326 return spci_msg_get_next_state(mem_transition_table,
327 transition_table_size,
328 memory_to_attributes, *orig_from_mode,
329 orig_to_mode, from_mode, to_mode);
330}
Jose Marinho09b1db82019-08-08 09:16:59 +0100331
332/**
333 * Updates a VM's page table such that the given set of physical address ranges
334 * are mapped in the address space at the corresponding address ranges, in the
335 * mode provided.
336 *
337 * If commit is false, the page tables will be allocated from the mpool but no
338 * mappings will actually be updated. This function must always be called first
339 * with commit false to check that it will succeed before calling with commit
340 * true, to avoid leaving the page table in a half-updated state. To make a
341 * series of changes atomically you can call them all with commit false before
342 * calling them all with commit true.
343 *
344 * mm_vm_defrag should always be called after a series of page table updates,
345 * whether they succeed or fail.
346 *
347 * Returns true on success, or false if the update failed and no changes were
348 * made to memory mappings.
349 */
350static bool spci_region_group_identity_map(
Andrew Walbranf4b51af2020-02-03 14:44:54 +0000351 struct vm_locked vm_locked,
352 struct spci_memory_region_constituent *constituents,
353 uint32_t constituent_count, int mode, struct mpool *ppool, bool commit)
Jose Marinho09b1db82019-08-08 09:16:59 +0100354{
Jose Marinho09b1db82019-08-08 09:16:59 +0100355 /* Iterate over the memory region constituents. */
Andrew Walbranf4b51af2020-02-03 14:44:54 +0000356 for (uint32_t index = 0; index < constituent_count; index++) {
Jose Marinho09b1db82019-08-08 09:16:59 +0100357 size_t size = constituents[index].page_count * PAGE_SIZE;
Andrew Walbrand88ee922020-01-15 18:13:21 +0000358 paddr_t pa_begin = pa_from_ipa(
359 ipa_init(spci_memory_region_constituent_get_address(
360 &constituents[index])));
Jose Marinho09b1db82019-08-08 09:16:59 +0100361 paddr_t pa_end = pa_add(pa_begin, size);
362
363 if (commit) {
Andrew Scull3c257452019-11-26 13:32:50 +0000364 vm_identity_commit(vm_locked, pa_begin, pa_end, mode,
365 ppool, NULL);
366 } else if (!vm_identity_prepare(vm_locked, pa_begin, pa_end,
367 mode, ppool)) {
Jose Marinho09b1db82019-08-08 09:16:59 +0100368 return false;
369 }
370 }
371
372 return true;
373}
374
375/**
376 * Clears a region of physical memory by overwriting it with zeros. The data is
377 * flushed from the cache so the memory has been cleared across the system.
378 */
379static bool clear_memory(paddr_t begin, paddr_t end, struct mpool *ppool)
380{
381 /*
Fuad Tabbaed294af2019-12-20 10:43:01 +0000382 * TODO: change this to a CPU local single page window rather than a
Jose Marinho09b1db82019-08-08 09:16:59 +0100383 * global mapping of the whole range. Such an approach will limit
384 * the changes to stage-1 tables and will allow only local
385 * invalidation.
386 */
387 bool ret;
388 struct mm_stage1_locked stage1_locked = mm_lock_stage1();
389 void *ptr =
390 mm_identity_map(stage1_locked, begin, end, MM_MODE_W, ppool);
391 size_t size = pa_difference(begin, end);
392
393 if (!ptr) {
394 /* TODO: partial defrag of failed range. */
395 /* Recover any memory consumed in failed mapping. */
396 mm_defrag(stage1_locked, ppool);
397 goto fail;
398 }
399
400 memset_s(ptr, size, 0, size);
401 arch_mm_flush_dcache(ptr, size);
402 mm_unmap(stage1_locked, begin, end, ppool);
403
404 ret = true;
405 goto out;
406
407fail:
408 ret = false;
409
410out:
411 mm_unlock_stage1(&stage1_locked);
412
413 return ret;
414}
415
416/**
417 * Clears a region of physical memory by overwriting it with zeros. The data is
418 * flushed from the cache so the memory has been cleared across the system.
419 */
Andrew Walbranf4b51af2020-02-03 14:44:54 +0000420static bool spci_clear_memory_constituents(
421 struct spci_memory_region_constituent *constituents,
Andrew Walbran475c1452020-02-07 13:22:22 +0000422 uint32_t constituent_count, struct mpool *page_pool)
Jose Marinho09b1db82019-08-08 09:16:59 +0100423{
424 struct mpool local_page_pool;
Jose Marinho09b1db82019-08-08 09:16:59 +0100425 struct mm_stage1_locked stage1_locked;
426 bool ret = false;
427
428 /*
429 * Create a local pool so any freed memory can't be used by another
430 * thread. This is to ensure each constituent that is mapped can be
431 * unmapped again afterwards.
432 */
Andrew Walbran475c1452020-02-07 13:22:22 +0000433 mpool_init_with_fallback(&local_page_pool, page_pool);
Jose Marinho09b1db82019-08-08 09:16:59 +0100434
435 /* Iterate over the memory region constituents. */
Andrew Walbranf4b51af2020-02-03 14:44:54 +0000436 for (uint32_t i = 0; i < constituent_count; ++i) {
Jose Marinho09b1db82019-08-08 09:16:59 +0100437 size_t size = constituents[i].page_count * PAGE_SIZE;
Andrew Walbrand88ee922020-01-15 18:13:21 +0000438 paddr_t begin = pa_from_ipa(
439 ipa_init(spci_memory_region_constituent_get_address(
440 &constituents[i])));
Jose Marinho09b1db82019-08-08 09:16:59 +0100441 paddr_t end = pa_add(begin, size);
442
443 if (!clear_memory(begin, end, &local_page_pool)) {
444 /*
445 * api_clear_memory will defrag on failure, so no need
446 * to do it here.
447 */
448 goto out;
449 }
450 }
451
452 /*
453 * Need to defrag after clearing, as it may have added extra mappings to
454 * the stage 1 page table.
455 */
456 stage1_locked = mm_lock_stage1();
457 mm_defrag(stage1_locked, &local_page_pool);
458 mm_unlock_stage1(&stage1_locked);
459
460 ret = true;
461
462out:
463 mpool_fini(&local_page_pool);
464 return ret;
465}
466
467/**
468 * Shares memory from the calling VM with another. The memory can be shared in
469 * different modes.
470 *
471 * This function requires the calling context to hold the <to> and <from> locks.
472 *
473 * Returns:
474 * In case of error one of the following values is returned:
475 * 1) SPCI_INVALID_PARAMETERS - The endpoint provided parameters were
476 * erroneous;
477 * 2) SPCI_NO_MEMORY - Hafnium did not have sufficient memory to complete
478 * the request.
479 * Success is indicated by SPCI_SUCCESS.
480 */
481static struct spci_value spci_share_memory(
482 struct vm_locked to_locked, struct vm_locked from_locked,
Andrew Walbranf4b51af2020-02-03 14:44:54 +0000483 struct spci_memory_region_constituent *constituents,
484 uint32_t constituent_count, uint32_t memory_to_attributes,
Andrew Walbran475c1452020-02-07 13:22:22 +0000485 uint32_t share_func, struct mpool *page_pool, bool clear)
Jose Marinho09b1db82019-08-08 09:16:59 +0100486{
487 struct vm *to = to_locked.vm;
488 struct vm *from = from_locked.vm;
489 uint32_t orig_from_mode;
490 uint32_t from_mode;
491 uint32_t to_mode;
492 struct mpool local_page_pool;
493 struct spci_value ret;
Jose Marinho09b1db82019-08-08 09:16:59 +0100494
495 /*
Andrew Walbrand88ee922020-01-15 18:13:21 +0000496 * Make sure constituents are properly aligned to a 32-bit boundary. If
497 * not we would get alignment faults trying to read (32-bit) values.
Jose Marinho09b1db82019-08-08 09:16:59 +0100498 */
Andrew Walbrand88ee922020-01-15 18:13:21 +0000499 if (!is_aligned(constituents, 4)) {
Jose Marinho09b1db82019-08-08 09:16:59 +0100500 return spci_error(SPCI_INVALID_PARAMETERS);
501 }
502
503 /* Disallow reflexive shares as this suggests an error in the VM. */
504 if (to == from) {
505 return spci_error(SPCI_INVALID_PARAMETERS);
506 }
507
508 /*
509 * Check if the state transition is lawful for both VMs involved
510 * in the memory exchange, ensure that all constituents of a memory
511 * region being shared are at the same state.
512 */
Andrew Walbrane7ad3c02019-12-24 17:03:04 +0000513 if (!spci_msg_check_transition(to, from, share_func, &orig_from_mode,
Andrew Walbranf4b51af2020-02-03 14:44:54 +0000514 constituents, constituent_count,
515 memory_to_attributes, &from_mode,
516 &to_mode)) {
Jose Marinho09b1db82019-08-08 09:16:59 +0100517 return spci_error(SPCI_INVALID_PARAMETERS);
518 }
519
520 /*
521 * Create a local pool so any freed memory can't be used by another
522 * thread. This is to ensure the original mapping can be restored if the
523 * clear fails.
524 */
Andrew Walbran475c1452020-02-07 13:22:22 +0000525 mpool_init_with_fallback(&local_page_pool, page_pool);
Jose Marinho09b1db82019-08-08 09:16:59 +0100526
527 /*
528 * First reserve all required memory for the new page table entries in
529 * both sender and recipient page tables without committing, to make
530 * sure the entire operation will succeed without exhausting the page
531 * pool.
532 */
Andrew Walbranf4b51af2020-02-03 14:44:54 +0000533 if (!spci_region_group_identity_map(from_locked, constituents,
534 constituent_count, from_mode,
Andrew Walbran475c1452020-02-07 13:22:22 +0000535 page_pool, false) ||
Andrew Walbranf4b51af2020-02-03 14:44:54 +0000536 !spci_region_group_identity_map(to_locked, constituents,
537 constituent_count, to_mode,
Andrew Walbran475c1452020-02-07 13:22:22 +0000538 page_pool, false)) {
Jose Marinho09b1db82019-08-08 09:16:59 +0100539 /* TODO: partial defrag of failed range. */
540 ret = spci_error(SPCI_NO_MEMORY);
541 goto out;
542 }
543
544 /*
545 * First update the mapping for the sender so there is no overlap with
546 * the recipient. This won't allocate because the transaction was
547 * already prepared above, but may free pages in the case that a whole
548 * block is being unmapped that was previously partially mapped.
549 */
Andrew Walbranf4b51af2020-02-03 14:44:54 +0000550 CHECK(spci_region_group_identity_map(from_locked, constituents,
551 constituent_count, from_mode,
552 &local_page_pool, true));
Jose Marinho09b1db82019-08-08 09:16:59 +0100553
554 /* Clear the memory so no VM or device can see the previous contents. */
Andrew Walbranf4b51af2020-02-03 14:44:54 +0000555 if (clear && !spci_clear_memory_constituents(
Andrew Walbran475c1452020-02-07 13:22:22 +0000556 constituents, constituent_count, page_pool)) {
Jose Marinho09b1db82019-08-08 09:16:59 +0100557 /*
558 * On failure, roll back by returning memory to the sender. This
559 * may allocate pages which were previously freed into
560 * `local_page_pool` by the call above, but will never allocate
561 * more pages than that so can never fail.
562 */
Andrew Walbranf4b51af2020-02-03 14:44:54 +0000563 CHECK(spci_region_group_identity_map(
564 from_locked, constituents, constituent_count,
565 orig_from_mode, &local_page_pool, true));
Jose Marinho09b1db82019-08-08 09:16:59 +0100566
567 ret = spci_error(SPCI_NO_MEMORY);
568 goto out;
569 }
570
571 /*
572 * Complete the transfer by mapping the memory into the recipient. This
573 * won't allocate because the transaction was already prepared above, so
574 * it doesn't need to use the `local_page_pool`.
575 */
Andrew Walbranf4b51af2020-02-03 14:44:54 +0000576 CHECK(spci_region_group_identity_map(to_locked, constituents,
577 constituent_count, to_mode,
Andrew Walbran475c1452020-02-07 13:22:22 +0000578 page_pool, true));
Jose Marinho09b1db82019-08-08 09:16:59 +0100579
580 ret = (struct spci_value){.func = SPCI_SUCCESS_32};
581
582out:
583 mpool_fini(&local_page_pool);
584
585 /*
586 * Tidy up the page tables by reclaiming failed mappings (if there was
587 * an error) or merging entries into blocks where possible (on success).
588 */
Andrew Walbran475c1452020-02-07 13:22:22 +0000589 mm_vm_defrag(&to->ptable, page_pool);
590 mm_vm_defrag(&from->ptable, page_pool);
Jose Marinho09b1db82019-08-08 09:16:59 +0100591
592 return ret;
593}
594
595/**
Andrew Walbran475c1452020-02-07 13:22:22 +0000596 * Validates a call to donate, lend or share memory and then updates the stage-2
597 * page tables. Specifically, check if the message length and number of memory
598 * region constituents match, and if the transition is valid for the type of
599 * memory sending operation.
600 *
601 * Assumes that the caller has already found and locked both VMs and ensured
602 * that the destination RX buffer is available, and copied the memory region
603 * descriptor from the sender's TX buffer to a trusted internal buffer.
Jose Marinho09b1db82019-08-08 09:16:59 +0100604 */
Andrew Walbran475c1452020-02-07 13:22:22 +0000605struct spci_value spci_memory_send(struct vm_locked to_locked,
606 struct vm_locked from_locked,
607 struct spci_memory_region *memory_region,
608 uint32_t memory_share_size,
609 uint32_t share_func, struct mpool *page_pool)
Jose Marinho09b1db82019-08-08 09:16:59 +0100610{
611 uint32_t memory_to_attributes;
612 uint32_t attributes_size;
613 uint32_t constituents_size;
Andrew Walbranf4b51af2020-02-03 14:44:54 +0000614 struct spci_memory_region_constituent *constituents;
615 uint32_t constituent_count = memory_region->constituent_count;
Jose Marinho09b1db82019-08-08 09:16:59 +0100616
617 /*
618 * Ensure the number of constituents are within the memory
619 * bounds.
620 */
621 attributes_size = sizeof(struct spci_memory_region_attributes) *
622 memory_region->attribute_count;
623 constituents_size = sizeof(struct spci_memory_region_constituent) *
Andrew Walbranf4b51af2020-02-03 14:44:54 +0000624 constituent_count;
Jose Marinho09b1db82019-08-08 09:16:59 +0100625 if (memory_region->constituent_offset <
626 sizeof(struct spci_memory_region) + attributes_size ||
627 memory_share_size !=
628 memory_region->constituent_offset + constituents_size) {
629 return spci_error(SPCI_INVALID_PARAMETERS);
630 }
631
Andrew Walbrane28f4a22019-12-24 15:45:36 +0000632 /* The sender must match the message sender. */
633 if (memory_region->sender != from_locked.vm->id) {
634 return spci_error(SPCI_INVALID_PARAMETERS);
635 }
636
Jose Marinho09b1db82019-08-08 09:16:59 +0100637 /* We only support a single recipient. */
638 if (memory_region->attribute_count != 1) {
Andrew Walbrane908c4a2019-12-02 17:13:47 +0000639 return spci_error(SPCI_NOT_SUPPORTED);
Jose Marinho09b1db82019-08-08 09:16:59 +0100640 }
641
Andrew Walbrancfe61642019-12-02 15:34:06 +0000642 /* The recipient must match the message recipient. */
643 if (memory_region->attributes[0].receiver != to_locked.vm->id) {
644 return spci_error(SPCI_INVALID_PARAMETERS);
645 }
646
Andrew Walbrane7ad3c02019-12-24 17:03:04 +0000647 switch (share_func) {
648 case SPCI_MEM_DONATE_32:
649 case SPCI_MEM_LEND_32:
650 case SPCI_MEM_SHARE_32:
Jose Marinho09b1db82019-08-08 09:16:59 +0100651 memory_to_attributes = spci_memory_attrs_to_mode(
652 memory_region->attributes[0].memory_attributes);
653 break;
Andrew Walbrane7ad3c02019-12-24 17:03:04 +0000654 case HF_SPCI_MEM_RELINQUISH:
Jose Marinho09b1db82019-08-08 09:16:59 +0100655 memory_to_attributes = MM_MODE_R | MM_MODE_W | MM_MODE_X;
656 break;
657 default:
Andrew Walbran17eebf92020-02-05 16:35:49 +0000658 dlog_error("Invalid memory sharing message.\n");
Jose Marinho09b1db82019-08-08 09:16:59 +0100659 return spci_error(SPCI_INVALID_PARAMETERS);
660 }
661
Andrew Walbranf4b51af2020-02-03 14:44:54 +0000662 constituents = spci_memory_region_get_constituents(memory_region);
663 return spci_share_memory(
664 to_locked, from_locked, constituents, constituent_count,
Andrew Walbran475c1452020-02-07 13:22:22 +0000665 memory_to_attributes, share_func, page_pool,
Andrew Walbranf4b51af2020-02-03 14:44:54 +0000666 memory_region->flags & SPCI_MEMORY_REGION_FLAG_CLEAR);
Jose Marinho09b1db82019-08-08 09:16:59 +0100667}