diff options
| -rw-r--r-- | drivers/char/tty_io.c | 58 | ||||
| -rw-r--r-- | fs/char_dev.c | 208 | ||||
| -rw-r--r-- | include/linux/cdev.h | 30 | ||||
| -rw-r--r-- | include/linux/fs.h | 6 | ||||
| -rw-r--r-- | include/linux/tty_driver.h | 2 |
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; |
