烧录成功但不开机 · SPI NAND 量产排查手册
编制日期 2026-09-21 | 版本号 Rev 1.0
XTX 芯天下 · FAE 现场技术文档 | 覆盖 SD NAND / Serial NAND / ONFI 兼容 SPI NAND | 2026-09 版
文中的命令、寄存器位、ECC 强度、坏块标记位置一律以器件 datasheet 与目标平台驱动代码为准,不得凭印象编写。
- SPI NAND 没有统一的强制标准:坏块标记位置、spare 区划分、ECC 保护范围、配置位地址与默认值都由各厂自定,同名位在不同厂器件上含义可能不同。
- 命令码与特征地址(如 SET/GET FEATURES、读状态、页读)需逐颗核对 datasheet;本文给出的均为常见写法示例,不是通用常量。
- 目标平台的 OOB 布局(ECC 位置、ECC 字节数、ECC 块大小)以驱动源码与设备树为准,不以烧录器默认值或经验为准。
- 凡本文标注「以 datasheet 为准」的条目,现场必须打开对应料号的正式 datasheet 复核后再动手。
一句话结论:烧录器的 Verify PASS 只证明
「写进去的字节和源镜像一致」,不证明「目标 SoC 读得懂这些字节」。
NAND 的校验是「数据搬运正确」,不是「系统语义正确」——
坏块策略、ECC 生成方、OOB 布局、分区偏移、控制位这五件事只要有一件与板上驱动不一致,
校验就会 100% 通过而系统 0% 起来。
这份文档解决什么
解决的是裸片烧录成功、贴板却不启动这一类问题的定位与根治方法, 适用于使用 SPI NAND(含 SD NAND、Serial NAND、ONFI 兼容 SPI NAND)作为启动介质或数据存储介质的量产场景。 全文按「现象 → 机制 → 类目 → 立即动作 → 根治动作」组织,第 9 章是可直接翻查的速查表。
怎么用这份文档
- 先做最小判别:把「不开机」按第 1 章的四种画像归类,确定是哪一段起不来,不要一上来就换芯片。
- 按第 2 章的决策树定层:先判断错位发生在烧录器侧、镜像侧还是目标驱动侧,再进对应章节。
- 拿「已知好板」做对照:找一块能正常启动的板子,把它的镜像、烧录配置、器件读回数据各存一份基线。
- 改一个变量就验一次:坏块策略、ECC 开关、偏移配置每次只改一处,改完全片重烧再上电。
- 用装板实跑闭环:任何配置变更,最终判定以「装板跑通完整启动 + 关键功能 + 一次断电重启」为准。
1. 问题本质:烧录通过 ≠ 系统能跑
烧录器报 Verify PASS、CRC 完全一致,芯片贴板后却黑屏、卡 logo 或文件系统挂不上—— 这通常不是芯片坏了,而是「写进去的字节」与「读它时使用的规则」之间错位了。本章先把现象还原成机制。
1.1 四种数据画像
现场接到「烧好了但不开机」时,先按串口输出把现象归到下面四类之一。 四类现象对应的是同一机制在不同启动阶段的暴露,不是四种独立故障。
1.2 校验通过,到底证明了什么
这是全文最关键的一张表。烧录器的校验(Verify)本质是把写进去的内容读回来与源镜像逐字节比对, 它检验的是「数据搬运」这一件事;而系统能否跑起来,取决于目标 SoC 侧的「数据解释」规则。
| 校验通过能证明 | 校验通过不能证明 | 由谁决定 |
|---|---|---|
| 烧录器缓冲区里的数据与源镜像逐字节一致 | 目标 SoC 能读懂这些字节 | 目标驱动 / BootROM |
| 每个被写的页都编程成功(编程失败标志为 0) | 这些页落在正确的物理块上 | 烧录器的坏块策略 |
| spare 区写入的字节与烧录配置一致 | spare 区布局与驱动的 OOB 布局一致 | 驱动 ECC 配置 |
| 在当前配置位状态下读写正常 | 断电后配置位仍保持烧录时设的值 | 器件上电默认值 |
| 校验时器件处于可编程状态 | 出厂坏块标记仍然存在且可识别 | 烧录前是否整片擦除 |
| 这一颗芯片能跑 | 这一批、这个料号、这个批次都能跑 | 首件确认 + 装板抽检 |
1.3 一句话机制:三层规则必须对齐
一颗 SPI NAND 从被烧录到被系统读出来,中间要经过三方规则的叠加:
- 烧录器侧规则:坏块怎么处置、ECC 由谁算、写入粒度是整页还是分段、空白页怎么处理、配置位设成什么。
- 镜像侧规则:分区表怎么划、各段按页对齐还是按块对齐、spare 区里带不带 ECC 字节。
- 目标驱动侧规则:BootROM 认哪几个块、驱动用片上 ECC 还是 SoC 硬件 ECC、OOB 布局硬编码成什么样。
这三层里任意一层与另外两层不一致,校验都会通过,系统都不会起来。 烧录器的校验逻辑完全不参与这三层规则的判定,所以它永远不会因此报错。
「烧录通过 ≠ 贴板能跑」。只要现象是「校验 100%% 通过但系统不起来」,就不要把时间花在怀疑芯片质量上——先查三层规则是否对齐。芯片本身不良的概率远低于规则错位的概率。
1.4 与 SPI NOR 相比,NAND 多了什么
很多 FAE 的直觉来自 SPI NOR:NOR 没有坏块、没有 spare 区、字节可寻址、烧什么读什么。 换成 NAND 之后,这条直觉链全部失效:
| 对比项 | SPI NOR | SPI NAND(含 SD NAND / Serial NAND) |
|---|---|---|
| 坏块 | 出厂无坏块,无需坏块管理 | 出厂允许存在坏块,需坏块表管理(以 datasheet 给出的最小有效块数为准) |
| 最小写入单位 | 可按字节 / 页编程 | 按页编程,且页内部分编程次数有限(常见为每页最多 4 次,以 datasheet 为准) |
| 擦除单位 | 扇区 / 块 | 块(一个块含数十页,以 datasheet 为准) |
| spare 区 | 无 | 每页带 spare 区(常见 64B / 128B),存放坏块标记、元数据与 ECC 校验字节 |
| ECC | 不需要 | 必须有;可由器件片上 ECC 生成,也可由 SoC 硬件 ECC 或软件 ECC 生成 |
| 读取流程 | 发地址直接读 | 先把整页从阵列搬到缓存,再从缓存读出(页读 + 读缓存两段式) |
| 地址语义 | 字节地址 | 页地址 + 列地址;物理块号会因跳过坏块而漂移 |
不要假设 SPI NAND 出厂坏块策略就是「跳过」——各厂不同,同一厂不同料号也可能不同。有的器件在出厂时把坏块标记写进坏块第一页的 spare 区首字节,有的厂商在坏块的前两页都写标记;有的器件内建坏块替换查找表(LUT),有的完全交给主机软件管理。现场必须打开实际料号的 datasheet 确认「坏块标记写在哪、怎么识别」。
2. 三层错位总览与排查决策树
所有的「烧录通过但不开机」都可以归结为三层规则(烧录器侧 / 镜像侧 / 目标驱动侧) 没有对齐。本章给出总览图与决策树,用来在动手之前先定层——定错层,后面所有动作都是白做。
2.1 三层错位总览
下面的三层各有自己的一套规则。烧录器的校验逻辑完全不参与这三层规则的判定, 所以三层之间错位时,校验永远通过。定位问题的第一步,是判断错位发生在哪一层。
2.2 三层各自的职责与典型错位点
| 层 | 它决定什么 | 典型错位点 | 由谁确认 |
|---|---|---|---|
| ① 烧录器侧 | 坏块如何处置、ECC 由谁生成、写入粒度、空白页如何处理、配置位设成什么 | 跳过坏块导致物理偏移整体漂移;空白页照烧触发重复编程(over program) | 烧录工程师 / 编程器原厂 |
| ② 镜像侧 | 分区表、各段起始偏移与对齐单位、spare 区是否预置 ECC 字节 | 镜像按逻辑偏移打包,未预留坏块跳过的容量;分段未按要求对齐 | 固件 / BSP 负责人 |
| ③ 目标驱动侧 | BootROM 认哪几个块、用哪种 ECC 引擎、OOB 布局硬编码成什么样 | 驱动的 OOB 布局与烧录侧 spare 布局不一致;BootROM 要求特殊分块而镜像未分块 | 驱动 / BSP 负责人 |
2.3 排查决策树
拿到一块「烧好了但不开机」的板子,按下面的顺序走。每一步都有明确的判定输出, 不要在没拿到串口日志之前就开始改配置。
2.4 判定顺序:从最便宜的动作开始
- 收集:串口完整日志(从第一条开始,不要只看后半段)、烧录器工程文件与配置截图、 镜像分区表、器件丝印料号与批次号。
- 取基线:找一块能正常启动的板子,用同一套工具读回它的全片内容(含 spare 区)存为基线。
- 定层:按图 3 决策树确定现象归属,进入第 3~7 章中对应的类目章节。
- 单点改动:一次只改一个变量(坏块策略 / ECC 开关 / 偏移配置 / 分块方式),改完整片重烧。
- 实跑验证:装板跑通完整启动 + 关键功能 + 一次断电重启,才算这个变量验完。
- 归档:把结论写回 SOP 与烧录工程文件,并标注料号 + 批次 + 固件版本三要素。
三层错位本质上是两份配置的差异。没有基线,就只能靠猜;有了基线,把好板与坏板的读回数据(含 spare 区)、烧录器工程配置、串口日志两两对比,差异点基本就是根因。
基线要包含 spare 区——主区数据完全一致但系统一好一坏的情况,根因几乎都在 spare 区(坏块标记 / ECC 校验字节 / 元数据)。
串口日志是唯一能区分「完全黑屏」「卡 logo」「文件系统挂不上」的依据,而这三种现象指向的类目完全不同。先接串口、先存日志,再谈改配置。
3. 第 1 类:坏块处理不一致
NAND 出厂就允许带坏块,坏块靠「标记」来识别。烧录器、镜像、目标驱动三方 对「标记写在哪、要不要跳过、跳过后偏移怎么算」的约定一旦不一致,就会出现 校验全过、贴板不启动。这是 SPI NAND 量产最高发的一类问题。
3.1 先把这颗料的坏块规则查清楚
动手之前,必须打开实际料号的 datasheet,把下面四个问题查明白。 这四件事各厂不同,且同一厂不同料号也可能不同。
| 要查清的问题 | 为什么重要 | 不查清会怎样 |
|---|---|---|
| 坏块标记写在哪 | 常见为每块第 0 页 spare 区首字节(即 byte 2048 处);部分厂商在第 0、1 两页都写标记 | 按错位置扫描 → 坏块被当成好块使用,运行期随机出错 |
| 标记的判定值是什么 | 常见为「非 FFh」(多数厂写 00h);也有厂用其它约定 | 判定阈值写错 → 好块被误判为坏块,可用容量骤减 |
| 出厂最少有效块数(NVB) | datasheet 会给出保证值(例如 1Gb 器件常见的 1004 / 1024,以 datasheet 为准) | 扫出的坏块数一旦超过上限,说明料已被擦过标记,应整批隔离 |
| 器件是否内建坏块替换表 | 部分器件内建查找表(LUT),可自动把坏块重映射到好块,条目数有限 | 误以为主机软件全权管理 → 与器件内建机制重复处理,偏移算法错乱 |
3.2 三种坏块处置策略及其后果
烧录器对坏块的处理方式,决定了贴板后驱动看到的「世界」长什么样。 三种策略里,只有策略 B 是天然贴近驱动预期的,A 依赖驱动能力,C 是明确禁止的。
3.3 现场排查动作
- 先扫原始状态:拿到未烧录的空白片(同批次),读回整片 raw 数据, 用下面的脚本统计坏块数量与块号,作为出厂基线。
- 再扫烧录后状态:同一颗片子烧完再读回,再统计一次。 坏块数从 N 变成 0,就是标记被擦过,直接查烧录器的擦除配置。
- 核对烧录器配置:在工程文件里确认「擦除方式」是 按块 / 按需擦除还是整片擦除,以及「坏块处理」选的是哪一项。
- 核对驱动侧:在驱动源码里确认 BBT 的建立方式—— 是上电全片扫描重建,还是从固定位置读取已有表,还是两者都有。
- 对齐后重烧验证:改完配置后整片重烧(不是增量烧),装板实跑。
3.4 读回与比对
所有结论都要落回「读回来的字节」上,不要停在配置界面里的选项名。 下面给出读回与比对的常用命令与一段扫描脚本。
读回与比对的常用命令
# ① 在目标板上读回含 spare 区的原始页(命令随平台而变,以实际 BSP 为准)
# U-Boot 下:
nand dump 0x0 # 查看第 0 页主区
nand dump.oob 0x0 # 查看第 0 页 OOB / spare 区
# Linux 下按 MTD 分区读回(含 OOB):
nanddump -o -f /tmp/mtd0_oob.bin /dev/mtd0
hexdump -C /tmp/mtd0_oob.bin | head -n 16
# ② 与烧录器读回的文件在同一台机器上逐字节比对
cmp -l /tmp/from_programmer.bin /tmp/from_board.bin | head -n 20
# ③ 重点看三处:spare 首字节(坏块标记)、ECC 校验区、分区起始块的内容
# 主区全对、spare 不对 -> 第 4 章(ECC / spare 区不一致)
# spare 全对、块号不对 -> 第 5 章(分区偏移错位)
坏块扫描脚本(参数必须按 datasheet 改)
# badblock_scan.py:从整片读回的 raw bin 中统计疑似出厂坏块
# 下面四个参数必须按实际器件 datasheet 修改,不要照抄
PAGE = 2048 + 128 # 页大小(主区 + spare 区)
PAGES_PER_BLK = 64 # 每块页数
BB_OFF = 2048 # 坏块标记在页内偏移(spare 首字节)
CHECK_PAGES = (0, 1) # 多数厂只标记第 0 页,部分厂标记前两页
data = open("dump_raw.bin", "rb").read()
nblk = len(data) // (PAGE * PAGES_PER_BLK)
bad = []
for b in range(nblk):
base = b * PAGE * PAGES_PER_BLK
for p in CHECK_PAGES:
off = base + p * PAGE + BB_OFF
if off < len(data) and data[off] != 0xFF:
bad.append((b, p, data[off]))
break
print("块总数 %d,疑似坏块 %d 个(%.2f%%)" % (nblk, len(bad), 100.0 * len(bad) / max(1, nblk)))
for b, p, m in bad[:40]:
print(" block %5d page %d mark = 0x%02X" % (b, p, m))
# 判读:
# - 烧录前扫描到 N 个坏块,烧录后扫描到 0 个 -> 标记被擦除过,必须查烧录器配置
# - 烧录前后坏块数一致,但块号变了 -> 烧录器做了重映射,见第 5 章
# - 坏块比例明显高于 datasheet 给的上限 -> 拿到的是被擦过标记的料,整批隔离
3.5 坏块相关的状态位
SPI NAND 的操作结果通过状态寄存器 / 特征寄存器回读。下面列出的是 ONFI 兼容器件中较为常见的一组定义,不同厂家的位分配与编码并不统一, 必须按实际料号 datasheet 逐位核对。
| 标志位(示例名) | 含义 | 典型处置 | 备注 |
|---|---|---|---|
OIP / BUSY | 器件正在执行内部操作 | 轮询等待清零 | 各厂均有,位置以 datasheet 为准 |
P_FAIL | 编程(写页)失败 | 标记该块为坏块,换块重写 | 出现即说明该块不可靠 |
E_FAIL | 擦除失败 | 标记该块为坏块,不再使用 | 出现即说明该块不可靠 |
ECCS2/1/0 | 读操作的 ECC 状态 | 见下表的编码说明 | ONFI 兼容器件常见命名 |
WEL | 写使能锁存 | 写/擦前必须先置位 | 未置位时写命令会被忽略 |
ECCS2/1/0 | 含义(示例编码) | 处置建议 |
|---|---|---|
| 000 | 无错误 | 正常 |
| 001 | 发现 1~3 bit 错误并已纠正 | 记录即可,可考虑刷新该页 |
| 011 | 发现 4~6 bit 错误并已纠正 | 建议数据刷新(搬走重写) |
| 101 | 发现 7~8 bit 错误并已纠正 | 必须刷新,保留余量已不多 |
| 010 | 错误位数超出纠正能力,不可纠 | 按坏块处理,触发换块流程 |
对 NAND 执行整片擦除(Chip Erase)或不受控的跨块回绕擦除,会把出厂坏块标记一起抹掉,且标记无法可靠恢复。坏块的物理缺陷不会因为擦除而消失,只是失去了被识别的手段——结果是坏块被当成好块编程写入数据,现场表现为「校验 100% 通过,运行期随机崩、批量良率莫名下降」。
产线纪律:原则上禁止对 NAND 做整片擦除;确需擦除时,必须先逐块读出并保存坏块标记,擦除后按原标记重写回去,并在 MES 中记录该操作。
各厂不同,同一厂不同料号也可能不同。有的器件在坏块第 0 页 spare 区首字节写标记,有的在第 0、1 两页都写;有的内建坏块替换查找表(条目数有限,用满后由标志位指示),有的完全交给主机软件管理。
如果器件内建替换表,而烧录器又做了一次逻辑跳过,两套重映射会叠加,偏移算法直接错乱。遇到内建 LUT 的料,先决定「用器件内建的」还是「关掉它用软件的」,两者不能同时生效。
4. 第 2 类:ECC 与 spare 区不一致
SPI NAND 必须有 ECC,但「谁来算 ECC、算完放哪、保护多大范围」这三件事, 在烧录器、器件片上引擎、目标驱动三方之间必须完全一致。 只要有一方多算或少算一层,主区数据可以完全正确,系统却照样起不来。
4.1 第一件事:确认器件片上 ECC 的上电默认状态
这是整个第 2 类的起点,也是现场最常被跳过的一步。 多数 SPI NAND 的片内 ECC 在上电后默认为使能, 且该位多为易失位——器件复位不会把它清零,但断电后会回到默认值。 具体默认值、所在寄存器与位号一律以实际料号 datasheet 为准。
很多 SoC 用硬件 ECC,也有很多器件片内已经算了 ECC。烧录器再算一遍 ECC,反而会与它们冲突。
ECC 生成方只能有一个:器件片内引擎、SoC 硬件 ECC 引擎、软件 ECC,三选一。两套并存时,主区数据通常还是对的(所以校验通过),但 spare 里的校验字节被后写的一方覆盖,读回来校验失败,现场表现为「能读到数据但报不可纠错误」。
4.2 四种组合,只有一种是对的
把「器件片上 ECC 开关」与「烧录器是否算 ECC 并写 spare」两两组合, 得到下面四种情形。先把当前产线落在哪一格确定下来,再谈怎么改。
4.3 ECC 布局的三方对齐检查表
如果确定走「烧录器算 ECC」这条路(图 6 的 C 格),下面每一项都必须 在烧录器配置与驱动源码 / 设备树两侧取到同一个值。
| 对齐项 | 烧录器侧取值 | 驱动侧取值 | 不一致的后果 |
|---|---|---|---|
| 纠错算法 | BCH / Hamming(按驱动要求选) | 驱动或 SoC 寄存器里配置的算法 | 校验字节无法被正确译码 |
| 纠错强度 | 如 4 / 8 / 24 bit(以 datasheet 与驱动为准) | 驱动实际配置的强度 | 强度低于需求则不可纠,高于需求则布局对不上 |
| ECC 块大小 | 每个校验分组覆盖的字节数 | 驱动的 step size | 分组边界错位,全部校验失败 |
| 校验字节位置 | spare 区内的起始偏移(ECCPOS) | 驱动硬编码的 OOB 布局 | 差一个字节就全崩 |
| 校验字节长度 | 每个分组对应的字节数(ECCBYTES) | 驱动的 ecc bytes | 越界覆盖坏块标记或元数据 |
| 受保护范围 | 仅主区 / 主区 + 部分元数据 | 驱动声明的保护范围 | 元数据被当作无保护数据处理 |
4.4 spare 区布局示例与差异警告
下表是一种常见的 2K + 128B 页结构的划分示例, 用于说明「spare 区不是一整块空地,而是被切成若干用途不同的小段」这一事实。 不同厂家的划分差别很大,必须按实际料号 datasheet 复核。
| 页内地址(示例) | 区域 | 受片内 ECC 保护 | 说明 |
|---|---|---|---|
| 0x000 ~ 0x7FF | Main 0~3 用户数据(4 × 512B) | 是 | 用户数据主体 |
| 0x800 ~ 0x803 | Spare 0 首字节 | 否 | 出厂坏块标记;须在片内 ECC 关闭时读取才可靠 |
| 0x804 ~ 0x83F | Spare 元数据区 | 各厂不同 | 部分厂按 4B + 12B 再分组,受保护性不同 |
| 0x840 ~ 0x87F | ECC 校验区(4 × 16B) | 是 | 片内 ECC 开启时由器件自写,主机写入被忽略 |
同样是 2K + 128B 的页,不同厂家的 spare 区划分并不一致:有的把未受保护区域划为 4 字节,有的划到 32 字节;有的把 ECC 校验区放在 spare 区末尾,有的放在中间。尤其要注意「未受保护区域的起始地址与长度」——它直接决定坏块标记会不会被 ECC 覆盖。
现场做法:以实际料号 datasheet 的 spare / ECC 布局表为准,并把该表截图贴进烧录 SOP,避免二次查错。
4.5 空白页与页内重复编程(over program)陷阱
这是片内 ECC 开启时特有的坑,也是「校验通过但运行期出错」的高频根因:
- 片内 ECC 引擎一次对整页(或整个 ECC 分组)算出校验字节并写进 spare 区。
- 如果烧录器把一页「全 FFh 的空白页」也照烧一遍,器件照样会为它算出校验字节并写入 spare—— 于是这一页在物理上就不再是「已擦除」状态。
- 文件系统按「全 FFh = 已擦除」的约定,会直接往这一页写数据而不先擦除, 造成页内重复编程,写入的校验字节与数据不再匹配。
- 由于存储单元只能从 1 写成 0、不能从 0 写回 1,第二次写入的校验字节无法覆盖第一次的, 结果是数据错乱。
对策一:烧录前判断整页是否全为 FFh,是则跳过该页不编程。
对策二:确认片内 ECC 可关闭时,先把 ECC 使能位关掉再烧,烧完再恢复。
两条都不做的话,「空白页照烧」几乎必然在运行期暴露为文件系统校验错误。
4.6 现场核对动作
- 查器件默认值:打开料号 datasheet,确认片内 ECC 使能位的上电默认值、寄存器地址、位号、是否易失。
- 查驱动实际值:在目标板上用下面的命令读回驱动真正在用的 ECC 参数, 不要相信配置项的名字,要看运行时值。
- 查烧录器配置:确认烧录器是否勾选了「计算并写入 ECC」、 「跳过空白页」、「包含 spare 区」等选项。
- 三方对齐后再烧:只保留一个 ECC 生成方。若器件片内 ECC 已开, 就把烧录器的 ECC 关掉;反之亦然。
- 装板实跑:跑通启动 + 读写文件 + 断电重启,确认文件系统可正常挂载与写入。
核对驱动侧 ECC 参数的常用命令
# 在目标板上确认驱动实际使用的 ECC 参数(路径随内核版本而变,以实际平台为准)
cat /proc/mtd
cat /sys/class/mtd/mtd0/ecc_strength # 纠错强度(bit)
cat /sys/class/mtd/mtd0/ecc_step_size # 每个 ECC 块的大小(Byte)
cat /sys/class/mtd/mtd0/oobsize # spare / OOB 区大小(Byte)
cat /sys/class/mtd/mtd0/bad_blocks # 驱动认定的坏块数
# U-Boot 下查看识别到的器件与分区
nand info
mtd list
mtdparts
# 内核启动日志里抓 ECC / 坏块相关行(这是最快的一步)
dmesg | grep -iE "nand|ecc|spinand|ubi|bad"
如果主区数据逐字节全对,但系统报 ECC 错误,那么 90%% 以上落在第 2 类:把 spare 区单独 dump 出来,对照「坏块标记位置 / 元数据区 / ECC 校验区」三段逐一比对,差异点即为根因。