嵌入式u-boot浅析

2019-07-13 06:00发布

u-boot主要目的是为操作系统的运行提供准备工作,根据其运行流程简单的分为四部分:_start、board_init_f、relocate_code和board_init_r。其中_start和relocate_code是运行在flash上,而board_init_f和board_init_r是运行于DRAM上的。
下面对其四个部分进行简单的介绍分析:
1、_start
这是u-boot的起始部分,程序从/u-boot/cpu/mips/start.S文件中的_start代码段开始执行的。由于此时内存未初始化,所以该部分是运行于flash上的。其主要的内容有:
(1)    对cpu相关寄存器的初始化;
(2)    TLB(Translation Lookaside Buffer)初始化;
(3)    初始化一块临时内存作为栈空间scratch memory,为内存初始化之前调用c语言使用;
(4)    初始化全局符号表指针GOT pointer;
(5)    使用跳转命令跳转到board_init_f; 2、board_init_f
    该部分是在上面申请的临时内存空间中运行的,主要是内存的初始化及整个寻址空间的部分初始化。其主要是循环调用init_sequence函数指针数组中的成员,如下所示 init_fnc_t *init_sequence[] = { octeon_boot_bus_init, // GPIO使能、flash空间映射及大小设置等; timer_init, env_init, early_board_init, //cpu主频、板子型号等初始化; init_baudrate, //串口频率配置; serial_init, //串口初始化; console_init_f, display_banner, init_dram, //内存初始化; checkboard, //一些检测; init_func_ram, switch_init, //初始化switch的配置; NULL, };
完成上述初始化后,将会申请1MB的内存空间用于存放u-boot代码,并初始化这一内存空间。然后跳转到relocate_code。
3、relocate_code
     该部分主要是将u-boot拷贝到上面申请的1MB内存空间中,然后跳转到board_init_r。
4、board_init_r
  该部分主要是完成后续环境的初始化,如堆空间的分配、串口初始化和网卡初始化等。到目前为止u-boot启动基本完成,已为操作系统运行准备了必要的环境。

5、调试
u-boot的调试方法有很多种,下面介绍两种方式:使用led灯(即点灯)、利用一个已调试完成的u-boot引导调试另一个u-boot等。
在调试过程中在串口未初始化时可以使用点灯的方式跟踪,如果串口已初始化而未输出调试信息,可以关注对串口的配置是否正确。


(1)点灯
所谓的点灯既是在串口设备未初始化之前,驱动led灯的闪烁来判断程序的执行流程来完成调试。使用点灯调试首先需要了解到cpu芯片和led的连接方式,以及所使用的寄存器等。根据pcb图可以得知cpu芯片和led是通过GPIO的进行连接的,再从cpu芯片厂商提供的datasheet查询操作GPIO的相关寄存器和方法。
点灯可以分为两种方式:一种是使用汇编指令进行点灯操作,具体的汇编指令依赖所使用的cpu架构,本文的cpu架构为MIPS;另一种是使用u-boot提供的相关C语言接口进行点灯操作。下面均以GPIO 10的操作分别进行简单介绍:
1)汇编指令点灯操作
#if 1 nop dli t0, 0x8001070000000850 /* GPIO 10 */ ld t1, 0(t0) or t1, 1 sd t1, 0(t0) nop ld t1, 1 nop led: ld t1, 1 bne zero, t1, led nop #endif
2)C语言接口点灯操作
void gpio_set(int bit) //bit为GPIO号 { uint64_t val = read_csr(GPIO_BIT_CFGX(bit)); write_csr(GPIO_BIT_CFGX(bit), val | 1); val = read_csr(GPIO_BOOT_ENA); write_csr(GPIO_BOOT_ENA, val & ~(1<
(2)u-boot引导u-boot
使用u-boot调试u-boot的方式主要是通过已调试完成的u-boot去调试一个待调试的u-boot,本文使用已调试完成的first_uboot作为引导。调试的方式分为两种:
1)待first_uboot启动完成之后,使用其提供的命令(如go)跳转到待调试的u-boot;
2)在first_uboot刚启动时,通过控制汇编跳转指令跳转到待调试的u-boot。
上述两种方式都是为了在调试u-boot过程中减少将flash摘除到擦写器中擦写的次数,提高调试效率。
两者的调试方式虽然类似,但是使用的时期具有一定的区别。第一种方式通过go命令跳转说明first_uboot已经将硬件环境进行了相关的初始化,所以跳转执行时会将待调试的u-boot拷贝到内存中执行,这将导致待调试u-boot初始化硬件的过程不完整。所以建议第一种调试方式主要用于进行初步调试,查看一些不依赖硬件的相关参数。第二种方式可以通过控制跳转点的位置来确保safe_loader还未硬件初始化就跳转到待调试u-boot,这样就能较为完整的调试u-boot,过程中需要结合上述的点灯等操作。虽然这样跳转能确保大部分硬件得到初始化,但是仍然会有一定的问题,目前发现的问题是该cpu为双核,但是通过这样的引导之后加载linux内核未能将第二颗核启动。调试完成之后只使用该u-boot可以顺利将第二颗核启动。

1)go命令跳转
使用go命令跳转进行调试时需要确定已调试的u-boot是否支持该命令,以及待调试u-boot中的相关配置需要进行调整。本文使用已调试完成的first_uboot可以通过命令go跳转到待调试的u-boot进行调试。其中待调试的u-boot需要修改顶层Makefile中的指定其在flash的位置,本文改为0xbec30000。
从上图可以看到需要将first_uboot和u-boot烧录到flash中,先启动first_uboot,完成之后执行go命令跳转到待调试的u-boot中。在调试过程中需要打开u-boot中的相关调试开关,也可以在关键地方增加相应调试信息。

2)汇编指令跳转
根据pcb图并结合u-boot提供的启动汇编代码可以得出,可以使用设备上的RST键进行跳转。从如下截图中可以看出RST键是通过GPIO连接到CPU芯片的GPIO 5号脚。
 
据上面的分析可以设计的调试方式是在启动first_uboot时按RST键则跳转到调试的u-boot,否则继续执行。如下的汇编代码实现了这一调试需求,即在first_uboot刚加载时读取GPIO 5的值,用于判断执行分支。注意跳转指令所使用的地址,j指令为直接跳转,其跳转的地址范围是28位即最大跳转地址空间为256MB;如果跳转地址大过该范围,需要使用寄存器跳转方式,如jr   t1。
#define GPIO_BIT 5 //GPIO 引脚号 #define GPIO_RX_DAT 0x8001070000000880 //GPIO状态寄存器 dli t0, GPIO_RX_DAT ld t1, 0(t0) andi t1, (0x1 << GPIO_BIT) bne zero, t1, boot_cont nop li t1, 0xbfc30000 //跳转地址赋值; j t1 // 跳转到指定地址,即调试u-boot的起始地址