1、参考自带的nand flash驱动,位于drivers/mtd/nand/s3c2410.c中

1.1 为什么nand在mtd目录下?

因为mtd(memory technology device 存储 技术设备 ) 是用于访问 memory 设备( ROM 、 flash )的Linux 的子系统。 MTD 的主要目的是为了使新的 memory 设备的驱动更加简单,为此它在硬件和上层之间提供了一个抽象的接口。

1.2首先来看s3c2410.c的入口函数:

static int __init s3c2410_nand_init(void)
{printk("S3C24XX NAND Driver, (c) 2004 Simtec Electronics\n");platform_driver_register(&s3c2412_nand_driver);     platform_driver_register(&s3c2440_nand_driver);     return platform_driver_register(&s3c2410_nand_driver);
}

在入口函数中,注册了一个platform平台设备驱动,也是说当与nandflash设备匹配时,就会调用s3c2440_nand_driver ->probe来初始化。

我们进入probe函数中,看看是如何初始化:

static int s3c24xx_nand_probe(struct platform_device *pdev, enum s3c_cpu_type cpu_type)
{
... ...err = s3c2410_nand_inithw(info, pdev);       //初始化硬件hardware,设置TACLS 、TWRPH0、TWRPH1通信时序等s3c2410_nand_init_chip(info, nmtd, sets);    //初始化芯片nmtd->scan_res = nand_scan(&nmtd->mtd, (sets) ? sets->nr_chips : 1); //3.扫描nandflash
... ...
s3c2410_nand_add_partition(info, nmtd, sets);         //4.调用add_mtd_partitions()来添加mtd分区
... ...
}

通过上面代码和注释,得出:驱动主要调用内核的nand_scan()函数,add_mtd_partitions()函数,来完成注册nand flash。

2、上面probe()里的 nand_scan()扫描函数 位于/drivers/mtd/nand/nand_base.c 

它会调用nand_scan()->nand_scan_ident()->nand_get_flash_type()来获取flash存储器的类型。

以及nand_scan()->nand_scan_ident()->nand_scan_tail()来构造mtd设备的成员(实现对nandflash的读,写,擦除等)。

2.1 其中nand_get_flash_type()函数如下所示:

static struct nand_flash_dev *nand_get_flash_type(struct mtd_info *mtd,struct nand_chip *chip,int busw, int *maf_id)
{struct nand_flash_dev *type = NULL;int i, dev_id, maf_idx;chip->select_chip(mtd, 0);     //调用nand_chip结构体的成员select_chip使能flash片选chip->cmdfunc(mtd, NAND_CMD_READID, 0x00, -1); //3.2调用nand_chip结构体的成员cmdfunc,发送读id命令,最后数据保存在mtd结构体里 *maf_id = chip->read_byte(mtd); // 获取厂家ID,dev_id = chip->read_byte(mtd);   //获取设备ID/* 3.3for循环匹配nand_flash_ids[]数组,找到对应的nandflash信息*/for (i = 0; nand_flash_ids[i].name != NULL; i++)    {  if (dev_id == nand_flash_ids[i].id)     //匹配设备ID         {type =  &nand_flash_ids[i];break;}  }... .../* 3.4 匹配成功,便打印nandflash参数   */
printk(KERN_INFO "NAND device: Manufacturer ID:"" 0x%02x, Chip ID: 0x%02x (%s %s)\n", *maf_id,dev_id, nand_manuf_ids[maf_idx].name, mtd->name);  ... ...
}

从上面代码和注释得出, nand_chip结构体就是保存与硬件相关的函数(后面会讲这个结构体)。

2.2 NAND_CMD_READID定义为0x90,也就是发送0X90命令,和0x00地址来读id,最后放到mtd中

2.3 nand_flash_ids[]数组是个全局变量,通过匹配设备ID,来确定我们的nand flash是多大存储

如下图所示,在芯片手册中,看到nand flash的设备ID=0XDA

所以就匹配到nand_flash_ids[]里的0XDA:

2.4 然后打印出nand flash参数,我们启动内核就可以看到:

3、probe()里的s3c2410_nand_add_partition()函数主要是注册mtd设备的nand flash

最终它调用了s3c2410_nand_add_partition()->add_mtd_partitions() -> add_mtd_device()

其中add_mtd_partitions()函数主要实现多个分区创建,也就是多次调用add_mtd_device()

当只设置nand_flash为一个分区时,就直接调用add_mtd_device()即可。

4、add_mtd_partitions()函数原型如下:

int add_mtd_partitions(struct mtd_info *master, const struct mtd_partition *parts,int nbparts);  //创建多个分区mtd设备
//函 数 成 员 介 绍 : //master:就是要创建的mtd设备
//parts:分区信息的数组,它的结构体是mtd_partition,该结构体如下所示:
/*
struct mtd_partition {char *name;                  //分区名,比如bootloader、params、kernel、rootu_int32_t size;               //分区大小u_int32_t offset;            //分区所在的偏移值u_int32_t mask_flags;            //掩码标志struct nand_ecclayout *ecclayout; //OOB布局struct mtd_info **mtdp;              //MTD的指针,不常用
};
*///nbparts:等于分区信息的数组个数,表示要创建分区的个数

比如我们启动内核时,也能找到内核自带的nandflash的分区信息:

 3.2 其中add_mtd_device()函数如下所示:

int add_mtd_device(struct mtd_info *mtd)    //创建一个mtd设备
{struct list_head *this;... ...list_for_each(this, &mtd_notifiers)     //4.3找mtd_notifiers链表里的list_head结构体  {struct mtd_notifier *not = list_entry(this, struct mtd_notifier, list); //通过list_head找到struct mtd_notifier *notnot->add(mtd);            //最后调用mtd_notifier 的add()函数}... ...
}

3.3 我们搜索上面函数里的mtd_notifiers链表

看看里面的list_head结构体,在哪里放入的,就能找到执行的add()是什么了。

3.4 如下图,发现list_head在register_mtd_user()里放到mtd_notifiers链表中:

3.5 继续搜索register_mtd_user(),被哪个调用:

如上图,找到被drivers/mtd/mtdchar.cdrivers/mtd/mtd_blkdevs.c调用(3.6节和3.7节会分析)

因为mtd层既提供了字符设备的操作接口(mtdchar.c), 也实现了块设备的操作接口(mtd_blkdevs.c)

我们在控制台输入ls -l /dev/mtd*,也能找到块MTD设备节点和字符MTD设备节点,如下图所示:

上图中,可以看到共创了4个分区的设备,每个分区都包含了两个字符设备(mtd%d,mtd%dro)、一个块设备(mtdblock0).其中MTD的块设备的主设备号为31,MTD的字符设备的主设备号为90 (后面会讲到在哪被创建)。

3.6 我们进入上面搜到的drivers/mtd/mtdchar.c, 找到它的入口函数是init_mtdchar():

static void mtd_notify_add(struct mtd_info* mtd)
{class_device_create(mtd_class, NULL, MKDEV(MTD_CHAR_MAJOR, mtd->index*2),NULL, "mtd%d", mtd->index);class_device_create(mtd_class, NULL,MKDEV(MTD_CHAR_MAJOR, mtd->index*2+1),NULL, "mtd%dro", mtd->index);
}static struct mtd_notifier notifier = {.add   = mtd_notify_add,.remove   = mtd_notify_remove,
};static int __init init_mtdchar(void)
{/*创建字符设备mtd,主设备号为90 ,cat /proc/devices 可以看到 */if (register_chrdev(MTD_CHAR_MAJOR, "mtd", &mtd_fops)) {printk(KERN_NOTICE "Can't allocate major number %d for Memory Technology Devices.\n",MTD_CHAR_MAJOR);return -EAGAIN;}mtd_class = class_create(THIS_MODULE, "mtd");  //创建类if (IS_ERR(mtd_class)) {printk(KERN_ERR "Error creating mtd class.\n");unregister_chrdev(MTD_CHAR_MAJOR, "mtd");return PTR_ERR(mtd_class);}register_mtd_user(&notifier); //调用register_mtd_user(),将notifier添加mtd_notifiers链表中return 0;
}

所以在mtdchar.c这个分支,not->add(mtd) 即 :mtd_notify_add(mtd)

该函数创建了两个字符设备(mtd%d, mtd%dro ),其中ro的字符设备表示为只读

总结出:

mtdchar.c的入口函数 将notifie添加到mtd_notifiers链表中,

然后在add_mtd_device()函数中当查找到mtd字符设备的list_head时,就调用mtd_notifiers->add()来创建两个字符设备(mtd%d,mtd%dro)

3.7 同样,我们也进入mtd_blkdevs.c (MTD块设备)中,找到注册到mtd_notifiers链表的是blktrans_notifier变量:

 3.7.1 然后进入blktrans_notifier变量的blktrans_notify_add ()函数:

static void blktrans_notify_add(struct mtd_info *mtd)
{struct list_head *this;if (mtd->type == MTD_ABSENT)return;list_for_each(this, &blktrans_majors) //找blktrans_majors链表里的list_head结构体{struct mtd_blktrans_ops *tr = list_entry(this, struct mtd_blktrans_ops, list);tr->add_mtd(tr, mtd);    // 执行mtd_blktrans_ops结构体的add_mtd()}
}

从上面的代码和注释得出:块设备的add()是查找blktrans_majors链表,然后执行mtd_blktrans_ops结构体的add_mtd()

3.7.2 我们搜索blktrans_majors链表,看看mtd_blktrans_ops结构体在哪里添加进去的

找到该链表在register_mtd_blktrans()函数中:

int register_mtd_blktrans(struct mtd_blktrans_ops *tr)
{... ...
ret = register_blkdev(tr->major, tr->name);              //注册块设备
tr->blkcore_priv->rq=blk_init_queue(mtd_blktrans_request, &tr->blkcore_priv->queue_lock);//分配一个请求队列
... ...list_add(&tr->list, &blktrans_majors);                //将tr->list 添加到blktrans_majors链表
}

继续搜索register_mtd_blktrans(),如下图,找到被drivers/mtd/Mtdblock.c、Mtdblock_ro.c调用

3.7.3 我们进入drivers/mtd/Mtdblock.c函数中,如下图所示: 

找到执行mtd_blktrans_ops结构体的add_mtd()函数,就是上图的mtdblock_add_mtd()函数

在mtdblock_add_mtd()函数中最终会调用add_mtd_blktrans_dev()

3.7.4 add_mtd_blktrans_dev()函数如下所示:

int add_mtd_blktrans_dev(struct mtd_blktrans_dev *new)
{... ...gd = alloc_disk(1 << tr->part_bits);                  //分配一个gendisk结构体gd->major = tr->major;                                //设置gendisk的主设备号gd->first_minor = (new->devnum) << tr->part_bits;     //设置gendisk的起始此设备号gd->fops = &mtd_blktrans_ops;                         //设置操作函数... ...        gd->queue = tr->blkcore_priv->rq;                     //设置请求队列add_disk(gd);                                         //向内核注册gendisk结构体
}void add_disk(struct gendisk *disk)
{disk->flags |= GENHD_FL_UP;blk_register_region(MKDEV(disk->major, disk->first_minor),disk->minors, NULL, exact_match, exact_lock, disk);register_disk(disk);blk_register_queue(disk);
}

总结出:

mtd_blkdevs()块设备的入口函数将blktrans_notifier添加到mtd_notifiers链表中

然后在add_mtd_device()函数中,当查找到有blktrans_notifier时,就调用blktrans_notifier->add()来分配设置注册gendisk结构体,并创建块设备,请求队列。

从这里我们也可以知道,mtd比gendisk更高一级;我们知道一个块设备需要用一个gendisk 结构体来描述,一章用ram来模拟块设备,就是直接分配了一个gendisk,然后设置注册他;这里我们也就理解了本文第一句话:MTD 的主要目的是为了使新的 memory 设备的驱动更加简单,它在硬件和上层之间提供了一个抽象的接口。

4、显然在内核中,mtd已经帮我们做了整个框架,而我们的nand flash驱动只需要以下几步即可:

1)设置mtd_info结构体成员

2)设置nand_chip结构体成员

3)设置硬件相关(设置nand控制器时序等)

4)通过nand_scan()来扫描nandflash

5)通过add_mtd_partitions()来添加分区(一个分区对应一个块设备gendisk),创建MTD字符/块设备

4.1 mtd_info结构体介绍:

主要是实现对nand flash的read()、write()、read_oob()、write_oob()、erase()等操作,属于软件的部分,它会通过它的成员priv来找到对应的nand_chip结构体,来调用与硬件相关的操作.

4.2 nand_chip结构体介绍:

它是mtd_info结构体的priv成员,主要是对MTD设备中的nandflash硬件相关的描述.

当我们不设置nand_chip的成员时,以下的成员就会被mtd自动设为默认值,代码位于: nand_scan()->nand_scan_ident()->nand_set_defaults()。

if (!chip->select_chip)
chip->select_chip = nand_select_chip; // 默认值不适用if (chip->cmdfunc == NULL) cmdfunc名字上知是发命令的函数。若为空就给一个默认的nand_command/
chip->cmdfunc = nand_command;
-->chip->cmd_ctrl(mtd, command, ctrl);if (!chip->read_byte)
chip->read_byte = nand_read_byte;
-->readb(chip->IO_ADDR_R);if (chip->waitfunc == NULL)
chip->waitfunc = nand_wait;
-->chip->dev_ready

显然。默认值有些不适合我们,我们应该自己实现。

第19课:nand flash驱动相关推荐

  1. nand flash驱动调试学习

    nand flash基础知识 nand flash上为什么只有地址线.如何传输数据和命令: 地址,数据,命令是复用的.使用ALE,CLE来进行区分的 通过RnB引脚来区分数据操作是否完成 nand f ...

  2. nand flash 经典 全面 ------如何编写Linux下Nand Flash驱动

    Crifan Li 摘要 本文先解释了Nand Flash相关的一些名词,再从Flash硬件机制开始,介绍到Nand Flash的常见的物理特性,且深入介绍了Nand Flash的一些高级功能,然后开 ...

  3. 15.NAND FLASH驱动

    NAND FLASH 原理以及操作详见:https://blog.csdn.net/qq_16933601/article/details/100001443 一.基本的问题 NAND FLASH是一 ...

  4. linux用户空间flash驱动,全面掌握Linux驱动框架——字符设备驱动、I2C驱动、总线设备驱动、NAND FLASH驱动...

    原标题:全面掌握Linux驱动框架--字符设备驱动.I2C驱动.总线设备驱动.NAND FLASH驱动 字符设备驱动 哈~ 这几天都在发图,通过这种方式,我们希望能帮大家梳理学过的知识,全局的掌握Li ...

  5. linux下nand flash驱动工作原理,Linux下Nand Flash 驱动代码分析

    随着越来越多的平台支持从Nand Flash 中启动,掌握Nand Flash 的驱动编写有着重要的现实意义,由于内核已经完成了大部分的工作,实际工作中大部分工程师对Nand Flash 驱动只是简单 ...

  6. Nand flash驱动的编写与移植

    1 Nand flash工作原理     S3C2410板的Nand Flash支持由两部分组成:Nand Flash控制器(集成在S3C2410 CPU)和Nand Flash存储 芯片(K9F12 ...

  7. linux驱动编写(nand flash驱动)

    [ 声明:版权所有,欢迎转载,请勿用于商业用途. 联系信箱:feixiaoxing @163.com] 很长一段时间,nand flash都是嵌入式的标配产品.nand flash价格便宜,存储量大, ...

  8. linux下nand flash驱动工作原理,1.3.4. Nand flash驱动工作原理

    1.3.4. Nand flash驱动工作原理 在介绍具体如何写Nand Flash驱动之前,我们先要了解,大概的整个系统,和Nand Flash相关的部分的驱动工作流程,这样,对于后面的驱动实现,才 ...

  9. linux nand 驱动,Linux NAND FLASH驱动分析(一)

    最近一直在忙着工作上的事情,好久都没有更新博客了,发现最近思想是比较混乱的.学任何东西都坚持不下去,既然选择驱动开发这条路就要坚持下去. 之前分析了Linux块设备驱动,是以内存块来模拟的虚拟块设备. ...

最新文章

  1. 深度估计相关原理(计算机视觉和深度学习基础)
  2. RDKit | 基于支持向量机(SVM)的二分类活性预测模型
  3. 第章量子计算机产业,又一个世界第一,九章量子计算机诞生,中国战斗机智能空战不是梦...
  4. flink sql udf jar包_flink教程flink 1.11 集成zeppelin实现简易实时计算平台
  5. zookeeper服务发现实战及原理--spring-cloud-zookeeper源码分析
  6. vs2010利用属性表自动配置OpenCV(XP的32位系统,opencv版本是2.4.10)
  7. tornado.httpclient.HTTPClient()的用法
  8. oracle修改成olap模式,的Oracle OLAP Java实现 - 正确源加入
  9. poj 2439 ArcticNetwork 最小生成树Kruskal、(Prim方法还没做
  10. AcWing 1750. 救生员(差分+暴力枚举)
  11. 逐像元地表反射率计算(GF4)
  12. 计算机常用的采样频率,采样频率
  13. Spring Boot入门教程(零): yaml使用详解
  14. mac去除视频水印用什么软件?
  15. [Paper Reading]开始写Paper Reading Report
  16. STP和RSTP的BPDU报文中flag位 对比+分析
  17. 项目4-分数类和整型数的四则运算
  18. CSDN博文周刊第一期 | 2018年总结:向死而生,为爱而活——忆编程青椒的戎马岁月
  19. create-react-app配置总结
  20. 成功解决Qt中ui_xxx.h: no such file or directory”

热门文章

  1. 京东商品详情API调用实例讲解
  2. 【阶段总结】大三上学期总结
  3. 【英语】三月全英环境
  4. CTF BugKu平台——Crypto篇刷题记录(后续更新)
  5. 为什么我在抖音上发的视频第一次不火,重新发一次就火了?
  6. Ubuntu搜狗输入法美化与修复
  7. 知乎出现了“串号”事件,这给所有的“阴暗匿名者”敲响了警钟
  8. ×××灰产故事(下)
  9. 有没有简单好用的换天空背景软件推荐?
  10. 焊锡丝的全球与中国市场2022-2028年:技术、参与者、趋势、市场规模及占有率研究报告