make smdkc100_defconfig
以被默认支持的smdkc100单板为背景分析u-boot v2018.01
第24行:
char __image_copy_start[0] __attribute__((section(".__image_copy_start")));
不占内存空间,可在u-boot镜像开始位置生成标签__image_copy_start。
图2 .vectors段头部
_start:
建立异常向量表。
某些SOC要求Bootloader头部有hook数据用来指导BL0(固化在iROM)将Nand flash中的BootLoader加载到iRAM中。此时需定义
CONFIG_ENABLE_ARM_SOC_BOOT0_HOOK
,及定义asm/arch/boot0.h 文件,参考arch\arm\include\asm\arch-bcm281xx\boot0.h
reset:
1. 设置CPSR:CPU为SVC模式,禁止IRQ/FIQ;
2. 通过SCTLR和VBAR设置异常向量表的地址到_start
;
3. cpu_init_cp15
: 失效TLB、L1 icache、BP数组;关MMU、dcache,开icache和分支预测;将CPU的
variant + revision存于R2;
4. cpu_init_crit
: 调用lowlevel_init
(board\samsung\smdkc100\lowlevel_init.S):
① 关闭看门狗;
② 设置SRAM;
③ 禁止所有中断线,并设为IRQ及清除标志位;
④ 初始化uart的引脚;
⑤ 初始化tzpc为关闭;
5. 跳到_main
。
_main:
1. 设置sp
为0x2f000000
;
2. 调用board_init_f_alloc_reserve (top=0x2f000000)
(common\init\board_init.c):返回top
=
(0x2f000000 - 1KB(malloc)
- sizeof(struct global_data)
) & ~0xF ;
3. r9(gd)
= sp
= top
;
4. 调用board_init_f_init_reserve (base=top)
:struct global_data
清0,gd->malloc_base
设在
struct global_data
之上;如下图所示为建立C语言运行环境的内存分布:
图3 C运行环境建立
5. 调用board_init_f (boot_flags=0)
(common\board_f.c):gd->flags = 0
,gd->have_console = 0
,执行
init_sequence_f[]
中的函数:
(1) setup_mon_len()
:设gd->mon_len
为__bss_end
-_start
;
(2) fdtdec_setup()
:gd->fdt_blob
设在_end
(CONFIG_OF_SEPARATE
=> u-boot.bin = uboot+dtb),或用
default_environment
的"fdtcontroladdr"
覆盖其值;检查设备树的header;
(3) initf_malloc()
:设gd->malloc_limit
为(1KB),gd->malloc_ptr = 0
;
(4) log_init()
,initf_bootstage()
, initf_console_record()
:空;
(5) arch_cpu_init()
:读PRO_ID寄存器内容解析出CPU id到全局变量s5p_cpu_id
;
(6) mach_cpu_init()
:空;
(7) initf_dm()
:初始化dm资源,绑定dm驱动到gd
中,扫描设备树中dm设备内容;
(8) arch_cpu_init_dm()
:空;
(9) timer_init()
:初始化定时器4和gd->arch
中的定时器成员;
(10) env_init()
:通过默认env_driver
初始化env或者gd->env_addr = (ulong)&default_environment[0];
,
gd->env_valid = ENV_VALID;
(11) init_baud_rate()
:gd->baudrate
设为env中"baudrate"
的值;
(12) serial_init()
(drivers\serial\serial-uclass.c):在设备树中找"stdout-path"
的节点,用节点找
UCLASS_SERIAL
类设备probe
起来,gd->cur_serial_dev = dev;
,gd->flags |= GD_FLG_SERIAL_READY
;
(13) console_init_f()
:gd->have_console = 1
,用CONFIG_SILENT_CONSOLE
可让控制台“沉默”;
(14) display_options()
:打印u-boot版本信息;
(15) display_text_info()
:开debug时,打印u-boot code的内存地址;
(16) print_cpuinfo()
(arch\arm\cpu\armv7\s5p-common\cpu_info.c):打印设备树"cpu-model"
标签的data,
或字符串S5P
和s5p_cpu_id
变量值;打印CPU主频;
(17) show_board_info()
:打印设备树"model"
的data和单板名;
(18) announce_dram_init()
,dram_init()
:初始化gd->ram_size
为通过写读SDRAM校验后得到的实际大小;
Now that we have DRAM mapped and working, we can relocate the code and continue running from DRAM.
(19) setup_dest_addr()
:gd->ram_top
,gd->relocaddr
设为SDRAM末尾:
CONFIG_SYS_SDRAM_BASE + gd->ram_size
;
(20) reserve_round_4k()
:gd->relocaddr
调整为4KB对齐;
(21) reserve_mmu()
:gd->arch.tlb_size
设为16KB,SDRAM为TLB预留空间,设置gd->arch.tlb_addr
;
(22) reserve_video()
:依赖CONFIG_LCD
(未定义),为显存预留内存,初始化gd->fb_base
;
(23) reserve_trace()
:依赖CONFIG_TRACE
(未定义),初始化gd->trace_buff
;
(24) reserve_uboot()
:预留gd->mon_len
个字节给u-boot code,地址存于gd->relocaddr
;
(25) reserve_malloc()
:预留malloc和env区;
(26) reserve_board()
:预留struct bd_info
的空间并清零,地址存于gd->bd
;
(27) setup_machine()
:依赖CONFIG_MACH_TYPE
(未定义),设置gd->bd->bi_arch_number
;
(28) reserve_global_data()
:预留struct global_data
的空间,地址存于gd->new_gd
;
(29) reserve_fdt()
:预留存放设备树的内存,设置gd->fdt_size
和gd->new_fdt
;
(30) reserve_bootstage()
:依赖CONFIG_BOOTSTAGE
(未定义),预留存放struct bootstage_data
的内存,设置
gd->new_bootstage
;
(31) reserve_arch()
:空;
(32) reserve_stacks()
:设置gd->irq_sp
(需16B对齐),预留为4个word的地址记到gd->start_addr_sp
;
函数(19)到(32)进行的内存划分结果如图4所示:
图4 重定位前内存划分
(33) dram_init_banksize()
:初始化gd->bd->bi_dram
;
(34) show_dram_config()
:打印DRAM的大小;
(35) display_new_sp()
:打印gd->start_addr_sp
的值;
(36) reloc_fdt()
:将gd->fdt_blob
地址的设备树重定位到gd->new_fdt
地址上,更新gd->fdt_blob
;
(37) reloc_bootstage()
:依赖CONFIG_BOOTSTAGE
(未定义),重定位gd->bootstage
内容到
gd->new_bootstage
,更新gd->bootstage
;
(38) setup_reloc()
:初始化gd->reloc_off
为重定位目标地址与链接地址之差,重定位gd_t
内容到
gd->new_gd
;
6. 执行sp = gd->start_addr_sp
,r9(gd) = gd->new_gd
,记录重定位代码后的here
地址到lr
,执行
relocate_code (gd->relocaddr)
(arch\arm\lib\relocate.S),如图5所示:
① 将地址__image_copy_start
至__image_copy_end
的u-boot code 重定位到地址gd->relocaddr
;
② 通过.rel.dyn
段确定u-boot code中所有符号索引的内存地址,用重定位偏移校正符号索引的值[1];
图5 动态重定位
跳转到重定位后的u-boot code执行以下代码:
here:
7. 调用relocate_vectors()
设置VBAR重定位异常向量表地址到gd->relocaddr
;c_runtime_cpu_setup()
(arch\arm\cpu\armv7\start.S)失效icache内容,数据同步内存屏障(DSB),指令同步内存屏障(ISB);执行
memset(__bss_start,__bss_end,__bss_end-__bss_start)
清零BSS段;
8. coloured_LED_init()
,red_led_on()
,空;
9. 执行board_init_r (gd, gd->relocaddr)
;正式进入bootloader第二阶段。
1. gd->flags &= ~GD_FLG_LOG_READY;
:指示log系统未初始化;
2. 调用init_sequence_r[]
中函数,打印函数指针链接地址和重定位地址(需开DEBUG
):
(1) initr_trace()
:依赖CONFIG_TRACE
(未定义),trace system
函数未实现;
(2) initr_reloc()
:gd->flags |= GD_FLG_RELOC | GD_FLG_FULL_MALLOC_INIT;
标志重定位完成;
(3) initr_caches()
:调用arch/arm/mach-s5pc1xx/cache.c
函数,开dcache (undef CONFIG_SYS_DCACHE_OFF
);
(4) initr_reloc_global_data()
:重定位全局变量:monitor_flash_len
,gd->fdt_blob
(CONFIG_OF_EMBED
),
EFI的扩展固件(CONFIG_EFI_LOADER
);
(5) initr_barrier()
:空;
(6) initr_malloc()
:初始化malloc功能和清零malloc区;
(7) log_init()
:依赖CONFIG_LOG
(未定义),初始化log驱动;
(8) initr_bootstage()
:设进度为BOOTSTAGE_ID_START_UBOOT_R
,并记到bootstage(依赖CONFIG_BOOTSTAGE
-未定义),show_boot_progress()
(未实现)提示进度,枚举bootstage_id
罗列了进度id;
(9) initr_console_record()
:依赖CONFIG_CONSOLE_RECORD
(未定义),给console record功能分配内存;
(10) bootstage_relocate()
:依赖CONFIG_BOOTSTAGE
(未定义),重定位gd->bootstage
的内容;
(11) initr_of_live()
:依赖CONFIG_OF_LIVE
(未定义),用gd->fdt_blob
在堆上建立设备树;
(12) initr_dm()
:依赖CONFIG_DM
,初始化驱动模型,绑定所有设备(使用U_BOOT_DEVICE
或设备树中声明)和
驱动(U_BOOT_DRIVER
声明)并probe;
(13) board_init()
:smc9115连到SOC接口和对应SROMC的初始化,保存机器ID到gd->bd->bi_arch_number
,
设置gd->bd->bi_boot_params
保存引导操作系统的启动参数;
(14) efi_memory_init()
:依赖CONFIG_EFI_LOADER
,初始化EFI功能及分配内存;
(15) stdio_init_tables()
:初始化标准输入输出设备链表;
(16) initr_serial()
:调用drivers/serial/serial-uclass.c
(依赖CONFIG_DM_SERIAL
),在设备树alias
节点
获得属性"stdout-path"
或"console"
,从而得到作为标准输入输出的设备节点,生成UCLASS_SERIAL
类的
udevice
来匹配兼容的驱动及probe;该串行设备记录到gd->cur_serial_dev
;标志GD_FLG_SERIAL_READY;
;
(17) initr_announce()
:打印u-boot重定位后起始地址(需开DEBUG);
(18) power_init_board()
:空;
(19) initr_nand()
:依赖CONFIG_CMD_NAND
,调用nand硬件驱动之board_nand_init()
填充nand_chip
的成员,
架构层通过nand驱动扫描外部nand设备,从而完善MTD原始设备mtd_info
并注册到MTD子系统;打印nand
的容量;
(20) initr_env()
:通用env层(env/env.c)调用env硬件驱动层(若定义CONFIG_ENV_IS_IN_NAND
则在env/nand.c),
加载nand中CONFIG_ENV_OFFSET
开始的env数据到栈中,检查crc成功则将其(失败则使用default_environment
)复制到堆中,内存地址记录进env_htab
,标志置位GD_FLG_ENV_READY
或GD_FLG_ENV_DEFAULT
;插入或设置环
境变量fdtcontroladdr
为gd->fdt_blob
;
(21) initr_secondary_cpu()
:空;
(22) stdio_add_devices()
:调用drv_xxx_init()
(需开CONFIG_XXX
),如drv_lcd_init()
(需定义CONFIG_LCD
),
drv_system_init()
则关于串口;驱动通用层填充stdio_dev
并注册添加到标准输入输出链表上,在硬件驱动
层做硬件初始化;
(23) initr_jumptable()
:为函数跳转表(struct jt_funcs
定义)分配内存并记录内存地址到gd->jt
;
(24) console_init_r()
:定义CONFIG_SILENT_CONSOLE
和环境变量"silent"
可标志GD_FLG_SILENT
,在标准输入输
出设备表(stdio_add_devices()
生成,common/console.c)将首个标志为DEV_FLAGS_INPUT
或DEV_FLAGS_OUTPUT
作
为控制台io设备,设置环境变量”stdxxx”为设备名,标志GD_FLG_DEVINIT
;
(25) interrupt_init(),initr_enable_interrupts
:关于irq栈的设置;
(26) initr_ethaddr()
:设置gd->bd->bi_enetaddr
为环境变量"ethaddr"
的值;
(27) initr_net()
:通过网络设备驱动通用层(net/eth_legacy.c)调用硬件驱动层smc911x_initialize()
初始化网
路设备,检测环境变量"ethaddr"
值有效性,为空则生成随机MAC地址(需开CONFIG_NET_RANDOM_ETHADDR
),
网络设备名记录到环境变量"ethact"
;
(28) run_main_loop()
:初始化hush解析器(CONFIG_HUSH_PARSER
),用环境变量"bootdelay"
或设备树节点config
的属性bootdelay
作为启动延迟时间,通过hush解析控制台输入的内容打断倒计时并进入命令行;倒计时期间
控制台无输入则执行环境变量或设备树/config
节点的bootcmd
,最后执行命令bootm 0x30007FC0
;
bootm 0x30007FC0
(cmd/bootm.c) 1. do_bootm(...)
执行该命令,作命令的解析;
2. do_bootm_states(...)
,如下内容:
3. bootm_start()
:环境变量verify
决定后续是否对kernel镜像进行校验和检查,lmb(logical memory blocks)相关内
容的初始化;
4. bootm_find_os()
:
(1) boot_get_kernel()
:获取kernel镜像格式为IMAGE_FORMAT_LEGACY
,验证镜像hcrc,打印镜像的名字、类型、数
据大小、加载地址和入口地址,验证dcrc(依赖env的verify
),判断arch是否支持;
(2) 解析镜像的结果填充images.os
的成员,kernel入口地址记到images.ep
,镜像头部地址记到images.os.start
;
5. bootm_find_other()
:
(1) boot_get_ramdisk()
:解析ramdisk镜像,bootm
第三个参数为其地址(如bootm xxx yyy
,yyy
为对应地址);
(2) boot_get_fdt()
:获取和解析设备树镜像内容,设备树镜像的起始地址需在bootm
命令第四个参数指明,如
bootm xxx yyy zzz
,zzz
为对应地址;
6. bootm_load_os()
:解压os数据或移动到images->os.load
地址,所以kernel应有Load Address
=Entry Point
;
7. boot_ramdisk_high()
:重新定位并初始化ramdisk,需定义CONFIG_SYS_BOOT_RAMDISK_HIGH
;
8. bootm_os_get_boot_func(images->os.os)
根据os类型获得启动函数boot_fn = do_bootm_linux
;
9. do_bootm_linux(BOOTM_STATE_OS_PREP, argc, argv, images)
: boot_prep_linux()
:若未指定传递给kernel的设
备树地址,则建立各种tag到地址gd->bd->bi_boot_params
;
10. boot_selected_os()
:通过函数指针boot_fn
调用do_bootm_linux(BOOTM_STATE_OS_GO, ...)
,进而调用
boot_jump_linux(images, BOOTM_STATE_OS_GO)
:
(1) 通过gd->bd->bi_arch_number
或者环境变量machid
获得机器码;
(2) announce_and_cleanup()
:打印提示开始启动内核,注销驱动模型下设备驱动;调用cleanup_before_linux()
:
关L1/2 D-cache和MMU,冲刷掉dcache内数据;关I-cache,失效I-cache内条目,失效整个分支预测器阵列;
执行数据和指令内存屏障,确保前面的操作完成;
(3) kernel_entry(0, machid, r2)
:参数r2传递启动参数(tag或设备树)的内存地址,正式跳转到kernel。
[1] fireaxe. PIC(与位置无关代码)在u-boot上的实现[EB/OL]. ChinaUnix,2014