0,0 → 1,467 |
/* |
* class.c - basic device class management |
* |
* Copyright (c) 2002-3 Patrick Mochel |
* Copyright (c) 2002-3 Open Source Development Labs |
* Copyright (c) 2003 Greg Kroah-Hartman |
* Copyright (c) 2003 IBM Corp. |
* |
* This file is released under the GPLv2 |
* |
*/ |
|
#undef DEBUG |
|
#include <linuxcomp.h> |
|
#include <linux/device.h> |
#include <linux/module.h> |
#include <linux/init.h> |
#include <linux/string.h> |
#include "base.h" |
|
#define to_class_attr(_attr) container_of(_attr,struct class_attribute,attr) |
#define to_class(obj) container_of(obj,struct class,subsys.kset.kobj) |
|
static ssize_t |
class_attr_show(struct kobject * kobj, struct attribute * attr, char * buf) |
{ |
struct class_attribute * class_attr = to_class_attr(attr); |
struct class * dc = to_class(kobj); |
ssize_t ret = 0; |
|
if (class_attr->show) |
ret = class_attr->show(dc,buf); |
return ret; |
} |
|
static ssize_t |
class_attr_store(struct kobject * kobj, struct attribute * attr, |
const char * buf, size_t count) |
{ |
struct class_attribute * class_attr = to_class_attr(attr); |
struct class * dc = to_class(kobj); |
ssize_t ret = 0; |
|
if (class_attr->store) |
ret = class_attr->store(dc,buf,count); |
return ret; |
} |
|
static struct sysfs_ops class_sysfs_ops = { |
.show = class_attr_show, |
.store = class_attr_store, |
}; |
|
static struct kobj_type ktype_class = { |
.sysfs_ops = &class_sysfs_ops, |
}; |
|
/* Hotplug events for classes go to the class_obj subsys */ |
static decl_subsys(class,&ktype_class,NULL); |
|
|
int class_create_file(struct class * cls, const struct class_attribute * attr) |
{ |
int error; |
if (cls) { |
error = 0;//sysfs_create_file(&cls->subsys.kset.kobj,&attr->attr); |
} else |
error = -EINVAL; |
return error; |
} |
|
void class_remove_file(struct class * cls, const struct class_attribute * attr) |
{ |
//if (cls) |
// sysfs_remove_file(&cls->subsys.kset.kobj,&attr->attr); |
} |
|
struct class * class_get(struct class * cls) |
{ |
if (cls) |
return container_of(subsys_get(&cls->subsys),struct class,subsys); |
return NULL; |
} |
|
void class_put(struct class * cls) |
{ |
subsys_put(&cls->subsys); |
} |
|
int class_register(struct class * cls) |
{ |
pr_debug("device class '%s': registering\n",cls->name); |
|
INIT_LIST_HEAD(&cls->children); |
INIT_LIST_HEAD(&cls->interfaces); |
kobject_set_name(&cls->subsys.kset.kobj,cls->name); |
subsys_set_kset(cls,class_subsys); |
subsystem_register(&cls->subsys); |
|
return 0; |
} |
|
void class_unregister(struct class * cls) |
{ |
pr_debug("device class '%s': unregistering\n",cls->name); |
subsystem_unregister(&cls->subsys); |
} |
|
/* Class Device Stuff */ |
|
int class_device_create_file(struct class_device * class_dev, |
const struct class_device_attribute * attr) |
{ |
int error = -EINVAL; |
if (class_dev) |
error = 0;//sysfs_create_file(&class_dev->kobj, &attr->attr); |
return error; |
} |
|
void class_device_remove_file(struct class_device * class_dev, |
const struct class_device_attribute * attr) |
{ |
//if (class_dev) |
// sysfs_remove_file(&class_dev->kobj, &attr->attr); |
} |
|
static int class_device_dev_link(struct class_device * class_dev) |
{ |
//if (class_dev->dev) |
// return sysfs_create_link(&class_dev->kobj, |
// &class_dev->dev->kobj, "device"); |
return 0; |
} |
|
static void class_device_dev_unlink(struct class_device * class_dev) |
{ |
//if (class_dev->dev) |
// sysfs_remove_link(&class_dev->kobj, "device"); |
} |
|
static int class_device_driver_link(struct class_device * class_dev) |
{ |
//if ((class_dev->dev) && (class_dev->dev->driver)) |
// return sysfs_create_link(&class_dev->kobj, |
// &class_dev->dev->driver->kobj, "driver"); |
return 0; |
} |
|
static void class_device_driver_unlink(struct class_device * class_dev) |
{ |
//if ((class_dev->dev) && (class_dev->dev->driver)) |
// sysfs_remove_link(&class_dev->kobj, "driver"); |
} |
|
|
static ssize_t |
class_device_attr_show(struct kobject * kobj, struct attribute * attr, |
char * buf) |
{ |
struct class_device_attribute * class_dev_attr = to_class_dev_attr(attr); |
struct class_device * cd = to_class_dev(kobj); |
ssize_t ret = 0; |
|
if (class_dev_attr->show) |
ret = class_dev_attr->show(cd,buf); |
return ret; |
} |
|
static ssize_t |
class_device_attr_store(struct kobject * kobj, struct attribute * attr, |
const char * buf, size_t count) |
{ |
struct class_device_attribute * class_dev_attr = to_class_dev_attr(attr); |
struct class_device * cd = to_class_dev(kobj); |
ssize_t ret = 0; |
|
if (class_dev_attr->store) |
ret = class_dev_attr->store(cd,buf,count); |
return ret; |
} |
|
static struct sysfs_ops class_dev_sysfs_ops = { |
.show = class_device_attr_show, |
.store = class_device_attr_store, |
}; |
|
static void class_dev_release(struct kobject * kobj) |
{ |
struct class_device *cd = to_class_dev(kobj); |
struct class * cls = cd->class; |
|
pr_debug("device class '%s': release.\n",cd->class_id); |
|
if (cls->release) |
cls->release(cd); |
else { |
printk(KERN_ERR "Device class '%s' does not have a release() function, " |
"it is broken and must be fixed.\n", |
cd->class_id); |
WARN_ON(1); |
} |
} |
|
static struct kobj_type ktype_class_device = { |
.sysfs_ops = &class_dev_sysfs_ops, |
.release = class_dev_release, |
}; |
|
static int class_hotplug_filter(struct kset *kset, struct kobject *kobj) |
{ |
struct kobj_type *ktype = get_ktype(kobj); |
|
if (ktype == &ktype_class_device) { |
struct class_device *class_dev = to_class_dev(kobj); |
if (class_dev->class) |
return 1; |
} |
return 0; |
} |
|
static char *class_hotplug_name(struct kset *kset, struct kobject *kobj) |
{ |
struct class_device *class_dev = to_class_dev(kobj); |
|
return class_dev->class->name; |
} |
|
static int class_hotplug(struct kset *kset, struct kobject *kobj, char **envp, |
int num_envp, char *buffer, int buffer_size) |
{ |
struct class_device *class_dev = to_class_dev(kobj); |
int retval = 0; |
|
pr_debug("%s - name = %s\n", __FUNCTION__, class_dev->class_id); |
if (class_dev->class->hotplug) { |
/* have the bus specific function add its stuff */ |
retval = class_dev->class->hotplug (class_dev, envp, num_envp, |
buffer, buffer_size); |
if (retval) { |
pr_debug ("%s - hotplug() returned %d\n", |
__FUNCTION__, retval); |
} |
} |
|
return retval; |
} |
|
static struct kset_hotplug_ops class_hotplug_ops = { |
.filter = class_hotplug_filter, |
.name = class_hotplug_name, |
.hotplug = class_hotplug, |
}; |
|
static decl_subsys(class_obj, &ktype_class_device, &class_hotplug_ops); |
|
void class_device_initialize(struct class_device *class_dev) |
{ |
kobj_set_kset_s(class_dev, class_obj_subsys); |
kobject_init(&class_dev->kobj); |
INIT_LIST_HEAD(&class_dev->node); |
} |
|
int class_device_add(struct class_device *class_dev) |
{ |
struct class * parent; |
struct class_interface * class_intf; |
struct list_head * entry; |
int error; |
|
class_dev = class_device_get(class_dev); |
if (!class_dev || !strlen(class_dev->class_id)) |
return -EINVAL; |
|
parent = class_get(class_dev->class); |
|
pr_debug("CLASS: registering class device: ID = '%s'\n", |
class_dev->class_id); |
|
/* first, register with generic layer. */ |
kobject_set_name(&class_dev->kobj, class_dev->class_id); |
if (parent) |
class_dev->kobj.parent = &parent->subsys.kset.kobj; |
|
if ((error = kobject_add(&class_dev->kobj))) |
goto register_done; |
|
/* now take care of our own registration */ |
if (parent) { |
//down_write(&parent->subsys.rwsem); |
list_add_tail(&class_dev->node, &parent->children); |
list_for_each(entry, &parent->interfaces) { |
class_intf = container_of(entry, struct class_interface, node); |
if (class_intf->add) |
class_intf->add(class_dev); |
} |
//up_write(&parent->subsys.rwsem); |
} |
|
class_device_dev_link(class_dev); |
class_device_driver_link(class_dev); |
|
register_done: |
if (error && parent) |
class_put(parent); |
class_device_put(class_dev); |
return error; |
} |
|
int class_device_register(struct class_device *class_dev) |
{ |
class_device_initialize(class_dev); |
return class_device_add(class_dev); |
} |
|
void class_device_del(struct class_device *class_dev) |
{ |
struct class * parent = class_dev->class; |
struct class_interface * class_intf; |
struct list_head * entry; |
|
if (parent) { |
//down_write(&parent->subsys.rwsem); |
list_del_init(&class_dev->node); |
list_for_each(entry, &parent->interfaces) { |
class_intf = container_of(entry, struct class_interface, node); |
if (class_intf->remove) |
class_intf->remove(class_dev); |
} |
//up_write(&parent->subsys.rwsem); |
} |
|
class_device_dev_unlink(class_dev); |
class_device_driver_unlink(class_dev); |
|
kobject_del(&class_dev->kobj); |
|
if (parent) |
class_put(parent); |
} |
|
void class_device_unregister(struct class_device *class_dev) |
{ |
pr_debug("CLASS: Unregistering class device. ID = '%s'\n", |
class_dev->class_id); |
class_device_del(class_dev); |
class_device_put(class_dev); |
} |
|
int class_device_rename(struct class_device *class_dev, char *new_name) |
{ |
class_dev = class_device_get(class_dev); |
if (!class_dev) |
return -EINVAL; |
|
pr_debug("CLASS: renaming '%s' to '%s'\n", class_dev->class_id, |
new_name); |
|
strncpy(class_dev->class_id, new_name, KOBJ_NAME_LEN); |
|
kobject_rename(&class_dev->kobj, new_name); |
|
class_device_put(class_dev); |
|
return 0; |
} |
|
struct class_device * class_device_get(struct class_device *class_dev) |
{ |
if (class_dev) |
return to_class_dev(kobject_get(&class_dev->kobj)); |
return NULL; |
} |
|
void class_device_put(struct class_device *class_dev) |
{ |
kobject_put(&class_dev->kobj); |
} |
|
|
int class_interface_register(struct class_interface *class_intf) |
{ |
struct class * parent; |
struct class_device * class_dev; |
struct list_head * entry; |
|
if (!class_intf || !class_intf->class) |
return -ENODEV; |
|
parent = class_get(class_intf->class); |
if (!parent) |
return -EINVAL; |
|
//down_write(&parent->subsys.rwsem); |
list_add_tail(&class_intf->node, &parent->interfaces); |
|
if (class_intf->add) { |
list_for_each(entry, &parent->children) { |
class_dev = container_of(entry, struct class_device, node); |
class_intf->add(class_dev); |
} |
} |
//up_write(&parent->subsys.rwsem); |
|
return 0; |
} |
|
void class_interface_unregister(struct class_interface *class_intf) |
{ |
struct class * parent = class_intf->class; |
struct list_head * entry; |
|
if (!parent) |
return; |
|
//down_write(&parent->subsys.rwsem); |
list_del_init(&class_intf->node); |
|
if (class_intf->remove) { |
list_for_each(entry, &parent->children) { |
struct class_device *class_dev = container_of(entry, struct class_device, node); |
class_intf->remove(class_dev); |
} |
} |
//up_write(&parent->subsys.rwsem); |
|
class_put(parent); |
} |
|
|
|
int __init classes_init(void) |
{ |
int retval; |
|
retval = subsystem_register(&class_subsys); |
if (retval) |
return retval; |
|
/* ick, this is ugly, the things we go through to keep from showing up |
* in sysfs... */ |
subsystem_init(&class_obj_subsys); |
if (!class_obj_subsys.kset.subsys) |
class_obj_subsys.kset.subsys = &class_obj_subsys; |
return 0; |
} |
|
EXPORT_SYMBOL(class_create_file); |
EXPORT_SYMBOL(class_remove_file); |
EXPORT_SYMBOL(class_register); |
EXPORT_SYMBOL(class_unregister); |
EXPORT_SYMBOL(class_get); |
EXPORT_SYMBOL(class_put); |
|
EXPORT_SYMBOL(class_device_register); |
EXPORT_SYMBOL(class_device_unregister); |
EXPORT_SYMBOL(class_device_initialize); |
EXPORT_SYMBOL(class_device_add); |
EXPORT_SYMBOL(class_device_del); |
EXPORT_SYMBOL(class_device_get); |
EXPORT_SYMBOL(class_device_put); |
EXPORT_SYMBOL(class_device_create_file); |
EXPORT_SYMBOL(class_device_remove_file); |
|
EXPORT_SYMBOL(class_interface_register); |
EXPORT_SYMBOL(class_interface_unregister); |