mtd_blkdevs.c 10.2 KB
Newer Older
L
Linus Torvalds 已提交
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
/*
 * (C) 2003 David Woodhouse <dwmw2@infradead.org>
 *
 * Interface to Linux 2.5 block layer for MTD 'translation layers'.
 *
 */

#include <linux/kernel.h>
#include <linux/slab.h>
#include <linux/module.h>
#include <linux/list.h>
#include <linux/fs.h>
#include <linux/mtd/blktrans.h>
#include <linux/mtd/mtd.h>
#include <linux/blkdev.h>
#include <linux/blkpg.h>
17
#include <linux/freezer.h>
L
Linus Torvalds 已提交
18 19 20
#include <linux/spinlock.h>
#include <linux/hdreg.h>
#include <linux/init.h>
I
Ingo Molnar 已提交
21
#include <linux/mutex.h>
22
#include <linux/kthread.h>
L
Linus Torvalds 已提交
23 24
#include <asm/uaccess.h>

25
#include "mtdcore.h"
L
Linus Torvalds 已提交
26

27
static LIST_HEAD(blktrans_majors);
L
Linus Torvalds 已提交
28 29

struct mtd_blkcore_priv {
30
	struct task_struct *thread;
L
Linus Torvalds 已提交
31 32 33 34
	struct request_queue *rq;
	spinlock_t queue_lock;
};

35 36 37 38 39 40 41 42
static int blktrans_discard_request(struct request_queue *q,
				    struct request *req)
{
	req->cmd_type = REQ_TYPE_LINUX_BLOCK;
	req->cmd[0] = REQ_LB_OP_DISCARD;
	return 0;
}

L
Linus Torvalds 已提交
43 44 45 46 47 48 49
static int do_blktrans_request(struct mtd_blktrans_ops *tr,
			       struct mtd_blktrans_dev *dev,
			       struct request *req)
{
	unsigned long block, nsect;
	char *buf;

50 51 52
	block = req->sector << 9 >> tr->blkshift;
	nsect = req->current_nr_sectors << 9 >> tr->blkshift;

L
Linus Torvalds 已提交
53 54
	buf = req->buffer;

55 56 57 58
	if (req->cmd_type == REQ_TYPE_LINUX_BLOCK &&
	    req->cmd[0] == REQ_LB_OP_DISCARD)
		return !tr->discard(dev, block, nsect);

59
	if (!blk_fs_request(req))
L
Linus Torvalds 已提交
60 61
		return 0;

62
	if (req->sector + req->current_nr_sectors > get_capacity(req->rq_disk))
L
Linus Torvalds 已提交
63 64 65 66
		return 0;

	switch(rq_data_dir(req)) {
	case READ:
67
		for (; nsect > 0; nsect--, block++, buf += tr->blksize)
L
Linus Torvalds 已提交
68 69 70 71 72 73 74 75
			if (tr->readsect(dev, block, buf))
				return 0;
		return 1;

	case WRITE:
		if (!tr->writesect)
			return 0;

76
		for (; nsect > 0; nsect--, block++, buf += tr->blksize)
L
Linus Torvalds 已提交
77 78 79 80 81
			if (tr->writesect(dev, block, buf))
				return 0;
		return 1;

	default:
J
Jeff Garzik 已提交
82
		printk(KERN_NOTICE "Unknown request %u\n", rq_data_dir(req));
L
Linus Torvalds 已提交
83 84 85 86 87 88 89 90 91 92
		return 0;
	}
}

static int mtd_blktrans_thread(void *arg)
{
	struct mtd_blktrans_ops *tr = arg;
	struct request_queue *rq = tr->blkcore_priv->rq;

	/* we might get involved when memory gets low, so use PF_MEMALLOC */
93
	current->flags |= PF_MEMALLOC;
L
Linus Torvalds 已提交
94 95

	spin_lock_irq(rq->queue_lock);
96
	while (!kthread_should_stop()) {
L
Linus Torvalds 已提交
97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115
		struct request *req;
		struct mtd_blktrans_dev *dev;
		int res = 0;

		req = elv_next_request(rq);

		if (!req) {
			set_current_state(TASK_INTERRUPTIBLE);
			spin_unlock_irq(rq->queue_lock);
			schedule();
			spin_lock_irq(rq->queue_lock);
			continue;
		}

		dev = req->rq_disk->private_data;
		tr = dev->tr;

		spin_unlock_irq(rq->queue_lock);

I
Ingo Molnar 已提交
116
		mutex_lock(&dev->lock);
L
Linus Torvalds 已提交
117
		res = do_blktrans_request(tr, dev, req);
I
Ingo Molnar 已提交
118
		mutex_unlock(&dev->lock);
L
Linus Torvalds 已提交
119 120 121 122 123 124 125

		spin_lock_irq(rq->queue_lock);

		end_request(req, res);
	}
	spin_unlock_irq(rq->queue_lock);

126
	return 0;
L
Linus Torvalds 已提交
127 128 129 130 131
}

static void mtd_blktrans_request(struct request_queue *rq)
{
	struct mtd_blktrans_ops *tr = rq->queuedata;
132
	wake_up_process(tr->blkcore_priv->thread);
L
Linus Torvalds 已提交
133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150
}


static int blktrans_open(struct inode *i, struct file *f)
{
	struct mtd_blktrans_dev *dev;
	struct mtd_blktrans_ops *tr;
	int ret = -ENODEV;

	dev = i->i_bdev->bd_disk->private_data;
	tr = dev->tr;

	if (!try_module_get(dev->mtd->owner))
		goto out;

	if (!try_module_get(tr->owner))
		goto out_tr;

151
	/* FIXME: Locking. A hot pluggable device can go away
L
Linus Torvalds 已提交
152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187
	   (del_mtd_device can be called for it) without its module
	   being unloaded. */
	dev->mtd->usecount++;

	ret = 0;
	if (tr->open && (ret = tr->open(dev))) {
		dev->mtd->usecount--;
		module_put(dev->mtd->owner);
	out_tr:
		module_put(tr->owner);
	}
 out:
	return ret;
}

static int blktrans_release(struct inode *i, struct file *f)
{
	struct mtd_blktrans_dev *dev;
	struct mtd_blktrans_ops *tr;
	int ret = 0;

	dev = i->i_bdev->bd_disk->private_data;
	tr = dev->tr;

	if (tr->release)
		ret = tr->release(dev);

	if (!ret) {
		dev->mtd->usecount--;
		module_put(dev->mtd->owner);
		module_put(tr->owner);
	}

	return ret;
}

188 189 190 191 192 193 194 195
static int blktrans_getgeo(struct block_device *bdev, struct hd_geometry *geo)
{
	struct mtd_blktrans_dev *dev = bdev->bd_disk->private_data;

	if (dev->tr->getgeo)
		return dev->tr->getgeo(dev, geo);
	return -ENOTTY;
}
L
Linus Torvalds 已提交
196

197
static int blktrans_ioctl(struct inode *inode, struct file *file,
L
Linus Torvalds 已提交
198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213
			      unsigned int cmd, unsigned long arg)
{
	struct mtd_blktrans_dev *dev = inode->i_bdev->bd_disk->private_data;
	struct mtd_blktrans_ops *tr = dev->tr;

	switch (cmd) {
	case BLKFLSBUF:
		if (tr->flush)
			return tr->flush(dev);
		/* The core code did the work, we had nothing to do. */
		return 0;
	default:
		return -ENOTTY;
	}
}

214
static struct block_device_operations mtd_blktrans_ops = {
L
Linus Torvalds 已提交
215 216 217 218
	.owner		= THIS_MODULE,
	.open		= blktrans_open,
	.release	= blktrans_release,
	.ioctl		= blktrans_ioctl,
219
	.getgeo		= blktrans_getgeo,
L
Linus Torvalds 已提交
220 221 222 223 224
};

int add_mtd_blktrans_dev(struct mtd_blktrans_dev *new)
{
	struct mtd_blktrans_ops *tr = new->tr;
225
	struct mtd_blktrans_dev *d;
L
Linus Torvalds 已提交
226 227 228
	int last_devnum = -1;
	struct gendisk *gd;

J
Jean Delvare 已提交
229
	if (mutex_trylock(&mtd_table_mutex)) {
I
Ingo Molnar 已提交
230
		mutex_unlock(&mtd_table_mutex);
L
Linus Torvalds 已提交
231 232 233
		BUG();
	}

234
	list_for_each_entry(d, &tr->devs, list) {
L
Linus Torvalds 已提交
235 236 237 238 239 240 241 242 243 244 245 246 247 248 249
		if (new->devnum == -1) {
			/* Use first free number */
			if (d->devnum != last_devnum+1) {
				/* Found a free devnum. Plug it in here */
				new->devnum = last_devnum+1;
				list_add_tail(&new->list, &d->list);
				goto added;
			}
		} else if (d->devnum == new->devnum) {
			/* Required number taken */
			return -EBUSY;
		} else if (d->devnum > new->devnum) {
			/* Required number was free */
			list_add_tail(&new->list, &d->list);
			goto added;
250
		}
L
Linus Torvalds 已提交
251 252 253 254 255 256 257 258 259 260 261
		last_devnum = d->devnum;
	}
	if (new->devnum == -1)
		new->devnum = last_devnum+1;

	if ((new->devnum << tr->part_bits) > 256) {
		return -EBUSY;
	}

	list_add_tail(&new->list, &tr->devs);
 added:
262
	mutex_init(&new->lock);
L
Linus Torvalds 已提交
263 264 265 266 267 268 269 270 271 272 273
	if (!tr->writesect)
		new->readonly = 1;

	gd = alloc_disk(1 << tr->part_bits);
	if (!gd) {
		list_del(&new->list);
		return -ENOMEM;
	}
	gd->major = tr->major;
	gd->first_minor = (new->devnum) << tr->part_bits;
	gd->fops = &mtd_blktrans_ops;
274

275 276 277 278 279 280 281 282 283 284 285 286
	if (tr->part_bits)
		if (new->devnum < 26)
			snprintf(gd->disk_name, sizeof(gd->disk_name),
				 "%s%c", tr->name, 'a' + new->devnum);
		else
			snprintf(gd->disk_name, sizeof(gd->disk_name),
				 "%s%c%c", tr->name,
				 'a' - 1 + new->devnum / 26,
				 'a' + new->devnum % 26);
	else
		snprintf(gd->disk_name, sizeof(gd->disk_name),
			 "%s%d", tr->name, new->devnum);
L
Linus Torvalds 已提交
287 288 289

	/* 2.5 has capacity in units of 512 bytes while still
	   having BLOCK_SIZE_BITS set to 10. Just to keep us amused. */
290
	set_capacity(gd, (new->size * tr->blksize) >> 9);
L
Linus Torvalds 已提交
291 292 293 294 295 296 297 298 299

	gd->private_data = new;
	new->blkcore_priv = gd;
	gd->queue = tr->blkcore_priv->rq;

	if (new->readonly)
		set_disk_ro(gd, 1);

	add_disk(gd);
300

L
Linus Torvalds 已提交
301 302 303 304 305
	return 0;
}

int del_mtd_blktrans_dev(struct mtd_blktrans_dev *old)
{
J
Jean Delvare 已提交
306
	if (mutex_trylock(&mtd_table_mutex)) {
I
Ingo Molnar 已提交
307
		mutex_unlock(&mtd_table_mutex);
L
Linus Torvalds 已提交
308 309 310 311 312 313 314
		BUG();
	}

	list_del(&old->list);

	del_gendisk(old->blkcore_priv);
	put_disk(old->blkcore_priv);
315

L
Linus Torvalds 已提交
316 317 318 319 320
	return 0;
}

static void blktrans_notify_remove(struct mtd_info *mtd)
{
321 322
	struct mtd_blktrans_ops *tr;
	struct mtd_blktrans_dev *dev, *next;
L
Linus Torvalds 已提交
323

324 325
	list_for_each_entry(tr, &blktrans_majors, list)
		list_for_each_entry_safe(dev, next, &tr->devs, list)
L
Linus Torvalds 已提交
326 327 328 329 330 331
			if (dev->mtd == mtd)
				tr->remove_dev(dev);
}

static void blktrans_notify_add(struct mtd_info *mtd)
{
332
	struct mtd_blktrans_ops *tr;
L
Linus Torvalds 已提交
333 334 335 336

	if (mtd->type == MTD_ABSENT)
		return;

337
	list_for_each_entry(tr, &blktrans_majors, list)
L
Linus Torvalds 已提交
338 339 340 341 342 343 344
		tr->add_mtd(tr, mtd);
}

static struct mtd_notifier blktrans_notifier = {
	.add = blktrans_notify_add,
	.remove = blktrans_notify_remove,
};
345

L
Linus Torvalds 已提交
346 347 348 349
int register_mtd_blktrans(struct mtd_blktrans_ops *tr)
{
	int ret, i;

350
	/* Register the notifier if/when the first device type is
L
Linus Torvalds 已提交
351 352 353 354 355
	   registered, to prevent the link/init ordering from fucking
	   us over. */
	if (!blktrans_notifier.list.next)
		register_mtd_user(&blktrans_notifier);

356
	tr->blkcore_priv = kzalloc(sizeof(*tr->blkcore_priv), GFP_KERNEL);
L
Linus Torvalds 已提交
357 358 359
	if (!tr->blkcore_priv)
		return -ENOMEM;

I
Ingo Molnar 已提交
360
	mutex_lock(&mtd_table_mutex);
L
Linus Torvalds 已提交
361 362 363 364 365 366

	ret = register_blkdev(tr->major, tr->name);
	if (ret) {
		printk(KERN_WARNING "Unable to register %s block device on major %d: %d\n",
		       tr->name, tr->major, ret);
		kfree(tr->blkcore_priv);
I
Ingo Molnar 已提交
367
		mutex_unlock(&mtd_table_mutex);
L
Linus Torvalds 已提交
368 369 370 371 372 373 374 375
		return ret;
	}
	spin_lock_init(&tr->blkcore_priv->queue_lock);

	tr->blkcore_priv->rq = blk_init_queue(mtd_blktrans_request, &tr->blkcore_priv->queue_lock);
	if (!tr->blkcore_priv->rq) {
		unregister_blkdev(tr->major, tr->name);
		kfree(tr->blkcore_priv);
I
Ingo Molnar 已提交
376
		mutex_unlock(&mtd_table_mutex);
L
Linus Torvalds 已提交
377 378 379 380
		return -ENOMEM;
	}

	tr->blkcore_priv->rq->queuedata = tr;
381
	blk_queue_hardsect_size(tr->blkcore_priv->rq, tr->blksize);
382 383 384 385
	if (tr->discard)
		blk_queue_set_discard(tr->blkcore_priv->rq,
				      blktrans_discard_request);

386
	tr->blkshift = ffs(tr->blksize) - 1;
L
Linus Torvalds 已提交
387

388 389 390
	tr->blkcore_priv->thread = kthread_run(mtd_blktrans_thread, tr,
			"%sd", tr->name);
	if (IS_ERR(tr->blkcore_priv->thread)) {
L
Linus Torvalds 已提交
391 392 393
		blk_cleanup_queue(tr->blkcore_priv->rq);
		unregister_blkdev(tr->major, tr->name);
		kfree(tr->blkcore_priv);
I
Ingo Molnar 已提交
394
		mutex_unlock(&mtd_table_mutex);
395
		return PTR_ERR(tr->blkcore_priv->thread);
396
	}
L
Linus Torvalds 已提交
397 398 399 400 401 402 403 404 405

	INIT_LIST_HEAD(&tr->devs);
	list_add(&tr->list, &blktrans_majors);

	for (i=0; i<MAX_MTD_DEVICES; i++) {
		if (mtd_table[i] && mtd_table[i]->type != MTD_ABSENT)
			tr->add_mtd(tr, mtd_table[i]);
	}

I
Ingo Molnar 已提交
406
	mutex_unlock(&mtd_table_mutex);
L
Linus Torvalds 已提交
407 408 409 410 411 412

	return 0;
}

int deregister_mtd_blktrans(struct mtd_blktrans_ops *tr)
{
413
	struct mtd_blktrans_dev *dev, *next;
L
Linus Torvalds 已提交
414

I
Ingo Molnar 已提交
415
	mutex_lock(&mtd_table_mutex);
L
Linus Torvalds 已提交
416 417

	/* Clean up the kernel thread */
418
	kthread_stop(tr->blkcore_priv->thread);
L
Linus Torvalds 已提交
419 420 421 422

	/* Remove it from the list of active majors */
	list_del(&tr->list);

423
	list_for_each_entry_safe(dev, next, &tr->devs, list)
L
Linus Torvalds 已提交
424 425 426 427 428
		tr->remove_dev(dev);

	blk_cleanup_queue(tr->blkcore_priv->rq);
	unregister_blkdev(tr->major, tr->name);

I
Ingo Molnar 已提交
429
	mutex_unlock(&mtd_table_mutex);
L
Linus Torvalds 已提交
430 431 432

	kfree(tr->blkcore_priv);

433
	BUG_ON(!list_empty(&tr->devs));
L
Linus Torvalds 已提交
434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454
	return 0;
}

static void __exit mtd_blktrans_exit(void)
{
	/* No race here -- if someone's currently in register_mtd_blktrans
	   we're screwed anyway. */
	if (blktrans_notifier.list.next)
		unregister_mtd_user(&blktrans_notifier);
}

module_exit(mtd_blktrans_exit);

EXPORT_SYMBOL_GPL(register_mtd_blktrans);
EXPORT_SYMBOL_GPL(deregister_mtd_blktrans);
EXPORT_SYMBOL_GPL(add_mtd_blktrans_dev);
EXPORT_SYMBOL_GPL(del_mtd_blktrans_dev);

MODULE_AUTHOR("David Woodhouse <dwmw2@infradead.org>");
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("Common interface to block layer for MTD 'translation layers'");