25.Linux-Nor Flash驱动(详解)

1.nor硬件介绍:

从原理图中我们能看到NOR FLASH有地址线,有数据线,它和我们的SDRAM接口相似,能直接读取数据,但是不能像SDRAM直接写入数据,需要有命令才行

1.1其中我们2440的地址线共有27根(LADDR0~26),为什么是27根?

因为2440共有7个bank内存块,每个bank=128MB=(2^27)B,所以共有27根数据线

1.2为什么Nor Flash的地址线A0是接在2440的LADDR1上?

因为Nor Flash的数据共有16位,也就是每个地址保存了2B数据,而我们的2440每个地址是保存的1B数据,比如:当2440访问0X00地址时,就会读取到Nor Flash上0地址的16位数据,然后2440的内存控制器会根据0x00来找到低8位字节,并返回给CPU

1.3 nand和nor区别:

nor flash在价格上比nand贵,且容量很小 ,擦除和写数据都慢,好处在于接口简单,稳定,无位反转,坏块,常用于保存关键数据,而nand flash常用于保存大容量数据

在2440中是通过硬件开关来设置OM0为Nand启动还是Nor启动,如下图所示:

OM0具体参数如下所示,其中2440的OM1引脚默认接地

对于nand启动:nand flash的开始4KB会自动地被加载到2440内置的SRAM缓存器中,就可以直接读写

对于nor启动:2440访问的内存就是nor flash,可以直接写,但是不能直接读

2.nor flash命令如下所示(参考MX29LV800BBTC.pdf)

 其中word是针对16位nand,byte针对8位nand

2.1 比如,当我们要program(往0x20地址写入0xff数据)时

需要以下几步:

往nor地址0x555写入0xAA

往nor地址0x2AA写入0x55

往nor地址0x555写入0xA0                //进入program模式

往nor地址0x20(PA)写入0xff(PD)          //往0x20写入0xff

(接下来就会一直是program模式,执行reset模式便可以退出)

2.2该NOR有两种规范, jedec, cfi(common flash interface) 

jedec

就是和nandflash的一样,通过读ID来匹配linux内核中drivers/mtd/chips/jedec_probe.c里的jedec_table[]数组,来确定norflash的各个参数(名称、容量、位宽等),如下图所示:

cfi

就是将这些参数保存在cfi模式下指定地址中, 往nor的0x55地址写入0x98,即可进入cfi模式,

cfi模式部分命令如下图所示:

当我们在cfi模式下,比如:读取nor地址0x27处的数据,便能读到nor的容量

如下图所示,之所以地址*2,是因为nor地址线A0接在我们2440的A1上

读到0X15,0x15=21,如下图,刚好对应我们原理图的21根nor地址线,所以容量为2^21=2MB

2.3为什么上图的A20引脚没有接?

对于2440来讲,因为此时的A0~A19的容量刚好为2MB,与cfi模式下读取的数据一致,所以没有接A20

3.接下来便来分析如何写norflash驱动

3.1 先来回忆下之前的nandflsh驱动:

nandflsh驱动会放在内核的mtd设备中,而mtd设备知道如何通过命令/地址/数据来操作nandflash,所以我们之前的nandflash驱动只实现了硬件相关的操作(构造mtd_info,nand_chip结构体、启动nand控制器等)

同样地,norflash驱动也是放在内核的mtd设备中,mtd设备也知道对nor如何来读写擦除,只是不知道norflash的位宽(数据线个数),基地址等,所以我们的norflash驱动同样要实现硬件相关的操作,供给mtd设备调用

3.2参考内核自带的nor驱动:drivers/mtd/maps/physmap.c

进入它的init函数:

发现注册了两个platform平台设备驱动,进入physmap_flash结构体中:

发现3个未定义的变量:

CONFIG_MTD_PHYSMAP_BANKWIDTH: nandflash的字节位宽

CONFIG_MTD_PHYSMAP_START:nandflash的物理基地址

CONFIG_MTD_PHYSMAP_LEN: nandflash的容量长度

这3个变量是通过linux的menuconfig菜单配置出来的,若自己填入值,就不需要用menuconfig菜单配置了

3.3接下来我们就来配置内核,然后挂载这个内核自带的norflash驱动实验一番

3.4 首先make menuconfig,配置上面3个变量,然后设为模块

-> Device Drivers                

 -> Memory Technology Device (MTD) support  

 -> Mapping drivers for chip access                   //进入映射驱动

<M> CFI Flash device in physical memory map          //将支持cfi的norflash设置为模块

  •    (0x0) Physical start address of flash mapping  // 设置物理基地址
  •    (0x1000000) Physical length of flash mapping  // 设置容量长度,必须大于等于自身nor的2MB
  •    (2)   Bank width in octets (NEW)                   // 设置字节位宽,因为nor为16位,所以等于2

3.5 make modules 编译模块

如下图所示,可以看到physmap.c编译成.ko模块了

3.6 然后放在nfs目录下,启动开发板

如下图所示,insmod后打印了一串信息:

如下图所示,可以看到创建了2个mtd0字符设备,一个mtd0块设备:

4.接下来我们便分析physmap.c,如何写出norflash驱动的

其中physmap.c的probe函数如下

struct physmap_flash_info {
       struct mtd_info             *mtd;             //实现对flash的读写擦除等操作
       struct map_info            map;              //存放硬件相关的结构体
       struct resource             *res;
#ifdef CONFIG_MTD_PARTITIONS
       int                 nr_parts;
       struct mtd_partition      *parts;
#endif
};

static const char *rom_probe_types[] = { "cfi_probe", "jedec_probe", "map_rom", NULL };    //芯片名称
... ...
static int physmap_flash_probe(struct platform_device *dev)
{
       const char **probe_type;
       ... ...
       /*1. 分配结构体*/
       info = kzalloc(sizeof(struct physmap_flash_info), GFP_KERNEL);

       /*2.设置map_info 结构体*/
    info->map.name = dev->dev.bus_id;                 //norflash的名字
       info->map.phys = dev->resource->start;          //物理基地址
       info->map.size = dev->resource->end - dev->resource->start + 1;       //容量长度
       info->map.bankwidth = physmap_data->width;                     //字节位宽
       info->map.virt = ioremap(info->map.phys, info->map.size);    //虚拟地址
       simple_map_init(&info->map);                   //简单初始化map_info的其它成员

       probe_type = rom_probe_types;
       /*3. 设置mtd_info 结构体 */
       /*通过probe_type指向的名称来识别芯片,当do_map_probe()函数返回NULL表示没找到*/
       /*当找到对应的芯片mtd_info结构体,便返回给当前的info->mtd */
       for (; info->mtd == NULL && *probe_type != NULL; probe_type++)       
       info->mtd = do_map_probe(*probe_type, &info->map); //通过do_map_probe ()来识别芯片
   

       if (info->mtd == NULL) {             //最终还是没找到芯片,便注销之前注册的东西并退出
              dev_err(&dev->dev, "map_probe failed\n");
              err = -ENXIO;
              goto err_out;
       }

       info->mtd->owner = THIS_MODULE;        

 

       /*4.添加mtd设备*/

       add_mtd_device(info->mtd);              

       return 0;

 

err_out:

       physmap_flash_remove(dev);                      //该函数用来注销之前注册的东西

       return err;

}

通过上面的代码和注释分析到,和我们上一节的nandflash驱动相似,这里是设置map_info 结构体和mtd_info结构体来完成的,当我们要对norflash分区就要使用add_mtd_partitions()才行

其中当*probe_type==“cfi_probe”时:

就会通过do_map_probe("cfi_probe", &info->map)来识别芯片.

最终会进入drivers/mtd/chips/cfi_probe.c中的cfi_probe_chip()函数来进入cfi模式,读取芯片信息

当*probe_type=="jedec_probe"时:

最终会进入drivers/mtd/chips/jedec_probe.c中的jedec_probe_chip ()函数来使用读ID命令,通过ID来匹配jedec_table[]数组.

所以注册一个块设备驱动,需要以下步骤:

  • 1. 分配mtd_info结构体和map_info结构体
  • 2. 设置map_info 结构体
  • 3. 设置mtd_info 结构体
  • 4. 使用add_mtd_partitions()或者add_mtd_device()来创建MTD字符/块 设备

5.接下来我们来参考physmap.c来自己写norflah驱动

代码如下:

#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 <asm/io.h>

 
static struct mtd_info        *mynor_mtd_info;
static struct map_info         *mynor_map_info;


static struct mtd_partition mynor_partitions[] = {
       [0] = {
        .name   = "bootloader",
        .size   = 0x00040000,
        .offset     = 0,
       },
       [1] = {
        .name   = "root",
        .offset = MTDPART_OFS_APPEND,
        .size   = MTDPART_SIZ_FULL,
       }
};
 
static const char *mynor_probe_types[] = { "cfi_probe", "jedec_probe",NULL};


static int mynor_init(void)
{
    int val;

/*1. 分配map_info 结构体和mtd_info结构体*/
    mynor_mtd_info=kzalloc(sizeof(struct mtd_info), GFP_KERNEL);
    mynor_map_info=kzalloc(sizeof(struct map_info), GFP_KERNEL);

/*2. 设置map_info 结构体*/
    mynor_map_info->name="my_nor";
    mynor_map_info->phys=0x0;                          //物理地址
    mynor_map_info->size=0x1000000;                 //=16M,长度必须大于等于norflash的2M容量
    mynor_map_info->bankwidth=2;                     //16位宽
    mynor_map_info->virt = ioremap(0x0, mynor_map_info->size);   //虚拟地址
    simple_map_init(mynor_map_info);

/*3. 设置mtd_info 结构体*/   mynor_mtd_info = do_map_probe("cfi_probe", mynor_map_info);   if (!mynor_mtd_info)   {     mynor_mtd_info = do_map_probe("jedec_probe", mynor_map_info);    }

    if (!mynor_mtd_info)     {     printk("not available norflash !!!\r\n");     goto err_out;     }     mynor_mtd_info->owner=THIS_MODULE;

/*4. 使用add_mtd_partitions()或者add_mtd_device()来创建MTD字符/块 设备*/
       add_mtd_partitions(mynor_mtd_info,mynor_partitions,2);
       return 0;

       
err_out:
       iounmap(mynor_map_info->virt);                      //取消虚拟地址映射
       kfree(mynor_map_info);
       kfree(mynor_mtd_info);      
       return 0;
}


static  void mynor_exit(void)
{
    del_mtd_partitions(mynor_mtd_info);                  //卸载分区
    iounmap(mynor_map_info->virt);                      //取消虚拟地址映射
    kfree(mynor_map_info);
    kfree(mynor_mtd_info);       
}

module_init(mynor_init);
module_exit(mynor_exit);
MODULE_LICENSE("GPL");

6.挂载驱动试验(一定要在nor启动下挂载才行)

insmod挂载驱动后,如下图所示:

可以看到创建了两个分区“bootloader”,“root”,如下图所示,可以看到创建了2对mtd字符/块设备

6.1 接下来便来对root分区(mtd1)来试验(使用flash之前最好擦除一次)

步骤如下:

./flash_eraseall -j /dev/mtd1                      //使用flash_eraseal来擦除root分区(mtd1)

mount -t jffs2 /dev/mtdblock1 /mnt/                //使用mount挂载文件系统, -t:文件系统类型(type)

接下来就可以在/mnt目录下来任意读写文件了,最终会保存在flash的mtdblock1块设备中

(PS:可以参考内核自带的mtdram.c,里面是使用内存来模拟flash, 里面通过memcopy()等来实现对内存读写擦除)

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏编程

我是如何利用CSRF Get DedeCms Shell的

说实话,有一两个月没有审计大厂了,然后随便看到群里有人问dede最新有没有漏洞,就下了一套最新的dede,结果我一看还真发现了。 ? 我们发现后台添加广告的地方...

2187
来自专栏ml

c/c++----网站及其后门(CGI应用程序)

      C/C++学习到这儿,结合自己曾经学过的javasweb知识,现在让我们来看看,如何做一个CGI程序吧!       首先了解一下啥子叫CGI  :...

3524
来自专栏FreeBuf

利用Winrm.vbs绕过白名单限制执行任意代码

winrm.vbs(一个位于system32目录下的具有Windows签名的脚本文件)可以被用来调用用户定义的XSL文件,从而导致任意的、没有签名的代码执行。当...

2254
来自专栏程序员叨叨叨

compileSdkVersion 'android-24' requires JDK 1.8 or later to compile

今天,好久没有写Android程序的我突发奇想,想简单写一个每日任务APP。好的!新建工程->写好代码框架->开启模拟器->运行!哎哎哎?!那啥!咋报错了嘞?!...

1234
来自专栏java思维导图

Java中高级面试题部分答案解析(4)

这里选了几道高频面试题以及一些解答。不一定全部正确,有一些是没有固定答案的,如果发现有错误的欢迎纠正,如果有更好的回答,热烈欢迎留言探讨。

1353
来自专栏james大数据架构

SQL SERVER 内存分配及常见内存问题 简介

一、问题: 1、SQL Server 所占用内存数量从启动以后就不断地增加:       首先,作为成熟的产品,内存溢出的机会微乎其微。对此要了解SQL SER...

35410
来自专栏杨建荣的学习笔记

oracle的TAF浅析 (r6笔记第37天)

在使用rac的时候,有几个很闪亮的使用特性,一个就是load balance,这块毋庸置疑,确实做了很大的改进,从10g版本开始的多个vip地址的load b...

4865
来自专栏有趣的django

Django REST framework+Vue 打造生鲜超市(十二) 十三、首页、商品数量、缓存和限速功能开发

十三、首页、商品数量、缓存和限速功能开发  13.1.轮播图接口实现 首先把pycharm环境改成本地的,vue中local_host也改成本地  (1)goo...

7217
来自专栏Java技术栈

75 道 BAJT 中高级 Java 面试题,你能答上几道?

整理了下 BATJ 关于 JAVA 的面试题目,大家参考下吧,希望对大家有所帮助,可以帮大家查漏不缺。

1012
来自专栏美团技术团队

分布式系统互斥性与幂等性问题的分析与解决

前言 随着互联网信息技术的飞速发展,数据量不断增大,业务逻辑也日趋复杂,对系统的高并发访问、海量数据处理的场景也越来越多。如何用较低成本实现系统的高可用、易伸缩...

5574

扫码关注云+社区

领取腾讯云代金券