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