]> git.ipfire.org Git - thirdparty/linux.git/commitdiff
wifi: mac80211: add support for NDP ADDBA/DELBA for S1G
authorRia Thomas <ria.thomas@morsemicro.com>
Thu, 5 Mar 2026 09:13:04 +0000 (14:43 +0530)
committerJohannes Berg <johannes.berg@intel.com>
Fri, 6 Mar 2026 09:52:11 +0000 (10:52 +0100)
S1G defines use of NDP Block Ack (BA) for aggregation, requiring negotiation
of NDP ADDBA/DELBA action frames. If the S1G recipient supports HT-immediate
block ack, the sender must send an NDP ADDBA Request indicating it expects
only NDP BlockAck frames for the agreement.

Introduce support for NDP ADDBA and DELBA exchange in mac80211. The
implementation negotiates the BA mechanism during setup based on station
capabilities and driver support (IEEE80211_HW_SUPPORTS_NDP_BLOCKACK).
If negotiation fails due to mismatched expectations, a rejection with status code
WLAN_STATUS_REJECTED_NDP_BLOCK_ACK_SUGGESTED is returned as per IEEE 802.11-2024.

Trace sample:

IEEE 802.11 Wireless Management
    Fixed parameters
        Category code: Block Ack (3)
        Action code: NDP ADDBA Request (0x80)
        Dialog token: 0x01
        Block Ack Parameters: 0x1003, A-MSDUs, Block Ack Policy
            .... .... .... ...1 = A-MSDUs: Permitted in QoS Data MPDUs
            .... .... .... ..1. = Block Ack Policy: Immediate Block Ack
            .... .... ..00 00.. = Traffic Identifier: 0x0
            0001 0000 00.. .... = Number of Buffers (1 Buffer = 2304 Bytes): 64
        Block Ack Timeout: 0x0000
        Block Ack Starting Sequence Control (SSC): 0x0010
            .... .... .... 0000 = Fragment: 0
            0000 0000 0001 .... = Starting Sequence Number: 1

IEEE 802.11 Wireless Management
    Fixed parameters
        Category code: Block Ack (3)
        Action code: NDP ADDBA Response (0x81)
        Dialog token: 0x02
        Status code: BlockAck negotiation refused because, due to buffer constraints and other unspecified reasons, the recipient prefers to generate only NDP BlockAck frames (0x006d)
        Block Ack Parameters: 0x1002, Block Ack Policy
            .... .... .... ...0 = A-MSDUs: Not Permitted
            .... .... .... ..1. = Block Ack Policy: Immediate Block Ack
            .... .... ..00 00.. = Traffic Identifier: 0x0
            0001 0000 00.. .... = Number of Buffers (1 Buffer = 2304 Bytes): 64
        Block Ack Timeout: 0x0000

Signed-off-by: Ria Thomas <ria.thomas@morsemicro.com>
Link: https://patch.msgid.link/20260305091304.310990-1-ria.thomas@morsemicro.com
Signed-off-by: Johannes Berg <johannes.berg@intel.com>
12 files changed:
include/linux/ieee80211-ht.h
include/linux/ieee80211.h
include/net/mac80211.h
net/mac80211/agg-rx.c
net/mac80211/agg-tx.c
net/mac80211/debugfs.c
net/mac80211/ht.c
net/mac80211/ieee80211_i.h
net/mac80211/iface.c
net/mac80211/rx.c
net/mac80211/s1g.c
net/mac80211/sta_info.h

index 21bbf470540f6a5868dbdebb90b5d449763786fc..7612b72f9c7cbbfb19681435f0cd4414ee7a9d4a 100644 (file)
@@ -281,6 +281,9 @@ enum ieee80211_back_actioncode {
        WLAN_ACTION_ADDBA_REQ = 0,
        WLAN_ACTION_ADDBA_RESP = 1,
        WLAN_ACTION_DELBA = 2,
+       WLAN_ACTION_NDP_ADDBA_REQ = 128,
+       WLAN_ACTION_NDP_ADDBA_RESP = 129,
+       WLAN_ACTION_NDP_DELBA = 130,
 };
 
 /* BACK (block-ack) parties */
index aea360e90cb1f02c53be6debc2c57c22d916fcb4..52db36120314e3bd8bbf54e9714c87dd3c20d152 100644 (file)
@@ -1482,6 +1482,8 @@ enum ieee80211_statuscode {
        WLAN_STATUS_REJECT_DSE_BAND = 96,
        WLAN_STATUS_DENIED_WITH_SUGGESTED_BAND_AND_CHANNEL = 99,
        WLAN_STATUS_DENIED_DUE_TO_SPECTRUM_MANAGEMENT = 103,
+       /* 802.11ah */
+       WLAN_STATUS_REJECTED_NDP_BLOCK_ACK_SUGGESTED = 109,
        /* 802.11ai */
        WLAN_STATUS_FILS_AUTHENTICATION_FAILURE = 112,
        WLAN_STATUS_UNKNOWN_AUTHENTICATION_SERVER = 113,
index 9f8251fb9832d9c5480c25c532443902ce16ab44..9cc482191ab9b2baf8c7e7f85ff3d1d466236c8a 100644 (file)
@@ -2913,6 +2913,9 @@ struct ieee80211_txq {
  *     HW flag so drivers can opt in according to their own control, e.g. in
  *     testing.
  *
+ * @IEEE80211_HW_SUPPORTS_NDP_BLOCKACK: HW can transmit/receive S1G NDP
+ *     BlockAck frames.
+ *
  * @NUM_IEEE80211_HW_FLAGS: number of hardware flags, used for sizing arrays
  */
 enum ieee80211_hw_flags {
@@ -2973,6 +2976,7 @@ enum ieee80211_hw_flags {
        IEEE80211_HW_DISALLOW_PUNCTURING,
        IEEE80211_HW_HANDLES_QUIET_CSA,
        IEEE80211_HW_STRICT,
+       IEEE80211_HW_SUPPORTS_NDP_BLOCKACK,
 
        /* keep last, obviously */
        NUM_IEEE80211_HW_FLAGS
index 0a2be8cb600fad94eb7b29f644d4bd91c743dd49..0140ac826b23aecda1198a619719826b76b77efd 100644 (file)
@@ -94,7 +94,8 @@ void __ieee80211_stop_rx_ba_session(struct sta_info *sta, u16 tid,
        /* check if this is a self generated aggregation halt */
        if (initiator == WLAN_BACK_RECIPIENT && tx)
                ieee80211_send_delba(sta->sdata, sta->sta.addr,
-                                    tid, WLAN_BACK_RECIPIENT, reason);
+                                    tid, WLAN_BACK_RECIPIENT, reason,
+                                    ieee80211_s1g_use_ndp_ba(sta->sdata, sta));
 
        /*
         * return here in case tid_rx is not assigned - which will happen if
@@ -240,6 +241,7 @@ static void ieee80211_send_addba_resp(struct sta_info *sta, u8 *da, u16 tid,
        struct sk_buff *skb;
        struct ieee80211_mgmt *mgmt;
        bool amsdu = ieee80211_hw_check(&local->hw, SUPPORTS_AMSDU_IN_AMPDU);
+       bool use_ndp = ieee80211_s1g_use_ndp_ba(sdata, sta);
        u16 capab;
 
        skb = dev_alloc_skb(sizeof(*mgmt) +
@@ -253,7 +255,8 @@ static void ieee80211_send_addba_resp(struct sta_info *sta, u8 *da, u16 tid,
 
        skb_put(skb, 2 + sizeof(mgmt->u.action.addba_resp));
        mgmt->u.action.category = WLAN_CATEGORY_BACK;
-       mgmt->u.action.action_code = WLAN_ACTION_ADDBA_RESP;
+       mgmt->u.action.action_code = use_ndp ?
+               WLAN_ACTION_NDP_ADDBA_RESP : WLAN_ACTION_ADDBA_RESP;
 
        mgmt->u.action.addba_resp.dialog_token = dialog_token;
 
@@ -276,6 +279,7 @@ void __ieee80211_start_rx_ba_session(struct sta_info *sta,
                                     u8 dialog_token, u16 timeout,
                                     u16 start_seq_num, u16 ba_policy, u16 tid,
                                     u16 buf_size, bool tx, bool auto_seq,
+                                    bool req_ndp,
                                     const u8 addba_ext_data)
 {
        struct ieee80211_local *local = sta->sdata->local;
@@ -301,6 +305,18 @@ void __ieee80211_start_rx_ba_session(struct sta_info *sta,
                goto end;
        }
 
+       if (tx && ieee80211_s1g_use_ndp_ba(sta->sdata, sta) && !req_ndp) {
+               /*
+                * According to IEEE 802.11-2024: Inform S1G originator
+                * ADDBA rejected as NDP BlockAck is preferred
+                */
+               status = WLAN_STATUS_REJECTED_NDP_BLOCK_ACK_SUGGESTED;
+               ht_dbg(sta->sdata,
+                      "Rejecting AddBA Req from %pM tid %u - require NDP BlockAck\n",
+                      sta->sta.addr, tid);
+               goto end;
+       }
+
        if (!sta->sta.valid_links &&
            !sta->sta.deflink.ht_cap.ht_supported &&
            !sta->sta.deflink.he_cap.has_he &&
@@ -474,6 +490,7 @@ void ieee80211_process_addba_request(struct ieee80211_local *local,
                                     struct ieee80211_mgmt *mgmt,
                                     size_t len)
 {
+       bool req_ndp = mgmt->u.action.action_code == WLAN_ACTION_NDP_ADDBA_REQ;
        u16 capab, tid, timeout, ba_policy, buf_size, start_seq_num;
        u8 dialog_token, addba_ext_data;
 
@@ -498,7 +515,8 @@ void ieee80211_process_addba_request(struct ieee80211_local *local,
 
        __ieee80211_start_rx_ba_session(sta, dialog_token, timeout,
                                        start_seq_num, ba_policy, tid,
-                                       buf_size, true, false, addba_ext_data);
+                                       buf_size, true, false,
+                                       req_ndp, addba_ext_data);
 }
 
 void ieee80211_manage_rx_ba_offl(struct ieee80211_vif *vif,
index d5a62b8d5a80138b4754e8b9389f1cfddd15c064..01d927b88264f400f7a2f6c0218288d68b7f1395 100644 (file)
@@ -60,7 +60,7 @@
 
 static void ieee80211_send_addba_request(struct sta_info *sta, u16 tid,
                                         u8 dialog_token, u16 start_seq_num,
-                                        u16 agg_size, u16 timeout)
+                                        u16 agg_size, u16 timeout, bool ndp)
 {
        struct ieee80211_sub_if_data *sdata = sta->sdata;
        struct ieee80211_local *local = sdata->local;
@@ -80,7 +80,8 @@ static void ieee80211_send_addba_request(struct sta_info *sta, u16 tid,
        skb_put(skb, 2 + sizeof(mgmt->u.action.addba_req));
 
        mgmt->u.action.category = WLAN_CATEGORY_BACK;
-       mgmt->u.action.action_code = WLAN_ACTION_ADDBA_REQ;
+       mgmt->u.action.action_code = ndp ?
+               WLAN_ACTION_NDP_ADDBA_REQ : WLAN_ACTION_ADDBA_REQ;
 
        mgmt->u.action.addba_req.dialog_token = dialog_token;
        capab = IEEE80211_ADDBA_PARAM_AMSDU_MASK;
@@ -484,7 +485,8 @@ static void ieee80211_send_addba_with_timeout(struct sta_info *sta,
 
        /* send AddBA request */
        ieee80211_send_addba_request(sta, tid, tid_tx->dialog_token,
-                                    tid_tx->ssn, buf_size, tid_tx->timeout);
+                                    tid_tx->ssn, buf_size, tid_tx->timeout,
+                                    tid_tx->ndp);
 
        WARN_ON(test_and_set_bit(HT_AGG_STATE_SENT_ADDBA, &tid_tx->state));
 }
@@ -521,6 +523,7 @@ void ieee80211_tx_ba_session_handle_start(struct sta_info *sta, int tid)
         */
        synchronize_net();
 
+       tid_tx->ndp = ieee80211_s1g_use_ndp_ba(sdata, sta);
        params.ssn = sta->tid_seq[tid] >> 4;
        ret = drv_ampdu_action(local, sdata, &params);
        tid_tx->ssn = params.ssn;
@@ -940,7 +943,9 @@ void ieee80211_stop_tx_ba_cb(struct sta_info *sta, int tid,
 
        if (send_delba)
                ieee80211_send_delba(sdata, sta->sta.addr, tid,
-                       WLAN_BACK_INITIATOR, WLAN_REASON_QSTA_NOT_USE);
+                                    WLAN_BACK_INITIATOR,
+                                    WLAN_REASON_QSTA_NOT_USE,
+                                    tid_tx->ndp);
 }
 
 void ieee80211_stop_tx_ba_cb_irqsafe(struct ieee80211_vif *vif,
index d02f07368c5119837a64214543d272040b878cd2..e8d0a8b71d5952fde50bf1f4227919b707de57ed 100644 (file)
@@ -490,6 +490,7 @@ static const char *hw_flag_names[] = {
        FLAG(DISALLOW_PUNCTURING),
        FLAG(HANDLES_QUIET_CSA),
        FLAG(STRICT),
+       FLAG(SUPPORTS_NDP_BLOCKACK),
 #undef FLAG
 };
 
index 9e2469a8ce645af0a57539e87ece0633ae9dd0f7..33f1e1b235e95a44711c6b4f9d774c50675a8174 100644 (file)
@@ -379,7 +379,7 @@ void ieee80211_ba_session_work(struct wiphy *wiphy, struct wiphy_work *work)
                                       sta->ampdu_mlme.tid_rx_manage_offl))
                        __ieee80211_start_rx_ba_session(sta, 0, 0, 0, 1, tid,
                                                        IEEE80211_MAX_AMPDU_BUF_HT,
-                                                       false, true, 0);
+                                                       false, true, false, 0);
 
                if (test_and_clear_bit(tid + IEEE80211_NUM_TIDS,
                                       sta->ampdu_mlme.tid_rx_manage_offl))
@@ -455,7 +455,8 @@ void ieee80211_ba_session_work(struct wiphy *wiphy, struct wiphy_work *work)
 
 void ieee80211_send_delba(struct ieee80211_sub_if_data *sdata,
                          const u8 *da, u16 tid,
-                         u16 initiator, u16 reason_code)
+                         u16 initiator, u16 reason_code,
+                         bool use_ndp)
 {
        struct ieee80211_local *local = sdata->local;
        struct sk_buff *skb;
@@ -473,7 +474,8 @@ void ieee80211_send_delba(struct ieee80211_sub_if_data *sdata,
        skb_put(skb, 2 + sizeof(mgmt->u.action.delba));
 
        mgmt->u.action.category = WLAN_CATEGORY_BACK;
-       mgmt->u.action.action_code = WLAN_ACTION_DELBA;
+       mgmt->u.action.action_code = use_ndp ?
+               WLAN_ACTION_NDP_DELBA : WLAN_ACTION_DELBA;
        params = (u16)(initiator << 11);        /* bit 11 initiator */
        params |= (u16)(tid << 12);             /* bit 15:12 TID number */
 
index a4babf7624e5de4a56843571aac0e7b571a8d4a3..d71e0c6d21659165b5bb176bfbf07debe88308c8 100644 (file)
@@ -2190,7 +2190,8 @@ bool ieee80211_ht_cap_ie_to_sta_ht_cap(struct ieee80211_sub_if_data *sdata,
                                       struct link_sta_info *link_sta);
 void ieee80211_send_delba(struct ieee80211_sub_if_data *sdata,
                          const u8 *da, u16 tid,
-                         u16 initiator, u16 reason_code);
+                         u16 initiator, u16 reason_code,
+                         bool use_ndp);
 int ieee80211_send_smps_action(struct ieee80211_sub_if_data *sdata,
                               enum ieee80211_smps_mode smps, const u8 *da,
                               const u8 *bssid, int link_id);
@@ -2206,6 +2207,7 @@ void __ieee80211_start_rx_ba_session(struct sta_info *sta,
                                     u8 dialog_token, u16 timeout,
                                     u16 start_seq_num, u16 ba_policy, u16 tid,
                                     u16 buf_size, bool tx, bool auto_seq,
+                                    bool req_ndp,
                                     const u8 addba_ext_data);
 void ieee80211_sta_tear_down_BA_sessions(struct sta_info *sta,
                                         enum ieee80211_agg_stop_reason reason);
@@ -2331,6 +2333,8 @@ void ieee80211_s1g_status_twt_action(struct ieee80211_sub_if_data *sdata,
 void ieee80211_s1g_cap_to_sta_s1g_cap(struct ieee80211_sub_if_data *sdata,
                                      const struct ieee80211_s1g_cap *s1g_cap_ie,
                                      struct link_sta_info *link_sta);
+bool ieee80211_s1g_use_ndp_ba(const struct ieee80211_sub_if_data *sdata,
+                             const struct sta_info *sta);
 
 /* Spectrum management */
 void ieee80211_process_measurement_req(struct ieee80211_sub_if_data *sdata,
index 2e391cec73a09af3243b415b6586218716bd4464..40ce0bb727267d7ebae2f092555eaeb644be0d56 100644 (file)
@@ -1581,14 +1581,17 @@ static void ieee80211_iface_process_skb(struct ieee80211_local *local,
                if (sta) {
                        switch (mgmt->u.action.action_code) {
                        case WLAN_ACTION_ADDBA_REQ:
+                       case WLAN_ACTION_NDP_ADDBA_REQ:
                                ieee80211_process_addba_request(local, sta,
                                                                mgmt, len);
                                break;
                        case WLAN_ACTION_ADDBA_RESP:
+                       case WLAN_ACTION_NDP_ADDBA_RESP:
                                ieee80211_process_addba_resp(local, sta,
                                                             mgmt, len);
                                break;
                        case WLAN_ACTION_DELBA:
+                       case WLAN_ACTION_NDP_DELBA:
                                ieee80211_process_delba(sdata, sta,
                                                        mgmt, len);
                                break;
index 3bd046bebf9e1fef1871c8e7ebac3c9ea1ae8f99..19c33f7a819343e3e238e4541a4a172cfb36637b 100644 (file)
@@ -1475,7 +1475,9 @@ static void ieee80211_rx_reorder_ampdu(struct ieee80211_rx_data *rx,
                    !test_and_set_bit(tid, rx->sta->ampdu_mlme.unexpected_agg))
                        ieee80211_send_delba(rx->sdata, rx->sta->sta.addr, tid,
                                             WLAN_BACK_RECIPIENT,
-                                            WLAN_REASON_QSTA_REQUIRE_SETUP);
+                                            WLAN_REASON_QSTA_REQUIRE_SETUP,
+                                            ieee80211_s1g_use_ndp_ba(rx->sdata,
+                                                                     rx->sta));
                goto dont_reorder;
        }
 
@@ -3372,7 +3374,9 @@ ieee80211_rx_h_ctrl(struct ieee80211_rx_data *rx, struct sk_buff_head *frames)
                    !test_and_set_bit(tid, rx->sta->ampdu_mlme.unexpected_agg))
                        ieee80211_send_delba(rx->sdata, rx->sta->sta.addr, tid,
                                             WLAN_BACK_RECIPIENT,
-                                            WLAN_REASON_QSTA_REQUIRE_SETUP);
+                                            WLAN_REASON_QSTA_REQUIRE_SETUP,
+                                            ieee80211_s1g_use_ndp_ba(rx->sdata,
+                                                                     rx->sta));
 
                tid_agg_rx = rcu_dereference(rx->sta->ampdu_mlme.tid_rx[tid]);
                if (!tid_agg_rx)
@@ -3753,14 +3757,17 @@ ieee80211_rx_h_action(struct ieee80211_rx_data *rx)
 
                switch (mgmt->u.action.action_code) {
                case WLAN_ACTION_ADDBA_REQ:
+               case WLAN_ACTION_NDP_ADDBA_REQ:
                        if (len < IEEE80211_MIN_ACTION_SIZE(addba_req))
                                goto invalid;
                        break;
                case WLAN_ACTION_ADDBA_RESP:
+               case WLAN_ACTION_NDP_ADDBA_RESP:
                        if (len < IEEE80211_MIN_ACTION_SIZE(addba_resp))
                                goto invalid;
                        break;
                case WLAN_ACTION_DELBA:
+               case WLAN_ACTION_NDP_DELBA:
                        if (len < IEEE80211_MIN_ACTION_SIZE(delba))
                                goto invalid;
                        break;
index 297abaa6fecfe9ba41c3fd52cc85eb877a60c8a6..5af4a0c6c6424b4b9dfd6c6e5d9e13b6c8b94b27 100644 (file)
@@ -220,3 +220,11 @@ void ieee80211_s1g_cap_to_sta_s1g_cap(struct ieee80211_sub_if_data *sdata,
 
        ieee80211_sta_recalc_aggregates(&link_sta->sta->sta);
 }
+
+bool ieee80211_s1g_use_ndp_ba(const struct ieee80211_sub_if_data *sdata,
+                             const struct sta_info *sta)
+{
+       return sdata->vif.cfg.s1g &&
+               ieee80211_hw_check(&sdata->local->hw, SUPPORTS_NDP_BLOCKACK) &&
+               (sta && sta->sta.deflink.s1g_cap.s1g);
+}
index f1b1bbf2a2d4c82a264d389e957361d16e7c38fa..58ccbea7f6f6090b032618385c9127ef3fe41971 100644 (file)
@@ -171,6 +171,7 @@ struct sta_info;
  * @bar_pending: BAR needs to be re-sent
  * @amsdu: support A-MSDU within A-MDPU
  * @ssn: starting sequence number of the session
+ * @ndp: this session is using NDP Block ACKs
  *
  * This structure's lifetime is managed by RCU, assignments to
  * the array holding it must hold the aggregation mutex.
@@ -199,6 +200,7 @@ struct tid_ampdu_tx {
        u16 failed_bar_ssn;
        bool bar_pending;
        bool amsdu;
+       bool ndp;
        u8 tid;
 };