Simple command line implementation based on STM32
Hi,long time no see. I looked at the atomic usmart and lwip official shell, and found that they all had one thing in common. After identifying the first command, I began to find the callback, and did not count the argc and argv. There are also many people to ask how the main functions argc and argv in linux applications come from. Let's find the uboot source code to see what we have done and how we can move them to 32.
- Source code analysis of u-boot command line
First I'll take a look at my version number - > / u-boot / makefile /
VERSION = 2012 PATCHLEVEL = 04 SUBLEVEL = 01 EXTRAVERSION = ifneq "$(SUBLEVEL)" "" U_BOOT_VERSION = $(VERSION).$(PATCHLEVEL).$(SUBLEVEL)$(EXTRAVERSION)
As always, we found out after throwing u-boot into sourceinsight. Open main.c. After entering init of some columns, the
#ifdef CONFIG_PREBOOT if ((p = getenv ("preboot")) != NULL){ # ifdef CONFIG_AUTOBOOT_KEYED int prev = disable_ctrlc(1); /* disable Control C checking # endif run_command(p, 0); # ifdef CONFIG_AUTOBOOT_KEYED disable_ctrlc(prev); /* restore Control C checking */ # endif } #endif
We found out in run_ No run before command_ Init and so on, so this is the entry we are going to enter.
/* * 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 }
CONFIG_ SYS_ HUSH_ In order to flexibly configure environment variables, we look at the above.
/**************************************************************************** * returns: * 1 - command executed, repeatable * 0 - command executed but not repeatable, interrupted commands are * always considered not repeatable * -1 - not executed (unrecognized, bootd recursion or too many args) * (If cmd is NULL or "" or longer than CONFIG_SYS_CBSIZE-1 it is * considered unrecognized) * * WARNING: * * We must create a temporary copy of the command since the command we get * may be the result from getenv(), which returns a pointer directly to * the environment data, which may change magicly when the command we run * creates or modifies environment variables (like "bootp" does). */ static int builtin_run_command(const char *cmd, int flag) { char cmdbuf[CONFIG_SYS_CBSIZE]; /* working copy of cmd */ char *token; /* start of token in cmdbuf */ char *sep; /* end of token (separator) in cmdbuf */ char finaltoken[CONFIG_SYS_CBSIZE]; char *str = cmdbuf; char *argv[CONFIG_SYS_MAXARGS + 1]; /* NULL terminated */ int argc, inquotes; int repeatable = 1; int rc = 0; #ifdef DEBUG_PARSER printf ("[RUN_COMMAND] cmd[%p]=\"", cmd); puts (cmd ? cmd : "NULL"); /* use puts - string may be loooong */ puts ("\"\n"); #endif clear_ctrlc(); /* forget any previous Control C */ if (!cmd || !*cmd) { return -1; /* empty command */ } if (strlen(cmd) >= CONFIG_SYS_CBSIZE) { puts ("## Command too long!\n"); return -1; } strcpy (cmdbuf, cmd); /* Process separators and check for invalid * repeatable commands */ #ifdef DEBUG_PARSER printf ("[PROCESS_SEPARATORS] %s\n", cmd); #endif 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 */ #ifdef DEBUG_PARSER printf ("token: \"%s\"\n", token); #endif /* 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)) rc = -1; /* Did the user stop this? */ if (had_ctrlc ()) return -1; /* if stopped then not repeatable */ } return rc ? rc : repeatable; }
It's not hard to see from the process
1.process_ Macros - > find token macro replacement
2.parse_ Line - > parse parameters
3.cmd_ Process - > find the command and run it
A simple command line, we only need to parse the parameters, as well as the command to move over. Let's see how the u-boot command is registered
We can open a CMD in the directory / u-boot/command / at will_ ***. C pull to the bottom to see
U_BOOT_CMD( log, 255, 1, do_log, "manipulate logbuffer", "info - show pointer details\n" "log reset - clear contents\n" "log show - show contents\n" "log append <msg> - append <msg> to the logbuffer" );
Seeing this, we are familiar with it. We have introduced function segments before. The macro here is shown in / u-boot/include/command.h
The usage is the same as our function segment. After the process is basically sorted out, we start to move.
Here is the program of stm32
command.h:
#ifndef _MY_COMMAND_H_ #define _MY_COMMAND_H_ #include "project.h" #define isblank(c) (c == ' ' || c == '\t')#define My_Puts(node,fmt,args...) do{\ snprintf( Telnet_Send_Buff , TELNET_SEND_BUFF_SIZE, fmt"\r\n" , ## args );\ tcp_write( node->socket , Telnet_Send_Buff , strlen(Telnet_Send_Buff) , TCP_WRITE_FLAG_COPY );\ tcp_output( node->socket );\ }while( 0 ); #define CONFIG_SYS_LONGHELP /* */ struct cmd_tbl_s { char *name; /* Command Name */ int maxargs; /* maximum number of arguments */ int repeatable; /* autorepeat allowed? */ /* Implementation function */ int (*cmd)(struct cmd_tbl_s *, int, int, char *[], struct My_NetList *node); char *usage; /* Usage message (short) */ #ifdef CONFIG_SYS_LONGHELP char *help; /* Help message (long) */ #endif #ifdef CONFIG_AUTO_COMPLETE /* do auto completion on the arguments */ int (*complete)(int argc, char * const argv[], char last_char, int maxv, char *cmdv[]); #endif }; typedef struct cmd_tbl_s cmd_tbl_t; #define Struct_Section __attribute__((used, section("my_cmd"), \ aligned(4))) #ifdef CONFIG_AUTO_COMPLETE # define _CMD_COMPLETE(x) x, #else # define _CMD_COMPLETE(x) #endif #ifdef CONFIG_SYS_LONGHELP # define _CMD_HELP(x) x, #else # define _CMD_HELP(x) #endif #define VI_CMD_MKENT_COMPLETE(name,maxargs,rep,cmd,usage,help,comp) \ {#name, maxargs, rep, cmd, usage, _CMD_HELP(help) _CMD_COMPLETE(comp)} #define VI_CMD_MKENT(name,maxargs,rep,cmd,usage,help) \ VI_CMD_MKENT_COMPLETE(name,maxargs,rep,cmd,usage,help,NULL) #define VI_CMD_COMPLETE(name,maxargs,rep,cmd,usage,help,comp) \ cmd_tbl_t my_cmd_##name Struct_Section = \ VI_CMD_MKENT_COMPLETE(name,maxargs,rep,cmd,usage,help,comp) #define VI_CMD(name,maxargs,rep,cmd,usage,help) \ VI_CMD_COMPLETE(name,maxargs,rep,cmd,usage,help,NULL) int readline ( char * prompt , char *buffer , struct My_NetList *node ); #endif
Here is command.c
#include "project.h" #define CONFIG_SYS_CBSIZE 256 #define CONFIG_SYS_MAXARG 16 //static char erase_seq[] = "\b \b"; /* erase sequence */ //static char tab_seq[] = " "; /* used to expand TABs */ extern unsigned int Image$$my_cmdreg$$Base; extern unsigned int Image$$my_cmdreg$$Length; /*************************************************************************** * find command table entry for a command */ cmd_tbl_t *find_cmd_tbl ( const char *cmd , cmd_tbl_t *table , int table_len ){ cmd_tbl_t *cmdtp; cmd_tbl_t *cmdtp_temp = table; /*Init value */ const char *p; int len; int n_found = 0; if (!cmd) return NULL; /* * Some commands allow length modifiers (like "cp.b"); * compare command name only until first dot. */ len = ((p = strchr(cmd, '.')) == NULL) ? strlen (cmd) : (p - cmd); for (cmdtp = table; (int)cmdtp != (int)table + table_len; cmdtp++) { if (strncmp (cmd, cmdtp->name, len) == 0) { if (len == strlen (cmdtp->name)) return cmdtp; /* full match */ cmdtp_temp = cmdtp; /* abbreviated command ? */ n_found++; } } if (n_found == 1) { /* exactly one match */ return cmdtp_temp; } return NULL; } cmd_tbl_t *find_cmd (const char *cmd){ return find_cmd_tbl( cmd, ( cmd_tbl_t * )&Image$$my_cmdreg$$Base , (int)&Image$$my_cmdreg$$Length ); } static int make_argv( char *s , int argvsz , char *argv[] ){ int argc = 0; /* split into argv */ while ( argc < argvsz - 1 ) { /* skip any white space */ while ( isblank( *s ) ) ++s; if ( *s == '\r' || *s == '\n' || *s == '\0' ) /* end of s, no more args */ break; argv[argc++] = s; /* begin of argument string */ /* find end of string */ while (*s && !isblank(*s)) ++s; if ( *s == '\r' || *s == '\n' || *s == '\0' ) /* end of s, no more args */ break; *s++ = '\0'; /* terminate current arg */ } argv[argc] = NULL; return argc; } /** * Call a command function. This should be the only route in U-Boot to call * a command, so that we can track whether we are waiting for input or * executing a command. * * @param cmdtp Pointer to the command to execute * @param flag Some flags normally 0 (see CMD_FLAG_.. above) * @param argc Number of arguments (arg 0 must be the command text) * @param argv Arguments * @return 0 if command succeeded, else non-zero (CMD_RET_...) */ static int cmd_call( cmd_tbl_t *cmdtp , int flag , int argc , char *argv[] , struct My_NetList *node ){ int result; result = (cmdtp->cmd)( cmdtp, flag, argc, argv, node ); if (result){ printf("Command failed, result=%d\a", result); } } /* * 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 readline ( char * prompt , char *buffer , struct My_NetList *node ){ char *argv[CONFIG_SYS_MAXARG + 1]; int argc; cmd_tbl_t *cmdtp; argc = make_argv( buffer , sizeof(argv)/sizeof(argv[0]) , argv ); if(argc != 0) { /* Look up command in command table */ cmdtp = find_cmd(argv[0]); if ( cmdtp == NULL ) { My_Puts( node , "Unknown command '%s' - try 'help'\n" , argv[0] ); return -1; } if( argc > cmdtp->maxargs ) { // too many args return -1; } cmd_call( cmdtp , 0 , argc , argv , node ); }else{ // argc equal zero. maybe it means ENTER press tcp_write( node->socket , prompt , strlen( prompt ) , TCP_WRITE_FLAG_COPY ); node->usrdata.telnet_arg.Crlf = TRUE; } return 0; } /* System interrupt ... */ int do_stop ( struct cmd_tbl_s *cmdtp, int flag, int argc, char *argv[], struct My_NetList *node ){ node->usrdata.telnet_arg.Play = FALSE; My_Puts( node , " logo stop here ------------ " ); return 0; } int do_play ( struct cmd_tbl_s *cmdtp, int flag, int argc, char *argv[], struct My_NetList *node ){ node->usrdata.telnet_arg.Play = TRUE; My_Puts( node , " logo start here ------------ " ); return 0; } int do_exit( struct cmd_tbl_s *cmdtp, int flag, int argc, char *argv[], struct My_NetList *node ){ VI_Net_shutdown(node->name); return 0; } int do_cmem( struct cmd_tbl_s *cmdtp, int flag, int argc, char *argv[], struct My_NetList *node ){ My_Puts( node , MEM_CHECK , mem_perused(SRAMIN),mem_perused(SRAMEX),mem_perused(SRAMCCM) ); return 0; } int do_pupdate( struct cmd_tbl_s *cmdtp, int flag, int argc, char *argv[], struct My_NetList *node ){ /* Step 1 : Reset the Updata Flag */ g_unPro_Updata_Flsg.UpdataFlag = 0x00; /* Step 2 : Write to Flash */ W25QXX_WithoutBackErase_Write((u8*)(&g_unPro_Updata_Flsg.UpdataFlag),OTA_IPGW,6); Ny_Puts( node , " Prepare update finish " ); return 0; } int do_reboot( struct cmd_tbl_s *cmdtp, int flag, int argc, char *argv[], struct My_NetList *node ){ __disable_fault_irq(); NVIC_SystemReset(); return 0; } int do_reset( struct cmd_tbl_s *cmdtp, int flag, int argc, char *argv[], struct My_NetList *node ){ //waiting for th add return 0; } int do_help( struct cmd_tbl_s *cmdtp, int flag, int argc, char *argv[], struct My_NetList *node ){ if(argc == 1){ /* show list of commands */ for( cmdtp = ( struct cmd_tbl_s* )&Image$$my_cmdreg$$Base ; \ (int)cmdtp != (int)&Image$$my_cmdreg$$Base + (int)&Image$$my_cmdreg$$Length ;\ cmdtp++){ My_Puts( node, "%-*s- %s" , 8 , cmdtp->name , cmdtp->usage ); } }else{ unsigned char i; for( i = 1 ; i < argc ; i++ ){ if( ( cmdtp = find_cmd(argv[i] ) ) != NULL ){ My_Puts( node , "Usage:\n%s\n",cmdtp->name ); if(!cmdtp->help){ My_Puts( node , "- No additional help available." ); return -1; } My_Puts( node , "%s" , cmdtp->help ); }else{ My_Puts ( node , "Unknown command '%s' - try 'help'" " without arguments for list of all" " known commands\n\n", argv[i]); } } } return 0; } VI_CMD(stop , 1 , 0 , do_stop , \ "stop the debug logo" ,\ " Just stop logo output , args needless " ); VI_CMD(play , 1 , 0 , do_play , \ "play the debug logo" , \ " Just play logo output , args needless " ); VI_CMD(exit , 1 , 0 , do_exit , \ "exit terminal" , \ " Exit terminal , args needless " ); VI_CMD(cmem , 1 , 0 , do_cmem , \ " check the memory of manager " , \ " Just check the memory usage rate , args needless "); VI_CMD(pupdate , 1 , 0 , do_pupdate , \ " prepare update. " , \ " Earse Flash Flag to prepare update , args needless "); VI_CMD(reboot , 1 , 0 , do_reboot ,\ " reboot " , " reboot , args needless "); VI_CMD(reset , 1 , 0 , do_reset ,\ " earse the device flash ",\ " earse the device flash "); VI_CMD(help , CONFIG_SYS_MAXARG , 0 , do_help , \ "print command description/usage",\ "\n"\ " - print brief description of all commands\n"\ "help command ...\n"\ " - print detailed usage of 'command'"); /******************* (C) COPYRIGHT 2019 Doon****************END OF FILE****/
Of course, I also implemented some other commands. Auto completion requires echo first, so here we only implement a relatively simple command line based on telnet. The following is the printing interface connected with putty:
Some customized contents are included, please modify by yourself. (such as network node, project.h, etc.)