diff options
author | Linus Torvalds <torvalds@ppc970.osdl.org> | 2005-04-16 18:20:36 -0400 |
---|---|---|
committer | Linus Torvalds <torvalds@ppc970.osdl.org> | 2005-04-16 18:20:36 -0400 |
commit | 1da177e4c3f41524e886b7f1b8a0c1fc7321cac2 (patch) | |
tree | 0bba044c4ce775e45a88a51686b5d9f90697ea9d /drivers/mtd/mtdchar.c |
Linux-2.6.12-rc2v2.6.12-rc2
Initial git repository build. I'm not bothering with the full history,
even though we have it. We can create a separate "historical" git
archive of that later if we want to, and in the meantime it's about
3.2GB when imported into git - space that would just make the early
git days unnecessarily complicated, when we don't have a lot of good
infrastructure for it.
Let it rip!
Diffstat (limited to 'drivers/mtd/mtdchar.c')
-rw-r--r-- | drivers/mtd/mtdchar.c | 562 |
1 files changed, 562 insertions, 0 deletions
diff --git a/drivers/mtd/mtdchar.c b/drivers/mtd/mtdchar.c new file mode 100644 index 000000000000..510ad78312cc --- /dev/null +++ b/drivers/mtd/mtdchar.c | |||
@@ -0,0 +1,562 @@ | |||
1 | /* | ||
2 | * $Id: mtdchar.c,v 1.66 2005/01/05 18:05:11 dwmw2 Exp $ | ||
3 | * | ||
4 | * Character-device access to raw MTD devices. | ||
5 | * | ||
6 | */ | ||
7 | |||
8 | #include <linux/config.h> | ||
9 | #include <linux/kernel.h> | ||
10 | #include <linux/module.h> | ||
11 | #include <linux/mtd/mtd.h> | ||
12 | #include <linux/mtd/compatmac.h> | ||
13 | #include <linux/slab.h> | ||
14 | #include <linux/init.h> | ||
15 | #include <linux/fs.h> | ||
16 | #include <asm/uaccess.h> | ||
17 | |||
18 | #ifdef CONFIG_DEVFS_FS | ||
19 | #include <linux/devfs_fs_kernel.h> | ||
20 | |||
21 | static void mtd_notify_add(struct mtd_info* mtd) | ||
22 | { | ||
23 | if (!mtd) | ||
24 | return; | ||
25 | |||
26 | devfs_mk_cdev(MKDEV(MTD_CHAR_MAJOR, mtd->index*2), | ||
27 | S_IFCHR | S_IRUGO | S_IWUGO, "mtd/%d", mtd->index); | ||
28 | |||
29 | devfs_mk_cdev(MKDEV(MTD_CHAR_MAJOR, mtd->index*2+1), | ||
30 | S_IFCHR | S_IRUGO, "mtd/%dro", mtd->index); | ||
31 | } | ||
32 | |||
33 | static void mtd_notify_remove(struct mtd_info* mtd) | ||
34 | { | ||
35 | if (!mtd) | ||
36 | return; | ||
37 | devfs_remove("mtd/%d", mtd->index); | ||
38 | devfs_remove("mtd/%dro", mtd->index); | ||
39 | } | ||
40 | |||
41 | static struct mtd_notifier notifier = { | ||
42 | .add = mtd_notify_add, | ||
43 | .remove = mtd_notify_remove, | ||
44 | }; | ||
45 | |||
46 | static inline void mtdchar_devfs_init(void) | ||
47 | { | ||
48 | devfs_mk_dir("mtd"); | ||
49 | register_mtd_user(¬ifier); | ||
50 | } | ||
51 | |||
52 | static inline void mtdchar_devfs_exit(void) | ||
53 | { | ||
54 | unregister_mtd_user(¬ifier); | ||
55 | devfs_remove("mtd"); | ||
56 | } | ||
57 | #else /* !DEVFS */ | ||
58 | #define mtdchar_devfs_init() do { } while(0) | ||
59 | #define mtdchar_devfs_exit() do { } while(0) | ||
60 | #endif | ||
61 | |||
62 | static loff_t mtd_lseek (struct file *file, loff_t offset, int orig) | ||
63 | { | ||
64 | struct mtd_info *mtd = file->private_data; | ||
65 | |||
66 | switch (orig) { | ||
67 | case 0: | ||
68 | /* SEEK_SET */ | ||
69 | file->f_pos = offset; | ||
70 | break; | ||
71 | case 1: | ||
72 | /* SEEK_CUR */ | ||
73 | file->f_pos += offset; | ||
74 | break; | ||
75 | case 2: | ||
76 | /* SEEK_END */ | ||
77 | file->f_pos =mtd->size + offset; | ||
78 | break; | ||
79 | default: | ||
80 | return -EINVAL; | ||
81 | } | ||
82 | |||
83 | if (file->f_pos < 0) | ||
84 | file->f_pos = 0; | ||
85 | else if (file->f_pos >= mtd->size) | ||
86 | file->f_pos = mtd->size - 1; | ||
87 | |||
88 | return file->f_pos; | ||
89 | } | ||
90 | |||
91 | |||
92 | |||
93 | static int mtd_open(struct inode *inode, struct file *file) | ||
94 | { | ||
95 | int minor = iminor(inode); | ||
96 | int devnum = minor >> 1; | ||
97 | struct mtd_info *mtd; | ||
98 | |||
99 | DEBUG(MTD_DEBUG_LEVEL0, "MTD_open\n"); | ||
100 | |||
101 | if (devnum >= MAX_MTD_DEVICES) | ||
102 | return -ENODEV; | ||
103 | |||
104 | /* You can't open the RO devices RW */ | ||
105 | if ((file->f_mode & 2) && (minor & 1)) | ||
106 | return -EACCES; | ||
107 | |||
108 | mtd = get_mtd_device(NULL, devnum); | ||
109 | |||
110 | if (!mtd) | ||
111 | return -ENODEV; | ||
112 | |||
113 | if (MTD_ABSENT == mtd->type) { | ||
114 | put_mtd_device(mtd); | ||
115 | return -ENODEV; | ||
116 | } | ||
117 | |||
118 | file->private_data = mtd; | ||
119 | |||
120 | /* You can't open it RW if it's not a writeable device */ | ||
121 | if ((file->f_mode & 2) && !(mtd->flags & MTD_WRITEABLE)) { | ||
122 | put_mtd_device(mtd); | ||
123 | return -EACCES; | ||
124 | } | ||
125 | |||
126 | return 0; | ||
127 | } /* mtd_open */ | ||
128 | |||
129 | /*====================================================================*/ | ||
130 | |||
131 | static int mtd_close(struct inode *inode, struct file *file) | ||
132 | { | ||
133 | struct mtd_info *mtd; | ||
134 | |||
135 | DEBUG(MTD_DEBUG_LEVEL0, "MTD_close\n"); | ||
136 | |||
137 | mtd = file->private_data; | ||
138 | |||
139 | if (mtd->sync) | ||
140 | mtd->sync(mtd); | ||
141 | |||
142 | put_mtd_device(mtd); | ||
143 | |||
144 | return 0; | ||
145 | } /* mtd_close */ | ||
146 | |||
147 | /* FIXME: This _really_ needs to die. In 2.5, we should lock the | ||
148 | userspace buffer down and use it directly with readv/writev. | ||
149 | */ | ||
150 | #define MAX_KMALLOC_SIZE 0x20000 | ||
151 | |||
152 | static ssize_t mtd_read(struct file *file, char __user *buf, size_t count,loff_t *ppos) | ||
153 | { | ||
154 | struct mtd_info *mtd = file->private_data; | ||
155 | size_t retlen=0; | ||
156 | size_t total_retlen=0; | ||
157 | int ret=0; | ||
158 | int len; | ||
159 | char *kbuf; | ||
160 | |||
161 | DEBUG(MTD_DEBUG_LEVEL0,"MTD_read\n"); | ||
162 | |||
163 | if (*ppos + count > mtd->size) | ||
164 | count = mtd->size - *ppos; | ||
165 | |||
166 | if (!count) | ||
167 | return 0; | ||
168 | |||
169 | /* FIXME: Use kiovec in 2.5 to lock down the user's buffers | ||
170 | and pass them directly to the MTD functions */ | ||
171 | while (count) { | ||
172 | if (count > MAX_KMALLOC_SIZE) | ||
173 | len = MAX_KMALLOC_SIZE; | ||
174 | else | ||
175 | len = count; | ||
176 | |||
177 | kbuf=kmalloc(len,GFP_KERNEL); | ||
178 | if (!kbuf) | ||
179 | return -ENOMEM; | ||
180 | |||
181 | ret = MTD_READ(mtd, *ppos, len, &retlen, kbuf); | ||
182 | /* Nand returns -EBADMSG on ecc errors, but it returns | ||
183 | * the data. For our userspace tools it is important | ||
184 | * to dump areas with ecc errors ! | ||
185 | * Userspace software which accesses NAND this way | ||
186 | * must be aware of the fact that it deals with NAND | ||
187 | */ | ||
188 | if (!ret || (ret == -EBADMSG)) { | ||
189 | *ppos += retlen; | ||
190 | if (copy_to_user(buf, kbuf, retlen)) { | ||
191 | kfree(kbuf); | ||
192 | return -EFAULT; | ||
193 | } | ||
194 | else | ||
195 | total_retlen += retlen; | ||
196 | |||
197 | count -= retlen; | ||
198 | buf += retlen; | ||
199 | } | ||
200 | else { | ||
201 | kfree(kbuf); | ||
202 | return ret; | ||
203 | } | ||
204 | |||
205 | kfree(kbuf); | ||
206 | } | ||
207 | |||
208 | return total_retlen; | ||
209 | } /* mtd_read */ | ||
210 | |||
211 | static ssize_t mtd_write(struct file *file, const char __user *buf, size_t count,loff_t *ppos) | ||
212 | { | ||
213 | struct mtd_info *mtd = file->private_data; | ||
214 | char *kbuf; | ||
215 | size_t retlen; | ||
216 | size_t total_retlen=0; | ||
217 | int ret=0; | ||
218 | int len; | ||
219 | |||
220 | DEBUG(MTD_DEBUG_LEVEL0,"MTD_write\n"); | ||
221 | |||
222 | if (*ppos == mtd->size) | ||
223 | return -ENOSPC; | ||
224 | |||
225 | if (*ppos + count > mtd->size) | ||
226 | count = mtd->size - *ppos; | ||
227 | |||
228 | if (!count) | ||
229 | return 0; | ||
230 | |||
231 | while (count) { | ||
232 | if (count > MAX_KMALLOC_SIZE) | ||
233 | len = MAX_KMALLOC_SIZE; | ||
234 | else | ||
235 | len = count; | ||
236 | |||
237 | kbuf=kmalloc(len,GFP_KERNEL); | ||
238 | if (!kbuf) { | ||
239 | printk("kmalloc is null\n"); | ||
240 | return -ENOMEM; | ||
241 | } | ||
242 | |||
243 | if (copy_from_user(kbuf, buf, len)) { | ||
244 | kfree(kbuf); | ||
245 | return -EFAULT; | ||
246 | } | ||
247 | |||
248 | ret = (*(mtd->write))(mtd, *ppos, len, &retlen, kbuf); | ||
249 | if (!ret) { | ||
250 | *ppos += retlen; | ||
251 | total_retlen += retlen; | ||
252 | count -= retlen; | ||
253 | buf += retlen; | ||
254 | } | ||
255 | else { | ||
256 | kfree(kbuf); | ||
257 | return ret; | ||
258 | } | ||
259 | |||
260 | kfree(kbuf); | ||
261 | } | ||
262 | |||
263 | return total_retlen; | ||
264 | } /* mtd_write */ | ||
265 | |||
266 | /*====================================================================== | ||
267 | |||
268 | IOCTL calls for getting device parameters. | ||
269 | |||
270 | ======================================================================*/ | ||
271 | static void mtdchar_erase_callback (struct erase_info *instr) | ||
272 | { | ||
273 | wake_up((wait_queue_head_t *)instr->priv); | ||
274 | } | ||
275 | |||
276 | static int mtd_ioctl(struct inode *inode, struct file *file, | ||
277 | u_int cmd, u_long arg) | ||
278 | { | ||
279 | struct mtd_info *mtd = file->private_data; | ||
280 | void __user *argp = (void __user *)arg; | ||
281 | int ret = 0; | ||
282 | u_long size; | ||
283 | |||
284 | DEBUG(MTD_DEBUG_LEVEL0, "MTD_ioctl\n"); | ||
285 | |||
286 | size = (cmd & IOCSIZE_MASK) >> IOCSIZE_SHIFT; | ||
287 | if (cmd & IOC_IN) { | ||
288 | if (!access_ok(VERIFY_READ, argp, size)) | ||
289 | return -EFAULT; | ||
290 | } | ||
291 | if (cmd & IOC_OUT) { | ||
292 | if (!access_ok(VERIFY_WRITE, argp, size)) | ||
293 | return -EFAULT; | ||
294 | } | ||
295 | |||
296 | switch (cmd) { | ||
297 | case MEMGETREGIONCOUNT: | ||
298 | if (copy_to_user(argp, &(mtd->numeraseregions), sizeof(int))) | ||
299 | return -EFAULT; | ||
300 | break; | ||
301 | |||
302 | case MEMGETREGIONINFO: | ||
303 | { | ||
304 | struct region_info_user ur; | ||
305 | |||
306 | if (copy_from_user(&ur, argp, sizeof(struct region_info_user))) | ||
307 | return -EFAULT; | ||
308 | |||
309 | if (ur.regionindex >= mtd->numeraseregions) | ||
310 | return -EINVAL; | ||
311 | if (copy_to_user(argp, &(mtd->eraseregions[ur.regionindex]), | ||
312 | sizeof(struct mtd_erase_region_info))) | ||
313 | return -EFAULT; | ||
314 | break; | ||
315 | } | ||
316 | |||
317 | case MEMGETINFO: | ||
318 | if (copy_to_user(argp, mtd, sizeof(struct mtd_info_user))) | ||
319 | return -EFAULT; | ||
320 | break; | ||
321 | |||
322 | case MEMERASE: | ||
323 | { | ||
324 | struct erase_info *erase; | ||
325 | |||
326 | if(!(file->f_mode & 2)) | ||
327 | return -EPERM; | ||
328 | |||
329 | erase=kmalloc(sizeof(struct erase_info),GFP_KERNEL); | ||
330 | if (!erase) | ||
331 | ret = -ENOMEM; | ||
332 | else { | ||
333 | wait_queue_head_t waitq; | ||
334 | DECLARE_WAITQUEUE(wait, current); | ||
335 | |||
336 | init_waitqueue_head(&waitq); | ||
337 | |||
338 | memset (erase,0,sizeof(struct erase_info)); | ||
339 | if (copy_from_user(&erase->addr, argp, | ||
340 | sizeof(struct erase_info_user))) { | ||
341 | kfree(erase); | ||
342 | return -EFAULT; | ||
343 | } | ||
344 | erase->mtd = mtd; | ||
345 | erase->callback = mtdchar_erase_callback; | ||
346 | erase->priv = (unsigned long)&waitq; | ||
347 | |||
348 | /* | ||
349 | FIXME: Allow INTERRUPTIBLE. Which means | ||
350 | not having the wait_queue head on the stack. | ||
351 | |||
352 | If the wq_head is on the stack, and we | ||
353 | leave because we got interrupted, then the | ||
354 | wq_head is no longer there when the | ||
355 | callback routine tries to wake us up. | ||
356 | */ | ||
357 | ret = mtd->erase(mtd, erase); | ||
358 | if (!ret) { | ||
359 | set_current_state(TASK_UNINTERRUPTIBLE); | ||
360 | add_wait_queue(&waitq, &wait); | ||
361 | if (erase->state != MTD_ERASE_DONE && | ||
362 | erase->state != MTD_ERASE_FAILED) | ||
363 | schedule(); | ||
364 | remove_wait_queue(&waitq, &wait); | ||
365 | set_current_state(TASK_RUNNING); | ||
366 | |||
367 | ret = (erase->state == MTD_ERASE_FAILED)?-EIO:0; | ||
368 | } | ||
369 | kfree(erase); | ||
370 | } | ||
371 | break; | ||
372 | } | ||
373 | |||
374 | case MEMWRITEOOB: | ||
375 | { | ||
376 | struct mtd_oob_buf buf; | ||
377 | void *databuf; | ||
378 | ssize_t retlen; | ||
379 | |||
380 | if(!(file->f_mode & 2)) | ||
381 | return -EPERM; | ||
382 | |||
383 | if (copy_from_user(&buf, argp, sizeof(struct mtd_oob_buf))) | ||
384 | return -EFAULT; | ||
385 | |||
386 | if (buf.length > 0x4096) | ||
387 | return -EINVAL; | ||
388 | |||
389 | if (!mtd->write_oob) | ||
390 | ret = -EOPNOTSUPP; | ||
391 | else | ||
392 | ret = access_ok(VERIFY_READ, buf.ptr, | ||
393 | buf.length) ? 0 : EFAULT; | ||
394 | |||
395 | if (ret) | ||
396 | return ret; | ||
397 | |||
398 | databuf = kmalloc(buf.length, GFP_KERNEL); | ||
399 | if (!databuf) | ||
400 | return -ENOMEM; | ||
401 | |||
402 | if (copy_from_user(databuf, buf.ptr, buf.length)) { | ||
403 | kfree(databuf); | ||
404 | return -EFAULT; | ||
405 | } | ||
406 | |||
407 | ret = (mtd->write_oob)(mtd, buf.start, buf.length, &retlen, databuf); | ||
408 | |||
409 | if (copy_to_user(argp + sizeof(uint32_t), &retlen, sizeof(uint32_t))) | ||
410 | ret = -EFAULT; | ||
411 | |||
412 | kfree(databuf); | ||
413 | break; | ||
414 | |||
415 | } | ||
416 | |||
417 | case MEMREADOOB: | ||
418 | { | ||
419 | struct mtd_oob_buf buf; | ||
420 | void *databuf; | ||
421 | ssize_t retlen; | ||
422 | |||
423 | if (copy_from_user(&buf, argp, sizeof(struct mtd_oob_buf))) | ||
424 | return -EFAULT; | ||
425 | |||
426 | if (buf.length > 0x4096) | ||
427 | return -EINVAL; | ||
428 | |||
429 | if (!mtd->read_oob) | ||
430 | ret = -EOPNOTSUPP; | ||
431 | else | ||
432 | ret = access_ok(VERIFY_WRITE, buf.ptr, | ||
433 | buf.length) ? 0 : -EFAULT; | ||
434 | |||
435 | if (ret) | ||
436 | return ret; | ||
437 | |||
438 | databuf = kmalloc(buf.length, GFP_KERNEL); | ||
439 | if (!databuf) | ||
440 | return -ENOMEM; | ||
441 | |||
442 | ret = (mtd->read_oob)(mtd, buf.start, buf.length, &retlen, databuf); | ||
443 | |||
444 | if (put_user(retlen, (uint32_t __user *)argp)) | ||
445 | ret = -EFAULT; | ||
446 | else if (retlen && copy_to_user(buf.ptr, databuf, retlen)) | ||
447 | ret = -EFAULT; | ||
448 | |||
449 | kfree(databuf); | ||
450 | break; | ||
451 | } | ||
452 | |||
453 | case MEMLOCK: | ||
454 | { | ||
455 | struct erase_info_user info; | ||
456 | |||
457 | if (copy_from_user(&info, argp, sizeof(info))) | ||
458 | return -EFAULT; | ||
459 | |||
460 | if (!mtd->lock) | ||
461 | ret = -EOPNOTSUPP; | ||
462 | else | ||
463 | ret = mtd->lock(mtd, info.start, info.length); | ||
464 | break; | ||
465 | } | ||
466 | |||
467 | case MEMUNLOCK: | ||
468 | { | ||
469 | struct erase_info_user info; | ||
470 | |||
471 | if (copy_from_user(&info, argp, sizeof(info))) | ||
472 | return -EFAULT; | ||
473 | |||
474 | if (!mtd->unlock) | ||
475 | ret = -EOPNOTSUPP; | ||
476 | else | ||
477 | ret = mtd->unlock(mtd, info.start, info.length); | ||
478 | break; | ||
479 | } | ||
480 | |||
481 | case MEMSETOOBSEL: | ||
482 | { | ||
483 | if (copy_from_user(&mtd->oobinfo, argp, sizeof(struct nand_oobinfo))) | ||
484 | return -EFAULT; | ||
485 | break; | ||
486 | } | ||
487 | |||
488 | case MEMGETOOBSEL: | ||
489 | { | ||
490 | if (copy_to_user(argp, &(mtd->oobinfo), sizeof(struct nand_oobinfo))) | ||
491 | return -EFAULT; | ||
492 | break; | ||
493 | } | ||
494 | |||
495 | case MEMGETBADBLOCK: | ||
496 | { | ||
497 | loff_t offs; | ||
498 | |||
499 | if (copy_from_user(&offs, argp, sizeof(loff_t))) | ||
500 | return -EFAULT; | ||
501 | if (!mtd->block_isbad) | ||
502 | ret = -EOPNOTSUPP; | ||
503 | else | ||
504 | return mtd->block_isbad(mtd, offs); | ||
505 | break; | ||
506 | } | ||
507 | |||
508 | case MEMSETBADBLOCK: | ||
509 | { | ||
510 | loff_t offs; | ||
511 | |||
512 | if (copy_from_user(&offs, argp, sizeof(loff_t))) | ||
513 | return -EFAULT; | ||
514 | if (!mtd->block_markbad) | ||
515 | ret = -EOPNOTSUPP; | ||
516 | else | ||
517 | return mtd->block_markbad(mtd, offs); | ||
518 | break; | ||
519 | } | ||
520 | |||
521 | default: | ||
522 | ret = -ENOTTY; | ||
523 | } | ||
524 | |||
525 | return ret; | ||
526 | } /* memory_ioctl */ | ||
527 | |||
528 | static struct file_operations mtd_fops = { | ||
529 | .owner = THIS_MODULE, | ||
530 | .llseek = mtd_lseek, | ||
531 | .read = mtd_read, | ||
532 | .write = mtd_write, | ||
533 | .ioctl = mtd_ioctl, | ||
534 | .open = mtd_open, | ||
535 | .release = mtd_close, | ||
536 | }; | ||
537 | |||
538 | static int __init init_mtdchar(void) | ||
539 | { | ||
540 | if (register_chrdev(MTD_CHAR_MAJOR, "mtd", &mtd_fops)) { | ||
541 | printk(KERN_NOTICE "Can't allocate major number %d for Memory Technology Devices.\n", | ||
542 | MTD_CHAR_MAJOR); | ||
543 | return -EAGAIN; | ||
544 | } | ||
545 | |||
546 | mtdchar_devfs_init(); | ||
547 | return 0; | ||
548 | } | ||
549 | |||
550 | static void __exit cleanup_mtdchar(void) | ||
551 | { | ||
552 | mtdchar_devfs_exit(); | ||
553 | unregister_chrdev(MTD_CHAR_MAJOR, "mtd"); | ||
554 | } | ||
555 | |||
556 | module_init(init_mtdchar); | ||
557 | module_exit(cleanup_mtdchar); | ||
558 | |||
559 | |||
560 | MODULE_LICENSE("GPL"); | ||
561 | MODULE_AUTHOR("David Woodhouse <dwmw2@infradead.org>"); | ||
562 | MODULE_DESCRIPTION("Direct character-device access to MTD devices"); | ||