/* * Copyright (c) 2023 Google LLC * * SPDX-License-Identifier: Apache-2.0 */ #include #include #include #include #include #include #include #include #include "uart_mock.h" #define CMD_HEADER_SIZE (sizeof(struct ec_host_cmd_request_header)) #define RSP_HEADER_SIZE (sizeof(struct ec_host_cmd_response_header)) /* Recovery time for the backend from invalid command. It has to be bigger than the RX timeout. */ #define UART_BACKEND_RECOVERY_TIME K_MSEC(160) #define MAX_RESP_WAIT_TIME K_MSEC(1) static uint8_t cal_checksum(const uint8_t *const buffer, const uint16_t size) { uint8_t checksum = 0; for (size_t i = 0; i < size; ++i) { checksum += buffer[i]; } return (uint8_t)(-checksum); } static void tx_done(void) { struct uart_event evt; struct uart_mock_data *data = uart_mock.data; /* Prepare UART event passed to the UART callback */ evt.type = UART_TX_DONE; evt.data.tx.buf = data->tx_buf; evt.data.tx.len = data->tx_len; data->cb(&uart_mock, &evt, data->user_data); } #define EC_CMD_HELLO 0x0001 #define EC_HELLO_STR "hello_ec" const static uint8_t hello_magic[4] = {0xAB, 0xBC, 0xDE, 0xF1}; struct hello_cmd_data { uint8_t magic[sizeof(hello_magic)]; } __packed; static enum ec_host_cmd_status ec_host_cmd_hello(struct ec_host_cmd_handler_args *args) { const struct hello_cmd_data *cmd_data = args->input_buf; args->output_buf_size = 0; if (args->version != 0) { zassert_unreachable("Should not get version %d", args->version); return EC_HOST_CMD_INVALID_VERSION; } if (args->input_buf_size != sizeof(struct hello_cmd_data)) { return EC_HOST_CMD_INVALID_PARAM; } if (memcmp(hello_magic, cmd_data->magic, sizeof(hello_magic))) { return EC_HOST_CMD_INVALID_PARAM; } memcpy(args->output_buf, EC_HELLO_STR, sizeof(EC_HELLO_STR)); args->output_buf_size = sizeof(EC_HELLO_STR); return EC_HOST_CMD_SUCCESS; } EC_HOST_CMD_HANDLER_UNBOUND(EC_CMD_HELLO, ec_host_cmd_hello, BIT(0)); static void prepare_hello_cmd(uint8_t *buf) { struct ec_host_cmd_request_header *cmd = (struct ec_host_cmd_request_header *)buf; struct hello_cmd_data *cmd_data = (struct hello_cmd_data *)(buf + CMD_HEADER_SIZE); memset(cmd, 0, CMD_HEADER_SIZE); cmd->cmd_id = EC_CMD_HELLO; cmd->cmd_ver = 0; cmd->prtcl_ver = 3; cmd->data_len = sizeof(*cmd_data); memcpy(cmd_data->magic, hello_magic, sizeof(hello_magic)); cmd->checksum = cal_checksum((uint8_t *)cmd, CMD_HEADER_SIZE + sizeof(*cmd_data)); } static void test_hello(void) { struct uart_event evt; struct uart_mock_data *data = uart_mock.data; struct ec_host_cmd_request_header *cmd = (struct ec_host_cmd_request_header *)data->rx_buf; uint8_t tx_buf[RSP_HEADER_SIZE + sizeof(EC_HELLO_STR)]; struct ec_host_cmd_response_header *rsp = (struct ec_host_cmd_response_header *)tx_buf; int ret; /* Prepare command request */ prepare_hello_cmd((uint8_t *)cmd); /* Prepare UART event passed to the UART callback */ evt.type = UART_RX_RDY; evt.data.rx.len = CMD_HEADER_SIZE + sizeof(struct hello_cmd_data); evt.data.rx.offset = 0; evt.data.rx.buf = data->rx_buf; /* Prepare expected response to the Hello command */ memset(rsp, 0, RSP_HEADER_SIZE); rsp->data_len = sizeof(EC_HELLO_STR); rsp->prtcl_ver = 3; rsp->result = 0; memcpy(&tx_buf[RSP_HEADER_SIZE], EC_HELLO_STR, sizeof(EC_HELLO_STR)); rsp->checksum = cal_checksum(tx_buf, sizeof(tx_buf)); /* Set expected data set from the EC */ ztest_expect_value(uart_mock_tx, len, RSP_HEADER_SIZE + sizeof(EC_HELLO_STR)); ztest_expect_data(uart_mock_tx, buf, tx_buf); /* Call the UART callback to inform about a new data */ data->cb(&uart_mock, &evt, data->user_data); /* Let the handler handle command */ ret = k_sem_take(&data->resp_sent, MAX_RESP_WAIT_TIME); zassert_equal(ret, 0, "Response not sent"); tx_done(); } /* Test recovering from overrun(receiving more data than the header indicates)*/ ZTEST(ec_host_cmd, test_recovery_from_overrun) { struct uart_mock_data *data = uart_mock.data; struct ec_host_cmd_request_header *cmd = (struct ec_host_cmd_request_header *)data->rx_buf; struct uart_event evt; int ret; /* Header that indicates 0 data bytes */ memset(cmd, 0, CMD_HEADER_SIZE); cmd->prtcl_ver = 3; cmd->data_len = 0; evt.type = UART_RX_RDY; evt.data.rx.len = CMD_HEADER_SIZE + 1; evt.data.rx.offset = 1; evt.data.rx.buf = data->rx_buf; /* Call the UART callback to inform about a new data */ data->cb(&uart_mock, &evt, data->user_data); /* Make sure we don't get response */ ret = k_sem_take(&data->resp_sent, UART_BACKEND_RECOVERY_TIME); zassert_equal(ret, -EAGAIN, "Got unexpected response"); /* Make sure the backend is ready to receive a new command again */ test_hello(); } /* Test recovering from receiving invalid header*/ ZTEST(ec_host_cmd, test_recovery_from_invalid_header) { int ret; struct uart_event evt; struct uart_mock_data *data = uart_mock.data; struct ec_host_cmd_request_header *cmd = (struct ec_host_cmd_request_header *)data->rx_buf; /* Different types of invalid header */ struct ec_host_cmd_request_header cmds[] = { { .prtcl_ver = 3, .data_len = data->rx_buf_size + 1 - CMD_HEADER_SIZE, }, { .prtcl_ver = 2, .data_len = 0, }}; for (int i = 0; i < ARRAY_SIZE(cmds); i++) { memset(cmd, 0, CMD_HEADER_SIZE); cmd->prtcl_ver = cmds[i].prtcl_ver; cmd->data_len = cmds[i].data_len; evt.type = UART_RX_RDY; evt.data.rx.len = CMD_HEADER_SIZE; evt.data.rx.offset = 0; evt.data.rx.buf = data->rx_buf; /* Call the UART callback to inform about a new data */ data->cb(&uart_mock, &evt, data->user_data); /* Make sure we don't get response */ ret = k_sem_take(&data->resp_sent, UART_BACKEND_RECOVERY_TIME); zassert_equal(ret, -EAGAIN, "Got unexpected response"); /* Make sure the backend is ready to receive a new command again */ test_hello(); } } /* Test recovering from receiving data that exceed buf size*/ ZTEST(ec_host_cmd, test_recovery_from_too_much_data) { struct uart_event evt; int ret; struct uart_mock_data *data = uart_mock.data; /* One big chunk larger that the buff size */ evt.type = UART_RX_RDY; evt.data.rx.len = data->rx_buf_size + 1; evt.data.rx.offset = 0; evt.data.rx.buf = data->rx_buf; /* Call the UART callback to inform about a new data */ data->cb(&uart_mock, &evt, data->user_data); /* Make sure we don't get response */ ret = k_sem_take(&data->resp_sent, UART_BACKEND_RECOVERY_TIME); zassert_equal(ret, -EAGAIN, "Got unexpected response"); /* Make sure the backend is ready to receive a new command again */ test_hello(); /* Two chunks larger than the buf size */ evt.type = UART_RX_RDY; evt.data.rx.len = CMD_HEADER_SIZE - 1; evt.data.rx.offset = 0; evt.data.rx.buf = data->rx_buf; /* Call the UART callback to inform about a new data */ data->cb(&uart_mock, &evt, data->user_data); evt.type = UART_RX_RDY; evt.data.rx.len = data->rx_buf_size; evt.data.rx.offset = CMD_HEADER_SIZE - 1; evt.data.rx.buf = data->rx_buf; /* Call the UART callback to inform about a new data */ data->cb(&uart_mock, &evt, data->user_data); /* Make sure we don't get response */ ret = k_sem_take(&data->resp_sent, UART_BACKEND_RECOVERY_TIME); zassert_equal(ret, -EAGAIN, "Got response to incomplete command"); /* Make sure the backend is ready to receive a new command again */ test_hello(); } /* Test recovering from incomplete command */ ZTEST(ec_host_cmd, test_recovery_from_underrun) { struct uart_event evt; struct uart_mock_data *data = uart_mock.data; uint8_t *cmd = data->rx_buf; const size_t cmd_size = CMD_HEADER_SIZE + sizeof(struct hello_cmd_data); /* Test different types of underrun */ size_t size_to_send[] = {CMD_HEADER_SIZE - 1, CMD_HEADER_SIZE, cmd_size - 1}; int ret; for (int i = 0; i < ARRAY_SIZE(size_to_send); i++) { /* Prepare command request */ prepare_hello_cmd((uint8_t *)cmd); memset(cmd + size_to_send[i], 0, cmd_size - size_to_send[i]); /* Prepare UART event passed to the UART callback */ evt.type = UART_RX_RDY; evt.data.rx.len = size_to_send[i]; evt.data.rx.offset = 0; evt.data.rx.buf = cmd; /* Call the UART callback to inform about a new data */ data->cb(&uart_mock, &evt, data->user_data); /* Make sure we don't get response */ ret = k_sem_take(&data->resp_sent, UART_BACKEND_RECOVERY_TIME); zassert_equal(ret, -EAGAIN, "Got unexpected response"); /* Make sure the backend is ready to receive a new command again */ test_hello(); } } /* Test basic hello command */ ZTEST(ec_host_cmd, test_hello) { test_hello(); } static void *ec_host_cmd_tests_setup(void) { struct uart_mock_data *data = uart_mock.data; k_sem_init(&data->resp_sent, 0, 1); ec_host_cmd_init(ec_host_cmd_backend_get_uart(&uart_mock)); return NULL; } ZTEST_SUITE(ec_host_cmd, NULL, ec_host_cmd_tests_setup, NULL, NULL, NULL);