uboot启动流程详细分析(基于i.m6ull)

news/2023/6/9 19:37:20

uboot介绍

uboot就是一段引导程序在加载系统内核之前,完成硬件初始化,内存映射,为后续内核的引导提供一个良好的环境。uboot是bootloader的一种,全称为universal boot loader。

一、uboot的makefile

1.1 makefile整体解析过程

为了生成u-boot.bin这个文件,首先要生成构成u-boot.bin的各个库文件、目标文件。为了各个库文件、目标文件就必须进入各个子目录执行其中的Makefile。由此,确定了整个编译的命令和顺序。

1.2 makefile整体编译过程

  • 首先,根据各个库文件、目标文件出现的先后顺序,依次进入各个子目录编译从而生成这些目标
  • 然后,回到顶层目录,继续执行顶层Makefile的总目标,最后生成u-boot.bin。

uboot的编译分为两步:配置、编译。

(1)第一步:配置,执行make pangu_basic_defconfig进行配置,生成.config文件
在这里插入图片描述

(2)第二步:编译,执行make进行编译,生成u-boot.bin
在这里插入图片描述

二、uboot启动流程

在这里插入图片描述

  • uboot分为 uboot-spl 和 uboot 两个组成部分。

uboot启动分三个阶段

  1. BL0
    ROM上的固化程序(Boot Rom)

  2. BL1(u-boot-spl)

  • 初始化部分时钟(和SDRAM相关)
  • 初始化DDR(外部SDRAM)
  • 从存储介质上(比如SD\eMMC\nand flash)将BL2镜像加载到SDRAM上
  • 验证BL2镜像的合法性
  • 跳转到BL2镜像所在的地址上
  1. BL2 (uboot)
  • 初始化部分硬件,包括时钟、内存等等
  • 加载内核到内存上
  • 加载文件系统、atags或者dtb到内存上
  • 根据操作系统启动要求正确配置好一些硬件

启动操作系统

2.1 uboot的链接文件(u-boot.lds)

链接文件的作用

  1. 指定代码段和数据段、只读数据段在内存中的存放地址;(地址具体为i.m6ull , 其他芯片可能不是 0X87800000)

    • u-boot.map 是 uboot 的映射文件,看到某个文件或者函数链接到了哪个地址,

    • __image_copy_start 为 0X87800000,而.text 的起始地址也是0X87800000。

    • vectors 段保存中断向量表,vectors 段的起始地址也是 0X87800000,说明整个 uboot 的起始地址就是 0X87800000,

    • 这也是为什么我们裸机例程的链接起始地址选择 0X87800000 了,目的就是为了和 uboot 一致。
      在这里插入图片描述
      在这里插入图片描述

  2. 指定代码的入口地址;

    • 连接文件中找到程序的入口点:_start, 其中_start 在文件 arch/arm/lib/vectors.S 。

2.2 uboot启动流程

第一阶段

  • SPL是Secondary Program Loader的简称,第二阶段程序加载器,这里所谓的第二阶段是相对于SOC中的Boot ROM来说的

  • Boot ROM会通过检测启动方式来加载第二阶段bootloader。uboot已经是一个bootloader了,那么为什么还多一个uboot spl呢?

  • 这个主要原因是对于一些SOC来说,它的内部SRAM可能会比较小,小到无法装载下一个完整的uboot镜像,那么就需要spl,它主要负责初始化外部RAM和环境,并加载真正的uboot镜像到外部RAM(DDR)中来执行。

  • 所以由此来看,SPL应该是一个非常小的loader程序,可以运行于SOC的内部SRAM中,它的主要功能就是加载真正的uboot并运行之。

通过uboot-spl编译脚本:u-boot/arch/arm/cpu/u-boot-spl.lds
所以uboot-spl的代码入口函数是_start
对应于路径u-boot/arch/arm/lib/vector.S的_start,后续就是从这个函数开始分析。

2.2.1. 由u-boot.lds中知道入口点是 _start , 进入_start函数:

有一条跳转指令b reset跳转到reset函数处去执行

2.2.2. reset函数处的代码如下:(spl的流程在reset中结束,reset中进入uboot)

reset 函数跳转到了 save_boot_params 函数

2.2.3. save_boot_params 函数

100 ENTRY(save_boot_params)
101 b save_boot_params_ret @ back to my caller

save_boot_params 函数也是只有一句跳转语句,跳转到 save_boot_params_ret 函数,save_boot_params_ret 函数代码如下:

38 save_boot_params_ret:
39 /*
40 * disable interrupts (FIQ and IRQ), also set the cpu to SVC32
41 * mode, except if in HYP mode already
42 */
43 mrs r0, cpsr
44 and r1, r0,  #0x1f @ mask mode bits
45 teq r1, #0x1a @ test for HYP mode
46 bicne r0, r0, #0x1f @ clear all mode bits
47 orrne r0, r0, #0x13 @ set SVC mode
48 orr r0, r0, #0xc0 @ disable FIQ and IRQ
49 msr cpsr,r0

完成设置 CPU 处于 SVC32 模式(超级管理权限 / 保护模式),并且关闭FIQ 和 IRQ 这两个中断。(其他芯片还会关闭看门狗,但是im6ulll没有看门狗)

2.2.4. 然后返回_start函数接着执行

51 /*
52 * Setup vector:
53 * (OMAP4 spl TEXT_BASE is not 32 byte aligned.
54 * Continue to use ROM code vector only in OMAP4 spl)
55 */
56 #if !(defined(CONFIG_OMAP44XX) && defined(CONFIG_SPL_BUILD))
57 /* Set V=0 in CP15 SCTLR register - for VBAR to point to vector */
58 mrc p15, 0, r0, c1, c0, 0 @ Read CP15 SCTLR Register
59 bic r0, #CR_V @ V = 0
60 mcr p15, 0, r0, c1, c0, 0 @ Write CP15 SCTLR Register
61
62 /* Set vector address in CP15 VBAR register */
63 ldr r0, =_start
64 mcr p15, 0, r0, c12, c0, 0 @Set VBAR
65 #endif

第63行设置r0寄存器的值为_start,_start就是整个uboot的入口地址,其值为0X87800000,相当于 uboot 的起始地址,因此 0x87800000 也是向量表的起始地址。
第 64 行将 r0 寄存器的值(向量表值)写入到 CP15 的 c12 寄存器中,也就是 VBAR 寄存器。

因此第 58~64 行就是 设置中断向量表重定位的。 中断向量表的内容就是我们前面在reset下面看到的那些ldr xxxx指令

67 /* the mask ROM code should have PLL and others stable */
68 #ifndef CONFIG_SKIP_LOWLEVEL_INIT
69 bl cpu_init_cp15
70 bl cpu_init_crit
71 #endif
72
73 bl _main

完成上面操作后,会跳转到cpu_init_cp15和cpu_init_crit标号执行,最后转到_main函数去执行。

  • cpu_init_cp15 用来设置 CP15 相关的内容,完成启动ICACHE,关闭DCACHE,关闭MMU和TLB
    (ICACHE为指令缓存,可以不关闭,指令直接操作的硬件,实际的物理地址。但是DCACHE就必须要关闭,此时MMU没有使能,虚拟地址映射不成功,sdram无法访问,DCACHE无数据,这就可能导致读取到错误的数据)
  • cpu_init_crit 处的代码很简单,就是调用lowlevel_init进行初始化

2.2.5. lowlevel_init 函数(初始化内存空间,为第二阶段准备ram)

lowlevel_init主要完成平台级和板级的初始化 ,如:时钟、内存、网卡、串口的初始化。

此时初始化SP指向 内存空间为IRAM(内部ram ,OCRAM 128K ,0x00900000)
在这里插入图片描述

2.2.6. _main函数

在这里插入图片描述

  • 因为后面是C语言环境,首先是设置堆栈

  • 初始化gd(下图中global date,内部ram) , 进行清零(同上内部ram)
    在这里插入图片描述

  • 调用 board_init_f 函数(将SP指针从内部IRAM,转移到外部DDR),主要用来初始化 DDR,定时器,完成代码拷贝等等

  • 调用函数 relocate_code,也就是代码重定位函数,此函数负责将 uboot 拷贝到新的地方去

  • 调用函数 relocate_vectors,对中断向量表做重定位

  • 调用函数c_runtime_cpu_setup , 清除 BSS 段 , 。
    bss段不占用空间,都是未初始化的全局变量或者已经初始化为零的变量,本来就是零,直接清零就好。不清零的话未初始化的变量可能会存在未知的数值。

  • 设置函数 board_init_r 的两个参数调用 board_init_r 函数

  • board_init_r 函数打印一些列的信息到串口,然后会进入main_loop() 。main_loop会进行倒计时,如果此时按下回车就会进入uboot的shell交互界面,否则就会自动引导启动OS系统。

2.2.7. board_init_f 函数

  • 重新设置环境(sp 和 gd) , 新的 sp 和 gd 将会存放到 DDR 中(外部),而不是内部的 RAM 了

  • uboot 会将自己重定位到 DRAM(DDR) 最后面的地址区域,也就是将自己拷贝到 DRAM 最后面的内存区域中。这么做的目的是给 Linux 腾出空间,防止 Linuxkernel 覆盖掉 uboot,将 DRAM 前面的区域完整的空出来。

  • 在拷贝之前肯定要给 uboot 各部分分配好内存位置和大小,比这些信息都保存在 gd 的成员变量中(从板子配置文件里读取),因此要对 gd 的这些成员变量做初始化。最终形成一个完整的内存“分配图”,在后面重定位 uboot 的时候就会用到这个内存“分配图”(外部)。

  • 注:上电后芯片内部Boot ROM把uboot搬移到DRAM头部(0x87800000),重定位则再搬移到DDR后部
    在这里插入图片描述

2.2.8. relocate_code 函数

  • relocate_code 函数是用于代码拷贝

重定位就是 uboot 将自身拷贝到 DRAM 的另一个地放去继续运行(DRAM 的高地址处)。我们知道,一个可执行的 bin 文件,其链接地址和运行地址要相等,也就是链接到哪个地址,在运行之前就要拷贝到哪个地址去。现在我们重定位以后,运行地址就和链接地址不同了,这样寻址的时候不会出问题吗?

代码动态重定位原理

  1. 分析问题产生原因

    r0 = gd->relocaddr = 0x9ff47000 , uboot重定位后的首地址
    r1 = 0x87800000 源地址的首地址
    r2 = 0x8785dc6c 源地址的结束地址
    r4 = 0x9ff46000 - 0x87800000 = 0x18747000 偏移量
    拷贝是从r1复制往r0粘贴 , 一次两个32位
    当r1等于r2,拷贝完成

    当简单粗暴的将uboot从0x87800000拷贝到0x9ff47000 ,程序运行时地址和连接地址不同,发生错误。uboot解决方法是使用位置无关码,借用 .rel.dyn 段

  2. 使用位置无关码解重定位后和连接地址不同问题的原理

    举例: board_init 函数会调用 rel_test,rel_test 会调用全局变量 rel_a
    源代码

     static int rel_a = 0;void rel_test(void){rel_a = 100;printf("rel_test\r\n");}
    int board_init(void)
    {...rel_test();...
    }
    

    反汇编代码

    8785dcf8 <rel_a>:
    8785dcf8:	00000000 	andeq	r0, r0, r0878042b4 <rel_test>:
    878042b4:	e59f300c 	ldr	r3, [pc, #12]	; 878042c8 <rel_test+0x14>
    878042b8:	e3a02064 	mov	r2, #100	; 0x64
    878042bc:	e59f0008 	ldr	r0, [pc, #8]	; 878042cc <rel_test+0x18>
    878042c0:	e5832000 	str	r2, [r3]
    878042c4:	ea00d64c 	b	87839bfc <printf>
    878042c8:	8785dcf8 			; <UNDEFINED> instruction: 0x8785dcf8
    878042cc:	87842aaf 	strhi	r2, [r4, pc, lsr #21]
    

    从反汇编代码中分析:

    • 想要找到 rel_a 的地址,首先 r3 = pc + 12 = 0x878042b4 + 8 + 12 = 0x878042c8 。(由ARM 流水线决定 pc = 当前地址 + 8)
    • 之后 r3 在0x878042c8 中存储数据为0x8785dcf8,即为rel_a地址
    • 这里没直接读取rel_a , 而是借助0x878042c8 。 0x878042c8 就是Label

    重定位后,地址变化

    9ffa4cf8 <rel_a>:
    9ffa4cf8:	00000000 	andeq	r0, r0, r09ff4b2b4<rel_test>:
    9ff4b2b4:	e59f300c 	ldr	r3, [pc, #12]	; 878042c8 <rel_test+0x14>
    9ff4b2b8:	e3a02064 	mov	r2, #100	; 0x64
    9ff4b2bc: 	e59f0008 	ldr	r0, [pc, #8]	; 878042cc <rel_test+0x18>
    9ff4b2c0: 	e5832000 	str	r2, [r3]
    9ff4b2c4: 	ea00d64c 	b	87839bfc <printf>
    9ff4b2c8:	8785dcf8 			; <UNDEFINED> instruction: 0x8785dcf8
    9ff4b2cc:  	87842aaf 	strhi	r2, [r4, pc, lsr #21]
    
    • 这时 Label中的值还是重定位之前的,必须要将8785dcf8换为重定位后的rel_a地址
    • 重定位后的Label中的数据 = 0x878042c8(老的Label) + 0x18747000(uboot整体的偏移量) = 0x9ffa4cf8
    • 读取 rel_a 地址为 ,r3 = 0x9ff4b2b4 + 8 + 12 = 0x9ff4b2c8 ,即为Label地址,然后从Label中读取到 0x9ffa4cf8,为rel_a重定义后真是地址
  3. uboot 中使用 .rel.dyn 段具体实现位置无关码的原理

    完成这个功能在连接的时候需要加上”-pie”

    .rel.dyn 段代码段

    8785dcec:	87800020 	strhi	r0, [r0, r0, lsr #32]
    8785dcf0:	00000017 	andeq	r0, r0, r7, lsl r0
    ……
    8785e2fc:	878042c8	strhi	r4, [r0, r8, asr #5]
    8785e300:	00000017 	andeq	r0, r0, r7, lsl r0
    
    • .rel.dyn 段的格式,也就是两个 4 字节数据为一组。
      高 4 字节是 Label 地址标识 0X17,低 4 字节就是 Label 的地址,
    • 第三行是878042c8,第四行是00000017 。说明878042c8是一个Label。正是上述分析中 存放 rel_a 地址的Label
    • 在重定位后,rel_a 真是地址 = Label内数据 + offset(0x18747000)

2.2.9 relocate_vectors函数

  • 中断向量表重定位后的地址,就是重定位后uboot的首地址

2.2.7. board_init_r 函数

  • 初始化一系列外设,比如串口、定时器,对uboot重定位后分配内存,或者打印一些消息等。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.exyb.cn/news/show-4565514.html

如若内容造成侵权/违法违规/事实不符,请联系郑州代理记账网进行投诉反馈,一经查实,立即删除!

相关文章

神经网络与深度学习(六)卷积神经网络(5)使用预训练resnet18实现CIFAR-10分类

目录 5.5 实践&#xff1a;基于ResNet18网络完成图像分类任务 5.5.1 数据处理 5.5.1.1 数据集介绍 5.5.1.2 数据读取 5.5.1.3 构造Dataset类 5.5.2 模型构建 1.什么是“预训练模型”&#xff1f;什么是“迁移学习”&#xff1f; 2.比较“使用预训练模型”和“不使用预…

转载: ASP.NET生成汉字图片验证码

Code<% Page Language"C#" AutoEventWireup"True" %> <% import Namespace"System"%><% import Namespace"System.Collections.Generic"%><% import Namespace"System.Text"%><% import Namesp

【matlab图像处理】图像处理的逻辑运算

中国史之【周公定《周礼》】&#xff1a; 周礼是西周的政治制度之一&#xff0c;在周公主持下制定。其内容比较广泛&#xff0c;除了有关政刑的各种制度外&#xff0c;还有吉、凶、军、宾、嘉礼。周朝礼乐制度对维护当时社会秩序、巩固王朝统治起到了重大作用。 ——来源&#…

CS231n课程笔记翻译2:图像分类笔记

译者注 &#xff1a;本文 智能单元 首发&#xff0c;译自斯坦福CS231n课程笔记 image classification notes &#xff0c;由课程教师 Andrej Karpathy 授权进行翻译。本篇教程由 杜客 翻译完成。 ShiqingFan 对译文进行了仔细校对&#xff0c;提出了大量修改建议&#xff0c;态…

matlab求组合数不想求组合数矩阵,【潘德的预言】用关系模型与组合数计算NPC最大相容人数和所有组合...

本帖最后由 wwwmajin 于 2013-6-19 19:44 编辑本文重在从纯技术角度来寻找潘德NPC的最佳组合&#xff0c;共分4个部分第一 背景介绍第二 本文研究方法第三 结果第四 结论第一 背景介绍大家喜欢收集NPC吗&#xff1f; 喜欢尽量收集全NPC吗&#xff1f; 本人便是一个具有收集NPC癖…

人工智能图像形状检测算法

博主简介 博主是一名大二学生&#xff0c;主攻人工智能研究。感谢让我们在CSDN相遇&#xff0c;博主致力于在这里分享关于人工智能&#xff0c;c&#xff0c;Python&#xff0c;爬虫等方面知识的分享。 如果有需要的小伙伴可以关注博主&#xff0c;博主会继续更新的&#xff0c…

电源参数

前言 电源的特性参数包括&#xff1a; 输入电压范围输入冲击电流启动时间输出电压范围输出纹波和噪声功率效率负载调解率电压调解率维持时间过电流保护过电压保护欠压保护反向电压保护反向电流保护隔离电压等级 解释&#xff1a;

变频电源的效率与损耗

关于变频电源的效率与损耗&#xff0c;中港扬盛技工分析由于输出的谐波问题&#xff0c;这些谐波会产生相应的铜耗和铁耗,使电机固定损耗增加,电机温升增高,降低运行效率和功率因数,因此变频电源供电下电动机的谐波损耗是一个大问题。变频电源效率是指其本身变换效率。交-交变频…