嵌入式Linux开发之uboot启动Linux整体流程分析

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、执行流程分析:

           ①、中断向量表

复制代码
 1 .globl _start                        //定义一个全局标号_star
 2 _start: b    reset                //标号_star处的内容为跳转到reset标号开始执行
 3 //将_undefined_instruction标号表示的内容作为地址,加载到PC中,
 4 //这种用法可以实现执行流程的跳转
 5     ldr    pc, _undefined_instruction    
 6     ldr    pc, _software_interrupt
 7     ldr    pc, _prefetch_abort
 8     ldr    pc, _data_abort
 9     ldr    pc, _not_used
10     ldr    pc, _irq
11     ldr    pc, _fiq
12     //以上七条ldr pc,x加上b reset共八条指令组成中断向量表
13       …
14 //_undefined_instruction标号表示定义了一个word类型的变量undefined_instruction
15 _undefined_instruction: .word undefined_instruction 
16 …
17 //exception handlers  //异常处理
18 .align    5            //5字节对齐
19 
20 //可知undefined_instruction的真正用途是指向此处代码,即异常处理的具体实现
21 undefined_instruction: 
22 get_bad_stack
23 bad_save_user_regs
24 bl    do_undefined_instruction
复制代码

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

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

  ②、reset

复制代码
1 reset:
2     /*
3      * set the cpu to SVC32 mode 设置CPU为SVC32模式
4      */
5     mrs    r0, cpsr             //读出
6     bic    r0, r0, #0x1f        //低五位清0
7     orr    r0, r0, #0xd3        //与D3做与操作
8     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模式来说,可以访问资源的能力相同,但是拥有更多的硬件资源。

 

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

1 #ifndef CONFIG_SKIP_LOWLEVEL_INIT
2          bl      cpu_init_cp15
3          bl      cpu_init_crit
4 #endif

         ③cpu_init_cp15

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

  

复制代码
 1 /*************************************************************************
 2 * Setup CP15 registers (cache, MMU, TLBs). The I-cache is turned on unless
 3 * CONFIG_SYS_ICACHE_OFF is defined.
 4 *************************************************************************/
 5 ENTRY(cpu_init_cp15)
 6          /*
 7           * Invalidate L1 I/D
 8           */
 9          mov r0, #0                          @ set up for MCR
10          mcr  p15, 0, r0, c8, c7, 0 @ invalidate TLBs
11          mcr  p15, 0, r0, c7, c5, 0 @ invalidate icache
12          mcr  p15, 0, r0, c7, c5, 6 @ invalidate BP array
13          mcr     p15, 0, r0, c7, c10, 4        @ DSB
14          mcr     p15, 0, r0, c7, c5, 4 @ ISB
15          /*
16           * disable MMU stuff and caches
17           */
18          mrc  p15, 0, r0, c1, c0, 0
19          bic    r0, r0, #0x00002000         @ clear bits 13 (--V-)
20          bic    r0, r0, #0x00000007         @ clear bits 2:0 (-CAM)
21          orr    r0, r0, #0x00000002         @ set bit 1 (--A-) Align
22          orr    r0, r0, #0x00000800         @ set bit 11 (Z---) BTB
23 
24 #ifdef CONFIG_SYS_ICACHE_OFF
25          bic    r0, r0, #0x00001000         @ clear bit 12 (I) I-cache
26 #else
27          orr    r0, r0, #0x00001000         @ set bit 12 (I) I-cache
28 #endif
29 
30          mcr  p15, 0, r0, c1, c0, 0
31          mov pc, lr                            @ back to my caller
32 
33 ENDPROC(cpu_init_cp15)
复制代码

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

         ④cpu_init_crit

复制代码
1 ENTRY(cpu_init_crit)
2     /*
3      * Jump to board specific initialization...
4      * The Mask ROM will have already initialized
5      * basic memory. Go here to bump up clock rate and handle
6      * wake up conditions.
7      */
8     b    lowlevel_init        @ go setup pll,mux,memory
9 ENDPROC(cpu_init_crit)
复制代码

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

         ⑤lowlevel_init

复制代码
 1 ENTRY(lowlevel_init)
 2     // Setup a temporary stack
 3     ldr    sp, =CONFIG_SYS_INIT_SP_ADDR
 4     bic    sp, sp, #7 /* 8-byte alignment for ABI compliance */
 5 
 6     // Save the old lr(passed in ip) and the current lr to stack
 7     push    {ip, lr}
 8 
 9     // go setup pll, mux, memory
10     bl    s_init
11     pop    {ip, pc}
12 ENDPROC(lowlevel_init)
复制代码

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

复制代码
 1 void s_init(void)
 2 {
 3     …
 4     /* Watchdog init */
 5     writew(0xA500, &rwdt0->rwtcsra0);
 6     writew(0xA500, &rwdt1->rwtcsra0);
 7     …
 8     /* CPG */
 9     writel(0xFF800080, &cpg->rmstpcr4);
10     writel(0xFF800080, &cpg->smstpcr4);
11     …
12 }
复制代码

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

         call_board_init_f:

复制代码
 1     void board_init_f(ulong bootflag)
 2 {
 3     bd_t *bd;
 4     init_fnc_t **init_fnc_ptr;
 5     gd_t *id;
 6     ulong addr, addr_sp;
 7 
 8     bootstage_mark_name(BOOTSTAGE_ID_START_UBOOT_F, "board_init_f");
 9 
10     gd = (gd_t *) ((CONFIG_SYS_INIT_SP_ADDR) & ~0x07);
11 
12     memset((void *)gd, 0, sizeof(gd_t));
13     gd->mon_len = _bss_end_ofs;
14     ..//gd全局结构体成员复制
15 
16     for (init_fnc_ptr = init_sequence; *init_fnc_ptr; ++init_fnc_ptr) {
17         if ((*init_fnc_ptr)() != 0) {
18             hang ();
19         }
20     }
21     ... ..//gd全局结构体成员复制
22     gd->bd->bi_baudrate = gd->baudrate;
23     /* Ram ist board specific, so move it to board code ... */
24     dram_init_banksize();
25     display_dram_config();    /* and display it */
26     ...
27     gd->relocaddr = addr;
28     gd->start_addr_sp = addr_sp;
29     gd->reloc_off = addr - _TEXT_BASE;
30     debug("relocation Offset is: %08lx\n", gd->reloc_off);
31     memcpy(id, (void *)gd, sizeof(gd_t));
32 
33     relocate_code(addr_sp, id, addr); //调用start.s中的汇编函数
34 }
复制代码

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

  ⑦、relocate_code

复制代码
 1 ENTRY(relocate_code)
 2     mov    r4, r0    /* save addr_sp */
 3     mov    r5, r1    /* save addr of gd */
 4     mov    r6, r2    /* save addr of destination */
 5 
 6 /* Set up the stack                            */
 7 stack_setup:
 8 … //堆栈处理
 9 copy_loop:
10  … //循环拷贝uboot到RAM
11 //这个过程只能用到ARM的通用寄存器,所以每次只能拷贝几字节,循环
12 clear_bss:
13  …//bss段,由于是未初始化数据,没什么用,无需拷贝,直接留出空间,并初始化为0即可
14 jump_2_ram: //调到第二阶段,即调到RAM中去执行
15 …
16     ldr    r0, _board_init_r_ofs
17     adr    r1, _start
18     add    lr, r0, r1
19     add    lr, lr, r9
20     /* 上面下面都是调用C函数的准备工作 */
21     mov    r0, r5        /* gd_t */
22     mov    r1, r6        /* dest_addr */
23 /* jump to it ... */
24 mov    pc, lr
25 ENDPROC(relocate_code)
复制代码

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

         ⑧call_board_init_r:

复制代码
 1 {
 2     gd = id;
 3 
 4     gd->flags |= GD_FLG_RELOC;    /* tell others: relocation done */
 5     bootstage_mark_name(BOOTSTAGE_ID_START_UBOOT_R, "board_init_r");
 6     board_init();    /* Setup chipselects */
 7     mem_malloc_init (malloc_start, TOTAL_MALLOC_LEN);
 8 
 9     env_relocate(); //环境变量初始化
10     stdio_init();     //标准IO初始化
11     jumptable_init();
12     console_init_r(); //终端初始化
13     load_addr = getenv_ulong("loadaddr", 16, load_addr);
14 
15     for (;;) 
    { 16     //一切准备就绪后进入死循环,此循环用于判断是否有用户输入,并且随之做出相应 17 main_loop(); 18   } 19 }
复制代码

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

 

三、uboot流程第二阶段:

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

复制代码
 1 void main_loop (void)
 2 {
 3     s = getenv ("bootcmd"); //获取引导命令
 4 
 5   //检测是否有按键按下,有则执行下面的死循环
 6     if (bootdelay != -1 && s && !abortboot(bootdelay))    
 7   {
 8         run_command_list(s, -1, 0);
 9     }
10     for (;;) 
11   {
12         len = readline (CONFIG_SYS_PROMPT);
13         flag = 0;    /* assume no special flags for now */
14         if (len > 0)
15             strcpy (lastcommand, console_buffer);
16         else if (len == 0)
17             flag |= CMD_FLAG_REPEAT;
18         
19     //输入命令的的合法性验证
20         if (len == -1)
21             puts ("<INTERRUPT>\n");
22         else
23             rc = run_command(lastcommand, flag); //执行命令
24         
25         if (rc <= 0) {
26             /* invalid command or not repeatable, forget it */
27             lastcommand[0] = 0;
28         }
29     }
  }
复制代码

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

  ↓

复制代码
1 int run_command(const char *cmd, int flag)
2 {
3     if (builtin_run_command(cmd, flag) == -1)
4         return 1;
5     return 0;
6 }
复制代码

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

  ↓

复制代码
 1 static int builtin_run_command(const char *cmd, int flag)
 2 {
 3     //合法性校验
 4     while (*str) {
 5         //特殊字符解析
 6         }
 7     process_macros (token, finaltoken); //宏展开,即完全解析命令
 8 
 9     //命令执行过程
10     if (cmd_process(flag, argc, argv, &repeatable))
11         rc = -1;
12     return rc ? rc : repeatable;
13 }
复制代码

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

  ↓

复制代码
 1 cmd_process(int flag, int argc, char * const argv[],
 2                    int *repeatable)
 3 {
 4     cmd_tbl_t *cmdtp;
 5 
 6     cmdtp = find_cmd(argv[0]); //查找命令
 7     if (cmdtp == NULL) {
 8         printf("Unknown command '%s' - try 'help'\n", argv[0]);
 9         return 1;
10     }
11 
12     if (argc > cmdtp->maxargs)
13         rc = CMD_RET_USAGE;
14 
15     /* If OK so far, then do the command */
16     if (!rc) {
17         rc = cmd_call(cmdtp, flag, argc, argv); //真正的执行命令
18         *repeatable &= cmdtp->repeatable;
19     }
20     return rc;
21 }
复制代码

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

posted @ 2020-04-14 11:08  瘋耔  阅读(1283)  评论(0编辑  收藏  举报
跳至侧栏