[PATCH] driver core : fix request_firmware_nowait

From: tom . leiming
Date: Fri May 22 2009 - 11:06:58 EST


From: Ming Lei <tom.leiming@xxxxxxxxx>

request_firmware_nowait declares it can be called in non-sleep contexts,
but kthead_run which is called by request_firmware_nowait may sleep.
So fix it by starting thread in workqueue to request firmware asynchronously.

Signed-off-by: Ming Lei <tom.leiming@xxxxxxxxx>
---
drivers/base/firmware_class.c | 66 ++++++++++++++++++++++++++++++++++------
1 files changed, 56 insertions(+), 10 deletions(-)

diff --git a/drivers/base/firmware_class.c b/drivers/base/firmware_class.c
index 2d296b7..0388cf7 100644
--- a/drivers/base/firmware_class.c
+++ b/drivers/base/firmware_class.c
@@ -573,15 +573,25 @@ release_firmware(const struct firmware *fw)

/* Async support */
struct firmware_work {
- struct work_struct work;
struct module *module;
const char *name;
struct device *device;
void *context;
void (*cont)(const struct firmware *fw, void *context);
int uevent;
+ struct list_head list;
+ struct task_struct *task;
+};
+
+struct firmware_schedule{
+ spinlock_t lock;
+ struct list_head head;
+ struct work_struct work;
};

+static struct firmware_schedule fw_schedule;
+
+
static int
request_firmware_work_func(void *arg)
{
@@ -605,6 +615,35 @@ request_firmware_work_func(void *arg)
return ret;
}

+static void fw_schedule_fun(struct work_struct *work)
+{
+ struct firmware_schedule *fs =
+ container_of(work, struct firmware_schedule, work);
+ unsigned long flags;
+
+ spin_lock_irqsave(&fs->lock, flags);
+ while (!list_empty(&fs->head)) {
+ struct firmware_work *fw_work;
+
+ fw_work = list_entry(fs->head.next, struct firmware_work, list);
+ list_del(&fw_work->list);
+
+ spin_unlock_irqrestore(&fs->lock, flags);
+ fw_work->task = kthread_run(request_firmware_work_func, fw_work,
+ "firmware/%s", fw_work->name);
+ spin_lock_irqsave(&fs->lock, flags);
+
+ if (IS_ERR(fw_work->task)) {
+ fw_work->cont(NULL, fw_work->context);
+ module_put(fw_work->module);
+ kfree(fw_work);
+ dev_err(fw_work->device, "%s: kthread_run failed\n",
+ __func__);
+ }
+ }
+ spin_unlock_irqrestore(&fs->lock, flags);
+}
+
/**
* request_firmware_nowait: asynchronous version of request_firmware
* @module: module requesting the firmware
@@ -626,7 +665,8 @@ request_firmware_nowait(
const char *name, struct device *device, void *context,
void (*cont)(const struct firmware *fw, void *context))
{
- struct task_struct *task;
+ unsigned long flags;
+ struct firmware_schedule *fs = &fw_schedule;
struct firmware_work *fw_work = kmalloc(sizeof (struct firmware_work),
GFP_ATOMIC);

@@ -646,15 +686,14 @@ request_firmware_nowait(
.uevent = uevent,
};

- task = kthread_run(request_firmware_work_func, fw_work,
- "firmware/%s", name);
+ INIT_LIST_HEAD(&fw_work->list);
+
+ spin_lock_irqsave(&fs->lock, flags);
+ list_add_tail(&fw_work->list, &fs->head);
+ spin_unlock_irqrestore(&fs->lock, flags);
+
+ schedule_work(&fs->work);

- if (IS_ERR(task)) {
- fw_work->cont(NULL, fw_work->context);
- module_put(fw_work->module);
- kfree(fw_work);
- return PTR_ERR(task);
- }
return 0;
}

@@ -662,6 +701,13 @@ static int __init
firmware_class_init(void)
{
int error;
+ struct firmware_schedule *fs = &fw_schedule;
+
+ memset(fs, 0, sizeof(*fs));
+ spin_lock_init(&fs->lock);
+ INIT_LIST_HEAD(&fs->head);
+ INIT_WORK(&fs->work, fw_schedule_fun);
+
error = class_register(&firmware_class);
if (error) {
printk(KERN_ERR "%s: class_register failed\n", __func__);
--
1.6.0.GIT

--
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at http://vger.kernel.org/majordomo-info.html
Please read the FAQ at http://www.tux.org/lkml/