Commit 787d458a authored by Alexander Viro's avatar Alexander Viro Committed by Linus Torvalds

[PATCH] cdev-cidr, part 1

New object: struct cdev.  It contains a kobject, a pointer to
file_operations and a pointer to owner module.  These guys have a search
structure of the same sort as gendisks and chrdev_open() picks
file_operations from them.

Intended use: embed such animal in driver-owned structure (e.g.
tty_driver) and register it as associated with given range of device
numbers.  Generic code will do lookup for such object and use it for the
rest.

The behaviour of register_chrdev() is _not_ changed - it allocates
struct cdev and registers it; any old driver will work as if nothing had
changed.

On that stage we only use it during chrdev_open() to find
file_operations.  Later it will be cached in inode->i_cdev (and index in
range - in inode->i_cindex) so that ->open() could get whatever objects
it wants directly without any special-cased lookups, etc.
parent 6abc19b4
...@@ -2242,6 +2242,7 @@ void tty_unregister_device(struct tty_driver *driver, unsigned index) ...@@ -2242,6 +2242,7 @@ void tty_unregister_device(struct tty_driver *driver, unsigned index)
EXPORT_SYMBOL(tty_register_device); EXPORT_SYMBOL(tty_register_device);
EXPORT_SYMBOL(tty_unregister_device); EXPORT_SYMBOL(tty_unregister_device);
static struct kobject tty_kobj = {.name = "tty"};
/* /*
* Called by a tty driver to register itself. * Called by a tty driver to register itself.
*/ */
...@@ -2250,13 +2251,14 @@ int tty_register_driver(struct tty_driver *driver) ...@@ -2250,13 +2251,14 @@ int tty_register_driver(struct tty_driver *driver)
int error; int error;
int i; int i;
dev_t dev; dev_t dev;
char *s;
if (driver->flags & TTY_DRIVER_INSTALLED) if (driver->flags & TTY_DRIVER_INSTALLED)
return 0; return 0;
if (!driver->major) { if (!driver->major) {
error = alloc_chrdev_region(&dev, driver->num, error = alloc_chrdev_region(&dev, driver->num,
(char*)driver->name, &tty_fops); (char*)driver->name);
if (!error) { if (!error) {
driver->major = MAJOR(dev); driver->major = MAJOR(dev);
driver->minor_start = MINOR(dev); driver->minor_start = MINOR(dev);
...@@ -2264,11 +2266,24 @@ int tty_register_driver(struct tty_driver *driver) ...@@ -2264,11 +2266,24 @@ int tty_register_driver(struct tty_driver *driver)
} else { } else {
dev = MKDEV(driver->major, driver->minor_start); dev = MKDEV(driver->major, driver->minor_start);
error = register_chrdev_region(dev, driver->num, error = register_chrdev_region(dev, driver->num,
(char*)driver->name, &tty_fops); (char*)driver->name);
} }
if (error < 0) if (error < 0)
return error; 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) if (!driver->put_char)
driver->put_char = tty_default_put_char; driver->put_char = tty_default_put_char;
...@@ -2279,7 +2294,7 @@ int tty_register_driver(struct tty_driver *driver) ...@@ -2279,7 +2294,7 @@ int tty_register_driver(struct tty_driver *driver)
tty_register_device(driver, i, NULL); tty_register_device(driver, i, NULL);
} }
proc_tty_register_driver(driver); proc_tty_register_driver(driver);
return error; return 0;
} }
/* /*
...@@ -2293,6 +2308,7 @@ int tty_unregister_driver(struct tty_driver *driver) ...@@ -2293,6 +2308,7 @@ int tty_unregister_driver(struct tty_driver *driver)
if (*driver->refcount) if (*driver->refcount)
return -EBUSY; return -EBUSY;
cdev_unmap(MKDEV(driver->major, driver->minor_start), driver->num);
unregister_chrdev_region(MKDEV(driver->major, driver->minor_start), unregister_chrdev_region(MKDEV(driver->major, driver->minor_start),
driver->num); driver->num);
...@@ -2318,6 +2334,7 @@ int tty_unregister_driver(struct tty_driver *driver) ...@@ -2318,6 +2334,7 @@ int tty_unregister_driver(struct tty_driver *driver)
tty_unregister_device(driver, i); tty_unregister_device(driver, i);
} }
proc_tty_unregister_driver(driver); proc_tty_unregister_driver(driver);
cdev_del(&driver->cdev);
return 0; return 0;
} }
...@@ -2364,6 +2381,14 @@ static int __init tty_class_init(void) ...@@ -2364,6 +2381,14 @@ static int __init tty_class_init(void)
} }
postcore_initcall(tty_class_init); 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 * Ok, now we can initialize the rest of the tty devices and can count
...@@ -2371,29 +2396,40 @@ postcore_initcall(tty_class_init); ...@@ -2371,29 +2396,40 @@ postcore_initcall(tty_class_init);
*/ */
void __init tty_init(void) void __init tty_init(void)
{ {
if (register_chrdev_region(MKDEV(TTYAUX_MAJOR, 0), 1, strcpy(tty_cdev.kobj.name, "dev.tty");
"/dev/tty", &tty_fops) < 0) 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"); panic("Couldn't register /dev/tty driver\n");
devfs_mk_cdev(MKDEV(TTYAUX_MAJOR, 0), S_IFCHR|S_IRUGO|S_IWUGO, "tty"); devfs_mk_cdev(MKDEV(TTYAUX_MAJOR, 0), S_IFCHR|S_IRUGO|S_IWUGO, "tty");
tty_add_class_device ("tty", MKDEV(TTYAUX_MAJOR, 0), NULL); tty_add_class_device ("tty", MKDEV(TTYAUX_MAJOR, 0), NULL);
if (register_chrdev_region(MKDEV(TTYAUX_MAJOR, 1), 1, strcpy(console_cdev.kobj.name, "dev.console");
"/dev/console", &tty_fops) < 0) 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"); panic("Couldn't register /dev/console driver\n");
devfs_mk_cdev(MKDEV(TTYAUX_MAJOR, 1), S_IFCHR|S_IRUSR|S_IWUSR, "console"); 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_add_class_device ("console", MKDEV(TTYAUX_MAJOR, 1), NULL);
tty_kobj.kset = tty_cdev.kobj.kset;
kobject_register(&tty_kobj);
#ifdef CONFIG_UNIX98_PTYS #ifdef CONFIG_UNIX98_PTYS
if (register_chrdev_region(MKDEV(TTYAUX_MAJOR, 2), 1, strcpy(ptmx_cdev.kobj.name, "dev.ptmx");
"/dev/ptmx", &tty_fops) < 0) 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"); panic("Couldn't register /dev/ptmx driver\n");
devfs_mk_cdev(MKDEV(TTYAUX_MAJOR, 2), S_IFCHR|S_IRUGO|S_IWUGO, "ptmx"); devfs_mk_cdev(MKDEV(TTYAUX_MAJOR, 2), S_IFCHR|S_IRUGO|S_IWUGO, "ptmx");
tty_add_class_device ("ptmx", MKDEV(TTYAUX_MAJOR, 2), NULL); tty_add_class_device ("ptmx", MKDEV(TTYAUX_MAJOR, 2), NULL);
#endif #endif
#ifdef CONFIG_VT #ifdef CONFIG_VT
if (register_chrdev_region(MKDEV(TTY_MAJOR, 0), 1, strcpy(vc0_cdev.kobj.name, "dev.vc0");
"/dev/vc/0", &tty_fops) < 0) 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"); panic("Couldn't register /dev/tty0 driver\n");
devfs_mk_cdev(MKDEV(TTY_MAJOR, 0), S_IFCHR|S_IRUSR|S_IWUSR, "vc/0"); 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); tty_add_class_device ("tty0", MKDEV(TTY_MAJOR, 0), NULL);
......
...@@ -17,10 +17,16 @@ ...@@ -17,10 +17,16 @@
#include <linux/smp_lock.h> #include <linux/smp_lock.h>
#include <linux/devfs_fs_kernel.h> #include <linux/devfs_fs_kernel.h>
#include <linux/kobject.h>
#include <linux/kobj_map.h>
#include <linux/cdev.h>
#ifdef CONFIG_KMOD #ifdef CONFIG_KMOD
#include <linux/kmod.h> #include <linux/kmod.h>
#endif #endif
static struct kobj_map *cdev_map;
#define MAX_PROBE_HASH 255 /* random */ #define MAX_PROBE_HASH 255 /* random */
static rwlock_t chrdevs_lock = RW_LOCK_UNLOCKED; static rwlock_t chrdevs_lock = RW_LOCK_UNLOCKED;
...@@ -32,6 +38,7 @@ static struct char_device_struct { ...@@ -32,6 +38,7 @@ static struct char_device_struct {
int minorct; int minorct;
const char *name; const char *name;
struct file_operations *fops; struct file_operations *fops;
struct cdev *cdev; /* will die */
} *chrdevs[MAX_PROBE_HASH]; } *chrdevs[MAX_PROBE_HASH];
/* index in the above */ /* index in the above */
...@@ -59,56 +66,24 @@ int get_chrdev_list(char *page) ...@@ -59,56 +66,24 @@ int get_chrdev_list(char *page)
return len; return len;
} }
/*
* 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. * Return the function table of a device, if present.
* Load the driver if needed. * Load the driver if needed.
* Increment the reference count of module in question. * Increment the reference count of module in question.
*/ */
static struct file_operations * static struct file_operations *get_chrfops(dev_t dev)
get_chrfops(unsigned int major, unsigned int minor)
{ {
struct file_operations *ret = NULL; struct file_operations *ret = NULL;
int index;
if (!major) struct kobject *kobj = kobj_lookup(cdev_map, dev, &index);
return NULL;
if (kobj) {
ret = lookup_chrfops(major, minor); struct cdev *p = container_of(kobj, struct cdev, kobj);
struct module *owner = p->owner;
#ifdef CONFIG_KMOD ret = fops_get(p->ops);
if (!ret) { cdev_put(p);
request_module("char-major-%d", major); module_put(owner);
read_lock(&chrdevs_lock);
ret = lookup_chrfops(major, minor);
read_unlock(&chrdevs_lock);
} }
#endif
return ret; return ret;
} }
...@@ -125,8 +100,7 @@ get_chrfops(unsigned int major, unsigned int minor) ...@@ -125,8 +100,7 @@ get_chrfops(unsigned int major, unsigned int minor)
*/ */
static struct char_device_struct * static struct char_device_struct *
__register_chrdev_region(unsigned int major, unsigned int baseminor, __register_chrdev_region(unsigned int major, unsigned int baseminor,
int minorct, const char *name, int minorct, const char *name)
struct file_operations *fops)
{ {
struct char_device_struct *cd, **cp; struct char_device_struct *cd, **cp;
int ret = 0; int ret = 0;
...@@ -157,7 +131,6 @@ __register_chrdev_region(unsigned int major, unsigned int baseminor, ...@@ -157,7 +131,6 @@ __register_chrdev_region(unsigned int major, unsigned int baseminor,
cd->baseminor = baseminor; cd->baseminor = baseminor;
cd->minorct = minorct; cd->minorct = minorct;
cd->name = name; cd->name = name;
cd->fops = fops;
i = major_to_index(major); i = major_to_index(major);
...@@ -200,8 +173,7 @@ __unregister_chrdev_region(unsigned major, unsigned baseminor, int minorct) ...@@ -200,8 +173,7 @@ __unregister_chrdev_region(unsigned major, unsigned baseminor, int minorct)
return cd; return cd;
} }
int register_chrdev_region(dev_t from, unsigned count, char *name, int register_chrdev_region(dev_t from, unsigned count, char *name)
struct file_operations *fops)
{ {
struct char_device_struct *cd; struct char_device_struct *cd;
dev_t to = from + count; dev_t to = from + count;
...@@ -212,7 +184,7 @@ int register_chrdev_region(dev_t from, unsigned count, char *name, ...@@ -212,7 +184,7 @@ int register_chrdev_region(dev_t from, unsigned count, char *name,
if (next > to) if (next > to)
next = to; next = to;
cd = __register_chrdev_region(MAJOR(n), MINOR(n), cd = __register_chrdev_region(MAJOR(n), MINOR(n),
next - n, name, fops); next - n, name);
if (IS_ERR(cd)) if (IS_ERR(cd))
goto fail; goto fail;
} }
...@@ -226,11 +198,10 @@ int register_chrdev_region(dev_t from, unsigned count, char *name, ...@@ -226,11 +198,10 @@ int register_chrdev_region(dev_t from, unsigned count, char *name,
return PTR_ERR(cd); return PTR_ERR(cd);
} }
int alloc_chrdev_region(dev_t *dev, unsigned count, char *name, int alloc_chrdev_region(dev_t *dev, unsigned count, char *name)
struct file_operations *fops)
{ {
struct char_device_struct *cd; 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)) if (IS_ERR(cd))
return PTR_ERR(cd); return PTR_ERR(cd);
*dev = MKDEV(cd->major, cd->baseminor); *dev = MKDEV(cd->major, cd->baseminor);
...@@ -241,11 +212,36 @@ int register_chrdev(unsigned int major, const char *name, ...@@ -241,11 +212,36 @@ int register_chrdev(unsigned int major, const char *name,
struct file_operations *fops) struct file_operations *fops)
{ {
struct char_device_struct *cd; 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)) if (IS_ERR(cd))
return PTR_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) void unregister_chrdev_region(dev_t from, unsigned count)
...@@ -263,7 +259,12 @@ 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) 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; return 0;
} }
...@@ -274,7 +275,7 @@ int chrdev_open(struct inode * inode, struct file * filp) ...@@ -274,7 +275,7 @@ int chrdev_open(struct inode * inode, struct file * filp)
{ {
int ret = -ENODEV; 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) { if (filp->f_op) {
ret = 0; ret = 0;
if (filp->f_op->open != NULL) { if (filp->f_op->open != NULL) {
...@@ -315,3 +316,100 @@ const char *cdevname(kdev_t dev) ...@@ -315,3 +316,100 @@ const char *cdevname(kdev_t dev)
return buffer; 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);
#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
...@@ -1056,10 +1056,8 @@ extern void bd_release(struct block_device *); ...@@ -1056,10 +1056,8 @@ extern void bd_release(struct block_device *);
extern void blk_run_queues(void); extern void blk_run_queues(void);
/* fs/char_dev.c */ /* fs/char_dev.c */
extern int alloc_chrdev_region(dev_t *, unsigned, char *, extern int alloc_chrdev_region(dev_t *, unsigned, char *);
struct file_operations *); extern int register_chrdev_region(dev_t, unsigned, char *);
extern int register_chrdev_region(dev_t, unsigned, char *,
struct file_operations *);
extern int register_chrdev(unsigned int, const char *, extern int register_chrdev(unsigned int, const char *,
struct file_operations *); struct file_operations *);
extern int unregister_chrdev(unsigned int, const char *); extern int unregister_chrdev(unsigned int, const char *);
......
...@@ -117,9 +117,11 @@ ...@@ -117,9 +117,11 @@
#include <linux/fs.h> #include <linux/fs.h>
#include <linux/list.h> #include <linux/list.h>
#include <linux/cdev.h>
struct tty_driver { struct tty_driver {
int magic; /* magic number for this structure */ int magic; /* magic number for this structure */
struct cdev cdev;
struct module *owner; struct module *owner;
const char *driver_name; const char *driver_name;
const char *name; const char *name;
......
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