--- /dev/null
+From: Felix Fietkau <nbd@nbd.name>
+Date: Sun, 9 Oct 2022 20:15:46 +0200
+Subject: [PATCH] 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: Felix Fietkau <nbd@nbd.name>
+---
+
+--- a/include/net/fq_impl.h
++++ b/include/net/fq_impl.h
+@@ -200,6 +200,7 @@ static void fq_tin_enqueue(struct fq *fq
+ fq_skb_free_t free_func)
+ {
+ struct fq_flow *flow;
++ struct sk_buff *next;
+ bool oom;
+
+ lockdep_assert_held(&fq->lock);
+@@ -214,11 +215,15 @@ static void fq_tin_enqueue(struct fq *fq
+ }
+
+ flow->tin = tin;
+- flow->backlog += skb->len;
+- tin->backlog_bytes += skb->len;
+- tin->backlog_packets++;
+- fq->memory_usage += skb->truesize;
+- fq->backlog++;
++ skb_list_walk_safe(skb, skb, next) {
++ skb_mark_not_on_list(skb);
++ flow->backlog += skb->len;
++ tin->backlog_bytes += skb->len;
++ tin->backlog_packets++;
++ fq->memory_usage += skb->truesize;
++ fq->backlog++;
++ __skb_queue_tail(&flow->queue, skb);
++ }
+
+ if (list_empty(&flow->flowchain)) {
+ flow->deficit = fq->quantum;
+@@ -226,7 +231,6 @@ static void fq_tin_enqueue(struct fq *fq
+ &tin->new_flows);
+ }
+
+- __skb_queue_tail(&flow->queue, skb);
+ oom = (fq->memory_usage > fq->memory_limit);
+ while (fq->backlog > fq->limit || oom) {
+ flow = fq_find_fattest_flow(fq);
+--- a/include/net/mac80211.h
++++ b/include/net/mac80211.h
+@@ -1685,6 +1685,10 @@ enum ieee80211_offload_flags {
+ * write-protected by sdata_lock and local->mtx so holding either is fine
+ * for read access.
+ * @mu_mimo_owner: indicates interface owns MU-MIMO capability
++ * @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,
+ * these need to be set (or cleared) when the interface is added
+ * or, if supported by the driver, the interface type is changed
+@@ -1736,6 +1740,7 @@ struct ieee80211_vif {
+
+ struct ieee80211_chanctx_conf __rcu *chanctx_conf;
+
++ netdev_features_t netdev_features;
+ u32 driver_flags;
+ u32 offload_flags;
+
+--- a/net/mac80211/iface.c
++++ b/net/mac80211/iface.c
+@@ -2209,6 +2209,7 @@ int ieee80211_if_add(struct ieee80211_lo
+ ndev->features |= local->hw.netdev_features;
+ ndev->hw_features |= ndev->features &
+ MAC80211_SUPPORTED_FEATURES_TX;
++ sdata->vif.netdev_features = local->hw.netdev_features;
+
+ netdev_set_default_ethtool_ops(ndev, &ieee80211_ethtool_ops);
+
+--- a/net/mac80211/tx.c
++++ b/net/mac80211/tx.c
+@@ -1310,7 +1310,11 @@ static struct txq_info *ieee80211_get_tx
+
+ 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)
+@@ -3499,47 +3503,71 @@ ieee80211_xmit_fast_finish(struct ieee80
+ return TX_CONTINUE;
+ }
+
+-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)
++static netdev_features_t
++ieee80211_sdata_netdev_features(struct ieee80211_sub_if_data *sdata)
+ {
+- struct ieee80211_local *local = sdata->local;
+- u16 ethertype = (skb->data[12] << 8) | skb->data[13];
+- 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;
++ if (sdata->vif.type != NL80211_IFTYPE_AP_VLAN)
++ return sdata->vif.netdev_features;
+
+- /* control port protocol needs a lot of special handling */
+- if (cpu_to_be16(ethertype) == sdata->control_port_protocol)
+- return false;
++ if (!sdata->bss)
++ return 0;
+
+- /* only RFC 1042 SNAP */
+- if (ethertype < ETH_P_802_3_MIN)
+- return false;
++ sdata = container_of(sdata->bss, struct ieee80211_sub_if_data, u.ap);
++ return sdata->vif.netdev_features;
++}
+
+- /* don't handle TX status request here either */
+- if (skb->sk && skb_shinfo(skb)->tx_flags & SKBTX_WIFI_STATUS)
+- return false;
++static struct sk_buff *
++ieee80211_tx_skb_fixup(struct sk_buff *skb, netdev_features_t features)
++{
++ if (skb_is_gso(skb)) {
++ struct sk_buff *segs;
+
+- 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;
+- }
++ segs = skb_gso_segment(skb, features);
++ if (!segs)
++ return skb;
++ if (IS_ERR(segs))
++ goto free;
++
++ consume_skb(skb);
++ return segs;
++ }
++
++ 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;
+ }
+
+- /* after this point (skb is modified) we cannot return false */
++ 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;
+
+ if (skb_shared(skb)) {
+ struct sk_buff *tmp_skb = skb;
+@@ -3548,12 +3576,12 @@ static bool ieee80211_xmit_fast(struct i
+ kfree_skb(tmp_skb);
+
+ if (!skb)
+- return true;
++ return;
+ }
+
+ if ((hdr->frame_control & cpu_to_le16(IEEE80211_STYPE_QOS_DATA)) &&
+ ieee80211_amsdu_aggregate(sdata, sta, fast_tx, skb))
+- return true;
++ return;
+
+ /* 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
+@@ -3562,10 +3590,8 @@ static bool ieee80211_xmit_fast(struct i
+ if (unlikely(ieee80211_skb_resize(sdata, skb,
+ max_t(int, extra_head + hw_headroom -
+ skb_headroom(skb), 0),
+- ENCRYPT_NO))) {
+- kfree_skb(skb);
+- return true;
+- }
++ ENCRYPT_NO)))
++ goto free;
+
+ memcpy(ð, skb->data, ETH_HLEN - 2);
+ hdr = skb_push(skb, extra_head);
+@@ -3579,7 +3605,7 @@ static bool ieee80211_xmit_fast(struct i
+ info->control.vif = &sdata->vif;
+ info->flags = IEEE80211_TX_CTL_FIRST_FRAGMENT |
+ 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;
+
+ #ifdef CPTCFG_MAC80211_DEBUGFS
+@@ -3601,16 +3627,14 @@ static bool ieee80211_xmit_fast(struct i
+ tx.key = fast_tx->key;
+
+ if (ieee80211_queue_skb(local, sdata, sta, skb))
+- return true;
++ return;
+
+ tx.skb = skb;
+ r = ieee80211_xmit_fast_finish(sdata, sta, fast_tx->pn_offs,
+ fast_tx->key, &tx);
+ tx.skb = NULL;
+- if (r == TX_DROP) {
+- kfree_skb(skb);
+- return true;
+- }
++ if (r == TX_DROP)
++ goto free;
+
+ if (sdata->vif.type == NL80211_IFTYPE_AP_VLAN)
+ sdata = container_of(sdata->bss,
+@@ -3618,6 +3642,55 @@ static bool ieee80211_xmit_fast(struct i
+
+ __skb_queue_tail(&tx.skbs, skb);
+ 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;
++ }
++ }
++
++ 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;
+ }
+
+@@ -4123,31 +4196,14 @@ void __ieee80211_subif_start_xmit(struct
+ goto out;
+ }
+
+- if (skb_is_gso(skb)) {
+- struct sk_buff *segs;
+-
+- segs = skb_gso_segment(skb, 0);
+- if (IS_ERR(segs)) {
+- goto out_free;
+- } else if (segs) {
+- consume_skb(skb);
+- 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;
+- }
++ /* the frame could be fragmented, software-encrypted, and other
++ * things so we cannot really handle checksum or GSO offload.
++ * fix it up in software before we handle anything else.
++ */
++ skb = ieee80211_tx_skb_fixup(skb, 0);
++ if (!skb) {
++ len = 0;
++ goto out;
+ }
+
+ skb_list_walk_safe(skb, skb, next) {
+@@ -4310,9 +4366,11 @@ netdev_tx_t ieee80211_subif_start_xmit(s
+ 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_tx_control control = {};
+@@ -4321,14 +4379,6 @@ static bool ieee80211_tx_8023(struct iee
+ unsigned long flags;
+ 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);
+
+ if (local->queue_stop_reasons[q] ||
+@@ -4355,27 +4405,50 @@ static bool ieee80211_tx_8023(struct iee
+ 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,
+ struct net_device *dev, struct sta_info *sta,
+ struct ieee80211_key *key, struct sk_buff *skb)
+ {
+- struct ieee80211_tx_info *info = IEEE80211_SKB_CB(skb);
++ struct ieee80211_tx_info *info;
+ struct ieee80211_local *local = sdata->local;
+ struct tid_ampdu_tx *tid_tx;
++ struct sk_buff *seg, *next;
++ unsigned int skbs = 0, len = 0;
++ u16 queue;
+ u8 tid;
+
+ if (local->ops->wake_tx_queue) {
+- u16 queue = __ieee80211_select_queue(sdata, sta, skb);
++ queue = __ieee80211_select_queue(sdata, sta, skb);
+ skb_set_queue_mapping(skb, queue);
+ skb_get_hash(skb);
++ } else {
++ queue = skb_get_queue_mapping(skb);
+ }
+
+ if (unlikely(test_bit(SCAN_SW_SCANNING, &local->scanning)) &&
+ test_bit(SDATA_STATE_OFFCHANNEL, &sdata->state))
+ goto out_free;
+
+- memset(info, 0, sizeof(*info));
+-
+ ieee80211_aggr_check(sdata, sta, skb);
+
+ tid = skb->priority & IEEE80211_QOS_CTL_TAG1D_MASK;
+@@ -4387,22 +4460,20 @@ static void ieee80211_8023_xmit(struct i
+ return;
+ }
+
+- info->flags |= IEEE80211_TX_CTL_AMPDU;
+ if (tid_tx->timeout)
+ tid_tx->last_tx = jiffies;
+ }
+
+- 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);
++ skb = ieee80211_tx_skb_fixup(skb, ieee80211_sdata_netdev_features(sdata));
++ if (!skb)
++ return;
+
+- info->hw_queue = sdata->vif.hw_queue[skb_get_queue_mapping(skb)];
++ info = IEEE80211_SKB_CB(skb);
++ memset(info, 0, sizeof(*info));
++ if (tid_tx)
++ info->flags |= IEEE80211_TX_CTL_AMPDU;
+
+- dev_sw_netstats_tx_add(dev, 1, skb->len);
+-
+- sta->tx_stats.bytes[skb_get_queue_mapping(skb)] += skb->len;
+- sta->tx_stats.packets[skb_get_queue_mapping(skb)]++;
++ info->hw_queue = sdata->vif.hw_queue[queue];
+
+ if (sdata->vif.type == NL80211_IFTYPE_AP_VLAN)
+ sdata = container_of(sdata->bss,
+@@ -4414,6 +4485,24 @@ static void ieee80211_8023_xmit(struct i
+ if (key)
+ 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->tx_stats.packets[queue] += skbs;
++ sta->tx_stats.bytes[queue] += len;
++
++ ieee80211_tpt_led_trig_tx(local, len);
++
+ ieee80211_tx_8023(sdata, skb, sta, false);
+
+ return;
+@@ -4455,6 +4544,7 @@ netdev_tx_t ieee80211_subif_start_xmit_8
+ key->conf.cipher == WLAN_CIPHER_SUITE_TKIP))
+ goto skip_offload;
+
++ sk_pacing_shift_update(skb->sk, sdata->local->hw.tx_sk_pacing_shift);
+ ieee80211_8023_xmit(sdata, dev, sta, key, skb);
+ goto out;
+