linux mtd
2019-12-20 17:33:08 weixin_41922484 阅读数 40
  • Linux驱动程序之NOR FLASH

    norflash驱动程序的步骤: (1)分配map_info结构体,并设置:物理地址,位宽,虚拟地址等 (2)设置读写函数,用默认函数即可 (3)调用NOR FLASH协议层提供的函数来识别:do_map_probe (4)添加分区:add_mtd_partitions

    2616 人正在学习 去看看 韦东山

一、参考资料

转载自嵌入式文件系统简介(一) — Linux MTD设备文件系统

在嵌入式系统中,与文件系统相关的存储设备包括硬盘、Flash存储器等。Flash存储器又分为Flash芯片设备(Raw Flash device,也叫MTD设备)和带Flash控制器的设备(Flash Translation Layer device, FTL设备),两者的关键区别是是否带有Flash控制器,这也直接决定了文件系统分为不同的两类。

在这里插入图片描述

其中MTD设备包括NOR Flash、NAND Flash等,FTL设备包括SD、eMMC、SSD、USB大容量存储设备等。如图所示JFFS2、YAFFS2、UBIF、LogFS支持MTD设备,FAT、EXT3/4、XFS和Btrfs支持 FTL设备和硬盘(HDD)。MTD设备对应的设备文件为/dev/mtd,FTL设备对应的设备文件可为/dev/mtdblock。
在这里插入图片描述
在这里插入图片描述

二、mtd-utils 工具

mtd-utils需要预先编译进linux_rootfs中,否则没有这些命令。

1.使用 cat /proc/mtd 命令查看QSPI FLASH的分区信息

root@zedboard:~# cat /proc/mtd
dev:    size   erasesize  name
mtd0: 00500000 00010000 "boot"
mtd1: 00020000 00010000 "bootenv"
mtd2: 00a80000 00010000 "kernel"
mtd3: 01060000 00010000 "spare"

2.使用 mtd_debug info /dev/mtd0 命令查看FLASH类型、分区大小、块大小

root@zedboard:~# mtd_debug info /dev/mtd0
mtd.type = MTD_NORFLASH
mtd.flags = MTD_CAP_NORFLASH
mtd.size = 5242880 (5M)
mtd.erasesize = 65536 (64K)
mtd.writesize = 1
mtd.oobsize = 0
regions = 0

3.使用 flash_eraseall /dev/mtd1 擦除选择的某个mtd分区。

root@zedboard:~# flash_eraseall /dev/mtd1
flash_eraseall has been replaced by `flash_erase <mtddev> 0 0`; please use it
Erasing 64 Kibyte @ 10000 -- 100 % complete

4.使用 flashcp -v BOOT.BIN /dev/mtd0 更新linux系统固件

更新U-BOOT。(对于ZYNQ,打包FSBL、BIT流、U-BOOT成BOOT.BIN,放在mtd0分区)

root@zedboard:~# flashcp -v firmware/BOOT.BIN /dev/mtd0
Erasing blocks: 7/7 (100%)
Writing data: 395k/0k (100%)
Verifying data: 395k/0k (100%)

更新image.ub内核。

root@zedboard:~# flashcp -v firmware/image.ub /dev/mtd2
Erasing blocks: 1/58 (1%)random: crng init done
Erasing blocks: 58/58 (100%)
Writing data: 3674k/0k (100%))
Verifying data: 3674k/0k (100%))

更新jffs2根文件系统。

root@zedboard:~# flashcp -v firmware/rootfs.jffs2 /dev/mtd3
Erasing blocks: 104/104 (100%)
Writing data: 6656k/0k (100%))
Verifying data: 6656k/0k (100%))

5.使用 mtd_debug read /dev/mtd0 0x0 0x20000 读取出FLASH中的数据,确认是否刷写成功

root@zedboard_jffs2:~# mtd_debug read /dev/mtd0 0x0 0x20000 ~/test
Copied 131072 bytes from address 0x00000000 in flash to /home/root/test

在这里插入图片描述

2019-07-09 17:02:03 feiying0canglang 阅读数 303
  • Linux驱动程序之NOR FLASH

    norflash驱动程序的步骤: (1)分配map_info结构体,并设置:物理地址,位宽,虚拟地址等 (2)设置读写函数,用默认函数即可 (3)调用NOR FLASH协议层提供的函数来识别:do_map_probe (4)添加分区:add_mtd_partitions

    2616 人正在学习 去看看 韦东山

目录

linux的mtd概述

简介

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

mtd工具

分层注册函数

字符节点分层调用流程

open

擦除

控制器驱动probe流程

代码流程

结构体关系图

核心层流程

字符设备

块设备                

/sys/class/mtd的相关成员

常用函数


内核版本: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;
}
 

2016-06-01 22:08:58 liqinghan 阅读数 3943
  • Linux驱动程序之NOR FLASH

    norflash驱动程序的步骤: (1)分配map_info结构体,并设置:物理地址,位宽,虚拟地址等 (2)设置读写函数,用默认函数即可 (3)调用NOR FLASH协议层提供的函数来识别:do_map_probe (4)添加分区:add_mtd_partitions

    2616 人正在学习 去看看 韦东山

使用mtd-utils工具实现对flash的升级分区的烧写yaffs2

yaffs2的格式是根据所使用的nandflash来制作的,不同的nandflash,得到的yaffs2是不一样的,具体可以参考自己所用的nandflash,以及生成yaffs2文件系统的工具mkyaffs2image。
yaffs2包含了oob数据,所以写flash的时候要分开,本文所使用的是256M oob是64bit,page是2048byte-2kByte,block=64page制作的yaffs2的大小是(2048+64)的倍数!
每次写入是按页(page)的大小写入,而擦除是按照块来的,坏块也是按照块来的,如果当前块是坏的就必须跳过该块!
下载mtd-utils源码!

yaffs2的写入函数

int mtd_write_yaffs2_skip_bad(libmtd_t desc,const struct mtd_dev_info *mtd, int fd, int eb, int offs,
		  const char *img_name)函数用来烧写flash,如下:
{
	int tmp, ret, in_fd, len, written = 0;
	int write_eb_num,i;
	int data_length,left_to_write,writesize;
	off_t seek;
	struct stat st;
	char *buf,*temp,*dataAddr,*oobAddr;
	
	if (offs < 0 || offs >= mtd->eb_size) {
		errmsg("bad offset %d, mtd%d eraseblock size is %d",
				   offs, mtd->mtd_num, mtd->eb_size);
		errno = EINVAL;
		return -1;
	}
	if (offs % mtd->subpage_size) {
		//start address must align to page (2048)0x800
		errmsg("write offset %d is not aligned to mtd%d min. I/O size %d",
			offs, mtd->mtd_num, mtd->subpage_size);
		errno = EINVAL;
		return -1;
	}
	in_fd = open(img_name, O_RDONLY | O_CLOEXEC);
	if (in_fd == -1)
			return sys_errmsg("cannot open \"%s\"", img_name);
	
	if (fstat(in_fd, &st)){
			sys_errmsg("cannot stat %s", img_name);
			goto out_close;
	}
	
	len = st.st_size;
	if (len % (mtd->subpage_size + mtd->oob_size)){
		errmsg("size of \"%s\" is %d byte, which is not aligned to "
			   "mtd%d min. I/O size %d, it is not a yaffs2 file ", img_name, len, mtd->mtd_num,
			   mtd->subpage_size + mtd->oob_size);
		errno = EINVAL;
		goto out_close;
	}
	data_length = len / (mtd->subpage_size + mtd->oob_size) * mtd->subpage_size;
	
	tmp = (offs + data_length + mtd->eb_size - 1) / mtd->eb_size;
	if (eb + tmp > mtd->eb_cnt) {
		errmsg("\"%s\" image size(except oob size) is %d bytes, mtd%d size is %d "
			   "eraseblocks, the image does not fit if we write it "
			   "starting from eraseblock %d, offset %d",
			   img_name, data_length, mtd->mtd_num, mtd->eb_cnt, eb, offs);
		errno = EINVAL;
		goto out_close;
	}
	
	/* Seek to the beginning of the eraseblock */
	seek = (off_t)eb * mtd->eb_size + offs;
	if (lseek(fd, seek, SEEK_SET) != seek) {
		sys_errmsg("cannot seek mtd%d to offset %llu",
				mtd->mtd_num, (unsigned long long)seek);
		goto out_close;
	}
	writesize = (mtd->eb_size / mtd->subpage_size) *( mtd->subpage_size + mtd->oob_size);
	printf("write size with oob size is: %d \n",writesize);
	buf = xmalloc(writesize);
	
	left_to_write = len;
	write_eb_num = eb;
	///writeoffs = eb*mtd->eb_size + offs;
	
	while (left_to_write > 0) {
		int rd = 0;

		ret = mtd_is_bad(mtd,fd,write_eb_num);//判断当前块是否是坏块!
		if(ret >0){ //是坏块!
			write_eb_num = write_eb_num + 1;
			if(write_eb_num >= mtd->eb_cnt)
			{
				if(left_to_write < 1){ 
					goto out_free;
				}
			}				
			else {
				printf("skip bad blocks at offset: %d \n",write_eb_num);
				continue;
			}
		}else if(ret <0){
			printf("get bad blocks error: %d\n",errno);
		}

		if(left_to_write < (mtd->eb_size +mtd->oob_size)){
			writesize = left_to_write;
		}
		else{
			writesize = (mtd->eb_size / mtd->subpage_size) *( mtd->subpage_size + mtd->oob_size);
		}

		ret = read(in_fd, buf, writesize);
		if(ret == -1) {
			sys_errmsg("cannot read \"%s\"", img_name);
			goto out_free;
		}
		
		temp = buf;
		
		dataAddr = temp;
		oobAddr = temp + mtd->subpage_size;

		for(i=0;i< mtd->eb_size/mtd->subpage_size;i++){ //完成一个块的写入!
			ret = mtd_write(desc,mtd,fd,write_eb_num,i*mtd->subpage_size,dataAddr,mtd->subpage_size,oobAddr,mtd->oob_size,MTD_OPS_RAW);
			if(ret < 0){
				printf("write data witd oob error : %d \n",errno);
			}
			temp = oobAddr + mtd->oob_size; 
			dataAddr = temp;
			oobAddr = temp + mtd->subpage_size;
		}
		write_eb_num = write_eb_num +1;
		left_to_write -= writesize;
		printf("left_to_write :%d write_eb_num: %d,writesize:%d\n",left_to_write,write_eb_num,writesize);
		
	}

	free(buf);
	close(in_fd);
	return 0;

out_free:
	free(buf);
out_close:
	close(in_fd);
	return -1;
	
}

yaffs2的读取函数

int mtd_read_yaffs2_skip_bad(libmtd_t desc,const struct mtd_dev_info *mtd, int fd, int eb, int offs,
	const char *img_name)
{
	int tmp, ret, out_fd, len, written = 0;
	int read_eb_num,i,sekOffs;
	int data_length,left_to_read,readsize;
	off_t seek;
	struct stat st;
	char *buf,*dataAddr,*oobAddr;
	
	if (offs < 0 || offs >= mtd->eb_size) {
		errmsg("bad offset %d, mtd%d eraseblock size is %d",
				   offs, mtd->mtd_num, mtd->eb_size);
		errno = EINVAL;
		return -1;
	}
	if (offs % mtd->subpage_size) {
		//start address must align to page (2048)0x800
		errmsg("write offset %d is not aligned to mtd%d min. I/O size %d",
			offs, mtd->mtd_num, mtd->subpage_size);
		errno = EINVAL;
		return -1;
	}
	len = 0x193b3c0;//for test's length, you can read nand flash frome straddr to endaddr!!!
	//also can read all mtdx device!
	if (len % (mtd->subpage_size + mtd->oob_size)){
		errmsg("size of \"%s\" is %d byte, which is not aligned to "
			   "mtd%d min. I/O size %d, it is not a yaffs2 file ", img_name, len, mtd->mtd_num,
			   mtd->subpage_size + mtd->oob_size);
		errno = EINVAL;
		goto out_close;
	}
	data_length = len / (mtd->subpage_size + mtd->oob_size) * mtd->subpage_size;
	
	tmp = (offs + data_length + mtd->eb_size - 1) / mtd->eb_size;
	if (eb + tmp > mtd->eb_cnt) {
		errmsg("\"%s\" image size(except oob size) is %d bytes, mtd%d size is %d "
			   "eraseblocks, the image does not fit if we write it "
			   "starting from eraseblock %d, offset %d",
			   img_name, data_length, mtd->mtd_num, mtd->eb_cnt, eb, offs);
		errno = EINVAL;
		goto out_close;
	}
	/* Seek to the beginning of the eraseblock */
	seek = (off_t)eb * mtd->eb_size + offs;
	if (lseek(fd, seek, SEEK_SET) != seek) {
		sys_errmsg("cannot seek mtd%d to offset %llu",
				mtd->mtd_num, (unsigned long long)seek);
		goto out_close;
	}
	
	readsize = mtd->subpage_size + mtd->oob_size;
	printf("read size with oob size is: %d \n",readsize);
	
	buf = xmalloc(readsize);
	
	dataAddr = buf;
	oobAddr = buf + mtd->subpage_size;
	
	left_to_read = len;
	read_eb_num = eb;

	out_fd = open(img_name,O_WRONLY|O_CREAT|O_TRUNC|O_APPEND, S_IRUSR | S_IWUSR);
	if(out_fd < 0){
		printf("open write file error: %d \n",errno);
		return -1;
	}
	
	while (left_to_read > 0) {
		int rd = 0;

		ret = mtd_is_bad(mtd,fd,read_eb_num);
		if(ret >0){
			read_eb_num = read_eb_num + 1;
			if(read_eb_num >= mtd->eb_cnt)
			{
				if(left_to_read < 1){ 
					goto out_free;
				}
			}				
			else {
				printf("skip bad blocks at offset: %d \n",read_eb_num);
				continue;
			}
		}else if(ret <0){
			printf("get bad blocks error: %d\n",errno);
		}

		if(left_to_read < (mtd->eb_size +mtd->oob_size)){
			readsize = left_to_read;
		}
		else{
			readsize = (mtd->eb_size / mtd->subpage_size) *( mtd->subpage_size + mtd->oob_size);
		}
	
		for(i=0;i< mtd->eb_size/mtd->subpage_size;i++){

			ret = mtd_read(mtd,fd,read_eb_num,i*mtd->subpage_size,dataAddr,mtd->subpage_size);
			if(ret < 0){
				printf("read data error: %d \n",errno);
				goto out_free;
			}
			
			sekOffs = read_eb_num * mtd->eb_size + i*mtd->subpage_size;
			seek = (off_t)sekOffs;
			//printf("seek %#llx sek offs %#x \n",seek,sekOffs);
			ret = mtd_read_oob(desc,mtd,fd,seek,mtd->oob_size,oobAddr);
			if(ret < 0){
				printf("read oob error: %d \n",errno);
				goto out_free;
			}
			
			ret = write(out_fd, dataAddr, mtd->subpage_size);
			if(ret == -1) {
				sys_errmsg("cannot write data \"%s\"", img_name);
				goto out_free;
			}
			ret = write(out_fd, oobAddr, mtd->oob_size);
			if(ret == -1) {
				sys_errmsg("cannot write oob \"%s\"", img_name);
				goto out_free;
			}
		}
		read_eb_num = read_eb_num +1;
		left_to_read -= readsize;
		printf("left_to_read :%-7d write_eb_num: %-3d,readsize:%-6d\n",left_to_read,read_eb_num,readsize);
	}
	free(buf);
	close(out_fd);
	return 0;
out_free:
	free(buf);
out_close:
	close(out_fd);
	return -1;
	
}

测试烧写;

具体如下:
	int mtdDevFd1 =0,ret =0,i;
	libmtd_t mtd_desc;	
	char* image_file = FILE_NAME;
	struct mtd_dev_info mtd;
	//open mtd device, see it at /dev/mtdX
	//get mtd message : cat /proc/mtd
	if ((mtdDevFd1 = open(mtdDev1, O_RDWR)) < 0){
		_SEND_DBUG_MSG("open %s error \n",mtdDev2);
		ret =-1;
		goto out_close;
	}
	mtd_desc = libmtd_open();
	if(mtd_desc == NULL){
		_SEND_DBUG_MSG("can not initlize mtd lib \n");
		ret =-1;
		goto out_close;
	}
		
	if (mtd_get_dev_info(mtd_desc,mtdDev1,&mtd) < 0){
		ret =-1;
		goto out_close;;
		_SEND_DBUG_MSG("get dev info error!\n");
	}
	printf("size:%#x \n",mtd.size);
	printf("eb_size:%#x \n",mtd.eb_size);
	printf("name:%s \n",mtd.name);
	printf("subpage size: %#x \n",mtd.subpage_size);
	printf("oob size: %#x \n",mtd.oob_size);
	printf("eb_cnt: %#x \n",mtd.eb_cnt);

	for(i = 0; i< mtd.eb_cnt;i++){
		if(mtd_is_bad(&mtd, mtdDevFd1, i))
		{
			printf("erase skip bad block num: %d \n",i);
			continue;
		}
		if(0 != mtd_erase(mtd_desc,&mtd,mtdDevFd1,i)){//擦除!
			_SEND_DBUG_MSG("ersae error \n");
			ret =-1;
			goto out_close;
		}
	}
	if(0 != mtd_write_yaffs2_skip_bad(mtd_desc,&mtd,mtdDevFd1,0,0,image_file)){
		_SEND_DBUG_MSG("write yaffs2 error \n");
		ret = -1;
	}
out_close:
	libmtd_close(mtd_desc);
	close(mtdDevFd1);
	//write end!!!


2015-10-24 11:31:17 u013286409 阅读数 1829
  • Linux驱动程序之NOR FLASH

    norflash驱动程序的步骤: (1)分配map_info结构体,并设置:物理地址,位宽,虚拟地址等 (2)设置读写函数,用默认函数即可 (3)调用NOR FLASH协议层提供的函数来识别:do_map_probe (4)添加分区:add_mtd_partitions

    2616 人正在学习 去看看 韦东山
擦除第二分区-kernel

# flash_eraseall /dev/mtd1 

Erasing 64 Kibyte @ 200000 - 100% complete.

重写kernel分区

# flashcp kernel.img /dev/mtd1


备注:第一分区/dev/mtd0为uboot,尽量不要动,其他分区操作错误,只要uboot没死都可以想办法恢复系统,uboot也死掉了,就只能拆flash芯片用烧录工具进行烧写了。

擦除第三分区-rootfs

# flash_eraseall /dev/mtd2


Erasing 64 Kibyte @ 300000 - 100% complete.

重写rootfs分区

# flashcp rootfs.img  /dev/mtd2

擦除第四分区-application

# flash_eraseall /dev/mtd3


Erasing 64 Kibyte @ a00000 - 100% complete.

重写application分区

# flashcp app /dev/mtd3


重写完,就可以reboot启动更新的系统

2018-04-01 20:05:33 yedushu 阅读数 44
  • Linux驱动程序之NOR FLASH

    norflash驱动程序的步骤: (1)分配map_info结构体,并设置:物理地址,位宽,虚拟地址等 (2)设置读写函数,用默认函数即可 (3)调用NOR FLASH协议层提供的函数来识别:do_map_probe (4)添加分区:add_mtd_partitions

    2616 人正在学习 去看看 韦东山

MTD,Memory Technology Device即内存技术设备,在Linux内核中,引入MTD层为NOR FLASH和NAND FLASH设备提供统一接口。MTD将文件系统与底层FLASH存储器进行了隔离。


如上图所示,MTD设备通常可分为四层,从上到下依次是:设备节点、MTD设备层、MTD原始设备层、硬件驱动层。

Flash硬件驱动层:Flash硬件驱动层负责对Flash硬件的读、写和擦除操作。MTD设备的Nand Flash芯片的驱动则drivers/mtd/nand/子目录下,Nor Flash芯片驱动位于drivers/mtd/chips/子目录下。

MTD原始设备层:用于描述MTD原始设备的数据结构是mtd_info,它定义了大量的关于MTD的数据和操作函数。其中mtdcore.c:  MTD原始设备接口相关实现,mtdpart.c :  MTD分区接口相关实现。

MTD设备层:基于MTD原始设备,linux系统可以定义出MTD的块设备(主设备号31)字符设备(设备号90)。其中mtdchar.c :  MTD字符设备接口相关实现,mtdblock.c : MTD块设备接口相关实现。

设备节点:通过mknod在/dev子目录下建立MTD块设备节点(主设备号为31)MTD字符设备节点(主设备号为90)。通过访问此设备节点即可访问MTD字符设备和块设备 

MTD数据结构:

1.Linux内核使用mtd_info结构体表示MTD原始设备,这其中定义了大量关于MTD的数据和操作函数(后面将会看到),所有的mtd_info结构体存放在mtd_table结构体数据里。在/drivers/mtd/mtdcore.c里:

  1. struct mtd_info *mtd_table[MAX_MTD_DEVICES];  
2.Linux内核使用mtd_part结构体表示分区,其中mtd_info结构体成员用于描述该分区,大部分成员由其主分区mtd_part->master决定,各种函数也指向主分区的相应函数。

  1. struct mtd_part {  
  2.     struct mtd_info mtd;        /* 分区信息, 大部分由master决定 */  
  3.     struct mtd_info *master;    /* 分区的主分区 */  
  4.     uint64_t offset;            /* 分区的偏移地址 */  
  5.     int index;                  /* 分区号 (Linux3.0后不存在该字段) */  
  6.     struct list_head list;      /* 将mtd_part链成一个链表mtd_partitons */  
  7.     int registered;  
  8. };  
mtd_info结构体主要成员,为了便于观察,将重要的数据放在前面,不大重要的编写在后面。

  1. struct mtd_info {  
  2.     u_char type;         /* MTD类型,包括MTD_NORFLASH,MTD_NANDFLASH等(可参考mtd-abi.h) */  
  3.     uint32_t flags;      /* MTD属性标志,MTD_WRITEABLE,MTD_NO_ERASE等(可参考mtd-abi.h) */  
  4.     uint64_t size;       /* mtd设备的大小 */  
  5.     uint32_t erasesize;  /* MTD设备的擦除单元大小,对于NandFlash来说就是Block的大小 */  
  6.     uint32_t writesize;  /* 写大小, 对于norFlash是字节,对nandFlash为一页 */  
  7.     uint32_t oobsize;    /* OOB字节数 */  
  8.     uint32_t oobavail;   /* 可用的OOB字节数 */  
  9.     unsigned int erasesize_shift;   /* 默认为0,不重要 */  
  10.     unsigned int writesize_shift;   /* 默认为0,不重要 */  
  11.     unsigned int erasesize_mask;    /* 默认为1,不重要 */  
  12.     unsigned int writesize_mask;    /* 默认为1,不重要 */  
  13.     const char *name;               /* 名字,   不重要*/  
  14.     int index;                      /* 索引号,不重要 */  
  15.     int numeraseregions;            /* 通常为1 */  
  16.     struct mtd_erase_region_info *eraseregions; /* 可变擦除区域 */  
  17.       
  18.     void *priv;     /* 设备私有数据指针,对于NandFlash来说指nand_chip结构体 */  
  19.     struct module *owner;   /* 一般设置为THIS_MODULE */  
  20.       
  21.     /* 擦除函数 */  
  22.     int (*erase) (struct mtd_info *mtd, struct erase_info *instr);  
  23.   
  24.     /* 读写flash函数 */  
  25.     int (*read) (struct mtd_info *mtd, loff_t from, size_t len, size_t *retlen, u_char *buf);  
  26.     int (*write) (struct mtd_info *mtd, loff_t to, size_t len, size_t *retlen, const u_char *buf);  
  27.   
  28.     /* 带oob读写Flash函数 */  
  29.     int (*read_oob) (struct mtd_info *mtd, loff_t from,  
  30.              struct mtd_oob_ops *ops);  
  31.     int (*write_oob) (struct mtd_info *mtd, loff_t to,  
  32.              struct mtd_oob_ops *ops);  
  33.   
  34.     int (*get_fact_prot_info) (struct mtd_info *mtd, struct otp_info *buf, size_t len);  
  35.     int (*read_fact_prot_reg) (struct mtd_info *mtd, loff_t from, size_t len, size_t *retlen, u_char *buf);  
  36.     int (*get_user_prot_info) (struct mtd_info *mtd, struct otp_info *buf, size_t len);  
  37.     int (*read_user_prot_reg) (struct mtd_info *mtd, loff_t from, size_t len, size_t *retlen, u_char *buf);  
  38.     int (*write_user_prot_reg) (struct mtd_info *mtd, loff_t from, size_t len, size_t *retlen, u_char *buf);  
  39.     int (*lock_user_prot_reg) (struct mtd_info *mtd, loff_t from, size_t len);  
  40.   
  41.     int (*writev) (struct mtd_info *mtd, const struct kvec *vecs, unsigned long count, loff_t to, size_t *retlen);  
  42.     int (*panic_write) (struct mtd_info *mtd, loff_t to, size_t len, size_t *retlen, const u_char *buf);  
  43.     /* Sync */  
  44.     void (*sync) (struct mtd_info *mtd);  
  45.   
  46.     /* Chip-supported device locking */  
  47.     int (*lock) (struct mtd_info *mtd, loff_t ofs, uint64_t len);  
  48.     int (*unlock) (struct mtd_info *mtd, loff_t ofs, uint64_t len);  
  49.   
  50.     /* 电源管理函数 */  
  51.     int (*suspend) (struct mtd_info *mtd);  
  52.     void (*resume) (struct mtd_info *mtd);  
  53.   
  54.     /* 坏块管理函数 */  
  55.     int (*block_isbad) (struct mtd_info *mtd, loff_t ofs);  
  56.     int (*block_markbad) (struct mtd_info *mtd, loff_t ofs);  
  57.   
  58.     void (*unpoint) (struct mtd_info *mtd, loff_t from, size_t len);  
  59.     unsigned long (*get_unmapped_area) (struct mtd_info *mtd,  
  60.                         unsigned long len,  
  61.                         unsigned long offset,  
  62.                         unsigned long flags);  
  63.     struct backing_dev_info *backing_dev_info;  
  64.     struct notifier_block reboot_notifier;  /* default mode before reboot */  
  65.   
  66.     /* ECC status information */  
  67.     struct mtd_ecc_stats ecc_stats;  
  68.     int subpage_sft;  
  69.     struct device dev;  
  70.     int usecount;  
  71.     int (*get_device) (struct mtd_info *mtd);  
  72.     void (*put_device) (struct mtd_info *mtd);  
  73. };  
mtd_info结构体中的read()、write()、read_oob()、write_oob()、erase()是MTD设备驱动要实现的主要函数,幸运的是Linux大牛已经帮我们实现了一套适合大部分FLASH设备的mtd_info成员函数。

如果MTD设备只有一个分区,那么使用下面两个函数注册和注销MTD设备。

  1. int add_mtd_device(struct mtd_info *mtd)  
  2. int del_mtd_device (struct mtd_info *mtd)  
如果MTD设备存在其他分区,那么使用下面两个函数注册和注销MTD设备。
  1. int add_mtd_partitions(struct mtd_info *master,const struct mtd_partition *parts,int nbparts)  
  2. int del_mtd_partitions(struct mtd_info *master)  
其中mtd_partition结构体表示分区的信息

  1. struct mtd_partition {  
  2.     char *name;             /* 分区名,如TQ2440_Board_uboot、TQ2440_Board_kernel、TQ2440_Board_yaffs2 */  
  3.     uint64_t size;          /* 分区大小 */  
  4.     uint64_t offset;        /* 分区偏移值 */  
  5.     uint32_t mask_flags;    /* 掩码标识,不重要 */  
  6.     struct nand_ecclayout *ecclayout;   /* OOB布局 */  
  7.     struct mtd_info **mtdp;     /* pointer to store the MTD object */  
  8. };  
  9. 其中nand_ecclayout结构体:  
  10. struct nand_ecclayout {  
  11.     __u32 eccbytes;     /* ECC字节数 */  
  12.     __u32 eccpos[64];   /* ECC校验码在OOB区域存放位置 */  
  13.     __u32 oobavail;       
  14.     /* 除了ECC校验码之外可用的OOB字节数 */  
  15.     struct nand_oobfree oobfree[MTD_MAX_OOBFREE_ENTRIES];  
  16. };  
关于nand_ecclayout结构体实例,更多可参考drivers/mtd/nand/nand_base.c下的nand_oob_8、nand_oob_16、nand_oob_64实例。
MTD设备层:

mtd字符设备接口:

/drivers/mtd/mtdchar.c文件实现了MTD字符设备接口,通过它,可以直接访问Flash设备,与前面的字符驱动一样,通过file_operations结构体里面的open()、read()、write()、ioctl()可以读写Flash,通过一系列IOCTL 命令可以获取Flash 设备信息、擦除Flash、读写NAND 的OOB、获取OOB layout 及检查NAND 坏块等(MEMGETINFO、MEMERASE、MEMREADOOB、MEMWRITEOOB、MEMGETBADBLOCK IOCRL) 

mtd块设备接口:

/drivers/mtd/mtdblock.c文件实现了MTD块设备接口,主要原理是将Flash的erase block 中的数据在内存中建立映射,然后对其进行修改,最后擦除Flash 上的block,将内存中的映射块写入Flash 块。整个过程被称为read/modify/erase/rewrite 周期。 但是,这样做是不安全的,当下列操作序列发生时,read/modify/erase/poweroff,就会丢失这个block 块的数据。
MTD硬件驱动层:

Linux内核再MTD层下实现了通用的NAND驱动(/driver/mtd/nand/nand_base.c),因此芯片级的NAND驱动不再需要实现mtd_info结构体中的read()、write()、read_oob()、write_oob()等成员函数。

MTD使用nand_chip来表示一个NAND FLASH芯片, 该结构体包含了关于Nand Flash的地址信息,读写方法,ECC模式,硬件控制等一系列底层机制。

  1. struct nand_chip {  
  2.     void  __iomem   *IO_ADDR_R;     /* 读8位I/O线地址 */  
  3.     void  __iomem   *IO_ADDR_W;     /* 写8位I/O线地址 */  
  4.   
  5.     /* 从芯片中读一个字节 */  
  6.     uint8_t (*read_byte)(struct mtd_info *mtd);       
  7.     /* 从芯片中读一个字 */  
  8.     u16     (*read_word)(struct mtd_info *mtd);       
  9.     /* 将缓冲区内容写入芯片 */  
  10.     void    (*write_buf)(struct mtd_info *mtd, const uint8_t *buf, int len);      
  11.     /* 读芯片读取内容至缓冲区/ */  
  12.     void    (*read_buf)(struct mtd_info *mtd, uint8_t *buf, int len);  
  13.     /* 验证芯片和写入缓冲区中的数据 */  
  14.     int     (*verify_buf)(struct mtd_info *mtd, const uint8_t *buf, int len);  
  15.     /* 选中芯片 */  
  16.     void    (*select_chip)(struct mtd_info *mtd, int chip);  
  17.     /* 检测是否有坏块 */  
  18.     int     (*block_bad)(struct mtd_info *mtd, loff_t ofs, int getchip);  
  19.     /* 标记坏块 */  
  20.     int     (*block_markbad)(struct mtd_info *mtd, loff_t ofs);  
  21.     /* 命令、地址、数据控制函数 */  
  22.     void    (*cmd_ctrl)(struct mtd_info *mtd, int dat,unsigned int ctrl);  
  23.     /* 设备是否就绪 */  
  24.     int     (*dev_ready)(struct mtd_info *mtd);  
  25.     /* 实现命令发送 */  
  26.     void    (*cmdfunc)(struct mtd_info *mtd, unsigned command, int column, int page_addr);  
  27.     int     (*waitfunc)(struct mtd_info *mtd, struct nand_chip *this);  
  28.     /* 擦除命令的处理 */  
  29.     void    (*erase_cmd)(struct mtd_info *mtd, int page);  
  30.     /* 扫描坏块 */  
  31.     int     (*scan_bbt)(struct mtd_info *mtd);  
  32.     int     (*errstat)(struct mtd_info *mtd, struct nand_chip *thisint state, int status, int page);  
  33.     /* 写一页 */  
  34.     int     (*write_page)(struct mtd_info *mtd, struct nand_chip *chip,  
  35.                       const uint8_t *buf, int page, int cached, int raw);  
  36.   
  37.     int     chip_delay;         /* 由板决定的延迟时间 */  
  38.     /* 与具体的NAND芯片相关的一些选项,如NAND_NO_AUTOINCR,NAND_BUSWIDTH_16等 */  
  39.     unsigned int    options;      
  40.   
  41.     /* 用位表示的NAND芯片的page大小,如某片NAND芯片 
  42.      * 的一个page有512个字节,那么page_shift就是9  
  43.      */  
  44.     int      page_shift;  
  45.     /* 用位表示的NAND芯片的每次可擦除的大小,如某片NAND芯片每次可 
  46.      * 擦除16K字节(通常就是一个block的大小),那么phys_erase_shift就是14 
  47.      */  
  48.     int      phys_erase_shift;  
  49.     /* 用位表示的bad block table的大小,通常一个bbt占用一个block, 
  50.      * 所以bbt_erase_shift通常与phys_erase_shift相等 
  51.      */  
  52.     int      bbt_erase_shift;  
  53.     /* 用位表示的NAND芯片的容量 */  
  54.     int      chip_shift;  
  55.     /* NADN FLASH芯片的数量 */  
  56.     int      numchips;  
  57.     /* NAND芯片的大小 */  
  58.     uint64_t chipsize;  
  59.     int      pagemask;  
  60.     int      pagebuf;  
  61.     int      subpagesize;  
  62.     uint8_t  cellinfo;  
  63.     int      badblockpos;  
  64.     nand_state_t    state;  
  65.     uint8_t     *oob_poi;  
  66.     struct nand_hw_control  *controller;  
  67.     struct nand_ecclayout   *ecclayout; /* ECC布局 */  
  68.       
  69.     struct nand_ecc_ctrl ecc;   /* ECC校验结构体,里面有大量的函数进行ECC校验 */  
  70.     struct nand_buffers *buffers;  
  71.     struct nand_hw_control hwcontrol;  
  72.     struct mtd_oob_ops ops;  
  73.     uint8_t     *bbt;  
  74.     struct nand_bbt_descr   *bbt_td;  
  75.     struct nand_bbt_descr   *bbt_md;  
  76.     struct nand_bbt_descr   *badblock_pattern;  
  77.     void        *priv;  
  78. };  
最后,我们来用图表的形式来总结一下,MTD设备层、MTD原始设备层、FLASH硬件驱动层之间的联系。



MTD设备及其驱动

阅读数 1256

linux下的mtd

阅读数 447

没有更多推荐了,返回首页