Linux下的mtd子系统与源码分析

目录

1 概述

2 mtd子系统框架

3 flash一些概念理解

4 mtd设备应用层访问

5 设备注册过程

5.1 spi (nor) flash注册

5.2 nor flash(JEDEC/CFI)注册过程

5.3 nand flash注册过程

5.4 mtd子系统注册设备过程

6 /dev/mtdx字符设备的操作流程

6.1 字符设备的open过程

6.2 read 过程

6.3 write过程

7 mtd分区建立使用与解析

7.1 mtd分区的建立

7.1.1 在内核中定义添加分区

7.1.2 使用设备树建立分区

7.1.3 使用uboot传递参数建立分区

7.2 分区建立的过程

7.4 关于文件系统


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读写寄存器硬件接口ÿ
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值