aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorgdisirio <gdisirio@35acf78f-673a-0410-8e92-d51de3d6d3f4>2009-12-10 15:44:55 +0000
committergdisirio <gdisirio@35acf78f-673a-0410-8e92-d51de3d6d3f4>2009-12-10 15:44:55 +0000
commitdea70dbc797e0936c2532ae55881437e06e169fa (patch)
tree8b2e54295e4f0d670c0c9625ae1d4c475c6c7176
parent97b35245d19b59ff87d69fa9f7df701eea8f18c9 (diff)
downloadChibiOS-dea70dbc797e0936c2532ae55881437e06e169fa.tar.gz
ChibiOS-dea70dbc797e0936c2532ae55881437e06e169fa.tar.bz2
ChibiOS-dea70dbc797e0936c2532ae55881437e06e169fa.zip
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
-rw-r--r--demos/Win32-MinGW/Makefile3
-rw-r--r--demos/Win32-MinGW/main.c316
-rw-r--r--os/various/shell.c284
-rw-r--r--os/various/shell.h90
-rw-r--r--os/various/various.dox10
5 files changed, 480 insertions, 223 deletions
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 <http://www.gnu.org/licenses/>.
*/
-#include <string.h>
-#include <stdio.h>
-
#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 <http://www.gnu.org/licenses/>.
+*/
+
+/**
+ * @file shell.c
+ * @brief Simple CLI shell code.
+ * @addtogroup SHELL
+ * @{
+ */
+
+#include <stdio.h>
+#include <string.h>
+
+#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 <http://www.gnu.org/licenses/>.
+*/
+
+/**
+ * @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
+ */