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