1. 项目概述与核心挑战在嵌入式Linux开发领域为一块全新的定制板卡Custom Board适配并构建一个稳定、可启动的系统是每一位嵌入式工程师都必须跨越的“成人礼”。这个过程我们通常称之为BSPBoard Support Package移植。今天我想以NXP的i.MX8QXP平台为例结合L5.4版本的Linux BSP和大家深入聊聊如何从零开始完成一个定制板卡的完整启动镜像构建。这不仅仅是照着官方文档敲命令更是理解从芯片上电到Linux内核接管系统的整个“交响乐团”如何协同工作的过程。当你拿到一块全新的i.MX8/8X系列板卡上面可能搭载了不同的内存、存储、外设甚至不同的核心配置比如是否启用了Cortex-M4协处理器。官方的评估板MEK镜像直接烧录进去大概率是“点不亮”的。核心问题在于系统的启动流程——从ROM Code、SCFW系统控制器固件、ATFARM Trusted Firmware、U-Boot SPL/Full到最终的Linux内核——每一步都严重依赖于对当前硬件平台的精确描述和配置。我们的工作就是为这套复杂的启动链“量身定做”一套配置让它们能在新板卡上和谐地运行起来。这其中设备树Device Tree的裁剪与适配、多级启动镜像的组装、以及针对新外设的驱动集成构成了移植工作的三大支柱。理解并掌握它们你就能摆脱对预编译SDK的依赖真正掌控自己的硬件平台。2. 启动流程深度解析与移植总览在动手修改一行代码之前我们必须先成为这个系统启动过程的“导演”清楚每一个“演员”固件组件在什么时间、什么地点、以什么方式登场。i.MX8系列的启动流程是典型的多阶段、安全引导过程远比传统的单阶段U-Boot复杂。2.1 i.MX8系列启动链全景图当芯片上电后固化在ROM中的代码ROM Code会首先执行。它的任务很简单从预设的启动设备如SD卡、eMMC、QSPI NOR的特定偏移地址处读取一个叫做flash.bin的复合镜像文件。这个flash.bin就是我们整个移植工作的最终产出物它是一个容器Container里面按顺序打包了多个独立的二进制映像。ROM Code会按照容器头部的描述将这些映像加载到芯片内部或外部的不同内存区域SCFW (System Controller Firmware) 被加载到芯片内部的TCM紧耦合内存。它是整个系统的“大管家”运行在独立的Cortex-M4核心上与用户可用的M4不同负责芯片最底层的电源、时钟、复位和资源分区管理。这是整个启动流程的基石任何对电源域、时钟源或外设资源的分配都必须通过SCFW的配置来完成。SECO FW (Security Controller Firmware) 被加载到安全域。它处理与硬件安全相关的功能如加密、密钥管理、信任根等。对于大多数应用开发我们不需要修改它但必须使用与BSP版本严格匹配的二进制文件。ATF (ARM Trusted Firmware) 被加载到DDR内存。它实现了ARM的TrustZone安全架构为安全世界Secure World和正常世界Normal World提供切换和管理。我们的Linux内核和用户态程序都运行在正常世界。U-Boot 同样被加载到DDR。这里可能有两种情况非SPL模式 ROM Code直接加载完整的U-Boot映像到DDR并执行。SPL模式 ROM Code先加载一个精简的U-Boot SPLSecondary Program Loader到芯片内部的OCRAM片上RAM中执行。SPL的任务是初始化DDR等更复杂的外设然后再从启动设备加载包含ATF和完整U-Boot的“第二阶段”容器到DDR。SPL模式常用于启动设备接口或内存初始化代码体积较大无法全部放入ROM Code限制的加载空间的情况。2.2 移植工作分解我们究竟要做什么基于上述流程为一块新板卡移植BSP可以分解为以下几个核心任务它们环环相扣硬件描述与配置生成 为新板卡的DDR内存颗粒生成正确的初始化参数DDR PHY训练固件。这是系统能访问外部内存的前提错误配置将导致后续所有组件都无法加载。SCFW板级支持 创建或修改SCFW的板级配置文件board.c,board.h,pin_mux.c等告诉“大管家”新板卡上的电源、时钟、引脚复用是如何连接的。ATF配置 主要涉及资源划分Resource Partitioning。如果板卡上使用了Cortex-M4协处理器需要在ATF中定义哪些外设如I2C、CAN、UART分配给M4核心哪些留给A核Linux。U-Boot移植创建板级目录和配置文件 复制参考板配置重命名为新板卡名称。裁剪与适配设备树 这是U-Boot移植的核心。需要根据实际硬件启用或禁用设备树中的节点特别是对于SPL为了减小其体积必须进行激进裁剪。构建启动镜像容器 使用imx-mkimage工具将上述编译好的SCFW、ATF、U-Boot及可能的M4固件二进制文件按照正确的顺序和参数打包成最终的flash.bin。Linux内核移植创建设备树源文件 基于参考板DTS文件修改为新板卡的硬件连接如LCD、以太网PHY、摄像头接口等。集成新外设驱动 如果板卡上有内核未默认支持的外设需要将对应的驱动源码集成到内核树中并在配置中启用。整个移植过程可以看作是一个“自底向上”的配置传递过程底层的DDR配置和SCFW板级支持为上层提供了稳定的硬件运行环境ATF的资源划分决定了软硬件资源的归属U-Boot的设备树描述了U-Boot阶段可访问的设备最后Linux内核的设备树和驱动构成了完整的操作系统运行时视图。3. 底层基石DDR配置与SCFW板级移植万丈高楼平地起系统的稳定性始于内存。对于i.MX8这类高性能应用处理器DDR内存的初始化是一个精密而复杂的过程无法通过简单的寄存器配置完成。3.1 DDR初始化不仅仅是配置寄存器i.MX8的DDR子系统包含一个DDR控制器和一个DDR PHY物理层。PHY需要在上电后进行一系列训练Training以补偿PCB走线带来的时序和信号完整性偏差。这个过程由SCFW调用DDR PHY固件来完成。因此我们的任务不是直接写寄存器而是为这个训练过程提供正确的配置参数。NXP提供了名为“Retention Power Analysis (RPA)”的工具通常包含在BSP发布包中用于生成这些参数。你需要做的是获取与你板卡上使用的具体DDR颗粒型号、位宽、拓扑结构如单通道/双通道完全一致的硬件设计文件。在RPA工具中导入这些设计文件工具会根据芯片的I/O特性、PCB的布线长度等信息计算出一套最优的初始化参数并生成一个C语言头文件例如ddr_stress_test.c。这个头文件中包含了DDR频率、时序参数CL, tRCD, tRP, tRAS等、以及PHY训练所需的复杂寄存器序列。实操心得DDR配置是硬件依赖最强的部分。务必与硬件工程师确认DDR颗粒的完整料号和数据手册。一个常见的坑是使用了“兼容”型号但某个次级时序参数不匹配导致系统在高负载或高低温下不稳定。生成配置后强烈建议运行DDR压力测试工具如memtester进行长时间烤机测试。3.2 SCFW板级文件创建告诉“大管家”硬件细节SCFW的代码位于imx-sc-firmware仓库中。为新板卡添加支持主要是在src/board/mx8qx_mek以MEK板为例的目录结构上进行复制和修改。复制并重命名板级目录cp -r imx-sc-firmware/src/board/mx8qx_mek imx-sc-firmware/src/board/mx8qx_myboard随后需要将新目录下所有文件中的mx8qx_mek字符串替换为mx8qx_myboard并更新相关的Makefile和Kconfig文件将新板卡添加到编译选项中。修改核心板级文件board.c: 这是最重要的文件。你需要修改board_system_config()函数其中定义了资源分区Resource Partitioning 指定哪些外设如I2C0, UART0, MIPI CSI等分配给哪个核心A35集群、M4等。这必须与后续ATF中的配置保持一致。板级初始化 任何需要在SCFW早期执行的硬件初始化例如配置某些关键的电源或时钟。pin_mux.c: 定义芯片引脚的复用功能。你必须根据原理图将每个被使用的引脚配置为正确的功能例如将某个引脚设置为I2C1的SDA而非默认的GPIO。一个引脚配置错误就可能导致外设无法通信。board.h: 定义板级相关的常量如DDR配置数据的指针。你需要将之前用RPA工具生成的ddr_stress_test.c中的数组引用到这里。ddr.c和ddr.h: 通常直接包含或引用生成的DDR配置数据。编译SCFW 修改完成后在SCFW源码根目录通过指定板型配置进行编译make clean make BOARDmx8qx_myboard RB0 # 假设芯片为B0版本编译产物scfw_tcm.bin就是我们需要的SCFW固件。注意事项SCFW的编译依赖特定的工具链且其版本与BSP版本绑定紧密。务必使用NXP官方为该版本BSP推荐的编译器。编译错误经常源于工具链不匹配或板级目录中的文件没有完全重命名。4. 安全与资源管理ATF配置详解ATF在i.MX8平台上的移植工作相对轻量其主要关注点在于“资源划分”和“电源管理”。ATF的配置信息通过设备树fdts目录下的文件传递。4.1 资源划分Resource Partitioning这是多核系统特别是包含Cortex-M4的关键配置。在imx-atf源码的plat/imx/imx8qx/imx8qx.dtsi或板级特定的dts文件中你可以找到类似下面的节点m4_partition: partition0 { compatible imx8m-partition; reg 0x0 0x80000000 0x0 0x10000000; // M4核心的内存区域 core-id 0; // 核心ID status okay; };你需要根据在SCFW的board.c中定义的资源分配方案来同步修改ATF设备树中的分区设置。例如如果你将I2C0分配给了M4核心那么在Linux的设备树中I2C0节点就应该被禁用status “disabled”;或者通过RPMSG等通信机制进行虚拟化访问。4.2 电源管理配置ATF也负责一部分系统级的电源状态切换。对于新板卡你需要检查电源管理相关的设备树节点确保它们与板卡的电源树设计相符。不过对于大多数定制板卡如果电源设计与参考板类似这部分通常无需改动。4.3 编译ATFATF的编译较为直接make PLATimx8qx bl31编译完成后在build/imx8qx/release/目录下会生成bl31.bin文件。这个文件将被整合进最终的启动镜像。5. 承上启下的引导者U-Boot深度移植U-Boot是连接底层固件和上层操作系统的桥梁其移植工作量通常最大。我们需要创建一个新的板级支持并精心裁剪其设备树。5.1 创建U-Boot板级支持复制板级模板 在U-Boot源码中找到最接近的参考板如imx8qxp_mek。cp -r board/freescale/imx8qxp_mek board/freescale/imx8qxp_myboard cp include/configs/imx8qxp_mek.h include/configs/imx8qxp_myboard.h重命名与适配 将新目录和头文件中的所有mek引用改为myboard。修改Kconfig,Makefile等文件添加新板型的选项。创建Defconfig 复制参考板的defconfig并重命名。cp configs/imx8qxp_mek_defconfig configs/imx8qxp_myboard_defconfig在这个文件中你需要确保CONFIG_TARGET_IMX8QXP_MYKBOARDy被正确设置。5.2 设备树裁剪SPL与Full U-Boot的差异设备树是U-Boot移植的重中之重。i.MX8的U-Boot通常使用两套设备树一套给SPL用一套给完整的U-Boot用。完整U-Boot设备树 在arch/arm/dts/目录下创建imx8qxp-myboard.dts。它通常包含一个顶层的.dts文件和多个.dtsi包含文件。你需要根据实际硬件启用或禁用不需要的外设节点如将不存在的以太网PHY、LCD屏幕的节点status设为disabled。修改引脚复用pinctrl配置与SCFW中的配置保持一致。调整内存节点匹配板载DDR的实际大小。修改启动参数如bootargs中的控制台、根文件系统位置等。SPL设备树裁剪 这是优化启动速度的关键。SPL运行在空间有限的OCRAM中其设备树必须极致精简。官方文档给出了明确指导只保留带有u-boot,dm-pre-reloc或u-boot,dm-spl属性的节点。创建一个SPL专用的设备树文件例如imx8qxp-myboard-u-boot.dtsi注意后缀约定。在这个文件中你通常只保留以下节点dram(内存控制器) 用于DDR初始化。启动设备节点 如usdhc1(SD卡)、flexspi(QSPI Flash)取决于你的启动介质。必要的时钟和复位控制器。串口节点用于调试输出。你可以参考arch/arm/dts/fsl-imx8qxp-mek-u-boot.dtsi这个官方例子它清晰地展示了如何裁剪一个庞大的设备树只留下SPL必需的“骨架”。核心原理u-boot,dm-pre-reloc属性告诉U-Boot的设备模型Driver Model这个驱动需要在重定位relocation之前也就是在SPL阶段就被初始化。只有这些驱动对应的设备树节点才需要被包含进SPL的设备树中。通过这种裁剪SPL镜像大小可以显著减少确保能被ROM Code加载到OCRAM。5.3 编译U-Boot完成上述修改后即可编译U-Boot# 设置交叉编译工具链例如 export ARCHarm64 export CROSS_COMPILEaarch64-linux-gnu- # 生成配置 make imx8qxp_myboard_defconfig # 编译 make -j$(nproc)编译完成后你会得到两个关键文件u-boot.bin: 完整的U-Boot镜像。spl/u-boot-spl.bin: SPL镜像如果配置中启用了CONFIG_SPL。6. 镜像组装的艺术使用imx-mkimage构建flash.bin至此我们已经准备好了所有“乐高积木”scfw_tcm.bin,bl31.bin,u-boot.bin,u-boot-spl.bin可选以及可能的M4固件m4_image.bin。imx-mkimage工具就是按照乐高说明书把它们拼装成最终成品flash.bin的组装台。6.1 准备工作收集组件首先你需要从NXP官网下载与BSP版本对应的imx-mkimage工具源码和SECO FW固件。假设你的芯片是i.MX8QXP C0版本。将scfw_tcm.bin复制到imx-mkimage/iMX8QX/。将mx8qxc0-ahab-container.imgSECO FW复制到同一目录。将bl31.bin复制到同一目录。将u-boot.bin和u-boot-spl.bin复制到同一目录。可选如果有M4程序将编译好的m4_image.bin也复制过来。6.2 理解Makefile目标选择你的启动配方imx-mkimage/iMX8QX/soc.mak文件中预定义了许多make目标target每个目标对应一种启动组件的组合方式。理解它们至关重要flash: 最基础的目标。包含SCFW、SECO FW、ATF和完整U-Boot。不包含SPL。此时A核Cortex-A35的启动地址是DDR的0x80000000。flash_linux_m4: 在flash基础上增加了SPL镜像。这是最常用的带SPL的启动方式。当使用此目标时ROM Code只加载SPL到OCRAM地址0x00100000。SPL会初始化DDR然后从启动设备加载一个名为u-boot-atf-container.img的容器它内部包含了ATF和完整U-Boot到DDR。参数-dcd skip被使用这告诉ROM Code“DDR初始化由后面的SCFW来完成你不用管了”。这优化了启动流程。flash_regression_linux_m4: 在flash基础上增加了M4镜像。注意其中的-flags 0x00200000选项它设置了SC_BD_FLAGS_ALT_CONFIG标志这个标志会触发SCFW中特定的板级配置board_system_config通常用于为M4创建独立的分区。flash_linux_m4_ddr: 与flash_linux_m4类似但假设M4程序是从DDR启动的地址0x88000000。此时不能使用-dcd skip因为ROM Code需要初始化DDR来加载M4镜像。flash_linux_m4_xip: 用于M4从QSPI NOR Flash直接执行XIP, Execute In Place的场景。除了M4地址不同还需要特别注意FSPI/QSPI头文件imx-mkimage/scripts/fspi_header。这个头文件包含了ROM Code初始化FlexSPI控制器所需的参数如设备类型、数据线宽度、时钟频率等必须根据板载Flash芯片的型号进行修改。6.3 生成与烧写镜像假设我们要生成一个带SPL和M4从TCM启动的镜像命令如下cd imx-mkimage make SOCiMX8QX REVC0 flash_linux_m4SOC指定芯片系列REV指定芯片版本影响SECO FW的选择TARGET就是上面提到的目标名。执行成功后会在iMX8QX/目录下生成flash.bin。烧写到SD卡进行测试是最快捷的方式# 假设SD卡在系统中为 /dev/sdX sudo dd ifiMX8QX/flash.bin of/dev/sdX bs1K seek32 convfsync syncseek32表示从SD卡的第32个扇区即32KB偏移开始写入这是i.MX8 ROM Code规定的启动镜像位置。7. 操作系统适配Linux内核与设备树移植当U-Boot成功启动打印出提示符后下一步就是加载并启动Linux内核。这需要为内核提供正确的设备树二进制文件DTB和内核镜像Image。7.1 创建设备树与驱动文件内核移植的核心工作是设备树。你需要在内核源码的arch/arm64/boot/dts/freescale/目录下为你的新板卡创建.dts和.dtsi文件。复制并修改DTS 从最接近的参考板如imx8qxp-mek.dts复制重命名为imx8qxp-myboard.dts。这个文件通常通过#include包含芯片级的.dtsi和板级的.dtsi。imx8qxp.dtsi: 芯片通用定义一般不动。imx8x-myboard.dtsi: 你创建的板级通用定义在这里启用或禁用外设节点修改引脚控制设置寄存器参数如以太网PHY的地址、LCD时序等。设备树连接关系 对于复杂的显示或视频采集链路设备树需要精确描述硬件连接拓扑。例如原文中提到的LVDS0连接ldb1 - ds90ub947 - ds90ub948 - it6263 - HDMI screen。在设备树中这需要通过port和endpoint子节点来表述各个器件之间的输入输出关系确保驱动能正确识别数据流路径。添加新驱动 如果板卡使用了内核未默认集成的外设如特定的摄像头传感器、串行器/解串器SerDes你需要将驱动源代码放入内核树的相应目录如drivers/media/platform/imx8/并修改对应的Kconfig和Makefile使它们能被编译进内核或模块。7.2 修改编译系统修改arch/arm64/boot/dts/freescale/Makefile添加你的设备树编译目标dtb-$(CONFIG_ARCH_FSL_IMX8QXP) imx8qxp-myboard.dtb如果你添加了新驱动同样需要更新对应子目录的Makefile和Kconfig。7.3 编译与烧写内核# 使用与U-Boot相同的工具链 export ARCHarm64 export CROSS_COMPILEaarch64-linux-gnu- # 使用默认配置或你的板级defconfig make imx_v8_defconfig # 如果需要可以通过 menuconfig 进一步配置 # make menuconfig # 编译内核和DTB make -j$(nproc) Image dtbs编译产物arch/arm64/boot/Image: 内核镜像。arch/arm64/boot/dts/freescale/imx8qxp-myboard.dtb: 设备树二进制文件。将这两个文件拷贝到SD卡的第一个分区FAT格式即boot分区U-Boot就可以通过fatload或ext4load命令加载它们并启动了。8. 调试技巧与常见问题排查实录移植过程很少一帆风顺。以下是一些我踩过坑后总结的排查思路和技巧。8.1 启动阶段问题排查现象可能原因排查思路上电后完全无输出DDR配置错误SCFW未正常运行启动设备或镜像偏移错误。1. 检查串口接线和波特率早期SCFW输出通常为115200。2. 使用仿真器如JTAG连接查看PC指针是否卡在ROM Code区域。如果卡住首先怀疑DDR初始化失败或flash.bin未正确烧录到指定偏移。3. 确认flash.bin是否包含所有必要组件且使用正确的make目标生成。SPL能打印但卡在加载后续镜像u-boot-atf-container.img生成或加载失败DDR初始化在SPL阶段不完整。1. 检查SPL日志看它是否成功从存储设备读取了容器镜像。2. 确认imx-mkimage使用的组件版本匹配且u-boot.bin和bl31.bin是配套编译的。3. 检查SPL的设备树是否包含了正确的存储设备节点如usdhc1。U-Boot启动后无法加载内核内核或DTB文件路径错误存储设备驱动未初始化bootcmd设置错误。1. 在U-Boot中手动使用fatls mmc 0:1或ext4ls mmc 0:1查看boot分区文件。2. 尝试手动加载并启动fatload mmc 0:1 ${loadaddr} Image; fatload mmc 0:1 ${fdt_addr} myboard.dtb; booti ${loadaddr} - ${fdt_addr}。3. 检查环境变量bootargs中的根文件系统参数是否正确。内核卡在Uncompressing Linux...设备树内存节点错误内核与设备树不匹配。1. 检查DTS中的内存节点/memory80000000其reg属性是否与物理内存大小一致。2. 确认使用的DTB文件是否为当前编译内核所生成。8.2 外设驱动问题排查I2C/SPI设备不识别 首先在U-Boot下使用i2c probe或spi read等命令测试总线是否工作。如果不工作检查设备树中该总线的引脚复用pinctrl配置是否正确是否与SCFW配置冲突。时钟配置是否正确。上拉电阻是否已焊接。以太网不通 检查PHY的复位引脚、MDIO总线地址。在设备树中确认PHY兼容性字符串compatible与驱动匹配。有时需要为特定的PHY添加补丁或配置寄存器。显示异常 这是最复杂的问题之一。确保显示控制器的时钟和电源域已正确开启。设备树中的显示管线连接ports,endpoint顺序与硬件完全一致。屏时序参数如display-timings节点与屏幕规格书一致。背光使能引脚和PWM配置正确。8.3 一个关键的心得善用调试串口在整个移植过程中第一个需要确保打通的必然是调试串口。在SCFW、U-Boot、内核的代码中尽早添加串口输出信息。在SCFW和U-Boot的板级初始化函数中甚至在DDR初始化之前就尝试初始化一个最简单的串口并输出字符比如“A”、“B”、“C”可以帮助你精确判断代码执行到了哪个阶段。当系统“死”得毫无声息时这些简单的字符就是救命的稻草。移植i.MX8 BSP是一项系统工程它要求开发者对硬件、固件、驱动和构建系统都有深入的理解。这个过程充满挑战但当你看到自己定制的板卡最终成功引导进入Linux命令行时那种成就感是无与伦比的。希望这篇结合了原理与实操的指南能为你照亮前行的路。记住耐心阅读芯片参考手册、善用官方工具和社区资源、以及严谨的调试方法是攻克所有难题的不二法门。
i.MX8QXP BSP移植实战:从零构建定制板卡启动镜像
1. 项目概述与核心挑战在嵌入式Linux开发领域为一块全新的定制板卡Custom Board适配并构建一个稳定、可启动的系统是每一位嵌入式工程师都必须跨越的“成人礼”。这个过程我们通常称之为BSPBoard Support Package移植。今天我想以NXP的i.MX8QXP平台为例结合L5.4版本的Linux BSP和大家深入聊聊如何从零开始完成一个定制板卡的完整启动镜像构建。这不仅仅是照着官方文档敲命令更是理解从芯片上电到Linux内核接管系统的整个“交响乐团”如何协同工作的过程。当你拿到一块全新的i.MX8/8X系列板卡上面可能搭载了不同的内存、存储、外设甚至不同的核心配置比如是否启用了Cortex-M4协处理器。官方的评估板MEK镜像直接烧录进去大概率是“点不亮”的。核心问题在于系统的启动流程——从ROM Code、SCFW系统控制器固件、ATFARM Trusted Firmware、U-Boot SPL/Full到最终的Linux内核——每一步都严重依赖于对当前硬件平台的精确描述和配置。我们的工作就是为这套复杂的启动链“量身定做”一套配置让它们能在新板卡上和谐地运行起来。这其中设备树Device Tree的裁剪与适配、多级启动镜像的组装、以及针对新外设的驱动集成构成了移植工作的三大支柱。理解并掌握它们你就能摆脱对预编译SDK的依赖真正掌控自己的硬件平台。2. 启动流程深度解析与移植总览在动手修改一行代码之前我们必须先成为这个系统启动过程的“导演”清楚每一个“演员”固件组件在什么时间、什么地点、以什么方式登场。i.MX8系列的启动流程是典型的多阶段、安全引导过程远比传统的单阶段U-Boot复杂。2.1 i.MX8系列启动链全景图当芯片上电后固化在ROM中的代码ROM Code会首先执行。它的任务很简单从预设的启动设备如SD卡、eMMC、QSPI NOR的特定偏移地址处读取一个叫做flash.bin的复合镜像文件。这个flash.bin就是我们整个移植工作的最终产出物它是一个容器Container里面按顺序打包了多个独立的二进制映像。ROM Code会按照容器头部的描述将这些映像加载到芯片内部或外部的不同内存区域SCFW (System Controller Firmware) 被加载到芯片内部的TCM紧耦合内存。它是整个系统的“大管家”运行在独立的Cortex-M4核心上与用户可用的M4不同负责芯片最底层的电源、时钟、复位和资源分区管理。这是整个启动流程的基石任何对电源域、时钟源或外设资源的分配都必须通过SCFW的配置来完成。SECO FW (Security Controller Firmware) 被加载到安全域。它处理与硬件安全相关的功能如加密、密钥管理、信任根等。对于大多数应用开发我们不需要修改它但必须使用与BSP版本严格匹配的二进制文件。ATF (ARM Trusted Firmware) 被加载到DDR内存。它实现了ARM的TrustZone安全架构为安全世界Secure World和正常世界Normal World提供切换和管理。我们的Linux内核和用户态程序都运行在正常世界。U-Boot 同样被加载到DDR。这里可能有两种情况非SPL模式 ROM Code直接加载完整的U-Boot映像到DDR并执行。SPL模式 ROM Code先加载一个精简的U-Boot SPLSecondary Program Loader到芯片内部的OCRAM片上RAM中执行。SPL的任务是初始化DDR等更复杂的外设然后再从启动设备加载包含ATF和完整U-Boot的“第二阶段”容器到DDR。SPL模式常用于启动设备接口或内存初始化代码体积较大无法全部放入ROM Code限制的加载空间的情况。2.2 移植工作分解我们究竟要做什么基于上述流程为一块新板卡移植BSP可以分解为以下几个核心任务它们环环相扣硬件描述与配置生成 为新板卡的DDR内存颗粒生成正确的初始化参数DDR PHY训练固件。这是系统能访问外部内存的前提错误配置将导致后续所有组件都无法加载。SCFW板级支持 创建或修改SCFW的板级配置文件board.c,board.h,pin_mux.c等告诉“大管家”新板卡上的电源、时钟、引脚复用是如何连接的。ATF配置 主要涉及资源划分Resource Partitioning。如果板卡上使用了Cortex-M4协处理器需要在ATF中定义哪些外设如I2C、CAN、UART分配给M4核心哪些留给A核Linux。U-Boot移植创建板级目录和配置文件 复制参考板配置重命名为新板卡名称。裁剪与适配设备树 这是U-Boot移植的核心。需要根据实际硬件启用或禁用设备树中的节点特别是对于SPL为了减小其体积必须进行激进裁剪。构建启动镜像容器 使用imx-mkimage工具将上述编译好的SCFW、ATF、U-Boot及可能的M4固件二进制文件按照正确的顺序和参数打包成最终的flash.bin。Linux内核移植创建设备树源文件 基于参考板DTS文件修改为新板卡的硬件连接如LCD、以太网PHY、摄像头接口等。集成新外设驱动 如果板卡上有内核未默认支持的外设需要将对应的驱动源码集成到内核树中并在配置中启用。整个移植过程可以看作是一个“自底向上”的配置传递过程底层的DDR配置和SCFW板级支持为上层提供了稳定的硬件运行环境ATF的资源划分决定了软硬件资源的归属U-Boot的设备树描述了U-Boot阶段可访问的设备最后Linux内核的设备树和驱动构成了完整的操作系统运行时视图。3. 底层基石DDR配置与SCFW板级移植万丈高楼平地起系统的稳定性始于内存。对于i.MX8这类高性能应用处理器DDR内存的初始化是一个精密而复杂的过程无法通过简单的寄存器配置完成。3.1 DDR初始化不仅仅是配置寄存器i.MX8的DDR子系统包含一个DDR控制器和一个DDR PHY物理层。PHY需要在上电后进行一系列训练Training以补偿PCB走线带来的时序和信号完整性偏差。这个过程由SCFW调用DDR PHY固件来完成。因此我们的任务不是直接写寄存器而是为这个训练过程提供正确的配置参数。NXP提供了名为“Retention Power Analysis (RPA)”的工具通常包含在BSP发布包中用于生成这些参数。你需要做的是获取与你板卡上使用的具体DDR颗粒型号、位宽、拓扑结构如单通道/双通道完全一致的硬件设计文件。在RPA工具中导入这些设计文件工具会根据芯片的I/O特性、PCB的布线长度等信息计算出一套最优的初始化参数并生成一个C语言头文件例如ddr_stress_test.c。这个头文件中包含了DDR频率、时序参数CL, tRCD, tRP, tRAS等、以及PHY训练所需的复杂寄存器序列。实操心得DDR配置是硬件依赖最强的部分。务必与硬件工程师确认DDR颗粒的完整料号和数据手册。一个常见的坑是使用了“兼容”型号但某个次级时序参数不匹配导致系统在高负载或高低温下不稳定。生成配置后强烈建议运行DDR压力测试工具如memtester进行长时间烤机测试。3.2 SCFW板级文件创建告诉“大管家”硬件细节SCFW的代码位于imx-sc-firmware仓库中。为新板卡添加支持主要是在src/board/mx8qx_mek以MEK板为例的目录结构上进行复制和修改。复制并重命名板级目录cp -r imx-sc-firmware/src/board/mx8qx_mek imx-sc-firmware/src/board/mx8qx_myboard随后需要将新目录下所有文件中的mx8qx_mek字符串替换为mx8qx_myboard并更新相关的Makefile和Kconfig文件将新板卡添加到编译选项中。修改核心板级文件board.c: 这是最重要的文件。你需要修改board_system_config()函数其中定义了资源分区Resource Partitioning 指定哪些外设如I2C0, UART0, MIPI CSI等分配给哪个核心A35集群、M4等。这必须与后续ATF中的配置保持一致。板级初始化 任何需要在SCFW早期执行的硬件初始化例如配置某些关键的电源或时钟。pin_mux.c: 定义芯片引脚的复用功能。你必须根据原理图将每个被使用的引脚配置为正确的功能例如将某个引脚设置为I2C1的SDA而非默认的GPIO。一个引脚配置错误就可能导致外设无法通信。board.h: 定义板级相关的常量如DDR配置数据的指针。你需要将之前用RPA工具生成的ddr_stress_test.c中的数组引用到这里。ddr.c和ddr.h: 通常直接包含或引用生成的DDR配置数据。编译SCFW 修改完成后在SCFW源码根目录通过指定板型配置进行编译make clean make BOARDmx8qx_myboard RB0 # 假设芯片为B0版本编译产物scfw_tcm.bin就是我们需要的SCFW固件。注意事项SCFW的编译依赖特定的工具链且其版本与BSP版本绑定紧密。务必使用NXP官方为该版本BSP推荐的编译器。编译错误经常源于工具链不匹配或板级目录中的文件没有完全重命名。4. 安全与资源管理ATF配置详解ATF在i.MX8平台上的移植工作相对轻量其主要关注点在于“资源划分”和“电源管理”。ATF的配置信息通过设备树fdts目录下的文件传递。4.1 资源划分Resource Partitioning这是多核系统特别是包含Cortex-M4的关键配置。在imx-atf源码的plat/imx/imx8qx/imx8qx.dtsi或板级特定的dts文件中你可以找到类似下面的节点m4_partition: partition0 { compatible imx8m-partition; reg 0x0 0x80000000 0x0 0x10000000; // M4核心的内存区域 core-id 0; // 核心ID status okay; };你需要根据在SCFW的board.c中定义的资源分配方案来同步修改ATF设备树中的分区设置。例如如果你将I2C0分配给了M4核心那么在Linux的设备树中I2C0节点就应该被禁用status “disabled”;或者通过RPMSG等通信机制进行虚拟化访问。4.2 电源管理配置ATF也负责一部分系统级的电源状态切换。对于新板卡你需要检查电源管理相关的设备树节点确保它们与板卡的电源树设计相符。不过对于大多数定制板卡如果电源设计与参考板类似这部分通常无需改动。4.3 编译ATFATF的编译较为直接make PLATimx8qx bl31编译完成后在build/imx8qx/release/目录下会生成bl31.bin文件。这个文件将被整合进最终的启动镜像。5. 承上启下的引导者U-Boot深度移植U-Boot是连接底层固件和上层操作系统的桥梁其移植工作量通常最大。我们需要创建一个新的板级支持并精心裁剪其设备树。5.1 创建U-Boot板级支持复制板级模板 在U-Boot源码中找到最接近的参考板如imx8qxp_mek。cp -r board/freescale/imx8qxp_mek board/freescale/imx8qxp_myboard cp include/configs/imx8qxp_mek.h include/configs/imx8qxp_myboard.h重命名与适配 将新目录和头文件中的所有mek引用改为myboard。修改Kconfig,Makefile等文件添加新板型的选项。创建Defconfig 复制参考板的defconfig并重命名。cp configs/imx8qxp_mek_defconfig configs/imx8qxp_myboard_defconfig在这个文件中你需要确保CONFIG_TARGET_IMX8QXP_MYKBOARDy被正确设置。5.2 设备树裁剪SPL与Full U-Boot的差异设备树是U-Boot移植的重中之重。i.MX8的U-Boot通常使用两套设备树一套给SPL用一套给完整的U-Boot用。完整U-Boot设备树 在arch/arm/dts/目录下创建imx8qxp-myboard.dts。它通常包含一个顶层的.dts文件和多个.dtsi包含文件。你需要根据实际硬件启用或禁用不需要的外设节点如将不存在的以太网PHY、LCD屏幕的节点status设为disabled。修改引脚复用pinctrl配置与SCFW中的配置保持一致。调整内存节点匹配板载DDR的实际大小。修改启动参数如bootargs中的控制台、根文件系统位置等。SPL设备树裁剪 这是优化启动速度的关键。SPL运行在空间有限的OCRAM中其设备树必须极致精简。官方文档给出了明确指导只保留带有u-boot,dm-pre-reloc或u-boot,dm-spl属性的节点。创建一个SPL专用的设备树文件例如imx8qxp-myboard-u-boot.dtsi注意后缀约定。在这个文件中你通常只保留以下节点dram(内存控制器) 用于DDR初始化。启动设备节点 如usdhc1(SD卡)、flexspi(QSPI Flash)取决于你的启动介质。必要的时钟和复位控制器。串口节点用于调试输出。你可以参考arch/arm/dts/fsl-imx8qxp-mek-u-boot.dtsi这个官方例子它清晰地展示了如何裁剪一个庞大的设备树只留下SPL必需的“骨架”。核心原理u-boot,dm-pre-reloc属性告诉U-Boot的设备模型Driver Model这个驱动需要在重定位relocation之前也就是在SPL阶段就被初始化。只有这些驱动对应的设备树节点才需要被包含进SPL的设备树中。通过这种裁剪SPL镜像大小可以显著减少确保能被ROM Code加载到OCRAM。5.3 编译U-Boot完成上述修改后即可编译U-Boot# 设置交叉编译工具链例如 export ARCHarm64 export CROSS_COMPILEaarch64-linux-gnu- # 生成配置 make imx8qxp_myboard_defconfig # 编译 make -j$(nproc)编译完成后你会得到两个关键文件u-boot.bin: 完整的U-Boot镜像。spl/u-boot-spl.bin: SPL镜像如果配置中启用了CONFIG_SPL。6. 镜像组装的艺术使用imx-mkimage构建flash.bin至此我们已经准备好了所有“乐高积木”scfw_tcm.bin,bl31.bin,u-boot.bin,u-boot-spl.bin可选以及可能的M4固件m4_image.bin。imx-mkimage工具就是按照乐高说明书把它们拼装成最终成品flash.bin的组装台。6.1 准备工作收集组件首先你需要从NXP官网下载与BSP版本对应的imx-mkimage工具源码和SECO FW固件。假设你的芯片是i.MX8QXP C0版本。将scfw_tcm.bin复制到imx-mkimage/iMX8QX/。将mx8qxc0-ahab-container.imgSECO FW复制到同一目录。将bl31.bin复制到同一目录。将u-boot.bin和u-boot-spl.bin复制到同一目录。可选如果有M4程序将编译好的m4_image.bin也复制过来。6.2 理解Makefile目标选择你的启动配方imx-mkimage/iMX8QX/soc.mak文件中预定义了许多make目标target每个目标对应一种启动组件的组合方式。理解它们至关重要flash: 最基础的目标。包含SCFW、SECO FW、ATF和完整U-Boot。不包含SPL。此时A核Cortex-A35的启动地址是DDR的0x80000000。flash_linux_m4: 在flash基础上增加了SPL镜像。这是最常用的带SPL的启动方式。当使用此目标时ROM Code只加载SPL到OCRAM地址0x00100000。SPL会初始化DDR然后从启动设备加载一个名为u-boot-atf-container.img的容器它内部包含了ATF和完整U-Boot到DDR。参数-dcd skip被使用这告诉ROM Code“DDR初始化由后面的SCFW来完成你不用管了”。这优化了启动流程。flash_regression_linux_m4: 在flash基础上增加了M4镜像。注意其中的-flags 0x00200000选项它设置了SC_BD_FLAGS_ALT_CONFIG标志这个标志会触发SCFW中特定的板级配置board_system_config通常用于为M4创建独立的分区。flash_linux_m4_ddr: 与flash_linux_m4类似但假设M4程序是从DDR启动的地址0x88000000。此时不能使用-dcd skip因为ROM Code需要初始化DDR来加载M4镜像。flash_linux_m4_xip: 用于M4从QSPI NOR Flash直接执行XIP, Execute In Place的场景。除了M4地址不同还需要特别注意FSPI/QSPI头文件imx-mkimage/scripts/fspi_header。这个头文件包含了ROM Code初始化FlexSPI控制器所需的参数如设备类型、数据线宽度、时钟频率等必须根据板载Flash芯片的型号进行修改。6.3 生成与烧写镜像假设我们要生成一个带SPL和M4从TCM启动的镜像命令如下cd imx-mkimage make SOCiMX8QX REVC0 flash_linux_m4SOC指定芯片系列REV指定芯片版本影响SECO FW的选择TARGET就是上面提到的目标名。执行成功后会在iMX8QX/目录下生成flash.bin。烧写到SD卡进行测试是最快捷的方式# 假设SD卡在系统中为 /dev/sdX sudo dd ifiMX8QX/flash.bin of/dev/sdX bs1K seek32 convfsync syncseek32表示从SD卡的第32个扇区即32KB偏移开始写入这是i.MX8 ROM Code规定的启动镜像位置。7. 操作系统适配Linux内核与设备树移植当U-Boot成功启动打印出提示符后下一步就是加载并启动Linux内核。这需要为内核提供正确的设备树二进制文件DTB和内核镜像Image。7.1 创建设备树与驱动文件内核移植的核心工作是设备树。你需要在内核源码的arch/arm64/boot/dts/freescale/目录下为你的新板卡创建.dts和.dtsi文件。复制并修改DTS 从最接近的参考板如imx8qxp-mek.dts复制重命名为imx8qxp-myboard.dts。这个文件通常通过#include包含芯片级的.dtsi和板级的.dtsi。imx8qxp.dtsi: 芯片通用定义一般不动。imx8x-myboard.dtsi: 你创建的板级通用定义在这里启用或禁用外设节点修改引脚控制设置寄存器参数如以太网PHY的地址、LCD时序等。设备树连接关系 对于复杂的显示或视频采集链路设备树需要精确描述硬件连接拓扑。例如原文中提到的LVDS0连接ldb1 - ds90ub947 - ds90ub948 - it6263 - HDMI screen。在设备树中这需要通过port和endpoint子节点来表述各个器件之间的输入输出关系确保驱动能正确识别数据流路径。添加新驱动 如果板卡使用了内核未默认集成的外设如特定的摄像头传感器、串行器/解串器SerDes你需要将驱动源代码放入内核树的相应目录如drivers/media/platform/imx8/并修改对应的Kconfig和Makefile使它们能被编译进内核或模块。7.2 修改编译系统修改arch/arm64/boot/dts/freescale/Makefile添加你的设备树编译目标dtb-$(CONFIG_ARCH_FSL_IMX8QXP) imx8qxp-myboard.dtb如果你添加了新驱动同样需要更新对应子目录的Makefile和Kconfig。7.3 编译与烧写内核# 使用与U-Boot相同的工具链 export ARCHarm64 export CROSS_COMPILEaarch64-linux-gnu- # 使用默认配置或你的板级defconfig make imx_v8_defconfig # 如果需要可以通过 menuconfig 进一步配置 # make menuconfig # 编译内核和DTB make -j$(nproc) Image dtbs编译产物arch/arm64/boot/Image: 内核镜像。arch/arm64/boot/dts/freescale/imx8qxp-myboard.dtb: 设备树二进制文件。将这两个文件拷贝到SD卡的第一个分区FAT格式即boot分区U-Boot就可以通过fatload或ext4load命令加载它们并启动了。8. 调试技巧与常见问题排查实录移植过程很少一帆风顺。以下是一些我踩过坑后总结的排查思路和技巧。8.1 启动阶段问题排查现象可能原因排查思路上电后完全无输出DDR配置错误SCFW未正常运行启动设备或镜像偏移错误。1. 检查串口接线和波特率早期SCFW输出通常为115200。2. 使用仿真器如JTAG连接查看PC指针是否卡在ROM Code区域。如果卡住首先怀疑DDR初始化失败或flash.bin未正确烧录到指定偏移。3. 确认flash.bin是否包含所有必要组件且使用正确的make目标生成。SPL能打印但卡在加载后续镜像u-boot-atf-container.img生成或加载失败DDR初始化在SPL阶段不完整。1. 检查SPL日志看它是否成功从存储设备读取了容器镜像。2. 确认imx-mkimage使用的组件版本匹配且u-boot.bin和bl31.bin是配套编译的。3. 检查SPL的设备树是否包含了正确的存储设备节点如usdhc1。U-Boot启动后无法加载内核内核或DTB文件路径错误存储设备驱动未初始化bootcmd设置错误。1. 在U-Boot中手动使用fatls mmc 0:1或ext4ls mmc 0:1查看boot分区文件。2. 尝试手动加载并启动fatload mmc 0:1 ${loadaddr} Image; fatload mmc 0:1 ${fdt_addr} myboard.dtb; booti ${loadaddr} - ${fdt_addr}。3. 检查环境变量bootargs中的根文件系统参数是否正确。内核卡在Uncompressing Linux...设备树内存节点错误内核与设备树不匹配。1. 检查DTS中的内存节点/memory80000000其reg属性是否与物理内存大小一致。2. 确认使用的DTB文件是否为当前编译内核所生成。8.2 外设驱动问题排查I2C/SPI设备不识别 首先在U-Boot下使用i2c probe或spi read等命令测试总线是否工作。如果不工作检查设备树中该总线的引脚复用pinctrl配置是否正确是否与SCFW配置冲突。时钟配置是否正确。上拉电阻是否已焊接。以太网不通 检查PHY的复位引脚、MDIO总线地址。在设备树中确认PHY兼容性字符串compatible与驱动匹配。有时需要为特定的PHY添加补丁或配置寄存器。显示异常 这是最复杂的问题之一。确保显示控制器的时钟和电源域已正确开启。设备树中的显示管线连接ports,endpoint顺序与硬件完全一致。屏时序参数如display-timings节点与屏幕规格书一致。背光使能引脚和PWM配置正确。8.3 一个关键的心得善用调试串口在整个移植过程中第一个需要确保打通的必然是调试串口。在SCFW、U-Boot、内核的代码中尽早添加串口输出信息。在SCFW和U-Boot的板级初始化函数中甚至在DDR初始化之前就尝试初始化一个最简单的串口并输出字符比如“A”、“B”、“C”可以帮助你精确判断代码执行到了哪个阶段。当系统“死”得毫无声息时这些简单的字符就是救命的稻草。移植i.MX8 BSP是一项系统工程它要求开发者对硬件、固件、驱动和构建系统都有深入的理解。这个过程充满挑战但当你看到自己定制的板卡最终成功引导进入Linux命令行时那种成就感是无与伦比的。希望这篇结合了原理与实操的指南能为你照亮前行的路。记住耐心阅读芯片参考手册、善用官方工具和社区资源、以及严谨的调试方法是攻克所有难题的不二法门。