3. 车载开发板U-Boot移植与启动流程调试

车载开发板U-Boot移植与启动流程调试 学习卡片

任务目标

在嵌入式Linux项目中,目标板使用NAND Flash作为启动介质。当前U-Boot默认配置不支持NAND控制器,需要修改源码、添加驱动支持并配置环境变量,使U-Boot能够从NAND Flash读取并启动内核。

环境与依赖

  • U-Boot源码版本:2023.04(或其他已适配目标板的版本)
  • 交叉编译工具链:arm-linux-gnueabihf-
  • 目标板NAND控制器:以S3C2440(或类似ARM9平台)为例,NAND芯片为K9F2G08U0C(256MB)
  • Linux内核镜像:uImage(已生成并烧录至NAND指定分区)

实现步骤与代码

以下修改基于U-Boot源码顶层目录执行。核心工作分为三部分:板级配置、驱动使能、环境变量设置。

// 1. 修改板级头文件 include/configs/s3c2440_nand.h
// 添加NAND配置宏(若原文件不存在则新建)
#ifndef __S3C2440_NAND_H
#define __S3C2440_NAND_H

#include <configs/s3c2440.h>

/* NAND Flash 配置 */
#define CONFIG_CMD_NAND
#define CONFIG_NAND_S3C2440
#define CONFIG_SYS_MAX_NAND_DEVICE     1
#define CONFIG_SYS_NAND_BASE           0x4E000000
#define CONFIG_SYS_NAND_USE_FLASH_BBT

/* 环境变量分区:NAND 0x0~0x40000 存放U-Boot,0x40000~0x60000 存放环境变量 */
#define CONFIG_ENV_IS_IN_NAND
#define CONFIG_ENV_OFFSET              0x40000
#define CONFIG_ENV_SIZE                0x20000
#define CONFIG_ENV_RANGE               (2 * CONFIG_ENV_SIZE)

/* 内核启动参数分区:内核存放于 0x60000,DTB存放于 0x500000 */
#define CONFIG_BOOTCOMMAND \
    "nand read 0x30007FC0 0x60000 0x400000; " \
    "nand read 0x31000000 0x500000 0x80000; " \
    "bootm 0x30007FC0 - 0x31000000"

#define CONFIG_BOOTARGS \
    "console=ttySAC0,115200 root=/dev/mtdblock3 rootfstype=jffs2"

#endif /* __S3C2440_NAND_H */
// 2. 在板级目录中添加驱动文件 drivers/mtd/nand/s3c2440_nand.c
// 最小驱动实现:初始化控制器、读取NAND ID、实现读写页
#include <common.h>
#include <nand.h>
#include <asm/io.h>
#include <asm/arch/s3c2440.h>

#define NAND_CMD_READ0      0x00
#define NAND_CMD_READID     0x90
#define NAND_CMD_STATUS     0x70

static void s3c2440_nand_select(struct mtd_info *mtd, int chip)
{
    struct s3c2440_nand_regs *regs = (struct s3c2440_nand_regs *)CONFIG_SYS_NAND_BASE;
    writel(readl(&regs->NFCONT) | (1 << 1), &regs->NFCONT);  // 使能片选
}

static void s3c2440_nand_deselect(struct mtd_info *mtd, int chip)
{
    struct s3c2440_nand_regs *regs = (struct s3c2440_nand_regs *)CONFIG_SYS_NAND_BASE;
    writel(readl(&regs->NFCONT) & ~(1 << 1), &regs->NFCONT); // 禁用片选
}

static void s3c2440_nand_command(struct mtd_info *mtd, unsigned int command,
                                 int column, int page_addr)
{
    struct s3c2440_nand_regs *regs = (struct s3c2440_nand_regs *)CONFIG_SYS_NAND_BASE;
    writeb(command, &regs->NFCMD);
    if (column != -1) {
        writeb(column & 0xFF, &regs->NFADDR);
        writeb((column >> 8) & 0xFF, &regs->NFADDR);
    }
    if (page_addr != -1) {
        writeb(page_addr & 0xFF, &regs->NFADDR);
        writeb((page_addr >> 8) & 0xFF, &regs->NFADDR);
        writeb((page_addr >> 16) & 0xFF, &regs->NFADDR);
    }
}

static int s3c2440_nand_read_page(struct mtd_info *mtd, struct nand_chip *chip,
                                  uint8_t *buf, int oob_required, int page)
{
    struct s3c2440_nand_regs *regs = (struct s3c2440_nand_regs *)CONFIG_SYS_NAND_BASE;
    int i;
    uint8_t *p = buf;

    s3c2440_nand_command(mtd, NAND_CMD_READ0, 0, page);
    writeb(NAND_CMD_READ0, &regs->NFCMD);

    for (i = 0; i < mtd->writesize; i++)
        p[i] = readb(&regs->NFDATA);

    return 0;
}

int board_nand_init(struct nand_chip *nand)
{
    struct s3c2440_nand_regs *regs = (struct s3c2440_nand_regs *)CONFIG_SYS_NAND_BASE;

    /* 初始化NAND控制器时序 */
    writel(0x0, &regs->NFCONF);   // 简化配置,实际需按芯片手册设置TACLS/TWRPH0/TWRPH1
    writel((1 << 0) | (1 << 4), &regs->NFCONT);  // 使能NAND控制器、关闭片选

    nand->options = NAND_NO_AUTOINCR;
    nand->select_chip = s3c2440_nand_select;
    nand->deselect_chip = s3c2440_nand_deselect;
    nand->cmdfunc = s3c2440_nand_command;
    nand->read_byte = s3c2440_nand_read_byte;
    nand->read_buf = s3c2440_nand_read_buf;

    return 0;
}
// 3. 在板级配置文件中使能驱动(configs/s3c2440_nand_defconfig)
CONFIG_NAND=y
CONFIG_NAND_S3C2440=y
CONFIG_CMD_NAND=y
CONFIG_ENV_IS_IN_NAND=y
CONFIG_ENV_OFFSET=0x40000
CONFIG_ENV_SIZE=0x20000

编译与烧录

# 编译U-Boot
make s3c2440_nand_defconfig
make CROSS_COMPILE=arm-linux-gnueabihf- -j4

# 烧录U-Boot到NAND(通过JTAG或已有SD卡启动的U-Boot)
# 假设已通过其他方式进入U-Boot命令行:
# nand erase 0 0x40000
# tftp 0x30000000 u-boot.bin
# nand write 0x30000000 0 0x40000

# 烧录内核与DTB
# nand erase 0x60000 0x400000
# tftp 0x30007FC0 uImage
# nand write 0x30007FC0 0x60000 0x400000
# nand erase 0x500000 0x80000
# tftp 0x31000000 exynos.dtb
# nand write 0x31000000 0x500000 0x80000

验证方法

  1. 重新上电后,U-Boot应从NAND启动并进入命令行。
  2. 执行 nand info,应显示检测到的NAND芯片型号与容量(如K9F2G08U0C 256MB)。
  3. 执行 nand read 0x30000000 0x60000 0x100,无错误提示且返回读取成功。
  4. 执行 saveenv 后重启,环境变量应能持久保存(修改bootdelay后重启验证)。
  5. 执行 boot 或直接复位,观察内核是否从NAND加载并启动,串口输出内核启动日志。

通过条件:上述5项全部通过,特别是第5项能完整启动内核并挂载根文件系统。

常见问题

  • NAND ID读取失败:检查NFCONF时序配置是否匹配NAND芯片数据手册;确认片选信号GPIO配置正确。
  • 环境变量无法保存:确认CONFIG_ENV_OFFSET未与U-Boot镜像或内核分区重叠;检查NAND坏块管理是否启用。
  • 启动内核时ECC错误:若内核镜像较大,需在nand read前使用 nand ecc on 开启硬件ECC,或确保烧录时写入ECC数据。