Linux Audio

Check our new training course

Embedded Linux Audio

Check our new training course
with Creative Commons CC-BY-SA
lecture materials

Bootlin logo

Elixir Cross Referencer

Loading...
/*
 * Copyright (c) 2020 Google LLC.
 *
 * SPDX-License-Identifier: Apache-2.0
 */

#include <zephyr/devicetree.h>
#include <zephyr/kernel.h>
#include <zephyr/logging/log.h>
#include <zephyr/mgmt/ec_host_cmd/ec_host_cmd.h>
#include <zephyr/mgmt/ec_host_cmd/backend.h>
#include <zephyr/sys/iterable_sections.h>
#include <string.h>

LOG_MODULE_REGISTER(host_cmd_handler, CONFIG_EC_HC_LOG_LEVEL);

#define EC_HOST_CMD_CHOSEN_BACKEND_LIST                                                            \
	zephyr_host_cmd_espi_backend, zephyr_host_cmd_shi_backend, zephyr_host_cmd_uart_backend

#define EC_HOST_CMD_ADD_CHOSEN(chosen) COND_CODE_1(DT_NODE_EXISTS(DT_CHOSEN(chosen)), (1), (0))

#define NUMBER_OF_CHOSEN_BACKENDS                                                                  \
	FOR_EACH(EC_HOST_CMD_ADD_CHOSEN, (+), EC_HOST_CMD_CHOSEN_BACKEND_LIST)                     \
	+0

BUILD_ASSERT(NUMBER_OF_CHOSEN_BACKENDS < 2, "Number of chosen backends > 1");

#define RX_HEADER_SIZE (sizeof(struct ec_host_cmd_request_header))
#define TX_HEADER_SIZE (sizeof(struct ec_host_cmd_response_header))

COND_CODE_1(CONFIG_EC_HOST_CMD_HANDLER_RX_BUFFER_DEF,
	    (static uint8_t hc_rx_buffer[CONFIG_EC_HOST_CMD_HANDLER_RX_BUFFER_SIZE];), ())
COND_CODE_1(CONFIG_EC_HOST_CMD_HANDLER_TX_BUFFER_DEF,
	    (static uint8_t hc_tx_buffer[CONFIG_EC_HOST_CMD_HANDLER_TX_BUFFER_SIZE];), ())

#ifdef CONFIG_EC_HOST_CMD_DEDICATED_THREAD
static K_KERNEL_STACK_DEFINE(hc_stack, CONFIG_EC_HOST_CMD_HANDLER_STACK_SIZE);
#endif /* CONFIG_EC_HOST_CMD_DEDICATED_THREAD */

static struct ec_host_cmd ec_host_cmd = {
	.rx_ctx = {
			.buf = COND_CODE_1(CONFIG_EC_HOST_CMD_HANDLER_RX_BUFFER_DEF, (hc_rx_buffer),
					   (NULL)),
		},
	.tx = {
			.buf = COND_CODE_1(CONFIG_EC_HOST_CMD_HANDLER_TX_BUFFER_DEF, (hc_tx_buffer),
					   (NULL)),
			.len_max = COND_CODE_1(CONFIG_EC_HOST_CMD_HANDLER_TX_BUFFER_DEF,
					       (CONFIG_EC_HOST_CMD_HANDLER_TX_BUFFER_SIZE), (0)),
		},
};

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 send_status_response(const struct ec_host_cmd_backend *backend,
				 struct ec_host_cmd_tx_buf *tx,
				 const enum ec_host_cmd_status status)
{
	struct ec_host_cmd_response_header *const tx_header = (void *)tx->buf;

	tx_header->prtcl_ver = 3;
	tx_header->result = status;
	tx_header->data_len = 0;
	tx_header->reserved = 0;
	tx_header->checksum = 0;
	tx_header->checksum = cal_checksum((uint8_t *)tx_header, TX_HEADER_SIZE);

	tx->len = TX_HEADER_SIZE;

	backend->api->send(backend);
}

static enum ec_host_cmd_status verify_rx(struct ec_host_cmd_rx_ctx *rx)
{
	/* rx buf and len now have valid incoming data */
	if (rx->len < RX_HEADER_SIZE) {
		return EC_HOST_CMD_REQUEST_TRUNCATED;
	}

	const struct ec_host_cmd_request_header *rx_header =
		(struct ec_host_cmd_request_header *)rx->buf;

	/* Only support version 3 */
	if (rx_header->prtcl_ver != 3) {
		return EC_HOST_CMD_INVALID_HEADER;
	}

	const uint16_t rx_valid_data_size = rx_header->data_len + RX_HEADER_SIZE;
	/*
	 * Ensure we received at least as much data as is expected.
	 * It is okay to receive more since some hardware interfaces
	 * add on extra padding bytes at the end.
	 */
	if (rx->len < rx_valid_data_size) {
		return EC_HOST_CMD_REQUEST_TRUNCATED;
	}

	/* Validate checksum */
	if (cal_checksum((uint8_t *)rx_header, rx_valid_data_size) != 0) {
		return EC_HOST_CMD_INVALID_CHECKSUM;
	}

	return EC_HOST_CMD_SUCCESS;
}

static enum ec_host_cmd_status validate_handler(const struct ec_host_cmd_handler *handler,
						const struct ec_host_cmd_handler_args *args)
{
	if (handler->min_rqt_size > args->input_buf_size) {
		return EC_HOST_CMD_REQUEST_TRUNCATED;
	}

	if (handler->min_rsp_size > args->output_buf_max) {
		return EC_HOST_CMD_INVALID_RESPONSE;
	}

	if (args->version > sizeof(handler->version_mask) ||
	    !(handler->version_mask & BIT(args->version))) {
		return EC_HOST_CMD_INVALID_VERSION;
	}

	return EC_HOST_CMD_SUCCESS;
}

static enum ec_host_cmd_status prepare_response(struct ec_host_cmd_tx_buf *tx, uint16_t len)
{
	struct ec_host_cmd_response_header *const tx_header = (void *)tx->buf;

	tx_header->prtcl_ver = 3;
	tx_header->result = EC_HOST_CMD_SUCCESS;
	tx_header->data_len = len;
	tx_header->reserved = 0;

	const uint16_t tx_valid_data_size = tx_header->data_len + TX_HEADER_SIZE;

	if (tx_valid_data_size > tx->len_max) {
		return EC_HOST_CMD_INVALID_RESPONSE;
	}

	/* Calculate checksum */
	tx_header->checksum = 0;
	tx_header->checksum = cal_checksum(tx->buf, tx_valid_data_size);

	tx->len = tx_valid_data_size;

	return EC_HOST_CMD_SUCCESS;
}

int ec_host_cmd_send_response(enum ec_host_cmd_status status,
			      const struct ec_host_cmd_handler_args *args)
{
	struct ec_host_cmd *hc = &ec_host_cmd;
	struct ec_host_cmd_tx_buf *tx = &hc->tx;

	if (status != EC_HOST_CMD_SUCCESS) {
		send_status_response(hc->backend, tx, status);
		return status;
	}

	status = prepare_response(tx, args->output_buf_size);
	if (status != EC_HOST_CMD_SUCCESS) {
		send_status_response(hc->backend, tx, status);
		return status;
	}

	return hc->backend->api->send(hc->backend);
}

FUNC_NORETURN static void ec_host_cmd_thread(void *hc_handle, void *arg2, void *arg3)
{
	ARG_UNUSED(arg2);
	ARG_UNUSED(arg3);
	enum ec_host_cmd_status status;
	struct ec_host_cmd *hc = (struct ec_host_cmd *)hc_handle;
	struct ec_host_cmd_rx_ctx *rx = &hc->rx_ctx;
	struct ec_host_cmd_tx_buf *tx = &hc->tx;
	const struct ec_host_cmd_handler *found_handler;
	const struct ec_host_cmd_request_header *const rx_header = (void *)rx->buf;
	/* The pointer to rx buffer is constant during communication */
	struct ec_host_cmd_handler_args args = {
		.output_buf = (uint8_t *)tx->buf + TX_HEADER_SIZE,
		.output_buf_max = tx->len_max - TX_HEADER_SIZE,
		.input_buf = rx->buf + RX_HEADER_SIZE,
		.reserved = NULL,
	};

	while (1) {
		/* Wait until RX messages is received on host interface */
		k_sem_take(&rx->handler_owns, K_FOREVER);

		status = verify_rx(rx);
		if (status != EC_HOST_CMD_SUCCESS) {
			send_status_response(hc->backend, tx, status);
			continue;
		}

		found_handler = NULL;
		STRUCT_SECTION_FOREACH(ec_host_cmd_handler, handler)
		{
			if (handler->id == rx_header->cmd_id) {
				found_handler = handler;
				break;
			}
		}

		/* No handler in this image for requested command */
		if (found_handler == NULL) {
			send_status_response(hc->backend, tx, EC_HOST_CMD_INVALID_COMMAND);
			continue;
		}

		args.command = rx_header->cmd_id;
		args.version = rx_header->cmd_ver;
		args.input_buf_size = rx_header->data_len;
		args.output_buf_size = 0;

		status = validate_handler(found_handler, &args);
		if (status != EC_HOST_CMD_SUCCESS) {
			send_status_response(hc->backend, tx, status);
			continue;
		}

		status = found_handler->handler(&args);

		ec_host_cmd_send_response(status, &args);
	}
}

#ifndef CONFIG_EC_HOST_CMD_DEDICATED_THREAD
FUNC_NORETURN void ec_host_cmd_task(void)
{
	ec_host_cmd_thread(&ec_host_cmd, NULL, NULL);
}
#endif

int ec_host_cmd_init(struct ec_host_cmd_backend *backend)
{
	struct ec_host_cmd *hc = &ec_host_cmd;
	int ret;
	uint8_t *handler_tx_buf, *handler_rx_buf;
	uint8_t *handler_tx_buf_end, *handler_rx_buf_end;
	uint8_t *backend_tx_buf, *backend_rx_buf;

	hc->backend = backend;

	/* Allow writing to rx buff at startup */
	k_sem_init(&hc->rx_ctx.handler_owns, 0, 1);

	handler_tx_buf = hc->tx.buf;
	handler_rx_buf = hc->rx_ctx.buf;
	handler_tx_buf_end = handler_tx_buf + CONFIG_EC_HOST_CMD_HANDLER_TX_BUFFER_SIZE;
	handler_rx_buf_end = handler_rx_buf + CONFIG_EC_HOST_CMD_HANDLER_RX_BUFFER_SIZE;

	ret = backend->api->init(backend, &hc->rx_ctx, &hc->tx);

	backend_tx_buf = hc->tx.buf;
	backend_rx_buf = hc->rx_ctx.buf;

	if (ret != 0) {
		return ret;
	}

	if (!backend_tx_buf || !backend_rx_buf) {
		LOG_ERR("No buffer for Host Command communication");
		return -EIO;
	}

	/* Check if a backend uses provided buffers. The buffer pointers can be shifted within the
	 * buffer to make space for preamble. Make sure the rx/tx pointers are within the provided
	 * buffers ranges.
	 */
	if ((handler_tx_buf &&
	     !((handler_tx_buf <= backend_tx_buf) && (handler_tx_buf_end > backend_tx_buf))) ||
	    (handler_rx_buf &&
	     !((handler_rx_buf <= backend_rx_buf) && (handler_rx_buf_end > backend_rx_buf)))) {
		LOG_WRN("Host Command handler provided unused buffer");
	}

#ifdef CONFIG_EC_HOST_CMD_DEDICATED_THREAD
	k_thread_create(&hc->thread, hc_stack, CONFIG_EC_HOST_CMD_HANDLER_STACK_SIZE,
			ec_host_cmd_thread, (void *)hc, NULL, NULL, CONFIG_EC_HOST_CMD_HANDLER_PRIO,
			0, K_NO_WAIT);
	k_thread_name_set(&hc->thread, "ec_host_cmd");
#endif /* CONFIG_EC_HOST_CMD_DEDICATED_THREAD */

	return 0;
}

const struct ec_host_cmd *ec_host_cmd_get_hc(void)
{
	return &ec_host_cmd;
}