📄 mtdchar.c
字号:
/* * Character-device access to raw MTD devices. * */#include <linux/device.h>#include <linux/fs.h>#include <linux/mm.h>#include <linux/err.h>#include <linux/init.h>#include <linux/kernel.h>#include <linux/module.h>#include <linux/slab.h>#include <linux/sched.h>#include <linux/smp_lock.h>#include <linux/mtd/mtd.h>#include <linux/mtd/compatmac.h>#include <asm/uaccess.h>static struct class *mtd_class;static void mtd_notify_add(struct mtd_info* mtd){ if (!mtd) return; device_create(mtd_class, NULL, MKDEV(MTD_CHAR_MAJOR, mtd->index*2), NULL, "mtd%d", mtd->index); device_create(mtd_class, NULL, MKDEV(MTD_CHAR_MAJOR, mtd->index*2+1), NULL, "mtd%dro", mtd->index);}static void mtd_notify_remove(struct mtd_info* mtd){ if (!mtd) return; device_destroy(mtd_class, MKDEV(MTD_CHAR_MAJOR, mtd->index*2)); device_destroy(mtd_class, MKDEV(MTD_CHAR_MAJOR, mtd->index*2+1));}static struct mtd_notifier notifier = { .add = mtd_notify_add, .remove = mtd_notify_remove,};/* * Data structure to hold the pointer to the mtd device as well * as mode information ofr various use cases. */struct mtd_file_info { struct mtd_info *mtd; enum mtd_file_modes mode;};static loff_t mtd_lseek (struct file *file, loff_t offset, int orig){ struct mtd_file_info *mfi = file->private_data; struct mtd_info *mtd = mfi->mtd; switch (orig) { case SEEK_SET: break; case SEEK_CUR: offset += file->f_pos; break; case SEEK_END: offset += mtd->size; break; default: return -EINVAL; } if (offset >= 0 && offset <= mtd->size) return file->f_pos = offset; return -EINVAL;}static int mtd_open(struct inode *inode, struct file *file){ int minor = iminor(inode); int devnum = minor >> 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)) return -EACCES; lock_kernel(); mtd = get_mtd_device(NULL, devnum); if (IS_ERR(mtd)) { ret = PTR_ERR(mtd); goto out; } if (MTD_ABSENT == mtd->type) { put_mtd_device(mtd); ret = -ENODEV; goto out; } /* 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 *//*====================================================================*/static int mtd_close(struct inode *inode, struct file *file){ struct mtd_file_info *mfi = file->private_data; struct mtd_info *mtd = mfi->mtd; DEBUG(MTD_DEBUG_LEVEL0, "MTD_close\n"); /* Only sync if opened RW */ if ((file->f_mode & FMODE_WRITE) && mtd->sync) mtd->sync(mtd); put_mtd_device(mtd); file->private_data = NULL; kfree(mfi); return 0;} /* mtd_close *//* FIXME: This _really_ needs to die. In 2.5, we should lock the userspace buffer down and use it directly with readv/writev.*/#define MAX_KMALLOC_SIZE 0x20000static 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 */ 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) { 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 */static ssize_t mtd_write(struct file *file, const char __user *buf, size_t count,loff_t *ppos){ struct mtd_file_info *mfi = file->private_data; struct mtd_info *mtd = mfi->mtd; char *kbuf; size_t retlen; size_t total_retlen=0; int ret=0; int len; DEBUG(MTD_DEBUG_LEVEL0,"MTD_write\n"); if (*ppos == mtd->size) return -ENOSPC; if (*ppos + count > mtd->size) count = mtd->size - *ppos; if (!count) return 0; 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; if (copy_from_user(kbuf, buf, len)) { kfree(kbuf); return -EFAULT; } switch (mfi->mode) { case MTD_MODE_OTP_FACTORY: ret = -EROFS; break; case MTD_MODE_OTP_USER: if (!mtd->write_user_prot_reg) { ret = -EOPNOTSUPP; break; } ret = mtd->write_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->write_oob(mtd, *ppos, &ops); retlen = ops.retlen; break; } default: ret = (*(mtd->write))(mtd, *ppos, len, &retlen, kbuf); } if (!ret) { *ppos += retlen; total_retlen += retlen; count -= retlen; buf += retlen; } else { kfree(kbuf); return ret; } } kfree(kbuf); return total_retlen;} /* mtd_write *//*====================================================================== IOCTL calls for getting device parameters.======================================================================*/static void mtdchar_erase_callback (struct erase_info *instr){ wake_up((wait_queue_head_t *)instr->priv);}#ifdef CONFIG_HAVE_MTD_OTPstatic int otp_select_filemode(struct mtd_file_info *mfi, int mode){ struct mtd_info *mtd = mfi->mtd; int ret = 0; switch (mode) { case MTD_OTP_FACTORY: if (!mtd->read_fact_prot_reg) ret = -EOPNOTSUPP; else mfi->mode = MTD_MODE_OTP_FACTORY; break; case MTD_OTP_USER: if (!mtd->read_fact_prot_reg) ret = -EOPNOTSUPP; else mfi->mode = MTD_MODE_OTP_USER; break; default: ret = -EINVAL; case MTD_OTP_OFF: break; } return ret;}#else# define otp_select_filemode(f,m) -EOPNOTSUPP#endifstatic int mtd_ioctl(struct inode *inode, struct file *file, u_int cmd, u_long arg){ struct mtd_file_info *mfi = file->private_data; struct mtd_info *mtd = mfi->mtd; void __user *argp = (void __user *)arg; int ret = 0; u_long size; struct mtd_info_user info; DEBUG(MTD_DEBUG_LEVEL0, "MTD_ioctl\n"); size = (cmd & IOCSIZE_MASK) >> IOCSIZE_SHIFT; if (cmd & IOC_IN) { if (!access_ok(VERIFY_READ, argp, size)) return -EFAULT; } if (cmd & IOC_OUT) { if (!access_ok(VERIFY_WRITE, argp, size)) return -EFAULT; } switch (cmd) { case MEMGETREGIONCOUNT: if (copy_to_user(argp, &(mtd->numeraseregions), sizeof(int))) return -EFAULT; break; case MEMGETREGIONINFO: { uint32_t ur_idx; struct mtd_erase_region_info *kr;
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -