This series is a refactorring of the RFC previously posted as
orinoco: Don't keep cached firmware around permanently
The recent patch to load orinoco firmware correctly on resume simply
kept the firmware in RAM for the entire time the module was loaded, and
only applied to Agere firmware.
The first two patches extend this to Symbol firmware.
The third makes the spectrum_cs driver use this functionality directly
during resume instead of resetting the card.
The final patch provides an option to use the new power management
notifiers to load the firmware prior to suspend and release it after
resume.
David Kilroy (4):
orinoco: Separate fw caching from download
orinoco: Cache Symbol firmware
orinoco: Resume spectrum_cs in the same way as orinoco_cs
orinoco: Provide option to avoid unnecessary fw caching
drivers/net/wireless/Kconfig | 15 +++
drivers/net/wireless/orinoco/orinoco.c | 156 ++++++++++++++++++++++-----
drivers/net/wireless/orinoco/orinoco.h | 6 +-
drivers/net/wireless/orinoco/spectrum_cs.c | 21 ++++-
4 files changed, 167 insertions(+), 31 deletions(-)
Make firmware caching on startup optional, and make it default.
When the option is not selected and PM_SLEEP is configured, then
cache firmware in the suspend pm_notifier. This configuration saves
about 64k RAM in normal use, but can lead to a situation where the
driver is configured to use a different firmware.
Signed-off by: David Kilroy <[email protected]>
---
drivers/net/wireless/Kconfig | 15 ++++++++
drivers/net/wireless/orinoco/orinoco.c | 57 ++++++++++++++++++++++++++++++++
drivers/net/wireless/orinoco/orinoco.h | 3 ++
3 files changed, 75 insertions(+), 0 deletions(-)
diff --git a/drivers/net/wireless/Kconfig b/drivers/net/wireless/Kconfig
index 7ea916b..ea543fc 100644
--- a/drivers/net/wireless/Kconfig
+++ b/drivers/net/wireless/Kconfig
@@ -214,6 +214,21 @@ config HERMES
configure your card and that /etc/pcmcia/wireless.opts works :
<http://www.hpl.hp.com/personal/Jean_Tourrilhes/Linux/Tools.html>
+config HERMES_CACHE_FW_ON_INIT
+ bool "Cache Hermes firmware on driver initialisation"
+ depends on HERMES
+ default y
+ ---help---
+ Say Y to cache any firmware required by the Hermes drivers
+ on startup. The firmware will remain cached until the
+ driver is unloaded. The cache uses 64K of RAM.
+
+ Otherwise load the firmware from userspace as required. In
+ this case the driver should be unloaded and restarted
+ whenever the firmware is changed.
+
+ If you are not sure, say Y.
+
config APPLE_AIRPORT
tristate "Apple Airport support (built-in)"
depends on PPC_PMAC && HERMES
diff --git a/drivers/net/wireless/orinoco/orinoco.c b/drivers/net/wireless/orinoco/orinoco.c
index 4c8f4c2..40f662e 100644
--- a/drivers/net/wireless/orinoco/orinoco.c
+++ b/drivers/net/wireless/orinoco/orinoco.c
@@ -84,6 +84,7 @@
#include <linux/etherdevice.h>
#include <linux/ethtool.h>
#include <linux/firmware.h>
+#include <linux/suspend.h>
#include <linux/if_arp.h>
#include <linux/wireless.h>
#include <linux/ieee80211.h>
@@ -712,6 +713,7 @@ static int orinoco_download(struct orinoco_private *priv)
return err;
}
+#if defined(CONFIG_HERMES_CACHE_FW_ON_INIT) || defined(CONFIG_PM_SLEEP)
static void orinoco_cache_fw(struct orinoco_private *priv, int ap)
{
const struct firmware *fw_entry = NULL;
@@ -745,6 +747,10 @@ static void orinoco_uncache_fw(struct orinoco_private *priv)
priv->cached_pri_fw = NULL;
priv->cached_fw = NULL;
}
+#else
+#define orinoco_cache_fw(priv, ap)
+#define orinoco_uncache_fw(priv)
+#endif
/********************************************************************/
/* Device methods */
@@ -3103,6 +3109,50 @@ irqreturn_t orinoco_interrupt(int irq, void *dev_id)
}
/********************************************************************/
+/* Power management */
+/********************************************************************/
+#if defined(CONFIG_PM_SLEEP) && !defined(CONFIG_HERMES_CACHE_FW_ON_INIT)
+static int orinoco_pm_notifier(struct notifier_block *notifier,
+ unsigned long pm_event,
+ void *unused)
+{
+ struct orinoco_private *priv = container_of(notifier,
+ struct orinoco_private,
+ pm_notifier);
+
+ /* All we need to do is cache the firmware before suspend, and
+ * release it when we come out.
+ *
+ * Only need to do this if we're downloading firmware. */
+ if (!priv->do_fw_download)
+ return NOTIFY_DONE;
+
+ switch (pm_event) {
+ case PM_HIBERNATION_PREPARE:
+ case PM_SUSPEND_PREPARE:
+ orinoco_cache_fw(priv, 0);
+ 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:
+ orinoco_uncache_fw(priv);
+ break;
+
+ case PM_RESTORE_PREPARE:
+ default:
+ break;
+ }
+
+ return NOTIFY_DONE;
+}
+#else /* !PM_SLEEP || HERMES_CACHE_FW_ON_INIT */
+#define orinoco_pm_notifier NULL
+#endif
+
+/********************************************************************/
/* Initialization */
/********************************************************************/
@@ -3346,7 +3396,9 @@ static int orinoco_init(struct net_device *dev)
}
if (priv->do_fw_download) {
+#ifdef CONFIG_HERMES_CACHE_FW_ON_INIT
orinoco_cache_fw(priv, 0);
+#endif
err = orinoco_download(priv);
if (err)
@@ -3587,6 +3639,10 @@ struct net_device
priv->cached_pri_fw = NULL;
priv->cached_fw = NULL;
+ /* Register PM notifiers */
+ priv->pm_notifier.notifier_call = orinoco_pm_notifier;
+ register_pm_notifier(&priv->pm_notifier);
+
return dev;
}
@@ -3599,6 +3655,7 @@ void free_orinocodev(struct net_device *dev)
* emptying the list */
tasklet_kill(&priv->rx_tasklet);
+ unregister_pm_notifier(&priv->pm_notifier);
orinoco_uncache_fw(priv);
priv->wpa_ie_len = 0;
diff --git a/drivers/net/wireless/orinoco/orinoco.h b/drivers/net/wireless/orinoco/orinoco.h
index f6eaea9..00750c8 100644
--- a/drivers/net/wireless/orinoco/orinoco.h
+++ b/drivers/net/wireless/orinoco/orinoco.h
@@ -10,6 +10,7 @@
#define DRIVER_VERSION "0.15"
#include <linux/interrupt.h>
+#include <linux/suspend.h>
#include <linux/netdevice.h>
#include <linux/wireless.h>
#include <net/iw_handler.h>
@@ -170,6 +171,8 @@ struct orinoco_private {
/* Cached in memory firmware to use during ->resume. */
const struct firmware *cached_pri_fw;
const struct firmware *cached_fw;
+
+ struct notifier_block pm_notifier;
};
#ifdef ORINOCO_DEBUG
--
1.5.6.4
Signed-off by: David Kilroy <[email protected]>
---
drivers/net/wireless/orinoco/orinoco.c | 46 ++++++++++++++++++++++---------
drivers/net/wireless/orinoco/orinoco.h | 3 +-
2 files changed, 34 insertions(+), 15 deletions(-)
diff --git a/drivers/net/wireless/orinoco/orinoco.c b/drivers/net/wireless/orinoco/orinoco.c
index 4cefb80..4c8f4c2 100644
--- a/drivers/net/wireless/orinoco/orinoco.c
+++ b/drivers/net/wireless/orinoco/orinoco.c
@@ -645,34 +645,41 @@ symbol_dl_firmware(struct orinoco_private *priv,
int ret;
const struct firmware *fw_entry;
- if (request_firmware(&fw_entry, fw->pri_fw,
- priv->dev) != 0) {
- printk(KERN_ERR "%s: Cannot find firmware: %s\n",
- dev->name, fw->pri_fw);
- return -ENOENT;
- }
+ if (!priv->cached_pri_fw) {
+ if (request_firmware(&fw_entry, fw->pri_fw, priv->dev) != 0) {
+ printk(KERN_ERR "%s: Cannot find firmware: %s\n",
+ dev->name, fw->pri_fw);
+ return -ENOENT;
+ }
+ } else
+ fw_entry = priv->cached_pri_fw;
/* Load primary firmware */
ret = symbol_dl_image(priv, fw, fw_entry->data,
fw_entry->data + fw_entry->size, 0);
- release_firmware(fw_entry);
+
+ if (!priv->cached_pri_fw)
+ release_firmware(fw_entry);
if (ret) {
printk(KERN_ERR "%s: Primary firmware download failed\n",
dev->name);
return ret;
}
- if (request_firmware(&fw_entry, fw->sta_fw,
- priv->dev) != 0) {
- printk(KERN_ERR "%s: Cannot find firmware: %s\n",
- dev->name, fw->sta_fw);
- return -ENOENT;
- }
+ if (!priv->cached_fw) {
+ if (request_firmware(&fw_entry, fw->sta_fw, priv->dev) != 0) {
+ printk(KERN_ERR "%s: Cannot find firmware: %s\n",
+ dev->name, fw->sta_fw);
+ return -ENOENT;
+ }
+ } else
+ fw_entry = priv->cached_fw;
/* Load secondary firmware */
ret = symbol_dl_image(priv, fw, fw_entry->data,
fw_entry->data + fw_entry->size, 1);
- release_firmware(fw_entry);
+ if (!priv->cached_fw)
+ release_firmware(fw_entry);
if (ret) {
printk(KERN_ERR "%s: Secondary firmware download failed\n",
dev->name);
@@ -708,13 +715,20 @@ static int orinoco_download(struct orinoco_private *priv)
static void orinoco_cache_fw(struct orinoco_private *priv, int ap)
{
const struct firmware *fw_entry = NULL;
+ const char *pri_fw;
const char *fw;
+ pri_fw = orinoco_fw[priv->firmware_type].pri_fw;
if (ap)
fw = orinoco_fw[priv->firmware_type].ap_fw;
else
fw = orinoco_fw[priv->firmware_type].sta_fw;
+ if (pri_fw) {
+ if (request_firmware(&fw_entry, pri_fw, priv->dev) == 0)
+ priv->cached_pri_fw = fw_entry;
+ }
+
if (fw) {
if (request_firmware(&fw_entry, fw, priv->dev) == 0)
priv->cached_fw = fw_entry;
@@ -723,9 +737,12 @@ static void orinoco_cache_fw(struct orinoco_private *priv, int ap)
static void orinoco_uncache_fw(struct orinoco_private *priv)
{
+ if (priv->cached_pri_fw)
+ release_firmware(priv->cached_pri_fw);
if (priv->cached_fw)
release_firmware(priv->cached_fw);
+ priv->cached_pri_fw = NULL;
priv->cached_fw = NULL;
}
@@ -3567,6 +3584,7 @@ struct net_device
netif_carrier_off(dev);
priv->last_linkstatus = 0xffff;
+ priv->cached_pri_fw = NULL;
priv->cached_fw = NULL;
return dev;
diff --git a/drivers/net/wireless/orinoco/orinoco.h b/drivers/net/wireless/orinoco/orinoco.h
index 8c29538..f6eaea9 100644
--- a/drivers/net/wireless/orinoco/orinoco.h
+++ b/drivers/net/wireless/orinoco/orinoco.h
@@ -167,7 +167,8 @@ struct orinoco_private {
unsigned int tkip_cm_active:1;
unsigned int key_mgmt:3;
- /* Cached in memory firmware to use in ->resume */
+ /* Cached in memory firmware to use during ->resume. */
+ const struct firmware *cached_pri_fw;
const struct firmware *cached_fw;
};
--
1.5.6.4
Retrieval of external firmware has been resolved, and should work with
the standard orinoco resume algorithm.
This fixes an issue where priv->hw_unavailable indicates the card is
ready when firmware has not been loaded.
Signed-off by: David Kilroy <[email protected]>
---
drivers/net/wireless/orinoco/spectrum_cs.c | 21 ++++++++++++++++++++-
1 files changed, 20 insertions(+), 1 deletions(-)
diff --git a/drivers/net/wireless/orinoco/spectrum_cs.c b/drivers/net/wireless/orinoco/spectrum_cs.c
index 852789a..c6c6d12 100644
--- a/drivers/net/wireless/orinoco/spectrum_cs.c
+++ b/drivers/net/wireless/orinoco/spectrum_cs.c
@@ -450,10 +450,29 @@ spectrum_cs_resume(struct pcmcia_device *link)
{
struct net_device *dev = link->priv;
struct orinoco_private *priv = netdev_priv(dev);
+ unsigned long flags;
+ int err;
+
+ err = orinoco_reinit_firmware(dev);
+ if (err) {
+ printk(KERN_ERR "%s: Error %d re-initializing firmware\n",
+ dev->name, err);
+ return -EIO;
+ }
+
+ spin_lock_irqsave(&priv->lock, flags);
netif_device_attach(dev);
priv->hw_unavailable--;
- schedule_work(&priv->reset_work);
+
+ if (priv->open && !priv->hw_unavailable) {
+ err = __orinoco_up(dev);
+ if (err)
+ printk(KERN_ERR "%s: Error %d restarting card\n",
+ dev->name, err);
+ }
+
+ spin_unlock_irqrestore(&priv->lock, flags);
return 0;
}
--
1.5.6.4
This refactorring will make it easier to share logic with Symbol
firmware.
Signed-off by: David Kilroy <[email protected]>
---
drivers/net/wireless/orinoco/orinoco.c | 53 +++++++++++++++++++++++---------
1 files changed, 38 insertions(+), 15 deletions(-)
diff --git a/drivers/net/wireless/orinoco/orinoco.c b/drivers/net/wireless/orinoco/orinoco.c
index 5459b69..4cefb80 100644
--- a/drivers/net/wireless/orinoco/orinoco.c
+++ b/drivers/net/wireless/orinoco/orinoco.c
@@ -431,9 +431,9 @@ struct fw_info {
};
const static struct fw_info orinoco_fw[] = {
- { "", "agere_sta_fw.bin", "agere_ap_fw.bin", 0x00390000, 1000 },
- { "", "prism_sta_fw.bin", "prism_ap_fw.bin", 0, 1024 },
- { "symbol_sp24t_prim_fw", "symbol_sp24t_sec_fw", "", 0x00003100, 512 }
+ { NULL, "agere_sta_fw.bin", "agere_ap_fw.bin", 0x00390000, 1000 },
+ { NULL, "prism_sta_fw.bin", "prism_ap_fw.bin", 0, 1024 },
+ { "symbol_sp24t_prim_fw", "symbol_sp24t_sec_fw", NULL, 0x00003100, 512 }
};
/* Structure used to access fields in FW
@@ -487,18 +487,17 @@ orinoco_dl_firmware(struct orinoco_private *priv,
if (err)
goto free;
- if (priv->cached_fw)
- fw_entry = priv->cached_fw;
- else {
+ if (!priv->cached_fw) {
err = request_firmware(&fw_entry, firmware, priv->dev);
+
if (err) {
printk(KERN_ERR "%s: Cannot find firmware %s\n",
dev->name, firmware);
err = -ENOENT;
goto free;
}
- priv->cached_fw = fw_entry;
- }
+ } else
+ fw_entry = priv->cached_fw;
hdr = (const struct orinoco_fw_header *) fw_entry->data;
@@ -540,11 +539,9 @@ orinoco_dl_firmware(struct orinoco_private *priv,
dev->name, hermes_present(hw));
abort:
- /* In case of error, assume firmware was bogus and release it */
- if (err) {
- priv->cached_fw = NULL;
+ /* If we requested the firmware, release it. */
+ if (!priv->cached_fw)
release_firmware(fw_entry);
- }
free:
kfree(pda);
@@ -708,6 +705,30 @@ static int orinoco_download(struct orinoco_private *priv)
return err;
}
+static void orinoco_cache_fw(struct orinoco_private *priv, int ap)
+{
+ const struct firmware *fw_entry = NULL;
+ const char *fw;
+
+ if (ap)
+ fw = orinoco_fw[priv->firmware_type].ap_fw;
+ else
+ fw = orinoco_fw[priv->firmware_type].sta_fw;
+
+ if (fw) {
+ if (request_firmware(&fw_entry, fw, priv->dev) == 0)
+ priv->cached_fw = fw_entry;
+ }
+}
+
+static void orinoco_uncache_fw(struct orinoco_private *priv)
+{
+ if (priv->cached_fw)
+ release_firmware(priv->cached_fw);
+
+ priv->cached_fw = NULL;
+}
+
/********************************************************************/
/* Device methods */
/********************************************************************/
@@ -3308,6 +3329,8 @@ static int orinoco_init(struct net_device *dev)
}
if (priv->do_fw_download) {
+ orinoco_cache_fw(priv, 0);
+
err = orinoco_download(priv);
if (err)
priv->do_fw_download = 0;
@@ -3557,9 +3580,9 @@ void free_orinocodev(struct net_device *dev)
* when we call tasklet_kill it will run one final time,
* emptying the list */
tasklet_kill(&priv->rx_tasklet);
- if (priv->cached_fw)
- release_firmware(priv->cached_fw);
- priv->cached_fw = NULL;
+
+ orinoco_uncache_fw(priv);
+
priv->wpa_ie_len = 0;
kfree(priv->wpa_ie);
orinoco_mic_free(priv);
--
1.5.6.4