第4章:编译系统入门:ATF的Makefile体系、编译选项详解、如何配置目标平台
说实话,我第一次接触ATF的编译系统时,头都是大的。
几百行的Makefile,各种变量嵌套,还有一堆莫名其妙的编译选项。我当时就在想:这帮写ARM固件的人,是不是故意把编译系统搞这么复杂?
后来做多了才发现,其实ATF的Makefile体系设计得相当精巧。它要支持几十种不同的ARM平台,还要兼顾各种安全启动场景,复杂度是必然的。今天我就带你把它拆开揉碎了讲清楚。
4.1 ATF的Makefile体系:三层结构
ATF的编译系统,说白了就是三层结构:
- 顶层Makefile:在源码根目录,负责整体编译流程
- 平台Makefile:在plat/目录下,每个平台有自己的makefile
- 构建辅助脚本:在tools/和scripts/目录下,处理具体细节
我习惯把这三层比作「总指挥-地方官-办事员」的关系。顶层Makefile定大方向,平台Makefile管具体配置,辅助脚本干脏活累活。
核心要点:ATF的编译入口是顶层Makefile,但它会通过include机制把平台相关的配置拉进来。你不需要修改顶层Makefile,只需要配置好平台相关的变量就行。
4.2 编译选项详解:你必须要知道的几个关键变量
ATF的编译选项多到让人眼花缭乱。但别怕,真正常用的就那么几个。我列个表给你看:
| 变量名 | 作用 | 常用值 | 我的建议 |
|---|---|---|---|
| PLAT | 指定目标平台 | qemu, rpi3, hikey960等 | 必填,不填编译不了 |
| ARCH | 指定架构 | aarch32, aarch64 | 现在主流是aarch64 |
| DEBUG | 调试模式开关 | 0或1 | 开发阶段开1,发布时关掉 |
| LOG_LEVEL | 日志输出级别 | 10~50 | 调试用40,正式用20 |
| BL33 | 指定非安全侧镜像路径 | u-boot.bin路径 | 移植时经常要配 |
| ARM_ARCH_MAJOR | ARM架构主版本 | 7或8 | 根据你的核心来 |
举个例子,我当年移植ATF到某款国产芯片时,就踩过一个坑:LOG_LEVEL设成了50(最高级别),结果串口疯狂输出调试信息,启动时间从2秒变成了15秒。后来改成20,一切正常。
注意:DEBUG=1和LOG_LEVEL=50会显著增加镜像体积。我见过有人把这两个选项同时打开,结果BL31镜像从80KB膨胀到300多KB,直接撑爆了OCM空间。
4.3 如何配置目标平台:从零开始
配置目标平台,说白了就是告诉ATF:「我要给哪个板子编译,这个板子有什么特性」。具体分三步走:
4.3.1 第一步:选择平台目录
ATF的plat/目录下,每个子目录代表一个平台家族。比如:
plat/
├── arm/ # ARM官方开发板
│ ├── common/
│ ├── fvp/
│ └── juno/
├── hisilicon/ # 海思芯片
├── mediatek/ # 联发科
├── qemu/ # QEMU模拟器
└── rockchip/ # 瑞芯微
如果你要移植新平台,就在plat/下新建一个目录。我建议你复制一个已有的平台目录作为模板,比如复制qemu的,然后改改。这样省事很多。
4.3.2 第二步:编写平台描述文件
每个平台目录下,必须有一个platform.mk文件。这个文件告诉编译系统:
- 你的平台用哪个CPU核心(Cortex-A53还是A72?)
- 内存布局是怎样的(BL31放在哪?)
- 有哪些外设(串口、定时器、GIC版本)
- 安全特性(是否支持TBBR?是否用RoT?)
给你看个简化版的例子:
# 来自 plat/myboard/platform.mk
# 我自己的板子配置
# CPU核心配置
ARM_CORTEX_A53 := 1
ARM_ARCH_MAJOR := 8
# 内存布局
BL31_BASE := 0x10000000
BL31_SIZE := 0x00020000
# 外设配置
CONSOLE_UART_BASE := 0x1C090000
GICV3_SUPPORT := 1
# 安全特性
TRUSTED_BOARD_BOOT := 0
GENERATE_COT := 0
小技巧:我在做移植时,习惯先把TRUSTED_BOARD_BOOT关掉。等BL31能正常跑起来了,再逐步开启安全启动功能。否则问题混在一起,根本查不过来。
4.3.3 第三步:执行编译
配置好平台后,编译命令很简单:
# 基本编译命令
make PLAT=myboard DEBUG=1 LOG_LEVEL=40 all
# 如果你要生成FIP镜像(包含BL31+BL33)
make PLAT=myboard BL33=path/to/u-boot.bin fip
# 清理
make PLAT=myboard clean
嗯,这里要注意:make all会编译所有BL层(BL1、BL2、BL31、BL32)。但实际移植时,我建议你先只编BL31,因为它是ATF的核心,也是我们最常调试的部分。
4.4 编译系统的核心逻辑:一张图说清楚
说了这么多,你可能有点晕。我画了张图,把ATF编译系统的核心逻辑串起来:
这张图你看懂了吗?其实流程很简单:顶层Makefile根据PLAT变量找到对应的平台目录,然后加载平台Makefile和编译选项,最后执行编译生成镜像文件。
4.5 避坑指南:我踩过的三个坑
做ATF移植这几年,我在编译系统上栽过不少跟头。挑三个典型的跟你说说:
- 路径问题:我曾经把
BL33指向了一个不存在的路径,结果编译报错说找不到文件。我查了半天,最后发现是路径里有个拼写错误。建议你用绝对路径,或者用$(shell pwd)动态获取当前目录。 - 缓存问题:ATF的编译系统会缓存一些中间文件。如果你改了平台配置但没
make clean,可能会用旧的缓存。我习惯每次改配置都先make clean,虽然慢点但保险。 - 交叉编译链:ATF默认用
aarch64-linux-gnu-前缀的工具链。如果你用的工具链名字不一样,记得设置CROSS_COMPILE变量。我见过有人用aarch64-none-elf-,结果链接阶段报一堆错。
总结一下:ATF的编译系统其实不复杂,核心就是PLAT变量+平台Makefile+编译选项。你只要搞懂这三者的关系,剩下的就是填参数的事。我个人建议,刚开始移植时先用QEMU平台练手,把编译流程跑通了再上真硬件。
好了,这一章的内容就到这。编译系统是ATF移植的「入口」,搞懂了它,后面的事情就顺了。