linux设备驱动(32)MTD子系统详解

MTD(Memory Technology Device)即常说的Flash等使用存储芯片的存储设备,MTD子系统对应的是块设备驱动框架中的设备驱动层,可以说,MTD就是针对Flash设备设计的标准化硬件驱动框架。

1 MTD子系统框架

(1)设备节点层:MTD框架可以在/dev下创建字符设备节点(主设备号90)以及块设备节点(主设备号31), 用户通过访问此设备节点即可访问MTD字符设备或块设备。

(2)MTD设备层: 基于MTD原始设备, Linux在这一层次定义出了MTD字符设备和块设备, 字符设备在mtdchar.c中实现, 块设备则是通过结构mtdblk_dev来描述,"/drivers/mtd/mtdchar.c"文件实现了MTD字符设备接口; "/drivers/mtd/mtdblock.c"文件实现了MTD块设备接口

(3)MTD原始设备层: 由MTD原始设备的通用代码+特定的Flash数据组成。mtd_info、mtd_part、mtd_partition以及mtd_partitions等对象及其操作方法就属于这一层,对应的文件是"drivers/mtd/mtdcore.c"。类似于i2c驱动框架中的核心层。

(4)硬件驱动层: 内核将常用的flash操作都已经在这个层次实现, 驱动开发只需要将相应的设备信息添加进去即可, 比如,NOR flash的芯片驱动位于"drivers/mtd/chips/", Nand flash位于"drivers/mtd/nand/"(eg s3c2410.c)

2 主要数据结构

2.1 mtd_info 结构体

描述原始设备层的一个分区的结构, 描述一个设备或一个多分区设备中的一个分区。定义位于:include\linux\mtd\mtd.h

本身是没有list_head来供内核管理,对mtd_info对象的管理是通过mtd_part来实现的。mtd_info对象属于原始设备层,里面的很多函数接口内核已经实现了。mtd_info中的read()/write()等操作是MTD设备驱动要实现的主要函数,在NORFlash或NANDFlash中的驱动代码中几乎看不到mtd_info的成员函数,即这些函数对于Flash芯片是透明的,因为Linux在MTD的下层实现了针对NORFlash和NANDFlash的通用的mtd_info函数。

  1 struct mtd_info {
  2     u_char type;//MTD设备类型,有MTD_RAM,MTD_ROM、MTD_NORFLASH、MTD_NAND_FLASH
  3     uint32_t flags;//读写及权限标志位,有MTD_WRITEABLE、MTD_BIT_WRITEABLE、MTD_NO_ERASE、MTD_UP_LOCK
  4     uint64_t size;     // Total size of the MTD MTD设备的大小
  5     uint32_t erasesize;//主要的擦除块大小,NandFlash就是"块"的大小
  6     uint32_t writesize;//最小可写字节数,NandFlash一般对应"页"的大小
  7     /*
  8      * Size of the write buffer used by the MTD. MTD devices having a write
  9      * buffer can write multiple writesize chunks at a time. E.g. while
 10      * writing 4 * writesize bytes to a device with 2 * writesize bytes
 11      * buffer the MTD driver can (but doesn't have to) do 2 writesize
 12      * operations, but not 4. Currently, all NANDs have writebufsize
 13      * equivalent to writesize (NAND page size). Some NOR flashes do have
 14      * writebufsize greater than writesize.
 15      */
 16     uint32_t writebufsize;
 17 
 18     uint32_t oobsize;   //一个block中的OOB字节数
 19     uint32_t oobavail;  //一个block中可用oob的字节数
 20 
 21     unsigned int erasesize_shift;
 22     unsigned int writesize_shift;
 23     /* Masks based on erasesize_shift and writesize_shift */
 24     unsigned int erasesize_mask;
 25     unsigned int writesize_mask;
 26 
 27     unsigned int bitflip_threshold;
 28 
 29     // Kernel-only stuff starts here.
 30     const char *name;
 31     int index;
 32 
 33     /* ECC layout structure pointer - read only! */
 34     struct nand_ecclayout *ecclayout;//ECC布局结构体指针
 35 
 36     /* max number of correctible bit errors per ecc step */
 37     unsigned int ecc_strength;
 38 
 39     /* Data for variable erase regions. If numeraseregions is zero,
 40      * it means that the whole device has erasesize as given above.
 41      */
 42     int numeraseregions;
 43     struct mtd_erase_region_info *eraseregions;
 44 
 45     /*
 46      * Do not call via these pointers, use corresponding mtd_*()
 47      * wrappers instead.
 48      */
 49     int (*_erase) (struct mtd_info *mtd, struct erase_info *instr);
 50     int (*_point) (struct mtd_info *mtd, loff_t from, size_t len,//针对eXecute-In-Place,即XIP
 51                size_t *retlen, void **virt, resource_size_t *phys);
 52     int (*_unpoint) (struct mtd_info *mtd, loff_t from, size_t len);//如果这个指针为空,不允许XIP
 53     unsigned long (*_get_unmapped_area) (struct mtd_info *mtd,
 54                          unsigned long len,
 55                          unsigned long offset,
 56                          unsigned long flags);
 57     int (*_read) (struct mtd_info *mtd, loff_t from, size_t len,//读函数指针
 58               size_t *retlen, u_char *buf);
 59     int (*_write) (struct mtd_info *mtd, loff_t to, size_t len,//写函数指针
 60                size_t *retlen, const u_char *buf);
 61     int (*_panic_write) (struct mtd_info *mtd, loff_t to, size_t len,
 62                  size_t *retlen, const u_char *buf);
 63     int (*_read_oob) (struct mtd_info *mtd, loff_t from,
 64               struct mtd_oob_ops *ops);
 65     int (*_write_oob) (struct mtd_info *mtd, loff_t to,
 66                struct mtd_oob_ops *ops);
 67     int (*_get_fact_prot_info) (struct mtd_info *mtd, struct otp_info *buf,
 68                     size_t len);
 69     int (*_read_fact_prot_reg) (struct mtd_info *mtd, loff_t from,
 70                     size_t len, size_t *retlen, u_char *buf);
 71     int (*_get_user_prot_info) (struct mtd_info *mtd, struct otp_info *buf,
 72                     size_t len);
 73     int (*_read_user_prot_reg) (struct mtd_info *mtd, loff_t from,
 74                     size_t len, size_t *retlen, u_char *buf);
 75     int (*_write_user_prot_reg) (struct mtd_info *mtd, loff_t to,
 76                      size_t len, size_t *retlen, u_char *buf);
 77     int (*_lock_user_prot_reg) (struct mtd_info *mtd, loff_t from,
 78                     size_t len);
 79     int (*_writev) (struct mtd_info *mtd, const struct kvec *vecs,
 80             unsigned long count, loff_t to, size_t *retlen);
 81     void (*_sync) (struct mtd_info *mtd);
 82     int (*_lock) (struct mtd_info *mtd, loff_t ofs, uint64_t len);
 83     int (*_unlock) (struct mtd_info *mtd, loff_t ofs, uint64_t len);
 84     int (*_is_locked) (struct mtd_info *mtd, loff_t ofs, uint64_t len);
 85     int (*_block_isbad) (struct mtd_info *mtd, loff_t ofs);
 86     int (*_block_markbad) (struct mtd_info *mtd, loff_t ofs);
 87     int (*_suspend) (struct mtd_info *mtd);
 88     void (*_resume) (struct mtd_info *mtd);
 89     /*
 90      * If the driver is something smart, like UBI, it may need to maintain
 91      * its own reference counting. The below functions are only for driver.
 92      */
 93     int (*_get_device) (struct mtd_info *mtd);
 94     void (*_put_device) (struct mtd_info *mtd);
 95 
 96     /* Backing device capabilities for this device
 97      * - provides mmap capabilities
 98      */
 99     struct backing_dev_info *backing_dev_info;
100 
101     struct notifier_block reboot_notifier;  /* default mode before reboot */
102 
103     /* ECC status information */
104     struct mtd_ecc_stats ecc_stats;
105     /* Subpage shift (NAND) */
106     int subpage_sft;
107 
108     void *priv;//私有数据
109 
110     struct module *owner;
111     struct device dev;
112     int usecount;
113 }

2.2 mtd_part结构体

内核管理分区的链表节点,通过它来实现对mtd_info对象的管理。定义 位于:\linux-3.10.73\drivers\mtd\mtdpart.c

1 struct mtd_part {
2           struct mtd_info mtd;//对应的mtd_info对象
3           struct mtd_info *master; //父对象指针                                        
4           uint64_t offset;//偏移量
5           struct list_head list;//链表节点
6   };

2.3 mtd_partition 

描述一个分区,定义位于:\include\linux\mtd\partitions.h

1 struct mtd_partition {
2     char *name;            /*分区名 */
3     uint64_t size;            /*分区大小,使用MTDPART_SIZ_FULL表示使用全部空间 */
4     uint64_t offset;        /*分区在master设备中的偏移量。MTDPART_OFS_APPEND表示从上一个分区结束的地方开始,MTDPART_OFS_NXTBLK表示从下一个擦除块开始; MTDPART_OFS_RETAIN表示尽可能向后偏,把size大小的空间留下即可*/
5     uint32_t mask_flags;        /* 权限掩码,MTD_WRITEABLE表示将父设备的只读选项变成可写(可写分区要求size和offset要erasesize对齐,eg MTDPART_OFS_NEXTBLK) */
6     struct nand_ecclayout *ecclayout;    /* NANDFlash的OOB布局,OOB是NANDFlash中很有用空间,比如yaffs2就需要将坏块信息存储在OOB区域 */
7 };

2.4 链表mtd_partitions

链表头,将所有的mtd_partition连接起来。

 /* Our partition linked list */
 static LIST_HEAD(mtd_partitions);  

3 主要api

主要API的调用关系如下:

1 mtd_add_partition()
2    └── add_mtd_device()
3 add_mtd_partitions()
4    └── add_mtd_device()

3.1 函数mtd_add_partition

定义位于:drivers\mtd\mtdpart.c

通过将一个mtd_part对象注册到内核,将mtd_info对象注册到内核,即为一个设备添加一个分区。

 1 int mtd_add_partition(struct mtd_info *master, char *name,
 2               long long offset, long long length)
 3 {
 4     struct mtd_partition part;
 5     struct mtd_part *p, *new;
 6     uint64_t start, end;
 7     int ret = 0;
 8 
 9     /* the direct offset is expected */
10     if (offset == MTDPART_OFS_APPEND ||
11         offset == MTDPART_OFS_NXTBLK)
12         return -EINVAL;
13 
14     if (length == MTDPART_SIZ_FULL)
15         length = master->size - offset;
16 
17     if (length <= 0)
18         return -EINVAL;
19 
20     part.name = name;
21     part.size = length;
22     part.offset = offset;
23     part.mask_flags = 0;
24     part.ecclayout = NULL;
25 
26     new = allocate_partition(master, &part, -1, offset);
27     if (IS_ERR(new))
28         return PTR_ERR(new);
29 
30     start = offset;
31     end = offset + length;
32 
33     mutex_lock(&mtd_partitions_mutex);
34     list_for_each_entry(p, &mtd_partitions, list)
35         if (p->master == master) {
36             if ((start >= p->offset) &&
37                 (start < (p->offset + p->mtd.size)))
38                 goto err_inv;
39 
40             if ((end >= p->offset) &&
41                 (end < (p->offset + p->mtd.size)))
42                 goto err_inv;
43         }
44 
45     list_add(&new->list, &mtd_partitions);
46     mutex_unlock(&mtd_partitions_mutex);
47 
48     add_mtd_device(&new->mtd);
49 
50     return ret;
51 err_inv:
52     mutex_unlock(&mtd_partitions_mutex);
53     free_partition(new);
54     return -EINVAL;
55 }

3.2 函数add_mtd_partitions

添加一个分区表到内核,一个MTD设备一个分区表 。

 1 int add_mtd_partitions(struct mtd_info *master,
 2                const struct mtd_partition *parts,
 3                int nbparts)
 4 {
 5     struct mtd_part *slave;
 6     uint64_t cur_offset = 0;
 7     int i;
 8 
 9     printk(KERN_NOTICE "Creating %d MTD partitions on \"%s\":\n", nbparts, master->name);
10 
11     for (i = 0; i < nbparts; i++) {
12         slave = allocate_partition(master, parts + i, i, cur_offset);
13         if (IS_ERR(slave))
14             return PTR_ERR(slave);
15 
16         mutex_lock(&mtd_partitions_mutex);
17         list_add(&slave->list, &mtd_partitions);
18         mutex_unlock(&mtd_partitions_mutex);
19 
20         add_mtd_device(&slave->mtd);
21 
22         cur_offset = slave->offset + slave->mtd.size;
23     }
24 
25     return 0;
26 }

3.3 函数add_mtd_device

分配并初始化一个mtd对象。 定位于:drivers\mtd\mtdcore.c

 1 int add_mtd_device(struct mtd_info *mtd)
 2 {
 3     struct mtd_notifier *not;
 4     int i, error;
 5 
 6     if (!mtd->backing_dev_info) {
 7         switch (mtd->type) {
 8         case MTD_RAM:
 9             mtd->backing_dev_info = &mtd_bdi_rw_mappable;
10             break;
11         case MTD_ROM:
12             mtd->backing_dev_info = &mtd_bdi_ro_mappable;
13             break;
14         default:
15             mtd->backing_dev_info = &mtd_bdi_unmappable;
16             break;
17         }
18     }
19 
20     BUG_ON(mtd->writesize == 0);
21     mutex_lock(&mtd_table_mutex);
22 
23     i = idr_alloc(&mtd_idr, mtd, 0, 0, GFP_KERNEL);
24     if (i < 0)
25         goto fail_locked;
26 
27     mtd->index = i;
28     mtd->usecount = 0;
29 
30     /* default value if not set by driver */
31     if (mtd->bitflip_threshold == 0)
32         mtd->bitflip_threshold = mtd->ecc_strength;
33 
34     if (is_power_of_2(mtd->erasesize))
35         mtd->erasesize_shift = ffs(mtd->erasesize) - 1;
36     else
37         mtd->erasesize_shift = 0;
38 
39     if (is_power_of_2(mtd->writesize))
40         mtd->writesize_shift = ffs(mtd->writesize) - 1;
41     else
42         mtd->writesize_shift = 0;
43 
44     mtd->erasesize_mask = (1 << mtd->erasesize_shift) - 1;
45     mtd->writesize_mask = (1 << mtd->writesize_shift) - 1;
46 
47     /* Some chips always power up locked. Unlock them now */
48     if ((mtd->flags & MTD_WRITEABLE) && (mtd->flags & MTD_POWERUP_LOCK)) {
49         error = mtd_unlock(mtd, 0, mtd->size);
50         if (error && error != -EOPNOTSUPP)
51             printk(KERN_WARNING
52                    "%s: unlock failed, writes may not work\n",
53                    mtd->name);
54     }
55 
56     /* Caller should have set dev.parent to match the
57      * physical device.
58      */
59     mtd->dev.type = &mtd_devtype;
60     mtd->dev.class = &mtd_class;
61     mtd->dev.devt = MTD_DEVT(i);
62     dev_set_name(&mtd->dev, "mtd%d", i);//设置MTD设备的名字
63     dev_set_drvdata(&mtd->dev, mtd);//设置私有数据,将mtd地址藏到device->device_private->void* driver_data
64     if (device_register(&mtd->dev) != 0)
65         goto fail_added;
66 
67     if (MTD_DEVT(i))
68         device_create(&mtd_class, mtd->dev.parent,
69                   MTD_DEVT(i) + 1,
70                   NULL, "mtd%dro", i);
71 
72     pr_debug("mtd: Giving out device %d to %s\n", i, mtd->name);
73     /* No need to get a refcount on the module containing
74        the notifier, since we hold the mtd_table_mutex */
75     list_for_each_entry(not, &mtd_notifiers, list)//遍历所有的mtd_notifier,将其添加到通知链
76         not->add(mtd);
77 
78     mutex_unlock(&mtd_table_mutex);
79     /* We _know_ we aren't being removed, because
80        our caller is still holding us here. So none
81        of this try_ nonsense, and no bitching about it
82        either. :) */
83     __module_get(THIS_MODULE);
84     return 0;
85 
86 fail_added:
87     idr_remove(&mtd_idr, i);
88 fail_locked:
89     mutex_unlock(&mtd_table_mutex);
90     return 1;
91 }

4 用户空间编程

MTD设备提供了字符设备和块设备两种接口,对于字符设备接口,在"drivers/mtd/mtdchar.c"中实现了,比如,用户程序可以直接通过ioctl()回调相应的驱动实现。其中下面的几个是这些操作中常用的结构,这些结构是对用户空间开放的,类似于输入子系统中的input_event结构。

4.1 相关结构体

mtd_info_user,定义位于:include\uapi\mtd\mtd-abi.h

1 struct mtd_info_user {
2     __u8 type;
3     __u32 flags;
4     __u32 size;    /* Total size of the MTD */
5     __u32 erasesize;
6     __u32 writesize;
7     __u32 oobsize;    /* Amount of OOB data per block (e.g. 16) */
8     __u64 padding;    /* Old obsolete field; do not use */
9 };

mtd_oob_buf描述NandFlash的OOB(Out Of Band)信息。

1 struct mtd_oob_buf {
2     __u32 start;
3     __u32 length;
4     unsigned char __user *ptr;
5 };

结构体erase_info_user 

1 struct erase_info_user {
2     __u32 start;
3     __u32 length;
4 };

实例

 1 mtd_oob_buf oob;
 2 erase_info_user erase;
 3 mtd_info_user meminfo;
 4 
 5 /* 获得设备信息 */
 6 if(0 != ioctl(fd, MEMGETINFO, &meminfo))
 7     perror("MEMGETINFO");
 8     
 9 /* 擦除块 */
10 if(0 != ioctl(fd, MEMERASE, &erase))
11     perror("MEMERASE");
12 
13 /* 读OOB */
14 if(0 != ioctl(fd, MEMREADOOB, &oob))
15     perror("MEMREADOOB");
16 
17 /* 写OOB??? */    
18 if(0 != ioctl(fd, MEMWRITEOOB, &oob))
19     perror("MEMWRITEOOB");
20     
21 /* 检查坏块 */
22 if(blockstart != (ofs & (~meminfo.erase + 1))){
23     blockstart = ofs & (~meminfo.erasesize + 1);
24     if((badblock = ioctl(fd, MEMGETBADBLOCK, &blockstart)) < 0)
25         perror("MEMGETBADBLOCK");
26     else if(badblock)
27         /* 坏块代码 */
28     else
29         /* 好块代码 */
30 }

NANDFlash和NORFlash都是基于MTD框架编写的,由于MTD框架中通用代码已经在内核中实现了,所以驱动开发主要是进行MTD框架中的的开发。

5 nor flash

下图就是NORFlash驱动在MTD驱动框架中的位置:

基于上述的MTD框架, Flash驱动都变的十分的简单, 因为当下Flash的操作接口已经很统一, a, 相应的代码在"drivers/mtd/chips"中文件实现,所以在设备驱动层, 留给驱动工程师的工作就大大的减少了。
基于MTD子系统开发NOR FLash驱动,只需要构造一个map_info类型的对象并调用do_map_probe()来匹配内核中已经写好的驱动,比如CFI接口的驱动或JEDEC接口的驱动。当下编写一个NorFlash驱动的工作流程如下:

结构体map_info

定义位于:include\linux\mtd\map.h

 1 struct map_info {
 2     const char *name;
 3     unsigned long size;//NOR Flash设备的容量
 4     resource_size_t phys;//NOR Flash在物理地址空间中的地址
 5 #define NO_XIP (-1UL)
 6 
 7     void __iomem *virt;//由物理地址映射的虚拟地址
 8     void *cached;
 9 
10     int swap; /* this mapping's byte-swapping requirement */
11     int bankwidth; /* in octets. This isn't necessarily the width//总线宽度,NOR Flash是有地址总线的,所以才能片上执行,一般都是8位或16位宽
12                of actual bus cycles -- it's the repeat interval
13               in bytes, before you are talking to the first chip again.
14               */
15 
16 #ifdef CONFIG_MTD_COMPLEX_MAPPINGS
17     map_word (*read)(struct map_info *, unsigned long);
18     void (*copy_from)(struct map_info *, void *, unsigned long, ssize_t);
19 
20     void (*write)(struct map_info *, const map_word, unsigned long);
21     void (*copy_to)(struct map_info *, unsigned long, const void *, ssize_t);
22 
23     /* We can perhaps put in 'point' and 'unpoint' methods, if we really
24        want to enable XIP for non-linear mappings. Not yet though. */
25 #endif
26     /* It's possible for the map driver to use cached memory in its
27        copy_from implementation (and _only_ with copy_from).  However,
28        when the chip driver knows some flash area has changed contents,
29        it will signal it to the map driver through this routine to let
30        the map driver invalidate the corresponding cache as needed.
31        If there is no cache to care about this can be set to NULL. */
32     void (*inval_cache)(struct map_info *, unsigned long, ssize_t);
33 
34     /* set_vpp() must handle being reentered -- enable, enable, disable
35        must leave it enabled. */
36     void (*set_vpp)(struct map_info *, int);
37 
38     unsigned long pfow_base;
39     unsigned long map_priv_1;
40     unsigned long map_priv_2;
41     struct device_node *device_node;
42     void *fldrv_priv;
43     struct mtd_chip_driver *fldrv;
44 }

构造好一个map_info对象之后,接下来的工作就是匹配驱动+注册分区表。

函数do_map_probe用来根据传入的参数匹配一个map_info对象的驱动,比如CFI接口或JEDEC接口的NOR Flash,其定义位于:drivers\mtd\chips\chipreg.c 如下:

 1 struct mtd_info *do_map_probe(const char *name, struct map_info *map)
 2 {
 3     struct mtd_chip_driver *drv;
 4     struct mtd_info *ret;
 5 
 6     drv = get_mtd_chip_driver(name);
 7 
 8     if (!drv && !request_module("%s", name))
 9         drv = get_mtd_chip_driver(name);
10 
11     if (!drv)
12         return NULL;
13 
14     ret = drv->probe(map);
15 
16     /* We decrease the use count here. It may have been a
17        probe-only module, which is no longer required from this
18        point, having given us a handle on (and increased the use
19        count of) the actual driver code.
20     */
21     module_put(drv->module);
22 
23     return ret;
24 }

对于常用的NorFlash标准,这个函数的调用方式如下:

1 do_map_probe("cfi_probe", &xxx_map_info);
2 do_map_probe("jedec_probe",&xxx_map_info);
3 do_map_probe("map_rom",&xxx_map_info);

匹配了设备驱动,可以发现一个map_info对象中没有mtd_partitions相关的信息,对于一个NOR Flash的分区信息,需要通过do_map_probe返回的mtd_info对象来注册到内核。这里我们可以先调用parse_mtd_partitions()查看Flash上已有的分区信息,获取了分区信息之后再调用add_mtd_partitions()将分区信息写入内核。

nor flash的例子:

 1 #define WINDOW_SIZE ...
 2 #define WINDOW_ADDR ...
 3 static struct map_info xxx_map = {
 4     .name = "xxx flash",
 5     .size = WINDOW_SIZE,
 6     .bankwidth = 1,
 7     .phys = WINDOW_ADDR,
 8 };
 9 
10 static struct mtd_partition xxx_partitions[] = {
11     .name = "Drive A",
12     .offset = 0,
13     .size = 0x0e000,
14 };
15 
16 #define NUM_PARTITIONS ARRAY_SIZE(xxx_partitions)
17 
18 static struct mtd_info *mymtd;
19 
20 static int __init init_xxx_map(void)
21 {
22     int rc = 0;
23     xxx_map.virt = ioremap_nocache(xxx_map.phys, xxx_map.size);
24     if(!xxx_map.virt){
25         printk(KERN_ERR"Failed to ioremap_nocache\n");
26         rc = -EIO;
27         goto err2;
28     }
29     simple_map_init(&xxx_map);
30     mymtd = do_map_probe("jedec_probe", &xxx_map);
31     if(!mymtd){
32         rc = -ENXIO;
33         goto err1;
34     }
35     mymtd->owner = THIS_MODULE;
36     add_mtd_partitions(mymtd, xxx_partitions, NUM_PARTITIONS);
37     return 0;
38 err1:
39     map_destroy(mymtd);
40     iounmap(xxx_map.virt);
41 err2:
42     return rc;
43 }
44 static void __exit cleanup_xxx_map(void)
45 {
46     if(mymtd){
47         del_mtd_partitions(mymtd);
48         map_destroy(mymtd);
49     }
50     
51     if(xxx_map.virt){
52         iounmap(xxx_map.virt);
53         xxx_map.virt = NULL;
54     }
55 }

6 nand flash

基于MTD框架的NandFlash驱动的位置如下图:

Nand Flash和NOR Flash类似,内核中已经在"drivers/mtd/nand/nand_base.c"中实现了通用的驱动程序,驱动开发中不需要再实现mtd_info中的read, write, read_oob, write_oob等接口,只需要构造并注册一个nand_chip对象, 这个对象主要描述了一片flash芯片的相关信息,包括地址信息,读写方法,ECC模式,硬件控制等一系列底层机制。当下,编写一个NandFlash驱动的工作流程如下:

结构体nand_chip

这个结构描述一个NAND Flash设备,通常藏在mtd_info->priv中,以便在回调其中的接口的时候可以找到nand_chip对象。定义位于:include\linux\mtd\nand.h

 1 struct nand_chip {
 2     void __iomem *IO_ADDR_R;
 3     void __iomem *IO_ADDR_W;
 4 
 5     uint8_t (*read_byte)(struct mtd_info *mtd);
 6     u16 (*read_word)(struct mtd_info *mtd);
 7     void (*write_buf)(struct mtd_info *mtd, const uint8_t *buf, int len);
 8     void (*read_buf)(struct mtd_info *mtd, uint8_t *buf, int len);
 9     void (*select_chip)(struct mtd_info *mtd, int chip);
10     int (*block_bad)(struct mtd_info *mtd, loff_t ofs, int getchip);
11     int (*block_markbad)(struct mtd_info *mtd, loff_t ofs);
12     void (*cmd_ctrl)(struct mtd_info *mtd, int dat, unsigned int ctrl);
13     int (*init_size)(struct mtd_info *mtd, struct nand_chip *this,
14             u8 *id_data);
15     int (*dev_ready)(struct mtd_info *mtd);
16     void (*cmdfunc)(struct mtd_info *mtd, unsigned command, int column,
17             int page_addr);
18     int(*waitfunc)(struct mtd_info *mtd, struct nand_chip *this);
19     void (*erase_cmd)(struct mtd_info *mtd, int page);
20     int (*scan_bbt)(struct mtd_info *mtd);
21     int (*errstat)(struct mtd_info *mtd, struct nand_chip *this, int state,
22             int status, int page);
23     int (*write_page)(struct mtd_info *mtd, struct nand_chip *chip,
24             uint32_t offset, int data_len, const uint8_t *buf,
25             int oob_required, int page, int cached, int raw);
26     int (*onfi_set_features)(struct mtd_info *mtd, struct nand_chip *chip,
27             int feature_addr, uint8_t *subfeature_para);
28     int (*onfi_get_features)(struct mtd_info *mtd, struct nand_chip *chip,
29             int feature_addr, uint8_t *subfeature_para);
30 
31     int chip_delay;
32     unsigned int options;//与具体的NAND 芯片相关的一些选项,如NAND_BUSWIDTH_16 等,可以参考<Linux/mtd/nand.h>
33     unsigned int bbt_options;
34 
35     int page_shift;//用位表示的NAND 芯片的page 大小,如某片NAND 芯片的一个page 有512 个字节,那么page_shift 就是9
36     int phys_erase_shift;//用位表示的NAND 芯片的每次可擦除的大小,如某片NAND 芯片每次可擦除16K 字节( 通常就是一个block 的大小) ,那么phys_erase_shift 就是14
37     int bbt_erase_shift;//用位表示的bad block table 的大小,通常一个bbt 占用一个block ,所以bbt_erase_shift 通常与phys_erase_shift 相等
38     int chip_shift;
39     int numchips;//表示系统中有多少片NAND 芯片
40     uint64_t chipsize;//NAND 芯片的大小
41     int pagemask;//计算page number 时的掩码,总是等于chipsize/page 大小 - 1
42     int pagebuf;//用来保存当前读取的NAND 芯片的page number ,这样一来,下次读取的数据若还是属于同一个page ,就不必再从NAND 芯片读取了,而是从data_buf 中直接得到
43     unsigned int pagebuf_bitflips;
44     int subpagesize;
45     uint8_t cellinfo;
46     int badblockpos;//表示坏块信息保存在oob 中的第几个字节。对于绝大多数的NAND 芯片,若page size> 512,那么坏块信息从Byte 0 开始存储,否则就存储在Byte 5 ,即第六个字节
47     int badblockbits;
48 
49     int onfi_version;
50     struct nand_onfi_params    onfi_params;
51 
52     flstate_t state;
53 
54     uint8_t *oob_poi;
55     struct nand_hw_control *controller;
56     struct nand_ecclayout *ecclayout;
57 
58     struct nand_ecc_ctrl ecc;//NAND芯片的OOB分布和模式,如果不赋值,则会使用内核默认的OOB
59     struct nand_buffers *buffers;
60     struct nand_hw_control hwcontrol;
61 
62     uint8_t *bbt;
63     struct nand_bbt_descr *bbt_td;
64     struct nand_bbt_descr *bbt_md;
65 
66     struct nand_bbt_descr *badblock_pattern;
67 
68     void *priv;//私有数据
69 }

函数nand_scan

定义位于:drivers\mtd\nand\nand_base.c

准备好了一个nand_chip,接下来的工作就是匹配驱动+注册分区表
NAND flash使用nand_scan()来匹配驱动,这个函数会读取NAND芯片的ID,并根据mtd->priv即nand_chip中的成员初始化mtd_info。如果要分区,则以mtd_info和mtd_partition为参数调用add_mtd_partitions来添加分区信息。

 1 int nand_scan(struct mtd_info *mtd, int maxchips)
 2 {
 3     int ret;
 4 
 5     /* Many callers got this wrong, so check for it for a while... */
 6     if (!mtd->owner && caller_is_module()) {
 7         pr_crit("%s called with NULL mtd->owner!\n", __func__);
 8         BUG();
 9     }
10 
11     ret = nand_scan_ident(mtd, maxchips, NULL);
12     if (!ret)
13         ret = nand_scan_tail(mtd);
14     return ret;
15 }

nand flash驱动例子:

  1 #define CHIP_PHYSICAL_ADDRESS ...  
  2 #define NUM_PARTITIONS 2 
  3 static struct mtd_partition partition_info[] = { 
  4         { 
  5                 .name = "Flash partition 1", 
  6                 .info = 0, 
  7                 .size = 8 * 1024 * 1024, 
  8         }, 
  9         { 
 10                 .name = "Flash partition 2", 
 11                 offset = MTDPART_OFS_NEXT, 
 12                 size = MTDPART_SIZ_FULL, 
 13         }, 
 14 }; 
 15  
 16  
 17 int __init board_init(void) 
 18 { 
 19         struct nand_chip *this; 
 20         int err = 0; 
 21         /* 为MTD设备对象和nand_chip分配内存 */ 
 22         board_mtd = kmalloc(sizeof(struct mtd_info) + sizeof(struct nand_chip),GFP_KERNEL); 
 23         if(!board_mtd){ 
 24                 printk("Unable to allocate NAND MTD device structure\n"); 
 25                 err = -ENOMEM; 
 26                 goto out; 
 27         } 
 28  
 29         /* 初始化结构体 */ 
 30         memset((char *)board_mtd, 0 ,sizeof(struct mtd_info) + sizeof(struct nand_chip)); 
 31  
 32         /* 映射物理地址 */ 
 33         baseaddr = (unsigned long) ioremap(CHIP_PHYSICAL_ADDRESS,1024); 
 34         if(!baseaddr){ 
 35                 printk("Ioremap to access NAND Chip failed\n"); 
 36                 err = -EIO; 
 37                 goto out_mtd; 
 38         } 
 39  
 40         /* 获取私有数据(nand_chip)指针 */ 
 41         this = (struct nand_chip *)(&board_mtd[1]); 
 42  
 43         /* 将nand_chip赋予mtd_info私有指针 */ 
 44         board_mtd->priv = this; 
 45  
 46         /* 设置NAND Flash的IO基地址 */ 
 47         this->IO_ADDR_R = baseaddr; 
 48         this->IO_ADDR_W = baseaddr; 
 49  
 50         /* 硬件控制函数 */ 
 51         this->cmd_ctrl = board_hwcontrol; 
 52  
 53         /* 初始化设备ready函数 */ 
 54         this->dev_ready = board_dev_ready; 
 55          
 56         /* 扫描以确定设备的存在 */ 
 57         if(nand_scan(board_mtd, 1)){ 
 58                 err = -ENXIO; 
 59                 goto = out_ior; 
 60         } 
 61  
 62         /* 添加分区 */ 
 63         add_mtd_partitions(board_mtd,partition_info,NUM_PARTITIONS); 
 64         goto out; 
 65 out_ior: 
 66         iounmap((void *)baseaddr); 
 67 out_mtd: 
 68         kfree(board_mtd); 
 69 out: 
 70         return err; 
 71 } 
 72  
 73 static void __exit board_cleanup(void) 
 74 { 
 75         /* 释放资源,注销设备 */ 
 76         nand_release(board_mtd); 
 77  
 78         /* unmap物理地址 */ 
 79         iounmap((void *)baseaddr); 
 80  
 81         /* 释放MTD设备结构体 */ 
 82         kfree(board_mtd); 
 83 } 
 84  
 85 /* 硬件控制 */ 
 86 static void board_hwcontrol(struct mtd_info *mtd, int dat,unsigned int ctrl) 
 87 { 
 88         ... 
 89         if(ctrl & NAND_CTRL_CHANGE){ 
 90                 if(ctrl & NAND_NCE){ 
 91  
 92                 } 
 93         } 
 94         ... 
 95 } 
 96  
 97 /*返回ready状态*/ 
 98 static int board_dev_ready(struct mtd_info *mtd) 
 99 { 
100         return xxx_read_ready_bit(); 
101 } 

 

参考博文:https://www.cnblogs.com/xiaojiang1025/p/6615898.html

posted @ 2020-07-14 21:42  Action_er  阅读(6159)  评论(0编辑  收藏  举报