[PATCH] sysfs and driver core: add callback helper, used by SCSI and S390
authorAlan Stern <stern@rowland.harvard.edu>
Thu, 15 Mar 2007 19:50:34 +0000 (15:50 -0400)
committerLinus Torvalds <torvalds@woody.linux-foundation.org>
Thu, 15 Mar 2007 22:29:26 +0000 (15:29 -0700)
This patch (as868) adds a helper routine for device drivers that need
to set up a callback to perform some action in a different process's
context.  This is intended for use by attribute methods that want to
unregister themselves or their parent device.  Attribute method calls
are mutually exclusive with unregistration, so such actions cannot be
taken directly.

Two attribute methods are converted to use the new helper routine: one
for SCSI device deletion and one for System/390 ccwgroup devices.

Signed-off-by: Alan Stern <stern@rowland.harvard.edu>
Cc: Hugh Dickins <hugh@veritas.com>
Cc: Cornelia Huck <cornelia.huck@de.ibm.com>
Cc: Oliver Neukum <oneukum@suse.de>
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
drivers/base/core.c
drivers/s390/cio/ccwgroup.c
drivers/scsi/scsi_sysfs.c
fs/sysfs/file.c
include/linux/device.h
include/linux/sysfs.h

index f191afe62b4d06633dc84490ec6e630d983c6a1a..ad0f4a2f25c466079c6c471f8b0f77a83b2845f0 100644 (file)
@@ -407,6 +407,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);
index d48e3ca4752c444ba5d58d3c38abaa6a2daeeed9..5aeb68e732b024768dff52b0218b57fbfa106913 100644 (file)
@@ -71,19 +71,31 @@ __ccwgroup_remove_symlinks(struct ccwgroup_device *gdev)
  * Provide an 'ungroup' attribute so the user can remove group devices no
  * longer needed or accidentially created. Saves memory :)
  */
+static void ccwgroup_ungroup_callback(struct device *dev)
+{
+       struct ccwgroup_device *gdev = to_ccwgroupdev(dev);
+
+       __ccwgroup_remove_symlinks(gdev);
+       device_unregister(dev);
+}
+
 static ssize_t
 ccwgroup_ungroup_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count)
 {
        struct ccwgroup_device *gdev;
+       int rc;
 
        gdev = to_ccwgroupdev(dev);
 
        if (gdev->state != CCWGROUP_OFFLINE)
                return -EINVAL;
 
-       __ccwgroup_remove_symlinks(gdev);
-       device_unregister(dev);
-
+       /* Note that we cannot unregister the device from one of its
+        * attribute methods, so we have to use this roundabout approach.
+        */
+       rc = device_schedule_callback(dev, ccwgroup_ungroup_callback);
+       if (rc)
+               count = rc;
        return count;
 }
 
index c275dcac3f18b7b4abc1bd984cb1601de3f0a770..939de0de18bc5d30e271fe864fbd0c962f1b4ef6 100644 (file)
@@ -452,10 +452,22 @@ store_rescan_field (struct device *dev, struct device_attribute *attr, const cha
 }
 static DEVICE_ATTR(rescan, S_IWUSR, NULL, store_rescan_field);
 
+static void sdev_store_delete_callback(struct device *dev)
+{
+       scsi_remove_device(to_scsi_device(dev));
+}
+
 static ssize_t sdev_store_delete(struct device *dev, struct device_attribute *attr, const char *buf,
                                 size_t count)
 {
-       scsi_remove_device(to_scsi_device(dev));
+       int rc;
+
+       /* An attribute cannot be unregistered by one of its own methods,
+        * so we have to use this roundabout approach.
+        */
+       rc = device_schedule_callback(dev, sdev_store_delete_callback);
+       if (rc)
+               count = rc;
        return count;
 };
 static DEVICE_ATTR(delete, S_IWUSR, NULL, sdev_store_delete);
index 8d4d839a9d887eb4a753da824e55a98542b26f21..1bafdf6e171cb5c73d9c0650368b2ad8795684e6 100644 (file)
@@ -629,6 +629,60 @@ void sysfs_remove_file_from_group(struct kobject *kobj,
 }
 EXPORT_SYMBOL_GPL(sysfs_remove_file_from_group);
 
+struct sysfs_schedule_callback_struct {
+       struct kobject          *kobj;
+       void                    (*func)(void *);
+       void                    *data;
+       struct work_struct      work;
+};
+
+static void sysfs_schedule_callback_work(struct work_struct *work)
+{
+       struct sysfs_schedule_callback_struct *ss = container_of(work,
+                       struct sysfs_schedule_callback_struct, work);
+
+       (ss->func)(ss->data);
+       kobject_put(ss->kobj);
+       kfree(ss);
+}
+
+/**
+ * sysfs_schedule_callback - helper to schedule a callback for a kobject
+ * @kobj: object we're acting for.
+ * @func: callback function to invoke later.
+ * @data: argument to pass to @func.
+ *
+ * sysfs attribute methods must not unregister themselves or their parent
+ * kobject (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 @data as its
+ * argument in the workqueue's process context.  @kobj will be pinned
+ * until @func returns.
+ *
+ * Returns 0 if the request was submitted, -ENOMEM if storage could not
+ * be allocated.
+ */
+int sysfs_schedule_callback(struct kobject *kobj, void (*func)(void *),
+               void *data)
+{
+       struct sysfs_schedule_callback_struct *ss;
+
+       ss = kmalloc(sizeof(*ss), GFP_KERNEL);
+       if (!ss)
+               return -ENOMEM;
+       kobject_get(kobj);
+       ss->kobj = kobj;
+       ss->func = func;
+       ss->data = data;
+       INIT_WORK(&ss->work, sysfs_schedule_callback_work);
+       schedule_work(&ss->work);
+       return 0;
+}
+EXPORT_SYMBOL_GPL(sysfs_schedule_callback);
+
 
 EXPORT_SYMBOL_GPL(sysfs_create_file);
 EXPORT_SYMBOL_GPL(sysfs_remove_file);
index 39a3199a826db2726e60475195f64f03465c5acb..caad9bba965277f7abe77c25f234e7b3c3d07f3f 100644 (file)
@@ -353,6 +353,8 @@ extern int __must_check device_create_bin_file(struct device *dev,
                                               struct bin_attribute *attr);
 extern void device_remove_bin_file(struct device *dev,
                                   struct bin_attribute *attr);
+extern int device_schedule_callback(struct device *dev,
+               void (*func)(struct device *));
 
 /* device resource management */
 typedef void (*dr_release_t)(struct device *dev, void *res);
index 523405e1e1f6463a5f8aa925655d97234adaefae..0544edda716888ee3ee26823be8cd82ac497c606 100644 (file)
@@ -78,6 +78,9 @@ struct sysfs_ops {
 
 #ifdef CONFIG_SYSFS
 
+extern int sysfs_schedule_callback(struct kobject *kobj,
+               void (*func)(void *), void *data);
+
 extern int __must_check
 sysfs_create_dir(struct kobject *, struct dentry *);
 
@@ -132,6 +135,12 @@ extern int __must_check sysfs_init(void);
 
 #else /* CONFIG_SYSFS */
 
+static inline int sysfs_schedule_callback(struct kobject *kobj,
+               void (*func)(void *), void *data)
+{
+       return -ENOSYS;
+}
+
 static inline int sysfs_create_dir(struct kobject * k, struct dentry *shadow)
 {
        return 0;