Tiny4412 u-boot分析(2)u-boot启动流程

时间:2022-08-31 16:46:11

从大方面来说,u-boot的启动分成两个阶段,第一个阶段主要的职责是准备初始化的环境,主要有以下几点

①设置异常向量表

②把CPU的工作模式设置为SVC32模式

③关闭中断、MMU和cache

④关闭看门狗

⑤初始化内存、时钟、串口

⑥设置堆栈

⑦代码搬移

⑧清bss段

⑨跳转到c语言中执行(第二阶段)

此时系统还没有进入C语言的运行阶段,并没有堆栈,也就不需要额外的RAM。

第二阶段在上一段建立好C语言运行环境的基础上,进行各种外设的初始化,并循环执行用户命令。主要流程图如下

Tiny4412 u-boot分析(2)u-boot启动流程

当我们执行make命令来构建u-boot时,它的构建过程是:首先使用交叉编译工具将各目录下的源文件生成目标文件(*.o),目标文件生成后,会将若干个目标文件组合成静态库文件(*.a),最后通过链接各个静态库文件生成ELF格式的可执行文件。在链接的过程中,需要根据链接脚本(一般是各个以lds为后缀的文本文件),确定目标文件的各个段,链接文件通常是board/<board>/目录中的u-boot.lds文件。一般在链接脚本中通过

ENTRY(_start)

来指定入口为_start标号,通过文本段(.text)的第一个目标来制定u-boot入口文件。所以我们通过这个链接脚本文件可以确定u-boot执行的入口。

Tiny4412 u-boot的链接脚本内容为

//  board/samsung/tiny4412/u-boot.lds
OUTPUT_FORMAT("elf32-littlearm", "elf32-littlearm", "elf32-littlearm")
OUTPUT_ARCH(arm)
ENTRY(_start)
SECTIONS
{
.
= 0x00000000;
.
= ALIGN(4);
.text :
{
arch
/arm/cpu/armv7/start.o (.text)
board
/samsung/tiny4412/libtiny4412.o (.text)
arch
/arm/cpu/armv7/exynos/libexynos.o (.text)
*(.text)
}
.
= ALIGN(4);
.rodata : {
*(SORT_BY_ALIGNMENT(SORT_BY_NAME(.rodata*))) }
.
= ALIGN(4);
.data : {
*(.data)
}
.
= ALIGN(4);
.
= .;
__u_boot_cmd_start
= .;
.u_boot_cmd : {
*(.u_boot_cmd) }
__u_boot_cmd_end
= .;
.
= ALIGN(4);
.rel.dyn : {
__rel_dyn_start
= .;
*(.rel*)
__rel_dyn_end
= .;
}
.dynsym : {
__dynsym_start
= .;
*(.dynsym)
}
.bss __rel_dyn_start (OVERLAY) : {
__bss_start
= .;
*(.bss)
.
= ALIGN(4);
_end
= .;
}
/DISCARD/ : { *(.dynstr*) }
/DISCARD/ : { *(.dynamic*) }
/DISCARD/ : { *(.plt*) }
/DISCARD/ : { *(.interp*) }
/DISCARD/ : { *(.gnu*) }
}

在本链接脚本文件中,定义了起始地址为0x00000000,每个段使用4字节对齐(.ALIGN(4)),几个段分别为代码段(.text)、只读数据段(.rodata)、数据段(.data)其中,代码段的第一个目标为arch/arm/cpu/armv7/start.o,在其中定义了映像文件的入口_start。

下面来具体分析一下这个start.S。

在文件的一开始定义了映像的入口_start和中断向量表。

.globl _start  //定义u-boot入口
_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
_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 */

系统开机进入到u-boot运行时,首先进入到u-boot的入口_start标号处,然后通过 b  reset 跳转到reset标号处,我们就到reset标号一探究竟。

/*
* the actual reset code
*/
reset:
/*
*设置CPU工作模式为SVC32模式
* set the cpu to SVC32 mode
*/
mrs r0, cpsr
bic r0, r0, #0x1f
orr r0, r0, #0xd3
msr cpsr,r0
//......
//调用 cpu_init_crit
#ifndef CONFIG_SKIP_LOWLEVEL_INIT
bl cpu_init_crit
#endif

首先会将CPU的工作模式设置为svc32模式,然后便调用 cpu_init_crit ,需要注意的是,这里使用的是 bl 指令,也就是说在运行完 cpu_init_crit 标号处的代码之后,会通过

mov    pc, lr            @ back to my caller

指令回到reset中继续执行

bl    cpu_init_crit

下面的指令(所以这里我们应该使用 调用来描述更为贴切)。下面我们去看一下 cpu_init_crit 指令处做了哪些事

/*************************************************************************
*
* CPU_init_critical registers
*
* setup important registers
* setup memory timing
*
*************************************************************************/
cpu_init_crit:

//调用 cache_init
bl cache_init

/*
*使 L1 I/D 无效
* 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
/*
* 关闭 MMU 和 cache
* 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
12 (Z---) BTB
mcr p15,
0, r0, c1, c0, 0
/*
* 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.
*/
mov ip, lr @ persevere link reg across call
//调用 lowlevel_init
bl lowlevel_init @ go setup pll,mux,memory
mov lr, ip @ restore link
//返回到 reset 标号继续执行
mov pc, lr @ back to my caller
/*

首先分析 cache_init ,它被定义在  board/samsung/tiny4412/lowlevel_init.S 文件中

    .globl cache_init
cache_init:
mov pc, lr

可以看出来,这是一个空函数(暂且将它叫做函数-_-!!)。

接下来我们就要去分析 lowlevel_init 了,它也被定义在board/samsung/tiny4412/lowlevel_init.S 文件中

    .globl lowlevel_init
lowlevel_init:

//初始化串口
bl uart_asm_init

//Read booting information
//读取启动信息
bl read_om
/* when we already run
in ram, we don't need to relocate U-Boot.
* and actually, memory controller must be configured before U-Boot
* is running in ram.
*/
ldr r0, =0xff000fff
bic r1, pc, r0 /* r0 <- current base addr of code */
ldr r2, _TEXT_BASE /* r1 <- original base addr in ram */
bic r2, r2, r0 /* r0 <- current base addr of code */
cmp r1, r2 /* compare r0, r1 */
beq after_copy /* r0 == r1 then skip sdram init and u-boot.bin loading */
//初始化内存
/* Memory initialize */
bl mem_ctrl_asm_init
//初始化系统时钟
/* init system clock */
bl system_clock_init


/*
eg:
1: ;A
cmp r0, #0
beq 1f ; r0==0那么向前跳转到B处执行
bne 1b ; 否则向后跳转到A处执行
:1: ;B
*/

// 向前跳转到1: 标号处执行
b 1f
1:
//初始化 trust zone
bl tzpc_init
b load_uboot
after_copy:
#ifdef CONFIG_ENABLE_MMU
bl enable_mmu
#endif
/* store second boot information in u-boot C level variable */
ldr r0, =CONFIG_PHY_UBOOT_BASE
sub r0, r0, #8
ldr r1, [r0]
ldr r0, _second_boot_info
str r1, [r0]
/* Print
'K' */
ldr r0, =S5PV310_UART_CONSOLE_BASE
ldr r1, =0x4b4b4b4b
str r1, [r0, #UTXH_OFFSET]
//第二阶段入口,调用C语言函数:board_init_f
ldr r0, _board_init_f
mov pc, r0

_board_init_f:
.word board_init_f
load_uboot:
ldr r0, =INF_REG_BASE
ldr r1, [r0, #INF_REG3_OFFSET]
cmp r1, #BOOT_NAND
beq nand_boot
cmp r1, #BOOT_ONENAND
beq onenand_boot
cmp r1, #BOOT_MMCSD
beq mmcsd_boot
cmp r1, #BOOT_EMMC
beq emmc_boot
cmp r1, #BOOT_EMMC_4_4
beq emmc_boot_4_4
cmp r1, #BOOT_NOR
beq nor_boot
cmp r1, #BOOT_SEC_DEV
beq mmcsd_boot
nand_boot:
mov r0, #0x1000
bl copy_uboot_to_ram
b after_copy
onenand_boot:
bl onenand_bl2_copy /*goto 0x1010*/
b after_copy
mmcsd_boot:
#ifdef CONFIG_SMDKC220
//#ifdef CONFIG_CLK_BUS_DMC_200_400
ldr r0, =ELFIN_CLOCK_BASE
ldr r2, =CLK_DIV_FSYS2_OFFSET
ldr r1, [r0, r2]
orr r1, r1, #0xf
str r1, [r0, r2]
//#endif
#else
#if defined(CONFIG_CLK_1000_400_200) || defined(CONFIG_CLK_1000_200_200) || defined(CONFIG_CLK_800_400_200)
ldr r0, =ELFIN_CLOCK_BASE
ldr r2, =CLK_DIV_FSYS2_OFFSET
ldr r1, [r0, r2]
orr r1, r1, #0xf
str r1, [r0, r2]
#endif
#endif
bl movi_uboot_copy
b after_copy
emmc_boot:
#if defined(CONFIG_CLK_1000_400_200) || defined(CONFIG_CLK_1000_200_200) || defined(CONFIG_CLK_800_400_200)
ldr r0, =ELFIN_CLOCK_BASE
ldr r2, =CLK_DIV_FSYS1_OFFSET
ldr r1, [r0, r2]
orr r1, r1, #0xf
str r1, [r0, r2]
#endif
bl emmc_uboot_copy
b after_copy
emmc_boot_4_4:
/* read TCBCNT to get Transferred CIU card byte count */
ldr r0, =0x1255005c
ldr r1, [r0]
ldr r2, =0x6000
cmp r1, r2
/* store second boot information in DRAM */
ldr r0, =CONFIG_PHY_UBOOT_BASE
sub r0, r0, #8
mov r3, #0
movlo r3, #1
str r3, [r0]
/* if transferred CIU card byte count >= 0x6000 (24 KB) */
/* BL1 and BL2 are loaded from emmc 4.4 */
/* Otherwise BL1 and BL2 are loaded from sdmmc ch2. */
blo mmcsd_boot
/* mmc ch4 devider value change */
bl mmc_ch4_devider_change
/* u-boot image copy from boot partition to DRAM. */
bl emmc_4_4_uboot_copy
/* Exit Boot mood */
bl emmc_4_4_endbootOp_eMMC
b after_copy

在 lowlevel_init 中,主要做了一些初始化工作,比如系统时钟、内存、串口等的初始化工作,然后初始化堆栈、清bss段,并进行了代码搬移,为第二阶段C语言程序运行提供保障。最后通过

ldr r0, _board_init_f
mov pc, r0

指令跳转到第二阶段C语言函数 board_init_f 函数处。接着我们就去分析一下这个函数。

在分析board_init_f函数之前,先来了解以下gd_t数据结构

// arch/arm/include/asm/global_data.h
typedef struct global_data {
bd_t
*bd;
unsigned
long flags;
unsigned
long baudrate;
unsigned
long have_console; /* serial_init() was called */
unsigned
long env_addr; /* Address of Environment struct */
unsigned
long env_valid; /* Checksum of Environment valid? */
unsigned
long fb_base; /* base address of frame buffer */
#ifdef CONFIG_VFD
unsigned
char vfd_type; /* display type */
#endif
#ifdef CONFIG_FSL_ESDHC
unsigned
long sdhc_clk;
#endif
#ifdef CONFIG_AT91FAMILY
/* "static data" needed by at91's clock.c */
unsigned
long cpu_clk_rate_hz;
unsigned
long main_clk_rate_hz;
unsigned
long mck_rate_hz;
unsigned
long plla_rate_hz;
unsigned
long pllb_rate_hz;
unsigned
long at91_pllb_usb_init;
#endif
#ifdef CONFIG_ARM
/* "static data" needed by most of timer.c on ARM platforms */
unsigned
long timer_rate_hz;
unsigned
long tbl;
unsigned
long tbu;
unsigned
long long timer_reset_value;
unsigned
long lastinc;
#endif
unsigned
long relocaddr; /* Start address of U-Boot in RAM */
phys_size_t ram_size;
/* RAM size */
unsigned
long mon_len; /* monitor len */
unsigned
long irq_sp; /* irq stack pointer */
unsigned
long start_addr_sp; /* start_addr_stackpointer */
unsigned
long reloc_off;
#if !(defined(CONFIG_SYS_NO_ICACHE) && defined(CONFIG_SYS_NO_DCACHE))
unsigned
long tlb_addr;
#endif
void **jt; /* jump table */
char env_buf[32]; /* buffer for getenv() before reloc. */
} gd_t;
#define DECLARE_GLOBAL_DATA_PTR register volatile gd_t *gd asm ("r8")

u-boot中使用一个结构体gd_t来存储全局区的数据,使用一个存储在寄存器中的指针gd来记录全局数据区的地址。

DECLARE_GLOBAL_DATA_PTR

在board/samsung/tiny4412/tiny4412.c被声明。

u-boot 中还有一个数据结构 bd_t用来存放板级相关的全局数据,是gd_t中结构体指针成员bd的结构体类型。

//   arch/arm/include/asm/u-boot.h
typedef struct bd_info {
int bi_baudrate; /* serial console baudrate */
unsigned
long bi_ip_addr; /* IP Address */
ulong bi_arch_number; /* unique id for this board */
ulong bi_boot_params; /* where this board expects params */
struct /* RAM configuration */
{
ulong start;
ulong size;
} bi_dram[CONFIG_NR_DRAM_BANKS];
} bd_t;

u-boot启动内核时要给内核传递参数,这时需要使用gd_t、bd_t结构体中的信息来设置标记列表。了解了这两个数据结构我们就去分析一下board_init_f函数

//   arch/arm/lib/board.c
void board_init_f(ulong bootflag)
{
bd_t
*bd;
init_fnc_t
**init_fnc_ptr;
gd_t
*id;
ulong addr, addr_sp;
//计算全局数据结构的地址,保存在gd指针中
/* Pointer is writable since we allocated a register for it */
gd
= (gd_t *) ((CONFIG_SYS_INIT_SP_ADDR) & ~0x07);
/* compiler optimization barrier needed for GCC >= 3.4 */
__asm__ __volatile__(
"": : :"memory");

memset((
void*)gd, 0, sizeof (gd_t));
gd
->mon_len = _bss_end_ofs;
逐个调用init_sequence数组的初始化函数
for (init_fnc_ptr = init_sequence; *init_fnc_ptr; ++init_fnc_ptr) {
if ((*init_fnc_ptr)() != 0) {
hang();
}
}
debug (
"monitor len: %08lX\n", gd->mon_len);
/*
* Ram is setup, size stored in gd !!
*/
debug (
"ramsize: %08lX\n", gd->ram_size);
//填充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));
//调用arch/arm/cpu/armv7/start.S relocate_code
relocate_code(addr_sp, id, addr);
/* NOTREACHED - relocate_code() does not return */
}

u-boot使用一个init_sequence数组来存储大多数开发板都要执行的初始化函数的函数指针

// arch/arm/lib/board.c
typedef int (init_fnc_t)(void);
init_fnc_t
*init_sequence[] = {
#if defined(CONFIG_ARCH_CPU_INIT)
arch_cpu_init,
/* basic arch cpu dependent setup */
#endif
#if defined(CONFIG_BOARD_EARLY_INIT_F)
board_early_init_f,
#endif
timer_init,
/* initialize timer */
#ifdef CONFIG_FSL_ESDHC
get_clocks,
#endif
env_init,
/* initialize environment */
#if defined(CONFIG_S5P6450) && !defined(CONFIG_S5P6460_IP_TEST)
init_baudrate,
/* initialze baudrate settings */
serial_init,
/* serial communications setup */
#endif
console_init_f,
/* stage 1 init of console */
display_banner,
/* say that we are here */
#if defined(CONFIG_DISPLAY_CPUINFO)
print_cpuinfo,
/* display cpu info (and speed) */
#endif
#if defined(CONFIG_DISPLAY_BOARDINFO)
checkboard,
/* display board info */
#endif
#if defined(CONFIG_HARD_I2C) || defined(CONFIG_SOFT_I2C)
init_func_i2c,
#endif
dram_init,
/* configure available RAM banks */
#if defined(CONFIG_CMD_PCI) || defined(CONFIG_PCI)
arm_pci_init,
#endif
NULL,
};

board_init_f函数在调用完初始化函数指针、填充完gd结构之后,调用了arch/arm/cpu/armv7/start.S中的relocate_code去看一下relocate_code做了什么

    .globl    relocate_code
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:
mov sp, r4
adr r0, _start
#if defined(CONFIG_S5PC110) && defined(CONFIG_EVT1) && !defined(CONFIG_FUSED)
sub r0, r0, #16
#endif
#ifndef CONFIG_PRELOADER
cmp r0, r6
beq clear_bss /* skip relocation */
#endif
mov r1, r6 /* r1 <- scratch for copy_loop */
ldr r2, _TEXT_BASE
ldr r3, _bss_start_ofs
add r2, r0, r3 /* r2 <- source end address */
copy_loop:
ldmia r0!, {r9-r10} /* copy from source address [r0] */
stmia r1!, {r9-r10} /* copy to target address [r1] */
cmp r0, r2 /* until source end address [r2] */
blo copy_loop
#ifndef CONFIG_PRELOADER
/*
* fix .rel.dyn relocations
*/
ldr r0, _TEXT_BASE /* r0 <- Text base */
sub r9, r6, r0 /* r9 <- relocation offset */
ldr r10, _dynsym_start_ofs /* r10 <- sym table ofs */
add r10, r10, r0 /* r10 <- sym table in FLASH */
ldr r2, _rel_dyn_start_ofs /* r2 <- rel dyn start ofs */
add r2, r2, r0 /* r2 <- rel dyn start in FLASH */
ldr r3, _rel_dyn_end_ofs /* r3 <- rel dyn end ofs */
add r3, r3, r0 /* r3 <- rel dyn end in FLASH */
fixloop:
ldr r0, [r2] /* r0 <- location to fix up,
IN FLASH! */
add r0, r0, r9 /* r0 <- location to fix up in RAM */
ldr r1, [r2, #
4]
and r7, r1, #0xff
cmp r7, #23 /* relative fixup? */
beq fixrel
cmp r7, #2 /* absolute fixup? */
beq fixabs
/* ignore unknown type of fixup */
b fixnext
fixabs:
/* absolute
fix: set location to (offset) symbol value */
mov r1, r1, LSR #4 /* r1 <- symbol index in .dynsym */
add r1, r10, r1 /* r1 <- address of symbol in table */
ldr r1, [r1, #
4] /* r1 <- symbol value */
add r1, r1, r9 /* r1 <- relocated sym addr */
b fixnext
fixrel:
/* relative
fix: increase location by offset */
ldr r1, [r0]
add r1, r1, r9
fixnext:
str r1, [r0]
add r2, r2, #8 /* each rel.dyn entry is 8 bytes */
cmp r2, r3
blo fixloop
clear_bss:
ldr r0, _bss_start_ofs
ldr r1, _bss_end_ofs
ldr r3, _TEXT_BASE /* Text base */
mov r4, r6 /* reloc addr */
add r0, r0, r4
add r1, r1, r4
mov r2, #0x00000000 /* clear */
clbss_l:str r2, [r0] /* clear loop... */
add r0, r0, #4
cmp r0, r1
bne clbss_l
#endif /* #ifndef CONFIG_PRELOADER */
/*
* We are done. Do
not return, instead branch to second part of board
* initialization, now running from RAM.
*/

//调用board_init_r
jump_2_ram:
ldr r0, _board_init_r_ofs
adr r1, _start
add lr, r0, r1
@
add lr, lr, r9
/* setup parameters for board_init_r */
mov r0, r5 /* gd_t */
mov r1, r6 /* dest_addr */
/* jump to it ... */
mov pc, lr
_board_init_r_ofs:
.word board_init_r - _start

可见,最后调用了board_init_r函数

//   arch/arm/lib/board.c
void board_init_r(gd_t *id, ulong dest_addr)
{
char *s;
bd_t
*bd;
ulong malloc_start;
gd
= id;
bd
= gd->bd;
gd
->flags |= GD_FLG_RELOC; /* tell others: relocation done */
monitor_flash_len
= _bss_start_ofs;
debug (
"monitor flash len: %08lX\n", monitor_flash_len);
board_init();
/* Setup chipselects */
debug (
"Now running in RAM - U-Boot at: %08lx\n", dest_addr);
/* 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);
//初始化MMC
#ifdef CONFIG_GENERIC_MMC
mmc_initialize(bd);
#endif
//初始化环境变量
/* initialize environment */
env_relocate();
//将环境变量中的IP填充到gd结构体
/* IP Address */
gd
->bd->bi_ip_addr = getenv_IPaddr("ipaddr");
stdio_init();
/* get the devices list going. */
jumptable_init();
//初始化并使能中断
/* set up exceptions */
interrupt_init();
/* enable exceptions */
enable_interrupts();
/* Initialize from environment */
if ((s = getenv("loadaddr")) != NULL) {
load_addr
= simple_strtoul(s, NULL, 16);
}
//进入到main_loop
/* main_loop() can return to retry autoboot, if so just run it again. */
for (;;) {
main_loop();
}
/* NOTREACHED - no way out of command loop except booting */
}

最后调用了main_loop

待完成:

①main_loop分析

②启动内核过程

。。。。。。