《Linux 设备驱动开发详解:基于最新的 Linux 4.0 内核》
第 19 章 Linux MTD 设备驱动
参考:宋宝华 著,机械工业出版社,2015年版
19.1 MTD 设备驱动架构
19.1.1 MTD 的概念
MTD(Memory Technology Device,内存技术设备)是 Linux 内核中专门为Flash 存储器设计的子系统。Flash 存储器(NOR Flash、NAND Flash)与普通磁盘有本质区别,需要专门的驱动框架来处理其特殊性:
Flash 存储器的特殊性:
1. 写前必须擦除
Flash 只能将 1 写为 0,不能将 0 写为 1
写入前必须先擦除(将所有位置为 1)
擦除单位:扇区(NOR Flash)或块(NAND Flash)
2. 擦写次数有限
NOR Flash:约 10 万次擦写
NAND Flash:约 10 万次擦写(MLC 约 1 万次)
需要磨损均衡(Wear Leveling)算法
3. NAND Flash 有坏块
出厂时可能存在坏块
使用过程中也会产生新坏块
需要坏块管理(Bad Block Management)
4. 访问方式特殊
NOR Flash:支持字节寻址,可直接执行代码(XIP)
NAND Flash:只能按页读写,按块擦除
MTD 子系统的作用:
✓ 屏蔽不同 Flash 硬件的差异
✓ 提供统一的 Flash 操作接口
✓ 支持坏块管理
✓ 为上层文件系统(JFFS2/UBIFS/YAFFS2)提供接口
✓ 提供字符设备(/dev/mtdN)和块设备(/dev/mtdblockN)接口
19.1.2 MTD 体系结构
Linux MTD 体系结构:
┌─────────────────────────────────────────────────────────────┐
│ 用户空间 │
│ 应用程序 / flash_erase / nandwrite / mtd-utils │
└──────────────────────────┬──────────────────────────────────┘
│ /dev/mtdN(字符设备)
│ /dev/mtdblockN(块设备)
┌──────────────────────────▼──────────────────────────────────┐
│ MTD 设备层 │
│ ┌──────────────────────┐ ┌──────────────────────────────┐ │
│ │ MTD 字符设备 │ │ MTD 块设备 │ │
│ │ /dev/mtd0, mtd1... │ │ /dev/mtdblock0, mtdblock1.. │ │
│ └──────────────────────┘ └──────────────────────────────┘ │
└──────────────────────────┬──────────────────────────────────┘
│
┌──────────────────────────▼──────────────────────────────────┐
│ MTD 核心层(drivers/mtd/) │
│ mtd_info 结构体 / mtd_read / mtd_write / mtd_erase │
│ 坏块管理 / 分区管理(MTD Partition) │
└──────────────────────────┬──────────────────────────────────┘
│
┌──────────────────────────▼──────────────────────────────────┐
│ MTD 硬件驱动层 │
│ ┌──────────────────────┐ ┌──────────────────────────────┐ │
│ │ NOR Flash 驱动 │ │ NAND Flash 驱动 │ │
│ │ CFI/JEDEC 接口 │ │ NAND 控制器驱动 │ │
│ └──────────────────────┘ └──────────────────────────────┘ │
└──────────────────────────┬──────────────────────────────────┘
│ 操作硬件寄存器
┌──────────────────────────▼──────────────────────────────────┐
│ Flash 硬件 │
│ NOR Flash(M25P80/W25Q64 等) │
│ NAND Flash(MT29F2G08 等) │
└─────────────────────────────────────────────────────────────┘
上层文件系统:
JFFS2(Journalling Flash File System v2)← NOR Flash 常用
UBIFS(Unsorted Block Image File System)← NAND Flash 常用
YAFFS2(Yet Another Flash File System 2)← NAND Flash
SquashFS(只读压缩文件系统)← 根文件系统
19.1.3 MTD 核心数据结构
c
#include <linux/mtd/mtd.h>
/*
* mtd_info:MTD 设备描述符
* 每个 Flash 分区对应一个 mtd_info
*/
struct mtd_info {
u_char type; /* MTD 类型(MTD_NORFLASH/MTD_NANDFLASH)*/
uint32_t flags; /* 标志(MTD_WRITEABLE 等)*/
uint64_t size; /* 设备总大小(字节)*/
/* 擦除相关 */
uint32_t erasesize; /* 擦除块大小(字节)*/
uint32_t erasesize_shift;/* erasesize 的移位数 */
/* 读写相关 */
uint32_t writesize; /* 写入单位大小(字节)*/
uint32_t writebufsize; /* 写缓冲区大小 */
/* OOB(Out-Of-Band)相关(NAND Flash 专用)*/
uint32_t oobsize; /* OOB 区域大小 */
uint32_t oobavail; /* 可用 OOB 字节数 */
/* 设备名称 */
const char *name;
/* 设备编号 */
int index;
/* 操作函数 */
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);
/* 坏块管理(NAND Flash)*/
int (*block_isbad)(struct mtd_info *mtd, loff_t ofs);
int (*block_markbad)(struct mtd_info *mtd, loff_t ofs);
/* 同步 */
void (*sync)(struct mtd_info *mtd);
/* 私有数据 */
void *priv;
struct device dev;
int usecount;
};
/*
* erase_info:擦除操作描述符
*/
struct erase_info {
struct mtd_info *mtd;
uint64_t addr; /* 擦除起始地址 */
uint64_t len; /* 擦除长度 */
uint64_t fail_addr; /* 失败地址(擦除失败时)*/
u_long time; /* 超时时间 */
u_long retries; /* 重试次数 */
unsigned dev;
unsigned cell;
void (*callback)(struct erase_info *self); /* 完成回调 */
u_long priv;
u_char state; /* 擦除状态 */
struct erase_info *next;
};
19.1.4 MTD 核心 API
c
/* ── 注册/注销 MTD 设备 ──────────────────────────────────────── */
/* 注册 MTD 设备 */
int add_mtd_device(struct mtd_info *mtd);
/* 注销 MTD 设备 */
int del_mtd_device(struct mtd_info *mtd);
/* ── MTD 操作 API(供上层使用)──────────────────────────────── */
/* 擦除 */
static inline int mtd_erase(struct mtd_info *mtd, struct erase_info *instr)
{
return mtd->erase(mtd, instr);
}
/* 读取 */
static inline int mtd_read(struct mtd_info *mtd, loff_t from, size_t len,
size_t *retlen, u_char *buf)
{
return mtd->read(mtd, from, len, retlen, buf);
}
/* 写入 */
static inline int mtd_write(struct mtd_info *mtd, loff_t to, size_t len,
size_t *retlen, const u_char *buf)
{
return mtd->write(mtd, to, len, retlen, buf);
}
/* 检查坏块 */
static inline int mtd_block_isbad(struct mtd_info *mtd, loff_t ofs)
{
return mtd->block_isbad ? mtd->block_isbad(mtd, ofs) : 0;
}
/* 标记坏块 */
static inline int mtd_block_markbad(struct mtd_info *mtd, loff_t ofs)
{
return mtd->block_markbad(mtd, ofs);
}
19.1.5 MTD 分区
MTD 支持将一个 Flash 芯片划分为多个分区,每个分区对应一个独立的 MTD 设备:
c
#include <linux/mtd/partitions.h>
/*
* mtd_partition:MTD 分区描述
*/
struct mtd_partition {
const char *name; /* 分区名称 */
uint64_t size; /* 分区大小(字节)*/
uint64_t offset; /* 分区起始偏移 */
uint32_t mask_flags; /* 标志掩码 */
struct nand_ecclayout *ecclayout; /* ECC 布局(NAND)*/
};
/* 典型的 Flash 分区方案(以 128MB NAND Flash 为例)*/
static struct mtd_partition nand_partitions[] = {
{
.name = "bootloader", /* U-Boot */
.offset = 0,
.size = 1 * 1024 * 1024, /* 1MB */
},
{
.name = "bootloader-env", /* U-Boot 环境变量 */
.offset = MTDPART_OFS_APPEND, /* 紧接上一分区 */
.size = 1 * 1024 * 1024, /* 1MB */
},
{
.name = "kernel", /* Linux 内核 */
.offset = MTDPART_OFS_APPEND,
.size = 8 * 1024 * 1024, /* 8MB */
},
{
.name = "rootfs", /* 根文件系统 */
.offset = MTDPART_OFS_APPEND,
.size = MTDPART_SIZ_FULL, /* 剩余全部空间 */
},
};
/* 注册分区 */
int mtd_device_register(struct mtd_info *master,
const struct mtd_partition *parts,
int nr_parts);
设备树中的 MTD 分区配置:
dts
/* 设备树中的 NAND Flash 分区 */
&gpmi {
status = "okay";
partitions {
compatible = "fixed-partitions";
#address-cells = <1>;
#size-cells = <1>;
partition@0 {
label = "bootloader";
reg = <0x000000 0x100000>; /* 0 ~ 1MB */
};
partition@100000 {
label = "kernel";
reg = <0x100000 0x800000>; /* 1MB ~ 9MB */
};
partition@900000 {
label = "rootfs";
reg = <0x900000 0x7700000>; /* 9MB ~ 128MB */
};
};
};
19.2 NOR Flash 驱动
19.2.1 NOR Flash 特性
NOR Flash 特性:
接口:并行地址/数据总线(类似 SRAM)
访问:支持字节寻址,可直接执行代码(XIP,eXecute In Place)
擦除:按扇区擦除(64KB~128KB)
读取:快速,随机访问
写入:较慢,按字节或字写入
容量:通常 1MB~128MB
典型芯片:
Spansion S29GL256N(256Mbit,并行接口)
Micron M25P80(8Mbit,SPI 接口)
Winbond W25Q64(64Mbit,SPI 接口)
NOR Flash 命令集:
CFI(Common Flash Interface):Intel/AMD 标准
JEDEC:早期标准
SPI NOR:通过 SPI 接口访问的 NOR Flash
19.2.2 CFI NOR Flash 驱动
Linux 内核提供了通用的 CFI NOR Flash 驱动(drivers/mtd/chips/cfi_cmdset_0001.c 等),大多数并行 NOR Flash 无需编写专用驱动:
c
/*
* 使用 CFI 驱动的 NOR Flash 配置(通过 platform_data)
*/
#include <linux/mtd/physmap.h>
/* 平台数据:描述 NOR Flash 的物理连接 */
static struct physmap_flash_data nor_flash_data = {
.width = 2, /* 数据总线宽度(字节):1/2/4 */
.parts = nor_partitions,
.nr_parts = ARRAY_SIZE(nor_partitions),
};
/* NOR Flash 的内存资源(物理地址)*/
static struct resource nor_flash_resource = {
.start = 0x08000000, /* NOR Flash 物理基地址 */
.end = 0x09FFFFFF, /* 32MB */
.flags = IORESOURCE_MEM,
};
/* 平台设备 */
static struct platform_device nor_flash_device = {
.name = "physmap-flash",
.id = 0,
.dev = {
.platform_data = &nor_flash_data,
},
.num_resources = 1,
.resource = &nor_flash_resource,
};
19.2.3 SPI NOR Flash 驱动
SPI NOR Flash 通过 SPI 接口访问,Linux 提供了 m25p80 驱动(现已整合到 spi-nor 框架):
c
/*
* spi_nor_simple.c ------ SPI NOR Flash 驱动框架
* 以 W25Q64(64Mbit = 8MB)为例
*/
#include <linux/module.h>
#include <linux/spi/spi.h>
#include <linux/mtd/mtd.h>
#include <linux/mtd/spi-nor.h>
/* W25Q64 命令集 */
#define W25Q_WRITE_ENABLE 0x06 /* 写使能 */
#define W25Q_WRITE_DISABLE 0x04 /* 写禁止 */
#define W25Q_READ_STATUS1 0x05 /* 读状态寄存器1 */
#define W25Q_READ_STATUS2 0x35 /* 读状态寄存器2 */
#define W25Q_WRITE_STATUS 0x01 /* 写状态寄存器 */
#define W25Q_READ_DATA 0x03 /* 读数据 */
#define W25Q_FAST_READ 0x0B /* 快速读 */
#define W25Q_PAGE_PROGRAM 0x02 /* 页编程(写入)*/
#define W25Q_SECTOR_ERASE 0x20 /* 扇区擦除(4KB)*/
#define W25Q_BLOCK_ERASE32 0x52 /* 块擦除(32KB)*/
#define W25Q_BLOCK_ERASE64 0xD8 /* 块擦除(64KB)*/
#define W25Q_CHIP_ERASE 0xC7 /* 全片擦除 */
#define W25Q_READ_JEDEC_ID 0x9F /* 读 JEDEC ID */
#define W25Q_POWER_DOWN 0xB9 /* 掉电 */
#define W25Q_RELEASE_PD 0xAB /* 释放掉电 */
/* 状态寄存器位 */
#define W25Q_STATUS_BUSY BIT(0) /* 忙标志 */
#define W25Q_STATUS_WEL BIT(1) /* 写使能锁存 */
/* 驱动私有数据 */
struct w25q_flash {
struct spi_device *spi;
struct mtd_info mtd;
struct mutex lock;
u32 jedec_id;
u32 page_size; /* 页大小(256字节)*/
u32 sector_size; /* 扇区大小(4KB)*/
u32 n_sectors; /* 扇区总数 */
};
/* ── SPI 传输辅助函数 ──────────────────────────────────────── */
static int w25q_spi_write_then_read(struct w25q_flash *flash,
const u8 *txbuf, unsigned txlen,
u8 *rxbuf, unsigned rxlen)
{
return spi_write_then_read(flash->spi, txbuf, txlen, rxbuf, rxlen);
}
/* 读取状态寄存器 */
static u8 w25q_read_status(struct w25q_flash *flash)
{
u8 cmd = W25Q_READ_STATUS1;
u8 status;
w25q_spi_write_then_read(flash, &cmd, 1, &status, 1);
return status;
}
/* 等待操作完成(轮询 BUSY 位)*/
static int w25q_wait_ready(struct w25q_flash *flash, unsigned long timeout_ms)
{
unsigned long deadline = jiffies + msecs_to_jiffies(timeout_ms);
do {
if (!(w25q_read_status(flash) & W25Q_STATUS_BUSY))
return 0;
cond_resched();
} while (time_before(jiffies, deadline));
return -ETIMEDOUT;
}
/* 发送写使能命令 */
static int w25q_write_enable(struct w25q_flash *flash)
{
u8 cmd = W25Q_WRITE_ENABLE;
return spi_write(flash->spi, &cmd, 1);
}
/* ── 读取 JEDEC ID ──────────────────────────────────────────── */
static int w25q_read_jedec_id(struct w25q_flash *flash)
{
u8 cmd = W25Q_READ_JEDEC_ID;
u8 id[3];
int ret;
ret = w25q_spi_write_then_read(flash, &cmd, 1, id, 3);
if (ret)
return ret;
flash->jedec_id = (id[0] << 16) | (id[1] << 8) | id[2];
pr_info("w25q: JEDEC ID = 0x%06X\n", flash->jedec_id);
/* W25Q64 的 JEDEC ID = 0xEF4017 */
/* EF = Winbond, 40 = SPI Flash, 17 = 64Mbit */
return 0;
}
/* ── MTD 操作函数实现 ──────────────────────────────────────── */
/* 读取数据 */
static int w25q_mtd_read(struct mtd_info *mtd, loff_t from, size_t len,
size_t *retlen, u_char *buf)
{
struct w25q_flash *flash = mtd->priv;
u8 cmd[5];
int ret;
if (from + len > mtd->size)
return -EINVAL;
mutex_lock(&flash->lock);
/* 构造读命令:0x03 + 3字节地址 */
cmd[0] = W25Q_READ_DATA;
cmd[1] = (from >> 16) & 0xFF;
cmd[2] = (from >> 8) & 0xFF;
cmd[3] = (from >> 0) & 0xFF;
ret = w25q_spi_write_then_read(flash, cmd, 4, buf, len);
if (ret == 0)
*retlen = len;
mutex_unlock(&flash->lock);
return ret;
}
/* 擦除扇区 */
static int w25q_mtd_erase(struct mtd_info *mtd, struct erase_info *instr)
{
struct w25q_flash *flash = mtd->priv;
u32 addr = instr->addr;
u32 len = instr->len;
u8 cmd[4];
int ret;
/* 检查地址和长度是否对齐 */
if (addr % mtd->erasesize || len % mtd->erasesize)
return -EINVAL;
mutex_lock(&flash->lock);
while (len > 0) {
/* 写使能 */
ret = w25q_write_enable(flash);
if (ret)
goto err;
/* 发送扇区擦除命令(4KB)*/
cmd[0] = W25Q_SECTOR_ERASE;
cmd[1] = (addr >> 16) & 0xFF;
cmd[2] = (addr >> 8) & 0xFF;
cmd[3] = (addr >> 0) & 0xFF;
ret = spi_write(flash->spi, cmd, 4);
if (ret)
goto err;
/* 等待擦除完成(最长 400ms)*/
ret = w25q_wait_ready(flash, 400);
if (ret) {
pr_err("w25q: 擦除超时,地址 0x%08x\n", addr);
instr->fail_addr = addr;
goto err;
}
addr += mtd->erasesize;
len -= mtd->erasesize;
}
instr->state = MTD_ERASE_DONE;
mutex_unlock(&flash->lock);
mtd_erase_callback(instr);
return 0;
err:
instr->state = MTD_ERASE_FAILED;
mutex_unlock(&flash->lock);
return ret;
}
/* 写入数据(按页写入,每页 256 字节)*/
static int w25q_mtd_write(struct mtd_info *mtd, loff_t to, size_t len,
size_t *retlen, const u_char *buf)
{
struct w25q_flash *flash = mtd->priv;
u32 page_offset, page_size;
u8 cmd[4];
int ret = 0;
if (to + len > mtd->size)
return -EINVAL;
mutex_lock(&flash->lock);
*retlen = 0;
while (len > 0) {
/* 计算当前页内的偏移和可写字节数 */
page_offset = (u32)to % flash->page_size;
page_size = min_t(u32, len, flash->page_size - page_offset);
/* 写使能 */
ret = w25q_write_enable(flash);
if (ret)
break;
/* 发送页编程命令 */
cmd[0] = W25Q_PAGE_PROGRAM;
cmd[1] = (to >> 16) & 0xFF;
cmd[2] = (to >> 8) & 0xFF;
cmd[3] = (to >> 0) & 0xFF;
/* 发送命令 + 数据 */
{
struct spi_transfer xfers[2] = {
{ .tx_buf = cmd, .len = 4 },
{ .tx_buf = buf, .len = page_size },
};
struct spi_message msg;
spi_message_init(&msg);
spi_message_add_tail(&xfers[0], &msg);
spi_message_add_tail(&xfers[1], &msg);
ret = spi_sync(flash->spi, &msg);
}
if (ret)
break;
/* 等待写入完成(最长 3ms)*/
ret = w25q_wait_ready(flash, 3);
if (ret)
break;
to += page_size;
buf += page_size;
len -= page_size;
*retlen += page_size;
}
mutex_unlock(&flash->lock);
return ret;
}
/* ── probe 函数 ──────────────────────────────────────────────── */
static int w25q_probe(struct spi_device *spi)
{
struct w25q_flash *flash;
int ret;
flash = devm_kzalloc(&spi->dev, sizeof(*flash), GFP_KERNEL);
if (!flash)
return -ENOMEM;
flash->spi = spi;
mutex_init(&flash->lock);
/* 配置 SPI 参数 */
spi->max_speed_hz = 50000000; /* 50MHz */
spi->mode = SPI_MODE_0;
spi->bits_per_word = 8;
spi_setup(spi);
/* 读取 JEDEC ID,验证芯片 */
ret = w25q_read_jedec_id(flash);
if (ret)
return ret;
/* 根据 JEDEC ID 确定芯片参数 */
switch (flash->jedec_id) {
case 0xEF4017: /* W25Q64 */
flash->page_size = 256;
flash->sector_size = 4096;
flash->n_sectors = 2048; /* 8MB / 4KB = 2048 扇区 */
break;
case 0xEF4018: /* W25Q128 */
flash->page_size = 256;
flash->sector_size = 4096;
flash->n_sectors = 4096; /* 16MB / 4KB = 4096 扇区 */
break;
default:
dev_err(&spi->dev, "不支持的 Flash 芯片:0x%06X\n",
flash->jedec_id);
return -ENODEV;
}
/* 初始化 mtd_info */
flash->mtd.name = "w25q-nor";
flash->mtd.type = MTD_NORFLASH;
flash->mtd.flags = MTD_CAP_NORFLASH;
flash->mtd.size = (u64)flash->sector_size * flash->n_sectors;
flash->mtd.erasesize = flash->sector_size;
flash->mtd.writesize = 1; /* NOR Flash 支持字节写入 */
flash->mtd.writebufsize = flash->page_size;
flash->mtd.erase = w25q_mtd_erase;
flash->mtd.read = w25q_mtd_read;
flash->mtd.write = w25q_mtd_write;
flash->mtd.priv = flash;
flash->mtd.dev.parent = &spi->dev;
spi_set_drvdata(spi, flash);
/* 注册 MTD 设备 */
ret = mtd_device_register(&flash->mtd, NULL, 0);
if (ret) {
dev_err(&spi->dev, "注册 MTD 设备失败:%d\n", ret);
return ret;
}
dev_info(&spi->dev, "W25Q NOR Flash 初始化成功,大小 %llu MB\n",
flash->mtd.size / 1024 / 1024);
return 0;
}
static int w25q_remove(struct spi_device *spi)
{
struct w25q_flash *flash = spi_get_drvdata(spi);
mtd_device_unregister(&flash->mtd);
return 0;
}
static const struct spi_device_id w25q_id[] = {
{ "w25q64", 0 },
{ "w25q128", 0 },
{}
};
MODULE_DEVICE_TABLE(spi, w25q_id);
static const struct of_device_id w25q_of_match[] = {
{ .compatible = "winbond,w25q64" },
{ .compatible = "winbond,w25q128" },
{}
};
MODULE_DEVICE_TABLE(of, w25q_of_match);
static struct spi_driver w25q_driver = {
.driver = {
.name = "w25q",
.of_match_table = w25q_of_match,
},
.probe = w25q_probe,
.remove = w25q_remove,
.id_table = w25q_id,
};
module_spi_driver(w25q_driver);
MODULE_LICENSE("GPL v2");
MODULE_DESCRIPTION("W25Q SPI NOR Flash 驱动");
19.3 NAND Flash 驱动
19.3.1 NAND Flash 特性
NAND Flash 特性:
接口:串行 I/O 总线(8位或16位数据总线)
访问:只能按页读写(512B/2KB/4KB/8KB),按块擦除(64页/128页)
容量:通常 128MB~TB 级
速度:读写速度比 NOR Flash 快
价格:比 NOR Flash 便宜
坏块:出厂有坏块,使用中产生新坏块
NAND Flash 组织结构(以 2KB 页为例):
┌─────────────────────────────────────────────────────────────┐
│ 芯片 │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 块 0 │ │
│ │ ┌──────────────────────────────────────────────┐ │ │
│ │ │ 页 0(2048B 数据 + 64B OOB) │ │ │
│ │ │ 页 1(2048B 数据 + 64B OOB) │ │ │
│ │ │ ...(共 64 页) │ │ │
│ │ └──────────────────────────────────────────────┘ │ │
│ └─────────────────────────────────────────────────────┘ │
│ 块 1、块 2 ... 块 N(共 N 个块) │
└─────────────────────────────────────────────────────────────┘
OOB(Out-Of-Band)区域用途:
- ECC 校验数据(纠正位翻转错误)
- 坏块标记(Bad Block Marker)
- 文件系统元数据(JFFS2/UBIFS)
19.3.2 NAND Flash 驱动框架
Linux 提供了 NAND 子系统(drivers/mtd/nand/),驱动只需实现底层硬件操作:
c
#include <linux/mtd/nand.h>
/*
* nand_chip:NAND Flash 芯片描述符
* 包含 NAND 操作函数和芯片参数
*/
struct nand_chip {
void __iomem *IO_ADDR_R; /* 读数据寄存器地址 */
void __iomem *IO_ADDR_W; /* 写数据寄存器地址 */
/* 底层硬件操作函数(驱动必须实现)*/
void (*cmd_ctrl)(struct mtd_info *mtd, int dat, unsigned int ctrl);
int (*dev_ready)(struct mtd_info *mtd);
void (*select_chip)(struct mtd_info *mtd, int chip);
void (*write_buf)(struct mtd_info *mtd, const uint8_t *buf, int len);
void (*read_buf)(struct mtd_info *mtd, uint8_t *buf, int len);
/* ECC 配置 */
struct nand_ecc_ctrl ecc;
/* 芯片参数(由 NAND 子系统自动检测)*/
int page_shift; /* 页大小的移位数 */
int phys_erase_shift; /* 物理擦除块大小的移位数 */
int chip_shift; /* 芯片大小的移位数 */
int numchips; /* 芯片数量 */
uint64_t chipsize; /* 单片大小 */
int pagemask; /* 页掩码 */
int pagebuf; /* 当前缓存的页号 */
/* 坏块管理 */
int (*block_bad)(struct mtd_info *mtd, loff_t ofs, int getchip);
int (*block_markbad)(struct mtd_info *mtd, loff_t ofs);
/* 私有数据 */
void *priv;
};
/*
* NAND 控制信号(cmd_ctrl 的 ctrl 参数)
*/
#define NAND_NCE 0x01 /* 片选(低有效)*/
#define NAND_CLE 0x02 /* 命令锁存使能 */
#define NAND_ALE 0x04 /* 地址锁存使能 */
#define NAND_CTRL_CLE (NAND_NCE | NAND_CLE)
#define NAND_CTRL_ALE (NAND_NCE | NAND_ALE)
#define NAND_CTRL_CHANGE 0x80
19.3.3 完整的 NAND Flash 控制器驱动
c
/*
* my_nand.c ------ NAND Flash 控制器驱动
* 以简化的 NAND 控制器为例
*/
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/mtd/mtd.h>
#include <linux/mtd/nand.h>
#include <linux/mtd/partitions.h>
#include <linux/clk.h>
#include <linux/io.h>
#include <linux/dma-mapping.h>
/* NAND 控制器寄存器 */
#define NAND_CMD_REG 0x00 /* 命令寄存器 */
#define NAND_ADDR_REG 0x04 /* 地址寄存器 */
#define NAND_DATA_REG 0x08 /* 数据寄存器 */
#define NAND_STATUS_REG 0x0C /* 状态寄存器 */
#define NAND_CTRL_REG 0x10 /* 控制寄存器 */
#define NAND_ECC_REG 0x14 /* ECC 寄存器 */
/* 状态寄存器位 */
#define NAND_STATUS_BUSY BIT(0) /* 控制器忙 */
#define NAND_STATUS_RB BIT(1) /* NAND R/B# 引脚状态 */
/* 控制寄存器位 */
#define NAND_CTRL_CE BIT(0) /* 片选 */
#define NAND_CTRL_CLE BIT(1) /* 命令锁存使能 */
#define NAND_CTRL_ALE BIT(2) /* 地址锁存使能 */
#define NAND_CTRL_WP BIT(3) /* 写保护 */
/* NAND Flash 分区 */
static struct mtd_partition my_nand_partitions[] = {
{
.name = "bootloader",
.offset = 0,
.size = 2 * 1024 * 1024, /* 2MB */
},
{
.name = "kernel",
.offset = MTDPART_OFS_APPEND,
.size = 8 * 1024 * 1024, /* 8MB */
},
{
.name = "rootfs",
.offset = MTDPART_OFS_APPEND,
.size = MTDPART_SIZ_FULL, /* 剩余全部 */
},
};
/* 驱动私有数据 */
struct my_nand_host {
struct nand_chip chip; /* 内嵌 nand_chip(必须是第一个成员)*/
struct mtd_info mtd; /* MTD 设备 */
void __iomem *base; /* 寄存器基地址 */
struct clk *clk; /* 时钟 */
int irq; /* 中断号 */
struct completion op_done; /* 操作完成信号 */
};
/* ── 底层硬件操作函数 ──────────────────────────────────────── */
/*
* cmd_ctrl:发送命令或地址
* dat:命令/地址字节(-1 表示无数据)
* ctrl:控制信号(NAND_CLE/NAND_ALE/NAND_NCE)
*/
static void my_nand_cmd_ctrl(struct mtd_info *mtd, int dat,
unsigned int ctrl)
{
struct nand_chip *chip = mtd->priv;
struct my_nand_host *host = container_of(chip, struct my_nand_host, chip);
u32 reg_ctrl = 0;
/* 设置控制信号 */
if (ctrl & NAND_CLE)
reg_ctrl |= NAND_CTRL_CLE;
if (ctrl & NAND_ALE)
reg_ctrl |= NAND_CTRL_ALE;
if (ctrl & NAND_NCE)
reg_ctrl |= NAND_CTRL_CE;
writel(reg_ctrl, host->base + NAND_CTRL_REG);
/* 写入命令或地址 */
if (dat != NAND_CMD_NONE)
writeb(dat, host->base + NAND_DATA_REG);
}
/*
* dev_ready:检查 NAND Flash 是否就绪
* 返回:1(就绪),0(忙)
*/
static int my_nand_dev_ready(struct mtd_info *mtd)
{
struct nand_chip *chip = mtd->priv;
struct my_nand_host *host = container_of(chip, struct my_nand_host, chip);
return (readl(host->base + NAND_STATUS_REG) & NAND_STATUS_RB) ? 1 : 0;
}
/*
* read_buf:从 NAND Flash 读取数据
*/
static void my_nand_read_buf(struct mtd_info *mtd, uint8_t *buf, int len)
{
struct nand_chip *chip = mtd->priv;
struct my_nand_host *host = container_of(chip, struct my_nand_host, chip);
int i;
for (i = 0; i < len; i++)
buf[i] = readb(host->base + NAND_DATA_REG);
}
/*
* write_buf:向 NAND Flash 写入数据
*/
static void my_nand_write_buf(struct mtd_info *mtd, const uint8_t *buf, int len)
{
struct nand_chip *chip = mtd->priv;
struct my_nand_host *host = container_of(chip, struct my_nand_host, chip);
int i;
for (i = 0; i < len; i++)
writeb(buf[i], host->base + NAND_DATA_REG);
}
/* ── 中断处理(操作完成)──────────────────────────────────── */
static irqreturn_t my_nand_irq_handler(int irq, void *dev_id)
{
struct my_nand_host *host = dev_id;
/* 清除中断 */
writel(0x01, host->base + NAND_STATUS_REG);
/* 通知等待的操作 */
complete(&host->op_done);
return IRQ_HANDLED;
}
/* ── probe 函数 ──────────────────────────────────────────────── */
static int my_nand_probe(struct platform_device *pdev)
{
struct my_nand_host *host;
struct nand_chip *chip;
struct mtd_info *mtd;
struct resource *res;
int ret;
/* 分配私有数据 */
host = devm_kzalloc(&pdev->dev, sizeof(*host), GFP_KERNEL);
if (!host)
return -ENOMEM;
init_completion(&host->op_done);
/* 获取寄存器资源 */
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
host->base = devm_ioremap_resource(&pdev->dev, res);
if (IS_ERR(host->base))
return PTR_ERR(host->base);
/* 获取中断号 */
host->irq = platform_get_irq(pdev, 0);
if (host->irq < 0)
return host->irq;
/* 申请中断 */
ret = devm_request_irq(&pdev->dev, host->irq,
my_nand_irq_handler, 0, "my-nand", host);
if (ret)
return ret;
/* 获取并使能时钟 */
host->clk = devm_clk_get(&pdev->dev, NULL);
if (!IS_ERR(host->clk))
clk_prepare_enable(host->clk);
/* 初始化 nand_chip */
chip = &host->chip;
chip->cmd_ctrl = my_nand_cmd_ctrl;
chip->dev_ready = my_nand_dev_ready;
chip->read_buf = my_nand_read_buf;
chip->write_buf = my_nand_write_buf;
/* 设置 ECC 模式 */
chip->ecc.mode = NAND_ECC_SOFT; /* 软件 ECC */
chip->ecc.algo = NAND_ECC_HAMMING; /* Hamming 码 */
/* 或使用硬件 ECC */
/* chip->ecc.mode = NAND_ECC_HW; */
/* 设置 IO 地址(用于默认的读写函数)*/
chip->IO_ADDR_R = host->base + NAND_DATA_REG;
chip->IO_ADDR_W = host->base + NAND_DATA_REG;
/* 初始化 mtd_info */
mtd = &host->mtd;
mtd->priv = chip;
mtd->name = "my-nand";
mtd->owner = THIS_MODULE;
mtd->dev.parent = &pdev->dev;
/* 扫描 NAND Flash(自动检测芯片参数)*/
ret = nand_scan(mtd, 1); /* 1 = 芯片数量 */
if (ret) {
dev_err(&pdev->dev, "NAND 扫描失败:%d\n", ret);
goto err_clk;
}
/* 注册 MTD 设备(含分区)*/
ret = mtd_device_register(mtd, my_nand_partitions,
ARRAY_SIZE(my_nand_partitions));
if (ret) {
dev_err(&pdev->dev, "注册 MTD 设备失败:%d\n", ret);
goto err_nand;
}
platform_set_drvdata(pdev, host);
dev_info(&pdev->dev, "NAND Flash 初始化成功\n");
dev_info(&pdev->dev, " 页大小:%u 字节\n", mtd->writesize);
dev_info(&pdev->dev, " 块大小:%u 字节\n", mtd->erasesize);
dev_info(&pdev->dev, " 总大小:%llu MB\n", mtd->size / 1024 / 1024);
return 0;
err_nand:
nand_release(mtd);
err_clk:
if (!IS_ERR(host->clk))
clk_disable_unprepare(host->clk);
return ret;
}
static int my_nand_remove(struct platform_device *pdev)
{
struct my_nand_host *host = platform_get_drvdata(pdev);
mtd_device_unregister(&host->mtd);
nand_release(&host->mtd);
if (!IS_ERR(host->clk))
clk_disable_unprepare(host->clk);
return 0;
}
static const struct of_device_id my_nand_of_match[] = {
{ .compatible = "myvendor,my-nand" },
{}
};
MODULE_DEVICE_TABLE(of, my_nand_of_match);
static struct platform_driver my_nand_driver = {
.probe = my_nand_probe,
.remove = my_nand_remove,
.driver = {
.name = "my-nand",
.of_match_table = my_nand_of_match,
},
};
module_platform_driver(my_nand_driver);
MODULE_LICENSE("GPL v2");
MODULE_DESCRIPTION("My NAND Flash 控制器驱动");
19.3.4 NAND Flash 的 ECC
ECC(Error Correction Code,错误纠正码)是 NAND Flash 驱动的关键功能:
ECC 的必要性:
NAND Flash 的位翻转问题:
- 由于物理特性,NAND Flash 读取时可能出现位翻转
- 1 变成 0,或 0 变成 1
- 需要 ECC 来检测和纠正这些错误
ECC 类型:
1. Hamming 码(1位纠错,2位检错)
每 256 字节数据需要 3 字节 ECC
适合 SLC NAND Flash
2. BCH 码(多位纠错)
每 512 字节数据可纠正 4/8/16/24 位错误
适合 MLC/TLC NAND Flash
3. RS 码(Reed-Solomon)
用于 NAND Flash 的另一种 ECC 算法
ECC 存储位置:
ECC 数据存储在 OOB(Out-Of-Band)区域
每页 2KB 数据 + 64B OOB
OOB 中存储:ECC 数据 + 坏块标记 + 文件系统元数据
Linux NAND ECC 模式:
NAND_ECC_NONE:不使用 ECC(不推荐)
NAND_ECC_SOFT:软件 ECC(CPU 计算)
NAND_ECC_HW:硬件 ECC(NAND 控制器计算,性能好)
NAND_ECC_HW_SYNDROME:硬件 ECC(syndrome 模式)
NAND_ECC_ON_DIE:片上 ECC(NAND 芯片内部计算)
19.4 MTD 字符设备与块设备
19.4.1 MTD 字符设备(/dev/mtdN)
MTD 字符设备提供对 Flash 的直接访问接口,支持擦除、读写等操作:
bash
# 查看 MTD 设备
cat /proc/mtd
# dev: size erasesize name
# mtd0: 00100000 00010000 "bootloader" ← 1MB,64KB 擦除块
# mtd1: 00800000 00020000 "kernel" ← 8MB,128KB 擦除块
# mtd2: 07700000 00020000 "rootfs" ← 119MB
# 查看 MTD 设备文件
ls /dev/mtd*
# /dev/mtd0 ← 字符设备(可读写擦除)
# /dev/mtd0ro ← 只读字符设备
# /dev/mtdblock0 ← 块设备(可挂载文件系统)
# 使用 mtd-utils 工具操作 Flash
# 安装:apt-get install mtd-utils
# 擦除 Flash 分区
flash_erase /dev/mtd1 0 0 # 擦除整个 mtd1 分区(0=从头,0=全部)
# 写入内核镜像
nandwrite -p /dev/mtd1 zImage # -p:跳过坏块
# 读取 Flash 内容
nanddump /dev/mtd1 -f kernel.bin # 读取 mtd1 到文件
# 擦除并写入(合并操作)
flash_erase /dev/mtd2 0 0
nandwrite -p /dev/mtd2 rootfs.ubifs
# 查看坏块
nandtest -m /dev/mtd2 # 测试并标记坏块
19.4.2 MTD 字符设备的 ioctl
c
#include <mtd/mtd-user.h>
/* MTD 字符设备的 ioctl 命令 */
/* 获取 MTD 设备信息 */
struct mtd_info_user info;
ioctl(fd, MEMGETINFO, &info);
printf("大小:%u,擦除块:%u,写入单位:%u\n",
info.size, info.erasesize, info.writesize);
/* 擦除 Flash */
struct erase_info_user erase;
erase.start = 0; /* 擦除起始地址 */
erase.length = info.erasesize; /* 擦除一个块 */
ioctl(fd, MEMERASE, &erase);
/* 获取坏块信息 */
loff_t offset = 0;
int ret = ioctl(fd, MEMGETBADBLOCK, &offset);
if (ret == 1)
printf("偏移 0x%llx 是坏块\n", offset);
/* 标记坏块 */
loff_t bad_offset = 0x100000;
ioctl(fd, MEMSETBADBLOCK, &bad_offset);
/* 锁定/解锁 Flash */
struct erase_info_user lock;
lock.start = 0;
lock.length = info.size;
ioctl(fd, MEMLOCK, &lock); /* 锁定 */
ioctl(fd, MEMUNLOCK, &lock); /* 解锁 */
19.4.3 MTD 块设备(/dev/mtdblockN)
MTD 块设备将 Flash 模拟为普通块设备,可以挂载文件系统:
bash
# MTD 块设备的使用
# 格式化为 FAT 文件系统(NOR Flash,不推荐用于 NAND)
mkdosfs /dev/mtdblock0
# 挂载 JFFS2 文件系统(适合 NOR Flash)
mount -t jffs2 /dev/mtdblock2 /mnt/flash
# 挂载 UBIFS 文件系统(适合 NAND Flash)
# 需要先通过 UBI 层
ubiformat /dev/mtd2
ubiattach /dev/ubi_ctrl -m 2
ubimkvol /dev/ubi0 -N rootfs -m
mount -t ubifs ubi0:rootfs /mnt/rootfs
# 挂载 SquashFS(只读,适合根文件系统)
mount -t squashfs /dev/mtdblock2 /mnt/rootfs -o ro
19.4.4 MTD 与文件系统的关系
MTD 文件系统选择指南:
NOR Flash:
JFFS2(Journalling Flash File System v2)
- 日志文件系统,掉电安全
- 支持压缩
- 适合小容量(< 32MB)
- 挂载时间较长(需要扫描整个 Flash)
NAND Flash:
UBIFS(Unsorted Block Image File System)
- 基于 UBI(Unsorted Block Images)层
- UBI 提供磨损均衡和坏块管理
- 性能好,适合大容量
- 推荐用于 NAND Flash
YAFFS2(Yet Another Flash File System 2)
- 专为 NAND Flash 设计
- 不需要 UBI 层
- Android 早期使用
SquashFS(只读)
- 高压缩比
- 适合只读根文件系统
- 通常与 UBIFS 配合使用
文件系统层次(NAND Flash):
应用程序
↓
VFS(虚拟文件系统)
↓
UBIFS(文件系统)
↓
UBI(磨损均衡层)
↓
MTD(Flash 抽象层)
↓
NAND 控制器驱动
↓
NAND Flash 硬件
19.4.5 MTD 驱动的完整测试
bash
# 加载 NAND Flash 驱动
sudo insmod my_nand.ko
# 查看 MTD 设备
cat /proc/mtd
# dev: size erasesize name
# mtd0: 00200000 00020000 "bootloader"
# mtd1: 00800000 00020000 "kernel"
# mtd2: 07600000 00020000 "rootfs"
# 查看设备文件
ls /dev/mtd*
# /dev/mtd0 /dev/mtd0ro /dev/mtdblock0
# /dev/mtd1 /dev/mtd1ro /dev/mtdblock1
# /dev/mtd2 /dev/mtd2ro /dev/mtdblock2
# 测试 NOR Flash(W25Q64)
# 擦除第一个扇区(4KB)
flash_erase /dev/mtd0 0 1
# 写入测试数据
echo "Hello MTD!" | dd of=/dev/mtd0 bs=1 count=10
# 读取验证
dd if=/dev/mtd0 bs=1 count=10 | hexdump -C
# 00000000 48 65 6c 6c 6f 20 4d 54 44 21 |Hello MTD!|
# 测试 NAND Flash
# 擦除整个 rootfs 分区
flash_erase /dev/mtd2 0 0
# 写入 UBIFS 镜像
nandwrite -p /dev/mtd2 rootfs.ubifs
# 挂载 UBIFS
ubiattach /dev/ubi_ctrl -m 2
mount -t ubifs ubi0:rootfs /mnt/rootfs
# 查看挂载结果
ls /mnt/rootfs
df -h /mnt/rootfs
# 坏块测试
nandtest /dev/mtd2
# 查看 MTD 统计信息
cat /sys/class/mtd/mtd0/erasesize
cat /sys/class/mtd/mtd0/size
cat /sys/class/mtd/mtd0/writesize
cat /sys/class/mtd/mtd0/name
本章小结
| 章节 | 核心知识点 | 关键 API |
|---|---|---|
| 19.1 MTD设备驱动架构 | Flash特殊性(写前擦除/擦写次数/坏块);MTD体系结构图(用户空间→MTD设备层→MTD核心→硬件驱动);mtd_info/erase_info核心结构体;MTD核心API;MTD分区(mtd_partition/设备树配置) | add_mtd_device()、mtd_device_register()、mtd_erase()、mtd_read()、mtd_write() |
| 19.2 NOR Flash驱动 | NOR Flash特性(XIP/字节寻址/扇区擦除);CFI驱动(physmap-flash);完整W25Q64 SPI NOR驱动(JEDEC ID读取/擦除/读取/页编程/等待就绪);SPI传输实现 | mtd_device_register()、spi_write_then_read()、spi_sync() |
| 19.3 NAND Flash驱动 | NAND Flash特性(页读写/块擦除/坏块/OOB);nand_chip结构体;cmd_ctrl/dev_ready/read_buf/write_buf四个必须实现的函数;完整NAND控制器驱动(含ECC配置/nand_scan/分区注册);ECC类型详解(Hamming/BCH/RS) | nand_scan()、nand_release()、mtd_device_register() |
| 19.4 MTD字符设备与块设备 | /dev/mtdN字符设备;/dev/mtdblockN块设备;mtd-utils工具集(flash_erase/nandwrite/nanddump);MTD ioctl(MEMGETINFO/MEMERASE/MEMGETBADBLOCK);文件系统选择(JFFS2/UBIFS/YAFFS2/SquashFS);完整测试流程 | flash_erase、nandwrite、MEMERASE、MEMGETBADBLOCK |
MTD 驱动开发要点
1. NOR Flash 驱动
通常使用内核提供的 CFI/JEDEC 通用驱动
SPI NOR Flash 使用 spi-nor 框架
只需实现 SPI 传输层
2. NAND Flash 驱动
必须实现:cmd_ctrl、dev_ready、read_buf、write_buf
必须配置 ECC(推荐硬件 ECC)
使用 nand_scan() 自动检测芯片参数
3. ECC 配置
SLC NAND:Hamming 码(1位纠错)
MLC/TLC NAND:BCH 码(多位纠错)
优先使用硬件 ECC(NAND_ECC_HW)
4. 坏块管理
NAND 子系统自动处理坏块
驱动只需实现 block_isbad 和 block_markbad
5. 文件系统选择
NOR Flash → JFFS2
NAND Flash → UBIFS(推荐)或 YAFFS2
只读根文件系统 → SquashFS
6. 调试工具
cat /proc/mtd ← 查看 MTD 设备
flash_erase ← 擦除 Flash
nandwrite/nanddump ← 读写 NAND Flash
nandtest ← 测试坏块
参考文献:宋宝华《Linux设备驱动开发详解:基于最新的Linux 4.0内核》,机械工业出版社,2015年