第19章Flash设备驱动之Linux Flash驱动结构

本章重点

    Flash 在嵌入式系统中必不可少,Flash是 BootLoader、Linux 内核和文件系统的最佳载体。在 Linux 内核中,引入 MTD(内存技术设备) 层为 NOR Flash 和NAND Flash 设备提供统一的接口,使得 Flash 驱动的设计工作大为简化。

    1、 Linux Flash 驱动的结构,主要讲解MTD 系统的层次结构和接口。

    2、讲解NOR Flash 和 NAND Flash 驱动的设计方法,给出设计模板。

    3、以 S3C6410 外围 NOR Flash 和 NAND Flash 为实例讲解NOR Flash 和 NAND Flash 驱动的设计。

    4、如何在 Flash 上建立 cramfs、jffs/jffs2、yaffs/yaffs2 及ubifs 文件系统

19.1 Linux Flash驱动结构

19.1.1 Linux MTD 系统层次

    在 Linux 系统中,提供 MTD(MemoryTechnology Device,内存技术设备)系统来建立Flash 针对 Linux 的统一、抽象的接口。MTD 将文件系统与底层的 Flash 存储器进行隔离,使Flash 驱动工程师无须关心 Flash 作为字符设备和块设备与 Linux 内核的接口。

    如图 19.1 所示,在引入 MTD 后,Linux系统中的 Flash 设备驱动及接口可分为 4 层,从上到下依次是:设备节点MTD 设备层MTD 原始设备层硬件驱动层,这 4 层的作用如下。

第19章Flash设备驱动之Linux Flash驱动结构_第1张图片

图19.1 Linux MTD 系统

1、硬件驱动层:Flash 硬件驱动层负责 Flash 硬件设备的读、写、擦除,Linux MTD 设备的NOR Flash 芯片驱动位于 drivers/mtd/chips 子目录下,NAND 型 Flash 的驱动程序则位于drivers/mtd/nand 子目录下。

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

3、MTD 设备层:基于 MTD 原始设备,Linux 系统可以定义出 MTD 的块设备(主设备号31)和字符设备(设备号 90),构成 MTD 设备层。MTD 字符设备的定义在 mtdchar.c中实现,通过注册一系列 file_operation 函数(lseek、open、close、read、write、ioctl)可实现对 MTD 设备的读写和控制。MTD 块设备定义了一个描述 MTD 块设备的结构 mtdblk_dev,并声明一个名为 mtdblks 的指针数组,这数组中的每一个 mtdblk_dev

和 mtd_table 中的每一个 mtd_info 一一对应。

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

19.1.2 Linux MTD 系统接口

    如图 19.2 所示,在引入 MTD 后,底层 Flash 驱动直接与 MTD 原始设备层交互,利用其提供的接口注册设备和分区。

第19章Flash设备驱动之Linux Flash驱动结构_第2张图片

图19.2 底层 Flash 驱动

        描述 MTD 原始设备的数据结构是 mtd_info,定义了大量关于 MTD 的数据和操作函数,如代码清单 19.1 所示。mtd_info 是表示 MTD 原始设备的结构体,每个分区也被认为是一个 mtd_info,例如,如果有两个MTD 原始设备,每个上有 3 个分区,在系统中就有 6 个 mtd_info 结构体,这些 mtd_info 的指针被存放在名为 mtd_table 的数组里。

代码清单 19.1 mtd_info 结构体

include/linux/mtd/mtd.h

struct mtd_info {
        u_char type;/* 内存技术的类型 */
        u_int32_t flags;/*标志位*/
        u_int32_t size;  /*mtd 设备的大小*/

        /* "Major" erase size for the device. Naïve users may take this
         * to be the only erase size available, or may use the more detailed
         * information below if they desire
         */
        u_int32_t erasesize;/*主要的擦除块大小*/
        /* Minimal writable flash unit size. In case of NOR flash it is 1 (even
         * though individual bits can be cleared), in case of NAND flash it is
         * one NAND page (or half, or one-fourths of it), in case of ECC-ed NOR
         * it is of ECC block size, etc. It is illegal to have writesize = 0.
         * Any driver registering a struct mtd_info must ensure a writesize of
         * 1 or larger.
         */
        u_int32_t writesize;/*最小的可写单元的字节数*/

        u_int32_t oobsize;  /* OOB 字节数*/
        u_int32_t oobavail;  /*可用的 OOB 字节数*/

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

        /* ecc layout structure pointer - read only ! */
        struct nand_ecclayout *ecclayout;/*ECC 布局结构体指针*/

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

        int (*erase) (struct mtd_info *mtd, struct erase_info *instr);

        /* This stuff for eXecute-In-Place */
        int (*point) (struct mtd_info *mtd, loff_t from, size_t len, size_t *retlen, u_char **mtdbuf);

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

        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 (*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);

        /*
         * Methods to access the protection register area, present in some
         * flash devices. The user data is one time programmable but the
         * factory data is read only.
         */
        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, size_t len);
        int (*unlock) (struct mtd_info *mtd, loff_t ofs, size_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;

        void *priv;

        struct module *owner;
        int usecount;

        /* If the driver is something smart, like UBI, it may need to maintain
         * its own reference counting. The below functions are only for driver.
         * The driver may register its callbacks. These callbacks are not
         * supposed to be called by MTD users */
        int (*get_device) (struct mtd_info *mtd);
        void (*put_device) (struct mtd_info *mtd);

};

        mtd_info 的 type 字段给出底层物理设备的类型,包括 MTD_RAM、MTD_ROM、MTD_NORFLASH、MTD_NANDFLASH 等。

        flags 字段标志可以是 MTD_WRITEABLEMTD_BIT_WRITEABLEMTD_NO_ERASE
MTD_POWERUP_LOCK 等的组合。针对 ROM 而言,不具有上述任何属性,因此 MTD_CAP_ROM定义为 0;MTD_CAP_RAM 是 MTD_WRITEABLE、MTD_BIT_WRITEABLE、MTD_NO_ERASE的组合;MTD_CAP_NORFLASH 是 MTD_WRITEABLE、MTD_BIT_WRITEABLE 的组合,对

MTD_CAP_NANDFLASH 则仅意味着 MTD_WRITEABLE。

        mtd_info 中的 read()、write()、read_oob()、write_oob()、erase()是 MTD 设备驱动要实现的主要函数,在 NOR 和 NAND 的驱动代码中几乎看不到 mtd_info 的成员函数,这是因为 Linux 在 MTD 的下层实现了针对 NORFlash 和 NAND Flash 的通用的 mtd_info 成员函数。

        某些内存技术支持带外数据(OOB),例如,NAND Flash 每 512 字节就会有 16 个字节的“额外数据”,用于存放纠错码或文件系统元数据。这是因为,所有 Flash 器件都受“位翻转”现象的困扰,而 NAND 发生的概率比 NOR 大,因此 NAND 厂商推荐在使用 NAND 的时候最好要使用ECC(Error Checking and Correcting)。mtd_info 的 ecclayout 类型即是描述 OOB 区域中 ECC 字节的布局情况。

Flash 驱动中使用如下两个函数注册和注销 MTD 设备:

include/linux/mtd/mtd.h

int add_mtd_device(struct mtd_info *mtd);

int del_mtd_device (struct mtd_info *mtd);

drivers/mtd/mtdcore.c

/**
 *      add_mtd_device - register an MTD device
 *      @mtd: pointer to new MTD device info structure
 *
 *      Add a device to the list of MTD devices present in the system, and
 *      notify each currently active MTD 'user' of its arrival. Returns
 *      zero on success or 1 on failure, which currently will only happen
 *      if the number of present devices exceeds MAX_MTD_DEVICES (i.e. 16)
 */

int add_mtd_device(struct mtd_info *mtd)
{
        int i;

        BUG_ON(mtd->writesize == 0);
        mutex_lock(&mtd_table_mutex);

        for (i=0; i < MAX_MTD_DEVICES; i++)
                if (!mtd_table[i]) {
                        struct list_head *this;

                        mtd_table[i] = mtd;
                        mtd->index = i;
                        mtd->usecount = 0;

                        /* Some chips always power up locked. Unlock them now */
                        if ((mtd->flags & MTD_WRITEABLE)
                            && (mtd->flags & MTD_STUPID_LOCK) && mtd->unlock) {
                                if (mtd->unlock(mtd, 0, mtd->size))
                                        printk(KERN_WARNING  "%s: unlock failed, "
                                               "writes may not work\n",
                                               mtd->name);
                        }

                        DEBUG(0, "mtd: Giving out device %d to %s\n",i, mtd->name);
                        /* No need to get a refcount on the module containing
                           the notifier, since we hold the mtd_table_mutex */
                        list_for_each(this, &mtd_notifiers) {
                                struct mtd_notifier *not = list_entry(this, struct mtd_notifier, list);// 获取指向该结构体的指针
                                not->add(mtd);
                        }


                        mutex_unlock(&mtd_table_mutex);
                        /* We _know_ we aren't being removed, because
                           our caller is still holding us here. So none
                           of this try_ nonsense, and no bitching about it
                           either. :) */
                        __module_get(THIS_MODULE);
                        return 0;
                }

        mutex_unlock(&mtd_table_mutex);
        return 1;

}

/**
 *      del_mtd_device - unregister an MTD device
 *      @mtd: pointer to MTD device info structure
 *
 *      Remove a device from the list of MTD devices present in the system,
 *      and notify each currently active MTD 'user' of its departure.
 *      Returns zero on success or 1 on failure, which currently will happen
 *      if the requested device does not appear to be present in the list.
 */

int del_mtd_device (struct mtd_info *mtd)
{
        int ret;

        mutex_lock(&mtd_table_mutex);

        if (mtd_table[mtd->index] != mtd) {
                ret = -ENODEV;
        } else if (mtd->usecount) {
                printk(KERN_NOTICE "Removing MTD device #%d (%s) with use count %d\n",
                       mtd->index, mtd->name, mtd->usecount);
                ret = -EBUSY;
        } else {
                struct list_head *this;

                /* No need to get a refcount on the module containing
                   the notifier, since we hold the mtd_table_mutex */
                list_for_each(this, &mtd_notifiers) {
                        struct mtd_notifier *not = list_entry(this, struct mtd_notifier, list);//获取指向该结构体的指针
                        not->remove(mtd);
                }


                mtd_table[mtd->index] = NULL;
                module_put(THIS_MODULE);
                ret = 0;
        }

        mutex_unlock(&mtd_table_mutex);
        return ret;

}

        代码清单 19.2 所示的 mtd_part 结构体用于表示分区,其 mtd_info 结构体成员用于描述该分区,它会被加入到 mtd_table中,其大部分成员由其主分区 mtd_part→master 决定,各种函数也指向主分区的相应函数。

代码清单 19.2 mtd_part 结构体

drivers/mtd/mtdpart.c

struct mtd_part {
        struct mtd_info mtd;/*分区的信息(大部分由其 master 决定)*/
        struct mtd_info *master;/*该分区的主分区*/
        u_int32_t offset;/*该分区的偏移地址*/
        int index;/*分区号*/
        struct list_head list;
        int registered;

};

    mtd_partition 会在 MTD 原始设备层调用 add_mtd_partions()时传递分区信息用,这个结构体的定义如代码清单 19.3 所示。

代码清单 19.3 mtd_partition 结构体

include/linux/mtd/partitions.h

/*
 * Partition definition structure:
 *
 * An array of struct partition is passed along with a MTD object to
 * add_mtd_partitions() to create them.
 *
 * For each partition, these fields are available:
 * name: string that will be used to label the partition's MTD device.
 * size: the partition size; if defined as MTDPART_SIZ_FULL, the partition
 *      will extend to the end of the master MTD device.
 * offset: absolute starting position within the master MTD device; if
 *      defined as MTDPART_OFS_APPEND, the partition will start where the
 *      previous one ended; if MTDPART_OFS_NXTBLK, at the next erase block.
 * mask_flags: contains flags that have to be masked (removed) from the
 *      master MTD flag set for the corresponding MTD partition.
 *      For example, to force a read-only partition, simply adding
 *      MTD_WRITEABLE to the mask_flags will do the trick.
 *
 * Note: writeable partitions require their size and offset be
 * erasesize aligned (e.g. use MTDPART_OFS_NEXTBLK).
 */

struct mtd_partition {
        char *name;                     /* 标识字符串 */
        u_int32_t size;                 /* 分区大小 */
        u_int32_t offset;               /* 主 MTD 空间内的偏移*/
        u_int32_t mask_flags;           /* 掩码标志 */
        struct nand_ecclayout *ecclayout;       /* out of band layout for this partition (NAND only)*/
        struct mtd_info **mtdp;         /* pointer to store the MTD object */

};

Flash 驱动中使用如下两个函数注册和注销分区:

include/linux/mtd/partitions.h

int add_mtd_partitions(struct mtd_info *, const struct mtd_partition *, int);

int del_mtd_partitions(struct mtd _ info *master);

drivers/mtd/mtdpart.c

int add_mtd_partitions(struct mtd_info *master,
                       const struct mtd_partition *parts,
                       int nbparts)
{
        struct mtd_part *slave;
        u_int32_t cur_offset = 0;
        int i;

        printk (KERN_NOTICE "Creating %d MTD partitions on \"%s\":\n", nbparts, master->name);

        for (i = 0; i < nbparts; i++) {

                /* allocate the partition structure */
                slave = kzalloc (sizeof(*slave), GFP_KERNEL);
                if (!slave) {
                        printk ("memory allocation error while creating partitions for \"%s\"\n",
                                master->name);
                        del_mtd_partitions(master);
                        return -ENOMEM;
                }
                list_add(&slave->list, &mtd_partitions);

                /* set up the MTD object for this partition */
                slave->mtd.type = master->type;
                slave->mtd.flags = master->flags & ~parts[i].mask_flags;
                slave->mtd.size = parts[i].size;
                slave->mtd.writesize = master->writesize;
                slave->mtd.oobsize = master->oobsize;
                slave->mtd.oobavail = master->oobavail;
                slave->mtd.subpage_sft = master->subpage_sft;

                slave->mtd.name = parts[i].name;
                slave->mtd.owner = master->owner;

                slave->mtd.read = part_read;
                slave->mtd.write = part_write;

                if(master->point && master->unpoint){
                        slave->mtd.point = part_point;
                        slave->mtd.unpoint = part_unpoint;
                }

                if (master->read_oob)
                        slave->mtd.read_oob = part_read_oob;
                if (master->write_oob)
                        slave->mtd.write_oob = part_write_oob;
                if(master->read_user_prot_reg)
                        slave->mtd.read_user_prot_reg = part_read_user_prot_reg;
                if(master->read_fact_prot_reg)
                        slave->mtd.read_fact_prot_reg = part_read_fact_prot_reg;
                if(master->write_user_prot_reg)
                        slave->mtd.write_user_prot_reg = part_write_user_prot_reg;
                if(master->lock_user_prot_reg)
                        slave->mtd.lock_user_prot_reg = part_lock_user_prot_reg;
                if(master->get_user_prot_info)
                        slave->mtd.get_user_prot_info = part_get_user_prot_info;
                if(master->get_fact_prot_info)
                        slave->mtd.get_fact_prot_info = part_get_fact_prot_info;
                if (master->sync)
                        slave->mtd.sync = part_sync;
                if (!i && master->suspend && master->resume) {
                                slave->mtd.suspend = part_suspend;
                                slave->mtd.resume = part_resume;
                }
                if (master->writev)
                        slave->mtd.writev = part_writev;
                if (master->lock)
                        slave->mtd.lock = part_lock;
                if (master->unlock)
                        slave->mtd.unlock = part_unlock;
                if (master->block_isbad)
                        slave->mtd.block_isbad = part_block_isbad;
                if (master->block_markbad)
                        slave->mtd.block_markbad = part_block_markbad;
                slave->mtd.erase = part_erase;
                slave->master = master;
                slave->offset = parts[i].offset;
                slave->index = i;

                if (slave->offset == MTDPART_OFS_APPEND)
                        slave->offset = cur_offset;
                if (slave->offset == MTDPART_OFS_NXTBLK) {
                        slave->offset = cur_offset;
                        if ((cur_offset % master->erasesize) != 0) {
                                /* Round up to next erasesize */
                                slave->offset = ((cur_offset / master->erasesize) + 1) * master->erasesize;
                                printk(KERN_NOTICE "Moving partition %d: "
                                       "0x%08x -> 0x%08x\n", i,
                                       cur_offset, slave->offset);
                        }
                }
                if (slave->mtd.size == MTDPART_SIZ_FULL)
                        slave->mtd.size = master->size - slave->offset;
                cur_offset = slave->offset + slave->mtd.size;


                printk (KERN_NOTICE "0x%08x-0x%08x : \"%s\"\n", slave->offset,
                        slave->offset + slave->mtd.size, slave->mtd.name);


                /* let's do some sanity checks */
                if (slave->offset >= master->size) {
                                /* let's register it anyway to preserve ordering */
                        slave->offset = 0;
                        slave->mtd.size = 0;
                        printk ("mtd: partition \"%s\" is out of reach -- disabled\n",
                                parts[i].name);
                }
                if (slave->offset + slave->mtd.size > master->size) {
                        slave->mtd.size = master->size - slave->offset;
                        printk ("mtd: partition \"%s\" extends beyond the end of device \"%s\" -- size truncated to %#x\n",
                                parts[i].name, master->name, slave->mtd.size);
                }
                if (master->numeraseregions>1) {
                        /* Deal with variable erase size stuff */
                        int i;
                        struct mtd_erase_region_info *regions = master->eraseregions;


                        /* Find the first erase regions which is part of this partition. */
                        for (i=0; i < master->numeraseregions && slave->offset >= regions[i].offset; i++)
                                ;


                        for (i--; i < master->numeraseregions && slave->offset + slave->mtd.size > regions[i].offset; i++) {
                                if (slave->mtd.erasesize < regions[i].erasesize) {
                                        slave->mtd.erasesize = regions[i].erasesize;
                                }
                        }
                } else {
                        /* Single erase size */
                        slave->mtd.erasesize = master->erasesize;
                }


                if ((slave->mtd.flags & MTD_WRITEABLE) &&
                    (slave->offset % slave->mtd.erasesize)) {
                        /* Doesn't start on a boundary of major erase size */
                        /* FIXME: Let it be writable if it is on a boundary of _minor_ erase size though */
                        slave->mtd.flags &= ~MTD_WRITEABLE;
                        printk ("mtd: partition \"%s\" doesn't start on an erase block boundary -- force read-only\n",
                                parts[i].name);
                }
                if ((slave->mtd.flags & MTD_WRITEABLE) &&
                    (slave->mtd.size % slave->mtd.erasesize)) {
                        slave->mtd.flags &= ~MTD_WRITEABLE;
                        printk ("mtd: partition \"%s\" doesn't end on an erase block -- force read-only\n",
                                parts[i].name);
                }


                slave->mtd.ecclayout = master->ecclayout;
                if (master->block_isbad) {
                        uint32_t offs = 0;


                        while(offs < slave->mtd.size) {
                                if (master->block_isbad(master,
                                                        offs + slave->offset))
                                        slave->mtd.ecc_stats.badblocks++;
                                offs += slave->mtd.erasesize;
                        }
                }

                if(parts[i].mtdp)
                {       /* store the object pointer (caller may or may not register it */
                        *parts[i].mtdp = &slave->mtd;
                        slave->registered = 0;
                }
                else
                {
                        /* register our partition */
                        add_mtd_device(&slave->mtd);
                        slave->registered = 1;
                }
        }

        return 0;

}


int del_mtd_partitions(struct mtd_info *master)
{
        struct list_head *node;
        struct mtd_part *slave;

        for (node = mtd_partitions.next;
             node != &mtd_partitions;
             node = node->next) {
                slave = list_entry(node, struct mtd_part, list);
                if (slave->master == master) {
                        struct list_head *prev = node->prev;
                        __list_del(prev, node->next);
                        if(slave->registered)
                                del_mtd_device(&slave->mtd);
                        kfree(slave);
                        node = prev;
                }
        }

        return 0;

}

分析:

    为了使系统能支持 MTD 字符设备与块设备及 MTD 分区,在编译内核时应该包括相应的配置选项,如下所示。

第19章Flash设备驱动之Linux Flash驱动结构_第3张图片




你可能感兴趣的:(Linux驱动开发)