基于IMX6Q的uboot启动流程分析(1):uboot入口函数
基于IMX6Q的uboot启动流程分析(2):_main函数之board_init_f
基于IMX6Q的uboot启动流程分析(3):_main函数之relocate_code与board_init_r
基于IMX6Q的uboot启动流程分析(4):uboot中的串口设备
上一节内容中_start
函数最后调用_main
函数,其位置在arch/arm/lib/crt0.S
文件中。
_main
函数的功能已经在文件中进行英文注释,先看看_main
函数实现的功能有哪些。从注释中,我们可以大概知道_main
函数的执行顺序为:
global data
)结构,两者都是位于可以使用的RAM(SRAM,locked cache…)中,在调用board_init_f()函数前,GD应该被清0;_main
函数的内容为:
118
119 #if ! defined(CONFIG_SPL_BUILD)
120
121 /*
122 * Set up intermediate environment (new sp and gd) and call
123 * relocate_code(addr_moni). Trick here is that we'll return
124 * 'here' but relocated.
125 */
126
127 ldr r0, [r9, #GD_START_ADDR_SP] /* sp = gd->start_addr_sp */
128 bic r0, r0, #7 /* 8-byte alignment for ABI compliance */
129 mov sp, r0
130 ldr r9, [r9, #GD_NEW_GD] /* r9 <- gd->new_gd */
131
132 adr lr, here
133 ldr r0, [r9, #GD_RELOC_OFF] /* r0 = gd->reloc_off */
134 add lr, lr, r0
135 #if defined(CONFIG_CPU_V7M)
136 orr lr, #1 /* As required by Thumb-only */
137 #endif
138 ldr r0, [r9, #GD_RELOCADDR] /* r0 = gd->relocaddr */
139 b relocate_code
140 here:
141 /*
142 * now relocate vectors
143 */
144
145 bl relocate_vectors
146
147 /* Set up final (full) environment */
148
149 bl c_runtime_cpu_setup /* we still call old routine here */
150 #endif
151 #if !defined(CONFIG_SPL_BUILD) || CONFIG_IS_ENABLED(FRAMEWORK)
152
153 #if !defined(CONFIG_SPL_BUILD) || !defined(CONFIG_SPL_EARLY_BSS)
154 CLEAR_BSS
155 #endif
156
157 # ifdef CONFIG_SPL_BUILD
158 /* Use a DRAM stack for the rest of SPL, if requested */
159 bl spl_relocate_stack_gd
160 cmp r0, #0
161 movne sp, r0
162 movne r9, r0
163 # endif
164
165 #if ! defined(CONFIG_SPL_BUILD)
166 bl coloured_LED_init
167 bl red_led_on
168 #endif
169 /* call board_init_r(gd_t *id, ulong dest_addr) */
170 mov r0, r9 /* gd_t */
171 ldr r1, [r9, #GD_RELOCADDR] /* dest_addr */
172 /* call board_init_r */
173 #if CONFIG_IS_ENABLED(SYS_THUMB_BUILD)
174 ldr lr, =board_init_r /* this is auto-relocated! */
175 bx lr
176 #else
177 ldr pc, =board_init_r /* this is auto-relocated! */
178 #endif
179 /* we should not return here. */
180 #endif
181
182 ENDPROC(_main)
_main函数的实现比较复杂,需要对其分部分进行分析:
下面将详细介绍。
在调用board_init_f函数前,要设置初始化C运行环境,这部分代码为:
91 ENTRY(_main)
92
93 /*
94 * Set up initial C runtime environment and call board_init_f(0).
95 */
96
97 #if defined(CONFIG_TPL_BUILD) && defined(CONFIG_TPL_NEEDS_SEPARATE_STACK)
98 ldr r0, =(CONFIG_TPL_STACK)
99 #elif defined(CONFIG_SPL_BUILD) && defined(CONFIG_SPL_STACK)
100 ldr r0, =(CONFIG_SPL_STACK)
101 #else
102 ldr r0, =(CONFIG_SYS_INIT_SP_ADDR)
103 #endif
104 bic r0, r0, #7 /* 8-byte alignment for ABI compliance */
105 mov sp, r0
106 bl board_init_f_alloc_reserve
107 mov sp, r0
108 /* set up gd here, outside any C code */
109 mov r9, r0
110 bl board_init_f_init_reserve
111
112 #if defined(CONFIG_SPL_BUILD) && defined(CONFIG_SPL_EARLY_BSS)
113 CLEAR_BSS
114 #endif
115
116 mov r0, #0
117 bl board_init_f
第102-105行,将sp设置为CONFIG_SYS_INIT_SP_ADDR
,即sp = 0x0093FF00。
第106行,调用board_init_f_alloc_reserve
函数,留出早期的malloc内存区域和global_data结构体内存区域。其函数定义在common/init/board_init.c
,其内容为:
ulong board_init_f_alloc_reserve(ulong top) //top = 0x0093FF00
{
/* Reserve early malloc arena */
#if CONFIG_VAL(SYS_MALLOC_F_LEN)
top -= CONFIG_VAL(SYS_MALLOC_F_LEN); //top = 0x0093FF00 - 0x2000 = 0x0093DF00
#endif
/* LAST : reserve GD (rounded up to a multiple of 16 bytes) */
top = rounddown(top-sizeof(struct global_data), 16);
//top = top - sizeof(struct global_data),16字节对齐
//top = 0x0093DF00 - 248 - 8 = 0x0093DE00
return top;
}
board_init_f_alloc_reserve()函数是具有返回值,将top=0x0093DE00返回,最终的sp= 0x0093DE00。
补充:
对于ARM32的CPU,uboot定义了一个指向gd_t类型的gd指针,gd是一个全局变量,存放在r9寄存器,gd_t的类型定义在文件
include/asm-generic/global_data.h
中,其实就是struct global_data
。
第109行,将r0赋值给r9,其实就是设置gd指针的值为0x0093DE00,也就是指向在OCRAM中分配的struct global_data结构体的首地址。
第110行,调用board_init_f_init_reserve
函数,参数为r0寄存器的值,也就是struct global_data结构体首地址0x0093DE00。函数的内容为:
void board_init_f_init_reserve(ulong base)
{
struct global_data *gd_ptr;
/*
* clear GD entirely and set it up.
* Use gd_ptr, as gd may not be properly set yet.
*/
gd_ptr = (struct global_data *)base; //①:获取global_data结构体首地址
/* zero the area */
memset(gd_ptr, '\0', sizeof(*gd)); //②:将global_data结构体清0操作
/* set GD unless architecture did it already */
#if !defined(CONFIG_ARM)
arch_setup_gd(gd_ptr);
#endif
if (CONFIG_IS_ENABLED(SYS_REPORT_STACK_F_USAGE))
board_init_f_init_stack_protection_addr(base);
/* next alloc will be higher by one GD plus 16-byte alignment */
base += roundup(sizeof(struct global_data), 16);
//③:指向early_alloc的起始地址0x0093DF00
/*
* record early malloc arena start.
* Use gd as it is now properly set for all architectures.
*/
#if CONFIG_VAL(SYS_MALLOC_F_LEN)
/* go down one 'early malloc arena' */
gd->malloc_base = base; //④:设置gd->malloc_base指向early malloc首地址
#endif
if (CONFIG_IS_ENABLED(SYS_REPORT_STACK_F_USAGE))
board_init_f_init_stack_protection();
}
在上面的代码中可以看出,该函数主要是将OCRAM中分配的struct global_data
结构体区域进行清0操作,另外设置gd->malloc_base
成员的值为early malloc区域的首地址。
第116-117行,设置r0寄存器的值为0,然后调用board_init_f函数,传入的参数为r0寄存器的值0,下面详细介绍board_init_f函数。
board_init_f
函数定义在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();
...
}
该函数调用后,首先会设置gd->flags这个标志位,然后将gd结构体中的have_console成员变量设置为0,表示此时并没有console。
该函数的重点为initcall_run_list(init_sequence_f)
,也就是初始化init_sequence_f
序列。init_sequence_f
包含了一系列的初始化函数,定义同样在common/board_f.c
文件中,其内容为:
810 static const init_fnc_t init_sequence_f[] = { /* 初始化序列函数数组 */
811 setup_mon_len,
812 #ifdef CONFIG_OF_CONTROL
813 fdtdec_setup,
814 #endif
815 #ifdef CONFIG_TRACE_EARLY
816 trace_early_init,
817 #endif
818 initf_malloc,
819 log_init,
820 initf_bootstage, /* uses its own timer, so does not need DM */
821 #ifdef CONFIG_BLOBLIST
822 bloblist_init,
823 #endif
824 setup_spl_handoff,
825 #if defined(CONFIG_CONSOLE_RECORD_INIT_F)
826 console_record_init,
827 #endif
828 #if defined(CONFIG_HAVE_FSP)
829 arch_fsp_init,
830 #endif
831 arch_cpu_init, /* basic arch cpu dependent setup */
832 mach_cpu_init, /* SoC/machine dependent CPU setup */
833 initf_dm,
834 arch_cpu_init_dm,
835 #if defined(CONFIG_BOARD_EARLY_INIT_F)
836 board_early_init_f,
837 #endif
838 #if defined(CONFIG_PPC) || defined(CONFIG_SYS_FSL_CLK) || defined(CONFIG_M68K)
839 /* get CPU and bus clocks according to the environment variable */
840 get_clocks, /* get CPU and bus clocks (etc.) */
841 #endif
842 #if !defined(CONFIG_M68K)
843 timer_init, /* initialize timer */
844 #endif
845 #if defined(CONFIG_BOARD_POSTCLK_INIT)
846 board_postclk_init,
847 #endif
848 env_init, /* initialize environment */
849 init_baud_rate, /* initialze baudrate settings */
850 #ifndef CONFIG_ANDROID_AUTO_SUPPORT
851 serial_init, /* serial communications setup */
852 #endif
853 console_init_f, /* stage 1 init of console */
854 display_options, /* say that we are here */
855 display_text_info, /* show debugging info if required */
856 checkcpu,
857 #if defined(CONFIG_SYSRESET)
858 print_resetinfo,
859 #endif
860 #if defined(CONFIG_DISPLAY_CPUINFO)
861 print_cpuinfo, /* display cpu info (and speed) */
862 #endif
863 #if defined(CONFIG_DTB_RESELECT)
864 embedded_dtb_select,
865 #endif
866 #if defined(CONFIG_DISPLAY_BOARDINFO)
867 show_board_info,
868 #endif
869 INIT_FUNC_WATCHDOG_INIT
870 #if defined(CONFIG_MISC_INIT_F)
871 misc_init_f,
872 #endif
873 INIT_FUNC_WATCHDOG_RESET
874 #if defined(CONFIG_SYS_I2C)
875 init_func_i2c,
876 #endif
877 #if defined(CONFIG_VID) && !defined(CONFIG_SPL)
878 init_func_vid,
879 #endif
880 announce_dram_init,
881 dram_init, /* configure available RAM banks */
882 #ifdef CONFIG_POST
883 post_init_f,
884 #endif
885 INIT_FUNC_WATCHDOG_RESET
886 #if defined(CONFIG_SYS_DRAM_TEST)
887 testdram,
888 #endif /* CONFIG_SYS_DRAM_TEST */
889 INIT_FUNC_WATCHDOG_RESET
890
891 #ifdef CONFIG_POST
892 init_post,
893 #endif
894 INIT_FUNC_WATCHDOG_RESET
895 /*
896 * Now that we have DRAM mapped and working, we can
897 * relocate the code and continue running from DRAM.
898 *
899 * Reserve memory at end of RAM for (top down in that order):
900 * - area that won't get touched by U-Boot and Linux (optional)
901 * - kernel log buffer
902 * - protected RAM
903 * - LCD framebuffer
904 * - monitor code
905 * - board info struct
906 */
907 setup_dest_addr,
908 #ifdef CONFIG_OF_BOARD_FIXUP
909 fix_fdt,
910 #endif
911 #ifdef CONFIG_PRAM
912 reserve_pram,
913 #endif
914 reserve_round_4k,
915 arch_reserve_mmu,
916 reserve_video,
917 reserve_trace,
918 reserve_uboot,
919 reserve_malloc,
920 reserve_board,
921 reserve_global_data,
922 reserve_fdt,
923 reserve_bootstage,
924 reserve_bloblist,
925 reserve_arch,
926 reserve_stacks,
927 dram_init_banksize,
928 show_dram_config,
929 INIT_FUNC_WATCHDOG_RESET
930 setup_bdinfo,
931 display_new_sp,
932 INIT_FUNC_WATCHDOG_RESET
933 reloc_fdt,
934 reloc_bootstage,
935 reloc_bloblist,
936 setup_reloc,
937 #if defined(CONFIG_X86) || defined(CONFIG_ARC)
938 copy_uboot_to_ram,
939 do_elf_reloc_fixups,
940 #endif
941 clear_bss,
942 #if !defined(CONFIG_ARM) && !defined(CONFIG_SANDBOX) && \
943 !CONFIG_IS_ENABLED(X86_64)
944 jump_to_copy,
945 #endif
946 NULL,
947 };
第811行,setup_mon_len
,该函数用来设置gd结构中的mon_len成员变量,大小为__bss_end -_start
,用于保存uboot代码的长度。
第818行,initf_malloc
,该函数用来设置gd结构中与malloc相关的变量,定义在common/dlmalloc.c
,内容为:
int initf_malloc(void)
{
#if CONFIG_VAL(SYS_MALLOC_F_LEN)
assert(gd->malloc_base); /* Set up by crt0.S */
gd->malloc_limit = CONFIG_VAL(SYS_MALLOC_F_LEN);/* malloc的大小0x2000 */
gd->malloc_ptr = 0;
#endif
return 0;
}
第831-832行,arch_cpu_init
、mach_cpu_init
初始化与cpu相关的内容。
第833行,initf_dm
,初始化一些与驱动模型相关的东西。
第836行,board_early_init_f
,板子相关的早期的一些初始化设置,对于IMX6Q,初始化uart的IO配置与display相关。定义在board/freescale/mx6sabresd/mx6sabresd.c
文件中,其内容为:
int board_early_init_f(void)
{
setup_iomux_uart(); //串口初始化
#if defined(CONFIG_VIDEO_IPUV3)
setup_display(); //显示初始化,比如hdmi
#endif
return 0;
}
第840行,get_clocks
,该函数用于获取芯片内某些外设时钟,赋值给gd->arch.sdhc_clk
变量,也就是SD卡时钟。
第843行,timer_init
,该函数来初始化ARM内核中的定时器,赋值gd->arch.tbl
,gd->arch.tbu
变量。定义在arch/arm/mach-imx/timer.c
文件。
第848行,env_init
,该函数用来初始化gd结构中和env相关的成员,设置gd结构中的env_addr成员,也就是环境变量的保存地址。定义在env/env.c
文件中。
第849行,init_baud_rate
,该函数用于初始化debug调试串口的波特率,设置gd结构体中的baudrate成员,该值是从"baudrate"环境变量中读取的。函数内容为:
static int init_baud_rate(void)
{
gd->baudrate = env_get_ulong("baudrate", 10, CONFIG_BAUDRATE);
return 0;
}
第851行,serial_init
,该函数用于对串口进行初始化,会调用串口驱动设备中注册的start()函数完成初始化。定义在drivers/serial/serial.c
文件中,其内容为:
int serial_init(void)
{
gd->flags |= GD_FLG_SERIAL_READY;
return get_current()->start();
}
IMX6Q的串口驱动位置在drivers/serial/serial_mxc.c
,驱动结构体为:
static struct serial_device mxc_serial_drv = {
.name = "mxc_serial",
.start = mxc_serial_init, //被serial_init调用
.stop = NULL,
.setbrg = mxc_serial_setbrg,
.putc = mxc_serial_putc,
.puts = default_serial_puts,
.getc = mxc_serial_getc,
.tstc = mxc_serial_tstc,
};
第853行,console_init_f
,该函数将会设置gd结构体的have_console成员为1,标志则此时已经有了console,也就是上面已经初始化好的串口终端,至此可以正常使用串口打印信息。
第854行,display_options
,该函数用来打印输出uboot版本的字符串。
第855行,display_text_info
,该函数用于显示一些debug调试信息,需要在板子的配置文件中定义宏DEBUG,才会开启uboot的调试信息输出。
第861行,print_cpuinfo
,该函数输出板子上的CPU的相关信息。
第867行,show_board_info
,该函数用于显示板子的相关信息,若定义了宏CONFIG_DISPLAY_BOARDINFO。
第875行,init_func_i2c
,该函数用于初始化I2C接口,内容为:
static int init_func_i2c(void)
{
puts("I2C: ");
i2c_init_all();
puts("ready\n");
return 0;
}
第880行,announce_dram_init
,该函数只是输出"DRAM: "字符串。
第881行,dram_init
,该函数并没有初始化DRAM,只是设置gd结构体中ram_size成员变量,也就是DRAM的大小,ram_size=1G。
第907行,setup_dest_addr
,该函数用来设置目的地址,设置gd结构体中的ram_size成员、ram_top成员和relocaddr成员。其中ram_size表示DRAM的大小,ram_top表示DRAM的最终地址,relocaddr表示uboot重定位的地址。其代码内容为:
static int setup_dest_addr(void)
{
debug("Monitor len: %08lX\n", gd->mon_len);
debug("Ram size: %08lX\n", (ulong)gd->ram_size);
...
#ifdef CONFIG_SYS_SDRAM_BASE
gd->ram_base = CONFIG_SYS_SDRAM_BASE; //gd->ram_base = 0x10000000 芯片的DRAM基址
#endif
gd->ram_top = gd->ram_base + get_effective_memsize();
//gd->ram_top = gd->ram_base + gd->ram_size
// = 0x10000000 + 0x40000000 = 0x50000000
gd->ram_top = board_get_usable_ram_top(gd->mon_len);
gd->relocaddr = gd->ram_top;
//gd->relocaddr = 0x50000000
debug("Ram top: %08lX\n", (ulong)gd->ram_top);
...
return 0;
}
第919行,reserve_round_4k
,该函数是gd结构体中的relocaddr地址做4KB字节对齐,在setup_dest_addr
函数调用后,relocaddr的值被设置为0x50000000,已经是4KB字节对齐了,因此,该函数调用后,值不变,还是0x50000000。
第920行,arch_reserve_mmu
,该函数调用arm_reserve_mmu
函数,用于留出MMU的TLB表位置,留出该块内存后,对relocaddr地址做64KB字节对齐。函数内容为:
__weak int arm_reserve_mmu(void)
{
#if !(CONFIG_IS_ENABLED(SYS_ICACHE_OFF) && CONFIG_IS_ENABLED(SYS_DCACHE_OFF))
/* reserve TLB table */
gd->arch.tlb_size = PGTABLE_SIZE; //gd->arch.tlb_size = 0x4000
gd->relocaddr -= gd->arch.tlb_size;
/* round down to next 64 kB limit */
gd->relocaddr &= ~(0x10000 - 1); //gd->relocaddr = 0x4fff0000
gd->arch.tlb_addr = gd->relocaddr;
debug("TLB table from %08lx to %08lx\n", gd->arch.tlb_addr,
gd->arch.tlb_addr + gd->arch.tlb_size);
#ifdef CONFIG_SYS_MEM_RESERVE_SECURE
/*
* Record allocated tlb_addr in case gd->tlb_addr to be overwritten
* with location within secure ram.
*/
gd->arch.tlb_allocated = gd->arch.tlb_addr;
#endif
#endif
return 0;
}
第916-926行,如上述相同,为保留gd变量的成员,在DRAM中预留空间并赋值,不再绘图。
函数
reserve_uboot
用于留出重定位后uboot代码所占用的内存位置,uboot所占用的内存位置空间由gd结构体中mon_len成员变量指定,再4KB字节对齐。赋值gd->relocaddr变量,为uboot重定向地址,gd->relocaddr = 0x4EF78000函数
reserve_malloc
用于留出 malloc 区域 ,大小由TOTAL_MALLOC_LEN决定。函数
reserve_board
用于描述Board的一些信息。函数
reserve_global_data
用于留出gd_t结构体的内存区域,分配的内存大小为248Bytes,分配完成后,并对gd结构体中的new_gd成员变量重新赋值,表示新的gd_t结构体的内存地址。函数
reserve_fdt
用于留出设备树的内存区域。函数
reserve_stacks
用于留出栈区域的内存大小,大小为16Bytes,内存分配后,给gd->irq_sp
赋值,即irq预留空间。由文件arch/arm/lib/stack.c
函数arch_reserve_stacks
实现。
第927行,dram_init_banksize
,完成了内存栈的分配的后,该函数设置DRAM的一些配置信息,主要是设置DRAM的起始地址和大小,函数内容为:
__weak int dram_init_banksize(void)
{
gd->bd->bi_dram[0].start = gd->ram_base; //DRAM起始地址
gd->bd->bi_dram[0].size = get_effective_memsize(); //DRAM大小
return 0;
}
第927行,show_dram_config
,用于显示DRAM配置信息。该函数调用后,将会将的DRAM的大小以字符串的形式输出,也就是输出"1GB"。
第930行,display_new_sp
,内存预留后,该函数显示新的sp指针的值,即gd->start_addr_sp
的值,通过打印或计算得知gd->start_addr_sp = 0x4DF68F50。
第932行,reloc_fdt
,重定位设备树。函数内容为:
static int reloc_fdt(void)
{
if (!IS_ENABLED(CONFIG_OF_EMBED)) {
if (gd->flags & GD_FLG_SKIP_RELOC)
return 0;
if (gd->new_fdt) {
memcpy(gd->new_fdt, gd->fdt_blob,
fdt_totalsize(gd->fdt_blob)); //将设备树拷贝到DRAM
gd->fdt_blob = gd->new_fdt; //重定位到DRAM
}
}
return 0;
}
第935行,setup_reloc
,该函数设置gd结构体中一些成员变量,是和重定位相关。函数内容如下:
static int setup_reloc(void)
{
if (gd->flags & GD_FLG_SKIP_RELOC) {
debug("Skipping relocation due to flag\n");
return 0;
}
#ifdef CONFIG_SYS_TEXT_BASE
#ifdef ARM
gd->reloc_off = gd->relocaddr - (unsigned long)__image_copy_start;
#elif defined(CONFIG_M68K)
/*
* On all ColdFire arch cpu, monitor code starts always
* just after the default vector table location, so at 0x400
*/
gd->reloc_off = gd->relocaddr - (CONFIG_SYS_TEXT_BASE + 0x400);
#elif !defined(CONFIG_SANDBOX)
gd->reloc_off = gd->relocaddr - CONFIG_SYS_TEXT_BASE;
#endif
#endif
memcpy(gd->new_gd, (char *)gd, sizeof(gd_t));
return 0;
}