经过这些年的演变,U-boot已经从一个简单的loader慢慢发展成了一个小小系统,硬件上原生支持各种平台、外设,软件上支持Fat&ext234等文件系统、传输协议、测试工具,可以说相当完善了。这种进化,负面影响是让整体变得更复杂臃肿,但也有更多的正面影响,是让我们的移植工作更加简单、模块化。
Uboot代码结构
代码结构是了解uboot的基础,作为一个成熟的开源软件,uboot的代码结构很清晰。绝大多数的代码都是通用的,所以在移植过程中,基本上只需要找到自己的对应平台,修改板子特有的部分即可。
你看到的是非授权版本!爬虫凶猛,请尊重知识产权!
转载请注明出处:http://conanwhf.github.io/2017/06/09/bootup-3-uboot/
访问原文「ARM板移植Linux系统启动(三)UBOOT移植」获取最佳阅读体验并参与讨论
├── api uboot提供的API接口
├── arch 与体系结构相关的代码
│ ├── arm
│ │ ├── mach-omap2 BeagleBoneBlack使用的架构
│ ├── x86
├── board 根据不同的具体开发板而定制的代码
│ ├── ti 厂商:TI
│ │ ├── am335x BeagleBoneBlack使用的平台,主要的改动应该在这里,包括内存初始化
│ │ ├── beagle
│ │ ├── common
│ │ ├── evm
├── cmd 通用命令行处理工具
├── common 通用核心代码,主要给uboot使用,部分SPL
│ ├── spl SPL的通用代码
├── configs 配置文件
├── disk 磁盘分区相关
├── doc 文档
├── drivers 各种驱动,uboot使用
├── dts dtb文件的编译脚本
├── examples 范例
├── fs 文件系统代码
├── include 公用头文件
│ ├── configs 不同板子配置选项的头文件
├── lib 通用库
├── net 网络相关
├── post Power On Self Test,开机自检程序
├── scripts 编译脚本
├── test 测试程序
├── tools 相关小工具
如果是想要重新编译和优化uboot,那么通常修改配置文件即可;如果是一块硬件上有修改的板子,那么则需要另外关注board/XXX/部分,基于原有的初始化代码进行修改;如果是完全新设计的芯片,则必须根据芯片不同模块的datasheet自己添加一套初始化代码,只有arm平台的一些基本内容可以通用了。
启动流程
前一篇讲SPL的启动的时候提到,最初从start.S
call到了crt0.S
。crt0.S
会先后调用board_init_f()
和board_init_r()
两个函数,当作为SPL编译时board_init_f()
是直接返回的,而在uboot时是有用的。大致上来说,board_init_f()
侧重于基础硬件的初始化,以及软件堆栈等方面的准备,更底层一点;而board_init_r()
则更多的是软件方面的初始化,并且最终完成uboot启动过程或进入命令行。
common/board_f.c
这是uboot(实际上是uboot第二阶段,u-boot.img所包含的内容,下文不再区分)的入口函数board_init_f()
所在的文件。可以看到函数board_init_f()
很简单,初始化全局变量后,就根据一个init_sequence_f
依次运行不同的函数,失败则把系统hang住,成功当然就是进入正常的启动流程中去了。init_sequence_f
在同一个文件中也有定义,很长,我删减无关平台的内容大致看看:
static init_fnc_t init_sequence_f[] = {
//==========基本数据init,malloc数据块
setup_mon_len,
#ifdef CONFIG_OF_CONTROL
fdtdec_setup,
#endif
#ifdef CONFIG_TRACE
trace_early_init,
#endif
initf_malloc,
initf_console_record,
//初始化CPU
arch_cpu_init, /* basic arch cpu dependent setup */
mach_cpu_init, /* SoC/machine dependent CPU 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,
#endif
//==========Timer
#if defined(CONFIG_ARM) || defined(CONFIG_MIPS) || \
defined(CONFIG_BLACKFIN) || defined(CONFIG_NDS32) || \
defined(CONFIG_SH) || defined(CONFIG_SPARC)
timer_init, /* initialize timer */
#endif
//==========初始化环境变量
env_init, /* initialize environment */
//==========初始化串口,并打印信息
init_baud_rate, /* initialze baudrate settings */
serial_init, /* serial communications setup */
console_init_f, /* stage 1 init of console */
display_options, /* say that we are here */
display_text_info, /* show debugging info if required */
print_cpuinfo, /* display cpu info (and speed) */
#if defined(CONFIG_DISPLAY_BOARDINFO)
show_board_info,
#endif
//==========基本外设初始化,包括I2C,SPI,WatchDog等
INIT_FUNC_WATCHDOG_INIT
#if defined(CONFIG_MISC_INIT_F)
misc_init_f,
#endif
INIT_FUNC_WATCHDOG_RESET
#if defined(CONFIG_HARD_I2C) || defined(CONFIG_SYS_I2C)
init_func_i2c,
#endif
#if defined(CONFIG_HARD_SPI)
init_func_spi,
#endif
//==========内存初始化
announce_dram_init,
/* TODO: unify all these dram functions? */
#if defined(CONFIG_ARM) || defined(CONFIG_X86) || defined(CONFIG_NDS32) || \
defined(CONFIG_MICROBLAZE) || defined(CONFIG_AVR32) || \
defined(CONFIG_SH)
dram_init, /* configure available RAM banks */
#endif
INIT_FUNC_WATCHDOG_RESET
#if defined(CONFIG_SYS_DRAM_TEST)
testdram,
#endif /* CONFIG_SYS_DRAM_TEST */
INIT_FUNC_WATCHDOG_RESET
//==========一堆显示相关的初始化,略
/*
* 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_machine,
reserve_global_data,
reserve_fdt,
reserve_arch,
reserve_stacks,
setup_dram_config,
show_dram_config,
display_new_sp,
#ifdef CONFIG_SYS_EXTBDINFO
setup_board_extra,
#endif
INIT_FUNC_WATCHDOG_RESET
reloc_fdt,
setup_reloc,
NULL,
};
common/board_r.c
类似的,在board_init_r()
中也会按顺序调用这么一个函数列表,函数名都很容易看懂,具体就不列出来了。跟board_init_f()
不同的是,它最后有一个run_main_loop
:
init_fnc_t init_sequence_r[] = {
initr_trace,
initr_reloc,
......
run_main_loop,
};
这个函数会进入一个死循环,尝试自动启动系统或者进入uboot的命令行:
static int run_main_loop(void)
{
ifdef CONFIG_SANDBOX
sandbox_main_loop_init();
endif
/* main_loop() can return to retry autoboot, if so just run it again */
for (;;)
main_loop();
return 0;
}
common/main.c
函数main_loop()
的实现在main.c,可以看到,做好最后的准备工作后,uboot会尝试用autoboot_command(s)
来启动kernel,如果失败,则进入cli_loop()
函数中。
void main_loop(void)
{
const char *s;
bootstage_mark_name(BOOTSTAGE_ID_MAIN_LOOP, "main_loop");
#ifdef CONFIG_VERSION_VARIABLE
setenv("ver", version_string); /* set version variable */
#endif /* CONFIG_VERSION_VARIABLE */
cli_init();
run_preboot_environment_command();
#if defined(CONFIG_UPDATE_TFTP)
update_tftp(0UL, NULL, NULL);
#endif /* CONFIG_UPDATE_TFTP */
s = bootdelay_process();
if (cli_process_fdt(&s))
cli_secure_boot_cmd(s);
autoboot_command(s);
cli_loop();
panic("No CLI available");
}
cli_loop()
这个函数比较简单,追踪code可以看到,common/cli.c, cli_loop()->common/cli_simple.c, cli_simple_loop()
, cli_simple_loop()
就是uboot的命令行了,不断读取用户输入来做出反应,同样也是个死循环。所以绝大部分的时候,main_loop()
中的循环并不会进行多次,而是一旦自动启动失败就进入了命令行,不会再次自动尝试启动。
修改移植
一般简单的板子移植,主要是内存初始化代码,和硬件配置选项,以及启动变量。硬件的驱动是不需要自己写的,但要配置准确。这些修改主要集中在两个地方:配置头文件和平台对应的board.c。以BeagleBoneBlack为例,它的特别内容都在以下两个文件中:
board/ti/am335x/board.c
include/configs/am335x_evm.h
这是编译时脚本根据config中CONFIG_TARGET_AM335X_EVM=y
来对应的,不同的平台文件会有所不同,但都可以通过查看Makefile和Kconfig来找到正确的文件。下文均以BeagleBoneBlack为例。
启动方式
当uboot的启动流程进入到autoboot,其历史使命基本上也就完成了。但对于一个移植的板子来说,配置自动启动的选项却是一个很重要的问题。启动方式的列表定义在在am335x_evm.h
中:
define BOOT_TARGET_DEVICES(func) \
func(MMC, mmc, 0) \
func(LEGACY_MMC, legacy_mmc, 0) \
func(MMC, mmc, 1) \
func(LEGACY_MMC, legacy_mmc, 1) \
func(NAND, nand, 0) \
func(PXE, pxe, na) \
func(DHCP, dhcp, na)
endif
很容易看到这个启动方式是emmc,nand,PXE,DHCP,可以自己按照需要调整顺序或者添删。
内存初始化
在board/ti/am335x/board.c
中的函数sdram_init()
就是内存的初始化函数,它会根据EEPROM得到的板子型号,对应不同的DRAM配置。阅读代码不难发现,配置具体信息是定义在arch/arm/include/asm/arch-am33xx/ddr_defs.h
。如果需要修改或者添加不同的内存配置,修改这两个文件即可。
其他硬件调整
绝大多数硬件的配置定义都在am335x_evm.h
中,包括各种模块的Base address、初始化值、硬件定义等等,读者可以自行阅读。这些设置有的是根据芯片的手册而定,比如说memory的初始化值;有的是根据硬件设计而定,比如USB的Host & PERIPHERAL设置;有的是可以根据需要自行决定,比如CONFIG_SYS_BOOTM_LEN
。具体改什么、怎么改,要根据情况分析而定。