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 #include #include - +#include /* 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 */ _