1、start_armboot函数简介
(1)这个函数的是在UBOOT/lib_arm/board.c的第444行开始到908行结束
(2)450行里面还不是全部的函数,因为里面调用了很多函数。
(3)此函数构成了UBOOT启动部分的第二阶段。
(4)宏观分析:UBOOT的第二阶段应该做什么?
4.1、概括来讲UBOOT的第一阶段主要就是初始化SOC内部的一些部件(比如看门狗、时钟),然后初始化DDR并且完成重定位。
4.2、从宏观分析来讲,UBOOT的第二阶段就是要初始化剩下的还没有被初始化的硬件。主要是SOC外部硬件(比如INAND、网卡芯片。。。)
4.3、UBOOT本身的一些东西(uboot的命令、环境变量等。。。)
然后最终初始化完成必要的东西后进入UBOOT的命令行准备接受命令。
2、UBOOT第二阶段完结于何处?
(1)uboot启动后自动运行打印出很多信息:这些信息就是UBOOT在第一和第二阶段不断进行初始化时打印出来的信息。然后进入了倒数bootdelay秒然后执行bootcmd对应的启动命令。
(2)如果用户没有干涉则会执行bootcmd进入自动启动内核流程(uboot就死掉了);此时用户可以按下回车键打断UBOOT的自动启动进入UBOOT的命令行下。
(3)uboot的命令行就是一个死循环,循环体内不断重复:接收命令、解析命令、执行命令。这就是UBOOT最终的归宿。
3、start_armboot解析1
(1)分析数据类型:init_fnc_t
(2)typedef int (init_fnc_t)(void); //这是一个函数类型
(3)init_fnc_ptr是一个二重函数指针,回顾高级C语言中讲过:二重指针的作用有2个,其中一个是用来指向一重指针,一个是用来指向指针数组。因此这里的init_fuc_ptr可以用来指向一个函数指针数组。
init_fnc_t **init_fnc_ptr;
CFG_UBOOT_BASE : 0xc3e00000
CFG_UBOOT_SIZE : 210241024
CFG_MALLOC_LEN : 9161024
CFG_STACK_SIZE : 5121024
sizeof(gd_t) : 32BYTE
刚刚的gd_base是一个gd结构体的基地址。
然后把这个基地址的数据,
定义一个全局结构体的地址:然后把gd的结构体赋值过去。
然后用内存拷贝的函数memset函数将0填充到这个函数里面。
void start_armboot (void)
{
init_fnc_t **init_fnc_ptr;
char *s;
int mmc_exist = 0;
/* Pointer is writable since we allocated a register for it */
#ifdef CONFIG_MEMORY_UPPER_CODE /* by scsuh */
ulong gd_base;
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
gd = (gd_t*)(_armboot_start - CFG_MALLOC_LEN - sizeof(gd_t));
#endif
memset ((void*)gd, 0, sizeof (gd_t));
gd->bd = (bd_t*)((char*)gd - sizeof(bd_t));
memset (gd->bd, 0, sizeof (bd_t));
monitor_flash_len = _bss_start - _armboot_start;
for (init_fnc_ptr = init_sequence; *init_fnc_ptr; ++init_fnc_ptr) {
if ((*init_fnc_ptr)() != 0) {
hang ();
}
}
#ifndef CFG_NO_FLASH
/* configure available FLASH banks */
size = flash_init ();
display_flash_config (size);
#endif /* CFG_NO_FLASH */
关键部分的分析
(1)初始化数组的实现
下面的部分我把一些条件编译去掉了之后,让这个函数指针数组看起来清爽了很多。
我们是使用函数指针不断的后移来调用和执行指针的,如果函数的返回值不等于0的话,你就挂起这个函数。函数指针不断的后移,直到遇到 NULL这个空指针,结束这个循环。
init_fnc_t *init_sequence[] = {
cpu_init,
reloc_init,
board_init,
interrupt_init,
env_init,
init_baudrate,
serial_init,
console_init_f,
display_banner,
print_cpuinfo,
checkboard,
init_func_i2c,
dram_init, /* configure available RAM banks */
display_dram_config,
NULL,
};
for (init_fnc_ptr = init_sequence; *init_fnc_ptr; ++init_fnc_ptr)
{
if ((*init_fnc_ptr)() != 0)
{
hang ();
}
}
(2)全局数据的实现
typedef struct global_data {
bd_t *bd;
unsigned long flags;
unsigned long baudrate;
unsigned long have_console;
unsigned long reloc_off;
unsigned long env_addr;
unsigned long env_valid;
unsigned long fb_base;
void **jt; /* jump table */
} gd_t;
gd_t gd;
(1)#define DECLARE_GLOBAL_DATA_PTR register volatile gd_t *gd asm ("r8")
把这个gd的结构体,放置在r8的寄存器里面。
这个全局的数据结构,定义了UBOOT的很多重要的信息,在后面的移植和给内核传参的时候起到的关键的作用。
4、start_armboot的解析2
(1)内存使用排布
1、为什么要分配内存?
(1)uboot区: CFG_UBOOT_BASE --2MB
(2)堆区: 长度为CFG_MALLOC_LEN,实际为912KB
(3)栈区: 长度为CFG_STACK_SIZE,实际为512KB
(4)gd: 长度为sizeof(gd_t),实际为36字节
(5)bd: 长度为sizeof(bd_t),实际为44字节
(6)内存间隔: 为了防止高版本的gcc的优化造成错误
2、解析gd结构体的bd结构体
这个结构体里面定义了很多和开发板有关的信息:
比如波特率、IP地址,单板的信息,BootLoader的启动参数
比如bi_boot_params是单板的机器码。
(1)所谓的机器码就是UBOOT给这个开发板定义的一个唯一编号。
(2)机器码的主要作用就是UBOOT和Linux内核之间进行比对和适配。
(3)嵌入式设备中每一个设备的硬件都是定制化的,不能通用,嵌入式设备的高度定制化导致硬件和软件不能随便的适配使用,这就告诉我们:这个开发板移植的内核镜像绝对不能下载到另一个开发板去,否则也不能启动,就算启动也不能正常的工作,有很多隐患,因此Linux做了个设置,给每个开发板做个唯一编号(机器码)然后开发板、UBOOT、Linux三者去比对机器码,如果机器码对上了就启动,否则就不启动。
经过计算得知:bi_boot_params:的值就是0x30000100
这个内存地址就被分配用来做内核传参了,所以在UBOOT的其他地方使用内存时要注意。千万不能把这个地址给弄没了。
typedef struct bd_info {
int bi_baudrate;
unsigned long bi_ip_addr;
unsigned char bi_enetaddr[6];
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];
#ifdef CONFIG_HAS_ETH1
/* second onboard ethernet port */
unsigned char bi_enet1addr[6];
#endif
} bd_t;
5、start_armboot的解析3
(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中的设备号。
注意SD卡要链接到虚拟机Ubuntu中,不要链接到WINDOW中。
第五步:./sd_fusing.sh /dev/sdb完成烧录,注意不是sd_fusing2.sh。
总结:UBOOT就是一个庞大点的复杂点的裸机程序而已,我们完全可以对它进行调式,调式的方法就是按照上面步奏,根据自己对代码的分析和理解对代码进行更改,然后重新编译烧录运行,根据运行的结果来执行。
6、start_armboot的解析4
(1)console_init_r:
console_init_f是控制台的第一阶段初始化,console_init_r是第二阶段初始化。实际上第一阶段初始化并没有实质性的工作,第二阶段初始化才进行了实质性的工作。
(2)console_init_r就是纯软件架构方面的初始化,说白了就是给console相关的数据结构中填充相应的值,所以属于纯软件配置类型的初始化。
(3)剩下的初始化函数就不再进行详细的说明。
最后程序会运行到一个死循环里面不断的解析命令,执行命令。
开机倒数自动执行,命令补全。