【1】uboot启动流程
这个项目其实是2017年做的项目了,基于海思3519芯片,如何在3519中启动tftp,让uboot可以开机启动搜索tftp server,避免因为摄像机升级失控导致变成搬砖。
本次的使用到的项目规格如下:
board | hi3519v101 C020 |
---|---|
arch | hi3519 |
uboot version | 2010.06 |
1 编译uboot流程
1.1 配置与编译
配置编译环境
当启动介质是 eMMC、 SPI-Nor Flash 或 SPI-NAND Flash 时,使用编译命令:
1 | make ARCH=arm CROSS_COMPILE=arm-hisiv600-linux- hi3519v101_config |
当启动介质是 NAND Flash 时,使用编译命令:
1 | make ARCH=arm CROSS_COMPILE=arm-hisiv600-linux- hi3519v101_nand_config |
编译成功后,将在U-boot目录下生成u-boot.bin。
由于项目使用EMMC作为存储,默认使用hi3519_config配置
编译 U-boot
1 | make ARCH=arm CROSS_COMPILE=arm-hisiv600-linux- |
生产海思uboot镜像
完成配置表格的修改后,保存表格。单击表格第一个标签页上的按钮【 Generage regbin file】 (只能点此按钮),生成临时文件 reg_info.bin。
将临时文件 reg_info.bin 和编译 u-boot 得到的 u-boot.bin 都拷贝到 SDK 中的
“ osdrv/tools/pc/uboot_tools/”目录下,执行命令:
1 | ./mkboot.sh reg_info.bin u-boot-hi3519v101.bin |
其中 u-boot-hi3519v101.bin 就是能够在单板上运行的 U-boot 镜像。
1.2 uboot源码流程解析
通过阅读README可以了解到,所有软件配置在对应的include/configs/<borad_name.h>下,其中如何配置使用CONFIG在README文档中有详细说明,截取225-279如下:uboot很多内容在README中都有说明,可以详细阅读一下
对应在3519上,主要配置就在include/configs/hi3519v101.h
1 | //----->$TOPDIR/README |
当执行 make ARCH=arm CROSS_COMPILE=arm-hisiv600-linux- hi3519v101_config 配置时候,通过分析,会直接在makefile中执行hi3519_config选项(3334行)。
1 | //---> $TOPDIR/makefile |
通过调用config.mk,此处做的主要工作是将和CPU,硬件相关的配置,如文件夹arch,borad的include和asm使用软链接到上层common的asm下,通过配置文件,完成具体实现到抽象的配置。详细的代码可以参考config.mk,我已经将config.mk的6个参数表达的意义写出来了。其中最有意思是最有一段代码
1 | //--->$TOPDIR/mkconfig |
这段代码相当有意思,cat << EOF 表示遇到EOF则终止写入,此刻,会在include文件夹下新建一个config.h文件,这个文件包含configs/hi3519v101.h以及其他的configs文件,真相大白,通过调用mkconfig指令,将所有关于3519v101相关的链接进行链接,配置文件进行新建。这也就呼应了我们第一节所说的,配置configs/hi3519v101.h的CONFIG_XXXX就可以开启编译某些功能。
当执行make ARCH=arm CROSS_COMPILE=arm-hisiv600-linux-,则默认编译uboot.bin,粗略看了一下makefile代码,中间168行开始有大量LIB+=的库,makefile所做的工作非常简单,就是将各模块所编译出来的lib进行总体编译。而各模块根据第一步设置的3519编译选项而编出来的全局变量config.mk进行3519编译。
1.3 如果要新开一个基于3519开发板,uboot怎么办?
了解了uboot编译流程,如需要自己使用3519芯片,但是开发新开发板,新开发板在原有海思提供的开发板上,新增了一些器件,减少了一些功能,新开发板名字叫做dg3519v101,采用3519芯片,增加了时钟芯片,则应该怎么办呢?
1 | 1. makefile新增一个dg3519v101板子配置。配置格式参考上一节第2小节。 |
操作步骤如下:
- 在makefile中增加dg3519v101对应配置。只需要更改board即可。
1
2
3dg3519v101_config: unconfig
@$(MKCONFIG) $(@:_config=) arm hi3519v101 dg3519v101 NULL hi3519v101
$(MKCONFIG) BOARD_NAME ARCH CPU BOAD VENDOR SOC - 在board下新增开发板代码.
1
cp hi3519v101 dg3519v101 -rf
- 在include/configs下增加dg3519v101的配置。注意:
1
cp include/configs/hi3519v101.h include/configs/dg3519v101.h
- 在board文件中,主要是针对开发板自身的一些操作,如开发板有启动项目,比如加密芯片什么,可以在此处增加,在板子初始化的时候执行。
- 在include/configs/dg3519v101.h有很多配置文件的开关,请详细阅读。
- 如开发板芯片也更改了,则需要更改arch/arm下增加芯片。其中include/configs/dg3519v101.h中#define了Hi3519V101的相关配置,也需要一并更改
2 源码分析
从代码入口分析如下:
1 | 1. arch/arm/cpu/hi3519v101/start.s,跟进各自芯片使用汇编搭建C语言环境 |
综述只捡了重要的参数举例,更多的参数没有介绍,请详细查看源代码。但是套路一般是先搭建C语言环境,初始化全局结构体,初始化必备操作,初始化存储设备,存储设备初始化以后,才好进行输入输出,然后进入输出等待,等待用户出入并操作。具体操作后续会详细介绍
2.1 start.s
在arch/arm/cpu/hi3519v101/start.s,在flash中执行的引导代码,也就是bootloader中的stage1,负责初始化硬件环境,把u-boot从flash加载到RAM中去,然后跳到arch/arm/lib/board.c中的start_armboot中去执行。
1.1.6版本的start.s流程:
- 硬件环境初始化:
- 进入svc模式;
- 关闭watch_dog;
- 屏蔽所有IRQ掩码;
- 设置时钟频率FCLK、HCLK、PCLK;清I/D cache;
- 禁止MMU和CACHE;配置memory control;
- 重定位:
- 如果当前代码不在连接指定的地址上(对smdk2410是0x3f000000)则需要把u-boot从当前位置拷贝到RAM指定位置中;
- 建立堆栈,堆栈是进入C函数前必须初始化的
- 清.bss区。
- 跳到start_armboot函数中执行。(arch/arm/lib/board.c)
2.2 arch/arm/lib/board.c
使用start_armboot进入启动。初始化mmc,spi_flash等。
初始化全局结构体 gd =global_data
1 | gd = (gd_t*)(_armboot_start - CONFIG_SYS_MALLOC_LEN - sizeof(gd_t)); |
其中,CONFIG_SYS_MALLOC_LEN在include/configs/hi3519v101.h上定义。
- 关于DDR和DDR_MEM_BASE在include/asm/arch/platform.h下
- 在uboot中配置内存在hi3519v101.h中,#define CONFIG_NR_DRAM_BANKS 1,所以内存只有512M
根据分析,可知道缓冲区如下:gd,是全局数据,结构体如下:1
2
3
4
5
6
7
8显示缓冲区
u-boot(bss,data,text)
heap(for malloc)
gd(global data)
bd(board data)
stack
....
nor flash (0~2M)初始化gd->bd,板子的数据指针,关于开发板的数据记录在此,如下:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22typedef struct global_data {
bd_t *bd; //board data pointor板子数据指针
unsigned long flags; //指示标志,如设备已经初始化标志等。
unsigned long baudrate; //串口波特率
unsigned long have_console; /* serial_init() was called *//* 串口初始化标志*/
unsigned long env_addr; /* Address of Environment struct */ /* 环境参数地址*/
unsigned long env_valid; /* Checksum of Environment valid? */ /* 环境参数CRC检验有效标志 */
unsigned long fb_base; /* base address of frame buffer */
#ifdef CONFIG_VFD
unsigned char vfd_type; /* display type */
#endif
#ifdef CONFIG_FSL_ESDHC
unsigned long sdhc_clk;
#endif
#if 0
unsigned long cpu_clk; /* CPU clock in Hz! */
unsigned long bus_clk;
phys_size_t ram_size; /* RAM size */
unsigned long reset_status; /* reset status register at boot */
#endif
void **jt; /* jump table *//* 跳转表,1.1.6中用来函数调用地址登记 */
} gd_t;在配置过程中,内存只能配置200多M,和此处的配置有关系1
2
3
4
5
6
7
8
9
10
11
12typedef struct bd_info {
int bi_baudrate; /* 串口波特率 */
unsigned long bi_ip_addr; /* IP 地址 */
struct environment_s *bi_env;
ulong bi_arch_number; /* unique id for this board */
ulong bi_boot_params; /* 启动参数 */
struct /* RAM 配置 */
{
ulong start;
ulong size;
}bi_dram[CONFIG_NR_DRAM_BANKS];
} bd_t;
然后其中会初始化各种参数。这个步骤很重要,板子的初始化等都会在此处产生优先执行此结构体中的各种函数1
2
3
4
5
6for (init_fnc_ptr = init_sequence; *init_fnc_ptr; ++init_fnc_ptr) {
if ((*init_fnc_ptr)() != 0) {
hang ();
}
}着重提一下两个比较重要参数1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36int print_cpuinfo (void);
init_fnc_t *init_sequence[] = {
#if defined(CONFIG_ARCH_CPU_INIT)
arch_cpu_init, /* basic arch cpu dependent setup */
#endif
timer_init, /* initialize timer before usb init */
board_init, /* basic board dependent setup */
#if defined(CONFIG_USE_IRQ)
interrupt_init, /* set up exceptions */
#endif
// timer_init, /* initialize timer */
#ifdef CONFIG_FSL_ESDHC
get_clocks,
#endif
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 */
#if defined(CONFIG_DISPLAY_CPUINFO)
print_cpuinfo, /* display cpu info (and speed) */
#endif
#if defined(CONFIG_DISPLAY_BOARDINFO)
checkboard, /* display board info */
#endif
#if defined(CONFIG_HARD_I2C) || defined(CONFIG_SOFT_I2C)
init_func_i2c,
#endif
dram_init, /* configure available RAM banks */
#if defined(CONFIG_CMD_PCI) || defined (CONFIG_PCI)
arm_pci_init,
#endif
/* display_dram_config */
NULL,
}; - env_init()函数。定义了环境变量的参数
- env_init()定义在common/env_common_func.c中。
- env_common_func.c继续追踪,查看env是从default_environment[0]这个全局变量中拿到的各种参数。
- default_environment[0]是定义在common/env_common.c中的。此时,上层抽象的函数已经到位,具体的实现参数和实现函数,依据CONFIG_XXX宏定义寻找
- board_init函数,定义了所有开发板初始化操作,如果新开发板,此处的肯定有差异,需要更改
初始化malloc地址,也是程序栈地址
1 | mem_malloc_init (_armboot_start - CONFIG_SYS_MALLOC_LEN, |
初始化存储设备
初始化flash,此处比较重要,涉及到如何对flash进行操作。3519加入了EMCC的支持。但是定义了几个配置
1 | #define CONFIG_CMD_SF //354行,nor flash操作 |
从代码中可以看出,uboot采用了广撒网的操作,nor flash, nand flash, EMMC都尝试了一遍。
其中,针对于存储的操作,都采用hisi自己写的操作。具体查看drivers/mtd/中hi开头的di驱动。
关于EMMC的驱动,都在drivers/mmc/himciv200.c中。
- emmc擦写可以参考common/cmd_mmc.c参考mmc工作方式
- falsh擦写可以参考comon/cmd_mtd.c工作方式
stdio_init标准输入输出初始化
stdio_init在common/stdio.c中。
u-boot把可以用为控制台输入输出的设备添加到设备列表devlist,并把当前用作标准IO的设备指针加入stdio_devices数组中。
在调用标准IO函数如printf()时将调用stdio_devices数组对应设备的IO函数如putc()。
1 | struct stdio_dev { |
common/main.c
执行main_loop进入不断循环等待用户输入参数。
其中搜索bootdelay,里面有
1 | if (bootdelay >= 0 && s && !abortboot (bootdelay)) { |
这个是等待用户输入字符。
综上:这就是3519v101的uboot启动方式。