Uboot启动过程详解

Posted Jocelin47

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Uboot启动过程详解相关的知识,希望对你有一定的参考价值。

uboot就是将start.o和大量的built-in.o链接在一起。

built-in.o好像是把所有子目录下的.o文件进行链接到一起。

链接脚本为u-boot.lds ,uboot链接首地址为0x87800000,裸机的时候也是-Ttest 来执行链接首地址


查找一下这个链接的地址

grep -nR "87800000"


在mx6_common.h文件中设置

通过uboot-lds可以看到入口地址为_start

1)设置CPU为管理模式
2)关看门狗
3)关中断
4)设置时钟频率
5)关mmu,初始化各个bank

6)进入board_init_f()函数

初始化DDR,定时器,初始化波特率串口,打印前面暂存在缓冲区的数据 此时sp和gd是存放在DDR上了。而不是内部的RAM。

uboot会将自己重定位到 DRAM最后面的地址区域,也就是将自己拷贝到 DRAM最后面的内存区域中。这么做的目的是给Linux腾出空间,防止 Linux kernel覆盖掉 uboot,将 DRAM前面的区域完整的空出来。在拷贝之前肯定要给uboot各部分分配好内存位置和大小,比如 gd应该存放到哪个位置,malloc内存池应该存放到哪个位置等等。这些信息都保存在gd的成员变量中,因此要对 gd的这些成员变量做初始化。最终形成 一个完整的内存“分配图”,在后面重定位uboot的时候就会用到这个内存“分配图”。

(7)重定位

relocate_code代码重定位函数,负责将uboot拷贝到新的地方,完成代码拷贝。

8)清bss
9)跳转到board_init_r()函数,启动流程结束。

前面board_init_f函数里面会调用一系列的函数来初始化一些外设和gd的成员变量。但是
board_init_f并没有初始化所有的外设,还需要做一些后续工作,这些后续工作就是由函数
board_init_r(这里的外设更多包括EMMC,NANDFLASH)来完成的,存放于common/board_r.c。

最后运行的是run_main_loop,主循环,处理命令。


从这里开始就是:打断倒计时执行uboot命令要么进入内核的操作。

uboot启动以后会进入3秒倒计时,如果在3秒倒计时结束之前按下按下回车键,那么就会进入uboot的命令模式,如果倒计时结束以后都没有按下回车键,那么就会自动启动 Linux内 核,这个功能就是由run_main_loop函数来完成的。

A:如果自然结束:run_command_list去执行bootcmd的命令,保存着默认的启动命令,因此linux启动
B:循环处理输入的命令

bootcmd和bootargs的区别

bootcmd: 这个参数包含了一些命令,这些命令将在u-boot进入主循环后执行 示例:
bootcmd=boot_logo;nand read 10000003c0000 300000;bootm 1000000
意思是启动u-boot后,执行boot_logo显示logo信息,然后从nand flash中读内核映像到内存,然后启动内核。

bootargs 这个参数设置要传递给内核的信息,主要用来告诉内核分区信息和根文件系统所在的分区。 示例:
root=/dev/mtdblock5 rootfstype=jffs2console=ttyS0,115200 mem=35M
mtdparts=nand.0:3840k(u-boot),4096k(kernel),123136k(filesystem)

移植uboot-分析uboot启动流程(详解)

对于重定位:
一般片内ROM会根据程序的头部会有一个链接地址,然后程序
通过链接脚本 把源地址 目的地址,长度得到在进行重定位,通过链接脚本得到(至少得到代码段有多长)

什么是位置无关码?什么是绝对跳转指令以及相对跳转指令

通过lds的链接脚本确定链接地址

SECTIONS {
    . = 0x80100000;

    . = ALIGN(4);
    .text      :
    {
      *(.text)
    }

    . = ALIGN(4);
    .rodata : { *(.rodata) }

    . = ALIGN(4);

    data_load_addr = .;
    .data 0x900000 : AT(data_load_addr)
    {
      data_start = . ;
      *(.data) 
      data_end = . ;
    }

    . = ALIGN(4);
    __bss_start = .;
    .bss : { *(.bss) *(.COMMON) }
    __bss_end = .;
}

	
.text
.global  _start

_start: 

	/* 设置栈 */
	ldr  sp,=0x80200000

	/* 重定位text, rodata, data段 */
	bl copy_data

	/* 清除bss段 */
	bl clean_bss

	/* 跳转到主函数 */
	// bl main		/* 相对跳转,程序仍在DDR3内存中执行 */
	ldr pc, =main 	/* 绝对跳转,程序在片内RAM中执行 */

halt:
	b  halt 

实现的从链接脚本中读取的地址


/**********************************************************************
 * 函数名称: copy_data
 * 功能描述: 将整个程序(.text, .rodata, .data)从DDR3重定位到片内RAM
 * 输入参数: 无
 * 输出参数: 无
 * 返 回 值: 无
 * 修改日期        版本号        修改人        修改内容
 * -------------------------------------------------
 * 2020/02/20	    V1.0         阿和            创建
 ***********************************************************************/
void copy_data (void)
{
	/* 从链接脚本中获得参数 _start, __bss_start, */
	extern int _load_addr, _start, __bss_start;

	volatile unsigned int *dest = (volatile unsigned int *)&_start;			//_start = 0x900000
	volatile unsigned int *end = (volatile unsigned int *)&__bss_start;		//__bss_start = 0x9xxxxx
	volatile unsigned int *src = (volatile unsigned int *)&_load_addr;		//_load_addr = 0x80100000

	/* 重定位数据 */	
	while (dest < end)
	{
		*dest++ = *src++;
	}
}
		  			 		  						  					  				 	   		  	  	 	  
/**********************************************************************
 * 函数名称: clean_bss
 * 功能描述: 清除.bss段
 * 输入参数: start, end
 * 输出参数: 无
 * 返 回 值: 无
 * 修改日期        版本号        修改人        修改内容
 * -------------------------------------------------
 * 2020/02/20	    V1.0         阿和            创建
 ***********************************************************************/
void clean_bss(void)
{
	/* 从lds文件中获得 __bss_start, __bss_end */
	extern int __bss_end, __bss_start;

	volatile unsigned int *start = (volatile unsigned int *)&__bss_start;
	volatile unsigned int *end = (volatile unsigned int *)&__bss_end;

	while (start <= end)
	{
		*start++ = 0;
	}
}
		  			 		  						  					  				 	   		  	  	 	  

1、uboot分析之编译体验

  • 1、解压缩

  • 2、打补丁
    通过patch进行打补丁

    -p1表示忽略第一个目录

  • 3、配置
    make 100ask24x0_config

  • 4、编译
    make

uboot的终极目的就是启动内核
1、从Flash读出内核,放到SDRAM内存中
2、启动内核
因此uboot要实现的功能
1、能读Flash + 为了开发方面提供写Flash功能(通过网络[网卡] 或者串口[USB] 烧写内核)
2、初始化SDRAM(在此之前需要关看门狗、初始化时钟、初始化串口),能够写SDRAM;从Flash中读取内核到SDRAM中
3、启动内核

分析源码的架构最好的方法就是通过Makefile文件去分析。

二、uboot分析之Makefile结构分析

我们需要

  • 3、配置
    make 100ask24x0_config
  • 4、编译
    make

执行make 100ask24x0_config命令的时候,通过关键字找到,相当于执行下面这一行。

找去找一下MKCONFIG,在makefile中有定义

表示在源码树下的mkconfig
因此,我们配置的时候,实际上执行了这样的一个脚本命令。($@表示替换,结果为:100ask24x0)

$#表示参数的个数
$1表示第一个参数


‘>’ 表示新建一个文件
‘>>’ 表示追加
可以看到确实有config.mk有导入的内容

同时config.h也有


重点部分:

make的时候如果不指定目标,就回去执行第一个目标all

通过make编译后看完整的过程 ,

分析可以得到:arm-linux-ld链接命令:
1、链接脚本 u-boot.lds是在最前面的
2、其次是start.o
3、再是各种lib库

链接脚本0x00000000会加上 0x33F80000,会从这个地址开始排放代码段等等


可以看到整个内存段的排放。
1、第一个文件cpu/arm920t/start.S。
2、链接地址


board/100ask24x0/config.mk我们猜测是不是这个TEXT_BASE?
我们看一下之前Makefile中链接脚本中定义的LDFLAGS


在uboot根目录下的config.mk可以看到

其中TEXT_BASE是在board/100ask24x0/config.mk下定义的

对内存的上面空出512K的大小,如果你的uboot大小超过了512K你也可以修改它的大小,之前前面也提到过uboot一般放在内存的最上方,当linux内核加载的时候后面就会覆盖uboot的代码。

3、uboot分析之源码第一阶段

在裸机的时候启动步骤
1、初始化

  • 关看门口
  • 初始时钟
  • 初始化SDRAM

2、把程序从NAND->SDRAM
3、设置栈(也就是把sp指向某块内存 )

因此uboot在上面基础上再调用c函数从Flash读出内核,再启动内核。

具体步骤如下(第一阶段的全部是硬件部分的初始化)

  • 进入reset

  • 进入SVC32系统模式

  • 关看门狗

  • 关中断

  • 进入初始化

    关flash,清cache

    关MMU,初始化存储控制器,初始化后内存控制器才可以使用

  • 再设置栈

    通过框起来的代码后,内存布局就是这样,最上面是uboot的代码,底下存放的自己实现的malloc的区域以及IRQ、FIQ和SP指针指向的地方,各有各的用处。

    栈设置好了后才能去调用C函数

  • 接着block_init初始化时钟

  • 重定位:把代码从Flash拷贝到SDRAM中,读到SDRAM哪里去呢,读到链接地址中去

  • 清BSS段

  • 调用start_armboot(C函数),从这个函数开始就是第二阶段了(第二阶段有一些拓展的开发功能例如:烧写Flash、网卡、USB、串口、启动内核等等)

4、uboot分析之源码第二阶段

我们以
1、从Flash读出内核
2、启动
为目的来看。

  • 进入start_armboot

    分配一个gd结构体存放在在128k的CFG_GBL_DATA_SIZE内存中
  • 通过函数指针进行一系列的初始化

    cpu初始化,单板初始化。。。
  • 在单板初始化中会初始化一些管脚,同时还有机器ID以及启动参数的设置、nor nand的初始化


下一节中我们讲解内核的时候会关注这两个参数为什么会有。

再是NORflash初始化:NANDflash可以直接读写(比如我们往0地址写1234如果读出来是1234那就是NAND,读出来还是0的话就是NORflash),而NorFlash的写的时候我们就要配置它,我们因为会有一些拓展的功能就是重新烧写Flash所以我需要对NorFlash读写进行初始化

再是nand flash初始化,还有malloc初始化

现在CPU具备可以读可以写的能力了。
再是环境变量的初始化:

以下内容就是环境变量

环境变量会先去Flash上去找,如果没有设置的话就会去默认的环境变量去看。
网卡、设备、USB的初始化

  • 就这就到了一个main_loop死循环的地方

以上的步骤我们知道通过start_armboot:
执行了flash_init
nand_init一些硬件设备的初始化
到达main_loop的循环中。

在main_loop可看到一些环境变量比如:bootdelay

通过获取bootcmd环境变量得到启动命令

后面一段代码是,如果在倒数计时在到达0之前,没有输入空格键。就去打印booting linux并且执行run_command,bootcmd其实就是两个参数,回到我们这一节最开始的地方。

1、我们想要做的就是从flash读出内核也就是对应bootcmd的第一个指令

nand read.jffs2 0x30007FC0 kernel;

读到0x30007FC0(为什么是这个值我们后面再进行分析) 从kernel分区进行读;
2、怎么启动:通过bootm 0x30007FC0
前面已经从NANDflash把内核读到内存中来,启动内核。

因此内核的启动依赖于bootcmd命令


如果我们按了空格,就会跳到下面的代码中:

因此可以分为两类:
1、启动内核
s = getenv(“bootcmd”)
run_command(s,…)
2、u-boot界面
readline(读入串口的数据)
run_command

5、uboot启动内核

1、我们想要做的就是从flash读出内核也就是对应bootcmd的第一个指令

nand read.jffs2 0x30007FC0 kernel;

读到0x30007FC0(为什么是这个值我们后面再进行分析) 从kernel分区进行读;

  • kernel分区是什么?
    因为用的是Flash没有分区,我们只能通过软件写死分区 ,固定大小区间

    所以从NANDFlash中读,那从哪里读呢就是从kernel中读,然后读到SDRAM中

    其中kernel就是代表其实地址,当然我们用
    nand read.jffs2 0x30007FC0 0x00060000 0x00200000;
    
    效果是一样的,这个就代表从从起始地址0x00060000 ,长度为0x00200000,和图中kenel分区的偏移+大小是一致的。

    用jffs2是因为这个文件格式不需要页对齐。

2、怎么启动:通过bootm 0x30007FC0
前面已经从NANDflash把内核读到内存中来,启动内核->调用do_bootm。

U-boot在Flash上存的内核叫:uImage
uImage就是一个头部加上一个真正的内核。
这就是头部的结构体

ih_load表示加载地址,表示内核要放在哪里
ih_ep表示入口地址,运行内核的时候直接跳到这个地址就可以了

你可以随便放内存里面,只要不破坏uboot在内存最顶部存放的信息,因为uImage的头部已经包含了加载地址以及入口地址的信息

do_bootm函数做的事情

读出头部

把数据拷贝到加载地址中去

我们真正的加载地址是0x30008000那为什么bootm的地址是0x30007fc0
两个相减后得到的结果是64,所以大小是64字节,刚好是64字节的头部。

启动就是调用do_bootm_linux

那do_bootm_linux也需要做一些事情:
(1) uboot告诉内核一些参数->设置启动参数

在某个地址0x30000100,按某种格式保存数据,这个格式成为TAG


setup_start_tag
setup_memory_tag
setup_commandline_tag
setup_end_tag

第一个setup_start_tag函数:

第二个setup_memory_tag:

此时的内存布局,接下来就是params = tag_next(params);

到了第三个setup_commandline_tag:

来自于commandline


把bootargs的参数传入给内核,其中:
root = 表示根文件系统他位于flash的第四个分区
init = 表示第一个运行的应用程序
console表示内核的打印信息,从串口0打印出来

第四个setup_end_tag

设置0,0


至此整个TAG就填写完了,保存好了后,内核就回到这个地址来读取这些参数


启动的时候带了3个参数,其中后面两个参数在第4节中,我们就提到过,这里刚好对应上了,可以看到启动参数的启动地址也就是0x30000100。同时还要和内核比对机器ID是否匹配。

(2) 跳到入口地址启动内核,调用theKernel

以上是关于Uboot启动过程详解的主要内容,如果未能解决你的问题,请参考以下文章

Uboot启动过程详解

Uboot启动过程详解

Uboot启动过程详解

海思芯片(hi3516dv300)uboot启动过程分析

内核启动过程分析

uboot中环境变量的加载写入过程详解