aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAlan Stern <stern@rowland.harvard.edu>2007-04-26 00:12:04 -0700
committerGreg Kroah-Hartman <gregkh@suse.de>2007-04-27 10:57:32 -0700
commit523ded71de0c5e66973335bf99a80edfda9f401b (patch)
tree20f47e8ed91018977d9fb7f4169a6ef8db407b82
parentfa1a8c23eb7d3ded8a3c6d0e653339a2bc7fca9e (diff)
device_schedule_callback() needs a module reference
This patch (as896b) fixes an oversight in the design of device_schedule_callback(). It is necessary to acquire a reference to the module owning the callback routine, to prevent the module from being unloaded before the callback can run. Signed-off-by: Alan Stern <stern@rowland.harvard.edu> Cc: Satyam Sharma <satyam.sharma@gmail.com> Cc: Neil Brown <neilb@suse.de> Cc: Cornelia Huck <cornelia.huck@de.ibm.com> Signed-off-by: Andrew Morton <akpm@linux-foundation.org> Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de>
-rw-r--r--drivers/base/core.c16
-rw-r--r--fs/sysfs/file.c14
-rw-r--r--include/linux/device.h8
-rw-r--r--include/linux/sysfs.h4
4 files changed, 29 insertions, 13 deletions
diff --git a/drivers/base/core.c b/drivers/base/core.c
index f69305c7269..8aa090da1cd 100644
--- a/drivers/base/core.c
+++ b/drivers/base/core.c
@@ -480,9 +480,10 @@ 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
+ * device_schedule_callback_owner - helper to schedule a callback for a device
* @dev: device.
* @func: callback function to invoke later.
+ * @owner: module owning the callback routine
*
* Attribute methods must not unregister themselves or their parent device
* (which would amount to the same thing). Attempts to do so will deadlock,
@@ -493,20 +494,23 @@ EXPORT_SYMBOL_GPL(device_remove_bin_file);
* argument in the workqueue's process context. @dev will be pinned until
* @func returns.
*
+ * This routine is usually called via the inline device_schedule_callback(),
+ * which automatically sets @owner to THIS_MODULE.
+ *
* Returns 0 if the request was submitted, -ENOMEM if storage could not
- * be allocated.
+ * be allocated, -ENODEV if a reference to @owner isn't available.
*
* 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 *))
+int device_schedule_callback_owner(struct device *dev,
+ void (*func)(struct device *), struct module *owner)
{
return sysfs_schedule_callback(&dev->kobj,
- (void (*)(void *)) func, dev);
+ (void (*)(void *)) func, dev, owner);
}
-EXPORT_SYMBOL_GPL(device_schedule_callback);
+EXPORT_SYMBOL_GPL(device_schedule_callback_owner);
static void klist_children_get(struct klist_node *n)
{
diff --git a/fs/sysfs/file.c b/fs/sysfs/file.c
index fc4633378dc..db0413a411d 100644
--- a/fs/sysfs/file.c
+++ b/fs/sysfs/file.c
@@ -633,6 +633,7 @@ struct sysfs_schedule_callback_struct {
struct kobject *kobj;
void (*func)(void *);
void *data;
+ struct module *owner;
struct work_struct work;
};
@@ -643,6 +644,7 @@ static void sysfs_schedule_callback_work(struct work_struct *work)
(ss->func)(ss->data);
kobject_put(ss->kobj);
+ module_put(ss->owner);
kfree(ss);
}
@@ -651,6 +653,7 @@ static void sysfs_schedule_callback_work(struct work_struct *work)
* @kobj: object we're acting for.
* @func: callback function to invoke later.
* @data: argument to pass to @func.
+ * @owner: module owning the callback code
*
* sysfs attribute methods must not unregister themselves or their parent
* kobject (which would amount to the same thing). Attempts to do so will
@@ -663,20 +666,25 @@ static void sysfs_schedule_callback_work(struct work_struct *work)
* until @func returns.
*
* Returns 0 if the request was submitted, -ENOMEM if storage could not
- * be allocated.
+ * be allocated, -ENODEV if a reference to @owner isn't available.
*/
int sysfs_schedule_callback(struct kobject *kobj, void (*func)(void *),
- void *data)
+ void *data, struct module *owner)
{
struct sysfs_schedule_callback_struct *ss;
+ if (!try_module_get(owner))
+ return -ENODEV;
ss = kmalloc(sizeof(*ss), GFP_KERNEL);
- if (!ss)
+ if (!ss) {
+ module_put(owner);
return -ENOMEM;
+ }
kobject_get(kobj);
ss->kobj = kobj;
ss->func = func;
ss->data = data;
+ ss->owner = owner;
INIT_WORK(&ss->work, sysfs_schedule_callback_work);
schedule_work(&ss->work);
return 0;
diff --git a/include/linux/device.h b/include/linux/device.h
index af603a13769..8511d14071b 100644
--- a/include/linux/device.h
+++ b/include/linux/device.h
@@ -367,8 +367,12 @@ 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 *));
+extern int device_schedule_callback_owner(struct device *dev,
+ void (*func)(struct device *), struct module *owner);
+
+/* This is a macro to avoid include problems with THIS_MODULE */
+#define device_schedule_callback(dev, func) \
+ device_schedule_callback_owner(dev, func, THIS_MODULE)
/* device resource management */
typedef void (*dr_release_t)(struct device *dev, void *res);
diff --git a/include/linux/sysfs.h b/include/linux/sysfs.h
index fea9a6b3fb7..7d5d1ec95c2 100644
--- a/include/linux/sysfs.h
+++ b/include/linux/sysfs.h
@@ -80,7 +80,7 @@ struct sysfs_ops {
#ifdef CONFIG_SYSFS
extern int sysfs_schedule_callback(struct kobject *kobj,
- void (*func)(void *), void *data);
+ void (*func)(void *), void *data, struct module *owner);
extern int __must_check
sysfs_create_dir(struct kobject *, struct dentry *);
@@ -137,7 +137,7 @@ extern int __must_check sysfs_init(void);
#else /* CONFIG_SYSFS */
static inline int sysfs_schedule_callback(struct kobject *kobj,
- void (*func)(void *), void *data)
+ void (*func)(void *), void *data, struct module *owner)
{
return -ENOSYS;
}