diff --git a/driver/device-mapper/dm-blkdev.c b/driver/device-mapper/dm-blkdev.c new file mode 100644 index 000000000..ecf4e19db --- /dev/null +++ b/driver/device-mapper/dm-blkdev.c @@ -0,0 +1,168 @@ +/* + * dm-blkdev.c + * + * Copyright (C) 2001 Sistina Software + * + * This software is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2, or (at + * your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with GNU CC; see the file COPYING. If not, write to + * the Free Software Foundation, 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#include +#include +#include +#include "dm.h" + +struct dm_bdev { + struct list_head list; + struct block_device *bdev; + int use; +}; + +/* + * Lock ordering: Always get bdev_sem before bdev_lock if you need both locks. + * + * bdev_lock: A spinlock which protects the list + * bdev_sem: A semaphore which protects blkdev_get / blkdev_put so that we + * are certain to hold only a single reference at any point in time. + */ +static kmem_cache_t *bdev_cachep; +static LIST_HEAD(bdev_list); +static rwlock_t bdev_lock = RW_LOCK_UNLOCKED; +static DECLARE_MUTEX(bdev_sem); + +static struct dm_bdev *__dm_find_device(struct block_device *bdev) +{ + struct list_head *tmp, *head; + struct dm_bdev *b; + + tmp = head = &bdev_list; + for(;;) { + tmp = tmp->next; + if (tmp == head) + break; + b = list_entry(tmp, struct dm_bdev, list); + if (b->bdev != bdev) + continue; + b->use++; + return b; + } + + return NULL; +} + +static struct dm_bdev *dm_get_device(struct block_device *bdev) +{ + struct dm_bdev *d, *n; + int rv = 0; + + read_lock(&bdev_lock); + d = __dm_find_device(bdev); + read_unlock(&bdev_lock); + + if (d) + return d; + + n = kmem_cache_alloc(bdev_cachep, GFP_KERNEL); + if (!n) + return ERR_PTR(-ENOMEM); + + n->bdev = bdev; + n->use = 1; + n->flags = 0; + + down(&bdev_sem); + read_lock(&bdev_lock); + d = __dm_find_device(bdev); + read_unlock(&bdev_lock); + + + if (!d) { + rv = blkdev_get(d->bdev, FMODE_READ | FMODE_WRITE, 0, BDEV_FILE); + if (rv == 0) { + write_lock(&bdev_lock); + list_add(&bdev_list, &n->list); + d = n; + n = NULL; + write_unlock(&bdev_lock); + } + } + if (n) { + kmem_cache_free(bdev_cachep, n); + } + if (rv) { + d = ERR_PTR(rv); + } + up(&bdev_sem); + + return d; +} + +struct dm_bdev *dm_blkdev_get(const char *name) +{ + struct dm_bdev *d; + struct nameidata nd; + struct inode *inode; + int rv; + + if (!path_init(path, LOOKUP_FOLLOW, &nd)) + return ERR_PTR(-EINVAL); + + if (path_walk(path, &nd)) + return ERR_PTR(-EINVAL); + + inode = nd.dentry->d_inode; + if (!inode) { + d = ERR_PTR(-ENOENT); + goto out; + } + + d = dm_get_device(inode->i_bdev->bd_dev); + +out: + path_release(&nd); + return d; +} + +static void dm_blkdev_drop(struct dm_bdev *d) +{ + down(&bdev_sem); + write_lock(&bdev_lock); + if (d->use == 0) { + list_del(&d->list); + } else { + d = NULL; + } + write_unlock(&bdev_lock); + if (d) { + blkdev_put(d->bdev, BDEV_FILE); + kmem_cache_free(bdev_cachep, d); + } + up(&bdev_sem); +} + +void dm_blkdev_put(struct dm_bdev *d) +{ + int do_drop = 0; + + read_lock(&bdev_lock); + if (--d->use == 0) + do_drop = 1; + read_unlock(&bdev_lock); + + if (do_drop) + dm_blkdev_drop(d); +} + +