Skip to content

Commit

Permalink
[PATCH] sysfs and driver core: add callback helper, used by SCSI and …
Browse files Browse the repository at this point in the history
…S390

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>
  • Loading branch information
Alan Stern authored and Linus Torvalds committed Mar 15, 2007
1 parent 6ab27c6 commit d9a9cdf
Show file tree
Hide file tree
Showing 6 changed files with 122 additions and 4 deletions.
29 changes: 29 additions & 0 deletions drivers/base/core.c
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
18 changes: 15 additions & 3 deletions drivers/s390/cio/ccwgroup.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}

Expand Down
14 changes: 13 additions & 1 deletion drivers/scsi/scsi_sysfs.c
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
54 changes: 54 additions & 0 deletions fs/sysfs/file.c
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
2 changes: 2 additions & 0 deletions include/linux/device.h
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
9 changes: 9 additions & 0 deletions include/linux/sysfs.h
Original file line number Diff line number Diff line change
Expand Up @@ -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 *);

Expand Down Expand Up @@ -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;
Expand Down

0 comments on commit d9a9cdf

Please sign in to comment.