从u-boot.lds中ENTRY所指定处可以看出,整个uboot程序的起始就是_start处。

不简单的头文件包含

(1)#include <config.h>。config.h是在include目录下的,这个文件不是源码中本身存在的文件,而是配置过程中自动生成的文件(参考uboot配置和编译过程详解章节,该文件中的内容为 #include <configs/x210_sd.h> )。可见start.S中包含的第一个头文件就是:include/configs/x210_sd.h,这个文件是整个uboot移植时的配置文件。这里面是好多宏。

(2)#include <version.h>。include/version.h中包含了include/version_autogenerated.h,这个头文件就是配置过程中自动生成的(参考uboot配置和编译过程详解章节,该文件中的内容为 #define U_BOOT_VERSION "U-Boot 1.3.4" )。这里面定义的宏U_BOOT_VERSION的值是一个字符串,字符串中的版本号信息来自于Makefile中的配置值。这个宏在程序中会被调用,在uboot启动过程中会串口打印出uboot的版本号,那个版本号信息就是从这来的。

(3)#include <asm/proc/domain.h>。实际文件是:include/asm-arm/proc-armv/domain.h(参考uboot配置和编译过程详解章节中mkconfig脚本中创建符号链接部分)。这样的设计主要是为了可移植性,因为如果直接包含,则start.S文件和CPU架构(和硬件)有关了,可移植性就差了。使用符号链接之后再更改cpu等信息时就不用直接更改start.S文件,只需要更改配置阶段的值。

启动代码的16字节头部

(1)裸机中讲过,在SD卡启动/Nand启动等整个镜像开头需要16字节的校验头。(mkv210image.c中就是为了计算这个校验头)。我们以前做裸机程序时根本没考虑这16字节校验头,因为:1、如果我们是usb启动直接下载的方式启动的则不需要16字节校验头(irom application note);2、如果是SD卡启动mkv210image.c中会给原镜像前加16字节的校验头。

(2)uboot这里start.S中在开头位置放了16字节的填充占位,这个占位的16字节只是保证正式的image的头部确实有16字节,但是这16字节的内容是不对的,还是需要后面去计算校验和然后重新填充的。

(3)sd_fusing/C110-EVT1-mkbl1.c文件就是真正计算校验和的源代码

#if defined(CONFIG_EVT1) && !defined(CONFIG_FUSED).word 0x2000.word 0x0.word 0x0.word 0x0
#endif

_start

  • 异常向量表的构建

(1)根据ARM裸机中讲到的异常向量表知识,一般情况下异常向量表的地址是从0x0开始的,如果我们的代码也刚好是在0x0开始,那么这里就是我们所设置的异常向量表

(2)但是我们的S5PV210中uboot起始地址并不在0x0地址,并且uboot中并没有找到设置cp15协处理器的c12去重新设置中断向量表的基地址并且此时uboot的虚拟地址映射还没有起作用,所以我认为这里所设置的异常向量表并没有实际用处,除非芯片内部或者iROM代码中有做一些处理,例如修改cp15协处理器或者进行虚拟地址映射。(这里目前没有深入分析,如果你有更好的解释请评论区纠正)

(3)CPU在执行uboot代码时真正去执行的有效代码是reset处的代码,因此reset符号处才是真正的有意义的代码开始的地方。

(4)关于异常向量表的一些理解

  • 有点意思的deadbeef
.balignl 16,0xdeadbeef

(1).balignl 16,0xdeadbeef.    这一句指令是让当前地址对齐排布,如果当前地址不对齐则自动向后走地址直到对齐,并且向后走的那些内存要用0xdeadbeef来填充。

(2)0xdeadbeef这是一个十六进制的数字,这个数字很有意思,组成这个数字的十六进制数全是abcdef之中的字母,而且这8个字母刚好组成了英文的dead beef这两个单词,字面意思是坏牛肉。

(3)为什么要对齐访问?有时候是效率的要求,有时候是硬件的特殊要求。

  • TEXT_BASE等

(1)第100行这个TEXT_BASE就是上个课程中分析Makefile时讲到的那个配置阶段的TEXT_BASE,其实就是我们链接时指定的uboot的链接地址。(值就是c3e00000,这是虚拟地址)

(2)源代码中和配置Makefile中很多变量是可以互相运送的。简单来说有些符号的值可以从Makefile中传递到源代码中。

  • CFG_PHY_UBOOT_BASE     33e00000    uboot在DDR中的物理地址
  • 将CPU设置为禁止FIQ IRQ,ARM状态,SVC模式
msr cpsr_c, #0xd3       @ I & F disable, Mode: 0x13 - SVC

(1)其实ARM CPU在复位时默认就会进入SVC模式,但是这里还是使用软件将其置为SVC模式。整个uboot工作时CPU一直处于SVC模式。

  • 设置L2、L1cache和MMU

(1)bl    disable_l2cache        // 禁止L2 cache

(2)bl    set_l2cache_auxctrl_cycle    // l2 cache相关初始化

(3)bl    enable_l2cache        // 使能l2 cache

(4)刷新L1 cache的icache和dcache。

(5)关闭MMU

  • 识别并暂存启动介质选择

(1)从哪里启动是由SoC的OM5:OM0这6个引脚的高低电平决定的。

(2)实际上在210内部有一个寄存器(地址是0xE0000004),这个寄存器中的值是硬件根据OM引脚的设置而自动设置值的。这个值反映的就是OM引脚的接法(电平高低),也就是真正的启动介质是谁。

(3)我们代码中可以通过读取这个寄存器的值然后判断其值来确定当前选中的启动介质。

(4)260行中给r3,这个值决定了我们的启动介质,我们这里是SD启动所以值为#BOOT_MMCSD(0x03)。

(5)278行将r3中的值写入了INFORM3寄存器中存储着。包括INFORM3在内的INFORM0到INFORM6,是用户自定义信息寄存器,可供我们自己使用。所以INFORM3的值就表示了我们的启动介质。

  • 设置栈(SRAM中的栈)

(1)284-286行第一次设置栈。这次设置栈是在SRAM中设置的,因为当前整个代码还在SRAM中运行,此时DDR还未被初始化还不能用。栈地址0xd0036000是自己指定的,指定的原则就是这块空间只给栈用,不会被别人占用。

(2)在调用函数前初始化栈,主要原因是在被调用的函数内还有再次调用函数,而BL只会将返回地址存储到LR中,但是我们只有一个LR,所以在第二层调用函数前要先将LR入栈,否则函数返回时第一层的返回地址就丢了。

  • 调用lowlevel_init

(1)lowlevel_init函数真正的地方,是在uboot/board/samsumg/x210/lowlevel_init.S中

  • 供电锁存(lowlevel_init中已经做过)
  • 设置栈(DDR中的栈)

(1)之前在调用lowlevel_init程序前设置过1次栈(start.S 284-287行),那时候因为DDR尚未初始化,因此程序执行都是在SRAM中,所以在SRAM中分配了一部分内存作为栈。本次因为DDR已经被初始化了,因此要把栈挪移到DDR中,所以要重新设置栈,这是第二次(start.S 297-299行);这里实际设置的栈的地址是33E00000,刚好在uboot的代码段的前面紧挨着。

  • 再次判断当前地址以决定是否重定位

(1)再次用相同的代码判断运行地址是在SRAM中还是DDR中,不过本次判断的目的不同(上次判断是为了决定是否要执行初始化时钟和DDR的代码)这次判断是为了决定是否进行uboot的relocate。

  • 如果从MMCSD通道2启动则执行mmcsd_boot

(1)这部分代码应该是以补丁的形式加入uboot中的,使的uboot启动代码结构变得混乱

(2)D0037488这个内存地址在SRAM中,这个地址中的值是被硬件自动设置的。硬件根据我们实际电路中SD卡在哪个通道中,会将这个地址中的值设置为相应的数字。譬如我们从SD0通道启动时,这个值为EB000000;从SD2通道启动时,这个值为EB200000

  • 将INFORM3(表示了我们的启动介质)中的值读出来跟不同的值(代表了不同的启动介质)逐个进行比较,执行对应的xxx_boot函数。我们这里会执行mmcsd_boot
  • mmcsd_boot

(1)调用movi_bl2_copy函数根据启动时的MMCSD通道调用iROM提供的Device Copy Function函数将BL2从存储设备拷贝到DDR中

(2)以SD2为例

ret = copy_bl2(2, MOVI_BL2_POS, MOVI_BL2_BLKCNT,CFG_PHY_UBOOT_BASE, 0);
#define MOVI_BL2_POS      ((eFUSE_SIZE / MOVI_BLKSIZE) + MOVI_BL1_BLKCNT + MOVI_ENV_BLKCNT)    //1+16+32=49

2表示MMCSD通道2;MOVI_BL2_POS是uboot的第二部分在SD卡中的开始扇区(49号扇区),这个扇区数字必须和烧录uboot时烧录的位置相同;MOVI_BL2_BLKCNT是uboot的长度占用的扇区数(512KB);CFG_PHY_UBOOT_BASE是重定位时将uboot的第二部分复制到DDR中的起始地址(33E00000)

(3)调用after_copy

lowlevel_init

  • 检查复位状态
    (1)复杂CPU允许多种复位情况。譬如直接冷上电、热启动、睡眠(低功耗)状态下的唤醒等,这些情况都属于复位。所以我们在复位代码中要去检测复位状态,来判断到底是哪种情况。
    (2)判断哪种复位的意义在于:冷上电时DDR是需要初始化才能用的;而热启动或者低功耗状态下的复位则不需要再次初始化DDR。
  • IO状态恢复
  • 关看门狗
  • 一些SRAM SROM相关GPIO设置
  • 供电锁存
  • 判定当前运行是在SRAM中还是DDR中
    这几行代码的作用就是判定当前代码执行的位置在SRAM中还是在DDR中。为什么要做这个判定?原因1:BL1(uboot的前一部分)在SRAM中有一份,在DDR中也有一份,因此如果是冷启动那么当前代码应该是在SRAM中运行的BL1,如果是低功耗状态的复位这时候应该就是在DDR中运行的。原因2:我们判定当前运行代码的地址是有用的,可以指导后面代码的运行。譬如在lowlevel_init.S中判定当前代码的运行地址,就是为了确定要不要执行时钟初始化和初始化DDR的代码。如果当前代码是在SRAM中,说明冷启动,那么时钟和DDR都需要初始化;如果当前代码是在DDR中,那么说明是热启动则时钟和DDR都不用再次初始化。
    (1)bic    r1, pc, r0    这句代码的意义是:将pc的值中的某些bit位清0,剩下一些特殊的bit位赋值给r1(r0中为1的那些位清零)相等于:r1 = pc & ~(ff000fff)
              ldr    r2, _TEXT_BASE    加载链接地址到r2,然后将r2的相应位清0剩下特定位。
    (2)最后比较r1和r2,进行相应的处理
    (3)这一段代码是通过读取当前运行地址和链接地址,然后处理两个地址后对比是否相等,来判定当前运行是在SRAM中(不相等)还是DDR中(相等)。从而决定是否跳过下面的时钟和DDR初始化。
  • 调用system_clock_init初始化时钟
  • 调用mem_ctrl_asm_init初始化DDR
  • 调用uart_asm_init初始化串口并打印一个字符‘O’
  • tzpc_init
  • CONFIG_ONENAND和CONFIG_NAND在我们当前工程都没有定义,所以不用管。
  • 串口打印字符'K'
  • pop {pc}返回

system_clock_init

(1)在x210_sd.h中300行到428行,都是和时钟相关的配置值。这些宏定义就决定了210的时钟配置是多少。也就是说代码在lowlevel_init.S中都写好了,但是代码的设置值都被宏定义在x210_sd.h中了。因此,如果移植时需要更改CPU的时钟设置,根本不需要动代码,只需要在x210_sd.h中更改配置值即可。

mem_ctrl_asm_init

(1)在uboot中,可用的物理地址范围为:0x30000000-0x4FFFFFFF。一共512MB,其中30000000-3FFFFFFF为DMC0,40000000-4FFFFFFF为DMC1。

(2)我们需要的内存配置值在x210_sd.h的438行到468行之间。分析的时候要注意条件编译的条件,配置头文件中考虑了不同时钟配置下的内存配置值,这个的主要目的是让不同时钟需求的客户都能找到合适自己的内存配置值。

(3)猜测是由于寄存器配置的问题我们访问0x23e00000起始的一片空间和访问0x33e00000起始的一片空间实际访问的是同一个内存空间,所以对于操作0x23e00000和操作0x33e00000是一样的效果。

after_copy

  • 使能域访问(cp15的c3寄存器)

(1)cp15协处理器内部有c0到c15共16个寄存器,这些寄存器每一个都有自己的作用。我们通过mrc和mcr指令来访问这些寄存器。所谓的操作cp协处理器其实就是操作cp15的这些寄存器。

(2)c3寄存器在mmu中的作用是控制域访问。域访问是和MMU的访问控制有关的。

  • 设置转换表基地址TTB(cp15的c2寄存器)

(1)TT(translation table)表示转换表,TTB就是translation table base,转换表基地址。

(2)转换表是建立一套虚拟地址映射的关键。转换表分2部分,表索引和表项。表索引对应虚拟地址,表项对应物理地址。一对表索引和表项构成一个转换表单元,能够对一个内存块进行虚拟地址转换。(映射基本规定中规定了内存映射和管理是以块为单位的,至于块有多大,要看你的MMU的支持和你自己的选择。在ARM中支持3种块大小,细表1KB、粗表4KB、段1MB)。真正的转换表就是由若干个转换表单元构成的,每个单元负责1个内存块,总体的转换表负责整个内存空间(0-4G)的映射。

(3)整个建立虚拟地址映射的主要工作就是建立这张转换表mmu_table

(4)转换表放置在内存中的,放置时要求起始地址在内存中要xx位对齐。转换表不需要软件去干涉使用,而是将基地址TTB设置到cp15的c2寄存器中,然后MMU工作时会自动去查转换表。

  • 使能MMU单元

(1)cp15的c1寄存器的bit0控制MMU的开关。只要将这一个bit置1即可开启MMU。开启MMU之后上层软件层的地址就必须经过TT的转换才能发给下层物理层去执行。

  • 再次设置栈

(1)第三次设置栈。这次设置栈还是在DDR中,之前虽然已经在DDR中设置过一次栈了,但是本次设置栈的目的是将栈放在比较合适(安全,紧凑而不浪费内存)的地方。

(2)我们实际将栈设置在uboot起始地址上方2MB处,这样安全的栈空间是:2MB-uboot大小-0x1000=1.8MB左右。这个空间既没有太浪费内存,又足够安全。

  • 清理bss
  • ldr    pc, _start_armboot

(1)start_armboot在uboot/lib_arm/board.c中,这是一个C语言实现的函数。这个函数就是uboot的第二阶段。这句代码的作用就是将uboot第二阶段执行的函数的地址传给pc,实际上就是使用一个远跳转直接跳转到DDR中的第二阶段开始地址处。

(2)这里这个远跳转就是uboot第一阶段和第二阶段的分界线。

转换表

VA                  PA                  length
0-10000000          0-10000000          256MB
10000000-20000000   0                   256MB
20000000-60000000   20000000-60000000   1GB     512-1.5G
60000000-80000000   0                   512MB   1.5G-2G
80000000-b0000000   80000000-b0000000   768MB   2G-2.75G
b0000000-c0000000   b0000000-c0000000   256MB   2.75G-3G
c0000000-d0000000   30000000-40000000   256MB   3G-3.25G
d-完             d-完             768MB   3.25G-4G

(暂不分析)

x210:uboot和系统移植扩展--uboot启动第一阶段相关推荐

  1. x210:uboot和系统移植扩展--内核启动之解压缩阶段

    zImage的生成过程 依据arch/arm/boot/vmlinux.lds.S生成arch/arm/boot/vmlinux.lds #arch/arm/kernel/.vmlinux.lds.c ...

  2. x210:uboot和系统移植

    注:本文是对朱老师uboot和系统移植课程的备忘引导性笔记,主要是为了能够在学完后快速回忆起相关内容.本文主要记录了一些关键易忘性知识点并包含少量理解性内容,遵循尽量精简的原则,以尽量少的篇幅概括整个 ...

  3. 嵌入式linux之Uboot和系统移植--基础

    <uboot和系统移植-第1部分-uboot学习前传> (观看朱友鹏老师视频后整理的笔记)   1.为什么要有uboot 2.为什么是uboot 3.uboot必须解决哪些问题 4.ubo ...

  4. uboot和系统移植-第1部分-2.1 uboot学习前传

    uboot和系统移植-第1部分-2.1 uboot学习前传 第一部分.章节目录 2.1.1. 为什么要有uboot (1)uboot最主要作用是用来启动操作系统内核.因为操作系统内核本身不能自己启动自 ...

  5. 话说linux内核-uboot和系统移植第14部分-朱有鹏-专题视频课程

    话说linux内核-uboot和系统移植第14部分-5304人已学习 课程介绍         本课程为linux kernel移植的第1部分,主要内容是对linux内核有关的知识和概念的补充.认识清 ...

  6. 嵌入式之uboot源码分析-启动第一阶段学习笔记

    注: 以下的内容来自朱老师物联网大讲堂uboot部分课件 Uboot启动第一阶段start.S执行步骤 1.头文件包含 <config.h>(x210的各种宏定义) <version ...

  7. uboot启动第一阶段详解——汇编代码部分start.S

    前言 uboot启动第一阶段是用汇编语言实现的,大部分都是Soc内部的初始化,可以理解成一些通用的初始化,只要使用该款Soc,第一阶段的初始化流程基本是一样的.不直接用C语言进行初始化是因为,C语言运 ...

  8. 创客exynos-fs4412系统移植-(uboot,内核,文件系统)

    fs4412系统移植学习笔记 环境: ubuntu 14.04 发行版 FS4412 实验平台 交叉编译工具:arm-none-linux-gnueabi- gcc: 4.8.5 目录 fs4412系 ...

  9. [Zybo u-boot Linux系统移植]-ZYBO Zync-7000 Development Board Work Booting Linux on the ZYBO

    ZYBO Zync-7000 Development Board Work Booting Linux on the ZYBO 本文翻译自:http://www.dbrss.org/zybo/tuto ...

最新文章

  1. Android Studio的技巧
  2. linux升级ipv6协议栈,IPv6技术及基于Linux平台IPv6协议栈的实现
  3. linux centos lamp,Centos下搭建LAMP
  4. TensorFlow学习笔记(二十四)自制TFRecord数据集 读取、显示及代码详解
  5. 讲讲Bootstrap是在干啥?
  6. MyBatis 思维导图,让 MyBatis 不再难懂(一)
  7. 案例解读:Oracle目录由于TFA触发bug导致jdb文件未自动清理引起空间不足
  8. matlab编程求平均,matlab中的分组平均函数grpstats的用法
  9. oracle 0.1变.1,Oracle在12.1.0.2开始改变了补丁策略
  10. 小班安全使用计算机教案,小班教案安全用电
  11. 三款免费好用的Gif制作神器
  12. c语言中void delay0.5(),c语言 延时函数
  13. [QNX Hypervisor 2.2用户手册]8.5 vCPU和Hypervisor性能
  14. 魔兽世界单机(芒果3.3.5a)机器人操作命令大全
  15. 为不喝的朋友准备的!
  16. c语言幻数游戏,C中的幻数
  17. 优化:java递归实现笛卡尔积算法
  18. Beyond Compare忽略时间对比
  19. 甲方乙方 (1997)
  20. 基于 Matlab/simulink的锂电池建模与仿真——复现论文《基于二阶EKF的锂离子电池SOC估计的建模与仿真》的仿真部分

热门文章

  1. php手册用法,php手册究竟是什么?如何对php手册进行使用?
  2. 无线系统(EEEN3006J-Wireless Systems)复习笔记 (4)
  3. Nmon的安装及使用
  4. 建模学习笔记(二)Topsis模型算法步骤
  5. Karp-Rabin算法()不完备定理
  6. 用HTML和CSS简单语法写的一个简历Demo
  7. 关于组织幼儿园故事教学活动过程有感
  8. 上海市800电话查询
  9. 【合新通信】-光器件(OSA)之激光焊
  10. java提示版本过低怎么处理_eclipse提示jdk版本过低怎么办 eclipse提示jdk版本过低解决方法...