环境
U-Boot版本:官方2023.04
开发板:ZYNQ 7000 ZC702Kit Rev1.1
初始化过程
1. 前期工作
从汇编代码启动后一路配置到 board_init_f
,首先对全局变量的两个参数清零,然后开始执行 init_sequence_f
列出的有效函数开始一系列功能初始化。
// crt0.S
mov r0, #0
bl board_init_f
// board_f.c
void board_init_f(ulong boot_flags)
{
gd->flags = boot_flags;
gd->have_console = 0;
if (initcall_run_list(init_sequence_f))
hang();
}
2 _f
阶段串口初始化过程
完成串口初始化主要有三个函数 init_baud_rate,serial_init,console_init_f
。
在此之前已经从列表顺序里执行了一些初始化,最重要的是 initf_dm
完成了设备驱动的初始化。
2.1 init_baud_rate
static int init_baud_rate(void)
{
gd->baudrate = env_get_ulong("baudrate", 10, CONFIG_BAUDRATE);
return 0;
}
在全局变量中设置串口的波特率。
2.2 serial_init
int serial_init(void)
{
#if CONFIG_IS_ENABLED(SERIAL_PRESENT)
serial_find_console_or_panic();
gd->flags |= GD_FLG_SERIAL_READY;
serial_setbrg();
#endif
return 0;
}
static void serial_find_console_or_panic(void)
{
...
if (!serial_check_stdout(blob, &dev)) {
gd->cur_serial_dev = dev;
return;
}
...
}
先执行 serial_find_console_or_panic
根据设备树定义的输出设备从 uclass 列表里查找对应设备,在全局变量中保存查找到的设备。
修改全局变量的 flag
参数标记串口设备已经准备完成,并且将串口波特率设置为全局变量中的 baudrate
设定值。
2.3 console_init_f
// .config
# CONFIG_PRE_CONSOLE_BUFFER is not set
# CONFIG_SILENT_CONSOLE is not set
// console.c
#if CONFIG_IS_ENABLED(PRE_CONSOLE_BUFFER)
...
#else
static inline void pre_console_putc(const char c) {}
static inline void pre_console_puts(const char *s) {}
static inline void print_pre_console_buffer(int flushpoint) {}
#endif
static bool console_update_silent(void)
{
unsigned long flags = gd->flags;
if (!IS_ENABLED(CONFIG_SILENT_CONSOLE))
return false;
...
}
int console_init_f(void)
{
gd->have_console = 1;
console_update_silent();
print_pre_console_buffer(PRE_CONSOLE_FLUSHPOINT1_SERIAL);
return 0;
}
修改全局变量的 have_console
参数,标记控制台可用。
console_update_silent
没有执行有效功能,因为 CONFIG_SILENT_CONSOLE
未使能。
print_pre_console_buffer
也没有执行有效功能,因为 PRE_CONSOLE_BUFFER
未使能情况下所有的 pre_console
功能都是空的。
所以 console_init_f
实际只是标记控制台的使能。
2.4 _f
阶段串口初始化后的打印
void serial_putc(char ch)
{
if (gd->cur_serial_dev)
_serial_putc(gd->cur_serial_dev, ch);
}
void putc(const char c)
{
if (!gd)
return;
...
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 */
pre_console_putc(c);
serial_putc(c);
}
}
void serial_puts(const char *str)
{
if (gd->cur_serial_dev)
_serial_puts(gd->cur_serial_dev, str);
}
void puts(const char *s)
{
if (!gd)
return;
...
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_FLG_DEVINIT` 还没有被置位,所以实际会执行 `serial_putc` 和 `serial_puts`;通过全局变量保存的串口设备完成打印。
3 _r
阶段串口初始化过程
board_init_f
之后进入 board_init_r
,清除前面置位的 GD_FLG_SERIAL_READY
标记,开始执行 init_sequence_r
列表里的有效函数。这段过程跟串口有关的函数包括 stdio_init_tables,serial_initialize,stdio_add_devices,console_init_r
。
void board_init_r(gd_t *new_gd, ulong dest_addr)
{
gd->flags &= ~(GD_FLG_SERIAL_READY | GD_FLG_LOG_READY);
...
if (initcall_run_list(init_sequence_r))
hang();
/* NOTREACHED - run_main_loop() does not return */
hang();
}
3.1 stdio_init_tables
int stdio_init_tables(void)
{
...
/* Initialize the list */
INIT_LIST_HEAD(&devs.list);
return 0;
}
初始化标准设备列表。
3.2 serial_initialize
int serial_initialize(void)
{
...
return serial_init();
}
再次初始化串口,全局变量置位 GD_FLG_SERIAL_READY
标记。
3.3 stdio_add_devices
int stdio_add_devices(void)
{
...
drv_system_init();
...
}
创建一个标准设备并且注册到标准设备链表,新注册的标准设备数据流依旧是通过全局变量保存的串口设备实现。
3.4 console_init_r
int console_init_r(void)
{
...
/* 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_and_devices(stdout, outputdev);
console_setfile_and_devices(stderr, outputdev);
}
/* Initializes input console */
if (inputdev != NULL)
console_setfile_and_devices(stdin, inputdev);
if (!IS_ENABLED(CONFIG_SYS_CONSOLE_INFO_QUIET))
stdio_print_current_devices();
/* Setting environment variables */
for (i = 0; i < MAX_FILES; i++) {
env_set(stdio_names[i], stdio_devices[i]->name);
}
gd->flags |= GD_FLG_DEVINIT; /* device initialization completed */
print_pre_console_buffer(flushpoint);
return 0;
}
查找串口设备与标准输入输出设备绑定,stdio_print_current_devices
在控制台打印标准输入输出设备信息,最后全局变量置位 GD_FLG_DEVINIT
标记初始化彻底完成。
3.5 _f
阶段串口初始化后的打印
void putc(const char c)
{
if (!gd)
return;
...
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 */
pre_console_putc(c);
serial_putc(c);
}
}
void puts(const char *s)
{
if (!gd)
return;
...
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_FLG_DEVINIT
标记,所以实际会执行 fputc
和 fputs
;通过设置的标准设备输出,实质依旧是定义的串口设备。