--- /dev/null
+From: Felix Fietkau <nbd@nbd.name>
+Date: Sun, 17 Mar 2019 18:11:30 +0100
+Subject: [PATCH] mac80211: optimize skb resizing
+
+When forwarding unicast packets from ethernet to batman-adv over 802.11s
+(with forwarding disabled), the typical required headroom to transmit
+encrypted packets on mt76 is 32 (802.11) + 6 (802.11s) + 8 (CCMP) +
+2 (padding) + 6 (LLC) + 18 (batman-adv) - 14 (old ethernet header) = 58 bytes.
+
+On systems where NET_SKB_PAD is 64 this leads to a call to pskb_expand_head
+for every packet, since mac80211 also tries to allocate 16 bytes status
+headroom for radiotap headers.
+
+This patch fixes these unnecessary reallocations by only requiring the extra
+status headroom in ieee80211_tx_monitor()
+If however a reallocation happens before that call, the status headroom gets
+added there as well, in order to avoid double reallocation.
+
+The patch also cleans up the code by moving the headroom calculation to
+ieee80211_skb_resize.
+
+Signed-off-by: Felix Fietkau <nbd@nbd.name>
+---
+
+--- a/net/mac80211/ieee80211_i.h
++++ b/net/mac80211/ieee80211_i.h
+@@ -1779,6 +1779,9 @@ int ieee80211_tx_control_port(struct wip
+ const u8 *dest, __be16 proto, bool unencrypted);
+ int ieee80211_probe_mesh_link(struct wiphy *wiphy, struct net_device *dev,
+ const u8 *buf, size_t len);
++int ieee80211_skb_resize(struct ieee80211_local *local,
++ struct ieee80211_sub_if_data *sdata,
++ struct sk_buff *skb, int hdrlen, int hdr_add);
+
+ /* HT */
+ void ieee80211_apply_htcap_overrides(struct ieee80211_sub_if_data *sdata,
+--- a/net/mac80211/status.c
++++ b/net/mac80211/status.c
+@@ -655,6 +655,11 @@ void ieee80211_tx_monitor(struct ieee802
+ struct net_device *prev_dev = NULL;
+ int rtap_len;
+
++ if (ieee80211_skb_resize(local, NULL, skb, 0, 0)) {
++ dev_kfree_skb(skb);
++ return;
++ }
++
+ /* send frame to monitor interfaces now */
+ rtap_len = ieee80211_tx_radiotap_len(info);
+ if (WARN_ON_ONCE(skb_headroom(skb) < rtap_len)) {
+--- a/net/mac80211/tx.c
++++ b/net/mac80211/tx.c
+@@ -1936,37 +1936,53 @@ static bool ieee80211_tx(struct ieee8021
+ }
+
+ /* device xmit handlers */
+-
+-static int ieee80211_skb_resize(struct ieee80211_sub_if_data *sdata,
+- struct sk_buff *skb,
+- int head_need, bool may_encrypt)
++int ieee80211_skb_resize(struct ieee80211_local *local,
++ struct ieee80211_sub_if_data *sdata,
++ struct sk_buff *skb, int hdr_len, int hdr_extra)
+ {
+- struct ieee80211_local *local = sdata->local;
++ struct ieee80211_tx_info *info = IEEE80211_SKB_CB(skb);
+ struct ieee80211_hdr *hdr;
+- bool enc_tailroom;
+- int tail_need = 0;
++ int head_need, head_max;
++ int tail_need, tail_max;
++ bool enc_tailroom = false;
+
+- hdr = (struct ieee80211_hdr *) skb->data;
+- enc_tailroom = may_encrypt &&
+- (sdata->crypto_tx_tailroom_needed_cnt ||
+- ieee80211_is_mgmt(hdr->frame_control));
++ if (sdata && !hdr_len &&
++ !(info->flags & IEEE80211_TX_INTFL_DONT_ENCRYPT)) {
++ hdr = (struct ieee80211_hdr *) skb->data;
++ enc_tailroom = (sdata->crypto_tx_tailroom_needed_cnt ||
++ ieee80211_is_mgmt(hdr->frame_control));
++ hdr_len += sdata->encrypt_headroom;
++ }
+
+- if (enc_tailroom) {
+- tail_need = IEEE80211_ENCRYPT_TAILROOM;
+- tail_need -= skb_tailroom(skb);
+- tail_need = max_t(int, tail_need, 0);
++ head_need = head_max = hdr_len;
++ tail_need = tail_max = 0;
++ if (!sdata) {
++ head_need = head_max = local->tx_headroom;
++ } else {
++ head_max += hdr_extra;
++ head_max += max_t(int, local->tx_headroom,
++ local->hw.extra_tx_headroom);
++ head_need += local->hw.extra_tx_headroom;
++
++ tail_max = IEEE80211_ENCRYPT_TAILROOM;
++ if (enc_tailroom)
++ tail_need = tail_max;
+ }
+
+ if (skb_cloned(skb) &&
+ (!ieee80211_hw_check(&local->hw, SUPPORTS_CLONED_SKBS) ||
+ !skb_clone_writable(skb, ETH_HLEN) || enc_tailroom))
+ I802_DEBUG_INC(local->tx_expand_skb_head_cloned);
+- else if (head_need || tail_need)
++ else if (head_need > skb_headroom(skb) ||
++ tail_need > skb_tailroom(skb))
+ I802_DEBUG_INC(local->tx_expand_skb_head);
+ else
+ return 0;
+
+- if (pskb_expand_head(skb, head_need, tail_need, GFP_ATOMIC)) {
++ head_max = max_t(int, 0, head_max - skb_headroom(skb));
++ tail_max = max_t(int, 0, tail_max - skb_tailroom(skb));
++
++ if (pskb_expand_head(skb, head_max, tail_max, GFP_ATOMIC)) {
+ wiphy_debug(local->hw.wiphy,
+ "failed to reallocate TX buffer\n");
+ return -ENOMEM;
+@@ -1982,18 +1998,8 @@ void ieee80211_xmit(struct ieee80211_sub
+ struct ieee80211_local *local = sdata->local;
+ struct ieee80211_tx_info *info = IEEE80211_SKB_CB(skb);
+ struct ieee80211_hdr *hdr;
+- int headroom;
+- bool may_encrypt;
+-
+- may_encrypt = !(info->flags & IEEE80211_TX_INTFL_DONT_ENCRYPT);
+-
+- headroom = local->tx_headroom;
+- if (may_encrypt)
+- headroom += sdata->encrypt_headroom;
+- headroom -= skb_headroom(skb);
+- headroom = max_t(int, 0, headroom);
+
+- if (ieee80211_skb_resize(sdata, skb, headroom, may_encrypt)) {
++ if (ieee80211_skb_resize(local, sdata, skb, 0, 0)) {
+ ieee80211_free_txskb(&local->hw, skb);
+ return;
+ }
+@@ -2774,29 +2780,13 @@ static struct sk_buff *ieee80211_build_h
+ }
+
+ skb_pull(skb, skip_header_bytes);
+- head_need = hdrlen + encaps_len + meshhdrlen - skb_headroom(skb);
+-
+- /*
+- * So we need to modify the skb header and hence need a copy of
+- * that. The head_need variable above doesn't, so far, include
+- * the needed header space that we don't need right away. If we
+- * can, then we don't reallocate right now but only after the
+- * frame arrives at the master device (if it does...)
+- *
+- * If we cannot, however, then we will reallocate to include all
+- * the ever needed space. Also, if we need to reallocate it anyway,
+- * make it big enough for everything we may ever need.
+- */
++ head_need = hdrlen + encaps_len + meshhdrlen;
+
+- if (head_need > 0 || skb_cloned(skb)) {
+- head_need += sdata->encrypt_headroom;
+- head_need += local->tx_headroom;
+- head_need = max_t(int, 0, head_need);
+- if (ieee80211_skb_resize(sdata, skb, head_need, true)) {
+- ieee80211_free_txskb(&local->hw, skb);
+- skb = NULL;
+- return ERR_PTR(-ENOMEM);
+- }
++ if (ieee80211_skb_resize(local, sdata, skb, head_need,
++ sdata->encrypt_headroom)) {
++ ieee80211_free_txskb(&local->hw, skb);
++ skb = NULL;
++ return ERR_PTR(-ENOMEM);
+ }
+
+ if (encaps_data)
+@@ -3411,7 +3401,6 @@ static bool ieee80211_xmit_fast(struct i
+ 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;
+@@ -3463,10 +3452,7 @@ static bool ieee80211_xmit_fast(struct i
+ * as the may-encrypt argument for the resize to not account for
+ * more room than we already have in 'extra_head'
+ */
+- if (unlikely(ieee80211_skb_resize(sdata, skb,
+- max_t(int, extra_head + hw_headroom -
+- skb_headroom(skb), 0),
+- false))) {
++ if (unlikely(ieee80211_skb_resize(local, sdata, skb, extra_head, 0))) {
+ kfree_skb(skb);
+ return true;
+ }
+++ /dev/null
-From: Felix Fietkau <nbd@nbd.name>
-Date: Sun, 17 Mar 2019 18:11:30 +0100
-Subject: [PATCH] mac80211: optimize skb resizing
-
-When forwarding unicast packets from ethernet to batman-adv over 802.11s
-(with forwarding disabled), the typical required headroom to transmit
-encrypted packets on mt76 is 32 (802.11) + 6 (802.11s) + 8 (CCMP) +
-2 (padding) + 6 (LLC) + 18 (batman-adv) - 14 (old ethernet header) = 58 bytes.
-
-On systems where NET_SKB_PAD is 64 this leads to a call to pskb_expand_head
-for every packet, since mac80211 also tries to allocate 16 bytes status
-headroom for radiotap headers.
-
-This patch fixes these unnecessary reallocations by only requiring the extra
-status headroom in ieee80211_tx_monitor()
-If however a reallocation happens before that call, the status headroom gets
-added there as well, in order to avoid double reallocation.
-
-The patch also cleans up the code by moving the headroom calculation to
-ieee80211_skb_resize.
-
-Signed-off-by: Felix Fietkau <nbd@nbd.name>
----
-
---- a/net/mac80211/ieee80211_i.h
-+++ b/net/mac80211/ieee80211_i.h
-@@ -1779,6 +1779,9 @@ int ieee80211_tx_control_port(struct wip
- const u8 *dest, __be16 proto, bool unencrypted);
- int ieee80211_probe_mesh_link(struct wiphy *wiphy, struct net_device *dev,
- const u8 *buf, size_t len);
-+int ieee80211_skb_resize(struct ieee80211_local *local,
-+ struct ieee80211_sub_if_data *sdata,
-+ struct sk_buff *skb, int hdrlen, int hdr_add);
-
- /* HT */
- void ieee80211_apply_htcap_overrides(struct ieee80211_sub_if_data *sdata,
---- a/net/mac80211/status.c
-+++ b/net/mac80211/status.c
-@@ -655,6 +655,11 @@ void ieee80211_tx_monitor(struct ieee802
- struct net_device *prev_dev = NULL;
- int rtap_len;
-
-+ if (ieee80211_skb_resize(local, NULL, skb, 0, 0)) {
-+ dev_kfree_skb(skb);
-+ return;
-+ }
-+
- /* send frame to monitor interfaces now */
- rtap_len = ieee80211_tx_radiotap_len(info);
- if (WARN_ON_ONCE(skb_headroom(skb) < rtap_len)) {
---- a/net/mac80211/tx.c
-+++ b/net/mac80211/tx.c
-@@ -1936,37 +1936,53 @@ static bool ieee80211_tx(struct ieee8021
- }
-
- /* device xmit handlers */
--
--static int ieee80211_skb_resize(struct ieee80211_sub_if_data *sdata,
-- struct sk_buff *skb,
-- int head_need, bool may_encrypt)
-+int ieee80211_skb_resize(struct ieee80211_local *local,
-+ struct ieee80211_sub_if_data *sdata,
-+ struct sk_buff *skb, int hdr_len, int hdr_extra)
- {
-- struct ieee80211_local *local = sdata->local;
-+ struct ieee80211_tx_info *info = IEEE80211_SKB_CB(skb);
- struct ieee80211_hdr *hdr;
-- bool enc_tailroom;
-- int tail_need = 0;
-+ int head_need, head_max;
-+ int tail_need, tail_max;
-+ bool enc_tailroom = false;
-
-- hdr = (struct ieee80211_hdr *) skb->data;
-- enc_tailroom = may_encrypt &&
-- (sdata->crypto_tx_tailroom_needed_cnt ||
-- ieee80211_is_mgmt(hdr->frame_control));
-+ if (sdata && !hdr_len &&
-+ !(info->flags & IEEE80211_TX_INTFL_DONT_ENCRYPT)) {
-+ hdr = (struct ieee80211_hdr *) skb->data;
-+ enc_tailroom = (sdata->crypto_tx_tailroom_needed_cnt ||
-+ ieee80211_is_mgmt(hdr->frame_control));
-+ hdr_len += sdata->encrypt_headroom;
-+ }
-
-- if (enc_tailroom) {
-- tail_need = IEEE80211_ENCRYPT_TAILROOM;
-- tail_need -= skb_tailroom(skb);
-- tail_need = max_t(int, tail_need, 0);
-+ head_need = head_max = hdr_len;
-+ tail_need = tail_max = 0;
-+ if (!sdata) {
-+ head_need = head_max = local->tx_headroom;
-+ } else {
-+ head_max += hdr_extra;
-+ head_max += max_t(int, local->tx_headroom,
-+ local->hw.extra_tx_headroom);
-+ head_need += local->hw.extra_tx_headroom;
-+
-+ tail_max = IEEE80211_ENCRYPT_TAILROOM;
-+ if (enc_tailroom)
-+ tail_need = tail_max;
- }
-
- if (skb_cloned(skb) &&
- (!ieee80211_hw_check(&local->hw, SUPPORTS_CLONED_SKBS) ||
- !skb_clone_writable(skb, ETH_HLEN) || enc_tailroom))
- I802_DEBUG_INC(local->tx_expand_skb_head_cloned);
-- else if (head_need || tail_need)
-+ else if (head_need > skb_headroom(skb) ||
-+ tail_need > skb_tailroom(skb))
- I802_DEBUG_INC(local->tx_expand_skb_head);
- else
- return 0;
-
-- if (pskb_expand_head(skb, head_need, tail_need, GFP_ATOMIC)) {
-+ head_max = max_t(int, 0, head_max - skb_headroom(skb));
-+ tail_max = max_t(int, 0, tail_max - skb_tailroom(skb));
-+
-+ if (pskb_expand_head(skb, head_max, tail_max, GFP_ATOMIC)) {
- wiphy_debug(local->hw.wiphy,
- "failed to reallocate TX buffer\n");
- return -ENOMEM;
-@@ -1982,18 +1998,8 @@ void ieee80211_xmit(struct ieee80211_sub
- struct ieee80211_local *local = sdata->local;
- struct ieee80211_tx_info *info = IEEE80211_SKB_CB(skb);
- struct ieee80211_hdr *hdr;
-- int headroom;
-- bool may_encrypt;
--
-- may_encrypt = !(info->flags & IEEE80211_TX_INTFL_DONT_ENCRYPT);
--
-- headroom = local->tx_headroom;
-- if (may_encrypt)
-- headroom += sdata->encrypt_headroom;
-- headroom -= skb_headroom(skb);
-- headroom = max_t(int, 0, headroom);
-
-- if (ieee80211_skb_resize(sdata, skb, headroom, may_encrypt)) {
-+ if (ieee80211_skb_resize(local, sdata, skb, 0, 0)) {
- ieee80211_free_txskb(&local->hw, skb);
- return;
- }
-@@ -2774,29 +2780,13 @@ static struct sk_buff *ieee80211_build_h
- }
-
- skb_pull(skb, skip_header_bytes);
-- head_need = hdrlen + encaps_len + meshhdrlen - skb_headroom(skb);
--
-- /*
-- * So we need to modify the skb header and hence need a copy of
-- * that. The head_need variable above doesn't, so far, include
-- * the needed header space that we don't need right away. If we
-- * can, then we don't reallocate right now but only after the
-- * frame arrives at the master device (if it does...)
-- *
-- * If we cannot, however, then we will reallocate to include all
-- * the ever needed space. Also, if we need to reallocate it anyway,
-- * make it big enough for everything we may ever need.
-- */
-+ head_need = hdrlen + encaps_len + meshhdrlen;
-
-- if (head_need > 0 || skb_cloned(skb)) {
-- head_need += sdata->encrypt_headroom;
-- head_need += local->tx_headroom;
-- head_need = max_t(int, 0, head_need);
-- if (ieee80211_skb_resize(sdata, skb, head_need, true)) {
-- ieee80211_free_txskb(&local->hw, skb);
-- skb = NULL;
-- return ERR_PTR(-ENOMEM);
-- }
-+ if (ieee80211_skb_resize(local, sdata, skb, head_need,
-+ sdata->encrypt_headroom)) {
-+ ieee80211_free_txskb(&local->hw, skb);
-+ skb = NULL;
-+ return ERR_PTR(-ENOMEM);
- }
-
- if (encaps_data)
-@@ -3411,7 +3401,6 @@ static bool ieee80211_xmit_fast(struct i
- 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;
-@@ -3463,10 +3452,7 @@ static bool ieee80211_xmit_fast(struct i
- * as the may-encrypt argument for the resize to not account for
- * more room than we already have in 'extra_head'
- */
-- if (unlikely(ieee80211_skb_resize(sdata, skb,
-- max_t(int, extra_head + hw_headroom -
-- skb_headroom(skb), 0),
-- false))) {
-+ if (unlikely(ieee80211_skb_resize(local, sdata, skb, extra_head, 0))) {
- kfree_skb(skb);
- return true;
- }
--- /dev/null
+--- a/include/net/cfg80211.h
++++ b/include/net/cfg80211.h
+@@ -3344,6 +3344,7 @@ struct cfg80211_update_owe_info {
+ * (as advertised by the nl80211 feature flag.)
+ * @get_tx_power: store the current TX power into the dbm variable;
+ * return 0 if successful
++ * @set_antenna_gain: set antenna gain to reduce maximum tx power if necessary
+ *
+ * @set_wds_peer: set the WDS peer for a WDS interface
+ *
+@@ -3656,6 +3657,7 @@ struct cfg80211_ops {
+ enum nl80211_tx_power_setting type, int mbm);
+ int (*get_tx_power)(struct wiphy *wiphy, struct wireless_dev *wdev,
+ int *dbm);
++ int (*set_antenna_gain)(struct wiphy *wiphy, int dbi);
+
+ int (*set_wds_peer)(struct wiphy *wiphy, struct net_device *dev,
+ const u8 *addr);
+--- a/include/net/mac80211.h
++++ b/include/net/mac80211.h
+@@ -1476,6 +1476,7 @@ enum ieee80211_smps_mode {
+ *
+ * @power_level: requested transmit power (in dBm), backward compatibility
+ * value only that is set to the minimum of all interfaces
++ * @max_antenna_gain: maximum antenna gain adjusted by user config (in dBi)
+ *
+ * @chandef: the channel definition to tune to
+ * @radar_enabled: whether radar detection is enabled
+@@ -1496,6 +1497,7 @@ enum ieee80211_smps_mode {
+ struct ieee80211_conf {
+ u32 flags;
+ int power_level, dynamic_ps_timeout;
++ int max_antenna_gain;
+
+ u16 listen_interval;
+ u8 ps_dtim_period;
+--- a/include/uapi/linux/nl80211.h
++++ b/include/uapi/linux/nl80211.h
+@@ -2356,6 +2356,9 @@ enum nl80211_commands {
+ *
+ * @NL80211_ATTR_TWT_RESPONDER: Enable target wait time responder support.
+ *
++ * @NL80211_ATTR_WIPHY_ANTENNA_GAIN: Configured antenna gain. Used to reduce
++ * transmit power to stay within regulatory limits. u32, dBi.
++ *
+ * @NUM_NL80211_ATTR: total number of nl80211_attrs available
+ * @NL80211_ATTR_MAX: highest attribute number currently defined
+ * @__NL80211_ATTR_AFTER_LAST: internal use
+@@ -2813,6 +2816,8 @@ enum nl80211_attrs {
+
+ NL80211_ATTR_TWT_RESPONDER,
+
++ NL80211_ATTR_WIPHY_ANTENNA_GAIN,
++
+ /* add attributes here, update the policy in nl80211.c */
+
+ __NL80211_ATTR_AFTER_LAST,
+--- a/net/mac80211/cfg.c
++++ b/net/mac80211/cfg.c
+@@ -2584,6 +2584,19 @@ static int ieee80211_get_tx_power(struct
+ return 0;
+ }
+
++static int ieee80211_set_antenna_gain(struct wiphy *wiphy, int dbi)
++{
++ struct ieee80211_local *local = wiphy_priv(wiphy);
++
++ if (dbi < 0)
++ return -EINVAL;
++
++ local->user_antenna_gain = dbi;
++ ieee80211_hw_config(local, 0);
++
++ return 0;
++}
++
+ static int ieee80211_set_wds_peer(struct wiphy *wiphy, struct net_device *dev,
+ const u8 *addr)
+ {
+@@ -3997,6 +4010,7 @@ const struct cfg80211_ops mac80211_confi
+ .set_wiphy_params = ieee80211_set_wiphy_params,
+ .set_tx_power = ieee80211_set_tx_power,
+ .get_tx_power = ieee80211_get_tx_power,
++ .set_antenna_gain = ieee80211_set_antenna_gain,
+ .set_wds_peer = ieee80211_set_wds_peer,
+ .rfkill_poll = ieee80211_rfkill_poll,
+ CFG80211_TESTMODE_CMD(ieee80211_testmode_cmd)
+--- a/net/mac80211/ieee80211_i.h
++++ b/net/mac80211/ieee80211_i.h
+@@ -1372,6 +1372,7 @@ struct ieee80211_local {
+ int dynamic_ps_forced_timeout;
+
+ int user_power_level; /* in dBm, for all interfaces */
++ int user_antenna_gain; /* in dBi */
+
+ enum ieee80211_smps_mode smps_mode;
+
+--- a/net/mac80211/main.c
++++ b/net/mac80211/main.c
+@@ -93,7 +93,7 @@ static u32 ieee80211_hw_conf_chan(struct
+ struct ieee80211_sub_if_data *sdata;
+ struct cfg80211_chan_def chandef = {};
+ u32 changed = 0;
+- int power;
++ int power, max_power;
+ u32 offchannel_flag;
+
+ offchannel_flag = local->hw.conf.flags & IEEE80211_CONF_OFFCHANNEL;
+@@ -150,6 +150,12 @@ static u32 ieee80211_hw_conf_chan(struct
+ }
+ rcu_read_unlock();
+
++ max_power = chandef.chan->max_reg_power;
++ if (local->user_antenna_gain > 0) {
++ max_power -= local->user_antenna_gain;
++ power = min(power, max_power);
++ }
++
+ if (local->hw.conf.power_level != power) {
+ changed |= IEEE80211_CONF_CHANGE_POWER;
+ local->hw.conf.power_level = power;
+@@ -639,6 +645,7 @@ struct ieee80211_hw *ieee80211_alloc_hw_
+ IEEE80211_RADIOTAP_MCS_HAVE_BW;
+ local->hw.radiotap_vht_details = IEEE80211_RADIOTAP_VHT_KNOWN_GI |
+ IEEE80211_RADIOTAP_VHT_KNOWN_BANDWIDTH;
++ local->user_antenna_gain = 0;
+ local->hw.uapsd_queues = IEEE80211_DEFAULT_UAPSD_QUEUES;
+ local->hw.uapsd_max_sp_len = IEEE80211_DEFAULT_MAX_SP_LEN;
+ local->user_power_level = IEEE80211_UNSET_POWER_LEVEL;
+--- a/net/wireless/nl80211.c
++++ b/net/wireless/nl80211.c
+@@ -574,6 +574,7 @@ const struct nla_policy nl80211_policy[N
+ [NL80211_ATTR_SAE_PASSWORD] = { .type = NLA_BINARY,
+ .len = SAE_PASSWORD_MAX_LEN },
+ [NL80211_ATTR_TWT_RESPONDER] = { .type = NLA_FLAG },
++ [NL80211_ATTR_WIPHY_ANTENNA_GAIN] = { .type = NLA_U32 },
+ };
+
+ /* policy for the key attributes */
+@@ -2869,6 +2870,20 @@ static int nl80211_set_wiphy(struct sk_b
+ if (result)
+ return result;
+ }
++
++ if (info->attrs[NL80211_ATTR_WIPHY_ANTENNA_GAIN]) {
++ int idx, dbi = 0;
++
++ if (!rdev->ops->set_antenna_gain)
++ return -EOPNOTSUPP;
++
++ idx = NL80211_ATTR_WIPHY_ANTENNA_GAIN;
++ dbi = nla_get_u32(info->attrs[idx]);
++
++ result = rdev->ops->set_antenna_gain(&rdev->wiphy, dbi);
++ if (result)
++ return result;
++ }
+
+ if (info->attrs[NL80211_ATTR_WIPHY_ANTENNA_TX] &&
+ info->attrs[NL80211_ATTR_WIPHY_ANTENNA_RX]) {
+++ /dev/null
---- a/include/net/cfg80211.h
-+++ b/include/net/cfg80211.h
-@@ -3344,6 +3344,7 @@ struct cfg80211_update_owe_info {
- * (as advertised by the nl80211 feature flag.)
- * @get_tx_power: store the current TX power into the dbm variable;
- * return 0 if successful
-+ * @set_antenna_gain: set antenna gain to reduce maximum tx power if necessary
- *
- * @set_wds_peer: set the WDS peer for a WDS interface
- *
-@@ -3656,6 +3657,7 @@ struct cfg80211_ops {
- enum nl80211_tx_power_setting type, int mbm);
- int (*get_tx_power)(struct wiphy *wiphy, struct wireless_dev *wdev,
- int *dbm);
-+ int (*set_antenna_gain)(struct wiphy *wiphy, int dbi);
-
- int (*set_wds_peer)(struct wiphy *wiphy, struct net_device *dev,
- const u8 *addr);
---- a/include/net/mac80211.h
-+++ b/include/net/mac80211.h
-@@ -1476,6 +1476,7 @@ enum ieee80211_smps_mode {
- *
- * @power_level: requested transmit power (in dBm), backward compatibility
- * value only that is set to the minimum of all interfaces
-+ * @max_antenna_gain: maximum antenna gain adjusted by user config (in dBi)
- *
- * @chandef: the channel definition to tune to
- * @radar_enabled: whether radar detection is enabled
-@@ -1496,6 +1497,7 @@ enum ieee80211_smps_mode {
- struct ieee80211_conf {
- u32 flags;
- int power_level, dynamic_ps_timeout;
-+ int max_antenna_gain;
-
- u16 listen_interval;
- u8 ps_dtim_period;
---- a/include/uapi/linux/nl80211.h
-+++ b/include/uapi/linux/nl80211.h
-@@ -2356,6 +2356,9 @@ enum nl80211_commands {
- *
- * @NL80211_ATTR_TWT_RESPONDER: Enable target wait time responder support.
- *
-+ * @NL80211_ATTR_WIPHY_ANTENNA_GAIN: Configured antenna gain. Used to reduce
-+ * transmit power to stay within regulatory limits. u32, dBi.
-+ *
- * @NUM_NL80211_ATTR: total number of nl80211_attrs available
- * @NL80211_ATTR_MAX: highest attribute number currently defined
- * @__NL80211_ATTR_AFTER_LAST: internal use
-@@ -2813,6 +2816,8 @@ enum nl80211_attrs {
-
- NL80211_ATTR_TWT_RESPONDER,
-
-+ NL80211_ATTR_WIPHY_ANTENNA_GAIN,
-+
- /* add attributes here, update the policy in nl80211.c */
-
- __NL80211_ATTR_AFTER_LAST,
---- a/net/mac80211/cfg.c
-+++ b/net/mac80211/cfg.c
-@@ -2584,6 +2584,19 @@ static int ieee80211_get_tx_power(struct
- return 0;
- }
-
-+static int ieee80211_set_antenna_gain(struct wiphy *wiphy, int dbi)
-+{
-+ struct ieee80211_local *local = wiphy_priv(wiphy);
-+
-+ if (dbi < 0)
-+ return -EINVAL;
-+
-+ local->user_antenna_gain = dbi;
-+ ieee80211_hw_config(local, 0);
-+
-+ return 0;
-+}
-+
- static int ieee80211_set_wds_peer(struct wiphy *wiphy, struct net_device *dev,
- const u8 *addr)
- {
-@@ -3997,6 +4010,7 @@ const struct cfg80211_ops mac80211_confi
- .set_wiphy_params = ieee80211_set_wiphy_params,
- .set_tx_power = ieee80211_set_tx_power,
- .get_tx_power = ieee80211_get_tx_power,
-+ .set_antenna_gain = ieee80211_set_antenna_gain,
- .set_wds_peer = ieee80211_set_wds_peer,
- .rfkill_poll = ieee80211_rfkill_poll,
- CFG80211_TESTMODE_CMD(ieee80211_testmode_cmd)
---- a/net/mac80211/ieee80211_i.h
-+++ b/net/mac80211/ieee80211_i.h
-@@ -1372,6 +1372,7 @@ struct ieee80211_local {
- int dynamic_ps_forced_timeout;
-
- int user_power_level; /* in dBm, for all interfaces */
-+ int user_antenna_gain; /* in dBi */
-
- enum ieee80211_smps_mode smps_mode;
-
---- a/net/mac80211/main.c
-+++ b/net/mac80211/main.c
-@@ -93,7 +93,7 @@ static u32 ieee80211_hw_conf_chan(struct
- struct ieee80211_sub_if_data *sdata;
- struct cfg80211_chan_def chandef = {};
- u32 changed = 0;
-- int power;
-+ int power, max_power;
- u32 offchannel_flag;
-
- offchannel_flag = local->hw.conf.flags & IEEE80211_CONF_OFFCHANNEL;
-@@ -150,6 +150,12 @@ static u32 ieee80211_hw_conf_chan(struct
- }
- rcu_read_unlock();
-
-+ max_power = chandef.chan->max_reg_power;
-+ if (local->user_antenna_gain > 0) {
-+ max_power -= local->user_antenna_gain;
-+ power = min(power, max_power);
-+ }
-+
- if (local->hw.conf.power_level != power) {
- changed |= IEEE80211_CONF_CHANGE_POWER;
- local->hw.conf.power_level = power;
-@@ -639,6 +645,7 @@ struct ieee80211_hw *ieee80211_alloc_hw_
- IEEE80211_RADIOTAP_MCS_HAVE_BW;
- local->hw.radiotap_vht_details = IEEE80211_RADIOTAP_VHT_KNOWN_GI |
- IEEE80211_RADIOTAP_VHT_KNOWN_BANDWIDTH;
-+ local->user_antenna_gain = 0;
- local->hw.uapsd_queues = IEEE80211_DEFAULT_UAPSD_QUEUES;
- local->hw.uapsd_max_sp_len = IEEE80211_DEFAULT_MAX_SP_LEN;
- local->user_power_level = IEEE80211_UNSET_POWER_LEVEL;
---- a/net/wireless/nl80211.c
-+++ b/net/wireless/nl80211.c
-@@ -574,6 +574,7 @@ const struct nla_policy nl80211_policy[N
- [NL80211_ATTR_SAE_PASSWORD] = { .type = NLA_BINARY,
- .len = SAE_PASSWORD_MAX_LEN },
- [NL80211_ATTR_TWT_RESPONDER] = { .type = NLA_FLAG },
-+ [NL80211_ATTR_WIPHY_ANTENNA_GAIN] = { .type = NLA_U32 },
- };
-
- /* policy for the key attributes */
-@@ -2869,6 +2870,20 @@ static int nl80211_set_wiphy(struct sk_b
- if (result)
- return result;
- }
-+
-+ if (info->attrs[NL80211_ATTR_WIPHY_ANTENNA_GAIN]) {
-+ int idx, dbi = 0;
-+
-+ if (!rdev->ops->set_antenna_gain)
-+ return -EOPNOTSUPP;
-+
-+ idx = NL80211_ATTR_WIPHY_ANTENNA_GAIN;
-+ dbi = nla_get_u32(info->attrs[idx]);
-+
-+ result = rdev->ops->set_antenna_gain(&rdev->wiphy, dbi);
-+ if (result)
-+ return result;
-+ }
-
- if (info->attrs[NL80211_ATTR_WIPHY_ANTENNA_TX] &&
- info->attrs[NL80211_ATTR_WIPHY_ANTENNA_RX]) {