uboot移植准备四
Posted 涛涛声依旧
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了uboot移植准备四相关的知识,希望对你有一定的参考价值。
第二部分 start_armboot(void)函数简介
1. 在uboot/lib_arm/board.c中 从327-798。这不是全部,因为里面还调用了别的函数。
2. 为什么这么长的函数,怎么不分成两三个函数?主要因为这个函数整个构成了uboot启动的第二阶段。
3. 宏观分析:uboot第二阶段应该做什么? 概况来讲,uboot第一阶段主要是初始化soc内部的一些部件(譬如,看门狗,时钟,)然后初始化DDR并且完成重定位。 由宏观分析来讲,uboot第二阶段就是要初始化剩下的还没被初始化部件。主要是soc外部硬件(譬如iNand,网卡芯片….)uboot本身的一些东西(uboot命令,uboot环境变量)。然后最终初始化完必须的东西后进入uboot的命令行准备接受命令。
4. Uboot第二阶段完结于何处? Uboot启动后自动运行打印出很多信息(这些信息就是uboot在第一和第二阶段不断进行初始化时,打印出来的信息)。然后uboot进入了倒数bootdelay秒,然后执行bootcmd对应的启动命令。
5. 如果用户没有干涉则会执行bootcmd进入自动启动内核流程。(uboot就会死掉了);此时用户可以按下回车键打断uboot的自动启动进入uboot的命令行下。然后uboot就一直工作在命令行下。
6. Uboot的命令行就是一个死循环,循环体内不断重复,接收命令,解析命令,执行命令,这就是uboot最终归宿。
2.6.2 start_armboot(void)
1. init_fnc_t 、
typedef int (init_fnc_t) (void);这是一个函数类型。
Init_fnq_ptr是一个二重函数指针,回顾一下高级c语言中讲过;二重指针的作用有2个(其中一个是用来指向一重指针),一个是用来指向指针数组。因此这里的init_func_ptr指向一个函数指针数组。
3. 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中。
综合分析,DECLARE_GLOBAL_DATA_PTR就是定义了一个要放在寄存器r8中的全局变量。名字叫gd,类型是一个指向gd_t类型变量的指针。
为什么要定义为register?因为这个全局变量(gd)是uboot中很重要的一个全局变量(准确的说这个全局变量是一个结构体,里面有很多内容,这些内容加起来构成的结构体就是uboot中常用的所有的全局变量),这个gd在程序中经常被访问,因此放在register中提升效率。因此纯粹是运行效率方面的考虑,和功能要求无关。
gd_t定义在include/asm-arm/global_data.h 定义了很多全局变量,都是整个uboot使用的,其中一个bd_t类型的指针,指向一个bd_t类型的变量,这个bd是开发板的板级信息的结构体,里面有不少硬件相关的参数,譬如波特率,IP地址,机器码,DDR内存分布。
2.6.3 内存使用排布
1.为什么要分配内存 DECLARE_GLOBAL_DATA_PTR只能定义了一个指针,也就是说gd里的这些全局变量并没有被分配内存,我们在使用gd之前要给他分配内存,否则gd也只是一个野指针而已。
2.gd和bd需要内存,内存当前没有被人管理(因为没有操作系统统一管理内存),大片的DDR内存散放着可以随意使用(只要使用内存地址直接去访问内存即可)。但是因为uboot中后续很多操作还需要大片的连着内存,因此这里使用内存本着够用就好,紧凑排布的原则,所有我们在uboot中需要有一个整体规划。
2.6.3.2 内存排布
1.uboot区: CFG_UBOOT_BASE-XX 长度为uboot的实际长度
2.堆区: 长度为 CFG_MALLOC_LEN 为1040KB
3.栈区: 长度为CFG_STACK_SIZE 实际为512KB
4.gd 长度为sizeof(gd_t),实际36字节。
5.bd 长度为sizeof(bd_t),实际为44字节左右。
6.内存间隔 为了防止高版本的gcc的优化造成的错误。
2.6.4 start_armboot解析2
2.6.4.1 for循环执行init_sequence
1.init_sequence是一个函数指针数组,数组中存储了很多个函数指针,这些指向指向的函数都是以init_fnc_t类型(特征是接收参数是void类型,返回值int)。 init_sequence在定义时就同时给了初始化,初始化的函数指针都是一些函数名。 Init_fnc_ptr是一个二重函数指针,可以指向init_sequence这个函数指针数组。 用一个for循环肯定是想要去遍历这个函数指针数组(遍历的目的也是依次执行这个函数指针数组中的所有函数),思考:如何遍历一个函数指针数组?有2种,第一种也是最常用的一种,用下标去遍历,用数组元素个数来截止。
第二种,不常用,但是也可以,就是在数组的有效元素末尾放一个标志,一次遍历到标准出即可截止。(有点类似字符串的思路).我们这里使用了第二种思路,因为数组中存的全是函数指针,因此我们选用了NULL来作为标志,我们遍历时从开头依次进行。
Init_fnc_t的这些函数的返回值定义方式一样的,都是:函数执行正确是返回0,不正确则就挂起。从分析hang函数可知:uboot启动过程中初始化板级硬件时不能出任何错误,只要有一个错误整个启动就终止,除非重启开发板没有任何办法。
Init_sequence中的这些函数,都是board级别的各种硬件初始化。
2.6.4.2 cpu_init
看名字这个函数应该是cpu内部的初始化。所以是空的。
2.6.4.3 board_init
Board_init是在uboot/board ….. 里面。 DECLARE_GLOBAL_DATA_PTR在这里声明是为了后面使用gd方便,可以看出把gd的声明定义成一个宏的原因就是我们要到处去使用gd,因此就要到处声明,定义成宏比较方便。
网卡初始化。CONFIG_DRIVER_DM9000 这个宏是TQ210.h中定义的,这个宏用来配置开发板的网卡的,dm9000_pre_init()这个函数就是对应的DM9000网卡的初始化函数,开发板移植uboot时,如果要移植网卡,主要的工作就在这里。 这个函数主要是网卡的GPIO和端口的配置,而不是驱动,因为网卡的驱动都是现成的正确的,移植的时候驱动时不需要改动的,关键是这里的基本初始化,因为这些基本初始化是硬件相关的。
2.6.5 start_armboot解析3
2.6.5.1 gd->bd->bi_arch_number = MACH_TYPE;
1)bi_arch_number是board_info中的一个元素,含义是:开发板的机器码。所谓机器码就是uboot给这个开发板的一个唯一编号。机器码的主要作用就是在uboot和内核之间进行比对和适配。嵌入式设备中每一个设备的硬件都是定制化的,不能通用。这就告诉我们,这个开发板移植的内核镜像绝对不能下载另一个开发板去,否则也不能启动,就算启动也不能正常工作,有很多隐患。因此linux做了个设置;给每个开发板做个唯一编号(机器码)。然后再uboot,linux内核中都有一个软件维护的机器码编号。然后开发板,uboot,linux三者比对机器码,如果机器码对上了就启动,否则就不启动,(因为软件认为我和这个硬件不适配)。 MACH_TYPE在TQ210.h中定义,值是2456,并没有特殊含义,只是当前开发板对应的编号,这个编号代表了TQ210这个开发板的机器码,将来这个开发板上面移植的linux内核中的机器码也必须是2456,否则启动不起来。 Uboot中配置的这个机器码,会作为uboot给linux内核的传参的一部分传给linux内核,内核启动过程中会比对这个接收到的机器码,和自己本身的机器码相比对,如果相等就启动,如果不相等就不启动。 理论上说,一个开发板的机器码不能自己随便定。理论来说有权利发放这个机器码只有uboot官方,所有我们做好一个开发板并且移植了uboot之后,理论上应该提交给uboot官方审核并发放机器码(好像是免费的)。但是国内的开发板基本没有申请。(主要是因为国内开发者英文都不行,和国外开源社区接触比较少)。都是自己随便编号,随便编号的问题就是有可能和别人的编号冲退,但是只要保证uboot和kernel中的编号是一致的,就不影响自己的开发板启动。
gd->bd->bi_boot_params = (PHYS_SDRAM_1+0x100);
board_info中另一个主要元素,bi_boot_params表示uboot给linux kernel启动时的传参的内存地址。也就说uboot给linux内核传参的时候这么传的:uboot事先将准备好的传参(字符串:bootargs)放在内存的一个地址处(bi_boot_params),然后uboot就启动了内核(uboot在启动内核时真正是通过寄存器r0 r1 r2来直接传递参数的,其中有一个寄存器就是bi_boot_params)。内核启动后从寄存器中读取bi_boot_params就知道了uboot给我们传递的参数到底在内存的哪里。然后自己去内存的那个地方去找bootargs。
经过计算得知:TQ210中bi_boot_params值为0x20000100,这个内存地址就被分配用来做内核传参了。所以在uboot的其他地方使用内存时要注意,千万不敢把;这里给淹没了。
背景:DDR的配置。
board_init中除了网卡的初始化外,剩下的2行用来初始化DDR。
注意:这里的初始化DDR和汇编阶段lowlevel_init中初始化DDR是不同的,当时是硬件的初始化;目的是让DDR可以开始工作。现在是软件结构中一些DDR相关的属性配置,地址设置的初始化,是纯软件层面的。 软件层次初始化DDR的原因,对于uboot来说,他怎么知道开发板上到底有几片DDR内存,每一片的起始地址,长度这些信息呢?在uboot的设计中采用了一种简单直接有效的方式,程序员在移植uboot到一个开发板时,程序员在TQ210.h中使用宏定义去配置出来板子上DDR内存信息,然后uboot只要读取这些信息即可。(实际上还有另外一条思路:就是uboot通过代码读取硬件信息来指导DDR配置,但是uboot没有这样。实际上PC的Bios采用的是这种。)
TQ210.h544行中使用了标准的宏定义来配置DDR相关的参数。主要配置了这么几条信息:有几片DDR内存,每一片DDR的起始地址,长度。这里的配置信息我们在uboot代码中使用到内存时就可以从这里提取使用(想象uboot中使用到内存的地方都不是直接用地址数字的,都是用宏定义的)。
2.6.6 start_armboot解析4
2.6.6.1 interrupt_init 看名字函数是和中断初始化有关的,但实际上不是,实际上这个函数是用来初始化定时器的(实际使用的是Timer4). 裸机中讲过:210共有5个PWM定时器,其中Timer0-Timer3都有一个对应的PWM信号输出的引脚,而Timer4没有引脚,无法输出PWM波形。Timer4在设计的时候就不是用来输出pwm波形的(没有引脚,没有TCMPB寄存器),这个定时器被用来做计时。 Timer4用来做计时时要使用到2个寄存器:TCNTB4 TCNTO4.。 TCNTB中存了一个数,这个数就是定时次数,(每一次时间是由时钟决定的,其实就是由2级时钟分频器决定的)。我们定时是只需把定时时间/基准时间 = 数,这个数放入TCNTB中即可。我们通过TCNTO寄存器即可读取时间有没有减到0,读取到0后就知道定的时间已经到了。 使用Timer4来定时,因为没有中断支持。所以cpu不能做其他事情同时定时,cpu只能使用轮询方式来不断查看TCNTO寄存器才能知道定时时间到了没。因此Timer4定时不能实现微观上的并行。Uboot中定时是通过Timer4来定时,所以uboot中定时时不能做其他事。(考虑下,典型的就是bootdelay,bootdelay中实现定时并且检查用户输入用轮询方式实现的,原来参考裸机中按键章节的按键)。 Interrupt_init函数将timer4设置定时为10ms。关键部位就是get_PCLK函数获取系统设置的PCLK_PSYS时钟频率,然后设置TCFG0和TCFG1进行分频,然后计算出设置为10ms时需要要TCNTB中写入的值,将其写入TCNTB,然后设置为autoreload模式,然后开定时器开始计时就没了。
总结:在学习这个函数时,注意标准代码和之前裸机代码中的区别,重点学会:通过定义结构体的方式访问寄存器,通过函数来自动计算设置值以设置定时器。
2.6.6.2 env_init
1.env_init,看名字就知道是和环境变量有关的的初始化。 为什么有很多env_init函数,主要原因是uboot支持各种不同的启动介质(譬如norflash,nandflash,inand,sd卡….)我们一般从哪里启动就会把环境变量env放到哪里。而各种介质存取操作env的方法都是不一样的。因此uboot支持了各种不同介质中env操作方法。所有好多个env_xxx开头的c文件,实际使用的那一个要根据自己开发板使用的存储介质来定。(这些env_xxx.c同时只有一个会起作用,其他事不能进去的。通过TQ210.h配置的宏来决定谁被包含的。)对于TQ210来说,我们应该看的是env_movi.c函数。经过基本分析,这个函数只是对内存里维护的那一份uboot的env做了基本的初始化或者说是判定(判定里面有没有能用的环境变量).当前我们还没有进行环境变量SD卡到DDR中的relocate,因此当前环境变量是不能用的。 在start_armboot函数中调用env_relocate才进行环境变量从SD卡去读取。
2.6.7 start_armboot解析5
1. init_baudrate看名字就是初始化串口通信的波特率的。
2.getenv_r函数用来读取环境变量的值,用getenv函数读取环境变量中的”baudrate”的值(注意读取到的值是字符串类型),然后用simple_strtoul函数将字符串传换成数字格式的波特率。
3.baudrate初始化时的规则是:先去环境变量中读取”baudrate”这个环境变量的值。如果读取成功则使用这个值作为环境变量,记录在gd->baudrate和gd->bd->i_baudrate中;如果读取不成功则使用TQ210.h中的CONFIG_BAUDRATE的值作为波特率,
2.6.7.2 srial_init
1.初始化串口(疑问:start.S调用了lowlevel_init.S中已经使用汇编初始化过串口了,这里怎么又初始化?这两个初始化是重复的还是各自有不同?)
2.SI中可以看出uboot中有很多个derial_init函数,我们使用uboot/cpu/s5pc11x/serial.c中的serial_init函数。
2.进来后发现serial_init函数其实什么也没做。因为在汇编阶段串口已经被初始化过了,因此这里就不再进行硬件寄存器的初始化了。
2.6.8 start_armboot解析6
2.6.8.1 console_init_f
Console_init_f是console控制台的第一阶段初始化,f表示是第一阶段初始化,r表示第二阶段初始化,有时候初始化函数不能一次一起完成,中间必须要夹杂一些代码,因此将完整的一个模块的初始化分成了2个阶段。(我们的uboot中start_armboot的734行进行了console_init_r的初始化。)
Console_init_f 在uboot/common/console.c中,仅仅是对gd->have_console = 1;
2.6.8.2 display_banner函数
1.dispaly_banner用来串口输出显示uboot的logo
2.display_banner使用printf函数向串口输出了version_string这个字符串,那么上面的分析表示console_init_f并没有初始化号console怎么就可以printf了呢?
3.通过追踪printf的实现,发现printf->pusts,而puts函数中会判断当前uboot中console有没有初始化好,如果console初始化好了则调用fputs完成串口发送;如果console尚未初始化好则会调用serial_puts(在调用serial_putc直接操作串口寄存器进行内容发送)。
4.控制台也是通过串口输出,非控制台也是通过串口输出,究竟什么是控制台,和不用控制台有什么区别?实际上分析代码会发现,控制台是用软件虚拟出来的设备,这个设备有一套专用的通信函数(发送 接收 …),控制台的通信函数最终会映射到硬件的通信函数中来实现。Uboot中实际上控制台的通信函数是直接映射到硬件串口的通信函数中的,也就是说uboot中用没用控制器其实并没有本质差别。 但是在别的体系中,控制台的通信函数映射到硬件通信函数时可以用软件来做一些中间优化,譬如说缓冲机制。操作系统中的控制台都使用了缓冲机制,所有有时候我们printf了内容,但是屏幕上并没有看到输出信息,就是因为被缓冲了,我们输出的信息只是到了console的buffer中,buffer还没有被刷新到硬件输出设备上,尤其在输出设备的LCD屏幕时。
6.U_BOOT_VERSION在uboot源代码中找不到定义。这个变量实际上是在makefile中定义的,然后再编译时生成的include/autogenerated.h中用一个宏定义来实现的。
2.6.8.3 print_cpuinfo
1.uboot启动过程中:
这些信息都是pirnt_cpuinfo打印出来的。
2.回顾ARM裸机中时钟配置一章的内容,比对这里调用的函数中计算各种时钟的方法,自己慢慢分析体会这些代码的原理和实现方法,这就是学习。
2.6.9 start_armboot解析7
1. checkboard 看名字是检查,确认开发板的意思。这个函数的作用就是检查当前开发板时哪个开发板并且打印出开发板的名字。
2.init_func_i2c 这个函数实际没有被执行。如果将来我们的开发板要扩展I2C来接硬件,则在下TQ210.h中配置相应的宏即可开启。
2.6.9.3 uboot学习实践
1.对uboot源代码进行完修改(修改内容根据自己的理解和分析来修改)。
2.make distclean 然后make TQ210_config然后make
3.编译完成得到u-boot.bin,然后去烧录。烧录方法安照裸机第三部分讲的linux下使用dd命令来烧写的方法来烧写。
4.烧写过程:
第一步:进入sd_fusing目录下。
第二步:make clean
第三步:make
第四步:插入sd卡, ls /dev/sd* 得到SD卡在ubuntu中的设备号(一般是/dev/sdb,注意SD卡要链接到虚拟机ubuntu中)
第五步: ./sd_fusing.sh /dev/sdb
总结:uboot就是个庞大点复杂点的裸机程序而已,我们完全可以对他进行调试。调试的方法就是按照上面步骤,根据自己对代码的分析和理解对代码进行更改,然后重新编译烧录运行,根据运行结果来学习。
2.6.10 start_armboot解析8
2.6.10.1 dram_init:看名字是关于DDR的初始化,疑问:在汇编阶段已经初始化DDR了否则也无法relocate到第二部分运行,怎么在这里又初始化DDR?
Dram_init都是在给gd->bd里面关于DDR配置部分的全局变量赋值,让gd->bd数据记录下当前开发板的DDR的配置信息,以便uboot中使用内存。
从我们代码来看,其实就是初始化gd->bd->bi_dram这个结构体数组。
2.6.10.2 display_dram_config
1.看名字意思就是打印显示dram的配置信息。启动信息中的: (DRAM: 1G)就是在这个函数中打印出来的。 思考:如何在uboot运行中得知uboot的DDR配置信息?uboot中有一个命令叫bdinfo,这个命令可以打印出gd->bd中记录的所有硬件相关的全局变量的值,因此可以得知DDR的配置信息。
2.6.10.3 init_sequence总结
1.都是板级硬件的初始化以及gd gd->bd中的数据结构的初始化。譬如:网卡初始化,机器码(gd->bd->bi_arch_number),内核传参DDR地址(gd->bd->bi_boot_params),Timer4初始化为10ms一次,波特率设置(gd->bd->bi_baudrate和gd->baudrate),console第一阶段初始化(gd->have_console设置为1),打印uboot的启动信息,打印cpu相关设置信息,检查并打印当前开发板名字,DDR配置信息初始化(gd->bd->bi_dram),打印DDR总容量。
2.6.11 start_armboot解析9
1.虽然NandFlash和NorFlash都是flash,但是一般NandFlash会简称为NandFlash为Nand而不是Flash,一般讲Flash都是指的NorFlash。这里2行代码是Norflash相关的。
2.flash_init执行的是开发板中对应Norflash的初始化,display_flash_config打印的也是NorFlash的配置信息 Flash: 8 MB就是这里打印出来的。但是实际上TQ210中是没有Norflash的。所以这两行代码可以去掉的。(我也不知道为什么没去掉?猜测原因有可能是去掉这两行代码导致别的地方工作不正常,需要花时间去移植调试,实际上不去掉除了显示有8MB flash实际没有之外也没有别的影响。)
CONFIG_VFD是显示相关的,这个是uboot中自带的LCD显示相关的,这个是uboot中自带的LCD显示的软件架构。但是实际上我们用LCD而没有使用uboot中设置这套软件架构,我们自己在后面自己添加了一个LCD显示的部分。
2.6.11.2 mem_malloc_init
1.这个函数用来初始化uboot的堆管理器。
2.uboot中自己维护了一段堆内存,肯定自己就有一套代码来管理这个堆内存。有了这些东西uboot中你也可以malloc,free这套机制来申请内存和释放内存。我们DDR内存中给堆预留了896KB的内存。
2.6.11.3 代码实践,去掉FLASH行不行。
结论:加上CONFIG_NOFLASH宏之后编译出错,说明代码移植的不好,那个文件的包含没有被这个宏控制,于是乎移植的人就直接放着没管。
2.6.12 start_armboot解析10
1.开发板独有的初始化:mmc初始化
从421行开始,开发板独有的初始化。意思是三星用一套uboot同时满足了好多个系列型号的开发板,然后再这里把不同开发板自己独有的一些初始化写到了这里。用#if条件编译配合CONFIG_XXX宏来选定特定的开发板。
TQ210从510行,是它相关的配置。 mmc_initialize看名字就应该是MMC相关的一些基础的初始化,其实就是用来初始化SOC内部的SD/MMC控制器的。函数在uboot/drivers/mmc.c里面的。
Uboot中对硬件的操作(譬如网卡,SD卡)都是借用linux内核中的驱动来实现的,uboot根目录底下有个drivers文件夹都是从linux内核中移植过来的各种驱动源文件。
Mmc_initialize是具体硬件架构无关的一个MMC初始化函数,所有的使用了这套架构的代码都调用了这个函数完成MMC初始化。Mmc调用了board_mmc_init()和cpu_mmc_init(),来完成具体的硬件的MMC控制器初始化工作。
cpu_mmc_init在/uboot/cpu/s5pc11x/cpu.c中,在这里面又间接调用了drivers/mmc/s3c_mmcxxx.c中的驱动代码来初始化硬件MMC控制器。这里面分层很多,分层的思想一定要有,否则完全就糊涂了。
2.6.13 start_armboot解析11
1.env_relocate是环境变量的重定位,完成从SD卡中将环境变量读取到DDR中的任务。
2.环境变量到底从哪里来?SD卡中有一些(8个)独立的扇区作为环境变量存储区域的。但是我们烧录/部署系统时,我们只是烧录了uboot分区,kernel分区和rootfs分区,根本不曾烧录env分区,所有当我们烧录完系统第一次启动时ENV分区是空的,本次启动uboot尝试去SD卡的ENV分区读取环境变量时失败(读取回来后进行CRC校验时失败),我们uboot选择从uboot内部代码中设置的一套默认的环境变量出发来使用(这就是默认的环境变量)。这套默认的环境变量的本次运行时会被读取到DDR中的环境变量中,然后被写入(也可能是你savenv时写入,也可能是uboot设计了第一次读取默认环境变量后就写入)SD卡的ENV分区。然后下次再次开机时uboot就会从SD卡的ENV分区读取环境变量到DDR中,这就是
真正的从SD卡到ddr中重定位ENV的代码是在env_relocate_spec内部的movi_read_env完成的。
2.6.14 start_armboot解析12
1.IP地址、MAC地址确定 开发板的ip地址是在gd->bd维护的,来源于环境变量。Getenv函数用来获取字符串格式的IP地址。然后用string_to_ip将字符串格式的IP地址转成字符串格式的点分十进制格式。
2.IP地址由4个0-255之间的数字组成,因此一个IP地址在程序中最简单的存储方法就是一个unsigend int。但是人类容易看懂的并不是这种类型,而是点分十进制类型。这两种类型可以互相转换。
2.devices_init
看名字就是设备的初始化,这里的设备指的就是开发板上的硬件设备。放在这里初始化的设备都是驱动设备,这个函数本来就是从驱动框架中衍生出来的。Uboot中很多设备的驱动时直接移植linux内核(譬如网卡,SD卡),linux内核中的驱动都有相应的设备初始化函数。Linux内核在启动过程中就有一个devices_init(名字不一定完全对,但是差不多),作用就是集中执行各种硬件驱动的init函数。
Uboot的这个函数其实就是从Linux内核移植过来的,它的作用也是去执行所有的从linux内核中继承
4. Jumptable_init jumptable跳转表,本身是一个函数指针数组,里面记录了很多函数的函数名。看这阵势是要实现一个函数指针到具体函数的映射关系,将来通过跳转表中的函数指针就可以执行具体的函数。这个其实就是在用c语言实现面向对象编程。在linux内核中有很多这种技巧。 通过分析发现跳转表只是被赋值从未被引用,因此在uboot中没有用到。
2.6.14 start_armboot解析13
1.console_init_r console_init_f是控制台的第一阶段初始化,console_init_r是第二阶段初始化。第一阶段并没有做实质性东西,第二阶段初始化才开始实质性东西。
2.uboot中有很多同名函数,使用SI工具去索引到不对的函数出。自动索引到时错误的,真正的反而根本没找到。
3.console_init_r就是console的纯软件架构方面的初始化。(说白了就是去给console相关的数据结构中填充相应的值),所以属于纯软件配置类型的初始化。
4.uboot的console实际上并没有干什么意义的转化,它就是直接调用的串口通信函数。所以用不用console实际并没有什么分别。
5. enable_interrupts 看名字应该是中断初始化代码。这里指的是cpsr中总中断标志位的使能。因为我们uboot中没有使用中断,因此没有定义CONFIG_USE_IRQ宏,因此我们这里这个函数是空壳子。
6. uboot中经常出现一种情况就是根据一个宏是否定义了来条件编译决定是否调用一个函数内部的代码。Uboot中有2中解决方案来处理这种情况:方案一:在调用函数处使用条件编译,然后函数体实际完全提供代码。方案二;在调用函数处直接调用,然后再函数体处提供2个函数体,一个有实体的一个是空壳子。
2.6.15.3 loadaddr bootfile两个环境变量
这两个环境变量都是内核启动有关的,在启动linux内核时会参考这两个环境变量的值。
2.6.15.4 board_late_init 看名字这个函数就是开发板级别的一些初始化里比较晚的了,就是晚期初始化。所有晚期就是前面该初始化的都初始化过了,剩下的一些必须放在后面初始化的就在这里了。侧面说明了开发板级别的硬件软件初始化告一段落了。。对于TQ210来说这个函数是空的。
2.6.16 start_armboot解析
1.eth_initialize 看名字应该是网卡相关的初始化,这里不是soc与网卡芯片链接时soc这边的初始化,而是网卡芯片本身的一些初始化。 对于TQ210来说,这个函数是空的。
第三部分 随堂记录
1.uboot和内核到底是什么 uboot是一个裸机程序。
Uboot的本质就是一个复杂点的裸机程序。和我们在ARM裸机全集中学习的每个裸机程序并没有本质区别。
ARM裸机第十六部分写了个简单的shell,这东西其实就是个mini型的uboot。
2. 内核本身也是一个“裸机程序”
操作系统内核本身就是一个裸机程序,和uboot,和其他裸机程序并没有本质区别。
区别就是操作系统运行起来后在软件上分为内核层和应用层,分层后两层的权限不同,内存访问和设备操作的管理上更加精细(内核可以随便访问各种硬件,而应用程序只能限制的访问硬件和内存地址)。
直观来看,uboot的镜像是u-boot.bin。linux系统的镜像是zImage,这两个东西其实都是两个裸机程序镜像。从系统启动角度来讲,内核其实就是一个大的复杂点裸机程序。
2.7.1.3 部署在SD卡中特定分区内
一个完整的软件+硬件的嵌入式系统,静止时bootloader,kernel,rootfs等必须的软件都以镜像的形式存储在启动介质中;运行时都是DDR内存中运行的,与存储介质无关。上面2个状态都是稳定的,第3个状态是动态过程,即从静止态到运行态的过程,也就是启动过程。
动态的启动过程就是一个从SD卡逐步搬移到DDR内存,并且运行启动代码进行相关硬件初始化和软件架构的建立,最终达到运行时稳定状态。
静止时u-boot.bin zImage roots 都在SD卡中,它们不可能随意存在SD卡任意位置,因此需要对SD卡进行分区,然后将各种镜像各自存在各自的分区中,这样在启动过程中uboot,内核等就知道到哪里去找谁。(uboot和kernel中的分区表必须一致),同时和SD卡的实际使用分区要一致。
2.7.1.4 运行时必须先加载到DDR中链接地址处
Uboot在第一阶段中进行重定位时将第二阶段(整个uboot镜像)加载到DDR的0XC3E00000地址处,这个地址就是uboot的链接地址。内核也有类似的要求,uboot启动内核时将内存从SD卡读取放到DDR中(其实就是重定位的过程),不能随意放置,必须放在内核的链接地址处,否则启动不起来。譬如我们使用的内核链接地址为0x20008000.
2.7.1.5 内核启动需要必须的启动参数
1. uboot是无条件启动的,从零开始启动的。
2. 内核时不能开机自动完全从零开始启动的,内核启动要别人帮忙,uboot要帮助内核实现重定位(从SD卡到DDR),uboot还要给内核提供启动参数。
2.7.2 启动内核第一步,加载内核到DDR中。
Uboot要启动内核,分为2个步骤,第一步是将内核镜像从启动介质中加载到DDR中,第二步是去DDR中启动内核镜像。
(内核代码根本就没有考虑重定位,因为内核知道会有uboot之类的把自己加载到ddr中链接地址处的,所以内核直接就是从链接地址处开始运行的)
2.7.2.1 静态内核镜像在哪里?
1. SD卡/inand /nand/norflash等:raw分区
常规启动时各种镜像都在SD卡中,因此uboot只需要从SD卡的kernel分区读取内核镜像到DDR中即可。读取要使用uboot的命令来读取(譬如TQ210的iNand版本是nand命令,)。
2. 这种启动方式来加载ddr,使用命令 nand read kernel 30008000. 其中kernel指的是uboot中的kernel分区(就是uboot中规定的SD卡中的一个区域范围,这个范围被设计来存放kernel分区)。
3. Tftp nfs 等网络下载方式从远端服务器获取镜像
Uboot还支持远程启动,也就是内核镜像不烧录到开发板的SD卡中,而是放在主机的服务器中,然后需要启动时uboot通过网络从服务器中下载镜像到开发板的ddr中。
分析总结:最终结果要的是内核镜像到ddr中特定地址即可,不管内核镜像是怎么到ddr中的,以上2中方式各有优势,产品出厂时会设置为从SD卡中启动,tftp下载远程启动这种方式一般用来开发
以上是关于uboot移植准备四的主要内容,如果未能解决你的问题,请参考以下文章
嵌入式linux开发uboot移植——uboot启动内核的机制
[架构之路-27]:目标系统 - 系统软件 - bootloader uboot的方案选择移植与定制化(硬件电路板的适配)