一、具体需求基于瑞芯微RK3506平台、SPI-NAND闪存实现U-Boot阶段自定义write_bootloader函数通过网络下载固件完成双段Bootloader自动烧写第一段烧写 MiniloaderSPL 前置引导程序写入Flash物理0地址启动区第二段烧写完整U-Boot本体写入MTD uboot分区要求兼容RK原厂分包固件格式、无内存崩溃、无Flash擦写报错、烧写后设备可正常逐级启动完全使用MTD命令适配SPI-NAND摒弃MMC扇区逻辑适配项目现有分区表二、项目硬件与分区环境平台RK3506 SPI-NAND设备开机查询mtd list获取完整SPI-NAND设备与分区信息真实硬件分区布局如下unrecognized JEDEC id bytes: ff, 2c, 24 List of MTD devices: * spi-nand0 - device: flash0 - parent: spiff488000 - driver: spi_nand - type: NAND flash - block size: 0x20000 bytes - min I/O: 0x800 bytes - OOB size: 128 bytes - OOB available: 62 bytes - ECC strength: 8 bits - ECC step size: 512 bytes - bitflip threshold: 8 bits - 0x000000000000-0x000010000000 : spi-nand0 - 0x000000400000-0x000000a00000 : uboot - 0x000000a00000-0x000000b00000 : misc - 0x000000b00000-0x000001000000 : logo - 0x000001000000-0x000001a00000 : boot - 0x000001a00000-0x000010000000 : rootfs * spi-nand1 - device: flash0 - parent: spiff488000 - driver: spi_nand - type: NAND flash - block size: 0x20000 bytes - min I/O: 0x800 bytes - OOB size: 128 bytes - OOB available: 62 bytes - ECC strength: 8 bits - ECC step size: 512 bytes - bitflip threshold: 8 bits - 0x000000000000-0x000010000000 : spi-nand1裸启动区无MTD分区Flash物理 0x00000000 ~ 0x00400000 区间不属于任何MTD分区是BootROM/SPL专属启动区域用于存放Miniloader引导程序有效MTD分区起始0x00400000 偏移开始依次为 uboot、misc、logo、boot、rootfs 业务分区U-Boot本体需烧录至 uboot 分区内偏移0位置SPI-NAND硬件核心特性物理擦除块大小固定为128KB0x20000擦除、写入操作必须满足整块对齐非对齐尺寸会直接报错、执行失败设备说明系统存在spi-nand0、spi-nand1两个设备节点实际有效启动与业务分区均挂载在spi-nand0烧写操作仅针对该设备冗余告警说明开机打印unrecognized JEDEC id bytes: ff, 2c, 24为SPL阶段闪存ID未适配告警不影响上层U-Boot对Flash的正常读写与启动功能可直接忽略三、最终解决方案与完整执行逻辑1、旧版错误完整代码// 存在致命问题动态内存死机、mtd 0非法设备、非对齐擦除、丢失32KB头部、裸写loader int write_bootloader(unsigned long ram_addr, unsigned int len) { char cmd[128] {0}; void *buff_tmp; unsigned int size, *addr; struct blk_desc *dev_desc; disk_partition_t part; dev_desc blk_get_dev(mtd, 0); part_get_info_by_name(dev_desc, uboot, part); DEBUG_INFO(%s\r\n,__func__); addr (unsigned int *)ram_addr; size be32_to_cpu(addr[0]); // 致命问题1U-Boot阶段动态申请内存极易触发data abort死机 buff_tmp memalign(64, 32*1024 size); // 致命问题2使用非法设备 mtd 0系统无此设备命令全部报错 sprintf(cmd, mtd read 0 0x%lx 0 0x8000, (unsigned long)buff_tmp); run_cmd(cmd); memcpy(buff_tmp 32*1024, (void *)ram_addr 4, size); // 致命问题3动态长度擦除不满足128KB块对齐SPI-NAND硬件报错拒绝执行 sprintf(cmd, mtd erase 0 0 0x%x, 32*1024 size); run_cmd(cmd); sprintf(cmd, mtd write 0 0x%lx 0 0x%x, (unsigned long)buff_tmp, 32*1024 size); run_cmd(cmd); addr (unsigned int *)((unsigned long)addr 4 size); size be32_to_cpu(addr[0]); sprintf(cmd, mtd erase uboot); run_cmd(cmd); sprintf(cmd, mtd write uboot 0x%lx 0x0 0x%x, (unsigned long)addr 4, size); run_cmd(cmd); DEBUG_INFO(-%s\r\n,__func__); return 0; }2、关键修复改动修复1解决memalign动态内存死机问题替换内存申请逻辑旧代码问题使用buff_tmp memalign(64, 32*1024 size);动态申请堆内存U-Boot下载阶段内存资源有限极易触发内存越界、Data Abort死机重启。修复方案删除所有动态内存申请改用64字节对齐全局静态缓冲区内存地址固定、无堆溢出风险彻底规避系统崩溃。修复2解决MTD设备不存在报错修正Flash设备操作对象旧代码问题错误使用mtd read 0 / mtd erase 0系统无mtd0设备直接返回-19命令错误Loader读写完全失效。修复方案替换为系统真实合法设备节点spi-nand0所有物理0地址启动区读写、擦除均基于spi-nand0操作命令合法可执行。修复3解决SPI-NAND块非对齐报错固化硬件擦除规则旧代码问题动态计算擦除长度32KBsize长度随机、不满足128KB擦除块对齐硬件拒绝执行擦除失败导致Loader损坏。修复方案放弃动态长度固定擦除/写入128KB(0x20000)整启动块完全匹配SPI-NAND硬件最小擦除单元彻底消除对齐报错。修复4解决SPL启动失败-74错误还原RK标准启动头格式旧代码问题曾删减32KB头部拼接逻辑、裸写Loader破坏RK硬件启动校验头BootROM无法识别合法引导镜像。修复方案保留读取Flash原始128KB启动块保留前32KB硬件启动参数、校验标记仅在后段拼接新Loader保证启动镜像格式合规。修复5规范双段固件烧写逻辑区分启动区与业务分区旧代码问题未严格区分二级启动架构存在Loader和U-Boot分区混写、覆盖启动区的问题。修复方案物理0地址128KB区块专属烧写Miniloader(SPL)标准uboot分区专属烧写U-Boot本体两段独立烧写、互不干扰匹配RK3506原生启动链路。3、新版最终完整代码// 全局静态64字节对齐缓冲区 // 规避U-Boot动态内存memalign死机、栈溢出问题大小适配128KB启动块 static char g_tmp_buf[128*1024] __attribute__((aligned(64))); /** * write_bootloader - 双段Bootloader固件烧写函数 * ram_addr: 下载固件在内存的起始地址 * len: 固件总长度 * 适配RK3506SPI-NAND兼容原厂分包固件格式解决启动失败、内存死机、块对齐报错问题 */ int write_bootloader(unsigned long ram_addr, unsigned int len) { char cmd[128] {0}; unsigned int size, *addr; DEBUG_INFO(%s\r\n, __func__); addr (unsigned int *)ram_addr; // 1、解析第一段固件Miniloader(SPL)有效长度4字节大端长度头 size be32_to_cpu(addr[0]); // 2、读取spi-nand0物理0地址128KB启动整块 // 保留前32KB RK硬件启动私有头部启动校验标记、闪存参数等核心信息 sprintf(cmd, mtd read spi-nand0 0x%lx 0 0x20000, (unsigned long)g_tmp_buf); run_cmd(cmd); // 3、拼接新Miniloader固件覆盖32KB后的Loader有效区域保留头部不变 memcpy(g_tmp_buf 32*1024, (void *)ram_addr 4, size); // 4、整块擦除128KB启动区严格128KB块对齐规避SPI-NAND擦除报错 sprintf(cmd, mtd erase spi-nand0 0 0x20000); run_cmd(cmd); // 5、写入完整128KB启动块32KB头部新Miniloader sprintf(cmd, mtd write spi-nand0 0x%lx 0 0x20000, (unsigned long)g_tmp_buf); run_cmd(cmd); // 6、内存偏移跳转至第二段固件跳过第一段4字节长度头Miniloader数据 addr (unsigned int *)((unsigned long)addr 4 size); // 解析第二段固件U-Boot本体有效长度 size be32_to_cpu(addr[0]); // 7、擦除标准uboot分区MTD分区内操作无需物理地址偏移 sprintf(cmd, mtd erase uboot); run_cmd(cmd); // 8、U-Boot本体写入uboot分区起始偏移0位置匹配分区表布局 sprintf(cmd, mtd write uboot 0x%lx 0 0x%x, (unsigned long)addr 4, size); run_cmd(cmd); DEBUG_INFO(-%s\r\n, __func__); return 0; }4、最终完整执行逻辑解析内存固件首地址读取第一段4字节大端长度获取Miniloader有效数据长度读取SPI-NAND0物理0地址的128KB启动块保留原生32KB硬件启动头部信息将新的Miniloader固件拼接在32KB头部之后整块擦除128KB启动区写入拼接完成的完整启动数据完成前置引导更新内存地址偏移跳过第一段固件解析第二段U-Boot本体长度标准擦除uboot分区将U-Boot本体写入分区起始位置烧写完成设备可正常完成 BootROM→SPL→OP-TEE→U-Boot 完整启动链路四、初始问题与故障现象1、初始代码核心故障内存Data Abort死机、CPU重启初始代码使用memalign动态堆内存申请U-Boot下载阶段内存池紧张触发非法内存访问直接死机复位完整报错日志data abortpc : 0021ef16 lr : 0021ef65sp : 01f73288 ip : 01f733b0 fp : 004431c8...Resetting CPU ...### ERROR ### Please RESET the board ###MTD设备不存在报错初始错误使用mtd 0非法设备节点当前固件仅存在spi-nand0/spi-nand1设备导致读写命令全部失败完整报错日志unrecognized JEDEC id bytes: ff, 2c, 24MTD device 0 not found, ret -19cmd error!SPI-NAND块非对齐擦除报错初始按固件实际长度动态擦除未适配128KB硬件块对齐规则触发校验失败完整报错日志Size not a multiple of a block (0x20000)cmd error!Size not on a page boundary (0x800), rounding to 0x4b800SPL启动读取失败、整机无法开机Loader写入格式非法、启动头部损坏BootROM无法加载SPL启动链路断裂完整报错日志unrecognized JEDEC id bytes: ff, 2c, 24Trying to boot from MTD1NAND read from offset 0 failed -74NAND read from offset 0 failed -74Trying fit image at 0x4000 sectorNot fit magicSPL: failed to boot from all boot devices### ERROR ### Please RESET the board ###次生告警日志可忽略SPL阶段不识别闪存ID上层U-Boot正常适配不影响功能unrecognized JEDEC id bytes: ff, 2c, 24mtd: partition “rootfs” extends beyond the end of device “spi-nand0” -- size truncated2、核心根因精准补充结合旧代码逻辑补充最核心、此前遗漏的致命根因根因1旧代码读取长度不完整破坏RK启动块结构旧代码仅读取0x8000(32KB)头部擦除写入长度为「32KBloader长度」无法覆盖128KB硬件启动块导致启动块后半段残留脏数据、启动校验不完整最终SPL读取校验失败报-74错误。根因2双SPI-NAND设备歧义未处理系统同时存在spi-nand0、spi-nand1spi-nand0真实物理Flash包含0地址启动区、uboot/rootfs完整分区是正常启动介质spi-nand1虚拟镜像备份设备无真实物理启动能力不能用于烧写bootloader根因3缺失「读改写」原子逻辑旧代码逻辑为「局部读动态长度写」无法保留原厂完整128KB启动块参数RK芯片启动必须保证整128KB启动块合法合规局部改写必然损坏启动镜像。错误操作MTD设备、未适配SPI-NAND块对齐规则删除RK必需的32KB启动头部导致BootROM无法识别合法引导程序动态内存申请在U-Boot阶段不稳定引发系统崩溃未区分「物理0地址启动区」和「uboot分区」的两段烧写逻辑五、核心疑问解答关键逻辑1、为什么固件要分为两次下载、两段烧写RK平台SPI-NAND启动为二级启动架构固件天然分为两个独立镜像必须分别烧写至不同位置第一段 Miniloader(SPL)烧写在Flash物理0地址由硬件BootROM直接读取负责初始化DDR、SPI控制器是最前置的引导代码第二段 U-Boot本体烧写在uboot分区0x400000偏移由SPL加载启动是完整的系统引导程序固件分包格式每段固件头部固定4字节大端数据标识本段固件有效长度代码通过be32_to_cpu解析长度实现两段固件分离烧写。2、32KB头部是什么信息为什么必须读取保留32KB头部不是软件自定义协议头是RK SPI-NAND硬件启动规范预留区域。该区域存放BootROM识别必需的启动校验标记、硬件配置信息、SPI-NAND参数、启动链路配置BootROM上电只会校验Flash最前32KB的启动合法标识如果直接裸写Loader、覆盖或清空该区域会导致启动标记丢失SPL读取失败、设备无法开机业务逻辑保留原有32KB合法头部仅在32KB偏移后拼接新的Miniloader代码既更新引导程序又保留硬件启动合法性3、为什么必须「先读取原始128KB、再拼接、再整块回写」RK3506 SPI-NAND 启动机制硬性要求前32KB 是芯片出厂固化的硬件启动描述区包含SPI时序、ECC参数、启动校验位、DDR初始化预参数用户代码不能生成、不能清零32KB~128KB 是原厂预留填充区必须保持完整块对齐绝对不能直接裸写0地址直接写入会清空硬件启动参数BootROM识别不到合法Boot直接报 NAND read -74 失败因此正确流程只能是读取完整原始128KB → 保留前32KB → 替换后半段Loader → 整块擦除回写保证硬件参数不丢失、启动镜像合法。SPI-NAND硬件硬性限制擦除最小物理单元为128KB0x20000任何非128KB整数倍的擦除/写入操作硬件都会拒绝执行、触发对齐报错、导致Flash区块损坏。固定整块操作是唯一兼容方案。六、问题-根因-修复 一一对应总结故障现象根本原因最终修复方案Data Abort 死机重启Uboot阶段memalign动态堆内存不稳定替换为64字节对齐静态全局缓冲区MTD device 0 not found错误使用不存在的mtd0设备号改用真实物理设备 spi-nand0Size not a multiple of a block动态长度不满足128KB硬件块对齐固定128KB整块擦除/写入SPL read failed -74 无法开机破坏32KB启动头、启动块不完整、脏数据残留保留原厂32KB头部整块128KB原子更新双设备启动异常风险未区分spi-nand0物理设备与spi-nand1虚拟设备强制绑定spi-nand0为唯一烧写设备七、最终效果彻底解决内存死机、MTD报错、块对齐报错、SPL启动-74报错所有问题BootROM → SPL → OP-TEE → U-Boot 四级启动链路完整稳定完全兼容RK原厂分包固件格式可直接量产使用无内存崩溃、无MTD报错、无Flash块对齐报错Miniloader写入合法SPL不再读取失败