summaryrefslogtreecommitdiff
path: root/fs/char_dev.c
diff options
context:
space:
mode:
Diffstat (limited to 'fs/char_dev.c')
-rw-r--r--fs/char_dev.c208
1 files changed, 153 insertions, 55 deletions
diff --git a/fs/char_dev.c b/fs/char_dev.c
index f20b95221a02..430e1e28b452 100644
--- a/fs/char_dev.c
+++ b/fs/char_dev.c
@@ -17,10 +17,16 @@
#include <linux/smp_lock.h>
#include <linux/devfs_fs_kernel.h>
+#include <linux/kobject.h>
+#include <linux/kobj_map.h>
+#include <linux/cdev.h>
+
#ifdef CONFIG_KMOD
#include <linux/kmod.h>
#endif
+static struct kobj_map *cdev_map;
+
#define MAX_PROBE_HASH 255 /* random */
static rwlock_t chrdevs_lock = RW_LOCK_UNLOCKED;
@@ -32,6 +38,7 @@ static struct char_device_struct {
int minorct;
const char *name;
struct file_operations *fops;
+ struct cdev *cdev; /* will die */
} *chrdevs[MAX_PROBE_HASH];
/* index in the above */
@@ -61,54 +68,22 @@ int get_chrdev_list(char *page)
/*
* Return the function table of a device, if present.
- * Increment the reference count of module in question.
- */
-static struct file_operations *
-lookup_chrfops(unsigned int major, unsigned int minor)
-{
- struct char_device_struct *cd;
- struct file_operations *ret = NULL;
- int i;
-
- i = major_to_index(major);
-
- read_lock(&chrdevs_lock);
- for (cd = chrdevs[i]; cd; cd = cd->next) {
- if (major == cd->major &&
- minor - cd->baseminor < cd->minorct) {
- ret = fops_get(cd->fops);
- break;
- }
- }
- read_unlock(&chrdevs_lock);
-
- return ret;
-}
-
-/*
- * 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(unsigned int major, unsigned int minor)
+static struct file_operations *get_chrfops(dev_t dev)
{
struct file_operations *ret = NULL;
-
- if (!major)
- return NULL;
-
- ret = lookup_chrfops(major, minor);
-
-#ifdef CONFIG_KMOD
- if (!ret) {
- request_module("char-major-%d", major);
-
- read_lock(&chrdevs_lock);
- ret = lookup_chrfops(major, minor);
- read_unlock(&chrdevs_lock);
+ 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);
}
-#endif
return ret;
}
@@ -125,8 +100,7 @@ get_chrfops(unsigned int major, unsigned int minor)
*/
static struct char_device_struct *
__register_chrdev_region(unsigned int major, unsigned int baseminor,
- int minorct, const char *name,
- struct file_operations *fops)
+ int minorct, const char *name)
{
struct char_device_struct *cd, **cp;
int ret = 0;
@@ -157,7 +131,6 @@ __register_chrdev_region(unsigned int major, unsigned int baseminor,
cd->baseminor = baseminor;
cd->minorct = minorct;
cd->name = name;
- cd->fops = fops;
i = major_to_index(major);
@@ -200,8 +173,7 @@ __unregister_chrdev_region(unsigned major, unsigned baseminor, int minorct)
return cd;
}
-int register_chrdev_region(dev_t from, unsigned count, char *name,
- struct file_operations *fops)
+int register_chrdev_region(dev_t from, unsigned count, char *name)
{
struct char_device_struct *cd;
dev_t to = from + count;
@@ -212,7 +184,7 @@ int register_chrdev_region(dev_t from, unsigned count, char *name,
if (next > to)
next = to;
cd = __register_chrdev_region(MAJOR(n), MINOR(n),
- next - n, name, fops);
+ next - n, name);
if (IS_ERR(cd))
goto fail;
}
@@ -226,11 +198,10 @@ fail:
return PTR_ERR(cd);
}
-int alloc_chrdev_region(dev_t *dev, unsigned count, char *name,
- struct file_operations *fops)
+int alloc_chrdev_region(dev_t *dev, unsigned count, char *name)
{
struct char_device_struct *cd;
- cd = __register_chrdev_region(0, 0, count, name, fops);
+ cd = __register_chrdev_region(0, 0, count, name);
if (IS_ERR(cd))
return PTR_ERR(cd);
*dev = MKDEV(cd->major, cd->baseminor);
@@ -241,11 +212,36 @@ int register_chrdev(unsigned int major, const char *name,
struct file_operations *fops)
{
struct char_device_struct *cd;
+ struct cdev *cdev;
+ char *s;
+ int err = -ENOMEM;
- cd = __register_chrdev_region(major, 0, 256, name, fops);
+ cd = __register_chrdev_region(major, 0, 256, name);
if (IS_ERR(cd))
return PTR_ERR(cd);
- return cd->major;
+
+ cdev = cdev_alloc();
+ if (!cdev)
+ goto out2;
+
+ cdev->owner = fops->owner;
+ cdev->ops = fops;
+ strcpy(cdev->kobj.name, name);
+ for (s = strchr(cdev->kobj.name, '/'); s; s = strchr(s, '/'))
+ *s = '!';
+
+ err = cdev_add(cdev, MKDEV(cd->major, 0), 256);
+ if (err)
+ goto out;
+
+ cd->cdev = cdev;
+
+ return major ? 0 : cd->major;
+out:
+ cdev_put(cdev);
+out2:
+ __unregister_chrdev_region(cd->major, 0, 256);
+ return err;
}
void unregister_chrdev_region(dev_t from, unsigned count)
@@ -263,7 +259,12 @@ void unregister_chrdev_region(dev_t from, unsigned count)
int unregister_chrdev(unsigned int major, const char *name)
{
- kfree(__unregister_chrdev_region(major, 0, 256));
+ struct char_device_struct *cd;
+ cdev_unmap(MKDEV(major, 0), 256);
+ cd = __unregister_chrdev_region(major, 0, 256);
+ if (cd && cd->cdev)
+ cdev_del(cd->cdev);
+ kfree(cd);
return 0;
}
@@ -274,7 +275,7 @@ int chrdev_open(struct inode * inode, struct file * filp)
{
int ret = -ENODEV;
- filp->f_op = get_chrfops(major(inode->i_rdev), minor(inode->i_rdev));
+ filp->f_op = get_chrfops(kdev_t_to_nr(inode->i_rdev));
if (filp->f_op) {
ret = 0;
if (filp->f_op->open != NULL) {
@@ -315,3 +316,100 @@ const char *cdevname(kdev_t dev)
return buffer;
}
+
+static struct kobject *exact_match(dev_t dev, int *part, void *data)
+{
+ struct cdev *p = data;
+ return &p->kobj;
+}
+
+static int exact_lock(dev_t dev, void *data)
+{
+ struct cdev *p = data;
+ return cdev_get(p) ? 0 : -1;
+}
+
+int cdev_add(struct cdev *p, dev_t dev, unsigned count)
+{
+ int err = kobject_add(&p->kobj);
+ if (err)
+ return err;
+ return kobj_map(cdev_map, dev, count, NULL, exact_match, exact_lock, p);
+}
+
+void cdev_unmap(dev_t dev, unsigned count)
+{
+ kobj_unmap(cdev_map, dev, count);
+}
+
+void cdev_del(struct cdev *p)
+{
+ kobject_del(&p->kobj);
+ cdev_put(p);
+}
+
+struct kobject *cdev_get(struct cdev *p)
+{
+ struct module *owner = p->owner;
+ struct kobject *kobj;
+
+ if (owner && !try_module_get(owner))
+ return NULL;
+ kobj = kobject_get(&p->kobj);
+ if (!kobj)
+ module_put(owner);
+ return kobj;
+}
+
+static decl_subsys(cdev, NULL, NULL);
+
+static void cdev_dynamic_release(struct kobject *kobj)
+{
+ struct cdev *p = container_of(kobj, struct cdev, kobj);
+ kfree(p);
+}
+
+static struct kobj_type ktype_cdev_dynamic = {
+ .release = cdev_dynamic_release,
+};
+
+static struct kset kset_dynamic = {
+ .subsys = &cdev_subsys,
+ .kobj = {.name = "major",},
+ .ktype = &ktype_cdev_dynamic,
+};
+
+struct cdev *cdev_alloc(void)
+{
+ struct cdev *p = kmalloc(sizeof(struct cdev), GFP_KERNEL);
+ if (p) {
+ memset(p, 0, sizeof(struct cdev));
+ p->kobj.kset = &kset_dynamic;
+ kobject_init(&p->kobj);
+ }
+ return p;
+}
+
+void cdev_init(struct cdev *cdev, struct file_operations *fops)
+{
+ kobj_set_kset_s(cdev, cdev_subsys);
+ kobject_init(&cdev->kobj);
+ cdev->ops = fops;
+}
+
+static struct kobject *base_probe(dev_t dev, int *part, void *data)
+{
+ char name[30];
+ sprintf(name, "char-major-%d", MAJOR(dev));
+ request_module(name);
+ return NULL;
+}
+
+static int __init chrdev_init(void)
+{
+ subsystem_register(&cdev_subsys);
+ kset_register(&kset_dynamic);
+ cdev_map = kobj_map_init(base_probe, &cdev_subsys);
+ return 0;
+}
+subsys_initcall(chrdev_init);