uboot启动第二阶段分析

之前介绍了 uboot 启动第一阶段,现在介绍启动的第二阶段

启动阶段的工作

启动第一阶段的主要工作是对处理器的内部资源(如时钟、串口)、内存(ddr)初始化,并进行 uboot 的重定位,并跳转到启动第二阶段

启动第二阶段的主要工作则是对处理器的外部资源(iNand、网卡芯片…)、uboot环境(uboot命令、环境变量…)等初始化,并等待命令输入

工作流程

正常情况下,在 uboot 的初始化工作完毕后,会启动内核,在启动内核后结束 uboot 程序。

但是用户可以阻止 uboot 的结束,进入 uboot 的命令行模式,就是一个 uboot 中的死循环;uboot 在死循环中不断接受命令、解析命令、执行命令

start_armboot

如果说启动第一阶段主要工作是 lowlevel_init 完成的,那么启动第二阶段的主要工作是 start_armboot 完成的

相关变量介绍

init_fnc_ptr

它是一个 init_fnc_t 类型的函数指针数组,通过 typedef int (init_fnc_t) (void); 定义

gd

#define DECLARE_GLOBAL_DATA_PTR     register volatile gd_t *gd asm ("r8")

gd 这个变量通过 DECLARE_GLOBAL_DATA_PTR 定义,这个变量的作用是用来存储 uboot 需要使用到的全局变量,这样可以减少全局变量的数量,方便他人阅读代码。具体的作用可以从结构体成员中得知

typedef	struct	global_data {
	bd_t		*bd;
	unsigned long	flags;
	unsigned long	baudrate;
	unsigned long	have_console;	/* serial_init() was called */
	unsigned long	reloc_off;	/* Relocation Offset */
	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
	void		**jt;		/* jump table */
} gd_t;

gd 变量的结构体成员的功能基本可以从名字和注释中得知,不过对第一个成员 bd 的描述很少,这里再从 bd_t 中获取信息

typedef struct bd_info {
    int			bi_baudrate;	/* serial console baudrate */
    unsigned long	bi_ip_addr;	/* IP Address */
    unsigned char	bi_enetaddr[6]; /* Ethernet adress */
    struct environment_s	       *bi_env;
    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];
#ifdef CONFIG_HAS_ETH1
    /* second onboard ethernet port */
    unsigned char   bi_enet1addr[6];
#endif
} bd_t;

从 bd_t 的定义可以看出, bd 这个成员变量的主要作用是用来保存一些和板级相关的信息,如波特率、ip 地址等

因为 gd 这个变量需要被频繁访问,所以使用了 volatile ,避免编译器做出不适当的优化;另外使用 register asm (“r8”) 加速访问

内存排布

上面提到的 gd bd 指针变量只是声明,此时还不能使用,需要给他们分配内存空间

分配内存的原则就是够用、紧凑,所以首先需要做的是为 gd bd 分配基地址

在 uboot 中计算 gd 和 bd 的基地址的方式如下

gd_base = CFG_UBOOT_BASE + CFG_UBOOT_SIZE - CFG_MALLOC_LEN - CFG_STACK_SIZE - sizeof(gd_t);
#ifdef CONFIG_USE_IRQ
	gd_base -= (CONFIG_STACKSIZE_IRQ+CONFIG_STACKSIZE_FIQ);
#endif
...
gd->bd = (bd_t*)((char*)gd - sizeof(bd_t));

其中 CFG_UBOOT_BASE=0x33e00000 CFG_UBOOT_SIZE=2MB CFG_MALLOC_LEN=912KB CFG_STACK_SIZE=512KB gd_t=36字节 bd_t≈44字节

从而可以得到内存分配如图

uboot启动第二阶段分析_第1张图片

部分外设初始化工作

for (init_fnc_ptr = init_sequence; *init_fnc_ptr; ++init_fnc_ptr) {
		if ((*init_fnc_ptr)() != 0) {
			hang ();
		}
	}

这里初始化工作是通过一个 for 循环完成的,使用先前定义的 init_fnc_ptr 变量去指向一个预先定义的函数指针数组 init_sequence ,在 init_sequence 中存储了用来初始化外设的函数指针。通过 for 循环依次执行每一个初始化函数,一旦返回的值不为0,就停止程序的运行并报错

init_sequence 的定义如下

init_fnc_t *init_sequence[] = {
	cpu_init,		/* basic cpu dependent setup */
	board_init,		/* basic board dependent setup */
	interrupt_init,		/* set up exceptions */
	env_init,		/* initialize environment */
	init_baudrate,		/* initialze baudrate settings */
	serial_init,		/* serial communications setup */
	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 */
	display_dram_config,
	NULL,
};

cpu_init

cpu_init 只有当允许了中断的情况下才会做中断栈的设置工作,因为我们 uboot 中没有允许中断,所以这里相当于什么都没做

board_init

这里进行了网卡的初始化,本人使用的开发板使用的网卡芯片是 dm9000 ,所以使用了 dm9000_pre_init 进行预初始化操作

网卡的驱动是不需要修改的,关键是移植,dm9000_pre_init 需要做的事就是移植相关,就像 stm32 一样,使用 GPIO 前,需要进行初始化的工作,这里就是做这些的工作

除了网卡的初始化,这里还在 bd 中添加了机器码和参数内存地址

机器码是用来软件和开发板之间配对用的,避免将软件下载到错误的开发板导致损失。机器码是唯一的,需要向 uboot 进行申请,当然学习 uboot 的时候可以随意填写,只要 uboot 中配置的机器码和 linux 内核中的机器码一致即可

这里的参数地址中存放的是需要想内核中传送的字符串参数(bootargs)的地址, uboot 启动的时候是通过 r0 r1 r2 来传递参数,其中一个寄存器放的就是 bd->bi_boot_params 中的值

interrupt_init

这里是对定时器4做的相关初始化,进行了10ms的定时

env_init

这里是环境变量的初始化,通过 x210_sd.h 中定义的宏 CFG_ENV_IS_IN_AUTO 分析可以知道,env_init 应该使用的是 env_auto.c 中定义的 env_init

init_baudrate

首先是从 uboot 环境变量中读取波特率,如果环境变量中没有定义波特率就使用 x210_sd.h 定义的波特率

uboot 环境变量可以在 uboot 中使用 print 查看

serial_init

串口初始化,主要工作在汇编中完成了,这里没有做什么事

console_init_f

控制台初始化第一阶段,一般第一阶段后缀为 _f,第二阶段后缀为 _r

display_banner

用来输出 uboot 的 logo

const char version_string[] =
	U_BOOT_VERSION" (" __DATE__ " - " __TIME__ ")"CONFIG_IDENT_STRING;

uboot 启动时输出的 version_string 是定义在主 Makefile 中的,在编译时自动生成的 include/version_autogenerated.h 就会包含 U_BOOT_VERSION 相关信息

print_cpuinfo

这里可以输出 CPU 的相关信息

CPU:  S5PV210@1000MHz(OK)
        APLL = 1000MHz, HclkMsys = 200MHz, PclkMsys = 100MHz
        MPLL = 667MHz, EPLL = 96MHz
                       HclkDsys = 166MHz, PclkDsys = 83MHz
                       HclkPsys = 133MHz, PclkPsys = 66MHz
                       SCLKA2M  = 200MHz

dram_init

这里做的是 ddr 软件方面的初始化

因为嵌入式设备是定制性的,不像 PC 机都是标准化的,可以自动获取 ddr 的片数、大小

因此在 uboot 中,需要人为添加 ddr 的相关配置信息,相当于是将 ddr 的配置信息写入 gd->bd->bi_dram 这个数组中

display_dram_config

打印 dram 信息

堆管理器初始化

mem_malloc_init (CFG_UBOOT_BASE + CFG_UBOOT_SIZE - CFG_MALLOC_LEN - CFG_STACK_SIZE);

这样堆的地址如下图

uboot启动第二阶段分析_第2张图片

开发板专有初始化

为了保证 uboot 的通用性,三星使用了宏来判断当前的开发板的型号,并做对应的初始化

这里的初始化是针对 MMC 的,通过 Makefile 中的脚本,配置开发板是 sd 还是 onenand 启动;然后对 MMC 的初始化根据宏做相应的处理

环境变量重定位

uboot 的环境变量需要重定位到 ddr 中才能使用

在 env_relocate 中,先是通过宏判读环境变量是否嵌入在代码段中;本人的开发板的环境变量在 SD 卡中,所以直接跳过,直接分配一个缓存准备用来存放环境变量

uboot 第一次启动时,因为没有烧录 env 分区,gd->env_valid 应当为0,这时候会使用 uboot 的默认系统变量,并写入到 SD 卡的 env 分区中

控制台初始化

console_init_r 做的是控制台的第二阶段的初始化,主要是进行控制台的软件方面的配置

重定位了 stdin stdout stderr

网卡芯片初始化

eth_initialize 对网卡芯片本身进行初始化

开机logo显示

x210_preboot_init 进行开发板启动前的初始化,开启 lcd 显示开机 logo

logo 一般是使用软件制作的数组

通过sd卡烧录系统

通过 check_menu_update_from_sd 会判断开发部的 left 按键是否按下,开发板会在按键按下的时候,进入更新模式,读取 SD 卡中的镜像烧录系统

使用 SD 卡烧录比 fastboot 的方式简单、高效,一般用于量产的场景

死循环

主要是有三个功能

  • 命令解析器
  • 开机倒计时
  • 命令补全

总结

以下来自朱老师总结的 uboot 的工作

init_sequence
	cpu_init	空的
	board_init	网卡、机器码、内存传参地址
		dm9000_pre_init			网卡
		gd->bd->bi_arch_number	机器码
		gd->bd->bi_boot_params	内存传参地址
	interrupt_init	定时器
	env_init
	init_baudrate	gd数据结构中波特率
	serial_init		空的
	console_init_f	空的
	display_banner	打印启动信息
	print_cpuinfo	打印CPU时钟设置信息
	checkboard		检验开发板名字
	dram_init		gd数据结构中DDR信息
	display_dram_config	打印DDR配置信息表
mem_malloc_init		初始化uboot自己维护的堆管理器的内存
mmc_initialize		inand/SD卡的SoC控制器和卡的初始化
env_relocate		环境变量重定位
gd->bd->bi_ip_addr	gd数据结构赋值
gd->bd->bi_enetaddr	gd数据结构赋值
devices_init		空的
jumptable_init		不用关注的
console_init_r		真正的控制台初始化
enable_interrupts	空的
loadaddr、bootfile 	环境变量读出初始化全局变量
board_late_init		空的
eth_initialize		空的
x210_preboot_init	LCD初始化和显示logo
check_menu_update_from_sd	检查自动更新
main_loop			主循环

我的博客
公众号:greedyhao

你可能感兴趣的:(uboot启动第二阶段分析)