这个项目其实是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
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
36
37
38
39
//----->$TOPDIR/README

Software Configuration:
=======================

Configuration is usually done using C preprocessor defines; the
rationale behind that is to avoid dead code whenever possible.

There are two classes of configuration variables:

* Configuration _OPTIONS_:
These are selectable by the user and have names beginning with
"CONFIG_".

* Configuration _SETTINGS_:
These depend on the hardware etc. and should not be meddled with if
you don't know what you're doing; they have names beginning with
"CONFIG_SYS_".

Later we will add a configuration tool - probably similar to or even
identical to what's used for the Linux kernel. Right now, we have to
do the configuration by hand, which means creating some symbolic
links and editing some configuration files. We use the TQM8xxL boards
as an example here.

Configuration Options:
----------------------
Configuration depends on the combination of board and CPU type; all
such information is kept in a configuration file
"include/configs/<board_name>.h".

Example: For a TQM823L module, all configuration settings are in
"include/configs/TQM823L.h".


Many of the options are named exactly as the corresponding Linux
kernel configuration options. The intention is to make it easier to
build a config tool - later.

当执行 make ARCH=arm CROSS_COMPILE=arm-hisiv600-linux- hi3519v101_config 配置时候,通过分析,会直接在makefile中执行hi3519_config选项(3334行)。

1
2
3
4
5
6
//---> $TOPDIR/makefile
//先清除所有配置,然后使用本地的mkconfig进行配置。mkconfig可以输入4-6位参数
hi3519v101_config: unconfig
@$(MKCONFIG) $(@:_config=) arm hi3519v101 hi3519v101 NULL hi3519v101
//MKCONIFG 解析如下
$(MKCONFIG) BOARD_NAME ARCH CPU BOAD VENDOR SOC

通过调用config.mk,此处做的主要工作是将和CPU,硬件相关的配置,如文件夹arch,borad的include和asm使用软链接到上层common的asm下,通过配置文件,完成具体实现到抽象的配置。详细的代码可以参考config.mk,我已经将config.mk的6个参数表达的意义写出来了。其中最有意思是最有一段代码

1
2
3
4
5
6
7
//--->$TOPDIR/mkconfig
cat << EOF >> config.h
#define CONFIG_BOARDDIR board/$BOARDDIR
#include <config_defaults.h>
#include <configs/$1.h>
#include <asm/config.h>
EOF

这段代码相当有意思,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
2
3
4
5
1. makefile新增一个dg3519v101板子配置。配置格式参考上一节第2小节。
2. 在board下新增开发板代码。
3. 在include/configs下新增dg3519v101的配置,并开启或者关闭新功能。
4. 如更改了芯片,则还需要在arch/arm下新增芯片。
5. 如增加了驱动,则需要在drivers下更新驱动,并在include/configs下开启编译选项。

操作步骤如下:

  1. 在makefile中增加dg3519v101对应配置。只需要更改board即可。
    1
    2
    3
    dg3519v101_config: unconfig
    @$(MKCONFIG) $(@:_config=) arm hi3519v101 dg3519v101 NULL hi3519v101
    $(MKCONFIG) BOARD_NAME ARCH CPU BOAD VENDOR SOC
  2. 在board下新增开发板代码.
    1
    cp hi3519v101 dg3519v101 -rf
  3. 在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
2
3
4
5
6
7
8
9
10
1. arch/arm/cpu/hi3519v101/start.s,跟进各自芯片使用汇编搭建C语言环境
2. arch/arm/lib/board.c,在start_armboot下执行,初始化各种函数。
- 初始化GD,全局数据结构
- 初始化gd,全局的开发板数据结构,其中内存参数就在里面
- 初始化函数序列,init_sequence包含了board_init,env_init等。
- 初始化内存malloc。
- 初始化flash/nand/emmc等存储设备。
- 初始化stdio_init。输入输出。
- main_loop()循环操作
3. common/main.c, 执行main_loop。执行各函数。

综述只捡了重要的参数举例,更多的参数没有介绍,请详细查看源代码。但是套路一般是先搭建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
2
3
gd = (gd_t*)(_armboot_start - CONFIG_SYS_MALLOC_LEN - sizeof(gd_t));
//其中如要寻找CONFIG_SYS_MALLOC_LEN 可以通过以下命令寻找
find -path "*3519*" -iname *.h | xargs -I {} grep "CONFIG_SYS_MALLOC_LEN" {} -nH

其中,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
    根据分析,可知道缓冲区如下:
    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,是全局数据,结构体如下:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    typedef	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;
    初始化gd->bd,板子的数据指针,关于开发板的数据记录在此,如下:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    typedef 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;
    在配置过程中,内存只能配置200多M,和此处的配置有关系
    然后其中会初始化各种参数。这个步骤很重要,板子的初始化等都会在此处产生
    1
    2
    3
    4
    5
    6
    for (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
    36
    int 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
2
mem_malloc_init (_armboot_start - CONFIG_SYS_MALLOC_LEN,
CONFIG_SYS_MALLOC_LEN);

初始化存储设备

初始化flash,此处比较重要,涉及到如何对flash进行操作。3519加入了EMCC的支持。但是定义了几个配置

1
2
3
#define CONFIG_CMD_SF  //354行,nor flash操作
#define CONFIG_CMD_NAND //358行,nand flash操作
#define CONFIG_GENERIC_MMC //377行,EMMC操作

从代码中可以看出,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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
struct stdio_dev {
int flags; /* Device flags: input/output/system */
int ext; /* Supported extensions */
char name[16]; /* Device name */

/* GENERAL functions */

int (*start) (void); /* To start the device */
int (*stop) (void); /* To stop the device */

/* OUTPUT functions */

void (*putc) (const char c); /* To put a char */
void (*puts) (const char *s); /* To put a string (accelerator) */

/* INPUT functions */

int (*tstc) (void); /* To test if a char is ready... */
int (*getc) (void); /* To get that char */

/* Other functions */

void *priv; /* Private extensions */
struct list_head list;
}

common/main.c

执行main_loop进入不断循环等待用户输入参数。
其中搜索bootdelay,里面有

1
2
3
if (bootdelay >= 0 && s && !abortboot (bootdelay)) {
//
}

这个是等待用户输入字符。
综上:这就是3519v101的uboot启动方式。