1 /*
2  * Copyright (c) 2016 Intel Corporation
3  *
4  * SPDX-License-Identifier: Apache-2.0
5  */
6 
7 #include <zephyr/mgmt/ec_host_cmd/ec_host_cmd.h>
8 #include <zephyr/mgmt/ec_host_cmd/simulator.h>
9 #include <zephyr/ztest.h>
10 
11 /* Variables used to record what is "sent" to host for verification. */
12 K_SEM_DEFINE(send_called, 0, 1);
13 struct ec_host_cmd_tx_buf *sent;
14 
host_send(const struct ec_host_cmd_backend * backend)15 static int host_send(const struct ec_host_cmd_backend *backend)
16 {
17 	k_sem_give(&send_called);
18 	return 0;
19 }
20 
21 /**
22  * struct ec_params_add - Parameters to the add command.
23  * @in_data: Pass anything here.
24  */
25 struct ec_params_add {
26 	uint32_t in_data;
27 } __packed;
28 
29 struct ec_params_unbounded {
30 	uint32_t bytes_to_write;
31 } __packed;
32 
33 /**
34  * struct ec_response_add - Response to the add command.
35  * @out_data: Output will be in_data + 0x01020304.
36  */
37 struct ec_response_add {
38 	uint32_t out_data;
39 } __packed;
40 
41 struct ec_response_too_big {
42 	uint8_t out_data[512];
43 } __packed;
44 
45 /* Buffer used to simulate incoming data from host to EC. */
46 static uint8_t host_to_dut_buffer[256];
47 struct rx_structure {
48 	struct ec_host_cmd_request_header header;
49 	union {
50 		struct ec_params_add add;
51 		struct ec_params_unbounded unbounded;
52 		uint8_t raw[0];
53 	};
54 } __packed * const host_to_dut = (void *)&host_to_dut_buffer;
55 
56 /* Buffer used to verify expected outgoing data from EC to host. */
57 static uint8_t expected_dut_to_host_buffer[256];
58 struct tx_structure {
59 	struct ec_host_cmd_response_header header;
60 	union {
61 		struct ec_response_add add;
62 		uint8_t raw[0];
63 	};
64 } __packed * const expected_dut_to_host = (void *)&expected_dut_to_host_buffer;
65 
update_host_to_dut_checksum(void)66 static void update_host_to_dut_checksum(void)
67 {
68 	host_to_dut->header.checksum = 0;
69 
70 	uint8_t checksum = 0;
71 
72 	for (size_t i = 0;
73 	     i < sizeof(host_to_dut->header) + host_to_dut->header.data_len;
74 	     ++i) {
75 		checksum += host_to_dut_buffer[i];
76 	}
77 	host_to_dut->header.checksum = (uint8_t)(-checksum);
78 }
79 
update_dut_to_host_checksum(void)80 static void update_dut_to_host_checksum(void)
81 {
82 	const uint16_t buf_size = sizeof(expected_dut_to_host->header) +
83 				  expected_dut_to_host->header.data_len;
84 
85 	expected_dut_to_host->header.checksum = 0;
86 
87 	uint8_t checksum = 0;
88 
89 	for (size_t i = 0; i < buf_size; ++i) {
90 		checksum += expected_dut_to_host_buffer[i];
91 	}
92 
93 	expected_dut_to_host->header.checksum = (uint8_t)(-checksum);
94 }
95 
simulate_rx_data(void)96 static void simulate_rx_data(void)
97 {
98 	int rv;
99 
100 	update_host_to_dut_checksum();
101 	/*
102 	 * Always send entire buffer and let host command framework read what it
103 	 * needs.
104 	 */
105 	rv = ec_host_cmd_backend_sim_data_received(host_to_dut_buffer,
106 						  sizeof(host_to_dut_buffer));
107 	zassert_equal(rv, 0, "Could not send data %d", rv);
108 
109 	/* Ensure send was called so we can verify outputs */
110 	rv = k_sem_take(&send_called, K_SECONDS(1));
111 	zassert_equal(rv, 0, "Send was not called");
112 }
113 
expected_tx_size(void)114 static uint16_t expected_tx_size(void)
115 {
116 	return sizeof(expected_dut_to_host->header) +
117 	       expected_dut_to_host->header.data_len;
118 }
119 
verify_tx_data(void)120 static void verify_tx_data(void)
121 {
122 	update_dut_to_host_checksum();
123 
124 	zassert_equal(sent->len, expected_tx_size(), "Sent bytes did not match");
125 	zassert_mem_equal(sent->buf, expected_dut_to_host, expected_tx_size(),
126 			  "Sent buffer did not match");
127 }
128 
verify_tx_error(enum ec_host_cmd_status error)129 static void verify_tx_error(enum ec_host_cmd_status error)
130 {
131 	expected_dut_to_host->header.prtcl_ver = 3;
132 	expected_dut_to_host->header.result = error;
133 	expected_dut_to_host->header.data_len = 0;
134 	expected_dut_to_host->header.reserved = 0;
135 	update_dut_to_host_checksum();
136 
137 	zassert_equal(sent->len, expected_tx_size(), "Sent bytes did not match");
138 	zassert_mem_equal(sent->buf, expected_dut_to_host, expected_tx_size(),
139 			  "Sent buffer did not match");
140 }
141 
142 #define EC_CMD_HELLO 0x0001
143 static enum ec_host_cmd_status
ec_host_cmd_add(struct ec_host_cmd_handler_args * args)144 ec_host_cmd_add(struct ec_host_cmd_handler_args *args)
145 {
146 	const struct ec_params_add *const request = args->input_buf;
147 	struct ec_response_add *const response = args->output_buf;
148 
149 	if (args->version == 0) {
150 		response->out_data = request->in_data + 0x01020304;
151 	} else if (args->version == 1) {
152 		response->out_data = request->in_data + 0x02040608;
153 	} else if (args->version == 2) {
154 		return EC_HOST_CMD_OVERFLOW;
155 	} else {
156 		zassert_unreachable("Should not get version %d", args->version);
157 	}
158 
159 	args->output_buf_size = sizeof(*response);
160 	return EC_HOST_CMD_SUCCESS;
161 }
162 EC_HOST_CMD_HANDLER(EC_CMD_HELLO, ec_host_cmd_add, BIT(0) | BIT(1) | BIT(2),
163 		    struct ec_params_add, struct ec_response_add);
164 
ZTEST(ec_host_cmd,test_add)165 ZTEST(ec_host_cmd, test_add)
166 {
167 	host_to_dut->header.prtcl_ver = 3;
168 	host_to_dut->header.cmd_id = EC_CMD_HELLO;
169 	host_to_dut->header.cmd_ver = 0;
170 	host_to_dut->header.reserved = 0;
171 	host_to_dut->header.data_len = sizeof(host_to_dut->add);
172 	host_to_dut->add.in_data = 0x10203040;
173 
174 	simulate_rx_data();
175 
176 	expected_dut_to_host->header.prtcl_ver = 3;
177 	expected_dut_to_host->header.result = 0;
178 	expected_dut_to_host->header.reserved = 0;
179 	expected_dut_to_host->header.data_len =
180 		sizeof(expected_dut_to_host->add);
181 	expected_dut_to_host->add.out_data = 0x11223344;
182 
183 	verify_tx_data();
184 }
185 
ZTEST(ec_host_cmd,test_add_version_2)186 ZTEST(ec_host_cmd, test_add_version_2)
187 {
188 	host_to_dut->header.prtcl_ver = 3;
189 	host_to_dut->header.cmd_id = EC_CMD_HELLO;
190 	host_to_dut->header.cmd_ver = 1;
191 	host_to_dut->header.reserved = 0;
192 	host_to_dut->header.data_len = sizeof(host_to_dut->add);
193 	host_to_dut->add.in_data = 0x10203040;
194 
195 	simulate_rx_data();
196 
197 	expected_dut_to_host->header.prtcl_ver = 3;
198 	expected_dut_to_host->header.result = 0;
199 	expected_dut_to_host->header.reserved = 0;
200 	expected_dut_to_host->header.data_len =
201 		sizeof(expected_dut_to_host->add);
202 	expected_dut_to_host->add.out_data = 0x12243648;
203 
204 	verify_tx_data();
205 }
206 
ZTEST(ec_host_cmd,test_add_invalid_version)207 ZTEST(ec_host_cmd, test_add_invalid_version)
208 {
209 	host_to_dut->header.prtcl_ver = 3;
210 	host_to_dut->header.cmd_id = EC_CMD_HELLO;
211 	host_to_dut->header.cmd_ver = 3;
212 	host_to_dut->header.reserved = 0;
213 	host_to_dut->header.data_len = sizeof(host_to_dut->add);
214 	host_to_dut->add.in_data = 0x10203040;
215 
216 	simulate_rx_data();
217 
218 	verify_tx_error(EC_HOST_CMD_INVALID_VERSION);
219 }
220 
ZTEST(ec_host_cmd,test_add_invalid_version_big)221 ZTEST(ec_host_cmd, test_add_invalid_version_big)
222 {
223 	host_to_dut->header.prtcl_ver = 3;
224 	host_to_dut->header.cmd_id = EC_CMD_HELLO;
225 	host_to_dut->header.cmd_ver = 128;
226 	host_to_dut->header.reserved = 0;
227 	host_to_dut->header.data_len = sizeof(host_to_dut->add);
228 	host_to_dut->add.in_data = 0x10203040;
229 
230 	simulate_rx_data();
231 
232 	verify_tx_error(EC_HOST_CMD_INVALID_VERSION);
233 }
234 
ZTEST(ec_host_cmd,test_add_invalid_prtcl_ver_2)235 ZTEST(ec_host_cmd, test_add_invalid_prtcl_ver_2)
236 {
237 	host_to_dut->header.prtcl_ver = 2;
238 	host_to_dut->header.cmd_id = EC_CMD_HELLO;
239 	host_to_dut->header.cmd_ver = 2;
240 	host_to_dut->header.reserved = 0;
241 	host_to_dut->header.data_len = sizeof(host_to_dut->add);
242 	host_to_dut->add.in_data = 0x10203040;
243 
244 	simulate_rx_data();
245 
246 	verify_tx_error(EC_HOST_CMD_INVALID_HEADER);
247 }
248 
ZTEST(ec_host_cmd,test_add_invalid_prtcl_ver_4)249 ZTEST(ec_host_cmd, test_add_invalid_prtcl_ver_4)
250 {
251 	host_to_dut->header.prtcl_ver = 4;
252 	host_to_dut->header.cmd_id = EC_CMD_HELLO;
253 	host_to_dut->header.cmd_ver = 2;
254 	host_to_dut->header.reserved = 0;
255 	host_to_dut->header.data_len = sizeof(host_to_dut->add);
256 	host_to_dut->add.in_data = 0x10203040;
257 
258 	simulate_rx_data();
259 
260 	verify_tx_error(EC_HOST_CMD_INVALID_HEADER);
261 }
262 
ZTEST(ec_host_cmd,test_add_invalid_rx_checksum)263 ZTEST(ec_host_cmd, test_add_invalid_rx_checksum)
264 {
265 	int rv;
266 
267 	host_to_dut->header.prtcl_ver = 3;
268 	host_to_dut->header.cmd_id = EC_CMD_HELLO;
269 	host_to_dut->header.cmd_ver = 2;
270 	host_to_dut->header.reserved = 0;
271 	host_to_dut->header.data_len = sizeof(host_to_dut->add);
272 	host_to_dut->add.in_data = 0x10203040;
273 
274 	/* Set an invalid checksum */
275 	host_to_dut->header.checksum = 42;
276 
277 	/* Always send entire buffer and let host command framework read what it
278 	 * needs.
279 	 */
280 	rv = ec_host_cmd_backend_sim_data_received(host_to_dut_buffer,
281 						  sizeof(host_to_dut_buffer));
282 	zassert_equal(rv, 0, "Could not send data %d", rv);
283 
284 	/* Ensure Send was called so we can verify outputs */
285 	rv = k_sem_take(&send_called, K_SECONDS(1));
286 	zassert_equal(rv, 0, "Send was not called");
287 
288 	verify_tx_error(EC_HOST_CMD_INVALID_CHECKSUM);
289 }
290 
ZTEST(ec_host_cmd,test_add_rx_size_too_small_for_header)291 ZTEST(ec_host_cmd, test_add_rx_size_too_small_for_header)
292 {
293 	int rv;
294 
295 	host_to_dut->header.prtcl_ver = 3;
296 	host_to_dut->header.cmd_id = EC_CMD_HELLO;
297 	host_to_dut->header.cmd_ver = 2;
298 	host_to_dut->header.reserved = 0;
299 	host_to_dut->header.data_len = sizeof(host_to_dut->add);
300 	host_to_dut->add.in_data = 0x10203040;
301 
302 	rv = ec_host_cmd_backend_sim_data_received(host_to_dut_buffer, 4);
303 	zassert_equal(rv, 0, "Could not send data %d", rv);
304 
305 	/* Ensure Send was called so we can verify outputs */
306 	rv = k_sem_take(&send_called, K_SECONDS(1));
307 	zassert_equal(rv, 0, "Send was not called");
308 
309 	verify_tx_error(EC_HOST_CMD_REQUEST_TRUNCATED);
310 }
311 
ZTEST(ec_host_cmd,test_add_rx_size_too_small)312 ZTEST(ec_host_cmd, test_add_rx_size_too_small)
313 {
314 	int rv;
315 
316 	host_to_dut->header.prtcl_ver = 3;
317 	host_to_dut->header.cmd_id = EC_CMD_HELLO;
318 	host_to_dut->header.cmd_ver = 2;
319 	host_to_dut->header.reserved = 0;
320 	host_to_dut->header.data_len = sizeof(host_to_dut->add);
321 	host_to_dut->add.in_data = 0x10203040;
322 
323 	rv = ec_host_cmd_backend_sim_data_received(
324 		host_to_dut_buffer,
325 		sizeof(host_to_dut->header) + host_to_dut->header.data_len - 1);
326 	zassert_equal(rv, 0, "Could not send data %d", rv);
327 
328 	/* Ensure Send was called so we can verify outputs */
329 	rv = k_sem_take(&send_called, K_SECONDS(1));
330 	zassert_equal(rv, 0, "Send was not called");
331 
332 	verify_tx_error(EC_HOST_CMD_REQUEST_TRUNCATED);
333 }
334 
ZTEST(ec_host_cmd,test_unknown_command)335 ZTEST(ec_host_cmd, test_unknown_command)
336 {
337 	host_to_dut->header.prtcl_ver = 3;
338 	host_to_dut->header.cmd_id = 1234;
339 	host_to_dut->header.cmd_ver = 2;
340 	host_to_dut->header.reserved = 0;
341 	host_to_dut->header.data_len = 0;
342 
343 	simulate_rx_data();
344 
345 	verify_tx_error(EC_HOST_CMD_INVALID_COMMAND);
346 }
347 
348 #define EC_CMD_UNBOUNDED 0x0002
349 static enum ec_host_cmd_status
ec_host_cmd_unbounded(struct ec_host_cmd_handler_args * args)350 ec_host_cmd_unbounded(struct ec_host_cmd_handler_args *args)
351 {
352 	const struct ec_params_unbounded *const request = args->input_buf;
353 	const uint8_t *in_buffer = args->input_buf;
354 	uint8_t *out_buffer = args->output_buf;
355 
356 	/* Version 1 just says we used the space without writing */
357 	if (args->version == 1) {
358 		args->output_buf_size = request->bytes_to_write;
359 		return EC_HOST_CMD_SUCCESS;
360 	}
361 
362 	/* Version 2 adds extra asserts */
363 	if (args->version == 2) {
364 		zassert_equal(in_buffer[4], 0, "Ensure input data is clear");
365 		zassert_equal(out_buffer[0], 0, "Ensure output is clear");
366 		zassert_equal(out_buffer[1], 0, "Ensure output is clear");
367 		zassert_equal(out_buffer[2], 0, "Ensure output is clear");
368 		zassert_equal(out_buffer[3], 0, "Ensure output is clear");
369 	}
370 
371 	/* Version 0 (and 2) write request bytes if it can fit */
372 	if (request->bytes_to_write > args->output_buf_max) {
373 		return EC_HOST_CMD_OVERFLOW;
374 	}
375 
376 	for (int i = 0; i < request->bytes_to_write; ++i) {
377 		zassert_equal(out_buffer[i], 0, "Ensure every TX byte is 0");
378 		out_buffer[i] = i;
379 	}
380 
381 	args->output_buf_size = request->bytes_to_write;
382 	return EC_HOST_CMD_SUCCESS;
383 }
384 EC_HOST_CMD_HANDLER_UNBOUND(EC_CMD_UNBOUNDED, ec_host_cmd_unbounded,
385 			    BIT(0) | BIT(1) | BIT(2));
386 
ZTEST(ec_host_cmd,test_unbounded_handler_error_return)387 ZTEST(ec_host_cmd, test_unbounded_handler_error_return)
388 {
389 	host_to_dut->header.prtcl_ver = 3;
390 	host_to_dut->header.cmd_id = EC_CMD_UNBOUNDED;
391 	host_to_dut->header.cmd_ver = 0;
392 	host_to_dut->header.reserved = 0;
393 	host_to_dut->header.data_len = sizeof(host_to_dut->unbounded);
394 	host_to_dut->unbounded.bytes_to_write = INT16_MAX;
395 
396 	simulate_rx_data();
397 
398 	verify_tx_error(EC_HOST_CMD_OVERFLOW);
399 }
400 
ZTEST(ec_host_cmd,test_unbounded_handler_response_too_big)401 ZTEST(ec_host_cmd, test_unbounded_handler_response_too_big)
402 {
403 	host_to_dut->header.prtcl_ver = 3;
404 	host_to_dut->header.cmd_id = EC_CMD_UNBOUNDED;
405 	host_to_dut->header.cmd_ver = 1;
406 	host_to_dut->header.reserved = 0;
407 	host_to_dut->header.data_len = sizeof(host_to_dut->unbounded);
408 	host_to_dut->unbounded.bytes_to_write = INT16_MAX;
409 
410 	simulate_rx_data();
411 
412 	verify_tx_error(EC_HOST_CMD_INVALID_RESPONSE);
413 }
414 
415 #define EC_CMD_TOO_BIG 0x0003
416 static enum ec_host_cmd_status
ec_host_cmd_too_big(struct ec_host_cmd_handler_args * args)417 ec_host_cmd_too_big(struct ec_host_cmd_handler_args *args)
418 {
419 	return EC_HOST_CMD_SUCCESS;
420 }
421 EC_HOST_CMD_HANDLER(EC_CMD_TOO_BIG, ec_host_cmd_too_big, BIT(0), uint32_t,
422 		    struct ec_response_too_big);
423 
ZTEST(ec_host_cmd,test_response_always_too_big)424 ZTEST(ec_host_cmd, test_response_always_too_big)
425 {
426 	host_to_dut->header.prtcl_ver = 3;
427 	host_to_dut->header.cmd_id = EC_CMD_TOO_BIG;
428 	host_to_dut->header.cmd_ver = 0;
429 	host_to_dut->header.reserved = 0;
430 	host_to_dut->header.data_len = sizeof(uint32_t);
431 
432 	simulate_rx_data();
433 
434 	verify_tx_error(EC_HOST_CMD_INVALID_RESPONSE);
435 }
436 
ec_host_cmd_tests_setup(void)437 static void *ec_host_cmd_tests_setup(void)
438 {
439 	ec_host_cmd_backend_sim_install_send_cb(host_send, &sent);
440 	return NULL;
441 }
442 
443 ZTEST_SUITE(ec_host_cmd, NULL, ec_host_cmd_tests_setup, NULL, NULL, NULL);
444