diff mbox series

[V6,1/2] mac80211: add hw 80211 encapsulation offloading support

Message ID 20190618062807.17158-2-john@phrozen.org (mailing list archive)
State New, archived
Headers show
Series mac80211: add 802.11 encapsulation offloading | expand

Commit Message

John Crispin June 18, 2019, 6:28 a.m. UTC
From: Vasanthakumar Thiagarajan <vthiagar@qti.qualcomm.com>

This patch adds a new transmit path for hardware that supports 802.11
encapsulation offloading. In those cases 802.3a frames get passed
directly to the driver allowing to hardware to handle the encapsulation.

Certain features wont work and the patch masks these out.
* monitor interfaces are not supported if any of the vif is in encap mode.
* amsdu/non-linear frames wont work in encap offloading mode.
* TKIP countermeasures cannot be triggered and hence those keys are not
  accepted.

The patch defines a secondary netdev_ops struct that the device is assigned
to the device if 802.11 encap support is available and enabled. The driver
needs to enable the support on a per vif basis if it finds that all
pre-reqs are meet.

Signed-off-by: Vasanthakumar Thiagarajan <vthiagar@qti.qualcomm.com>
Signed-off-by: John Crispin <john@phrozen.org>
---
 include/net/mac80211.h     |  25 +++++
 net/mac80211/cfg.c         |   6 ++
 net/mac80211/debugfs.c     |   1 +
 net/mac80211/ieee80211_i.h |  10 ++
 net/mac80211/iface.c       |  84 +++++++++++++++++
 net/mac80211/key.c         |   7 ++
 net/mac80211/main.c        |  10 +-
 net/mac80211/status.c      |  79 ++++++++++++++++
 net/mac80211/tx.c          | 187 ++++++++++++++++++++++++++++++++++++-
 9 files changed, 401 insertions(+), 8 deletions(-)

Comments

John Crispin June 18, 2019, 10:07 a.m. UTC | #1
Hi,

had a couple mails regarding this series and will change it so that when 
a iface gets brought up it will check if there is already a monitor 
interface and then not enable encap and when we bring up a monitor 
interface, mak eit disable encap on all existing ifaces. That way we do 
not loose the monitor feature. V7 coming shortly.

     John

On 18/06/2019 08:28, John Crispin wrote:
> From: Vasanthakumar Thiagarajan <vthiagar@qti.qualcomm.com>
>
> This patch adds a new transmit path for hardware that supports 802.11
> encapsulation offloading. In those cases 802.3a frames get passed
> directly to the driver allowing to hardware to handle the encapsulation.
>
> Certain features wont work and the patch masks these out.
> * monitor interfaces are not supported if any of the vif is in encap mode.
> * amsdu/non-linear frames wont work in encap offloading mode.
> * TKIP countermeasures cannot be triggered and hence those keys are not
>    accepted.
>
> The patch defines a secondary netdev_ops struct that the device is assigned
> to the device if 802.11 encap support is available and enabled. The driver
> needs to enable the support on a per vif basis if it finds that all
> pre-reqs are meet.
>
> Signed-off-by: Vasanthakumar Thiagarajan <vthiagar@qti.qualcomm.com>
> Signed-off-by: John Crispin <john@phrozen.org>
> ---
>   include/net/mac80211.h     |  25 +++++
>   net/mac80211/cfg.c         |   6 ++
>   net/mac80211/debugfs.c     |   1 +
>   net/mac80211/ieee80211_i.h |  10 ++
>   net/mac80211/iface.c       |  84 +++++++++++++++++
>   net/mac80211/key.c         |   7 ++
>   net/mac80211/main.c        |  10 +-
>   net/mac80211/status.c      |  79 ++++++++++++++++
>   net/mac80211/tx.c          | 187 ++++++++++++++++++++++++++++++++++++-
>   9 files changed, 401 insertions(+), 8 deletions(-)
>
> diff --git a/include/net/mac80211.h b/include/net/mac80211.h
> index 1a93ca593c28..426b4f4d9020 100644
> --- a/include/net/mac80211.h
> +++ b/include/net/mac80211.h
> @@ -827,6 +827,7 @@ enum mac80211_tx_control_flags {
>   	IEEE80211_TX_CTRL_AMSDU			= BIT(3),
>   	IEEE80211_TX_CTRL_FAST_XMIT		= BIT(4),
>   	IEEE80211_TX_CTRL_SKIP_MPATH_LOOKUP	= BIT(5),
> +	IEEE80211_TX_CTRL_HW_80211_ENCAP	= BIT(6),
>   };
>   
>   /*
> @@ -2281,6 +2282,9 @@ struct ieee80211_txq {
>    * @IEEE80211_HW_NO_AMPDU_KEYBORDER_SUPPORT: The card/driver can't handle
>    *	active Tx A-MPDU sessions with Extended Key IDs during rekey.
>    *
> + * @IEEE80211_HW_SUPPORTS_80211_ENCAP: Hardware/driver supports 802.11
> + *	encap for data frames.
> + *
>    * @NUM_IEEE80211_HW_FLAGS: number of hardware flags, used for sizing arrays
>    */
>   enum ieee80211_hw_flags {
> @@ -2334,6 +2338,7 @@ enum ieee80211_hw_flags {
>   	IEEE80211_HW_SUPPORTS_ONLY_HE_MULTI_BSSID,
>   	IEEE80211_HW_EXT_KEY_ID_NATIVE,
>   	IEEE80211_HW_NO_AMPDU_KEYBORDER_SUPPORT,
> +	IEEE80211_HW_SUPPORTS_80211_ENCAP,
>   
>   	/* keep last, obviously */
>   	NUM_IEEE80211_HW_FLAGS
> @@ -4629,6 +4634,25 @@ static inline void ieee80211_tx_status_ni(struct ieee80211_hw *hw,
>   void ieee80211_tx_status_irqsafe(struct ieee80211_hw *hw,
>   				 struct sk_buff *skb);
>   
> +/**
> + * ieee80211_tx_status_8023 - transmit status callback for 802.3 frame format
> + *
> + * Call this function for all transmitted data frames after their transmit
> + * completion. This callback should only be called for data frames which
> + * are are using driver's (or hardware's) offload capability of encap/decap
> + * 802.11 frames.
> + *
> + * This function may not be called in IRQ context. Calls to this function
> + * for a single hardware must be synchronized against each other.
> + *
> + * @hw: the hardware the frame was transmitted by
> + * @vif: the interface for which the frame was transmitted
> + * @skb: the frame that was transmitted, owned by mac80211 after this call
> + */
> +void ieee80211_tx_status_8023(struct ieee80211_hw *hw,
> +			       struct ieee80211_vif *vif,
> +			       struct sk_buff *skb);
> +
>   /**
>    * ieee80211_report_low_ack - report non-responding station
>    *
> @@ -6384,4 +6408,5 @@ void ieee80211_nan_func_match(struct ieee80211_vif *vif,
>   			      struct cfg80211_nan_match_params *match,
>   			      gfp_t gfp);
>   
> +bool ieee80211_set_hw_80211_encap(struct ieee80211_vif *vif, bool enable);
>   #endif /* MAC80211_H */
> diff --git a/net/mac80211/cfg.c b/net/mac80211/cfg.c
> index 8a6a38d74878..fb2588ec90a1 100644
> --- a/net/mac80211/cfg.c
> +++ b/net/mac80211/cfg.c
> @@ -2424,11 +2424,17 @@ static int ieee80211_set_mcast_rate(struct wiphy *wiphy, struct net_device *dev,
>   static int ieee80211_set_wiphy_params(struct wiphy *wiphy, u32 changed)
>   {
>   	struct ieee80211_local *local = wiphy_priv(wiphy);
> +	struct ieee80211_sub_if_data *sdata;
>   	int err;
>   
>   	if (changed & WIPHY_PARAM_FRAG_THRESHOLD) {
>   		ieee80211_check_fast_xmit_all(local);
>   
> +		mutex_lock(&local->iflist_mtx);
> +		list_for_each_entry(sdata, &local->interfaces, list)
> +			ieee80211_set_hw_80211_encap(&sdata->vif, false);
> +		mutex_unlock(&local->iflist_mtx);
> +
>   		err = drv_set_frag_threshold(local, wiphy->frag_threshold);
>   
>   		if (err) {
> diff --git a/net/mac80211/debugfs.c b/net/mac80211/debugfs.c
> index 7c580010836e..69baf5d381ad 100644
> --- a/net/mac80211/debugfs.c
> +++ b/net/mac80211/debugfs.c
> @@ -275,6 +275,7 @@ static const char *hw_flag_names[] = {
>   	FLAG(SUPPORTS_ONLY_HE_MULTI_BSSID),
>   	FLAG(EXT_KEY_ID_NATIVE),
>   	FLAG(NO_AMPDU_KEYBORDER_SUPPORT),
> +	FLAG(SUPPORTS_80211_ENCAP),
>   #undef FLAG
>   };
>   
> diff --git a/net/mac80211/ieee80211_i.h b/net/mac80211/ieee80211_i.h
> index 07da9e49db94..9e8474782a60 100644
> --- a/net/mac80211/ieee80211_i.h
> +++ b/net/mac80211/ieee80211_i.h
> @@ -987,6 +987,8 @@ struct ieee80211_sub_if_data {
>   	} debugfs;
>   #endif
>   
> +	bool hw_80211_encap;
> +
>   	/* must be last, dynamically sized area in this! */
>   	struct ieee80211_vif vif;
>   };
> @@ -1733,6 +1735,7 @@ int ieee80211_if_add(struct ieee80211_local *local, const char *name,
>   		     struct vif_params *params);
>   int ieee80211_if_change_type(struct ieee80211_sub_if_data *sdata,
>   			     enum nl80211_iftype type);
> +bool ieee80211_is_hw_80211_encap(struct ieee80211_local *local);
>   void ieee80211_if_remove(struct ieee80211_sub_if_data *sdata);
>   void ieee80211_remove_interfaces(struct ieee80211_local *local);
>   u32 ieee80211_idle_off(struct ieee80211_local *local);
> @@ -1760,6 +1763,8 @@ netdev_tx_t ieee80211_monitor_start_xmit(struct sk_buff *skb,
>   					 struct net_device *dev);
>   netdev_tx_t ieee80211_subif_start_xmit(struct sk_buff *skb,
>   				       struct net_device *dev);
> +netdev_tx_t ieee80211_subif_start_xmit_8023(struct sk_buff *skb,
> +					    struct net_device *dev);
>   void __ieee80211_subif_start_xmit(struct sk_buff *skb,
>   				  struct net_device *dev,
>   				  u32 info_flags,
> @@ -1940,6 +1945,11 @@ void __ieee80211_tx_skb_tid_band(struct ieee80211_sub_if_data *sdata,
>   				 struct sk_buff *skb, int tid,
>   				 enum nl80211_band band, u32 txdata_flags);
>   
> +/* sta_out needs to be checked for ERR_PTR() before using */
> +int ieee80211_lookup_ra_sta(struct ieee80211_sub_if_data *sdata,
> +			    struct sk_buff *skb,
> +			    struct sta_info **sta_out);
> +
>   static inline void
>   ieee80211_tx_skb_tid_band(struct ieee80211_sub_if_data *sdata,
>   			  struct sk_buff *skb, int tid,
> diff --git a/net/mac80211/iface.c b/net/mac80211/iface.c
> index 410685d38c46..14a7c665afd0 100644
> --- a/net/mac80211/iface.c
> +++ b/net/mac80211/iface.c
> @@ -1176,6 +1176,89 @@ static const struct net_device_ops ieee80211_dataif_ops = {
>   	.ndo_get_stats64	= ieee80211_get_stats64,
>   };
>   
> +static const struct net_device_ops ieee80211_dataif_8023_ops = {
> +	.ndo_open		= ieee80211_open,
> +	.ndo_stop		= ieee80211_stop,
> +	.ndo_uninit		= ieee80211_uninit,
> +	.ndo_start_xmit		= ieee80211_subif_start_xmit_8023,
> +	.ndo_set_rx_mode	= ieee80211_set_multicast_list,
> +	.ndo_set_mac_address	= ieee80211_change_mac,
> +	.ndo_select_queue	= ieee80211_netdev_select_queue,
> +	.ndo_get_stats64	= ieee80211_get_stats64,
> +};
> +
> +bool ieee80211_set_hw_80211_encap(struct ieee80211_vif *vif, bool enable)
> +{
> +	struct ieee80211_sub_if_data *sdata = vif_to_sdata(vif);
> +	struct ieee80211_local *local = sdata->local;
> +	struct ieee80211_key *key;
> +
> +	sdata_assert_lock(sdata);
> +
> +	if (!sdata->dev)
> +		return 0;
> +
> +	if (enable == sdata->hw_80211_encap)
> +		return enable;
> +
> +	if (!ieee80211_hw_check(&local->hw, SUPPORTS_80211_ENCAP))
> +		enable = 0;
> +
> +	switch (vif->type) {
> +	case NL80211_IFTYPE_STATION:
> +		if (sdata->u.mgd.use_4addr)
> +			enable = 0;
> +		break;
> +	case NL80211_IFTYPE_AP_VLAN:
> +		if (sdata->wdev.use_4addr)
> +			enable = 0;
> +		break;
> +	default:
> +		break;
> +	}
> +
> +	if (!ieee80211_hw_check(&local->hw, SUPPORTS_TX_FRAG) &&
> +	    (local->hw.wiphy->frag_threshold != (u32)-1))
> +		enable = 0;
> +
> +	mutex_lock(&sdata->local->key_mtx);
> +	list_for_each_entry(key, &sdata->key_list, list) {
> +		if (key->conf.cipher == WLAN_CIPHER_SUITE_TKIP)
> +			enable = 0;
> +	}
> +	mutex_unlock(&sdata->local->key_mtx);
> +
> +	if (enable) {
> +		sdata->dev->netdev_ops = &ieee80211_dataif_8023_ops;
> +		sdata->hw_80211_encap = true;
> +	} else {
> +		sdata->dev->netdev_ops = &ieee80211_dataif_ops;
> +		sdata->hw_80211_encap = false;
> +	}
> +
> +	return enable;
> +}
> +EXPORT_SYMBOL(ieee80211_set_hw_80211_encap);
> +
> +bool ieee80211_is_hw_80211_encap(struct ieee80211_local *local)
> +{
> +	struct ieee80211_sub_if_data *sdata;
> +	bool offloaded = false;
> +	rcu_read_lock();
> +
> +	mutex_lock(&local->iflist_mtx);
> +	list_for_each_entry_rcu(sdata, &local->interfaces, list) {
> +		if (sdata->hw_80211_encap) {
> +			offloaded = true;
> +			break;
> +		}
> +	}
> +	mutex_unlock(&local->iflist_mtx);
> +	rcu_read_unlock();
> +
> +	return offloaded;
> +}
> +
>   static u16 ieee80211_monitor_select_queue(struct net_device *dev,
>   					  struct sk_buff *skb,
>   					  struct net_device *sb_dev)
> @@ -1408,6 +1491,7 @@ static void ieee80211_setup_sdata(struct ieee80211_sub_if_data *sdata,
>   	sdata->vif.bss_conf.idle = true;
>   
>   	sdata->noack_map = 0;
> +	sdata->hw_80211_encap = false;
>   
>   	/* only monitor/p2p-device differ */
>   	if (sdata->dev) {
> diff --git a/net/mac80211/key.c b/net/mac80211/key.c
> index faadfeea7127..4c9977770a45 100644
> --- a/net/mac80211/key.c
> +++ b/net/mac80211/key.c
> @@ -179,6 +179,10 @@ static int ieee80211_key_enable_hw_accel(struct ieee80211_key *key)
>   		}
>   	}
>   
> +	/* TKIP countermeasures wont work on encap offload mode */
> +	if (key->conf.cipher == WLAN_CIPHER_SUITE_TKIP)
> +		ieee80211_set_hw_80211_encap(&sdata->vif, false);
> +
>   	ret = drv_set_key(key->local, SET_KEY, sdata,
>   			  sta ? &sta->sta : NULL, &key->conf);
>   
> @@ -205,6 +209,9 @@ static int ieee80211_key_enable_hw_accel(struct ieee80211_key *key)
>   			  key->conf.keyidx,
>   			  sta ? sta->sta.addr : bcast_addr, ret);
>   
> +	if (sdata->hw_80211_encap)
> +		return -EINVAL;
> +
>    out_unsupported:
>   	switch (key->conf.cipher) {
>   	case WLAN_CIPHER_SUITE_WEP40:
> diff --git a/net/mac80211/main.c b/net/mac80211/main.c
> index 1f11907dc528..c5096e24d81b 100644
> --- a/net/mac80211/main.c
> +++ b/net/mac80211/main.c
> @@ -1000,9 +1000,13 @@ int ieee80211_register_hw(struct ieee80211_hw *hw)
>   		hw->wiphy->software_iftypes |= BIT(NL80211_IFTYPE_AP_VLAN);
>   	}
>   
> -	/* mac80211 always supports monitor */
> -	hw->wiphy->interface_modes |= BIT(NL80211_IFTYPE_MONITOR);
> -	hw->wiphy->software_iftypes |= BIT(NL80211_IFTYPE_MONITOR);
> +	if (ieee80211_hw_check(hw, SUPPORTS_80211_ENCAP)) {
> +		/* mac80211 always supports monitor unless we do 802.11
> +		 * encapsulation offloading.
> +		 */
> +		hw->wiphy->interface_modes |= BIT(NL80211_IFTYPE_MONITOR);
> +		hw->wiphy->software_iftypes |= BIT(NL80211_IFTYPE_MONITOR);
> +	}
>   
>   	/* mac80211 doesn't support more than one IBSS interface right now */
>   	for (i = 0; i < hw->wiphy->n_iface_combinations; i++) {
> diff --git a/net/mac80211/status.c b/net/mac80211/status.c
> index 5b9952b1caf3..0c8858c01e36 100644
> --- a/net/mac80211/status.c
> +++ b/net/mac80211/status.c
> @@ -1019,6 +1019,85 @@ void ieee80211_tx_rate_update(struct ieee80211_hw *hw,
>   }
>   EXPORT_SYMBOL(ieee80211_tx_rate_update);
>   
> +void ieee80211_tx_status_8023(struct ieee80211_hw *hw,
> +			      struct ieee80211_vif *vif,
> +			      struct sk_buff *skb)
> +{
> +	struct ieee80211_local *local = hw_to_local(hw);
> +	struct ieee80211_sub_if_data *sdata;
> +	struct ieee80211_tx_info *info = IEEE80211_SKB_CB(skb);
> +	struct sta_info *sta;
> +	int retry_count;
> +	int rates_idx;
> +	bool acked;
> +
> +	if (WARN_ON(!ieee80211_hw_check(hw, SUPPORTS_80211_ENCAP)))
> +		goto skip_stats_update;
> +
> +	sdata = vif_to_sdata(vif);
> +
> +	acked = info->flags & IEEE80211_TX_STAT_ACK;
> +	rates_idx = ieee80211_tx_get_rates(hw, info, &retry_count);
> +
> +	rcu_read_lock();
> +
> +	if (ieee80211_lookup_ra_sta(sdata, skb, &sta)) {
> +		rcu_read_unlock();
> +		goto counters_update;
> +	}
> +
> +	if (!sta || IS_ERR(sta)) {
> +		rcu_read_unlock();
> +		goto counters_update;
> +	}
> +
> +	if (!acked)
> +		sta->status_stats.retry_failed++;
> +
> +	if (rates_idx != -1)
> +		sta->tx_stats.last_rate = info->status.rates[rates_idx];
> +
> +	sta->status_stats.retry_count += retry_count;
> +
> +	if (ieee80211_hw_check(hw, REPORTS_TX_ACK_STATUS)) {
> +		if (acked && vif->type == NL80211_IFTYPE_STATION)
> +			ieee80211_sta_reset_conn_monitor(sdata);
> +
> +		sta->status_stats.last_ack = jiffies;
> +		if (info->flags & IEEE80211_TX_STAT_ACK) {
> +			if (sta->status_stats.lost_packets)
> +				sta->status_stats.lost_packets = 0;
> +
> +			if (test_sta_flag(sta, WLAN_STA_TDLS_PEER_AUTH))
> +				sta->status_stats.last_tdls_pkt_time = jiffies;
> +		} else {
> +			ieee80211_lost_packet(sta, info);
> +		}
> +	}
> +
> +	rcu_read_unlock();
> +
> +counters_update:
> +	ieee80211_led_tx(local);
> +
> +	if (!(info->flags & IEEE80211_TX_STAT_ACK) &&
> +	    !(info->flags & IEEE80211_TX_STAT_NOACK_TRANSMITTED))
> +		goto skip_stats_update;
> +
> +	I802_DEBUG_INC(local->dot11TransmittedFrameCount);
> +	if (is_multicast_ether_addr(skb->data))
> +		I802_DEBUG_INC(local->dot11MulticastTransmittedFrameCount);
> +	if (retry_count > 0)
> +		I802_DEBUG_INC(local->dot11RetryCount);
> +	if (retry_count > 1)
> +		I802_DEBUG_INC(local->dot11MultipleRetryCount);
> +
> +skip_stats_update:
> +	ieee80211_report_used_skb(local, skb, false);
> +	dev_kfree_skb(skb);
> +}
> +EXPORT_SYMBOL(ieee80211_tx_status_8023);
> +
>   void ieee80211_report_low_ack(struct ieee80211_sta *pubsta, u32 num_packets)
>   {
>   	struct sta_info *sta = container_of(pubsta, struct sta_info, sta);
> diff --git a/net/mac80211/tx.c b/net/mac80211/tx.c
> index dd220b977025..0d1f525c0b02 100644
> --- a/net/mac80211/tx.c
> +++ b/net/mac80211/tx.c
> @@ -1253,7 +1253,8 @@ static struct txq_info *ieee80211_get_txq(struct ieee80211_local *local,
>   	    (info->control.flags & IEEE80211_TX_CTRL_PS_RESPONSE))
>   		return NULL;
>   
> -	if (unlikely(!ieee80211_is_data_present(hdr->frame_control))) {
> +	if (!(info->control.flags & IEEE80211_TX_CTRL_HW_80211_ENCAP) &&
> +	    unlikely(!ieee80211_is_data_present(hdr->frame_control))) {
>   		if ((!ieee80211_is_mgmt(hdr->frame_control) ||
>   		     ieee80211_is_bufferable_mmpdu(hdr->frame_control) ||
>   		     vif->type == NL80211_IFTYPE_STATION) &&
> @@ -2358,9 +2359,9 @@ static inline bool ieee80211_is_tdls_setup(struct sk_buff *skb)
>   	       skb->data[14] == WLAN_TDLS_SNAP_RFTYPE;
>   }
>   
> -static int ieee80211_lookup_ra_sta(struct ieee80211_sub_if_data *sdata,
> -				   struct sk_buff *skb,
> -				   struct sta_info **sta_out)
> +int ieee80211_lookup_ra_sta(struct ieee80211_sub_if_data *sdata,
> +			    struct sk_buff *skb,
> +			    struct sta_info **sta_out)
>   {
>   	struct sta_info *sta;
>   
> @@ -2870,7 +2871,8 @@ void ieee80211_check_fast_xmit(struct sta_info *sta)
>   	struct ieee80211_chanctx_conf *chanctx_conf;
>   	__le16 fc;
>   
> -	if (!ieee80211_hw_check(&local->hw, SUPPORT_FAST_XMIT))
> +	if (!ieee80211_hw_check(&local->hw, SUPPORT_FAST_XMIT) ||
> +	    sdata->hw_80211_encap)
>   		return;
>   
>   	/* Locking here protects both the pointer itself, and against concurrent
> @@ -3600,6 +3602,9 @@ struct sk_buff *ieee80211_tx_dequeue(struct ieee80211_hw *hw,
>   	else
>   		info->flags &= ~IEEE80211_TX_CTL_AMPDU;
>   
> +	if (info->control.flags & IEEE80211_TX_CTRL_HW_80211_ENCAP)
> +		goto encap_out;
> +
>   	if (info->control.flags & IEEE80211_TX_CTRL_FAST_XMIT) {
>   		struct sta_info *sta = container_of(txq->sta, struct sta_info,
>   						    sta);
> @@ -3659,6 +3664,7 @@ struct sk_buff *ieee80211_tx_dequeue(struct ieee80211_hw *hw,
>   		break;
>   	}
>   
> +encap_out:
>   	IEEE80211_SKB_CB(skb)->control.vif = vif;
>   	return skb;
>   
> @@ -4030,6 +4036,167 @@ netdev_tx_t ieee80211_subif_start_xmit(struct sk_buff *skb,
>   	return NETDEV_TX_OK;
>   }
>   
> +static bool ieee80211_tx_8023(struct ieee80211_sub_if_data *sdata,
> +			      struct sk_buff *skb, int led_len,
> +			      struct sta_info *sta,
> +			      bool txpending)
> +{
> +	struct ieee80211_local *local = sdata->local;
> +	struct ieee80211_tx_control control = {};
> +	struct ieee80211_tx_info *info = IEEE80211_SKB_CB(skb);
> +	struct ieee80211_sta *pubsta = NULL;
> +	unsigned long flags;
> +	int q = info->hw_queue;
> +
> +	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] ||
> +	    (!txpending && !skb_queue_empty(&local->pending[q]))) {
> +		if (txpending)
> +			skb_queue_head(&local->pending[q], skb);
> +		else
> +			skb_queue_tail(&local->pending[q], skb);
> +
> +		spin_unlock_irqrestore(&local->queue_stop_reason_lock, flags);
> +
> +		return false;
> +	}
> +
> +	spin_unlock_irqrestore(&local->queue_stop_reason_lock, flags);
> +
> +	if (sta && sta->uploaded)
> +		pubsta = &sta->sta;
> +
> +	control.sta = pubsta;
> +
> +	drv_tx(local, &control, skb);
> +
> +	return true;
> +}
> +
> +static void ieee80211_8023_xmit(struct ieee80211_sub_if_data *sdata,
> +				struct net_device *dev, struct sta_info *sta,
> +				struct sk_buff *skb)
> +{
> +	struct ieee80211_tx_info *info = IEEE80211_SKB_CB(skb);
> +	struct ethhdr *ehdr = (struct ethhdr *)skb->data;
> +	struct ieee80211_local *local = sdata->local;
> +	bool authorized = false;
> +	bool multicast;
> +	bool tdls_peer;
> +	unsigned char *ra = NULL;
> +
> +	if (IS_ERR(sta) || (sta && !sta->uploaded))
> +		sta = NULL;
> +
> +	if (sdata->vif.type == NL80211_IFTYPE_STATION) {
> +		tdls_peer = test_sta_flag(sta, WLAN_STA_TDLS_PEER);
> +		if (tdls_peer)
> +			ra = skb->data;
> +		else
> +			ra = sdata->u.mgd.bssid;
> +	} else {
> +		ra = ehdr->h_dest;
> +	}
> +
> +	if (!ra)
> +		goto out_free;
> +	multicast = is_multicast_ether_addr(ra);
> +
> +	if (sta)
> +		authorized = test_sta_flag(sta, WLAN_STA_AUTHORIZED);
> +
> +	if (!multicast && !authorized &&
> +	    ((ehdr->h_proto != sdata->control_port_protocol) ||
> +	     !ether_addr_equal(sdata->vif.addr, ehdr->h_source)))
> +		goto out_free;
> +
> +	if (multicast && sdata->vif.type == NL80211_IFTYPE_AP &&
> +	    !atomic_read(&sdata->u.ap.num_mcast_sta))
> +		goto out_free;
> +
> +	if (unlikely(test_bit(SCAN_SW_SCANNING, &local->scanning)) &&
> +	    test_bit(SDATA_STATE_OFFCHANNEL, &sdata->state))
> +		goto out_free;
> +
> +	/* TODO: Handle frames requiring wifi tx status to be notified */
> +
> +	memset(info, 0, sizeof(*info));
> +
> +	if (unlikely(sdata->control_port_protocol == ehdr->h_proto)) {
> +		if (sdata->control_port_no_encrypt)
> +			info->flags |= IEEE80211_TX_INTFL_DONT_ENCRYPT;
> +		info->control.flags |= IEEE80211_TX_CTRL_PORT_CTRL_PROTO;
> +	}
> +
> +	if (multicast)
> +		info->flags |= IEEE80211_TX_CTL_NO_ACK;
> +
> +	info->hw_queue = sdata->vif.hw_queue[skb_get_queue_mapping(skb)];
> +
> +	ieee80211_tx_stats(dev, skb->len);
> +
> +	if (sta) {
> +		sta->tx_stats.bytes[skb_get_queue_mapping(skb)] += skb->len;
> +		sta->tx_stats.packets[skb_get_queue_mapping(skb)]++;
> +	}
> +
> +	if (sdata->vif.type == NL80211_IFTYPE_AP_VLAN)
> +		sdata = container_of(sdata->bss,
> +				     struct ieee80211_sub_if_data, u.ap);
> +
> +	info->control.flags |= IEEE80211_TX_CTRL_HW_80211_ENCAP;
> +	info->control.vif = &sdata->vif;
> +
> +	ieee80211_tx_8023(sdata, skb, skb->len, sta, false);
> +
> +	return;
> +
> +out_free:
> +	kfree_skb(skb);
> +}
> +
> +netdev_tx_t ieee80211_subif_start_xmit_8023(struct sk_buff *skb,
> +					    struct net_device *dev)
> +{
> +	struct ieee80211_sub_if_data *sdata = IEEE80211_DEV_TO_SUB_IF(dev);
> +	struct sta_info *sta;
> +
> +	if (WARN_ON(!sdata->hw_80211_encap)) {
> +		kfree_skb(skb);
> +		return NETDEV_TX_OK;
> +	}
> +
> +	if (unlikely(skb->len < ETH_HLEN)) {
> +		kfree_skb(skb);
> +		return NETDEV_TX_OK;
> +	}
> +
> +	if (WARN_ON(dev->ieee80211_ptr->use_4addr)) {
> +		kfree_skb(skb);
> +		return NETDEV_TX_OK;
> +	}
> +
> +	rcu_read_lock();
> +
> +	if (ieee80211_lookup_ra_sta(sdata, skb, &sta))
> +		goto out_free;
> +
> +	ieee80211_8023_xmit(sdata, dev, sta, skb);
> +
> +	goto out;
> +
> +out_free:
> +	kfree_skb(skb);
> +out:
> +	rcu_read_unlock();
> +
> +	return NETDEV_TX_OK;
> +}
> +
>   struct sk_buff *
>   ieee80211_build_data_template(struct ieee80211_sub_if_data *sdata,
>   			      struct sk_buff *skb, u32 info_flags)
> @@ -4108,6 +4275,16 @@ static bool ieee80211_tx_pending_skb(struct ieee80211_local *local,
>   		}
>   		info->band = chanctx_conf->def.chan->band;
>   		result = ieee80211_tx(sdata, NULL, skb, true, 0);
> +	} else if (info->control.flags & IEEE80211_TX_CTRL_HW_80211_ENCAP) {
> +		if (ieee80211_lookup_ra_sta(sdata, skb, &sta)) {
> +			dev_kfree_skb(skb);
> +			return true;
> +		}
> +
> +		if (IS_ERR(sta) || (sta && !sta->uploaded))
> +			sta = NULL;
> +
> +		result = ieee80211_tx_8023(sdata, skb, skb->len, sta, true);
>   	} else {
>   		struct sk_buff_head skbs;
>
diff mbox series

Patch

diff --git a/include/net/mac80211.h b/include/net/mac80211.h
index 1a93ca593c28..426b4f4d9020 100644
--- a/include/net/mac80211.h
+++ b/include/net/mac80211.h
@@ -827,6 +827,7 @@  enum mac80211_tx_control_flags {
 	IEEE80211_TX_CTRL_AMSDU			= BIT(3),
 	IEEE80211_TX_CTRL_FAST_XMIT		= BIT(4),
 	IEEE80211_TX_CTRL_SKIP_MPATH_LOOKUP	= BIT(5),
+	IEEE80211_TX_CTRL_HW_80211_ENCAP	= BIT(6),
 };
 
 /*
@@ -2281,6 +2282,9 @@  struct ieee80211_txq {
  * @IEEE80211_HW_NO_AMPDU_KEYBORDER_SUPPORT: The card/driver can't handle
  *	active Tx A-MPDU sessions with Extended Key IDs during rekey.
  *
+ * @IEEE80211_HW_SUPPORTS_80211_ENCAP: Hardware/driver supports 802.11
+ *	encap for data frames.
+ *
  * @NUM_IEEE80211_HW_FLAGS: number of hardware flags, used for sizing arrays
  */
 enum ieee80211_hw_flags {
@@ -2334,6 +2338,7 @@  enum ieee80211_hw_flags {
 	IEEE80211_HW_SUPPORTS_ONLY_HE_MULTI_BSSID,
 	IEEE80211_HW_EXT_KEY_ID_NATIVE,
 	IEEE80211_HW_NO_AMPDU_KEYBORDER_SUPPORT,
+	IEEE80211_HW_SUPPORTS_80211_ENCAP,
 
 	/* keep last, obviously */
 	NUM_IEEE80211_HW_FLAGS
@@ -4629,6 +4634,25 @@  static inline void ieee80211_tx_status_ni(struct ieee80211_hw *hw,
 void ieee80211_tx_status_irqsafe(struct ieee80211_hw *hw,
 				 struct sk_buff *skb);
 
+/**
+ * ieee80211_tx_status_8023 - transmit status callback for 802.3 frame format
+ *
+ * Call this function for all transmitted data frames after their transmit
+ * completion. This callback should only be called for data frames which
+ * are are using driver's (or hardware's) offload capability of encap/decap
+ * 802.11 frames.
+ *
+ * This function may not be called in IRQ context. Calls to this function
+ * for a single hardware must be synchronized against each other.
+ *
+ * @hw: the hardware the frame was transmitted by
+ * @vif: the interface for which the frame was transmitted
+ * @skb: the frame that was transmitted, owned by mac80211 after this call
+ */
+void ieee80211_tx_status_8023(struct ieee80211_hw *hw,
+			       struct ieee80211_vif *vif,
+			       struct sk_buff *skb);
+
 /**
  * ieee80211_report_low_ack - report non-responding station
  *
@@ -6384,4 +6408,5 @@  void ieee80211_nan_func_match(struct ieee80211_vif *vif,
 			      struct cfg80211_nan_match_params *match,
 			      gfp_t gfp);
 
+bool ieee80211_set_hw_80211_encap(struct ieee80211_vif *vif, bool enable);
 #endif /* MAC80211_H */
diff --git a/net/mac80211/cfg.c b/net/mac80211/cfg.c
index 8a6a38d74878..fb2588ec90a1 100644
--- a/net/mac80211/cfg.c
+++ b/net/mac80211/cfg.c
@@ -2424,11 +2424,17 @@  static int ieee80211_set_mcast_rate(struct wiphy *wiphy, struct net_device *dev,
 static int ieee80211_set_wiphy_params(struct wiphy *wiphy, u32 changed)
 {
 	struct ieee80211_local *local = wiphy_priv(wiphy);
+	struct ieee80211_sub_if_data *sdata;
 	int err;
 
 	if (changed & WIPHY_PARAM_FRAG_THRESHOLD) {
 		ieee80211_check_fast_xmit_all(local);
 
+		mutex_lock(&local->iflist_mtx);
+		list_for_each_entry(sdata, &local->interfaces, list)
+			ieee80211_set_hw_80211_encap(&sdata->vif, false);
+		mutex_unlock(&local->iflist_mtx);
+
 		err = drv_set_frag_threshold(local, wiphy->frag_threshold);
 
 		if (err) {
diff --git a/net/mac80211/debugfs.c b/net/mac80211/debugfs.c
index 7c580010836e..69baf5d381ad 100644
--- a/net/mac80211/debugfs.c
+++ b/net/mac80211/debugfs.c
@@ -275,6 +275,7 @@  static const char *hw_flag_names[] = {
 	FLAG(SUPPORTS_ONLY_HE_MULTI_BSSID),
 	FLAG(EXT_KEY_ID_NATIVE),
 	FLAG(NO_AMPDU_KEYBORDER_SUPPORT),
+	FLAG(SUPPORTS_80211_ENCAP),
 #undef FLAG
 };
 
diff --git a/net/mac80211/ieee80211_i.h b/net/mac80211/ieee80211_i.h
index 07da9e49db94..9e8474782a60 100644
--- a/net/mac80211/ieee80211_i.h
+++ b/net/mac80211/ieee80211_i.h
@@ -987,6 +987,8 @@  struct ieee80211_sub_if_data {
 	} debugfs;
 #endif
 
+	bool hw_80211_encap;
+
 	/* must be last, dynamically sized area in this! */
 	struct ieee80211_vif vif;
 };
@@ -1733,6 +1735,7 @@  int ieee80211_if_add(struct ieee80211_local *local, const char *name,
 		     struct vif_params *params);
 int ieee80211_if_change_type(struct ieee80211_sub_if_data *sdata,
 			     enum nl80211_iftype type);
+bool ieee80211_is_hw_80211_encap(struct ieee80211_local *local);
 void ieee80211_if_remove(struct ieee80211_sub_if_data *sdata);
 void ieee80211_remove_interfaces(struct ieee80211_local *local);
 u32 ieee80211_idle_off(struct ieee80211_local *local);
@@ -1760,6 +1763,8 @@  netdev_tx_t ieee80211_monitor_start_xmit(struct sk_buff *skb,
 					 struct net_device *dev);
 netdev_tx_t ieee80211_subif_start_xmit(struct sk_buff *skb,
 				       struct net_device *dev);
+netdev_tx_t ieee80211_subif_start_xmit_8023(struct sk_buff *skb,
+					    struct net_device *dev);
 void __ieee80211_subif_start_xmit(struct sk_buff *skb,
 				  struct net_device *dev,
 				  u32 info_flags,
@@ -1940,6 +1945,11 @@  void __ieee80211_tx_skb_tid_band(struct ieee80211_sub_if_data *sdata,
 				 struct sk_buff *skb, int tid,
 				 enum nl80211_band band, u32 txdata_flags);
 
+/* sta_out needs to be checked for ERR_PTR() before using */
+int ieee80211_lookup_ra_sta(struct ieee80211_sub_if_data *sdata,
+			    struct sk_buff *skb,
+			    struct sta_info **sta_out);
+
 static inline void
 ieee80211_tx_skb_tid_band(struct ieee80211_sub_if_data *sdata,
 			  struct sk_buff *skb, int tid,
diff --git a/net/mac80211/iface.c b/net/mac80211/iface.c
index 410685d38c46..14a7c665afd0 100644
--- a/net/mac80211/iface.c
+++ b/net/mac80211/iface.c
@@ -1176,6 +1176,89 @@  static const struct net_device_ops ieee80211_dataif_ops = {
 	.ndo_get_stats64	= ieee80211_get_stats64,
 };
 
+static const struct net_device_ops ieee80211_dataif_8023_ops = {
+	.ndo_open		= ieee80211_open,
+	.ndo_stop		= ieee80211_stop,
+	.ndo_uninit		= ieee80211_uninit,
+	.ndo_start_xmit		= ieee80211_subif_start_xmit_8023,
+	.ndo_set_rx_mode	= ieee80211_set_multicast_list,
+	.ndo_set_mac_address	= ieee80211_change_mac,
+	.ndo_select_queue	= ieee80211_netdev_select_queue,
+	.ndo_get_stats64	= ieee80211_get_stats64,
+};
+
+bool ieee80211_set_hw_80211_encap(struct ieee80211_vif *vif, bool enable)
+{
+	struct ieee80211_sub_if_data *sdata = vif_to_sdata(vif);
+	struct ieee80211_local *local = sdata->local;
+	struct ieee80211_key *key;
+
+	sdata_assert_lock(sdata);
+
+	if (!sdata->dev)
+		return 0;
+
+	if (enable == sdata->hw_80211_encap)
+		return enable;
+
+	if (!ieee80211_hw_check(&local->hw, SUPPORTS_80211_ENCAP))
+		enable = 0;
+
+	switch (vif->type) {
+	case NL80211_IFTYPE_STATION:
+		if (sdata->u.mgd.use_4addr)
+			enable = 0;
+		break;
+	case NL80211_IFTYPE_AP_VLAN:
+		if (sdata->wdev.use_4addr)
+			enable = 0;
+		break;
+	default:
+		break;
+	}
+
+	if (!ieee80211_hw_check(&local->hw, SUPPORTS_TX_FRAG) &&
+	    (local->hw.wiphy->frag_threshold != (u32)-1))
+		enable = 0;
+
+	mutex_lock(&sdata->local->key_mtx);
+	list_for_each_entry(key, &sdata->key_list, list) {
+		if (key->conf.cipher == WLAN_CIPHER_SUITE_TKIP)
+			enable = 0;
+	}
+	mutex_unlock(&sdata->local->key_mtx);
+
+	if (enable) {
+		sdata->dev->netdev_ops = &ieee80211_dataif_8023_ops;
+		sdata->hw_80211_encap = true;
+	} else {
+		sdata->dev->netdev_ops = &ieee80211_dataif_ops;
+		sdata->hw_80211_encap = false;
+	}
+
+	return enable;
+}
+EXPORT_SYMBOL(ieee80211_set_hw_80211_encap);
+
+bool ieee80211_is_hw_80211_encap(struct ieee80211_local *local)
+{
+	struct ieee80211_sub_if_data *sdata;
+	bool offloaded = false;
+	rcu_read_lock();
+
+	mutex_lock(&local->iflist_mtx);
+	list_for_each_entry_rcu(sdata, &local->interfaces, list) {
+		if (sdata->hw_80211_encap) {
+			offloaded = true;
+			break;
+		}
+	}
+	mutex_unlock(&local->iflist_mtx);
+	rcu_read_unlock();
+
+	return offloaded;
+}
+
 static u16 ieee80211_monitor_select_queue(struct net_device *dev,
 					  struct sk_buff *skb,
 					  struct net_device *sb_dev)
@@ -1408,6 +1491,7 @@  static void ieee80211_setup_sdata(struct ieee80211_sub_if_data *sdata,
 	sdata->vif.bss_conf.idle = true;
 
 	sdata->noack_map = 0;
+	sdata->hw_80211_encap = false;
 
 	/* only monitor/p2p-device differ */
 	if (sdata->dev) {
diff --git a/net/mac80211/key.c b/net/mac80211/key.c
index faadfeea7127..4c9977770a45 100644
--- a/net/mac80211/key.c
+++ b/net/mac80211/key.c
@@ -179,6 +179,10 @@  static int ieee80211_key_enable_hw_accel(struct ieee80211_key *key)
 		}
 	}
 
+	/* TKIP countermeasures wont work on encap offload mode */
+	if (key->conf.cipher == WLAN_CIPHER_SUITE_TKIP)
+		ieee80211_set_hw_80211_encap(&sdata->vif, false);
+
 	ret = drv_set_key(key->local, SET_KEY, sdata,
 			  sta ? &sta->sta : NULL, &key->conf);
 
@@ -205,6 +209,9 @@  static int ieee80211_key_enable_hw_accel(struct ieee80211_key *key)
 			  key->conf.keyidx,
 			  sta ? sta->sta.addr : bcast_addr, ret);
 
+	if (sdata->hw_80211_encap)
+		return -EINVAL;
+
  out_unsupported:
 	switch (key->conf.cipher) {
 	case WLAN_CIPHER_SUITE_WEP40:
diff --git a/net/mac80211/main.c b/net/mac80211/main.c
index 1f11907dc528..c5096e24d81b 100644
--- a/net/mac80211/main.c
+++ b/net/mac80211/main.c
@@ -1000,9 +1000,13 @@  int ieee80211_register_hw(struct ieee80211_hw *hw)
 		hw->wiphy->software_iftypes |= BIT(NL80211_IFTYPE_AP_VLAN);
 	}
 
-	/* mac80211 always supports monitor */
-	hw->wiphy->interface_modes |= BIT(NL80211_IFTYPE_MONITOR);
-	hw->wiphy->software_iftypes |= BIT(NL80211_IFTYPE_MONITOR);
+	if (ieee80211_hw_check(hw, SUPPORTS_80211_ENCAP)) {
+		/* mac80211 always supports monitor unless we do 802.11
+		 * encapsulation offloading.
+		 */
+		hw->wiphy->interface_modes |= BIT(NL80211_IFTYPE_MONITOR);
+		hw->wiphy->software_iftypes |= BIT(NL80211_IFTYPE_MONITOR);
+	}
 
 	/* mac80211 doesn't support more than one IBSS interface right now */
 	for (i = 0; i < hw->wiphy->n_iface_combinations; i++) {
diff --git a/net/mac80211/status.c b/net/mac80211/status.c
index 5b9952b1caf3..0c8858c01e36 100644
--- a/net/mac80211/status.c
+++ b/net/mac80211/status.c
@@ -1019,6 +1019,85 @@  void ieee80211_tx_rate_update(struct ieee80211_hw *hw,
 }
 EXPORT_SYMBOL(ieee80211_tx_rate_update);
 
+void ieee80211_tx_status_8023(struct ieee80211_hw *hw,
+			      struct ieee80211_vif *vif,
+			      struct sk_buff *skb)
+{
+	struct ieee80211_local *local = hw_to_local(hw);
+	struct ieee80211_sub_if_data *sdata;
+	struct ieee80211_tx_info *info = IEEE80211_SKB_CB(skb);
+	struct sta_info *sta;
+	int retry_count;
+	int rates_idx;
+	bool acked;
+
+	if (WARN_ON(!ieee80211_hw_check(hw, SUPPORTS_80211_ENCAP)))
+		goto skip_stats_update;
+
+	sdata = vif_to_sdata(vif);
+
+	acked = info->flags & IEEE80211_TX_STAT_ACK;
+	rates_idx = ieee80211_tx_get_rates(hw, info, &retry_count);
+
+	rcu_read_lock();
+
+	if (ieee80211_lookup_ra_sta(sdata, skb, &sta)) {
+		rcu_read_unlock();
+		goto counters_update;
+	}
+
+	if (!sta || IS_ERR(sta)) {
+		rcu_read_unlock();
+		goto counters_update;
+	}
+
+	if (!acked)
+		sta->status_stats.retry_failed++;
+
+	if (rates_idx != -1)
+		sta->tx_stats.last_rate = info->status.rates[rates_idx];
+
+	sta->status_stats.retry_count += retry_count;
+
+	if (ieee80211_hw_check(hw, REPORTS_TX_ACK_STATUS)) {
+		if (acked && vif->type == NL80211_IFTYPE_STATION)
+			ieee80211_sta_reset_conn_monitor(sdata);
+
+		sta->status_stats.last_ack = jiffies;
+		if (info->flags & IEEE80211_TX_STAT_ACK) {
+			if (sta->status_stats.lost_packets)
+				sta->status_stats.lost_packets = 0;
+
+			if (test_sta_flag(sta, WLAN_STA_TDLS_PEER_AUTH))
+				sta->status_stats.last_tdls_pkt_time = jiffies;
+		} else {
+			ieee80211_lost_packet(sta, info);
+		}
+	}
+
+	rcu_read_unlock();
+
+counters_update:
+	ieee80211_led_tx(local);
+
+	if (!(info->flags & IEEE80211_TX_STAT_ACK) &&
+	    !(info->flags & IEEE80211_TX_STAT_NOACK_TRANSMITTED))
+		goto skip_stats_update;
+
+	I802_DEBUG_INC(local->dot11TransmittedFrameCount);
+	if (is_multicast_ether_addr(skb->data))
+		I802_DEBUG_INC(local->dot11MulticastTransmittedFrameCount);
+	if (retry_count > 0)
+		I802_DEBUG_INC(local->dot11RetryCount);
+	if (retry_count > 1)
+		I802_DEBUG_INC(local->dot11MultipleRetryCount);
+
+skip_stats_update:
+	ieee80211_report_used_skb(local, skb, false);
+	dev_kfree_skb(skb);
+}
+EXPORT_SYMBOL(ieee80211_tx_status_8023);
+
 void ieee80211_report_low_ack(struct ieee80211_sta *pubsta, u32 num_packets)
 {
 	struct sta_info *sta = container_of(pubsta, struct sta_info, sta);
diff --git a/net/mac80211/tx.c b/net/mac80211/tx.c
index dd220b977025..0d1f525c0b02 100644
--- a/net/mac80211/tx.c
+++ b/net/mac80211/tx.c
@@ -1253,7 +1253,8 @@  static struct txq_info *ieee80211_get_txq(struct ieee80211_local *local,
 	    (info->control.flags & IEEE80211_TX_CTRL_PS_RESPONSE))
 		return NULL;
 
-	if (unlikely(!ieee80211_is_data_present(hdr->frame_control))) {
+	if (!(info->control.flags & IEEE80211_TX_CTRL_HW_80211_ENCAP) &&
+	    unlikely(!ieee80211_is_data_present(hdr->frame_control))) {
 		if ((!ieee80211_is_mgmt(hdr->frame_control) ||
 		     ieee80211_is_bufferable_mmpdu(hdr->frame_control) ||
 		     vif->type == NL80211_IFTYPE_STATION) &&
@@ -2358,9 +2359,9 @@  static inline bool ieee80211_is_tdls_setup(struct sk_buff *skb)
 	       skb->data[14] == WLAN_TDLS_SNAP_RFTYPE;
 }
 
-static int ieee80211_lookup_ra_sta(struct ieee80211_sub_if_data *sdata,
-				   struct sk_buff *skb,
-				   struct sta_info **sta_out)
+int ieee80211_lookup_ra_sta(struct ieee80211_sub_if_data *sdata,
+			    struct sk_buff *skb,
+			    struct sta_info **sta_out)
 {
 	struct sta_info *sta;
 
@@ -2870,7 +2871,8 @@  void ieee80211_check_fast_xmit(struct sta_info *sta)
 	struct ieee80211_chanctx_conf *chanctx_conf;
 	__le16 fc;
 
-	if (!ieee80211_hw_check(&local->hw, SUPPORT_FAST_XMIT))
+	if (!ieee80211_hw_check(&local->hw, SUPPORT_FAST_XMIT) ||
+	    sdata->hw_80211_encap)
 		return;
 
 	/* Locking here protects both the pointer itself, and against concurrent
@@ -3600,6 +3602,9 @@  struct sk_buff *ieee80211_tx_dequeue(struct ieee80211_hw *hw,
 	else
 		info->flags &= ~IEEE80211_TX_CTL_AMPDU;
 
+	if (info->control.flags & IEEE80211_TX_CTRL_HW_80211_ENCAP)
+		goto encap_out;
+
 	if (info->control.flags & IEEE80211_TX_CTRL_FAST_XMIT) {
 		struct sta_info *sta = container_of(txq->sta, struct sta_info,
 						    sta);
@@ -3659,6 +3664,7 @@  struct sk_buff *ieee80211_tx_dequeue(struct ieee80211_hw *hw,
 		break;
 	}
 
+encap_out:
 	IEEE80211_SKB_CB(skb)->control.vif = vif;
 	return skb;
 
@@ -4030,6 +4036,167 @@  netdev_tx_t ieee80211_subif_start_xmit(struct sk_buff *skb,
 	return NETDEV_TX_OK;
 }
 
+static bool ieee80211_tx_8023(struct ieee80211_sub_if_data *sdata,
+			      struct sk_buff *skb, int led_len,
+			      struct sta_info *sta,
+			      bool txpending)
+{
+	struct ieee80211_local *local = sdata->local;
+	struct ieee80211_tx_control control = {};
+	struct ieee80211_tx_info *info = IEEE80211_SKB_CB(skb);
+	struct ieee80211_sta *pubsta = NULL;
+	unsigned long flags;
+	int q = info->hw_queue;
+
+	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] ||
+	    (!txpending && !skb_queue_empty(&local->pending[q]))) {
+		if (txpending)
+			skb_queue_head(&local->pending[q], skb);
+		else
+			skb_queue_tail(&local->pending[q], skb);
+
+		spin_unlock_irqrestore(&local->queue_stop_reason_lock, flags);
+
+		return false;
+	}
+
+	spin_unlock_irqrestore(&local->queue_stop_reason_lock, flags);
+
+	if (sta && sta->uploaded)
+		pubsta = &sta->sta;
+
+	control.sta = pubsta;
+
+	drv_tx(local, &control, skb);
+
+	return true;
+}
+
+static void ieee80211_8023_xmit(struct ieee80211_sub_if_data *sdata,
+				struct net_device *dev, struct sta_info *sta,
+				struct sk_buff *skb)
+{
+	struct ieee80211_tx_info *info = IEEE80211_SKB_CB(skb);
+	struct ethhdr *ehdr = (struct ethhdr *)skb->data;
+	struct ieee80211_local *local = sdata->local;
+	bool authorized = false;
+	bool multicast;
+	bool tdls_peer;
+	unsigned char *ra = NULL;
+
+	if (IS_ERR(sta) || (sta && !sta->uploaded))
+		sta = NULL;
+
+	if (sdata->vif.type == NL80211_IFTYPE_STATION) {
+		tdls_peer = test_sta_flag(sta, WLAN_STA_TDLS_PEER);
+		if (tdls_peer)
+			ra = skb->data;
+		else
+			ra = sdata->u.mgd.bssid;
+	} else {
+		ra = ehdr->h_dest;
+	}
+
+	if (!ra)
+		goto out_free;
+	multicast = is_multicast_ether_addr(ra);
+
+	if (sta)
+		authorized = test_sta_flag(sta, WLAN_STA_AUTHORIZED);
+
+	if (!multicast && !authorized &&
+	    ((ehdr->h_proto != sdata->control_port_protocol) ||
+	     !ether_addr_equal(sdata->vif.addr, ehdr->h_source)))
+		goto out_free;
+
+	if (multicast && sdata->vif.type == NL80211_IFTYPE_AP &&
+	    !atomic_read(&sdata->u.ap.num_mcast_sta))
+		goto out_free;
+
+	if (unlikely(test_bit(SCAN_SW_SCANNING, &local->scanning)) &&
+	    test_bit(SDATA_STATE_OFFCHANNEL, &sdata->state))
+		goto out_free;
+
+	/* TODO: Handle frames requiring wifi tx status to be notified */
+
+	memset(info, 0, sizeof(*info));
+
+	if (unlikely(sdata->control_port_protocol == ehdr->h_proto)) {
+		if (sdata->control_port_no_encrypt)
+			info->flags |= IEEE80211_TX_INTFL_DONT_ENCRYPT;
+		info->control.flags |= IEEE80211_TX_CTRL_PORT_CTRL_PROTO;
+	}
+
+	if (multicast)
+		info->flags |= IEEE80211_TX_CTL_NO_ACK;
+
+	info->hw_queue = sdata->vif.hw_queue[skb_get_queue_mapping(skb)];
+
+	ieee80211_tx_stats(dev, skb->len);
+
+	if (sta) {
+		sta->tx_stats.bytes[skb_get_queue_mapping(skb)] += skb->len;
+		sta->tx_stats.packets[skb_get_queue_mapping(skb)]++;
+	}
+
+	if (sdata->vif.type == NL80211_IFTYPE_AP_VLAN)
+		sdata = container_of(sdata->bss,
+				     struct ieee80211_sub_if_data, u.ap);
+
+	info->control.flags |= IEEE80211_TX_CTRL_HW_80211_ENCAP;
+	info->control.vif = &sdata->vif;
+
+	ieee80211_tx_8023(sdata, skb, skb->len, sta, false);
+
+	return;
+
+out_free:
+	kfree_skb(skb);
+}
+
+netdev_tx_t ieee80211_subif_start_xmit_8023(struct sk_buff *skb,
+					    struct net_device *dev)
+{
+	struct ieee80211_sub_if_data *sdata = IEEE80211_DEV_TO_SUB_IF(dev);
+	struct sta_info *sta;
+
+	if (WARN_ON(!sdata->hw_80211_encap)) {
+		kfree_skb(skb);
+		return NETDEV_TX_OK;
+	}
+
+	if (unlikely(skb->len < ETH_HLEN)) {
+		kfree_skb(skb);
+		return NETDEV_TX_OK;
+	}
+
+	if (WARN_ON(dev->ieee80211_ptr->use_4addr)) {
+		kfree_skb(skb);
+		return NETDEV_TX_OK;
+	}
+
+	rcu_read_lock();
+
+	if (ieee80211_lookup_ra_sta(sdata, skb, &sta))
+		goto out_free;
+
+	ieee80211_8023_xmit(sdata, dev, sta, skb);
+
+	goto out;
+
+out_free:
+	kfree_skb(skb);
+out:
+	rcu_read_unlock();
+
+	return NETDEV_TX_OK;
+}
+
 struct sk_buff *
 ieee80211_build_data_template(struct ieee80211_sub_if_data *sdata,
 			      struct sk_buff *skb, u32 info_flags)
@@ -4108,6 +4275,16 @@  static bool ieee80211_tx_pending_skb(struct ieee80211_local *local,
 		}
 		info->band = chanctx_conf->def.chan->band;
 		result = ieee80211_tx(sdata, NULL, skb, true, 0);
+	} else if (info->control.flags & IEEE80211_TX_CTRL_HW_80211_ENCAP) {
+		if (ieee80211_lookup_ra_sta(sdata, skb, &sta)) {
+			dev_kfree_skb(skb);
+			return true;
+		}
+
+		if (IS_ERR(sta) || (sta && !sta->uploaded))
+			sta = NULL;
+
+		result = ieee80211_tx_8023(sdata, skb, skb->len, sta, true);
 	} else {
 		struct sk_buff_head skbs;