启动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;
}