blob: 6a572625bfc02be2ab2d271a6958d79db49c16ba [file] [log] [blame]
Linus Torvalds1da177e2005-04-16 15:20:36 -07001/*
Linus Torvalds1da177e2005-04-16 15:20:36 -07002 * (C) 2003 David Woodhouse <dwmw2@infradead.org>
3 *
4 * Interface to Linux 2.5 block layer for MTD 'translation layers'.
5 *
6 */
7
8#include <linux/kernel.h>
9#include <linux/slab.h>
10#include <linux/module.h>
11#include <linux/list.h>
12#include <linux/fs.h>
13#include <linux/mtd/blktrans.h>
14#include <linux/mtd/mtd.h>
15#include <linux/blkdev.h>
16#include <linux/blkpg.h>
17#include <linux/spinlock.h>
18#include <linux/hdreg.h>
19#include <linux/init.h>
Ingo Molnar48b19262006-03-31 02:29:41 -080020#include <linux/mutex.h>
Eric W. Biederman99f9b242007-04-19 01:58:33 -060021#include <linux/kthread.h>
Linus Torvalds1da177e2005-04-16 15:20:36 -070022#include <asm/uaccess.h>
Linus Torvalds1da177e2005-04-16 15:20:36 -070023
Ben Dooks356d70f2007-05-28 20:28:34 +010024#include "mtdcore.h"
Linus Torvalds1da177e2005-04-16 15:20:36 -070025
Ben Dooks356d70f2007-05-28 20:28:34 +010026static LIST_HEAD(blktrans_majors);
Linus Torvalds1da177e2005-04-16 15:20:36 -070027
Linus Torvalds1da177e2005-04-16 15:20:36 -070028
29static int do_blktrans_request(struct mtd_blktrans_ops *tr,
30 struct mtd_blktrans_dev *dev,
31 struct request *req)
32{
33 unsigned long block, nsect;
34 char *buf;
35
Tejun Heo83096eb2009-05-07 22:24:39 +090036 block = blk_rq_pos(req) << 9 >> tr->blkshift;
Tejun Heo1011c1b2009-05-07 22:24:45 +090037 nsect = blk_rq_cur_bytes(req) >> tr->blkshift;
Richard Purdie19187672006-10-27 09:09:33 +010038
Linus Torvalds1da177e2005-04-16 15:20:36 -070039 buf = req->buffer;
40
Jens Axboe4aff5e22006-08-10 08:44:47 +020041 if (!blk_fs_request(req))
Tejun Heof06d9a22009-04-23 11:05:19 +090042 return -EIO;
Linus Torvalds1da177e2005-04-16 15:20:36 -070043
Tejun Heo83096eb2009-05-07 22:24:39 +090044 if (blk_rq_pos(req) + blk_rq_cur_sectors(req) >
45 get_capacity(req->rq_disk))
Tejun Heof06d9a22009-04-23 11:05:19 +090046 return -EIO;
Linus Torvalds1da177e2005-04-16 15:20:36 -070047
Christoph Hellwig1122a262009-09-30 13:52:12 +020048 if (blk_discard_rq(req))
49 return tr->discard(dev, block, nsect);
50
Linus Torvalds1da177e2005-04-16 15:20:36 -070051 switch(rq_data_dir(req)) {
52 case READ:
Richard Purdie19187672006-10-27 09:09:33 +010053 for (; nsect > 0; nsect--, block++, buf += tr->blksize)
Linus Torvalds1da177e2005-04-16 15:20:36 -070054 if (tr->readsect(dev, block, buf))
Tejun Heof06d9a22009-04-23 11:05:19 +090055 return -EIO;
Ilya Loginov2d4dc892009-11-26 09:16:19 +010056 rq_flush_dcache_pages(req);
Tejun Heof06d9a22009-04-23 11:05:19 +090057 return 0;
Linus Torvalds1da177e2005-04-16 15:20:36 -070058 case WRITE:
59 if (!tr->writesect)
Tejun Heof06d9a22009-04-23 11:05:19 +090060 return -EIO;
Linus Torvalds1da177e2005-04-16 15:20:36 -070061
Ilya Loginov2d4dc892009-11-26 09:16:19 +010062 rq_flush_dcache_pages(req);
Richard Purdie19187672006-10-27 09:09:33 +010063 for (; nsect > 0; nsect--, block++, buf += tr->blksize)
Linus Torvalds1da177e2005-04-16 15:20:36 -070064 if (tr->writesect(dev, block, buf))
Tejun Heof06d9a22009-04-23 11:05:19 +090065 return -EIO;
66 return 0;
Linus Torvalds1da177e2005-04-16 15:20:36 -070067 default:
Jeff Garzik9a292302006-10-01 12:16:00 -040068 printk(KERN_NOTICE "Unknown request %u\n", rq_data_dir(req));
Tejun Heof06d9a22009-04-23 11:05:19 +090069 return -EIO;
Linus Torvalds1da177e2005-04-16 15:20:36 -070070 }
71}
72
73static int mtd_blktrans_thread(void *arg)
74{
Maxim Levitskya8638622010-02-22 20:39:29 +020075 struct mtd_blktrans_dev *dev = arg;
76 struct request_queue *rq = dev->rq;
Tejun Heo1498ada2009-05-08 11:54:11 +090077 struct request *req = NULL;
Linus Torvalds1da177e2005-04-16 15:20:36 -070078
Linus Torvalds1da177e2005-04-16 15:20:36 -070079 spin_lock_irq(rq->queue_lock);
Tejun Heo1498ada2009-05-08 11:54:11 +090080
Christoph Hellwig3e67fe42007-04-22 20:40:57 +010081 while (!kthread_should_stop()) {
Tejun Heof06d9a22009-04-23 11:05:19 +090082 int res;
Linus Torvalds1da177e2005-04-16 15:20:36 -070083
Tejun Heo9934c8c2009-05-08 11:54:16 +090084 if (!req && !(req = blk_fetch_request(rq))) {
Linus Torvalds1da177e2005-04-16 15:20:36 -070085 set_current_state(TASK_INTERRUPTIBLE);
Linus Torvalds1da177e2005-04-16 15:20:36 -070086 spin_unlock_irq(rq->queue_lock);
Linus Torvalds1da177e2005-04-16 15:20:36 -070087 schedule();
Linus Torvalds1da177e2005-04-16 15:20:36 -070088 spin_lock_irq(rq->queue_lock);
Linus Torvalds1da177e2005-04-16 15:20:36 -070089 continue;
90 }
91
Linus Torvalds1da177e2005-04-16 15:20:36 -070092 spin_unlock_irq(rq->queue_lock);
93
Ingo Molnar48b19262006-03-31 02:29:41 -080094 mutex_lock(&dev->lock);
Maxim Levitskya8638622010-02-22 20:39:29 +020095 res = do_blktrans_request(dev->tr, dev, req);
Ingo Molnar48b19262006-03-31 02:29:41 -080096 mutex_unlock(&dev->lock);
Linus Torvalds1da177e2005-04-16 15:20:36 -070097
98 spin_lock_irq(rq->queue_lock);
99
Tejun Heo1498ada2009-05-08 11:54:11 +0900100 if (!__blk_end_request_cur(req, res))
101 req = NULL;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700102 }
Tejun Heo1498ada2009-05-08 11:54:11 +0900103
104 if (req)
105 __blk_end_request_all(req, -EIO);
106
Linus Torvalds1da177e2005-04-16 15:20:36 -0700107 spin_unlock_irq(rq->queue_lock);
108
Christoph Hellwig3e67fe42007-04-22 20:40:57 +0100109 return 0;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700110}
111
112static void mtd_blktrans_request(struct request_queue *rq)
113{
Maxim Levitskya8638622010-02-22 20:39:29 +0200114 struct mtd_blktrans_dev *dev = rq->queuedata;
115 wake_up_process(dev->thread);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700116}
117
118
Al Viroaf0e2a02008-03-02 10:35:06 -0500119static int blktrans_open(struct block_device *bdev, fmode_t mode)
Linus Torvalds1da177e2005-04-16 15:20:36 -0700120{
Al Viroaf0e2a02008-03-02 10:35:06 -0500121 struct mtd_blktrans_dev *dev = bdev->bd_disk->private_data;
122 struct mtd_blktrans_ops *tr = dev->tr;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700123 int ret = -ENODEV;
124
Artem Bityutskiy8022c132009-07-10 17:02:17 +0300125 if (!get_mtd_device(NULL, dev->mtd->index))
Linus Torvalds1da177e2005-04-16 15:20:36 -0700126 goto out;
127
128 if (!try_module_get(tr->owner))
129 goto out_tr;
130
Thomas Gleixner97894cd2005-11-07 11:15:26 +0000131 /* FIXME: Locking. A hot pluggable device can go away
Linus Torvalds1da177e2005-04-16 15:20:36 -0700132 (del_mtd_device can be called for it) without its module
133 being unloaded. */
134 dev->mtd->usecount++;
135
136 ret = 0;
137 if (tr->open && (ret = tr->open(dev))) {
138 dev->mtd->usecount--;
Artem Bityutskiy8022c132009-07-10 17:02:17 +0300139 put_mtd_device(dev->mtd);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700140 out_tr:
141 module_put(tr->owner);
142 }
143 out:
144 return ret;
145}
146
Al Viroaf0e2a02008-03-02 10:35:06 -0500147static int blktrans_release(struct gendisk *disk, fmode_t mode)
Linus Torvalds1da177e2005-04-16 15:20:36 -0700148{
Al Viroaf0e2a02008-03-02 10:35:06 -0500149 struct mtd_blktrans_dev *dev = disk->private_data;
150 struct mtd_blktrans_ops *tr = dev->tr;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700151 int ret = 0;
152
Linus Torvalds1da177e2005-04-16 15:20:36 -0700153 if (tr->release)
154 ret = tr->release(dev);
155
156 if (!ret) {
157 dev->mtd->usecount--;
Artem Bityutskiy8022c132009-07-10 17:02:17 +0300158 put_mtd_device(dev->mtd);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700159 module_put(tr->owner);
160 }
161
162 return ret;
163}
164
Christoph Hellwiga885c8c2006-01-08 01:02:50 -0800165static int blktrans_getgeo(struct block_device *bdev, struct hd_geometry *geo)
166{
167 struct mtd_blktrans_dev *dev = bdev->bd_disk->private_data;
168
169 if (dev->tr->getgeo)
170 return dev->tr->getgeo(dev, geo);
171 return -ENOTTY;
172}
Linus Torvalds1da177e2005-04-16 15:20:36 -0700173
Al Viroaf0e2a02008-03-02 10:35:06 -0500174static int blktrans_ioctl(struct block_device *bdev, fmode_t mode,
Linus Torvalds1da177e2005-04-16 15:20:36 -0700175 unsigned int cmd, unsigned long arg)
176{
Al Viroaf0e2a02008-03-02 10:35:06 -0500177 struct mtd_blktrans_dev *dev = bdev->bd_disk->private_data;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700178 struct mtd_blktrans_ops *tr = dev->tr;
179
180 switch (cmd) {
181 case BLKFLSBUF:
182 if (tr->flush)
183 return tr->flush(dev);
184 /* The core code did the work, we had nothing to do. */
185 return 0;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700186 default:
187 return -ENOTTY;
188 }
189}
190
Alexey Dobriyan83d5cde2009-09-21 17:01:13 -0700191static const struct block_device_operations mtd_blktrans_ops = {
Linus Torvalds1da177e2005-04-16 15:20:36 -0700192 .owner = THIS_MODULE,
Al Viroaf0e2a02008-03-02 10:35:06 -0500193 .open = blktrans_open,
194 .release = blktrans_release,
195 .locked_ioctl = blktrans_ioctl,
Christoph Hellwiga885c8c2006-01-08 01:02:50 -0800196 .getgeo = blktrans_getgeo,
Linus Torvalds1da177e2005-04-16 15:20:36 -0700197};
198
199int add_mtd_blktrans_dev(struct mtd_blktrans_dev *new)
200{
201 struct mtd_blktrans_ops *tr = new->tr;
Chris Malley71a928c2008-05-19 20:11:50 +0100202 struct mtd_blktrans_dev *d;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700203 int last_devnum = -1;
204 struct gendisk *gd;
Maxim Levitskya8638622010-02-22 20:39:29 +0200205 int ret;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700206
Jean Delvareb3561ea2007-05-08 00:30:46 -0700207 if (mutex_trylock(&mtd_table_mutex)) {
Ingo Molnar48b19262006-03-31 02:29:41 -0800208 mutex_unlock(&mtd_table_mutex);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700209 BUG();
210 }
211
Chris Malley71a928c2008-05-19 20:11:50 +0100212 list_for_each_entry(d, &tr->devs, list) {
Linus Torvalds1da177e2005-04-16 15:20:36 -0700213 if (new->devnum == -1) {
214 /* Use first free number */
215 if (d->devnum != last_devnum+1) {
216 /* Found a free devnum. Plug it in here */
217 new->devnum = last_devnum+1;
218 list_add_tail(&new->list, &d->list);
219 goto added;
220 }
221 } else if (d->devnum == new->devnum) {
222 /* Required number taken */
223 return -EBUSY;
224 } else if (d->devnum > new->devnum) {
225 /* Required number was free */
226 list_add_tail(&new->list, &d->list);
227 goto added;
Thomas Gleixner97894cd2005-11-07 11:15:26 +0000228 }
Linus Torvalds1da177e2005-04-16 15:20:36 -0700229 last_devnum = d->devnum;
230 }
Maxim Levitskya8638622010-02-22 20:39:29 +0200231
232 ret = -EBUSY;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700233 if (new->devnum == -1)
234 new->devnum = last_devnum+1;
235
Ben Hutchings4d3a8532010-01-29 20:59:53 +0000236 /* Check that the device and any partitions will get valid
237 * minor numbers and that the disk naming code below can cope
238 * with this number. */
239 if (new->devnum > (MINORMASK >> tr->part_bits) ||
240 (tr->part_bits && new->devnum >= 27 * 26))
Maxim Levitskya8638622010-02-22 20:39:29 +0200241 goto error1;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700242
Linus Torvalds1da177e2005-04-16 15:20:36 -0700243 list_add_tail(&new->list, &tr->devs);
244 added:
David Woodhousece37ab42007-12-03 12:46:12 +0000245 mutex_init(&new->lock);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700246 if (!tr->writesect)
247 new->readonly = 1;
248
Maxim Levitskya8638622010-02-22 20:39:29 +0200249
250 /* Create gendisk */
251 ret = -ENOMEM;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700252 gd = alloc_disk(1 << tr->part_bits);
Maxim Levitskya8638622010-02-22 20:39:29 +0200253
254 if (!gd)
255 goto error2;
256
257 new->disk = gd;
258 gd->private_data = new;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700259 gd->major = tr->major;
260 gd->first_minor = (new->devnum) << tr->part_bits;
261 gd->fops = &mtd_blktrans_ops;
Thomas Gleixner97894cd2005-11-07 11:15:26 +0000262
Todd Poynor65a8de32005-07-29 20:42:07 +0100263 if (tr->part_bits)
264 if (new->devnum < 26)
265 snprintf(gd->disk_name, sizeof(gd->disk_name),
266 "%s%c", tr->name, 'a' + new->devnum);
267 else
268 snprintf(gd->disk_name, sizeof(gd->disk_name),
269 "%s%c%c", tr->name,
270 'a' - 1 + new->devnum / 26,
271 'a' + new->devnum % 26);
272 else
273 snprintf(gd->disk_name, sizeof(gd->disk_name),
274 "%s%d", tr->name, new->devnum);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700275
Richard Purdie19187672006-10-27 09:09:33 +0100276 set_capacity(gd, (new->size * tr->blksize) >> 9);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700277
Maxim Levitskya8638622010-02-22 20:39:29 +0200278
279 /* Create the request queue */
280 spin_lock_init(&new->queue_lock);
281 new->rq = blk_init_queue(mtd_blktrans_request, &new->queue_lock);
282
283 if (!new->rq)
284 goto error3;
285
286 new->rq->queuedata = new;
287 blk_queue_logical_block_size(new->rq, tr->blksize);
288
289 if (tr->discard)
290 queue_flag_set_unlocked(QUEUE_FLAG_DISCARD,
291 new->rq);
292
293 gd->queue = new->rq;
294
295 /* Create processing thread */
296 /* TODO: workqueue ? */
297 new->thread = kthread_run(mtd_blktrans_thread, new,
298 "%s%d", tr->name, new->mtd->index);
299 if (IS_ERR(new->thread)) {
300 ret = PTR_ERR(new->thread);
301 goto error4;
302 }
David Woodhoused6948462009-04-05 07:38:33 -0700303 gd->driverfs_dev = &new->mtd->dev;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700304
305 if (new->readonly)
306 set_disk_ro(gd, 1);
307
308 add_disk(gd);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700309 return 0;
Maxim Levitskya8638622010-02-22 20:39:29 +0200310error4:
311 blk_cleanup_queue(new->rq);
312error3:
313 put_disk(new->disk);
314error2:
315 list_del(&new->list);
316error1:
317 kfree(new);
318 return ret;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700319}
320
321int del_mtd_blktrans_dev(struct mtd_blktrans_dev *old)
322{
Jean Delvareb3561ea2007-05-08 00:30:46 -0700323 if (mutex_trylock(&mtd_table_mutex)) {
Ingo Molnar48b19262006-03-31 02:29:41 -0800324 mutex_unlock(&mtd_table_mutex);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700325 BUG();
326 }
327
328 list_del(&old->list);
329
Maxim Levitskya8638622010-02-22 20:39:29 +0200330 /* stop new requests to arrive */
331 del_gendisk(old->disk);
Thomas Gleixner97894cd2005-11-07 11:15:26 +0000332
Maxim Levitskya8638622010-02-22 20:39:29 +0200333 /* Stop the thread */
334 kthread_stop(old->thread);
335
336 blk_cleanup_queue(old->rq);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700337 return 0;
338}
339
340static void blktrans_notify_remove(struct mtd_info *mtd)
341{
Chris Malley71a928c2008-05-19 20:11:50 +0100342 struct mtd_blktrans_ops *tr;
343 struct mtd_blktrans_dev *dev, *next;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700344
Chris Malley71a928c2008-05-19 20:11:50 +0100345 list_for_each_entry(tr, &blktrans_majors, list)
346 list_for_each_entry_safe(dev, next, &tr->devs, list)
Linus Torvalds1da177e2005-04-16 15:20:36 -0700347 if (dev->mtd == mtd)
348 tr->remove_dev(dev);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700349}
350
351static void blktrans_notify_add(struct mtd_info *mtd)
352{
Chris Malley71a928c2008-05-19 20:11:50 +0100353 struct mtd_blktrans_ops *tr;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700354
355 if (mtd->type == MTD_ABSENT)
356 return;
357
Chris Malley71a928c2008-05-19 20:11:50 +0100358 list_for_each_entry(tr, &blktrans_majors, list)
Linus Torvalds1da177e2005-04-16 15:20:36 -0700359 tr->add_mtd(tr, mtd);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700360}
361
362static struct mtd_notifier blktrans_notifier = {
363 .add = blktrans_notify_add,
364 .remove = blktrans_notify_remove,
365};
Thomas Gleixner97894cd2005-11-07 11:15:26 +0000366
Linus Torvalds1da177e2005-04-16 15:20:36 -0700367int register_mtd_blktrans(struct mtd_blktrans_ops *tr)
368{
Ben Hutchingsf1332ba2010-01-29 20:57:11 +0000369 struct mtd_info *mtd;
370 int ret;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700371
Thomas Gleixner97894cd2005-11-07 11:15:26 +0000372 /* Register the notifier if/when the first device type is
Linus Torvalds1da177e2005-04-16 15:20:36 -0700373 registered, to prevent the link/init ordering from fucking
374 us over. */
375 if (!blktrans_notifier.list.next)
376 register_mtd_user(&blktrans_notifier);
377
Linus Torvalds1da177e2005-04-16 15:20:36 -0700378
Ingo Molnar48b19262006-03-31 02:29:41 -0800379 mutex_lock(&mtd_table_mutex);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700380
381 ret = register_blkdev(tr->major, tr->name);
382 if (ret) {
383 printk(KERN_WARNING "Unable to register %s block device on major %d: %d\n",
384 tr->name, tr->major, ret);
Ingo Molnar48b19262006-03-31 02:29:41 -0800385 mutex_unlock(&mtd_table_mutex);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700386 return ret;
387 }
David Woodhouseeae9acd2008-08-05 18:08:25 +0100388
Richard Purdie19187672006-10-27 09:09:33 +0100389 tr->blkshift = ffs(tr->blksize) - 1;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700390
Linus Torvalds1da177e2005-04-16 15:20:36 -0700391 INIT_LIST_HEAD(&tr->devs);
392 list_add(&tr->list, &blktrans_majors);
393
Ben Hutchingsf1332ba2010-01-29 20:57:11 +0000394 mtd_for_each_device(mtd)
395 if (mtd->type != MTD_ABSENT)
396 tr->add_mtd(tr, mtd);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700397
Ingo Molnar48b19262006-03-31 02:29:41 -0800398 mutex_unlock(&mtd_table_mutex);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700399
400 return 0;
401}
402
403int deregister_mtd_blktrans(struct mtd_blktrans_ops *tr)
404{
Chris Malley71a928c2008-05-19 20:11:50 +0100405 struct mtd_blktrans_dev *dev, *next;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700406
Ingo Molnar48b19262006-03-31 02:29:41 -0800407 mutex_lock(&mtd_table_mutex);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700408
Linus Torvalds1da177e2005-04-16 15:20:36 -0700409
410 /* Remove it from the list of active majors */
411 list_del(&tr->list);
412
Chris Malley71a928c2008-05-19 20:11:50 +0100413 list_for_each_entry_safe(dev, next, &tr->devs, list)
Linus Torvalds1da177e2005-04-16 15:20:36 -0700414 tr->remove_dev(dev);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700415
Linus Torvalds1da177e2005-04-16 15:20:36 -0700416 unregister_blkdev(tr->major, tr->name);
Ingo Molnar48b19262006-03-31 02:29:41 -0800417 mutex_unlock(&mtd_table_mutex);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700418
Eric Sesterhenn373ebfb2006-03-26 18:15:12 +0200419 BUG_ON(!list_empty(&tr->devs));
Linus Torvalds1da177e2005-04-16 15:20:36 -0700420 return 0;
421}
422
423static void __exit mtd_blktrans_exit(void)
424{
425 /* No race here -- if someone's currently in register_mtd_blktrans
426 we're screwed anyway. */
427 if (blktrans_notifier.list.next)
428 unregister_mtd_user(&blktrans_notifier);
429}
430
431module_exit(mtd_blktrans_exit);
432
433EXPORT_SYMBOL_GPL(register_mtd_blktrans);
434EXPORT_SYMBOL_GPL(deregister_mtd_blktrans);
435EXPORT_SYMBOL_GPL(add_mtd_blktrans_dev);
436EXPORT_SYMBOL_GPL(del_mtd_blktrans_dev);
437
438MODULE_AUTHOR("David Woodhouse <dwmw2@infradead.org>");
439MODULE_LICENSE("GPL");
440MODULE_DESCRIPTION("Common interface to block layer for MTD 'translation layers'");