U-Boot 2026年9月13日 185 分钟

正点原子U-Boot移植过程深度解析(修订版)

前言 本文档目标: 修订说明: 本版依据 rel_imx_4.1.15_2.1.0_ga 源码逐项核对,重点修正了 Kc...
本文目录 展开目录

前言

本文档目标:

  • 深度解析正点原子 I.MX6ULL-ALPHA 开发板的实际移植步骤
  • 解释每个操作的目的、原因和必要性
  • 将移植步骤映射到 U-Boot 启动流程,理解影响范围
  • 对比理论分析与实际移植的差异
  • 追踪每个修改在后续流程中的作用

修订说明: 本版依据 rel_imx_4.1.15_2.1.0_ga 源码逐项核对,重点修正了 Kconfig 的定位和层级、include/config.h 的真实生成机制、arch/arm/imx-common/ 的版本路径、板级 dram_init() 与 imx_ddr_size() 的实际实现,以及 mkimage 命令行示例。原文中将 Kconfig 说成图形配置文件、把 config_defaults.h 描述为宏拼接入口等内容,均已改为与本树源码一致的表述。

核心问题:

  1. 为什么实际移植只需要 4 个步骤,而启动流程有那么多阶段?
  2. 这些配置文件的修改如何影响最终的启动行为?
  3. 移植工作的本质是什么?

目录

  1. 移植步骤总览
  2. 步骤一:添加开发板默认配置文件
  3. 步骤二:添加开发板头文件
  4. 步骤三:添加板级文件夹
  5. 步骤四:修改 Kconfig 配置文件
  6. 配置系统工作原理
  7. 移植步骤与启动流程的映射
  8. 理论分析 vs 实际移植
  9. 完整影响链路追踪
  10. 基础移植与外设适配

1. 移植步骤总览

1.1 四大步骤概览

graph TD
    A[开始移植] --> B["步骤1: 创建 defconfig<br/>configs/<br/>mx6ull_alientek_emmc_defconfig"]
    B --> C["步骤2: 创建头文件<br/>include/configs/<br/>mx6ull_alientek_emmc.h"]
    C --> D["步骤3: 创建板级目录<br/>board/freescale/<br/>mx6ull_alientek_emmc/"]
    D --> E["步骤4: 修改 Kconfig<br/>arch/arm/cpu/armv7/<br/>mx6/Kconfig"]
    E --> F[配置完成]
    
    F --> G["执行 make<br/>mx6ull_alientek_emmc_defconfig"]
    G --> H[执行 make]
    H --> I[生成 u-boot.imx]
    
    style B fill:#ffd43b,stroke:#fab005
    style C fill:#ffd43b,stroke:#fab005
    style D fill:#ff6b6b,stroke:#c92a2a
    style E fill:#51cf66,stroke:#37b24d

1.2 步骤对照表

步骤操作涉及文件目的难度
1创建 defconfigconfigs/mx6ull_alientek_emmc_defconfig定义板级默认配置⭐⭐
2创建头文件include/configs/mx6ull_alientek_emmc.h定义编译时宏配置⭐⭐
3创建板级目录board/freescale/mx6ull_alientek_emmc/提供板级初始化代码和 DCD 表⭐⭐⭐⭐⭐
4修改 Kconfigarch/arm/cpu/armv7/mx6/Kconfig将新板集成到配置系统⭐⭐

1.3 移植本质

关键理解:

移植工作的本质是 把板级硬件差异接入既有的启动框架。这通常包含配置基础设施、启动镜像数据和板级钩子函数,不能简单理解为只搭建配置文件。

实际移植 ≠ 只修改通用启动流程
实际移植 = 配置框架 + 启动镜像数据 + 板级代码

为什么?

  1. SoC 级启动框架大多是通用的:_start、lowlevel_init、board_init_f 等代码在本树中由 ARM/U-Boot 公共代码和 i.MX 支持代码提供,但它们会调用板级钩子
  2. 板级差异既有数据也有代码:DDR 初始化数据、引脚复用、PHY 地址、存储介质和板级初始化函数都可能不同
  3. 配置系统决定编译边界:Kconfig/defconfig 选择符号和目录,Makefile 再据此决定编译哪些对象;运行时是否调用某个钩子还取决于对应的 CONFIG_* 条件和初始化数组

2. 步骤一:添加开发板默认配置文件

2.1 操作内容

文件: configs/mx6ull_alientek_emmc_defconfig

操作: 复制 mx6ull_14x14_evk_emmc_defconfig → 重命名 → 修改内容

修改后的内容:

CONFIG_SYS_EXTRA_OPTIONS="IMX_CONFIG=board/freescale/mx6ull_alientek_emmc/imximage.cfg,MX6ULL_EVK_EMMC_REWORK"
CONFIG_ARM=y
CONFIG_ARCH_MX6=y
CONFIG_TARGET_MX6ULL_ALIENTEK_EMMC=y
CONFIG_CMD_GPIO=y

2.2 为什么这么做?

问题:defconfig 文件的作用是什么?

答案: defconfig 是板级配置的 入口文件,定义了这块板子编译 U-Boot 时的默认选项。

工作流程:

sequenceDiagram
    participant User as 用户
    participant Make as make 系统
    participant Kconfig as Kconfig 系统
    participant Config as .config 文件
    
    User->>Make: make mx6ull_alientek_emmc_defconfig
    Make->>Kconfig: 读取 configs/mx6ull_alientek_emmc_defconfig
    Kconfig->>Config: 生成 .config 文件
    Config->>Config: 展开所有依赖配置项
    User->>Make: make
    Make->>Config: 读取 .config
    Make->>Make: 根据配置选择编译哪些文件

2.3 逐行解析

第 1 行:CONFIG_SYS_EXTRA_OPTIONS

CONFIG_SYS_EXTRA_OPTIONS="IMX_CONFIG=board/freescale/mx6ull_alientek_emmc/imximage.cfg,MX6ULL_EVK_EMMC_REWORK"

作用: 通过本树仍保留的兼容机制传递额外的配置选项,包含两个关键参数。SYS_EXTRA_OPTIONS 在顶层 Kconfig 中已经标记为 DEPRECATED;它适合解释这棵旧版代码树的现状,新板移植应优先使用正式的 Kconfig 符号。

参数值作用
IMX_CONFIGboard/freescale/mx6ull_alientek_emmc/imximage.cfg指定 i.MX 镜像配置源文件;该文件在未启用插件时包含 DCD 项,在启用插件时选择 PLUGIN
MX6ULL_EVK_EMMC_REWORK(额外宏)兼容参考板 eMMC 改版代码路径;本树把它转成 CONFIG_MX6ULL_EVK_EMMC_REWORK,用于选择 eMMC 引脚和卡检测分支

IMX_CONFIG 的重要性:

graph LR
    A[IMX_CONFIG 指定] --> B[imximage.cfg]
    B --> C[DCD 表<br/>DDR 初始化寄存器配置]
    C --> D[BootROM 执行]
    D --> E{DDR 初始化成功?}
    E -->|是| F[加载 U-Boot 到 DDR<br/>✅ 启动成功]
    E -->|否| G[❌ 无法启动]
    
    style B fill:#ff6b6b,stroke:#c92a2a,stroke-width:3px
    style C fill:#ff6b6b,stroke:#c92a2a,stroke-width:3px

影响链路:

    defconfig 中的 IMX_CONFIG
    ↓
    scripts/Makefile.autoconf 将其展开为 CONFIG_IMX_CONFIG
    ↓
    C 预处理生成 imximage.cfg.cfgtmp
    ↓
    arch/arm/imx-common/Makefile 调用 mkimage
    ↓
    将 DCD 或插件描述写入 u-boot.imx 镜像
    ↓
BootROM 加载镜像时执行 DCD 表
    ↓
初始化 DDR 控制器
    ↓
U-Boot 代码才能运行

为什么要修改这一行?

因为参考板 EVK 的 imximage.cfg 路径是:

board/freescale/mx6ullevk/imximage.cfg

命名说明(易混淆点): 参考板 EVK 在不同层次上用了两个名字,都是对的:

层次名称由谁决定
Kconfig 符号TARGET_MX6ULL_14X14_EVKarch/arm/cpu/armv7/mx6/Kconfig:195
defconfig 文件configs/mx6ull_14x14_evk_defconfig文件名本身
板级目录board/freescale/mx6ullevk/SYS_BOARD default "mx6ullevk"
板级头文件include/configs/mx6ullevk.hSYS_CONFIG_NAME default "mx6ullevk"

即 EVK 的符号名带 14x14,目录名不带。而我们新建的 alientek 板三者统一为 mx6ull_alientek_emmc,不存在这个割裂。

我们的新板路径是:

board/freescale/mx6ull_alientek_emmc/imximage.cfg

必须修改路径,否则编译系统会找错文件!

第 2-3 行:架构配置

CONFIG_ARM=y
CONFIG_ARCH_MX6=y

作用: 指定处理器架构

  • CONFIG_ARM=y:这是 ARM 架构芯片
  • CONFIG_ARCH_MX6=y:具体是 NXP i.MX6 系列

影响: 决定编译系统包含哪些架构相关代码

    CONFIG_ARM=y
        ↓
    进入 arch/arm/ 配置和编译规则
        ↓
    CONFIG_ARCH_MX6=y
        ↓
    引入 arch/arm/cpu/armv7/mx6/ 与 arch/arm/imx-common/ 的规则
        ↓
    编译与 i.MX6 相关的代码

版本提示: 本文所用代码树是 NXP rel_imx_4.1.15_2.1.0_ga(基线 U-Boot 2016.03), i.MX 公共代码位于 arch/arm/imx-common/。较新的上游版本已将相关代码迁移到 arch/arm/mach-imx/,本树中不存在 mach-imx 目录, 参考新版资料时注意区分。

第 4 行:目标板配置(最关键)

CONFIG_TARGET_MX6ULL_ALIENTEK_EMMC=y

作用: 这是新板的 唯一标识符

为什么这是最关键的配置?

这个宏会触发一系列连锁反应:

graph TD
    A["CONFIG_TARGET_MX6ULL_<br/>ALIENTEK_EMMC=y"] --> B["Kconfig 系统匹配"]
    B --> C["arch/arm/cpu/armv7/mx6/<br/>Kconfig 中的配置块"]
    C --> D["设置 CONFIG_SYS_BOARD=<br/>mx6ull_alientek_emmc"]
    C --> E["设置 CONFIG_SYS_VENDOR=<br/>freescale"]
    C --> F["设置 CONFIG_SYS_CONFIG_NAME=<br/>mx6ull_alientek_emmc"]
    
    F --> G[自动 include 头文件<br/>include/configs/mx6ull_alientek_emmc.h]
    D --> H[指定板级目录<br/>board/freescale/mx6ull_alientek_emmc/]
    H --> I[编译该目录下的 .c 文件]
    I --> J[链接板级初始化函数]
    
    style A fill:#ff6b6b,stroke:#c92a2a,stroke-width:3px
    style F fill:#ffd43b,stroke:#fab005,stroke-width:2px
    style H fill:#ffd43b,stroke:#fab005,stroke-width:2px

影响链路详解:

  1. 头文件自动包含


    // U-Boot 编译系统会自动生成:
    #include <configs/mx6ull_alientek_emmc.h>

  2. 板级目录自动编译


    # make 系统会编译:
    board/freescale/mx6ull_alientek_emmc/mx6ull_alientek_emmc.o

  3. 板级函数自动调用


    // 启动流程中会调用板级函数:
    board_init_f() {
    // common/board_f.c 的初始化数组
    board_early_init_f(); // ← 板级文件提供
    dram_init(); // ← 板级文件提供
    }

第 5 行:GPIO 命令支持

CONFIG_CMD_GPIO=y

作用: 启用 GPIO 命令,可以在 U-Boot 命令行中操作 GPIO

# U-Boot 命令行示例:
=> gpio status
=> gpio set 100
=> gpio clear 100

为什么需要? 调试硬件时很有用(测试 LED、按键等)

2.4 这个文件在后续的影响

编译时:

make mx6ull_alientek_emmc_defconfig
    ↓
读取 configs/mx6ull_alientek_emmc_defconfig
    ↓
生成 .config 文件
    ↓
展开所有 Kconfig 依赖
    ↓
确定编译哪些文件、包含哪些头文件

运行时:

BootROM 读取 u-boot.imx
    ↓
执行嵌入的 DCD 表(IMX_CONFIG 指定)
    ↓
初始化 DDR
    ↓
加载 U-Boot 代码到 DDR
    ↓
跳转到 _start(启动流程开始)

3. 步骤二:添加开发板头文件

3.1 操作内容

文件: include/configs/mx6ull_alientek_emmc.h

操作: 复制 include/configs/mx6ullevk.h → 重命名 → 修改头文件保护宏

修改内容:

// 原来:
#ifndef __MX6ULLEVK_CONFIG_H
#define __MX6ULLEVK_CONFIG_H

// 修改为:
#ifndef __MX6ULL_ALIENTEK_EMMC_CONFIG_H
#define __MX6ULL_ALIENTEK_EMMC_CONFIG_H

3.2 为什么这么做?

问题:板级头文件的作用是什么?

答案: 定义板级的 编译时配置宏,影响代码编译行为。

典型配置内容:

/* DDR 配置 */
#define PHYS_SDRAM_SIZE    SZ_512M   // 512MB DDR

/* 串口配置 */
#define CONFIG_MXC_UART_BASE   UART1_BASE

/* 网络配置 */
#define CONFIG_FEC_ENET_DEV    1
#define CONFIG_FEC_MXC_PHYADDR 0x1

/* 启动参数 */
#define CONFIG_BOOTCOMMAND \
    "mmc dev 1; " \
    "fatload mmc 1:1 0x80800000 zImage; " \
    "bootz 0x80800000"

3.3 头文件保护宏为什么要改名

拷贝 mx6ullevk.h 之后,文件开头的保护宏还是参考板的名字,需要改成新板的名字:

#ifndef __MX6ULL_ALEITENK_EMMC_CONFIG_H  // 首次包含:未定义,进入
#define __MX6ULL_ALEITENK_EMMC_CONFIG_H  // 定义宏

/* 配置内容 */

#endif  // 第二次包含:已定义,跳过

注意拼写: 正点原子官方源码里这个宏实际写的是 __MX6ULL_ALEITENK_EMMC_CONFIG_H, ALEITENK 是 ALIENTEK 的拼写错误。它只是个内部保护宏,拼错不影响编译, 所以一直保留至今。你自己移植时写成 __MX6ULL_ALIENTEK_EMMC_CONFIG_H 也完全没问题。

改名的真实原因是什么?

常见的说法是”如果不改名,同时包含 mx6ullevk.h 和 mx6ull_alientek_emmc.h 时, 第二个会因为宏已定义而被整体跳过”。这个说法在 U-Boot 中并不成立 —— 因为 include/config.h 只会包含一个板级配置头文件(见 3.4 节),两个板级头文件 同时出现在一次编译中的场景根本不存在。

改名的实际理由是工程规范层面的:

理由说明
可读性宏名与文件名对应,看到 __MX6ULLEVK_CONFIG_H 却在 alientek 头文件里,会误导读者
防止将来误用如果哪天有代码显式 #include 了两个板级头文件(比如做配置对比工具),重名会导致静默失效
避免与其它头文件冲突保护宏是全局命名空间,重名的隐患是长期的

也就是说:不改也能编译通过并正常启动,但这是应该改的坏味道,不是必须修的 bug。

3.4 这个文件如何被使用?

自动包含机制:

关键点:拼接不是由 C 预处理器完成的,而是由 Makefile 完成的。 Makefile 根据 CONFIG_SYS_CONFIG_NAME 生成一个中间文件 include/config.h, 把板子名硬写进 #include 行里。

graph LR
    A["defconfig:<br/>CONFIG_TARGET_MX6ULL_<br/>ALIENTEK_EMMC=y"] --> B["Kconfig 系统<br/>board/.../Kconfig"]
    B --> C[".config 中生成<br/>CONFIG_SYS_CONFIG_NAME=<br/>&quot;mx6ull_alientek_emmc&quot;"]
    C --> D["Makefile 目标<br/>include/config.h<br/>scripts/Makefile.autoconf"]
    D --> E["生成文本行:<br/>#include &lt;configs/mx6ull_alientek_emmc.h&gt;"]
    E --> F["编译 common.h 时<br/>包含 include/config.h"]

    style A fill:#51cf66,stroke:#37b24d
    style F fill:#ff6b6b,stroke:#c92a2a

实际生成的文件(本树中的真实内容):

/* include/config.h —— Automatically generated - do not edit */
#define CONFIG_IMX_CONFIG    board/freescale/mx6ull_alientek_emmc/imximage.cfg
#define CONFIG_MX6ULL_EVK_EMMC_REWORK    1
#define CONFIG_BOARDDIR board/freescale/mx6ull_alientek_emmc
#include <config_defaults.h>
#include <config_uncmd_spl.h>
#include <configs/mx6ull_alientek_emmc.h>   /* ← 板级头文件在这里被包含 */
#include <asm/config.h>
#include <config_fallbacks.h>

可以看到 CONFIG_SYS_EXTRA_OPTIONS 里的 IMX_CONFIG=... 和 MX6ULL_EVK_EMMC_REWORK 也在这里被拆成了两个 #define,这正是步骤一第 1 行配置的落地位置。

常见误传,需要澄清: 有资料称 include/config_defaults.h 中存在如下代码:

#define __STRINGIFY(x) #x
#define STRINGIFY(x) __STRINGIFY(x)
#include <configs/STRINGIFY(CONFIG_SYS_CONFIG_NAME).h>

这是错误的,两个层面都不成立:

  1. 文件内容不对。 本树 include/config_defaults.h 只有少量默认宏,
    全部是 CONFIG_BOOTM_LINUX、CONFIG_GZIP、CONFIG_PARTITIONS
    这类”给所有板子的合理默认值”,没有任何 STRINGIFY,也没有 #include <configs/...>。
  2. 语法上根本行不通。 C 预处理器的 #include 只接受 <...>、"..."
    或一个展开后形如二者之一的宏,不支持在尖括号内部做宏替换后拼路径。
    这段代码写出来是编译错误。

真正做”名字→路径”这一步的是 Makefile(scripts/Makefile.autoconf), 它用 shell 把字符串拼好后写进 include/config.h。C 语言侧看到的永远是 一个已经确定的、字面的文件名。

3.5 这个文件在后续的影响

编译时影响:

需要配置宏的编译单元通过 include/common.h
    ↓
include/common.h 包含 include/config.h
    ↓
include/config.h 包含 include/configs/mx6ull_alientek_emmc.h
    ↓
所有宏定义生效
    ↓
影响条件编译(#ifdef)
    ↓
决定哪些代码被编译

运行时影响:

// 本树的实际实现:DDR 大小由 MMDC 配置读取
int dram_init(void) {
    gd->ram_size = imx_ddr_size();
    return 0;
}

// 串口驱动会使用头文件提供的基地址
#define CONFIG_MXC_UART_BASE UART1_BASE

4. 步骤三:添加板级文件夹

4.1 操作内容

目录: board/freescale/mx6ull_alientek_emmc/

操作: 复制 board/freescale/mx6ullevk/ → 重命名 → 修改多个文件

修改的文件:

  1. Makefile
  2. imximage.cfg
  3. Kconfig
  4. MAINTAINERS
  5. mx6ullevk.c → mx6ull_alientek_emmc.c

4.2 为什么这是最关键的步骤?

答案:这个目录包含板级的 运行时代码 和 硬件初始化脚本

目录结构:

board/freescale/mx6ull_alientek_emmc/
├── Makefile                         # 编译规则
├── Kconfig                          # 配置定义
├── MAINTAINERS                      # 维护者信息
├── imximage.cfg                     # ⭐ DCD 表(DDR 初始化)
├── mx6ull_alientek_emmc.c           # ⭐ 板级初始化代码
├── plugin.S                         # DDR 初始化插件源码(可选)
└── plugin.bin                       # 由 Makefile 生成的插件二进制(可选)

4.3 修改 1:Makefile

原内容(对应参考板文件中的插件路径):

obj-y  := mx6ullevk.o

修改为:

obj-y  := mx6ull_alientek_emmc.o

作用: 告诉 make 系统编译 mx6ull_alientek_emmc.c

影响链路:

make
    ↓
读取 board/freescale/mx6ull_alientek_emmc/Makefile
    ↓
编译 mx6ull_alientek_emmc.c
    ↓
生成 mx6ull_alientek_emmc.o
    ↓
链接到 u-boot.elf
    ↓
运行时调用其中的 board_init()、dram_init() 等函数

4.4 修改 2:imximage.cfg(最关键!)

原内容:

PLUGIN board/freescale/mx6ullevk/plugin.bin 0x00907000

修改为:

PLUGIN board/freescale/mx6ull_alientek_emmc/plugin.bin 0x00907000

作用: 在启用 CONFIG_USE_PLUGIN 时指定插件路径。当前 defconfig 没有启用该选项,因此本树默认走 #else 分支中的 DCD DATA 表;两种方式不能混为一谈。

⚠️ 注意:这个文件是整个移植最核心的部分!

imximage.cfg 的作用

这个文件是传给 mkimage 的 i.MX 镜像配置源。未启用插件时,它包含 DCD(Device Configuration Data)表;启用 CONFIG_USE_PLUGIN 时则使用 PLUGIN 行。DCD 是一系列寄存器配置指令:

/*
 * DCD Table - DDR 初始化
 */

/* 当前板级文件中的实际 DCD 片段 */
DATA 4 0x020c4068 0xffffffff
DATA 4 0x020E04B4 0x000C0000
DATA 4 0x021B000C 0x676B52F3
DATA 4 0x021B0010 0xB66D0B63
DATA 4 0x021B0014 0x01FF00DB
DATA 4 0x021B0000 0x84180000
/* ... 其余寄存器配置必须保持与硬件和工具输出一致 ... */

上面的寄存器值只用于说明本树的格式,不能据此推断任意 DDR 型号。更换颗粒、容量、位宽或 PCB 后,应重新生成并验证完整 DCD,而不是只改一行十六进制数。

imximage.cfg 文件从哪里来?如何获取正确的 DCD 参数?

⭐ 这是最关键的问题!答案分 3 种情况:

情况 1:使用相同 DDR 芯片且硬件拓扑一致的参考板(最简单)

步骤:

  1. 找到使用相同 DDR 芯片、数据位宽、片选数量和布线拓扑的 NXP 官方参考板
  2. 直接复制其 imximage.cfg 文件
  3. 修改板级路径,并逐项核对 IOMUX、时钟、位宽和片选配置

示例:

# 检查参考板和目标板使用的 DDR 芯片;型号必须以原理图和数据手册为准
# 下面的型号只作示例,不能从 U-Boot 源码反推硬件型号

# 如果你的板子也用相同的 DDR 芯片:
cp board/freescale/mx6ullevk/imximage.cfg \
   board/freescale/mx6ull_alientek_emmc/imximage.cfg

# 可作为起点;只有硬件拓扑和参考板一致且实机验证通过,才可以保持 DCD 不变

判断是否相同芯片:

  • 查看你的硬件原理图(DDR 芯片型号)
  • 对比参考板原理图或用户手册
  • 型号相同只是必要条件;位宽、片选、地址/数据线连接和电源时序也必须一致
  • 只有硬件拓扑一致并通过 DDR 压力测试,才能复用完整 DCD
情况 2:使用不同 DDR 芯片(需要工具生成)

推荐工具:NXP DDR Stress Test Tool(官方工具)

获取途径:

  • NXP 官网:https://www.nxp.com
  • 搜索:”i.MX DDR Stress Test Tool”
  • 需要注册 NXP 账号下载(免费)

使用步骤:

graph TD
    A[准备硬件信息] --> B[启动 DDR Stress Test Tool]
    B --> C[输入 DDR 芯片参数]
    C --> D[输入 PCB 设计参数]
    D --> E[工具自动计算]
    E --> F[生成 DCD 寄存器配置]
    F --> G[导出为 imximage.cfg 格式]
    G --> H[复制到板级目录]
    
    C --> C1["厂商:<br/>以硬件资料为准"]
    C --> C2["型号:<br/>以数据手册为准"]
    C --> C3["容量:<br/>按位宽和颗粒数计算"]
    C --> C4[位宽: 16-bit]
    C --> C5[时序: DDR3-800]
    
    D --> D1[走线长度]
    D --> D2[阻抗匹配]
    D --> D3[拓扑结构]
    
    style A fill:#ffd43b
    style F fill:#ff6b6b
    style G fill:#51cf66

需要准备的信息:

信息类型来源示例
DDR 芯片型号硬件原理图以实际型号为准
容量DDR 芯片数据手册示例:单颗 256M x 16 约为 512MiB;颗粒数量需按原理图确认
位宽原理图(数据线数量)16-bit 或 32-bit
时序等级芯片数据手册DDR3-800 (PC3-6400)
时序参数芯片数据手册tRCD=13.75ns, tRP=13.75ns…
PCB 走线长度PCB 设计文件数据线约 50mm, 地址线约 60mm
ODT 电阻原理图120Ω

工具界面(简化示意):

┌─────── NXP DDR Stress Test Tool ───────┐
│                                          │
│ [1] 选择 SoC: i.MX6ULL                  │
│                                          │
│ [2] DDR 配置:                            │
│     厂商: 以硬件资料为准 ▼               │
│     型号: 以数据手册为准                 │
│     容量: 按颗粒数和位宽计算             │
│     位宽: 16-bit                         │
│     类型: DDR3L                          │
│                                          │
│ [3] 时序参数:                            │
│     时钟: 396MHz (DDR3-800)             │
│     CL: 6   tRCD: 14   tRP: 14          │
│     tRAS: 35   tRC: 49   tRFC: 160      │
│                                          │
│ [4] PCB 参数:                            │
│     数据线长度: 50mm                     │
│     地址线长度: 60mm                     │
│     ODT: 120Ω                            │
│                                          │
│ [生成配置] [运行测试] [导出 DCD]         │
└──────────────────────────────────────────┘

生成的 DCD 配置示例:

/* Generated by NXP DDR Stress Test Tool */
/* DDR 参数示例:具体型号、容量和频率以硬件资料为准 */

DATA 4 0x020c4018 0x00260324    /* PLL: 396MHz */
DATA 4 0x020e04b4 0x000C0000    /* IOMUX: DRAM_D0 */
/* ... 数百行寄存器配置 ... */
DATA 4 0x021B0000 0x84180000    /* 示例:具体值以工具输出为准 */
情况 3:无工具手动计算(不推荐,容易出错)

仅在无法获取工具时使用,步骤:

  1. 阅读芯片手册 – i.MX6ULL 参考手册(3000+ 页)
  2. 阅读 DDR 芯片数据手册 – 获取时序参数
  3. 手动计算寄存器值

计算思路(不提供可直接写入寄存器的数值):

假设:
- DDR 时钟 396MHz → 周期 2.53ns
- DDR 芯片要求 tRCD = 13.75ns

时序周期数通常按向上取整:
tRCD_cycles = ceil(13.75ns / 2.53ns) = 6

然后按照 i.MX6ULL 参考手册规定的字段位置、编码方式和约束,换算为 MMDC 配置。

字段位置和编码不能凭经验填写;不同寄存器还有单位、最小值和校准约束,实际移植应以官方工具输出和实机压力测试为准。

⚠️ 警告:这种方法:

  • 需要深入理解 DDR 原理
  • 容易出错(一个参数错误就无法启动)
  • 耗时长(需要计算几十个参数)
  • 强烈建议使用官方工具!

实战:不同 DDR 配置的修改点

场景:从 512MB DDR 改为 256MB DDR

需要重新生成的内容(imximage.cfg):

/* 不要只修改 MMDC_MDCTL 的某一位。
 * 行列地址、位宽、时序、校准和模式寄存器必须整体重新计算。 */

同时检查头文件:

// include/configs/mx6ull_alientek_emmc.h
// 仅当其他代码确实使用该编译期常量时才同步修改:
#define PHYS_SDRAM_SIZE    SZ_512M

⚠️ 注意:

  • imximage.cfg 配置错误 → BootROM 或插件无法正确初始化 DDR → U-Boot 可能无法启动
  • 仅修改 PHYS_SDRAM_SIZE 不能替代 DCD;本板 dram_init() 通过 imx_ddr_size() 读取 MMDC 配置
  • DCD/MMDC 把实际 256MiB 配成 512MiB → gd->ram_size 可能错误,重定位或内存测试可能访问越界

DCD 参数验证方法

生成配置后,必须验证!

方法 1:DDR Stress Test(工具内置)

# 工具会生成测试脚本,通过 JTAG 在真实硬件上测试
# 测试内容:
- 写入数据模式(0x5555, 0xAAAA, 0xFF00...)
- 回读验证
- 压力测试(满速读写)
- 温度测试(如果有传感器)

# 全部通过 → DCD 配置正确

方法 2:U-Boot 内存测试命令

# U-Boot 命令行
=> bdinfo
=> mtest 0x80000000 0x88000000
Pattern 00000000  Writing...  Reading...
Pattern FFFFFFFF  Writing...  Reading...
Pattern 55555555  Writing...  Reading...
# Ctrl+C 停止

# 示例范围来自本树的 CONFIG_SYS_MEMTEST_START/END;实际结束地址必须落在已确认的 DDR 范围内

# 无错误 → DDR 工作正常

方法 3:示波器验证(硬件工程师)

  • 测量 DDR 时钟信号质量
  • 检查数据眼图
  • 验证建立时间和保持时间

DCD 表的执行时机

sequenceDiagram
    participant ROM as BootROM
    participant Flash as Flash/eMMC
    participant DDR as DDR SDRAM
    participant CPU as CPU
    
    Note over ROM: 芯片上电
    ROM->>Flash: 读取 u-boot.imx 文件头
    Flash-->>ROM: 返回 IVT(镜像向量表)
    ROM->>Flash: 读取 DCD 表(imximage.cfg 生成)
    Flash-->>ROM: 返回 DCD 表数据
    
    Note over ROM: ⬇️ 关键时刻!
    ROM->>DDR: 执行 DCD 表中的寄存器配置
    Note over DDR: DDR 控制器初始化完成
    
    ROM->>Flash: 读取 U-Boot 代码
    Flash-->>ROM: 返回 U-Boot 镜像
    ROM->>DDR: 将 U-Boot 代码加载到 DDR
    ROM->>CPU: 跳转到 _start(DDR 中的 U-Boot 代码)
    
    Note over CPU: U-Boot 启动流程开始

关键点:

  1. DCD 表在 U-Boot 代码运行之前 执行
  2. 由 BootROM(芯片内部固化代码)执行,不是 U-Boot 执行
  3. 如果 DCD 配置错误 → DDR 无法初始化 → U-Boot 无法加载 → 完全无法启动

4.5 修改 3:Kconfig

原内容:

if TARGET_MX6ULL_14X14_EVK

config SYS_BOARD
    default "mx6ull_14x14_evk"

config SYS_VENDOR
    default "freescale"

config SYS_CONFIG_NAME
    default "mx6ull_14x14_evk"

endif

修改为:

if TARGET_MX6ULL_ALIENTEK_EMMC

config SYS_BOARD
    default "mx6ull_alientek_emmc"

config SYS_VENDOR
    default "freescale"

config SYS_SOC
    default "mx6"

config SYS_CONFIG_NAME
    default "mx6ull_alientek_emmc"

endif

作用: 定义板级配置变量

影响链路:

defconfig 中的 CONFIG_TARGET_MX6ULL_ALIENTEK_EMMC=y
    ↓
Kconfig 系统匹配到这个 if 块
    ↓
设置默认值:
    CONFIG_SYS_BOARD = "mx6ull_alientek_emmc"
    CONFIG_SYS_VENDOR = "freescale"
    CONFIG_SYS_CONFIG_NAME = "mx6ull_alientek_emmc"
    ↓
make 系统使用这些变量:
    - 编译 board/$(CONFIG_SYS_VENDOR)/$(CONFIG_SYS_BOARD)/
    - 包含 include/configs/$(CONFIG_SYS_CONFIG_NAME).h

本树的 arch/arm/cpu/armv7/mx6/Kconfig 已经把 SYS_SOC 默认设为 "mx6",因此板级 Kconfig 中再次写 SYS_SOC 是冗余但无害的。真正决定板目录和头文件的是 SYS_VENDOR、SYS_BOARD 和 SYS_CONFIG_NAME。

4.6 修改 4:MAINTAINERS

原内容:

MX6ULL_14X14_EVK BOARD
M:    Peng Fan <peng.fan@nxp.com>
S:    Maintained
F:    board/freescale/mx6ull_14x14_evk/
F:    include/configs/mx6ull_14x14_evk.h
F:    configs/mx6ull_14x14_evk_defconfig

修改为:

MX6ULL_ALIENTEK_EMMC BOARD
M:    <填写实际维护者姓名和邮箱>
S:    Maintained
F:    board/freescale/mx6ull_alientek_emmc/
F:    include/configs/mx6ull_alientek_emmc.h
F:    configs/mx6ull_alientek_emmc_defconfig

作用: 声明维护者和相关文件

重要性: 不影响编译,但用于:

  • get_maintainer.pl 脚本查找维护者
  • 补丁审查和责任归属

复制参考板时不要保留 NXP 或参考板维护者的邮箱,除非该维护者明确负责新板。

4.7 这个目录在后续的影响

编译时:

make
    ↓
编译 board/freescale/mx6ull_alientek_emmc/mx6ull_alientek_emmc.c
    ↓
生成 mx6ull_alientek_emmc.o
    ↓
链接到最终的 u-boot.elf

运行时(DDR 初始化):

BootROM 读取 u-boot.imx
    ↓
解析 IVT(镜像向量表)
    ↓
读取 DCD 表(imximage.cfg 生成)
    ↓
执行 DCD 表中的寄存器写操作
    ↓
DDR 控制器初始化完成

运行时(板级初始化):

U-Boot 启动流程
    ↓
board_init_f()
    ├── dram_init()                  ← 来自 mx6ull_alientek_emmc.c
    └── board_early_init_f()         ← 来自 mx6ull_alientek_emmc.c
    ↓
board_init_r()
    ├── board_init()                 ← 来自 mx6ull_alientek_emmc.c
    └── board_late_init()            ← 来自 mx6ull_alientek_emmc.c

5. 步骤四:修改 Kconfig 配置文件

术语更正: 正点原子教程把这一步叫做”修改图形配置文件”,本文不沿用这个叫法。 Kconfig 是文本配置描述文件,不是图形界面文件。详见 5.2 节。

5.1 操作内容

文件: arch/arm/cpu/armv7/mx6/Kconfig

操作: 添加新板的配置选项

添加内容(第 207 行):

config TARGET_MX6ULL_ALIENTEK_EMMC
    bool "Support mx6ull_alientek_emmc"
    select MX6ULL
    select DM
    select DM_THERMAL

添加内容(最后一行 endif 之前):

source "board/freescale/mx6ull_alientek_emmc/Kconfig"

5.2 为什么这么做?

问题:这个 Kconfig 文件的作用是什么?

答案: 它是 i.MX6 SoC 级 Kconfig,向配置系统注册一个新的板型符号。

这里有两个需要澄清的常见误解。


误解一:「Kconfig 是图形配置文件」

Kconfig 是一套文本配置描述语言(源自 Linux 内核),常见语句包括 config、bool、string、choice、select、depends on、default、source 和 help, 不含任何界面代码。真正的界面程序在 scripts/kconfig/ 下,图形界面只是它的其中一个前端:

前端程序入口命令界面形态
confmake xxx_defconfig、make oldconfig、make silentoldconfig无界面,纯批处理
mconfmake menuconfigncurses 文本菜单(伪图形)
nconfmake nconfigncurses 文本菜单
gconfmake gconfigGTK 真图形窗口
qconfmake xconfigQt 真图形窗口

这些程序读的是同一批 Kconfig 文件。我们移植时执行的 make mx6ull_alientek_emmc_defconfig 用的是非图形的 conf 前端, 它照样要完整解析 arch/arm/cpu/armv7/mx6/Kconfig。

所以这一步的实质是:在配置数据库里注册一个板型符号。 “改完之后能在 menuconfig 里看到这个选项”只是顺带的效果,不是目的。 即使你从头到尾不打开 menuconfig,这一步也必须做 —— 否则 defconfig 里的 CONFIG_TARGET_MX6ULL_ALIENTEK_EMMC=y 没有对应的 Kconfig 符号,配置工具会忽略或告警,后续不会得到期望的板级变量。


误解二:「这是顶层 Kconfig」

arch/arm/cpu/armv7/mx6/Kconfig 是 SoC 级文件,只管 i.MX6 系列的板型, 管不到 x86、PowerPC、RISC-V。真实的层级关系是四层:

./Kconfig                                  ← 真正的顶层 Kconfig(工程根目录)
    └── source "arch/Kconfig"              ← 架构选择层(ARM / x86 / RISC-V ...)
            └── source "arch/arm/Kconfig"  ← ARM 架构层(选择 SoC 家族)
                    └── source "arch/arm/cpu/armv7/mx6/Kconfig"   ← ★ 我们改的是这一层(SoC 级)
                            └── source "board/freescale/mx6ull_alientek_emmc/Kconfig"  ← 板级

改错层次会出问题:写到 arch/arm/Kconfig 里,这个板型在非 i.MX6 配置下也会出现; 只新增板级 Kconfig 而不在 SoC 的 choice 中定义 TARGET_MX6ULL_ALIENTEK_EMMC,则 defconfig 没有可用的目标符号,板级 if 块也不会按预期生效。


顺带一提,menuconfig 里的效果:

┌─────────── U-Boot Configuration ──────────┐
│  Arrow keys navigate the menu.            │
│  <Enter> selects submenus --->.           │
│                                            │
│  Target Architecture (ARM)  --->          │
│  System Type (Freescale i.MX6ULL)  --->   │
│  Target Board  --->                        │
│    ( ) mx6ull_14x14_evk                   │
│    (X) mx6ull_alientek_emmc           ← 新增选项 │
│                                            │
└────────────────────────────────────────────┘

注意这里是 ( ) / (X) 单选框而不是 [ ] 复选框 —— 因为本树中所有 TARGET_xxx 板型都被包在一个 choice ... endchoice 块里(endchoice 在 arch/arm/cpu/armv7/mx6/Kconfig:253),同时只能选一个板子。

Kconfig 在 U-Boot 中具体负责什么

可以把 Kconfig 看成“配置模型”,而不是运行时程序。以本板为例,它负责四件事:

  1. 声明符号和菜单结构:TARGET_MX6ULL_ALIENTEK_EMMC 是板型符号,choice 保证同一 SoC 选择一个目标,source 把板级配置文件纳入同一配置模型。
  2. 表达默认值和约束:select MX6ULL、select DM、select DM_THERMAL 会在目标板选中时打开相应符号;default 则为 SYS_BOARD、SYS_VENDOR、SYS_CONFIG_NAME 提供派生值。
  3. 生成配置结果:conf 前端读取顶层 Kconfig 和 defconfig,解析条件后写出 .config。.config 是配置结果,不是 Kconfig 源文件。
  4. 把结果交给编译系统:Makefile 使用 CONFIG_SYS_VENDOR、CONFIG_SYS_BOARD 和 CONFIG_* 选择目录与对象;C 编译通过 include/config.h、include/generated/autoconf.h 等得到宏。Kconfig 本身不调用 board_init(),也不执行 DDR、串口或 PHY 初始化。

因此,移植时新增 Kconfig 的目标是让“板型符号、派生变量和编译规则”连成一条可解析的链路;硬件初始化仍由 DCD、板级 C 文件和驱动代码完成。

5.3 添加配置选项

config TARGET_MX6ULL_ALIENTEK_EMMC
    bool "Support mx6ull_alientek_emmc"
    select MX6ULL
    select DM
    select DM_THERMAL

逐行解析:

行内容作用
config TARGET_MX6ULL_ALIENTEK_EMMC配置项名称对应 defconfig 中的 CONFIG_TARGET_MX6ULL_ALIENTEK_EMMC=y
bool "Support mx6ull_alientek_emmc"布尔类型选项在 menuconfig 中显示的文字
select MX6ULL自动选择 MX6ULL启用 i.MX6ULL 芯片特定代码
select DM自动选择 Driver Model启用驱动模型框架
select DM_THERMAL自动选择温度管理启用 Driver Model 温度框架;具体传感器驱动仍需单独配置

select 的作用:

select 是一种反向依赖写法:目标符号为 y 时会强制把被选符号置为 y。它不是完整的依赖检查,使用不当可能绕过被选符号的依赖约束;板级移植时应确认被选项确实适合该 SoC。

graph TD
    A[用户选择<br/>TARGET_MX6ULL_ALIENTEK_EMMC] --> B[自动启用 MX6ULL]
    B --> C["编译 arch/arm/cpu/armv7/mx6/<br/>相关代码"]
    A --> D[自动启用 DM]
    D --> E[使用驱动模型框架]
    A --> F[自动启用 DM_THERMAL]
    F --> G[编译 drivers/thermal/ 代码]
    
    style A fill:#ff6b6b,stroke:#c92a2a

5.4 添加 source 语句

source "board/freescale/mx6ull_alientek_emmc/Kconfig"

作用: 引入板级 Kconfig 文件

为什么需要?

    i.MX6 SoC Kconfig
    ├── 定义 TARGET_MX6ULL_ALIENTEK_EMMC 选项
    └── source "board/.../Kconfig"  ← 引入板级配置
            ↓
板级 Kconfig (board/freescale/mx6ull_alientek_emmc/Kconfig)
    ├── if TARGET_MX6ULL_ALIENTEK_EMMC
    ├──     config SYS_BOARD
    ├──         default "mx6ull_alientek_emmc"
    ├──     config SYS_CONFIG_NAME
    ├──         default "mx6ull_alientek_emmc"
    └── endif

完整的配置链:

用户执行: make mx6ull_alientek_emmc_defconfig
    ↓
Kconfig 系统处理:
    1. 读取 configs/mx6ull_alientek_emmc_defconfig
    2. 找到 CONFIG_TARGET_MX6ULL_ALIENTEK_EMMC=y
    3. 在 arch/arm/cpu/armv7/mx6/Kconfig 中匹配
    4. source 引入 board/freescale/mx6ull_alientek_emmc/Kconfig
    5. 设置 SYS_BOARD、SYS_CONFIG_NAME 等变量
    6. 展开所有依赖(MX6ULL、DM、DM_THERMAL...)
    7. 生成最终的 .config 文件

5.5 这个修改在后续的影响

配置时:

$ make menuconfig
# 可以在 ncurses 文本菜单中看到并选择 "mx6ull_alientek_emmc"

编译时:

make 系统根据 .config
    ↓
CONFIG_TARGET_MX6ULL_ALIENTEK_EMMC=y
    ↓
编译系统知道:
    - 要编译哪个板级目录
    - 要包含哪个头文件
    - 要启用哪些驱动

5.6 如何验证 Kconfig 修改真的生效

不要只看 menuconfig 里是否出现文字,应检查配置结果和派生文件:

make mx6ull_alientek_emmc_defconfig
grep -E '^CONFIG_(ARM|ARCH_MX6|MX6ULL|TARGET_MX6ULL_ALIENTEK_EMMC|DM|DM_THERMAL)=' .config
grep -E '^CONFIG_SYS_(SOC|VENDOR|BOARD|CONFIG_NAME)=' include/config/auto.conf
grep -E 'CONFIG_(IMX_CONFIG|MX6ULL_EVK_EMMC_REWORK)' include/generated/autoconf.h include/config.h

当前代码树中还应确认 include/config.h 的字面包含行是:

#include <configs/mx6ull_alientek_emmc.h>

如果目标符号存在但 SYS_BOARD、SYS_CONFIG_NAME 或 CONFIG_IMX_CONFIG 没有生成,通常是 Kconfig 的 source 路径、if 条件或 defconfig 名称没有接通。


6. 配置系统工作原理

6.1 配置系统全景图

graph TD
    A[configs/xxx_defconfig<br/>板级默认配置] --> B[make xxx_defconfig]
    B --> C[Kconfig 系统处理]
    
    D["arch/arm/.../Kconfig<br/>架构和 SoC 配置"] --> C
    E["board/.../Kconfig<br/>板级配置"] --> C
    F["drivers/.../Kconfig<br/>驱动配置"] --> C
    
    C --> G[.config<br/>完整配置文件]
    G --> H[include/autoconf.mk<br/>Makefile 可用]
    G --> I[include/config.h<br/>C 代码可用]
    
    H --> J[make 编译]
    I --> J
    
    J --> K{选择编译哪些文件}
    K --> L[编译 board/xxx/]
    K --> M[编译 drivers/xxx/]
    K --> N[编译 arch/xxx/]
    
    L --> O[链接生成 u-boot.elf]
    M --> O
    N --> O
    
    O --> P[mkimage 处理]
    P --> Q["生成 u-boot.imx<br/>包含 DCD 或插件信息"]
    
    style A fill:#ffd43b,stroke:#fab005
    style G fill:#ff6b6b,stroke:#c92a2a
    style Q fill:#51cf66,stroke:#37b24d

6.2 Kconfig 依赖关系

以 CONFIG_TARGET_MX6ULL_ALIENTEK_EMMC 为例:

# arch/arm/cpu/armv7/mx6/Kconfig
config TARGET_MX6ULL_ALIENTEK_EMMC
    bool "Support mx6ull_alientek_emmc"
    select MX6ULL              # ← 依赖 1
    select DM                  # ← 依赖 2
    select DM_THERMAL          # ← 依赖 3

实际依赖链:

graph TD
    A["CONFIG_TARGET_MX6ULL_<br/>ALIENTEK_EMMC"] --> B[CONFIG_MX6ULL]
    B --> C[CONFIG_MX6UL]
    C --> D[CONFIG_SYS_L2CACHE_OFF]
    C --> E[CONFIG_ROM_UNIFIED_SECTIONS]
    
    A --> F[CONFIG_DM]
    A --> G[CONFIG_DM_THERMAL]
    G --> H["编译 drivers/thermal/<br/>中的温度框架"]
    
    I["CONFIG_ARCH_MX6=y<br/>CONFIG_ARM=y"] --> A
    
    style A fill:#ff6b6b,stroke:#c92a2a,stroke-width:3px

最终生成的 .config 文件(部分):

CONFIG_ARM=y
CONFIG_ARCH_MX6=y
CONFIG_MX6=y
CONFIG_MX6ULL=y
CONFIG_TARGET_MX6ULL_ALIENTEK_EMMC=y
CONFIG_SYS_BOARD="mx6ull_alientek_emmc"
CONFIG_SYS_VENDOR="freescale"
CONFIG_SYS_CONFIG_NAME="mx6ull_alientek_emmc"
CONFIG_DM=y
CONFIG_DM_THERMAL=y
# CONFIG_DM_SERIAL、CONFIG_DM_GPIO、CONFIG_DM_MMC 不会因 CONFIG_DM 自动生成
# ... 数百个配置项 ...

select DM 只打开 Driver Model 核心;串口、GPIO、MMC 等 uclass 是否启用,仍由各自的 Kconfig 选项、架构选择或旧式板级头文件决定。本树当前生成的 .config 也没有 CONFIG_DM_SERIAL、CONFIG_DM_GPIO 和 CONFIG_DM_MMC,因此不能把它们画成 DM 的必然子项。

6.3 配置如何影响编译

机制 1:条件编译

Makefile 中的条件编译:

# drivers/Makefile(普通 U-Boot 构建)
obj-$(CONFIG_DM) += core/
obj-y += thermal/

# drivers/gpio/Makefile
obj-$(CONFIG_DM_GPIO) += gpio-uclass.o

# 展开后(如果 CONFIG_DM=y):
# obj-y += core/
# thermal/ 目录始终进入普通驱动库,目录内对象再按各自配置选择

C 代码中的条件编译:

// board/freescale/mx6ull_alientek_emmc/mx6ull_alientek_emmc.c
int board_init(void)
{
    /* ... */
    
#ifdef CONFIG_FEC_MXC
    setup_fec(CONFIG_FEC_ENET_DEV);  // 只有启用网络时才编译
#endif

#ifdef CONFIG_USB_EHCI_MX6
    setup_usb();  // 只有启用 USB 时才编译
#endif

    return 0;
}

机制 2:自动变量替换

CONFIG_SYS_BOARD 的使用:

# config.mk
BOARD := $(CONFIG_SYS_BOARD:"%"=%)
VENDOR := $(CONFIG_SYS_VENDOR:"%"=%)
BOARDDIR = $(VENDOR)/$(BOARD)

# 展开后:
# BOARDDIR = board/freescale/mx6ull_alientek_emmc

# 顶层 Makefile 把板级目录加入库列表,目录内的 Makefile 再由 obj-y 决定对象
libs-y += board/$(BOARDDIR)/

机制 3:头文件自动包含

CONFIG_SYS_CONFIG_NAME 的使用:

# scripts/Makefile.autoconf 的 filechk_config_h
echo \#include \<configs/$(CONFIG_SYS_CONFIG_NAME).h\>

# 生成 include/config.h 后,文件中已经是字面路径:
#include <configs/mx6ull_alientek_emmc.h>

这里不是 config_defaults.h 中的宏拼接,也不是 C 预处理器把变量名替换成文件名;名字到路径的拼接发生在 Makefile 生成 include/config.h 时。

6.4 生成文件各自负责什么

配置系统会产生多个文件,它们的角色不同,不能互相替代:

文件性质主要用途是否手工编辑
Kconfig配置模型源文件声明符号、依赖、默认值和菜单结构可以按移植需要修改
configs/*_defconfig精简输入模板为某个目标板提供默认配置入口可以维护
.configKconfig 的完整结果保存本次配置的最终符号值不建议手工编辑
include/config/auto.conf从 .config 生成的 Make 片段让 Makefile 读取 CONFIG_* 值不要手工编辑
include/autoconf.mk旧版兼容的 Make 配置文件供部分旧式 Makefile 使用不要手工编辑
include/generated/autoconf.h生成的 C 宏头文件为 C 预处理提供配置宏不要手工编辑
include/config.h本树的旧式汇总头文件汇总额外选项、默认头文件和板级配置头文件不要手工编辑

其中,Kconfig 和 defconfig 是输入,.config 及其派生文件是输出。include/config.h 虽然名字容易让人误以为是 Kconfig 的直接产物,但在本树中它由 scripts/Makefile.autoconf 生成,且仍然承担旧版板级头文件汇总入口的角色。执行 make <board>_defconfig 或 make menuconfig 后,应通过配置命令重新生成这些文件,而不是直接修改生成文件。

保存精简配置:

make mx6ull_alientek_emmc_defconfig
# 在 menuconfig 中完成必要调整后:
make savedefconfig
cp defconfig configs/mx6ull_alientek_emmc_defconfig

defconfig 是去除可由默认值推导出的精简结果;select 或 default 产生的符号不一定逐项写回其中。因此,检查配置时应同时查看 .config 和生成的配置文件,不能因为某个符号没有出现在精简 defconfig 中就判断它没有生效。

6.5 配置、编译、运行的完整流程

sequenceDiagram
    participant User as 用户
    participant Make as Make 系统
    participant Kconfig as Kconfig
    participant GCC as 编译器
    participant Linker as 链接器
    participant mkimage as mkimage
    participant BootROM as BootROM
    
    User->>Make: make mx6ull_alientek_emmc_defconfig
    Make->>Kconfig: 读取 defconfig
    Kconfig->>Kconfig: 解析所有 Kconfig 文件
    Kconfig->>Kconfig: 展开依赖关系
    Kconfig->>Make: 生成 .config
    
    User->>Make: make
    Make->>Make: 读取 .config
    Make->>Make: 确定编译哪些目录和文件
    
    Make->>GCC: 编译板级 C 文件
    GCC-->>Make: mx6ull_alientek_emmc.o
    
    Make->>GCC: 编译其他文件 (arch/arm/..., drivers/..., ...)
    GCC-->>Make: 大量 .o 文件
    
    Make->>Linker: 链接所有 .o 文件
    Linker-->>Make: u-boot.elf
    
    Make->>GCC: 预处理 imximage.cfg
    GCC-->>Make: imximage.cfg.cfgtmp
    Make->>mkimage: 处理 u-boot.bin + cfgtmp
    mkimage->>mkimage: 生成 IVT,并写入 DCD 或插件信息
    mkimage-->>Make: u-boot.imx
    
    Note over User: 烧录 u-boot.imx 到板子
    
    BootROM->>BootROM: 芯片上电
    BootROM->>BootROM: 读取 u-boot.imx
    BootROM->>BootROM: 执行 DCD 表(初始化 DDR)
    BootROM->>BootROM: 加载 U-Boot 代码到 DDR
    BootROM->>BootROM: 跳转到 _start
    
    Note over BootROM: U-Boot 开始运行

7. 移植步骤与启动流程的映射

7.1 关键问题

问题:移植只修改了配置文件,为什么能影响启动流程?

答案:配置决定了启动流程中调用哪些板级函数,以及这些函数的行为。

7.2 SPL 与普通 U-Boot 的边界

本树同时包含 SPL 相关代码,但“存在 SPL 代码”不等于每一种启动方式都会实际执行 SPL。是否生成、加载和运行 SPL,要由目标板配置、启动介质以及 i.MX6ULL BootROM 的启动方式共同决定。

当前板级文件中的两个 DDR 初始化函数属于不同阶段:

函数所属阶段当前实现作用
spl_dram_init()SPL 的 board_init_f()mx6ul_dram_iocfg()、mx6_dram_cfg()在 SPL 中直接配置 IOMUX、MMDC 和 DDR
dram_init()普通 U-Boot 的 board_init_f() 初始化序列gd->ram_size = imx_ddr_size()在 DDR 已可用后读取 MMDC 配置并上报容量

因此,不能把 spl_dram_init() 和 dram_init() 画成同一个函数,也不能据此断言所有启动路径都必然经过 SPL。分析实际启动日志时,应先确认构建产物、CONFIG_SPL_BUILD 条件和启动介质,再判断执行的是哪条路径。

7.3 启动流程回顾

完整的启动流程(来自调用关系图):

BootROM(芯片内部)
    ↓ 执行 DCD 表(imximage.cfg)
    ↓ 初始化 DDR
    ↓ 加载 U-Boot 到 DDR
    ↓
_start(arch/arm/cpu/armv7/start.S)
    ↓
reset
    ↓
cpu_init_cp15
    ↓
cpu_init_crit
    ↓
lowlevel_init(arch/arm/cpu/armv7/lowlevel_init.S)
    ↓
_main(arch/arm/lib/crt0.S)
    ↓
board_init_f(common/board_f.c)
    ├── init_sequence_f[] 数组中的函数
    ├── arch_cpu_init()
    ├── board_early_init_f()   ← 板级函数
    ├── dram_init()            ← 板级函数
    └── ...
    ↓
relocate_code(arch/arm/lib/relocate.S)
    ↓ 重定位到 DDR 高地址
    ↓
board_init_r(common/board_r.c)
    ├── init_sequence_r[] 数组中的函数
    ├── board_init()           ← 板级函数
    ├── board_late_init()      ← 板级函数
    └── ...
    ↓
main_loop(common/main.c)
    ↓ 执行 bootcmd 启动 Linux

7.4 移植步骤到启动流程的映射

graph TD
    subgraph "移植步骤"
        A1[步骤1: defconfig<br/>IMX_CONFIG 指定 DCD 表路径]
        A2[步骤2: 头文件<br/>定义宏配置]
        A3[步骤3: 板级目录<br/>imximage.cfg<br/>板级代码 .c]
        A4[步骤4: Kconfig<br/>配置选项]
    end
    
    subgraph "启动流程"
        B1[BootROM<br/>执行 DCD 表]
        B2[_start → lowlevel_init]
        B3[board_init_f<br/>dram_init<br/>board_early_init_f]
        B4[重定位]
        B5[board_init_r<br/>board_init<br/>board_late_init]
        B6[main_loop]
    end
    
    A1 --> B1
    A3 --> B1
    A2 --> B3
    A3 --> B3
    A2 --> B5
    A3 --> B5
    
    B1 --> B2
    B2 --> B3
    B3 --> B4
    B4 --> B5
    B5 --> B6
    
    style A3 fill:#ff6b6b,stroke:#c92a2a,stroke-width:3px
    style B1 fill:#ff6b6b,stroke:#c92a2a,stroke-width:3px

7.5 详细映射表

移植步骤影响的启动阶段具体影响代码位置
defconfig: IMX_CONFIGBootROM 阶段指定 DCD 表路径 → BootROM 执行 DCD 表初始化 DDRBootROM 内部
imximage.cfgBootROM 阶段DCD 表内容 → DDR 控制器寄存器配置BootROM 内部
DCD/MMDC 配置BootROM 和 board_init_fBootROM 初始化 DDR;板级 dram_init() 通过 imx_ddr_size() 读取 MMDC 容量imximage.cfg、arch/arm/imx-common/cpu.c
头文件: CONFIG_MXC_UART_BASEboard_init_f定义串口基地址 → serial_init() 初始化common/board_f.c:serial_init()
板级代码: board_early_init_f()board_init_f早期初始化(IOMUX、时钟)board/freescale/.../xxx.c
板级代码: dram_init()board_init_f通过 imx_ddr_size() 上报 DDR 大小board/freescale/.../xxx.c
板级代码: board_init()、board_eth_init()board_init_r、网络初始化启动参数、外设时钟、FEC 引脚和 PHY 初始化board/freescale/.../xxx.c
板级代码: board_late_init()board_init_r环境变量设置board/freescale/.../xxx.c
头文件: CONFIG_BOOTCOMMANDmain_loop默认启动命令common/main.c:main_loop()

7.6 配置到运行的完整链路

链路 1:DDR 初始化(最关键)

sequenceDiagram
    participant defconfig as defconfig 文件
    participant make as make 系统
    participant mkimage as mkimage 工具
    participant imx as u-boot.imx
    participant rom as BootROM
    participant ddr as DDR 控制器
    
    defconfig->>defconfig: IMX_CONFIG=.../imximage.cfg
    defconfig->>make: make 读取配置
    make->>GCC: 预处理 imximage.cfg
    GCC-->>make: 生成 imximage.cfg.cfgtmp
    make->>mkimage: 调用 mkimage 处理 cfgtmp
    mkimage->>mkimage: 解析 DCD 或插件配置
    mkimage->>imx: 将 DCD 或插件信息写入镜像
    
    Note over imx: 烧录到板子
    
    rom->>imx: 芯片上电,BootROM 读取镜像
    rom->>rom: 解析 IVT(镜像向量表)
    rom->>rom: 找到 DCD 或插件信息
    rom->>ddr: 执行启动配置(写寄存器)
    ddr->>ddr: DDR 控制器初始化完成
    
    Note over ddr: U-Boot 可以加载到 DDR 了

关键代码路径:

configs/mx6ull_alientek_emmc_defconfig
    CONFIG_SYS_EXTRA_OPTIONS="IMX_CONFIG=board/freescale/mx6ull_alientek_emmc/imximage.cfg,..."
        ↓
Makefile 处理
        ↓
u-boot.imx 目标规则:
    $(Q)$(MAKE) $(build)=arch/arm/imx-common $(objtree)/u-boot.imx
        ↓
    arch/arm/imx-common/Makefile:
        $(IMX_CONFIG) → board/freescale/mx6ull_alientek_emmc/imximage.cfg.cfgtmp
        ↓
    C 预处理后调用 mkimage:
    mkimage -n <imximage.cfg.cfgtmp> -T imximage -e $(CONFIG_SYS_TEXT_BASE) -d u-boot.bin u-boot.imx
        ↓
mkimage 读取 imximage.cfg.cfgtmp:
    DATA 4 0x021B000C 0x676B52F3
    DATA 4 0x021B0010 0xB66D0B63
    ...
        ↓
嵌入 DCD 表到 u-boot.imx 头部
        ↓
BootROM 执行 DCD 表初始化 DDR

链路 2:板级函数调用

sequenceDiagram
    participant Kconfig as Kconfig 系统
    participant config as .config
    participant make as Make
    participant gcc as GCC
    participant board_f as board_init_f()
    participant dram_init as dram_init()
    
    Kconfig->>config: CONFIG_TARGET_MX6ULL_ALIENTEK_EMMC=y
    Kconfig->>config: CONFIG_SYS_BOARD="mx6ull_alientek_emmc"
    
    make->>gcc: 编译板级 C 文件
    gcc->>gcc: 生成 mx6ull_alientek_emmc.o
    gcc->>gcc: 包含符号: dram_init, board_init, board_late_init
    
    make->>make: 链接 mx6ull_alientek_emmc.o 到 u-boot.elf
    
    Note over board_f: U-Boot 运行时
    
    board_f->>board_f: init_sequence_f[] 数组
    board_f->>dram_init: 调用 dram_init()
    dram_init->>dram_init: gd->ram_size = imx_ddr_size()
    dram_init-->>board_f: 返回 DDR 大小

关键代码:

1. 配置决定编译哪个文件:

# board/freescale/mx6ull_alientek_emmc/Makefile
obj-y := mx6ull_alientek_emmc.o
# make 系统会编译这个文件

2. 板级代码提供函数实现:

// board/freescale/mx6ull_alientek_emmc/mx6ull_alientek_emmc.c
int board_early_init_f(void)
{
    setup_iomux_uart();       // board_init_f 阶段配置 UART1 引脚
    return 0;
}

int dram_init(void)
{
    gd->ram_size = imx_ddr_size();
    return 0;
}

int board_init(void)
{
    /* board_init_r 阶段设置启动参数、I2C、FEC 时钟等 */
    setup_fec(CONFIG_FEC_ENET_DEV);
    return 0;
}

int board_eth_init(bd_t *bis)
{
    setup_iomux_fec(CONFIG_FEC_ENET_DEV);  // 网络引脚复用
    return fecmxc_initialize_multi(bis, CONFIG_FEC_ENET_DEV,
                                   CONFIG_FEC_MXC_PHYADDR, IMX_FEC_BASE);
}

3. 启动流程调用板级函数:

// common/board_f.c
static init_fnc_t init_sequence_f[] = {
    // ...
    dram_init,           // ← 调用板级提供的 dram_init()
    // ...
};

void board_init_f(ulong boot_flags)
{
    for (init_fnc_t *init_fnc_ptr = init_sequence_f; *init_fnc_ptr; ++init_fnc_ptr) {
        if ((*init_fnc_ptr)() != 0)
            hang();
    }
}

链路 3:头文件宏配置

graph LR
    A["defconfig:<br/>CONFIG_SYS_CONFIG_NAME=<br/>mx6ull_alientek_emmc"] --> B[自动包含头文件]
    B --> C[include/configs/<br/>mx6ull_alientek_emmc.h]
    C --> D["定义宏:<br/>PHYS_SDRAM_SIZE<br/>CONFIG_MXC_UART_BASE<br/>CONFIG_BOOTCOMMAND"]
    D --> E[板级代码使用宏]
    E --> F["dram_init() 使用<br/>imx_ddr_size()"]
    E --> G[serial_init 使用 UART_BASE]
    E --> H[main_loop 执行 BOOTCOMMAND]
    
    style C fill:#ffd43b,stroke:#fab005

代码示例:

// include/configs/mx6ull_alientek_emmc.h
#define PHYS_SDRAM_SIZE    SZ_512M   // 其他编译期代码可能使用

// board/freescale/mx6ull_alientek_emmc/mx6ull_alientek_emmc.c
int dram_init(void)
{
    gd->ram_size = imx_ddr_size();   // ← 读取 MMDC 配置
    return 0;
}

8. 理论分析 vs 实际移植

8.1 核心问题

用户的疑问:

“为什么它不按照我们分析的那样进行移植工作?”

分析的启动流程:

  • _start → reset → cpu_init_cp15 → cpu_init_crit → lowlevel_init → _main → board_init_f → relocate_code → board_init_r → main_loop

实际移植步骤:

  1. 创建 defconfig
  2. 创建头文件
  3. 创建板级目录
  4. 修改 Kconfig

看起来完全不相关!为什么?

8.2 答案:两个不同的层次

graph TD
    subgraph "理论层:代码执行流程"
        A1[_start]
        A2[reset]
        A3[lowlevel_init]
        A4[board_init_f]
        A5[board_init_r]
        A6[main_loop]
        A1 --> A2 --> A3 --> A4 --> A5 --> A6
    end
    
    subgraph "实践层:配置基础设施"
        B1[defconfig]
        B2[头文件]
        B3[板级目录]
        B4[Kconfig]
        B1 --> B2 --> B3 --> B4
    end
    
    subgraph "连接层:编译系统"
        C1[make 系统]
        C2[条件编译]
        C3[链接规则]
    end
    
    B1 --> C1
    B2 --> C2
    B3 --> C3
    B4 --> C1
    
    C1 --> A4
    C2 --> A4
    C3 --> A4
    C1 --> A5
    C2 --> A5
    C3 --> A5
    
    style A4 fill:#ff6b6b,stroke:#c92a2a
    style B3 fill:#ff6b6b,stroke:#c92a2a

8.3 详细对比

对比维度理论分析(启动流程)实际移植(配置)关系
关注点代码如何执行配置如何搭建配置决定执行路径
层次运行时编译时编译时配置影响运行时行为
核心问题CPU 如何从复位到运行如何告诉编译系统编译什么配置选择代码
文件类型汇编、C 代码配置文件、Makefile配置驱动代码编译
修改内容通常不修改通用代码创建板级配置和代码板级代码插入通用流程
SoC 相关性通用(NXP 提供)板级特定板级覆盖通用默认值

8.4 为什么不直接修改启动流程代码?

原因 1:启动流程代码是通用的

i.MX6ULL 芯片的所有板子都用相同的启动流程:

// arch/arm/cpu/armv7/start.S - 所有 i.MX6ULL 板共用
_start:
    b   reset
    // ...

reset:
    // ...
    bl  cpu_init_cp15
    bl  cpu_init_crit
    // ...

这些公共代码已经适配 i.MX6ULL 的 SoC 级流程,通常不需要为换板修改;真正需要调整的是板级钩子、硬件数据和配置条件。

原因 2:板级差异在配置和数据,不在流程

不同板子的差异:

差异项i.MX6ULL EVK正点原子板差异类型
DDR 类型/型号参考板和目标板需分别核对以原理图和数据手册为准需要匹配 DCD/MMDC
DDR 大小由参考板硬件决定由目标板硬件决定DCD 与 imx_ddr_size() 必须一致
串口UART1UART1配置参数
PHYCONFIG_PHY_MICREL(参考板)CONFIG_PHY_SMSC(目标板)型号、地址和复位脚需按原理图确认
LED GPIO需查参考板原理图需查目标板原理图源码不能自动推断连接关系

差异体现在:

  • DCD 表(DDR 初始化参数)
  • IOMUX 配置(引脚复用)
  • 外设初始化参数(PHY 地址、GPIO 编号等)

这些都是数据,不是流程逻辑!

原因 3:可以用面向对象的方式类比,但它不是 C++ 继承

U-Boot 使用初始化函数表和板级钩子,把通用流程与板级实现分开。把它类比成面向对象有助于理解, 但 C 语言中并不存在真正的类、继承或虚函数:

// 基类(通用流程) - common/board_f.c
void board_init_f(ulong boot_flags)
{
    // 通用初始化流程
    for (init_fnc_t *init_fnc_ptr = init_sequence_f; *init_fnc_ptr; ++init_fnc_ptr) {
        if ((*init_fnc_ptr)() != 0)  // 通过函数指针调用初始化钩子
            hang();
    }
}

// 派生类(板级实现) - board/freescale/mx6ull_alientek_emmc/xxx.c
int dram_init(void)  // 板级实现的同名钩子
{
    gd->ram_size = imx_ddr_size();
    return 0;
}

移植可以理解为:为通用启动流程提供一组新的板级钩子实现。

8.5 移植的本质

graph TD
    A[移植的本质] --> B[搭建配置框架]
    A --> C[提供板级数据]
    A --> D[实现板级函数]
    
    B --> E[defconfig<br/>Kconfig<br/>Makefile]
    C --> F[DCD 表<br/>头文件宏<br/>引脚配置]
    D --> G[dram_init<br/>board_init<br/>board_late_init]
    
    E --> H[告诉编译系统<br/>编译什么]
    F --> I[告诉硬件<br/>如何初始化]
    D --> J[告诉启动流程<br/>调用什么]
    
    H --> K[生成定制的 U-Boot]
    I --> K
    J --> K
    
    style A fill:#ff6b6b,stroke:#c92a2a,stroke-width:3px

一句话总结:

移植不是修改启动流程,而是在启动流程的”钩子”上挂上板级特定的配置和代码。


9. 完整影响链路追踪

9.1 修改点到影响的完整追踪

追踪 1:IMX_CONFIG 参数

graph TD
    A["defconfig:<br/>IMX_CONFIG=board/freescale/<br/>mx6ull_alientek_emmc/<br/>imximage.cfg"] --> B[make 读取]
    B --> C["传递给 arch/arm/imx-common/<br/>Makefile"]
    C --> D["mkimage 处理 cfgtmp<br/>-T imximage<br/>u-boot.bin → u-boot.imx"]
    D --> E["mkimage 读取<br/>imximage.cfg.cfgtmp"]
    E --> F[解析 DCD 表]
    F --> G["DCD 表内容:<br/>DATA 4 0x021B000C 0x676B52F3<br/>DATA 4 0x021B0010 0xB66D0B63<br/>其余寄存器配置"]
    G --> H[嵌入到 u-boot.imx 头部]
    
    H --> I[烧录到板子]
    I --> J[BootROM 读取镜像]
    J --> K[BootROM 执行 DCD 表]
    K --> L["写寄存器:<br/>MMDC 控制器<br/>IOMUX<br/>CCM 时钟"]
    L --> M[DDR 控制器初始化完成]
    M --> N[U-Boot 可以加载到 DDR]
    N --> O[跳转到 _start]
    
    style A fill:#ffd43b,stroke:#fab005,stroke-width:2px
    style G fill:#ff6b6b,stroke:#c92a2a,stroke-width:3px
    style M fill:#51cf66,stroke:#37b24d,stroke-width:2px

影响范围:

  • ✅ BootROM 阶段(DDR 初始化)
  • ❌ 不影响 U-Boot 代码执行(在 U-Boot 运行前就完成了)

如果配置错误:

  • DDR 无法初始化 → U-Boot 代码无法加载 → 完全无法启动

追踪 2:CONFIG_TARGET_MX6ULL_ALIENTEK_EMMC

graph TD
    A["defconfig:<br/>CONFIG_TARGET_MX6ULL_<br/>ALIENTEK_EMMC=y"] --> B[Kconfig 匹配]
    B --> C["arch/arm/cpu/armv7/mx6/Kconfig<br/>找到对应的 config 块"]
    C --> D[自动 select 依赖]
    D --> E[CONFIG_MX6ULL=y]
    D --> F[CONFIG_DM=y]
    D --> G[CONFIG_DM_THERMAL=y]
    
    C --> H["source board/freescale/<br/>mx6ull_alientek_emmc/<br/>Kconfig"]
    H --> I[设置板级变量]
    I --> J["CONFIG_SYS_BOARD=<br/>mx6ull_alientek_emmc"]
    I --> K["CONFIG_SYS_CONFIG_NAME=<br/>mx6ull_alientek_emmc"]
    
    J --> L["make 编译<br/>board/freescale/<br/>mx6ull_alientek_emmc/"]
    K --> M["自动包含<br/>include/configs/<br/>mx6ull_alientek_emmc.h"]
    
    E --> N["编译 arch/arm/cpu/armv7/mx6/<br/>相关代码"]
    F --> O[编译 drivers/core/ 驱动模型]
    G --> P[编译 drivers/thermal/ 温度驱动]
    
    L --> Q[链接板级函数到 u-boot.elf]
    M --> R[板级宏生效]
    N --> Q
    O --> Q
    P --> Q
    
    Q --> S[u-boot.elf]
    R --> S
    
    style A fill:#ff6b6b,stroke:#c92a2a,stroke-width:3px
    style J fill:#ffd43b,stroke:#fab005,stroke-width:2px
    style K fill:#ffd43b,stroke:#fab005,stroke-width:2px

影响范围:

  • ✅ 编译阶段(选择编译哪些代码)
  • ✅ 链接阶段(包含哪些板级函数)
  • ✅ 运行时(启动流程调用板级函数)

后续影响链:

CONFIG_TARGET_MX6ULL_ALIENTEK_EMMC=y
    ↓
CONFIG_SYS_BOARD="mx6ull_alientek_emmc"
    ↓
编译 board/freescale/mx6ull_alientek_emmc/mx6ull_alientek_emmc.c
    ↓
生成符号:dram_init, board_init, board_late_init
    ↓
链接到 u-boot.elf
    ↓
运行时 board_init_f() 调用 dram_init()
运行时 board_init_r() 调用 board_init()

追踪 3:DCD 与 dram_init() 的 DDR 容量链路

graph TD
    A["imximage.cfg<br/>DCD/MMDC 配置"] --> B["BootROM 执行 DCD<br/>初始化 DDR 控制器"]
    B --> C["U-Boot 进入<br/>board_init_f()"]
    C --> D["板级 dram_init()<br/>调用 imx_ddr_size()"]
    D --> E["读取 MMDC 配置<br/>计算 gd->ram_size"]
    E --> F["后续初始化和重定位<br/>使用 gd->ram_size"]
    F --> G["bdinfo、内存命令和<br/>Linux 参数准备"]

    H["PHYS_SDRAM_SIZE"] -.-> I["本树中的编译期常量<br/>不能替代 DCD 或 imx_ddr_size()"]

    style A fill:#ffd43b,stroke:#fab005,stroke-width:2px
    style E fill:#51cf66,stroke:#37b24d,stroke-width:2px

影响范围:

  • ✅ BootROM 阶段(DCD/MMDC 初始化)
  • ✅ 运行时 board_init_f 阶段(dram_init() 设置 gd->ram_size)
  • ✅ 后续重定位、bdinfo 和内存命令使用 gd->ram_size

如果配置错误:

假设实际 DDR 是 256MiB,但 DCD/MMDC 配置使 `imx_ddr_size()` 返回 512MiB
    ↓
gd->ram_size = 0x20000000  (512MiB)
    ↓
重定位和内存边界按错误的容量计算
    ↓
⚠️ 实际 DDR 只到 0x90000000
    ↓
后续访问可能越过物理内存边界
    ↓
❌ 系统崩溃

追踪 4:板级函数 board_init()

graph TD
    A["board/freescale/<br/>mx6ull_alientek_emmc/<br/>mx6ull_alientek_emmc.c"] --> B["int board_init(void) {<br/>setup_fec(CONFIG_FEC_ENET_DEV);<br/>return 0;<br/>}"]
    
    B --> C[编译生成 .o 文件]
    C --> D[链接到 u-boot.elf]
    D --> E[符号表包含 board_init]
    
    E --> F[运行时:board_init_r 阶段]
    F --> G["init_sequence_r[] = {<br/>...<br/>board_init,<br/>...<br/>}"]
    G --> H[调用 board_init]
    
    H --> I[执行 setup_fec]
    I --> J[配置 FEC 时钟和复用选择]
    
    F --> K[网络初始化阶段]
    K --> L[调用 board_eth_init]
    L --> M[执行 setup_iomux_fec]
    M --> N["配置网卡引脚:<br/>ENET1_MDC → FEC_MDC<br/>ENET1_MDIO → FEC_MDIO<br/>..."]
    N --> O[初始化 PHY 芯片]
    O --> P[网卡可以使用]

    A -.-> Q[board_early_init_f 阶段执行 setup_iomux_uart]
    Q --> R["配置 UART1 引脚:<br/>PAD_UART1_TX_DATA → UART1_TX<br/>PAD_UART1_RX_DATA → UART1_RX"]
    R --> S[串口可以正常输出]
    
    style B fill:#ff6b6b,stroke:#c92a2a,stroke-width:2px
    style P fill:#51cf66,stroke:#37b24d,stroke-width:2px
    style S fill:#51cf66,stroke:#37b24d,stroke-width:2px

影响范围:

  • ✅ board_init_f 阶段:board_early_init_f() 配置 UART1 引脚
  • ✅ board_init_r 阶段:board_init() 配置启动参数、I2C、FEC 时钟等
  • ✅ 网络初始化阶段:board_eth_init() 配置 FEC 引脚并初始化 PHY
  • ✅ 后续串口、网络等外设功能

如果缺少这个函数:

链接阶段:
    未定义符号 board_init
    ↓
❌ 链接失败,无法生成 u-boot.elf

如果函数实现错误(例如引脚配置错误):

board_early_init_f() 执行
    ↓
setup_iomux_uart() 配置了错误的引脚
    ↓
串口 TX/RX 信号没有连接到正确的物理引脚
    ↓
❌ 串口无输出(看起来像启动失败)

9.2 修改影响总结表

修改点文件影响的启动阶段影响的功能严重程度
IMX_CONFIGdefconfigBootROMDDR 初始化🔴 致命(无法启动)
CONFIG_TARGET_XXXdefconfig编译+运行整体板级集成🔴 致命(编译失败)
DCD/MMDC 配置imximage.cfgBootROM + board_init_fDDR 初始化和 imx_ddr_size() 返回值🟡 严重(可能无法启动或崩溃)
CONFIG_MXC_UART_BASE头文件board_init_f串口初始化🟡 严重(无输出)
board_init()、board_eth_init()板级代码board_init_r、网络初始化外设时钟、FEC 引脚和 PHY 初始化🟡 严重(外设不工作)
dram_init()板级代码board_init_fDDR 大小上报🟡 严重(内存检测错误)
CONFIG_BOOTCOMMAND头文件main_loop启动 Linux 的命令🟢 功能性(启动失败但可调试)
MAINTAINERS板级目录无文档⚪ 无影响

10. 基础移植与外设适配

10.1 核心要点

1. 移植的本质

移植 ≠ 修改启动流程代码
移植 = 配置基础设施搭建 + 板级数据提供 + 板级函数实现

2. 配置系统的作用

defconfig + Kconfig + Makefile
    ↓
告诉编译系统:
    - 编译哪些代码
    - 包含哪些头文件
    - 链接哪些函数
    ↓
生成定制的 U-Boot

3. DCD 表的关键地位

imximage.cfg(DCD 表)
    ↓
BootROM 执行(U-Boot 运行前)
    ↓
初始化 DDR
    ↓
没有 DDR,U-Boot 无法加载
    ↓
这是移植最关键的一步!

4. 理论与实践的关系

理论分析(启动流程):
    告诉你代码如何执行
    ↓
实际移植(配置搭建):
    告诉编译系统选择执行哪些代码
    ↓
关系:
    配置驱动代码选择
    代码实现启动流程

10.2 移植检查清单

✅ 步骤 1:创建 defconfig

  • 复制参考板的 defconfig
  • 修改 IMX_CONFIG 路径
  • 修改 CONFIG_TARGET_XXX 为新板名称
  • 检查其他必要的 CONFIG 选项

✅ 步骤 2:创建头文件

  • 复制参考板的头文件
  • 修改头文件保护宏(防止重复包含)
  • 核对 DCD/MMDC 配置与 DDR 型号、容量、位宽和时序
  • 了解 dram_init() 是否通过 imx_ddr_size() 或固定宏上报容量
  • 检查 CONFIG_MXC_UART_BASE(串口地址)
  • 检查 CONFIG_BOOTCOMMAND(启动命令)

✅ 步骤 3:创建板级目录(最关键)

  • 复制参考板目录
  • 修改 Makefile(obj-y 指向新的 .c 文件)
  • 重点:修改 imximage.cfg(DCD 表)
    • 检查 DDR 芯片型号
    • 检查 DDR 容量配置
    • 检查时序参数
    • 检查校准参数
  • 修改 Kconfig(板级变量)
  • 修改 MAINTAINERS(维护者信息)
  • 修改板级 .c 文件名和内容

✅ 步骤 4:修改 i.MX6 SoC 层 Kconfig

  • 添加 config TARGET_XXX 选项
  • 添加 source 语句引入板级 Kconfig

10.3 调试建议

问题:修改后无法启动,串口无输出

排查顺序:

  1. 首先检查 DCD 表(最可能)


    # 检查 IMX_CONFIG 路径是否正确
    $ grep IMX_CONFIG configs/mx6ull_alientek_emmc_defconfig

    # 检查 imximage.cfg 是否存在
    $ ls board/freescale/mx6ull_alientek_emmc/imximage.cfg

  2. 检查 DDR 配置

    • DDR 芯片型号是否正确?
    • DDR 容量配置是否匹配?
    • 时序参数是否来自 DDR 芯片数据手册?
  3. 检查编译是否成功


    # 检查是否生成了板级 .o 文件
    $ ls board/freescale/mx6ull_alientek_emmc/*.o

    # 检查符号表中是否有板级函数
    $ nm u-boot | grep board_init
    $ nm u-boot | grep dram_init

  4. 检查配置是否生效


    # 检查 .config 文件
    $ grep CONFIG_TARGET_MX6ULL_ALIENTEK_EMMC .config
    $ grep CONFIG_SYS_BOARD .config

10.4 与启动流程的对应关系

移植步骤          →  影响的启动阶段        →  作用
═══════════════════════════════════════════════════════════
imximage.cfg     →  BootROM 阶段         →  DDR 初始化
defconfig        →  编译阶段             →  选择代码
头文件           →  board_init_f/r      →  配置参数
板级代码         →  board_init_f/r      →  硬件初始化
Kconfig          →  编译阶段             →  集成到系统

10.5 与《移植过程概述》的交叉核对

《移植过程概述》记录的内容可以分成两个阶段。前四步(defconfig、板级头文件、板级目录、Kconfig) 解决的是“让新板进入 U-Boot 的配置、编译和启动框架”;后面的 LCD、FEC/PHY、复位 GPIO、 bootcmd/bootargs 修改,解决的是“让具体硬件外设真正工作”。两者不是互相矛盾的两套移植方法, 而是基础移植和外设适配两个层次。

还要修正概述中一个容易产生误解的简化:IMX_CONFIG=... 并不是直接作为一个普通的 GCC -D 参数传给所有源文件。 在本树中,它先作为 CONFIG_SYS_EXTRA_OPTIONS 的兼容字符串保存,再由 scripts/Makefile.autoconf 拆分并写入 include/config.h,最后由 arch/arm/imx-common/Makefile 用它定位 .cfgtmp 并调用 mkimage。

阶段需要核对的内容本树中的落点不能直接照搬的部分
基础移植生成 .config、板级头文件和 u-boot.imxconfigs/、include/configs/、board/freescale/、SoC Kconfig目标板的 DCD、启动介质和维护者信息
LCD 适配LCD IOMUX、背光/复位 GPIO、分辨率和时序mx6ull_alientek_emmc.c 的 display_info_t、LCD 引脚表;头文件中的 panel=TFT7016只改 panel 字符串不能替代引脚、背光和时序检查
FEC/PHY 适配使用哪个 FEC、PHY 地址、PHY 驱动、复位时序当前头文件为 CONFIG_FEC_ENET_DEV=1、PHY 地址 0x1、CONFIG_PHY_SMSC;板级代码由 board_eth_init() 和 setup_iomux_fec() 完成ENET1/ENET2、PHY 地址和型号必须以原理图和实测 MDIO 结果为准
外部 IO 扩展是否使用 74LV595 等扩展器,PHY 复位由谁控制当前板级代码使用 ENET1_RESET/ENET2_RESET 直接 GPIO只有确认目标板没有扩展器时,才能删除参考板的扩展器代码
启动环境bootcmd、bootargs、mmcdev、mmcroot、fdt_fileCONFIG_EXTRA_ENV_SETTINGS 和 CONFIG_BOOTCOMMAND环境变量可能来自存储介质,头文件默认值不一定是运行时最终值

概述中网络修改的边界

概述建议修改 drivers/net/phy/phy.c 的 genphy_update_link()。这不是所有 LAN8720A 或所有板子都必须做的修改。 当前树已经提供板级 board_phy_config(),并在其中完成 PHY 的板级寄存器配置;应先确认 PHY 型号、复位时序、 MDIO 地址和现有板级钩子是否足够,只有在复现出通用驱动缺陷并能说明影响范围时,才修改 drivers/net/phy/phy.c。 通用驱动改动会影响使用同一 PHY 驱动的其他板子,风险高于板级钩子调整。

基础移植完成后的可复现构建

概述中的构建顺序可以整理为:

make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- distclean
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- mx6ull_alientek_emmc_defconfig
make V=1 ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- -j$(nproc)

其中编译成功只说明配置、编译和链接链路接通;还必须检查镜像头、串口日志、bdinfo、mmc list、mii info、 mtest 以及实际 LCD/网口行为,才能确认硬件移植完成。

10.6 还可以继续完善的内容

这篇文章覆盖了“复制参考板并接入配置系统”的主线,但实际产品移植还应补充以下验证:

方向建议补充内容
硬件证据给出 DDR 颗粒、位宽、PHY 型号和复位脚的原理图页码;不要只凭参考板名称推断参数
启动介质分别验证 SD、eMMC、NAND 的 BOOT_FROM、烧写偏移、环境变量位置和启动开关
板级代码逐项核对 IOMUX、时钟、复位、USB、I2C、FEC 和显示初始化;说明哪些函数来自复制文件、哪些是新增代码
设备树如果启用 CONFIG_OF_CONTROL,同步说明 U-Boot 使用的 DTB 来源、节点和 Linux DTB 的关系
配置迁移旧树仍依赖 CONFIG_SYS_EXTRA_OPTIONS 和板级头文件,应标注这些是迁移兼容层,并说明新版本优先使用 Kconfig
可复现构建给出交叉编译器版本、make mrproper/O= 构建方式、生成镜像和校验命令
实机验证增加串口日志、bdinfo、mmc list、mii info、mtest 和镜像头检查结果,区分“编译成功”和“硬件启动成功”

这些内容不会改变四步移植主线,但能把文章从源码说明提升为可以复现和排障的移植记录。

10.7 镜像和启动介质验证

编译成功后,先在主机上检查镜像格式和实际构建命令:

# 查看 i.MX 镜像头信息;需要使用与构建时相同版本的 mkimage
mkimage -l u-boot.imx

# 展开但不执行 u-boot.imx 目标,查看 Makefile 最终命令
make V=1 -n u-boot.imx

# 本树也会生成该目标的命令记录,可用于核对 IMX_CONFIG 和参数
cat .u-boot.imx.cmd

重点确认:

  • 镜像类型是 i.MX/Freescale boot image;
  • 入口地址与 CONFIG_SYS_TEXT_BASE 一致;
  • DCD 或插件信息存在且来源是当前板级 imximage.cfg;
  • 命令中使用的是 u-boot.bin、.cfgtmp 和 u-boot.imx 的当前构建产物。

mkimage -l 只能证明文件具有可解析的镜像格式,不能证明 DCD 参数适合目标 DDR,也不能替代上板串口日志和实际外设测试。

10.8 烧写介质、分区和偏移

本树的 imximage.cfg 在未定义 CONFIG_SYS_BOOT_QSPI 或 CONFIG_SYS_BOOT_EIMNOR 时选择:

BOOT_FROM sd

这只说明镜像头按 SD 启动方式生成,不等于已经确定所有板卡的烧写命令和偏移。实际烧写前还必须核对:

  1. 启动拨码、熔丝或硬件启动配置选择的介质;
  2. 使用 SD、eMMC 用户区、eMMC boot 分区还是 NAND;
  3. BootROM 要求的镜像起始扇区/字节偏移;
  4. 是否需要保留介质已有分区表、环境区或其他启动数据;
  5. 烧写回读后的镜像是否与构建产物校验和一致。

因此,文章不把某个未经 BootROM 文档和板卡手册确认的固定偏移写成通用命令。u-boot.bin、u-boot.imx 和实际写入位置也不能互换:裸二进制是否可直接启动,取决于启动介质和 BootROM 所需的 IVT/DCD 镜像格式。

10.9 源码不能替代硬件资料

复制参考板目录只能建立软件结构,不能证明目标板硬件兼容。以下信息必须分别从原理图、芯片数据手册、板卡手册或实测中确认:

项目必须核对的事实
DDR颗粒型号、容量、位宽、片选、地址线连接、时序和校准参数
PHY芯片型号、MDIO 地址、复位脚、电源时序和连接的 FEC 控制器
LCD数据总线、IOMUX、背光/复位 GPIO、分辨率和时序
启动介质启动开关、用户区/boot 分区、写入偏移和环境存储位置
USB、I2C、GPIO引脚复用、电气连接、外部器件和有效电平

源码中的宏名、目录名和参考板名称只能说明软件当前假设,不能单独推出这些硬件事实。尤其是 DDR DCD、PHY 地址和复位 GPIO,一项错误就可能导致无串口输出或外设不可用。


附录

附录 A:参考文档

  • 04-U-Boot完整启动流程详解.md – 启动流程理论
  • 09-U-Boot移植关键点与修改指南.md – 移植实践指导
  • 移植过程概述.txt – 实际四步移植和后续外设适配记录
  • 调用关系图.png – 完整函数调用流程

附录 B:关键文件路径速查

configs/
└── mx6ull_alientek_emmc_defconfig          # 默认配置

include/configs/
└── mx6ull_alientek_emmc.h                  # 板级头文件

board/freescale/mx6ull_alientek_emmc/
├── Makefile                                # 编译规则
├── Kconfig                                 # 板级配置
├── MAINTAINERS                             # 维护者
├── imximage.cfg                            # ⭐ DCD 表
└── mx6ull_alientek_emmc.c                  # 板级代码

arch/arm/cpu/armv7/mx6/
└── Kconfig                                 # i.MX6 SoC 层配置

附录 C:常用命令

# 配置
make mx6ull_alientek_emmc_defconfig

# 交互式配置(可选,ncurses 文本菜单)
make menuconfig

# 将当前配置保存为精简 defconfig(确认内容后再覆盖 configs/ 中的文件)
make savedefconfig
cp defconfig configs/mx6ull_alientek_emmc_defconfig

# 编译
make V=1 -j$(nproc)

# 查看配置及其生成结果
grep MX6ULL .config
grep -E '^CONFIG_SYS_(SOC|VENDOR|BOARD|CONFIG_NAME)=' include/config/auto.conf
grep -E 'CONFIG_(IMX_CONFIG|MX6ULL_EVK_EMMC_REWORK)' include/generated/autoconf.h include/config.h

# 检查 i.MX 镜像头和 DCD/插件信息
mkimage -l u-boot.imx

# 查看 u-boot.imx 目标的实际命令,不执行构建
make V=1 -n u-boot.imx
cat .u-boot.imx.cmd

# 查看符号
nm u-boot | grep board_init
nm u-boot | grep dram_init

# 查看反汇编
arm-linux-gnueabihf-objdump -d u-boot > u-boot.dis

完成! ✅

这份文档完整地回答了你的所有问题:

  1. ✅ 详细讲解移植步骤的目的和原因
  2. ✅ 提供大型流程图展示操作关系
  3. ✅ 结合启动流程分析移植行为的影响
  4. ✅ 解释理论分析与实际移植的差异
  5. ✅ 追踪每个修改的后续影响
上一篇 uboot make过程分析

下一篇 查看更多专栏文章

当前已经是最后一篇,可以返回目录继续浏览。