| lh | 9ed821d | 2023-04-07 01:36:19 -0700 | [diff] [blame] | 1 | /* | 
|  | 2 | * Copyright © 1999-2010 David Woodhouse <dwmw2@infradead.org> | 
|  | 3 | * | 
|  | 4 | * This program is free software; you can redistribute it and/or modify | 
|  | 5 | * it under the terms of the GNU General Public License as published by | 
|  | 6 | * the Free Software Foundation; either version 2 of the License, or | 
|  | 7 | * (at your option) any later version. | 
|  | 8 | * | 
|  | 9 | * This program is distributed in the hope that it will be useful, | 
|  | 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | 
|  | 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | 
|  | 12 | * GNU General Public License for more details. | 
|  | 13 | * | 
|  | 14 | * You should have received a copy of the GNU General Public License | 
|  | 15 | * along with this program; if not, write to the Free Software | 
|  | 16 | * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA | 
|  | 17 | * | 
|  | 18 | */ | 
|  | 19 |  | 
|  | 20 | #include <linux/device.h> | 
|  | 21 | #include <linux/fs.h> | 
|  | 22 | #include <linux/mm.h> | 
|  | 23 | #include <linux/err.h> | 
|  | 24 | #include <linux/init.h> | 
|  | 25 | #include <linux/kernel.h> | 
|  | 26 | #include <linux/module.h> | 
|  | 27 | #include <linux/slab.h> | 
|  | 28 | #include <linux/sched.h> | 
|  | 29 | #include <linux/mutex.h> | 
|  | 30 | #include <linux/backing-dev.h> | 
|  | 31 | #include <linux/compat.h> | 
|  | 32 | #include <linux/mount.h> | 
|  | 33 | #include <linux/blkpg.h> | 
|  | 34 | #include <linux/magic.h> | 
|  | 35 | #include <linux/mtd/mtd.h> | 
|  | 36 | #include <linux/mtd/partitions.h> | 
|  | 37 | #include <linux/mtd/map.h> | 
|  | 38 |  | 
|  | 39 | #include <asm/uaccess.h> | 
|  | 40 |  | 
|  | 41 | static DEFINE_MUTEX(mtd_mutex); | 
|  | 42 |  | 
|  | 43 | extern int g_zload_read_only_flag; | 
|  | 44 |  | 
|  | 45 | /* | 
|  | 46 | * Data structure to hold the pointer to the mtd device as well | 
|  | 47 | * as mode information of various use cases. | 
|  | 48 | */ | 
|  | 49 | struct mtd_file_info { | 
|  | 50 | struct mtd_info *mtd; | 
|  | 51 | struct inode *ino; | 
|  | 52 | enum mtd_file_modes mode; | 
|  | 53 | }; | 
|  | 54 |  | 
|  | 55 | static loff_t mtdchar_lseek(struct file *file, loff_t offset, int orig) | 
|  | 56 | { | 
|  | 57 | struct mtd_file_info *mfi = file->private_data; | 
|  | 58 | struct mtd_info *mtd = mfi->mtd; | 
|  | 59 |  | 
|  | 60 | switch (orig) { | 
|  | 61 | case SEEK_SET: | 
|  | 62 | break; | 
|  | 63 | case SEEK_CUR: | 
|  | 64 | offset += file->f_pos; | 
|  | 65 | break; | 
|  | 66 | case SEEK_END: | 
|  | 67 | offset += mtd->size; | 
|  | 68 | break; | 
|  | 69 | default: | 
|  | 70 | return -EINVAL; | 
|  | 71 | } | 
|  | 72 |  | 
|  | 73 | if (offset >= 0 && offset <= mtd->size) | 
|  | 74 | return file->f_pos = offset; | 
|  | 75 |  | 
|  | 76 | return -EINVAL; | 
|  | 77 | } | 
|  | 78 |  | 
|  | 79 | static int count; | 
|  | 80 | static struct vfsmount *mnt; | 
|  | 81 | static struct file_system_type mtd_inodefs_type; | 
|  | 82 |  | 
|  | 83 | static int mtdchar_open(struct inode *inode, struct file *file) | 
|  | 84 | { | 
|  | 85 | int minor = iminor(inode); | 
|  | 86 | int devnum = minor >> 1; | 
|  | 87 | int ret = 0; | 
|  | 88 | struct mtd_info *mtd; | 
|  | 89 | struct mtd_file_info *mfi; | 
|  | 90 | struct inode *mtd_ino; | 
|  | 91 |  | 
|  | 92 | pr_debug("MTD_open\n"); | 
|  | 93 |  | 
|  | 94 | /* You can't open the RO devices RW */ | 
|  | 95 | if ((file->f_mode & FMODE_WRITE) && (minor & 1)) | 
|  | 96 | return -EACCES; | 
|  | 97 |  | 
|  | 98 | ret = simple_pin_fs(&mtd_inodefs_type, &mnt, &count); | 
|  | 99 | if (ret) | 
|  | 100 | return ret; | 
|  | 101 |  | 
|  | 102 | mutex_lock(&mtd_mutex); | 
|  | 103 | mtd = get_mtd_device(NULL, devnum); | 
|  | 104 |  | 
|  | 105 | if (IS_ERR(mtd)) { | 
|  | 106 | ret = PTR_ERR(mtd); | 
|  | 107 | goto out; | 
|  | 108 | } | 
|  | 109 |  | 
|  | 110 | if (mtd->type == MTD_ABSENT) { | 
|  | 111 | ret = -ENODEV; | 
|  | 112 | goto out1; | 
|  | 113 | } | 
|  | 114 |  | 
|  | 115 | mtd_ino = iget_locked(mnt->mnt_sb, devnum); | 
|  | 116 | if (!mtd_ino) { | 
|  | 117 | ret = -ENOMEM; | 
|  | 118 | goto out1; | 
|  | 119 | } | 
|  | 120 | if (mtd_ino->i_state & I_NEW) { | 
|  | 121 | mtd_ino->i_private = mtd; | 
|  | 122 | mtd_ino->i_mode = S_IFCHR; | 
|  | 123 | mtd_ino->i_data.backing_dev_info = mtd->backing_dev_info; | 
|  | 124 | unlock_new_inode(mtd_ino); | 
|  | 125 | } | 
|  | 126 | file->f_mapping = mtd_ino->i_mapping; | 
|  | 127 |  | 
|  | 128 | /* You can't open it RW if it's not a writeable device */ | 
|  | 129 | if ((file->f_mode & FMODE_WRITE) && !(mtd->flags & MTD_WRITEABLE)) { | 
|  | 130 | ret = -EACCES; | 
|  | 131 | goto out2; | 
|  | 132 | } | 
|  | 133 |  | 
|  | 134 | mfi = kzalloc(sizeof(*mfi), GFP_KERNEL); | 
|  | 135 | if (!mfi) { | 
|  | 136 | ret = -ENOMEM; | 
|  | 137 | goto out2; | 
|  | 138 | } | 
|  | 139 | mfi->ino = mtd_ino; | 
|  | 140 | mfi->mtd = mtd; | 
|  | 141 | file->private_data = mfi; | 
|  | 142 | mutex_unlock(&mtd_mutex); | 
|  | 143 | return 0; | 
|  | 144 |  | 
|  | 145 | out2: | 
|  | 146 | iput(mtd_ino); | 
|  | 147 | out1: | 
|  | 148 | put_mtd_device(mtd); | 
|  | 149 | out: | 
|  | 150 | mutex_unlock(&mtd_mutex); | 
|  | 151 | simple_release_fs(&mnt, &count); | 
|  | 152 | return ret; | 
|  | 153 | } /* mtdchar_open */ | 
|  | 154 |  | 
|  | 155 | /*====================================================================*/ | 
|  | 156 |  | 
|  | 157 | static int mtdchar_close(struct inode *inode, struct file *file) | 
|  | 158 | { | 
|  | 159 | struct mtd_file_info *mfi = file->private_data; | 
|  | 160 | struct mtd_info *mtd = mfi->mtd; | 
|  | 161 |  | 
|  | 162 | pr_debug("MTD_close\n"); | 
|  | 163 |  | 
|  | 164 | /* Only sync if opened RW */ | 
|  | 165 | if ((file->f_mode & FMODE_WRITE)) | 
|  | 166 | mtd_sync(mtd); | 
|  | 167 |  | 
|  | 168 | iput(mfi->ino); | 
|  | 169 |  | 
|  | 170 | put_mtd_device(mtd); | 
|  | 171 | file->private_data = NULL; | 
|  | 172 | kfree(mfi); | 
|  | 173 | simple_release_fs(&mnt, &count); | 
|  | 174 |  | 
|  | 175 | return 0; | 
|  | 176 | } /* mtdchar_close */ | 
|  | 177 |  | 
|  | 178 | /* Back in June 2001, dwmw2 wrote: | 
|  | 179 | * | 
|  | 180 | *   FIXME: This _really_ needs to die. In 2.5, we should lock the | 
|  | 181 | *   userspace buffer down and use it directly with readv/writev. | 
|  | 182 | * | 
|  | 183 | * The implementation below, using mtd_kmalloc_up_to, mitigates | 
|  | 184 | * allocation failures when the system is under low-memory situations | 
|  | 185 | * or if memory is highly fragmented at the cost of reducing the | 
|  | 186 | * performance of the requested transfer due to a smaller buffer size. | 
|  | 187 | * | 
|  | 188 | * A more complex but more memory-efficient implementation based on | 
|  | 189 | * get_user_pages and iovecs to cover extents of those pages is a | 
|  | 190 | * longer-term goal, as intimated by dwmw2 above. However, for the | 
|  | 191 | * write case, this requires yet more complex head and tail transfer | 
|  | 192 | * handling when those head and tail offsets and sizes are such that | 
|  | 193 | * alignment requirements are not met in the NAND subdriver. | 
|  | 194 | */ | 
|  | 195 |  | 
|  | 196 | static ssize_t mtdchar_read(struct file *file, char __user *buf, size_t count, | 
|  | 197 | loff_t *ppos) | 
|  | 198 | { | 
|  | 199 | struct mtd_file_info *mfi = file->private_data; | 
|  | 200 | struct mtd_info *mtd = mfi->mtd; | 
|  | 201 | size_t retlen; | 
|  | 202 | size_t total_retlen=0; | 
|  | 203 | int ret=0; | 
|  | 204 | int len; | 
|  | 205 | size_t size = count; | 
|  | 206 | char *kbuf; | 
|  | 207 |  | 
|  | 208 | pr_debug("MTD_read\n"); | 
|  | 209 |  | 
|  | 210 | if (*ppos + count > mtd->size) | 
|  | 211 | count = mtd->size - *ppos; | 
|  | 212 |  | 
|  | 213 | if (!count) | 
|  | 214 | return 0; | 
|  | 215 |  | 
|  | 216 | kbuf = mtd_kmalloc_up_to(mtd, &size); | 
|  | 217 | if (!kbuf) | 
|  | 218 | return -ENOMEM; | 
|  | 219 |  | 
|  | 220 | while (count) { | 
|  | 221 | len = min_t(size_t, count, size); | 
|  | 222 |  | 
|  | 223 | switch (mfi->mode) { | 
|  | 224 | case MTD_FILE_MODE_OTP_FACTORY: | 
|  | 225 | ret = mtd_read_fact_prot_reg(mtd, *ppos, len, | 
|  | 226 | &retlen, kbuf); | 
|  | 227 | break; | 
|  | 228 | case MTD_FILE_MODE_OTP_USER: | 
|  | 229 | ret = mtd_read_user_prot_reg(mtd, *ppos, len, | 
|  | 230 | &retlen, kbuf); | 
|  | 231 | break; | 
|  | 232 | case MTD_FILE_MODE_RAW: | 
|  | 233 | { | 
|  | 234 | struct mtd_oob_ops ops; | 
|  | 235 |  | 
|  | 236 | ops.mode = MTD_OPS_RAW; | 
|  | 237 | ops.datbuf = kbuf; | 
|  | 238 | ops.oobbuf = NULL; | 
|  | 239 | ops.len = len; | 
|  | 240 |  | 
|  | 241 | ret = mtd_read_oob(mtd, *ppos, &ops); | 
|  | 242 | retlen = ops.retlen; | 
|  | 243 | break; | 
|  | 244 | } | 
|  | 245 | default: | 
|  | 246 | ret = mtd_read(mtd, *ppos, len, &retlen, kbuf); | 
|  | 247 | } | 
|  | 248 | /* Nand returns -EBADMSG on ECC errors, but it returns | 
|  | 249 | * the data. For our userspace tools it is important | 
|  | 250 | * to dump areas with ECC errors! | 
|  | 251 | * For kernel internal usage it also might return -EUCLEAN | 
|  | 252 | * to signal the caller that a bitflip has occurred and has | 
|  | 253 | * been corrected by the ECC algorithm. | 
|  | 254 | * Userspace software which accesses NAND this way | 
|  | 255 | * must be aware of the fact that it deals with NAND | 
|  | 256 | */ | 
|  | 257 | if (!ret || mtd_is_bitflip_or_eccerr(ret)) { | 
|  | 258 | *ppos += retlen; | 
|  | 259 | if (copy_to_user(buf, kbuf, retlen)) { | 
|  | 260 | kfree(kbuf); | 
|  | 261 | return -EFAULT; | 
|  | 262 | } | 
|  | 263 | else | 
|  | 264 | total_retlen += retlen; | 
|  | 265 |  | 
|  | 266 | count -= retlen; | 
|  | 267 | buf += retlen; | 
|  | 268 | if (retlen == 0) | 
|  | 269 | count = 0; | 
|  | 270 | } | 
|  | 271 | else { | 
|  | 272 | kfree(kbuf); | 
|  | 273 | return ret; | 
|  | 274 | } | 
|  | 275 |  | 
|  | 276 | } | 
|  | 277 |  | 
|  | 278 | kfree(kbuf); | 
|  | 279 | return total_retlen; | 
|  | 280 | } /* mtdchar_read */ | 
|  | 281 |  | 
|  | 282 | static ssize_t mtdchar_write(struct file *file, const char __user *buf, size_t count, | 
|  | 283 | loff_t *ppos) | 
|  | 284 | { | 
|  | 285 | struct mtd_file_info *mfi = file->private_data; | 
|  | 286 | struct mtd_info *mtd = mfi->mtd; | 
|  | 287 | size_t size = count; | 
|  | 288 | char *kbuf; | 
|  | 289 | size_t retlen; | 
|  | 290 | size_t total_retlen=0; | 
|  | 291 | int ret=0; | 
|  | 292 | int len; | 
|  | 293 |  | 
|  | 294 | pr_debug("MTD_write\n"); | 
|  | 295 |  | 
|  | 296 | if (*ppos == mtd->size) | 
|  | 297 | return -ENOSPC; | 
|  | 298 |  | 
|  | 299 | if (*ppos + count > mtd->size) | 
|  | 300 | count = mtd->size - *ppos; | 
|  | 301 |  | 
|  | 302 | if (!count) | 
|  | 303 | return 0; | 
|  | 304 |  | 
|  | 305 | kbuf = mtd_kmalloc_up_to(mtd, &size); | 
|  | 306 | if (!kbuf) | 
|  | 307 | return -ENOMEM; | 
|  | 308 |  | 
|  | 309 | while (count) { | 
|  | 310 | len = min_t(size_t, count, size); | 
|  | 311 |  | 
|  | 312 | if (copy_from_user(kbuf, buf, len)) { | 
|  | 313 | kfree(kbuf); | 
|  | 314 | return -EFAULT; | 
|  | 315 | } | 
|  | 316 |  | 
|  | 317 | switch (mfi->mode) { | 
|  | 318 | case MTD_FILE_MODE_OTP_FACTORY: | 
|  | 319 | ret = -EROFS; | 
|  | 320 | break; | 
|  | 321 | case MTD_FILE_MODE_OTP_USER: | 
|  | 322 | ret = mtd_write_user_prot_reg(mtd, *ppos, len, | 
|  | 323 | &retlen, kbuf); | 
|  | 324 | break; | 
|  | 325 |  | 
|  | 326 | case MTD_FILE_MODE_RAW: | 
|  | 327 | { | 
|  | 328 | struct mtd_oob_ops ops; | 
|  | 329 |  | 
|  | 330 | ops.mode = MTD_OPS_RAW; | 
|  | 331 | ops.datbuf = kbuf; | 
|  | 332 | ops.oobbuf = NULL; | 
|  | 333 | ops.ooboffs = 0; | 
|  | 334 | ops.len = len; | 
|  | 335 |  | 
|  | 336 | ret = mtd_write_oob(mtd, *ppos, &ops); | 
|  | 337 | retlen = ops.retlen; | 
|  | 338 | break; | 
|  | 339 | } | 
|  | 340 |  | 
|  | 341 | default: | 
|  | 342 | ret = mtd_write(mtd, *ppos, len, &retlen, kbuf); | 
|  | 343 | } | 
|  | 344 | if (!ret) { | 
|  | 345 | *ppos += retlen; | 
|  | 346 | total_retlen += retlen; | 
|  | 347 | count -= retlen; | 
|  | 348 | buf += retlen; | 
|  | 349 | } | 
|  | 350 | else { | 
|  | 351 | kfree(kbuf); | 
|  | 352 | return ret; | 
|  | 353 | } | 
|  | 354 | } | 
|  | 355 |  | 
|  | 356 | kfree(kbuf); | 
|  | 357 | return total_retlen; | 
|  | 358 | } /* mtdchar_write */ | 
|  | 359 |  | 
|  | 360 | /*====================================================================== | 
|  | 361 |  | 
|  | 362 | IOCTL calls for getting device parameters. | 
|  | 363 |  | 
|  | 364 | ======================================================================*/ | 
|  | 365 | static void mtdchar_erase_callback (struct erase_info *instr) | 
|  | 366 | { | 
|  | 367 | wake_up((wait_queue_head_t *)instr->priv); | 
|  | 368 | } | 
|  | 369 |  | 
|  | 370 | #ifdef CONFIG_HAVE_MTD_OTP | 
|  | 371 | static int otp_select_filemode(struct mtd_file_info *mfi, int mode) | 
|  | 372 | { | 
|  | 373 | struct mtd_info *mtd = mfi->mtd; | 
|  | 374 | size_t retlen; | 
|  | 375 | int ret = 0; | 
|  | 376 |  | 
|  | 377 | /* | 
|  | 378 | * Make a fake call to mtd_read_fact_prot_reg() to check if OTP | 
|  | 379 | * operations are supported. | 
|  | 380 | */ | 
|  | 381 | if (mtd_read_fact_prot_reg(mtd, -1, 0, &retlen, NULL) == -EOPNOTSUPP) | 
|  | 382 | return -EOPNOTSUPP; | 
|  | 383 |  | 
|  | 384 | switch (mode) { | 
|  | 385 | case MTD_OTP_FACTORY: | 
|  | 386 | mfi->mode = MTD_FILE_MODE_OTP_FACTORY; | 
|  | 387 | break; | 
|  | 388 | case MTD_OTP_USER: | 
|  | 389 | mfi->mode = MTD_FILE_MODE_OTP_USER; | 
|  | 390 | break; | 
|  | 391 | default: | 
|  | 392 | ret = -EINVAL; | 
|  | 393 | case MTD_OTP_OFF: | 
|  | 394 | break; | 
|  | 395 | } | 
|  | 396 | return ret; | 
|  | 397 | } | 
|  | 398 | #else | 
|  | 399 | # define otp_select_filemode(f,m)	-EOPNOTSUPP | 
|  | 400 | #endif | 
|  | 401 |  | 
|  | 402 | static int mtdchar_writeoob(struct file *file, struct mtd_info *mtd, | 
|  | 403 | uint64_t start, uint32_t length, void __user *ptr, | 
|  | 404 | uint32_t __user *retp) | 
|  | 405 | { | 
|  | 406 | struct mtd_file_info *mfi = file->private_data; | 
|  | 407 | struct mtd_oob_ops ops; | 
|  | 408 | uint32_t retlen; | 
|  | 409 | int ret = 0; | 
|  | 410 |  | 
|  | 411 | if (!(file->f_mode & FMODE_WRITE)) | 
|  | 412 | return -EPERM; | 
|  | 413 |  | 
|  | 414 | if (length > 4096) | 
|  | 415 | return -EINVAL; | 
|  | 416 |  | 
|  | 417 | if (!mtd->_write_oob) | 
|  | 418 | ret = -EOPNOTSUPP; | 
|  | 419 | else | 
|  | 420 | ret = access_ok(VERIFY_READ, ptr, length) ? 0 : -EFAULT; | 
|  | 421 |  | 
|  | 422 | if (ret) | 
|  | 423 | return ret; | 
|  | 424 |  | 
|  | 425 | ops.ooblen = length; | 
|  | 426 | ops.ooboffs = start & (mtd->writesize - 1); | 
|  | 427 | ops.datbuf = NULL; | 
|  | 428 | ops.mode = (mfi->mode == MTD_FILE_MODE_RAW) ? MTD_OPS_RAW : | 
|  | 429 | MTD_OPS_PLACE_OOB; | 
|  | 430 |  | 
|  | 431 | if (ops.ooboffs && ops.ooblen > (mtd->oobsize - ops.ooboffs)) | 
|  | 432 | return -EINVAL; | 
|  | 433 |  | 
|  | 434 | ops.oobbuf = memdup_user(ptr, length); | 
|  | 435 | if (IS_ERR(ops.oobbuf)) | 
|  | 436 | return PTR_ERR(ops.oobbuf); | 
|  | 437 |  | 
|  | 438 | start &= ~((uint64_t)mtd->writesize - 1); | 
|  | 439 | ret = mtd_write_oob(mtd, start, &ops); | 
|  | 440 |  | 
|  | 441 | if (ops.oobretlen > 0xFFFFFFFFU) | 
|  | 442 | ret = -EOVERFLOW; | 
|  | 443 | retlen = ops.oobretlen; | 
|  | 444 | if (copy_to_user(retp, &retlen, sizeof(length))) | 
|  | 445 | ret = -EFAULT; | 
|  | 446 |  | 
|  | 447 | kfree(ops.oobbuf); | 
|  | 448 | return ret; | 
|  | 449 | } | 
|  | 450 |  | 
|  | 451 | static int mtdchar_readoob(struct file *file, struct mtd_info *mtd, | 
|  | 452 | uint64_t start, uint32_t length, void __user *ptr, | 
|  | 453 | uint32_t __user *retp) | 
|  | 454 | { | 
|  | 455 | struct mtd_file_info *mfi = file->private_data; | 
|  | 456 | struct mtd_oob_ops ops; | 
|  | 457 | int ret = 0; | 
|  | 458 |  | 
|  | 459 | if (length > 4096) | 
|  | 460 | return -EINVAL; | 
|  | 461 |  | 
|  | 462 | if (!access_ok(VERIFY_WRITE, ptr, length)) | 
|  | 463 | return -EFAULT; | 
|  | 464 |  | 
|  | 465 | ops.ooblen = length; | 
|  | 466 | ops.ooboffs = start & (mtd->writesize - 1); | 
|  | 467 | ops.datbuf = NULL; | 
|  | 468 | ops.mode = (mfi->mode == MTD_FILE_MODE_RAW) ? MTD_OPS_RAW : | 
|  | 469 | MTD_OPS_PLACE_OOB; | 
|  | 470 |  | 
|  | 471 | if (ops.ooboffs && ops.ooblen > (mtd->oobsize - ops.ooboffs)) | 
|  | 472 | return -EINVAL; | 
|  | 473 |  | 
|  | 474 | ops.oobbuf = kmalloc(length, GFP_KERNEL); | 
|  | 475 | if (!ops.oobbuf) | 
|  | 476 | return -ENOMEM; | 
|  | 477 |  | 
|  | 478 | start &= ~((uint64_t)mtd->writesize - 1); | 
|  | 479 | ret = mtd_read_oob(mtd, start, &ops); | 
|  | 480 |  | 
|  | 481 | if (put_user(ops.oobretlen, retp)) | 
|  | 482 | ret = -EFAULT; | 
|  | 483 | else if (ops.oobretlen && copy_to_user(ptr, ops.oobbuf, | 
|  | 484 | ops.oobretlen)) | 
|  | 485 | ret = -EFAULT; | 
|  | 486 |  | 
|  | 487 | kfree(ops.oobbuf); | 
|  | 488 |  | 
|  | 489 | /* | 
|  | 490 | * NAND returns -EBADMSG on ECC errors, but it returns the OOB | 
|  | 491 | * data. For our userspace tools it is important to dump areas | 
|  | 492 | * with ECC errors! | 
|  | 493 | * For kernel internal usage it also might return -EUCLEAN | 
|  | 494 | * to signal the caller that a bitflip has occured and has | 
|  | 495 | * been corrected by the ECC algorithm. | 
|  | 496 | * | 
|  | 497 | * Note: currently the standard NAND function, nand_read_oob_std, | 
|  | 498 | * does not calculate ECC for the OOB area, so do not rely on | 
|  | 499 | * this behavior unless you have replaced it with your own. | 
|  | 500 | */ | 
|  | 501 | if (mtd_is_bitflip_or_eccerr(ret)) | 
|  | 502 | return 0; | 
|  | 503 |  | 
|  | 504 | return ret; | 
|  | 505 | } | 
|  | 506 |  | 
|  | 507 | /* | 
|  | 508 | * Copies (and truncates, if necessary) data from the larger struct, | 
|  | 509 | * nand_ecclayout, to the smaller, deprecated layout struct, | 
|  | 510 | * nand_ecclayout_user. This is necessary only to support the deprecated | 
|  | 511 | * API ioctl ECCGETLAYOUT while allowing all new functionality to use | 
|  | 512 | * nand_ecclayout flexibly (i.e. the struct may change size in new | 
|  | 513 | * releases without requiring major rewrites). | 
|  | 514 | */ | 
|  | 515 | static int shrink_ecclayout(const struct nand_ecclayout *from, | 
|  | 516 | struct nand_ecclayout_user *to) | 
|  | 517 | { | 
|  | 518 | int i; | 
|  | 519 |  | 
|  | 520 | if (!from || !to) | 
|  | 521 | return -EINVAL; | 
|  | 522 |  | 
|  | 523 | memset(to, 0, sizeof(*to)); | 
|  | 524 |  | 
|  | 525 | to->eccbytes = min((int)from->eccbytes, MTD_MAX_ECCPOS_ENTRIES); | 
|  | 526 | for (i = 0; i < to->eccbytes; i++) | 
|  | 527 | to->eccpos[i] = from->eccpos[i]; | 
|  | 528 |  | 
|  | 529 | for (i = 0; i < MTD_MAX_OOBFREE_ENTRIES; i++) { | 
|  | 530 | if (from->oobfree[i].length == 0 && | 
|  | 531 | from->oobfree[i].offset == 0) | 
|  | 532 | break; | 
|  | 533 | to->oobavail += from->oobfree[i].length; | 
|  | 534 | to->oobfree[i] = from->oobfree[i]; | 
|  | 535 | } | 
|  | 536 |  | 
|  | 537 | return 0; | 
|  | 538 | } | 
|  | 539 |  | 
|  | 540 | static int mtdchar_blkpg_ioctl(struct mtd_info *mtd, | 
|  | 541 | struct blkpg_ioctl_arg __user *arg) | 
|  | 542 | { | 
|  | 543 | struct blkpg_ioctl_arg a; | 
|  | 544 | struct blkpg_partition p; | 
|  | 545 |  | 
|  | 546 | if (!capable(CAP_SYS_ADMIN)) | 
|  | 547 | return -EPERM; | 
|  | 548 |  | 
|  | 549 | if (copy_from_user(&a, arg, sizeof(struct blkpg_ioctl_arg))) | 
|  | 550 | return -EFAULT; | 
|  | 551 |  | 
|  | 552 | if (copy_from_user(&p, a.data, sizeof(struct blkpg_partition))) | 
|  | 553 | return -EFAULT; | 
|  | 554 |  | 
|  | 555 | switch (a.op) { | 
|  | 556 | case BLKPG_ADD_PARTITION: | 
|  | 557 |  | 
|  | 558 | /* Only master mtd device must be used to add partitions */ | 
|  | 559 | if (mtd_is_partition(mtd)) | 
|  | 560 | return -EINVAL; | 
|  | 561 |  | 
|  | 562 | return mtd_add_partition(mtd, p.devname, p.start, p.length); | 
|  | 563 |  | 
|  | 564 | case BLKPG_DEL_PARTITION: | 
|  | 565 |  | 
|  | 566 | if (p.pno < 0) | 
|  | 567 | return -EINVAL; | 
|  | 568 |  | 
|  | 569 | return mtd_del_partition(mtd, p.pno); | 
|  | 570 |  | 
|  | 571 | default: | 
|  | 572 | return -EINVAL; | 
|  | 573 | } | 
|  | 574 | } | 
|  | 575 |  | 
|  | 576 | static int mtdchar_write_ioctl(struct mtd_info *mtd, | 
|  | 577 | struct mtd_write_req __user *argp) | 
|  | 578 | { | 
|  | 579 | struct mtd_write_req req; | 
|  | 580 | struct mtd_oob_ops ops; | 
|  | 581 | void __user *usr_data, *usr_oob; | 
|  | 582 | int ret; | 
|  | 583 |  | 
|  | 584 | if (copy_from_user(&req, argp, sizeof(req)) || | 
|  | 585 | !access_ok(VERIFY_READ, req.usr_data, req.len) || | 
|  | 586 | !access_ok(VERIFY_READ, req.usr_oob, req.ooblen)) | 
|  | 587 | return -EFAULT; | 
|  | 588 | if (!mtd->_write_oob) | 
|  | 589 | return -EOPNOTSUPP; | 
|  | 590 |  | 
|  | 591 | ops.mode = req.mode; | 
|  | 592 | ops.len = (size_t)req.len; | 
|  | 593 | ops.ooblen = (size_t)req.ooblen; | 
|  | 594 | ops.ooboffs = 0; | 
|  | 595 |  | 
|  | 596 | usr_data = (void __user *)(uintptr_t)req.usr_data; | 
|  | 597 | usr_oob = (void __user *)(uintptr_t)req.usr_oob; | 
|  | 598 |  | 
|  | 599 | if (req.usr_data) { | 
|  | 600 | ops.datbuf = memdup_user(usr_data, ops.len); | 
|  | 601 | if (IS_ERR(ops.datbuf)) | 
|  | 602 | return PTR_ERR(ops.datbuf); | 
|  | 603 | } else { | 
|  | 604 | ops.datbuf = NULL; | 
|  | 605 | } | 
|  | 606 |  | 
|  | 607 | if (req.usr_oob) { | 
|  | 608 | ops.oobbuf = memdup_user(usr_oob, ops.ooblen); | 
|  | 609 | if (IS_ERR(ops.oobbuf)) { | 
|  | 610 | kfree(ops.datbuf); | 
|  | 611 | return PTR_ERR(ops.oobbuf); | 
|  | 612 | } | 
|  | 613 | } else { | 
|  | 614 | ops.oobbuf = NULL; | 
|  | 615 | } | 
|  | 616 |  | 
|  | 617 | ret = mtd_write_oob(mtd, (loff_t)req.start, &ops); | 
|  | 618 |  | 
|  | 619 | kfree(ops.datbuf); | 
|  | 620 | kfree(ops.oobbuf); | 
|  | 621 |  | 
|  | 622 | return ret; | 
|  | 623 | } | 
|  | 624 |  | 
|  | 625 | static int mtdchar_ioctl(struct file *file, u_int cmd, u_long arg) | 
|  | 626 | { | 
|  | 627 | struct mtd_file_info *mfi = file->private_data; | 
|  | 628 | struct mtd_info *mtd = mfi->mtd; | 
|  | 629 | void __user *argp = (void __user *)arg; | 
|  | 630 | int ret = 0; | 
|  | 631 | u_long size; | 
|  | 632 | struct mtd_info_user info; | 
|  | 633 |  | 
|  | 634 | pr_debug("MTD_ioctl\n"); | 
|  | 635 |  | 
|  | 636 | size = (cmd & IOCSIZE_MASK) >> IOCSIZE_SHIFT; | 
|  | 637 | if (cmd & IOC_IN) { | 
|  | 638 | if (!access_ok(VERIFY_READ, argp, size)) | 
|  | 639 | return -EFAULT; | 
|  | 640 | } | 
|  | 641 | if (cmd & IOC_OUT) { | 
|  | 642 | if (!access_ok(VERIFY_WRITE, argp, size)) | 
|  | 643 | return -EFAULT; | 
|  | 644 | } | 
|  | 645 |  | 
|  | 646 | switch (cmd) { | 
|  | 647 | case MEMGETREGIONCOUNT: | 
|  | 648 | if (copy_to_user(argp, &(mtd->numeraseregions), sizeof(int))) | 
|  | 649 | return -EFAULT; | 
|  | 650 | break; | 
|  | 651 |  | 
|  | 652 | case MEMGETREGIONINFO: | 
|  | 653 | { | 
|  | 654 | uint32_t ur_idx; | 
|  | 655 | struct mtd_erase_region_info *kr; | 
|  | 656 | struct region_info_user __user *ur = argp; | 
|  | 657 |  | 
|  | 658 | if (get_user(ur_idx, &(ur->regionindex))) | 
|  | 659 | return -EFAULT; | 
|  | 660 |  | 
|  | 661 | if (ur_idx >= mtd->numeraseregions) | 
|  | 662 | return -EINVAL; | 
|  | 663 |  | 
|  | 664 | kr = &(mtd->eraseregions[ur_idx]); | 
|  | 665 |  | 
|  | 666 | if (put_user(kr->offset, &(ur->offset)) | 
|  | 667 | || put_user(kr->erasesize, &(ur->erasesize)) | 
|  | 668 | || put_user(kr->numblocks, &(ur->numblocks))) | 
|  | 669 | return -EFAULT; | 
|  | 670 |  | 
|  | 671 | break; | 
|  | 672 | } | 
|  | 673 |  | 
|  | 674 | case MEMGETINFO: | 
|  | 675 | memset(&info, 0, sizeof(info)); | 
|  | 676 | info.type	= mtd->type; | 
|  | 677 | info.flags	= mtd->flags; | 
|  | 678 | info.size	= mtd->size; | 
|  | 679 | info.erasesize	= mtd->erasesize; | 
|  | 680 | info.writesize	= mtd->writesize; | 
|  | 681 | info.oobsize	= mtd->oobsize; | 
|  | 682 | /* The below field is obsolete */ | 
|  | 683 | info.padding	= 0; | 
|  | 684 | if (copy_to_user(argp, &info, sizeof(struct mtd_info_user))) | 
|  | 685 | return -EFAULT; | 
|  | 686 | break; | 
|  | 687 |  | 
|  | 688 | case MEMERASE: | 
|  | 689 | case MEMERASE64: | 
|  | 690 | { | 
|  | 691 | struct erase_info *erase; | 
|  | 692 |  | 
|  | 693 | if(!(file->f_mode & FMODE_WRITE)) | 
|  | 694 | return -EPERM; | 
|  | 695 |  | 
|  | 696 | erase=kzalloc(sizeof(struct erase_info),GFP_KERNEL); | 
|  | 697 | if (!erase) | 
|  | 698 | ret = -ENOMEM; | 
|  | 699 | else { | 
|  | 700 | wait_queue_head_t waitq; | 
|  | 701 | DECLARE_WAITQUEUE(wait, current); | 
|  | 702 |  | 
|  | 703 | init_waitqueue_head(&waitq); | 
|  | 704 |  | 
|  | 705 | if (cmd == MEMERASE64) { | 
|  | 706 | struct erase_info_user64 einfo64; | 
|  | 707 |  | 
|  | 708 | if (copy_from_user(&einfo64, argp, | 
|  | 709 | sizeof(struct erase_info_user64))) { | 
|  | 710 | kfree(erase); | 
|  | 711 | return -EFAULT; | 
|  | 712 | } | 
|  | 713 | erase->addr = einfo64.start; | 
|  | 714 | erase->len = einfo64.length; | 
|  | 715 | } else { | 
|  | 716 | struct erase_info_user einfo32; | 
|  | 717 |  | 
|  | 718 | if (copy_from_user(&einfo32, argp, | 
|  | 719 | sizeof(struct erase_info_user))) { | 
|  | 720 | kfree(erase); | 
|  | 721 | return -EFAULT; | 
|  | 722 | } | 
|  | 723 | erase->addr = einfo32.start; | 
|  | 724 | erase->len = einfo32.length; | 
|  | 725 | } | 
|  | 726 | erase->mtd = mtd; | 
|  | 727 | erase->callback = mtdchar_erase_callback; | 
|  | 728 | erase->priv = (unsigned long)&waitq; | 
|  | 729 |  | 
|  | 730 | /* | 
|  | 731 | FIXME: Allow INTERRUPTIBLE. Which means | 
|  | 732 | not having the wait_queue head on the stack. | 
|  | 733 |  | 
|  | 734 | If the wq_head is on the stack, and we | 
|  | 735 | leave because we got interrupted, then the | 
|  | 736 | wq_head is no longer there when the | 
|  | 737 | callback routine tries to wake us up. | 
|  | 738 | */ | 
|  | 739 | ret = mtd_erase(mtd, erase); | 
|  | 740 | if (!ret) { | 
|  | 741 | set_current_state(TASK_UNINTERRUPTIBLE); | 
|  | 742 | add_wait_queue(&waitq, &wait); | 
|  | 743 | if (erase->state != MTD_ERASE_DONE && | 
|  | 744 | erase->state != MTD_ERASE_FAILED) | 
|  | 745 | schedule(); | 
|  | 746 | remove_wait_queue(&waitq, &wait); | 
|  | 747 | set_current_state(TASK_RUNNING); | 
|  | 748 |  | 
|  | 749 | ret = (erase->state == MTD_ERASE_FAILED)?-EIO:0; | 
|  | 750 | } | 
|  | 751 | kfree(erase); | 
|  | 752 | } | 
|  | 753 | break; | 
|  | 754 | } | 
|  | 755 |  | 
|  | 756 | case MEMWRITEOOB: | 
|  | 757 | { | 
|  | 758 | struct mtd_oob_buf buf; | 
|  | 759 | struct mtd_oob_buf __user *buf_user = argp; | 
|  | 760 |  | 
|  | 761 | /* NOTE: writes return length to buf_user->length */ | 
|  | 762 | if (copy_from_user(&buf, argp, sizeof(buf))) | 
|  | 763 | ret = -EFAULT; | 
|  | 764 | else | 
|  | 765 | ret = mtdchar_writeoob(file, mtd, buf.start, buf.length, | 
|  | 766 | buf.ptr, &buf_user->length); | 
|  | 767 | break; | 
|  | 768 | } | 
|  | 769 |  | 
|  | 770 | case MEMREADOOB: | 
|  | 771 | { | 
|  | 772 | struct mtd_oob_buf buf; | 
|  | 773 | struct mtd_oob_buf __user *buf_user = argp; | 
|  | 774 |  | 
|  | 775 | /* NOTE: writes return length to buf_user->start */ | 
|  | 776 | if (copy_from_user(&buf, argp, sizeof(buf))) | 
|  | 777 | ret = -EFAULT; | 
|  | 778 | else | 
|  | 779 | ret = mtdchar_readoob(file, mtd, buf.start, buf.length, | 
|  | 780 | buf.ptr, &buf_user->start); | 
|  | 781 | break; | 
|  | 782 | } | 
|  | 783 |  | 
|  | 784 | case MEMWRITEOOB64: | 
|  | 785 | { | 
|  | 786 | struct mtd_oob_buf64 buf; | 
|  | 787 | struct mtd_oob_buf64 __user *buf_user = argp; | 
|  | 788 |  | 
|  | 789 | if (copy_from_user(&buf, argp, sizeof(buf))) | 
|  | 790 | ret = -EFAULT; | 
|  | 791 | else | 
|  | 792 | ret = mtdchar_writeoob(file, mtd, buf.start, buf.length, | 
|  | 793 | (void __user *)(uintptr_t)buf.usr_ptr, | 
|  | 794 | &buf_user->length); | 
|  | 795 | break; | 
|  | 796 | } | 
|  | 797 |  | 
|  | 798 | case MEMREADOOB64: | 
|  | 799 | { | 
|  | 800 | struct mtd_oob_buf64 buf; | 
|  | 801 | struct mtd_oob_buf64 __user *buf_user = argp; | 
|  | 802 |  | 
|  | 803 | if (copy_from_user(&buf, argp, sizeof(buf))) | 
|  | 804 | ret = -EFAULT; | 
|  | 805 | else | 
|  | 806 | ret = mtdchar_readoob(file, mtd, buf.start, buf.length, | 
|  | 807 | (void __user *)(uintptr_t)buf.usr_ptr, | 
|  | 808 | &buf_user->length); | 
|  | 809 | break; | 
|  | 810 | } | 
|  | 811 |  | 
|  | 812 | case MEMWRITE: | 
|  | 813 | { | 
|  | 814 | ret = mtdchar_write_ioctl(mtd, | 
|  | 815 | (struct mtd_write_req __user *)arg); | 
|  | 816 | break; | 
|  | 817 | } | 
|  | 818 |  | 
|  | 819 | case MEMLOCK: | 
|  | 820 | { | 
|  | 821 | struct erase_info_user einfo; | 
|  | 822 |  | 
|  | 823 | if (copy_from_user(&einfo, argp, sizeof(einfo))) | 
|  | 824 | return -EFAULT; | 
|  | 825 |  | 
|  | 826 | ret = mtd_lock(mtd, einfo.start, einfo.length); | 
|  | 827 | break; | 
|  | 828 | } | 
|  | 829 |  | 
|  | 830 | case MEMUNLOCK: | 
|  | 831 | { | 
|  | 832 | struct erase_info_user einfo; | 
|  | 833 |  | 
|  | 834 | if (copy_from_user(&einfo, argp, sizeof(einfo))) | 
|  | 835 | return -EFAULT; | 
|  | 836 |  | 
|  | 837 | ret = mtd_unlock(mtd, einfo.start, einfo.length); | 
|  | 838 | break; | 
|  | 839 | } | 
|  | 840 |  | 
|  | 841 | case MEMISLOCKED: | 
|  | 842 | { | 
|  | 843 | struct erase_info_user einfo; | 
|  | 844 |  | 
|  | 845 | if (copy_from_user(&einfo, argp, sizeof(einfo))) | 
|  | 846 | return -EFAULT; | 
|  | 847 |  | 
|  | 848 | ret = mtd_is_locked(mtd, einfo.start, einfo.length); | 
|  | 849 | break; | 
|  | 850 | } | 
|  | 851 |  | 
|  | 852 | /* Legacy interface */ | 
|  | 853 | case MEMGETOOBSEL: | 
|  | 854 | { | 
|  | 855 | struct nand_oobinfo oi; | 
|  | 856 |  | 
|  | 857 | if (!mtd->ecclayout) | 
|  | 858 | return -EOPNOTSUPP; | 
|  | 859 | if (mtd->ecclayout->eccbytes > ARRAY_SIZE(oi.eccpos)) | 
|  | 860 | return -EINVAL; | 
|  | 861 |  | 
|  | 862 | oi.useecc = MTD_NANDECC_AUTOPLACE; | 
|  | 863 | memcpy(&oi.eccpos, mtd->ecclayout->eccpos, sizeof(oi.eccpos)); | 
|  | 864 | memcpy(&oi.oobfree, mtd->ecclayout->oobfree, | 
|  | 865 | sizeof(oi.oobfree)); | 
|  | 866 | oi.eccbytes = mtd->ecclayout->eccbytes; | 
|  | 867 |  | 
|  | 868 | if (copy_to_user(argp, &oi, sizeof(struct nand_oobinfo))) | 
|  | 869 | return -EFAULT; | 
|  | 870 | break; | 
|  | 871 | } | 
|  | 872 |  | 
|  | 873 | case MEMGETBADBLOCK: | 
|  | 874 | { | 
|  | 875 | loff_t offs; | 
|  | 876 |  | 
|  | 877 | if (copy_from_user(&offs, argp, sizeof(loff_t))) | 
|  | 878 | return -EFAULT; | 
|  | 879 | return mtd_block_isbad(mtd, offs); | 
|  | 880 | break; | 
|  | 881 | } | 
|  | 882 |  | 
|  | 883 | case MEMSETBADBLOCK: | 
|  | 884 | { | 
|  | 885 | loff_t offs; | 
|  | 886 |  | 
|  | 887 | if (copy_from_user(&offs, argp, sizeof(loff_t))) | 
|  | 888 | return -EFAULT; | 
|  | 889 | return mtd_block_markbad(mtd, offs); | 
|  | 890 | break; | 
|  | 891 | } | 
|  | 892 |  | 
|  | 893 | #ifdef CONFIG_HAVE_MTD_OTP | 
|  | 894 | case OTPSELECT: | 
|  | 895 | { | 
|  | 896 | int mode; | 
|  | 897 | if (copy_from_user(&mode, argp, sizeof(int))) | 
|  | 898 | return -EFAULT; | 
|  | 899 |  | 
|  | 900 | mfi->mode = MTD_FILE_MODE_NORMAL; | 
|  | 901 |  | 
|  | 902 | ret = otp_select_filemode(mfi, mode); | 
|  | 903 |  | 
|  | 904 | file->f_pos = 0; | 
|  | 905 | break; | 
|  | 906 | } | 
|  | 907 |  | 
|  | 908 | case OTPGETREGIONCOUNT: | 
|  | 909 | case OTPGETREGIONINFO: | 
|  | 910 | { | 
|  | 911 | struct otp_info *buf = kmalloc(4096, GFP_KERNEL); | 
|  | 912 | if (!buf) | 
|  | 913 | return -ENOMEM; | 
|  | 914 | switch (mfi->mode) { | 
|  | 915 | case MTD_FILE_MODE_OTP_FACTORY: | 
|  | 916 | ret = mtd_get_fact_prot_info(mtd, buf, 4096); | 
|  | 917 | break; | 
|  | 918 | case MTD_FILE_MODE_OTP_USER: | 
|  | 919 | ret = mtd_get_user_prot_info(mtd, buf, 4096); | 
|  | 920 | break; | 
|  | 921 | default: | 
|  | 922 | ret = -EINVAL; | 
|  | 923 | break; | 
|  | 924 | } | 
|  | 925 | if (ret >= 0) { | 
|  | 926 | if (cmd == OTPGETREGIONCOUNT) { | 
|  | 927 | int nbr = ret / sizeof(struct otp_info); | 
|  | 928 | ret = copy_to_user(argp, &nbr, sizeof(int)); | 
|  | 929 | } else | 
|  | 930 | ret = copy_to_user(argp, buf, ret); | 
|  | 931 | if (ret) | 
|  | 932 | ret = -EFAULT; | 
|  | 933 | } | 
|  | 934 | kfree(buf); | 
|  | 935 | break; | 
|  | 936 | } | 
|  | 937 |  | 
|  | 938 | case OTPLOCK: | 
|  | 939 | { | 
|  | 940 | struct otp_info oinfo; | 
|  | 941 |  | 
|  | 942 | if (mfi->mode != MTD_FILE_MODE_OTP_USER) | 
|  | 943 | return -EINVAL; | 
|  | 944 | if (copy_from_user(&oinfo, argp, sizeof(oinfo))) | 
|  | 945 | return -EFAULT; | 
|  | 946 | ret = mtd_lock_user_prot_reg(mtd, oinfo.start, oinfo.length); | 
|  | 947 | break; | 
|  | 948 | } | 
|  | 949 | #endif | 
|  | 950 |  | 
|  | 951 | /* This ioctl is being deprecated - it truncates the ECC layout */ | 
|  | 952 | case ECCGETLAYOUT: | 
|  | 953 | { | 
|  | 954 | struct nand_ecclayout_user *usrlay; | 
|  | 955 |  | 
|  | 956 | if (!mtd->ecclayout) | 
|  | 957 | return -EOPNOTSUPP; | 
|  | 958 |  | 
|  | 959 | usrlay = kmalloc(sizeof(*usrlay), GFP_KERNEL); | 
|  | 960 | if (!usrlay) | 
|  | 961 | return -ENOMEM; | 
|  | 962 |  | 
|  | 963 | shrink_ecclayout(mtd->ecclayout, usrlay); | 
|  | 964 |  | 
|  | 965 | if (copy_to_user(argp, usrlay, sizeof(*usrlay))) | 
|  | 966 | ret = -EFAULT; | 
|  | 967 | kfree(usrlay); | 
|  | 968 | break; | 
|  | 969 | } | 
|  | 970 |  | 
|  | 971 | case ECCGETSTATS: | 
|  | 972 | { | 
|  | 973 | if (copy_to_user(argp, &mtd->ecc_stats, | 
|  | 974 | sizeof(struct mtd_ecc_stats))) | 
|  | 975 | return -EFAULT; | 
|  | 976 | break; | 
|  | 977 | } | 
|  | 978 |  | 
|  | 979 | case MTDFILEMODE: | 
|  | 980 | { | 
|  | 981 | mfi->mode = 0; | 
|  | 982 |  | 
|  | 983 | switch(arg) { | 
|  | 984 | case MTD_FILE_MODE_OTP_FACTORY: | 
|  | 985 | case MTD_FILE_MODE_OTP_USER: | 
|  | 986 | ret = otp_select_filemode(mfi, arg); | 
|  | 987 | break; | 
|  | 988 |  | 
|  | 989 | case MTD_FILE_MODE_RAW: | 
|  | 990 | if (!mtd_has_oob(mtd)) | 
|  | 991 | return -EOPNOTSUPP; | 
|  | 992 | mfi->mode = arg; | 
|  | 993 |  | 
|  | 994 | case MTD_FILE_MODE_NORMAL: | 
|  | 995 | break; | 
|  | 996 | default: | 
|  | 997 | ret = -EINVAL; | 
|  | 998 | } | 
|  | 999 | file->f_pos = 0; | 
|  | 1000 | break; | 
|  | 1001 | } | 
|  | 1002 |  | 
|  | 1003 | case BLKPG: | 
|  | 1004 | { | 
|  | 1005 | ret = mtdchar_blkpg_ioctl(mtd, | 
|  | 1006 | (struct blkpg_ioctl_arg __user *)arg); | 
|  | 1007 | break; | 
|  | 1008 | } | 
|  | 1009 |  | 
|  | 1010 | case BLKRRPART: | 
|  | 1011 | { | 
|  | 1012 | /* No reread partition feature. Just return ok */ | 
|  | 1013 | ret = 0; | 
|  | 1014 | break; | 
|  | 1015 | } | 
|  | 1016 |  | 
|  | 1017 | case WRITEENABLE: | 
|  | 1018 | { | 
|  | 1019 | g_zload_read_only_flag = 1; | 
|  | 1020 | break; | 
|  | 1021 | } | 
|  | 1022 | case WRITEDISABLE: | 
|  | 1023 | { | 
|  | 1024 | g_zload_read_only_flag = 0; | 
|  | 1025 | break; | 
|  | 1026 | } | 
|  | 1027 |  | 
|  | 1028 | default: | 
|  | 1029 | ret = -ENOTTY; | 
|  | 1030 | } | 
|  | 1031 |  | 
|  | 1032 | return ret; | 
|  | 1033 | } /* memory_ioctl */ | 
|  | 1034 |  | 
|  | 1035 | static long mtdchar_unlocked_ioctl(struct file *file, u_int cmd, u_long arg) | 
|  | 1036 | { | 
|  | 1037 | int ret; | 
|  | 1038 |  | 
|  | 1039 | mutex_lock(&mtd_mutex); | 
|  | 1040 | ret = mtdchar_ioctl(file, cmd, arg); | 
|  | 1041 | mutex_unlock(&mtd_mutex); | 
|  | 1042 |  | 
|  | 1043 | return ret; | 
|  | 1044 | } | 
|  | 1045 |  | 
|  | 1046 | #ifdef CONFIG_COMPAT | 
|  | 1047 |  | 
|  | 1048 | struct mtd_oob_buf32 { | 
|  | 1049 | u_int32_t start; | 
|  | 1050 | u_int32_t length; | 
|  | 1051 | compat_caddr_t ptr;	/* unsigned char* */ | 
|  | 1052 | }; | 
|  | 1053 |  | 
|  | 1054 | #define MEMWRITEOOB32		_IOWR('M', 3, struct mtd_oob_buf32) | 
|  | 1055 | #define MEMREADOOB32		_IOWR('M', 4, struct mtd_oob_buf32) | 
|  | 1056 |  | 
|  | 1057 | static long mtdchar_compat_ioctl(struct file *file, unsigned int cmd, | 
|  | 1058 | unsigned long arg) | 
|  | 1059 | { | 
|  | 1060 | struct mtd_file_info *mfi = file->private_data; | 
|  | 1061 | struct mtd_info *mtd = mfi->mtd; | 
|  | 1062 | void __user *argp = compat_ptr(arg); | 
|  | 1063 | int ret = 0; | 
|  | 1064 |  | 
|  | 1065 | mutex_lock(&mtd_mutex); | 
|  | 1066 |  | 
|  | 1067 | switch (cmd) { | 
|  | 1068 | case MEMWRITEOOB32: | 
|  | 1069 | { | 
|  | 1070 | struct mtd_oob_buf32 buf; | 
|  | 1071 | struct mtd_oob_buf32 __user *buf_user = argp; | 
|  | 1072 |  | 
|  | 1073 | if (copy_from_user(&buf, argp, sizeof(buf))) | 
|  | 1074 | ret = -EFAULT; | 
|  | 1075 | else | 
|  | 1076 | ret = mtdchar_writeoob(file, mtd, buf.start, | 
|  | 1077 | buf.length, compat_ptr(buf.ptr), | 
|  | 1078 | &buf_user->length); | 
|  | 1079 | break; | 
|  | 1080 | } | 
|  | 1081 |  | 
|  | 1082 | case MEMREADOOB32: | 
|  | 1083 | { | 
|  | 1084 | struct mtd_oob_buf32 buf; | 
|  | 1085 | struct mtd_oob_buf32 __user *buf_user = argp; | 
|  | 1086 |  | 
|  | 1087 | /* NOTE: writes return length to buf->start */ | 
|  | 1088 | if (copy_from_user(&buf, argp, sizeof(buf))) | 
|  | 1089 | ret = -EFAULT; | 
|  | 1090 | else | 
|  | 1091 | ret = mtdchar_readoob(file, mtd, buf.start, | 
|  | 1092 | buf.length, compat_ptr(buf.ptr), | 
|  | 1093 | &buf_user->start); | 
|  | 1094 | break; | 
|  | 1095 | } | 
|  | 1096 | default: | 
|  | 1097 | ret = mtdchar_ioctl(file, cmd, (unsigned long)argp); | 
|  | 1098 | } | 
|  | 1099 |  | 
|  | 1100 | mutex_unlock(&mtd_mutex); | 
|  | 1101 |  | 
|  | 1102 | return ret; | 
|  | 1103 | } | 
|  | 1104 |  | 
|  | 1105 | #endif /* CONFIG_COMPAT */ | 
|  | 1106 |  | 
|  | 1107 | /* | 
|  | 1108 | * try to determine where a shared mapping can be made | 
|  | 1109 | * - only supported for NOMMU at the moment (MMU can't doesn't copy private | 
|  | 1110 | *   mappings) | 
|  | 1111 | */ | 
|  | 1112 | #ifndef CONFIG_MMU | 
|  | 1113 | static unsigned long mtdchar_get_unmapped_area(struct file *file, | 
|  | 1114 | unsigned long addr, | 
|  | 1115 | unsigned long len, | 
|  | 1116 | unsigned long pgoff, | 
|  | 1117 | unsigned long flags) | 
|  | 1118 | { | 
|  | 1119 | struct mtd_file_info *mfi = file->private_data; | 
|  | 1120 | struct mtd_info *mtd = mfi->mtd; | 
|  | 1121 | unsigned long offset; | 
|  | 1122 | int ret; | 
|  | 1123 |  | 
|  | 1124 | if (addr != 0) | 
|  | 1125 | return (unsigned long) -EINVAL; | 
|  | 1126 |  | 
|  | 1127 | if (len > mtd->size || pgoff >= (mtd->size >> PAGE_SHIFT)) | 
|  | 1128 | return (unsigned long) -EINVAL; | 
|  | 1129 |  | 
|  | 1130 | offset = pgoff << PAGE_SHIFT; | 
|  | 1131 | if (offset > mtd->size - len) | 
|  | 1132 | return (unsigned long) -EINVAL; | 
|  | 1133 |  | 
|  | 1134 | ret = mtd_get_unmapped_area(mtd, len, offset, flags); | 
|  | 1135 | return ret == -EOPNOTSUPP ? -ENOSYS : ret; | 
|  | 1136 | } | 
|  | 1137 | #endif | 
|  | 1138 |  | 
|  | 1139 | static inline unsigned long get_vm_size(struct vm_area_struct *vma) | 
|  | 1140 | { | 
|  | 1141 | return vma->vm_end - vma->vm_start; | 
|  | 1142 | } | 
|  | 1143 |  | 
|  | 1144 | static inline resource_size_t get_vm_offset(struct vm_area_struct *vma) | 
|  | 1145 | { | 
|  | 1146 | return (resource_size_t) vma->vm_pgoff << PAGE_SHIFT; | 
|  | 1147 | } | 
|  | 1148 |  | 
|  | 1149 | /* | 
|  | 1150 | * Set a new vm offset. | 
|  | 1151 | * | 
|  | 1152 | * Verify that the incoming offset really works as a page offset, | 
|  | 1153 | * and that the offset and size fit in a resource_size_t. | 
|  | 1154 | */ | 
|  | 1155 | static inline int set_vm_offset(struct vm_area_struct *vma, resource_size_t off) | 
|  | 1156 | { | 
|  | 1157 | pgoff_t pgoff = off >> PAGE_SHIFT; | 
|  | 1158 | if (off != (resource_size_t) pgoff << PAGE_SHIFT) | 
|  | 1159 | return -EINVAL; | 
|  | 1160 | if (off + get_vm_size(vma) - 1 < off) | 
|  | 1161 | return -EINVAL; | 
|  | 1162 | vma->vm_pgoff = pgoff; | 
|  | 1163 | return 0; | 
|  | 1164 | } | 
|  | 1165 |  | 
|  | 1166 | /* | 
|  | 1167 | * set up a mapping for shared memory segments | 
|  | 1168 | */ | 
|  | 1169 | static int mtdchar_mmap(struct file *file, struct vm_area_struct *vma) | 
|  | 1170 | { | 
|  | 1171 | #ifdef CONFIG_MMU | 
|  | 1172 | struct mtd_file_info *mfi = file->private_data; | 
|  | 1173 | struct mtd_info *mtd = mfi->mtd; | 
|  | 1174 | struct map_info *map = mtd->priv; | 
|  | 1175 |  | 
|  | 1176 | /* This is broken because it assumes the MTD device is map-based | 
|  | 1177 | and that mtd->priv is a valid struct map_info.  It should be | 
|  | 1178 | replaced with something that uses the mtd_get_unmapped_area() | 
|  | 1179 | operation properly. */ | 
|  | 1180 | if (0 /*mtd->type == MTD_RAM || mtd->type == MTD_ROM*/) { | 
|  | 1181 | #ifdef pgprot_noncached | 
|  | 1182 | if (file->f_flags & O_DSYNC || map->phys >= __pa(high_memory)) | 
|  | 1183 | vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot); | 
|  | 1184 | #endif | 
|  | 1185 | return vm_iomap_memory(vma, map->phys, map->size); | 
|  | 1186 | } | 
|  | 1187 | return -ENOSYS; | 
|  | 1188 | #else | 
|  | 1189 | return vma->vm_flags & VM_SHARED ? 0 : -ENOSYS; | 
|  | 1190 | #endif | 
|  | 1191 | } | 
|  | 1192 |  | 
|  | 1193 | static const struct file_operations mtd_fops = { | 
|  | 1194 | .owner		= THIS_MODULE, | 
|  | 1195 | .llseek		= mtdchar_lseek, | 
|  | 1196 | .read		= mtdchar_read, | 
|  | 1197 | .write		= mtdchar_write, | 
|  | 1198 | .unlocked_ioctl	= mtdchar_unlocked_ioctl, | 
|  | 1199 | #ifdef CONFIG_COMPAT | 
|  | 1200 | .compat_ioctl	= mtdchar_compat_ioctl, | 
|  | 1201 | #endif | 
|  | 1202 | .open		= mtdchar_open, | 
|  | 1203 | .release	= mtdchar_close, | 
|  | 1204 | .mmap		= mtdchar_mmap, | 
|  | 1205 | #ifndef CONFIG_MMU | 
|  | 1206 | .get_unmapped_area = mtdchar_get_unmapped_area, | 
|  | 1207 | #endif | 
|  | 1208 | }; | 
|  | 1209 |  | 
|  | 1210 | static const struct super_operations mtd_ops = { | 
|  | 1211 | .drop_inode = generic_delete_inode, | 
|  | 1212 | .statfs = simple_statfs, | 
|  | 1213 | }; | 
|  | 1214 |  | 
|  | 1215 | static struct dentry *mtd_inodefs_mount(struct file_system_type *fs_type, | 
|  | 1216 | int flags, const char *dev_name, void *data) | 
|  | 1217 | { | 
|  | 1218 | return mount_pseudo(fs_type, "mtd_inode:", &mtd_ops, NULL, MTD_INODE_FS_MAGIC); | 
|  | 1219 | } | 
|  | 1220 |  | 
|  | 1221 | static struct file_system_type mtd_inodefs_type = { | 
|  | 1222 | .name = "mtd_inodefs", | 
|  | 1223 | .mount = mtd_inodefs_mount, | 
|  | 1224 | .kill_sb = kill_anon_super, | 
|  | 1225 | }; | 
|  | 1226 |  | 
|  | 1227 | static int __init init_mtdchar(void) | 
|  | 1228 | { | 
|  | 1229 | int ret; | 
|  | 1230 |  | 
|  | 1231 | ret = __register_chrdev(MTD_CHAR_MAJOR, 0, 1 << MINORBITS, | 
|  | 1232 | "mtd", &mtd_fops); | 
|  | 1233 | if (ret < 0) { | 
|  | 1234 | pr_notice("Can't allocate major number %d for " | 
|  | 1235 | "Memory Technology Devices.\n", MTD_CHAR_MAJOR); | 
|  | 1236 | return ret; | 
|  | 1237 | } | 
|  | 1238 |  | 
|  | 1239 | ret = register_filesystem(&mtd_inodefs_type); | 
|  | 1240 | if (ret) { | 
|  | 1241 | pr_notice("Can't register mtd_inodefs filesystem: %d\n", ret); | 
|  | 1242 | goto err_unregister_chdev; | 
|  | 1243 | } | 
|  | 1244 | return ret; | 
|  | 1245 |  | 
|  | 1246 | err_unregister_chdev: | 
|  | 1247 | __unregister_chrdev(MTD_CHAR_MAJOR, 0, 1 << MINORBITS, "mtd"); | 
|  | 1248 | return ret; | 
|  | 1249 | } | 
|  | 1250 |  | 
|  | 1251 | static void __exit cleanup_mtdchar(void) | 
|  | 1252 | { | 
|  | 1253 | unregister_filesystem(&mtd_inodefs_type); | 
|  | 1254 | __unregister_chrdev(MTD_CHAR_MAJOR, 0, 1 << MINORBITS, "mtd"); | 
|  | 1255 | } | 
|  | 1256 |  | 
|  | 1257 | module_init(init_mtdchar); | 
|  | 1258 | module_exit(cleanup_mtdchar); | 
|  | 1259 |  | 
|  | 1260 | MODULE_ALIAS_CHARDEV_MAJOR(MTD_CHAR_MAJOR); | 
|  | 1261 |  | 
|  | 1262 | MODULE_LICENSE("GPL"); | 
|  | 1263 | MODULE_AUTHOR("David Woodhouse <dwmw2@infradead.org>"); | 
|  | 1264 | MODULE_DESCRIPTION("Direct character-device access to MTD devices"); | 
|  | 1265 | MODULE_ALIAS_CHARDEV_MAJOR(MTD_CHAR_MAJOR); |