Advertisement

uboot源码--第二阶段

阅读量:

在start.s代码的414行跳转到start_armboot函数

start_armboot函数简介

一个很长的函数
(1)该函数位于源代码文件lib_arm/board.c中第444行至908行之间完成
(2)450行并非终点站,在此之后还嵌套调用了其他子函数
(3)整个该函数构成了uboot启动过程中的第二阶段核心模块
构成uboot第二阶段的核心模块
(1)在整个 boot 过程中,第一个主要任务就是初始化Soc内部的各种基础组件(例如看门狗控制器、系统时钟生成器等),随后还包括初始化DDR控制器并完成其重定位操作
(2)从宏观层面来看,在完成第一阶段后 uboot 的第二阶段将致力于初始化剩余尚未被配置的硬件设备。这些硬件设备主要包括外部接口芯片如iNand控制器以及存储器管理芯片忘啦控制器等。当所有必要的硬件配置完毕后 uboot 就会进入命令行模式来接收并处理相应的启动命令
uboot第二阶段运行至此的状态
(1)在 uboot 启动完成后会自动打印一系列初始化过程中的日志信息(这些信息反映了 uboot 在执行第一和第二个主要任务期间所经历的各种状态)。随后 uboot 进入了一个称为倒数 bootdelay 的特殊等待状态,并开始执行 bootcmd命令以对应启动所需的初始操作流程
(2)如果用户没有干预上述过程,则会直接进入自动启动内核模式 (此时 uboot 程序就会退出)。此时用户可以通过按下回车键进入命令行界面来继续与内核交互操作
(3)而一旦用户进入命令行界面后 uboot 就会陷入一个持续循环的工作状态:不断接受输入指令、解析指令并执行相应操作

start_armboot函数源码解析

在初步识别阶段确定的init_fnc_t
init_fnc_t
(1)该函数类型被定义为int类型的void函数
(2)初始化操作符初始化器 init_fnc_ptr 的作用机制如下:要么指向一个一重指针对象;要么指向一个数组结构
gd_t
(1)在start_armboot源程序中的第465行处首次声明了一个gd_t变量;通过查找发现其定义位于宏_DECLARE_GLOBAL_DATA_PTR 的声明范围内

复制代码
    #define DECLARE_GLOBAL_DATA_PTR     register volatile gd_t *gd asm ("r8")

该宏被设计用于声明一个全局变量gd。该变量具有指针类型属性,并被标记为volatile修饰符以保证其可变性。此外,它还被指定为使用寄存器来操作,并采用GCC支持使用的汇编语法形式asm(“r8”)来实现这一点。其作用是将gd移动至寄存器r8中。
DECLARE_GLOBAL_DATA_Ptr这一指令等价于定义并指定一个位于寄存器r8中的全局变量。
由于GD常被频繁访问且属于关键数据块的一部分,在uboot框架中占据重要地位。因此,在uboot内核构建过程中将其配置为一个指向特定存储区域(如GD_s)的指针结构体是非常合理的。
在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的数据结构体;这个bd字段存储了开发板的级联信息,在此结构体内包含了若干硬件相关的参数配置项如波特率设置、IP地址配置、机器码信息等以及DDR内存布局信息。
从469行代码开始为gd分配内存空间

内存使用排布

为了实现高效的数据管理如何进行内存分配?(1)仅仅声明了一个指针变量并不能保证数据的有效性,在程序运行时如果未为这些全局变量预先分配一块独立的内存区域(如果不这样做,在程序运行时可能会出现逻辑漏洞)。此外如何确保数据安全也是关键问题。(2)这两个数据结构都需要占用一段连续的系统资源,并且在正常运行状态下这两个结构都需要占用一段独立的大片物理地址空间。然而由于系统未对整体内存量进行有效规划(即当前存在大量的碎片化空闲空间),因此即使拥有足够的物理容量也无法简单地将它们随意排列组合在一起使用。特别是在uboot内核完成后如何规划系统的物理地址空间布局成为了核心任务之一。(3)为了满足后续操作需求我们通常会采用一种称为"紧凑排列"的技术策略即尽可能地将多个相关对象集中存储在一个连续的空间块中以减少地址转换开销并提高缓存效率

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));

start_armboot函数源码解析

第483行的位置上发现了init_sequence这一项;随后我们在上文部分进行了定位以确定其具体含义;在此过程中发现该项转录信息被转录至当前文件的第416行

复制代码
    	for (init_fnc_ptr = init_sequence; *init_fnc_ptr; ++init_fnc_ptr) {
    		if ((*init_fnc_ptr)() != 0) {
    			hang ();
    			}
    		}

(2)init_sequence由多个函数指针组成,在内存中构成一个数组结构;其中包含了多个指向特定操作的function pointers;这些被指向的对象均为init_fnc_ptr类型的function pointers;其特点在于仅接受void类型的参数;而返回值则为整数值。

复制代码
    	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)在定义时就赋予了相应的初始化操作,在此过程中所有的function pointers都被预先设定好了对应的目标功能。
(4)这是一个二级function pointers变量,在程序运行时能够指向其所属的function array pointer。
(5)我们再次聚焦于start_armboot 函数在第 483 行处,并对其中的那个for loop进行深入分析。

复制代码
    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) 循环内转向我们的Board.c第416行以完成初始化流程

该函数应属于CPU内部的初始化操作;因此目前位置为空;处于UBOOT阶段;尚未加载内核。

从上面的board_init跳转到具体的板子初始化后

board_init
(1)涉及uboot\board\samsung\x210.c文件中的初始化函数
(2)主要原因是后面要频繁使用gd变量。因此,在这里进行全局数据指针的声明较为合理。
(3)可以看出把gd的声明定义成宏的主要原因是为了方便后续多次引用。

复制代码
    smc9115_pre_init();

(3)网卡初始化。CONFIG_DRIVER_DM9000这个宏是x210.c中定义的,这个宏是用来配置网卡的。dm9000_pre_init函数就是对应的DM9000网卡的初始化函数。开发板移植uboot时,如果要移植网卡,主要的工作就在这里。
(4)这函数中主要的网卡的GPIO和端口的配置,而不是驱动。因为网卡的驱动都是现成的正确的,移植的时候驱动是不需要改动的,关键是这里的基本初始化。因为这些基本初始化时硬件相关的。

start_armboot函数源码解析

回到我们的board_init函数

复制代码
    	gd->bd->bi_arch_number = MACH_TYPE;

(1)在board info中,bi_arch_number是一个元素,在该字段中存储的是开发板机器码的信息。所谓机器码是指uboot在给开发板赋予权限时所使用的唯一标识符。
(2)机器码的主要功能在于实现uboot与linux内核间的匹配与兼容。
(3)嵌入式设备的硬件配置通常是高度定制的,在这种情况下不同设备之间的硬件与软件往往是不兼容的。因此,在移植开发板时必须谨慎选择内核镜像版本,并根据具体开发板预先设置对应的机器码信息以确保兼容性。
(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

init_sequence–interrupt_init

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_sequence–init_baudrate

**(1) 由名称可知, init_baudrate主要负责串口通信波特率的初始化设置。(2) getenv_r函数用于获取环境变量的具体值, 在本实现中, 该函数被用来读取环境变量中的baudrate参数, 并将其转换为整数形式供后续使用。(3) 关于baudrate的初始化规则: 首先, 程序将尝试从环境中获取"baudrate"变量的具体值; 若成功获取到该参数, 则将其赋值给gd->bd->bi_baudrate字段并记录于gd->baudrate属性中; 若环境变量读取失败, 则默认采用x210_sd.h文件中定义的CONFIG_BAUDRATE作为默认值进行配置。由此可见, 环境变量的作用具有较高的优先级。(4) 在初始化串口的过程中发现存在两个具有相同功能名称的不同初始化函数: init_sequence和serial_init; 这种重复设计可能带来功能上的冗余与不必要冲突。(5) 根据源代码分析, 本实现所使用的初始化函数来自于uboot/cpvs5pc11x/serial.c文件; (6) 然而, 在调用该函数时发现其内部逻辑并未执行任何操作: 这是因为串口硬件已经被之前的汇编阶段完成初始化配置, 因此在此处无需再单独进行硬件寄存器的配置操作。

init_sequence–console_init_f–display_banner–print_cpuinfo

console_init_f
(1)console_init_fconsole(控制台)的初始阶段初始化函数。其中 f 表示第一阶段初始化函数, r 表示第二阶段初始化函数。通常情况下, 初始化操作无法一次性完成, 因此需要将完整的模块初始化过程划分为两个阶段进行处理
(2)在 uboot/common/console.c 文件中,console_init_f 主要负责将 gd->have_conscle 设置为 1, 其他操作并未执行。

复制代码
    		gd->have_console = 1

(1)display-banner用于通过串口输出uboot标志
(2)在display-banner中调用printf函数将version_string发送到串口上
(3)通过对printf实现的研究发现:其调用关系为printf->puts;而puts函数会根据当前uboot环境判断console是否初始化良好;若初始化正常,则会采用fputs方式完成串口数据发送;若未初始化则会使用serial_puts并直接操作串口寄存器完成数据发送
(4)控制台也是一个通过软件模拟实现的功能模块;其具备专用通信接口功能(包括数据发送接收等操作);这些接口最终都会映射至硬件串口通信功能;因此即使没有 dedicated controller,在uboot环境中也同样能够实现基本通信功能
(5)值得注意的是:在其他系统架构中可以通过软件进行中间层优化设计;例如引入缓冲机制;此时信息仅会被存储于console的buffer中等待刷新至硬件显示端(如LCD屏)
(6)U_BOOT_VERSION这一变量并未直接存在于uboot源代码中;而是由makefile文件进行预先定义;具体数值则来自编译时生成的include/version_autogenerated.h文件中的宏定义实现
uboot启动过程包含以下几个关键步骤

请添加图片描述

这些信息都是print_cpuinfo打印出来的。

init_sequence–checkboard–init_func_i2c–实践

实践

初始化

init_sequence–dram_init–display_dram_config

dram_init
(1)从名称来看,dram_init涉及对DDR的初始化操作。疑问:在汇编阶段是否已经对DDR进行了初始化?如果不进行初始化,在后续运行阶段能否实现重新定位?
(2)该过程会对GD→BD中与DDR配置相关的全局变量进行赋值操作。这一步骤旨在记录当前开发板上 DDR 的具体配置参数(如容量),以便后续系统 boot 时能够正确引用这些资源。
(3)从代码结构可以看出,默认情况下 GD→BD 中定义了一个名为 bi_dram 的结构体数组来管理 DDR 初始化相关参数。
display_dram_config
(1)该函数的主要作用是打印出 DRAM 配置信息的相关数据。
(2)其中一项启动信息显示(DRAM: 512MB)。这个数值反映了当前系统中 DDR 的总存储容量设定。
(3)通过 uboot 运行时获得 GD→BD 中记录的所有硬件相关全局变量的信息包括但不限于内存类型、容量设置等参数。

请添加图片描述

主要涉及板级硬件的初始化以及GD参数配置,在GD到BD的数据链路层中完成相关数据结构的初始化工作。其中涉及网卡初始化设置为GD到BD到BI Arch Number,并完成机器码设置为GD到BD到BI Arch Number;内核传参至DDR地址(GD→BD→BI Boot Parameters);完成Timer4定时器每隔10ms执行一次的操作;完成GD→BD→BAUDRATE波特率设置;在GD→BD→BI_ArchNumber阶段完成SOSSOLE第一阶段的关键配置;在UBOOT启动过程中记录并输出关键步骤信息;完成当前开发板名称信息及系统内存容量等重要参数的信息采集与输出工作

start_armboot489–CFG_NO_FLASH

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系统中,我们自主维护了一段专用堆内存空间,并开发了一套完整的管理和维护机制来操作这一区域。通过这些机制,在这种特殊区域里你可以执行malloc和free操作以申请或释放所需的系统资源。我们已经在DDR类型的存储设备上预留了896KB的空间用于该专用区域。

代码实践时,请考虑移除CFG_NO_FLASH
(1)当我们在代码中定义CFG_NOFLASH宏后,则会遇到编译器报告的错误信息(此处假设为静态链接错误),这表明代码移植存在困难且与固件配置深度绑定紧密相关的问题。具体来说,在该源文件中并未启用该宏配置(即未被该宏包含),因此相关部门未能采取措施解决问题。

请添加图片描述

start_armboot源码解析

start_armboot
(1)针对536至768行代码块设计了特定的开发板初始化流程;这种设计实现了三星Soc芯片对多型号开发板的支持;特别地,在这一区域集中记录了不同开发板特有的初始化步骤;通过使用#if条件判断配合599至632行代码实现了这一功能;
(2)x210相关配置位于599至632行代码块内;这些配置涉及对硬件接口参数的定义以及相应的控制逻辑设置;
(3)mmcInitialize函数主要用于初始化Soc内部的SD/MMC控制器;其核心功能是从底层linux内核移植而来,并结合Soc特有的资源进行了优化实现;该函数位于uboot/drivers/mmc/mmc.c文件中;
(4)在uboot根目录下的drivers文件夹中包含了从linux内核移植而来的各种硬件驱动代码;这些驱动涵盖了网卡、SD卡等多种类型的硬件模块;
(5)mmcInitialize函数为所有基于相同架构的设备提供了一套统一的MMC 初始化流程;该流程通过调用boardMmcInit和cpuMmcInit两个子函数来完成具体的控制器初始化工作;
(6)在MMC主初始化完成后会调用该函数来完成控制器的具体配置;

start_armboot解析

776行env_relocate
(1)env_relocate负责将来自SD卡的特定区域中的环境变量复制至DDR内存中完成读取任务。
(2)这些环境变量最初存储在SD卡上的8个独立扇区中,在常规系统烧录过程中并未包含这些区域的数据。因此,在首次开机时由于未预先加载任何default值而无法成功读取这些关键参数(随后在进行CRC验证的过程中也确认了这一问题)。为了解决这一困境,默认值将被从uboot内部代码中生成并加载至DDR内存以便后续使用(这也是为何我们在每次运行前都会先加载default配置的原因)。这些预设的default值则会被直接加载至DDR内存中,并在下次开机时由uboot从该区域重新提取以维持系统的正常运转状态。
start_armboot–env_relocate–265set_default_env
(1)预设的一组default值
start_armboot–env_relocate–env_relocate_spec–movi_read_env
(1)该模块负责实现SD卡与DDR之间的完整重定位过程

复制代码
    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已经使用的部分
    }

start_armboot

788getenv_IPaddr
(1) 开发板模块中的IP地址由gd->bd进行管理,并基于环境变量ipaddr获取。通过调用getenv函数能够捕获到一个以字符串形式表示的IP地址。随后使用string_to_ip函数将该字符串转换为点分十进制格式表示。
(2) 每个IP地址由四个介于0到255之间的数字字段构成。最简单的方式是使用无符号整数来表示这些数值值域范围内的整数值便于计算机处理与存储然而,在实际应用中我们通常使用标准点分十进制表示法例如常见的网络接口配置采用的是类似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中没有使用,只是定义了。

start_armboot–console_init_r

console_init_r
(1)console_init_r是控制台的第一阶段初始化程序,负责完成初始配置并为后续操作做准备; console_init_r同样是第二阶段初始化程序的一部分,实际上第一阶段初始化过程较为简单,主要完成基础设置; 而第二阶段初始化才是具有实质意义的工作,真正执行核心功能.
(2)在uboot内核中存在大量同名函数,通过SI工具进行定位时容易出现指向错误函数的情况,
enable_interrupts
(1)此函数主要用于启用中断处理代码.其核心功能在于控制CPSR中断标志位的状态切换;
(2)由于我们并未在uboot内核中启用中断机制,因此未定义CONFIG_USE_IRQ这一宏参数; 因此我们设计了一个空壳函数来承载这一逻辑;
(3)为了解决基于宏条件编译的问题,uboot提供了两种解决方案:方案一是在调用目标函数时使用条件编译指令,使能特定代码路径;方案二是在被调用函数内部提供两个版本的实现代码:一个是有功能实现的完整版本,另一个是空壳版本.具体选择哪种方式取决于开发者的实际需求.
859load_addr–BootFile环境变量
(1)这两个环境变量主要用于指导内核启动过程中的地址加载操作;它们存储的是与启动相关的关键信息;在启动过程中内核会根据这些变量的值进行相应的地址解析和文件加载操作.
board_late_init
(1)该阶段属于开发板后期初始化流程的一部分,负责处理前文已完成初始化任务后的剩余配置工作;这一阶段的工作依赖于前一步骤的成功完成才能顺利推进.然而需要注意的是,由于某些预设条件未能满足而导致这一阶段的工作无法正常展开.

start_armboot

872eth_initialize
(1) 属于网卡相关的基本配置操作,在Soc与外设建立通信后,并非直接由Soc完成该部分的本地化设置,而是由外设本身负责这部分的工作流程。
(2) 针对x210系列(如DM9000)而言,在该设备上本功能并未实现。具体而言,在x210系列中,默认情况下其网络接口配置主要通过board_init函数完成;而其网卡芯片的具体初始化则体现在驱动代码中。

877x210_preboot_init(LCD和logo显示)
(1)在uboot启动过程的末期设置了自动生成更新功能。具体来说,在每次uboot启动时都会检查特定条件以决定是否进行软件升级操作:我们首先将新的镜像文件放置在预设好的SD卡目录中,在开机时会触发一个检查流程来确认是否需要执行更新操作(通过检测LEFT按键被按下以判断当前状态是否为update mode)。如果检测到该按键被按下,则表示已经切换至update mode状态;否则则表示处于boot mode状态。
(2)这种机制能够帮助我们快速完成系统烧录工作,在量产过程中通常采用这种方式对设备进行预置系统镜像文件的操作。
dead循环904main_loop
(1)解析器CFG_HUSH_PARSER模块
(2)系统在开机倒数阶段自动触发该功能
(3)输入命令时提供实时补全功能

uboot启动2阶段总结

启动流程回顾,重点函数
(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)特定硬件的初始化函数位置(譬如网卡)

全部评论 (0)

还没有任何评论哟~