嵌入式Linux之我行——S3C2440上Flash驱动实例开发讲解

2019-07-12 16:22发布


嵌入式Linux之我行,主要讲述和总结了本人在学习嵌入式linux中的每个步骤。一为总结经验,二希望能给想入门嵌入式Linux的朋友提供方便。如有错误之处,谢请指正。


一、Linux中Flash硬件知识




  1. Flash用途和分类:


    在嵌入式系统开发设计中,存储模块是不可缺少的重要部分,而
    Flash是目前市场上主要的非易失闪存技术,他主要分为:Nor Flash和Nand Flash两种。那么他们有什么区别呢?简单的讲:Nor
    Flash容量小,价格高,写速度慢但随机读速度快,所以较适合存储少量的程序代码,比如u-boot启动代码;而Nand
    Flash则容量大,价格低,写速度快但读速度慢,所以他相当于PC上的硬盘用于存储大量的数据。Nor Flash与Nand
    Flash更详细区别如下表:




  2. Flash在硬件设计中的应用:


    以Mini2440开发板为例:该开发板上带有一块2M的Nor Flash和一块64M的Nand Flash。下面先看看他们是怎样被应用于嵌入式Linux的。Nor Flash和Nand Flash电路原理图分别如下:(由mini2440提供)




    原理图上可以看到,Nor Flash内部提供的是有类似于DRam之类的地址总线,可以直接与CPU相连,CPU可以直接通过地址总线对Nor
    Flash进行访问;而Nand Flash没有这类的总线,其内部只提供IO接口,因此只能通过IO接口发送命令和地址,对Nand
    Flash内部数据进行访问。这可以说是二者最大的区别了,也说明了二者读写速度不同的所在。因此,各有各的优点,Nor Flash访问快,Nand
    Flash简化了电路。注意:电路原理图中字母上面有一横杠的表示该引脚是低电平有效,没有的是默认的高电平。




二、Linux中Flash软件知识




  1. Linux MTD子系统:



    在Linux系统中,提供了MTD(内存技术设备)子系统来建立Flash针对Linux的统一、抽象的接口。MTD子系统将上层文件系统与底层
    Flash硬件进行了隔离,使Flash驱动开发者无需再关心Flash作为字符设备或者块设备与Linux内核的接口。MTD将Linux系统
    Flash设备驱动及接口分成了4个层次,如图所示,从上往下分别为:设备节点、MTD设备层、MTD原始设备层和Flash硬件驱动层。


    设备节点:
    用户在/dev目录下使用mknod命令建立MTD字符设备节点(主设备号为90),或者MTD块设备节点(主设备号为31),使用该设备节点即可访问MTD设备。

    MTD设备层:
    基于MTD原始设备层,系统将MTD设备可以定义为MTD字符设备(在/mtd/mtdchar.c中实现)和MTD块设备(在/mtd/mtdblock.c中实现)。

    MTD原始设备层:
    MTD原始设备层由两个部分组成,分别是MTD原始设备的通用代码和各个特定的Flash的数据,如分区信息。

    Flash硬件驱动层:
    Flash硬件驱动层负责对Flash硬件的读、写和擦除操作。MTD设备的Nor Flash芯片驱动一般位于drivers/mtd/chips/子目录下,Nand Flash芯片的驱动则位于drivers/mtd/nand/子目录下。



    合上述我们可知,MTD子系统已经对Flash设备对于上层的应用进行了封装,我们在写硬件驱动的时候直接调用MTD原始设备层提供的接口函数做相应的操
    作即可。那么,对于MTD设备层,MTD原始设备层提供了哪些接口呢?对于Flash硬件驱动层,MTD原始设备层又提供了哪些接口呢?下面开始了解。




  2. MTD子系统接口:


    在MTD子系统中,MTD设备层、MTD原始设备层和Flash硬件驱动层之间的接口关系如下图所示:


    从上图可知,MTD设备层是通过原始设备层提供的接口来注册MTD字符设备或MTD块设备的,同样,驱动工程师要编写的Flash硬件驱动也是通过原始设备层提供的接口来添加MTD设备和MTD分区的,分别如下:



    //使用这两个接口函数进行添加和删除MTD设备

    int
    add_mtd_device(
    struct
    mtd_info *
    mtd)
    ;

    int
    del_mtd_device(
    struct
    mtd_info *
    mtd)









    //使用这两个接口函数进行添加和删除MTD分区

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

    )
    ;

    int
    del_mtd_partitions(
    struct
    mtd_info *master

    )













    MTD中,一个MTD原始设备用mtd_info结构体来表示,定义在include/linux/mtd/mtd.h中;一个MTD原始设备分区用
    mtd_part结构体来表示,定义在drivers/mtd/mtdpart.c中。其中每个分区也被认为是一个mtd_info,比如:有一个MTD
    原始设备,上面有3个分区,那么将共有3个mtd_info,而这3个mtd_info的指针将被存放在mtd_table的数组中进行管理,定义在
    drivers/mtd/mtdcore.h中,如下所示:



    extern
    struct
    mtd_info *
    mtd_table[
    MAX_MTD_DEVICES]
    ;//最多有MAX_MTD_DEVICES(默认定义为32)个设备











  3. MTD子系统中重要的一些数据结构:



    struct
    mtd_info
    {

    //硬件设备的类型,如:MTD_RAM,MTD_ROM,MTD_NORFlash,MTD_NANDFlash,MTD_PEROM等


    u_char type;


    //设备支持的选项,如:MTD_ERASEABLE(可擦除),MTD_WRITEB_WRITEALBE(可编程),

    //MTD_XIP(可片内执行),MTD_OOB(NAND额外数据),MTD_ECC(支持自动ECC)等

    uint32_t
    flags;


    uint64_t
    size;
    //MTD设备的大小

    uint32_t
    erasesize;
    //主要的擦除块大小(注意:同一个MTD设备可能有几种不同的erasesize)

    uint32_t
    writesize;
    //编程块大小

    uint32_t
    oobsize;
    //OOB数据大小

    uint32_t
    oobavail;


    unsigned
    int
    erasesize_shift;

    unsigned
    int
    writesize_shift;

    unsigned
    int
    erasesize_mask;

    unsigned
    int
    writesize_mask;


    const
    char
    *
    name;

    int
    index;


    struct
    nand_ecclayout *
    ecclayout;
    //ECC布局结构


    int
    numeraseregions;
    //擦除区域的个数,通常为1

    struct
    mtd_erase_region_info *
    eraseregions;

    //擦除区域的指针


    //此方法将一个erase_info结构放入擦除队列中

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


    //point和unpoint方法分别用于允许和禁止芯片内执行(eXecute-In-Place,简称XIP),如果unpoint为NULL,则表示禁止XIP

    int
    (
    *
    point)
    (
    struct
    mtd_info *
    mtd,
    loff_t from,
    size_t
    len,
    size_t
    *
    retlen,
    void
    *
    *
    virt,
    resource_size_t *
    phys)
    ;

    void
    (
    *
    unpoint)
    (
    struct
    mtd_info *
    mtd,
    loff_t from,
    size_t
    len)
    ;


    //如果不为NULL,则表示允许无MMU单元的虚拟地址映射

    unsigned
    long
    (
    *
    get_unmapped_area)
    (
    struct
    mtd_info *
    mtd,
    unsigned
    long
    len,
    unsigned
    long
    offset,
    unsigned
    long
    flags)
    ;

    struct
    backing_dev_info *
    backing_dev_info;


    //read和write分别用于MTD设备的读和写

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


    //read_oob和write_oob分别用于读写MTD设备的OOB数据

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


    //一下几个方法是用于实现访问一些受保护的寄存器(一般这只是出现在某些特定的Flash设备上)

    int
    (
    *
    get_fact_prot_info)
    (
    struct
    mtd_info *
    mtd,
    struct
    otp_info *
    buf,
    size_t
    len)
    ;

    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,
    struct
    otp_info *
    buf,
    size_t
    len)
    ;

    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 from,
    size_t
    len,
    size_t
    *
    retlen,
    u_char *
    buf)
    ;

    int
    (
    *
    lock_user_prot_reg)
    (
    struct
    mtd_info *
    mtd,
    loff_t from,
    size_t
    len)
    ;


    //基于kvec的形式写

    int
    (
    *
    writev)
    (
    struct
    mtd_info *
    mtd,
    const
    struct
    kvec *
    vecs,
    unsigned
    long
    count
    ,
    loff_t to,
    size_t
    *
    retlen)
    ;


    //实现MTD设备的同步操作

    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
    (
    *
    suspend)
    (
    struct
    mtd_info *
    mtd)
    ;

    void
    (
    *
    resume)
    (
    struct
    mtd_info *
    mtd)
    ;


    //坏块管理功能

    int
    (
    *
    block_isbad)
    (
    struct
    mtd_info *
    mtd,
    loff_t ofs)
    ;

    int
    (
    *
    block_markbad)
    (
    struct
    mtd_info *
    mtd,
    loff_t ofs)
    ;


    //默认重启的MTD设备工作模式

    struct
    notifier_block reboot_notifier;


    //用于记录ECC状态的信息

    struct
    mtd_ecc_stats ecc_stats;


    /* Subpage shift (NAND) */

    int
    subpage_sft;


    //私有数据,注意是void类型的指针

    void
    *
    priv;


    struct
    module *
    owner;

    struct
    device dev;

    int
    usecount;
    //记录用户的个数


    //这两个方法用于设备驱动的回调,可以根据具体需要来决定是否实现他们

    int
    (
    *
    get_device)
    (
    struct
    mtd_info *
    mtd)
    ;

    void
    (
    *
    put_device)
    (
    struct
    mtd_info *
    mtd)
    ;

    }
    ;









    struct
    mtd_part
    {

    struct
    mtd_info mtd;
    //本分区信息(会被加入到mtd_table中,其大部分成员由其master决定)

    struct
    mtd_info *
    master;
    //该分区的主分区(不作为一个mtd_info加入到mtd_table。这也解释了上面的一个比喻,1个原始设备上有3个分区,最后将只有3个mtd_info加入到mtd_table而不是4个)

    uint64_t
    offset;
    //该分区的偏移地址

    int
    index;
    //该分区号

    struct
    list_head list
    ;

    int
    registered;

    }
    ;








三、Linux中的Nor Flash驱动



未完,待续。。。。。。