X-Git-Url: http://ftp.safe.ca/?a=blobdiff_plain;f=drivers%2Fmtd%2Fmtdchar.c;h=5b081cb84351340dd6164fe448703f93c6dfca47;hb=f653398c86a1c104f0992bd788dd4bb065449be4;hp=b42553cd9af5378c7ed09ccfce92c0c260fcd6f0;hpb=e9d8d48253c50106d85b288939e5227083360863;p=safe%2Fjmp%2Flinux-2.6 diff --git a/drivers/mtd/mtdchar.c b/drivers/mtd/mtdchar.c index b42553c..5b081cb 100644 --- a/drivers/mtd/mtdchar.c +++ b/drivers/mtd/mtdchar.c @@ -1,6 +1,4 @@ /* - * $Id: mtdchar.c,v 1.76 2005/11/07 11:14:20 gleixner Exp $ - * * Character-device access to raw MTD devices. * */ @@ -14,40 +12,15 @@ #include #include #include +#include +#include +#include #include #include #include -static struct class *mtd_class; - -static void mtd_notify_add(struct mtd_info* mtd) -{ - if (!mtd) - return; - - class_device_create(mtd_class, NULL, MKDEV(MTD_CHAR_MAJOR, mtd->index*2), - NULL, "mtd%d", mtd->index); - - class_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; - - class_device_destroy(mtd_class, MKDEV(MTD_CHAR_MAJOR, mtd->index*2)); - class_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 @@ -88,6 +61,7 @@ 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; @@ -97,34 +71,45 @@ static int mtd_open(struct inode *inode, struct file *file) return -ENODEV; /* You can't open the RO devices RW */ - if ((file->f_mode & 2) && (minor & 1)) + if ((file->f_mode & FMODE_WRITE) && (minor & 1)) return -EACCES; + lock_kernel(); mtd = get_mtd_device(NULL, devnum); - if (IS_ERR(mtd)) - return PTR_ERR(mtd); + if (IS_ERR(mtd)) { + ret = PTR_ERR(mtd); + goto out; + } - if (MTD_ABSENT == mtd->type) { + if (mtd->type == MTD_ABSENT) { put_mtd_device(mtd); - return -ENODEV; + 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 & 2) && !(mtd->flags & MTD_WRITEABLE)) { + if ((file->f_mode & FMODE_WRITE) && !(mtd->flags & MTD_WRITEABLE)) { put_mtd_device(mtd); - return -EACCES; + ret = -EACCES; + goto out; } mfi = kzalloc(sizeof(*mfi), GFP_KERNEL); if (!mfi) { put_mtd_device(mtd); - return -ENOMEM; + ret = -ENOMEM; + goto out; } mfi->mtd = mtd; file->private_data = mfi; - return 0; +out: + unlock_kernel(); + return ret; } /* mtd_open */ /*====================================================================*/ @@ -137,7 +122,7 @@ static int mtd_close(struct inode *inode, struct file *file) DEBUG(MTD_DEBUG_LEVEL0, "MTD_close\n"); /* Only sync if opened RW */ - if ((file->f_mode & 2) && mtd->sync) + if ((file->f_mode & FMODE_WRITE) && mtd->sync) mtd->sync(mtd); put_mtd_device(mtd); @@ -341,7 +326,7 @@ static void mtdchar_erase_callback (struct erase_info *instr) wake_up((wait_queue_head_t *)instr->priv); } -#if defined(CONFIG_MTD_OTP) || defined(CONFIG_MTD_ONENAND_OTP) +#ifdef CONFIG_HAVE_MTD_OTP static int otp_select_filemode(struct mtd_file_info *mfi, int mode) { struct mtd_info *mtd = mfi->mtd; @@ -371,6 +356,100 @@ static int otp_select_filemode(struct mtd_file_info *mfi, int mode) # define otp_select_filemode(f,m) -EOPNOTSUPP #endif +static int mtd_do_writeoob(struct file *file, struct mtd_info *mtd, + uint64_t start, uint32_t length, void __user *ptr, + uint32_t __user *retp) +{ + struct mtd_oob_ops ops; + uint32_t retlen; + int ret = 0; + + if (!(file->f_mode & FMODE_WRITE)) + return -EPERM; + + if (length > 4096) + return -EINVAL; + + if (!mtd->write_oob) + ret = -EOPNOTSUPP; + else + ret = access_ok(VERIFY_READ, ptr, length) ? 0 : EFAULT; + + if (ret) + return ret; + + ops.ooblen = length; + ops.ooboffs = start & (mtd->oobsize - 1); + ops.datbuf = NULL; + ops.mode = MTD_OOB_PLACE; + + if (ops.ooboffs && ops.ooblen > (mtd->oobsize - ops.ooboffs)) + return -EINVAL; + + ops.oobbuf = kmalloc(length, GFP_KERNEL); + if (!ops.oobbuf) + return -ENOMEM; + + if (copy_from_user(ops.oobbuf, ptr, length)) { + kfree(ops.oobbuf); + return -EFAULT; + } + + start &= ~((uint64_t)mtd->oobsize - 1); + ret = mtd->write_oob(mtd, start, &ops); + + if (ops.oobretlen > 0xFFFFFFFFU) + ret = -EOVERFLOW; + retlen = ops.oobretlen; + if (copy_to_user(retp, &retlen, sizeof(length))) + ret = -EFAULT; + + kfree(ops.oobbuf); + return ret; +} + +static int mtd_do_readoob(struct mtd_info *mtd, uint64_t start, + uint32_t length, void __user *ptr, uint32_t __user *retp) +{ + struct mtd_oob_ops ops; + int ret = 0; + + if (length > 4096) + return -EINVAL; + + if (!mtd->read_oob) + ret = -EOPNOTSUPP; + else + ret = access_ok(VERIFY_WRITE, ptr, + length) ? 0 : -EFAULT; + if (ret) + return ret; + + ops.ooblen = length; + ops.ooboffs = start & (mtd->oobsize - 1); + ops.datbuf = NULL; + ops.mode = MTD_OOB_PLACE; + + if (ops.ooboffs && ops.ooblen > (mtd->oobsize - ops.ooboffs)) + return -EINVAL; + + ops.oobbuf = kmalloc(length, GFP_KERNEL); + if (!ops.oobbuf) + return -ENOMEM; + + start &= ~((uint64_t)mtd->oobsize - 1); + ret = mtd->read_oob(mtd, start, &ops); + + if (put_user(ops.oobretlen, retp)) + ret = -EFAULT; + else if (ops.oobretlen && copy_to_user(ptr, ops.oobbuf, + ops.oobretlen)) + ret = -EFAULT; + + kfree(ops.oobbuf); + return ret; +} + static int mtd_ioctl(struct inode *inode, struct file *file, u_int cmd, u_long arg) { @@ -401,16 +480,20 @@ static int mtd_ioctl(struct inode *inode, struct file *file, case MEMGETREGIONINFO: { - struct region_info_user ur; + uint32_t ur_idx; + struct mtd_erase_region_info *kr; + struct region_info_user *ur = (struct region_info_user *) argp; - if (copy_from_user(&ur, argp, sizeof(struct region_info_user))) + if (get_user(ur_idx, &(ur->regionindex))) return -EFAULT; - if (ur.regionindex >= mtd->numeraseregions) - return -EINVAL; - if (copy_to_user(argp, &(mtd->eraseregions[ur.regionindex]), - sizeof(struct mtd_erase_region_info))) + kr = &(mtd->eraseregions[ur_idx]); + + if (put_user(kr->offset, &(ur->offset)) + || put_user(kr->erasesize, &(ur->erasesize)) + || put_user(kr->numblocks, &(ur->numblocks))) return -EFAULT; + break; } @@ -429,10 +512,11 @@ static int mtd_ioctl(struct inode *inode, struct file *file, break; case MEMERASE: + case MEMERASE64: { struct erase_info *erase; - if(!(file->f_mode & 2)) + if(!(file->f_mode & FMODE_WRITE)) return -EPERM; erase=kzalloc(sizeof(struct erase_info),GFP_KERNEL); @@ -444,10 +528,26 @@ static int mtd_ioctl(struct inode *inode, struct file *file, init_waitqueue_head(&waitq); - if (copy_from_user(&erase->addr, argp, - sizeof(struct erase_info_user))) { - kfree(erase); - return -EFAULT; + if (cmd == MEMERASE64) { + struct erase_info_user64 einfo64; + + if (copy_from_user(&einfo64, argp, + sizeof(struct erase_info_user64))) { + kfree(erase); + return -EFAULT; + } + erase->addr = einfo64.start; + erase->len = einfo64.length; + } else { + struct erase_info_user einfo32; + + if (copy_from_user(&einfo32, argp, + sizeof(struct erase_info_user))) { + kfree(erase); + return -EFAULT; + } + erase->addr = einfo32.start; + erase->len = einfo32.length; } erase->mtd = mtd; erase->callback = mtdchar_erase_callback; @@ -482,128 +582,84 @@ static int mtd_ioctl(struct inode *inode, struct file *file, case MEMWRITEOOB: { struct mtd_oob_buf buf; - struct mtd_oob_ops ops; - uint32_t retlen; - - if(!(file->f_mode & 2)) - return -EPERM; - - if (copy_from_user(&buf, argp, sizeof(struct mtd_oob_buf))) - return -EFAULT; - - if (buf.length > 4096) - return -EINVAL; - - if (!mtd->write_oob) - ret = -EOPNOTSUPP; - else - ret = access_ok(VERIFY_READ, buf.ptr, - buf.length) ? 0 : EFAULT; - - if (ret) - return ret; - - ops.ooblen = buf.length; - ops.ooboffs = buf.start & (mtd->oobsize - 1); - ops.datbuf = NULL; - ops.mode = MTD_OOB_PLACE; + struct mtd_oob_buf __user *buf_user = argp; - if (ops.ooboffs && ops.ooblen > (mtd->oobsize - ops.ooboffs)) - return -EINVAL; - - ops.oobbuf = kmalloc(buf.length, GFP_KERNEL); - if (!ops.oobbuf) - return -ENOMEM; - - if (copy_from_user(ops.oobbuf, buf.ptr, buf.length)) { - kfree(ops.oobbuf); - return -EFAULT; - } - - buf.start &= ~(mtd->oobsize - 1); - ret = mtd->write_oob(mtd, buf.start, &ops); - - if (ops.oobretlen > 0xFFFFFFFFU) - ret = -EOVERFLOW; - retlen = ops.oobretlen; - if (copy_to_user(&((struct mtd_oob_buf *)argp)->length, - &retlen, sizeof(buf.length))) + /* NOTE: writes return length to buf_user->length */ + if (copy_from_user(&buf, argp, sizeof(buf))) ret = -EFAULT; - - kfree(ops.oobbuf); + else + ret = mtd_do_writeoob(file, mtd, buf.start, buf.length, + buf.ptr, &buf_user->length); break; - } case MEMREADOOB: { struct mtd_oob_buf buf; - struct mtd_oob_ops ops; - - if (copy_from_user(&buf, argp, sizeof(struct mtd_oob_buf))) - return -EFAULT; + struct mtd_oob_buf __user *buf_user = argp; - if (buf.length > 4096) - return -EINVAL; - - if (!mtd->read_oob) - ret = -EOPNOTSUPP; + /* NOTE: writes return length to buf_user->start */ + if (copy_from_user(&buf, argp, sizeof(buf))) + ret = -EFAULT; else - ret = access_ok(VERIFY_WRITE, buf.ptr, - buf.length) ? 0 : -EFAULT; - if (ret) - return ret; - - ops.ooblen = buf.length; - ops.ooboffs = buf.start & (mtd->oobsize - 1); - ops.datbuf = NULL; - ops.mode = MTD_OOB_PLACE; + ret = mtd_do_readoob(mtd, buf.start, buf.length, + buf.ptr, &buf_user->start); + break; + } - if (ops.ooboffs && ops.ooblen > (mtd->oobsize - ops.ooboffs)) - return -EINVAL; + case MEMWRITEOOB64: + { + struct mtd_oob_buf64 buf; + struct mtd_oob_buf64 __user *buf_user = argp; - ops.oobbuf = kmalloc(buf.length, GFP_KERNEL); - if (!ops.oobbuf) - return -ENOMEM; + if (copy_from_user(&buf, argp, sizeof(buf))) + ret = -EFAULT; + else + ret = mtd_do_writeoob(file, mtd, buf.start, buf.length, + (void __user *)(uintptr_t)buf.usr_ptr, + &buf_user->length); + break; + } - buf.start &= ~(mtd->oobsize - 1); - ret = mtd->read_oob(mtd, buf.start, &ops); + case MEMREADOOB64: + { + struct mtd_oob_buf64 buf; + struct mtd_oob_buf64 __user *buf_user = argp; - if (put_user(ops.oobretlen, (uint32_t __user *)argp)) - ret = -EFAULT; - else if (ops.oobretlen && copy_to_user(buf.ptr, ops.oobbuf, - ops.oobretlen)) + if (copy_from_user(&buf, argp, sizeof(buf))) ret = -EFAULT; - - kfree(ops.oobbuf); + else + ret = mtd_do_readoob(mtd, buf.start, buf.length, + (void __user *)(uintptr_t)buf.usr_ptr, + &buf_user->length); break; } case MEMLOCK: { - struct erase_info_user info; + struct erase_info_user einfo; - if (copy_from_user(&info, argp, sizeof(info))) + if (copy_from_user(&einfo, argp, sizeof(einfo))) return -EFAULT; if (!mtd->lock) ret = -EOPNOTSUPP; else - ret = mtd->lock(mtd, info.start, info.length); + ret = mtd->lock(mtd, einfo.start, einfo.length); break; } case MEMUNLOCK: { - struct erase_info_user info; + struct erase_info_user einfo; - if (copy_from_user(&info, argp, sizeof(info))) + if (copy_from_user(&einfo, argp, sizeof(einfo))) return -EFAULT; if (!mtd->unlock) ret = -EOPNOTSUPP; else - ret = mtd->unlock(mtd, info.start, info.length); + ret = mtd->unlock(mtd, einfo.start, einfo.length); break; } @@ -654,7 +710,7 @@ static int mtd_ioctl(struct inode *inode, struct file *file, break; } -#if defined(CONFIG_MTD_OTP) || defined(CONFIG_MTD_ONENAND_OTP) +#ifdef CONFIG_HAVE_MTD_OTP case OTPSELECT: { int mode; @@ -703,15 +759,15 @@ static int mtd_ioctl(struct inode *inode, struct file *file, case OTPLOCK: { - struct otp_info info; + struct otp_info oinfo; if (mfi->mode != MTD_MODE_OTP_USER) return -EINVAL; - if (copy_from_user(&info, argp, sizeof(info))) + if (copy_from_user(&oinfo, argp, sizeof(oinfo))) return -EFAULT; if (!mtd->lock_user_prot_reg) return -EOPNOTSUPP; - ret = mtd->lock_user_prot_reg(mtd, info.start, info.length); + ret = mtd->lock_user_prot_reg(mtd, oinfo.start, oinfo.length); break; } #endif @@ -766,47 +822,162 @@ static int mtd_ioctl(struct inode *inode, struct file *file, return ret; } /* memory_ioctl */ +#ifdef CONFIG_COMPAT + +struct mtd_oob_buf32 { + u_int32_t start; + u_int32_t length; + compat_caddr_t ptr; /* unsigned char* */ +}; + +#define MEMWRITEOOB32 _IOWR('M', 3, struct mtd_oob_buf32) +#define MEMREADOOB32 _IOWR('M', 4, struct mtd_oob_buf32) + +static long mtd_compat_ioctl(struct file *file, unsigned int cmd, + unsigned long arg) +{ + struct inode *inode = file->f_path.dentry->d_inode; + struct mtd_file_info *mfi = file->private_data; + struct mtd_info *mtd = mfi->mtd; + void __user *argp = compat_ptr(arg); + int ret = 0; + + lock_kernel(); + + switch (cmd) { + case MEMWRITEOOB32: + { + struct mtd_oob_buf32 buf; + struct mtd_oob_buf32 __user *buf_user = argp; + + if (copy_from_user(&buf, argp, sizeof(buf))) + ret = -EFAULT; + else + ret = mtd_do_writeoob(file, mtd, buf.start, + buf.length, compat_ptr(buf.ptr), + &buf_user->length); + break; + } + + case MEMREADOOB32: + { + struct mtd_oob_buf32 buf; + struct mtd_oob_buf32 __user *buf_user = argp; + + /* NOTE: writes return length to buf->start */ + if (copy_from_user(&buf, argp, sizeof(buf))) + ret = -EFAULT; + else + ret = mtd_do_readoob(mtd, buf.start, + buf.length, compat_ptr(buf.ptr), + &buf_user->start); + break; + } + default: + ret = mtd_ioctl(inode, file, cmd, (unsigned long)argp); + } + + unlock_kernel(); + + return ret; +} + +#endif /* CONFIG_COMPAT */ + +/* + * try to determine where a shared mapping can be made + * - only supported for NOMMU at the moment (MMU can't doesn't copy private + * mappings) + */ +#ifndef CONFIG_MMU +static unsigned long mtd_get_unmapped_area(struct file *file, + unsigned long addr, + unsigned long len, + unsigned long pgoff, + unsigned long flags) +{ + struct mtd_file_info *mfi = file->private_data; + struct mtd_info *mtd = mfi->mtd; + + if (mtd->get_unmapped_area) { + unsigned long offset; + + if (addr != 0) + return (unsigned long) -EINVAL; + + if (len > mtd->size || pgoff >= (mtd->size >> PAGE_SHIFT)) + return (unsigned long) -EINVAL; + + offset = pgoff << PAGE_SHIFT; + if (offset > mtd->size - len) + return (unsigned long) -EINVAL; + + return mtd->get_unmapped_area(mtd, len, offset, flags); + } + + /* can't map directly */ + return (unsigned long) -ENOSYS; +} +#endif + +/* + * set up a mapping for shared memory segments + */ +static int mtd_mmap(struct file *file, struct vm_area_struct *vma) +{ +#ifdef CONFIG_MMU + struct mtd_file_info *mfi = file->private_data; + struct mtd_info *mtd = mfi->mtd; + + if (mtd->type == MTD_RAM || mtd->type == MTD_ROM) + return 0; + return -ENOSYS; +#else + return vma->vm_flags & VM_SHARED ? 0 : -ENOSYS; +#endif +} + static const struct file_operations mtd_fops = { .owner = THIS_MODULE, .llseek = mtd_lseek, .read = mtd_read, .write = mtd_write, .ioctl = mtd_ioctl, +#ifdef CONFIG_COMPAT + .compat_ioctl = mtd_compat_ioctl, +#endif .open = mtd_open, .release = mtd_close, + .mmap = mtd_mmap, +#ifndef CONFIG_MMU + .get_unmapped_area = mtd_get_unmapped_area, +#endif }; static int __init init_mtdchar(void) { - if (register_chrdev(MTD_CHAR_MAJOR, "mtd", &mtd_fops)) { + 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 -EAGAIN; } - mtd_class = class_create(THIS_MODULE, "mtd"); - - if (IS_ERR(mtd_class)) { - printk(KERN_ERR "Error creating mtd class.\n"); - unregister_chrdev(MTD_CHAR_MAJOR, "mtd"); - return PTR_ERR(mtd_class); - } - - register_mtd_user(¬ifier); - return 0; + return status; } static void __exit cleanup_mtdchar(void) { - unregister_mtd_user(¬ifier); - class_destroy(mtd_class); unregister_chrdev(MTD_CHAR_MAJOR, "mtd"); } module_init(init_mtdchar); module_exit(cleanup_mtdchar); +MODULE_ALIAS_CHARDEV_MAJOR(MTD_CHAR_MAJOR); MODULE_LICENSE("GPL"); MODULE_AUTHOR("David Woodhouse "); MODULE_DESCRIPTION("Direct character-device access to MTD devices"); +MODULE_ALIAS_CHARDEV_MAJOR(MTD_CHAR_MAJOR);