首页 uboot启动代码详解

uboot启动代码详解

举报
开通vip

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

uboot启动代码详解
uboot启动代码详解 ?1 引言 在专用的嵌入式板子运行 GNU/Linux 系统已经变得越来越流行。一个嵌入式 Linux 系统从软件的角度看通常可以分为四个层次: 1. 引导加载程序。固化在固件(firmware)中的 boot 代码,也就是 Boot Loader,它的启动通常分为两个阶段。 2. Linux 内核。特定于嵌入式板子的定制内核以及内核的启动参数。 3. 文件系统。包括根文件系统和建立于 Flash 内存设备之上文件系统,root fs。 4. 用户应用程序。特定于用户的应用程序。有时在用户应用程序和内核层之间可能还会包括一个嵌入式图形用户界面。常用的嵌入式 GUI 有:MicroWindows 和 MiniGUI 等。 引导加载程序是系统加电后运行的第一段软件代码。回忆一下 PC 的体系结构我们可以知道,PC 机中的引导加载程序由 BIOS(其本质就是一段固件程序)和位于硬盘 MBR 中的 OS Boot Loader(比如,LILO 和 GRUB 等)一起组成。BIOS 在完成硬件检测和资源分配后,将硬盘 MBR 中的 Boot Loader 读到系统的 RAM 中,然后将控制权交给 OS Boot Loader。Boot Loader 的主要运行任务就是将内核映象从硬盘上读到 RAM 中,然后跳转到内核的入口点去运行,也即开始启动操作系统。 而在嵌入式系统中,通常并没有像 BIOS 那样的固件程序(注,有的嵌入式 CPU 也会内嵌一段短小的启动程序),因此整个系统的加载启动任务就完全由 Boot Loader 来完成。比如在一个基于 ARM7TDMI core 的嵌入式系统中,系统在上电或复位时通常都从地址 0x00000000 处开始执行,而在这个地址处安排的通常就是系统的 Boot Loader 程序。 ?2 bootloader简介 应用程序 文件系统 操作系统内核 BootLoader 简单地说,Boot Loader (引导加载程序)就是在操作系统内核运行之前运行的一段小程序,它的作用就是加载操作系统,它是系统加电后运行的第一段软件代码。通过这段代码实现硬件的初始化,建立内存空间的映射图,为操作系统内核准备好硬件环境并引导内核的启动。如上图所示的那样在设备的启动过程中bootloader位于最底层,首先被运行来引导操作系统运行,很容易可以看出 bootloader是底层程序所以它的实现严重地依赖于硬件,特别是在嵌入式世界。因此,在嵌入式世界里建立一个通用的BootLoader几乎是不可能的。尽管如此,一些功能强大、支持硬件环境较多的BootLoader也被广大的使用者和爱好者所支持,从而形成了一些被广泛认可的、较为通用的的bootloader实现。 2.1 Boot Loader 所支持的 CPU 和嵌入式板 每种不同的 CPU 体系结构都有不同的 Boot Loader。有些 Boot Loader 也支持多种体系结构的 CPU,比如 U-Boot 就同时支持 ARM 体系结构和MIPS 体系结构。除了依赖于 CPU 的体系结构外,Boot Loader 实际上也依赖于具体的嵌入式板级设备的配置。这也就是说,对于两块不同的嵌入式板而言,即使它们是基于同一种 CPU 而构建的,要想让运行在一块板子上的 Boot Loader 程序也能运行在另一块板子上,通常也都需要修改 Boot Loader 的源程序。 2.2 Boot Loader 的安装媒介(Installation Medium) 系统加电或复位后,所有的 CPU 通常都从某个由 CPU 制造商预先安排的地址上取指令。比如,基于 ARM7TDMI core 的 CPU 在复位时通常都从地址 0x00000000 取它的第一条指令。而基于 CPU 构建的嵌入式系统通常都有某种类型的固态存储设备(比如:ROM、EEPROM 或 FLASH 等)被映射到这个预先安排的地址上。因此在系统加电后,CPU 将首先执行 Boot Loader 程序。 下图1就是一个同时装有 Boot Loader、内核的启动参数、内核映像和根文件系统映像的固态存储设备的典型空间分配结构图: 图1 固态存储设备的典型空间分配结构 2.3 Boot Loader 的启动过程:单阶段(Single Stage)/多阶段(Multi-Stage) 通常多阶段的 Boot Loader 能提供更为复杂的功能,以及更好的可移植性。从固态存储设备上启动的 Boot Loader 大多都是 2 阶段的启动过程,也即启动过程可以分为 stage 1 和 stage 2 两部分。而至于在 stage 1 和 stage 2 具体完成哪些任务将在下面讨论。 2.4 Boot Loader 的操作模式 (Operation Mode) 大多数 Boot Loader 都包含两种不同的操作模式:"启动加载"模式和"下载"模式,这种区别仅对于开发人员才有意义。但从最终用户的角度看,Boot Loader 的作用就是用来加载操作系统,而并不存在所谓的启动加载模式与下载工作模式的区别。 启动加载(Boot loading)模式:这种模式也称为"自主"(Autonomous)模式。也即 Boot Loader 从目标机上的某个固态存储设备上将操作系统加载到 RAM 中运行,整个过程并没有用户的介入。这种模式是 Boot Loader 的正常工作模式,因此在嵌入式产品发布的时侯,Boot Loader 显然必须工作在这种模式下。 下载(Downloading)模式:在这种模式下,目标机上的 Boot Loader 将通过串口连接或网络连接等通信手段从主机(Host)下载文件,比如:下载内核映像和根文件系统映像等。从主机下载的文件通常首先被 Boot Loader 保存到目标机的 RAM 中,然后再被 Boot Loader 写到目标机上的FLASH 类固态存储设备中。Boot Loader 的这种模式通常在第一次安装内核与根文件系统时被使用;此外,以后的系统更新也会使用 Boot Loader 的这种工作模式。工作于这种模式下的 Boot Loader 通常都会向它的终端用户提供一个简单的菜单界面或命令行接口来接收要执行的操作。 像 Blob 或 U-Boot 等这样功能强大的 Boot Loader 通常同时支持这两种工作模式,而且允许用户在这两种工作模式之间进行切换。比如,Blob 在启动时处于正常的启动加载模式,但是它会延时 10 秒等待终端用户按下任意键而将 blob 切换到下载模式。如果在 10 秒内没有用户按键,则 blob 继续启动 Linux 内核。 2.5 常见的Boot Loader U-BOOT:U-Boot是Das U-Boot的简称,其含义是Universal Boot Loader,是遵循GPL条款的开放源码项目。uboot是一个庞大的公开源码的软件。它支持一些系列的arm体系,包含常见的外设的驱动,是一个功能强大的板极支持包。 vivi:vivi是韩国mizi 公司开发的bootloader, 适用于ARM9处理器。 Vivi也有两种工作模式:启动加载模式和下载模式。启动加载模式可以在一段时间后(这个时间可更改)自行启动linux内核,这是vivi的默认模式。如果修改或更新需要进入下载模式,在下载模式下,vivi为用户提供一个命令行接口通过接口可以使用vivi提供的一些命令,来实现flash的烧 写、管理、操作mtd分区信息、启动系统等功能。 2.6 U-BOOT的目录结构 目录 说明 和开发板相关的文件,每一个开发板都以一个子目录出现在当前目录board 中,比如:smdk2410。该子目录中存放于开发板相关的配置文件,如 makefile和U-Boot.lds。其中包含SDRAM初始化代码、Flash底层驱动、 板级初始化文件。config.mk定义了TEXT_BASE是代码在内存的真实 地址 与体系结构无关的文件,实现各种命令的C文件。该文件主要实现ubootcommon 命令行下支持的命令,每一条命令都对应一个文件。例如bootm命令 对应就是cmd_bootm.c。 与特定CPU架构相关目录,每一款uboot下支持的CPU在该目录下对cpu 应一个子目录,比如arm920t。每个CPU子目录中都包括cpu.c和 interrupt.c、start.S。cpu.c初始化CPU、设置指令Cache和数据Cache 等interrupt.c设置系统的各种中断和异常start.S是U-boot启动时执行的 第一个文件,它主要做早期系统初始化,代码重定向和设置系统堆栈 Disk分区处理代码,对磁盘的支持 disk 文档目录,uboot有非常完整的文档 doc Uboot支持的设备驱动程序都放在该目录,例如各种网卡、支持CFI的drivers Flash、串口、USB等 支持的文件系统,目前支持cramfs、fat、fdos、jffs2和registerfs fs 头文件,还有对各种硬件平台支持的汇编文件,系统配置文件和对文件include 系统支持的文件等。该目录下的configs目录有与开发板相关的配置文 件,如smdk2410.h。该目录下的asm目录有与CPU体系结构相关的头 文件,比如arm对应的就是asm-arm。 与网络 协议 离婚协议模板下载合伙人协议 下载渠道分销协议免费下载敬业协议下载授课协议下载 栈相关的代码,BOOTP协议、TFTP协议、RARP和NFSnet 文件系统的实现等 与ARM体系结构相关的库文件。如与arm相关的库放在lib_arm中 lib_xxx 生成uboot的工具,如mkimage,crc等等 tools ?3 Boot Loader 的主要任务与典型结构框架 从操作系统的角度看,Boot Loader 的总目标就是正确地调用内核来执行。另外,由于 Boot Loader 的实现依赖于 CPU 的体系结构,因此大多数 Boot Loader 都分为 stage1 和 stage2 两大部分。依赖于 CPU 体系结构的代码,比如设备初始化代码等,通常都放在 stage1 中,而且通常都用汇编语言来实现,以达到短小精悍的目的。而 stage2 则通常用C语言来实现,这样可以实现给复杂的功能,而且代码会具有更好的可读性和可移植性。 以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跳转到第二阶段。 ?第二阶段(stage 2) lib_arm/board.c 该阶段主要都是用,语言来实现。start_armboot()进行一系列初始化(cpu, 板卡,中断,串口,控制台等),开启I/D cache。初始化FLASH,根据系统配置执行其他初始化操作。打印LOG,使能中断,获取环境变量,初始化网卡。最后进入main_loop()函数。 综上所述,可简单的归纳两个阶段的功能如下: ?第一阶段的功能: 硬件设备初始化 加载U-Boot第二阶段代码到RAM空间 设置好栈 跳转到第二阶段代码入口 ?第二阶段的功能: 初始化本阶段使用的硬件设备 检测系统内存映射 将内核从Flash读取到RAM中 为内核设置启动参数 调用内核 U-Boot启动第一阶段 流程 快递问题件怎么处理流程河南自建厂房流程下载关于规范招聘需求审批流程制作流程表下载邮件下载流程设计 如下: 3.1 u-boot 的 stage1详细分析 uboot的第一阶段 设计 领导形象设计圆作业设计ao工艺污水处理厂设计附属工程施工组织设计清扫机器人结构设计 的非常巧妙,几乎都是用汇编语言实现的。 首先我们来看一下它的链接脚本(u-boot-1.1.6\board\smdk2410\u-boot.lds),通过它我们可以知道它整个程序的各个段是怎么存放的。它定义了整个程序编译之后的连接过程,决定了一个可执行程序的各个段的存储位置。 /* 指定输出可执行文件是elf 格式,32 位ARM 指令,小端 */ OUTPUT_FORMAT("elf32-littlearm", "elf32-littlearm", "elf32-littlearm") /* 指定输出可执行文件的平台架构为ARM架构 */ OUTPUT_ARCH(arm) /* 指定输出可执行文件的起始代码段为_start */ ENTRY(_start) SECTIONS { . = 0x00000000;//入口地址 . = ALIGN(4);//四字节对齐 .text ://代码段,上面3行标识是不占任何空间的 { cpu/arm920t/start.o (.text)//这里将start.o放在第一位就表示把start.s编译时放在最开始, 也就是uboot启动是最先执行start.S *(.text)//所有的其他程序的代码段以四字节对齐放在它后面 } . = ALIGN(4); //前面的“.”表示当前值 .rodata : { *(.rodata) }//只读数据段 . = ALIGN(4); .data : { *(.data) }//指定读/写数据段 . = ALIGN(4); .got : { *(.got) }//指定got段,got段式是uboot自定义的一个段,非 标准 excel标准偏差excel标准偏差函数exl标准差函数国标检验抽样标准表免费下载红头文件格式标准下载 段 . = .; __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_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 相同。 //该部分为处理器的异常处理向量表。地址范围为0x00000000 ~ 0x00000020,刚好8 条指令。 .globl _start /* u-boot启动入口 */ _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 /* irq中断向量 */ ldr pc, _fiq /* fiq中断向量 */ /* 中断向量表入口地址 */ //.word 伪操作用于分配一段字内存单元(分配的单元都是字对齐的),并用伪操作中的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异常向量表,各个异常向量介绍如下: 地址 异常 进入模式 描述 复位 管理模式 复位电平有效时,产生复位异常,程序跳转0x00000000 到复位处理程序处执行 未定义指令 未定义模式 遇到不能处理的指令时产生未定义指令异常 0x00000004 软件中断 管理模式 执行SWI指令产生,用于用户模式下的程序0x00000008 调用特权操作指令 预存指令 中止模式 处理器预取指令的地址不存在,或该地址不0x0000000c 允许当前指令访问,产生指令预取中止异常 数据操作 中止模式 处理器数据访问指令的地址不存在或该地址0x00000010 不允许当前指令访问时,产生数据中止异常 未使用 未使用 未使用 0x00000014 外部中断请求有效,且CPSR中的I位为00x00000018 IRQ IRQ 时,产生IRQ异常 快速中断请求引脚有效,且CPSR中的F位0x0000001c FIQ FIQ 为0时,产生FIQ异常 在cpu/arm920t/start.S中还有这些异常对应的异常处理程序。当一个异常产生时,CPU根据异常号在异常向量表中找到对应的异常向量,然后执行异常向量处的跳转指令,CPU就跳转到对应的异常处理程序执行。 其中复位异常向量的指令“b reset”决定了U-Boot启动后将自动跳转到标号reset处执行。 许多人都认为_start的值是0x00000000,为什么是这个地址呢? 因为连接脚本上指定了。真的是这样吗,我们来看看我们编译好之后,在u-boot目录下有个System.map,这里面有各个变量的值,其中会告诉你,_start的值为:0x33f80000。注意,这里有两个地址:编译地址和运行地址。 什么是编译地址,什么是运行地址, 32 位的处理器,它的每一条指令是4个字节,以4个字节存储顺序,进行顺序执行,CPU是顺序执行的,只要没发生什么跳转,它会顺序进行执行,编译器会对每一条指令分配一个编译地址,这是编译器分配的,在编译过程中分配的地址,我们称之为编译地址。 运行地址是指,程序指令真正运行的地址,是由用户指定的,用户将运行地址烧录到哪里,哪里就是运行的地址。编译地址和运行地址如何来算呢,假如有两个编译地址a=0x10,b=0x7,b的运行地址是0x300 ,那么a的运行地址就是b的运行地址加上两者编译地址的差值,a-b=0x10-0x7=0x3,a的运行地址就是0x300+0x3=0x303。 (2)CPU进入SVC模式 start_code: /* * set the cpu to SVC32 mode */ mrs r0, cpsr bic r0, r0, #0x1f /*工作模式位清零 */ orr r0, r0, #0xd3 /*工作模式位设置为“10011”(管理模式),并将中断禁止位和快中断禁止位置1 */ msr cpsr, r0 以上代码将CPU的工作模式位设置为管理模式,并将中断禁止位和快中断禁止位置一,从而屏蔽了IRQ和FIQ中断。 (3)设置控制寄存器地址 #if defined(CONFIG_S3C2400) #define pWTCON 0x15300000 #define INTMSK 0x14400008 #define CLKDIVN 0x14800014 #else /* s3c2410与s3c2440下面4个寄存器地址相同 */ #define pWTCON 0x53000000 /* WATCHDOG控制寄存器地址 */ #define INTMSK 0x4A000008 /* INTMSK寄存器地址 */ #define INTSUBMSK 0x4A00001C /* INTSUBMSK寄存器地址 */ #define CLKDIVN 0x4C000014 /* CLKDIVN寄存器地址 */ # endif 对于s3c2440开发板,以上代码完成了WATCHDOG,INTMSK,INTSUBMSK,CLKDIVN四个寄存器的地址的设置。 (4)关闭看门狗 ldr r0, =pWTCON mov r1, #0x0 str r1, [r0] /* 看门狗控制器的最低位为0时,看门狗不输出复位信号 */ 以上代码向看门狗控制寄存器写入0,关闭看门狗。为什么需要关闭看门狗呢,这里有个喂狗的过程,所谓的喂狗是每隔一段时间给某个寄存器置位而已,在实际中会专门启动一个线程或进程会专门喂狗,当上层软件出现故障时就会停止喂狗,停止喂狗之后,cpu会自动复位,一般都在外部专门有一个看门狗,做一个外部的电路,不在cpu内部使用看门狗,否则在U-Boot启动过程中,CPU将不断重启。 (5)屏蔽中断 /* * mask all IRQs by setting all bits in the INTMR - default */ mov r1, #0xffffffff /* 某位被置1则对应的中断被屏蔽 */ ldr r0, =INTMSK str r1, [r0] INTMSK是主中断屏蔽寄存器,每一位对应SRCPND(中断源引脚寄存器)中的一位,表明SRCPND相应位代表的中断请求是否被CPU所处理。INTMSK寄存器是一个32位的寄存器,每位对应一个中断,向其中写入0xffffffff就将INTMSK寄存器全部位置1,从而 屏蔽对应的中断。为什么要关闭中断呢,中断处理(ldr pc ..)是将代码的编译地址放在了指针上,而这段时间内还没有搬移代码,所以不能进行跳转。 #if defined(CONFIG_S3C2440) ldr r1, =0x7fff ldr r0, =INTSUBMSK str r1, [r0] #endif INTSUBMSK每一位对应SUBSRCPND中的一位,表明SUBSRCPND相应位代表的中断请求是否被CPU所处理。INTSUBMSK寄存器是一个32位的寄存器,但是只使用了低15位。向其中写入0x7fff就是将INTSUBMSK寄存器全部有效位(低15位)置1,从而屏蔽对应的中断。 (6)设置MPLLCON,UPLLCON, CLKDIVN #if defined(CONFIG_S3C2440) #define MPLLCON 0x4C000004 #define UPLLCON 0x4C000008 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 ! */ ldr r0, =CLKDIVN mov r1, #3 str r1, [r0] #endif CPU上电几毫秒后,晶振输出稳定,FCLK=Fin(晶振频率),CPU开始执行指令。但实际上,FCLK可以高于Fin,为了提高系统时钟,需要用软件来启用PLL。这就需要设置CLKDIVN,MPLLCON,UPLLCON这3个寄存器。 CLKDIVN寄存器用于设置FCLK,HCLK,PCLK三者间的比例如下: 位 说明 初始值 CLKDIVN HDIVN [2:1] 00 : HCLK = FCLK/1. 00 01 : HCLK = FCLK/2. 10 : HCLK = FCLK/4 (当 CAMDIVN[9] = 0 时) HCLK= FCLK/8 (当 CAMDIVN[9] = 1 时) 11 : HCLK = FCLK/3 (当 CAMDIVN[8] = 0 时) HCLK = FCLK/6 (当 CAMDIVN[8] = 1时) PDIVN [0] 0: PCLK = HCLK/1 1: PCLK = HCLK/2 0 设置CLKDIVN为5,就将HDIVN设置为二进制的10,由于CAMDIVN[9]没有被改变过,取默认值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通常设置如下: 输入频率 输出频率 MDIV PDIV SDIV 12.0000MHz 48.00 MHz 56(0x38) 2 2 12.0000MHz 405.00 MHz 127(0x7f) 2 1 当s3c2440系统主频设置为405MHZ,USB时钟频率设置为48MHZ时,系统可以稳定运行,因此设置MPLLCON与UPLLCON为: MPLLCON=(0x7f<<12) | (0x02<<4) | (0x01) = 0x7f021 UPLLCON=(0x38<<12) | (0x02<<4) | (0x02) = 0x38022 (7)关闭MMU,cache 接着往下看: #ifndef CONFIG_SKIP_LOWLEVEL_INIT bl cpu_init_crit #endif cpu_init_crit这段代码在U-Boot正常启动时才需要执行,若将U-Boot从RAM中启动则应该注释掉这段代码。 下面分析一下cpu_init_crit到底做了什么: #ifndef CONFIG_SKIP_LOWLEVEL_INIT cpu_init_crit: /* * 使数据cache与指令cache无效 */ */ mov r0, #0 mcr p15, 0, r0, c7, c7, 0 /* 向c7写入0将使ICache与DCache无效*/ mcr p15, 0, r0, c8, c7, 0 /* 向c8写入0将使TLB失效 */ /* * disable MMU stuff and caches */ mrc p15, 0, r0, c1, c0, 0 /* 读出控制寄存器到r0中 */ bic r0, r0, #0x00002300 @ clear bits 13, 9:8 (--V- --RS) bic r0, r0, #0x00000087 @ clear bits 7, 2:0 (B--- -CAM) orr r0, r0, #0x00000002 @ set bit 2 (A) Align orr r0, r0, #0x00001000 @ set bit 12 (I) I-Cache mcr p15, 0, r0, c1, c0, 0 /* 保存r0到控制寄存器 */ /* * 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 mov lr, ip mov pc, 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 呢,catch 和MMU 是做什么用的, catch 是cpu内部的一个2级缓存,她的作用是将常用的数据和指令放在cpu内部,MMU是用来做虚实地址转换用的,我们的目的是设置控制的寄存器,寄存器都是实地址,如果既要开启MMU又要做虚实地址转换的话,中间还多一步,先要把实地址转换成虚地址,然后再做设置,但对uboot 而言就是起到一个简单的初始化的作用和引导操作系统,如果开启MMU 的话,很麻烦,也没必要,所以关闭MMU. 说到catch 就必须提到一个关键字Volatile,以后在设置寄存器时会经常遇到,他的本质是告诉编译器不要对我的代码进行优化,优化的过程是将常用的代码取出来放到catch中,它没有从实际的物理地址去取,它直接从cpu的缓存中去取,但常用的代码就是为了感知一些常用变量的变化,所以在这种情况下要用Volatile关键字告诉编译器不要做优化,每次从实际的物理地址中去取指令。 (8)初始化RAM控制寄存器 其中的bl lowlevel_init用于初始化各个bank,完成了内存初始化的工作,由于内存初始化是依赖于开发板的,因此lowlevel_init的代码一般放在board下面相应的目录中。对于s3c2440,lowlevel_init在board/smdk2440/lowlevel_init.S中定义如下: #define BWSCON 0x48000000 /* 13个存储控制器的开始地址 */ … … _TEXT_BASE: .word TEXT_BASE .globl lowlevel_init lowlevel_init: /* memory control configuration */ /* make r0 relative the current location so that it */ /* reads SMRDATA out of FLASH rather than memory ! */ ldr r0, =SMRDATA ldr r1, _TEXT_BASE sub r0, r0, r1 /* SMRDATA减 _TEXT_BASE就是13个寄存器的偏移地址 */ ldr r1, =BWSCON /* Bus Width Status Controller */ add r2, r0, #13*4 0: ldr r3, [r0], #4 /*将13个寄存器的值逐一赋值给对应的寄存器*/ str r3, [r1], #4 cmp r2, r0 bne 0b /* everything is fine now */ mov pc, lr .ltorg /* the literal pools origin */ SMRDATA: /* 下面是13个寄存器的值 */ .word … … .word … … … … lowlevel_init的作用就是将SMRDATA开始的13个值复制给开始地址[BWSCON]的13个寄存器,从而完成了存储控制器的设置。 (9)复制U-Boot第二阶段代码到RAM relocate: adr r0, _start /* r0 <- current position of code */ ldr r1, _TEXT_BASE /* test if we run from flash or RAM */ /* 判断U-Boot是否是下载到RAM中运行,若是,则不用再复制到RAM中了,这种情况通常在调试U-Boot时才发生 */ cmp r0, r1 /*_start等于_TEXT_BASE说明是下载到RAM中运行 */ beq stack_setup ldr r2, _armboot_start ldr r3, _bss_start sub r2, r3, r2 /* r2 <- size of armboot */ add r2, r0, r2 /* r2 <- source end address */ /* 搬运U-Boot自身到RAM中*/ copy_loop: ldmia r0!,{r3-r10}/* 从地址为[r0]的NOR Flash中读入8个字的数据 */ stmia r1!,{r3-r10}/* 将r3至r10寄存器的数据复制给地址为[r1]的内存 */ cmp r0, r2 /* until source end addreee [r2] */ ble copy_loop 终于到重点部分了:代码重定向(拷贝stage2到RAM中),拷贝时要确定两点: (1) stage2的可执行映象在固态存储设备的存放起始地址和终止地址; (2) RAM空间的起始地址。 下面我们来看看它到底是怎么重定向的: adr r0,_start adr伪指令,汇编器会将执行到_start时PC的值放到r0中。所以此时r0中保存的不是编译地址,而是运行地址。假如U-boot 是从RAM 开始运行,则从adr,r0,_start 得到的地址信息为r0=_start=_TEXT_BASE=TEXT_BASE=0x33f80000;假如U-boot 从Flash 开始运行,即从处理器对应的地址运行,则r0=0x00000000。而这里r0=0。 ldr r1,_TEXT_BASE 这条汇编指令的意思是把_TEXT_BASE的值作为地址,把这个地址的内容赋给r1,从下面可以知道: _TEXT_BASE里面存储的内容是TEXT_BASE,我们通过查看board/smdk2410/config.mk 发现TEXT_BASE的值为0x33f80000,所以r1的值就是0x33f80000 cmp r0,r1 将r0和r1做比较,此时r0 = 0x00000000,r1 = 0x33f80000,显然不相等,那么执行的就是下面的汇编指令: ldr r2, _armboot_start 由此可以知道r2的值是_start,通过ldr将标号的编译地址放到r2中,也就是0x33f80000,即代码段的起始地址。 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 :0x300000000 ~ 0x34000000) 将flash里面的代码拷贝到sdram里面了。 (10)设置堆栈 /* 设置堆栈 */ stack_setup: ldr r0, _TEXT_BASE /* upper 128 KiB: relocated uboot */ sub r0, r0, #CONFIG_SYS_MALLOC_LEN /* malloc area */ sub r0, r0, #CONFIG_SYS_GBL_DATA_SIZE /* 跳过全局数据区 */ #ifdef CONFIG_USE_IRQ sub r0, r0, #(CONFIG_STACKSIZE_IRQ+CONFIG_STACKSIZE_FIQ) #endif sub sp, r0, #12 /* leave 3 words for abort-stack */ 只要将sp指针指向一段没有被使用的内存就完成栈的设置了。U-Boot内存使用情况如下图所示: (11)清除BSS段 clear_bss: ldr r0, _bss_start /* BSS段开始地址,在u-boot.lds中指定*/ ldr r1, _bss_end /* BSS段结束地址,在u-boot.lds中指定*/ mov r2, #0x00000000 clbss_l:str r2, [r0] /* 将bss段清零*/ add r0, r0, #4 cmp r0, r1 ble clbss_l 初始值为0,无初始值的全局变量,静态变量将自动被放在BSS段。应该将这些变量的初始值赋为0,否则这些变量的初始值将是一个随机的值,若有些程序直接使用这些没有初始化的变量将引起未知的后果。 (12)跳转到第二阶段代码入口 ldr pc, _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函数前先来看看一些重要的数据结构: (1)gd_t结构体 U-Boot使用了一个结构体gd_t来存储全局数据区的数据,这个结构体在include/asm-arm/global_data.h中定义如下: typedef struct global_data{ bd_t *bd; /* 与板子相关的结构体,见下面 */ unsigned long flags; /* 选项 */ unsigned long baudrate; /* 波特率 */ unsigned long have_console; /* serial_init() was called */ unsigned long env_addr; /* Address of Environment struct */ unsigned long env_valid; /* Checksum of Environment valid? */ unsigned long fb_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全局数据结构的指针,这个指针存放在指定的寄存器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{ int bi_baudrate; /* 串口通讯波特率 */ unsigned long bi_ip_addr; /* IP 地址*/ struct environment_s *bi_env; /* 环境变量开始地址 */ ulong bi_arch_number; /* 开发板的机器码 */ ulong bi_boot_params; /* 内核参数的开始地址 */ struct /* RAM配置信息 */ { ulong start; /* 起始地址 */ ulong size; /* 长度 */ }bi_dram[CONFIG_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_fnc_t为函数类型,该函数返回int型,无参数 */ init_fnc_t *init_sequence[] = { /* 定义一个init_fnc_t指针类型的数组。简单的说就是定义了个函数指针数组,指向一系列cpu初始化函数 */ cpu_init, /* basic cpu dependent setup CPU的相关配置,如初始化IRQ/FIQ模式的栈cpu/arm920t/cpu.c */ board_init, /* basic board dependent setup开发板相关配置,是对板子的初始化,设置MPLL,改变系统时钟,以及一些GPIO寄存器的值,还设置了U-Boot机器码和内核 启动参数地址,它是开发板相关的函数,比如2410是在:board/smdk2410/smdk2410.c中实现 */ interrupt_init, /* set up exceptions 初始化中断,在cpu/arm920t/s3c24x0/interrupts.c 实现 */ env_init, /* initialize environment 初始化环境变量,检查flash上的环境变量是否有效common/env_flash.c 或common/env_nand.c */ init_baudrate, /* initialze baudrate settings 初始化波特率lib_arm/board.c */ serial_init, /* serial communications setup串口初始化串口初始化后我们就可以打印信息了cpu/arm920t/s3c24x0/serial.c */ console_init_f, /* stage 1 init of console 控制台初始化第一阶段common/console.c */ display_banner, /* say that we are here通知代码已经运行到该处,打印U-Boot版本、编译的时间-- lib_arm/board.c */ #if defined(CONFIG_DISPLAY_CPUINFO) print_cpuinfo, /* display cpu info (and speed) */ #endif #if defined(CONFIG_DISPLAY_BOARDINFO) checkboard, /* display board info */ #endif dram_init, /* configure available RAM banks 配置可用的内存区,检测系统内存映射,设置内存的起始地址和大小board/smdk2410/smdk2410.c */ display_dram_config, NULL, }; 可以看出这里定义了一个指针数组,它的每一个元素都是指针变量,这些指针变量指向的类型为init_fnc_t,在C语言中函数的入口地址就是函数名,所以这里使用一系列函数名来初始化这个数组。现在来看看到底做了哪些初始化工作: ?cpu_init函数:cpu/arm920t/cpu.c int cpu_init (void) { #ifdef CONFIG_USE_IRQ IRQ_STACK_START =_armboot_start-CFG_MALLOC_LEN-CFG_GBL_DATA_SIZE-4; FIQ_STACK_START = IRQ_STACK_START - CONFIG_STACKSIZE_IRQ; #endif return 0; } 其实这个函数没有做任何工作,因为CONFIG_USE_IRQ这个宏没有定义。实际上就是把IRQ_STACK_START、 FIQ_STACK_START指到RAM中的IRQ stuff区域。 ?board_init函数:board/smdk2410/smdk2410.c int board_init (void) { /* 获取power和clock及GPIO方面的寄存器地址,稍后的操作会对这些寄存器操作,需要注意的是,像S3C24X0_CLOCK_POWER里面的field对象都是按照实际寄存器的地址来安排的 */ S3C24X0_CLOCK_POWER * const clk_power = S3C24X0_GetBase_CLOCK_POWER(); S3C24X0_GPIO * const gpio = S3C24X0_GetBase_GPIO(); /* to reduce PLL lock time, adjust the LOCKTIME register降低PLL的lock time的值*/ clk_power->LOCKTIME = 0xFFFFFF; /* configure MPLL配置MPLL */ clk_power->MPLLCON = ((M_MDIV << 12) + (M_PDIV << 4) + M_SDIV); /* some delay between MPLL and UPLL延时 */ delay (4000); /* configure UPLL配置UPLL */ clk_power->UPLLCON = ((U_M_MDIV << 12) + (U_M_PDIV << 4) + U_M_SDIV); /* some delay between MPLL and UPLL延时 */ delay (8000); /* 配置每个GPIO的功能,输入输出,等参数 */ gpio->GPACON = 0x007FFFFF; gpio->GPBCON = 0x00044555; gpio->GPBUP = 0x000007FF; gpio->GPCCON = 0xAAAAAAAA; gpio->GPCUP = 0x0000FFFF; gpio->GPDCON = 0xAAAAAAAA; gpio->GPDUP = 0x0000FFFF; gpio->GPECON = 0xAAAAAAAA; gpio->GPEUP = 0x0000FFFF; gpio->GPFCON = 0x000055AA; gpio->GPFUP = 0x000000FF; gpio->GPGCON = 0xFF95FFBA; gpio->GPGUP = 0x0000FFFF; gpio->GPHCON = 0x002AFAAA; gpio->GPHUP = 0x000007FF; /* SMDK2410开发板的机器码 */ gd->bd->bi_arch_number = MACH_TYPE_SMDK2410; /* adress of boot parameters内核启动参数地址,运行时在linux内核之下 */ gd->bd->bi_boot_params = 0x30000100; /* 使能指令cache和数据cache */ icache_enable(); dcache_enable(); return 0; } 函数icache_enable和dcache_enable,定义在cpu/arm920t/cpu.c中,这两个函数是通过修改CP15的c1寄存器来实现的,使能cache很简单,只要把协处理器15的相关位打开就行了,这里来是将c1的I、C位置1,来开启Icache、DCaches。我这里只分析icache_enable,dcache_enable类似。icache_enable具体实现如下: void icache_enable (void) { ulong reg; reg = read_p15_c1 (); /* get control reg. 获取CP15的c1寄存器值存到reg中*/ cp_delay (); write_p15_c1 (reg | C1_IC); /*这里将C1寄存器的I、C位置1,来开启Icache、Dcaches*/ } 这里须要理解的是read_p15_c1与write_p15_c1函数,它们分别在cpu/arm920t/cpu.c中定义如下: static unsigned long read_p15_c1 (void) { unsigned long value; __asm__ __volatile__( "mrc p15, 0, %0, c1, c0, 0 @ read control reg\n" /* %0是参数传递时R0寄存器,其功能是读取CP15的c1寄存器值放到r0中*/ : "=r" (value) : : "memory"); #ifdef MMU_DEBUG printf ("p15/c1 is = %08lx\n", value); #endif return value; 返回读取CP15的c1寄存器的值 } /* write to co-processor 15, register #1 (control register) */ static void write_p15_c1 (unsigned long value) { #ifdef MMU_DEBUG printf ("write %08lx to p15/c1\n", value); #endif __asm__ __volatile__( "mcr p15, 0, %0, c1, c0, 0 @ write it back\n" @保存r0的值到控制寄存器CP15的c1寄存器中,因为函数参数传递时,第一个参数都是放在r0寄存器中的。 : : "r" (value) : "memory"); read_p15_c1 (); } ?interrupt_init函数:cpu/arm920t/s3c24x0/interrupts.c int timer_load_val = 0; static ulong timestamp; static ulong lastdec; int interrupt_init (void) /* 初始化timer4相关寄存器,用于产生10ms定时中断信号 */ { /* 返回定时器配置寄存器地址0x51000000,即TCFG0寄存器地址 */ S3C24X0_TIMERS * const timers = S3C24X0_GetBase_TIMERS(); /* 这里使用定时器4,定时器4有只有一个内部定器没有输出管脚 */ /* prescaler for Timer 4 is 16 */ timers->TCFG0 = 0x0f00; if (timer_load_val == 0) { timer_load_val = get_PCLK()/(2 * 16 * 100); /* get_PCLK返回PCLK频率 */ } /* load value for 10 ms timeout */ lastdec = timers->TCNTB4 = timer_load_val; /* 设置计数缓存寄存器初始值 */ /* 设置定时器4手动更新,自动加载模式,并关闭定时器4 */ timers->TCON = (timers->TCON & ~0x0700000) | 0x600000; /* auto load, 启动Timer 4 */ timers->TCON = (timers->TCON & ~0x0700000) | 0x500000; timestamp = 0; return (0); } 对着datasheet来看这个函数, 实际上这个函数使用timer 4来作为系统clock, 即时钟滴答, 10ms一次,到点就产生一个中断,但由于此时中断还没打开所以这个中断不会响应。 ?env_init函数: 由于我们在inculde/configs/smdk2410.h下定义了CFG_ENV_IS_IN_FLASH因此该函数位于common/env_flash.c下 int env_init(void) { #ifdef CONFIG_OMAP2420H4 int flash_probe(void); if(flash_probe() == 0) goto bad_flash; #endif if (crc32(0, env_ptr->data, ENV_SIZE) == env_ptr->crc) { gd->env_addr = (ulong)&(env_ptr->data); gd->env_valid = 1; /* 使用include/configs/smdk2410.h配置的环境变量则设置环境变量可用标志 */ return(0); } /* env_ptr在前面定义为env_t *env_ptr = (env_t *)CFG_ENV_ADDR;而CFG_ENV_ADDR被定义在include/configs/smdk2410.h中了,这里判断如果校验正确(即CFG_ENV_ADDR被定义了)则环境变量的存放地址使用smdk2410.h中定义的,否则使用后面的默认的环境变量值default_environment数组*/ #ifdef CONFIG_OMAP2420H4 bad_flash: #endif gd->env_addr = (ulong)&default_environment[0]; gd->env_valid = 0; 使用默认的环境配置变量则设置环境变量不可用标志 return (0); } 这个函数主要是在gd里保存环境变量的存放地址。一般使用默认的环境变量值即default_environment数组,它在common/env_commom.c中定义如下: uchar default_environment[] = { #ifdef CONFIG_BOOTARGS "bootargs=" CONFIG_BOOTARGS "\0" #endif #ifdef CONFIG_BOOTCOMMAND "bootcmd=" CONFIG_BOOTCOMMAND "\0" #endif #if defined(CONFIG_BOOTDELAY) && (CONFIG_BOOTDELAY >= 0) "bootdelay=" MK_STR(CONFIG_BOOTDELAY) "\0" #endif …… #ifdef CONFIG_EXTRA_ENV_SETTINGS CONFIG_EXTRA_ENV_SETTINGS #endif "\0" }; 可见环境变量以如下的方式存放在数组中:Name=value,并且以”/0”结束, 而类似CONFIG_BOOTARGS的宏都定义在板子自己的配置文件中即smdk2410.h里。 ?init_baudrate函数:lib_arm/board.c static int init_baudrate (void) { char tmp[64]; /* long enough for environment variables */ int i = getenv_r ("baudrate", tmp, sizeof (tmp)); /* 从环境变量中获取波特率值 */ gd->bd->bi_baudrate = gd->baudrate = (i > 0) ? (int) simple_strtoul (tmp, NULL, 10) : CONFIG_BAUDRATE; return (0); } 该函数从上面刚初始化好的环境变量列表里找波特率值,如果没有就赋初始值为 CONFIG_BAUDRATE。 ?serial_init函数:cpu/arm920t/s3c24x0/serial.c int serial_init (void) { serial_setbrg (); /* 设置波特率,停止位等 */ return (0); } void serial_setbrg (void) { S3C24X0_UART * const uart = S3C24X0_GetBase_UART(UART_NR); int i; unsigned int reg = 0; /* value is calculated so : (int)(PCLK/16./baudrate) -1 */ reg = get_PCLK() / (16 * gd->baudrate) - 1; /* FIFO enable, Tx/Rx FIFO clear */ uart->UFCON = 0x07; uart->UMCON = 0x0; /* Normal,No parity,1 stop,8 bit */ uart->ULCON = 0x3; uart->UCON = 0x245; uart->UBRDIV = reg; #ifdef CONFIG_HWFLOW uart->UMCON = 0x1; /* RTS up */ #endif for (i = 0; i < 100; i++); } 上面这个函数对着datasheet看,无非是设置波特率,起始位,检验中断类型等等。 ?console_init_f函数:common/console.c int console_init_f (void) { gd->have_console = 1; #ifdef CONFIG_SILENT_CONSOLE if (getenv("silent") != NULL) gd->flags |= GD_FLG_SILENT; /* 设置控制台模式 */ #endif return (0); } 该函数初始化了几个控制台相关的标记。 ?display_banner函数:lib_arm/board.c static int display_banner (void) { printf ("\n\n%s\n\n", version_string); /* 打印U-BOOT的版本信息 */ debug ("U-Boot code: %08lX -> %08lX BSS: -> %08lX\n", _armboot_start, _bss_start, _bss_end); /* 打印U-BOOT代码位置 */ #ifdef CONFIG_MODEM_SUPPORT debug ("Modem Support enabled\n"); #endif #ifdef CONFIG_USE_IRQ debug ("IRQ Stack: %08lx\n", IRQ_STACK_START); debug ("FIQ Stack: %08lx\n", FIQ_STACK_START); #endif return (0); } 这个函数就是在控制台上打印一些系统信息。 ?dram_init函数:board/smdk2410/smdk2410.c int dram_init (void) { gd->bd->bi_dram[0].start = PHYS_SDRAM_1; /* RAM起始地址 */ gd->bd->bi_dram[0].size = PHYS_SDRAM_1_SIZE; /* RAM大小 */ return 0; } 2410使用2片32MB的SDRAM组成了64MB的内存,接在存储控制器的BANK6,地址空间是0x30000000~0x34000000。在include/configs/smdk2410.h中PHYS_SDRAM_1和PHYS_SDRAM_1_SIZE 分别被定义为0x30000000和0x04000000(64M)。 ?display_dram_config函数:lib_arm/board.c static int display_dram_config (void) { int i; #ifdef DEBUG puts ("RAM Configuration:\n"); for(i=0; ibd->bi_dram[i].start); print_size (gd->bd->bi_dram[i].size, "\n"); } #else ulong size = 0; for (i=0; ibd->bi_dram[i].size; } puts("DRAM: "); print_size(size, "\n"); #endif return (0); } 该函数用于打印系统RAM的信息。 这样整个初始化函数表的函数都看完了,总结一下主要做了如下过程: 1(cpu, borad, interrupt的初始化,包括cache等,这些都于特定板子的配置有关 2(环境变量的初始化 3(串口,控制台,RAM的初始化 4(在控制台上实时的显示系统配置等相关参数 最后需要说明的是,大部分的配置参数都是预先在include/configs/board_name.h下定义的,因此如果我们要移植u-boot到我们自己的板子的话,这个文件必不可少,它描述了我们板子的配置情况如CPU型号,RAM大小等。 分析完上述的数据结构,下面来分析start_armboot函数,现在我贴出start_armboot ()的源代码,然后具体的在其中解释一些代码的作用。 void start_armboot (void) { init_fnc_t **init_fnc_ptr; /* 它是指向指针的指针变量,变量的类型为init_fnc_t,这是一个使用typedef定义的函数类型, 其中init_fnc_ptr将在后面指向一个数组指针 */ char *s; #ifndef CFG_NO_FLASH ulong size; #endif #if defined(CONFIG_VFD) || defined(CONFIG_LCD) unsigned long addr; #endif /* Pointer is writable since we allocated a register for it */ gd = (gd_t*)(_armboot_start - CFG_MALLOC_LEN - sizeof(gd_t)); /* 计算全局数据结构的地址gd */ /* compiler optimization barrier needed for GCC >= 3.4 */ __asm__ __volatile__("": : :"memory"); memset ((void*)gd, 0, sizeof (gd_t)); /* 初始化全局数据区为0 */ gd->bd = (bd_t*)((char*)gd - sizeof(bd_t)); /* 为bd_t分配空间,并赋值到gd */ memset (gd->bd, 0, sizeof (bd_t)); monitor_flash_len = _bss_start - _armboot_start; /* 代码段的大小 */ /* 逐个调用init_sequence数组中的初始化函数 */ for (init_fnc_ptr = init_sequence; *init_fnc_ptr; ++init_fnc_ptr) { if ((*init_fnc_ptr)() != 0) { hang (); } } /* CFG_NO_FLASH 表示没有flash,如果没定义该常量则表示板子上有flash,此时调用flash_init()对其进行初始化.这里是norflash初始化 */ #ifndef CFG_NO_FLASH /* configure available FLASH banks */ size = flash_init (); /* 初始化norflash详见后分析 */ display_flash_config (size); /* 打印flash的信息,仅是其大小 */ #endif /* CFG_NO_FLASH */ #ifdef CONFIG_VFD /* 没定义CONFIG_VFD */ #ifndef PAGE_SIZE #define PAGE_SIZE 4096 #endif /* * reserve memory for VFD display (always full pages) */ /* bss_end is defined in the board-specific linker script */ addr = (_bss_end + (PAGE_SIZE - 1)) & ~(PAGE_SIZE - 1); size = vfd_setmem (addr); gd->fb_base = addr; #endif /* CONFIG_VFD */ #ifdef CONFIG_LCD #ifndef PAGE_SIZE #define PAGE_SIZE 4096 #endif /* * reserve memory for LCD display (always full pages) */ /* bss_end is defined in the board-specific linker script */ addr = (_bss_end + (PAGE_SIZE - 1)) & ~(PAGE_SIZE - 1); size = lcd_setmem (addr); gd->fb_base = addr; #endif /* CONFIG_LCD */ /* armboot_start is defined in the board-specific linker script */ mem_malloc_init (_armboot_start - CFG_MALLOC_LEN); /* 初始化malloc区域, _armboot_start=0x33f80000,CFG_MALLOC_LEN=0x30000,mem_malloc_init对 (0x33f80000-0x30000)~(0x33f80000)这段存储区清零 */ /* 如果定义了命令和NAND命令,初始化nand */ #if (CONFIG_COMMANDS & CFG_CMD_NAND) puts ("NAND: "); nand_init(); /*探测NAND flash并根据NAND flash的类型填充相应的数据结构,全局 结构体变量nand_dev_desc[0](类型struct nand_chip) */ #endif #ifdef CONFIG_HAS_DATAFLASH /* 没定义 */ AT91F_DataflashInit(); dataflash_print_info(); #endif /* initialize environment配置环境变量,重新定位 */ env_relocate (); /* 这个函数在common\env_common.c中 */ #ifdef CONFIG_VFD /* 没定义 */ /* must do this after the framebuffer is allocated */ drv_vfd_init(); #endif /* CONFIG_VFD */ /* IP Address 从环境变量中获取IP地址 */ gd->bd->bi_ip_addr = getenv_IPaddr ("ipaddr"); /* MAC Address */ { int i; ulong reg; char *s, *e; char tmp[64]; /* 从环境变量获取网卡的MAC地址并填充gd结构体变量 */ i = getenv_r ("ethaddr", tmp, sizeof (tmp)); s = (i > 0) ? tmp : NULL; for (reg = 0; reg < 6; ++reg) { gd->bd->bi_enetaddr[reg] = s ? simple_strtoul (s, &e, 16) : 0; if (s) s = (*e) ? e + 1 : e; } #ifdef CONFIG_HAS_ETH1 i = getenv_r ("eth1addr", tmp, sizeof (tmp)); s = (i > 0) ? tmp : NULL; for (reg = 0; reg < 6; ++reg) { gd->bd->bi_enet1addr[reg] = s ? simple_strtoul (s, &e, 16) : 0; if (s) s = (*e) ? e + 1 : e; } #endif } devices_init (); /* get the devices list going. 对设备初始化,在common/devices.c文件中,会根据配置来完成各个设备的初始化,其中会调用函数drv_system_init(),这个函数对串口设备初始化,然后注册串口设备 */ #ifdef CONFIG_CMC_PU2 //没定义 load_sernum_ethaddr (); #endif /* CONFIG_CMC_PU2 */ jumptable_init (); /* 跳转表初始化,函数在common\exports.c文件中 */ console_init_r (); /* fully init console as a device函数在common\console.c */ #if defined(CONFIG_MISC_INIT_R) //没有定义 /* miscellaneous platform dependent initialisations杂项设备平台依赖初始化 */ misc_init_r (); #endif /* enable exceptions */ enable_interrupts (); /*使能中断,在cpu/arm920t/interrupts.c smdk2410是个空函数void enable_interrupts (void){return;} */ /* Perform network card initialisation if necessary */ #ifdef CONFIG_DRIVER_CS8900 /*smdk2410没定义 */ cs8900_get_enetaddr (gd->bd->bi_enetaddr); /* 获取网卡的MAC地址 */ #endif #if defined(CONFIG_DRIVER_SMC91111) || defined (CONFIG_DRIVER_LAN91C96) if (getenv ("ethaddr")) { smc_set_mac_addr(gd->bd->bi_enetaddr); } #endif /* CONFIG_DRIVER_SMC91111 || CONFIG_DRIVER_LAN91C96 没定义 */ /* Initialize from environment */ if ((s = getenv ("loadaddr")) != NULL) { load_addr = simple_strtoul (s, NULL, 16); /* 读取环境变量loadaddr到全局变量load_addr中 */ } #if (CONFIG_COMMANDS & CFG_CMD_NET) if ((s = getenv ("bootfile")) != NULL) { copy_filename (BootFile, s, sizeof (BootFile)); /* 读取环境变量bootfile到全局变量BootFile中 */ } #endif /* CFG_CMD_NET */ #ifdef BOARD_LATE_INIT /* 没定义 */ board_late_init (); #endif #if (CONFIG_COMMANDS & CFG_CMD_NET) #if defined(CONFIG_NET_MULTI) /* smdk2410没定义 */ puts ("Net: "); #endif eth_initialize(gd->bd); #endif /* main_loop() can return to retry autoboot, if so just run it again. */ for (;;) { main_loop (); /* 直接进入main_loop 该函数在common\main.c中,至此,硬件初始化完成 */ } /* NOTREACHED - no way out of command loop except booting */ } 对start_armboot中相关调用的函数分析: ?flash_init函数:board/smdk2410/flash.c ulong flash_init (void) { int i, j; ulong size = 0; for (i = 0; i < CFG_MAX_FLASH_BANKS; i++) { ulong flashbase = 0; flash_info[i].flash_id = /* 保存flash ID */ #if defined(CONFIG_AMD_LV400) (AMD_MANUFACT & FLASH_VENDMASK) | (AMD_ID_LV400B & FLASH_TYPEMASK); #elif defined(CONFIG_AMD_LV800) (AMD_MANUFACT & FLASH_VENDMASK) | (AMD_ID_LV800B & FLASH_TYPEMASK); #else #error "Unknown flash configured" #endif /*保存每个flash blank的大小,sector数量,起始地址等信息*/ flash_info[i].size = FLASH_BANK_SIZE; flash_info[i].sector_count = CFG_MAX_FLASH_SECT; memset (flash_info[i].protect, 0, CFG_MAX_FLASH_SECT); if (i == 0) flashbase = PHYS_FLASH_1; else panic ("configured too many flash banks!\n"); /*为每个sector分配不同的大小,作为不同的用途*/ for (j = 0; j < flash_info[i].sector_count; j++) { if (j <= 3) { /* 1st one is 16 KB */ if (j == 0) { flash_info[i].start[j] = flashbase + 0; } /* 2nd and 3rd are both 8 KB */ if ((j == 1) || (j == 2)) { flash_info[i].start[j] = flashbase + 0x4000 + (j - 1) * 0x2000; } /* 4th 32 KB */ if (j == 3) { flash_info[i].start[j] = flashbase + 0x8000; } } else { flash_info[i].start[j] = flashbase + (j - 3) * MAIN_SECT_SIZE; } } size += flash_info[i].size; } /*对flash上保存有RO, RW的地方进行保护,monitor_flash_len , RO + RW的长度*/ flash_protect (FLAG_PROTECT_SET, CFG_FLASH_BASE, CFG_FLASH_BASE + monitor_flash_len - 1, &flash_info[0]); /*对flash上保存有环境变量的地方进行保护 */ flash_protect (FLAG_PROTECT_SET, CFG_ENV_ADDR, CFG_ENV_ADDR + CFG_ENV_SIZE - 1, &flash_info[0]); return size; } 该函数就是记录下flash的大小,数量,sector的大小数量等,并对flash上重要的数据进行保护。 ?display_flash_config函数:lib_arm/board.c #ifndef CFG_NO_FLASH static void display_flash_config (ulong size) { puts ("Flash: "); print_size (size, "\n"); } #endif 用于打印flash的大小信息。 ?mem_malloc_init函数:lib_arm/board.c static ulong mem_malloc_start = 0; static ulong mem_malloc_end = 0; static ulong mem_malloc_brk = 0; static void mem_malloc_init (ulong dest_addr) { mem_malloc_start = dest_addr; mem_malloc_end = dest_addr + CFG_MALLOC_LEN; mem_malloc_brk = mem_malloc_start; memset ((void *) mem_malloc_start, 0, mem_malloc_end - mem_malloc_start); } 这个函数就是保存malloc区域的起始地址,结束地址,并清0这块区域,这块区域在RAM中的具体位置可查看前面的图。 ?env_relocate函数:common/env_common.c Uboot在完成汇编部分的初始化之后,将跳到start_armboot()去执行,其中便会执行env_relocate()初始化环境变量。 第一步,初始化一个全局指针 env_t *env_ptr = 0; 第二步,重新初始化函数指针 static uchar env_get_char_init (int index); uchar (*env_get_char)(int) = env_get_char_init; 该函数指针原来被初始化为env_get_char_init,现在改为env_get_char_memory。对于nand flash,这两个函数是一样的。 第三步,如果flash没有参数表,则使用默认参数 memcpy (env_ptr->data,default_environment, sizeof(default_environment)); 第四步,如果flash上有参数表可用,则从flash上加载,通过env_relocate_spec()来实现 第五步,gd->env_addr = (ulong)&(env_ptr->data) 即将环境变量的值赋值给全局变量gd->env_addr,这样只要通过这个全局变量就可以访问这些变量了。值得一提的是,字符串数组data里面的变量与变量之间是通过’/0’来分割的。 env_t *env_ptr = 0; void env_relocate (void) { DEBUGF ("%s[%d] offset = 0x%lx\n", __FUNCTION__, __LINE__, gd->reloc_off); #ifdef CONFIG_AMIGAONEG3SE /* 没有定义 */ enable_nvram(); #endif /*env_ptr指向存放环境变量的区域*/ #ifdef ENV_IS_EMBEDDED env_ptr = (env_t *)((ulong)env_ptr + gd->reloc_off); DEBUGF ("%s[%d] embedded ENV at %p\n", __FUNCTION__,__LINE__,env_ptr); #else env_ptr = (env_t *)malloc (CFG_ENV_SIZE);/*为环境变量分配内存,env_ptr是全局变量,在下面的env_relocate_spec ()函数中将环境变量读取到env_ptr指向的内存中*/ DEBUGF ("%s[%d] malloced ENV at %p\n", __FUNCTION__,__LINE__,env_ptr); #endif env_get_char = env_get_char_memory; /*如果在env_init中没有初始化合适的环境变量则使用默认的环境变量来作为环境变量值,否则使用env_init中定义好的环境变量值*/ /* 在env_init函数中如果使用环境变量地址是include/configs/smdk2410.h配置的gd->env_valid被初始化为1,即flash上有参数表,如果使用环境变量是默认的环境变量default_environment,则gd->env_valid被初始化为0了 */ if (gd->env_valid == 0) { /* 如果flash没有参数表,则使用默认参数,这里是通过下面 来加载 */ #if defined(CONFIG_GTH) || defined(CFG_ENV_IS_NOWHERE) /* Environment not changable */ puts ("Using default environment\n\n"); #else puts ("*** Warning - bad CRC, using default environment\n\n"); SHOW_BOOT_PROGRESS (-1); #endif if (sizeof(default_environment) > ENV_SIZE) { puts ("*** Error - default environment is too large\n\n"); return; } memset (env_ptr, 0, sizeof(env_t)); memcpy (env_ptr->data, default_environment, sizeof(default_environment)); /*把默认值存入RAM相应区域*/ #ifdef CFG_REDUNDAND_ENVIRONMENT env_ptr->flags = 0xFF; #endif env_crc_update ();/*更新crc校验值*/ gd->env_valid = 1; /*标记环境变量有效*/ } else { env_relocate_spec ();/*如果flash上有参数表可用,则从flash上加载,通过env_relocate_spec()来实现*/ } gd->env_addr = (ulong)&(env_ptr->data); /* 填充结构体变量, 即将环境变量的值赋值给全局变量gd->env_addr,这样只要通过这个全局变量就可以访问这些变量了。值得一提的是,字符串数组data里面的变量与变量之间是通过’/0’来分割的 */ #ifdef CONFIG_AMIGAONEG3SE //没定义 disable_nvram(); #endif } 该函数重新分配了一块区域用于存放环境变量,并在gd_t区域保存了这个地址值。 void env_relocate_spec (void) { #if !defined(ENV_IS_EMBEDDED) /* 如果不是使用嵌入参数的形式,即为参数表的形式 */ ulong total; int ret; total = CFG_ENV_SIZE; /* 参数表大小,包括参数表头部 */ /*从nand flash 中读取环境变量到全局变量env_ptr指向的内存中,这里需要注意的是CFG_ENV_OFFSET、CFG_ENV_SIZE这两个宏定义,它们表示从NAND flash中偏移为CFG_ENV_OFFSET处读取大小为CFG_ENV_SIZE字节的数据,注意这两个宏定义决定了环境变量在nand flash中保存的位置和大小,不能和nand flash 中的其他分区(kernel分区、rootfs分区)相冲突,不然在执行saveenv命令时可能会覆盖其他分区的内容,导致系统无法启动*/ ret = nand_read(&nand_info[0], CFG_ENV_OFFSET, &total, (u_char*)env_ptr); /*读出操作,flash设备为nand_info,偏移为CFG_ENV_OFFSET,读出的大小为total,目标地址由env_ptr所指*/ if (ret || total != CFG_ENV_SIZE) return use_default(); /* 如果读出的长度不对或出错,则使用默认值 */ if (crc32(0, env_ptr->data, ENV_SIZE) != env_ptr->crc) return use_default(); /* 如果校验出错,使用默认值 */ #endif /* ! ENV_IS_EMBEDDED */ } #endif /* CFG_ENV_OFFSET_REDUND */ 该函数实现真正的重定位功能,先从NAND flash中读取环境变量,如果读取成功,并且crc校验正确的话,就使用NAND flash中读取出来的环境变量,否则使用默认的环境变量。 此外,uboot的参数表还支持一种被称为CFG_ENV_OFFSET_REDUND的冗余模式,它会在flash上保存两个参数表副本,这样在一个副本出错的时候,还可以从另一个副本中去读取,通过这种方式,提高了数据的安全性。 ?devices_init函数:common/devices.c int devices_init (void) { #ifndef CONFIG_ARM /* already relocated for current ARM implementation */ ulong relocation_offset = gd->reloc_off; int i; /* relocate device name pointers */ for (i = 0; i < (sizeof (stdio_names) / sizeof (char *)); ++i) { stdio_names[i] = (char *) (((ulong) stdio_names[i]) + relocation_offset); } #endif /* Initialize the list 创建一个保存device的列表*/ devlist = ListCreate (sizeof (device_t)); if (devlist == NULL) { eputs ("Cannot initialize the list of devices!\n"); return -1; } #if defined(CONFIG_HARD_I2C) || defined(CONFIG_SOFT_I2C) /* 没定义 */ i2c_init (CFG_I2C_SPEED, CFG_I2C_SLAVE); #endif #ifdef CONFIG_LCD /* 没定义 */ drv_lcd_init (); #endif #if defined(CONFIG_VIDEO) || defined(CONFIG_CFB_CONSOLE) /* 没定义 */ drv_video_init (); #endif #ifdef CONFIG_KEYBOARD /* 没定义 */ drv_keyboard_init (); #endif #ifdef CONFIG_LOGBUFFER /* 没定义 */ drv_logbuff_init (); #endif drv_system_init (); #ifdef CONFIG_SERIAL_MULTI/* 没定义 */ serial_devices_init (); #endif #ifdef CONFIG_USB_TTY /* 没定义 */ drv_usbtty_init (); #endif #ifdef CONFIG_NETCONSOLE /* 没定义 */ drv_nc_init (); #endif return (0); } 这个函数实际上就是根据板子的配置初始化各种设备,并调用device_register()注册到系统中去,在这个中函数仅对串口设备初始化,然后注册串口设备.这里我们以drv_system_init 为例解释一下,定义也在common/devices.c,其他代码类似。 static void drv_system_init (void) { device_t dev; memset (&dev, 0, sizeof (dev)); strcpy (dev.name, "serial"); /*串口设备*/ dev.flags = DEV_FLAGS_OUTPUT | DEV_FLAGS_INPUT | DEV_FLAGS_SYSTEM; /*设备属性*/ #ifdef CONFIG_SERIAL_SOFTWARE_FIFO /*注册设备操作函数集(输入函数,输出函数等)*/ dev.putc = serial_buffered_putc; dev.puts = serial_buffered_puts; dev.getc = serial_buffered_getc; dev.tstc = serial_buffered_tstc; #else dev.putc = serial_putc; dev.puts = serial_puts; dev.getc = serial_getc; dev.tstc = serial_tstc; #endif device_register (&dev); /*把该设备注册进系统中去,即把dev添加进上面创建的设备列表中去*/ #ifdef CFG_DEVICE_NULLDEV memset (&dev, 0, sizeof (dev)); strcpy (dev.name, "nulldev"); dev.flags = DEV_FLAGS_OUTPUT | DEV_FLAGS_INPUT | DEV_FLAGS_SYSTEM; dev.putc = nulldev_putc; dev.puts = nulldev_puts; dev.getc = nulldev_input; dev.tstc = nulldev_input; device_register (&dev); #endif } 该函数注册了一个串口设备和一个空设备(根据配置而定)。其他的设备初始化函数以此大同小异,主要就是初始化好相关设备的设备信息,并注册到系统中去,详细代码大家可以自己去分析。 ?jumptable_init函数:common/exports.c void jumptable_init (void) { int i; /*分配一块buffer用于存放跳转函数地址*/ gd->jt = (void **) malloc (XF_MAX * sizeof (void *)); for (i = 0; i < XF_MAX; i++) gd->jt[i] = (void *) dummy; /*默认跳转函数地址,是一个空函数的地址*/ /*为每种功能定义各自的跳转函数*/ gd->jt[XF_get_version] = (void *) get_version; gd->jt[XF_malloc] = (void *) malloc; gd->jt[XF_free] = (void *) free; gd->jt[XF_getenv] = (void *) getenv; gd->jt[XF_setenv] = (void *) setenv; gd->jt[XF_get_timer] = (void *) get_timer; gd->jt[XF_simple_strtoul] = (void *) simple_strtoul; gd->jt[XF_udelay] = (void *) udelay; #if defined(CONFIG_I386) || defined(CONFIG_PPC) gd->jt[XF_install_hdlr] = (void *) irq_install_handler; gd->jt[XF_free_hdlr] = (void *) irq_free_handler; #endif /* I386 || PPC */ #if (CONFIG_COMMANDS & CFG_CMD_I2C) gd->jt[XF_i2c_write] = (void *) i2c_write; gd->jt[XF_i2c_read] = (void *) i2c_read; #endif /* CFG_CMD_I2C */ } static void dummy(void) { } 可以看出该函数主要就是为不同的功能安装了不同的功能函数。 ?console_init_r函数:common/console.c /* Called after the relocation - use desired console functions */ int console_init_r (void) { /*1. 首先获取由device_init里注册的input,output设备*/ device_t *inputdev = NULL, *outputdev = NULL; int i, items = ListNumItems (devlist); #ifdef CONFIG_SPLASH_SCREEN //没定义 /* suppress all output if splash screen is enabled and we have a bmp to display */ if (getenv("splashimage") != NULL) outputdev = search_device (DEV_FLAGS_OUTPUT, "nulldev"); #endif #ifdef CONFIG_SILENT_CONSOLE //没定义 /* Suppress all output if "silent" mode requested */ if (gd->flags & GD_FLG_SILENT) outputdev = search_device (DEV_FLAGS_OUTPUT, "nulldev"); #endif /* Scan devices looking for input and output devices */ /*寻找系统上存在的输入/输出设备,别忘了上面注册的串口设备就是输入/输出设备*/ for (i = 1; (i <= items) && ((inputdev == NULL) || (outputdev == NULL)); i++) { device_t *dev = ListGetPtrToItem (devlist, i); if ((dev->flags & DEV_FLAGS_INPUT) && (inputdev == NULL)) { inputdev = dev; /*找到输入设备,参考drv_system_init */ } if ((dev->flags & DEV_FLAGS_OUTPUT) && (outputdev == NULL)) { outputdev = dev; /*找到输出设备,参考drv_system_init */ } } /* Initializes output console first */ /*由drv_system_init可知,我们可以找到输入/输出设备,而且都是同一个serial设备*/ if (outputdev != NULL) { console_setfile (stdout, outputdev); /*设置标准输出设备*/ console_setfile (stderr, outputdev); /*设置标准的错误输出设备*/ } /* Initializes input console */ if (inputdev != NULL) { console_setfile (stdin, inputdev); /*设置标准输入设备*/ } gd->flags |= GD_FLG_DEVINIT; /*device initialization completed设置初始化完成标记*/ #ifndef CFG_CONSOLE_INFO_QUIET /* Print information 打印相关设备信息*/ puts ("In: "); if (stdio_devices[stdin] == NULL) { puts ("No input devices available!\n"); } else { printf ("%s\n", stdio_devices[stdin]->name); } puts ("Out: "); if (stdio_devices[stdout] == NULL) { puts ("No output devices available!\n"); } else { printf ("%s\n", stdio_devices[stdout]->name); } puts ("Err: "); if (stdio_devices[stderr] == NULL) { puts ("No error devices available!\n"); } else { printf ("%s\n", stdio_devices[stderr]->name); } #endif /* CFG_CONSOLE_INFO_QUIET */ /* Setting environment variables 设置环境变量*/ for (i = 0; i < 3; i++) { setenv (stdio_names[i], stdio_devices[i]->name); } #if 0 /* If nothing usable installed, use only the initial console */ if ((stdio_devices[stdin] == NULL) && (stdio_devices[stdout] == NULL)) return (0); #endif return (0); } 该函数主要是设置好了标准输入,标准输出,标准错误输出设备,并定义好相关输入,输出函数,以使后面的如puts(),printf()等函数可以运行,这个应该不陌生的。为了一解疑 惑我们可以继续分析下去: static int console_setfile (int file, device_t * dev) /* common/console.c */ { int error = 0; if (dev == NULL) return -1; switch (file) { case stdin: case stdout: case stderr: /* Start new device */ if (dev->start) { error = dev->start ();/*在drv_system_init下没定义这个函数*/ /* If it's not started dont use it */ if (error < 0) break; } /* Assign the new device (leaving the existing one started) */ stdio_devices[file] = dev; /*保存标准输入,标准输出,标准错误输出设备*/ /* 设置好标准输入,标准输出,标准错误输出设备的输入,输出函数,可以从drv_system_init下查到 */ switch (file) { case stdin: gd->jt[XF_getc] = dev->getc; gd->jt[XF_tstc] = dev->tstc; break; case stdout: gd->jt[XF_putc] = dev->putc; gd->jt[XF_puts] = dev->puts; gd->jt[XF_printf] = printf; break; } break; default: /* Invalid file ID */ error = -1; } return error; } 这个函数就是初始化好标准输入,标准输出,标准错误输出设备,这以后就可以调用如puts,printf等函数了. 我们以puts为例继续分析: void puts (const char *s) /* common/console.c */ { #ifdef CONFIG_SILENT_CONSOLE if (gd->flags & GD_FLG_SILENT) return; #endif if (gd->flags & GD_FLG_DEVINIT) {/* 这个标记前面设置过了 */ /* Send to the standard output */ fputs (stdout, s); /*就是调用这个函数*/ } else { /* Send directly to the handler */ serial_puts (s); } } void fputs (int file, const char *s) { if (file < MAX_FILES) stdio_devices[file]->puts (s); /* 这里就是调用我们初始化时设置的了即serial_puts()函数,可以在drv_system_init查到 */ } serial_puts函数就是和具体设备相关了,对于smdk2410的代码如下: void serial_puts (const char *s) /* cpu/arm920t/s3c24x0/serial.c */ { while (*s) { serial_putc (*s++); } } void serial_putc (const char c) /* cpu/arm920t/s3c24x0/serial.c */ { S3C24X0_UART * const uart = S3C24X0_GetBase_UART(UART_NR); #ifdef CONFIG_MODEM_SUPPORT if (be_quiet) return; #endif /* wait for room in the tx FIFO */ while (!(uart->UTRSTAT & 0x2)); #ifdef CONFIG_HWFLOW /* Wait for CTS up */ while(hwflow && !(uart->UMSTAT & 0x1)) ; #endif uart->UTXH = c; /* If \n, also do \r */ if (c == '\n') serial_putc ('\r'); } 这些函数对照着datasheet就很好理解了。printf函数最终也是调用puts函数。至此我们对打印的来龙去脉了解了。 ?main_loop函数common/main.c void main_loop (void) { #ifndef CFG_HUSH_PARSER /*smdk2410没定义*/ static char lastcommand[CFG_CBSIZE] = { 0, }; int len; int rc = 1; int flag; #endif #if defined(CONFIG_BOOTDELAY) && (CONFIG_BOOTDELAY >= 0) char *s; int bootdelay; #endif #ifdef CONFIG_PREBOOT char *p; #endif #ifdef CONFIG_BOOTCOUNT_LIMIT unsigned long bootcount = 0; unsigned long bootlimit = 0; char *bcs; char bcs_set[16]; #endif /* CONFIG_BOOTCOUNT_LIMIT */ #if defined(CONFIG_VFD) && defined(VFD_TEST_LOGO) /*smdk2410没定义*/ ulong bmp = 0; /* default bitmap */ extern int trab_vfd (ulong bitmap); #ifdef CONFIG_MODEM_SUPPORT /*smdk2410没定义*/ if (do_mdm_init) bmp = 1; /* alternate bitmap */ #endif trab_vfd (bmp); #endif /* CONFIG_VFD && VFD_TEST_LOGO */ #ifdef CONFIG_BOOTCOUNT_LIMIT /*smdk2410没定义*/ bootcount = bootcount_load(); bootcount++; bootcount_store (bootcount); sprintf (bcs_set, "%lu", bootcount); setenv ("bootcount", bcs_set); bcs = getenv ("bootlimit"); bootlimit = bcs ? simple_strtoul (bcs, NULL, 10) : 0; #endif /* CONFIG_BOOTCOUNT_LIMIT */ #ifdef CONFIG_MODEM_SUPPORT /*smdk2410没定义*/ debug ("DEBUG: main_loop: do_mdm_init=%d\n", do_mdm_init); if (do_mdm_init) { char *str = strdup(getenv("mdm_cmd")); setenv ("preboot", str); /* set or delete definition */ if (str != NULL) free (str); mdm_init(); /* wait for modem connection */ } #endif /* CONFIG_MODEM_SUPPORT */ #ifdef CONFIG_VERSION_VARIABLE /*smdk2410没定义*/ { extern char version_string[]; setenv ("ver", version_string); /* set version variable */ } #endif /* CONFIG_VERSION_VARIABLE */ #ifdef CFG_HUSH_PARSER /*smdk2410没定义*/ u_boot_hush_start (); #endif #ifdef CONFIG_AUTO_COMPLETE /*smdk2410没定义*/ install_auto_complete(); #endif #ifdef CONFIG_PREBOOT /*smdk2410没定义*/ if ((p = getenv ("preboot")) != NULL) { # ifdef CONFIG_AUTOBOOT_KEYED /*smdk2410没定义*/ int prev = disable_ctrlc(1); /* disable Control C checking */ # endif # ifndef CFG_HUSH_PARSER /*smdk2410没定义*/ run_command (p, 0); # else parse_string_outer(p, FLAG_PARSE_SEMICOLON | FLAG_EXIT_FROM_LOOP); # endif # ifdef CONFIG_AUTOBOOT_KEYED /*smdk2410没定义*/ disable_ctrlc(prev); /* restore Control C checking */ # endif } #endif /* CONFIG_PREBOOT */ #if defined(CONFIG_BOOTDELAY) && (CONFIG_BOOTDELAY >= 0) s = getenv ("bootdelay"); bootdelay = s ? (int)simple_strtol(s, NULL, 10) : CONFIG_BOOTDELAY; debug ("### main_loop entered: bootdelay=%d\n\n", bootdelay); # ifdef CONFIG_BOOT_RETRY_TIME init_cmd_timeout (); # endif /* CONFIG_BOOT_RETRY_TIME */ #ifdef CONFIG_BOOTCOUNT_LIMIT if (bootlimit && (bootcount > bootlimit)) { printf ("Warning: Bootlimit (%u) exceeded. Using altbootcmd.\n", (unsigned)bootlimit); s = getenv ("altbootcmd"); } else #endif /* CONFIG_BOOTCOUNT_LIMIT */ s = getenv ("bootcmd"); /*smdk2410下这个环境变量是空的*/ debug ("### main_loop: bootcmd=\"%s\"\n", s ? s : ""); if (bootdelay >= 0 && s && !abortboot (bootdelay)) { # ifdef CONFIG_AUTOBOOT_KEYED int prev = disable_ctrlc(1); /* disable Control C checking */ # endif # ifndef CFG_HUSH_PARSER run_command (s, 0); # else parse_string_outer(s, FLAG_PARSE_SEMICOLON | FLAG_EXIT_FROM_LOOP); # endif # ifdef CONFIG_AUTOBOOT_KEYED disable_ctrlc(prev); /* restore Control C checking */ # endif } # ifdef CONFIG_MENUKEY if (menukey == CONFIG_MENUKEY) { s = getenv("menucmd"); if (s) { # ifndef CFG_HUSH_PARSER run_command (s, 0); # else parse_string_outer(s, FLAG_PARSE_SEMICOLON | FLAG_EXIT_FROM_LOOP); # endif } } #endif /* CONFIG_MENUKEY */ #endif /* CONFIG_BOOTDELAY */ #ifdef CONFIG_AMIGAONEG3SE { extern void video_banner(void); video_banner(); } #endif /* * Main Loop for Monitor Command Processing 最核心的就是这个死循环 */ #ifdef CFG_HUSH_PARSER parse_file_outer(); /* This point is never reached */ for (;;); #else for (;;) { #ifdef CONFIG_BOOT_RETRY_TIME if (rc >= 0) { /* Saw enough of a valid command to * restart the timeout. */ reset_cmd_timeout(); } #endif len = readline (CFG_PROMPT); flag = 0; /* assume no special flags for now */ if (len > 0) strcpy (lastcommand, console_buffer); else if (len == 0) flag |= CMD_FLAG_REPEAT; #ifdef CONFIG_BOOT_RETRY_TIME else if (len == -2) { /* -2 means timed out, retry autoboot */ puts ("\nTimed out waiting for command\n"); # ifdef CONFIG_RESET_TO_RETRY /* Reinit board to run initialization code again */ do_reset (NULL, 0, 0, NULL); # else return; /* retry autoboot */ # endif } #endif if (len == -1) puts ("\n"); else rc = run_command (lastcommand, flag); if (rc <= 0) { /* invalid command or not repeatable, forget it */ lastcommand[0] = 0; } } #endif /*CFG_HUSH_PARSER*/ } 该函数比较长,但核心就是不停的从串口获取命令并解析执行此命令,这个功能就在函数尾部的那个死循环里,我们重点就是分析这个循环。 ?readline函数:common/main.c /* * Prompt for input and read a line. * If CONFIG_BOOT_RETRY_TIME is defined and retry_time >= 0, * time out when time goes past endtime (timebase time in ticks). * Return: number of read characters * -1 if break * -2 if timed out */上面的注释说的很清楚了,就是提示用户输入命令,并读取这个命令 int readline (const char *const prompt) { #ifdef CONFIG_CMDLINE_EDITING /*sdmk2410没定义*/ char *p = console_buffer; /* console_buffer 是个全局变量*/ unsigned int len=MAX_CMDBUF_SIZE; int rc; static int initted = 0; if (!initted) { hist_init(); initted = 1; } puts (prompt); rc = cread_line(p, &len); return rc < 0 ? rc : len; #else char *p = console_buffer; int n = 0; /* buffer index */ int plen = 0; /* prompt length */ int col; /* output column cnt */ char c; /* print prompt */ if (prompt) { /*如果要输出提示符,则输出*/ plen = strlen (prompt); puts (prompt); } col = plen; for (;;) { #ifdef CONFIG_BOOT_RETRY_TIME /*sdmk2410没定义*/ while (!tstc()) { /* while no incoming data */ if (retry_time >= 0 && get_ticks() > endtime) return (-2); /* timed out */ } #endif WATCHDOG_RESET(); /* Trigger watchdog, if needed */ #ifdef CONFIG_SHOW_ACTIVITY /*sdmk2410没定义*/ while (!tstc()) { extern void show_activity(int arg); show_activity(0); } #endif c = getc();/*从串口获取用户输入的命令*/ /* * Special character handling */ /*下面这个switch就是处理不同的字符了*/ switch (c) { case '\r': /* Enter */ case '\n': *p = '\0'; puts ("\r\n"); return (p - console_buffer); /*输入结束*/ case '\0': /* nul */ /*忽略,继续*/ continue; case 0x03: /* ^C - break linux下的ctrl+c功能*/ console_buffer[0] = '\0'; /* discard input */ return (-1); case 0x15: /* ^U - erase line删除*/ while (col > plen) { puts (erase_seq); --col; } p = console_buffer; n = 0; continue; case 0x17: /* ^W - erase word删除*/ p=delete_char(console_buffer, p, &col, &n, plen); while ((n > 0) && (*p != ' ')) { p=delete_char(console_buffer, p, &col, &n, plen); } continue; case 0x08: /* ^H - backspace*/ case 0x7F: /* DEL - backspace*/ p=delete_char(console_buffer, p, &col, &n, plen); 删除 continue; default: /*获取常规字符*/ /* * Must be a normal character then */ if (n < CFG_CBSIZE-2) { if (c == '\t') { /* expand TABs*/ #ifdef CONFIG_AUTO_COMPLETE /* if auto completion triggered just continue */ /*自动补全功能,熟悉linux的肯定知道*/ *p = '\0'; if (cmd_auto_complete(prompt, console_buffer, &n, &col)) { p = console_buffer + n; /* reset */ continue; } #endif puts (tab_seq+(col&07)); col += 8 - (col&07); } else { ++col; /* echo input */ putc (c); } *p++ = c; /*把字符保存在buffer中*/ ++n; } else { /* Buffer full*/ putc ('\a'); } } } #endif /* CONFIG_CMDLINE_EDITING */ } 上面这个函数就是提示用户输入,然后一直等待用户输入,指到用户输入完成, 然后获 取用户数据,并存入全局变量console_buffer中。 再来回顾下那个for循环, 核心中的核心就是run_command函数了,一猜就知道是用户 执行所有用户下达命令的函数 /* * returns: * 1 - command executed, repeatable * 0 - command executed but not repeatable, interrupted commands are * always considered not repeatable * -1 - not executed (unrecognized, bootd recursion or too many args) * (If cmd is NULL or "" or longer than CFG_CBSIZE-1 it is * considered unrecognized) * * WARNING: * * We must create a temporary copy of the command since the command we get * may be the result from getenv(), which returns a pointer directly to * the environment data, which may change magicly when the command we run * creates or modifies environment variables (like "bootp" does). */ int run_command (const char *cmd, int flag) { cmd_tbl_t *cmdtp; char cmdbuf[CFG_CBSIZE]; /* working copy of cmd */ char *token; /* start of token in cmdbuf */ char *sep; /* end of token (separator) in cmdbuf */ char finaltoken[CFG_CBSIZE]; char *str = cmdbuf; char *argv[CFG_MAXARGS + 1]; /* NULL terminated */ int argc, inquotes; int repeatable = 1; int rc = 0; #ifdef DEBUG_PARSER printf ("[RUN_COMMAND] cmd[%p]=\"", cmd); puts (cmd ? cmd : "NULL"); /* use puts - string may be loooong */ puts ("\"\n"); #endif clear_ctrlc(); /* forget any previous Control C */ if (!cmd || !*cmd) { /*先是对命令的有效性进行检测*/ return -1; /* empty command */ } if (strlen(cmd) >= CFG_CBSIZE) { puts ("## Command too long!\n"); return -1; } strcpy (cmdbuf, cmd); /*备份command*/ /* Process separators and check for invalid * repeatable commands */ #ifdef DEBUG_PARSER printf ("[PROCESS_SEPARATORS] %s\n", cmd); #endif while (*str) { /*看前面的定义,str指向cmdbuf */ /* * Find separator, or string end * Allow simple escape of ';' by writing "\;" */ /*下面这个for是对u-boot的特殊语法的解析,这里就不分析了*/ for (inquotes = 0, sep = str; *sep; sep++) { if ((*sep=='\'') && (*(sep-1) != '\\')) inquotes=!inquotes; if (!inquotes && (*sep == ';') &&/* separator */ ( sep != str) && /* past string start */ (*(sep-1) != '\\')) /* and NOT escaped */ break; } /* * Limit the token to data between separators */ token = str; if (*sep) { str = sep + 1; /* start of command for next pass */ *sep = '\0'; } else str = sep; /* no more commands for next pass */ #ifdef DEBUG_PARSER printf ("token: \"%s\"\n", token); #endif /* find macros in this token and replace them */ process_macros (token, finaltoken); /* Extract arguments */ if ((argc = parse_line (finaltoken, argv)) == 0) { /*获取参数*/ rc = -1; /* no command at all */ continue; } /* Look up command in command table */ if ((cmdtp = find_cmd(argv[0])) == NULL) { /*获取对应的command*/ printf ("Unknown command '%s' - try 'help'\n", argv[0]); rc = -1; /* give up after bad command */ continue; } /* found - check max args */ if (argc > cmdtp->maxargs) { /*检测command语法是否正确*/ printf ("Usage:\n%s\n", cmdtp->usage); rc = -1; continue; } #if (CONFIG_COMMANDS & CFG_CMD_BOOTD) /* avoid "bootd" recursion */ if (cmdtp->cmd == do_bootd) { #ifdef DEBUG_PARSER printf ("[%s]\n", finaltoken); #endif if (flag & CMD_FLAG_BOOTD) { puts ("'bootd' recursion detected\n"); rc = -1; continue; } else { flag |= CMD_FLAG_BOOTD; } } #endif /* CFG_CMD_BOOTD */ /* OK - call function to do the command */ if ((cmdtp->cmd) (cmdtp, flag, argc, argv) != 0) { /*执行具体的command*/ rc = -1; } repeatable &= cmdtp->repeatable; /* Did the user stop this? */ if (had_ctrlc ()) return 0; /* if stopped then not repeatable */ } return rc ? rc : repeatable; } 这个函数主要是对用户输入的命令进行语法分析,从中获取命令,参数等信息,并查找一张系统保存的命令表,找到该命令对应的处理函数。并调用它来处理这个命令。 首先看看命令表中存的每个命令的属性,结构体cmd_tbl_t在include/command.h中定义如下: struct cmd_tbl_s { char *name; /* Command Name命令名*/ int maxargs; /* maximum number of arguments最大参数个数*/ int repeatable; /* autorepeat allowed? 是否自动重复*/ int (*cmd)(struct cmd_tbl_s *, int, int, char *[]); /*响应函数 */ char *usage; /* Usage message (short) 简短的帮助信息*/ #ifdef CFG_LONGHELP char *help; /* Help message (long) 较详细的帮助信息*/ #endif #ifdef CONFIG_AUTO_COMPLETE /* do auto completion on the arguments自动补全参数*/ int (*complete)(int argc, char *argv[], char last_char, int maxv, char *cmdv[]); #endif }; 内存中保存命令的help字段会占用一定的内存,通过配置U-Boot可以选择是否保存help字段。若在include/configs/smdk2410.h中定义了CFG_LONGHELP宏,则在U-Boot中使用help命令查看某个命令的帮助信息时将显示 usage和help字段的内容,否则就只显示usage字段的内容。接着看如何定义命令: include/command.h #define Struct_Section __attribute__ ((unused,section (".u_boot_cmd"))) 凡是带有__attribute__ ((unused,section (".u_boot_cmd"))属性声明的变量都将被存放在".u_boot_cmd"段中,并且即使该变量没有在代码中显式的使用编译器也不产生警告信息。在U-Boot连接脚本u-boot.lds中定义了".u_boot_cmd"段: . = .; __u_boot_cmd_start = .; /* 将__u_boot_cmd_start指定为当前地址 */ .u_boot_cmd : { *(.u_boot_cmd) } __u_boot_cmd_end = .; /* 将__u_boot_cmd_end指定为当前地址 */ 这表明带有“.u_boot_cmd”声明的函数或变量将存储在“u_boot_cmd”段。这样只要将U-Boot所有命令对应的 cmd_tbl_t变量加上“.u_boot_cmd”声明,编译器就会自动将其放在“u_boot_cmd”段,查找cmd_tbl_t变量时只要在 __u_boot_cmd_start与__u_boot_cmd_end之间查找就可以了。 #ifdef CFG_LONGHELP /*每个命令就用这个宏来定义,并加进系统命令表中*/ #define U_BOOT_CMD(name,maxargs,rep,cmd,usage,help) \ cmd_tbl_t __u_boot_cmd_##name Struct_Section = {#name, maxargs, rep, cmd, usage, help} /*“##”与“#”都是预编译操作符,“##”有字符串连接的功能,“#”表示后面紧接着的 是一个字符串。它定义一个command变量,并把它放入".u_boot_cmd"节中*/ #else /* no long help info */ #define U_BOOT_CMD(name,maxargs,rep,cmd,usage,help) \ cmd_tbl_t __u_boot_cmd_##name Struct_Section = {#name, maxargs, rep, cmd, usage} #endif /* CFG_LONGHELP */ #endif /* __COMMAND_H */ U_BOOT_CMD宏在include/command.h中定义,其中U_BOOT_CMD命令格式如下: U_BOOT_CMD(name,maxargs,rep,cmd,usage,help) 各个参数的意义如下: name:命令名,非字符串,但在U_BOOT_CMD中用“#”符号转化为字符串 maxargs:命令的最大参数个数 rep:是否自动重复(按Enter键是否会重复执行) cmd:该命令对应的响应函数 usage:简短的使用说明(字符串) help:较详细的使用说明(字符串) 实质上就是用U_BOOT_CMD宏定义的信息构造了一个cmd_tbl_t类型的结构体。编译 器将该结构体放在“u_boot_cmd”段,执行命令时就可以在“u_boot_cmd”段查找到对应的 cmd_tbl_t类型结构体。 命令执行的过程: (1)在U-Boot中输入命令“AAA”执行时,U-Boot接收输入的字符串“AAA”,传递给 run_command函数。run_command函数在common/main.c中定义。 (2)run_command函数调用common/command.c中实现的find_cmd函数在 __u_boot_cmd_start与__u_boot_cmd_end间查找命令,并返回命令的cmd_tbl_t结构。find_cmd 函数在common/command.c定义。 (3)然后run_command函数使用返回的cmd_tbl_t结构中的函数指针调用AAA命令的响应 函数do_AAA,从而完成了命令的执行。 下面我们就以引导linux内核的命令bootm为例,说一下u-boot到linux过渡及参数传 递的整个过程:common/cmd_bootm.c: U_BOOT_CMD( bootm, CFG_MAXARGS, 1, do_bootm, "bootm - boot application image from memory\n", "[addr [arg ...]]\n - boot application image stored in memory\n" "\tpassing arguments 'arg ...'; when booting a Linux kernel,\n" "\t'arg' can be the address of an initrd image\n" #ifdef CONFIG_OF_FLAT_TREE "\tWhen booting a Linux kernel which requires a flat device-tree\n" "\ta third argument is required which is the address of the of the\n" "\tdevice-tree blob. To boot that kernel without an initrd image,\n" "\tuse a '-' for the second argument. If you do not pass a third\n" "\ta bd_info struct will be passed instead\n" #endif ); U-Boot使用命令bootm来启动已经加载到内存中的内核。而bootm命令实际上调用的 是do_bootm函数。do_bootm()函数很大,但是大部分代码都很简单,就是做一些检测,看 是否是正确的image,做的检测有:magic number, crc校验,体系结构是否正确等等,如果是压 缩的还会先解压缩。对于Linux内核,最后do_bootm函数会调用do_bootm_linux函数来设 置标记列表和启动内核。do_bootm_linux函数在lib_arm/armlinux.c中定义。 do_bootm_linux中: void (*theKernel)(int zero, int arch, uint params); theKernel = (void (*)(int, int, uint))images->ep; theKernel (0, machid, bd->bi_boot_params); 第2行代码将内核的入口地址“images->ep”强制类型转换为函数指针。根据ATPCS规则, 函数的参数个数不超过4个时,使用r0~r3这4个寄存器来传递参数。因此第3行的函数调用则 会将0放入r0,机器码machid放入r1,内核参数地址 bd->bi_boot_params放入r2,从而完 成了寄存器的设置,最后转到内核的入口地址。 到这里,U-Boot的工作就结束了,系统跳转到Linux内核代码执行。
本文档为【uboot启动代码详解】,请使用软件OFFICE或WPS软件打开。作品中的文字与图均可以修改和编辑, 图片更改请在作品中右键图片并更换,文字修改请直接点击文字进行修改,也可以新增和删除文档中的内容。
该文档来自用户分享,如有侵权行为请发邮件ishare@vip.sina.com联系网站客服,我们会及时删除。
[版权声明] 本站所有资料为用户分享产生,若发现您的权利被侵害,请联系客服邮件isharekefu@iask.cn,我们尽快处理。
本作品所展示的图片、画像、字体、音乐的版权可能需版权方额外授权,请谨慎使用。
网站提供的党政主题相关内容(国旗、国徽、党徽..)目的在于配合国家政策宣传,仅限个人学习分享使用,禁止用于任何广告和商用目的。
下载需要: 免费 已有0 人下载
最新资料
资料动态
专题动态
is_721103
暂无简介~
格式:doc
大小:272KB
软件:Word
页数:88
分类:工学
上传时间:2017-09-20
浏览量:44