Here's another iteration.  I think this should be safe - if someone
with slow MIPS can bang on it and see if queues hang any more frequently
or the WARN_ON is hit or any kind of lockup, that would be a useful
datapoint.

notes:
 - we don't stop hw tx before freeing the skbs in drain txbuffs
   (didn't we do that once upon a time?)  so we could have transmitted
   garbage there.
 - stopping all dma is a bit heavy-handed, I just need to set txd
   on the queues before draining the bufs.  it's probably
   a fair bit slower due to calling that twice in reset path and a
   third time on every flush() -> will rework
 - wmb is probably excessive too.
 - found a memory leak in beacon buf dma map failure
 - I think the tx_poll_mark should only be reset after we successfully
   process a descriptor instead of before...maybe.
 - can't figure out why we had skb null check in tx tasklet, comment
   doesn't make any sense, so would like to see the WARN_ON when it
   triggers.

Will split this up and resend if it gets some positive testing and no
objections..

diff --git a/drivers/net/wireless/ath/ath5k/ath5k.h 
b/drivers/net/wireless/ath/ath5k/ath5k.h
index 277d5cb..5104a99 100644
--- a/drivers/net/wireless/ath/ath5k/ath5k.h
+++ b/drivers/net/wireless/ath/ath5k/ath5k.h
@@ -1354,6 +1354,7 @@ int ath5k_beacon_update(struct ieee80211_hw *hw, struct 
ieee80211_vif *vif);
 void ath5k_beacon_config(struct ath5k_hw *ah);
 void ath5k_txbuf_free_skb(struct ath5k_hw *ah, struct ath5k_buf *bf);
 void ath5k_rxbuf_free_skb(struct ath5k_hw *ah, struct ath5k_buf *bf);
+void ath5k_flush_tx(struct ath5k_hw *ah, bool drop);
 
 /*Chip id helper functions */
 const char *ath5k_chip_name(enum ath5k_srev_type type, u_int16_t val);
diff --git a/drivers/net/wireless/ath/ath5k/base.c 
b/drivers/net/wireless/ath/ath5k/base.c
index a74d286..73492ee 100644
--- a/drivers/net/wireless/ath/ath5k/base.c
+++ b/drivers/net/wireless/ath/ath5k/base.c
@@ -87,6 +87,8 @@ MODULE_LICENSE("Dual BSD/GPL");
 static int ath5k_init(struct ieee80211_hw *hw);
 static int ath5k_reset(struct ath5k_hw *ah, struct ieee80211_channel *chan,
                                                                bool skip_pcu);
+static int
+ath5k_rx_start(struct ath5k_hw *ah);
 
 /* Known SREVs */
 static const struct ath5k_srev_name srev_names[] = {
@@ -748,6 +750,11 @@ ath5k_txbuf_setup(struct ath5k_hw *ah, struct ath5k_buf 
*bf,
        spin_lock_bh(&txq->lock);
        list_add_tail(&bf->list, &txq->q);
        txq->txq_len++;
+
+       /*
+        * make sure ds_link update is seen before txq->link write...
+        */
+       wmb();
        if (txq->link == NULL) /* is this first packet? */
                ath5k_hw_set_txdp(ah, txq->qnum, bf->daddr);
        else /* no, so only link it */
@@ -1020,16 +1027,28 @@ err:
        return ret;
 }
 
+static bool
+ath5k_has_pending_frames(struct ath5k_hw *ah, struct ath5k_txq *txq)
+{
+       bool pending;
+
+       spin_lock_bh(&txq->lock);
+       pending = txq->setup && txq->txq_len > 0;
+       spin_unlock_bh(&txq->lock);
+
+       return pending;
+}
+
 /**
  * ath5k_drain_tx_buffs - Empty tx buffers
  *
  * @ah The &struct ath5k_hw
  *
  * Empty tx buffers from all queues in preparation
- * of a reset or during shutdown.
+ * of a reset or during shutdown.  We take txq->lock
+ * to avoid races against ath5k_tx_processq().
  *
- * NB: this assumes output has been stopped and
- *     we do not need to block ath5k_tx_tasklet
+ * Hardware TX should be disabled.
  */
 static void
 ath5k_drain_tx_buffs(struct ath5k_hw *ah)
@@ -1060,6 +1079,40 @@ ath5k_drain_tx_buffs(struct ath5k_hw *ah)
        }
 }
 
+/**
+ * ath5k_flush_tx - wait for all pending tx to be processed
+ *
+ * @ah: The &struct ath5k_hw
+ * @drop: true if we should drop packets rather than wait
+ *
+ * Queuing of new packets should be stopped.  In any case we
+ * don't wait more than a small timeout (200 ms) for packets.
+ */
+void ath5k_flush_tx(struct ath5k_hw *ah, bool drop)
+{
+       int i;
+       bool pending = true;
+       unsigned long timeout = jiffies + msecs_to_jiffies(200);
+
+       if (drop)
+               pending = false;
+
+       /* sleep until some timeout occurs or all queues are empty */
+       while (pending && !time_after(jiffies, timeout))
+       {
+               pending = false;
+               for (i = 0; i < ARRAY_SIZE(ah->txqs); i++)
+                       pending |= ath5k_has_pending_frames(ah, &ah->txqs[i]);
+
+               if (pending)
+                       msleep(10);
+       }
+       ath5k_hw_dma_stop(ah);
+       ath5k_rx_start(ah);
+       ath5k_drain_tx_buffs(ah);
+}
+
+
 static void
 ath5k_txq_release(struct ath5k_hw *ah)
 {
@@ -1650,43 +1703,41 @@ ath5k_tx_processq(struct ath5k_hw *ah, struct ath5k_txq 
*txq)
        spin_lock(&txq->lock);
        list_for_each_entry_safe(bf, bf0, &txq->q, list) {
 
-               txq->txq_poll_mark = false;
-
-               /* skb might already have been processed last time. */
-               if (bf->skb != NULL) {
-                       ds = bf->desc;
-
-                       ret = ah->ah_proc_tx_desc(ah, ds, &ts);
-                       if (unlikely(ret == -EINPROGRESS))
-                               break;
-                       else if (unlikely(ret)) {
-                               ATH5K_ERR(ah,
-                                       "error %d while processing "
-                                       "queue %u\n", ret, txq->qnum);
-                               break;
-                       }
+               WARN_ON(!bf->skb);
 
-                       skb = bf->skb;
-                       bf->skb = NULL;
+               ds = bf->desc;
 
-                       dma_unmap_single(ah->dev, bf->skbaddr, skb->len,
-                                       DMA_TO_DEVICE);
-                       ath5k_tx_frame_completed(ah, skb, txq, &ts);
+               ret = ah->ah_proc_tx_desc(ah, ds, &ts);
+               if (unlikely(ret == -EINPROGRESS))
+                       break;
+               else if (unlikely(ret)) {
+                       ATH5K_ERR(ah,
+                               "error %d while processing "
+                               "queue %u\n", ret, txq->qnum);
+                       break;
                }
+               txq->txq_poll_mark = false;
 
-               /*
-                * It's possible that the hardware can say the buffer is
-                * completed when it hasn't yet loaded the ds_link from
-                * host memory and moved on.
-                * Always keep the last descriptor to avoid HW races...
+               /* if there's no race here, should not cause issues.
+                * if there is, queue should hang regularly since
+                * driver thinks there are packets queued and writes
+                * to wrong txq->link while hw is re-reading this
+                * descriptor for link updates.
                 */
-               if (ath5k_hw_get_txdp(ah, txq->qnum) != bf->daddr) {
-                       spin_lock(&ah->txbuflock);
-                       list_move_tail(&bf->list, &ah->txbuf);
-                       ah->txbuf_len++;
-                       txq->txq_len--;
-                       spin_unlock(&ah->txbuflock);
-               }
+               ds->ds_link = 0;
+
+               skb = bf->skb;
+               bf->skb = NULL;
+
+               dma_unmap_single(ah->dev, bf->skbaddr, skb->len,
+                               DMA_TO_DEVICE);
+               ath5k_tx_frame_completed(ah, skb, txq, &ts);
+
+               spin_lock(&ah->txbuflock);
+               list_move_tail(&bf->list, &ah->txbuf);
+               ah->txbuf_len++;
+               txq->txq_len--;
+               spin_unlock(&ah->txbuflock);
        }
        spin_unlock(&txq->lock);
        if (txq->txq_len < ATH5K_TXQ_LEN_LOW && txq->qnum < 4)
@@ -1818,8 +1869,6 @@ ath5k_beacon_update(struct ieee80211_hw *hw, struct 
ieee80211_vif *vif)
        ath5k_txbuf_free_skb(ah, avf->bbuf);
        avf->bbuf->skb = skb;
        ret = ath5k_beacon_setup(ah, avf->bbuf);
-       if (ret)
-               avf->bbuf->skb = NULL;
 out:
        return ret;
 }
@@ -2308,7 +2357,7 @@ ath5k_tx_complete_poll_work(struct work_struct *work)
                if (ah->txqs[i].setup) {
                        txq = &ah->txqs[i];
                        spin_lock_bh(&txq->lock);
-                       if (txq->txq_len > 1) {
+                       if (txq->txq_len) {
                                if (txq->txq_poll_mark) {
                                        ATH5K_DBG(ah, ATH5K_DEBUG_XMIT,
                                                  "TX queue stuck %d\n",
@@ -2664,6 +2713,7 @@ ath5k_reset(struct ath5k_hw *ah, struct ieee80211_channel 
*chan,
 
        ath5k_hw_set_imr(ah, 0);
        synchronize_irq(ah->irq);
+       ath5k_hw_dma_stop(ah);
        ath5k_stop_tasklets(ah);
 
        /* Save ani mode and disable ANI during
diff --git a/drivers/net/wireless/ath/ath5k/mac80211-ops.c 
b/drivers/net/wireless/ath/ath5k/mac80211-ops.c
index 2a715ca..2fc09bc 100644
--- a/drivers/net/wireless/ath/ath5k/mac80211-ops.c
+++ b/drivers/net/wireless/ath/ath5k/mac80211-ops.c
@@ -693,6 +693,16 @@ ath5k_set_coverage_class(struct ieee80211_hw *hw, u8 
coverage_class)
        mutex_unlock(&ah->lock);
 }
 
+static void
+ath5k_flush(struct ieee80211_hw *hw, bool drop)
+{
+       struct ath5k_hw *ah = hw->priv;
+
+       mutex_lock(&ah->lock);
+       ath5k_flush_tx(ah, drop);
+       mutex_unlock(&ah->lock);
+}
+
 
 static int
 ath5k_set_antenna(struct ieee80211_hw *hw, u32 tx_ant, u32 rx_ant)
@@ -802,7 +812,7 @@ const struct ieee80211_ops ath5k_hw_ops = {
        .get_survey             = ath5k_get_survey,
        .set_coverage_class     = ath5k_set_coverage_class,
        /* .rfkill_poll         = not implemented */
-       /* .flush               = not implemented */
+       .flush                  = ath5k_flush,
        /* .channel_switch      = not implemented */
        /* .napi_poll           = not implemented */
        .set_antenna            = ath5k_set_antenna,
-- 
Bob Copeland %% www.bobcopeland.com

_______________________________________________
ath5k-devel mailing list
ath5k-devel@lists.ath5k.org
https://lists.ath5k.org/mailman/listinfo/ath5k-devel

Reply via email to