加入收藏 | 设为首页 | 会员中心 | 我要投稿 李大同 (https://www.lidatong.com.cn/)- 科技、建站、经验、云计算、5G、大数据,站长网!
当前位置: 首页 > 百科 > 正文

mtd nandflash 分析

发布时间:2020-12-15 07:04:33 所属栏目:百科 来源:网络整理
导读:一、MTD?的概念和层次 MTD(memory technology device?存储?技术设备?)?是用于访问?memory?设备(?ROM?、?flash?)的?Linux?的子系统。?MTD?的主要目的是为了使新的?memory?设备的驱动更加简单,为此它在硬件和上层之间提供了一个抽象的接口。?MTD?的所有源代

一、MTD?的概念和层次

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

传统上,?UNIX?只认识块设备和字符设备。字符设备是类似键盘或者鼠标的这类设备,你必须从它读取当前数据,但是不可以定位也没有大小。块设备有固定的大小并且可以定位, 它们恰好组织成许多字节的块,通常为?512字节。

闪存既不满足块设备描述也不满足字符设备的描述。它们表现的类似块设备,但又有所不同。比如,块设备不区分写和擦除操作。因此,一种符合闪存特性的特殊设备类型诞生了, 就是?MTD?设备。所以?MTD?既不是块设备,也不是字符设备?。?[2]

关于?MTD?的层次,网络上有一张流传盛广的图片,如下所示,但是最初我看了这幅图根本是一点概念都没有的,不过通过看代码和网上查阅资料,知道了详细一点的分层结构,也纠正了一些前期对这张图的误解。

(?以下这部分纯属个人理解,如果有误,请高人拍砖?!)

为了方便理解,先声明两点:

1. xxx?层?(MTD?原始设备层,?MTD?块设备层?)?,实现封装的代码。

2. xxx?设备?(MTD?原始设备,?MTD?块设备?)?,是?xxx?层向下封装后呈现给上层的表象就 是一个?xxx?设备。

Flash?硬件驱动层:该层的基于特定处理器和特定?flash?芯片,这里以?pxa935?和Hynix??NAND 512MB 1.8V 16-bit?为例。使用类型为?nand_chip,???pxa3xx_nand_info,dfc_context,pxa3xx_bbm?这几个结构体来实现硬 件驱动。代码位于?drivers/mtd/nand?目录下。

MTD?原始设备层: 用类型?mtd_info?的结构体来描述?MTD?原始设备,该结构体中有一 个域会指向?Flash?硬件驱动层中所有使用的结构体?(?串联形式,另外??pxa3xx_bbm?结构体是只在底层驱动中使用?)?。

??NAND flash?在嵌入式系统中通常需要划分多个分区,系统没有运 行起来的时候分区表现为?mtd_partition?类型的结构体数组,该数组由 工程师自己决定。在系统初始化,确切的说是在nand?的驱动加载时 执行相应的?prob?函数时,会将上述数组中的每一个分区用类型为?mtd_part的结构体来描述。因为?mtd_part?结构体中内嵌了一个?mtd_info?的结构体,所以每一个分区在系统看来都是一个?MTD?原始 设备,另外?mtd_part?种还有一个?master?指针,指向描述整片?flash原 始设备的?mtd_info?结构体,所以这个描述整片?nand?的?mtd_info?结构 体也被叫做主分区。

??在?MTD?原始设备层和其上层?MTD?块设备层?(FTL)?活跃着一个牛?X?的指针数组?mtd_table,定义于文件?mtdcore.c?中,该数组就是所有?MTD?原始设备的指针列表?(?当然有数量限制,这里限制在?32?范围内?)?。 不过上面所说的主分区没有在?mtd_table?之列。

???????如果你系统中有?2?片?nand flash?,每个有?8?个分区,那么系统中总 共存在有?18?个?mtd_info?结构体对象,?mtd_table?数组中有?16?个指针 已经有归属。

本层和其上层?FTL?之间就全靠?mtd_table?数组和?mtd_notifiers?链表来?????????

联系了?,至于如何联系的,下文再详细解释。

MTD?块设备层: 该层也叫flash?翻译层?(FTL)?。以前为了在?MTD?设备上使用某种传统 的文件系统,?linux?系统中存在一个叫做?flash?翻译层?(FTL)?,该?FTL?是在?MTD?原始设备的基础上模拟出块设备,所以?FTL?以下的所有内 容呈现给上层的就是一个块设备。这样可以使用通用的块设备的接口 了。

这里也存在一个著名的结构体指针数组,定义于?mtdblock.c?文件中, 其中的每一个指针均指向一个?struct mtdblk_dev?的类型的对象,每一 个?struct mtdblk_dev?类型的对象都是一个MTD?块设备。

网上流传着说使用该?FTL?如何不好,这种观点其实是基于使用传统 文件系统存在的问题,现在有专门针对?nand flash?的?yaffs?日志型文 件系统了。所以那种掉电丢失数据的风险降低到了很小很小。

通用磁盘层:????再上层就是通用磁盘层了,其实每个分区在最后都是向通用磁盘层注 册成了一个?disk?来使用的,后面分析代码会看到这部分。当然对于?block?层的分析不在本文之中讨论。

本来最初的目的只是为了研究内核中nand的驱动,但好奇心太强,忍不住往上层追溯,然后再往下层跟踪。这个过程中,
?特别是在block层,yaffs文件系统和用户空间中相关的分析,其实仅仅只是找到了他们之间的关键联系点而已,没有深
?入详细分析。
?
?* linux2.6.29
?* pxa935
?* Hynix NAND 512MB 1.8V 16-bit
?* 李枝果/lizgo 2010-11-8 lizhiguo0532@163.com
?* 由于本人水平有限,望读者阅读时三思,同时也指正我的错误,谢谢!
?话说MTD子系统向上层提供了几种在用户空间可以直接使用的接口:Raw char device、Raw block device、FTL、NFTL、
?JFFS(2)。前文中主要讨论的yaffs文件系统没有包含在其中,因为yaffs是直接建立在MTD原始设备层之上的。在nand驱动
?注册的时候,会在probe函数中将nand的每个分区通过mtd块设备层、通用磁盘层、block层向内核注册成一个block设备。
?另外挂载yaffs文件系统的时候呢,就会通过设备节点找到block层中对应的block_device结构体,最后在填充超级块的时候
?直接通过主次设备号在mtd_table[]中找到对应的mtd_info结构体。而且在yaffs文件系统层封装的读写等函数中,也是将
?找到的mtd_info结构体传入,直接利用了mtd_info结构体中的相关读写函数指针来进行底层的读写。
?本文主要讨论的接口是Raw block device,也就是传说中的mtdblock,本文中称为mtdblock翻译层,
?主要集中在文件drivers/mtd/mtdblock.c
一、init_mtdblock()
static struct mtd_blktrans_ops mtdblock_tr = {
?.name? = "mtdblock",
?.major? = 31,51); font-family:Arial; font-size:14px; line-height:26px">?.part_bits = 0,51); font-family:Arial; font-size:14px; line-height:26px">?.blksize? = 512,51); font-family:Arial; font-size:14px; line-height:26px">?.open? = mtdblock_open,51); font-family:Arial; font-size:14px; line-height:26px">?.flush? = mtdblock_flush,51); font-family:Arial; font-size:14px; line-height:26px">?.release = mtdblock_release,51); font-family:Arial; font-size:14px; line-height:26px">?.readsect = mtdblock_readsect,51); font-family:Arial; font-size:14px; line-height:26px">?.writesect = mtdblock_writesect,51); font-family:Arial; font-size:14px; line-height:26px">?.add_mtd = mtdblock_add_mtd,51); font-family:Arial; font-size:14px; line-height:26px">?.remove_dev = mtdblock_remove_dev,51); font-family:Arial; font-size:14px; line-height:26px">?.owner? = THIS_MODULE,51); font-family:Arial; font-size:14px; line-height:26px">};
static int __init init_mtdblock(void)
{
?return register_mtd_blktrans(&mtdblock_tr);
}
int register_mtd_blktrans(struct mtd_blktrans_ops *tr)
?int ret,i;
?/* Register the notifier if/when the first device type is
??? registered,to prevent the link/init ordering from fucking
??? us over. */?
?// 注册一个该接口的用户通知器,在分区动态添加或删除的时候被调用,
?// 通知使用该接口的用户做出相应动作。
?// 关于这个用户通知器后面再介绍
?if (!blktrans_notifier.list.next)
? register_mtd_user(&blktrans_notifier);
?tr->blkcore_priv = kzalloc(sizeof(*tr->blkcore_priv),GFP_KERNEL);
?if (!tr->blkcore_priv)
? return -ENOMEM;
?mutex_lock(&mtd_table_mutex);
?ret = register_blkdev(tr->major,tr->name); // 块设备注册,major_names
?// 此时可以在proc/devices中看到注册的该设备
?if (ret) {
? printk(KERN_WARNING "Unable to register %s block device on major %d: %d/n",51); font-family:Arial; font-size:14px; line-height:26px">???????? tr->name,tr->major,ret);
? kfree(tr->blkcore_priv);
? mutex_unlock(&mtd_table_mutex);
? return ret;
?}
?spin_lock_init(&tr->blkcore_priv->queue_lock);? // 初始化请求队列锁
?tr->blkcore_priv->rq = blk_init_queue(mtd_blktrans_request,&tr->blkcore_priv->queue_lock);
?// 初始化请求队列头和安装请求处理函数mtd_blktrans_request()
?if (!tr->blkcore_priv->rq) {
? unregister_blkdev(tr->major,tr->name);
?tr->blkcore_priv->rq->queuedata = tr; // 保存该翻译层的操作集? , &mtdblock_tr
?blk_queue_hardsect_size(tr->blkcore_priv->rq,tr->blksize);
?if (tr->discard)
? blk_queue_set_discard(tr->blkcore_priv->rq,51); font-family:Arial; font-size:14px; line-height:26px">????????? blktrans_discard_request);
?tr->blkshift = ffs(tr->blksize) - 1;
?tr->blkcore_priv->thread = kthread_run(mtd_blktrans_thread,tr,51); font-family:Arial; font-size:14px; line-height:26px">?? "%sd",tr->name);????? // 创建内核线程mtd_blktrans_thread
?if (IS_ERR(tr->blkcore_priv->thread)) {
? blk_cleanup_queue(tr->blkcore_priv->rq);
? return PTR_ERR(tr->blkcore_priv->thread);
?INIT_LIST_HEAD(&tr->devs);// 该链表用来挂接使用该翻译层的所有设备
?list_add(&tr->list,&blktrans_majors);
?// 所有翻译层通过blktrans_majors链表来链接在一起
?for (i=0; i<MAX_MTD_DEVICES; i++) {
? if (mtd_table[i] && mtd_table[i]->type != MTD_ABSENT)
?? tr->add_mtd(tr,mtd_table[i]);
?// 对mtd_table中的所有分区调用该翻译层操作集中的add_mtd函数
?mutex_unlock(&mtd_table_mutex);
?return 0;
?使用函数blk_init_queue初始化一个请求队列和安装一个请求处理函数mtd_blktrans_request():
static void mtd_blktrans_request(struct request_queue *rq)
?{
? struct mtd_blktrans_ops *tr = rq->queuedata;
? wake_up_process(tr->blkcore_priv->thread);? // 唤醒内核线程
?接下来就创建一个内核线程:mtd_blktrans_thread()
static int mtd_blktrans_thread(void *arg)
?struct mtd_blktrans_ops *tr = arg;
?struct request_queue *rq = tr->blkcore_priv->rq;
?/* we might get involved when memory gets low,so use PF_MEMALLOC */
?current->flags |= PF_MEMALLOC;
?spin_lock_irq(rq->queue_lock);
?while (!kthread_should_stop()) {
? struct request *req;
? struct mtd_blktrans_dev *dev;
? int res = 0;
? req = elv_next_request(rq);? // 获取对列中第一个未完成的请求
? if (!req) {????? // 如果请求为空将线程置于睡眠状态
?? set_current_state(TASK_INTERRUPTIBLE);
?? spin_unlock_irq(rq->queue_lock);
?? schedule();
?? spin_lock_irq(rq->queue_lock);
?? continue;
? }
??
? /*
? 一个请求队列管理着很多请求,但是每一个请求都只能针对一个块设备gendisk。
? 所以每一个请求被创建出来后都会指向它的请求对象gendisk。
? 这个指向关系在函数__make_request()->init_request_from_bio()->blk_rq_bio_prep()
? -> rq->rq_disk = bio->bi_bdev->bd_disk
? 中建立。
? */?
? dev = req->rq_disk->private_data;
? tr = dev->tr;
? spin_unlock_irq(rq->queue_lock);
? mutex_lock(&dev->lock);
? res = do_blktrans_request(tr,dev,req);
? 在函数do_blktrans_request(tr,req)中
? 根据请求的数据传输方向来决定调用读或写函数进行数据传输
? tr->readsect(dev,block,buf)
? tr->writesect(dev,51); font-family:Arial; font-size:14px; line-height:26px">? */
? //如果res是0表示不能成功完成请求,为非0表示成功完成请求
? mutex_unlock(&dev->lock);
? spin_lock_irq(rq->queue_lock);
? end_request(req,res);
?spin_unlock_irq(rq->queue_lock);
二、用户通知器
结构体定义:
struct mtd_notifier {
?void (*add)(struct mtd_info *mtd);
?void (*remove)(struct mtd_info *mtd);
?struct list_head list;
有两个方法和一个链表挂钩,参数均为mtd_info指针。
在drivers/mtd/mtd_blkdevs.c中定义了下面的块翻译层通知器
static struct mtd_notifier blktrans_notifier = {
?.add = blktrans_notify_add,51); font-family:Arial; font-size:14px; line-height:26px">?.remove = blktrans_notify_remove,51); font-family:Arial; font-size:14px; line-height:26px">该通知器被FTL、NFTL、mtdblock翻译层使用。
...
/* Register the notifier if/when the first device type is
? registered,51); font-family:Arial; font-size:14px; line-height:26px">? us over. */?
if (!blktrans_notifier.list.next)
? register_mtd_user(&blktrans_notifier); // 只有第一个翻译层注册的时候该函数才会调用
void register_mtd_user (struct mtd_notifier *new)
?int i;
?list_add(&new->list,&mtd_notifiers);
?// 将这个新的用户通知器添加到全局链表mtd_notifiers中(该链表中还可能存在其他用户通知器)
? __module_get(THIS_MODULE);// 增加模块引用计数
?for (i=0; i< MAX_MTD_DEVICES; i++)
? if (mtd_table[i])
?? new->add(mtd_table[i]);
?? // 对mtd_table中的所有分区调用该翻译层操作集中的add_mtd函数,第一次注册该工作在后面会重复再做一次
?? // blktrans_notify_add()
static void blktrans_notify_add(struct mtd_info *mtd)
?struct mtd_blktrans_ops *tr;
?if (mtd->type == MTD_ABSENT)
? return;
?list_for_each_entry(tr,&blktrans_majors,list)
? tr->add_mtd(tr,mtd);
? // blktrans_majors链表管理着所有的翻译层操作集结构体
? // 该处的意思是对于传入的同一个mtd_info结构体,所有的翻译层都会调用自己的
? // add_mtd函数(这些函数都不一样,对于mtdblock层该函数是mtdblock_add_mtd())
在系统启动的时候,register_mtd_blktrans(&mtdblock_tr)执行的时候,mtd_table数组中是空的,所以就不会执行到翻译层
的add_mtd函数上来,那么在又在什么时候调用了翻译层的add_mtd()函数了呢?请看下面
三、将分区向上层注册成block device。
?// pxa3xx_nand.c
?在注册nand驱动的时候:
?pxa3xx_nand_init()
?--> platform_driver_register()
?? --> ...经过注册和设备匹配后调用probe()函数
???? --> pxa3xx_nand_probe()
?????? --> ...
?????? --> add_mtd_partitions(monahans_mtd,pdata->parts,pdata->nr_parts)
???????? --> add_one_partition()
?????????? --> add_mtd_device()
???????????? --> list_for_each_entry(not,&mtd_notifiers,51); font-family:Arial; font-size:14px; line-height:26px">???????????????? not->add(mtd);
?????????????? // 这里就是调用mtd_notifiers链表中所有用户通知器的add函数,以mtdblock的用户通知
?????????????? // 器为例,那么就是调用函数 mtdblock_add_mtd()。这就是证实了当添加一个分区的时候
?????????????? // 用户通知器的add函数被调用,那么当移除分区的时候,remove函数就会被调用,只是
?????????????? // 我们这里没有移除的分区动作。
代码如下:
static int pxa3xx_nand_probe(struct platform_device *pdev)
?...
?return add_mtd_partitions(monahans_mtd,pdata->nr_parts);
int add_mtd_partitions(struct mtd_info *master,51); font-family:Arial; font-size:14px; line-height:26px">???????? const struct mtd_partition *parts,51); font-family:Arial; font-size:14px; line-height:26px">???????? int nbparts)
?struct mtd_part *slave;
?uint64_t cur_offset = 0;
?printk(KERN_NOTICE "Creating %d MTD partitions on /"%s/":/n",nbparts,master->name);
?for (i = 0; i < nbparts; i++) {
? slave = add_one_partition(master,parts + i,i,cur_offset);
? if (!slave)
?? return -ENOMEM;
? cur_offset = slave->offset + slave->mtd.size;
static struct mtd_part *add_one_partition(struct mtd_info *master,51); font-family:Arial; font-size:14px; line-height:26px">? const struct mtd_partition *part,int partno,51); font-family:Arial; font-size:14px; line-height:26px">? uint64_t cur_offset)
?/* allocate the partition structure */
?slave = kzalloc(sizeof(*slave),51); font-family:Arial; font-size:14px; line-height:26px">?if (!slave) {
? printk(KERN_ERR"memory allocation error while creating partitions for /"%s/"/n",51); font-family:Arial; font-size:14px; line-height:26px">?? master->name);
? del_mtd_partitions(master);
? return NULL;
?list_add(&slave->list,&mtd_partitions);
?// mtd_partitions 用于在MTD原始设备层统一管理所有分区信息
?// static LIST_HEAD(mtd_partitions) 本文件中定义
?/* set up the MTD object for this partition */
?slave->mtd.type = master->type;
?slave->mtd.flags = master->flags & ~part->mask_flags;
?slave->mtd.size = part->size;? // 分区大小
?slave->mtd.writesize = master->writesize;
?slave->mtd.oobsize = master->oobsize;
?slave->mtd.oobavail = master->oobavail;
?slave->mtd.subpage_sft = master->subpage_sft;
?slave->mtd.name = part->name;? // 分区名字
?slave->mtd.owner = master->owner;
?slave->mtd.read = part_read;? // 分区读写函数
?slave->mtd.write = part_write;
?if (master->panic_write)
? slave->mtd.panic_write = part_panic_write;
?if (master->point && master->unpoint) {
? slave->mtd.point = part_point;
? slave->mtd.unpoint = part_unpoint;
?if (master->read_oob)
? slave->mtd.read_oob = part_read_oob;
?if (master->write_oob)
? slave->mtd.write_oob = part_write_oob;
?if (master->read_user_prot_reg)
? slave->mtd.read_user_prot_reg = part_read_user_prot_reg;
?if (master->read_fact_prot_reg)
? slave->mtd.read_fact_prot_reg = part_read_fact_prot_reg;
?if (master->write_user_prot_reg)
? slave->mtd.write_user_prot_reg = part_write_user_prot_reg;
?if (master->lock_user_prot_reg)
? slave->mtd.lock_user_prot_reg = part_lock_user_prot_reg;
?if (master->get_user_prot_info)
? slave->mtd.get_user_prot_info = part_get_user_prot_info;
?if (master->get_fact_prot_info)
? slave->mtd.get_fact_prot_info = part_get_fact_prot_info;
?if (master->sync)
? slave->mtd.sync = part_sync;
?if (!partno && master->suspend && master->resume) {
?? slave->mtd.suspend = part_suspend;
?? slave->mtd.resume = part_resume;
?if (master->writev)
? slave->mtd.writev = part_writev;
?if (master->lock)
? slave->mtd.lock = part_lock;
?if (master->unlock)
? slave->mtd.unlock = part_unlock;
?if (master->block_isbad)
? slave->mtd.block_isbad = part_block_isbad;
?if (master->block_markbad)
? slave->mtd.block_markbad = part_block_markbad;
?slave->mtd.erase = part_erase;
?slave->master = master;?? // 该分区的主分区
?slave->offset = part->offset; // 该分区偏移
?slave->index = partno;?? // 分区索引
?if (slave->offset == MTDPART_OFS_APPEND)
? slave->offset = cur_offset;
?if (slave->offset == MTDPART_OFS_NXTBLK) {
? if (mtd_mod_by_eb(cur_offset,master) != 0) {
?? /* Round up to next erasesize */
?? slave->offset = (mtd_div_by_eb(cur_offset,master) + 1) * master->erasesize;
?? printk(KERN_NOTICE "Moving partition %d: "
????????? "0x%012llx -> 0x%012llx/n",partno,51); font-family:Arial; font-size:14px; line-height:26px">????????? (unsigned long long)cur_offset,(unsigned long long)slave->offset);
?if (slave->mtd.size == MTDPART_SIZ_FULL)
? slave->mtd.size = master->size - slave->offset;
?printk(KERN_NOTICE "0x%012llx-0x%012llx : /"%s/"/n",(unsigned long long)slave->offset,51); font-family:Arial; font-size:14px; line-height:26px">? (unsigned long long)(slave->offset + slave->mtd.size),slave->mtd.name);
?// 打印分区表信息
?/* let's do some sanity checks */
?if (slave->offset >= master->size) {
? /* let's register it anyway to preserve ordering */
? slave->offset = 0;
? slave->mtd.size = 0;
? printk(KERN_ERR"mtd: partition /"%s/" is out of reach -- disabled/n",51); font-family:Arial; font-size:14px; line-height:26px">?? part->name);
? goto out_register;
?if (slave->offset + slave->mtd.size > master->size) {
? printk(KERN_WARNING"mtd: partition /"%s/" extends beyond the end of device /"%s/" -- size truncated to %#llx/n",51); font-family:Arial; font-size:14px; line-height:26px">?? part->name,master->name,(unsigned long long)slave->mtd.size);
?if (master->numeraseregions > 1) {
? /* Deal with variable erase size stuff */
? int i,max = master->numeraseregions;
? u64 end = slave->offset + slave->mtd.size;
? struct mtd_erase_region_info *regions = master->eraseregions;
? /* Find the first erase regions which is part of this
?? * partition. */
? for (i = 0; i < max && regions[i].offset <= slave->offset; i++)
?? ;
? /* The loop searched for the region _behind_ the first one */
? i--;
? /* Pick biggest erasesize */
? for (; i < max && regions[i].offset < end; i++) {
?? if (slave->mtd.erasesize < regions[i].erasesize) {
??? slave->mtd.erasesize = regions[i].erasesize;
?? }
? BUG_ON(slave->mtd.erasesize == 0);
?} else {
? /* Single erase size */
? slave->mtd.erasesize = master->erasesize;? // 分区擦除大小赋值
?if ((slave->mtd.flags & MTD_WRITEABLE) &&
???? mtd_mod_by_eb(slave->offset,&slave->mtd)) {
? /* Doesn't start on a boundary of major erase size */
? /* FIXME: Let it be writable if it is on a boundary of
?? * _minor_ erase size though */
? slave->mtd.flags &= ~MTD_WRITEABLE;
? printk(KERN_WARNING"mtd: partition /"%s/" doesn't start on an erase block boundary -- force read-only/n",51); font-family:Arial; font-size:14px; line-height:26px">???? mtd_mod_by_eb(slave->mtd.size,51); font-family:Arial; font-size:14px; line-height:26px">? printk(KERN_WARNING"mtd: partition /"%s/" doesn't end on an erase block -- force read-only/n",51); font-family:Arial; font-size:14px; line-height:26px">?slave->mtd.ecclayout = master->ecclayout;
?if (master->block_isbad) {
? uint64_t offs = 0;
? while (offs < slave->mtd.size) {
?? if (master->block_isbad(master,51); font-family:Arial; font-size:14px; line-height:26px">????? offs + slave->offset))
??? slave->mtd.ecc_stats.badblocks++; // 分区内坏块检查统计
?? offs += slave->mtd.erasesize;
out_register:
?if (part->mtdp) {
? /* store the object pointer (caller may or may not register it*/
? *part->mtdp = &slave->mtd;
? slave->registered = 0;
? /* register our partition */
? add_mtd_device(&slave->mtd);????? // importment
? // 将该从分区作为MTD原始设备加入到mtd_table中,成功返回0
? // MTD原始设备层和MTD设备层就是依靠mtd_table来联系的
? slave->registered = 1;
?return slave;
int add_mtd_device(struct mtd_info *mtd)
?BUG_ON(mtd->writesize == 0);
?for (i=0; i < MAX_MTD_DEVICES; i++)
? if (!mtd_table[i]) {
?? struct mtd_notifier *not;
?? mtd_table[i] = mtd;?? // 填充mtd_table[]数组,mtd原始设备层和mtd块设备层通过mtd_table[]联系在了一起
?? mtd->index = i;???? // mtd_table[]数组的下标赋给mtd->index
?? mtd->usecount = 0;
?? if (is_power_of_2(mtd->erasesize))
??? mtd->erasesize_shift = ffs(mtd->erasesize) - 1;
?? else
??? mtd->erasesize_shift = 0;
?? if (is_power_of_2(mtd->writesize))
??? mtd->writesize_shift = ffs(mtd->writesize) - 1;
??? mtd->writesize_shift = 0;
?? mtd->erasesize_mask = (1 << mtd->erasesize_shift) - 1;
?? mtd->writesize_mask = (1 << mtd->writesize_shift) - 1;
?? /* Some chips always power up locked. Unlock them now */
?? if ((mtd->flags & MTD_WRITEABLE)
?????? && (mtd->flags & MTD_POWERUP_LOCK) && mtd->unlock) {
??? if (mtd->unlock(mtd,mtd->size))
???? printk(KERN_WARNING
??????????? "%s: unlock failed,"
??????????? "writes may not work/n",51); font-family:Arial; font-size:14px; line-height:26px">??????????? mtd->name);
?? DEBUG(0,"mtd: Giving out device %d to %s/n",mtd->name);
?? /* No need to get a refcount on the module containing
????? the notifier,since we hold the mtd_table_mutex */
?? list_for_each_entry(not,51); font-family:Arial; font-size:14px; line-height:26px">???? not->add(mtd);??????? // 只要底层向上层添加一个mtd原始设备的话,那么就会遍历所有用户通知器
??????????????????? // 然后调用其add函数,再向mtd块设备层的上层通用磁盘层和block层注册。
??????????????????? // 对于mtdblock翻译层,其add_mtd函数指针指向mtdblock_add_mtd()
?? mutex_unlock(&mtd_table_mutex);
?? /* We _know_ we aren't being removed,because
????? our caller is still holding us here. So none
????? of this try_ nonsense,and no bitching about it
????? either. :) */
?? __module_get(THIS_MODULE);
?? return 0;
?return 1;
//////////////////////////////////////////////////////////////////////////////////////////////
struct mtd_blktrans_dev {
?struct mtd_info *mtd;
?struct mutex lock;
?int devnum;
?unsigned long size;
?int readonly;
?void *blkcore_priv; /* gendisk in 2.5,devfs_handle in 2.4 */
static void mtdblock_add_mtd(struct mtd_blktrans_ops *tr,struct mtd_info *mtd)
?struct mtd_blktrans_dev *dev = kzalloc(sizeof(*dev),51); font-family:Arial; font-size:14px; line-height:26px">?if (!dev)
?// 这里将分区作为的MTD设备联系在了一起,mtd_table
?dev->mtd = mtd;
?dev->devnum = mtd->index; // 分区表的索引值
?dev->size = mtd->size >> 9; // 该分区的大小以512为单位来计算
?dev->tr = tr;??? // 操作集
?if (!(mtd->flags & MTD_WRITEABLE))
? dev->readonly = 1;
?add_mtd_blktrans_dev(dev);
?// 后面详解
int add_mtd_blktrans_dev(struct mtd_blktrans_dev *new)
?struct mtd_blktrans_ops *tr = new->tr;
?struct mtd_blktrans_dev *d;
?int last_devnum = -1;
?struct gendisk *gd;
?if (mutex_trylock(&mtd_table_mutex)) {
? BUG();
?list_for_each_entry(d,&tr->devs,list) {
? if (new->devnum == -1) {
?? /* Use first free number */
?? if (d->devnum != last_devnum+1) {
??? /* Found a free devnum. Plug it in here */
??? new->devnum = last_devnum+1;
??? list_add_tail(&new->list,&d->list);
??? goto added;
? } else if (d->devnum == new->devnum) {
?? /* Required number taken */
?? return -EBUSY;
?? // 这里返回上层没有错误判断,因此也就没有释放掉上层函数开辟的struct mtd_blktrans_dev
?? // 的内存空间,存在内存泄露,这里应该算是一个内核bug吧,不过呢,这个bug基本上是不会出现的
?? // 如果你的分区不会动态被增加或者删除的话,这里就不会返回这个错误的?
? } else if (d->devnum > new->devnum) {
?? /* Required number was free */
?? list_add_tail(&new->list,51); font-family:Arial; font-size:14px; line-height:26px">?? goto added;
? last_devnum = d->devnum;
?if (new->devnum == -1)
? new->devnum = last_devnum+1;
?if ((new->devnum << tr->part_bits) > 256) {
? return -EBUSY;
?list_add_tail(&new->list,&tr->devs);
?// 翻译层操作集管理者所有属于该层的设备
?added:
?mutex_init(&new->lock);
?if (!tr->writesect)
? new->readonly = 1;
?gd = alloc_disk(1 << tr->part_bits);??????? // note 1
?// 分配gendisk结构体空间,并做一些初始的设置
?if (!gd) {
? list_del(&new->list);
?gd->major = tr->major;?????????????? // mtdblock?,31
?gd->first_minor = (new->devnum) << tr->part_bits;? // mtd_table[]下标
?gd->fops = &mtd_blktrans_ops;??????????? // 操作函数集
?if (tr->part_bits)? // 0
? if (new->devnum < 26)
?? snprintf(gd->disk_name,sizeof(gd->disk_name),51); font-family:Arial; font-size:14px; line-height:26px">???? "%s%c",tr->name,'a' + new->devnum);
? else
???? "%s%c%c",51); font-family:Arial; font-size:14px; line-height:26px">???? 'a' - 1 + new->devnum / 26,51); font-family:Arial; font-size:14px; line-height:26px">???? 'a' + new->devnum % 26);
?else
? snprintf(gd->disk_name,51); font-family:Arial; font-size:14px; line-height:26px">??? "%s%d",new->devnum);
?????????????????????????? // gd->disk_name = mtdblock{0 - 31}
?/* 2.5 has capacity in units of 512 bytes while still
??? having BLOCK_SIZE_BITS set to 10. Just to keep us amused. */
?set_capacity(gd,(new->size * tr->blksize) >> 9);
?gd->private_data = new;
?new->blkcore_priv = gd;? // 互相指向对方
?gd->queue = tr->blkcore_priv->rq; // 该mtd设备的请求队列
?if (new->readonly)
? set_disk_ro(gd,1);
?add_disk(gd);? // 向上层添加一个gendisk note2
/** note1 gd = alloc_disk(1) **/
struct gendisk *alloc_disk(int minors)
?return alloc_disk_node(minors,-1);? // note1-1
/**** note1-1? alloc_disk_node(1,-1) ****/
struct gendisk *alloc_disk_node(int minors,int node_id)
?struct gendisk *disk;
?disk = kmalloc_node(sizeof(struct gendisk),51); font-family:Arial; font-size:14px; line-height:26px">??? GFP_KERNEL | __GFP_ZERO,node_id);?? // node_id就是NUMA系统中的节点号
?if (disk) {
? if (!init_part_stats(&disk->part0)) {
?? kfree(disk);
?? return NULL;
? disk->node_id = node_id;????????? // -1
? if (disk_expand_part_tbl(disk,0)) {
?? free_part_stats(&disk->part0);
? disk->part_tbl->part[0] = &disk->part0;
? disk->minors = minors;?????????? // 1
? rand_initialize_disk(disk);
? disk_to_dev(disk)->class = &block_class;? // #define disk_to_dev(disk) (&(disk)->part0.__dev)
? disk_to_dev(disk)->type = &disk_type;
? device_initialize(disk_to_dev(disk));
? INIT_WORK(&disk->async_notify,51); font-family:Arial; font-size:14px; line-height:26px">?? media_change_notify_thread);
?return disk;
/** note2 add_disk(gd) **/
void add_disk(struct gendisk *disk)
?struct backing_dev_info *bdi;
?dev_t devt;
?int retval;
?/* minors == 0 indicates to use ext devt from part0 and should
? * be accompanied with EXT_DEVT flag.? Make sure all
? * parameters make sense.
?WARN_ON(disk->minors && !(disk->major || disk->first_minor));
?WARN_ON(!disk->minors && !(disk->flags & GENHD_FL_EXT_DEVT));
?disk->flags |= GENHD_FL_UP;
?retval = blk_alloc_devt(&disk->part0,&devt);?? // note2-1
?if (retval) {
? WARN_ON(1);
?disk_to_dev(disk)->devt = devt;? // disk->part0.__dev->devt = devt
?// 主次设备号
?/* ->major and ->first_minor aren't supposed to be
? * dereferenced from here on,but set them just in case.
?disk->major = MAJOR(devt);??? // 31?
?disk->first_minor = MINOR(devt); // {0 - 31}
?blk_register_region(disk_devt(disk),disk->minors,NULL,51); font-family:Arial; font-size:14px; line-height:26px">?????? exact_match,exact_lock,disk);
?register_disk(disk);?????? // note2-2
?blk_register_queue(disk);
?bdi = &disk->queue->backing_dev_info;
?bdi_register_dev(bdi,disk_devt(disk));
?retval = sysfs_create_link(&disk_to_dev(disk)->kobj,&bdi->dev->kobj,51); font-family:Arial; font-size:14px; line-height:26px">?????? "bdi");
?WARN_ON(retval);
/**** note2-1? blk_alloc_devt() ****/
int blk_alloc_devt(struct hd_struct *part,dev_t *devt)
?struct gendisk *disk = part_to_disk(part);
?int idx,rc;
// part->partno = 0,disk->minors = 1,disk->major = 31,disk->first_minor = {0 - 31}
?/* in consecutive minor range? */
?if (part->partno < disk->minors) {
? *devt = MKDEV(disk->major,disk->first_minor + part->partno);? // 最后组织的主次设备号为31<<20 | {0 ~ 31}
? return 0;
?/* allocate ext devt */????? // 另外的方式来分配主次设备号
?do {
? if (!idr_pre_get(&ext_devt_idr,GFP_KERNEL))
? rc = idr_get_new(&ext_devt_idr,part,&idx);
?} while (rc == -EAGAIN);
?if (rc)
? return rc;
?if (idx > MAX_EXT_DEVT) {
? idr_remove(&ext_devt_idr,idx);
?*devt = MKDEV(BLOCK_EXT_MAJOR,blk_mangle_minor(idx));
/**** note2-2? register_disk(disk) ****/
void register_disk(struct gendisk *disk)
?struct device *ddev = disk_to_dev(disk);
?struct block_device *bdev;
?struct disk_part_iter piter;
?struct hd_struct *part;
?int err;
?ddev->parent = disk->driverfs_dev;
?dev_set_name(ddev,disk->disk_name);??? // mtdblock{0 - 31}
?/* delay uevents,until we scanned partition table */
?ddev->uevent_suppress = 1;
?if (device_add(ddev))??????????? // 将该设备添加到系统设备树中
#ifndef CONFIG_SYSFS_DEPRECATED
?err = sysfs_create_link(block_depr,&ddev->kobj,51); font-family:Arial; font-size:14px; line-height:26px">??? kobject_name(&ddev->kobj));
?if (err) {
? device_del(ddev);
#endif
?disk->part0.holder_dir = kobject_create_and_add("holders",&ddev->kobj);
?disk->slave_dir = kobject_create_and_add("slaves",51); font-family:Arial; font-size:14px; line-height:26px">?/* No minors to use for partitions */
?if (!disk_partitionable(disk))
? goto exit;
?/* No such device (e.g.,media were just removed) */
?if (!get_capacity(disk))
?bdev = bdget_disk(disk,0);?? // note2-2-1
?if (!bdev)
?bdev->bd_invalidated = 1;
?err = blkdev_get(bdev,FMODE_READ); // 获取一次以验证
?if (err < 0)
?blkdev_put(bdev,FMODE_READ);
exit:
?/* announce disk after possible partitions are created */
?ddev->uevent_suppress = 0;
?kobject_uevent(&ddev->kobj,KOBJ_ADD);
?/* announce possible partitions */
?disk_part_iter_init(&piter,disk,0);
?while ((part = disk_part_iter_next(&piter)))
? kobject_uevent(&part_to_dev(part)->kobj,51); font-family:Arial; font-size:14px; line-height:26px">?disk_part_iter_exit(&piter);
/****** note2-2-1? bdev = bdget_disk(disk,0) ******/
struct block_device *bdget_disk(struct gendisk *disk,int partno)
?struct block_device *bdev = NULL;
?part = disk_get_part(disk,partno);
?if (part)
? bdev = bdget(part_devt(part));? // note 2-2-1-1
? // 根据主次设备号得到block_device结构体
?disk_put_part(part);
?return bdev;
/******** note2-2-1-1? bdev = bdget(part_devt(part)) ********/
// fs/block_dev.c
struct bdev_inode {
?struct block_device bdev;
?struct inode vfs_inode;
static inline struct bdev_inode *BDEV_I(struct inode *inode)
?return container_of(inode,struct bdev_inode,vfs_inode);
inline struct block_device *I_BDEV(struct inode *inode)
?return &BDEV_I(inode)->bdev;
static inline unsigned long hash(dev_t dev)
?return MAJOR(dev)+MINOR(dev);
static int bdev_test(struct inode *inode,void *data)
?return BDEV_I(inode)->bdev.bd_dev == *(dev_t *)data;
static int bdev_set(struct inode *inode,51); font-family:Arial; font-size:14px; line-height:26px">?BDEV_I(inode)->bdev.bd_dev = *(dev_t *)data;
static LIST_HEAD(all_bdevs);
struct block_device *bdget(dev_t dev)
?struct inode *inode;
?inode = iget5_locked(blockdev_superblock,hash(dev),51); font-family:Arial; font-size:14px; line-height:26px">?? bdev_test,bdev_set,&dev);
?// 该函数最终会调用函数bdev_set()将dev的值赋值给BDEV_I(inode)->bdev.bd_dev
?if (!inode)
?bdev = &BDEV_I(inode)->bdev;
?if (inode->i_state & I_NEW) {
? bdev->bd_contains = NULL;
? bdev->bd_inode = inode;? // inode关联到block_device结构体中
? bdev->bd_block_size = (1 << inode->i_blkbits);
? bdev->bd_part_count = 0;
? bdev->bd_invalidated = 0;
? inode->i_mode = S_IFBLK; // 对应的是块设备节点
? inode->i_rdev = dev;?? // 主次设备号关联
? inode->i_bdev = bdev;?? // block_device结构体关联
? /************************
? 在使用mount挂载该设备上的文件系统时,例如上一篇文章中为了挂载nand分区中的yaffs2文件系统,那么系统是在哪个
? 地方使用了注册时设置的这些信息呢?
? ...
? get_sb_bdev()
? --> open_bdev_exclusive()
??? --> lookup_bdev()
????? --> kern_path()
????? --> bdev = bd_acquire(inode)
??????? 这里只是取出了block_device结构体的指针
? 而对于上面函数iget5_locked()-->bdev_set()中设置在对应block_device结构体中的主次设备号在文件系统挂载的时候
? ,在下面这个函数中获取到后存放在超级块中。
? --> sget()
??? --> set_bdev_super()
????? --> s->s_bdev = data;??????? // 就是open_bdev_exclusive()函数获得的block_device结构体
????? --> s->s_dev = s->s_bdev->bd_dev;? // 主次设备号
? 最后在yaffs2文件系统超级块填充函数yaffs_internal_read_super()中是这么使用的:
? if (MAJOR(sb->s_dev) != MTD_BLOCK_MAJOR)
?? return NULL; /* This isn't an mtd device */
? mtd = get_mtd_device(NULL,MINOR(sb->s_dev)); // 取得对应的mtd_info结构体
? ************************/
? inode->i_data.a_ops = &def_blk_aops;
? mapping_set_gfp_mask(&inode->i_data,GFP_USER);
? inode->i_data.backing_dev_info = &default_backing_dev_info;
? spin_lock(&bdev_lock);
? list_add(&bdev->bd_list,&all_bdevs);
? spin_unlock(&bdev_lock);
? unlock_new_inode(inode);
?return bdev;??????? // 返回这个block_device指针
四、一点补充
static struct mtdblk_dev {
?int count;
?struct mutex cache_mutex;
?unsigned char *cache_data;
?unsigned long cache_offset;
?unsigned int cache_size;
?enum { STATE_EMPTY,STATE_CLEAN,STATE_DIRTY } cache_state;
} *mtdblks[MAX_MTD_DEVICES];? // #define MAX_MTD_DEVICES 32
/*
** 这里是定义了一个指针数组,其中的每一个指针均指向一个struct mtdblk_dev
** 的类型的对象,每一个struct mtdblk_dev类型的对象都是一个MTD块设备,
** 这里的每一个指针指向的MTD块设备和mtd_table[]中元素指向的每一个
** struct mtd_info一一对应。
** 另外,可以看出linux最多支持32个MTD块设备
?关于mtdblks[]指针数组,我这里是以yaffs文件系统为例,所以是不会使用到这个指针数组的,应为yaffs它是建立在mtd原始设备层上,在其封装的函数内直接使用了mtd_info结构体内的函数,而没有经过mtdblock翻译层。如果我们的系统中使用的是mtd的其他接口,比如block device,那么就会使用到这个指针数组,在哪里使用呢?
?mtdblock.c文件中定义了mtdblock翻译层的操作集:
这些mtdblks[]的指针在哪里赋值的呢?请看函数mtdblock_open()中:
static int mtdblock_open(struct mtd_blktrans_dev *mbd)
?// mtd_blktrans_dev这个设备就是我们在初始化经过mtdblock翻译层向上层注册时的产物,表示了本层环境中的mtd设备
?// mbd->mtd在mtdblock_add_mtd()函数中被赋值,就是对应的mtd原始设备
?struct mtdblk_dev *mtdblk;
?struct mtd_info *mtd = mbd->mtd;
?int dev = mbd->devnum;
?DEBUG(MTD_DEBUG_LEVEL1,"mtdblock_open/n");
?if (mtdblks[dev]) {
? mtdblks[dev]->count++;
?} // 如果已经打开了,那么只需要增加引用计数
?/* OK,it's not open. Create cache info for it */
?mtdblk = kzalloc(sizeof(struct mtdblk_dev),GFP_KERNEL); // 否则,分配空间
?if (!mtdblk)
?mtdblk->count = 1;? // 引用计数初始化成1
?mtdblk->mtd = mtd;? // 重要的联系
?mutex_init(&mtdblk->cache_mutex);
?mtdblk->cache_state = STATE_EMPTY;
?if ( !(mtdblk->mtd->flags & MTD_NO_ERASE) && mtdblk->mtd->erasesize) {
? mtdblk->cache_size = mtdblk->mtd->erasesize;
? mtdblk->cache_data = NULL;
?mtdblks[dev] = mtdblk; // mtdblks指针数组中相应位置设置

}

其实mtd块设备层和mtd原始设备层的分界线很明显,只是通过
?mtd_table[]和mtd_notifiers链表来联系,具体怎么联系的请参考上一篇文章。
?本文中将详细分析linux内核中nand设备注册和驱动注册,同时文中会穿插关于nand坏块管理的部分,另外在适当的地方会
?讲解DMA原理和其在nand驱动程序中的应用。
?
?* OS?? : linux2.6.29
?* SOC? : pxa935
?* NAND : Hynix(512MB 1.8V 16-bit) - H8BCS0UN0MCR
?* Author: 李枝果/lizgo? 2010-11-9? lizhiguo0532@163.com
?* note? : 本文中涉及的内容基本都是平台相关的,读者可只关注共性的东西

?2.6版本的linux内核驱动模型中流传着一个时髦的词:“platform”,其中也存在platform device和platform driver, 内核中使用platform bus统一管理这些设备和驱动,所以注册包括device和driver的注册。 一、platform device注册 ?MACHINE_START和MACHINE_END定义的结构体是平台相关的,这里定义如下: ?MACHINE_START(BENZGLB,"Benzglb") ? .phys_io??????? = 0x40000000,? .boot_params??? = 0xa0000100,? .io_pg_offst??? = (io_p2v(0x40000000) >> 18) & 0xfffc,?? ? .map_io???????? = pxa_map_io,? // start_kernel()-->setup_arch()-->paging_init()-->devicemaps_init()-->pxa_map_io ? .init_irq?????? = pxa3xx_init_irq,? // 在setup_arch()中被赋值给全局函数指针init_arch_irq ? // start_kernel()-->init_IRQ()-->"init_arch_irq()"间接调用pxa3xx_init_irq ? .timer????????? = &pxa_timer,? // 在setup_arch()中被赋值给全局struct sys_timer对象指针system_timer ? // start_kernel()-->time_init-->"system_timer->init()"间接调用pxa_timer.init = pxa_timer_init() ? .init_machine?? = benzglb_init,? // 在setup_arch()中被赋值给全局函数指针init_machine ? // 由于arch_initcall(customize_machine),所以在start_kernel()-->rest_init() ? // -->kernel_init()-->do_basic_setup()-->do_initcalls()的第3个等级上被调用 ? // init.h ?MACHINE_END ?/******************************* ?其中的宏定义于文件:arch/arm/include/asm/mach/arch.h ?#define MACHINE_START(_type,_name)?? / ?static const struct machine_desc __mach_desc_##_type / ? __used?????? / ? __attribute__((__section__(".arch.info.init"))) = { / ? .nr? = MACH_TYPE_##_type,? / ? .name? = _name,? ?#define MACHINE_END??? / ?}; ?将上面的展开,实际上就是定义了一个结构体: ?static const struct machine_desc __mach_desc_BENZGLB?? __used / ????????????????? __attribute__((__section__(".arch.info.init"))) = { ????????????????? .nr? = MACH_TYPE_BENZGLB,????????????????? .name? = "Benzglb",????????????????? .phys_io??????? = 0x40000000,??????????????????? .boot_params??? = 0xa0000100,??????????????????? .io_pg_offst??? = (io_p2v(0x40000000) >> 18) & 0xfffc,??????????????????? .map_io???????? = pxa_map_io,??????????????????? .init_irq?????? = pxa3xx_init_irq,??????????????????? .timer????????? = &pxa_timer,??????????????????? .init_machine?? = benzglb_init,??????????????????? }; ?*******************************/ ?benzglb_init()函数可谓是重量级的了,初始化了很多东西。但是是在哪里调用该函数的呢?或许从上面的注释你也可以 ?注意到了,下面就再来wlak一下: ?start_kernel() ?--> setup_arch() ?? --> ... ?? --> init_machine = mdesc->init_machine; ?? --> ... ?? init_machine是文件arch/arm/kernel/setup.c中的静态全局变量,定义和调用如下: ?? static void (*init_machine)(void) __initdata; ?? static int __init customize_machine(void) ?? { ??? /* customizes platform devices,or adds new ones */ ??? if (init_machine) ???? init_machine(); ??? return 0; ?? } ?? arch_initcall(customize_machine);??? // init.h? initcall3 ?? 可以看到customize_machine()函数将会在do_initcalls()的第3个等级上被调用,接着就会调用函数init_machine(), ?? 也就是函数benzglb_init(): ?do_initcalls() ?--> customize_machine() ?? --> init_machine() == benzglb_init() ???? --> benzina_init_nand()?????? // 该函数中我们只关注nand初始化 ?????? 看来有必要将benzina_init_nand()全部列出来看一看了: ?????? static struct pxa3xx_nand_platform_data benzina_nand_info; ?????? /****** ?????? struct pxa3xx_nand_platform_data { ??????? struct mtd_partition *parts; ??????? unsigned int??? nr_parts; ?????? }; ?????? ******/ ?????? static void __init benzina_init_nand(void) ?????? { ??????? benzina_nand_info.parts = android_256m_v75_partitions;??????? // nand分区数组 ??????? benzina_nand_info.nr_parts = ARRAY_SIZE(android_256m_v75_partitions); // nand分区数目 ???????? ??????? pxa3xx_device_nand.dev.platform_data = &benzina_nand_info; ??????? platform_device_register(&pxa3xx_device_nand); ?????? } ?????? android_256m_v75_partitions定义于arch/arm/mach-pxa/include/mach/part_table.h中,这是一个 ?????? struct mtd_partition类型结构体的数组,描述了系统上nand分区情况:name、offset、size等。 ?????? pxa3xx_device_nand结构体对象是struct platform_device类型: ?????? static u64 pxa3xx_nand_dma_mask = DMA_BIT_MASK(32); ?????? static struct resource pxa3xx_resource_nand[] = { ??????? [0] = { ???????? .start = 0x43100000,???????? .end = 0x431000ff,???????? .flags = IORESOURCE_MEM,??????? },??????? [1] = { ???????? .start = IRQ_NAND,???????? .end = IRQ_NAND,???????? .flags = IORESOURCE_IRQ,?????? }; ??????? ?????? struct platform_device pxa3xx_device_nand = { ??????? .name? = "pxa3xx-nand",??????? .id? = -1,??????? .dev? = { ???????? .dma_mask = &pxa3xx_nand_dma_mask,???????? .coherent_dma_mask = DMA_BIT_MASK(32),??????? .resource = pxa3xx_resource_nand,?? // see up ??????? .num_resources = ARRAY_SIZE(pxa3xx_resource_nand),?????? }; ?????? struct platform_device结构体的定义位于文件include/linux/platform_device.h中: ?????? struct platform_device { ??????? const char * name; ??????? int? id; ??????? struct device dev; ??????? u32? num_resources; ??????? struct resource * resource; ?????? }; ?????? benzina_init_nand()函数中将描述nand分区的结构体benzina_nand_info与描述nand device的结构体 ?????? 联系起来: ?????? pxa3xx_device_nand.dev.platform_data = &benzina_nand_info; ?????? 最后调用函数platform_device_register(&pxa3xx_device_nand)将nand的平台设备注册进系统的设备树内。 ?????? 对于注册的过程这里就不跟踪了,如果有兴趣,可参考我的另篇文章或自行分析。 二、platform driver注册 ?该部分的内容位于文件drivers/mtd/nand/pxa3xx_nand.c ?static struct platform_driver pxa3xx_nand_driver = { ? .driver = { ?? .name = "pxa3xx-nand",? },? .probe? = pxa3xx_nand_probe,? .remove? = pxa3xx_nand_remove,?#ifdef CONFIG_PM??????????????? // 电源管理的部分 ? .suspend = pxa3xx_nand_suspend,? .resume? = pxa3xx_nand_resume,?#endif ?}; ?static int __init pxa3xx_nand_init(void) ?{ ? ... ? return platform_driver_register(&pxa3xx_nand_driver); ?} ?module_init(pxa3xx_nand_init);? ?这里说明一点:device和driver的注册其实是没有先后之分的,device注册的时候除了将自己挂在platform bus上外,另外 ?会去遍历该bus上的所有drivers,直到匹配到(device和driver的名字相同)一个driver为止。而driver注册的时候,也是除 ?了将自己挂在platfrom bus上之外,另外也会去遍历该bus上的所有设备区匹配,这里和前面不同的是,它会去找到多有该 ?driver可以管理到的设备为止。下面就跟踪一下driver注册时,如何调用到probe函数的,又如何传递了 ?struct platform_device的参数: ?platform_driver_register(&pxa3xx_nand_driver) ?--> drv->driver.bus = &platform_bus_type ?--> drv->driver.probe = platform_drv_probe ?--> ... ?--> driver_register(&drv->driver) ?? --> driver_find(drv->name,drv->bus)??? // 线检查是否已经注册过了 ?? --> bus_add_driver(drv) ???? --> driver_attach(drv) ??????? --> bus_for_each_dev(drv->bus,drv,__driver_attach) ????????? --> fn(dev,data) = __driver_attach(dev,data) // dev - each device on platfrom bus ????????????????????????????????? // data - driver ??????????? --> driver_probe_device(drv,dev) ????????????? --> drv->bus->match(dev,drv) = platform_match(dev,drv) // 名字匹配 ????????????? --> really_probe(dev,drv) ??????????????? --> drv->probe(dev) = platform_drv_probe(dev) ????????????????? --> static int platform_drv_probe(struct device *_dev) ?????????????????? { ??????????????????? struct platform_driver *drv = to_platform_driver(_dev->driver); ??????????????????? struct platform_device *dev = to_platform_device(_dev); ????????????? ??????????????????? return drv->probe(dev); ?????????????????? } ?????????????????? -->pxa3xx_nand_probe(&pxa3xx_device_nand); ?????????????????? ... ?????????????????? 到这个过程中,我们就还可以看到,driver注册的时候,会优先使用platform bus ?????????????????? 的probe函数,如果它的probe函数为NULL,那么就使用注册driver的probe函数 ?????????????????? (前提是要存在probe函数) 三、pxa3xx_nand_probe()函数分析 ? 由于内容加多,参见文档:pxa3xx_nand_probe.c 四、底层几个关键结构体的联系 static struct mtd_info *monahans_mtd = NULL; struct nand_chip *this;? struct pxa3xx_nand_info *info; struct dfc_context dfc_context = { ?.dfc_mode = &dfc_mode,}; static struct dfc_flash_info hynix4GbX16 = { ?.timing = { ? .tCH = 10,????? /* tCH,Enable signal hold time */ ? .tCS = 35,????? /* tCS,Enable signal setup time */ ? .tWH = 15,????? /* tWH,ND_nWE high duration */ ? .tWP = 25,????? /* tWP,ND_nWE pulse time */ ? .tRH = 15,????? /* tRH,ND_nRE high duration */ ? .tRP = 25,????? /* tRP,ND_nRE pulse width */ ? /* tR = tR+tRR+tWB+1,ND_nWE high to ND_nRE low for read */ ? .tR = 25000,? /* tWHR,ND_nWE high to ND_nRE low delay for status read */ ? .tWHR = 60,? .tAR = 10,????? /* tAR,ND_ALE low to ND_nRE low delay */ ?},?.enable_arbiter = 1,??? /* Data flash bus arbiter enable */ ?.page_per_block = 64,?? /* Pages per block */ ?.row_addr_start = 1,/* third cycle start,Row address start position */ ?.read_id_bytes = 4,???? /* Returned ID bytes */ ?.dfc_mode = 0,????????? /* NAND mode */ ?.ncsx = 0,?.page_size = 2048,????? /* Page size in bytes */ ?.oob_size = 64,???????? /* OOB size in bytes */ ?.flash_width = 16,????? /* Width of Flash memory */ ?.dfc_width = 16,??????? /* Width of flash controller */ ?.num_blocks = 4096,???? /* Number of physical blocks in Flash */?? //modified sunqidong ?.chip_id =? 0xbcad,???????????????????????? //modified sunqidong ?.read_prog_cycles = 5,/* Read,Program Cycles */ ?/* command codes */ ?.read1 = 0x3000,??????? /* Read */ ?.read2 = 0x0050,??????? /* Read1 unused,current DFC don't support */ ?.program = 0x1080,????? /* Write,two cycle command */?????? //modified sunqidong ?.read_status = 0x0070,? /* Read status */ ?.read_id = 0x0090,????? /* Read ID */ ?.erase =? 0xD060,?????? /* Erase,two cycle command */ ?.reset = 0x00FF,??????? /* Reset */ ?.lock = 0x002A,???????? /* Lock whole flash */ ?.unlock = 0x2423,/* Unlock,two cycle command,supporting partial unlock */ ?.lock_status = 0x007A,? /* Read block lock status */ ?.addr2ndcb1 = HYNIX4GbX16Addr2NDCB1,?.ndbbr2addr = HYNIX4GbX16NDBBR2Addr,}; ///////////////////////// 这几个底层关键结构体的联系 //////////////////////////// context->flash_info = &hynix4GbX16 // 分配mtd_info、nand_chip、pxa3xx_nand_info的空间 monahans_mtd = kzalloc(sizeof(struct mtd_info) + sizeof(struct nand_chip) + ?? sizeof(struct pxa3xx_nand_info),GFP_KERNEL); ... this = (struct nand_chip *)((void *)monahans_mtd + sizeof(struct mtd_info)); info = (struct pxa3xx_nand_info *)((void *)this + sizeof(struct nand_chip)); ... monahans_mtd->priv = this; this->priv = info; ... info->context = &dfc_context ...

(编辑:李大同)

【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容!

    推荐文章
      热点阅读