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
- Lines: 361
- Date:
Thu Jan 6 10:14:36 2000
- Orig file:
v2.3.36/linux/fs/block_dev.c
- Orig date:
Tue Jun 22 10:45:40 1999
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)