Branch data Line data Source code
1 : : /*
2 : : * Interface to Linux block layer for MTD 'translation layers'.
3 : : *
4 : : * Copyright © 2003-2010 David Woodhouse <dwmw2@infradead.org>
5 : : *
6 : : * This program is free software; you can redistribute it and/or modify
7 : : * it under the terms of the GNU General Public License as published by
8 : : * the Free Software Foundation; either version 2 of the License, or
9 : : * (at your option) any later version.
10 : : *
11 : : * This program is distributed in the hope that it will be useful,
12 : : * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 : : * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 : : * GNU General Public License for more details.
15 : : *
16 : : * You should have received a copy of the GNU General Public License
17 : : * along with this program; if not, write to the Free Software
18 : : * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
19 : : *
20 : : */
21 : :
22 : : #include <linux/kernel.h>
23 : : #include <linux/slab.h>
24 : : #include <linux/module.h>
25 : : #include <linux/list.h>
26 : : #include <linux/fs.h>
27 : : #include <linux/mtd/blktrans.h>
28 : : #include <linux/mtd/mtd.h>
29 : : #include <linux/blkdev.h>
30 : : #include <linux/blkpg.h>
31 : : #include <linux/spinlock.h>
32 : : #include <linux/hdreg.h>
33 : : #include <linux/init.h>
34 : : #include <linux/mutex.h>
35 : : #include <asm/uaccess.h>
36 : :
37 : : #include "mtdcore.h"
38 : :
39 : : static LIST_HEAD(blktrans_majors);
40 : : static DEFINE_MUTEX(blktrans_ref_mutex);
41 : :
42 : 0 : static void blktrans_dev_release(struct kref *kref)
43 : : {
44 : 0 : struct mtd_blktrans_dev *dev =
45 : : container_of(kref, struct mtd_blktrans_dev, ref);
46 : :
47 : 0 : dev->disk->private_data = NULL;
48 : 0 : blk_cleanup_queue(dev->rq);
49 : 0 : put_disk(dev->disk);
50 : : list_del(&dev->list);
51 : 0 : kfree(dev);
52 : 0 : }
53 : :
54 : 0 : static struct mtd_blktrans_dev *blktrans_dev_get(struct gendisk *disk)
55 : : {
56 : : struct mtd_blktrans_dev *dev;
57 : :
58 : 0 : mutex_lock(&blktrans_ref_mutex);
59 : 0 : dev = disk->private_data;
60 : :
61 [ # # ]: 0 : if (!dev)
62 : : goto unlock;
63 : : kref_get(&dev->ref);
64 : : unlock:
65 : 0 : mutex_unlock(&blktrans_ref_mutex);
66 : 0 : return dev;
67 : : }
68 : :
69 : 0 : static void blktrans_dev_put(struct mtd_blktrans_dev *dev)
70 : : {
71 : 0 : mutex_lock(&blktrans_ref_mutex);
72 : 0 : kref_put(&dev->ref, blktrans_dev_release);
73 : 0 : mutex_unlock(&blktrans_ref_mutex);
74 : 0 : }
75 : :
76 : :
77 : 0 : static int do_blktrans_request(struct mtd_blktrans_ops *tr,
78 : : struct mtd_blktrans_dev *dev,
79 : 0 : struct request *req)
80 : : {
81 : : unsigned long block, nsect;
82 : : char *buf;
83 : :
84 : 0 : block = blk_rq_pos(req) << 9 >> tr->blkshift;
85 : 0 : nsect = blk_rq_cur_bytes(req) >> tr->blkshift;
86 : :
87 : 0 : buf = req->buffer;
88 : :
89 [ # # ]: 0 : if (req->cmd_type != REQ_TYPE_FS)
90 : : return -EIO;
91 : :
92 [ # # ]: 0 : if (blk_rq_pos(req) + blk_rq_cur_sectors(req) >
93 : 0 : get_capacity(req->rq_disk))
94 : : return -EIO;
95 : :
96 [ # # ]: 0 : if (req->cmd_flags & REQ_DISCARD)
97 : 0 : return tr->discard(dev, block, nsect);
98 : :
99 [ # # # ]: 0 : switch(rq_data_dir(req)) {
100 : : case READ:
101 [ # # ]: 0 : for (; nsect > 0; nsect--, block++, buf += tr->blksize)
102 [ # # ]: 0 : if (tr->readsect(dev, block, buf))
103 : : return -EIO;
104 : 0 : rq_flush_dcache_pages(req);
105 : 0 : return 0;
106 : : case WRITE:
107 [ # # ]: 0 : if (!tr->writesect)
108 : : return -EIO;
109 : :
110 : 0 : rq_flush_dcache_pages(req);
111 [ # # ]: 0 : for (; nsect > 0; nsect--, block++, buf += tr->blksize)
112 [ # # ]: 0 : if (tr->writesect(dev, block, buf))
113 : : return -EIO;
114 : : return 0;
115 : : default:
116 : 0 : printk(KERN_NOTICE "Unknown request %u\n", rq_data_dir(req));
117 : 0 : return -EIO;
118 : : }
119 : : }
120 : :
121 : 0 : int mtd_blktrans_cease_background(struct mtd_blktrans_dev *dev)
122 : : {
123 : 0 : return dev->bg_stop;
124 : : }
125 : : EXPORT_SYMBOL_GPL(mtd_blktrans_cease_background);
126 : :
127 : 0 : static void mtd_blktrans_work(struct work_struct *work)
128 : : {
129 : 0 : struct mtd_blktrans_dev *dev =
130 : : container_of(work, struct mtd_blktrans_dev, work);
131 : 0 : struct mtd_blktrans_ops *tr = dev->tr;
132 : 0 : struct request_queue *rq = dev->rq;
133 : : struct request *req = NULL;
134 : : int background_done = 0;
135 : :
136 : 0 : spin_lock_irq(rq->queue_lock);
137 : :
138 : : while (1) {
139 : : int res;
140 : :
141 : 0 : dev->bg_stop = false;
142 [ # # ][ # # ]: 0 : if (!req && !(req = blk_fetch_request(rq))) {
143 [ # # ][ # # ]: 0 : if (tr->background && !background_done) {
144 : 0 : spin_unlock_irq(rq->queue_lock);
145 : 0 : mutex_lock(&dev->lock);
146 : 0 : tr->background(dev);
147 : 0 : mutex_unlock(&dev->lock);
148 : 0 : spin_lock_irq(rq->queue_lock);
149 : : /*
150 : : * Do background processing just once per idle
151 : : * period.
152 : : */
153 : 0 : background_done = !dev->bg_stop;
154 : 0 : continue;
155 : : }
156 : : break;
157 : : }
158 : :
159 : 0 : spin_unlock_irq(rq->queue_lock);
160 : :
161 : 0 : mutex_lock(&dev->lock);
162 : 0 : res = do_blktrans_request(dev->tr, dev, req);
163 : 0 : mutex_unlock(&dev->lock);
164 : :
165 : 0 : spin_lock_irq(rq->queue_lock);
166 : :
167 [ # # ]: 0 : if (!__blk_end_request_cur(req, res))
168 : : req = NULL;
169 : :
170 : : background_done = 0;
171 : : }
172 : :
173 [ # # ]: 0 : if (req)
174 : 0 : __blk_end_request_all(req, -EIO);
175 : :
176 : 0 : spin_unlock_irq(rq->queue_lock);
177 : 0 : }
178 : :
179 : 0 : static void mtd_blktrans_request(struct request_queue *rq)
180 : : {
181 : : struct mtd_blktrans_dev *dev;
182 : : struct request *req = NULL;
183 : :
184 : 0 : dev = rq->queuedata;
185 : :
186 [ # # ]: 0 : if (!dev)
187 [ # # ]: 0 : while ((req = blk_fetch_request(rq)) != NULL)
188 : 0 : __blk_end_request_all(req, -ENODEV);
189 : : else
190 : 0 : queue_work(dev->wq, &dev->work);
191 : 0 : }
192 : :
193 : 0 : static int blktrans_open(struct block_device *bdev, fmode_t mode)
194 : : {
195 : 0 : struct mtd_blktrans_dev *dev = blktrans_dev_get(bdev->bd_disk);
196 : : int ret = 0;
197 : :
198 [ # # ]: 0 : if (!dev)
199 : : return -ERESTARTSYS; /* FIXME: busy loop! -arnd*/
200 : :
201 : 0 : mutex_lock(&dev->lock);
202 : :
203 [ # # ]: 0 : if (dev->open)
204 : : goto unlock;
205 : :
206 : : kref_get(&dev->ref);
207 : 0 : __module_get(dev->tr->owner);
208 : :
209 [ # # ]: 0 : if (!dev->mtd)
210 : : goto unlock;
211 : :
212 [ # # ]: 0 : if (dev->tr->open) {
213 : 0 : ret = dev->tr->open(dev);
214 [ # # ]: 0 : if (ret)
215 : : goto error_put;
216 : : }
217 : :
218 : 0 : ret = __get_mtd_device(dev->mtd);
219 [ # # ]: 0 : if (ret)
220 : : goto error_release;
221 : 0 : dev->file_mode = mode;
222 : :
223 : : unlock:
224 : 0 : dev->open++;
225 : 0 : mutex_unlock(&dev->lock);
226 : 0 : blktrans_dev_put(dev);
227 : 0 : return ret;
228 : :
229 : : error_release:
230 [ # # ]: 0 : if (dev->tr->release)
231 : 0 : dev->tr->release(dev);
232 : : error_put:
233 : 0 : module_put(dev->tr->owner);
234 : : kref_put(&dev->ref, blktrans_dev_release);
235 : 0 : mutex_unlock(&dev->lock);
236 : 0 : blktrans_dev_put(dev);
237 : 0 : return ret;
238 : : }
239 : :
240 : 0 : static void blktrans_release(struct gendisk *disk, fmode_t mode)
241 : : {
242 : 0 : struct mtd_blktrans_dev *dev = blktrans_dev_get(disk);
243 : :
244 [ # # ]: 0 : if (!dev)
245 : 0 : return;
246 : :
247 : 0 : mutex_lock(&dev->lock);
248 : :
249 [ # # ]: 0 : if (--dev->open)
250 : : goto unlock;
251 : :
252 : 0 : kref_put(&dev->ref, blktrans_dev_release);
253 : 0 : module_put(dev->tr->owner);
254 : :
255 [ # # ]: 0 : if (dev->mtd) {
256 [ # # ]: 0 : if (dev->tr->release)
257 : 0 : dev->tr->release(dev);
258 : 0 : __put_mtd_device(dev->mtd);
259 : : }
260 : : unlock:
261 : 0 : mutex_unlock(&dev->lock);
262 : 0 : blktrans_dev_put(dev);
263 : : }
264 : :
265 : 0 : static int blktrans_getgeo(struct block_device *bdev, struct hd_geometry *geo)
266 : : {
267 : 0 : struct mtd_blktrans_dev *dev = blktrans_dev_get(bdev->bd_disk);
268 : : int ret = -ENXIO;
269 : :
270 [ # # ]: 0 : if (!dev)
271 : : return ret;
272 : :
273 : 0 : mutex_lock(&dev->lock);
274 : :
275 [ # # ]: 0 : if (!dev->mtd)
276 : : goto unlock;
277 : :
278 [ # # ]: 0 : ret = dev->tr->getgeo ? dev->tr->getgeo(dev, geo) : 0;
279 : : unlock:
280 : 0 : mutex_unlock(&dev->lock);
281 : 0 : blktrans_dev_put(dev);
282 : 0 : return ret;
283 : : }
284 : :
285 : 0 : static int blktrans_ioctl(struct block_device *bdev, fmode_t mode,
286 : : unsigned int cmd, unsigned long arg)
287 : : {
288 : 0 : struct mtd_blktrans_dev *dev = blktrans_dev_get(bdev->bd_disk);
289 : : int ret = -ENXIO;
290 : :
291 [ # # ]: 0 : if (!dev)
292 : : return ret;
293 : :
294 : 0 : mutex_lock(&dev->lock);
295 : :
296 [ # # ]: 0 : if (!dev->mtd)
297 : : goto unlock;
298 : :
299 [ # # ]: 0 : switch (cmd) {
300 : : case BLKFLSBUF:
301 [ # # ]: 0 : ret = dev->tr->flush ? dev->tr->flush(dev) : 0;
302 : 0 : break;
303 : : default:
304 : : ret = -ENOTTY;
305 : : }
306 : : unlock:
307 : 0 : mutex_unlock(&dev->lock);
308 : 0 : blktrans_dev_put(dev);
309 : 0 : return ret;
310 : : }
311 : :
312 : : static const struct block_device_operations mtd_block_ops = {
313 : : .owner = THIS_MODULE,
314 : : .open = blktrans_open,
315 : : .release = blktrans_release,
316 : : .ioctl = blktrans_ioctl,
317 : : .getgeo = blktrans_getgeo,
318 : : };
319 : :
320 : 0 : int add_mtd_blktrans_dev(struct mtd_blktrans_dev *new)
321 : : {
322 : 0 : struct mtd_blktrans_ops *tr = new->tr;
323 : : struct mtd_blktrans_dev *d;
324 : : int last_devnum = -1;
325 : : struct gendisk *gd;
326 : : int ret;
327 : :
328 [ # # ]: 0 : if (mutex_trylock(&mtd_table_mutex)) {
329 : 0 : mutex_unlock(&mtd_table_mutex);
330 : 0 : BUG();
331 : : }
332 : :
333 : 0 : mutex_lock(&blktrans_ref_mutex);
334 [ # # ]: 0 : list_for_each_entry(d, &tr->devs, list) {
335 [ # # ]: 0 : if (new->devnum == -1) {
336 : : /* Use first free number */
337 [ # # ]: 0 : if (d->devnum != last_devnum+1) {
338 : : /* Found a free devnum. Plug it in here */
339 : 0 : new->devnum = last_devnum+1;
340 : 0 : list_add_tail(&new->list, &d->list);
341 : : goto added;
342 : : }
343 [ # # ]: 0 : } else if (d->devnum == new->devnum) {
344 : : /* Required number taken */
345 : 0 : mutex_unlock(&blktrans_ref_mutex);
346 : 0 : return -EBUSY;
347 [ # # ]: 0 : } else if (d->devnum > new->devnum) {
348 : : /* Required number was free */
349 : 0 : list_add_tail(&new->list, &d->list);
350 : : goto added;
351 : : }
352 : 0 : last_devnum = d->devnum;
353 : : }
354 : :
355 : : ret = -EBUSY;
356 [ # # ]: 0 : if (new->devnum == -1)
357 : 0 : new->devnum = last_devnum+1;
358 : :
359 : : /* Check that the device and any partitions will get valid
360 : : * minor numbers and that the disk naming code below can cope
361 : : * with this number. */
362 [ # # ][ # # ]: 0 : if (new->devnum > (MINORMASK >> tr->part_bits) ||
363 [ # # ]: 0 : (tr->part_bits && new->devnum >= 27 * 26)) {
364 : 0 : mutex_unlock(&blktrans_ref_mutex);
365 : 0 : goto error1;
366 : : }
367 : :
368 : 0 : list_add_tail(&new->list, &tr->devs);
369 : : added:
370 : 0 : mutex_unlock(&blktrans_ref_mutex);
371 : :
372 : 0 : mutex_init(&new->lock);
373 : : kref_init(&new->ref);
374 [ # # ]: 0 : if (!tr->writesect)
375 : 0 : new->readonly = 1;
376 : :
377 : : /* Create gendisk */
378 : : ret = -ENOMEM;
379 : 0 : gd = alloc_disk(1 << tr->part_bits);
380 : :
381 [ # # ]: 0 : if (!gd)
382 : : goto error2;
383 : :
384 : 0 : new->disk = gd;
385 : 0 : gd->private_data = new;
386 : 0 : gd->major = tr->major;
387 : 0 : gd->first_minor = (new->devnum) << tr->part_bits;
388 : 0 : gd->fops = &mtd_block_ops;
389 : :
390 [ # # ]: 0 : if (tr->part_bits)
391 [ # # ]: 0 : if (new->devnum < 26)
392 : 0 : snprintf(gd->disk_name, sizeof(gd->disk_name),
393 : : "%s%c", tr->name, 'a' + new->devnum);
394 : : else
395 : 0 : snprintf(gd->disk_name, sizeof(gd->disk_name),
396 : : "%s%c%c", tr->name,
397 : 0 : 'a' - 1 + new->devnum / 26,
398 : 0 : 'a' + new->devnum % 26);
399 : : else
400 : 0 : snprintf(gd->disk_name, sizeof(gd->disk_name),
401 : : "%s%d", tr->name, new->devnum);
402 : :
403 : 0 : set_capacity(gd, (new->size * tr->blksize) >> 9);
404 : :
405 : : /* Create the request queue */
406 : 0 : spin_lock_init(&new->queue_lock);
407 : 0 : new->rq = blk_init_queue(mtd_blktrans_request, &new->queue_lock);
408 : :
409 [ # # ]: 0 : if (!new->rq)
410 : : goto error3;
411 : :
412 : 0 : new->rq->queuedata = new;
413 : 0 : blk_queue_logical_block_size(new->rq, tr->blksize);
414 : :
415 : 0 : queue_flag_set_unlocked(QUEUE_FLAG_NONROT, new->rq);
416 : :
417 [ # # ]: 0 : if (tr->discard) {
418 : 0 : queue_flag_set_unlocked(QUEUE_FLAG_DISCARD, new->rq);
419 : 0 : new->rq->limits.max_discard_sectors = UINT_MAX;
420 : : }
421 : :
422 : 0 : gd->queue = new->rq;
423 : :
424 : : /* Create processing workqueue */
425 : 0 : new->wq = alloc_workqueue("%s%d", 0, 0,
426 : : tr->name, new->mtd->index);
427 [ # # ]: 0 : if (!new->wq)
428 : : goto error4;
429 : 0 : INIT_WORK(&new->work, mtd_blktrans_work);
430 : :
431 : 0 : gd->driverfs_dev = &new->mtd->dev;
432 : :
433 [ # # ]: 0 : if (new->readonly)
434 : 0 : set_disk_ro(gd, 1);
435 : :
436 : 0 : add_disk(gd);
437 : :
438 [ # # ]: 0 : if (new->disk_attributes) {
439 : 0 : ret = sysfs_create_group(&disk_to_dev(gd)->kobj,
440 : : new->disk_attributes);
441 [ # # ]: 0 : WARN_ON(ret);
442 : : }
443 : : return 0;
444 : : error4:
445 : 0 : blk_cleanup_queue(new->rq);
446 : : error3:
447 : 0 : put_disk(new->disk);
448 : : error2:
449 : : list_del(&new->list);
450 : : error1:
451 : 0 : return ret;
452 : : }
453 : :
454 : 0 : int del_mtd_blktrans_dev(struct mtd_blktrans_dev *old)
455 : : {
456 : : unsigned long flags;
457 : :
458 [ # # ]: 0 : if (mutex_trylock(&mtd_table_mutex)) {
459 : 0 : mutex_unlock(&mtd_table_mutex);
460 : 0 : BUG();
461 : : }
462 : :
463 [ # # ]: 0 : if (old->disk_attributes)
464 : 0 : sysfs_remove_group(&disk_to_dev(old->disk)->kobj,
465 : : old->disk_attributes);
466 : :
467 : : /* Stop new requests to arrive */
468 : 0 : del_gendisk(old->disk);
469 : :
470 : : /* Stop workqueue. This will perform any pending request. */
471 : 0 : destroy_workqueue(old->wq);
472 : :
473 : : /* Kill current requests */
474 : 0 : spin_lock_irqsave(&old->queue_lock, flags);
475 : 0 : old->rq->queuedata = NULL;
476 : 0 : blk_start_queue(old->rq);
477 : : spin_unlock_irqrestore(&old->queue_lock, flags);
478 : :
479 : : /* If the device is currently open, tell trans driver to close it,
480 : : then put mtd device, and don't touch it again */
481 : 0 : mutex_lock(&old->lock);
482 [ # # ]: 0 : if (old->open) {
483 [ # # ]: 0 : if (old->tr->release)
484 : 0 : old->tr->release(old);
485 : 0 : __put_mtd_device(old->mtd);
486 : : }
487 : :
488 : 0 : old->mtd = NULL;
489 : :
490 : 0 : mutex_unlock(&old->lock);
491 : 0 : blktrans_dev_put(old);
492 : 0 : return 0;
493 : : }
494 : :
495 : 0 : static void blktrans_notify_remove(struct mtd_info *mtd)
496 : : {
497 : : struct mtd_blktrans_ops *tr;
498 : : struct mtd_blktrans_dev *dev, *next;
499 : :
500 [ # # ]: 0 : list_for_each_entry(tr, &blktrans_majors, list)
501 [ # # ]: 0 : list_for_each_entry_safe(dev, next, &tr->devs, list)
502 [ # # ]: 0 : if (dev->mtd == mtd)
503 : 0 : tr->remove_dev(dev);
504 : 0 : }
505 : :
506 : 0 : static void blktrans_notify_add(struct mtd_info *mtd)
507 : : {
508 : : struct mtd_blktrans_ops *tr;
509 : :
510 [ # # ]: 0 : if (mtd->type == MTD_ABSENT)
511 : 0 : return;
512 : :
513 [ # # ]: 0 : list_for_each_entry(tr, &blktrans_majors, list)
514 : 0 : tr->add_mtd(tr, mtd);
515 : : }
516 : :
517 : : static struct mtd_notifier blktrans_notifier = {
518 : : .add = blktrans_notify_add,
519 : : .remove = blktrans_notify_remove,
520 : : };
521 : :
522 : 0 : int register_mtd_blktrans(struct mtd_blktrans_ops *tr)
523 : : {
524 : : struct mtd_info *mtd;
525 : : int ret;
526 : :
527 : : /* Register the notifier if/when the first device type is
528 : : registered, to prevent the link/init ordering from fucking
529 : : us over. */
530 [ # # ]: 0 : if (!blktrans_notifier.list.next)
531 : 0 : register_mtd_user(&blktrans_notifier);
532 : :
533 : :
534 : 0 : mutex_lock(&mtd_table_mutex);
535 : :
536 : 0 : ret = register_blkdev(tr->major, tr->name);
537 [ # # ]: 0 : if (ret < 0) {
538 : 0 : printk(KERN_WARNING "Unable to register %s block device on major %d: %d\n",
539 : : tr->name, tr->major, ret);
540 : 0 : mutex_unlock(&mtd_table_mutex);
541 : 0 : return ret;
542 : : }
543 : :
544 [ # # ]: 0 : if (ret)
545 : 0 : tr->major = ret;
546 : :
547 : 0 : tr->blkshift = ffs(tr->blksize) - 1;
548 : :
549 : 0 : INIT_LIST_HEAD(&tr->devs);
550 : 0 : list_add(&tr->list, &blktrans_majors);
551 : :
552 [ # # ]: 0 : mtd_for_each_device(mtd)
553 [ # # ]: 0 : if (mtd->type != MTD_ABSENT)
554 : 0 : tr->add_mtd(tr, mtd);
555 : :
556 : 0 : mutex_unlock(&mtd_table_mutex);
557 : 0 : return 0;
558 : : }
559 : :
560 : 0 : int deregister_mtd_blktrans(struct mtd_blktrans_ops *tr)
561 : : {
562 : : struct mtd_blktrans_dev *dev, *next;
563 : :
564 : 0 : mutex_lock(&mtd_table_mutex);
565 : :
566 : : /* Remove it from the list of active majors */
567 : : list_del(&tr->list);
568 : :
569 [ # # ]: 0 : list_for_each_entry_safe(dev, next, &tr->devs, list)
570 : 0 : tr->remove_dev(dev);
571 : :
572 : 0 : unregister_blkdev(tr->major, tr->name);
573 : 0 : mutex_unlock(&mtd_table_mutex);
574 : :
575 [ # # ]: 0 : BUG_ON(!list_empty(&tr->devs));
576 : 0 : return 0;
577 : : }
578 : :
579 : 0 : static void __exit mtd_blktrans_exit(void)
580 : : {
581 : : /* No race here -- if someone's currently in register_mtd_blktrans
582 : : we're screwed anyway. */
583 [ # # ]: 0 : if (blktrans_notifier.list.next)
584 : 0 : unregister_mtd_user(&blktrans_notifier);
585 : 0 : }
586 : :
587 : : module_exit(mtd_blktrans_exit);
588 : :
589 : : EXPORT_SYMBOL_GPL(register_mtd_blktrans);
590 : : EXPORT_SYMBOL_GPL(deregister_mtd_blktrans);
591 : : EXPORT_SYMBOL_GPL(add_mtd_blktrans_dev);
592 : : EXPORT_SYMBOL_GPL(del_mtd_blktrans_dev);
593 : :
594 : : MODULE_AUTHOR("David Woodhouse <dwmw2@infradead.org>");
595 : : MODULE_LICENSE("GPL");
596 : : MODULE_DESCRIPTION("Common interface to block layer for MTD 'translation layers'");
|