ARM上电启动及Uboot代码分析

注意:由于文档是去年写的,内有多个图片,上传图片很麻烦(需要截图另存插入等等),我把文章的PDF版本上传到了CSDN下载资源中。为了给自己赚点积分,所以标价2分,没有积分的同学可以直接留言跟我要,记得留下邮箱。

以下是文章内容,由于我懒得编辑图片了,所以文章看来会很不爽,强烈推荐点击以上红色链接下载pdf版。

文件编号:DCC01

版本号:1.0

ARM上电启动及Uboot代码分析


部    门:


                        


作    者:


                    


联系方式:


                   


日    期:


   2013.03.08                        

文件修订记录


时间


作者


主要修订内容

目录

文件修订记录...
1

目录...
2

摘要...
4

1ARM上电取第一条指令流程...
5

1.1上电后的第一条指令在哪里?...
5

1.1.1norflash和nandflash的异同...
5

2Uboot.lds链接脚本分析...
7

2.1为什么要分析uboot链接脚本?...
7

2.2连接代码具体分析...
7

3Uboot中start.S文件分析...
9

3.1start.S详解...
9

3.1.1_start9

3.1.2reset9

3.1.3cpu_init_cp15.
11

3.1.4cpu_init_crit11

3.1.5lowlevel_init13

3.1.6s_init14

3.1.7call_board_init_f15

3.1.8board_init_f15

3.1.9relocate_code.
16

3.1.10clear_bss.
17

3.1.11jump_2_ram..
17

3.2本章小结...
18

4板级初始化及跳入Linux内核执行...
19

4.1board_init_r19

4.1.1三级标题...
19

4.2本章小结...
19

5Uboot异常处理...
20

5.1Uboot异常向量表...
20

5.1.1异常处理入口函数...
20

5.1.2异常处理函数跳转...
21

5.1.3异常真正处理函数...
22

5.2本章总结...
24

结论...
25

参考文献...
26

问题总结及解答...
27

附录...
29

摘要

网上关于ARM的bootloader(以Uboot为例)的启动顺序的资料有好多,但是对于Uboot的地址映射、体系结构级操作介绍很少,都是直接开始Start.s代码的阅读。本文拟详细分析Uboot从上电,到第一条指令的执行,同时分析代码对于cache、TLB等部件的操作过程。

以下内容以u-boot-2012.04.01源码为例,从网上很容易下载该版本。

1 ARM上电取第一条指令流程

1.1 上电后的第一条指令在哪里?

首先明确:对于ARM芯片,启动时pc值由CPU设计者规定,不同的ARM CPU有不同的值,例如S3C2440芯片上电后PC值被硬件设计者规定为0x0;其他ARM芯片不一定是0x0。

第一章讲述的上电取第一条指令过程以S3C2440为例,该芯片是ARMv4T架构,其他芯片在原理上类似。

S3C2440的启动时读取的第一条指令是在内存0x00地址处,不管是从nand flash还是nor flash启动。

但是上电后内存中是没有数据的,那么0x00地址处的指令是如何放进去的?针对不同的flash(nandflash、norflash),操作方式是不同的,下面讲述从nandflash和norflash启动的不同流程。

1.1.1  norflash和nandflash的异同

nandflash:价格低,容量大,适合大容量数据存储,地址线和数据线共用I/O线,所有信息都通过一条线传送,类比于PC的硬盘,

norflash:价格贵,容量小,适合小容量的程序或数据存储,类似硬盘,但是能在其中运行程序;有独立地址线、数据线

sdram:主要用于程序执行时的程序存储、执行或计算,类比于PC的内存;

综上:norflash比较适合频繁随即读写的场合,通常用于存储代码并直接在其中运行。nandflash用于存储资料。

只要知道以上大概区别就行。以下说明ARM从两种flash启动方式的异同。

1.1.1.1  ARM从nandFlash启动

若从nandflash启动,上电后nandflash控制器自动把nandflash存储器中的0——4K内容加载到芯片内的起步石(Steppingstone,起步石这个机制是处理器中集成的功能,对程序员透明),即内部SRAM缓冲器中,同时把内部SRAM的起始地址设置为0x0(不同的CPU上电后的PC值不尽相同,对不同的CPU该值也不尽相同),然后把这段片内SRAM映射到nGCS0片选的空间,进而CPU开始从内部SRAM的0x0处开始取得第一条指令,该过程全部是硬件自动完成,不需要程序代码控制。

或许你有个疑问,为什么不能直接把nandflash映射到0x0地址处?非要经过内部SRAM缓冲?

答案是,nandflash根本没有地址线,没法直接映射,必须使用SRAM做一个载体,通过SRAM把剩余的nandflash代码(即剩余的uboot启动代码)复制到SDRAM中运行。

若想从nandflash启动,那么uboot最核心的代码必须放在前4k完成。这4k代码要完成ARM CPU的核心配置以及将剩余的代码拷贝到SDRAM中(若从norflash启动则没有4k这个大小的限制,但是还会在完成最主要的设置后进入SDRAM中运行)。

1.1.1.2 ARM从norflash启动

若从norflash启动,则norflash直接被映射到内存的0x0地址处(就是nGCS0,这里就不需要片内SRAM来辅助了,所以片内SRAM的起始地址不变,还是0x40000000),然后cpu从0x00000000开始执行(也就是在Norfalsh中执行)。

需要说明的是,uboot代码段(.text段)起始位置必须是与上电后PC值一致,即编译uboot时,TEXT_BASE宏必须设置成0x0 ,反汇编uboot文件后,文本段第一条指令的地址也是0.

总结:

1、从norflash还是从nandflash启动,是由ARM的OM1和OM0引脚组合决定

2、不管从norflash还是nandflash启动,S3C2440上电后的pc值为0x0

3、如果某芯片上电后PC值不是0x0,假如是0x38ff0000,那么从norflash启动时,硬件就要自动将其映射到0x38ff0000地址处;如果从nandflash启动,那么硬件就要自动将nandflash中的前4K内容加载到0x38ff0000地址处。

2 Uboot.lds链接脚本分析

2.1 为什么要分析uboot链接脚本?

因为u-boot.lds决定了u-boot可执行映像的链接方式,以及各个段的装载地址(装载域)和执行地址(运行域),也就是说,Uboot.lds文件指定uboot.bin可执行文件放到ROM中的哪个地址、在运行时在RAM中运行的起始地址,具体内容涉及装载域和运行域的概念,这里不详述。

2.2 连接代码具体分析

以u-boot-2012.04.01版本为例。

总结:

1、SECTION后面的段都是按照顺序放到内存中的,例如text段后面跟着rodata段

2、该文件中没有指定段的加载地址(用AT命令),没指定的情况下加载地址和执行地址是相同的,也就是说在uboot.bin在rom和ram中的地址相同。

3 Uboot中start.S文件分析

3.1  start.S详解

上面分析的链接脚本中已经规定,首先启动的文件是arch/arm/cpu/armv7/start.S。对于uboot的start.S,主要做的事情就是系统的各个方面的初始化,然后复制剩余代码到RAM中继续运行。

(1)    设置CPU模式

(2)    关闭cache, MMU, TLBs

(3)    设置栈,pll, mux, memory

(4)    设置watchdog, muxing,  and clocks

(5)    板级初始化

(6)    自我拷贝到RAM中,并跳转到RAM中继续运行。

以下内容按照程序执行流程进行讲解,以ARMv7架构为例。

3.1.1 _start

文本段第一条指令就是一条跳转指令:

根据1.1章的分析,如果uboot是烧写到norflash中的,那么一上电的_start标号肯定是在0x0处,如果uboot已经启动,程序运行过了relocate(见后面),那么该标号就会被移动到TEXT_BASE标号处,该标号是编译uboot时程序员指定的,具体数值见开发板的board/~/config.mk文件。

3.1.2 reset

CPSR的位域见ARM手册,下图截图方便参考:

3.1.3 cpu_init_cp15

3.1.4 cpu_init_crit

3.1.5lowlevel_init

3.1.6 s_init

3.1.7 call_board_init_f

3.1.8board_init_f

3.1.9 relocate_code

为什么uboot代码需要relocate?见问题总结及解答。

3.1.10 clear_bss

3.1.11jump_2_ram

3.2 本章小结

主要分析了uboot启动的第一阶段代码。

4 板级初始化及跳入Linux内核执行

4.1 board_init_r

该函数在u-boot-2012.04.01\arch\arm\cpu\armv7\omap-common\spl.c中。

其功能是:

(1)    初始化内存分配函数

(2)    如果系统有mmc设备,则初始化mmc设备

(3)    如果系统有nand设备,则初始化nand设备

(4)    进入uboot命令循环或者直接开始执行linux内核。

目前暂时不需要详细分析该部分代码,后期若需要会加上。

千万不要删除行尾的分节符,此行不会被打印。“结论”以前的所有正文内容都要编写在此行之前。

5 Uboot异常处理

5.1 Uboot异常向量表

紧跟b reset后面的就是异常向量表,发生异常后pc会被自动置为相应的值,进入相应异常处理程序。

5.1.1异常处理入口函数

5.1.2 异常处理函数跳转

/*

* exception handlers

*/

.align     5

undefined_instruction:

get_bad_stack

bad_save_user_regs

bl    do_undefined_instruction

.align     5

software_interrupt:

get_bad_stack_swi

bad_save_user_regs

bl    do_software_interrupt

.align     5

prefetch_abort:

get_bad_stack

bad_save_user_regs

bl    do_prefetch_abort

.align     5

data_abort:

get_bad_stack

bad_save_user_regs

bl    do_data_abort

.align     5

not_used:

get_bad_stack

bad_save_user_regs

bl    do_not_used

#ifdef CONFIG_USE_IRQ        //如果在uboot中启用了用户中断,则跳入相应处理函

                         //数运行

.align     5

irq:

get_irq_stack

irq_save_user_regs

bl    do_irq

irq_restore_user_regs

.align     5

fiq:

get_fiq_stack

/* someone ought to write a more effective fiq_save_user_regs*/

irq_save_user_regs

bl    do_fiq

irq_restore_user_regs

#else            //如果没有配置,则走另一条路径。

.align     5

irq:

get_bad_stack

bad_save_user_regs

bl    do_irq

.align     5

fiq:

get_bad_stack

bad_save_user_regs

bl    do_fiq

#endif /* CONFIG_USE_IRQ */

#endif /* CONFIG_SPL_BUILD*/

5.1.3 异常真正处理函数

// u-boot-2012.04.01\arch\arm\lib\Interrupts.c

void do_undefined_instruction (struct pt_regs *pt_regs)

{

printf ("undefinedinstruction\n");

show_regs (pt_regs);

bad_mode ();

}

void do_software_interrupt (struct pt_regs *pt_regs)

{

printf ("software interrupt\n");

show_regs (pt_regs);

bad_mode ();

}

void do_prefetch_abort (struct pt_regs *pt_regs)

{

printf ("prefetchabort\n");

show_regs (pt_regs);

bad_mode ();

}

void do_data_abort (struct pt_regs *pt_regs)

{

printf ("dataabort\n");

show_regs (pt_regs);

bad_mode ();

}

void do_not_used (struct pt_regs *pt_regs)

{

printf ("notused\n");

show_regs (pt_regs);

bad_mode ();

}

void do_fiq (struct pt_regs *pt_regs)

{

printf ("fastinterrupt request\n");

show_regs (pt_regs);

bad_mode ();

}

#ifndef CONFIG_USE_IRQ

void do_irq (struct pt_regs *pt_regs)

{

printf ("interruptrequest\n");

show_regs (pt_regs);

bad_mode ();

}

#endif

void bad_mode (void)         //以上异常处理函数都跳转到bad_mode,该函数只是

                         //挂起CPU,木有具体处理。

{

      panic ("Resetting CPU ...\n");

      reset_cpu (0);

}

 

5.2 本章总结

总结uboot下异常处理流程,发现ARMv7下的uboot没有实现异常处理,ARM的其他架构有,有可能是因为uboot代码不够新的原因。Uboot中的ARM异常处理流程都相同。

结论

参考文献

[1]

[2]

[3]

千万不要删除行尾的分节符,此行不会被打印。

问题总结及解答

1、对于加载时地址和运行时地址不同的段,运行时它是怎么跳转到运行时地址的?

答: 连接地址<==>运行地址

存储地址<==>加载地址

(1)对于有操作系统时,运行地址与加载地址不同,在加载过程中装载器就把段加载到它应该去的连接地址处(也就是生成该段时的运行地址)

(2)对于uboot,运行地址与加载地址不同时,需要它自己(例如前4k代码)将自己加载到运行地址处执行。

Uboot.lds文件中起始地址是0x00,但是config.mk中的TEXT_BASE是0x57e00000,但是生成的uboot反汇编文件中,为什么start.s的第一条指令地址也是0x57e00000?不应该是0x00么?因为start.s的加载地址和运行地址都是0x00啊!?

答:Uboot.lds的0x00:

跟在SECTION后面的第一条

location counter,总是默认初始化为0。config.mk中的TEXT_BASE就是ROM在CPU上的地址,也就是说,不同的CPU已经规定了不同的ROM地址

2、关于为何不能直接用mov指令,而非要用adr伪指令?

把所有uboot代码拷贝到内存新地址处。

在分析uboot的start.S中,看到一些指令,比如:

adr r0, _start

觉得好像可以直接用mov指令实现即可,为啥还要这么麻烦地,去用ldr去实现?

关于此处的代码,为何要用adr指令:

adr r0, _start

其被编译器编译后,会被翻译成:sub r0, pc, #172

而不直接用mov指令直接将_start的值赋值给r0,类似于这样:

mov r0, _start

呢?

其原因主要是,

sub r0, pc, #172

这样的代码,所处理的值,都是相对于PC的偏移量来说的,这样的代码中,没有绝对的物理地址值,都是相对的值,利用产生位置无关代码。因为如果用mov指令:

mov r0, _start

那么就会被编译成这样的代码:

mov r0, 0x33d00000

如果用了上面这样的代码:

mov r0, 0x33d00000

那么,如果整个代码,即要执行的程序的指令,被移动到其他位置,那么

mov r0, 0x33d00000

这行指令,执行的功能,就是跳转到绝对的物理地址,而不是跳转到相对的_start的位置了,就不能实现我们想要的功能了,这样包含了绝对物理地址的代码,也就不是位置无关的代码了。

与此相对,这行指令:

sub r0, pc, #172

即使程序被移动到其他位置,那么该行指令还是可以跳转到相对PC往前172字节的地方,也还是我们想要的_start的位置,这样包含的都是相对的偏移位置的代码,就叫做位置无关代码。其优点就是不用担心你的代码被移动,即使程序的基地址变了,所有的代码的相对位置还是固定的,程序还是可以正常运行的。

关于,之所以不用上面的:

mov r0, 0x33d00000

类似的代码,除了上面说的,不是位置无关的代码之外,其还有个潜在的问题,那就是,关于mov指令的源操作数,此处即为0x33d00000,不一定是合法的mov 指令所允许的值。

【总结】

之所以用adr而不用mov,主要是为了生成地址无关代码,以及由于不方便判断一个数,是否是有效的mov的操作数。

3、为什么uboot代码需要relocate?

因为uboot启动时不在片外RAM中,为了加快运行,需要将uboot重新拷贝到RAM中运行。

附录

千万不要删除行尾的分节符,此行不会被打印!

ARM上电启动及Uboot代码分析,布布扣,bubuko.com

时间: 2024-11-08 23:32:28

ARM上电启动及Uboot代码分析的相关文章

10th.U-boot——代码结构分析(一)

简介 U-Boot(Universal Boot Loader),通用BootLoader,一是可以引导多种操作系统,二是支持多种架构CPU. 教程中是在sourcefoge网站上下载的U-boot-1.1.6源码,进行分析与移植. 结构分析 U-boot-1.1.6有26个子目录,可以简单分为四类: 1.平台相关或开发板相关 2.通用的函数 3.通用的设备驱动程序 4.U-boot工具,示例程序,文档. 目前U-boot-1.1.6支持10种架构.很容易找到与自己板子相似的配置,在上面稍作修改

【嵌入式开发】 Bootloader 详解 ( 代码环境 | ARM 启动流程 | uboot 工作流程 | 架构设计)

作者 : 韩曙亮 博客地址 : http://blog.csdn.net/shulianghan/article/details/42462795 转载请著名出处 相关资源下载 :  -- u-boot 源码 : http://download.csdn.net/detail/han1202012/8342761 -- S3C2440 文档 : http://download.csdn.net/detail/han1202012/8342701 -- S5PV210_iROM_Applicati

linux-3.2.36内核启动1-启动参数(arm平台 启动参数的获取和处理,分析setup_arch)【转】

转自:http://blog.csdn.net/tommy_wxie/article/details/17093297 最近公司要求调试一个内核,启动时有问题,所以就花了一点时间看看内核启动. 看的过程中总结了一点东西,希望可以帮助大家调试内核. 当我开始看的时候,第一件事是从网上搜集资料,不看不知道,一看吓一跳!牛人太多了,像这种内核启动的上古代码早就被人分析的彻彻底底.这注定我写的只能是烂微博了. 为了此微博有存在的必要,我会显示内核启动打印的代码位置(用绿色表示)及出现错误打印的原因(用红

嵌入式linux开发uboot移植(三)——uboot启动过程源码分析

嵌入式linux开发uboot移植(三)--uboot启动过程源码分析 一.uboot启动流程简介 与大多数BootLoader一样,uboot的启动过程分为BL1和BL2两个阶段.BL1阶段通常是开发板的配置等设备初始化代码,需要依赖依赖于SoC体系结构,通常用汇编语言来实现:BL2阶段主要是对外部设备如网卡.Flash等的初始化以及uboot命令集等的自身实现,通常用C语言来实现. 1.BL1阶段 uboot的BL1阶段代码通常放在start.s文件中,用汇编语言实现,其主要代码功能如下:

ARM Linux 大小核切换 ——cortex-A7 big.LITTLE 大小核 切换代码分析

ARM Linux 大小核切换——cortex-A7 big.LITTLE 大小切换代码分析 8核CPU或者是更多核的处理器,这些CPU有可能不完全对称.有的是4个A15和4个A7,或者是4个A57和4个A53,甚至像海思麒麟935处理器(4核A53 2.2 GHz + 4核A53 1.5 GHz),这8个核的频率可能不一样,则使用过程中需要大小核切换(频率高的是大核,频率低的是小核).本文以ARM cortex-A7为例,分析大小核切换的代码,着重于分析实现切换的代码,对于为什么要这样切换.以

MPC8313ERDB在Linux从NAND FLASH读取UBoot环境变量的代码分析

[email protected] 一.故事起因 因为文件系统的增大,已经大大的超出了8MB的NOR FLASH,而不得不把内核,文件系统和设备树文件保存到NAND FLASH上.但是因为使用的是RAMDISK,而无法保存一些个别的配置和参数,最简单的需要就是设置系统的IP了,,, 要使用统一的RAMDISK,而实现LINUX启动之后,设置成不能的参数功能,比较方便的就是从UBOOT把这些参数传递过去,这个得到了大家的认证,我们可以直接添加启动参数,然后在内核里面读出来,这种方法比较方法,唯一不

六、uboot 代码流程分析---start.S

6.1 _start 入口函数 6.1.1 vectors.S (arch\arm\lib) 从上一节可以知道,uboot 的入口函数为 _start .此 函数定义在 vectors.S (arch\arm\lib) 中. 在此文件中,定义了异常向量表,及其操作函数._start 开始后,直接跳入  reset 复位中执行启动. 1 /* 头文件包含,包含架构和配置相关的头文件,自动生成的 */ 2 #include <config.h> 3 4 /* 5 * A macro to allo

NodeManager代码分析之NodeManager启动过程

1.NodeManager概述 NodeManager(NM)是YARN中每个节点上的代理,它管理Hadoop集群中单个计算节点,包括与ResourceManger保持通信,监督Container的生命周期管理,监控每个Container的资源使用(内存.CPU等)情况,追踪节点健康状况,管理日志和不同应用程序用到的附属服务. NodeManager整体架构: 2.NodeManager分析 接下来将按照启动NodeManager时代码执行的顺序为主线进行代码分析. 2.1 main函数 打印N

S5PV210 uboot中的 MMU代码分析

1:经过上一节的分析,如果采用SECTION虚拟地址映射的话: 程序员只需要做的事情: (1) 建立转换表,Tanslation Table: (2) 将TTB(转换表基地址Tanslation Table Base)写入协处理器CP15的C2寄存器,这里要注意转换表 基地址为16K对齐的(因为4096*32bit=16K)所以TTB的bit0-bit13为0. (3) 使能MMU,将CP15的C1寄存器0bit写1: (4) 设置域的访问权限:设置C3寄存器: CPU/MMU做的事情: (1)