x210中的uboot启动流程分析

目录

第一部分 背景介绍

一、什么是uboot?

二、为什么要有uboot

三、uboot的需要解决的问题?/uboot的作用?

第二部分 准备工作

一、代码来源

二、查看工具

第三部分 uboot结构分析

一、uboot_jiuding目录分析(位置:uboot_jiuding/)

  • 文件夹分析
  • 文件分析

第四部分 uboot工作过程

内容

一、uboot源码分析1-启动第一阶段

第一部分 背景介绍

一、什么是uboot?

U-Boot,全称 Universal Boot Loader,是遵循GPL条款的开放源码项目。
(1)uboot就是universal bootloader(通用启动代码),通用的意思就是在各种地方都可以用。所以说uboot具有可移植性。
(2)uboot具有可移植性并不是说uboot在哪个开发板都可以随便用,而是说uboot具有在源代码级别的移植能力,可以针对多个开发板进行移植,移植后就可以在这个开发板上使用。

二、为什么要有uboot?

首先要说明嵌入式Linux相关设备的启动流程:
嵌入式系统上电后先执行uboot,然后uboot负责初始化DDR,初始化Flash,然后将OS从Flash中读取到DDR中,然后启动OS(OS启动后uboot就无用了)
所以要使用uboot来完成OS启动前的相关准备。(在goole这个问题的时候,看到了知乎中有网友提问“问什么不把uboot和Linux系统写在一起”,结合网友回答,主要是为了提高Linux中的代码的代码可移植性,尽量将硬件相关的代码分开来,这样就可以在内核中屏蔽掉硬件的相关配置,从而提高内核的兼容性。)

三、uboot的需要解决的问题?/uboot的作用?

1、自身可开机直接启动
2、能引导操作系统内核启动并给内核传参
3、能提供系统部署功能
4、能进行SoC级和板级硬件管理
(1)uboot中实现了一部分硬件的控制能力(uboot初始化了部分硬件),因为uboot为了实现一些任务必须让这些硬件工作,譬如uboot要实现刷机必须能驱动iNand,譬如uboot在刷机时要在LCD上显示进度条就必须能驱动LCD,譬如能通过串口提供操作界面就必须驱动串口。譬如uboot要实现网络功能,就必须驱动网卡芯片。
(2)SoC级就是SoC内部外设,板级就是SoC外开发板上的硬件(譬如网卡、iNand)

第二部分 准备工作

一、代码来源

本代码来自朱老师物联网中嵌入式核心课程中提供的九鼎的x210开发板的uboot。

二、查看工具

使用Source Insight查看代码。

第三部分 uboot结构分析(位置:uboot_jiuding/)

文件夹分析

  • api:和硬件功能无关的功能函数的API,uboot移植时基本不用管,这 些函数是uboot本身使用的。
  • api_examples:示例代码
  • board:board文件夹下每一个文件夹下都代表一个开发板,这个文件夹下就是描述这个开发板的信息。board目录下有多少个文件夹,就表示当前的uboot可以支持多少个开发板
  • common:是普遍的,这个文件夹下放的是一些与具体硬件无关的普遍适用的一些代码。譬如控制台实现、crc校验/但是更多的是主要2类:一类是cmd开头的,是用来实现uboot的命令系统的;另一类是env开头的,是用来实现环境变量的。
  • cpu:是SoC相关的,里面存放的是SoC相关的初始化和控制代码(譬如CPU的、中断的、串口等SoC内部外设,包括起始代码start.s)。
  • disk:磁盘分区相关代码
  • doc:存放文档,里面存放了uboot相关的文档,可以帮助理解uboot的代码。
  • drivers:常用的设备驱动程序,每个类型的设备驱动占用一个子目录。这里面放的是从Linux源代码中抠出来的Linux设备驱动,主要是开发板上必须用到的驱动(网卡驱动、iNand、NandFlash驱动。uboot中的驱动就是Linux中的驱动)。但是Linux是操作系统而uboot是裸机程序,因此这种一直会有不同。uboot中的驱动是Linux驱动的一部分。
  • examples:示例代码
  • fs:file system 文件系统,支持嵌入式开发常见的fs(cramfs,ext2,ext3,jffs2,etc)。这个也是从Linux移植过来,用来管理Flash等资源
  • include:头文件目录,uboot和Linux kernel在管理头文件都采用了同一个思路,就是把所有头文件都包含在了include目录下,而不是头文件跟着自己对应的C文件。所以uboot中头文件包含时路径结构要在这里去找。
  • lib_arm:架构相关的库文件。
  • lib_generic:架构相关的库文件。
  • libfdt:设备数有关的,Linux内核在3.4左右的版本,更改了启动传参的机制,改动设备数来进行启动传参,及逆行硬件信息的描述。
  • nand_spi:Nand相关的。
  • net:net相关的,譬如uboot中的tftp中的ping命令
  • onenand_bl1:onenand启动相关的代码。
  • onenand_ipl:onenand启动相关的代码。
  • post:上电自检相关代码
  • sd_fusing:实现了烧录uboot镜像到SD卡
  • SI_Proj:source insight工程文件
  • tools:里面是一些工具类的代码。譬如mkimage

文件分析

  1. .gitignore:github版本管理

  2. arm_config.mk:后缀是.mk,是一个Makefile文件,将来在某个Makefile中会去调用它。

  3. CHANGELOG
    Changelog_Samsung
    CHANGELOG-before-U-Boot-1.1.5
    修改记录文件,该文件记录了这个uboot项目的版本变迁以及每个版本较上一个版本修改的记录。正式的项目都有这写记录。

  4. config.mk:和arm_config.mk类似

  5. COPYING:版权声明

  6. CREDITS:鸣谢

  7. image_split:是一个脚本,是用来分割uboot.bin

  8. MAINTAINERS:维护者,当前在参与维护uboot源码的参与者

  9. MAKEALL:一个脚本,帮助编译uboot

  10. Makefile:是uboot源代码的主Makefile,将来整个uboot被编译时就是用这个Makefile管理编译的,所以在研究uboot配置时需要关注。

  11. mk:快速编译的脚本,其实就是先清理然后配置然后编译而已。

  12. mkconfig:uboot配置阶段的主要配置脚本,uboot的可以执行很大程度就是靠这个配置脚本。

  13. mkmovi:一个脚本,和iNand/SD启动有关

  14. README

  15. rules.mk:是我们uboot的makefile使用的规则。

第四部分 uboot工作过程

一、uboot源码分析1-启动第一阶段

(代码位置:uboot_jiuding/cpu/s5pc11x/start.S)
主要思路:部分硬件初始化—>加载完整的uboot到DDR—>跳转到第二阶段入口开始执行
需要完成的工作:
(1)构建异常向量表
(2)设置CPU位SVC模式
(3)关看门狗
(4)开发板供电的置锁(维持供电)
(5)始终初始化、DDR初始化
(6)串口初始化,打印OK
(7)进行重定位
(8)建立映射表,并开启MMU
(9)设置栈,进行远跳转到第二阶段。
主要代码:
start.S汇编文件,涉及到特定硬件设备的读写寄存器操作以及特定体系结构的汇编语言
lowlevel_init.S底层硬件的初始化

  1. 不简单的头文件包含
#include 
#include 
#if defined(CONFIG_ENABLE_MMU)
#include 
#endif
#include 
#ifndef CONFIG_ENABLE_MMU
#ifndef CFG_PHY_UBOOT_BASE
#define CFG_PHY_UBOOT_BASE CFG_UBOOT_BASE
#endif
#endif
 #include 

config.h文件是在include目录下,这个文件不是源码中本身存在的文件,而是在配置中自动生成的文件。这个文件内容又包含了一个头文件:#include 。(详见mkconfig第141行代附近码:echo “#include ” >>config.h,此处的"$1"为x210_sd)

  • #include
    include/version.h中包含了include/version_autogenerated.h,这个有文件就是配置过程自动生成的。其内容只有一行:#define U_BOOT_VERSION “U-Boot 1.3.4”,字符串的版本号来自于Makefile中的配置值。这个宏在我们的程序中会被调用,在uboot启动中会串口打印出uboot的版本号,那个版本号信息就是来自于这里。
  • #include
    uboot中本身是没有asm目录,asm是我们配置时创造的符号链接,实际指向的就是asm-arm。
  1. 启动代码的16字节头部
    #if defined(CONFIG_EVT1) && !defined(CONFIG_FUSED)
         .word 0x2000
         .word 0x0
         .word 0x0
         .word 0x0
	#endif

(1)在SD卡/Nand启动等整个镜像开头需要16字节的校验头。(mkv210image.c就是为了计算这个校验头)。以前做裸机程序时根本没考虑这16字节的校验头,因为:如果是usb直接下载启动则不需要16字节的校验头:如果是SD卡启动mkv210image.c中会给原镜像前加16字节校验头。
(2)uboot这里start.S中在开头位置放了16字节的填充占位,这个占位的16字节只是保证正式的image的头部确实有16字节,但是这16字节的内容是不对的,还是需要后面去计算校验和然后重新填充的。
3.异常向量表的构建

.globl _start		//定义一个全局标号——start
_start: b reset	//_start处的内容为跳转到reset标号开始执行
   //以下7条ldr pc,x加上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
//定义了一个word类型的变量undefined_instruction
 _undefined_instruction:
 .word undefined_instruction
 //定义了一个word类型的变量software_interrupt
_software_interrupt:
 .word software_interrupt
  //定义了一个word类型的变量prefetch_abort
_prefetch_abort:
 .word prefetch_abort
  //定义了一个word类型的变量data_abort
_data_abort:
 .word data_abort
 //定义了一个word类型的变量not_used
_not_used:
 .word not_used
   //定义了一个word类型的变量irq
_irq:
 .word irq
  //定义了一个word类型的变量fiq
_fiq:
 .word fiq
  //now 16*4=64 
_pad:
 .word 0x12345678
.global _end_vect
_end_vect:
  1. 有点意思的deadbeef

.balignl 16,0xdeadbeef
(1).balignl 16,0xdeadbeef
这句指令是让当前地址对其排布,如果当前地址不对齐,则地址自动向后走直到对齐,并且向后走的内存由0xdeadbeef填充。
(2)0xdeadbeef这是一个十六进制的数字。
(3)为什么要对齐访问,有时候是效率的要求,有时候是硬件的特殊要求。
5. TEXT_BASE

_TEXT_BASE:
 .word TEXT_BASE

TEXT_BASE就是Makefile时中的那个配置阶段的TEXT_BASE,其实就是我们链接时制定的uboot的链接地址。(值就是c3e00000)
5.MMU相关变量的定义,在此uboot没有用到
//CFG _PHY_UBOOT_BASE 33e00000 uboot在DDR中的物理地址
_TEXT_PHY_BASE:
.word CFG_PHY_UBOOT_BASE
.globl _armboot_start
_armboot_start:
.word _start
6. 设置cpu为svc模式

/*
 * the actual reset code
 */
 reset:
 /*
  * set the cpu to SVC32 mode and IRQ & FIQ disable
  */
   @;mrs r0,cpsr
 @;bic r0,r0,#0x1f
 @;orr r0,r0,#0xd3
 @;msr cpsr,r0
 msr cpsr_c, #0xd3  @ I & F disable, Mode: 0x13 - SVC

(1)msr cpsr_c, #0xd3将CPU设置为禁止FIQ IRQ,ARM状态,SVC模式。

(2)其实ARM CPU在复位时默认就会进入SVC模式,但是这里还是使用软件将其置为SVC模式。整个uboot工作时CPU一直处于SVC模式。
7. 设置L1、L2cache和MMU
bl set_l2cache_auxctrl
bl enable_l2cache
bl disable_l2cache
bl set_l2cache_auxctrl_cycle
bl enable_l2cache

(1)bl disable_l2cache //禁止L2cache
(2)bl set_l2cache_auxctrl_cycle //L2cache相关初始化
(3)bl enable_l2cache //使能L2cache
(4)刷新L1 cache的icache和dcache
(5)关闭MMU
总结:上面这5步都是和CPU的cache和MMU相关。
8. 识别并暂存启动介质选择

         /* Read booting information */
        ldr r0, =PRO_ID_BASE
        ldr r1, [r0,#OMR_OFFSET]
        bic r2, r1, #0xffffffc1

(1)从哪里启动时由SoC的OM5:OM0这6个引脚的高低电平决定的。
(2)实际上在210内部有个寄存器(地址时0xE0000004),这个寄存器中的值时硬件根据OM引脚的设置二自动设置值,反应的就是OM引脚的接法也就是真正的启动介质是谁
(3)我们代码中可以通过读取这个寄存器的值然后判断其值来确定当前选中的启动介质是Nand还是SD还是其它。
(4)

ldr r0, =PRO_ID_BASE
ldr r1, [r0,#OMR_OFFSET]
bic r2, r1, #0xffffffc1

将PRO_ID_BASE + OMR_OFFSET
(这是一个启动方式寄存器,从中可以得到上电时的启动状态)地址处的读取启动信息,值0x0~0x6分别对应几种不同的nandflash启动,0xc对应MMC/SD启动。当确定是某一种启动方式后,将其对应的十六进制存放于INFORM3寄存器中。
在R2寄存器中存储了一个数字,这个数字等于某个特定值就表示SD启动,等于另一个特定值时表示从Nand启动.
(5)第260行

moveq r3, #BOOT_MMCSD

这里给r3中赋值#BOOT_MMCSD(其值为0x03),
10. 第一次设置栈(SRAM中的栈)并调用lowlevel_init

    /*
      * Go setup Memory and board specific bits prior to relocation.
      */
      ldr sp, =0xd0036000 /* end of sram dedicated to u-boot */
     sub sp, sp, #12 /* set stack */
     mov fp, #0
     
     bl lowlevel_init /* go setup pll,mux,memory */

(1)这是第一次设置栈,这次设置栈是在SRAM中进行的,因为当前整个代码还是在SRAM中运行,此时DDR还未被初始化还不能用,栈地址0xd0036000是自己指定的,指定的原则就是这块空间只给栈用,不会给别人占用。
(2)在调用函数前初始化栈,主要原因是在被调用的函数内还有再次调用函数,而BL只会将返回地址存储到LR中,但是我们只有一个LR,所以在第二层调用函数前要将LR入栈,否则函数返回第一层地址就丢失了。
从这儿开始,进入了lowlevel_init函数:
11. 检查复位状态

/* check reset status  */
 
 ldr r0, =(ELFIN_CLOCK_POWER_BASE+RST_STAT_OFFSET)
 ldr r1, [r0]
 bic r1, r1, #0xfff6ffff
 cmp r1, #0x10000
 beq wakeup_reset_pre
 cmp r1, #0x80000
 beq wakeup_reset_from_didle

(1)复杂cpu允许多种复位情况,譬如直接冷上电、热启动、睡眠(低功耗)状态下的唤醒,这些情况都属于复位。我们在复位代码中要去检测复位状态,来判断到底是那种情况。
(2)判断哪种复位状态的意义在于:冷上电时DDR需要初始化才可以使用,热启动或者低功耗启动DDR可以直接使用。
12. IO状态恢复

/* IO Retention release */
 ldr r0, =(ELFIN_CLOCK_POWER_BASE + OTHERS_OFFSET)
 ldr r1, [r0]
 ldr r2, =IO_RET_REL
 orr r1, r1, r2
 str r1, [r0]

这个和上一个和主线启动代码都无关,因此暂时不用去管他。
13. 看门狗

 /* Disable Watchdog */

 ldr r0, =ELFIN_WATCHDOG_BASE /* 0xE2700000 */
 mov r1, #0
 str r1, [r0]
  1. 供电锁存
  2. 判断当前代码执行位置
  3. system_clcok_init
  4. mem_ctrl_asm_init
  5. uart_asm_init
  6. tzpc_init
  7. pop {pc}以返回

三、uboot源码分析2-启动第二阶段

四、总结

你可能感兴趣的:(x210中的uboot启动流程分析)