官方U-Boot下载
Github
下载后拷贝tar.bz2至Linux,然后tar -vxjf
U-boot工程目录
除了文件夹之外,还有一些文件:

验证defconfig
cd /configs/,确认存在有I.MX6ULL的配置文件:

新版本uboot中mx6ull_14x14_evk_defconfig用于传统的非安全启动(Non-Secure Boot)模式,而mx6ull_14x14_evk_plugin_defconfig支持安全启动(Secure Boot),允许 U-Boot 在 ROM 加载阶段执行自定义代码,通常用于 HAB 安全启动初始化(如 DRAM 训练)。通常使用前者即可。
添加变量至顶层Makefile
在uboot根目录下的顶层Makefile中添加:
ARCH=armCROSS_COMPILE=arm-linux-gnueabihf-编写make用Shell,编译
touch mx6ull_14x14_evk.sh
#!/bin/bashmake ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- distcleanmake ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- mx6ull_14x14_evk_emmc_defconfigmake V=1 ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- -j16chmod 777 mx6ull_14x14_evk.sh./mx6ull_14x14_evk.sh,编译
这里用的uboot是NXP最新的分支v2022.04,这个uboot版本比较新,如果用老版本的交叉编译器会报错,因为它不支持比较新的语法。这里使用的uboot、kernel和编译器的版本都为:
- uboot:
v2022.04 - kernel:
if 6.6y - arm-linux-gnueabihf-gcc:
7.5.0
编译完成后,imxdownload烧录至SD卡,设置开发板从SD卡启动。
此时Uboot启动之后,由于LCD未做配置,LCD上是不会显示NXP的Logo的。下一步是修改LCD驱动。
修改LCD驱动
IO部分不用修改,官方和开发板一致,仅需修改参数。较老版本的LCD参数在board/freescale/mx6ull_aki/mx6ull_14x14_evk_emmc.c的display_info_t结构体里。但是新版本uboot全部把诸如LCD之类的外设配置参数全部移到了设备树中。并且,imx6ull是由imx6ul这块板子拓展而来的,二者的设备树也呈非常明显的层次关系,即先读imx6ul的设备树,再读imx6ull的,所以很多外设节点都在/arch/arm/dts/imx6ul-14x14-evk.dtsi这个设备树文件中定义。
打开该设备树文件,找到:
&lcdif { pinctrl-names = "default"; pinctrl-0 = <&pinctrl_lcdif_dat &pinctrl_lcdif_ctrl>;
display = <&display0>; status = "okay";
display0: display@0 { bits-per-pixel = <24>; bus-width = <24>;
display-timings { native-mode = <&timing0>; timing0: timing0 { clock-frequency = <51200000>; //像素时钟频率,Hz hactive = <1024>; //水平可见像素数(水平分辨率) vactive = <600>; //垂直可见像素数(垂直分辨率) hfront-porch = <160>; //水平前肩,HFP hback-porch = <140>; //水平后肩,HBP hsync-len = <20>; //水平同步脉冲,HSPW vback-porch = <20>; //水平后肩,VBP vfront-porch = <12>; //水平前肩,VFP vsync-len = <3>; //垂直同步脉冲,VSPW
hsync-active = <0>; //水平同步信号极性 vsync-active = <0>; //垂直同步信号极性 de-active = <1>; //数据使能,高电平有效 pixelclk-active = <0>; //下降沿采样 }; }; };};按注释修改即可。新版设备树似乎没有定义LCD名字的地方,感觉不是那么在意设备名,毕竟都用设备树了。
联网
主机部分
物理部分:路由器WAN连接校园网,LAN1接电脑,LAN2接开发板ENET2(网卡1)。
VMware需开启桥接模式:

虚拟网络编辑器中一般会自动设置桥接网卡,不用手动设置。桥接模式下虚拟机和物理主机一样存在于局域网中,可以和主机相通,和互联网相通,和局域网中其它主机相通。
配置成桥接模式后虚拟机的IP、网关、DNS、netmask等全部会自动配置。在虚拟机中ifconfig:
ens33: flags=4163<UP,BROADCAST,RUNNING,MULTICAST> mtu 1500 inet 192.168.1.105 netmask 255.255.255.0 broadcast 192.168.1.255 ether 00:0c:29:0d:4a:09 txqueuelen 1000 (以太网) RX packets 4642 bytes 4241810 (4.2 MB) RX errors 0 dropped 0 overruns 0 frame 0 TX packets 2980 bytes 400799 (400.7 KB) TX errors 0 dropped 5 overruns 0 carrier 0 collisions 0
lo: flags=73<UP,LOOPBACK,RUNNING> mtu 65536 inet 127.0.0.1 netmask 255.0.0.0 inet6 ::1 prefixlen 128 scopeid 0x10<host> loop txqueuelen 1000 (本地环回) RX packets 1298 bytes 110055 (110.0 KB) RX errors 0 dropped 0 overruns 0 frame 0 TX packets 1298 bytes 110055 (110.0 KB) TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0此时主机的IPv4地址为192.168.1.100,可以看到桥接模式已经生效,主机和虚拟机处于同一个网段下。
板级部分
官方的NXP IMX6ULL EVK开发板使用的不是LAN8720A这个IC,因此需要修改一系列设置。好在官方的板子和正点原子阿尔法开发板的PHY物理地址是一致的,这点从设备树里可以看到:
&fec1 { pinctrl-names = "default"; pinctrl-0 = <&pinctrl_enet1>; phy-mode = "rmii"; phy-handle = <ðphy0>; phy-reset-gpios = <&gpio5 7 GPIO_ACTIVE_LOW>; phy-reset-duration = <100>; phy-reset-post-delay = <100>; status = "disable";};
&fec2 { pinctrl-names = "default"; pinctrl-0 = <&pinctrl_enet2>; phy-mode = "rmii"; phy-handle = <ðphy1>; phy-reset-gpios = <&gpio5 8 GPIO_ACTIVE_LOW>; phy-reset-duration = <100>; phy-reset-post-delay = <100>; status = "okay";
mdio { #address-cells = <1>; #size-cells = <0>;
ethphy0: ethernet-phy@2 { reg = <2>; //PHY0物理地址为2 micrel,led-mode = <1>; clocks = <&clks IMX6UL_CLK_ENET_REF>; clock-names = "rmii-ref"; };
ethphy1: ethernet-phy@1 { reg = <1>; //PHY1物理地址为1 micrel,led-mode = <1>; clocks = <&clks IMX6UL_CLK_ENET2_REF>; clock-names = "rmii-ref"; }; };};初始化检查
检查/common/board_r.c,其中从网络初始化入口initr_net开始Uboot会进行网络设置,其中initr_net会调用eth_initialize()进行网络初始化,然后调用reset_phy()对PHY进行复位,而phy_init()中有以下条件宏语句:
int phy_init(void){...#ifdef CONFIG_PHY_MICREL_KSZ8XXX phy_micrel_ksz8xxx_init();#endif#ifdef CONFIG_PHY_MICREL_KSZ90X1 phy_micrel_ksz90x1_init();#endif...#ifdef CONFIG_PHY_SMSC phy_smsc_init();#endif... genphy_init();
return 0;}显然这是根据defconfig里的键值来对应不同的PHY初始化函数。阿尔法开发板上的SR8201F是Realtek公司生产的,因此根据phy_init,defconfig里要设置CONFIG_PHY_REALTEK=y才会触发phy_realtek_init()这个函数。
打开/configs/imx6ull_14x14_evk_emmc_defconfig,删除或屏蔽:
CONFIG_PHY_MICREL=yCONFIG_PHY_MICREL_KSZ8XXX=y添加:
CONFIG_PHY_REALTEK=y(不过这里我一开始错误的改成了CONFIG_PHY_SMSC=y,发现网络也能正常使用,不知道为什么)
配置fec复位管脚
fecmxc_probe函数中还调用了fec_gpio_reset()来复位fec网卡:
#if CONFIG_IS_ENABLED(DM_GPIO) fec_gpio_reset(priv);#endif /* Reset chip. */ writel(readl(&priv->eth->ecntrl) | FEC_ECNTRL_RESET, &priv->eth->ecntrl); start = get_timer(0); while (readl(&priv->eth->ecntrl) & FEC_ECNTRL_RESET) { if (get_timer(start) > (CONFIG_SYS_HZ * 5)) { printf("FEC MXC: Timeout resetting chip\n"); goto err_timeout; } udelay(10); }而这里设备树会使用fecmxc_of_to_plat这个OF函数来读取节点信息到驱动,该函数中有:
#if CONFIG_IS_ENABLED(DM_GPIO) ret = gpio_request_by_name(dev, "phy-reset-gpios", 0, &priv->phy_reset_gpio, GPIOD_IS_OUT); if (ret < 0) return 0; /* property is optional, don't return error! */
priv->reset_delay = dev_read_u32_default(dev, "phy-reset-duration", 1); if (priv->reset_delay > 1000) { printf("FEC MXC: phy reset duration should be <= 1000ms\n"); /* property value wrong, use default value */ priv->reset_delay = 1; }
priv->reset_post_delay = dev_read_u32_default(dev, "phy-reset-post-delay", 0); if (priv->reset_post_delay > 1000) { printf("FEC MXC: phy reset post delay should be <= 1000ms\n"); /* property value wrong, use default value */ priv->reset_post_delay = 0; }#endif可以看到这里依赖节点的三个用于描述复位的值:
phy-reset-gpiosphy-reset-durationphy-reset-post-delay
回到设备树,将这三个属性加进节点:
&fec1 { pinctrl-names = "default"; pinctrl-0 = <&pinctrl_enet1>; phy-mode = "rmii"; phy-handle = <ðphy0>; phy-reset-gpios = <&gpio5 7 GPIO_ACTIVE_LOW>; phy-reset-duration = <100>; phy-reset-post-delay = <100>; status = "disable";};
&fec2 { pinctrl-names = "default"; pinctrl-0 = <&pinctrl_enet2>; phy-mode = "rmii"; phy-handle = <ðphy1>; phy-reset-gpios = <&gpio5 8 GPIO_ACTIVE_LOW>; phy-reset-duration = <100>; phy-reset-post-delay = <100>; status = "okay";
mdio { #address-cells = <1>; #size-cells = <0>;
ethphy0: ethernet-phy@2 { reg = <2>; micrel,led-mode = <1>; clocks = <&clks IMX6UL_CLK_ENET_REF>; clock-names = "rmii-ref"; };
ethphy1: ethernet-phy@1 { reg = <1>; micrel,led-mode = <1>; clocks = <&clks IMX6UL_CLK_ENET2_REF>; clock-names = "rmii-ref"; }; };};GPIO5_IO07和GPIO5_IO08会跟SPI4的两个IO冲突,这里直接把spi4 disable掉即可。通常uboot会自动加载fec2,为了方便起见,我把fec1也disable了。
最后,还需要在/drviers/net/phy中,找到genphy_config_aneg函数,这个函数负责PHY自协商的完整配置,在初始化PHY时会调用(通过操作BMCR_RESET位)。在函数中加入:
int genphy_config_aneg(struct phy_device *phydev){ int result;
/* Soft reset */ phy_reset(phydev); mdelay(150); //IC要求复位后延时150ms
......}uboot设置
在uboot内设置开发板网络配置:
setenv ipaddr 192.168.1.55setenv ethaddr b8:ae:1d:01:00:00setenv gatewayip 192.168.1.1setenv netmask 255.255.255.0setenv serverip 192.168.1.105 # Ubuntu IP然后saveenv,保存变量。
uboot支持自动配置物理地址,只需要在defconfig中加入:CONFIG_NET_RANDOM_ETHADDR=y
如果对静态IP没有要求,或者想要简单一点,可以直接在Uboot内使用DHCP命令。DHCP会自动申请可用IP,自动配置网关、DNS、子网掩码等。
此时在Uboot内ping 虚拟机地址192.168.1.105,应当是能Ping通的。为防止IP被复用,关闭虚拟机,再Ping一次虚拟机,如果无法Ping通,说明配置正确:

反过来,从虚拟机Ping开发板,肯定是Ping不通的(Ping通了说明有问题,这个IP被其他的什么东西复用了),因为此时Linux内核还未启动,网口不会被使能。
此时,三台机器的IP地址分别为:
- 主机:192.168.1.100
- 虚拟机:192.168.1.105
- 开发板:192.168.1.101(DHCP获取)
uboot启动流程
reset函数
uboot编译后会在根目录下生成链接脚本u-boot.lds。其内容的前几行里有:
OUTPUT_FORMAT("elf32-littlearm", "elf32-littlearm", "elf32-littlearm")OUTPUT_ARCH(arm)ENTRY(_start)SECTIONS{ ........很显然第3行的ENTRY(_start)中的_start是代码入口点,在arch/arm/lib/vectors.S中定义:
/* * vectors - Generic ARM exception table code * * Copyright (c) 1998 Dan Malek <dmalek@jlc.net> * Copyright (c) 1999 Magnus Damm <kieraypc01.p.y.kie.era.ericsson.se> * Copyright (c) 2000 Wolfgang Denk <wd@denx.de> * Copyright (c) 2001 Alex Züpke <azu@sysgo.de> * Copyright (c) 2001 Marius Gröger <mag@sysgo.de> * Copyright (c) 2002 Alex Züpke <azu@sysgo.de> * Copyright (c) 2002 Gary Jennejohn <garyj@denx.de> * Copyright (c) 2002 Kyle Harris <kharris@nexus-tech.net> * * SPDX-License-Identifier: GPL-2.0+ */
#include <config.h>
/* ************************************************************************* * * Symbol _start is referenced elsewhere, so make it global * ************************************************************************* */
.globl _start
/* ************************************************************************* * * Vectors have their own section so linker script can map them easily * ************************************************************************* */
.section ".vectors", "ax"
/* ************************************************************************* * * Exception vectors as described in ARM reference manuals * * Uses indirect branch to allow reaching handlers anywhere in memory. * ************************************************************************* */
_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
.................显然_start后面就是中断向量表,.section ".vectors", "ax"表明此代码存放在vectors里面,具体的地址可以在u-boot.map中查询。b reset跳转至reset函数内,该函数在arch/arm/cpu/armv7/start.S里:
reset: /* Allow the board to save important registers */ b save_boot_paramsreset函数紧接着又跳转到了save_boot_params函数,该函数同样定义在start.S里:
ENTRY(save_boot_params) b save_boot_params_ret @ back to my caller这个函数又直接跳转到了save_boot_params_ret函数:
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 @ 读取CPSR and r1, r0, #0x1f @ 提取bit0 - bit4,用于设置Cortex-A7工作模式 teq r1, #0x1a @ 判断处理器是否处于HYP模式 bicne r0, r0, #0x1f @ 若不处于HYP模式,清除模式位 orrne r0, r0, #0x13 @ 进入SVC模式(特权) orr r0, r0, #0xc0 @ 关闭FIQ和IRQ msr cpsr,r0 @ 恢复CPSR然后,继续执行:
/* * 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 @ 读取 CP15 SCTLR 寄存器 bic r0, #CR_V @ V = 0 mcr p15, 0, r0, c1, c0, 0 @ 写 CP15 SCTLR 寄存器,V清零,准备重定位向量表
/* CP15 VBAR 寄存器中设置向量表地址 */ 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 @ 设置CP15相关内容 bl cpu_init_crit #endif
bl _main这已经是跳转至main的最后一步。cpu_init_crit定义如下:
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)注释已经写了,该函数即将要跳转到的lowlevel_init会设置PLL、MUX和内存。
lowlevel_init
函数在arch/arm/cpu/armv7/lowlevel_init.S中定义:
/* * A lowlevel_init function that sets up the stack to call a C function to * perform further init. * * (C) Copyright 2010 * Texas Instruments, <www.ti.com> * * Author : * Aneesh V <aneesh@ti.com> * * SPDX-License-Identifier: GPL-2.0+ */
#include <asm-offsets.h>#include <config.h>#include <linux/linkage.h>
ENTRY(lowlevel_init) /* * Setup a temporary stack. Global data is not available yet. */ 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)第22行ldr sp, =CONFIG_SYS_INIT_SP_ADDR,将栈指针指向CONFIG_SYS_INIT_SP_ADDR,这个宏在include/configs/mx6ullevk.h里定义:
#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_ADDR和IRAM_SIZE在arch/arm/include/asm/arch-mx6/imx-regs.h中定义,实际上就是MX6ULL内部OCRAM的首地址和大小。这里CONFIG_SYS_INIT_RAM_ADDR = IRAM_BASE_ADDR = 0x00900000,CONFIG_SYS_INIT_RAM_SIZE = 0x00020000 =128KB。
根据这两行:
#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)显然还需要知道GENERATED_GBL_DATA_SIZE的值,在include/generated/generic-asm-offsets.h中定义,大小为256.那么综上,CONFIG_SYS_INIT_SP_ADDR的值如下:
CONFIG_SYS_INIT_SP_OFFSET = 0x00020000 - 256 = 0x1FF00CONFIG_SYS_INIT_SP_ADDR = 0x00900000 + 0x1FF00 = 0x0091FF00此时栈指针SP指向0x0091FF00,这属于IMX6ULL的内部RAM。
回到lowlevel_init.S,bic sp, sp, #7对栈指针作8字节对齐处理。第34行sub sp, sp, #GD_SIZE用栈指针减去GD_SIZE,这个变量在generic-asm-offsets.h中定义,大小为248;第35行bic sp, sp, #7,8字节对齐SP,此时SP=0x0091FF00 - 248 = 0x0091FE08。第36行将SP保存至R9,第42行将ip和lr入栈,57行调用s_init函数,然后58行出栈ip和lr,并将lr赋给pc。
s_init
lowlevel_init在最后回调用s_init,定义在arch/arm/cpu/armv7/mx6/soc.c中。对于MX6ULL而言,s_init()是个空函数(不满足触发要求)。至此lowlevel_init结束,返回cpu_init_crit,该函数也结束,最后返回save_boot_params_ret,紧接着就会执行_main函数。
_main
定义在arch/arm/lib/crt0.S。总体而言非常长,总共调用了4个函数:board_init_f、relocate_code、relocate_vectors、board_init_r。
board_init_f
在common/board_f.c中定义,该函数的工作:
- 初始化外设,包括串口、定时器等,并打印一些消息
- 初始化 gd 的各个成员变量,uboot 会将自己重定位到 DRAM 最后面的地址区域,也就是将自己拷贝到 DRAM 最后面的内存区域中。这么做的目的是给 Linux 腾出空间,防止 Linux kernel 覆盖掉 uboot,将 DRAM 前面的区域完整的空出来。在拷贝之前肯定要给 uboot 各部分分配好内存位置和大小,比如 gd 应该存放到哪个位置,malloc 内存池应该存放到哪个位置等等。这些信息都保存在 gd 的成员变量中,因此要对 gd 的这些成员变量做初始化。最终形成一个完整的内存map,在后面重定位 uboot 的时候就会用到这个内存map。
该函数中包括非常关键的一行:
if (initcall_run_list(init_sequence_f))hang();initcall_run_list会初始化一系列外设:
static init_fnc_t init_sequence_f[] = {setup_mon_len, //设置代码长度initf_malloc, //初始化malloc相关的成员变量initf_console_record, //无效,返回0arch_cpu_init, //基本的arch cpu依赖项初始化initf_dm,arch_cpu_init_dm,mark_bootstage,board_early_init_f, //初始化串口IO配置timer_init, //初始化Cortex-A7内核定时器,为Uboot提供时间(类似systick)board_postclk_init, //设置VDDSOC电压get_clocks, //获取SD卡外设时钟env_init, //初始化环境变量init_baud_rate, //初始化波特率serial_init, //初始化串口console_init_f,display_options, //串口打印信息display_text_info, //打印调试信息print_cpuinfo, //打印CPU信息show_board_info, //打印板子信息,调用checkboard()INIT_FUNC_WATCHDOG_INIT //无效INIT_FUNC_WATCHDOG_RESET //无效init_func_i2c, //初始化I2Cannounce_dram_init,dram_init, //设置gd->ram_sizepost_init_f, //测试函数INIT_FUNC_WATCHDOG_RESETtestdram, //无效INIT_FUNC_WATCHDOG_RESETINIT_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(ram大小),gd->ram_top(ram最高地址)和gd->relocaddr(重定位后最高地址)reserve_round_4k, //gd->relocaddr 4字节对齐reserve_mmu, //留出MMU的TLB表位置reserve_trace,reserve_uboot, //留出重定位后uboot所占内存区域,大小由gd->monlen指定,留出的空间需4KB对齐并设置gd->start_addr_spreserve_malloc, //留出malloc区域,由宏TOTAL_MALLOC_LEN定义(默认16MB)reserve_board, //留出bd_t内存区setup_machine, //无效reserve_global_data, //留出gd_t内存区域reserve_fdt, //无效reserve_arch, //空reserve_stacks, //留出栈空间,如果使能IRQ还需要留出IRQ相应的内存setup_dram_config, //设置DRAM,告知linux DRAM起始地址和大小show_dram_config,display_new_sp, //显示新的sp位置INIT_FUNC_WATCHDOG_RESETreloc_fdt, //无效setup_reloc, //设置gd的其他成员变量NULL,};至此,board_init_f执行完成,最终的内存分配:

relocate_code / relocate_vectors
用于拷贝代码和重定位向量表。
board_init_r
用于初始化高级外设,包括EMMC、LCD、BSP芯片、网络等。
小结
总的来说,uboot的汇编启动阶段包括:
- 入口点:
_start(arch/arm/lib/vectors.S)设置异常向量表 - CPU模式切换:进入SVC模式,禁用IRQ/FIQ
- 关闭MMU/Cache,确保直接访问物理地址
- 初始化栈指针
- lowlevel_init(板级特定):
- 配置系统时钟
- DDR控制器初始化
- 代码重定位:将U-Boot从加载地址(如SRAM)拷贝到DDR目标地址
C语言初始化阶段(board_init_f)包括:
- 全局数据结构初始化:
gd_t和bd_t - 低级驱动初始化:
- 串口、GPIO、MMU、内核定时器
- DDR
- 重定位准备
- 计算uboot在DDR中的最终位置,预留内核、DTB、initrd、代码空间
- 执行重定位,更新代码地址相关引用(如全局变量指针)
- 设备树预解析:若使用DTS,加载并校验DTB
重定位后主流程(board_init_r)包括:
- 高级外设初始化:
- 以太网、SD、USB、EEPROM等
- 展开DTB,匹配驱动,设置硬件参数
- 环境变量加载:从存储介质读取
uEnv.txt或环境分区 - 用户交互
- 检测输入
- 加载zImage、设备树、initrd到指定内存地址
- 准备加载内核