Uboot启动流程分析(三)
1、前言
在前面的文章《Uboot启动流程分析(二)》中,链接如下:
https://www.cnblogs.com/Cqlismy/p/12002764.html
已经对_main函数的整个大体调用流程,以及函数的实现的各个功能进行了简单地分析,接下来,本篇文章将对board_init_f函数进行分析,在此之前,先来回顾一下_main函数的简单调用流程,如下所示:
_main | board_init_f_alloc_reserve-->reserve gd and early malloc area | board_init_f_init_reserve-->initialize global data | board_init_f-->initialize ddr,timer...,and fill gd_t | relocate_code-->relocate uboot code | relocate_vectors-->relocate vectors | board_init_r-->calling board_init_r
2、board_init_f函数
接下来,将对board_init_f()函数进行分析,该函数的定义在文件
uboot-imx-rel_imx_4.1.15/common/board_f.c
该函数的实现源码(忽略无关宏)如下所示:
/* 该函数在_main函数中进行调用 */ void board_init_f(ulong boot_flags) { gd->flags = boot_flags; /* 启动标志 */ gd->have_console = 0; /* 是否具有console标志 */ if (initcall_run_list(init_sequence_f)) hang(); }
该函数调用后,首先会设置gd->flags这个标志位,然后将gd结构体中的have_console成员变量设置为0,表示此时并没有console,该函数的重点在if这个判断语句,将会调用initcall_run_list,并传入init_sequence_f,也就是初始化序列,接下来,重点关注init_sequence_f这个初始化序列。
init_sequence_f包含了一系列的初始化函数,定义同样在board_f.c文件中,它包含了大量的条件编译的相关函数,去掉一些无关的条件编译后,init_sequence_f的定义如下:
/* 初始化序列函数数组 */ static init_fnc_t init_sequence_f[] = { setup_mon_len, /* 设置gd的mon_len成员,表示uboot代码的长度 */ initf_malloc, /* 初始化gd中与malloc相关的成员 */ initf_console_record, /* 初始化arm架构相关的东西 */ arch_cpu_init, /* basic arch cpu dependent setup */ initf_dm, /* 初始化驱动模型相关 */ arch_cpu_init_dm, mark_bootstage, /* need timer, go after init dm */
#if defined(CONFIG_BOARD_EARLY_INIT_F) board_early_init_f, /* 与板子的早期外设初始化,imx6ul用来初始化串口 */ #endif #if defined(CONFIG_ARM) || defined(CONFIG_MIPS) || \ defined(CONFIG_BLACKFIN) || defined(CONFIG_NDS32) || \ defined(CONFIG_SPARC) timer_init, /* initialize timer */ /* 初始化Cortex-A7中的定时器 */ #endif #if defined(CONFIG_SYS_FSL_CLK) || defined(CONFIG_M68K) get_clocks, /* 获取时钟值 */ #endif /* 和环境变量有关,设置gd的env_addr成员 */ env_init, /* initialize environment */ init_baud_rate, /* initialze baudrate settings */ /* 初始化串口波特率 */ serial_init, /* serial communications setup */ /* 初始化串口 */ console_init_f, /* stage 1 init of console */ /* 设置console标志位 */ display_options, /* say that we are here */ /* 显示uboot版本和编译时间字符串 */ display_text_info, /* show debugging info if required */ /* 显示cpu的相关信息 */ print_cpuinfo, /* display cpu info (and speed) */ #if defined(CONFIG_DISPLAY_BOARDINFO) show_board_info, /* 显示board的相关信息 */ #endif INIT_FUNC_WATCHDOG_INIT INIT_FUNC_WATCHDOG_RESET #if defined(CONFIG_HARD_I2C) || defined(CONFIG_SYS_I2C) init_func_i2c, /* 初始化i2c接口(实际初始化的是SPD_BUS) */ #endif #if defined(CONFIG_HARD_SPI) init_func_spi, /* i.mx6ul并没有初始化SPI接口 */ #endif
announce_dram_init, /* 输出"DRAM:"字符串 */
#if defined(CONFIG_ARM) || defined(CONFIG_X86) || defined(CONFIG_NDS32) || \ defined(CONFIG_MICROBLAZE) || defined(CONFIG_AVR32) dram_init, /* configure available RAM banks */ /* 获取DDR的大小 */ #endif
/* * Now that we have DRAM mapped and working, we can * relocate the code and continue running from DRAM. * * Reserve memory at end of RAM for (top down in that order): * - area that won't get touched by U-Boot and Linux (optional) * - kernel log buffer * - protected RAM * - LCD framebuffer * - monitor code * - board info struct */ setup_dest_addr,/* 设置目的地址(gd->ram_size,gd->ram_top,gd->relocaddr) */ reserve_round_4k, /* 对gd->relocaddr做4K对齐 */ #if !(defined(CONFIG_SYS_ICACHE_OFF) && defined(CONFIG_SYS_DCACHE_OFF)) && \ defined(CONFIG_ARM) reserve_mmu, /* 留出mmu的TLB表位置 */ #endif reserve_trace, /* 留出ddr调试追踪的内存 */
#if !defined(CONFIG_BLACKFIN) reserve_uboot, /* 留出重定位uboot占用的位置 */ #endif
#ifndef CONFIG_SPL_BUILD reserve_malloc, /* 留出malloc的内存位置和ENV的内存大小 */ reserve_board, /* 留出bd所占用的内存大小(80字节) */ #endif
setup_machine, /* 对于i.mx6ul该函数无效 */ reserve_global_data, /* 留出gd_t结构的内存大小(248字节) */ reserve_fdt, /* 留出设备树的内存大小(i.mx6ul没有用) */ reserve_arch, /* 空函数 */ reserve_stacks, /* 留出栈空间(16字节)并做16字节对齐 */ setup_dram_config, /* 设置DRAM的信息 */ show_dram_config, /* 显示DRAM的位置 */ display_new_sp, /* 显示新的sp位置 */ INIT_FUNC_WATCHDOG_RESET reloc_fdt, /* 重定位fdt(没有用) */ setup_reloc, /* 设置gd结构体的一些其他成员 */ NULL, };
接下来,对上面的函数进行分析:
首先是setup_mon_len()函数,它的定义(去掉条件编译)如下:
static int setup_mon_len(void) { #if defined(__ARM__) || defined(__MICROBLAZE__) gd->mon_len = (ulong)&__bss_end - (ulong)_start; #endif return 0; }
该函数用来设置gd结构中的mon_len成员变量,该变量用于保存uboot代码的长度。
接下来调用initf_malloc()函数,该函数的定义如下:
int initf_malloc(void) { #ifdef CONFIG_SYS_MALLOC_F_LEN assert(gd->malloc_base); /* Set up by crt0.S */ gd->malloc_limit = CONFIG_SYS_MALLOC_F_LEN; /* malloc的大小0x400 */ gd->malloc_ptr = 0; #endif return 0; }
该函数用来设置gd结构中与malloc相关的变量,对于imx6ul的配置文件中,定义有CONFIG_SYS_MALLOC_F_LEN宏,该宏的值定义为0x400,malloc_limit变量表示malloc的内存大小。
接下来调用initf_console_record()函数,该函数的定义如下:
static int initf_console_record(void) { #if defined(CONFIG_CONSOLE_RECORD) && defined(CONFIG_SYS_MALLOC_F_LEN) return console_record_init(); #else return 0; #endif }
对于该函数,由于imx6ul中没有定义CONFIG_CONSOLE_RECORD宏,所以该函数直接返回0。
接下来调用arch_cpu_init()函数,该函数用与初始化一些与arm架构相关的东西,继续调用initf_dm()函数,初始化一些与驱动模型相关的东西,arch_cpu_init_dm()函数没有实现,继续调用mark_bootstage()函数,该函数用于做某些标志。
接下来,判断CONFIG_BOARD_EARLY_INIT_F宏是否定义,对于imx6ul中的配置文件中,定义了该宏,因此,board_early_init_f()函数将会被调用,该函数的定义如下:
int board_early_init_f(void) { /* board相关的debug串口1引脚初始化 */ setup_iomux_uart(); return 0; }
对于imx6ul,该函数用来对SoC中的串口1引脚初始化,主要是完成相关引脚的复用配置和电气属性配置。
接下来,调用timer_init()函数来初始化ARM内核中的定时器。
继续往下运行,调用get_clocks()函数,该函数用于获取芯片内某些外设时钟。
然后调用env_init()函数,该函数用来初始化gd结构中和env相关的成员,设置gd结构中的env_addr成员,也就是环境变量的保存地址。
接下来,调用init_baud_rate()函数,该函数的定义如下:
/* 将gd中的baudrate成员进行初始化,获取"baudrate"环境变量值 */ static int init_baud_rate(void) { gd->baudrate = getenv_ulong("baudrate", 10, CONFIG_BAUDRATE); return 0; }
该函数用于初始化debug调试串口的波特率,设置gd结构体中的baudrate成员,该值是从"baudrate"环境变量中读取的。
接下来,则是调用serial_init()函数,该函数的定义如下所示:
/* 初始化串口 */ int serial_init(void) { gd->flags |= GD_FLG_SERIAL_READY; return get_current()->start(); }
该函数用于对串口进行初始化,会调用串口驱动设备中注册的start()函数完成初始化。
函数console_init_f()将会设置gd结构体的have_console成员为1,标志则此时已经有了console,也就是上面已经初始化好的串口终端。
接下来,将会调用display_options()函数,函数的定义如下:
int display_options (void) { #if defined(BUILD_TAG) printf ("\n\n%s, Build: %s\n\n", version_string, BUILD_TAG); #else printf ("\n\n%s\n\n", version_string); #endif return 0; }
该函数用来打印输出uboot版本的字符串,类似如下的字符串:
U-Boot 2016.03 (Jan 02 2020 - 20:50:58 +0800)
接下来调用display_text_info()函数,该函数用于显示一些debug调试信息,需要在板子的配置文件中定义宏DEBUG,才会开启uboot的调试信息输出。
函数继续往下运行,则会调用print_cpuinfo()函数,该函数输出板子上的CPU的相关信息,输出类似如下:
CPU: Freescale i.MX6UL rev1.0 528 MHz (running at 396 MHz) CPU: Industrial temperature grade (-40C to 105C) at 37C Reset cause: WDOG
由于在板子的配置文件中,定义了宏CONFIG_DISPLAY_BOARDINFO,因此show_board_info()函数将会被调用,该函数用于显示板子的相关信息,类似如下:
Board: MX6UL 14x14 COMP6UL
接下来,调用init_func_i2c()函数用于初始化I2C接口,函数定义如下:
#if defined(CONFIG_HARD_I2C) || defined(CONFIG_SYS_I2C) static int init_func_i2c(void) { puts("I2C: "); #ifdef CONFIG_SYS_I2C i2c_init_all(); /* 对所有i2c接口进行初始化 */ #else i2c_init(CONFIG_SYS_I2C_SPEED, CONFIG_SYS_I2C_SLAVE); #endif puts("ready\n"); return 0; } #endif
函数调用完成后,将会在终端输出下面字符串:
I2C: ready
程序继续往下运行,调用announce_dram_init()函数,该函数比较简单,只是输出"DRAM: "字符串。
接下来,调用函数dram_init(),该函数并没有初始化DRAM,只是设置gd结构体中ram_size成员变量,也就是DRAM的大小,在comp6ul核心板中,DRAM的大小为256MB。
程序继续运行后,就是重点内容,后半部分留到下篇文章介绍,该函数的后半部分,将完成DRAM的内存分配,以及对gd结构的一些成员进行填充初始化。
3、小结
本篇文章主要是对board_init_f函数的前半部分进行了简要分析,在init_sequence_f函数初始化序列的前半部分,主要完成的功能有debug调试串口的初始化,以及一些调试输出。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
· 浏览器原生「磁吸」效果!Anchor Positioning 锚点定位神器解析
· DeepSeek 开源周回顾「GitHub 热点速览」
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· AI与.NET技术实操系列(二):开始使用ML.NET
· 单线程的Redis速度为什么快?