#include <linux/module.h>
#include <linux/slab.h>
#include <linux/string.h>
+#include <linux/kdev_t.h>
#include <asm/semaphore.h>
* sysfs bindings for devices.
*/
+/**
+ * dev_driver_string - Return a device's driver name, if at all possible
+ * @dev: struct device to get the name of
+ *
+ * Will return the device's driver's name if it is bound to a device. If
+ * the device is not bound to a device, it will return the name of the bus
+ * it is attached to. If it is not attached to a bus either, an empty
+ * string will be returned.
+ */
+const char *dev_driver_string(struct device *dev)
+{
+ return dev->driver ? dev->driver->name :
+ (dev->bus ? dev->bus->name : "");
+}
+EXPORT_SYMBOL_GPL(dev_driver_string);
+
#define to_dev(obj) container_of(obj, struct device, kobj)
#define to_dev_attr(_attr) container_of(_attr, struct device_attribute, attr)
};
-static int dev_hotplug_filter(struct kset *kset, struct kobject *kobj)
+static int dev_uevent_filter(struct kset *kset, struct kobject *kobj)
{
struct kobj_type *ktype = get_ktype(kobj);
struct device *dev = to_dev(kobj);
if (dev->bus)
return 1;
+ if (dev->class)
+ return 1;
}
return 0;
}
-static const char *dev_hotplug_name(struct kset *kset, struct kobject *kobj)
+static const char *dev_uevent_name(struct kset *kset, struct kobject *kobj)
{
struct device *dev = to_dev(kobj);
- return dev->bus->name;
+ if (dev->bus)
+ return dev->bus->name;
+ if (dev->class)
+ return dev->class->name;
+ return NULL;
}
-static int dev_hotplug(struct kset *kset, struct kobject *kobj, char **envp,
+static int dev_uevent(struct kset *kset, struct kobject *kobj, char **envp,
int num_envp, char *buffer, int buffer_size)
{
struct device *dev = to_dev(kobj);
int length = 0;
int retval = 0;
+ /* add the major/minor if present */
+ if (MAJOR(dev->devt)) {
+ add_uevent_var(envp, num_envp, &i,
+ buffer, buffer_size, &length,
+ "MAJOR=%u", MAJOR(dev->devt));
+ add_uevent_var(envp, num_envp, &i,
+ buffer, buffer_size, &length,
+ "MINOR=%u", MINOR(dev->devt));
+ }
+
/* add bus name of physical device */
if (dev->bus)
- add_hotplug_env_var(envp, num_envp, &i,
- buffer, buffer_size, &length,
- "PHYSDEVBUS=%s", dev->bus->name);
+ add_uevent_var(envp, num_envp, &i,
+ buffer, buffer_size, &length,
+ "PHYSDEVBUS=%s", dev->bus->name);
/* add driver name of physical device */
if (dev->driver)
- add_hotplug_env_var(envp, num_envp, &i,
- buffer, buffer_size, &length,
- "PHYSDEVDRIVER=%s", dev->driver->name);
+ add_uevent_var(envp, num_envp, &i,
+ buffer, buffer_size, &length,
+ "PHYSDEVDRIVER=%s", dev->driver->name);
/* terminate, set to next free slot, shrink available space */
envp[i] = NULL;
buffer = &buffer[length];
buffer_size -= length;
- if (dev->bus && dev->bus->hotplug) {
+ if (dev->bus && dev->bus->uevent) {
/* have the bus specific function add its stuff */
- retval = dev->bus->hotplug (dev, envp, num_envp, buffer, buffer_size);
+ retval = dev->bus->uevent(dev, envp, num_envp, buffer, buffer_size);
if (retval) {
- pr_debug ("%s - hotplug() returned %d\n",
+ pr_debug ("%s - uevent() returned %d\n",
__FUNCTION__, retval);
}
}
return retval;
}
-static struct kset_hotplug_ops device_hotplug_ops = {
- .filter = dev_hotplug_filter,
- .name = dev_hotplug_name,
- .hotplug = dev_hotplug,
+static struct kset_uevent_ops device_uevent_ops = {
+ .filter = dev_uevent_filter,
+ .name = dev_uevent_name,
+ .uevent = dev_uevent,
};
static ssize_t store_uevent(struct device *dev, struct device_attribute *attr,
const char *buf, size_t count)
{
- kobject_hotplug(&dev->kobj, KOBJ_ADD);
+ kobject_uevent(&dev->kobj, KOBJ_ADD);
return count;
}
-/**
- * device_subsys - structure to be registered with kobject core.
+static ssize_t show_dev(struct device *dev, struct device_attribute *attr,
+ char *buf)
+{
+ return print_dev_t(buf, dev->devt);
+}
+
+/*
+ * devices_subsys - structure to be registered with kobject core.
*/
-decl_subsys(devices, &ktype_device, &device_hotplug_ops);
+decl_subsys(devices, &ktype_device, &device_uevent_ops);
/**
klist_init(&dev->klist_children, klist_children_get,
klist_children_put);
INIT_LIST_HEAD(&dev->dma_pools);
+ INIT_LIST_HEAD(&dev->node);
init_MUTEX(&dev->sem);
device_init_wakeup(dev, 0);
}
int device_add(struct device *dev)
{
struct device *parent = NULL;
+ char *class_name = NULL;
int error = -EINVAL;
dev = get_device(dev);
dev->uevent_attr.store = store_uevent;
device_create_file(dev, &dev->uevent_attr);
- kobject_hotplug(&dev->kobj, KOBJ_ADD);
+ if (MAJOR(dev->devt)) {
+ struct device_attribute *attr;
+ attr = kzalloc(sizeof(*attr), GFP_KERNEL);
+ if (!attr) {
+ error = -ENOMEM;
+ goto PMError;
+ }
+ attr->attr.name = "dev";
+ attr->attr.mode = S_IRUGO;
+ if (dev->driver)
+ attr->attr.owner = dev->driver->owner;
+ attr->show = show_dev;
+ error = device_create_file(dev, attr);
+ if (error) {
+ kfree(attr);
+ goto attrError;
+ }
+
+ dev->devt_attr = attr;
+ }
+
+ if (dev->class) {
+ sysfs_create_link(&dev->kobj, &dev->class->subsys.kset.kobj,
+ "subsystem");
+ sysfs_create_link(&dev->class->subsys.kset.kobj, &dev->kobj,
+ dev->bus_id);
+
+ sysfs_create_link(&dev->kobj, &dev->parent->kobj, "device");
+ class_name = make_class_name(dev->class->name, &dev->kobj);
+ sysfs_create_link(&dev->parent->kobj, &dev->kobj, class_name);
+ }
+
if ((error = device_pm_add(dev)))
goto PMError;
if ((error = bus_add_device(dev)))
goto BusError;
+ kobject_uevent(&dev->kobj, KOBJ_ADD);
+ bus_attach_device(dev);
if (parent)
klist_add_tail(&dev->knode_parent, &parent->klist_children);
+ if (dev->class) {
+ /* tie the class to the device */
+ down(&dev->class->sem);
+ list_add_tail(&dev->node, &dev->class->devices);
+ up(&dev->class->sem);
+ }
+
/* notify platform of device entry */
if (platform_notify)
platform_notify(dev);
Done:
+ kfree(class_name);
put_device(dev);
return error;
BusError:
device_pm_remove(dev);
PMError:
- kobject_hotplug(&dev->kobj, KOBJ_REMOVE);
+ if (dev->devt_attr) {
+ device_remove_file(dev, dev->devt_attr);
+ kfree(dev->devt_attr);
+ }
+ attrError:
+ kobject_uevent(&dev->kobj, KOBJ_REMOVE);
kobject_del(&dev->kobj);
Error:
if (parent)
void device_del(struct device * dev)
{
struct device * parent = dev->parent;
+ char *class_name = NULL;
if (parent)
klist_del(&dev->knode_parent);
+ if (dev->devt_attr)
+ device_remove_file(dev, dev->devt_attr);
+ if (dev->class) {
+ sysfs_remove_link(&dev->kobj, "subsystem");
+ sysfs_remove_link(&dev->class->subsys.kset.kobj, dev->bus_id);
+ class_name = make_class_name(dev->class->name, &dev->kobj);
+ sysfs_remove_link(&dev->kobj, "device");
+ sysfs_remove_link(&dev->parent->kobj, class_name);
+ kfree(class_name);
+ down(&dev->class->sem);
+ list_del_init(&dev->node);
+ up(&dev->class->sem);
+ }
device_remove_file(dev, &dev->uevent_attr);
/* Notify the platform of the removal, in case they
platform_notify_remove(dev);
bus_remove_device(dev);
device_pm_remove(dev);
- kobject_hotplug(&dev->kobj, KOBJ_REMOVE);
+ kobject_uevent(&dev->kobj, KOBJ_REMOVE);
kobject_del(&dev->kobj);
if (parent)
put_device(parent);
/**
* device_for_each_child - device child iterator.
- * @dev: parent struct device.
+ * @parent: parent struct device.
* @data: data for the callback.
* @fn: function to be called for each device.
*
- * Iterate over @dev's child devices, and call @fn for each,
+ * Iterate over @parent's child devices, and call @fn for each,
* passing it @data.
*
* We check the return of @fn each time. If it returns anything
EXPORT_SYMBOL_GPL(device_create_file);
EXPORT_SYMBOL_GPL(device_remove_file);
+
+
+static void device_create_release(struct device *dev)
+{
+ pr_debug("%s called for %s\n", __FUNCTION__, dev->bus_id);
+ kfree(dev);
+}
+
+/**
+ * device_create - creates a device and registers it with sysfs
+ * @cs: pointer to the struct class that this device should be registered to.
+ * @parent: pointer to the parent struct device of this new device, if any.
+ * @dev: the dev_t for the char device to be added.
+ * @fmt: string for the class device's name
+ *
+ * This function can be used by char device classes. A struct
+ * device will be created in sysfs, registered to the specified
+ * class.
+ * A "dev" file will be created, showing the dev_t for the device, if
+ * the dev_t is not 0,0.
+ * If a pointer to a parent struct device is passed in, the newly
+ * created struct device will be a child of that device in sysfs. The
+ * pointer to the struct device will be returned from the call. Any
+ * further sysfs files that might be required can be created using this
+ * pointer.
+ *
+ * Note: the struct class passed to this function must have previously
+ * been created with a call to class_create().
+ */
+struct device *device_create(struct class *class, struct device *parent,
+ dev_t devt, char *fmt, ...)
+{
+ va_list args;
+ struct device *dev = NULL;
+ int retval = -ENODEV;
+
+ if (class == NULL || IS_ERR(class))
+ goto error;
+ if (parent == NULL) {
+ printk(KERN_WARNING "%s does not work yet for NULL parents\n", __FUNCTION__);
+ goto error;
+ }
+
+ dev = kzalloc(sizeof(*dev), GFP_KERNEL);
+ if (!dev) {
+ retval = -ENOMEM;
+ goto error;
+ }
+
+ dev->devt = devt;
+ dev->class = class;
+ dev->parent = parent;
+ dev->release = device_create_release;
+
+ va_start(args, fmt);
+ vsnprintf(dev->bus_id, BUS_ID_SIZE, fmt, args);
+ va_end(args);
+ retval = device_register(dev);
+ if (retval)
+ goto error;
+
+ return dev;
+
+error:
+ kfree(dev);
+ return ERR_PTR(retval);
+}
+EXPORT_SYMBOL_GPL(device_create);
+
+/**
+ * device_destroy - removes a device that was created with device_create()
+ * @class: the pointer to the struct class that this device was registered * with.
+ * @dev: the dev_t of the device that was previously registered.
+ *
+ * This call unregisters and cleans up a class device that was created with a
+ * call to class_device_create()
+ */
+void device_destroy(struct class *class, dev_t devt)
+{
+ struct device *dev = NULL;
+ struct device *dev_tmp;
+
+ down(&class->sem);
+ list_for_each_entry(dev_tmp, &class->devices, node) {
+ if (dev_tmp->devt == devt) {
+ dev = dev_tmp;
+ break;
+ }
+ }
+ up(&class->sem);
+
+ if (dev)
+ device_unregister(dev);
+}
+EXPORT_SYMBOL_GPL(device_destroy);