嵌入式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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM