int console_init_r(void)
{
char *stdinname, *stdoutname, *stderrname;
struct stdio_dev *inputdev = NULL, *outputdev = NULL, *errdev = NULL;
#ifdef CONFIG_CONSOLE_MUX
int iomux_err = 0;
#endif
/* set default handlers at first */
gd->jt->getc = serial_getc;
gd->jt->tstc = serial_tstc;
gd->jt->putc = serial_putc;
gd->jt->puts = serial_puts;
gd->jt->printf = serial_printf;
/*--------------------------以上为代码段1--------------------------------------------*/
/* stdin stdout and stderr are in environment */
/* scan for it */
stdinname = getenv("stdin");
stdoutname = getenv("stdout");
stderrname = getenv("stderr"); //setenv stdout serial,vga标准输出被重载,如果u-boot中环境变量stdou被设定,那么stdout就被重定位
if (OVERWRITE_CONSOLE == 0) { /* if not overwritten by config switch */ 这里OVERWRITE_CONSOLE值为1
inputdev = search_device(DEV_FLAGS_INPUT, stdinname);
outputdev = search_device(DEV_FLAGS_OUTPUT, stdoutname);
errdev = search_device(DEV_FLAGS_OUTPUT, stderrname);
#ifdef CONFIG_CONSOLE_MUX //如setenv stdout serial,vga
iomux_err = iomux_doenv(stdin, stdinname);
iomux_err += iomux_doenv(stdout, stdoutname);
iomux_err += iomux_doenv(stderr, stderrname);
if (!iomux_err)
/* Successful, so skip all the code below. */
goto done;
#endif
}
/*--------------------------以上为代码段2--------------------------------------------*/
/* if the devices are overwritten or not found, use default device */
if (inputdev == NULL) {
inputdev = search_device(DEV_FLAGS_INPUT, "serial");
}
if (outputdev == NULL) {
outputdev = search_device(DEV_FLAGS_OUTPUT, "serial");
}
if (errdev == NULL) {
errdev = search_device(DEV_FLAGS_OUTPUT, "serial");
}
/*--------------------------以上为代码段3--------------------------------------------*/
/* Initializes output console first */
if (outputdev != NULL) {
/* need to set a console if not done above. */
console_doenv(stdout, outputdev);
}
if (errdev != NULL) {
/* need to set a console if not done above. */
console_doenv(stderr, errdev);
}
if (inputdev != NULL) {
/* need to set a console if not done above. */
console_doenv(stdin, inputdev);
}
/*--------------------------以上为代码段4--------------------------------------------*/
#ifdef CONFIG_CONSOLE_MUX
done:
#endif
#ifndef CONFIG_SYS_CONSOLE_INFO_QUIET /*defined*/
stdio_print_current_devices();
#endif /* CONFIG_SYS_CONSOLE_INFO_QUIET */
#ifdef CONFIG_SYS_CONSOLE_ENV_OVERWRITE /*no defined*/
/* set the environment variables (will overwrite previous env settings) */
for (i = 0; i < 3; i++) {
setenv(stdio_names[i], stdio_devices[i]->name);
}
#endif /* CONFIG_SYS_CONSOLE_ENV_OVERWRITE */ /*defined*/
gd->flags |= GD_FLG_DEVINIT; /* device initialization completed */
print_pre_console_buffer(PRE_CONSOLE_FLUSHPOINT2_EVERYTHING_BUT_SERIAL);
/*--------------------------以上为代码段5--------------------------------------------*/
return 0;
}
上述程序按其实现的功能可分为5部分,为了便于分析,我们下面仅以stdout设备为例,逐步进行讨论:
gd->jt->getc = serial_getc;
gd->jt->tstc = serial_tstc;
....
上述的代码段设置jt操作的默认函数为串口相关函数。关于gd->jt所包含函数的使用,我们将在后续的章节中讨论。
stdinname =getenv("stdin");
stdoutname = getenv("stdout");
stderrname = getenv("stderr"); /*setenv stdout serial,vga标准输出被重载,如果u-boot中环境变量stdou被设定,那么stdout就被重定位*/
if (OVERWRITE_CONSOLE == 0) { /* if not overwritten by config switch */ /* OVERWRITE_CONSOLE或为宏定义,或为函数返回值,这里为返回值1*/
inputdev = search_device(DEV_FLAGS_INPUT, stdinname);
outputdev = search_device(DEV_FLAGS_OUTPUT, stdoutname);
errdev = search_device(DEV_FLAGS_OUTPUT, stderrname);
#ifdef CONFIG_CONSOLE_MUX //如setenv stdout serial,vga
iomux_err = iomux_doenv(stdin, stdinname);
iomux_err += iomux_doenv(stdout, stdoutname);
iomux_err += iomux_doenv(stderr, stderrname);
if (!iomux_err)
/* Successful, so skip all the code below. */
goto done;
#endif
如我们曾在u-boot中执行命令:
stdoutname = getenv("stdout"); /*setenv stdout serial,vga标准输出被重载,如果u-boot中环境变量stdou被设定,那么stdout就被重定位*/
...
if (OVERWRITE_CONSOLE == 0) { /* if not overwritten by config switch */ /* OVERWRITE_CONSOLE或为宏定义,或为函数返回值,这里为返回值1*/
#ifdef CONFIG_CONSOLE_MUX //如setenv stdout serial,vga
iomux_err = iomux_doenv(stdin, stdinname);
...
if (!iomux_err)
/* Successful, so skip all the code below. */
goto done;
#endif
inputdev = search_device(DEV_FLAGS_INPUT, stdinname);
...
这样,当定义了CONFIG_CONSOLE_MUX时,如果iomux_doenv的执行没有错误,那么跳过search_device。如果有错误,则进一步执行search_device段代码。
#ifdef CONFIG_CONSOLE_MUX
/* This tries to preserve the old list if an error occurs. */
int iomux_doenv(const int console, const char *arg)
{
char *console_args, *temp, **start;
int i, j, k, io_flag, cs_idx, repeat;
struct stdio_dev *dev;
struct stdio_dev **cons_set;
console_args = strdup(arg);
...
i = 0;
temp = console_args;
for (;;) {
temp = strchr(temp, ',');
if (temp != NULL) {
i++;
temp++;
continue;
}
/* There's always one entry more than the number of commas. */
i++;
break;
}
start = (char **)malloc(i * sizeof(char *));
...
/* setenv stdout serial,vga 几个用逗号分隔的参数*/
i = 0;
start[0] = console_args;
for (;;) {
temp = strchr(start[i++], ',');
if (temp == NULL)
break;
*temp = '\0';
start[i] = temp + 1;
}
/*start是一个指向字符串的指针数组。这里start[0]指向serial, start[1]指向vga*/
/*--------------------------以上为代码段2.1 --------------------------------------------*/
cons_set = (struct stdio_dev **)calloc(i, sizeof(struct stdio_dev *));
/*...cons_set检查,出错返回1*/
switch (console) {
case stdout:
io_flag = DEV_FLAGS_OUTPUT;
break;
default:
/*...释放资源start,console_args,cons_set*/
return 1;
}
cs_idx = 0;
for (j = 0; j < i; j++) {
dev = search_device(io_flag, start[j]);
if (dev == NULL)
continue;
repeat = 0;
for (k = 0; k < cs_idx; k++) {
if (dev == cons_set[k]) {
repeat++;
break;
}
}
if (repeat)
continue;
if (console_assign(console, start[j]) < 0)
continue;
cons_set[cs_idx++] = dev;
}
/*--------------------------以上为代码段2.2 --------------------------------------------*/
free(console_args);
free(start);
/* failed to set any console */
if (cs_idx == 0) {
free(cons_set);
return 1;
} else {
console_devices[console] =
(struct stdio_dev **)realloc(console_devices[console],
cs_idx * sizeof(struct stdio_dev *));
if (console_devices[console] == NULL) {
free(cons_set);
return 1;
}
memcpy(console_devices[console], cons_set, cs_idx *
sizeof(struct stdio_dev *));
cd_count[console] = cs_idx;
}
free(cons_set);
/*--------------------------以上为代码段2.3 --------------------------------------------*/
return 0;
}
#endif /* CONFIG_CONSOLE_MUX */
上述程序中的stdin,stdout,stderr在include/common.h中定义:
#define stdin 0
#define stdout 1
#define stderr 2
#define MAX_FILES 3
讨论上述代码之前,首先要强调的是,只有定义了CONFIG_CONSOLE_MUX,才会有函数iomux_doenv的定义和实现。
int console_assign(int file, const char *devname)
{
int flag;
struct stdio_dev *dev;
/* Check for valid file */
switch (file) {
case stdin:
flag = DEV_FLAGS_INPUT;
break;
case stdout:
case stderr:
flag = DEV_FLAGS_OUTPUT;
break;
default:
return -1;
}
/* Check for valid device name */
dev = search_device(flag, devname);
if (dev)
return console_setfile(file, dev);
return -1;
}
为了此处的讨论尽可能清晰简单,search_device函数我们放在后面分析。
static int console_setfile(int file, struct stdio_dev * dev)
{
int error = 0;
if (dev == NULL)
return -1;
switch (file) {
case stdin:
case stdout:
case stderr:
/* Start new device */
if (dev->start) {
error = dev->start(dev);
/* If it's not started dont use it */
if (error < 0)
break;
}
/* Assign the new device (leaving the existing one started) */
stdio_devices[file] = dev;
/*
* Update monitor functions
* (to use the console stuff by other applications)
*/
switch (file) {
case stdin:
gd->jt->getc = getc;
...
break;
case stdout:
...
gd->jt->printf = printf;
break;
}
break;
default: /* Invalid file ID */
error = -1;
}
return error;
}
首先尝试启动入口参数中的stdio设备。需要注意的是,在前面"stdio_add_devices"一节stdio设备的注册中,只是填充了相关的结构体,如果其后没有被使用(如serial就曾被使用了),就还未真正实际启动被注册的设备。而这里,为console分配stdio设备时,就要启动它(实际是初始化该硬件设备),因为接下就要使用该硬件完成stdio实际的硬件输入输出操作。上述dev->start代码中,一旦启动失败(有可能已启动,或硬件自身的原因),函数console_setfile就立即返回,返回值为0。console_setfile的上层函数也返回0,这样就回到代码段2.2,但接下来还是会填充cons_set,但不会在函数console_setfile中接着填充下面的stdio_devices。
程序最后将更新gd->jt函数列表。
代码段2.3
该段代码主要实现:
将上面代码段查找到的设备存储到全局变量console_devices[console]中,其设备个数存储到全局变量cd_count[console]中。
这里的console即stdin,stdout,stderr常量之一。当这三者之一拥有多个stdio设备时,console_devices[console]会保存这多个设备,且用cd_count[console]来记录设备个数。如环境变量stdout的值为serial,vga,那么console_devices[1]指向的struct stdio_dev结构体指针数组中会包含两个指针,分别指向serial和vga设备对应的结构体地址。
cd_count[1]为console_devices[1]指向的数组的长度,这里值为2。
我们可以在最终的输出函数console_puts实现中看到console_devices和cd_count的使用:
static void console_puts(int file, const char *s)
{
int i;
struct stdio_dev *dev;
for (i = 0; i < cd_count[file]; i++) {
dev = console_devices[file][i];
if (dev->puts != NULL)
dev->puts(dev, s);
}
}
函数iomux_doenv总结:
static struct stdio_dev *tstcdev;
struct stdio_dev **console_devices[MAX_FILES];
int cd_count[MAX_FILES];
其中的MAX_FILES在 include/common.h中定义为3,即stdin,stdout,stderr。
if (outputdev == NULL) {
outputdev = search_device(DEV_FLAGS_OUTPUT, "serial");
}
search_device函数执行设备查找,其输入参数DEV_FLAGS_OUTPUT为stdin,stdout,stderr对应的三者之一。
struct stdio_dev *search_device(int flags, const char *name)
{
struct stdio_dev *dev;
dev = stdio_get_by_name(name);
if (dev && (dev->flags & flags))
return dev;
return ((void *)0);
}
函数stdio_get_by_name在common/stdio.c中实现:
struct stdio_dev* stdio_get_by_name(const char *name)
{
struct list_head *pos;
struct stdio_dev *dev;
if(!name)
return NULL;
list_for_each(pos, &(devs.list)) {
dev = list_entry(pos, struct stdio_dev, list);
if(strcmp(dev->name, name) == 0)
return dev;
}
return NULL;
}
在前面"stdio_add_devices"函数讨论的一节中,所有注册的stdio设备使用全局变量devs.list链表串接起来,stdio_get_by_name函数就是在此链表中查找名字为涵参name的stdio设备。我们继续跟踪list_entry,可以看到其定义为:
#define list_entry(ptr, type, member) \
container_of(ptr, type, member)
container_of和linux驱动中用法一致,也是通过结构体成员来查找结构体自身的首址。我们在前面的"stdio_add_devices" 一节中也曾经提及过,devs.list的链表成员只是struct stdio_dev结构体的成员变量list,而非struct stdio_dev结构体变量本身。所以要使用container_of查找描述stdio设备的struct stdio_dev变量自身。stdio_get_by_name执行完返回到search_device后,如果找到设备,还会核对查找到的设备属性标志是否和输入参数的标志一致,设备属性标志在该设备注册时设置。
static inline void console_doenv(int file, struct stdio_dev *dev)
{
console_setfile(file, dev);
}
函数console_setfile将上述查找到的设备最终存储在全局变量stdio_devices中。这我们在上面代码段2.2已经讨论过。最终将默认的serial设备赋值到stdio_devices中去。
gd->flags |= GD_FLG_DEVINIT; /* device initialization completed */
print_pre_console_buffer(PRE_CONSOLE_FLUSHPOINT2_EVERYTHING_BUT_SERIAL);
由于CONFIG_PRE_CONSOLE_BUFFER没有定义,print_pre_console_buffer为空函数。
void puts(const char *s)
{
...
if(!gd->have_console)
return pre_console_puts(s);
if (gd->flags & GD_FLG_DEVINIT) {
/* Send to the standard output */
fputs(stdout, s);
} else {
/* Send directly to the handler */
pre_console_puts(s);
serial_puts(s);
}
}
上述代码中,如果gd->flags & GD_FLG_DEVINIT为真时,将使用fputs执行信息输出,fputs定义为:
void fputs(int file,constchar*s)
{
if (file < MAX_FILES)
console_puts(file, s);
}
函数console_puts我们在代码段2.2中粗略提及过,其具体实现如下:
static void console_puts(int file, const char *s)
{
int i;
struct stdio_dev *dev;
for (i = 0; i < cd_count[file]; i++) {
dev = console_devices[file][i];
if (dev->puts != NULL)
dev->puts(dev, s);
}
}
可见,gd->flags & GD_FLG_DEVINIT为真时,最终将使用console_devices中注册过的控制台函数执行相关操作。也即是,执行了代表gd->flags |= GD_FLG_DEVINIT后,gd->flags & GD_FLG_DEVINIT为真 ,代表console控制台中的相关操作函数可用了。否则使用默认的串口输出函数serial_puts。
考虑到这样一种情况,我们在上述代码段5的语句
gd->flags |= GD_FLG_DEVINIT;
之前的printf输出信息,将会使用默认的串口输出函数serial_puts,该函数在board_f阶段被注册且其后续可用。而该代码段之后的程序,所使用的printf,都将使用该节讨论的console控制台输出函数。
gd->flags |= GD_FLG_DEVINIT语句制造了一个这样的分水岭。
那么在stdio和serial结构图的基础上,加上console,三者之间的结构总图如下: