mtdchar.c 19.9 KB
Newer Older
L
Linus Torvalds 已提交
1 2 3 4 5
/*
 * Character-device access to raw MTD devices.
 *
 */

6 7
#include <linux/device.h>
#include <linux/fs.h>
A
Andrew Morton 已提交
8
#include <linux/mm.h>
9
#include <linux/err.h>
10
#include <linux/init.h>
L
Linus Torvalds 已提交
11 12
#include <linux/kernel.h>
#include <linux/module.h>
13 14
#include <linux/slab.h>
#include <linux/sched.h>
15
#include <linux/smp_lock.h>
16
#include <linux/backing-dev.h>
K
Kevin Cernekee 已提交
17
#include <linux/compat.h>
18

L
Linus Torvalds 已提交
19 20 21
#include <linux/mtd/mtd.h>
#include <linux/mtd/compatmac.h>

22
#include <asm/uaccess.h>
23

L
Linus Torvalds 已提交
24

25
/*
26 27
 * Data structure to hold the pointer to the mtd device as well
 * as mode information ofr various use cases.
28
 */
29 30 31 32
struct mtd_file_info {
	struct mtd_info *mtd;
	enum mtd_file_modes mode;
};
33

L
Linus Torvalds 已提交
34 35
static loff_t mtd_lseek (struct file *file, loff_t offset, int orig)
{
36 37
	struct mtd_file_info *mfi = file->private_data;
	struct mtd_info *mtd = mfi->mtd;
L
Linus Torvalds 已提交
38 39

	switch (orig) {
40
	case SEEK_SET:
L
Linus Torvalds 已提交
41
		break;
42
	case SEEK_CUR:
43
		offset += file->f_pos;
L
Linus Torvalds 已提交
44
		break;
45
	case SEEK_END:
46
		offset += mtd->size;
L
Linus Torvalds 已提交
47 48 49 50 51
		break;
	default:
		return -EINVAL;
	}

52
	if (offset >= 0 && offset <= mtd->size)
53
		return file->f_pos = offset;
L
Linus Torvalds 已提交
54

55
	return -EINVAL;
L
Linus Torvalds 已提交
56 57 58 59 60 61 62 63
}



static int mtd_open(struct inode *inode, struct file *file)
{
	int minor = iminor(inode);
	int devnum = minor >> 1;
64
	int ret = 0;
L
Linus Torvalds 已提交
65
	struct mtd_info *mtd;
66
	struct mtd_file_info *mfi;
L
Linus Torvalds 已提交
67 68 69 70

	DEBUG(MTD_DEBUG_LEVEL0, "MTD_open\n");

	/* You can't open the RO devices RW */
71
	if ((file->f_mode & FMODE_WRITE) && (minor & 1))
L
Linus Torvalds 已提交
72 73
		return -EACCES;

74
	lock_kernel();
L
Linus Torvalds 已提交
75
	mtd = get_mtd_device(NULL, devnum);
76

77 78 79 80
	if (IS_ERR(mtd)) {
		ret = PTR_ERR(mtd);
		goto out;
	}
81

82
	if (mtd->type == MTD_ABSENT) {
L
Linus Torvalds 已提交
83
		put_mtd_device(mtd);
84 85
		ret = -ENODEV;
		goto out;
L
Linus Torvalds 已提交
86 87
	}

88 89 90
	if (mtd->backing_dev_info)
		file->f_mapping->backing_dev_info = mtd->backing_dev_info;

L
Linus Torvalds 已提交
91
	/* You can't open it RW if it's not a writeable device */
92
	if ((file->f_mode & FMODE_WRITE) && !(mtd->flags & MTD_WRITEABLE)) {
L
Linus Torvalds 已提交
93
		put_mtd_device(mtd);
94 95
		ret = -EACCES;
		goto out;
L
Linus Torvalds 已提交
96
	}
97

98 99 100
	mfi = kzalloc(sizeof(*mfi), GFP_KERNEL);
	if (!mfi) {
		put_mtd_device(mtd);
101 102
		ret = -ENOMEM;
		goto out;
103 104 105 106
	}
	mfi->mtd = mtd;
	file->private_data = mfi;

107 108 109
out:
	unlock_kernel();
	return ret;
L
Linus Torvalds 已提交
110 111 112 113 114 115
} /* mtd_open */

/*====================================================================*/

static int mtd_close(struct inode *inode, struct file *file)
{
116 117
	struct mtd_file_info *mfi = file->private_data;
	struct mtd_info *mtd = mfi->mtd;
L
Linus Torvalds 已提交
118 119 120

	DEBUG(MTD_DEBUG_LEVEL0, "MTD_close\n");

121
	/* Only sync if opened RW */
122
	if ((file->f_mode & FMODE_WRITE) && mtd->sync)
L
Linus Torvalds 已提交
123
		mtd->sync(mtd);
124

L
Linus Torvalds 已提交
125
	put_mtd_device(mtd);
126 127
	file->private_data = NULL;
	kfree(mfi);
L
Linus Torvalds 已提交
128 129 130 131 132 133 134 135 136 137 138

	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 0x20000

static ssize_t mtd_read(struct file *file, char __user *buf, size_t count,loff_t *ppos)
{
139 140
	struct mtd_file_info *mfi = file->private_data;
	struct mtd_info *mtd = mfi->mtd;
L
Linus Torvalds 已提交
141 142 143 144 145
	size_t retlen=0;
	size_t total_retlen=0;
	int ret=0;
	int len;
	char *kbuf;
146

L
Linus Torvalds 已提交
147 148 149 150 151 152 153
	DEBUG(MTD_DEBUG_LEVEL0,"MTD_read\n");

	if (*ppos + count > mtd->size)
		count = mtd->size - *ppos;

	if (!count)
		return 0;
154

L
Linus Torvalds 已提交
155 156
	/* FIXME: Use kiovec in 2.5 to lock down the user's buffers
	   and pass them directly to the MTD functions */
157 158 159 160 161 162 163 164 165

	if (count > MAX_KMALLOC_SIZE)
		kbuf=kmalloc(MAX_KMALLOC_SIZE, GFP_KERNEL);
	else
		kbuf=kmalloc(count, GFP_KERNEL);

	if (!kbuf)
		return -ENOMEM;

L
Linus Torvalds 已提交
166
	while (count) {
167

168
		if (count > MAX_KMALLOC_SIZE)
L
Linus Torvalds 已提交
169 170 171 172
			len = MAX_KMALLOC_SIZE;
		else
			len = count;

173 174
		switch (mfi->mode) {
		case MTD_MODE_OTP_FACTORY:
175 176 177 178 179
			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;
180 181 182 183 184 185 186 187 188 189 190 191 192
		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;
		}
193
		default:
194
			ret = mtd->read(mtd, *ppos, len, &retlen, kbuf);
195
		}
L
Linus Torvalds 已提交
196 197
		/* Nand returns -EBADMSG on ecc errors, but it returns
		 * the data. For our userspace tools it is important
198
		 * to dump areas with ecc errors !
199 200 201
		 * 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.
L
Linus Torvalds 已提交
202 203 204
		 * Userspace software which accesses NAND this way
		 * must be aware of the fact that it deals with NAND
		 */
205
		if (!ret || (ret == -EUCLEAN) || (ret == -EBADMSG)) {
L
Linus Torvalds 已提交
206 207
			*ppos += retlen;
			if (copy_to_user(buf, kbuf, retlen)) {
208
				kfree(kbuf);
L
Linus Torvalds 已提交
209 210 211 212 213 214 215
				return -EFAULT;
			}
			else
				total_retlen += retlen;

			count -= retlen;
			buf += retlen;
216 217
			if (retlen == 0)
				count = 0;
L
Linus Torvalds 已提交
218 219 220 221 222
		}
		else {
			kfree(kbuf);
			return ret;
		}
223

L
Linus Torvalds 已提交
224 225
	}

226
	kfree(kbuf);
L
Linus Torvalds 已提交
227 228 229 230 231
	return total_retlen;
} /* mtd_read */

static ssize_t mtd_write(struct file *file, const char __user *buf, size_t count,loff_t *ppos)
{
232 233
	struct mtd_file_info *mfi = file->private_data;
	struct mtd_info *mtd = mfi->mtd;
L
Linus Torvalds 已提交
234 235 236 237 238 239 240
	char *kbuf;
	size_t retlen;
	size_t total_retlen=0;
	int ret=0;
	int len;

	DEBUG(MTD_DEBUG_LEVEL0,"MTD_write\n");
241

L
Linus Torvalds 已提交
242 243
	if (*ppos == mtd->size)
		return -ENOSPC;
244

L
Linus Torvalds 已提交
245 246 247 248 249 250
	if (*ppos + count > mtd->size)
		count = mtd->size - *ppos;

	if (!count)
		return 0;

251 252 253 254 255 256 257 258
	if (count > MAX_KMALLOC_SIZE)
		kbuf=kmalloc(MAX_KMALLOC_SIZE, GFP_KERNEL);
	else
		kbuf=kmalloc(count, GFP_KERNEL);

	if (!kbuf)
		return -ENOMEM;

L
Linus Torvalds 已提交
259
	while (count) {
260

261
		if (count > MAX_KMALLOC_SIZE)
L
Linus Torvalds 已提交
262 263 264 265 266 267 268 269
			len = MAX_KMALLOC_SIZE;
		else
			len = count;

		if (copy_from_user(kbuf, buf, len)) {
			kfree(kbuf);
			return -EFAULT;
		}
270

271 272
		switch (mfi->mode) {
		case MTD_MODE_OTP_FACTORY:
273 274 275 276 277 278 279 280 281
			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;
282 283 284 285 286 287 288 289 290 291 292 293 294 295 296

		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;
		}

297 298 299
		default:
			ret = (*(mtd->write))(mtd, *ppos, len, &retlen, kbuf);
		}
L
Linus Torvalds 已提交
300 301 302 303 304 305 306 307 308 309 310 311
		if (!ret) {
			*ppos += retlen;
			total_retlen += retlen;
			count -= retlen;
			buf += retlen;
		}
		else {
			kfree(kbuf);
			return ret;
		}
	}

312
	kfree(kbuf);
L
Linus Torvalds 已提交
313 314 315 316 317 318 319 320 321 322 323 324 325
	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);
}

D
David Brownell 已提交
326
#ifdef CONFIG_HAVE_MTD_OTP
327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355
static 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
#endif

K
Kevin Cernekee 已提交
356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372
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
373
		ret = access_ok(VERIFY_READ, ptr, length) ? 0 : -EFAULT;
K
Kevin Cernekee 已提交
374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449

	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;
}

L
Linus Torvalds 已提交
450 451 452
static int mtd_ioctl(struct inode *inode, struct file *file,
		     u_int cmd, u_long arg)
{
453 454
	struct mtd_file_info *mfi = file->private_data;
	struct mtd_info *mtd = mfi->mtd;
L
Linus Torvalds 已提交
455 456 457
	void __user *argp = (void __user *)arg;
	int ret = 0;
	u_long size;
458
	struct mtd_info_user info;
459

L
Linus Torvalds 已提交
460 461 462 463 464 465 466 467 468 469 470
	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;
	}
471

L
Linus Torvalds 已提交
472 473 474 475 476 477 478 479
	switch (cmd) {
	case MEMGETREGIONCOUNT:
		if (copy_to_user(argp, &(mtd->numeraseregions), sizeof(int)))
			return -EFAULT;
		break;

	case MEMGETREGIONINFO:
	{
480 481
		uint32_t ur_idx;
		struct mtd_erase_region_info *kr;
482
		struct region_info_user __user *ur = argp;
L
Linus Torvalds 已提交
483

484
		if (get_user(ur_idx, &(ur->regionindex)))
L
Linus Torvalds 已提交
485 486
			return -EFAULT;

487 488 489 490 491
		kr = &(mtd->eraseregions[ur_idx]);

		if (put_user(kr->offset, &(ur->offset))
		    || put_user(kr->erasesize, &(ur->erasesize))
		    || put_user(kr->numblocks, &(ur->numblocks)))
L
Linus Torvalds 已提交
492
			return -EFAULT;
493

L
Linus Torvalds 已提交
494 495 496 497
		break;
	}

	case MEMGETINFO:
498 499 500 501 502 503
		info.type	= mtd->type;
		info.flags	= mtd->flags;
		info.size	= mtd->size;
		info.erasesize	= mtd->erasesize;
		info.writesize	= mtd->writesize;
		info.oobsize	= mtd->oobsize;
504 505 506
		/* The below fields are obsolete */
		info.ecctype	= -1;
		info.eccsize	= 0;
507
		if (copy_to_user(argp, &info, sizeof(struct mtd_info_user)))
L
Linus Torvalds 已提交
508 509 510 511
			return -EFAULT;
		break;

	case MEMERASE:
512
	case MEMERASE64:
L
Linus Torvalds 已提交
513 514 515
	{
		struct erase_info *erase;

516
		if(!(file->f_mode & FMODE_WRITE))
L
Linus Torvalds 已提交
517 518
			return -EPERM;

519
		erase=kzalloc(sizeof(struct erase_info),GFP_KERNEL);
L
Linus Torvalds 已提交
520 521 522 523 524 525 526 527
		if (!erase)
			ret = -ENOMEM;
		else {
			wait_queue_head_t waitq;
			DECLARE_WAITQUEUE(wait, current);

			init_waitqueue_head(&waitq);

528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547
			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;
L
Linus Torvalds 已提交
548 549 550 551
			}
			erase->mtd = mtd;
			erase->callback = mtdchar_erase_callback;
			erase->priv = (unsigned long)&waitq;
552

L
Linus Torvalds 已提交
553 554 555
			/*
			  FIXME: Allow INTERRUPTIBLE. Which means
			  not having the wait_queue head on the stack.
556

L
Linus Torvalds 已提交
557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581
			  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;
	}

	case MEMWRITEOOB:
	{
		struct mtd_oob_buf buf;
K
Kevin Cernekee 已提交
582
		struct mtd_oob_buf __user *buf_user = argp;
L
Linus Torvalds 已提交
583

K
Kevin Cernekee 已提交
584 585
		/* NOTE: writes return length to buf_user->length */
		if (copy_from_user(&buf, argp, sizeof(buf)))
L
Linus Torvalds 已提交
586
			ret = -EFAULT;
K
Kevin Cernekee 已提交
587 588 589
		else
			ret = mtd_do_writeoob(file, mtd, buf.start, buf.length,
				buf.ptr, &buf_user->length);
L
Linus Torvalds 已提交
590 591 592 593 594 595
		break;
	}

	case MEMREADOOB:
	{
		struct mtd_oob_buf buf;
K
Kevin Cernekee 已提交
596
		struct mtd_oob_buf __user *buf_user = argp;
597

K
Kevin Cernekee 已提交
598 599
		/* NOTE: writes return length to buf_user->start */
		if (copy_from_user(&buf, argp, sizeof(buf)))
L
Linus Torvalds 已提交
600
			ret = -EFAULT;
K
Kevin Cernekee 已提交
601 602 603
		else
			ret = mtd_do_readoob(mtd, buf.start, buf.length,
				buf.ptr, &buf_user->start);
L
Linus Torvalds 已提交
604 605 606
		break;
	}

607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634
	case MEMWRITEOOB64:
	{
		struct mtd_oob_buf64 buf;
		struct mtd_oob_buf64 __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,
				(void __user *)(uintptr_t)buf.usr_ptr,
				&buf_user->length);
		break;
	}

	case MEMREADOOB64:
	{
		struct mtd_oob_buf64 buf;
		struct mtd_oob_buf64 __user *buf_user = argp;

		if (copy_from_user(&buf, argp, sizeof(buf)))
			ret = -EFAULT;
		else
			ret = mtd_do_readoob(mtd, buf.start, buf.length,
				(void __user *)(uintptr_t)buf.usr_ptr,
				&buf_user->length);
		break;
	}

L
Linus Torvalds 已提交
635 636
	case MEMLOCK:
	{
637
		struct erase_info_user einfo;
L
Linus Torvalds 已提交
638

639
		if (copy_from_user(&einfo, argp, sizeof(einfo)))
L
Linus Torvalds 已提交
640 641 642 643 644
			return -EFAULT;

		if (!mtd->lock)
			ret = -EOPNOTSUPP;
		else
645
			ret = mtd->lock(mtd, einfo.start, einfo.length);
L
Linus Torvalds 已提交
646 647 648 649 650
		break;
	}

	case MEMUNLOCK:
	{
651
		struct erase_info_user einfo;
L
Linus Torvalds 已提交
652

653
		if (copy_from_user(&einfo, argp, sizeof(einfo)))
L
Linus Torvalds 已提交
654 655 656 657 658
			return -EFAULT;

		if (!mtd->unlock)
			ret = -EOPNOTSUPP;
		else
659
			ret = mtd->unlock(mtd, einfo.start, einfo.length);
L
Linus Torvalds 已提交
660 661 662
		break;
	}

663
	/* Legacy interface */
L
Linus Torvalds 已提交
664 665
	case MEMGETOOBSEL:
	{
666 667 668 669 670 671 672 673 674 675 676
		struct nand_oobinfo oi;

		if (!mtd->ecclayout)
			return -EOPNOTSUPP;
		if (mtd->ecclayout->eccbytes > ARRAY_SIZE(oi.eccpos))
			return -EINVAL;

		oi.useecc = MTD_NANDECC_AUTOPLACE;
		memcpy(&oi.eccpos, mtd->ecclayout->eccpos, sizeof(oi.eccpos));
		memcpy(&oi.oobfree, mtd->ecclayout->oobfree,
		       sizeof(oi.oobfree));
677
		oi.eccbytes = mtd->ecclayout->eccbytes;
678 679

		if (copy_to_user(argp, &oi, sizeof(struct nand_oobinfo)))
L
Linus Torvalds 已提交
680 681 682 683 684 685 686
			return -EFAULT;
		break;
	}

	case MEMGETBADBLOCK:
	{
		loff_t offs;
687

L
Linus Torvalds 已提交
688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709
		if (copy_from_user(&offs, argp, sizeof(loff_t)))
			return -EFAULT;
		if (!mtd->block_isbad)
			ret = -EOPNOTSUPP;
		else
			return mtd->block_isbad(mtd, offs);
		break;
	}

	case MEMSETBADBLOCK:
	{
		loff_t offs;

		if (copy_from_user(&offs, argp, sizeof(loff_t)))
			return -EFAULT;
		if (!mtd->block_markbad)
			ret = -EOPNOTSUPP;
		else
			return mtd->block_markbad(mtd, offs);
		break;
	}

D
David Brownell 已提交
710
#ifdef CONFIG_HAVE_MTD_OTP
711 712 713 714 715
	case OTPSELECT:
	{
		int mode;
		if (copy_from_user(&mode, argp, sizeof(int)))
			return -EFAULT;
716 717 718 719 720

		mfi->mode = MTD_MODE_NORMAL;

		ret = otp_select_filemode(mfi, mode);

721
		file->f_pos = 0;
722 723 724 725 726 727 728 729 730 731
		break;
	}

	case OTPGETREGIONCOUNT:
	case OTPGETREGIONINFO:
	{
		struct otp_info *buf = kmalloc(4096, GFP_KERNEL);
		if (!buf)
			return -ENOMEM;
		ret = -EOPNOTSUPP;
732 733
		switch (mfi->mode) {
		case MTD_MODE_OTP_FACTORY:
734 735 736 737 738 739 740
			if (mtd->get_fact_prot_info)
				ret = mtd->get_fact_prot_info(mtd, buf, 4096);
			break;
		case MTD_MODE_OTP_USER:
			if (mtd->get_user_prot_info)
				ret = mtd->get_user_prot_info(mtd, buf, 4096);
			break;
741 742
		default:
			break;
743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758
		}
		if (ret >= 0) {
			if (cmd == OTPGETREGIONCOUNT) {
				int nbr = ret / sizeof(struct otp_info);
				ret = copy_to_user(argp, &nbr, sizeof(int));
			} else
				ret = copy_to_user(argp, buf, ret);
			if (ret)
				ret = -EFAULT;
		}
		kfree(buf);
		break;
	}

	case OTPLOCK:
	{
759
		struct otp_info oinfo;
760

761
		if (mfi->mode != MTD_MODE_OTP_USER)
762
			return -EINVAL;
763
		if (copy_from_user(&oinfo, argp, sizeof(oinfo)))
764 765 766
			return -EFAULT;
		if (!mtd->lock_user_prot_reg)
			return -EOPNOTSUPP;
767
		ret = mtd->lock_user_prot_reg(mtd, oinfo.start, oinfo.length);
768 769 770 771
		break;
	}
#endif

772 773 774 775 776
	case ECCGETLAYOUT:
	{
		if (!mtd->ecclayout)
			return -EOPNOTSUPP;

777
		if (copy_to_user(argp, mtd->ecclayout,
778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814
				 sizeof(struct nand_ecclayout)))
			return -EFAULT;
		break;
	}

	case ECCGETSTATS:
	{
		if (copy_to_user(argp, &mtd->ecc_stats,
				 sizeof(struct mtd_ecc_stats)))
			return -EFAULT;
		break;
	}

	case MTDFILEMODE:
	{
		mfi->mode = 0;

		switch(arg) {
		case MTD_MODE_OTP_FACTORY:
		case MTD_MODE_OTP_USER:
			ret = otp_select_filemode(mfi, arg);
			break;

		case MTD_MODE_RAW:
			if (!mtd->read_oob || !mtd->write_oob)
				return -EOPNOTSUPP;
			mfi->mode = arg;

		case MTD_MODE_NORMAL:
			break;
		default:
			ret = -EINVAL;
		}
		file->f_pos = 0;
		break;
	}

L
Linus Torvalds 已提交
815 816 817 818 819 820 821
	default:
		ret = -ENOTTY;
	}

	return ret;
} /* memory_ioctl */

K
Kevin Cernekee 已提交
822 823 824 825 826 827 828 829 830 831 832 833 834 835
#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)
{
836
	struct inode *inode = file->f_path.dentry->d_inode;
K
Kevin Cernekee 已提交
837 838
	struct mtd_file_info *mfi = file->private_data;
	struct mtd_info *mtd = mfi->mtd;
839
	void __user *argp = compat_ptr(arg);
K
Kevin Cernekee 已提交
840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873
	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:
874
		ret = mtd_ioctl(inode, file, cmd, (unsigned long)argp);
K
Kevin Cernekee 已提交
875 876 877 878 879 880 881 882 883
	}

	unlock_kernel();

	return ret;
}

#endif /* CONFIG_COMPAT */

884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912 913 914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929 930 931 932 933 934 935 936
/*
 * 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
}

937
static const struct file_operations mtd_fops = {
L
Linus Torvalds 已提交
938 939 940 941 942
	.owner		= THIS_MODULE,
	.llseek		= mtd_lseek,
	.read		= mtd_read,
	.write		= mtd_write,
	.ioctl		= mtd_ioctl,
K
Kevin Cernekee 已提交
943 944 945
#ifdef CONFIG_COMPAT
	.compat_ioctl	= mtd_compat_ioctl,
#endif
L
Linus Torvalds 已提交
946 947
	.open		= mtd_open,
	.release	= mtd_close,
948 949 950 951
	.mmap		= mtd_mmap,
#ifndef CONFIG_MMU
	.get_unmapped_area = mtd_get_unmapped_area,
#endif
L
Linus Torvalds 已提交
952 953 954 955
};

static int __init init_mtdchar(void)
{
D
David Brownell 已提交
956 957 958 959
	int status;

	status = register_chrdev(MTD_CHAR_MAJOR, "mtd", &mtd_fops);
	if (status < 0) {
L
Linus Torvalds 已提交
960 961
		printk(KERN_NOTICE "Can't allocate major number %d for Memory Technology Devices.\n",
		       MTD_CHAR_MAJOR);
962 963
	}

D
David Brownell 已提交
964
	return status;
L
Linus Torvalds 已提交
965 966 967 968 969 970 971 972 973 974
}

static void __exit cleanup_mtdchar(void)
{
	unregister_chrdev(MTD_CHAR_MAJOR, "mtd");
}

module_init(init_mtdchar);
module_exit(cleanup_mtdchar);

D
David Brownell 已提交
975
MODULE_ALIAS_CHARDEV_MAJOR(MTD_CHAR_MAJOR);
L
Linus Torvalds 已提交
976 977 978 979

MODULE_LICENSE("GPL");
MODULE_AUTHOR("David Woodhouse <dwmw2@infradead.org>");
MODULE_DESCRIPTION("Direct character-device access to MTD devices");
980
MODULE_ALIAS_CHARDEV_MAJOR(MTD_CHAR_MAJOR);