mirror of
https://github.com/edk2-porting/linux-next.git
synced 2024-12-20 03:04:01 +08:00
wimax/i2400m: cache firmware on system suspend
In preparation for a reset_resume implementation, have the firmware image be cached in memory when the system goes to suspend and released when out. This is needed in case the device resets during suspend; the driver can't load firmware until resume is completed or bad deadlocks happen. The modus operandi for this was copied from the Orinoco USB driver. The caching is done with a kobject to avoid race conditions when releasing it. The fw loader path is altered only to first check for a cached image before trying to load from disk. A Power Management event notifier is register to call i2400m_fw_cache() or i2400m_fw_uncache() which take care of the actual cache management. Signed-off-by: Inaky Perez-Gonzalez <inaky@linux.intel.com>
This commit is contained in:
parent
3ef6129e57
commit
7b43ca708a
@ -66,6 +66,7 @@
|
|||||||
#include <linux/wimax/i2400m.h>
|
#include <linux/wimax/i2400m.h>
|
||||||
#include <linux/module.h>
|
#include <linux/module.h>
|
||||||
#include <linux/moduleparam.h>
|
#include <linux/moduleparam.h>
|
||||||
|
#include <linux/suspend.h>
|
||||||
|
|
||||||
#define D_SUBMODULE driver
|
#define D_SUBMODULE driver
|
||||||
#include "debug-levels.h"
|
#include "debug-levels.h"
|
||||||
@ -554,6 +555,51 @@ void i2400m_dev_stop(struct i2400m *i2400m)
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Listen to PM events to cache the firmware before suspend/hibernation
|
||||||
|
*
|
||||||
|
* When the device comes out of suspend, it might go into reset and
|
||||||
|
* firmware has to be uploaded again. At resume, most of the times, we
|
||||||
|
* can't load firmware images from disk, so we need to cache it.
|
||||||
|
*
|
||||||
|
* i2400m_fw_cache() will allocate a kobject and attach the firmware
|
||||||
|
* to it; that way we don't have to worry too much about the fw loader
|
||||||
|
* hitting a race condition.
|
||||||
|
*
|
||||||
|
* Note: modus operandi stolen from the Orinoco driver; thx.
|
||||||
|
*/
|
||||||
|
static
|
||||||
|
int i2400m_pm_notifier(struct notifier_block *notifier,
|
||||||
|
unsigned long pm_event,
|
||||||
|
void *unused)
|
||||||
|
{
|
||||||
|
struct i2400m *i2400m =
|
||||||
|
container_of(notifier, struct i2400m, pm_notifier);
|
||||||
|
struct device *dev = i2400m_dev(i2400m);
|
||||||
|
|
||||||
|
d_fnstart(3, dev, "(i2400m %p pm_event %lx)\n", i2400m, pm_event);
|
||||||
|
switch (pm_event) {
|
||||||
|
case PM_HIBERNATION_PREPARE:
|
||||||
|
case PM_SUSPEND_PREPARE:
|
||||||
|
i2400m_fw_cache(i2400m);
|
||||||
|
break;
|
||||||
|
case PM_POST_RESTORE:
|
||||||
|
/* Restore from hibernation failed. We need to clean
|
||||||
|
* up in exactly the same way, so fall through. */
|
||||||
|
case PM_POST_HIBERNATION:
|
||||||
|
case PM_POST_SUSPEND:
|
||||||
|
i2400m_fw_uncache(i2400m);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case PM_RESTORE_PREPARE:
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
d_fnend(3, dev, "(i2400m %p pm_event %lx) = void\n", i2400m, pm_event);
|
||||||
|
return NOTIFY_DONE;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* The device has rebooted; fix up the device and the driver
|
* The device has rebooted; fix up the device and the driver
|
||||||
*
|
*
|
||||||
@ -738,6 +784,9 @@ int i2400m_setup(struct i2400m *i2400m, enum i2400m_bri bm_flags)
|
|||||||
goto error_read_mac_addr;
|
goto error_read_mac_addr;
|
||||||
random_ether_addr(i2400m->src_mac_addr);
|
random_ether_addr(i2400m->src_mac_addr);
|
||||||
|
|
||||||
|
i2400m->pm_notifier.notifier_call = i2400m_pm_notifier;
|
||||||
|
register_pm_notifier(&i2400m->pm_notifier);
|
||||||
|
|
||||||
result = register_netdev(net_dev); /* Okey dokey, bring it up */
|
result = register_netdev(net_dev); /* Okey dokey, bring it up */
|
||||||
if (result < 0) {
|
if (result < 0) {
|
||||||
dev_err(dev, "cannot register i2400m network device: %d\n",
|
dev_err(dev, "cannot register i2400m network device: %d\n",
|
||||||
@ -783,6 +832,7 @@ error_wimax_dev_add:
|
|||||||
error_dev_start:
|
error_dev_start:
|
||||||
unregister_netdev(net_dev);
|
unregister_netdev(net_dev);
|
||||||
error_register_netdev:
|
error_register_netdev:
|
||||||
|
unregister_pm_notifier(&i2400m->pm_notifier);
|
||||||
error_read_mac_addr:
|
error_read_mac_addr:
|
||||||
error_bootrom_init:
|
error_bootrom_init:
|
||||||
d_fnend(3, dev, "(i2400m %p) = %d\n", i2400m, result);
|
d_fnend(3, dev, "(i2400m %p) = %d\n", i2400m, result);
|
||||||
@ -809,6 +859,7 @@ void i2400m_release(struct i2400m *i2400m)
|
|||||||
wimax_dev_rm(&i2400m->wimax_dev);
|
wimax_dev_rm(&i2400m->wimax_dev);
|
||||||
i2400m_dev_stop(i2400m);
|
i2400m_dev_stop(i2400m);
|
||||||
unregister_netdev(i2400m->wimax_dev.net_dev);
|
unregister_netdev(i2400m->wimax_dev.net_dev);
|
||||||
|
unregister_pm_notifier(&i2400m->pm_notifier);
|
||||||
kfree(i2400m->bm_ack_buf);
|
kfree(i2400m->bm_ack_buf);
|
||||||
kfree(i2400m->bm_cmd_buf);
|
kfree(i2400m->bm_cmd_buf);
|
||||||
d_fnend(3, dev, "(i2400m %p) = void\n", i2400m);
|
d_fnend(3, dev, "(i2400m %p) = void\n", i2400m);
|
||||||
|
@ -105,6 +105,13 @@
|
|||||||
* read an acknolwedgement from it (or an asynchronous notification)
|
* read an acknolwedgement from it (or an asynchronous notification)
|
||||||
* from it.
|
* from it.
|
||||||
*
|
*
|
||||||
|
* FIRMWARE LOADING
|
||||||
|
*
|
||||||
|
* Note that in some cases, we can't just load a firmware file (for
|
||||||
|
* example, when resuming). For that, we might cache the firmware
|
||||||
|
* file. Thus, when doing the bootstrap, if there is a cache firmware
|
||||||
|
* file, it is used; if not, loading from disk is attempted.
|
||||||
|
*
|
||||||
* ROADMAP
|
* ROADMAP
|
||||||
*
|
*
|
||||||
* i2400m_barker_db_init Called by i2400m_driver_init()
|
* i2400m_barker_db_init Called by i2400m_driver_init()
|
||||||
@ -114,9 +121,10 @@
|
|||||||
*
|
*
|
||||||
* i2400m_dev_bootstrap Called by __i2400m_dev_start()
|
* i2400m_dev_bootstrap Called by __i2400m_dev_start()
|
||||||
* request_firmware
|
* request_firmware
|
||||||
* i2400m_fw_check
|
* i2400m_fw_bootstrap
|
||||||
* i2400m_fw_hdr_check
|
* i2400m_fw_check
|
||||||
* i2400m_fw_dnload
|
* i2400m_fw_hdr_check
|
||||||
|
* i2400m_fw_dnload
|
||||||
* release_firmware
|
* release_firmware
|
||||||
*
|
*
|
||||||
* i2400m_fw_dnload
|
* i2400m_fw_dnload
|
||||||
@ -141,6 +149,10 @@
|
|||||||
*
|
*
|
||||||
* i2400m_bm_cmd_prepare Used by bus-drivers to prep
|
* i2400m_bm_cmd_prepare Used by bus-drivers to prep
|
||||||
* commands before sending
|
* commands before sending
|
||||||
|
*
|
||||||
|
* i2400m_pm_notifier Called on Power Management events
|
||||||
|
* i2400m_fw_cache
|
||||||
|
* i2400m_fw_uncache
|
||||||
*/
|
*/
|
||||||
#include <linux/firmware.h>
|
#include <linux/firmware.h>
|
||||||
#include <linux/sched.h>
|
#include <linux/sched.h>
|
||||||
@ -1459,6 +1471,61 @@ error_dev_rebooted:
|
|||||||
goto hw_reboot;
|
goto hw_reboot;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static
|
||||||
|
int i2400m_fw_bootstrap(struct i2400m *i2400m, const struct firmware *fw,
|
||||||
|
enum i2400m_bri flags)
|
||||||
|
{
|
||||||
|
int ret;
|
||||||
|
struct device *dev = i2400m_dev(i2400m);
|
||||||
|
const struct i2400m_bcf_hdr *bcf; /* Firmware data */
|
||||||
|
|
||||||
|
d_fnstart(5, dev, "(i2400m %p)\n", i2400m);
|
||||||
|
bcf = (void *) fw->data;
|
||||||
|
ret = i2400m_fw_check(i2400m, bcf, fw->size);
|
||||||
|
if (ret >= 0)
|
||||||
|
ret = i2400m_fw_dnload(i2400m, bcf, fw->size, flags);
|
||||||
|
if (ret < 0)
|
||||||
|
dev_err(dev, "%s: cannot use: %d, skipping\n",
|
||||||
|
i2400m->fw_name, ret);
|
||||||
|
kfree(i2400m->fw_hdrs);
|
||||||
|
i2400m->fw_hdrs = NULL;
|
||||||
|
d_fnend(5, dev, "(i2400m %p) = %d\n", i2400m, ret);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* Refcounted container for firmware data */
|
||||||
|
struct i2400m_fw {
|
||||||
|
struct kref kref;
|
||||||
|
const struct firmware *fw;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
static
|
||||||
|
void i2400m_fw_destroy(struct kref *kref)
|
||||||
|
{
|
||||||
|
struct i2400m_fw *i2400m_fw =
|
||||||
|
container_of(kref, struct i2400m_fw, kref);
|
||||||
|
release_firmware(i2400m_fw->fw);
|
||||||
|
kfree(i2400m_fw);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static
|
||||||
|
struct i2400m_fw *i2400m_fw_get(struct i2400m_fw *i2400m_fw)
|
||||||
|
{
|
||||||
|
if (i2400m_fw != NULL && i2400m_fw != (void *) ~0)
|
||||||
|
kref_get(&i2400m_fw->kref);
|
||||||
|
return i2400m_fw;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static
|
||||||
|
void i2400m_fw_put(struct i2400m_fw *i2400m_fw)
|
||||||
|
{
|
||||||
|
kref_put(&i2400m_fw->kref, i2400m_fw_destroy);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* i2400m_dev_bootstrap - Bring the device to a known state and upload firmware
|
* i2400m_dev_bootstrap - Bring the device to a known state and upload firmware
|
||||||
@ -1479,12 +1546,28 @@ int i2400m_dev_bootstrap(struct i2400m *i2400m, enum i2400m_bri flags)
|
|||||||
{
|
{
|
||||||
int ret, itr;
|
int ret, itr;
|
||||||
struct device *dev = i2400m_dev(i2400m);
|
struct device *dev = i2400m_dev(i2400m);
|
||||||
const struct firmware *fw;
|
struct i2400m_fw *i2400m_fw;
|
||||||
const struct i2400m_bcf_hdr *bcf; /* Firmware data */
|
const struct i2400m_bcf_hdr *bcf; /* Firmware data */
|
||||||
|
const struct firmware *fw;
|
||||||
const char *fw_name;
|
const char *fw_name;
|
||||||
|
|
||||||
d_fnstart(5, dev, "(i2400m %p)\n", i2400m);
|
d_fnstart(5, dev, "(i2400m %p)\n", i2400m);
|
||||||
|
|
||||||
|
ret = -ENODEV;
|
||||||
|
spin_lock(&i2400m->rx_lock);
|
||||||
|
i2400m_fw = i2400m_fw_get(i2400m->fw_cached);
|
||||||
|
spin_unlock(&i2400m->rx_lock);
|
||||||
|
if (i2400m_fw == (void *) ~0) {
|
||||||
|
dev_err(dev, "can't load firmware now!");
|
||||||
|
goto out;
|
||||||
|
} else if (i2400m_fw != NULL) {
|
||||||
|
dev_info(dev, "firmware %s: loading from cache\n",
|
||||||
|
i2400m->fw_name);
|
||||||
|
ret = i2400m_fw_bootstrap(i2400m, i2400m_fw->fw, flags);
|
||||||
|
i2400m_fw_put(i2400m_fw);
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
|
||||||
/* Load firmware files to memory. */
|
/* Load firmware files to memory. */
|
||||||
for (itr = 0, bcf = NULL, ret = -ENOENT; ; itr++) {
|
for (itr = 0, bcf = NULL, ret = -ENOENT; ; itr++) {
|
||||||
fw_name = i2400m->bus_fw_names[itr];
|
fw_name = i2400m->bus_fw_names[itr];
|
||||||
@ -1500,21 +1583,71 @@ int i2400m_dev_bootstrap(struct i2400m *i2400m, enum i2400m_bri flags)
|
|||||||
fw_name, ret);
|
fw_name, ret);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
bcf = (void *) fw->data;
|
|
||||||
i2400m->fw_name = fw_name;
|
i2400m->fw_name = fw_name;
|
||||||
ret = i2400m_fw_check(i2400m, bcf, fw->size);
|
ret = i2400m_fw_bootstrap(i2400m, fw, flags);
|
||||||
if (ret >= 0)
|
|
||||||
ret = i2400m_fw_dnload(i2400m, bcf, fw->size, flags);
|
|
||||||
if (ret < 0)
|
|
||||||
dev_err(dev, "%s: cannot use: %d, skipping\n",
|
|
||||||
fw_name, ret);
|
|
||||||
kfree(i2400m->fw_hdrs);
|
|
||||||
i2400m->fw_hdrs = NULL;
|
|
||||||
release_firmware(fw);
|
release_firmware(fw);
|
||||||
if (ret >= 0) /* firmware loaded succesfully */
|
if (ret >= 0) /* firmware loaded succesfully */
|
||||||
break;
|
break;
|
||||||
|
i2400m->fw_name = NULL;
|
||||||
}
|
}
|
||||||
|
out:
|
||||||
d_fnend(5, dev, "(i2400m %p) = %d\n", i2400m, ret);
|
d_fnend(5, dev, "(i2400m %p) = %d\n", i2400m, ret);
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
EXPORT_SYMBOL_GPL(i2400m_dev_bootstrap);
|
EXPORT_SYMBOL_GPL(i2400m_dev_bootstrap);
|
||||||
|
|
||||||
|
|
||||||
|
void i2400m_fw_cache(struct i2400m *i2400m)
|
||||||
|
{
|
||||||
|
int result;
|
||||||
|
struct i2400m_fw *i2400m_fw;
|
||||||
|
struct device *dev = i2400m_dev(i2400m);
|
||||||
|
|
||||||
|
/* if there is anything there, free it -- now, this'd be weird */
|
||||||
|
spin_lock(&i2400m->rx_lock);
|
||||||
|
i2400m_fw = i2400m->fw_cached;
|
||||||
|
spin_unlock(&i2400m->rx_lock);
|
||||||
|
if (i2400m_fw != NULL && i2400m_fw != (void *) ~0) {
|
||||||
|
i2400m_fw_put(i2400m_fw);
|
||||||
|
WARN(1, "%s:%u: still cached fw still present?\n",
|
||||||
|
__func__, __LINE__);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (i2400m->fw_name == NULL) {
|
||||||
|
dev_err(dev, "firmware n/a: can't cache\n");
|
||||||
|
i2400m_fw = (void *) ~0;
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
|
||||||
|
i2400m_fw = kzalloc(sizeof(*i2400m_fw), GFP_ATOMIC);
|
||||||
|
if (i2400m_fw == NULL)
|
||||||
|
goto out;
|
||||||
|
kref_init(&i2400m_fw->kref);
|
||||||
|
result = request_firmware(&i2400m_fw->fw, i2400m->fw_name, dev);
|
||||||
|
if (result < 0) {
|
||||||
|
dev_err(dev, "firmware %s: failed to cache: %d\n",
|
||||||
|
i2400m->fw_name, result);
|
||||||
|
kfree(i2400m_fw);
|
||||||
|
i2400m_fw = (void *) ~0;
|
||||||
|
} else
|
||||||
|
dev_info(dev, "firmware %s: cached\n", i2400m->fw_name);
|
||||||
|
out:
|
||||||
|
spin_lock(&i2400m->rx_lock);
|
||||||
|
i2400m->fw_cached = i2400m_fw;
|
||||||
|
spin_unlock(&i2400m->rx_lock);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void i2400m_fw_uncache(struct i2400m *i2400m)
|
||||||
|
{
|
||||||
|
struct i2400m_fw *i2400m_fw;
|
||||||
|
|
||||||
|
spin_lock(&i2400m->rx_lock);
|
||||||
|
i2400m_fw = i2400m->fw_cached;
|
||||||
|
i2400m->fw_cached = NULL;
|
||||||
|
spin_unlock(&i2400m->rx_lock);
|
||||||
|
|
||||||
|
if (i2400m_fw != NULL && i2400m_fw != (void *) ~0)
|
||||||
|
i2400m_fw_put(i2400m_fw);
|
||||||
|
}
|
||||||
|
|
||||||
|
@ -424,11 +424,21 @@ struct i2400m_barker_db;
|
|||||||
* @fw_hdrs: NULL terminated array of pointers to the firmware
|
* @fw_hdrs: NULL terminated array of pointers to the firmware
|
||||||
* headers. This is only available during firmware load time.
|
* headers. This is only available during firmware load time.
|
||||||
*
|
*
|
||||||
|
* @fw_cached: Used to cache firmware when the system goes to
|
||||||
|
* suspend/standby/hibernation (as on resume we can't read it). If
|
||||||
|
* NULL, no firmware was cached, read it. If ~0, you can't read
|
||||||
|
* any firmware files (the system still didn't come out of suspend
|
||||||
|
* and failed to cache one), so abort; otherwise, a valid cached
|
||||||
|
* firmware to be used. Access to this variable is protected by
|
||||||
|
* the spinlock i2400m->rx_lock.
|
||||||
|
*
|
||||||
* @barker: barker type that the device uses; this is initialized by
|
* @barker: barker type that the device uses; this is initialized by
|
||||||
* i2400m_is_boot_barker() the first time it is called. Then it
|
* i2400m_is_boot_barker() the first time it is called. Then it
|
||||||
* won't change during the life cycle of the device and everytime
|
* won't change during the life cycle of the device and everytime
|
||||||
* a boot barker is received, it is just verified for it being the
|
* a boot barker is received, it is just verified for it being the
|
||||||
* same.
|
* same.
|
||||||
|
*
|
||||||
|
* @pm_notifier: used to register for PM events
|
||||||
*/
|
*/
|
||||||
struct i2400m {
|
struct i2400m {
|
||||||
struct wimax_dev wimax_dev; /* FIRST! See doc */
|
struct wimax_dev wimax_dev; /* FIRST! See doc */
|
||||||
@ -495,7 +505,10 @@ struct i2400m {
|
|||||||
const char *fw_name; /* name of the current firmware image */
|
const char *fw_name; /* name of the current firmware image */
|
||||||
unsigned long fw_version; /* version of the firmware interface */
|
unsigned long fw_version; /* version of the firmware interface */
|
||||||
const struct i2400m_bcf_hdr **fw_hdrs;
|
const struct i2400m_bcf_hdr **fw_hdrs;
|
||||||
|
struct i2400m_fw *fw_cached; /* protected by rx_lock */
|
||||||
struct i2400m_barker_db *barker;
|
struct i2400m_barker_db *barker;
|
||||||
|
|
||||||
|
struct notifier_block pm_notifier;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
@ -671,6 +684,9 @@ extern void i2400m_tx_release(struct i2400m *);
|
|||||||
extern int i2400m_rx_setup(struct i2400m *);
|
extern int i2400m_rx_setup(struct i2400m *);
|
||||||
extern void i2400m_rx_release(struct i2400m *);
|
extern void i2400m_rx_release(struct i2400m *);
|
||||||
|
|
||||||
|
extern void i2400m_fw_cache(struct i2400m *);
|
||||||
|
extern void i2400m_fw_uncache(struct i2400m *);
|
||||||
|
|
||||||
extern void i2400m_net_rx(struct i2400m *, struct sk_buff *, unsigned,
|
extern void i2400m_net_rx(struct i2400m *, struct sk_buff *, unsigned,
|
||||||
const void *, int);
|
const void *, int);
|
||||||
extern void i2400m_net_erx(struct i2400m *, struct sk_buff *,
|
extern void i2400m_net_erx(struct i2400m *, struct sk_buff *,
|
||||||
|
Loading…
Reference in New Issue
Block a user