[PATCH] sysfs and driver core: add callback helper, used by SCSI and S390

Alan Stern
2007-03-15T19:50:38Z

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>

---

Index: 2.6.21-rc3-git9/include/linux/sysfs.h
===================================================================
--- 2.6.21-rc3-git9.orig/include/linux/sysfs.h
+++ 2.6.21-rc3-git9/include/linux/sysfs.h
@@ -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;
Index: 2.6.21-rc3-git9/fs/sysfs/file.c
===================================================================
--- 2.6.21-rc3-git9.orig/fs/sysfs/file.c
+++ 2.6.21-rc3-git9/fs/sysfs/file.c
@@ -629,6 +629,60 @@ void sysfs_remove_file_from_group(struct
}
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: 2.6.21-rc3-git9/include/linux/device.h
===================================================================
--- 2.6.21-rc3-git9.orig/include/linux/device.h
+++ 2.6.21-rc3-git9/include/linux/device.h
@@ -353,6 +353,8 @@ extern int __must_check device_create_bi
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: 2.6.21-rc3-git9/drivers/base/core.c
===================================================================
--- 2.6.21-rc3-git9.orig/drivers/base/core.c
+++ 2.6.21-rc3-git9/drivers/base/core.c
@@ -407,6 +407,35 @@ void device_remove_bin_file(struct devic
}
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: 2.6.21-rc3-git9/drivers/scsi/scsi_sysfs.c
===================================================================
--- 2.6.21-rc3-git9.orig/drivers/scsi/scsi_sysfs.c
+++ 2.6.21-rc3-git9/drivers/scsi/scsi_sysfs.c
@@ -452,10 +452,22 @@ store_rescan_field (struct device *dev,
}
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: 2.6.21-rc3-git9/drivers/s390/cio/ccwgroup.c
===================================================================
--- 2.6.21-rc3-git9.orig/drivers/s390/cio/ccwgroup.c
+++ 2.6.21-rc3-git9/drivers/s390/cio/ccwgroup.c
@@ -71,19 +71,31 @@ __ccwgroup_remove_symlinks(struct ccwgro
* 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;
}