uboot启动代码详解_第1页
uboot启动代码详解_第2页
uboot启动代码详解_第3页
uboot启动代码详解_第4页
uboot启动代码详解_第5页
已阅读5页,还剩45页未读 继续免费阅读

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

1、1 引言在专用的嵌入式板子运行 GNU/Linux 系统已经变得越来越流行。一个嵌入式 Linux 系统从软件的角度看通常可以分为四个层次:1. 引导加载程序。固化在固件(firmware)中的 boot 代码,也就是 Boot Loader,它的启动通常分为两个阶段。2.Linux 内核。特定于嵌入式板子的定制内核以及内核的启动参数。3.文件系统。包括根文件系统和建立于 Flash 内存设备之上文件系统,root fs。4.用户应用程序。特定于用户的应用程序。有时在用户应用程序和内核层之间可能还会包括一个嵌入式图形用户界面。常用的嵌入式 GUI 有:MicroWindows 和 MiniGU

2、I 等。引导加载程序是系统加电后运行的第一段软件代码。回忆一下 PC 的体系结构我们可以知道,PC 机中的引导加载程序由 BIOS(其本质就是一段固件程序)和位于硬盘 MBR 中的 OS Boot Loader(比如,LILO 和 GRUB 等)一起组成。BIOS 在完成硬件检测和资源分配后,将硬盘 MBR 中的 Boot Loader 读到系统的 RAM 中,然后将控制权交给 OS Boot Loader。Boot Loader 的主要运行任务就是将内核映象从硬盘上读到 RAM 中,然后跳转到内核的入口点去运行,也即开始启动操作系统。而在嵌入式系统中,通常并没有像 BIOS 那样的固件程序(

3、注,有的嵌入式 CPU 也会内嵌一段短小的启动程序),因此整个系统的加载启动任务就完全由 Boot Loader 来完成。比如在一个基于 ARM7TDMI core 的嵌入式系统中,系统在上电或复位时通常都从地址 0x 处开始执行,而在这个地址处安排的通常就是系统的 Boot Loader 程序。2 bootloader简介应用程序文件系统操作系统内核BootLoader简单地说,Boot Loader (引导加载程序)就是在操作系统内核运行之前运行的一段小程序,它的作用就是加载操作系统,它是系统加电后运行的第一段软件代码。通过这段代码实现硬件的初始化,建立内存空间的映射图,为操作系统内核准备

4、好硬件环境并引导内核的启动。如上图所示的那样在设备的启动过程中bootloader位于最底层,首先被运行来引导操作系统运行,很容易可以看出 bootloader是底层程序所以它的实现严重地依赖于硬件,特别是在嵌入式世界。因此,在嵌入式世界里建立一个通用的BootLoader几乎是不可能的。尽管如此,一些功能强大、支持硬件环境较多的BootLoader也被广大的使用者和爱好者所支持,从而形成了一些被广泛认可的、较为通用的的bootloader实现。2.1 Boot Loader 所支持的 CPU 和嵌入式板每种不同的 CPU 体系结构都有不同的 Boot Loader。有些 Boot Loade

5、r 也支持多种体系结构的 CPU,比如 U-Boot 就同时支持 ARM 体系结构和MIPS 体系结构。除了依赖于 CPU 的体系结构外,Boot Loader 实际上也依赖于具体的嵌入式板级设备的配置。这也就是说,对于两块不同的嵌入式板而言,即使它们是基于同一种 CPU 而构建的,要想让运行在一块板子上的 Boot Loader 程序也能运行在另一块板子上,通常也都需要修改 Boot Loader 的源程序。2.2 Boot Loader 的安装媒介(Installation Medium)系统加电或复位后,所有的 CPU 通常都从某个由 CPU 制造商预先安排的地址上取指令。比如,基于 A

6、RM7TDMI core 的 CPU 在复位时通常都从地址 0x 取它的第一条指令。而基于 CPU 构建的嵌入式系统通常都有某种类型的固态存储设备(比如:ROM、EEPROM 或 FLASH 等)被映射到这个预先安排的地址上。因此在系统加电后,CPU 将首先执行 Boot Loader 程序。下图1就是一个同时装有 Boot Loader、内核的启动参数、内核映像和根文件系统映像的固态存储设备的典型空间分配结构图:图1 固态存储设备的典型空间分配结构2.3 Boot Loader 的启动过程:单阶段(Single Stage)/多阶段(Multi-Stage)通常多阶段的 Boot Loade

7、r 能提供更为复杂的功能,以及更好的可移植性。从固态存储设备上启动的 Boot Loader 大多都是 2 阶段的启动过程,也即启动过程可以分为 stage 1 和 stage 2 两部分。而至于在 stage 1 和 stage 2 具体完成哪些任务将在下面讨论。2.4 Boot Loader 的操作模式 (Operation Mode)大多数 Boot Loader 都包含两种不同的操作模式:启动加载模式和下载模式,这种区别仅对于开发人员才有意义。但从最终用户的角度看,Boot Loader 的作用就是用来加载操作系统,而并不存在所谓的启动加载模式与下载工作模式的区别。启动加载(Boot

8、loading)模式:这种模式也称为自主(Autonomous)模式。也即 Boot Loader 从目标机上的某个固态存储设备上将操作系统加载到 RAM 中运行,整个过程并没有用户的介入。这种模式是 Boot Loader 的正常工作模式,因此在嵌入式产品发布的时侯,Boot Loader 显然必须工作在这种模式下。下载(Downloading)模式:在这种模式下,目标机上的 Boot Loader 将通过串口连接或网络连接等通信手段从主机(Host)下载文件,比如:下载内核映像和根文件系统映像等。从主机下载的文件通常首先被 Boot Loader 保存到目标机的 RAM 中,然后再被 Bo

9、ot Loader 写到目标机上的FLASH 类固态存储设备中。Boot Loader 的这种模式通常在第一次安装内核与根文件系统时被使用;此外,以后的系统更新也会使用 Boot Loader 的这种工作模式。工作于这种模式下的 Boot Loader 通常都会向它的终端用户提供一个简单的菜单界面或命令行接口来接收要执行的操作。像 Blob 或 U-Boot 等这样功能强大的 Boot Loader 通常同时支持这两种工作模式,而且允许用户在这两种工作模式之间进行切换。比如,Blob 在启动时处于正常的启动加载模式,但是它会延时 10 秒等待终端用户按下任意键而将 blob 切换到下载模式。如

10、果在 10 秒内没有用户按键,则 blob 继续启动 Linux 内核。2.5 常见的Boot LoaderU-BOOT:U-Boot是Das U-Boot的简称,其含义是Universal Boot Loader,是遵循GPL条款的开放源码项目。uboot是一个庞大的公开源码的软件。它支持一些系列的arm体系,包含常见的外设的驱动,是一个功能强大的板极支持包。vivi:vivi是韩国mizi 公司开发的bootloader, 适用于ARM9处理器。 Vivi也有两种工作模式:启动加载模式和下载模式。启动加载模式可以在一段时间后(这个时间可更改)自行启动linux内核,这是vivi的默认模式。

11、如果修改或更新需要进入下载模式,在下载模式下,vivi为用户提供一个命令行接口通过接口可以使用vivi提供的一些命令,来实现flash的烧写、管理、操作mtd分区信息、启动系统等功能。2.6 U-BOOT的目录结构目录说明board和开发板相关的文件,每一个开发板都以一个子目录出现在当前目录中,比如:smdk2410。该子目录中存放于开发板相关的配置文件,如makefile和U-Boot.lds。其中包含SDRAM初始化代码、Flash底层驱动、板级初始化文件。config.mk定义了TEXT_BASE是代码在内存的真实地址common与体系结构无关的文件,实现各种命令的C文件。该文件主要实现

12、uboot命令行下支持的命令,每一条命令都对应一个文件。例如bootm命令对应就是cmd_bootm.c。cpu与特定CPU架构相关目录,每一款uboot下支持的CPU在该目录下对应一个子目录,比如arm920t。每个CPU子目录中都包括cpu.c和interrupt.c、start.S。cpu.c初始化CPU、设置指令Cache和数据Cache等interrupt.c设置系统的各种中断和异常start.S是U-boot启动时执行的第一个文件,它主要做早期系统初始化,代码重定向和设置系统堆栈diskDisk分区处理代码,对磁盘的支持doc文档目录,uboot有非常完整的文档driversUbo

13、ot支持的设备驱动程序都放在该目录,例如各种网卡、支持CFI的Flash、串口、USB等fs支持的文件系统,目前支持cramfs、fat、fdos、jffs2和registerfsinclude头文件,还有对各种硬件平台支持的汇编文件,系统配置文件和对文件系统支持的文件等。该目录下的configs目录有与开发板相关的配置文件,如smdk2410.h。该目录下的asm目录有与CPU体系结构相关的头文件,比如arm对应的就是与网络协议栈相关的代码,BOOTP协议、TFTP协议、RARP和NFS文件系统的实现等lib_xxx与ARM体系结构相关的库文件。如与arm相关的库放在l

14、ib_arm中tools生成uboot的工具,如mkimage,crc等等3 Boot Loader 的主要任务与典型结构框架从操作系统的角度看,Boot Loader 的总目标就是正确地调用内核来执行。另外,由于 Boot Loader 的实现依赖于 CPU 的体系结构,因此大多数 Boot Loader 都分为 stage1 和 stage2 两大部分。依赖于 CPU 体系结构的代码,比如设备初始化代码等,通常都放在 stage1 中,而且通常都用汇编语言来实现,以达到短小精悍的目的。而 stage2 则通常用C语言来实现,这样可以实现给复杂的功能,而且代码会具有更好的可读性和可移植性。以

15、u-boot为例,它启动过程的两个阶段(stage) 如下:第一阶段(stage 1) cpu/arm920t/start.S依赖于CPU体系结构的代码(如设备初始化代码等),一般用汇编语言来实现。主要进行以下方面的设置:设置ARM进入SVC模式、禁止IRQ和FIQ、关闭看门狗、屏蔽所有中断。设置时钟(FCLK,HCLK,PCLK)、清空I/D cache、清空TLB、禁止MMU和cache、配置内存控制器、为搬运代码做准备、搬移uboot映像到RAM中(使用copy_loop实现)、分配堆栈、清空bss段(使用clbss_l实现)。最后通过ldr pc, _start_armboot跳转到第

16、二阶段。第二阶段(stage 2) lib_arm/board.c该阶段主要都是用语言来实现。start_armboot()进行一系列初始化(cpu, 板卡,中断,串口,控制台等),开启I/D cache。初始化FLASH,根据系统配置执行其他初始化操作。打印LOG,使能中断,获取环境变量,初始化网卡。最后进入main_loop()函数。综上所述,可简单的归纳两个阶段的功能如下:第一阶段的功能: 硬件设备初始化 加载U-Boot第二阶段代码到RAM空间 设置好栈 跳转到第二阶段代码入口第二阶段的功能: 初始化本阶段使用的硬件设备 检测系统内存映射 将内核从Flash读取到RAM中 为内核设置启

17、动参数 调用内核U-Boot启动第一阶段流程如下:3.1 u-boot 的 stage1详细分析uboot的第一阶段设计的非常巧妙,几乎都是用汇编语言实现的。首先我们来看一下它的链接脚本(u-boot-1.1.6boardsmdk2410u-boot.lds),通过它我们可以知道它整个程序的各个段是怎么存放的。它定义了整个程序编译之后的连接过程,决定了一个可执行程序的各个段的存储位置。/* 指定输出可执行文件是elf 格式,32 位ARM 指令,小端 */OUTPUT_FORMAT(elf32-littlearm, elf32-littlearm, elf32-littlearm)/* 指定输

18、出可执行文件的平台架构为ARM架构 */OUTPUT_ARCH(arm)/* 指定输出可执行文件的起始代码段为_start */ENTRY(_start)SECTIONS. = 0x;/入口地址. = ALIGN(4);/四字节对齐.text :/代码段,上面3行标识是不占任何空间的cpu/arm920t/start.o(.text)/这里将start.o放在第一位就表示把start.s编译时放在最开始,也就是uboot启动是最先执行start.S*(.text)/所有的其他程序的代码段以四字节对齐放在它后面. = ALIGN(4);/前面的“.”表示当前值.rodata : *(.rodat

19、a) /只读数据段. = ALIGN(4);.data : *(.data) /指定读/写数据段. = ALIGN(4);.got : *(.got) /指定got段,got段式是uboot自定义的一个段,非标准段. = .;_u_boot_cmd_start = .;/把_u_boot_cmd_start赋值为当前位置,即起始位置.u_boot_cmd : *(.u_boot_cmd) /指定u_boot_cmd段,uboot把所有的uboot命令放在该段_u_boot_cmd_end = .;/把 _u_boot_cmd_end赋值为当前位置,即结束位置. = ALIGN(4);_bss_

20、start = .;/_bss_start赋值为当前位置,即bss段得开始位置.bss : *(.bss) _end = .;/把_end赋值为当前位置,即bss段得结束地址从上面这段代码我们可以看出uboot运行的第一个程序是cpu/arm920t/start.S里面的第一个段_start。我们查看start.S的源码。3.1.1硬件设备初始化(1)设置异常向量cpu/arm920t/start.S开头有如下的代码:/global用于声明一个符号可被其他文档引用,相当于声明了一个全局变量,.globl 和.global 相同。/该部分为处理器的异常处理向量表。地址范围为0x 0x,刚好8 条

21、指令。.globl _start/* u-boot启动入口 */_start:breset/* 复位*/ldrpc, _undefined_instruction/* 未定义指令向量 */ldrpc, _software_interrupt/* 软件中断向量 */ldrpc, _prefetch_abort/* 预取指令异常向量 */ldrpc, _data_abort/* 数据操作异常向量 */ldrpc, _not_used/* 未使用 */ldrpc, _irq/* irq中断向量 */ldrpc, _fiq/* fiq中断向量 */* 中断向量表入口地址 */.word 伪操作用于分配

22、一段字内存单元(分配的单元都是字对齐的),并用伪操作中的expr 初始化。.long 和.int 作用与之相同。_undefined_instruction:.word undefined_instruction_software_interrupt:.word software_interrupt_prefetch_abort:.word prefetch_abort_data_abort:.word data_abort_not_used:.word not_used_irq:.word irq_fiq:.word fiq.balignl 16,0xdeadbeef以上代码设置了ARM异常

23、向量表,各个异常向量介绍如下:地址异常进入模式描述0x复位管理模式复位电平有效时,产生复位异常,程序跳转到复位处理程序处执行0x未定义指令未定义模式遇到不能处理的指令时产生未定义指令异常0x软件中断管理模式执行SWI指令产生,用于用户模式下的程序调用特权操作指令0xc预存指令中止模式处理器预取指令的地址不存在,或该地址不允许当前指令访问,产生指令预取中止异常0x数据操作中止模式处理器数据访问指令的地址不存在或该地址不允许当前指令访问时,产生数据中止异常0x未使用未使用未使用0xIRQIRQ外部中断请求有效,且CPSR中的I位为0时,产生IRQ异常0xcFIQFIQ快速中断请求引脚有效,且CPS

24、R中的F位为0时,产生FIQ异常在cpu/arm920t/start.S中还有这些异常对应的异常处理程序。当一个异常产生时,CPU根据异常号在异常向量表中找到对应的异常向量,然后执行异常向量处的跳转指令,CPU就跳转到对应的异常处理程序执行。其中复位异常向量的指令“b reset”决定了U-Boot启动后将自动跳转到标号reset处执行。许多人都认为_start的值是0x,为什么是这个地址呢? 因为连接脚本上指定了。真的是这样吗?我们来看看我们编译好之后,在u-boot目录下有个System.map,这里面有各个变量的值,其中会告诉你,_start的值为:0x33f80000。注意,这里有两个

25、地址:编译地址和运行地址。什么是编译地址?什么是运行地址? 32 位的处理器,它的每一条指令是4个字节,以4个字节存储顺序,进行顺序执行,CPU是顺序执行的,只要没发生什么跳转,它会顺序进行执行,编译器会对每一条指令分配一个编译地址,这是编译器分配的,在编译过程中分配的地址,我们称之为编译地址。运行地址是指,程序指令真正运行的地址,是由用户指定的,用户将运行地址烧录到哪里,哪里就是运行的地址。编译地址和运行地址如何来算呢?假如有两个编译地址a=0x10,b=0x7,b的运行地址是0x300 ,那么a的运行地址就是b的运行地址加上两者编译地址的差值,a-b=0x10-0x7=0x3,a的运行地址

26、就是0x300+0x3=0x303。(2)CPU进入SVC模式start_code:/* * set the cpu to SVC32 mode */mrsr0, cpsrbicr0, r0, #0x1f/*工作模式位清零 */orrr0, r0, #0xd3/*工作模式位设置为“10011”(管理模式),并将中断禁止位和快中断禁止位置1 */msrcpsr, r0以上代码将CPU的工作模式位设置为管理模式,并将中断禁止位和快中断禁止位置一,从而屏蔽了IRQ和FIQ中断。(3)设置控制寄存器地址#if defined(CONFIG_S3C2400)#define pWTCON0x#define

27、 INTMSK0x#define CLKDIVN0x#else/* s3c2410与s3c2440下面4个寄存器地址相同 */#define pWTCON0x/* WATCHDOG控制寄存器地址*/#define INTMSK0x4A/* INTMSK寄存器地址 */#define INTSUBMSK0x4A00001C/* INTSUBMSK寄存器地址 */#define CLKDIVN0x4C /* CLKDIVN寄存器地址 */# endif对于s3c2440开发板,以上代码完成了WATCHDOG,INTMSK,INTSUBMSK,CLKDIVN四个寄存器的地址的设置。(4)关闭看门狗l

28、drr0, =pWTCONmovr1, #0x0strr1, r0/* 看门狗控制器的最低位为0时,看门狗不输出复位信号 */以上代码向看门狗控制寄存器写入0,关闭看门狗。为什么需要关闭看门狗呢?这里有个喂狗的过程,所谓的喂狗是每隔一段时间给某个寄存器置位而已,在实际中会专门启动一个线程或进程会专门喂狗,当上层软件出现故障时就会停止喂狗,停止喂狗之后,cpu会自动复位,一般都在外部专门有一个看门狗,做一个外部的电路,不在cpu内部使用看门狗,否则在U-Boot启动过程中,CPU将不断重启。(5)屏蔽中断/* * mask all IRQs by setting all bits in the

29、INTMR - default */movr1, #0xffffffff/* 某位被置1则对应的中断被屏蔽 */ldrr0, =INTMSKstrr1, r0INTMSK是主中断屏蔽寄存器,每一位对应SRCPND(中断源引脚寄存器)中的一位,表明SRCPND相应位代表的中断请求是否被CPU所处理。INTMSK寄存器是一个32位的寄存器,每位对应一个中断,向其中写入0xffffffff就将INTMSK寄存器全部位置1,从而屏蔽对应的中断。为什么要关闭中断呢?中断处理(ldr pc .)是将代码的编译地址放在了指针上,而这段时间内还没有搬移代码,所以不能进行跳转。#if defined(CONFI

30、G_S3C2440) ldr r1, =0x7fff ldr r0, =INTSUBMSK str r1, r0#endifINTSUBMSK每一位对应SUBSRCPND中的一位,表明SUBSRCPND相应位代表的中断请求是否被CPU所处理。INTSUBMSK寄存器是一个32位的寄存器,但是只使用了低15位。向其中写入0x7fff就是将INTSUBMSK寄存器全部有效位(低15位)置1,从而屏蔽对应的中断。(6)设置MPLLCON,UPLLCON, CLKDIVN#if defined(CONFIG_S3C2440)#define MPLLCON 0x4C#define UPLLCON 0x4

31、C ldr r0, =CLKDIVN mov r1, #5 str r1, r0 ldr r0, =MPLLCON ldr r1, =0x7F021 str r1, r0 ldr r0, =UPLLCON ldr r1, =0x38022 str r1, r0#else/* FCLK:HCLK:PCLK = 1:2:4 */* default FCLK is 120 MHz ! */ldrr0, =CLKDIVNmovr1, #3strr1, r0#endifCPU上电几毫秒后,晶振输出稳定,FCLK=Fin(晶振频率),CPU开始执行指令。但实际上,FCLK可以高于Fin,为了提高系统时钟,

32、需要用软件来启用PLL。这就需要设置CLKDIVN,MPLLCON,UPLLCON这3个寄存器。CLKDIVN寄存器用于设置FCLK,HCLK,PCLK三者间的比例如下:CLKDIVN位说明初始值HDIVN2:100 : HCLK = FCLK/1.01 : HCLK = FCLK/2.10 : HCLK = FCLK/4 (当 CAMDIVN9 = 0 时)HCLK= FCLK/8 (当 CAMDIVN9 = 1 时)11 : HCLK = FCLK/3 (当 CAMDIVN8 = 0 时)HCLK = FCLK/6 (当 CAMDIVN8 = 1时)00PDIVN00: PCLK = HC

33、LK/1 1: PCLK = HCLK/20设置CLKDIVN为5,就将HDIVN设置为二进制的10,由于CAMDIVN9没有被改变过,取默认值0,因此HCLK = FCLK/4。PDIVN被设置为1,因此PCLK= HCLK/2。因此分频比FCLK:HCLK:PCLK = 1:4:8 。MPLLCON寄存器用于设置FCLK与Fin的倍数。MPLLCON的位19:12称为MDIV,位9:4称为PDIV,位1:0称为SDIV。对于S3C2440,FCLK与Fin的关系如下面公式:MPLLCON与UPLLCON通常设置如下:输入频率输出频率MDIVPDIVSDIV12.0000MHz48.00 M

34、Hz56(0x38)2212.0000MHz405.00 MHz127(0x7f)21当s3c2440系统主频设置为405MHZ,USB时钟频率设置为48MHZ时,系统可以稳定运行,因此设置MPLLCON与UPLLCON为:MPLLCON=(0x7f12) | (0x024) | (0x01) = 0x7f021UPLLCON=(0x3812) | (0x024) | (0x02) = 0x38022(7)关闭MMU,cache接着往下看:#ifndef CONFIG_SKIP_LOWLEVEL_INITblcpu_init_crit#endifcpu_init_crit这段代码在U-Boot

35、正常启动时才需要执行,若将U-Boot从RAM中启动则应该注释掉这段代码。下面分析一下cpu_init_crit到底做了什么:#ifndef CONFIG_SKIP_LOWLEVEL_INITcpu_init_crit: /* * 使数据cache与指令cache无效 */ */ movr0, #0 mcrp15, 0, r0, c7, c7, 0/* 向c7写入0将使ICache与DCache无效*/ mcrp15, 0, r0, c8, c7, 0/* 向c8写入0将使TLB失效 */ /* * disable MMU stuff and caches */ mrcp15, 0, r0,

36、c1, c0, 0/* 读出控制寄存器到r0中 */ bicr0, r0, #0x clear bits 13, 9:8 (-V- -RS) bicr0, r0, #0x clear bits 7, 2:0 (B- -CAM) orrr0, r0, #0x set bit 2 (A) Align orrr0, r0, #0x set bit 12 (I) I-Cache mcrp15, 0, r0, c1, c0, 0/* 保存r0到控制寄存器 */ /* * before relocating, we have to setup RAM timing * because memory tim

37、ing is board-dependend, you will * find a lowlevel_init.S in your board directory. */ movip, lr bllowlevel_init movlr, ipmovpc, lr#endif /* CONFIG_SKIP_LOWLEVEL_INIT */代码中的c0,c1,c7,c8都是ARM920T的协处理器CP15的寄存器。其中c7是cache控制寄存器,c8是TLB控制寄存器。将0写入c7、c8,使Cache,TLB内容无效。通过修改CP15的c1寄存器来关闭了MMU。为什么要关闭catch 和MMU 呢?

38、catch 和MMU 是做什么用的? catch 是cpu内部的一个2级缓存,她的作用是将常用的数据和指令放在cpu内部,MMU是用来做虚实地址转换用的,我们的目的是设置控制的寄存器,寄存器都是实地址,如果既要开启MMU又要做虚实地址转换的话,中间还多一步,先要把实地址转换成虚地址,然后再做设置,但对uboot 而言就是起到一个简单的初始化的作用和引导操作系统,如果开启MMU 的话,很麻烦,也没必要,所以关闭MMU. 说到catch 就必须提到一个关键字Volatile,以后在设置寄存器时会经常遇到,他的本质是告诉编译器不要对我的代码进行优化,优化的过程是将常用的代码取出来放到catch中,它

39、没有从实际的物理地址去取,它直接从cpu的缓存中去取,但常用的代码就是为了感知一些常用变量的变化,所以在这种情况下要用Volatile关键字告诉编译器不要做优化,每次从实际的物理地址中去取指令。(8)初始化RAM控制寄存器其中的bl lowlevel_init用于初始化各个bank,完成了内存初始化的工作,由于内存初始化是依赖于开发板的,因此lowlevel_init的代码一般放在board下面相应的目录中。对于s3c2440,lowlevel_init在board/smdk2440/lowlevel_init.S中定义如下:#define BWSCON0x/* 13个存储控制器的开始地址 *

40、/ _TEXT_BASE: .wordTEXT_BASE.globl lowlevel_initlowlevel_init: /* memory control configuration */ /* make r0 relative the current location so that it */ /* reads SMRDATA out of FLASH rather than memory ! */ ldr r0, =SMRDATA ldrr1, _TEXT_BASE subr0, r0, r1/* SMRDATA减 _TEXT_BASE就是13个寄存器的偏移地址 */ ldrr1,

41、 =BWSCON/* Bus Width Status Controller */ add r2, r0, #13*40: ldr r3, r0, #4/*将13个寄存器的值逐一赋值给对应的寄存器*/ str r3, r1, #4 cmp r2, r0 bne 0b /* everything is fine now */ movpc, lr .ltorg /* the literal pools origin */SMRDATA:/* 下面是13个寄存器的值 */ .word .word lowlevel_init的作用就是将SMRDATA开始的13个值复制给开始地址BWSCON的13个寄存

42、器,从而完成了存储控制器的设置。(9)复制U-Boot第二阶段代码到RAMrelocate: adrr0, _start/* r0 - current position of code */ ldrr1, _TEXT_BASE/* test if we run from flash or RAM */* 判断U-Boot是否是下载到RAM中运行,若是,则不用再复制到RAM中了,这种情况通常在调试U-Boot时才发生 */ cmp r0, r1/*_start等于_TEXT_BASE说明是下载到RAM中运行 */ beqstack_setup ldrr2, _armboot_start ldrr

43、3, _bss_start subr2, r3, r2/* r2 - size of armboot */ addr2, r0, r2/* r2 - source end address */ /* 搬运U-Boot自身到RAM中*/copy_loop: ldmiar0!,r3-r10/* 从地址为r0的NOR Flash中读入8个字的数据 */ stmiar1!,r3-r10/* 将r3至r10寄存器的数据复制给地址为r1的内存 */ cmpr0, r2/* until source end addreee r2 */ blecopy_loop终于到重点部分了:代码重定向(拷贝stage2到

44、RAM中),拷贝时要确定两点:(1) stage2的可执行映象在固态存储设备的存放起始地址和终止地址;(2) RAM空间的起始地址。下面我们来看看它到底是怎么重定向的:adr r0,_startadr伪指令,汇编器会将执行到_start时PC的值放到r0中。所以此时r0中保存的不是编译地址,而是运行地址。假如U-boot 是从RAM 开始运行,则从adr,r0,_start 得到的地址信息为r0=_start=_TEXT_BASE=TEXT_BASE=0x33f80000;假如U-boot 从Flash 开始运行,即从处理器对应的地址运行,则r0=0x。而这里r0=0。ldr r1,_TEXT

45、_BASE这条汇编指令的意思是把_TEXT_BASE的值作为地址,把这个地址的内容赋给r1,从下面可以知道:_TEXT_BASE里面存储的内容是TEXT_BASE,我们通过查看board/smdk2410/config.mk发现TEXT_BASE的值为0x33f80000,所以r1的值就是0x33f80000cmp r0,r1将r0和r1做比较,此时r0 = 0x,r1 = 0x33f80000,显然不相等,那么执行的就是下面的汇编指令:ldr r2, _armboot_start由此可以知道r2的值是_start,通过ldr将标号的编译地址放到r2中,也就是0x33f80000,即代码段的起

46、始地址。ldr r3, _bss_start有此可知,r3就是_bss_start的值。由u-boot.lds的链接脚本可以知道,r3的值是整个代码得结尾.sub r2,r3,r2这条指令的意思是r2 = r3 -r2,即r2 = 代码结束 - 代码开始,这样得到的是r2 = 整个代码的大小。add r2,r0,r2这条指令的意思是r2 = r0 + r2,即 r2 = 代码开始 + 代码大小,这样得到的是r2 = falsh 里面代码的结尾,此时我们得到r0 = flash 代码的起始位置,r1 = 0x33f80000(sdram :0x 0x)将flash里面的代码拷贝到sdram里面了

47、。(10)设置堆栈/* 设置堆栈 */stack_setup:ldrr0, _TEXT_BASE/* upper 128 KiB: relocated uboot */subr0, r0, #CONFIG_SYS_MALLOC_LEN/* malloc area */subr0, r0, #CONFIG_SYS_GBL_DATA_SIZE /* 跳过全局数据区 */#ifdef CONFIG_USE_IRQsubr0, r0, #(CONFIG_STACKSIZE_IRQ+CONFIG_STACKSIZE_FIQ)#endifsubsp, r0, #12/* leave 3 words for

48、 abort-stack */只要将sp指针指向一段没有被使用的内存就完成栈的设置了。U-Boot内存使用情况如下图所示:(11)清除BSS段clear_bss:ldrr0, _bss_start/* BSS段开始地址,在u-boot.lds中指定*/ldrr1, _bss_end/* BSS段结束地址,在u-boot.lds中指定*/movr2, #0xclbss_l:strr2, r0/* 将bss段清零*/addr0, r0, #4cmp r0, r1bleclbss_l初始值为0,无初始值的全局变量,静态变量将自动被放在BSS段。应该将这些变量的初始值赋为0,否则这些变量的初始值将是一

49、个随机的值,若有些程序直接使用这些没有初始化的变量将引起未知的后果。(12)跳转到第二阶段代码入口ldrpc, _start_armboot_start_armboot:.word start_armboot跳转到第二阶段代码入口start_armboot处。3.2 Boot Loader 的 stage2详细分析start_armboot函数在lib_arm/board.c中定义,是U-Boot第二阶段代码的入口。U-Boot启动第二阶段流程如下:正如前面所说,stage2 的代码通常用 C 语言来实现,以便于实现更复杂的功能和取得更好的代码可读性和可移植性。在分析start_armboot

50、函数前先来看看一些重要的数据结构:(1)gd_t结构体U-Boot使用了一个结构体gd_t来存储全局数据区的数据,这个结构体在include/asm-arm/global_data.h中定义如下:typedefstructglobal_databd_t*bd;/* 与板子相关的结构体,见下面 */unsigned longflags;/* 选项 */unsigned longbaudrate;/* 波特率 */unsigned longhave_console;/* serial_init() was called */unsigned longenv_addr;/* Address of E

51、nvironment struct */unsigned longenv_valid;/* Checksum of Environment valid? */unsigned longfb_base;/* base address of frame buffer */void*jt;/* jump table */gd_t;U-Boot使用了一个存储在寄存器中的指针gd来记录全局数据区的地址:#define DECLARE_GLOBAL_DATA_PTR register volatile gd_t *gd asm (r8)DECLARE_GLOBAL_DATA_PTR定义一个gd_t全局数据

52、结构的指针,这个指针存放在指定的寄存器r8中。这个声明也避免编译器把r8分配给其它的变量。任何想要访问全局数据区的代码,只要代码开头加入“DECLARE_GLOBAL_DATA_PTR”一行代码,然后就可以使用gd指针来访问全局数据区了。根据U-Boot内存使用图中可以计算gd的值:gd = TEXT_BASE CONFIG_SYS_MALLOC_LEN sizeof(gd_t) (2)bd_t结构体bd_t在include/asm-arm.u/u-boot.h中定义如下:typedef struct bd_info intbi_baudrate;/* 串口通讯波特率 */ unsigned

53、longbi_ip_addr;/* IP 地址*/ struct environment_s *bi_env;/* 环境变量开始地址 */ ulong bi_arch_number;/* 开发板的机器码 */ ulong bi_boot_params;/* 内核参数的开始地址 */ struct/* RAM配置信息 */ ulong start;/* 起始地址 */ulong size;/* 长度 */ bi_dramCONFIG_NR_DRAM_BANKS;bd_t;U-Boot启动内核时要给内核传递参数,这时就要使用gd_t,bd_t结构体中的信息来设置标记列表。(3)init_sequence数组U-Boot使用一个数组init_sequence(在lib_arm/board.c中)来存储对于大多数开发板都要执行的初始化函数的函数指针。init_sequence数组中有较多的编译选项,去掉编译选项后init_sequence数组如下所示:typedef int (init_fnc_t) (void);/* 这是使用typedef定义一个init

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

评论

0/150

提交评论