1 /*
2  * Copyright 2018 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 <stdalign.h>
10 #include <stdint.h>
11 
12 #include "hf/arch/vm/power_mgmt.h"
13 
14 #include "hf/mm.h"
15 #include "hf/std.h"
16 
17 #include "vmapi/hf/call.h"
18 
19 #include "primary_with_secondary.h"
20 #include "test/hftest.h"
21 #include "test/vmapi/ffa.h"
22 
23 struct cpu_state {
24 	struct mailbox_buffers *mb;
25 	struct spinlock run_lock;
26 };
27 
28 /**
29  * Iterates trying to run vCPU of the secondary VM. Returns when a message
30  * of non-zero length is received.
31  */
run_loop(struct mailbox_buffers * mb)32 static bool run_loop(struct mailbox_buffers *mb)
33 {
34 	struct ffa_value run_res;
35 	bool ok = false;
36 
37 	for (;;) {
38 		/* Run until it manages to schedule vCPU on this CPU. */
39 		do {
40 			run_res = ffa_run(SERVICE_VM1, 0);
41 		} while (run_res.func == FFA_ERROR_32 &&
42 			 ffa_error_code(run_res) == FFA_BUSY);
43 
44 		/* Break out if we received a message with non-zero length. */
45 		if (run_res.func == FFA_MSG_SEND_32 &&
46 		    ffa_msg_send_size(run_res) != 0) {
47 			break;
48 		}
49 
50 		/* Clear mailbox so that next message can be received. */
51 		EXPECT_EQ(ffa_rx_release().func, FFA_SUCCESS_32);
52 	}
53 
54 	/* Copies the contents of the received boolean to the return value. */
55 	if (ffa_msg_send_size(run_res) == sizeof(ok)) {
56 		ok = *(bool *)mb->recv;
57 	}
58 
59 	EXPECT_EQ(ffa_rx_release().func, FFA_SUCCESS_32);
60 
61 	return ok;
62 }
63 
64 /**
65  * This is the entry point of the additional primary VM vCPU. It just calls
66  * the run loop so that two CPUs compete for the chance to run a secondary VM.
67  */
vm_cpu_entry(uintptr_t arg)68 static void vm_cpu_entry(uintptr_t arg)
69 {
70 	/*
71 	 * The function prototype must match the entry function so we permit the
72 	 * int to pointer conversion.
73 	 */
74 	// NOLINTNEXTLINE(performance-no-int-to-ptr)
75 	struct cpu_state *state = (struct cpu_state *)arg;
76 
77 	run_loop(state->mb);
78 	sl_unlock(&state->run_lock);
79 }
80 
TEAR_DOWN(vcpu_state)81 TEAR_DOWN(vcpu_state)
82 {
83 	EXPECT_FFA_ERROR(ffa_rx_release(), FFA_DENIED);
84 }
85 
86 /**
87  * This test tries to run the same secondary vCPU from two different physical
88  * CPUs concurrently. The vCPU checks that the state is ok while it bounces
89  * between the physical CPUs.
90  *
91  * Test is marked long-running because our implementation of spin-locks does not
92  * perform well under QEMU.
93  */
TEST_LONG_RUNNING(vcpu_state,concurrent_save_restore)94 TEST_LONG_RUNNING(vcpu_state, concurrent_save_restore)
95 {
96 	alignas(4096) static char stack[4096];
97 	static struct mailbox_buffers mb;
98 	struct cpu_state state;
99 
100 	mb = set_up_mailbox();
101 
102 	SERVICE_SELECT(SERVICE_VM1, "check_state", mb.send);
103 
104 	/* Start second CPU. */
105 	state.mb = &mb;
106 	state.run_lock = SPINLOCK_INIT;
107 	sl_lock(&state.run_lock);
108 	ASSERT_TRUE(hftest_cpu_start(hftest_get_cpu_id(1), stack, sizeof(stack),
109 				     vm_cpu_entry, (uintptr_t)&state));
110 
111 	/* Run on a loop until the secondary VM is done. */
112 	EXPECT_TRUE(run_loop(&mb));
113 
114 	/*
115 	 * Wait for the second CPU to release its runlock to show it has
116 	 * finished handling messages so the RX buffer is not idle.
117 	 */
118 	sl_lock(&state.run_lock);
119 }
120