patch-2.3.37 linux/fs/block_dev.c

Next file: linux/fs/devices.c
Previous file: linux/drivers/video/vga16fb.c
Back to the patch index
Back to the overall index

diff -u --recursive --new-file v2.3.36/linux/fs/block_dev.c linux/fs/block_dev.c
@@ -7,6 +7,8 @@
 #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 +301,351 @@
 {
 	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;
+	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 file_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.
+*/
+struct file_operations * get_blkfops(unsigned int major)
+{
+	const struct file_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 file_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 file_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 file_operations *fops = get_blkfops(MAJOR(rdev));
+	struct inode inode_fake;
+	int res;
+	mm_segment_t old_fs = get_fs();
+
+	if (!fops || !fops->ioctl)
+		return -EINVAL;
+	inode_fake.i_rdev=rdev;
+	init_waitqueue_head(&inode_fake.i_wait);
+	set_fs(KERNEL_DS);
+	res = fops->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 */
+	struct file_operations *fops = get_blkfops(MAJOR(rdev));
+	down(&bdev->bd_sem);
+	if (fops) {
+		/*
+		 * 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 (fops->open)
+				ret = fops->open(fake_inode, &fake_file);
+			if (!ret)
+				atomic_inc(&bdev->bd_openers);
+			iput(fake_inode);
+		}
+	}
+	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 */
+	struct file_operations *fops = get_blkfops(MAJOR(rdev));
+	down(&bdev->bd_sem);
+	/* syncing will go here */
+	if (atomic_dec_and_test(&bdev->bd_openers)) {
+		/* invalidating buffers will go here */
+	}
+	if (fops->release) {
+		struct inode * fake_inode = get_empty_inode();
+		ret = -ENOMEM;
+		if (fake_inode) {
+			fake_inode->i_rdev = rdev;
+			ret = fops->release(fake_inode, NULL);
+			iput(fake_inode);
+		}
+	}
+	up(&bdev->bd_sem);
+	return ret;
+}
+
+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;
+}
+
+/*
+ * Called every time a block special file is opened
+ */
+int blkdev_open(struct inode * inode, struct file * filp)
+{
+	int ret = -ENODEV;
+	filp->f_op = get_blkfops(MAJOR(inode->i_rdev));
+	if (filp->f_op != NULL){
+		ret = 0;
+		if (filp->f_op->open != NULL)
+			ret = filp->f_op->open(inode,filp);
+	}	
+	return ret;
+}	
+
+/*
+ * Dummy default file-operations: the only thing this does
+ * is contain the open that then fills in the correct operations
+ * depending on the special file...
+ */
+struct file_operations def_blk_fops = {
+	NULL,		/* lseek */
+	NULL,		/* read */
+	NULL,		/* write */
+	NULL,		/* readdir */
+	NULL,		/* poll */
+	NULL,		/* ioctl */
+	NULL,		/* mmap */
+	blkdev_open,	/* open */
+	NULL,		/* flush */
+	NULL,		/* release */
+};
+
+struct inode_operations blkdev_inode_operations = {
+	&def_blk_fops,		/* default file operations */
+};

FUNET's LINUX-ADM group, linux-adm@nic.funet.fi
TCL-scripts by Sam Shen (who was at: slshen@lbl.gov)