在之前的博文 Linux 之八 完整嵌入式 Linux 环境介绍及搭建过程详解 中我们说了要一步步搭建整个嵌入式 Linux 运行环境,今天就开始编译 U-Boot。我所使用的硬件平台及整个要搭建的嵌入式 Linux 环境见博文 Linux 之八 完整嵌入式 Linux 环境介绍及搭建过程详解,这里的编译都是基于以上环境的,就不过多说明了。
这篇博文我们仅仅关注编译过程本身,想要吃透 U-Boot,有太多东西需要学习!最开始我想放到一篇文章中,写着写着内容越来越多,最终超过了 CSDN 编辑器的限制。。。最终决定把内容拆分成多篇文章。你可能需要:
- U-Boot 之三 U-Boot 源码解析及移植过程详解
- U-Boot 之四 配置构建过程(Kconfig、Kuild)详解
- U-Boot 之五 详解 U-Boot 及 SPL 的启动流程(代码分析)
编译过程
编译环境
Ubuntu 20.04.3 LTS 准备就绪之后,还有一些工具需要安装。第一个就是 GCC,GCC 就使用 Ubuntu 自带的 9.3.0 版即可。在编译 U-Boot 的过程中,我们还需要安装其他一些依赖工具,这个在后面用到的时候缺啥装啥就可以(出现各种错误的时候再安装相应工具即可)。
U-Boot 本身没有提供对于我使用的 STM32F769-EVAL 板子的支持,我这里就是用它支持的 STM32f769-Disco 板子来进行编译。如果不出意外,编译之后运行肯定会有问题,到时候我们在一个一个解决问题即可。详细内容可以参考:U-Boot 之三 U-Boot 源码解析及移植过程详解
这里介绍一下安装依赖包的一些方法。正常我们应该是先使用 find
命令查找依赖是否存在。因为存在一种情况是,依赖文件本身存在但是没有在环境变量 LD_LIBRARY_PATH 里,如果不存在直接安装即可。
- 直接安装的情况。一般使用命令
sudo apt update
->apt-cache search xxxx
->sudo apt install xxxx
,具体示例(仅仅是个示例,安装了没啥用哈)如下:
这样我们可以查看响应的依赖包的具体说明,然后根据需要来安装。 - 没有在环境变量 LD_LIBRARY_PATH 里的情况,目前有两种方法:
- 就是依赖的动态库拷贝到上面执行的可执行命令时显示被找到的库的目录下。
- 把依赖的动态库所在的目录添加到环境变量 LD_LIBRARY_PATH 里:
export LD_LIBRARY_PATH=/path_to_lib/:/lib/x86_64-linux-gnu/:$LD_LIBRARY_PATH
过程
在开始编译之前,我们先介绍两个命令:make clean
用于清空编译中间文件 和 make distclean
用于清除所有编译产生的文件。如果我们想要重新编译,可以使用以上两个命令清理环境(这两个命令在 U-Boot 根目录的 Makefile 中有详细的定义,感兴趣的可以自行查看)。
-
第一步肯定是获取 U-Boot 的源代码,我这里直接使用了当前最新存档版:
u-boot-2021.10.tar.bz2
。这里需要重点注意,我最开始直接使用 Git 获取了最新的源代码,结果编译之后运行直接 HardFault,分析好久没找到原因,最后决定使用一个稳定发布版试试,结果没有问题。。。
成功下载并解压源代码之后(注意我这里将解压后的文件名命名为了 u-boot),我们需要进入 u-boot 目录下,使用命令:cd u-boot
。此后就在 u-boot 目录下进行各种操作。 -
第二步就是生成配置,直接使用命令:
make stm32f769-disco_defconfig
。不出意外的话会出现以下错误:-
/bin/sh: 1: bison: not found
这个错误是由于我们没有安装 bison 这个工具。Ubuntu 下直接使用命令:sudo apt iinstall bison
即可。 -
/bin/sh: 1: flex: not found
这个错误是由于我们没有安装 flex 这个工具。Ubuntu 下直接使用命令:sudo apt iinstall flex
即可。 -
正常完成如下所示:
-
-
第三步修改配置(裁剪)。直接使用命令:
make menuconfig
。不出意外的话会出现以下错误:-
Unable to find the ncurses package
这个错误是由于我们没有安装 ncurses 这个工具。Ubuntu 下直接使用命令:sudo apt iinstall libncurses-dev
即可。安装成功之后,重新make menuconfig
,就会进入下面的界面:
我们需要做的就是选择其中的 SPL / TPL 菜单项,然后回车,在其中下翻页找到Activate Falcon Mode
项,将选择去掉。
其中还有很多项,这个就需要根据自己需要来具体进行裁剪了。当然有个前提是,修改了配置很大可能还需要配套修改对应的源代码,因为之所以修改肯定是为了适应自己开发板。 -
Your display is too small to run Menuconfig!
这个错误提示很明显,就是终端界面太小,把终端拖大一些就好了。
-
Unable to find the ncurses package
-
第四步就是真正的编译了,直接使用命令:
CROSS_COMPILE=arm-none-eabi- ARCH=arm make -j8
不出意外的话会出现以下错误:-
/bin/sh: 1: arm-none-eabi-gcc: not found
这个错误是由于我们没有安装 GCC for ARM 导致的。也就是没有编译 U-Boot 使用的编译器。解决方法也非常简单,就是安装 GCC for ARM 即可。这里有个需要注意的点,如果大家搜索 Ubuntu 下安装 GCC for ARM,很多文章都过推荐使用 apt 命令来安装,类似于:
sudo apt-get install gcc-arm-none-eabi
,这个版本并不是最新的(貌似使用这个旧版本也可以,我选择了使用最新版)。更重要的是,ARM 之前已经宣布不再更新 Launchpad 上的 GCC for ARM 了(具体见 Launchpad 上的说明)。其只在官网提供编译好的压缩包及源代码的压缩包。这里简单来讲解一下直接从 ARM 官网下载压缩包的安装方法:
- 下载最新版的 Linux x86_64 Tarball。目前最新的是 gcc-arm-none-eabi-10.3-2021.10-x86_64-linux.tar.bz2 。GCC for ARM 下载地址:https://developer.arm.com/tools-and-software/open-source-software/developer-tools/gnu-toolchain/gnu-rm/downloads
- 将压缩包解压。我这里将其解压到了
/usr/share/
目录下。直接使用命令:tar -xjvf gcc-arm-none-eabi-10.3-2021.10-x86_64-linux.tar.bz2 -C /usr/share/
。等待解压完成,/usr/share/gcc-arm-none-eabi-10.3-2021.10
目录下就是最新的 GCC for ARM 的各种可执行程序、库等文件了。 - 此时我们需要将
/usr/share/gcc-arm-none-eabi-10.3-2021.10
添加到系统环境变量这样才能正常在终端中使用各种命令。具体有两种方法(我采用了第二种):- 第一种是创建连接
sudo ln -s /usr/share/gcc-arm-none-eabi-your-version/bin/arm-none-eabi-gcc /usr/bin/arm-none-eabi-gcc sudo ln -s /usr/share/gcc-arm-none-eabi-your-version/bin/arm-none-eabi-g++ /usr/bin/arm-none-eabi-g++ sudo ln -s /usr/share/gcc-arm-none-eabi-your-version/bin/arm-none-eabi-gdb /usr/bin/arm-none-eabi-gdb sudo ln -s /usr/share/gcc-arm-none-eabi-your-version/bin/arm-none-eabi-size /usr/bin/arm-none-eabi-size
- 第二种是在
.bashrc
文件(在 Linux 系统普通用户目录(cd /home/xxx)或 root 用户目录(cd /root)下,用指令ls -al
可以看到该隐藏文件,一般使用用户目录下的即可)最后面增加以下内容:# >>> ARM >>> export PATH="$PATH:/home/user/path/to/gcc-arm-none-eabi-10-2020-q4-major/bin/" # <<< ARM <<<
- 第一种是创建连接
- 可能需要重启我们之前已经打开的终端以上配置才会生效。
这里有一点需要注意,通过压缩包安装不能解决依赖关系,需要我们自己运行尝试,看看少啥安啥。目前已知的依赖是
ncures5
(会报错:error while loading shared libraries: libncurses.so.5),其需要安装sudo apt install libncurses5
(貌似应该是需要 32 位的)。
至于需不需要其他的依赖,大家自行尝试,我这里是没有提示需要其他任何组件。-
fatal error: openssl/evp.h:
这个错误主要是由于 U-Boot 代码使用了 openssl 中的相代码,而我们的环境中没有安装 openssl。Ubuntu 下直接使用命令:sudo apt install libssl-dev
即可。
-
-
正常编译完成之后。我们需要的文件就有了。我们真正需要的是根目录下的
u-boot.bin
和 spl 目录下的u-boot-spl.bin
。 -
其他一些问题。我在试用
make CHANGELOG
命令时出现错误 unrecognized command line option ‘-mno-unaligned-access’,不知道为啥,也没找到如何解决。
使用说明
经过上面的编译之后,我们需要的 U-Boot 就编译出来了。U-Boot 编译完成后实际包含两部分:spl/u-boot-spl.bin
和 u-boot.bin
。下图是针对 STM32F769 的一个布局映射图:
-
spl/u-boot-spl.bin
负责初始化 sdram,配置时钟,引导加载 U-Boot。需要将 SPL 烧写在 0x08000000 地址,即內部 flash首地址 -
u-boot.bin
是 uboot 主程序,负责 relocate 向量表及自身。并完成开发板的基本初始化,然后引导内核。需要将 SPL 烧写在 0x08008000 地址。
烧写完之后重启,就会看到如下信息:
U-Boot SPL 2021.10 (Dec 24 2021 - 20:53:15 +0800)
Trying to boot from XIP
U-Boot 2021.10 (Dec 24 2021 - 20:53:15 +0800)
Model: STMicroelectronics STM32F769-DISCO board
DRAM: 16 MiB
stm32fx_rcc_clock rcc@40023800: set_rate not implemented for clock index 4
stm32fx_rcc_clock rcc@40023800: set_rate not implemented for clock index 4
stm32fx_rcc_clock rcc@40023800: set_rate not implemented for clock index 4
Flash: 1 MiB
MMC: sdio2@40011c00: 0
Loading Environment from nowhere... OK
In: serial
Out: serial
Err: serial
Net:
Warning: ethernet@40028000 (eth0) using random MAC address - fa:d8:b5:97:81:15
eth0: ethernet@40028000
Hit SPACE in 3 seconds to stop autoboot.
MMC: no card present
U-Boot >
Flash: 1 MiB
通过 STM32F769 的手册可以知道,它的 FLASH 大小是 2M,这里 U-Boot 显示为 1M,明显是不对的!关于 FLASH 的初始化可以在文件:drivers\mtd\stm32_flash.c
找到如下接口:
unsigned long flash_init(void)
{
unsigned long total_size = 0;
u8 i, j;
for (i = 0; i < CONFIG_SYS_MAX_FLASH_BANKS; i++) {
flash_info[i].flash_id = FLASH_STM32;
flash_info[i].sector_count = CONFIG_SYS_MAX_FLASH_SECT;
flash_info[i].start[0] = CONFIG_SYS_FLASH_BASE + (i << 20);
flash_info[i].size = sect_sz_kb[0];
for (j = 1; j < CONFIG_SYS_MAX_FLASH_SECT; j++) {
flash_info[i].start[j] = flash_info[i].start[j - 1]
+ (sect_sz_kb[j - 1]);
flash_info[i].size += sect_sz_kb[j];
}
total_size += flash_info[i].size;
}
return total_size;
}
其中,宏 CONFIG_SYS_MAX_FLASH_BANKS
很重要。在前面的移植章节我们已经说过,STM32F769 是复用了 STM32F746 的相关文件的。那么,最终我们会在在 include\configs\stm32f746-disco.h
中找到该宏的定义:
对比这个 STM32F769 和 STM32F746 这两个 CPU,STM32F746 只有一个 BANK,但是 STM32F769 却有两个 BANK。那么是不是把他改成 2 就可以了呢?答案是不可以!我们继续分析上面的代码。
我们再看第二个宏值 CONFIG_SYS_MAX_FLASH_SECT
,它被定义为 8,接下来的 for 循环就是把这 8 个扇区的大小加起来,每个扇区的值放在了 arch\arm\include\asm\arch-stm32f7\stm32.h
文件中的 sect_sz_kb
变量中,如下所示:
static const u32 sect_sz_kb[CONFIG_SYS_MAX_FLASH_SECT] = {
[0 ... 3] = 32 * 1024,
[4] = 128 * 1024,
[5 ... 7] = 256 * 1024
};
这个就很清楚了,就是 SMT32 的 FLASH 的扇区大小分配。知道了代码的实现,我们再来看看 STM32 手册手册中对于 FLASH 的定义,如下图所示:
通过上面的图我们可以看到,STM32F769 的 FLASH 本身支持单 BANK 和 双 BANK 模式,不过双 BANK 模式,每个扇区的大小与 单 BANK 不一样。默认情况是 单 BANK 模式的。因此,我们最终的处理方案是 CONFIG_SYS_MAX_FLASH_BANKS
保持为 1,把宏 CONFIG_SYS_MAX_FLASH_SECT
改为 12 ,并且必须要修改 sect_sz_kb
。
Warning: ethernet@40028000 (eth0) using random MAC address
每块网卡都有一个MAC 地址,MAC 地址是一个 6 字节(48bit)的数据。前 3 字节称为 OUI,是由 IEEE 组织注册给网络设备生产商的;每个厂商拥有一个或多个 OUI,彼此不同。后三字节则是由网络设备生产商分配给自己生产的每一个拥有 MAC 地址的设备,互不重复。
U-Boot 在启动过程中提示使用了随机 MAC 地址,这是因为如果 MAC 地址相同的两块开发板在同一局域网中,会互相影响。那么,如果我们想要自己固定一个固定的 MAC 地址应该如何操作呢?如下图所示默认开启了不设置 MAC 地址时使用随机地址:
知道了为何会有这个随机 MAC 地址后,我们就可以根据源代码针对性进行修改。这里我提供我所使用的的两种方法:
- 环境变量中设置 MAC 地址。具体方法是:找到
.\include\configs\stm32f746-disco.h
(注意替换自己板子使用的头文件),然后在以下内容中新增"ethaddr=ea:2f:4b:f7:ac:ab\0" \
:#include <config_distro_bootcmd.h> #define CONFIG_EXTRA_ENV_SETTINGS \ "kernel_addr_r=0xC0008000\0" \ "fdtfile=stm32f746-disco.dtb\0" \ "fdt_addr_r=0xC0408000\0" \ "scriptaddr=0xC0418000\0" \ "pxefile_addr_r=0xC0428000\0" \ "ramdisk_addr_r=0xC0438000\0" \ "ethaddr=ea:2f:4b:f7:ac:ab\0" \ BOOTENV
- 修改设备树,增加 MAC 地址。体方法是:找到
.\arch\arm\dts\stm32f7-u-boot.dtsi
(注意替换自己板子使用的设备树),然后在以下内容中新增local-mac-address = [ea 2f 4b f7 ac ab];
:mac: ethernet@40028000 { compatible = "st,stm32-dwmac"; reg = <0x40028000 0x8000>; reg-names = "stmmaceth"; clocks = <&rcc 0 STM32F7_AHB1_CLOCK(ETHMAC)>, <&rcc 0 STM32F7_AHB1_CLOCK(ETHMACTX)>, <&rcc 0 STM32F7_AHB1_CLOCK(ETHMACRX)>; interrupts = <61>, <62>; interrupt-names = "macirq", "eth_wake_irq"; local-mac-address = [ea 2f 4b f7 ac ab]; snps,pbl = <8>; snps,mixed-burst; pinctrl-0 = <ðernet_mii>; phy-mode = "rmii"; phy-handle = <&phy0>; status = "okay"; mdio0 { #address-cells = <1>; #size-cells = <0>; compatible = "snps,dwmac-mdio"; phy0: ethernet-phy@0 { reg = <0>; }; }; };
我们需要注意的是,如果选择了手动修改 MAC 地址,必须自己保证 MAC 不能重复。一个比较常用的方法是:网卡生产商的 OUI + 芯片的唯一 ID 组成 MAC。注意,部分网卡中会有专门记录 MAC 地址的地方,我们只需要读取即可。
set_rate not implemented for clock index 4
这个其实并不是个错误,应该算是源代码中的一个 BUG,通过搜索该提示文字关键字,我们最终可以找到函数 static ulong stm32_set_rate(struct clk *ckl, ulong rate)
,如下图所示:
由于 stm32_set_rate
被调用了多次,而每次调用第一个 if 条件存在不成立的情况,因此就会一直打印该提示信息。
MMC: no card present
STM32F769-EVAL 开发板上是有 SD 卡的。但是这里显示 没有卡。不出意外的话,这里是由于 STM32F769-Disco 与 STM32F769-EVAL 在这方面配置不同导致。下图是两款开发板关于 SD 卡的说明:
从中我们可以看出,两款开发板使用的 SDMMC 并不相同!EVAL 开发板有两个 SD 卡插槽:SD1 -> SDMMC1
,SD2 -> SDMMC2
。而 Discovery 板子只有一个 SD卡插槽:SD -> SDMMC2
。关键在于 SDMMC2 的管脚使用是不一样的!这里我们需要更改对应的引脚。
SD 目前还是有问题,后续再解决一下!
参考
- https://askubuntu.com/questions/1243252/how-to-install-arm-none-eabi-gdb-on-ubuntu-20-04-lts-focal-fossa
- https://james-hui.com/2021/07/02/building-a-small-uboot-linux-and-rootfs-for-arm-cortex-m7/
- https://www.cnblogs.com/dylancao/p/8621789.html
- https://wowothink.com/1e031f74/
- https://blog.csdn.net/linuxweiyh/article/details/99331659
- https://loee.xyz/2021/04/27/uboot-%E6%B5%81%E7%A8%8B%E5%88%86%E6%9E%90/
- https://www.twblogs.net/t/5d26d62ebd9eee1ede06f20d
- https://mrchen.love/Article/ID/57
- https://blog.csdn.net/weixin_39890452/article/details/114470827
- https://www.cnblogs.com/cslunatic/archive/2013/03/28/2986146.html
- https://wowothink.com/146db8db/
- https://www.pianshen.com/article/70672050376/
- https://my.oschina.net/u/4232364/blog/3134261
- https://adrianalin.gitlab.io/popsblog.me/posts/build-linux-for-stm32f769i-disco-using-buildroot/