mac80211: rewrite fragmentation
Fragmentation currently uses an allocated array to store the
fragment skbs, and then keeps track of which have been sent
and which are still pending etc. This is rather complicated;
make it simpler by just chaining the fragments into skb->next
and removing from that list when sent. Also simplifies all
code that needs to touch fragments, since it now only needs
to walk the skb->next list.
This is a prerequisite for fixing the stored packet code,
which I need to do for proper aggregation packet storing.
Signed-off-by: Johannes Berg <johannes@sipsolutions.net>
Reviewed-by: Luis R. Rodriguez <lrodriguez@atheros.com>
Signed-off-by: John W. Linville <linville@tuxdriver.com>
diff --git a/net/mac80211/tx.c b/net/mac80211/tx.c
index f3f240c..51bf49c 100644
--- a/net/mac80211/tx.c
+++ b/net/mac80211/tx.c
@@ -34,8 +34,7 @@
#define IEEE80211_TX_OK 0
#define IEEE80211_TX_AGAIN 1
-#define IEEE80211_TX_FRAG_AGAIN 2
-#define IEEE80211_TX_PENDING 3
+#define IEEE80211_TX_PENDING 2
/* misc utils */
@@ -702,17 +701,62 @@
return TX_CONTINUE;
}
+static int ieee80211_fragment(struct ieee80211_local *local,
+ struct sk_buff *skb, int hdrlen,
+ int frag_threshold)
+{
+ struct sk_buff *tail = skb, *tmp;
+ int per_fragm = frag_threshold - hdrlen - FCS_LEN;
+ int pos = hdrlen + per_fragm;
+ int rem = skb->len - hdrlen - per_fragm;
+
+ if (WARN_ON(rem < 0))
+ return -EINVAL;
+
+ while (rem) {
+ int fraglen = per_fragm;
+
+ if (fraglen > rem)
+ fraglen = rem;
+ rem -= fraglen;
+ tmp = dev_alloc_skb(local->tx_headroom +
+ frag_threshold +
+ IEEE80211_ENCRYPT_HEADROOM +
+ IEEE80211_ENCRYPT_TAILROOM);
+ if (!tmp)
+ return -ENOMEM;
+ tail->next = tmp;
+ tail = tmp;
+ skb_reserve(tmp, local->tx_headroom +
+ IEEE80211_ENCRYPT_HEADROOM);
+ /* copy control information */
+ memcpy(tmp->cb, skb->cb, sizeof(tmp->cb));
+ skb_copy_queue_mapping(tmp, skb);
+ tmp->priority = skb->priority;
+ tmp->do_not_encrypt = skb->do_not_encrypt;
+ tmp->dev = skb->dev;
+ tmp->iif = skb->iif;
+
+ /* copy header and data */
+ memcpy(skb_put(tmp, hdrlen), skb->data, hdrlen);
+ memcpy(skb_put(tmp, fraglen), skb->data + pos, fraglen);
+
+ pos += fraglen;
+ }
+
+ skb->len = hdrlen + per_fragm;
+ return 0;
+}
+
static ieee80211_tx_result debug_noinline
ieee80211_tx_h_fragment(struct ieee80211_tx_data *tx)
{
- struct ieee80211_tx_info *info = IEEE80211_SKB_CB(tx->skb);
- struct ieee80211_hdr *hdr = (struct ieee80211_hdr *)tx->skb->data;
- size_t hdrlen, per_fragm, num_fragm, payload_len, left;
- struct sk_buff **frags, *first, *frag;
- int i;
- u16 seq;
- u8 *pos;
+ struct sk_buff *skb = tx->skb;
+ struct ieee80211_tx_info *info = IEEE80211_SKB_CB(skb);
+ struct ieee80211_hdr *hdr = (void *)skb->data;
int frag_threshold = tx->local->fragmentation_threshold;
+ int hdrlen;
+ int fragnum;
if (!(tx->flags & IEEE80211_TX_FRAGMENTED))
return TX_CONTINUE;
@@ -725,58 +769,35 @@
if (WARN_ON(info->flags & IEEE80211_TX_CTL_AMPDU))
return TX_DROP;
- first = tx->skb;
-
hdrlen = ieee80211_hdrlen(hdr->frame_control);
- payload_len = first->len - hdrlen;
- per_fragm = frag_threshold - hdrlen - FCS_LEN;
- num_fragm = DIV_ROUND_UP(payload_len, per_fragm);
- frags = kzalloc(num_fragm * sizeof(struct sk_buff *), GFP_ATOMIC);
- if (!frags)
- goto fail;
+ /* internal error, why is TX_FRAGMENTED set? */
+ if (WARN_ON(skb->len <= frag_threshold))
+ return TX_DROP;
- hdr->frame_control |= cpu_to_le16(IEEE80211_FCTL_MOREFRAGS);
- seq = le16_to_cpu(hdr->seq_ctrl) & IEEE80211_SCTL_SEQ;
- pos = first->data + hdrlen + per_fragm;
- left = payload_len - per_fragm;
- for (i = 0; i < num_fragm - 1; i++) {
- struct ieee80211_hdr *fhdr;
- size_t copylen;
+ /*
+ * Now fragment the frame. This will allocate all the fragments and
+ * chain them (using skb as the first fragment) to skb->next.
+ * During transmission, we will remove the successfully transmitted
+ * fragments from this list. When the low-level driver rejects one
+ * of the fragments then we will simply pretend to accept the skb
+ * but store it away as pending.
+ */
+ if (ieee80211_fragment(tx->local, skb, hdrlen, frag_threshold))
+ return TX_DROP;
- if (left <= 0)
- goto fail;
+ /* update duration/seq/flags of fragments */
+ fragnum = 0;
+ do {
+ int next_len;
+ const __le16 morefrags = cpu_to_le16(IEEE80211_FCTL_MOREFRAGS);
- /* reserve enough extra head and tail room for possible
- * encryption */
- frag = frags[i] =
- dev_alloc_skb(tx->local->tx_headroom +
- frag_threshold +
- IEEE80211_ENCRYPT_HEADROOM +
- IEEE80211_ENCRYPT_TAILROOM);
- if (!frag)
- goto fail;
+ hdr = (void *)skb->data;
+ info = IEEE80211_SKB_CB(skb);
- /* Make sure that all fragments use the same priority so
- * that they end up using the same TX queue */
- frag->priority = first->priority;
-
- skb_reserve(frag, tx->local->tx_headroom +
- IEEE80211_ENCRYPT_HEADROOM);
-
- /* copy TX information */
- info = IEEE80211_SKB_CB(frag);
- memcpy(info, first->cb, sizeof(frag->cb));
-
- /* copy/fill in 802.11 header */
- fhdr = (struct ieee80211_hdr *) skb_put(frag, hdrlen);
- memcpy(fhdr, first->data, hdrlen);
- fhdr->seq_ctrl = cpu_to_le16(seq | ((i + 1) & IEEE80211_SCTL_FRAG));
-
- if (i == num_fragm - 2) {
- /* clear MOREFRAGS bit for the last fragment */
- fhdr->frame_control &= cpu_to_le16(~IEEE80211_FCTL_MOREFRAGS);
- } else {
+ if (skb->next) {
+ hdr->frame_control |= morefrags;
+ next_len = skb->next->len;
/*
* No multi-rate retries for fragmented frames, that
* would completely throw off the NAV at other STAs.
@@ -787,37 +808,16 @@
info->control.rates[4].idx = -1;
BUILD_BUG_ON(IEEE80211_TX_MAX_RATES != 5);
info->flags &= ~IEEE80211_TX_CTL_RATE_CTRL_PROBE;
+ } else {
+ hdr->frame_control &= ~morefrags;
+ next_len = 0;
}
-
- /* copy data */
- copylen = left > per_fragm ? per_fragm : left;
- memcpy(skb_put(frag, copylen), pos, copylen);
-
- skb_copy_queue_mapping(frag, first);
-
- frag->do_not_encrypt = first->do_not_encrypt;
- frag->dev = first->dev;
- frag->iif = first->iif;
-
- pos += copylen;
- left -= copylen;
- }
- skb_trim(first, hdrlen + per_fragm);
-
- tx->num_extra_frag = num_fragm - 1;
- tx->extra_frag = frags;
+ hdr->duration_id = ieee80211_duration(tx, 0, next_len);
+ hdr->seq_ctrl |= cpu_to_le16(fragnum & IEEE80211_SCTL_FRAG);
+ fragnum++;
+ } while ((skb = skb->next));
return TX_CONTINUE;
-
- fail:
- if (frags) {
- for (i = 0; i < num_fragm - 1; i++)
- if (frags[i])
- dev_kfree_skb(frags[i]);
- kfree(frags);
- }
- I802_DEBUG_INC(tx->local->tx_handlers_drop_fragment);
- return TX_DROP;
}
static ieee80211_tx_result debug_noinline
@@ -845,27 +845,19 @@
static ieee80211_tx_result debug_noinline
ieee80211_tx_h_calculate_duration(struct ieee80211_tx_data *tx)
{
- struct ieee80211_hdr *hdr = (struct ieee80211_hdr *)tx->skb->data;
- int next_len, i;
- int group_addr = is_multicast_ether_addr(hdr->addr1);
+ struct sk_buff *skb = tx->skb;
+ struct ieee80211_hdr *hdr;
+ int next_len;
+ bool group_addr;
- if (!(tx->flags & IEEE80211_TX_FRAGMENTED)) {
- hdr->duration_id = ieee80211_duration(tx, group_addr, 0);
- return TX_CONTINUE;
- }
+ do {
+ hdr = (void *) skb->data;
+ next_len = skb->next ? skb->next->len : 0;
+ group_addr = is_multicast_ether_addr(hdr->addr1);
- hdr->duration_id = ieee80211_duration(tx, group_addr,
- tx->extra_frag[0]->len);
-
- for (i = 0; i < tx->num_extra_frag; i++) {
- if (i + 1 < tx->num_extra_frag)
- next_len = tx->extra_frag[i + 1]->len;
- else
- next_len = 0;
-
- hdr = (struct ieee80211_hdr *)tx->extra_frag[i]->data;
- hdr->duration_id = ieee80211_duration(tx, 0, next_len);
- }
+ hdr->duration_id =
+ ieee80211_duration(tx, group_addr, next_len);
+ } while ((skb = skb->next));
return TX_CONTINUE;
}
@@ -873,19 +865,16 @@
static ieee80211_tx_result debug_noinline
ieee80211_tx_h_stats(struct ieee80211_tx_data *tx)
{
- int i;
+ struct sk_buff *skb = tx->skb;
if (!tx->sta)
return TX_CONTINUE;
tx->sta->tx_packets++;
- tx->sta->tx_fragments++;
- tx->sta->tx_bytes += tx->skb->len;
- if (tx->extra_frag) {
- tx->sta->tx_fragments += tx->num_extra_frag;
- for (i = 0; i < tx->num_extra_frag; i++)
- tx->sta->tx_bytes += tx->extra_frag[i]->len;
- }
+ do {
+ tx->sta->tx_fragments++;
+ tx->sta->tx_bytes += skb->len;
+ } while ((skb = skb->next));
return TX_CONTINUE;
}
@@ -1099,45 +1088,36 @@
return 0;
}
-static int __ieee80211_tx(struct ieee80211_local *local, struct sk_buff *skb,
+static int __ieee80211_tx(struct ieee80211_local *local,
struct ieee80211_tx_data *tx)
{
+ struct sk_buff *skb = tx->skb, *next;
struct ieee80211_tx_info *info;
- int ret, i;
+ int ret;
+ bool fragm = false;
- if (skb) {
+ local->mdev->trans_start = jiffies;
+
+ while (skb) {
if (ieee80211_queue_stopped(&local->hw,
skb_get_queue_mapping(skb)))
return IEEE80211_TX_PENDING;
- ret = local->ops->tx(local_to_hw(local), skb);
- if (ret)
- return IEEE80211_TX_AGAIN;
- local->mdev->trans_start = jiffies;
- ieee80211_led_tx(local, 1);
- }
- if (tx->extra_frag) {
- for (i = 0; i < tx->num_extra_frag; i++) {
- if (!tx->extra_frag[i])
- continue;
- info = IEEE80211_SKB_CB(tx->extra_frag[i]);
+ if (fragm) {
+ info = IEEE80211_SKB_CB(skb);
info->flags &= ~(IEEE80211_TX_CTL_CLEAR_PS_FILT |
IEEE80211_TX_CTL_FIRST_FRAGMENT);
- if (ieee80211_queue_stopped(&local->hw,
- skb_get_queue_mapping(tx->extra_frag[i])))
- return IEEE80211_TX_FRAG_AGAIN;
-
- ret = local->ops->tx(local_to_hw(local),
- tx->extra_frag[i]);
- if (ret)
- return IEEE80211_TX_FRAG_AGAIN;
- local->mdev->trans_start = jiffies;
- ieee80211_led_tx(local, 1);
- tx->extra_frag[i] = NULL;
}
- kfree(tx->extra_frag);
- tx->extra_frag = NULL;
+
+ next = skb->next;
+ ret = local->ops->tx(local_to_hw(local), skb);
+ if (ret != NETDEV_TX_OK)
+ return IEEE80211_TX_AGAIN;
+ tx->skb = skb = next;
+ ieee80211_led_tx(local, 1);
+ fragm = true;
}
+
return IEEE80211_TX_OK;
}
@@ -1149,7 +1129,6 @@
{
struct sk_buff *skb = tx->skb;
ieee80211_tx_result res = TX_DROP;
- int i;
#define CALL_TXH(txh) \
res = txh(tx); \
@@ -1173,11 +1152,13 @@
txh_done:
if (unlikely(res == TX_DROP)) {
I802_DEBUG_INC(tx->local->tx_handlers_drop);
- dev_kfree_skb(skb);
- for (i = 0; i < tx->num_extra_frag; i++)
- if (tx->extra_frag[i])
- dev_kfree_skb(tx->extra_frag[i]);
- kfree(tx->extra_frag);
+ while (skb) {
+ struct sk_buff *next;
+
+ next = skb->next;
+ dev_kfree_skb(skb);
+ skb = next;
+ }
return -1;
} else if (unlikely(res == TX_QUEUED)) {
I802_DEBUG_INC(tx->local->tx_handlers_queued);
@@ -1194,7 +1175,7 @@
struct ieee80211_tx_data tx;
ieee80211_tx_result res_prepare;
struct ieee80211_tx_info *info = IEEE80211_SKB_CB(skb);
- int ret, i;
+ int ret;
u16 queue;
queue = skb_get_queue_mapping(skb);
@@ -1225,7 +1206,7 @@
goto out;
retry:
- ret = __ieee80211_tx(local, skb, &tx);
+ ret = __ieee80211_tx(local, &tx);
if (ret) {
struct ieee80211_tx_stored_packet *store;
@@ -1240,9 +1221,6 @@
store = &local->pending_packet[queue];
- if (ret == IEEE80211_TX_FRAG_AGAIN)
- skb = NULL;
-
set_bit(queue, local->queues_pending);
smp_mb();
/*
@@ -1260,22 +1238,23 @@
clear_bit(queue, local->queues_pending);
goto retry;
}
- store->skb = skb;
- store->extra_frag = tx.extra_frag;
- store->num_extra_frag = tx.num_extra_frag;
+ store->skb = tx.skb;
}
out:
rcu_read_unlock();
return 0;
drop:
- if (skb)
- dev_kfree_skb(skb);
- for (i = 0; i < tx.num_extra_frag; i++)
- if (tx.extra_frag[i])
- dev_kfree_skb(tx.extra_frag[i]);
- kfree(tx.extra_frag);
rcu_read_unlock();
+
+ skb = tx.skb;
+ while (skb) {
+ struct sk_buff *next;
+
+ next = skb->next;
+ dev_kfree_skb(skb);
+ skb = next;
+ }
return 0;
}
@@ -1810,17 +1789,21 @@
*/
void ieee80211_clear_tx_pending(struct ieee80211_local *local)
{
- int i, j;
- struct ieee80211_tx_stored_packet *store;
+ struct sk_buff *skb;
+ int i;
for (i = 0; i < local->hw.queues; i++) {
if (!test_bit(i, local->queues_pending))
continue;
- store = &local->pending_packet[i];
- kfree_skb(store->skb);
- for (j = 0; j < store->num_extra_frag; j++)
- kfree_skb(store->extra_frag[j]);
- kfree(store->extra_frag);
+
+ 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);
}
}
@@ -1854,14 +1837,11 @@
netif_start_subqueue(local->mdev, i);
store = &local->pending_packet[i];
- tx.extra_frag = store->extra_frag;
- tx.num_extra_frag = store->num_extra_frag;
tx.flags = 0;
- ret = __ieee80211_tx(local, store->skb, &tx);
- if (ret) {
- if (ret == IEEE80211_TX_FRAG_AGAIN)
- store->skb = NULL;
- } else {
+ tx.skb = store->skb;
+ ret = __ieee80211_tx(local, &tx);
+ store->skb = tx.skb;
+ if (!ret) {
clear_bit(i, local->queues_pending);
ieee80211_wake_queue(&local->hw, i);
}