Commit 7d360f60 authored by Felix Fietkau's avatar Felix Fietkau Committed by Johannes Berg

wifi: mac80211: add support for restricting netdev features per vif

This can be used to selectively disable feature flags for checksum offload,
scatter/gather or GSO by changing vif->netdev_features.
Removing features from vif->netdev_features does not affect the netdev
features themselves, but instead fixes up skbs in the tx path so that the
offloads are not needed in the driver.

Aside from making it easier to deal with vif type based hardware limitations,
this also makes it possible to optimize performance on hardware without native
GSO support by declaring GSO support in hw->netdev_features and removing it
from vif->netdev_features. This allows mac80211 to handle GSO segmentation
after the sta lookup, but before itxq enqueue, thus reducing the number of
unnecessary sta lookups, as well as some other per-packet processing.
Signed-off-by: default avatarFelix Fietkau <nbd@nbd.name>
Link: https://lore.kernel.org/r/20221010094338.78070-1-nbd@nbd.nameSigned-off-by: default avatarJohannes Berg <johannes.berg@intel.com>
parent 209d70d3
...@@ -200,6 +200,7 @@ static void fq_tin_enqueue(struct fq *fq, ...@@ -200,6 +200,7 @@ static void fq_tin_enqueue(struct fq *fq,
fq_skb_free_t free_func) fq_skb_free_t free_func)
{ {
struct fq_flow *flow; struct fq_flow *flow;
struct sk_buff *next;
bool oom; bool oom;
lockdep_assert_held(&fq->lock); lockdep_assert_held(&fq->lock);
...@@ -214,11 +215,15 @@ static void fq_tin_enqueue(struct fq *fq, ...@@ -214,11 +215,15 @@ static void fq_tin_enqueue(struct fq *fq,
} }
flow->tin = tin; flow->tin = tin;
flow->backlog += skb->len; skb_list_walk_safe(skb, skb, next) {
tin->backlog_bytes += skb->len; skb_mark_not_on_list(skb);
tin->backlog_packets++; flow->backlog += skb->len;
fq->memory_usage += skb->truesize; tin->backlog_bytes += skb->len;
fq->backlog++; tin->backlog_packets++;
fq->memory_usage += skb->truesize;
fq->backlog++;
__skb_queue_tail(&flow->queue, skb);
}
if (list_empty(&flow->flowchain)) { if (list_empty(&flow->flowchain)) {
flow->deficit = fq->quantum; flow->deficit = fq->quantum;
...@@ -226,7 +231,6 @@ static void fq_tin_enqueue(struct fq *fq, ...@@ -226,7 +231,6 @@ static void fq_tin_enqueue(struct fq *fq,
&tin->new_flows); &tin->new_flows);
} }
__skb_queue_tail(&flow->queue, skb);
oom = (fq->memory_usage > fq->memory_limit); oom = (fq->memory_usage > fq->memory_limit);
while (fq->backlog > fq->limit || oom) { while (fq->backlog > fq->limit || oom) {
flow = fq_find_fattest_flow(fq); flow = fq_find_fattest_flow(fq);
......
...@@ -1807,6 +1807,10 @@ struct ieee80211_vif_cfg { ...@@ -1807,6 +1807,10 @@ struct ieee80211_vif_cfg {
* @addr: address of this interface * @addr: address of this interface
* @p2p: indicates whether this AP or STA interface is a p2p * @p2p: indicates whether this AP or STA interface is a p2p
* interface, i.e. a GO or p2p-sta respectively * interface, i.e. a GO or p2p-sta respectively
* @netdev_features: tx netdev features supported by the hardware for this
* vif. mac80211 initializes this to hw->netdev_features, and the driver
* can mask out specific tx features. mac80211 will handle software fixup
* for masked offloads (GSO, CSUM)
* @driver_flags: flags/capabilities the driver has for this interface, * @driver_flags: flags/capabilities the driver has for this interface,
* these need to be set (or cleared) when the interface is added * these need to be set (or cleared) when the interface is added
* or, if supported by the driver, the interface type is changed * or, if supported by the driver, the interface type is changed
...@@ -1848,6 +1852,7 @@ struct ieee80211_vif { ...@@ -1848,6 +1852,7 @@ struct ieee80211_vif {
struct ieee80211_txq *txq; struct ieee80211_txq *txq;
netdev_features_t netdev_features;
u32 driver_flags; u32 driver_flags;
u32 offload_flags; u32 offload_flags;
......
...@@ -2178,6 +2178,7 @@ int ieee80211_if_add(struct ieee80211_local *local, const char *name, ...@@ -2178,6 +2178,7 @@ int ieee80211_if_add(struct ieee80211_local *local, const char *name,
ndev->priv_flags |= IFF_LIVE_ADDR_CHANGE; ndev->priv_flags |= IFF_LIVE_ADDR_CHANGE;
ndev->hw_features |= ndev->features & ndev->hw_features |= ndev->features &
MAC80211_SUPPORTED_FEATURES_TX; MAC80211_SUPPORTED_FEATURES_TX;
sdata->vif.netdev_features = local->hw.netdev_features;
netdev_set_default_ethtool_ops(ndev, &ieee80211_ethtool_ops); netdev_set_default_ethtool_ops(ndev, &ieee80211_ethtool_ops);
......
...@@ -1355,7 +1355,11 @@ static struct txq_info *ieee80211_get_txq(struct ieee80211_local *local, ...@@ -1355,7 +1355,11 @@ static struct txq_info *ieee80211_get_txq(struct ieee80211_local *local,
static void ieee80211_set_skb_enqueue_time(struct sk_buff *skb) static void ieee80211_set_skb_enqueue_time(struct sk_buff *skb)
{ {
IEEE80211_SKB_CB(skb)->control.enqueue_time = codel_get_time(); struct sk_buff *next;
codel_time_t now = codel_get_time();
skb_list_walk_safe(skb, skb, next)
IEEE80211_SKB_CB(skb)->control.enqueue_time = now;
} }
static u32 codel_skb_len_func(const struct sk_buff *skb) static u32 codel_skb_len_func(const struct sk_buff *skb)
...@@ -3578,55 +3582,79 @@ ieee80211_xmit_fast_finish(struct ieee80211_sub_if_data *sdata, ...@@ -3578,55 +3582,79 @@ ieee80211_xmit_fast_finish(struct ieee80211_sub_if_data *sdata,
return TX_CONTINUE; return TX_CONTINUE;
} }
static bool ieee80211_xmit_fast(struct ieee80211_sub_if_data *sdata, static netdev_features_t
struct sta_info *sta, ieee80211_sdata_netdev_features(struct ieee80211_sub_if_data *sdata)
struct ieee80211_fast_tx *fast_tx,
struct sk_buff *skb)
{ {
struct ieee80211_local *local = sdata->local; if (sdata->vif.type != NL80211_IFTYPE_AP_VLAN)
u16 ethertype = (skb->data[12] << 8) | skb->data[13]; return sdata->vif.netdev_features;
int extra_head = fast_tx->hdr_len - (ETH_HLEN - 2);
int hw_headroom = sdata->local->hw.extra_tx_headroom;
struct ethhdr eth;
struct ieee80211_tx_info *info;
struct ieee80211_hdr *hdr = (void *)fast_tx->hdr;
struct ieee80211_tx_data tx;
ieee80211_tx_result r;
struct tid_ampdu_tx *tid_tx = NULL;
u8 tid = IEEE80211_NUM_TIDS;
/* control port protocol needs a lot of special handling */ if (!sdata->bss)
if (cpu_to_be16(ethertype) == sdata->control_port_protocol) return 0;
return false;
/* only RFC 1042 SNAP */ sdata = container_of(sdata->bss, struct ieee80211_sub_if_data, u.ap);
if (ethertype < ETH_P_802_3_MIN) return sdata->vif.netdev_features;
return false; }
/* don't handle TX status request here either */ static struct sk_buff *
if (skb->sk && skb_shinfo(skb)->tx_flags & SKBTX_WIFI_STATUS) ieee80211_tx_skb_fixup(struct sk_buff *skb, netdev_features_t features)
return false; {
if (skb_is_gso(skb)) {
struct sk_buff *segs;
if (hdr->frame_control & cpu_to_le16(IEEE80211_STYPE_QOS_DATA)) { segs = skb_gso_segment(skb, features);
tid = skb->priority & IEEE80211_QOS_CTL_TAG1D_MASK; if (!segs)
tid_tx = rcu_dereference(sta->ampdu_mlme.tid_tx[tid]); return skb;
if (tid_tx) { if (IS_ERR(segs))
if (!test_bit(HT_AGG_STATE_OPERATIONAL, &tid_tx->state)) goto free;
return false;
if (tid_tx->timeout) consume_skb(skb);
tid_tx->last_tx = jiffies; return segs;
}
} }
/* after this point (skb is modified) we cannot return false */ if (skb_needs_linearize(skb, features) && __skb_linearize(skb))
goto free;
if (skb->ip_summed == CHECKSUM_PARTIAL) {
int ofs = skb_checksum_start_offset(skb);
if (skb->encapsulation)
skb_set_inner_transport_header(skb, ofs);
else
skb_set_transport_header(skb, ofs);
if (skb_csum_hwoffload_help(skb, features))
goto free;
}
skb_mark_not_on_list(skb);
return skb;
free:
kfree_skb(skb);
return NULL;
}
static void __ieee80211_xmit_fast(struct ieee80211_sub_if_data *sdata,
struct sta_info *sta,
struct ieee80211_fast_tx *fast_tx,
struct sk_buff *skb, u8 tid, bool ampdu)
{
struct ieee80211_local *local = sdata->local;
struct ieee80211_hdr *hdr = (void *)fast_tx->hdr;
struct ieee80211_tx_info *info;
struct ieee80211_tx_data tx;
ieee80211_tx_result r;
int hw_headroom = sdata->local->hw.extra_tx_headroom;
int extra_head = fast_tx->hdr_len - (ETH_HLEN - 2);
struct ethhdr eth;
skb = skb_share_check(skb, GFP_ATOMIC); skb = skb_share_check(skb, GFP_ATOMIC);
if (unlikely(!skb)) if (unlikely(!skb))
return true; return;
if ((hdr->frame_control & cpu_to_le16(IEEE80211_STYPE_QOS_DATA)) && if ((hdr->frame_control & cpu_to_le16(IEEE80211_STYPE_QOS_DATA)) &&
ieee80211_amsdu_aggregate(sdata, sta, fast_tx, skb)) ieee80211_amsdu_aggregate(sdata, sta, fast_tx, skb))
return true; return;
/* will not be crypto-handled beyond what we do here, so use false /* will not be crypto-handled beyond what we do here, so use false
* as the may-encrypt argument for the resize to not account for * as the may-encrypt argument for the resize to not account for
...@@ -3635,10 +3663,8 @@ static bool ieee80211_xmit_fast(struct ieee80211_sub_if_data *sdata, ...@@ -3635,10 +3663,8 @@ static bool ieee80211_xmit_fast(struct ieee80211_sub_if_data *sdata,
if (unlikely(ieee80211_skb_resize(sdata, skb, if (unlikely(ieee80211_skb_resize(sdata, skb,
max_t(int, extra_head + hw_headroom - max_t(int, extra_head + hw_headroom -
skb_headroom(skb), 0), skb_headroom(skb), 0),
ENCRYPT_NO))) { ENCRYPT_NO)))
kfree_skb(skb); goto free;
return true;
}
memcpy(&eth, skb->data, ETH_HLEN - 2); memcpy(&eth, skb->data, ETH_HLEN - 2);
hdr = skb_push(skb, extra_head); hdr = skb_push(skb, extra_head);
...@@ -3652,7 +3678,7 @@ static bool ieee80211_xmit_fast(struct ieee80211_sub_if_data *sdata, ...@@ -3652,7 +3678,7 @@ static bool ieee80211_xmit_fast(struct ieee80211_sub_if_data *sdata,
info->control.vif = &sdata->vif; info->control.vif = &sdata->vif;
info->flags = IEEE80211_TX_CTL_FIRST_FRAGMENT | info->flags = IEEE80211_TX_CTL_FIRST_FRAGMENT |
IEEE80211_TX_CTL_DONTFRAG | IEEE80211_TX_CTL_DONTFRAG |
(tid_tx ? IEEE80211_TX_CTL_AMPDU : 0); (ampdu ? IEEE80211_TX_CTL_AMPDU : 0);
info->control.flags = IEEE80211_TX_CTRL_FAST_XMIT | info->control.flags = IEEE80211_TX_CTRL_FAST_XMIT |
u32_encode_bits(IEEE80211_LINK_UNSPECIFIED, u32_encode_bits(IEEE80211_LINK_UNSPECIFIED,
IEEE80211_TX_CTRL_MLO_LINK); IEEE80211_TX_CTRL_MLO_LINK);
...@@ -3676,16 +3702,14 @@ static bool ieee80211_xmit_fast(struct ieee80211_sub_if_data *sdata, ...@@ -3676,16 +3702,14 @@ static bool ieee80211_xmit_fast(struct ieee80211_sub_if_data *sdata,
tx.key = fast_tx->key; tx.key = fast_tx->key;
if (ieee80211_queue_skb(local, sdata, sta, skb)) if (ieee80211_queue_skb(local, sdata, sta, skb))
return true; return;
tx.skb = skb; tx.skb = skb;
r = ieee80211_xmit_fast_finish(sdata, sta, fast_tx->pn_offs, r = ieee80211_xmit_fast_finish(sdata, sta, fast_tx->pn_offs,
fast_tx->key, &tx); fast_tx->key, &tx);
tx.skb = NULL; tx.skb = NULL;
if (r == TX_DROP) { if (r == TX_DROP)
kfree_skb(skb); goto free;
return true;
}
if (sdata->vif.type == NL80211_IFTYPE_AP_VLAN) if (sdata->vif.type == NL80211_IFTYPE_AP_VLAN)
sdata = container_of(sdata->bss, sdata = container_of(sdata->bss,
...@@ -3693,6 +3717,56 @@ static bool ieee80211_xmit_fast(struct ieee80211_sub_if_data *sdata, ...@@ -3693,6 +3717,56 @@ static bool ieee80211_xmit_fast(struct ieee80211_sub_if_data *sdata,
__skb_queue_tail(&tx.skbs, skb); __skb_queue_tail(&tx.skbs, skb);
ieee80211_tx_frags(local, &sdata->vif, sta, &tx.skbs, false); ieee80211_tx_frags(local, &sdata->vif, sta, &tx.skbs, false);
return;
free:
kfree_skb(skb);
}
static bool ieee80211_xmit_fast(struct ieee80211_sub_if_data *sdata,
struct sta_info *sta,
struct ieee80211_fast_tx *fast_tx,
struct sk_buff *skb)
{
u16 ethertype = (skb->data[12] << 8) | skb->data[13];
struct ieee80211_hdr *hdr = (void *)fast_tx->hdr;
struct tid_ampdu_tx *tid_tx = NULL;
struct sk_buff *next;
u8 tid = IEEE80211_NUM_TIDS;
/* control port protocol needs a lot of special handling */
if (cpu_to_be16(ethertype) == sdata->control_port_protocol)
return false;
/* only RFC 1042 SNAP */
if (ethertype < ETH_P_802_3_MIN)
return false;
/* don't handle TX status request here either */
if (skb->sk && skb_shinfo(skb)->tx_flags & SKBTX_WIFI_STATUS)
return false;
if (hdr->frame_control & cpu_to_le16(IEEE80211_STYPE_QOS_DATA)) {
tid = skb->priority & IEEE80211_QOS_CTL_TAG1D_MASK;
tid_tx = rcu_dereference(sta->ampdu_mlme.tid_tx[tid]);
if (tid_tx) {
if (!test_bit(HT_AGG_STATE_OPERATIONAL, &tid_tx->state))
return false;
if (tid_tx->timeout)
tid_tx->last_tx = jiffies;
}
}
/* after this point (skb is modified) we cannot return false */
skb = ieee80211_tx_skb_fixup(skb, ieee80211_sdata_netdev_features(sdata));
if (!skb)
return true;
skb_list_walk_safe(skb, skb, next) {
skb_mark_not_on_list(skb);
__ieee80211_xmit_fast(sdata, sta, fast_tx, skb, tid, tid_tx);
}
return true; return true;
} }
...@@ -4192,31 +4266,14 @@ void __ieee80211_subif_start_xmit(struct sk_buff *skb, ...@@ -4192,31 +4266,14 @@ void __ieee80211_subif_start_xmit(struct sk_buff *skb,
goto out; goto out;
} }
if (skb_is_gso(skb)) { /* the frame could be fragmented, software-encrypted, and other
struct sk_buff *segs; * things so we cannot really handle checksum or GSO offload.
* fix it up in software before we handle anything else.
segs = skb_gso_segment(skb, 0); */
if (IS_ERR(segs)) { skb = ieee80211_tx_skb_fixup(skb, 0);
goto out_free; if (!skb) {
} else if (segs) { len = 0;
consume_skb(skb); goto out;
skb = segs;
}
} else {
/* we cannot process non-linear frames on this path */
if (skb_linearize(skb))
goto out_free;
/* the frame could be fragmented, software-encrypted, and other
* things so we cannot really handle checksum offload with it -
* fix it up in software before we handle anything else.
*/
if (skb->ip_summed == CHECKSUM_PARTIAL) {
skb_set_transport_header(skb,
skb_checksum_start_offset(skb));
if (skb_checksum_help(skb))
goto out_free;
}
} }
skb_list_walk_safe(skb, skb, next) { skb_list_walk_safe(skb, skb, next) {
...@@ -4434,9 +4491,11 @@ netdev_tx_t ieee80211_subif_start_xmit(struct sk_buff *skb, ...@@ -4434,9 +4491,11 @@ netdev_tx_t ieee80211_subif_start_xmit(struct sk_buff *skb,
return NETDEV_TX_OK; return NETDEV_TX_OK;
} }
static bool ieee80211_tx_8023(struct ieee80211_sub_if_data *sdata,
struct sk_buff *skb, struct sta_info *sta,
bool txpending) static bool __ieee80211_tx_8023(struct ieee80211_sub_if_data *sdata,
struct sk_buff *skb, struct sta_info *sta,
bool txpending)
{ {
struct ieee80211_local *local = sdata->local; struct ieee80211_local *local = sdata->local;
struct ieee80211_tx_control control = {}; struct ieee80211_tx_control control = {};
...@@ -4445,14 +4504,6 @@ static bool ieee80211_tx_8023(struct ieee80211_sub_if_data *sdata, ...@@ -4445,14 +4504,6 @@ static bool ieee80211_tx_8023(struct ieee80211_sub_if_data *sdata,
unsigned long flags; unsigned long flags;
int q = info->hw_queue; int q = info->hw_queue;
if (sta)
sk_pacing_shift_update(skb->sk, local->hw.tx_sk_pacing_shift);
ieee80211_tpt_led_trig_tx(local, skb->len);
if (ieee80211_queue_skb(local, sdata, sta, skb))
return true;
spin_lock_irqsave(&local->queue_stop_reason_lock, flags); spin_lock_irqsave(&local->queue_stop_reason_lock, flags);
if (local->queue_stop_reasons[q] || if (local->queue_stop_reasons[q] ||
...@@ -4479,6 +4530,26 @@ static bool ieee80211_tx_8023(struct ieee80211_sub_if_data *sdata, ...@@ -4479,6 +4530,26 @@ static bool ieee80211_tx_8023(struct ieee80211_sub_if_data *sdata,
return true; return true;
} }
static bool ieee80211_tx_8023(struct ieee80211_sub_if_data *sdata,
struct sk_buff *skb, struct sta_info *sta,
bool txpending)
{
struct ieee80211_local *local = sdata->local;
struct sk_buff *next;
bool ret = true;
if (ieee80211_queue_skb(local, sdata, sta, skb))
return true;
skb_list_walk_safe(skb, skb, next) {
skb_mark_not_on_list(skb);
if (!__ieee80211_tx_8023(sdata, skb, sta, txpending))
ret = false;
}
return ret;
}
static void ieee80211_8023_xmit(struct ieee80211_sub_if_data *sdata, static void ieee80211_8023_xmit(struct ieee80211_sub_if_data *sdata,
struct net_device *dev, struct sta_info *sta, struct net_device *dev, struct sta_info *sta,
struct ieee80211_key *key, struct sk_buff *skb) struct ieee80211_key *key, struct sk_buff *skb)
...@@ -4486,9 +4557,13 @@ static void ieee80211_8023_xmit(struct ieee80211_sub_if_data *sdata, ...@@ -4486,9 +4557,13 @@ static void ieee80211_8023_xmit(struct ieee80211_sub_if_data *sdata,
struct ieee80211_tx_info *info; struct ieee80211_tx_info *info;
struct ieee80211_local *local = sdata->local; struct ieee80211_local *local = sdata->local;
struct tid_ampdu_tx *tid_tx; struct tid_ampdu_tx *tid_tx;
struct sk_buff *seg, *next;
unsigned int skbs = 0, len = 0;
u16 queue;
u8 tid; u8 tid;
skb_set_queue_mapping(skb, ieee80211_select_queue(sdata, sta, skb)); queue = ieee80211_select_queue(sdata, sta, skb);
skb_set_queue_mapping(skb, queue);
if (unlikely(test_bit(SCAN_SW_SCANNING, &local->scanning)) && if (unlikely(test_bit(SCAN_SW_SCANNING, &local->scanning)) &&
test_bit(SDATA_STATE_OFFCHANNEL, &sdata->state)) test_bit(SDATA_STATE_OFFCHANNEL, &sdata->state))
...@@ -4498,9 +4573,6 @@ static void ieee80211_8023_xmit(struct ieee80211_sub_if_data *sdata, ...@@ -4498,9 +4573,6 @@ static void ieee80211_8023_xmit(struct ieee80211_sub_if_data *sdata,
if (unlikely(!skb)) if (unlikely(!skb))
return; return;
info = IEEE80211_SKB_CB(skb);
memset(info, 0, sizeof(*info));
ieee80211_aggr_check(sdata, sta, skb); ieee80211_aggr_check(sdata, sta, skb);
tid = skb->priority & IEEE80211_QOS_CTL_TAG1D_MASK; tid = skb->priority & IEEE80211_QOS_CTL_TAG1D_MASK;
...@@ -4514,22 +4586,20 @@ static void ieee80211_8023_xmit(struct ieee80211_sub_if_data *sdata, ...@@ -4514,22 +4586,20 @@ static void ieee80211_8023_xmit(struct ieee80211_sub_if_data *sdata,
return; return;
} }
info->flags |= IEEE80211_TX_CTL_AMPDU;
if (tid_tx->timeout) if (tid_tx->timeout)
tid_tx->last_tx = jiffies; tid_tx->last_tx = jiffies;
} }
if (unlikely(skb->sk && skb = ieee80211_tx_skb_fixup(skb, ieee80211_sdata_netdev_features(sdata));
skb_shinfo(skb)->tx_flags & SKBTX_WIFI_STATUS)) if (!skb)
info->ack_frame_id = ieee80211_store_ack_skb(local, skb, return;
&info->flags, NULL);
info->hw_queue = sdata->vif.hw_queue[skb_get_queue_mapping(skb)];
dev_sw_netstats_tx_add(dev, 1, skb->len); info = IEEE80211_SKB_CB(skb);
memset(info, 0, sizeof(*info));
if (tid_tx)
info->flags |= IEEE80211_TX_CTL_AMPDU;
sta->deflink.tx_stats.bytes[skb_get_queue_mapping(skb)] += skb->len; info->hw_queue = sdata->vif.hw_queue[queue];
sta->deflink.tx_stats.packets[skb_get_queue_mapping(skb)]++;
if (sdata->vif.type == NL80211_IFTYPE_AP_VLAN) if (sdata->vif.type == NL80211_IFTYPE_AP_VLAN)
sdata = container_of(sdata->bss, sdata = container_of(sdata->bss,
...@@ -4541,6 +4611,24 @@ static void ieee80211_8023_xmit(struct ieee80211_sub_if_data *sdata, ...@@ -4541,6 +4611,24 @@ static void ieee80211_8023_xmit(struct ieee80211_sub_if_data *sdata,
if (key) if (key)
info->control.hw_key = &key->conf; info->control.hw_key = &key->conf;
skb_list_walk_safe(skb, seg, next) {
skbs++;
len += seg->len;
if (seg != skb)
memcpy(IEEE80211_SKB_CB(seg), info, sizeof(*info));
}
if (unlikely(skb->sk &&
skb_shinfo(skb)->tx_flags & SKBTX_WIFI_STATUS))
info->ack_frame_id = ieee80211_store_ack_skb(local, skb,
&info->flags, NULL);
dev_sw_netstats_tx_add(dev, skbs, len);
sta->deflink.tx_stats.packets[queue] += skbs;
sta->deflink.tx_stats.bytes[queue] += len;
ieee80211_tpt_led_trig_tx(local, len);
ieee80211_tx_8023(sdata, skb, sta, false); ieee80211_tx_8023(sdata, skb, sta, false);
return; return;
...@@ -4582,6 +4670,7 @@ netdev_tx_t ieee80211_subif_start_xmit_8023(struct sk_buff *skb, ...@@ -4582,6 +4670,7 @@ netdev_tx_t ieee80211_subif_start_xmit_8023(struct sk_buff *skb,
key->conf.cipher == WLAN_CIPHER_SUITE_TKIP)) key->conf.cipher == WLAN_CIPHER_SUITE_TKIP))
goto skip_offload; goto skip_offload;
sk_pacing_shift_update(skb->sk, sdata->local->hw.tx_sk_pacing_shift);
ieee80211_8023_xmit(sdata, dev, sta, key, skb); ieee80211_8023_xmit(sdata, dev, sta, key, skb);
goto out; goto out;
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment