第19课:nand flash驱动

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、root
       u_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 *not
   not->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

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

  • 1
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

内核分析笔记

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

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

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

打赏作者

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

抵扣说明:

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

余额充值