|
@@ -139,13 +139,18 @@ struct mwl8k_priv {
|
|
|
|
|
|
struct pci_dev *pdev;
|
|
|
u8 name[16];
|
|
|
- /* firmware access lock */
|
|
|
- spinlock_t fw_lock;
|
|
|
|
|
|
/* firmware files and meta data */
|
|
|
struct mwl8k_firmware fw;
|
|
|
u32 part_num;
|
|
|
|
|
|
+ /* firmware access */
|
|
|
+ struct mutex fw_mutex;
|
|
|
+ struct task_struct *fw_mutex_owner;
|
|
|
+ int fw_mutex_depth;
|
|
|
+ struct completion *tx_wait;
|
|
|
+ struct completion *hostcmd_wait;
|
|
|
+
|
|
|
/* lock held over TX and TX reap */
|
|
|
spinlock_t tx_lock;
|
|
|
|
|
@@ -179,9 +184,6 @@ struct mwl8k_priv {
|
|
|
bool radio_short_preamble;
|
|
|
bool wmm_enabled;
|
|
|
|
|
|
- /* Set if PHY config is in progress */
|
|
|
- bool inconfig;
|
|
|
-
|
|
|
/* XXX need to convert this to handle multiple interfaces */
|
|
|
bool capture_beacon;
|
|
|
u8 capture_bssid[ETH_ALEN];
|
|
@@ -200,8 +202,6 @@ struct mwl8k_priv {
|
|
|
|
|
|
/* Work thread to serialize configuration requests */
|
|
|
struct workqueue_struct *config_wq;
|
|
|
- struct completion *hostcmd_wait;
|
|
|
- struct completion *tx_wait;
|
|
|
};
|
|
|
|
|
|
/* Per interface specific private data */
|
|
@@ -1113,6 +1113,9 @@ static int mwl8k_scan_tx_ring(struct mwl8k_priv *priv,
|
|
|
return ndescs;
|
|
|
}
|
|
|
|
|
|
+/*
|
|
|
+ * Must be called with hw->fw_mutex held and tx queues stopped.
|
|
|
+ */
|
|
|
static int mwl8k_tx_wait_empty(struct ieee80211_hw *hw)
|
|
|
{
|
|
|
struct mwl8k_priv *priv = hw->priv;
|
|
@@ -1122,9 +1125,6 @@ static int mwl8k_tx_wait_empty(struct ieee80211_hw *hw)
|
|
|
|
|
|
might_sleep();
|
|
|
|
|
|
- if (priv->tx_wait != NULL)
|
|
|
- printk(KERN_ERR "WARNING Previous TXWaitEmpty instance\n");
|
|
|
-
|
|
|
spin_lock_bh(&priv->tx_lock);
|
|
|
count = mwl8k_txq_busy(priv);
|
|
|
if (count) {
|
|
@@ -1140,7 +1140,7 @@ static int mwl8k_tx_wait_empty(struct ieee80211_hw *hw)
|
|
|
int newcount;
|
|
|
|
|
|
timeout = wait_for_completion_timeout(&cmd_wait,
|
|
|
- msecs_to_jiffies(1000));
|
|
|
+ msecs_to_jiffies(5000));
|
|
|
if (timeout)
|
|
|
return 0;
|
|
|
|
|
@@ -1149,7 +1149,7 @@ static int mwl8k_tx_wait_empty(struct ieee80211_hw *hw)
|
|
|
newcount = mwl8k_txq_busy(priv);
|
|
|
spin_unlock_bh(&priv->tx_lock);
|
|
|
|
|
|
- printk(KERN_ERR "%s(%u) TIMEDOUT:1000ms Pend:%u-->%u\n",
|
|
|
+ printk(KERN_ERR "%s(%u) TIMEDOUT:5000ms Pend:%u-->%u\n",
|
|
|
__func__, __LINE__, count, newcount);
|
|
|
|
|
|
mwl8k_scan_tx_ring(priv, txinfo);
|
|
@@ -1228,10 +1228,10 @@ static void mwl8k_txq_reclaim(struct ieee80211_hw *hw, int index, int force)
|
|
|
|
|
|
ieee80211_tx_status_irqsafe(hw, skb);
|
|
|
|
|
|
- wake = !priv->inconfig && priv->radio_on;
|
|
|
+ wake = 1;
|
|
|
}
|
|
|
|
|
|
- if (wake)
|
|
|
+ if (wake && priv->radio_on && !mutex_is_locked(&priv->fw_mutex))
|
|
|
ieee80211_wake_queue(hw, index);
|
|
|
}
|
|
|
|
|
@@ -1359,6 +1359,60 @@ mwl8k_txq_xmit(struct ieee80211_hw *hw, int index, struct sk_buff *skb)
|
|
|
}
|
|
|
|
|
|
|
|
|
+/*
|
|
|
+ * Firmware access.
|
|
|
+ *
|
|
|
+ * We have the following requirements for issuing firmware commands:
|
|
|
+ * - Some commands require that the packet transmit path is idle when
|
|
|
+ * the command is issued. (For simplicity, we'll just quiesce the
|
|
|
+ * transmit path for every command.)
|
|
|
+ * - There are certain sequences of commands that need to be issued to
|
|
|
+ * the hardware sequentially, with no other intervening commands.
|
|
|
+ *
|
|
|
+ * This leads to an implementation of a "firmware lock" as a mutex that
|
|
|
+ * can be taken recursively, and which is taken by both the low-level
|
|
|
+ * command submission function (mwl8k_post_cmd) as well as any users of
|
|
|
+ * that function that require issuing of an atomic sequence of commands,
|
|
|
+ * and quiesces the transmit path whenever it's taken.
|
|
|
+ */
|
|
|
+static int mwl8k_fw_lock(struct ieee80211_hw *hw)
|
|
|
+{
|
|
|
+ struct mwl8k_priv *priv = hw->priv;
|
|
|
+
|
|
|
+ if (priv->fw_mutex_owner != current) {
|
|
|
+ int rc;
|
|
|
+
|
|
|
+ mutex_lock(&priv->fw_mutex);
|
|
|
+ ieee80211_stop_queues(hw);
|
|
|
+
|
|
|
+ rc = mwl8k_tx_wait_empty(hw);
|
|
|
+ if (rc) {
|
|
|
+ ieee80211_wake_queues(hw);
|
|
|
+ mutex_unlock(&priv->fw_mutex);
|
|
|
+
|
|
|
+ return rc;
|
|
|
+ }
|
|
|
+
|
|
|
+ priv->fw_mutex_owner = current;
|
|
|
+ }
|
|
|
+
|
|
|
+ priv->fw_mutex_depth++;
|
|
|
+
|
|
|
+ return 0;
|
|
|
+}
|
|
|
+
|
|
|
+static void mwl8k_fw_unlock(struct ieee80211_hw *hw)
|
|
|
+{
|
|
|
+ struct mwl8k_priv *priv = hw->priv;
|
|
|
+
|
|
|
+ if (!--priv->fw_mutex_depth) {
|
|
|
+ ieee80211_wake_queues(hw);
|
|
|
+ priv->fw_mutex_owner = NULL;
|
|
|
+ mutex_unlock(&priv->fw_mutex);
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
/*
|
|
|
* Command processing.
|
|
|
*/
|
|
@@ -1384,28 +1438,28 @@ static int mwl8k_post_cmd(struct ieee80211_hw *hw, struct mwl8k_cmd_pkt *cmd)
|
|
|
if (pci_dma_mapping_error(priv->pdev, dma_addr))
|
|
|
return -ENOMEM;
|
|
|
|
|
|
- if (priv->hostcmd_wait != NULL)
|
|
|
- printk(KERN_ERR "WARNING host command in progress\n");
|
|
|
+ rc = mwl8k_fw_lock(hw);
|
|
|
+ if (rc)
|
|
|
+ return rc;
|
|
|
|
|
|
- spin_lock_irq(&priv->fw_lock);
|
|
|
priv->hostcmd_wait = &cmd_wait;
|
|
|
iowrite32(dma_addr, regs + MWL8K_HIU_GEN_PTR);
|
|
|
iowrite32(MWL8K_H2A_INT_DOORBELL,
|
|
|
regs + MWL8K_HIU_H2A_INTERRUPT_EVENTS);
|
|
|
iowrite32(MWL8K_H2A_INT_DUMMY,
|
|
|
regs + MWL8K_HIU_H2A_INTERRUPT_EVENTS);
|
|
|
- spin_unlock_irq(&priv->fw_lock);
|
|
|
|
|
|
timeout = wait_for_completion_timeout(&cmd_wait,
|
|
|
msecs_to_jiffies(MWL8K_CMD_TIMEOUT_MS));
|
|
|
|
|
|
+ priv->hostcmd_wait = NULL;
|
|
|
+
|
|
|
+ mwl8k_fw_unlock(hw);
|
|
|
+
|
|
|
pci_unmap_single(priv->pdev, dma_addr, dma_size,
|
|
|
PCI_DMA_BIDIRECTIONAL);
|
|
|
|
|
|
if (!timeout) {
|
|
|
- spin_lock_irq(&priv->fw_lock);
|
|
|
- priv->hostcmd_wait = NULL;
|
|
|
- spin_unlock_irq(&priv->fw_lock);
|
|
|
printk(KERN_ERR "%s: Command %s timeout after %u ms\n",
|
|
|
priv->name,
|
|
|
mwl8k_cmd_name(cmd->code, buf, sizeof(buf)),
|
|
@@ -2336,17 +2390,14 @@ static irqreturn_t mwl8k_interrupt(int irq, void *dev_id)
|
|
|
}
|
|
|
|
|
|
if (status & MWL8K_A2H_INT_OPC_DONE) {
|
|
|
- if (priv->hostcmd_wait != NULL) {
|
|
|
+ if (priv->hostcmd_wait != NULL)
|
|
|
complete(priv->hostcmd_wait);
|
|
|
- priv->hostcmd_wait = NULL;
|
|
|
- }
|
|
|
}
|
|
|
|
|
|
if (status & MWL8K_A2H_INT_QUEUE_EMPTY) {
|
|
|
- if (!priv->inconfig &&
|
|
|
- priv->radio_on &&
|
|
|
- mwl8k_txq_busy(priv))
|
|
|
- mwl8k_tx_start(priv);
|
|
|
+ if (!mutex_is_locked(&priv->fw_mutex) &&
|
|
|
+ priv->radio_on && mwl8k_txq_busy(priv))
|
|
|
+ mwl8k_tx_start(priv);
|
|
|
}
|
|
|
|
|
|
return IRQ_HANDLED;
|
|
@@ -2395,41 +2446,13 @@ static void mwl8k_config_thread(struct work_struct *wt)
|
|
|
{
|
|
|
struct mwl8k_work_struct *worker = (struct mwl8k_work_struct *)wt;
|
|
|
struct ieee80211_hw *hw = worker->hw;
|
|
|
- struct mwl8k_priv *priv = hw->priv;
|
|
|
int rc = 0;
|
|
|
- int iter;
|
|
|
|
|
|
- spin_lock_irq(&priv->tx_lock);
|
|
|
- priv->inconfig = true;
|
|
|
- spin_unlock_irq(&priv->tx_lock);
|
|
|
-
|
|
|
- ieee80211_stop_queues(hw);
|
|
|
-
|
|
|
- /*
|
|
|
- * Wait for host queues to drain before doing PHY
|
|
|
- * reconfiguration. This avoids interrupting any in-flight
|
|
|
- * DMA transfers to the hardware.
|
|
|
- */
|
|
|
- iter = 4;
|
|
|
- do {
|
|
|
- rc = mwl8k_tx_wait_empty(hw);
|
|
|
- if (rc)
|
|
|
- printk(KERN_ERR "%s() txwait timeout=1000ms "
|
|
|
- "Retry:%u/%u\n", __func__, 4 - iter + 1, 4);
|
|
|
- } while (rc && --iter);
|
|
|
-
|
|
|
- rc = iter ? 0 : -ETIMEDOUT;
|
|
|
-
|
|
|
- if (!rc)
|
|
|
+ rc = mwl8k_fw_lock(hw);
|
|
|
+ if (!rc) {
|
|
|
rc = worker->wfunc(wt);
|
|
|
-
|
|
|
- spin_lock_irq(&priv->tx_lock);
|
|
|
- priv->inconfig = false;
|
|
|
- if (priv->pending_tx_pkts && priv->radio_on)
|
|
|
- mwl8k_tx_start(priv);
|
|
|
- spin_unlock_irq(&priv->tx_lock);
|
|
|
-
|
|
|
- ieee80211_wake_queues(hw);
|
|
|
+ mwl8k_fw_unlock(hw);
|
|
|
+ }
|
|
|
|
|
|
worker->rc = rc;
|
|
|
complete(worker->cmd_wait);
|
|
@@ -3145,15 +3168,10 @@ static int __devinit mwl8k_probe(struct pci_dev *pdev,
|
|
|
priv = hw->priv;
|
|
|
priv->hw = hw;
|
|
|
priv->pdev = pdev;
|
|
|
- priv->hostcmd_wait = NULL;
|
|
|
- priv->tx_wait = NULL;
|
|
|
- priv->inconfig = false;
|
|
|
priv->wmm_enabled = false;
|
|
|
priv->pending_tx_pkts = 0;
|
|
|
strncpy(priv->name, MWL8K_NAME, sizeof(priv->name));
|
|
|
|
|
|
- spin_lock_init(&priv->fw_lock);
|
|
|
-
|
|
|
SET_IEEE80211_DEV(hw, &pdev->dev);
|
|
|
pci_set_drvdata(pdev, hw);
|
|
|
|
|
@@ -3219,6 +3237,12 @@ static int __devinit mwl8k_probe(struct pci_dev *pdev,
|
|
|
goto err_iounmap;
|
|
|
rxq_refill(hw, 0, INT_MAX);
|
|
|
|
|
|
+ mutex_init(&priv->fw_mutex);
|
|
|
+ priv->fw_mutex_owner = NULL;
|
|
|
+ priv->fw_mutex_depth = 0;
|
|
|
+ priv->tx_wait = NULL;
|
|
|
+ priv->hostcmd_wait = NULL;
|
|
|
+
|
|
|
spin_lock_init(&priv->tx_lock);
|
|
|
|
|
|
for (i = 0; i < MWL8K_TX_QUEUES; i++) {
|