uboot源码分析-uboot启动流程

导读:以三星S5PV210的uboot为例,从系统复位运行第一句uboot汇编代码开始,顺着启动流程逐步分析uboot源码。为了展示方便,图片中做了很多的注释,并且删除了一些无用的代码方便阅读,可能有误删。本文参考了九鼎x210开发板uboot和朱有鹏老师相关文档。

目录

  • 0、uboot启动流程预览
  • 1、汇编阶段
    • 1.1 _start
    • 1.2 reset复位
    • 1.3 重定位
    • 1.4 after_copy
    • 1.5 lowlevel_init (补充)
    • 1.6 小结
  • 2、C语言阶段:init_sequence
    • 2.1 硬件初始化函数调用
    • 2.2 init_sequence函数指针数组成员
    • 2.3 小结
  • 3、C语言阶段:start_armboot剩余部分
    • 3.1 初始化flash
    • 3.2 晚期初始化
    • 3.3 启动结束

0、uboot启动流程预览

_start -> lowlevel_init -> 重定位 -> start_armboot -> init_sequence -> main_loop

  • uboot从_start开始运行,中间调用lowlevel_init进行底层硬件初始化;然后进行重定位,调用start_armboot跳转到内存中运行,进入C语言阶段;在start_armboot函数中调用了init_sequence函数指针数组里面的所有函数,最后死循环main_loop进入了命令行。

1、汇编阶段

1.1 _start

由链接脚本可以得知,uboot程序入口在start.S里的_start处,系统复位,程序就从_start开始运行,下面开始详细分析。

  • 留出16字节校验头:程序开头就通过4个.word指令占用16字节空间,编译uboot后可以通过程序计算uboot校验和,再计算值填充到校验头里。
  • 定义异常中断向量表:当程序发生异常时,硬件自动跳转到向量表中的地址,然后执行对应的异常处理函数。(异常处理函数也只是打印寄存器信息,并提示系统奔溃,然后重启。)
  • 声明异常处理函数:下面的标签都是函数入口,发生异常时通过异常向量表调用对应的异常处理函数。
    uboot源码分析-uboot启动流程_第1张图片

1.2 reset复位

  • 系统复位直接跳转到reset标签处,首先做了以下工作:
    • 设置为SVC32管理模式(整个uboot一直处于SVC模式)
    • 禁用中断
    • 设置二级缓存l2cache,TLB和icache
    • 关MMU和cache
    • 读取启动介质信息:判断是从什么设备启动,然后将启动信息保存再r3寄存器中。

uboot源码分析-uboot启动流程_第2张图片

  • 第一次设置栈:为lowlevel_init递归调用函数压栈做准备。
  • 调用lowlevel_init函数:初始化硬件相关东西(IO恢复,关看门狗,初始化内存、串口、时钟)
  • 执行供电锁存:让电源稳定输入
  • 第二次设置栈:执行lowlevel_init后,内存已经初始化了,要将栈指针指向内存空间。
  • 判断运行位置:如果在内存中运行就跳过重定位,直接跳转到after_copy;否则就执行后面的重定位代码,然后跳转到after_copy;
    uboot源码分析-uboot启动流程_第3张图片

1.3 重定位

  • 判断启动介质:通过读取对应寄存器的值,判断是从什么存储设备启动
  • 调用对应的重定位代码:理论上应该根据r3的值来判断启动介质,但又重复读取了一遍
    uboot源码分析-uboot启动流程_第4张图片

1.4 after_copy

  • 打开了MMU:通过操作cp15协处理器打开MMU,存储了虚拟地址转换表
  • 第三次设置栈:这次设置在uboot往上2M的位置
  • 清理BSS:通过循环给BSS空间赋0来清理BSS
  • 进入C语言阶段:通过远跳转指令ldr pc, _start_armboot跳转到内存中运行,执行_start_armboot函数,正式进入C语言阶段。
    uboot源码分析-uboot启动流程_第5张图片

1.5 lowlevel_init (补充)

上面介绍到,重定位之前调用了lowlevel_init函数,该函数比较重要,主要工作如下:

  • 将lr压栈:防止递归调用函数破坏lr,导致函数无法返回(lowlevel_init之前设置过栈)
  • 检查复位状态:如果是睡眠下复位就直接运行内核,跳过后面所有代码;
  • IO恢复:将IO引脚的状态恢复到之前的状态
  • 关看门狗:没时间喂狗,防止自动复位
    uboot源码分析-uboot启动流程_第6张图片
  • 判断代码运行位置:如果在DDR中运行,跳过时钟和内存初始化
  • 初始化时钟和内存
  • 函数返回:调用pop {pc}语句实现函数返回,到此lowlevel_init结束。
    uboot源码分析-uboot启动流程_第7张图片

1.6 小结

汇编阶段,uboot主要做的事情有:

  1. 空出16字节校验头
  2. 设置异常向量表
  3. 设置成SVC管理模式
  4. 关MMU和cache
  5. 读取OM引脚,判断启动介质
  6. 执行lowlevel_init(检查复位状态,IO引脚电平恢复,关看门狗、初始化时钟、内存、串口和可信任区域)
  7. 检查代码运行位置决定是否要重定位
  8. 根据启动介质,调用对应的重定位代码
  9. 重定位之后打开MMU,清除BSS段
  10. 跳转到DDR中运行,执行start_armboot函数,进入C语言阶段

2、C语言阶段:init_sequence

2.1 硬件初始化函数调用

前面介绍到,汇编阶段已经设置了栈,程序已经运行在内存里了,最后调用start_armboot函数进入C语言阶段,下面开始详细讲解start_armboot函数:

  • 为全局变量gd和bd申请内存空间:gd和bd是两个比较重要的全局变量,后面的工作基本都是为他初始化
  • 运行init_sequence:init_sequence是一个函数指针数组,使用一个for循环,调用里面的所有函数。里面都是非常重要的硬件初始化,只要有一个函数执行失败就会挂起,整个启动就失败。(init_sequence最后一个元素是NULL,因此可以通过非空判断退出循环)
    uboot源码分析-uboot启动流程_第8张图片

2.2 init_sequence函数指针数组成员

上面介绍到了start_armboot会调用init_sequence里面所有的函数成员,下面详细介绍里面的每一个函数:

  • 函数指针数组定义如下,init_fnc_t 是一个传参为void,返回值为int类型的函数指针,返回0表示成功
init_fnc_t *init_sequence[] = {
     
	cpu_init,/* basic cpu dependent setup */       //前面已经初始化了CPU,函数里面为空
	board_init,	/* basic board dependent setup */  //板级初始化
	interrupt_init,	/* set up exceptions */        //bootdelay倒计时定时器初始化(函数名写的不对)
	env_init,		/* initialize environment */   //环境变量初始化
	init_baudrate,	/* initialze baudrate settings */    //波特率初始化
	serial_init,	/* serial communications setup */    //串口初始化,汇编初始化过了,这里实际什么都没做
	console_init_f,	/* stage 1 init of console */        //控制台初始化
	display_banner,	/* say that we are here */           //打印uboot版本信息
	print_cpuinfo,	/* display cpu info (and speed) */   //打印CPU时钟信息
	checkboard,		/* display board info */             //打印一些外设信息
	dram_init,		/* configure available RAM banks */  //DRAM软件部分初始化
	display_dram_config,
	NULL,          //用做退出循环条件
};
  • cpu_init 函数里面什么都没做,直接返回
  • board_init 函数初始化了DM9000网卡,然后将机器码和传参内存地址存放在gd全局变量中。
    uboot源码分析-uboot启动流程_第9张图片
  • interrupt_init 设置timer4,定时10毫秒,用于uboot启动时的bootdelay倒计时,到后面cpu循环检测是否按下回车键决定进入命令行还是启动Linux内核。
    uboot源码分析-uboot启动流程_第10张图片
  • env_init 函数进行了环境变量基本判定初始化,具体的环境变量初始化在start_armboot末尾。
  • init_baudrate 函数负责给全局变量中的波特率赋值
    uboot源码分析-uboot启动流程_第11张图片
  • init_baudrate 函数本意是初始化串口,然而汇编已经初始化了,这里实际什么都没做。
  • console_init_f 函数就执行了gd->have_console = 1; //给全局变量中控制台赋值
  • display_banner 函数就打印了uboot版本信息,这个版本信息字符串是通过Makefile指定的,需要编译过后才能生成。
  • print_cpuinfo 函数打印了CPU时钟信息
  • checkboard 函数打印了一些外设信息,包括串口,Flash,DRAM和控制台等
  • dram_init 函数给全局变量赋值,主要是内存的起始地址和内存大小
    uboot源码分析-uboot启动流程_第12张图片
  • display_dram_config 函数打印了内存大小
    uboot源码分析-uboot启动流程_第13张图片
    补充:使用uboot命令bdinfo可以直接打印全局变量的值

2.3 小结

  • init_sequence中有很多函数是不起作用的,主要任务是给gd全局变量赋值(包括机器码、传参地址、波特率、内存起始地址和大小、),初始化环境变量,然后打印一些硬件信息,打开了一个定时器用于倒计时。(注:并不是所有uboot都有这些函数)

3、C语言阶段:start_armboot剩余部分

3.1 初始化flash

init_sequence里面的函数都执行成功后,start_armboot还剩下一些杂碎的东西,uboot最终运行结果就是进入命令行或者启动内核,下面开始分析start_armboot剩余部分,讲解进入命令行前的一些工作:

  • 初始化堆管理器:为malloc内存分配做准备
  • 初始化存储介质:根据配置文件里定义的宏,正式初始化SD卡、OneNand或Nand
    uboot源码分析-uboot启动流程_第14张图片

3.2 晚期初始化

  • 重定位环境变量:初始化flash后,调用env_relocate函数将flash设备中的环境变量重定位到DDR内存中,如果flash没有存放环境变量会使用代码中的默认环境变量。
  • 获取IP和MAC地址:然后调用getenv_IPaddr和getenv_r函数获取IP地址和MAC地址,再存放到全局变量gd中
  • 驱动初始化:调用device_init函数初始化外围设备(驱动初始化,通过宏定义确定要初始化那些设备)
  • 初始化跳转表:调用jumptable_init函数初始化跳转表,实现通过函数指针调用函数(通过C语言实现面向对象),实际uboot没有调用里面的函数,只初始化没调用。
  • 初始化控制台:调用console_init_r函数初始化控制台,初始化标准输入、标准输出和标准错误(stdin、stdout和stderror)
  • 获取内核启动相关:调用getenv函数获取loadaddr和bootfile这两个环境变量,并存放到全局变量load_addr和BootFile中(内核启动有关)
  • 硬件晚期初始化:调用board_late_init函数实现硬件晚期初始化(可以为空)
    uboot源码分析-uboot启动流程_第15张图片

3.3 启动结束

  • 初始化LCD:调用x210_preboot_init初始化LCD引脚和LCD寄存器,然后显示logo图片
  • uboot自动升级:读取按键引脚状态,判断是否要升级uboot,实现SD卡升级系统功能
  • 进入命令行:最后跳转到一个死循环(进入命令行),不断执行main_loop函数,main_loop不断判断用户输入命令,遇到回车就解析命令。
    uboot源码分析-uboot启动流程_第16张图片

main_loop 命令行解析和倒计时相关代码分析请参考:uboot主循环main_loop

你可能感兴趣的:(uboot源码分析,uboot,嵌入式,linux)