Uboot全称Universal Boot Loader,一个遵循GPL协议的的开源项目,其作用是引导操作系统,支持引导linux、VxWorks、Solaris等操作系统;其源码组织形式和linux源码很相似,编译也可参照linux源码编译,且包含许多linux源码中的驱动源码,所以uboot实际上可以算作一个微型的操作系统,可以做一些简单工作。

  本文的分析对象是u-boot-2012.10版本,以下内容将根据此版本源码和特定的board展开分析,从设备上电运行的第一行程序开始到引导linux系统正常启动。

一、uboot 目录组织形式:

1、../u-boot-2012.10 //一级目录:

├── api

├── arch

├── board

├── common

├── disk

├── doc

├── drivers

├── dts

├── examples

├── fs

├── include

├── lib

├── nand_spl

├── net

├── post

├── spl

├── test

└── tools

  可见其目录组织样式和linux是极其相似的。

2arch为所支持的体系结构,内容如下:

├── arch

│   ├── arm

│   ├── avr32

│   ├── blackfin

│   ├── m68k

│   ├── microblaze

│   ├── mips

│   ├── nds32

│   ├── nios2

│   ├── openrisc

│   ├── powerpc

│   ├── sandbox

│   ├── sh

│   ├── sparc

│   └── x86

看得出来uboot支持的硬件体系是很全面的。

3board目录是目前已适配的板子:

├── board

│   ├── a3000

│   ├── a4m072

│   ├── actux1

│   ├── actux2

│   ├── actux3

│   ├── actux4

│   ├── adder

│   ├── afeb9260

│   ├── … //太多,此处不再列举

其他目录就不一一列举,和linux相比,还是熟悉的味道,还是熟悉的配方。

  好了,进入正题,uboot的启动本身分为两个大的阶段,第一阶段是从存储介质中读取小部分程序到cpu中,这部分程序要完成引导linux所用的硬件的初始化,以及加载uboot其余程序到RAM中;第二阶段是继续初始化必备硬件,加载linux镜像到RAM中,把执行权限交给linux,完成使命。

二、Uboot启动第一阶段:

主脉络:部分硬件初始化——>加载完整uboot到RAM——>跳转到第二阶段入口开始执行

整个过程最重要的两个文件:

  start.S,汇编语言文件,涉及到特定硬件设备的读写寄存器操作以及特定体系结构的汇编语言;

  lowlevel_init.S,设计的操作和文件名吻合,即完成底层的初始化。

         1、执行流程分析:

  ①、中断向量表

 .globl _start                        //定义一个全局标号_star
_start: b reset //标号_star处的内容为跳转到reset标号开始执行
//将_undefined_instruction标号表示的内容作为地址,加载到PC中,
//这种用法可以实现执行流程的跳转
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
//以上七条ldr pc,x加上b reset共八条指令组成中断向量表

//_undefined_instruction标号表示定义了一个word类型的变量undefined_instruction
_undefined_instruction: .word undefined_instruction

//exception handlers //异常处理
.align //5字节对齐 //可知undefined_instruction的真正用途是指向此处代码,即异常处理的具体实现
undefined_instruction:
get_bad_stack
bad_save_user_regs
bl do_undefined_instruction

由以上内容可知,除第一行代码外,其余代码都是跳转到特定位置去执行中断服务子程序。

  由b reset可知程序正常的流程并不会走到中断处理流程中去(正常情况下当然不应该执行中断子程序,只有发生中断时才去执行),而是直接跳转到reset标号处开始执行。

  ②、reset

 reset:
/*
* set the cpu to SVC32 mode 设置CPU为SVC32模式
*/
mrs r0, cpsr //读出
bic r0, r0, #0x1f //低五位清0
orr r0, r0, #0xd3 //与D3做与操作
msr cpsr,r0 //写回

  CPSR是ARM体系结构中的程序状态寄存器,其结构如下:

M[4:0]     处理器模式             可访问的寄存器

ob10000          user                  pc,r14~r0,CPSR

0b10001          FIQ                     PC,R14_FIQ-R8_FIQ,R7~R0,CPSR,SPSR_FIQ

0b10010            IRQ                             PC,R14_IRQ-R13_IRQ,R12~R0,CPSR,SPSR_IRQ

0B10011          SUPERVISOR   PC,R14_SVC-R13_SVC,R12~R0,CPSR,SPSR_SVC

0b10111          ABORT             PC,R14_ABT-R13_ABT,R12~R0,CPSR,SPSR_ABT

0b11011          UNDEFINEED PC,R14_UND-R8_UND,R12~R0,CPSR,SPSR_UND

0b11111          SYSTEM            PC,R14-R0,CPSR(ARM V4以及更高版本)

I、F、T三位如果写1即禁用,所以以上四句操作的结果为设置CPU为SUPERVISOR模式且禁用中断,至于为什么选择当前模式而不是其他模式?

首先可以排除的是,中止abt和未定义und模式,那都是不太正常的模式;

其次,对于快中断fiq和中断irq来说,此处uboot初始化的时候,也还没啥中断要处理和能够处理,而且即使是注册了终端服务程序后,能够处理中断,那么这两种模式,也是自动切换过去的,所以,此处也不应该设置为其中任何一种模式。

至于usr模式,由于此模式无法直接访问很多的硬件资源,而uboot初始化,就必须要去访问这类资源,所以此处可以排除,不能设置为用户usr模式。

而svc模式本身就属于特权模式,本身就可以访问那些受控资源,而且,比sys模式还多了些自己模式下的影子寄存器,所以,相对sys模式来说,可以访问资源的能力相同,但是拥有更多的硬件资源。

  好了,接着上面的源码继续向下分析: 

 #ifndef CONFIG_SKIP_LOWLEVEL_INIT
bl cpu_init_cp15
bl cpu_init_crit
#endif

         ③cpu_init_cp15

    CP15是协处理器,uboot引导时不需要这部分功能,所以相关的寄存器都配置为不工作状态。

  

 /*************************************************************************
* Setup CP15 registers (cache, MMU, TLBs). The I-cache is turned on unless
* CONFIG_SYS_ICACHE_OFF is defined.
*************************************************************************/
ENTRY(cpu_init_cp15)
/*
* Invalidate L1 I/D
*/
mov r0, # @ set up for MCR
mcr p15, , r0, c8, c7, @ invalidate TLBs
mcr p15, , r0, c7, c5, @ invalidate icache
mcr p15, , r0, c7, c5, @ invalidate BP array
mcr p15, , r0, c7, c10, @ DSB
mcr p15, , r0, c7, c5, @ ISB
/*
* disable MMU stuff and caches
*/
mrc p15, , r0, c1, c0,
bic r0, r0, #0x00002000 @ clear bits (--V-)
bic r0, r0, #0x00000007 @ clear bits : (-CAM)
orr r0, r0, #0x00000002 @ set bit (--A-) Align
orr r0, r0, #0x00000800 @ set bit (Z---) BTB #ifdef CONFIG_SYS_ICACHE_OFF
bic r0, r0, #0x00001000 @ clear bit (I) I-cache
#else
orr r0, r0, #0x00001000 @ set bit (I) I-cache
#endif mcr p15, , r0, c1, c0,
mov pc, lr @ back to my caller ENDPROC(cpu_init_cp15)

  执行完这部分代码后返回跳转点继续向下执行,即cpu_init_crit

         ④cpu_init_crit

 ENTRY(cpu_init_crit)
/*
* Jump to board specific initialization...
* The Mask ROM will have already initialized
* basic memory. Go here to bump up clock rate and handle
* wake up conditions.
*/
b lowlevel_init @ go setup pll,mux,memory
ENDPROC(cpu_init_crit)

  接下来会跳转到lowlevel_init去执行,所做工作即注释所提及的,初始化PLL\MUX\MEM

         ⑤lowlevel_init

 ENTRY(lowlevel_init)
// Setup a temporary stack
ldr sp, =CONFIG_SYS_INIT_SP_ADDR
bic sp, sp, # /* -byte alignment for ABI compliance */ // Save the old lr(passed in ip) and the current lr to stack
push {ip, lr} // go setup pll, mux, memory
bl s_init
pop {ip, pc}
ENDPROC(lowlevel_init)

  而s_init是个C函数,所以在调用之前需要准备堆栈。在这个函数中做了一系列的初始化操作,其实就是禁用看门狗等,可以看到很多板子的初始化操作会直接忽略这一部分。

 void s_init(void)
{

/* Watchdog init */
writew(0xA500, &rwdt0->rwtcsra0);
writew(0xA500, &rwdt1->rwtcsra0);

/* CPG */
writel(0xFF800080, &cpg->rmstpcr4);
writel(0xFF800080, &cpg->smstpcr4);

}

  执行完这个函数后执行流程返回到lowlevel_init,再返回到调用cpu_init_crit的地方,流程往下执行到call_board_init_f

call_board_init_f:

     void board_init_f(ulong bootflag)
{
bd_t *bd;
init_fnc_t **init_fnc_ptr;
gd_t *id;
ulong addr, addr_sp; bootstage_mark_name(BOOTSTAGE_ID_START_UBOOT_F, "board_init_f"); gd = (gd_t *) ((CONFIG_SYS_INIT_SP_ADDR) & ~0x07); memset((void *)gd, , sizeof(gd_t));
gd->mon_len = _bss_end_ofs;
..//gd全局结构体成员复制 for (init_fnc_ptr = init_sequence; *init_fnc_ptr; ++init_fnc_ptr) {
if ((*init_fnc_ptr)() != ) {
hang ();
}
}
... ..//gd全局结构体成员复制
gd->bd->bi_baudrate = gd->baudrate;
/* Ram ist board specific, so move it to board code ... */
dram_init_banksize();
display_dram_config(); /* and display it */
...
gd->relocaddr = addr;
gd->start_addr_sp = addr_sp;
gd->reloc_off = addr - _TEXT_BASE;
debug("relocation Offset is: %08lx\n", gd->reloc_off);
memcpy(id, (void *)gd, sizeof(gd_t)); relocate_code(addr_sp, id, addr); //调用start.s中的汇编函数
}

  以上C函数完成了gt结构体部分成员赋值,这个结构体将会在之后的流程中用到;此函数最后调用汇编函数,执行流程再次跳转。

  ⑦、relocate_code

 ENTRY(relocate_code)
mov r4, r0 /* save addr_sp */
mov r5, r1 /* save addr of gd */
mov r6, r2 /* save addr of destination */ /* Set up the stack */
stack_setup:
… //堆栈处理
copy_loop:
… //循环拷贝uboot到RAM
//这个过程只能用到ARM的通用寄存器,所以每次只能拷贝几字节,循环
clear_bss:
…//bss段,由于是未初始化数据,没什么用,无需拷贝,直接留出空间,并初始化为0即可
jump_2_ram: //调到第二阶段,即调到RAM中去执行

ldr r0, _board_init_r_ofs
adr r1, _start
add lr, r0, r1
add lr, lr, r9
/* 上面下面都是调用C函数的准备工作 */
mov r0, r5 /* gd_t */
mov r1, r6 /* dest_addr */
/* jump to it ... */
mov pc, lr
ENDPROC(relocate_code)

  经过以上诸多过程,uboot已经把自己从flash中拷贝到RAM中,并且为之后的执行准备好了各种参数,最终跳转到第二部分的入口call_board_init_r

         ⑧call_board_init_r:

 {
gd = id; gd->flags |= GD_FLG_RELOC; /* tell others: relocation done */
bootstage_mark_name(BOOTSTAGE_ID_START_UBOOT_R, "board_init_r");
board_init(); /* Setup chipselects */
mem_malloc_init (malloc_start, TOTAL_MALLOC_LEN); env_relocate(); //环境变量初始化
stdio_init(); //标准IO初始化
jumptable_init();
console_init_r(); //终端初始化
load_addr = getenv_ulong("loadaddr", , load_addr); for (;;)
    {
    //一切准备就绪后进入死循环,此循环用于判断是否有用户输入,并且随之做出相应
main_loop();
  }
}

  此函数执行的操作是后一阶段的各种初始化,为引导linux kernel做好准备,接下来分析一下main_loop()的大致过程。

 

三、uboot流程第二阶段:

       Main_loop经过简化后如下所示,看见,这是个很清晰很简单的流程,检测是否有按键按下,没有则倒计时,有则开始处理命令相关的内容。

 void main_loop (void)
{
s = getenv ("bootcmd"); //获取引导命令   //检测是否有按键按下,有则执行下面的死循环
if (bootdelay != - && s && !abortboot(bootdelay))
  {
run_command_list(s, -, );
}
for (;;)
  {
len = readline (CONFIG_SYS_PROMPT);
flag = ; /* assume no special flags for now */
if (len > )
strcpy (lastcommand, console_buffer);
else if (len == )
flag |= CMD_FLAG_REPEAT;     //输入命令的的合法性验证
if (len == -)
puts ("<INTERRUPT>\n");
else
rc = run_command(lastcommand, flag); //执行命令 if (rc <= ) {
/* invalid command or not repeatable, forget it */
lastcommand[] = ;
}
}
  }

  ②、流程转到run_command,经简化可得:这部分是对函数的进一步封装,这里其实是有两个流程的,一个是有关哈希查找命令的,另一个就是下面这个,简单粗暴的。

  ↓

 int run_command(const char *cmd, int flag)
{
if (builtin_run_command(cmd, flag) == -)
return ;
return ;
}

  ③、流程转到r builtin_run_command,经简化可得:这里所做的各种为完整解析命令,并调用函数去进一步执行。

  ↓

 static int builtin_run_command(const char *cmd, int flag)
{
//合法性校验
while (*str) {
//特殊字符解析
}
process_macros (token, finaltoken); //宏展开,即完全解析命令 //命令执行过程
if (cmd_process(flag, argc, argv, &repeatable))
rc = -;
return rc ? rc : repeatable;
}

  ④、流程转到cmd_process,经简化可得:得到完整的命令和参数,执行命令。

  ↓

 cmd_process(int flag, int argc, char * const argv[],
int *repeatable)
{
cmd_tbl_t *cmdtp; cmdtp = find_cmd(argv[]); //查找命令
if (cmdtp == NULL) {
printf("Unknown command '%s' - try 'help'\n", argv[]);
return ;
} if (argc > cmdtp->maxargs)
rc = CMD_RET_USAGE; /* If OK so far, then do the command */
if (!rc) {
rc = cmd_call(cmdtp, flag, argc, argv); //真正的执行命令
*repeatable &= cmdtp->repeatable;
}
return rc;
}

至此,uboot的使命便完成了,将舞台交给linux

嵌入式Linux开发之uboot启动Linux整体流程分析的更多相关文章

  1. Qemu搭建ARM vexpress开发环境(二)----通过u-boot启动Linux内核

    Qemu搭建ARM vexpress开发环境(二)----通过u-boot启动Linux内核 标签(空格分隔): Qemu ARM Linux 在上文<Qemu搭建ARM vexpress开发环 ...

  2. u-boot中nandflash初始化流程分析(转)

    u-boot中nandflash初始化流程分析(转) 原文地址http://zhuairlunjj.blog.163.com/blog/static/80050945201092011249136/ ...

  3. 嵌入式linux开发uboot启动过程源码分析(一)

    一.uboot启动流程简介 与大多数BootLoader一样,uboot的启动过程分为BL1和BL2两个阶段.BL1阶段通常是开发板的配置等设备初始化代码,需要依赖依赖于SoC体系结构,通常用汇编语言 ...

  4. uboot启动linux的过程

    一.概述 linux内核镜像常见到的有两种形式,zImage和uImage.这两种文件的格式稍有差别,所以启动这两种格式的内核镜像也会有所不同.目前,uboot只支持启动uImage类型的镜像,对zI ...

  5. Linux学习 :Uboot, Kernel, 根文件系统初步分析

    1.U-Boot启动内核的过程可以分为两个阶段: 1)第一阶段的功能 硬件设备初始化 加载U-Boot第二阶段代码到RAM空间 设置好栈 跳转到第二阶段代码入口 2)第二阶段的功能 初始化本阶段使用的 ...

  6. 基于devkit8600的2011.04版uboot启动代码Start.s分析

    /* * armboot - Startup Code for OMAP3530/ARM Cortex CPU-core * * Copyright (c) 2004 Texas Instrument ...

  7. Fpm启动机制及流程分析———详细

    FPM(FastCGI Process Manager)是PHP FastCGI运行模式的一个进程管理器,从它的定义可以看出,FPM的核心功能是进程管理,那么它用来管理什么进程呢?这个问题就需要从Fa ...

  8. spark 启动job的流程分析

    从WordCount開始分析 编写一个样例程序 编写一个从HDFS中读取并计算wordcount的样例程序: packageorg.apache.spark.examples importorg.ap ...

  9. 嵌入式驱动开发之uboot---uboot 中的常见命令参数参数

    Uboot相关命令介绍 bootm bootp cmp cp crc32 echo erase flinfo go minfo loadb loads mw 14mw 用指定的数据填充内存 15md ...

随机推荐

  1. Linq基础+Lambda表达式对数据库的增删改及简单查询

    一.Linq to sql 类 高集成化的数据库访问技术 使用Linq可以代替之前的Ado.Net,省去了自己敲代码的实体类和数据访问类的大量工作 实体类: 添加一个Linq to sql 类 --- ...

  2. Tableau 学习资料

    官方文档: https://www.tableau.com/zh-cn/support/help 其他教程: tablaue破解版_tableau10 破解_tableau server 破解:htt ...

  3. vb配置下位机CAN寄存器小结

    2011-12-14 18:44:32 效果图 1,完成设计(由于没有eeprom等存储设备,所以每次上电后需要通过串口配置某些寄存器).在设计中,列出技术评估难度,并进行尝试,参看<我的设计& ...

  4. 关于富文本编辑器—UEditor(java版)的使用,以及如何将UEditor的文件/图片上传路径改成绝对路径

    突然发现好久没写博客了,感觉变懒了,是要让自己养成经常写文章的习惯才行.既可以分享自己的所学,和所想,和大家一起讨论,发现自己的不足的问题. 大家可能经常会用到富文本编辑器,今天我要说的是UEdito ...

  5. 【模板】快速幂&取余运算

    输入\(b\),\(p\),\(k\)的值,求\(b^p mod k\)的值.其中\(b\),\(p\),\(k^2\)为长整型数. 1.普通做法 \(print\) \(pow(b,p)\)\(mo ...

  6. DHCP的IP地址租约、释放

    转自:https://blog.csdn.net/wangdk789/article/details/27052505 当DHCP客户端获取到一个IP地址后,并不代表可以永久使用这个地址,而是有一个使 ...

  7. centos7 openssl 生成证书给自己使用

    Step1: centos7 系统自己生成证书 给自己签发不安全的域名证书 openssl genrsa - #生成ca根秘钥 是长度 openssl req - -key ca.key -out c ...

  8. String,StringBuilder,tringBuffer

    这三个类之间的区别主要是在两个方面,即运行速度和线程安全这两方面. 运行速度,或者说是执行速度,在这方面运行速度快慢为:StringBuilder > StringBuffer > Str ...

  9. 智能化脚本autoit v3的简单了解

    AutoIt v3 是一个类似 BASIC 脚本语言的免费软件, 它设计用于 Windows GUI(图形用户界面) 中进行自动化操作. 利用模拟键盘按键, 鼠标移动和窗口/控件的操作实现自动化任务. ...

  10. node一些相关

    1.Node node的核心语言是JavaScript ,基于Google的V8引擎. 2.node使用 找到当前文件所在目录 node  文件名.js 直接用绝对路径 在当前目录打开命令窗口 3.n ...