1 /*
2  * Copyright 2021 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 "hf/arch/vm/power_mgmt.h"
10 
11 #include "hf/dlog.h"
12 #include "hf/ffa.h"
13 #include "hf/spinlock.h"
14 
15 #include "vmapi/hf/call.h"
16 
17 #include "ffa_endpoints.h"
18 #include "partition_services.h"
19 #include "test/hftest.h"
20 #include "test/vmapi/ffa.h"
21 
22 struct pwr_mgt_cpu_entry_args {
23 	ffa_id_t receiver_id;
24 	ffa_vcpu_count_t vcpu_count;
25 	ffa_vcpu_index_t vcpu_id;
26 	struct spinlock lock;
27 };
28 
29 /**
30  * Performs direct request echo test in the running CPU.
31  */
cpu_entry_echo(uintptr_t arg)32 static void cpu_entry_echo(uintptr_t arg)
33 {
34 	ffa_id_t own_id = hf_vm_get_id();
35 	const uint32_t msg[] = {SP_ECHO_CMD, 0x1, 0x2, 0x3, 0x4};
36 	struct pwr_mgt_cpu_entry_args *args =
37 		// NOLINTNEXTLINE(performance-no-int-to-ptr)
38 		(struct pwr_mgt_cpu_entry_args *)arg;
39 	struct ffa_value res;
40 
41 	res = sp_echo_cmd_send(own_id, args->receiver_id, msg[0], msg[1],
42 			       msg[2], msg[3]);
43 
44 	EXPECT_EQ(ffa_func_id(res), FFA_MSG_SEND_DIRECT_RESP_32);
45 	EXPECT_EQ(res.arg4, msg[0]);
46 	EXPECT_EQ(res.arg5, msg[1]);
47 	EXPECT_EQ(res.arg6, msg[2]);
48 	EXPECT_EQ(res.arg7, msg[3]);
49 
50 	/* Releases the lock passed in. */
51 	sl_unlock(&args->lock);
52 	arch_cpu_stop();
53 }
54 
cpu_entry_echo_second_sp(uintptr_t arg)55 static void cpu_entry_echo_second_sp(uintptr_t arg)
56 {
57 	struct pwr_mgt_cpu_entry_args *args =
58 		// NOLINTNEXTLINE(performance-no-int-to-ptr)
59 		(struct pwr_mgt_cpu_entry_args *)arg;
60 	struct ffa_value res;
61 	ffa_vcpu_index_t vcpu_id = args->vcpu_count == 1 ? 0 : args->vcpu_id;
62 
63 	/*
64 	 * Second SP needs FFA_RUN before communicating with it.
65 	 * TODO: the FFA_RUN ABI only needs to be called for the MP UP endpoints
66 	 * to bootstrap the EC in the current core. Though there is an issue
67 	 * with the current FFA_RUN implementation: it returns back to the
68 	 * caller with FFA_MSG_WAIT interface, without resuming the target
69 	 * SP. When fixing the FFA_RUN issue, this bit of code needs addressing.
70 	 */
71 	res = ffa_run(args->receiver_id, vcpu_id);
72 	EXPECT_EQ(ffa_func_id(res), FFA_MSG_WAIT_32);
73 
74 	cpu_entry_echo(arg);
75 }
76 
77 /**
78  * Validates that the core index passed, matches the vMPDIR set by the SPMC.
79  */
cpu_entry_check_cpu_idx(uintptr_t arg)80 static void cpu_entry_check_cpu_idx(uintptr_t arg)
81 {
82 	ffa_id_t own_id = hf_vm_get_id();
83 	struct pwr_mgt_cpu_entry_args *args =
84 		// NOLINTNEXTLINE(performance-no-int-to-ptr)
85 		(struct pwr_mgt_cpu_entry_args *)arg;
86 	struct ffa_value res;
87 
88 	/*
89 	 * For S-EL1 MP partitions, the linear cpu index is expected to match
90 	 * the vCPU ID.
91 	 */
92 	res = sp_check_cpu_idx_cmd_send(own_id, args->receiver_id,
93 					args->vcpu_id);
94 	EXPECT_EQ(res.func, FFA_MSG_SEND_DIRECT_RESP_32);
95 	EXPECT_EQ(sp_resp(res), SP_SUCCESS);
96 
97 	/* Releases the lock passed in. */
98 	sl_unlock(&args->lock);
99 	arch_cpu_stop();
100 }
101 
cpu_entry_check_cpu_idx_second_sp(uintptr_t arg)102 static void cpu_entry_check_cpu_idx_second_sp(uintptr_t arg)
103 {
104 	struct pwr_mgt_cpu_entry_args *args =
105 		// NOLINTNEXTLINE(performance-no-int-to-ptr)
106 		(struct pwr_mgt_cpu_entry_args *)arg;
107 	struct ffa_value res;
108 
109 	/*
110 	 * Make the receiver VM reach the message loop in the respective EC.
111 	 * This function is meant to be used if the receiver is an MP FF-A
112 	 * endpoint.
113 	 */
114 	res = ffa_run(args->receiver_id, args->vcpu_id);
115 	EXPECT_EQ(ffa_func_id(res), FFA_MSG_WAIT_32);
116 
117 	cpu_entry_check_cpu_idx(arg);
118 }
119 
base_cpu_start_test(struct ffa_uuid * recv_uuid,void (* entry)(uintptr_t arg),bool skip_if_up_sp)120 static void base_cpu_start_test(struct ffa_uuid *recv_uuid,
121 				void (*entry)(uintptr_t arg),
122 				bool skip_if_up_sp)
123 {
124 	struct pwr_mgt_cpu_entry_args args = {.lock = SPINLOCK_INIT};
125 	struct ffa_partition_info receiver;
126 	struct mailbox_buffers mb = set_up_mailbox();
127 
128 	EXPECT_EQ(get_ffa_partition_info(recv_uuid, &receiver, 1, mb.recv), 1);
129 
130 	args.receiver_id = receiver.vm_id;
131 	args.vcpu_count = receiver.vcpu_count;
132 
133 	if (args.vcpu_count == 1 && skip_if_up_sp) {
134 		HFTEST_LOG("Skipping test as receiver is UP SP.\n");
135 		return;
136 	}
137 
138 	/* Start secondary EC while holding lock. */
139 	sl_lock(&args.lock);
140 
141 	for (size_t i = 1; i < MAX_CPUS - 1; i++) {
142 		HFTEST_LOG("Booting CPU %zu", i);
143 
144 		/*
145 		 * If receiver is an S-EL0 partition it is expected to have one
146 		 * execution context. If it is S-EL1 partition can have MAX_CPUS
147 		 * or 1.
148 		 */
149 		args.vcpu_id = (ffa_vcpu_index_t)i;
150 
151 		EXPECT_EQ(hftest_cpu_start(hftest_get_cpu_id(i),
152 					   hftest_get_secondary_ec_stack(i),
153 					   entry, (uintptr_t)&args),
154 			  true);
155 
156 		/* Wait for CPU to release the lock. */
157 		sl_lock(&args.lock);
158 
159 		HFTEST_LOG("Done with CPU %zu", i);
160 	}
161 }
162 
TEST(ffa_power_mgt,cpu_start_echo_second_sp)163 TEST(ffa_power_mgt, cpu_start_echo_second_sp)
164 {
165 	/* Second SP can be either S-EL0 or S-EL1 SP. */
166 	base_cpu_start_test(&(struct ffa_uuid){SP_SERVICE_SECOND_UUID},
167 			    cpu_entry_echo_second_sp, false);
168 }
169 
TEST(ffa_power_mgt,cpu_start_echo_first_sp)170 TEST(ffa_power_mgt, cpu_start_echo_first_sp)
171 {
172 	base_cpu_start_test(&(struct ffa_uuid){SP_SERVICE_FIRST_UUID},
173 			    cpu_entry_echo, false);
174 }
175 
TEST(ffa_power_mgt,cpu_start_core_idx_second_sp)176 TEST(ffa_power_mgt, cpu_start_core_idx_second_sp)
177 {
178 	/* Test to be skipped for S-EL0 partition. */
179 	base_cpu_start_test(&(struct ffa_uuid){SP_SERVICE_SECOND_UUID},
180 			    cpu_entry_check_cpu_idx_second_sp, true);
181 }
182 
TEST(ffa_power_mgt,cpu_start_core_idx_first_sp)183 TEST(ffa_power_mgt, cpu_start_core_idx_first_sp)
184 {
185 	/* Test to be skipped for S-EL0 partition. */
186 	base_cpu_start_test(&(struct ffa_uuid){SP_SERVICE_FIRST_UUID},
187 			    cpu_entry_check_cpu_idx, true);
188 }
189