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

news2024/11/13 8:50:36

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, r0
    
    878042b4 <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, r0
    
    9ff4b2b4<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.coloradmin.cn/o/172695.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

ccc-sklearn-16-XGBoost(2)

文章目录XGBoost的其他参数选择弱评估器&#xff1a;参数boosterXGB的目标函数&#xff1a;参数objectiveXGB目标函数的求解参数化决策树ftf_tft​&#xff1a;参数alpha&#xff0c;lambda寻找最佳树结构&#xff1a;求解w和T寻找最佳分枝&#xff1a;结构分数之差让树停止生长…

C++ 函数重载:女友说的话到底是什么意思?

&#x1f451;专栏内容&#xff1a;C学习笔记⛪个人主页&#xff1a;子夜的星的主页&#x1f495;座右铭&#xff1a;日拱一卒&#xff0c;功不唐捐 目录一、前言二、函数重载1、函数重载概念2、函数重载的分类Ⅰ、参数类型不同Ⅱ、参数个数不同Ⅲ、参数类型顺序不同3、函数重载…

vs code,platform下载Arduino程序到ESP8266,并传送文件到flash

参考视频&#xff1a;https://www.bilibili.com/video/BV1yR4y1X72D/ 首先要知道 存储空间中有程序的存储地址和文件存储地址&#xff0c;可以对单独一个部分的写入不影响另一部分内容。 vs code 的platformIO插件进行程序和文件的上传 基本创建工程和程序可以参考&#x…

启明欣欣STM32开发板移植FreeRTOS

承接这篇文章&#xff0c;本篇讲述如何把FreeRTOS移植到启明欣欣STM32开发板里&#xff0c;比较简单&#xff0c;网上也有各种教程&#xff0c;本文也是参考其它文章&#xff0c;这里再记录一下。 一 搭建基础工程 启明欣欣STM32开发板上的MCU是STM32F407ZGT6&#xff0c;根据…

智能电视机安装App

每年的12月18日是世界电视机日&#xff0c;电视机诞生于1925年&#xff0c;最初是电子机械式电视机&#xff1b;到了1933年&#xff0c;诞生CRT电视&#xff0c;即黑白电视&#xff0c;它只有黑色或白色&#xff0c;看任何物品都是黑色或者白色。又过了20年&#xff0c;1953年彩…

Java之collection集合、常见数据结构、List和泛型

目录集合概述总结Collection集合的体系特点总结Collection集合常用APICollection集合的遍历方式方式一&#xff1a;迭代器总结方式二&#xff1a;foreach/增强for循环方式三&#xff1a;lambda表达式Collection集合存储自定义类型的对象总结常见数据结构数据结构概述、栈、队列…

【C++初阶】C++基础(一)

C是在C的基础之上&#xff0c;容纳进去了面向对象编程思想&#xff0c;并增加了许多有用的库&#xff0c;以及编程范式等。熟悉C语言之后&#xff0c;对C学习有一定的帮助&#xff0c;本文主要目标&#xff1a;1. 补充C语言语法的不足&#xff0c;以及C是如何对C语言设计不合理…

尚医通-首页显示-前端数据整合(二十六)

目录&#xff1a; &#xff08;1&#xff09;前台用户系统-首页显示-整合静态页面 &#xff08;2&#xff09;前台用户系统-首页显示-数据接口开发 &#xff08;3&#xff09;前端用户系统-首页显示-前端整合 &#xff08;1&#xff09;前台用户系统-首页显示-整合静态页面 …

代码随想录第七天(541、剑指05)

文章目录541. 反转字符串 II发现了三个基础知识的问题看答案改进剑指 Offer 05. 替换空格答案方法1答案方法2知识点一、二、三、总结541. 反转字符串 II 发现了三个基础知识的问题 第一个 这个题目发现了一个非常大的问题&#xff0c;有点不知道自己的Java基础到底有多少窟窿…

C++ 20 新特性 ranges 精讲

C 20 新特性 ranges 精讲 C20 中的 ranges 库使得使用 STL 更加舒适和强大。ranges 库中的算法是惰性的&#xff0c;可以直接在容器上工作&#xff0c;并且可以很容易地组合。简而言之&#xff0c;ranges 库的舒适性和强大性都源于它的函数思想。 在深入细节之前&#xff0c;…

程序员的7个被动收入途径——我如何每月赚 5万

每个人都想过时间和财富自由的生活&#xff0c;世界上有70亿人&#xff0c;但只有不到18000人能做到这一点&#xff0c;大多数人一生都在为钱工作。 研究表明&#xff0c;全世界65.8万富人至少有三种收入来源&#xff0c;而且都是被动收入。换句话说&#xff0c;大多数富人知道…

Create Realtime-chat app

Tech:React,Node.js,Socket.io,MongoDB styled-component ​​​​​​​ 目录 Base setup Register funcitonality Login funcitonality set Avatar/profile picture Chat container setup useEffect basic hook ChatHeader ChatInput ChatMessage Set socket an…

I.MX6ULL裸机开发笔记2:镜像文件

目录 一、boot ROM程序 二、镜像文件五要素 三、芯片手册 四、芯片手册数据解读 1、空偏移 2、IVT表 3、DCD表 一、boot ROM程序 选择内部启动方式&#xff0c;启动boot ROM程序 初始化时钟&#xff0c;外部DDR3从外部存储介质加载代码 boot ROM程序是芯片厂…

十五天学会Autodesk Inventor,看完这一系列就够了(十一),放样和螺旋扫掠(绘弹簧)

众所周知&#xff0c;Autocad是一款用于二维绘图、详细绘制、设计文档和基本三维设计&#xff0c;现已经成为国际上广为流行的绘图工具。Autodesk Inventor软件也是美国AutoDesk公司推出的三维可视化实体模拟软件。因为很多人都熟悉Autocad&#xff0c;所以再学习Inventor&…

Redis缓存数据 | 黑马点评

目录 一、什么是缓存 二、添加Redis缓存操作 三、缓存更新策略 缓存的更新策略 ​编辑 业务场景 主动更新策略 案例 四、缓存穿透 1、是什么 2、解决方案 &#xff08;1&#xff09;缓存空对象 &#xff08;2&#xff09;布隆过滤器 &#xff08;3&#xff09;其…

【春节安全保障有我们】安全狗春节放假值班通知

兔年纳福 辛勤拼搏了一年 终于迎来了福兔吉祥年 众人沉浸于准备过年的氛围中 却有些人为春节期间的网络安全担忧 因为春节也是不法分子们 伺机而动、“搞事情”的“好时机” 2023 NEW YEAR 不得不防的安全风险 1、主机安全遭受威胁 &#xff08;云&#xff09;主机系统…

Rust语言基础

安装 Rust 官网&#xff1a;https://www.rust-lang.org/Linux or Mac: curl https://rustup.rs -sSf | sh Windows: 按官网指示操作 Windows Subsystem for Linux: curl --proto ‘https’ --tlsv1.2 -sSf https://sh.rustup.rs | sh 查看是否安装成功 rustc --version 更…

【openEuler】x2openEuler工具使用

文章目录一、关于x2openEuler二、工具准备三、前期准备1、安装CentOS72、下载x2openEuler3、安装x2openEuler4、执行bash5、访问上述网站6、安装x2openEuler-client&#xff08;1&#xff09;在CentOS-7.6Evetything1上找到x2openEuler-client&#xff08;2&#xff09;把x2ope…

Android Studio 支持手机投屏电脑

有时当我们在线上做技术分享或者功能演示时&#xff0c;希望共享连接中的手机屏幕&#xff0c;此时我们会求助 ApowerMirror&#xff0c;LetsView&#xff0c;Vysor&#xff0c;Scrcpy 等工具。如果你是一个 Android Developer&#xff0c;那么现在你有了更好的选择。 Android…

蓝桥杯--快排+队列+尺取法

&#x1f603;这只松鼠如约而至 - 许嵩 - 单曲 - 网易云音乐 &#x1f603;你买菜吗玫瑰 - 要不要买菜 - 单曲 - 网易云音乐 &#x1f603;一起玩吧这世界那么多人&#xff08;电影《我要我们在一起》主题曲&#xff09; - 莫文蔚 - 单曲 - 网易云音乐 前言 这是我在CSD…