Linux块设备驱动(二) _MTD驱动及其用户空间编程

时间:2023-03-08 16:52:09
Linux块设备驱动(二) _MTD驱动及其用户空间编程

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

MTD子系统框架

Linux块设备驱动(二) _MTD驱动及其用户空间编程

  • 设备节点层:MTD框架可以在/dev下创建字符设备节点(主设备号90)以及块设备节点(主设备号31), 用户通过访问此设备节点即可访问MTD字符设备或块设备。
  • MTD设备层: 基于MTD原始设备, Linux在这一层次定义出了MTD字符设备和块设备, 字符设备在mtdchar.c中实现, 块设备则是通过结构mtdblk_dev来描述,"/drivers/mtd/mtdchar.c"文件实现了MTD字符设备接口; "/drivers/mtd/mtdblock.c"文件实现了MTD块设备接口
  • MTD原始设备层: 由MTD原始设备的通用代码+特定的Flash数据组成。mtd_info、mtd_part、mtd_partition以及mtd_partitions等对象及其操作方法就属于这一层,对应的文件是"drivers/mtd/mtdcore.c"。类似于i2c驱动框架中的核心层。
  • 硬件驱动层: 内核将常用的flash操作都已经在这个层次实现, 驱动开发只需要将相应的设备信息添加进去即可, 比如,NOR flash的芯片驱动位于"drivers/mtd/chips/", Nand flash位于"drivers/mtd/nand/"(eg s3c2410.c)

核心结构和方法简述

为了实现上述的框架, 内核中使用了如下类和API, 这些几乎是开发一个MTD驱动必须的

核心结构

  • mtd_info描述原始设备层的一个分区的结构, 描述一个设备或一个多分区设备中的一个分区
  • mtd_table管理原始设备层的mtd_info的数组
  • mtd_part表示一个分区, 其中的struct mtd_info mtd描述该分区的信息, 一个物理Flash设备可以有多于1个mtd_part,每个mtd_part都对应一个mtd_info。
  • mtd_partition描述一个分区表, 通过管理mtd_part以及每一个mtd_part中的mtd_info来描述所有的分区,一个物理Flash设备只有一个mtd_partition
  • mtd_partitions是一个list_head对象,用于管理mtd_partition们

核心方法

  • add_mtd_device()/del_mtd_device()注册/注销一个MTD设备
  • add_mtd_partitions()/del_mtd_partitions()注册注销一个或多个分区表,

核心结构与方法详述

mtd_info

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

114 struct mtd_info {
115         u_char type;
116         uint32_t flags;
117         uint64_t size;   // Total size of the MTD
118
123         uint32_t erasesize;
131         uint32_t writesize;
132
142         uint32_t writebufsize;
143
144         uint32_t oobsize;   // Amount of OOB data per block (e.g. 16)
145         uint32_t oobavail;  // Available OOB bytes per block
146
151         unsigned int erasesize_shift;
152         unsigned int writesize_shift;
153         /* Masks based on erasesize_shift and writesize_shift */
154         unsigned int erasesize_mask;
155         unsigned int writesize_mask;
156
164         unsigned int bitflip_threshold;
165
166         // Kernel-only stuff starts here.
167         const char *name;
168         int index;
169
170         /* ECC layout structure pointer - read only! */
171         struct nand_ecclayout *ecclayout;
172
173         /* the ecc step size. */
174         unsigned int ecc_step_size;
175
176         /* max number of correctible bit errors per ecc step */
177         unsigned int ecc_strength;
178
179         /* Data for variable erase regions. If numeraseregions is zero,
180          * it means that the whole device has erasesize as given above.
181          */
182         int numeraseregions;
183         struct mtd_erase_region_info *eraseregions;
184
185         /*
186          * Do not call via these pointers, use corresponding mtd_*()
187          * wrappers instead.
188          */
189         int (*_erase) (struct mtd_info *mtd, struct erase_info *instr);
190         int (*_point) (struct mtd_info *mtd, loff_t from, size_t len,
191                        size_t *retlen, void **virt, resource_size_t *phys);
192         int (*_unpoint) (struct mtd_info *mtd, loff_t from, size_t len);
193         unsigned long (*_get_unmapped_area) (struct mtd_info *mtd,
194                                              unsigned long len,
195                                              unsigned long offset,
196                                              unsigned long flags);
197         int (*_read) (struct mtd_info *mtd, loff_t from, size_t len,
198                       size_t *retlen, u_char *buf);
199         int (*_write) (struct mtd_info *mtd, loff_t to, size_t len,
200                        size_t *retlen, const u_char *buf);
201         int (*_panic_write) (struct mtd_info *mtd, loff_t to, size_t len,
202                              size_t *retlen, const u_char *buf);
203         int (*_read_oob) (struct mtd_info *mtd, loff_t from,
204                           struct mtd_oob_ops *ops);
205         int (*_write_oob) (struct mtd_info *mtd, loff_t to,
206                            struct mtd_oob_ops *ops);
207         int (*_get_fact_prot_info) (struct mtd_info *mtd, struct otp_info *buf,
208                                     size_t len);
209         int (*_read_fact_prot_reg) (struct mtd_info *mtd, loff_t from,
210                                     size_t len, size_t *retlen, u_char *buf);
211         int (*_get_user_prot_info) (struct mtd_info *mtd, struct otp_info *buf,
212                                     size_t len);
213         int (*_read_user_prot_reg) (struct mtd_info *mtd, loff_t from,
214                                     size_t len, size_t *retlen, u_char *buf);
215         int (*_write_user_prot_reg) (struct mtd_info *mtd, loff_t to,
216                                      size_t len, size_t *retlen, u_char *buf);
217         int (*_lock_user_prot_reg) (struct mtd_info *mtd, loff_t from,
218                                     size_t len);
219         int (*_writev) (struct mtd_info *mtd, const struct kvec *vecs,
220                         unsigned long count, loff_t to, size_t *retlen);
221         void (*_sync) (struct mtd_info *mtd);
222         int (*_lock) (struct mtd_info *mtd, loff_t ofs, uint64_t len);
223         int (*_unlock) (struct mtd_info *mtd, loff_t ofs, uint64_t len);
224         int (*_is_locked) (struct mtd_info *mtd, loff_t ofs, uint64_t len);
225         int (*_block_isbad) (struct mtd_info *mtd, loff_t ofs);
226         int (*_block_markbad) (struct mtd_info *mtd, loff_t ofs);
227         int (*_suspend) (struct mtd_info *mtd);
228         void (*_resume) (struct mtd_info *mtd);
229         /*
230          * If the driver is something smart, like UBI, it may need to maintain
231          * its own reference counting. The below functions are only for driver.
232          */
233         int (*_get_device) (struct mtd_info *mtd);
234         void (*_put_device) (struct mtd_info *mtd);
235
236         /* Backing device capabilities for this device
237          * - provides mmap capabilities
238          */
239         struct backing_dev_info *backing_dev_info;
240
241         struct notifier_block reboot_notifier;  /* default mode before reboot */
242
243         /* ECC status information */
244         struct mtd_ecc_stats ecc_stats;
245         /* Subpage shift (NAND) */
246         int subpage_sft;
247
248         void *priv;
249
250         struct module *owner;
251         struct device dev;
252         int usecount;
253 };

struct mtd_info

--115-->MTD设备类型,有MTD_RAM,MTD_ROM、MTD_NORFLASH、MTD_NAND_FLASH

--116-->读写及权限标志位,有MTD_WRITEABLE、MTD_BIT_WRITEABLE、MTD_NO_ERASE、MTD_UP_LOCK

--117-->MTD设备的大小

--123-->主要的擦除块大小,NandFlash就是"块"的大小

--131-->最小可写字节数,NandFlash一般对应"页"的大小

--144-->一个block中的OOB字节数

--145-->一个block中可用oob的字节数

--171-->ECC布局结构体指针

--190-->针对eXecute-In-Place,即XIP

--192-->如果这个指针为空,不允许XIP

--197-->读函数指针

--199-->写函数指针

--248-->私有数据

mtd_part

内核管理分区的链表节点,通过它来实现对mtd_info对象的管理。

 41 struct mtd_part {
 42         struct mtd_info mtd;
 43         struct mtd_info *master;
 44         uint64_t offset;
 45         struct list_head list;
 46 };

struct mtd_part

--42-->对应的mtd_info对象

--43-->父对象指针

--44-->偏移量

--45-->链表节点

mtd_partition

描述一个分区

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

mtd_partition

--40-->分区名

--41-->分区大小,使用MTDPART_SIZ_FULL表示使用全部空间

--42-->分区在master设备中的偏移量。MTDPART_OFS_APPEND表示从上一个分区结束的地方开始,MTDPART_OFS_NXTBLK表示从下一个擦除块开始; MTDPART_OFS_RETAIN表示尽可能向后偏,把size大小的空间留下即可

--43-->权限掩码,MTD_WRITEABLE表示将父设备的只读选项变成可写(可写分区要求size和offset要erasesize对齐,eg MTDPART_OFS_NEXTBLK)

--44-->NANDFlash的OOB布局,OOB是NANDFlash中很有用空间,比如yaffs2就需要将坏块信息存储在OOB区域

mtd_partitions

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

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

下图是关键API的调用关系。

mtd_add_partition()

   └── add_mtd_device()

add_mtd_partitions()

   └── add_mtd_device()

add_mtd_device()

分配并初始化一个mtd对象。

 367 334 int add_mtd_device(struct mtd_info *mtd)
 335 {
 336         struct mtd_notifier *not;
 337         int i, error;
 338
 339         if (!mtd->backing_dev_info) {
 340                 switch (mtd->type) {
 341                 case MTD_RAM:
 342                         mtd->backing_dev_info = &mtd_bdi_rw_mappable;
 343                         break;
 344                 case MTD_ROM:
 345                         mtd->backing_dev_info = &mtd_bdi_ro_mappable;
 346                         break;
 347                 default:
 348                         mtd->backing_dev_info = &mtd_bdi_unmappable;
 349                         break;
 350                 }
 351         }
 355
 356         i = idr_alloc(&mtd_idr, mtd, 0, 0, GFP_KERNEL);
 357         if (i < 0)
 358                 goto fail_locked;
 359
 360         mtd->index = i;
 361         mtd->usecount = 0;
 362
 363         /* default value if not set by driver */
 364         if (mtd->bitflip_threshold == 0)
 365                 mtd->bitflip_threshold = mtd->ecc_strength;
 366
 367         if (is_power_of_2(mtd->erasesize))
 368                 mtd->erasesize_shift = ffs(mtd->erasesize) - 1;
 369         else
 370                 mtd->erasesize_shift = 0;
 371
 372         if (is_power_of_2(mtd->writesize))
 373                 mtd->writesize_shift = ffs(mtd->writesize) - 1;
 374         else
 375                 mtd->writesize_shift = 0;
 376
 377         mtd->erasesize_mask = (1 << mtd->erasesize_shift) - 1;
 378         mtd->writesize_mask = (1 << mtd->writesize_shift) - 1;
 379
 380         /* Some chips always power up locked. Unlock them now */
 381         if ((mtd->flags & MTD_WRITEABLE) && (mtd->flags & MTD_POWERUP_LOCK)) {
 382                 error = mtd_unlock(mtd, 0, mtd->size);
 387         }
 388
 392         mtd->dev.type = &mtd_devtype;
 393         mtd->dev.class = &mtd_class;
 394         mtd->dev.devt = MTD_DEVT(i);
 395         dev_set_name(&mtd->dev, "mtd%d", i);
 396         dev_set_drvdata(&mtd->dev, mtd);
 397         if (device_register(&mtd->dev) != 0)
 399
 400         if (MTD_DEVT(i))
 401                 device_create(&mtd_class, mtd->dev.parent,
 402                               MTD_DEVT(i) + 1,
 403                               NULL, "mtd%dro", i);
 408         list_for_each_entry(not, &mtd_notifiers, list)
 409                 not->add(mtd);
 417         return 0;
 424 }

add_mtd_device()

--395-->设置MTD设备的名字

--396-->设置私有数据,将mtd地址藏到device->device_private->void* driver_data

--408-->遍历所有的mtd_notifier,将其添加到通知链

mtd_add_partition()

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

537 int mtd_add_partition(struct mtd_info *master, const char *name,
538                       long long offset, long long length)
539 {
540         struct mtd_partition part;
541         struct mtd_part *p, *new;
542         uint64_t start, end;
543         int ret = 0;
545         /* the direct offset is expected */
546         if (offset == MTDPART_OFS_APPEND ||
547             offset == MTDPART_OFS_NXTBLK)
548                 return -EINVAL;
549
550         if (length == MTDPART_SIZ_FULL)
551                 length = master->size - offset;
552
553         if (length <= 0)
554                 return -EINVAL;
555
556         part.name = name;
557         part.size = length;
558         part.offset = offset;
559         part.mask_flags = 0;
560         part.ecclayout = NULL;
561
562         new = allocate_partition(master, &part, -1, offset);
563         if (IS_ERR(new))
564                 return PTR_ERR(new);
565
566         start = offset;
567         end = offset + length;
568
569         mutex_lock(&mtd_partitions_mutex);
570         list_for_each_entry(p, &mtd_partitions, list)
571                 if (p->master == master) {
572                         if ((start >= p->offset) &&
573                             (start < (p->offset + p->mtd.size)))
574                                 goto err_inv;
575
576                         if ((end >= p->offset) &&
577                             (end < (p->offset + p->mtd.size)))
578                                 goto err_inv;
579                 }
580
581         list_add(&new->list, &mtd_partitions);
582         mutex_unlock(&mtd_partitions_mutex);
583
584         add_mtd_device(&new->mtd);
585
586         return ret;
591 }

add_mtd_partitions()

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

626 int add_mtd_partitions(struct mtd_info *master,
627                        const struct mtd_partition *parts,
628                        int nbparts)
629 {
630         struct mtd_part *slave;
631         uint64_t cur_offset = 0;
632         int i;
636         for (i = 0; i < nbparts; i++) {
637                 slave = allocate_partition(master, parts + i, i, cur_offset);
642                 list_add(&slave->list, &mtd_partitions);
645                 add_mtd_device(&slave->mtd);
647                 cur_offset = slave->offset + slave->mtd.size;
648         }
649
650         return 0;
651 }

用户空间编程

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

mtd_info_user

//include/uapi/mtd/mtd-abi.h
125 struct mtd_info_user {
126         __u8 type;
127         __u32 flags;
128         __u32 size;     /* Total size of the MTD */
129         __u32 erasesize;
130         __u32 writesize;
131         __u32 oobsize;  /* Amount of OOB data per block (e.g. 16) */
132         __u64 padding;  /* Old obsolete field; do not use */
133 };

mtd_oob_buf

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

 35 struct mtd_oob_buf {
 36         __u32 start;
 37         __u32 length;
 38         unsigned char __user *ptr;
 39 };

erase_info_user

 25 struct erase_info_user {
 26         __u32 start;
 27         __u32 length;
 28 };

实例

mtd_oob_buf oob;
erase_info_user erase;
mtd_info_user meminfo;

/* 获得设备信息 */
if(0 != ioctl(fd, MEMGETINFO, &meminfo))
    perror("MEMGETINFO");

/* 擦除块 */
if(0 != ioctl(fd, MEMERASE, &erase))
    perror("MEMERASE");

/* 读OOB */
if(0 != ioctl(fd, MEMREADOOB, &oob))
    perror("MEMREADOOB");

/* 写OOB??? */
if(0 != ioctl(fd, MEMWRITEOOB, &oob))
    perror("MEMWRITEOOB");

/* 检查坏块 */
if(blockstart != (ofs & (~meminfo.erase + 1))){
    blockstart = ofs & (~meminfo.erasesize + 1);
    if((badblock = ioctl(fd, MEMGETBADBLOCK, &blockstart)) < 0)
        perror("MEMGETBADBLOCK");
    else if(badblock)
        /* 坏块代码 */
    else
        /* 好块代码 */
}