【嵌入式Linux】U-boot启动分析

U-boot源码分析

一、脚本

当前配置

//.config 
CONFIG_SYS_ARCH="arm"
CONFIG_SYS_CPU="armv8"
CONFIG_SYS_SOC="tegra210"
CONFIG_TEGRA210=y
CONFIG_SYS_VENDOR="nvidia"
CONFIG_SYS_BOARD="p3450-0000"   //对应 arch/arm/dts/tegra210-p3450-0000.dts
CONFIG_TARGET_P3450_0000=y

CONFIG_TEGRA=y

CONFIG_TEGRA_GP_PADCTRL=y
CONFIG_TEGRA_PINCTRL=y

CONFIG_SPL_SERIAL_SUPPORT=y   //串口

CONFIG_DEFAULT_DEVICE_TREE="tegra210-p3450-0000"   //默认的设备树

链接脚本

找到入口,第一个运行的代码

//u-boot.lds
OUTPUT_FORMAT("elf64-littleaarch64", "elf64-littleaarch64", "elf64-littleaarch64")
OUTPUT_ARCH(aarch64)
ENTRY(_start)  //入口地址
SECTIONS
{
 . = 0x00000000;
 . = ALIGN(8);
 .text :
 {
  *(.__image_copy_start)
  arch/arm/cpu/armv8/start.o (.text*)  //第一行代码所在文件
 }
 ......
}	

编译映射图

确认那些函数文件,最终编译到镜像里

//u-boot.map 
 目的 //确认那些函数文件,最终编译到镜像里
arch/arm/cpu/armv8/start.o(.text*)
 .text          0x0000000080080000      0x1c0 arch/arm/cpu/armv8/start.o
                0x0000000080080000                _start
                0x0000000080080040                _TEXT_BASE
                0x0000000080080064                save_boot_params_ret
                0x0000000080080140                lowlevel_init
                0x000000008008016c                c_runtime_cpu_setup 	
                
 .text          0x0000000080081000      0x390 arch/arm/cpu/armv8/built-in.o
                0x0000000080081000                vectors      

二、入口

//arch/arm/cpu/armv8/start.S  入口
.globl  _start //声明_start为全局标号
_start:
    b   reset  //上电(或复位)执行的第一条指令,
    .align 3  // 8字节对齐(2^3 )

.globl  _TEXT_BASE //声明_TEXT_BASE(代码段的基地址)为全局标号
_TEXT_BASE:
    .quad   CONFIG_SYS_TEXT_BASE	   /* 分配8字节空间 存放CONFIG_SYS_TEXT_BASE    按ctrl+] 可跳转到定义处 , 按ctrl+o可返回 
   5    215  include/generated/autoconf.h <<CONFIG_SYS_TEXT_BASE>>
             #define CONFIG_SYS_TEXT_BASE 0x80080000   系统代码断的基地址
  */

reset:
    /* Allow the board to save important registers */
    b   save_boot_params 

#ifdef CONFIG_SYS_RESET_SCTRL  //条件编译(如果CONFIG_SYS_RESET_SCTRL 有定义,则把里面指令编译进来)
     bl reset_sctrl  //调用reset_sctrl函数
#endif
    adr	x0, vectors   //读标号(地址)vectors到x0,小范围读取,vectors代表异常向量表的首地址 (按ctrl+]跳转到定义处) 
     /*
      * Could be EL3/EL2/EL1, Initial State:
      * Little Endian, MMU Disabled, i/dCache Disabled
      */
     switch_el x1, 3f, 2f, 1f /* 调用宏函数 switch_el 根据异常级别,跳到对应标号处 (光标移到switch_el上 -> 按ctrl+]跳转到定义处)
 可在官方寄存器列表中搜 CurrentEL 见 https://developer.arm.com/documentation/ddi0595/2021-09?lang=en  
 .macro  switch_el, xreg, el3_label, el2_label, el1_label
     mrs \xreg, CurrentEL    bit[3:2]: 00(EL0)  01(EL1) 10(EL2) 11(EL3)  
     cmp \xreg, 0xc
     b.eq    \el3_label
     cmp \xreg, 0x8
     b.eq    \el2_label
     cmp \xreg, 0x4
     b.eq    \el1_label
 .endm
arch/arm/include/asm/macro.h   
      */
3:  set_vbar vbar_el3, x0
    mrs x0, scr_el3
    orr x0, x0, #0xf            /* SCR_EL3.NS|IRQ|FIQ|EA */
    msr scr_el3, x0
    msr cptr_el3, xzr           /* Enable FP/SIMD */
    b   0f
2:  set_vbar    vbar_el2, x0
    mov x0, #0x33ff
    msr cptr_el2, x0            /* Enable FP/SIMD */
    b   0f
1:  set_vbar    vbar_el1, x0     //把X0值写入vbar_el1(完成设置异常向量表基地址)
	       //Armv8-A 架构寄存器 https://developer.arm.com/documentation/ddi0595/2021-09?lang=en中 可查看VBAR_EL1的详细描述
    mov x0, #3 << 20
    msr cpacr_el1, x0           /* Enable FP/SIMD */
0:

    /* Apply ARM core specific erratas */
    bl  apply_core_errata      // 光标移到 apply_core_errata上 -> 按n键,

    bl  lowlevel_init	 //低级别初始化(处理器特有的)

master_cpu:
    bl  _main	 //跳转到C语言的main函数(汇编阶段结束) 
               //注:在u-boot.map中找_main -> arch/arm/lib/built-in.o -> 在arch/arm/lib文件夹下搜索_main找到
               //     ->arch/arm/lib/crt0_64.S
               // 如果还找不到_main,可用qemu 放在,模拟运行调试跟踪

三、异常向量表

//arch/arm/cpu/armv8/exceptions.S
	.align	11  //2^11=2048  整个异常向量表 2K对齐  -> 通过对齐,实现向量表空间的预留
	            //16个异常 ,每个异常32条指令    16*32*4=2048 
	.globl	vectors  
vectors:  //标号地址0x1000(见u-boot.map)
	//对齐  2^7= 1000 0000 =128字节   128/4= 32条指令的空间 (64位机指令是32位的,只是寄存器空间是64位的) 
	//每个异常向量,占32条指令空间。
	.align	7		/*1: 同步异常 Current EL Synchronous Thread */
	stp		x29, x30, [sp, #-16]! //入栈:把x29, x30 存储到sp-16指向的空间后,sp自减16 (因寄存器是8字节,栈是向下生长故是 -16)                              
                            	//类似前索引: *(sp-16) = x29,x30   sp=sp-16 (!使得sp能自更新)  把 x29,x30看成整体              
                              //stp只支持2个寄存器,代替了复杂的stmfd  (64位汇编,取消了批量操作指令)     
	bl	_exception_entry  //进入异常(进栈保存现场)
	bl	do_bad_sync   //同步异常处理程序
	b		exception_exit   //退出异常(出栈恢复现场)

/*
 * Save (most of) the GP registers to the stack frame.
 * This is the first part of the shared routine called into from all entries.
 */
_exception_entry: 
	//保存现场:把x1~x28 入栈
	stp	x27, x28, [sp, #-16]! //x27, x28入栈
	stp	x25, x26, [sp, #-16]! //x25, x26入栈
	stp	x23, x24, [sp, #-16]!
	stp	x21, x22, [sp, #-16]!
	stp	x19, x20, [sp, #-16]!
	stp	x17, x18, [sp, #-16]!
	stp	x15, x16, [sp, #-16]!
	stp	x13, x14, [sp, #-16]!
	stp	x11, x12, [sp, #-16]!
	stp	x9, x10, [sp, #-16]!
	stp	x7, x8, [sp, #-16]!
	stp	x5, x6, [sp, #-16]!
	stp	x3, x4, [sp, #-16]!
	stp	x1, x2, [sp, #-16]!
	b	_save_el_regs			/* jump to the second part */  //报错特殊寄存器(ELR  ESR

	.align	7		/*2:中断异常 Current EL IRQ Thread */
	stp	x29, x30, [sp, #-16]!
	bl	_exception_entry
	bl	do_bad_irq
	b	exception_exit

/*
 * Save exception specific context: ESR and ELR, for all exception levels.
 * This is the second part of the shared routine called into from all entries.
 */
_save_el_regs:
	/* Could be running at EL3/EL2/EL1 */
	switch_el x11, 3f, 2f, 1f
3:	mrs	x1, esr_el3   
	mrs	x2, elr_el3
	b	0f
2:	mrs	x1, esr_el2
	mrs	x2, elr_el2
	b	0f
1:	mrs	x1, esr_el1  //esr 记录异常产生的原因
	mrs	x2, elr_el1    //elr 记录异常的链接地址(类似lr)  
0:
	stp	x2, x0, [sp, #-16]!
	mov	x0, sp
	ret

	.align	7		/*3:快速中断异常 Current EL FIQ Thread */
	stp	x29, x30, [sp, #-16]!
	bl	_exception_entry
	bl	do_bad_fiq
				/* falling through to _exception_exit */
/*
 * Restore the exception return address, for all exception levels.
 * This is the first part of the shared routine called into from all entries.
 */
exception_exit:  //出栈恢复数据(特殊机器esr elr和x0~x30通用的寄存器)
	ldp	x2, x0, [sp],#16
	switch_el x11, 3f, 2f, 1f
3:	msr	elr_el3, x2
	b	_restore_regs
2:	msr	elr_el2, x2
	b	_restore_regs
1:	msr	elr_el1, x2
	b	_restore_regs		/* jump to the second part */

	.align	7		/*4:系统错误异常 Current EL Error Thread */
	stp	x29, x30, [sp, #-16]!
	bl	_exception_entry
	bl	do_bad_error
	b	exception_exit

/*
 * Restore the general purpose registers from the exception stack, then return.
 * This is the second part of the shared routine called into from all entries.
 */
_restore_regs:
	//恢复现场:出栈,恢复x1~x30的内容
	ldp	x1, x2, [sp],#16
	ldp	x3, x4, [sp],#16
	ldp	x5, x6, [sp],#16
	ldp	x7, x8, [sp],#16
	ldp	x9, x10, [sp],#16
	ldp	x11, x12, [sp],#16
	ldp	x13, x14, [sp],#16
	ldp	x15, x16, [sp],#16
	ldp	x17, x18, [sp],#16
	ldp	x19, x20, [sp],#16
	ldp	x21, x22, [sp],#16
	ldp	x23, x24, [sp],#16
	ldp	x25, x26, [sp],#16
	ldp	x27, x28, [sp],#16
	ldp	x29, x30, [sp],#16
	eret

	.align	7		 /*5:同步异常 Current EL (SP_ELx) Synchronous Handler */
	stp	x29, x30, [sp, #-16]!
	bl	_exception_entry
	bl	do_sync
	b	exception_exit

	.align	7		 /*6:中断异常 Current EL (SP_ELx) IRQ Handler */
	stp	x29, x30, [sp, #-16]!
	bl	_exception_entry
	bl	do_irq	
	b	exception_exit

	.align	7		 /*7:快速中断异常 Current EL (SP_ELx) FIQ Handler */
	stp	x29, x30, [sp, #-16]!
	bl	_exception_entry
	bl	do_fiq
	b	exception_exit

	.align	7		 /*8:系统错误异常 Current EL (SP_ELx) Error Handler */
	stp	x29, x30, [sp, #-16]!
	bl	_exception_entry
	bl	do_error
	b	exception_exit

四、准备C语言的执行环境

//arch/arm/lib/crt0_64.S   准备C语言的执行环境(c runtiime  environment)

#include <config.h>
#include <asm-offsets.h>
#include <asm/macro.h>
#include <linux/linkage.h>

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	x0, =(CONFIG_TPL_STACK)
#elif defined(CONFIG_SPL_BUILD) && defined(CONFIG_SPL_STACK)
	ldr	x0, =(CONFIG_SPL_STACK)
#elif defined(CONFIG_INIT_SP_RELATIVE)
	adr	x0, __bss_start
	add	x0, x0, #CONFIG_SYS_INIT_SP_BSS_OFFSET
#else
	ldr	x0, =(CONFIG_SYS_INIT_SP_ADDR)  // 栈地址 初始化
#endif
	bic	sp, x0, #0xf	/* 16-byte alignment for ABI compliance */
	mov	x0, sp
	bl	board_init_f_alloc_reserve   // 预留空间分配( ctrl+] -> 跳转进common/init/board_init.c)
	mov	sp, x0
	/* set up gd here, outside any C code */
	mov	x18, x0
	bl	board_init_f_init_reserve //板子预留空间初始化(堆 栈 全局数据区)
		{//common/init/board_init.c   板子预留空间初始化(用后面的点灯法,可确认运行到这里)
			 -------------
			 |    栈     |  <--stack_pointer
			 |    Stack  | 
			 -------------	 
			 |           |
			 |     GD    | //全局数据区
			 -------------	 
			 |     堆    |
			 |   mallock |  <--mallock_pointer 
			 -------------
		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;
			/* zero the area */
			memset(gd_ptr, '\0', sizeof(*gd));
			/* 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);

			/*
			 * 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;
		#endif

			if (CONFIG_IS_ENABLED(SYS_REPORT_STACK_F_USAGE))
				board_init_f_init_stack_protection();
		}	
		}

	mov	x0, #0
	bl	board_init_f  //前置的板级初始化 (重定位前 定时器 时钟 串口 内存) 
	                  //ctrl+] 跳转 有多个,选哪一个
	                  //u-boot.map 里搜,发现是在common目录下
	                  //vscode下在 common目录下找发现有  common/board_f.c 和common/spl/spl.c
	                  //再查看common 下.o 文件生成,发现只有board_f.c有生成board_f.o ,故确定编译的是board_f.c
	                  //如果都生成.o 则用加打印跟踪方式,或点灯法 

#if !defined(CONFIG_SPL_BUILD) || CONFIG_IS_ENABLED(FRAMEWORK)
/*
 * Clear BSS section
 */
	ldr	x0, =__bss_start		/* this is auto-relocated! */
	ldr	x1, =__bss_end			/* this is auto-relocated! */
clear_loop:
	str	xzr, [x0], #8
	cmp	x0, x1
	b.lo	clear_loop

	/* call board_init_r(gd_t *id, ulong dest_addr) */
	mov	x0, x18				/* gd_t */
	ldr	x1, [x18, #GD_RELOCADDR]	/* dest_addr */
	b	board_init_r			//重定位后的板级初始化     见  Ctrl+] -> common/board_r.c

五、前置板级初始化(重定位前)

//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();
} 	
	
static const init_fnc_t init_sequence_f[] = { //前置的初始化函数指针数组
	setup_mon_len,
#ifdef CONFIG_OF_CONTROL //ctrl+] 发现有定义
  //FDT,flatted device tree,扁平设备树
  //uboot最终将其device tree编译成dtb文件,使用过程中通过解析该dtb来获取板级设备信息。 
  //uboot的dtb和kernel中的dtb是一致的
	fdtdec_setup, //设备树的安装:获取dtb的地址,并且验证dtb的合法性
	            //-> reserve_fdt  和 reloc_fdt
             //dtb镜像会添加到u-boot.bin的后面见 -> board_fdt_blob_setup ->  fdt_blob = (ulong *)&_end;
#endif
#ifdef CONFIG_TRACE_EARLY
	trace_early_init,
#endif
	initf_malloc,
	log_init,
	initf_bootstage,	/* uses its own timer, so does not need DM */
#ifdef CONFIG_BLOBLIST
	bloblist_init,
#endif
	setup_spl_handoff,
	initf_console_record,
#if defined(CONFIG_HAVE_FSP)
	arch_fsp_init,
#endif
	arch_cpu_init,		/* basic arch cpu dependent setup */
	mach_cpu_init,		/* SoC/machine dependent CPU setup */
	initf_dm,   //设备模型初始化: 初始化驱动树和uclass树,然后从平台数据和FDT中扫描和绑定可用的设备
	 
	arch_cpu_init_dm,
#if defined(CONFIG_BOARD_EARLY_INIT_F)  // .config 中搜,发现有使用该配置项的
	board_early_init_f, //板子的早期初始化(u-boot.map中搜发现在arch/arm/mach-tegra/中) -> board2.c

#endif
#if defined(CONFIG_PPC) || defined(CONFIG_SYS_FSL_CLK) || defined(CONFIG_M68K)
	/* get CPU and bus clocks according to the environment variable */
	get_clocks,		/* get CPU and bus clocks (etc.) */
#endif
#if !defined(CONFIG_M68K)
	timer_init,		/* 定时器初始化 initialize timer  */
#endif
#if defined(CONFIG_BOARD_POSTCLK_INIT)
	board_postclk_init,
#endif
	env_init,		/* u-boot环境变量的初始化 initialize environment */
	init_baud_rate,		/*初始波特率  115200 initialze baudrate settings  */
	serial_init,		/*串口初始化设置 serial communications setup */ 
	              //追踪:ctrl + ] 选哪一个(看最终编译的)
	              //  u-boot.map -> drivers/serial/.built-in.o.cmd  -> serial-uclass.o ns16550.o
	              // serial-uclass.c 会在前面 initf_dm中扫描建立串口设备模型
	              
	console_init_f,		/* stage 1 init of console */
	display_options,	/* say that we are here */
	display_text_info,	/* show debugging info if required */
	checkcpu,
#if defined(CONFIG_SYSRESET)
	print_resetinfo,
#endif
#if defined(CONFIG_DISPLAY_CPUINFO)
	print_cpuinfo,		/* display cpu info (and speed) */
#endif
#if defined(CONFIG_DTB_RESELECT)
	embedded_dtb_select,
#endif
#if defined(CONFIG_DISPLAY_BOARDINFO)
	show_board_info,
#endif
	INIT_FUNC_WATCHDOG_INIT
#if defined(CONFIG_MISC_INIT_F)
	misc_init_f,
#endif
	INIT_FUNC_WATCHDOG_RESET
#if defined(CONFIG_SYS_I2C)
	init_func_i2c,
#endif
#if defined(CONFIG_VID) && !defined(CONFIG_SPL)
	init_func_vid,
#endif
	announce_dram_init,
	dram_init,		/*内存初始化  configure available RAM banks */
#ifdef CONFIG_POST
	post_init_f,
#endif
	INIT_FUNC_WATCHDOG_RESET
#if defined(CONFIG_SYS_DRAM_TEST)
	testdram,
#endif /* CONFIG_SYS_DRAM_TEST */
	INIT_FUNC_WATCHDOG_RESET

#ifdef CONFIG_POST
	init_post,
#endif
	INIT_FUNC_WATCHDOG_RESET

#ifdef CONFIG_PRAM
	reserve_pram,
#endif
	reserve_round_4k,
#ifdef CONFIG_ARM
	reserve_mmu,
#endif

	reserve_fdt, //为dtb分配新的内存地址空间
#if defined(CONFIG_M68K) || defined(CONFIG_MIPS) || defined(CONFIG_PPC) || \
	defined(CONFIG_SH)
	setup_board_part1,
#endif
#if defined(CONFIG_PPC) || defined(CONFIG_M68K)
	INIT_FUNC_WATCHDOG_RESET
	setup_board_part2,
#endif
	display_new_sp,
#ifdef CONFIG_OF_BOARD_FIXUP
	fix_fdt,
#endif
	INIT_FUNC_WATCHDOG_RESET
	reloc_fdt, //重定位设备树
	reloc_bootstage,
	reloc_bloblist,
	setup_reloc,  //重定位的安装
#if defined(CONFIG_X86) || defined(CONFIG_ARC)
	copy_uboot_to_ram,
	do_elf_reloc_fixups,
#endif
	clear_bss,
#if !defined(CONFIG_ARM) && !defined(CONFIG_SANDBOX) && \
		!CONFIG_IS_ENABLED(X86_64)
	jump_to_copy,
#endif
	NULL,
};
	
	

static int initf_dm(void) //设备树及其驱动模块初始化
{
	#if defined(CONFIG_DM) && CONFIG_VAL(SYS_MALLOC_F_LEN) //ctrl+] 发现CONFIG_DM 是有定义的
		int ret;

		bootstage_start(BOOTSTATE_ID_ACCUM_DM_F, "dm_f");
		ret = dm_init_and_scan(true);  //扫描设备树建立设备模型
		bootstage_accum(BOOTSTATE_ID_ACCUM_DM_F);
		if (ret)
			return ret;
	#endif
	#ifdef CONFIG_TIMER_EARLY
		ret = dm_timer_init();
		if (ret)
			return ret;
	#endif

		return 0;

}

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));  //数据搬移,重定位

	debug("Relocation Offset is: %08lx\n", gd->reloc_off);
	debug("Relocating to %08lx, new gd at %08lx, sp at %08lx\n",
	      gd->relocaddr, (ulong)map_to_sysmem(gd->new_gd),
	      gd->start_addr_sp);

	return 0;
}

六、后置板级初始化(重定位后)

//common/board_r.c
void board_init_r(gd_t *new_gd, ulong dest_addr)
{
	if (initcall_run_list(init_sequence_r))
		hang();

	/* NOTREACHED - run_main_loop() does not return */
	hang();
}	

static init_fnc_t init_sequence_r[] = {  //后置的初始化列表
	initr_trace,
	initr_reloc,
	/* TODO: could x86/PPC have this also perhaps? */
#ifdef CONFIG_ARM
	initr_caches,
	/* Note: For Freescale LS2 SoCs, new MMU table is created in DDR.
	 *	 A temporary mapping of IFC high region is since removed,
	 *	 so environmental variables in NOR flash is not available
	 *	 until board_init() is called below to remap IFC to high
	 *	 region.
	 */
#endif
	initr_reloc_global_data,
#if defined(CONFIG_SYS_INIT_RAM_LOCK) && defined(CONFIG_E500)
	initr_unlock_ram_in_cache,
#endif
	initr_barrier,
	initr_malloc,
	log_init,
	initr_bootstage,	/* Needs malloc() but has its own timer */
	initr_console_record,
#ifdef CONFIG_SYS_NONCACHED_MEMORY
	initr_noncached,
#endif
#ifdef CONFIG_OF_LIVE
	initr_of_live,
#endif
#ifdef CONFIG_DM
	initr_dm,    //设备树模块初始化
#endif
#if defined(CONFIG_ARM) || defined(CONFIG_NDS32) || defined(CONFIG_RISCV) || \
	defined(CONFIG_SANDBOX)
	board_init,	/* Setup chipselects */
#endif
	/*
	 * TODO: printing of the clock inforamtion of the board is now
	 * implemented as part of bdinfo command. Currently only support for
	 * davinci SOC's is added. Remove this check once all the board
	 * implement this.
	 */
#ifdef CONFIG_CLOCKS
	set_cpu_clk_info, /* Setup clock information */
#endif
#ifdef CONFIG_EFI_LOADER
	efi_memory_init,
#endif
	initr_binman,
#ifdef CONFIG_FSP_VERSION2
	arch_fsp_init_r,
#endif
	initr_dm_devices, //设备树设备的舒适和
	stdio_init_tables,
	initr_serial,      //串口初始化
	initr_announce,
#if CONFIG_IS_ENABLED(WDT)
	initr_watchdog,      //看门狗初始化
#endif
	INIT_FUNC_WATCHDOG_RESET
#ifdef CONFIG_NEEDS_MANUAL_RELOC
	initr_manual_reloc_cmdtable,
#endif
#if defined(CONFIG_PPC) || defined(CONFIG_M68K) || defined(CONFIG_MIPS)
	initr_trap,
#endif
#ifdef CONFIG_ADDR_MAP
	initr_addr_map,
#endif
#if defined(CONFIG_BOARD_EARLY_INIT_R)
	board_early_init_r,
#endif
	INIT_FUNC_WATCHDOG_RESET
#ifdef CONFIG_POST
	initr_post_backlog,
#endif
	INIT_FUNC_WATCHDOG_RESET
#if defined(CONFIG_PCI) && defined(CONFIG_SYS_EARLY_PCI_INIT)
	/*
	 * Do early PCI configuration _before_ the flash gets initialised,
	 * because PCU resources are crucial for flash access on some boards.
	 */
	initr_pci, //pci初始化
#endif
#ifdef CONFIG_ARCH_EARLY_INIT_R
	arch_early_init_r,
#endif
	power_init_board,
#ifdef CONFIG_MTD_NOR_FLASH
	initr_flash, //flash初始化
#endif
	INIT_FUNC_WATCHDOG_RESET
#if defined(CONFIG_PPC) || defined(CONFIG_M68K) || defined(CONFIG_X86)
	/* initialize higher level parts of CPU like time base and timers */
	cpu_init_r,  
#endif
#ifdef CONFIG_CMD_NAND
	initr_nand,
#endif
#ifdef CONFIG_CMD_ONENAND
	initr_onenand,
#endif
#ifdef CONFIG_MMC
	initr_mmc,
#endif
	initr_env, //环境变量初始化
#ifdef CONFIG_SYS_BOOTPARAMS_LEN
	initr_malloc_bootparams,
#endif
	INIT_FUNC_WATCHDOG_RESET
	initr_secondary_cpu,
#if defined(CONFIG_ID_EEPROM) || defined(CONFIG_SYS_I2C_MAC_OFFSET)
	mac_read_from_eeprom,
#endif
	INIT_FUNC_WATCHDOG_RESET
#if defined(CONFIG_PCI) && !defined(CONFIG_SYS_EARLY_PCI_INIT)
	/*
	 * Do pci configuration
	 */
	initr_pci,
#endif
	stdio_add_devices,
	initr_jumptable,
#ifdef CONFIG_API
	initr_api,
#endif
	console_init_r,		/* fully init console as a device */
#ifdef CONFIG_DISPLAY_BOARDINFO_LATE
	console_announce_r,
	show_board_info,
#endif
#ifdef CONFIG_ARCH_MISC_INIT
	arch_misc_init,		/* miscellaneous arch-dependent init */
#endif
#ifdef CONFIG_MISC_INIT_R
	misc_init_r,		/* miscellaneous platform-dependent init */
#endif
	INIT_FUNC_WATCHDOG_RESET
#ifdef CONFIG_CMD_KGDB
	initr_kgdb,
#endif
	interrupt_init,  //中断初始化
#ifdef CONFIG_ARM
	initr_enable_interrupts,
#endif
#if defined(CONFIG_MICROBLAZE) || defined(CONFIG_M68K)
	timer_init,		/*定时器初始化 initialize timer */
#endif
#if defined(CONFIG_LED_STATUS)
	initr_status_led,
#endif
	/* PPC has a udelay(20) here dating from 2002. Why? */
#ifdef CONFIG_CMD_NET
	initr_ethaddr, //网卡初始化
#endif
#if defined(CONFIG_GPIO_HOG)
	gpio_hog_probe_all,
#endif
#ifdef CONFIG_BOARD_LATE_INIT
	board_late_init,
#endif
#if defined(CONFIG_SCSI) && !defined(CONFIG_DM_SCSI)
	INIT_FUNC_WATCHDOG_RESET
	initr_scsi,
#endif
#ifdef CONFIG_BITBANGMII
	initr_bbmii,
#endif
#ifdef CONFIG_CMD_NET
	INIT_FUNC_WATCHDOG_RESET
	initr_net,
#endif
#ifdef CONFIG_POST
	initr_post,
#endif
#if defined(CONFIG_IDE) && !defined(CONFIG_BLK)
	initr_ide,
#endif
#ifdef CONFIG_LAST_STAGE_INIT
	INIT_FUNC_WATCHDOG_RESET
	/*
	 * Some parts can be only initialized if all others (like
	 * Interrupts) are up and running (i.e. the PC-style ISA
	 * keyboard).
	 */
	last_stage_init,
#endif
#ifdef CONFIG_CMD_BEDBUG
	INIT_FUNC_WATCHDOG_RESET
	initr_bedbug,
#endif
#if defined(CONFIG_PRAM)
	initr_mem,
#endif
#if defined(CONFIG_M68K) && defined(CONFIG_BLOCK_CACHE)
	blkcache_init,
#endif
	run_main_loop,  //进入主循环

七、进入主循环

(无输入自启动,有输入则进入交互模式)

//common/main.c   进入主循环(无输入自启动;有输入则进入交互模式)
/* We come here after U-Boot is initialised and ready to process commands */
void main_loop(void)
{
	const char *s;

	bootstage_mark_name(BOOTSTAGE_ID_MAIN_LOOP, "main_loop");

	if (IS_ENABLED(CONFIG_VERSION_VARIABLE))
		env_set("ver", version_string);  /* set version variable */

	cli_init();

	if (IS_ENABLED(CONFIG_USE_PREBOOT))
		run_preboot_environment_command();

	if (IS_ENABLED(CONFIG_UPDATE_TFTP))
		update_tftp(0UL, NULL, NULL);

	s = bootdelay_process(); /* bootdelay延时时间到之前无输入,则加载bootcmd进入自启动模式;如果有输入则进入交互模式
	  common/autoboot.c
	   const char *bootdelay_process(void)
			{
				char *s;
				int bootdelay;

				bootcount_inc();

				s = env_get("bootdelay");
				bootdelay = s ? (int)simple_strtol(s, NULL, 10) : CONFIG_BOOTDELAY;


				if (bootcount_error())
					s = env_get("altbootcmd");
				else
					s = env_get("bootcmd");

				if (IS_ENABLED(CONFIG_OF_CONTROL))
					process_fdt_options(gd->fdt_blob);
				stored_bootdelay = bootdelay;

				return s;
			}
	    */
	if (cli_process_fdt(&s))
		cli_secure_boot_cmd(s);

	autoboot_command(s);

	cli_loop();
	panic("No CLI available");
}	

八、跟踪示例

串口跟踪

线索 /*
common/board_f.c  
init_sequence_f[]  
board_early_init_f -> board2.c    
                    clock_early_init -> tegra210/clock.c            
                    board_init_uart_f -> board.c
                                     CONFIG_TEGRA_ENABLE_UARTA
                                       setup_uarts -> uart_configs  -> FUNCMUX_UART1_UART1,  复用配置
serial_init -> drivers/serial/.built-in.o.cmd ->   serial-uclass.c  和 ns16550.c
                                   
*/
方法 //1. u-boot.map 搜索  board_early_init_f  确认是在 arch/arm/mach-tegra/里
     //2. vscode 里arch/arm/mach-tegra/ 右键 -> 在文件夹中查找 -> 确认是在哪个文件里
     //3. 如果还有多个文件,再查看,看哪个生成.o 文件(有编译进来)
板子早期初始化
//arch/arm/mach-tegra/board2.c
#ifdef CONFIG_BOARD_EARLY_INIT_F   //ctrl+]  发现include/generated/autoconf.h有,表示下面代码有编译进来
                                   //和 .config里搜 发现 CONFIG_BOARD_EARLY_INIT_F=y 是一样的

int board_early_init_f(void)
{
#if IS_ENABLED(CONFIG_TEGRA_CLKRST)  //ctrl+]  发现include/generated/autoconf.h有CONFIG_TEGRA_CLKRST,表示下面代码有编译进来
	if (!clock_early_init_done())
		clock_early_init(); //早期时钟初始化 ,ctrl+]跳转时为何选tegra210下的,因.config配置里是 CONFIG_SYS_SOC="tegra210"
		                  //或u-boot.map里搜clock_early_init  发现是目录arch/arm/mach-tegra下的,再看对应的.o文件生成否,来确定
                      //->clock.c
#endif

#if defined(CONFIG_TEGRA_DISCONNECT_UDC_ON_BOOT)
#define USBCMD_FS2 (1 << 15)
	{
		struct usb_ctlr *usbctlr = (struct usb_ctlr *)0x7d000000;
		writel(USBCMD_FS2, &usbctlr->usb_cmd);
	}
#endif

	pinmux_init();
	board_init_uart_f(); //串口早期初始化 

	/* Initialize periph GPIOs */
	gpio_early_init();
	gpio_early_init_uart();

	return 0;
}
#endif	/* EARLY_INIT */
时钟的设置
//arch/arm/mach-tegra/tegra210/clock.c  时钟的设置 复杂且关键 
void clock_early_init(void)
{
	struct clk_rst_ctlr *clkrst =
		(struct clk_rst_ctlr *)NV_PA_CLK_RST_BASE;
	struct clk_pll_info *pllinfo = &tegra_pll_info_table[CLOCK_ID_DISPLAY];
	u32 data;

	tegra210_setup_pllp();

	/*
	 * PLLC output frequency set to 600Mhz
	 * PLLD output frequency set to 925Mhz
	 */
	switch (clock_get_osc_freq()) {
	case CLOCK_OSC_FREQ_12_0: /* OSC is 12Mhz */
		clock_set_rate(CLOCK_ID_CGENERAL, 600, 12, 0, 8);
		clock_set_rate(CLOCK_ID_DISPLAY, 925, 12, 0, 12);
		break;

	case CLOCK_OSC_FREQ_26_0: /* OSC is 26Mhz */
		clock_set_rate(CLOCK_ID_CGENERAL, 600, 26, 0, 8);
		clock_set_rate(CLOCK_ID_DISPLAY, 925, 26, 0, 12);
		break;

	case CLOCK_OSC_FREQ_13_0: /* OSC is 13Mhz */
		clock_set_rate(CLOCK_ID_CGENERAL, 600, 13, 0, 8);
		clock_set_rate(CLOCK_ID_DISPLAY, 925, 13, 0, 12);
		break;
	case CLOCK_OSC_FREQ_19_2:
		clock_set_rate(CLOCK_ID_CGENERAL, 125, 4, 0, 0);
		clock_set_rate(CLOCK_ID_DISPLAY, 96, 2, 0, 12);
		break;
	case CLOCK_OSC_FREQ_38_4:
		clock_set_rate(CLOCK_ID_CGENERAL, 125, 8, 0, 0);
		clock_set_rate(CLOCK_ID_DISPLAY, 96, 4, 0, 0);
		break;
	default:
		/*
		 * These are not supported. It is too early to print a
		 * message and the UART likely won't work anyway due to the
		 * oscillator being wrong.
		 */
		break;
	}

	/* PLLC_MISC1: Turn IDDQ off. NOTE: T210 PLLC_MISC_1 maps to pll_misc */
	clrbits_le32(&clkrst->crc_pll[CLOCK_ID_CGENERAL].pll_misc,
		     (1 << PLLC_IDDQ));
	udelay(2);

	/*
	 * PLLC_MISC: Take PLLC out of reset. NOTE: T210 PLLC_MISC maps
	 * to pll_out[1]
	 */
	clrbits_le32(&clkrst->crc_pll[CLOCK_ID_CGENERAL].pll_out[1],
		     (1 << PLLC_RESET));
	udelay(2);

	/* PLLD_MISC: Set CLKENABLE and LOCK_DETECT bits */
	data = (1 << PLLD_ENABLE_CLK) | (1 << pllinfo->lock_ena);
	writel(data, &clkrst->crc_pll[CLOCK_ID_DISPLAY].pll_misc);
	udelay(2);
}
串口初始化
//arch/arm/mach-tegra/board.c  串口初始化

enum {
	/* UARTs which we can enable */
	UARTA	= 1 << 0,
	UARTB	= 1 << 1,
	UARTC	= 1 << 2,
	UARTD	= 1 << 3,
	UARTE	= 1 << 4,
	UART_COUNT = 5,
};

#if IS_ENABLED(CONFIG_TEGRA_PINCTRL)
static int uart_configs[] = {
#else	/* Tegra210 我们使用的配置 */
	FUNCMUX_UART1_UART1,	/* UARTA */
	-1,
	-1,
	FUNCMUX_UART4_UART4,	/* UARTD */
	-1,
#endif
};

/**
 * Set up the specified uarts
 *
 * @param uarts_ids	Mask containing UARTs to init (UARTx)
 */
static void setup_uarts(int uart_ids)
{
	static enum periph_id id_for_uart[] = {
		PERIPH_ID_UART1,
		PERIPH_ID_UART2,
		PERIPH_ID_UART3,
		PERIPH_ID_UART4,
		PERIPH_ID_UART5,
	};
	size_t i;

	for (i = 0; i < UART_COUNT; i++) {
		if (uart_ids & (1 << i)) {
			enum periph_id id = id_for_uart[i];

			funcmux_select(id, uart_configs[i]);
			clock_ll_start_uart(id);
		}
	}
}
#endif

void board_init_uart_f(void)   //uart 初始化
{
#if IS_ENABLED(CONFIG_TEGRA_PINCTRL)
	int uart_ids = 0;	/* bit mask of which UART ids to enable */

#ifdef CONFIG_TEGRA_ENABLE_UARTA //ctrl+] 发现p3541-0000.h 中有定义 -> 使用的是UARTA
	uart_ids |= UARTA;  
#endif
#ifdef CONFIG_TEGRA_ENABLE_UARTB //ctrl+]  无
	uart_ids |= UARTB;
#endif
#ifdef CONFIG_TEGRA_ENABLE_UARTC
	uart_ids |= UARTC;
#endif
#ifdef CONFIG_TEGRA_ENABLE_UARTD
	uart_ids |= UARTD;
#endif
#ifdef CONFIG_TEGRA_ENABLE_UARTE
	uart_ids |= UARTE;
#endif
	setup_uarts(uart_ids); //传入id号指定初始化的串口是UART
#endif
}

#if !CONFIG_IS_ENABLED(OF_CONTROL)
static struct ns16550_platdata ns16550_com1_pdata = {
	.base = CONFIG_SYS_NS16550_COM1,
	.reg_shift = 2,
	.clock = CONFIG_SYS_NS16550_CLK,
	.fcr = UART_FCR_DEFVAL,
};

U_BOOT_DEVICE(ns16550_com1) = {
	"ns16550_serial", &ns16550_com1_pdata
};
#endif
驱动通用接口
//drivers/serial/serial-uclass.c 驱动通用接口

void serial_putc(char ch)  //发送字符
{
	if (gd->cur_serial_dev)
		_serial_putc(gd->cur_serial_dev, ch);
}

void serial_puts(const char *str)  //发送字符串
{
	if (gd->cur_serial_dev)
		_serial_puts(gd->cur_serial_dev, str);
}

int serial_getc(void)  //接收字符
{
	if (!gd->cur_serial_dev)
		return 0;

	return _serial_getc(gd->cur_serial_dev);
}

static void serial_stub_putc(struct stdio_dev *sdev, const char ch)
{
	_serial_putc(sdev->priv, ch);
}


/**
 * on_baudrate() - Update the actual baudrate when the env var changes
 *
 * This will check for a valid baudrate and only apply it if valid.
 */
static int on_baudrate(const char *name, const char *value, enum env_op op,
	int flags)
{
	int i;
	int baudrate;

	switch (op) {
	case env_op_create:
	case env_op_overwrite:
		/*
		 * Switch to new baudrate if new baudrate is supported
		 */
		baudrate = simple_strtoul(value, NULL, 10);

		/* Not actually changing */
		if (gd->baudrate == baudrate)
			return 0;

		for (i = 0; i < ARRAY_SIZE(baudrate_table); ++i) {
			if (baudrate == baudrate_table[i])
				break;
		}
		if (i == ARRAY_SIZE(baudrate_table)) {
			if ((flags & H_FORCE) == 0)
				printf("## Baudrate %d bps not supported\n",
				       baudrate);
			return 1;
		}
		if ((flags & H_INTERACTIVE) != 0) {
			printf("## Switch baudrate to %d bps and press ENTER ...\n",
			       baudrate);
			udelay(50000);
		}

		gd->baudrate = baudrate;

		serial_setbrg();

		udelay(50000);

		if ((flags & H_INTERACTIVE) != 0)
			while (1) {
				if (getc() == '\r')
					break;
			}

		return 0;
	case env_op_delete:
		printf("## Baudrate may not be deleted\n");
		return 1;
	default:
		return 0;
	}
}
U_BOOT_ENV_CALLBACK(baudrate, on_baudrate); 

#if CONFIG_IS_ENABLED(SERIAL_PRESENT)
static int serial_post_probe(struct udevice *dev)  
{
	sdev.flags = DEV_FLAGS_OUTPUT | DEV_FLAGS_INPUT | DEV_FLAGS_DM;
	sdev.priv = dev;
	sdev.putc = serial_stub_putc;
	sdev.puts = serial_stub_puts;
	sdev.getc = serial_stub_getc;
	sdev.tstc = serial_stub_tstc;

#if CONFIG_IS_ENABLED(SERIAL_RX_BUFFER)
	/* Allocate the RX buffer */
	upriv->buf = malloc(CONFIG_SERIAL_RX_BUFFER_SIZE);
#endif

	stdio_register_dev(&sdev, &upriv->sdev);
#endif
	return 0;
}

UCLASS_DRIVER(serial) = {
	.id		= UCLASS_SERIAL,
	.name		= "serial",
	.flags		= DM_UC_FLAG_SEQ_ALIAS,
	.post_probe	= serial_post_probe,  //探测串口并初始化
	.pre_remove	= serial_pre_remove,
	.per_device_auto_alloc_size = sizeof(struct serial_dev_priv),
};
串口驱动
//drivers/serial/ns16550.c 串口驱动

const struct dm_serial_ops ns16550_serial_ops = {
	.putc = ns16550_serial_putc, //发送字符
	.pending = ns16550_serial_pending,
	.getc = ns16550_serial_getc, //接收字符
	.setbrg = ns16550_serial_setbrg,
	.setconfig = ns16550_serial_setconfig,
	.getinfo = ns16550_serial_getinfo,
};


static const struct udevice_id ns16550_serial_ids[] = {
	{ .compatible = "ns16550",		.data = PORT_NS16550 },
	{ .compatible = "ns16550a",		.data = PORT_NS16550 },
	{ .compatible = "ingenic,jz4780-uart",	.data = PORT_JZ4780  },
	{ .compatible = "nvidia,tegra20-uart",	.data = PORT_NS16550 },  //英伟达是 基于现有框架 NS16550 改的
	{ .compatible = "snps,dw-apb-uart",	.data = PORT_NS16550 },
	{}
};


U_BOOT_DRIVER(ns16550_serial) = {
	.name	= "ns16550_serial",
	.id	= UCLASS_SERIAL,
#if CONFIG_IS_ENABLED(OF_CONTROL) && !CONFIG_IS_ENABLED(OF_PLATDATA)
	.of_match = ns16550_serial_ids,
	.ofdata_to_platdata = ns16550_serial_ofdata_to_platdata,
	.platdata_auto_alloc_size = sizeof(struct ns16550_platdata),
#endif
	.priv_auto_alloc_size = sizeof(struct NS16550),
	.probe = ns16550_serial_probe,
	.ops	= &ns16550_serial_ops,
#if !CONFIG_IS_ENABLED(OF_CONTROL)
	.flags	= DM_FLAG_PRE_RELOC,
#endif
};
  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值