From dea70dbc797e0936c2532ae55881437e06e169fa Mon Sep 17 00:00:00 2001 From: gdisirio Date: Thu, 10 Dec 2009 15:44:55 +0000 Subject: Added a small generic command line shell. git-svn-id: svn://svn.code.sf.net/p/chibios/svn/trunk@1411 35acf78f-673a-0410-8e92-d51de3d6d3f4 --- demos/Win32-MinGW/Makefile | 3 +- demos/Win32-MinGW/main.c | 316 ++++++++++++++------------------------------- os/various/shell.c | 284 ++++++++++++++++++++++++++++++++++++++++ os/various/shell.h | 90 +++++++++++++ os/various/various.dox | 10 ++ 5 files changed, 480 insertions(+), 223 deletions(-) create mode 100644 os/various/shell.c create mode 100644 os/various/shell.h diff --git a/demos/Win32-MinGW/Makefile b/demos/Win32-MinGW/Makefile index e690b7804..76362dec1 100644 --- a/demos/Win32-MinGW/Makefile +++ b/demos/Win32-MinGW/Makefile @@ -21,7 +21,7 @@ CC = $(TRGT)gcc AS = $(TRGT)gcc -x assembler-with-cpp # List all default C defines here, like -D_DEBUG=1 -DDEFS = +DDEFS = -DSHELL_USE_IPRINTF=FALSE # List all default ASM defines here, like -D_DEBUG=1 DADEFS = @@ -69,6 +69,7 @@ SRC = ${PORTSRC} \ ${TESTSRC} \ ${HALSRC} \ ${PLATFORMSRC} \ + ${CHIBIOS}/os/various/shell.c \ main.c # List ASM source files here diff --git a/demos/Win32-MinGW/main.c b/demos/Win32-MinGW/main.c index 2b0c5ea15..c2ead083e 100644 --- a/demos/Win32-MinGW/main.c +++ b/demos/Win32-MinGW/main.c @@ -17,53 +17,36 @@ along with this program. If not, see . */ -#include -#include - #include "ch.h" #include "hal.h" +#include "test.h" +#include "shell.h" -static uint32_t wdguard; -static WORKING_AREA(wdarea, 2048); - -static uint32_t cdguard; -static WORKING_AREA(cdarea, 2048); -static Thread *cdtp; - -static msg_t WatchdogThread(void *arg); -static msg_t ConsoleThread(void *arg); - -msg_t TestThread(void *p); +#define SHELL_WA_SIZE THD_WA_SIZE(4096) +#define CONSOLE_WA_SIZE THD_WA_SIZE(4096) #define cprint(msg) chMsgSend(cdtp, (msg_t)msg) -/* - * Watchdog thread, it checks magic values located under the various stack - * areas. The system is halted if something is wrong. - */ -static msg_t WatchdogThread(void *arg) { +static Thread *cdtp; +static Thread *shelltp1; +static Thread *shelltp2; - (void)arg; - wdguard = 0xA51F2E3D; - cdguard = 0xA51F2E3D; - while (TRUE) { - if ((wdguard != 0xA51F2E3D) || - (cdguard != 0xA51F2E3D)) { - printf("Halted by watchdog"); - fflush(stdout); - chSysHalt(); - } - chThdSleep(50); - } - return 0; -} +static const ShellConfig shell_cfg1 = { + (BaseChannel *)&SD1, + NULL +}; + +static const ShellConfig shell_cfg2 = { + (BaseChannel *)&SD2, + NULL +}; /* * Console print server done using synchronous messages. This makes the access * to the C printf() thread safe and the print operation atomic among threads. * In this example the message is the zero termitated string itself. */ -static msg_t ConsoleThread(void *arg) { +static msg_t console_thread(void *arg) { (void)arg; while (!chThdShouldTerminate()) { @@ -74,221 +57,87 @@ static msg_t ConsoleThread(void *arg) { return 0; } -static void PrintLineSD(SerialDriver *sd, char *msg) { - - while (*msg) - sdPut(sd, *msg++); -} - -static bool_t GetLineFDD(SerialDriver *sd, char *line, int size) { - char *p = line; - - while (TRUE) { - short c = chIQGet(&sd->d2.iqueue); - if (c < 0) - return TRUE; - if (c == 4) { - PrintLineSD(sd, "^D\r\n"); - return TRUE; - } - if (c == 8) { - if (p != line) { - sdPut(sd, (uint8_t)c); - sdPut(sd, 0x20); - sdPut(sd, (uint8_t)c); - p--; - } - continue; - } - if (c == '\r') { - PrintLineSD(sd, "\r\n"); - *p = 0; - return FALSE; - } - if (c < 0x20) - continue; - if (p < line + size - 1) { - sdPut(sd, (uint8_t)c); - *p++ = (uint8_t)c; - } - } -} - -/* - * Example thread, not much to see here. It simulates the CTRL-C but there - * are no real signals involved. +/** + * @brief Shell termination handler. + * + * @param[in] id event id. */ -static msg_t HelloWorldThread(void *arg) { - int i; - short c; - SerialDriver *sd = (SerialDriver *)arg; - - for (i = 0; i < 10; i++) { +static void termination_handler(eventid_t id) { - PrintLineSD(sd, "Hello World\r\n"); - c = sdGetTimeout(sd, 333); - switch (c) { - case Q_TIMEOUT: - continue; - case Q_RESET: - return 1; - case 3: - PrintLineSD(sd, "^C\r\n"); - return 0; - default: - chThdSleep(333); - } + (void)id; + if (shelltp1 && chThdTerminated(shelltp1)) { + chThdWait(shelltp1); + shelltp1 = NULL; + cprint("Init: shell on SD1 terminated\n"); + chSysLock(); + chOQResetI(&SD1.d2.oqueue); + chSysUnlock(); } - return 0; -} - -static bool_t checkend(SerialDriver *sd) { - - char * lp = strtok(NULL, " \009"); /* It is not thread safe but this is a demo.*/ - if (lp) { - PrintLineSD(sd, lp); - PrintLineSD(sd, " ?\r\n"); - return TRUE; + if (shelltp2 && chThdTerminated(shelltp2)) { + chThdWait(shelltp2); + shelltp2 = NULL; + cprint("Init: shell on SD2 terminated\n"); + chSysLock(); + chOQResetI(&SD2.d2.oqueue); + chSysUnlock(); } - return FALSE; } -/* - * Simple command shell thread, the argument is the serial line for the - * standard input and output. It recognizes few simple commands. +/** + * @brief SD1 status change handler. + * + * @param[in] id event id. */ -static msg_t ShellThread(void *arg) { - SerialDriver *sd = (SerialDriver *)arg; - char *lp, line[64]; - Thread *tp; - WORKING_AREA(tarea, 2048); - - chSysLock(); - chIQResetI(&sd->d2.iqueue); - chOQResetI(&sd->d2.oqueue); - chSysUnlock(); - PrintLineSD(sd, "ChibiOS/RT Command Shell\r\n\n"); - while (TRUE) { - PrintLineSD(sd, "ch> "); - if (GetLineFDD(sd, line, sizeof(line))) { - PrintLineSD(sd, "\nlogout"); - break; - } - lp = strtok(line, " \009"); // Note: not thread safe but it is just a demo. - if (lp) { - if ((stricmp(lp, "help") == 0) || - (stricmp(lp, "h") == 0) || - (stricmp(lp, "?") == 0)) { - if (checkend(sd)) - continue; - PrintLineSD(sd, "Commands:\r\n"); - PrintLineSD(sd, " help,h,? - This help\r\n"); - PrintLineSD(sd, " exit - Logout from ChibiOS/RT\r\n"); - PrintLineSD(sd, " time - Prints the system timer value\r\n"); - PrintLineSD(sd, " hello - Runs the Hello World demo thread\r\n"); - PrintLineSD(sd, " test - Runs the System Test thread\r\n"); - } - else if (stricmp(lp, "exit") == 0) { - if (checkend(sd)) - continue; - PrintLineSD(sd, "\nlogout"); - break; - } - else if (stricmp(lp, "time") == 0) { - if (checkend(sd)) - continue; - sprintf(line, "Time: %d\r\n", chTimeNow()); - PrintLineSD(sd, line); - } - else if (stricmp(lp, "hello") == 0) { - if (checkend(sd)) - continue; - tp = chThdCreateStatic(tarea, sizeof(tarea), - NORMALPRIO, HelloWorldThread, sd); - if (chThdWait(tp)) - break; // Lost connection while executing the hello thread. - } - else if (stricmp(lp, "test") == 0) { - if (checkend(sd)) - continue; - tp = chThdCreateStatic(tarea, sizeof(tarea), - NORMALPRIO, TestThread, arg); - if (chThdWait(tp)) - break; // Lost connection while executing the hello thread. - } - else { - PrintLineSD(sd, lp); - PrintLineSD(sd, " ?\r\n"); - } - } - } - return 0; -} - -static WORKING_AREA(s1area, 4096); -static Thread *s1; -EventListener s1tel; - -static void COM1Handler(eventid_t id) { +static void sd1_handler(eventid_t id) { sdflags_t flags; (void)id; - if (s1 && chThdTerminated(s1)) { - s1 = NULL; - cprint("Init: disconnection on SD1\n"); - } flags = sdGetAndClearFlags(&SD1); - if ((flags & SD_CONNECTED) && (s1 == NULL)) { + if ((flags & SD_CONNECTED) && (shelltp1 == NULL)) { cprint("Init: connection on SD1\n"); - s1 = chThdInit(s1area, sizeof(s1area), - NORMALPRIO, ShellThread, &SD1); - chEvtRegister(chThdGetExitEventSource(s1), &s1tel, 0); - chThdResume(s1); + shelltp1 = shellCreate(&shell_cfg1, SHELL_WA_SIZE, NORMALPRIO + 1); } - if ((flags & SD_DISCONNECTED) && (s1 != NULL)) { + if (flags & SD_DISCONNECTED) { + cprint("Init: disconnection on SD1\n"); chSysLock(); chIQResetI(&SD1.d2.iqueue); chSysUnlock(); } } -static WORKING_AREA(s2area, 4096); -static Thread *s2; -EventListener s2tel; - -static void COM2Handler(eventid_t id) { +/** + * @brief SD2 status change handler. + * + * @param[in] id event id. + */ +static void sd2_handler(eventid_t id) { sdflags_t flags; (void)id; - if (s2 && chThdTerminated(s2)) { - s2 = NULL; - cprint("Init: disconnection on SD2\n"); - } flags = sdGetAndClearFlags(&SD2); - if ((flags & SD_CONNECTED) && (s2 == NULL)) { + if ((flags & SD_CONNECTED) && (shelltp2 == NULL)) { cprint("Init: connection on SD2\n"); - s2 = chThdInit(s2area, sizeof(s1area), - NORMALPRIO, ShellThread, &SD2); - chEvtRegister(chThdGetExitEventSource(s2), &s2tel, 1); - chThdResume(s2); + shelltp2 = shellCreate(&shell_cfg2, SHELL_WA_SIZE, NORMALPRIO + 10); } - if ((flags & SD_DISCONNECTED) && (s2 != NULL)) { + if (flags & SD_DISCONNECTED) { + cprint("Init: disconnection on SD2\n"); chSysLock(); chIQResetI(&SD2.d2.iqueue); chSysUnlock(); } } -static evhandler_t fhandlers[2] = { - COM1Handler, - COM2Handler +static evhandler_t fhandlers[] = { + termination_handler, + sd1_handler, + sd2_handler }; /*------------------------------------------------------------------------* - * Simulator main, start here your threads, examples inside. * + * Simulator main. * *------------------------------------------------------------------------*/ int main(void) { - EventListener c1fel, c2fel; + EventListener sd1fel, sd2fel, tel; /* * HAL initialization. @@ -300,22 +149,45 @@ int main(void) { */ chSysInit(); + /* + * Serial ports (simulated) initialization. + */ sdStart(&SD1, NULL); sdStart(&SD2, NULL); - chThdCreateStatic(wdarea, sizeof(wdarea), NORMALPRIO + 2, WatchdogThread, NULL); - cdtp = chThdCreateStatic(cdarea, sizeof(cdarea), NORMALPRIO + 1, ConsoleThread, NULL); + /* + * Shell manager initialization. + */ + shellInit(); + chEvtRegister(&shell_terminated, &tel, 0); + + /* + * Console thread started. + */ + cdtp = chThdCreateFromHeap(NULL, CONSOLE_WA_SIZE, NORMALPRIO + 1, + console_thread, NULL); - cprint("Console service started on SD1, SD2\n"); + /* + * Initializing connection/disconnection events. + */ + cprint("Shell service started on SD1, SD2\n"); cprint(" - Listening for connections on SD1\n"); - sdGetAndClearFlags(&SD1); - chEvtRegister(&SD1.d2.sevent, &c1fel, 0); + (void) sdGetAndClearFlags(&SD1); + chEvtRegister(&SD1.d2.sevent, &sd1fel, 1); cprint(" - Listening for connections on SD2\n"); - sdGetAndClearFlags(&SD2); - chEvtRegister(&SD2.d2.sevent, &c2fel, 1); + (void) sdGetAndClearFlags(&SD2); + chEvtRegister(&SD2.d2.sevent, &sd2fel, 2); + + /* + * Events servicing loop. + */ while (!chThdShouldTerminate()) chEvtDispatch(fhandlers, chEvtWaitOne(ALL_EVENTS)); - chEvtUnregister(&SD1.d2.sevent, &c2fel); // Never invoked but this is an example... - chEvtUnregister(&SD2.d2.sevent, &c1fel); // Never invoked but this is an example... + + /* + * Clean simulator exit. + */ + chEvtUnregister(&SD1.d2.sevent, &sd1fel); + chEvtUnregister(&SD2.d2.sevent, &sd2fel); return 0; } diff --git a/os/various/shell.c b/os/various/shell.c new file mode 100644 index 000000000..8ba101d01 --- /dev/null +++ b/os/various/shell.c @@ -0,0 +1,284 @@ +/* + ChibiOS/RT - Copyright (C) 2006-2007 Giovanni Di Sirio. + + This file is part of ChibiOS/RT. + + ChibiOS/RT is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + ChibiOS/RT is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +/** + * @file shell.c + * @brief Simple CLI shell code. + * @addtogroup SHELL + * @{ + */ + +#include +#include + +#include "ch.h" +#include "shell.h" + +#if SHELL_USE_IPRINTF +#define sprintf siprintf +#endif + +/** + * @brief Shell termination event source. + */ +EventSource shell_terminated; + +/* + * MinGW does not seem to have this function... + */ +static char *strtok_r(char *str, const char *delim, char **saveptr) { + char *token; + if (str) + *saveptr = str; + token = *saveptr; + + if (!token) + return NULL; + + token += strspn(token, delim); + *saveptr = strpbrk(token, delim); + if (*saveptr) + *(*saveptr)++ = '\0'; + + return *token ? token : NULL; +} + +static void usage(BaseChannel *chp, char *p) { + + shellPrint(chp, "Usage: "); + shellPrintLine(chp, p); +} + +static void list_commands(BaseChannel *chp, const ShellCommand *scp) { + + while (scp->sc_name != NULL) { + shellPrint(chp, scp->sc_name); + shellPrint(chp, " "); + scp++; + } +} + +static void cmd_info(BaseChannel *chp, int argc, char *argv[]) { + + (void)argv; + if (argc > 0) { + usage(chp, "info"); + return; + } + + shellPrint(chp, "Kernel version "); + shellPrintLine(chp, CH_KERNEL_VERSION); + shellPrint(chp, "Architecture "); + shellPrintLine(chp, CH_ARCHITECTURE_NAME); +#ifdef __GNUC__ + shellPrint(chp, "GCC Version "); + shellPrintLine(chp, __VERSION__); +#endif +} + +static void cmd_systime(BaseChannel *chp, int argc, char *argv[]) { + char buf[12]; + + (void)argv; + if (argc > 0) { + usage(chp, "systime"); + return; + } + sprintf(buf, "%lu", (unsigned long)chTimeNow()); + shellPrintLine(chp, buf); +} + +/** + * @brief Array of the default commands. + */ +static ShellCommand local_commands[] = { + {"info", cmd_info}, + {"systime", cmd_systime}, + {NULL, NULL} +}; + +static bool_t cmdexec(const ShellCommand *scp, BaseChannel *chp, + char *name, int argc, char *argv[]) { + + while (scp->sc_name != NULL) { + if (strcmpi(scp->sc_name, name) == 0) { + scp->sc_function(chp, argc, argv); + return FALSE; + } + scp++; + } + return TRUE; +} + +/** + * @brief Shell thread function. + * + * @param[in] p pointer to an @p BaseChannel object + * @return Termination reason. + * @retval RDY_OK terminated by command. + * @retval RDY_RESET terminated by reset condition on the I/O channel. + */ +static msg_t shell_thread(void *p) { + int n; + msg_t msg = RDY_OK; + BaseChannel *chp = ((ShellConfig *)p)->sc_channel; + const ShellCommand *scp = ((ShellConfig *)p)->sc_commands; + char *lp, *cmd, *tokp, line[SHELL_MAX_LINE_LENGTH]; + char *args[SHELL_MAX_ARGUMENTS + 1]; + + shellPrintLine(chp, ""); + shellPrintLine(chp, "ChibiOS/RT Shell"); + while (TRUE) { + shellPrint(chp, "ch> "); + if (shellGetLine(chp, line, sizeof(line))) { + shellPrint(chp, "\nlogout"); + break; + } + lp = strtok_r(line, " \009", &tokp); + cmd = lp; + n = 0; + while ((lp = strtok_r(NULL, " \009", &tokp)) != NULL) { + if (n >= SHELL_MAX_ARGUMENTS) { + shellPrintLine(chp, "too many arguments"); + cmd = NULL; + break; + } + args[n++] = lp; + } + args[n] = NULL; + if (cmd != NULL) { + if (strcmpi(cmd, "exit") == 0) { + if (n > 0) + usage(chp, "exit"); + break; + } + else if (strcmpi(cmd, "help") == 0) { + if (n > 0) + usage(chp, "help"); + shellPrint(chp, "Commands: help exit "); + list_commands(chp, local_commands); + if (scp != NULL) + list_commands(chp, scp); + shellPrintLine(chp, ""); + } + else if (cmdexec(local_commands, chp, cmd, n, args) && + ((scp == NULL) || cmdexec(scp, chp, cmd, n, args))) { + shellPrint(chp, cmd); + shellPrintLine(chp, " ?"); + } + } + } + chSysLock(); + chEvtBroadcastI(&shell_terminated); + return msg; +} + +/** + * @brief Shell manager initialization. + */ +void shellInit(void) { + + chEvtInit(&shell_terminated); +} + +/** + * @brief Spawns a new shell. + * + * @param[in] chp pointer to an @p BaseChannel object + * @param[in] size size of the shell working area to be allocated + * @param[in] prio the priority level for the new shell + * + * @return A pointer to the shell thread. + * @retval NULL thread creation failed because memory allocation. + */ +Thread *shellCreate(const ShellConfig *scp, size_t size, tprio_t prio) { + + return chThdCreateFromHeap(NULL, size, prio, shell_thread, (void *)scp); +} + +/** + * @brief Prints a string. + * + * @param[in] chp pointer to an @p BaseChannel object + * @param[in] msg pointer to the string + */ +void shellPrint(BaseChannel *chp, const char *msg) { + + while (*msg) + chIOPut(chp, *msg++); +} + +/** + * @brief Prints a string with a final newline. + * + * @param[in] chp pointer to an @p BaseChannel object + * @param[in] msg pointer to the string + */ +void shellPrintLine(BaseChannel *chp, const char *msg) { + + shellPrint(chp, msg); + shellPrint(chp, "\r\n"); +} + +/** + * @brief Reads a whole line from the input channel. + * + * @param[in] chp pointer to an @p BaseChannel object + * @param[in] line pointer to the line buffer + * @param[in] size buffer maximum length + * + * @return The operation status. + * @retval TRUE the channel was reset or CTRL-D pressed. + * @retval FALSE operation successful. + */ +bool_t shellGetLine(BaseChannel *chp, char *line, unsigned size) { + char *p = line; + + while (TRUE) { + short c = (short)chIOGet(chp); + if (c < 0) + return TRUE; + if (c == 4) { + shellPrintLine(chp, "^D"); + return TRUE; + } + if (c == 8) { + if (p != line) { + chIOPut(chp, (uint8_t)c); + chIOPut(chp, 0x20); + chIOPut(chp, (uint8_t)c); + p--; + } + continue; + } + if (c == '\r') { + shellPrintLine(chp, ""); + *p = 0; + return FALSE; + } + if (c < 0x20) + continue; + if (p < line + size - 1) { + chIOPut(chp, (uint8_t)c); + *p++ = (char)c; + } + } +} + +/** @} */ diff --git a/os/various/shell.h b/os/various/shell.h new file mode 100644 index 000000000..d96acc4c4 --- /dev/null +++ b/os/various/shell.h @@ -0,0 +1,90 @@ +/* + ChibiOS/RT - Copyright (C) 2006-2007 Giovanni Di Sirio. + + This file is part of ChibiOS/RT. + + ChibiOS/RT is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + ChibiOS/RT is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +/** + * @file shell.h + * @brief Simple CLI shell header. + * @addtogroup SHELL + * @{ + */ + +#ifndef _SHELL_H_ +#define _SHELL_H_ + +/** + * @brief Shell maximum input line length. + */ +#if !defined(SHELL_MAX_LINE_LENGTH) || defined(__DOXYGEN__) +#define SHELL_MAX_LINE_LENGTH 64 +#endif + +/** + * @brief Shell maximum input line length. + */ +#if !defined(SHELL_MAX_ARGUMENTS) || defined(__DOXYGEN__) +#define SHELL_MAX_ARGUMENTS 4 +#endif + +/** + * @brief Enforces the use of iprintf() on newlib. + */ +#if !defined(SHELL_USE_IPRINTF) || defined(__DOXYGEN__) +#define SHELL_USE_IPRINTF TRUE +#endif + +/** + * @brief Command handler function type. + */ +typedef void (*shellcmd_t)(BaseChannel *chp, int argc, char *argv[]); + +/** + * @brief Custom command entry type. + */ +typedef struct { + const char *sc_name; /**< @brief Command name. */ + shellcmd_t sc_function; /**< @brief Command function. */ +} ShellCommand; + +/** + * @brief Shell descriptor type. + */ +typedef struct { + BaseChannel *sc_channel; /**< @brief I/O channel associated + to the shell. */ + const ShellCommand *sc_commands; /**< @brief Shell extra commands + table. */ +} ShellConfig; + +extern EventSource shell_terminated; + +#ifdef __cplusplus +extern "C" { +#endif + void shellInit(void); + Thread *shellCreate(const ShellConfig *scp, size_t size, tprio_t prio); + void shellPrint(BaseChannel *chp, const char *msg); + void shellPrintLine(BaseChannel *chp, const char *msg); + bool_t shellGetLine(BaseChannel *chp, char *line, unsigned size); +#ifdef __cplusplus +} +#endif + +#endif /* _SHELL_H_ */ + +/** @} */ diff --git a/os/various/various.dox b/os/various/various.dox index fae5e65e5..10dfe874f 100644 --- a/os/various/various.dox +++ b/os/various/various.dox @@ -47,3 +47,13 @@ * * @ingroup various */ + +/** + * @defgroup SHELL Command Shell + * @brief Small extendible command line shell. + * @details This module implements a generic extendible command line interface. + * The CLI just requires an I/O channel (@p BaseChannel), more commands can be + * added to the shell using the configuration structure. + * + * @ingroup various + */ -- cgit v1.2.3