summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--drivers/char/tty_io.c58
-rw-r--r--fs/char_dev.c208
-rw-r--r--include/linux/cdev.h30
-rw-r--r--include/linux/fs.h6
-rw-r--r--include/linux/tty_driver.h2
5 files changed, 234 insertions, 70 deletions
diff --git a/drivers/char/tty_io.c b/drivers/char/tty_io.c
index ec5bd80e34ff..6d698dee57d8 100644
--- a/drivers/char/tty_io.c
+++ b/drivers/char/tty_io.c
@@ -2242,6 +2242,7 @@ void tty_unregister_device(struct tty_driver *driver, unsigned index)
EXPORT_SYMBOL(tty_register_device);
EXPORT_SYMBOL(tty_unregister_device);
+static struct kobject tty_kobj = {.name = "tty"};
/*
* Called by a tty driver to register itself.
*/
@@ -2250,13 +2251,14 @@ int tty_register_driver(struct tty_driver *driver)
int error;
int i;
dev_t dev;
+ char *s;
if (driver->flags & TTY_DRIVER_INSTALLED)
return 0;
if (!driver->major) {
error = alloc_chrdev_region(&dev, driver->num,
- (char*)driver->name, &tty_fops);
+ (char*)driver->name);
if (!error) {
driver->major = MAJOR(dev);
driver->minor_start = MINOR(dev);
@@ -2264,11 +2266,24 @@ int tty_register_driver(struct tty_driver *driver)
} else {
dev = MKDEV(driver->major, driver->minor_start);
error = register_chrdev_region(dev, driver->num,
- (char*)driver->name, &tty_fops);
+ (char*)driver->name);
}
if (error < 0)
return error;
+ driver->cdev.kobj.parent = &tty_kobj;
+ strcpy(driver->cdev.kobj.name, driver->name);
+ for (s = strchr(driver->cdev.kobj.name, '/'); s; s = strchr(s, '/'))
+ *s = '!';
+ cdev_init(&driver->cdev, &tty_fops);
+ driver->cdev.owner = driver->owner;
+ error = cdev_add(&driver->cdev, dev, driver->num);
+ if (error) {
+ cdev_put(&driver->cdev);
+ unregister_chrdev_region(dev, driver->num);
+ return error;
+ }
+
if (!driver->put_char)
driver->put_char = tty_default_put_char;
@@ -2279,7 +2294,7 @@ int tty_register_driver(struct tty_driver *driver)
tty_register_device(driver, i, NULL);
}
proc_tty_register_driver(driver);
- return error;
+ return 0;
}
/*
@@ -2293,6 +2308,7 @@ int tty_unregister_driver(struct tty_driver *driver)
if (*driver->refcount)
return -EBUSY;
+ cdev_unmap(MKDEV(driver->major, driver->minor_start), driver->num);
unregister_chrdev_region(MKDEV(driver->major, driver->minor_start),
driver->num);
@@ -2318,6 +2334,7 @@ int tty_unregister_driver(struct tty_driver *driver)
tty_unregister_device(driver, i);
}
proc_tty_unregister_driver(driver);
+ cdev_del(&driver->cdev);
return 0;
}
@@ -2364,6 +2381,14 @@ static int __init tty_class_init(void)
}
postcore_initcall(tty_class_init);
+
+static struct cdev tty_cdev, console_cdev;
+#ifdef CONFIG_UNIX98_PTYS
+static struct cdev ptmx_cdev;
+#endif
+#ifdef CONFIG_VT
+static struct cdev vc0_cdev;
+#endif
/*
* Ok, now we can initialize the rest of the tty devices and can count
@@ -2371,29 +2396,40 @@ postcore_initcall(tty_class_init);
*/
void __init tty_init(void)
{
- if (register_chrdev_region(MKDEV(TTYAUX_MAJOR, 0), 1,
- "/dev/tty", &tty_fops) < 0)
+ strcpy(tty_cdev.kobj.name, "dev.tty");
+ cdev_init(&tty_cdev, &tty_fops);
+ if (cdev_add(&tty_cdev, MKDEV(TTYAUX_MAJOR, 0), 1) ||
+ register_chrdev_region(MKDEV(TTYAUX_MAJOR, 0), 1, "/dev/tty") < 0)
panic("Couldn't register /dev/tty driver\n");
devfs_mk_cdev(MKDEV(TTYAUX_MAJOR, 0), S_IFCHR|S_IRUGO|S_IWUGO, "tty");
tty_add_class_device ("tty", MKDEV(TTYAUX_MAJOR, 0), NULL);
- if (register_chrdev_region(MKDEV(TTYAUX_MAJOR, 1), 1,
- "/dev/console", &tty_fops) < 0)
+ strcpy(console_cdev.kobj.name, "dev.console");
+ cdev_init(&console_cdev, &tty_fops);
+ if (cdev_add(&console_cdev, MKDEV(TTYAUX_MAJOR, 1), 1) ||
+ register_chrdev_region(MKDEV(TTYAUX_MAJOR, 1), 1, "/dev/console") < 0)
panic("Couldn't register /dev/console driver\n");
devfs_mk_cdev(MKDEV(TTYAUX_MAJOR, 1), S_IFCHR|S_IRUSR|S_IWUSR, "console");
tty_add_class_device ("console", MKDEV(TTYAUX_MAJOR, 1), NULL);
+ tty_kobj.kset = tty_cdev.kobj.kset;
+ kobject_register(&tty_kobj);
+
#ifdef CONFIG_UNIX98_PTYS
- if (register_chrdev_region(MKDEV(TTYAUX_MAJOR, 2), 1,
- "/dev/ptmx", &tty_fops) < 0)
+ strcpy(ptmx_cdev.kobj.name, "dev.ptmx");
+ cdev_init(&ptmx_cdev, &tty_fops);
+ if (cdev_add(&ptmx_cdev, MKDEV(TTYAUX_MAJOR, 2), 1) ||
+ register_chrdev_region(MKDEV(TTYAUX_MAJOR, 2), 1, "/dev/ptmx") < 0)
panic("Couldn't register /dev/ptmx driver\n");
devfs_mk_cdev(MKDEV(TTYAUX_MAJOR, 2), S_IFCHR|S_IRUGO|S_IWUGO, "ptmx");
tty_add_class_device ("ptmx", MKDEV(TTYAUX_MAJOR, 2), NULL);
#endif
#ifdef CONFIG_VT
- if (register_chrdev_region(MKDEV(TTY_MAJOR, 0), 1,
- "/dev/vc/0", &tty_fops) < 0)
+ strcpy(vc0_cdev.kobj.name, "dev.vc0");
+ cdev_init(&vc0_cdev, &tty_fops);
+ if (cdev_add(&vc0_cdev, MKDEV(TTY_MAJOR, 0), 1) ||
+ register_chrdev_region(MKDEV(TTY_MAJOR, 0), 1, "/dev/vc/0") < 0)
panic("Couldn't register /dev/tty0 driver\n");
devfs_mk_cdev(MKDEV(TTY_MAJOR, 0), S_IFCHR|S_IRUSR|S_IWUSR, "vc/0");
tty_add_class_device ("tty0", MKDEV(TTY_MAJOR, 0), NULL);
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);
diff --git a/include/linux/cdev.h b/include/linux/cdev.h
new file mode 100644
index 000000000000..faa67d2f4323
--- /dev/null
+++ b/include/linux/cdev.h
@@ -0,0 +1,30 @@
+#ifndef _LINUX_CDEV_H
+#define _LINUX_CDEV_H
+#ifdef __KERNEL__
+
+struct cdev {
+ struct kobject kobj;
+ struct module *owner;
+ struct file_operations *ops;
+};
+
+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);
+}
+
+struct kobject *cdev_get(struct cdev *);
+
+int cdev_add(struct cdev *, dev_t, unsigned);
+
+void cdev_del(struct cdev *);
+
+void cdev_unmap(dev_t, unsigned);
+
+#endif
+#endif
diff --git a/include/linux/fs.h b/include/linux/fs.h
index 1b7fac010f1f..6b32c6ab8727 100644
--- a/include/linux/fs.h
+++ b/include/linux/fs.h
@@ -1056,10 +1056,8 @@ extern void bd_release(struct block_device *);
extern void blk_run_queues(void);
/* fs/char_dev.c */
-extern int alloc_chrdev_region(dev_t *, unsigned, char *,
- struct file_operations *);
-extern int register_chrdev_region(dev_t, unsigned, char *,
- struct file_operations *);
+extern int alloc_chrdev_region(dev_t *, unsigned, char *);
+extern int register_chrdev_region(dev_t, unsigned, char *);
extern int register_chrdev(unsigned int, const char *,
struct file_operations *);
extern int unregister_chrdev(unsigned int, const char *);
diff --git a/include/linux/tty_driver.h b/include/linux/tty_driver.h
index d8d9fc435a3f..ef234da28a8c 100644
--- a/include/linux/tty_driver.h
+++ b/include/linux/tty_driver.h
@@ -117,9 +117,11 @@
#include <linux/fs.h>
#include <linux/list.h>
+#include <linux/cdev.h>
struct tty_driver {
int magic; /* magic number for this structure */
+ struct cdev cdev;
struct module *owner;
const char *driver_name;
const char *name;