辛苦之作,轉載請註明出處,謝謝!
最近開始接觸uboot,現在需要將2014.4版本uboot移植到公司armv7開發板。
在網上搜索講uboot啓動過程的文章,大多都是比較老版本的uboot,於是決定將新版uboot啓動過程記錄下來,和大家共享。
- #
- # (C) Copyright 2000-2013
- # Wolfgang Denk, DENX Software Engineering, [email protected].
- #
- # SPDX-License-Identifier: GPL-2.0+
- #
- VERSION = 2014
- PATCHLEVEL = 04
- SUBLEVEL =
- EXTRAVERSION =
- NAME =
2014.4版本uboot啓動至命令行幾個重要函數爲:_start,_main,board_init_f,relocate_code,board_init_r。
一 _start
對於任何程序,入口函數是在鏈接時決定的,uboot的入口是由鏈接腳本決定的。uboot下armv7鏈接腳本默認目錄爲arch/arm/cpu/u-boot.lds。這個可以在配置文件中與CONFIG_SYS_LDSCRIPT來指定。
入口地址也是由連接器決定的,在配置文件中可以由CONFIG_SYS_TEXT_BASE指定。這個會在編譯時加在ld連接器的選項-Ttext中
uboot的配置編譯原理也非常值得學習,我想在另外寫一篇文章來記錄,這裏不詳細說了。
查看u-boot.lds
- <span style="font-size:14px;">OUTPUT_FORMAT("elf32-littlearm", "elf32-littlearm", "elf32-littlearm")
- OUTPUT_ARCH(arm)
- ENTRY(_start)
- SECTIONS
- {
- . = 0x00000000;
- . = ALIGN(4);
- .text :
- {
- *(.__image_copy_start)
- CPUDIR/start.o (.text*)
- *(.text*)
- }
- . = ALIGN(4);
- .rodata : { *(SORT_BY_ALIGNMENT(SORT_BY_NAME(.rodata*))) }
- . = ALIGN(4);
- .data : {
- *(.data*)
- }
- </span>
_start在arch/arm/cpu/armv7/start.S中,一段一段的分析,如下:
- <span style="font-size:14px;">.globl _start
- _start: 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
- #ifdef CONFIG_SPL_BUILD
- _undefined_instruction: .word _undefined_instruction
- _software_interrupt: .word _software_interrupt
- _prefetch_abort: .word _prefetch_abort
- _data_abort: .word _data_abort
- _not_used: .word _not_used
- _irq: .word _irq
- _fiq: .word _fiq
- _pad: .word 0x12345678 /* now 16*4=64 */
- #else
- .globl _undefined_instruction
- _undefined_instruction: .word undefined_instruction
- .globl _software_interrupt
- _software_interrupt: .word software_interrupt
- .globl _prefetch_abort
- _prefetch_abort: .word prefetch_abort
- .globl _data_abort
- _data_abort: .word data_abort
- .globl _not_used
- _not_used: .word not_used
- .globl _irq
- _irq: .word irq
- .globl _fiq
- _fiq: .word fiq
- _pad: .word 0x12345678 /* now 16*4=64 */</span>
- <span style="font-size:14px;">#endif /* CONFIG_SPL_BUILD */
- .global _end_vect
- _end_vect:
- .balignl 16,0xdeadbeef
- </span>
以上代碼是設置arm的異常向量表,arm異常向量表如下:
地址 |
異常 |
進入模式 |
描述 |
0x00000000 |
復位 |
管理模式 |
復位電平有效時,產生復位異常,程序跳轉到復位處理程序處執行 |
0x00000004 |
未定義指令 |
未定義模式 |
遇到不能處理的指令時,產生未定義指令異常 |
0x00000008 |
軟件中斷 |
管理模式 |
執行SWI指令產生,用於用戶模式下的程序調用特權操作指令 |
0x0000000c |
預存指令 |
中止模式 |
處理器預取指令的地址不存在,或該地址不允許當前指令訪問,產生指令預取中止異常 |
0x00000010 |
數據操作 |
中止模式 |
處理器數據訪問指令的地址不存在,或該地址不允許當前指令訪問時,產生數據中止異常 |
0x00000014 |
未使用 |
未使用 |
未使用 |
0x00000018 |
IRQ |
IRQ |
外部中斷請求有效,且CPSR中的I位爲0時,產生IRQ異常 |
0x0000001c |
FIQ |
FIQ |
快速中斷請求引腳有效,且CPSR中的F位爲0時,產生FIQ異常 |
後面彙編是定義了7種異常的入口函數,這裏沒有定義CONFIG_SPL_BUILD,所以走後面一個。
接下來定義的_end_vect中用.balignl來指定接下來的代碼要16字節對齊,空缺的用0xdeadbeef,方便更加高效的訪問內存。接着分析下面一段代碼
- <span style="font-size:14px;">#ifdef CONFIG_USE_IRQ
- /* IRQ stack memory (calculated at run-time) */
- .globl IRQ_STACK_START
- IRQ_STACK_START:
- .word 0x0badc0de
- /* IRQ stack memory (calculated at run-time) */
- .globl FIQ_STACK_START
- FIQ_STACK_START:
- .word 0x0badc0de
- #endif
- /* IRQ stack memory (calculated at run-time) + 8 bytes */
- .globl IRQ_STACK_START_IN
- IRQ_STACK_START_IN:
- .word 0x0badc0de
- </span>
如果uboot中使用中斷,這裏聲明中斷處理函數棧起始地址,這裏給出的值是0x0badc0de,是一個非法值,註釋也說明了,這個值會在運行時重新計算,我查找了一下代碼是在interrupt_init中。
- <span style="font-size:14px;">reset:
- bl save_boot_params
- /*
- * disable interrupts (FIQ and IRQ), also set the cpu to SVC32 mode,
- * except if in HYP mode already
- */
- mrs r0, cpsr
- and r1, r0, #0x1f @ mask mode bits
- teq r1, #0x1a @ test for HYP mode
- bicne r0, r0, #0x1f @ clear all mode bits
- orrne r0, r0, #0x13 @ set SVC mode
- orr r0, r0, #0xc0 @ disable FIQ and IRQ
- msr cpsr,r0
- </span>
- <span style="font-size:14px;">/*************************************************************************
- *
- * void save_boot_params(u32 r0, u32 r1, u32 r2, u32 r3)
- * __attribute__((weak));
- *
- * Stack pointer is not yet initialized at this moment
- * Don't save anything to stack even if compiled with -O0
- *
- *************************************************************************/
- ENTRY(save_boot_params)
- bx lr @ back to my caller
- ENDPROC(save_boot_params)
- .weak save_boot_params
- </span>
這裏值得注意的是.weak關鍵字,在網上找了到的解釋,我的理解是.weak相當於聲明一個函數,如果該函數在其他地方沒有定義,則爲空函數,有定義則調用該定義的函數。
具體解釋可以看這位大神的詳解:
http://blog.csdn.net/norains/article/details/5954459
接下來reset執行7條指令,修改cpsr寄存器,設置處理器進入svc模式,並且關掉irq和fiq。
- <span style="font-size:14px;">/*
- * Setup vector:
- * (OMAP4 spl TEXT_BASE is not 32 byte aligned.
- * Continue to use ROM code vector only in OMAP4 spl)
- */
- #if !(defined(CONFIG_OMAP44XX) && defined(CONFIG_SPL_BUILD))
- /* Set V=0 in CP15 SCTRL register - for VBAR to point to vector */
- mrc p15, 0, r0, c1, c0, 0 @ Read CP15 SCTRL Register
- bic r0, #CR_V @ V = 0
- mcr p15, 0, r0, c1, c0, 0 @ Write CP15 SCTRL Register
- /* Set vector address in CP15 VBAR register */
- ldr r0, =_start
- mcr p15, 0, r0, c12, c0, 0 @Set VBAR
- #endif
- /* the mask ROM code should have PLL and others stable */
- #ifndef CONFIG_SKIP_LOWLEVEL_INIT
- bl cpu_init_cp15
- bl cpu_init_crit
- #endif
- bl _main
- </span>
前面6條彙編指令是對協處理器cp15進行操作,設置了處理器的異常向量入口地址爲_start,但是我在網上沒有找到cp15協處理器的c12寄存器的說明,可能是armv7新添加的
協處理器cp15的說明可以看我轉的一篇文章:
http://blog.csdn.net/skyflying2012/article/details/25823967
接下來如果沒有定義宏CONFIG_SKIP_LOWLEVEL_INIT,則會分別跳轉執行cpu_init_cp15以及cpu_init_crit。
在分析這2個函數之前先總結一下上面分析的這一段_start中彙編的作用:
1 初始化異常向量表 2 設置cpu svc模式,關中斷 3 配置cp15,設置異常向量入口
都是跟異常有關的部分。
接下來先分析cpu_init_cp15
- <span style="font-size:14px;">/*************************************************************************
- *
- * cpu_init_cp15
- *
- * 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, #0 @ set up for MCR
- mcr p15, 0, r0, c8, c7, 0 @ invalidate TLBs
- mcr p15, 0, r0, c7, c5, 0 @ invalidate icache
- mcr p15, 0, r0, c7, c5, 6 @ invalidate BP array
- mcr p15, 0, r0, c7, c10, 4 @ DSB
- mcr p15, 0, r0, c7, c5, 4 @ ISB
- /*
- * disable MMU stuff and caches
- */
- mrc p15, 0, r0, c1, c0, 0
- bic r0, r0, #0x00002000 @ clear bits 13 (--V-)
- bic r0, r0, #0x00000007 @ clear bits 2:0 (-CAM)
- orr r0, r0, #0x00000002 @ set bit 1 (--A-) Align
- orr r0, r0, #0x00000800 @ set bit 11 (Z---) BTB
- #ifdef CONFIG_SYS_ICACHE_OFF
- bic r0, r0, #0x00001000 @ clear bit 12 (I) I-cache
- #else
- orr r0, r0, #0x00001000 @ set bit 12 (I) I-cache
- #endif
- mcr p15, 0, r0, c1, c0, 0
- </span>
- <span style="font-size:14px;">#ifdef CONFIG_ARM_ERRATA_716044
- mrc p15, 0, r0, c1, c0, 0 @ read system control register
- orr r0, r0, #1 << 11 @ set bit #11
- mcr p15, 0, r0, c1, c0, 0 @ write system control register
- #endif
- #if (defined(CONFIG_ARM_ERRATA_742230) || defined(CONFIG_ARM_ERRATA_794072))
- mrc p15, 0, r0, c15, c0, 1 @ read diagnostic register
- orr r0, r0, #1 << 4 @ set bit #4
- mcr p15, 0, r0, c15, c0, 1 @ write diagnostic register
- #endif
- #ifdef CONFIG_ARM_ERRATA_743622
- mrc p15, 0, r0, c15, c0, 1 @ read diagnostic register
- orr r0, r0, #1 << 6 @ set bit #6
- mcr p15, 0, r0, c15, c0, 1 @ write diagnostic register
- #endif
- #ifdef CONFIG_ARM_ERRATA_751472
- mrc p15, 0, r0, c15, c0, 1 @ read diagnostic register
- orr r0, r0, #1 << 11 @ set bit #11
- mcr p15, 0, r0, c15, c0, 1 @ write diagnostic register
- #endif
- #ifdef CONFIG_ARM_ERRATA_761320
- mrc p15, 0, r0, c15, c0, 1 @ read diagnostic register
- orr r0, r0, #1 << 21 @ set bit #21
- mcr p15, 0, r0, c15, c0, 1 @ write diagnostic register
- #endif
- mov pc, lr @ back to my caller
- ENDPROC(cpu_init_cp15)
- </span>
具體配置過程可以對照cp15寄存器來看,這裏不詳細說了
接下來看cpu_init_crit
- <span style="font-size:14px;">/*************************************************************************
- *
- * CPU_init_critical registers
- *
- * setup important registers
- * setup memory timing
- *
- *************************************************************************/
- 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)
- </span>
看註釋可以明白,cpu_init_crit調用的lowlevel_init函數是與特定開發板相關的初始化函數,在這個函數裏會做一些pll初始化,如果不是從mem啓動,則會做memory初始化,方便後續拷貝到mem中運行。
lowlevel_init函數則是需要移植來實現,做clk初始化以及ddr初始化
從cpu_init_crit返回後,_start的工作就完成了,接下來就要調用_main,總結一下_start工作:
1 前面總結過的部分,初始化異常向量表,設置svc模式,關中斷
2 配置cp15,初始化mmu cache tlb
3 板級初始化,pll memory初始化
二 _main
_main函數在arch/arm/lib/crt0.S中,mian函數的作用在註釋中有詳細的說明,我們分段來分析一下
- ENTRY(_main)
- /*
- * Set up initial C runtime environment and call board_init_f(0).
- */
- #if defined(CONFIG_SPL_BUILD) && defined(CONFIG_SPL_STACK)
- ldr sp, =(CONFIG_SPL_STACK)
- #else
- ldr sp, =(CONFIG_SYS_INIT_SP_ADDR)
- #endif
- bic sp, sp, #7 /* 8-byte alignment for ABI compliance */
- sub sp, sp, #GD_SIZE /* allocate one GD above SP */
- bic sp, sp, #7 /* 8-byte alignment for ABI compliance */
- mov r9, sp /* GD is above SP */
- mov r0, #0
- bl board_init_f
8字節對齊,然後減掉GD_SIZE,這個宏定義是指的全局結構體gd的大小,是160字節在此處,這個結構體用來保存uboot一些全局信息,需要一塊單獨的內存。
最後將sp保存在r9寄存器中。因此r9寄存器中的地址就是gd結構體的首地址。
在後面所有code中如果要使用gd結構體,必須在文件中加入DECLARE_GLOBAL_DATA_PTR宏定義,定義如下:
- #define DECLARE_GLOBAL_DATA_PTR register volatile gd_t *gd asm ("r9")
接着說_main上面一段代碼,接着r0賦爲0,也就是參數0爲0,調用board_init_f
三 board_init_f
移植uboot先做一個最精簡版本,很多配置選項都沒有打開,比如fb mmc等硬件都默認不打開,只配置基本的ddr serial,這樣先保證uboot能正常啓動進入命令行,然後再去添加其他。
我們這裏分析就是按最精簡版本來,這樣可以更加簡潔的說明uboot的啓動流程。
board_init_f函數主要是根據配置對全局信息結構體gd進行初始化。
gd結構體中有個別成員意義我也不是很理解,這裏我只說我理解並且在後面起到作用的成員。
- gd->mon_len = (ulong)&__bss_end - (ulong)_start;
- for (init_fnc_ptr = init_sequence; *init_fnc_ptr; ++init_fnc_ptr) {
- if ((*init_fnc_ptr)() != 0) {
- hang ();
- }
- }
- init_fnc_t *init_sequence[] = {
- arch_cpu_init, /* basic arch cpu dependent setup */
- mark_bootstage,
- #ifdef CONFIG_OF_CONTROL
- fdtdec_check_fdt,
- #endif
- #if defined(CONFIG_BOARD_EARLY_INIT_F)
- board_early_init_f,
- #endif
- timer_init, /* initialize timer */
- #ifdef CONFIG_BOARD_POSTCLK_INIT
- board_postclk_init,
- #endif
- #ifdef CONFIG_FSL_ESDHC
- get_clocks,
- #endif
- env_init, /* initialize environment */
- init_baudrate, /* initialze baudrate settings */
- serial_init, /* serial communications setup */
- console_init_f, /* stage 1 init of console */
- display_banner, /* say that we are here */
- print_cpuinfo, /* display cpu info (and speed) */
- #if defined(CONFIG_DISPLAY_BOARDINFO)
- checkboard, /* display board info */
- #endif
- #if defined(CONFIG_HARD_I2C) || defined(CONFIG_SYS_I2C)
- init_func_i2c,
- #endif
- dram_init, /* configure available RAM banks */
- NULL,
- };
timer_init在lib/time.c中有實現,也是空函數,但是有__WEAK關鍵字,如果自己實現,則會調用自己實現的這個函數
對最精簡uboot,需要做好就是ddr和serial,所以我們最關心是serial_init,console_init_f以及dram_init.
先看serial_init
- <span style="font-size:14px;">int serial_init(void)
- {
- return get_current()->start();
- }
- </span>
- <span style="font-size:14px;">static struct serial_device *get_current(void)
- {
- struct serial_device *dev;
- if (!(gd->flags & GD_FLG_RELOC))
- dev = default_serial_console();
- else if (!serial_current)
- dev = default_serial_console();
- else
- dev = serial_current;
- /* We must have a console device */
- if (!dev) {
- #ifdef CONFIG_SPL_BUILD
- puts("Cannot find console\n");
- hang();
- #else
- panic("Cannot find console\n");
- #endif
- }
- return dev;
- }
- </span>
serial_device結構體代表了一個串口設備,其中的成員都需要在自己的serial驅動中實現。
這樣在serial_init中get_current獲取就是串口驅動中給出的默認調試串口結構體,執行start,做一些特定串口初始化。
console_init_f將gd中have_console置1,這個函數不詳細說了。
display_banner,print_cpuinfo利用現在的調試串口打印了uboot的信息。
接下來就是dram_init。
dram_init對gd->ram_size初始化,以便board_init_f後面代碼對dram空間進行規劃。
dram_init實現可以通過配置文件定義宏定義來實現,也可以通過對ddrc控制器讀獲取dram信息。
繼續分析board_init_f,剩餘代碼將會對sdram空間進行規劃!
- <span style="font-size:14px;">#if defined(CONFIG_SYS_MEM_TOP_HIDE)
- /*
- * Subtract specified amount of memory to hide so that it won't
- * get "touched" at all by U-Boot. By fixing up gd->ram_size
- * the Linux kernel should now get passed the now "corrected"
- * memory size and won't touch it either. This should work
- * for arch/ppc and arch/powerpc. Only Linux board ports in
- * arch/powerpc with bootwrapper support, that recalculate the
- * memory size from the SDRAM controller setup will have to
- * get fixed.
- */
- gd->ram_size -= CONFIG_SYS_MEM_TOP_HIDE;
- #endif
- addr = CONFIG_SYS_SDRAM_BASE + get_effective_memsize();
- </span>
addr的值由CONFIG_SYS_SDRAM_BASE加上ram_size。也就是到了可用sdram的頂端。
- <span style="font-size:14px;">#if !(defined(CONFIG_SYS_ICACHE_OFF) && defined(CONFIG_SYS_DCACHE_OFF))
- /* reserve TLB table */
- gd->arch.tlb_size = PGTABLE_SIZE;
- addr -= gd->arch.tlb_size;
- /* round down to next 64 kB limit */
- addr &= ~(0x10000 - 1);
- gd->arch.tlb_addr = addr;
- debug("TLB table from %08lx to %08lx\n", addr, addr + gd->arch.tlb_size);
- #endif
- /* round down to next 4 kB limit */
- addr &= ~(4096 - 1);
- debug("Top of RAM usable for U-Boot at: %08lx\n", addr);
- </span>
最後addr此時值就是tlb的地址,4kB對齊。
- <span style="font-size:14px;">#ifdef CONFIG_LCD
- #ifdef CONFIG_FB_ADDR
- gd->fb_base = CONFIG_FB_ADDR;
- #else
- /* reserve memory for LCD display (always full pages) */
- addr = lcd_setmem(addr);
- gd->fb_base = addr;
- #endif /* CONFIG_FB_ADDR */
- #endif /* CONFIG_LCD */
- /*
- * reserve memory for U-Boot code, data & bss
- * round down to next 4 kB limit
- */
- addr -= gd->mon_len;
- addr &= ~(4096 - 1);
- debug("Reserving %ldk for U-Boot at: %08lx\n", gd->mon_len >> 10, addr);
- </span>
gd->fb_base保存fb首地址。
接着-gd->mon_len爲uboot的code留出空間,到這裏addr的值就確定,addr作爲uboot relocate的目標addr。
到這裏,可以看出uboot現在空間劃分是從頂端往下進行的。
先總結一下addr之上sdram空間的劃分:
由高到低 : top-->hide mem-->tlb space(16K)-->framebuffer space-->uboot code space-->addr
接下來要確定addr_sp的值。
- <span style="font-size:14px;">#ifndef CONFIG_SPL_BUILD
- /*
- * reserve memory for malloc() arena
- */
- addr_sp = addr - TOTAL_MALLOC_LEN;
- debug("Reserving %dk for malloc() at: %08lx\n",
- TOTAL_MALLOC_LEN >> 10, addr_sp);
- /*
- * (permanently) allocate a Board Info struct
- * and a permanent copy of the "global" data
- */
- addr_sp -= sizeof (bd_t);
- bd = (bd_t *) addr_sp;
- gd->bd = bd;
- debug("Reserving %zu Bytes for Board Info at: %08lx\n",
- sizeof (bd_t), addr_sp);
- </span>
- <span style="font-size:14px;">#ifdef CONFIG_MACH_TYPE
- gd->bd->bi_arch_number = CONFIG_MACH_TYPE; /* board id for Linux */
- #endif
- addr_sp -= sizeof (gd_t);
- id = (gd_t *) addr_sp;
- debug("Reserving %zu Bytes for Global Data at: %08lx\n",
- sizeof (gd_t), addr_sp);</span>
- <span style="font-size:14px;">#ifndef CONFIG_ARM64
- /* setup stackpointer for exeptions */
- gd->irq_sp = addr_sp;
- #ifdef CONFIG_USE_IRQ
- addr_sp -= (CONFIG_STACKSIZE_IRQ+CONFIG_STACKSIZE_FIQ);
- debug("Reserving %zu Bytes for IRQ stack at: %08lx\n",
- CONFIG_STACKSIZE_IRQ+CONFIG_STACKSIZE_FIQ, addr_sp);
- #endif
- /* leave 3 words for abort-stack */
- addr_sp -= 12;
- /* 8-byte alignment for ABI compliance */
- addr_sp &= ~0x07;
- #else /* CONFIG_ARM64 */
- /* 16-byte alignment for ABI compliance */
- addr_sp &= ~0x0f;
- #endif /* CONFIG_ARM64 */</span>
首先預留malloc len,這裏我定義的是0x400000.
註釋中說明,爲bd,gd做一個永久的copy。
留出了全局信息bd_t結構體的空間,首地址存在gd->bd。
留出gd_t結構體的空間。首地址存在id中。
將此地址保存在gd->irq_sp中作爲異常棧指針。uboot中我們沒有用到中斷。
最後留出12字節,for abort stack,這個沒看懂。
到這裏addr_sp值確定,總結一下addr_sp之上空間分配。
由高到低 : addr-->malloc len(0x400000)-->bd len-->gd len-->12 byte-->addr_sp(棧往下增長,addr_sp之下空間作爲棧空間)
- <span style="font-size:14px;"> 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 - (ulong)&_start;
- debug("relocation Offset is: %08lx\n", gd->reloc_off);
- if (new_fdt) {
- memcpy(new_fdt, gd->fdt_blob, fdt_size);
- gd->fdt_blob = new_fdt;
- }
- memcpy(id, (void *)gd, sizeof(gd_t));
- </span>
dram_init_banksize()是需要實現的板級函數。根據板上ddrc獲取ddr的bank信息。填充在gd->bd->bi_dram[CONFIG_NR_DRAM_BANKS]。
gd->relocaaddr爲目標addr,gd->start_addr_sp爲目標addr_sp,gd->reloc_off爲目標addr和現在實際code起始地址的偏移。reloc_off非常重要,會作爲後面relocate_code函數的參數,來實現code的拷貝。
最後將gd結構體的數據拷貝到新的地址id上。
至此,board_init_f結束,回到_main
四 _main
board_init_f結束後,代碼如下:
- <span style="font-size:14px;">#if ! defined(CONFIG_SPL_BUILD)
- /*
- * Set up intermediate environment (new sp and gd) and call
- * relocate_code(addr_moni). Trick here is that we'll return
- * 'here' but relocated.
- */
- ldr sp, [r9, #GD_START_ADDR_SP] /* sp = gd->start_addr_sp */
- bic sp, sp, #7 /* 8-byte alignment for ABI compliance */
- ldr r9, [r9, #GD_BD] /* r9 = gd->bd */
- sub r9, r9, #GD_SIZE /* new GD is below bd */
- adr lr, here
- ldr r0, [r9, #GD_RELOC_OFF] /* r0 = gd->reloc_off */
- add lr, lr, r0
- ldr r0, [r9, #GD_RELOCADDR] /* r0 = gd->relocaddr */
- b relocate_code
- here:
- </span>
首先更新sp,並且將sp 8字節對齊,方便後面函數開闢棧能對齊,
然後獲取gd->bd地址到r9中,需要注意,在board_init_f中gd->bd已經更新爲新分配的bd了,下一條彙編將r9減掉bd的size,這樣就獲取到了board_init_f中新分配的gd了!
後面彙編則是爲relocate_code做準備,首先加載here地址,然後加上新地址偏移量給lr,則是code relocate後的新here了,relocate_code返回條轉到lr,則是新位置的here!
最後在r0中保存code的新地址,跳轉到relocate_code
五 relocate_code
relocate_code函數在arch/arm/lib/relocate.S中,這個函數實現了將uboot code拷貝到relocaddr。具體實現有空再詳細說明。
到這裏需要總結一下,經過上面的分析可以看出,
新版uboot在sdram空間分配上,是自頂向下,
不管uboot是從哪裏啓動,spiflash,nandflash,sram等跑到這裏code都會被從新定位到sdram上部的一個位置,繼續運行。
我找了一個2010.6版本的uboot大體看了一下啓動代碼,是通過判斷_start和TEXT_BASE(鏈接地址)是否相等來確定是否需要relocate。如果uboot是從sdram啓動則不需要relocate。
新版uboot在這方面還是有較大變動。
這樣變動我考慮好處可能有二,一是不用考慮啓動方式,all relocate code。二是不用考慮uboot鏈接地址,因爲都要重新relocate。
uboot sdram空間規劃圖:
六 _main
從relocate_code回到_main中,接下來是main最後一段代碼,如下:
- <span style="font-size:14px;">/* Set up final (full) environment */
- bl c_runtime_cpu_setup /* we still call old routine here */
- ldr r0, =__bss_start /* this is auto-relocated! */
- ldr r1, =__bss_end /* this is auto-relocated! */
- mov r2, #0x00000000 /* prepare zero to clear BSS */
- clbss_l:cmp r0, r1 /* while not at end of BSS */
- strlo r2, [r0] /* clear 32-bit BSS word */
- addlo r0, r0, #4 /* move to next */
- blo clbss_l
- bl coloured_LED_init
- bl red_led_on
- /* call board_init_r(gd_t *id, ulong dest_addr) */
- mov r0, r9 /* gd_t */
- ldr r1, [r9, #GD_RELOCADDR] /* dest_addr */
- /* call board_init_r */
- ldr pc, =board_init_r /* this is auto-relocated! */
- /* we should not return here. */
- </span>
- <span style="font-size:14px;">ENTRY(c_runtime_cpu_setup)
- /*
- * If I-cache is enabled invalidate it
- */
- #ifndef CONFIG_SYS_ICACHE_OFF
- mcr p15, 0, r0, c7, c5, 0 @ invalidate icache
- mcr p15, 0, r0, c7, c10, 4 @ DSB
- mcr p15, 0, r0, c7, c5, 4 @ ISB
- #endif
- /*
- * Move vector table
- */
- /* Set vector address in CP15 VBAR register */
- ldr r0, =_start
- mcr p15, 0, r0, c12, c0, 0 @Set VBAR
- bx lr
- ENDPROC(c_runtime_cpu_setup)
- </span>
接着更新異常向量表首地址,因爲code被relocate,所以異常向量表也被relocate。
從c_runtime_cpu_setup返回,下面一段彙編是將bss段清空。
接下來分別調用了coloured_LED_init以及red_led_on,很多開發板都會有led指示燈,這裏可以實現上電指示燈亮,有調試作用。
最後r0賦值gd指針,r1賦值relocaddr,進入最後的board_init_r !
七 board_init_r
參數1是新gd指針,參數2是relocate addr,也就是新code地址
- <span style="font-size:14px;"> gd->flags |= GD_FLG_RELOC; /* tell others: relocation done */
- bootstage_mark_name(BOOTSTAGE_ID_START_UBOOT_R, "board_init_r");
- monitor_flash_len = (ulong)&__rel_dyn_end - (ulong)_start;
- /* Enable caches */
- enable_caches();
- debug("monitor flash len: %08lX\n", monitor_flash_len);
- board_init(); /* Setup chipselects */
- </span>
置位gd->flags,標誌已經relocate。monitor_flash_len這個變量作用沒看懂。使能cache,最後board_init是需要實現的板級支持函數。做開發板的基本初始化。
- <span style="font-size:14px;">#ifdef CONFIG_CLOCKS
- set_cpu_clk_info(); /* Setup clock information */
- #endif
- serial_initialize();
- debug("Now running in RAM - U-Boot at: %08lx\n", dest_addr);
- </span>
重點來說一些serial_initialize,對於最精簡能正常啓動的uboot,serial和ddr是必須正常工作的。
實現在drivers/serial/serial.c中,如下:
- <span style="font-size:14px;">void serial_initialize(void)
- {
- mpc8xx_serial_initialize();
- ns16550_serial_initialize();
- pxa_serial_initialize();
- s3c24xx_serial_initialize();
- s5p_serial_initialize();
- mpc512x_serial_initialize();。。。。
- mxs_auart_initialize();
- arc_serial_initialize();
- vc0718_serial_initialize();
- serial_assign(default_serial_console()->name);
- }</span>
xxxx_serial_initialize函數中是將所有需要的串口(用結構體struct serial_device表示,其中實現了基本的收 發 配置)調用serial_register註冊,serial_register如下:
- <span style="font-size:14px;">void serial_register(struct serial_device *dev)
- {
- #ifdef CONFIG_NEEDS_MANUAL_RELOC
- if (dev->start)
- dev->start += gd->reloc_off;
- if (dev->stop)
- dev->stop += gd->reloc_off;
- if (dev->setbrg)
- dev->setbrg += gd->reloc_off;
- if (dev->getc)
- dev->getc += gd->reloc_off;
- if (dev->tstc)
- dev->tstc += gd->reloc_off;
- if (dev->putc)
- dev->putc += gd->reloc_off;
- if (dev->puts)
- dev->puts += gd->reloc_off;
- #endif
- dev->next = serial_devices;
- serial_devices = dev;
- }</span>
可以想象,如果你有4個串口,則再你的串口驅動中分別定義4個serial device,並實現對應的收發配置,然後serial_register註冊者4個串口。
回到serial-initialize,最後調用serial_assign,default_serial_console我們之前說過,就是你在串口驅動給出一個默認調試串口,serial_assign如下:
- <span style="font-size:14px;">int serial_assign(const char *name)
- {
- struct serial_device *s;
- for (s = serial_devices; s; s = s->next) {
- if (strcmp(s->name, name))
- continue;
- serial_current = s;
- return 0;
- }
- return -EINVAL;
- }</span>
總結一下,serial_initialize工作是將所有serial驅動中所有串口註冊到serial_devices鏈表中,然後找到指定的默認串口。
- <span style="font-size:14px;"> /* The Malloc area is immediately below the monitor copy in DRAM */
- malloc_start = dest_addr - TOTAL_MALLOC_LEN;
- mem_malloc_init (malloc_start, TOTAL_MALLOC_LEN);</span>
根據咱們之前board_init_f中的分析,relocate addr之下的部分就是malloc的預留空間了。這裏獲取malloc首地址malloc_start.
- <span style="font-size:14px;">void mem_malloc_init(ulong start, ulong size)
- {
- mem_malloc_start = start;
- mem_malloc_end = start + size;
- mem_malloc_brk = start;
- memset((void *)mem_malloc_start, 0, size);
- malloc_bin_reloc();
- }</span>
board_init_r接下來的代碼是做一些外設的初始化,比如mmc flash eth,環境變量的設置,還有中斷的使能等,這裏需要說一下是關於串口的2個函數,stdio_init和console_init_r.
看stdio_init代碼,我們只定義了serial,會調到serial_stdio_init,如下:
- <span style="font-size:14px;">void serial_stdio_init(void)
- {
- struct stdio_dev dev;
- struct serial_device *s = serial_devices;
- while (s) {
- memset(&dev, 0, sizeof(dev));
- strcpy(dev.name, s->name);
- dev.flags = DEV_FLAGS_OUTPUT | DEV_FLAGS_INPUT;
- dev.start = s->start;
- dev.stop = s->stop;
- dev.putc = s->putc;
- dev.puts = s->puts;
- dev.getc = s->getc;
- dev.tstc = s->tstc;
- stdio_register(&dev);
- s = s->next;
- }
- }</span>
可以想象,serial_stdio_init是在drivers/serial/serial.c中實現,uboot在這裏是利用的內核分層思想,drivers/serial下是特定serial驅動,分別調用serial_register註冊到serial_devices中,這可以說是通用的serial驅動層,
通用serial層調用serial-stdio-init將所有serial註冊到stdio device中,這就是通用的stdio層。
看來分層思想還是非常重要的!
board_init_r中調用完stdio_init後又調用了console_init_r,如下
- <span style="font-size:14px;">int console_init_r(void)
- {
- struct stdio_dev *inputdev = NULL, *outputdev = NULL;
- int i;
- struct list_head *list = stdio_get_list();
- struct list_head *pos;
- struct stdio_dev *dev;
- /* Scan devices looking for input and output devices */
- list_for_each(pos, list) {
- dev = list_entry(pos, struct stdio_dev, list);
- if ((dev->flags & DEV_FLAGS_INPUT) && (inputdev == NULL)) {
- inputdev = dev;
- }
- if ((dev->flags & DEV_FLAGS_OUTPUT) && (outputdev == NULL)) {
- outputdev = dev;
- }
- if(inputdev && outputdev)
- break;
- }
- if (outputdev != NULL) {
- console_setfile(stdout, outputdev);
- console_setfile(stderr, outputdev);
- }
- /* Initializes input console */
- if (inputdev != NULL) {
- console_setfile(stdin, inputdev);
- }
- #ifndef CONFIG_SYS_CONSOLE_INFO_QUIET
- stdio_print_current_devices();
- #endif /* CONFIG_SYS_CONSOLE_INFO_QUIET */
- /* Setting environment variables */
- for (i = 0; i < 3; i++) {
- setenv(stdio_names[i], stdio_devices[i]->name);
- }
- gd->flags |= GD_FLG_DEVINIT; /* device initialization completed */
- return 0;
- }</span>
之後調用console_setfile,如下:
- <span style="font-size:14px;">static int console_setfile(int file, struct stdio_dev * dev)
- {
- int error = 0;
- if (dev == NULL)
- return -1;
- switch (file) {
- case stdin:
- case stdout:
- case stderr:
- /* Start new device */
- if (dev->start) {
- error = dev->start();
- /* If it's not started dont use it */
- if (error < 0)
- break;
- }
- /* Assign the new device (leaving the existing one started) */
- stdio_devices[file] = dev;
- /*
- * Update monitor functions
- * (to use the console stuff by other applications)
- */
- switch (file) {
- case stdin:
- gd->jt[XF_getc] = dev->getc;
- gd->jt[XF_tstc] = dev->tstc;
- break;
- case stdout:
- gd->jt[XF_putc] = dev->putc;
- gd->jt[XF_puts] = dev->puts;
- gd->jt[XF_printf] = printf;
- break;
- }
- break;
- default: /* Invalid file ID */
- error = -1;
- }
- return error;
- }</span>
在console_init_r中最後會改變gd中flag狀態,爲GD_FLG_DEVINIT。表示設備初始化完成。
board_init_r進行完板級初始化後最後進入死循環,打印命令行,等待命令輸入和解析。到這裏uboot的啓動過程就全部結束了!
上面用很大篇幅自下往上解釋uboot下serial到console的架構,那來看一下實際使用時由printf到最後serial輸出這個自上到下的流程吧。
首先來看printf,實現在common/console.c中如下:
- <span style="font-size:14px;">int printf(const char *fmt, ...)
- {
- va_list args;
- uint i;
- char printbuffer[CONFIG_SYS_PBSIZE];
- #if !defined(CONFIG_SANDBOX) && !defined(CONFIG_PRE_CONSOLE_BUFFER)
- if (!gd->have_console)
- return 0;
- #endif
- va_start(args, fmt);
- /* For this to work, printbuffer must be larger than
- * anything we ever want to print.
- */
- i = vscnprintf(printbuffer, sizeof(printbuffer), fmt, args);
- va_end(args);
- /* Print the string */
- puts(printbuffer);
- return i;
- }</span>
- <span style="font-size:14px;">void puts(const char *s)
- {
- #ifdef CONFIG_SANDBOX
- if (!gd) {
- os_puts(s);
- return;
- }
- #endif
- #ifdef CONFIG_SILENT_CONSOLE
- if (gd->flags & GD_FLG_SILENT)
- return;
- #endif
- #ifdef CONFIG_DISABLE_CONSOLE
- if (gd->flags & GD_FLG_DISABLE_CONSOLE)
- return;
- #endif
- if (!gd->have_console)
- return pre_console_puts(s);
- if (gd->flags & GD_FLG_DEVINIT) {
- /* Send to the standard output */
- fputs(stdout, s);
- } else {
- /* Send directly to the handler */
- serial_puts(s);
- }
- }</span>
gd->have_console在board_init_f的console_init_f中置位,flag的GD_FLG_DEVINIT則是在剛纔board_init_r中console_init_r最後置位。
如果GD_FLG_DEVINIT沒有置位,表明console沒有註冊,是在board_init_f之後,board_init_r執行完成之前,這時調用serial_puts,如下:
- <span style="font-size:14px;">void serial_puts(const char *s)
- {
- get_current()->puts(s);
- } </span>
直接調到serial.c中的函數,完全符合board_init_f中serial_init的配置,僅僅找到一個默認串口來使用,其他串口暫且不管。
如果GD_FLG_DEVINIT置位,表明console註冊完成。調用fputs,如下:
- <span style="font-size:14px;">void fputs(int file, const char *s)
- {
- if (file < MAX_FILES)
- console_puts(file, s);
- }
- static inline void console_puts(int file, const char *s)
- {
- stdio_devices[file]->puts(s);
- }</span>
分析後總結一下:
可以看出,對於serial,uboot實現了一個2級初始化:
stage 1,僅初始化default console serial,printf到puts後會直接調用特定串口的puts函數,實現打印
stage 2,將所有serial註冊爲stdio_device,並挑出指定調試串口作爲stdio_devices的stdout stdin stderr。printf到puts後再到全局stdio_devices中找到對應stdio_device,調用stdio-device的puts,最終調用特定serial的puts,實現打印。
區分這2個stage,是利用gd的flag,GD_FLG_DEVINIT。
原作者:http://blog.csdn.net/skyflying2012/article/details/25804209