am335x linux 的uboot工作流程.doc,ARM板移植Linux系统启动(三)UBOOT移植

本文详细介绍了U-Boot从简单的加载器演变为成熟系统的历程,以及其代码结构。U-Boot的代码组织清晰,移植过程中主要关注board目录下的平台定制代码和配置文件。启动流程涉及board_init_f()和board_init_r()函数,前者侧重硬件初始化,后者关注软件初始化。主要的修改通常在配置文件和特定平台的board.c中进行,如内存初始化、硬件配置等。此外,启动方式和命令行交互也是移植的重要环节。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

经过这些年的演变,U-boot已经从一个简单的loader慢慢发展成了一个小小系统,硬件上原生支持各种平台、外设,软件上支持Fat&ext234等文件系统、传输协议、测试工具,可以说相当完善了。这种进化,负面影响是让整体变得更复杂臃肿,但也有更多的正面影响,是让我们的移植工作更加简单、模块化。

Uboot代码结构

代码结构是了解uboot的基础,作为一个成熟的开源软件,uboot的代码结构很清晰。绝大多数的代码都是通用的,所以在移植过程中,基本上只需要找到自己的对应平台,修改板子特有的部分即可。你看到的是非授权版本!爬虫凶猛,请尊重知识产权!

转载请注明出处:http://conanwhf.github.io/2017/06/09/bootup-3-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。具体改什么、怎么改,要根据情况分析而定。

参考资源链接:[UBoot移植全面解析:支持多种嵌入式系统与处理器](https://wenku.csdn.net/doc/1iou6790v3?utm_source=wenku_answer2doc_content) 要将UBoot移植到基于ARM处理器的嵌入式Linux系统,你需要遵循一系列细致的步骤来确保移植的顺利进行。《UBoot移植全面解析:支持多种嵌入式系统与处理器》这本书为你提供了详尽的指南,将帮助你理解并实践整个移植过程。 首先,进行环境搭建,安装交叉编译工具链,如arm-linux-gnueabi或arm-linux-gnueabihf,并设置好相关的环境变量,例如PATH。这一步骤确保了你能在PC上编译适用于ARM平台的代码。 接下来,获取UBoot的最新源码,可以从其官方网站或者使用版本控制系统克隆代码仓库。获得源码后,需要根据你的目标硬件平台进行配置。可以通过make menuconfig或者make xconfig命令进入配置界面,选择你的ARM处理器型号、对应的级支持包(BSP)以及其他必要的配置选项。 之后,使用make命令开始编译过程。如果你的系统配置正确,这个步骤将生成针对你的ARM平台的UBoot二进制文件。这个文件是嵌入式Linux系统启动的关键。 编译完成后,需要将生成的UBoot二进制文件烧录到目标系统的存储介质中,如NAND Flash或者SD卡。这一步通常需要使用到特定的烧录工具,例如uboot-mkimage,或者通过JTAG等硬件接口工具。 最后,验证移植是否成功。重启目标硬件,检查UBoot是否能够正常引导,并且能够加载嵌入式Linux内核。这个过程可能需要通过串口来监控输出信息,并确保所有的硬件初始化顺序正确。 在整个过程中,遇到问题进行调试是常见的。你需要根据UBoot启动日志和错误信息,调整配置或者修改源码,以解决兼容性和功能上的问题。 通过《UBoot移植全面解析:支持多种嵌入式系统与处理器》提供的实战指导,你可以系统地掌握UBoot移植技术,并将其成功应用到基于ARM处理器的嵌入式Linux系统中。这本书不仅覆盖了移植流程的每个步骤,还提供了深入的技术讨论和案例分析,是解决UBoot移植问题的宝贵资源。 参考资源链接:[UBoot移植全面解析:支持多种嵌入式系统与处理器](https://wenku.csdn.net/doc/1iou6790v3?utm_source=wenku_answer2doc_content)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值