转载地址:http://blog.csdn.net/gujintong1110/article/details/48683501
这里我们来分析下uboot串口设备初始化和串口stdio标准输入输出设备的初始化过程。
一、display_banner与print_cpuinfo
uboot从启动开始,第一句打印就是调用display_banner,打印出我们期待已久的“U-Boot 2012.10-xxxx”信息。接着调用print_cpuinfo打印出cpu信息。
const char version_string[] = U_BOOT_VERSION" (" U_BOOT_DATE " - " U_BOOT_TIME ")"CONFIG_IDENT_STRING;
static int display_banner (void)
{
printf ("\n\n%s\n\n", version_string);
debug ("U-Boot code: %08lX -> %08lX BSS: -> %08lX\n",
_armboot_start, _bss_start, _bss_end);
……
return (0);
}
二、Uboot是如何初始化串口的呢?
Uboot第二阶段入口函数board_init_f通过init_sequence初始化数组调用了5个串口相关的函数:
init_baudrate, /* initialze baudrate settings */
serial_init, /* serial communications setup */
console_init_f, /* stage 1 init of console */
display_banner, /* say that we are here */
#if defined(CONFIG_DISPLAY_CPUINFO)
print_cpuinfo, /* display cpu info (and speed) */
- 函数分别作了以下工作:
1.首先获取波特率设置。这里把获取到的波特率值存入gd->baudrate在串口初始化时候会用到。
static int init_baudrate(void)
{
int ret;
ret = early_access_env_vars();
/* If the above call succeeded, gd's baudrate gets initialized within the
* call
*/
if (ret == -1) {
gd->baudrate = getenv_ulong("baudrate", 10, CONFIG_BAUDRATE);
}
return 0;
}
2.串口初始化,serial_init函数定义在drivers/serial目录下,此目录下有各种各样的串口驱动,makefile会通过之前uboot的配置去编译指定的串口驱动。串口驱动中包含了
serial_init, serial_getc, serial_putc, serial_puts, serial_tstc, serial_setbrg的底层API函数。串口打印和标准输入输出的功能都依赖于这些底层函数实现。这一步完成之后,我们的串口就可以用了。可以用putc,puts,getc了。这里我们不在分析具体CPU平台的串口驱动代码。详细可参见具体处理器的串口数据手册。
3.console_init_f这个函数的作用是把在串口还没有初始化之前的puts或putc的打印内容给打印出来。换句话说在串口没初始化之前也能调用putc,puts函数。只是要等到串口初始化成功之后才会有打印输出。
console_init_f()--> print_pre_console_buffer()
static void print_pre_console_buffer(void)
{
unsigned long i = 0;
char *buffer = (char *)CONFIG_PRE_CON_BUF_ADDR;
if (gd->precon_buf_idx > CONFIG_PRE_CON_BUF_SZ)
i = gd->precon_buf_idx - CONFIG_PRE_CON_BUF_SZ;
while (i < gd->precon_buf_idx)
putc(buffer[CIRC_BUF_IDX(i++)]);
}
这里我们看到在内存地址CONFIG_PRE_CON_BUF_ADDR处有一个长度为CONFIG_PRE_CON_BUF_SZ的内存区域。用于在串口没有使能之前,putc的内容暂存于这个数组内,以便等串口使能后一起打印输出。
我们来看putc源码实现:
void putc(const char c)
{
#ifdef CONFIG_SILENT_CONSOLE
if (gd->flags & GD_FLG_SILENT)
return;
#endif
#ifdef CONFIG_DISABLE_CONSOLE
if (gd->flags & GD_FLG_DISABLE_CONSOLE)
return;
#endif
if (!gd->have_console)
return pre_console_putc(c);
if (gd->flags & GD_FLG_DEVINIT) {
/* Send to the standard output */
fputc(stdout, c);
} else {
/* Send directly to the handler */
serial_putc(c);
}
}
如果!gd->have_console,即串口还未初始化,则调用pre_console_putc(c)函数,这个函数直接把要打印的字符保存到地址CONFIG_PRE_CON_BUF_ADDR的buf中去。
如果串口已经使能,则又要判断gd->flags & GD_FLG_DEVINIT看标准输入输出设备(stdio)是否初始化,如果stdio初始化成功则会置GD_FLG_DEVINIT标志位,这时候就调用fputc(stdout, c);送给stdio处理,否则,直接调用串口底层API函数serial_putc(c)输出。
其它函数如Puts, fputs,fputc, getc,tstc等函数是实现可参见putc的实现方式。
4.这个时候串口输入输出已经OK。我们来分析下printf的实现。
int printf(const char *fmt, ...)
{
va_list args;
uint i;
char printbuffer[CONFIG_SYS_PBSIZE];
#ifndef CONFIG_PRE_CONSOLE_BUFFER
if (!gd->have_console)
return 0;
#endif
va_start(args, fmt);
/* For this to work, printbuffer must be larger than
* anything we ever want to print.
*/
i = vscnprintf(printbuffer, sizeof(printbuffer), fmt, args);
va_end(args);
/* Print the string */
puts(printbuffer);
return i;
}
我们看到printf是调用puts来输出到串口的。如果在串口初始化完成之前调用printf是可以的。如果配置了CONFIG_PRE_CONSOLE_BUFFER,则会保存到CONFIG_PRE_CON_BUF_ADDR内存中,如果没有配置,则不处理。
三、stdio设备初始化
board_init_r—> serial_initialize, stdio_init, console_init_r
接下来board_init_r会调用这三个函数去创建stdio。
首先serial_initialize会注册串口设备。注册实际上就是把之前串口初始化函数挂载到设备结构体指针。
接着调用stdio_init,它会调用serial_stdio_init来创建一个stdio_dev设备结构体。
stdio_register(&dev);是注册设备到一个链表中。这样,一个串口stdio设备就OK了。
调用过程:Stdio_init—-> serial_stdio_init—-> stdio_register。
void serial_stdio_init(void)
{
struct stdio_dev dev;
struct serial_device *s = serial_devices;
while (s) {
memset(&dev, 0, sizeof(dev));
strcpy(dev.name, s->name);
dev.flags = DEV_FLAGS_OUTPUT | DEV_FLAGS_INPUT;
dev.start = s->init;
dev.stop = s->uninit;
dev.putc = s->putc;
dev.puts = s->puts;
dev.getc = s->getc;
dev.tstc = s->tstc;
stdio_register(&dev);
s = s->next;
}
}
接下来调用 console_init_r把上面创建好的stdio设备对接文件描述符的标准输入、输出、错误。
int console_init_r(void)
{
struct stdio_dev *inputdev = NULL, *outputdev = NULL;
int i;
struct list_head *list = stdio_get_list();
struct list_head *pos;
struct stdio_dev *dev;
#ifdef CONFIG_SPLASH_SCREEN
/*
* suppress all output if splash screen is enabled and we have
* a bmp to display. We redirect the output from frame buffer
* console to serial console in this case or suppress it if
* "silent" mode was requested.
*/
if (getenv("splashimage") != NULL) {
if (!(gd->flags & GD_FLG_SILENT))
outputdev = search_device (DEV_FLAGS_OUTPUT, "serial");
}
#endif
/* Scan devices looking for input and output devices */
list_for_each(pos, list) {
dev = list_entry(pos, struct stdio_dev, list);
if ((dev->flags & DEV_FLAGS_INPUT) && (inputdev == NULL)) {
inputdev = dev;
}
if ((dev->flags & DEV_FLAGS_OUTPUT) && (outputdev == NULL)) {
outputdev = dev;
}
if(inputdev && outputdev)
break;
}
/* Initializes output console first */
if (outputdev != NULL) {
console_setfile(stdout, outputdev);
console_setfile(stderr, outputdev);
#ifdef CONFIG_CONSOLE_MUX
console_devices[stdout][0] = outputdev;
console_devices[stderr][0] = outputdev;
#endif
}
/* Initializes input console */
if (inputdev != NULL) {
console_setfile(stdin, inputdev);
#ifdef CONFIG_CONSOLE_MUX
console_devices[stdin][0] = inputdev;
#endif
}
gd->flags |= GD_FLG_DEVINIT; /* device initialization completed */
stdio_print_current_devices();
/* Setting environment variables */
for (i = 0; i < 3; i++) {
setenv(stdio_names[i], stdio_devices[i]->name);
}
return 0;
}
Stdio初始化完毕之后,通过stdio_print_current_devices函数打印出stdio设备名。
void stdio_print_current_devices(void)
{
#ifndef CONFIG_SYS_CONSOLE_INFO_QUIET
/* Print information */
puts("In: ");
if (stdio_devices[stdin] == NULL) {
puts("No input devices available!\n");
} else {
printf ("%s\n", stdio_devices[stdin]->name);
}
puts("Out: ");
if (stdio_devices[stdout] == NULL) {
puts("No output devices available!\n");
} else {
printf ("%s\n", stdio_devices[stdout]->name);
}
puts("Err: ");
if (stdio_devices[stderr] == NULL) {
puts("No error devices available!\n");
} else {
printf ("%s\n", stdio_devices[stderr]->name);
}
#endif /* CONFIG_SYS_CONSOLE_INFO_QUIET */
}
void fputc(int file, const char c)
{
if (file < MAX_FILES)
console_putc(file, c);
}
static inline void console_putc(int file, const char c)
{
stdio_devices[file]->putc(c);
}
至此,UBOOT串口串口和stdio初始化完毕。