summaryrefslogtreecommitdiffstats
path: root/fs/block_dev.c
diff options
context:
space:
mode:
authorRalf Baechle <ralf@linux-mips.org>2000-02-05 06:47:02 +0000
committerRalf Baechle <ralf@linux-mips.org>2000-02-05 06:47:02 +0000
commit99a7e12f34b3661a0d1354eef83a0eef4df5e34c (patch)
tree3560aca9ca86792f9ab7bd87861ea143a1b3c7a3 /fs/block_dev.c
parente73a04659c0b8cdee4dd40e58630e2cf63afb316 (diff)
Merge with Linux 2.3.38.
Diffstat (limited to 'fs/block_dev.c')
-rw-r--r--fs/block_dev.c361
1 files changed, 361 insertions, 0 deletions
diff --git a/fs/block_dev.c b/fs/block_dev.c
index 8331e9514..c32b8c0f2 100644
--- a/fs/block_dev.c
+++ b/fs/block_dev.c
@@ -4,9 +4,12 @@
* Copyright (C) 1991, 1992 Linus Torvalds
*/
+#include <linux/config.h>
#include <linux/mm.h>
#include <linux/locks.h>
#include <linux/fcntl.h>
+#include <linux/malloc.h>
+#include <linux/kmod.h>
#include <asm/uaccess.h>
@@ -299,3 +302,361 @@ int block_fsync(struct file *filp, struct dentry *dentry)
{
return fsync_dev(dentry->d_inode->i_rdev);
}
+
+/*
+ * bdev cache handling - shamelessly stolen from inode.c
+ * We use smaller hashtable, though.
+ */
+
+#define HASH_BITS 6
+#define HASH_SIZE (1UL << HASH_BITS)
+#define HASH_MASK (HASH_SIZE-1)
+static struct list_head bdev_hashtable[HASH_SIZE];
+static spinlock_t bdev_lock = SPIN_LOCK_UNLOCKED;
+static kmem_cache_t * bdev_cachep;
+
+#define alloc_bdev() \
+ ((struct block_device *) kmem_cache_alloc(bdev_cachep, SLAB_KERNEL))
+#define destroy_bdev(bdev) kmem_cache_free(bdev_cachep, (bdev))
+
+static void init_once(void * foo, kmem_cache_t * cachep, unsigned long flags)
+{
+ struct block_device * bdev = (struct block_device *) foo;
+
+ if ((flags & (SLAB_CTOR_VERIFY|SLAB_CTOR_CONSTRUCTOR)) ==
+ SLAB_CTOR_CONSTRUCTOR)
+ {
+ memset(bdev, 0, sizeof(*bdev));
+ sema_init(&bdev->bd_sem, 1);
+ }
+}
+
+void bdev_init(void)
+{
+ int i;
+ struct list_head *head = bdev_hashtable;
+
+ i = HASH_SIZE;
+ do {
+ INIT_LIST_HEAD(head);
+ head++;
+ i--;
+ } while (i);
+
+ bdev_cachep = kmem_cache_create("bdev_cache",
+ sizeof(struct block_device),
+ 0, SLAB_HWCACHE_ALIGN, init_once,
+ NULL);
+ if (!bdev_cachep)
+ panic("cannot create bdev slab cache");
+}
+
+/*
+ * Most likely _very_ bad one - but then it's hardly critical for small
+ * /dev and can be fixed when somebody will need really large one.
+ */
+static inline unsigned long hash(dev_t dev)
+{
+ unsigned long tmp = dev;
+ tmp = tmp + (tmp >> HASH_BITS) + (tmp >> HASH_BITS*2);
+ return tmp & HASH_MASK;
+}
+
+static struct block_device *bdfind(dev_t dev, struct list_head *head)
+{
+ struct list_head *p;
+ struct block_device *bdev;
+ for (p=head->next; p!=head; p=p->next) {
+ bdev = list_entry(p, struct block_device, bd_hash);
+ if (bdev->bd_dev != dev)
+ continue;
+ atomic_inc(&bdev->bd_count);
+ return bdev;
+ }
+ return NULL;
+}
+
+struct block_device *bdget(dev_t dev)
+{
+ struct list_head * head = bdev_hashtable + hash(dev);
+ struct block_device *bdev, *new_bdev;
+ spin_lock(&bdev_lock);
+ bdev = bdfind(dev, head);
+ spin_unlock(&bdev_lock);
+ if (bdev)
+ return bdev;
+ new_bdev = alloc_bdev();
+ if (!new_bdev)
+ return NULL;
+ atomic_set(&new_bdev->bd_count,1);
+ new_bdev->bd_dev = dev;
+ new_bdev->bd_op = NULL;
+ spin_lock(&bdev_lock);
+ bdev = bdfind(dev, head);
+ if (!bdev) {
+ list_add(&new_bdev->bd_hash, head);
+ spin_unlock(&bdev_lock);
+ return new_bdev;
+ }
+ spin_unlock(&bdev_lock);
+ destroy_bdev(new_bdev);
+ return bdev;
+}
+
+void bdput(struct block_device *bdev)
+{
+ if (atomic_dec_and_test(&bdev->bd_count)) {
+ spin_lock(&bdev_lock);
+ if (atomic_read(&bdev->bd_openers))
+ BUG();
+ list_del(&bdev->bd_hash);
+ spin_unlock(&bdev_lock);
+ destroy_bdev(bdev);
+ }
+}
+
+static struct {
+ const char *name;
+ struct block_device_operations *bdops;
+} blkdevs[MAX_BLKDEV] = {
+ { NULL, NULL },
+};
+
+int get_blkdev_list(char * p)
+{
+ int i;
+ int len;
+
+ len = sprintf(p, "\nBlock devices:\n");
+ for (i = 0; i < MAX_BLKDEV ; i++) {
+ if (blkdevs[i].bdops) {
+ len += sprintf(p+len, "%3d %s\n", i, blkdevs[i].name);
+ }
+ }
+ return len;
+}
+
+/*
+ Return the function table of a device.
+ Load the driver if needed.
+*/
+static const struct block_device_operations * get_blkfops(unsigned int major)
+{
+ const struct block_device_operations *ret = NULL;
+
+ /* major 0 is used for non-device mounts */
+ if (major && major < MAX_BLKDEV) {
+#ifdef CONFIG_KMOD
+ if (!blkdevs[major].bdops) {
+ char name[20];
+ sprintf(name, "block-major-%d", major);
+ request_module(name);
+ }
+#endif
+ ret = blkdevs[major].bdops;
+ }
+ return ret;
+}
+
+int register_blkdev(unsigned int major, const char * name, struct block_device_operations *bdops)
+{
+ if (major == 0) {
+ for (major = MAX_BLKDEV-1; major > 0; major--) {
+ if (blkdevs[major].bdops == NULL) {
+ blkdevs[major].name = name;
+ blkdevs[major].bdops = bdops;
+ return major;
+ }
+ }
+ return -EBUSY;
+ }
+ if (major >= MAX_BLKDEV)
+ return -EINVAL;
+ if (blkdevs[major].bdops && blkdevs[major].bdops != bdops)
+ return -EBUSY;
+ blkdevs[major].name = name;
+ blkdevs[major].bdops = bdops;
+ return 0;
+}
+
+int unregister_blkdev(unsigned int major, const char * name)
+{
+ if (major >= MAX_BLKDEV)
+ return -EINVAL;
+ if (!blkdevs[major].bdops)
+ return -EINVAL;
+ if (strcmp(blkdevs[major].name, name))
+ return -EINVAL;
+ blkdevs[major].name = NULL;
+ blkdevs[major].bdops = NULL;
+ return 0;
+}
+
+/*
+ * This routine checks whether a removable media has been changed,
+ * and invalidates all buffer-cache-entries in that case. This
+ * is a relatively slow routine, so we have to try to minimize using
+ * it. Thus it is called only upon a 'mount' or 'open'. This
+ * is the best way of combining speed and utility, I think.
+ * People changing diskettes in the middle of an operation deserve
+ * to lose :-)
+ */
+int check_disk_change(kdev_t dev)
+{
+ int i;
+ const struct block_device_operations * bdops;
+ struct super_block * sb;
+
+ i = MAJOR(dev);
+ if (i >= MAX_BLKDEV || (bdops = blkdevs[i].bdops) == NULL)
+ return 0;
+ if (bdops->check_media_change == NULL)
+ return 0;
+ if (!bdops->check_media_change(dev))
+ return 0;
+
+ printk(KERN_DEBUG "VFS: Disk change detected on device %s\n",
+ bdevname(dev));
+
+ sb = get_super(dev);
+ if (sb && invalidate_inodes(sb))
+ printk("VFS: busy inodes on changed media.\n");
+
+ invalidate_buffers(dev);
+
+ if (bdops->revalidate)
+ bdops->revalidate(dev);
+ return 1;
+}
+
+int ioctl_by_bdev(struct block_device *bdev, unsigned cmd, unsigned long arg)
+{
+ kdev_t rdev = to_kdev_t(bdev->bd_dev);
+ struct inode inode_fake;
+ int res;
+ mm_segment_t old_fs = get_fs();
+
+ if (!bdev->bd_op->ioctl)
+ return -EINVAL;
+ inode_fake.i_rdev=rdev;
+ init_waitqueue_head(&inode_fake.i_wait);
+ set_fs(KERNEL_DS);
+ res = bdev->bd_op->ioctl(&inode_fake, NULL, cmd, arg);
+ set_fs(old_fs);
+ return res;
+}
+
+int blkdev_get(struct block_device *bdev, mode_t mode, unsigned flags, int kind)
+{
+ int ret = -ENODEV;
+ kdev_t rdev = to_kdev_t(bdev->bd_dev); /* this should become bdev */
+ down(&bdev->bd_sem);
+ if (!bdev->bd_op)
+ bdev->bd_op = get_blkfops(MAJOR(rdev));
+ if (bdev->bd_op) {
+ /*
+ * This crockload is due to bad choice of ->open() type.
+ * It will go away.
+ */
+ struct file fake_file = {};
+ struct dentry fake_dentry = {};
+ struct inode *fake_inode = get_empty_inode();
+ ret = -ENOMEM;
+ if (fake_inode) {
+ fake_file.f_mode = mode;
+ fake_file.f_flags = flags;
+ fake_file.f_dentry = &fake_dentry;
+ fake_dentry.d_inode = fake_inode;
+ fake_inode->i_rdev = rdev;
+ ret = 0;
+ if (bdev->bd_op->open)
+ ret = bdev->bd_op->open(fake_inode, &fake_file);
+ if (!ret)
+ atomic_inc(&bdev->bd_openers);
+ iput(fake_inode);
+ }
+ }
+ up(&bdev->bd_sem);
+ return ret;
+}
+
+int blkdev_open(struct inode * inode, struct file * filp)
+{
+ int ret = -ENODEV;
+ struct block_device *bdev = inode->i_bdev;
+ down(&bdev->bd_sem);
+ if (!bdev->bd_op)
+ bdev->bd_op = get_blkfops(MAJOR(inode->i_rdev));
+ if (bdev->bd_op) {
+ ret = 0;
+ if (bdev->bd_op->open)
+ ret = bdev->bd_op->open(inode,filp);
+ if (!ret)
+ atomic_inc(&bdev->bd_openers);
+ }
+ up(&bdev->bd_sem);
+ return ret;
+}
+
+int blkdev_put(struct block_device *bdev, int kind)
+{
+ int ret = 0;
+ kdev_t rdev = to_kdev_t(bdev->bd_dev); /* this should become bdev */
+ down(&bdev->bd_sem);
+ /* syncing will go here */
+ if (atomic_dec_and_test(&bdev->bd_openers)) {
+ /* invalidating buffers will go here */
+ }
+ if (bdev->bd_op->release) {
+ struct inode * fake_inode = get_empty_inode();
+ ret = -ENOMEM;
+ if (fake_inode) {
+ fake_inode->i_rdev = rdev;
+ ret = bdev->bd_op->release(fake_inode, NULL);
+ iput(fake_inode);
+ }
+ }
+ if (!atomic_read(&bdev->bd_openers))
+ bdev->bd_op = NULL; /* we can't rely on driver being */
+ /* kind to stay around. */
+ up(&bdev->bd_sem);
+ return ret;
+}
+
+static int blkdev_close(struct inode * inode, struct file * filp)
+{
+ return blkdev_put(inode->i_bdev, BDEV_FILE);
+}
+
+static int blkdev_ioctl(struct inode *inode, struct file *file, unsigned cmd,
+ unsigned long arg)
+{
+ if (inode->i_bdev->bd_op->ioctl)
+ return inode->i_bdev->bd_op->ioctl(inode, file, cmd, arg);
+ return -EINVAL;
+}
+
+struct file_operations def_blk_fops = {
+ open: blkdev_open,
+ release: blkdev_close,
+ read: block_read,
+ write: block_write,
+ fsync: block_fsync,
+ ioctl: blkdev_ioctl,
+};
+
+struct inode_operations blkdev_inode_operations = {
+ &def_blk_fops, /* default file operations */
+};
+
+char * bdevname(kdev_t dev)
+{
+ static char buffer[32];
+ const char * name = blkdevs[MAJOR(dev)].name;
+
+ if (!name)
+ name = "unknown-block";
+
+ sprintf(buffer, "%s(%d,%d)", name, MAJOR(dev), MINOR(dev));
+ return buffer;
+}