]> www.pilppa.org Git - linux-2.6-omap-h63xx.git/blobdiff - drivers/base/core.c
driver core: fix namespace issue with devices assigned to classes
[linux-2.6-omap-h63xx.git] / drivers / base / core.c
index f191afe62b4d06633dc84490ec6e630d983c6a1a..658eae5dacda194d7d3396b16616e124b6cdedd4 100644 (file)
 int (*platform_notify)(struct device * dev) = NULL;
 int (*platform_notify_remove)(struct device * dev) = NULL;
 
-/*
- * Detect the LANANA-assigned LOCAL/EXPERIMENTAL majors
- */
-bool is_lanana_major(unsigned int major)
-{
-       if (major >= 60 && major <= 63)
-               return 1;
-       if (major >= 120 && major <= 127)
-               return 1;
-       if (major >= 240 && major <= 254)
-               return 1;
-       return 0;
-}
-
 /*
  * sysfs bindings for devices.
  */
@@ -407,6 +393,35 @@ void device_remove_bin_file(struct device *dev, struct bin_attribute *attr)
 }
 EXPORT_SYMBOL_GPL(device_remove_bin_file);
 
+/**
+ * device_schedule_callback - helper to schedule a callback for a device
+ * @dev: device.
+ * @func: callback function to invoke later.
+ *
+ * Attribute methods must not unregister themselves or their parent device
+ * (which would amount to the same thing).  Attempts to do so will deadlock,
+ * since unregistration is mutually exclusive with driver callbacks.
+ *
+ * Instead methods can call this routine, which will attempt to allocate
+ * and schedule a workqueue request to call back @func with @dev as its
+ * argument in the workqueue's process context.  @dev will be pinned until
+ * @func returns.
+ *
+ * Returns 0 if the request was submitted, -ENOMEM if storage could not
+ * be allocated.
+ *
+ * NOTE: This routine won't work if CONFIG_SYSFS isn't set!  It uses an
+ * underlying sysfs routine (since it is intended for use by attribute
+ * methods), and if sysfs isn't available you'll get nothing but -ENOSYS.
+ */
+int device_schedule_callback(struct device *dev,
+               void (*func)(struct device *))
+{
+       return sysfs_schedule_callback(&dev->kobj,
+                       (void (*)(void *)) func, dev);
+}
+EXPORT_SYMBOL_GPL(device_schedule_callback);
+
 static void klist_children_get(struct klist_node *n)
 {
        struct device *dev = container_of(n, struct device, knode_parent);
@@ -462,34 +477,58 @@ static struct kobject * get_device_parent(struct device *dev,
        return NULL;
 }
 #else
-static struct kobject * virtual_device_parent(struct device *dev)
+static struct kobject *virtual_device_parent(struct device *dev)
 {
-       if (!dev->class)
-               return ERR_PTR(-ENODEV);
-
-       if (!dev->class->virtual_dir) {
-               static struct kobject *virtual_dir = NULL;
+       static struct kobject *virtual_dir = NULL;
 
-               if (!virtual_dir)
-                       virtual_dir = kobject_add_dir(&devices_subsys.kset.kobj, "virtual");
-               dev->class->virtual_dir = kobject_add_dir(virtual_dir, dev->class->name);
-       }
+       if (!virtual_dir)
+               virtual_dir = kobject_add_dir(&devices_subsys.kset.kobj, "virtual");
 
-       return dev->class->virtual_dir;
+       return virtual_dir;
 }
 
 static struct kobject * get_device_parent(struct device *dev,
                                          struct device *parent)
 {
-       /* if this is a class device, and has no parent, create one */
-       if ((dev->class) && (parent == NULL)) {
-               return virtual_device_parent(dev);
-       } else if (parent)
+       if (dev->class) {
+               struct kobject *kobj = NULL;
+               struct kobject *parent_kobj;
+               struct kobject *k;
+
+               /*
+                * If we have no parent, we live in "virtual".
+                * Class-devices with a bus-device as parent, live
+                * in a class-directory to prevent namespace collisions.
+                */
+               if (parent == NULL)
+                       parent_kobj = virtual_device_parent(dev);
+               else if (parent->class)
+                       return &parent->kobj;
+               else
+                       parent_kobj = &parent->kobj;
+
+               /* find our class-directory at the parent and reference it */
+               spin_lock(&dev->class->class_dirs.list_lock);
+               list_for_each_entry(k, &dev->class->class_dirs.list, entry)
+                       if (k->parent == parent_kobj) {
+                               kobj = kobject_get(k);
+                               break;
+                       }
+               spin_unlock(&dev->class->class_dirs.list_lock);
+               if (kobj)
+                       return kobj;
+
+               /* or create a new class-directory at the parent device */
+               return kobject_kset_add_dir(&dev->class->class_dirs,
+                                           parent_kobj, dev->class->name);
+       }
+
+       if (parent)
                return &parent->kobj;
        return NULL;
 }
-
 #endif
+
 static int setup_parent(struct device *dev, struct device *parent)
 {
        struct kobject *kobj;
@@ -526,7 +565,6 @@ int device_add(struct device *dev)
        pr_debug("DEV: registering device: ID = '%s'\n", dev->bus_id);
 
        parent = get_device(dev->parent);
-
        error = setup_parent(dev, parent);
        if (error)
                goto Error;
@@ -662,15 +700,6 @@ int device_add(struct device *dev)
 #endif
                        sysfs_remove_link(&dev->kobj, "device");
                }
-
-               down(&dev->class->sem);
-               /* notify any interfaces that the device is now gone */
-               list_for_each_entry(class_intf, &dev->class->interfaces, node)
-                       if (class_intf->remove_dev)
-                               class_intf->remove_dev(dev, class_intf);
-               /* remove the device from the class list */
-               list_del_init(&dev->node);
-               up(&dev->class->sem);
        }
  ueventattrError:
        device_remove_file(dev, &dev->uevent_attr);
@@ -781,6 +810,31 @@ void device_del(struct device * dev)
                /* remove the device from the class list */
                list_del_init(&dev->node);
                up(&dev->class->sem);
+
+               /* If we live in a parent class-directory, unreference it */
+               if (dev->kobj.parent->kset == &dev->class->class_dirs) {
+                       struct device *d;
+                       int other = 0;
+
+                       /*
+                        * if we are the last child of our class, delete
+                        * our class-directory at this parent
+                        */
+                       down(&dev->class->sem);
+                       list_for_each_entry(d, &dev->class->devices, node) {
+                               if (d == dev)
+                                       continue;
+                               if (d->kobj.parent == dev->kobj.parent) {
+                                       other = 1;
+                                       break;
+                               }
+                       }
+                       if (!other)
+                               kobject_del(dev->kobj.parent);
+
+                       kobject_put(dev->kobj.parent);
+                       up(&dev->class->sem);
+               }
        }
        device_remove_file(dev, &dev->uevent_attr);
        device_remove_groups(dev);