Skip to content

Commit

Permalink
---
Browse files Browse the repository at this point in the history
yaml
---
r: 324376
b: refs/heads/master
c: 37276a5
h: refs/heads/master
v: v3
  • Loading branch information
Ming Lei authored and Greg Kroah-Hartman committed Aug 16, 2012
1 parent cbcd075 commit 4a935bf
Show file tree
Hide file tree
Showing 2 changed files with 216 additions and 7 deletions.
2 changes: 1 addition & 1 deletion [refs]
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
---
refs/heads/master: bddb1b9078505bb0e430c87c5015c0963f7a594f
refs/heads/master: 37276a51f803ef63be988e26293ac85188475556
221 changes: 215 additions & 6 deletions trunk/drivers/base/firmware_class.c
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,11 @@
#include <linux/slab.h>
#include <linux/sched.h>
#include <linux/list.h>
#include <linux/async.h>
#include <linux/pm.h>

#include "base.h"
#include "power/power.h"

MODULE_AUTHOR("Manuel Estrada Sainz");
MODULE_DESCRIPTION("Multi purpose firmware loading support");
Expand Down Expand Up @@ -90,6 +95,19 @@ struct firmware_cache {
/* firmware_buf instance will be added into the below list */
spinlock_t lock;
struct list_head head;

/*
* Names of firmware images which have been cached successfully
* will be added into the below list so that device uncache
* helper can trace which firmware images have been cached
* before.
*/
spinlock_t name_lock;
struct list_head fw_names;

wait_queue_head_t wait_queue;
int cnt;
struct delayed_work work;
};

struct firmware_buf {
Expand All @@ -106,6 +124,11 @@ struct firmware_buf {
char fw_id[];
};

struct fw_cache_entry {
struct list_head list;
char name[];
};

struct firmware_priv {
struct timer_list timeout;
bool nowait;
Expand Down Expand Up @@ -220,12 +243,6 @@ static void fw_free_buf(struct firmware_buf *buf)
kref_put(&buf->ref, __fw_free_buf);
}

static void __init fw_cache_init(void)
{
spin_lock_init(&fw_cache.lock);
INIT_LIST_HEAD(&fw_cache.head);
}

static struct firmware_priv *to_firmware_priv(struct device *dev)
{
return container_of(dev, struct firmware_priv, dev);
Expand Down Expand Up @@ -1008,6 +1025,198 @@ int uncache_firmware(const char *fw_name)
return -EINVAL;
}

static struct fw_cache_entry *alloc_fw_cache_entry(const char *name)
{
struct fw_cache_entry *fce;

fce = kzalloc(sizeof(*fce) + strlen(name) + 1, GFP_ATOMIC);
if (!fce)
goto exit;

strcpy(fce->name, name);
exit:
return fce;
}

static void free_fw_cache_entry(struct fw_cache_entry *fce)
{
kfree(fce);
}

static void __async_dev_cache_fw_image(void *fw_entry,
async_cookie_t cookie)
{
struct fw_cache_entry *fce = fw_entry;
struct firmware_cache *fwc = &fw_cache;
int ret;

ret = cache_firmware(fce->name);
if (ret)
goto free;

spin_lock(&fwc->name_lock);
list_add(&fce->list, &fwc->fw_names);
spin_unlock(&fwc->name_lock);
goto drop_ref;

free:
free_fw_cache_entry(fce);
drop_ref:
spin_lock(&fwc->name_lock);
fwc->cnt--;
spin_unlock(&fwc->name_lock);

wake_up(&fwc->wait_queue);
}

/* called with dev->devres_lock held */
static void dev_create_fw_entry(struct device *dev, void *res,
void *data)
{
struct fw_name_devm *fwn = res;
const char *fw_name = fwn->name;
struct list_head *head = data;
struct fw_cache_entry *fce;

fce = alloc_fw_cache_entry(fw_name);
if (fce)
list_add(&fce->list, head);
}

static int devm_name_match(struct device *dev, void *res,
void *match_data)
{
struct fw_name_devm *fwn = res;
return (fwn->magic == (unsigned long)match_data);
}

static void dev_cache_fw_image(struct device *dev)
{
LIST_HEAD(todo);
struct fw_cache_entry *fce;
struct fw_cache_entry *fce_next;
struct firmware_cache *fwc = &fw_cache;

devres_for_each_res(dev, fw_name_devm_release,
devm_name_match, &fw_cache,
dev_create_fw_entry, &todo);

list_for_each_entry_safe(fce, fce_next, &todo, list) {
list_del(&fce->list);

spin_lock(&fwc->name_lock);
fwc->cnt++;
spin_unlock(&fwc->name_lock);

async_schedule(__async_dev_cache_fw_image, (void *)fce);
}
}

static void __device_uncache_fw_images(void)
{
struct firmware_cache *fwc = &fw_cache;
struct fw_cache_entry *fce;

spin_lock(&fwc->name_lock);
while (!list_empty(&fwc->fw_names)) {
fce = list_entry(fwc->fw_names.next,
struct fw_cache_entry, list);
list_del(&fce->list);
spin_unlock(&fwc->name_lock);

uncache_firmware(fce->name);
free_fw_cache_entry(fce);

spin_lock(&fwc->name_lock);
}
spin_unlock(&fwc->name_lock);
}

/**
* device_cache_fw_images - cache devices' firmware
*
* If one device called request_firmware or its nowait version
* successfully before, the firmware names are recored into the
* device's devres link list, so device_cache_fw_images can call
* cache_firmware() to cache these firmwares for the device,
* then the device driver can load its firmwares easily at
* time when system is not ready to complete loading firmware.
*/
static void device_cache_fw_images(void)
{
struct firmware_cache *fwc = &fw_cache;
struct device *dev;
DEFINE_WAIT(wait);

pr_debug("%s\n", __func__);

device_pm_lock();
list_for_each_entry(dev, &dpm_list, power.entry)
dev_cache_fw_image(dev);
device_pm_unlock();

/* wait for completion of caching firmware for all devices */
spin_lock(&fwc->name_lock);
for (;;) {
prepare_to_wait(&fwc->wait_queue, &wait,
TASK_UNINTERRUPTIBLE);
if (!fwc->cnt)
break;

spin_unlock(&fwc->name_lock);

schedule();

spin_lock(&fwc->name_lock);
}
spin_unlock(&fwc->name_lock);
finish_wait(&fwc->wait_queue, &wait);
}

/**
* device_uncache_fw_images - uncache devices' firmware
*
* uncache all firmwares which have been cached successfully
* by device_uncache_fw_images earlier
*/
static void device_uncache_fw_images(void)
{
pr_debug("%s\n", __func__);
__device_uncache_fw_images();
}

static void device_uncache_fw_images_work(struct work_struct *work)
{
device_uncache_fw_images();
}

/**
* device_uncache_fw_images_delay - uncache devices firmwares
* @delay: number of milliseconds to delay uncache device firmwares
*
* uncache all devices's firmwares which has been cached successfully
* by device_cache_fw_images after @delay milliseconds.
*/
static void device_uncache_fw_images_delay(unsigned long delay)
{
schedule_delayed_work(&fw_cache.work,
msecs_to_jiffies(delay));
}

static void __init fw_cache_init(void)
{
spin_lock_init(&fw_cache.lock);
INIT_LIST_HEAD(&fw_cache.head);

spin_lock_init(&fw_cache.name_lock);
INIT_LIST_HEAD(&fw_cache.fw_names);
fw_cache.cnt = 0;

init_waitqueue_head(&fw_cache.wait_queue);
INIT_DELAYED_WORK(&fw_cache.work,
device_uncache_fw_images_work);
}

static int __init firmware_class_init(void)
{
fw_cache_init();
Expand Down

0 comments on commit 4a935bf

Please sign in to comment.