Chinaunix首页 | 论坛 | 博客
  • 博客访问: 3125645
  • 博文数量: 685
  • 博客积分: 0
  • 博客等级: 民兵
  • 技术积分: 5303
  • 用 户 组: 普通用户
  • 注册时间: 2014-04-19 14:17
个人简介

文章分类

全部博文(685)

文章存档

2015年(116)

2014年(569)

分类: LINUX

2014-10-27 12:32:31

原文地址:http://blog.csdn.net/xgbing/article/details/7680506

Mtdchar.clinux下字符设备驱动程序的实现:

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");

}

2Open:

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_deviceget_mtd_device是成对函数,它释放对mtdinfo的使用。

3mtd_lseek, mtd_close很简单。

4mtd_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);

5mtd_writemtd_read代码相似。

6mtd_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);

}



阅读(1910) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~