Simple command line implementation based on STM32

Keywords: socket shell Linux Makefile

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.

  1. 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 -1return 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.)

Posted by warrenk on Sun, 07 Jun 2020 19:42:38 -0700