Linux与uboot开发分析

news/发布时间2024/5/22 6:46:29
Linux与uboot开发分析
7.1. 嵌入式 Linux 环境
  嵌入式 Linux 环境与熟悉的 PC 环境还是有很大区别的,要搭建出一套完整的嵌入式 Linux 环境需要做的工作相当多。图7.1表示一个嵌入式 Linux 环境示意图:
 
图7.1. 嵌入式 Linux 环境示意图
BootLoader:通常使用的是 U-Boot,就是一个复杂点的裸机程序。与通常编写的裸机程序(例如 ARM 架构)没有本质区别。BootLoader 需帮助内核实现重定位,BootLoader 还要给内核提供启动参数。
BootLoader 是无条件启动的,从零开始启动的。
Linux Kernel: 本身也是一个复杂的裸机程序。与裸机程序相比,内核运行起来后,在软件上分为内核层和应用层,分层后两层的权限不同,内存访问和设备操作的管理上更加精细(内核可以随便访问各种硬件,而应用程序只能被限制地访问硬件和内存地址)。
内核是不能开机自动完全从零开始启动的,需要 BootLoader 帮忙。
裸机程序编译工具链: 这个编译工具链(名字是自己起的)编译出特定于架构的纯裸机程序以在指定架构上运行。一般由内核厂家提供,最为熟知的就是 ARM 提供的 GNU Arm Embedded Toolchain 中的 arm-none-eabi-*、Keil 中的 armcc、IAR。
通常采用交叉编译。
linux 程序编译工具链: 这个编译工具链(名字是自己起的)用于编译出在嵌入式 Linux 环境中运行的用户程序。通常这个编译工具链需要根据自己的嵌入式 Linux 环境自己编译。如果是直接买的 SoC 或者开发套件,SoC 或者开发套件厂家会自己编译好然后提供给客户。
通常采用交叉编译。
自己编译出来的 linux 程序编译工具链通常会被称为 SDK。嵌入式 linux 编译套件往往不是通用的!
linux 程序编译工具链也可以编译裸机程序,例如编译裸机 U-Boot、Linux Kernel。 但是通常不会使用 linux 程序编译工具链来进行纯裸机开发。
文件系统: 其中主要就是根文件系统(RootFS),包括 Linux 启动时所必须的目录和关键性的文件,例如,Linux 启动时都需要有 init 目录下的相关文件。Linux 启动时,第一个必须挂载的是根文件系统,若系统不能从指定设备上挂载根文件系统,则系统会出错而退出启动。成功之后可以自动或手动挂载其他的文件系统。
根文件系统是第一个需要使用自己的编译套件来编译的程序。
用户 APP: 这个就是嵌入式 Linux 中运行的应用程序了。肯定是需要使用自己制作的 linux 程序编译工具链来产生。
  以上就是完整的嵌入式 Linux 环境各个部分的基本情况。从搭建嵌入式 Linux 环境的角度来说,需要自己编译 BootLoader、Linux Kernel、文件系统、linux 程序编译工具链 这四大部分;从使用者角度来说,通常会直接购买以上完整的嵌入式环境,然后在以上环境上开发用户 APP,基本不会涉及以上四大部分的修改(二次开发除外)。
其中,最麻烦的应该就是构建自己的 linux 程序编译工具链了。
构建过程
  嵌入式 Linux 环境的搭建是从源代码开始的,可以手动构建每一部分,也可以选择使用自动化构建工具。如果选择纯手工搭建就要熟悉每一部分的源码的构建细节(好消息是,Linux 系统下的源码基本都是 configure + make 的处理流程),使用自动化构建工具则需要学习对应工具的使用方法。
 
CPU 体系架构
  当前全球公认为主流的 CPU 体系架构有 x86、PowerPC、ARM、RISC-V 和 MIPS 等。早年,x86 和 PowerPC 专注于桌面计算机、服务器等领域,ARM、RISC-V 和 MIPS 则在嵌入式领域发力。如今,x86、PowerPC 也布局了嵌入式领域,ARM、RISC-V 和 MIPS 也开始布局桌面计算机和服务器。
 
之前主要使用的是 ARM 架构,最近转移到了RISC-V 架构,因此,以下以 ARM 作为对比,重点介绍 RISC-V。
ARM
  ARM 架构是由 ARM 公司推出的基于精简指令集计算机(RISC)指令集架构(ISA)。ARM 架构版本从 ARMv3 到 ARMv7 支持 32 位空间和 32 位算数运算,大部分架构的指令为定长 32 位(Thumb 指令集支持变长的指令集,提供对 32 位和 16 位指令集的支持),而 2011 年发布的 ARMv8-A 架构添加了对 64 位空间和 64 位算术运算的支持,同时也更新了 32 位定长指令集。
ARM 公司除了 ARM 指令集相关产品,也提供了一些列的开发软件,例如 Keil、DS-5 等。
  指令集架构(Instruction Set Architecture,ISA)是计算机抽象模型的一部分。它定义了软件如何控制 CPU。Arm ISA 允许开发人员编写符合 Arm 规范的软件和固件,以此实现在任何基于 Arm 的处理器上都可以以同样的方式执行它们。ARM 指令集架构有三种:
A64: A64 指令集是在 Armv8-A 中引入的,以支持 64 位架构。A64 指令集有固定的 32 位指令长度。
对应的 CPU 通常使用 AArch64 来表示
A32: A32 指令集有固定的 32 位指令长度,并在 4 字节边界上对齐。A32 指令集就是在 Armv6 和 Armv7 架构中常说的 ARM 指令集,Armv8 及之后改名 A32 以与 A64 进行区分。
A32 指令主要被用于 A-profile 和 R-profile。
对应的 CPU 通常使用 AArch32 来表示
T32: T32 指令集最初是作为 16 位指令的补充集引入的,用于改进的用户代码的代码密度。随着时间的推移,T32 演变成 16 位和 32 位混合长度的指令集。因此,编译器可以在单个指令集中平衡性能和代码大小。
  T32 指令集就是在 Armv6 和 Armv7 架构中被所熟知的 Thumb 指令集,Armv8 及之后改名为 T32。 T32 支持所有架构的 Profile,并且是 M-Profile 架构所支持的唯一指令集。
随着 Thumb-2 技术的引入,A32 的大部分功能都被纳入了 T32 中。目前 32 位的 ARM 指令集通常指的就是它。
  以上这三种指令集被称为 ARM 基础指令集(ARM Base ISAs),除此之外,ARM 还提供指令集扩展:自定义指令、DSP、浮点等等。
  此外还需要注意,自 ARM11(ARMv6 指令集) 之后,ARM 启用了全新的产品命名方式:Cortex-A、Cortex-R、Cortex-M,他们之间的指令集是独立的,且会有区别。
Linux Kernel 之十一 Yocto、OpenEmbedded、BitBake 详解
构建过程
  嵌入式 Linux 环境的搭建是从源代码开始的,可以手动构建每一部分,也可以选择使用自动化构建工具。如果选择纯手工搭建就要熟悉每一部分的源码的构建细节(好消息是,Linux 系统下的源码基本都是 configure + make 的处理流程),使用自动化构建工具则需要学习对应工具的使用方法。
 
Yocto 全称是 Yocto Project(官方简称 YP) 是 Linux 基金会在 2010 年推出的一个开源的协作项目。提供模板、工具和方法以创建定制的 Linux 系统和配套工具,而无需关心硬件体系。主要由 Poky 和 其他一些工具组成。
 
从历史上看,Yocto Project 是从 OpenEmbedded 项目发展而来的。他们本是两个不同的项目(左侧分离视图),然而,目前的 OpenEmbedded 与 Yocto Project 已经融合为一体了(右侧合并视图),因为目前已经很少见单独使用 OpenEmbedded 了。
在很多文献中都是右侧这种结构,个人认为左侧结构更好理解一些!
  虽然 Yocto Project 和 OpenEmbedded 的代码仓库是分开的(https://git.yoctoproject.org/ 和 https://git.openembedded.org/),但是其中的内容都是相互关联的,很多概念也不再区分是由 Yocto Project 引入的还是属于 OpenEmbedded。即 Yocto Project 构建过程 = OpenEmbedded 构建过程。
Poky
  Poky 官方定义为 Yocto Project 的参考嵌入式发行版(Reference Embedded Distribution),它才是真正使用的构建系统工具(更确切的说这就是一个可以构建出嵌入式 Linux 的 DEMO)。而 Yocto Project 这个名字(或者常简称的 Yocto)是指的这个项目本身或者这个项目组织机构。
源码
  从 Yocto Project 网站下载的发行版的 Yocto (官方命名为 YP CORE-xxxx)就是 Poky 源码包。下面是一个 Poky 源码包的简单说明及与 OpenEmbedded-Core 的对比,从中不难看出 Poky = BitBake + OpenEmbedded-Core(稍作改动) + Yocto 自定义
 
Poky 不包含 Yocto Project 提供的其他工具,需要单独下载
除了版本号,每个Yocto Project 的发行版 Poky 源码包都会有个代号!例如,目前最新的 HONISTER、上一版 HARDKNOTT 等。
文档
  Poky 的文档(源码目录/documentation/*)使用的是 Sphinx 搭建的文档系统。Sphinx 也是基于 Python 的,使用的是 reStructuredText 语言格式,文件扩展名通常是 .rst。这个目录下包含了一系列的文档,用于描述当前 Poky 的各种特性。
 
Sphinx 文档系统使用 make 命令来生成发布的文档,可以生成 html、pdf 等格式。例如,在 源码目录/documentation/ 下执行 make html 命令,就会生成一个 _build 的目录,其中就包含了生成的文档。
 
依赖工具: sudo apt install python3-pip 、sudo pip install -U Sphinx sphinx_rtd_theme
在 Poky 源码根目录也有个 Makefile,也是用来生成这些文档的,只不过使用时必须先指出一些环境变量。感觉有些多余。
在 Yocto Project 官方仓库中,有个叫 yocto-docs 的单独的仓库,这个仓库其实就是整个文档的开发仓库,没有问题之后会被合并到 Poky 下,然后随着 Poky 进行发布。
使用
  实际使用中,可以直接使用 Poky 外加一些自己的 Layer 作为自己的环境,也可以参考 Poky 来搭建出一个自己的 Poky。下图所示的是在用的一个参考 Poky,纯手工打造出来的(有很多个自定义的 meta-xxx ,这里使用 meta-custom 代替),用于构建自己的嵌入式 Linux 系统的构建环境。
vendor-setup-env 是自己添加的用于设置当前目录结构,然后调用 source/openembedded-core/oe-init-build-env 这个文件。
自定义的基本思路就是定义并导出 BitBake 要的各种变量,其他 Layer 中会使用这些环境变量
Poky 只是一个示例,并不能直接拿来通过配置就用于自己的环境中。
  整个构建系统的入口就是 OpenEmbedded-Core 中的 oe-init-build-env 这个文件。它是一个 Bash 脚本文件,用来初始化 OpenEmbedded 构建环境。在开启构建之前,必须先执行一次命令 source oe-init-build-env <builddir> 这个命令。除了初始化构建环境,这个命令还会建立一个 build 目录(不指定 <builddir> 时的默认名),用来存放构建中产生的所有内容。执行命令后,会自动跳转到 <builddir> 目录下,后续就可以使用 bitbake <target> 启动指定的目标的构建了。
关于 source 命令:
source 命令是一个内置的 shell 命令,用于从当前 shell 会话中的文件读取和执行命令。source 命令通常用于保留、更改当前 shell 中的环境变量。主要有以下四个用途:
刷新当前的 shell 环境
在当前环境使用 source 执行 Shell 脚本
从脚本中导入环境中一个 Shell 函数
从另一个 Shell 脚本中读取变量
source 也可以用一个英语的 . 表示。source 命令从 C Shell 而来,是 bash shell 的内置命令;. 从 Bourne Shell 而来,是 source 的另一名称。
  当输入 source oe-init-build-env <builddir> 并执行执行时,会依次调用 oe-init-build-env ➔ scripts/oe-buildenv-internal ➔ scripts/oe-setup-builddir ➔ .templateconf 这四个文件,以下是每个文件中的一些具体操作。
 
Poky 采用的是被称为影子构建的构建模式。即将所有在构建过程中输出的文件都会放到一个单独的顶级文件夹(默认目录名 build)中,从而不会对源码有任何影响。执行 source oe-init-build-env <builddir> 就时为了准备好这个构建环境。
构建输出目录中,最为关心的是 conf 下的 local.conf、bblayers.conf、templateconf.cfg 这三个配置文件(后续详细介绍),以及 tmp 目录下的 deploy 文件夹包含构建的最终输出文件,例如,deploy/images/ 下就包含编译好的 Linux 镜像、Bootloader 等。
以上构建输出目录是 OpenEmbedded 默认的目录结构,它是支持自己进行自定义的!
其他工具
  除了 Poky 这个构建工具系统之外,Yocto Project 还维护其他一些单独的组件。有些是 Yocto 项目内部使用的,有些则是可以单独在开发过程中使用的。所接触过的主要就是下面这几个,其他还有一些参见官网介绍。
开发工具
CROPS:CROSS PLATFORM ENABLEMENT WITH CONTAINERS(CROPS)是一个开源的跨平台开发框架,它利用 Docker 容器提供了一个易于管理的、可扩展的环境,允许开发者在 Windows、Linux 和 macOS 主机上为各种架构构建二进制文件。
eSDK:Extensible Software Development Kit(ESDK)提供了一个针对特定镜像内容定制的交叉开发工具链和库。可扩展 SDK 使得向镜像添加新的应用程序和库、修改现有组件的源代码、测试目标硬件上的更改以及轻松集成到 openbedded 构建系统的其余部分变得很容易。
devtool: devtool 是一个命令行工具,是 eSDK 的基础。该工具可以帮助在 eSDK 中构建、测试和打包软件,并可以选择将其集成到由 openbedded 构建系统构建的镜像中。
Toaster:Toaster 是 OpenEmbedded 和 BitBake 的 web 界面。Toaster 允许配置和运行构建,并提供关于构建过程的信息和统计信息。
生产工具
AUTO UPGRADE HELPER:这是一个旨在与OpenEmbedded 构建系统(Bitbake 和 OE-Core)一起使用的实用程序,以便根据上游发布的新版本自动为 Recipes 生成升级。
AUTOBUILDER:AutoBuilder 是一个自动化构建测试和质量保证(QA)的项目(基于 Buildbot)。
PSEUDO:一个伪装管理的程序,可以使一些操作以管理员权限成功执行
Layer Model
  Yocto Project 引入了 Layer Model 这一机制,这也是它区别于其他构建系统的一点。其中,BSP 和 DISTRO 是其 Layer Model 中最具有代表性的两个 Layer。他俩除了有一般 Layer 的文件结构及配置文件外,还额外多了一些自己特有的配
BSP Layer:Board Support Packages(BSP)主要包含 Machine Configuration 相关的内容(通常,相比于其他 Layer,BSP Layer 会有 conf/machine/* 配置文件)。它定义了如何支持一个特定的硬件设备、一组设备或硬件平台。BSP 包含关于设备上出现的硬件特性的信息、内核配置信息以及所需的任何其他硬件驱动程序。BSP 还列出了必要的和可选的平台特性所需的通用 Linux 软件堆栈之外的任何其他软件组件。
一般只有一个 BSP Layer
Poky 源码中的 meta-yocto-bsp 中就是就是一个 BSP Layer
OpenEmbedded-Core 就一个 BSP Layer(仅支持模拟器) + DISTRO Layer(基本没啥功能) 集合。
DISTRO Layer:DISTRO 是 Distribution 的缩写,主要包含 Distro Policy Configuration 相关的内容(通常,相比于其他 Layer,DISTRO Layer 会有 conf/distro/* 配置文件)。它包含的系统特性的相关配置。这是通过添加到 DISTRO FEATURES 变量来完成的。
一般只有一个 DISTRO Layer
Poky 源码中的 meta-poky 就是一个 DISTRO Layer
构建出来的 Linux 系统通常被称为 Linux Distribution。常用的 Ubuntu 就是一个有名的 Linux Distribution。
Software Layer:软件层为构建过程中使用的其他软件包提供元数据,不包括特定于 DISTRO 或机器的元数据。通常,Software Layer 会有很多个。
OpenEmbedded
  OpenEmbedded (简称 OE)是一个自动化构建框架和交叉编译环境,用于为嵌入式设备创建 Linux 发行版。OpenEmbedded 由成立于 2003 年的 OpenEmbedded 社区开发,其诞生远早于 Yocto Project。2011 年 3 月,它与 Yocto Project 开始合作(实际就是合并了)。
 
从与 Yocto Project 合作开始,OpenEmbedded 便以 OpenEmbedded-Core 项目作为项目发展的名称(简称 OE-Core),之前的称为 OpenEmbedded-Classic(简称 OE-Classic 或 oe-dev),OpenEmbedded 这个名字就用来代指整个 OpenEmbedded 项目。现在的 OpenEmbedded 也可以理解为基于 OE-Core 的一个实现(BitBake + OpenEmbedded-Core + 一组元数据)。
 
目前,OpenEmbedded 的文档大都直接引用 Yocto Project 上的对应文档了。注意,在一些老文档中,OpenEmbedded 这个名字有可能还是指的旧的 OpenEmbedded 。例如,OpenEmbedded 的原始代码仓库并没有改名为 OpenEmbedded-Classic。旧版 OpenEmbedded 项目的代码仓库见 https://git.openembedded.org/openembedded。
OpenEmbedded-Core。
OpenEmbedded-Core 之前的 OpenEmbedded 将所有自动构建的 Recipes 都放在一起(参见旧版 OpenEmbedded 代码仓库),随着发展越来越难以维护。OpenEmbedded 项目组一直想要改进,直到与 Yocto Project 合作后,Yocto Project 开始派人处理这个事。
Yocto Project 的人参与到 OpenEmbedded 后,将原来的 OpenEmbedded 中的大部分 Recipes 使用 Layer 的概念拆分了出去(单独建立源码仓库 meta-openembedded 来进行维护),并重新组织了剩余 OpenEmbedded 的源代码的结构,新的代码源码被命名为 OpenEmbedded-Core(简称 OE-Core)。
OpenEmbedded-Core 是一个比较特殊的 Layer
  调整后的 OpenEmbedded-Core 是原来 OpenEmbedded 的子集,只包含了大多数人需要用来构建小型的嵌入式设备的一些 Recipes 以及一些共享类及相关的文件等。目前由 OpenEmbedded 项目组和 Yocto 项目组共同维护。而旧的 OpenEmbedded 源代码(现在称为 OpenEmbedded-Classic)不再维护,也基本没有使用了!
 
OpenEmbedded-Core 可以独立使用。实际情况是,更多的是被集成在 Angstrom、SHR、Yocto Project 等系统中来使用,很少见单独使用 OE-Core 的情况。OpenEmbedded 基本就成了 Yocto Project 中的一部分(其他基本死的差不多了)。
OpenEmbedded-Core 是无发行版的(没有明确的 DISTRO 定义),并且只包含模拟机器支持。具体见 openembedded-core/meta/conf 下相关代码。
meta-openembedded
  meta-openembedded 是从原来的 OpenEmbedded 中的 Recipes 中拆分出来的 Recipes 的一个集合。这些拆出来的 Recipes 也被组织为一个个的 Layer,并作为对 OpenEmbedded-Core 的扩展。
meta-openembedded 无法单独使用,因为它里面的各个 Layer 依赖于 OpenEmbedded-Core。因此,如果要使用它,就必须同时使用 OpenEmbedded-Core。但是,OpenEmbedded-Core 可以独立使用!
可以选择不使用 meta-openembedded,Poky 就没有使用 meta-openembedded。
  meta-openembedded 中的每个 Layer 都有专门的人负责维护。此外,OpenEmbedded 官方还维护了一个可以在 OpenEmbedded-Core 中使用的 Layer 列表,其中包含了很多第三方提供的 Layer。
  还有一个需要注意的问题是使用时的路径问题。meta-openembedded 本身不是一个 Layer,它里面的内容才是一个个的 Layer。因此在使用时,需要注意他比其他 Layer 多个一级目录。例如 ../meta-openembedded/meta-oe。
BitBake
  BitBake 是一个任务调度和执行引擎,用来解析指令(Recipes)和配置数据。它允许 shell 和 Python 脚本高效并行运行,同时在复杂的任务间依赖关系约束下工作。基本就是一个类似于 GNU Make 的东西(make 使用 Makefile,BitBake 使用 Recipe)。
  BitBake 最初是 OpenEmbedded 项目的一部分。它的灵感来自 Gentoo Linux 发行版使用的 Portage 包管理系统。2004 年 12 月 7 日,OpenEmbedded 项目团队成员 Chris Larson 将项目分为 BitBake 和 OpenEmbedded 两个独立的部分,其中,前者作为一个通用的任务执行器,后者则包含 BitBake 使用的元数据集。
源码
  BitBake 是一个用 Python 语言编写的程序,目前由 Yocto Project 与 OpenEmbedded 项目成员共同维护。BitBake 的源代码结构其实并不复杂,代码量也不是很多。如下是一个目录的基本说明:
 
文档
  BitBake 的文档(源码目录/doc/*)使用的也是 Sphinx 搭建的文档系统。Sphinx 也是基于 Python 的,使用的是 reStructuredText 语言格式,文件扩展名通常是 .rst。在线文档则位于 Yocto Project 网站上:https://docs.yoctoproject.org/bitbake/index.html。
使用
  严格来说,BitBake 是一个可以独立使用的任务处理引擎。可以选择用在其他方面,但是一般没有人会选择这样做。目前,也没有见过在其他方面有使用 BitBake 的。下面是 BitBake 的简单使用目录结构及使用示例:
示例代码:git clone https://gitee.com/itexp/bitbake-demo.git
init-env.sh 用于配置 BitBake 的工作环境,而 helloworld/classes/base.bbclass 、helloworld/conf/*、meta-*是 BitBake 工作的基本文件
Metadata
  Metadata 是构建系统(BitBake)在构建过程中解析并使用的基本数据的统称。这些数据描述了如何构建一个 Linux 发行版。通常,Recipes、配置文件和其他引用构建指令本身的信息,以及用于控制构建内容和影响构建方式的数据都属于 Metadata。 OE-Core 和 meta-openembedded 就是一些 Metadata 的集合。
除了 BitBake 预定义的一些 Metadata 之外,Yocto Project 还额外扩展了一些。
  Metadata 使用 BitBake DSL (Domain Specific Language) 来编写,其中包含变量和可执行的 shell 或 python 代码。该语法与其他几种语言具有相似之处,但也具有一些独特的功能。
Configuration Files
  配置文件主要用来控制构建过程,使用 .conf 作为扩展名,主要保存全局变量定义、用户定义变量和硬件配置信息等数据。这些文件大体可以分为 用户配置、发行版配置、机器配置、可能的编译器优化、通用配置 等这几大类。
配文件也可以有 xxx.inc 文件,然后在 yyy.conf 中使用 require xxx.inc
配置文件可以使用 require 关键字引用其他配置文件
基本配置元数据是全局的,会影响所执行的所有 Recipes 和 Task。
配置文件中只允许定义变量以及使用 include 或 require 指令包含其他配置相关的文件
User Configuration
  用户配置(User Configuration)主要是告诉 BitBake 要构建的镜像的目标架构,在哪里存储下载的源代码,以及其他构建属性。当执行 source oe-init-build-env <builddir> 命令时,oe-init-build-env 会调用 scripts/oe-setup-builddir 这个脚本文件生成用户配置文件(<builddir>/conf/下的文件)。
local.conf:提供了许多定义构建环境的基本变量。在 OpenEmbedded-Core 中,该文件是由 /meta/conf/local.conf.sample 这个模板文件生成的;在 Poky 中,该文件是由 meta-poky/conf/local.conf.sample 这个模板文件生成的。 该文件主要包含以下内容:
Target Machine Selection:Controlled by the MACHINE variable.
Download Directory:Controlled by the DL_DIR variable.
Shared State Directory:Controlled by the SSTATE_DIR variable.
Build Output:Controlled by the TMPDIR variable.
Distribution Policy:Controlled by the DISTRO variable.
Packaging Format:Controlled by the PACKAGE_CLASSES variable.
SDK Target Architecture:Controlled by the SDKMACHINE variable.
Extra Image Packages:Controlled by the EXTRA_IMAGE_FEATURES variable.
site.conf:主要用来配置多个构建目录。如果需要,必须手动根据 /meta/conf/site.conf.sample 这个模板创建该文件。
auto.conf:该文件通常是由 autobuilder 创建和写入的,里面的内容与 local.conf 或者 site.conf 相同。
bblayers.conf:主要告诉 BitBake 在构建过程中需要处理哪些 Layer。默认情况下,此文件中列出的 Layer 包括构建系统最少需要的Layer。必须手动添加自己创建的 Layer。当执行 bitbake <target> 命令时,BitBake 在当前工作目录下寻找的第一个文件就是 conf/bblayers.conf。 这个文件中有以下几个很重要的变量:
BBPATH:存放 BitBake 用来寻找 Class 文件(.bbclass)和配置文件(.conf)的路径。当执行到其他目录时,必须要重新设置 BBPATH 的值。
BBFILES:存放 BitBake 用来寻找 Recipes 文件(.bb)和附加文件(.bbappend)的路径。构建目录根目录下的 conf/bblayers.conf 中 BBFILES 为空。
BBLAYERS:列出了构建时需要的所有 Layers 路径。每个 Layer 路径对应一个 Layer,都是完整的绝对路径。不同 Layer 用空格分开。其中的每个 Layer 下都会有自己的 conf/layer.conf,其中配置一些当前路径、Recipes 文件路径等。
bitbake.conf:解析 bblayers.conf 之后,BitBake 会在用户指定的 BBPATH 中找 conf/bitbake.conf 文件。该配置文件通常包含用于导入任何其他元数据的指令,比如特定于体系结构、计算机、本地环境等的文件。下图是个示例
Machine Configuration
这部分配置主要是指 BSP Layer 中的配置。提供特定于机器的配置。这种类型的信息是特定于特定目标体系结构的。例如,在 Poky 中的 meta-yocto-bsp/conf 下的配置文件。
Policy Configuration
这部分配置主要指的是 DISTRO Layer 中为特定分发版构建的镜像或 SDK 提供的顶级或通用的策略。例如,在 Poky 中的 meta-poky/conf/ 下的配置文件。
通常,DISTRO Layer 下的 conf/distro/distro.conf 会覆盖 BitBake 源码目录下的 conf/local.conf 中的相同配置。
Recipes
Recipes 是最基本的 Metadata 形式,描述如何处理给定的应用程序,通常的文件命名规则为 <application-name>_<version>.bb。Recipes 包含了一系列的指令,描述了如何对给定的应用进行获取、打补丁、编译、安装和生成二进制包,它还定义了构建或者运行时所需要的依赖。
一个 Recipe 文件通常包含:name、license、dependencies、获取源码的地址以及真正可以被执行的函数(通常被称为任务)。
在解析配置文件时,BitBake 会拿到 BBFILES 这个变量的值。BitBake 使用它来构造要解析的 Recipes 列表,以及要应用的任何附加文件(.bbappend)。BitBake 解析每个 Recipe 和匹配的附加文件,并将各种变量的值存储到数据存储中。
对于每个 Recipe 文件,生成一个新的基本配置副本,然后逐行解析。如果文件中有 inherit xxxx ,则 BitBake 就会使用BBPATH 作为搜索路径来查找并解析 Class 文件(.bbclass)。最后,BitBake 按 BBFILES 中列出的追加文件(.bbappend)的顺序查找并解析相关追加文件。
在源码组织上,Recipe 就是那些 recipes-* 开头的文件夹中的内容。很多应用程序会有多个 Recipe 以支持不同的版本。在这种情况下,公共部分通常放在 <application>.inc 文件中,而将专有部分放到 <application>_<version>.bb 文件中(通过 require <application>.inc 引用)。
也可以使用 include <application>.inc,区别在于 如果引用的文件不存在,include 指令不会报错,而 require 会报错。
Class
  Class 文件包含在 Metadata 文件之间可以共用的信息,文件扩展名为 .bbclass。Class 文件中的内容并没有特殊要求,所有可以用在 Recipe 中的内容都可以放到 Class 文件中。只要自己觉得它不违背 Class 设计的目的就好。它基本就和 C 库似的,提供一些公共功能,不需要每次自己重新写。
  BitBake 工作必须要一个名为 ./classes/base.bbclass 的文件。这个 Class 文件比较特殊,它会默认被包含在所有的 Recipes 和 其他 Class 文件中,不需要显示的引用。这个类包含标准基本任务的定义,比如抓取、解包、配置(默认为空)、编译(运行当前的任何Makefile)、安装(默认为空)和打包(默认为空)。这些任务通常由项目开发过程中添加的其他类重写或扩展。
 
BitBake 源码目录下有这个文件 bitbake源码/conf/bitbake.conf,可以拿过来使用
  在源码组织上,Class 通常会单独放在所在 Layer下的一个名为 classes 的文件夹中,名字一般为 xxxx.bbclass,其他 Recipe 文件中可以使用 inherit xxxx(注意不需要扩展名 .bbclass) 来引用其中的内容。
不同的 Layer 可以有自己的 Class,OpenEmbedded 的公共 Class 位于 .\openembedded-core\meta\classes 目录下
Layer
  Layer 主要就是用来组织众多 Recipes 的。随着 Recipes 的增多,BitBake 引入了 Layer 的概念,以将 Recipes 按照不同的分类组织起来,不同的 Layer 之间相互独立。Layer 就是一些 Recipes 的集合。
Layer 可以在任何时候包含对先前指令或设置的更改。
常见的 Layer 有 BSP、GUI、distro configuration、middleware、application
  在源码组织上,Layer 就是那些 meta-* 开头的文件夹(它有一个约定俗成的目录结构),其中包号一系列的 Recipes。在实际使用中,需要根据自己的需求,添加自己的 Layer。OpenEmbedded 官方维护的 meta-openembedded 就是一些 Layer 的集合。它主要用来扩展 OpenEmbedded-Core 的功能。
U-Boot 之一 零基础编译 U-Boot 过程详解、Image 镜像介绍及使用说明、DTB 文件使用说明
开发转移到嵌入式 Linux 系统开发,在之前的博文 Linux 之八 完整嵌入式 Linux 环境、(交叉)编译工具链、CPU 体系架构、嵌入式系统构建工具 中详细介绍了嵌入式 Linux 环境,接下来就是实际动手学习了。
仅仅关注构建过程本身,想要吃透 U-Boot,有太多东西需要学习!最开始想放到一篇文章中,写着写着内容越来越多,最终超过了 CSDN 编辑器的限制。。。最终决定把内容拆分成多篇文章。可能需要:
运行环境
  使用的嵌入式环境是 STM32F769I-EVAL 板子。STM32F769I-EVAL 板子使用的 STM32F769NI 这个 MCU,STM32F769NI 这款 MCU 采用的是 ARM Cortex-M7 的核心,指令集架构是 ARMv7m。此外,还需要注意,这个板子上的的串口的 RX 默认是断开,需要用短路帽连接起来。
 
U-Boot 本身没有提供对于 STM32F769I-EVAL 板子的支持,但是它支持的 STM32F769-Disco 板子。STM32F769-Disco 开发板与 STM32F769I-EVAL 评估板的 MCU 都使用的是 STM32F769NI,因此,直接编译后下载是可以运行的。
  不过两者的板载资源不同(例如,DRAM 大小),因此,U-Boot 识别的某些外设信息是不对的。这里仅仅关注如何进行零基础编译,在博文 U-Boot 之二 移植过程详解、 STM32F769I-EVAL 开发板适配 中详细介绍了如何将 U-Boot 移植到 STM32F769I-EVAL 板子。
编译过程
  在开始编译之前,先介绍两个命令:make clean 用于清空编译中间文件 和 make distclean 用于清除所有编译产生的文件。如果想要重新编译,可以使用以上两个命令清理环境(这两个命令在 U-Boot 根目录的 Makefile 中有详细的定义,在博文 U-Boot 之四 构建过程(Kconfig 配置 + Kbuild 编译)详解 中会有说明)。
第一步肯定是获取 U-Boot 的源代码,这里直接使用了当前最新存档版:u-boot-2021.10.tar.bz2。这里需要重点注意,最开始直接使用 Git 获取了最新的源代码,结果编译之后运行直接 HardFault,分析好久没找到原因,最后决定使用一个稳定发布版试试,结果没有问题。
Image 镜像
  成功编译之后,就会在 U-Boot 源码的根目录下产生多个可执行二进制文件以及编译过程文件,这些文件都是 u-boot.xxx 的命名方式。这些文件由一些列名为 .xxx.cmd 的文件生成,.xxx.cmd 这些文件都是由编译系统产生的用于处理最终的可执行程序的。注意,下面部分文件可能没有与自己的 make menuconfig 中的配置有关系。
u-boot 这个文件是编译后产生的 ELF 格式的最原始的 U-Boot 镜像文件,后续的文件都是由它产生的!.u-boot.cmd 这个命令脚本描述了如何产生。
u-boot-nodtb.bin: 这文件是使用编译工具链的 objcopy 工具从 u-boot 这个文件中提取来的,它只包含可执行的二进制代码。就是把 u-boot 这个文件中对于执行不需要的节区删除后剩余的仅执行需要的部分。由 .u-boot-nodtb.bin.cmd 这个命令脚本产生。
u-boot.img: 在 u-boot.bin 开头拼接一些信息后形成的文件。由 .u-boot.img.cmd 这个命令脚本产生。
u-boot-dtb.img: 在 u-boot.bin 开头拼接一些信息后形成的文件。由 .u-boot-dtb.img.cmd 这个命令脚本产生。
u-boot.srec: S-Record 格式的镜像文件。由 .u-boot.srec.cmd 这个命令脚本产生。
u-boot.sym: 这个是从 u-boot 中导出的符号表文件。由 .u-boot.sym.cmd 这个命令脚本产生。
u-boot.lds: 编译使用的链接脚本文件。由 .u-boot.lds.cmd 这个命令脚本产生。
u-boot.map: 编译的内存映射文件。该文件是有编译工具链的连接器输出的!
System.map: 记录 U-Boot 中各个符号在内核中位置,但是这个文件是使用了 nm 和 grep工具来手动生成的
Image 使用
  编译之后,源码的根目录下会生成一堆二进制的文件(.bin),其中,在默认的 U-Boot 配置下,实际需要的 U-Boot 实际包含两部分:spl/u-boot-spl.bin 和 u-boot.bin。而且,默认情况下,编译的这俩文件是可以直接在 MCU 内部的 Nor Flash 中运行的。
u-boot-dtb.bin 在 u-boot-nodtb.bin 尾部拼接上设备树后形成的文件。由 .u-boot-dtb.bin.cmd 这个命令脚本产生。
u-boot.bin 就是把 u-boot-dtb.bin 重命名得到的。由 .u-boot.bin.cmd 这个命令脚本产生。
u-boot.img: 在 u-boot.bin 开头拼接一些信息后形成的文件。由 .u-boot.img.cmd 这个命令脚本产生。
u-boot-dtb.img: 在 u-boot.bin 开头拼接一些信息后形成的文件。由 .u-boot-dtb.img.cmd 这个命令脚本产生。
u-boot.srec: S-Record 格式的镜像文件。由 .u-boot.srec.cmd 这个命令脚本产生。
u-boot.sym: 这个是从 u-boot 中导出的符号表文件。由 .u-boot.sym.cmd 这个命令脚本产生。
u-boot.lds: 编译使用的链接脚本文件。由 .u-boot.lds.cmd 这个命令脚本产生。
u-boot.map: 编译的内存映射文件。该文件是有编译工具链的连接器输出的!
System.map: 记录 U-Boot 中各个符号在内核中位置,但是这个文件是使用了 nm 和 grep工具来手动生成的
u-boot.dtb: 这个是编译好的设备树二进制文件。就是 ./dts/dt.dtb 重命名得到的。./dts/dt.dtb 来自于 arch/arm/dts/stm32f769-eval.dtb 重命名。
  默认是开启了 SPL 的,因此,在编译 U-Boot 时会额外单独编译 SPL,编译产生的镜像文件就存放在 ./SPL 目录下。这下面的镜像生成方式与 U-Boot 基本是一模一样的。
u-boot-spl: 这个文件是编译后产生的 ELF 格式的 SPL 镜像文件,后续的文件都是由它产生的!.u-boot-spl.cmd 这个命令脚本描述了如何产生。
u-boot-spl-nodtb.bin: 这文件是使用编译工具链的 objcopy 工具从 u-boot-spl 这个文件中提取来的,它只包含可执行的二进制代码。就是把 u-boot-spl 这个文件中对于执行不需要的节区删除后剩余的仅执行需要的部分。由 .u-boot-spl-nodtb.bin.cmd 这个命令脚本产生。
u-boot-spl-dtb.bin: 在 u-boot-nodtb.bin 尾部依次拼接上 u-boot-spl-pad.bin 和 u-boot-spl.dtb 后形成的文件。由 .u-boot-spl-dtb.bin.cmd 这个命令脚本产生。
u-boot-spl.bin: 就是把 u-boot-dtb.bin 重命名得到的。由 .u-boot-spl.bin.cmd 这个命令脚本产生。
u-boot-spl.sym: 这个是从 u-boot 中导出的符号表文件。由 .u-boot-spl.sym.cmd 这个命令脚本产生。
u-boot-spl.lds: 编译使用的链接脚本文件。由 .u-boot-spl.lds.cmd 这个命令脚本产生。
u-boot-spl.map: 编译 SPL 的内存映射文件。
u-boot-spl.dtb: 这个是编译好的设备树二进制文件。就是 ./dts/dt.dtb 重命名得到的。./dts/dt.dtb 来自于 arch/arm/dts/stm32f769-eval.dtb 重命名。
u-boot-spl-pad.bin: 应该是对齐使用的数据,具体为啥需要这个还没找到。
Image 使用
  编译之后,源码的根目录下会生成一堆二进制的文件(.bin),其中,在默认的 U-Boot 配置下,我们实际需要的 U-Boot 实际包含两部分:spl/u-boot-spl.bin 和 u-boot.bin。而且,默认情况下,编译的这俩文件是可以直接在 MCU 内部的 Nor Flash 中运行的。
因此,我们可以直接使用 J-link 等调试器将这俩文件烧写到 Flash 中。至于烧写地址,在 U-Boot 源码中(具体见于 ./config/stm32f769-disco_defconfig 和 ./include/configs/stm32f746-disco.h)都是配置好了的。
设备树
  设备树源文件被最终编译为二进制的 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 的目的。
参考
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/
opdenacker-understanding-u-boot-falcon-mode.pdf
https://adrianalin.gitlab.io/popsblog.me/posts/build-linux-for-stm32f769i-disco-using-buildroot/
移植过程
  绝大多数情况下,移植工作都不是从零开始。例如,U-Boot 默认提供了对于 STM32F769i-disco 的支持,STM32F769I-EVAL 的移植完全可以参考它来进行。如果是一个全新的芯片,移植过程本身和参考已有的是一样的,更多的是在后续适配开发板工作比较繁重。
移植后的 U-Boot 代码放到了Github 上:https://github.com/ZCShou/BOARD-STM32F769I-EVAL
参考 STM32F769i-disco 移植 STM32F769I-EVAL 的具体步骤如下:
新增 CPU 架构相关的文件:arch/架构/cpu/xxxx。这个一般不需要添加,一般人也搞不了。唯一的可能就是用的芯片使用了比较新的架构,而 U-Boot 还不支持。例如,如果芯片使用了最新的 ARMv9 架构,U-Boot 目前还没有支持!
新增芯片(或许应该说是开发板)的设备树文件:arch\arm\dts\xxxxx.dts,然后将新增的设备树文件添加到 arch/arm/dts/Makefile 中。一般比较常见的 MCU,U-Boot 都是支持的,无需关心。通常,往往是将一个与芯片类似的做一些对应的更改以适用于自己。
  具体到这里使用的 STM32F769I-EVAL 板子,使用的 STM32F769 设备树没有,但是有个类似的 STM32F769i-disco 的设备树,所以这里就依据 STM32F769i-disco 添加 STM32F769-eval 相关设备树。具体更改如下:
 
这里需要注意,在 CPU 的 Kconfig 中有可能已经直接引用了开发板的 Kconfig,而 CPU 的 Kconfig 又被包含到了架构一级的 Kconfig 文件,因此,如果有这个包含关系,这里就不用再次引入开发板的 Kconfig 了。例如 STM32F769-disco 的包含关系如下:
存储映射
为了后续调试方便,更改了默认的存储映射,具体如下图所示:
 
DRAM: 16 MiB
  根据 STM32F769I-EVAL 手册说明,DRAM 应该是 32MB,这里显示是 16 MiB,显然是不对的。
STM32F769I-EVAL 手册中说 DRAM 芯片是 IS42S32800G-6BLI,需要根据手册,修改 FMC 在设备树文件中有描述。
 
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 中找到该宏的定义,需需要将上面移植的 include\configs\stm32f769-eval.h 中的宏做相应修改。
对比这个 STM32F769 和 STM32F746 这两个 MCU,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 模式(nDBANK = 1)的。因此,我们最终的处理方案是 CONFIG_SYS_MAX_FLASH_BANKS 仍然为 1 ,CONFIG_SYS_MAX_FLASH_SECT 改为 12 即可。
每块网卡都有一个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 = <&ethernet_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 地址的地方,我们只需要读取即可。
U-Boot 中默认配置的 MAC 与 PHY 是通过 RMII 接口通信的,而 EVAL 板子使用的是 MII 接口
 
MMC: no card present
  STM32F769-EVAL 开发板上是有 SD 卡的。但是这里显示没有卡。不出意外的话,这里是由于 STM32F769-Disco 与 STM32F769-EVAL 在这方面配置不同导致。下图是两款开发板关于 SD 卡的说明:
 
设置 IP 信息,基本就可以 PING 通了。注意,只能从 uboot 中 ping 其他的机器,其他机器不能 ping uboot,因为 uboot 没有对 ping 命令做处理,如果用其他的机器 ping uboot 的话会失败!
 
set_rate not implemented for clock index 4
  这个其实并不是个错误,应该算是源代码中的一个 BUG,通过搜索该提示文字关键字,我们最终可以找到函数 static ulong stm32_set_rate(struct clk *ckl, ulong rate),如下图所示:
 
由于 stm32_set_rate 被调用了多次,而每次调用第一个 if 条件存在不成立的情况,因此就会一直打印该提示信息。
 
从中可以看出,两款开发板使用的 SDMMC 并不相同!EVAL 开发板有两个 SD 卡插槽:SD1 -> SDMMC1,SD2 -> SDMMC2。而 Discovery 板子只有一个 SD卡插槽:SD -> SDMMC2。关键在于 SDMMC2 的管脚使用是不一样的!
EVAL 板子需要修改一下板子的硬件,如上图红色框中所示。从修改便捷性来说,直接使用 SD2 即可,只需要配置 JP7 即可
 
MicroSDcard _detect 引脚需要更改。但是这里有个问题。EVAL 板子中,这个引脚是连接到扩展 IO 的,如何配置到设备树还没处理!不过,可以找个临时处理方法:借用 PC13 这个引脚,因为正好这个引脚是低电平,正好可以表示 SD 卡插入。
 
然而,貌似 U-Boot 不支持低电平有效,因此这里的配置是 broken-cd,表示轮序检测。
 
这样启动板子就可以看到 SD 卡相关信息了
 
U-Boot 调试修改
开启调试选项
  默认情况下,U-Boot 的编译已经进行了优化,且默认并不开启调试的,因此我们需要更改一下 U-Boot 的配置。第一个是需要取消 General Setup --> Optimise for size,在一个就是开启 General Setup --> Configure standard U-Boot features (expert users) -> enable debug information for tools,具体如下图所示:
 
这里需要注意,如果使用 make distclean 会清理所有文件,这就会导致以上的配置被清理!所以除非必要,否则还是使用 make clean 好一些。目前来看 make menuconfig 这一步还是需要在终端中执行。
  警告:目前我在测试中发现,去掉 General Setup --> Optimise for size 可能导致程序无法运行,暂时没找到解决方法!我目前是在仅开启了 General Setup --> Configure standard U-Boot features (expert users) -> enable debug information for tools 的情况进行调试的。就是偶尔会出现断点位置不正确,不影响正常调试!
  这里有个比较严重的问题,去掉 General Setup --> Optimise for size 之后,会导致程序变大,从而原来默认的 SPL 的大小(0x8000)不能容纳实际 SPL 大小,进一步导致了 U-Boot 无法启动。因此,这里我们必须要修改 U-Boot 的基地址。目前,有如下地方需要修改(图里面的 stm32f769-eval 是我移植的,上文暂时还没有更换):
 
启动阶段
  同大多数的 Bootloader 一样,U-Boot 的启动过程也分为 BL1、BL2 两个阶段,分别对应着 SPL 阶段 和 U-Boot 阶段。此外,芯片内部通常还有个固化的引导程序,以 STM32 为例,这个固化的 BootLoader 在博文 STM32 之十四 System Memory、Bootloader 中有过详细的介绍。这段程序的会初始化部分外设以与外部通信,具体可以参考官方手册。在引入了 SPL 之后,整个启动过程就是如下所示:
 
SPL 即 Secondary Program Loader 的缩写,中文就是第二段程序加载器。这里的第二段程序其实就是指的 U-Boot,也就是,SPL 是第一段程序,优先执行,然后他再去加载 U-Boot。更详细的信息请参见博文U-Boot 之零 源码文件、启动阶段(TPL、SPL)、FALCON、设备树中关于启动阶段的介绍。
  在 U-Boot 源码中,启动过程没有完全单独出 SPL 的代码,而是复用了大量 U-Boot 里面的代码。在代码中,通过宏 CONFIG_SPL_XXX 来进行区分。因此,SPL 的启动 与 U-Boot 的启动流程是一样的(但是所具体实现的功能是不一样的),下面来介绍一下 U-Boot 启动过程。
  可以将 U-Boot 的启动过程划分为两个阶段:架构特定初始化 和 通用初始化。芯片初始化阶段的代码主要是位于 ./arch/架构/cpu 以及 ./arch/lib 等目录下,主要以汇编语言为主,下图展示了 ./arch 目录的基本介绍;
 
板级初始化阶段的代码主要位于 ./common、./board 目录下,代码也由汇编语言过度到 C 语言了。当然这两个阶段都可能引用一些公共的代码(例如平台无关的头文件)。./board 目录基本就是按照厂商来组织(例如 ST),同一厂家的开发板放在同一个目录下。一个完整的初始化流程如下所示:
 
启动入口
  U-Boot 源码文件众多,如何知道最开始的启动文件(程序入口)是哪个呢?这就需要查看 .\arch\arm\cpu 目录下的 u-boot.lds 文件了(对于 SPL/TPL 对应的就是 .\arch\arm\cpu\u-boot-spl.lds 文件)。.lds 是连接脚本文件,它描述了如何生成最终的二进制文件,其中就包含程序入口。以下是 .\arch\arm\cpu\u-boot-spl.lds 完整注释:
/* SPDX-License-Identifier: GPL-2.0+ */
/*
 * Copyright (c) 2004-2008 Texas Instruments
 *
 * (C) Copyright 2002
 * Gary Jennejohn, DENX Software Engineering, <garyj@denx.de>
 */
 
/* 指定输出可执行文件: "elf32 位小端格式" */
OUTPUT_FORMAT("elf32-littlearm", "elf32-littlearm", "elf32-littlearm")
/* 指定输出可执行文件的目标架构:"arm" */
OUTPUT_ARCH(arm)
/* 指定输出可执行文件的入口地址(起始代码段):"_start" */
ENTRY(_start)
SECTIONS
{
       /*
        * 设置 0 的原因是 arm 内核的处理器,上电后默认是从 0x00000000 处启动
        * 1. stm32 片内的 nor-flash 起始地址是0x08000000,上电后,系统会自动将该地址(0x08000000) 映射到 0x00000000(硬件设计实现)
        */
       . = 0x00000000;
      
       /*
        * 代码以 4 字节对齐 .text为 代码段
        * 各个段按先后顺序依次排列
        * ARM 规定在 cortex-m 的内核中, 镜像入口处首地址存放的是主堆栈的地址,其次是复位中断地址,再其后依次存放其他中断地址
        * 更详细的启动过程可以参见之前的博文:ARM 之九 Cortex-M/R 内核启动过程 / 程序启动流程(基于 ARMCC、Keil)
        */
       . = ALIGN(4);
       .text :
       {
              __image_copy_start = .;       /* u-boot 的设计中需要将 u-boot 的镜像拷贝到 ram(sdram,ddr....)中执行,这里表示复制的开始地址 */
              *(.vectors)                                  /* 中断向量表 */
              CPUDIR/start.o (.text*) /* CPUDIR/start.o 中的所有.text 段 */
              *(.text*)                               /* 其他 .o 中的所有.text 段 */
              *(.glue*)                              /* 其他 .o 中的所有.glue 段 */
       }
 
       /*
        * .rodata 段,确保是以4字节对齐
        */
       . = ALIGN(4); 
       .rodata : {
     *(SORT_BY_ALIGNMENT(SORT_BY_NAME(.rodata*))) }  /* 按名称依次存放其他 .o 文件中的 .rodata */
 
       /*
        * data段,确保是以4字节对齐
        */
       . = ALIGN(4); 
       .data : {
   
              *(.data*)
       }
 
       /*
        * u_boot_list 段,确保是以 4 字节对齐
        * 这里存放的都是 u_boot_list 中的函数
        * 例如: base/bdinfo/blkcache/cmp....
        * 具体的可参看 ./u-boot.map .u_boot_list
        * tips:要想优化编译出来的 u-boot.bin 大小,可以参看此文件进行对照裁剪
        */
       . = ALIGN(4);
       .u_boot_list : {
              KEEP(*(SORT(.u_boot_list*)));
       }
 
       /*
        * binman_sym_table 段,确保是以 4 字节对齐
        * binman 实现的功能是让 c 代码通过 binman_* 的函数接口字节调用镜像中的个别函数
        * 具体可参看 binman_sym.h 中的接口
        */
       . = ALIGN(4);
       .binman_sym_table : {
              __binman_sym_start = .;
              KEEP(*(SORT(.binman_sym*)));
              __binman_sym_end = .;
       }
 
       /*
        * __image_copy_end 也是个符号表示一个结束地址,确保是以4字节对齐
        */
       . = ALIGN(4);
 
       __image_copy_end = .; /* u-boot 的设计中需要将 u-boot 的镜像拷贝到ram(sdram,ddr....)中执行,这里表示复制的结束地址 */
 
       .rel.dyn : {
              __rel_dyn_start = .;
              *(.rel*)
              __rel_dyn_end = .;
       }
 
       .end :
       {
              *(.__end)
       }
      
       _image_binary_end = .; /* bin文件结束 */
 
       .bss __rel_dyn_start (OVERLAY) : {
              __bss_start = .;
              *(.bss*)
               . = ALIGN(4);
              __bss_end = .;
       }
       __bss_size = __bss_end - __bss_start;
       .dynsym _image_binary_end : {
     *(.dynsym) }
       .dynbss : {
     *(.dynbss) }
       .dynstr : {
     *(.dynstr*) }
       .dynamic : {
     *(.dynamic*) }
       .hash : {
     *(.hash*) }
       .plt : {
     *(.plt*) }
       .interp : {
     *(.interp*) }
       .gnu : {
     *(.gnu*) }
       .ARM.exidx : {
     *(.ARM.exidx*) }
}
/* 下面就是检查一些限制 */
#if defined(CONFIG_SPL_MAX_SIZE)
ASSERT(__image_copy_end - __image_copy_start < (CONFIG_SPL_MAX_SIZE), \
       "SPL image too big");
#endif
 
#if defined(CONFIG_SPL_BSS_MAX_SIZE)
ASSERT(__bss_end - __bss_start < (CONFIG_SPL_BSS_MAX_SIZE), \
       "SPL image BSS too big");
#endif
 
#if defined(CONFIG_SPL_MAX_FOOTPRINT)
ASSERT(__bss_end - _start < (CONFIG_SPL_MAX_FOOTPRINT), \
       "SPL image plus BSS too big");
#endif

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

如若内容造成侵权/违法违规/事实不符,请联系编程大学网进行投诉反馈email:xxxxxxxx@qq.com,一经查实,立即删除!

相关文章

c语言基础

一、环境搭建编译器(MinGW)下载地址:https://osdn.net/projects/mingw/downloads/68260/mingw-get-setup.exe 安装中一个注意细节 需要依次右键标记安装,然后点击Apply ChangesIDE(CLion)下载地址:https://www.jetbrains.com/clion/download/other.html 建议选择2022.1.…

每日学习之hive

Hive利用HDFS存储数据,利用MapReduce查询数据 Hive能将数据文件映射成为一张表,在hive中能够写sql处理的前提是针对表,而不是针对文件,因此需要将文件和表之间的对应关系描述记录清楚。映射信息专业的叫法称为元数据信息(元数据是指用来描述数据的数据)。元数据存储在关系…

python websocket

目录websocket简介实例python-socketio依赖客户端服务端flask-socketio依赖客户端服务端tornado-websocket依赖客户端服务端websockets依赖客户端服务端 websocket 简介 1. websocket特点全双工实时通信连接建立一次 可携带有状态信息 数据传输相比http更高效减少重复请求和响应…

cmake之解析宏或者函数参数

本文将介绍cmake如何解析函数或者宏对应的参数列表语法查询打开cmake手册,查询cmake_parse_arguments关键字即可, 如下图语法 cmake_parse_arguments(<prefix> <options> <one_value_keywords><multi_value_keywords> <args>...)cmake_parse_a…

4.6 Java成员方法的声明和调用

声明成员方法可以定义类的行为,行为表示一个对象能够做的事情或者能够从一个对象取得的信息。类的各种功能操作都是用方法来实现的,属性只不过提供了相应的数据。一个完整的方法通常包括方法名称、方法主体、方法参数和方法返回值类型,其结构如图 1 所示。 图 1 方法组成元…

app演唱会抢票全自动实现

抢票流程先去演唱会主页预选好场次、价格还有观演人,点击想看 (可选)手机后台杀掉大麦app任务 打开辅助app,给于对应权限 (可选)输入歌手名字,默认五月天 点击开抢按钮即可 如果点击开抢后,页面未开始自动跳转,可手动杀死大麦,再次切到辅助app点击开抢如果想终止辅助…