【uboot】Uboot的启动流程

news2024/12/22 19:46:21

引言

在驱动岗位上,每一位新员工刚入职期间都需要理解和掌握uboot,但深入的理解代码往往需要耗费大量的时间去反复阅读。本文希望对uboot进行尽可能详细的解析,帮助其他人更快的掌握和理解uboot源码。

准备工作

uboot源码

本文是基于Hi3536_demo开发板对应的uboot源码进行分析,可以通过下面面的tag信息使用git下载Hi3536_demo的uboot源码。

project path="packages/linux_lsp/boot/u-boot-2010.06",

name="sysdev/packages/linux_lsp/boot/u-boot-2010.06",

revision="sysdev_v1.x-201703" 

代码阅读软件source insight

可以想象uboot源码包有10000多个文件,每个文件都有几百行甚至上千行代码。需要专业的代码阅读器查找函数原型,根据需求去查找阅读,完全没必要重头读到尾。

Hi3536_demo开发板和对应的数据手册

在uboot源码中常常直接对一块地址进行操作,看的人云里雾里,通过查阅数据手册可以协助我们理解那些语句的作用。

开发环境

我们还需要一个linux环境去编译uboot代码,还需要在linux下对uboot镜像进行反汇编。

注意:编译uboot镜像前需要配置交叉编译工具链。

U-Boot源码分析

uboot的配置过程

在拿到uboot代码后的第一步,我们需要做什么?执行make Hi3536_demo_config。

那为什么要执行make Hi3536_demo_config? 我们可以通过查看源代码去理解执行make Hi3536_demo_config的深层次原因。

通过顶层Makefile可以看到,在执行make Hi3536_demo_config的时候,实质上调用了如下部分:

#### u-boot-2010.06/Makefile ####

Hi3536_config: unconfig

        @$(MKCONFIG) $(@:_config=) arm hi3536 Hi3536 NULL hi3536

#### 注意:$(@:_config=) 就是将Hi3536_config中的_config替换为空!得到Hi3536; ####

#### 注意:每段代码段的第一行指明了代码存在的目录 ####

首先,确定下变量的值,这里以Hi3536_demo板为例:

#### 在顶层Makefile中会涉及到如下变量 ####

$1 = Hi3536_demo         “BOARD_NAME”

    $2 = arm          “ARCH”

    $3 = hi3536        “CPU”

    $4 = Hi3536_demo          “BOARD”

    $5 = NULL        “VENDOR”

    $6 = hi3536        “SOC”

     

#OBJTREE        = ./  //输出目录

#SRCTREE        = ./   //源码目录

#TOPDIR         = ./  //顶层目录

#LNDIR          = ./  //连接目录

    MKCONFIG= $(SRCTREE)/mkconfig = ./mkconfig 

     

    BOARD_NAME = "$1" = Hi3536_demo

    ARCH= arm 

    OBJTREE= $(if $(BUILD_DIR),$(BUILD_DIR),$(CURDIR))  = ./ 

    LNPREFIX =  

    BOARDDIR = $4 = Hi3536_demo

通过上面的代码可以推导出:@$(MKCONFIG) $(@:_config=) arm hi3536 Hi3536_demo NULL hi3536 等于 ./mkconfig Hi3536_demo arm hi3536 Hi3536_demo NULL hi3536

推导出:”make Hi3536_demo_config” 实际执行 “./mkconfig Hi3536_demo arm hi3536 Hi3536_demo NULL hi3536”

上面那这段代码具体干了什么事情呢?咱们继续向下分析。

mkconfig实际上就是顶层目录下的一个文件。那么,就来研究下顶层目录下的mkconfig文件:

#### u-boot-2010.06/mkconfig ####

    /* $#: ./mkconfig Hi3536_demo arm hi3536 Hi3536_demo NULL hi3536命令行参数的个数

     *     $0         $1   $2  $3     $4   $5   $6

     */ 

    /* 创建include目录,将相关文件软连接 */ 

    if [ "$SRCTREE" != "$OBJTREE" ] ; then 

        mkdir -p ${OBJTREE}/include 

        …

        cd ../include 

        ln -s ${SRCTREE}/arch/$2/include/asm asm 

    else 

        cd ./include 

        …

        ln -s ../arch/$2/include/asm asm 

    fi 

     

    /* 即/arch/$2/include/asm/arch-$6,为执行make Hi3536_demo_config产生的连接文件, arch/arm/include/asm/arch-hi3536 */ 

    rm -f asm/arch 

     

    /* -z STRING: 判断字符串STRING是否为0,若STRING为空字符串,则为true

     * -o: or或的意思

     */ 

    if [ -z "$6" -o "$6" = "NULL" ] ; then      /* 软连接 */

        ln -s ${LNPREFIX}arch-$3 asm/arch 

    else 

        /* arch->arch/arm/include/asm/arch-hi3536 */ 

        ln -s ${LNPREFIX}arch-$6 asm/arch 

    fi 

      

    if [ "$2" = "arm" ] ; then    /* 软连接 */

        /* proc->arch/arm/include/asm/proc-armv */ 

        rm -f asm/proc 

        ln -s ${LNPREFIX}proc-armv asm/proc 

    fi 

     

    /*   创建include/config.mk,将ARCHCPUBOARD等信息写入

     * >: 定向输出到文件,若文件不存在创建空文件

     * >>: 追加内容到指定的文件末尾

     */ 

    echo "ARCH   = $2" >  config.mk 

    echo "CPU    = $3" >> config.mk 

    echo "BOARD  = $4" >> config.mk 

    [ "$5" ] && [ "$5" != "NULL" ] && echo "VENDOR = $5" >> config.mk 

    [ "$6" ] && [ "$6" != "NULL" ] && echo "SOC    = $6" >> config.mk 

     …

    /* config.mk中的数据写入到 config.h 并添加相关信息*/

    for i in ${TARGETS} ; do 

        echo "#define CONFIG_MK_${i} 1" >>config.h ; 

    done 

    cat << EOF >> config.h 

    #define CONFIG_BOARDDIR board/$BOARDDIR 

    #include <config_defaults.h> 

    #include <configs/$1.h> 

    #include <asm/config.h> 

    EOF 

    exit 0 

./include/config.h文件内容:

#### u-boot-2010.06/include/config.h ####

    /* Automatically generated - do not edit */   

    #define CONFIG_BOARDDIR board/Hi3536_demo  

    #include <config_defaults.h>   

    #include <configs/Hi3536_demo.h>   

    #include <asm/config.h> 

./include/config.mk文件内容:

#### u-boot-2010.06/include/config.mk ####

    ARCH    = arm 

    CPU     = hi3536

    BOARD   = Hi3536_demo

    VENDOR  =

    SOC     = Hi3536_demo

综上,总结下mkconfig文件(或者叫make Hi3536_demo_config)的作用:

  1. 确定ARCHCPUBOARD等变量的值,并存到./include/config.mk文件中
  2. 建立板级相关的 ./include/config.h文件
  3. 建立指向其他文件的软链接

uboot的编译与链接过程

说完配置我们再回到Makefile中来看看编译与链接,面对Makefile的时候首先我们就会想到最后的目标文件u-boot.bin是怎样产生的:

#### u-boot-2010.06/Makefile ####

/*  CROSS_COMPILE =                             #指定编译器种类   */

/*  OBJCOPY        = $(CROSS_COMPILE)objcopy   #转换目标文件格式  */

/*  OBJCFLAGS     += --gap-fill=0xff                #段之间的空隙用0xff填充  */

$(obj)u-boot.bin: $(obj)u-boot

$(OBJCOPY) ${OBJCFLAGS} -O binary $< $@

#### 注意:/* */这些是注释 ####

从上段代码可以看到u-boot.bin 是用$(OBJCOPY) 从u-boot生成的,u-boot是elf格式的文件,不能直接在裸机上运行,所以需要用$(OBJCOPY) 把u-boot转换成二进制u-boot.bin文件。

#### u-boot-2010.06/Makefile ####

$(obj)u-boot:      ddr_training depend $(SUBDIRS) $(OBJS) $(LIBBOARD) $(LIBS) $(LDSCRIPT) $(obj)u-boot.lds

                 $(GEN_UBOOT)

ifeq ($(CONFIG_KALLSYMS),y)

                 smap=`$(call SYSTEM_MAP,u-boot) | \

                         awk '$$2 ~ /[tTwW]/ {printf $$1 $$3 "\\\\000"}'` ; \

                 $(CC) $(CFLAGS) -DSYSTEM_MAP="\"$${smap}\"" \

                         -c common/system_map.c -o $(obj)common/system_map.o

                 $(GEN_UBOOT) $(obj)common/system_map.o

endif

通过上面代码可以分析出:u-boot的产生依赖于depend,$(SUBDIRS),$(OBJS),$(LIBBOARD),$(LIBS),$(LDSCRIPT)。这里介绍一下这几个依赖目标(其中涉及到很多变量,均在顶层config.mk中):

$(SUBDIRS):进入各个子目录中执行make

SUBDIRS  = tools \

          examples/standalone \

          examples/api

.PHONY : $(SUBDIRS)

...

$(SUBDIRS):     depend

                 $(MAKE) -C $@ all

$(OBJS):OBJS  = $(CPUDIR)/start.o, 即为'arch/arm/cpu/hi3536/start.o',而要产生start.o需要进入$(CPUDIR)进行编译。

CPUDIR=arch/$(ARCH)/cpu/$(CPU)  #### u-boot-2010.06\config.mk ####

OBJS  = $(CPUDIR)/start.o        

$(OBJS):   depend

                 $(MAKE) -C $(CPUDIR) $(if $(REMOTE_BUILD),$@,$(notdir $@))

$(LIBBOARD):这个也很好理解就是产生board/$(BOARDDIR)/lib$(BOARD).a,对Hi3536_demo来说,LIBBOARD = board /Hi3536_demo/libHi3536_demo.a

BOARD    = Hi3536_demo          #### u-boot-2010.06\include\config.mk ####

BOARDDIR = $(BOARD)    #### u-boot-2010.06\config.mk ####

LIBBOARD = board/$(BOARDDIR)/lib$(BOARD).a

LIBBOARD := $(addprefix $(obj),$(LIBBOARD))

$(LIBBOARD):  depend $(LIBS)

                 $(MAKE) -C $(dir $(subst $(obj),,$@))

$(LIBS):LIBS包括的目标非常多,都是将子目录的源码编成*.a库文件,通过执行每个目录的Makefile来实现。

LIBS  = lib/libgeneric.a

LIBS += lib/lzma/liblzma.a

$(LIBS):    depend $(SUBDIRS)

                 $(MAKE) -C $(dir $(subst $(obj),,$@))

$(LDSCRIPT):这里其实就是执行链接所需要的链接脚本,这里我需要特别强调链接脚本,链接脚本是程序链接的依据,它规定了可执行文件中的程序的输出格式是大端还是小端,程序如何来布局(第一条指令是那一条,各个依赖文件是如何组成最后的目标文件的),程序的入口是那里(只对elf文件有用)。

CURDIR       = ./

SRCTREE         := $(CURDIR)

TOPDIR            := $(SRCTREE)

LDSCRIPT := $(TOPDIR)/board/$(BOARDDIR)/u-boot.lds

$(LDSCRIPT):   depend

                $(MAKE) -C $(dir $@) $(notdir $@)

总结:u-boot的产生其实简单来说就进入各个目录下执行make,将指定目录下的.c文件编译生成.o文件,将指定目录下源码编成*.a库,最后再将这些文件按照链接脚本组合成最后的目标文件。

还有一点,通常放到板子上运行的镜像为u-boot.bin而不是u-boot,是因为u-boot虽然是一个可执行镜像,但里面包含了大量的调试信息,文件也非常的大。而u-boot.bin是将u-boot镜像通过objcopy转换为二进制,去掉了其中调试信息,代码非常紧凑,文件小很多,适合作为镜像放板子上运行。

uboot第一阶段解析

接下来正式开始uboot源码之旅,分析代码当然要从上电后执行的第一条指令开始看起咯,那第一条指令在哪呢? 还是以Hi3536_demo为例,首先我们来看一下它的链接脚本,通过它我们可以知道它整个程序的各个段是怎么存放的uboot运行的第一段代码在arch/arm/cpu/hi3536/start.S文件中)

#### u-boot-2010.06\arch\arm\cpu\hi3536\ u-boot.lds ####

OUTPUT_FORMAT("elf32-littlearm", "elf32-littlearm", "elf32-littlearm")    /*指定输出可执行文件是elf格式,*/

/* 32ARM指令,小端 */

OUTPUT_ARCH(arm)    /*指定输出可执行文件的平台为ARM*/

ENTRY(_start)          /*指定输出可执行文件的起始代码段为_start*/

SECTIONS            

{

/*指定可执行image文件的全局入口点,通常这个地址都放在ROM(flash)0x0位置。*/

/*必须使编译器知道这个地址,通常都是修改此处来完成*/

        . = 0x00000000;    /*;0x0位置开始运行*/

        . = ALIGN(4);     /*代码以4字节对齐*/

        .text  :

        {

                 __text_start = .;

                 arch/arm/cpu/hi3536/start.o  (.text)    /* 代码段的起始部分就是最开始运行代码的地方, */

                                         /* 因此uboot运行的第一条指令在arch/arm/cpu/hi3536/start.S文件 */

                 drivers/ddr/ddr_training_impl.o (.text)

                 drivers/ddr/ddr_training_ctl.o (.text)

                 drivers/ddr/ddr_training_boot.o (.text)

                 drivers/ddr/ddr_training_custom.o (.text)

                 __init_end = .;

                 ASSERT(((__init_end - __text_start) < 0x16000), "init sections too big!");

                 *(.text)      /*下面依次为各个text段函数*/

        }

        . = ALIGN(4);    /*代码以4字节对齐*/

        .rodata : { *(SORT_BY_ALIGNMENT(SORT_BY_NAME(.rodata*))) }     /*指定只读数据段*/

        . = ALIGN(4);

        .data : { *(.data) }

        . = ALIGN(4);

        .got : { *(.got) }                    /*指定got, got段是uboot自定义的一个段, 非标准段*/

        __u_boot_cmd_start = .;             /*__u_boot_cmd_start赋值为当前位置, 即起始位置*/

        .u_boot_cmd : { *(.u_boot_cmd) }     /*指定u_boot_cmd, uboot把所有的uboot命令放在该段.*/

        __u_boot_cmd_end = .;             /*__u_boot_cmd_end赋值为当前位置,即结束位置*/

        . = ALIGN(4);

        __bss_start = .;                    /*__bss_start赋值为当前位置,bss段的开始位置*/

        .bss : { *(.bss) }                   /*指定bss */

        _end = .;                         /*_end赋值为当前位置,bss段的结束位置*/

}

现在知道uboot的第一行代码在哪里运行了吗?(在arch/arm/cpu/hi3536/start.S中运行)下面我们来分析start.S汇编代码。

#### u-boot-2010.06\arch\arm\cpu\hi3536\start.S ####

.globl _start

_start: b     reset

        ldr    pc, _undefined_instruction

        ldr    pc, _software_interrupt

        ldr    pc, _prefetch_abort

        ldr    pc, _data_abort

        ldr    pc, _not_used

        ldr    pc, _irq

        ldr    pc, _fiq

在这里我们终于看到了第一条运行指令是_start:b reset,呵呵!看到这段代码的时候许多人都认为_start的值是0x00000000,为什么是这个地址呢? 因为连接脚本上指定了。真的是这样吗?我们来看看我们编译好之后,在u-boot目录下有个System.map,这里面有各个变量的值。

#### u-boot-2010.06\System.map ####

40c00000 T __text_start

40c00000 T _start

40c00020 t _undefined_instruction

40c00024 t _software_interrupt

40c00028 t _prefetch_abort

40c0002c t _data_abort

40c00030 t _not_used

40c00034 t _irq

40c00038 t _fiq

哈哈,_start的值怎么会是40c00000?这是因为在顶层的Makefile里面我们指定了它的连接地址。

#### u-boot-2010.06\Makefile ####

GEN_UBOOT = \

                 UNDEF_SYM=`$(OBJDUMP) -x $(LIBBOARD) $(LIBS) | \

                 sed  -n -e 's/.*\($(SYM_PREFIX)__u_boot_cmd_.*\)/-u\1/p'|sort|uniq`;\

                 cd $(LNDIR) && $(LD) $(LDFLAGS) $$UNDEF_SYM $(__OBJS) \

                         --start-group $(__LIBS) --end-group $(PLATFORM_LIBS) \

                         -Map u-boot.map -o u-boot

$(obj)u-boot:      ddr_training depend $(SUBDIRS) $(OBJS) $(LIBBOARD) $(LIBS) $(LDSCRIPT) $(obj)u-boot.lds

                 $(GEN_UBOOT)

看到那个LDFLAGS变量了吗?它是什么呢,我们继续往下面看:

#### u-boot-2010.06\config.mk ####

LDFLAGS += -Bstatic -T $(obj)u-boot.lds $(PLATFORM_LDFLAGS)

ifneq ($(TEXT_BASE),)

LDFLAGS += -Ttext $(TEXT_BASE)    /* 如果有TEXT_BASE变量,那LDFLAGS重新赋值 */

endif

看到了没有,LDFLAGS先等于链接脚本中的地址,再判断TEXT_BASE是否等于空,如果TEXT_BASE不为空,LDFLAGS会被重新赋值。TEXT_BASE的值是多少呢?我们可以在u-boot-2010.06\board\Hi3536_demo\config.mk里面找到定义,它的值为0x40c00000。这样我就可以知道为什么System.map的起始地址0x40c00000。

#### u-boot-2010.06\board\Hi3536_demo\config.mk ####

TEXT_BASE = 0x40c00000

下面我们继续来看第一条汇编指令b reset,初始化相关硬件操作。

Reset处代码有点不按照常理出牌,和网上通用的汇编起始代码有点不一样,它先判断部分寄存器中的值,再跳转到不同标志处运行。其中,“after_ziju”标志处代码执行初始化PLL/DDRC/pin mux/…等命令;

#### u-boot-2010.06\arch\arm\cpu\hi3536\start.S ####

reset:                                 /* uboot刚进来就进行的初始化操作 */

        beq   after_ziju                   /* REG_SC_GEN2寄存器值 == 魔数,跳转到after_ziju标志处运行 */

        bne   normal_start_flow           /* REG_SC_GEN20寄存器值 =魔数,跳转到 normal_start_flow 标志处运行 */

after_ziju:    /* 初始化 PLL/DDRC/pin mux/... */

     …

        beq   pcie_slave_addr    /* 跳转到 pcie_slave_addr 处执行(PCIE相关初始化操作) */

        …

        b       ziju_ddr_init    /* 跳转到 ziju_ddr_init 处运行(初始化PLL/DDRC/pin mux/... */

pcie_slave_addr:    /* PCIE相关初始化操作 */

    …

ziju_ddr_init:    /*初始化PLL/DDRC/pin mux/... */

    …

        bl      init_registers  /*跳转到 init_registers函数处运行,初始化PLL/DDRC/... */

    …

        bl      start_ddr_training  /*跳转到 start_ddr_training函数处运行,DDR training */

    …

        beq   pcie_slave_hold  /* 跳转到 pcie_slave_hold标志处运行,通常代码不会跑到这里 */

        …

        mov  pc, r1  /* pc指针返回到 bootrom */

pcie_slave_hold:  /* pcie 出错保持,通常代码不对跑到这里 */

    …

        b       .                        /* bug here */

若满足“bne normal_start_flow”条件,运行“normal_start_flow”标志处的代码,这部分代码是普通uboot最开始启动时执行的命令。重要部分看注释。

这段汇编代码很好理解,就是设置CPU为管理模式、将cache置无效、关闭MMU和cache。这边抛出一个问题:

在汇编代码中,Invalidate cache、disable cache、flash cache分别表示什么含义?

Invalidate cache表示当前cache内的数据无效,所有cpu获取数据只能重新读取;flash cache表示清空cache中的数据;disable cache表示关闭cache。

#### u-boot-2010.06\arch\arm\cpu\hi3536\start.S ####

normal_start_flow:

mrs    r0, cpsr   /* set the cpu to SVC32 mode 设置管理模式 */

mov   r0, #0   /* Invalidate L1 I/D   -- 置无效 I/D cache */

mrc    p15, 0, r0, c1, c0, 0   /* disable MMU stuff and caches    --关闭MMUcache */

在normal_start_flow标志处代码执行到尾部,都没有跳转这一类指令,因此pc指针继续向下执行main_core标志处代码。

此处代码内容为找到对应的存储介质,将其中的代码拷贝到DDR中运行。

#### u-boot-2010.06\arch\arm\cpu\hi3536\start.S ####

main_core:

    …

        bne    check_bootrom_type  /*检测是否需要跳转,PC的高八位如果不为0(已经在ram中运行了)则跳转,不等于则跳转*/

#ifndef CONFIG_HI3536_A7  /* 找到对应的存储介质 */

cmp    r6, #0

    ldreq   pc, _clr_remap_spi_entry  /* SPI存储 */

    cmp    r6, #1

    ldreq   pc, _clr_remap_spi_nand_entry  /* SPI_NAND 存储 */

        cmp   r6, #2

    ldreq   pc, _clr_remap_nand_entry  /* NAND 存储 */

        cmp     r6, #3

        ldreq   pc, _clr_remap_ddr_entry  /* DDR 存储 */

        ldr     pc, _clr_remap_nand_entry  /* 所有其他情况,默认采用 NAND 存储 */

#endif

check_bootrom_type:  /* bootrom中的u-boot.bin 拷贝到RAM(0x4010c00) */

    …

    ldreq  pc, _clr_remap_ram_entry  /* 根据不同的存储介质,传不同参数 */

do_clr_remap:  /*清除remap */

#ifndef CONFIG_HI3536_A7

/*清除remap */

#endif

#ifdef CONFIG_ARCH_HI3536

/* 如果使用Hi3536板卡,那就需要使能I/D cache */

#endif

   …

/* 使能 Cache 操作 */

bne    ddr_init  /* DDR初始化 */

    …

        b          copy_to_ddr  /* u-boot.bin 拷贝到DDR */

ddr_init:  /* DDR初始化相关 */

#ifndef CONFIG_HI3536_A7

    …

        bl      init_registers  /* 初始化寄存器 */

#endif

#ifdef CONFIG_DDR_TRAINING_V2

    ….

        bl      start_ddr_training  /* DDR training */

#endif

#ifndef CONFIG_HI3536_A7

    …

        bne       copy_flash_to_ddr  /* 拷贝镜像到DDR */

#ifdef CONFIG_EMMC_SUPPORT

emmc_boot:  /* 初始化emmc,跳转到 jump_to_ddr  */

        bl      emmc_boot_read  /* 拷贝镜像 */

        b       jump_to_ddr  /*跳转到 jump_to_ddr */

#endif

copy_flash_to_ddr:  /* NAND中拷贝镜像,跳转到 copy_to_ddr */

    ..

        bne   spi_nor_copy  /* 拷贝镜像 */

    …

        b       copy_to_ddr  /* 跳转到copy_to_ddr */

spi_nor_copy:  /* SPI_NOR中拷贝镜像,跳转到 copy_to_ddr */

    …

        bne   spi_nand_copy  /* 拷贝镜像 */

    …

        b       copy_to_ddr  /* 跳转到copy_to_ddr */

spi_nand_copy:   /* SPI_NAND中拷贝镜像,跳转到 copy_to_ddr */

    …

        b       copy_to_ddr  /* 跳转到copy_to_ddr */

#endif

copy_to_ddr:  /* 将指定存储内的数据拷贝到DDR */

    …

        beq     copy_abort_code  /* 拷贝操作 */

    …

        bl      memcpy  /* 拷贝操作 */

jump_to_ddr:

    …

        ldr     pc, _copy_abort_code  /* 拷贝操作 */

copy_abort_code:

    …

        bl      memcpy  /* 拷贝操作 */

又到了熟悉的部分,如果要在C语言环境下执行代码,必须先初始化堆栈。

这段代码的意思是设置一些堆栈。

#### u-boot-2010.06\arch\arm\cpu\hi3536\start.S ####

stack_setup:  /* 设置栈指针 */

        ldr    r0, _TEXT_BASE               @ upper 128 KiB: relocated uboot   

        sub   r0, r0, #CONFIG_SYS_MALLOC_LEN @ malloc area           

        sub   r0, r0, #CONFIG_SYS_GBL_DATA_SIZE @ bdinfo             

        sub   sp, r0, #12          @ leave 3 words for abort-stack       

        and   sp, sp, #~7          @ 8 byte alinged for (ldr/str)d       

只要将sp指针指向一段没有被使用的内存就完成栈的设置了。根据上面的代码可以知道U-Boot内存使用情况了,如下图所示:

这段代码的意思是清bss段。

#### u-boot-2010.06\arch\arm\cpu\hi3536\start.S ####

clear_bss:  /* 清除bss */

        ldr    r0, _bss_start  /* r0 = bss段的起始位置 */

        ldr    r1, _bss_end               @ stop here                        /* r1 = bss段结束位置 */

        mov  r2, #0x0             @ clear value                           /* r2 = 0 */

clbss_l:

        str     r2, [r0]               @ clear BSS location                    /* 先将r2,0x0,存到地址为r0的内存中去 */

        cmp  r0, r1                 @ are we at the end yet                  /* 比较r0地址和r1地址,即比较当前地址是否到了bss段的结束位置 */

        add   r0, r0, #4            @ increment clear index pointer           /* 然后r0地址加上4 */

        bne   clbss_l                        @ keep clearing till at end           /* 如果不等于,那么就跳到clbss_l,即接着这几个步骤,直到地址超过了bss_end位置,即实现了将整个bss段,都清零。*/

这个时候,pc指针开始跳到RAM里面执行代码,这也就到了第二阶段(C语言阶段),后面的代码都是用C语言写的。

#### u-boot-2010.06\arch\arm\cpu\hi3536\start.S ####

        ldr    pc, _start_armboot                 /* start_armboot,赋值给PC,即调用start_armboot函数 */

_start_armboot: .word start_armboot        /* start_armboot函数,在C文件中,即跳转执行c代码 */

总结:汇编第一阶段的代码主要可以分为以下部分:

  1. 设置异常向量表
  2. 设置特权管理模式
  3. 初始化PLLDDRMUX…
  4. MMU,关CACHE
  5. 判断代码在RAM还是FLASH,将FLASH代码复制至RAM
  6. 设置堆栈、清空bss
  7. 跳转至C语言处,进入第二阶段

uboot第二阶段解析

在uboot第一阶段启动完成后将会调用start_armboot开始第二阶段的启动流程,这个阶段的代码由c语言编写,代码位于u-boot-2010.06\arch\arm\lib\board.c。

基础数据结构

第二阶段主要用到了两个数据结构即 gd_t 和 bd_t,其定义如下:

这两个类型变量记录了刚启动时的信息,还将记录作为引导内核和文件系统的参数,如 bootargs 等,并且将来还会在启动内核时,由 uboot 交由 kernel 时会有所用。

#### u-boot-2010.06\arch\arm\include\asm\global_data.h ####

/*  U-Boot使用了一个存储在寄存器中的指针gd来记录全局数据区的地址,这个指针存放在指定的寄存器r8 */

typedef      struct global_data {  /* 全局数据结构 */ 

        bd_t          *bd;  /* 指向板级信息结构 */ 

        unsigned long     flags;   /* 标记位 */ 

        unsigned long     baudrate;  /* 串口波特率 */ 

        unsigned long     have_console;  /* serial_init() was called */

        unsigned long     env_addr;  /* 环境参数地址 */

        unsigned long     env_valid;  /* 环境参数 CRC 校验有效标志 */

        unsigned long     fb_base;  /* fb 起始地址 */

#ifdef CONFIG_VFD

        unsigned char     vfd_type;  /* 显示器类型(VFD代指真空荧光屏) */

#endif

#ifdef CONFIG_FSL_ESDHC  /* 宏未定义 */

        unsigned long     sdhc_clk; 

#endif

#if 0  /* 未定义 */

        unsigned long     cpu_clk;  /* cpu 频率*/

        unsigned long     bus_clk;  /* bus 频率 */ 

        phys_size_t        ram_size;  /* ram 大小 */

        unsigned long     reset_status;       /* reset status register at boot */

#endif

        void          **jt;  /* 跳转函数表 */

} gd_t;

typedef struct bd_info {  /* 板级信息结构 */

    int                       bi_baudrate;  /* 波特率 */

    unsigned long      bi_ip_addr;  /* IP地址 */

    struct environment_s            *bi_env;  /* 板子的环境变量 */ 

    ulong         bi_arch_number;  /* 板子的 id */

    ulong         bi_boot_params;  /* 板子的启动参数 */

    struct    /* RAM 配置 */

    {

        ulong start;

        ulong size;

    }                         bi_dram[CONFIG_NR_DRAM_BANKS];

} bd_t;

启动流程

start_armboot 首先为全局数据结构和板级信息结构分配内存,代码如下:

可以看到 bd_t 、gd_t 以及 MALLOC 区域是紧挨着的。

#### u-boot-2010.06\arch\arm\lib\board.c ####

        gd = (gd_t*)(_armboot_start - CONFIG_SYS_MALLOC_LEN - sizeof(gd_t));

        /* compiler optimization barrier needed for GCC >= 3.4 */

        __asm__ __volatile__("": : :"memory");  /* 内存屏障,防止编译器优化 */

        memset ((void*)gd, 0, sizeof (gd_t));  /* 将指定的内存地址清零( 将全局数据清零 ) */

        gd->bd = (bd_t*)((char*)gd - sizeof(bd_t));  /* gd->bd指向一块地址( 取得板级信息数据结构的起始地址 )  */

        memset (gd->bd, 0, sizeof (bd_t));  /* gd->db指向地址中的内容清零( 将板级信息清零 ) */

        gd->flags |= GD_FLG_RELOC;  /* 标记为代码已经转移到 RAM */

然后依次调用 init_sequence数组中的函数指针完成各部分的初始化,代码如下:

#### u-boot-2010.06\arch\arm\lib\board.c ####

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,  /* 初始化定时器 */

#ifdef CONFIG_FSL_ESDHC

        get_clocks,

#endif

        env_init,  /* 初始化环境变量(默认的环境变量) -- initialize environment */

        init_baudrate,  /* 初始化波特率设置 -- initialze baudrate settings */

        serial_init,  /* 初始化串口 */

        console_init_f,  /* 控制台初始化 -- stage 1 init of console */

        display_banner,  /* 打印uboot版本信息 -- say that we are here */

#if defined(CONFIG_DISPLAY_CPUINFO)

        print_cpuinfo,  /* 显示cpu信息 -- 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,  /* 初始化IIChard:真正iicsoft:gpio模拟iic */

#endif

        dram_init,  /* 配置可用RAM -- configure available RAM banks */

#if defined(CONFIG_CMD_PCI) || defined (CONFIG_PCI)

        arm_pci_init,  /* 初始化pci */

#endif

        NULL,

};

/* 函数指针,执行指针数组中的内容(实际内容为函数指针),初始化cpu、总线、设备等等*/

for (init_fnc_ptr = init_sequence; *init_fnc_ptr; ++init_fnc_ptr) {

if ((*init_fnc_ptr)() != 0) {

hang ();

}

}

void hang (void) {

        puts ("### ERROR ### Please RESET the board ###\n");

        for (;;);

}

在我们平台比较重要的初始化函数有 board_init 以及 env_init,代码如下:

#### u-boot-2010.06\board\Hi3536_demo\board.c ####

int board_init(void)

{

        unsigned long reg;

        /* set uart clk from apb bus */

        reg = readl(CRG_REG_BASE + PERI_CRG57);  /* 设置串口时钟 */

        reg &= ~UART_CKSEL_APB;

        writel(reg, CRG_REG_BASE + PERI_CRG57);

        DECLARE_GLOBAL_DATA_PTR;

        gd->bd->bi_arch_number = MACH_TYPE_HI3536;

        gd->bd->bi_boot_params = CFG_BOOT_PARAMS;

        gd->flags = 0;

        boot_flag_init();

        add_board_partition(&pri_board_part, FLASH_TYPE_EMMC);

        return 0;

}

#### u-boot-2010.06\common\env_common_func.c ####

/* 初始化环境变量 */ 

int env_init(void)

{

#ifdef CONFIG_HI3536_A7

        env_cmn_func = &nw_env_cmn_func;

#else

        switch (get_boot_media()) {

        default:

                 env_cmn_func = NULL;

                 break;

        case BOOT_MEDIA_NAND:

                 env_cmn_func = &nand_env_cmn_func;

                 break;

        case BOOT_MEDIA_SPIFLASH:

                 env_cmn_func = &sf_env_cmn_func;

                 break;

        case BOOT_MEDIA_EMMC:

                 env_cmn_func = &emmc_env_cmn_func;

                 break;

        case BOOT_MEDIA_DDR:

                 env_cmn_func = &nw_env_cmn_func;

                 break;

        }

#endif

        if (env_cmn_func && !env_cmn_func->env_name_spec)

                 env_cmn_func = NULL;

        /* unknow start media */

        if (!env_cmn_func)

                 return -1;

        env_cmn_func->env_init();

        env_name_spec = env_cmn_func->env_name_spec;

        return 0;

}

在环境变量 default_environment 中我们设置了很多参数,列表如下:

我们可以在 uboot 命令行模式下输入 printenv 命令查看当前的环境变量值。

#### u-boot-2010.06\tools\env\fw_env.c ####

static char default_environment[] = {

#if defined(CONFIG_BOOTARGS)

        "bootargs=" CONFIG_BOOTARGS "\0"

#endif

#if defined(CONFIG_BOOTCOMMAND)

        "bootcmd=" CONFIG_BOOTCOMMAND "\0"

#endif

    …

};

start_armboot 在接下来的流程中还做了如下操作:

#### u-boot-2010.06\arch\arm\lib\board.c ####

void start_armboot (void)

{

    …

        nand_init();  /* 初始化 NAND */ 

    …

        mmc_initialize(0);  /* 初始化MMC */

        mmc_flash_init(0);

        env_relocate ()  /* 重定位环境变量,将其从 NAND 拷贝到内存中 */ 

    …

        gd->bd->bi_ip_addr = getenv_IPaddr ("ipaddr");  /* 设置IP地址 */

        stdio_init ();  /* 初始化外设 */ 

        jumptable_init ();  /* 初始化跳转函数表 */

    …

        console_init_r ();  /* 控制台初始化第二阶段 */

    …

        misc_init_r ();  /* 杂项设备初始化, eg:battery */ 

    …

        enable_interrupts ();  /* 使能中断 */

#ifdef CONFIG_KEDACOM_E2PROM

        extern int kd_set_ethaddr();

        kd_set_ethaddr();

#endif

    …

        /* 如果存在则从环境变量中读取装载地址,其默认为 ulong load_addr = CONFIG_SYS_LOAD_ADDR; */

        if ((s = getenv ("loadaddr")) != NULL) {

                 load_addr = simple_strtoul (s, NULL, 16);

        }

#if defined(CONFIG_CMD_NET)

        if ((s = getenv ("bootfile")) != NULL) {

                 copy_filename (BootFile, s, sizeof (BootFile));

        }

#endif

    …

#if defined(CONFIG_CMD_NET)

    …

        eth_initialize(gd->bd);  /* 网络初始化 */

    …

#endif

#if defined(CONFIG_BOOTROM_SUPPORT)

        extern void download_boot(const int (*handle)(void));

        download_boot(NULL);

#endif

        product_control();

    …

#ifdef CONFIG_PARTTAB_ON_FLASH

        partition_check_update_flags();

#endif

        /* main_loop() can return to retry autoboot, if so just run it again. */

        for (;;) {

                 main_loop ();  /* 进入主循环 common/main.c */

        }

}

start_armboot 最终进入 main_loop 函数,首先判断用户选择的启动模式,如果是命令模式则等待输入命令然后执行,代码如下:

#### u-boot-2010.06\arch\arm\lib\board.c ####

void main_loop (void)

{

    …

setenv ("ver", version_string);  /* 设置版本信息 */

    …

        update_tftp ();

    ….

#if defined(CONFIG_BOOTDELAY) && (CONFIG_BOOTDELAY >= 0)

        s = getenv ("bootdelay");    /* 获取bootdelay环境变量的值 */

        bootdelay = s ? (int)simple_strtol(s, NULL, 10) : CONFIG_BOOTDELAY;    /* 将字符串转换为long类型变量 */

        debug ("### main_loop entered: bootdelay=%d\n\n", bootdelay);

        debug ("### main_loop: bootcmd=\"%s\"\n", s ? s : "<UNDEFINED>");

    /* 倒数读秒,如果delay时间内没有操作,执行run_command命令 */

        if (bootdelay >= 0 && s && !abortboot (bootdelay)) {

                 run_command (s, 0);

        }

#endif        /* CONFIG_BOOTDELAY */

    …

        for (;;) {

        …

                 len = readline (CONFIG_SYS_PROMPT); /* 读取输入 */

                 flag = 0;     /* assume no special flags for now */

                 if (len > 0)

                         strcpy (lastcommand, console_buffer);  /* 将输入保存到历史记录中 */

                 else if (len == 0)

                         flag |= CMD_FLAG_REPEAT;  /* 如果没有输入则重复上次 */ 

        …

                 if (len == -1)

                         puts ("<INTERRUPT>\n");

                 else

                         rc = run_command(lastcommand, flag);  /* 执行命令 */

                         lastcommand[0] = 0;  /* 将命令置无效命令令其不可重复 */

        }

}

总结,C语言第二阶段代码可以分为以下部分:

  1. gdbd数据结构分配地址,并清零
  2. 执行 init_fnc_ptr 函数指针数组中的各个初始化函数

板级特殊设备初始化(board_init)、时钟初始化(timer_init)、初始化环境变量(env_init)、串口控制台初始化(init_baudrateconsole_init_f)、打印U-Boot信息(display_bannerprint_cpuinfocheckboard)、配置可用RAM大小(dram_init

  1. gd , bd 数据结构赋值初始化
  2. 各种设备初始化
  3. NAND Flash初始化(nand_init)、MMC初始化(mmc_initializemmc_flash_init)、网络初始化(eth_initialize)、初始化串口(serial_initconsole_init_r)、初始化其他外设(stdio_init)、杂项设备初始化(misc_init_r
  4. 环境变量代码重定位(env_relocate
  5. 使能中断(enable_interrupts
  6. 进入主循环(main_loop

总结

u-boot的配置过程,可以简单描述为:

  1. 创建到目标板相关文件的链接
  2. 创建include/config.mk文件,内容如下:
  3. 创建与目标板相关的头文件include/config.h
  4. 后续执行编译的时候,到哪些路径下面找文件都是在配置时确定的。

        uboot的编译和链接过程,可以简单描述为:

  1. 将所有需要的.c文件编译生成.o文件,将需要的部分文件编成.a库,最后再将这些文件按照链接脚本组合成最后的目标文件。

第一阶段代码,可以简单描述为:

  1. 初始化本阶段要使用到的硬件设备
  2. 为加载Bootloader的第二阶段代码准备RAM空间
  3. 复制Bootloader的第二阶段到RAM空间
  4. 设置好堆栈
  5. 跳转到第二阶段的C入口点

第二阶段代码,可以简单描述为:

  1. 初始化本阶段要使用到的硬件设备
  2. 配置系统内存映射,
  3. 将内核映像和根文件系统映像从Flash上读到RAM空间中
  4. 为内核设置启动参数

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1098436.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Redux详解(二)

1. 认识Redux Toolkit Redux Toolkit 是官方推荐的编写 Redux 逻辑的方法。 通过传统的redux编写逻辑方式&#xff0c;会造成文件分离过多&#xff0c;逻辑抽离过于繁琐&#xff08;具体可看上篇文章 Redux详解一&#xff09;&#xff0c;React官方为解决这一问题&#xff0c;推…

【设计模式】单例模式、“多例模式”的实现以及对单例的一些思考

文章目录 1.概述2.单例模式实现代码2.1.饿汉式单例2.2.懒汉式单例2.3.双检锁单例2.4.静态内部类单例2.5.枚举单例 3.对单例的一些思考3.1.是否需要严格的禁止单例被破坏&#xff1f;3.2.懒汉式真的比饿汉式更佳吗&#xff1f;3.3.单例存在的问题 4.其他作用范围的单例模式4.1.线…

空调原理与结构、制冷剂类型及相关先进技术

一、空调相关知识 1. 空调定义 空调是指利用各种技术和设备对某一空间内空气的温度、湿度、洁净度和流速进行调节&#xff0c;以满足人们对舒适性要求或不同工艺环境要求。 2. 基本原理 蒸发吸热、冷凝放热 压力越低沸点越低 3. 空调主要结构 空调主要由压缩机、冷凝器、…

求臻医学:MRD的十八般武艺 AI双驱动流派

作为专注于肿瘤精准诊疗领域的国家高科技企业&#xff0c;求臻医学依托《中国肿瘤基因图谱计划》和《肿瘤精准医学大数据平台》项目&#xff0c;围绕肿瘤诊断监测、预后评估、肿瘤早筛、遗传筛查、药物研发服务等场景&#xff0c;开发了针对肺癌、结直肠癌、胃癌、前列腺癌等实…

【Qt高阶】Qt D-Bus 简介【2023.10.16】

Qt D-Bus介绍 简介总线技术名词消息&#xff08;阐述总线的消息内涵&#xff09;服务名对象路径接口备忘表(便于记住名字的格式)调试 麒麟V10 与D-Bus 简介 D-Bus 是一个进程间通信(IPC)和远程过程调用(RPC)机制,最初是为了 Linux 开发,用来取代现有的竞争的 IPC 解决方案,提供…

交通部 EDI是什么?如何处理?

交通部于1996年开始实施《国际集装箱运输电子信息传输和运作系统及示范工程》&#xff0c;即在中国远洋运输集团、上海口岸、宁波口岸、天津口岸和青岛口岸建立 EDI 示范工程。 交通部 EDI 的数据结构 电子口岸或者其他物流企业需要确保能够生成和解析符合交通部要求的EDI数据…

两个pdf合并成一个pdf?

两个pdf合并成一个pdf&#xff1f;pdf合并是我们在处理PDF中非常常见的一个操作。我们看似有很多方法能够实现这一操作&#xff0c;但是真正适合自己的方法确实能够帮助我们很多。那么多方法的话&#xff0c;小编今天打算汇总几个比较适合新手的快速方法&#xff0c;这样效率更…

建立线上线下一体化营销体系,数字化营销系统必不可少

​在当今的市场环境中&#xff0c;实体行业想要取得持续的收入增长&#xff0c;必须将线上线下业务相结合&#xff0c;充分利用数字化营销系统的功能&#xff0c;以构建“全链路式”数字化营销体系。 而数字化营销系统中&#xff0c;常见的如分销系统、拼团系统、分红系统、积分…

mission planner通过串口连接3DR数传,远程飞控

前提 pixhaw2.4.8已布线&#xff0c;有单独的电源供电&#xff0c;通过电量计接power接口 电量计的输入端接24V电源&#xff0c;飞控的输入是5v电源&#xff0c;电量计上有个模块可以分压将5v的电输入到飞控 数传接在接口telem 2上&#xff08;一个接飞控&#xff0c;一个接电…

用浏览器进行web应用测试,你会怎么做?

有没有遇到这样的一个场景&#xff1a;你在使用浏览器进行web应用测试&#xff0c;但是你想知道你在测试过程中的前端输出和后端响应的情况究竟如何。那么&#xff0c;你会怎么做呢&#xff1f;想必大多人会毫不犹豫地回答&#xff1a;通过浏览器console面板和network面板抓取信…

idea使用debug无法启动,使用run可以启动

1、将调试断点清除 使用快捷键ctrl shift F8&#xff0c;将勾选的选项去除即可 2、Error running SampleApplication: Command line is too long. Shorten command line for SampleApplication or also for Spring Boot default configuration&#xff0c;报这种错误&#x…

信号完整性分析基础知识之有损传输线、上升时间衰减和材料特性(五):有损传输线的特性阻抗和信号传输速度

有损传输线的特性阻抗 理想有损传输线特性阻抗是和频率相关的&#xff0c;很复杂。可以有以下公式&#xff1a; 按照代数知识&#xff0c;特性阻抗的实部和虚部如下&#xff1a; 其中RL表示单位长度导体的串联电阻 CL表示单位长度电容 LL单位长度串联环路电感 GL电介质单位长度…

番茄小说推文怎么申请授权?

以下为申请步骤 1.使用“巨量推文” 2.找到番茄小说这个小说app 3.按照要求申请关键词 完成以上步骤即可申请番茄小说推文关键词授权

前端新特性:Compute Pressure API!!!

前几天&#xff0c;review 同事代码的时候发现了一个新的 JS API PressureObserver。 通过一番搜索&#xff0c;发现这个 API 是 Compute Pressure API 的一部分。 Compute Pressure API&#xff1a;https://www.w3.org/TR/compute-pressure/ 它的作用是可以观察 CPU 的变…

yolov8如何进行训练验证推理

1、新建脚本main.py&#xff0c;也可以建一个yaml文件&#xff08;避免改到default.yaml&#xff09;&#xff0c;这个yaml文件是在训练时用到 batchsize什么的都可以在yaml文件改&#xff0c;这俩东西不用填 2、两种训练的方法&#xff0c;用的时候可以注释掉其他 from u…

【无标题】三分钟快速实现MQTT网关远程连接三菱系列PLC

MQTT协议网关串口连接三菱FX3UPLC操作说明v1.2 目录 一. 使用流程 二. 准备工作 2.1 需要准备如下物品 2.2 LF220网关准备工作 2.3 PLC准备工作 2.4 电脑的准备工作 2.5 MQTT服务器准备工作 三. 腾讯云平台配置步骤 3.1 创建产品 3.2 添加设备 3.3 获取…

Python 中的变量Variable

六、Python 中的变量 1、变量的创建和赋值 在 Python 程序中,变量是用一个变量名表示,可以是任意数据类型,变量名必须是大小写英文、数字和下划线(_)的组合,且不能用数字开头,比如: a=88这里的 a 就是一个变量,代表一个整数,注意一点是 Python 是不用声明数据类型…

h5端自动滑动轮播效果实现

一、客户需要的效果图 二、具体代码实现如下&#xff1a; dom:<div class"swiper-container"> <div class"swiper-wrapper ul" click"setInputText"> <div class"swiper-slide li" v-for"(item, index) in answe…

如何使用内网穿透实现U8用友ERP本地部署并远程访问办公?

文章目录 前言1. 服务器本机安装U8并调试设置2. 用友U8借助cpolar实现企业远程办公2.1 在被控端电脑上&#xff0c;点击开始菜单栏&#xff0c;打开设置——系统2.2 找到远程桌面2.3 启用远程桌面 3. 安装cpolar内网穿透3.1 注册cpolar账号3.2 下载cpolar客户端 4. 获取远程桌面…

HarmonyOS/OpenHarmony原生应用开发-华为Serverless服务支持情况(四)

文档中的TS作者认为就是ArkTS之意。 一、云存储 AppGallery Connect&#xff08;简称AGC&#xff09;云存储是一种可伸缩、免维护的云端存储服务&#xff0c;可用于存储图片、音频、视频或其他由用户生成的内容。借助云存储服务&#xff0c;您可以无需关心存储服务器的开发、…