U-Boot 之零 源码文件、启动阶段(TPL、SPL)、FALCON、设备树

阿里云国内75折 回扣 微信号:monov8
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6

  最近工作重心要从裸机开发转移到嵌入式 Linux 系统开发在之前的博文 Linux 之八 完整嵌入式 Linux 环境、交叉编译工具链、CPU 体系架构、嵌入式系统构建工具 中详细介绍了嵌入式 Linux 环境接下来就是重点学习一下 U-Boot。

  文中涉及的代码均放到了我个人的 Github 上https://github.com/ZCShou/BOARD-STM32F769I-EVAL大家可以直接拿来边学习边验证避免眼高手低。

开发环境

  前几天我将开发环境 Ubuntu 20.04 LTS + Arm GNU Toolchain 10.3 -2021.10 进行了升级升级后的开发环境及需要注意的问题如下所示本文后续内容同时对新 / 旧这两个环境进行了验证。
在这里插入图片描述

  1. 由于 Ubuntu 22.04 LTS 默认是标配 OpenSSL 3.x而旧版 U-Boot 使用的是 OpenSSL 1.x因此该环境编译旧版 U-Boot从 commit e927e21c07483337ffb63b828d4ddb5e0db342b2 开始添加了相关处理 将出现一堆警告
    在这里插入图片描述
  2. Arm GNU Toolchain 10.2-2022.02 存在 BUG导致编译 U-Boot 报错不要使用这个版本
    在这里插入图片描述
  3. 新版的 Arm GNU Toolchain 10.3 之后的版本在 Linux 上 GDB 需要 Python3.8。然而Ubuntu 22.04 默认的 Python 是 3.10。报错如下
    在这里插入图片描述
    解决方法就是直接手动安装 Python3.8 即可。 旧版的 Arm GNU Toolchain 10.3 -2021.10 不需要 Python 支持
    sudo add-apt-repository ppa:deadsnakes/ppa -y
    sudo apt install python3.8
    

起源

  U-Boot 起源于 Magnus Damm 编写的名为 8xxROM 的针对于 8xx PowerPC 的引导加载程序。1999 年 10 月Wolfgang Denk 将其在 SourceForge.net 开源由于该网站不允许项目名称以数字开头因此更名为 PPCBoot即 PowerPC Boot 的缩写。2000 年 7 月 19 日首次公开发布 PPCBoot-0.4.1。

  1. Wolfgang Denk 是 DENX Software Engineering GmbH简称 DENX 的创始人PPCBoot 实际属于 DENX 公司
  2. 因为 linus ➔ linux 所以 Denk ➔ DENX
  3. DENX 是一家致力于使用自由软件的公司

  随着 PPCBoot 被扩展到了 ARM 架构DENX 认为PPCBoot 这个名字已经不再合适于是在 PPCBoot−2.0.0 于 2002 年 10 月发布时决定将项目更名SourceForge.net 新建项目为 Das U-BootUniversal Boot Loader 的缩写其中的 Das 是一个德语定冠词官方说是为了创建一个双关语致敬经典的 1981 年德国潜艇电影 Das Boot。
在这里插入图片描述

除了官方有些文档称为 Das U-Boot外界几乎没人用这个名大家都是直接称呼为 U-Boot。

  PPCBoot−2.0.0 就对应 Das U-Boot 第一版 U−Boot-0.1.0 。并紧接着扩展到了 x86 处理器架构。然后在接下来的几个月中陆续增加了其他架构功能2003 年 3 月的 MIPS32、4 月份的 MIPS64、10 月份的 Nios II、12 月的 ColdFire 和 2004 年 4 月的 MicroBlaze。

Das U-Boot

  现在U-Boot 已经成为了是嵌入式设备首选的用于包装指令以引导设备操作系统内核的启动加载程序并且是基于 GPL 协议开源的项目地址https://source.denx.de/u-boot。它可用于许多计算机架构包括 68kARMBlackfinMicroBlazeMIPSNiosSuperHPPCRISC-V 和 x86。

  1. 代码仓库https://source.denx.de/u-boot
  2. Github 仓库https://github.com/u-boot/u-boot

  U-Boot 的代码结构及开发模式在尽可能的遵循 Linux Kernel 的方式但在实际过程中也存在差异。例如与 Linux Kernel 类似在每个版本发布之后将立即出现一个通常为 21 天的“合并窗口”然后在发布 rc1 后开始以修复错误为主但是由于 U-Boot 相对于 Linux kernel 简单很多因此它没有 linux 的 MonorepoMonotree 模式。

代码风格也是与 Linux Kernel 一样

  与 Linux 不同从 2008 年 10 月的版本开始U-Boot 版本的名称从没有更深层次含义的数字版本号更改为基于时间戳的编号通常格式 U-Boot vYYYY.MM.x其中YYYY 是年份如 2022MM 是月份如 08.x 可能没有如果存在这部分是 bug 修复版本如 1或者候选版本如 rc1。

  1. 自 2010 年 8 月起实际的 U-Boot 源代码树中不再有 CHANGELOG 文件但是可以使用 make CHANGELOG 命令从 Git 日志动态创建它
  2. 本文主要是使用 U-Boot v2021.10 和 U-Boot v2022.10 这两个版本

架构

  U-Boot 其实就是一个功能复杂一些的裸机程序这个程序最主要的一个功能就是传递内核参数跳转内核。当然除了跳转到内核U-Boot 本身还实现了其他一些功能U-Boot 命令以方便大家进行各种操作。
在这里插入图片描述
  U-Boot 支持多种架构的多种 CPU在众多支持的架构中ARM 是最麻烦的一个。因为 ARM 卖 IP 且市场占有率相当高导致产生了非常多的 ARM 核心的厂商这些厂商会有自己的改动进一步导致了 U-Boot 的 ARM 架构文件夹./arch/arm下有非常多的 mach-xxx 文件夹。

  此外在 U-Boot 中 board 这个词随处可见U-Boot 中一大部分代码都是与 board 相关的。CPUSoC、MCU 本身内部资源是有限的绝大多数情况内部资源都无法运行操作系统内核。所以U-Boot 必须要使用很多 CPU 外部的资源这些资源就在 board 上了。

文档

  大约在 2021 年 7 月份U-Boot 将文档统一迁移到了新版本整个文档系统的章节内容也进行了重新组织界面也焕然一新。新的在线文档地址是 https://u-boot.readthedocs.io/en/latest/旧版的文档已经无法访问了
在这里插入图片描述
  新的文档采用的 Sphinx 文档系统搭建的Sphinx 也是基于 Python 的使用的是 reStructuredText 语言格式文件扩展名通常是 .rst。现在文档系统也是向 Linux 看齐了。

  1. 个人认为U-Boot 的文档缺少对于总体架构的介绍缺少图示等直观的示例
  2. U-Boot 的文章仍然在逐步完善中代码仓库中有很多介绍文档但是在线文档系统中搜不到

  U-Boot 的文档就位于源代码的 doc 目录下。在源码根目录下不支持在 doc 目录下使用命令 make htmldocs 构建脚本就可在 doc 目录下自动建立 output 文件夹然后该目录下生成 HTML 格式的文档直接使用浏览器打开 index.html 即可。
在这里插入图片描述
  由于我的 Ubuntu 22.04 LTS 中的 Sphinx 升级到了 5.0.1 版其配置与旧版有些不兼容U-Boot 目前还没有适配导致无法生成 pdf、epub 等格式的文档。

  1. 最小依赖工具 sudo apt install python3-pipsudo pip install -U Sphinx sphinx_rtd_themesudo apt install imagemagick如果构建其他格式的文档如 PDF还需要安装 texlive* 等依赖
  2. 根据构建规则必须先 make xxx_defconfig 后才能正常运行后面的 make 命令否则报错找不到 .config 文件

源码

  源代码可以直接使用 Git 命令 git clone https://source.denx.de/u-boot/u-boot.git 来获取。也可以通过 U-Boot 在 Github 上的镜像仓库来获取 git clone https://github.com/u-boot/u-boot。我们通常是使用某个特定的 Tags 版本git checkout v2020.10
在这里插入图片描述
  U-Boot 的源码的结构基本也是向 Linux 看齐其中部分代码就来自于 Linux kernel只不过没有 Linux 代码那么复杂。如今U-Boot 源码每天都有大量变更最新的版本有 1 万 4 千多个文件近 300 万行代码。源码中各文件的层级结构可以参考下图
在这里插入图片描述

目录文件

下面是对 U-Boot 源代码中各个目录的一个简介

  • api 供外部应用程序使用的与架构或设备无关的 API。例如标准化输入输出显示网络 API、存储 API 等为 CMD 提供支持
  • arch 特定于架构的源码文件。实现了不同体系结构的 CPU指令集、设备树底层抽象利用链接绑定实现了符号入口相对位置保持不变故才能实现将内核镜像拷贝到内存然后进行引导的功能
    • arc 通用的架构文件
    • arm ARM 架构
      • lib 实现了初始化 C 运行时环境栈/堆指针等的初始化
      • dts 实现了设备树的底层体系架构依赖的具体抽象剥离
      • cpu 不同的 ARM 指令集的 CPU 分开处理
      • mach-xxx 由于同样的内核相同各家芯片外设都不尽相同所以将各自个性实现剥离实现于此这主要体系在 ARM 体系的芯片由于 ARM 公司售卖 IP各家芯片厂商在内核的基础上延伸出各自不同的芯片所以需要将差异性剥离实现
    • m68k m68k 架构
    • microblaze microblaze 架构
    • mips MIPS 架构
    • nds32 NDS32 架构
    • nios2 Altera NIOS2 架构
    • powerpc PowerPC 架构
    • riscv RISC-V 架构
    • sandbox 独立于硬件的 “sandbox” 模式。U-Boot 可以使用“沙盒”板在 Linux 主机上运行。这允许在原生平台上进行不特定于主板或架构的特性开发。沙盒还用于运行 U-Boot 的一些测试。
    • sh SH 架构
    • x86 x86 架构
    • xtensa Xtensa 架构
  • board 开发板依赖文件实现了产业链下游设备厂商的差异性对于产品设计而言需要将各自在 boot 阶段需要严格初始化的实现放在这里比如 IO 口的初始化产品中大部分 IO 口必须显式设置其初始状态
  • boot images and booting 文件
  • cmd U-Boot 命令相关接口
  • common 与架构无关的一些通用文件. 是 U-Boot 主体如系统停留在 U-Boot 阶段CPU 始终在执行一个死循环run_main_loop()
  • configs 开发板默认的配置文件。格式均为开发板名_defconfig
  • disk 磁盘驱动器分区处理的代码.实现了轻量级磁盘管理
  • doc 该目录下是 U-Boot 的文档现在使用的是 Sphinx 文档系统。Sphinx 也是基于 Python 的使用的是 reStructuredText 语言格式文件扩展名通常是 .rst
      Sphinx 文档系统使用 make 命令来生成发布的文档可以生成 html、pdf 等格式。例如在 源码目录/documentation/ 下执行 make html 命令就会生成一个 _build 的目录其中就包含了生成的文档。
  • drivers 设备驱动这里实现了boot阶段必要的设备驱动如网口、显示等
  • dts 实现了设备树.用于构建 内部 U-Boot fdt 的 Makefile
  • env 环境支持
  • examples 示例代码
  • fs 文件系统代码cramfs、ext2、jffs2等
  • include 头文件
  • lib 通用于所有架构的库例程。比如 CRC 算法加密算法压缩算法字符串操作等
  • Licenses 各种许可证文件
  • net 网络代码实现网络协议层
  • post 上电自检
  • scripts 各种构建脚本和 Makefile 文件。跟 make menuconfig 配置界面的图形绘制相关的文件我们作为使用者无需关心这个文件夹的内容
  • test 各种单元测试文件
  • tools 里面包含一系列构建 U-Boot 使用的工具的源代码

源文件过滤

  由于 U-Boot 源码文件众多而具体到某一平台开发板之后其中的大多数文件我们根本不需要。为了学习的方便剔除无用文件仅仅保留我们需要的文件对于我们学习将有很大帮助。如果可以正常整理出需要的源代码那基本对于 U-Boot 的文件结构掌握差不多了。以下是在 VSCode 查看代码时的过滤配置

{
    "files.exclude": {
        "**/.git": true,
        "**/.svn": true,
        "**/.hg": true,
        "**/CVS": true,
        "**/.DS_Store": true,
        "u-boot-v2021.10":true,
        // arch
        "**/mips": true,
        "**/powerpc": true,
        "**/riscv": true,
        "**/ti": true,
        "**/x86": true,
        "**/sandbox": true,
        "**/arch/{arc,m68k,microblaze,mips,nios2,powerpc,riscv,sandbox,sh,x86,xtensa,um,sparc,s390,parisc,openrisc,nds32,ia64,hexagon,h8300,csky,arm64,alpha}": true,
        // cpu
        "**/arch/arm/cpu/{arm11,arm720t,arm920t,arm926ejs,arm946es,arm1136,arm1176,armv7,armv8}": true,
        // machine
        "**/arch/arm/mach-[^s]*": true,
        "**/arch/arm/mach-s[^t]*": true,
        "**/arch/arm/mach-st[^m]*": true,
        "**/arch/arm/mach-stm32[$^m]*": true,
        // dts
        "**/dts/[^s|^M|^i|^a|^d|^.]*": true,
        "**/dts/d[^t]*": true,
        "**/dts/i[^n]*": true,
        "**/dts/in[^c]*": true,
        "**/dts/a[^r]*": true,
        "**/dts/ar[^m]*": true,
        "**/dts/arm[^v]*": true,
        "**/dts/s[^t]*": true,
        "**/dts/st[^m|^-]*": true,
        "**/dts/stm32[^f]*": true,
        "**/dts/stm32f[^7]*": true,
        "**/dts/stm32f7[^6|4|^-]*": true,
        "**/dts/stm32f769-[^e|d|p]*": true,
        // configs
        "**/configs/[^s]*": true,
        "**/configs/s[^t]*": true,
        "**/configs/st[^m]*": true,
        "**/configs/stm[^3]*": true,
        // "**/configs/stm3[^2]*": true,
        "**/configs/stm32[^f|^_]*": true,
        "**/configs/stm32f[^7]*": true,
        // "**/configs/stm32f7[^6|^4]*": true,
        // "**/configs/stm32f769-[^e]*": true,
        // board
        "**/board/[^s]*": true,
        "**/board/s[^t]*": true,
        "**/board/ste": true,
        "**/board/sto*": true,
        "**/board/st/st[^m]*": true,
        "**/board/st/stm32[^f]*": true,
        // "**/board/st/stm32f[^7]*": true,
        // "**/board/st/stm32f7[^6|^4]*": true,
    }
}

  由于 U-Boot 很多文件是编译过程中产生的如何过滤有效文件是个问题。我在网上看到有个网友搞了一个可以根据编译过程提取源代码的脚本https://github.com/tonyho/Generate_Kernel_Uboot_Project_forIDE但是经过我尝试发现并不是很准确但基本可以用。

配置

  U-Boot 源码的配置使用的是 Linux 系统的 Kconfig 配置系统详细的配置过程说明见独立博文 U-Boot 之四 构建过程Kconfig 配置 + Kbuild 编译详解 。所有 U-Boot 支持的开发板都会在 ./configs 目录下有个默认的配置文件其中的配置项介绍见独立博文U-Boot 之六 最全配置项CONFIG_BOARD、CONFIG_SYS详解
  
  U-Boot 对于众多架构的支持已经到达了开发板级别。对于一些常用的开发板U-Boot 直接实现了对他们的支持也就意味着 U-Boot 可以直接在这些开发板上运行。需要注意开发板可能需要改动才能使用所有资源。

构建

  U-Boot 源码的构建过程使用的是 Linux 系统的 Kbuild 构建系统Kbuild 详细说明见独立博文 U-Boot 之四 构建过程Kconfig 配置 + Kbuild 编译详解 。详细的构建过程见独立博文 U-Boot 之一 零基础编译 U-Boot 过程详解、Image 镜像介绍及使用说明、DTB 文件使用说明简要构建过程如下所示

  1. 获取源代码git clone https://github.com/ZCShou/BOARD-STM32F769I-EVAL.git
  2. 生成配置ARCH=arm CROSS_COMPILE=arm-none-eabi- make O=build_stm32 stm32f769-eval_defconfig
  3. 裁剪ARCH=arm CROSS_COMPILE=arm-none-eabi- make O=build_stm32 menuconfig
  4. 编译ARCH=arm CROSS_COMPILE=arm-none-eabi- make O=build_stm32 -j$(nproc)
  5. 将生成的 Image 烧录到开发板验证

调试参数

  在博文 U-Boot 之一 零基础编译 U-Boot 过程详解、Image 镜像介绍及使用说明、DTB 文件使用说明 中我有详细的介绍这里就不多说了。下面重点介绍几个在构建中有用的配置或者说参数。

  1. O=<dir>在构建过程中指定该参数就可以将构建过程中生成的文件全部包括 .config放到 <dir> 这个目录下从而避免对源码文件产生污染。需要注意的是每个命令都必须加该参数。
  2. V=1输出构建过程中的详细信息。开启后输出内容非常多。
  3. NO_LTO=1禁用 LTOLink-time optimisation。U-Boot 支持链接时间优化这可以减少最终 U-Boot 二进制文件的大小。目前ARM 板可以通过在 defconfig 文件中添加 CONFIG LTO=y 来启用这一功能。不支持其他体系结构。

    LTO 默认为沙盒启用。

XIP

  构建镜像文件时XIP 也是我们遇到的一个比较重要的概念。XIP 是 Execute In Place 的缩写表示的含义就是生成的 Image 可以在 Nor FLASH 上直接运行而无需要复制到内存运行。
在这里插入图片描述
  此外在 Linux Kernel 编译的时候也是有 XIP 配置项的如果我们选择了 XIP 功能则会专门有 xipImage 这种 Image 文件。当然一般芯片都没有足够的内部 FLASH 来存放 Linux Kernel 镜像文件所以很少用。

启动阶段

  为了适用于各种 CPUU-Boot 本身的启动阶段划分为了多个不同的阶段TPLVPLSPLU-Boot。在源码代码设计中SPL 其实是一个框架TPLVPL 都属于 SPL 框架中的一部分。
在这里插入图片描述

TPL

  TPLTertiary Program Loader第三段程序加载器 用于早期的初始化并且尽可能的小它负责加载 VPL 或 SPL。根据官方文档TPL 本身属于 SPL 的精简代码就在 SPL 代码中通过宏 CONFIG_TPL_BUILD 来区分而且现在只有 powerpc 的 mpc85xx 有这个要求并将实现它。

VPL

  VPL 是一个可选的安全启动验证过程。从裸机功能上来看VPL 是一个独立的过程负责校验 A/B 两个 SPL并选择正确的来执行在代码实现上VPL 也是 SPL 中的一部分。目前VPL 的细节还在设计实现中现在它会直接跳转到 SPL。
在这里插入图片描述

SPL

  SPLSecondary Program Loader第二段程序加载器这里的第二段程序其实就是指的 U-Boot也就是SPL 是第一段程序优先执行然后他再去加载 U-Boot。那么 U-Boot 本身已经是一个bootloader了为啥要有 SPL 这个东西的存在呢

  这个主要原因是对于一些 MCU 来说它的内部 SRAM 可能会比较小小到无法装载下一个完整的 U-Boot 镜像那么就需要 SPL它主要负责初始化外部 RAM 运行环境并加载真正的 U-Boot 镜像到外部 RAM 中来执行。

用不用 SPL 取决于自己的芯片如果资源充足则可用可不用

  SPL 的对象文件被独立构建并放置在 spl 目录中。这里需要注意构建 SPL 时会使用 fdtgrep 工具过滤掉设备树中的一些属性从而生成一个比较小的设备树文件 spl/u-boot-spl.dtb

Falcon Mode

  Falcon Mode 就是指的由 SPL 直接启动操作系统内核这种模式主要用于减少 Bootloader 阶段的时间。U-Boot 本身提供了很多功能对于某些芯片应用环境来说U-Boot 的众多功能基本不会被使用。
在这里插入图片描述

U-Boot

  U-Boot 阶段包含完整的 U-Boot 功能例如引导逻辑、各种 U-Boot 命令。

设备树

  U-Boot 使用与 Linux Kernel 相同的设备树但是引导加载器环境与 Linux Kernel 的需求并不一样因此U-Boot 添加了一些必要的东西。添加的方式不是更改原有的 Linux Kernel 设备树而是引入了 *-u-boot.dtsi 的 U-Boot 专用文件。U-Boot 增加的 *-u-boot.dtsi 并不会被其他任何 *.dts 文件件所引用而是直接在 Makefile 文件中引入。
在这里插入图片描述
  在编译某个 *.dts 文件时U-Boot 会自动在要编译的 *.dts 所在目录下查找并包含一个只会包含一个 *-u-boot.dtsi*.dts 中包含的优先级由高到低如下:

<orig_filename>-u-boot.dtsi    # <orig_filename> 就是 .dts 对应的名字
<CONFIG_SYS_SOC>-u-boot.dtsi
<CONFIG_SYS_CPU>-u-boot.dtsi
<CONFIG_SYS_VENDOR>-u-boot.dtsi
u-boot.dtsi

  在实际使用中有时候我们必须要为自己的开发板指定一个私有 xxx.dtsi而这个 xxx.dtsi 通常不能公开因此直接编辑 *.dts 包含 #include xxx.dtsi 是不可取的。此时U-Boot 提供了 CONFIG_DEVICE_TREE_INCLUDES 这个配置项来指定我们自己的私有 xxx.dtsi

  设备树源文件被最终编译为二进制的 DTB 文件原始的 DTB 文件位于 arch/arm/dts/xxx.dtb 下面构建系统会复制到 ./dts/dt.dtb进一步重命名为 ./u-boot.dtb。u-boot 支持两种形式将 dtb 编译到 u-boot 的镜像中

  • dtb 和 u-boot 的 bin文件分离

    • 需要打开 CONFIG_OF_SEPARATE 宏来使能。
    • 在这种方式下u-boot 的编译和 dtb 的编译是分开的先生成 u-boot 的 bin 文件然后再另外生成dtb 文件。
    • dtb 最终会自动追加到 u-boot 的 bin 文件的最后面。因此可以通过 u-boot 的结束地址符号也就是 _end 符号来获取 dtb 的地址。
  • dtb 集成到 u-boot 的 bin 文件内部

    • 需要打开 CONFIG_OF_EMBED 宏来使能。
    • 在这种方式下在编译 u-boot 的过程中也会编译 dtb。
    • 最终 dtb 是包含到了u-boot 的 bin 文件内部的。dtb 会位于 u-boot 的 .dtb.init.rodata 段中并且在代码中可以通过 __dtb_dt_begin 符号获取其符号。

    官方不推荐这种方式建议仅用于调试

  • 另外也可以通过 fdtcontroladdr 环境变量来指定 dtb 的地址。可以通过直接把 dtb 加载到内存的某个位置并在环境变量中设置 fdtcontroladdr 为这个地址达到动态指定 dtb 的目的。

参考

  1. U-Boot 官方文档
阿里云国内75折 回扣 微信号:monov8
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6