MTD子系统和NAND

先前的文章《虚拟文件系统 (VFS)-基于linux3.10》和《UBIFS文件系统》只是对文件系统进行各层的分析,并没有连贯到读写flash。透过本文可以知道ubifs文件系统发出的读在linux操作系统上是到底是如何完成的。

NAND设备

Linux将裸NAND(区别于emmc、usbstick)归纳到MTD设备类型里,这类设备通常相关的操作通常位于drivers/mtd/nand目录下。

NAND驱动加载过程

图1.1 nand设备初始化

对于使用nand作为存储设备架构的系统,通常会在drivers/mtd/nand目录下有各个厂商定义定用于控制自己NAND控制器驱动,比如海思3516平台会在该目录下有一个hinfc610文件夹,该文件夹里的就是该芯片的NAND驱动,设备的注册过程类似于i2c等设备的驱动,

这里就以图1.1中给出的代码流程来分析该NAND驱动(代码结构非常明晰)的初始化过程。Probe方法是在注册设备时常常会回调的函数,这里就从该函数着手。

ambarella_nand_get_resource首先获取nand相关的资源,这里的获取资源和以前的linux版本区别不大,只是有几个是关于设备树解析,设备树的内容在《 Linux系统启动那些事—基于Linux 3.10内核》一文已经详细解释过了,这里跳过。

ambarella_nand_init_chip(nand_info,pdev->dev.of_node)用于初始化该芯片一些信息,包括ecc校验是软件还是硬件完成、ecc校验的比特数、FLASH大小、nand是否写保护等,其中比较关心的方法是nand读写方法。

[cpp]  view plain  copy
  1. chip->chip_delay = 0;  
  2.     chip->controller = &nand_info->controller;  
  3.     chip->read_byte = amb_nand_read_byte;  
  4.     chip->read_word = amb_nand_read_word;  
  5.     chip->write_buf = amb_nand_write_buf;  
  6.     chip->read_buf = amb_nand_read_buf;  
  7.     chip->select_chip = amb_nand_select_chip;  
  8.     chip->cmd_ctrl = amb_nand_cmd_ctrl;  
  9.     chip->dev_ready = amb_nand_dev_ready;  
  10.     chip->waitfunc = amb_nand_waitfunc;  
  11.     chip->cmdfunc = amb_nand_cmdfunc;  

该函数的最后将chip指针赋值给nand_info的->mtd.priv,这样方便查找chip。

[cpp]  view plain  copy
  1. nand_info->mtd.priv = chip;  

这里不妨来看看读实现的细节,尽管这是下一节MTD的内容。

[cpp]  view plain  copy
  1. static u16 amb_nand_read_word(struct mtd_info *mtd)  
  2. {  
  3.     struct ambarella_nand_info      *nand_info;  
  4.     u16                 *data;  
  5.   
  6.     nand_info = (struct ambarella_nand_info *)mtd->priv;  
  7.   
  8.     data = (u16 *)(nand_info->dmabuf + nand_info->dma_bufpos);  
  9.     nand_info->dma_bufpos += 2;  
  10.   
  11.     return *data;  
  12. }  

读一个字的处理非常简单,就是将buf(基地址)和pos(偏移)的内容按16位长度取出即可,这对应到汇编指令就是一个movw指定。

nand_scan_ident用于检测nand芯片接口是否有效。

nand_scan_tail函数的参数是一个structmtd_info类型的参数。该函数是nand_scan()第二阶段扫描函数,该函数初始化未初始化的函数指针,然后扫描坏块信息表。

[cpp]  view plain  copy
  1. chip->write_page = nand_write_page;  
  2. chip->onfi_set_features = nand_onfi_set_features;  
  3. chip->onfi_get_features = nand_onfi_get_features;  
  4. chip->ecc.read_page = nand_read_page_hwecc;  
  5. chip->ecc.write_page = nand_write_page_hwecc;  
  6. chip->ecc.read_page_raw = nand_read_page_raw;  
  7. chip->ecc.write_page_raw = nand_write_page_raw;  
  8. chip->ecc.read_oob = nand_read_oob_std;  
  9. chip->ecc.write_oob = nand_write_oob_std;  
  10. chip->ecc.read_subpage = nand_read_subpage;  
  11. chip->ecc.write_subpage = nand_write_subpage_hwecc;  
  12.   
  13. mtd->type = MTD_NANDFLASH;  
  14. mtd->flags = (chip->options & NAND_ROM) ? MTD_CAP_ROM :   MTD_CAP_NANDFLASH;  
  15. mtd->_erase = nand_erase;  
  16. mtd->_point = NULL;  
  17. mtd->_unpoint = NULL;  
  18. mtd->_read = nand_read;  
  19. mtd->_write = nand_write;  
  20. mtd->_panic_write = panic_nand_write;  
  21. mtd->_read_oob = nand_read_oob;  
  22. mtd->_write_oob = nand_write_oob;  
  23. mtd->_sync = nand_sync;  
  24. mtd->_lock = NULL;  
  25. mtd->_unlock = NULL;  
  26. mtd->_suspend = nand_suspend;  
  27. mtd->_resume = nand_resume;  
  28. mtd->_block_isbad = nand_block_isbad;  
  29. mtd->_block_markbad = nand_block_markbad;  
  30. mtd->writebufsize = mtd->writesize;  
  31.   
  32. /* propagate ecc info to mtd_info */  
  33. mtd->ecclayout = chip->ecc.layout;  
  34. mtd->ecc_strength = chip->ecc.strength;  

最后调用returnchip->scan_bbt(mtd);返回坏块表,scan_bbt实际上指向nand_default_bbt函数。

mtd_device_parse_register用于解析设备树并创建MTD分区,实际上静态设备树并没有这些分区信息,这些分区信息是在uboot下通过修改内存里的设备树添加的内容。

图1.2 mtd分区实例

NAND读写流程

接下来看mtd->_read =nand_read;这个实现的细节。

[cpp]  view plain  copy
  1. <drivers/mtd/nand/nand_base.c>  
  2. 1576 static int nand_read(struct mtd_info *mtd, loff_t from, size_t len,  
  3. 1577              size_t *retlen, uint8_t *buf)  
  4. 1578 {  
  5. 1579     struct mtd_oob_ops ops;  
  6. 1580     int ret;  
  7. 1581     nand_get_device(mtd, FL_READING);  
  8. 1582     ops.len = len;  
  9. 1583     ops.datbuf = buf;  
  10. 1584     ops.oobbuf = NULL;  
  11. 1585     ops.mode = MTD_OPS_PLACE_OOB;  
  12. 1586     ret = nand_do_read_ops(mtd, from, &ops);  
  13. 1587     *retlen = ops.retlen;  
  14. 1588     nand_release_device(mtd);  
  15. 1589     return ret;  
  16. 1590 }  

为了让说明过程更为清晰,首先来看图1.3,该图显示了nand_read在一个完整的读流程中所处在的位置。由文件系统发起的一次读可能要多次调用ubi_io_read才能完成,这也是圆形箭头存在的意义。最低层的amb_nand_read_buf是安霸SDK提供的安霸芯片读写flash方法。如果是其它厂商,则该函数的命名会有所区别。

图1.3 基于raw nand的读流程

当操作系统加载到内核以后一般是读写内存而非flash了。下面要分析的是安霸平台的nand操作方法,整个读流程的框架直接参考图1.3就可以了,实际上nand的读取细节要比框架难些,也是最有意思的部分。而amb_nand_cmdfunc则是一个nand命令分发器,类似于http的302跳转,由于该文件声明遵循GNU标准了,所以这里完全展示该函数:

[cpp]  view plain  copy
  1. <drivers/mtd/nand/ambarella_nand.c>  
  2. static void amb_nand_cmdfunc(struct mtd_info *mtd, unsigned command,  
  3.     int column, int page_addr)  
  4. {  
  5.     struct ambarella_nand_info      *nand_info;  
  6.   
  7.     nand_info = (struct ambarella_nand_info *)mtd->priv;  
  8.     nand_info->err_code = 0;  
  9.   
  10.     switch(command) {  
  11.     case NAND_CMD_RESET:  
  12.         nand_amb_reset(nand_info);  
  13.         break;  
  14.     case NAND_CMD_READID:  
  15.         nand_info->dma_bufpos = 0;  
  16.         nand_amb_read_id(nand_info);  
  17.         break;  
  18.     case NAND_CMD_STATUS:  
  19.         nand_info->dma_bufpos = 0;  
  20.         nand_amb_read_status(nand_info);  
  21.         break;  
  22.     case NAND_CMD_ERASE1:  
  23.         nand_amb_erase(nand_info, page_addr);  
  24.         break;  
  25.     case NAND_CMD_ERASE2:  
  26.         break;  
  27.     case NAND_CMD_READOOB:  
  28.         nand_info->dma_bufpos = column;  
  29.         if (nand_info->ecc_bits > 1) {  
  30.             u8 area = nand_info->soft_ecc ? MAIN_ONLY : MAIN_ECC;  
  31.             nand_info->dma_bufpos = mtd->writesize;  
  32.             nand_amb_read_data(nand_info, page_addr,  
  33.                     nand_info->dmaaddr, area);  
  34.         } else {  
  35.             nand_amb_read_data(nand_info, page_addr,  
  36.                     nand_info->dmaaddr, SPARE_ONLY);  
  37.         }  
  38.         break;  
  39.     case NAND_CMD_READ0:  
  40.     {  
  41.         u8 area = nand_info->soft_ecc ? MAIN_ONLY : MAIN_ECC;  
  42.   
  43.         nand_info->dma_bufpos = column;  
  44.         nand_amb_read_data(nand_info, page_addr, nand_info->dmaaddr, area);  
  45.         if (nand_info->ecc_bits == 1)  
  46.             nand_amb_read_data(nand_info, page_addr,  
  47.                 nand_info->dmaaddr + mtd->writesize, SPARE_ONLY);  
  48.         break;  
  49.     }  
  50.     case NAND_CMD_SEQIN:  
  51.         nand_info->dma_bufpos = column;  
  52.         nand_info->seqin_column = column;  
  53.         nand_info->seqin_page_addr = page_addr;  
  54.         break;  
  55.     case NAND_CMD_PAGEPROG:  
  56.     {  
  57.         u32 mn_area, sp_area, offset;  
  58.   
  59.         mn_area = nand_info->soft_ecc ? MAIN_ONLY : MAIN_ECC;  
  60.         sp_area = nand_amb_is_hw_bch(nand_info) ? SPARE_ECC : SPARE_ONLY;  
  61.         offset = (nand_info->ecc_bits > 1) ? 0 : mtd->writesize;  
  62.   
  63.         if (nand_info->seqin_column < mtd->writesize) {  
  64.             nand_amb_write_data(nand_info,  
  65.                 nand_info->seqin_page_addr,  
  66.                 nand_info->dmaaddr, mn_area);  
  67.             if (nand_info->soft_ecc && nand_info->ecc_bits == 1) {  
  68.                 nand_amb_write_data(nand_info,  
  69.                     nand_info->seqin_page_addr,  
  70.                     nand_info->dmaaddr + mtd->writesize,  
  71.                     sp_area);  
  72.             }  
  73.         } else {  
  74.             nand_amb_write_data(nand_info,  
  75.                 nand_info->seqin_page_addr,  
  76.                 nand_info->dmaaddr + offset,  
  77.                 sp_area);  
  78.         }  
  79.         break;  
  80.     }  
  81.     default:  
  82.         dev_err(nand_info->dev, "%s: 0x%x, %d, %d\n",  
  83.                 __func__, command, column, page_addr);  
  84.         BUG();  
  85.         break;  
  86.     }  
  87. }  

一个switch语句实现命令分发,这里只关心图1.3中的那个命令,

[cpp]  view plain  copy
  1. case NAND_CMD_READ0:  
  2.     {  
  3.         u8 area = nand_info->soft_ecc ? MAIN_ONLY : MAIN_ECC;  
  4.   
  5.         nand_info->dma_bufpos = column;  
  6.         nand_amb_read_data(nand_info, page_addr, nand_info->dmaaddr, area);  
  7.         if (nand_info->ecc_bits == 1)  
  8.             nand_amb_read_data(nand_info, page_addr,  
  9.                 nand_info->dmaaddr + mtd->writesize, SPARE_ONLY);  
  10.   
  11.         break;  
  12.     }  

该命令的核心函数是nand_amb_read_data,该函数同样GNUlicense,照样贴出:

[cpp]  view plain  copy
  1. int nand_amb_read_data(struct ambarella_nand_info *nand_info,  
  2.     u32 page_addr, dma_addr_t buf_dma, u8 area)  
  3. {  
  4.     int                 errorCode = 0;  
  5.     u32                 addr_hi;  
  6.     u32                 addr;  
  7.     u32                 len;  
  8.     u64                 addr64;  
  9.     u8                  ecc = 0;  
  10.   
  11.     addr64 = (u64)(page_addr * nand_info->mtd.writesize);  
  12.     addr_hi = (u32)(addr64 >> 32);  
  13.     addr = (u32)addr64;  
  14.   
  15.     switch (area) {  
  16.     case MAIN_ONLY:  
  17.         ecc = EC_MDSD;  
  18.         len = nand_info->mtd.writesize;  
  19.         break;  
  20.     case MAIN_ECC:  
  21.         ecc = EC_MESD;  
  22.         len = nand_info->mtd.writesize;  
  23.         break;  
  24.     case SPARE_ONLY:  
  25.         ecc = EC_MDSD;  
  26.         len = nand_info->mtd.oobsize;  
  27.         break;  
  28.     case SPARE_ECC:  
  29.         ecc = EC_MDSE;  
  30.         len = nand_info->mtd.oobsize;  
  31.         break;  
  32.     default:  
  33.         dev_err(nand_info->dev, "%s: Wrong area.\n", __func__);  
  34.         errorCode = -EINVAL;  
  35.         goto nand_amb_read_page_exit;  
  36.         break;  
  37.     }  
  38.   
  39.     nand_info->slen = 0;  
  40.     if (nand_info->ecc_bits > 1) {  
  41.         /* when use BCH, the EG and EC should be 0 */  
  42.         ecc = 0;  
  43.         len = nand_info->mtd.writesize;  
  44.         nand_info->slen = nand_info->mtd.oobsize;  
  45.         nand_info->spare_buf_phys = buf_dma + len;  
  46.     }  
  47.   
  48.     nand_info->cmd = NAND_AMB_CMD_READ;  
  49.     nand_info->addr_hi = addr_hi;  
  50.     nand_info->addr = addr;  
  51.     nand_info->buf_phys = buf_dma;  
  52.     nand_info->len = len;  
  53.     nand_info->area = area;  
  54.     nand_info->ecc = ecc;  
  55.   
  56.     errorCode = nand_amb_request(nand_info);  
  57.   
  58. nand_amb_read_page_exit:  
  59.     return errorCode;  
  60. }  

该函数前面一直在初始化nand_info成员,最后突然调用nand_amb_request函数,可知该函数应该是比较重要的,读flash的所有信息存放在了nand_info里。amb_request函数有点长,不过还好遵循GNUlicense。

[cpp]  view plain  copy
  1. static int nand_amb_request(struct ambarella_nand_info *nand_info)  
  2. {  
  3.     int                 errorCode = 0;  
  4.     u32                 cmd;  
  5.     u32                 nand_ctr_reg = 0;  
  6.     u32                 nand_cmd_reg = 0;  
  7.     u32                 fio_ctr_reg = 0;  
  8.     long                    timeout;  
  9.   
  10.     cmd = nand_info->cmd;  
  11.   
  12.     switch (cmd) {  
  13.   
  14.     case NAND_AMB_CMD_READ:  
  15.         nand_ctr_reg |= NAND_CTR_A(nand_info->addr_hi);  
  16.   
  17.         if (nand_amb_is_hw_bch(nand_info)) {//硬件校验ECC  
  18.             /* Setup FIO DMA Control Register */  
  19.             nand_amb_enable_bch(nand_info);  
  20.             /* in dual space mode,enable the SE bit */  
  21.             nand_ctr_reg |= NAND_CTR_SE;  
  22.   
  23.             /* Clean Flash_IO_ecc_rpt_status Register */  
  24.             amba_writel(nand_info->regbase + FIO_ECC_RPT_STA_OFFSET, 0x0);  
  25.         } else if (nand_amb_is_sw_bch(nand_info)) {//软件校验ECC  
  26.             /* Setup FIO DMA Control Register */  
  27.             nand_amb_enable_dsm(nand_info);  
  28.             /* in dual space mode,enable the SE bit */  
  29.             nand_ctr_reg |= NAND_CTR_SE;  
  30.         } else {  
  31.             if (nand_info->area == MAIN_ECC)  
  32.                 nand_ctr_reg |= (NAND_CTR_SE);  
  33.             else if (nand_info->area == SPARE_ONLY ||  
  34.                 nand_info->area == SPARE_ECC)  
  35.                 nand_ctr_reg |= (NAND_CTR_SE | NAND_CTR_SA);  
  36.   
  37.             fio_ctr_reg = amba_readl(nand_info->regbase + FIO_CTR_OFFSET);  
  38.   
  39.             if (nand_info->area == SPARE_ONLY ||  
  40.                 nand_info->area == SPARE_ECC  ||  
  41.                 nand_info->area == MAIN_ECC)  
  42.                 fio_ctr_reg |= (FIO_CTR_RS);  
  43.   
  44.             switch (nand_info->ecc) {  
  45.             case EC_MDSE:  
  46.                 nand_ctr_reg |= NAND_CTR_EC_SPARE;  
  47.                 fio_ctr_reg |= FIO_CTR_CO;  
  48.                 break;  
  49.             case EC_MESD:  
  50.                 nand_ctr_reg |= NAND_CTR_EC_MAIN;  
  51.                 fio_ctr_reg |= FIO_CTR_CO;  
  52.                 break;  
  53.             case EC_MESE:  
  54.                 nand_ctr_reg |= (NAND_CTR_EC_MAIN | NAND_CTR_EC_SPARE);  
  55.                 fio_ctr_reg |= FIO_CTR_CO;  
  56.                 break;  
  57.             case EC_MDSD:  
  58.             default:  
  59.                 break;  
  60.             }  
  61.   
  62.             amba_writel(nand_info->regbase + FIO_CTR_OFFSET,  
  63.                         fio_ctr_reg);  
  64.         }  
  65.   
  66.         amba_writel(nand_info->regbase + FLASH_CTR_OFFSET, nand_ctr_reg);  
  67.         nand_amb_setup_dma_devmem(nand_info);  
  68.   
  69.         break;  
  70.   
  71.     default:  
  72.         dev_warn(nand_info->dev,  
  73.             "%s: wrong command %d!\n", __func__, cmd);  
  74.         errorCode = -EINVAL;  
  75.         goto nand_amb_request_done;  
  76.         break;  
  77.     }  
  78.   
  79.     if (cmd == NAND_AMB_CMD_READ || cmd == NAND_AMB_CMD_PROGRAM) {  
  80.         timeout = wait_event_timeout(nand_info->wq,  
  81.             atomic_read(&nand_info->irq_flag) == 0x0, 1 * HZ);  
  82.         if (timeout <= 0) {  
  83.             errorCode = -EBUSY;  
  84.             dev_err(nand_info->dev, "%s: cmd=0x%x timeout 0x%08x\n",  
  85.                 __func__, cmd, atomic_read(&nand_info->irq_flag));  
  86.         } else {  
  87.             dev_dbg(nand_info->dev, "%ld jiffies left.\n", timeout);  
  88.         }  
  89.   
  90.         if (nand_info->dma_status & (DMA_CHANX_STA_OE | DMA_CHANX_STA_ME |  
  91.             DMA_CHANX_STA_BE | DMA_CHANX_STA_RWE |  
  92.             DMA_CHANX_STA_AE)) {  
  93.             dev_err(nand_info->dev,  
  94.                 "%s: Errors happend in DMA transaction %d!\n",  
  95.                 __func__, nand_info->dma_status);  
  96.             errorCode = -EIO;  
  97.             goto nand_amb_request_done;  
  98.         }  
  99.   
  100.         if (nand_amb_is_hw_bch(nand_info)) {  
  101.             if (cmd == NAND_AMB_CMD_READ) {  
  102.                 if (nand_info->fio_ecc_sta & FIO_ECC_RPT_FAIL) {  
  103.                     int ret = 0;  
  104.                     /* Workaround for page never used, BCH will be failed */  
  105.                     if (nand_info->area == MAIN_ECC || nand_info->area == SPARE_ECC)  
  106.                         ret = nand_bch_spare_cmp(nand_info);  
  107.   
  108.                     if (ret < 0) {  
  109.                         nand_info->mtd.ecc_stats.failed++;  
  110.                         dev_err(nand_info->dev,  
  111.                             "BCH corrected failed (0x%08x), addr is 0x[%x]!\n",  
  112.                             nand_info->fio_ecc_sta, nand_info->addr);  
  113.                     }  
  114.                 } else if (nand_info->fio_ecc_sta & FIO_ECC_RPT_ERR) {  
  115.                     nand_info->mtd.ecc_stats.corrected++;  
  116.                     /* once bitflip and data corrected happened, BCH will keep on 
  117.                      * to report bitflip in following read operations, even though 
  118.                      * there is no bitflip happened really. So this is a workaround 
  119.                      * to get it back. */  
  120.                     nand_amb_corrected_recovery(nand_info);  
  121.                 }  
  122.             } else if (cmd == NAND_AMB_CMD_PROGRAM) {  
  123.                 if (nand_info->fio_ecc_sta & FIO_ECC_RPT_FAIL) {  
  124.                     dev_err(nand_info->dev,  
  125.                         "BCH program program failed (0x%08x)!\n",  
  126.                         nand_info->fio_ecc_sta);  
  127.                 }  
  128.             }  
  129.         }  
  130.   
  131.         if ((nand_info->fio_dma_sta & FIO_DMASTA_RE)  
  132.             || (nand_info->fio_dma_sta & FIO_DMASTA_AE)  
  133.             || !(nand_info->fio_dma_sta & FIO_DMASTA_DN)) {  
  134.             u32 block_addr;  
  135.             block_addr = nand_info->addr /  
  136.                     nand_info->mtd.erasesize *  
  137.                     nand_info->mtd.erasesize;  
  138.             dev_err(nand_info->dev,  
  139.                 "%s: dma_status=0x%08x, cmd=0x%x, addr_hi=0x%x, "  
  140.                 "addr=0x%x, dst=0x%x, buf=0x%x, "  
  141.                 "len=0x%x, area=0x%x, ecc=0x%x, "  
  142.                 "block addr=0x%x!\n",  
  143.                 __func__,  
  144.                 nand_info->fio_dma_sta,  
  145.                 cmd,  
  146.                 nand_info->addr_hi,  
  147.                 nand_info->addr,  
  148.                 nand_info->dst,  
  149.                 nand_info->buf_phys,  
  150.                 nand_info->len,  
  151.                 nand_info->area,  
  152.                 nand_info->ecc,  
  153.                 block_addr);  
  154.             errorCode = -EIO;  
  155.             goto nand_amb_request_done;  
  156.         }  
  157.     }  
  158.   
  159. nand_amb_request_done:  
  160.     atomic_set(&nand_info->irq_flag, 0x7);  
  161.     nand_info->dma_status = 0;  
  162.     /* Avoid to flush previous error info */  
  163.     if (nand_info->err_code == 0)  
  164.         nand_info->err_code = errorCode;  
  165.   
  166.     if ((nand_info->nand_wp) &&  
  167.         (cmd == NAND_AMB_CMD_ERASE || cmd == NAND_AMB_CMD_COPYBACK ||  
  168.          cmd == NAND_AMB_CMD_PROGRAM || cmd == NAND_AMB_CMD_READSTATUS)) {  
  169.             nand_ctr_reg |= NAND_CTR_WP;  
  170.             amba_writel(nand_info->regbase + FLASH_CTR_OFFSET, nand_ctr_reg);  
  171.     }  
  172.   
  173.     if ((cmd == NAND_AMB_CMD_READ || cmd == NAND_AMB_CMD_PROGRAM)  
  174.         && nand_amb_is_hw_bch(nand_info))  
  175.         nand_amb_disable_bch(nand_info);  
  176.   
  177.     fio_unlock(SELECT_FIO_FL);  
  178.   
  179. nand_amb_request_exit:  
  180.     return errorCode;  
  181. }  
后面就是读写nand控制寄存器了,这里需要说明的是这里读方式,一次会读会使用DMA方式读取一个页的内容,而不是一个字节一个字节读。

MTD子系统

MTD(memorytechnology device内存技术设备)用于访问memory设备(ROM、flash)的linux子系统。在Linux中其被作为一种类型的设备文件以访问flash,该层用于在特定的flash芯片驱动层和上层硬件之间提供一层抽象。

MTD层的代码在drivers/mtd目录下。该目录下的Makefile文件内容如下:

图2.1 MTDMakefile

如果Makemenuconfig将MTD层选中(M或者Y)如下图:

图2.2 MTD编译配置选项

上述读出的信息源于各个Kconfig文件。

obj-$(CONFIG_MTD)                 += mtd.o这行意味着将这个目录下的文件编译成mtd.o这个目标,在后续链接内核映像时会使用到这个目标文件。

mtd-y哪行时必须编译的,不论有没有使用这一选项,mtdcore.o使用了Makefile的隐式规则,其由mtdcore.c文件生成,其它的依次类推。这一行代表着mtd抽象层(也有进一步将其分为MTD原始层和MTD设备层)。

obj-y这一行指定了目录,将会进入这个目录下去寻找目标并编译,这行的内容可以看成是芯片级驱动。遵循CFI(Common flash interface,Intel发起的NOR flash标准接口)驱动位于chips目录下,nand指的是NAND型flash驱动所在的目录。Maps子目录存放特定flash数据。

最后由于涉及的UBIFS,所以这里最后一行的UBI也是需要看看的。

UBIFS文件系统》一文中的读代码流程图显示了mtd_read是其读过程的一环。在上一节中遇到过该函数,mtd->_read = nand_read; mtd->_write = nand_write;只是那里没有展开。

图1.2所示的那些分区,每一个分区对应一个一个struct mtd_info的结构体。

[cpp]  view plain  copy
  1. struct mtd_info {  
  2. /** 
  3. *可选类型 
  4. #define MTD_ABSENT      0 
  5. #define MTD_RAM         1 
  6. #define MTD_ROM         2 
  7. #define MTD_NORFLASH        3 
  8. #define MTD_NANDFLASH       4 
  9. #define MTD_DATAFLASH       6 
  10. #define MTD_UBIVOLUME       7 
  11. #define MTD_MLCNANDFLASH    8 
  12. */  
  13.     u_char type;  
  14.   
  15. /**可选flag 
  16. #define MTD_WRITEABLE       0x400   /* Device is writeable */  
  17. #define MTD_BIT_WRITEABLE   0x800   /* Single bits can be flipped */  
  18. #define MTD_NO_ERASE        0x1000  /* No erase necessary */  
  19. #define MTD_POWERUP_LOCK    0x2000  /* Always locked after reset */  
  20. #define MTD_CAP_ROM     0  
  21. #define MTD_CAP_RAM     (MTD_WRITEABLE | MTD_BIT_WRITEABLE | MTD_NO_ERASE)  
  22. #define MTD_CAP_NORFLASH    (MTD_WRITEABLE | MTD_BIT_WRITEABLE)  
  23. #define MTD_CAP_NANDFLASH   (MTD_WRITEABLE)  
  24. */  
  25.     uint32_t flags;  
  26.     uint64_t size;   // Total size of the MTD  
  27.   
  28.     /* "Major" erase size for the device. Na茂ve users may take this 
  29.      * to be the only erase size available, or may use the more detailed 
  30.      * information below if they desire 
  31.      */  
  32.     uint32_t erasesize;  
  33.     /* Minimal writable flash unit size. In case of NOR flash it is 1 (even 
  34.      * though individual bits can be cleared), in case of NAND flash it is 
  35.      * one NAND page (or half, or one-fourths of it), in case of ECC-ed NOR 
  36.      * it is of ECC block size, etc. It is illegal to have writesize = 0. 
  37.      * Any driver registering a struct mtd_info must ensure a writesize of 
  38.      * 1 or larger. 
  39.      */  
  40.     uint32_t writesize;  
  41.   
  42.     /* 
  43.      * Size of the write buffer used by the MTD. MTD devices having a write 
  44.      * buffer can write multiple writesize chunks at a time. E.g. while 
  45.      * writing 4 * writesize bytes to a device with 2 * writesize bytes 
  46.      * buffer the MTD driver can (but doesn't have to) do 2 writesize 
  47.      * operations, but not 4. Currently, all NANDs have writebufsize 
  48.      * equivalent to writesize (NAND page size). Some NOR flashes do have 
  49.      * writebufsize greater than writesize. 
  50.      */  
  51.     uint32_t writebufsize;  
  52.   
  53.     uint32_t oobsize;   // Amount of OOB data per block (e.g. 16)  
  54.     uint32_t oobavail;  // Available OOB bytes per block  
  55.   
  56.     /* 
  57.      * If erasesize is a power of 2 then the shift is stored in 
  58.      * erasesize_shift otherwise erasesize_shift is zero. Ditto writesize. 
  59.      */  
  60.     unsigned int erasesize_shift;  
  61.     unsigned int writesize_shift;  
  62.     /* Masks based on erasesize_shift and writesize_shift */  
  63.     unsigned int erasesize_mask;  
  64.     unsigned int writesize_mask;  
  65.     int (*_erase) (struct mtd_info *mtd, struct erase_info *instr);  
  66.     int (*_point) (struct mtd_info *mtd, loff_t from, size_t len,  
  67.                size_t *retlen, void **virt, resource_size_t *phys);  
  68.     int (*_unpoint) (struct mtd_info *mtd, loff_t from, size_t len);  
  69.     unsigned long (*_get_unmapped_area) (struct mtd_info *mtd,  
  70.                          unsigned long len,  
  71.                          unsigned long offset,  
  72.                          unsigned long flags);  
  73.     int (*_read) (struct mtd_info *mtd, loff_t from, size_t len,  
  74.               size_t *retlen, u_char *buf);  
  75.     int (*_write) (struct mtd_info *mtd, loff_t to, size_t len,  
  76.                size_t *retlen, const u_char *buf);  
  77.     int (*_panic_write) (struct mtd_info *mtd, loff_t to, size_t len,  
  78.                  size_t *retlen, const u_char *buf);  
  79.     int (*_read_oob) (struct mtd_info *mtd, loff_t from,  
  80.               struct mtd_oob_ops *ops);  
  81.     int (*_write_oob) (struct mtd_info *mtd, loff_t to,  
  82.                struct mtd_oob_ops *ops);  
  83.     int (*_get_fact_prot_info) (struct mtd_info *mtd, struct otp_info *buf,  
  84.                     size_t len);  
  85. };  

这里再回到上一节的图1.1的mtd_device_parse_register函数来。该函数在这里的调用代码如下:

ppdata.of_node =pdev->dev.of_node;//设备树节点信息包含的是MTD分区信息

mtd_device_parse_register(mtd,NULL, &ppdata, NULL, 0);

该函数首先调用parse_mtd_partitions解析设备树得到MTD分区信息。

[cpp]  view plain  copy
  1. int mtd_device_parse_register(struct mtd_info *mtd, const char * const *types,  
  2.                   struct mtd_part_parser_data *parser_data,  
  3.                   const struct mtd_partition *parts,  
  4.                   int nr_parts)  
  5. {  
  6.     int err;  
  7.     struct mtd_partition *real_parts;  
  8.   
  9.     //该函数将解析得到的分许信息存放在real_parts里  
  10.     err = parse_mtd_partitions(mtd, NULL, &real_parts, &ppdata);  
  11.   
  12.     if (err > 0) {  
  13.         err = add_mtd_partitions(mtd, real_parts, err);  
  14.         kfree(real_parts);  
  15.     } else if (err == 0) {  
  16.         err = add_mtd_device(mtd);  
  17.         if (err == 1)  
  18.             err = -ENODEV;  
  19.     }  
  20.   
  21.     return err;  
  22. }  

parse_mtd_partitions函数主要是要找到解析的函数,该类函数由part_parsers串接在一起,通过匹配名称的方式,这些名称在不显示指定的情况下默认有cmdlinepart和ofpart两种,解析函数是由register_mtd_parser注册的。通过这两种默认的解析名称可以看出cmdlinepart方法使用于命令行方式解析MTD分区信息,而ofpart适用于设备树解析解析MTD分区信息,这里走的是设备树方式,所以可能看到类似下面的输出:

[cpp]  view plain  copy
  1. 16 ofpart partitions found on MTD device amba_nand  

如果返回值大于0,则说明解析到有MTD分区需要创建,这是会调用add_mtd_partitions将分区新添加到MTD设备里。如下给出了我的嵌入式平台633行打印的信息:

[cpp]  view plain  copy
  1. Creating 16 MTD partitions on "amba_nand":  
  2. <drivers/mtd/mtdpart.c>  
  3. 625 int add_mtd_partitions(struct mtd_info *master,  
  4. 626                const struct mtd_partition *parts,  
  5. 627                int nbparts)  
  6. 628 {  
  7. 629     struct mtd_part *slave;  
  8. 630     uint64_t cur_offset = 0;  
  9. 631     int i;  
  10. 632   
  11. 633     printk(KERN_NOTICE "Creating %d MTD partitions on \"%s\":\n", nbparts, master->name);  
  12. 634   
  13. 635     for (i = 0; i < nbparts; i++) {  
  14. 636         slave = allocate_partition(master, parts + i, i, cur_offset);  
  15. 637         if (IS_ERR(slave))  
  16. 638             return PTR_ERR(slave);  
  17. 639   
  18. 640         mutex_lock(&mtd_partitions_mutex);  
  19. 641         list_add(&slave->list, &mtd_partitions);  
  20. 642         mutex_unlock(&mtd_partitions_mutex);  
  21. 643   
  22. 644         add_mtd_device(&slave->mtd);  
  23. 645   
  24. 646         cur_offset = slave->offset + slave->mtd.size;  
  25. 647     }  
  26. 648   
  27. 649     return 0;  
  28. 650 }  

该函数的核心工作由allocate_partition和add_mtd_device这两个函数完成,linux下函数命名真的非常贴切,这里根据函数名就可以知道意义,首先根据解析设备树得到的分区信息创建MTD分区,然后将这个创建好的分区注册。

由于不同的MTD分区均存在于同一块flash上面,所以allocate_partition创建的新分区一些参数可由master参数继承,这就会看到如下的代码:

[cpp]  view plain  copy
  1. slave->mtd.type = master->type;  
  2.     slave->mtd.flags = master->flags & ~part->mask_flags;  
  3.     slave->mtd.size = part->size;  
  4.     slave->mtd.writesize = master->writesize;  
  5.     slave->mtd.writebufsize = master->writebufsize;  
  6.     slave->mtd.oobsize = master->oobsize;  
  7.     slave->mtd.oobavail = master->oobavail;  
  8.     slave->mtd.subpage_sft = master->subpage_sft;  
  9.   
  10.     slave->mtd.name = name;  
  11.     slave->mtd.owner = master->owner;  
  12.     slave->mtd.backing_dev_info = master->backing_dev_info;  
  13.   
  14.     /* NOTE:  we don't arrange MTDs as a tree; it'd be error-prone 
  15.      * to have the same data be in two different partitions. 
  16.      */  
  17.     slave->mtd.dev.parent = master->dev.parent;  
  18. if (master->_get_unmapped_area)  
  19.         slave->mtd._get_unmapped_area = part_get_unmapped_area;  
  20.     if (master->_read_oob)  
  21.         slave->mtd._read_oob = part_read_oob;  
  22.     if (master->_write_oob)  
  23. …  

除了上述显示的继承了父分区参数外,还有隐式继承方式,比如如下的代码:

[cpp]  view plain  copy
  1. slave->mtd._read = part_read;  
  2.     slave->mtd._write = part_write;  
  3. slave->mtd._erase = part_erase;  

这些方式实际上是对父方法的封装,part_read中的读的关键代码就可以证明这一点。

[cpp]  view plain  copy
  1. part->master->_read(part->master, from + part->offset, len, retlen, buf);  

随后就是做一些安全性检查,这包括新分区的偏移地址是否超出flash容量,偏移地址加分区大小是否超出flash容量等,最后根据传递进来的flag参数对新分区的mtd分区flag参数进行适当的设置。

641行将新创建的分区链接到mtd_partitions链表上,所有的MTD分区都将串接到这个链表上。

644行添加一个MTD分区类型的设备。其参数是allocate_partition创建的新分区。该函数首先根据MTD类型(RAM、ROM…)设置其bdi(backing dev info)信息,这些信息就是对设备操作的权限设置。

[cpp]  view plain  copy
  1. if (!mtd->backing_dev_info) {  
  2.         switch (mtd->type) {  
  3.         case MTD_RAM:  
  4.             mtd->backing_dev_info = &mtd_bdi_rw_mappable;  
  5.             break;  
  6.         case MTD_ROM:  
  7.             mtd->backing_dev_info = &mtd_bdi_ro_mappable;  
  8.             break;  
  9.         default:  
  10.             mtd->backing_dev_info = &mtd_bdi_unmappable;  
  11.             break;  
  12.         }  
  13.     }  

接下来需要申请一个idr(integerID management,小整形ID数集合),这个数用来索引MTD分区。所以会把这个数存放在mtd的索引字段。

[cpp]  view plain  copy
  1. mtd->index = i;  

接下来设置设备的信息,这些信息包括设备类型、设备所述的类、设备号、设备名称等:

[cpp]  view plain  copy
  1. mtd->dev.type = &mtd_devtype;  
  2.     mtd->dev.class = &mtd_class;  
  3.     mtd->dev.devt = MTD_DEVT(i);  
  4.     dev_set_name(&mtd->dev, "mtd%d", i);  
  5.     dev_set_drvdata(&mtd->dev, mtd);  

完成上述操作后还需要注册设备,这些都是标准的接口流程了。

[cpp]  view plain  copy
  1. if (device_register(&mtd->dev) != 0)  
  2.         goto fail_added;  

注册完成后还需要创建这个设备类:

[cpp]  view plain  copy
  1. device_create(&mtd_class, mtd->dev.parent,  
  2.                   MTD_DEVT(i) + 1,  
  3.                   NULL, "mtd%dro", i);  

最后将这一事件添加到通知链上去,这一组件也是内核的标准组件,网络中很多需要异步通知的都采用了这种方式。

[cpp]  view plain  copy
  1. list_for_each_entry(not, &mtd_notifiers, list)  
  2.         not->add(mtd);  
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值