diff options
author | Ralf Baechle <ralf@linux-mips.org> | 2000-02-05 06:47:02 +0000 |
---|---|---|
committer | Ralf Baechle <ralf@linux-mips.org> | 2000-02-05 06:47:02 +0000 |
commit | 99a7e12f34b3661a0d1354eef83a0eef4df5e34c (patch) | |
tree | 3560aca9ca86792f9ab7bd87861ea143a1b3c7a3 /fs/block_dev.c | |
parent | e73a04659c0b8cdee4dd40e58630e2cf63afb316 (diff) |
Merge with Linux 2.3.38.
Diffstat (limited to 'fs/block_dev.c')
-rw-r--r-- | fs/block_dev.c | 361 |
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; +} |