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有关mtd字符设备号的说明:
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的擦除时间有点长、所以程序中使用了系统调度:
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); }