由第一阶段,已经将在SD卡的整个uboot重定位拷贝到了DDR的链接地址中去后,又回到了调用这个函数的位置
。接着往下执行遇到了这个start_armboot函数(uboot启动的第二阶段)。
uboot启动的第二阶段分析:
6、1、start_armboot函数
(1)一个很长的函数,从444行到908行。这个函数不仅仅只有这么长,这个函数的内部还调用了一些函数,所以说这个函数是很庞大的。这个函数构成了整个uboot启动的第二个阶段。
(2)宏观上分析,uboot启动的第二阶段应该做什么?
(3)uboot的第一阶段主要完成的任务就是,初始化SOC内部的一些部件(关看门狗,初始化时钟等),也初始化了DDR并且将整个uboot重定位到了DDR中去。
(4)所以uboot的第二阶段应该要做的就是,去初始化一些在uboot第一阶段没有初始化的硬件,主要是初始化SOC外部的硬件(,如iNand、网卡芯片····),还有uboot本身的东西(uboot的命令,环境变量等·····),然后最终完成这些任务时,进入到uboot的命令行,准备接受命令,人机开始交互。
(5)uboot启动时是开机自动启动的,在启动的过程中,会打印出来很多的信息(这些信息就是uboot在第一阶段和第二阶段不断进行初始化时,打印出来的信息,我通过这些信息也可以跟踪uboot的轨迹,看uboot执行是否成功运行。)然后uboot开始bootdelay倒数,如果在倒数的过程中,我们敲了回车,则会进入到uboot的命令行下,如果我们没有干涉bootdelay月的倒数,倒数结束后,将直接执行bootcmd对应的命令(一般是启动内核命令),uboot就会死掉了。不管内核这次是否启动成功,uboot这一生都已经死掉了。
(6)uboot的命令行就是一个死循环,这个循环体内,不断的接受命令,解析命令,执行命令,直到接收解析执行了启动内核命令,uboot就会死掉。
6、2、start_armboot函数解析1
1、init_fnc_t 通过搜索可以知道这是一个函数类型。
(1)typedef int (init_fnc_t) (void); 这是将一个返回值为int的,参数为void的函数类型,重命名为init_fnc_t,所以这个init_fuc_t是一个函数类型。
(2)init_fnc_t **init_fnc_ptr; 所以这句话的意思就是定义了一个二重的函数指针。
init_fnc_ptr就是一个二重函数指针。回顾学过的高级C语言,二重指针有两个作用,一个是用来指向一重指针,一个是用来指向指针数组(数组中的没个元素都是指针)。
所以这个init_fnc_ptr这个二重函数指针,可以用来指向一个函数指针数组(数组里面的元素全是函数指针)
2、board.c文件中,开始的内容有个下面的东西
(1)DECLARE_GLOBAL_DATA_PTR;
//#define DECLARE_GLOBAL_DATA_PTR register volatile gd_t *gd asm ("r8")
(2)这个宏的意思就是,定义了一个全局变量gd,这个全局变量gd是一个指针,用volatile修饰,表示这个变量是可变的,在编译之外的情况下,这个变量的值是可以改变的,不让编译对这个变量做优化,用register修饰,表示让这个变量尽量放在寄存器中,提高速度,asm ("r8")意思是,让这个变量放在寄存器的r8中,就是不仅让这个变量放在了寄存器中,还要让这个变量放到的寄存器是寄存器r8中。
(3)综合分析:DECLARE_GLOBAL_DATA_PTR 这个宏的意思就是,定义了一个放在寄存器r8中,并且值是可以在编译之外改变的全局变量,名字叫gd,并且这个全局变量是一个指针类型,指向gd_t类型变量的指针。
(4)为什么要用register修饰这个全局变量呢,因为这个全局变量是一个gd_t * 类型的,是一个指向结构体的指针。同时这个全局变量gd(global data)指向的那个结构体中放了很多内容,组成了这个结构体,这些内容就是uboot中常用的全局变量。
也就是说,这个gd全局变量所指向的那个结构体中放的内容是uboot常用的全局变量。也就是说,那个把那个放了uboot常用的全局变量的结构体的地址放再了gd这个全局变量指针中。gd这个指针本身占4个字节。
所以这个gd全局变量是会经常被访问的,所以为了提升程序的效率,用register来修饰
(5)gd_t这个结构体类型定义在include/asm-arm/global_data.h中,这个结构体类型中,第一个又是定义了一个结构体类型的指针变量,这个结构体类型bd_t定义在include/asm-arm/U-boot.h中,找到bd_t这个结构体类型的封装,可以看着个封装的名字bd_info,知道这个结构体封装的是开发板相关的硬件信息。
(6)typedef struct global_data {
bd_t *bd; //结构体类型指针变量,指向一个struct bd_info结构体,里面封装了开发板的
//硬件相关信息
unsigned long flags; //标志位,干嘛的现在不清楚
unsigned long baudrate; //波特率 因为uboot要用串口发送到人机界面上,实现人机交互,所以需要波特率
//都是全局的,因为都是由gd这个全局变量指针开始指向的。控制台的波特率
unsigned long have_console; /* serial_init() was called */ //bool类型的变量,虽然定义是四字节,但
//是后面会知道只用了一个位,可以看出应该是代表有无控制台的,就是能否进行printf scanf ,在控制台建立起来以后就可以printf scanf了,不然只能用串口的putc等
unsigned long reloc_off; /* Relocation Offset */ //重定位时候的偏移量
unsigned long env_addr; /* Address of Environment struct */ //封装环境变量那个结构体的偏移量
//地址
unsigned long env_valid; /* Checksum of Environment valid? */ //在内存里的环境变量当前是可以使用的
//还是不可以使用的。也是一个bool类型的变量,如果是1就表示
//内存中的那一份环境变量已经可以使用了。
//为什么要检查内存中那一份环境变量是否可以使用呢,因为开始的时候我们的
//环境变量是放在SD卡中,当我们把在SD卡中的环境变量读取到内存中的时候,这些个环境
//变量是不能马上建立好的,因为内存中开始没有的,所以要检查,等所有的环境变量从SD卡
//读取到内存中,建立好后,我们才可以使用内存中那一份环境变量
unsigned long fb_base; /* base address of frame buffer */ //显存空间的起始地址,因为在裸机中的LCD
//那一章我们知道显存空间中的数据图像,会通过lcd控制器自动硬件刷到lcd液晶屏上显示,
//所以需要一个显存空间的起始地址
#ifdef CONFIG_VFD //这个宏没有定义
unsigned char vfd_type; /* display type */
#endif
#if 0 //用0注释掉了
unsigned long cpu_clk; /* CPU clock in Hz! */
unsigned long bus_clk;
phys_size_t ram_size; /* RAM size */
unsigned long reset_status; /* reset status register at boot */
#endif
void **jt; /* jump table */ //调转表,在uboot中应该是没有用到
} gd_t;
/***************************************************************************/
typedef struct bd_info {
int bi_baudrate; /* serial console baudrate */ //开发板上的波特率,这个波特率和上面的那
//个波特率设置为一模一样的,可以能是重复了
//bi表示是在bd_info这个结构体中的。也有
//告诉我们这些是跟开发板板上有关的
unsigned long bi_ip_addr; /* IP Address */ //开发板的IP地址
unsigned char bi_enetaddr[6]; /* Ethernet adress */ //开发板的MAC地址
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]; //开发板上DDR的信息,定义了一个结构体数组变量
//这个结构体数组bi_dram有两个元素,CONFIG_NR_DRAM_BANKS值
//是看代码知道的,为什么是2呢,因为我们有两个DDR,每一个
//元素是一个结构体,里面有两个信息一个是在哪里个地址开始,
//一个是有多大。一个元素表示一个DDR
#ifdef CONFIG_HAS_ETH1 //这个宏我们有定义
/* second onboard ethernet port */ //两个网卡的意思,但是上面的宏没有定义
unsigned char bi_enet1addr[6];
#endif
} bd_t; //将这封装了很多东西的结构体类型重命名为bd_t
6、3、内存使用排布
1、为什么要分配内存给gd和bd
(1)#define DECLARE_GLOBAL_DATA_PTR register volatile gd_t *gd asm ("r8")
gd这个只是一个指针变量,而且他不在占内存,被分配到寄存器中去了,也就是说gd所指向的那个结构体中环境变量并没有被分配内存。所以我们在使用gd之前,要给他分配内存,否则gd也只是一个野指针而已,但是现在是在裸机程序下,不是在操作系统下,所以没有办法使用malloc去给gd分配内存。裸机程序下没有malloc管理器,没有操作系统,无法用malloc分配内存。
(2)gd和bd需要内存,但是当前没有操作系统去管理内存,不能malloc去申请内存。在DDR中现在有大片的内存可以使用,我们只需要直接去用内存地址访问内存即可。但是DDR中的内存我们在uboot中不能随意的去使用,因为uboot会被反复使用的,比如如果我们在uboot中用fastboot命令将操作系统的内核下载到DDR的指定的内存中去,如果我们在uboot中随意使用了这段内存,那么结果可想而知,所以我们在uboot中使用DDR的内存的时候,不能随意的去使用。uboot的后续操作中,会需要用到紧凑排布的内存块空间,所以这里我们使用内存的时候,要尽量的本着我们申请的内存够用就好,而且要紧凑排布,不能这里一块那里一块,避免以后的uboot的操作和内存相关的造成不便。
(3)所以我们uboot中,使用内存的时候要有一个整体的规划。
在DDR中,共有512M,DDR1和DDR2两个DDR分别是256M,一共512M,DDR的起始地址是0X30000000,uboot的被重定位到了DDR的0X33E00000地址中去了,我在重定位到uboot的时候,我们给了uboot的大小是2M,也就是说从0X33E00000开始向上的2M空间是我们整个uboot所拥有的,在这空间中,实际上我们的uboot用不了那么大的空间,在这2M的空间内,紧挨着实际uboot的大小上面的地方放的是我们在DDR中设置的栈。
(4)gd_base = CFG_UBOOT_BASE + CFG_UBOOT_SIZE - CFG_MALLOC_LEN - CFG_STACK_SIZE - sizeof(gd_t);
gd_base就是这个gd的内存起始地址。
6、4、内存排布
1、uboot区: 从CFG_UBOOT_BASE起始地址开始,到一个地方。CFG_UBOOT_BASE+XX(长度为实际uboot的大小)
CFG_UBOOT_BASE是uboot在DDR中的起始地址0X33E00000
CFG_UBOOT_SIZE是uboot的在DDR中的整个大小
2、堆区:CFG_MALLOC_LEN 堆区的长度
在uboot中,我们也想建立一个堆去供我们使用,所以我们也建造了一个堆区,这个堆区的长度是CFG_MALLOC_LEN,
#define CFG_MALLOC_LEN (CFG_ENV_SIZE + 896*1024)
#define CFG_ENV_SIZE 0x4000
所以可以知道堆区的长度大小是CFG_MALLOC_LEN = 0X4000 + 896*1024 = 16KB + 896KB = 912KB
3、栈区:#define CFG_STACK_SIZE 512*1024 //512KB
4、gd :sizeof(gd_t) 就是gd所指向的那个结构体的大小,可以知道是36B
5、bd :sizeof(bd) 就是bd所指向的那个结构体的大小,大概是44B.
6、所以大概可以知道gd内存起始地址的位置了,gd大概是在uboot在DDR中起始地址0X33E00000开始的位置往上走了2M的长度,又减掉了堆区912KB的长度,又减去了栈区512KB的长度,又减去了gd那个结构体的大小36B的位置。
uboot实际的大小很小,只有200或者300多KB,而gd的位置应该在uboot实际大小的上面200或者300KB的位置,不会踩到uboot,如果uboot写的很大,怕会踩到gd的内存部分,我们可以改变CFG_UBOOT_SIZE这个值,把2M改的更大,这样就不会踩到了gd了。
(1)gd = (gd_t*)gd_base;//将算好规划好的gd应该在DDR中内存的起始地址位置赋值给gd,此时gd就已经有了好的安身
//也可叫将gd实例化,由单纯的指针,变成了有指向实际内存的指针
(2)memset ((void*)gd, 0, sizeof (gd_t));//怕这段gd所用的内存不干净,全部清零
(3)gd->bd = (bd_t*)((char*)gd - sizeof(bd_t));//因为bd也是指针,此时也没有确切的身体,所以我们也要将bd实例化,给他一个内存,因为DDR内存是向上生长的,所以我们让gd这个内存起始的地址位置,减去了一个bd大小的空间,让这个空间的起始地址作为bd的内存起始位置即可。
总结:所以总的来说就是为了给gd和bd原先没有指向确切内存,让他指向一个确切的可用安全的内存空间的起始位置去。这样gd和bd就被实例化了,有了身体了。可以使用了。
7、内存间隔: __asm__ __volatile__("": : :"memory");
__asm__ 是在C语言中内嵌汇编的意思,就是在C语言中内嵌了汇编,让C语言和汇编可以交叠工作。
知道这句代码的意思就行,就是为了不让GCC过度优化造成错误。为了防止高版本的gcc优化造成错误,一般在我们的gcc交叉编译工具大于3.4版本的时候就要加上这句话,因为我们GCC已经是4.4以上的了,所以一定要加。
6、4 start_armboot函数的解析2
1.board.c中483行的for循环
for (init_fnc_ptr = init_sequence; *init_fnc_ptr; ++init_fnc_ptr) {
if ((*init_fnc_ptr)() != 0) {
hang ();
}
}
(1)init_fnc_ptr 可以知道这是一个二重函数指针
(2)init_sequence 是一个函数指针数组,数组中有很多函数指针,指针指向的函数都是init_fnc_t类型的(typedef (3)int (init_fnc_t) (void); )返回值是int类型的,参数是void
(4)*init_fnc_ptr for循环的终止条件, 解引用这个二重函数指针指向的数组元素的地址,也就是解引用后,得到数组偏移地址所对应的那个函数指针
(5)++init_fnc_ptr for循环的增量 指针遍历函数指针数组的元素地址,+1表示指向下一个元素的地址
(6)init_sequence 这个数组在board.c的416行,定义的同时初始化了,初始化的内容都是函数名(函数名就是函数指针,代码段的首地址)。
(7)我们怎么遍历一个函数指针数组呢?一般由两种方法,一个是将终止条件设置为数组的大小,当不满足时,则循环结束。另一种是在数组的内部弄一个元素让其为终止的条件,如果遇到了这个元素则其终止,这里我们用到的就是第二种方法。
init_sequence数组中的函数指针,这些函数在运行成功的时候都会返回一个0,返回0表示运行成功了。如果在遍历这个函数指针数组时,里面有一个函数运行不成功,不返回0,则会执行hang();挂起,就是证明启动过程执行失败了
(8)init_sequence里面的函数初始化,都是board级别的,板级的硬件相关的初始化。cpu外面的,开发板里面的。
2、cpu_init 里面是空的,直接返回了0,代表确实这个init_sequence里面的函数是初始化CPU外面的,开发板里面相关的硬件的东西,并且cpu初始化在前面的start.S中已经完成了
3、board_init 函数体在board/samsung/X210/X210.c中
(1)DECLARE_GLOBAL_DATA_PTR; 在board_init函数中的一句话,意思是声明gd的意思,因为下面要用到gd。定义的同时不初始化就是声明。
宏定义的变量也是放在头文件中的,在调用时也是需要包含头文件的。只是说这个宏如果不使用,这个变量就不会声明。
(2)网卡初始化
接着这个宏CONFIG_DRIVER_DM9000
这个宏是在x210.h中定义的,定义了这个宏就表示我们的开发板要配置这个DM9000这个网卡,下面就是这个网卡的初始化了。dm9000_pre_init();这个函数就是对应的DM9000这个网卡的初始化函数,如果你用的网卡在uboot中没有,那么就需要你自己去移植了,怎么移植呢,也是用一个宏来进行条件编译的选择,完了将你对应的网卡的初始化函数放在这个宏下,可以看到,这些个函数都是放在,x210.c中的。
dm9000_pre_init();这个函数主要是GPIO和端口的配置,而不是驱动,因为网卡驱动是现成的,移植的时候驱动是不需要改动的,关键就是这个函数中的基本的初始化,因为这些基本初始化是和硬件相关的。
6、5 start_armboot函数解析3
背景:
(1)初始化了DDR
注意:这里初始化DDR和我们在汇编阶段在lowlevel_init函数中初始化DDR是不同的。
当时DDR的初始化是DDR硬件下的初始化,为了让DDR可以工作,而现在的初始化是和软件层面相关的,一些DDR的属性配置、地址设置的初始化。
为什么要在软件层次上初始化DDR,因为对于uboot来说,我们要知道开发板上有几片DDR,每一片DDR的起始地址和长度是多大这些信息。uboot采用了一个简单有效的方式:程序员在移植uboot到一个开发板上时,程序员在x210_sd.h这个文件中,自己用宏定义的方式配置出板子上DDR的相关信息,然后uboot只要读取这些信息即可。(实际上还有另外一种方式,就是uboot通过代码的方式直接读取硬件中的硬件相关的信息来知道DDR的配置,但是uboot没有这么做,在我们PC机上,BIOS用的是这种方式,比如我们的电脑是4G的内存,当我加了一条内存条的时候,电脑就识别到了内存变成了8G了,这就是直接读取硬件相关的信息,来知道DDR相关的信息)
(2)在x210_sd.h中的499行到505行,用标准宏定义的方式配置了我们开发板上DDR的相关的信息,为什么说是标准的宏定义呢,因为这些宏定义的名字是不可以被改变的,我们只能改变这些宏定义的值。所以叫做标准的宏定义。
#define CONFIG_NR_DRAM_BANKS 2 /* we have 2 bank of DRAM */
#define SDRAM_BANK_SIZE 0x10000000 //实际256MB /* 512 MB lqm 这个512MB的注释纯属瞎写 */
//#define SDRAM_BANK_SIZE 0x20000000 /* 1GB lqm*/ //这个可能是以前的,但实际应该是512MB
#define PHYS_SDRAM_1 MEMORY_BASE_ADDRESS /* SDRAM Bank #1 base address */
#define PHYS_SDRAM_1_SIZE SDRAM_BANK_SIZE //我们用的DDR一片是256MB
#define PHYS_SDRAM_2 MEMORY_BASE_ADDRESS2 /* SDRAM Bank #2 */
#define PHYS_SDRAM_2_SIZE SDRAM_BANK_SIZE //我们用的DDR第二片也是256MB
1、gd->bd->bi_arch_number = MACH_TYPE;
(1)bi_arch_number是bd_info结构体中的一个成员,他的意思就是开发板的机器码,所谓机器码就是uboot给我们的这个开发板定义的唯一编号。
(2)机器码的作用,就是uboot和linux内核之间进行比对和适配。
因为嵌入式设备的硬件都是定制化的,每一个开发板的机器码都不同,在这个开发板上跑起来的内核,同样的内核镜像在另一个开发板上就跑不起来,不能通用,这就是嵌入式设备和PC机的不同。
(3)因此linux做了个设置:就是给每个开发板做了一个唯一的编号(机器码),然后在uboot、linux内核中都有一个软件维护机器码的编号,然后开发板、uboot、linux三者比较适配机器码,如果机器码对上了就启动,对不上就不启动(因为软件认为,我和这个硬件不匹配,不能启动了把他给坑了,因为如果没有这个机器码和软件去维护,那么随意的去的启动,可能启动不起来,如果启动起来了,就可能带来无法预料的损失,金钱,生命都有可能)。
(4)MACH_TYPE 在x210_sd.h中123行定义,值是2456,没有特殊的含义,只是当前开发板对应的编号。这个编号就代表x210这个开发板的机器码,将来这个开发板上一直的linux内核的机器码也必须是这个2456的值。不然比对适配不上启动不起来。
(5)uboot中配置的这个机器码,将来会在启动linux内核的时候,作为传参的一部分传参给linux内核,这样linux内核就知道uboot的机器码了。内核启动过程中,会将这个传参过来的机器码和自己的机器码进行想比对。
(6)在国内,这个机器码我们可以随便给,但是如果你是在公司中做产品的话,就要慎重了,尽量不要和别人的重了
2、gd->bd->bi_boot_params = (PHYS_SDRAM_1+0x100);
(1)bi_boot_params,也是bd_info中的一个成员,含义是uboot给linuxkernel启动时传参的内存地址。
也就是说,uboot在给linux内核传递参数的时候是这么传的:uboot事先将准备好的传参(字符串。bootargs)放在内存的一个地址处(bi_boot_params),然后uboot就启动了内核(uboot在启动内核时真正是通过r0、r1、r2、寄存器来直接传递参数的,这三个寄存器中,其中有一个就是这个bd_boot_params参数,这个参数就是内存的地址),然后内核在启动的时候,通过读取这个寄存器中的值就可以知道uboot传递过来的参数在内存中哪里。然后自己去内存的那个地方去找bootargs
(2)经过计算知道这个bi_boot_params的值是0x30000100,所以可以知道uboot将这个内存地址连续的一定空间内,作为给内核传参的地方了,所以我们在uboot的其他代码中,就不去这个地址附近玩耍了,将来linux内核启动的时候,也会通过读取到那三个寄存器中的某一个后,知道了参数在内存的哪个位置了,完了去那个内存的位置去读取相应的uboot传给linux内核的参数。
6、6 start_armboot函数解析4
1、interrupt_init;
(1)这个函数指针指向的函数,跳到这个函数,发现代表和函数的名字含义不相关。实际上这个函数是用来初始化定时器的(使用的是timer4定时器)。
(2)在裸机中,我们知道,我们x210开发板,一共有五个PWM定时器,中timer0-timer3这个四个PWM定时器有信号的输出引脚,和timer4没有信号的输出引脚,无法输出PWM波形。timer4在设计的时候就不是用来输出PWM波形的(第一。没有输出引脚,第二、没有TCMPB这个用来计算PWM波的寄存器),所以这个定时器在设计的时候是用来做计时用的(因为有TCNTB这个用来存放计数的寄存器)。
(3)timer4用来做计时时,需要用到2个寄存器,一个是TCNTB、一个是TCNTO。
TCNTB寄存器中,存的是一个数,这个数代表着定时次数(每一次的时间是由时钟决定的,实际上是由PCLKPSYS过来的时钟频率,经过预分频器和分频器得到的最终给了这个定时器的一个时钟频率,这个寄存中的数乘以过来的时钟频率就是定时的时间长度,每一个时钟频率过来,这里面的数就会减1,当减到0的时候,证明定时时间到了)。我们定时的时候只需要将我们要定的时间/这个定时器的时钟记一次数的时间=要放在这个寄存中的数。将这个数放入这个TCNTB寄存器中即可。
TCNTO寄存器,这个寄存器是用来观察TCNTB寄存器中的数的(实际上是TCNTB背后那个寄存器的数),我们通过TCNTO寄存器可以知道TCNTB中的数有没有减到0,当TCNTO读取到TCNTB寄存器中数减到0后,就知道定时间到了。(因为通过TCNTB这个寄存器,我们把数放到里面后,是看不到这个寄存器中数在减的,这个寄存器相当于一个buff。将数传到这个寄存器的背后里面那个寄存中,那个寄存器中的数是会减的,但是我们看不到,那个寄存器是看不见的,所以要通过TCNTO这个寄存器去观察那个寄存器中的数有没有减到0,定时时间有没有到)
(4)使用timer4定时器,因为这个timer4没有中断支持,不支持中断的,所以当时间到了以后,不会有中断标志位的改变,我们无法知道时间是否定时到了,所以要用TCNTO这个寄存器,通过读,取里面的值,来知道timer4定时器的定时时间是否到了,并且我们的CPU不能在做其实事情的后,来知道定时器时间到了,因为不支持中断,不会有中断通知我们时间到了,所以我们只能用查询的方式来读取TCNTO寄存器中的值来知道定时时间是否到了,因为timer4的定时是无法实现微观上的并行的,用它来定时,只能不断的轮询查看这个寄存器,来知道时间是否到了。
(5)uboot中是用这个timer4来实现定时的,所以uboot在使用这个定时器定时的时候,是无法做其他的事情的。只能看着这个定时器的定时时间到(这不就是bootdelay的原理嘛,为什么uboot在启动的时候,bootdelay在倒计时,在进行等到,程序并没有执行其他的事情,因为这个定时器实现不了并且,定时时程序只能停留在那个位置,查看定时时间是否到了,所以bootdelay在实现定时的时候是用的轮询的方式,检查用户有没有输入回车键,如果没有继续定时)。
(6)interrupt_init;函数就是将timer4设置为定时10ms,用get_PCLK函数来获取系统设置的PCLK_PSYS的时钟频率。然后进行TCFG0,TCFG1分频和预分频,然后计算10ms应该向TCNTB寄存器中写入的值。最后写入,完了设置是自动刷新数到TCNTB背后的寄存器中,还是手动刷新。最后在开启timer4。
总结:学习这个函数的时候,重点要知道这种标准代码和之前裸机代码的区别,并且要学会怎么用结构体封装寄存器的方式,用结构体的首地址经过偏移去访问一类的寄存器,因为一类寄存器的内存地址是4字节连续向上生长的,所以可以将某一类寄存器的基地址给结构体的指针,用结构体指针的方式去访问,因为结构体的是内存对齐的,如果成员如果都是4个字节的话,那么成员和成员之前就相差了四个字节的内存,正好一类寄存器的内存地址也是4字节4字节相连续的,还要学会用函数的方式来获取设置值的方式。这样可以让代码具有可移植性。
2、env_init
(1)看名字就知道是和环境变量的初始化有关。
(2)为什么有很多env_init函数呢?因为我们uboot支持很多种启动介质(比如,从iNand、norflash、nandflash、sd卡启动····很多种启动),当我们用不同的启动介质启动uboot的时候,从启动介质操作env环境变量到内存中,或者从内存中存到启动介质中的的方法都是不一样的,操作方法都是不一样的。因此uboot支持各种启动介质中,env的存取操作方法,所以有很多env_xxx名字的.c文件。(当我们编译链接的时候,只会有一个env_xxx.c文件中的env_init函数被链接,因为在x210_sd.h头文件中,已经用宏定义的方式来配置了,有条件编译的控制,所以只会有一个会起作用),对于我们x210iNand版本的开发板来说,我们应该看是env_movi.c这个文件中的env_init函数。
(3)这个env_init函数做的操作就是对我们内存中维护的那一份uboot的env环境变量进行了判定(判定内存中有没有环境变量),当前因为我们还没进行环境从SD卡到DDR的relocate,因此当前的环境变量是不能用。
(4)在start_armboot这个函数的776行调用env_relocate才将环境变量从SD卡中relocate到DDR中,那时才可以用。重定位之后,在需要环境变量的时候,才能从DDR中去使用环境变量,在重定位之前,我们只能从SD卡中去读取环境变量。内存中并没有。
6、6 start_armboot函数解析5
1、init_baudrate
(1)看名字就知道是初始化串口通信波特率的,uboot在启动时,就是通过串口和我们主机进行通信的。通过串口形成的控制台来控制我们的uboot。
(2)getenv_r函数,读取环境变量的值的。因为我们uboot希望不改变源代码的情况下,直接在uboot的命令行下,通过更改baudrate这个环境变量的值来设置uboot的串口波特率,所以我们将把baudrate弄成了环境变量。
int i = getenv_r ("baudrate", tmp, sizeof (tmp)); 这个函数的目的就是将baudrate这个字符串代表的环境变量的值读取到tmp数组中,注意,这个环境变量的值是一个字符串,因为tmp是一个字符数组,并且uboot界面中的东西都是字符串。函数执行成功后返回一个大于1的数给i。
(3)int) simple_strtoul (tmp, NULL, 10) 这句话的意思就是tmp数组中放的把baudrate的值(字符串)转换成数字。
(4)baudrate初始化的规则:首先先从环境变量中读取这个环境变量的值放在tmp字符数组中,如果读取成功,则将这个值转换成数字给我们gd->baudrate和gd->bd->baudrate,如果读取失败了,就用x210_sd.h中的CONFIG_BAUDRATE这个宏的值(115200)给这两个成员,作为波特率的值。从这里可以看出来,环境变量的优先级是很高的,环境变量如果有则用环境变量的,环境变量中没有则在考虑用别的。
2、serial_init
(1)看名字知道是串口的初始化,疑问(我们start.S中的lowlevel_init中,已经初始化串口了,为什么这里还要初始化呢?和之前在汇编阶段的串口初始化有什么不同吗)。
(2)和我们这个开发板有关系的串口初始化serial_init函数是在uboot/uboot_jiuding/cpu/s5pc11x/serial.c中。
(3)看代码知道,我们这个函数什么都没有做,紧紧就是用for循环延时了一小小下,所以就说明我们在汇编阶段初始化了串口,这里就没有在进行硬件寄存器的初始化了。
6、6 start_armboot函数解析6
1、console_init_f函数
(1)初始化控制台的函数,_f表示这个初始化是第一阶段的初始化,_r表示是第二阶段的初始化,因为有的初始化一个阶段初始化不完,所以会有第二阶段的初始化。有时候初始化函数不能一次一起完成初始化,中间还会夹杂这一些代码,所以需要将一个完成的初始化模块的代码分成了2个阶段(我们uboot中的,start_armboot函数的第826行进行了console_init_r第二阶段的初始化)。
(2)console_init_f函数在uboot/common/console.c中,common表示这个文件中放的是与硬件无关的普遍使用的代码。
(3)这个函数的功能仅仅是将我们gd->have_console设置为1了。
2、display_banner函数
(1)这个函数是用来串口输出显示uboot的logo的。在uboot刚开始启动的时候,OK是在lowlevel_init函数中初始化完串口后,执行完毕lowlevel_init函数后打印的OK字样,U-Boot 1.3.4 (Aug 7 2013 - 14:53:08) for x210是uboot的logo,是在uboot启动的第二阶段,start_armboot函数中调用disolay_banner函数显示的。
(2)在这个函数中开始用到了printf,但是我们的控制台还没有初始化好呢,上面的那个console_init_f初始化函数中,相当于什么都没有,那这里为什么可以进行printf呢?
(3)我们追到printf这函,发现这个函数实际上是通过调用puts函数,接着在puts函数中判断我们控制台是否初始化好,如果初始化好了则调用fputs函数(这样发送才是控制台的路),如果控制台console没有初始化则调用了serial_puts函数,我们此时的控制台console还没有初始化成功,所以我们走到了这个函数serial_puts中,这个函数中又调用serial_putc函数,最后在serial_putc中是通过,将串口寄存器组的基地址给了封装串口寄存器的结构体的指针变量,通过这个指针变量将要通过串口发送的数据放到了串口的发送寄存器中。所以是跟控制台无关的。
(4)我们的控制台是用串口输出,控制台没有初始化好,我们用的也是串口输出,那么控制台究竟有什么好处呢?什么是控制台呢?实际上如果通过代码的分析,会发现,控制台实际上是通过软件虚拟出来的设备,这个设备有一套专用的通信函数(发送。接收···),控制台的通信函数,最终会映射到硬件的通信函数中来实现。也就是说uboot的控制台的通信函数实际上是映射到硬件的串口通信函数总的,也就是说uboot用没用控制台,其实并没有本质的差别。但是在别的体系中,控制台的通信函数映射到硬件的通信函数中,可以用软件来做一些优化,比如说是缓冲机制。(在操作系统中,用的就是这样控制台的方式,这样可以将我们要发送的很多字节放到console的buff中进行缓冲,如果不这样的话,因为硬件的串口本身是一个字节一个字节的发送的,会很慢,而CPU的速度很快,这样去将发送的东西放到控制台的buff中去缓冲就会提供通信的速度。尤其是输出到lcd的输出设备上,如果没有这个console的缓冲机制,那么输出一个字节就要去刷新一次屏幕,如果有这个console的缓冲机制就可以将发送的东西放到console的缓冲中,一次刷到lcd屏幕上)
(5)printf函数要发送的是version_string这个字符数组。
const char version_string[] =
U_BOOT_VERSION" (" __DATE__ " - " __TIME__ ")"CONFIG_IDENT_STRING;
数组中的U_BOOT_VERSION在源代码中是找不到定义的,这个变量数组中的U_BOOT_VERSION在源代码中是找不到定义的,这个变量(字符串)实际上是在makefile中定义的makefile的365行,在配置编译的时候才能生成,在include/version_autogenerated.h中的一个宏。CONFIG_IDENT_STRING是在x210_sd.h中的宏,配置的信息。DATE是编译的日期,TIME是时间
3、print_cpuinfo函数
(1)打印CPU的信息。uboot启动过程中,
CPU: S5PV210@1000MHz(OK)
APLL = 1000MHz, HclkMsys = 200MHz, PclkMsys = 100MHz
MPLL = 667MHz, EPLL = 96MHz
HclkDsys = 166MHz, PclkDsys = 83MHz
HclkPsys = 133MHz, PclkPsys = 66MHz
SCLKA2M = 200MHz
Serial = CLKUART
这些信息就是print_cpuinfo打印出来的
6、6、start_armboot函数解析7
1、checkboard函数
(1)检查当前开发板是哪一个开发板,并且打印出来开发板的名字
2、#if defined(CONFIG_HARD_I2C) || defined(CONFIG_SOFT_I2C)
(1)CONFIG_HARD_I2C是硬件的I2C(就是SOC中有I2C控制器),这个如果定义了,或者CONFIG_SOFT_I2C软件I2C(就是两个GPIO来模拟I2C的),两个中一个成立就会执行init_func_i2c函数,看似我们的硬件的I2C的宏是被定义的,但是追踪发现我们这个硬件I2C的宏没有定义,被条件编译给拦住了。
3、init_func_i2c函数
所以可以知道这个函数实际没有被执行的,因为硬件和软件的I2C宏都没有定义。所以串口中也没有打印I2C的信息。
4、uboot学习实践
(1)对uboot源码进行修改(按照我们的需要进行修改)、然后make distclean将之前编译生成的东西删除掉、然后在make x210_sd_config进行配置、然后在make编译
(2)之后编译完成后生成的u-boot.bin,在linux下用dd命令的方法进行烧写。
(3)烧写的过程:
第一步:进入到uboot根目录下的sd_fusing文件夹中,这里面有一个烧写脚本,我们当前是不可以用的,因为之前九鼎是在64位的Ubuntu中用的,而我们现在的Ubuntu是32位,file mkbll可以看出来
第二部:make clean 将原先的跟烧写脚本相关的东西删掉,在make,因为我们的Ubuntu是32位的,所以这个时候make的时候就是我们能用的了。因为是在32位Ubuntu中make的,在file mkbll可以看出来。
第三步:看sd_fusing.sh脚本中的烧录uboot的路径和烧录到SD卡的扇区(和我们从SD卡重定位到DDR中的那个SD卡起始扇区位置一致,因为我源码中的扇区是哪里,我们在烧写uboot到SD卡的时候就应该烧写到哪里,不然将来从SD卡重定位到DDR中,就不能运行了,因为源码我们是那么写的)对不对。
第四步:插上SD卡,Ubuntu中识别到之后执行 ./sd_fusing.sh /dev/sdb 烧录到SD卡中。
注意:破坏linux平台下的SD0通道所连启动介质中的bootloader:在linux平台下输入
busybox dd if=/dev/zero of=/dev/mmcblk0 bs=512 seek=1 count=1 conv=sync
在执行sync确保破坏的前一块数据有效。
6、6 start_armboot函数解析8
1、dram_init函数
(1)在汇编阶段我们已经初始化DDR了,不然也不能relocate到这里运行了,这里初始化DDR是初始化DDR软件相关的,就是初始化DDR的信息,将DDR相关的信息,比如DDR起始地址,DDR的大小的放到gd全局变量下的DDR相关的成员中。将DDR的相关信息记录到这两个成员中,以便于以后用到。这些相关的信息在x210_sd.h中可以进行配置。
(2)从代码来看,这个函数的目的就是初始化了gd->bd->bi_dram这个结构体数组,内容就是我们开发板用的DDR的相关信息,起始地址和大小。
2、display_dram_config函数
(1)打印DDR的信息。打印显示DDR的配置信息。
(2)启动过程中的 DRAM: 512 MB 就是在这个函数中被打印显示出来的。
(3)如何在uboot运行的时候,知道DDR的配置信息呢,有一个命令 bdinfo,这个命令可以打印出来gd->bd->bi_dram成员中的配置信息,就是DDR的配置信息。并且这个bdinfo命令还可以打印出来所有硬件相关的全局变量的值
3、init_sequence函数指针数组中做的事情的总结:
总结:
(1)都是板级的初始化,gd全局变量中的成员的初始化,cpu_init是没有的,网卡的初始化、机器码的初始化(gd->bd->bi_arch_number)、内存传参地址的初始化(gd->bd->bi_boot_params)。
(2)定时器的初始化,使用的是定时器timer4,这个定时器没有PWM输出引脚,不支持中断,所以在用这个定时器进行定时的时候,我们CPU要不断的进行轮询去查找那个TCNTO寄存器中的值去观察定时时间是否到了。bootdelay的延时就是由这个实现的,所以在bootdelay倒计时时,uboot不干别的,只会看时间是否到了,和是否有回车键按下。
(3)看内存中有无环境变量,有无的信息记录在gd->env_valid成员中
(4)波特率的初始化,串口的初始在uboot汇编阶段(uboot第一阶段已经初始化好了,否则OK也不会打印出来),串口初始化中没有实质的内容。
(5)控制台初始化的第一部分,将gd->console成员赋值我1
(6)显示uboot的logo
(7)打印显示CPU相关的信息
(8)检查打印是哪个开发板
(9)硬件和软件的i2c都没有进行。
(10)将DDR的相关配置信息放在gd->bd->bi_dram的数据结构中。
(11)打印DDR的大小
总结:至此我们到了start_armboot的487行。
6、6 start_armboot函数解析9
1、CFG_NO_FLASH
(1)Nandflash和norflash都是flash,但是一般的情况下,flash指的norflash,nandflash简称为nand。
(2)491行,执行的flash_init是norflash的初始化的,display_flash_config打印的是,norflash的大小。uboot启动信息中的flash 8M 就是在这里打印的。
2、CONFIG_VFD和CONFIG_LCD lcd显示相关的,uboot中自带的lcd显示架构,我们没有用这个,我们在后面中,自己添加了一个LCD
显示的部分
3、mem_malloc_init函数
(1)用来初始化uboot的堆管理器的。
(2)在uboot中,开始是没有堆管理器的,但是uboot在这里自己维护了一段堆内存,肯定有自己的一套代码来管理这个堆内存,有了这些东西以后,我们就可以使用malloc、free这套机制来申请内存,释放内存了。
(3)我们在DDR中,建立的堆内存大小是912KB,我们用代码写出来的堆管理器,来管理DDR中的这段堆内存,当我们malloc申请堆内存的时候,堆管理器就会去这个堆内存中分配给我们。并且把那段申请的堆内存的起始地址给我们。这个函数就是将堆内存的部分内容请0。
6、6 start_armboot函数解析10
1、接着的在start_armboot的599行-632行是我们x210相关的代码。530-769行是开发板独有的初始化代码
(1)mmc_initialize,开发板MMC相关的初始化,初始化SOC内部SD/MMC控制器的。函数在uboot/drivers/mmc/mmc.c中。
(2)uboot对硬件的操作,如网卡、SD卡···都是借用的linux内核中的驱动来实现的,uboot根目录下的drivers文件夹,这里面放的就全是linux内核中移植过来的各种驱动文件
(3)mmc_initialize,所有使用MMC架构的MMC卡,都是使用了这个函数来初始化MMC的。
(4)board_mmc_init这个函数在mmc_initialize返回了一个 -1(这个函数开发板级别的MMC初始化,意思就是说,如果我们SOC内部没MMC控制器的话,是开发板上提供的MMC控制器,外部扩展的,所用就用这个函数,所以这个函数的优先级要高于cpu_mmc_init),实际干活的函数是cpu_mmc_init(如果我们的SOC内部有MMC控制器的话,就可以执行这个函数,显然我们x210开发板,内部SOC上有MMC控制器,所以我们可以跳过board_mmc_init函数,执行cpu_mmc_init函数)
(5)cpu_mmc_init函数在uboot/cpu/s5pc11x/cpu.c中,这里面又间接的调用了uboot/driver/mmc/s3c_mmcxxx.c的驱动代码来实现mmc控制器的初始化。
6、6 start_armboot函数解析11
1、start_armboot函数中到了770行
2、776行 env_relocate函数
(1)环境变量的重定位,将SD卡中的环境变量重定位到DDR中。
(2)环境变量到底重哪里来?虽然SD卡中我们在划分的时候有些扇区是放我们的env的,但是我们在烧录的时候,不曾烧录env分区,只烧录了uboot分区、kernel分区、rootfs分区,在SD卡的那些放env扇区的位置是没有env的,因为没有烧录env过去,在第一次启动的时候。所以第一次uboot启动的时候,去SD卡的env分区读取环境变量的时候是失败(读取回来后,做CRC校验时是失败的),env分区是空的,所以第一次启动的时候,我们uboot选择从uboot内部代码中设置的一套默认的环境变量出发来使用(这套代码的ENV,在第一次启动的时候,被弄到了DDR中,当我们将内存中的环境变量改变后,在saveenv后会被写入到sd卡的env分区中,当我们下次启动的时候,在sd卡的env分区中就有了环境变量了,所以下次读取的时候就成功了。也可能是uboot在第一次读取内部默认的环境变量后,就直接写入到SD卡的env分区中了)。这就是为什么我们的SD卡烧录好后,虽然没有烧录环境变量分区,但是在第一次启动的时候,uboot界面中是可以看到环境变量的,并且在本次我们将改动的环境变量改动好,保存起来后,我们重新烧录SD卡,但是还是没有烧录env分区,但是下一次启动的时候,发现环境变量的值确是我们上一次更改后的环境变量的值。
总结:第一次启动的时候,SD卡的env分区是没有环境变量的,uboot代码中的gd->env_valid 的值是等于0的,所以这个时候uboot读取的是uboot代码内置的一套环境变量到DDR中,将这套环境变量通过代码写入到SD卡的env分区中(或者我们改变这套环境变量的值后,saveenv保存到SD卡的env分区中),并且计算出CRC校验,将gd->env_valid 值赋值为1,这样在下次启动的时候,uboot重SD卡的env分区读取环境变量的时候,CRC校验就可以通过了,gd->env_valid 值就不为0 了, 就可以成功的重SD卡的env分区中将环境变量读取到DDR中了。
(3)真正的环境变量从SD卡重定位到DDR中的代码是在env_relocate_spec函数中的movi_read_env函数
6、6 start_armboot函数解析12
1、IP地址。MAC地址的获取
(1)IP地址的获取,优先从环境变量中获取,来源于ipaddr这个环境变量,存放在gd->bd->bi_ipaddr中
(2)getenv_IPaddr ("ipaddr")函数获取ipaddr这个环境变量的值,这个函数中的getenv用来获取ip地址的值(注意是字符串格式的),string_to_ip函数用来将这个字符串格式的IP地址值转换为正常的数字的IP地址值(点分的十进制的数字),然后最后返回这个数,存放在那个gd中的ip地址成员中。
(3)IP地址是由4个0-255的数字组成的,所以最简单的存储格式,就是用一个unsigned int的大小32位。一个存储单元。但是我们人能看懂的IP地址是点分十进制格式的(192.168.1.10),这两种类型可以进行互相的转换,一般给我们人看的话就是转换成点分十进制类型的,给机器用就是unsigned int类型的。所以get_env函数得到的IP地址应该是unsigned int类型的,因为这样的在内存中存储是比较省内存空间的,机器也好用。
2、MAC地址(网卡的硬件的地址)
(1)ethaddr这个环境变量中放,放的就是MAC地址,MAC地址是由6为0-255的数字组成的。
(2)环境变量的值,在第一次启动的时候,我们可以通过x210_sd.h中的宏配置来决定。在我们更改后,下一次启动,环境变量的值以我们改动过的在SD卡env分区中那一份环境变量为准。
3、devices_init函数
(1)设备的初始化,指的是,开发板上的硬件设备的初始化。这里初始化的是前面没有初始化的一部分的设备初始化,只是把名字弄成了设备初始化的名字,放在这个函数中初始化的设备,都是驱动设备。这个函数本来就是从驱动框架中延生出来的。uboot中的很多设配的驱动是直接移植linux内核中的驱动的。如网卡,SD卡··。
(2)这个函数在linux内核中,就是集中的将一些设备进行初始化。在uboot中也是如此,但是在uboot中,这个函数从linux内核中拿过来后,有好多设备的初始化都没有,几乎一个都没有用,因为在前面的阶段,我们能用到的设备基本都初始化了,uboot毕竟用的少。
4、jumptable_init函数
(1)jumptable跳转表,在uboot中,这个函数中的相关内容,只是最为了左值,在整个uboot中,并没有做右值被使用,所以也可以说是空有起名的。本身是一个函数指针数组,里面记录了很多函数的函数名(函数指针),看这个阵势,是要实现一个函数指针到真正函数的一个映射。将来通过跳转表中的函数指针就可以执行具体的函数。这个其实就是在用C语言实现面向对象的编程,在linux内核中有很多这种技巧,所以我们以前有一个说法,C语言不是面向对象的,但是linux内核是面向对象的。
(2)通过分析返现这个跳转表只是被赋值,但是从来没有被引用,因此这个跳转表,在uboot中,根本就没有使用他
6、6 start_armboot函数解析13
1、console_init_r函数
(1)console_init_f函数是我们console第一阶段的初始化,console_init_r函数是我们第二阶段的初始化,实际上我们的console_init_f第一阶段的初始化并没有做什么,只是将gd->have_console赋值为了1.第二阶段的初始化才进行了实质性的工作。
(2)uboot中有很多同名的函数,我们SI进行索引的时候,经常搜到的是一些不对的函数。有是自动索引找到的是错的,真正却没有找到,一般情况下是一搜有很多个,一定要判断是哪个。
(3)console函数,其实是纯软件架构方面的初始化(说白了其实就是给我们console相关的数据结构体中,填充一个些相应的值),比如标准输入配置成什么,标准错误,标准输出。
(4)但是实际上,我们uboot中console实际上并没有干有意义的初始化,他就是直接调用的串口通信的函数进行通信的,所以用不用consoleinit实际上并没有什么分别。但是在linux内核中console就可以提供缓冲机制,不用console就不能实现的东西。
2、enable_interrupts函数
(1)这个函数如果定义了那个USE_IRQ的宏,就是值的CPSR中的总中断使能,但是我们因为我们的uboot中是用不到中断的,所以我们没有定义这个宏,看代码知道,如果我们有定义这个宏那么这个函数就是一个空函数,所以在这里我们的这个函数是一个空函数。
(2)因为我们的uboot中没有使用中断,因此没有定义这个USE_IIRQ宏,因此我们这个函数是一个函数,为的是在编译的时候防止报错,因为你没有定义这个宏,你还调用了这个函数,那个宏既然没有包含,这个函数的函数体就不会存在,所以uboot中为我们提供了一个空函数,在没有定义那个宏的时候,这个函数被调用的时候,执行的就是那个空函数。
总结:uboot中经常出现一个情况,就是根据一个宏是否定义了来条件编译决定是否调用这个函数,这种情况有两种解决方案来处理这种情况,方案一就是在调用函数处使用条用编译,函数体实际完全提供代码。方案二就是我们在函数处直接调用函数,然后在函数体处,提供两个函数体,一个是有实体的函数,一个是空函数,用宏定义的条件编译来决定实际编译时编译哪个函数进去。
3、loadaddr、bootfile两个环境变量
(1)这两个环境变量都是内核启动有关的,在启动linux内核时会参考这两个环境变量的值,这里猜测应该是用在tftpboot命令下,下载内核镜像到内存地址中的两个参数,一个是要下载到内存地址中的地址,一个是从tftp服务器中下载的内核的名字,下载到内存地址的内核镜像的名字。
4、board_late_init函数
(1)实际是空的,看名字是开发板的初始化里比较晚期的初始化,就是前面该初始化的基本都初始化完了,剩下的一些必须放在后面初始化的就放在这里了,这也侧面说明了,开发板级别的硬件软件初始化告一段落了,基本结束了。对于x210来说,这个函数是空的,说明都没有干。
6、6 start_armboot函数解析14
1、eth_initialize函数
(1)看名字是网卡相关的初始化,在这之前的那个函数指针数组中的boar_init中,已经初始化了网卡,那这里的这个是什么呢。
(2)实际上这个函数应该是对网卡芯片本身的初始化,这里不是SOC与网卡芯片链接时,我们SOC这边的初始化。而是网卡芯片本身的初始化,
(3)对于x210来说,我们使用的是DM9000网卡来说,这个函数是空的,我们x210开发板的网卡DM9000的初始化在前面的那个函数指针数组中的board_init中初始化的,网卡芯片的初始化在驱动中,所以这个函数对于我们来说是空的。
2、x210_preboot_init
(1)x210开发板在启动起来之前的一些初始化,也就是说开发板在运行uboot时,进入到uboot的命令行之前的初始化
(2)这个函数中调用了mpadfb_init函数
(3)mpadfb_init函数,看名字是和LCD相关的,以及LCD屏幕上的LOGO显示
3、check_menu_update_from_sd函数
(1)uboot启动的最后阶段设计了一个自动更新的功能,就是将我们要升级的镜像放到SD卡的固定目录中,然后开机时,在uboot启动的最后阶段,检查升级标志(是一个按键,按键中标志为"LEFT"的那个按键,这个按键如果按键按下,则表示Uupdate mode 如果没有按下,则表示boot mode),如果是update mode,则uboot会自动从SD卡中的那个固定目录下读取镜像文件烧录到iNand中,如果进入到了boot mode中,则uboot不执行update 直接启动,正常运行
(2)这种机制,能够帮助我们快速的烧录系统,常用于量产时,用SD卡对系统的烧录和部署。
4、死循环
(1)start_armboot进入了死循环,去main_loop函数执行了。
(2)解析器
(3)开机倒数自动执行
(4)命令的补全