// SPDX-License-Identifier: GPL-2.0-or-later/* * Copyright © 1999-2010 David Woodhouse <dwmw2@infradead.org> */ #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/mutex.h>#include <linux/backing-dev.h>#include <linux/compat.h>#include <linux/mount.h>#include <linux/blkpg.h>#include <linux/magic.h>#include <linux/major.h>#include <linux/mtd/mtd.h>#include <linux/mtd/partitions.h>#include <linux/mtd/map.h> #include <linux/uaccess.h> #include "mtdcore.h" /* * Data structure to hold the pointer to the mtd device as well * as mode information of various use cases. */struct mtd_file_info { struct mtd_info *mtd; enum mtd_file_modes mode;}; static loff_t mtdchar_lseek(struct file *file, loff_t offset, int orig){ struct mtd_file_info *mfi = file->private_data; return fixed_size_llseek(file, offset, orig, mfi->mtd->size);} static int mtdchar_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; pr_debug("MTD_open\n"); /* You can't open the RO devices RW */ if ((file->f_mode & FMODE_WRITE) && (minor & 1)) return -EACCES; mtd = get_mtd_device(NULL, devnum); if (IS_ERR(mtd)) return PTR_ERR(mtd); if (mtd->type == MTD_ABSENT) { ret = -ENODEV; goto out1; } /* You can't open it RW if it's not a writeable device */ if ((file->f_mode & FMODE_WRITE) && !(mtd->flags & MTD_WRITEABLE)) { ret = -EACCES; goto out1; } mfi = kzalloc_obj(*mfi); if (!mfi) { ret = -ENOMEM; goto out1; } mfi->mtd = mtd; file->private_data = mfi; return 0; out1: put_mtd_device(mtd); return ret;} /* mtdchar_open */ /*====================================================================*/ static int mtdchar_close(struct inode *inode, struct file *file){ struct mtd_file_info *mfi = file->private_data; struct mtd_info *mtd = mfi->mtd; pr_debug("MTD_close\n"); /* Only sync if opened RW */ if ((file->f_mode & FMODE_WRITE)) mtd_sync(mtd); put_mtd_device(mtd); file->private_data = NULL; kfree(mfi); return 0;} /* mtdchar_close */ /* Back in June 2001, dwmw2 wrote: * * FIXME: This _really_ needs to die. In 2.5, we should lock the * userspace buffer down and use it directly with readv/writev. * * The implementation below, using mtd_kmalloc_up_to, mitigates * allocation failures when the system is under low-memory situations * or if memory is highly fragmented at the cost of reducing the * performance of the requested transfer due to a smaller buffer size. * * A more complex but more memory-efficient implementation based on * get_user_pages and iovecs to cover extents of those pages is a * longer-term goal, as intimated by dwmw2 above. However, for the * write case, this requires yet more complex head and tail transfer * handling when those head and tail offsets and sizes are such that * alignment requirements are not met in the NAND subdriver. */ static ssize_t mtdchar_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; size_t total_retlen=0; int ret=0; int len; size_t size = count; char *kbuf; pr_debug("MTD_read\n"); if (*ppos + count > mtd->size) { if (*ppos < mtd->size) count = mtd->size - *ppos; else count = 0; } if (!count) return 0; kbuf = mtd_kmalloc_up_to(mtd, &size); if (!kbuf) return -ENOMEM; while (count) { len = min_t(size_t, count, size); switch (mfi->mode) { case MTD_FILE_MODE_OTP_FACTORY: ret = mtd_read_fact_prot_reg(mtd, *ppos, len, &retlen, kbuf); break; case MTD_FILE_MODE_OTP_USER: ret = mtd_read_user_prot_reg(mtd, *ppos, len, &retlen, kbuf); break; case MTD_FILE_MODE_RAW: { struct mtd_oob_ops ops = {}; ops.mode = MTD_OPS_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 occurred 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 || mtd_is_bitflip_or_eccerr(ret)) { *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;} /* mtdchar_read */ static ssize_t mtdchar_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; size_t size = count; char *kbuf; size_t retlen; size_t total_retlen=0; int ret=0; int len; pr_debug("MTD_write\n"); if (*ppos >= mtd->size) return -ENOSPC; if (*ppos + count > mtd->size) count = mtd->size - *ppos; if (!count) return 0; kbuf = mtd_kmalloc_up_to(mtd, &size); if (!kbuf) return -ENOMEM; while (count) { len = min_t(size_t, count, size); if (copy_from_user(kbuf, buf, len)) { kfree(kbuf); return -EFAULT; } switch (mfi->mode) { case MTD_FILE_MODE_OTP_FACTORY: ret = -EROFS; break; case MTD_FILE_MODE_OTP_USER: ret = mtd_write_user_prot_reg(mtd, *ppos, len, &retlen, kbuf); break; case MTD_FILE_MODE_RAW: { struct mtd_oob_ops ops = {}; ops.mode = MTD_OPS_RAW; ops.datbuf = kbuf; ops.oobbuf = NULL; ops.ooboffs = 0; ops.len = len; ret = mtd_write_oob(mtd, *ppos, &ops); retlen = ops.retlen; break; } default: ret = mtd_write(mtd, *ppos, len, &retlen, kbuf); } /* * Return -ENOSPC only if no data could be written at all. * Otherwise just return the number of bytes that actually * have been written. */ if ((ret == -ENOSPC) && (total_retlen)) break; if (!ret) { *ppos += retlen; total_retlen += retlen; count -= retlen; buf += retlen; } else { kfree(kbuf); return ret; } } kfree(kbuf); return total_retlen;} /* mtdchar_write */ /*====================================================================== IOCTL calls for getting device parameters. ======================================================================*/ static int otp_select_filemode(struct mtd_file_info *mfi, int mode){ struct mtd_info *mtd = mfi->mtd; size_t retlen; switch (mode) { case MTD_OTP_FACTORY: if (mtd_read_fact_prot_reg(mtd, -1, 0, &retlen, NULL) == -EOPNOTSUPP) return -EOPNOTSUPP; mfi->mode = MTD_FILE_MODE_OTP_FACTORY; break; case MTD_OTP_USER: if (mtd_read_user_prot_reg(mtd, -1, 0, &retlen, NULL) == -EOPNOTSUPP) return -EOPNOTSUPP; mfi->mode = MTD_FILE_MODE_OTP_USER; break; case MTD_OTP_OFF: mfi->mode = MTD_FILE_MODE_NORMAL; break; default: return -EINVAL; } return 0;} static int mtdchar_writeoob(struct file *file, struct mtd_info *mtd, uint64_t start, uint32_t length, void __user *ptr, uint32_t __user *retp){ struct mtd_info *master = mtd_get_master(mtd); struct mtd_file_info *mfi = file->private_data; struct mtd_oob_ops ops = {}; uint32_t retlen; int ret = 0; if (length > 4096) return -EINVAL; if (!master->_write_oob) return -EOPNOTSUPP; ops.ooblen = length; ops.ooboffs = start & (mtd->writesize - 1); ops.datbuf = NULL; ops.mode = (mfi->mode == MTD_FILE_MODE_RAW) ? MTD_OPS_RAW : MTD_OPS_PLACE_OOB; if (ops.ooboffs && ops.ooblen > (mtd->oobsize - ops.ooboffs)) return -EINVAL; ops.oobbuf = memdup_user(ptr, length); if (IS_ERR(ops.oobbuf)) return PTR_ERR(ops.oobbuf); start &= ~((uint64_t)mtd->writesize - 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 mtdchar_readoob(struct file *file, struct mtd_info *mtd, uint64_t start, uint32_t length, void __user *ptr, uint32_t __user *retp){ struct mtd_file_info *mfi = file->private_data; struct mtd_oob_ops ops = {}; int ret = 0; if (length > 4096) return -EINVAL; ops.ooblen = length; ops.ooboffs = start & (mtd->writesize - 1); ops.datbuf = NULL; ops.mode = (mfi->mode == MTD_FILE_MODE_RAW) ? MTD_OPS_RAW : MTD_OPS_PLACE_OOB; 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->writesize - 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); /* * NAND returns -EBADMSG on ECC errors, but it returns the OOB * 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 occurred and has * been corrected by the ECC algorithm. * * Note: currently the standard NAND function, nand_read_oob_std, * does not calculate ECC for the OOB area, so do not rely on * this behavior unless you have replaced it with your own. */ if (mtd_is_bitflip_or_eccerr(ret)) return 0; return ret;} /* * Copies (and truncates, if necessary) OOB layout information to the * deprecated layout struct, nand_ecclayout_user. This is necessary only to * support the deprecated API ioctl ECCGETLAYOUT while allowing all new * functionality to use mtd_ooblayout_ops flexibly (i.e. mtd_ooblayout_ops * can describe any kind of OOB layout with almost zero overhead from a * memory usage point of view). */static int shrink_ecclayout(struct mtd_info *mtd, struct nand_ecclayout_user *to){ struct mtd_oob_region oobregion; int i, section = 0, ret; if (!mtd || !to) return -EINVAL; memset(to, 0, sizeof(*to)); to->eccbytes = 0; for (i = 0; i < MTD_MAX_ECCPOS_ENTRIES;) { u32 eccpos; ret = mtd_ooblayout_ecc(mtd, section++, &oobregion); if (ret < 0) { if (ret != -ERANGE) return ret; break; } eccpos = oobregion.offset; for (; i < MTD_MAX_ECCPOS_ENTRIES && eccpos < oobregion.offset + oobregion.length; i++) { to->eccpos[i] = eccpos++; to->eccbytes++; } } for (i = 0; i < MTD_MAX_OOBFREE_ENTRIES; i++) { ret = mtd_ooblayout_free(mtd, i, &oobregion); if (ret < 0) { if (ret != -ERANGE) return ret; break; } to->oobfree[i].offset = oobregion.offset; to->oobfree[i].length = oobregion.length; to->oobavail += to->oobfree[i].length; } return 0;} static int get_oobinfo(struct mtd_info *mtd, struct nand_oobinfo *to){ struct mtd_oob_region oobregion; int i, section = 0, ret; if (!mtd || !to) return -EINVAL; memset(to, 0, sizeof(*to)); Lines 1–500 of 1426. Load more
// SPDX-License-Identifier: GPL-2.0-or-later /* * Copyright © 1999-2010 David Woodhouse <dwmw2@infradead.org> */ #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/mutex.h> #include <linux/backing-dev.h> #include <linux/compat.h> #include <linux/mount.h> #include <linux/blkpg.h> #include <linux/magic.h> #include <linux/major.h> #include <linux/mtd/mtd.h> #include <linux/mtd/partitions.h> #include <linux/mtd/map.h> #include <linux/uaccess.h> #include "mtdcore.h" /* * Data structure to hold the pointer to the mtd device as well * as mode information of various use cases. */ struct mtd_file_info { struct mtd_info *mtd; enum mtd_file_modes mode; }; static loff_t mtdchar_lseek(struct file *file, loff_t offset, int orig) { struct mtd_file_info *mfi = file->private_data; return fixed_size_llseek(file, offset, orig, mfi->mtd->size); } static int mtdchar_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; pr_debug("MTD_open\n"); /* You can't open the RO devices RW */ if ((file->f_mode & FMODE_WRITE) && (minor & 1)) return -EACCES; mtd = get_mtd_device(NULL, devnum); if (IS_ERR(mtd)) return PTR_ERR(mtd); if (mtd->type == MTD_ABSENT) { ret = -ENODEV; goto out1; } /* You can't open it RW if it's not a writeable device */ if ((file->f_mode & FMODE_WRITE) && !(mtd->flags & MTD_WRITEABLE)) { ret = -EACCES; goto out1; } mfi = kzalloc_obj(*mfi); if (!mfi) { ret = -ENOMEM; goto out1; } mfi->mtd = mtd; file->private_data = mfi; return 0; out1: put_mtd_device(mtd); return ret; } /* mtdchar_open */ /*====================================================================*/ static int mtdchar_close(struct inode *inode, struct file *file) { struct mtd_file_info *mfi = file->private_data; struct mtd_info *mtd = mfi->mtd; pr_debug("MTD_close\n"); /* Only sync if opened RW */ if ((file->f_mode & FMODE_WRITE)) mtd_sync(mtd); put_mtd_device(mtd); file->private_data = NULL; kfree(mfi); return 0; } /* mtdchar_close */ /* Back in June 2001, dwmw2 wrote: * * FIXME: This _really_ needs to die. In 2.5, we should lock the * userspace buffer down and use it directly with readv/writev. * * The implementation below, using mtd_kmalloc_up_to, mitigates * allocation failures when the system is under low-memory situations * or if memory is highly fragmented at the cost of reducing the * performance of the requested transfer due to a smaller buffer size. * * A more complex but more memory-efficient implementation based on * get_user_pages and iovecs to cover extents of those pages is a * longer-term goal, as intimated by dwmw2 above. However, for the * write case, this requires yet more complex head and tail transfer * handling when those head and tail offsets and sizes are such that * alignment requirements are not met in the NAND subdriver. */ static ssize_t mtdchar_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; size_t total_retlen=0; int ret=0; int len; size_t size = count; char *kbuf; pr_debug("MTD_read\n"); if (*ppos + count > mtd->size) { if (*ppos < mtd->size) count = mtd->size - *ppos; else count = 0; } if (!count) return 0; kbuf = mtd_kmalloc_up_to(mtd, &size); if (!kbuf) return -ENOMEM; while (count) { len = min_t(size_t, count, size); switch (mfi->mode) { case MTD_FILE_MODE_OTP_FACTORY: ret = mtd_read_fact_prot_reg(mtd, *ppos, len, &retlen, kbuf); break; case MTD_FILE_MODE_OTP_USER: ret = mtd_read_user_prot_reg(mtd, *ppos, len, &retlen, kbuf); break; case MTD_FILE_MODE_RAW: { struct mtd_oob_ops ops = {}; ops.mode = MTD_OPS_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 occurred 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 || mtd_is_bitflip_or_eccerr(ret)) { *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; } /* mtdchar_read */ static ssize_t mtdchar_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; size_t size = count; char *kbuf; size_t retlen; size_t total_retlen=0; int ret=0; int len; pr_debug("MTD_write\n"); if (*ppos >= mtd->size) return -ENOSPC; if (*ppos + count > mtd->size) count = mtd->size - *ppos; if (!count) return 0; kbuf = mtd_kmalloc_up_to(mtd, &size); if (!kbuf) return -ENOMEM; while (count) { len = min_t(size_t, count, size); if (copy_from_user(kbuf, buf, len)) { kfree(kbuf); return -EFAULT; } switch (mfi->mode) { case MTD_FILE_MODE_OTP_FACTORY: ret = -EROFS; break; case MTD_FILE_MODE_OTP_USER: ret = mtd_write_user_prot_reg(mtd, *ppos, len, &retlen, kbuf); break; case MTD_FILE_MODE_RAW: { struct mtd_oob_ops ops = {}; ops.mode = MTD_OPS_RAW; ops.datbuf = kbuf; ops.oobbuf = NULL; ops.ooboffs = 0; ops.len = len; ret = mtd_write_oob(mtd, *ppos, &ops); retlen = ops.retlen; break; } default: ret = mtd_write(mtd, *ppos, len, &retlen, kbuf); } /* * Return -ENOSPC only if no data could be written at all. * Otherwise just return the number of bytes that actually * have been written. */ if ((ret == -ENOSPC) && (total_retlen)) break; if (!ret) { *ppos += retlen; total_retlen += retlen; count -= retlen; buf += retlen; } else { kfree(kbuf); return ret; } } kfree(kbuf); return total_retlen; } /* mtdchar_write */ /*====================================================================== IOCTL calls for getting device parameters. ======================================================================*/ static int otp_select_filemode(struct mtd_file_info *mfi, int mode) { struct mtd_info *mtd = mfi->mtd; size_t retlen; switch (mode) { case MTD_OTP_FACTORY: if (mtd_read_fact_prot_reg(mtd, -1, 0, &retlen, NULL) == -EOPNOTSUPP) return -EOPNOTSUPP; mfi->mode = MTD_FILE_MODE_OTP_FACTORY; break; case MTD_OTP_USER: if (mtd_read_user_prot_reg(mtd, -1, 0, &retlen, NULL) == -EOPNOTSUPP) return -EOPNOTSUPP; mfi->mode = MTD_FILE_MODE_OTP_USER; break; case MTD_OTP_OFF: mfi->mode = MTD_FILE_MODE_NORMAL; break; default: return -EINVAL; } return 0; } static int mtdchar_writeoob(struct file *file, struct mtd_info *mtd, uint64_t start, uint32_t length, void __user *ptr, uint32_t __user *retp) { struct mtd_info *master = mtd_get_master(mtd); struct mtd_file_info *mfi = file->private_data; struct mtd_oob_ops ops = {}; uint32_t retlen; int ret = 0; if (length > 4096) return -EINVAL; if (!master->_write_oob) return -EOPNOTSUPP; ops.ooblen = length; ops.ooboffs = start & (mtd->writesize - 1); ops.datbuf = NULL; ops.mode = (mfi->mode == MTD_FILE_MODE_RAW) ? MTD_OPS_RAW : MTD_OPS_PLACE_OOB; if (ops.ooboffs && ops.ooblen > (mtd->oobsize - ops.ooboffs)) return -EINVAL; ops.oobbuf = memdup_user(ptr, length); if (IS_ERR(ops.oobbuf)) return PTR_ERR(ops.oobbuf); start &= ~((uint64_t)mtd->writesize - 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 mtdchar_readoob(struct file *file, struct mtd_info *mtd, uint64_t start, uint32_t length, void __user *ptr, uint32_t __user *retp) { struct mtd_file_info *mfi = file->private_data; struct mtd_oob_ops ops = {}; int ret = 0; if (length > 4096) return -EINVAL; ops.ooblen = length; ops.ooboffs = start & (mtd->writesize - 1); ops.datbuf = NULL; ops.mode = (mfi->mode == MTD_FILE_MODE_RAW) ? MTD_OPS_RAW : MTD_OPS_PLACE_OOB; 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->writesize - 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); /* * NAND returns -EBADMSG on ECC errors, but it returns the OOB * 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 occurred and has * been corrected by the ECC algorithm. * * Note: currently the standard NAND function, nand_read_oob_std, * does not calculate ECC for the OOB area, so do not rely on * this behavior unless you have replaced it with your own. */ if (mtd_is_bitflip_or_eccerr(ret)) return 0; return ret; } /* * Copies (and truncates, if necessary) OOB layout information to the * deprecated layout struct, nand_ecclayout_user. This is necessary only to * support the deprecated API ioctl ECCGETLAYOUT while allowing all new * functionality to use mtd_ooblayout_ops flexibly (i.e. mtd_ooblayout_ops * can describe any kind of OOB layout with almost zero overhead from a * memory usage point of view). */ static int shrink_ecclayout(struct mtd_info *mtd, struct nand_ecclayout_user *to) { struct mtd_oob_region oobregion; int i, section = 0, ret; if (!mtd || !to) return -EINVAL; memset(to, 0, sizeof(*to)); to->eccbytes = 0; for (i = 0; i < MTD_MAX_ECCPOS_ENTRIES;) { u32 eccpos; ret = mtd_ooblayout_ecc(mtd, section++, &oobregion); if (ret < 0) { if (ret != -ERANGE) return ret; break; } eccpos = oobregion.offset; for (; i < MTD_MAX_ECCPOS_ENTRIES && eccpos < oobregion.offset + oobregion.length; i++) { to->eccpos[i] = eccpos++; to->eccbytes++; } } for (i = 0; i < MTD_MAX_OOBFREE_ENTRIES; i++) { ret = mtd_ooblayout_free(mtd, i, &oobregion); if (ret < 0) { if (ret != -ERANGE) return ret; break; } to->oobfree[i].offset = oobregion.offset; to->oobfree[i].length = oobregion.length; to->oobavail += to->oobfree[i].length; } return 0; } static int get_oobinfo(struct mtd_info *mtd, struct nand_oobinfo *to) { struct mtd_oob_region oobregion; int i, section = 0, ret; if (!mtd || !to) return -EINVAL; memset(to, 0, sizeof(*to));