mirror of
https://github.com/torvalds/linux.git
synced 2026-05-29 17:43:52 +02:00
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>
231 lines
6.6 KiB
C
231 lines
6.6 KiB
C
// SPDX-License-Identifier: GPL-2.0
|
|
/*
|
|
* S1G handling
|
|
* Copyright(c) 2020 Adapt-IP
|
|
* Copyright (C) 2023, 2026 Intel Corporation
|
|
*/
|
|
#include <linux/ieee80211.h>
|
|
#include <net/mac80211.h>
|
|
#include "ieee80211_i.h"
|
|
#include "driver-ops.h"
|
|
|
|
void ieee80211_s1g_sta_rate_init(struct sta_info *sta)
|
|
{
|
|
/* avoid indicating legacy bitrates for S1G STAs */
|
|
sta->deflink.tx_stats.last_rate.flags |= IEEE80211_TX_RC_S1G_MCS;
|
|
sta->deflink.rx_stats.last_rate =
|
|
STA_STATS_FIELD(TYPE, STA_STATS_RATE_TYPE_S1G);
|
|
}
|
|
|
|
bool ieee80211_s1g_is_twt_setup(struct sk_buff *skb)
|
|
{
|
|
struct ieee80211_mgmt *mgmt = (struct ieee80211_mgmt *)skb->data;
|
|
|
|
if (likely(!ieee80211_is_action(mgmt->frame_control)))
|
|
return false;
|
|
|
|
if (likely(mgmt->u.action.category != WLAN_CATEGORY_S1G))
|
|
return false;
|
|
|
|
return mgmt->u.action.action_code == WLAN_S1G_TWT_SETUP;
|
|
}
|
|
|
|
static void
|
|
ieee80211_s1g_send_twt_setup(struct ieee80211_sub_if_data *sdata, const u8 *da,
|
|
const u8 *bssid, struct ieee80211_twt_setup *twt)
|
|
{
|
|
int len = IEEE80211_MIN_ACTION_SIZE(s1g) + 3 + twt->length;
|
|
struct ieee80211_local *local = sdata->local;
|
|
struct ieee80211_mgmt *mgmt;
|
|
struct sk_buff *skb;
|
|
|
|
skb = dev_alloc_skb(local->hw.extra_tx_headroom + len);
|
|
if (!skb)
|
|
return;
|
|
|
|
skb_reserve(skb, local->hw.extra_tx_headroom);
|
|
mgmt = skb_put_zero(skb, len);
|
|
mgmt->frame_control = cpu_to_le16(IEEE80211_FTYPE_MGMT |
|
|
IEEE80211_STYPE_ACTION);
|
|
memcpy(mgmt->da, da, ETH_ALEN);
|
|
memcpy(mgmt->sa, sdata->vif.addr, ETH_ALEN);
|
|
memcpy(mgmt->bssid, bssid, ETH_ALEN);
|
|
|
|
mgmt->u.action.category = WLAN_CATEGORY_S1G;
|
|
mgmt->u.action.action_code = WLAN_S1G_TWT_SETUP;
|
|
memcpy(mgmt->u.action.s1g.variable, twt, 3 + twt->length);
|
|
|
|
IEEE80211_SKB_CB(skb)->flags |= IEEE80211_TX_INTFL_DONT_ENCRYPT |
|
|
IEEE80211_TX_INTFL_MLME_CONN_TX |
|
|
IEEE80211_TX_CTL_REQ_TX_STATUS;
|
|
ieee80211_tx_skb(sdata, skb);
|
|
}
|
|
|
|
static void
|
|
ieee80211_s1g_send_twt_teardown(struct ieee80211_sub_if_data *sdata,
|
|
const u8 *da, const u8 *bssid, u8 flowid)
|
|
{
|
|
struct ieee80211_local *local = sdata->local;
|
|
struct ieee80211_mgmt *mgmt;
|
|
struct sk_buff *skb;
|
|
u8 *id;
|
|
|
|
skb = dev_alloc_skb(local->hw.extra_tx_headroom +
|
|
IEEE80211_MIN_ACTION_SIZE(s1g) + 1);
|
|
if (!skb)
|
|
return;
|
|
|
|
skb_reserve(skb, local->hw.extra_tx_headroom);
|
|
mgmt = skb_put_zero(skb, IEEE80211_MIN_ACTION_SIZE(s1g) + 1);
|
|
mgmt->frame_control = cpu_to_le16(IEEE80211_FTYPE_MGMT |
|
|
IEEE80211_STYPE_ACTION);
|
|
memcpy(mgmt->da, da, ETH_ALEN);
|
|
memcpy(mgmt->sa, sdata->vif.addr, ETH_ALEN);
|
|
memcpy(mgmt->bssid, bssid, ETH_ALEN);
|
|
|
|
mgmt->u.action.category = WLAN_CATEGORY_S1G;
|
|
mgmt->u.action.action_code = WLAN_S1G_TWT_TEARDOWN;
|
|
id = (u8 *)mgmt->u.action.s1g.variable;
|
|
*id = flowid;
|
|
|
|
IEEE80211_SKB_CB(skb)->flags |= IEEE80211_TX_INTFL_DONT_ENCRYPT |
|
|
IEEE80211_TX_CTL_REQ_TX_STATUS;
|
|
ieee80211_tx_skb(sdata, skb);
|
|
}
|
|
|
|
static void
|
|
ieee80211_s1g_rx_twt_setup(struct ieee80211_sub_if_data *sdata,
|
|
struct sta_info *sta, struct sk_buff *skb)
|
|
{
|
|
struct ieee80211_mgmt *mgmt = (void *)skb->data;
|
|
struct ieee80211_twt_setup *twt = (void *)mgmt->u.action.s1g.variable;
|
|
struct ieee80211_twt_params *twt_agrt = (void *)twt->params;
|
|
|
|
twt_agrt->req_type &= cpu_to_le16(~IEEE80211_TWT_REQTYPE_REQUEST);
|
|
|
|
/* broadcast TWT not supported yet */
|
|
if (twt->control & IEEE80211_TWT_CONTROL_NEG_TYPE_BROADCAST) {
|
|
twt_agrt->req_type &=
|
|
~cpu_to_le16(IEEE80211_TWT_REQTYPE_SETUP_CMD);
|
|
twt_agrt->req_type |=
|
|
le16_encode_bits(TWT_SETUP_CMD_REJECT,
|
|
IEEE80211_TWT_REQTYPE_SETUP_CMD);
|
|
goto out;
|
|
}
|
|
|
|
/* TWT Information not supported yet */
|
|
twt->control |= IEEE80211_TWT_CONTROL_RX_DISABLED;
|
|
|
|
drv_add_twt_setup(sdata->local, sdata, &sta->sta, twt);
|
|
out:
|
|
ieee80211_s1g_send_twt_setup(sdata, mgmt->sa, sdata->vif.addr, twt);
|
|
}
|
|
|
|
static void
|
|
ieee80211_s1g_rx_twt_teardown(struct ieee80211_sub_if_data *sdata,
|
|
struct sta_info *sta, struct sk_buff *skb)
|
|
{
|
|
struct ieee80211_mgmt *mgmt = (struct ieee80211_mgmt *)skb->data;
|
|
|
|
drv_twt_teardown_request(sdata->local, sdata, &sta->sta,
|
|
mgmt->u.action.s1g.variable[0]);
|
|
}
|
|
|
|
static void
|
|
ieee80211_s1g_tx_twt_setup_fail(struct ieee80211_sub_if_data *sdata,
|
|
struct sta_info *sta, struct sk_buff *skb)
|
|
{
|
|
struct ieee80211_mgmt *mgmt = (struct ieee80211_mgmt *)skb->data;
|
|
struct ieee80211_twt_setup *twt = (void *)mgmt->u.action.s1g.variable;
|
|
struct ieee80211_twt_params *twt_agrt = (void *)twt->params;
|
|
u8 flowid = le16_get_bits(twt_agrt->req_type,
|
|
IEEE80211_TWT_REQTYPE_FLOWID);
|
|
|
|
drv_twt_teardown_request(sdata->local, sdata, &sta->sta, flowid);
|
|
|
|
ieee80211_s1g_send_twt_teardown(sdata, mgmt->sa, sdata->vif.addr,
|
|
flowid);
|
|
}
|
|
|
|
void ieee80211_s1g_rx_twt_action(struct ieee80211_sub_if_data *sdata,
|
|
struct sk_buff *skb)
|
|
{
|
|
struct ieee80211_mgmt *mgmt = (struct ieee80211_mgmt *)skb->data;
|
|
struct ieee80211_local *local = sdata->local;
|
|
struct sta_info *sta;
|
|
|
|
lockdep_assert_wiphy(local->hw.wiphy);
|
|
|
|
sta = sta_info_get_bss(sdata, mgmt->sa);
|
|
if (!sta)
|
|
return;
|
|
|
|
switch (mgmt->u.action.action_code) {
|
|
case WLAN_S1G_TWT_SETUP:
|
|
ieee80211_s1g_rx_twt_setup(sdata, sta, skb);
|
|
break;
|
|
case WLAN_S1G_TWT_TEARDOWN:
|
|
ieee80211_s1g_rx_twt_teardown(sdata, sta, skb);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
void ieee80211_s1g_status_twt_action(struct ieee80211_sub_if_data *sdata,
|
|
struct sk_buff *skb)
|
|
{
|
|
struct ieee80211_mgmt *mgmt = (struct ieee80211_mgmt *)skb->data;
|
|
struct ieee80211_local *local = sdata->local;
|
|
struct sta_info *sta;
|
|
|
|
lockdep_assert_wiphy(local->hw.wiphy);
|
|
|
|
sta = sta_info_get_bss(sdata, mgmt->da);
|
|
if (!sta)
|
|
return;
|
|
|
|
switch (mgmt->u.action.action_code) {
|
|
case WLAN_S1G_TWT_SETUP:
|
|
/* process failed twt setup frames */
|
|
ieee80211_s1g_tx_twt_setup_fail(sdata, sta, skb);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
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)
|
|
{
|
|
struct ieee80211_sta_s1g_cap *s1g_cap = &link_sta->pub->s1g_cap;
|
|
|
|
memset(s1g_cap, 0, sizeof(*s1g_cap));
|
|
|
|
memcpy(s1g_cap->cap, s1g_cap_ie->capab_info, sizeof(s1g_cap->cap));
|
|
memcpy(s1g_cap->nss_mcs, s1g_cap_ie->supp_mcs_nss,
|
|
sizeof(s1g_cap->nss_mcs));
|
|
|
|
s1g_cap->s1g = true;
|
|
|
|
/* Maximum MPDU length is 1 bit for S1G */
|
|
if (s1g_cap->cap[3] & S1G_CAP3_MAX_MPDU_LEN) {
|
|
link_sta->pub->agg.max_amsdu_len =
|
|
IEEE80211_MAX_MPDU_LEN_VHT_7991;
|
|
} else {
|
|
link_sta->pub->agg.max_amsdu_len =
|
|
IEEE80211_MAX_MPDU_LEN_VHT_3895;
|
|
}
|
|
|
|
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);
|
|
}
|