在start.s代码的414行跳转到start_armboot函数
一个很长的函数
(1)这个函数在uboot/lib_arm/board.c的第444行到908行结束
(2)450行还不是全部,里面还调用了别的函数
(3)这个函数整体构成了uboot启动的第二阶段
一个函数组成uboot的第二阶段
(1)uboot的第一阶段主要就是初始化了Soc内部的一些部件(譬如看门狗,系统时钟),然后初始化DDR并且完成重定位。
(2)宏观来说,uboot的第二阶段就是要初始化剩下的还没被初始化的硬件。主要是Soc外部硬件(譬如iNand,忘啦芯片)。最终初始化完必要的东西后进入uboot的命令行尊卑接收命令
uboot第二阶段完结何处?
(1)uboot启动后自动运行打印出来的信息(这些信息就是uboot在第一和第二阶段不断进行初始化时,打印出来的信息)。然后uboot进入了倒数bootdelay,然后执行bootcmd对应的启动命令
(2)如果用户没有干涉则会执行bootcmd进入自动启动内核流程(uboot就死掉了);此时用户可以按下回车键大端uboot的自动启动进入uboot的命令行下。然后uboot就一直工作在命令行下。
(3)uboot的命令行就是一个死循环,循环体内不断重复:接收命令,解析命令,执行命令
在开头发现的init_fnc_t
init_fnc_t
(1)typedef int (init_fnc_t) (void);这是一个函数类型
(2)init_fnc_ptr,这是一二重指针。二重指针的作用(其中一个指向一个一重指针,或者一个是指向一个数组)
gd_t
(1)在start_armboot的465行,定义了一个gd_t,查找gd_t,发现是在DECLARE_GLOBAL_DATA_PTR宏中,
#define DECLARE_GLOBAL_DATA_PTR register volatile gd_t *gd asm ("r8")
这个宏定义了一个全局变量,名字叫gd,这个全局变量是一个指针类型,占4字节。用volatile 修饰,代表是可变的,register 修饰,代表是用寄存器去执行,速度更快。asm (“r8”)是gcc支持的语法,意思是要把gd放到寄存器r8中。
(2)综合分析,DECLARE_GLOBAL_DATA_PTR就是定义了一个要放在寄存器r8中的全局变量,名字叫gd,类型是一个指向gd_s类型变量的指针。
(3)为什么要定义为register?因为这个全局变量gd(global data的简称)是uboot中很重要的一个全局变量(准确的说这个全局变量是一个结构体,里面有很多内容,这些内容加起来构成的结构体即使uboot中常用的所有的全局变量),这个gd在程序中经常被访问,因此放在register中提升效率。因此纯粹是运行效率方面考虑,和功能要求无关。
(4)在global data这个结构体中,
bd_t *bd; //bd_t,这个又定义了变量,这里面存的是我们开发板的硬件信息
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 */
(5)下面是在bd这个结构体中的代码,定义在inlcude/asm-arm/global_data.h中
int bi_baudrate; /* 我们开发板的波特率 */
unsigned long bi_ip_addr; /* 板子的IP地址 */
unsigned char bi_enetaddr[6]; /* Ethernet adress */
struct environment_s *bi_env; //环境变量的指针
ulong bi_arch_number; /* 板子的机器码 */
ulong bi_boot_params; /* 启动参数的地址 */
struct /* RAM configuration */
{
ulong start;
ulong size;
} bi_dram[CONFIG_NR_DRAM_BANKS];//板子的地址从什么地方开始,有多大,这里告诉的
总结:gd中定义了很多的全局变量,整个uboot使用的;其中有一个bd_t类型的指针,执行一个bd_t类型的变量,这个bd是开发板的板级信息的结构体,里面有不少硬件相关的参数,譬如波特率,IP地址,机器码,DDR内存分布。
从469行开始对gd分配内存
为什么要分配内存
(1)DECLARE_GLOBAL_DATA_PTR 只是定义了一个指针,也就是说gd里的这些全局变量并没有分配内存,我们在使用gd之前要给他分配内存,否则gd也只是一个野指针。
(2)gd和bd需要内存,内存当前没有被人管理(因为没有操作系统同一管理内存),大片的DDR内存散放着可以随意使用(只要使用内存地址直接去访问内存即可)。但是因为uboot中后续很多操作还需要大片的连着内存,采用紧凑排布的原则
内存排布
UBOOT区 | CFG_UBOOT_BASE-XX(长度为uboot的实际长度) |
---|---|
堆区 | 长度为CFG_MALLOC_LEN ,实际为912kb |
栈区 | 长度为CFG_STACK_SIZE ,实际为512KB |
gd | 长度为sizeof(gd_t),实际为36字节 |
bd | 长度为sizeof(bd-t),实际为44字节左右 |
内存间隔 | 是为了避免gcc的优化,导致程序错误 |
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 = (gd_t*)gd_base; //将我们的指针和我们实际的地址进行绑定
#else //CONFIG_MEMORY_UPPER_CODE
gd = (gd_t*)(_armboot_start - CFG_MALLOC_LEN - sizeof(gd_t));
#endif
这里是给我们的bd分配内存空间
/* 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));//因为内存是向下生长的,用gd的地址减去一个bd的长度,新地址作为我们bd的地址,至于为什么是char类型的,在C高级中有说道,char占1字节,减1就是减1,好算出其准确的地址,而且前面还强制类型转换为bd_t*类型,是为了类型匹配
memset (gd->bd, 0, sizeof (bd_t));
(1)第483行,我们看到了init_sequence,我们向上查找init_sequence到底是什么,,跳转到了当前文件的第416行
for (init_fnc_ptr = init_sequence; *init_fnc_ptr; ++init_fnc_ptr) {
if ((*init_fnc_ptr)() != 0) {
hang ();
}
}
(2)init_sequence是一个函数指针数组,数组中存储了很多个函数指针,这些指向的函数都是init_fnc_ptr类型(特征是接收参数都是void类型,返回值是int)
typedef int (init_fnc_t) (void);//这个是函数的定义,
//下面是部分源代码,函数数组指针,即二重指针
init_fnc_t *init_sequence[] = {
cpu_init, /* basic cpu dependent setup */
#if defined(CONFIG_SKIP_RELOCATE_UBOOT)
reloc_init, /* Set the relocation done flag, must
do this AFTER cpu_init(), but as soon
as possible */
#endif
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 */
(3)init_sequence在定义时就同时给了初始化,初始化的函数指针都是一些函数名(函数名的实质就是函数指针)
(4)init_fnc_ptr 是一个二重函数指针,可以指向init_sequence这个函数数组指针
(5)回到我们的start_armboot函数的483行,再次分析那个for循环
for (init_fnc_ptr = init_sequence; *init_fnc_ptr; ++init_fnc_ptr) {//*init_fnc_ptr,这里面实质是访问我们init_sequence数组中的元素,判断依据是是否为null,其实可以写为*init_fnc_ptr!=null
if ((*init_fnc_ptr)() != 0) {
hang ();
}
}
(6)用for循环肯定是想要去遍历这个函数指针数组(遍历的目的也是去依次执行这个函数指针数组中的所有函数。)如何遍历一个函数指针数组?有2种方法:第一种也是最常用的,用下标去遍历,用数组元素个数来截止。第二种不常用,但是也可以,就是在数组的有效元素末尾放一个标志,依次遍历到标准处即可。
我们这使用了第二种思路。因为数组中存的全是函数指针,因此我们选用了NULL来作为标志。我们编译时从开头依次进行。这个优势是不用实现统计数组有多少个元素!
(7)init_fnc_t的这些函数的返回值定义方式一样的,都是:函数执行正确时返回0,不正确时返回-1.所以我们在遍历时去检查函数返回值,如果遍历中有一个函数返回值不等于0则hang()挂起。从我们分析这个函数可知:uboot启动过程中初始化板级硬件时不能处任何错误,只要有一个错误整个启动就终止,除非重启开发板没有任何办法。
这里是从start_armboot的if ((*init_fnc_ptr)() != 0) 循环中init_fnc_ptr跳转到我们的Board.c的416行,执行初始化函数
cpu_init
(1)cpu_init函数应该是cpu内部的初始化,所以这里是空的。因为现在还在是uboot阶段。内核还没加载。
从上面的board_init跳转到具体的板子初始化后
board_init
(1)board_init在uboot\board\samsung\x210.c中,这个看名字就知道是x210开发板相关的初始化
(2)DECLARE_GLOBAL_DATA_PTR在这里声明就是为后面使用gd方便。可以看出把gd的声明定义成宏的原因就是我们要到处使用gd,因此就要到处声明,定义为宏方便很多。
smc9115_pre_init();
(3)网卡初始化。CONFIG_DRIVER_DM9000这个宏是x210.c中定义的,这个宏是用来配置网卡的。dm9000_pre_init函数就是对应的DM9000网卡的初始化函数。开发板移植uboot时,如果要移植网卡,主要的工作就在这里。
(4)这函数中主要的网卡的GPIO和端口的配置,而不是驱动。因为网卡的驱动都是现成的正确的,移植的时候驱动是不需要改动的,关键是这里的基本初始化。因为这些基本初始化时硬件相关的。
回到我们的board_init函数
gd->bd->bi_arch_number = MACH_TYPE;
(1)bi_arch_number是board info中的一个元素,含义是:开发板的机器码。所谓的机器码就是uboot给这个开发板定义的一个唯一编号。
(2)机器码的主要作用就是在uboot和linux内核之间进行比对和适配
(3)嵌入式设备中每一个设备的硬件都是定制化的,不能通用。嵌入式设备的高度定制化导致硬件和软件不能随便适配使用。这就告诉我们:这个开发吧移植的内核镜像不能随便给其它开发板使用。因此linux做了个设置:给每个开发板做了唯一编号(机器码),然后在uboot,linux内核中都有一个软件维护的机器码编号。然后开发吧,uboot,linux三者去比对机器码,如果机器码对不上就启动,就会启动不了(软件认为我这个和硬件不适配)
(4)这里的MACH_TYPE是2456,并没有特殊含义,只是当前开发板的机器码。
gd->bd->bi_boot_params = (PHYS_SDRAM_1+0x100);
(1)bd_info中另一个主要元素,bi_boot_params表示uboot给linux kernel启动时的传参的内存地址。也就是说uboot给linux内核传参的时候:uboot事先将尊卑好的传参(字符串,就是bootargs)放在内存的一个地址处(就是bi_boot_params),然后uboot就启动了内核(uboot在启动时真正的是通过寄存器r0,r1,r2来直接传递参数的,其中有一个寄存器中就是bi_boot_params就知道了uboot给我传递的参数到底在内存的哪里。然后自己去内存的那个地方去寻找bootargs)
(2)经过计算得知:X210中bi_boot_params的值为0x30000100,这个内存地址就被分配用来内核传参了。所以在uboot的其他地方使用内存时要注意,千万不要把这里淹没了。
关于DDR的配置
(1)board_init中除了网卡的初始化之外,剩下的2行用来初始化DDR。
(2)注意:这里的初始化DDR和汇编阶段lowlevel_init中初始化DDR是不同的。当时的硬件初始化,目的是让DDR可以开始工作。现在是软件结构中一些DDR相关的属性配置,地址设置的初始化,是纯软件层面的。
(3)程序员在移植uboot到一个开发板时,程序员自己在x210_sd.h中使用宏定义去配置出来的板子上DDR内存的信息,然后uboot只要读取这些信息即可。(实际上还有一条路:就是uboot自行读取硬件信息来知道DDR配置,但是uboot没有这样,实际上pc的BIOS采用的是这种)
从board_init函数的PHYS_SDRAM_1跳转
#define CONFIG_NR_DRAM_BANKS 2 /* we have 2 bank of DRAM */
#define SDRAM_BANK_SIZE 0x10000000 /* 512 MB lqm*/
//#define SDRAM_BANK_SIZE 0x20000000 /* 1GB lqm*/ 三星以前的
#define PHYS_SDRAM_1 MEMORY_BASE_ADDRESS /* SDRAM Bank #1 */
#define PHYS_SDRAM_1_SIZE SDRAM_BANK_SIZE
#define PHYS_SDRAM_2 MEMORY_BASE_ADDRESS2 /* SDRAM Bank #2 */
#define PHYS_SDRAM_2_SIZE SDRAM_BANK_SIZE
#define CFG_FLASH_BASE 0x80000000
interrupt_init
(1)初看以为是和中断相关的,但是不是,实际上这个函数是用来初始化定时器的(实际上使用的Timer4)
(2)210共有5个PWM定时器。其中timer0-timer3都有一个对应的PWM信号输出的引脚。而timer4没有引脚,无法输出PWM波形。timer4在设计的时候就不是用来输出PWM波形的(没有引脚,没有TCMPB寄存器),这个定时器被设计用来计时的。
(3)Timer4用来计时是要使用两个寄存器:TCNTB4,TCNTO4。TCNTB中存了两个数,这个数就是定时次数(每一次时间是由时钟决定的,其实就是由2级时钟分频器决定的)。我么定时时只需要把定时时间/基准时间=数,将这个数放入TCNTB中即可;我们通过TCNTO寄存器即可读取时间有没有减到0,读取到0后就知道定的时间到了。
(4)使用timer4来定时,因为没有中断支持,所以cpu不能做其他事情同时定时,CPU只能使用轮询方式来不断查看TCNTO寄存器才能知道定时时间到了没。因为TIMER4的定时是不能实现微观上的并行的。所以上uboot定时时不能做其他事情(典型就是bootdelay,bootdelay中实现定时并且检查用户输入是轮询方式实现的)
(5)interrupt_init函数将timer4设置为定时10ms。关键部位就是get_PCLK函数获取系统设置的PCLK_PSYS时钟频率,然后设置TCFG0和TCFG1进行分频,然后计算出设置为10ms时需要向TCNTB中写入的值,将其写入TCNTB4,然后设置为auto reload模式,然后开定时器开始计时就没了。
回到init_sequence跳转到env_init
(1)看名字知道和环境变量有关的初始化
(2)为什么有很多的env_init函数,主要原因是uboot支持各种不同的启动介质(譬如norflash,nandflash,inand,sd卡)我们一般从哪里启动就会把环境变量env放到哪里。而各种介质存取操作env的方法是不一样的。因此uboot支持了各种不同介质中env的操作方法。所以有好多个env_xx开头的c文件。实际使用的是那一个要根据自己开发板使用的存储介质来定(这些环境变量只有一个会起作用,其他是不能进去的,通过x210_sd.h中配置的宏来决定谁被包含),对于x210来说,我们应该看env_movi.c中的函数。
(3)经过基本分析,这个函数只是对内存里维护的那一份uboot的env做了基本的初始化或者说判定(判定里面有没有能用的环境变量)。当前因为我们还没进行环境变量从sd卡到DDR中的relocate,因此当前环境变量是不能用的。
(4)在start_armboot函数中(776行)调用env_relocate才进行环境变量从SD卡中到DDR中的重定位。重定位之后需要环境变量时才可以从DDR中去取,重定位之前如果要使用环境变量只能从SD卡去读取。
init_baudrate
(1)init_baudrate看名字就是初始化串口通信的波特率的。
(2)getenv_r函数用来读取环境变量的值。用getenv_r函数读取环境变量中baudrate的值(注意读取到的不是int型而是字符串类型),然后用simple_strtoul函数将字符串转成数字格式的波特率
(3)baudrate初始化的规则是:先去环境变量中读取“baudrate”这个环境变量的值。如果读取成则使用这个值作为环境变量,记录在gd->bd->bi_baudrate = gd->baudrate中,如果读取失败,则使用x210_sd.h中的CONFIG_BAUDRATE的值作为波特率。从这里可以看出:可变的环境变量的优先级很高。
init_sequence----serial_init
(1)看名字是初始化串口(在start.s中调用lowlevelnit.s中一级使用汇编初始化串口了,这里怎么又初始化?这两个初始化是重复哈市各自有不同?)
(2)SI中可以看出uboot有很多个serial_init函数,我们使用的是uboot/cpvs5pc11x/serial.c中的serial_init函数
(3)但是我们进来serial_init函数其实什么都没有。因为在汇编阶段串口已经被初始化了,因此这里就不能再进行硬件寄存器得到初始化了。
console_init_f
(1)console_init_f是console(控制台)的第一阶段初始化。f表示是第一阶段初始化,r表示第二阶段初始化。有时候初始化函数不能一次一起完成,中间必须要夹杂一些代码,因此将完整的一个模块的初始化分成了2个阶段
(2)console_init_f在uboot/common/console.c中,仅仅是对gd->have_conscle设置为1而已,其他事情没做。
gd->have_console = 1
display_banner
(1)display_banner用来串口输出显示uboot的logo
(2)display_banner中使用printf函数向串口输出version_string这个字符串。那么上面的分析表示console_init_f并没有初始化好console怎么就可以用printf了?
(3)通过追踪printf的实现,发现printf->puts,而puts函数会判断当前uboot中console有没有初始化好。如果console初始化好了就会调用fputs完成串口发送(这条线才是控制台)。如果console尚未初始化好则会调用serial_puts()再调用serial_putc直接操作串口寄存器进行内容发送)
(4)控制台也是通过串口输出,非控制台也是通过串口输出。究竟什么是控制台?和不用控制台的区别。控制台就是一个用软件虚拟出来的设备,这个设备有一套专用的通信函数(发送,接收…)控制台的通信函数最终会映射到硬件串口的通信函数中,也就是说uboot中用没有控制器其实并没有本质区别。
(5)但是在别的体系中,控制台的通信函数映射到硬件通信函数时可以用软件来做中间优化,譬如缓冲机制。我们输出的信息只是到了console的buffer中,buffer还没有被刷新到硬件输出设备上,尤其是在输出设备是LCD屏幕时)
(6)U_BOOT_VERSION在uboot源代码中找不到定义,这个变量实际上是在makefile中定义的,然后在编译时生成的include/version_autogenerated.h中用一个宏定义来实现的。
print_cpuinfo
(1)uboot启动过程中:
这些信息都是print_cpuinfo打印出来的。
checkboard
(1)checkboard看名字是检查,确认开发板的意思。这个函数的作用就是检查当前开发吧是哪个开发板并且打印出开发板的名字。
init_func_i2c
(1)这个函数实际没有被执行,x210的uboot中并没有使用I2C。如果将来我们的开发吧要扩展I2C来外接硬件,则在x210_sd.h中配置响应的宏即可开启
实践
(1)对uboot源代码进行完全修改(修改的内容根据自己的理解和分析来修改)
(2)make disclean然后make x210_sd_config然后make
(3)编译完后得到u-boot.bin,然后去烧写。烧录的方法按照裸机第三部分的linux下使用dd命令来烧写的方法来烧写。
(4)烧写过程:
第一步:进入sd_fusing目录下
第二步:make clean
第三步:make
第四步:插入SD卡,ls /dev/sd*得到SD卡在ubuntu中的设备号(一般是/dev/sdb,注意SD卡要连接到虚拟机ubuntu中,不要接到windows中)
第五步:./sd_fusing.sh /dev/sdb完成烧录
dram_init
(1)dram_init看名字是关于DDR的初始化。疑问:在汇编阶段已经初始化DDR了否则也无法relocate到第二部分运行,怎么在这里又初始化DDR?
(2)dram_init都再给gd->bd里面关于DDR配置部分的全局变量赋值,让gd->bd数据记录下当前开发板的DDR的配置信息,以便uboot中使用内存。
(3)从代码来看,其实就是初始化gd->bd->bi_dram这个结构体数组。
display_dram_config
(1)看名字意思就是打印显示dram的配置信息
(2)启动信息中的: (DRAM: 512MB)就是这个函数中打印出来的。
(3)uboot运行中得知uboot的DDR配置信息?uboot中有个命令叫bdinfo,这个命令可以打印出gd->bd中记录的所有硬件相关的全局变量的值,因此可以知道DDR的配置信息。
总结:都是板级硬件的初始化以及gd,gd->bd中的数据结构的初始化。譬如:网卡初始化,机器码(gd->bd->bi_arch_number),内核传参DDR地址(gd->bd->bi_boot_params),Timer4初始化为10ms一次,波特率设置(gd->bd->baudrate和gd->baudrate),sonsole第一阶段初始化(gd->have_console设置为1),打印uboot的启动信息,打印cpu相关的设置信息,检查并打印当前开发板名字,DDR配置信息初始化(gd->bd->bi_dram),打印DDR总容量。
CFG_NO_FLASH
(1)虽然NandFlash和NorFlash都是Flash,但是一般NandFlash会简称为Nand而不是Flash打印的也是NorFlash的配置信息(Flash: 8 MB就是这里打印出来的)。但是实际上x210中是没有Norflash的。所以是没有用的,至于为啥不删除,这个不清楚。
CONFIG_VFD–CONFIG_LCD
CONFIG_VFD–CONFIG_LCD和显示相关的,这个是uboot中自带的LCD显示额软件架构,但是实际上我们用LCD而没有使用uboot中设置的这套软件架构,我们自己在后面自己添加了一个LCD显示的部分。
mem_malloc_init
#ifdef CONFIG_MEMORY_UPPER_CODE /* by scsuh */
mem_malloc_init (CFG_UBOOT_BASE + CFG_UBOOT_SIZE - CFG_MALLOC_LEN - CFG_STACK_SIZE);
#else
mem_malloc_init (_armboot_start - CFG_MALLOC_LEN);
#endif
(1)mem_malloc_init函数用来初始化uboot的堆管理器。
(2)uboot中自己维护了一段堆内存,肯定自己就有一套代码来管理这个堆内存。有了这些东西uboot中你也可以malloc,free这套机制来申请内存和释放内存。我们在DDR内存中给堆预留了896KB的内存。
代码实践,看是否去掉CFG_NO_FLASH
(1)当我们定义CFG_NO_FLASH宏之后,编译出错,说明代码的移植性不行,和硬编码相关。那个文件的包含没有被这个宏包含。于是官方的人没有去解决这个问题。
start_armboot
(1)从536到768行为开发板独有的初始化,意思是三星用一套uboot同时满足了好多型号的开发板,然后在这里把不同开发板自己独有的一些初始化写到了这里。用#if条件编译配合599行到632行。
(2)x210相关的配置在599行到632行
(3)mmc_initialize看名字就应该是MMC相关的一些基础的初始化,其实就是用来初始化Soc内部的SD/MMC控制器的。函数在uboot/drivers/mmc/mmc.c里
(4)uboot中对硬件的操作(譬如网卡,SD卡。。。)都是借用linux内核中的驱动来实现的,uboot根目录下有个drivers文件夹,这里面放的全都是从linux内核移植过来的各种驱动文件。
(5)mmc_initialize视具体硬件架构无关的一个MMC初始化函数,所有的使用了这套架构的代码都调用这个函数进行MMC得到初始化。mmc_initialize中再调用board_mmc_init和cpu_mmc_init来完成具体的硬件的MMC控制器初始化工作。
(6)cpu_mmc_init在uboot/cpu/s5pc11x/cpu.c中。在mmc_initialize跳转到cpu_mmc_init。在里面又间接的调用了drivers/mmc/s3c_mmcxxx.c中的驱动代码来初始化硬件MMC控制器。这里面分层很多。
776行env_relocate
(1)env_relocate是环境变量的重定位,完成从SD卡中将环境变量读取到DDR中的任务。
(2)环境变量从哪里来?SD卡中有一些(8个)独立的扇区作为环境变量存储区域的。但是我们烧录/部署系统的时候,我们只烧录了uboot分区,kernel分区和rootfs分区,根本不曾烧录env分区,所以当我们烧录完系统第一次启动时ENV分区是空的,本次启动uboot尝试去SD卡的ENV分区读取环境变量是失败(读取回来后进行CRC校验时失败),我们uboot选择从uboot内部代码中设置的一默认的环境变量出来时使用(这就是默认环境变量);这套默认的环境变量在本次运行时会被读取到DDR中的环境变量中(可能是你saveerv时写入,也可能是uboot设计了第一次读取默认环境变量后就写入),然后被写入SD卡的ENV分区。然后下次开机时uboot就会从SD卡的ENV分区读取环境变量到DDR
start_armboot–env_relocate–265set_default_env
(1)一些默认环境变量
start_armboot–env_relocate–env_relocate_spec–movi_read_env
(1)真正的SD卡到DDR重定位ENV的代码
void movi_read_env(ulong addr)
{
//raw_area_control原始分区信息表
movi_read(raw_area_control.image[2].start_blk,//读取ENV的一个起始扇区号
raw_area_control.image[2].used_blk, addr);//used_blk已经使用的部分
}
788getenv_IPaddr
(1)开发板的IP地址是gd->bd中维护的,来源于环境变量ipaddr。getenv函数用来获取字符串格式的IP地址,然后用string_to_ip将字符串格式的IP地址转成字符串格式的点分十进制格式。
(2)IP地址是由4个0-255之间的数字组成,因此一个IP地址在程序中最简单的存储方式是unsigend int。但是我们一般看的收拾点分十进制类型(192.168.1.2)。这两种类型可以互相转换。
IPaddr_t getenv_IPaddr (char *var)
{
return (string_to_ip(getenv(var)));
}
818devices_init
(1)devices_init是设备的初始化。这里的设备就是开发吧上的硬件设备。放在这个函数里面的都是驱动设备,这个函数本来就是从驱动框架中衍生出来的。uboot中很多设备的驱动是直接移植的linux内核中的(譬如网卡,SD卡等),linux内核中的驱动都有相应的设备初始化函数。linux内核在启动的过程中就有一个devices_init函数,作用就是集中执行各种硬件驱动的init函数。
(2)uboot的这个函数就是从linux内核中移植过来的,它的作用也是去执行所有的从linux内核中继承来的那些硬件驱动初始化函数。
824jumptable_init
(1)jumptable跳转表,本身是一个函数指针数组,里面记录了很多函数的函数名。主要是实现一个函数指针到具体函数的映射关系,将来通过跳转表中的函数指针就可以执行具体的函数,这个其实就是在用C语言实现面向对象编程。在linux内核中有很多的这种技巧。
(2)通过分析跳转表只是被赋值但是没有被引用,因此跳转表在uboot中没有使用,只是定义了。
console_init_r
(1)console_init_r是控制台的第一阶段初始化,console_init_r是第二阶段初始化。实际上第一阶段初始化。实际上第一阶段初始化并没有实质化工作,第二阶段初始化才进行实质性工作。
(2)uboot中很多同名函数,使用SI工具去索引经常引到不对的函数处,
enable_interrupts
(1)看名字应该是中断初始化代码。这里指的是CPSR中中断标志位的使能。
(2)因为我们uboot中没有使用中断,因此没有定义CONFIG_USE_IRQ宏,因此我们这里这个函数是一个空壳子
(3)uboot中经常出现一种情况就是根据一个宏是否定义来条件编译决定是否调用一个函数内部的代码。uboot中有2中解决方案来处理这情况:方案一:在调用函数处使用条件编译,然后函数体实际完全提供代码。方案二:在调用函数处直接调用,然后在函数体处提供2个函数体,一个是有实体的,一个是空壳,用宏定义编译来决定编译时编译哪个函数进去。
859load_addr–BootFile两个环境变量
(1)这两个环境变量都是内核启动有关的,在启动linux内核时会参考这个两个环境变量的值
board_late_init
(1)开发板后期初始化,指的是前面的该初始化的都初始化了,剩下的初始化,就放在里面,但是前面的宏定义未满足,为空。
872eth_initialize
(1)是网卡相关的初始化,这里不是Soc与网卡芯片连接时Soc这边的初始化,而是网卡芯片本身的一些初始化。
(2)对于x210(DM9000)来说,这个函数是空的。x210的网卡初始化在board_init函数中,网卡芯片的初始化在驱动中。
877x210_preboot_init(LCD和logo显示)
(1)uboot启动的最后阶段设计了一个自动更新的功能。就是:我们可以将要升级的镜像放到SD卡的固定目录,然后开机时在uboot启动的最后阶段检查升级标志(就是一个按键标志位:LEFT按键,这个按键如果按下则表示update mode,如果启动时未按下则表示boot mode)。如果进入update mode则uboot会自动从SD卡中读取镜像文件然后烧录到iNand中;如果进入boot mode则uboot不会执行update,直接启动正常执行。
(2)这种机制能够帮我们快速烧录系统,常用于量产时用SD卡进行系统烧录部署。
死循环904main_loop
(1)解析器CFG_HUSH_PARSER
(2)开机倒数自动执行
(3)命令补全
启动流程回顾,重点函数
(1)先是对内存进行了初始化,
(2)init_sequence
cpu_init 空
board_init 网卡,机器码,内存传参的地址
dm9000_pre_init 网卡
gd->bd->bi_arch_number 机器码
gd->bd->bi_boot_params 传参的地址
interrupt_init 定时器timer4
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 环境重定位,也就是将SD卡的环境变量读到DDR中
gd->bd->bi_ip_addr gd数据结构赋值
gd->bd->bi_enet1addr gd数据结构赋值
devices_init 空的
jumptable_init 我们没有使用
console_init_r 真正的控制台的初始化,但是内部和串口操作没啥区别
enable_interrupts 没用
bootfile–loadaddr 环境变量读出初始化全局变量
board_late_init 空的
eth_initialize 空的
x210_preboot_init LCD初始化和显示logo
check_menu_update_from_sd 检查自动更新,实现量产
main_loop 工作的主循环
启动过程特征总结
(1)第一阶段为汇编阶段(除了读取SD卡那段,其余都是汇编),第二阶段为C语言阶段
(2)第一阶段在SRAM,第二阶段在DRAM中
(3)第一阶段注重Soc内部,第二阶段注重Soc外部Board内部
移植时的注意点
(1)x210_sd.h头文件的宏定义
(2)特定硬件的初始化函数位置(譬如网卡)