UBOOT1.3.4 学习笔记(五) uboot启动第二阶段--start_armboot详解

uboot启动第二阶段要做的事情:
宏观上来讲,就是要初始化剩下的还未被初始化的硬件,主要是SoC外部硬件(网卡,iNand等)和uboot本身的一些东西(uboot命令,环境变量等).

uboot在何处完结:

  • uboot启动后自动运行,打印一些信息(这些信息是uboot第一阶段和第二阶段初始化时打印的),然后uboot进入bootdelay,倒数完后执行bootcmd的启动命令,启动OS后uboot就消亡了。
  • 在uboot的bootdelay时,用户可以选择按下回车打断uboot进入uboot的命令下,然后uboot就一直工作在命令行下(直到用户输入启动命令,启动OS,然后uboot消亡)
  • uboot的命令行是一个死循环,不断重复接收、解析、执行命令。

下面开始uboot第二阶段详解:
uboot第一阶段完成后会自动跳转到start_armboot函数:

  • 这个函数整个构成了uboot启动的第二阶段。
  • 这个函数在uboot/lib_arm/board.c的第444行开始到908行结束

1.定义了多个数据类型:

其中难以理解的:

init_fnc_t **init_fnc_ptr;

这个二重函数指针在另一篇文章里分析过了

2.分配内存:

#define DECLARE_GLOBAL_DATA_PTR     register volatile gd_t *gd asm ("r8")
  1. 定义了一个全局变量gd,这个全局变量是指针类型,指向gd_t类型
    (1) gd_t定义在include/asm-arm/global_data.h中,是一个结构体
    (2)gd_t中定义了很多全局变量,都是整个uboot使用的;其中有一个bd_t类型的指针,指向一个bd_t类型的变量,这个bd是开发板的板级信息的结构体,里面有不少硬件相关的参数,譬如波特率、IP地址、机器码、DDR内存分布。

  2. volatile修饰表示可变,register修饰表示优先放在寄存器中

  3. 后面的asm(“r8”)是gcc支持的一种语法,意思就是要把gd放到寄存器r8中

下面进行了内存分配:

/* Pointer is writable since we allocated a register for it */
#ifdef CONFIG_MEMORY_UPPER_CODE /* by scsuh */
   ulong gd_base;    #gd在ddr中的基地址

   #计算gd_base
   gd_base = CFG_UBOOT_BASE + CFG_UBOOT_SIZE - CFG_MALLOC_LEN - CFG_STACK_SIZE - sizeof(gd_t);
   gd = (gd_t*)gd_base; #

   /* compiler optimization barrier needed for GCC >= 3.4 */
   __asm__ __volatile__("": : :"memory"); #为了防止高版本的gcc的优化造成错误
   
   #清理内存
   memset ((void*)gd, 0, sizeof (gd_t));
   gd->bd = (bd_t*)((char*)gd - sizeof(bd_t));
   memset (gd->bd, 0, sizeof (bd_t));

分配内存的原因:
(1)DECLARE_GLOBAL_DATA_PTR只能定义了一个指针,也就是说gd里的这些全局变量并没有被分配内存,我们在使用gd之前要给他分配内存,否则gd也只是一个野指针而已。
(2)gd和bd需要内存,内存当前没有被人管理(因为没有操作系统统一管理内存),大片的DDR内存散放着可以随意使用(只要使用内存地址直接去访问内存即可)。但是因为uboot中后续很多操作还需要大片的连着内存块,因此这里使用内存要本着够用就好,紧凑排布的原则。所以我们在uboot中需要有一个整体规划。

内存排布:
(1)uboot区 CFG_UBOOT_BASE-xx(长度为uboot的实际长度)
(2)堆区 长度为CFG_MALLOC_LEN,实际为912KB
(3)栈区 长度为CFG_STACK_SIZE,实际为512KB
(4)gd 长度为sizeof(gd_t),实际36字节
(5)bd 长度为sizeof(bd_t),实际为44字节左右
(6)内存间隔 为了防止高版本的gcc的优化造成错误。
计算出不同区的大概大小,各种宏定义在x210_sd.h中

3.进入

for (init_fnc_ptr = init_sequence; *init_fnc_ptr; ++init_fnc_ptr) {
			if ((*init_fnc_ptr)() != 0) {
				hang ();
			}
		}
  • 在init_sequence函数指针数组里存储了多个函数,这些指向指向的函数都是init_fnc_t类型(特征是接收参数是void类型,返回值是int)
  • init_sequence在定义时就同时给了初始化,初始化的函数指针都是一些函数名(即首地址即函数指针)
  • init_sequence中的这些函数,都是board级别的各种硬件初始化

接下来介绍init_sequence中各种硬件的初始化函数:

1.cpu_init
cpu内部的初始化,第一阶段已经做过了,所以这里为空

2.board_init
开发板相关的初始化

int board_init(void)
{
	DECLARE_GLOBAL_DATA_PTR;
#ifdef CONFIG_DRIVER_DM9000
	dm9000_pre_init();   #网卡初始化
#endif

	gd->bd->bi_arch_number = MACH_TYPE;
	gd->bd->bi_boot_params = (PHYS_SDRAM_1+0x100);

	return 0;
}
  1. 网卡初始化
  2. 这个函数中主要是网卡的GPIO和端口的配置(不是驱动,驱动是现成的,移植即可)
  3. bi_arch_number是board_info中的一个元素,含义是:开发板的机器码。所谓机器码就是uboot给这个开发板定义的一个唯一编号,机器码的主要作用就是在uboot和linux内核之间进行比对和适配
  4. bi_boot_params表示uboot给linux kernel启动时的传参的内存地址。
    (1)uboot事先将准备好的传参(字符串,就是bootargs)放在内存的一个地址处(就是bi_boot_params),然后uboot就启动了内核(uboot在启动内核时真正是通过寄存器r0 r1 r2来直接传递参数的,其中有一个寄存器中就是bi_boot_params)。内核启动后从寄存器中读取bi_boot_params就知道了uboot给我传递的参数到底在内存的哪里。然后自己去内存的那个地方去找bootargs。
    (2)经过计算得知:X210中bi_boot_params的值为0x30000100,这个内存地址就被分配用来做内核传参了。所以在uboot的其他地方使用内存时要注意不能重复使用该地址

3.interrupt_init
这个函数是用来初始化定时器Timer4(PWM定时器)的,这个定时器没有中断功能,CPU通过轮询方式查看TCNTO寄存器来确定时间到没到,设置方法和裸机差不多:通过get_PCLK函数得到PCLK_PSYS时钟频率,设置TCFG0,TCFG1分频,然后计算出设置10ms需要的值写入TCNTB,然后设置为autoload模式,最后开始计时。

4.env_init
环境变量的初始化,该函数只是对内存里那一份uboot的env做了判断是否存在,因为当前还没把环境变量从SD卡重定位到DDR,所以环境变量还不用

5.init_baudrate
波特率初始化,

static int init_baudrate (void)
{
	char tmp[64];	/* long enough for environment variables */
	int i = getenv_r ("baudrate", tmp, sizeof (tmp));
	gd->bd->bi_baudrate = gd->baudrate = (i > 0)
			? (int) simple_strtoul (tmp, NULL, 10)
			: CONFIG_BAUDRATE;

	return (0);
}
  • 用getenv函数读取环境变量中“baudrate”的值(注意读取到的不是int型而是字符串类型),然后用simple_strtoul函数将字符串转成数字格式的波特率
  • baudrate初始化时的规则是:先去环境变量中读取"baudrate"这个环境变量的值。如果读取成功则使用这个值作为环境变量,记录在gd->baudrate和gd->bd->bi_baudrate中;如果读取不成功则使用x210_sd.h中的的CONFIG_BAUDRATE的值作为波特率。从这可以看出:环境变量的优先级是很高的。

6.serial_init
串口初始化,第一阶段做过了,这里啥也没干
7.console_init_f

  • 控制台第一阶段初始化
  • _f表示第一阶段,_r表示第二阶段,对于一次不能初始化完的函数会分两个
  • 这个函数仅仅对gd->have_console置1,其他没做啥

8.display_banner

  • 用串口输出uboot的logo
  • 这个函数内部使用了printf函数。但追踪发现实际使用puts函数,还是串口输出(因为控制台还没初始化完)
  • 控制台的通信函数映射到硬件通信函数时可以用软件来做一些中间优化,譬如说缓冲机制,但这个uboot控制台的通信函数是直接映射到硬件串口的通信函数中的,也就是说uboot中用没用控制器其实并没有本质差别

9.printf_cpuinfo
打印CPU信息,通过各种软件计算得到的,和裸机时钟计算差不多
10.check_board
打印开发板信息
11.init_func_i2c

这个函数没有执行,因为 x210的uboot没有使用IIC,如果外接,可以通过开启宏来实现

12.dram_init
DDR初始化,在第一阶段已经初始化过了,这里是给gd->bd里面的关于DDR的全局变量赋值,让gd->bd记录当前开发板的DDR配置信息,以便UBOOT中使用内存,时间上就是初始化gd->bd->bi_dram这个结构体数组

13.display_dram_config
打印dram的配置信息

另:运行uboot时通过bdinfo命令可以打印出gd->bd中关于所有硬件的全局变量值
至此,init_sequence中所有函数结束,这里面的函数都是板级硬件的初始化以及gd ,gd->bd中数据结构的初始化

接下来返回start_armboot 487行继续:

norflash相关初始化:

#ifndef CFG_NO_FLASH    #x210没有norflash,这两行只是打印了信息,并没有什么用
		/* configure available FLASH banks */
		size = flash_init ();
		display_flash_config (size);
#endif /* CFG_NO_FLASH */

因为x210没有norflash,这个没用

LCD显示相关初始化:

#ifdef CONFIG_VFD
。。。
#endif /* CONFIG_VFD */

#ifdef CONFIG_LCD
。。。
#endif /* CONFIG_LCD */

这个是uboot中自带的LCD显示的软件架构。但是实际上我们用LCD而没有使用uboot中设置的这套软件架构,后面添加了一个LCD显示的部分

初始化UBOOT堆管理器:

#ifdef CONFIG_MEMORY_UPPER_CODE /* by scsuh */
		mem_malloc_init (CFG_UBOOT_BASE + CFG_UBOOT_SIZE - CFG_MALLOC_LEN - CFG_STACK_SIZE);
#else

DDR中预留了内存给堆使用,这套代码来管理这个堆内存。有了这些东西uboot中就可以用malloc、free这套机制来申请内存和释放内存

MMC初始化:

开发板独有的初始化,从536—768行都是相关开发板的mmc初始化,通过宏定义选取自己开发板相关的部分

#if defined(CONFIG_X210)

		#if defined(CONFIG_GENERIC_MMC)
			puts ("SD/MMC:  ");
			mmc_exist = mmc_initialize(gd->bd);
			if (mmc_exist != 0)
			{
				puts ("0 MB\n");

			}
		#endif
		#if defined(CONFIG_CMD_NAND)
			puts("NAND:    ");
			nand_init();
		#endif

#endif /* CONFIG_X210 */

初始化soc内部的sd/mmc控制器。

环境变量重定位:

		/* initialize environment */
		env_relocate ();
  • 完成环境变量从SD卡读取到DDR中的任务
  • SD卡中有一些(8个)独立的扇区作为环境变量存储区域的。但是我们烧录/部署系统时,我们只是烧录了uboot分区、kernel分区和rootfs分区,没有烧录env分区。所以第一次启动时ENV分区是空的,本次启动uboot尝试去SD卡的ENV分区读取环境变量时失败(读取回来后进行CRC校验时失败),我们uboot选择从uboot内部代码中设置的一套默认的环境变量出发来使用(这就是默认环境变量);这套默认的环境变量在本次运行时会被读取到DDR中的环境变量中,然后被写入(也可能是你saveenv时写入,也可能是uboot设计了第一次读取默认环境变量后就写入)SD卡的ENV分区。然后下次再次开机时uboot就会从SD卡的ENV分区读取环境变量到DDR中,这次读取就不会失败了。
  • 真正的从SD卡到DDR中重定位ENV的代码是在env_relocate_spec内部的movi_read_env完成的。

IP,MAC地址的确定

  • 开发板的IP地址是在gd->bd中维护的,来源于环境变量ipaddr
  • getenv函数用来获取字符串格式的IP地址,然后用string_to_ip将字符串格式的IP地址转成字符串格式的点分十进制格式

设备初始化

devices_init ();	/* get the devices list going. */
  • 开发板上硬件设备的初始化,放在这里的都是驱动设备初始化
  • uboot的这个函数其实就是从linux内核中移植过来的,它的作用也是去执行所有的从linux内核中继承来的那些硬件驱动的初始化函数

jumptable_init跳转表

作用未知

控制台第二阶段初始化

#if !defined(CONFIG_SMDK6442)
		console_init_r ();	/* fully init console as a device */
  • 控制台纯软件架构方面的初始化,就是给相关数据结构填充值。
  • uboot的console实际上并没有干有意义的转化,它就是直接调用的串口通信的函数

中断初始化

		/* enable exceptions */
		enable_interrupts ();
  • CPSR中断标志位使能
  • uboot中没有使用中断,这里的函数是个空壳

loadaddr、bootfile两个环境变量

这两个环境变量是内核启动相关的,启动内核时会参考这两个的值

board_late_init ():

#ifdef BOARD_LATE_INIT
		board_late_init ();
#endif

最后没有初始化的且必须初始化的硬件设备都在这里做,对于x210来说这里为空

网卡初始化

		eth_initialize(gd->bd);

(1)网卡相关的初始化。这里不是SoC与网卡芯片连接时SoC这边的初始化,而是网卡芯片本身的一些初始化。
(2)对于X210(DM9000)来说,这个函数是空的。X210的网卡初始化在board_init函数中,网卡芯片的初始化在驱动中

x210_preboot_init():

#ifdef CONFIG_MPAD
		extern int x210_preboot_init(void);
		x210_preboot_init();
#endif

x210开发板启动起来之前的一些初始化,以及LCD屏幕上的LOGO显示

自动更新功能

		/* check menukey to update from sd */
		extern void update_all(void);
		if(check_menu_update_from_sd()==0)//update mode
		{
			puts ("[LEFT DOWN] update mode\n");
			run_command("fdisk -c 0",0);
			update_all();
		}
		else
			puts ("[LEFT UP] boot mode\n");
  • 可以将要升级的镜像放到SD卡的固定目录中,然后开机时在uboot启动的最后阶段检查升级标志(是一个按键。按键中标志为"LEFT"的那个按键,这个按键如果按下则表示update mode,如果启动时未按下则表示boot mode)。如果进入update mode则uboot会自动从SD卡中读取镜像文件然后烧录到iNand中;如果进入boot mode则uboot不执行update,直接启动正常运行。
  • 这种机制能够帮助我们快速烧录系统,常用于量产时用SD卡进行系统烧录部署,非必须。

死循环

for (;;) {
			main_loop ();
		}
  • uboot第二阶段到此结束
  • main_loop里面实现解析器,开机倒数自动执行,命令补全等功能

第二阶段总结:

  1. 第二阶段主要是对开发板硬件,软件数据结构进行初始化
  2. 主要函数如下:

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 主循环

UBOOT启动过程总结

  1. 第一阶段为汇编编程,第二阶段主要为C,内嵌少量汇编
  2. 第一阶段代码在SRAM中,第二阶段在DDR(DRAM)中
  3. 第一阶段主要对SoC内部初始化,第二阶段对SoC外部初始化

你可能感兴趣的:(YT的学习笔记)