Merge to Fedora kernel-2.6.18-1.2224_FC5 patched with stable patch-2.6.18.1-vs2.0...
[linux-2.6.git] / drivers / base / core.c
index 6b355bd..be6b5bc 100644 (file)
@@ -8,13 +8,13 @@
  *
  */
 
-#include <linux/config.h>
 #include <linux/device.h>
 #include <linux/err.h>
 #include <linux/init.h>
 #include <linux/module.h>
 #include <linux/slab.h>
 #include <linux/string.h>
+#include <linux/kdev_t.h>
 
 #include <asm/semaphore.h>
 
@@ -28,6 +28,22 @@ int (*platform_notify_remove)(struct device * dev) = NULL;
  * 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)
 
@@ -98,6 +114,8 @@ static int dev_uevent_filter(struct kset *kset, struct kobject *kobj)
                struct device *dev = to_dev(kobj);
                if (dev->bus)
                        return 1;
+               if (dev->class)
+                       return 1;
        }
        return 0;
 }
@@ -106,7 +124,11 @@ 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_uevent(struct kset *kset, struct kobject *kobj, char **envp,
@@ -117,6 +139,16 @@ static int dev_uevent(struct kset *kset, struct kobject *kobj, char **envp,
        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_uevent_var(envp, num_envp, &i,
@@ -161,6 +193,12 @@ static ssize_t store_uevent(struct device *dev, struct device_attribute *attr,
        return count;
 }
 
+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.
  */
@@ -231,6 +269,7 @@ void device_initialize(struct device *dev)
        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);
 }
@@ -249,6 +288,7 @@ void device_initialize(struct device *dev)
 int device_add(struct device *dev)
 {
        struct device *parent = NULL;
+       char *class_name = NULL;
        int error = -EINVAL;
 
        dev = get_device(dev);
@@ -274,23 +314,69 @@ int device_add(struct device *dev)
        dev->uevent_attr.store = store_uevent;
        device_create_file(dev, &dev->uevent_attr);
 
-       kobject_uevent(&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:
+       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:
@@ -362,9 +448,23 @@ void put_device(struct device * dev)
 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
@@ -449,3 +549,98 @@ EXPORT_SYMBOL_GPL(put_device);
 
 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
+ * @class: 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
+ * @devt: the dev_t for the char device to be added
+ * @fmt: string for the 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: pointer to the struct class that this device was registered with
+ * @devt: the dev_t of the device that was previously registered
+ *
+ * This call unregisters and cleans up a device that was created with a
+ * call to 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);