1
2
3
4
5
6
7
8
9#include <linux/config.h>
10#include <linux/types.h>
11#include <linux/module.h>
12#include <linux/kernel.h>
13#include <linux/slab.h>
14#include <linux/mtd/mtd.h>
15#include <linux/mtd/compatmac.h>
16
17#define MAJOR_NR MTD_BLOCK_MAJOR
18#define DEVICE_NAME "mtdblock"
19#define DEVICE_REQUEST mtdblock_request
20#define DEVICE_NR(device) (device)
21#define DEVICE_ON(device)
22#define DEVICE_OFF(device)
23#define DEVICE_NO_RANDOM
24#include <linux/blk.h>
25
26#ifndef QUEUE_EMPTY
27#define QUEUE_EMPTY (!CURRENT)
28#endif
29#if LINUX_VERSION_CODE < 0x20300
30#define QUEUE_PLUGGED (blk_dev[MAJOR_NR].plug_tq.sync)
31#else
32#define QUEUE_PLUGGED (blk_dev[MAJOR_NR].request_queue.plugged)
33#endif
34
35#ifdef CONFIG_DEVFS_FS
36#include <linux/devfs_fs_kernel.h>
37static void mtd_notify_add(struct mtd_info* mtd);
38static void mtd_notify_remove(struct mtd_info* mtd);
39static struct mtd_notifier notifier = {
40 mtd_notify_add,
41 mtd_notify_remove,
42 NULL
43};
44static devfs_handle_t devfs_dir_handle = NULL;
45static devfs_handle_t devfs_rw_handle[MAX_MTD_DEVICES];
46#endif
47
48static struct mtdblk_dev {
49 struct mtd_info *mtd;
50 int count;
51 struct semaphore cache_sem;
52 unsigned char *cache_data;
53 unsigned long cache_offset;
54 unsigned int cache_size;
55 enum { STATE_EMPTY, STATE_CLEAN, STATE_DIRTY } cache_state;
56} *mtdblks[MAX_MTD_DEVICES];
57
58static spinlock_t mtdblks_lock;
59
60static int mtd_sizes[MAX_MTD_DEVICES];
61static int mtd_blksizes[MAX_MTD_DEVICES];
62
63
64
65
66
67
68
69
70
71
72
73
74static void erase_callback(struct erase_info *done)
75{
76 wait_queue_head_t *wait_q = (wait_queue_head_t *)done->priv;
77 wake_up(wait_q);
78}
79
80static int erase_write (struct mtd_info *mtd, unsigned long pos,
81 int len, const char *buf)
82{
83 struct erase_info erase;
84 DECLARE_WAITQUEUE(wait, current);
85 wait_queue_head_t wait_q;
86 size_t retlen;
87 int ret;
88
89
90
91
92
93 init_waitqueue_head(&wait_q);
94 erase.mtd = mtd;
95 erase.callback = erase_callback;
96 erase.addr = pos;
97 erase.len = len;
98 erase.priv = (u_long)&wait_q;
99
100 set_current_state(TASK_INTERRUPTIBLE);
101 add_wait_queue(&wait_q, &wait);
102
103 ret = MTD_ERASE(mtd, &erase);
104 if (ret) {
105 set_current_state(TASK_RUNNING);
106 remove_wait_queue(&wait_q, &wait);
107 printk (KERN_WARNING "mtdblock: erase of region [0x%lx, 0x%x] "
108 "on \"%s\" failed\n",
109 pos, len, mtd->name);
110 return ret;
111 }
112
113 schedule();
114 remove_wait_queue(&wait_q, &wait);
115
116
117
118
119
120 ret = MTD_WRITE (mtd, pos, len, &retlen, buf);
121 if (ret)
122 return ret;
123 if (retlen != len)
124 return -EIO;
125 return 0;
126}
127
128
129static int write_cached_data (struct mtdblk_dev *mtdblk)
130{
131 struct mtd_info *mtd = mtdblk->mtd;
132 int ret;
133
134 if (mtdblk->cache_state != STATE_DIRTY)
135 return 0;
136
137 DEBUG(MTD_DEBUG_LEVEL2, "mtdblock: writing cached data for \"%s\" "
138 "at 0x%lx, size 0x%x\n", mtd->name,
139 mtdblk->cache_offset, mtdblk->cache_size);
140
141 ret = erase_write (mtd, mtdblk->cache_offset,
142 mtdblk->cache_size, mtdblk->cache_data);
143 if (ret)
144 return ret;
145
146
147
148
149
150
151
152
153 mtdblk->cache_state = STATE_EMPTY;
154 return 0;
155}
156
157
158static int do_cached_write (struct mtdblk_dev *mtdblk, unsigned long pos,
159 int len, const char *buf)
160{
161 struct mtd_info *mtd = mtdblk->mtd;
162 unsigned int sect_size = mtdblk->cache_size;
163 size_t retlen;
164 int ret;
165
166 DEBUG(MTD_DEBUG_LEVEL2, "mtdblock: write on \"%s\" at 0x%lx, size 0x%x\n",
167 mtd->name, pos, len);
168
169 if (!sect_size)
170 return MTD_WRITE (mtd, pos, len, &retlen, buf);
171
172 while (len > 0) {
173 unsigned long sect_start = (pos/sect_size)*sect_size;
174 unsigned int offset = pos - sect_start;
175 unsigned int size = sect_size - offset;
176 if( size > len )
177 size = len;
178
179 if (size == sect_size) {
180
181
182
183
184
185 ret = erase_write (mtd, pos, size, buf);
186 if (ret)
187 return ret;
188 } else {
189
190
191 if (mtdblk->cache_state == STATE_DIRTY &&
192 mtdblk->cache_offset != sect_start) {
193 ret = write_cached_data(mtdblk);
194 if (ret)
195 return ret;
196 }
197
198 if (mtdblk->cache_state == STATE_EMPTY ||
199 mtdblk->cache_offset != sect_start) {
200
201 mtdblk->cache_state = STATE_EMPTY;
202 ret = MTD_READ(mtd, sect_start, sect_size, &retlen, mtdblk->cache_data);
203 if (ret)
204 return ret;
205 if (retlen != sect_size)
206 return -EIO;
207
208 mtdblk->cache_offset = sect_start;
209 mtdblk->cache_size = sect_size;
210 mtdblk->cache_state = STATE_CLEAN;
211 }
212
213
214 memcpy (mtdblk->cache_data + offset, buf, size);
215 mtdblk->cache_state = STATE_DIRTY;
216 }
217
218 buf += size;
219 pos += size;
220 len -= size;
221 }
222
223 return 0;
224}
225
226
227static int do_cached_read (struct mtdblk_dev *mtdblk, unsigned long pos,
228 int len, char *buf)
229{
230 struct mtd_info *mtd = mtdblk->mtd;
231 unsigned int sect_size = mtdblk->cache_size;
232 size_t retlen;
233 int ret;
234
235 DEBUG(MTD_DEBUG_LEVEL2, "mtdblock: read on \"%s\" at 0x%lx, size 0x%x\n",
236 mtd->name, pos, len);
237
238 if (!sect_size)
239 return MTD_READ (mtd, pos, len, &retlen, buf);
240
241 while (len > 0) {
242 unsigned long sect_start = (pos/sect_size)*sect_size;
243 unsigned int offset = pos - sect_start;
244 unsigned int size = sect_size - offset;
245 if (size > len)
246 size = len;
247
248
249
250
251
252
253
254 if (mtdblk->cache_state != STATE_EMPTY &&
255 mtdblk->cache_offset == sect_start) {
256 memcpy (buf, mtdblk->cache_data + offset, size);
257 } else {
258 ret = MTD_READ (mtd, pos, size, &retlen, buf);
259 if (ret)
260 return ret;
261 if (retlen != size)
262 return -EIO;
263 }
264
265 buf += size;
266 pos += size;
267 len -= size;
268 }
269
270 return 0;
271}
272
273
274
275static int mtdblock_open(struct inode *inode, struct file *file)
276{
277 struct mtdblk_dev *mtdblk;
278 struct mtd_info *mtd;
279 int dev;
280
281 DEBUG(MTD_DEBUG_LEVEL1,"mtdblock_open\n");
282
283 if (!inode)
284 return -EINVAL;
285
286 dev = MINOR(inode->i_rdev);
287 if (dev >= MAX_MTD_DEVICES)
288 return -EINVAL;
289
290 mtd = get_mtd_device(NULL, dev);
291 if (!mtd)
292 return -ENODEV;
293 if (MTD_ABSENT == mtd->type) {
294 put_mtd_device(mtd);
295 return -ENODEV;
296 }
297
298 spin_lock(&mtdblks_lock);
299
300
301 if (mtdblks[dev]) {
302 mtdblks[dev]->count++;
303 spin_unlock(&mtdblks_lock);
304 return 0;
305 }
306
307
308
309
310
311
312 spin_unlock(&mtdblks_lock);
313
314 mtdblk = kmalloc(sizeof(struct mtdblk_dev), GFP_KERNEL);
315 if (!mtdblk) {
316 put_mtd_device(mtd);
317 return -ENOMEM;
318 }
319 memset(mtdblk, 0, sizeof(*mtdblk));
320 mtdblk->count = 1;
321 mtdblk->mtd = mtd;
322
323 init_MUTEX (&mtdblk->cache_sem);
324 mtdblk->cache_state = STATE_EMPTY;
325 if ((mtdblk->mtd->flags & MTD_CAP_RAM) != MTD_CAP_RAM &&
326 mtdblk->mtd->erasesize) {
327 mtdblk->cache_size = mtdblk->mtd->erasesize;
328 mtdblk->cache_data = vmalloc(mtdblk->mtd->erasesize);
329 if (!mtdblk->cache_data) {
330 put_mtd_device(mtdblk->mtd);
331 kfree(mtdblk);
332 return -ENOMEM;
333 }
334 }
335
336
337
338 spin_lock(&mtdblks_lock);
339
340 if (mtdblks[dev]) {
341
342 mtdblks[dev]->count++;
343 spin_unlock(&mtdblks_lock);
344 put_mtd_device(mtdblk->mtd);
345 vfree(mtdblk->cache_data);
346 kfree(mtdblk);
347 return 0;
348 }
349
350 mtdblks[dev] = mtdblk;
351 mtd_sizes[dev] = mtdblk->mtd->size/1024;
352 if (mtdblk->mtd->erasesize)
353 mtd_blksizes[dev] = mtdblk->mtd->erasesize;
354 if (mtd_blksizes[dev] > PAGE_SIZE)
355 mtd_blksizes[dev] = PAGE_SIZE;
356 set_device_ro (inode->i_rdev, !(mtdblk->mtd->flags & MTD_WRITEABLE));
357
358 spin_unlock(&mtdblks_lock);
359
360 DEBUG(MTD_DEBUG_LEVEL1, "ok\n");
361
362 return 0;
363}
364
365static release_t mtdblock_release(struct inode *inode, struct file *file)
366{
367 int dev;
368 struct mtdblk_dev *mtdblk;
369 DEBUG(MTD_DEBUG_LEVEL1, "mtdblock_release\n");
370
371 if (inode == NULL)
372 release_return(-ENODEV);
373
374 invalidate_device(inode->i_rdev, 1);
375
376 dev = MINOR(inode->i_rdev);
377 mtdblk = mtdblks[dev];
378
379 down(&mtdblk->cache_sem);
380 write_cached_data(mtdblk);
381 up(&mtdblk->cache_sem);
382
383 spin_lock(&mtdblks_lock);
384 if (!--mtdblk->count) {
385
386 mtdblks[dev] = NULL;
387 spin_unlock(&mtdblks_lock);
388 if (mtdblk->mtd->sync)
389 mtdblk->mtd->sync(mtdblk->mtd);
390 put_mtd_device(mtdblk->mtd);
391 vfree(mtdblk->cache_data);
392 kfree(mtdblk);
393 } else {
394 spin_unlock(&mtdblks_lock);
395 }
396
397 DEBUG(MTD_DEBUG_LEVEL1, "ok\n");
398
399 release_return(0);
400}
401
402
403
404
405
406
407
408
409
410static void handle_mtdblock_request(void)
411{
412 struct request *req;
413 struct mtdblk_dev *mtdblk;
414 unsigned int res;
415
416 for (;;) {
417 INIT_REQUEST;
418 req = CURRENT;
419 spin_unlock_irq(&io_request_lock);
420 mtdblk = mtdblks[MINOR(req->rq_dev)];
421 res = 0;
422
423 if (MINOR(req->rq_dev) >= MAX_MTD_DEVICES)
424 panic(__FUNCTION__": minor out of bound");
425
426 if ((req->sector + req->current_nr_sectors) > (mtdblk->mtd->size >> 9))
427 goto end_req;
428
429
430 switch (req->cmd)
431 {
432 int err;
433
434 case READ:
435 down(&mtdblk->cache_sem);
436 err = do_cached_read (mtdblk, req->sector << 9,
437 req->current_nr_sectors << 9,
438 req->buffer);
439 up(&mtdblk->cache_sem);
440 if (!err)
441 res = 1;
442 break;
443
444 case WRITE:
445
446 if ( !(mtdblk->mtd->flags & MTD_WRITEABLE) )
447 break;
448
449
450 down(&mtdblk->cache_sem);
451 err = do_cached_write (mtdblk, req->sector << 9,
452 req->current_nr_sectors << 9,
453 req->buffer);
454 up(&mtdblk->cache_sem);
455 if (!err)
456 res = 1;
457 break;
458 }
459
460end_req:
461 spin_lock_irq(&io_request_lock);
462 end_request(res);
463 }
464}
465
466static volatile int leaving = 0;
467static DECLARE_MUTEX_LOCKED(thread_sem);
468static DECLARE_WAIT_QUEUE_HEAD(thr_wq);
469
470int mtdblock_thread(void *dummy)
471{
472 struct task_struct *tsk = current;
473 DECLARE_WAITQUEUE(wait, tsk);
474
475 tsk->session = 1;
476 tsk->pgrp = 1;
477
478 tsk->flags |= PF_MEMALLOC;
479 strcpy(tsk->comm, "mtdblockd");
480 tsk->tty = NULL;
481 spin_lock_irq(&tsk->sigmask_lock);
482 sigfillset(&tsk->blocked);
483 recalc_sigpending(tsk);
484 spin_unlock_irq(&tsk->sigmask_lock);
485 exit_mm(tsk);
486 exit_files(tsk);
487 exit_sighand(tsk);
488 exit_fs(tsk);
489
490 while (!leaving) {
491 add_wait_queue(&thr_wq, &wait);
492 set_current_state(TASK_INTERRUPTIBLE);
493 spin_lock_irq(&io_request_lock);
494 if (QUEUE_EMPTY || QUEUE_PLUGGED) {
495 spin_unlock_irq(&io_request_lock);
496 schedule();
497 remove_wait_queue(&thr_wq, &wait);
498 } else {
499 remove_wait_queue(&thr_wq, &wait);
500 set_current_state(TASK_RUNNING);
501 handle_mtdblock_request();
502 spin_unlock_irq(&io_request_lock);
503 }
504 }
505
506 up(&thread_sem);
507 return 0;
508}
509
510#if LINUX_VERSION_CODE < 0x20300
511#define RQFUNC_ARG void
512#else
513#define RQFUNC_ARG request_queue_t *q
514#endif
515
516static void mtdblock_request(RQFUNC_ARG)
517{
518
519 wake_up(&thr_wq);
520}
521
522
523static int mtdblock_ioctl(struct inode * inode, struct file * file,
524 unsigned int cmd, unsigned long arg)
525{
526 struct mtdblk_dev *mtdblk;
527
528 mtdblk = mtdblks[MINOR(inode->i_rdev)];
529
530#ifdef PARANOIA
531 if (!mtdblk)
532 BUG();
533#endif
534
535 switch (cmd) {
536 case BLKGETSIZE:
537 return put_user((mtdblk->mtd->size >> 9), (unsigned long *) arg);
538 case BLKGETSIZE64:
539 return put_user((u64)mtdblk->mtd->size, (u64 *)arg);
540
541 case BLKFLSBUF:
542#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,2,0)
543 if(!capable(CAP_SYS_ADMIN))
544 return -EACCES;
545#endif
546 fsync_dev(inode->i_rdev);
547 invalidate_buffers(inode->i_rdev);
548 down(&mtdblk->cache_sem);
549 write_cached_data(mtdblk);
550 up(&mtdblk->cache_sem);
551 if (mtdblk->mtd->sync)
552 mtdblk->mtd->sync(mtdblk->mtd);
553 return 0;
554
555 default:
556 return -EINVAL;
557 }
558}
559
560#if LINUX_VERSION_CODE < 0x20326
561static struct file_operations mtd_fops =
562{
563 open: mtdblock_open,
564 ioctl: mtdblock_ioctl,
565 release: mtdblock_release,
566 read: block_read,
567 write: block_write
568};
569#else
570static struct block_device_operations mtd_fops =
571{
572 owner: THIS_MODULE,
573 open: mtdblock_open,
574 release: mtdblock_release,
575 ioctl: mtdblock_ioctl
576};
577#endif
578
579#ifdef CONFIG_DEVFS_FS
580
581
582
583static void mtd_notify_add(struct mtd_info* mtd)
584{
585 char name[8];
586
587 if (!mtd || mtd->type == MTD_ABSENT)
588 return;
589
590 sprintf(name, "%d", mtd->index);
591 devfs_rw_handle[mtd->index] = devfs_register(devfs_dir_handle, name,
592 DEVFS_FL_DEFAULT, MTD_BLOCK_MAJOR, mtd->index,
593 S_IFBLK | S_IRUGO | S_IWUGO,
594 &mtd_fops, NULL);
595}
596
597static void mtd_notify_remove(struct mtd_info* mtd)
598{
599 if (!mtd || mtd->type == MTD_ABSENT)
600 return;
601
602 devfs_unregister(devfs_rw_handle[mtd->index]);
603}
604#endif
605
606int __init init_mtdblock(void)
607{
608 int i;
609
610 spin_lock_init(&mtdblks_lock);
611#ifdef CONFIG_DEVFS_FS
612 if (devfs_register_blkdev(MTD_BLOCK_MAJOR, DEVICE_NAME, &mtd_fops))
613 {
614 printk(KERN_NOTICE "Can't allocate major number %d for Memory Technology Devices.\n",
615 MTD_BLOCK_MAJOR);
616 return -EAGAIN;
617 }
618
619 devfs_dir_handle = devfs_mk_dir(NULL, DEVICE_NAME, NULL);
620 register_mtd_user(¬ifier);
621#else
622 if (register_blkdev(MAJOR_NR,DEVICE_NAME,&mtd_fops)) {
623 printk(KERN_NOTICE "Can't allocate major number %d for Memory Technology Devices.\n",
624 MTD_BLOCK_MAJOR);
625 return -EAGAIN;
626 }
627#endif
628
629
630 for (i=0; i< MAX_MTD_DEVICES; i++) {
631 mtd_sizes[i] = 0;
632 mtd_blksizes[i] = BLOCK_SIZE;
633 }
634 init_waitqueue_head(&thr_wq);
635
636 blksize_size[MAJOR_NR] = mtd_blksizes;
637 blk_size[MAJOR_NR] = mtd_sizes;
638
639 blk_init_queue(BLK_DEFAULT_QUEUE(MAJOR_NR), &mtdblock_request);
640 kernel_thread (mtdblock_thread, NULL, CLONE_FS|CLONE_FILES|CLONE_SIGHAND);
641 return 0;
642}
643
644static void __exit cleanup_mtdblock(void)
645{
646 leaving = 1;
647 wake_up(&thr_wq);
648 down(&thread_sem);
649#ifdef CONFIG_DEVFS_FS
650 unregister_mtd_user(¬ifier);
651 devfs_unregister(devfs_dir_handle);
652 devfs_unregister_blkdev(MTD_BLOCK_MAJOR, DEVICE_NAME);
653#else
654 unregister_blkdev(MAJOR_NR,DEVICE_NAME);
655#endif
656 blk_cleanup_queue(BLK_DEFAULT_QUEUE(MAJOR_NR));
657 blksize_size[MAJOR_NR] = NULL;
658 blk_size[MAJOR_NR] = NULL;
659}
660
661module_init(init_mtdblock);
662module_exit(cleanup_mtdblock);
663
664
665MODULE_LICENSE("GPL");
666MODULE_AUTHOR("Nicolas Pitre <nico@cam.org> et al.");
667MODULE_DESCRIPTION("Caching read/erase/writeback block device emulation access to MTD devices");
668