MTD源码分析(1)

Linux MTD源代码分析
by jim zeus
vision 0.1

If you got any Problem, Suggestion, Advice or Question ,
Please mail to: jimzeus@sina.com.cn
Any correction will be appreciated.

专有名词:

 

1. MTD Memory Technology Device ,内存技术设备,

2. JEDEC Joint Electron Device Engineering Council ,电子电器设备联合会

3. CFI Common Flash Interface ,通用 Flash 接口, Intel 发起的一个 Flash 的接口标准

4. OOB out of band ,某些内存技术支持 out-of-band 数据——例如, NAND flash 512 字节的块有 16 个字节的 extra data ,用于纠错或元数据。

5. ECC error correction ,某些硬件不仅允许对 flash 的访问,也有 ecc 功能,所有 flash 器件都受位交换现象的困扰。在某些情况下,一个比特位会发生反转或被报告反转了,如果此位真的反转了,就要采用 ECC 算法。

6. erasesize 一个 erase 命令可以擦除的最小块的尺寸

7. buswidth MTD 设备的接口总线宽度

8. interleave 交错数,几块芯片平行连接成一块芯片,使 buswidth 变大

9. devicetype 芯片类型, x8 x16 或者 x32

10.              NAND 一种 Flash 技术,参看 NAND NOR 的比较

11.              NOR 一种 Flash 技术,参看 NAND NOR 的比较


Linux MTD 介绍:

MTD(memory technology device 内存技术设备 ) 是用于访问 memory 设备( ROM flash )的 Linux 的子系统。 MTD 的主要目的是为了使新的 memory 设备的驱动更加简单,为此它在硬件和上层之间提供了一个抽象的接口。 MTD 的所有源代码在 /drivers/mtd 子目录下。我将 CFI 接口的 MTD 设备分为四层(从设备节点直到底层硬件驱动),这四层从上到下依次是:设备节点、 MTD 设备层、 MTD 原始设备层和硬件驱动层。

        图1 Linux MTD 的层次结构 

        具体的NAND闪存驱动是和NAND通用驱动相关联的,要实现一个NAND闪存硬件驱动,需要实现以下部分:初始化函数,硬件相关的设备就绪函数和控制函数,为了灵活起见,还可以实现硬件相关的命令函数、硬件相关的等待函数和硬件ECC函数。

一、 Flash 硬件驱动层 :硬件驱动层负责在 init 时驱动 Flash 硬件, Linux MTD 设备的 NOR   Flash 芯片驱动遵循 CFI 接口标准,其驱动程序位于 drivers/mtd/chips 子目录下。 NAND Flash 的驱动程序则位于 /drivers/mtd/nand 子目录下

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

       用于描述 MTD 原始设备的数据结构是 mtd_info ,这其中定义了大量的关于 MTD 的数据和操作函数。 mtd_table mtdcore.c )则是所有 MTD 原始设备的列表, mtd_part mtd_part.c )是用于表示 MTD 原始设备分区的结构,其中包含了 mtd_info ,因为每一个分区都是被看成一个 MTD 原始设备加在 mtd_table 中的, mtd_part.mtd_info 中的大部分数据都从该分区的主分区 mtd_part->master 中获得。

       drivers/mtd/maps/ 子目录下存放的是特定的 flash 的数据,每一个文件都描述了一块板子上的 flash 。其中调用 add_mtd_device() del_mtd_device() 建立 / 删除 mtd_info 结构并将其加入 / 删除 mtd_table (或者调用 add_mtd_partition() del_mtd_partition() mtdpart.c )建立 / 删除 mtd_part 结构并将 mtd_part.mtd_info 加入 / 删除 mtd_table 中)。

三、 MTD 设备层 :基于 MTD 原始设备, linux 系统可以定义出 MTD 的块设备(主设备号 31 )和字符设备(设备号 90 )。 MTD 字符设备的定义在 mtdchar.c 中实现,通过注册一系列 file operation 函数( lseek open close read write )。 MTD 块设备则是定义了一个描述 MTD 块设备的结构 mtdblk_dev ,并声明了一个名为 mtdblks 的指针数组,这数组中的每一个 mtdblk_dev mtd_table 中的每一个 mtd_info 一一对应。

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

五、根文件系统 :在 Bootloader 中将 JFFS (或 JFFS2 )的文件系统映像 jffs.image (或 jffs2.img )烧到 flash 的某一个分区中,在 /arch/arm/mach-your/arch.c 文件的 your_fixup 函数中将该分区作为根文件系统挂载。

六、文件系统: 内核启动后,通过 mount 命令可以将 flash 中的其余分区作为文件系统挂载到 mountpoint 上。

NOR Flash 芯片驱动与 MTD 原始设备

       所有的 NOR Flash 的驱动(探测 probe )程序都放在 drivers/mtd/chips 下,一个 MTD 原始设备可以由一块或者数块相同的 Flash 芯片组成。假设由 4 devicetype x8 Flash ,每块大小为 8M interleave 2 ,起始地址为 0x01000000 ,地址相连,则构成一个 MTD 原始设备( 0x01000000-0x03000000 ),其中两块 interleave 成一个 chip ,其地址从 0x01000000 0x02000000 ,另两块 interleave 成一个 chip ,其地址从 0x02000000 0x03000000

       请注意,所有组成一个 MTD 原始设备的 Flash 芯片必须是同类型的(无论是 interleave 还是地址相连),在描述 MTD 原始设备的数据结构中也只是采用了同一个结构来描述组成它的 Flash 芯片。

 

 

 

Chip#1      Chip#2

 

 

 

Chip#3      Chip#4

 
           0x03000000

 

 

 

           0x02000000

 

 

 

                 0x01000000

 

每个 MTD 原始设备都有一个 mtd_info 结构,其中的 priv 指针指向一个 map_info 结构, map_info 结构中的 fldrv_priv 指向一个 cfi_private 结构, cfi_private 结构的 cfiq 指针指向一个 cfi_ident 结构, chips 指针指向一个 flchip 结构的数组。其中 mtd_info map_info cfi_private 结构用于描述 MTD 原始设备;因为组成 MTD 原始设备的 NOR Flash 相同, cfi_ident 结构用于描述 Flash 芯片的信息;而 flchip 结构用于描述每个 Flash 芯片的专有信息(比如说起始地址)

 

NAND NOR 的比较

   NOR NAND 是现在市场上两种主要的非易失闪存技术。 Intel 1988 年首先开发出 NOR flash 技术,彻底改变了原先由 EPROM EEPROM 一统天下的局面。紧接着, 1989 年,东芝公司发表了 NAND flash 结构,强调降低每比特的成本,更高的性能,并且象磁盘一样可以通过接口轻松升级。但是经过了十多年之后,仍然有相当多的硬件工程师分不清 NOR NAND 闪存。
  相 “flash 存储器 经常可以与相 “NOR 存储器 互换使用。许多业内人士也搞不清楚 NAND 闪存技术相对于 NOR 技术的优越之处,因为大多数情况下闪存只是用来存储少量的代码,这时 NOR 闪存更适合一些。而 NAND 则是高数据存储密度的理想解决方案。
   NOR 的特点是芯片内执行 (XIP, eXecute In Place) ,这样应用程序可以直接在 flash 闪存内运行,不必再把代码读到系统 RAM 中。 NOR 的传输效率很高,在 1 4MB 的小容量时具有很高的成本效益,但是很低的写入和擦除速度大大影响了它的性能。
   NAND 结构能提供极高的单元密度,可以达到高存储密度,并且写入和擦除的速度也很快。应用 NAND 的困难在于 flash 的管理和需要特殊的系统接口。

性能比较

   flash 闪存是非易失存储器,可以对称为块的存储器单元块进行擦写和再编程。任何 flash 器件的写入操作只能在空或已擦除的单元内进行,所以大多数情况下,在进行写入操作之前必须先执行擦除。 NAND 器件执行擦除操作是十分简单的,而 NOR 则要求在进行擦除前先要将目标块内所有的位都写为 0
  由于擦除 NOR 器件时是以 64 128KB 的块进行的,执行一个写入 / 擦除操作的时间为 5s ,与此相反,擦除 NAND 器件是以 8 32KB 的块进行的,执行相同的操作最多只需要 4ms
  执行擦除时块尺寸的不同进一步拉大了 NOR NADN 之间的性能差距,统计表明,对于给定的一套写入操作 ( 尤其是更新小文件时 ) ,更多的擦除操作必须在基于 NOR 的单元中进行。这样,当选择存储解决方案时,设计师必须权衡以下的各项因素。
   ● NOR 的读速度比 NAND 稍快一些。
   ● NAND 的写入速度比 NOR 快很多。
   ● NAND 4ms 擦除速度远比 NOR 5s 快。
   大多数写入操作需要先进行擦除操作。
   ● NAND 的擦除单元更小,相应的擦除电路更少。


接口差别
   NOR flash 带有 SRAM 接口,有足够的地址引脚来寻址,可以很容易地存取其内部的每一个字节。
   NAND 器件使用复杂的 I/O 口来串行地存取数据,各个产品或厂商的方法可能各不相同。 8 个引脚用来传送控制、地址和数据信息。
   NAND 读和写操作采用 512 字节的块,这一点有点像硬盘管理此类操作,很自然地,基于 NAND 的存储器就可以取代硬盘或其他块设备。


容量和成本
   NAND flash 的单元尺寸几乎是 NOR 器件的一半,由于生产过程更为简单, NAND 结构可以在给定的模具尺寸内提供更高的容量,也就相应地降低了价格。
   NOR flash 占据了容量为 1 16MB 闪存市场的大部分,而 NAND flash 只是用在 8 128MB 的产品当中,这也说明 NOR 主要应用在代码存储介质中, NAND 适合于数据存储, NAND CompactFlash Secure Digital PC Cards MMC 存储卡市场上所占份额最大。


可靠性和耐用性
  采用 flahs 介质时一个需要重点考虑的问题是可靠性。对于需要扩展 MTBF 的系统来说, Flash 是非常合适的存储方案。可以从寿命 ( 耐用性 ) 、位交换和坏块处理三个方面来比较 NOR NAND 的可靠性。
  寿命 ( 耐用性 )
  在 NAND 闪存中每个块的最大擦写次数是一百万次,而 NOR 的擦写次数是十万次。 NAND 存储器除了具有 10 1 的块擦除周期优势,典型的 NAND 块尺寸要比 NOR 器件小 8 倍,每个 NAND 存储器块在给定的时间内的删除次数要少一些。
  位交换
  所有 flash 器件都受位交换现象的困扰。在某些情况下 ( 很少见, NAND 发生的次数要比 NOR ) ,一个比特位会发生反转或被报告反转了。
  一位的变化可能不很明显,但是如果发生在一个关键文件上,这个小小的故障可能导致系统停机。如果只是报告有问题,多读几次就可能解决了。
  当然,如果这个位真的改变了,就必须采用错误探测 / 错误更正 (EDC/ECC) 算法。位反转的问题更多见于 NAND 闪存, NAND 的供应商建议使用 NAND 闪存的时候,同时使用 EDC/ECC 算法。
  这个问题对于用 NAND 存储多媒体信息时倒不是致命的。当然,如果用本地存储设备来存储操作系统、配置文件或其他敏感信息时,必须使用 EDC/ECC 系统以确保可靠性。
  坏块处理
   NAND 器件中的坏块是随机分布的。以前也曾有过消除坏块的努力,但发现成品率太低,代价太高,根本不划算。
   NAND 器件需要对介质进行初始化扫描以发现坏块,并将坏块标记为不可用。在已制成的器件中,如果通过可靠的方法不能进行这项处理,将导致高故障率。

易于使用
  可以非常直接地使用基于 NOR 的闪存,可以像其他存储器那样连接,并可以在上面直接运行代码。
  由于需要 I/O 接口, NAND 要复杂得多。各种 NAND 器件的存取方法因厂家而异。
  在使用 NAND 器件时,必须先写入驱动程序,才能继续执行其他操作。向 NAND 器件写入信息需要相当的技巧,因为设计师绝不能向坏块写入,这就意味着在 NAND 器件上自始至终都必须进行虚拟映射。


软件支持
  当讨论软件支持的时候,应该区别基本的读 / / 擦操作和高一级的用于磁盘仿真和闪存管理算法的软件,包括性能优化。
  在 NOR 器件上运行代码不需要任何的软件支持,在 NAND 器件上进行同样操作时,通常需要驱动程序,也就是内存技术驱动程序 (MTD) NAND NOR 器件在进行写入和擦除操作时都需要 MTD
  使用 NOR 器件时所需要的 MTD 要相对少一些,许多厂商都提供用于 NOR 器件的更高级软件,这其中包括 M-System TrueFFS 驱动,该驱动被 Wind River System Microsoft QNX Software System Symbian Intel 等厂商所采用。
  驱动还用于对 DiskOnChip 产品进行仿真和 NAND 闪存的管理,包括纠错、坏块处理和损耗平衡。

 

下面具体介绍每个文件的内容和作用:

 

 

头文件分析

 

mtd.h

MTD_CHAR_MAJOR

#define MTD_CHAR_MAJOR 90         MTD 字符设备的主设备号

MTD_BLOCK_MAJOR

#define MTD_BLOCK_MAJOR 31       MTD 块设备的主设备号

MAX_MTD_DEVICES

#define MAX_MTD_DEVICES 16       最大 MTD 原始设备数

 

 

mtd_info

表示 MTD 原始设备的结构,每个分区也被实现为一个 mtd_info ,如果有两个 MTD 原始设备,每个上有三个分区,在系统中就一共有 6 mtd_info 结构,这些 mtd_info 的指针被存放在名为 mtd_table 的数组里。

struct mtd_info {         

       u_char type;          内存技术的类型

       u_int32_t flags;       标志位

       u_int32_t size; // Total size of the MTD             mtd 设备的大小

 

       /* "Major" erase size for the device. Na e 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; “主要的” erasesize (同一个 mtd 设备可能有数种不同的 erasesize

 

       u_int32_t oobblock;  // Size of OOB blocks (e.g. 512)             oob 块大小

       u_int32_t oobsize;   // Amount of OOB data per block (e.g. 16)              oob 特别数据大小

       u_int32_t ecctype;         ecc 类型

       u_int32_t eccsize;          自动 ecc 可以工作的范围

 

       // Kernel-only stuff starts here.

       char *name;

       int index;

 

       /* Data for variable erase regions. If numeraseregions is zero,

         * it means that the whole device has erasesize as given above.

         */ 不同的 erasesize 的区域

       int numeraseregions; 不同 erasesize 的区域的数目(通常是 1

       struct mtd_erase_region_info *eraseregions;

 

       /* This really shouldn't be here. It can go away in 2.5 */

       u_int32_t bank_size;

 

       struct module *module;

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

                     routine 用于将一个 erase_info 加入 erase queue

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

 

 

       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_ecc) (struct mtd_info *mtd, loff_t from, size_t len, size_t *retlen, u_char *buf, u_char *eccbuf);

       int (*write_ecc) (struct mtd_info *mtd, loff_t to, size_t len, size_t *retlen, const u_char *buf, u_char *eccbuf);

 

       int (*read_oob) (struct mtd_info *mtd, loff_t from, size_t len, size_t *retlen, u_char *buf);

       int (*write_oob) (struct mtd_info *mtd, loff_t to, size_t len, size_t *retlen, const u_char *buf);

 

       /* iovec-based read/write methods. We need these especially for NAND flash,

          with its limited number of write cycles per erase.

          NB: The 'count' parameter is the number of _vectors_, each of

          which contains an (ofs, len) tuple.

       */

       int (*readv) (struct mtd_info *mtd, struct iovec *vecs, unsigned long count, loff_t from, size_t *retlen);

       int (*writev) (struct mtd_info *mtd, const struct iovec *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);

 

       void *priv;                    // 指向 map_info 结构

}

 

mtd_info.type 的取值

#define MTD_ABSENT        0

#define MTD_RAM                     1

#define MTD_ROM                     2

#define MTD_NORFLASH           3

#define MTD_NANDFLASH        4

#define MTD_PEROM          5

#define MTD_OTHER          14

#define MTD_UNKNOWN           15

 

mtd_info.flags 的取值

#define MTD_CLEAR_BITS        1       // Bits can be cleared (flash)

#define MTD_SET_BITS             2       // Bits can be set

#define MTD_ERASEABLE          4       // Has an erase function

#define MTD_WRITEB_WRITEABLE 8       // Direct IO is possible

#define MTD_VOLATILE            16      // Set for RAMs

#define MTD_XIP                32    // eXecute-In-Place possible

#define MTD_OOB                     64    // Out-of-band data (NAND flash)

#define MTD_ECC               128  // Device capable of automatic ECC

 

// Some common devices / combinations of capabilities

#define MTD_CAP_ROM            0

#define MTD_CAP_RAM             (MTD_CLEAR_BITS|MTD_SET_BITS|MTD_WRITEB_WRITEABLE)

#define MTD_CAP_NORFLASH        (MTD_CLEAR_BITS|MTD_ERASEABLE)

#define MTD_CAP_NANDFLASH       (MTD_CLEAR_BITS|MTD_ERASEABLE|MTD_OOB)

#define MTD_WRITEABLE         (MTD_CLEAR_BITS|MTD_SET_BITS)

 

mtd_info.ecctype 的取值

#define MTD_ECC_NONE           0     // No automatic ECC available

#define MTD_ECC_RS_DiskOnChip    1     // Automatic ECC on DiskOnChip

#define MTD_ECC_SW        2     // SW ECC for Toshiba & Samsung devices

 

erase_info

表示 erase 动作的结构,由设备层调用 mtd_info->erase 函数传递给原始设备层

struct erase_info {        

       struct mtd_info *mtd;    被操作的 MTD 原始设备

       u_int32_t addr;              被操作的地址( byte

       u_int32_t len;                长度

       u_long time;

       u_long retries;

       u_int dev;

       u_int cell;

       void (*callback) (struct erase_info *self);   

callback 函数指针,当 erase 结束后该函数被调用

       u_long priv;                   user 模块提供的 private 数据

       u_char state;                  当前状态

       struct erase_info *next;  erase 队列中的下一个 erase_info

};

 

erase_info.state 的取值

#define MTD_ERASE_PENDING          0x01

#define MTD_ERASING                                   0x02

#define MTD_ERASE_SUSPEND               0x04

#define MTD_ERASE_DONE             0x08

#define MTD_ERASE_FAILED            0x10

驱动模块在 erase 要求进入 erase 队列时将 state 设置为 MTD_ERASE_PENDING ,当 callback 被调用时设置为 MTD_ERASE_DONE MTD_ERASE_FAILED

 

 

mtd_notifier

MTD 通知器(这个名字很古怪,但我找不到更好的词来翻译),加入 / 删除 MTD 设备和原始设备时调用的函数,在设备层,当 MTD 字符设备或块设备注册时,如果定义了 CONFIG_DEVFS_FS ,则会将一个 mtd_notifier 加入 MTD 原始设备层的 mtd_notifiers 链表,其中的函数会在两种情况下被调用 , 一是加入 / 删除新的 MTD 字符 / 块设备时,此时调用该 MTD 字符 / 块设备的 notifier 对下层所有的 MTD 原始设备操作一遍,二是加入 / 删除新的 MTD 原始设备时,此时调用所有的 notifier 对该原始设备执行一遍。

struct mtd_notifier {                   

void (*add)(struct mtd_info *mtd);      加入时调用

void (*remove)(struct mtd_info *mtd); 删除时调用

       struct mtd_notifier *next;      指向 mtd_notifiers 队列中的下一个 mtd_notifier

};

 

 

get_mtd_device

格式:

       static inline struct mtd_info *get_mtd_device(struct mtd_info *mtd, int num)

注释:

      

功能:

       获得一个 MTD 设备

说明:

       调用 __get_mtd_device()

参数:

       __get_mtd_device()

返回:

       要求的 MTD 原始设备

调用:

       __get_mtd_device()

被调用:

       设备层函数,在设备层的 open 函数中被调用以获得一个 MTD 原始设备

源代码:

{

       struct mtd_info *ret;

      

       ret = __get_mtd_device(mtd, num);

 

       if (ret && ret->module && !try_inc_mod_count(ret->module))

              return NULL;

 

       return ret;

}

 

put_mtd_device

格式:

       static inline void put_mtd_device(struct mtd_info *mtd)

注释:

      

功能:

       归还( put )一个使用完的 MTD 设备

说明:

      

参数:

       MTD 设备

返回:

      

调用:

      

被调用:

       设备层函数,通常在设备层的 release 函数中调用。

源代码:

{

       if (mtd->module)

              __MOD_DEC_USE_COUNT(mtd->module);

}

 

 

partitions.h

此文件中的数据用来从 /maps 子目录下的文件向原始设备层传递分区信息

 

mtd_partition

/*

  * 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.

  * 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.

  */

struct mtd_partition {     描述 mtd 设备分区的结构,在 MTD 原始设备层调用 add_mtd_partions

传递分区信息

       char *name;           /* identifier string */     

       u_int32_t size;        /* partition size */

       u_int32_t offset;            /* offset within the master MTD space */

       u_int32_t mask_flags;    /* master MTD flags to mask out for this partition */ 将被掩去的标

志位,(例如如果在分区时置 mask_flags WRITEABLE ,则此分区为只读分区)

};

 

 

MTDPART_OFS_APPEND

#define MTDPART_OFS_APPEND      (-1)          如果 mtd_partition.offset 取此值,表示此分区

offset 紧接上一个分区的结束

MTDPART_SIZ_FULL

#define MTDPART_SIZ_FULL     (0)                  如果 mtd_partion.size 取此值,表示此分区一

直到 MTD 原始设备的最后结束

 

 

 

map.h

 

map_info

 

struct map_info {

       char *name;

       unsigned long size;

       int buswidth; /* in octets */

       __u8 (*read8)(struct map_info *, unsigned long);              Flash 的读写函数,( NOR Flash

       __u16 (*read16)(struct map_info *, unsigned long);   可以像内存一样进行读写), read8

       __u32 (*read32)(struct map_info *, unsigned long);  read16 read32 分别用于 buswidth

1 2 4 字节的情况( write 同理)

       /* If it returned a 'long' I'd call it readl.

         * It doesn't.

         * I won't.

         * dwmw2 */

      

       void (*copy_from)(struct map_info *, void *, unsigned long, ssize_t);

       void (*write8)(struct map_info *, __u8, unsigned long);

       void (*write16)(struct map_info *, __u16, unsigned long);

       void (*write32)(struct map_info *, __u32, unsigned long);

       void (*copy_to)(struct map_info *, unsigned long, const void *, ssize_t);

 

       void (*set_vpp)(struct map_info *, int);

       /* We put these two here rather than a single void *map_priv,

          because we want mappers to be able to have quickly-accessible

          cache for the 'currently-mapped page' without the _extra_

          redirection that would be necessary. If you need more than

          two longs, turn the second into a pointer. dwmw2 */

       unsigned long map_priv_1;

       unsigned long map_priv_2;

       void *fldrv_priv;           指向 cfi_private 结构

       struct mtd_chip_driver *fldrv;

};

 

 

mtd_chip_driver

struct mtd_chip_driver {

       struct mtd_info *(*probe)(struct map_info *map);     该芯片的 probe 程序

       void (*destroy)(struct mtd_info *);

       struct module *module;         模块

       char *name;           芯片名

       struct list_head list;

};

MTD 芯片驱动器的结构

 

 

 

gen_probe.h

 

chip_probe

struct chip_probe {

       char *name;

       int (*probe_chip)(struct map_info *map, __u32 base,

                       struct flchip *chips, struct cfi_private *cfi);   

 

};

通用探测程序的参数结构,此结构类型将作为参数 传进 mtd_do_chip_probe() 中, mtd_do_chip_probe() 将间接调用其中的 probe_chip 函数

 

 

 

cfi.h

cfi_private

CFI 私有信息

struct cfi_private {       

       __u16 cmdset;

       void *cmdset_priv;

       int interleave;         芯片交错数, interleave 片芯片交织连接成一个芯片

       int device_type;      每一片 Flash 的类型( 1 8bits 2 16bits 4 32bits

       int cfi_mode;         /* Are we a JEDEC device pretending to be CFI? */    CFI 的模式

       int addr_unlock1;   命令地址,所谓“ unlock ”是指无论 Flash 处于读模式或写模式,都

       int addr_unlock2;          可向该地址写命令

       int fast_prog;         允许 Fast Program 模式

       struct mtd_info *(*cmdset_setup)(struct map_info *);

       struct cfi_ident *cfiq; /* For now only one. We insist that all devs     每个芯片的信息

                              must be of the same type. */                   (所有芯片是同类型的)

       int mfr, id;

       int numchips;         Flash 芯片个数( N interleave 算一块芯片)

       unsigned long chipshift; /* Because they're of the same type */

每块芯片大小 *interleave=2 chipshift 次方

       const char *im_name;    /* inter_module name for cmdset_setup */

       struct flchip chips[0];  /* per-chip data structure for each chip */ 每一块芯片的信息

};

 

 

cfi_private.cfi_mode 的取值

#define CFI_MODE_CFI      0            真的是 CFI

#define CFI_MODE_JEDEC  1            JEDEC 仿真 CFI

 

cfi_ident

CFI 接口芯片的查询结构,从芯片起始地址 +0x10 开始的 0x2C 个字节

/* Basic Query Structure */

struct cfi_ident {

  __u8  qry[3];           QRY        0x10,0x11,0x12

  __u16 P_ID;             制造商 ID      Primary ID            0x13,0x14

  __u16 P_ADR;                               Primary Address    0x15,0x16

  __u16 A_ID;                                  Alternate ID          0x17,0x18     

  __u16 A_ADR;                               Alternate Address   0x19,0x1A

  __u8  VccMin;         逻辑供电 Vcc 最小电压        0x1B

  __u8  VccMax;        逻辑供电 Vcc 最大电压        0x1C

  __u8  VppMin;         编程 / 擦除供电 Vpp 最小电压       0x1D

  __u8  VppMax;        编程 / 擦除供电 Vpp 最大电压       0x1E

  __u8  WordWriteTimeoutTyp;         典型单字节 / 字写周期定时时间           0x1F

  __u8  BufWriteTimeoutTyp;                                              0x20

  __u8  BlockEraseTimeoutTyp; 典型单块擦除定时时间        0x21

  __u8  ChipEraseTimeoutTyp;   典型整片擦除定时时间        0x22

  __u8  WordWriteTimeoutMax; 单字节 / 字写周期最大定时时间          0x23

  __u8  BufWriteTimeoutMax;                                              0x24

  __u8  BlockEraseTimeoutMax; 单块擦除最大定时时间       0x25

  __u8  ChipEraseTimeoutMax; 整片擦除最大定时时间        0x26

  __u8  DevSize;                      器件体积                             0x27

  __u16 InterfaceDesc;               Flash 器件接口识别码 ID     0x28,0x29

  __u16 MaxBufWriteSize;                                                    0x2A,0x2B

  __u8  NumEraseRegions;       器件可擦除块区域个数        0x2C

  __u32 EraseRegionInfo[0]; /* Not host ordered */   擦除块区域信息:        0x2D 0x3C

} __attribute__((packed));`           bit15-0=y 该擦除块区域内含同样体积擦除块的个数 =y+1

bit31-16=z 该区域每个擦除块体积 =z x 256 字节

 

 

 

 

 

 

 

cfi_ident.P_ID 的取值

#define P_ID_NONE 0

#define P_ID_INTEL_EXT 1

#define P_ID_AMD_STD 2

#define P_ID_INTEL_STD 3

#define P_ID_AMD_EXT 4

#define P_ID_MITSUBISHI_STD 256

#define P_ID_MITSUBISHI_EXT 257

#define P_ID_RESERVED 65535

 

 

flashchip.h

 

 

flchip

struct flchip {

       unsigned long start; /* Offset within the map */

       //     unsigned long len;          假设所有的 Flash chip 都是相同的,所以没有定义 len

       /* We omit len for now, because when we group them together

          we insist that they're all of the same size, and the chip size

          is held in the next level up. If we get more versatile later,

          it'll make it a damn sight harder to find which chip we want from

          a given offset, and we'll want to add the per-chip length field

          back in.

       */

       flstate_t state;

       flstate_t oldstate;

       spinlock_t *mutex;

       spinlock_t _spinlock; /* We do it like this because sometimes they'll be shared. */

       wait_queue_head_t wq; /* Wait on here when we're waiting for the chip

                          to be ready */

       int word_write_time;

       int buffer_write_time;

       int erase_time;

};

 

 

 

/drivers/mtd/ 子目录

 

 

mtdcore.c

本文件主要实现了 MTD 原始设备层的数据结构 mtd_table 和对其的操作函数,主要包括 add_mtd_device() (加入 MTD 原始设备)、 del_mtd_device() (删除 MTD 原始设备)

mtd_table

static struct mtd_info *mtd_table[MAX_MTD_DEVICES];

标示 MTD 设备的数组,最多可以有 MAX_MTD_DEVICES 个设备(每一个 MTD 分区也算一个 MTD 设备)

 

mtd_notifiers

static struct mtd_notifier *mtd_notifiers = NULL;

标示 mtd_notifier 队列的指针

 

 

add_mtd_device

格式:

int add_mtd_device(struct mtd_info *mtd)

注释 :

/**

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

  */

功能:

加入一个 MTD 原始设备。

参数:

mtd :被加入的原始设备

返回:

       成功:返回 0

       mtd_table 已满:返回 1

说明:

1.  加入此原始设备的 mtd_info mtd_table 中,

2.  notify 所有的 MTD 设备一个 mtd 被加入(调用所有的 notifier->add(mtd) )。

调用:

       notifers->add(mtd)

被调用:

       Flash 相关文件( your-flash.c )初始化时( maps 子目录下的文件中的 init_xxx 函数)

源代码:

{

       int i;

 

       down(&mtd_table_mutex);

 

       for (i=0; i< MAX_MTD_DEVICES; i++)

              if (!mtd_table[i])                         // 找到 mtd_table 中的空项

              {

                     struct mtd_notifier *not=mtd_notifiers;

 

                     mtd_table[i] = mtd;               // 将其指向新加入的 MTD 原始设备

                     mtd->index = i;

                     DEBUG(0, "mtd: Giving out device %d to %s/n",i, mtd->name);

                     while (not)                                  // 如果有 notifier ,调用所有的 notifier 对新加

                     {                                               // 入的 MTD 原始设备操作一遍

                            (*(not->add))(mtd);

                            not = not->next;

                     }

                     up(&mtd_table_mutex);

                     MOD_INC_USE_COUNT;

                     return 0;

              }

      

       up(&mtd_table_mutex);

       return 1;

}    

 

del_mtd_device

格式:

       int del_mtd_device (struct mtd_info *mtd)

注释:

/**

  *    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.

  */

 

功能:

       删除一个 mtd 设备。

说明:

       mtd_table 中删除此设备的 mtd_info ,通知所有的 MTD 设备(调用所有的 notifier.remove mtd ))

参数:

       mtd :描述此设备的 mtd_info

返回:

       成功:返回 0

       此设备不在 mtd_table 中:返回 1

调用:

       notifiers->remove(mtd)

被调用:

       Flash 相关文件( your-flash.c )清除设备时( drivers/mtd/maps 子目录下的文件中的 cleanupt_xxx 函数)

源代码:

{

       struct mtd_notifier *not=mtd_notifiers;

       int i;

      

       down(&mtd_table_mutex);

 

       for (i=0; i < MAX_MTD_DEVICES; i++)

       {

              if (mtd_table[i] == mtd)

              {

                     while (not)                                         // 如果有 notifier ,调用所有的 notifier

                     {                                                      // 被删除的 MTD 原始设备操作一遍

                            (*(not->remove))(mtd);

                            not = not->next;

                     }

                     mtd_table[i] = NULL;

                     up (&mtd_table_mutex);

                     MOD_DEC_USE_COUNT;

                     return 0;

              }

       }

 

       up(&mtd_table_mutex);

       return 1;

}

 

 

register_mtd_user

格式:

       void register_mtd_user (struct mtd_notifier *new)

注释:

/**

  *    register_mtd_user - register a 'user' of MTD devices.

  *    @new: pointer to notifier info structure

  *

  *    Registers a pair of callbacks function to be called upon addition

  *    or removal of MTD devices. Causes the 'add' callback to be immediately

  *    invoked for each MTD device currently present in the system.

  */

 

功能:

       加入一个 MTD 原始设备的使用者(即 MTD 设备, MTD 块设备或字符设备)。

说明:

1.  将新的 mtd 通知器 new 加入 notifer 队列中,

2.  增加模块的 use count

3.  对所有已有的 MTD 原始设备调用 new->add()

参数:

       new :新 MTD 设备的 mtd_notifier

返回:

      

调用:

       new->add()

被调用:

       (如果定义了 CONFIG_DEVFS MTD 设备级的初始化( mtdblock.c mtdchar.c ftl.c 中的 init_xxx

源代码:

{

       int i;

 

       down(&mtd_table_mutex);

 

       new->next = mtd_notifiers;

       mtd_notifiers = new;

 

       MOD_INC_USE_COUNT;

      

       for (i=0; i< MAX_MTD_DEVICES; i++)     // 对所有的 MTD 原始设备操作一遍

              if (mtd_table[i])

                     new->add(mtd_table[i]);

 

       up(&mtd_table_mutex);

}

 

 

unregister_mtd_user

格式:

       int unregister_mtd_user (struct mtd_notifier *old)

注释:

/**

  *    unregister_mtd_user - unregister a 'user' of MTD devices.

  *    @new: pointer to notifier info structure

  *

  *    Removes a callback function pair from the list of 'users' to be

  *    notified upon addition or removal of MTD devices. Causes the

  *    'remove' callback to be immediately invoked for each MTD device

  *    currently present in the system.

  */

功能:

       删除一个 MTD 设备

说明:

1.  将被删除 MTD 设备的通知器 old notifer 队列中删除

2.  减模块的 use count

3.  对所有已有的 MTD 原始设备调用 old->remove()

参数:

       old :被删除 MTD 设备的 mtd_notifier

返回:

       成功:返回 0

       notifiers 队列中无此设备的 notifier :返回 1

调用:

       old->remove()

被调用:

       (如果定义了 CONFIG_DEVFS MTD 设备级的清除( mtdblock.c mtdchar.c ftl.c 中的 cleanup_xxx

源代码:

{

       struct mtd_notifier **prev = &mtd_notifiers;

       struct mtd_notifier *cur;

       int i;

 

       down(&mtd_table_mutex);

 

       while ((cur = *prev)) {

              if (cur == old) {

                     *prev = cur->next;

 

                     MOD_DEC_USE_COUNT;

 

                     for (i=0; i< MAX_MTD_DEVICES; i++)     // 对所有的 MTD 原始设备调用一遍

                            if (mtd_table[i])                                  //remove 函数

                                   old->remove(mtd_table[i]);

                    

                     up(&mtd_table_mutex);

                     return 0;

              }

              prev = &cur->next;

       }

       up(&mtd_table_mutex);

       return 1;

}

 

 

__get_mtd_device

格式:

       struct mtd_info *__get_mtd_device(struct mtd_info *mtd, int num)

注释:

/**

  *    __get_mtd_device - obtain a validated handle for an MTD device

  *    @mtd: last known address of the required MTD device

  *    @num: internal device number of the required MTD device

  *

  *    Given a number and NULL address, return the num'th entry in the device

  *    table, if any.    Given an address and num == -1, search the device table

  *    for a device with that address and return if it's still present. Given

  *    both, return the num'th driver only if its address matches. Return NULL

  *    if not. get_mtd_device() increases the use count, but

  *    __get_mtd_device() doesn't - you should generally use get_mtd_device().

  */

功能:

       返回指定的 MTD 原始设备的 handler

说明:

       num==-1&mtd!=NULL :返回与 mtd 地址相同的设备

       0<num<MAX_MTD_DEVICES&mtd==NULL :返回 mtd_table 中的第 num MTD 设备

       0<num<MAX_MTD_DEVICES&mtd!=NULL :如果 mtd_table 中第 num 个设备为 mtd

返回 mtd ;否则返回空。

       其余情况:返回 NULL

参数:

       mtd :指定 MTD 原始设备的指针

       num :见说明

返回:

       见说明

调用:

      

被调用:

       get_mtd_device()

源代码:

{

       struct mtd_info *ret = NULL;

       int i;

 

       down(&mtd_table_mutex);

 

       if (num == -1) {

              for (i=0; i< MAX_MTD_DEVICES; i++)

                     if (mtd_table[i] == mtd)

                            ret = mtd_table[i];

       } else if (num < MAX_MTD_DEVICES) {

              ret = mtd_table[num];

              if (mtd && mtd != ret)

                     ret = NULL;

       }

      

       up(&mtd_table_mutex);

       return ret;

}

 

 

mtdpart.c

       MTD 原始设备层分区的实现, mtd_part 结构是用于描述分区的,由 mtd_partitons mtd_part 中的 list 成员链成一个链表。每个 mtd_part 结构中的 mtd_info 结构用于描述本分区,被加入 mtd_table 中,其中大部分成员由其主分区 mtd_part->master 决定,各种函数也指向主分区的相应函数。而主分区(其大小涵盖所有分区)则不作为一个 MTD 原始设备加入 mtd_table

       本文件向外提供的 add_mtd_partitions() del_mtd_partitions() 根据参数 map_info 结构将所有的分区加入 mtd_table

       mtdpart.c 中还实现了 part_read part_write 等函数,这些函数注册在每个分区中,指向主分区的 read write 函数,之所以这样做而不直接将主分区的 read write 函数连接到每个分区中的原因是因为函数中的参数 mtd_info 会被调用者置为函数所属的 mtd_info ,即 mtd->read(mtd…) ,而参数 mtd_info 其实应该指向主分区。

mtd_partitions

/* Our partition linked list */

static LIST_HEAD(mtd_partitions);                    MTD 原始设备分区的链表

 

 

mtd_part

描述分区的结构

/* Our partition node structure */

struct mtd_part {          

       struct mtd_info mtd;             分区的信息(大部分由其 master 决定)

       struct mtd_info *master;              该分区的主分区

       u_int32_t offset;                   该分区的偏移地址

       int index;                             分区号

       struct list_head list;

};

 

 

PART(x)

/*

  * Given a pointer to the MTD object in the mtd_part structure, we can retrieve

  * the pointer to that structure with this macro.

  */

#define PART(x)  ((struct mtd_part *)(x))         mtd_info 结构转换为 mtd_part 结构

 

 

add_mtd_partitions

格式:

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

注释:

/*

  * This function, given a master MTD object and a partition table, creates

  * and registers slave MTD objects which are bound to the master according to

  * the partition definitions.

  * (Q: should we register the master MTD object as well?)

  */

功能:

       MTD 原始设备分区

说明:

       打印信息(型如“ Creating 3 MTD partitions on "Your Flash map ”),

       对每一个新建分区 {

建立一个新的 mtd_part 结构,并将其加入 mtd_partitions 中,

根据 master 设置 mtd_part.mtd_info 的成员,

              打印信息(型如“ 0x00020000-0x00800000 : "Your Kernel" ”),

              进行安全性检查,

              根据 master->numeraseregions 处理多 erasesize 区域的事务,

              调用 add_mtd_device 将此分区作为 MTD 设备加入 mtd_table

       }

参数:

       master :指定的 MTD 设备

       parts :关于分区信息的指针

       nbparts :分区的数目

返回:

       成功:返回 0

       分配 mtd_part 时内存不足:返回 -ENOMEM

调用:

       add_mtd_device()

       del_mtd_partitions()

被调用:

       flash 相关的初始化函数中( maps 子目录下的文件的函数 init_xxx 中)

源代码:

{

       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 = kmalloc (sizeof(*slave), GFP_KERNEL);

              if (!slave) {

                     printk ("memory allocation error while creating partitions for /"%s/"/n",

                            master->name);

                     del_mtd_partitions(master);

                     return -ENOMEM;

              }

              memset(slave, 0, sizeof(*slave));

              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.oobblock = master->oobblock;

              slave->mtd.oobsize = master->oobsize;

              slave->mtd.ecctype = master->ecctype;

              slave->mtd.eccsize = master->eccsize;

 

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

              slave->mtd.bank_size = master->bank_size;

 

              slave->mtd.module = master->module;

 

              slave->mtd.read = part_read;

              slave->mtd.write = part_write;

              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->readv)

                     slave->mtd.readv = part_readv;

              if (master->lock)

                     slave->mtd.lock = part_lock;

              if (master->unlock)

                     slave->mtd.unlock = part_unlock;

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

              }

 

              /* register our partition */

              add_mtd_device(&slave->mtd);

       }

 

       return 0;

}

 

 

del_mtd_partitions

格式:

       int del_mtd_partitions(struct mtd_info *master)

注释:

/*

  * This function unregisters and destroy all slave MTD objects which are

  * attached to the given master MTD object.

  */  

功能:

       删除 master 上的所有分区。

说明:

       mtd_partitions 上的每一个分区 {

       如果它的主分区是 master ,将它从 mtd_partitions mtd_table 中删除并 free

}

参数:

       master :被删除分区的主分区

返回:

       返回 0

调用:

       del_mtd_device()

       kfree()

被调用:

       flash 相关的清除函数( maps 子目录下的文件中的 cleanup_xxx 函数)

源代码:

{

       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);        // mtd_partitions 中删除

                     del_mtd_device(&slave->mtd);     // mtd_table 中删除

                     kfree(slave);

                     node = prev;

              }

       }

 

       return 0;

}

 

 

part_read

part_write

part_readv

part_writev

part_erase

part_lock

part_unlock

part_sync

part_suspend

part_resume

格式:

      

注释:

/*

  * MTD methods which simply translate the effective address and pass through

  * to the _real_ device.

  */

功能:

       非主分区的 xxxx 函数。

说明:

       进行安全性检查,

       调用主分区的 xxxx 函数

参数:

      

返回:

      

调用:

      

被调用:

       add_mtd_partitions 中被注册进非主分区的 md_info

源代码:

      

 

 

mtdblock.c

       MTD 设备层的块设备的相关数据结构和程序,其中定义了 MTD 块设备的 notifier MTD 块设备的结构 mtdblk_dev 类型的 mtdblks 数组,该数组中的每个成员与 MTD 原始设备层的 mtd_table 数组中的每个成员一一对应。

notifier

       设备层的 mtdblcok 设备的 notifier

static struct mtd_notifier notifier = {   

        mtd_notify_add,

        mtd_notify_remove,

        NULL

};

 

 

mtdblk_dev

设备层的块设备的结构( and its private , I think ), MTD 字符设备没有相对应的结构。

struct mtdblk_dev {                    

       struct mtd_info *mtd; /* Locked */       下层原始设备层的 MTD 设备结构

       int count;

       struct semaphore cache_sem;

       unsigned char *cache_data;                 缓冲区(与 MTD 设备块对齐)

       unsigned long cache_offset;                缓冲区内数据在原始设备层 MTD 设备内的偏移

       unsigned int cache_size;                      缓冲区大小(通常被设置为 MTD 设备的 erasesize

       enum { STATE_EMPTY, STATE_CLEAN, STATE_DIRTY } cache_state;        缓冲区状态

}           

 

mtdblks

设备层(块)设备数组(与原始设备层的 MAX_MTD_DEVICES MTD 设备一一对应)

static struct mtdblk_dev *mtdblks[MAX_MTD_DEVICES];      

 

 

erase_callback

格式:

       static void erase_callback(struct erase_info *done)

注释:

/*

  * Cache stuff...

  *

  * Since typical flash erasable sectors are much larger than what Linux's

  * buffer cache can handle, we must implement read-modify-write on flash

  * sectors for each block write requests.  To avoid over-erasing flash sectors

  * and to speed things up, we locally cache a whole flash sector while it is

  * being written to until a different sector is required.

  */

功能:

       回调函数,当写完时被调用。

说明:

       erase_write 中被赋给 erase_info 结构

参数:

       done

返回:

      

调用:

       wake_up

被调用:

       erase_write 中被注册

源代码:

{

       wait_queue_head_t *wait_q = (wait_queue_head_t *)done->priv;

       wake_up(wait_q);

}

 

 

erase_write

格式:

       static int erase_write (struct mtd_info *mtd, unsigned long pos,       int len, const char *buf)

注释:

      

功能:

       擦除并写 flash

说明:

1.  声明一个 erase_info 变量 erase 并给其赋值,

2.  调用原始设备层函数 mtd_info->erase(mtd,erase)

3.  调用原始设备层函数 mtd_info->write()

参数:

       mtd :被操作的 MTD 原始设备

       pos MTD 原始设备的起始位置

       len :长度

       buf :被写入的内容

返回:

       FIXME

调用:

       mtd_info->erase

       mtd_info->write

被调用:

       write_cached_data

       do_cached_write

源代码:

{

       struct erase_info erase;

       DECLARE_WAITQUEUE(wait, current);

       wait_queue_head_t wait_q;

       size_t retlen;

       int ret;

 

       /*

         * First, let's erase the flash block.

         */

 

       init_waitqueue_head(&wait_q);

       erase.mtd = mtd;

       erase.callback = erase_callback;

       erase.addr = pos;

       erase.len = len;

       erase.priv = (u_long)&wait_q;

 

       set_current_state(TASK_INTERRUPTIBLE);

       add_wait_queue(&wait_q, &wait);

 

       ret = MTD_ERASE(mtd, &erase);              // 调用 mtd->erase()

       if (ret) {

              set_current_state(TASK_RUNNING);

              remove_wait_queue(&wait_q, &wait);

              printk (KERN_WARNING "mtdblock: erase of region [0x%lx, 0x%x] "

                                 "on /"%s/" failed/n",

                     pos, len, mtd->name);

              return ret;

       }

 

       schedule();  /* Wait for erase to finish. */

       remove_wait_queue(&wait_q, &wait);

 

       /*

         * Next, writhe data to flash.

         */

 

       ret = MTD_WRITE (mtd, pos, len, &retlen, buf);              // 调用 mtd->write()

       if (ret)

              return ret;

       if (retlen != len)

              return -EIO;

       return 0;

}

 

 

write_cached_data

格式:

       static int write_cached_data (struct mtdblk_dev *mtdblk)

注释:

      

功能:

       将指定设备层 MTD 块设备缓冲区内的数据写入 MTD 设备

说明:

      

参数:

       mtdblk :指定的设备层 MTD 块设备结构

返回:

       成功:返回 0

       失败: FIXME

调用:

       erase_write() 写入 MTD 设备

被调用:

       do_cached_write()

       mtdblock_release()

       mtdblock_ioctl()

源代码:

{

       struct mtd_info *mtd = mtdblk->mtd;

       int ret;

 

       if (mtdblk->cache_state != STATE_DIRTY)

              return 0;

 

       DEBUG(MTD_DEBUG_LEVEL2, "mtdblock: writing cached data for /"%s/" "

                     "at 0x%lx, size 0x%x/n", mtd->name,

                     mtdblk->cache_offset, mtdblk->cache_size);

      

       ret = erase_write (mtd, mtdblk->cache_offset,

                        mtdblk->cache_size, mtdblk->cache_data);

       if (ret)

              return ret;

 

       /*

         * Here we could argably set the cache state to STATE_CLEAN.

         * However this could lead to inconsistency since we will not

         * be notified if this content is altered on the flash by other

         * means.  Let's declare it empty and leave buffering tasks to

         * the buffer cache instead.

         */

       mtdblk->cache_state = STATE_EMPTY;

       return 0;

}

 

do_cached_write

格式:

       static int do_cached_write (struct mtdblk_dev *mtdblk, unsigned long pos,

                         int len, const char *buf)

注释:

      

功能:

       buf 向指定的设备层 MTD 块设备中缓冲写

说明:

       如果缓冲区大小为 0 ,则调用 mtd_info->write() 直接向 MTD 设备中写,

       当( len>0 {

              如果是整块 block ,则直接向 MTD 设备中写

              否则 {

先调用 write_cached_data() 将缓冲内的数据写入 MTD 设备

                     在调用 mtd_info->read MTD 设备中将对应块的数据读入缓冲

                     buf 的数据(非整块)写入缓冲

              }

              len 自减

       }

参数:

       mtdblk :指定的 MTD 块设备

       pos :写入原始设备层 MTD 设备的位置

       len :数据长度

       buf :被写入的数据

返回:

       成功:返回 0

       失败:返回错误码

调用:

       erase_write() 用于直接写 MTD 设备

       write_cached_data() 用于清空 MTD 块设备的缓冲区

       mtd_info->read() 用于从 MTD 设备中读数据

       mtd_info->write() 用于向 MTD 设备中写数据

被调用:

       handle_mtdblock_request()

源代码:

{

       struct mtd_info *mtd = mtdblk->mtd;

       unsigned int sect_size = mtdblk->cache_size;

       size_t retlen;

       int ret;

 

       DEBUG(MTD_DEBUG_LEVEL2, "mtdblock: write on /"%s/" at 0x%lx, size 0x%x/n",

              mtd->name, pos, len);

      

       if (!sect_size)

              return MTD_WRITE (mtd, pos, len, &retlen, buf);

 

       while (len > 0) {

              unsigned long sect_start = (pos/sect_size)*sect_size;//we should write by erase block

              unsigned int offset = pos - sect_start;//real start address

              unsigned int size = sect_size - offset;

              if( size > len )

                     size = len;

 

              if (size == sect_size) {

                     /*

                       * We are covering a whole sector.  Thus there is no

                       * need to bother with the cache while it may still be

                       * useful for other partial writes.

                       */

                     ret = erase_write (mtd, pos, size, buf);

                     if (ret)

                            return ret;

              } else {

                     /* Partial sector: need to use the cache */

 

                     if (mtdblk->cache_state == STATE_DIRTY &&

                         mtdblk->cache_offset != sect_start) {

                            ret = write_cached_data(mtdblk);

                            if (ret)

                                   return ret;

                     }

 

                     if (mtdblk->cache_state == STATE_EMPTY ||

                         mtdblk->cache_offset != sect_start) {

                            /* fill the cache with the current sector */

                            mtdblk->cache_state = STATE_EMPTY;

                            ret = MTD_READ(mtd, sect_start, sect_size, &retlen, mtdblk->cache_data);

                            if (ret)

                                   return ret;

                            if (retlen != sect_size)

                                   return -EIO;

 

                            mtdblk->cache_offset = sect_start;

                            mtdblk->cache_size = sect_size;

                            mtdblk->cache_state = STATE_CLEAN;

                     }

 

                     /* write data to our local cache */

                     memcpy (mtdblk->cache_data + offset, buf, size);

                     mtdblk->cache_state = STATE_DIRTY;

              }

 

              buf += size;

              pos += size;

              len -= size;

       }

 

       return 0;

}

 

 

do_cached_read

格式:

       static int do_cached_read (struct mtdblk_dev *mtdblk, unsigned long pos,

                        int len, char *buf)

注释:

      

功能:

       从指定的 MTD 块设备中缓冲读到指定位置 buf

说明:

       如果缓冲区大小为 0 ,调用 mtd_info->read() 直接从原始设备层 MTD 设备中读取

       否则当( len>0 {

              如果缓冲区不为空且其中数据正好是所需数据,则从缓冲区拷贝到 buf

              否则调用 mtd_info->read() MTD 中读

       }

参数:

       mtdblk :指定的 MTD 块设备

       pos MTD 设备中指定的位置

       len :长度

       buf :被写入的地址

返回:

       成功:返回 0

       失败:返回错误码

调用:

       mtd_info->read() 从指定的 MTD 设备中读

被调用:

       handle_mtdblock_request()

源代码:

{

       struct mtd_info *mtd = mtdblk->mtd;

       unsigned int sect_size = mtdblk->cache_size;

       size_t retlen;

       int ret;

 

       DEBUG(MTD_DEBUG_LEVEL2, "mtdblock: read on /"%s/" at 0x%lx, size 0x%x/n",

                     mtd->name, pos, len);

      

       if (!sect_size)

              return MTD_READ (mtd, pos, len, &retlen, buf);

 

       while (len > 0) {

              unsigned long sect_start = (pos/sect_size)*sect_size;

              unsigned int offset = pos - sect_start;

              unsigned int size = sect_size - offset;

              if (size > len)

                     size = len;

 

              /*

                * Check if the requested data is already cached

                * Read the requested amount of data from our internal cache if it

                * contains what we want, otherwise we read the data directly

                * from flash.

                */

              if (mtdblk->cache_state != STATE_EMPTY &&

                  mtdblk->cache_offset == sect_start) {

                     memcpy (buf, mtdblk->cache_data + offset, size);

              } else {

                     ret = MTD_READ (mtd, pos, size, &retlen, buf);

                     if (ret)

                            return ret;

                     if (retlen != size)

                            return -EIO;

              }

 

              buf += size;

              pos += size;

              len -= size;

       }

 

       return 0;

}

 

 

mtdblock_open

格式:

       static int mtdblock_open(struct inode *inode, struct file *file)

注释:

      

功能:

       打开一个 MTD 块设备(设备层)

说明:

       获得指定节点在原始设备层的 MTD 设备( mtd_table ),

如果 其在设备层的对应块设备( mtdblks )被打开,返回,

否则 将其打开并初始化,

MTD 设备的大小 /1024 赋给对应的 mtd_sizes

MTD 设备的 erasesize PAGE_SIZE 中较小的一个赋给对应的 mtd_blksizes

参数:

       inode :指定的索引节点

       file :没用

返回:

       成功:返回 0

       失败:返回错误码

调用:

       get_mtd_device() put_mtd_device() 获取原始设备层的 MTD 设备

被调用:

       注册进 mtd_fops 结构

源代码:

{

       struct mtdblk_dev *mtdblk;

       struct mtd_info *mtd;

       int dev;

 

       DEBUG(MTD_DEBUG_LEVEL1,"mtdblock_open/n");

      

       if (!inode)

              return -EINVAL;

      

       dev = MINOR(inode->i_rdev);

       if (dev >= MAX_MTD_DEVICES)

              return -EINVAL;

 

       MOD_INC_USE_COUNT;

 

       mtd = get_mtd_device(NULL, dev);

       if (!mtd)

              return -ENODEV;

       if (MTD_ABSENT == mtd->type) {

              put_mtd_device(mtd);

              MOD_DEC_USE_COUNT;

              return -ENODEV;

       }

      

       spin_lock(&mtdblks_lock);

 

       /* If it's already open, no need to piss about. */

       if (mtdblks[dev]) {

              mtdblks[dev]->count++;

              spin_unlock(&mtdblks_lock);

              return 0;

       }

      

       /* OK, it's not open. Try to find it */

 

       /* First we have to drop the lock, because we have to

          to things which might sleep.

       */

       spin_unlock(&mtdblks_lock);

 

       mtdblk = kmalloc(sizeof(struct mtdblk_dev), GFP_KERNEL);

       if (!mtdblk) {

              put_mtd_device(mtd);

              MOD_DEC_USE_COUNT;

              return -ENOMEM;

       }

       memset(mtdblk, 0, sizeof(*mtdblk));

       mtdblk->count = 1;

       mtdblk->mtd = mtd;

 

       init_MUTEX (&mtdblk->cache_sem);

       mtdblk->cache_state = STATE_EMPTY;

       if ((mtdblk->mtd->flags & MTD_CAP_RAM) != MTD_CAP_RAM &&

           mtdblk->mtd->erasesize) {

              mtdblk->cache_size = mtdblk->mtd->erasesize;

              mtdblk->cache_data = vmalloc(mtdblk->mtd->erasesize);

              if (!mtdblk->cache_data) {

                     put_mtd_device(mtdblk->mtd);

                     kfree(mtdblk);

                     MOD_DEC_USE_COUNT;

                     return -ENOMEM;

              }

       }

 

       /* OK, we've created a new one. Add it to the list. */

 

       spin_lock(&mtdblks_lock);

 

       if (mtdblks[dev]) {

              /* Another CPU made one at the same time as us. */

              mtdblks[dev]->count++;

              spin_unlock(&mtdblks_lock);

              put_mtd_device(mtdblk->mtd);

              vfree(mtdblk->cache_data);

              kfree(mtdblk);

              return 0;

       }

 

       mtdblks[dev] = mtdblk;

       mtd_sizes[dev] = mtdblk->mtd->size/1024;

       if (mtdblk->mtd->erasesize)

              mtd_blksizes[dev] = mtdblk->mtd->erasesize;

       if (mtd_blksizes[dev] > PAGE_SIZE)

              mtd_blksizes[dev] = PAGE_SIZE;

       set_device_ro (inode->i_rdev, !(mtdblk->mtd->flags & MTD_WRITEABLE));

      

       spin_unlock(&mtdblks_lock);

      

       DEBUG(MTD_DEBUG_LEVEL1, "ok/n");

 

       return 0;

}

 

      

 

mtdblock_release

格式:

       static release_t mtdblock_release(struct inode *inode, struct file *file)

注释:

      

功能:

       释放一个设备层的 MTD 块设备

说明:

       调用 write_cached_data 刷新缓冲区,

       MTD 块设备使用数减 1 ,如果变为 0 ,释放原始设备层的 MTD 设备

参数:

       inode :指定的索引节点

       file :无用

返回:

       成功:返回 0

       inode NULL :返回 -ENODEV

调用:

       write_cached_data() 刷新缓冲

       put_mtd_device() 释放 MTD 设备

被调用:

       注册进 mtd_fops 结构

源代码:

{

       int dev;

       struct mtdblk_dev *mtdblk;

     DEBUG(MTD_DEBUG_LEVEL1, "mtdblock_release/n");

 

       if (inode == NULL)

              release_return(-ENODEV);

 

       invalidate_device(inode->i_rdev, 1);

 

       dev = MINOR(inode->i_rdev);

       mtdblk = mtdblks[dev];

 

       down(&mtdblk->cache_sem);

       write_cached_data(mtdblk);

       up(&mtdblk->cache_sem);

 

       spin_lock(&mtdblks_lock);

       if (!--mtdblk->count) {

              /* It was the last usage. Free the device */

              mtdblks[dev] = NULL;

              spin_unlock(&mtdblks_lock);

              if (mtdblk->mtd->sync)

                     mtdblk->mtd->sync(mtdblk->mtd);

              put_mtd_device(mtdblk->mtd);

              vfree(mtdblk->cache_data);

              kfree(mtdblk);

       } else {

              spin_unlock(&mtdblks_lock);

       }

 

       DEBUG(MTD_DEBUG_LEVEL1, "ok/n");

 

       MOD_DEC_USE_COUNT;

       release_return(0);

 

 

handle_mtdblock_request

格式:

       static void handle_mtdblock_request(void)

注释:

/*

  * This is a special request_fn because it is executed in a process context

  * to be able to sleep independently of the caller.  The io_request_lock

  * is held upon entry and exit.

  * The head of our request queue is considered active so there is no need

  * to dequeue requests before we are done.

  */

功能:

       处理对 MTD 块设备的请求

说明:

       获取当前请求,分情况处理

参数:

      

返回:

      

调用:

       do_cached_read() 处理读请求

       do_cached_write() 处理写请求

被调用:

       mtdblock_thread()

源代码:

{

       struct request *req;

       struct mtdblk_dev *mtdblk;

       unsigned int res;

 

       for (;;) {

              INIT_REQUEST;

              req = CURRENT;

              spin_unlock_irq(&io_request_lock);

              mtdblk = mtdblks[MINOR(req->rq_dev)];

              res = 0;

 

              if (MINOR(req->rq_dev) >= MAX_MTD_DEVICES)

                     panic(__FUNCTION__": minor out of bound");

 

              if ((req->sector + req->current_nr_sectors) > (mtdblk->mtd->size >> 9))

                     goto end_req;

 

              // Handle the request

              switch (req->cmd)

              {

                     int err;

 

                     case READ:

                     down(&mtdblk->cache_sem);

                     err = do_cached_read (mtdblk, req->sector << 9,

                                   req->current_nr_sectors << 9,

                                   req->buffer);

                     up(&mtdblk->cache_sem);

                     if (!err)

                            res = 1;

                     break;

 

                     case WRITE:

                     // Read only device

                     if ( !(mtdblk->mtd->flags & MTD_WRITEABLE) )

                            break;

 

                     // Do the write

                     down(&mtdblk->cache_sem);

                     err = do_cached_write (mtdblk, req->sector << 9,

                                   req->current_nr_sectors << 9,

                                   req->buffer);

                     up(&mtdblk->cache_sem);

                     if (!err)

                            res = 1;

                     break;

              }

 

end_req:

              spin_lock_irq(&io_request_lock);

              end_request(res);

       }

}

 

leaving

static volatile int leaving = 0;         控制线程的变量,在 mtdblock_thread 作为测试条件,在 cleanup_mtdblock 中被赋 1

mtdblock_thread

格式:

       int mtdblock_thread(void *dummy)

注释:

      

功能:

       启动一个 MTD 块设备线程

说明:

       调用 handle_mtdblock_request() 处理对 MTD 块设备的读写

参数:

       dummy FIXME

返回:

       线程结束:返回 0

调用:

       handle_mtdblock_request()

被调用:

       init_mtdblock()

源代码:

{

       struct task_struct *tsk = current;

       DECLARE_WAITQUEUE(wait, tsk);

 

       tsk->session = 1;

       tsk->pgrp = 1;

       /* we might get involved when memory gets low, so use PF_MEMALLOC */

       tsk->flags |= PF_MEMALLOC;

       strcpy(tsk->comm, "mtdblockd");

       tsk->tty = NULL;

       spin_lock_irq(&tsk->sigmask_lock);

       sigfillset(&tsk->blocked);

       recalc_sigpending(tsk);

       spin_unlock_irq(&tsk->sigmask_lock);

       exit_mm(tsk);

       exit_files(tsk);

       exit_sighand(tsk);

       exit_fs(tsk);

 

       while (!leaving) {

              add_wait_queue(&thr_wq, &wait);

              set_current_state(TASK_INTERRUPTIBLE);

              spin_lock_irq(&io_request_lock);

              if (QUEUE_EMPTY || QUEUE_PLUGGED) {

                     spin_unlock_irq(&io_request_lock);

                     schedule();

                     remove_wait_queue(&thr_wq, &wait);

              } else {

                     remove_wait_queue(&thr_wq, &wait);

                     set_current_state(TASK_RUNNING);

                     handle_mtdblock_request();

                     spin_unlock_irq(&io_request_lock);

              }

       }

 

       up(&thread_sem);

       return 0;

}

 

 

mtdblock_ioctl

格式:

static int mtdblock_ioctl(struct inode * inode, struct file * file,

                    unsigned int cmd, unsigned long arg)

注释:

      

功能:

       MTD 块设备的 IO 控制

说明:

      

参数:

       FIXME

返回:

       成功:返回 0

       失败:返回错误码

调用:

       write_cached_data()

被调用:

       被注册进 mtd_fops 结构

源代码:

{

       struct mtdblk_dev *mtdblk;

 

       mtdblk = mtdblks[MINOR(inode->i_rdev)];

 

#ifdef PARANOIA

       if (!mtdblk)

              BUG();

#endif

 

       switch (cmd) {

       case BLKGETSIZE:   /* Return device size */

              return put_user((mtdblk->mtd->size >> 9), (long *) arg);

 

#ifdef BLKGETSIZE64

       case BLKGETSIZE64:

              return put_user((u64)mtdblk->mtd->size, (u64 *)arg);

#endif

             

       case BLKFLSBUF:

#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,2,0)

              if(!capable(CAP_SYS_ADMIN))

                     return -EACCES;

#endif

              fsync_dev(inode->i_rdev);

              invalidate_buffers(inode->i_rdev);

              down(&mtdblk->cache_sem);

              write_cached_data(mtdblk);

              up(&mtdblk->cache_sem);

              if (mtdblk->mtd->sync)

                     mtdblk->mtd->sync(mtdblk->mtd);

              return 0;

 

       default:

              return -EINVAL;

       }

}

 

mtd_fops

MTD 块设备操作的函数结构

static struct block_device_operations mtd_fops =     

{

       open: mtdblock_open,

       release: mtdblock_release,

       ioctl: mtdblock_ioctl

};

 

 

init_mtdblock

格式:

       int __init init_mtdblock(void)

注释:

      

功能:

       初始化 MTD 块设备

说明:

       如果定义了 CONFIG_DEVFS_FS{

              调用 devfs_register_blkdev() 注册块设备,

              调用 register_mtd_user() 将设备层 MTD 块设备的 notifier 注册进原始设备层

}

否则调用 register_blkdev() 注册块设备,

       初始化 mtd_sizes mtd_blksizes 数组,

       启动 mtdblock_thread 线程

参数:

      

返回:

       成功:返回 0

       注册块设备失败:返回 -EAGAIN

调用:

       kernel_thread() 启动 mtdblock_thread 线程

       register_blkdev() 注册块设备

被调用:

       module_init

       __init

源代码:

{

       int i;

 

       spin_lock_init(&mtdblks_lock);

#ifdef CONFIG_DEVFS_FS

       if (devfs_register_blkdev(MTD_BLOCK_MAJOR, DEVICE_NAME, &mtd_fops))

       {

              printk(KERN_NOTICE "Can't allocate major number %d for Memory Technology Devices./n",

                     MTD_BLOCK_MAJOR);

              return -EAGAIN;

       }

 

       devfs_dir_handle = devfs_mk_dir(NULL, DEVICE_NAME, NULL);

       register_mtd_user(&notifier);

#else

       if (register_blkdev(MAJOR_NR,DEVICE_NAME,&mtd_fops)) {

              printk(KERN_NOTICE "Can't allocate major number %d for Memory Technology Devices./n",

                     MTD_BLOCK_MAJOR);

              return -EAGAIN;

       }

#endif

      

       /* We fill it in at open() time. */

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

              mtd_sizes[i] = 0;

              mtd_blksizes[i] = BLOCK_SIZE;

       }

       init_waitqueue_head(&thr_wq);

       /* Allow the block size to default to BLOCK_SIZE. */

       blksize_size[MAJOR_NR] = mtd_blksizes;

       blk_size[MAJOR_NR] = mtd_sizes;

      

       blk_init_queue(BLK_DEFAULT_QUEUE(MAJOR_NR), &mtdblock_request);

       kernel_thread (mtdblock_thread, NULL, CLONE_FS|CLONE_FILES|CLONE_SIGHAND);

       return 0;

}

 

cleanup_mtdblock

格式:

       static void __exit cleanup_mtdblock(void)

注释:

      

功能:

       清除 MTD 块设备

说明:

       如果定义了 CONFIG_DEVFS_FS{

              调用 unregister_mtd_user() 注销 MTD 块设备的 notifier

              调用 devfs_unregister_blkdev() 注销 MTD 块设备

       }

否则调用 unregister_blkdev() 注销 MTD 块设备

参数:

      

返回:

      

调用:

       unregister_blkdev() 注销 MTD 块设备

被调用:

       __exit

       module_exit

源代码:

{

       leaving = 1;

       wake_up(&thr_wq);

       down(&thread_sem);

#ifdef CONFIG_DEVFS_FS

       unregister_mtd_user(&notifier);

       devfs_unregister(devfs_dir_handle);

       devfs_unregister_blkdev(MTD_BLOCK_MAJOR, DEVICE_NAME);

#else

       unregister_blkdev(MAJOR_NR,DEVICE_NAME);

#endif

       blk_cleanup_queue(BLK_DEFAULT_QUEUE(MAJOR_NR));

       blksize_size[MAJOR_NR] = NULL;

       blk_size[MAJOR_NR] = NULL;

}

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值