3. 车载开发板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(®s->NFCONT) | (1 << 1), ®s->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(®s->NFCONT) & ~(1 << 1), ®s->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, ®s->NFCMD);
if (column != -1) {
writeb(column & 0xFF, ®s->NFADDR);
writeb((column >> 8) & 0xFF, ®s->NFADDR);
}
if (page_addr != -1) {
writeb(page_addr & 0xFF, ®s->NFADDR);
writeb((page_addr >> 8) & 0xFF, ®s->NFADDR);
writeb((page_addr >> 16) & 0xFF, ®s->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, ®s->NFCMD);
for (i = 0; i < mtd->writesize; i++)
p[i] = readb(®s->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, ®s->NFCONF); // 简化配置,实际需按芯片手册设置TACLS/TWRPH0/TWRPH1
writel((1 << 0) | (1 << 4), ®s->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
验证方法
- 重新上电后,U-Boot应从NAND启动并进入命令行。
- 执行
nand info,应显示检测到的NAND芯片型号与容量(如K9F2G08U0C 256MB)。 - 执行
nand read 0x30000000 0x60000 0x100,无错误提示且返回读取成功。 - 执行
saveenv后重启,环境变量应能持久保存(修改bootdelay后重启验证)。 - 执行
boot或直接复位,观察内核是否从NAND加载并启动,串口输出内核启动日志。
通过条件:上述5项全部通过,特别是第5项能完整启动内核并挂载根文件系统。
常见问题
- NAND ID读取失败:检查NFCONF时序配置是否匹配NAND芯片数据手册;确认片选信号GPIO配置正确。
- 环境变量无法保存:确认CONFIG_ENV_OFFSET未与U-Boot镜像或内核分区重叠;检查NAND坏块管理是否启用。
- 启动内核时ECC错误:若内核镜像较大,需在nand read前使用
nand ecc on开启硬件ECC,或确保烧录时写入ECC数据。