blob: c27e65e3e291a8b287c2e7561a4cd74aa976cbc5 [file] [log] [blame]
Linus Torvalds1da177e2005-04-16 15:20:36 -07001/*
Linus Torvalds1da177e2005-04-16 15:20:36 -07002 * Character-device access to raw MTD devices.
3 *
4 */
5
Thomas Gleixner15fdc522005-11-07 00:14:42 +01006#include <linux/device.h>
7#include <linux/fs.h>
Andrew Morton0c1eafd2007-08-10 13:01:06 -07008#include <linux/mm.h>
Artem Bityutskiy9c740342006-10-11 14:52:47 +03009#include <linux/err.h>
Thomas Gleixner15fdc522005-11-07 00:14:42 +010010#include <linux/init.h>
Linus Torvalds1da177e2005-04-16 15:20:36 -070011#include <linux/kernel.h>
12#include <linux/module.h>
Thomas Gleixner15fdc522005-11-07 00:14:42 +010013#include <linux/slab.h>
14#include <linux/sched.h>
Jonathan Corbet60712392008-05-15 10:10:37 -060015#include <linux/smp_lock.h>
David Howells402d3262009-02-12 10:40:00 +000016#include <linux/backing-dev.h>
Kevin Cernekee97718542009-04-08 22:53:13 -070017#include <linux/compat.h>
Kirill A. Shutemovcd874232010-05-17 16:55:47 +030018#include <linux/mount.h>
Thomas Gleixner15fdc522005-11-07 00:14:42 +010019
Linus Torvalds1da177e2005-04-16 15:20:36 -070020#include <linux/mtd/mtd.h>
Anatolij Gustschindd02b672010-06-15 09:30:15 +020021#include <linux/mtd/map.h>
Linus Torvalds1da177e2005-04-16 15:20:36 -070022#include <linux/mtd/compatmac.h>
Linus Torvalds1da177e2005-04-16 15:20:36 -070023
Thomas Gleixner15fdc522005-11-07 00:14:42 +010024#include <asm/uaccess.h>
Todd Poynor9bc7b382005-06-30 01:23:27 +010025
Kirill A. Shutemovcd874232010-05-17 16:55:47 +030026#define MTD_INODE_FS_MAGIC 0x11307854
27static struct vfsmount *mtd_inode_mnt __read_mostly;
Linus Torvalds1da177e2005-04-16 15:20:36 -070028
Nicolas Pitre045e9a52005-02-08 19:12:53 +000029/*
Thomas Gleixnerf1a28c02006-05-30 00:37:34 +020030 * Data structure to hold the pointer to the mtd device as well
31 * as mode information ofr various use cases.
Nicolas Pitre045e9a52005-02-08 19:12:53 +000032 */
Thomas Gleixnerf1a28c02006-05-30 00:37:34 +020033struct mtd_file_info {
34 struct mtd_info *mtd;
Kirill A. Shutemovcd874232010-05-17 16:55:47 +030035 struct inode *ino;
Thomas Gleixnerf1a28c02006-05-30 00:37:34 +020036 enum mtd_file_modes mode;
37};
Nicolas Pitre31f42332005-02-08 17:45:55 +000038
Linus Torvalds1da177e2005-04-16 15:20:36 -070039static loff_t mtd_lseek (struct file *file, loff_t offset, int orig)
40{
Thomas Gleixnerf1a28c02006-05-30 00:37:34 +020041 struct mtd_file_info *mfi = file->private_data;
42 struct mtd_info *mtd = mfi->mtd;
Linus Torvalds1da177e2005-04-16 15:20:36 -070043
44 switch (orig) {
Josef 'Jeff' Sipekea598302006-09-16 21:09:29 -040045 case SEEK_SET:
Linus Torvalds1da177e2005-04-16 15:20:36 -070046 break;
Josef 'Jeff' Sipekea598302006-09-16 21:09:29 -040047 case SEEK_CUR:
Todd Poynor8b491d72005-08-04 02:05:51 +010048 offset += file->f_pos;
Linus Torvalds1da177e2005-04-16 15:20:36 -070049 break;
Josef 'Jeff' Sipekea598302006-09-16 21:09:29 -040050 case SEEK_END:
Todd Poynor8b491d72005-08-04 02:05:51 +010051 offset += mtd->size;
Linus Torvalds1da177e2005-04-16 15:20:36 -070052 break;
53 default:
54 return -EINVAL;
55 }
56
Herbert Valerio Riedel1887f512006-06-24 00:03:36 +020057 if (offset >= 0 && offset <= mtd->size)
Todd Poynor8b491d72005-08-04 02:05:51 +010058 return file->f_pos = offset;
Linus Torvalds1da177e2005-04-16 15:20:36 -070059
Todd Poynor8b491d72005-08-04 02:05:51 +010060 return -EINVAL;
Linus Torvalds1da177e2005-04-16 15:20:36 -070061}
62
63
64
65static int mtd_open(struct inode *inode, struct file *file)
66{
67 int minor = iminor(inode);
68 int devnum = minor >> 1;
Jonathan Corbet60712392008-05-15 10:10:37 -060069 int ret = 0;
Linus Torvalds1da177e2005-04-16 15:20:36 -070070 struct mtd_info *mtd;
Thomas Gleixnerf1a28c02006-05-30 00:37:34 +020071 struct mtd_file_info *mfi;
Kirill A. Shutemovcd874232010-05-17 16:55:47 +030072 struct inode *mtd_ino;
Linus Torvalds1da177e2005-04-16 15:20:36 -070073
74 DEBUG(MTD_DEBUG_LEVEL0, "MTD_open\n");
75
Linus Torvalds1da177e2005-04-16 15:20:36 -070076 /* You can't open the RO devices RW */
Al Viroaeb5d722008-09-02 15:28:45 -040077 if ((file->f_mode & FMODE_WRITE) && (minor & 1))
Linus Torvalds1da177e2005-04-16 15:20:36 -070078 return -EACCES;
79
Jonathan Corbet60712392008-05-15 10:10:37 -060080 lock_kernel();
Linus Torvalds1da177e2005-04-16 15:20:36 -070081 mtd = get_mtd_device(NULL, devnum);
Thomas Gleixner97894cd2005-11-07 11:15:26 +000082
Jonathan Corbet60712392008-05-15 10:10:37 -060083 if (IS_ERR(mtd)) {
84 ret = PTR_ERR(mtd);
85 goto out;
86 }
Thomas Gleixner97894cd2005-11-07 11:15:26 +000087
David Howells402d3262009-02-12 10:40:00 +000088 if (mtd->type == MTD_ABSENT) {
Linus Torvalds1da177e2005-04-16 15:20:36 -070089 put_mtd_device(mtd);
Jonathan Corbet60712392008-05-15 10:10:37 -060090 ret = -ENODEV;
91 goto out;
Linus Torvalds1da177e2005-04-16 15:20:36 -070092 }
93
Kirill A. Shutemovcd874232010-05-17 16:55:47 +030094 mtd_ino = iget_locked(mtd_inode_mnt->mnt_sb, devnum);
95 if (!mtd_ino) {
96 put_mtd_device(mtd);
97 ret = -ENOMEM;
98 goto out;
99 }
100 if (mtd_ino->i_state & I_NEW) {
101 mtd_ino->i_private = mtd;
102 mtd_ino->i_mode = S_IFCHR;
103 mtd_ino->i_data.backing_dev_info = mtd->backing_dev_info;
104 unlock_new_inode(mtd_ino);
105 }
106 file->f_mapping = mtd_ino->i_mapping;
David Howells402d3262009-02-12 10:40:00 +0000107
Linus Torvalds1da177e2005-04-16 15:20:36 -0700108 /* You can't open it RW if it's not a writeable device */
Al Viroaeb5d722008-09-02 15:28:45 -0400109 if ((file->f_mode & FMODE_WRITE) && !(mtd->flags & MTD_WRITEABLE)) {
Kirill A. Shutemovcd874232010-05-17 16:55:47 +0300110 iput(mtd_ino);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700111 put_mtd_device(mtd);
Jonathan Corbet60712392008-05-15 10:10:37 -0600112 ret = -EACCES;
113 goto out;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700114 }
Thomas Gleixner97894cd2005-11-07 11:15:26 +0000115
Thomas Gleixnerf1a28c02006-05-30 00:37:34 +0200116 mfi = kzalloc(sizeof(*mfi), GFP_KERNEL);
117 if (!mfi) {
Kirill A. Shutemovcd874232010-05-17 16:55:47 +0300118 iput(mtd_ino);
Thomas Gleixnerf1a28c02006-05-30 00:37:34 +0200119 put_mtd_device(mtd);
Jonathan Corbet60712392008-05-15 10:10:37 -0600120 ret = -ENOMEM;
121 goto out;
Thomas Gleixnerf1a28c02006-05-30 00:37:34 +0200122 }
Kirill A. Shutemovcd874232010-05-17 16:55:47 +0300123 mfi->ino = mtd_ino;
Thomas Gleixnerf1a28c02006-05-30 00:37:34 +0200124 mfi->mtd = mtd;
125 file->private_data = mfi;
126
Jonathan Corbet60712392008-05-15 10:10:37 -0600127out:
128 unlock_kernel();
129 return ret;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700130} /* mtd_open */
131
132/*====================================================================*/
133
134static int mtd_close(struct inode *inode, struct file *file)
135{
Thomas Gleixnerf1a28c02006-05-30 00:37:34 +0200136 struct mtd_file_info *mfi = file->private_data;
137 struct mtd_info *mtd = mfi->mtd;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700138
139 DEBUG(MTD_DEBUG_LEVEL0, "MTD_close\n");
140
Joakim Tjernlund7eafaed2007-06-27 00:56:40 +0200141 /* Only sync if opened RW */
Al Viroaeb5d722008-09-02 15:28:45 -0400142 if ((file->f_mode & FMODE_WRITE) && mtd->sync)
Linus Torvalds1da177e2005-04-16 15:20:36 -0700143 mtd->sync(mtd);
Thomas Gleixner97894cd2005-11-07 11:15:26 +0000144
Kirill A. Shutemovcd874232010-05-17 16:55:47 +0300145 iput(mfi->ino);
146
Linus Torvalds1da177e2005-04-16 15:20:36 -0700147 put_mtd_device(mtd);
Thomas Gleixnerf1a28c02006-05-30 00:37:34 +0200148 file->private_data = NULL;
149 kfree(mfi);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700150
151 return 0;
152} /* mtd_close */
153
154/* FIXME: This _really_ needs to die. In 2.5, we should lock the
155 userspace buffer down and use it directly with readv/writev.
156*/
157#define MAX_KMALLOC_SIZE 0x20000
158
159static ssize_t mtd_read(struct file *file, char __user *buf, size_t count,loff_t *ppos)
160{
Thomas Gleixnerf1a28c02006-05-30 00:37:34 +0200161 struct mtd_file_info *mfi = file->private_data;
162 struct mtd_info *mtd = mfi->mtd;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700163 size_t retlen=0;
164 size_t total_retlen=0;
165 int ret=0;
166 int len;
167 char *kbuf;
Thomas Gleixner97894cd2005-11-07 11:15:26 +0000168
Linus Torvalds1da177e2005-04-16 15:20:36 -0700169 DEBUG(MTD_DEBUG_LEVEL0,"MTD_read\n");
170
171 if (*ppos + count > mtd->size)
172 count = mtd->size - *ppos;
173
174 if (!count)
175 return 0;
Thomas Gleixner97894cd2005-11-07 11:15:26 +0000176
Linus Torvalds1da177e2005-04-16 15:20:36 -0700177 /* FIXME: Use kiovec in 2.5 to lock down the user's buffers
178 and pass them directly to the MTD functions */
Thago Galesib802c072006-04-17 17:38:15 +0100179
180 if (count > MAX_KMALLOC_SIZE)
181 kbuf=kmalloc(MAX_KMALLOC_SIZE, GFP_KERNEL);
182 else
183 kbuf=kmalloc(count, GFP_KERNEL);
184
185 if (!kbuf)
186 return -ENOMEM;
187
Linus Torvalds1da177e2005-04-16 15:20:36 -0700188 while (count) {
Thago Galesib802c072006-04-17 17:38:15 +0100189
Thomas Gleixner97894cd2005-11-07 11:15:26 +0000190 if (count > MAX_KMALLOC_SIZE)
Linus Torvalds1da177e2005-04-16 15:20:36 -0700191 len = MAX_KMALLOC_SIZE;
192 else
193 len = count;
194
Thomas Gleixnerf1a28c02006-05-30 00:37:34 +0200195 switch (mfi->mode) {
196 case MTD_MODE_OTP_FACTORY:
Nicolas Pitre31f42332005-02-08 17:45:55 +0000197 ret = mtd->read_fact_prot_reg(mtd, *ppos, len, &retlen, kbuf);
198 break;
199 case MTD_MODE_OTP_USER:
200 ret = mtd->read_user_prot_reg(mtd, *ppos, len, &retlen, kbuf);
201 break;
Thomas Gleixnerf1a28c02006-05-30 00:37:34 +0200202 case MTD_MODE_RAW:
203 {
204 struct mtd_oob_ops ops;
205
206 ops.mode = MTD_OOB_RAW;
207 ops.datbuf = kbuf;
208 ops.oobbuf = NULL;
209 ops.len = len;
210
211 ret = mtd->read_oob(mtd, *ppos, &ops);
212 retlen = ops.retlen;
213 break;
214 }
Nicolas Pitre31f42332005-02-08 17:45:55 +0000215 default:
Thomas Gleixnerf4a43cf2006-05-28 11:01:53 +0200216 ret = mtd->read(mtd, *ppos, len, &retlen, kbuf);
Nicolas Pitre31f42332005-02-08 17:45:55 +0000217 }
Linus Torvalds1da177e2005-04-16 15:20:36 -0700218 /* Nand returns -EBADMSG on ecc errors, but it returns
219 * the data. For our userspace tools it is important
Thomas Gleixner97894cd2005-11-07 11:15:26 +0000220 * to dump areas with ecc errors !
Thomas Gleixner9a1fcdf2006-05-29 14:56:39 +0200221 * For kernel internal usage it also might return -EUCLEAN
222 * to signal the caller that a bitflip has occured and has
223 * been corrected by the ECC algorithm.
Linus Torvalds1da177e2005-04-16 15:20:36 -0700224 * Userspace software which accesses NAND this way
225 * must be aware of the fact that it deals with NAND
226 */
Thomas Gleixner9a1fcdf2006-05-29 14:56:39 +0200227 if (!ret || (ret == -EUCLEAN) || (ret == -EBADMSG)) {
Linus Torvalds1da177e2005-04-16 15:20:36 -0700228 *ppos += retlen;
229 if (copy_to_user(buf, kbuf, retlen)) {
Thomas Gleixnerf4a43cf2006-05-28 11:01:53 +0200230 kfree(kbuf);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700231 return -EFAULT;
232 }
233 else
234 total_retlen += retlen;
235
236 count -= retlen;
237 buf += retlen;
Nicolas Pitre31f42332005-02-08 17:45:55 +0000238 if (retlen == 0)
239 count = 0;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700240 }
241 else {
242 kfree(kbuf);
243 return ret;
244 }
Thomas Gleixner97894cd2005-11-07 11:15:26 +0000245
Linus Torvalds1da177e2005-04-16 15:20:36 -0700246 }
247
Thago Galesib802c072006-04-17 17:38:15 +0100248 kfree(kbuf);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700249 return total_retlen;
250} /* mtd_read */
251
252static ssize_t mtd_write(struct file *file, const char __user *buf, size_t count,loff_t *ppos)
253{
Thomas Gleixnerf1a28c02006-05-30 00:37:34 +0200254 struct mtd_file_info *mfi = file->private_data;
255 struct mtd_info *mtd = mfi->mtd;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700256 char *kbuf;
257 size_t retlen;
258 size_t total_retlen=0;
259 int ret=0;
260 int len;
261
262 DEBUG(MTD_DEBUG_LEVEL0,"MTD_write\n");
Thomas Gleixner97894cd2005-11-07 11:15:26 +0000263
Linus Torvalds1da177e2005-04-16 15:20:36 -0700264 if (*ppos == mtd->size)
265 return -ENOSPC;
Thomas Gleixner97894cd2005-11-07 11:15:26 +0000266
Linus Torvalds1da177e2005-04-16 15:20:36 -0700267 if (*ppos + count > mtd->size)
268 count = mtd->size - *ppos;
269
270 if (!count)
271 return 0;
272
Thago Galesib802c072006-04-17 17:38:15 +0100273 if (count > MAX_KMALLOC_SIZE)
274 kbuf=kmalloc(MAX_KMALLOC_SIZE, GFP_KERNEL);
275 else
276 kbuf=kmalloc(count, GFP_KERNEL);
277
278 if (!kbuf)
279 return -ENOMEM;
280
Linus Torvalds1da177e2005-04-16 15:20:36 -0700281 while (count) {
Thago Galesib802c072006-04-17 17:38:15 +0100282
Thomas Gleixner97894cd2005-11-07 11:15:26 +0000283 if (count > MAX_KMALLOC_SIZE)
Linus Torvalds1da177e2005-04-16 15:20:36 -0700284 len = MAX_KMALLOC_SIZE;
285 else
286 len = count;
287
Linus Torvalds1da177e2005-04-16 15:20:36 -0700288 if (copy_from_user(kbuf, buf, len)) {
289 kfree(kbuf);
290 return -EFAULT;
291 }
Thomas Gleixner97894cd2005-11-07 11:15:26 +0000292
Thomas Gleixnerf1a28c02006-05-30 00:37:34 +0200293 switch (mfi->mode) {
294 case MTD_MODE_OTP_FACTORY:
Nicolas Pitre31f42332005-02-08 17:45:55 +0000295 ret = -EROFS;
296 break;
297 case MTD_MODE_OTP_USER:
298 if (!mtd->write_user_prot_reg) {
299 ret = -EOPNOTSUPP;
300 break;
301 }
302 ret = mtd->write_user_prot_reg(mtd, *ppos, len, &retlen, kbuf);
303 break;
Thomas Gleixnerf1a28c02006-05-30 00:37:34 +0200304
305 case MTD_MODE_RAW:
306 {
307 struct mtd_oob_ops ops;
308
309 ops.mode = MTD_OOB_RAW;
310 ops.datbuf = kbuf;
311 ops.oobbuf = NULL;
312 ops.len = len;
313
314 ret = mtd->write_oob(mtd, *ppos, &ops);
315 retlen = ops.retlen;
316 break;
317 }
318
Nicolas Pitre31f42332005-02-08 17:45:55 +0000319 default:
320 ret = (*(mtd->write))(mtd, *ppos, len, &retlen, kbuf);
321 }
Linus Torvalds1da177e2005-04-16 15:20:36 -0700322 if (!ret) {
323 *ppos += retlen;
324 total_retlen += retlen;
325 count -= retlen;
326 buf += retlen;
327 }
328 else {
329 kfree(kbuf);
330 return ret;
331 }
Linus Torvalds1da177e2005-04-16 15:20:36 -0700332 }
333
Thago Galesib802c072006-04-17 17:38:15 +0100334 kfree(kbuf);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700335 return total_retlen;
336} /* mtd_write */
337
338/*======================================================================
339
340 IOCTL calls for getting device parameters.
341
342======================================================================*/
343static void mtdchar_erase_callback (struct erase_info *instr)
344{
345 wake_up((wait_queue_head_t *)instr->priv);
346}
347
David Brownell34a82442008-07-30 12:35:05 -0700348#ifdef CONFIG_HAVE_MTD_OTP
Thomas Gleixnerf1a28c02006-05-30 00:37:34 +0200349static int otp_select_filemode(struct mtd_file_info *mfi, int mode)
350{
351 struct mtd_info *mtd = mfi->mtd;
352 int ret = 0;
353
354 switch (mode) {
355 case MTD_OTP_FACTORY:
356 if (!mtd->read_fact_prot_reg)
357 ret = -EOPNOTSUPP;
358 else
359 mfi->mode = MTD_MODE_OTP_FACTORY;
360 break;
361 case MTD_OTP_USER:
362 if (!mtd->read_fact_prot_reg)
363 ret = -EOPNOTSUPP;
364 else
365 mfi->mode = MTD_MODE_OTP_USER;
366 break;
367 default:
368 ret = -EINVAL;
369 case MTD_OTP_OFF:
370 break;
371 }
372 return ret;
373}
374#else
375# define otp_select_filemode(f,m) -EOPNOTSUPP
376#endif
377
Kevin Cernekee97718542009-04-08 22:53:13 -0700378static int mtd_do_writeoob(struct file *file, struct mtd_info *mtd,
379 uint64_t start, uint32_t length, void __user *ptr,
380 uint32_t __user *retp)
381{
382 struct mtd_oob_ops ops;
383 uint32_t retlen;
384 int ret = 0;
385
386 if (!(file->f_mode & FMODE_WRITE))
387 return -EPERM;
388
389 if (length > 4096)
390 return -EINVAL;
391
392 if (!mtd->write_oob)
393 ret = -EOPNOTSUPP;
394 else
Roel Kluin00404762010-01-29 10:35:04 +0100395 ret = access_ok(VERIFY_READ, ptr, length) ? 0 : -EFAULT;
Kevin Cernekee97718542009-04-08 22:53:13 -0700396
397 if (ret)
398 return ret;
399
400 ops.ooblen = length;
401 ops.ooboffs = start & (mtd->oobsize - 1);
402 ops.datbuf = NULL;
403 ops.mode = MTD_OOB_PLACE;
404
405 if (ops.ooboffs && ops.ooblen > (mtd->oobsize - ops.ooboffs))
406 return -EINVAL;
407
Julia Lawalldf1f1d12010-05-22 10:22:49 +0200408 ops.oobbuf = memdup_user(ptr, length);
409 if (IS_ERR(ops.oobbuf))
410 return PTR_ERR(ops.oobbuf);
Kevin Cernekee97718542009-04-08 22:53:13 -0700411
412 start &= ~((uint64_t)mtd->oobsize - 1);
413 ret = mtd->write_oob(mtd, start, &ops);
414
415 if (ops.oobretlen > 0xFFFFFFFFU)
416 ret = -EOVERFLOW;
417 retlen = ops.oobretlen;
418 if (copy_to_user(retp, &retlen, sizeof(length)))
419 ret = -EFAULT;
420
421 kfree(ops.oobbuf);
422 return ret;
423}
424
425static int mtd_do_readoob(struct mtd_info *mtd, uint64_t start,
426 uint32_t length, void __user *ptr, uint32_t __user *retp)
427{
428 struct mtd_oob_ops ops;
429 int ret = 0;
430
431 if (length > 4096)
432 return -EINVAL;
433
434 if (!mtd->read_oob)
435 ret = -EOPNOTSUPP;
436 else
437 ret = access_ok(VERIFY_WRITE, ptr,
438 length) ? 0 : -EFAULT;
439 if (ret)
440 return ret;
441
442 ops.ooblen = length;
443 ops.ooboffs = start & (mtd->oobsize - 1);
444 ops.datbuf = NULL;
445 ops.mode = MTD_OOB_PLACE;
446
447 if (ops.ooboffs && ops.ooblen > (mtd->oobsize - ops.ooboffs))
448 return -EINVAL;
449
450 ops.oobbuf = kmalloc(length, GFP_KERNEL);
451 if (!ops.oobbuf)
452 return -ENOMEM;
453
454 start &= ~((uint64_t)mtd->oobsize - 1);
455 ret = mtd->read_oob(mtd, start, &ops);
456
457 if (put_user(ops.oobretlen, retp))
458 ret = -EFAULT;
459 else if (ops.oobretlen && copy_to_user(ptr, ops.oobbuf,
460 ops.oobretlen))
461 ret = -EFAULT;
462
463 kfree(ops.oobbuf);
464 return ret;
465}
466
Linus Torvalds1da177e2005-04-16 15:20:36 -0700467static int mtd_ioctl(struct inode *inode, struct file *file,
468 u_int cmd, u_long arg)
469{
Thomas Gleixnerf1a28c02006-05-30 00:37:34 +0200470 struct mtd_file_info *mfi = file->private_data;
471 struct mtd_info *mtd = mfi->mtd;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700472 void __user *argp = (void __user *)arg;
473 int ret = 0;
474 u_long size;
Joern Engel73c619e2006-05-30 14:25:35 +0200475 struct mtd_info_user info;
Thomas Gleixner97894cd2005-11-07 11:15:26 +0000476
Linus Torvalds1da177e2005-04-16 15:20:36 -0700477 DEBUG(MTD_DEBUG_LEVEL0, "MTD_ioctl\n");
478
479 size = (cmd & IOCSIZE_MASK) >> IOCSIZE_SHIFT;
480 if (cmd & IOC_IN) {
481 if (!access_ok(VERIFY_READ, argp, size))
482 return -EFAULT;
483 }
484 if (cmd & IOC_OUT) {
485 if (!access_ok(VERIFY_WRITE, argp, size))
486 return -EFAULT;
487 }
Thomas Gleixner97894cd2005-11-07 11:15:26 +0000488
Linus Torvalds1da177e2005-04-16 15:20:36 -0700489 switch (cmd) {
490 case MEMGETREGIONCOUNT:
491 if (copy_to_user(argp, &(mtd->numeraseregions), sizeof(int)))
492 return -EFAULT;
493 break;
494
495 case MEMGETREGIONINFO:
496 {
Zev Weissb67c5f82008-09-01 05:02:12 -0700497 uint32_t ur_idx;
498 struct mtd_erase_region_info *kr;
H Hartley Sweetenbcc98a42010-01-15 11:25:38 -0700499 struct region_info_user __user *ur = argp;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700500
Zev Weissb67c5f82008-09-01 05:02:12 -0700501 if (get_user(ur_idx, &(ur->regionindex)))
Linus Torvalds1da177e2005-04-16 15:20:36 -0700502 return -EFAULT;
503
Zev Weissb67c5f82008-09-01 05:02:12 -0700504 kr = &(mtd->eraseregions[ur_idx]);
505
506 if (put_user(kr->offset, &(ur->offset))
507 || put_user(kr->erasesize, &(ur->erasesize))
508 || put_user(kr->numblocks, &(ur->numblocks)))
Linus Torvalds1da177e2005-04-16 15:20:36 -0700509 return -EFAULT;
Zev Weissb67c5f82008-09-01 05:02:12 -0700510
Linus Torvalds1da177e2005-04-16 15:20:36 -0700511 break;
512 }
513
514 case MEMGETINFO:
Joern Engel73c619e2006-05-30 14:25:35 +0200515 info.type = mtd->type;
516 info.flags = mtd->flags;
517 info.size = mtd->size;
518 info.erasesize = mtd->erasesize;
519 info.writesize = mtd->writesize;
520 info.oobsize = mtd->oobsize;
Artem Bityutskiy64f60712007-01-30 10:50:43 +0200521 /* The below fields are obsolete */
522 info.ecctype = -1;
523 info.eccsize = 0;
Joern Engel73c619e2006-05-30 14:25:35 +0200524 if (copy_to_user(argp, &info, sizeof(struct mtd_info_user)))
Linus Torvalds1da177e2005-04-16 15:20:36 -0700525 return -EFAULT;
526 break;
527
528 case MEMERASE:
Kevin Cernekee0dc54e92009-04-08 22:52:28 -0700529 case MEMERASE64:
Linus Torvalds1da177e2005-04-16 15:20:36 -0700530 {
531 struct erase_info *erase;
532
Al Viroaeb5d722008-09-02 15:28:45 -0400533 if(!(file->f_mode & FMODE_WRITE))
Linus Torvalds1da177e2005-04-16 15:20:36 -0700534 return -EPERM;
535
Burman Yan95b93a02006-11-15 21:10:29 +0200536 erase=kzalloc(sizeof(struct erase_info),GFP_KERNEL);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700537 if (!erase)
538 ret = -ENOMEM;
539 else {
540 wait_queue_head_t waitq;
541 DECLARE_WAITQUEUE(wait, current);
542
543 init_waitqueue_head(&waitq);
544
Kevin Cernekee0dc54e92009-04-08 22:52:28 -0700545 if (cmd == MEMERASE64) {
546 struct erase_info_user64 einfo64;
547
548 if (copy_from_user(&einfo64, argp,
549 sizeof(struct erase_info_user64))) {
550 kfree(erase);
551 return -EFAULT;
552 }
553 erase->addr = einfo64.start;
554 erase->len = einfo64.length;
555 } else {
556 struct erase_info_user einfo32;
557
558 if (copy_from_user(&einfo32, argp,
559 sizeof(struct erase_info_user))) {
560 kfree(erase);
561 return -EFAULT;
562 }
563 erase->addr = einfo32.start;
564 erase->len = einfo32.length;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700565 }
566 erase->mtd = mtd;
567 erase->callback = mtdchar_erase_callback;
568 erase->priv = (unsigned long)&waitq;
Thomas Gleixner97894cd2005-11-07 11:15:26 +0000569
Linus Torvalds1da177e2005-04-16 15:20:36 -0700570 /*
571 FIXME: Allow INTERRUPTIBLE. Which means
572 not having the wait_queue head on the stack.
Thomas Gleixner97894cd2005-11-07 11:15:26 +0000573
Linus Torvalds1da177e2005-04-16 15:20:36 -0700574 If the wq_head is on the stack, and we
575 leave because we got interrupted, then the
576 wq_head is no longer there when the
577 callback routine tries to wake us up.
578 */
579 ret = mtd->erase(mtd, erase);
580 if (!ret) {
581 set_current_state(TASK_UNINTERRUPTIBLE);
582 add_wait_queue(&waitq, &wait);
583 if (erase->state != MTD_ERASE_DONE &&
584 erase->state != MTD_ERASE_FAILED)
585 schedule();
586 remove_wait_queue(&waitq, &wait);
587 set_current_state(TASK_RUNNING);
588
589 ret = (erase->state == MTD_ERASE_FAILED)?-EIO:0;
590 }
591 kfree(erase);
592 }
593 break;
594 }
595
596 case MEMWRITEOOB:
597 {
598 struct mtd_oob_buf buf;
Kevin Cernekee97718542009-04-08 22:53:13 -0700599 struct mtd_oob_buf __user *buf_user = argp;
Thomas Gleixner97894cd2005-11-07 11:15:26 +0000600
Kevin Cernekee97718542009-04-08 22:53:13 -0700601 /* NOTE: writes return length to buf_user->length */
602 if (copy_from_user(&buf, argp, sizeof(buf)))
Linus Torvalds1da177e2005-04-16 15:20:36 -0700603 ret = -EFAULT;
Kevin Cernekee97718542009-04-08 22:53:13 -0700604 else
605 ret = mtd_do_writeoob(file, mtd, buf.start, buf.length,
606 buf.ptr, &buf_user->length);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700607 break;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700608 }
609
610 case MEMREADOOB:
611 {
612 struct mtd_oob_buf buf;
Kevin Cernekee97718542009-04-08 22:53:13 -0700613 struct mtd_oob_buf __user *buf_user = argp;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700614
Kevin Cernekee97718542009-04-08 22:53:13 -0700615 /* NOTE: writes return length to buf_user->start */
616 if (copy_from_user(&buf, argp, sizeof(buf)))
617 ret = -EFAULT;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700618 else
Kevin Cernekee97718542009-04-08 22:53:13 -0700619 ret = mtd_do_readoob(mtd, buf.start, buf.length,
620 buf.ptr, &buf_user->start);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700621 break;
622 }
623
Kevin Cernekeeaea7cea2009-04-08 22:53:49 -0700624 case MEMWRITEOOB64:
625 {
626 struct mtd_oob_buf64 buf;
627 struct mtd_oob_buf64 __user *buf_user = argp;
628
629 if (copy_from_user(&buf, argp, sizeof(buf)))
630 ret = -EFAULT;
631 else
632 ret = mtd_do_writeoob(file, mtd, buf.start, buf.length,
633 (void __user *)(uintptr_t)buf.usr_ptr,
634 &buf_user->length);
635 break;
636 }
637
638 case MEMREADOOB64:
639 {
640 struct mtd_oob_buf64 buf;
641 struct mtd_oob_buf64 __user *buf_user = argp;
642
643 if (copy_from_user(&buf, argp, sizeof(buf)))
644 ret = -EFAULT;
645 else
646 ret = mtd_do_readoob(mtd, buf.start, buf.length,
647 (void __user *)(uintptr_t)buf.usr_ptr,
648 &buf_user->length);
649 break;
650 }
651
Linus Torvalds1da177e2005-04-16 15:20:36 -0700652 case MEMLOCK:
653 {
Harvey Harrison175428b2008-07-03 23:40:14 -0700654 struct erase_info_user einfo;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700655
Harvey Harrison175428b2008-07-03 23:40:14 -0700656 if (copy_from_user(&einfo, argp, sizeof(einfo)))
Linus Torvalds1da177e2005-04-16 15:20:36 -0700657 return -EFAULT;
658
659 if (!mtd->lock)
660 ret = -EOPNOTSUPP;
661 else
Harvey Harrison175428b2008-07-03 23:40:14 -0700662 ret = mtd->lock(mtd, einfo.start, einfo.length);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700663 break;
664 }
665
666 case MEMUNLOCK:
667 {
Harvey Harrison175428b2008-07-03 23:40:14 -0700668 struct erase_info_user einfo;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700669
Harvey Harrison175428b2008-07-03 23:40:14 -0700670 if (copy_from_user(&einfo, argp, sizeof(einfo)))
Linus Torvalds1da177e2005-04-16 15:20:36 -0700671 return -EFAULT;
672
673 if (!mtd->unlock)
674 ret = -EOPNOTSUPP;
675 else
Harvey Harrison175428b2008-07-03 23:40:14 -0700676 ret = mtd->unlock(mtd, einfo.start, einfo.length);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700677 break;
678 }
679
Richard Cochran99384242010-06-14 18:10:33 +0200680 case MEMISLOCKED:
681 {
682 struct erase_info_user einfo;
683
684 if (copy_from_user(&einfo, argp, sizeof(einfo)))
685 return -EFAULT;
686
687 if (!mtd->is_locked)
688 ret = -EOPNOTSUPP;
689 else
690 ret = mtd->is_locked(mtd, einfo.start, einfo.length);
691 break;
692 }
693
Thomas Gleixner5bd34c02006-05-27 22:16:10 +0200694 /* Legacy interface */
Linus Torvalds1da177e2005-04-16 15:20:36 -0700695 case MEMGETOOBSEL:
696 {
Thomas Gleixner5bd34c02006-05-27 22:16:10 +0200697 struct nand_oobinfo oi;
698
699 if (!mtd->ecclayout)
700 return -EOPNOTSUPP;
701 if (mtd->ecclayout->eccbytes > ARRAY_SIZE(oi.eccpos))
702 return -EINVAL;
703
704 oi.useecc = MTD_NANDECC_AUTOPLACE;
705 memcpy(&oi.eccpos, mtd->ecclayout->eccpos, sizeof(oi.eccpos));
706 memcpy(&oi.oobfree, mtd->ecclayout->oobfree,
707 sizeof(oi.oobfree));
Ricard Wanderlöfd25ade72006-10-17 17:27:11 +0200708 oi.eccbytes = mtd->ecclayout->eccbytes;
Thomas Gleixner5bd34c02006-05-27 22:16:10 +0200709
710 if (copy_to_user(argp, &oi, sizeof(struct nand_oobinfo)))
Linus Torvalds1da177e2005-04-16 15:20:36 -0700711 return -EFAULT;
712 break;
713 }
714
715 case MEMGETBADBLOCK:
716 {
717 loff_t offs;
Thomas Gleixner97894cd2005-11-07 11:15:26 +0000718
Linus Torvalds1da177e2005-04-16 15:20:36 -0700719 if (copy_from_user(&offs, argp, sizeof(loff_t)))
720 return -EFAULT;
721 if (!mtd->block_isbad)
722 ret = -EOPNOTSUPP;
723 else
724 return mtd->block_isbad(mtd, offs);
725 break;
726 }
727
728 case MEMSETBADBLOCK:
729 {
730 loff_t offs;
731
732 if (copy_from_user(&offs, argp, sizeof(loff_t)))
733 return -EFAULT;
734 if (!mtd->block_markbad)
735 ret = -EOPNOTSUPP;
736 else
737 return mtd->block_markbad(mtd, offs);
738 break;
739 }
740
David Brownell34a82442008-07-30 12:35:05 -0700741#ifdef CONFIG_HAVE_MTD_OTP
Nicolas Pitre31f42332005-02-08 17:45:55 +0000742 case OTPSELECT:
743 {
744 int mode;
745 if (copy_from_user(&mode, argp, sizeof(int)))
746 return -EFAULT;
Thomas Gleixnerf1a28c02006-05-30 00:37:34 +0200747
748 mfi->mode = MTD_MODE_NORMAL;
749
750 ret = otp_select_filemode(mfi, mode);
751
Nicolas Pitre81dba482005-04-01 16:36:15 +0100752 file->f_pos = 0;
Nicolas Pitre31f42332005-02-08 17:45:55 +0000753 break;
754 }
755
756 case OTPGETREGIONCOUNT:
757 case OTPGETREGIONINFO:
758 {
759 struct otp_info *buf = kmalloc(4096, GFP_KERNEL);
760 if (!buf)
761 return -ENOMEM;
762 ret = -EOPNOTSUPP;
Thomas Gleixnerf1a28c02006-05-30 00:37:34 +0200763 switch (mfi->mode) {
764 case MTD_MODE_OTP_FACTORY:
Nicolas Pitre31f42332005-02-08 17:45:55 +0000765 if (mtd->get_fact_prot_info)
766 ret = mtd->get_fact_prot_info(mtd, buf, 4096);
767 break;
768 case MTD_MODE_OTP_USER:
769 if (mtd->get_user_prot_info)
770 ret = mtd->get_user_prot_info(mtd, buf, 4096);
771 break;
Thomas Gleixnerf1a28c02006-05-30 00:37:34 +0200772 default:
773 break;
Nicolas Pitre31f42332005-02-08 17:45:55 +0000774 }
775 if (ret >= 0) {
776 if (cmd == OTPGETREGIONCOUNT) {
777 int nbr = ret / sizeof(struct otp_info);
778 ret = copy_to_user(argp, &nbr, sizeof(int));
779 } else
780 ret = copy_to_user(argp, buf, ret);
781 if (ret)
782 ret = -EFAULT;
783 }
784 kfree(buf);
785 break;
786 }
787
788 case OTPLOCK:
789 {
Harvey Harrison175428b2008-07-03 23:40:14 -0700790 struct otp_info oinfo;
Nicolas Pitre31f42332005-02-08 17:45:55 +0000791
Thomas Gleixnerf1a28c02006-05-30 00:37:34 +0200792 if (mfi->mode != MTD_MODE_OTP_USER)
Nicolas Pitre31f42332005-02-08 17:45:55 +0000793 return -EINVAL;
Harvey Harrison175428b2008-07-03 23:40:14 -0700794 if (copy_from_user(&oinfo, argp, sizeof(oinfo)))
Nicolas Pitre31f42332005-02-08 17:45:55 +0000795 return -EFAULT;
796 if (!mtd->lock_user_prot_reg)
797 return -EOPNOTSUPP;
Harvey Harrison175428b2008-07-03 23:40:14 -0700798 ret = mtd->lock_user_prot_reg(mtd, oinfo.start, oinfo.length);
Nicolas Pitre31f42332005-02-08 17:45:55 +0000799 break;
800 }
801#endif
802
Thomas Gleixnerf1a28c02006-05-30 00:37:34 +0200803 case ECCGETLAYOUT:
804 {
805 if (!mtd->ecclayout)
806 return -EOPNOTSUPP;
807
Ricard Wanderlöfd25ade72006-10-17 17:27:11 +0200808 if (copy_to_user(argp, mtd->ecclayout,
Thomas Gleixnerf1a28c02006-05-30 00:37:34 +0200809 sizeof(struct nand_ecclayout)))
810 return -EFAULT;
811 break;
812 }
813
814 case ECCGETSTATS:
815 {
816 if (copy_to_user(argp, &mtd->ecc_stats,
817 sizeof(struct mtd_ecc_stats)))
818 return -EFAULT;
819 break;
820 }
821
822 case MTDFILEMODE:
823 {
824 mfi->mode = 0;
825
826 switch(arg) {
827 case MTD_MODE_OTP_FACTORY:
828 case MTD_MODE_OTP_USER:
829 ret = otp_select_filemode(mfi, arg);
830 break;
831
832 case MTD_MODE_RAW:
833 if (!mtd->read_oob || !mtd->write_oob)
834 return -EOPNOTSUPP;
835 mfi->mode = arg;
836
837 case MTD_MODE_NORMAL:
838 break;
839 default:
840 ret = -EINVAL;
841 }
842 file->f_pos = 0;
843 break;
844 }
845
Linus Torvalds1da177e2005-04-16 15:20:36 -0700846 default:
847 ret = -ENOTTY;
848 }
849
850 return ret;
851} /* memory_ioctl */
852
Kevin Cernekee97718542009-04-08 22:53:13 -0700853#ifdef CONFIG_COMPAT
854
855struct mtd_oob_buf32 {
856 u_int32_t start;
857 u_int32_t length;
858 compat_caddr_t ptr; /* unsigned char* */
859};
860
861#define MEMWRITEOOB32 _IOWR('M', 3, struct mtd_oob_buf32)
862#define MEMREADOOB32 _IOWR('M', 4, struct mtd_oob_buf32)
863
864static long mtd_compat_ioctl(struct file *file, unsigned int cmd,
865 unsigned long arg)
866{
Kevin Cernekee668ff9a2009-04-14 21:59:22 -0700867 struct inode *inode = file->f_path.dentry->d_inode;
Kevin Cernekee97718542009-04-08 22:53:13 -0700868 struct mtd_file_info *mfi = file->private_data;
869 struct mtd_info *mtd = mfi->mtd;
David Woodhouse0b6585c2009-05-29 16:09:08 +0100870 void __user *argp = compat_ptr(arg);
Kevin Cernekee97718542009-04-08 22:53:13 -0700871 int ret = 0;
872
873 lock_kernel();
874
875 switch (cmd) {
876 case MEMWRITEOOB32:
877 {
878 struct mtd_oob_buf32 buf;
879 struct mtd_oob_buf32 __user *buf_user = argp;
880
881 if (copy_from_user(&buf, argp, sizeof(buf)))
882 ret = -EFAULT;
883 else
884 ret = mtd_do_writeoob(file, mtd, buf.start,
885 buf.length, compat_ptr(buf.ptr),
886 &buf_user->length);
887 break;
888 }
889
890 case MEMREADOOB32:
891 {
892 struct mtd_oob_buf32 buf;
893 struct mtd_oob_buf32 __user *buf_user = argp;
894
895 /* NOTE: writes return length to buf->start */
896 if (copy_from_user(&buf, argp, sizeof(buf)))
897 ret = -EFAULT;
898 else
899 ret = mtd_do_readoob(mtd, buf.start,
900 buf.length, compat_ptr(buf.ptr),
901 &buf_user->start);
902 break;
903 }
904 default:
David Woodhouse0b6585c2009-05-29 16:09:08 +0100905 ret = mtd_ioctl(inode, file, cmd, (unsigned long)argp);
Kevin Cernekee97718542009-04-08 22:53:13 -0700906 }
907
908 unlock_kernel();
909
910 return ret;
911}
912
913#endif /* CONFIG_COMPAT */
914
David Howells402d3262009-02-12 10:40:00 +0000915/*
916 * try to determine where a shared mapping can be made
917 * - only supported for NOMMU at the moment (MMU can't doesn't copy private
918 * mappings)
919 */
920#ifndef CONFIG_MMU
921static unsigned long mtd_get_unmapped_area(struct file *file,
922 unsigned long addr,
923 unsigned long len,
924 unsigned long pgoff,
925 unsigned long flags)
926{
927 struct mtd_file_info *mfi = file->private_data;
928 struct mtd_info *mtd = mfi->mtd;
929
930 if (mtd->get_unmapped_area) {
931 unsigned long offset;
932
933 if (addr != 0)
934 return (unsigned long) -EINVAL;
935
936 if (len > mtd->size || pgoff >= (mtd->size >> PAGE_SHIFT))
937 return (unsigned long) -EINVAL;
938
939 offset = pgoff << PAGE_SHIFT;
940 if (offset > mtd->size - len)
941 return (unsigned long) -EINVAL;
942
943 return mtd->get_unmapped_area(mtd, len, offset, flags);
944 }
945
946 /* can't map directly */
947 return (unsigned long) -ENOSYS;
948}
949#endif
950
951/*
952 * set up a mapping for shared memory segments
953 */
954static int mtd_mmap(struct file *file, struct vm_area_struct *vma)
955{
956#ifdef CONFIG_MMU
957 struct mtd_file_info *mfi = file->private_data;
958 struct mtd_info *mtd = mfi->mtd;
Anatolij Gustschindd02b672010-06-15 09:30:15 +0200959 struct map_info *map = mtd->priv;
960 unsigned long start;
961 unsigned long off;
962 u32 len;
David Howells402d3262009-02-12 10:40:00 +0000963
Anatolij Gustschindd02b672010-06-15 09:30:15 +0200964 if (mtd->type == MTD_RAM || mtd->type == MTD_ROM) {
965 off = vma->vm_pgoff << PAGE_SHIFT;
966 start = map->phys;
967 len = PAGE_ALIGN((start & ~PAGE_MASK) + map->size);
968 start &= PAGE_MASK;
969 if ((vma->vm_end - vma->vm_start + off) > len)
970 return -EINVAL;
971
972 off += start;
973 vma->vm_pgoff = off >> PAGE_SHIFT;
974 vma->vm_flags |= VM_IO | VM_RESERVED;
975
976#ifdef pgprot_noncached
977 if (file->f_flags & O_DSYNC || off >= __pa(high_memory))
978 vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot);
979#endif
980 if (io_remap_pfn_range(vma, vma->vm_start, off >> PAGE_SHIFT,
981 vma->vm_end - vma->vm_start,
982 vma->vm_page_prot))
983 return -EAGAIN;
984
David Howells402d3262009-02-12 10:40:00 +0000985 return 0;
Anatolij Gustschindd02b672010-06-15 09:30:15 +0200986 }
David Howells402d3262009-02-12 10:40:00 +0000987 return -ENOSYS;
988#else
989 return vma->vm_flags & VM_SHARED ? 0 : -ENOSYS;
990#endif
991}
992
Arjan van de Vend54b1fd2007-02-12 00:55:34 -0800993static const struct file_operations mtd_fops = {
Linus Torvalds1da177e2005-04-16 15:20:36 -0700994 .owner = THIS_MODULE,
995 .llseek = mtd_lseek,
996 .read = mtd_read,
997 .write = mtd_write,
998 .ioctl = mtd_ioctl,
Kevin Cernekee97718542009-04-08 22:53:13 -0700999#ifdef CONFIG_COMPAT
1000 .compat_ioctl = mtd_compat_ioctl,
1001#endif
Linus Torvalds1da177e2005-04-16 15:20:36 -07001002 .open = mtd_open,
1003 .release = mtd_close,
David Howells402d3262009-02-12 10:40:00 +00001004 .mmap = mtd_mmap,
1005#ifndef CONFIG_MMU
1006 .get_unmapped_area = mtd_get_unmapped_area,
1007#endif
Linus Torvalds1da177e2005-04-16 15:20:36 -07001008};
1009
Kirill A. Shutemovcd874232010-05-17 16:55:47 +03001010static int mtd_inodefs_get_sb(struct file_system_type *fs_type, int flags,
1011 const char *dev_name, void *data,
1012 struct vfsmount *mnt)
1013{
1014 return get_sb_pseudo(fs_type, "mtd_inode:", NULL, MTD_INODE_FS_MAGIC,
1015 mnt);
1016}
1017
1018static struct file_system_type mtd_inodefs_type = {
1019 .name = "mtd_inodefs",
1020 .get_sb = mtd_inodefs_get_sb,
1021 .kill_sb = kill_anon_super,
1022};
1023
1024static void mtdchar_notify_add(struct mtd_info *mtd)
1025{
1026}
1027
1028static void mtdchar_notify_remove(struct mtd_info *mtd)
1029{
1030 struct inode *mtd_ino = ilookup(mtd_inode_mnt->mnt_sb, mtd->index);
1031
1032 if (mtd_ino) {
1033 /* Destroy the inode if it exists */
1034 mtd_ino->i_nlink = 0;
1035 iput(mtd_ino);
1036 }
1037}
1038
1039static struct mtd_notifier mtdchar_notifier = {
1040 .add = mtdchar_notify_add,
1041 .remove = mtdchar_notify_remove,
1042};
1043
Linus Torvalds1da177e2005-04-16 15:20:36 -07001044static int __init init_mtdchar(void)
1045{
Kirill A. Shutemovcd874232010-05-17 16:55:47 +03001046 int ret;
David Brownell1f24b5a2009-03-26 00:42:41 -07001047
Kirill A. Shutemovcd874232010-05-17 16:55:47 +03001048 ret = __register_chrdev(MTD_CHAR_MAJOR, 0, 1 << MINORBITS,
Ben Hutchingsdad0db32010-01-29 21:00:04 +00001049 "mtd", &mtd_fops);
Kirill A. Shutemovcd874232010-05-17 16:55:47 +03001050 if (ret < 0) {
1051 pr_notice("Can't allocate major number %d for "
1052 "Memory Technology Devices.\n", MTD_CHAR_MAJOR);
1053 return ret;
Linus Torvalds1da177e2005-04-16 15:20:36 -07001054 }
1055
Kirill A. Shutemovcd874232010-05-17 16:55:47 +03001056 ret = register_filesystem(&mtd_inodefs_type);
1057 if (ret) {
1058 pr_notice("Can't register mtd_inodefs filesystem: %d\n", ret);
1059 goto err_unregister_chdev;
1060 }
1061
1062 mtd_inode_mnt = kern_mount(&mtd_inodefs_type);
1063 if (IS_ERR(mtd_inode_mnt)) {
1064 ret = PTR_ERR(mtd_inode_mnt);
1065 pr_notice("Error mounting mtd_inodefs filesystem: %d\n", ret);
1066 goto err_unregister_filesystem;
1067 }
1068 register_mtd_user(&mtdchar_notifier);
1069
1070 return ret;
1071
1072err_unregister_filesystem:
1073 unregister_filesystem(&mtd_inodefs_type);
1074err_unregister_chdev:
1075 __unregister_chrdev(MTD_CHAR_MAJOR, 0, 1 << MINORBITS, "mtd");
1076 return ret;
Linus Torvalds1da177e2005-04-16 15:20:36 -07001077}
1078
1079static void __exit cleanup_mtdchar(void)
1080{
Kirill A. Shutemovcd874232010-05-17 16:55:47 +03001081 unregister_mtd_user(&mtdchar_notifier);
1082 mntput(mtd_inode_mnt);
1083 unregister_filesystem(&mtd_inodefs_type);
Ben Hutchingsdad0db32010-01-29 21:00:04 +00001084 __unregister_chrdev(MTD_CHAR_MAJOR, 0, 1 << MINORBITS, "mtd");
Linus Torvalds1da177e2005-04-16 15:20:36 -07001085}
1086
1087module_init(init_mtdchar);
1088module_exit(cleanup_mtdchar);
1089
David Brownell1f24b5a2009-03-26 00:42:41 -07001090MODULE_ALIAS_CHARDEV_MAJOR(MTD_CHAR_MAJOR);
Linus Torvalds1da177e2005-04-16 15:20:36 -07001091
1092MODULE_LICENSE("GPL");
1093MODULE_AUTHOR("David Woodhouse <dwmw2@infradead.org>");
1094MODULE_DESCRIPTION("Direct character-device access to MTD devices");
Scott James Remnant90160e12009-03-02 18:42:39 +00001095MODULE_ALIAS_CHARDEV_MAJOR(MTD_CHAR_MAJOR);