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

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函数。

  1. 114 struct mtd_info {
  2. 115 u_char type;
  3. 116 uint32_t flags;
  4. 117 uint64_t size; // Total size of the MTD
  5. 118
  6. 123 uint32_t erasesize;
  7. 131 uint32_t writesize;
  8. 132
  9. 142 uint32_t writebufsize;
  10. 143
  11. 144 uint32_t oobsize; // Amount of OOB data per block (e.g. 16)
  12. 145 uint32_t oobavail; // Available OOB bytes per block
  13. 146
  14. 151 unsigned int erasesize_shift;
  15. 152 unsigned int writesize_shift;
  16. 153 /* Masks based on erasesize_shift and writesize_shift */
  17. 154 unsigned int erasesize_mask;
  18. 155 unsigned int writesize_mask;
  19. 156
  20. 164 unsigned int bitflip_threshold;
  21. 165
  22. 166 // Kernel-only stuff starts here.
  23. 167 const char *name;
  24. 168 int index;
  25. 169
  26. 170 /* ECC layout structure pointer - read only! */
  27. 171 struct nand_ecclayout *ecclayout;
  28. 172
  29. 173 /* the ecc step size. */
  30. 174 unsigned int ecc_step_size;
  31. 175
  32. 176 /* max number of correctible bit errors per ecc step */
  33. 177 unsigned int ecc_strength;
  34. 178
  35. 179 /* Data for variable erase regions. If numeraseregions is zero,
  36. 180 * it means that the whole device has erasesize as given above.
  37. 181 */
  38. 182 int numeraseregions;
  39. 183 struct mtd_erase_region_info *eraseregions;
  40. 184
  41. 185 /*
  42. 186 * Do not call via these pointers, use corresponding mtd_*()
  43. 187 * wrappers instead.
  44. 188 */
  45. 189 int (*_erase) (struct mtd_info *mtd, struct erase_info *instr);
  46. 190 int (*_point) (struct mtd_info *mtd, loff_t from, size_t len,
  47. 191 size_t *retlen, void **virt, resource_size_t *phys);
  48. 192 int (*_unpoint) (struct mtd_info *mtd, loff_t from, size_t len);
  49. 193 unsigned long (*_get_unmapped_area) (struct mtd_info *mtd,
  50. 194 unsigned long len,
  51. 195 unsigned long offset,
  52. 196 unsigned long flags);
  53. 197 int (*_read) (struct mtd_info *mtd, loff_t from, size_t len,
  54. 198 size_t *retlen, u_char *buf);
  55. 199 int (*_write) (struct mtd_info *mtd, loff_t to, size_t len,
  56. 200 size_t *retlen, const u_char *buf);
  57. 201 int (*_panic_write) (struct mtd_info *mtd, loff_t to, size_t len,
  58. 202 size_t *retlen, const u_char *buf);
  59. 203 int (*_read_oob) (struct mtd_info *mtd, loff_t from,
  60. 204 struct mtd_oob_ops *ops);
  61. 205 int (*_write_oob) (struct mtd_info *mtd, loff_t to,
  62. 206 struct mtd_oob_ops *ops);
  63. 207 int (*_get_fact_prot_info) (struct mtd_info *mtd, struct otp_info *buf,
  64. 208 size_t len);
  65. 209 int (*_read_fact_prot_reg) (struct mtd_info *mtd, loff_t from,
  66. 210 size_t len, size_t *retlen, u_char *buf);
  67. 211 int (*_get_user_prot_info) (struct mtd_info *mtd, struct otp_info *buf,
  68. 212 size_t len);
  69. 213 int (*_read_user_prot_reg) (struct mtd_info *mtd, loff_t from,
  70. 214 size_t len, size_t *retlen, u_char *buf);
  71. 215 int (*_write_user_prot_reg) (struct mtd_info *mtd, loff_t to,
  72. 216 size_t len, size_t *retlen, u_char *buf);
  73. 217 int (*_lock_user_prot_reg) (struct mtd_info *mtd, loff_t from,
  74. 218 size_t len);
  75. 219 int (*_writev) (struct mtd_info *mtd, const struct kvec *vecs,
  76. 220 unsigned long count, loff_t to, size_t *retlen);
  77. 221 void (*_sync) (struct mtd_info *mtd);
  78. 222 int (*_lock) (struct mtd_info *mtd, loff_t ofs, uint64_t len);
  79. 223 int (*_unlock) (struct mtd_info *mtd, loff_t ofs, uint64_t len);
  80. 224 int (*_is_locked) (struct mtd_info *mtd, loff_t ofs, uint64_t len);
  81. 225 int (*_block_isbad) (struct mtd_info *mtd, loff_t ofs);
  82. 226 int (*_block_markbad) (struct mtd_info *mtd, loff_t ofs);
  83. 227 int (*_suspend) (struct mtd_info *mtd);
  84. 228 void (*_resume) (struct mtd_info *mtd);
  85. 229 /*
  86. 230 * If the driver is something smart, like UBI, it may need to maintain
  87. 231 * its own reference counting. The below functions are only for driver.
  88. 232 */
  89. 233 int (*_get_device) (struct mtd_info *mtd);
  90. 234 void (*_put_device) (struct mtd_info *mtd);
  91. 235
  92. 236 /* Backing device capabilities for this device
  93. 237 * - provides mmap capabilities
  94. 238 */
  95. 239 struct backing_dev_info *backing_dev_info;
  96. 240
  97. 241 struct notifier_block reboot_notifier; /* default mode before reboot */
  98. 242
  99. 243 /* ECC status information */
  100. 244 struct mtd_ecc_stats ecc_stats;
  101. 245 /* Subpage shift (NAND) */
  102. 246 int subpage_sft;
  103. 247
  104. 248 void *priv;
  105. 249
  106. 250 struct module *owner;
  107. 251 struct device dev;
  108. 252 int usecount;
  109. 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对象的管理。

  1. 41 struct mtd_part {
  2. 42 struct mtd_info mtd;
  3. 43 struct mtd_info *master;
  4. 44 uint64_t offset;
  5. 45 struct list_head list;
  6. 46 };

struct mtd_part

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

--43-->父对象指针

--44-->偏移量

--45-->链表节点

mtd_partition

描述一个分区

  1. 39 struct mtd_partition {
  2. 40 const char *name; /* identifier string */
  3. 41 uint64_t size; /* partition size */
  4. 42 uint64_t offset; /* offset within the master MTD space */
  5. 43 uint32_t mask_flags; /* master MTD flags to mask out for this partition */
  6. 44 struct nand_ecclayout *ecclayout; /* out of band layout for this partition (NAND only) */
  7. 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连接起来。

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

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

mtd_add_partition()

   └── add_mtd_device()

add_mtd_partitions()

   └── add_mtd_device()

add_mtd_device()

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

  1. 367 334 int add_mtd_device(struct mtd_info *mtd)
  2. 335 {
  3. 336 struct mtd_notifier *not;
  4. 337 int i, error;
  5. 338
  6. 339 if (!mtd->backing_dev_info) {
  7. 340 switch (mtd->type) {
  8. 341 case MTD_RAM:
  9. 342 mtd->backing_dev_info = &mtd_bdi_rw_mappable;
  10. 343 break;
  11. 344 case MTD_ROM:
  12. 345 mtd->backing_dev_info = &mtd_bdi_ro_mappable;
  13. 346 break;
  14. 347 default:
  15. 348 mtd->backing_dev_info = &mtd_bdi_unmappable;
  16. 349 break;
  17. 350 }
  18. 351 }
  19. 355
  20. 356 i = idr_alloc(&mtd_idr, mtd, 0, 0, GFP_KERNEL);
  21. 357 if (i < 0)
  22. 358 goto fail_locked;
  23. 359
  24. 360 mtd->index = i;
  25. 361 mtd->usecount = 0;
  26. 362
  27. 363 /* default value if not set by driver */
  28. 364 if (mtd->bitflip_threshold == 0)
  29. 365 mtd->bitflip_threshold = mtd->ecc_strength;
  30. 366
  31. 367 if (is_power_of_2(mtd->erasesize))
  32. 368 mtd->erasesize_shift = ffs(mtd->erasesize) - 1;
  33. 369 else
  34. 370 mtd->erasesize_shift = 0;
  35. 371
  36. 372 if (is_power_of_2(mtd->writesize))
  37. 373 mtd->writesize_shift = ffs(mtd->writesize) - 1;
  38. 374 else
  39. 375 mtd->writesize_shift = 0;
  40. 376
  41. 377 mtd->erasesize_mask = (1 << mtd->erasesize_shift) - 1;
  42. 378 mtd->writesize_mask = (1 << mtd->writesize_shift) - 1;
  43. 379
  44. 380 /* Some chips always power up locked. Unlock them now */
  45. 381 if ((mtd->flags & MTD_WRITEABLE) && (mtd->flags & MTD_POWERUP_LOCK)) {
  46. 382 error = mtd_unlock(mtd, 0, mtd->size);
  47. 387 }
  48. 388
  49. 392 mtd->dev.type = &mtd_devtype;
  50. 393 mtd->dev.class = &mtd_class;
  51. 394 mtd->dev.devt = MTD_DEVT(i);
  52. 395 dev_set_name(&mtd->dev, "mtd%d", i);
  53. 396 dev_set_drvdata(&mtd->dev, mtd);
  54. 397 if (device_register(&mtd->dev) != 0)
  55. 399
  56. 400 if (MTD_DEVT(i))
  57. 401 device_create(&mtd_class, mtd->dev.parent,
  58. 402 MTD_DEVT(i) + 1,
  59. 403 NULL, "mtd%dro", i);
  60. 408 list_for_each_entry(not, &mtd_notifiers, list)
  61. 409 not->add(mtd);
  62. 417 return 0;
  63. 424 }

add_mtd_device()

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

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

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

mtd_add_partition()

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

  1. 537 int mtd_add_partition(struct mtd_info *master, const char *name,
  2. 538 long long offset, long long length)
  3. 539 {
  4. 540 struct mtd_partition part;
  5. 541 struct mtd_part *p, *new;
  6. 542 uint64_t start, end;
  7. 543 int ret = 0;
  8. 545 /* the direct offset is expected */
  9. 546 if (offset == MTDPART_OFS_APPEND ||
  10. 547 offset == MTDPART_OFS_NXTBLK)
  11. 548 return -EINVAL;
  12. 549
  13. 550 if (length == MTDPART_SIZ_FULL)
  14. 551 length = master->size - offset;
  15. 552
  16. 553 if (length <= 0)
  17. 554 return -EINVAL;
  18. 555
  19. 556 part.name = name;
  20. 557 part.size = length;
  21. 558 part.offset = offset;
  22. 559 part.mask_flags = 0;
  23. 560 part.ecclayout = NULL;
  24. 561
  25. 562 new = allocate_partition(master, &part, -1, offset);
  26. 563 if (IS_ERR(new))
  27. 564 return PTR_ERR(new);
  28. 565
  29. 566 start = offset;
  30. 567 end = offset + length;
  31. 568
  32. 569 mutex_lock(&mtd_partitions_mutex);
  33. 570 list_for_each_entry(p, &mtd_partitions, list)
  34. 571 if (p->master == master) {
  35. 572 if ((start >= p->offset) &&
  36. 573 (start < (p->offset + p->mtd.size)))
  37. 574 goto err_inv;
  38. 575
  39. 576 if ((end >= p->offset) &&
  40. 577 (end < (p->offset + p->mtd.size)))
  41. 578 goto err_inv;
  42. 579 }
  43. 580
  44. 581 list_add(&new->list, &mtd_partitions);
  45. 582 mutex_unlock(&mtd_partitions_mutex);
  46. 583
  47. 584 add_mtd_device(&new->mtd);
  48. 585
  49. 586 return ret;
  50. 591 }

add_mtd_partitions()

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

  1. 626 int add_mtd_partitions(struct mtd_info *master,
  2. 627 const struct mtd_partition *parts,
  3. 628 int nbparts)
  4. 629 {
  5. 630 struct mtd_part *slave;
  6. 631 uint64_t cur_offset = 0;
  7. 632 int i;
  8. 636 for (i = 0; i < nbparts; i++) {
  9. 637 slave = allocate_partition(master, parts + i, i, cur_offset);
  10. 642 list_add(&slave->list, &mtd_partitions);
  11. 645 add_mtd_device(&slave->mtd);
  12. 647 cur_offset = slave->offset + slave->mtd.size;
  13. 648 }
  14. 649
  15. 650 return 0;
  16. 651 }

用户空间编程

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

mtd_info_user

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

mtd_oob_buf

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

  1. 35 struct mtd_oob_buf {
  2. 36 __u32 start;
  3. 37 __u32 length;
  4. 38 unsigned char __user *ptr;
  5. 39 };

erase_info_user

  1. 25 struct erase_info_user {
  2. 26 __u32 start;
  3. 27 __u32 length;
  4. 28 };

实例

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

Linux块设备驱动(二) _MTD驱动及其用户空间编程的更多相关文章

  1. linux块设备驱动之实例

    1.注册:向内核注册个块设备驱动,其实就是用主设备号告诉内核这个代表块设备驱动 sbull_major  =  register_blkdev(sbull_major, "sbull&quo ...

  2. Linux块设备驱动(一) _驱动模型

    块设备是Linux三大设备之一,其驱动模型主要针对磁盘,Flash等存储类设备,本文以3.14为蓝本,探讨内核中的块设备驱动模型 框架 下图是Linux中的块设备模型示意图,应用层程序有两种方式访问一 ...

  3. Linux块设备IO子系统(一) _驱动模型

    块设备是Linux三大设备之一,其驱动模型主要针对磁盘,Flash等存储类设备,块设备(blockdevice)是一种具有一定结构的随机存取设备,对这种设备的读写是按块(所以叫块设备)进行的,他使用缓 ...

  4. Linux块设备驱动详解

    <机械硬盘> a:磁盘结构 -----传统的机械硬盘一般为3.5英寸硬盘,并由多个圆形蝶片组成,每个蝶片拥有独立的机械臂和磁头,每个堞片的圆形平面被划分了不同的同心圆,每一个同心圆称为一个 ...

  5. linux块设备模型架构框架

    Linux块设备的原理远比字符设备要复杂得多,尽管在linux这一块的方法论有很多相似之处,但考虑到它是用中块结构,它常常要搭配内存页管理,页缓冲块缓冲来改善硬盘访问的速度,按照块硬件最大的性能要求进 ...

  6. (linux)块设备驱动程序

      1.4.1  Linux块设备驱动程序原理(1) 顾名思义,块设备驱动程序就是支持以块的方式进行读写的设备.块设备和字符设备最大的区别在于读写数据的基本单元不同.块设备读写数据的基本单元为块,例如 ...

  7. Linux时间子系统之(三):用户空间接口函数

    专题文档汇总目录 Notes:用户空间时间相关接口函数: 类型 API 精度 说明 时间 time stime time_t 精度为秒级 逐渐要被淘汰.需要定义__ARCH_WANT_SYS_TIME ...

  8. Linux 块设备驱动 (二)

    linux下Ramdisk驱动 1 什么是Ramdisk Ramdisk是一种模拟磁盘,其数据实际上是存储在RAM中,它使用一部分内存空间来模拟出一个磁盘设备,并以块设备的方式来组织和访问这片内存.对 ...

  9. Linux设备驱动--块设备(二)之相关结构体

    上回最后面介绍了相关数据结构,下面再详细介绍 块设备对象结构 block_device 内核用结构block_device实例代表一个块设备对象,如:整个硬盘或特定分区.如果该结构代表一个分区,则其成 ...

随机推荐

  1. wince开发环境搭建与全套教程

    http://www.cnblogs.com/zhchongyao/archive/2010/12/28/1919176.html http://blog.csdn.net/weiren2006/ar ...

  2. 蓝桥网试题 java 基础练习 特殊的数字

    -------------------------------------------------------- 笑脸 :-) ------------------------------------ ...

  3. eNSP仿真学习,网络入门!

    为了简单的认识Internet的框架的整体结构,简单学习华为的eNSP软件来高度模拟仿真网络框架!(华为和思科公司都发布了自己的网络设备仿真软件,当然我就用国产的吧~) 华为官方的eNSP学习论坛网站 ...

  4. 模仿jquery的fileupload插件

    仅需要new一个对象,将上传后台的url和点击触发上传的元素id传给对象,就可以自从实现上传 暂不支持IE <html> <body> <a href="#&q ...

  5. sqlite数据库学习

    1.0版代码: package com.swust.sqlitedatabase.test; import com.swust.sqlitedatabase.myOpenHelper; import ...

  6. java Socket(TCP)编程小项目

    package 服务器端相关操作; import java.io.Serializable; /* * 创建存储需要传输信息的对象,方便客户端向服务器端传送数据 */ public class Cli ...

  7. java-9 异常处理

    1.异常处理的基础知识 异常是程序中的一些错误,但并不是所有的错误都是异常,并且错误有时候是可以避免的. 比如说,你的代码少了一个分号,那么运行出来结果是提示是错误 java.lang.Error:如 ...

  8. devexpress显示缓冲滚动条与实现类似QQ消息推送效果

    1.一般在项目中处理大数据,或者查询大量数据时,耗时会很长,这个时候缓冲条是必不可少的.这里展示一个devexpress不错的缓冲条,如图所示: 使用到了控件splashScreenManager,运 ...

  9. esri-leaflet入门教程(5)- 动态要素加载

    esri-leaflet入门教程(5)- 动态要素加载 by 李远祥 在上一章节中已经说明了esr-leaflet是如何加载ArcGIS Server提供的各种服务,这些都是服务本身来决定的,API脚 ...

  10. 微信群之Java技术红包问答

    缘起 年前公司拿到B+轮融资,相应的在战略上也做了很大的调整,毕竟B轮要做的事情不仅仅是增加用户数,于是乎公司在2017年的开头补充了一部分技术团队,这次人员选择上主要针对一些工作经验在1-2年的技术 ...