Boot Linux faster!

Check our new training course

Boot Linux faster!

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

Bootlin logo

Elixir Cross Referencer

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

#include <drivers/ec_host_cmd_periph.h>
#include <ec_host_cmd.h>
#include <devicetree.h>
#include <string.h>

#if !DT_HAS_CHOSEN(zephyr_ec_host_interface)
#error Must chose zephyr,ec-host-interface in device tree
#endif

#define DT_HOST_CMD_DEV DT_CHOSEN(zephyr_ec_host_interface)

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

/** Used by host command handlers for their response before going over wire */
uint8_t tx_buffer[CONFIG_EC_HOST_CMD_HANDLER_TX_BUFFER];

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_error_response(const struct device *const ec_host_cmd_dev,
				const enum ec_host_cmd_status error)
{
	struct ec_host_cmd_response_header *const tx_header = (void *)tx_buffer;

	tx_header->prtcl_ver = 3;
	tx_header->result = error;
	tx_header->data_len = 0;
	tx_header->reserved = 0;
	tx_header->checksum = 0;
	tx_header->checksum = cal_checksum(tx_buffer, TX_HEADER_SIZE);

	const struct ec_host_cmd_periph_tx_buf tx = {
		.buf = tx_buffer,
		.len = TX_HEADER_SIZE,
	};
	ec_host_cmd_periph_send(ec_host_cmd_dev, &tx);
}

static void handle_host_cmds_entry(void *arg1, void *arg2, void *arg3)
{
	ARG_UNUSED(arg1);
	ARG_UNUSED(arg2);
	ARG_UNUSED(arg3);
	const struct device *ec_host_cmd_dev;
	struct ec_host_cmd_periph_rx_ctx rx;

	ec_host_cmd_dev = device_get_binding(DT_LABEL(DT_HOST_CMD_DEV));

	ec_host_cmd_periph_init(ec_host_cmd_dev, &rx);

	while (1) {
		/* We have finished reading from RX buffer, so allow another
		 * incoming msg.
		 */
		k_sem_give(rx.dev_owns);

		/* Wait until and RX messages is received on host interace */
		if (k_sem_take(rx.handler_owns, K_FOREVER) < 0) {
			/* This code path should never occur due to the nature of
			 * k_sem_take with K_FOREVER
			 */
			send_error_response(ec_host_cmd_dev,
					    EC_HOST_CMD_ERROR);
		}
		/* rx buf and len now have valid incoming data */

		if (*rx.len < RX_HEADER_SIZE) {
			send_error_response(ec_host_cmd_dev,
					    EC_HOST_CMD_REQUEST_TRUNCATED);
			continue;
		}

		const struct ec_host_cmd_request_header *const rx_header =
			(void *)rx.buf;

		/* Only support version 3 */
		if (rx_header->prtcl_ver != 3) {
			send_error_response(ec_host_cmd_dev,
					    EC_HOST_CMD_INVALID_HEADER);
			continue;
		}

		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) {
			send_error_response(ec_host_cmd_dev,
					    EC_HOST_CMD_REQUEST_TRUNCATED);
			continue;
		}

		/* Validate checksum */
		if (cal_checksum(rx.buf, rx_valid_data_size) != 0) {
			send_error_response(ec_host_cmd_dev,
					    EC_HOST_CMD_INVALID_CHECKSUM);
			continue;
		}

		const struct ec_host_cmd_handler *found_handler = NULL;

		Z_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_error_response(ec_host_cmd_dev,
					    EC_HOST_CMD_INVALID_COMMAND);
			continue;
		}

		/*
		 * Ensure that RX/TX buffers are cleared between each host
		 * command to ensure subsequent host command handlers cannot
		 * read data from previous host command runs.
		 */
		memset(&rx.buf[rx_valid_data_size], 0,
		       *rx.len - rx_valid_data_size);
		memset(tx_buffer, 0, sizeof(tx_buffer));

		struct ec_host_cmd_handler_args args = {
			.input_buf = rx.buf + RX_HEADER_SIZE,
			.input_buf_size = rx_header->data_len,
			.output_buf = tx_buffer + TX_HEADER_SIZE,
			.output_buf_size = sizeof(tx_buffer) - TX_HEADER_SIZE,
			.version = rx_header->cmd_ver,
		};

		if (found_handler->min_rqt_size > args.input_buf_size) {
			send_error_response(ec_host_cmd_dev,
					    EC_HOST_CMD_REQUEST_TRUNCATED);
			continue;
		}

		if (found_handler->min_rsp_size > args.output_buf_size) {
			send_error_response(ec_host_cmd_dev,
					    EC_HOST_CMD_INVALID_RESPONSE);
			continue;
		}

		if (args.version > sizeof(found_handler->version_mask) ||
		    !(found_handler->version_mask & BIT(args.version))) {
			send_error_response(ec_host_cmd_dev,
					    EC_HOST_CMD_INVALID_VERSION);
			continue;
		}

		const enum ec_host_cmd_status handler_rv =
			found_handler->handler(&args);

		if (handler_rv != EC_HOST_CMD_SUCCESS) {
			send_error_response(ec_host_cmd_dev, handler_rv);
			continue;
		}

		struct ec_host_cmd_response_header *const tx_header =
			(void *)tx_buffer;

		tx_header->prtcl_ver = 3;
		tx_header->result = EC_HOST_CMD_SUCCESS;
		tx_header->data_len = args.output_buf_size;

		const uint16_t tx_valid_data_size =
			tx_header->data_len + TX_HEADER_SIZE;
		if (tx_valid_data_size > sizeof(tx_buffer)) {
			send_error_response(ec_host_cmd_dev,
					    EC_HOST_CMD_INVALID_RESPONSE);
			continue;
		}

		/* Calculate checksum */
		tx_header->checksum =
			cal_checksum(tx_buffer, tx_valid_data_size);

		const struct ec_host_cmd_periph_tx_buf tx = {
			.buf = tx_buffer,
			.len = tx_valid_data_size,
		};
		ec_host_cmd_periph_send(ec_host_cmd_dev, &tx);
	}
}

K_THREAD_DEFINE(ec_host_cmd_handler_tid, CONFIG_EC_HOST_CMD_HANDLER_STACK_SIZE,
		handle_host_cmds_entry, NULL, NULL, NULL,
		K_LOWEST_APPLICATION_THREAD_PRIO, 0, 0);