norflash linux 驱动

      上一节已经了解了norfalsh的用法。这一节熟悉下norflash(xip结构)的用法。

      首先看下图,MTD层已经帮我们做好了如何擦除falsh,怎么读flash,怎么写flash。我们写驱动主要就是告诉kernel,我们的norfalsh相关信息,来匹配MTD层函数。

     块设备怎么读写,比如应用层open,write后发生什么,主要就是通过文件系统调用了ll_rw_block;具体见下面文章介绍怎么操作块设备的。https://blog.csdn.net/dachunfree/article/details/80595044

norflash驱动代码如下:

#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 map_info *s3c_nor_map;
static struct mtd_info *s3c_nor_mtd;

static struct mtd_partition s3c_nor_parts[] = {
	[0] = {
        .name   = "bootloader_nor",
        .size   = 0x00040000,
		.offset	= 0,
	},
	[1] = {
        .name   = "root_nor",
        .offset = MTDPART_OFS_APPEND,
        .size   = MTDPART_SIZ_FULL,
	}
};


static int s3c_nor_init(void)
{
	/* 1. 分配map_info结构体 */
	s3c_nor_map = kzalloc(sizeof(struct map_info), GFP_KERNEL);;
	
	/* 2. 设置: 物理基地址(phys), 大小(size), 位宽(bankwidth), 虚拟基地址(virt) */
	s3c_nor_map->name = "s3c_nor";
	s3c_nor_map->phys = 0;
	s3c_nor_map->size = 0x1000000; /* >= NOR的真正大小 */
	s3c_nor_map->bankwidth = 2;
	s3c_nor_map->virt = ioremap(s3c_nor_map->phys, s3c_nor_map->size);

	simple_map_init(s3c_nor_map);
	
	/* 3. 使用: 调用NOR FLASH协议层提供的函数来识别 */
	printk("use cfi_probe\n");
	s3c_nor_mtd = do_map_probe("cfi_probe", s3c_nor_map);
	if (!s3c_nor_mtd)
	{
		printk("use jedec_probe\n");
		s3c_nor_mtd = do_map_probe("jedec_probe", s3c_nor_map);
	}

	if (!s3c_nor_mtd)
	{		
		iounmap(s3c_nor_map->virt);
		kfree(s3c_nor_map);
		return -EIO;
	}
	
	/* 4. add_mtd_partitions */
	add_mtd_partitions(s3c_nor_mtd, s3c_nor_parts, 2);
	
	return 0;
}

static void s3c_nor_exit(void)
{
	del_mtd_partitions(s3c_nor_mtd);
	iounmap(s3c_nor_map->virt);
	kfree(s3c_nor_map);
}

module_init(s3c_nor_init);
module_exit(s3c_nor_exit);

MODULE_LICENSE("GPL");

首先分配map_info结构体,里面包含了对norflash(xip结构)的基本描述:大小,数据总线位宽,虚拟地址等信息。然后通过do_map_probe函数传入:

//主要通过cfi和jedec获取norfalsh的信息。通过name传入
struct mtd_info *do_map_probe(const char *name, struct map_info *map) 
    	drv = get_mtd_chip_driver(name); //判断jedec还是cfi,对应不同的方式和驱动
            list_for_each(pos, &chip_drvs_list) //cfi和jedec驱动list 加入到chip_drvs_list
        ret = drv->probe(map); //调用probe函数。这个probe可能是cfi驱动或者jedec驱动。看name传入

再来分析chip_drvs_list这个链表是谁list_add 的:

再来搜索都谁注册了这个驱动:可以看到是cfi_probe和jedec_probe

1.先看jedec_probe

mtd_do_chip_probe(map, &jedec_chip_probe);
    cfi = genprobe_ident_chips(map, cp);
        cp->probe_chip //函数指针,调用jedec_probe_chip函数判断
        /***********************jedec_probe_chip**********************************/
        
            cfi_send_gen_cmd(0xaa, //三条指令解锁获取厂家ID和设备ID,看芯片手册
            cfi_send_gen_cmd(0x55, 
            cfi_send_gen_cmd(0x90,
                
            //获取厂家ID和设备ID
		    cfi->mfr = jedec_read_mfr(map, base, cfi);
		    cfi->id = jedec_read_id(map, base, cfi);
            //和jedec驱动中的数组比对,找到相关falsh信息
            for (i = 0; i < ARRAY_SIZE(jedec_table); i++) 
                if ( jedec_match( base, map, cfi, &jedec_table[i] ) ) //dedec_table 
                //regions,扇区(blocks),如下
                 {
	                .mfr_id		= MANUFACTURER_MACRONIX,
		            .dev_id		= MX29LV160T,
		            .name		= "MXIC MX29LV160T",
		            .uaddr		= {
			        [0] = MTD_UADDR_0x0AAA_0x0555,  /* x8 */
			        [1] = MTD_UADDR_0x0555_0x02AA,  /* x16 */
		            },
		            .DevSize	= SIZE_2MiB,
		            .CmdSet		= P_ID_AMD_STD,
		            .NumEraseRegions= 4,
		            .regions	= {
			        ERASEINFO(0x10000,31),
			        ERASEINFO(0x08000,1),
			        ERASEINFO(0x02000,2),
			        ERASEINFO(0x04000,1)
                    }
		        }
	/********************************jedec 匹配完成*************************************/
    mtd = check_cmd_set(map, 1); //上面的数组信息,会传给map,开始实现mtd层函数
        cfi_cmdset_0001(map, primary);
            	mtd->erase   = cfi_intelext_erase_varsize;
	            mtd->read    = cfi_intelext_read;
	            mtd->write   = cfi_intelext_write_words;
	            mtd->sync    = cfi_intelext_sync;
	            mtd->lock    = cfi_intelext_lock;
	            mtd->unlock  = cfi_intelext_unlock;
	            mtd->suspend = cfi_intelext_suspend;
	            mtd->resume  = cfi_intelext_resume;
	            mtd->flags   = MTD_CAP_NORFLASH;
	            mtd->name    = map->name;
	            mtd->writesize = 1;
    

2.再分析cif_probe。

          其实和jedec区别就是:cfi不通过数组方式获取flash信息,而是通过命令 获取。具体见下链接。

         https://blog.csdn.net/dachunfree/article/details/83652594

mtd_do_chip_probe(map, &cfi_chip_probe); 
    cfi = genprobe_ident_chips(map, cp);
        cp->probe_chip //函数指针,调用cfi_probe_chip
/****************************cfi_probe_chip******************************/
        cfi_send_gen_cmd(0x98, 0x55, //进入cfi模式
        qry_present(map,base,cfi) //验证是否可以正确读取QRY
        cfi_chip_setup
            	cfi->cfiq->P_ID = le16_to_cpu(cfi->cfiq->P_ID);
	            cfi->cfiq->P_ADR = le16_to_cpu(cfi->cfiq->P_ADR);
	            cfi->cfiq->A_ID = le16_to_cpu(cfi->cfiq->A_ID);
	            cfi->cfiq->A_ADR = le16_to_cpu(cfi->cfiq->A_ADR);
	            cfi->cfiq->InterfaceDesc = le16_to_cpu(cfi->cfiq->InterfaceDesc);
	            cfi->cfiq->MaxBufWriteSize = le16_to_cpu(cfi->cfiq->MaxBufWriteSize);
        
 /***************************************cfi end*******************************/     
      check_cmd_set
        cfi_cmdset_0001 //开始往MTD info中写入相关操作函数
            	mtd->erase   = cfi_intelext_erase_varsize;
	            mtd->read    = cfi_intelext_read;
	            mtd->write   = cfi_intelext_write_words;
	            mtd->sync    = cfi_intelext_sync;
	            mtd->lock    = cfi_intelext_lock;
	            mtd->unlock  = cfi_intelext_unlock;
	            mtd->suspend = cfi_intelext_suspend;
	            mtd->resume  = cfi_intelext_resume;
	            mtd->flags   = MTD_CAP_NORFLASH;
	            mtd->name    = map->name;
	            mtd->writesize = 1; 

现在读写擦除等函数已经实现好了(实际就做了个匹配而已)。最后一步:

int add_mtd_partitions(struct mtd_info ,struct mtd_partition *parts,int nbparts)
    add_mtd_device(&slave->mtd);
        list_for_each(this, &mtd_notifiers)
        not->add(mtd);//遍历mtd_notifiers链表,调用add函数指针
/***********************************************************************************/
register_mtd_blktrans(struct mtd_blktrans_ops *tr)
    register_mtd_user(&blktrans_notifier);
        list_add(&blktrans_notifier->list, &mtd_notifiers);//找到mtd_notifiers链表添加出
    blk_init_queue(mtd_blktrans_request, &tr->blkcore_priv->queue_lock);//mtd_blkrans是注册执行函数。理论执行电梯调度算法和读写函数都在此。这个只是唤醒下个线程.
    tr->blkcore_priv->thread = kthread_run(mtd_blktrans_thread, tr,); //线程执行函数
        res = do_blktrans_request(tr, dev, req);
            case READ:
            tr->readsect(dev, block, buf//调度执行函数,readsect

/***********************************************************************************/


static struct mtd_notifier blktrans_notifier = {
	.add = blktrans_notify_add,//这就是not->add(mtd)调用的函数,如下所示:
}
blktrans_notify_add(struct mtd_info *mtd)
    list_for_each(this, &blktrans_majors)
    tr->add_mtd(tr, mtd);//最终调用add_mtd,继续搜索
    .add_mtd	= mtdblock_add_mtd,//mtdblockro.c中找到
        struct mtd_blktrans_dev *dev = kzalloc(sizeof(*dev), GFP_KERNEL);
        dev->mtd = mtd;//将我们开始做好的读写函数(再mtd里面),赋给dev结构体
        add_mtd_blktrans_dev(dev);
            gd = alloc_disk(1 << tr->part_bits);//分配gendisk结构体
            set_capacity(gd, (new->size * tr->blksize) >> 9);//设置容量
            add_disk(gd);//添加驱动

        最后这一步,最重要的就是 blk_init_queue(mtd_blktrans_request, &tr->blkcore_priv->queue_lock);这个函数,mtd_blktrans_request唤醒了那个线程是真正的执行电梯合并调度算法的处理。

        我们执行read,write时候,最终会调用ll_rw_block函数。最终就是调用blk_init_queue中的函数指针。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值