1 /*
2 * Copyright (c) 2023 Google LLC
3 *
4 * SPDX-License-Identifier: Apache-2.0
5 */
6
7 #include <zephyr/drivers/uart.h>
8 #include <zephyr/kernel.h>
9 #include <zephyr/mgmt/ec_host_cmd/ec_host_cmd.h>
10 #include <zephyr/shell/shell_dummy.h>
11 #include <zephyr/types.h>
12 #include <zephyr/ztest.h>
13 #include <zephyr/ztest_assert.h>
14 #include <zephyr/ztest_mock.h>
15
16 #include "uart_mock.h"
17
18 #define CMD_HEADER_SIZE (sizeof(struct ec_host_cmd_request_header))
19 #define RSP_HEADER_SIZE (sizeof(struct ec_host_cmd_response_header))
20 /* Recovery time for the backend from invalid command. It has to be bigger than the RX timeout. */
21 #define UART_BACKEND_RECOVERY_TIME K_MSEC(160)
22 #define MAX_RESP_WAIT_TIME K_MSEC(1)
23
cal_checksum(const uint8_t * const buffer,const uint16_t size)24 static uint8_t cal_checksum(const uint8_t *const buffer, const uint16_t size)
25 {
26 uint8_t checksum = 0;
27
28 for (size_t i = 0; i < size; ++i) {
29 checksum += buffer[i];
30 }
31 return (uint8_t)(-checksum);
32 }
33
tx_done(void)34 static void tx_done(void)
35 {
36 struct uart_event evt;
37 struct uart_mock_data *data = uart_mock.data;
38
39 /* Prepare UART event passed to the UART callback */
40 evt.type = UART_TX_DONE;
41 evt.data.tx.buf = data->tx_buf;
42 evt.data.tx.len = data->tx_len;
43
44 data->cb(&uart_mock, &evt, data->user_data);
45 }
46
47 #define EC_CMD_HELLO 0x0001
48 #define EC_HELLO_STR "hello_ec"
49
50 const static uint8_t hello_magic[4] = {0xAB, 0xBC, 0xDE, 0xF1};
51 struct hello_cmd_data {
52 uint8_t magic[sizeof(hello_magic)];
53 } __packed;
54
ec_host_cmd_hello(struct ec_host_cmd_handler_args * args)55 static enum ec_host_cmd_status ec_host_cmd_hello(struct ec_host_cmd_handler_args *args)
56 {
57 const struct hello_cmd_data *cmd_data = args->input_buf;
58
59 args->output_buf_size = 0;
60
61 if (args->version != 0) {
62 zassert_unreachable("Should not get version %d", args->version);
63 return EC_HOST_CMD_INVALID_VERSION;
64 }
65
66 if (args->input_buf_size != sizeof(struct hello_cmd_data)) {
67 return EC_HOST_CMD_INVALID_PARAM;
68 }
69
70 if (memcmp(hello_magic, cmd_data->magic, sizeof(hello_magic))) {
71 return EC_HOST_CMD_INVALID_PARAM;
72 }
73
74 memcpy(args->output_buf, EC_HELLO_STR, sizeof(EC_HELLO_STR));
75 args->output_buf_size = sizeof(EC_HELLO_STR);
76
77 return EC_HOST_CMD_SUCCESS;
78 }
79 EC_HOST_CMD_HANDLER_UNBOUND(EC_CMD_HELLO, ec_host_cmd_hello, BIT(0));
80
prepare_hello_cmd(uint8_t * buf)81 static void prepare_hello_cmd(uint8_t *buf)
82 {
83 struct ec_host_cmd_request_header *cmd = (struct ec_host_cmd_request_header *)buf;
84 struct hello_cmd_data *cmd_data = (struct hello_cmd_data *)(buf + CMD_HEADER_SIZE);
85
86 memset(cmd, 0, CMD_HEADER_SIZE);
87 cmd->cmd_id = EC_CMD_HELLO;
88 cmd->cmd_ver = 0;
89 cmd->prtcl_ver = 3;
90 cmd->data_len = sizeof(*cmd_data);
91 memcpy(cmd_data->magic, hello_magic, sizeof(hello_magic));
92 cmd->checksum = cal_checksum((uint8_t *)cmd, CMD_HEADER_SIZE + sizeof(*cmd_data));
93 }
94
test_hello(void)95 static void test_hello(void)
96 {
97 struct uart_event evt;
98 struct uart_mock_data *data = uart_mock.data;
99 struct ec_host_cmd_request_header *cmd = (struct ec_host_cmd_request_header *)data->rx_buf;
100 uint8_t tx_buf[RSP_HEADER_SIZE + sizeof(EC_HELLO_STR)];
101 struct ec_host_cmd_response_header *rsp = (struct ec_host_cmd_response_header *)tx_buf;
102 int ret;
103
104 /* Prepare command request */
105 prepare_hello_cmd((uint8_t *)cmd);
106
107 /* Prepare UART event passed to the UART callback */
108 evt.type = UART_RX_RDY;
109 evt.data.rx.len = CMD_HEADER_SIZE + sizeof(struct hello_cmd_data);
110 evt.data.rx.offset = 0;
111 evt.data.rx.buf = data->rx_buf;
112
113 /* Prepare expected response to the Hello command */
114 memset(rsp, 0, RSP_HEADER_SIZE);
115 rsp->data_len = sizeof(EC_HELLO_STR);
116 rsp->prtcl_ver = 3;
117 rsp->result = 0;
118 memcpy(&tx_buf[RSP_HEADER_SIZE], EC_HELLO_STR, sizeof(EC_HELLO_STR));
119 rsp->checksum = cal_checksum(tx_buf, sizeof(tx_buf));
120
121 /* Set expected data set from the EC */
122 ztest_expect_value(uart_mock_tx, len, RSP_HEADER_SIZE + sizeof(EC_HELLO_STR));
123 ztest_expect_data(uart_mock_tx, buf, tx_buf);
124
125 /* Call the UART callback to inform about a new data */
126 data->cb(&uart_mock, &evt, data->user_data);
127
128 /* Let the handler handle command */
129 ret = k_sem_take(&data->resp_sent, MAX_RESP_WAIT_TIME);
130
131 zassert_equal(ret, 0, "Response not sent");
132
133 tx_done();
134 }
135
136 /* Test recovering from overrun(receiving more data than the header indicates)*/
ZTEST(ec_host_cmd,test_recovery_from_overrun)137 ZTEST(ec_host_cmd, test_recovery_from_overrun)
138 {
139 struct uart_mock_data *data = uart_mock.data;
140 struct ec_host_cmd_request_header *cmd = (struct ec_host_cmd_request_header *)data->rx_buf;
141 struct uart_event evt;
142 int ret;
143
144 /* Header that indicates 0 data bytes */
145 memset(cmd, 0, CMD_HEADER_SIZE);
146 cmd->prtcl_ver = 3;
147 cmd->data_len = 0;
148
149 evt.type = UART_RX_RDY;
150 evt.data.rx.len = CMD_HEADER_SIZE + 1;
151 evt.data.rx.offset = 1;
152 evt.data.rx.buf = data->rx_buf;
153
154 /* Call the UART callback to inform about a new data */
155 data->cb(&uart_mock, &evt, data->user_data);
156
157 /* Make sure we don't get response */
158 ret = k_sem_take(&data->resp_sent, UART_BACKEND_RECOVERY_TIME);
159 zassert_equal(ret, -EAGAIN, "Got unexpected response");
160
161 /* Make sure the backend is ready to receive a new command again */
162 test_hello();
163 }
164
165 /* Test recovering from receiving invalid header*/
ZTEST(ec_host_cmd,test_recovery_from_invalid_header)166 ZTEST(ec_host_cmd, test_recovery_from_invalid_header)
167 {
168 int ret;
169 struct uart_event evt;
170 struct uart_mock_data *data = uart_mock.data;
171 struct ec_host_cmd_request_header *cmd = (struct ec_host_cmd_request_header *)data->rx_buf;
172 /* Different types of invalid header */
173 struct ec_host_cmd_request_header cmds[] = {
174 {
175 .prtcl_ver = 3,
176 .data_len = data->rx_buf_size + 1 - CMD_HEADER_SIZE,
177 },
178 {
179 .prtcl_ver = 2,
180 .data_len = 0,
181 }};
182
183 for (int i = 0; i < ARRAY_SIZE(cmds); i++) {
184 memset(cmd, 0, CMD_HEADER_SIZE);
185 cmd->prtcl_ver = cmds[i].prtcl_ver;
186 cmd->data_len = cmds[i].data_len;
187
188 evt.type = UART_RX_RDY;
189 evt.data.rx.len = CMD_HEADER_SIZE;
190 evt.data.rx.offset = 0;
191 evt.data.rx.buf = data->rx_buf;
192
193 /* Call the UART callback to inform about a new data */
194 data->cb(&uart_mock, &evt, data->user_data);
195
196 /* Make sure we don't get response */
197 ret = k_sem_take(&data->resp_sent, UART_BACKEND_RECOVERY_TIME);
198 zassert_equal(ret, -EAGAIN, "Got unexpected response");
199
200 /* Make sure the backend is ready to receive a new command again */
201 test_hello();
202 }
203 }
204
205 /* Test recovering from receiving data that exceed buf size*/
ZTEST(ec_host_cmd,test_recovery_from_too_much_data)206 ZTEST(ec_host_cmd, test_recovery_from_too_much_data)
207 {
208 struct uart_event evt;
209 int ret;
210 struct uart_mock_data *data = uart_mock.data;
211
212 /* One big chunk larger that the buff size */
213 evt.type = UART_RX_RDY;
214 evt.data.rx.len = data->rx_buf_size + 1;
215 evt.data.rx.offset = 0;
216 evt.data.rx.buf = data->rx_buf;
217
218 /* Call the UART callback to inform about a new data */
219 data->cb(&uart_mock, &evt, data->user_data);
220
221 /* Make sure we don't get response */
222 ret = k_sem_take(&data->resp_sent, UART_BACKEND_RECOVERY_TIME);
223 zassert_equal(ret, -EAGAIN, "Got unexpected response");
224
225 /* Make sure the backend is ready to receive a new command again */
226 test_hello();
227
228 /* Two chunks larger than the buf size */
229 evt.type = UART_RX_RDY;
230 evt.data.rx.len = CMD_HEADER_SIZE - 1;
231 evt.data.rx.offset = 0;
232 evt.data.rx.buf = data->rx_buf;
233
234 /* Call the UART callback to inform about a new data */
235 data->cb(&uart_mock, &evt, data->user_data);
236
237 evt.type = UART_RX_RDY;
238 evt.data.rx.len = data->rx_buf_size;
239 evt.data.rx.offset = CMD_HEADER_SIZE - 1;
240 evt.data.rx.buf = data->rx_buf;
241
242 /* Call the UART callback to inform about a new data */
243 data->cb(&uart_mock, &evt, data->user_data);
244
245 /* Make sure we don't get response */
246 ret = k_sem_take(&data->resp_sent, UART_BACKEND_RECOVERY_TIME);
247 zassert_equal(ret, -EAGAIN, "Got response to incomplete command");
248
249 /* Make sure the backend is ready to receive a new command again */
250 test_hello();
251 }
252
253 /* Test recovering from incomplete command */
ZTEST(ec_host_cmd,test_recovery_from_underrun)254 ZTEST(ec_host_cmd, test_recovery_from_underrun)
255 {
256 struct uart_event evt;
257 struct uart_mock_data *data = uart_mock.data;
258 uint8_t *cmd = data->rx_buf;
259 const size_t cmd_size = CMD_HEADER_SIZE + sizeof(struct hello_cmd_data);
260 /* Test different types of underrun */
261 size_t size_to_send[] = {CMD_HEADER_SIZE - 1, CMD_HEADER_SIZE, cmd_size - 1};
262 int ret;
263
264 for (int i = 0; i < ARRAY_SIZE(size_to_send); i++) {
265 /* Prepare command request */
266 prepare_hello_cmd((uint8_t *)cmd);
267 memset(cmd + size_to_send[i], 0, cmd_size - size_to_send[i]);
268
269 /* Prepare UART event passed to the UART callback */
270 evt.type = UART_RX_RDY;
271 evt.data.rx.len = size_to_send[i];
272 evt.data.rx.offset = 0;
273 evt.data.rx.buf = cmd;
274
275 /* Call the UART callback to inform about a new data */
276 data->cb(&uart_mock, &evt, data->user_data);
277
278 /* Make sure we don't get response */
279 ret = k_sem_take(&data->resp_sent, UART_BACKEND_RECOVERY_TIME);
280 zassert_equal(ret, -EAGAIN, "Got unexpected response");
281
282 /* Make sure the backend is ready to receive a new command again */
283 test_hello();
284 }
285 }
286
287 /* Test basic hello command */
ZTEST(ec_host_cmd,test_hello)288 ZTEST(ec_host_cmd, test_hello)
289 {
290 test_hello();
291 }
292
ec_host_cmd_tests_setup(void)293 static void *ec_host_cmd_tests_setup(void)
294 {
295 struct uart_mock_data *data = uart_mock.data;
296
297 k_sem_init(&data->resp_sent, 0, 1);
298 ec_host_cmd_init(ec_host_cmd_backend_get_uart(&uart_mock));
299 return NULL;
300 }
301
302 ZTEST_SUITE(ec_host_cmd, NULL, ec_host_cmd_tests_setup, NULL, NULL, NULL);
303