Skip to content

Latest commit

 

History

History
2640 lines (2091 loc) · 90 KB

File metadata and controls

2640 lines (2091 loc) · 90 KB

Linux 内核 MTD / NAND Flash 子系统深度分析

基于 Linux 内核源码(主线 master 分支)实际读取分析。 主要源文件:include/linux/mtd/mtd.hdrivers/mtd/mtdcore.cdrivers/mtd/nand/raw/nand_base.cinclude/linux/mtd/rawnand.hinclude/linux/mtd/nand.hdrivers/mtd/ubi/ubi.hinclude/linux/mtd/ubi.hdrivers/mtd/spi-nor/core.cinclude/linux/mtd/spi-nor.hfs/ubifs/ubifs.h


目录

  1. MTD 子系统概述
  2. MTD 层次架构
  3. 核心数据结构 mtd_info
  4. MTD 操作接口
  5. OOB(Out-Of-Band)区域
  6. NAND Flash 驱动层
  7. nand_chip 结构体详解
  8. NAND 页/块/擦除单元结构
  9. ECC 纠错码机制
  10. 坏块管理(BBM)
  11. UBI 子系统
  12. UBIFS 文件系统
  13. SPI NOR Flash
  14. MTD 字符设备与块设备
  15. JFFS2:历史背景
  16. MTD 注册流程
  17. 典型使用场景与配置

1. MTD 子系统概述

MTD(Memory Technology Devices)是 Linux 内核对各类闪存介质的统一抽象层,由 David Woodhouse 于 1999 年创立(drivers/mtd/mtdcore.c 第 4 行版权声明 Copyright © 1999-2010 David Woodhouse),目的是屏蔽不同闪存硬件(NOR、NAND、 DataFlash 等)之间的差异,向上层文件系统和用户空间提供一致的接口。

MTD 与传统块设备(如 HDD/SSD)的核心区别:

特性 块设备(HDD/SSD) MTD 设备
写入粒度 512 字节扇区 页(Page),通常 2KB~
擦除操作 无需单独擦除 必须先擦除再写入
坏块 由控制器透明处理 需要软件层显式管理
磨损均衡 内置(SSD FTL) 需要 UBI 等上层实现
OOB 区域 每页附带 OOB 字节

MTD 子系统在内核中的位置(Kconfig 路径:CONFIG_MTD):

drivers/mtd/
├── mtdcore.c          # MTD 核心,设备注册/注销,字符设备
├── mtdchar.c          # /dev/mtdX 字符设备实现
├── mtdblock.c         # /dev/mtdblockX 块设备模拟
├── mtd_blkdevs.c      # 块设备层适配
├── partitions/        # 分区解析(cmdlinepart、ofpart 等)
├── nand/
│   ├── raw/           # raw NAND 驱动(nand_base.c 等)
│   └── spi/           # SPI NAND 驱动
├── spi-nor/           # SPI NOR Flash 驱动
├── ubi/               # UBI(Unsorted Block Images)
└── chips/             # NOR Flash 芯片驱动(CFI 等)

2. MTD 层次架构

  用户空间应用
       |
  +-----------+   +-----------+   +-----------+
  | /dev/mtdX |   |  UBIFS    |   |  JFFS2    |
  | 字符设备  |   |  文件系统 |   |  文件系统 |
  +-----------+   +-----------+   +-----------+
       |                |
  +-----------+   +-----------+
  |/dev/mtdblk|   |   UBI     |  <--- 磨损均衡 / 坏块管理
  | 块设备层 |   | 卷管理层  |
  +-----------+   +-----------+
            \          /
         +------------------+
         |   MTD 核心层     |  <--- struct mtd_info
         |  (mtdcore.c)     |       统一 read/write/erase 接口
         +------------------+
                  |
     +------------+------------+
     |            |            |
+--------+  +---------+  +---------+
|  NAND  |  | SPI NOR |  |  NOR   |
| 驱动层 |  |  驱动层 |  | 驱动层 |
|nand_chip|  | spi_nor |  |  ...   |
+--------+  +---------+  +---------+
     |            |
  硬件 Flash 芯片(通过 NAND 控制器 / SPI 控制器)

2.1 MTD Master 与 Partition

MTD 设备有两种角色,由 include/linux/mtd/mtd.h 第 213~236 行定义:

// mtd.h L213-L218
struct mtd_part {
    struct list_head node;   // 挂入父设备的分区链表
    u64 offset;              // 在父设备中的偏移
    u64 size;                // 分区大小
    u32 flags;               // 原始 flags(分区逻辑可能调整)
};

// mtd.h L232-L236
struct mtd_master {
    struct mutex partitions_lock;  // 保护分区链表的锁
    struct mutex chrdev_lock;
    unsigned int suspended : 1;
};
  • Master(主设备):直接对应物理闪存,mtd->parent == NULL
  • Partition(分区):逻辑子设备,mtd->parent 指向父设备。

内联函数 mtd_is_partition()(mtd.h L421)判断当前设备是否为分区:

static inline bool mtd_is_partition(const struct mtd_info *mtd)
{
    return mtd->parent;
}

mtd_get_master()(mtd.h L403)沿 parent 链向上遍历,找到根设备:

static inline struct mtd_info *mtd_get_master(struct mtd_info *mtd)
{
    while (mtd->parent)
        mtd = mtd->parent;
    return mtd;
}

MTD 使用 IDR(DEFINE_IDR(mtd_idr),mtdcore.c L71)管理设备编号,字符设备号 通过宏 MTD_DEVT(index) = MKDEV(MTD_CHAR_MAJOR, (index)*2)(mtdcore.c L87)计算。 每个 MTD 设备占用两个次设备号:偶数为读写设备 /dev/mtdX,奇数为只读设备 /dev/mtdXro


3. 核心数据结构 mtd_info

struct mtd_infoinclude/linux/mtd/mtd.h L238-L401)是整个 MTD 子系统的 核心,它同时描述 Master 设备和 Partition,嵌入了 mtd_partmtd_master

// mtd.h L238
struct mtd_info {
    u_char  type;         // 设备类型:MTD_NANDFLASH、MTD_NORFLASH 等
    uint32_t flags;       // 标志位:MTD_WRITEABLE、MTD_NO_ERASE 等
    uint64_t size;        // 设备总大小(字节)

    uint32_t erasesize;   // 擦除块大小(主要粒度,如 128KB)
    uint32_t writesize;   // 最小写入单元(NAND 为页大小,NOR 为 1)
    uint32_t writebufsize;// 写缓冲区大小(某些 NOR > writesize)

    uint32_t oobsize;     // 每个写单元的 OOB 字节数(如 64 字节)
    uint32_t oobavail;    // 可用 OOB 字节数(去掉 ECC 占用后)

    // 位移和掩码,用于地址对齐快速计算
    unsigned int erasesize_shift;
    unsigned int writesize_shift;
    unsigned int erasesize_mask;
    unsigned int writesize_mask;

    unsigned int bitflip_threshold; // 触发 -EUCLEAN 的 bitflip 阈值

    const char *name;    // 设备名称(如 "nand0")
    int index;           // 在 mtd_idr 中的编号

    const struct mtd_ooblayout_ops *ooblayout;  // OOB 布局描述
    const struct mtd_pairing_scheme *pairing;   // MLC/TLC 配对方案

    unsigned int ecc_step_size;  // ECC 步长(每步保护的字节数)
    unsigned int ecc_strength;   // 每步最多纠正的位翻转数

    int numeraseregions;
    struct mtd_erase_region_info *eraseregions; // 变长擦除区域描述

    // 操作函数指针(不要直接调用,使用 mtd_*() 包装函数)
    int (*_erase)(struct mtd_info *mtd, struct erase_info *instr);
    int (*_read)(struct mtd_info *mtd, loff_t from, size_t len,
                 size_t *retlen, u_char *buf);
    int (*_write)(struct mtd_info *mtd, loff_t to, size_t len,
                  size_t *retlen, const u_char *buf);
    int (*_read_oob)(struct mtd_info *mtd, loff_t from,
                     struct mtd_oob_ops *ops);
    int (*_write_oob)(struct mtd_info *mtd, loff_t to,
                      struct mtd_oob_ops *ops);
    int (*_block_isbad)(struct mtd_info *mtd, loff_t ofs);
    int (*_block_markbad)(struct mtd_info *mtd, loff_t ofs);
    // ... 更多 ops

    struct mtd_ecc_stats ecc_stats; // ECC 统计(corrected/uncorrectable)
    int subpage_sft;    // 子页移位(NAND)

    void *priv;         // 驱动私有数据指针
    struct module *owner;
    struct device dev;
    struct kref refcnt;
    struct nvmem_device *nvmem;  // NVMEM 提供者

    struct mtd_info *parent;     // 父设备(为 NULL 则是 master)
    struct list_head partitions; // 子分区链表

    struct mtd_part   part;      // 分区字段(offset/size/flags)
    struct mtd_master master;    // master 字段(partitions_lock 等)
};

3.1 MTD 设备类型枚举

mtdcore.c L142~L168 的 mtd_type_show() 函数揭示了所有设备类型:

类型常量 字符串 说明
MTD_ABSENT "absent" 不存在
MTD_RAM "ram" RAM 类型
MTD_ROM "rom" 只读 ROM
MTD_NORFLASH "nor" NOR Flash
MTD_NANDFLASH "nand" SLC NAND Flash
MTD_DATAFLASH "dataflash" Atmel DataFlash
MTD_UBIVOLUME "ubi" UBI 卷
MTD_MLCNANDFLASH "mlc-nand" MLC NAND Flash

4. MTD 操作接口

4.1 公开的包装函数

内核代码不应直接调用 mtd->_read(),而必须使用 mtd.h L499 起定义的包装函数:

// mtd.h L499
int mtd_erase(struct mtd_info *mtd, struct erase_info *instr);

// mtd.h L505
int mtd_read(struct mtd_info *mtd, loff_t from, size_t len,
             size_t *retlen, u_char *buf);

// mtd.h L507
int mtd_write(struct mtd_info *mtd, loff_t to, size_t len,
              size_t *retlen, const u_char *buf);

// mtd.h L512
int mtd_read_oob(struct mtd_info *mtd, loff_t from, struct mtd_oob_ops *ops);
int mtd_write_oob(struct mtd_info *mtd, loff_t to, struct mtd_oob_ops *ops);

// mtd.h L543
int mtd_block_isbad(struct mtd_info *mtd, loff_t ofs);
int mtd_block_markbad(struct mtd_info *mtd, loff_t ofs);

这些包装函数会处理分区偏移转换(通过 mtd_get_master_ofs() 将分区内偏移换算 为物理设备偏移),保证分区视图的正确性。

4.2 擦除操作

struct erase_info(mtd.h L30-L34):

struct erase_info {
    uint64_t addr;       // 擦除起始地址(必须对齐 erasesize)
    uint64_t len;        // 擦除长度(必须是 erasesize 的整数倍)
    uint64_t fail_addr;  // 失败地址(MTD_FAIL_ADDR_UNKNOWN 表示未知)
};

擦除操作是 Flash 中最危险的操作:一次擦除通常将整个块(128KB 或 256KB)全部 置 1(0xFF)。NAND Flash 的擦除次数有限(SLC 约 10 万次,MLC 约 1 万次)。

4.3 读取返回值语义

MTD 读取操作有特殊的返回值约定:

  • 0:完全成功,无位翻转
  • -EUCLEAN:有位翻转,但已被 ECC 纠正,且翻转数达到 bitflip_threshold, 表示该块正在老化,建议上层(如 UBI)搬移数据
  • -EBADMSG:ECC 无法纠正,数据不可靠
  • 其他负值:I/O 错误

mtdcore.c L288 处的 sysfs 属性 bitflip_threshold 可由用户态动态调整 (/sys/class/mtd/mtdX/bitflip_threshold)。


5. OOB(Out-Of-Band)区域

5.1 什么是 OOB

NAND Flash 的每个页(Page)除了主数据区域外,还附带一段"带外"(Out-Of-Band)区域。 典型布局:

+------------------------------------------+----------+
|         主数据区(Main Area)             |  OOB 区  |
|              2048 字节                    |  64 字节  |
+------------------------------------------+----------+

OOB 区域用途:

  1. ECC 校验码:存储对应页数据的 ECC 字节,用于错误检测和纠正
  2. 坏块标记(BBM):出厂时坏块在特定位置标记为非 0xFF
  3. 文件系统元数据:JFFS2 等文件系统会在 OOB 存储额外信息

5.2 mtd_oob_ops 操作结构

struct mtd_oob_ops(mtd.h L70-L80)描述一次读写 OOB 的操作参数:

struct mtd_oob_ops {
    unsigned int mode;    // MTD_OPS_PLACE_OOB / MTD_OPS_AUTO_OOB / MTD_OPS_RAW
    size_t   len;         // 主数据区要读/写的字节数
    size_t   retlen;      // 实际读/写的主数据字节数
    size_t   ooblen;      // OOB 区要读/写的字节数
    size_t   oobretlen;   // 实际读/写的 OOB 字节数
    uint32_t ooboffs;     // 在 OOB 区内的偏移(PLACE_OOB/RAW 模式有效)
    uint8_t *datbuf;      // 主数据缓冲区(NULL 则只操作 OOB)
    uint8_t *oobbuf;      // OOB 数据缓冲区
    struct mtd_req_stats *stats; // 统计信息(bitflip 计数等)
};

操作模式说明:

  • MTD_OPS_PLACE_OOB:在 ooboffs 处手动放置 OOB 数据
  • MTD_OPS_AUTO_OOB:自动跳过 ECC 字节,只操作"free"区域
  • MTD_OPS_RAW:原始模式,完整读写整个 OOB,不做任何 ECC 处理

5.3 OOB 布局描述

struct mtd_ooblayout_ops(mtd.h L106-L111)定义如何在 OOB 中定位 ECC 和 空闲区域:

struct mtd_ooblayout_ops {
    // 返回第 section 个 ECC 区域的偏移和长度
    int (*ecc)(struct mtd_info *mtd, int section,
               struct mtd_oob_region *oobecc);
    // 返回第 section 个空闲区域的偏移和长度
    int (*free)(struct mtd_info *mtd, int section,
                struct mtd_oob_region *oobfree);
};

struct mtd_oob_region(mtd.h L92-L95):

struct mtd_oob_region {
    u32 offset;  // 在 OOB 内的偏移
    u32 length;  // 区域长度
};

典型的 2KB 页 NAND(64 字节 OOB),使用 BCH 8 位强度 ECC 时,OOB 布局可能为:

OOB 64 字节:
[0..1]   坏块标记(BBM)
[2..63]  ECC 字节(13 字节/512字节步长,共 4 步 = 52 字节)

辅助函数(mtd.h L431-L447):

  • mtd_ooblayout_ecc():获取第 N 段 ECC 区域
  • mtd_ooblayout_free():获取第 N 段空闲区域
  • mtd_ooblayout_get_eccbytes():从 OOB 缓冲区中拷贝 ECC 字节
  • mtd_ooblayout_count_freebytes():统计可用 OOB 空闲字节总数
  • mtd_ooblayout_count_eccbytes():统计 ECC 字节总数

6. NAND Flash 驱动层

6.1 通用 NAND 设备抽象

include/linux/mtd/nand.h 定义了与硬件无关的通用 NAND 抽象层。

struct nand_memory_organization(nand.h L29-L39)描述 NAND 的物理存储组织:

struct nand_memory_organization {
    unsigned int bits_per_cell;            // 每单元位数:1=SLC, 2=MLC, 3=TLC
    unsigned int pagesize;                 // 页大小(字节)
    unsigned int oobsize;                  // OOB 大小(字节/页)
    unsigned int pages_per_eraseblock;     // 每块的页数
    unsigned int eraseblocks_per_lun;      // 每 LUN 的块数
    unsigned int max_bad_eraseblocks_per_lun; // 最大允许坏块数/LUN
    unsigned int planes_per_lun;           // 每 LUN 的 plane 数
    unsigned int luns_per_target;          // 每 target(die)的 LUN 数
    unsigned int ntargets;                 // 总 target(die)数
};

struct nand_device(nand.h L420-L427)是所有 NAND 类型的基类:

struct nand_device {
    struct mtd_info mtd;            // 内嵌的 MTD 设备
    struct nand_memory_organization memorg; // 存储组织
    struct nand_ecc ecc;            // ECC 配置
    struct nand_row_converter rowconv; // 行地址转换
    struct nand_bbt bbt;            // 坏块表(Bad Block Table)
    const struct nand_ops *ops;     // NAND 基本操作(erase/markbad/isbad)
};

通过 container_of 实现的类型转换辅助函数(nand.h L453-L467):

// nand.h L453
static inline struct nand_device *mtd_to_nanddev(struct mtd_info *mtd)
{
    return container_of(mtd, struct nand_device, mtd);
}

// nand.h L464
static inline struct mtd_info *nanddev_to_mtd(struct nand_device *nand)
{
    return &nand->mtd;
}

6.2 Raw NAND 初始化入口

drivers/mtd/nand/raw/nand_base.c 是 Raw NAND 的核心实现,作者为 Steven J. Hill 和 Thomas Gleixner(nand_base.c L1-L24 注释)。

选片与取消选片(nand_base.c L171-L201):

// nand_base.c L171
void nand_select_target(struct nand_chip *chip, unsigned int cs)
{
    chip->cur_cs = cs;
    if (chip->legacy.select_chip)
        chip->legacy.select_chip(chip, cs);
}

// nand_base.c L194
void nand_deselect_target(struct nand_chip *chip)
{
    if (chip->legacy.select_chip)
        chip->legacy.select_chip(chip, -1);
    chip->cur_cs = -1;
}

chip->controller->lockchip->lock 两级锁(nand_base.c L212-L214)保证 了控制器级别和芯片级别的并发安全。


7. nand_chip 结构体详解

struct nand_chipinclude/linux/mtd/rawnand.h L1288-L1346)是 Raw NAND 驱动的核心数据结构,继承自 nand_device

struct nand_chip {
    struct nand_device base;         // 基类,内嵌 mtd_info

    struct nand_id id;               // NAND ID(最多 8 字节)
    struct nand_parameters parameters; // ONFI/JEDEC 参数
    struct nand_manufacturer manufacturer; // 厂商信息
    struct nand_chip_ops ops;        // 芯片级操作(suspend/resume/lock)
    struct nand_legacy legacy;       // 旧版接口(已废弃,勿用)
    unsigned int options;            // 芯片选项标志(NAND_BUSWIDTH_16 等)

    /* 数据接口配置 */
    const struct nand_interface_config *current_interface_config;
    struct nand_interface_config *best_interface_config;

    /* 坏块管理 */
    unsigned int bbt_erase_shift;    // BBT 条目地址位数
    unsigned int bbt_options;        // BBT 选项
    unsigned int badblockpos;        // 坏块标记在 OOB 中的位置
    unsigned int badblockbits;       // 判断好块需要的最少置 1 位数
    struct nand_bbt_descr *bbt_td;   // BBT 主描述符
    struct nand_bbt_descr *bbt_md;   // BBT 镜像描述符
    u8 *bbt;                         // 内存中的 BBT 缓存

    /* 地址布局(移位值加速计算) */
    unsigned int page_shift;         // 页内偏移位数(log2 pagesize)
    unsigned int phys_erase_shift;   // 块内偏移位数(log2 blocksize)
    unsigned int chip_shift;         // 芯片内偏移位数(log2 chipsize)
    unsigned int pagemask;           // 芯片内页号掩码

    /* I/O 缓冲区 */
    u8 *data_buf;                    // 数据缓冲区(pagesize + oobsize)
    u8 *oob_poi;                     // OOB 缓冲区指针(指向 data_buf 末尾)
    struct {
        unsigned int bitflips;
        int page;                    // 当前缓存的页号(-1 无缓存)
    } pagecache;

    /* 并发控制 */
    struct mutex lock;
    unsigned int suspended : 1;
    wait_queue_head_t resume_wq;
    int cur_cs;                      // 当前选中的 CS(-1 未选)

    /* 安全区域(来自 DT) */
    struct nand_secure_region *secure_regions;
    u8 nr_secure_regions;

    /* 连续读(Sequential Cache Read)*/
    struct {
        bool ongoing;
        unsigned int first_page;
        unsigned int pause_page;
        unsigned int last_page;
    } cont_read;

    struct nand_controller *controller; // 所属控制器
    struct nand_ecc_ctrl ecc;           // ECC 控制结构
    void *priv;                          // 驱动私有数据
};

类型转换辅助(rawnand.h L1348-L1356):

static inline struct nand_chip *mtd_to_nand(struct mtd_info *mtd)
{
    return container_of(mtd, struct nand_chip, base.mtd);
}

static inline struct mtd_info *nand_to_mtd(struct nand_chip *chip)
{
    return &chip->base.mtd;
}

7.1 NAND 控制器抽象

struct nand_controller(rawnand.h L1120-L1128)描述共享控制器:

struct nand_controller {
    struct mutex lock;                  // 序列化对控制器的访问
    const struct nand_controller_ops *ops;
    struct {
        unsigned int data_only_read: 1; // 支持不重启就继续读数据
        unsigned int cont_read: 1;      // 支持连续缓存读
    } supported_op;
    bool controller_wp;                 // 控制器管理 WP 引脚
};

struct nand_controller_ops(rawnand.h L1097-L1105)定义现代控制器的操作接口:

struct nand_controller_ops {
    int (*attach_chip)(struct nand_chip *chip);   // ECC 检测后调用,分配资源
    void (*detach_chip)(struct nand_chip *chip);  // 释放资源
    int (*exec_op)(struct nand_chip *chip,         // 执行 NAND 操作序列
                   const struct nand_operation *op,
                   bool check_only);
    int (*setup_interface)(struct nand_chip *chip, int chipnr,
                           const struct nand_interface_config *conf);
};

exec_op 接口替代了旧版的 cmdfunc/read_buf/write_buf,通过 struct nand_operation 描述一系列原子指令(CMD/ADDR/DATA_IN/DATA_OUT/WAITRDY), 让控制器驱动可以精确控制时序(见 rawnand.h L670-L1034 的指令定义体系)。


8. NAND 页/块/擦除单元结构

8.1 物理层次

NAND Flash 的存储层次从小到大:

  位(Bit)
    |
  字节(Byte)
    |
  页(Page)= 主数据区 + OOB
    |        典型值:512B(旧)、2KB、4KB、16KB
    |
  块(Block / Eraseblock)= N 个页
    |        典型值:64页/块(2KB页=128KB块)、256页/块
    |
  LUN(Logical Unit Number)= N 个块
    |
  Target(Die)= N 个 LUN
    |
  NAND 芯片 = N 个 Target(多 Die 封装)

以 Samsung K9F2G08 为例(nand_memory_organization 填充值):

  • bits_per_cell = 1(SLC)
  • pagesize = 2048(2KB 主区 + 64 字节 OOB)
  • pages_per_eraseblock = 64(=> 块大小 = 128KB)
  • eraseblocks_per_lun = 2048(=> 总容量 = 256MB)

8.2 MLC/TLC 页配对方案

MLC(Multi-Level Cell)每个物理单元存储 2 位,TLC 存储 3 位。由于物理单元共享, 同一单元内的多个页(称为"pair")相互影响,写入顺序有严格要求。

struct mtd_pairing_scheme(mtd.h L181-L187)抽象了页配对关系:

struct mtd_pairing_scheme {
    int ngroups;  // 组数(MLC=2, TLC=3)
    int (*get_info)(struct mtd_info *mtd, int wunit,
                    struct mtd_pairing_info *info);   // wunit -> (pair, group)
    int (*get_wunit)(struct mtd_info *mtd,
                     const struct mtd_pairing_info *info); // (pair, group) -> wunit
};

nand_base.c L49~L97 实现了 dist3_pairing_scheme(针对 dist=3 距离的 MLC):

// nand_base.c L93
const struct mtd_pairing_scheme dist3_pairing_scheme = {
    .ngroups = 2,
    .get_info = nand_pairing_dist3_get_info,
    .get_wunit = nand_pairing_dist3_get_wunit,
};

H27UCG8T2BTR-BC 的配对方案示例(mtd.h L130~L145 注释):

         group-0    group-1
pair-0   page-0     page-4
pair-1   page-1     page-5
pair-2   page-2     page-8
...
pair-127 page-251   page-255

8.3 NAND 地址空间

check_offs_len()(nand_base.c L99-L116)检查地址和长度必须对齐到物理块边界:

// nand_base.c L104
if (ofs & ((1ULL << chip->phys_erase_shift) - 1)) {
    pr_debug("%s: unaligned address\n", __func__);
    ret = -EINVAL;
}

chip->page_shiftchip->phys_erase_shiftchip->chip_shift 三个移位值 是频繁用于地址计算的加速字段。chip->pagemask 用于从绝对页号取芯片内页号。


9. ECC 纠错码机制

9.1 ECC 架构总览

NAND Flash 的存储单元由于物理特性会产生位翻转(bit flip),ECC 机制用于检测和 纠正这些错误。内核支持多种 ECC 方案,由 include/linux/mtd/nand.h L144~L197 定义的枚举类型描述:

// nand.h L145
enum nand_ecc_engine_type {
    NAND_ECC_ENGINE_TYPE_INVALID,
    NAND_ECC_ENGINE_TYPE_NONE,       // 不做 ECC
    NAND_ECC_ENGINE_TYPE_SOFT,       // 软件 ECC
    NAND_ECC_ENGINE_TYPE_ON_HOST,    // 主机端硬件 ECC(NAND 控制器内置)
    NAND_ECC_ENGINE_TYPE_ON_DIE,     // 芯片内部 ECC(芯片固件处理)
};

// nand.h L174
enum nand_ecc_algo {
    NAND_ECC_ALGO_UNKNOWN,
    NAND_ECC_ALGO_HAMMING,  // Hamming 码(1 位纠错)
    NAND_ECC_ALGO_BCH,      // BCH 码(多位纠错,常用)
    NAND_ECC_ALGO_RS,       // Reed-Solomon 码
};

// nand.h L161
enum nand_ecc_placement {
    NAND_ECC_PLACEMENT_UNKNOWN,
    NAND_ECC_PLACEMENT_OOB,         // ECC 字节存放在 OOB 区
    NAND_ECC_PLACEMENT_INTERLEAVED, // ECC 与数据交织存放(Syndrome 布局)
};

9.2 struct nand_ecc_ctrl

struct nand_ecc_ctrl(rawnand.h L349-L385)是 Raw NAND ECC 的核心控制结构:

struct nand_ecc_ctrl {
    enum nand_ecc_engine_type engine_type;
    enum nand_ecc_placement placement;
    enum nand_ecc_algo algo;
    int steps;      // 每页的 ECC 步数(通常 pagesize / step_size)
    int size;       // 每步保护的数据字节数(常见:256 或 512)
    int bytes;      // 每步产生的 ECC 字节数
    int total;      // 全页 ECC 字节总数 = steps * bytes
    int strength;   // 每步最大可纠正位翻转数
    int prepad;     // Syndrome 布局的前填充字节
    int postpad;    // Syndrome 布局的后填充字节

    u8 *calc_buf;   // 计算出的 ECC 缓冲区
    u8 *code_buf;   // 从 flash 读出的 ECC 缓冲区

    // 操作函数指针
    void (*hwctl)(struct nand_chip *chip, int mode);
    int (*calculate)(struct nand_chip *chip, const uint8_t *dat,
                     uint8_t *ecc_code);
    int (*correct)(struct nand_chip *chip, uint8_t *dat, uint8_t *read_ecc,
                   uint8_t *calc_ecc);  // 返回纠正的位翻转数,或 -EBADMSG

    int (*read_page)(struct nand_chip *chip, uint8_t *buf,
                     int oob_required, int page);
    int (*write_page)(struct nand_chip *chip, const uint8_t *buf,
                      int oob_required, int page);
    int (*read_oob)(struct nand_chip *chip, int page);
    int (*write_oob)(struct nand_chip *chip, int page);
    // ... 更多读写变体
};

9.3 通用 ECC 引擎抽象(新接口)

include/linux/mtd/nand.h L242-L267 定义了更新的 ECC 引擎抽象:

// nand.h L242
struct nand_ecc_context {
    struct nand_ecc_props conf;  // 引擎参数(algo/strength/step_size)
    unsigned int nsteps;         // 步数
    unsigned int total;          // ECC 总字节数
    void *priv;                  // 引擎私有数据
};

// nand.h L260
struct nand_ecc_engine_ops {
    int (*init_ctx)(struct nand_device *nand);       // 初始化 ECC 上下文
    void (*cleanup_ctx)(struct nand_device *nand);   // 清理 ECC 上下文
    int (*prepare_io_req)(struct nand_device *nand,  // 读写前准备
                          struct nand_page_io_req *req);
    int (*finish_io_req)(struct nand_device *nand,   // 读写后完成
                         struct nand_page_io_req *req);
};

软件 ECC 引擎的获取接口(nand.h L333-L348):

// Hamming 软件引擎(nand-ecc-sw-hamming.c)
struct nand_ecc_engine *nand_ecc_sw_hamming_get_engine(void);

// BCH 软件引擎(nand-ecc-sw-bch.c,依赖 lib/bch.c)
struct nand_ecc_engine *nand_ecc_sw_bch_get_engine(void);

nand_base.c L38-L39 引入了这两个 ECC 模块的头文件:

#include <linux/mtd/nand-ecc-sw-hamming.h>
#include <linux/mtd/nand-ecc-sw-bch.h>

9.4 Hamming 码原理

Hamming 码是最简单的 ECC 方案,每 256 字节数据产生 3 字节(22 位)ECC:

  • 可以检测 2 位错误,纠正 1 位错误
  • 计算复杂度低,适合旧版 SLC NAND
  • 现代 MLC/TLC NAND 通常需要更强的 ECC(4T~ 24T BCH)

9.5 BCH 码原理

BCH(Bose-Chaudhuri-Hocquenghem)码是更强的多位纠错码。内核 lib/bch.c 提供 了通用 BCH 实现(GF(2^m) 有限域运算):

  • 强度(t):可纠正的最大位翻转数
  • 每 512 字节步长,BCH-4 需要约 7 字节 ECC,BCH-8 需要约 13 字节
  • 现代 NAND 控制器通常内置硬件 BCH 引擎(ON_HOST 类型)

ECC 强度要求来自芯片本身(nand_ecc_props.requirements),由驱动在检测阶段填入, 内核通过 nand_ecc_is_strong_enough()(nand.h L308)验证配置是否满足芯片要求。


10. 坏块管理(BBM)

10.1 坏块标记位置

NAND Flash 出厂时,厂商会在坏块的特定位置留下标记(通常为非 0xFF 值)。 rawnand.h 定义了三种可能的位置(L203-L205):

#define NAND_BBM_FIRSTPAGE   BIT(24)  // 块的第一页 OOB
#define NAND_BBM_SECONDPAGE  BIT(25)  // 块的第二页 OOB
#define NAND_BBM_LASTPAGE    BIT(26)  // 块的最后一页 OOB

OOB 中的具体字节位置(rawnand.h L221-L222):

#define NAND_BBM_POS_SMALL  5  // 小页(512B)NAND
#define NAND_BBM_POS_LARGE  0  // 大页(2KB+)NAND(第 0 字节)

10.2 坏块检测函数

nand_block_bad()(nand_base.c L252-L279)是默认的坏块检测实现:

static int nand_block_bad(struct nand_chip *chip, loff_t ofs)
{
    int first_page, page_offset;
    u8 bad;

    first_page = (int)(ofs >> chip->page_shift) & chip->pagemask;
    page_offset = nand_bbm_get_next_page(chip, 0);

    while (page_offset >= 0) {
        res = chip->ecc.read_oob(chip, first_page + page_offset);
        bad = chip->oob_poi[chip->badblockpos];

        if (likely(chip->badblockbits == 8))
            res = bad != 0xFF;            // 精确匹配
        else
            res = hweight8(bad) < chip->badblockbits; // 汉明重量判断

        if (res)
            return res;  // 发现坏块标记
        page_offset = nand_bbm_get_next_page(chip, page_offset + 1);
    }
    return 0;
}

nand_bbm_get_next_page()(nand_base.c L225-L243)根据 NAND_BBM_* 标志遍历 需要检查的页偏移列表。

10.3 坏块表(BBT)

为避免每次操作都要读取 OOB 检查坏块,内核会在 NAND 启动时扫描坏块并建立内存 BBT 缓存(chip->bbt)。BBT 每个块使用 2 bit 编码:

含义
00 已知坏块
01 工厂坏块
10 保留
11 好块

chip->bbt_tdchip->bbt_md(rawnand.h L1306-L1307)分别指向 BBT 主描述符 和镜像描述符,内核还会将 BBT 持久化到闪存末尾的特定区域(通常占用 4 个预留块)。

NAND_SKIP_BBTSCAN(rawnand.h L155)标志可以跳过启动时的 BBT 扫描(由更上层 的 UBI 接管坏块管理时使用)。


11. UBI 子系统

UBI(Unsorted Block Images)是构建在 MTD 之上的卷管理层,提供:

  1. 磨损均衡(Wear Leveling):均匀分配擦写次数
  2. 坏块管理:透明地屏蔽坏块
  3. 卷管理:将物理擦除块(PEB)映射到逻辑擦除块(LEB),创建多个逻辑卷
  +------------------+
  |  UBIFS / 用户态  |
  +------------------+
  |  UBI 卷(Volume)|  <-- struct ubi_volume
  +------------------+
  | UBI 设备(Device)|  <-- struct ubi_device
  |  - EBA(LEB->PEB映射)
  |  - WL(磨损均衡)
  |  - I/O 子系统
  +------------------+
  |    MTD 设备       |
  +------------------+

11.1 struct ubi_device

struct ubi_devicedrivers/mtd/ubi/ubi.h L555-L656)是 UBI 设备的核心描述符, 包含 700+ 行注释,以下列出关键字段:

struct ubi_device {
    struct cdev cdev;         // 字符设备(/dev/ubiX)
    struct device dev;
    int ubi_num;              // UBI 设备编号
    int vol_count;            // 卷数量
    struct ubi_volume *volumes[UBI_MAX_VOLUMES + UBI_INT_VOL_COUNT];
    spinlock_t volumes_lock;

    /* PEB 统计 */
    int rsvd_pebs;            // 保留 PEB 数
    int avail_pebs;           // 可用 PEB 数
    int beb_rsvd_pebs;        // 为坏块处理预留的 PEB 数
    int bad_peb_count;        // 坏 PEB 数量
    int good_peb_count;       // 好 PEB 数量

    /* 卷表 */
    int vtbl_slots;
    struct ubi_vtbl_record *vtbl;  // 内存中的卷表副本
    struct mutex device_mutex;

    /* 磨损均衡计数 */
    int max_ec;               // 最大擦写计数
    int mean_ec;              // 平均擦写计数

    /* EBA 子系统 */
    unsigned long long global_sqnum;  // 全局序列号
    spinlock_t ltree_lock;
    struct rb_root ltree;             // LEB 锁树(并发控制)

    /* Fastmap 优化 */
    int fm_disabled;
    struct ubi_fastmap_layout *fm;    // Fastmap 布局
    struct ubi_fm_pool fm_pool;       // Fastmap PEB 池
    struct ubi_fm_pool fm_wl_pool;    // WL 子系统用的池

    /* 磨损均衡(WL)子系统 */
    struct rb_root used;      // 已使用 PEB 的红黑树(按 EC 排序)
    struct rb_root free;      // 空闲 PEB 的红黑树(按 EC 排序)
    int free_count;
    struct rb_root scrub;     // 需要 scrubbing 的 PEB
    struct list_head pq[UBI_PROT_QUEUE_LEN];  // 保护队列(10 个 slot)
    int pq_head;
    spinlock_t wl_lock;
    struct ubi_wl_entry **lookuptbl; // PEB 号 -> wl_entry 快速查找表
    struct ubi_wl_entry *move_from; // 正在搬移的源 PEB
    struct ubi_wl_entry *move_to;   // 正在搬移的目标 PEB
    struct task_struct *bgt_thread; // 后台线程(ubi_bgt%dd)
    char bgt_name[sizeof(UBI_BGT_NAME_PATTERN)+2];

    /* 物理设备参数 */
    long long flash_size;     // 底层 MTD 设备大小
    int peb_count;            // PEB 总数
    int peb_size;             // PEB 大小(= MTD erasesize)
    int leb_size;             // LEB 大小(= peb_size - 头部开销)
    int leb_start;            // LEB 在 PEB 内的起始偏移
    struct mtd_info *mtd;     // 指向底层 MTD 设备

    struct ubi_debug_info dbg;
};

11.2 磨损均衡(Wear Leveling)

struct ubi_wl_entry(ubi.h L170-L177)是磨损均衡的基本单元:

struct ubi_wl_entry {
    union {
        struct rb_node rb;       // 在 free/used 红黑树中的节点
        struct list_head list;   // 在保护队列中的节点
    } u;
    int ec;    // 擦写计数(Erase Counter)
    int pnum;  // 物理擦除块号
};

磨损均衡策略

  • 静态磨损均衡(Static WL):即使某个 PEB 没有被写入,也会定期将其数据 搬移到 EC 较高的块中,同时用低 EC 块取代高 EC 块
  • 动态磨损均衡(Dynamic WL):每次写入新数据时,选择当前 EC 最小的空闲块
  • 触发阈值:当 max_ec - min_ec 超过某个阈值(默认 UBI_WL_THRESHOLD=128)时, 后台线程触发数据搬移

free 红黑树按 EC 从小到大排列,写入时从树的最左节点取块;used 树存放已用块, scrubbing 时遍历 EC 差异大的块对。

保护队列(Protection Queue,PQ):长度为 UBI_PROT_QUEUE_LEN=10(ubi.h L73), 刚被搬移的 PEB 先放入保护队列,经过足够多的全局擦除操作后才重新加入自由池, 防止同一块被反复搬移。

11.3 EBA(Eraseblock Association)子系统

EBA 维护 LEB -> PEB 的映射表(ubi_volume->eba_tbl)。每个卷有独立的 EBA 表。

struct ubi_eba_leb_desc(ubi.h L269-L272):

struct ubi_eba_leb_desc {
    int lnum;  // 逻辑擦除块号
    int pnum;  // 物理擦除块号
};

struct ubi_ltree_entry(ubi.h L193-L199)实现每个 LEB 的读写锁:

struct ubi_ltree_entry {
    struct rb_node rb;
    int vol_id;
    int lnum;
    int users;              // 当前使用者数
    struct rw_semaphore mutex; // 读写信号量
};

11.4 UBI 卷(Volume)

struct ubi_volume(ubi.h L330-L371):

struct ubi_volume {
    struct device dev;
    struct cdev cdev;           // /dev/ubiX_Y
    struct ubi_device *ubi;
    int vol_id;
    int reserved_pebs;          // 为此卷保留的 PEB 数
    int vol_type;               // UBI_DYNAMIC_VOLUME 或 UBI_STATIC_VOLUME
    int usable_leb_size;        // 可用 LEB 大小(考虑对齐后)
    int used_ebs;               // 实际含数据的 LEB 数
    long long used_bytes;       // 数据字节总量
    int alignment;              // 卷对齐要求
    int data_pad;               // 末尾填充字节数
    char name[UBI_VOL_NAME_MAX + 1];

    struct ubi_eba_table *eba_tbl;  // LEB->PEB 映射表

    unsigned int corrupted:1;   // 静态卷是否损坏
    unsigned int upd_marker:1;  // 更新被中断标记
    unsigned int updating:1;    // 正在更新中
};

卷分两种类型:

  • Dynamic Volume:可变内容,适合文件系统(UBIFS)
  • Static Volume:不可变内容,UBI 会对整个卷做 CRC 校验(适合内核/ramdisk 镜像)

11.5 UBI 内核 API

include/linux/mtd/ubi.h L240-L261 定义了供内核模块使用的 UBI 操作接口:

struct ubi_volume_desc *ubi_open_volume(int ubi_num, int vol_id, int mode);
struct ubi_volume_desc *ubi_open_volume_nm(int ubi_num, const char *name, int mode);
void ubi_close_volume(struct ubi_volume_desc *desc);

int ubi_leb_read(struct ubi_volume_desc *desc, int lnum, char *buf,
                 int offset, int len, int check);
int ubi_leb_write(struct ubi_volume_desc *desc, int lnum, const void *buf,
                  int offset, int len);
int ubi_leb_change(struct ubi_volume_desc *desc, int lnum, const void *buf,
                   int len);
int ubi_leb_erase(struct ubi_volume_desc *desc, int lnum);
int ubi_leb_unmap(struct ubi_volume_desc *desc, int lnum);

ubi_leb_change() 是原子 LEB 变更操作——先将新内容写入临时 PEB,验证无误后 再更新 EBA 映射,保证掉电安全性。

11.6 Fastmap

Fastmap(CONFIG_MTD_UBI_FASTMAP)是 UBI 的快速挂载优化。正常 UBI attach 需要 扫描所有 PEB 读取 EC/VID 头,对于大容量 NAND(如 8GB)可能需要数十秒。

Fastmap 将整个 UBI 状态(WL 树、EBA 表等)序列化后写入专用 PEB,下次 attach 只 需读取 Fastmap PEB 即可,将挂载时间降低到秒级以下。

struct ubi_fastmap_layout(ubi.h L232-L238):

struct ubi_fastmap_layout {
    struct ubi_wl_entry *e[UBI_FM_MAX_BLOCKS];  // Fastmap 占用的 PEB
    int to_be_tortured[UBI_FM_MAX_BLOCKS];
    int used_blocks;
    int max_pool_size;
    int max_wl_pool_size;
};

I/O 错误码(ubi.h L105-L111)体现了 UBI 对 MTD 错误的精细处理:

enum {
    UBI_IO_FF = 1,           // 读到全 0xFF
    UBI_IO_FF_BITFLIPS,      // 全 0xFF 但有位翻转
    UBI_IO_BAD_HDR,          // EC/VID 头损坏(CRC 错误)
    UBI_IO_BAD_HDR_EBADMSG,  // 头损坏且有 ECC 不可纠正错误
    UBI_IO_BITFLIPS,         // 有位翻转但已纠正
};

12. UBIFS 文件系统

UBIFS(Unsorted Block Images File System)是专为 UBI 设计的日志结构文件系统, 由 Nokia 的 Artem Bityutskiy 和 Adrian Hunter 开发(fs/ubifs/ubifs.h L1-L9)。

  UBIFS 文件系统结构

  +--------------------------------------------------+
  |                  VFS 层接口                       |
  +--------------------------------------------------+
  |                                                  |
  |  Journal(日志)         Index(B-tree 索引)    |
  |  +-----------+           +------------------+   |
  |  | GC Head   |           |  TNC(Tree Node  |   |
  |  | Base Head |           |  Cache)内存 B树  |   |
  |  | Data Head |           +------------------+   |
  |  +-----------+                    |              |
  |       |                    Index Node             |
  |       v                   (存储在 Flash 上)       |
  |  LEB(Logical Eraseblock)                        |
  +--------------------------------------------------+
  |                 UBI 卷                            |
  +--------------------------------------------------+

12.1 UBIFS 关键常量

fs/ubifs/ubifs.h 定义的核心常量:

// ubifs.h L41
#define UBIFS_VERSION 1

// ubifs.h L44
#define UBIFS_SUPER_MAGIC 0x24051905

// ubifs.h L60
#define MIN_INDEX_LEBS 2          // 最少保留 2 个 LEB 给索引

// ubifs.h L81
#define BGT_NAME_PATTERN "ubifs_bgt%d_%d"  // 后台线程名

// ubifs.h L87
#define NONDATA_JHEADS_CNT 2       // 非数据 Journal Head 数(GC + Base)

12.2 Journal 机制

UBIFS 使用多个 Journal Head(日志头)来分离不同类型的写入流量:

Journal Head 宏名 用途
GC Head GCHD 垃圾回收使用
Base Head BASEHD inode、目录项等元数据节点
Data Head DATAHD 文件数据节点

每个 Journal Head 对应一个当前活跃的 LEB,新数据顺序追加写入,满后换新 LEB。 这种设计使写入成为顺序操作,避免了随机写入对闪存的伤害。

提交状态机(ubifs.h L189-L196):

enum {
    COMMIT_RESTING = 0,          // 无需提交
    COMMIT_BACKGROUND,           // 后台提交已请求
    COMMIT_REQUIRED,             // 必须提交
    COMMIT_RUNNING_BACKGROUND,   // 后台提交进行中
    COMMIT_RUNNING_REQUIRED,     // 提交进行中且必须完成
    COMMIT_BROKEN,               // 提交失败
};

12.3 B-tree 索引(TNC)

UBIFS 使用 B-tree 作为文件系统索引,在内存中维护 B-tree 的缓存节点(znode)。

union ubifs_key(ubifs.h L281-L286)是 B-tree 的键类型:

union ubifs_key {
    uint8_t  u8[UBIFS_SK_LEN];
    uint32_t u32[UBIFS_SK_LEN/4];
    uint64_t u64[UBIFS_SK_LEN/8];
    __le32   j32[UBIFS_SK_LEN/4];
};

键由 inode 号 + 键类型 + offset 组成,支持以下节点类型:

  • UBIFS_INO_KEY:inode 节点
  • UBIFS_DENT_KEY:目录项节点
  • UBIFS_DATA_KEY:数据块节点
  • UBIFS_XATTR_KEY:扩展属性节点

Znode 标志(ubifs.h L173-L177):

enum {
    DIRTY_ZNODE    = 0,  // znode 已脏(需要写回)
    COW_ZNODE      = 1,  // 提交中,修改前需先 COW
    OBSOLETE_ZNODE = 2,  // 已删除,等待提交后释放
};

12.4 LPT(LEB Properties Tree)

LPT 管理每个 LEB 的属性(空闲空间、脏数据量等),帮助 UBIFS 选择合适的 LEB 用于写入或垃圾回收。LPT_HEAP_SZ = 256(ubifs.h L75)是 LPT 堆的最大条目数。

12.5 垃圾回收

UBIFS 后台线程(ubifs_bgt%d_%d)负责垃圾回收,将已删除/修改数据占用的 LEB 清理出来,返还给 UBI。垃圾回收的 LEB 操作结果(ubifs.h L249-L252):

enum {
    LEB_FREED,      // LEB 已释放,可立即使用
    LEB_FREED_IDX,  // 索引 LEB 已释放,只能在下次提交后使用
    LEB_RETAINED,   // LEB 被保留用于 GC
};

12.6 UBIFS inode

struct ubifs_inode(ubifs.h L399+)扩展了 VFS inode,包含:

  • creat_sqnum:创建时的全局序列号
  • synced_i_size:已同步到 Flash 的 inode 大小
  • ui_size:UBIFS 使用的"影子"大小变量(与 inode->i_size 解耦)
  • compr_type:该 inode 使用的压缩算法(LZO/ZLIB/ZSTD)
  • bulk_read:批量读取优化标志

13. SPI NOR Flash

SPI NOR Flash 是通过 SPI 总线访问的 NOR 型闪存,常见于嵌入式系统存储 bootloader、 内核和小型只读文件系统。

13.1 SPI NOR 驱动架构

  应用/文件系统
       |
  +----------+
  | MTD 层   |  struct mtd_info(内嵌于 spi_nor)
  +----------+
  | spi-nor  |  struct spi_nor  (drivers/mtd/spi-nor/core.c)
  |  核心层  |  - SFDP 解析
  |          |  - 厂商特定修复
  +----------+
  | spi-mem  |  struct spi_mem  (drivers/spi/spi-mem.c)
  | 抽象层   |  统一 SPI 存储操作(cmd+addr+dummy+data)
  +----------+
  | SPI 控制器驱动
  +----------+

13.2 struct spi_nor

struct spi_norinclude/linux/mtd/spi-nor.h L382-L423):

struct spi_nor {
    struct mtd_info mtd;           // 内嵌 MTD 设备

    struct mutex lock;             // 序列化读/写/擦除操作
    struct spi_nor_rww {           // Read-While-Write 同步(多 bank 芯片)
        wait_queue_head_t wait;
        bool ongoing_io;
        bool ongoing_rd;
        bool ongoing_pe;           // 程序/擦除进行中
        unsigned int used_banks;
    } rww;

    struct device *dev;            // 指向 SPI 设备或 SPI NOR 控制器设备
    struct spi_mem *spimem;        // spi-mem 设备指针(如果使用 spi-mem 框架)
    u8 *bouncebuf;                 // 弹跳缓冲区(栈上/vmalloc 数据不能 DMA)
    size_t bouncebuf_size;

    u8 *id;                        // JEDEC ID(3~20 字节)
    const struct flash_info *info; // 芯片型号信息(名称/容量/标志)

    u8 addr_nbytes;                // 地址字节数(3 或 4)
    u8 erase_opcode;               // 擦除命令字节(如 0xD8)
    u8 read_opcode;                // 读命令字节(如 0x03、0x0B、0xEB)
    u8 read_dummy;                 // 读命令后的 dummy 时钟数
    u8 program_opcode;             // 编程命令字节(如 0x02)

    enum spi_nor_protocol read_proto;   // 读操作协议
    enum spi_nor_protocol write_proto;  // 写操作协议
    enum spi_nor_protocol reg_proto;    // 寄存器操作协议

    u32 flags;                     // SNOR_F_* 标志
    enum spi_nor_cmd_ext cmd_ext_type; // DTR 模式命令扩展类型
    struct sfdp *sfdp;             // 解析后的 SFDP 数据

    const struct spi_nor_controller_ops *controller_ops;
    struct spi_nor_flash_parameter *params; // Flash 参数(可由 SFDP 覆盖)

    struct {
        struct spi_mem_dirmap_desc *rdesc; // 直接映射读描述符
        struct spi_mem_dirmap_desc *wdesc; // 直接映射写描述符
    } dirmap;

    void *priv;
};

13.3 SPI NOR 协议(spi_nor_protocol)

include/linux/mtd/spi-nor.h L159-L176 定义了丰富的 SPI 传输协议:

enum spi_nor_protocol {
    SNOR_PROTO_1_1_1 = ...,   // 标准 SPI:1线指令-1线地址-1线数据
    SNOR_PROTO_1_1_2 = ...,   // Dual Output
    SNOR_PROTO_1_2_2 = ...,   // Dual I/O
    SNOR_PROTO_1_1_4 = ...,   // Quad Output
    SNOR_PROTO_1_4_4 = ...,   // Quad I/O(常见高速读)
    SNOR_PROTO_1_1_8 = ...,   // Octal Output
    SNOR_PROTO_1_8_8 = ...,   // Octal I/O
    SNOR_PROTO_8_8_8 = ...,   // Octal SPI(所有阶段 8 线)
    SNOR_PROTO_8_8_8_DTR = ..., // Octal DTR(双倍速率)
    // ...
};

协议编码方式(spi-nor.h L131-L147)将指令位宽、地址位宽、数据位宽各用 8 位编码 打包进一个 u32 值中,DTR 标志位于第 24 位(SNOR_PROTO_IS_DTR BIT(24))。

常见 SPI NOR 操作码(spi-nor.h L22-L51):

#define SPINOR_OP_READ          0x03  // 标准读
#define SPINOR_OP_READ_FAST     0x0b  // 快速读(需要 dummy byte)
#define SPINOR_OP_READ_1_4_4    0xeb  // Quad I/O 读
#define SPINOR_OP_PP            0x02  // 页编程(Page Program,最多 256 字节)
#define SPINOR_OP_BE_4K         0x20  // 4KB 块擦除
#define SPINOR_OP_SE            0xd8  // 扇区擦除(通常 64KB)
#define SPINOR_OP_CHIP_ERASE    0xc7  // 全片擦除
#define SPINOR_OP_WREN          0x06  // 写使能
#define SPINOR_OP_RDID          0x9f  // 读 JEDEC ID
#define SPINOR_OP_RDSFDP        0x5a  // 读 SFDP 表

13.4 spi-mem 抽象层

spi-mem 框架将 SPI 存储操作抽象为四个阶段的操作结构体 struct spi_mem_op

CMD (1字节指令) | ADDR (n字节地址) | DUMMY (m个空时钟) | DATA (数据)

spi_nor_spimem_setup_op()(core.c L82-L121)根据选定协议设置每个阶段的 位宽(buswidth)和 DTR 模式,是协议选择和实际总线操作之间的桥梁。

spi_nor_spimem_read_data()(core.c L201-L236)展示了读操作的完整流程:

static ssize_t spi_nor_spimem_read_data(struct spi_nor *nor, loff_t from,
                                         size_t len, u8 *buf)
{
    struct spi_mem_op op =
        SPI_MEM_OP(SPI_MEM_OP_CMD(nor->read_opcode, 0),
                   SPI_MEM_OP_ADDR(nor->addr_nbytes, from, 0),
                   SPI_MEM_OP_DUMMY(nor->read_dummy, 0),
                   SPI_MEM_OP_DATA_IN(len, buf, 0));

    spi_nor_spimem_setup_op(nor, &op, nor->read_proto);  // 设置位宽

    // 优先使用直接内存映射(dirmap),其次执行 spi_mem_op
    if (nor->dirmap.rdesc) {
        nbytes = spi_mem_dirmap_read(nor->dirmap.rdesc, ...);
    } else {
        error = spi_nor_spimem_exec_op(nor, &op);
    }
    return nbytes;
}

dirmap(Direct Mapping)是高性能 SPI NOR 控制器的特性,将 Flash 地址空间 直接映射到 CPU 地址空间,允许 CPU 以普通内存读操作访问 Flash,避免每次读取 都经过 SPI 控制器驱动层。

13.5 SFDP(Serial Flash Discoverable Parameters)

SFDP(JEDEC JESD216 标准)是 SPI NOR Flash 的自描述参数表,芯片在出厂时将自身 特性(容量、擦除大小/命令、各种 Quad/Octal 模式支持情况等)写入内部 SFDP 区域。

通过操作码 SPINOR_OP_RDSFDP = 0x5a(spi-nor.h L47)读取 SFDP 数据。

spi_nor_scan()(spi-nor.h L450)是 SPI NOR 驱动的核心初始化函数:

  1. 读取 JEDEC ID
  2. 在已知芯片数据库中查找匹配项
  3. 若未找到,尝试读取 SFDP 表自动识别
  4. 根据控制器 hwcaps 选择最优读写协议
  5. 填充 mtd_info 字段并注册到 MTD 核心

13.6 SPI NOR 控制器 ops

struct spi_nor_controller_ops(spi-nor.h L305-L316):

struct spi_nor_controller_ops {
    int (*prepare)(struct spi_nor *nor);
    void (*unprepare)(struct spi_nor *nor);
    int (*read_reg)(struct spi_nor *nor, u8 opcode, u8 *buf, size_t len);
    int (*write_reg)(struct spi_nor *nor, u8 opcode, const u8 *buf, size_t len);
    ssize_t (*read)(struct spi_nor *nor, loff_t from, size_t len, u8 *buf);
    ssize_t (*write)(struct spi_nor *nor, loff_t to, size_t len, const u8 *buf);
    int (*erase)(struct spi_nor *nor, loff_t offs);
};

当控制器不支持 spi-mem 框架时,使用此接口;支持 spi-mem 时,nor->spimem 非 NULL,优先通过 spi_mem_exec_op() 执行所有操作。


14. MTD 字符设备与块设备

14.1 /dev/mtdX 字符设备

MTD 核心为每个注册的 MTD 设备创建两个字符设备节点:

设备节点 次设备号 权限 用途
/dev/mtdX index * 2 rw 读写访问,支持全部 ioctl
/dev/mtdXro index * 2 + 1 r 只读访问

字符设备主设备号 MTD_CHAR_MAJOR(通常为 90),设备号计算宏(mtdcore.c L87):

#define MTD_DEVT(index) MKDEV(MTD_CHAR_MAJOR, (index)*2)

常用 ioctl 命令(来自 mtd/mtd-abi.h):

ioctl 功能
MEMGETINFO 获取 MTD 设备信息(size/erasesize 等)
MEMERASE 擦除指定区域
MEMWRITEOOB 写带 OOB 的页
MEMREADOOB 读带 OOB 的页
MEMGETBADBLOCK 检查某块是否为坏块
MEMSETBADBLOCK 标记坏块
MEMLOCK / MEMUNLOCK 锁定/解锁块(NOR Flash)
MEMGETOOBSEL 获取 OOB 布局选项(旧接口)
MEMWRITE 通用写操作(可附带 OOB)

14.2 /dev/mtdblockX 块设备

/dev/mtdblockX 是对 MTD 设备的块设备模拟(drivers/mtd/mtdblock.c), 主设备号 MTD_BLOCK_MAJOR = 31。由于 MTD 的擦写特性,此块设备不做磨损均衡, 仅适用于:

  • 只读挂载(如挂载 squashfs 镜像)
  • 测试环境
  • 不需要可靠性的临时存储

对于需要可靠读写的场景,应通过 UBI + UBIFS 而非直接使用 mtdblock。

14.3 sysfs 属性

每个 MTD 设备在 /sys/class/mtd/mtdX/ 下暴露以下属性(由 mtdcore.c 实现):

/sys/class/mtd/mtd0/
├── type              # "nand" / "nor" / "mlc-nand" 等
├── flags             # 标志位(十六进制)
├── size              # 设备总大小(字节)
├── erasesize         # 擦除块大小
├── writesize         # 最小写入单元
├── subpagesize       # 子页大小(writesize >> subpage_sft)
├── oobsize           # OOB 字节数
├── oobavail          # 可用 OOB 字节数
├── ecc_strength      # ECC 强度(可纠正位数)
├── ecc_step_size     # ECC 步长
├── bitflip_threshold # bitflip 触发 -EUCLEAN 的阈值(可写)
└── numeraseregions   # 擦除区域数(0 表示均匀)

15. JFFS2:历史背景

JFFS2(Journalling Flash File System version 2)是 Linux 上第一个广泛使用的 Flash 文件系统(2001 年),由 David Woodhouse 和 Red Hat 开发,直接工作在 MTD 层之上,不需要 UBI。

15.1 JFFS2 工作原理

JFFS2 是一个日志结构文件系统(Log-Structured File System):

  • 所有数据和元数据都以"节点"(node)形式顺序追加写入
  • 旧版本数据被标记为"过时"(obsolete),不立即擦除
  • 垃圾回收进程寻找大部分为过时数据的块,将有效数据搬到新块,然后擦除旧块
  • 磨损均衡:通过随机选择擦除目标块(而非总选最"脏"的)实现均匀擦写

15.2 JFFS2 的局限性

JFFS2 在现代系统中已逐渐被 UBIFS 取代,原因:

  1. 挂载时间:JFFS2 挂载时需要扫描整个 Flash 建立内存索引,大容量 Flash 可能需要数分钟
  2. 内存占用:Flash 大小与内存中的节点索引成正比,大容量 Flash 内存开销大
  3. 不支持 UBI:JFFS2 自己管理磨损均衡,与 UBI 重复且不如 UBI 灵活
  4. 不支持加密:UBIFS 原生支持 fscrypt(Linux 文件系统加密框架)

JFFS2 的源码位于 fs/jffs2/,核心数据结构 struct jffs2_sb_infostruct jffs2_inode_info;在小容量(< 8MB)、只读或偶尔写入场景下仍有价值。

15.3 JFFS2 vs UBIFS 对比

特性 JFFS2 UBIFS
底层依赖 直接用 MTD 需要 UBI
挂载时间 慢(正比于 Flash 大小) 快(O(1),或 Fastmap 极快)
内存占用 小(B-tree 按需加载)
压缩 支持(zlib/lzo) 支持(lzo/zlib/zstd)
加密 不支持 支持 fscrypt
原子操作 支持 支持(journal + commit)
适用场景 小容量、简单嵌入式系统 大容量、需要可靠性的系统

16. MTD 注册流程

16.1 add_mtd_device() 函数

add_mtd_device()(mtdcore.c L693-L799+)是 MTD 设备注册的核心函数:

add_mtd_device(mtd)
  |
  +-- 基本校验(writesize != 0,读写 ops 不重叠等)
  |
  +-- 获取 mtd_table_mutex 全局锁
  |
  +-- idr_alloc() 分配设备编号
  |
  +-- 计算 erasesize_shift/mask、writesize_shift/mask
  |
  +-- MTD_POWERUP_LOCK: mtd_unlock() 解锁
  |
  +-- 注册 sysfs device(dev_set_name "mtd%d")
  |
  +-- mtd_check_of_node() 关联 DT 节点
  |
  +-- mtd_register_nvmem() 注册 NVMEM 提供者
  |
  +-- 通知所有已注册的 MTD notifier(mtd_notifiers 链表)
  |
  +-- 创建 debugfs 目录
  |
  +-- 释放锁

关键校验(mtdcore.c L714-L732):

// 读写 ops 不能同时提供 _read/_write 和 _read_oob/_write_oob
if (WARN_ON((mtd->_write && mtd->_write_oob) ||
            (mtd->_read && mtd->_read_oob)))
    return -EINVAL;

// 必须有擦除大小和擦除函数(或设置 MTD_NO_ERASE)
if (WARN_ON((!mtd->erasesize || !master->_erase) &&
            !(mtd->flags & MTD_NO_ERASE)))
    return -EINVAL;

mtd->bitflip_threshold 默认值设置(mtdcore.c L753-L754):

if (mtd->bitflip_threshold == 0)
    mtd->bitflip_threshold = mtd->ecc_strength;

16.2 MTD Notifier 机制

MTD 核心维护一个通知链 mtd_notifiers(mtdcore.c L84),当设备添加/删除时 通知所有订阅者(如 UBI、mtdblock 等):

// 注册通知
void register_mtd_user(struct mtd_notifier *new);
// 注销通知
int unregister_mtd_user(struct mtd_notifier *old);

UBI 通过此机制在 MTD 设备出现时自动 attach(如果配置了 ubi.mtd= 内核参数)。

16.3 分区解析

MTD 分区通过 mtd_device_parse_register()drivers/mtd/mtdpart.c)注册。 分区解析器(Parsers)从不同来源读取分区信息:

解析器 Kconfig 来源
cmdlinepart CONFIG_MTD_CMDLINE_PARTS 内核命令行 mtdparts=
ofpart CONFIG_MTD_OF_PARTS 设备树(Device Tree)
redboot CONFIG_MTD_REDBOOT_PARTS RedBoot 分区表
tplink CONFIG_MTD_TP_LINK_PARTS TP-Link 路由器固件格式

设备树分区示例:

flash@0 {
    partitions {
        compatible = "fixed-partitions";
        #address-cells = <1>;
        #size-cells = <1>;

        partition-bootloader@0 {
            label = "bootloader";
            reg = <0x0 0x100000>;
            read-only;
        };
        partition-kernel@100000 {
            label = "kernel";
            reg = <0x100000 0x400000>;
        };
        partition-rootfs@500000 {
            label = "rootfs";
            reg = <0x500000 0x1b00000>;
        };
    };
};

17. 典型使用场景与配置

17.1 嵌入式 Linux 标准 Flash 栈

              用户空间
      mount -t ubifs ubi0:rootfs /
                  |
    +-------------------------------+
    |           UBIFS               |
    |  Journal + B-tree + GC        |
    +-------------------------------+
    |            UBI                |
    |  WL + EBA + Bad Block 管理    |
    |  /dev/ubi0, /dev/ubi0_0       |
    +-------------------------------+
    |         Raw NAND MTD          |
    |  nand_chip + ECC(BCH)       |
    |  /dev/mtd0                    |
    +-------------------------------+
    |     NAND 控制器驱动           |
    |  (如 sunxi_nand, mxc_nand)   |
    +-------------------------------+
    |         硬件                  |
    +-------------------------------+

内核命令行参数示例:

ubi.mtd=0 root=ubi0:rootfs rootfstype=ubifs

17.2 SPI NOR Flash 典型用法

          文件系统(squashfs/jffs2)
                   |
    +---------------------------------+
    |       SPI NOR MTD 层            |
    |  spi_nor + SFDP 自动识别        |
    |  /dev/mtd0(bootloader)        |
    |  /dev/mtd1(kernel)            |
    |  /dev/mtd2(rootfs)            |
    +---------------------------------+
    |   spi-mem 框架 + SPI 控制器     |
    +---------------------------------+
    |   SPI NOR 芯片(W25Q128 等)    |
    +---------------------------------+

17.3 关键内核配置项

配置项 功能
CONFIG_MTD MTD 核心支持
CONFIG_MTD_NAND NAND Flash 通用支持
CONFIG_MTD_RAW_NAND Raw NAND 支持
CONFIG_MTD_NAND_ECC_SW_BCH 软件 BCH ECC
CONFIG_MTD_SPI_NOR SPI NOR Flash 驱动
CONFIG_MTD_UBI UBI 支持
CONFIG_MTD_UBI_FASTMAP UBI Fastmap 快速挂载
CONFIG_UBIFS_FS UBIFS 文件系统
CONFIG_JFFS2_FS JFFS2 文件系统(传统)
CONFIG_MTD_PARTITIONED_MASTER 允许 Master 设备本身也注册
CONFIG_MTD_CMDLINE_PARTS 命令行分区
CONFIG_MTD_OF_PARTS 设备树分区

17.4 调试与诊断

MTD 设备信息

cat /proc/mtd               # 列出所有 MTD 设备
mtdinfo -a                  # 详细设备信息(mtd-utils)

ECC 统计查看

cat /sys/class/mtd/mtd0/ecc_strength
cat /sys/class/mtd/mtd0/bitflip_threshold

UBI 状态

ubiinfo -a                  # UBI 设备信息
ubinfo /dev/ubi0            # 特定 UBI 设备
cat /sys/class/ubi/ubi0/total_eraseblocks
cat /sys/class/ubi/ubi0/bad_peb_count

NAND 坏块

nandtest -k /dev/mtd0       # 坏块扫描(mtd-utils)
flash_erase /dev/mtd0 0 0   # 全片擦除(谨慎使用)

内核调试: 通过 debugfs(CONFIG_MTD_UBI_FASTMAP 启用 chk_genchk_io 等选项)可模拟 位翻转和 I/O 错误,方便测试 UBI/UBIFS 的错误恢复路径。


总结

Linux MTD 子系统是一个层次清晰、扩展性强的闪存管理框架:

  1. MTD 核心(mtd_info) 通过函数指针 _read/_write/_erase/_read_oob 等提供统一接口,add_mtd_device() 负责注册管理,IDR 分配设备编号。

  2. NAND 驱动(nand_chip) 通过三层继承(nand_device -> nand_chip -> 具体控制器驱动)实现抽象,nand_ecc_ctrl 管理 ECC 算法和布局,BBT 缓存 坏块信息。

  3. UBI 在 MTD 之上实现磨损均衡(WL RB 树)和逻辑块映射(EBA),保护队列 防止热块,后台线程持续均衡磨损,Fastmap 优化启动时间。

  4. UBIFS 以日志结构 + B-tree 索引构建在 UBI 之上,多 Journal Head 分流写入, commit 机制保证一致性,LPT 追踪空间状态。

  5. SPI NOR(spi_nor) 通过 SFDP 标准自动识别芯片参数,spi-mem 框架统一 了 SPI 存储操作,dirmap 支持直接内存映射实现高性能读取。


由 Claude Code 分析生成


18. NAND Timing 参数与 ONFI 规范

18.1 ONFI(Open NAND Flash Interface)协议

ONFI 是 NAND Flash 互操作性规范,由 Intel、Micron 等厂商于 2006 年联合制定。 ONFI 定义了:

  1. 命令集:统一的读/写/擦除/复位等命令编码
  2. Timing 参数:详尽的信号时序要求
  3. 参数页(Parameter Page):通过 READ PARAMETER PAGE(命令 0xEC)读取 256 字节 芯片信息,包含厂商、型号、几何参数和 timing 模式

ONFI 参数页在内核中由 struct nand_onfi_params 表示 (include/linux/mtd/rawnand.h 第 132 行附近):

/* include/linux/mtd/rawnand.h */
struct nand_onfi_params {
    /* revision information and features block */
    __le16 revision;            /* ONFI 版本(bit0=1.0, bit1=2.0, bit2=3.0...)*/
    __le16 features;            /* 支持特性位图 */
    __le16 opt_cmd;             /* 可选命令支持 */
    u8     advanced_cmd[2];
    /* ... */
    /* manufacturer information block */
    char   manufacturer[12];    /* 厂商名 */
    char   model[20];           /* 型号 */
    u8     jedec_id;            /* JEDEC 厂商 ID */
    /* memory organization block */
    __le32 byte_per_page;       /* 每页字节数(不含 OOB)*/
    __le16 spare_bytes_per_page;/* 每页 OOB 字节数 */
    __le32 pages_per_block;     /* 每块页数 */
    __le32 blocks_per_lun;      /* 每 LUN 块数 */
    u8     lun_count;           /* LUN 数量 */
    /* electrical parameters block */
    u8     async_timing_mode;   /* 异步 timing 模式位图 (bit N = 支持模式 N) */
    /* ... */
};

18.2 NAND Timing 模式

ONFI 定义了 0~5 共 6 种 Timing 模式,数字越大速度越快:

模式    tCLS(min)  tALS(min)  tRC(min)   tWC(min)   最大速度
------  ---------  ---------  ---------  ---------  --------
Mode 0   50 ns      50 ns     100 ns     100 ns     10 MB/s
Mode 1   25 ns      25 ns      50 ns      45 ns     20 MB/s
Mode 2   15 ns      15 ns      35 ns      35 ns     28 MB/s
Mode 3   10 ns      10 ns      30 ns      30 ns     33 MB/s
Mode 4   10 ns      10 ns      25 ns      25 ns     40 MB/s
Mode 5    7 ns       7 ns      20 ns      20 ns     50 MB/s

内核中 timing 参数由 struct nand_interface_config 封装 (include/linux/mtd/rawnand.h):

struct nand_interface_config {
    enum nand_interface_type type;   /* NAND_SDR_IFACE 或 NAND_NVDDR_IFACE */
    union {
        struct nand_sdr_timings  sdr;   /* 异步 SDR 时序 */
        struct nand_nvddr_timings nvddr; /* NV-DDR 时序 */
    } timings;
    int cs_hold_delay;               /* CS 保持延时(ns)*/
    int cs_setup_delay;              /* CS 建立延时(ns)*/
};

struct nand_sdr_timings 包含约 20 个字段(单位皆为 ps):

struct nand_sdr_timings {
    u64 tBERS_max;   /* 块擦除时间(最大值)*/
    u32 tCCS_min;    /* 更换芯片选择建立时间 */
    u32 tPROG_max;   /* 页编程时间(最大值)*/
    u32 tR_max;      /* 页读取时间(最大值)*/
    u32 tALH_min;    /* ALE 保持时间 */
    u32 tALS_min;    /* ALE 建立时间 */
    u32 tARH_min;    /* ALE 到 RE# 保持时间 */
    u32 tADL_min;    /* ALE 到数据加载时间 */
    /* ... 约 40 个时序参数 */
};

18.3 Timing 协商流程

                 NAND 控制器驱动初始化
                        |
              nand_scan() -> nand_scan_ident()
                        |
                nand_detect() 读取 ONFI 参数页
                        |
              nand_onfi_detect() 解析 timing 模式
                        |
          nand_choose_best_sdr_timings()
          遍历支持的 mode 5..0,选最高兼容模式
                        |
          controller->setup_interface(chip, cs, cfg)
          控制器驱动配置硬件寄存器
                        |
          nand_change_interface_config(chip)
          发送 SET FEATURES(timing mode) 命令到芯片

ONFI SET FEATURES 命令(0xEF + feature addr 0x01)将 timing 模式写入芯片寄存器:

/* drivers/mtd/nand/raw/nand_base.c */
int nand_set_features(struct nand_chip *chip, int feature_addr,
                      const u8 *subfeature_param)
{
    /* 发送 SET FEATURES 命令 */
    chip->legacy.cmd_ctrl(chip, NAND_CMD_SET_FEATURES, ...);
    /* 写入 feature address */
    chip->legacy.cmd_ctrl(chip, feature_addr, ...);
    /* 写入 4 字节参数 */
    for (i = 0; i < ONFI_SUBFEATURE_PARAM_LEN; i++)
        chip->legacy.write_byte(chip, subfeature_param[i]);
    /* 等待 R/B# 信号 */
    return nand_wait_ready(chip);
}

18.4 Toggle DDR / JEDEC NAND

部分高端 NAND(如 Samsung 的 Toggle Mode DDR)支持双倍数据速率传输。 JEDEC NAND 规范(JESD230)也定义了类似的 NV-DDR 接口,速度可达 200 MB/s 以上。 内核在 nand_detect_interface_config() 中区分 SDR 与 NV-DDR 接口。


19. MTD 分区:固定分区与 DT 分区

19.1 分区框架概述

MTD 分区子系统位于 drivers/mtd/partitions/,提供将一个物理 MTD 设备划分为 多个逻辑子设备的机制。每个子分区也是一个完整的 struct mtd_info,其 parent 字段指向父设备。

分区解析器列表(Kconfig 选项):

drivers/mtd/partitions/
├── cmdlinepart.c    # CONFIG_MTD_CMDLINE_PARTS: bootargs 中指定分区
├── ofpart.c         # CONFIG_MTD_OF_PARTS: Device Tree 分区(主流)
├── redboot.c        # CONFIG_MTD_REDBOOT_PARTS: RedBoot FIS 表
├── afs.c            # CONFIG_MTD_AFS_PARTS: ARM Firmware Suite
└── bcm63xxpart.c    # Broadcom 私有分区格式

19.2 Device Tree 分区(ofpart)

现代嵌入式系统普遍通过 Device Tree 描述 NAND 分区布局。

典型 DTS 片段:

/* 以 QSPI NAND 为例 */
&qspi {
    flash@0 {
        compatible = "spi-nand";
        reg = <0>;

        partitions {
            compatible = "fixed-partitions";
            #address-cells = <1>;
            #size-cells = <1>;

            partition@0 {
                label = "u-boot";
                reg = <0x00000000 0x00100000>;  /* 1 MB */
                read-only;                       /* 只读分区 */
            };
            partition@100000 {
                label = "u-boot-env";
                reg = <0x00100000 0x00040000>;  /* 256 KB */
            };
            partition@140000 {
                label = "kernel";
                reg = <0x00140000 0x00800000>;  /* 8 MB */
            };
            partition@940000 {
                label = "rootfs";
                reg = <0x00940000 0x07000000>;  /* 112 MB */
            };
        };
    };
};

ofpart.cparse_ofpart_partitions() 遍历 partitions 子节点,读取 reg 属性为 mtd_partition 数组,然后调用 add_mtd_partitions() 注册。

19.3 cmdlinepart 分区

通过内核命令行(bootargs)指定分区,格式为:

mtdparts=<mtddef>[;<mtddef>]
mtddef := <mtd-id>:<partdef>[,<partdef>]
partdef := <size>[@offset][(<name>)][ro]

示例:

mtdparts=spi0.0:1m(u-boot)ro,256k(u-boot-env),8m(kernel),-(rootfs)

- 表示"剩余所有空间",ro 表示只读。内核启动时由 cmdlinepart.cparse_cmdline_partitions() 解析并注册。

19.4 分区注册流程

mtd_device_parse_register(mtd, types, &parser_data, parts, nr_parts)
    |
    +-- 按 types 数组顺序尝试各解析器
    |     parse_mtd_partitions(master, type, ...)
    |         -> parser->parse_fn(master, &pparts, data)
    |
    +-- 如失败,使用静态 parts 数组(驱动硬编码)
    |
    +-- add_mtd_partitions(master, parts, nr_parts)
          for each part:
              allocate slave mtd_info
              slave->parent = master
              slave->_read  = mtd_part_read  (限制偏移/长度后转发到 parent)
              slave->_write = mtd_part_write
              slave->_erase = mtd_part_erase
              add_mtd_device(slave)  -> 注册为独立 MTD 设备

分区的 read/write/erase 操作通过 drivers/mtd/mtd_part.c(或集成在 mtdcore.c) 的包装函数转发到父设备,同时做边界检查:

/* drivers/mtd/mtd_part.c */
static int mtd_part_read(struct mtd_info *mtd, loff_t from, size_t len,
                         size_t *retlen, u_char *buf)
{
    struct mtd_part *part = mtd_to_part(mtd);
    /* 检查越界 */
    if (from >= mtd->size || len > mtd->size - from)
        return -EINVAL;
    /* 加上分区起始偏移,转发到父设备 */
    return parent->_read(parent, from + part->offset, len, retlen, buf);
}

19.5 只读分区保护

MTD_WRITEABLE 标志(include/linux/mtd/mtd.h)控制分区是否可写。 DTS 中的 read-only 属性会清除此标志:

/* drivers/mtd/partitions/ofpart.c */
if (of_property_read_bool(child, "read-only"))
    parts[i].mask_flags |= MTD_WRITEABLE;

20. ECC 算法深度解析

20.1 Hamming Code(汉明码)

汉明码是 NAND Flash 最早使用的 ECC 方案,可纠正 1 位错误,检测 2 位错误(1-bit ECC per 256/512 bytes)。内核实现位于 lib/reed_solomon/drivers/mtd/nand/

汉明码计算原理(以 256 字节为例):

256 字节数据:b[0]..b[255],每字节 8 位
共 256 * 8 = 2048 位

校验位计算:
  P1  = 所有奇数列位 XOR   (列奇偶校验 1)
  P2  = 所有偶数列位 XOR   (列奇偶校验 2)
  P4  = 所有奇数行(上半)XOR
  P8  = 所有偶数行(下半)XOR
  P16 = 所有奇数位(0,2,4...)XOR
  P32 = 所有偶数位(1,3,5...)XOR
  (共 6 位行校验 + 6 位列校验 = 12 位,存 22 字节 OOB)

纠错:
  纠错码 = 接收到的校验位 XOR 重新计算的校验位
  如果非零,指出了错误位的位置 -> 翻转该位

内核 Hamming ECC 实现:drivers/mtd/nand/raw/nand_ecc.c,函数 nand_calculate_ecc()nand_correct_data()

限制:每 256 字节只能纠 1 位,对于现代 MLC/TLC NAND 的多位错误无能为力。

20.2 BCH 码(Bose-Chaudhuri-Hocquenghem)

BCH 是 NAND Flash 现代 ECC 的主流选择,支持纠正多位错误(4-bit、8-bit、 16-bit、24-bit 等)。

内核 BCH 库位于 lib/bch.c,关键 API:

/* lib/bch.c */
struct bch_control *bch_init(int m, int t, unsigned int prim_poly,
                              bool swap_bits);

参数说明:

  • m:GF(2^m) 域参数,常用 m=13(GF(8192))
  • t:纠错能力(可纠正 t 个错误位)
  • prim_poly:本原多项式(定义 Galois 域)
BCH(1024, 956) 示例:
  数据:1024 字节(8192 位)
  纠错:4 位
  ECC:7 字节(56 位)校验码
  OOB 占用:7 字节

BCH(512, 473) 纠 8 位:
  ECC:13 字节校验码

BCH 编码步骤

1. 将数据视为 GF(2^m) 上的多项式 d(x)
2. ECC = d(x) * x^(m*t) mod g(x)
   其中 g(x) 是生成多项式(t 个共轭根的最小多项式乘积)
3. 发送 c(x) = d(x) * x^(m*t) + ECC

解码:
1. 计算伴随式 S_i = c(α^i),i=1..2t
2. Berlekamp-Massey 算法求错误定位多项式 σ(x)
3. Chien search 找出 σ(x) 的根(即错误位置)
4. 翻转对应位

NAND 控制器硬件 BCH 示例(以 Allwinner NAND 控制器为例):

/* drivers/mtd/nand/raw/sunxi_nand.c */
/* 控制器支持硬件 BCH,ECC 强度可配置 16/24/28/32/40/48/56/60/64 位 */
static const int sunxi_nand_ecc_strengths[] = {16, 24, 28, 32, 40, 48, 56, 60, 64};

20.3 LDPC 码(Low-Density Parity-Check)

LDPC 是 3D TLC/QLC NAND 时代的高级 ECC,主要由 NAND 控制器硬件实现,内核层 通常不直接处理 LDPC(由控制器固件或 FTL 层处理)。

LDPC vs BCH 对比

特性         BCH                    LDPC
---------    --------------------   --------------------
类型         代数码                 迭代码
纠错能力     中(≤ 60 bit/1KB)    高(≥ 120 bit/1KB)
硬件复杂度   中等                   高(需要大量存储)
解码延迟     低(解析算法)         较高(迭代收敛)
实现方式     硬件/软件均可          几乎仅硬件实现
应用场景     SLC/MLC NAND           MLC/TLC/QLC NAND

对于软件 ECC 场景,内核优先使用 BCH;LDPC 主要在 SSD 控制器内部使用, Linux MTD 层不暴露 LDPC API。

20.4 ECC 强度选择与 NAND 寿命

NAND 类型    典型 RBER(原始误码率)   需要 ECC 强度
---------    -----------------------   -------------
SLC          1e-6 ~ 1e-5              1 bit/512B (Hamming)
MLC          1e-4 ~ 1e-3              8~24 bit/1KB (BCH)
TLC          1e-3 ~ 1e-2             40~60 bit/1KB (BCH/LDPC)
QLC          1e-2 ~ 1e-1             80-120 bit/1KB (LDPC)

nand_ecc_is_strong_enough() 函数(drivers/mtd/nand/raw/nand_base.c) 在初始化时检验 ECC 强度是否满足 ONFI 要求:

/* ECC 强度必须满足:strength / step_size >= nand_chip->base.eccreq.strength / step */
if (chip->ecc.strength * chip->base.eccreq.step_size <
    chip->base.eccreq.strength * chip->ecc.size) {
    WARN(1, "ECC strength too low for this NAND");
    return false;
}

21. NAND 坏块管理(BBT)深度解析

21.1 BBT 数据结构

BBT(Bad Block Table)将整个 NAND 的坏块信息以位图形式缓存在内存中, 避免每次访问都扫描 OOB 区域。

核心结构(include/linux/mtd/rawnand.h):

struct nand_bbt_descr {
    int     options;           /* BBT 选项标志 */
    int     pages[NAND_MAX_CHIPS]; /* BBT 存放的页地址(每颗 die) */
    int     offs;              /* 在 OOB 中的偏移 */
    int     veroffs;           /* 版本号在 OOB 中的偏移 */
    uint8_t version[NAND_MAX_CHIPS]; /* 各 die 的 BBT 版本号 */
    int     len;               /* BBT 标识字节数 */
    int     maxblocks;         /* BBT 保留的最大块数(从尾部算) */
    int     reserved_block_code; /* 保留块标识码 */
    uint8_t *pattern;          /* BBT 标识模式(魔数)*/
};

内存 BBT 的位图格式(每块 2 位):

00 = 块好(good)
01 = 块坏(BBT 创建时发现的坏块)
10 = 保留(BBT 本身占用的块)
11 = 坏(运行时发现的新坏块)

一个 PEB 对应 2 位,1KB 可以描述 4096 个块(128MB @ 32KB/block)

21.2 BBT 存储格式

BBT 存储在 NAND 芯片末尾几个块(maxblocks 决定,通常 4 个)。 默认主 BBT 使用模式 "Bbt0",镜像 BBT 使用 "1tbB"(反序):

BBT 页布局:
+------------------------------------+
| 魔数 ("Bbt0", 4 字节)              |  <- OOB[0..3]
| 版本号 (1 字节)                    |  <- OOB[4]
+------------------------------------+
| BBT 位图数据(主区 = page data)   |
| 每 2 位描述一个块                  |
| ...                                |
+------------------------------------+

21.3 BBT 扫描与更新

nand_scan()
    -> nand_scan_tail()
        -> nand_create_bbt()
            -> chip->bbt_td 和 chip->bbt_md 描述符(若为 NULL 用默认值)
            -> nand_scan_bbt(chip)
                +--> 搜索已存在的 BBT(在末尾 maxblocks 个块中查找魔数)
                |    nand_read_bbt() 将位图读入 chip->bbt[]
                |
                +--> 如无 BBT(全新芯片或 BBT 损坏):
                     nand_create_bbt_from_oob()
                         -> 扫描所有块的 OOB 第一字节:
                            非 0xFF 判为坏块(工厂标记)
                     nand_write_bbt() 将新建 BBT 写入 NAND

21.4 运行时坏块处理

/* drivers/mtd/nand/raw/nand_base.c */
/* 写入失败时标记坏块 */
static int nand_write_page(struct nand_chip *chip, const uint8_t *buf, ...)
{
    ret = chip->ecc.write_page(chip, buf, oob_required, page);
    if (ret) {
        if (ret == -EIO) {
            /* 写失败,标记此块为坏块 */
            nand_block_markbad(mtd, page_to_block_offset(chip, page));
        }
        return ret;
    }
    return 0;
}

nand_block_markbad() 流程:

1. 更新内存 BBT:chip->bbt[block/4] |= 0x01 << ((block & 0x03) * 2)
2. 调用 nand_markbad_bbts():将更新后的 BBT 写回 NAND(更新版本号)
3. 在坏块的 OOB 第一字节写入 0x00(工厂坏块标记格式兼容)

21.5 BBT 选项标志

/* include/linux/mtd/rawnand.h */
#define NAND_BBT_LASTBLOCK  0x00000010  /* BBT 存放在最后几个块 */
#define NAND_BBT_ABSPAGE    0x00000020  /* BBT 在绝对页地址 */
#define NAND_BBT_SEARCH     0x00000040  /* 扫描查找 BBT */
#define NAND_BBT_PERCHIP    0x00000080  /* 每 die 有独立 BBT */
#define NAND_BBT_VERSION    0x00000100  /* 使用版本号追踪最新 BBT */
#define NAND_BBT_CREATE     0x00000200  /* 不存在时创建 BBT */
#define NAND_BBT_WRITE      0x00002000  /* 将 BBT 写入 NAND */
#define NAND_BBT_2BIT       0x00004000  /* 每块用 2 位描述 */
#define NAND_BBT_NO_OOB     0x00020000  /* BBT 存在 DATA 区(无 OOB 控制器用)*/

22. UBI 子系统深度:Fastmap 优化

22.1 传统 UBI 扫描的问题

UBI 在挂载时需要扫描所有 PEB 的头部(VID header + EC header),每次读取约 需要一次 NAND 页读,对于大容量 NAND(如 4GB = 4096 个 PEB),扫描时间可能 长达数十秒,严重影响启动速度。

22.2 Fastmap 工作原理

Fastmap(CONFIG_MTD_UBI_FASTMAP)将 UBI 的内存数据结构(EBA 表、WL 树等) 序列化后存储在专用 PEB 中,挂载时只需读取少量 Fastmap PEB 即可恢复状态。

传统扫描:
  4096 PEBs × 1 次读 × ~100μs = ~409ms

Fastmap 扫描:
  Fastmap 通常 1~4 个 PEB × 读取 = ~0.4ms
  加上解析时间 ~10ms
  总计 < 15ms(提速约 40x)

Fastmap 在磁盘上的布局(drivers/mtd/ubi/fastmap.c):

Fastmap PEB(位于固定 PEB 范围 FM_MAX_START ~ FM_MAX_START+FM_MAX_BLOCKS):
+------------------------+
| EC Header              |
+------------------------+
| VID Header (FM类型)    |
+------------------------+
| struct ubi_fm_sb       |  Fastmap 超级块(magic, version, data_crc...)
+------------------------+
| struct ubi_fm_hdr      |  头部(free/used/scrub/erase 块计数)
+------------------------+
| struct ubi_fm_scan_pool|  FM pool(待处理 PEB 列表)
+------------------------+
| EBA 映射数据           |  所有 volume 的 LEB->PEB 映射
+------------------------+

22.3 Fastmap 更新时机

Fastmap 不在每次 PEB 操作后更新(否则会造成大量写入),而是:

  1. 卸载时ubi_detach_mtd_dev() -> ubi_update_fastmap() 写入最新状态
  2. 后台线程ubi_bgt_thread() 定期检查是否需要更新
  3. 强制触发:当 FM pool 耗尽时触发更新

23. SPI NAND Flash 支持

23.1 SPI NAND 与 Raw NAND 的区别

SPI NAND(通过 SPI 总线连接的 NAND Flash)在内核中由 drivers/mtd/nand/spi/ 管理,与 Raw NAND 共享部分基础设施:

SPI NAND 驱动栈:
+---------------------------------+
|   MTD 核心层(mtd_info)        |
+---------------------------------+
|   NAND 通用层(nand_device)    |  <-- 共享 BBT、ECC 框架
+---------------------------------+
|   SPI NAND 核心(spinand_core.c)|
+---------------------------------+
|   SPI NOR/NAND 抽象(spi-mem)  |  <-- spi_mem_exec_op()
+---------------------------------+
|   SPI 控制器驱动                |
+---------------------------------+

核心文件:

  • drivers/mtd/nand/spi/core.c:SPI NAND 通用逻辑
  • drivers/mtd/nand/spi/gigadevice.c:GigaDevice SPI NAND
  • drivers/mtd/nand/spi/macronix.c:Macronix SPI NAND
  • drivers/mtd/nand/spi/micron.c:Micron SPI NAND
  • drivers/mtd/nand/spi/winbond.c:Winbond SPI NAND

23.2 spinand_device 结构体

/* drivers/mtd/nand/spi/spinand.h */
struct spinand_device {
    struct spi_mem         *spimem;        /* SPI 内存设备句柄 */
    struct mutex           lock;           /* 并发保护 */
    struct spinand_id      id;             /* 芯片 ID */
    const struct spinand_info *info;       /* 芯片参数表 */

    /* 操作模板(SPI 命令) */
    struct spi_mem_op      op_templates;
    struct {
        const struct spi_mem_op *read_cache;
        const struct spi_mem_op *write_cache;
        const struct spi_mem_op *update_cache;
    } op_templates;

    /* 数据/OOB 缓冲区 */
    struct {
        unsigned int        buf_size;
        void                *buf;
        void                *databuf;
        void                *oobbuf;
    } scratchbuf;

    bool                    cont_read_possible;/* 支持连续读 */
    struct nand_device      base;              /* 继承自 nand_device */
};

23.3 典型 SPI NAND 操作时序

SPI NAND 的操作与 Raw NAND 的主要差异在于命令通过 SPI 协议发送:

读操作:
1. PAGE READ (0x13) + 24-bit 行地址
   -> 芯片将页数据从 NAND array 搬到内部 cache
2. GET FEATURE (0x0F) 轮询 OIP 位(操作进行中)
   -> 等待 OIP=0
3. READ FROM CACHE (0x03) + 16-bit 列地址
   -> 从内部 cache 通过 SPI 读取数据

写操作:
1. WRITE ENABLE (0x06)
2. PROGRAM LOAD (0x02) + 列地址 + 数据(写入内部 cache)
3. PROGRAM EXECUTE (0x10) + 行地址(将 cache 写入 array)
4. GET FEATURE 轮询 OIP + PROG_FAIL 位

擦除操作:
1. WRITE ENABLE (0x06)
2. BLOCK ERASE (0xD8) + 行地址
3. GET FEATURE 轮询 OIP + ERASE_FAIL 位

23.4 SPI NAND Quad/Octal 模式

高速 SPI NAND 支持四线(Quad SPI)和八线(Octal SPI)传输:

SPI 模式          MISO 线数   最大速度(@104MHz 时钟)
--------------    ---------   ---------------------------
Standard SPI (x1)    1          ~13 MB/s
Dual SPI (x2)        2          ~26 MB/s
Quad SPI (x4)        4          ~52 MB/s
Octal SPI (x8)       8          ~100+ MB/s

内核通过 spi_mem_opbuswidth 字段指定线宽:

#define SPINAND_PAGE_READ_FROM_CACHE_X4_OP(addr, ndummy, buf, len) \
    SPI_MEM_OP(SPI_MEM_OP_CMD(0x6B, 1),           /* 命令单线 */    \
               SPI_MEM_OP_ADDR(2, addr, 1),        /* 地址单线 */    \
               SPI_MEM_OP_DUMMY(ndummy, 1),        /* 虚拟字节 */    \
               SPI_MEM_OP_DATA_IN(len, buf, 4))    /* 数据四线 */

24. JFFS2 历史与 UBIFS 演进对比

24.1 JFFS2 设计局限(完整分析)

JFFS2 在大容量 NAND 上存在以下根本性问题:

启动时间问题

JFFS2 挂载流程:
1. 扫描整个 MTD 设备,读取所有节点头部
2. 建立内存中的 inode 树和节点映射
3. 识别过时(obsolete)节点

对于 256MB NAND(1024 个 128KB 块):
  每块读取时间 ~2ms(加上 ECC)
  扫描时间 ≈ 1024 × 2ms = 2 秒
  对于 1GB NAND:8 秒!

内存消耗问题: JFFS2 需要在内存中为每个节点保留元数据,内存占用随存储容量线性增长。 对于 256MB 分区,内存占用可达数十 MB,在内存受限的嵌入式设备上难以接受。

GC 效率问题: JFFS2 的 GC 粒度为整个擦除块,需要将块内所有有效节点迁移到其他块, 在随机写场景下 GC 会造成大量写放大。

24.2 YAFFS2 的历史地位

YAFFS2(Yet Another Flash File System 2)是 Android 系统早期(2.2 之前) 采用的 NAND 文件系统,特点:

  • 专为 NAND 设计,节点以 NAND 页为单位
  • 每页存储一个对象块(object chunk)+对象头
  • 使用"分代垃圾回收"减少 GC 延迟
  • 历史意义:Android 3.x 之后被 ext4(配合 eMMC 的 FTL)替代, 再后来 Android 10+ 系统分区改用 EROFS

24.3 现代替代方案比较

文件系统    类型          MTD/块设备  压缩  日志  适用场景
---------   ---------     ----------  ----  ----  --------
JFFS2       读写 LFS      MTD         软件  是    小容量 NOR/NAND(传统)
YAFFS2      读写          MTD         否    是    NAND(Android 旧版)
UBIFS       读写 B-tree   MTD+UBI     软件  是    大容量 NAND(主流嵌入式)
F2FS        读写 LFS      块设备      软件  是    eMMC/UFS/SSD(Android 现代)
EROFS       只读          块设备      硬件  否    系统分区(Android/容器)
SquashFS    只读          块设备      zlib  否    LiveCD/路由器(传统只读)

24.4 UBIFS 的 JFFS2 优势

特性比较:
                JFFS2              UBIFS
挂载时间        O(容量)            O(1)(日志重放)
内存占用        O(容量)            O(目录树深度)
并发性          单一挂载           支持并发读写
压缩            zlib/lzo           lzo/lz4/zstd
日志            无独立日志         有 Journal Heads
坏块处理        直接在 MTD 上      通过 UBI 抽象
大容量支持      较差(>256MB 慢)  良好(GB 级)

25. MTD 子系统调试与工具链

25.1 mtd-utils 用户空间工具

工具名           功能
-----------      --------------------------------------------------
flash_erase      擦除 MTD 分区(支持 -j 选项创建 JFFS2 擦除标记)
nandwrite        将镜像写入 NAND(跳过坏块)
nanddump         读取 NAND 内容(含 OOB)
flash_otp_info   读取 OTP 区域信息
mtdinfo          显示 MTD 设备详细信息
ubiformat        格式化 MTD 设备为 UBI
ubinize          创建 UBI 镜像文件
ubiattach        将 UBI 镜像附加到 MTD 设备
ubimkvol         创建 UBI 卷
ubirmvol         删除 UBI 卷
ubiupdatevol     更新 UBI 卷内容

25.2 /proc/mtd 接口

cat /proc/mtd
# 输出示例:
# dev:    size   erasesize  name
# mtd0: 00100000 00020000 "u-boot"       (1MB, 128KB/block)
# mtd1: 00040000 00020000 "u-boot-env"   (256KB)
# mtd2: 00800000 00020000 "kernel"       (8MB)
# mtd3: 07000000 00020000 "rootfs"       (112MB)

25.3 内核 MTD 调试配置

CONFIG_MTD_TESTS          # MTD 测试模块(mtd_torturetest、mtd_stresstest 等)
CONFIG_MTD_UBI_DEBUG      # UBI 调试(fastmap 一致性检查)
CONFIG_UBIFS_FS_DEBUG     # UBIFS 调试(挂载时额外一致性验证)
CONFIG_MTD_NAND_ECC_SW_BCH # 软件 BCH ECC(无硬件 ECC 时使用)

25.4 典型调试场景

场景一:ECC 错误率统计

# 通过 debugfs 查看 UBI ECC 统计(需 CONFIG_MTD_UBI_DEBUG)
cat /sys/kernel/debug/ubi/ubi0/detailed_erase_counters

# MTD ECC 统计
cat /sys/class/mtd/mtd0/ecc_failures
cat /sys/class/mtd/mtd0/corrected_bits

场景二:NAND 坏块分布

# 查看 BBT 内容(十六进制)
cat /sys/kernel/debug/mtd/mtd0/bbt

# 查看坏块数量
cat /sys/class/ubi/ubi0/bad_peb_count

场景三:UBI 磨损均衡状态

# EC 分布直方图(需 debugfs)
cat /sys/kernel/debug/ubi/ubi0/wear_leveling_worker
# 查看最大、最小、平均 EC
ubiinfo -a /dev/ubi0

场景四:ftrace 跟踪 MTD 操作

# 追踪 MTD 读写操作
echo 'mtd_read mtd_write mtd_erase' > /sys/kernel/tracing/set_ftrace_filter
echo function > /sys/kernel/tracing/current_tracer
echo 1 > /sys/kernel/tracing/tracing_on
# 执行操作后查看 trace
cat /sys/kernel/tracing/trace

26. NAND Flash 物理原理与可靠性

26.1 NAND Flash Cell 物理结构

NAND Flash Floating Gate Cell(浮栅型):

Control Gate (CG)
    |
 =========  <- 绝缘层(ONO: Oxide-Nitride-Oxide)
 | F.Gate |  <- 浮栅(存储电荷)
 =========  <- 隧道氧化层(~8nm)
[  沟道   ]  <- p 型半导体
 S       D   <- 源极和漏极

SLC: 1 bit/cell(2 个阈值电压状态)
  +-----+-----+
  | 擦除| 编程 |    阈值电压
  |  0  |  1  |
  +-----+-----+   -3V  0V  3V  6V

MLC: 2 bits/cell(4 个状态)
  +--+--+--+--+
  |11|10|00|01|    4 个电压窗口需精确控制
  +--+--+--+--+   -3V  0V  2V  4V  6V

TLC: 3 bits/cell(8 个状态)
  |111|110|101|100|011|010|001|000|
  8 个电压窗口,更小的间距 -> 更高误码率

26.2 P/E Cycles 与寿命

NAND 类型    典型 P/E 寿命    实际使用场景
---------    -------------    ----------------
SLC          100,000 次       工业级、高可靠场景
eMLC         30,000 次        企业 SSD、工业存储
MLC          3,000~10,000 次  消费级 SSD
TLC          500~3,000 次     主流消费 SSD、手机存储
QLC          100~500 次       大容量冷存储

软件层(UBI/FTL)通过磨损均衡延长有效寿命:

有效寿命 ≈ 单块寿命 × 存储总容量 / 实际数据量

26.3 Charge Trap Flash(CTF)

现代 3D NAND(如三星 V-NAND、Intel 3D XPoint 外的 QLC NAND)大多采用 Charge Trap Flash 而非传统浮栅技术:

  • 电荷存储在氮化物层的陷阱(trap)中,而非金属浮栅
  • 更好的隔离特性,减少单元间串扰(Cell-to-Cell Interference)
  • 适合 3D 堆叠(TANOS 结构)

内核层面对 CTF 和浮栅 NAND 透明处理,差异由控制器固件吸收。


由 Claude Code 分析生成