u-boot commandline接口移植

启动u-boot后,输入help会得到很多指令,常用的比如tftp,nfs,fatload,fastboot,bootm.bootd等等。这些命令的结构其实很简单。以下从命令的获取/解析/执行来做一个最小化的command line interface,而且很容易移植到比如单片机,zynq裸机等等环境,只有两个文件command.c和command.h

在文末会说明命令行接口对外的接口类型及所有的源码。

不过:u-boot提供的命令行接口是阻塞的,什么意思呢?即接受命令的过程中不能执行其他内容,在裸机的情况下这个没问题,但是当commandline在RTOS里面执行的时候就会有问题;关于non-block的问题读者自行解决。很easy的。

目录

数据结构

命令的获取

解析命令

命令执行

对外接口

最后源码


数据结构

struct cmd_tbl_s {
	char		*name;		/* Command Name			*/
	int			maxargs;	/* maximum number of arguments	*/
	int			(*cmd)(struct cmd_tbl_s *, int, char * const []);
	char		*usage;		/* Usage message	(short)	*/
};

typedef struct cmd_tbl_s	cmd_tbl_t;

#define Struct_Section __attribute__ ((unused,section(".cli_cmd")))

#define REGISTER_CMD(name, maxargs,handler, usage) \
	const cmd_tbl_t cli_cmd_##name Struct_Section = {#name, maxargs ,handler , usage}

以上不难看出,所有的命令会调用REGISTER_CMD,例如

REGISTER_CMD(
		help,
		1,
		do_help,
		"print help information"
);

其handler原型为

int do_help(struct cmd_tbl_s *cmd, int argc, char * const args[])

由于数据结构里面的__attribute__,可知所有REGISTER_CMD定义的命令构造的struct cmd_tbl_s结构体最后都会链接到.cli_cmd这个段,因此,用户需要在链接脚本里面自行添加.cli_cmd段

比如zynq里面的ldscript.ld

.cli_cmd : {
   __cli_start = .;
   *(.cli_cmd)
   __cli_end = .;
} > ps7_ram_0

命令的获取

命令的获取在command_loop里面

static int readline_into_buffer(const char *const prompt, char *buffer, int timeout)
{
	char *p = buffer;
	char * p_buf = p;
	int	n = 0;				/* buffer index		*/
	int	plen = 0;			/* prompt length	*/
	int	col;				/* output column cnt	*/
	unsigned char	c;
	int count = 0;


	/* print prompt */
	if (prompt) {
		plen = strlen (prompt);
		ops->outs (prompt);
	}
	col = plen;

	for (;;) {
		count = ops->ins(&c, 1);
		if(count == 0)
		{
			continue;
		}

		/*
		 * Special character handling
		 */
		switch (c) {
		case '\r':			/* Enter		*/
		case '\n':
			*p = '\0';
			ops->outs ("\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) {
				ops->outs (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 */
					ops->outs (tab_seq+(col&07));
					col += 8 - (col&07);
				} else {
					char buf[2];

					/*
					 * Echo input using puts() to force an
					 * LCD flush if we are using an LCD
					 */
					++col;
					buf[0] = c;
					buf[1] = '\0';
					ops->outs(buf);
				}
				*p++ = c;
				++n;
			} else {			/* Buffer full		*/
				ops->outs ("buffer full .\r\n");
			}
		}
	}
}

里面有一个for (;;) {},一直等待串口获取字符, 正常情况下在用户输入回车时才跳出来。这里的裸机即我们按下回车键即主机以为命令输入完毕。

解析命令

解析命令使用parse_line

int parse_line (char *line, char *argv[])
{
	int nargs = 0;

	//ops->outs("parse_line: \"%s\"\r\n", line);
	while (nargs < CONFIG_SYS_MAXARGS) {

		/* skip any white space */
		while (isblank(*line))
			++line;

		if (*line == '\0') {	/* end of line, no more args	*/
			argv[nargs] = NULL;
			ops->outs("parse_line: nargs=%d\r\n", nargs);
			return nargs;
		}

		argv[nargs++] = line;	/* begin of argument string	*/

		/* find end of string */
		while (*line && !isblank(*line))
			++line;

		if (*line == '\0') {	/* end of line, no more args	*/
			argv[nargs] = NULL;

			return nargs;
		}


		*line++ = '\0';		/* terminate current arg	 */
	}

	ops->outs ("** Too many args (max. %d) **\r\n", CONFIG_SYS_MAXARGS);

	ops->outs("parse_line: nargs=%d\r\n", nargs);
	return (nargs);
}

parse_line其实就是以空格来分解命令字和参数,最后存到argv(字符串类型)里面

命令执行

命令执行前会先比对输入的命令是否是我们REGISTER_CMD的命令,其实即扫描.cli_cmd段

扫描函数find_cmd原型

/*
* match the cmd , return 1 while found
*/
int find_cmd(const char *cmdname, cmd_tbl_t **cmd)
{
	int found = 0;
	cmd_tbl_t *cmdtp;

	int len = cli_cmd_end - cli_cmd_start;

	for(cmdtp = cli_cmd_start; cmdtp != cli_cmd_start + len; cmdtp++)
	{
		if(0 == strcmp(cmdname, cmdtp->name))
		{
			*cmd = cmdtp;
			found = 1;
			break;
		}
	}
	return found;
}

匹配后执行命令调用cmd_call, 

static int cmd_call(cmd_tbl_t *cmdtp, int argc, char * const argv[])
{
	int result;

	result = (cmdtp->cmd)(cmdtp, argc, argv);
	if (result)
		ops->outs("Command failed, result=%d", result);
	return result;
}

对外接口

command line的角度对外的接口即需要提供串口的读,写等操作

这里封装了以下:

struct cli_fops
{
	char	*name;                                              /* name */
	void	(*outs)(const char *fmt,...);                       /* put string */
	int		(*outc)(const unsigned char *, int );               /* put char */
	int		(*ins)(unsigned char *, int );                      /* get string */
};

很明显,三个函数指针需要实现,其中outs表示串口输出格式化字符串,outc输出一个字符, ins输入指定长度的字符。

这三个必须要实现

最后源码

最后贴出所有文件,即command.c和command.h

/*
 * command.h
 *
 *  Created on: 2019年6月24日
 *      Author: Flinn
 */

#ifndef SRC_COMMAND_H_
#define SRC_COMMAND_H_


#define CONFIG_SYS_CBSIZE		256	/* Console I/O Buffer Size */
#define CONFIG_SYS_PBSIZE		384	/* Print Buffer Size */
#define CONFIG_SYS_MAXARGS		16	/* max number of command args */

#define CONFIG_SYS_PROMPT		"zynq # "

struct cmd_tbl_s {
	char		*name;		/* Command Name			*/
	int			maxargs;	/* maximum number of arguments	*/
	int			(*cmd)(struct cmd_tbl_s *, int, char * const []);
	char		*usage;		/* Usage message	(short)	*/
};

typedef struct cmd_tbl_s	cmd_tbl_t;

#define Struct_Section __attribute__ ((unused,section(".cli_cmd")))

#define REGISTER_CMD(name, maxargs,handler, usage) \
	const cmd_tbl_t cli_cmd_##name Struct_Section = {#name, maxargs ,handler , usage}

struct cli_fops
{
	char	*name;                                              /* name */
	void	(*outs)(const char *fmt,...);                       /* put string */
	int		(*outc)(const unsigned char *, int );               /* put char */
	int		(*ins)(unsigned char *, int );                      /* get string */
};


void command_loop(void);

void command_init(void);

#endif /* SRC_COMMAND_H_ */

command.c

/*
 * command.c
 *
 *  Created on: 2019年6月24日
 *      Author: Flinn
 */

#include 
#include "command.h"

#include "serial.h"

static cmd_tbl_t *cli_cmd_start = NULL;
static cmd_tbl_t *cli_cmd_end = NULL;

/*
 *  TODO: update this pointer of funcs
 */
static  struct cli_fops cli_ops =
{
	.name = "cli fops",
	.outs = serial_puts,
	.outc = serial_send,
	.ins = serial_gets,
};

static struct cli_fops *ops = NULL;


#define VER_MAJ		0
#define VER_MIN		1
int do_version(struct cmd_tbl_s *cmd, int argc, char * const args[])
{
	ops->outs("firmware ver %d.%d\r\n\r\n", VER_MAJ, VER_MIN);
	return 0;
}


REGISTER_CMD(
		ver,
		1,
		do_version,
		"print firmware version info"
);

int do_help(struct cmd_tbl_s *cmd, int argc, char * const args[])
{
	cmd_tbl_t *cmdtp;

	int len = cli_cmd_end - cli_cmd_start;

	for(cmdtp = cli_cmd_start; cmdtp != cli_cmd_start + len; cmdtp++)
	{
		ops->outs("%-8s%s\r\n", cmdtp->name, cmdtp->usage);
	}
	ops->outs("\r\n");
	return 0;
}


REGISTER_CMD(
		help,
		1,
		do_help,
		"print help information"
);



char    console_buffer[CONFIG_SYS_CBSIZE + 1] = {0x0};	/* console I/O buffer	*/

static const char erase_seq[] = "\b \b";		/* erase sequence	*/
static const char   tab_seq[] = "        ";		/* used to expand TABs	*/
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) {
			ops->outs (erase_seq);
			(*colp)--;
		}
		for (s=buffer; souts (tab_seq+((*colp) & 07));
				*colp += 8 - ((*colp) & 07);
			} else {
				++(*colp);
				ops->outc (*s, 1);
			}
		}
	} else {
		ops->outs (erase_seq);
		(*colp)--;
	}
	(*np)--;
	return (p);
}

static int readline_into_buffer(const char *const prompt, char *buffer, int timeout)
{
	char *p = buffer;
	char * p_buf = p;
	int	n = 0;				/* buffer index		*/
	int	plen = 0;			/* prompt length	*/
	int	col;				/* output column cnt	*/
	unsigned char	c;
	int count = 0;


	/* print prompt */
	if (prompt) {
		plen = strlen (prompt);
		ops->outs (prompt);
	}
	col = plen;

	for (;;) {
		count = ops->ins(&c, 1);
		if(count == 0)
		{
			continue;
		}

		/*
		 * Special character handling
		 */
		switch (c) {
		case '\r':			/* Enter		*/
		case '\n':
			*p = '\0';
			ops->outs ("\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) {
				ops->outs (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 */
					ops->outs (tab_seq+(col&07));
					col += 8 - (col&07);
				} else {
					char buf[2];

					/*
					 * Echo input using puts() to force an
					 * LCD flush if we are using an LCD
					 */
					++col;
					buf[0] = c;
					buf[1] = '\0';
					ops->outs(buf);
				}
				*p++ = c;
				++n;
			} else {			/* Buffer full		*/
				ops->outs ("buffer full .\r\n");
			}
		}
	}
}

static int readline (const char *const prompt)
{
	console_buffer[0] = '\0';

	return readline_into_buffer(prompt, console_buffer, 0);
}



#define isblank(c)	(c == ' ' || c == '\t')
int parse_line (char *line, char *argv[])
{
	int nargs = 0;

	//ops->outs("parse_line: \"%s\"\r\n", line);
	while (nargs < CONFIG_SYS_MAXARGS) {

		/* skip any white space */
		while (isblank(*line))
			++line;

		if (*line == '\0') {	/* end of line, no more args	*/
			argv[nargs] = NULL;
			ops->outs("parse_line: nargs=%d\r\n", nargs);
			return nargs;
		}

		argv[nargs++] = line;	/* begin of argument string	*/

		/* find end of string */
		while (*line && !isblank(*line))
			++line;

		if (*line == '\0') {	/* end of line, no more args	*/
			argv[nargs] = NULL;

			return nargs;
		}


		*line++ = '\0';		/* terminate current arg	 */
	}

	ops->outs ("** Too many args (max. %d) **\r\n", CONFIG_SYS_MAXARGS);

	ops->outs("parse_line: nargs=%d\r\n", nargs);
	return (nargs);
}

static int cmd_call(cmd_tbl_t *cmdtp, int argc, char * const argv[])
{
	int result;

	result = (cmdtp->cmd)(cmdtp, argc, argv);
	if (result)
		ops->outs("Command failed, result=%d", result);
	return result;
}

int cmd_usage(const cmd_tbl_t *cmdtp)
{
	ops->outs("%s - %s\n\n", cmdtp->name, cmdtp->usage);

	return 1;
}



/*
* match the cmd , return 1 while found
*/
int find_cmd(const char *cmdname, cmd_tbl_t **cmd)
{
	int found = 0;
	cmd_tbl_t *cmdtp;

	int len = cli_cmd_end - cli_cmd_start;

	for(cmdtp = cli_cmd_start; cmdtp != cli_cmd_start + len; cmdtp++)
	{
		if(0 == strcmp(cmdname, cmdtp->name))
		{
			*cmd = cmdtp;
			found = 1;
			break;
		}
	}
	return found;
}

static int cmd_process(int argc, char * const argv[])
{
	int rc = 0;
	cmd_tbl_t *cmdtp = NULL;

	/* Look up command in command table */
	rc = find_cmd(argv[0], &cmdtp);
	if (rc != 1) {
		ops->outs("Unknown command '%s' - try 'help'\r\n", argv[0]);
		return 1;
	}

	/* found - check max args */
	if (argc > cmdtp->maxargs)
	{
		ops->outs ("## Too much args, will skipped !\r\n");
		rc = 1;
	}
	else if(argc < cmdtp->maxargs)
	{
		//ops->outs ("## Not enough args, try -'help'- \r\n");
		rc = -1;
	}


	/* If OK so far, then do the command */
	if (rc == 1) {
		rc = cmd_call(cmdtp, argc, argv);
	}
	if (rc == -1)
		rc = cmd_usage(cmdtp);
	return rc;
}


static int builtin_run_command(const char *cmd)
{
	char cmdbuf[CONFIG_SYS_CBSIZE] = {0}; /* working copy of cmd		*/
	char *token = cmdbuf;			/* start of token in cmdbuf */

	char *argv[CONFIG_SYS_MAXARGS + 1]; /* NULL terminated	*/
	int argc = 0;
	int rc = 0;

	if (!cmd || !*cmd) {
		return -1;	/* empty command */
	}

	if (strlen(cmd) >= CONFIG_SYS_CBSIZE) {
		ops->outs ("## Command too long!\r\n");
		return -1;
	}

	strcpy (cmdbuf, cmd);

	/* Extract arguments */
	if ((argc = parse_line (token, argv)) == 0) {
		rc = -1;	/* no command at all */
		return rc;
	}

	if (cmd_process(argc, argv))
		rc = -1;

	return rc;
}

static int run_command(const char *cmd)
{
	if (builtin_run_command(cmd) == -1)
		return 1;

	return 0;
}

void command_loop(void)
{
	static char lastcommand[CONFIG_SYS_CBSIZE] = {0x0};
	int len;
	int rc = 1;

	len = readline (CONFIG_SYS_PROMPT);
	if (len > 0)
	{
		rc = run_command(console_buffer);
		strcpy (lastcommand, console_buffer);
	}
//	else
//		rc = run_command(lastcommand);

	if (rc <= 0) {
		/* invalid command or not repeatable, forget it */
		lastcommand[0] = 0;
	}
}

void command_init(void)
{
	extern cmd_tbl_t __cli_start, __cli_end;
	cli_cmd_start = &__cli_start;
	cli_cmd_end = &__cli_end;

	ops = &cli_ops;
}

 

你可能感兴趣的:(zynq)