File: /usr/src/linux/drivers/mtd/mtdchar.c
1 /*
2 * $Id: mtdchar.c,v 1.38.2.1 2001/06/09 17:31:16 dwmw2 Exp $
3 *
4 * Character-device access to raw MTD devices.
5 *
6 */
7
8
9 #include <linux/mtd/compatmac.h>
10 #include <linux/config.h>
11 #include <linux/kernel.h>
12 #include <linux/module.h>
13 #include <linux/mtd/mtd.h>
14 #include <linux/slab.h>
15
16 #ifdef CONFIG_DEVFS_FS
17 #include <linux/devfs_fs_kernel.h>
18 static void mtd_notify_add(struct mtd_info* mtd);
19 static void mtd_notify_remove(struct mtd_info* mtd);
20
21 static struct mtd_notifier notifier = {
22 add: mtd_notify_add,
23 remove: mtd_notify_remove,
24 };
25
26 static devfs_handle_t devfs_dir_handle;
27 static devfs_handle_t devfs_rw_handle[MAX_MTD_DEVICES];
28 static devfs_handle_t devfs_ro_handle[MAX_MTD_DEVICES];
29 #endif
30
31 static loff_t mtd_lseek (struct file *file, loff_t offset, int orig)
32 {
33 struct mtd_info *mtd=(struct mtd_info *)file->private_data;
34
35 switch (orig) {
36 case 0:
37 /* SEEK_SET */
38 file->f_pos = offset;
39 break;
40 case 1:
41 /* SEEK_CUR */
42 file->f_pos += offset;
43 break;
44 case 2:
45 /* SEEK_END */
46 file->f_pos =mtd->size + offset;
47 break;
48 default:
49 return -EINVAL;
50 }
51
52 if (file->f_pos < 0)
53 file->f_pos = 0;
54 else if (file->f_pos >= mtd->size)
55 file->f_pos = mtd->size - 1;
56
57 return file->f_pos;
58 }
59
60
61
62 static int mtd_open(struct inode *inode, struct file *file)
63 {
64 int minor = MINOR(inode->i_rdev);
65 int devnum = minor >> 1;
66 struct mtd_info *mtd;
67
68 DEBUG(MTD_DEBUG_LEVEL0, "MTD_open\n");
69
70 if (devnum >= MAX_MTD_DEVICES)
71 return -ENODEV;
72
73 /* You can't open the RO devices RW */
74 if ((file->f_mode & 2) && (minor & 1))
75 return -EACCES;
76
77 mtd = get_mtd_device(NULL, devnum);
78
79 if (!mtd)
80 return -ENODEV;
81
82 file->private_data = mtd;
83
84 /* You can't open it RW if it's not a writeable device */
85 if ((file->f_mode & 2) && !(mtd->flags & MTD_WRITEABLE)) {
86 put_mtd_device(mtd);
87 return -EACCES;
88 }
89
90 return 0;
91 } /* mtd_open */
92
93 /*====================================================================*/
94
95 static release_t mtd_close(struct inode *inode,
96 struct file *file)
97 {
98 struct mtd_info *mtd;
99
100 DEBUG(MTD_DEBUG_LEVEL0, "MTD_close\n");
101
102 mtd = (struct mtd_info *)file->private_data;
103
104 if (mtd->sync)
105 mtd->sync(mtd);
106
107 put_mtd_device(mtd);
108
109 release_return(0);
110 } /* mtd_close */
111
112 #if LINUX_VERSION_CODE >= KERNEL_VERSION(2,2,0)
113 #define FILE_POS *ppos
114 #else
115 #define FILE_POS file->f_pos
116 #endif
117
118 /* FIXME: This _really_ needs to die. In 2.5, we should lock the
119 userspace buffer down and use it directly with readv/writev.
120 */
121 #define MAX_KMALLOC_SIZE 0x20000
122
123 static ssize_t mtd_read(struct file *file, char *buf, size_t count,loff_t *ppos)
124 {
125 struct mtd_info *mtd = (struct mtd_info *)file->private_data;
126 size_t retlen=0;
127 size_t total_retlen=0;
128 int ret=0;
129 int len;
130 char *kbuf;
131
132 DEBUG(MTD_DEBUG_LEVEL0,"MTD_read\n");
133
134 if (FILE_POS + count > mtd->size)
135 count = mtd->size - FILE_POS;
136
137 if (!count)
138 return 0;
139
140 /* FIXME: Use kiovec in 2.5 to lock down the user's buffers
141 and pass them directly to the MTD functions */
142 while (count) {
143 if (count > MAX_KMALLOC_SIZE)
144 len = MAX_KMALLOC_SIZE;
145 else
146 len = count;
147
148 kbuf=kmalloc(len,GFP_KERNEL);
149 if (!kbuf)
150 return -ENOMEM;
151
152 ret = MTD_READ(mtd, FILE_POS, len, &retlen, kbuf);
153 if (!ret) {
154 FILE_POS += retlen;
155 if (copy_to_user(buf, kbuf, retlen)) {
156 kfree(kbuf);
157 return -EFAULT;
158 }
159 else
160 total_retlen += retlen;
161
162 count -= retlen;
163 buf += retlen;
164 }
165 else {
166 kfree(kbuf);
167 return ret;
168 }
169
170 kfree(kbuf);
171 }
172
173 return total_retlen;
174 } /* mtd_read */
175
176 static ssize_t mtd_write(struct file *file, const char *buf, size_t count,loff_t *ppos)
177 {
178 struct mtd_info *mtd = (struct mtd_info *)file->private_data;
179 char *kbuf;
180 size_t retlen;
181 size_t total_retlen=0;
182 int ret=0;
183 int len;
184
185 DEBUG(MTD_DEBUG_LEVEL0,"MTD_write\n");
186
187 if (FILE_POS == mtd->size)
188 return -ENOSPC;
189
190 if (FILE_POS + count > mtd->size)
191 count = mtd->size - FILE_POS;
192
193 if (!count)
194 return 0;
195
196 while (count) {
197 if (count > MAX_KMALLOC_SIZE)
198 len = MAX_KMALLOC_SIZE;
199 else
200 len = count;
201
202 kbuf=kmalloc(len,GFP_KERNEL);
203 if (!kbuf) {
204 printk("kmalloc is null\n");
205 return -ENOMEM;
206 }
207
208 if (copy_from_user(kbuf, buf, len)) {
209 kfree(kbuf);
210 return -EFAULT;
211 }
212
213 ret = (*(mtd->write))(mtd, FILE_POS, len, &retlen, kbuf);
214 if (!ret) {
215 FILE_POS += retlen;
216 total_retlen += retlen;
217 count -= retlen;
218 buf += retlen;
219 }
220 else {
221 kfree(kbuf);
222 return ret;
223 }
224
225 kfree(kbuf);
226 }
227
228 return total_retlen;
229 } /* mtd_write */
230
231 /*======================================================================
232
233 IOCTL calls for getting device parameters.
234
235 ======================================================================*/
236 static void mtd_erase_callback (struct erase_info *instr)
237 {
238 wake_up((wait_queue_head_t *)instr->priv);
239 }
240
241 static int mtd_ioctl(struct inode *inode, struct file *file,
242 u_int cmd, u_long arg)
243 {
244 struct mtd_info *mtd = (struct mtd_info *)file->private_data;
245 int ret = 0;
246 u_long size;
247
248 DEBUG(MTD_DEBUG_LEVEL0, "MTD_ioctl\n");
249
250 size = (cmd & IOCSIZE_MASK) >> IOCSIZE_SHIFT;
251 if (cmd & IOC_IN) {
252 ret = verify_area(VERIFY_READ, (char *)arg, size);
253 if (ret) return ret;
254 }
255 if (cmd & IOC_OUT) {
256 ret = verify_area(VERIFY_WRITE, (char *)arg, size);
257 if (ret) return ret;
258 }
259
260 switch (cmd) {
261 case MEMGETREGIONCOUNT:
262 if (copy_to_user((int *) arg, &(mtd->numeraseregions), sizeof(int)))
263 return -EFAULT;
264 break;
265
266 case MEMGETREGIONINFO:
267 {
268 struct region_info_user ur;
269
270 if (copy_from_user( &ur,
271 (struct region_info_user *)arg,
272 sizeof(struct region_info_user))) {
273 return -EFAULT;
274 }
275
276 if (ur.regionindex >= mtd->numeraseregions)
277 return -EINVAL;
278 if (copy_to_user((struct mtd_erase_region_info *) arg,
279 &(mtd->eraseregions[ur.regionindex]),
280 sizeof(struct mtd_erase_region_info)))
281 return -EFAULT;
282 break;
283 }
284
285 case MEMGETINFO:
286 if (copy_to_user((struct mtd_info *)arg, mtd,
287 sizeof(struct mtd_info_user)))
288 return -EFAULT;
289 break;
290
291 case MEMERASE:
292 {
293 struct erase_info *erase=kmalloc(sizeof(struct erase_info),GFP_KERNEL);
294 if (!erase)
295 ret = -ENOMEM;
296 else {
297 wait_queue_head_t waitq;
298 DECLARE_WAITQUEUE(wait, current);
299
300 init_waitqueue_head(&waitq);
301
302 memset (erase,0,sizeof(struct erase_info));
303 if (copy_from_user(&erase->addr, (u_long *)arg,
304 2 * sizeof(u_long))) {
305 kfree(erase);
306 return -EFAULT;
307 }
308 erase->mtd = mtd;
309 erase->callback = mtd_erase_callback;
310 erase->priv = (unsigned long)&waitq;
311
312 /*
313 FIXME: Allow INTERRUPTIBLE. Which means
314 not having the wait_queue head on the stack.
315
316 If the wq_head is on the stack, and we
317 leave because we got interrupted, then the
318 wq_head is no longer there when the
319 callback routine tries to wake us up.
320 */
321 current->state = TASK_UNINTERRUPTIBLE;
322 add_wait_queue(&waitq, &wait);
323 ret = mtd->erase(mtd, erase);
324 if (!ret)
325 schedule();
326 remove_wait_queue(&waitq, &wait);
327 current->state = TASK_RUNNING;
328 if (!ret)
329 ret = (erase->state == MTD_ERASE_FAILED);
330 kfree(erase);
331 }
332 break;
333 }
334
335 case MEMWRITEOOB:
336 {
337 struct mtd_oob_buf buf;
338 void *databuf;
339 ssize_t retlen;
340
341 if (copy_from_user(&buf, (struct mtd_oob_buf *)arg, sizeof(struct mtd_oob_buf)))
342 return -EFAULT;
343
344 if (buf.length > 0x4096)
345 return -EINVAL;
346
347 if (!mtd->write_oob)
348 ret = -EOPNOTSUPP;
349 else
350 ret = verify_area(VERIFY_READ, (char *)buf.ptr, buf.length);
351
352 if (ret)
353 return ret;
354
355 databuf = kmalloc(buf.length, GFP_KERNEL);
356 if (!databuf)
357 return -ENOMEM;
358
359 if (copy_from_user(databuf, buf.ptr, buf.length)) {
360 kfree(databuf);
361 return -EFAULT;
362 }
363
364 ret = (mtd->write_oob)(mtd, buf.start, buf.length, &retlen, databuf);
365
366 if (copy_to_user((void *)arg + sizeof(u_int32_t), &retlen, sizeof(u_int32_t)))
367 ret = -EFAULT;
368
369 kfree(databuf);
370 break;
371
372 }
373
374 case MEMREADOOB:
375 {
376 struct mtd_oob_buf buf;
377 void *databuf;
378 ssize_t retlen;
379
380 if (copy_from_user(&buf, (struct mtd_oob_buf *)arg, sizeof(struct mtd_oob_buf)))
381 return -EFAULT;
382
383 if (buf.length > 0x4096)
384 return -EINVAL;
385
386 if (!mtd->read_oob)
387 ret = -EOPNOTSUPP;
388 else
389 ret = verify_area(VERIFY_WRITE, (char *)buf.ptr, buf.length);
390
391 if (ret)
392 return ret;
393
394 databuf = kmalloc(buf.length, GFP_KERNEL);
395 if (!databuf)
396 return -ENOMEM;
397
398 ret = (mtd->read_oob)(mtd, buf.start, buf.length, &retlen, databuf);
399
400 if (copy_to_user((void *)arg + sizeof(u_int32_t), &retlen, sizeof(u_int32_t)))
401 ret = -EFAULT;
402 else if (retlen && copy_to_user(buf.ptr, databuf, retlen))
403 ret = -EFAULT;
404
405 kfree(databuf);
406 break;
407 }
408
409 case MEMLOCK:
410 {
411 unsigned long adrs[2];
412
413 if (copy_from_user(adrs ,(void *)arg, 2* sizeof(unsigned long)))
414 return -EFAULT;
415
416 if (!mtd->lock)
417 ret = -EOPNOTSUPP;
418 else
419 ret = mtd->lock(mtd, adrs[0], adrs[1]);
420 break;
421 }
422
423 case MEMUNLOCK:
424 {
425 unsigned long adrs[2];
426
427 if (copy_from_user(adrs, (void *)arg, 2* sizeof(unsigned long)))
428 return -EFAULT;
429
430 if (!mtd->unlock)
431 ret = -EOPNOTSUPP;
432 else
433 ret = mtd->unlock(mtd, adrs[0], adrs[1]);
434 break;
435 }
436
437
438 default:
439 DEBUG(MTD_DEBUG_LEVEL0, "Invalid ioctl %x (MEMGETINFO = %x)\n", cmd, MEMGETINFO);
440 ret = -ENOTTY;
441 }
442
443 return ret;
444 } /* memory_ioctl */
445
446 static struct file_operations mtd_fops = {
447 owner: THIS_MODULE,
448 llseek: mtd_lseek, /* lseek */
449 read: mtd_read, /* read */
450 write: mtd_write, /* write */
451 ioctl: mtd_ioctl, /* ioctl */
452 open: mtd_open, /* open */
453 release: mtd_close, /* release */
454 };
455
456
457 #ifdef CONFIG_DEVFS_FS
458 /* Notification that a new device has been added. Create the devfs entry for
459 * it. */
460
461 static void mtd_notify_add(struct mtd_info* mtd)
462 {
463 char name[8];
464
465 if (!mtd)
466 return;
467
468 sprintf(name, "%d", mtd->index);
469 devfs_rw_handle[mtd->index] = devfs_register(devfs_dir_handle, name,
470 DEVFS_FL_DEFAULT, MTD_CHAR_MAJOR, mtd->index*2,
471 S_IFCHR | S_IRUGO | S_IWUGO,
472 &mtd_fops, NULL);
473
474 sprintf(name, "%dro", mtd->index);
475 devfs_ro_handle[mtd->index] = devfs_register(devfs_dir_handle, name,
476 DEVFS_FL_DEFAULT, MTD_CHAR_MAJOR, mtd->index*2+1,
477 S_IFCHR | S_IRUGO | S_IWUGO,
478 &mtd_fops, NULL);
479 }
480
481 static void mtd_notify_remove(struct mtd_info* mtd)
482 {
483 if (!mtd)
484 return;
485
486 devfs_unregister(devfs_rw_handle[mtd->index]);
487 devfs_unregister(devfs_ro_handle[mtd->index]);
488 }
489 #endif
490
491 #if LINUX_VERSION_CODE < 0x20212 && defined(MODULE)
492 #define init_mtdchar init_module
493 #define cleanup_mtdchar cleanup_module
494 #endif
495
496 mod_init_t init_mtdchar(void)
497 {
498 #ifdef CONFIG_DEVFS_FS
499 if (devfs_register_chrdev(MTD_CHAR_MAJOR, "mtd", &mtd_fops))
500 {
501 printk(KERN_NOTICE "Can't allocate major number %d for Memory Technology Devices.\n",
502 MTD_CHAR_MAJOR);
503 return -EAGAIN;
504 }
505
506 devfs_dir_handle = devfs_mk_dir(NULL, "mtd", NULL);
507
508 register_mtd_user(¬ifier);
509 #else
510 if (register_chrdev(MTD_CHAR_MAJOR, "mtd", &mtd_fops))
511 {
512 printk(KERN_NOTICE "Can't allocate major number %d for Memory Technology Devices.\n",
513 MTD_CHAR_MAJOR);
514 return -EAGAIN;
515 }
516 #endif
517
518 return 0;
519 }
520
521 mod_exit_t cleanup_mtdchar(void)
522 {
523 #ifdef CONFIG_DEVFS_FS
524 unregister_mtd_user(¬ifier);
525 devfs_unregister(devfs_dir_handle);
526 devfs_unregister_chrdev(MTD_CHAR_MAJOR, "mtd");
527 #else
528 unregister_chrdev(MTD_CHAR_MAJOR, "mtd");
529 #endif
530 }
531
532 module_init(init_mtdchar);
533 module_exit(cleanup_mtdchar);
534