Commit 9bda5f68 authored by Alexander Viro's avatar Alexander Viro Committed by Linus Torvalds

[PATCH] i_cdev/i_cindex

new fields in struct inode - i_cdev and i_cindex.  When we do open() on
a character device we cache result of cdev lookup in inode and put the
inode on a cyclic list anchored in cdev.  If we already have that done,
we don't bother with any lookups.  When inode disappears it's removed
from the list.  When cdev gets unregistered we remove all cached
references to it (and remove such inodes from the list).  cdev is held
until final fput() now.
parent 787d458a
......@@ -2279,7 +2279,7 @@ int tty_register_driver(struct tty_driver *driver)
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;
}
......
......@@ -66,27 +66,6 @@ int get_chrdev_list(char *page)
return len;
}
/*
* 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.
*
......@@ -238,7 +217,7 @@ int register_chrdev(unsigned int major, const char *name,
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, const char *name)
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;
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();
}
struct cdev *p;
struct cdev *new = NULL;
int ret = 0;
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 count)
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;
}
......
......@@ -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);
......
......@@ -128,6 +128,7 @@ static struct inode *alloc_inode(struct super_block *sb)
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;
}
......
......@@ -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
......@@ -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 */
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment