只读分享解决方案文档原理方案设计📅 2026-10-14🔖 magic⏳ 还有3日到期
🔧解决方案&应用市场分析 -> 原理方案设计 -> 嵌入式驱动&系统开发 -> Linux_SPI_NAND_驱动开发指南

Linux_SPI_NAND_驱动开发指南

同系列 · 原理方案设计
扫码打开本页
微信扫码

Linux 系统(SPI NAND)驱动开发指南

编制日期 2026-10-14

面向 XTX XT26G01D / XT26G11D 等串行 SLC NAND 器件,覆盖 开发步骤 → 内核配置 → 驱动架构 → 设备树与分区 → 新器件适配 → 测试 → 调试 → 性能优化 → 可靠性 → 量产运维 的完整链路;每一章都给出可直接复制的 config 片段 / DTS 片段 / 命令行 / 判据, 并明确区分「测试(验合格)」与「调试(找 bug)」两条线。

文档编号LINUX-SPINAND-DRV-001
版本 / 状态V1.0 · 正式发布
适用器件XTX XT26G01D / XT26G11D / XT26Q01D / XT26G02D / XT26G04D 等同系列 1Gb~4Gb SPI NAND(On-Die ECC 8bit/512B);文中方法同样适用于 Winbond / GD / MXIC / Micron / Toshiba 等品牌器件
内核版本主线 Linux 5.10 / 5.15 / 6.1 / 6.6 为主(drivers/mtd/nand/spi/ 原生 spinand 框架);4.19~5.4 的差异见 0.3 节
硬件平台ARM/ARM64 SoC(SPI 控制器走 spi-mem 框架)+ 板载 SPI NAND;示例以 3.3V 1Gb 器件、104MHz Quad 模式为基准
配套文档《SPI_NAND_Flash_电路设计指南.html》(硬件 / 原理图 / 布线)、《SPI_NAND_Flash_软件设计规范.html》(命令 / 寄存器 / 坏块 / ECC 协议层)

0.1 编写目的与读者对象

SPI NAND 在 Linux 下几乎不需要从零写驱动——内核主线已有完整的 spinand 框架与厂商器件表。真正的工作量集中在:选对内核配置、写对设备树、适配新器件、 把 UBI/UBIFS 跑通、以及量产阶段的可靠性与运维。本文把这些工作按可执行的顺序固化下来。

  • 驱动工程师:按第 3~7 章完成配置、DTS、器件适配与驱动改造;按第 10 章定位问题。
  • 硬件工程师:重点读第 2 章(原理图 / 电源 / 测试点 / BOM 约束)与第 5 章(reset-gpio、pinctrl、供电绑定)。
  • 测试工程师:按第 9 章执行功能 / 压力 / 性能 / 回归四项测试,按第 17 章核查清单验收。
  • 量产与运维:按第 6 章做产线烧录,按第 15 章做日志采集、坏块监控与 A/B 分区 OTA。
不在本文范围:裸机 / RTOS 下的 SPI NAND 驱动实现(无 MTD 子系统); UBI/UBIFS 内部算法的实现细节(磨损均衡、垃圾回收的数学建模);SPI NOR / eMMC 的驱动开发细节(仅在附录 E 做选型对照)。
本文贯穿的三条铁律

① SPI NAND 不是 SPI NOR:不能 XIP、不能直接挂 ext4、必须走 UBI/UBIFS(或只读 SquashFS + 可写 UBIFS 叠加)。
② 分区表必须在 U-Boot、设备树、内核命令行三处保持一致——这是量产阶段最高频的故障源(见 5.3)。
③ On-Die ECC 由器件内部完成,主机只负责读状态位判级:正确可纠要上报 bitflip 计数,不可纠必须返回 -EBADMSG,不能静默返回数据(见 12.1)。

结论:SPI NAND 在 Linux 里的「驱动开发」,本质是 把器件能力(ID / 页大小 / OOB 布局 / ECC 强度 / 命令变体)正确喂给 spinand 框架, 再把 MTD → UBI → UBIFS 这条链路打通;剩下的 80% 工作量在设备树、分区表、bootargs、 量产烧录与可靠性验证上,而不是在 drivers/mtd/nand/spi/ 里写新代码。

0.2 术语与缩略语

下表是本文(以及内核 MTD 邮件列表、datasheet)里高频出现的词。驱动调试时大量日志来自这些子系统, 先建立词与层级的对应关系,能省掉一半读日志的时间。

缩写 / 术语中文所在层级含义与要点
MTD存储技术设备内核 subsystemLinux 对裸 Flash 的统一抽象层,向上提供字符设备 /dev/mtdX 与只读块设备 /dev/mtdblockX,向下接 raw nand / spinand / nor。
spi-memSPI 存储器抽象drivers/spi把 Flash 操作抽象成「命令-地址-dummy-数据」四段式 spi_mem_op,屏蔽单/双/四线差异。spinand 框架依赖它。
spinandSPI NAND 框架drivers/mtd/nand/spi内核原生 SPI NAND 驱动框架,模块名 spinand,配置开关 CONFIG_MTD_SPI_NAND。
UBI无序块镜像层drivers/mtd/ubi在 MTD 之上做卷管理、坏块管理、磨损均衡。把 PEB(物理擦除块)映射成 LEB(逻辑擦除块)。
UBIFSUBI 文件系统fs/ubifs跑在 UBI 卷之上的日志型 Flash 文件系统,支持压缩、掉电安全。
PEB / LEB物理 / 逻辑擦除块UBIPEB 即一个 NAND block(如 128KB);LEB = PEB − EC/VID 头开销(如 126KB)。
OOB / spare备用区NAND 物理层每页附带的额外字节(如 2048B 页配 64B/128B),放 ECC 校验值与坏块标记。
ECC纠错码物理层 / 控制器纠正位翻转。SPI NAND 通常是 On-Die ECC(片内完成),主机读 status 判级。
bitflip位翻转物理层读回数据与写入值不一致的 bit 数。可纠范围内由 ECC 修正,驱动需向上报 max_bitflips。
On-Die ECC片内 ECC器件内部ECC 计算与纠错在 Flash 芯片内部完成,主机不生成校验码,只读 ECC 状态位。
BBT / BBM坏块表 / 坏块标记NAND core / UBI出厂坏块由厂商标记在 OOB;运行时新增坏块由 UBI 或驱动标记。
FTL闪存转换层中间层把块设备语义翻译成 Flash 语义。UBI 不是完整 FTL(不做地址映射表持久化),这是它和 eMMC 的根本差别。
DTS / DTB设备树源 / 二进制boot 阶段描述硬件连接。编译产物 .dtb 由 bootloader 传给内核。
probe驱动探测驱动模型设备与驱动 matching 成功后调用的初始化入口,SPI NAND 是 spinand_probe()。
compatible兼容属性设备树驱动与设备匹配的键。SPI NAND 通用值是 spi-nand,某些厂商再加具体型号。
pinctrl引脚复用控制设备树 / 驱动配置 SPI 引脚功能与电气特性,错误的 pinctrl 会表现为「SPI 读全 FF」。
Fastmap快速映射UBI把 UBI 的 EC/VID 头扫描结果持久化到固定 PEB,大幅缩短 attach 时间(CONFIG_MTD_UBI_FASTMAP)。
GC垃圾回收UBIFS回收含大量无效页的 LEB。GC 压力大时写性能骤降,是性能调优重点。
Wear Leveling磨损均衡UBI把擦写分散到不同 PEB,避免局部过早失效。
XIP片内执行—SPI NAND 不支持(无线性地址映射),这是与 SPI NOR 的关键差异。
mtdparts命令行分区内核 cmdline用内核命令行指定分区表的老方式,与设备树 fixed-partitions 二选一。
ubinizeUBI 镜像打包主机工具把若干 UBIFS 卷镜像打包成一个可直接烧进裸 Flash 的 UBI 镜像(含 EC/VID 头)。
最容易混淆的三组概念

① MTD 分区 ≠ UBI 卷:分区是 Flash 上的一段地址范围(/dev/mtdX), 卷是 UBI 在一个分区内部再划分出来的逻辑卷(ubi0:rootfs)。
② /dev/mtdX ≠ /dev/mtdblockX:前者是字符设备(正确用法), 后者是无坏块管理、无磨损均衡的「假块设备」,禁止在其上挂 ext4/vfat(见 8.1)。
③ On-Die ECC 状态位 ≠ 驱动自己算 ECC:片内 ECC 器件只需实现 ecc_get_status(),不要再用 nand_ecc_ctrl 那套软件 ECC 流程。

0.3 内核版本与 MTD 接口差异

MTD 子系统在 4.x → 5.x → 6.x 之间做过多次重构,照抄旧教程会直接编译不过或行为异常。 下表按版本给出关键差异,适配前先确认自己的内核基线。

内核版本关键状态对 SPI NAND 驱动的影响
4.19 ~ 5.4spinand 框架已合入(CONFIG_MTD_SPI_NAND),但器件表较薄只有 gigadevice / macronix / micron / toshiba / winbond 几家,缺 XTX、ESMT、ATO、Alliance 等;OOB layout 与 ECC 判级接口与新版不同。老项目多为这一档,新器件需要自己 backport。
5.10 ~ 5.15器件表大幅扩充,OOB layout 接口稳定struct mtd_ooblayout_ops 的 .ecc/.free 语义定型;SPINAND_OP_VARIANTS 宏成为标准写法。量产项目推荐基线。
6.1 ~ 6.6nand_ecc_engine 抽象引入,xtx.c 等新厂商合入ECC 由 struct nand_ecc_engine 统一管理,spinand_eccinfo 的部分字段被替换;新增 on-die ECC 的 NAND_ECC_ENGINE_TYPE_ON_DIE。从 5.x backport patch 时需改 ECC 相关字段。
6.7+spinand 支持 Octal DTR、Continuous Read,厂商表继续扩充高性能器件的 8 线 / DTR 模式需要 spi-mem 控制器侧同步支持;老 SoC 控制器不支持时会自动降级到低性能变体。
4.19/5.4 老内核的三个坑

① spinand_eccinfo 字段名与新版不同,直接套用 6.x 的 patch 会编译失败;
② 老内核的 spinand_read_page() 不检查 ECCS 返回值,读坏块会静默返回坏数据;
③ 老内核(4.19/5.4)默认未开 FASTMAP——该选项自 3.7 起即存在,但早期默认关闭且偏实验性,ubiattach 会全片扫描、attach 耗时长达数十秒,容易被误判为「卡死」。务必打开 CONFIG_MTD_UBI_FASTMAP 并设 ubi.fm_autoconvert=1。

结论:能升级就升级到 5.10+ / 6.1+;无法升级时,务必把 ECC 判级与 OOB layout 两处按目标内核源码核对后再改。

0.4 硬件平台与参考器件

本文以「ARM SoC + 板载 SPI NAND」为基准平台。驱动视角下,硬件只需要落实三件事: SPI 控制器接在哪个片选、器件供电域是多少、RESET#/WP# 是否可控。

参考硬件平台:SoC + SPI NAND(以 XT26G01D / XT26G11D 为例)SoC / MCUSPI 控制器 (spi_master)DMA / FIFO / CSpinctrlSCLK / MOSI / MISO / CS#GPIORESET# / WP#(可选)VCC_IO 1.8V / 3.3V 域SPI NAND FlashXT26G01D (1Gb, 3.3V)2048B page / 128KB blockOn-Die ECC8 bit / 512BFeature 寄存器A0h/B0h/C0h/D0hVCC 去耦 100nF + 10µFSPISCLKMOSIMISOCS#RESET# / WP#① 驱动只关心「SPI 控制器能否正确发出 spi-mem 操作」与「器件 ID / OOB / ECC 能力是否匹配」两件事。② RESET#/WP# 是否接 GPIO,决定设备树里要不要写 reset-gpios,也决定驱动能否做硬件复位与写保护。③ 硬件框图必须与设备树的 reg(片选号)、spi-max-frequency、供电域三者一致,否则 probe 阶段就会失败。
图 1 · 硬件平台与 SPI NAND 连接框图
项目示例值驱动侧对应配置
SoC SPI 控制器spi0(支持 spi-mem、DMA)设备树 &spi0 { ... } 下挂 flash@0
片选 / regCS#0reg = <0>; —— 写错片选号是最常见的 probe 失败原因
器件XTX XT26G01D(1Gb, 3.3V)compatible = "spi-nand",ID 由 xtx.c 匹配(MFR ID 0x0B)
页 / 块 / OOB2048B / 128KB / 64B 或 128B由驱动从 ID 表读出,设备树不应硬编码几何参数
ECC片内 8bit / 512Bxt26xxxd_ecc_get_status() 判级,CPU 不参与 ECC 计算
RESET# / WP#接 GPIO(可选)reset-gpios;WP# 若接 GPIO 可配合块保护寄存器使用
SPI 模式Mode 0 / Mode 3(以 datasheet 为准)spi-cpol / spi-cpha,一般默认 Mode 0 即可
最高频率104MHz(Quad)spi-max-frequency = <104000000>;,超频会表现为偶发 ECC 错误

1 · 前置知识依赖

动手改驱动之前,先确认四类前置知识齐备:内核驱动模型、Flash 物理原理、交叉编译环境、调试环境。 这四项任何一项缺失,后面每调试一个问题都会多花一倍时间。

1.1 Linux 内核驱动基础

SPI NAND 驱动落在「SPI 总线 + MTD 子系统」的交叉点上,需要理解的概念可以收敛到下面几条:

概念在 SPI NAND 场景下的具体表现
设备树(DTS)描述「SPI 控制器上挂了一个 spi-nand,片选 0,最高 104MHz」。内核启动时把节点转成 spi_device。
SPI 总线模型spi_master / spi_controller(控制器侧驱动)与 spi_device(器件侧)通过 compatible 匹配到 spinand 驱动。
spi-mem 层把「读页 = 13h 命令 + 24bit 地址 + 读 cache = 03h/0Bh/6Bh/EBh」这类序列抽象成 spi_mem_op,控制器侧只要会执行 op 即可。
platform 驱动模型probe() / remove() 生命周期、-EPROBE_DEFER(pinctrl/供电未就绪时延迟探测)、devm 资源管理。
字符设备 / MTDMTD 向用户态暴露 /dev/mtdX(字符设备)与 /dev/mtdblockX(只读兼容块设备),并注册 sysfs 属性。
并发与锁spinand 框架内部有互斥;用户态对同一 MTD 分区并发擦写仍需要上层串行化,不要并发 flash_erase。

1.2 Flash 基础原理

SPI NAND 保留了 NAND 的全部物理约束,只是把并行总线换成了 SPI。下面这些概念在后面每一章都会反复出现:

概念典型值(1Gb SLC)驱动 / 文件系统侧的含义
Page(页)2048 B(+64/128 B OOB)最小读写单位。写必须整页(或按 ECC 扇区)对齐编程,不支持字节改写。
Block(块)128 KB(64 页)最小擦除单位。改一个字节也要整块擦除,这是所有 Flash 文件系统的设计起点。
Plane / LUN / Die1~2 plane影响多平面并发操作与 cache 读命令选择。
坏块(Bad Block)出厂 ≤ 2%,运行时新增出厂坏块标记在 OOB 第 0 字节(非 0xFF);UBI 负责替换与屏蔽。
位翻转(bitflip)随 P/E 次数与读干扰增加ECC 可纠则返回修正后的数据并上报 bitflip 数;不可纠必须报 -EBADMSG。
ECC片内 8 bit / 512 B每 512 B 一个 ECC 扇区,一页 4 个扇区。ECC 校验值写在 OOB 里,由器件内部使用。
P/E CycleSLC 约 5 万~10 万次决定寿命。UBI 的磨损均衡目标就是让各 PEB 的 EC 值尽量接近。
Wear LevelingUBI 静态 + 动态把冷数据搬走,避免静态数据所在的块从不参与均衡。
Read Disturb同一块读次数过多反复读同一块会影响邻近页,需要在驱动或上层做读计数与刷新。
Program / Erase 时间tPROG ~ 数百 µs ~ 数 ms;tBERS ~ 数 ms不能用固定延时,必须轮询 OIP 状态位,超时门限取 datasheet max。
SPI NAND 与 SPI NOR 的三个根本差异(决定软件架构)

① 不能 XIP:没有线性地址映射,代码必须在 RAM 里执行,所以 bootloader 要先把 SPL/U-Boot 读进 RAM。
② 有坏块、有 OOB、有 P/E 寿命:必须引入 UBI 做坏块管理与磨损均衡,不能像 NOR 一样直接挂 squashfs 了事(除非只读分区)。
③ 写前必须擦:NOR 可以字节改写(写 1→0),NAND 不行。造成 UBIFS 是日志型文件系统,写放大与 GC 成为性能主矛盾。

1.3 交叉编译与内核构建环境

下图给出一条标准的「源码 → 镜像 → 部署」流水线。关键是四条产物各自独立: 内核镜像、设备树 blob、内核模块、rootfs 镜像,任何一条没更新都会表现为「改了没生效」。

从源码到可启动镜像:四条产物必须分别更新与部署主机 Hostx86_64 Linux交叉工具链aarch64-linux-gnu-内核源码树make ARCH=arm64SDKBuildroot / Yoctomake 四条产物(分别部署,缺一不可)Image / uImage内核本体*.dtb设备树 blob*.ko内核模块rootfs.ubi根文件系统镜像部署到目标板(四条路径)TFTPU-Boot tftpbootNFS rootfs调试阶段免烧录U-Boot 烧录mtd / ubi write编程器 / 产线首次烧片经验:调试阶段用 TFTP 拉内核 + NFS 挂 rootfs,烧录验证再走 Flash,迭代周期可压到秒级。
图 2 · 交叉编译与构建流水线:四条产物分别部署

最小可用构建命令

# 1) 内核(以 arm64 为例;ARCH/CROSS_COMPILE 按平台替换)
export ARCH=arm64
export CROSS_COMPILE=aarch64-linux-gnu-
make <soc>_defconfig
make menuconfig          # 按第 3 章勾选 MTD / SPI_NAND / UBI / UBIFS
make -j$(nproc) Image dtbs modules
make modules_install INSTALL_MOD_PATH=./_install

# 2) 设备树单独编译(改 DTS 后只需重编 dtb,秒级)
make dtbs
# 或单文件反编译排查:dtc -I dtb -O dts board.dtb -o board.dts

# 3) 检查设备树语法
dtc -I dts -O dtb -o /dev/null arch/arm64/boot/dts/<vendor>/board.dts
改 DTS 后最常见的「改了没生效」

① 只 make Image 没 make dtbs;
② dtb 没拷到 TFTP 目录 / 没烧进 Flash;
③ U-Boot 里 fdt_addr_r 指向的还是旧 dtb,或者用了 bootm 但没传 dtb 参数;
④ 改的是 dtsi 但板级 dts 又覆盖了一次同名节点(/delete-node/ 或重复定义)。

判据:启动后 cat /proc/device-tree/... 或 ls /sys/firmware/devicetree/base/ 复核实际生效的树。

1.4 开发环境:SDK 构建与 TFTP / NFS 调试环境

SPI NAND 项目通常是整机构建(Bootloader + Kernel + Rootfs),推荐先用 SDK(Buildroot / Yocto) 统一产出,再用 TFTP/NFS 做快速迭代。

环境组件作用配置要点
Buildroot轻量 rootfs + 工具链BR2_PACKAGE_MTD_UTILS(含 mtd-utils)、BR2_PACKAGE_UBIFS_UTILS;target 上要有 ubiattach/ubimkvol/ubiformat/flash_erase/nanddump/nandwrite。
Yocto / OpenEmbedded量产级发行版IMAGE_INSTALL:append = " mtd-utils";内核配置走 kernel-meta 的 .cfg 片段,避免每次 menuconfig。
TFTPU-Boot 拉内核 / dtb主机装 tftpd-hpa,U-Boot 侧设 serverip 与 tftpboot ${kernel_addr_r} Image。
NFS rootfs免烧录调 rootfs内核需开 CONFIG_ROOT_NFS 与网卡驱动;bootargs 用 root=/dev/nfs nfsroot=...。注意 NAND 场景下 NFS 调试不能验证 Flash 写入行为,仅用于应用层调试。
串口日志唯一的救砖手段内核 cmdline 带 console=ttyS0,115200 earlycon;早期死机只能靠串口或 earlycon 输出判断。
JTAG / SWD无串口时兜底用于确认 SoC 是否跑飞、BootROM 是否加载到 SPL。
推荐的最小调试闭环

串口(看 dmesg)+ TFTP(换内核)+ NFS(换 rootfs)+ mtd-utils(验证 Flash) 四件套搭好之后, 一次「改 DTS → 重启 → 看 probe 日志」的循环可以压到 30 秒以内。项目初期先花半天搭好这套环境,后面能省下数天。

2 · 硬件设计规范(驱动视角)

驱动工程师不需要画原理图,但必须能读懂原理图并指出哪些地方会让驱动跑不起来。 本章只讲与驱动行为直接相关的硬件约定;器件级电气参数与封装以《SPI NAND Flash 电路设计指南》与 datasheet 为准。

2.1 原理图设计要点

信号 / 项目设计要求驱动侧症状(设计错时的表现)
SCLK尽量短、少过孔;与其他高速信号保持间距;串联阻尼电阻(典型 22~33Ω)靠近 SoC 端高频下读回数据错位、ECC 报错;降低 spi-max-frequency 后恢复正常 → 基本可判定 SI 问题。
MOSI / MISO与 SCLK 大致等长;避免形成长分支(Stub)特定命令(如 Quad 读 6Bh/EBh)返回全 FF 或全 00。
CS#每器件独占;不建议硬件上下拉复用多器件时 ID 读错,或 probe 到「不存在的器件」。
上拉 / 下拉CS# 上拉(避免上电期间误选中);MISO 视控制器要求;WP# / HOLD# 不可浮空上电偶发读错;WP# 浮空导致写入被随机禁止或允许。
RESET#建议接 GPIO(或硬件 RC 复位);无 GPIO 时至少保证上电复位可靠掉电/热复位后器件状态机卡死,驱动只能等超时。
WP#接 GPIO 或固定上拉(不使用写保护时)写保护位生效时写操作被静默丢弃,flash_erase 报 EIO。
HOLD# / DQ3Quad 模式下该脚复用为 DQ3,必须按 datasheet 处理(通常上拉)Quad 模式失效、退回单线,吞吐掉到 1/4。
阻抗与等长SPI 四线组内等长(误差按平台规范,通常 ≤ 数 mm~cm 量级)高频读写偶发失败,低温/高温下更明显。

2.2 电源设计

项目要求说明
供电域VCC 与 VCC_IO 按器件规格(1.8V 或 3.3V)1.8V 与 3.3V 器件混用是最低级的硬件事故:能读到 ID 但读写大量报错,甚至永久损坏器件。
去耦电容VCC 引脚就近 100 nF + 1~10 µF(按 datasheet)编程/擦除瞬间电流尖峰大,去耦不足会造成瞬时跌落 → 表现为偶发 P_FAIL / E_FAIL。
电源轨与 SoC SPI 控制器 IO 电压匹配,必要时加电平转换电平不匹配会导致读回数据恒为 0xFF 或 0x00。
浪涌 / ESD接口侧 ESD 保护;热插拔场景加缓启动ESD 损伤往往表现为「能读 ID 但某几块永远写不进」。
上电时序VCC 稳定后再释放 RESET#;满足 datasheet 的 tPU / tRST复位未释放时访问器件 → 全 FF 或总线挂死。
掉电检测关键应用建议加掉电检测电路 + 足够储能配合 12.4 节的上层 sync / 双备份策略才能真正保证数据完整。

2.3 硬件信号测试标准

测试项判据(示例,以 datasheet 为准)工具与做法
电源纹波VCC 峰峰值在器件容限内(典型 ≤ 5% VCC)示波器 20MHz 带宽限制,探头地线尽量短,测试点见 2.4。
SPI 信号完整性SCLK 单调、无过冲超限;数据建立/保持时间满足器件 tSU/tHD示波器看 SCLK/MOSI 眼图;高频(> 50MHz)必须实测。
复位时序RESET# 低电平宽度与释放后的 tRST 满足规格逻辑分析仪抓 RESET# 与第一次读 ID 命令的间隔。
CS# 时序满足 tCSH / tCSS(片选建立保持)逻辑分析仪;CS# 提前撤销是高频下最常见的控制器侧 bug。
Quad 模式QE 位使能后,6Bh/EBh 命令确实走四线逻辑分析仪解码;若仍为单线,检查 QE 位与 op variant 选择。
读写耗时实测tPROG / tBERS 与 datasheet 一致示波器抓 CS# 拉低持续时间,与驱动轮询耗时对照,可判断轮询是否正常。

2.4 必留测试点

板子回来看不到信号就没法联调。下面这张图是硬件必须提供的最小测试点集合, 新板评审时按图核对。

驱动联调必须能抓到的物理点(硬件工程师按此留测试点)SPI NANDSOP8 / WSON8VCCCS#SCLKMOSIMISOGNDSoCSPI 控制器1.8/3.3V IOSPI 四线测试点定义(建议丝印编号)TP1 · VCCTP2 · CS#TP3 · SCLKTP4 · MOSITP5 · MISOTP6 · GND① 四线 SPI 各留一个测试点(焊盘或过孔),逻辑分析仪才能把驱动命令序列与物理时序对照起来。② VCC 测试点靠近器件引脚,并单独引一颗 GND 针做参考地;长地线夹测出来的纹波没有参考价值。③ RESET# / WP# / HOLD# 若接 GPIO 同样留测试点,用于验证复位与写保护时序。
图 3 · SPI NAND 关键信号与必留测试点

2.5 BOM 选型约束

不同品牌、不同系列的 SPI NAND 在页大小、OOB 大小、ECC 强度、命令集、QE 位位置上并不一致。 驱动侧的器件表就是为这些差异准备的——选型阶段就要把能力表定下来。

品牌MFR ID(常见值)典型差异点适配注意
XTX(芯天下)0x0BXT26G01D/G11D(1Gb)、G02D/G12D(2Gb)、G04D(4Gb);On-Die ECC 8bit/512B主线 xtx.c 已支持(6.1+ 系列);老内核需 backport,注意 xt26xxxd_ooblayout 与 xt26g0xa 是两套不同布局。
Winbond0xEFW25N01GV / W25N02KV 等;部分型号支持 Continuous Read主线 winbond.c;注意 BUF 位(B0h)与 Continuous Read 的组合。
GigaDevice(兆易)0xC8GD5FxGQ4xA / GD5FxGQ5x;ECC 状态位编码与 XTX 不同主线 gigadevice.c;ECCS 编码各家不通用,必须按 datasheet 实现 ecc_get_status()。
Macronix(旺宏)0xC2MX35LF 系列;OOB 布局与 ECC 状态位自成体系主线 macronix.c;注意八线 / DTR 变体需要控制器支持。
Micron0x2CMT29F 系列;最早的参考实现(spinand 框架即源自 Micron 提交)主线 micron.c;老器件 ECC 状态位在 C0h 的 bit[5:4]。
Toshiba / Kioxia0x98TC58CVG 系列;部分容量页大小为 4KB主线 toshiba.c;4KB 页器件要确认 UBI 的 subpage 与 VID 偏移。
ESMT / ATO / Alliance / Paragon / Foresee各异多为兼容型号,参数接近主流品牌主线已分别提供 esmt.c / ato.c / alliancememory.c / paragon.c / foresee.c(版本不同略有差异)。
选型阶段必须锁定的 6 个参数

① 页大小(2KB / 4KB) ② OOB 大小(64B / 128B / 256B) ③ 块大小与总块数
④ ECC 强度与扇区粒度(如 8bit/512B) ⑤ 是否 On-Die ECC、能否关闭 ⑥ 命令集(是否支持 Quad / QPI / DTR / Continuous Read)

这 6 项直接决定第 7 章适配时 SPINAND_INFO() 怎么填。缺任何一项都要先找原厂要全量 datasheet,不要靠「兼容替代」的口头承诺。

3 · 内核配置与编译构建

SPI NAND 在内核里是一个「开开关」的问题:把 MTD、spinand、UBI、UBIFS 四项开对, 器件就能被识别。本章给出完整可勾选的 config 清单、内置/模块两种编译方式,以及 U-Boot 侧配套配置。

3.1 内核 config 配置项清单

SPI NAND + UBI + UBIFS 的四组配置项(缩进 = 被上层自动 select)① SPI 总线侧CONFIG_SPICONFIG_SPI_MASTERCONFIG_SPI_MEM(自动 select)CONFIG_SPI_xxx(SoC 控制器)② MTD + SPI NANDCONFIG_MTDCONFIG_MTD_SPI_NAND(=y 或 =m)CONFIG_MTD_NAND_CORE(自动)CONFIG_MTD_OF_PARTS(设备树分区)③ UBI + UBIFSCONFIG_MTD_UBICONFIG_MTD_UBI_FASTMAP(强烈建议)CONFIG_UBIFS_FSCONFIG_UBIFS_FS_ZSTD(可选)④ 调试开关CONFIG_DYNAMIC_DEBUG(必备)CONFIG_DEBUG_FSCONFIG_MTD_TESTS(自测模块)CONFIG_MTD_OOPS(panic 落盘)① 真正的必勾项只有 5 个:SPI 控制器 + MTD_SPI_NAND + UBI + FASTMAP + UBIFS。② CONFIG_MTD_BLOCK 只在只读场景开启,不能拿 mtdblock 当可写块设备(见 8.1)。③ CONFIG_DYNAMIC_DEBUG 不开就没有 dynamic_debug/control,调试时只能重新编译内核。
图 4 · SPI NAND + UBI + UBIFS 内核配置四组清单

必选项(缺一不可)

# —— SPI 侧 ——
CONFIG_SPI=y                       # SPI 总线核心
CONFIG_SPI_MASTER=y                # (部分内核版本中与 SPI 合并)
CONFIG_SPI_MEM=y                   # spi-mem 抽象层(通常被自动 select)
CONFIG_SPI_<YOUR_SOC>=y            # 具体 SoC 的 SPI 控制器驱动(如 CONFIG_SPI_PL022 / CONFIG_SPI_SUNXI 等)

# —— MTD / SPI NAND ——
CONFIG_MTD=y
CONFIG_MTD_SPI_NAND=y              # 或 =m,产出 spinand.ko
CONFIG_MTD_NAND_CORE=y             # 被 MTD_SPI_NAND 自动 select
CONFIG_MTD_OOPS=y                  # 可选:panic 时把日志写进 Flash,对量产排查很有用

# —— UBI / UBIFS ——
CONFIG_MTD_UBI=y
CONFIG_MTD_UBI_FASTMAP=y           # 强烈建议:attach 时间从数十秒降到秒级
CONFIG_MTD_UBI_GLUEBI=m            # 可选:把 UBI 卷再暴露成 MTD(很少用,按需)
CONFIG_UBIFS_FS=y
CONFIG_UBIFS_FS_ZSTD=y             # 可选:压缩算法(另有 LZO / ZLIB)

# —— 分区表 ——
CONFIG_MTD_OF_PARTS=y              # 设备树 fixed-partitions(推荐)
# CONFIG_MTD_CMDLINE_PARTS=y       # 二选一:命令行 mtdparts(老方案)

调试用选项(问题定位时打开,量产后关闭)

CONFIG_MTD_TESTS=m                 # mtd_nandecctest / mtd_pagetest 等内核自测模块
CONFIG_MTD_UBI_DEBUG=y             # UBI 内部详细日志(部分版本提供)
CONFIG_MTD_UBI_DEBUG_MSG=y
CONFIG_MTD_UBI_DEBUG_PARANOID=y
CONFIG_UBIFS_FS_DEBUG=y            # UBIFS 断言与调试统计
CONFIG_UBIFS_FS_DEBUG_CHKS=y
CONFIG_DYNAMIC_DEBUG=y             # 【关键】dynamic_debug 的前提,见 10.1
CONFIG_DEBUG_FS=y                  # debugfs,ubi/ubifs 的统计都在下面
CONFIG_MAGIC_SYSRQ=y               # 掉电测试时手动触发 sync / 重启
两个最容易漏开、且症状极具迷惑性的选项

① CONFIG_MTD_UBI_FASTMAP:不开的话大容量器件 attach 会全片扫描, 表现为「开机卡在 ubi attach 十几秒」,常被误判为驱动死锁。
② CONFIG_DYNAMIC_DEBUG:不开的话 dynamic_debug/control 不存在, 调试时想开 spinand / ubi 的逐条日志无从下手,只能重新编译内核。

3.2 内置进内核 vs 编译成 ko 模块

方式配置与部署适用场景注意事项
内置(=y)编进 Image;无需 insmod放 rootfs 的那个器件必须内置——内核启动时就要用它挂载根文件系统,模块此时还没加载。改一次要重编内核 + 重新烧录,迭代慢。
模块(=m)make modules → spinand.ko → 拷进 rootfs 后 insmod调试阶段改驱动最快:只换 ko 文件,不用动内核镜像。依赖 spi_mem / mtd 等基础模块先加载;模块版本与内核 vermagic 必须一致。
混合核心内置 + 厂商 ops 做成 ko多品牌器件共用一套镜像时,按需加载不同厂商模块。要注意模块加载顺序与 modprobe 依赖表(depmod)。
# 模块方式常用命令
make M=drivers/mtd/nand/spi modules          # 只编 spinand 模块(秒级)
adb push spinand.ko /tmp/                    # 或用 scp / NFS
insmod /lib/modules/$(uname -r)/kernel/drivers/mtd/nand/spi/spinand.ko
rmmod  spinand
modinfo spinand                              # 看依赖与参数
depmod -a                                    # 重建依赖表后可用 modprobe spinand
ko 方式的三条纪律

① 换 ko 前先 rmmod,否则加载的是内存里的旧代码,改了看不到效果;
② ko 与内核必须同一次编译产出,混用会报 版本号 magic ... should be 而拒绝加载;
③ 根文件系统所在的 MTD 器件,其驱动(含 SPI 控制器驱动)必须内置,否则会出现 VFS: Unable to mount root fs。

3.3 DTS 编译与设备树 overlay

# 全量编译设备树
make dtbs
# 单文件编译(快)
cpp -nostdinc -I include -I arch -undef -x assembler-with-cpp board.dts board.dts.pre
dtc -I dts -O dtb board.dts.pre -o board.dtb

# 反编译已生效的 dtb(排查「到底生效了哪棵树」)
dtc -I dtb -O dts /sys/firmware/fdt -o running.dts

# 运行时确认(比反编译更直接)
ls /sys/firmware/devicetree/base/
find /sys/firmware/devicetree/base -name compatible | xargs grep -l spi-nand
方案做法适用
板级 DTS / DTSI每个板型一个 board.dts,公共部分抽到 board.dtsi主流做法,量产推荐。
设备树 overlay运行时用 dtbo 叠加节点(需 CONFIG_OF_OVERLAY)同一镜像适配多种板型 / 扩展板;U-Boot 侧用 fdt apply。
U-Boot 内修改U-Boot 命令行 fdt set / fdt rm 后再 bootm调试阶段临时改参数(如降 spi-max-frequency)最快,不用重编内核。
调试期最省时间的一招

怀疑频率太高导致偶发 ECC 错误时,不要重编内核,直接在 U-Boot 里改: fdt set /soc/spi@xxx/flash@0 spi-max-frequency <0x3200000>(改为 52MHz)后启动, 看是否稳定。确认是频率问题后再回头改 DTS。

3.4 Rootfs 构建:mtd-utils 工具包

没有 mtd-utils 就没有调试手段。无论 Buildroot 还是 Yocto,这个包必须进 rootfs。

# Buildroot
make menuconfig
  Target packages  --->  Hardware handling  --->
      [*] mtd-utils           # 含 flash_erase / nanddump / nandwrite / mtd_debug
      [*]   ubi health        # ubiattach / ubimkvol / ubiformat / ubinize ...
# 或直接写 defconfig
echo 'BR2_PACKAGE_MTD_UTILS=y' >> configs/board_defconfig

# Yocto
IMAGE_INSTALL:append = " mtd-utils mtd-utils-ubifs"

# 主机侧(做镜像用,通常 apt 安装)
sudo apt install mtd-utils
工具来源包用途
flash_erase / flash_eraseallmtd-utils擦除 MTD 分区 / 整片
nanddump / nandwritemtd-utils带 OOB 的裸页读写(调试 ECC 与坏块必备)
mtd_debugmtd-utils直接 ioctl 读写 MTD(read/write/erase/info)
flashcp / ddmtd-utils / coreutils往 MTD 分区写镜像(NOR 常用,NAND 慎用)
ubiformatmtd-utils把 MTD 分区格式化为 UBI(含坏块处理)
ubiattach / ubidetachmtd-utils把 MTD 分区挂到 / 摘出 UBI
ubimkvol / ubirmvolmtd-utils创建 / 删除 UBI 卷
ubiupdatevolmtd-utils往指定卷写镜像(OTA 常用)
ubinize / mkfs.ubifsmtd-utils(主机侧)制作 UBIFS 卷镜像 / 打包 UBI 镜像
ubinfo / ubihealthdmtd-utils查看 UBI 状态 / 健康监控(版本差异较大)

3.5 U-Boot 配套配置

U-Boot 需要能读 SPI NAND,才能把内核 / dtb / UBI 镜像加载起来。U-Boot 与内核用的是同一套 spinand 代码(U-Boot 会周期性从 Linux 同步),但配置开关名字不同。

# U-Boot defconfig 必开项
CONFIG_MTD=y
CONFIG_MTD_SPI_NAND=y          # U-Boot 侧 spinand 框架
CONFIG_SPI=y
CONFIG_DM_SPI=y
CONFIG_SPI_MEM=y
CONFIG_CMD_MTD=y               # mtd list / mtd read / mtd write / mtd erase
CONFIG_CMD_UBI=y               # ubi part / ubi attach
CONFIG_CMD_UBIFS=y             # ubifsmount / ubifsload / ubifsls
CONFIG_CMD_SF=y                # (SPI NOR 用,NAND 场景可选)
CONFIG_ENV_IS_IN_UBI=y         # 环境变量存 UBI 卷(可选)
CONFIG_SPL_SPI_NAND_SUPPORT=y  # 若由 SPL 从 SPI NAND 加载 U-Boot
U-Boot 命令作用示例
mtd list列出 MTD 设备与分区mtd list
mtd read/write/erase裸读写擦(按分区名或偏移)mtd read rootfs ${loadaddr}
ubi part把某分区 attach 到 UBIubi part rootfs
ubifsmount挂载 UBIFS 卷ubifsmount ubi0:rootfs
ubifsload从 UBIFS 卷读文件ubifsload ${kernel_addr_r} /boot/Image
ubi writevol写 UBI 卷(产线/OTA)ubi writevol ${addr} rootfs ${size}
U-Boot 与内核器件表不同步 = 量产事故

U-Boot 的 drivers/mtd/nand/spi/ 是从 Linux 周期性同步的,版本落后于内核是常态。 后果是:同一颗器件,内核能识别、U-Boot 不认识(或反之),表现为「U-Boot 里 mtd 看不到设备,但 Linux 起来后一切正常」。

对策:新器件导入时,U-Boot 与内核要分别验证 ID 匹配,把两边都合上;不能只验证内核。

4 · 驱动架构与代码框架

本章回答「内核里到底有哪些代码在跑」。读完应当能定位任意一个问题落在哪一层, 以及新器件适配时要改哪个文件、填哪张表。

4.1 软件分层总览

SPI NAND 的完整链路是 UBIFS → UBI → MTD → NAND core → spinand → spi-mem → SPI 控制器 → 器件。 其中「自己写的代码」通常只占最下面两层中的厂商 ops 部分。

一次 read() 调用穿过的七层(右侧标注各层源码位置)用户态 / VFSread() / write() / ioctl()libc / busyboxUBIFS 文件系统日志、压缩、GC、掉电一致性fs/ubifs/UBI 层卷管理 / 坏块 / 磨损均衡 / LEB↔PEBdrivers/mtd/ubi/MTD 子系统统一 mtd_info 抽象、/dev/mtdX、OOBdrivers/mtd/MTD NAND corenand_device、坏块表、ECC 引擎抽象drivers/mtd/nand/core.cspi-nand 框架(spinand)命令序列、页读写、ECC 状态判级drivers/mtd/nand/spi/core.c厂商 ops(xtx / winbond / gd ...)ID 表、OOB layout、ECC 判级、QEdrivers/mtd/nand/spi/xtx.c 等spi-mem 抽象层把 Flash 操作变成 op 序列drivers/spi/spi-mem.cSPI 控制器驱动DMA / FIFO / CS 时序 / 时钟drivers/spi/spi-*.c硬件:SPI NAND 器件页 / 块 / 片内 ECC—① 真正写代码的通常只有「厂商 ops」一层;其余各层都是内核现成的。② 6.1+ 内核把 ECC 抽成 nand_ecc_engine,On-Die ECC 走 NAND_ECC_ENGINE_TYPE_ON_DIE。
图 5 · Linux SPI NAND 软件分层架构(UBIFS → UBI → MTD → spinand → spi-mem → 控制器)
一个非常有用的分层判断法

拿到任何一条异常日志,先判断它来自哪一层,问题范围立刻收敛一半:

  • spi-nand: ... → spinand 框架或厂商 ops(ID、ECC 判级、命令序列)
  • spi_master spi0: ... / spi-xxx: ... → SPI 控制器(DMA、时钟、CS)
  • ubi0: ... / UBI error → UBI 层(卷、坏块、attach、fastmap)
  • UBIFS error → 文件系统(日志、GC、压缩、掉电恢复)
  • mtd: ... → MTD 核心(分区、OOB、ioctl)

4.2 源码目录与文件职责

路径职责什么时候要动它
drivers/mtd/nand/spi/core.cspinand 框架主体:probe、读/写/擦、ECC 状态轮询、坏块判断原则上不改;只有框架级 bug 才动,且优先 upstream。
drivers/mtd/nand/spi/xtx.c
(winbond.c / gigadevice.c / macronix.c / micron.c / toshiba.c ...)
厂商器件:ID 表、OOB layout、ECC 判级、QE 使能、op 变体新增器件时主要改这里(见第 7 章)。
drivers/mtd/nand/spi/Makefile
drivers/mtd/nand/spi/Kconfig
编译进 spinand 模块新增厂商 .c 文件时改这两个。
include/linux/mtd/spinand.h框架数据结构与宏(SPINAND_ID / SPINAND_INFO / 各 OP 宏)新增厂商时加一行 extern const struct spinand_manufacturer xxx;。
drivers/mtd/nand/core.cNAND 通用层:memorg、坏块表、ECC engine 抽象一般不动。
drivers/mtd/mtdcore.c / mtdchar.cMTD 子系统:分区解析、/dev/mtdX、ioctl一般不动。
drivers/mtd/ubi/*.cUBI:attach、卷管理、磨损均衡、fastmap、坏块处理调参与排错,不改逻辑。
drivers/spi/spi-mem.c
drivers/spi/spi-<soc>.c
spi-mem 抽象与具体控制器驱动控制器不支持某 op / DMA 异常时才动。
fs/ubifs/*.cUBIFS 文件系统调挂载参数与排错,不改逻辑。

4.3 关键数据结构

下面三个(组)结构是所有适配工作的落点。字段名随内核版本有变化, 动手前请以手上源码的 include/linux/mtd/spinand.h 为准。

结构作用适配时关心的字段
struct spinand_device一个 SPI NAND 器件的软件实例slave(spi_mem 句柄)、base(nand_device)、scratchbuf(页缓存)、eccinfo(ECC 判级)、op_templates(读/写/更新 cache 的命令变体)、manufacturer
struct nand_device
(struct nand_memory_organization)
器件几何与 NAND 通用行为pagesize / oobsize / pages_per_eraseblock / eraseblocks_per_lun / planes_per_lun;由 SPINAND_INFO 的 memorg 填充,不要手写
struct mtd_infoMTD 层对上暴露的抽象size / erasesize / writesize / oobsize / type / flags / _read / _write / _erase;驱动注册后由用户态通过 /dev/mtdX 访问
struct spinand_manufacturer厂商描述:ID + 器件表 + opsid / name / chips / nchips / ops;新厂商要在 core.c 的 spinand_manufacturers[] 数组里登记
struct spinand_info(SPINAND_INFO 宏)单个器件的全部能力.model / .devid / .memorg / .op_variants / .flags / .eccinfo / .ooblayout / .select_target
struct mtd_ooblayout_opsOOB 里哪些字节归 ECC、哪些可给用户.ecc / .free;布局错了会导致 ECC 校验值被用户数据覆盖
struct nand_ecc_engine(6.x)ECC 引擎抽象On-Die ECC 对应 NAND_ECC_ENGINE_TYPE_ON_DIE;5.x 用 spinand_eccinfo 的 get_status
/* 典型厂商器件表条目(示意,字段名以实际内核为准) */
static const struct spinand_info xtx_spinand_table[] = {
    SPINAND_INFO("XT26G01D",
                 SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0xE1),
                 NAND_MEMORG(1, 2048, 128, 64, 1024, 1, 1, 1),  /* luns, pagesize, oobsize, pages_per_eraseblock(64), blocks_per_lun, planes, ... */
                 NAND_ECCREQ(8, 512),          /* On-Die ECC: 8bit / 512B */
                 SPINAND_INFO_OP_VARIANTS(&read_cache_variants,
                                          &write_cache_variants,
                                          &update_cache_variants),
                 0,
                 SPINAND_ECCINFO(&xt26xxxd_ooblayout,
                                 xt26xxxd_ecc_get_status)),
};

const struct spinand_manufacturer xtx_spinand_manufacturer = {
    .id    = SPINAND_MFR_XTX,       /* 0x0B */
    .name  = "XTX",
    .chips = xtx_spinand_table,
    .nchips = ARRAY_SIZE(xtx_spinand_table),
    .ops   = &xtx_spinand_manuf_ops,
};
两个被反复写错的地方

① OOB layout 的坏块标记区:section 0 的第 0 字节(部分规范要求 2 字节)必须保留给 BBM, .free 从 offset 1(或 2)开始,否则 UBI 会把坏块标记当成可用空间写掉。
② ECC 状态判级函数返回值:可纠应返回纠正的 bitflip 数(0 表示无翻转), 不可纠必须返回 -EBADMSG;返回 0 会让上层以为数据完好,造成静默数据损坏。

4.4 probe 执行流程

下图是 spinand_probe() 的主干。理解它之后,「设备树写了但没出 mtd 设备」这类问题可以逐步对位。

从上电到 /dev/mtdX 出现的七个阶段(任一阶段失败都会表现为「没有 mtd 设备」)① 设备树匹配compatible = spi-nand② 读 JEDEC ID9Fh + MFR/DEV ID③ 匹配器件表制造商 + 型号④ 填 memorg页 / 块 / OOB⑦ 注册 mtd_infomtd_device_register()⑥ 设置 ECC + OOBengine / layout⑤ 选 op 变体单 / 双 / 四线失败点速判:① 无 /sys/bus/spi/devices → DTS 或 pinctrl;② ID 全 FF/00 → 供电或片选;③ 器件表缺项(见第 7 章)。
图 6 · spinand probe 执行流程:从设备树匹配到 mtd_info 注册

各阶段失败时的典型日志与判据

阶段失败判据 / 日志下一步
① 设备树匹配/sys/bus/spi/devices/ 下没有对应设备;或 of_match 失败检查 compatible 拼写、节点是否在 &spi0 下、pinctrl 是否生效、SPI 控制器驱动是否加载
② 读 IDunknown raw ID / ID 为 000000 或 ffffff全 FF → 供电/片选/接线;全 00 → MISO 未连通或模式错;有值但不认识 → 器件表缺项
③ 匹配器件表spinand: unknown raw ID ...按第 7 章加 ID 表条目
④ 填 memorg启动后 /proc/mtd 的 erasesize / writesize 与器件不符核对 NAND_MEMORG() 参数与 datasheet
⑤ 选 op 变体Quad 读返回错误数据,单线正常检查 QE 位使能流程与 op variants 顺序
⑥ ECC / OOB读写报 -EBADMSG 或 bitflip 异常升高核对 ECC 判级函数与 OOB layout
⑦ 注册 MTD/proc/mtd 为空看 mtd_device_register() 返回值;分区解析失败也会在此暴露

4.5 页 / OOB / ECC 布局与 UBI 开销

理解这张图,就能解释「为什么 128MB 的器件格式化成 UBI 后只剩 100 多 MB 可用」。

一个 PEB(128KB 块)内部:EC 头 + VID 头 + 数据页;一页内部:4 个 ECC 扇区 + OOBPEB(物理擦除块,128KB = 64 页)EC 头2048BVID 头2048B数据区(62 页)126976BLEB = PEB − (EC 头 + VID 头) = 126KB;UBI 用这两个头做卷映射与磨损均衡计数单页(2048B main + 64B OOB)512BECC 扇区 1512BECC 扇区 2512BECC 扇区 3512BECC 扇区 4OOB 64BBBM + ECC 校验值+ 用户可用字节① 片内 ECC 器件:ECC 计算与校验值都在芯片内部完成,主机不生成校验码,只读 status 判级。② OOB 的第 0(或前 2)字节是坏块标记区,驱动/UBI 判坏块的依据就是它是否 ≠ 0xFF。③ 4KB 页器件的 UBI subpage 与 VID 偏移不同,ubinize 配置必须同步改(见附录 B)。
图 7 · PEB / 页 / OOB 布局与 UBI 头开销
开销项典型值(2KB 页 / 128KB 块)说明
EC 头(Erase Counter Header)1 页记录该 PEB 的擦除次数,磨损均衡依据
VID 头(Volume Identifier Header)1 页记录该 PEB 属于哪个卷、LEB 号
LEB 大小PEB − 2 页 = 126 KB用户实际可用的逻辑块大小
UBI 管理开销约 2 个 PEB(layout volume)+ 坏块预留ubinize 与 ubiformat 会自动预留
坏块预留默认按器件标称坏块比例的 2 倍左右预留可用 ubiformat / ubinize 参数调整
UBIFS 自身开销日志 + 索引节点 + LPT挂载后 df 看到的可用空间还会再少一些

4.6 中断与 DMA

SPI NAND 走的是 SPI 控制器,没有 NAND 控制器那种专用中断。性能优化与稳定性问题基本都落在 SPI 控制器的 DMA 与 FIFO 行为上。

项目说明排查要点
DMA 传输大页读写(2KB / 4KB)应由 DMA 承担;PIO 模式 CPU 占用极高cat /proc/interrupts 看 SPI 中断频率;top 看 CPU 是否被读写打满
FIFO 深度控制器 FIFO 太小会频繁中断;大页传输可能被拆成多段控制器驱动需支持 spi_mem 的分段传输;FIFO 溢出表现为数据错位
CS 保持整条 op 期间 CS# 必须保持有效,不能每字节拉高一次逻辑分析仪看 CS#:若被切成碎段,驱动或控制器配置有问题
时钟上限spi-max-frequency 取「SoC 能力 ∩ 器件能力」超规格偶发 ECC 错误;降温/加压可复现
中断 vs 轮询spinand 用轮询状态寄存器(OIP),不用中断轮询间隔与超时在框架内;超时短会误判失败
缓存一致性DMA buffer 必须是 dma_alloc_coherent 或正确 sync非一致性 DMA 表现为「读出的数据总是旧的一页」
DMA 相关的两条硬纪律

① 传给 SPI 的 buffer 不能是栈上的临时变量(除非框架内部已做 bounce buffer)—— 栈内存可能不满足 DMA 对齐与一致性要求,在部分架构上会直接踩坏内存。
② DMA 完成前不要复用 buffer;spi_sync() 返回后才代表传输结束。

4.7 编码规范与 remove 函数

  1. checkpatch 必过:./scripts/checkpatch.pl --strict --no-tree 0001-*.patch;upstream 提交对 --strict 也要求零 error。
  2. 错误处理走 goto 链:内核惯例,出错时按资源申请的反序释放,不要用「一路 return + 每层重复释放」。
  3. 优先 devm_* 系列:devm_kzalloc / devm_gpiod_get 等,remove 时自动释放,减少漏释放。
  4. remove 与 probe 严格对称:probe 里申请的每一份资源都要有对应释放;rmmod 后再 insmod 必须能正常工作(这是最有效的自测)。
  5. 日志等级克制:正常路径用 dev_dbg(由 dynamic_debug 控制),异常才用 dev_err;量产日志里刷屏的 dev_info 要清掉。
  6. 提交粒度:一个 patch 做一件事;新增器件、修 bug、改格式分成三个 patch。
  7. Signed-off-by 必带:git commit -s,否则 upstream 不接受。
/* remove 与 probe 对称的典型写法(示意) */
static int spinand_remove(struct spi_mem *mem)
{
    struct spinand_device *spinand = spi_mem_get_drvdata(mem);
    int ret;

    ret = mtd_device_unregister(&spinand->base.mtd);
    if (ret)
        return ret;

    nanddev_cleanup(&spinand->base);
    kfree(spinand->scratchbuf);
    /* devm_* 申请的资源无需在此释放 */
    return 0;
}

/* 自测:反复加载卸载 20 次不应有内存泄漏或 oops */
# for i in $(seq 1 20); do modprobe spinand; rmmod spinand; done; dmesg | tail
相关报告 · 交叉引用 1 器件全部展开收起 ▾
点开类目即可展开:具体文档 / 章节(点章直接跳转到该章)
SPI NAND
电路设计指南
软件设计规范
样机问题排查
量产问题排查4
全志平台 SPI NAND NOR
章节(点章跳转,共 11 章)
海思平台 SPI NAND 量产常见失败
章节(点章跳转,共 11 章)
烧录成功但不开机 SPI NAND
章节(点章跳转,共 12 章)
瑞芯微平台 SPI NAND eMMC
章节(点章跳转,共 11 章)
系统设计指南3