diff options
Diffstat (limited to 'drivers/base/map.c')
-rw-r--r-- | drivers/base/map.c | 155 |
1 files changed, 155 insertions, 0 deletions
diff --git a/drivers/base/map.c b/drivers/base/map.c new file mode 100644 index 00000000000..2f455d86793 --- /dev/null +++ b/drivers/base/map.c @@ -0,0 +1,155 @@ +/* + * linux/drivers/base/map.c + * + * (C) Copyright Al Viro 2002,2003 + * Released under GPL v2. + * + * NOTE: data structure needs to be changed. It works, but for large dev_t + * it will be too slow. It is isolated, though, so these changes will be + * local to that file. + */ + +#include <linux/module.h> +#include <linux/slab.h> +#include <linux/kdev_t.h> +#include <linux/kobject.h> +#include <linux/kobj_map.h> + +struct kobj_map { + struct probe { + struct probe *next; + dev_t dev; + unsigned long range; + struct module *owner; + kobj_probe_t *get; + int (*lock)(dev_t, void *); + void *data; + } *probes[255]; + struct semaphore *sem; +}; + +int kobj_map(struct kobj_map *domain, dev_t dev, unsigned long range, + struct module *module, kobj_probe_t *probe, + int (*lock)(dev_t, void *), void *data) +{ + unsigned n = MAJOR(dev + range - 1) - MAJOR(dev) + 1; + unsigned index = MAJOR(dev); + unsigned i; + struct probe *p; + + if (n > 255) + n = 255; + + p = kmalloc(sizeof(struct probe) * n, GFP_KERNEL); + + if (p == NULL) + return -ENOMEM; + + for (i = 0; i < n; i++, p++) { + p->owner = module; + p->get = probe; + p->lock = lock; + p->dev = dev; + p->range = range; + p->data = data; + } + down(domain->sem); + for (i = 0, p -= n; i < n; i++, p++, index++) { + struct probe **s = &domain->probes[index % 255]; + while (*s && (*s)->range < range) + s = &(*s)->next; + p->next = *s; + *s = p; + } + up(domain->sem); + return 0; +} + +void kobj_unmap(struct kobj_map *domain, dev_t dev, unsigned long range) +{ + unsigned n = MAJOR(dev + range - 1) - MAJOR(dev) + 1; + unsigned index = MAJOR(dev); + unsigned i; + struct probe *found = NULL; + + if (n > 255) + n = 255; + + down(domain->sem); + for (i = 0; i < n; i++, index++) { + struct probe **s; + for (s = &domain->probes[index % 255]; *s; s = &(*s)->next) { + struct probe *p = *s; + if (p->dev == dev && p->range == range) { + *s = p->next; + if (!found) + found = p; + break; + } + } + } + up(domain->sem); + kfree(found); +} + +struct kobject *kobj_lookup(struct kobj_map *domain, dev_t dev, int *index) +{ + struct kobject *kobj; + struct probe *p; + unsigned long best = ~0UL; + +retry: + down(domain->sem); + for (p = domain->probes[MAJOR(dev) % 255]; p; p = p->next) { + struct kobject *(*probe)(dev_t, int *, void *); + struct module *owner; + void *data; + + if (p->dev > dev || p->dev + p->range - 1 < dev) + continue; + if (p->range - 1 >= best) + break; + if (!try_module_get(p->owner)) + continue; + owner = p->owner; + data = p->data; + probe = p->get; + best = p->range - 1; + *index = dev - p->dev; + if (p->lock && p->lock(dev, data) < 0) { + module_put(owner); + continue; + } + up(domain->sem); + kobj = probe(dev, index, data); + /* Currently ->owner protects _only_ ->probe() itself. */ + module_put(owner); + if (kobj) + return kobj; + goto retry; + } + up(domain->sem); + return NULL; +} + +struct kobj_map *kobj_map_init(kobj_probe_t *base_probe, struct semaphore *sem) +{ + struct kobj_map *p = kmalloc(sizeof(struct kobj_map), GFP_KERNEL); + struct probe *base = kmalloc(sizeof(struct probe), GFP_KERNEL); + int i; + + if ((p == NULL) || (base == NULL)) { + kfree(p); + kfree(base); + return NULL; + } + + memset(base, 0, sizeof(struct probe)); + base->dev = 1; + base->range = ~0; + base->get = base_probe; + for (i = 0; i < 255; i++) + p->probes[i] = base; + p->sem = sem; + return p; +} |