/* Copyright 2020 The Chromium OS Authors. All rights reserved.
 * Use of this source code is governed by a BSD-style license that can be
 * found in the LICENSE file.
 *
 * Packetized console interface.
 */

#include "console.h"
#include "crc8.h"
#include "task.h"
#include "timer.h"
#include "uart.h"
#include "usb_console.h"
#include "util.h"

#define CONSOLE_PACKET_HEADER_LEN 13
#define CONSOLE_PACKET_MAGIC 0xc2
#define CONSOLE_PACKET_END 0xc1

/*
 * Packet header structure was borrowed from the Acropora project and adopted
 * for use in Cr50.
 */
struct console_packet {
	/* Magic number = CONSOLE_PACKET_MAGIC */
	uint8_t magic;

	/*
	 * Packet sequence number, incremented each time sender sends a packet.
	 * So receiver can detect small numbers of corrupt/dropped packets.
	 */
	uint8_t sequence : 4;

	/* Set if the sender had to discard packets due to buffer overflow. */
	uint8_t overflow : 1;
	uint8_t dummy : 3;

	/* Channel; values from enum console_channel */
	uint8_t channel;

	/* Bottom 48 bits of system time; enough for 8 years @ 1 us */
	uint8_t timestamp[6];

	/*
	 * Length of variable-length section in bytes, not including the
	 * packet end trailer.
	 */
	uint8_t data_len;

	uint16_t str_index; /* Index of the string in the database. */

	/* Header checksum */
	uint8_t crc;

	/* Fixed length header to here. */

	/*
	 * Followed by variable-length data, if data_len > 0.
	 *
	 * params: 1-8 of objects matching the format of the string indexed by
	 * 'str_index' above.
	 *
	 * CONSOLE_PACKET_END, as a sanity-check that we haven't dropped
	 * anything.  A checksum or CRC would be kinda expensive for debug
	 * data.  Note that it is not present if data_len == 0.
	 */
} __packed;

BUILD_ASSERT(sizeof(struct console_packet) == CONSOLE_PACKET_HEADER_LEN);

/*
 * Types of parameters recognized by util/util_precompile.py when processing
 * format string in .E files.
 *
 * When invoking the cmsgX functions all parameters are typecasted to
 * uintptr_t, and as such must fit into 4 bytes (on the 32 bit machine
 * cortex-m is).
 */
enum param_types {
	PARAM_INT = 1,
	/* Parameter is a pointer to an 8 byte integer. */
	PARAM_LONG = 2,
	/* Parameter is a pointer to an ASCIIZ string. */
	PARAM_STRING = 3,
	PARAM_PTR = 4,
	PARAM_HEX_BUF = 5,
	/*
	 * Parameter is an int, the number of the function name string in the
	 * dictionary prepared by util_precompile.py. Since it is passed as a
	 * %s parameter, to allow the terminal to tell the difference this
	 * value is sent as 3 bytes, first 0xff and then the two byte function
	 * name string index.
	 */
	PARAM_FUNC_NAME = 6,
	/*
	 * Parameter is a pointer to a 8 byte value, unless the parameter is
	 * zero, in which case current time is used.
	 */
	PARAM_TIMESTAMP = 7,
};

/*
 * copy_params: copy actual parameter values into the console channel.
 *
 * This function is processing parameters 'on the fly' and sends them into the
 * console channel. There are two channels (uart and usb), so this fucnction
 * is called twice. It could be called once and save the parameters in a
 * buffer, but the buffer would have to be allocated on the stack, this
 * remains under consideration.
 *
 * mask is a set of 4 bit fields (enum param_types),
 * params[] includes as many values as there are non-zero fields in mask.
 * cmpuc is a pointer to the function which actually sends data.
 */
static void copy_params(uint32_t mask, uintptr_t params[],
			int (*cmputc)(const char *out, int len))
{
	enum param_types param_type;
	uintptr_t param;
	uint64_t t;
	uint16_t func_num;

	while ((param_type = mask & 0xf) != 0) {
		const uint8_t ff = 0xff;

		param = *params++;
		mask >>= 4;

		switch (param_type) {
		case PARAM_FUNC_NAME:
			cmputc(&ff, sizeof(ff));
			func_num = param;
			cmputc((const void *) &func_num, sizeof(func_num));
			break;

		case PARAM_INT:
		case PARAM_PTR:
			cmputc((const void *)&param, sizeof(param));
			break;

		case PARAM_LONG:
			cmputc((const void *)param, sizeof(uint64_t));
			break;

		case PARAM_TIMESTAMP:
			if (param == 0)
				/*
				 * Terminal will use timestamp saved in the
				 * packet header.
				 */
				t = 0;
			else
				t = get_time().val;
			cmputc((const void *)&t, sizeof(t));
			break;

		case PARAM_HEX_BUF: {
			const struct hex_buffer_params *hbp =
				(const struct hex_buffer_params *)param;
			cmputc((const void *)&hbp->size, sizeof(hbp->size));
			cmputc((const void *)hbp->buffer, hbp->size);
			break;
		}

		case PARAM_STRING:
			/* Include trailing zero. */
			cmputc((const void *)param,
			       strlen((const char *)param) + 1);
		}
	}
}

/* A structure describing a communication channel and its state. */
struct packet_channel {
	/* Function to send data on the channel. */
	int (*cmputc)(const char *out, int len);
	/*
	 * Function to check how much room is left in the channel buffer, the
	 * packet is not sent if it won't fit.
	 */
	size_t (*room_check)(void);
	/*
	 * Channel run time context - sequence number to be used on the next
	 * packet and the indicator of channel overflow when packet had to be
	 * dropped because there was no room in the channel buffer.
	 */
	uint8_t seq_num;
	uint8_t overflow;
};

/*
 * ship_packet:  send a console packet on a channel.
 *
 * cpp - pointer to a partially filled packet header.
 * mask - a set of 4 bit parameter descriptions
 * params - an array of parameters matching the mask fields
 * pc - pointer to the packet channel structure used to send the packet.
 *
 * Returns overflow error if there is no room in the channel buffer to send
 *     the packet.
 */
static int ship_packet(struct console_packet *cpp, uint32_t mask,
		       uintptr_t params[], struct packet_channel *pc)
{
	pc->seq_num++;

	/* See if there is room to send this packet. */
	if (pc->room_check() < (sizeof(*cpp) + cpp->data_len + 1)) {
		pc->overflow = 1;
		return EC_ERROR_OVERFLOW;
	}

	cpp->sequence = pc->seq_num;
	cpp->overflow = pc->overflow & 1;
	pc->overflow = 0;

	/* Now crc can be calculated. */
	cpp->crc = crc8((void *)cpp, sizeof(*cpp) - sizeof(cpp->crc));
	pc->cmputc((void *)cpp, sizeof(*cpp));
	if (mask) {
		const char pe = CONSOLE_PACKET_END;

		copy_params(mask, params, pc->cmputc);
		pc->cmputc(&pe, 1);
	}
	return EC_SUCCESS;
}

/*
 * zz_msg - send console packets on both console physical channels.
 *
 * Calculate the size of the packet (needs to be done in advance to decide if
 * there is enough room in the channel buffer) and then call channel specific
 * transmit functions.
 *
 * chan - logical console channel, the one which can be turned and off by
 *        the cli 'channel' command.
 * str_index - index of the original format string in the list of strings
 *        prepared by util_precompile.py
 * fmt_mask is a set of 4 bit fields (enum param_types),
 * params[] includes as many values as there are non-zero fields in mask.
 *
 * Returns overflow error if either or both channels could not fit the packet.
 */
static int zz_msg(enum console_channel chan, int str_index, uint32_t fmt_mask,
		  uintptr_t params[])
{
	uint16_t total_param_length = 0;
	enum param_types param_type;
	int param_count = 0;
	uint64_t t;
	uint32_t mask_copy;
	int rv_uart;
	int rv_usb;
	static struct packet_channel pcs[] = {
		{ .cmputc = uart_put, .room_check = uart_buffer_room },
		{ .cmputc = usb_put, .room_check = usb_buffer_room }
	};

	struct console_packet cp = {
		.magic = CONSOLE_PACKET_MAGIC,
	};

	if (!(CC_MASK(chan) & channel_mask))
		return EC_SUCCESS;

	/* Let's scan the mask to see how many parameters there are. */
	mask_copy = fmt_mask;
	param_type = mask_copy & 0xf;
	while (param_type) {
		switch (param_type) {
		case PARAM_FUNC_NAME:
			/* 0xff and the 2 byte function name index.*/
			total_param_length += 3;
			break;

		case PARAM_INT:
		case PARAM_PTR:
			total_param_length += sizeof(uintptr_t);
			break;
		case PARAM_LONG:
		case PARAM_TIMESTAMP:
			total_param_length += sizeof(uint64_t);
			break;
		case PARAM_HEX_BUF: {
			const struct hex_buffer_params *hbp =
				(void *)params[param_count];

			total_param_length += hbp->size + sizeof(hbp->size);
			break;
		}

		case PARAM_STRING:
			/* Include trailing zero. */
			total_param_length +=
				strlen((const char *)params[param_count]) + 1;
			break;

		default:
			/* TODO(vbendeb): add panic message generation. */
			break;
		}

		mask_copy >>= 4;
		param_type = mask_copy & 0xf;
		param_count++;
	}

	cp.channel = chan;

	t = get_time().val;
	memcpy(cp.timestamp, &t, sizeof(cp.timestamp));
	cp.str_index = str_index;
	cp.data_len = total_param_length;

	if (!in_interrupt_context())
		interrupt_disable();

	rv_uart = ship_packet(&cp, fmt_mask, params, &pcs[0]);
	rv_usb = ship_packet(&cp, fmt_mask, params, &pcs[1]);

	if (!in_interrupt_context())
		interrupt_enable();

	if (rv_uart != EC_SUCCESS)
		return rv_uart;

	return rv_usb;
}

/* Packet mode console interface functions. */
int cmsg0(enum console_channel chan, int str_index)
{
	return zz_msg(chan, str_index, 0, NULL);
}

int cmsg1(enum console_channel chan, int str_index, uint32_t mask, uintptr_t p1)
{
	uintptr_t params[] = { p1 };

	return zz_msg(chan, str_index, mask, params);
}

int cmsg2(enum console_channel chan, int str_index, uint32_t mask, uintptr_t p1,
	  uintptr_t p2)
{
	uintptr_t params[] = { p1, p2 };

	return zz_msg(chan, str_index, mask, params);
}

int cmsg3(enum console_channel chan, int str_index, uint32_t mask, uintptr_t p1,
	  uintptr_t p2, uintptr_t p3)
{
	uintptr_t params[] = { p1, p2, p3 };

	return zz_msg(chan, str_index, mask, params);
}

int cmsg4(enum console_channel chan, int str_index, uint32_t mask, uintptr_t p1,
	  uintptr_t p2, uintptr_t p3, uintptr_t p4)
{
	uintptr_t params[] = { p1, p2, p3, p4 };

	return zz_msg(chan, str_index, mask, params);
}

int cmsg5(enum console_channel chan, int str_index, uint32_t mask, uintptr_t p1,
	  uintptr_t p2, uintptr_t p3, uintptr_t p4, uintptr_t p5)
{
	uintptr_t params[] = { p1, p2, p3, p4, p5 };

	return zz_msg(chan, str_index, mask, params);
}

int cmsg6(enum console_channel chan, int str_index, uint32_t mask, uintptr_t p1,
	  uintptr_t p2, uintptr_t p3, uintptr_t p4, uintptr_t p5, uintptr_t p6)
{
	uintptr_t params[] = { p1, p2, p3, p4, p5, p6 };

	return zz_msg(chan, str_index, mask, params);
}

int cmsg7(enum console_channel chan, int str_index, uint32_t mask, uintptr_t p1,
	  uintptr_t p2, uintptr_t p3, uintptr_t p4, uintptr_t p5, uintptr_t p6,
	  uintptr_t p7)
{
	uintptr_t params[] = { p1, p2, p3, p4, p5, p6, p7 };

	return zz_msg(chan, str_index, mask, params);
}

int cmsg8(enum console_channel chan, int str_index, uint32_t mask, uintptr_t p1,
	  uintptr_t p2, uintptr_t p3, uintptr_t p4, uintptr_t p5, uintptr_t p6,
	  uintptr_t p7, uintptr_t p8)
{
	uintptr_t params[] = { p1, p2, p3, p4, p5, p6, p7, p8 };

	return zz_msg(chan, str_index, mask, params);
}
