From: Alexander Viro <viro@math.psu.edu> To: Linus Torvalds <torvalds@transmeta.com> Subject: [PATCH] struct char_device Date: Tue, 22 May 2001 10:18:28 -0400 (EDT) Cc: linux-kernel@vger.kernel.org Linus, patch below adds the missing half of kdev_t - for block devices we already have a unique pointer (struct block_device *, inode->i_bdev) and that adds a similar animal for character devices. That is, it adds a new structure (struct char_device) and a cache indexed by dev_t. init_special_inode() sets ->i_cdev to corresponding element of cache (creating it if needed). Result: ->i_cdev is shared by all inodes of given character device (i.e. if we need per-device objects we can put them there), we can use stuct char_device * as ID for character devices, we can (in 2.5) get rid of i_rdev - it's covered by ->i_bdev and ->i_cdev now. Patch is pretty straightforward - cache handling is lifted from fs/block_device, the rest is trivial. Please, consider applying. Al diff -urN S5-pre4/fs/Makefile S5-pre4-cdev/fs/Makefile --- S5-pre4/fs/Makefile Thu May 3 17:13:26 2001 +++ S5-pre4-cdev/fs/Makefile Tue May 22 09:12:11 2001 @@ -11,8 +11,8 @@ mod-subdirs := nls obj-y := open.o read_write.o devices.o file_table.o buffer.o \ - super.o block_dev.o stat.o exec.o pipe.o namei.o fcntl.o \ - ioctl.o readdir.o select.o fifo.o locks.o \ + super.o block_dev.o char_dev.o stat.o exec.o pipe.o namei.o \ + fcntl.o ioctl.o readdir.o select.o fifo.o locks.o \ dcache.o inode.o attr.o bad_inode.o file.o iobuf.o dnotify.o \ filesystems.o diff -urN S5-pre4/fs/block_dev.c S5-pre4-cdev/fs/block_dev.c --- S5-pre4/fs/block_dev.c Sat May 19 22:46:35 2001 +++ S5-pre4-cdev/fs/block_dev.c Tue May 22 08:34:44 2001 @@ -392,7 +392,7 @@ } } -void __init bdev_init(void) +void __init bdev_cache_init(void) { int i; struct list_head *head = bdev_hashtable; diff -urN S5-pre4/fs/char_dev.c S5-pre4-cdev/fs/char_dev.c --- S5-pre4/fs/char_dev.c Wed Dec 31 19:00:00 1969 +++ S5-pre4-cdev/fs/char_dev.c Tue May 22 10:03:10 2001 @@ -0,0 +1,114 @@ +/* + * linux/fs/block_dev.c + * + * Copyright (C) 1991, 1992 Linus Torvalds + */ + +#include <linux/config.h> +#include <linux/init.h> +#include <linux/slab.h> + +#define HASH_BITS 6 +#define HASH_SIZE (1UL << HASH_BITS) +#define HASH_MASK (HASH_SIZE-1) +static struct list_head cdev_hashtable[HASH_SIZE]; +static spinlock_t cdev_lock = SPIN_LOCK_UNLOCKED; +static kmem_cache_t * cdev_cachep; + +#define alloc_cdev() \ + ((struct char_device *) kmem_cache_alloc(cdev_cachep, SLAB_KERNEL)) +#define destroy_cdev(cdev) kmem_cache_free(cdev_cachep, (cdev)) + +static void init_once(void * foo, kmem_cache_t * cachep, unsigned long flags) +{ + struct char_device * cdev = (struct char_device *) foo; + + if ((flags & (SLAB_CTOR_VERIFY|SLAB_CTOR_CONSTRUCTOR)) == + SLAB_CTOR_CONSTRUCTOR) + { + memset(cdev, 0, sizeof(*cdev)); + sema_init(&cdev->sem, 1); + } +} + +void __init cdev_cache_init(void) +{ + int i; + struct list_head *head = cdev_hashtable; + + i = HASH_SIZE; + do { + INIT_LIST_HEAD(head); + head++; + i--; + } while (i); + + cdev_cachep = kmem_cache_create("cdev_cache", + sizeof(struct char_device), + 0, SLAB_HWCACHE_ALIGN, init_once, + NULL); + if (!cdev_cachep) + panic("Cannot create cdev_cache 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 char_device *cdfind(dev_t dev, struct list_head *head) +{ + struct list_head *p; + struct char_device *cdev; + for (p=head->next; p!=head; p=p->next) { + cdev = list_entry(p, struct char_device, hash); + if (cdev->dev != dev) + continue; + atomic_inc(&cdev->count); + return cdev; + } + return NULL; +} + +struct char_device *cdget(dev_t dev) +{ + struct list_head * head = cdev_hashtable + hash(dev); + struct char_device *cdev, *new_cdev; + spin_lock(&cdev_lock); + cdev = cdfind(dev, head); + spin_unlock(&cdev_lock); + if (cdev) + return cdev; + new_cdev = alloc_cdev(); + if (!new_cdev) + return NULL; + atomic_set(&new_cdev->count,1); + new_cdev->dev = dev; + spin_lock(&cdev_lock); + cdev = cdfind(dev, head); + if (!cdev) { + list_add(&new_cdev->hash, head); + spin_unlock(&cdev_lock); + return new_cdev; + } + spin_unlock(&cdev_lock); + destroy_cdev(new_cdev); + return cdev; +} + +void cdput(struct char_device *cdev) +{ + if (atomic_dec_and_test(&cdev->count)) { + spin_lock(&cdev_lock); + list_del(&cdev->hash); + spin_unlock(&cdev_lock); + destroy_cdev(cdev); + } +} + diff -urN S5-pre4/fs/dcache.c S5-pre4-cdev/fs/dcache.c --- S5-pre4/fs/dcache.c Sat Apr 28 02:12:56 2001 +++ S5-pre4-cdev/fs/dcache.c Tue May 22 09:22:43 2001 @@ -1250,6 +1250,9 @@ kmem_cache_t *bh_cachep; EXPORT_SYMBOL(bh_cachep); +extern void bdev_cache_init(void); +extern void cdev_cache_init(void); + void __init vfs_caches_init(unsigned long mempages) { bh_cachep = kmem_cache_create("buffer_head", @@ -1279,4 +1282,7 @@ #endif dcache_init(mempages); + inode_init(mempages); + bdev_cache_init(); + cdev_cache_init(); } diff -urN S5-pre4/fs/devfs/base.c S5-pre4-cdev/fs/devfs/base.c --- S5-pre4/fs/devfs/base.c Sat May 19 22:46:35 2001 +++ S5-pre4-cdev/fs/devfs/base.c Tue May 22 08:51:03 2001 @@ -2256,6 +2256,7 @@ { inode->i_rdev = MKDEV (de->u.fcb.u.device.major, de->u.fcb.u.device.minor); + inode->i_cdev = cdget (kdev_t_to_nr(inode->i_rdev)); } else if ( S_ISBLK (de->inode.mode) ) { diff -urN S5-pre4/fs/devices.c S5-pre4-cdev/fs/devices.c --- S5-pre4/fs/devices.c Fri Feb 16 19:00:19 2001 +++ S5-pre4-cdev/fs/devices.c Tue May 22 08:31:04 2001 @@ -203,6 +203,7 @@ if (S_ISCHR(mode)) { inode->i_fop = &def_chr_fops; inode->i_rdev = to_kdev_t(rdev); + inode->i_cdev = cdget(rdev); } else if (S_ISBLK(mode)) { inode->i_fop = &def_blk_fops; inode->i_rdev = to_kdev_t(rdev); diff -urN S5-pre4/fs/inode.c S5-pre4-cdev/fs/inode.c --- S5-pre4/fs/inode.c Sat May 19 22:46:35 2001 +++ S5-pre4-cdev/fs/inode.c Mon May 21 22:49:48 2001 @@ -497,6 +497,10 @@ bdput(inode->i_bdev); inode->i_bdev = NULL; } + if (inode->i_cdev) { + cdput(inode->i_cdev); + inode->i_cdev = NULL; + } inode->i_state = I_CLEAR; } @@ -750,6 +754,7 @@ memset(&inode->i_dquot, 0, sizeof(inode->i_dquot)); inode->i_pipe = NULL; inode->i_bdev = NULL; + inode->i_cdev = NULL; inode->i_data.a_ops = &empty_aops; inode->i_data.host = inode; inode->i_data.gfp_mask = GFP_HIGHUSER; diff -urN S5-pre4/include/linux/fs.h S5-pre4-cdev/include/linux/fs.h --- S5-pre4/include/linux/fs.h Sat May 19 22:46:36 2001 +++ S5-pre4-cdev/include/linux/fs.h Tue May 22 09:14:25 2001 @@ -384,6 +384,14 @@ int gfp_mask; /* how to allocate the pages */ }; +struct char_device { + struct list_head hash; + atomic_t count; + dev_t dev; + atomic_t openers; + struct semaphore sem; +}; + struct block_device { struct list_head bd_hash; atomic_t bd_count; @@ -426,8 +434,10 @@ struct address_space *i_mapping; struct address_space i_data; struct dquot *i_dquot[MAXQUOTAS]; + /* These three should probably be a union */ struct pipe_inode_info *i_pipe; struct block_device *i_bdev; + struct char_device *i_cdev; unsigned long i_dnotify_mask; /* Directory notify events */ struct dnotify_struct *i_dnotify; /* for directory notifications */ @@ -982,6 +992,8 @@ extern int unregister_blkdev(unsigned int, const char *); extern struct block_device *bdget(dev_t); extern void bdput(struct block_device *); +extern struct char_device *cdget(dev_t); +extern void cdput(struct char_device *); extern int blkdev_open(struct inode *, struct file *); extern struct file_operations def_blk_fops; extern struct file_operations def_fifo_fops; diff -urN S5-pre4/init/main.c S5-pre4-cdev/init/main.c --- S5-pre4/init/main.c Sat May 19 22:46:36 2001 +++ S5-pre4-cdev/init/main.c Tue May 22 08:34:09 2001 @@ -93,7 +93,6 @@ extern void ppc_init(void); extern void sysctl_init(void); extern void signals_init(void); -extern void bdev_init(void); extern int init_pcmcia_ds(void); extern void net_notifier_init(void); @@ -569,8 +568,6 @@ ccwcache_init(); #endif signals_init(); - bdev_init(); - inode_init(mempages); #ifdef CONFIG_PROC_FS proc_root_init(); #endif diff -urN S5-pre4/kernel/ksyms.c S5-pre4-cdev/kernel/ksyms.c --- S5-pre4/kernel/ksyms.c Sat May 19 22:46:37 2001 +++ S5-pre4-cdev/kernel/ksyms.c Tue May 22 09:06:47 2001 @@ -186,6 +186,8 @@ EXPORT_SYMBOL(notify_change); EXPORT_SYMBOL(set_blocksize); EXPORT_SYMBOL(getblk); +EXPORT_SYMBOL(cdget); +EXPORT_SYMBOL(cdput); EXPORT_SYMBOL(bdget); EXPORT_SYMBOL(bdput); EXPORT_SYMBOL(bread); - To unsubscribe from this list: send the line "unsubscribe linux-kernel" in the body of a message to majordomo@vger.kernel.org More majordomo info at http://vger.kernel.org/majordomo-info.html Please read the FAQ at http://www.tux.org/lkml/