mtd子系统

本文围绕Linux的MTD子系统展开,介绍其为访问memory设备提供的抽象接口,阐述了MTD从设备节点到硬件驱动的四层结构,包括硬件驱动层、原始设备层、设备层和设备节点。还说明了字符设备和块设备的命名规则、mtd工具,以及分层注册函数、调用流程、控制器驱动probe流程等内容。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

原文网址:mtd子系统_IT利刃出鞘的博客-CSDN博客

内核版本:linux-4.9.37

linux的mtd概述

简介

        MTD(memory  technology  device内存技术设备)是用于访问memory设备(ROM、flash)的Linux的子系统。MTD的主要目的是为了使新的memory设备的驱动更加简单,
为此它在硬件和上层之间提供了一个抽象的接口。MTD的所有源代码在/drivers/mtd子目录下。将CFI接口的MTD设备分为四层(从设备节点直到底层硬件驱动),这四层从上到下依次是:设备节点、MTD设备层、MTD原始设备层和硬件驱动层。

                                           

一、Flash硬件驱动层
        硬件驱动层负责在init时驱动Flash硬件,Linux  MTD设备的NOR  Flash芯片驱动遵循CFI接口标准,其驱动程序位于drivers/mtd/chips子目录下。
NAND型Flash的驱动程序则位于/drivers/mtd/nand子目录下。
二、MTD原始设备
        原始设备层有两部分组成,一部分是MTD原始设备的通用代码,另一部分是各个特定的Flash的数据,例如分区。
        用于描述  MTD原始设备的数据结构是  mtd_info,这其中定义了大量的关于  MTD的数据和操作函数。
        mtd_idr(mtdcore.c=>static  DEFINE_IDR(mtd_idr))是所有MTD原始设备的列表,所有mtd设备通过i  =  idr_alloc(&mtd_idr,  mtd,  0,  0,  GFP_KERNEL)等类似操作与mtd_idr建立连接。
        mtd_part(mtdpart.c)是用于表示MTD原始设备分区的结构,其中包含了mtd_info,因为每一个分区都是被看成一个MTD原始设备加在mtd_idr中的,
mtd_part.mtd中的大部分数据都从该分区的主分区mtd_part->master中获得(add_mtd_partitions)。
        在drivers/mtd/maps/子目录下存放的是特定的flash的数据,每一个文件都描述了一块板子上的flash。其中调用add_mtd_device()、del_mtd_device()建立/删除mtd_info结构
并将其加入/删除mtd_idr(或者调用add_mtd_partition()、del_mtd_partition()(mtdpart.c)建立/删除mtd_part结构并将mtd_part.mtd_info加入/删除mtd_idr  中)。
三、MTD设备层
        基于MTD原始设备,linux系统可以定义出MTD的块设备(主设备号31)和字符设备(主设备号90)。
        MTD字符设备的定义在mtdchar.c中实现,注册一系列file  operation函数(lseek、open、close、read、write)。
        MTD块设备的定义在mtdblock.c(其调用mtd_blkdevs.c的函数),在mtd_blkdevs.c=>  add_mtd_blktrans_dev函数中,
以mtd_blkdevs.c=>  block_device_operations  mtd_block_ops为ops,以mtdblock.c=>mtd_blktrans_ops  mtdblock_tr的.name和.major注册块设备、生成块设备节点。

四、设备节点

        通过mknod在/dev子目录下建立MTD字符设备节点(主设备号为90)和MTD块设备节点(主设备号为31),通过访问此设备节点即可访问MTD字符设备和块设备。
/dev/mtdN是字符设备节点,/dev/mtdblockN是块设备节点。
/dev/mtdN和/dev/mtdblockN的关系和区别
1.它们是对同一分区的不同的描述方法
2.生成节点的方法
        字符节点是mtdchar.c注册生成的,支持open,read,write,ioctl等。flash_erase,  nanddump等都是对字符节点,通过read,write,ioctl等执行。见分层调用流程
        块设备节点对应mtdblock.c和mtd_blkdevs.c,是Flash驱动中用add_mtd_partitions()添加MTD设备分区,而生成的对应的块设备。
        MTD块设备驱动程序可以让flash器件伪装成块设备,实际上它通过把整块的erase  block放到ram里面进行访问,然后再更新到flash,用户可以在这个块设备上创建通常的文件系统。
        对于MTD块设备,MTD设备层是不提供ioctl的实现方法的,不能使用nandwrite,flash_eraseall,flash_erase等工具去对/dev/mtdblockN去进行操作。
3.mtd-utils工具只能用与/dev/mtdN的MTD字符设备。mount、umount命令只对/dev/mtdblockN的MTD块设备有效。

五、根文件系统

        在Bootloader中将JFFS(或JFFS2)的文件系统映像jffs.image(或jffs2.img)烧到flash的某一个分区中,在/arch/arm/mach-your/arch.c文件的your_fixup函数中
将该分区作为根文件系统挂载。

六、文件系统

        内核启动后,通过mount  命令可以将flash中的其余分区作为文件系统挂载到mountpoint上。

mtd的字符设备和块设备的命名规则

Table 7-1. MTD /dev entries, corresponding MTD user modules, and relevant device major numbers

/dev entry

Accessible MTD user module

Device type

Major number

mtdN

char device

char

90

mtdrN

char device

char

90

mtdblockN

block device, read-only block device, JFFS, and JFFS2

block

31

nftlLN

NFTL

block

93

ftlLN

FTL

block

44


Table 7-2. MTD /dev entries, minor numbers, and naming schemes

/dev entry

Minor number range

Naming scheme

mtdN

0 to 32 per increments of 2

N = minor / 2

mtdrN

1 to 33 per increments of 2

N = (minor – 1) / 2

mtdblockN

0 to 16 per increments of 1

N = minor

nftlLN

0 to 255 per sets of 16

L = set;[2] N = minor – (set – 1) x 16; N is not appended to entry name if its value is zero.

ftlLN

0 to 255 per sets of 16

Same as NFTL.

mtd工具

        mtd-utils可以编译出很多mtd相关工具,例如:mkfs,flash_erase,ubiformat,nanddump,mtdinfo
        下载地址:   mtd官网       mtd-utils下载地址         mtd-utils版本差异
        

分层注册函数

分层注册函数图片

mtd_blkdevs.c的struct mtd_notifier blktrans_notifier的list成员  会添加到  mtdcore.c的            链表头mtd_nitifier
mtdblock.c的struct mtd_blktrans_ops mtdblock_tr的list成员     会添加到  mtd_blkdevs.c的    链表头blktrans_majors

1.add_mtd_partations()和add_mtd_devices()区别:
add_mtd_partations():
        有多个(即定义了struct  partition_info)    时使用此函数。
        此函数先调用allocate_partition按照分区信息进行分区,再调用add_mtd_devices()
add_mtd_devices():
只有一个分区时使用。
注:
可以直接使用mtd_device_register()
第一个参数是主分区(描述整个设备)
第二个参数是struct  partition_info的parts首地址
第三个参数是struct  partition_info的parts_num成员
2.注册函数区别
调用关系如下,详细见代码流程
mtd_device_register
        add_mtd_partations
                add_mtd_devices
mtd子系统详细分层
若将mtd子系统详细分层,则如下图所示。

详细分层图片

字符节点分层调用流程

示例:nanddump  -p  -c  -l  0x800  /dev/mtd8

open

open(“/dev/mtd8”...)  =  3
        mtdchar_open(struct  inode  *inode,  struct  file  *file)                //mtdchar.c
                int  minor  =  iminor(inode);
                int  devnum  =  minor  >>  1;
                struct  mtd_info  *mtd;        
                struct  mtd_file_info  *mfi;

                mtd  =  get_mtd_device(NULL,  devnum);
                        struct  mtd_info  *ret  =  NULL
                        ret  =  idr_find(&mtd_idr,  num);
                        __get_mtd_device(ret);
                        return  ret;

                mfi  =  kzalloc(sizeof(*mfi),  GFP_KERNEL);
                mfi->mtd  =  mtd;
                file->private_data  =  mfi;

ioctl

ioctl(3,  MIXER_READ(18)  or  ECCGETSTATS,  ...)
        struct  mtd_file_info  *mfi  =  file->private_data;
        struct  mtd_info  *mtd  =  mfi->mtd;

        检查坏块:MEMSETBADBLOCK
        擦除:MEMERASE、MEMERASE64            

擦除

mtd_erase(mtd,  erase);                                                                   //mtdcore.c
        mtd->_erase(mtd,  instr)                                                           //nand_base.c=>nand_scan_tail中指定的
                nand_erase(mtd,  instr);                                                    //nand_base.c
                        nand_erase_nand(mtd,  instr,  0)                             //nand_base.c
                                chip->select_chip(mtd,  chipnr);                       //控制器驱动或nand_base.c=>nand_scan_ident中指定
                                nand_block_checkbad(mtd,  ((loff_t)  page)  <<  chip->page_shift,  allowbbt)   //若是坏块,则不擦除
                                        if (!chip->bbt)   //否则 return nand_isbad_bbt(mtd, ofs, allowbbt);   nand_bbt.c
                                              chip->block_bad(mtd, ofs);   //nand_base.c=> nand_scan_ident=> nand_set_defaults
                                                      nand_block_bad(mtd, ofs)      //nand_base.c
                                                              如果(chip->bbt_options & NAND_BBT_SCANLASTPAGE)
                                                                      ofs += mtd->erasesize - mtd->writesize;
                                                              chip->cmdfunc(mtd, NAND_CMD_READOOB, chip->badblockpos, page);
                                                              bad = chip->read_byte(mtd);
                                                              res = bad != 0xFF;
                                                              如果(chip->bbt_options & NAND_BBT_SCAN2NDPAGE)
                                                                      会再读下一页的bb数据
                                                              return res;
                                chip->erase(mtd,  page  &  chip->pagemask)      //nand_base.c=>  nand_get_flash_type中指定
                                        single_erase(mtd,  page  &  chip->pagemask)
                                                //控制器驱动或nand_base.c=>nand_scan_ident中指定
                                                chip->cmdfunc(mtd,  NAND_CMD_ERASE1,  -1,  page);                
                                                       发送页地址
                                                chip->cmdfunc(mtd,  NAND_CMD_ERASE2,  -1,  -1);

read(3,  ...)  //应用层

mtdchar_read(struct  file  *file,  char  __user  *buf,  size_t  count,  loff_t  *ppos)    //mtdchar.c    
       //一般走switch的默认路径
        struct mtd_file_info *mfi = file->private_data;
        struct mtd_info *mtd = mfi->mtd;
        ret = mtd_read(mtd,  *ppos,  len,  &retlen,  kbuf);                           //mtdcore.c
                ret_code = mtd->_read(mtd,  from,  len,  retlen,  buf);           //mtdpart.c=> allocate_partition指定  
                           part_read(mtd,  from,  len,  retlen,  buf);           //mtdpart.c
                                         stats = part->master->ecc_stats;
                                         res = part->master->_read(part->master, from + part->offset, len, retlen, buf);
                                                  //nand_base.c=>nand_scan_tail指定
                                                  nand_read(mtd,  from,  len,  retlen,  buf)          //nand_base.c
                                                          ops.mode  =  MTD_OPS_PLACE_OOB;
                                                          ret = nand_do_read_ops(mtd,  from,  &ops);        //nand_base.c
                                                                uint8_t *oob; 
                                                                unsigned int max_bitflips = 0;
                                                                oob = ops->oobbuf;             
                                                                 ecc_failures = mtd->ecc_stats.failed;
                                                                 chip->select_chip(mtd,  chipnr);       
                                                                         //控制器驱动或nand_base.c=>nand_scan_ident => nand_set_defaults
                                                                 chip->cmdfunc(mtd,  NAND_CMD_READ0,  0x00,  page);                              
                                                                         //控制器驱动或nand_base.c=>nand_scan_ident => nand_set_defaults
                                                                         发送地址、读命令                                               
                                                                 如果ops->mode是MTD_OPS_RAW
                                                                          ret = chip->ecc.read_page_raw(mtd,  chip,  bufpoi,  
                                                                                                                                     oob_required,  page);
                                                                           //nand_base.c=>nand_scan_tail函数中设置
                                                                  若没对齐 且 chip->options有NAND_SUBPAGE_READ 且
                                                                                                                                         ops->oobbuf是NULL
                                                                          ret = chip->ecc.read_subpage(mtd,  chip,  col,  
                                                                                                                         bytes,  bufpoi,  page);
                                                                  其他  //一般走此分支
                                                                          ret = chip->ecc.read_page(mtd, chip, bufpoi, oob_required, page);

                                                                   ret = chip->ecc.read_page(mtd,  chip,  bufpoi,  oob_required,  page);
                                                                           //nand_base.c=> nand_scan_tail指定
                                                                           nand_read_page_raw(mtd,  chip,  bufpoi,  oob_required,  page)
                                                                                   //nand_base.c       //此函数返回值一定为0
                                                                                   chip->read_buf(mtd,  buf,  mtd->writesize);                                              
                                                                                          //控制器驱动或nand_base.c=>nand_scan_ident =>
                                                                                           //nand_set_defaults指定
                                                                                          只是把host->buf数据拷贝到buf
                                                                                          hisi_fmc_read_buf(struct mtd_info *mtd,
                                                                                                                                 uint8_t *buf, int len)
                                                                                                  如果读失败了:mtd->ecc_stats.failed++;
                                                                                                  设置ECC纠正的位数:mtd->ecc_stats.corrected += ..

                                                                       max_bitflips = max_t(unsigned int, max_bitflips, ret);
                                                                       if(oob)
                                                                                    oob = nand_transfer_oob(mtd, oob, ops, toread);
                                                                       //如果ecc出错了,重传
                                                                       if (mtd->ecc_stats.failed - ecc_failures) 
                                                                                //如果还有剩余尝试次数,重传
                                                                                if (retry_mode + 1 < chip->read_retries){
                                                                                                mtd->ecc_stats.failed = ecc_failures;
                                                                                                goto read_retry;
                                                                                }else{
                                                                                                ecc_fail = true;
                                                                                }
                                                                       }
                                                                        
                                                                       if (ret < 0)
                                                                                       return ret;

                                                                       if (ecc_fail)
                                                                                       return -EBADMSG;

                                                                       return max_bitflips; 
                                                               return ret;
                                        if ((mtd_is_eccerr(res)))
                                             mtd->ecc_stats.failed += part->master->ecc_stats.failed - stats.failed;
                                       else
                                             mtd->ecc_stats.corrected += part->master->ecc_stats.corrected - stats.corrected; 
                                       return res;
                若ret_code < 0,直接返回
                若mtd->ecc_strength == 0,直接返回0
                若ret_code大于mtd->bitflip_threshold
                        返回-EUCLEAN
                否则
                        返回0
        如果读出错(ret != 0)
                ret = mtd_is_bitflip_or_eccerr(ret)             //mtd.h
                        return mtd_is_bitflip(err) || mtd_is_eccerr(err)
                              如果err是-EUCLEAN,是位反转(但被校正过来了);如果err是-EBADMSG,是ECC错误
                如果ret == 0或者错误类型是位反转或ecc错误,拷贝数据给用户        

write(3, ...)        //应用层
mtdchar_write(struct file *file, const char __user *buf, size_t count, loff_t *ppos)                //mtdchar.c
        //默认走default流程
        mtd_write(mtd, *ppos, len, &retlen, kbuf);               //mtdcore.c
                mtd->_write(mtd, to, len, retlen, buf);          //mtdpart.c=> allocate_partition指定 
                part_write(mtd, to, len, retlen, buf);           //mtdpart.c
                       part->master->_write(part->master, to + part->offset, len,  retlen, buf);
                       //nand_base.c=>nand_scan_tail指定
                       nand_write(mtd, to, len, retlen, buf);          //nand_base.c
                                ops.mode = MTD_OPS_PLACE_OOB;
                                nand_do_write_ops(mtd, to, &ops);                      //nand_base.c
                                        chip->select_chip(mtd, chipnr);          
                                        //控制器驱动或nand_base.c=>nand_scan_ident => nand_set_defaults中指定
                                        chip->write_page(mtd, chip, column, ..., (ops->mode == MTD_OPS_RAW))                  
                                        //控制器驱动或nand_base.c=>nand_scan_tail指定
                                               nand_write_page                                  //nand_base.c
                                                    chip->cmdfunc(mtd, NAND_CMD_SEQIN, 0x00, page);
                                                             设置写的地址
                                                    如果是raw,走chip->ecc.write_page_raw;
                                                    如果支持subpage,走chip->ecc.write_subpage;
                                                    其他:(一般走此分支)
                                                    chip->ecc.write_page(mtd, chip, buf, oob_required, page);
                                                    //nand_base.c=>nand_scan_tail指定
                                                            nand_write_page_raw(mtd, chip, buf, oob_required, page);    //nand_base.c
                                                                    chip->write_buf(mtd, buf, mtd->writesize);
                                                                    //控制器驱动或nand_base.c=>nand_scan_ident => nand_set_defaults
                                                                    只是将数据复制到host->buffer
                                                    chip->cmdfunc(mtd, NAND_CMD_PAGEPROG, -1, -1);    
                                                    //控制器驱动或nand_base.c=>nand_scan_ident => nand_set_defaults指定 
                                                              执行烧写命令

控制器驱动probe流程

代码流程

hifmc100_nand_os.c        (drivers/mtd/nand/hifmc100_nand)
        hisi_nand_os_probe(struct  platform_device  *pltdev)
        struct  hik_nand_host  *host
        struct  nand_chip  *nand_chip;
        struct  mtd_info  *mtd;        
        struct  device  *dev  =  &pdev->dev;
        struct  device_node  *np  =  dev->of_node;
        dma_addr_t  dma_handle;
        host  =  devm_kzalloc(dev,  sizeof(*host),  GFP_KERNEL);
        pdev->dev.driver_data  =  host
        nand_chip  =  &host->chip;
        mtd    =  nand_to_mtd(nand_chip);                                        //即:mtd  =  &(nand_chip->mtd)
        host->mtd  =  mtd;
        nand_set_controller_data(nand_chip,  host);                        //nand_chip->priv  =  host
        nand_set_flash_node(nand_chip,  np);                                  //nand_chip->mtd.dev.of_node  =  pdev->dev.of_node
        host->buffer  =  dma_alloc_coherent(dev,  HIKFMC_BUFFER_LEN,  &dma_handle,  GFP_KERNEL)
        host->phy_buffer  =  dma_handle;
        设置nand_chip的函数及变量
                //读写选中芯片等函数,ecc模式等
                nand_chip->cmdfunc        =  hik_fmc_cmdfunc;
                nand_chip->select_chip        =  hik_fmc_select_chip;
                nand_chip->read_byte        =  hik_fmc_read_byte;
                nand_chip->read_buf        =  hik_fmc_read_buf;
                ...
                //nand_chip->options在nand.h中有所有宏
                nand_chip->options  =  NAND_SKIP_BBTSCAN  |  NAND_BROKEN_XD  |  NAND_SCAN_SILENT_NODEV;
                nand_chip->ecc.mode  =  NAND_ECC_NONE;

        //一般情况下,会调用nans_scan函数,nand_scan会调用nand_scan_ident和nand_scan_tail
        nand_scan_ident(mtd,  1,  NULL);                                            //nand_base.c
                //设置默认的读写等函数
                nand_set_defaults(chip,  chip->options  &  NAND_BUSWIDTH_16);
                        如果没设置chip的函数,则设置默认函数,对默认函数的设置如下。
                      (默认函数是drivers/mtd/nand/nand_base.c中的)
                        chip->read_byte  =  busw  ?  nand_read_byte16  :  nand_read_byte;
                        chip->block_bad  =  nand_block_bad;
                        chip->write_buf  =  busw  ?  nand_write_buf16  :  nand_write_buf
                        ...
                        // 这几个函数是一定会用到的,若不使用默认函数,则需要自己指定:
                        // select_chip,    cmdfunc,    read_byte,    read_buf,  write_buf,    dev_ready  
                        // chip->erase是强制设为默认函数的,见下边nand_get_type函数

                //获得nand  flash基本信息赋值到struct  nand_flash_dev结构体成员
                type  =  nand_get_flash_type(mtd,  chip,  &nand_maf_id,    &nand_dev_id,  table);
                        chip->erase  =  single_erase;                //nand_base.c
                        chip->options  |=  type->options;
                        打印flash相关信息:Manufacturer  ID、Chip  ID、SLC或者MLC、块大小、页大小、OOB大小等
                        chip->badblockbits = 8;
                        chip->erase = single_erase;
        hisifmc_host_init(host);                            //初始化fpu,spi,fmc控制器
        hisifmc_chipinfo_init(host);                        //控制器端的ECC模式、page大小等设置
        nand_scan_tail(mtd);                                 //nand_base.c
                struct  nand_chip  *chip  =  mtd->priv;
                struct nand_ecc_ctrl *ecc = &chip->ecc;
                分配并设置chip->buffer
                //设置内部oob buffer的位置(page data之后)
                chip->oob_poi = chip->buffers->databuf + mtd->writesize;
                mtd_set_ooblayout(mtd, &nand_ooblayout_lp_hamming_ops);
                根据chip->ecc.mode设置chip->ecc的读写等函数成员、chip->ecc的size、bytes、strength成员
                如果mode为NAND_ECC_NONE,则强制设置上述成员
                        ecc->read_page  =  nand_read_page_raw;                //nand_base.c
                        ecc->write_page  =  nand_write_page_raw;
                        ecc->size  =  mtd->writesize;
                        ecc->bytes  =  0;
                        ecc->strength  =  0;
                        ...
                ecc->steps = mtd->writesize / ecc->size;
                ecc->total = ecc->steps * ecc->bytes;
                设置chip->subpagesize等
                设置mtd的读写等函数、type、ecc信息等
                        mtd->_erase  =  nand_erase;                                //nand_base.c
                        mtd->_read   =  nand_read;
                        mtd->_write  =  nand_write;
                        ...                
                创建坏块表
                mtd->priv    =  nand_chip;
        ptn_info  =  get_partition_info(host->mtd);      //获取分区信息
        if  (ptn_info)  {
                nr_parts  =  ptn_info->parts_num;
                parts  =  ptn_info->parts;
        }
        ////解析分区,然后注册mtd设备。找到一个分区就分配分区
        mtd_device_register(host->mtd,  parts,  nr_parts);       //include/linux/mtd/mtd.h
                //传进来第1个参数名为master,第2个为parts,第3个为nr_parts
                mtd_device_parse_register(master,  NULL,  NULL,  parts,  nr_parts)                     //mtdcore.c实现
                        //第一个参数是主分区(描述整个设备)
                        parse_mtd_partitions(mtd,  types,  &parsed,  parser_data)                             //mtdpart.c实现
                        mtd_add_device_partitions(mtd,  &parsed)                                                       //mtdcore.c实现
                                int  nbparts  =  parts->nr_parts;
                                if  (nbparts  ==  0  ||  IS_ENABLED(CONFIG_MTD_PARTITIONED_MASTER))  {
                                        ret  =  add_mtd_device(mtd);
                                                        mtd->dev.type  =  &mtd_devtype;
                                                        mtd->dev.class  =  &mtd_class;
                                                        device_register(&mtd->dev);
                                                        device_create(&mtd_class,  mtd->dev.parent,  MTD_DEVT(i)  +  1,  
                                                                                      NULL,  "mtd%dro",  i);
                                }

                                if  (nbparts  >  0)  {
                                        ret  =  add_mtd_partitions(mtd,  real_parts,  nbparts);                     //mtdpart.c实现
                                                        struct  mtd_part  *slave;
                                                        printk("Creating  %d  MTD  partitions  on  \"%s\":\n",  nbparts,  master->name);
                                                        for  (i  =  0;  i  <  nbparts;  i++)  {
                                                                slave  =  allocate_partition(master,  parts  +  i,  i,  cur_offset);
                                                                                struct  mtd_part  *slave;
                                                                                slave  =  kzalloc(sizeof(*slave),  GFP_KERNEL);
                                                                                //设置slave的mtd_info成员mtd。mtd的变量成员基本都来自于master,
                                                                                // 函数成员基本都来自mtdpart.c
                                                                                slave->mtd.type  =  master->type;
                                                                                slave->mtd._read  =  part_read;
                                                                                slave->master  =  master;
                                                                                //打印本分区的区间和名称
                                                                                printk("0x%012llx-0x%012llx  :  \"%s\"\n",  
                                                                                      (unsigned  long  long)slave->offset,
                                                                                      (unsigned  long  long)(slave->offset  +  slave->mtd.size),  
                                                                                      slave->mtd.name);
                                                                                如果master->_block_isbad已经被设置
                                                                                uint64_t  offs  =  0;
                                                                                while  (offs  <  slave->mtd.size)  {
                                                                                //如果是被保留的块,则bbt
                                                                                if  (mtd_block_isreserved(master,  offs  +  slave->offset))
                                                                                        slave->mtd.ecc_stats.bbtblocks++;
                                                                                else  if  (mtd_block_isbad(master,  offs  +  slave->offset))
                                                                                        slave->mtd.ecc_stats.badblocks++;
                                                                                offs  +=  slave->mtd.erasesize;
                                                                list_add(&slave->list,  &mtd_partitions);
                                                                add_mtd_device(&slave->mtd);                                  //mtdcore.c
                                                                        i  =  idr_alloc(&mtd_idr,  mtd,  0,  0,  GFP_KERNEL)    
                                                                                //调用到的地方:  register_mtd_blktrans=>  mtd_for_each_device,
                                                                                //  其调用idr_get_next(&mtd_idr,  &i)
                                                                        mtd->index  =  i;
                                                                
                                                                        mtd->dev.type  =  &mtd_devtype;
                                                                        mtd->dev.class  =  &mtd_class;
                                                                        mtd->dev.devt  =  MTD_DEVT(i);
                                                                        dev_set_name(&mtd->dev,  "mtd%d",  i);
                                                                        dev_set_drvdata(&mtd->dev,  mtd);
                                                                        //  在/dev下创建mtd%d节点。
                                                                        device_register(&mtd->dev);
                                                                        //  在/dev下创建mtd  %dro节点。
                                                                        device_create(&mtd_class,  mtd->dev.parent,  MTD_DEVT(i)  +  1,  NULL,
                                                                                                                "mtd%dro",  i);
                                                                        list_for_each_entry(not,  &mtd_notifiers,  list)
                                                                               not->add(mtd);                        //见核心层流程
                                        if  (ret  &&  IS_ENABLED(CONFIG_MTD_PARTITIONED_MASTER))
                                                del_mtd_device(mtd);
                                        return  ret;
                                }

结构体关系图

probe函数确定的结构体关系图如下

结构体关系图

核心层流程

字符设备

mtdcore.c         (drivers/mtd)
        class_register(&mtd_class);
        mtd_bdi_init(&mtd_bdi, "mtd");                //mtdcore.c
        proc_mtd = proc_create("mtd", 0, NULL, &mtd_proc_ops);
        init_mtdchar();                                    //mtdchar.c (drivers/mtd)
                __register_chrdev(MTD_CHAR_MAJOR, 0, 1 << MINORBITS, "mtd", &mtd_fops);
                //mtdcore.c => add_mtd_device中会创建设备节点/dev/mtdN,对应mtd_fops (mtdchar.c定义)

块设备                

mtdblock.c  (drivers/mtd)    //此文件的init函数调用register_mtd_blktrans

static  struct  mtd_blktrans_ops  mtdblock_tr  =  {
        .name                =  "mtdblock",
        .major                =  MTD_BLOCK_MAJOR,    //MTD_BLOCK_MAJOR  =  31
        .part_bits        =  0,
        .blksize          =  512,
        ...
        .writesect        =  mtdblock_writesect,
        .add_mtd        =  mtdblock_add_mtd,
        .owner                =  THIS_MODULE,
//        struct  list_head  devs;
};

register_mtd_blktrans(&mtdblock_tr)                        //mtd_blkdevs.c实现,它定义static  LIST_HEAD(blktrans_majors);
        //如果是在注册第一个设备类型,则注册notifier。
        if  (!blktrans_notifier.list.next)                               //static  struct  mtd_notifier  blktrans_notifier  (mtd_blkdev.c)
                register_mtd_user(&blktrans_notifier);      //mtdcore.c
                        list_add(&blktrans_notifier->list,  &mtd_notifiers);     //static  LIST_HEAD(mtd_notifiers)          (mtdcore.c)
                        mtd_for_each_device(mtd)                     //此mtd与mtd_idr关联:static  DEFINE_IDR(mtd_idr);  //mtdcore.c
                                                                                           //add_mtd_device里边,把每个mtd设备关联到了mtd_idr
                                //对于mtd_idr上的每个设备(mtd),调用blktrans_notifier->add,会调用blktrans_majors链表头上每
                                 //个成员(tr)的add_mtd函数(以tr和mtd为参数)。
                                new->add(mtd);
                        
        register_blkdev(mtdblock_tr->major,  mtdblock_tr->name)                //注册块设备

        list_add(&mtdblock_tr->list,  &blktrans_majors);                                //把本tr挂到blktrans_majors链表头上
        mtd_for_each_device(mtd)                                                                                                                                                                                
                if  (mtd->type  !=  MTD_ABSENT)
                        mtdblock_tr->add_mtd(mtdblock_tr,  mtd);
                                mtdblock_add_mtd
                                struct  gendisk  *gd;
                                struct  mtdblk_dev  *dev  =  kzalloc(sizeof(*dev),  GFP_KERNEL);
                                dev->mbd.mtd  =  mtd;
                                dev->mbd.devnum  =  mtd->index;
                                dev->mbd.size  =  mtd->size  >>  9;
                                dev->mbd.tr  =  mtdblock_tr;
                                add_mtd_blktrans_dev(&dev->mbd)                                //mtd_blkdevs.c
                                        //传进来的参数名字为new
                                        struct  mtd_blktrans_ops  *tr  =  new->tr;    //tr  =  new->tr  =  mtdblock_tr
                                        struct  mtd_blktrans_dev  *d;
                                        list_for_each_entry(d,  &tr->devs,  list)  {
                                                //第一次执行时,tr->devs上没有成员,直接跳出本循环
                                                //如果不是第一次执行
                                                        //如果d->devnum  ==  new->devnum,说明此号已经被使用,直接return  -EBUSY
                                                        //如果d->devnum  >  new->devnum,说明已经添加过了,直接跳出到added标号
                                                //如果执行到这里,说明d->devnum  <  new->devnum
                                                list_add_tail(&new->list,  &tr->devs);
                                                
                                                gd  =  alloc_disk(1  <<  tr->part_bits);
                                                
                                                new->disk  =  gd;
                                                        gd->private_data  =  new;
                                                        gd->major  =  tr->major;
                                                        gd->first_minor  =  (new->devnum)  <<  tr->part_bits;
                                                        gd->fops  =  &mtd_block_ops;
                                                //设置gd->dist_name为/dev/mtdblockN
                                                snprintf(gd->disk_name,  sizeof(gd->disk_name),  "%s%d",  tr->name,  new->devnum);                                                
                                                /*  Create  the  request  queue  */
                                                new->rq  =  blk_init_queue(mtd_blktrans_request,  &new->queue_lock);
                                                new->rq->queuedata  =  new;
                                                
                                                gd->queue  =  new->rq;
                                                
                                                new->wq  =  alloc_workqueue("%s%d",  0,  0,  tr->name,  new->mtd->index);
                                                device_add_disk(&new->mtd->dev,  gd);                //genhd.c  (drivers/block)
                                                        //传进来的第二个参数名为disk
                                                        disk->major  =  MAJOR(devt);
                                                        disk->first_minor  =  MINOR(devt);
                                                        register_disk(parent,  disk);        
                                                                struct  device  *ddev  =  disk_to_dev(disk);
                                                                dev_set_name(ddev,  "%s",  disk->disk_name);        
                                                                device_add(ddev)

/sys/class/mtd的相关成员

mtd的相关信息可以在/sys/class/mtd/mtdN下直接cat得到,对应相应分区的mtd_info结构体
dev:                              主设备号和次设备号
name:                              分区的名字。(自己定义的分区表(partition_info)的分区的名字)
type:                              设备类型。如:nand,nor。对应MTD_NORFLASH、MTD_NANDFLASH等(可参考mtd-abi.h)。
size:                              本分区总大小。10进制的
erasesize:                    擦除大小。10进制的,一般是一个块大小。
writesize:                    写大小。10进制的,一般是一个页大小。
subpagesize:                子页大小。10进制的。
oobsize:                        oob大小。10进制的。
numeraseregions:        可变擦除区域数量。如果是0,则表示整个设备的擦除大小都是erasesize
flags:                            MTD_WRITEABLE,MTD_NO_ERASE等(可参考mtd-abi.h)

常用函数

nand.h中有一些常用函数:nand_set_flash_node、nand_get_flash_node、mtd_to_nand、nand_to_mtd、nand_get_controller_data、nand_set_controller_data
它们实现如下:
static  inline  void  nand_set_flash_node(struct  nand_chip  *chip,  struct  device_node  *np)
{
        mtd_set_of_node(&chip->mtd,  np);
}

static  inline  struct  device_node  *nand_get_flash_node(struct  nand_chip  *chip)
{
        return  mtd_get_of_node(&chip->mtd);
}

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

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

static  inline  void  *nand_get_controller_data(struct  nand_chip  *chip)
{
        return  chip->priv;
}

static  inline  void  nand_set_controller_data(struct  nand_chip  *chip,  void  *priv)
{
        chip->priv  =  priv;
}
 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

IT利刃出鞘

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值