一,u-boot启动第一阶段
1,启动流程
ENTRY(_start) //arch/arm/lib/vectors.S
----b resets //arch/arm/cpu/armv7/start.S
--------b save_boot_params
------------b save_boot_params_ret //将cpu的工作模式设置为SVC32模式(即管理模式),同时将中断禁止位与快速中断禁止位都设置为1, 以此屏蔽IRQ和FIQ的中断
--------bl cpu_init_cp15 //关闭mmu,不需要它转换地址,直接操作寄存器方便快捷
--------bl cpu_init_crit
------------b lowlevel_init //arch/arm/cpu/armv7/lowlevel_init.S ,与特定开发板相关的初始化函数,在这个函数里会做一些pll初始化,如果不是从内存启动,则会做内存初始化,方便后续拷贝到内存中运行。
--------bl _main //arch/arm/lib/crt0.S //初始化c语言环境,以便调用board_init_f函数。这个环境只提供了一个堆栈和一个存储GD(全局数据)结构的地方,两者都位于一些可用的RAM中。在调用board_init_f()之前,GD应该被归零。
------------bl board_init_f_alloc_reserve // common/init/board_init.c,该函数主要作用是保留早期malloc区域,且为GD(全局数据区)留出空间,函数返回值也是r0,r0保存着预留早期malloc区域和GD后的地址
------------bl board_init_f_init_reserve //common/init/board_init.c 将GD区域清零,返回最初malloc区域的地址
------------bl board_init_f //common/board_f.c
----------------initcall_run_list(init_sequence_f)
------------b relocate_code //arch/arm/lib/relocate.S,实现u-boot自身重定位的
----------------ldr r1, =__image_copy_start /* r1 <- SRC &__image_copy_start */
----------------ldr r2, =__image_copy_end /* r2 <- SRC &__image_copy_end */ 从__image_copy_start地址到__image_copy_end地址中间包含了代码段、数据段以及只读数据段,但是不包括动态链接rel_dyn部分
----------------ldr r2, =__rel_dyn_start /* r2 <- SRC &__rel_dyn_start */
----------------ldr r3, =__rel_dyn_end /* r3 <- SRC &__rel_dyn_end */ 直到修改完整个__rel_dyn段后结束,完成重定位
------------bl relocate_vectors //主要完成的工作就是实现异常向量表的重定位,拷贝到正确的地址中去
------------bl c_runtime_cpu_setup //关闭指令缓存I-cache,重定位后到了新的介质中运行也是要设置一下运行环境
------------ldr r0, =__bss_start /* this is auto-relocated! */
------------ldr r3, =__bss_end /* this is auto-relocated! */
------------bl memset //清除BSS段
------------ldr pc, =board_init_r /* this is auto-relocated! */ 第二阶段的入口board_init_r,common/board_r.c
----------------initcall_run_list(init_sequence_r)
_main:
arch/arm/lib/crt0.S
/*
* entry point of crt0 sequence
*/
ENTRY(_main)
/*
* Set up initial C runtime environment and call board_init_f(0).
*/
#if defined(CONFIG_TPL_BUILD) && defined(CONFIG_TPL_NEEDS_SEPARATE_STACK)
ldr r0, =(CONFIG_TPL_STACK)
#elif defined(CONFIG_SPL_BUILD) && defined(CONFIG_SPL_STACK)
ldr r0, =(CONFIG_SPL_STACK)
#else
ldr r0, =(CONFIG_SYS_INIT_SP_ADDR)
#endif
bic r0, r0, #7 /* 8-byte alignment for ABI compliance */
mov sp, r0
bl board_init_f_alloc_reserve
mov sp, r0
/* set up gd here, outside any C code */
mov r9, r0 //gd是一个保存在ARM的r9寄存器中的gd_t结构体的指针
bl board_init_f_init_reserve
#if defined(CONFIG_SPL_BUILD) && defined(CONFIG_SPL_EARLY_BSS)
CLEAR_BSS
#endif
mov r0, #0
bl board_init_f
#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 r0, [r9, #GD_START_ADDR_SP] /* sp = gd->start_addr_sp */
bic r0, r0, #7 /* 8-byte alignment for ABI compliance */
mov sp, r0
ldr r9, [r9, #GD_NEW_GD] /* r9 <- gd->new_gd */
adr lr, here
ldr r0, [r9, #GD_RELOC_OFF] /* r0 = gd->reloc_off */
add lr, lr, r0
#if defined(CONFIG_CPU_V7M)
orr lr, #1 /* As required by Thumb-only */
#endif
ldr r0, [r9, #GD_RELOCADDR] /* r0 = gd->relocaddr */
b relocate_code
here:
/*
* now relocate vectors
*/
bl relocate_vectors
/* Set up final (full) environment */
bl c_runtime_cpu_setup /* we still call old routine here */
#endif
#if !defined(CONFIG_SPL_BUILD) || CONFIG_IS_ENABLED(FRAMEWORK)
#if !defined(CONFIG_SPL_BUILD) || !defined(CONFIG_SPL_EARLY_BSS)
CLEAR_BSS
#endif
# ifdef CONFIG_SPL_BUILD
/* Use a DRAM stack for the rest of SPL, if requested */
bl spl_relocate_stack_gd
cmp r0, #0
movne sp, r0
movne r9, r0
# endif
#if ! defined(CONFIG_SPL_BUILD)
bl coloured_LED_init
bl red_led_on
#endif
/* 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 */
#if CONFIG_IS_ENABLED(SYS_THUMB_BUILD)
ldr lr, =board_init_r /* this is auto-relocated! */
bx lr
#else
ldr pc, =board_init_r /* this is auto-relocated! */
#endif
/* we should not return here. */
#endif
ENDPROC(_main)
2,global data
2.1 为什么使用global data
u-boot是一个bootloader,有些情况下,它可能位于系统的只读存储器(ROM或者flash)中,并从那里开始执行。
因此,这种情况下,在u-boot执行的前期(在将自己copy到可读写的存储器之前),它所在的存储空间,是不可写的,这会有两个问题:
1)堆栈无法使用,无法执行函数调用,也即C环境不可用。
2)没有data段(或者正确初始化的data段)可用,不同函数或者代码之间,无法通过全局变量的形式共享数据。
对于问题1,通常的解决方案是:
u-boot运行起来之后,在那些不需要执行任何初始化动作即可使用的、可读写的存储区域,开辟一段堆栈(stack)空间。
一般来说,大部分的平台(如很多ARM平台),都有自己的SRAM,可用作堆栈空间。如果实在不行,也有可借用CPU的data cache的方法。
对于问题2,解决方案要稍微复杂一些:
首先,对于开发者来说,在u-boot被拷贝到可读写的RAM(这个动作称作relocation)之前,永远不要使用全局变量。
其次,在relocation之前,不同模块之间,确实有通过全局变量的形式传递数据的需求。怎么办?这就是global data需要解决的事情。
2.2 GD空间分配和初始化
为了在relocation前通过全局变量的形式传递数据,u-boot设计了一个巧妙的方法:
1)定义一个struct global_data类型的数据结构,里面保存了各色各样需要传递的数据。
2)堆栈配置好之后,在堆栈开始的位置,为struct global_data预留空间,并将开始地址(就是一个struct global_data指针)保存在一个寄存器中,后续的传递,都是通过保存在寄存器中的指针实现。
arch/arm/lib/crt0.S
ENTRY(_main)
/** Set up initial C runtime environment and call board_init_f(0).*/
#if defined(CONFIG_SPL_BUILD) && defined(CONFIG_SPL_STACK)
ldr r0, =(CONFIG_SPL_STACK)
#else
ldr r0, =(CONFIG_SYS_INIT_SP_ADDR) //加载CONFIG_SYS_INIT_SP_ADDR到r0寄存器
#endif
bic r0, r0, #7 /* 8-byte alignment for ABI compliance */ 遵从ABI的8字节对齐
mov sp, r0 //将堆栈指针指向r0寄存器的值
bl board_init_f_alloc_reserve //主要作用是保留早期malloc区域,且为GD(全局数据区)留出空间,函数返回值也是r0,r0保存着预留早期malloc区域和GD后的地址
mov sp, r0
/* set up gd here, outside any C code */
mov r9, r0 //定义一个寄存器全局变量指针,并指定其使用的寄存器是r9,类型为gd_t
bl board_init_f_init_reserve //该函数主要作用是将GD区域清零,返回最初malloc区域的地址
mov r0, #0 //清空r0,然后把参数r0传给board_init_f函数,并调用board_init_f
bl board_init_f
2.3 gt_t 和 bd_t 结构体
gd_t结构体几乎包含了u-boot中用到的所有全局变量, gd_t和bd_t都u-boot中两个重要的数据结构,在初始化操作很多都要靠这两个数据结构来保存或传递。 gd_t结构体如下所示:
include/asm-generic/global_data.h
typedef struct global_data {
bd_t *bd;
unsigned long flags;
unsigned int baudrate;
unsigned long cpu_clk; /* CPU clock in Hz! */
unsigned long bus_clk;
/* We cannot bracket this with CONFIG_PCI due to mpc5xxx */
unsigned long pci_clk;
unsigned long mem_clk;
#if defined(CONFIG_LCD) || defined(CONFIG_VIDEO)
unsigned long fb_base; /* Base address of framebuffer mem */
#endif
#if defined(CONFIG_POST) || defined(CONFIG_LOGBUFFER)
unsigned long post_log_word; /* Record POST activities */
unsigned long post_log_res; /* success of POST test */
unsigned long post_init_f_time; /* When post_init_f started */
#endif
#ifdef CONFIG_BOARD_TYPES
unsigned long board_type;
#endif
unsigned long have_console; /* serial_init() was called */
#if CONFIG_IS_ENABLED(PRE_CONSOLE_BUFFER)
unsigned long precon_buf_idx; /* Pre-Console buffer index */
#endif
unsigned long env_addr; /* Address of Environment struct */
unsigned long env_valid; /* Checksum of Environment valid? */
unsigned long ram_top; /* Top address of RAM used by U-Boot */
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;
struct global_data *new_gd; /* relocated global data */
#ifdef CONFIG_DM
struct udevice *dm_root; /* Root instance for Driver Model */
struct udevice *dm_root_f; /* Pre-relocation root instance */
struct list_head uclass_root; /* Head of core tree */
#endif
#ifdef CONFIG_TIMER
struct udevice *timer; /* Timer instance for Driver Model */
#endif
const void *fdt_blob; /* Our device tree, NULL if none */
void *new_fdt; /* Relocated FDT */
unsigned long fdt_size; /* Space reserved for relocated FDT */
struct jt_funcs *jt; /* jump table */
char env_buf[32]; /* buffer for getenv() before reloc. */
#ifdef CONFIG_TRACE
void *trace_buff; /* The trace buffer */
#endif
#if defined(CONFIG_SYS_I2C)
int cur_i2c_bus; /* current used i2c bus */
#endif
#ifdef CONFIG_SYS_I2C_MXC
void *srdata[10];
#endif
unsigned long timebase_h;
unsigned long timebase_l;
#ifdef CONFIG_SYS_MALLOC_F_LEN
unsigned long malloc_base; /* base address of early malloc() */
unsigned long malloc_limit; /* limit address */
unsigned long malloc_ptr; /* current address */
#endif
#ifdef CONFIG_PCI
struct pci_controller *hose; /* PCI hose for early use */
phys_addr_t pci_ram_top; /* top of region accessible to PCI */
#endif
#ifdef CONFIG_PCI_BOOTDELAY
int pcidelay_done;
#endif
struct udevice *cur_serial_dev; /* current serial device */
struct arch_global_data arch; /* architecture-specific data */
#ifdef CONFIG_CONSOLE_RECORD
struct membuff console_out; /* console output */
struct membuff console_in; /* console input */
#endif
#ifdef CONFIG_DM_VIDEO
ulong video_top; /* Top of video frame buffer area */
ulong video_bottom; /* Bottom of video frame buffer area */
#endif
} gd_t;
bd_t结构体如下所示:
typedef struct bd_info {
unsigned long bi_memstart; /* start of DRAM memory */
phys_size_t bi_memsize; /* size of DRAM memory in bytes */
unsigned long bi_flashstart; /* start of FLASH memory */
unsigned long bi_flashsize; /* size of FLASH memory */
unsigned long bi_flashoffset; /* reserved area for startup monitor */
unsigned long bi_sramstart; /* start of SRAM memory */
unsigned long bi_sramsize; /* size of SRAM memory */
#ifdef CONFIG_AVR32
unsigned char bi_phy_id[4]; /* PHY address for ATAG_ETHERNET */
unsigned long bi_board_number;/* ATAG_BOARDINFO */
#endif
#ifdef CONFIG_ARM
unsigned long bi_arm_freq; /* arm frequency */
unsigned long bi_dsp_freq; /* dsp core frequency */
unsigned long bi_ddr_freq; /* ddr frequency */
#endif
#if defined(CONFIG_5xx) || defined(CONFIG_8xx) || defined(CONFIG_MPC8260) \
|| defined(CONFIG_E500) || defined(CONFIG_MPC86xx)
unsigned long bi_immr_base; /* base of IMMR register */
#endif
#if defined(CONFIG_MPC5xxx) || defined(CONFIG_M68K)
unsigned long bi_mbar_base; /* base of internal registers */
#endif
#if defined(CONFIG_MPC83xx)
unsigned long bi_immrbar;
#endif
unsigned long bi_bootflags; /* boot / reboot flag (Unused) */
unsigned long bi_ip_addr; /* IP Address */
unsigned char bi_enetaddr[6]; /* OLD: see README.enetaddr */
unsigned short bi_ethspeed; /* Ethernet speed in Mbps */
unsigned long bi_intfreq; /* Internal Freq, in MHz */
unsigned long bi_busfreq; /* Bus Freq, in MHz */
#if defined(CONFIG_CPM2)
unsigned long bi_cpmfreq; /* CPM_CLK Freq, in MHz */
unsigned long bi_brgfreq; /* BRG_CLK Freq, in MHz */
unsigned long bi_sccfreq; /* SCC_CLK Freq, in MHz */
unsigned long bi_vco; /* VCO Out from PLL, in MHz */
#endif
#if defined(CONFIG_MPC512X)
unsigned long bi_ipsfreq; /* IPS Bus Freq, in MHz */
#endif /* CONFIG_MPC512X */
#if defined(CONFIG_MPC5xxx) || defined(CONFIG_M68K)
unsigned long bi_ipbfreq; /* IPB Bus Freq, in MHz */
unsigned long bi_pcifreq; /* PCI Bus Freq, in MHz */
#endif
#if defined(CONFIG_EXTRA_CLOCK)
unsigned long bi_inpfreq; /* input Freq in MHz */
unsigned long bi_vcofreq; /* vco Freq in MHz */
unsigned long bi_flbfreq; /* Flexbus Freq in MHz */
#endif
#if defined(CONFIG_405) || \
defined(CONFIG_405GP) || \
defined(CONFIG_405EP) || \
defined(CONFIG_405EZ) || \
defined(CONFIG_405EX) || \
defined(CONFIG_440)
unsigned char bi_s_version[4]; /* Version of this structure */
unsigned char bi_r_version[32]; /* Version of the ROM (AMCC) */
unsigned int bi_procfreq; /* CPU (Internal) Freq, in Hz */
unsigned int bi_plb_busfreq; /* PLB Bus speed, in Hz */
unsigned int bi_pci_busfreq; /* PCI Bus speed, in Hz */
unsigned char bi_pci_enetaddr[6]; /* PCI Ethernet MAC address */
#endif
#ifdef CONFIG_HAS_ETH1
unsigned char bi_enet1addr[6]; /* OLD: see README.enetaddr */
#endif
#ifdef CONFIG_HAS_ETH2
unsigned char bi_enet2addr[6]; /* OLD: see README.enetaddr */
#endif
#ifdef CONFIG_HAS_ETH3
unsigned char bi_enet3addr[6]; /* OLD: see README.enetaddr */
#endif
#ifdef CONFIG_HAS_ETH4
unsigned char bi_enet4addr[6]; /* OLD: see README.enetaddr */
#endif
#ifdef CONFIG_HAS_ETH5
unsigned char bi_enet5addr[6]; /* OLD: see README.enetaddr */
#endif
#if defined(CONFIG_405GP) || defined(CONFIG_405EP) || \
defined(CONFIG_405EZ) || defined(CONFIG_440GX) || \
defined(CONFIG_440EP) || defined(CONFIG_440GR) || \
defined(CONFIG_440EPX) || defined(CONFIG_440GRX) || \
defined(CONFIG_460EX) || defined(CONFIG_460GT)
unsigned int bi_opbfreq; /* OPB clock in Hz */
int bi_iic_fast[2]; /* Use fast i2c mode */
#endif
#if defined(CONFIG_4xx)
#if defined(CONFIG_440GX) || \
defined(CONFIG_460EX) || defined(CONFIG_460GT)
int bi_phynum[4]; /* Determines phy mapping */
int bi_phymode[4]; /* Determines phy mode */
#elif defined(CONFIG_405EP) || defined(CONFIG_405EX) || defined(CONFIG_440)
int bi_phynum[2]; /* Determines phy mapping */
int bi_phymode[2]; /* Determines phy mode */
#else
int bi_phynum[1]; /* Determines phy mapping */
int bi_phymode[1]; /* Determines phy mode */
#endif
#endif /* defined(CONFIG_4xx) */
ulong bi_arch_number; /* unique id for this board */
ulong bi_boot_params; /* where this board expects params */
#ifdef CONFIG_NR_DRAM_BANKS
struct { /* RAM configuration */
phys_addr_t start;
phys_size_t size;
} bi_dram[CONFIG_NR_DRAM_BANKS];
#endif /* CONFIG_NR_DRAM_BANKS */
} bd_t;
3,前置的板级初始化操作
3.1 board_init_f
global data准备好之后,u-boot会执行前置的板级初始化动作,即board_init_f。所谓的前置的初始化动作,主要是relocation之前的初始化操作,也就是说:执行board_init_f的时候,u-boot很有可能还在只读的存储器中。
common/board_f.c
void board_init_f(ulong boot_flags)
{
gd->flags = boot_flags;
gd->have_console = 0;
if (initcall_run_list(init_sequence_f))
hang();
#if !defined(CONFIG_ARM) && !defined(CONFIG_SANDBOX) && \ !defined(CONFIG_EFI_APP) && !CONFIG_IS_ENABLED(X86_64) && \ !defined(CONFIG_ARC)
/* NOTREACHED - jump_to_copy() does not return */
hang();
#endif
}
对global data进行简单的初始化之后,调用位于init_sequence_f数组中的各种初始化API,进行各式各样的初始化动作。后面将会简单介绍一些和ARM平台有关的、和平台的移植工作有关的、比较重要的API。其它API,大家可以参考source code自行理解。
3.2 init_sequence_f
common/board_f.c
static const init_fnc_t init_sequence_f[] = {
setup_mon_len, /* 设置gd->mon_len为编译出来的u-boot.bin+bss段的大小 */
fdtdec_setup, /* 和设备树有关 */
initf_malloc, /* 初始化并设置内存池 */
log_init, /* log初始化 */
initf_bootstage, /* 用于记录board_init_f()的引导阶段 */
setup_spl_handoff,
initf_console_record, /* 平台信息记录初始化 */
arch_cpu_init, /* 空函数 */
mach_cpu_init, /* 空函数 */
initf_dm, /* 驱动模型初始化 */
arch_cpu_init_dm, /* 空函数 */
board_early_init_f, /* 设置时钟和GPIO */
timer_init, /* 定时器初始化 */
env_init, /* 找到最适合存放环境变量的地址,并初始化 */
init_baud_rate, /* 波特率初始化 */
serial_init, /* 串口初始化 */
console_init_f, /* 使能在重定位之前用的串口功能 gd->have_console = 1 */
display_options, /* 显示banner,如u-boot版本、编译时间等信息 */
display_text_info, /* 显示调试信息 */
print_cpuinfo, /* 显示cpu信息,如cpu速度 */
show_board_info, /* 显示板子信息 */
announce_dram_init, /* 准备显示DRAM大小,在u-boot启动时可以看到DRAM大小的信息 */
dram_init, /* DRAM初始化,对于本imx6ull设置dg->ram_size = 512 MiB */
setup_dest_addr, /* 设置重定位地址,gd->relocaddr = gd->ram_top */
reserve_round_4k, /* 4字节对齐,将内存指针调到下一个4 kB */
reserve_mmu, /* 为mmu区域腾出空间 */
reserve_video, /* 预留video显示内存 */
reserve_trace,
reserve_uboot, /* 预留U-Boot代码、data和bss区 */
reserve_malloc, /* 预留malloc区 */
reserve_board, /* 预留存放板子信息区 */
setup_machine, /* 板子ID,这里没有用到 */
reserve_global_data, /* 预留GD区域,栈gd->start_addr_sp指向gd段基地址*/
reserve_fdt, /* 预留设备树区域 */
reserve_bootstage,
reserve_bloblist,
reserve_arch, /* 架构相关预留区 */
reserve_stacks, /* 预留栈区,gd->start_addr_sp指向栈底基地址 */
dram_init_banksize, /* DRAM的大小初始化 */
show_dram_config, /* 显示DRAM的配置 */
display_new_sp, /* 显示新的栈地址 */
reloc_fdt, /* 和设备树有关 */
reloc_bootstage, /* 和u-boot阶段有关 */
reloc_bloblist, /* 和blob列表有关 */
setup_reloc, /* 重定位 */
NULL,
};
3.3 env_init
common/env_mmc.c
int env_init(void)
{
/* use default */
gd->env_addr = (ulong)&default_environment[0]; //default_environment[]数组存放着默认的环境变量,将默认环境变量default_environment的地址赋值给全局变量gd->env_addr
gd->env_valid = 1;
return 0;
}
include/env_default.h
static char default_environment[] = {
#else
const uchar default_environment[] = {
#endif
#ifdef CONFIG_ENV_CALLBACK_LIST_DEFAULT
ENV_CALLBACK_VAR "=" CONFIG_ENV_CALLBACK_LIST_DEFAULT "\0"
#endif
#ifdef CONFIG_ENV_FLAGS_LIST_DEFAULT
ENV_FLAGS_VAR "=" CONFIG_ENV_FLAGS_LIST_DEFAULT "\0"
#endif
#ifdef CONFIG_BOOTARGS
"bootargs=" CONFIG_BOOTARGS "\0"
#endif
#ifdef CONFIG_BOOTCOMMAND
"bootcmd=" CONFIG_BOOTCOMMAND "\0"
#endif
#ifdef CONFIG_RAMBOOTCOMMAND
"ramboot=" CONFIG_RAMBOOTCOMMAND "\0"
#endif
#ifdef CONFIG_NFSBOOTCOMMAND
"nfsboot=" CONFIG_NFSBOOTCOMMAND "\0"
#endif
#if defined(CONFIG_BOOTDELAY) && (CONFIG_BOOTDELAY >= 0)
"bootdelay=" __stringify(CONFIG_BOOTDELAY) "\0"
#endif
#if defined(CONFIG_BAUDRATE) && (CONFIG_BAUDRATE >= 0)
"baudrate=" __stringify(CONFIG_BAUDRATE) "\0"
#endif
#ifdef CONFIG_LOADS_ECHO
"loads_echo=" __stringify(CONFIG_LOADS_ECHO) "\0"
#endif
#ifdef CONFIG_ETHPRIME
"ethprime=" CONFIG_ETHPRIME "\0"
#endif
#ifdef CONFIG_IPADDR
"ipaddr=" __stringify(CONFIG_IPADDR) "\0"
#endif
#ifdef CONFIG_SERVERIP
"serverip=" __stringify(CONFIG_SERVERIP) "\0"
#endif
#ifdef CONFIG_SYS_AUTOLOAD
"autoload=" CONFIG_SYS_AUTOLOAD "\0"
#endif
#ifdef CONFIG_PREBOOT
"preboot=" CONFIG_PREBOOT "\0"
#endif
#ifdef CONFIG_ROOTPATH
"rootpath=" CONFIG_ROOTPATH "\0"
#endif
#ifdef CONFIG_GATEWAYIP
"gatewayip=" __stringify(CONFIG_GATEWAYIP) "\0"
#endif
#ifdef CONFIG_NETMASK
"netmask=" __stringify(CONFIG_NETMASK) "\0"
#endif
#ifdef CONFIG_HOSTNAME
"hostname=" __stringify(CONFIG_HOSTNAME) "\0"
#endif
#ifdef CONFIG_BOOTFILE
"bootfile=" CONFIG_BOOTFILE "\0"
#endif
#ifdef CONFIG_LOADADDR
"loadaddr=" __stringify(CONFIG_LOADADDR) "\0"
#endif
#ifdef CONFIG_CLOCKS_IN_MHZ
"clocks_in_mhz=1\0"
#endif
#if defined(CONFIG_PCI_BOOTDELAY) && (CONFIG_PCI_BOOTDELAY > 0)
"pcidelay=" __stringify(CONFIG_PCI_BOOTDELAY)"\0"
#endif
#ifdef CONFIG_ENV_VARS_UBOOT_CONFIG
"arch=" CONFIG_SYS_ARCH "\0"
#ifdef CONFIG_SYS_CPU
"cpu=" CONFIG_SYS_CPU "\0"
#endif
#ifdef CONFIG_SYS_BOARD
"board=" CONFIG_SYS_BOARD "\0"
"board_name=" CONFIG_SYS_BOARD "\0"
#endif
#ifdef CONFIG_SYS_VENDOR
"vendor=" CONFIG_SYS_VENDOR "\0"
#endif
#ifdef CONFIG_SYS_SOC
"soc=" CONFIG_SYS_SOC "\0"
#endif
#endif
#ifdef CONFIG_EXTRA_ENV_SETTINGS
CONFIG_EXTRA_ENV_SETTINGS
#endif
"\0"
#ifdef DEFAULT_ENV_INSTANCE_EMBEDDED
}
#endif
};
可以根据宏定义去配置默认的环境变量,如bootargs、bootcmd、bootdelay等。
例如修改Uboot-2017.03/configs/mx6ull_14x14_evk_defconfig 中的CONFIG_BOOTDELAY,便可以设置默认的u-boot延时时间。
3.4 dram_init
#if defined(CONFIG_ARM) || defined(CONFIG_X86) || defined(CONFIG_NDS32) || \
defined(CONFIG_MICROBLAZE) || defined(CONFIG_AVR32) || \
defined(CONFIG_SH)
dram_init, /* configure available RAM banks */
#endif
调用dram_init接口,初始化系统的DDR。dram_init应该由平台相关的代码实现。
如果DDR在SPL中已经初始化过了,则不需要重新初始化,只需要把DDR信息保存在global data中即可,例如:
gd->ram_size = …
3.5 DRAM空间的分配
DRAM初始化完成后,就可以着手规划u-boot需要使用的部分,如下图:
总结如下:
1)考虑到后续的kernel是在RAM的低端位置解压缩并执行的,为了避免麻烦,u-boot将使用DRAM的顶端地址,即gd->ram_top所代表的位置。其中gd->ram_top是由setup_dest_addr函数配置的。
2)u-boot所使用的DRAM,主要分为三类:各种特殊功能所需的空间,如log buffer、MMU page table、LCD fb buffer、trace buffer、等等;u-boot的代码段、数据段、BSS段所占用的空间(就是u-boot relocate之后的执行空间),由gd->relocaddr标示;堆栈空间,从gd->start_addr_sp处递减。
3)特殊功能以及u-boot所需空间,是由reserve_xxx系列函数保留的,具体可参考source code,这里不再详细分析。
4)reserve空间分配完毕后,堆栈紧随其后,递减即可。
3.6 reloc_fdt
reloc_fdt函数负责将设备树数据搬运到新分配的new_fdt地址中去,如下所示:
static int reloc_fdt(void)
{
#ifndef CONFIG_OF_EMBED /* 有定义 */
if (gd->flags & GD_FLG_SKIP_RELOC)
return 0;
if (gd->new_fdt) {
memcpy(gd->new_fdt, gd->fdt_blob, gd->fdt_size); //将老的设备树段拷贝到新的设备树段
gd->fdt_blob = gd->new_fdt; //将老的设备树地址更新为新的设备树地址
}
#endif
return 0;
}
3.7 setup_reloc
计算relocation有关的信息,主要是 gd->reloc_off,计算公式如下:
gd->reloc_off = gd->relocaddr - CONFIG_SYS_TEXT_BASE;
其中CONFIG_SYS_TEXT_BASE是u-boot relocation之前在(只读)memory的位置(也是编译时指定的位置),gd->relocaddr是relocation之后的位置,因此gd->reloc_off代表u-boot relocation操作之后的偏移量,后面relocation时会用到。
同时,该函数顺便把global data拷贝到了上图所示的“new global data”处,其实就是global data的relocation。
memcpy(gd->new_gd, (char *)gd, sizeof(gd_t));
4,u-boot的relocation
relocate_code函数会传入一个参数,该参数为gd->relocaddr,也就是uboot重定位的目的地址
接下来,对relocate_code函数进行分析,该函数用于重定位uboot,函数的定义在下面的汇编文件中:
uboot/arch/arm/lib/relocate.S
/*
* void relocate_code(addr_moni)
*
* This function relocates the monitor code.
*
* NOTE:
* To prevent the code below from containing references with an R_ARM_ABS32
* relocation record type, we never refer to linker-defined symbols directly.
* Instead, we declare literals which contain their relative location with
* respect to relocate_code, and at run time, add relocate_code back to them.
*/
ENTRY(relocate_code)
ldr r1, =__image_copy_start/* r1 <- SRC &__image_copy_start */ //r1保存源image开始地址
//r0 = gd->relocaddr,r4 = r0 - r1 = 0x8ff3b000 - 0x87800000 = 0x873b000
subs r4, r0, r1 /* r4 <- relocation offset */ //r4 = gd->reloc_off,保存偏移地址
beq relocate_done /* skip relocation */ //如果r0和r1相等,则不需要uboot重定位
ldr r2, =__image_copy_end /* r2 <- SRC &__image_copy_end */ //r2保存源image结束地址
copy_loop:
ldmia r1!, {r10-r11} /* copy from source address [r1] */ //拷贝uboot代码到r10和r11
stmia r0!, {r10-r11} /* copy to target address [r0] */ //将uboot代码写到目的地址
cmp r1, r2 /* until source end address [r2] */ //判断uboot是否拷贝完成
blo copy_loop //循环拷贝
/*
* fix .rel.dyn relocations //修复.rel.dyn段
*/
ldr r2, =__rel_dyn_start /* r2 <- SRC &__rel_dyn_start */
ldr r3, =__rel_dyn_end /* r3 <- SRC &__rel_dyn_end */
fixloop:
ldmia r2!, {r0-r1} /* (r0,r1) <- (SRC location,fixup) */
and r1, r1, #0xff
cmp r1, #23 /* relative fixup? */
bne fixnext
/* relative fix: increase location by offset */
add r0, r0, r4
ldr r1, [r0]
add r1, r1, r4
str r1, [r0]
fixnext:
cmp r2, r3
blo fixloop
relocate_done:
ENDPROC(relocate_code)
函数传入的参数为r0 = 0x8ff3b000,uboot重定位的目的地址,函数进来后,将__image_copy_start的数值赋值给r1,也就是uboot复制开始地址,从表格可以知道r1 = 0x87800000,接下来进行一个减法操作,此时r4将保存着uboot的偏移量,也就是r4 = gd->reloc_off。
接下来会进行一个判断,判断uboot重定位的目的地址是否和uboot源地址是否相等,如果相等的话,表示不需要重定位,跳到relocate_done处,继续运行,如果不相等的话,则是需要进行重定位。
将__image_copy_end的值赋值给r2,也就是uboot复制结束的地址,从表格中可以知道,此时r2 = 0x87868960,接下来,开始进行uboot代码的拷贝,进入到copy_loop循环,从r1,也就是__image_copy_start开始,读取uboot代码到r10和r11寄存器,一次就拷贝两个32位的数据,r1自动更新,保存下一个需要拷贝的地址,然后将r10和r11里面的数据,写到r0保存的地址,也就是uboot重定位的目的地址,r0自动更新。
接下来,比较r1和r2是否相等,也就是判断uboot代码是否拷贝完成,如果没完成的话,跳转到copy_loop处,继续循环,直到uboot拷贝完成。
函数relocate_code()的第二部分是修复.rel.dyn的定位问题,.rel.dyn段存放了.text段中需要重定位地址(也就是搬移了__image_copy_start~__image_copy_end的代码后,需要对其中存的绝对地址进行更新,即绝对地址+偏移地址)的集合,uboot重定位就是uboot将自身拷贝到DRAM的另外一个地址继续运行(DRAM的高地址),一个可执行的bin文件,它的链接地址和运行地址一定要相等,也就是链接到哪个地址,运行之前就需要拷贝到哪个地址中进行运行,在前面的重定位后,运行地址和链接地址就不一样了,这样在进行寻址就会出问题,对.rel.dyn的定位问题进行修复,就是为了解决该问题。
下面,使用一个简单的例子进行分析:
先在新适配的板级文件中添加测试代码,文件如下:
uboot/board/freescale/mx6ul_comp6ul_nand/mx6ul_comp6ul_nand.c
添加的内容如下:
static int rel_a = 0;
void rel_test(void)
{
rel_a = 100;
printf("rel_test\n");
}
/* 在board_init()函数中调用rel_test()函数 */
int board_init(void)
{
...
...
rel_test();
return 0;
}
接下来对uboot源码重新编译,并将u-boot文件进行反汇编分析,使用命令如下:
$ cd uboot
$ make
$ arm-linux-gnueabihf-objdump -D -m arm u-boot > u-boot.dis
然后,打开u-boot.dis文件,并在文件中,找到rel_a变量、rel_test函数和board_init函数相关的汇编代码,如下所示:
/* rel_test寻址分析 */8780424c <rel_test>:8780424c: e59f300c ldr r3, [pc, #12] ; 87804260 <rel_test+0x14>87804250: e3a02064 mov r2, #100 ; 0x6487804254: e59f0008 ldr r0, [pc, #8] ; 87804264 <rel_test+0x18>87804258: e5832000 str r2, [r3]8780425c: ea00f1a9 b 87840908 <printf>87804260: 87868994 ; <UNDEFINED> instruction: 0x8786899487804264: 878494ce strhi r9, [r4, lr, asr #9]......878043e4: e59f2038 ldr r2, [pc, #56] ; 87804424 <board_init+0x1bc>878043e8: e5923068 ldr r3, [r2, #104] ; 0x68878043ec: e3833030 orr r3, r3, #48 ; 0x30878043f0: e5823068 str r3, [r2, #104] ; 0x68878043f4: ebffff94 bl 8780424c <rel_test>......87868994 <rel_a>:87868994: 00000000 andeq r0, r0, r0
在0x878043f4处,也就是board_init()函数中调用了rel_test()函数,在汇编代码中,可以看到是使用了bl指令,而bl指令是相对寻址的(pc + offset),因此,可以知道,uboot中的函数调用时与绝对地址无关。
接下来,分析rel_test()函数对全局变量rel_a的调用过程,在0x8780424c处开始,先设置r3的值为pc + 12地址处的值,由于ARM流水线的原因,当前pc的值为当前的地址加8,所以pc = 0x8780424c + 8 = 0x87804254,因此r3 = pc + 12 = 0x87804254 + 12 = 0x87804260,从反汇编的代码中可以看到0x87804260处的值为0x87868994,所以,最终r3的值为0x87868994,而且从反汇编代码中可以看到0x87868994就是变量rel_a的地址,rel_test()函数继续执行,将100赋值到r2寄存器,然后通过str指令,将r2的值写到r3的地址处,也就是将0x87868994地址处的值赋值100,就是函数中调用的rel_a = 100;语句。
从上面的分析,可以知道rel_a = 100;的执行过程为:
-
在函数rel_test()的末尾处有一个地址为0x87804260的内存空间,此内存空间中保存着变量rel_a的地址;
-
函数rel_test()想要访问变量rel_a,首先需要访问0x87804260内存空间获取变量rel_a的地址,而访问0x87804260是通过偏移来访问的,与位置无关的操作;
-
通过访问0x87804260获取变量rel_a的地址,然后对变量rel_a进行赋值操作;
-
函数rel_test()对变量rel_a的访问并没有直接进行,而是通过一个偏移地址0x87804260,找到变量rel_a真正的地址,然后才对其操作,偏移地址的专业术语为Label。
从分析中,可以知道,该偏移地址就是uboot重定位运行是否会出错的原因,在上面可以知道,uboot重定位的偏移量为0x8ff3b000,将上面给出的反汇编代码进行重定位后,也就是加上地址偏移量0x873b000后,如下:
/* rel_test寻址分析(重定位后) */8ff3f24c <rel_test>:8ff3f24c: e59f300c ldr r3, [pc, #12]8ff3f250: e3a02064 mov r2, #100 ; 0x648ff3f254: e59f0008 ldr r0, [pc, #8]8ff3f258: e5832000 str r2, [r3]8ff3f25c: ea00f1a9 b 87840908 <printf>8ff3f260: 87868994 ; <UNDEFINED> instruction: 0x878689948ff3f264: 878494ce strhi r9, [r4, lr, asr #9]......8ff3f3e4: e59f2038 ldr r2, [pc, #56]8ff3f3e8: e5923068 ldr r3, [r2, #104] ; 0x688ff3f3ec: e3833030 orr r3, r3, #48 ; 0x308ff3f3f0: e5823068 str r3, [r2, #104] ; 0x688ff3f3f4: ebffff94 bl 8780424c <rel_test>......8ffa3994 <rel_a>:8ffa3994: 00000000 andeq r0, r0, r0
函数rel_test()假设调用后对rel_a变量进行访问,分析和上面一样,先通过pc和偏移量找到0x8ff3f260,该地址是重定位后的地址,可此时该地址的值为0x87868994,也就是没有重定位后的rel_a变量的地址,但是从上面可以知道,uboot重定位后,rel_a变量的新地址为0x8ffa3994,因此,我们可以知道,如果uboot重定位后,要想正常访问rel_a变量,必须要将0x8ff3f260地址中的值0x87868994加上偏移量0x873b000,才能保证uboot重定位后,能正常访问到rel_a变量,将.rel.dyn段进行重定位修复,就是为了解决链接地址和运行地址不一致的问题。
在uboot中,对于重定位后链接地址与运行地址不一致的解决办法就是使用位置无关码,在uboot编译使用ld链接的时候使用参数"-pie"可生成与位置无关的可执行程序,使用该参数后,会生成一个.rel.dyn段,uboot则是靠该段去修复重定位后产生的问题的,在uboot的反汇编文件中,有.rel.dyn段代码,如下:
Disassembly of section .rel.dyn:
87868988 <__rel_dyn_end-0x91a0>:
87868988: 87800020 strhi r0, [r0, r0, lsr #32]
8786898c: 00000017 andeq r0, r0, r7, lsl r0
87868990: 87800024 strhi r0, [r0, r4, lsr #32]
87868994: 00000017 andeq r0, r0, r7, lsl r0
...
...
87868f08: 87804260 strhi r4, [r0, r0, ror #4]
87868f0c: 00000017 andeq r0, r0, r7, lsl r0
87868f10: 87804264 strhi r4, [r0, r4, ror #4]
87868f14: 00000017 andeq r0, r0, r7, lsl r0
...
...
rel.dyn段的格式如下,类似下面的两行就是一组:
87868f08: 87804260 strhi r4, [r0, r0, ror #4]
87868f0c: 00000017 andeq r0, r0, r7, lsl r0
也就是两个4字节数据为一组,其中高4字节是Label地址的标识0x17,低4字节就是Label的地址,首先会判断Label地址标识是否正确,也就是判断高4字节是否为0x17,如果是的话,低4字节就是Label地址,在上面给出的两行代码中就是存放rel_a变量地址的Label,0x87868f0c的值为0x17,说明低4字节是Label地址,也就是0x87804260,需要将0x87804260 + offset处的值改为重定位后的变量rel_a地址。
在对.rel.dyn段以及Label的相关概念有一定的了解后,接下来,我们分析函数relocate_code()的第二部分代码,修复.rel.dyn段重定位问题,代码如下:
/*
* fix .rel.dyn relocations //修复.rel.dyn段
*/
ldr r2, =__rel_dyn_start /* r2 <- SRC &__rel_dyn_start */
ldr r3, =__rel_dyn_end /* r3 <- SRC &__rel_dyn_end */
fixloop:
ldmia r2!, {r0-r1} /* (r0,r1) <- (SRC location,fixup) */
and r1, r1, #0xff
cmp r1, #23 /* relative fixup? */
bne fixnext
/* relative fix: increase location by offset */
add r0, r0, r4
ldr r1, [r0]
add r1, r1, r4
str r1, [r0]
fixnext:
cmp r2, r3
blo fixloop
relocate_code()函数第二部分代码调用后,将__rel_dyn_start的值赋给r2,也就是r2中保存着.rel.dyn段的起始地址,然后将__rel_dyn_end的值赋给r3,也就是r3中保存着.rel.dyn段的结束地址。
接下来,进入到fixloop处执行,使用ldmia指令,从.rel.dyn段起始地址开始,每次读取两个4字节数据存放到r0和r1寄存器中,其中r0存放低4字节的数据,也就是Label的地址,r1存放高4字节的数据,也就是Label的标识,然后将r1的值与0xff相与,取r1值的低8位,并将结果保存到r1中,接下来,判断r1中的值是否等于23(0x17),如果r1不等于23的话,也就说明不是描述Label,跳到fixnext处循环执行,直到r2和r3相等,也就是遍历完.rel.dyn段。
如果r1的值等于23(0x17)的话,继续执行,r0保存着Label地址,r4保存着uboot重定位的偏移,因此,r0 + r4就得到了重定位后的Label地址,也就是上面分析的0x87804260 + 0x873b000 = 0x8ff3f260 = r0,此时r0已经保存着重定位后的Label地址,然后使用ldr指令,读取r0中保存的值到r1中,也就是Label地址所保存的rel_a变量的地址,但是此时,该rel_a变量的地址仍然是重定位之前的地址0x87868994,所以,此时r1 = 0x87868994,接下来,使用add指令,将r1中的值加上r4,也就是加上uboot重定位偏移量,所以,此时r1 = 0x87868994 + 0x873b000 = 0x8ffa3994,这不就是前面分析的uboot重定位后的rel_a变量的地址吗?接下来使用str指令,将r1中的值写入到r0保存的地址中,也就是将Label地址中的值设置为0x8ffa3994,就是重定位后rel_a变量的新的地址。
比较r2和r3的值,查看.rel.dyn段重定位修复是否完成,循环直到完成,才能执行完relocate_code()函数。
第二部分执行完成后,就解决了.rel.dyn段的重定位问题,从而解决了uboot重定位后,链接地址和运行地址不一致的问题。
二,u-boot启动第二阶段
relocate完成之后,真正的C运行环境才算建立了起来,接下来会执行“后置的板级初始化操作”,即board_init_r函数。board_init_r和board_init_f的设计思路基本一样,也有一个很长的初始化序列----init_sequence_r,该序列中包含如下的初始化函数:
common/board_r.c
1)initr_trace,初始化并使能u-boot的tracing system,涉及的配置项有CONFIG_TRACE。
2)initr_reloc,设置relocation完成的标志。
3)initr_caches,使能dcache、icache等,涉及的配置项有CONFIG_ARM。
4)initr_malloc,malloc有关的初始化。
5)initr_dm,relocate之后,重新初始化DM,涉及的配置项有CONFIG_DM。
6)board_init,具体的板级初始化,需要由board代码根据需要实现,涉及的配置项有CONFIG_ARM。
7)set_cpu_clk_info,Initialize clock framework,涉及的配置项有CONFIG_CLOCKS。
8)initr_serial,重新初始化串口(不太明白什么意思)。
9)initr_announce,宣布已经在RAM中执行,会打印relocate后的地址。
10)board_early_init_r,由板级代码实现,涉及的配置项有CONFIG_BOARD_EARLY_INIT_R。
11)arch_early_init_r,由arch代码实现,涉及的配置项有CONFIG_ARCH_EARLY_INIT_R。
12)power_init_board,板级的power init代码,由板级代码实现,例如hold住power。
13)initr_flash、initr_nand、initr_onenand、initr_mmc、initr_dataflash,各种flash设备的初始化。
14)initr_env,环境变量有关的初始化。
15)initr_secondary_cpu,初始化其它的CPU core。
16)stdio_add_devices,各种输入输出设备的初始化,如LCD driver等。
17)interrupt_init,中断有关的初始化。
18)initr_enable_interrupts,使能系统的中断,涉及的配置项有CONFIG_ARM(ARM平台u-boot实在开中断的情况下运行的)。
19)initr_status_led,状态指示LED的初始化,涉及的配置项有CONFIG_STATUS_LED、STATUS_LED_BOOT。
20)initr_ethaddr,Ethernet的初始化,涉及的配置项有CONFIG_CMD_NET。
21)board_late_init,由板级代码实现,涉及的配置项有CONFIG_BOARD_LATE_INIT。
22)等等…
23)run_main_loop/main_loop,执行到main_loop,开始命令行操作。
三,u-boot加载内核阶段
... ...
参考链接:
27. Uboot启动流程分析——下 — [野火]嵌入式Linux镜像构建与部署——基于LubanCat-i.MX6ULL开发板 文档
u-boot启动流程分析(2)_板级(board)部分
https://www.cnblogs.com/god-of-death/p/16971688.html