linux驱动移植-Nand Flash ONFI标准和MTD子系统

本文详细介绍了Nand Flash中的ONFI标准,包括Raw Nand分类、内存模型、信号与封装、接口命令等。此外,还探讨了Linux的MTD子系统,讲解了其架构、核心结构体、Nand Flash和Nor Flash的驱动层,并分析了MTD设备的注册过程。通过对Nand Chip结构体和相关命令的解析,揭示了Linux如何抽象出通用接口以简化驱动编写。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

----------------------------------------------------------------------------------------------------------------------------

内核版本:linux 5.2.8根文件系统:busybox 1.25.0u-boot:2016.05----------------------------------------------------------------------------------------------------------------------------

一、ONFI标准

Nand Flash是嵌入式世界里常见的存储器,对于嵌入式开发而言,Nand Flash主要分为两大类:Serial Nand、Raw Nand,这两类Nand的差异是很大的。

Raw Nand是相对于Serial Nand而言的,Serial Nand即串行接口的Nand Flash,比如采用SPI通信协议的Nand Flash,而Raw Nand是并行接口的Nand Flash。

这里我们首先介绍ONFI协议,主要是因为在Nand Flash驱动源码分析的时候涉及到ONFI协议。而我们使用的K9F2G08U0C这款芯片并没有支持ONFI协议,我们将该芯片支持的命令和ONFI 1.0规定的命令对比就可以发现。

1.1 ONFI标准

说到Raw Nand发展史,其实早期的Raw Nand没有统一标准,虽然早在1989年Toshiba便发表了Nand Flash结构,但具体到Raw Nand芯片,各厂商都是自由设计,因此尺寸不统一、存储结构差异大、接口命令不通用等问题导致客户使用起来很难受。

为了改变这一现状,2006年几个主流的Raw Nand厂商(Hynix、Intel、Micron、Phison、Sony、ST)联合起来商量制订一个Raw Nand标准,这个标准叫Open Nand Flash Interface,简称ONFI,2006年12月ONFI 1.0标准正式推出,此后几乎所有的Raw Nand厂商都按照ONFI标准设计生产Raw Nand,从此不管哪家生产的Raw Nand对嵌入式设计者来说几乎都是一样的,至少在驱动代码层面是一样的。

ONFI官网:http://www.onfi.org/,在这里我们下载到ONFI协议规范:

1.2 Raw Nand分类
1.2.1 单元层数

Nand Flash内存单元按照层数可以分为:

  • 单层单元(Single Level Cell,简称SLC):这种类型的闪存在读写数据时具有最为精确,并且还具有持续最长的数据读写寿命的优点。SLC擦写寿命约在9万到10万次之间。这种类型的闪存由于其使用寿命,准确性和综合性能,在企业市场上十分受众。但由于储存成本高、存储容量相对较小,在家用市场则不太受青睐。
  • 多层单元(Multi Level Cell,简称MLC):它的命名来源于它在SLC的1位/单元的基础上,变成了2位/单元。这样做的一大优势在于大大降低了大容量储存闪存的成本,约3000--10000次擦写寿命。
  • 三层单元(Triple Level Cell,简称TLC):TLC闪存是闪存生产中最低廉的规格,其储存达到了3位/单元,虽然高储存密度实现了较廉价的大容量格式,但其读写的生命周期被极大地缩短,擦写寿命只有短短的500~1000次,同时读写速度较差,只适合普通消费者使用,不能达到工业使用的标准。
  • 四层单元(Quad Lebel Cell,简称QLC):QLC每个单元可储存4bit数据,跟TLC相比,QLC的储存密度提高了33%。QLC不仅能经受1000次编程或擦写循环(与TLC相当,甚至更好),而且容量提升了,成本也更低。

结论:SLC>MLC>TLC。

目前大多数U盘都是采用TLC芯片颗粒,其优点是价格便宜,不过速度一般,寿命相对较短。

而SSD固态硬盘中,目前MLC颗粒固态硬盘是主流,其价格适中,速度与寿命相对较好,而低价SSD固态硬盘普遍采用的是TLC芯片颗粒,大家在购买固态硬盘的时候,可以在产品参数中去了解。 

SLC颗粒固态目前主要在一些高端固态硬盘中出现,售价多数上千元,甚至更贵。

智能手机方面,目前多数智能手机存储也是采用TLC芯片存储,而苹果iPhone6部分产品采用的TLC芯片,另外还有部分采用的是MLC芯片颗粒。总的来说,MLC闪存芯片颗粒是时下主流,产品在速度、寿命以及价格上适中,比较适合推荐。

1.2.2 数据线宽度

数据线宽度可以分为x8 、x16。

1.2.3 数据采集模式

数据采集模式可以分为 SDR、DDR。

1.2.4 接口命令标准

接口命令标准可以分为:非标、ONFI。

1.3 Raw Nand内存模型

ONFI规定了Raw Nand内存单元从大到小最多分为:Device、LUN(Die、Target)、Plane、Block、Page、Cell。

  • Device:就是指单片Nand Flash,对外提供Package封装的芯片,1个Device包含1个或者多个LUN;
  • LUN(Die、Target):是接收和执行Flash命令的基本单元,1个LUN包含1个或者多个plane。
  • Plane:1个Plane包含多个Block。
  • Block:能够执行擦除操作的最小单元,通常由多个Page组成。
  • Page:能够执行编程和读操作的最小单元,通常大小为2KB等。
  • Cell:Page中的最小操作擦写读单元,对应一个浮栅晶体管,可以存储1bit或多bit。

其中Page和Block是必有的,因为Page是读写的最小单元,Block是擦除的最小单元。而LUN和Plane则不是必有的(如没有,可认为LUN=1, Plane=1),一般在大容量Raw Nand(至少8Gb以上)上才会出现。

常见的Nand Flash内部只有一个chip(LUN)、每个chip只有1个plane,而有些复杂得,容量更大的Nand Flash,内部有多个chip,每个chip有多个plane。这类的Nand Flash,其实就是多了一个主控将多块Flash叠加在一起,如下图:

注:对于chip的概念,我理解就是上面的LUN,其实任何型号的Nand Flash,都可以称其是一个chip;但是上面我们所提到的,是针对内部来说的,也就是某型号的Nand Flash,内部有几个chip,比如:

  • 三星的2GB的K9WAG08U1A芯片(可以理解为外部芯片/型号)内部装了2个单片是1GB的K9K8G08U0A,此时就称K9WAG08U1A内部有2个chip;
  • 而有些单个的chip,内部又包含多个plane,比如上面的K9K8G08U0A内部包含4个单片是2Gb的Plane;
1.4 Raw Nand信号与封装

ONFI规定了Raw Nand信号线与封装,如下是典型的x8 Raw Nand内部结构图:

除了内存单元外,还有两大组成,分别是IO控制单元和逻辑控制单元,信号线主要挂在IO控制与逻辑单元,x8 Raw Nand主要有15根信号线(其中必须的是13根,$\overline{CE}$和$R\overline{B}$可以不用)。

引脚名称 描述
CLE 命令使能,当CLE为高电平时,$\overline{WE}$ 上升沿锁存I/O输入到命令寄存器
ALE  地址使能,当ALE为高电平时,$\overline{WE}$上升沿锁存I/O输入到地址寄存器
$\overline{CE}$  片选信号,低电位有效
$\overline{RE}$  读使能,低电位有效
$\overline{WE}$  $\overline{WE}$上升沿锁存I/O输入到命令、地址、数据寄存器
$\overline{WP}$  写保护
$R\overline{B}$  就绪/忙输出信号(低电平表示操作还在进行中,高电平表示操作完成)
VCC  电源
VSS 地 
NC 不接 
I/O0 ~ I/O7  数据输入输出(命令、地址、数据公用数据总线)

ONFI规定的封装标准有很多,比如TSOP48、LGA52、BGA63/100/132/152/272/316,其中对于嵌入式开发而言,最常用的是如下图扁平封装的TSOP-48,这种封装常用于容量较小的Raw Nand(1/2/4/8/16/32Gb),1-32Gb容量对于嵌入式设计而言差不多够用,且TSOP-48封装易于PCB设计,因此得以流行。

1.5 Raw Nand接口命令

ONFI 1.0规定了Raw Nand接口命令,如下表所示,其中一部分是必须要支持的(M),还有一部分是可选支持的(O)。必须支持的命令里最常用的是Read(Read Page)、Page Program、Block Erase、Read Status这三条,涵盖读写擦最基本的三种操作。

此外比较重要的还有:

  • Read Status,用于获取命令执行状态与结果。
  • Read Parameter Page:用于获取芯片内部存储的出厂信息(包括内存结构、特性、时序、其他行为参数等),其结构已由ONFI规定如下表,在设计Nand软件驱动时,可以通过获取这个Parameter Page来做到代码通用。

二、MTD设备驱动

MTD(Memory Technology Drivers)是用于访问memory设备( ROM 、 Flash)的Linux 的子系统, MTD 的主要目的是为了使新的memory设备的驱动更加简单,为此它在硬件和上层之间提供了一个抽象的接口。

2.1 MTD子系统概要

在介绍MTD之前,我们思考一个问题,linux内核为什么抽象出了MTD子系统呢?

我们回顾一下我们上一节块设备驱动编写的流程:

  • 调用register_blkdev注册块设备主设备号;
  • 使用alloc_disk申请一个通用磁盘对象gendisk;
  • 使用blk_mq_init_sq_queue初始化一个请求队列;
  • 设置gendisk结构体的成员;
    • 设置成员参数major、first_minor、disk_name、fops;
    • 设置成员参数queue,等于之前初始化的请求队列;
  • 使用add_disk注册gendisk;

针对于每一种型号的Flash设备,我们进行块设备驱动编写的时候,都要重复进行如上的操作。那我们就开始想了,各种型号的Flash设备有什么区别呢?以Nand Flash为例,主要就是内存模型(页大小、块大小、页数/块、OOB等)、以及时序参数略有差别,那我们是否可以将与Nand Flash紧密相关的部分抽离出来,由Nand Flash驱动层提供,而其他相同部分单独抽离出来。MTD子系统就是做了这样的事情。

2.2 MTD子系统框架

如上图所示,MTD程序框架通用可以分为四层,从上到下以此为设备节点、MTD设备层、MTD原始设备层,Flash驱动层。

  • 设备节点:通过mknod在/dev子目录下建立MTD块设备节点(主设备号为31)和MTD字符设备节点(主设备号为90),通过访问此设备节点即可访问MTD字符设备和块设备 。
  • MTD设备层:基于MTD原始设备,linux系统可以定义出MTD的块设备(主设备号31)和字符设备(设备号90)。其中:
    • mtdchar.c:MTD字符设备接口相关实现;
    • mtdblock.c:MTD块设备接口相关实现;这部分负责设备的建立、数据的读写、优化处理等。这跟传统的块设备驱动一样,块设备主设备号的申请,gendisk结构体的分配设置、队列的初始化等,这些都是由内核自动完成。
  • MTD原始设备层:用于描述MTD原始设备的数据结构是mtd_info,它定义了大量的关于MTD的数据和操作函数。其中:
    • mtdcore.c: MTD原始设备接口相关实现;
    • mtdpart.c :  MTD分区接口相关实现;
  • Flash驱动层:Flash驱动层负责对Flash硬件的读、写和擦除操作,Nand Flash和Nor Flash有不同的协议和硬件细节,这部分知道发什么,如发送什么命令可以识别、读写、擦除等操作,以及硬件该怎么发。Nand Flash有Nand的协议,Nor Flash有Nor的协议,不同协议有不同的函数,通过对应的结构体和函数构造对应的操作环境。用户只需要完成Flash驱动层的相关结构体的分配、设置、注册,并建立从具体设备到MTD原始设备映射关系。
    • Nand Flash芯片的驱动位于drivers/mtd/nand/子目录下,Nand Flash使用nand_chip结构体;
    • Nor Flash芯片驱动位于drivers/mtd/chips/子目录下,Nor Flash使用map_info结构体;
2.2.1 Flash驱动层

(1) Nor Flash驱动

linux内核实现了针对CFI、JEDEC等接口标准的通用Nor Flash驱动。在上述接口驱动基础上,芯片级驱动较简单 :定义具体内存映射结构体map_info,然后通过接口类型后调用do_map_probe。

以physmap.c(位于drivers/mtd/maps/)为例:

  • 定义map_info结构体,初始化成员name、size、phys、bankwidth;
  • 通过ioremap映射成员virt(虚拟内存地址);
  • 通过函数simple_map_init初始化map_info成员函数read、write、copy_from、copy_to;
  • 通过do_map_probe进行CFI接口探测,返回mtd_info结构体;
  • 通过mtd_device_parse_register注册MTD原始设备;

(2) Nand Flash驱动

linux内核实现了通用Nand Flash驱动(drivers/mtd/nand/raw/nand_base.c),芯片级驱动需要实现nand_chip结构。

MTD使用nand_chip来表示一个Nand Flash芯片, 该结构体包含了关于Nand Flash的内存模型信息,读写方法,ECC模式,硬件控制等一系列底层机制。 

以s3c2410.c(位于drivers/mtd/nand/raw)为例:

  • 分配nand_chip内存;

  • 根据SOC Nand控制器初始化nand_chip成员,比如:chip->legacy(成员write_buf、read_buf、select_chip、cmd_ctrl、dev_ready、IO_ADDR_R、IO_ADDR_W)、chip->controller;

  • 设置chip->priv为mtd_info;
  • 以mtd_info为参数调用nand_scan()探测Nand Flash,nand_scan()会读取nand芯片ID:

    • 初始化chip->base.mtd(成员writesize、oobsize、erasesize等);
    • 初始化chip->base.memorg(成员bits_per_cell、pagesize、oobsize、pages_per_eraseblock、planes_per_lun、luns_per_target、ntatgets等);
    • 初始化chip->options、chip->base.eccreq;
    • 初始化chip->ecc各个成员(设置ecc模式及处理函数);
    • chip成员中所有未初始化函数指针则使用nand_base.c中的默认函数;
  • mtd_info和mtd_partition为参数调用mtd_device_register()进行MTD设备注册;

2.3 MTD核心结构体
2.3.1 struct mtd_info

linux内核使用mtd_info结构体表示MTD原始设备,描述一个设备或一个多分区设备中的一个分区,这其中定义了大量关于MTD的数据和操作函数;所有mtd_info结构体都被存放在mtd_info数组mtd_table中。

mtd_info定义在include/linux/mtd/mtd.h:

struct mtd_info {
        u_char type;     // MTD设备类型  包括MTD_NORFALSH、MTD_NANDFALSH等
        uint32_t flags;  // 标志  MTD_WRITEABLE、MTD_NO_ERASE等
        uint32_t orig_flags; /* Flags as before running mtd checks */
        uint64_t size;   // Total size of the MTD  MTD设备总容量

        /* "Major" erase size for the device. Naïve users may take this
         * to be the only erase size available, or may use the more detailed
         * information below if they desire
         */
        uint32_t erasesize;   // MTD设备擦除单位大小,对于Nand Flash来说就是Block的大小
        /* Minimal writable flash unit size. In case of NOR flash it is 1 (even
         * though individual bits can be cleared), in case of NAND flash it is
         * one NAND page (or half, or one-fourths of it), in case of ECC-ed NOR
         * it is of ECC block size, etc. It is illegal to have writesize = 0.
         * Any driver registering a struct mtd_info must ensure a writesize of
         * 1 or larger.
         */
        uint32_t writesize;  // 可写入数据最小字节数,对于Nor Flash是字节,对于Nand Flash为一页

        /*
         * Size of the write buffer used by the MTD. MTD devices having a write
         * buffer can write multiple writesize chunks at a time. E.g. while
         * writing 4 * writesize bytes to a device with 2 * writesize bytes
         * buffer the MTD driver can (but doesn't have to) do 2 writesize
         * operations, but not 4. Currently, all NANDs have writebufsize
         * equivalent to writesize (NAND page size). Some NOR flashes do have
         * writebufsize greater than writesize.
        uint32_t writebufsize;

        uint32_t oobsize;   // Amount of OOB data per block (e.g. 16)
        uint32_t oobavail;  // Available OOB bytes per block

        /*
         * If erasesize is a power of 2 then the shift is stored in
         * erasesize_shift otherwise erasesize_shift is zero. Ditto writesize.
         */
        unsigned int erasesize_shift;   // 擦除数据偏移值,根据erasesize计算
        unsigned int writesize_shift;    // 写入数据偏移值,根据writesize计算
        /* Masks based on erasesize_shift and writesize_shift */
        unsigned int erasesize_mask;     // 擦除数据大小掩码,根据erasesize_shift计算
        unsigned int writesize_mask;     // 写入数据大小掩码,根据writesize_shift计算

        /*
         * read ops return -EUCLEAN if max number of bitflips corrected on any
         * one region comprising an ecc step equals or exceeds this value.
         * Settable by driver, else defaults to ecc_strength.  User can override
         * in sysfs.  N.B. The meaning of the -EUCLEAN return code has changed;
         * see Documentation/ABI/testing/sysfs-class-mtd for more detail.
         */
        unsigned int bitflip_threshold;

        /* Kernel-only stuff starts here. */
        const char *name;  // MTD设备名称
        int index;         // 索引值  

        /* OOB layout description */
        const struct mtd_ooblayout_ops *ooblayout;  // oob布局描述

        /* NAND pairing scheme, only provided for MLC/TLC NANDs */
        const struct mtd_pairing_scheme *pairing;

        /* the ecc step size. */
        unsigned int ecc_step_size;

        /* max number of correctible bit errors per ecc step */
        unsigned int ecc_strength;

        /* Data for variable erase regions. If numeraseregions is zero,
         * it means that the whole device has erasesize as given above.
         */
        int numeraseregions;  // 可变擦除区域的数目,通常为1
        struct mtd_erase_region_info *eraseregions;  // 可变擦除区域
        /*
         * Do not call via these pointers, use corresponding mtd_*()
         * wrappers instead.
         */
        int (*_erase) (struct mtd_info *mtd, struct erase_info *instr);  // 擦除
        int (*_point) (struct mtd_info *mtd, loff_t from, size_t len,
                       size_t *retlen, void **virt, resource_size_t *phys);
        int (*_unpoint) (struct mtd_info *mtd, loff_t from, size_t len);
        int (*_read) (struct mtd_info *mtd, loff_t from, size_t len,  // 读取
                      size_t *retlen, u_char *buf);
        int (*_write) (struct mtd_info *mtd, loff_t to, size_t len,    // 写入
                       size_t *retlen, const u_char *buf);
        int (*_panic_write) (struct mtd_info *mtd, loff_t to, size_t len,
                             size_t *retlen, const u_char *buf);
        int (*_read_oob) (struct mtd_info *mtd, loff_t from,
                          struct mtd_oob_ops *ops);
        int (*_write_oob) (struct mtd_info *mtd, loff_t to,
                           struct mtd_oob_ops *ops);
        int (*_get_fact_prot_info) (struct mtd_info *mtd, size_t len,
                                    size_t *retlen, struct otp_info *buf);
        int (*_read_fact_prot_reg) (struct mtd_info *mtd, loff_t from,
                                    size_t len, size_t *retlen, u_char *buf);
        int (*_get_user_prot_info) (struct mtd_info *mtd, size_t len,
                                    size_t *retlen, struct otp_info *buf);
        int (*_read_user_prot_reg) (struct mtd_info *mtd, loff_t from,
                                    size_t len, size_t *retlen, u_char *buf);
        int (*_write_user_prot_reg) (struct mtd_info *mtd, loff_t to,
                                     size_t len, size_t *retlen, u_char *buf);
        int (*_lock_user_prot_reg) (struct mtd_info *mtd, loff_t from,
                                    size_t len);
        int (*_writev) (struct mtd_info *mtd, const struct kvec *vecs,
                        unsigned long count, loff_t to, size_t *retlen);
        void (*_sync) (struct mtd_info *mtd);
        int (*_lock) (struct mtd_info *mtd, loff_t ofs, uint64_t len);
        int (*_unlock) (struct mtd_info *mtd, loff_t ofs, uint64_t len);
        int (*_is_locked) (struct mtd_info *mtd, loff_t ofs, uint64_t len);
        int (*_block_isreserved) (struct mtd_info *mtd, loff_t ofs);
        int (*_block_isbad) (struct mtd_info *mtd, loff_t ofs);  
        int (*_block_markbad) (struct mtd_info *mtd, loff_t ofs);
        int (*_max_bad_blocks) (struct mtd_info *mtd, loff_t ofs, size_t len);
        int (*_suspend) (struct mtd_info *mtd);
        void (*_resume) (struct mtd_info *mtd);
        void (*_reboot) (struct mtd_info *mtd);
        /*
         * If the driver is something smart, like UBI, it may need to maintain
         * its own reference counting. The below functions are only for driver.
         */
        int (*_get_device) (struct mtd_info *mtd);
        void (*_put_device) (struct mtd_info *mtd);

        struct notifier_block reboot_notifier;  /* default mode before reboot */

        /* ECC status information */
        struct mtd_ecc_stats ecc_stats;
        /* Subpage shift (NAND) */
        int subpage_sft;

        void *priv;

        struct module *owner;
        struct device dev;
        int usecount;
        struct mtd_debug_info dbg;
        struct nvmem_device *nvmem;
};

mtd_info结构体中的read()、write()、read_oob()、write_oob()、erase()是MTD设备驱动要实现的主要函数,这是

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Graceful_scenery

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值