Linux内核MTD子系统二之设备驱动模型

来源:互联网 发布:陈意涵用的跑步软件 编辑:程序博客网 时间:2024/06/06 01:23
    

经过UBOOT初步的移植,Linux内核初步的移植,Linux内核总线设备模型的分析,等一系列

痛苦的折腾,目的就是想更好的来分析下NANDFLASH的驱动。。大概一共历经了半个月

的时间,慢慢的对NANDFLASH驱动程序有感觉了。。。

一、MTD体系结构:Linux内核提供MTD子系统来建立FLASH针对Linux的统一、抽象接口。MTD将文件系统与底层的FLASH存储器进行隔离。

      引入MTD后Linux系统中对FLASH的设备驱动分为4层

    

设备节点:用户在/dev目录下使用mknod命令建立MTD字符设备节点(主设备号为90),或者MTD块设备节点(主设备号为31),使用该设备节点即可访问MTD设备。

MTD设备层:基于MTD原始设备层,系统将MTD设备可以定义为MTD字符(在/mtd/mtdchar.c中实现,设备号90)和MTD块设备(在/mtd/mtdblock.c中实现,设备号31)。

MTD原始设备层:MTD原始设备层由两部分构成,一部分是MTD原始设备的通用代码,另一部分是各个特定Flash的数据,如分区。

                主要构成的文件有:

                     drivers/mtd/mtdcore.c支持mtd字符设备

                     driver/mtd/mtdpart.c支持mtd块设备


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

二、Linux内核中基于MTD的NANDFLASH驱动代码布局:

在Linux2.6.35内核中,MTD源代码放在driver/mtd目录中,该目录中包含chips、devices、maps、nand、onenand、lpdrr、tests和ubi八个子目录。
其中只有nand和onenand目录中的代码才与NAND驱动相关,不过nand目录中的代码比较通用,而onenand目录中的代码相对于nand中的代码而言则简化了很多,它是针对三星公司开发的另一类Flash芯片,即OneNAND Flash。
本文我们需要关注的代码是linux-2.6.35/drivers/mtd/nand目录中,在该目录中我们关心的文件如下:
1、  nand_base.c:
定义了NAND驱动中对NAND芯片最基本的操作函数和操作流程,如擦除、读写page、读写oob等。当然这些函数都只是进行一些default的操作,若你的系统在对NAND操作时有一些特殊的动作,则需要在你自己的驱动代码中进行定义,然后Replace这些default的函数。
2、  nand_bbt.c:
定义了NAND驱动中与坏块管理有关的函数和结构体。
3、  nand_ids.c:
定义了两个全局类型的结构体:struct nand_flash_dev nand_flash_ids[ ]和struct nand_manufacturers nand_manuf_ids[ ]。其中前者定义了一些NAND芯片的类型,后者定义了NAND芯片的几个厂商。NAND芯片的ID至少包含两项内容:厂商ID和厂商为自己的NAND芯片定义的芯片ID。当NAND驱动被加载的时候,它会去读取具体NAND芯片的ID,然后根据读取的内容到上述定义的nand_manuf_ids[ ]和nand_flash_ids[ ]两个结构体中去查找,以此判断该NAND芯片是那个厂商的产品,以及该NAND芯片的类型。若查找不到,则NAND驱动就会加载失败,因此在开发NAND驱动前必须事先将你的NAND芯片添加到这两个结构体中去(其实这两个结构体中已经定义了市场上绝大多数的NAND芯片,所以除非你的NAND芯片实在比较特殊,否则一般不需要额外添加)。值得一提的是,nand_flash_ids[ ]中有三项属性比较重要,即pagesize、chipsize和erasesize,驱动就是依据这三项属性来决定对NAND芯片进行擦除,读写等操作时的大小的。其中pagesize即NAND芯片的页大小,一般为256、512或2048;chipsize即NAND芯片的容量;erasesize即每次擦除操作的大小,通常就是NAND芯片的block大小。
4、  nand_ecc.c:
定义了NAND驱动中与softeware ECC有关的函数和结构体,若你的系统支持hardware ECC,且不需要software ECC,则该文件也不需理会。
上面这些内容我是Copy别人的我觉得写得太好了,因为一开始我真的很迷茫,在nand目录下有那么多的文件,到底哪个是值得我读的.我真的不值得,读了这个大神的博客后对NANDDLASH的驱动我不再是那么的迷茫。

三、NANDFLASH的硬件特性

要想读懂后面Linux系统中对NANDFLASH硬件驱动代码,了解NANDFLASH的硬件特性这是再好不过的。

1、NANDFLASH的内部布局

    

2、Nand Flash的物理存储单元的阵列组织结构(以开发板上的K9F2G08为例)

    

        K9F2G08的大小是256M

    a)block:"Block是Nand Flash的擦除操作的基本/最小单位",一片NANDFLASH(chip)由很多块

(block)组成,块的大小一般是 128KB, 256KB,512KB,此处是 128KB。。其他的小于 128KB 的,

比如 64KB称之为small block的Nand Flash。

    b)page:"page是读写操作的最小单位",每一个block里面包又含了许多page(页),每个页的大小,

对于现在常见的Nand Flash多数是2KB,最新的Nand Flash的是4KB、8KB等,这类的页大小大于

2KB的NandFlash,被称作 big block的 Nand Flash,对应的发读写命令地址,一共 5个周期(cycle),

而老的 Nand Flash,页大小是 256B,512B,,这类的 Nand Flash被称作 small block的nandflash

地址周期只有4个。

    c)oob:每一个页,对应还有一块区域,叫做空闲区域(spare area)/冗余区域(redundant area)而
Linux 系统中,一般叫做 OOB(Out Of Band),这个区域,是最初基于Nand Flash的硬件特
性:数据在读写时候相对容易错误,所以为了保证数据的正确性,必须要有对应的检测和纠
错机制,此机制被叫做 EDC(Error Detection Code)/ECC(Error Code Correction, 或者  Error 
Checking and Correcting),所以设计了多余的区域,用于放置数据的校验值。 
Oob 的读写操作,一般是随着页的操作一起完成的,即读写页的时候,对应地就读写了 oob。  
关于 oob具体用途,总结起来有: 
   1、 标记是否是坏快 
   2、存储ECC数据 
   3、存储一些和文件系统相关的数据。如 jffs2 就会用到这些空间存储一些特定信息,
   4、而yaffs2 文件系统,会在 oob中,存放很多和自己文件系统相关的信息。 

3、K9F2G08的引脚定义

    

      IO7~IO0:用于输入地址/数据/命令,输出数据

    CLE:命令锁存使能位,在发送命令之前要先将模式寄存器中设置CLE使能(高电平有效)。

    ALE:地址锁存使能位,在发送地址之前,要先将模式寄存器中设置ALE使能(高电平有效)。

    CE:(nFCE)芯片的片选信号,操作nandflash前应该拉低该位使之选中该芯片。

    RE:(nFRE)读使能,低电平有效,读之前使CE有效。

    WE:(nFWE)写使能,低电平有效,写之前必须使WE有效。

    WP:写保护低电平有效

    R/B:(R/nB)Ready/Busy Output,就绪/忙,主要用于在发送完编程/擦除命令后,检测这些操作是

否完成,忙,表示编程/擦除操作仍在进行中,就绪表示操作完成。(其中就绪:高电平,忙:低电平)。

四、常见的NANDFLASH的操作

      1、要实现对 Nand Flash 的操作,比如读取一页的数据,写入一页的数据等,都要发送对应的命令,而且要符合硬件的规定,如图:

    

比如说要实现读一页的数据,就要发送Read命令,而且分两个周期发送,即分两次发送对应的命令,第一次是 0x00h,第二次是 0x30h,而两次命令中间,需要发送对应的你所要读取的页的地址,对应地,其他常见的一些操作,比如写一个页的数据(Page Program),就是先发送 0x80h,然后发送要写入的地址,再发送0x10h。

    2、读(Read)nandflash操作过程分析

    

    1)红色竖线穿过的第一行,是 CLE。前面介绍命令所存使能(CLE)的那个引脚将CLE 置 1,就说明你将要通过 I/O 复用端口发送进入Nand Flash的,是命令,而不是地址或者其他类型的数据。只有这样将 CLE 置 1,使其有效,才能去通知了内部硬件逻辑,你接下来将收到的是命令,内部硬件逻辑才会直到收到的是命令,放到命令寄存器中,才能实现后面正确的操作,否则,不去将 CLE 置 1使其有效硬件会无所适从,不知道你传入的到底是数据还是命令了。

    2)而第二行,是 CE,那一刻的值是 0。这个道理很简单,你既然要向Nand Flash发命令,那么先要选中它,所以,要保证 CE为低电平,使其有效,也就是片选有效。

    3)第三行是 WE,意思是写使能。因为接下来是往 Nand Flash里面写命令,所以,要使得 WE有效所以设为低电平。

    4)第四行,是 ALE 是低电平,而 ALE 是高电平有效,此时意思就是使其无效。而对应地,前面介绍的使 CLE 有效,因为将要数据的是命令(此时是发送图示所示的读命令第二周期的 0x30) ,而不是地址。如果在其他某些场合,比如接下来的要输入地址的时候,就要使其有效,而使 CLE 无效了。

    5)第五行,RE,此时是高电平,无效。可以看到,知道后面低 6 阶段,才变成低电平,才有效,因为那时候要发生读取命令,去读取数据。

    6)第六行,就是我们重点要介绍的,复用的输入输出 I/O 端口了,此刻,还没有输入数据,接下来,在不同的阶段,会输入或输出不同的数据/地址。

    7)第七行,R/B,高电平,表示 R(Ready)/就绪,因为到了后面的第 5阶段,硬件内部,在第四阶段,接受了外界的读取命令后,把该页的数据一点点送到页寄存器中,这段时间,属于系统在忙着干活,属于忙的阶段所以,R/B才变成低,表示 Busy忙的状态的。 其他的时序的就类似的理解。

3、计算我们要读取或者写入的行地址和例地址

     以mini2440开发板上的K9F2G08为例,此Nand Flash,一共有 2048 个块,每个块内有 64 页,每个页是 2K+64 Bytes。

     假设,我们要访问其中的第 1000个块中的第 25 页中的 1208字节处的地址,此时,我们就要先把具体的地址算出来:

    物理地址
    =块大小*块号 + 页大小*页号 + 页内地址 

    

    从上图可以看出,该FLASH的地址周期一共有5个,2个列地址(Column)周期,3个行地址(Row)周期

    a)对应的列地址就是页内地址,该flash一个页的大小是2K即2048个字节,所以它的地址范围是0~2047,对应的上图的列地址A0-A10就是页内地址。你可能会发现多出了一个A11,从A0-A11,这样一共就有了12位,那它的地址范围就是0~2^12,即0~4096了,实际上,由于我们访问页内地址,可能会访问到 oob 的位置,即 2048-2111 这 64 个字节的范围内,所以,此处实际上只用到了 2048~2111,用于表示页内的 oob 区域,其大小是 64字节。 

    b)对应地,A12~A28,称作页号,页的号码,可以定位到具体是哪一个页,该FLASH一共是64页一共需要6位即A12~A17,而其中A18~A28表示对应的块号,即属于哪个块。

这里有一个很重要的地方就是我们要传入的地址的每一位,就是对应着上表中的 A0 到 A28 ,实际上上表中的 A11是比较特殊的,只有当我们访问页内地址处于 oob的位置,即属于 2048~2111 的时候,A11才会其效果,才会用 A0-A11用来表示对应的某个属于 2048~2111 的某个值,属于 oob 的某个位置,而我们此处的页内地址为 1208,还没有超过 2047 呢,所以 A11肯定是 0

然后我们再来算上面我们要访问的地址:第 1000个块中的第 25 页中的 1208字节处的地址

第 1000个块中的第 25 页中的 1208字节处的地址它对应着的页内地址为:

页内地址 =1208Bytes

              =0x4B8

页      号 =块数*每块多少页 + 块内的页号

              =1000*64 + 25

              =0xFA19

也就是我们要访问0xFA19页内的0x4B8地址,再把这个地址转换成列和行地址

A0~A10是用来表示页内地址的所以把0x4B8拆分成两列

列地址1=0xB8,列地址2=0x04;

再把页号0xFA19拆分成3行

行地址1=0x19,行地址2=0xFA,行地址3=0x0;

    对应的看看linux2.6.35/driver/mtd/nand/nand_base.c中地址的发送

    

上面的column即对应着页内地址,通常情况是0,如果不是0则通过传入进来的地址除于页地址就可得到相应的列地址了。而 page_addr 即页号,就是通过要访问的地址,除于页大小,即可得到。

对于其他操作还正在研究中。。。。。。虽然说上面这些东东大部分都是来自别人的东西,但是我相信现在它已经变成我自己的东西了。。我不记得那个大帅的博客了,因为我是直接把它的博客给保存到本地了。。


尴尬的说:我突然发现在写这些关于NAND驱动的文章的时候,原来我一直是在改写别人的博客。。。。。其实这并不要紧的,我也觉得这不仅仅是一种比较好的学习方法了,为什么呢,因为当我在看他的博客的时候,我明白了一点,然后当我自己要写的时候。。对这个东东又进一步了解一点了。。呵呵Copy也分档次了

五、硬件时序到软件代码的演变过程对nand_base.c部分代码的分析

该文件位于</linux2.6.35/dricer/mtd/nand/nand_base.c>

还是把那个读NAND的硬件时序图给贴上,如下图:

   

①:此阶段,是读命令第一个周期,发送的命令为0x00。
②:此阶段,依次发送列地址,关于这些行地址,列地址等是如何计算出来的,后面的内容
会有详细解释。 
③:此阶段是发送对应的行地址 
④:此阶段是发送读命令第二周期 2nd cycle所对应的命令,0x30 
⑤:此阶段是等待时间,等待 Nand Flash硬件上准备好对应的数据,以便后续读出。 
⑥:此阶段,就是一点点地把所需要的数据读出来。 

MTD 读取数据的入口是 nand_read,然后调用 nand_do_read_ops,此函数主体如下:

static int nand_do_read_ops(struct mtd_info *mtd, loff_t from,
                struct mtd_oob_ops *ops)
{
    /***此处省略部分代码**/

    。。。。。。。。。。。。。。

    while(1) {
           /******省略****/

          .。。。。。。。。。。。。。。。

            if (likely(sndcmd)) {/*#define NAND_CMD_READ0 0*/

                /*1)***读取数据前肯定要先发送对应的读页命令******/

                chip->cmdfunc(mtd, NAND_CMD_READ0, 0x00, page);
                sndcmd = 0;
            }

            /* Now read the page into the buffer */
            if (unlikely(ops->mode == MTD_OOB_RAW))
                ret = chip->ecc.read_page_raw(mtd, chip,
                                  bufpoi, page);
            else if (!aligned && NAND_SUBPAGE_READ(chip) && !oob)
                ret = chip->ecc.read_subpage(mtd, chip, col, bytes, bufpoi);
            else

             /******执行到这里read_page函数读取对应的数据了******/

                ret = chip->ecc.read_page(mtd, chip, bufpoi,
                              page);
            if (ret < 0)
                break;

            /* Transfer not aligned data */
            if (!aligned) {
                if (!NAND_SUBPAGE_READ(chip) && !oob)
                    chip->pagebuf = realpage;
                memcpy(buf, chip->buffers->databuf + col, bytes);
            }

            buf += bytes;
          。。。。。。。。。。。。。。。。。。

    if (mtd->ecc_stats.failed - stats.failed)
        return -EBADMSG;

    return  mtd->ecc_stats.corrected - stats.corrected ? -EUCLEAN : 0;
}

上面这些代码都不需要我们去实现的,使用MTD层的自定义代码就行。。。

nand_command_lp的分析

static void nand_command_lp(struct mtd_info *mtd, unsigned int command,
                int column, int page_addr)
{
    register struct nand_chip *chip = mtd->priv;

    /* Emulate NAND_CMD_READOOB */
    if (command == NAND_CMD_READOOB) {
        column += mtd->writesize;
        command = NAND_CMD_READ0;
    }

    /* Command latch cycle */

   /* 此处就是就是发送读命令的第一个周期1st Cycle的命令,即0x00,对应着上述步骤中的① */

    chip->cmd_ctrl(mtd, command & 0xff,
               NAND_NCE | NAND_CLE | NAND_CTRL_CHANGE);

    if (column != -1 || page_addr != -1) {
        int ctrl = NAND_CTRL_CHANGE | NAND_NCE | NAND_ALE;

        /* Serially input address */
        if (column != -1) {
            /* Adjust columns for 16 bit buswidth */
            if (chip->options & NAND_BUSWIDTH_16)
                column >>= 1;

          /* 发送两个column列地址,对应着上述步骤中的② */

            chip->cmd_ctrl(mtd, column, ctrl);/*发送列地址1*/
            ctrl &= ~NAND_CTRL_CHANGE;
            chip->cmd_ctrl(mtd, column >> 8, ctrl);/*发送列地址2*/
        }
        if (page_addr != -1) {

          /* 接下来是发送三个Row,行地址,对应着上述步骤中的② */

            chip->cmd_ctrl(mtd, page_addr, ctrl);/*发送行地址1*/
            chip->cmd_ctrl(mtd, page_addr >> 8,/*发送行地址2*/
                       NAND_NCE | NAND_ALE);
            /* One more address cycle for devices > 128MiB */
            if (chip->chipsize > (128 << 20))
                chip->cmd_ctrl(mtd, page_addr >> 16,/*发送行地址3*/
                           NAND_NCE | NAND_ALE);
        }
    }
    chip->cmd_ctrl(mtd, NAND_CMD_NONE, NAND_NCE | NAND_CTRL_CHANGE);

    /*
     * program and erase have their own busy handlers
     * status, sequential in, and deplete1 need no delay
     */
    switch (command) {
。。。。。。。。。。。。。
        return;
   /***复位**/
    case NAND_CMD_RESET:
        if (chip->dev_ready)
            break;
        udelay(chip->chip_delay);
        chip->cmd_ctrl(mtd, NAND_CMD_STATUS,
                   NAND_NCE | NAND_CLE | NAND_CTRL_CHANGE);
        chip->cmd_ctrl(mtd, NAND_CMD_NONE,
                   NAND_NCE | NAND_CTRL_CHANGE);
        while (!(chip->read_byte(mtd) & NAND_STATUS_READY)) ;
        return;
    /*读忙信号*/
    case NAND_CMD_RNDOUT:
        /* No ready / busy check necessary */
        chip->cmd_ctrl(mtd, NAND_CMD_RNDOUTSTART,
                   NAND_NCE | NAND_CLE | NAND_CTRL_CHANGE);
        chip->cmd_ctrl(mtd, NAND_CMD_NONE,
                   NAND_NCE | NAND_CTRL_CHANGE);
        return;
/* 接下来发送读命令的第二个周期2nd Cycle的命令,即0x30,对应着上述步骤
中的④ */ 
    case NAND_CMD_READ0:
        chip->cmd_ctrl(mtd, NAND_CMD_READSTART,
                   NAND_NCE | NAND_CLE | NAND_CTRL_CHANGE);
        chip->cmd_ctrl(mtd, NAND_CMD_NONE,
                   NAND_NCE | NAND_CTRL_CHANGE);

        /* This applies to read commands */
    default:
        /*
         * If we don't have access to the busy pin, we apply the given
         * command delay
         */
        if (!chip->dev_ready) {
            udelay(chip->chip_delay);
            return;
        }
    }

    /* Apply this short delay always to ensure that we do wait tWB in
     * any case on any machine. */

/* 此处是对应着④中的tWB的等待时间*/

    ndelay(100);
/* 接下来就是要等待一定的时间,使得Nand Flash硬件上准备好数据,以供你之
后读取,即对应着步骤⑤ */ 
    nand_wait_ready(mtd);
}

还有一个步骤没有实现那就是步骤⑥了一点一点的把数据读出来

 nand_read_page_hwecc分析

static int nand_read_page_hwecc(struct mtd_info *mtd, struct nand_chip *chip,
                uint8_t *buf, int page)
{
   。。。。。。。。。。。。。。。。。。。。。。
    for (i = 0; eccsteps; eccsteps--, i += eccbytes, p += eccsize) {
        chip->ecc.hwctl(mtd, NAND_ECC_READ);

         /**这个最重要了这才是真正的从NAND的缓冲区中把数据给读出来****/

        chip->read_buf(mtd, p, eccsize);
        chip->ecc.calculate(mtd, p, &ecc_calc[i]);
    }

 

  。。。。。。。。。。
    return 0;
}

上面的 read_buf,就是真正的去读取数据的函数了,由于不同的Nand Flash controller 控制器所实现的方式不同,所以这个函数必须在你的 Nand Flash驱动中实现,即MTD 层,能帮我们实现的都实现了,不能实现的,那肯定是我们自己的事情了。。。接下来的工作是什么?MTD原始设备和硬件驱动层的交互了.这个才是我们要去真正实现的。。


    

进过前面3篇文章对NANDFLASH的一些硬件特性以及MTD的上层操作已经有了一个大体概念,这些东西的重要性就像你要吃饭那么你首先得学会拿筷子道理一样吧,应该一样的。

、MTD原始设备层和硬件驱动层的桥梁:

    

熟悉这几个重要的结构体:linux/mtd/mtd.h

struct mtd_info {
    u_char type;               /**内存技术类型(包括MTD_RAM,MTD_ROM,MTD_NANDFLASH等)**/
    uint32_t flags;           /*MTD设备属性标志*/
    uint64_t size;     // Total size of the MTD

    uint32_t erasesize;//MTD设备的擦除单元大小,对于NandFlash来说就是Block的大小
    
    uint32_t writesize;//最小的可写单元的字节数

    uint32_t oobsize;   // Amount of OOB data per block (e.g. 16)
    uint32_t oobavail;  // Available OOB bytes per block

    unsigned int erasesize_shift;
    unsigned int writesize_shift;
    /* Masks based on erasesize_shift and writesize_shift */
    unsigned int erasesize_mask;
    unsigned int writesize_mask;

    // Kernel-only stuff starts here.
    const char *name;
    int index;

    /* ecc layout structure pointer - read only ! */
    struct nand_ecclayout *ecclayout;

    /* Data for variable erase regions. If numeraseregions is zero,
     * it means that the whole device has erasesize as given above.

     *一般为1吧

     */
    int numeraseregions;

    //擦除区域的指针

    struct mtd_erase_region_info *eraseregions;
    //擦除函数将一个erase_info结构放入擦除队列中
    int (*erase) (struct mtd_info *mtd, struct erase_info *instr);

    /* This stuff for eXecute-In-Place */
    /* phys is optional and may be set to NULL */
    int (*point) (struct mtd_info *mtd, loff_t from, size_t len,
            size_t *retlen, void **virt, resource_size_t *phys);

    /* We probably shouldn't allow XIP if the unpoint isn't a NULL */
    void (*unpoint) (struct mtd_info *mtd, loff_t from, size_t len);

    /* Allow NOMMU mmap() to directly map the device (if not NULL)
     * - return the address to which the offset maps
     * - return -ENOSYS to indicate refusal to do the mapping
     */
    unsigned long (*get_unmapped_area) (struct mtd_info *mtd,
                        unsigned long len,
                        unsigned long offset,
                        unsigned long flags);

    /* Backing device capabilities for this device
     * - provides mmap capabilities
     */
    struct backing_dev_info *backing_dev_info;

  //read和write分别用于MTD设备的读和写
    int (*read) (struct mtd_info *mtd, loff_t from, size_t len, size_t *retlen, u_char *buf);
    int (*write) (struct mtd_info *mtd, loff_t to, size_t len, size_t *retlen, const u_char *buf);

    int (*panic_write) (struct mtd_info *mtd, loff_t to, size_t len, size_t *retlen, const u_char *buf);
    //读写MTD设备的OOB区域的数据
    int (*read_oob) (struct mtd_info *mtd, loff_t from,
             struct mtd_oob_ops *ops);
    int (*write_oob) (struct mtd_info *mtd, loff_t to,
             struct mtd_oob_ops *ops);
     //访问一些受保护的寄存器  
    int (*get_fact_prot_info) (struct mtd_info *mtd, struct otp_info *buf, size_t len);
    int (*read_fact_prot_reg) (struct mtd_info *mtd, loff_t from, size_t len, size_t *retlen, u_char *buf);
    int (*get_user_prot_info) (struct mtd_info *mtd, struct otp_info *buf, size_t len);
    int (*read_user_prot_reg) (struct mtd_info *mtd, loff_t from, size_t len, size_t *retlen, u_char *buf);
    int (*write_user_prot_reg) (struct mtd_info *mtd, loff_t from, size_t len, size_t *retlen, u_char *buf);
    int (*lock_user_prot_reg) (struct mtd_info *mtd, loff_t from, size_t len);

    /* kvec-based read/write methods.
       NB: The 'count' parameter is the number of _vectors_, each of
       which contains an (ofs, len) tuple.
    */
    int (*writev) (struct mtd_info *mtd, const struct kvec *vecs, unsigned long count, loff_t to, size_t *retlen);

    /* Sync *//*同步*/
    void (*sync) (struct mtd_info *mtd);

    /* Chip-supported device locking */
    int (*lock) (struct mtd_info *mtd, loff_t ofs, uint64_t len);
    int (*unlock) (struct mtd_info *mtd, loff_t ofs, uint64_t len);

    /* Power Management functions */
    int (*suspend) (struct mtd_info *mtd);
    void (*resume) (struct mtd_info *mtd);

    /* Bad block management functions */
    int (*block_isbad) (struct mtd_info *mtd, loff_t ofs);
    int (*block_markbad) (struct mtd_info *mtd, loff_t ofs);

    struct notifier_block reboot_notifier;  /* default mode before reboot */

    /* ECC status information */
    struct mtd_ecc_stats ecc_stats;
    /* Subpage shift (NAND) */
    int subpage_sft;
   //私有数据 指向map_info结构
    void *priv;

    struct module *owner;
    struct device dev;
    int usecount;

   //设备驱动回调函数

    int (*get_device) (struct mtd_info *mtd);
    void (*put_device) (struct mtd_info *mtd);
};

上面的read()、write()、read_oob()、等都是MTD设备驱动要实现的主要函数,不过这些函数都是透明的不需要我们自己去实现,因为Linux在MTD的下层实现了针对NORFLASH和NANDFLASH的通用mtd_info成员函数。

感觉没什么可写的了,因为这些都不是我要关注的东西,但是又不能不知道有这么回事

这些结构体还是得了解了解

driver/mtd/mtdpart.c

/* Our partition node structure */
struct mtd_part {
    struct mtd_info mtd;    //分区信息
    struct mtd_info *master; //该分区的主分区
    uint64_t offset;              //该分区的偏移量
    struct list_head list;
};

mtd_partition会在MTD原始设备调用add_mtd_partitions()的时候传递分区参数/linux/mtd/partition.h

struct mtd_partition {
    char *name;            /* identifier string */
    uint64_t size;            /* partition size */
    uint64_t offset;        /* offset within the master MTD space */
    uint32_t mask_flags;        /* master MTD flags to mask out for this partition */
    struct nand_ecclayout *ecclayout;    /* out of band layout for this partition (NAND only)*/
};

一个MTD原始设备可以通过mtd_part分割成数个MTD原始设备注册进mtd_table,mtd_table中的每个MTD原始设备都可以被注册成一个MTD设备,有两个函数可以完成这个工作,即add_mtd_device函数和add_mtd_partitions函数。

其中add_mtd_device函数是把整个NAND FLASH注册进MTD Core,而add_mtd_partitions函数则是把NAND FLASH的各个分区分别注册进MTD Core。

int add_mtd_device(struct mtd_info *mtd)

int del_mtd_device(struct mtd_info *mtd)

int add_mtd_partitions(struct mtd_info *master,structmtd_partitions *parts,int nbparts)

int del_mtd_partitions(struct mtd_info *master)

当MTD原始设备调用add_mtd_partitions()的时候它会对每一个新建分区建立一个struct mtd_part 结构将其加入mtd_partitions中并调用add_mtd_device()将此分区做为MTD设备注册进MTD Core,成功的时返回0

重点关注一下add_mtd_partitions(struct mtd_info *master,struct mtd_partitions*parts,int nbparts)其中master就是这个MTD原始设备,parts即NAND的分区信息,nbparts指有几个分区。那么parts和nbparts怎么来的呢,其实上面有一句话已经说了。。举个例子在我们移植Linux内核到我们的开发板的时候我们会对NANDFLASH进行分区 这eilian240_default_nand_part就是起到上面这两个参数的作用如下:

static struct mtd_partition eilian240_default_nand_part[] = {
    [0] = {
        .name    = "bootloader",/*uboot存放的地址对应dev/mtdblock0*/
        .size    = 0x00040000,  /*大小256KB=((D)(0x00040000))/1024*/
        .offset    = 0,
    },
    [1] = {
        .name    = "param",
        .offset = 0x00040000,/***如果UBOOT比较大就放在这个区域可以将前面的覆盖掉**//**0x00040000是偏移量**/
        .size    = 0x00020000,  /**128KB**/
    },
    [2] = {
        .name    = "Linux Kernel",/********用于存放Linux内核镜像dev/mtdblock3*/
        .offset = 0x00060000,
        .size    = 0x00500000,        /*5M*/
    },
    [3] = {
        .name    = "rootfs",
        .offset = 0x00560000,
        .size    = 1024 * 1024 * 1024, //1G因为该移植同时支持大容量的NAND
    },
    [4] = {
        .name    = "nand",
        .offset = 0x00000000,
        .size    = 1024 * 1024 * 1024, //
    }
};
/*arch/arm/plat-samsung/include/plat/nand.h **/



0 0
原创粉丝点击