zoukankan      html  css  js  c++  java
  • NOR Flash驱动程序

    前面学习了Linux内核中Nand Flash驱动程序的编写,现在继续学习如何编写NOR Flash驱动程序,在编写自己的NOR Flash驱动前,我们还是先来弄清楚NOR Flash驱动程序编写的框架。

    我们知道在MTD系统层次下,Flash硬件驱动通过mtd_info结构体和上层的设备层进行交互,mtd_info结构体是一个高度抽象的接口,向上层提供了对存储设备的_read、_erase、_write等操作函数,这些接口屏蔽了硬件的差异,使得设备层不需要了解硬件特性直接使用mtd_info结构体中函数完成数据传递。这意味着mtd_info结构体中的成员需要在驱动程序中构造和向上注册,如在Nand Flash驱动中内核为它抽象出了nand_chip结构体,驱动程序编写者只需实现不同硬件间差异的一些内容,所有NAND Flash共性的读写的协议部分已经由内核实现。驱动程序编写者只需调用nand_scan函数扫描硬件信息,完成mtd_info结构体成员填充。那么NOR Flash驱动中是否也实现了一个像nand_chip的结构体呢?下面我们猜想和疑问进行分析。


    先从内核中自带的NOR Flash驱动程序physmap.c函数进行分析

    入口函数physmap_init

    static int __init physmap_init(void)
    {
        int err;
    
        err = platform_driver_register(&physmap_flash_driver);--------------------->①
    #ifdef CONFIG_MTD_PHYSMAP_COMPAT
        if (err == 0) {
            err = platform_device_register(&physmap_flash);------------------------>②
            if (err)
                platform_driver_unregister(&physmap_flash_driver);
        }
    #endif
    
        return err;
    }

    ① 注册了一个平台相关的driver,注册的平台相关的driver在注册时会自动搜索平台总线下挂接的所有device,查找是否有同名的device,如果有调用driver的probe函数

    ② 如果定义了CONFIG_MTD_PHYSMAP_COMPAT宏,注册一个平台相关的device。同注册的平台总线的driver一样,注册的平台相关的device在注册时会自动搜索平台总线下挂接的所有driver,找到是否有同名的driver,如果有调用driver的probe函数。

    CONFIG_MTD_PHYSMAP_COMPAT这些宏是在内核配置时设置的,如make menuconfig按照下面步骤进行设置

    Device Drivers  --->
        <*> Memory Technology Device (MTD) support  ---> 
            Mapping drivers for chip access  ---> 
                <M> Flash device in physical memory map 
                [*]   Physmap compat support  
                (0x0)   Physical start address of flash mapping----------------------->①
                (0x10000000) Physical length of flash mapping------------------------->②
                (2)     Bank width in octets------------------------------------------>③

    如果选中了Physmap compat support,还需要设置它下面的三项内容,分别是flash映射的物理起始地址、映射的长度、以及位宽。这些配置信息最终在编译内核时自动生成宏定义,存放在autoconfig.h文件中,这个文件将被内核中的其他源文件包含。

    继续看注册的平台相关的driver结构体的probe函数

    physmap_flash_probe

    static int physmap_flash_probe(struct platform_device *dev)
    {
        struct physmap_flash_data *physmap_data;
        struct physmap_flash_info *info;
        const char **probe_type;
        const char **part_types;
        ...
        physmap_data = dev->dev.platform_data;
        ...
        info = devm_kzalloc(&dev->dev, sizeof(struct physmap_flash_info), ------------------------>①
                    GFP_KERNEL);
        ...
        for (i = 0; i < dev->num_resources; i++) {
            ...
            if (!devm_request_mem_region(&dev->dev,----------------------------------------------->②
                dev->resource[i].start,
                resource_size(&dev->resource[i]),
                dev_name(&dev->dev))) 
            ...
            info->map[i].name = dev_name(&dev->dev);---------------------------------------------->③
            info->map[i].phys = dev->resource[i].start;
            info->map[i].size = resource_size(&dev->resource[i]);
            info->map[i].bankwidth = physmap_data->width;
            ...
            info->map[i].virt = devm_ioremap(&dev->dev, info->map[i].phys,
                             info->map[i].size);
            ...
            simple_map_init(&info->map[i]);------------------------------------------------------>④
            probe_type = rom_probe_types;
            if (physmap_data->probe_type == NULL) {
                for (; info->mtd[i] == NULL && *probe_type != NULL; probe_type++)
                    info->mtd[i] = do_map_probe(*probe_type, &info->map[i]);--------------------->⑤
            } else
                info->mtd[i] = do_map_probe(physmap_data->probe_type, &info->map[i]);
            ...
            info->mtd[i]->owner = THIS_MODULE;
            info->mtd[i]->dev.parent = &dev->dev;
        }
    
        if (devices_found == 1) {
            info->cmtd = info->mtd[0];
        } else if (devices_found > 1) {
            /*
             * We detected multiple devices. Concatenate them together.
             */
            info->cmtd = mtd_concat_create(info->mtd, devices_found, dev_name(&dev->dev));------>⑥
            if (info->cmtd == NULL)
                err = -ENXIO;
        }
        ...
        part_types = physmap_data->part_probe_types ? : part_probe_types;
    
        mtd_device_parse_register(info->cmtd, part_types, NULL,---------------------------------->⑦
                      physmap_data->parts, physmap_data->nr_parts);
        ...
    }

    ① 分配一个struct physmap_flash_info结构体,这个结构体包含了mtd_info结构体指针和map_info结构体数据成员。关于mtd_info结构体前面已经接触过了,map_info结构体是新引入的结构体,可以猜想它的功能可能类似Nand Flash驱动中nand_chip结构体,用于描述NOR Flash设备

    ② devm_request_mem_region获取平台总线匹配到的platform_device的信息,platform_device中存放的一般是硬件相关信息。该平台总线注册的driver匹配到的platform_device结构体也是在physmap.c文件中定义,内容如下:

    static struct physmap_flash_data physmap_flash_data = {
        .width        = CONFIG_MTD_PHYSMAP_BANKWIDTH,
    };
    
    static struct resource physmap_flash_resource = {
        .start        = CONFIG_MTD_PHYSMAP_START,
        .end        = CONFIG_MTD_PHYSMAP_START + CONFIG_MTD_PHYSMAP_LEN - 1,
        .flags        = IORESOURCE_MEM,
    };
    
    static struct platform_device physmap_flash = {
        .name        = "physmap-flash",
        .id        = 0,
        .dev        = {
            .platform_data    = &physmap_flash_data,
        },
        .num_resources    = 1,
        .resource    = &physmap_flash_resource,
    };

    physmap_flash该结构体描述了硬件相关的资源信息

    .num_resources = 1代表只有一个硬件资源

    CONFIG_MTD_PHYSMAP_BANKWIDTH、CONFIG_MTD_PHYSMAP_START、CONFIG_MTD_PHYSMAP_LEN这些宏的值都是在只需make menuconfig配置内核时设置的,按照前面的配置,这些宏的信息值如下所示:

    CONFIG_MTD_PHYSMAP_BANKWIDTH = 2
    CONFIG_MTD_PHYSMAP_START = 0
    CONFIG_MTD_PHYSMAP_LEN = 0x10000000

    ③ 根据②获取的描述硬件的信息设置map_info结构体,最后根据物理值范围大小,映射对应的虚拟地址传给map_info结构体的virt成员变量

    ④ 调用simple_map_init函数,顾名思义,该函数完成map_info结构体简单的初始化,设置默认的读、写、拷贝等函数

    ⑤ 如果匹配platform_device中没有指定probe_type信息,遍历probe_type指针指向内容中的所有参数,调用do_map_probe函数,如果do_map_probe函数返回的内容不为空(即匹配成功),结束后面查找

    probe_type是一个二级指针,指向指针数组rom_probe_types,这个数组指针每一项指向一个字符串

    static const char *rom_probe_types[] = { "cfi_probe", "jedec_probe", NULL };

    do_probe_type是一个比较核心的函数,根据传入的字符串名称在链表中找到相关的probe函数,如cif_probe将尝试以CFI规范去解析nor flash,如果nor flash支持cfi协议,将解析成功,读取nor flash信息,分配、设置mtd_info结构体

    ⑥ 如果检测到多个flash设备,将他们连接到一起,设置统一的mtd_info结构体

    ⑦ 调用mtd_device_parse_register函数,设置分区范围、注册mtd_info结构体,尝试以指定文件系统类型去解析flash分区

    通过上面对physmap_flash_probe分析,我们基本可以知道了编写一个nor flash驱动的步骤,do_map_probe函数是其中的关键步骤。do_map_probe函数根据传入的参数去调用相关规范的probe函数,判断该flash支持那种规范去访问。我们下面详细分析do_map_probe函数

    do_map_probe函数

    struct mtd_info *do_map_probe(const char *name, struct map_info *map)
    {
        struct mtd_chip_driver *drv;
        struct mtd_info *ret;
    
        drv = get_mtd_chip_driver(name);---------------->①
        ...
        ret = drv->probe(map);-------------------------->②
            ...  
    }

    ① 根据传入名称找到一个mtd_chip_driver函数

    ② 调用找到的mtd_chip_driver结构体的probe函数

    get_mtd_chip_driver函数是如何寻找的呢?继续看该函数

    static struct mtd_chip_driver *get_mtd_chip_driver (const char *name)
    {
        .........
    
        list_for_each(pos, &chip_drvs_list) {
            this = list_entry(pos, typeof(*this), list);
    
            if (!strcmp(this->name, name)) {
                ret = this;
                break;
            }
        }
        ..........
    }

    get_mtd_chip_driver函数遍历chip_drv_list链表,找到链表中挂载的所有内容,如果和传入名称匹配,将返回匹配成功链表对象的地址。

    chip_drv_list链表在哪里被设置呢?在内核源码树中查找,找到了往链表中注册成员的register_mtd_chip_driver函数。该函数又被很多驱动的入口函数调用,如cfi_probe.c/jedec_probe.c/map_ram.c/map_rom.c等文件的入口函数,在以cfi_probe.c文件入口函数为例进行分析

    static struct mtd_chip_driver cfi_chipdrv = {
        .probe        = cfi_probe,
        .name        = "cfi_probe",
        .module        = THIS_MODULE
    };
    
    static int __init cfi_probe_init(void)
    {
        register_mtd_chip_driver(&cfi_chipdrv);
        return 0;
    }

    调用register_mtd_chip_driver函数,注册了一个名为cif_chipdrv的mtd_chip_driver结构体,该结构体name成员名为“cfi_probe”,probe指针指向cif_probe函数。

    由此可见,在对于nor_flash这种类内存类型接口的存储设备进行访问时,驱动程序会调用do_map_probe函数,使用内核中支持的mtd_chip_driver的probe函数去检测硬件的类型。如果内核中注册的mtd_chip_driver支持该芯片的访问,将填充mtd_info结构体,设置mtd_info结构体的设备读写函数。同时,我们可以使用内核中注册的mtd_chip_driver,去尝试匹配一个新的类内存接口的flash设备,可以仿照内核physmap.c文件,将mtd_chip_driver名称放在一个指针数组中,使用时遍历指针数组中每一项内容进行匹配,直到匹配完成返回。


    下面我们将简化上述驱动,不使用make menuconfig配置传入相关参数,编写一个更简练的nor flash驱动程序,源码实现如下:

    #include <linux/module.h>
    #include <linux/types.h>
    #include <linux/kernel.h>
    #include <linux/init.h>
    #include <linux/slab.h>
    #include <linux/device.h>
    #include <linux/platform_device.h>
    #include <linux/mtd/mtd.h>
    #include <linux/mtd/map.h>
    #include <linux/mtd/partitions.h>
    #include <linux/mtd/physmap.h>
    #include <linux/mtd/concat.h>
    #include <linux/io.h>
    
    struct mtd_info *mtd = NULL;
    struct map_info *map;
    
    
    static const char *rom_probe_types[] = { "cfi_probe", "jedec_probe", NULL };
    static const char *part_probe_types[] = { "cmdlinepart", "RedBoot", NULL };
    
    static struct mtd_partition nor_partitions[] = {
           [0] = {
            .name   = "bootloader",
            .size   = 0x00040000,
            .offset     = 0,
           },
           [1] = {
            .name   = "root",
            .offset = MTDPART_OFS_APPEND,
            .size   = MTDPART_SIZ_FULL,
           }
    };
    
    
    int s3c_nor_drv_init(void)
    {
        const char **probe_type;
        const char **part_types;
    
        map = kzalloc(sizeof(struct map_info), GFP_KERNEL);
    
        map->name = "nor_flash";
        map->phys = 0;
        map->size = 0x1000000;
        map->bankwidth = 2;
        //map->set_vpp = physmap_data->set_vpp;
    
        map->virt = ioremap(map->phys, map->size);
    
        simple_map_init(map);
    
        probe_type = rom_probe_types;
        for (; mtd == NULL && *probe_type != NULL; probe_type++)
            mtd = do_map_probe(*probe_type, map);
    
        mtd->owner = THIS_MODULE;
    
        part_types = part_probe_types;
        mtd_device_parse_register(mtd, part_types, NULL, nor_partitions, ARRAY_SIZE(nor_partitions)); 
    
        return 0;
    }
    
    void s3c_nor_drv_exit(void)
    {
        mtd_device_unregister(mtd);
        map_destroy(mtd);
    
        kfree(map);
    }
    
    module_init(s3c_nor_drv_init);
    module_exit(s3c_nor_drv_exit);
    
    MODULE_LICENSE("GPL"); 

    测试驱动程序

    1)insmod s3c_nor_drv.c

    2) 执行./flash_eraseall -j /dev/mtd1格式化分区,注意格式化分区是以MTD的字符设备进行的。

    -j代表格式化为jffs2文件类型,可通过./flash_eraseall --help查看应用程序的帮助信息

    3)挂载分区到mnt目录, mount -t jffs2 /dev/mtdblok1 /mnt,使用命令进行读写测试,-t代表挂载文件系统的类型

    由测试结果可以看出我们编写的nor flash驱动程序可以正常运行,还有须注意的是s3c2440测试nor flash驱动程序时,启动方式一定要设置成nor flash驱动,如果是nand flash启动,芯片的0地址将映射到4K的Boot Internal SRAM区域,这样肯定不能访问到nor flash设备。

  • 相关阅读:
    ==和equals的区别
    Java标准库定义的常用异常
    java.lang.ArithmeticException: / by zero
    类的设计
    预处理&枚举&其他
    内存管理
    指针和字符串
    指针
    数组
    流程控制语句
  • 原文地址:https://www.cnblogs.com/053179hu/p/14017902.html
Copyright © 2011-2022 走看看