[Feature]add MT2731_MP2_MR2_SVN388 baseline version
Change-Id: Ief04314834b31e27effab435d3ca8ba33b499059
diff --git a/src/bsp/lk/lib/console/console.c b/src/bsp/lk/lib/console/console.c
new file mode 100644
index 0000000..2f83a4f
--- /dev/null
+++ b/src/bsp/lk/lib/console/console.c
@@ -0,0 +1,899 @@
+/*
+ * Copyright (c) 2008-2009 Travis Geiselbrecht
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files
+ * (the "Software"), to deal in the Software without restriction,
+ * including without limitation the rights to use, copy, modify, merge,
+ * publish, distribute, sublicense, and/or sell copies of the Software,
+ * and to permit persons to whom the Software is furnished to do so,
+ * subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+ * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+ * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+ * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+ * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#include <debug.h>
+#include <trace.h>
+#include <assert.h>
+#include <err.h>
+#include <string.h>
+#include <ctype.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <kernel/thread.h>
+#include <kernel/mutex.h>
+#include <lib/console.h>
+#if WITH_LIB_ENV
+#include <lib/env.h>
+#endif
+
+#ifndef CONSOLE_ENABLE_HISTORY
+#define CONSOLE_ENABLE_HISTORY 1
+#endif
+
+#define LINE_LEN 128
+
+#define PANIC_LINE_LEN 32
+
+#define MAX_NUM_ARGS 16
+
+#define HISTORY_LEN 16
+
+#define LOCAL_TRACE 0
+
+#define WHITESPACE " \t"
+
+/* debug buffer */
+static char *debug_buffer;
+
+/* echo commands? */
+static bool echo = true;
+
+/* command processor state */
+static mutex_t *command_lock;
+int lastresult;
+static bool abort_script;
+
+#if CONSOLE_ENABLE_HISTORY
+/* command history stuff */
+static char *history; // HISTORY_LEN rows of LINE_LEN chars a piece
+static uint history_next;
+
+static void init_history(void);
+static void add_history(const char *line);
+static uint start_history_cursor(void);
+static const char *next_history(uint *cursor);
+static const char *prev_history(uint *cursor);
+static void dump_history(void);
+#endif
+
+/* list of installed commands */
+static cmd_block *command_list = NULL;
+
+/* a linear array of statically defined command blocks,
+ defined in the linker script.
+ */
+extern cmd_block __commands_start[];
+extern cmd_block __commands_end[];
+
+static int cmd_help(int argc, const cmd_args *argv);
+static int cmd_help_panic(int argc, const cmd_args *argv);
+static int cmd_echo(int argc, const cmd_args *argv);
+static int cmd_test(int argc, const cmd_args *argv);
+#if CONSOLE_ENABLE_HISTORY
+static int cmd_history(int argc, const cmd_args *argv);
+#endif
+
+STATIC_COMMAND_START
+STATIC_COMMAND("help", "this list", &cmd_help)
+STATIC_COMMAND_MASKED("help", "this list", &cmd_help_panic, CMD_AVAIL_PANIC)
+STATIC_COMMAND("echo", NULL, &cmd_echo)
+#if LK_DEBUGLEVEL > 1
+STATIC_COMMAND("test", "test the command processor", &cmd_test)
+#if CONSOLE_ENABLE_HISTORY
+STATIC_COMMAND("history", "command history", &cmd_history)
+#endif
+#endif
+STATIC_COMMAND_END(help);
+
+int console_init(void)
+{
+ LTRACE_ENTRY;
+
+ command_lock = calloc(sizeof(mutex_t), 1);
+ mutex_init(command_lock);
+
+ /* add all the statically defined commands to the list */
+ cmd_block *block;
+ for (block = __commands_start; block != __commands_end; block++) {
+ console_register_commands(block);
+ }
+
+#if CONSOLE_ENABLE_HISTORY
+ init_history();
+#endif
+
+ return 0;
+}
+
+#if CONSOLE_ENABLE_HISTORY
+static int cmd_history(int argc, const cmd_args *argv)
+{
+ dump_history();
+ return 0;
+}
+
+static inline char *history_line(uint line)
+{
+ return history + line * LINE_LEN;
+}
+
+static inline uint ptrnext(uint ptr)
+{
+ return (ptr + 1) % HISTORY_LEN;
+}
+
+static inline uint ptrprev(uint ptr)
+{
+ return (ptr - 1) % HISTORY_LEN;
+}
+
+static void dump_history(void)
+{
+ printf("command history:\n");
+ uint ptr = ptrprev(history_next);
+ int i;
+ for (i=0; i < HISTORY_LEN; i++) {
+ if (history_line(ptr)[0] != 0)
+ printf("\t%s\n", history_line(ptr));
+ ptr = ptrprev(ptr);
+ }
+}
+
+static void init_history(void)
+{
+ /* allocate and set up the history buffer */
+ history = calloc(1, HISTORY_LEN * LINE_LEN);
+ history_next = 0;
+}
+
+static void add_history(const char *line)
+{
+ // reject some stuff
+ if (line[0] == 0)
+ return;
+
+ uint last = ptrprev(history_next);
+ if (strcmp(line, history_line(last)) == 0)
+ return;
+
+ strlcpy(history_line(history_next), line, LINE_LEN);
+ history_next = ptrnext(history_next);
+}
+
+static uint start_history_cursor(void)
+{
+ return ptrprev(history_next);
+}
+
+static const char *next_history(uint *cursor)
+{
+ uint i = ptrnext(*cursor);
+
+ if (i == history_next)
+ return ""; // can't let the cursor hit the head
+
+ *cursor = i;
+ return history_line(i);
+}
+
+static const char *prev_history(uint *cursor)
+{
+ uint i;
+ const char *str = history_line(*cursor);
+
+ /* if we are already at head, stop here */
+ if (*cursor == history_next)
+ return str;
+
+ /* back up one */
+ i = ptrprev(*cursor);
+
+ /* if the next one is gonna be null */
+ if (history_line(i)[0] == '\0')
+ return str;
+
+ /* update the cursor */
+ *cursor = i;
+ return str;
+}
+#endif
+
+static const cmd *match_command(const char *command, const uint8_t availability_mask)
+{
+ cmd_block *block;
+ size_t i;
+
+ for (block = command_list; block != NULL; block = block->next) {
+ const cmd *curr_cmd = block->list;
+ for (i = 0; i < block->count; i++) {
+ if ((availability_mask & curr_cmd[i].availability_mask) == 0) {
+ continue;
+ }
+ if (strcmp(command, curr_cmd[i].cmd_str) == 0) {
+ return &curr_cmd[i];
+ }
+ }
+ }
+
+ return NULL;
+}
+
+static int read_debug_line(const char **outbuffer, void *cookie)
+{
+ int pos = 0;
+ int escape_level = 0;
+#if CONSOLE_ENABLE_HISTORY
+ uint history_cursor = start_history_cursor();
+#endif
+
+ char *buffer = debug_buffer;
+
+ for (;;) {
+ /* loop until we get a char */
+ int c;
+ if ((c = getchar()) < 0)
+ continue;
+
+// TRACEF("c = 0x%hhx\n", c);
+
+ if (escape_level == 0) {
+ switch (c) {
+ case '\r':
+ case '\n':
+ if (echo)
+ putchar('\n');
+ goto done;
+
+ case 0x7f: // backspace or delete
+ case 0x8:
+ if (pos > 0) {
+ pos--;
+ fputc('\b', stdout);
+ putchar(' ');
+ fputc('\b', stdout); // move to the left one
+ }
+ break;
+
+ case 0x1b: // escape
+ escape_level++;
+ break;
+
+ default:
+ buffer[pos++] = c;
+ if (echo)
+ putchar(c);
+ }
+ } else if (escape_level == 1) {
+ // inside an escape, look for '['
+ if (c == '[') {
+ escape_level++;
+ } else {
+ // we didn't get it, abort
+ escape_level = 0;
+ }
+ } else { // escape_level > 1
+ switch (c) {
+ case 67: // right arrow
+ buffer[pos++] = ' ';
+ if (echo)
+ putchar(' ');
+ break;
+ case 68: // left arrow
+ if (pos > 0) {
+ pos--;
+ if (echo) {
+ fputc('\b', stdout); // move to the left one
+ putchar(' ');
+ fputc('\b', stdout); // move to the left one
+ }
+ }
+ break;
+#if CONSOLE_ENABLE_HISTORY
+ case 65: // up arrow -- previous history
+ case 66: // down arrow -- next history
+ // wipe out the current line
+ while (pos > 0) {
+ pos--;
+ if (echo) {
+ fputc('\b', stdout); // move to the left one
+ putchar(' ');
+ fputc('\b', stdout); // move to the left one
+ }
+ }
+
+ if (c == 65)
+ strlcpy(buffer, prev_history(&history_cursor), LINE_LEN);
+ else
+ strlcpy(buffer, next_history(&history_cursor), LINE_LEN);
+ pos = strlen(buffer);
+ if (echo)
+ fputs(buffer, stdout);
+ break;
+#endif
+ default:
+ break;
+ }
+ escape_level = 0;
+ }
+
+ /* end of line. */
+ if (pos == (LINE_LEN - 1)) {
+ fputs("\nerror: line too long\n", stdout);
+ pos = 0;
+ goto done;
+ }
+ }
+
+done:
+// dprintf("returning pos %d\n", pos);
+
+ // null terminate
+ buffer[pos] = 0;
+
+#if CONSOLE_ENABLE_HISTORY
+ // add to history
+ add_history(buffer);
+#endif
+
+ // return a pointer to our buffer
+ *outbuffer = buffer;
+
+ return pos;
+}
+
+static int tokenize_command(const char *inbuffer, const char **continuebuffer, char *buffer, size_t buflen, cmd_args *args, int arg_count)
+{
+ int inpos;
+ int outpos;
+ int arg;
+ enum {
+ INITIAL = 0,
+ NEXT_FIELD,
+ SPACE,
+ IN_SPACE,
+ TOKEN,
+ IN_TOKEN,
+ QUOTED_TOKEN,
+ IN_QUOTED_TOKEN,
+ VAR,
+ IN_VAR,
+ COMMAND_SEP,
+ } state;
+ char varname[128];
+ int varnamepos;
+
+ inpos = 0;
+ outpos = 0;
+ arg = 0;
+ varnamepos = 0;
+ state = INITIAL;
+ *continuebuffer = NULL;
+
+ for (;;) {
+ char c = inbuffer[inpos];
+
+// dprintf(SPEW, "c 0x%hhx state %d arg %d inpos %d pos %d\n", c, state, arg, inpos, outpos);
+
+ switch (state) {
+ case INITIAL:
+ case NEXT_FIELD:
+ if (c == '\0')
+ goto done;
+ if (isspace(c))
+ state = SPACE;
+ else if (c == ';')
+ state = COMMAND_SEP;
+ else
+ state = TOKEN;
+ break;
+ case SPACE:
+ state = IN_SPACE;
+ break;
+ case IN_SPACE:
+ if (c == '\0')
+ goto done;
+ if (c == ';') {
+ state = COMMAND_SEP;
+ } else if (!isspace(c)) {
+ state = TOKEN;
+ } else {
+ inpos++; // consume the space
+ }
+ break;
+ case TOKEN:
+ // start of a token
+ DEBUG_ASSERT(c != '\0');
+ if (c == '"') {
+ // start of a quoted token
+ state = QUOTED_TOKEN;
+ } else if (c == '$') {
+ // start of a variable
+ state = VAR;
+ } else {
+ // regular, unquoted token
+ state = IN_TOKEN;
+ args[arg].str = &buffer[outpos];
+ }
+ break;
+ case IN_TOKEN:
+ if (c == '\0') {
+ arg++;
+ goto done;
+ }
+ if (isspace(c) || c == ';') {
+ arg++;
+ buffer[outpos] = 0;
+ outpos++;
+ /* are we out of tokens? */
+ if (arg == arg_count)
+ goto done;
+ state = NEXT_FIELD;
+ } else {
+ buffer[outpos] = c;
+ outpos++;
+ inpos++;
+ }
+ break;
+ case QUOTED_TOKEN:
+ // start of a quoted token
+ DEBUG_ASSERT(c == '"');
+
+ state = IN_QUOTED_TOKEN;
+ args[arg].str = &buffer[outpos];
+ inpos++; // consume the quote
+ break;
+ case IN_QUOTED_TOKEN:
+ if (c == '\0') {
+ arg++;
+ goto done;
+ }
+ if (c == '"') {
+ arg++;
+ buffer[outpos] = 0;
+ outpos++;
+ /* are we out of tokens? */
+ if (arg == arg_count)
+ goto done;
+
+ state = NEXT_FIELD;
+ }
+ buffer[outpos] = c;
+ outpos++;
+ inpos++;
+ break;
+ case VAR:
+ DEBUG_ASSERT(c == '$');
+
+ state = IN_VAR;
+ args[arg].str = &buffer[outpos];
+ inpos++; // consume the dollar sign
+
+ // initialize the place to store the variable name
+ varnamepos = 0;
+ break;
+ case IN_VAR:
+ if (c == '\0' || isspace(c) || c == ';') {
+ // hit the end of variable, look it up and stick it inline
+ varname[varnamepos] = 0;
+#if WITH_LIB_ENV
+ int rc = env_get(varname, &buffer[outpos], buflen - outpos);
+#else
+ (void)varname[0]; // nuke a warning
+ int rc = -1;
+#endif
+ if (rc < 0) {
+ buffer[outpos++] = '0';
+ buffer[outpos++] = 0;
+ } else {
+ outpos += strlen(&buffer[outpos]) + 1;
+ }
+ arg++;
+ /* are we out of tokens? */
+ if (arg == arg_count)
+ goto done;
+
+ state = NEXT_FIELD;
+ } else {
+ varname[varnamepos] = c;
+ varnamepos++;
+ inpos++;
+ }
+ break;
+ case COMMAND_SEP:
+ // we hit a ;, so terminate the command and pass the remainder of the command back in continuebuffer
+ DEBUG_ASSERT(c == ';');
+
+ inpos++; // consume the ';'
+ *continuebuffer = &inbuffer[inpos];
+ goto done;
+ }
+ }
+
+done:
+ buffer[outpos] = 0;
+ return arg;
+}
+
+static void convert_args(int argc, cmd_args *argv)
+{
+ int i;
+
+ for (i = 0; i < argc; i++) {
+ unsigned long u = atoul(argv[i].str);
+ argv[i].u = u;
+ argv[i].p = (void*)u;
+ argv[i].i = atol(argv[i].str);
+
+ if (!strcmp(argv[i].str, "true") || !strcmp(argv[i].str, "on")) {
+ argv[i].b = true;
+ } else if (!strcmp(argv[i].str, "false") || !strcmp(argv[i].str, "off")) {
+ argv[i].b = false;
+ } else {
+ argv[i].b = (argv[i].u == 0) ? false : true;
+ }
+ }
+}
+
+
+static status_t command_loop(int (*get_line)(const char **, void *), void *get_line_cookie, bool showprompt, bool locked)
+{
+ bool exit;
+#if WITH_LIB_ENV
+ bool report_result;
+#endif
+ cmd_args *args = NULL;
+ const char *buffer;
+ const char *continuebuffer;
+ char *outbuf = NULL;
+
+ args = (cmd_args *) malloc (MAX_NUM_ARGS * sizeof(cmd_args));
+ if (unlikely(args == NULL)) {
+ goto no_mem_error;
+ }
+
+ const size_t outbuflen = 1024;
+ outbuf = malloc(outbuflen);
+ if (unlikely(outbuf == NULL)) {
+ goto no_mem_error;
+ }
+
+ exit = false;
+ continuebuffer = NULL;
+ while (!exit) {
+ // read a new line if it hadn't been split previously and passed back from tokenize_command
+ if (continuebuffer == NULL) {
+ if (showprompt)
+ fputs("] ", stdout);
+
+ int len = get_line(&buffer, get_line_cookie);
+ if (len < 0)
+ break;
+ if (len == 0)
+ continue;
+ } else {
+ buffer = continuebuffer;
+ }
+
+// dprintf("line = '%s'\n", buffer);
+
+ /* tokenize the line */
+ int argc = tokenize_command(buffer, &continuebuffer, outbuf, outbuflen,
+ args, MAX_NUM_ARGS);
+ if (argc < 0) {
+ if (showprompt)
+ printf("syntax error\n");
+ continue;
+ } else if (argc == 0) {
+ continue;
+ }
+
+// dprintf("after tokenize: argc %d\n", argc);
+// for (int i = 0; i < argc; i++)
+// dprintf("%d: '%s'\n", i, args[i].str);
+
+ /* convert the args */
+ convert_args(argc, args);
+
+ /* try to match the command */
+ const cmd *command = match_command(args[0].str, CMD_AVAIL_NORMAL);
+ if (!command) {
+ if (showprompt)
+ printf("command not found\n");
+ continue;
+ }
+
+ if (!locked)
+ mutex_acquire(command_lock);
+
+ abort_script = false;
+ lastresult = command->cmd_callback(argc, args);
+
+#if WITH_LIB_ENV
+ bool report_result;
+ env_get_bool("reportresult", &report_result, false);
+ if (report_result) {
+ if (lastresult < 0)
+ printf("FAIL %d\n", lastresult);
+ else
+ printf("PASS %d\n", lastresult);
+ }
+#endif
+
+#if WITH_LIB_ENV
+ // stuff the result in an environment var
+ env_set_int("?", lastresult, true);
+#endif
+
+ // someone must have aborted the current script
+ if (abort_script)
+ exit = true;
+ abort_script = false;
+
+ if (!locked)
+ mutex_release(command_lock);
+ }
+
+ free(outbuf);
+ free(args);
+ return NO_ERROR;
+
+no_mem_error:
+ if (outbuf)
+ free(outbuf);
+
+ if (args)
+ free(args);
+
+ dprintf(INFO, "%s: not enough memory\n", __func__);
+ return ERR_NO_MEMORY;
+}
+
+void console_abort_script(void)
+{
+ abort_script = true;
+}
+
+void console_start(void)
+{
+ debug_buffer = malloc(LINE_LEN);
+
+ dprintf(INFO, "entering main console loop\n");
+
+
+ while (command_loop(&read_debug_line, NULL, true, false) == NO_ERROR)
+ ;
+
+ dprintf(INFO, "exiting main console loop\n");
+
+ free (debug_buffer);
+}
+
+struct line_read_struct {
+ const char *string;
+ int pos;
+ char *buffer;
+ size_t buflen;
+};
+
+static int fetch_next_line(const char **buffer, void *cookie)
+{
+ struct line_read_struct *lineread = (struct line_read_struct *)cookie;
+
+ // we're done
+ if (lineread->string[lineread->pos] == 0)
+ return -1;
+
+ size_t bufpos = 0;
+ while (lineread->string[lineread->pos] != 0) {
+ if (lineread->string[lineread->pos] == '\n') {
+ lineread->pos++;
+ break;
+ }
+ if (bufpos == (lineread->buflen - 1))
+ break;
+ lineread->buffer[bufpos] = lineread->string[lineread->pos];
+ lineread->pos++;
+ bufpos++;
+ }
+ lineread->buffer[bufpos] = 0;
+
+ *buffer = lineread->buffer;
+
+ return bufpos;
+}
+
+static int console_run_script_etc(const char *string, bool locked)
+{
+ struct line_read_struct lineread;
+
+ lineread.string = string;
+ lineread.pos = 0;
+ lineread.buffer = malloc(LINE_LEN);
+ lineread.buflen = LINE_LEN;
+
+ command_loop(&fetch_next_line, (void *)&lineread, false, locked);
+
+ free(lineread.buffer);
+
+ return lastresult;
+}
+
+int console_run_script(const char *string)
+{
+ return console_run_script_etc(string, false);
+}
+
+int console_run_script_locked(const char *string)
+{
+ return console_run_script_etc(string, true);
+}
+
+console_cmd console_get_command_handler(const char *commandstr)
+{
+ const cmd *command = match_command(commandstr, CMD_AVAIL_NORMAL);
+
+ if (command)
+ return command->cmd_callback;
+ else
+ return NULL;
+}
+
+void console_register_commands(cmd_block *block)
+{
+ DEBUG_ASSERT(block);
+ DEBUG_ASSERT(block->next == NULL);
+
+ block->next = command_list;
+ command_list = block;
+}
+
+
+static int cmd_help_impl(uint8_t availability_mask)
+{
+ printf("command list:\n");
+
+ cmd_block *block;
+ size_t i;
+
+ for (block = command_list; block != NULL; block = block->next) {
+ const cmd *curr_cmd = block->list;
+ for (i = 0; i < block->count; i++) {
+ if ((availability_mask & curr_cmd[i].availability_mask) == 0) {
+ // Skip commands that aren't available in the current shell.
+ continue;
+ }
+ if (curr_cmd[i].help_str)
+ printf("\t%-16s: %s\n", curr_cmd[i].cmd_str, curr_cmd[i].help_str);
+ }
+ }
+
+ return 0;
+}
+
+static int cmd_help(int argc, const cmd_args *argv)
+{
+ return cmd_help_impl(CMD_AVAIL_NORMAL);
+}
+
+static int cmd_help_panic(int argc, const cmd_args *argv)
+{
+ return cmd_help_impl(CMD_AVAIL_PANIC);
+}
+
+static int cmd_echo(int argc, const cmd_args *argv)
+{
+ if (argc > 1)
+ echo = argv[1].b;
+ return NO_ERROR;
+}
+
+static void read_line_panic(char* buffer, const size_t len, FILE* panic_fd)
+{
+ size_t pos = 0;
+
+ for (;;) {
+ int c;
+ if ((c = getc(panic_fd)) < 0) {
+ continue;
+ }
+
+ switch (c) {
+ case '\r':
+ case '\n':
+ fputc('\n', panic_fd);
+ goto done;
+ case 0x7f: // backspace or delete
+ case 0x8:
+ if (pos > 0) {
+ pos--;
+ fputc('\b', stdout);
+ fputc(' ', panic_fd);
+ fputc('\b', stdout); // move to the left one
+ }
+ break;
+ default:
+ buffer[pos++] = c;
+ fputc(c, panic_fd);
+ }
+ if (pos == (len - 1)) {
+ fputs("\nerror: line too long\n", panic_fd);
+ pos = 0;
+ goto done;
+ }
+ }
+done:
+ buffer[pos] = 0;
+}
+
+void panic_shell_start(void)
+{
+ dprintf(INFO, "entering panic shell loop\n");
+ char input_buffer[PANIC_LINE_LEN];
+ cmd_args args[MAX_NUM_ARGS];
+
+ // panic_fd allows us to do I/O using the polling drivers.
+ // These drivers function even if interrupts are disabled.
+ FILE _panic_fd = get_panic_fd();
+ FILE *panic_fd = &_panic_fd;
+
+ for (;;) {
+ fputs("! ", panic_fd);
+ read_line_panic(input_buffer, PANIC_LINE_LEN, panic_fd);
+
+ int argc;
+ char* tok = strtok(input_buffer, WHITESPACE);
+ for (argc = 0; argc < MAX_NUM_ARGS; argc++) {
+ if (tok == NULL) {
+ break;
+ }
+ args[argc].str = tok;
+ tok = strtok(NULL, WHITESPACE);
+ }
+
+ if (argc == 0) {
+ continue;
+ }
+
+ convert_args(argc, args);
+
+ const cmd* command = match_command(args[0].str, CMD_AVAIL_PANIC);
+ if (!command) {
+ fputs("command not found\n", panic_fd);
+ continue;
+ }
+
+ command->cmd_callback(argc, args);
+ }
+}
+
+#if LK_DEBUGLEVEL > 1
+static int cmd_test(int argc, const cmd_args *argv)
+{
+ int i;
+
+ printf("argc %d, argv %p\n", argc, argv);
+ for (i = 0; i < argc; i++)
+ printf("\t%d: str '%s', i %ld, u %#lx, b %d\n", i, argv[i].str, argv[i].i, argv[i].u, argv[i].b);
+
+ return 0;
+}
+#endif
diff --git a/src/bsp/lk/lib/console/console.ld b/src/bsp/lk/lib/console/console.ld
new file mode 100644
index 0000000..f657cc1
--- /dev/null
+++ b/src/bsp/lk/lib/console/console.ld
@@ -0,0 +1,8 @@
+SECTIONS {
+ .commands : {
+ __commands_start = .;
+ KEEP (*(.commands))
+ __commands_end = .;
+ }
+}
+INSERT AFTER .data;
diff --git a/src/bsp/lk/lib/console/rules.mk b/src/bsp/lk/lib/console/rules.mk
new file mode 100644
index 0000000..d4b93c0
--- /dev/null
+++ b/src/bsp/lk/lib/console/rules.mk
@@ -0,0 +1,10 @@
+LOCAL_DIR := $(GET_LOCAL_DIR)
+
+MODULE := $(LOCAL_DIR)
+
+MODULE_SRCS += \
+ $(LOCAL_DIR)/console.c
+
+EXTRA_LINKER_SCRIPTS += $(LOCAL_DIR)/console.ld
+
+include make/module.mk