嵌入式Linux——nand flash驱动开发(二):框架介绍

2019-07-13 01:52发布

        在写这篇文章之前我想声明一下,由于我在写这篇文章之前看了一些写nand flash的文章。所以如果我的文章中出现您的文章中所有的东西,请您指出,我会细心改正或删除,而如果我的文章对你有帮助这是我的荣幸。         下面言归正传讲nand flash的框架,而好像所有的文章在介绍nand flash框架的时候总是离不开一张图:     下面我们就从这张图说起。     FLASH在嵌入式系统中是必不可少的,它是bootloader、linux内核和文件系统的最佳载体。在Linux内核中引入了MTD子系统为NOR FLASH和NAND FLASH设备提供统一的接口,从而使得FLASH驱动的设计大为简化。在引入MTD后Linux系统中FLASH设备驱动可分为四层: 1. 硬件驱动层:(nand_chip)FLASH硬件驱动层负责FLASH硬件设备的读、写、擦出,LINUX MTD设备的NOR FLASH驱动位于/driver/mtd/chips子目录下,NAND FLASH驱动则位于/driver/mtd/nand子目录下, s3c2410对应的nand Flash驱动为s3c2410.c。
2. MTD原始设备层:(mtd_info)MTD原始设备层由两部分构成,一部分是MTD原始设备的通用代码(mtdcore.c、mtdpart.c),另一部分是各个特定的FLASH的数据,例如分区。
3. MTD设备层:基于MTD原始设备,LINUX系统可以定义出MTD的块设备(主设备号31)和字符设备(设备号90),构成设备层。MTD字符设备在mtdchar.c实现,MTD块设备在mtdblock.c实现。 4. 设备节点:通过mknod在/dev子目录下建立MTD字符设备节点(主设备号为90)和块设备节点(主设备号为31),用户通过访问此设备节点即可访问MTD字符设备和块设备。 而更详细的图为:   Nand_scan是在初始化nand的时候对nand进行的一步非常好重要的操作,在nand_scan中会对我们所写的关于特定芯片的读写函数重载到nand_chip结构中去,并会将mtd_info结构体中的函数用nand的函数来重载,实现了mtd到底层驱动的联系。 并且在nand_scan函数中会通过读取nand芯片的设备号和厂家号自动在芯片列表中寻找相应的型号和参数,并将其注册进去。 而要更好的去分析和理解nand flash驱动的框架就要去分析nand_scan和nand_add_partition函数。通过这两个函数我们可以了解nand flash驱动是怎么识别nand flash设备,以及怎样将其加入到字符设备和块设备的队列中的。我先将他们的关系写下来,然后在慢慢介绍: nand_scan(&nmtd->mtd, 1)( driversmtd and and_base.c)     nand_scan_ident(mtd, maxchips); ( driversmtd and and_base.c) nand_set_defaults(chip, busw); ( driversmtd and and_base.c)     if (chip->cmdfunc == NULL) chip->cmdfunc = nand_command;      chip->cmd_ctrl(mtd,readcmd, ctrl);     if (chip->waitfunc == NULL) chip->waitfunc = nand_wait;             chip->dev_ready(mtd)     if (!chip->read_byte) chip->read_byte = nand_read_byte;      chip->IO_ADDR_R)     if (!chip->select_chip) chip->select_chip = nand_select_chip;          chip->select_chip = nand_select_chip;     if (!chip->write_buf) chip->write_buf = nand_write_buf;                                                         chip->IO_ADDR_W         type = nand_get_flash_type(mtd, chip, busw, &nand_maf_id);     chip->select_chip(mtd, 0);     chip->cmdfunc(mtd, NAND_CMD_READID, 0x00, -1);     *maf_id = chip->read_byte(mtd);     dev_id = chip->read_byte(mtd);     for (i = 0; nand_flash_ids[i].name != NULL; i++) {         if (dev_id == nand_flash_ids[i].id) {          type = &nand_flash_ids[i];      break; }     }     nand_scan_tail(mtd); ( driversmtd and and_base.c) mtd->type = MTD_NANDFLASH; mtd->flags = MTD_CAP_NANDFLASH; mtd->erase = nand_erase; mtd->point = NULL; mtd->unpoint = NULL; mtd->read = nand_read; mtd->write = nand_write; mtd->read_oob = nand_read_oob; mtd->write_oob = nand_write_oob; mtd->sync = nand_sync; mtd->lock = NULL; mtd->unlock = NULL; mtd->suspend = nand_suspend; mtd->resume = nand_resume; mtd->block_isbad = nand_block_isbad; mtd->block_markbad = nand_block_markbad; int add_mtd_device(struct mtd_info *mtd) list_for_each(this, &mtd_notifiers) { struct mtd_notifier *not = list_entry(this, struct mtd_notifier, list); not->add(mtd); } add_mtd_partitions(&mtd->mtd, set->partitions, set->nr_partitions); add_mtd_device(&slave->mtd); list_for_each(this, &mtd_notifiers) { struct mtd_notifier *not = list_entry(this, struct mtd_notifier, list); not->add(mtd); } mtd_notifiers void register_mtd_user (struct mtd_notifier *new) static int __init init_mtdchar(void)( driversmtdmtdchar.c) register_chrdev(MTD_CHAR_MAJOR, "mtd", &mtd_fops) mtd_class = class_create(THIS_MODULE, "mtd"); register_mtd_user(¬ifier) static struct mtd_notifier notifier = { .add = mtd_notify_add,                 class_device_create(); class_device_create(); .remove = mtd_notify_remove, }; int register_mtd_blktrans(struct mtd_blktrans_ops *tr)( driversmtdmtd_blkdevs.c) register_mtd_user(&blktrans_notifier) ( driversmtdmtdcore.c) static struct mtd_notifier blktrans_notifier = { .add = blktrans_notify_add, list_for_each(this, &blktrans_majors) { struct mtd_blktrans_ops *tr = list_entry(); tr->add_mtd(tr, mtd); } .remove = blktrans_notify_remove, } register_blkdev(tr->major, tr->name); blktrans_majors int register_mtd_blktrans(struct mtd_blktrans_ops *tr) list_add(&tr->list, &blktrans_majors); static int __init init_mtdblock(void)( driversmtdmtdblock.c) { return register_mtd_blktrans(&mtdblock_tr); static struct mtd_blktrans_ops mtdblock_tr = { .name = "mtdblock", .major = 31, .part_bits = 0, .blksize = 512, .readsect = mtdblock_readsect, .writesect = mtdblock_writesect, .add_mtd = mtdblock_add_mtd, add_mtd_blktrans_dev(dev);(driversmtdmtd_blkdevs.c) struct gendisk *gd; gd = alloc_disk(1 << tr->part_bits); set_capacity(); add_disk(gd); .remove_dev = mtdblock_remove_dev, .owner = THIS_MODULE, }; } 下面我们来分析nand_scan函数( driversmtd and and_base.c):         nand_scan(mtd,1)就是用来扫描nand 设备,并用默认的函数去完成没有初始化的函数,同时读flash的ID并为mtd_info或者nand_chip填上合适的值。从上面看我们知道,nand_scan中有两个重要的函数:nand_scan_ident(mtd, maxchips)和nand_scan_tail(mtd); 他们分别是第一和第二阶段的扫描以读flash ID,设置MTD参数。         下面我们看nand_scan_ident(mtd,maxchips) (driversmtd and and_base.c)而在这个函数中又有两个重要的函数nand_set_defaults(chip, busw)和nand_get_flash_type(mtd, chip, busw, &nand_maf_id);         nand_set_defaults:顾名思义就是设置默认的函数或者参数。而更详细点就是,用来确定nand_chip结构体中是否定义了相应的函数,如果定义了就调用nand_chip中定义的函数,而如果没有定义就调用默认的函数。例如下面:         /* 检测用户是否定义了写命令函数:chip->cmdfunc,如果没有就调用默认的 */        if (chip->cmdfunc == NULL) chip->cmdfunc = nand_command;             chip->cmd_ctrl(mtd,readcmd, ctrl); /* 检测用户是否定义了等待就绪函数:chip->waitfunc */ if (chip->waitfunc == NULL) chip->waitfunc = nand_wait;     chip->dev_ready(mtd) if (!chip->read_byte) chip->read_byte = nand_read_byte; /* 检测用户是否设置了读地址 */ chip->IO_ADDR_R) if (!chip->select_chip) chip->select_chip = nand_select_chip; /* 检测用户是否定义了片选函数 */     chip->select_chip = nand_select_chip; if (!chip->write_buf) chip->write_buf = nand_write_buf; /* 检测用户是否设置了写地址 */                                                     chip->IO_ADDR_W 通过上面我们可以知道我们在写驱动函数时要确定上面所列举的这些默认函数是否可以用到你的芯片上,如果可以我们就可以用默认的函数了,这样也会更方便。但是如果不能用,那么我们就要去编写nand_chip中的相应的函数来适应我们的芯片。     而另一个函数nand_get_flash_type:同样让人一看就知道他的具体意思了,他是通过获得flash和制造厂商的id来查询系统是否支持这款芯片。下面就是他的具体代码实现,我会在代码中讲解: /* 通过上面设置的片选函数来选中芯片 */ chip->select_chip(mtd, 0); /* 发送命令值来获取ID */ chip->cmdfunc(mtd, NAND_CMD_READID, 0x00, -1); /* 发送读取ID的命令90h,和地址00h */ /* 读取厂商ID和设备ID,先读取厂商ID */ *maf_id = chip->read_byte(mtd); /* 读取厂商ID */ dev_id = chip->read_byte(mtd); /* 读取设备ID */ /* 在nand_flash_id这个数组中查找与上面设备相匹配的flash */ for (i = 0; nand_flash_ids[i].name != NULL; i++) { if (dev_id == nand_flash_ids[i].id) { type = &nand_flash_ids[i]; break; } } 下表是用于比较的nand_flash_ids中的一部分以及与厂家ID相对应的值: /* * Chip ID list * * Name. ID code, pagesize, chipsize in MegaByte, eraseblock size, * options * * Pagesize; 0, 256, 512 * 0 get this information from the extended chip ID + 256 256 Byte page size * 512 512 Byte page size */ struct nand_flash_dev nand_flash_ids[] = { /* 2 Gigabit */ {"NAND 256MiB 1,8V 8-bit", 0xAA, 0, 256, 0, LP_OPTIONS}, {"NAND 256MiB 3,3V 8-bit", 0xDA, 0, 256, 0, LP_OPTIONS}, /* 本设备的设备ID为DA */ {"NAND 256MiB 1,8V 16-bit", 0xBA, 0, 256, 0, LP_OPTIONS16}, {"NAND 256MiB 3,3V 16-bit", 0xCA, 0, 256, 0, LP_OPTIONS16},         …………………… {NULL,} }; /* * Manufacturer ID list */ struct nand_manufacturers nand_manuf_ids[] = { {NAND_MFR_TOSHIBA, "Toshiba"}, {NAND_MFR_SAMSUNG, "Samsung"}, /* 本设备值为EC表三星 */ {0x0, "Unknown"} };   通过上面的介绍我们知道了如何设置nand_chip结构体来设置默认函数也就是主要完成了硬件驱动层的设置。而下面我们将介绍扫描的第二阶段:nand_scan_tail(mtd):对MTD原始设备层的设置,他主要是完成对于默认函数没有完成的设置以及坏块的设置。部分代码为:           /* 填写剩余的mtd数据*/ mtd->type = MTD_NANDFLASH; mtd->flags = MTD_CAP_NANDFLASH; mtd->erase = nand_erase; mtd->point = NULL; mtd->unpoint = NULL; mtd->read = nand_read; mtd->write = nand_write; mtd->read_oob = nand_read_oob; mtd->write_oob = nand_write_oob; mtd->sync = nand_sync; mtd->lock = NULL; mtd->unlock = NULL; mtd->suspend = nand_suspend; mtd->resume = nand_resume; mtd->block_isbad = nand_block_isbad; mtd->block_markbad = nand_block_markbad;           而讲完nand_scan,接下我们就要讲另一个非常重要的函数:s3c2410_nand_add_partition(info,nmtd, sets);( driversmtd ands3c2410.c),为nand flash添加分区的函数,而通过分析这个函数,我们将更好的了解nand flash驱动的框架设计。add_mtd_partitions( driversmtdmtdpart.c)为一个主MTD设备分区,而分区是根据分区表进行分区的,具体为: 如上图就是将nand flash设备分了四个区,分别为BootLoader,params,kernel和root。 而分完区后会调用add_mtd_device(&slave->mtd)( driversmtdmtdcore.c)函数,该函数会增加一个MTD设备到已有的系统中,并告知每一个现有的“用户”来进行比较。详细的代码为: list_for_each(this, &mtd_notifiers) { struct mtd_notifier *not = list_entry(this, struct mtd_notifier, list); not->add(mtd); } 然后大家就要问了,和什么样的“用户”比较啊?而这就要问mtd_notifiers结构体了,他通知那个用户来那个用户就会来。我们通过后面的代码分析知道会有两个用户过来,一个是字符设备,而另一个是块设备。当然这是后话了。我们先来看是谁设置了mtd_notifiers结构体,通过分析我们知道是注册mtd用户函数register_mtd_user设置的mtd_notifiers结构体,而又是谁调用的注册函数那?我们在下面的代码中查到在( driversmtdmtdchar.c)中 static int __init init_mtdchar(void)( driversmtdmtdchar.c) register_chrdev(MTD_CHAR_MAJOR, "mtd", &mtd_fops)         mtd_class = class_create(THIS_MODULE, "mtd"); register_mtd_user(¬ifier) static struct mtd_notifier notifier = {                     .add = mtd_notify_add,                                 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);                     .remove = mtd_notify_remove, }; 而通过分析上面函数的代码我们知道这就是字符设备驱动的注册过程。而上一个程序段中的add函数对应于这个程序段中的mtd_notify_add函数 而通过分析代码另一个注册mtd用户函数的函数register_mtd_user( driversmtdmtd_blkdevs.c)中: int register_mtd_blktrans(struct mtd_blktrans_ops *tr)( driversmtdmtd_blkdevs.c) register_mtd_user(&blktrans_notifier) ( driversmtdmtdcore.c)     static struct mtd_notifier blktrans_notifier = {         .add = blktrans_notify_add,     list_for_each(this, &blktrans_majors) { struct mtd_blktrans_ops *tr = list_entry(); tr->add_mtd(tr, mtd);     }         .remove = blktrans_notify_remove,             } register_blkdev(tr->major, tr->name); 而通过上面的分析我们看出这是块设备注册的过程。但是我们通过上面的分析不知道add对应于那个函数,这就要看blktrans_majors结构体了。通过代码发现是register_mtd_blktrans函数设置了blktrans_majors结构体而他又是被下面代码中的函数调用: static int __init init_mtdblock(void)( driversmtdmtdblock.c) { return register_mtd_blktrans(&mtdblock_tr); static struct mtd_blktrans_ops mtdblock_tr = { .name = "mtdblock", .major = 31, .part_bits = 0, .blksize = 512, .readsect = mtdblock_readsect, .writesect = mtdblock_writesect, .add_mtd = mtdblock_add_mtd, add_mtd_blktrans_dev(dev);(driversmtdmtd_blkdevs.c) struct gendisk *gd; gd = alloc_disk(1 << tr->part_bits); set_capacity(); add_disk(gd); .remove_dev = mtdblock_remove_dev, .owner = THIS_MODULE, }; } 通过上面的分析我们可以看出add最终就是调用的add_mtd_blktrans_dev函数。而通过上下这些代码的分析我们可以清楚的看到nand flash的框架。而当我们分析清楚这个框架后,将更加有利于我们后面对nand flash驱动代码的分析,以及最后写我们自己更加简短的驱动代码。