mtdchar.c 18.1 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>
17

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

21
#include <asm/uaccess.h>
22 23

static struct class *mtd_class;
L
Linus Torvalds 已提交
24 25 26 27 28 29

static void mtd_notify_add(struct mtd_info* mtd)
{
	if (!mtd)
		return;

30 31
	device_create(mtd_class, NULL, MKDEV(MTD_CHAR_MAJOR, mtd->index*2),
		      NULL, "mtd%d", mtd->index);
32

33 34
	device_create(mtd_class, NULL, MKDEV(MTD_CHAR_MAJOR, mtd->index*2+1),
		      NULL, "mtd%dro", mtd->index);
L
Linus Torvalds 已提交
35 36 37 38 39 40
}

static void mtd_notify_remove(struct mtd_info* mtd)
{
	if (!mtd)
		return;
41

42 43
	device_destroy(mtd_class, MKDEV(MTD_CHAR_MAJOR, mtd->index*2));
	device_destroy(mtd_class, MKDEV(MTD_CHAR_MAJOR, mtd->index*2+1));
L
Linus Torvalds 已提交
44 45 46 47 48 49 50
}

static struct mtd_notifier notifier = {
	.add	= mtd_notify_add,
	.remove	= mtd_notify_remove,
};

51
/*
52 53
 * Data structure to hold the pointer to the mtd device as well
 * as mode information ofr various use cases.
54
 */
55 56 57 58
struct mtd_file_info {
	struct mtd_info *mtd;
	enum mtd_file_modes mode;
};
59

L
Linus Torvalds 已提交
60 61
static loff_t mtd_lseek (struct file *file, loff_t offset, int orig)
{
62 63
	struct mtd_file_info *mfi = file->private_data;
	struct mtd_info *mtd = mfi->mtd;
L
Linus Torvalds 已提交
64 65

	switch (orig) {
66
	case SEEK_SET:
L
Linus Torvalds 已提交
67
		break;
68
	case SEEK_CUR:
69
		offset += file->f_pos;
L
Linus Torvalds 已提交
70
		break;
71
	case SEEK_END:
72
		offset += mtd->size;
L
Linus Torvalds 已提交
73 74 75 76 77
		break;
	default:
		return -EINVAL;
	}

78
	if (offset >= 0 && offset <= mtd->size)
79
		return file->f_pos = offset;
L
Linus Torvalds 已提交
80

81
	return -EINVAL;
L
Linus Torvalds 已提交
82 83 84 85 86 87 88 89
}



static int mtd_open(struct inode *inode, struct file *file)
{
	int minor = iminor(inode);
	int devnum = minor >> 1;
90
	int ret = 0;
L
Linus Torvalds 已提交
91
	struct mtd_info *mtd;
92
	struct mtd_file_info *mfi;
L
Linus Torvalds 已提交
93 94 95 96 97 98 99

	DEBUG(MTD_DEBUG_LEVEL0, "MTD_open\n");

	if (devnum >= MAX_MTD_DEVICES)
		return -ENODEV;

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

103
	lock_kernel();
L
Linus Torvalds 已提交
104
	mtd = get_mtd_device(NULL, devnum);
105

106 107 108 109
	if (IS_ERR(mtd)) {
		ret = PTR_ERR(mtd);
		goto out;
	}
110

111
	if (mtd->type == MTD_ABSENT) {
L
Linus Torvalds 已提交
112
		put_mtd_device(mtd);
113 114
		ret = -ENODEV;
		goto out;
L
Linus Torvalds 已提交
115 116
	}

117 118 119
	if (mtd->backing_dev_info)
		file->f_mapping->backing_dev_info = mtd->backing_dev_info;

L
Linus Torvalds 已提交
120
	/* You can't open it RW if it's not a writeable device */
121
	if ((file->f_mode & FMODE_WRITE) && !(mtd->flags & MTD_WRITEABLE)) {
L
Linus Torvalds 已提交
122
		put_mtd_device(mtd);
123 124
		ret = -EACCES;
		goto out;
L
Linus Torvalds 已提交
125
	}
126

127 128 129
	mfi = kzalloc(sizeof(*mfi), GFP_KERNEL);
	if (!mfi) {
		put_mtd_device(mtd);
130 131
		ret = -ENOMEM;
		goto out;
132 133 134 135
	}
	mfi->mtd = mtd;
	file->private_data = mfi;

136 137 138
out:
	unlock_kernel();
	return ret;
L
Linus Torvalds 已提交
139 140 141 142 143 144
} /* mtd_open */

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

static int mtd_close(struct inode *inode, struct file *file)
{
145 146
	struct mtd_file_info *mfi = file->private_data;
	struct mtd_info *mtd = mfi->mtd;
L
Linus Torvalds 已提交
147 148 149

	DEBUG(MTD_DEBUG_LEVEL0, "MTD_close\n");

150
	/* Only sync if opened RW */
151
	if ((file->f_mode & FMODE_WRITE) && mtd->sync)
L
Linus Torvalds 已提交
152
		mtd->sync(mtd);
153

L
Linus Torvalds 已提交
154
	put_mtd_device(mtd);
155 156
	file->private_data = NULL;
	kfree(mfi);
L
Linus Torvalds 已提交
157 158 159 160 161 162 163 164 165 166 167

	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)
{
168 169
	struct mtd_file_info *mfi = file->private_data;
	struct mtd_info *mtd = mfi->mtd;
L
Linus Torvalds 已提交
170 171 172 173 174
	size_t retlen=0;
	size_t total_retlen=0;
	int ret=0;
	int len;
	char *kbuf;
175

L
Linus Torvalds 已提交
176 177 178 179 180 181 182
	DEBUG(MTD_DEBUG_LEVEL0,"MTD_read\n");

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

	if (!count)
		return 0;
183

L
Linus Torvalds 已提交
184 185
	/* FIXME: Use kiovec in 2.5 to lock down the user's buffers
	   and pass them directly to the MTD functions */
186 187 188 189 190 191 192 193 194

	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 已提交
195
	while (count) {
196

197
		if (count > MAX_KMALLOC_SIZE)
L
Linus Torvalds 已提交
198 199 200 201
			len = MAX_KMALLOC_SIZE;
		else
			len = count;

202 203
		switch (mfi->mode) {
		case MTD_MODE_OTP_FACTORY:
204 205 206 207 208
			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;
209 210 211 212 213 214 215 216 217 218 219 220 221
		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;
		}
222
		default:
223
			ret = mtd->read(mtd, *ppos, len, &retlen, kbuf);
224
		}
L
Linus Torvalds 已提交
225 226
		/* Nand returns -EBADMSG on ecc errors, but it returns
		 * the data. For our userspace tools it is important
227
		 * to dump areas with ecc errors !
228 229 230
		 * 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 已提交
231 232 233
		 * Userspace software which accesses NAND this way
		 * must be aware of the fact that it deals with NAND
		 */
234
		if (!ret || (ret == -EUCLEAN) || (ret == -EBADMSG)) {
L
Linus Torvalds 已提交
235 236
			*ppos += retlen;
			if (copy_to_user(buf, kbuf, retlen)) {
237
				kfree(kbuf);
L
Linus Torvalds 已提交
238 239 240 241 242 243 244
				return -EFAULT;
			}
			else
				total_retlen += retlen;

			count -= retlen;
			buf += retlen;
245 246
			if (retlen == 0)
				count = 0;
L
Linus Torvalds 已提交
247 248 249 250 251
		}
		else {
			kfree(kbuf);
			return ret;
		}
252

L
Linus Torvalds 已提交
253 254
	}

255
	kfree(kbuf);
L
Linus Torvalds 已提交
256 257 258 259 260
	return total_retlen;
} /* mtd_read */

static ssize_t mtd_write(struct file *file, const char __user *buf, size_t count,loff_t *ppos)
{
261 262
	struct mtd_file_info *mfi = file->private_data;
	struct mtd_info *mtd = mfi->mtd;
L
Linus Torvalds 已提交
263 264 265 266 267 268 269
	char *kbuf;
	size_t retlen;
	size_t total_retlen=0;
	int ret=0;
	int len;

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

L
Linus Torvalds 已提交
271 272
	if (*ppos == mtd->size)
		return -ENOSPC;
273

L
Linus Torvalds 已提交
274 275 276 277 278 279
	if (*ppos + count > mtd->size)
		count = mtd->size - *ppos;

	if (!count)
		return 0;

280 281 282 283 284 285 286 287
	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 已提交
288
	while (count) {
289

290
		if (count > MAX_KMALLOC_SIZE)
L
Linus Torvalds 已提交
291 292 293 294 295 296 297 298
			len = MAX_KMALLOC_SIZE;
		else
			len = count;

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

300 301
		switch (mfi->mode) {
		case MTD_MODE_OTP_FACTORY:
302 303 304 305 306 307 308 309 310
			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;
311 312 313 314 315 316 317 318 319 320 321 322 323 324 325

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

326 327 328
		default:
			ret = (*(mtd->write))(mtd, *ppos, len, &retlen, kbuf);
		}
L
Linus Torvalds 已提交
329 330 331 332 333 334 335 336 337 338 339 340
		if (!ret) {
			*ppos += retlen;
			total_retlen += retlen;
			count -= retlen;
			buf += retlen;
		}
		else {
			kfree(kbuf);
			return ret;
		}
	}

341
	kfree(kbuf);
L
Linus Torvalds 已提交
342 343 344 345 346 347 348 349 350 351 352 353 354
	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 已提交
355
#ifdef CONFIG_HAVE_MTD_OTP
356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384
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

L
Linus Torvalds 已提交
385 386 387
static int mtd_ioctl(struct inode *inode, struct file *file,
		     u_int cmd, u_long arg)
{
388 389
	struct mtd_file_info *mfi = file->private_data;
	struct mtd_info *mtd = mfi->mtd;
L
Linus Torvalds 已提交
390 391 392
	void __user *argp = (void __user *)arg;
	int ret = 0;
	u_long size;
393
	struct mtd_info_user info;
394

L
Linus Torvalds 已提交
395 396 397 398 399 400 401 402 403 404 405
	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;
	}
406

L
Linus Torvalds 已提交
407 408 409 410 411 412 413 414
	switch (cmd) {
	case MEMGETREGIONCOUNT:
		if (copy_to_user(argp, &(mtd->numeraseregions), sizeof(int)))
			return -EFAULT;
		break;

	case MEMGETREGIONINFO:
	{
415 416 417
		uint32_t ur_idx;
		struct mtd_erase_region_info *kr;
		struct region_info_user *ur = (struct region_info_user *) argp;
L
Linus Torvalds 已提交
418

419
		if (get_user(ur_idx, &(ur->regionindex)))
L
Linus Torvalds 已提交
420 421
			return -EFAULT;

422 423 424 425 426
		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 已提交
427
			return -EFAULT;
428

L
Linus Torvalds 已提交
429 430 431 432
		break;
	}

	case MEMGETINFO:
433 434 435 436 437 438
		info.type	= mtd->type;
		info.flags	= mtd->flags;
		info.size	= mtd->size;
		info.erasesize	= mtd->erasesize;
		info.writesize	= mtd->writesize;
		info.oobsize	= mtd->oobsize;
439 440 441
		/* The below fields are obsolete */
		info.ecctype	= -1;
		info.eccsize	= 0;
442
		if (copy_to_user(argp, &info, sizeof(struct mtd_info_user)))
L
Linus Torvalds 已提交
443 444 445 446 447 448 449
			return -EFAULT;
		break;

	case MEMERASE:
	{
		struct erase_info *erase;

450
		if(!(file->f_mode & FMODE_WRITE))
L
Linus Torvalds 已提交
451 452
			return -EPERM;

453
		erase=kzalloc(sizeof(struct erase_info),GFP_KERNEL);
L
Linus Torvalds 已提交
454 455 456
		if (!erase)
			ret = -ENOMEM;
		else {
457 458
			struct erase_info_user einfo;

L
Linus Torvalds 已提交
459 460 461 462 463
			wait_queue_head_t waitq;
			DECLARE_WAITQUEUE(wait, current);

			init_waitqueue_head(&waitq);

464
			if (copy_from_user(&einfo, argp,
L
Linus Torvalds 已提交
465 466 467 468
				    sizeof(struct erase_info_user))) {
				kfree(erase);
				return -EFAULT;
			}
469 470
			erase->addr = einfo.start;
			erase->len = einfo.length;
L
Linus Torvalds 已提交
471 472 473
			erase->mtd = mtd;
			erase->callback = mtdchar_erase_callback;
			erase->priv = (unsigned long)&waitq;
474

L
Linus Torvalds 已提交
475 476 477
			/*
			  FIXME: Allow INTERRUPTIBLE. Which means
			  not having the wait_queue head on the stack.
478

L
Linus Torvalds 已提交
479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503
			  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;
504
		struct mtd_oob_ops ops;
505
		struct mtd_oob_buf __user *user_buf = argp;
506
	        uint32_t retlen;
507

508
		if(!(file->f_mode & FMODE_WRITE))
L
Linus Torvalds 已提交
509 510 511 512
			return -EPERM;

		if (copy_from_user(&buf, argp, sizeof(struct mtd_oob_buf)))
			return -EFAULT;
513

514
		if (buf.length > 4096)
L
Linus Torvalds 已提交
515 516 517 518 519 520 521 522 523 524 525
			return -EINVAL;

		if (!mtd->write_oob)
			ret = -EOPNOTSUPP;
		else
			ret = access_ok(VERIFY_READ, buf.ptr,
					buf.length) ? 0 : EFAULT;

		if (ret)
			return ret;

526
		ops.ooblen = buf.length;
527 528 529 530
		ops.ooboffs = buf.start & (mtd->oobsize - 1);
		ops.datbuf = NULL;
		ops.mode = MTD_OOB_PLACE;

531
		if (ops.ooboffs && ops.ooblen > (mtd->oobsize - ops.ooboffs))
532 533 534 535
			return -EINVAL;

		ops.oobbuf = kmalloc(buf.length, GFP_KERNEL);
		if (!ops.oobbuf)
L
Linus Torvalds 已提交
536
			return -ENOMEM;
537

538 539
		if (copy_from_user(ops.oobbuf, buf.ptr, buf.length)) {
			kfree(ops.oobbuf);
L
Linus Torvalds 已提交
540 541 542
			return -EFAULT;
		}

543 544
		buf.start &= ~(mtd->oobsize - 1);
		ret = mtd->write_oob(mtd, buf.start, &ops);
L
Linus Torvalds 已提交
545

546 547 548
		if (ops.oobretlen > 0xFFFFFFFFU)
			ret = -EOVERFLOW;
		retlen = ops.oobretlen;
549
		if (copy_to_user(&user_buf->length, &retlen, sizeof(buf.length)))
L
Linus Torvalds 已提交
550 551
			ret = -EFAULT;

552
		kfree(ops.oobbuf);
L
Linus Torvalds 已提交
553 554 555 556 557 558 559
		break;

	}

	case MEMREADOOB:
	{
		struct mtd_oob_buf buf;
560
		struct mtd_oob_ops ops;
L
Linus Torvalds 已提交
561 562 563

		if (copy_from_user(&buf, argp, sizeof(struct mtd_oob_buf)))
			return -EFAULT;
564

565
		if (buf.length > 4096)
L
Linus Torvalds 已提交
566 567 568 569 570 571 572 573 574 575
			return -EINVAL;

		if (!mtd->read_oob)
			ret = -EOPNOTSUPP;
		else
			ret = access_ok(VERIFY_WRITE, buf.ptr,
					buf.length) ? 0 : -EFAULT;
		if (ret)
			return ret;

576
		ops.ooblen = buf.length;
577 578 579 580
		ops.ooboffs = buf.start & (mtd->oobsize - 1);
		ops.datbuf = NULL;
		ops.mode = MTD_OOB_PLACE;

581
		if (ops.ooboffs && ops.ooblen > (mtd->oobsize - ops.ooboffs))
582 583 584 585
			return -EINVAL;

		ops.oobbuf = kmalloc(buf.length, GFP_KERNEL);
		if (!ops.oobbuf)
L
Linus Torvalds 已提交
586
			return -ENOMEM;
587

588 589
		buf.start &= ~(mtd->oobsize - 1);
		ret = mtd->read_oob(mtd, buf.start, &ops);
L
Linus Torvalds 已提交
590

591
		if (put_user(ops.oobretlen, (uint32_t __user *)argp))
L
Linus Torvalds 已提交
592
			ret = -EFAULT;
593 594
		else if (ops.oobretlen && copy_to_user(buf.ptr, ops.oobbuf,
						    ops.oobretlen))
L
Linus Torvalds 已提交
595
			ret = -EFAULT;
596

597
		kfree(ops.oobbuf);
L
Linus Torvalds 已提交
598 599 600 601 602
		break;
	}

	case MEMLOCK:
	{
603
		struct erase_info_user einfo;
L
Linus Torvalds 已提交
604

605
		if (copy_from_user(&einfo, argp, sizeof(einfo)))
L
Linus Torvalds 已提交
606 607 608 609 610
			return -EFAULT;

		if (!mtd->lock)
			ret = -EOPNOTSUPP;
		else
611
			ret = mtd->lock(mtd, einfo.start, einfo.length);
L
Linus Torvalds 已提交
612 613 614 615 616
		break;
	}

	case MEMUNLOCK:
	{
617
		struct erase_info_user einfo;
L
Linus Torvalds 已提交
618

619
		if (copy_from_user(&einfo, argp, sizeof(einfo)))
L
Linus Torvalds 已提交
620 621 622 623 624
			return -EFAULT;

		if (!mtd->unlock)
			ret = -EOPNOTSUPP;
		else
625
			ret = mtd->unlock(mtd, einfo.start, einfo.length);
L
Linus Torvalds 已提交
626 627 628
		break;
	}

629
	/* Legacy interface */
L
Linus Torvalds 已提交
630 631
	case MEMGETOOBSEL:
	{
632 633 634 635 636 637 638 639 640 641 642
		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));
643
		oi.eccbytes = mtd->ecclayout->eccbytes;
644 645

		if (copy_to_user(argp, &oi, sizeof(struct nand_oobinfo)))
L
Linus Torvalds 已提交
646 647 648 649 650 651 652
			return -EFAULT;
		break;
	}

	case MEMGETBADBLOCK:
	{
		loff_t offs;
653

L
Linus Torvalds 已提交
654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675
		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 已提交
676
#ifdef CONFIG_HAVE_MTD_OTP
677 678 679 680 681
	case OTPSELECT:
	{
		int mode;
		if (copy_from_user(&mode, argp, sizeof(int)))
			return -EFAULT;
682 683 684 685 686

		mfi->mode = MTD_MODE_NORMAL;

		ret = otp_select_filemode(mfi, mode);

687
		file->f_pos = 0;
688 689 690 691 692 693 694 695 696 697
		break;
	}

	case OTPGETREGIONCOUNT:
	case OTPGETREGIONINFO:
	{
		struct otp_info *buf = kmalloc(4096, GFP_KERNEL);
		if (!buf)
			return -ENOMEM;
		ret = -EOPNOTSUPP;
698 699
		switch (mfi->mode) {
		case MTD_MODE_OTP_FACTORY:
700 701 702 703 704 705 706
			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;
707 708
		default:
			break;
709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724
		}
		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:
	{
725
		struct otp_info oinfo;
726

727
		if (mfi->mode != MTD_MODE_OTP_USER)
728
			return -EINVAL;
729
		if (copy_from_user(&oinfo, argp, sizeof(oinfo)))
730 731 732
			return -EFAULT;
		if (!mtd->lock_user_prot_reg)
			return -EOPNOTSUPP;
733
		ret = mtd->lock_user_prot_reg(mtd, oinfo.start, oinfo.length);
734 735 736 737
		break;
	}
#endif

738 739 740 741 742
	case ECCGETLAYOUT:
	{
		if (!mtd->ecclayout)
			return -EOPNOTSUPP;

743
		if (copy_to_user(argp, mtd->ecclayout,
744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780
				 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 已提交
781 782 783 784 785 786 787
	default:
		ret = -ENOTTY;
	}

	return ret;
} /* memory_ioctl */

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 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840
/*
 * 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
}

841
static const struct file_operations mtd_fops = {
L
Linus Torvalds 已提交
842 843 844 845 846 847 848
	.owner		= THIS_MODULE,
	.llseek		= mtd_lseek,
	.read		= mtd_read,
	.write		= mtd_write,
	.ioctl		= mtd_ioctl,
	.open		= mtd_open,
	.release	= mtd_close,
849 850 851 852
	.mmap		= mtd_mmap,
#ifndef CONFIG_MMU
	.get_unmapped_area = mtd_get_unmapped_area,
#endif
L
Linus Torvalds 已提交
853 854 855 856 857 858 859 860 861 862
};

static int __init init_mtdchar(void)
{
	if (register_chrdev(MTD_CHAR_MAJOR, "mtd", &mtd_fops)) {
		printk(KERN_NOTICE "Can't allocate major number %d for Memory Technology Devices.\n",
		       MTD_CHAR_MAJOR);
		return -EAGAIN;
	}

863 864 865 866 867
	mtd_class = class_create(THIS_MODULE, "mtd");

	if (IS_ERR(mtd_class)) {
		printk(KERN_ERR "Error creating mtd class.\n");
		unregister_chrdev(MTD_CHAR_MAJOR, "mtd");
868
		return PTR_ERR(mtd_class);
869 870 871
	}

	register_mtd_user(&notifier);
L
Linus Torvalds 已提交
872 873 874 875 876
	return 0;
}

static void __exit cleanup_mtdchar(void)
{
877 878
	unregister_mtd_user(&notifier);
	class_destroy(mtd_class);
L
Linus Torvalds 已提交
879 880 881 882 883 884 885 886 887 888
	unregister_chrdev(MTD_CHAR_MAJOR, "mtd");
}

module_init(init_mtdchar);
module_exit(cleanup_mtdchar);


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