只读分享解决方案文档原理方案设计📅 2026-10-05🔖 V1.0⏳ 26天有效(至 2026-11-05)
🔧解决方案&应用市场分析 -> 原理方案设计 -> 嵌入式驱动&系统开发 -> Linux_SPI_NAND_驱动开发指南

Linux_SPI_NAND_驱动开发指南

生成时间 2026-10-04 11:48:38 有效期至 2026-11-05 15:32:30(26天有效(至 2026-11-05))
同系列 · 原理方案设计

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

编制日期 2026-10-05

面向 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 与内核必须同一次编译产出,混用会报 版本号 Rev 1.4 ... 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
扫码打开本页
微信扫码 · 展会可扫

再分享给同事

同事可直接打开这份资料《Linux_SPI_NAND_驱动开发指南》,在线阅读;完整章节请下载芯参谋。