533 lines
15 KiB
C
Raw Normal View History

2022-10-23 23:45:43 -07:00
/*************************************************************************************************/
/*!
* \file terminal.c
*
* \brief Terminal handler.
*
* $Date: 2016-03-29 14:55:12 -0700 (Tue, 29 Mar 2016) $
* $Revision: 6524 $
*
* Copyright (c) 2015-2017 ARM Ltd., all rights reserved.
* ARM confidential and proprietary.
*
* IMPORTANT. Your use of this file is governed by a Software License Agreement
* ("Agreement") that must be accepted in order to download or otherwise receive a
* copy of this file. You may not use or copy this file for any purpose other than
* as described in the Agreement. If you do not agree to all of the terms of the
* Agreement do not use this file and delete all copies in your possession or control;
* if you do not have a copy of the Agreement, you must contact ARM Ltd. prior
* to any use, copying or further distribution of this software.
*/
/*************************************************************************************************/
#include <stdlib.h>
#include <string.h>
#include <stdarg.h>
#include "terminal.h"
#include "print.h"
#include "wsf_types.h"
#include "wsf_assert.h"
#include "wsf_trace.h"
#include "bstream.h"
/**************************************************************************************************
Macros
**************************************************************************************************/
#define TERMINAL_IS_SPACE(c) ((c == '\n') || (c == '\t') || (c == '\r') || (c == ' ') || (c == '\v') || (c == '\f'))
#define TERMINAL_IS_PRINT(c) ((c >= 0x20) && (c != 0x7F))
/*! \brief Terminal events. */
enum
{
TERMINAL_EVENT_COMMAND_RX = (1 << 0)
};
/**************************************************************************************************
Data Types
**************************************************************************************************/
/*! \brief Control block for terminal. */
typedef struct
{
wsfHandlerId_t handlerId; /*!< Handler ID for TerminalHandler(). */
terminalCommand_t *pFirstCommand; /*!< Pointer to first command. */
char buf[TERMINAL_MAX_COMMAND_LEN + 1]; /*!< Command buffer. */
bool_t isExecuting; /*!< TRUE if command in buffer is executing. */
bool_t doEcho; /*!< TRUE if input should be echoed. */
uint32_t bufOffset; /*!< Offset within buffer. */
terminalUartTx_t terminalTx; /*!< Function to transmit via UART. */
} terminalCtrlBlk_t;
/**************************************************************************************************
Local Function Prototypes
**************************************************************************************************/
/*! \brief Help Command Handler */
static uint8_t terminalCommandHelpHandler(uint32_t argc, char **argv);
/*! \brief Echo Command Handler */
static uint8_t terminalCommandEchoHandler(uint32_t argc, char **argv);
/**************************************************************************************************
Local Variables
**************************************************************************************************/
/*! \brief Control block for terminal. */
static terminalCtrlBlk_t terminalCb;
/*! \brief Help command. */
static terminalCommand_t terminalCommandHelp = { NULL, "help", "help", terminalCommandHelpHandler };
/*! \brief Echo command. */
static terminalCommand_t terminalCommandEcho = { NULL, "echo", "echo <on|off>", terminalCommandEchoHandler };
/*************************************************************************************************/
/*!
* \fn TerminalInit
*
* \brief Initialize terminal.
*
* \param handlerId Handler ID for TerminalHandler().
*
* \return None.
*/
/*************************************************************************************************/
void TerminalInit(wsfHandlerId_t handlerId)
{
APP_TRACE_INFO0("terminal: init");
terminalCb.handlerId = handlerId;
terminalCb.pFirstCommand = NULL;
terminalCb.isExecuting = FALSE;
terminalCb.doEcho = TRUE;
terminalCb.bufOffset = 0;
TerminalRegisterCommand(&terminalCommandHelp);
TerminalRegisterCommand(&terminalCommandEcho);
}
/*************************************************************************************************/
/*!
* \fn TerminalRegisterUartTxFunc
*
* \brief Register the UART Tx Function for the platform.
*
* \return None.
*/
/*************************************************************************************************/
void TerminalRegisterUartTxFunc(terminalUartTx_t uartTxFunc)
{
terminalCb.terminalTx = uartTxFunc;
}
/*************************************************************************************************/
/*!
* \fn TerminalRegisterCommand
*
* \brief Register command with terminal.
*
* \param pCommand Command.
*
* \return None.
*/
/*************************************************************************************************/
void TerminalRegisterCommand(terminalCommand_t *pCommand)
{
terminalCommand_t *pCommandTemp = terminalCb.pFirstCommand;
if (pCommandTemp == NULL)
{
terminalCb.pFirstCommand = pCommand;
}
else
{
while (pCommandTemp->pNext != NULL)
{
pCommandTemp = pCommandTemp->pNext;
}
pCommandTemp->pNext = pCommand;
}
pCommand->pNext = NULL;
}
/*************************************************************************************************/
/*!
* \fn terminalExecute
*
* \brief Execute a received command.
*
* \param pBuf Command string.
*
* \return None.
*/
/*************************************************************************************************/
static void terminalExecute(char *pBuf)
{
uint32_t argc = 0;
char *argv[TERMINAL_MAX_ARGC + 1];
uint32_t length;
char *pBufCur;
int state;
enum
{
STATE_OUTSIDE_OF_ARG,
STATE_JUST_GOT_QUOTE,
STATE_INSIDE_OF_ARG,
STATE_INSIDE_OF_ARG_IN_QUOTES
};
/* Parse arguments in command */
state = STATE_OUTSIDE_OF_ARG;
length = strlen(pBuf);
for (pBufCur = pBuf; pBufCur < pBuf + length; pBufCur++)
{
switch (state)
{
case STATE_OUTSIDE_OF_ARG:
{
if (*pBufCur == '\"')
{
state = STATE_JUST_GOT_QUOTE;
}
else if (!TERMINAL_IS_SPACE(*pBufCur))
{
state = STATE_INSIDE_OF_ARG;
if (argc < TERMINAL_MAX_ARGC)
{
argv[argc] = pBufCur;
}
argc++;
}
break;
}
case STATE_JUST_GOT_QUOTE:
{
if (argc < TERMINAL_MAX_ARGC)
{
argv[state] = pBufCur;
}
argc++;
if (*pBufCur == '\"')
{
state = STATE_OUTSIDE_OF_ARG;
*pBufCur = '\0';
}
else
{
state = STATE_INSIDE_OF_ARG_IN_QUOTES;
}
break;
}
case STATE_INSIDE_OF_ARG:
{
if (TERMINAL_IS_SPACE(*pBufCur))
{
state = STATE_OUTSIDE_OF_ARG;
*pBufCur = '\0';
}
else if (*pBufCur == '\"')
{
state = STATE_JUST_GOT_QUOTE;
*pBufCur = '\0';
}
break;
}
case STATE_INSIDE_OF_ARG_IN_QUOTES:
{
if (*pBufCur == '\"')
{
state = STATE_OUTSIDE_OF_ARG;
*pBufCur = '\0';
}
break;
}
}
}
/* Find & invoke command. */
if (argc > TERMINAL_MAX_ARGC)
{
TerminalTxStr(TERMINAL_STRING_ERROR "too many arguments" TERMINAL_STRING_NEW_LINE);
}
else if (argc > 0)
{
terminalCommand_t *pCommand = terminalCb.pFirstCommand;
while (pCommand != NULL)
{
if (strcmp(pCommand->pName, argv[0]) == 0)
{
break;
}
pCommand = pCommand->pNext;
}
if (pCommand == NULL)
{
TerminalTxStr(TERMINAL_STRING_ERROR "unrecognized command \"");
TerminalTxStr(argv[0]);
TerminalTxStr("\"" TERMINAL_STRING_NEW_LINE);
}
else
{
uint8_t r = pCommand->handler(argc, argv);
switch (r)
{
case TERMINAL_ERROR_EXEC:
case TERMINAL_ERROR_OK:
break;
case TERMINAL_ERROR_BAD_ARGUMENTS:
TerminalTxStr(TERMINAL_STRING_ERROR "Invalid argument(s)" TERMINAL_STRING_NEW_LINE);
break;
case TERMINAL_ERROR_TOO_FEW_ARGUMENTS:
TerminalTxStr(TERMINAL_STRING_ERROR "Too few arguments" TERMINAL_STRING_NEW_LINE);
break;
case TERMINAL_ERROR_TOO_MANY_ARGUMENTS:
TerminalTxStr(TERMINAL_STRING_ERROR "Too many arguments" TERMINAL_STRING_NEW_LINE);
break;
default:
TerminalTxStr(TERMINAL_STRING_ERROR "Unknown error" TERMINAL_STRING_NEW_LINE);
break;
}
}
}
}
/*************************************************************************************************/
/*!
* \fn TerminalHandler
*
* \brief Handler for terminal messages.
*
* \param event WSF event mask.
* \param pMsg WSF message.
*
* \return None.
*/
/*************************************************************************************************/
void TerminalHandler(wsfEventMask_t event, wsfMsgHdr_t *pMsg)
{
if ((event & TERMINAL_EVENT_COMMAND_RX) != 0)
{
terminalExecute(terminalCb.buf);
TerminalTxStr(TERMINAL_STRING_PROMPT);
terminalCb.bufOffset = 0;
terminalCb.isExecuting = FALSE;
}
}
/*************************************************************************************************/
/*!
* \fn TerminalRx
*
* \brief Called by application when a data byte is received.
*
* \param dataByte Received byte.
*
* \return None.
*/
/*************************************************************************************************/
void TerminalRx(uint8_t dataByte)
{
/* Hands off buf if command is executing. */
if (!terminalCb.isExecuting)
{
/* If this is the end of a line, signal task. */
if ((dataByte == '\n') || (dataByte == '\r'))
{
TerminalTxStr(TERMINAL_STRING_NEW_LINE);
terminalCb.buf[terminalCb.bufOffset] = '\0';
WsfSetEvent(terminalCb.handlerId, TERMINAL_EVENT_COMMAND_RX);
terminalCb.isExecuting = TRUE;
}
/* Check for delete. */
else if (dataByte == 0x7F)
{
if (terminalCb.bufOffset > 0)
{
terminalCb.bufOffset--;
if (terminalCb.doEcho)
{
TerminalTxStr("\b \b");
}
}
}
/* If we still have room in the buf, put it in buf. Othewise ignore it. */
else if (terminalCb.bufOffset < TERMINAL_MAX_COMMAND_LEN)
{
/* Ignore non-printable characters. */
if (TERMINAL_IS_PRINT(dataByte))
{
terminalCb.buf[terminalCb.bufOffset] = dataByte;
terminalCb.bufOffset++;
if (terminalCb.doEcho)
{
TerminalTxChar(dataByte);
}
}
}
}
}
/*************************************************************************************************/
/*!
* \fn TerminalTxStr
*
* \brief Called by application to transmit string.
*
* \param pStr String.
*
* \return None.
*/
/*************************************************************************************************/
void TerminalTxStr(const char *pStr)
{
TerminalTx((const uint8_t *)pStr, strlen(pStr));
}
/*************************************************************************************************/
/*!
* \fn TerminalTxChar
*
* \brief Called by application to transmit character.
*
* \param c Character.
*
* \return None.
*/
/*************************************************************************************************/
void TerminalTxChar(char c)
{
TerminalTx((const uint8_t *)&c, 1);
}
/*************************************************************************************************/
/*!
* \fn TerminalTxPrint
*
* \brief Called by application to print formatted data.
*
* \param pStr Message format string
* \param ... Additional arguments, printf-style
*
* \return None.
*/
/*************************************************************************************************/
void TerminalTxPrint(const char *pStr, ...)
{
uint32_t len;
char buf[TERMINAL_PRINTF_MAX_LEN];
va_list args;
va_start(args, pStr);
len = PrintVsn(buf, TERMINAL_PRINTF_MAX_LEN, pStr, args);
va_end(args);
TerminalTx((uint8_t *)buf, len);
}
/*************************************************************************************************/
/*!
* \fn terminalCommandHelpHandler
*
* \brief Handler for a terminal command.
*
* \param argc The number of arguments passed to the command.
* \param argv The array of arguments; the 0th argument is the command.
*
* \return Error code.
*/
/*************************************************************************************************/
static uint8_t terminalCommandHelpHandler(uint32_t argc, char **argv)
{
terminalCommand_t *pCommand = terminalCb.pFirstCommand;
if (argc > 1)
{
return TERMINAL_ERROR_TOO_MANY_ARGUMENTS;
}
while (pCommand != NULL)
{
TerminalTxStr(pCommand->pHelpStr);
TerminalTxStr(TERMINAL_STRING_NEW_LINE);
pCommand = pCommand->pNext;
}
TerminalTxStr(TERMINAL_STRING_NEW_LINE);
return TERMINAL_ERROR_OK;
}
/*************************************************************************************************/
/*!
* \fn TerminalTx
*
* \brief Transmit buffer on platform UART.
*
* \param pBuf Buffer to transmit.
* \param len Length of buffer in octets.
*
* \return None.
*/
/*************************************************************************************************/
void TerminalTx(const uint8_t *pData, uint16_t len)
{
WSF_ASSERT(terminalCb.terminalTx);
(*terminalCb.terminalTx)(pData, len);
}
/*************************************************************************************************/
/*!
* \fn terminalCommandEchoHandler
*
* \brief Handler for a terminal command.
*
* \param argc The number of arguments passed to the command.
* \param argv The array of arguments; the 0th argument is the command.
*
* \return Error code.
*/
/*************************************************************************************************/
static uint8_t terminalCommandEchoHandler(uint32_t argc, char **argv)
{
if (argc < 2)
{
return TERMINAL_ERROR_TOO_FEW_ARGUMENTS;
}
else if (argc == 2)
{
if (strcmp(argv[1], "on") == 0)
{
terminalCb.doEcho = TRUE;
TerminalTxStr("echo on" TERMINAL_STRING_NEW_LINE);
}
else if (strcmp(argv[1], "off") == 0)
{
terminalCb.doEcho = FALSE;
TerminalTxStr("echo off" TERMINAL_STRING_NEW_LINE);
}
else
{
return TERMINAL_ERROR_BAD_ARGUMENTS;
}
}
else
{
return TERMINAL_ERROR_TOO_MANY_ARGUMENTS;
}
return TERMINAL_ERROR_OK;
}