drivers/char/tty_io.c | 58 ++++++++++-- fs/char_dev.c | 208 ++++++++++++++++++++++++++++++++------------- include/linux/cdev.h | 30 ++++++ include/linux/fs.h | 6 - include/linux/tty_driver.h | 2 5 files changed, 233 insertions(+), 71 deletions(-) diff -puN drivers/char/tty_io.c~T30-cdev-C69 drivers/char/tty_io.c --- 25/drivers/char/tty_io.c~T30-cdev-C69 2003-05-05 22:38:55.000000000 -0700 +++ 25-akpm/drivers/char/tty_io.c 2003-05-05 22:41:35.000000000 -0700 @@ -2130,6 +2130,7 @@ void tty_unregister_device(struct tty_dr 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. */ @@ -2138,13 +2139,14 @@ int tty_register_driver(struct tty_drive 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); @@ -2152,11 +2154,24 @@ int tty_register_driver(struct tty_drive } 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; @@ -2167,7 +2182,7 @@ int tty_register_driver(struct tty_drive tty_register_device(driver, i); } proc_tty_register_driver(driver); - return error; + return 0; } /* @@ -2181,6 +2196,7 @@ int tty_unregister_driver(struct tty_dri 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); @@ -2206,6 +2222,7 @@ int tty_unregister_driver(struct tty_dri tty_unregister_device(driver, i); } proc_tty_unregister_driver(driver); + cdev_del(&driver->cdev); return 0; } @@ -2257,32 +2274,51 @@ 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 * on memory allocations, interrupts etc.. */ 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"); - 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_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"); #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"); vty_init(); diff -puN fs/char_dev.c~T30-cdev-C69 fs/char_dev.c --- 25/fs/char_dev.c~T30-cdev-C69 2003-05-05 22:38:55.000000000 -0700 +++ 25-akpm/fs/char_dev.c 2003-05-05 22:38:55.000000000 -0700 @@ -17,10 +17,16 @@ #include #include +#include +#include +#include + #ifdef CONFIG_KMOD #include #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,56 +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; + int index; + struct kobject *kobj = kobj_lookup(cdev_map, dev, &index); - if (!major) - return NULL; - - ret = lookup_chrfops(major, minor); - -#ifdef CONFIG_KMOD - if (!ret) { - char name[32]; - sprintf(name, "char-major-%d", major); - request_module(name); - - read_lock(&chrdevs_lock); - ret = lookup_chrfops(major, minor); - read_unlock(&chrdevs_lock); + 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; } @@ -127,8 +100,7 @@ get_chrfops(unsigned int major, unsigned */ 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; @@ -159,7 +131,6 @@ __register_chrdev_region(unsigned int ma cd->baseminor = baseminor; cd->minorct = minorct; cd->name = name; - cd->fops = fops; i = major_to_index(major); @@ -202,8 +173,7 @@ __unregister_chrdev_region(unsigned majo 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; @@ -214,7 +184,7 @@ int register_chrdev_region(dev_t from, u 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; } @@ -228,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); @@ -243,11 +212,36 @@ int register_chrdev(unsigned int major, 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) @@ -265,7 +259,12 @@ void unregister_chrdev_region(dev_t from 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; } @@ -276,7 +275,7 @@ int chrdev_open(struct inode * inode, st { 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) { @@ -317,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 -puN /dev/null include/linux/cdev.h --- /dev/null 2002-08-30 16:31:37.000000000 -0700 +++ 25-akpm/include/linux/cdev.h 2003-05-05 22:38:55.000000000 -0700 @@ -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 -puN include/linux/fs.h~T30-cdev-C69 include/linux/fs.h --- 25/include/linux/fs.h~T30-cdev-C69 2003-05-05 22:38:55.000000000 -0700 +++ 25-akpm/include/linux/fs.h 2003-05-05 22:38:55.000000000 -0700 @@ -1056,10 +1056,8 @@ extern void bd_release(struct block_devi 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 -puN include/linux/tty_driver.h~T30-cdev-C69 include/linux/tty_driver.h --- 25/include/linux/tty_driver.h~T30-cdev-C69 2003-05-05 22:38:55.000000000 -0700 +++ 25-akpm/include/linux/tty_driver.h 2003-05-05 22:38:55.000000000 -0700 @@ -117,9 +117,11 @@ #include #include +#include struct tty_driver { int magic; /* magic number for this structure */ + struct cdev cdev; struct module *owner; const char *driver_name; const char *name; _