目录
1 概述
本文主要介绍mtd子系统,包括mtd子系统框架、spi flash、nor flash和nand flash注册流程,mtd子系统注册设备过程、分区建立过程以及应用试过程等。本文不对flash本身的硬件和上层文件系统做深入介绍描述,读者须有一定的flash硬件认识和文件系统知识。
2 mtd子系统框架
MTD(memory technology device内存技术设备)是用于访问memory设备(RAM、ROM、flash)的Linux的子系统。MTD的主要目的是为了使新的memory设备的驱动更加简单,为此它在硬件和上层之间提供了一个抽象的接口。下图大致描述了mtd子系统在Linux中的位置以及层级框架,其中红色部分是本文介绍的内容。
根据flash硬件协议差异,Linux内核实现了nand flash、spi flash和nor flash的mtd硬件驱动,分别向mtd原始设备接口层注册设备和分区。mtd子系统向上提供的设备有字符设备(/dev/mtdx、/dev/mtdxro...)和块设备(/dev/mtdblockx...),其中块设备在注册磁盘时生成,块管理模块向上为IO调度层提供接口。
这里只描述了flash对mtd的使用,实际mtd也能用于rom、ram的映射,向上提供对一个的设备,是一个内存技术设备,本文只讨论flash,其他介质原理一样。
3 flash一些概念理解
本节提出几个影响到对后面理解mtd子系统源码的概念。
(1)flash OOB
Nand Flash每一个页对应一个空闲区域(OOB,Out Of Band),这个区域是基于Nand Flash的硬件特性,数据在读写的时候容易出错,为了保证数据的正确性,就产生了这样一个检测和纠错的区域,用来放置数据的校验值。OOB的读写操作,一般都是随着页的操作一起完成,也就是在读写页的时候,对应的OOB就产生了,比较常见NAND Flash每一页大小为(2048+64)字节(还有其他格式的NANDFlash,比如每页大小为(256+8)、(512+16)、(2048+128)等),其中的2048字节就是一般存储数据的区域,64字节称为OOB区。
(2)ECC
ECC(Error Checking and Correction),是一种用于Nand Flash的差错检测和修正的算法。由于操作的时序和电路稳定性等原因,常常会出现一些bit出错,也就是原来的某个位,本来是0而变成了1,或者本来是1而变成0;当往Nand Flash写入数据时候,每256个字节生成一个ECC校验,针对这些数据会生成一个ECC校验码,然后保存到对应的page的OOB数据区,当读取Nand Flash的数据时候,每256个字节就会生成一个ECC校验,那么对于这些数据就会计算出一个ECC校验码,然后将从OOB中读取存储的ECC校验和计算的ECC校验相比较。
(3)JEDEC:Joint Electron Device Engineering Council,电子电器设备联合会。
(4)CFI:Common Flash Interface,通用Flash接口,Intel发起的一个Flash的接口标准
(5)devicetype:芯片类型,x8、x16或者x32。
4 mtd设备应用层访问
mtd字符设备提供了应用层直接访问flash的方法,通过打开/dev/mtd设备可直接进行数据读写,对一个mtd字符设备操作示例如下:
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <linux/mtd/mtd.h>
#include <linux/mtddll.h>
#define MTD_DEVICE_0 "/dev/mtd0"
#define MTD_DEVICE_1 "/dev/mtd1"
int main() {
int mtd0_fd, mtd1_fd;
struct mtd_info_user mtd_info;
struct erase_info_user erase_info;
ssize_t bytes_read, bytes_written;
char buffer[4096]; // 缓冲区大小
if ((mtd0_fd = open(MTD_DEVICE_0, O_RDONLY)) < 0) {
perror("Error opening MTD device 0");
return EXIT_FAILURE;
}
// 获取MTD设备信息
if (ioctl(mtd0_fd, MEMGETINFO, &mtd_info) < 0) {
perror("Error getting MTD device information");
close(mtd0_fd);
return EXIT_FAILURE;
}
// 打开第二个MTD设备进行写入
if ((mtd1_fd = open(MTD_DEVICE_1, O_WRONLY)) < 0) {
perror("Error opening MTD device 1");
close(mtd0_fd);
return EXIT_FAILURE;
}
// 擦除第二个MTD设备的相关块
for (int i = 0; i < mtd_info.eraseblocks; i++) {
erase_info.start = i * mtd_info.erasesize;
erase_info.length = mtd_info.erasesize;
if (ioctl(mtd1_fd, MEMERASE, &erase_info) < 0) {
perror("Error erasing MTD device 1");
close(mtd0_fd);
close(mtd1_fd);
return EXIT_FAILURE;
}
}
// 从第一个MTD设备读取数据
while ((bytes_read = read(mtd0_fd, buffer, sizeof(buffer))) > 0) {
// 将数据写入第二个MTD设备
bytes_written = write(mtd1_fd, buffer, bytes_read);
if (bytes_written != bytes_read) {
perror("Error writing to MTD device 1");
close(mtd0_fd);
close(mtd1_fd);
return EXIT_FAILURE;
}
}
// 关闭MTD设备文件描述符
close(mtd0_fd);
close(mtd1_fd);
printf("Data transfer from MTD device 0 to MTD device 1 completed successfully.\n");
return EXIT_SUCCESS;
}
对一个块设备的访问示例如下,其中BLKGETSIZE64 用于获取块设备的大小(以字节为单位),返回的值是一个 unsigned long long 类型的整数,表示设备的总容量。BLKGETSIZE 用于获取块设备的大小(以扇区为单位),返回的值是一个 long 类型的整数,表示设备的总扇区数。
#define MTD_BLOCK_DEVICE "/dev/mtdblock1"
int main() {
int fd;
unsigned long long erase_size, total_size;
unsigned int erase_flags = 0; // 通常为0,表示默认擦除选项
// 打开MTD块设备进行读写
if ((fd = open(MTD_BLOCK_DEVICE, O_RDWR)) < 0) {
perror("Error opening MTD block device");
return EXIT_FAILURE;
}
// 获取MTD块设备的擦除大小和总大小
if (ioctl(fd, BLKGETSIZE64, &total_size) != 0) {
perror("Error getting device size");
close(fd);
return EXIT_FAILURE;
}
erase_size = total_size; // 假设整个设备需要擦除
// 擦除MTD块设备
if (ioctl(fd, BLKERASE, &erase_size) != 0) {
perror("Error erasing MTD block device");
close(fd);
return EXIT_FAILURE;
}
// 准备要写入的数据
const char *data_to_write = "mtd block device 1\n";
size_t data_len = strlen(data_to_write) + 1; // 加1是为了包括字符串的终止符'\0'
if (write(fd, data_to_write, data_len) != data_len) {
perror("Error writing to MTD block device");
close(fd);
return EXIT_FAILURE;
}
// 关闭MTD块设备
close(fd);
return EXIT_SUCCESS;
}
5 设备注册过程
无论是spi nor flash、nor flash、nand flash注册过程,最后都将调到mtd子系统core层提供的注册接口mtd_device_parse_register
5.1 spi (nor) flash注册
在mtd子系统中,kernel内核开发人员在drivers/mtd/spi-nor下已经实现好了大部分芯片的spi flash的适配,spi flash开发人员只需要将芯片id和额外的芯片初始化差异定义好即可,这里以华邦flash为例子。
在drivers/mtd/spi-nor/core.c文件中进行spi flash的识别注册开始:
static const struct of_device_id spi_nor_of_table[] = {
{ .compatible = "jedec,spi-nor" },
...
};
MODULE_DEVICE_TABLE(of, spi_nor_of_table);
static struct spi_mem_driver spi_nor_driver = {
.spidrv = {
.driver = {
.name = "spi-nor",
.of_match_table = spi_nor_of_table,
.dev_groups = spi_nor_sysfs_groups,
},
.id_table = spi_nor_dev_ids,
},
.probe = spi_nor_probe,
.remove = spi_nor_remove,
.shutdown = spi_nor_shutdown,
};
static int __init spi_nor_module_init(void)
{
return spi_mem_driver_register(&spi_nor_driver);
}
module_init(spi_nor_module_init);
设备树定义
&spi0 {
status = "okay";
flash@0 {
compatible = "jedec,spi-nor";
reg = <0>;
/* maximum speed for Rockchip SPI */
spi-max-frequency = <50000000>;
};
};
上述过程将flash挂在spi下,spi将为flash节点注册device到spi bus,这里同时注册了name(jedec,spi-nor)一致的driverspi_mem_driver_register,spi子系统将为该driver调用probe函数,并传入device对象(关于spi子系统参考之前写的spi文章)。
在probe函数里,主要进行flash的id读取与匹配,设备的注册等:
static int spi_nor_probe(struct spi_mem *spimem)
{
struct spi_device *spi = spimem->spi;
struct flash_platform_data *data = dev_get_platdata(&spi->dev);
struct spi_nor *nor;
...
nor = devm_kzalloc(&spi->dev, sizeof(*nor), GFP_KERNEL);//申请该硬件驱动层对管理对象
...
nor->spimem = spimem;
nor->dev = &spi->dev;
spi_nor_set_flash_node(nor, spi->dev.of_node);//绑定设备树节点,后面将从该几点取设备树定义信息
spi_mem_set_drvdata(spimem, nor);
if (data && data->name)
nor->mtd.name = data->name;
if (!nor->mtd.name)
nor->mtd.name = spi_mem_get_name(spimem);
...
/*flash_name是后面用来匹配的名称,这里的没有定义platform_data和modalia,为NULL*/
if (data && data->type)
flash_name = data->type;
else if (!strcmp(spi->modalias, "spi-nor"))
flash_name = NULL; /* auto-detect */
else
flash_name = spi->modalias;
//该函数通过spi读取当前flash的id与预定义比较,闭关读取flash其他信息,为注册device生成并初始化一个mtd,后面详细分析
ret = spi_nor_scan(nor, flash_name, &hwcaps);
if (ret)
return ret;
spi_nor_debugfs_register(nor);
...
//提供spi下直接读写映射
ret = spi_nor_create_read_dirmap(nor);
ret = spi_nor_create_write_dirmap(nor);
//调用mtd子系统注册接口注册,后面专门章节分析
return mtd_device_register(&nor->mtd, data ? data->parts : NULL,
data ? data->nr_parts : 0);
}
spi初始化识别过程
int spi_nor_scan(struct spi_nor *nor, const char *name,
const struct spi_nor_hwcaps *hwcaps)
{
const struct flash_info *info;
struct device *dev = nor->dev;
struct mtd_info *mtd = &nor->mtd;
int ret;
int i;
//检查spi读写寄存器硬件接口ÿ