summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--net/mac80211/ieee80211_i.h9
-rw-r--r--net/mac80211/main.c2
-rw-r--r--net/mac80211/tx.c144
-rw-r--r--net/mac80211/util.c22
4 files changed, 98 insertions, 79 deletions
diff --git a/net/mac80211/ieee80211_i.h b/net/mac80211/ieee80211_i.h
index 785f636..6ce62e5 100644
--- a/net/mac80211/ieee80211_i.h
+++ b/net/mac80211/ieee80211_i.h
@@ -184,10 +184,6 @@ struct ieee80211_rx_data {
u16 tkip_iv16;
};
-struct ieee80211_tx_stored_packet {
- struct sk_buff *skb;
-};
-
struct beacon_data {
u8 *head, *tail;
int head_len, tail_len;
@@ -583,6 +579,7 @@ enum queue_stop_reason {
IEEE80211_QUEUE_STOP_REASON_CSA,
IEEE80211_QUEUE_STOP_REASON_AGGREGATION,
IEEE80211_QUEUE_STOP_REASON_SUSPEND,
+ IEEE80211_QUEUE_STOP_REASON_PENDING,
};
struct ieee80211_master_priv {
@@ -639,9 +636,7 @@ struct ieee80211_local {
struct sta_info *sta_hash[STA_HASH_SIZE];
struct timer_list sta_cleanup;
- unsigned long queues_pending[BITS_TO_LONGS(IEEE80211_MAX_QUEUES)];
- unsigned long queues_pending_run[BITS_TO_LONGS(IEEE80211_MAX_QUEUES)];
- struct ieee80211_tx_stored_packet pending_packet[IEEE80211_MAX_QUEUES];
+ struct sk_buff_head pending[IEEE80211_MAX_QUEUES];
struct tasklet_struct tx_pending_tasklet;
/* number of interfaces with corresponding IFF_ flags */
diff --git a/net/mac80211/main.c b/net/mac80211/main.c
index dac68d4..a7430e9 100644
--- a/net/mac80211/main.c
+++ b/net/mac80211/main.c
@@ -781,6 +781,8 @@ struct ieee80211_hw *ieee80211_alloc_hw(size_t priv_data_len,
sta_info_init(local);
+ for (i = 0; i < IEEE80211_MAX_QUEUES; i++)
+ skb_queue_head_init(&local->pending[i]);
tasklet_init(&local->tx_pending_tasklet, ieee80211_tx_pending,
(unsigned long)local);
tasklet_disable(&local->tx_pending_tasklet);
diff --git a/net/mac80211/tx.c b/net/mac80211/tx.c
index 0d97cad..ee1b77f 100644
--- a/net/mac80211/tx.c
+++ b/net/mac80211/tx.c
@@ -1189,12 +1189,14 @@ static int ieee80211_tx(struct net_device *dev, struct sk_buff *skb)
struct ieee80211_tx_data tx;
ieee80211_tx_result res_prepare;
struct ieee80211_tx_info *info = IEEE80211_SKB_CB(skb);
- int ret;
+ struct sk_buff *next;
+ unsigned long flags;
+ int ret, retries;
u16 queue;
queue = skb_get_queue_mapping(skb);
- WARN_ON(test_bit(queue, local->queues_pending));
+ WARN_ON(!skb_queue_empty(&local->pending[queue]));
if (unlikely(skb->len < 10)) {
dev_kfree_skb(skb);
@@ -1219,40 +1221,52 @@ static int ieee80211_tx(struct net_device *dev, struct sk_buff *skb)
if (invoke_tx_handlers(&tx))
goto out;
-retry:
+ retries = 0;
+ retry:
ret = __ieee80211_tx(local, &tx);
- if (ret) {
- struct ieee80211_tx_stored_packet *store;
-
+ switch (ret) {
+ case IEEE80211_TX_OK:
+ break;
+ case IEEE80211_TX_AGAIN:
/*
* Since there are no fragmented frames on A-MPDU
* queues, there's no reason for a driver to reject
* a frame there, warn and drop it.
*/
- if (ret != IEEE80211_TX_PENDING)
- if (WARN_ON(info->flags & IEEE80211_TX_CTL_AMPDU))
- goto drop;
+ if (WARN_ON(info->flags & IEEE80211_TX_CTL_AMPDU))
+ goto drop;
+ /* fall through */
+ case IEEE80211_TX_PENDING:
+ skb = tx.skb;
- store = &local->pending_packet[queue];
+ spin_lock_irqsave(&local->queue_stop_reason_lock, flags);
- set_bit(queue, local->queues_pending);
- smp_mb();
- /*
- * When the driver gets out of buffers during sending of
- * fragments and calls ieee80211_stop_queue, the netif
- * subqueue is stopped. There is, however, a small window
- * in which the PENDING bit is not yet set. If a buffer
- * gets available in that window (i.e. driver calls
- * ieee80211_wake_queue), we would end up with ieee80211_tx
- * called with the PENDING bit still set. Prevent this by
- * continuing transmitting here when that situation is
- * possible to have happened.
- */
- if (!__netif_subqueue_stopped(local->mdev, queue)) {
- clear_bit(queue, local->queues_pending);
+ if (__netif_subqueue_stopped(local->mdev, queue)) {
+ do {
+ next = skb->next;
+ skb->next = NULL;
+ skb_queue_tail(&local->pending[queue], skb);
+ } while ((skb = next));
+
+ /*
+ * Make sure nobody will enable the queue on us
+ * (without going through the tasklet) nor disable the
+ * netdev queue underneath the pending handling code.
+ */
+ __set_bit(IEEE80211_QUEUE_STOP_REASON_PENDING,
+ &local->queue_stop_reasons[queue]);
+
+ spin_unlock_irqrestore(&local->queue_stop_reason_lock,
+ flags);
+ } else {
+ spin_unlock_irqrestore(&local->queue_stop_reason_lock,
+ flags);
+
+ retries++;
+ if (WARN(retries > 10, "tx refused but queue active"))
+ goto drop;
goto retry;
}
- store->skb = tx.skb;
}
out:
rcu_read_unlock();
@@ -1263,8 +1277,6 @@ retry:
skb = tx.skb;
while (skb) {
- struct sk_buff *next;
-
next = skb->next;
dev_kfree_skb(skb);
skb = next;
@@ -1803,23 +1815,10 @@ int ieee80211_subif_start_xmit(struct sk_buff *skb,
*/
void ieee80211_clear_tx_pending(struct ieee80211_local *local)
{
- struct sk_buff *skb;
int i;
- for (i = 0; i < local->hw.queues; i++) {
- if (!test_bit(i, local->queues_pending))
- continue;
-
- skb = local->pending_packet[i].skb;
- while (skb) {
- struct sk_buff *next;
-
- next = skb->next;
- dev_kfree_skb(skb);
- skb = next;
- }
- clear_bit(i, local->queues_pending);
- }
+ for (i = 0; i < local->hw.queues; i++)
+ skb_queue_purge(&local->pending[i]);
}
/*
@@ -1830,40 +1829,57 @@ void ieee80211_tx_pending(unsigned long data)
{
struct ieee80211_local *local = (struct ieee80211_local *)data;
struct net_device *dev = local->mdev;
- struct ieee80211_tx_stored_packet *store;
struct ieee80211_hdr *hdr;
+ unsigned long flags;
struct ieee80211_tx_data tx;
int i, ret;
+ bool next;
rcu_read_lock();
netif_tx_lock_bh(dev);
- for (i = 0; i < local->hw.queues; i++) {
- /* Check that this queue is ok */
- if (__netif_subqueue_stopped(local->mdev, i) &&
- !test_bit(i, local->queues_pending_run))
- continue;
- if (!test_bit(i, local->queues_pending)) {
- clear_bit(i, local->queues_pending_run);
- ieee80211_wake_queue(&local->hw, i);
+ for (i = 0; i < local->hw.queues; i++) {
+ /*
+ * If queue is stopped by something other than due to pending
+ * frames, or we have no pending frames, proceed to next queue.
+ */
+ spin_lock_irqsave(&local->queue_stop_reason_lock, flags);
+ next = false;
+ if (local->queue_stop_reasons[i] !=
+ BIT(IEEE80211_QUEUE_STOP_REASON_PENDING) ||
+ skb_queue_empty(&local->pending[i]))
+ next = true;
+ spin_unlock_irqrestore(&local->queue_stop_reason_lock, flags);
+
+ if (next)
continue;
- }
- clear_bit(i, local->queues_pending_run);
+ /*
+ * start the queue now to allow processing our packets,
+ * we're under the tx lock here anyway so nothing will
+ * happen as a result of this
+ */
netif_start_subqueue(local->mdev, i);
- store = &local->pending_packet[i];
- tx.flags = 0;
- tx.skb = store->skb;
- hdr = (struct ieee80211_hdr *)tx.skb->data;
- tx.sta = sta_info_get(local, hdr->addr1);
- ret = __ieee80211_tx(local, &tx);
- store->skb = tx.skb;
- if (!ret) {
- clear_bit(i, local->queues_pending);
- ieee80211_wake_queue(&local->hw, i);
+ while (!skb_queue_empty(&local->pending[i])) {
+ tx.flags = 0;
+ tx.skb = skb_dequeue(&local->pending[i]);
+ hdr = (struct ieee80211_hdr *)tx.skb->data;
+ tx.sta = sta_info_get(local, hdr->addr1);
+
+ ret = __ieee80211_tx(local, &tx);
+ if (ret != IEEE80211_TX_OK) {
+ skb_queue_head(&local->pending[i], tx.skb);
+ break;
+ }
}
+
+ /* Start regular packet processing again. */
+ if (skb_queue_empty(&local->pending[i]))
+ ieee80211_wake_queue_by_reason(&local->hw, i,
+ IEEE80211_QUEUE_STOP_REASON_PENDING);
}
+
netif_tx_unlock_bh(dev);
rcu_read_unlock();
}
diff --git a/net/mac80211/util.c b/net/mac80211/util.c
index 021166c..0247d80 100644
--- a/net/mac80211/util.c
+++ b/net/mac80211/util.c
@@ -365,16 +365,16 @@ static void __ieee80211_wake_queue(struct ieee80211_hw *hw, int queue,
__clear_bit(reason, &local->queue_stop_reasons[queue]);
+ if (!skb_queue_empty(&local->pending[queue]) &&
+ local->queue_stop_reasons[queue] ==
+ BIT(IEEE80211_QUEUE_STOP_REASON_PENDING))
+ tasklet_schedule(&local->tx_pending_tasklet);
+
if (local->queue_stop_reasons[queue] != 0)
/* someone still has this queue stopped */
return;
- if (test_bit(queue, local->queues_pending)) {
- set_bit(queue, local->queues_pending_run);
- tasklet_schedule(&local->tx_pending_tasklet);
- } else {
- netif_wake_subqueue(local->mdev, queue);
- }
+ netif_wake_subqueue(local->mdev, queue);
}
void ieee80211_wake_queue_by_reason(struct ieee80211_hw *hw, int queue,
@@ -420,9 +420,15 @@ static void __ieee80211_stop_queue(struct ieee80211_hw *hw, int queue,
reason = IEEE80211_QUEUE_STOP_REASON_AGGREGATION;
}
- __set_bit(reason, &local->queue_stop_reasons[queue]);
+ /*
+ * Only stop if it was previously running, this is necessary
+ * for correct pending packets handling because there we may
+ * start (but not wake) the queue and rely on that.
+ */
+ if (!local->queue_stop_reasons[queue])
+ netif_stop_subqueue(local->mdev, queue);
- netif_stop_subqueue(local->mdev, queue);
+ __set_bit(reason, &local->queue_stop_reasons[queue]);
}
void ieee80211_stop_queue_by_reason(struct ieee80211_hw *hw, int queue,
OpenPOWER on IntegriCloud