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