您的位置:首页 > 运维架构 > Linux

linux mtd源码分析--mtdchar.c

2012-06-20 17:21 417 查看
Mtdchar.c是linux下字符设备驱动程序的实现:
static const struct file_operations mtd_fops = {

.owner = THIS_MODULE,

.llseek = mtd_lseek,

.read = mtd_read,

.write = mtd_write,

.ioctl = mtd_ioctl,

.open = mtd_open,

.release = mtd_close,

.mmap = mtd_mmap,
#ifndef CONFIG_MMU

.get_unmapped_area = mtd_get_unmapped_area,
#endif
};
(1)加载和卸载驱动:
static int __init init_mtdchar(void)
{

int status;


status = register_chrdev(MTD_CHAR_MAJOR, "mtd", &mtd_fops);

if (status < 0) {

printk(KERN_NOTICE "Can't allocate major number %d for Memory Technology Devices.\n",

MTD_CHAR_MAJOR);

}


return status;
}

static void __exit cleanup_mtdchar(void)
{

unregister_chrdev(MTD_CHAR_MAJOR, "mtd");
}
(2)Open:
static int mtd_open(struct inode *inode, struct file *file)
{

int minor = iminor(inode);

int devnum = minor >> 1; #<1>

int ret = 0;

struct mtd_info *mtd;

struct mtd_file_info *mfi;


DEBUG(MTD_DEBUG_LEVEL0, "MTD_open\n");


if (devnum >= MAX_MTD_DEVICES)

return -ENODEV;


/* You can't open the RO devices RW */

if ((file->f_mode & FMODE_WRITE) && (minor & 1)) 当minor为奇数时是只读防问

return -EACCES;


lock_kernel(); 内核锁

mtd = get_mtd_device(NULL, devnum); #<2>


if (IS_ERR(mtd)) {

ret = PTR_ERR(mtd);

goto out;

}


if (mtd->type == MTD_ABSENT) {

put_mtd_device(mtd);

ret = -ENODEV;

goto out;

}


if (mtd->backing_dev_info)

file->f_mapping->backing_dev_info = mtd->backing_dev_info;


/* You can't open it RW if it's not a writeable device */

if ((file->f_mode & FMODE_WRITE) && !(mtd->flags & MTD_WRITEABLE)) {

put_mtd_device(mtd);

ret = -EACCES;

goto out;

}


mfi = kzalloc(sizeof(*mfi), GFP_KERNEL);

if (!mfi) {

put_mtd_device(mtd);

ret = -ENOMEM;

goto out;

}

mfi->mtd = mtd;

file->private_data = mfi;

out:

unlock_kernel();

return ret;
} /* mtd_open */
#<1> Documentations/devices.txt有关这个的说明:
90 char
Memory Technology Device (RAM, ROM, Flash)

0 = /dev/mtd0 First MTD (rw)

1 = /dev/mtdr0 First MTD (ro)

...

30 = /dev/mtd15 16th MTD (rw)

31 = /dev/mtdr15 16th MTD (ro)
所以注意像”/dev/mtd2”实际访问的是第二个FLASH分区。
#<2> get_mtd_device函数代码在mtdcore.c中,它当参数num!=-1时是从取mtdtable[num]。
put_mtd_device与get_mtd_device是成对函数,它释放对mtdinfo的使用。

(3)mtd_lseek,
mtd_close很简单。

(4)mtd_read:
#define MAX_KMALLOC_SIZE 0x20000

static ssize_t mtd_read(struct file *file, char __user *buf, size_t count,loff_t *ppos)
{

struct mtd_file_info *mfi = file->private_data;

struct mtd_info *mtd = mfi->mtd;

size_t retlen=0;

size_t total_retlen=0;

int ret=0;

int len;

char *kbuf;


DEBUG(MTD_DEBUG_LEVEL0,"MTD_read\n");


if (*ppos + count > mtd->size)

count = mtd->size - *ppos;


if (!count)

return 0;


/* FIXME: Use kiovec in 2.5 to lock down the user's buffers

and pass them directly to the MTD functions */

#<1>


if (count > MAX_KMALLOC_SIZE)

kbuf=kmalloc(MAX_KMALLOC_SIZE, GFP_KERNEL);

else

kbuf=kmalloc(count, GFP_KERNEL);



if (!kbuf)

return -ENOMEM;


while (count) {


if (count > MAX_KMALLOC_SIZE)

len = MAX_KMALLOC_SIZE;

else

len = count;


switch (mfi->mode) { #<2>

case MTD_MODE_OTP_FACTORY:

ret = mtd->read_fact_prot_reg(mtd, *ppos, len, &retlen, kbuf);

break;

case MTD_MODE_OTP_USER:

ret = mtd->read_user_prot_reg(mtd, *ppos, len, &retlen, kbuf);

break;

case MTD_MODE_RAW:

{

struct mtd_oob_ops ops;


ops.mode = MTD_OOB_RAW;

ops.datbuf = kbuf;

ops.oobbuf = NULL;

ops.len = len;


ret = mtd->read_oob(mtd, *ppos, &ops);

retlen = ops.retlen;

break;

}

default:

ret = mtd->read(mtd, *ppos, len, &retlen, kbuf);

}

/* Nand returns -EBADMSG on ecc errors, but it returns

* the data. For our userspace tools it is important

* to dump areas with ecc errors !

* For kernel internal usage it also might return -EUCLEAN

* to signal the caller that a bitflip has occured and has

* been corrected by the ECC algorithm.

* Userspace software which accesses NAND this way

* must be aware of the fact that it deals with NAND

*/

if (!ret || (ret == -EUCLEAN) || (ret == -EBADMSG)) {

*ppos += retlen;

if (copy_to_user(buf, kbuf, retlen)) {

kfree(kbuf);

return -EFAULT;

}

else

total_retlen += retlen;


count -= retlen;

buf += retlen;

if (retlen == 0)

count = 0;

}

else {

kfree(kbuf);

return ret;

}


}


kfree(kbuf);

return total_retlen;
} /* mtd_read */
#<1>
这里只申请一小段内存也可以实现大量数据的访问,是一个良好的习惯,以后写驱动也应类似处理。

#<2> mfi->mode的值可以在mtd_ioctl中被改变。这几个宏主要是提供对一些保护数据的访问或正常区域的数据访问。下面是它们在mtdinfo结构中的原型说明:
/*

* 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);
(5)mtd_write和mtd_read代码相似。
(6)mtd_ioctl
常用的几个cmd说明:
cmd
arg
说明
MEMGETREGIONCOUNT
返回得到的信息
mtd->numeraseregions
MEMGETREGIONINFO
ur->regionindex指定区域序号。
返回得到的信息
返回struct region_info_user
MEMGETINFO
返回得到的信息
返回struct mtd_info_user
MEMERASE
传入struct erase_info_user
擦除区域
MEMWRITEOOB
传入的struct mtd_oob_buf信息
写入OOB数据,OOB是指FLASH中每个页的附加数据区域。
MEMREADOOB
传入的struct mtd_oob_buf信息,并写入
读取OOB数据
MEMGETBADBLOCK
测试块的地址
测试是否是坏块
MEMSETBADBLOCK
块的地址
标记是一个坏块
ECCGETLAYOUT
返回struct nand_ecclayout
获取ECC的信息
ECCGETSTATS
返回struct mtd_ecc_stats
获取ECC的状态
MTDFILEMODE
传入的读写模式:
MTD_MODE_OTP_FACTORY
MTD_MODE_OTP_USER
MTD_MODE_RAW
MTD_MODE_NORMAL
设置读写模式
擦除FLASH的时间可能会有点长。NOR
flash的擦除时间比Nandflash时间长,可能会有一两秒,所以程序中进入了系统调度。
case MEMERASE:

{

struct erase_info *erase;


if(!(file->f_mode & FMODE_WRITE))

return -EPERM;


erase=kzalloc(sizeof(struct erase_info),GFP_KERNEL);

if (!erase)

ret = -ENOMEM;

else {

struct erase_info_user einfo;


wait_queue_head_t waitq;

DECLARE_WAITQUEUE(wait, current);


init_waitqueue_head(&waitq);


if (copy_from_user(&einfo, argp,

sizeof(struct erase_info_user))) {

kfree(erase);

return -EFAULT;

}

erase->addr = einfo.start;

erase->len = einfo.length;

erase->mtd = mtd;

erase->callback = mtdchar_erase_callback;

erase->priv = (unsigned long)&waitq;


/*

FIXME: Allow INTERRUPTIBLE. Which means

not having the wait_queue head on the stack.


If the wq_head is on the stack, and we

leave because we got interrupted, then the

wq_head is no longer there when the

callback routine tries to wake us up.

*/

ret = mtd->erase(mtd, erase);

if (!ret) {

set_current_state(TASK_UNINTERRUPTIBLE);

add_wait_queue(&waitq, &wait);

if (erase->state != MTD_ERASE_DONE &&

erase->state != MTD_ERASE_FAILED)

schedule();

remove_wait_queue(&waitq, &wait);

set_current_state(TASK_RUNNING);


ret = (erase->state == MTD_ERASE_FAILED)?-EIO:0;

}

kfree(erase);

}

break;

}
擦除完成后,mtd会调用erase->callback来唤醒进程:
static void mtdchar_erase_callback (struct erase_info *instr)
{

wake_up((wait_queue_head_t *)instr->priv);
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: