第一次接触FIP这个概念时,我也是一头雾水。简单来说,FIP(Firmware Image Package)就像是一个专门为嵌入式系统设计的"固件集装箱",它可以把多个引导加载程序镜像和其他关键数据打包成一个文件。在实际项目中,我发现这种打包方式特别适合ARM架构的嵌入式设备启动流程。
举个例子,我们团队去年开发的一款物联网网关设备,就需要在启动时加载BL2、BL31、BL32等多个阶段的固件。如果每个固件都单独存放在Flash中,不仅管理起来麻烦,还容易因为地址配置错误导致启动失败。而使用FIP之后,所有固件被打包成一个文件,系统只需要记住一个存储位置就能按需加载各个组件。
FIP的核心价值在于:
对于嵌入式开发者来说,掌握FIP意味着你能:
打开一个FIP文件,最先看到的就是ToC(Table of Contents)表头。这个结构相当于整个包的元数据入口,我在分析TF-A源码时发现,它的定义严格遵循以下格式(对应firmware_image_package.h文件):
c复制typedef struct fip_toc_header {
char name[8]; // 固定为"TOC~HEAD"
uint64_t serial_number; // 创建工具生成的唯一序列号
uint64_t flags; // 标志位(平台可自定义部分)
} fip_toc_header_t;
实际项目中遇到过的一个坑是:某些自定义平台会修改flags字段的低32位,导致标准工具创建的FIP无法被识别。后来我们通过hook fiptool的创建过程解决了这个问题。
ToC条目就像图书馆的图书索引卡,每个条目对应一个固件组件。最关键的字段是这个UUID系统,TF-A预定义了这些常用组件的UUID:
c复制#define UUID_BL2_IMAGE \
{0x5f,0xf9,0xec,0x0b,0x4d,0x22,0x3e,0x4d,\
0xa5,0x44,0xc3,0x9d,0x81,0xc7,0x3f,0x0a}
在调试启动失败时,我经常用hexdump查看FIP文件,确认UUID是否匹配。有一次发现BL31的UUID被错误地设置成BL32的,导致系统卡在BL2阶段。
数据区存放着各个固件的二进制内容。这里有个重要细节:所有数据都是按64字节对齐存储的。这意味着即使你的BL2镜像实际大小是1025字节,它在FIP中也会占用1088字节的空间。
在TF-A源码目录中,fiptool的编译非常简单:
bash复制# 进入TF-A源码目录
cd arm-trusted-firmware
# 编译fiptool(DEBUG=1可生成调试版本)
make fiptool DEBUG=1
# 生成的工具路径
build/<platform>/debug/fiptool
建议把编译好的fiptool复制到系统路径:
bash复制sudo cp build/<platform>/debug/fiptool /usr/local/bin/
最基本的创建命令只需要指定输出文件名和要包含的镜像:
bash复制fiptool create --bl2 bl2.bin --bl31 bl31.bin --bl32 bl32.bin fip.bin
但实际项目中我们通常会添加更多参数:
bash复制fiptool create \
--bl2 bl2.bin \
--bl31 bl31.bin \
--bl32 bl32.bin \
--hw-config hw_config.dtb \
--tb-fw tb_fw.bin \
--tos-fw tos_fw.bin \
--nt-fw nt_fw.bin \
--fip-toc-version 2 \
--plat-toc-flags 0x1234 \
fip.bin
特别注意:--fip-toc-version参数在TF-A v2.5之后变为必选项,早期版本不加这个参数会导致创建失败。
查看FIP内容:
bash复制fiptool info fip.bin
输出示例:
code复制FIP: 6 components
- BL2: offset=0x60, size=0x5000
- BL31: offset=0x5060, size=0x7000
- BL32: offset=0xC060, size=0x3000
...
更新单个组件:
bash复制fiptool update --bl2 new_bl2.bin fip.bin
提取特定组件:
bash复制fiptool unpack --bl2 extracted_bl2.bin fip.bin
在TF-A的启动流程中,BL2阶段通过bl2_main()函数开始加载FIP。关键调用链如下:
code复制bl2_main()
└── bl2_load_images()
├── plat_get_image_source() // 平台特定实现
└── load_auth_image() // 带认证的加载
在Rockchip平台代码中,我找到了一个典型的实现:
c复制void __init plat_get_image_source(unsigned int image_id,
uintptr_t *dev_handle,
uintptr_t *image_spec)
{
/* 确定FIP存储在SPI Flash的0x8000位置 */
static const io_dev_connector_t *spi_dev_con;
static uintptr_t spi_dev_handle;
if (!spi_dev_handle) {
io_dev_open(..., &spi_dev_handle);
}
*dev_handle = spi_dev_handle;
*image_spec = (uintptr_t)&(struct plat_io_policy){
.fip_offset = 0x8000,
.fip_max_size = 0x200000
};
}
FIP驱动的核心在drivers/io/io_fip.c,其加载过程分为三步:
调试时可以在以下关键点添加打印:
c复制// io_fip.c
static int fip_open(io_dev_info_t *dev_info, const uintptr_t spec,
io_entity_t *entity)
{
NOTICE("FIP: Opening image with spec 0x%lx\n", spec);
...
}
问题现象:BL2卡在"Loading FIP"阶段
排查步骤:
典型错误:FIP文件被烧写到错误的Flash偏移地址。有一次我们团队花了三天时间才发现硬件工程师把烧写地址从0x8000改成了0x10000,但代码里没同步更新。
当Flash空间紧张时,可以通过这些方法优化:
压缩组件:在打包前用gzip压缩
bash复制gzip -9 bl31.bin
fiptool create --bl31 bl31.bin.gz --compress bl31:gzip fip.bin
精简组件:移除不需要的镜像
bash复制fiptool remove --tb-fw fip.bin
调整对齐:修改默认的64字节对齐(需改代码)
在生产环境中,我们建议:
启用TOC校验:在fiptool创建时添加HMAC
bash复制fiptool create --hmac-key hmac.key ... fip.bin
镜像签名:配合TF-A的认证框架使用
bash复制fiptool create --tb-fw-key tb_fw_key.pem ... fip.bin
防回滚:利用serial_number字段实现版本控制
查看详细加载过程:
bash复制make PLAT=<platform> LOG_LEVEL=50 DEBUG=1
内存检查:在BL2中dump加载后的镜像
c复制hexdump((void*)image_base, 256); // 打印前256字节
模拟器调试:使用FVP加载FIP
bash复制fvp --data fip.bin@0x80000000
在最近的一个项目中,我们发现BL31加载后立即崩溃。通过对比内存中的内容和原始文件,发现是DDR初始化不正确导致的数据损坏。最终通过在BL2阶段增加内存测试发现了这个问题。