mtdchar.c 28.3 KB
Newer Older
L
Linus Torvalds 已提交
1
/*
D
David Woodhouse 已提交
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
 * Copyright © 1999-2010 David Woodhouse <dwmw2@infradead.org>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
L
Linus Torvalds 已提交
17 18 19
 *
 */

20 21
#include <linux/device.h>
#include <linux/fs.h>
A
Andrew Morton 已提交
22
#include <linux/mm.h>
23
#include <linux/err.h>
24
#include <linux/init.h>
L
Linus Torvalds 已提交
25 26
#include <linux/kernel.h>
#include <linux/module.h>
27 28
#include <linux/slab.h>
#include <linux/sched.h>
29
#include <linux/mutex.h>
30
#include <linux/backing-dev.h>
K
Kevin Cernekee 已提交
31
#include <linux/compat.h>
32
#include <linux/mount.h>
33
#include <linux/blkpg.h>
34
#include <linux/magic.h>
L
Linus Torvalds 已提交
35
#include <linux/mtd/mtd.h>
36
#include <linux/mtd/partitions.h>
37
#include <linux/mtd/map.h>
L
Linus Torvalds 已提交
38

39
#include <asm/uaccess.h>
40

41
static DEFINE_MUTEX(mtd_mutex);
L
Linus Torvalds 已提交
42

43
/*
44
 * Data structure to hold the pointer to the mtd device as well
B
Brian Norris 已提交
45
 * as mode information of various use cases.
46
 */
47 48
struct mtd_file_info {
	struct mtd_info *mtd;
49
	struct inode *ino;
50 51
	enum mtd_file_modes mode;
};
52

53
static loff_t mtdchar_lseek(struct file *file, loff_t offset, int orig)
L
Linus Torvalds 已提交
54
{
55 56
	struct mtd_file_info *mfi = file->private_data;
	struct mtd_info *mtd = mfi->mtd;
L
Linus Torvalds 已提交
57 58

	switch (orig) {
59
	case SEEK_SET:
L
Linus Torvalds 已提交
60
		break;
61
	case SEEK_CUR:
62
		offset += file->f_pos;
L
Linus Torvalds 已提交
63
		break;
64
	case SEEK_END:
65
		offset += mtd->size;
L
Linus Torvalds 已提交
66 67 68 69 70
		break;
	default:
		return -EINVAL;
	}

71
	if (offset >= 0 && offset <= mtd->size)
72
		return file->f_pos = offset;
L
Linus Torvalds 已提交
73

74
	return -EINVAL;
L
Linus Torvalds 已提交
75 76
}

77 78 79
static int count;
static struct vfsmount *mnt;
static struct file_system_type mtd_inodefs_type;
L
Linus Torvalds 已提交
80

81
static int mtdchar_open(struct inode *inode, struct file *file)
L
Linus Torvalds 已提交
82 83 84
{
	int minor = iminor(inode);
	int devnum = minor >> 1;
85
	int ret = 0;
L
Linus Torvalds 已提交
86
	struct mtd_info *mtd;
87
	struct mtd_file_info *mfi;
88
	struct inode *mtd_ino;
L
Linus Torvalds 已提交
89

90
	pr_debug("MTD_open\n");
L
Linus Torvalds 已提交
91 92

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

96 97 98 99
	ret = simple_pin_fs(&mtd_inodefs_type, &mnt, &count);
	if (ret)
		return ret;

100
	mutex_lock(&mtd_mutex);
L
Linus Torvalds 已提交
101
	mtd = get_mtd_device(NULL, devnum);
102

103 104 105 106
	if (IS_ERR(mtd)) {
		ret = PTR_ERR(mtd);
		goto out;
	}
107

108
	if (mtd->type == MTD_ABSENT) {
109
		ret = -ENODEV;
110
		goto out1;
L
Linus Torvalds 已提交
111 112
	}

113
	mtd_ino = iget_locked(mnt->mnt_sb, devnum);
114 115
	if (!mtd_ino) {
		ret = -ENOMEM;
116
		goto out1;
117 118 119 120 121 122 123 124
	}
	if (mtd_ino->i_state & I_NEW) {
		mtd_ino->i_private = mtd;
		mtd_ino->i_mode = S_IFCHR;
		mtd_ino->i_data.backing_dev_info = mtd->backing_dev_info;
		unlock_new_inode(mtd_ino);
	}
	file->f_mapping = mtd_ino->i_mapping;
125

L
Linus Torvalds 已提交
126
	/* You can't open it RW if it's not a writeable device */
127
	if ((file->f_mode & FMODE_WRITE) && !(mtd->flags & MTD_WRITEABLE)) {
128
		ret = -EACCES;
129
		goto out2;
L
Linus Torvalds 已提交
130
	}
131

132 133
	mfi = kzalloc(sizeof(*mfi), GFP_KERNEL);
	if (!mfi) {
134
		ret = -ENOMEM;
135
		goto out2;
136
	}
137
	mfi->ino = mtd_ino;
138 139
	mfi->mtd = mtd;
	file->private_data = mfi;
140 141
	mutex_unlock(&mtd_mutex);
	return 0;
142

143 144 145 146
out2:
	iput(mtd_ino);
out1:
	put_mtd_device(mtd);
147
out:
148
	mutex_unlock(&mtd_mutex);
149
	simple_release_fs(&mnt, &count);
150
	return ret;
151
} /* mtdchar_open */
L
Linus Torvalds 已提交
152 153 154

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

155
static int mtdchar_close(struct inode *inode, struct file *file)
L
Linus Torvalds 已提交
156
{
157 158
	struct mtd_file_info *mfi = file->private_data;
	struct mtd_info *mtd = mfi->mtd;
L
Linus Torvalds 已提交
159

160
	pr_debug("MTD_close\n");
L
Linus Torvalds 已提交
161

162
	/* Only sync if opened RW */
163
	if ((file->f_mode & FMODE_WRITE))
164
		mtd_sync(mtd);
165

166 167
	iput(mfi->ino);

L
Linus Torvalds 已提交
168
	put_mtd_device(mtd);
169 170
	file->private_data = NULL;
	kfree(mfi);
171
	simple_release_fs(&mnt, &count);
L
Linus Torvalds 已提交
172 173

	return 0;
174
} /* mtdchar_close */
L
Linus Torvalds 已提交
175

176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192
/* Back in June 2001, dwmw2 wrote:
 *
 *   FIXME: This _really_ needs to die. In 2.5, we should lock the
 *   userspace buffer down and use it directly with readv/writev.
 *
 * The implementation below, using mtd_kmalloc_up_to, mitigates
 * allocation failures when the system is under low-memory situations
 * or if memory is highly fragmented at the cost of reducing the
 * performance of the requested transfer due to a smaller buffer size.
 *
 * A more complex but more memory-efficient implementation based on
 * get_user_pages and iovecs to cover extents of those pages is a
 * longer-term goal, as intimated by dwmw2 above. However, for the
 * write case, this requires yet more complex head and tail transfer
 * handling when those head and tail offsets and sizes are such that
 * alignment requirements are not met in the NAND subdriver.
 */
L
Linus Torvalds 已提交
193

194 195
static ssize_t mtdchar_read(struct file *file, char __user *buf, size_t count,
			loff_t *ppos)
L
Linus Torvalds 已提交
196
{
197 198
	struct mtd_file_info *mfi = file->private_data;
	struct mtd_info *mtd = mfi->mtd;
199
	size_t retlen;
L
Linus Torvalds 已提交
200 201 202
	size_t total_retlen=0;
	int ret=0;
	int len;
203
	size_t size = count;
L
Linus Torvalds 已提交
204
	char *kbuf;
205

206
	pr_debug("MTD_read\n");
L
Linus Torvalds 已提交
207 208 209 210 211 212

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

	if (!count)
		return 0;
213

214
	kbuf = mtd_kmalloc_up_to(mtd, &size);
215 216 217
	if (!kbuf)
		return -ENOMEM;

L
Linus Torvalds 已提交
218
	while (count) {
219
		len = min_t(size_t, count, size);
L
Linus Torvalds 已提交
220

221
		switch (mfi->mode) {
222
		case MTD_FILE_MODE_OTP_FACTORY:
223 224
			ret = mtd_read_fact_prot_reg(mtd, *ppos, len,
						     &retlen, kbuf);
225
			break;
226
		case MTD_FILE_MODE_OTP_USER:
227 228
			ret = mtd_read_user_prot_reg(mtd, *ppos, len,
						     &retlen, kbuf);
229
			break;
230
		case MTD_FILE_MODE_RAW:
231 232 233
		{
			struct mtd_oob_ops ops;

234
			ops.mode = MTD_OPS_RAW;
235 236 237 238
			ops.datbuf = kbuf;
			ops.oobbuf = NULL;
			ops.len = len;

239
			ret = mtd_read_oob(mtd, *ppos, &ops);
240 241 242
			retlen = ops.retlen;
			break;
		}
243
		default:
244
			ret = mtd_read(mtd, *ppos, len, &retlen, kbuf);
245
		}
246
		/* Nand returns -EBADMSG on ECC errors, but it returns
L
Linus Torvalds 已提交
247
		 * the data. For our userspace tools it is important
248
		 * to dump areas with ECC errors!
249
		 * For kernel internal usage it also might return -EUCLEAN
L
Lucas De Marchi 已提交
250
		 * to signal the caller that a bitflip has occurred and has
251
		 * been corrected by the ECC algorithm.
L
Linus Torvalds 已提交
252 253 254
		 * Userspace software which accesses NAND this way
		 * must be aware of the fact that it deals with NAND
		 */
255
		if (!ret || mtd_is_bitflip_or_eccerr(ret)) {
L
Linus Torvalds 已提交
256 257
			*ppos += retlen;
			if (copy_to_user(buf, kbuf, retlen)) {
258
				kfree(kbuf);
L
Linus Torvalds 已提交
259 260 261 262 263 264 265
				return -EFAULT;
			}
			else
				total_retlen += retlen;

			count -= retlen;
			buf += retlen;
266 267
			if (retlen == 0)
				count = 0;
L
Linus Torvalds 已提交
268 269 270 271 272
		}
		else {
			kfree(kbuf);
			return ret;
		}
273

L
Linus Torvalds 已提交
274 275
	}

276
	kfree(kbuf);
L
Linus Torvalds 已提交
277
	return total_retlen;
278
} /* mtdchar_read */
L
Linus Torvalds 已提交
279

280 281
static ssize_t mtdchar_write(struct file *file, const char __user *buf, size_t count,
			loff_t *ppos)
L
Linus Torvalds 已提交
282
{
283 284
	struct mtd_file_info *mfi = file->private_data;
	struct mtd_info *mtd = mfi->mtd;
285
	size_t size = count;
L
Linus Torvalds 已提交
286 287 288 289 290 291
	char *kbuf;
	size_t retlen;
	size_t total_retlen=0;
	int ret=0;
	int len;

292
	pr_debug("MTD_write\n");
293

L
Linus Torvalds 已提交
294 295
	if (*ppos == mtd->size)
		return -ENOSPC;
296

L
Linus Torvalds 已提交
297 298 299 300 301 302
	if (*ppos + count > mtd->size)
		count = mtd->size - *ppos;

	if (!count)
		return 0;

303
	kbuf = mtd_kmalloc_up_to(mtd, &size);
304 305 306
	if (!kbuf)
		return -ENOMEM;

L
Linus Torvalds 已提交
307
	while (count) {
308
		len = min_t(size_t, count, size);
L
Linus Torvalds 已提交
309 310 311 312 313

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

315
		switch (mfi->mode) {
316
		case MTD_FILE_MODE_OTP_FACTORY:
317 318
			ret = -EROFS;
			break;
319
		case MTD_FILE_MODE_OTP_USER:
320 321
			ret = mtd_write_user_prot_reg(mtd, *ppos, len,
						      &retlen, kbuf);
322
			break;
323

324
		case MTD_FILE_MODE_RAW:
325 326 327
		{
			struct mtd_oob_ops ops;

328
			ops.mode = MTD_OPS_RAW;
329 330
			ops.datbuf = kbuf;
			ops.oobbuf = NULL;
331
			ops.ooboffs = 0;
332 333
			ops.len = len;

334
			ret = mtd_write_oob(mtd, *ppos, &ops);
335 336 337 338
			retlen = ops.retlen;
			break;
		}

339
		default:
340
			ret = mtd_write(mtd, *ppos, len, &retlen, kbuf);
341
		}
L
Linus Torvalds 已提交
342 343 344 345 346 347 348 349 350 351 352 353
		if (!ret) {
			*ppos += retlen;
			total_retlen += retlen;
			count -= retlen;
			buf += retlen;
		}
		else {
			kfree(kbuf);
			return ret;
		}
	}

354
	kfree(kbuf);
L
Linus Torvalds 已提交
355
	return total_retlen;
356
} /* mtdchar_write */
L
Linus Torvalds 已提交
357 358 359 360 361 362 363 364 365 366 367

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

    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 已提交
368
#ifdef CONFIG_HAVE_MTD_OTP
369 370 371
static int otp_select_filemode(struct mtd_file_info *mfi, int mode)
{
	struct mtd_info *mtd = mfi->mtd;
372 373
	size_t retlen;

374 375
	switch (mode) {
	case MTD_OTP_FACTORY:
376 377 378 379
		if (mtd_read_fact_prot_reg(mtd, -1, 0, &retlen, NULL) ==
				-EOPNOTSUPP)
			return -EOPNOTSUPP;

380
		mfi->mode = MTD_FILE_MODE_OTP_FACTORY;
381 382
		break;
	case MTD_OTP_USER:
383 384 385 386
		if (mtd_read_user_prot_reg(mtd, -1, 0, &retlen, NULL) ==
				-EOPNOTSUPP)
			return -EOPNOTSUPP;

387
		mfi->mode = MTD_FILE_MODE_OTP_USER;
388 389
		break;
	case MTD_OTP_OFF:
390
		mfi->mode = MTD_FILE_MODE_NORMAL;
391
		break;
392 393
	default:
		return -EINVAL;
394
	}
395 396

	return 0;
397 398 399 400 401
}
#else
# define otp_select_filemode(f,m)	-EOPNOTSUPP
#endif

402
static int mtdchar_writeoob(struct file *file, struct mtd_info *mtd,
K
Kevin Cernekee 已提交
403 404 405
	uint64_t start, uint32_t length, void __user *ptr,
	uint32_t __user *retp)
{
406
	struct mtd_file_info *mfi = file->private_data;
K
Kevin Cernekee 已提交
407 408 409 410 411 412 413 414 415 416
	struct mtd_oob_ops ops;
	uint32_t retlen;
	int ret = 0;

	if (!(file->f_mode & FMODE_WRITE))
		return -EPERM;

	if (length > 4096)
		return -EINVAL;

417
	if (!mtd->_write_oob)
K
Kevin Cernekee 已提交
418 419
		ret = -EOPNOTSUPP;
	else
420
		ret = access_ok(VERIFY_READ, ptr, length) ? 0 : -EFAULT;
K
Kevin Cernekee 已提交
421 422 423 424 425

	if (ret)
		return ret;

	ops.ooblen = length;
426
	ops.ooboffs = start & (mtd->writesize - 1);
K
Kevin Cernekee 已提交
427
	ops.datbuf = NULL;
428
	ops.mode = (mfi->mode == MTD_FILE_MODE_RAW) ? MTD_OPS_RAW :
429
		MTD_OPS_PLACE_OOB;
K
Kevin Cernekee 已提交
430 431 432 433

	if (ops.ooboffs && ops.ooblen > (mtd->oobsize - ops.ooboffs))
		return -EINVAL;

J
Julia Lawall 已提交
434 435 436
	ops.oobbuf = memdup_user(ptr, length);
	if (IS_ERR(ops.oobbuf))
		return PTR_ERR(ops.oobbuf);
K
Kevin Cernekee 已提交
437

438
	start &= ~((uint64_t)mtd->writesize - 1);
439
	ret = mtd_write_oob(mtd, start, &ops);
K
Kevin Cernekee 已提交
440 441 442 443 444 445 446 447 448 449 450

	if (ops.oobretlen > 0xFFFFFFFFU)
		ret = -EOVERFLOW;
	retlen = ops.oobretlen;
	if (copy_to_user(retp, &retlen, sizeof(length)))
		ret = -EFAULT;

	kfree(ops.oobbuf);
	return ret;
}

451
static int mtdchar_readoob(struct file *file, struct mtd_info *mtd,
452 453
	uint64_t start, uint32_t length, void __user *ptr,
	uint32_t __user *retp)
K
Kevin Cernekee 已提交
454
{
455
	struct mtd_file_info *mfi = file->private_data;
K
Kevin Cernekee 已提交
456 457 458 459 460 461
	struct mtd_oob_ops ops;
	int ret = 0;

	if (length > 4096)
		return -EINVAL;

462 463
	if (!access_ok(VERIFY_WRITE, ptr, length))
		return -EFAULT;
K
Kevin Cernekee 已提交
464 465

	ops.ooblen = length;
466
	ops.ooboffs = start & (mtd->writesize - 1);
K
Kevin Cernekee 已提交
467
	ops.datbuf = NULL;
468
	ops.mode = (mfi->mode == MTD_FILE_MODE_RAW) ? MTD_OPS_RAW :
469
		MTD_OPS_PLACE_OOB;
K
Kevin Cernekee 已提交
470 471 472 473 474 475 476 477

	if (ops.ooboffs && ops.ooblen > (mtd->oobsize - ops.ooboffs))
		return -EINVAL;

	ops.oobbuf = kmalloc(length, GFP_KERNEL);
	if (!ops.oobbuf)
		return -ENOMEM;

478
	start &= ~((uint64_t)mtd->writesize - 1);
479
	ret = mtd_read_oob(mtd, start, &ops);
K
Kevin Cernekee 已提交
480 481 482 483 484 485 486 487

	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);
488 489 490 491 492 493 494 495 496

	/*
	 * NAND returns -EBADMSG on ECC errors, but it returns the OOB
	 * data. For our userspace tools it is important to dump areas
	 * with ECC errors!
	 * For kernel internal usage it also might return -EUCLEAN
	 * to signal the caller that a bitflip has occured and has
	 * been corrected by the ECC algorithm.
	 *
B
Brian Norris 已提交
497 498 499
	 * Note: currently the standard NAND function, nand_read_oob_std,
	 * does not calculate ECC for the OOB area, so do not rely on
	 * this behavior unless you have replaced it with your own.
500
	 */
501
	if (mtd_is_bitflip_or_eccerr(ret))
502 503
		return 0;

K
Kevin Cernekee 已提交
504 505 506
	return ret;
}

507 508 509
/*
 * Copies (and truncates, if necessary) data from the larger struct,
 * nand_ecclayout, to the smaller, deprecated layout struct,
B
Brian Norris 已提交
510
 * nand_ecclayout_user. This is necessary only to support the deprecated
511 512 513 514 515 516 517 518 519 520 521 522 523 524
 * API ioctl ECCGETLAYOUT while allowing all new functionality to use
 * nand_ecclayout flexibly (i.e. the struct may change size in new
 * releases without requiring major rewrites).
 */
static int shrink_ecclayout(const struct nand_ecclayout *from,
		struct nand_ecclayout_user *to)
{
	int i;

	if (!from || !to)
		return -EINVAL;

	memset(to, 0, sizeof(*to));

525
	to->eccbytes = min((int)from->eccbytes, MTD_MAX_ECCPOS_ENTRIES);
526 527 528 529 530 531 532 533 534 535 536 537 538 539
	for (i = 0; i < to->eccbytes; i++)
		to->eccpos[i] = from->eccpos[i];

	for (i = 0; i < MTD_MAX_OOBFREE_ENTRIES; i++) {
		if (from->oobfree[i].length == 0 &&
				from->oobfree[i].offset == 0)
			break;
		to->oobavail += from->oobfree[i].length;
		to->oobfree[i] = from->oobfree[i];
	}

	return 0;
}

540
static int mtdchar_blkpg_ioctl(struct mtd_info *mtd,
541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557
			   struct blkpg_ioctl_arg __user *arg)
{
	struct blkpg_ioctl_arg a;
	struct blkpg_partition p;

	if (!capable(CAP_SYS_ADMIN))
		return -EPERM;

	if (copy_from_user(&a, arg, sizeof(struct blkpg_ioctl_arg)))
		return -EFAULT;

	if (copy_from_user(&p, a.data, sizeof(struct blkpg_partition)))
		return -EFAULT;

	switch (a.op) {
	case BLKPG_ADD_PARTITION:

558 559 560 561
		/* Only master mtd device must be used to add partitions */
		if (mtd_is_partition(mtd))
			return -EINVAL;

562 563 564 565 566 567 568 569 570 571 572 573 574 575
		return mtd_add_partition(mtd, p.devname, p.start, p.length);

	case BLKPG_DEL_PARTITION:

		if (p.pno < 0)
			return -EINVAL;

		return mtd_del_partition(mtd, p.pno);

	default:
		return -EINVAL;
	}
}

576
static int mtdchar_write_ioctl(struct mtd_info *mtd,
B
Brian Norris 已提交
577 578 579 580 581 582 583 584 585 586 587
		struct mtd_write_req __user *argp)
{
	struct mtd_write_req req;
	struct mtd_oob_ops ops;
	void __user *usr_data, *usr_oob;
	int ret;

	if (copy_from_user(&req, argp, sizeof(req)) ||
			!access_ok(VERIFY_READ, req.usr_data, req.len) ||
			!access_ok(VERIFY_READ, req.usr_oob, req.ooblen))
		return -EFAULT;
588
	if (!mtd->_write_oob)
B
Brian Norris 已提交
589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616
		return -EOPNOTSUPP;

	ops.mode = req.mode;
	ops.len = (size_t)req.len;
	ops.ooblen = (size_t)req.ooblen;
	ops.ooboffs = 0;

	usr_data = (void __user *)(uintptr_t)req.usr_data;
	usr_oob = (void __user *)(uintptr_t)req.usr_oob;

	if (req.usr_data) {
		ops.datbuf = memdup_user(usr_data, ops.len);
		if (IS_ERR(ops.datbuf))
			return PTR_ERR(ops.datbuf);
	} else {
		ops.datbuf = NULL;
	}

	if (req.usr_oob) {
		ops.oobbuf = memdup_user(usr_oob, ops.ooblen);
		if (IS_ERR(ops.oobbuf)) {
			kfree(ops.datbuf);
			return PTR_ERR(ops.oobbuf);
		}
	} else {
		ops.oobbuf = NULL;
	}

617
	ret = mtd_write_oob(mtd, (loff_t)req.start, &ops);
B
Brian Norris 已提交
618 619 620 621 622 623 624

	kfree(ops.datbuf);
	kfree(ops.oobbuf);

	return ret;
}

625
static int mtdchar_ioctl(struct file *file, u_int cmd, u_long arg)
L
Linus Torvalds 已提交
626
{
627 628
	struct mtd_file_info *mfi = file->private_data;
	struct mtd_info *mtd = mfi->mtd;
L
Linus Torvalds 已提交
629 630 631
	void __user *argp = (void __user *)arg;
	int ret = 0;
	u_long size;
632
	struct mtd_info_user info;
633

634
	pr_debug("MTD_ioctl\n");
L
Linus Torvalds 已提交
635 636 637 638 639 640 641 642 643 644

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

L
Linus Torvalds 已提交
646 647 648 649 650 651 652 653
	switch (cmd) {
	case MEMGETREGIONCOUNT:
		if (copy_to_user(argp, &(mtd->numeraseregions), sizeof(int)))
			return -EFAULT;
		break;

	case MEMGETREGIONINFO:
	{
654 655
		uint32_t ur_idx;
		struct mtd_erase_region_info *kr;
656
		struct region_info_user __user *ur = argp;
L
Linus Torvalds 已提交
657

658
		if (get_user(ur_idx, &(ur->regionindex)))
L
Linus Torvalds 已提交
659 660
			return -EFAULT;

D
Dan Carpenter 已提交
661 662 663
		if (ur_idx >= mtd->numeraseregions)
			return -EINVAL;

664 665 666 667 668
		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 已提交
669
			return -EFAULT;
670

L
Linus Torvalds 已提交
671 672 673 674
		break;
	}

	case MEMGETINFO:
675
		memset(&info, 0, sizeof(info));
676 677 678 679 680 681
		info.type	= mtd->type;
		info.flags	= mtd->flags;
		info.size	= mtd->size;
		info.erasesize	= mtd->erasesize;
		info.writesize	= mtd->writesize;
		info.oobsize	= mtd->oobsize;
682 683
		/* The below field is obsolete */
		info.padding	= 0;
684
		if (copy_to_user(argp, &info, sizeof(struct mtd_info_user)))
L
Linus Torvalds 已提交
685 686 687 688
			return -EFAULT;
		break;

	case MEMERASE:
689
	case MEMERASE64:
L
Linus Torvalds 已提交
690 691 692
	{
		struct erase_info *erase;

693
		if(!(file->f_mode & FMODE_WRITE))
L
Linus Torvalds 已提交
694 695
			return -EPERM;

696
		erase=kzalloc(sizeof(struct erase_info),GFP_KERNEL);
L
Linus Torvalds 已提交
697 698 699 700 701 702 703 704
		if (!erase)
			ret = -ENOMEM;
		else {
			wait_queue_head_t waitq;
			DECLARE_WAITQUEUE(wait, current);

			init_waitqueue_head(&waitq);

705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724
			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 已提交
725 726 727 728
			}
			erase->mtd = mtd;
			erase->callback = mtdchar_erase_callback;
			erase->priv = (unsigned long)&waitq;
729

L
Linus Torvalds 已提交
730 731 732
			/*
			  FIXME: Allow INTERRUPTIBLE. Which means
			  not having the wait_queue head on the stack.
733

L
Linus Torvalds 已提交
734 735 736 737 738
			  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.
			*/
739
			ret = mtd_erase(mtd, erase);
L
Linus Torvalds 已提交
740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758
			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 已提交
759
		struct mtd_oob_buf __user *buf_user = argp;
L
Linus Torvalds 已提交
760

K
Kevin Cernekee 已提交
761 762
		/* NOTE: writes return length to buf_user->length */
		if (copy_from_user(&buf, argp, sizeof(buf)))
L
Linus Torvalds 已提交
763
			ret = -EFAULT;
K
Kevin Cernekee 已提交
764
		else
765
			ret = mtdchar_writeoob(file, mtd, buf.start, buf.length,
K
Kevin Cernekee 已提交
766
				buf.ptr, &buf_user->length);
L
Linus Torvalds 已提交
767 768 769 770 771 772
		break;
	}

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

K
Kevin Cernekee 已提交
775 776
		/* NOTE: writes return length to buf_user->start */
		if (copy_from_user(&buf, argp, sizeof(buf)))
L
Linus Torvalds 已提交
777
			ret = -EFAULT;
K
Kevin Cernekee 已提交
778
		else
779
			ret = mtdchar_readoob(file, mtd, buf.start, buf.length,
K
Kevin Cernekee 已提交
780
				buf.ptr, &buf_user->start);
L
Linus Torvalds 已提交
781 782 783
		break;
	}

784 785 786 787 788 789 790 791
	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
792
			ret = mtdchar_writeoob(file, mtd, buf.start, buf.length,
793 794 795 796 797 798 799 800 801 802 803 804 805
				(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
806
			ret = mtdchar_readoob(file, mtd, buf.start, buf.length,
807 808 809 810 811
				(void __user *)(uintptr_t)buf.usr_ptr,
				&buf_user->length);
		break;
	}

B
Brian Norris 已提交
812 813
	case MEMWRITE:
	{
814
		ret = mtdchar_write_ioctl(mtd,
B
Brian Norris 已提交
815 816 817 818
		      (struct mtd_write_req __user *)arg);
		break;
	}

L
Linus Torvalds 已提交
819 820
	case MEMLOCK:
	{
821
		struct erase_info_user einfo;
L
Linus Torvalds 已提交
822

823
		if (copy_from_user(&einfo, argp, sizeof(einfo)))
L
Linus Torvalds 已提交
824 825
			return -EFAULT;

826
		ret = mtd_lock(mtd, einfo.start, einfo.length);
L
Linus Torvalds 已提交
827 828 829 830 831
		break;
	}

	case MEMUNLOCK:
	{
832
		struct erase_info_user einfo;
L
Linus Torvalds 已提交
833

834
		if (copy_from_user(&einfo, argp, sizeof(einfo)))
L
Linus Torvalds 已提交
835 836
			return -EFAULT;

837
		ret = mtd_unlock(mtd, einfo.start, einfo.length);
L
Linus Torvalds 已提交
838 839 840
		break;
	}

841 842 843 844 845 846 847
	case MEMISLOCKED:
	{
		struct erase_info_user einfo;

		if (copy_from_user(&einfo, argp, sizeof(einfo)))
			return -EFAULT;

848
		ret = mtd_is_locked(mtd, einfo.start, einfo.length);
849 850 851
		break;
	}

852
	/* Legacy interface */
L
Linus Torvalds 已提交
853 854
	case MEMGETOOBSEL:
	{
855 856 857 858 859 860 861 862 863 864 865
		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));
866
		oi.eccbytes = mtd->ecclayout->eccbytes;
867 868

		if (copy_to_user(argp, &oi, sizeof(struct nand_oobinfo)))
L
Linus Torvalds 已提交
869 870 871 872 873 874 875
			return -EFAULT;
		break;
	}

	case MEMGETBADBLOCK:
	{
		loff_t offs;
876

L
Linus Torvalds 已提交
877 878
		if (copy_from_user(&offs, argp, sizeof(loff_t)))
			return -EFAULT;
879
		return mtd_block_isbad(mtd, offs);
L
Linus Torvalds 已提交
880 881 882 883 884 885 886 887 888
		break;
	}

	case MEMSETBADBLOCK:
	{
		loff_t offs;

		if (copy_from_user(&offs, argp, sizeof(loff_t)))
			return -EFAULT;
889
		return mtd_block_markbad(mtd, offs);
L
Linus Torvalds 已提交
890 891 892
		break;
	}

D
David Brownell 已提交
893
#ifdef CONFIG_HAVE_MTD_OTP
894 895 896 897 898
	case OTPSELECT:
	{
		int mode;
		if (copy_from_user(&mode, argp, sizeof(int)))
			return -EFAULT;
899

900
		mfi->mode = MTD_FILE_MODE_NORMAL;
901 902 903

		ret = otp_select_filemode(mfi, mode);

904
		file->f_pos = 0;
905 906 907 908 909 910 911 912 913
		break;
	}

	case OTPGETREGIONCOUNT:
	case OTPGETREGIONINFO:
	{
		struct otp_info *buf = kmalloc(4096, GFP_KERNEL);
		if (!buf)
			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 926 927 928 929 930 931 932 933 934 935 936 937 938 939
		}
		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:
	{
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 948 949 950
		break;
	}
#endif

951
	/* This ioctl is being deprecated - it truncates the ECC layout */
952 953
	case ECCGETLAYOUT:
	{
954 955
		struct nand_ecclayout_user *usrlay;

956 957 958
		if (!mtd->ecclayout)
			return -EOPNOTSUPP;

959 960 961 962 963 964 965 966 967
		usrlay = kmalloc(sizeof(*usrlay), GFP_KERNEL);
		if (!usrlay)
			return -ENOMEM;

		shrink_ecclayout(mtd->ecclayout, usrlay);

		if (copy_to_user(argp, usrlay, sizeof(*usrlay)))
			ret = -EFAULT;
		kfree(usrlay);
968 969 970 971 972 973 974 975 976 977 978 979 980 981 982 983
		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) {
984 985
		case MTD_FILE_MODE_OTP_FACTORY:
		case MTD_FILE_MODE_OTP_USER:
986 987 988
			ret = otp_select_filemode(mfi, arg);
			break;

989
		case MTD_FILE_MODE_RAW:
990
			if (!mtd_has_oob(mtd))
991 992 993
				return -EOPNOTSUPP;
			mfi->mode = arg;

994
		case MTD_FILE_MODE_NORMAL:
995 996 997 998 999 1000 1001 1002
			break;
		default:
			ret = -EINVAL;
		}
		file->f_pos = 0;
		break;
	}

1003 1004
	case BLKPG:
	{
1005
		ret = mtdchar_blkpg_ioctl(mtd,
1006 1007 1008 1009 1010 1011 1012 1013 1014 1015 1016
		      (struct blkpg_ioctl_arg __user *)arg);
		break;
	}

	case BLKRRPART:
	{
		/* No reread partition feature. Just return ok */
		ret = 0;
		break;
	}

L
Linus Torvalds 已提交
1017 1018 1019 1020 1021 1022 1023
	default:
		ret = -ENOTTY;
	}

	return ret;
} /* memory_ioctl */

1024
static long mtdchar_unlocked_ioctl(struct file *file, u_int cmd, u_long arg)
1025 1026 1027
{
	int ret;

1028
	mutex_lock(&mtd_mutex);
1029
	ret = mtdchar_ioctl(file, cmd, arg);
1030
	mutex_unlock(&mtd_mutex);
1031 1032 1033 1034

	return ret;
}

K
Kevin Cernekee 已提交
1035 1036 1037 1038 1039 1040 1041 1042 1043 1044 1045
#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)

1046
static long mtdchar_compat_ioctl(struct file *file, unsigned int cmd,
K
Kevin Cernekee 已提交
1047 1048 1049 1050
	unsigned long arg)
{
	struct mtd_file_info *mfi = file->private_data;
	struct mtd_info *mtd = mfi->mtd;
1051
	void __user *argp = compat_ptr(arg);
K
Kevin Cernekee 已提交
1052 1053
	int ret = 0;

1054
	mutex_lock(&mtd_mutex);
K
Kevin Cernekee 已提交
1055 1056 1057 1058 1059 1060 1061 1062 1063 1064

	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
1065
			ret = mtdchar_writeoob(file, mtd, buf.start,
K
Kevin Cernekee 已提交
1066 1067 1068 1069 1070 1071 1072 1073 1074 1075 1076 1077 1078 1079
				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
1080
			ret = mtdchar_readoob(file, mtd, buf.start,
K
Kevin Cernekee 已提交
1081 1082 1083 1084 1085
				buf.length, compat_ptr(buf.ptr),
				&buf_user->start);
		break;
	}
	default:
1086
		ret = mtdchar_ioctl(file, cmd, (unsigned long)argp);
K
Kevin Cernekee 已提交
1087 1088
	}

1089
	mutex_unlock(&mtd_mutex);
K
Kevin Cernekee 已提交
1090 1091 1092 1093 1094 1095

	return ret;
}

#endif /* CONFIG_COMPAT */

1096 1097 1098 1099 1100 1101
/*
 * 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
1102
static unsigned long mtdchar_get_unmapped_area(struct file *file,
1103 1104 1105 1106 1107 1108 1109
					   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;
1110 1111
	unsigned long offset;
	int ret;
1112

1113 1114
	if (addr != 0)
		return (unsigned long) -EINVAL;
1115

1116 1117
	if (len > mtd->size || pgoff >= (mtd->size >> PAGE_SHIFT))
		return (unsigned long) -EINVAL;
1118

1119 1120 1121
	offset = pgoff << PAGE_SHIFT;
	if (offset > mtd->size - len)
		return (unsigned long) -EINVAL;
1122

1123 1124
	ret = mtd_get_unmapped_area(mtd, len, offset, flags);
	return ret == -EOPNOTSUPP ? -ENOSYS : ret;
1125 1126 1127
}
#endif

1128 1129 1130 1131 1132 1133 1134 1135 1136 1137 1138 1139 1140 1141 1142 1143 1144 1145 1146 1147 1148 1149 1150 1151 1152 1153 1154
static inline unsigned long get_vm_size(struct vm_area_struct *vma)
{
	return vma->vm_end - vma->vm_start;
}

static inline resource_size_t get_vm_offset(struct vm_area_struct *vma)
{
	return (resource_size_t) vma->vm_pgoff << PAGE_SHIFT;
}

/*
 * Set a new vm offset.
 *
 * Verify that the incoming offset really works as a page offset,
 * and that the offset and size fit in a resource_size_t.
 */
static inline int set_vm_offset(struct vm_area_struct *vma, resource_size_t off)
{
	pgoff_t pgoff = off >> PAGE_SHIFT;
	if (off != (resource_size_t) pgoff << PAGE_SHIFT)
		return -EINVAL;
	if (off + get_vm_size(vma) - 1 < off)
		return -EINVAL;
	vma->vm_pgoff = pgoff;
	return 0;
}

1155 1156 1157
/*
 * set up a mapping for shared memory segments
 */
1158
static int mtdchar_mmap(struct file *file, struct vm_area_struct *vma)
1159 1160 1161 1162
{
#ifdef CONFIG_MMU
	struct mtd_file_info *mfi = file->private_data;
	struct mtd_info *mtd = mfi->mtd;
1163
	struct map_info *map = mtd->priv;
1164 1165
	resource_size_t start, off;
	unsigned long len, vma_len;
1166

1167 1168 1169 1170 1171
        /* This is broken because it assumes the MTD device is map-based
	   and that mtd->priv is a valid struct map_info.  It should be
	   replaced with something that uses the mtd_get_unmapped_area()
	   operation properly. */
	if (0 /*mtd->type == MTD_RAM || mtd->type == MTD_ROM*/) {
1172
		off = get_vm_offset(vma);
1173 1174 1175
		start = map->phys;
		len = PAGE_ALIGN((start & ~PAGE_MASK) + map->size);
		start &= PAGE_MASK;
1176 1177 1178 1179 1180 1181 1182
		vma_len = get_vm_size(vma);

		/* Overflow in off+len? */
		if (vma_len + off < off)
			return -EINVAL;
		/* Does it fit in the mapping? */
		if (vma_len + off > len)
1183 1184 1185
			return -EINVAL;

		off += start;
1186 1187 1188 1189 1190
		/* Did that overflow? */
		if (off < start)
			return -EINVAL;
		if (set_vm_offset(vma, off) < 0)
			return -EINVAL;
1191
		vma->vm_flags |= VM_IO | VM_DONTEXPAND | VM_DONTDUMP;
1192 1193 1194 1195 1196 1197 1198 1199 1200

#ifdef pgprot_noncached
		if (file->f_flags & O_DSYNC || off >= __pa(high_memory))
			vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot);
#endif
		if (io_remap_pfn_range(vma, vma->vm_start, off >> PAGE_SHIFT,
				       vma->vm_end - vma->vm_start,
				       vma->vm_page_prot))
			return -EAGAIN;
1201 1202

		return 0;
1203
	}
1204 1205 1206 1207 1208 1209
	return -ENOSYS;
#else
	return vma->vm_flags & VM_SHARED ? 0 : -ENOSYS;
#endif
}

1210
static const struct file_operations mtd_fops = {
L
Linus Torvalds 已提交
1211
	.owner		= THIS_MODULE,
1212 1213 1214 1215
	.llseek		= mtdchar_lseek,
	.read		= mtdchar_read,
	.write		= mtdchar_write,
	.unlocked_ioctl	= mtdchar_unlocked_ioctl,
K
Kevin Cernekee 已提交
1216
#ifdef CONFIG_COMPAT
1217
	.compat_ioctl	= mtdchar_compat_ioctl,
K
Kevin Cernekee 已提交
1218
#endif
1219 1220 1221
	.open		= mtdchar_open,
	.release	= mtdchar_close,
	.mmap		= mtdchar_mmap,
1222
#ifndef CONFIG_MMU
1223
	.get_unmapped_area = mtdchar_get_unmapped_area,
1224
#endif
L
Linus Torvalds 已提交
1225 1226
};

1227 1228 1229 1230 1231
static const struct super_operations mtd_ops = {
	.drop_inode = generic_delete_inode,
	.statfs = simple_statfs,
};

A
Al Viro 已提交
1232 1233
static struct dentry *mtd_inodefs_mount(struct file_system_type *fs_type,
				int flags, const char *dev_name, void *data)
1234
{
1235
	return mount_pseudo(fs_type, "mtd_inode:", &mtd_ops, NULL, MTD_INODE_FS_MAGIC);
1236 1237 1238 1239
}

static struct file_system_type mtd_inodefs_type = {
       .name = "mtd_inodefs",
A
Al Viro 已提交
1240
       .mount = mtd_inodefs_mount,
1241 1242
       .kill_sb = kill_anon_super,
};
1243
MODULE_ALIAS_FS("mtd_inodefs");
1244

L
Linus Torvalds 已提交
1245 1246
static int __init init_mtdchar(void)
{
1247
	int ret;
D
David Brownell 已提交
1248

1249
	ret = __register_chrdev(MTD_CHAR_MAJOR, 0, 1 << MINORBITS,
1250
				   "mtd", &mtd_fops);
1251 1252 1253 1254
	if (ret < 0) {
		pr_notice("Can't allocate major number %d for "
				"Memory Technology Devices.\n", MTD_CHAR_MAJOR);
		return ret;
1255 1256
	}

1257 1258 1259 1260 1261 1262 1263 1264 1265 1266
	ret = register_filesystem(&mtd_inodefs_type);
	if (ret) {
		pr_notice("Can't register mtd_inodefs filesystem: %d\n", ret);
		goto err_unregister_chdev;
	}
	return ret;

err_unregister_chdev:
	__unregister_chrdev(MTD_CHAR_MAJOR, 0, 1 << MINORBITS, "mtd");
	return ret;
L
Linus Torvalds 已提交
1267 1268 1269 1270
}

static void __exit cleanup_mtdchar(void)
{
1271
	unregister_filesystem(&mtd_inodefs_type);
1272
	__unregister_chrdev(MTD_CHAR_MAJOR, 0, 1 << MINORBITS, "mtd");
L
Linus Torvalds 已提交
1273 1274 1275 1276 1277
}

module_init(init_mtdchar);
module_exit(cleanup_mtdchar);

D
David Brownell 已提交
1278
MODULE_ALIAS_CHARDEV_MAJOR(MTD_CHAR_MAJOR);
L
Linus Torvalds 已提交
1279 1280 1281 1282

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