第一句子网 - 唯美句子、句子迷、好句子大全
第一句子网 > 嵌入式之uboot源码分析-启动第二阶段学习笔记(下篇)

嵌入式之uboot源码分析-启动第二阶段学习笔记(下篇)

时间:2019-05-13 06:12:19

相关推荐

嵌入式之uboot源码分析-启动第二阶段学习笔记(下篇)

接上部分---->嵌入式之uboot源码分析-启动第二阶段学习笔记(上篇)

:如下内容来自朱老师物联网大讲堂uboot课件

3.2.14 CFG_NO_FLASH

(1)虽然NandFlash和NorFlash都是Flash,但是一般NandFlash会简称为Nand而不是Flash,一般讲Flash都是指的Norflash。这里2行代码是Norflash相关的。

(2)flash_init执行的是开发板中对应的NorFlash的初始化、display_flash_config打印的也是NorFlash的配置信息(Flash: 8 MB就是这里打印出来的)。但是实际上X210中是没有Norflash的。所以着两行代码是可以去掉的(我也不知道为什么没去掉?猜测原因有可能是去掉着两行代码会导致别的地方工作不正常,需要花时间去移植调试,然后移植的人就懒得弄。实际上不去掉除了显示有8MB Flash实际没用之外也没有别的影响)

代码实践:去掉Flash看会不会出错。

结论:加上CONFIG_NOFLASH宏之后编译出错,说明代码移植的不好,那个文件的包含没有被这个宏控制。于是乎移植的人就直接放这没管。

3.2.15 CONFIG_VFD和CONFIG_LCD

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

3.2.16 mem_malloc_init

static void mem_malloc_init (ulong dest_addr){mem_malloc_start = dest_addr;mem_malloc_end = dest_addr + CFG_MALLOC_LEN;mem_malloc_brk = mem_malloc_start;memset ((void *) mem_malloc_start, 0,mem_malloc_end - mem_malloc_start);}#define CFG_MALLOC_LEN(CFG_ENV_SIZE + 896*1024)

(1)mem_malloc_init函数用来初始化uboot的堆管理器。

(2)uboot中自己维护了一段堆内存,肯定自己就有一套代码来管理这个堆内存。有了这些东西uboot中你也可以malloc、free这套机制来申请内存和释放内存。我们在DDR内存中给堆预留了896KB的内存。

3.2.17 mmc_initialize

(1)从536到768行为开发板独有的初始化。意思是三星用一套uboot同时满足了好多个系列型号的开发板,然后在这里把不同开发板自己独有的一些初始化写到了这里。用#if条件编译配合CONFIG_xxx宏来选定特定的开发板。

(2)X210相关的配置在603行到629行。

(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中,这里面又间接的调用了drivers/mmc/s3c_mmcxxx.c中的驱动代码来初始化硬件MMC控制器。这里面分层很多,分层的思想一定要有,否则完全就糊涂了。

程序中出现了双链表的访问

3.2.18 env_relocate

1.判断我们是否是内嵌环境变量(我们这里ENV_IS_EMBEDDED有被宏定义),如果不是则使用malloc分配

#ifdef ENV_IS_EMBEDDED/** The environment buffer is embedded with the text segment,* just relocate the environment pointer*/env_ptr = (env_t *)((ulong)env_ptr + gd->reloc_off);DEBUGF ("%s[%d] embedded ENV at %p\n", __FUNCTION__,__LINE__,env_ptr);#else/** We must allocate a buffer for the environment*/env_ptr = (env_t *)malloc (CFG_ENV_SIZE);DEBUGF ("%s[%d] malloced ENV at %p\n", __FUNCTION__,__LINE__,env_ptr);#endif

如果环境变量是无效的,并且环境变量也不存在的就输出Warning,同时将环境变量设置为默认值,这里调用set_default_env()函数,实现将系统默认的环境变量复制到分配的env_ptr->data中,更新CRC,并将env_valid设为1 ,下次启动就可直接重定位了。

知识点:什么是CRC校验

CRC即循环冗余校验码:是数据通信领域中最常用的一种查错校验码,其特征是信息字段和校验字段的长度可以任意选定。循环冗余检查(CRC)是一种数据传输检错功能,对数据进行多项式计算,并将得到的结果附在帧的后面,接收设备也执行类似的算法,以保证数据传输的正确性和完整性。

更多CRC相关请参考----->CRC校验原理及步骤

void set_default_env(void){if (sizeof(default_environment) > ENV_SIZE) {puts ("*** Error - default environment is too large\n\n");return;}memset(env_ptr, 0, sizeof(env_t));memcpy(env_ptr->data, default_environment,sizeof(default_environment));#ifdef CFG_REDUNDAND_ENVIRONMENTenv_ptr->flags = 0xFF;#endifenv_crc_update ();gd->env_valid = 1;}

如果环境变量是有效的,就执行环境变量的重定位

if (gd->env_valid == 0) {#if defined(CONFIG_GTH)|| defined(CFG_ENV_IS_NOWHERE)/* Environment not changable */puts ("Using default environment\n\n");#elseputs ("*** Warning - bad CRC, using default environment\n\n");show_boot_progress (-60);#endifset_default_env();}else {env_relocate_spec ();}gd->env_addr = (ulong)&(env_ptr->data);

(1)env_relocate是环境变量的重定位,完成从SD卡中将环境变量读取到DDR中的任务。

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

(3)真正的从SD卡到DDR中重定位ENV的代码是在env_relocate_spec内部的movi_read_env完成的。

3.2.19 IP地址

/* IP Address */gd->bd->bi_ip_addr = getenv_IPaddr ("ipaddr");IPaddr_t getenv_IPaddr (char *var){return (string_to_ip(getenv(var)));}

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

(2)IP地址由4个0-255之间的数字组成,因此一个IP地址在程序中最简单的存储方法就是一个unsigend int(0xC0A80102)。但是人类容易看懂的并不是这种类型,而是点分十进制类型(192.168.1.2)。这两种类型可以互相转换。

3.2.20 MAC地址

我们开发板的MAC地址为:ethaddr=00:40:5c:26:0a:5b

/* MAC Address */{int i;ulong reg;char *s, *e;char tmp[64];i = getenv_r ("ethaddr", tmp, sizeof (tmp));s = (i > 0) ? tmp : NULL;for (reg = 0; reg < 6; ++reg) {gd->bd->bi_enetaddr[reg] = s ? simple_strtoul (s, &e, 16) : 0;if (s)s = (*e) ? e + 1 : e;}#ifdef CONFIG_HAS_ETH1i = getenv_r ("eth1addr", tmp, sizeof (tmp));s = (i > 0) ? tmp : NULL;for (reg = 0; reg < 6; ++reg) {gd->bd->bi_enet1addr[reg] = s ? simple_strtoul (s, &e, 16) : 0;//用来我们的地址之间有字符之间有“:”或“.”符号,simple_strtoul 函数执行//结束后,s就会指向“:”或“.”,所以我们使用如下的函数去除“:”或“.”if (s)s = (*e) ? e + 1 : e;}#endif}

知识点1 :进制的表示
八进制:以0开头,由0~7组成的数。如,0126,050000十进制:除表示正负的符号外,以1~ 9开头,由0~9组成。如,128,+234,-278十六进制:以0X或0x开头,由0~ 9,A~ F或a~f 组成。如,0x12A,0x5a000
知识点2 :simple_strtoul函数解析

功能:将一个字符串转换成unsigend long long型数据。

返回:返回转换后数据。

参数:cp指向字符串的开始,endp指向分析的字符串末尾的位置,base为要用的基数(进制数)

更多参考simple_strtoul()

unsigned long simple_strtoul(const char *cp,char **endp,unsigned int base){unsigned long result = 0,value;//isxdigit判断cp[1]是否为十六进制数,是则返回非0值,不是则返回0,//我们发现函数先判断首字符是否为0,然后判断下一个字符是否为xif (*cp == '0') {cp++;//第一个字符为0,第二个字符为x则表示16进制if ((*cp == 'x') && isxdigit(cp[1])) {base = 16;cp++;}//第一个字符为0,第二个字符不是x,并且base不等于0,就表示为8进制if (!base) {base = 8;}}//如果首字符不是0,并且base不等于0,则就表示为十进制if (!base) {base = 10;}//如果*cp的值小于等于9则*cp-0,//进行拆分(islower(*cp) ? toupper(*cp) : *cp)表示将小写字母转换为大写//(islower(*cp) ? toupper(*cp) : *cp)-'A'+10表示将字符转换为十进制//所以如下就表示将值转化为十进制(value = isdigit(*cp) ? *cp-'0' : (islower(*cp) ? toupper(*cp) : *cp)-'A'+10)//然后将计算出来的值和进制比较 ,如果小于说明是合法的,则进行转换,大于则直接退出 while (isxdigit(*cp) && (value = isdigit(*cp) ? *cp-'0' : (islower(*cp)? toupper(*cp) : *cp)-'A'+10) < base) {result = result*base + value;cp++;}if (endp)*endp = (char *)cp;return result;}

3.2.21 devices_init

(1)devices_init看名字就是设备的初始化。这里的设备指的就是开发板上的硬件设备。放在这里初始化的设备都是驱动设备,这个函数本来就是从驱动框架中衍生出来的。uboot中很多设备的驱动是直接移植linux内核的(譬如网卡、SD卡),linux内核中的驱动都有相应的设备初始化函数。linux内核在启动过程中就有一个devices_init(名字不一定完全对,但是差不多),作用就是集中执行各种硬件驱动的init函数。

(2)uboot的这个函数其实就是从linux内核中移植过来的,它的作用也是去执行所有的从linux内核中继承来的那些硬件驱动的初始化函数。

3.2.22 jumptable_init

知识点:二重指针的作用

1)二重指针指向一重指针的地址

2)二重指针指向指针数组的地址

我们这里就是第二种作用指向了函数指针数组

(1)jumptable跳转表,本身是一个函数指针数组,里面记录了很多函数的函数名。看这阵势是要实现一个函数指针到具体函数的映射关系,将来通过跳转表中的函数指针就可以执行具体的函数。这个其实就是在用C语言实现面向对象编程。在linux内核中有很多这种技巧。

(2)通过分析发现跳转表只是被赋值从未被引用,因此跳转表在uboot中根本就没使用。

3.2.23 console_init_r

在我们启动过程中如上的信息就是通过这个函数打印出来的

(1)console_init_f是控制台的第一阶段初始化,console_init_r是第二阶段初始化。实际上第一阶段初始化并没有实质性工作,第二阶段初始化才进行了实质性工作。

(2)uboot中有很多同名函数,使用SI工具去索引时经常索引到不对的函数处(回忆下当时start.S中找lowlevel_init.S时,自动索引找到的是错误的,真正的反而根本没找到。)

(3)console_init_r就是console的纯软件架构方面的初始化(说白了就是去给console相关的数据结构中填充相应的值),所以属于纯软件配置类型的初始化。

(4)uboot的console实际上并没有干有意义的转化,它就是直接调用的串口通信的函数。所以用不用console实际并没有什么分别。(在linux内console就可以提供缓冲机制等不用console不能实现的东西)。

2.2.24 enable_interrupts

#ifdef CONFIG_USE_IRQ/* enable IRQ interrupts */void enable_interrupts(void){unsigned long temp;__asm__ __volatile__("mrs %0, cpsr\n" "bic %0, %0, #0x80\n" "msr cpsr_c, %0":"=r"(temp)::"memory");}#elsevoid enable_interrupts(void){return;}

(1)看名字应该是中断初始化代码。这里指的是CPSR中总中断标志位的使能。

(2)因为我们uboot中没有使用中断,因此没有定义CONFIG_USE_IRQ宏,因此我们这里这个函数是个空壳子。

知识点:uboot中如何调用函数

方案一:在调用函数处使用条件编译,然后函数体实际完全提供代码。

方案二:在调用函数处直接调用,然后在函数体处提供2个函数体,一个是有实体的一个是空壳子,用宏定义条件编译来决定实际编译时编译哪个函数进去。

2.2.25 loadaddr、bootfile两个环境变量

这两个环境变量都是内核启动有关的,在启动linux内核时会参考这两个环境变量的值。

2.2.26 board_late_init

#elseint board_late_init (void){return 0;}

(1)看名字这个函数就是开发板级别的一些初始化里比较晚的了,就是晚期初始化。所以晚期就是前面该初始化的都初始化过了,剩下的一些必须放在后面初始化的就在这里了。侧面说明了开发板级别的硬件软件初始化告一段落了。

(2)对于X210来说,这个函数是空的。

2.2.27 eth_initialize

(1)看名字应该是网卡相关的初始化。这里不是SoC与网卡芯片连接时SoC这边的初始化,而是网卡芯片本身的一些初始化。

(2)对于X210(DM9000)来说,这个函数是空的。X210的网卡初始化在board_init函数中,网卡芯片的初始化在驱动中。

2.2.28 x210_preboot_init(LCD和logo显示)

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

2.2.29 update_all

(1)uboot启动的最后阶段设计了一个自动更新的功能。就是:我们可以将要升级的镜像放到SD卡的固定目录中,然后开机时在uboot启动的最后阶段检查升级标志(是一个按键。按键中标志为"LEFT"的那个按键,这个按键如果按下则表示update mode,如果启动时未按下则表示boot mode)。如果进入update mode则uboot会自动从SD卡中读取镜像文件然后烧录到iNand中;如果进入boot mode则uboot不执行update,直接启动正常运行。

(2)这种机制能够帮助我们快速烧录系统,常用于量产时用SD卡进行系统烧录部署。

2.2.30 主循环

(1)解析器

(2)开机倒数自动执行

(3)命令补全

启动第二阶段流程梳理

首先第一阶段结束后跳转到start_armboot函数处

1.内存分配

2.执行init_sequence函数对board级别的各种硬件初始化,其中包含:

cpu_init(空)Board_init(网卡初始化、开发板的机器码、传参的内存地址)Interrupt_init(初始化定时器Timer4)Env_init(环境变量有关的初始化)Init_baudrate(初始化串口通信的波特率)Serial_init(空)console_init_f(控制台第一阶段初始化)display_banner(串口输出显示uboot的版本)print_cpuinfo(输出CPU的时钟频率信息)Checkboard(确认开发板名称)init_func_i2c(未使用)dram_init(初始化DDR)display_dram_config(显示DRAM大小)

3.CFG_NO_FLASH(无Norflash)

4.CONFIG_VFD和CONFIG_LCD是显示相关的

5.mem_malloc_init(初始化uboot自己维护的堆管理器的内存)

6.mmc_initialize(初始化SoC内部的SD/MMC控制器)

7.env_relocate(环境变量的重定位)

8.IP地址字符串转十进制并对gd数据结构赋值

9.MAC地址的字符串转十六进制并对gd数据结构赋值

10.devices_init(设备的初始化

11.jumptable_init(未使用)

12.console_init_r(控制台第二阶段纯软件配置类型的初始化)

13.enable_interrupts(未使用)

14.loadaddr、bootfile (环境变量读出初始化全局变量)

15.board_late_init(空)

16.eth_initialize(空)

17.x210_preboot_init(LCD屏幕上的logo显示)

18.update_all(启动过程中使用按键可进入自动更新模式进行烧录)

19.Main_loop主循环

启动过程特征总结

(1)第一阶段为汇编阶段、第二阶段为C阶段

(2)第一阶段在SRAM中、第二阶段在DRAM中

(3)第一阶段注重SoC内部、第二阶段注重SoC外部Board内部

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。