浅析 mtd 子系统

  以 nandflash 驱动程序为例,简单分析一下内核中的 MTD 子系统。前面分析过块设备驱动程序,一个块设备需要用一个 gendisk 结构体来描述,还要提供 I/O 请求队列和 I/O 请求处理函数。如果按照这个思路的话,nandflash 驱动程序我们就需要在请求处理函数中实现对 nandflash 的读写操作,事实上确实如此,只不过内核在块设备驱动的基础上又封装了一层,也就是所谓的MTD,现在只需要创建并填充一个 mtd_info 然后 add_mtd_partitions ,内核就会帮我们构造 gendisk 以及请求队列处理函数(它是个通用接口,最终还会调用的 mtd_info 芯片相关的函数)。

  本文以 nandflash 为例,简单分析 MTD 子系统的来龙去脉。首先来看一下两个简单的驱动程序:

nandflash驱动:

/* 参考 
 * drivers\mtd\nand\s3c2410.c
 * drivers\mtd\nand\at91_nand.c
 */

#include <linux/module.h>
#include <linux/types.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/string.h>
#include <linux/ioport.h>
#include <linux/platform_device.h>
#include <linux/delay.h>
#include <linux/err.h>
#include <linux/slab.h>
#include <linux/clk.h>
 
#include <linux/mtd/mtd.h>
#include <linux/mtd/nand.h>
#include <linux/mtd/nand_ecc.h>
#include <linux/mtd/partitions.h>
 
#include <asm/io.h>
 
#include <asm/arch/regs-nand.h>
#include <asm/arch/nand.h>

struct s3c_nand_regs {
	unsigned long nfconf  ;
	unsigned long nfcont  ;
	unsigned long nfcmd   ;
	unsigned long nfaddr  ;
	unsigned long nfdata  ;
	unsigned long nfeccd0 ;
	unsigned long nfeccd1 ;
	unsigned long nfeccd  ;
	unsigned long nfstat  ;
	unsigned long nfestat0;
	unsigned long nfestat1;
	unsigned long nfmecc0 ;
	unsigned long nfmecc1 ;
	unsigned long nfsecc  ;
	unsigned long nfsblk  ;
	unsigned long nfeblk  ;
};


static struct nand_chip *s3c_nand;
static struct mtd_info *s3c_mtd;
static struct s3c_nand_regs *s3c_nand_regs;

static struct mtd_partition s3c_nand_parts[] = {
	[0] = {
        .name   = "bootloader",
        .size   = 0x00040000,
		.offset	= 0,
	},
	[1] = {
        .name   = "params",
        .offset = MTDPART_OFS_APPEND,
        .size   = 0x00020000,
	},
	[2] = {
        .name   = "kernel",
        .offset = MTDPART_OFS_APPEND,
        .size   = 0x00200000,
	},
	[3] = {
        .name   = "root",
        .offset = MTDPART_OFS_APPEND,
        .size   = MTDPART_SIZ_FULL,
	}
};


static void s3c2440_select_chip(struct mtd_info *mtd, int chipnr)
{
	if (chipnr == -1)
	{
		/* 取消选中: NFCONT[1]设为1 */
		s3c_nand_regs->nfcont |= (1<<1);		
	}
	else
	{
		/* 选中: NFCONT[1]设为0 */
		s3c_nand_regs->nfcont &= ~(1<<1);
	}
}

static void s3c2440_cmd_ctrl(struct mtd_info *mtd, int dat, unsigned int ctrl)
{
	if (ctrl & NAND_CLE)
	{
		/* 发命令: NFCMMD=dat */
		s3c_nand_regs->nfcmd = dat;
	}
	else
	{
		/* 发地址: NFADDR=dat */
		s3c_nand_regs->nfaddr = dat;
	}
}

static int s3c2440_dev_ready(struct mtd_info *mtd)
{
	return (s3c_nand_regs->nfstat & (1<<0));
}


static int s3c_nand_init(void)
{
	struct clk *clk;
	
	/* 1. 分配一个nand_chip结构体 */
	s3c_nand = kzalloc(sizeof(struct nand_chip), GFP_KERNEL);

	s3c_nand_regs = ioremap(0x4E000000, sizeof(struct s3c_nand_regs));
	
	/* 2. 设置nand_chip */
	/* 设置nand_chip是给nand_scan函数使用的, 如果不知道怎么设置, 先看nand_scan怎么使用 
	 * 它应该提供:选中,发命令,发地址,发数据,读数据,判断状态的功能
	 */
	s3c_nand->select_chip = s3c2440_select_chip;
	s3c_nand->cmd_ctrl    = s3c2440_cmd_ctrl;
	s3c_nand->IO_ADDR_R   = &s3c_nand_regs->nfdata;
	s3c_nand->IO_ADDR_W   = &s3c_nand_regs->nfdata;
	s3c_nand->dev_ready   = s3c2440_dev_ready;
	s3c_nand->ecc.mode    = NAND_ECC_SOFT;
	
	/* 3. 硬件相关的设置: 根据NAND FLASH的手册设置时间参数 */
	/* 使能NAND FLASH控制器的时钟 */
	clk = clk_get(NULL, "nand");
	clk_enable(clk);              /* CLKCON'bit[4] */
	
	/* HCLK=100MHz
	 * TACLS:  发出CLE/ALE之后多长时间才发出nWE信号, 从NAND手册可知CLE/ALE与nWE可以同时发出,所以TACLS=0
	 * TWRPH0: nWE的脉冲宽度, HCLK x ( TWRPH0 + 1 ), 从NAND手册可知它要>=12ns, 所以TWRPH0>=1
	 * TWRPH1: nWE变为高电平后多长时间CLE/ALE才能变为低电平, 从NAND手册可知它要>=5ns, 所以TWRPH1>=0
	 */
#define TACLS    0
#define TWRPH0   1
#define TWRPH1   0
	s3c_nand_regs->nfconf = (TACLS<<12) | (TWRPH0<<8) | (TWRPH1<<4);

	/* NFCONT: 
	 * BIT1-设为1, 取消片选 
	 * BIT0-设为1, 使能NAND FLASH控制器
	 */
	s3c_nand_regs->nfcont = (1<<1) | (1<<0);
	
	/* 4. 使用: nand_scan */
	s3c_mtd = kzalloc(sizeof(struct mtd_info), GFP_KERNEL);
	s3c_mtd->owner = THIS_MODULE;
	s3c_mtd->priv  = s3c_nand;
	
	nand_scan(s3c_mtd, 1);  /* 识别NAND FLASH, 构造mtd_info */
	
	/* 5. add_mtd_partitions */
	 (s3c_mtd, s3c_nand_parts, 4);
	
	//add_mtd_device(s3c_mtd);
	return 0;
}

static void s3c_nand_exit(void)
{
	del_mtd_partitions(s3c_mtd);
	kfree(s3c_mtd);
	iounmap(s3c_nand_regs);
	kfree(s3c_nand);
}

module_init(s3c_nand_init);
module_exit(s3c_nand_exit);

MODULE_LICENSE("GPL");
norflash驱动:
/*
 * 参考 drivers\mtd\maps\physmap.c
 */

#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");
  经过对比不难发现,nandflash 和 norflash 驱动最终都会调用 add_mtd_partitions ,传递两个参数一个是 mtd_info 结构,另一个是分区信息相关的 mtd_partition 结构。不一样的是在 add_mtd_partitions 之前,它们 mtd_info 填充的方式不一样,但也类似。对于 nandflash 来说,填充 mtd_info 部分和 uboot2012 中的 nandflash 识别部分一模一样,之前这部分我已经在前边的移植 uboot2012 之 nandflash 识别中分析过了,这里就不再赘述,大致的过程如下:
s3c_nand->select_chip = s3c2440_select_chip;
s3c_nand->cmd_ctrl    = s3c2440_cmd_ctrl;
s3c_nand->IO_ADDR_R   = &s3c_nand_regs->nfdata;
s3c_nand->IO_ADDR_W   = &s3c_nand_regs->nfdata;
s3c_nand->dev_ready   = s3c2440_dev_ready;
s3c_nand->ecc.mode    = NAND_ECC_SOFT;
nand_scan  // drivers/mtd/nand/nand_base.c 根据nand_chip的底层操作函数识别NAND FLASH,构造mtd_info
    nand_scan_ident
        nand_set_defaults
            if (!chip->select_chip)
                chip->select_chip = nand_select_chip; // 默认值不适用
                if (chip->cmdfunc == NULL)
                    chip->cmdfunc = nand_command;
                    chip->cmd_ctrl(mtd, command, ctrl);
                if (!chip->read_byte)
                    chip->read_byte = nand_read_byte;
                    readb(chip->IO_ADDR_R);
                    if (chip->waitfunc == NULL)
                        chip->waitfunc = nand_wait;
                        chip->dev_ready
               
        nand_get_flash_type
            chip->select_chip(mtd, 0);
            chip->cmdfunc(mtd, NAND_CMD_READID, 0x00, -1);
            *maf_id = chip->read_byte(mtd);
            dev_id = chip->read_byte(mtd);
    nand_scan_tail
        mtd->erase = nand_erase;
        mtd->read = nand_read;
        mtd->write = nand_write;
下面来看 MTD 子系统相关部分:
static int __init init_mtdblock(void)
{
	mutex_init(&mtdblks_lock);

	return register_mtd_blktrans(&mtdblock_tr);
}
module_init(init_mtdblock);
在内核启动时,注册了这么一个块设备通用操作结构体:
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,
};

register_mtd_blktrans(&mtdblock_tr);

int register_mtd_blktrans(struct mtd_blktrans_ops *tr)
{

	if (!blktrans_notifier.list.next)
		register_mtd_user(&blktrans_notifier);

	tr->blkcore_priv = kzalloc(sizeof(*tr->blkcore_priv), GFP_KERNEL);
	// 注册块设备号
	ret = register_blkdev(tr->major, tr->name);
	// 自旋锁
	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_thread 线程
	tr->blkcore_priv->thread = kthread_run(mtd_blktrans_thread, tr,
			"%sd", tr->name);
		
	INIT_LIST_HEAD(&tr->devs);
	// 将 mtdblock_tr 加入全局链表 blktrans_majors
	list_add(&tr->list, &blktrans_majors);
}
  这不就是前面块设备驱动程序中干的那些工作么,blk_init_queue 初始化一个请求队列,并设置请求处理函数,只不过内核这里搞的是对所有的块设备通用的接口函数。

register_mtd_user(&blktrans_notifier);

// register_mtd_user(&blktrans_notifier);
// static LIST_HEAD(mtd_notifiers);
void register_mtd_user (struct mtd_notifier *new)
{
	// 将 blktrans_notifier 加入全局链表 mtd_notifiers 
	list_add(&new->list, &mtd_notifiers);
}
static struct mtd_notifier blktrans_notifier = {
	.add = blktrans_notify_add,
	.remove = blktrans_notify_remove,
};

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)
		// mtdblock_add_mtd(tr, mtd)
		tr->add_mtd(tr, mtd);
}
mtd_notifiers 成为对外接口
mtd_notifiers -> blktrans_notifier -> blktrans_notify_add -> blktrans_majors -> mtdblock_tr

    add_mtd_partitions
        add_mtd_device
            list_for_each(this, &mtd_notifiers) { 
                struct mtd_notifier *not = list_entry(this, struct mtd_notifier, list);
                not->add(mtd);//<span style="font-family: FangSong_GB2312;font-size:18px;">mtdblock_tr->add</span>

mtdblock_add_mtd

static void mtdblock_add_mtd(struct mtd_blktrans_ops *tr, struct mtd_info *mtd)
{
	struct mtd_blktrans_dev *dev = kzalloc(sizeof(*dev), GFP_KERNEL);

	dev->mtd = mtd;
	dev->devnum = mtd->index;

	dev->size = mtd->size >> 9;
	dev->tr = tr;

	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;
	// gendisk 结构体
	struct gendisk *gd;

	gd = alloc_disk(1 << tr->part_bits);

	gd->major = tr->major;
	gd->first_minor = (new->devnum) << tr->part_bits;
	gd->fops = &mtd_blktrans_ops;

	set_capacity(gd, (new->size * tr->blksize) >> 9);

	gd->private_data = new;
	new->blkcore_priv = gd;
	// tr->blkcore_priv->rq = blk_init_queue(mtd_blktrans_request, &tr->blkcore_priv->queue_lock);
	gd->queue = tr->blkcore_priv->rq;
	gd->driverfs_dev = &new->mtd->dev;

	if (new->readonly)
		set_disk_ro(gd, 1);
	
	// add_disk
	add_disk(gd);

	return 0;
}
  块设备的 gendisk 结构出现了,mtd_blktrans_request 是通用的请求处理函数接口
static void mtd_blktrans_request(struct request_queue *rq)
{
	struct mtd_blktrans_ops *tr = rq->queuedata;
	// tr->blkcore_priv->thread = kthread_run(mtd_blktrans_thread, tr,"%sd", tr->name);
	wake_up_process(tr->blkcore_priv->thread);
}

static int mtd_blktrans_thread(void *arg){
	...
	res = do_blktrans_request(tr, dev, req);
}
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 (!blk_fs_request(req))
		return -EIO;

	if (blk_rq_pos(req) + blk_rq_cur_sectors(req) >
	    get_capacity(req->rq_disk))
		return -EIO;

	if (blk_discard_rq(req))
		return tr->discard(dev, block, nsect);

	switch(rq_data_dir(req)) {
	case READ:
		for (; nsect > 0; nsect--, block++, buf += tr->blksize)
			// mtdblock_writesect
			// if (dev->mtd->write(dev->mtd, (block * 512), 512, &retlen, buf))
			if (tr->readsect(dev, block, buf))
				return -EIO;
		return 0;

	case WRITE:
		if (!tr->writesect)
			return -EIO;

		for (; nsect > 0; nsect--, block++, buf += tr->blksize)
			if (tr->writesect(dev, block, buf))
				return -EIO;
		return 0;

	default:
		printk(KERN_NOTICE "Unknown request %u\n", rq_data_dir(req));
		return -EIO;
	}
}









  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值