/* * (C) Copyright 2000 * Wolfgang Denk, DENX Software Engineering, wd@denx.de. * * Add to readline cmdline-editing by * (C) Copyright 2005 * JinHua Luo, GuangDong Linux Center, * * See file CREDITS for list of people who contributed to this * project. * * This program 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 2 of * the License, or (at your option) any later version. * * This program 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, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, * MA 02111-1307 USA */ /* #define DEBUG */ #include "ubootcli.h" /* * Board-specific Platform code can reimplement show_boot_progress () if needed */ void inline __show_boot_progress (int val) {} void show_boot_progress (int val) __attribute__((weak, alias("__show_boot_progress"))); #define MAX_DELAY_STOP_STR 32 #define DEBUG_PARSER 0 /* set to 1 to debug */ #define debug_parser(fmt, args...) \ debug_cond(DEBUG_PARSER, fmt, ##args) #ifndef DEBUG_BOOTKEYS #define DEBUG_BOOTKEYS 0 #endif #define debug_bootkeys(fmt, args...) \ debug_cond(DEBUG_BOOTKEYS, fmt, ##args) char console_buffer[CONFIG_SYS_CBSIZE + 1]; /* console I/O buffer */ #ifdef CONFIG_CMDLINE static char * delete_char (char *buffer, char *p, int *colp, int *np, int plen); #endif static const char erase_seq[] = "\b \b"; /* erase sequence */ static const char tab_seq[] = " "; /* used to expand TABs */ #ifdef CONFIG_BOOT_RETRY_TIME static uint64_t endtime = 0; /* must be set, default is instant timeout */ static int retry_time = -1; /* -1 so can call readline before main_loop */ #endif #define endtick(seconds) (get_ticks() + (uint64_t)(seconds) * get_tbclk()) #ifndef CONFIG_BOOT_RETRY_MIN #define CONFIG_BOOT_RETRY_MIN CONFIG_BOOT_RETRY_TIME #endif #ifdef CONFIG_MODEM_SUPPORT int do_mdm_init = 0; extern void mdm_init(void); /* defined in board.c */ #endif void main_loop(void) { int len, flag, rc = 0; char lastcommand[128]; /* * Main Loop for Monitor Command Processing */ for (;;) { len = ubreadline (CONFIG_SYS_PROMPT); flag = 0; /* assume no special flags for now */ if (len > 0) strcpy (lastcommand, console_buffer); else if (len == 0) flag |= CMD_FLAG_REPEAT; if (len == -1) printf ("\n"); else rc = run_command(lastcommand, flag); if (rc <= 0) { /* invalid command or not repeatable, forget it */ lastcommand[0] = 0; } } } #ifdef CONFIG_CMDLINE_EDITING /* * cmdline-editing related codes from vivi. * Author: Janghoon Lyu */ #define putnstr(str,n) do { \ printf ("%.*s", (int)n, str); \ } while (0) #define CTL_CH(c) ((c) - 'a' + 1) #define CTL_BACKSPACE ('\b') #define DEL ((char)255) #define DEL7 ((char)127) #define CREAD_HIST_CHAR ('!') #define getcmd_putch(ch) serial_putchar(ch) #define getcmd_getch() serial_getchar() #define getcmd_cbeep() getcmd_putch('\a') #define HIST_MAX 20 #define HIST_SIZE CONFIG_SYS_CBSIZE static int hist_max; static int hist_add_idx; static int hist_cur = -1; static unsigned hist_num; static char *hist_list[HIST_MAX]; static char hist_lines[HIST_MAX][HIST_SIZE + 1]; /* Save room for NULL */ #define add_idx_minus_one() ((hist_add_idx == 0) ? hist_max : hist_add_idx-1) static void hist_init(void) { int i; hist_max = 0; hist_add_idx = 0; hist_cur = -1; hist_num = 0; for (i = 0; i < HIST_MAX; i++) { hist_list[i] = hist_lines[i]; hist_list[i][0] = '\0'; } } static void cread_add_to_hist(char *line) { strcpy(hist_list[hist_add_idx], line); if (++hist_add_idx >= HIST_MAX) hist_add_idx = 0; if (hist_add_idx > hist_max) hist_max = hist_add_idx; hist_num++; } static char* hist_prev(void) { char *ret; int old_cur; if (hist_cur < 0) return NULL; old_cur = hist_cur; if (--hist_cur < 0) hist_cur = hist_max; if (hist_cur == hist_add_idx) { hist_cur = old_cur; ret = NULL; } else ret = hist_list[hist_cur]; return (ret); } static char* hist_next(void) { char *ret; if (hist_cur < 0) return NULL; if (hist_cur == hist_add_idx) return NULL; if (++hist_cur > hist_max) hist_cur = 0; if (hist_cur == hist_add_idx) { ret = ""; } else ret = hist_list[hist_cur]; return (ret); } #ifndef CONFIG_CMDLINE_EDITING static void cread_print_hist_list(void) { int i; unsigned long n; n = hist_num - hist_max; i = hist_add_idx + 1; while (1) { if (i > hist_max) i = 0; if (i == hist_add_idx) break; printf("%s\n", hist_list[i]); n++; i++; } } #endif /* CONFIG_CMDLINE_EDITING */ #define BEGINNING_OF_LINE() { \ while (num) { \ getcmd_putch(CTL_BACKSPACE); \ num--; \ } \ } #define ERASE_TO_EOL() { \ if (num < eol_num) { \ printf("%*s", (int)(eol_num - num), ""); \ do { \ getcmd_putch(CTL_BACKSPACE); \ } while (--eol_num > num); \ } \ } #define REFRESH_TO_EOL() { \ if (num < eol_num) { \ wlen = eol_num - num; \ putnstr(buf + num, wlen); \ num = eol_num; \ } \ } static void cread_add_char(char ichar, int insert, unsigned long *num, unsigned long *eol_num, char *buf, unsigned long len) { unsigned long wlen; /* room ??? */ if (insert || *num == *eol_num) { if (*eol_num > len - 1) { getcmd_cbeep(); return; } (*eol_num)++; } if (insert) { wlen = *eol_num - *num; if (wlen > 1) { memmove(&buf[*num+1], &buf[*num], wlen-1); } buf[*num] = ichar; putnstr(buf + *num, wlen); (*num)++; while (--wlen) { getcmd_putch(CTL_BACKSPACE); } } else { /* echo the character */ wlen = 1; buf[*num] = ichar; putnstr(buf + *num, wlen); (*num)++; } } static void cread_add_str(char *str, int strsize, int insert, unsigned long *num, unsigned long *eol_num, char *buf, unsigned long len) { while (strsize--) { cread_add_char(*str, insert, num, eol_num, buf, len); str++; } } static int cread_line(const char *const prompt, char *buf, unsigned int *len, int timeout) { unsigned long num = 0; unsigned long eol_num = 0; unsigned long wlen; char ichar; int insert = 1; int esc_len = 0; char esc_save[8]; int init_len = strlen(buf); //int first = 1; if (init_len) cread_add_str(buf, init_len, 1, &num, &eol_num, buf, *len); while (1) { #if 0 #ifdef CONFIG_BOOT_RETRY_TIME while (!tstc()) { /* while no incoming data */ if (retry_time >= 0 && get_ticks() > endtime) return (-2); /* timed out */ // WATCHDOG_RESET(); } #endif if (first && timeout) { uint64_t etime = endtick(timeout); while (!tstc()) { /* while no incoming data */ if (get_ticks() >= etime) return -2; /* timed out */ WATCHDOG_RESET(); } first = 0; } #endif ichar = getcmd_getch(); if ((ichar == '\n') || (ichar == '\r')) { serial_putchar('\n'); break; } /* * handle standard linux xterm esc sequences for arrow key, etc. */ if (esc_len != 0) { if (esc_len == 1) { if (ichar == '[') { esc_save[esc_len] = ichar; esc_len = 2; } else { cread_add_str(esc_save, esc_len, insert, &num, &eol_num, buf, *len); esc_len = 0; } continue; } switch (ichar) { case 'D': /* <- key */ ichar = CTL_CH('b'); esc_len = 0; break; case 'C': /* -> key */ ichar = CTL_CH('f'); esc_len = 0; break; /* pass off to ^F handler */ case 'H': /* Home key */ ichar = CTL_CH('a'); esc_len = 0; break; /* pass off to ^A handler */ case 'A': /* up arrow */ ichar = CTL_CH('p'); esc_len = 0; break; /* pass off to ^P handler */ case 'B': /* down arrow */ ichar = CTL_CH('n'); esc_len = 0; break; /* pass off to ^N handler */ default: esc_save[esc_len++] = ichar; cread_add_str(esc_save, esc_len, insert, &num, &eol_num, buf, *len); esc_len = 0; continue; } } switch (ichar) { case 0x1b: if (esc_len == 0) { esc_save[esc_len] = ichar; esc_len = 1; } else { printf("impossible condition #876\n"); esc_len = 0; } break; case CTL_CH('a'): BEGINNING_OF_LINE(); break; case CTL_CH('c'): /* ^C - break */ *buf = '\0'; /* discard input */ return (-1); case CTL_CH('f'): if (num < eol_num) { getcmd_putch(buf[num]); num++; } break; case CTL_CH('b'): if (num) { getcmd_putch(CTL_BACKSPACE); num--; } break; case CTL_CH('d'): if (num < eol_num) { wlen = eol_num - num - 1; if (wlen) { memmove(&buf[num], &buf[num+1], wlen); putnstr(buf + num, wlen); } getcmd_putch(' '); do { getcmd_putch(CTL_BACKSPACE); } while (wlen--); eol_num--; } break; case CTL_CH('k'): ERASE_TO_EOL(); break; case CTL_CH('e'): REFRESH_TO_EOL(); break; case CTL_CH('o'): insert = !insert; break; case CTL_CH('x'): case CTL_CH('u'): BEGINNING_OF_LINE(); ERASE_TO_EOL(); break; case DEL: case DEL7: case 8: if (num) { wlen = eol_num - num; num--; memmove(&buf[num], &buf[num+1], wlen); getcmd_putch(CTL_BACKSPACE); putnstr(buf + num, wlen); getcmd_putch(' '); do { getcmd_putch(CTL_BACKSPACE); } while (wlen--); eol_num--; } break; case CTL_CH('p'): case CTL_CH('n'): { char * hline; esc_len = 0; if (ichar == CTL_CH('p')) hline = hist_prev(); else hline = hist_next(); if (!hline) { getcmd_cbeep(); continue; } /* nuke the current line */ /* first, go home */ BEGINNING_OF_LINE(); /* erase to end of line */ ERASE_TO_EOL(); /* copy new line into place and display */ strcpy(buf, hline); eol_num = strlen(buf); REFRESH_TO_EOL(); continue; } #ifdef CONFIG_AUTO_COMPLETE case '\t': { int num2, col; /* do not autocomplete when in the middle */ if (num < eol_num) { getcmd_cbeep(); break; } buf[num] = '\0'; col = strlen(prompt) + eol_num; num2 = num; if (cmd_auto_complete(prompt, buf, &num2, &col)) { col = num2 - num; num += col; eol_num += col; } break; } #endif default: cread_add_char(ichar, insert, &num, &eol_num, buf, *len); break; } } *len = eol_num; buf[eol_num] = '\0'; /* lose the newline */ if (buf[0] && buf[0] != CREAD_HIST_CHAR) cread_add_to_hist(buf); hist_cur = hist_add_idx; return 0; } #endif /* CONFIG_CMDLINE_EDITING */ /****************************************************************************/ #ifdef CONFIG_CMDLINE /* * Prompt for input and read a line. * If CONFIG_BOOT_RETRY_TIME is defined and retry_time >= 0, * time out when time goes past endtime (timebase time in ticks). * Return: number of read characters * -1 if break * -2 if timed out */ int ubreadline (const char *const prompt) { /* * If console_buffer isn't 0-length the user will be prompted to modify * it instead of entering it from scratch as desired. */ console_buffer[0] = '\0'; return ubreadline_into_buffer(prompt, console_buffer, 0); } int ubreadline_into_buffer(const char *const prompt, char *buffer, int timeout) { char *p = buffer; unsigned int len = CONFIG_SYS_CBSIZE; int rc; static int initted = 0; /* * History uses a global array which is not * writable until after relocation to RAM. * Revert to non-history version if still * running from flash. */ if (0) { if (!initted) { hist_init(); initted = 1; } if (prompt) printf (prompt); rc = cread_line(prompt, p, &len, timeout); return rc < 0 ? rc : len; } else { char * p_buf = p; int n = 0; /* buffer index */ int plen = 0; /* prompt length */ int col; /* output column cnt */ char c; /* print prompt */ if (prompt) { plen = strlen (prompt); printf (prompt); } col = plen; for (;;) { c = serial_getchar(); /* * Special character handling */ switch (c) { case '\r': /* Enter */ case '\n': *p = '\0'; printf ("\r\n"); return p - p_buf; case '\0': /* nul */ continue; case 0x03: /* ^C - break */ p_buf[0] = '\0'; /* discard input */ return -1; case 0x15: /* ^U - erase line */ while (col > plen) { printf (erase_seq); --col; } p = p_buf; n = 0; continue; case 0x17: /* ^W - erase word */ p=delete_char(p_buf, p, &col, &n, plen); while ((n > 0) && (*p != ' ')) { p=delete_char(p_buf, p, &col, &n, plen); } continue; case 0x08: /* ^H - backspace */ case 0x7F: /* DEL - backspace */ p=delete_char(p_buf, p, &col, &n, plen); continue; default: /* * Must be a normal character then */ if (n < CONFIG_SYS_CBSIZE-2) { if (c == '\t') { /* expand TABs */ /* if auto completion triggered just continue */ *p = '\0'; if (cmd_auto_complete(prompt, console_buffer, &n, &col)) { p = p_buf + n; /* reset */ continue; } printf (tab_seq+(col&07)); col += 8 - (col&07); } else { char buf[2]; /* * Echo input using printf() to force an * LCD flush if we are using an LCD */ ++col; buf[0] = c; buf[1] = '\0'; printf(buf); } *p++ = c; ++n; } else { /* Buffer full */ serial_putchar('\a'); } } } } } /****************************************************************************/ static char * delete_char (char *buffer, char *p, int *colp, int *np, int plen) { char *s; if (*np == 0) { return (p); } if (*(--p) == '\t') { /* will retype the whole line */ while (*colp > plen) { printf (erase_seq); (*colp)--; } for (s=buffer; s= CONFIG_SYS_CBSIZE) { printf ("## Command too long!\n"); return -1; } strcpy (cmdbuf, cmd); /* Process separators and check for invalid * repeatable commands */ debug_parser("[PROCESS_SEPARATORS] %s\n", cmd); while (*str) { /* * Find separator, or string end * Allow simple escape of ';' by writing "\;" */ for (inquotes = 0, sep = str; *sep; sep++) { if ((*sep=='\'') && (*(sep-1) != '\\')) inquotes=!inquotes; if (!inquotes && (*sep == ';') && /* separator */ ( sep != str) && /* past string start */ (*(sep-1) != '\\')) /* and NOT escaped */ break; } /* * Limit the token to data between separators */ token = str; if (*sep) { str = sep + 1; /* start of command for next pass */ *sep = '\0'; } else str = sep; /* no more commands for next pass */ debug_parser("token: \"%s\"\n", token); /* find macros in this token and replace them */ process_macros (token, finaltoken); /* Extract arguments */ if ((argc = parse_line (finaltoken, argv)) == 0) { rc = -1; /* no command at all */ continue; } if (cmd_process(flag, argc, argv, &repeatable, NULL)) rc = -1; /* Did the user stop this? */ //if (had_ctrlc ()) // return -1; /* if stopped then not repeatable */ } return rc ? rc : repeatable; } #endif #ifdef CONFIG_CMDLINE /* * Run a command using the selected parser. * * @param cmd Command to run * @param flag Execution flags (CMD_FLAG_...) * @return 0 on success, or != 0 on error. */ int run_command(const char *cmd, int flag) { #ifndef CONFIG_SYS_HUSH_PARSER /* * builtin_run_command can return 0 or 1 for success, so clean up * its result. */ if (builtin_run_command(cmd, flag) == -1) return 1; return 0; #else return parse_string_outer(cmd, FLAG_PARSE_SEMICOLON | FLAG_EXIT_FROM_LOOP); #endif } #endif #if !defined(CONFIG_SYS_HUSH_PARSER) && defined(CONFIG_CMDLINE) /** * Execute a list of command separated by ; or \n using the built-in parser. * * This function cannot take a const char * for the command, since if it * finds newlines in the string, it replaces them with \0. * * @param cmd String containing list of commands * @param flag Execution flags (CMD_FLAG_...) * @return 0 on success, or != 0 on error. */ static int builtin_run_command_list(char *cmd, int flag) { char *line, *next; int rcode = 0; /* * Break into individual lines, and execute each line; terminate on * error. */ line = next = cmd; while (*next) { if (*next == '\n') { *next = '\0'; /* run only non-empty commands */ if (*line) { debug("** exec: \"%s\"\n", line); if (builtin_run_command(line, 0) < 0) { rcode = 1; break; } } line = next + 1; } ++next; } if (rcode == 0 && *line) rcode = (builtin_run_command(line, 0) >= 0); return rcode; } #endif #ifdef CONFIG_CMDLINE int run_command_list(const char *cmd, int len, int flag) { int need_buff = 1; char *buff = (char *)cmd; /* cast away const */ int rcode = 0; if (len == -1) { len = strlen(cmd); #ifdef CONFIG_SYS_HUSH_PARSER /* hush will never change our string */ need_buff = 0; #else /* the built-in parser will change our string if it sees \n */ need_buff = strchr(cmd, '\n') != NULL; #endif } if (need_buff) { buff = malloc(len + 1); if (!buff) return 1; memcpy(buff, cmd, len); buff[len] = '\0'; } #ifdef CONFIG_SYS_HUSH_PARSER rcode = parse_string_outer(buff, FLAG_PARSE_SEMICOLON); #else /* * This function will overwrite any \n it sees with a \0, which * is why it can't work with a const char *. Here we are making * using of internal knowledge of this function, to avoid always * doing a malloc() which is actually required only in a case that * is pretty rare. */ rcode = builtin_run_command_list(buff, flag); if (need_buff) free(buff); #endif return rcode; } #endif /****************************************************************************/ #if defined(CONFIG_CMD_RUN) int do_run (cmd_tbl_t * cmdtp, int flag, int argc, char * const argv[]) { int i; if (argc < 2) return CMD_RET_USAGE; for (i=1; i