精华内容
下载资源
问答
  • mtd驱动框架分析

    千次阅读 2015-06-17 10:14:45
    mtd驱动框架分为mtd block层,mtd raw层,具体的设备驱动。

    mtd驱动框架分为mtd block层,mtd raw层,具体的设备驱动。一个mtd原始设备可以通过mtd_partion结构被分成多个mtd原始设备mtd_part。比如:s3c2440的flash驱动加载的时候分配一个mtd_info结构(一般被芯片厂商内嵌),然后根据用户配置的分区信息,调用add_mtd_partions创建mtd_part结构,每个mtd_part又嵌套了一个mtd_info结构。再往上就是注册mtdblock。

    1. 重要的数据结构

    struct mtd_info {
    	u_char type;	//mtd类型
    	uint32_t flags;	//标志位
    	uint64_t size;	 // Total size of the MTD	//mtd设备总大小
    
    	/* "Major" erase size for the device. Na茂ve users may take this
    	 * to be the only erase size available, or may use the more detailed
    	 * information below if they desire
    	 */
    	uint32_t erasesize;		//擦除块大小
    	/* Minimal writable flash unit size. In case of NOR flash it is 1 (even
    	 * though individual bits can be cleared), in case of NAND flash it is
    	 * one NAND page (or half, or one-fourths of it), in case of ECC-ed NOR
    	 * it is of ECC block size, etc. It is illegal to have writesize = 0.
    	 * Any driver registering a struct mtd_info must ensure a writesize of
    	 * 1 or larger.
    	 */
    	uint32_t writesize;		//写入块大小
    
    	/*
    	 * Size of the write buffer used by the MTD. MTD devices having a write
    	 * buffer can write multiple writesize chunks at a time. E.g. while
    	 * writing 4 * writesize bytes to a device with 2 * writesize bytes
    	 * buffer the MTD driver can (but doesn't have to) do 2 writesize
    	 * operations, but not 4. Currently, all NANDs have writebufsize
    	 * equivalent to writesize (NAND page size). Some NOR flashes do have
    	 * writebufsize greater than writesize.
    	 */
    	uint32_t writebufsize;
    
    	uint32_t oobsize;   // Amount of OOB data per block (e.g. 16)		//oob数据大小
    	uint32_t oobavail;  // Available OOB bytes per block
    
    	/*
    	 * If erasesize is a power of 2 then the shift is stored in
    	 * erasesize_shift otherwise erasesize_shift is zero. Ditto writesize.
    	 */
    	unsigned int erasesize_shift;
    	unsigned int writesize_shift;
    	/* Masks based on erasesize_shift and writesize_shift */
    	unsigned int erasesize_mask;
    	unsigned int writesize_mask;
    
    	// Kernel-only stuff starts here.
    	const char *name;	//mtd设备名称
    	int index;	//索引
    
    	/* ecc layout structure pointer - read only ! */
    	struct nand_ecclayout *ecclayout;	//ecc工作布局
    
    	/* Data for variable erase regions. If numeraseregions is zero,
    	 * it means that the whole device has erasesize as given above.
    	 */
    	int numeraseregions;
    	struct mtd_erase_region_info *eraseregions;	//擦写块布局
    
    	/*
    	 * Erase is an asynchronous operation.  Device drivers are supposed
    	 * to call instr->callback() whenever the operation completes, even
    	 * if it completes with a failure.
    	 * Callers are supposed to pass a callback function and wait for it
    	 * to be called before writing to the block.
    	 */
    	int (*erase) (struct mtd_info *mtd, struct erase_info *instr);	//擦写回调函数
    
    	/* This stuff for eXecute-In-Place */
    	/* phys is optional and may be set to NULL */
    	int (*point) (struct mtd_info *mtd, loff_t from, size_t len,
    			size_t *retlen, void **virt, resource_size_t *phys);	//片内执行的回调函数
    
    	/* We probably shouldn't allow XIP if the unpoint isn't a NULL */
    	void (*unpoint) (struct mtd_info *mtd, loff_t from, size_t len);
    
    	/* Allow NOMMU mmap() to directly map the device (if not NULL)
    	 * - return the address to which the offset maps
    	 * - return -ENOSYS to indicate refusal to do the mapping
    	 */
    	unsigned long (*get_unmapped_area) (struct mtd_info *mtd,
    					    unsigned long len,
    					    unsigned long offset,
    					    unsigned long flags);
    
    	/* Backing device capabilities for this device
    	 * - provides mmap capabilities
    	 */
    	struct backing_dev_info *backing_dev_info;	//后备设备
    
    
    	int (*read) (struct mtd_info *mtd, loff_t from, size_t len, size_t *retlen, u_char *buf);
    	int (*write) (struct mtd_info *mtd, loff_t to, size_t len, size_t *retlen, const u_char *buf);
    
    	/* In blackbox flight recorder like scenarios we want to make successful
    	   writes in interrupt context. panic_write() is only intended to be
    	   called when its known the kernel is about to panic and we need the
    	   write to succeed. Since the kernel is not going to be running for much
    	   longer, this function can break locks and delay to ensure the write
    	   succeeds (but not sleep). */
    
    	int (*panic_write) (struct mtd_info *mtd, loff_t to, size_t len, size_t *retlen, const u_char *buf);
    
    	int (*read_oob) (struct mtd_info *mtd, loff_t from,
    			 struct mtd_oob_ops *ops);
    	int (*write_oob) (struct mtd_info *mtd, loff_t to,
    			 struct mtd_oob_ops *ops);
    
    	/*
    	 * Methods to access the protection register area, present in some
    	 * flash devices. The user data is one time programmable but the
    	 * factory data is read only.
    	 */
    	//下面是保护区域的操作函数
    	int (*get_fact_prot_info) (struct mtd_info *mtd, struct otp_info *buf, size_t len);
    	int (*read_fact_prot_reg) (struct mtd_info *mtd, loff_t from, size_t len, size_t *retlen, u_char *buf);
    	int (*get_user_prot_info) (struct mtd_info *mtd, struct otp_info *buf, size_t len);
    	int (*read_user_prot_reg) (struct mtd_info *mtd, loff_t from, size_t len, size_t *retlen, u_char *buf);
    	int (*write_user_prot_reg) (struct mtd_info *mtd, loff_t from, size_t len, size_t *retlen, u_char *buf);
    	int (*lock_user_prot_reg) (struct mtd_info *mtd, loff_t from, size_t len);
    
    	/* kvec-based read/write methods.
    	   NB: The 'count' parameter is the number of _vectors_, each of
    	   which contains an (ofs, len) tuple.
    	*/
    	int (*writev) (struct mtd_info *mtd, const struct kvec *vecs, unsigned long count, loff_t to, size_t *retlen);
    
    	/* Sync */
    	void (*sync) (struct mtd_info *mtd);	//同步
    
    	/* Chip-supported device locking */
    	int (*lock) (struct mtd_info *mtd, loff_t ofs, uint64_t len);	//加锁
    	int (*unlock) (struct mtd_info *mtd, loff_t ofs, uint64_t len);	//解锁
    	int (*is_locked) (struct mtd_info *mtd, loff_t ofs, uint64_t len);
    
    	/* Power Management functions */
    	int (*suspend) (struct mtd_info *mtd);	//挂起函数
    	void (*resume) (struct mtd_info *mtd);	//恢复函数
    
    	/* Bad block management functions */
    	int (*block_isbad) (struct mtd_info *mtd, loff_t ofs);	//检查坏块
    	int (*block_markbad) (struct mtd_info *mtd, loff_t ofs);	//标记坏块
    
    	struct notifier_block reboot_notifier;  /* default mode before reboot */
    
    	/* ECC status information */
    	struct mtd_ecc_stats ecc_stats;		//ecc状态
    	/* Subpage shift (NAND) */
    	int subpage_sft;
    
    	void *priv;		//私有数据
    
    	struct module *owner;
    	struct device dev;
    	int usecount;
    
    	/* If the driver is something smart, like UBI, it may need to maintain
    	 * its own reference counting. The below functions are only for driver.
    	 * The driver may register its callbacks. These callbacks are not
    	 * supposed to be called by MTD users */
    	int (*get_device) (struct mtd_info *mtd);
    	void (*put_device) (struct mtd_info *mtd);
    };
    static LIST_HEAD(mtd_partitions);
    static DEFINE_MUTEX(mtd_partitions_mutex);
    
    /* Our partition node structure */
    struct mtd_part {
    	struct mtd_info mtd;	//内嵌一个mtd_info,分区代表的原始设备
    	struct mtd_info *master;	//指向主mtd_info结构
    	uint64_t offset;	//偏移地址
    	struct list_head list;	//链接到mtd_partitions链表中
    };
    
    2. 以s3c2440的nand  flash驱动为例来说明初始化过程

    在看probe函数之前先看下三星封装的数据结构,本来一个mtd_info还要封装成一个s3c2410_nand_mtd结构

    struct s3c2410_nand_mtd {
    	struct mtd_info			mtd;	//内嵌一个mtd_info结构
    	struct nand_chip		chip;	// 内嵌一个nand_chip结构
    	struct s3c2410_nand_set		*set;	//与分区信息有关
    	struct s3c2410_nand_info	*info;	//flash控制器配置相关结构
    	int				scan_res;
    };
    struct s3c2410_nand_info {
    	/* mtd info */
    	struct nand_hw_control		controller;	//硬件控制结构
    	struct s3c2410_nand_mtd		*mtds;	//s3c2410_nand_mtd数组
    	struct s3c2410_platform_nand	*platform;	//平台有关的信息
    
    	/* device info */
    	struct device			*device;
    	struct resource			*area;
    	struct clk			*clk;	//时钟
    	void __iomem			*regs;	//寄存器
    	void __iomem			*sel_reg;
    	int				sel_bit;
    	int				mtd_count;
    	unsigned long			save_sel;
    	unsigned long			clk_rate;
    	enum s3c_nand_clk_state		clk_state;
    
    	enum s3c_cpu_type		cpu_type;
    
    #ifdef CONFIG_CPU_FREQ
    	struct notifier_block	freq_transition;
    #endif
    };
    好了,可以看probe函数了

    static int s3c24xx_nand_probe(struct platform_device *pdev)
    {
    	struct s3c2410_platform_nand *plat = to_nand_plat(pdev);
    	enum s3c_cpu_type cpu_type; 
    	struct s3c2410_nand_info *info;
    	struct s3c2410_nand_mtd *nmtd;
    	struct s3c2410_nand_set *sets;
    	struct resource *res;
    	int err = 0;
    	int size;
    	int nr_sets;
    	int setno;
    
    	cpu_type = platform_get_device_id(pdev)->driver_data;
    
    	pr_debug("s3c2410_nand_probe(%p)\n", pdev);
    
    	info = kzalloc(sizeof(*info), GFP_KERNEL);	//分配s3c2410_nand_info结构
    	if (info == NULL) {
    		dev_err(&pdev->dev, "no memory for flash info\n");
    		err = -ENOMEM;
    		goto exit_error;
    	}
    
    	platform_set_drvdata(pdev, info);
    
    	spin_lock_init(&info->controller.lock);
    	init_waitqueue_head(&info->controller.wq);
    
    	/* get the clock source and enable it */
    
    	info->clk = clk_get(&pdev->dev, "nand");	//获取nand时钟
    	if (IS_ERR(info->clk)) {
    		dev_err(&pdev->dev, "failed to get clock\n");
    		err = -ENOENT;
    		goto exit_error;
    	}
    
    	s3c2410_nand_clk_set_state(info, CLOCK_ENABLE);
    
    	/* allocate and map the resource */
    
    	/* currently we assume we have the one resource */
    	res  = pdev->resource;	//获取arch/arm/match-s3c2440里面定义的resource
    	size = resource_size(res);
    
    	info->area = request_mem_region(res->start, size, pdev->name);	//请求资源
    
    	if (info->area == NULL) {
    		dev_err(&pdev->dev, "cannot reserve register region\n");
    		err = -ENOENT;
    		goto exit_error;
    	}
    
    	info->device     = &pdev->dev;
    	info->platform   = plat;
    	info->regs       = ioremap(res->start, size);	
    	info->cpu_type   = cpu_type;
    
    	if (info->regs == NULL) {
    		dev_err(&pdev->dev, "cannot reserve register region\n");
    		err = -EIO;
    		goto exit_error;
    	}
    
    	dev_dbg(&pdev->dev, "mapped registers at %p\n", info->regs);
    
    	/* initialise the hardware */
    
    	err = s3c2410_nand_inithw(info);	//初始化控制器
    	if (err != 0)
    		goto exit_error;
    
    	sets = (plat != NULL) ? plat->sets : NULL;
    	nr_sets = (plat != NULL) ? plat->nr_sets : 1;	//获取芯片集的个数,一般为1
    
    	info->mtd_count = nr_sets;
    
    	/* allocate our information */
    
    	size = nr_sets * sizeof(*info->mtds);
    	info->mtds = kzalloc(size, GFP_KERNEL);
    	if (info->mtds == NULL) {
    		dev_err(&pdev->dev, "failed to allocate mtd storage\n");
    		err = -ENOMEM;
    		goto exit_error;
    	}
    
    	/* initialise all possible chips */
    
    	nmtd = info->mtds;
    	//对每一个nand_set的处理,一般就执行一个循环
    	for (setno = 0; setno < nr_sets; setno++, nmtd++) {
    		pr_debug("initialising set %d (%p, info %p)\n", setno, nmtd, info);
    
    		s3c2410_nand_init_chip(info, nmtd, sets);	//设置nand_chip结构
    
    		nmtd->scan_res = nand_scan_ident(&nmtd->mtd,
    						 (sets) ? sets->nr_chips : 1,
    						 NULL);	//探测nand  flash 芯片
    
    		if (nmtd->scan_res == 0) {
    			s3c2410_nand_update_chip(info, nmtd);
    			nand_scan_tail(&nmtd->mtd);	//ecc设置及剩余驱动的设置
    			s3c2410_nand_add_partition(info, nmtd, sets);	//解析分区表,并调用add_mtd_partitions
    		}
    
    		if (sets != NULL)
    			sets++;
    	}
    
    	err = s3c2410_nand_cpufreq_register(info);
    	if (err < 0) {
    		dev_err(&pdev->dev, "failed to init cpufreq support\n");
    		goto exit_error;
    	}
    
    	if (allow_clk_suspend(info)) {
    		dev_info(&pdev->dev, "clock idle support enabled\n");
    		s3c2410_nand_clk_set_state(info, CLOCK_SUSPEND);
    	}
    
    	pr_debug("initialised ok\n");
    	return 0;
    
     exit_error:
    	s3c24xx_nand_remove(pdev);
    
    	if (err == 0)
    		err = -EINVAL;
    	return err;
    }
    
    接着看add_mtd_partitions函数

    //根据主分区和分区表信息来创建mtd_part结构
    int add_mtd_partitions(struct mtd_info *master,
    		       const struct mtd_partition *parts,
    		       int nbparts)
    {
    	struct mtd_part *slave;
    	uint64_t cur_offset = 0;
    	int i;
    
    	printk(KERN_NOTICE "Creating %d MTD partitions on \"%s\":\n", nbparts, master->name);
    
    	for (i = 0; i < nbparts; i++) {
    		slave = allocate_partition(master, parts + i, i, cur_offset);	//分配结构并初始化
    		if (IS_ERR(slave))
    			return PTR_ERR(slave);
    
    		mutex_lock(&mtd_partitions_mutex);
    		list_add(&slave->list, &mtd_partitions);	//添加到mtd_partitions链表中
    		mutex_unlock(&mtd_partitions_mutex);
    
    		add_mtd_device(&slave->mtd);	//添加分区mtd_info结构
    
    		cur_offset = slave->offset + slave->mtd.size;
    	}
    
    	return 0;
    }
    int add_mtd_device(struct mtd_info *mtd)
    {
    	struct mtd_notifier *not;
    	int i, error;
    
    	if (!mtd->backing_dev_info) {
    		switch (mtd->type) {
    		case MTD_RAM:
    			mtd->backing_dev_info = &mtd_bdi_rw_mappable;
    			break;
    		case MTD_ROM:
    			mtd->backing_dev_info = &mtd_bdi_ro_mappable;
    			break;
    		default:
    			mtd->backing_dev_info = &mtd_bdi_unmappable;
    			break;
    		}
    	}
    
    	BUG_ON(mtd->writesize == 0);
    	mutex_lock(&mtd_table_mutex);
    	//idr相关
    	do {
    		if (!idr_pre_get(&mtd_idr, GFP_KERNEL))
    			goto fail_locked;
    		error = idr_get_new(&mtd_idr, mtd, &i);
    	} while (error == -EAGAIN);
    
    	if (error)
    		goto fail_locked;
    
    	mtd->index = i;
    	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;
    	else
    		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, 0, mtd->size))
    			printk(KERN_WARNING
    			       "%s: unlock failed, writes may not work\n",
    			       mtd->name);
    	}
    
    	/* Caller should have set dev.parent to match the
    	 * physical device.
    	 */
    	mtd->dev.type = &mtd_devtype;
    	mtd->dev.class = &mtd_class;
    	mtd->dev.devt = MTD_DEVT(i);
    	dev_set_name(&mtd->dev, "mtd%d", i);
    	dev_set_drvdata(&mtd->dev, mtd);
    	if (device_register(&mtd->dev) != 0)	//触发不了什么驱动
    		goto fail_added;
    
    	if (MTD_DEVT(i))
    		device_create(&mtd_class, mtd->dev.parent,
    			      MTD_DEVT(i) + 1,
    			      NULL, "mtd%dro", i);
    
    	DEBUG(0, "mtd: Giving out device %d to %s\n", i, 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, &mtd_notifiers, list)
    		not->add(mtd);	//这个函数比较重要,通知链中的add回调接口函数
    
    	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;
    
    fail_added:
    	idr_remove(&mtd_idr, i);
    fail_locked:
    	mutex_unlock(&mtd_table_mutex);
    	return 1;
    }
    
    那么这个not->add(mtd)到底是神马函数呢?

    static int __init init_mtdblock(void)
    {
    	mutex_init(&mtdblks_lock);
    
    	return register_mtd_blktrans(&mtdblock_tr);
    }
    static struct mtd_notifier blktrans_notifier = {
    	.add = blktrans_notify_add,
    	.remove = blktrans_notify_remove,
    };
    
    int register_mtd_blktrans(struct mtd_blktrans_ops *tr)
    {
    	struct mtd_info *mtd;
    	int ret;
    
    	/* 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);	//终于找到注册的地方了
    
    
    	mutex_lock(&mtd_table_mutex);
    
    	ret = register_blkdev(tr->major, tr->name);
    	if (ret < 0) {
    		printk(KERN_WARNING "Unable to register %s block device on major %d: %d\n",
    		       tr->name, tr->major, ret);
    		mutex_unlock(&mtd_table_mutex);
    		return ret;
    	}
    
    	if (ret)
    		tr->major = ret;
    
    	tr->blkshift = ffs(tr->blksize) - 1;
    
    	INIT_LIST_HEAD(&tr->devs);
    	list_add(&tr->list, &blktrans_majors);
    
    	mtd_for_each_device(mtd)
    		if (mtd->type != MTD_ABSENT)
    			tr->add_mtd(tr, mtd);
    
    	mutex_unlock(&mtd_table_mutex);
    	return 0;
    }
    void register_mtd_user (struct mtd_notifier *new)
    {
    	struct mtd_info *mtd;
    
    	mutex_lock(&mtd_table_mutex);
    
    	list_add(&new->list, &mtd_notifiers);	//添加到mtd_notifiers
    
    	__module_get(THIS_MODULE);
    
    	mtd_for_each_device(mtd)
    		new->add(mtd);
    
    	mutex_unlock(&mtd_table_mutex);
    }
    初始化的流程牵涉的数据结构多,每一层之间的关系也比较复杂,所以搞清楚初始化流程对了解执行流程是比较有帮助的。接着看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);
    }
    又遇到一个数据结构mtd_blktrans_ops,是mtd传输层的结构,直接看它的add_mtd函数
    static struct mtd_blktrans_ops mtdblock_tr = {
    	.name		= "mtdblock",
    	.major		= 31,
    	.part_bits	= 0,
    	.blksize 	= 512,
    	.open		= mtdblock_open,
    	.flush		= mtdblock_flush,
    	.release	= mtdblock_release,
    	.readsect	= mtdblock_readsect,
    	.writesect	= mtdblock_writesect,
    	.add_mtd	= mtdblock_add_mtd,
    	.remove_dev	= mtdblock_remove_dev,
    	.owner		= THIS_MODULE,
    };
    static void mtdblock_add_mtd(struct mtd_blktrans_ops *tr, struct mtd_info *mtd)
    {
    	//分配mtdblk_dev结构,此结构中嵌套了mtd_blktrans_dev结构
    	struct mtdblk_dev *dev = kzalloc(sizeof(*dev), GFP_KERNEL);	
    
    	if (!dev)
    		return;
    
    	dev->mbd.mtd = mtd;
    	dev->mbd.devnum = mtd->index;
    
    	dev->mbd.size = mtd->size >> 9;
    	dev->mbd.tr = tr;
    
    	if (!(mtd->flags & MTD_WRITEABLE))
    		dev->mbd.readonly = 1;
    
    	if (add_mtd_blktrans_dev(&dev->mbd))	//添加mtd_blktrans_dev结构,此函数也比较重要
    		kfree(dev);
    }
    
    add_mtd_blktrans_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;
    	int ret;
    
    	if (mutex_trylock(&mtd_table_mutex)) {
    		mutex_unlock(&mtd_table_mutex);
    		BUG();
    	}
    
    	mutex_lock(&blktrans_ref_mutex);
    	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 */
    			mutex_unlock(&blktrans_ref_mutex);
    			return -EBUSY;
    		} else if (d->devnum > new->devnum) {
    			/* Required number was free */
    			list_add_tail(&new->list, &d->list);
    			goto added;
    		}
    		last_devnum = d->devnum;
    	}
    
    	ret = -EBUSY;
    	if (new->devnum == -1)
    		new->devnum = last_devnum+1;
    
    	/* Check that the device and any partitions will get valid
    	 * minor numbers and that the disk naming code below can cope
    	 * with this number. */
    	if (new->devnum > (MINORMASK >> tr->part_bits) ||
    	    (tr->part_bits && new->devnum >= 27 * 26)) {
    		mutex_unlock(&blktrans_ref_mutex);
    		goto error1;
    	}
    
    	list_add_tail(&new->list, &tr->devs);
     added:
    	mutex_unlock(&blktrans_ref_mutex);
    
    	mutex_init(&new->lock);
    	kref_init(&new->ref);
    	if (!tr->writesect)
    		new->readonly = 1;
    
    	/* Create gendisk */
    	ret = -ENOMEM;
    	gd = alloc_disk(1 << tr->part_bits);	//分配gendisk结构
    
    	if (!gd)
    		goto error2;
    
    	new->disk = gd;
    	gd->private_data = new;
    	gd->major = tr->major;
    	gd->first_minor = (new->devnum) << tr->part_bits;
    	gd->fops = &mtd_blktrans_ops;
    
    	if (tr->part_bits)
    		if (new->devnum < 26)
    			snprintf(gd->disk_name, sizeof(gd->disk_name),
    				 "%s%c", tr->name, 'a' + new->devnum);
    		else
    			snprintf(gd->disk_name, sizeof(gd->disk_name),
    				 "%s%c%c", tr->name,
    				 'a' - 1 + new->devnum / 26,
    				 'a' + new->devnum % 26);
    	else
    		snprintf(gd->disk_name, sizeof(gd->disk_name),
    			 "%s%d", tr->name, new->devnum);
    
    	set_capacity(gd, (new->size * tr->blksize) >> 9);
    
    	/* Create the request queue */
    	spin_lock_init(&new->queue_lock);
    	new->rq = blk_init_queue(mtd_blktrans_request, &new->queue_lock);	//初始化request_queue结构
    
    	if (!new->rq)
    		goto error3;
    
    	new->rq->queuedata = new;
    	blk_queue_logical_block_size(new->rq, tr->blksize);
    
    	if (tr->discard) {
    		queue_flag_set_unlocked(QUEUE_FLAG_DISCARD, new->rq);
    		new->rq->limits.max_discard_sectors = UINT_MAX;
    	}
    
    	gd->queue = new->rq;
    
    	/* Create processing thread */
    	/* TODO: workqueue ? */
    	//创建mtdblockx内核线程,x代表分区号
    	new->thread = kthread_run(mtd_blktrans_thread, new,
    			"%s%d", tr->name, new->mtd->index);
    	if (IS_ERR(new->thread)) {
    		ret = PTR_ERR(new->thread);
    		goto error4;
    	}
    	gd->driverfs_dev = &new->mtd->dev;
    
    	if (new->readonly)
    		set_disk_ro(gd, 1);
    
    	add_disk(gd);	//添加gendisk
    
    	if (new->disk_attributes) {
    		ret = sysfs_create_group(&disk_to_dev(gd)->kobj,
    					new->disk_attributes);
    		WARN_ON(ret);
    	}
    	return 0;
    error4:
    	blk_cleanup_queue(new->rq);
    error3:
    	put_disk(new->disk);
    error2:
    	list_del(&new->list);
    error1:
    	return ret;
    }
    从nand驱动层到mtd原始设备层,再到mtd blcok层,再到block层,终于初始化完毕。

    2. 读写过程

    我们知道block io层要读写一个块时,最后要调用request_queue的reques_fn函数,而这个函数对于mcc 块设备则为mtd_blktrans_request

    static void mtd_blktrans_request(struct request_queue *rq)
    {
    	struct mtd_blktrans_dev *dev;
    	struct request *req = NULL;
    
    	dev = rq->queuedata;
    
    	if (!dev)
    		while ((req = blk_fetch_request(rq)) != NULL)
    			__blk_end_request_all(req, -ENODEV);
    	else {
    		dev->bg_stop = true;
    		wake_up_process(dev->thread);	//唤醒mtdblock内核线程
    	}
    }
    static int mtd_blktrans_thread(void *arg)
    {
    	struct mtd_blktrans_dev *dev = arg;
    	struct mtd_blktrans_ops *tr = dev->tr;
    	struct request_queue *rq = dev->rq;
    	struct request *req = NULL;
    	int background_done = 0;
    
    	spin_lock_irq(rq->queue_lock);
    
    	while (!kthread_should_stop()) {
    		int res;
    
    		dev->bg_stop = false;
    		if (!req && !(req = blk_fetch_request(rq))) {	//取出一个请求
    			if (tr->background && !background_done) {
    				spin_unlock_irq(rq->queue_lock);
    				mutex_lock(&dev->lock);
    				tr->background(dev);
    				mutex_unlock(&dev->lock);
    				spin_lock_irq(rq->queue_lock);
    				/*
    				 * Do background processing just once per idle
    				 * period.
    				 */
    				background_done = !dev->bg_stop;
    				continue;
    			}
    			set_current_state(TASK_INTERRUPTIBLE);
    
    			if (kthread_should_stop())
    				set_current_state(TASK_RUNNING);
    
    			spin_unlock_irq(rq->queue_lock);
    			schedule();
    			spin_lock_irq(rq->queue_lock);
    			continue;
    		}
    
    		spin_unlock_irq(rq->queue_lock);
    
    		mutex_lock(&dev->lock);
    		res = do_blktrans_request(dev->tr, dev, req);	//处理请求
    		mutex_unlock(&dev->lock);
    
    		spin_lock_irq(rq->queue_lock);
    
    		if (!__blk_end_request_cur(req, res))
    			req = NULL;
    
    		background_done = 0;
    	}
    
    	if (req)
    		__blk_end_request_all(req, -EIO);
    
    	spin_unlock_irq(rq->queue_lock);
    
    	return 0;
    }
    static int do_blktrans_request(struct mtd_blktrans_ops *tr,
    			       struct mtd_blktrans_dev *dev,
    			       struct request *req)
    {
    	unsigned long block, nsect;
    	char *buf;
    
    	block = blk_rq_pos(req) << 9 >> tr->blkshift;
    	nsect = blk_rq_cur_bytes(req) >> tr->blkshift;
    
    	buf = req->buffer;
    
    	if (req->cmd_type != REQ_TYPE_FS)
    		return -EIO;
    
    	if (blk_rq_pos(req) + blk_rq_cur_sectors(req) >
    	    get_capacity(req->rq_disk))
    		return -EIO;
    
    	if (req->cmd_flags & REQ_DISCARD)
    		return tr->discard(dev, block, nsect);
    
    	switch(rq_data_dir(req)) {
    	case READ:
    		for (; nsect > 0; nsect--, block++, buf += tr->blksize)
    			if (tr->readsect(dev, block, buf))	//调用mtdblock_readsect
    				return -EIO;
    		rq_flush_dcache_pages(req);
    		return 0;
    	case WRITE:
    		if (!tr->writesect)
    			return -EIO;
    
    		rq_flush_dcache_pages(req);
    		for (; nsect > 0; nsect--, block++, buf += tr->blksize)
    			if (tr->writesect(dev, block, buf))	//调用mtdblock_writesect
    				return -EIO;
    		return 0;
    	default:
    		printk(KERN_NOTICE "Unknown request %u\n", rq_data_dir(req));
    		return -EIO;
    	}
    }
    static int mtdblock_readsect(struct mtd_blktrans_dev *dev,
    			      unsigned long block, char *buf)
    {
    	struct mtdblk_dev *mtdblk = container_of(dev, struct mtdblk_dev, mbd);
    	return do_cached_read(mtdblk, block<<9, 512, buf);
    }
    static int do_cached_read (struct mtdblk_dev *mtdblk, unsigned long pos,
    			   int len, char *buf)
    {
    	struct mtd_info *mtd = mtdblk->mbd.mtd;
    	unsigned int sect_size = mtdblk->cache_size;
    	size_t retlen;
    	int ret;
    
    	DEBUG(MTD_DEBUG_LEVEL2, "mtdblock: read on \"%s\" at 0x%lx, size 0x%x\n",
    			mtd->name, pos, len);
    
    	if (!sect_size)
    		return mtd->read(mtd, pos, len, &retlen, buf);	//对应nand_read,在nand_scan_tail被赋值
    
    	while (len > 0) {
    		unsigned long sect_start = (pos/sect_size)*sect_size;
    		unsigned int offset = pos - sect_start;
    		unsigned int size = sect_size - offset;
    		if (size > len)
    			size = len;
    
    		/*
    		 * Check if the requested data is already cached
    		 * Read the requested amount of data from our internal cache if it
    		 * contains what we want, otherwise we read the data directly
    		 * from flash.
    		 */
    		if (mtdblk->cache_state != STATE_EMPTY &&
    		    mtdblk->cache_offset == sect_start) {
    			memcpy (buf, mtdblk->cache_data + offset, size);
    		} else {
    			ret = mtd->read(mtd, pos, size, &retlen, buf);
    			if (ret)
    				return ret;
    			if (retlen != size)
    				return -EIO;
    		}
    
    		buf += size;
    		pos += size;
    		len -= size;
    	}
    
    	return 0;
    }
    static int nand_read(struct mtd_info *mtd, loff_t from, size_t len,
    		     size_t *retlen, uint8_t *buf)
    {
    	struct nand_chip *chip = mtd->priv;
    	int ret;
    
    	/* Do not allow reads past end of device */
    	if ((from + len) > mtd->size)
    		return -EINVAL;
    	if (!len)
    		return 0;
    
    	nand_get_device(chip, mtd, FL_READING);
    
    	chip->ops.len = len;
    	chip->ops.datbuf = buf;
    	chip->ops.oobbuf = NULL;
    
    	ret = nand_do_read_ops(mtd, from, &chip->ops);
    
    	*retlen = chip->ops.retlen;
    
    	nand_release_device(mtd);
    
    	return ret;
    }
    
    nand_do_read_ops函数比较长,就不贴了,主要调用了chip->ecc.read_page_raw(mtd, chip, bufpoi, page),对应于nand_read_page_raw

    static int nand_read_page_raw(struct mtd_info *mtd, struct nand_chip *chip,
    			      uint8_t *buf, int page)
    {
    	chip->read_buf(mtd, buf, mtd->writesize);
    	chip->read_buf(mtd, chip->oob_poi, mtd->oobsize);
    	return 0;
    }
    chip->read_buf(mtd, buf, mtd->writesize)对应于s3c2410_nand_read_buf。

    static void s3c2410_nand_read_buf(struct mtd_info *mtd, u_char *buf, int len)
    {
    	struct nand_chip *this = mtd->priv;
    	readsb(this->IO_ADDR_R, buf, len);
    }
    终于写完了。

    展开全文
  • LINUX MTD 驱动

    2012-06-21 16:52:39
    MTD 的主要目的是为了使新的memory 设备的驱动更加简单,为此它在硬件和上层之间提供了一个抽象的接口。 MTD 的所有源代码在/drivers/mtd 子目录下。 概念: CFI: Common Flash Interface
    MTD(memory technology device 内存技术设备)是用于访问memory 设备(ROM、flash)的Linux的子系统。
    
    MTD 的主要目的是为了使新的memory 设备的驱动更加简单,为此它在硬件和上层之间提供了一个抽象的接口。
    MTD 的所有源代码在/drivers/mtd 子目录下。

    概念:

    CFI:    Common Flash Interface,通用Flash 接口,Intel 发起的一个Flash 的接口标准
    OOB:     out of band,某些内存技术支持out-of-band 数据—--如NAND 每512字节的块有16 个字节的extra data,用于纠错或元数据。
    ECC:     error correction,某些硬件不仅允许对flash 的访问,也有ecc 功能,所有flash器件都受位交换现象的困扰。在某些情况下,一个比特位会发生反转或被报告反转了,就要采用ECC 算法。
    erasesize:     一个 erase 命令可以擦除的最小块的尺寸
    buswidth:    MTD 设备的接口总线宽度
    interleave:    交错数,几块芯片平行连接成一块,使 buswidth 变大!!!
    devicetype:    芯片类型,x8、x16 或者x32
    Wear out:    Flash 的擦除次数有限制,一般在100!!!


    mtd_info结构是MTD原始设备层的一个重要结构,该结构定义了大量的关于MTD的数据和操作,定义在include/linux/mtd/mtd.h头文件。mtd_info结构成员主要由数据成员和操作函数两部分组成。


    struct mtd_part {
        struct mtd_info mtd;
        struct mtd_info *master;
        uint64_t offset;
        struct list_head list;
    };







    总结
    两种注册方式:
    1)直接注册整个flash设备(MTD Device)到MTD。
        ret = add_mtd_device(mtd);

    2)分partion添加到mtd_table,并将每个partion当成一个mtd设备注册到MTD。
        if (!(partitions && num_part > 0) )
            ret = add_mtd_partitions(mtd, parts, num_part);



    ----------------------------------------------------------------------------

    mtd_read:
    直接直接调用mtd_info 的read 函数,因此,字符设备接口跳过了patition 这一层。
    当count>0 时{
        裁减本次操作大小len 至min(MAX_KMALLOC_SIZE,count),
        申请一块大小为MAX_KMALLOC_SIZE 的内核空间kbuf,
        调用mtd_info->read 将MTD 设备中的数据读入kbuf,
        将kbuf 中的数据拷贝到用户空间buf,
        count 自减
        释放kbuf
    }

    ----------------------------------------------------------------------------

    Mtd_write
    mtd_write 直接直接调用mtd_info 的write 函数,因此,字符设备接口跳过了patition 这一层。
    当count>0 时{
        裁减本次操作大小len 至min(MAX_KMALLOC_SIZE,count),
        申请一块大小为MAX_KMALLOC_SIZE 的内核空间kbuf,
        将用户空间buf 中的数据拷贝到kbuf,
        调用mtd_info->write 将kbuf 中的数据读入MTD 设备,
        count 自减
        释放kbuf
    }

    主要原理是将Flash 的erase block 中的数据在内存中建立映射,然后对其进行修改,最后擦除Flash 上的block,将内存中的映射块写入Flash 块。整个过程被称为read/modify/erase/rewrite 周期。但是,这样做是不安全的,当下列操作序列发生时,read/modify/erase/poweroff,就会丢失这个block 块的数据。块设备模拟驱动按照block 号和偏移量来定位文件,因此在Flash 上除了文件数据,基本没有额外的控制数据。

    ----------------------------------------------------------------------------


    Linux内核在MTD的下层实现了通用的NAND 驱动( 主要通过drivers/mtd/nand/nand_base.c 文件实现),因此芯片级的NAND 驱动不再需要实现mtd_info中的read()、write()、read_oob()、write_oob()等成员函数,而主体转移到了nand_chip数据结构

    ----------------------------------------------------------------------------


    1. 如果Flash要分区,
        则定义mtd_partition数组,将实际电路板中Flash分区信息记录于其中。
    2. 在模块加载时分配和nand_chip的内存,根据目标板NAND 控制器的特殊情况初始化nand_chip 中的hwcontrol()、dev_ready()、calculate_ecc()、correct_data()、read_byte()、write_byte()等成员函数(如果不赋值会使用nand_base.c中的默认函数),注意将mtd_info的priv置为nand_chip。
    3. 以mtd_info为参数调用nand_scan()函数探测NAND Flash的存在。
    4. 如果要分区,
        则以mtd_info和mtd_partition为参数调用add_mtd_partitions()添加分区信息。

    ----------------------------------------------------------------------------


    gpmi_nfc_probe
        gpmi_nfc_mil_init
        初始化nandchip
            nand->cmd_ctrl     = mil_cmd_ctrl;
            nand->dev_ready   = mil_dev_ready;
            nand->select_chip = mil_select_chip;
            nand->read_byte = mil_read_byte;
            nand->read_buf  = mil_read_buf;
            nand->write_buf = mil_write_buf;
            ……
        nand_scan探测
        mil_partitions_init
            add_mtd_device/add_mtd_partitions


    ----------------------------------------------------------------------------





    754     if (mtd_has_cmdlinepart()) {
    755         static const char *probes[] __initconst = {
    756             "cmdlinepart", NULL
    757         };
    758
    759         mtd_parts_nb = parse_mtd_partitions(&info->mtd, probes,
    760                             &mtd_parts, 0);        //-->aa
    761     }
    762
    763     if (mtd_parts_nb <= 0) {
    764         mtd_parts = pdata->parts;
    765         mtd_parts_nb = pdata->nr_parts;
    766     }
    767
    768     /* Register any partitions */
    769     if (mtd_parts_nb > 0) {
    770         ret = mtd_device_register(&info->mtd, mtd_parts,
    771                       mtd_parts_nb);
    772         if (ret == 0)
    773             info->partitioned = true;
    774     }




    -->aa

    715 int parse_mtd_partitions(struct mtd_info *master, const char **types,
    716              struct mtd_partition **pparts, unsigned long origin)
    717 {
    718     struct mtd_part_parser *parser;
    719     int ret = 0;
    720         
    721     for ( ; ret <= 0 && *types; types++) {
    722         parser = get_partition_parser(*types);
    723         if (!parser && !request_module("%s", *types))
    724                 parser = get_partition_parser(*types);    //-->bb
    725         if (!parser)
    726             continue;
    727         ret = (*parser->parse_fn)(master, pparts, origin);
    728         if (ret > 0) {
    729             printk(KERN_NOTICE "%d %s partitions found on MTD device %s\n",
    730                    ret, parser->name, master->name);
    731         }
    732         put_partition_parser(parser);
    733     }
    734     return ret;
    735 }



    -->bb
    获得该名字的分析器
    679 static struct mtd_part_parser *get_partition_parser(const char *name)    //"cmdlinepart"
    680 {
    681     struct mtd_part_parser *p, *ret = NULL;
    682
    683     spin_lock(&part_parser_lock);
    684
    685     list_for_each_entry(p, &part_parsers, list)    //在链表上找到匹配的
    686         if (!strcmp(p->name, name) && try_module_get(p->owner)) {
    687             ret = p;
    688             break;
    689         }
    690
    691     spin_unlock(&part_parser_lock);
    692
    693     return ret;
    694 }




    -- drivers/mtd/cmdlinepart.c --

    380 static struct mtd_part_parser cmdline_parser = {
    381     .owner = THIS_MODULE,
    382     .parse_fn = parse_cmdline_partitions,
    383     .name = "cmdlinepart",
    384 };
    展开全文
  • linux下MTD驱动

    千次阅读 2016-02-16 21:58:02
    因为之前不是很了解文件系统,所以要探究一下mtd来加深一下印象。

    struct device中的platform_data是我们自己在编写驱动程序的时候自己定义的设备结构体的指针。  

    因为之前不是很了解文件系统,所以要探究一下mtd来加深一下印象。

    NAND FLASH每一页大小通常是512+16字节,16字节称为OOB区,通常在OOB区存放坏块标记,前面512字节的ECC校验码等。

    FLASH的内部存储时MOSFET(金属半场效应管),里面有悬浮门,是真正存储数据的单元。

    块是NandFlash的擦除操作的基本单元。

    页是NandFlash的写入操作的基本单元。

    在写数据之前,要先擦除,然后再写。

    在加载完驱动之后,如果没有加入参数要求跳过坏块扫描的话,会主动扫描坏块,建立必要的BBT,以备后面坏块管理使用。

    在一个块内,对每一页进行编程的话必须是顺序的,不能是随机的。

    通常读取Nor速度比Nand稍快一些,而Nand写入速度快很多,NorFlash特点是芯片内执行(XIP execute in place),不需要初始化CPU取址模块就能直接从中把指令取出来。

    NandFlash器件使用复杂的I/O来串行存取数据,8个引脚用来传送控制地址和数据信息。


    MTD:memory technology device内存技术设备

    MTD将文件系统与底层FLASH存储器进行了隔离

    设备节点->MTD设备层->MTD原始设备层->硬件驱动层

    MTD块设备(主设备号31) 字符设备(主设备号90)

      spi_write_then_read();函数中,先分配spi_transfer 两个内存空间,一个用于写,一个用于读,然后调用spi_message_add_tail(),将spi_transfer的地址分别放入spi_message中,之后填充spi_transfer中的tx_buf和rx_buf,最后调用spi_sync();做i/o操作。该函数是__spi_async()的封装,里面最后还是调用了spi_master->transfer()之前spi驱动注册的回调函数。

    如果是spi flash,对flash的操作就是对spi的读写操作。

    在xxx_flash_probe()中主要还是对mtd_info结构体(MTD原始设备)的参数赋值,和回调函数的注册。

             flash->mtd.type = MTD_NORFLASH;
             flash->mtd.writesize = 1;
             flash->mtd.flags = MTD_CAP_NORFLASH;
             flash->mtd.size = info->sector_size * info->n_sectors;
             flash->mtd.erase = m25p80_erase;
             flash->mtd.read = m25p80_read;
     
             /* sst flash chips use AAI word program */
             if (JEDEC_MFR(info->jedec_id) == CFI_MFR_SST)
                     flash->mtd.write = sst_write;
             else
                     flash->mtd.write = m25p80_write;       
            flash->mtd.dev.parent = &spi->dev;       
            flash->sector_size = info->sector_size;
            flash->page_size = info->page_size;

    在每次发送命令之前都需要进行写使能,然后发送命令的内容,最后再加上地址。

    struct spi_transfer t[2];
    struct spi_message m;        
    t.tx_buf = flash->command;
    t.len = m25p_cmdsz(flash) + FAST_READ_DUMMY_BYTE;
    spi_message_add_tail(&t, &m);      
    t.rx_buf = buf;t.len = len;
    spi_message_add_tail(&t, &m);
    flash->command = OPCODE_READ;
    flash->command = WR_CMD_TYPE;
    spi_sync(flash->spi, &m);

    最后调用mtd_device_register().如果没有分区的话就调用add_mtd_partition(),否则就调用add_mtd_partitions().

    这里有一个概念的区别:struct mtd_part和struct mtd_partition.mtd_partition就是每个子分区的分区信息,mtd_part是指一个分区的实例。真正主分区的信息还是由mtd_info获得.

    分配一个分区是函数allocate_partition().

      struct mtd_partition {
              char *name;                     /* identifier string */
              uint64_t size;                  /* partition size */
              uint64_t offset;                /* offset within the master MTD space */
              uint32_t mask_flags;            /* master MTD flags to mask out for this partition */
              struct nand_ecclayout *ecclayout;       /* out of band layout for this partition (NAND only) */
      }; 
      struct mtd_part {
              struct mtd_info mtd;
              struct mtd_info *master;
              uint64_t offset;
              struct list_head list;
      };

    在该函数中主要是分配mtd_part内存空间和参数设置,包括一些回调函数的注册,但是这些回调函数最终还是通过指针调用一开始xxx_flash_probe注册的回调函数,当然还有一些sanity checks。

      /* 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.writebufsize = master->writebufsize;
    slave->mtd.oobsize = master->oobsize;
    slave->mtd.oobavail = master->oobavail;
    slave->mtd.subpage_sft = master->subpage_sft; 
    slave->mtd.name = name;
    slave->mtd.owner = master->owner;
    slave->mtd.backing_dev_info = master->backing_dev_info;     
    slave->mtd.dev.parent = master->dev.parent;
    slave->mtd.read = part_read;
    slave->mtd.write = part_write;

    分配成功后将返回的std_part添加到mtd_partitions中去。

    最后调用add_mtd_device(),注册一个mtd设备。add_mtd_device中会遍历的调用通知链mtd_notifiers中的所有注册函数。

    在mtdchar.c中init_mtdchar();中相比一般字符设备注册地不同在于文件系统的注册register_filesystem,内核挂载kern_mount(),最终调用了vfs_kern_mount();

    在该函数中,分配vfsmnt结构体,并且挂载文件系统mount_fs();它最终调用了file_system_type原本就已经注册号的mount回调函数。

     static struct file_system_type mtd_inodefs_type = {
            .name = "mtd_inodefs",
            .mount = mtd_inodefs_mount,
            .kill_sb = kill_anon_super,
     };
    最终mtd_inodefs_mount();还是调用了mount_pseudo();//这是一个通用的挂载文件系统的函数(但是sockfs,pipefs,bdev是不能挂载的).

    该函数主要是进行参数的赋值,关键的是三个数据结构struct super_block, struct inode,  struct dentry;

             s->s_flags = MS_NOUSER;
             s->s_maxbytes = MAX_LFS_FILESIZE;
             s->s_blocksize = PAGE_SIZE;
             s->s_blocksize_bits = PAGE_SHIFT;
             s->s_magic = magic;
             s->s_op = ops ? ops : &simple_super_operations;//以便之后new_inode()调用部分回调函数
             s->s_time_gran = 1; 
             root = new_inode(s);
             if (!root)
                     goto Enomem;
             /*
              * since this is the first inode, make it number 1. New inodes created
              * after this must take care not to collide with it (by passing
              * max_reserved of 1 to iunique).
              */
             root->i_ino = 1;
             root->i_mode = S_IFDIR | S_IRUSR | S_IWUSR;
             root->i_atime = root->i_mtime = root->i_ctime = CURRENT_TIME;
             dentry = d_alloc(NULL, &d_name);
             if (!dentry) {
                     iput(root);
                     goto Enomem;
             }
             dentry->d_sb = s;
             dentry->d_parent = dentry;
             d_instantiate(dentry, root);//关键是该函数,将inode信息填充到目录入口中。
             s->s_root = dentry;
             s->s_d_op = dops;
             s->s_flags |= MS_ACTIVE;
             return dget(s->s_root);
    值得注意的是,几个file_operation还是要好好研究一下的

     static const struct file_operations mtd_fops = {
             .owner          = THIS_MODULE,
             .llseek         = mtd_lseek,
             .read           = mtd_read,
             .write          = mtd_write,
             .unlocked_ioctl = mtd_unlocked_ioctl,
     #ifdef CONFIG_COMPAT
             .compat_ioctl   = mtd_compat_ioctl,
     #endif
             .open           = mtd_open,
             .release        = mtd_close,
             .mmap           = mtd_mmap,
     #ifndef CONFIG_MMU
             .get_unmapped_area = mtd_get_unmapped_area,
     #endif
     };
    在/mtd/mtdblock.c中初始化函数中调用了register_mtd_blktrans();最终还是调用了register_blkev();注册块设备。

     static struct mtd_blktrans_ops mtdblock_tr = {
             .name           = "mtdblock",
             .major          = 31,
             .part_bits      = 0,
             .blksize        = 512,
             .open           = mtdblock_open,
             .flush          = mtdblock_flush,
             .release        = mtdblock_release,
             .readsect       = mtdblock_readsect,
             .writesect      = mtdblock_writesect,
             .add_mtd        = mtdblock_add_mtd,
             .remove_dev     = mtdblock_remove_dev,
             .owner          = THIS_MODULE,
     }; 

    在在最后调用回调函数mtd_blktrans_ops->add_mtd();在该回调函数中调用了add_mtd_blktrans_dev();

    调用alloc_disk();

    block_device_operation:

     static const struct block_device_operations mtd_blktrans_ops = {
             .owner          = THIS_MODULE,
             .open           = blktrans_open,
             .release        = blktrans_release,
             .ioctl          = blktrans_ioctl,
             .getgeo         = blktrans_getgeo,
     }; 
    当给磁盘分区,在分区上创建文件系统,运行文件系统检查程序,或装载一个分区时,块设备驱动程序会调用open函数。

    内核认为每个磁盘都是由512字节大小的扇区所组成的线性数组。所有的I/O请求都将定位在硬件扇区的开始位置,并且每个请求的大小都将是扇区大小的整数倍

    set_capacity();//设定扇区大小.

    struct request代表了一个块设备的I/O执行请求.请求被表示为一系列段,每个段都对应内存中的一个缓冲区。如果多个请求都是对磁盘中相邻扇区进行操作,则内核将合并它们,但是内核不会合并在单独request结构中的读写操作。

    从本质上讲,一个request结构是作为一个bio结构的链表实现的,bio结构是在底层对部分块设备I/O请求的描述。

    bio结构的核心是bi_io_vec的数组

     struct bio_vec {
              struct page     *bv_page;
              unsigned int    bv_len;
              unsigned int    bv_offset;
     };
    当快设置I/O请求被转换到bio结构后,它将被单独的物理内存页所销毁。驱动程序做的所有工作就是根据这个结构体数组,使用每页传输数据。

    blk_init_queue();分配请求队列,传入请求参数mtd_blktrans_request,负责块设备的读写请求(本质还是唤醒之后创建的内核线程wake_up_process())。当请求队列生成的时候,request函数就与该队列绑定在一起。

    在分配好请求队列后,要创建一个内核线程mtd_blktrans_thread();

    在该线程中首先通过blk_fetch_request来获得请求队列中的请求(最终还是调用__elv_next_request()来获取队列中第一个未完成的请求).

    接下来最核心的还是调用do_blktrans_request() 来处理mtd块设备的请求(通过switch来执行读或者写请求)。读写操作都是调用之前注册号的回调函数(最终调用的是driver/mtd/devices/xxx_flash.c中之前已经注册好的与硬件相关的读写函数),同时读写成功之后就调用rq_flush_dcache_pages().

    内核使用gendisk结构来表示一个独立的磁盘设备。一旦调用了add_disk,磁盘设备将被集火,并随时会调用它提供的方法。因此在驱动程序完全被初始化并且能够响应对磁盘的请求钱,不要调用add_disk.

    对磁盘操作最昂贵的代价总是确定读写数据开始的位置,一旦位置确定了之后,实际用于读取或者写入数据的时间几乎无关紧要的。

    展开全文
  • 注:MTD驱动代码我只看过大概,大体上知道流程是怎样走,具体的分析只局限于Flash硬件驱动层(具体flash芯片)和MTD设备层(file operation系列函数)。   介绍下环境及工具:  VMWare Station + Debian + ...
    注:MTD驱动代码我只看过大概,大体上知道流程是怎样走,具体的分析只局限于Flash硬件驱动层(具体flash芯片)和MTD设备层(file operation系列函数)。
     
    介绍下环境及工具:
       VMWare Station + Debian + samba + nfs + Source Insignt
       linux-source-2.6.18 + xxxx.patch
     
    问题描述:
       fd = open("/dev/mtd4", O_RDWR)失败,而fd = open("/dev/mtd4", O_RDONLY)成功
     
    开始之前,找了一些有关MTD设备的资料来看,有代表性有Jim Zeus的《Linux MTD源代码分析》。我没有很深入去研读,主要是去了解Mtd代码的层结构及几个重要的数据结构,毕竟我的主要任务是将mtd分区读写起来,过于探讨细节对于我来说是一个奢求。

      MTD(memory technology device内存技术设备)是用于访问memory设备(ROM、flash)的Linux的子系统。MTD的主要目的是为了使新的memory设备的驱动更加简单,为此它在硬件和上层之间提供了一个抽象的接口。MTD的所有源代码在/drivers/mtd子目录下。我将CFI接口的MTD设备分为四层(从设备节点直到底层硬件驱动),这四层从上到下依次是:设备节点、MTD设备层、MTD原始设备层和硬件驱动层。

      一、Flash硬件驱动层:硬件驱动层负责在init时驱动Flash硬件,Linux MTD设备的NOR Flash芯片驱动遵循CFI接口标准,其驱动程序位于drivers/mtd/chips子目录下。NAND型Flash的驱动程序则位于/drivers/mtd/nand子目录下

      二、MTD原始设备:原始设备层有两部分组成,一部分是MTD原始设备的通用代码,另一部分是各个特定的Flash的数据,例如分区。 用于描述MTD原始设备的数据结构是mtd_info,这其中定义了大量的关于MTD的数据和操作函数。mtd_table(mtdcore.c)则是所有MTD原始设备的列表,mtd_part(mtd_part.c)是用于表示MTD原始设备分区的结构,其中包含了mtd_info,因为每一个分区都是被看成一个MTD原始设备加在mtd_table中的,mtd_part.mtd_info中的大部分数据都从该分区的主分区mtd_part->master中获得。 在drivers/mtd/maps/子目录下存放的是特定的flash的数据,每一个文件都描述了一块板子上的flash。其中调用add_mtd_device()、del_mtd_device()建立/删除mtd_info结构并将其加入/删除mtd_table(或者调用add_mtd_partition()、del_mtd_partition()(mtdpart.c)建立/删除mtd_part结构并将mtd_part.mtd_info加入/删除mtd_table 中)。

      三、MTD设备层:基于MTD原始设备,linux系统可以定义出MTD的块设备(主设备号31)和字符设备(设备号90)。MTD字符设备的定义在mtdchar.c中实现,通过注册一系列file operation函数(lseek、open、close、read、write)。MTD块设备则是定义了一个描述MTD块设备的结构mtdblk_dev,并声明了一个名为mtdblks的指针数组,这数组中的每一个mtdblk_dev和mtd_table中的每一个mtd_info一一对应。

      四、设备节点:通过mknod在/dev子目录下建立MTD字符设备节点(主设备号为90)和MTD块设备节点(主设备号为31),通过访问此设备节点即可访问MTD字符设备和块设备。

      五、根文件系统:在Bootloader中将JFFS(或JFFS2)的文件系统映像jffs.image(或jffs2.img)烧到flash的某一个分区中,在/arch/arm/mach-your/arch.c文件的your_fixup函数中将该分区作为根文件系统挂载。

      六、文件系统:内核启动后,通过mount 命令可以将flash中的其余分区作为文件系统挂载到mountpoint上。

    另外我还了解一下cfi的驱动流程。如下:

      1:构造map_info结构,指定基址/位宽/大小等信息以及"cfi_probe"限定,然后调用do_map_probe()。

      2:do_map_probe()根据名字"cfi_probe"找到芯片驱动"cfi_probe.c"直接调用cfi_probe()。  

      3:cfi_probe()直接调用mtd_do_chip_probe(),传入cfi_probe_chip()函数指针。   

      4:mtd_do_chip_probe()分2步,先调用genprobe_ident_chips()探测芯片信息,后调用check_cmd_set()获取和初始化芯片命令集(多分区初始化就在里面)。   

      5:genprobe_ident_chips()函数如果不考虑多芯片串连的情况,那只需看前面的genprobe_new_chip()调用,完成后cfi.chipshift=cfi.cfiq->DevSize,2^chipshift=FLASH大小。   

      6:genprobe_new_chip()枚举各种不同的芯片位宽和背靠背数量,结合配置设定依次调用步骤3的cfi_probe_chip(),注意cfi->device_type=bankwidth/nr_chips,bankwidth是总线位宽,device_type是芯片位宽。这里我们只需要注意有限复杂情况即可,所谓有限复杂指的是编译时确定的复杂连接。这样,cfi_probe_chip()只有第1次调用才成功,如果考虑32位宽的FLASH插在16bit总线上的情况,那第2次调用成功。   

      7:cfi_probe_chip(),由于步骤6的原因,函数就在cfi_chip_setup()直接返回,后面的代码就不用考虑了。  

      8:cfi_chip_setup()读取CFI信息,可以留意下Linux是怎么实现要点4的。   

      9:回到步骤4的check_cmd_set()阶段,进入cfi_cmdset_0001()函数,先调用read_pri_intelext()读取Intel的扩展信息,然后调用cfi_intelext_setup()初始化自身结构。   

      10:read_pri_intelext()函数,可以留意下怎么读取变长结构的技巧,也就是"need_more"的用法。这里说明下一些变量的含义,例如对于StrataFlash 128Mb Bottom类型的的FLASH芯片,块结构是4*32KB+127*128KB=16MB,一共16个分区,每个分区1MB。nb_parts=2。  

      11:cfi_intelext_setup()函数首先根据CFI建立mtd_erase_region_info信息,然后调用cfi_intelext_partition_fixup()来支持分区。  

      12:cfi_intelext_partition_fixup()用来建立虚拟Chip,每个分区对应1个Chip,不过并没有完全根据CFI扩展信息来建立,而是假定每个分区的大小都一致。cfi->chipshift调整为partshift,各个虚拟chip->start调整为各分区的基址。将来访问FLASH的入口函数cfi_varsize_frob()就根据ofs得到chipnum(chipnum=ofs>>cfi->chipshift),这也是为什么要假定分区一致的原因。

    MTD几个重要的的数据结构

    struct mtd_info {
        u_char type;
        u_int32_t flags;
        u_int32_t size;
    // Total size of the MTD

        
    /* "Major" erase size for the device. Na茂ve users may take this
         * to be the only erase size available, or may use the more detailed
         * information below if they desire
         */

       
     u_int32_t erasesize;
        /* Minimal writable flash unit size. In case of NOR flash it is 1 (even
         * though individual bits can be cleared), in case of NAND flash it is
         * one NAND page (or half, or one-fourths of it), in case of ECC-ed NOR
         * it is of ECC block size, etc. It is illegal to have writesize = 0.
         * Any driver registering a struct mtd_info must ensure a writesize of
         * 1 or larger.
         */

        u_int32_t writesize;
        u_int32_t oobsize;
    // Amount of OOB data per block (e.g. 16)

        u_int32_t oobavail;
    // Available OOB bytes per block

        // Kernel-only stuff starts here.
        char *name;
        int index;

        /* ecc layout structure pointer - read only ! */
        struct nand_ecclayout *ecclayout;

        
    /* Data for variable erase regions. If numeraseregions is zero,
         * it means that the whole device has erasesize as given above.
         */

        int numeraseregions;
        struct mtd_erase_region_info *eraseregions;
    ……
    };

    struct erase_info {
        struct mtd_info *mtd;
       
     u_int32_t addr;
        u_int32_t len;

        u_int32_t fail_addr;
        u_long time;
        u_long retries;
        u_int dev;
        u_int cell;
        void (*callback) (struct erase_info *self);
        u_long priv;
        u_char state;
        struct erase_info *next;
    };

    struct mtd_erase_region_info {
        u_int32_t offset;            /* At which this region starts, from the beginning of the MTD */
        u_int32_t erasesize;        /* For this region */
        u_int32_t numblocks;        /* Number of blocks of erasesize in this region */
        unsigned long *lockmap;        /* If keeping bitmap of locks */
    };

    如果想了解一份驱动代码的流程及细节,先从这些重要的数据结构入手会快得多。

     

    我当时要了解MTD代码是因为fd = open("/dev/mtd4", O_RDWR)返回-1,而fd = open("/dev/mtd4", O_RDONLY)是成功的。可知mtd4是不可写的,但是中间件的移植要读写该mtd分区的,所以必须从驱动入手修改。以下的流程是我已经整理过的,当初并没有那么简单和条理。

    首先,从mtd设备层入手,因为open、close、read、write、ioctl等file operation函数都定义在这里。在mtdopen函数的实现中,很快可以看出苗头,注意如下代码框中字体加粗部分:

    /* You can't open the RO devices RW */
        
    if ((file->f_mode & 2) && (minor & 1))
            return -EACCES;

        mtd = get_mtd_device(NULL, devnum);

        if (!mtd)
            return -ENODEV;

        if (MTD_ABSENT == mtd->type) {
            put_mtd_device(mtd);
            return -ENODEV;
        }

        /* You can't open it RW if it's not a writeable device */
        
    if ((file->f_mode & 2) && !(mtd->flags & MTD_WRITEABLE)) {
            put_mtd_device(mtd);
            return -EACCES;

        }

    另外在fs.h中有定义

    #define FMODE_READ 1
    #define FMODE_WRITE 2

    显然,如果open的文件标志有写标记的话,那么会在open时判断该设备是否允许写操作。刚开始我根据if ((file->f_mode & 2) && (minor & 1))去追踪mtd4的次设备号与1相与是否非0。在目标板系统上执行ls -l /dev/mtd4,得知mtd4的次设备号为8,从而确定了不会在这一步返回。

    剩下的只有if ((file->f_mode & 2) && !(mtd->flags & MTD_WRITEABLE))了,mtd->flags有可能置!MTD_WRITEABLE标志,导致在这里open失败。之后的工作就定位在mtd->flags上

    mtd_info的初始化应该在flash硬件驱动层进行的,本着这样的想法,我特地去了解一下cfi的probe过程,事实证明那是没有必要的。现在回到flash硬件驱动层,找到我们项目的flash芯片的驱程,init_xxxx_map()这样定义:

        //以上是mtd各个区的offset、size设置
        
    xxxx_mtd = do_map_probe("cfi_probe", &xxxx_map);
        if (!xxxx_mtd) {
            iounmap((void *)xxxx_map.virt);
            return -ENXIO;
        }
            
        add_mtd_partitions(xxxx_mtd, xxxx_parts, numparts);
        xxxx_mtd->owner = THIS_MODULE;
        return 0;

    mtd_info的最初初始化是在do_map_probe()里完成的,它其实是通过调用cfi_probe()来完成这些工作的,从那里开始追踪可以看到mtd->flags = MTD_CAP_NORFLASH,而MTD_CAP_NORFLASH则等于MTD_WRITEABLE | MTD_BIT_WRITEABLE。add_mtd_partitions()是mtd partitions的加载实现,一般来说mtd_info等信息也会在这里被设置,进入该函数,果然可以找到我们关心的部分:

            if ((slave->mtd.flags & MTD_WRITEABLE) &&
             (slave->offset % slave->mtd.erasesize)) {
                /* 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 ("mtd: partition \"%s\" doesn't start on an erase block boundary -- force read-only\n",
                    parts[i].name);
            }
            if ((slave->mtd.flags & MTD_WRITEABLE) &&
             (slave->mtd.size % slave->mtd.erasesize)) {
                slave->mtd.flags &= ~MTD_WRITEABLE;

                printk ("mtd: partition \"%s\" doesn't end on an erase block -- force read-only\n",
                    parts[i].name);
            }

    这两个if判断,如果分区的偏移量offset不能被erasesize整除或者分区的大小size不能被erasesize整除的话,那么该分区的mtd.flags会被置不可写标志。从那些printk信息来看,offset、size必须满足erasesize的边界条件,才允许可写。

    找了原因,我验证了一下。ioctl(fd, MEMGETINFO, &mtd_info)得到mtd_info.erasesize=0x20000,xxxx-flash.c定义mtd分区信息是:

        { name: "rootfs",        offset: 0,            size: 28*1024*1024 },
        { name: "bootloader",     offset: 0x01C00000, size: 512*1024 },
        { name: "zImage",    offset: 0x01C80000, size: 3582*1024 },
        { name: "macadr",    offset: 0x01FFF800,    size: 144 },
        { name: "config",    offset: 0x01FFF890,    size: 1904 },

    可以计算到rootfs、bootloader分区的offset、size符合erasesize的边界条件,zImage、config、macadr分区则不符合erasesize的边界条件。以O_RDWR标志open /dev/mtd0和/dev/mtd1都是成功的,open后三个mtd区设备则失败,基本上验证了之前的分析。

    展开全文
  • AT91RM9200 Linux-2.6.20 MTD驱动 开发板上只有Nor Flash,所以为了实现层次文件系统,需要为Linux2.6.20增加Nor Flash MTD驱动支持。其实工作量并不大,因为已经有现成的程序可供参考。 MTD的驱动程序都集中在...
  • 项目中用到了spi flash芯片MX25L25635E,之前在uboot下简单分析了驱动代码,调试该flash擦除的bug,一直没有时间分析内核中关于该芯片的驱动,以下是对该芯片驱动的一个简单分析: 1、先粘贴一些flash的理论部分: ...
  • mtd Nand驱动

    千次阅读 2012-01-16 20:59:13
    大约用了两个礼拜不到的时间为公司的IPcamera系统写了基于MTD的NAND驱动(linux-2.6.22.10内核),目前已可以在该驱动的支持下跑cramfs和jffs2文件系统,另外,该驱动也可以同时支持small page(每页512 Byte)和big ...
  • mtd设备驱动

    2014-07-20 11:56:46
    mtd(memory t)是为了
  • 开发板上只有Nor Flash,所以为了实现层次文件系统,需要为Linux2.6.20增加Nor Flash MTD驱动支持。其实工作量并不大,因为已经有现成的程序可供参考。MTD的驱动程序都集中在drivers/mtd里面。我们需要做的,仅仅是...
  • Flash设备驱动及接口分为4层:设备节点,MTD设备层、MTD原始设备层(属于内核的子系统)和硬件驱动层块设备与文件系统的关系:(1)MTD+MTDblock+fat(2)MTD+NFTL+fat(3)SD(flash)+硬件翻译层+fat(4)MTD+...
  • Nandflash的驱动加载

    千次阅读 2014-02-15 14:16:45
    num_partitions = parse_mtd_partitions(mtd, part_probes, //提取flash中的分区信息 &partitions, 0); #endif //如果提取flash中的分区信息不存在,则读取板级初始化设定的分区信息 if (num_...
  • MTD下的Nand驱动

    2018-12-26 14:18:00
    目录 MTD下的Nand驱动 引入 平台设备资源文件 关键数据结构 平台框架 s3c24xx_nand_probe nand_scan s3c2410_nand_add_partition add_mtd_partit...
  • 修改linux kernel驱动加载的顺序

    千次阅读 2016-10-31 17:12:23
    最近有一个在LCD驱动中读取nand的需求(linux),这就要求nand 的驱动加载必须在LCD驱动前,而linux kernel驱动的加载顺序是由一个宏决定的 比如helper2416板子(s3c2416芯片)中的nand驱动module_init(s3c_nand_init...
  • 基于MTD的NAND驱动开发

    千次阅读 2010-09-26 10:04:00
    http://blog.chinaunix.net/u1/41134/showart_721586.html基于MTD的NAND驱动开发(一)○、说明 大约用了两个礼拜不到的时间为公司的IPcamera系统写了基于MTD的NAND驱动(linux-2.6.22.10内核),目前已可以在该驱动的...
  • mtd

    千次阅读 2012-01-18 12:02:03
    在Linux内核中引入了MTD子系统为NOR FLASH和NAND FLASH设备提供统一的接口,从而使得FLASH驱动的设计大为简化。 在引入MTD后Linux系统中FLASH设备驱动可分为四层,如图:   1. 硬件驱动层 FLASH硬
  • usb接口驱动加载流程分析struct usb_device_driver usb_generic_driver = { .name = "usb", .probe = generic_probe, .disconnect = generic_disconnect,#ifdef CONFIG_PM .suspend = generic_suspend, .
  • mtd下nand驱动开发

    2011-06-28 10:07:00
    原文:http://blogold.chinaunix.net/u2/67144/showart_1070950.html○、说明 大约用了两个礼拜不到的时间为公司的IPcamera系统写了基于MTD的NAND驱动(linux-2.6.22.10内核),目前已可以在该驱动的支持下跑cramfs和...
  • 文中分析了TrueFFS文件系统下FLASH驱动的实现过程,通过对套组件驱动注册、MTD驱动加载以及FLASH格式化等操作,实现了FLASH驱动,并对FLASH写操作进行了优化设计。该设计已成功应用于通信设备中,结果表明该FLASH...
  •  引入MTD后Linux系统中对FLASH的设备驱动分为4层 设备节点:用户在/dev目录下使用mknod命令建立MTD字符设备节点(主设备号为90),或者MTD块设备节点(主设备号为31),使用该设备节点即可访问MTD设备。MTD设备...
  • 基于MTD的NAND驱动开发(二) 基于MTD的NAND驱动开发(三) http://blog.csdn.net/leibniz_zsu/article/details/4977853 http://blog.csdn.net/leibniz_zsu/article/details/4977869 四、基于MTD的...
  • 三、MTD创建设备节点 MTD子系统下如何创建设备节点? 第一步:MTD设备层。(MTD子系统)  register_chrdev注册字符型mtd设备,并添加该设备到内核,主设备号为90。但是此时还未在/dev下形成mtd设备节点。 第二步:...
  • 【详解】如何编写Linux下Nand Flash驱动版本:v2.2.1Crifan Li摘要本文先解释了Nand Flash相关的一些名词,再从Flash硬件机制开始,介绍到Nand Flash的常见的...最后介绍了在Linux的MTD驱动框架下,如何实现Nand ...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 5,042
精华内容 2,016
关键字:

mtd驱动加载