【ARM】Uboot代码分析

一.摘要

这篇文章主要对BootLoader(UBoot)的源码进行了分析,并对UBoot的移植略作提及。  BootLoader的总目标是正确调用内核的执行,由于大部分的BoorLoader都依赖于CPU的体系结构。因此大部分的BootLoader都分为两个步骤启动。依赖于CPU体系结构(如设备初始化等)的代码都放在stage1。而stage2一般使用C语言实现,能够实现更加复杂的功能,代码的可移植性也提高。

二.本文提纲

1. 摘要

2. 本文提纲

3. UBoot启动过程

4. Stage1(汇编语言实现)代码分析

5. Stage2(C语言实现)代码分析

6. UBoot移植过程中串口没有显示或者显示乱码的原因

7. 总结

三.UBoot启动过程

UBoot其启动过程主要可以分为两个部分,Stage1和Stage2 。其中Stage1是用汇编语言实现的,主要完成硬件资源的初始化。而Stage2则是用C语言实现。主要完成内核程序的调用。这两个部分的主要执行流程如下:

stage1包含以下步骤:

1. 硬件设备初始化

2. 为加载stage2准备RAM空间

3. 拷贝stage2的代码到RAM空间

4. 设置好堆栈

5. 跳转到stage2的C语言入口点

 

stage2一般包括以下步骤:

1. 初始化本阶段要使用的硬件设备

2. 检测系统内存映射

3. 将kernel映射和根文件系统映射从Flash读到RAM空间中

4. 为内核设置启动参数

5. 调用内核

四. Stage1(汇编语言实现)代码分析

该阶段主要是在cpu/arm920t/start.S文件中执行,这个汇编程序是U-Boot的入口程序,程序的开头就是复位向量的代码,主要的执行流程见下图。

 

U-Boot启动代码流程图

start.S代码分析:

(1)主要实现复位向量,设置异常向量表。

_start: b reset //复位向量 ;;设置异常向量表
       ldr pc, _undefined_instruction
       ldr pc, _software_interrupt
       ldr pc, _prefetch_abort
       ldr pc, _data_abort
       ldr pc, _not_used
       ldr pc, _irq //中断向量
       ldr pc, _fiq //中断向量

(2)复位启动子程序,将CPU设置到SVC模式

/* the actual reset code */
reset: //复位启动子程序
       /* 设置CPU为SVC32模式 */
       mrs r0,cpsr
       bic r0,r0,#0x1f ;;位清除,将某些位的值置0:r0 = r0 AND ( !0x1f)
       orr r0,r0,#0xd3 ;;逻辑或,将r0与立即数进行逻辑或,放在r0中(第一个)
       msr cpsr,r0

(3)关闭看门狗

/* 关闭看门狗 */
 /* turn off the watchdog */
#if defined(CONFIG_S3C2400)
# define pWTCON 0x15300000
# define INTMSK 0x14400008 /* Interupt-Controller base addresses */
# define CLKDIVN 0x14800014 /* clock divisor register */
#elif defined(CONFIG_S3C2410)
# define pWTCON 0x53000000
# define INTMSK 0x4A000008 /* Interupt-Controller base addresses */
# define INTSUBMSK 0x4A00001C
# define CLKDIVN 0x4C000014 /* clock divisor register */
#endif

#if defined(CONFIG_S3C2400) || defined(CONFIG_S3C2410)
    ldr r0, =pWTCON
    mov r1, #0x0
    str r1, [r0]

(4)禁止所有中断,设置CPU频率

/* 禁止所有中断和设置CPU频率 */
    /*
     * mask all IRQs by setting all bits in the INTMR - default
     */
    mov r1, #0xffffffff
    ldr r0, =INTMSK
    str r1, [r0]
# if defined(CONFIG_S3C2410)
    ldr r1, =0x3ff
    ldr r0, =INTSUBMSK
    str r1, [r0]
# endif

    /* FCLK:HCLK:PCLK = 1:2:4 */ ;;FCLK用于CPU,HCLK用于AHB,PCLK用于APB
    /* default FCLK is 120 MHz ! */
    ldr r0, =CLKDIVN ;;根据硬件手册来设置CLKDIVN寄存器
    mov r1, #3 ;;用户手册的推荐值
    str r1, [r0]
#endif /* CONFIG_S3C2400 || CONFIG_S3C2410 */

(5)系统重启的时候执行的初始化代码,而不是系统热复位(从RAM中执行)的时候

/*
* we do sys-critical inits only at reboot,
* not when booting from
*/
#ifndef CONFIG_SKIP_LOWLEVEL_INIT
    bl cpu_init_crit ;;跳转去初始化CPU
#endif
;;#ifdef CONFIG_INIT_CRITICAL 原文中的,估计是1.1.16版本的
;; bl cpu_init_crit
;;#endif

(6)CPU和RAM两个关键的初始化子程序

函数一:/* 初始化CPU */

cpu_init_crit:
    /*
     * flush v4 I/D caches 设置CP15
     */
    mov r0, #0
    mcr p15, 0, r0, c7, c7, 0 /* flush v3/v4 cache */ ;;使I/D cache失效:将寄存器r0的数据传送到协处理器p15的c7中。C7寄存器位对应cp15中的cache控制寄存器
    mcr p15, 0, r0, c8, c7, 0 /* flush v4 TLB */ ;;使TLB操作寄存器失效:将r0数据送到cp15的c8、c7中。C8对应TLB操作寄存器

    /*
     * disable MMU stuff and caches 禁止MMU和caches
     */
    mrc p15, 0, r0, c1, c0, 0 ;;先把c1和c0寄存器的各位置0(r0 = 0)
    bic r0, r0, #0x00002300 @ clear bits 13, 9:8 (--V- --RS)
    bic r0, r0, #0x00000087 @ clear bits 7, 2:0 (B--- -CAM) ;;这里我本来有个疑问:为什么要分开设置。因为arm汇编要求的立即数格式所决定的
    orr r0, r0, #0x00000002 @ set bit 2(??) (A) Align ;;上一条已经设置bit1为0,这一条又设置为1??
    orr r0, r0, #0x00001000 @ set bit 12 (I) I-Cache
    mcr p15, 0, r0, c1, c0, 0 ;;用上面(见下面)设定的r0的值设置c1??(cache类型寄存器)和c0(control字寄存器),以下为c0的位定义
;;bit8: 0 = Disable System protection
;;bit9: 0 = Disable ROM protection
;;bit0: 0 = MMU disabled
;;bit1: 0 = Fault checking disabled 禁止纠错
;;bit2: 0 = Data cache disabled
;;bit7: 0 = Little-endian operation
;;bit12: 1 = Instruction cache enabled

    /* 配置内存区控制寄存器 ??有待分析,是1.1.4版本的
     * before relocating, we have to setup RAM timing
     * because memory timing is board-dependend, you will
     * find a lowlevel_init.S in your board directory.
     */
mov ip, lr
bl lowlevel_init ;;位于board/smdk2410/lowlevel_init.S:用于完成芯片存储器的初始化,执行完成后返回
mov lr, ip
mov pc, lr

函数二:/* 把U-Boot重新定位到RAM */

relocate:
       adr r0, _start /* r0是代码的当前位置 */ ;;adr伪指令,汇编器自动通过当前PC的值算出 如果执行到_start时PC的值,放到r0中:
当此段在flash中执行时r0 = _start = 0;当此段在RAM中执行时_start = _TEXT_BASE(在board/smdk2410/config.mk中指定的值为0x33F80000,即u-boot在把代码拷贝到RAM中去执行的代码段的开始)
       ldr r1, _TEXT_BASE /* 测试判断是从Flash启动,还是RAM */ ;;此句执行的结果r1始终是0x33FF80000,因为此值是又编译器指定的(ads中设置,或-D设置编译器参数)
       cmp r0, r1 /* 比较r0和r1,调试的时候不要执行重定位 */
       beq stack_setup /* 如果r0等于r1,跳过重定位代码 */
       /* 准备重新定位代码 */ ;;以上确定了复位启动代码是在flash中执行的(是系统重启,而不是软复位),就需要把代码拷贝到RAM中去执行,以下为计算即将拷贝的代码的长度
       ldr r2, _armboot_start ;;前面定义了,就是_start
       ldr r3, _bss_start ;;所谓bss段,就是未被初始化的静态变量存放的地方,这个地址是如何的出来的?根据board/smsk2410/u-boot.lds内容?
       sub r2, r3, r2 /* r2 得到armboot的大小 */
       add r2, r0, r2 /* r2 得到要复制代码的末尾地址 */

(7)重新定位代码,循环拷贝启动的代码到RAM中

copy_loop:
       ldmia {r3-r10} /*从源地址[r0]复制 */ ;;r0指向_start(=0)
       stmia {r3-r10} /* 复制到目的地址[r1] */ ;;r1指向_TEXT_BASE(=0x33F80000)
       cmp r0, r2 /* 复制数据块直到源数据末尾地址[r2] */
       ble copy_loop

(8)初始化堆栈等

stack_setup:
       ldr r0, _TEXT_BASE /* 上面是128 KiB重定位的u-boot */
       sub r0, r0, #CFG_MALLOC_LEN /* 向下是内存分配空间 */
       sub r0, r0, #CFG_GBL_DATA_SIZE /* 然后是bdinfo结构体地址空间 */
#ifdef CONFIG_USE_IRQ
       sub r0, r0, #(CONFIG_STACKSIZE_IRQ+CONFIG_STACKSIZE_FIQ)
#endif        ;;这些宏定义在/include/configs/smdk2410.h中:
#define CFG_MALLOC_LEN    (CFG_ENV_SIZE + 128*1024)        ;;64K+128K=0xC0
#define CFG_ENV_SIZE    0x10000        /* Total Size of Environment Sector 64k*/
#define CONFIG_STACKSIZE    (128*1024)    /* regular stack 128k */
#define CFG_GBL_DATA_SIZE     128    /* size in bytes reserved for initial data */
用0x33F8000 – 0xC0 – 0x80得到_TEXT_BASE向下(低地址)的堆栈指针sp的起点地址
       sub sp, r0, #12 /* 为abort-stack预留3个字 */    ;;得到最终sp指针初始值
clear_bss:
       ldr r0, _bss_start /* 找到bss段起始地址 */
       ldr r1, _bss_end /* bss段末尾地址 */
       mov r2, #0x00000000 /* 清零 */
clbss_l:str r2, [r0] /* bss段地址空间清零循环... */
       add r0, r0, #4
       cmp r0, r1
       bne clbss_l

(9)跳转到start_armboot函数入口,_start_armboot字保存函数的入口指针

       ldr pc, _start_armboot
_start_armboot: .word start_armboot ;;start_armboot函数在lib_arm/board.c中实现

 

五. Stage2(C语言实现)代码分析

这个文件是bootloader的stage2部分,这个文件中的start_armboot函数是U-Boot执行的第一个C语言函数,主要完成系统的初始化工作,然后进入主循环,等待并处理用户输入的命令。

在编译和链接BootLoader这样的程序的时候,不能使用glibc库中的任何支持函数,这就带来了一个问题:从何处跳入Main函数,最直接的想法是直接把Main函数的起始地址作为整个Stage2执行映像的入口。但是这样做有两个缺点:

a: 无法通过Main函数传递参数

b: 无法处理Main函数返回的情况

一种更好的解决方案是利用trampoline(弹簧床)的概念:用汇编写一段trampoline小程序,并将这段trampoline小程序作为Stage2可执行映像的入口点,然后就可以在trampoline小程序中用CPU跳转指令跳入Main函数去执行,当Main函数执行结束以后CPU执行路径显然再次回到trampoline程序。其核心思想就是用这段trampoline程序作为Main函数的外部包裹。

(1). 初始化本阶段要使用到的硬件设备,一般包括:

a:点亮LED,表示已经进入main函数执行(可选)

b: 至少一个串口,以便和终端用户进行IO信息交换

c: 初始化定时器等

d: 输出一些打印信息,程序名称,版本号等

(2). 检测系统的内存映射

所谓内存映射就是指在整个 4GB 物理地址空间中有哪些地址范围被分配用来寻址系统的 RAM 单元。比如,在 SA-1100 CPU 中,从 0xC000,0000 开始的 512M 地址空间被用作系统的 RAM 地址空间,而在 Samsung S3C44B0X CPU 中,从 0x0c00,0000 到 0x1000,0000 之间的 64M 地址空间被用作系统的 RAM 地址空间。虽然 CPU 通常预留出一大段足够的地址空间给系统 RAM,但是在搭建具体的嵌入式系统时却不一定会实现 CPU 预留的全部 RAM 地址空间。也就是说,具体的嵌入式系统往往只把
CPU 预留的全部 RAM 地址空间中的一部分映射到 RAM 单元上,而让剩下的那部分预留 RAM 地址空间处于未使用状态。  由于上述这个事实,因此 Boot Loader 的 stage2 必须在它想干点什么 (比如,将存储在 flash 上的内核映像读到 RAM 空间中) 之前检测整个系统的内存映射情况,也即它必须知道 CPU 预留的全部 RAM 地址空间中的哪些被真正映射到 RAM 地址单元,哪些是处于 "unused" 状态的。

(3). 加载内核映像和根文件系统映像

a:规划内存占用的布局:主要包括基地址和映像大小两个方面。对于内核映像,一般将其拷贝到从(MEM_START+0x8000) 这个基地址开始的大约 1MB大小的内存范围内(嵌入式 Linux 的内核一般都不操过 1MB)。为什么要把从 MEM_START到MEM_START+0x 8000 这段 32KB 大小的内存空出 来呢?这是因为 Linux 内核要在这段内存中放置一些全局数据结构,如:启动参数和内核页表等信息。而对于根文件系统映像,则一般将其拷贝到 MEM_START+0x0010,0000 开始的地方。如果用
Ramdisk 作为根文件系统映像,则其解压后的大小一般是 1MB。

b:从Flash中拷贝映像

while(count) {         

    *dest++ = *src++;  /* they are all aligned with word boundary */         

    count -= 4; /* byte number */ 

};

(4). 设置内核的启动参数

将内核映像拷贝到RAM中之后就可以启动了,但是一般都需要先设定Linux内核的启动参数。Linux2.4以后的内核都以标记列表(tagged list)的形式来传递启动参数。启动参数列表以标记ATAG_CORE开始,以标记ATAG_NONE结束。每个标记由标示被传递参数的tag_header结构以及随后的参数数据结构来组成。数据结构tag和tag_header定义在Linux内核源码的include/asm/setup.h头文件中。在嵌入式Linux系统中,通常需要由BootLoader设定的参数有:ATAG_CORE、ATAG_MEM、ATAG_CMDLINE、ATAG_RAMDISK、ATAG_INITRD。

比如,设置 ATAG_CORE 的代码如下:  

params = (struct tag *)BOOT_PARAMS;         

params->hdr.tag = ATAG_CORE;         

params->hdr.size = tag_size(tag_core);         

params->u.core.flags = 0;         

params->u.core.pagesize = 0;         

params->u.core.rootdev = 0;         

params = tag_next(params);  

其中,BOOT_PARAMS 表示内核启动参数在内存中的起始基地址,指针 params 是一个 struct tag 类型的指针。宏 tag_next() 将以指向当前标记的指针为参数,计算紧临当前标记的下一个标记的起始地址。注意,内核的根文件系统所在的设备 ID 就是在这里设置的。

(5). 调用内核

BootLoader调用内核的方法是直接跳转到内核的第一条指令处,即直接跳到MEM_START+0x8000处。在跳转的时候要满足下面的条件:

a: CPU寄存器的设置

R0 = 0;

R1 = 机器类型ID,

b: CPU必须在SVC模式

c: Cache和MMU的设置:

MMU必须关闭

指令Cache可以打开也可以关闭

数据Cache必须关闭

说明:如果用 C 语言,可以像下列示例代码这样来调用内核:  

void (*theKernel)(int zero, int arch, u32 params_addr) = (void  (*)(int, int, 

u32))KERNEL_RAM_BASE; 

…… 

theKernel(0, ARCH_NUMBER, (u32) kernel_params_start);  

注意:theKernel()函数调用应该永远不返回的。如果这个调用返回,则说明出错。          

六. UBoot移植过程中串口没有显示或者显示乱码的原因

(1). boot loader 对串口的初始化设置不正确。 

(2). 运行在 host 端的终端仿真程序对串口的设置不正确, 这包括:波特率、奇偶校验、数据位和停止位等方面的设置。

关于BootLoader启动时串口能输出,但是启动内核后不能正确显示的原因:

(1). 内核编译时缺少配置对串口驱动的支持,或配置正确的串口驱动

(2). BootLoader的串口配置和内核的不一致

(3). 内核没有正确启动

七.总结

U-Boot,全称 Universal Boot Loader,是遵循GPL条款的开放源码项目。从FADSROM、8xxROM、PPCBOOT逐步发展演化而来。其源码目录、编译形式与Linux内核很相似,事实上,不少U-Boot源码就是相应的Linux内核源程序的简化,尤其是一些设备的驱动程序,这从U-Boot源码的注释中能体现这一点。

 参考网址:http://www.cnblogs.com/tianyou/archive/2013/03/23/2977781.html

时间: 2024-08-18 07:01:27

【ARM】Uboot代码分析的相关文章

Device Tree(三):代码分析【转】

转自:http://www.wowotech.net/linux_kenrel/dt-code-analysis.html Device Tree(三):代码分析 作者:linuxer 发布于:2014-6-6 16:03 分类:统一设备模型 一.前言 Device Tree总共有三篇,分别是: 1.为何要引入Device Tree,这个机制是用来解决什么问题的?(请参考引入Device Tree的原因) 2.Device Tree的基础概念(请参考DT基础概念) 3.ARM linux中和De

Alipay UED推出网站代码分析插件:Monster

Monster 是 Alipay UED 推出的网站代码分析.质量检测及评分的浏览器扩展,它能智能分析CSS.JS.HTML内容并生动形象展示网页得分情况(类似YSlow).它是一个开源项目,您可以在GoogleCode中心检出MonsterForChrome项目源代码.不久会推出Firefox版扩展. Monster主要检测规则: 检测是否有重复ID的标签: 检测是否使用内联标签嵌套块级标签,如a嵌套div: 检测https协议页面,是否使用了http协议的图片.JS.CSS等: 检测comp

Javascript日期级联组件代码分析及demo

最近研究下JS日期级联效果 感觉还不错,然后看了下kissy也正好有这么一个 组件,也看了下源码,写的还不错,通过google最早是在2011年 淘宝的虎牙(花 名)用原审JS写了一个(貌似据说是从YUI那边重构下的) 具体的可以看他的 博 客园 , 感觉kissy组件源码 思路也是和YUI类似 所以我今天的基本思路也和他们 的一样 只是通过自己分析下及用自己的方式包装下. 基本原理 1.传参中有 '年份下拉框dom节点', '月份下拉框dom节点', '天数下拉框dom 节点', "开始日期&

C语言中的数组和指针汇编代码分析实例

  这篇文章主要介绍了C语言中的数组和指针汇编代码分析实例,本文用一则C语言例子来得到对应的汇编代码,并一一注解每句汇编代码的含义,需要的朋友可以参考下 今天看<程序员面试宝典>时偶然看到讲数组和指针的存取效率,闲着无聊,就自己写了段小代码,简单分析一下C语言背后的汇编,可能很多人只注重C语言,但在实际应用当中,当出现问题时,有时候还是通过分析汇编代码能够解决问题.本文只是为初学者,大牛可以飘过~ C源代码如下: 代码如下: #include "stdafx.h" int

传智播客c/c++公开课学习笔记--C语言与木马恶意代码分析和360安全防护揭秘

黑客代码分析与预防 笔记 [课程简介] C/C++语言是除了汇编之外,最接近底层的计算机语言,目前windows,linux,iOS,Android等主流操作系统都是用C/C++编写的,所以很多病毒.木马也都是用C/C++实现的.课程的目的就是通过C语言揭秘木马和各种远程控制软件的实现原理以及如何防护.  [课程知识点] 1.木马入侵系统的方式: 2.木马入侵到宿主目标后的关键行为分析: 3.可信任端口以及端口扫描技术: 4.远程控制的实现代码实现: 5.恶意代码中使用TCP.UDP协议与防火墙

谁有基于用户的推荐系统或者协同过滤的算法和代码分析啊

问题描述 求个大数据的大神给个基于用户的推荐系统或者协同过滤的算法和代码分析啊我有部分代码但是不知道怎么在Eclipse上实现求解答啊1.publicclassAggregateAndRecommendReducerextendsReducer<VarLongWritable,VectorWritable,VarLongWritable,RecommendedItemsWritable>{...publicviodreduce(VarLongWritablekey,Iterable<Ve

免费的Lucene 原理与代码分析完整版下载

Lucene是一个基于Java的高效的全文检索库.那么什么是全文检索,为什么需要全文检索?目前人们生活中出现的数据总的来说分为两类:结构化数据和非结构化数据.很容易理解,结构化数据是有固定格式和结构的或者有限长度的数据,比如数据库,元数据等.非结构化数据则是不定长或者没有固定格式的数据,如图片,邮件,文档等.还有一种较少的分类为半结构化数据,如XML,HTML等,在一定程度上我们可以将其按照结构化数据来处理,也可以抽取纯文本按照非结构化数据来处理.非结构化数据又称为全文数据.,对其搜索主要有两种

【C/C++学院】0907-象棋五子棋代码分析/寻找算法以及排序算法

象棋五子棋代码分析 编译代码报错: 错误 1 error MSB8031: Building an MFC project for a non-Unicode character set is deprecated. You must change the project property to Unicode or download an additional library. See http://go.microsoft.com/fwlink/p/?LinkId=286820 for mo

AngularJS PhoneCat代码分析

AngularJS 官方网站提供了一个用于学习的示例项目:PhoneCat.这是一个Web应用,用户可以浏览一些Android手机,了解它们的详细信息,并进行搜索和排序操作. 本文主要分析 AngularJS 官方网站提供的一个用于学习的示例项目 PhoneCat 的构建.测试过程以及代码的运行原理.希望能够对 PhoneCat 项目有一个更加深入全面的认识.这其中包括以下内容: 该项目如何运行起来的 该项目如何进行前端单元测试 AngularJS 相关代码分析 以下内容如有理解不正确,欢迎指正