跳到主要内容

I.MX6U学习笔记系统移植篇

WTF is Uboot

一个BootLoader,支持多种架构,类似于裸机编程,启动linux的

一般有zimage+dtb(设备树)

Uboot获取

  1. 官网
  2. SOC厂商比如nxp。st,他们有自己的开发板
  3. 做开发板的厂商,理智派

Uboot编译

记得设置好ARCH和CORSS_COMPILE

建议在Makefile里就设置好

Uboot Makefile讲解

编译后的文件

image-20230802151935178image-20230802152009807

  1. arm文件夹->cpu架构有关

    ​ ->board->freescale-> mx6ullevk 这个文件夹来定义我们的板子

  2. config文件夹 :mx6ull_14x14_ddr512_emmc_defconfig等等这些默认配置

  3. .u-boot.xxx.cmd文件

    这玩意有点玄机啊,基本上都是存命令的。比如说.u-boot.bin.cmd (bin)

    cmd_u-boot.bin := cp u-boot-nodtb.bin u-boot.bin

    可以看到cp u-boot-nodtb.bin过来并改名对吧

    那u-boot-nodtb.bin怎么来的?可以看他的_cmd文件.u-boot-nodtb.bin.cmd

    cmd_u-boot-nodtb.bin := arm-linux-gnueabihf-objcopy --gap-fill=0xff  -j .text -j .secure_text -j .rodata -j .hash -j .data -j .got -j .got.plt -j .u_boot_list -j .rel.dyn -O binary  u-boot u-boot-nodtb.bin

    使用 objcopy 将 ELF 格式的 u-boot 文件转换为二进制的 u-boot-nodtb.bin 文件

    之后就是.u-boot.cmd

    cmd_u-boot := arm-linux-gnueabihf-ld.bfd   -pie  --gc-sections -Bstatic -Ttext 0x87800000 -o u-boot -T u-boot.lds arch/arm/cpu/armv7/start.o --start-group  arch/arm/cpu/built-in.o  arch/arm/cpu/armv7/built-in.o  arch/arm/imx-common/built-in.o  arch/arm/lib/built-in.o  board/freescale/common/built-in.o  board/freescale/mx6ullevk/built-in.o  cmd/built-in.o  common/built-in.o  disk/built-in.o  drivers/built-in.o  drivers/dma/built-in.o  drivers/gpio/built-in.o  drivers/i2c/built-in.o  drivers/mmc/built-in.o  drivers/mtd/built-in.o  drivers/mtd/onenand/built-in.o  drivers/mtd/spi/built-in.o  drivers/net/built-in.o  drivers/net/phy/built-in.o  drivers/pci/built-in.o  drivers/power/built-in.o  drivers/power/battery/built-in.o  drivers/power/fuel_gauge/built-in.o  drivers/power/mfd/built-in.o  drivers/power/pmic/built-in.o  drivers/power/regulator/built-in.o  drivers/serial/built-in.o  drivers/spi/built-in.o  drivers/usb/dwc3/built-in.o  drivers/usb/emul/built-in.o  drivers/usb/eth/built-in.o  drivers/usb/gadget/built-in.o  drivers/usb/gadget/udc/built-in.o  drivers/usb/host/built-in.o  drivers/usb/musb-new/built-in.o  drivers/usb/musb/built-in.o  drivers/usb/phy/built-in.o  drivers/usb/ulpi/built-in.o  fs/built-in.o  lib/built-in.o  net/built-in.o  test/built-in.o  test/dm/built-in.o --end-group arch/arm/lib/eabi_compat.o  -L /usr/local/arm/gcc-linaro-7.5.0-2019.12-x86_64_arm-linux-gnueabihf/bin/../lib/gcc/arm-linux-gnueabihf/7.5.0 -lgcc -Map u-boot.map

    .u-boot.cmd 使用到了 arm-linux-gnueabihf-ld.bfd,也就是链接工具,使用 ld.bfd 将各个 built-in.o 文件链接在一起就形成了 u-boot 文件。uboot 在编译的时候会将同一个目录中的所有.c 文件都编译在一起,并命名为 built-in.o,相当于将众多的.c 文件对应的.o 文件集合在一起,这个就是 u-boot 文件的来源。

    然后需要转成Imx

    cmd_u-boot.imx := ./tools/mkimage -n
    board/freescale/mx6ull_alientek_emmc/imximage.cfg.cfgtmp -T imximage -
    e 0x87800000 -d u-boot.bin u-boot.imx

    用/tools/mkimage将保存在board/freescale/mx6ullevk/imximage-ddr512.cfg.cfgtmp的 IVT 、 DCD 等数据加到bin的头部合成

  4. Makefile文件

    顶层调用子模块链接

  5. u-boot.xxx文件

    u-boot:编译出来的 ELF 格式的 uboot 镜像文件。

    u-boot.bin:编译出来的二进制格式的 uboot 可执行镜像文件。

    u-boot.cfg:uboot 的另外一种配置文件。

    u-boot.imx:u-boot.bin 添加头部信息以后的文件,NXP 的 CPU 专用文件。

    u-boot.lds:链接脚本。

    u-boot.map:uboot 映射文件,通过查看此文件可以知道某个函数被链接到了哪个地址上。

    u-boot.srec:S-Record 格式的镜像文件。

    u-boot.sym:uboot 符号文件。

    u-boot-nodtb.bin:和 u-boot.bin 一样,u-boot.bin 就是 u-boot-nodtb.bin 的复制文件。

  6. config文件

    uboot 配置文件,使用命令“make xxx_defconfig”配置 uboot 以后就会自动生成

    image-20230806104057336

U-Boot顶层Makefile分析

First of all , is the version number .

VERSION = 2016
PATCHLEVEL = 03
SUBLEVEL =
EXTRAVERSION =
NAME =
  1. 主版本号
  2. 补丁版本号
  3. 次版本号
  4. 附加版本信息
  5. 名字有关
MAKEFLAGS变量

make可以递归make子目录然后链接,比如subdir这个子目录,可以通过下面的命令编译子目录

$(MAKE) -C subdir

$(MAKE)即调用make命令, -C指定子目录

提示

有时候需要向子make传递变量,需要用export来导出,不需要就unexport

但是有两个特殊的变量:SHELL MAKEFLAGS,这两个变量除非使用“unexport”声明,否则的话在整个 make 的执行过程中,它们的值始终自动的传递给子 make。在 uboot 的主 Makefile中有如下代码:

MAKEFLAGS += -rR --include-dir=$(CURDIR)

-rR表示禁止使用内置的隐含规则和变量定义,--include-dir指明搜索路径,$(CURDIR)表示当前目录。

命令输出

编译过程一般都是短命令,需要加V=1来输出完整的命令输出,方便调试

控制输出的代码:

ifeq ("$(origin V)", "command line")
KBUILD_VERBOSE = $(V)
endif
ifndef KBUILD_VERBOSE
KBUILD_VERBOSE = 0
endif

ifeq ($(KBUILD_VERBOSE),1)
quiet =
Q =
else
quiet=quiet_
Q = @
endif
  1. ifeq判断$(origin V)和command line时不时一样

    origin是Makefile的函数,用于告诉变量的来源,相当于判断这个V是不是来源于命令行

  2. V=1相当于KBUILD_VERBOSE=1

Makefile 中会用到变量 quiet 和 Q 来控制编译的时候是否在终端输出完整的命令,在顶层Makefile 中有很多如下所示的命令:

$(Q)$(MAKE) $(build)=tools

按照上面来展开,不输出就是@make ...=tools,输出就是make...=tools

静默输出

使用make -s静默输出

# If the user is running make -s (silent mode), suppress echoing of
# commands
ifneq ($(filter 4.%,$(MAKE_VERSION)),) # make-4
ifneq ($(filter %s ,$(firstword x$(MAKEFLAGS))),)
quiet=silent_
endif
else # make-3.8x
ifneq ($(filter s% -s%,$(MAKEFLAGS)),)
quiet=silent_
endif
endif

export quiet Q KBUILD_VERBOSE

用到了filter <parttern...> , <text> 过滤单词,查询版本对不对

获取了MAKEFLAGS的第一个单词,加入-s的编译选项后,firstword=xrRs

展开就是(filter(filter %s, xrRs),而(filter %s, xrRs)的返回值肯定不为空,条件成立,quiet=silent_

设置编译结果输出目录

kbuild supports saving output files in a separate directory.

To locate output files in a separate directory two syntaxes are supporte

In both cases the working directory must be the root of the kernel src.

  1. O=

    Use "make O=dir/to/store/output/files/"

  2. Set KBUILD_OUTPUT Set the environment variable KBUILD_OUTPUT to point to the directory where the output files shall be placed. export KBUILD_OUTPUT=dir/to/store/output/files/ make The O= assignment takes precedence over the KBUILD_OUTPUT environment variable.

使用O=out来指定输出目录到out

代码检查

uboot 支持代码检查,使用命令“make C=1”使能代码检查,检查那些需要重新编译的文件。“make C=2”用于检查所有的源码文件,顶层 Makefile 中的代码如下:

# Call a source code checker (by default, "sparse") as part of the
# C compilation.
#
# Use 'make C=1' to enable checking of only re-compiled files.
# Use 'make C=2' to enable checking of *all* source files, regardless
# of whether they are re-compiled or not.
#
# See the file "Documentation/sparse.txt" for more details, including
# where to get the "sparse" utility.

ifeq ("$(origin C)", "command line")
KBUILD_CHECKSRC = $(C)
endif
ifndef KBUILD_CHECKSRC
KBUILD_CHECKSRC = 0
endif

模块编译

make M=dir

# Use make M=dir to specify directory of external module to build
# Old syntax make ... SUBDIRS=$PWD is still supported
# Setting the environment variable KBUILD_EXTMOD take precedence
ifdef SUBDIRS
KBUILD_EXTMOD ?= $(SUBDIRS)
endif

ifeq ("$(origin M)", "command line")
KBUILD_EXTMOD := $(M)
endif


# If building an external module we do not care about the all: rule
# but instead _all depend on modules
PHONY += all
ifeq ($(KBUILD_EXTMOD),)
_all: all
else
_all: modules
endif

ifeq ($(KBUILD_SRC),)
# building in the source tree
srctree := .
else
ifeq ($(KBUILD_SRC)/,$(dir $(CURDIR)))
# building in a subdirectory of the source tree
srctree := ..
else
srctree := $(KBUILD_SRC)
endif
endif
objtree := .
src := $(srctree)
obj := $(objtree)

VPATH := $(srctree)$(if $(KBUILD_EXTMOD),:$(KBUILD_EXTMOD))

export srctree objtree VPATH

ifeq ($(KBUILD_EXTMOD),)处判断KBUILD_EXTMOD是不是空,为空,让_all=all

因此要先编译出all。否则的话默认目标_all 依赖 modules,要先编译出 modules,也就是编译模块。一般情况下我们不会在 uboot 中编译模块,所以此处会编译 all 这个目标。

最后导出变量

获取主机架构和系统

HOSTARCH := $(shell uname -m | \
sed -e s/i.86/x86/ \
-e s/sun4u/sparc64/ \
-e s/arm.*/arm/ \
-e s/sa110/arm/ \
-e s/ppc64/powerpc/ \
-e s/ppc/powerpc/ \
-e s/macppc/powerpc/\
-e s/sh.*/sh/)

HOSTOS := $(shell uname -s | tr '[:upper:]' '[:lower:]' | \
sed -e 's/\(cygwin\).*/cygwin/')

export HOSTARCH HOSTOS

调用SHELL获取当前系统架构与主机OS(会将大写的Linux换成小写的)

看了一下,好像可以用cygwin在win端做Uboot的交叉编译

设置目标架构、交叉编译器、配置文件

# set default to nothing for native builds
ifeq ($(HOSTARCH),$(ARCH))
CROSS_COMPILE ?=
endif

KCONFIG_CONFIG ?= .config
export KCONFIG_CONFIG

判断是不是架构一致

可以直接修改顶层 Makefile,在里面加入 ARCH CROSS_COMPILE 的定义

可以看到调用了.config,是kconfig生成的,基本上是从defconfig搬过来

调用scripts/Kbuild.include

# We need some generic definitions (do not try to remake the file).
scripts/Kbuild.include: ;
include scripts/Kbuild.include

用include包含了里面的很多变量

交叉编译工具变量设置

# Make variables (CC, etc...)

AS = $(CROSS_COMPILE)as
# Always use GNU ld
ifneq ($(shell $(CROSS_COMPILE)ld.bfd -v 2> /dev/null),)
LD = $(CROSS_COMPILE)ld.bfd
else
LD = $(CROSS_COMPILE)ld
endif
CC = $(CROSS_COMPILE)gcc
CPP = $(CC) -E
AR = $(CROSS_COMPILE)ar
NM = $(CROSS_COMPILE)nm
LDR = $(CROSS_COMPILE)ldr
STRIP = $(CROSS_COMPILE)strip
OBJCOPY = $(CROSS_COMPILE)objcopy
OBJDUMP = $(CROSS_COMPILE)objdump

所见即所得

导出其他变量

export VERSION PATCHLEVEL SUBLEVEL UBOOTRELEASE UBOOTVERSION
export ARCH CPU BOARD VENDOR SOC CPUDIR BOARDDIR
export CONFIG_SHELL HOSTCC HOSTCFLAGS HOSTLDFLAGS CROSS_COMPILE AS LD CC
export CPP AR NM LDR STRIP OBJCOPY OBJDUMP
export MAKE AWK PERL PYTHON
export HOSTCXX HOSTCXXFLAGS DTC CHECK CHECKFLAGS

export KBUILD_CPPFLAGS NOSTDINC_FLAGS UBOOTINCLUDE OBJCOPYFLAGS LDFLAGS
export KBUILD_CFLAGS KBUILD_AFLAGS

重点看看ARCH CPU BOARD VENDOR SOC CPUDIR BOARDDIR

image-20230807151712823

这几个变量哪来的?

config.mk,这 7 个变量就是在 config.mk 里面定义的

里面的变量又由.config定义

make xxx_defconfig过程

其实学到这里已经想起飞了,感觉讲的有点乱

对整体的Makefile架构极其混乱

scripts_basic 目标对应的命令
# Basic helpers built in scripts/
PHONY += scripts_basic
scripts_basic:
$(Q)$(MAKE) $(build)=scripts/basic
$(Q)rm -f .tmp_quiet_recordmcount

# To avoid any implicit rule to kick in, define an empty command.
scripts/basic/%: scripts_basic ;

PHONY += outputmakefile
# outputmakefile generates a Makefile in the output directory, if using a
# separate output directory. This allows convenient use of make in the
# output directory.
# 一般来说KBUILD_SRC为空,所以只有scripts_basic有用
outputmakefile:
ifneq ($(KBUILD_SRC),)
$(Q)ln -fsn $(srctree) source
$(Q)$(CONFIG_SHELL) $(srctree)/scripts/mkmakefile \
$(srctree) $(objtree) $(VERSION) $(PATCHLEVEL)
endif

这部分感觉正点讲的不是很清晰

看上面KBUILD_SRC的说明,这玩意不适合regular user (for now)

所以专注于

$(Q)$(MAKE) $(build)=scripts/basic

这句编译下来就是make -f $(srctree)/scripts/Makefile.build obj==scripts/basic

srctree.->./scripts/Makefile.build

now ,我们需要看看./scripts/Makefile.build

# The filename Kbuild has precedence over Makefile
kbuild-dir := $(if $(filter /%,$(src)),$(src),$(srctree)/$(src))
kbuild-file := $(if $(wildcard $(kbuild-dir)/Kbuild),$(kbuild-dir)/Kbuild,$(kbuild-dir)/Makefile)
include $(kbuild-file)

kbuild-dir展开后=$(if $(filter /%, scripts/basic), scripts/basic , ./scripts/basic)

kbuild-file 展开后=(if(if(wildcard./scripts/basic/Kbuild),./scripts/basic/Kbuild, ./scripts/basic/Makefile)

也=./scripts/basic/Makefile,因为没有Kbuild

include $(kbuild-file)=./scripts/basic/Makefile

继续看Makefile.build

# We keep a list of all modules in $(MODVERDIR)

__build: $(if $(KBUILD_BUILTIN),$(builtin-target) $(lib-target) $(extra-y)) \
$(if $(KBUILD_MODULES),$(obj-m) $(modorder-target)) \
$(subdir-ym) $(always)
@:

__build这哥们是默认的,一般make后没有指定目标会执行这个

在顶层的Makefile里,KBUILD_BUILTIN=1,KBUILD_MODULES=0,

因此展开build 后=

build:$(builtin-target) $(lib-target) $(extra-y)) $(subdir-ym) $(always)

KBUILD_MODULES搞没了

所以__build 有 5 个依赖:builtin-targetlib-targetextra-ysubdir-ymalways

最后echo发现只有always有路径,依赖于 scripts/basic/fixdep(some dependences)

提示

综上所述,scripts_basic 目标的作用就是编译出 scripts/basic/fixdep.c这个软件。

%config 目标对应的命令
config: scripts_basic outputmakefile FORCE
$(Q)$(MAKE) $(build)=scripts/kconfig $@

%config: scripts_basic outputmakefile FORCE
$(Q)$(MAKE) $(build)=scripts/kconfig $@

老顾客make + build 了

翻译成人话就是

@make -f ./scripts/Makefile.build obj=scripts/kconfig xxx_defconfig

image-20230812200201720

在解释一下各部分变量

# Modified for U-Boot
# pattern 用于替换(删除 src中的tpl/部分
# but we dont hava any pattern like tpl or spl
# so src = obj
prefix := tpl
src := $(patsubst $(prefix)/%,%,$(obj))
ifeq ($(obj),$(src))
prefix := spl
src := $(patsubst $(prefix)/%,%,$(obj))
ifeq ($(obj),$(src))
prefix := .
endif
endif
# The filename Kbuild has precedence over Makefile
kbuild-dir := $(if $(filter /%,$(src)),$(src),$(srctree)/$(src))
kbuild-file := $(if $(wildcard $(kbuild-dir)/Kbuild),$(kbuild-dir)/Kbuild,$(kbuild-dir)/Makefile)
include $(kbuild-file)

src= scripts/kconfig

kbuild-dir = ./scripts/kconfig

kbuild-file = ./scripts/kconfig/Makefile

include ./scripts/kconfig/Makefile

读取了 ./scripts/kconfig/Makefile ,

%_defconfig: $(obj)/conf
$(Q)$< $(silent) --defconfig=arch/$(SRCARCH)/configs/$@ $(Kconfig)

# Added for U-Boot (backward compatibility)
%_config: %_defconfig
@:

然后这个,符合了我们之前的什么xxx_defconfig的规则

展开后就是 scripts/kconfig/conf

接下来就是检查并生成依赖 scripts/kconfig/conf。

conf 是主机软件,到这里我们就打住,不要纠结 conf 是怎么编译出来的,否则就越陷越深,太绕了,像 conf 这种主机所使用的工具类软件我们一般不关心它是如何编译产生的。如果一定要看是 conf 是怎么生成的,可以输入如下命令重新配置 uboot,在重新配置 uboot 的过程中就会输出 conf 编译信息。

make distclean
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- mx6ull_14x14_ddr512_emmc_
defconfig V=1

$(SRCARCH)=..

将其展开就是:@ scripts/kconfig/conf --defconfig=arch/../configs/xxx_defconfig Kconfig

image-20230817210928995

u-boot.bin怎么生成

总的:

make V=1 -j16
注意

据说学习太多这些makefile用不上,学太细也会忘,过一下就好

默认目标

# That's our default target when none is given on the command line
PHONY := _all
_all:

后面的代码还有,可能将_all覆盖

# If building an external module we do not care about the all: rule
# but instead _all depend on modules
PHONY += all
ifeq ($(KBUILD_EXTMOD),)
_all: all
else
_all: modules
endif

继续找all

在800多行我们找到了

all: $(ALL-y)

这玩意=ALL-y+=u-boot.srec u-boot.bin u-boot.sym System.map u-boot.cfg binary_size_check

看到了u-boot.bin了,再搜

u-boot.bin: u-boot-dtb.bin FORCE
$(call if_changed,copy)
else
u-boot.bin: u-boot-nodtb.bin FORCE
$(call if_changed,copy)
endif

可以看到又回来u-boot-nodtb.bin了

u-boot-nodtb.bin: u-boot FORCE
$(call if_changed,objcopy)
$(call DO_STATIC_RELA,$<,$@,$(CONFIG_SYS_TEXT_BASE))
$(BOARD_SIZE_CHECK)

又回到了u-boot,翻到1100+行终于找到

u-boot: $(u-boot-init) $(u-boot-main) u-boot.lds FORCE
$(call if_changed,u-boot__)
ifeq ($(CONFIG_KALLSYMS),y)
$(call cmd,smap)
$(call cmd,u-boot__) common/system_map.o
endif

这里将$(u-boot-init) $(u-boot-main)用lds链接在了一起

u-boot-init := $(head-y)

u-boot-main := $(libs-y)

$(head-y)与cpu架构有关,不在这个Makefile里,head-y= arch/arm/cpu/armv7/start.o

$(libs-y)是Uboot文件里的源码集合,具体是

libs-y=保存源码的目录

such as

libs-y += fs/
libs-y += net/
libs-y += disk/
libs-y += drivers/
libs-y += drivers/dma/
libs-y += drivers/gpio/
libs-y += drivers/i2c/
libs-y += drivers/mmc/
libs-y += drivers/mtd/

libs-y += lib/ -》lib/built-in.o

libs-y保存大量的built-in.o

u-boot就是将start.o 和大量的built-in.o链接在一起。

假如

CONFIG_ONENAND_U_BOOT =y

ALL-$(CONFIG_ONENAND_U_BOOT)

ALL-y += u-boot-onenand.bin

链接

链接脚本为u-boot.lds,uboot链接首地址为0X87800000

image-20230817231330386

search得知,在mx6_common.h文件中设置CONFIG_SYS_TEXT_BASE=0X87800000

summary

image-20230817231429090

支持正点原子就到此打住,他们说Uboot和linux大差不差,后期在学吧

(为什么不用cmake,why?

Uboot启动流程

u-boot.lds

  1. 入口地址为_start 在arch/arm/lib/vectors.S中定义

    _start:

    #ifdef CONFIG_SYS_DV_NOR_BOOT_CFG
    .word CONFIG_SYS_DV_NOR_BOOT_CFG
    #endif

    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
  2. .__image_copy_start 0x87800000

  3. .vectors 0x87800000放中断向量表

  4. arch/arm/cpu/armv7/start.o start.c

  5. .image_copy_end,这两货的中间是拷贝镜像?

  1. rel_dyn_startrel段,很小,35k
  2. __rel_dyn_end衔接__end
  1. bss_start

函数

  1. reset

    reset:
    /* Allow the board to save important registers */
    b save_boot_params
    save_boot_params_ret:
    /*
    * disable interrupts (FIQ and IRQ), also set the cpu to SVC32 mode,
    * except if in HYP mode already
    */
    mrs r0, cpsr
    and r1, r0, #0x1f @ mask mode bits
    teq r1, #0x1a @ test for HYP mode
    bicne r0, r0, #0x1f @ clear all mode bits
    orrne r0, r0, #0x13 @ set SVC mode
    orr r0, r0, #0xc0 @ disable FIQ and IRQ
    msr cpsr,r0

    将处理器设置为svc mode,关闭FIQ、IRQ

    然后设置vector

    /*
    * Setup vector:
    * (OMAP4 spl TEXT_BASE is not 32 byte aligned.
    * Continue to use ROM code vector only in OMAP4 spl)
    */
    #if !(defined(CONFIG_OMAP44XX) && defined(CONFIG_SPL_BUILD))
    /* Set V=0 in CP15 SCTLR register - for VBAR to point to vector */
    mrc p15, 0, r0, c1, c0, 0 @ Read CP15 SCTLR Register
    bic r0, #CR_V @ V = 0控制bit13的V位,向量表控制位,重定位向量表
    mcr p15, 0, r0, c1, c0, 0 @ Write CP15 SCTLR Register

    /* Set vector address in CP15 VBAR register */
    ldr r0, =_start
    mcr p15, 0, r0, c12, c0, 0 @Set VBAR
    #endif
    /* the mask ROM code should have PLL and others stable */
#ifndef CONFIG_SKIP_LOWLEVEL_INIT
bl cpu_init_cp15
bl cpu_init_crit
#endif

可以看到这是底层的init,下面就是具体的

#ifndef CONFIG_SKIP_LOWLEVEL_INIT
/*************************************************************************
*
* CPU_init_critical registers
*
* setup important registers
* setup memory timing
*
*************************************************************************/
ENTRY(cpu_init_crit)
/*
* Jump to board specific initialization...
* The Mask ROM will have already initialized
* basic memory. Go here to bump up clock rate and handle
* wake up conditions.
*/
b lowlevel_init @ go setup pll,mux,memory
ENDPROC(cpu_init_crit)
#endif

函数lowlevel_init在文件arch/arm/cpu/armv7/lowlevel_init.S 中定义

ENTRY(lowlevel_init)
/*
* Setup a temporary stack. Global data is not available yet.
*/
# 初始化SP
#设置 sp 指向 CONFIG_SYS_INIT_SP_ADDR,CONFIG_SYS_INIT_SP_ADDR 在 include/configs/mx6ullevk.h 文件中,在 mx6ullevk.h 中有如下所示定义

ldr sp, =CONFIG_SYS_INIT_SP_ADDR
bic sp, sp, #7 /* 8-byte alignment for ABI compliance */
#ifdef CONFIG_SPL_DM
mov r9, #0
#else
/*
* Set up global data for boards that still need it. This will be
* removed soon.
*/
#ifdef CONFIG_SPL_BUILD
ldr r9, =gdata
#else
sub sp, sp, #GD_SIZE
bic sp, sp, #7
mov r9, sp
#endif
#endif
/*
* Save the old lr(passed in ip) and the current lr to stack
*/
push {ip, lr}

/*
* Call the very early init function. This should do only the
* absolute bare minimum to get started. It should not:
*
* - set up DRAM
* - use global_data
* - clear BSS
* - try to start a console
*
* For boards with SPL this should be empty since SPL can do all of
* this init in the SPL board_init_f() function which is called
* immediately after this.
*/
bl s_init
pop {ip, pc}
ENDPROC(lowlevel_init)
  1. sp 指向 CONFIG_SYS_INIT_SP_ADDR,在include/configs/mx6ullevk.h 文件中

    #define CONFIG_SYS_SDRAM_BASE       PHYS_SDRAM
    #define CONFIG_SYS_INIT_RAM_ADDR IRAM_BASE_ADDR
    #define CONFIG_SYS_INIT_RAM_SIZE IRAM_SIZE

    #define CONFIG_SYS_INIT_SP_OFFSET \
    (CONFIG_SYS_INIT_RAM_SIZE - GENERATED_GBL_DATA_SIZE)
    #define CONFIG_SYS_INIT_SP_ADDR \
    (CONFIG_SYS_INIT_RAM_ADDR + CONFIG_SYS_INIT_SP_OFFSET)

    指向的IRAM_BASE_ADDRarch/arm/include/asm/arch-mx6/imx-regs.h定义

    #define IRAM_BASE_ADDR           0x00900000
    #if !(defined(CONFIG_MX6SX) || defined(CONFIG_MX6UL) || \
    defined(CONFIG_MX6SLL) || defined(CONFIG_MX6SL))
    #define IRAM_SIZE 0x00040000
    #else
    #define IRAM_SIZE 0x00020000
    #endif

    最终是IRAM_SIZE=0x00020000=128KB

    还要知道GENERATED_GBL_DATA_SIZE的值,在文件 include/generated/generic-asm-offsets.h

    #ifndef __GENERIC_ASM_OFFSETS_H__
    #define __GENERIC_ASM_OFFSETS_H__
    #define GENERATED_GBL_DATA_SIZE 256 /* (sizeof(struct global_data) + 15) & ~15 @ */
    #define GENERATED_BD_INFO_SIZE 80 /* (sizeof(struct bd_info) + 15) & ~15 @ */
    #define GD_SIZE 248 /* sizeof(struct global_data) @ */
    #define GD_BD 0 /* offsetof(struct global_data, bd) @ */
    #define GD_MALLOC_BASE 188 /* offsetof(struct global_data, malloc_base) @ */
    #define GD_RELOCADDR 44 /* offsetof(struct global_data, relocaddr) @ */
    #define GD_RELOC_OFF 64 /* offsetof(struct global_data, reloc_off) @ */
    #define GD_START_ADDR_SP 60 /* offsetof(struct global_data, start_addr_sp) @ */

    #endif

    gd=256

    CONFIG_SYS_INIT_SP_OFFSET=CONFIG_SYS_INIT_RAM_SIZE - GENERATED_GBL_DATA_SIZE

    =0x00020000-256=0x1FF00

    CONFIG_SYS_INIT_SP_ADDR=CONFIG_SYS_INIT_RAM_ADDR + CONFIG_SYS_INIT_SP_OFFSET

    =0x00900000+0x1FF00= 0X0091FF00

    总结:设置SP指针,R9寄存器,运行s_init

  2. s_init函数

    啥也没干,return了

  3. _main函数

    1. 设置栈指针as CONFIG_SYS_INIT_SP_ADDR

    2. 对其做8byte alignment

    3. 将sp作为入参,bl to board_init_f_alloc_reserve,顾名思义应该是留出早期的 malloc 内存区域和 gd 内存区域 CONFIG_SYS_MALLOC_F_LEN=0X400( 在 文 件 include/generated/autoconf.h 中定义)

      #define rounddown(x, y) (               \
      { \
      typeof(x) __x = (x); \
      __x - (__x % (y)); \
      } \
      )
      ulong board_init_f_alloc_reserve(ulong top)
      {
      /* Reserve early malloc arena */
      #if defined(CONFIG_SYS_MALLOC_F)
      top -= CONFIG_SYS_MALLOC_F_LEN;
      #endif
      /* LAST : reserve GD (rounded up to a multiple of 16 bytes) */
      top = rounddown(top-sizeof(struct global_data), 16);
      return top;
      }

      image-20230818175026568

      将返回的top=r0写入sp

    4. 将 r0 寄存器的值写到寄存器 r9 里面,因为 r9 寄存器存放着全局变量 gd 的地址(set up gd outside any C code

    5. bl to board_init_f_init_reserve

      /*
      * Initialize reserved space (which has been safely allocated on the C
      * stack from the C runtime environment handling code).
      *
      * Notes:
      *
      * Actual reservation was done by the caller; the locations from base
      * to base+size-1 (where 'size' is the value returned by the allocation
      * function above) can be accessed freely without risk of corrupting the
      * C runtime environment.
      *
      * IMPORTANT:
      *
      * Upon return from the allocation function above, on some architectures
      * the caller will set gd to the lowest reserved location. Therefore, in
      * this initialization function, the global data MUST be placed at base.
      *
      * ALSO IMPORTANT:
      *
      * On some architectures, gd will already be good when entering this
      * function. On others, it will only be good once arch_setup_gd() returns.
      * Therefore, global data accesses must be done:
      *
      * - through gd_ptr if before the call to arch_setup_gd();
      *
      * - through gd once arch_setup_gd() has been called.
      *
      * Do not use 'gd->' until arch_setup_gd() has been called!
      *
      * IMPORTANT TOO:
      *
      * Initialization for each "chunk" (GD, early malloc arena...) ends with
      * an incrementation line of the form 'base += <some size>'. The last of
      * these incrementations seems useless, as base will not be used any
      * more after this incrementation; but if/when a new "chunk" is appended,
      * this increment will be essential as it will give base right value for
      * this new chunk (which will have to end with its own incrementation
      * statement). Besides, the compiler's optimizer will silently detect
      * and remove the last base incrementation, therefore leaving that last
      * (seemingly useless) incrementation causes no code increase.
      */
      void board_init_f_init_reserve(ulong base)
      {
      struct global_data *gd_ptr;
      #ifndef _USE_MEMCPY
      int *ptr;
      #endif

      /*
      * clear GD entirely and set it up.
      * Use gd_ptr, as gd may not be properly set yet.
      */

      gd_ptr = (struct global_data *)base;
      /* zero the area */
      #ifdef _USE_MEMCPY
      memset(gd_ptr, '\0', sizeof(*gd));
      #else
      for (ptr = (int *)gd_ptr; ptr < (int *)(gd_ptr + 1); )
      *ptr++ = 0;
      #endif
      /* set GD unless architecture did it already */
      #if !defined(CONFIG_ARM)
      arch_setup_gd(gd_ptr);
      #endif
      /* next alloc will be higher by one GD plus 16-byte alignment */
      base += roundup(sizeof(struct global_data), 16);

      /*
      * record early malloc arena start.
      * Use gd as it is now properly set for all architectures.
      */

      #if defined(CONFIG_SYS_MALLOC_F)
      /* go down one 'early malloc arena' */
      gd->malloc_base = base;
      /* next alloc will be higher by one 'early malloc arena' size */
      base += CONFIG_SYS_MALLOC_F_LEN;
      #endif
      }

      也就是很纯粹的menset=0,ARM端做16字节对齐,gd->malloc_base=0X0091FB00,early malloc base

    6. mov r0,#0对r0清零

    7. bl to board_init_f用于初始化DDR、定时器、代码拷贝等等

    8. ldr sp, [r9, #GD_START_ADDR_SP] /* sp = gd->start_addr_sp */

      Set up intermediate environment (new sp and gd) and call relocate_code(addr_moni). Trick here is that we'll return 'here' but relocated.设置刚刚搞好的gd->???为sp

    9. 将uboot拷贝到DDR最后面的地址去/* r0 = gd->relocaddr */

    10. b to relocate_code copy code

    11. bl to relocate_vectors重定位中断向量表

    12. bl to c_runtime_cpu_setup

      ENTRY(c_runtime_cpu_setup)
      /*
      * If I-cache is enabled invalidate it
      */
      #ifndef CONFIG_SYS_ICACHE_OFF
      mcr p15, 0, r0, c7, c5, 0 @ invalidate icache
      mcr p15, 0, r0, c7, c10, 4 @ DSB
      mcr p15, 0, r0, c7, c5, 4 @ ISB
      #endif
      bx lr
      ENDPROC(c_runtime_cpu_setup)
    13.     ldr r0, =__bss_start    /* this is auto-relocated! */

      #ifdef CONFIG_USE_ARCH_MEMSET
      ldr r3, =__bss_end /* this is auto-relocated! */
      mov r1, #0x00000000 /* prepare zero to clear BSS */

      subs r2, r3, r0 /* r2 = memset len */
      bl memset
      #else
      ldr r1, =__bss_end /* this is auto-relocated! */
      mov r2, #0x00000000 /* prepare zero to clear BSS */

      clbss_l:cmp r0, r1 /* while not at end of BSS */
      #if defined(CONFIG_CPU_V7M)
      itt lo
      #endif
      strlo r2, [r0] /* clear 32-bit BSS word */
      addlo r0, r0, #4 /* move to next */
      blo clbss_l
      #endif

      清除BSS段

    14. 调用board_init_r

    now focus on board_init_frelocate_coderelocate_vectors board_init_r 这 4 个函数

board_init_f 函数

void board_init_f(ulong boot_flags)
{

//这段没有定义
#ifdef CONFIG_SYS_GENERIC_GLOBAL_DATA
/*
* For some archtectures, global data is initialized and used before
* calling this function. The data should be preserved. For others,
* CONFIG_SYS_GENERIC_GLOBAL_DATA should be defined and use the stack
* here to host global data until relocation.
*/
gd_t data;

gd = &data;

/*
* Clear global data before it is accessed at debug print
* in initcall_run_list. Otherwise the debug print probably
* get the wrong vaule of gd->have_console.
*/
zero_global_data();
#endif

gd->flags = boot_flags;//初始化=0
gd->have_console = 0;
//初始化序列init_sequence_f里的一些函数
if (initcall_run_list(init_sequence_f))
hang();

#if !defined(CONFIG_ARM) && !defined(CONFIG_SANDBOX) && \
!defined(CONFIG_EFI_APP)
/* NOTREACHED - jump_to_copy() does not return */
hang();
#endif

/* Light up LED1 */
imx6_light_up_led1();
}

去掉条件编译以后的 init_sequence_f

 /*****************去掉条件编译语句后的 init_sequence_f***************/
static init_fnc_t init_sequence_f[] = {
setup_mon_len, //=gd->mon_len __bss_end-_start=代码长度
initf_malloc, //设置gd->malloc_limit=CONFIG_SYS_MALLOC_F_LEN=0X400 malloc_limit 表示 malloc 内存池大小。
initf_console_record,
arch_cpu_init, /* basic arch cpu dependent setup */
initf_dm, //驱动模型的一些初始化
arch_cpu_init_dm,
mark_bootstage, /* need timer, go after init dm */
board_early_init_f, //板子初期初始化,比如串口波特率
timer_init, /* initialize timer*/
board_postclk_init, //设置VDDSOC电压
get_clocks, //获取的是 sdhc_clk 时钟,也就是SD卡外设的时钟
env_init, /* initialize environment */
init_baud_rate, /* initialze baudrate settings */
serial_init, /* serial communications setup */
console_init_f, /* stage 1 init of console*/
display_options, /* say that we are here */
display_text_info, /* show debugging info if required */
//如果开启debug会显示:debug("U-Boot code: %08lX -> %08lX BSS: -> %08lX\n",text_base, bss_start, bss_end);
print_cpuinfo, /* display cpu info (and speed) */
show_board_info,
INIT_FUNC_WATCHDOG_INIT
INIT_FUNC_WATCHDOG_RESET
init_func_i2c,
announce_dram_init, /* TODO: unify all these dram functions? */
dram_init, /* configure available RAM banks */
post_init_f,
INIT_FUNC_WATCHDOG_RESET,
testdram,
INIT_FUNC_WATCHDOG_RESET
INIT_FUNC_WATCHDOG_RESET
/* Now that we have DRAM mapped and working, we can
* relocate the code and continue running from DRAM.
*
* Reserve memory at end of RAM for (top down in that order):
* - area that won't get touched by U-Boot and Linux (optional)
* - kernel log buffer
* - protected RAM
* - LCD framebuffer
* - monitor code
* - board info struct
*/
setup_dest_addr,//设置目的地址,设置 gd->ram_size,gd->ram_top,gd->relocaddr
reserve_round_4k,//用于对gd->relocaddr做4KB对齐,因为gd->relocaddr=0XA0000000,已经是 4K 对齐了,所以调整后不变
reserve_mmu,//留出MMU的TLB表的位置,分配MMU的TLB表内存以后会对 gd->relocaddr 做 64K 字节对齐
/*gd->arch.tlb_size= 0X4000 //MMU 的 TLB 表大小
gd->arch.tlb_addr=0X9FFF0000 //MMU 的 TLB 表起始地址,64KB 对齐以后
gd->relocaddr=0X9FFF0000 //relocaddr 地址 */

reserve_trace,//留出跟踪调试的内存
reserve_uboot,//留出重定位后的 uboot 所占用的内存区域,uboot 所占用大小由gd->mon_len 所指定,留出 uboot 的空间以后还要对 gd->relocaddr 做 4K 字节对齐,并且重新设置 gd->start_addr_sp
reserve_malloc,//留出 malloc 区域,调整 gd->start_addr_sp 位置
reserve_board,
setup_machine, //没用
reserve_global_data,//设置newgd在DRAM里
reserve_fdt,//设备树
reserve_arch,
reserve_stacks,
setup_dram_config,
show_dram_config,
display_new_sp,
INIT_FUNC_WATCHDOG_RESET
reloc_fdt,
setup_reloc,
NULL,
};
  1. 初始化一系列外设 比如 串口、定时器、或者打印一些消息

  2. 初始化gd成员变量,

  3. uboot 会将自己重定位到 DRAM 最后面的地址区域,也就是将自己拷贝到 DRAM 最后面的内存区域中。这么做的目的是给 Linux 腾出空间,防止 Linux kernel 覆盖掉 uboot,将 DRAM 前面的区域完整的空出来。在拷贝之前肯定要给 uboot 各部分分配好内存位置和大小,比如 gd 应该存放到哪个位置,malloc 内存池应该存放到哪个位置等等。这些信息都保存在 gd 的成员变量中,因此要对 gd 的这些成员变量做初始化。最终形成一个完整的内存“分配图”,在后面重定位 uboot 的时候就会用到这个内存“分配图”。

    how?

比较难理解的是gd->relocaddr