blob: ca8a8af01419cdd85a59b98c6ec813b491936625 [file] [log] [blame]
// Copyright lowRISC contributors.
// Licensed under the Apache License, Version 2.0, see LICENSE for details.
// SPDX-License-Identifier: Apache-2.0
#include "sw/device/sca/lib/simple_serial.h"
#include "sw/device/lib/arch/device.h"
#include "sw/device/lib/base/macros.h"
#include "sw/device/lib/base/memory.h"
#include "sw/device/lib/dif/dif_uart.h"
#include "sw/device/lib/runtime/print.h"
#include "sw/device/sca/lib/prng.h"
#include "hw/top_earlgrey/sw/autogen/top_earlgrey.h"
/**
* Macro for ignoring return values.
*
* This is needed because ‘(void)expr;` does not work for gcc.
*/
#define IGNORE_RESULT(expr) \
if (expr) { \
}
enum {
/**
* Simple serial protocol version 1.1.
*/
kSimpleSerialProtocolVersion = 1,
kUartMaxRxPacketSize = 64,
};
/**
* Command handlers.
*
* Clients can register handlers for commands 'a'-'z' using
* `simple_serial_register_handler()` except for 'v' (version) and 's' (seed
* PRNG), which are handled by this library. This array has an extra element
* (27) that is initialized in `simple_serial_init()` to point to
* `simple_serial_unknown_command()` in order to simplify handling of invalid
* commands in `simple_serial_process_packet()`.
*/
static simple_serial_command_handler handlers[27];
static const dif_uart_t *uart;
static bool simple_serial_is_valid_command(uint8_t cmd) {
return cmd >= 'a' && cmd <= 'z';
}
/**
* Converts a hex encoded nibble to binary.
*
* @param hex A hex encoded nibble.
* @param[out] val Value of the nibble.
*
* @return Result of the operation.
*/
static simple_serial_result_t simple_serial_hex_to_bin(uint8_t hex,
uint8_t *val) {
if (hex >= '0' && hex <= '9') {
*val = hex - '0';
} else if (hex >= 'A' && hex <= 'F') {
*val = hex - 'A' + 10;
} else if (hex >= 'a' && hex <= 'f') {
*val = hex - 'a' + 10;
} else {
return kSimpleSerialError;
}
return kSimpleSerialOk;
}
/**
* Receives a simple serial packet over UART.
*
* Simple serial packets are composed of:
* - Command: A single byte character,
* - Payload: A variable length hex encoded string,
* - Terminator: '\n'.
*
* @param[out] cmd Simple serial command.
* @param[out] data Buffer for received packet payload.
* @param data_buf_len Length of the packet payload buffer.
* @param[out] data_len Received packet payload length.
*/
static void simple_serial_receive_packet(uint8_t *cmd, uint8_t *data,
size_t data_buf_len,
size_t *data_len) {
while (true) {
// Read command byte - a single character.
IGNORE_RESULT(dif_uart_byte_receive_polled(uart, cmd));
if (*cmd == '\n') {
continue;
}
*data_len = 0;
// Read payload - a variable length hex encoded string terminated with '\n'.
do {
uint8_t hex_byte[2];
IGNORE_RESULT(dif_uart_byte_receive_polled(uart, &hex_byte[0]));
if (hex_byte[0] == '\n') {
return;
}
if (simple_serial_hex_to_bin(hex_byte[0], &hex_byte[0]) !=
kSimpleSerialOk) {
break;
}
IGNORE_RESULT(dif_uart_byte_receive_polled(uart, &hex_byte[1]));
if (simple_serial_hex_to_bin(hex_byte[1], &hex_byte[1]) !=
kSimpleSerialOk) {
break;
}
if (*data_len == data_buf_len) {
break;
}
data[*data_len] = hex_byte[0] << 4 | hex_byte[1];
++*data_len;
} while (true);
}
}
/**
* Returns the index of a command's handler in `handlers`.
*
* This function returns the index of the last element, which points to
* `simple_serial_unknown_command(), if the given command is not valid.
*
* @param cmd A simple serial command.
* @return Command handler's index in `handlers`.
*/
static size_t simple_serial_get_handler_index(uint8_t cmd) {
if (simple_serial_is_valid_command(cmd)) {
return cmd - 'a';
} else {
return ARRAYSIZE(handlers) - 1;
}
}
/**
* Simple serial 'v' (version) command handler.
*
* Returns the simple serial version that this file implements. This command is
* useful for checking that the host and the device can communicate properly
* before starting capturing traces.
*
* @param data Received packet payload.
* @param data_len Payload length.
*/
static void simple_serial_version(const uint8_t *data, size_t data_len) {
simple_serial_send_status(kSimpleSerialProtocolVersion);
}
/**
* Simple serial 's' (seed PRNG) command handler.
*
* This function only supports 4-byte seeds.
*
* @param seed A buffer holding the seed.
* @param seed_len Seed length.
*/
static void simple_serial_seed_prng(const uint8_t *seed, size_t seed_len) {
SS_CHECK(seed_len == sizeof(uint32_t));
prng_seed(read_32(seed));
}
/**
* Handler for uninmplemented simple serial commands.
*
* Sends an error packet over UART.
*
* @param data Received packet payload
* @param data_len Payload length.
*/
static void simple_serial_unknown_command(const uint8_t *data,
size_t data_len) {
simple_serial_send_status(kSimpleSerialError);
}
void simple_serial_init(const dif_uart_t *uart_) {
uart = uart_;
for (size_t i = 0; i < ARRAYSIZE(handlers); ++i) {
handlers[i] = simple_serial_unknown_command;
}
handlers[simple_serial_get_handler_index('s')] = simple_serial_seed_prng;
handlers[simple_serial_get_handler_index('v')] = simple_serial_version;
}
simple_serial_result_t simple_serial_register_handler(
uint8_t cmd, simple_serial_command_handler handler) {
if (!simple_serial_is_valid_command(cmd)) {
return kSimpleSerialError;
} else if (cmd == 's' || cmd == 'v') {
// Cannot register handlers for built-in commands.
return kSimpleSerialError;
} else {
handlers[simple_serial_get_handler_index(cmd)] = handler;
return kSimpleSerialOk;
}
}
void simple_serial_process_packet(void) {
uint8_t cmd;
uint8_t data[kUartMaxRxPacketSize];
size_t data_len;
simple_serial_receive_packet(&cmd, data, ARRAYSIZE(data), &data_len);
handlers[simple_serial_get_handler_index(cmd)](data, data_len);
}
void simple_serial_send_packet(const uint8_t cmd, const uint8_t *data,
size_t data_len) {
char buf;
base_snprintf(&buf, 1, "%c", cmd);
IGNORE_RESULT(dif_uart_byte_send_polled(uart, buf));
simple_serial_print_hex(data, data_len);
base_snprintf(&buf, 1, "\n");
IGNORE_RESULT(dif_uart_byte_send_polled(uart, buf));
}
void simple_serial_send_status(uint8_t res) {
simple_serial_send_packet('z', (uint8_t[1]){res}, 1);
}
void simple_serial_print_hex(const uint8_t *data, size_t data_len) {
char buf[2];
for (size_t i = 0; i < data_len; ++i) {
base_snprintf(&buf[0], 2, "%02x", data[i]);
IGNORE_RESULT(dif_uart_byte_send_polled(uart, buf[0]));
IGNORE_RESULT(dif_uart_byte_send_polled(uart, buf[1]));
}
}