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)
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;
}
......@@ -2365,35 +2382,54 @@ 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");
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);
......
......@@ -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 */
......@@ -59,56 +66,24 @@ int get_chrdev_list(char *page)
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.
* 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 @@ int register_chrdev_region(dev_t from, unsigned count, char *name,
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);
#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 *);
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 *);
......
......@@ -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;
......
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