drivers/char/tty_io.c |    2 
 fs/char_dev.c         |  116 ++++++++++++++++++++++++++++++++++++--------------
 fs/file_table.c       |    4 +
 fs/inode.c            |    3 +
 fs/open.c             |    4 -
 include/linux/cdev.h  |    9 +--
 include/linux/fs.h    |    2 
 7 files changed, 99 insertions(+), 41 deletions(-)

diff -puN drivers/char/tty_io.c~T31-i_cdev-C69 drivers/char/tty_io.c
--- 25/drivers/char/tty_io.c~T31-i_cdev-C69	2003-05-05 22:41:38.000000000 -0700
+++ 25-akpm/drivers/char/tty_io.c	2003-05-05 22:41:38.000000000 -0700
@@ -2167,7 +2167,7 @@ int tty_register_driver(struct tty_drive
 	driver->cdev.owner = driver->owner;
 	error = cdev_add(&driver->cdev, dev, driver->num);
 	if (error) {
-		cdev_put(&driver->cdev);
+		kobject_del(&driver->cdev.kobj);
 		unregister_chrdev_region(dev, driver->num);
 		return error;
 	}
diff -puN fs/char_dev.c~T31-i_cdev-C69 fs/char_dev.c
--- 25/fs/char_dev.c~T31-i_cdev-C69	2003-05-05 22:41:38.000000000 -0700
+++ 25-akpm/fs/char_dev.c	2003-05-05 22:41:38.000000000 -0700
@@ -67,27 +67,6 @@ int get_chrdev_list(char *page)
 }
 
 /*
- * Return the function table of a device, if present.
- * Load the driver if needed.
- * Increment the reference count of module in question.
- */
-static struct file_operations *get_chrfops(dev_t dev)
-{
-	struct file_operations *ret = NULL;
-	int index;
-	struct kobject *kobj = kobj_lookup(cdev_map, dev, &index);
-
-	if (kobj) {
-		struct cdev *p = container_of(kobj, struct cdev, kobj);
-		struct module *owner = p->owner;
-		ret = fops_get(p->ops);
-		cdev_put(p);
-		module_put(owner);
-	}
-	return ret;
-}
-
-/*
  * Register a single major with a specified minor range.
  *
  * If major == 0 this functions will dynamically allocate a major and return
@@ -238,7 +217,7 @@ int register_chrdev(unsigned int major, 
 
 	return major ? 0 : cd->major;
 out:
-	cdev_put(cdev);
+	kobject_put(&cdev->kobj);
 out2:
 	__unregister_chrdev_region(cd->major, 0, 256);
 	return err;
@@ -268,25 +247,76 @@ int unregister_chrdev(unsigned int major
 	return 0;
 }
 
+static spinlock_t cdev_lock = SPIN_LOCK_UNLOCKED;
 /*
  * Called every time a character special file is opened
  */
 int chrdev_open(struct inode * inode, struct file * filp)
 {
-	int ret = -ENODEV;
+	struct cdev *p;
+	struct cdev *new = NULL;
+	int ret = 0;
 
-	filp->f_op = get_chrfops(kdev_t_to_nr(inode->i_rdev));
-	if (filp->f_op) {
-		ret = 0;
-		if (filp->f_op->open != NULL) {
-			lock_kernel();
-			ret = filp->f_op->open(inode,filp);
-			unlock_kernel();
-		}
+	spin_lock(&cdev_lock);
+	p = inode->i_cdev;
+	if (!p) {
+		struct kobject *kobj;
+		int idx;
+		spin_unlock(&cdev_lock);
+		kobj = kobj_lookup(cdev_map, kdev_t_to_nr(inode->i_rdev), &idx);
+		if (!kobj)
+			return -ENODEV;
+		new = container_of(kobj, struct cdev, kobj);
+		spin_lock(&cdev_lock);
+		p = inode->i_cdev;
+		if (!p) {
+			inode->i_cdev = p = new;
+			inode->i_cindex = idx;
+			list_add(&inode->i_devices, &p->list);
+			new = NULL;
+		} else if (!cdev_get(p))
+			ret = -ENODEV;
+	} else if (!cdev_get(p))
+		ret = -ENODEV;
+	spin_unlock(&cdev_lock);
+	cdev_put(new);
+	if (ret)
+		return ret;
+	filp->f_op = fops_get(p->ops);
+	if (!filp->f_op) {
+		cdev_put(p);
+		return -ENODEV;
 	}
+	if (filp->f_op->open) {
+		lock_kernel();
+		ret = filp->f_op->open(inode,filp);
+		unlock_kernel();
+	}
+	if (ret)
+		cdev_put(p);
 	return ret;
 }
 
+void cd_forget(struct inode *inode)
+{
+	spin_lock(&cdev_lock);
+	list_del_init(&inode->i_devices);
+	inode->i_cdev = NULL;
+	spin_unlock(&cdev_lock);
+}
+
+void cdev_purge(struct cdev *cdev)
+{
+	spin_lock(&cdev_lock);
+	while (!list_empty(&cdev->list)) {
+		struct inode *inode;
+		inode = container_of(cdev->list.next, struct inode, i_devices);
+		list_del_init(&inode->i_devices);
+		inode->i_cdev = NULL;
+	}
+	spin_unlock(&cdev_lock);
+}
+
 /*
  * Dummy default file-operations: the only thing this does
  * is contain the open that then fills in the correct operations
@@ -345,7 +375,7 @@ void cdev_unmap(dev_t dev, unsigned coun
 void cdev_del(struct cdev *p)
 {
 	kobject_del(&p->kobj);
-	cdev_put(p);
+	kobject_put(&p->kobj);
 }
 
 struct kobject *cdev_get(struct cdev *p)
@@ -361,14 +391,33 @@ struct kobject *cdev_get(struct cdev *p)
 	return kobj;
 }
 
+void cdev_put(struct cdev *p)
+{
+	if (p) {
+		kobject_put(&p->kobj);
+		module_put(p->owner);
+	}
+}
+
 static decl_subsys(cdev, NULL, NULL);
 
+static void cdev_default_release(struct kobject *kobj)
+{
+	struct cdev *p = container_of(kobj, struct cdev, kobj);
+	cdev_purge(p);
+}
+
 static void cdev_dynamic_release(struct kobject *kobj)
 {
 	struct cdev *p = container_of(kobj, struct cdev, kobj);
+	cdev_purge(p);
 	kfree(p);
 }
 
+static struct kobj_type ktype_cdev_default = {
+	.release	= cdev_default_release,
+};
+
 static struct kobj_type ktype_cdev_dynamic = {
 	.release	= cdev_dynamic_release,
 };
@@ -385,6 +434,7 @@ struct cdev *cdev_alloc(void)
 	if (p) {
 		memset(p, 0, sizeof(struct cdev));
 		p->kobj.kset = &kset_dynamic;
+		INIT_LIST_HEAD(&p->list);
 		kobject_init(&p->kobj);
 	}
 	return p;
@@ -392,7 +442,9 @@ struct cdev *cdev_alloc(void)
 
 void cdev_init(struct cdev *cdev, struct file_operations *fops)
 {
+	INIT_LIST_HEAD(&cdev->list);
 	kobj_set_kset_s(cdev, cdev_subsys);
+	cdev->kobj.ktype = &ktype_cdev_default;
 	kobject_init(&cdev->kobj);
 	cdev->ops = fops;
 }
diff -puN fs/file_table.c~T31-i_cdev-C69 fs/file_table.c
--- 25/fs/file_table.c~T31-i_cdev-C69	2003-05-05 22:41:38.000000000 -0700
+++ 25-akpm/fs/file_table.c	2003-05-05 22:41:38.000000000 -0700
@@ -15,7 +15,7 @@
 #include <linux/security.h>
 #include <linux/eventpoll.h>
 #include <linux/mount.h>
-
+#include <linux/cdev.h>
 
 /* sysctl tunables... */
 struct files_stat_struct files_stat = {
@@ -166,6 +166,8 @@ void __fput(struct file *file)
 	if (file->f_op && file->f_op->release)
 		file->f_op->release(inode, file);
 	security_file_free(file);
+	if (unlikely(inode->i_cdev != NULL))
+		cdev_put(inode->i_cdev);
 	fops_put(file->f_op);
 	if (file->f_mode & FMODE_WRITE)
 		put_write_access(inode);
diff -puN fs/inode.c~T31-i_cdev-C69 fs/inode.c
--- 25/fs/inode.c~T31-i_cdev-C69	2003-05-05 22:41:38.000000000 -0700
+++ 25-akpm/fs/inode.c	2003-05-05 22:41:38.000000000 -0700
@@ -128,6 +128,7 @@ static struct inode *alloc_inode(struct 
 		memset(&inode->i_dquot, 0, sizeof(inode->i_dquot));
 		inode->i_pipe = NULL;
 		inode->i_bdev = NULL;
+		inode->i_cdev = NULL;
 		inode->i_rdev = to_kdev_t(0);
 		inode->i_security = NULL;
 		if (security_inode_alloc(inode)) {
@@ -241,6 +242,8 @@ void clear_inode(struct inode *inode)
 		inode->i_sb->s_op->clear_inode(inode);
 	if (inode->i_bdev)
 		bd_forget(inode);
+	if (inode->i_cdev)
+		cd_forget(inode);
 	inode->i_state = I_CLEAR;
 }
 
diff -puN fs/open.c~T31-i_cdev-C69 fs/open.c
--- 25/fs/open.c~T31-i_cdev-C69	2003-05-05 22:41:38.000000000 -0700
+++ 25-akpm/fs/open.c	2003-05-05 22:41:38.000000000 -0700
@@ -671,8 +671,8 @@ struct file *dentry_open(struct dentry *
 	if (f->f_flags & O_DIRECT) {
 		if (!inode->i_mapping || !inode->i_mapping->a_ops ||
 			!inode->i_mapping->a_ops->direct_IO) {
-				error = -EINVAL;
-				goto cleanup_all;
+				fput(f);
+				return ERR_PTR(-EINVAL);
 		}
 	}
 
diff -puN include/linux/cdev.h~T31-i_cdev-C69 include/linux/cdev.h
--- 25/include/linux/cdev.h~T31-i_cdev-C69	2003-05-05 22:41:38.000000000 -0700
+++ 25-akpm/include/linux/cdev.h	2003-05-05 22:41:38.000000000 -0700
@@ -6,17 +6,14 @@ struct cdev {
 	struct kobject kobj;
 	struct module *owner;
 	struct file_operations *ops;
+	struct list_head list;
 };
 
 void cdev_init(struct cdev *, struct file_operations *);
 
 struct cdev *cdev_alloc(void);
 
-static inline void cdev_put(struct cdev *p)
-{
-	if (p)
-		kobject_put(&p->kobj);
-}
+void cdev_put(struct cdev *p);
 
 struct kobject *cdev_get(struct cdev *);
 
@@ -26,5 +23,7 @@ void cdev_del(struct cdev *);
 
 void cdev_unmap(dev_t, unsigned);
 
+void cd_forget(struct inode *);
+
 #endif
 #endif
diff -puN include/linux/fs.h~T31-i_cdev-C69 include/linux/fs.h
--- 25/include/linux/fs.h~T31-i_cdev-C69	2003-05-05 22:41:38.000000000 -0700
+++ 25-akpm/include/linux/fs.h	2003-05-05 22:41:38.000000000 -0700
@@ -382,6 +382,8 @@ struct inode {
 	struct list_head	i_devices;
 	struct pipe_inode_info	*i_pipe;
 	struct block_device	*i_bdev;
+	struct cdev		*i_cdev;
+	int			i_cindex;
 
 	unsigned long		i_dnotify_mask; /* Directory notify events */
 	struct dnotify_struct	*i_dnotify; /* for directory notifications */

_