mirror of
git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
synced 2025-08-05 16:54:27 +00:00

add support for remain on channel on BSS vif for iwlmld. Signed-off-by: Pagadala Yesu Anjaneyulu <pagadala.yesu.anjaneyulu@intel.com> Link: https://patch.msgid.link/20250509104454.2582160-14-miriam.rachel.korenblit@intel.com Signed-off-by: Miri Korenblit <miriam.rachel.korenblit@intel.com>
1321 lines
36 KiB
C
1321 lines
36 KiB
C
// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause
|
|
/*
|
|
* Copyright (C) 2024-2025 Intel Corporation
|
|
*/
|
|
|
|
#include <linux/ieee80211.h>
|
|
#include <kunit/static_stub.h>
|
|
|
|
#include "sta.h"
|
|
#include "hcmd.h"
|
|
#include "iface.h"
|
|
#include "mlo.h"
|
|
#include "key.h"
|
|
#include "agg.h"
|
|
#include "tlc.h"
|
|
#include "fw/api/sta.h"
|
|
#include "fw/api/mac.h"
|
|
#include "fw/api/rx.h"
|
|
|
|
int iwl_mld_fw_sta_id_from_link_sta(struct iwl_mld *mld,
|
|
struct ieee80211_link_sta *link_sta)
|
|
{
|
|
struct iwl_mld_link_sta *mld_link_sta;
|
|
|
|
/* This function should only be used with the wiphy lock held,
|
|
* In other cases, it is not guaranteed that the link_sta will exist
|
|
* in the driver too, and it is checked here.
|
|
*/
|
|
lockdep_assert_wiphy(mld->wiphy);
|
|
|
|
/* This is not meant to be called with a NULL pointer */
|
|
if (WARN_ON(!link_sta))
|
|
return -ENOENT;
|
|
|
|
mld_link_sta = iwl_mld_link_sta_from_mac80211(link_sta);
|
|
if (!mld_link_sta) {
|
|
WARN_ON(!iwl_mld_error_before_recovery(mld));
|
|
return -ENOENT;
|
|
}
|
|
|
|
return mld_link_sta->fw_id;
|
|
}
|
|
|
|
static void
|
|
iwl_mld_fill_ampdu_size_and_dens(struct ieee80211_link_sta *link_sta,
|
|
struct ieee80211_bss_conf *link,
|
|
__le32 *tx_ampdu_max_size,
|
|
__le32 *tx_ampdu_spacing)
|
|
{
|
|
u32 agg_size = 0, mpdu_dens = 0;
|
|
|
|
if (WARN_ON(!link_sta || !link))
|
|
return;
|
|
|
|
/* Note that we always use only legacy & highest supported PPDUs, so
|
|
* of Draft P802.11be D.30 Table 10-12a--Fields used for calculating
|
|
* the maximum A-MPDU size of various PPDU types in different bands,
|
|
* we only need to worry about the highest supported PPDU type here.
|
|
*/
|
|
|
|
if (link_sta->ht_cap.ht_supported) {
|
|
agg_size = link_sta->ht_cap.ampdu_factor;
|
|
mpdu_dens = link_sta->ht_cap.ampdu_density;
|
|
}
|
|
|
|
if (link->chanreq.oper.chan->band == NL80211_BAND_6GHZ) {
|
|
/* overwrite HT values on 6 GHz */
|
|
mpdu_dens =
|
|
le16_get_bits(link_sta->he_6ghz_capa.capa,
|
|
IEEE80211_HE_6GHZ_CAP_MIN_MPDU_START);
|
|
agg_size =
|
|
le16_get_bits(link_sta->he_6ghz_capa.capa,
|
|
IEEE80211_HE_6GHZ_CAP_MAX_AMPDU_LEN_EXP);
|
|
} else if (link_sta->vht_cap.vht_supported) {
|
|
/* if VHT supported overwrite HT value */
|
|
agg_size =
|
|
u32_get_bits(link_sta->vht_cap.cap,
|
|
IEEE80211_VHT_CAP_MAX_A_MPDU_LENGTH_EXPONENT_MASK);
|
|
}
|
|
|
|
/* D6.0 10.12.2 A-MPDU length limit rules
|
|
* A STA indicates the maximum length of the A-MPDU preEOF padding
|
|
* that it can receive in an HE PPDU in the Maximum A-MPDU Length
|
|
* Exponent field in its HT Capabilities, VHT Capabilities,
|
|
* and HE 6 GHz Band Capabilities elements (if present) and the
|
|
* Maximum AMPDU Length Exponent Extension field in its HE
|
|
* Capabilities element
|
|
*/
|
|
if (link_sta->he_cap.has_he)
|
|
agg_size +=
|
|
u8_get_bits(link_sta->he_cap.he_cap_elem.mac_cap_info[3],
|
|
IEEE80211_HE_MAC_CAP3_MAX_AMPDU_LEN_EXP_MASK);
|
|
|
|
if (link_sta->eht_cap.has_eht)
|
|
agg_size +=
|
|
u8_get_bits(link_sta->eht_cap.eht_cap_elem.mac_cap_info[1],
|
|
IEEE80211_EHT_MAC_CAP1_MAX_AMPDU_LEN_MASK);
|
|
|
|
/* Limit to max A-MPDU supported by FW */
|
|
agg_size = min_t(u32, agg_size,
|
|
STA_FLG_MAX_AGG_SIZE_4M >> STA_FLG_MAX_AGG_SIZE_SHIFT);
|
|
|
|
*tx_ampdu_max_size = cpu_to_le32(agg_size);
|
|
*tx_ampdu_spacing = cpu_to_le32(mpdu_dens);
|
|
}
|
|
|
|
static u8 iwl_mld_get_uapsd_acs(struct ieee80211_sta *sta)
|
|
{
|
|
u8 uapsd_acs = 0;
|
|
|
|
if (sta->uapsd_queues & IEEE80211_WMM_IE_STA_QOSINFO_AC_BK)
|
|
uapsd_acs |= BIT(AC_BK);
|
|
if (sta->uapsd_queues & IEEE80211_WMM_IE_STA_QOSINFO_AC_BE)
|
|
uapsd_acs |= BIT(AC_BE);
|
|
if (sta->uapsd_queues & IEEE80211_WMM_IE_STA_QOSINFO_AC_VI)
|
|
uapsd_acs |= BIT(AC_VI);
|
|
if (sta->uapsd_queues & IEEE80211_WMM_IE_STA_QOSINFO_AC_VO)
|
|
uapsd_acs |= BIT(AC_VO);
|
|
|
|
return uapsd_acs | uapsd_acs << 4;
|
|
}
|
|
|
|
static u8 iwl_mld_he_get_ppe_val(u8 *ppe, u8 ppe_pos_bit)
|
|
{
|
|
u8 byte_num = ppe_pos_bit / 8;
|
|
u8 bit_num = ppe_pos_bit % 8;
|
|
u8 residue_bits;
|
|
u8 res;
|
|
|
|
if (bit_num <= 5)
|
|
return (ppe[byte_num] >> bit_num) &
|
|
(BIT(IEEE80211_PPE_THRES_INFO_PPET_SIZE) - 1);
|
|
|
|
/* If bit_num > 5, we have to combine bits with next byte.
|
|
* Calculate how many bits we need to take from current byte (called
|
|
* here "residue_bits"), and add them to bits from next byte.
|
|
*/
|
|
|
|
residue_bits = 8 - bit_num;
|
|
|
|
res = (ppe[byte_num + 1] &
|
|
(BIT(IEEE80211_PPE_THRES_INFO_PPET_SIZE - residue_bits) - 1)) <<
|
|
residue_bits;
|
|
res += (ppe[byte_num] >> bit_num) & (BIT(residue_bits) - 1);
|
|
|
|
return res;
|
|
}
|
|
|
|
static void iwl_mld_parse_ppe(struct iwl_mld *mld,
|
|
struct iwl_he_pkt_ext_v2 *pkt_ext, u8 nss,
|
|
u8 ru_index_bitmap, u8 *ppe, u8 ppe_pos_bit,
|
|
bool inheritance)
|
|
{
|
|
/* FW currently supports only nss == MAX_HE_SUPP_NSS
|
|
*
|
|
* If nss > MAX: we can ignore values we don't support
|
|
* If nss < MAX: we can set zeros in other streams
|
|
*/
|
|
if (nss > MAX_HE_SUPP_NSS) {
|
|
IWL_DEBUG_INFO(mld, "Got NSS = %d - trimming to %d\n", nss,
|
|
MAX_HE_SUPP_NSS);
|
|
nss = MAX_HE_SUPP_NSS;
|
|
}
|
|
|
|
for (int i = 0; i < nss; i++) {
|
|
u8 ru_index_tmp = ru_index_bitmap << 1;
|
|
u8 low_th = IWL_HE_PKT_EXT_NONE, high_th = IWL_HE_PKT_EXT_NONE;
|
|
|
|
for (u8 bw = 0;
|
|
bw < ARRAY_SIZE(pkt_ext->pkt_ext_qam_th[i]);
|
|
bw++) {
|
|
ru_index_tmp >>= 1;
|
|
|
|
/* According to the 11be spec, if for a specific BW the PPE Thresholds
|
|
* isn't present - it should inherit the thresholds from the last
|
|
* BW for which we had PPE Thresholds. In 11ax though, we don't have
|
|
* this inheritance - continue in this case
|
|
*/
|
|
if (!(ru_index_tmp & 1)) {
|
|
if (inheritance)
|
|
goto set_thresholds;
|
|
else
|
|
continue;
|
|
}
|
|
|
|
high_th = iwl_mld_he_get_ppe_val(ppe, ppe_pos_bit);
|
|
ppe_pos_bit += IEEE80211_PPE_THRES_INFO_PPET_SIZE;
|
|
low_th = iwl_mld_he_get_ppe_val(ppe, ppe_pos_bit);
|
|
ppe_pos_bit += IEEE80211_PPE_THRES_INFO_PPET_SIZE;
|
|
|
|
set_thresholds:
|
|
pkt_ext->pkt_ext_qam_th[i][bw][0] = low_th;
|
|
pkt_ext->pkt_ext_qam_th[i][bw][1] = high_th;
|
|
}
|
|
}
|
|
}
|
|
|
|
static void iwl_mld_set_pkt_ext_from_he_ppe(struct iwl_mld *mld,
|
|
struct ieee80211_link_sta *link_sta,
|
|
struct iwl_he_pkt_ext_v2 *pkt_ext,
|
|
bool inheritance)
|
|
{
|
|
u8 nss = (link_sta->he_cap.ppe_thres[0] &
|
|
IEEE80211_PPE_THRES_NSS_MASK) + 1;
|
|
u8 *ppe = &link_sta->he_cap.ppe_thres[0];
|
|
u8 ru_index_bitmap =
|
|
u8_get_bits(*ppe,
|
|
IEEE80211_PPE_THRES_RU_INDEX_BITMASK_MASK);
|
|
/* Starting after PPE header */
|
|
u8 ppe_pos_bit = IEEE80211_HE_PPE_THRES_INFO_HEADER_SIZE;
|
|
|
|
iwl_mld_parse_ppe(mld, pkt_ext, nss, ru_index_bitmap, ppe, ppe_pos_bit,
|
|
inheritance);
|
|
}
|
|
|
|
static int
|
|
iwl_mld_set_pkt_ext_from_nominal_padding(struct iwl_he_pkt_ext_v2 *pkt_ext,
|
|
u8 nominal_padding)
|
|
{
|
|
int low_th = -1;
|
|
int high_th = -1;
|
|
|
|
/* all the macros are the same for EHT and HE */
|
|
switch (nominal_padding) {
|
|
case IEEE80211_EHT_PHY_CAP5_COMMON_NOMINAL_PKT_PAD_0US:
|
|
low_th = IWL_HE_PKT_EXT_NONE;
|
|
high_th = IWL_HE_PKT_EXT_NONE;
|
|
break;
|
|
case IEEE80211_EHT_PHY_CAP5_COMMON_NOMINAL_PKT_PAD_8US:
|
|
low_th = IWL_HE_PKT_EXT_BPSK;
|
|
high_th = IWL_HE_PKT_EXT_NONE;
|
|
break;
|
|
case IEEE80211_EHT_PHY_CAP5_COMMON_NOMINAL_PKT_PAD_16US:
|
|
case IEEE80211_EHT_PHY_CAP5_COMMON_NOMINAL_PKT_PAD_20US:
|
|
low_th = IWL_HE_PKT_EXT_NONE;
|
|
high_th = IWL_HE_PKT_EXT_BPSK;
|
|
break;
|
|
}
|
|
|
|
if (low_th < 0 || high_th < 0)
|
|
return -EINVAL;
|
|
|
|
/* Set the PPE thresholds accordingly */
|
|
for (int i = 0; i < MAX_HE_SUPP_NSS; i++) {
|
|
for (u8 bw = 0;
|
|
bw < ARRAY_SIZE(pkt_ext->pkt_ext_qam_th[i]);
|
|
bw++) {
|
|
pkt_ext->pkt_ext_qam_th[i][bw][0] = low_th;
|
|
pkt_ext->pkt_ext_qam_th[i][bw][1] = high_th;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void iwl_mld_get_optimal_ppe_info(struct iwl_he_pkt_ext_v2 *pkt_ext,
|
|
u8 nominal_padding)
|
|
{
|
|
for (int i = 0; i < MAX_HE_SUPP_NSS; i++) {
|
|
for (u8 bw = 0; bw < ARRAY_SIZE(pkt_ext->pkt_ext_qam_th[i]);
|
|
bw++) {
|
|
u8 *qam_th = &pkt_ext->pkt_ext_qam_th[i][bw][0];
|
|
|
|
if (nominal_padding >
|
|
IEEE80211_EHT_PHY_CAP5_COMMON_NOMINAL_PKT_PAD_8US &&
|
|
qam_th[1] == IWL_HE_PKT_EXT_NONE)
|
|
qam_th[1] = IWL_HE_PKT_EXT_4096QAM;
|
|
else if (nominal_padding ==
|
|
IEEE80211_EHT_PHY_CAP5_COMMON_NOMINAL_PKT_PAD_8US &&
|
|
qam_th[0] == IWL_HE_PKT_EXT_NONE &&
|
|
qam_th[1] == IWL_HE_PKT_EXT_NONE)
|
|
qam_th[0] = IWL_HE_PKT_EXT_4096QAM;
|
|
}
|
|
}
|
|
}
|
|
|
|
static void iwl_mld_fill_pkt_ext(struct iwl_mld *mld,
|
|
struct ieee80211_link_sta *link_sta,
|
|
struct iwl_he_pkt_ext_v2 *pkt_ext)
|
|
{
|
|
if (WARN_ON(!link_sta))
|
|
return;
|
|
|
|
/* Initialize the PPE thresholds to "None" (7), as described in Table
|
|
* 9-262ac of 80211.ax/D3.0.
|
|
*/
|
|
memset(pkt_ext, IWL_HE_PKT_EXT_NONE, sizeof(*pkt_ext));
|
|
|
|
if (link_sta->eht_cap.has_eht) {
|
|
u8 nominal_padding =
|
|
u8_get_bits(link_sta->eht_cap.eht_cap_elem.phy_cap_info[5],
|
|
IEEE80211_EHT_PHY_CAP5_COMMON_NOMINAL_PKT_PAD_MASK);
|
|
|
|
/* If PPE Thresholds exists, parse them into a FW-familiar
|
|
* format.
|
|
*/
|
|
if (link_sta->eht_cap.eht_cap_elem.phy_cap_info[5] &
|
|
IEEE80211_EHT_PHY_CAP5_PPE_THRESHOLD_PRESENT) {
|
|
u8 nss = (link_sta->eht_cap.eht_ppe_thres[0] &
|
|
IEEE80211_EHT_PPE_THRES_NSS_MASK) + 1;
|
|
u8 *ppe = &link_sta->eht_cap.eht_ppe_thres[0];
|
|
u8 ru_index_bitmap =
|
|
u16_get_bits(*ppe,
|
|
IEEE80211_EHT_PPE_THRES_RU_INDEX_BITMASK_MASK);
|
|
/* Starting after PPE header */
|
|
u8 ppe_pos_bit = IEEE80211_EHT_PPE_THRES_INFO_HEADER_SIZE;
|
|
|
|
iwl_mld_parse_ppe(mld, pkt_ext, nss, ru_index_bitmap,
|
|
ppe, ppe_pos_bit, true);
|
|
/* EHT PPE Thresholds doesn't exist - set the API according to
|
|
* HE PPE Tresholds
|
|
*/
|
|
} else if (link_sta->he_cap.he_cap_elem.phy_cap_info[6] &
|
|
IEEE80211_HE_PHY_CAP6_PPE_THRESHOLD_PRESENT) {
|
|
/* Even though HE Capabilities IE doesn't contain PPE
|
|
* Thresholds for BW 320Mhz, thresholds for this BW will
|
|
* be filled in with the same values as 160Mhz, due to
|
|
* the inheritance, as required.
|
|
*/
|
|
iwl_mld_set_pkt_ext_from_he_ppe(mld, link_sta, pkt_ext,
|
|
true);
|
|
|
|
/* According to the requirements, for MCSs 12-13 the
|
|
* maximum value between HE PPE Threshold and Common
|
|
* Nominal Packet Padding needs to be taken
|
|
*/
|
|
iwl_mld_get_optimal_ppe_info(pkt_ext, nominal_padding);
|
|
|
|
/* if PPE Thresholds doesn't present in both EHT IE and HE IE -
|
|
* take the Thresholds from Common Nominal Packet Padding field
|
|
*/
|
|
} else {
|
|
iwl_mld_set_pkt_ext_from_nominal_padding(pkt_ext,
|
|
nominal_padding);
|
|
}
|
|
} else if (link_sta->he_cap.has_he) {
|
|
/* If PPE Thresholds exist, parse them into a FW-familiar format. */
|
|
if (link_sta->he_cap.he_cap_elem.phy_cap_info[6] &
|
|
IEEE80211_HE_PHY_CAP6_PPE_THRESHOLD_PRESENT) {
|
|
iwl_mld_set_pkt_ext_from_he_ppe(mld, link_sta, pkt_ext,
|
|
false);
|
|
/* PPE Thresholds doesn't exist - set the API PPE values
|
|
* according to Common Nominal Packet Padding field.
|
|
*/
|
|
} else {
|
|
u8 nominal_padding =
|
|
u8_get_bits(link_sta->he_cap.he_cap_elem.phy_cap_info[9],
|
|
IEEE80211_HE_PHY_CAP9_NOMINAL_PKT_PADDING_MASK);
|
|
if (nominal_padding != IEEE80211_HE_PHY_CAP9_NOMINAL_PKT_PADDING_RESERVED)
|
|
iwl_mld_set_pkt_ext_from_nominal_padding(pkt_ext,
|
|
nominal_padding);
|
|
}
|
|
}
|
|
|
|
for (int i = 0; i < MAX_HE_SUPP_NSS; i++) {
|
|
for (int bw = 0;
|
|
bw < ARRAY_SIZE(*pkt_ext->pkt_ext_qam_th[i]);
|
|
bw++) {
|
|
u8 *qam_th =
|
|
&pkt_ext->pkt_ext_qam_th[i][bw][0];
|
|
|
|
IWL_DEBUG_HT(mld,
|
|
"PPE table: nss[%d] bw[%d] PPET8 = %d, PPET16 = %d\n",
|
|
i, bw, qam_th[0], qam_th[1]);
|
|
}
|
|
}
|
|
}
|
|
|
|
static u32 iwl_mld_get_htc_flags(struct ieee80211_link_sta *link_sta)
|
|
{
|
|
u8 *mac_cap_info =
|
|
&link_sta->he_cap.he_cap_elem.mac_cap_info[0];
|
|
u32 htc_flags = 0;
|
|
|
|
if (mac_cap_info[0] & IEEE80211_HE_MAC_CAP0_HTC_HE)
|
|
htc_flags |= IWL_HE_HTC_SUPPORT;
|
|
if ((mac_cap_info[1] & IEEE80211_HE_MAC_CAP1_LINK_ADAPTATION) ||
|
|
(mac_cap_info[2] & IEEE80211_HE_MAC_CAP2_LINK_ADAPTATION)) {
|
|
u8 link_adap =
|
|
((mac_cap_info[2] &
|
|
IEEE80211_HE_MAC_CAP2_LINK_ADAPTATION) << 1) +
|
|
(mac_cap_info[1] &
|
|
IEEE80211_HE_MAC_CAP1_LINK_ADAPTATION);
|
|
|
|
if (link_adap == 2)
|
|
htc_flags |=
|
|
IWL_HE_HTC_LINK_ADAP_UNSOLICITED;
|
|
else if (link_adap == 3)
|
|
htc_flags |= IWL_HE_HTC_LINK_ADAP_BOTH;
|
|
}
|
|
if (mac_cap_info[2] & IEEE80211_HE_MAC_CAP2_BSR)
|
|
htc_flags |= IWL_HE_HTC_BSR_SUPP;
|
|
if (mac_cap_info[3] & IEEE80211_HE_MAC_CAP3_OMI_CONTROL)
|
|
htc_flags |= IWL_HE_HTC_OMI_SUPP;
|
|
if (mac_cap_info[4] & IEEE80211_HE_MAC_CAP4_BQR)
|
|
htc_flags |= IWL_HE_HTC_BQR_SUPP;
|
|
|
|
return htc_flags;
|
|
}
|
|
|
|
static int iwl_mld_send_sta_cmd(struct iwl_mld *mld,
|
|
const struct iwl_sta_cfg_cmd *cmd)
|
|
{
|
|
u32 cmd_id = WIDE_ID(MAC_CONF_GROUP, STA_CONFIG_CMD);
|
|
int cmd_len = iwl_fw_lookup_cmd_ver(mld->fw, cmd_id, 0) > 1 ?
|
|
sizeof(*cmd) :
|
|
sizeof(struct iwl_sta_cfg_cmd_v1);
|
|
int ret = iwl_mld_send_cmd_pdu(mld, cmd_id, cmd, cmd_len);
|
|
if (ret)
|
|
IWL_ERR(mld, "STA_CONFIG_CMD send failed, ret=0x%x\n", ret);
|
|
return ret;
|
|
}
|
|
|
|
static int
|
|
iwl_mld_add_modify_sta_cmd(struct iwl_mld *mld,
|
|
struct ieee80211_link_sta *link_sta)
|
|
{
|
|
struct ieee80211_sta *sta = link_sta->sta;
|
|
struct iwl_mld_sta *mld_sta = iwl_mld_sta_from_mac80211(sta);
|
|
struct ieee80211_bss_conf *link;
|
|
struct iwl_mld_link *mld_link;
|
|
struct iwl_sta_cfg_cmd cmd = {};
|
|
int fw_id = iwl_mld_fw_sta_id_from_link_sta(mld, link_sta);
|
|
|
|
lockdep_assert_wiphy(mld->wiphy);
|
|
|
|
link = link_conf_dereference_protected(mld_sta->vif,
|
|
link_sta->link_id);
|
|
|
|
mld_link = iwl_mld_link_from_mac80211(link);
|
|
|
|
if (WARN_ON(!link || !mld_link) || fw_id < 0)
|
|
return -EINVAL;
|
|
|
|
cmd.sta_id = cpu_to_le32(fw_id);
|
|
cmd.station_type = cpu_to_le32(mld_sta->sta_type);
|
|
cmd.link_id = cpu_to_le32(mld_link->fw_id);
|
|
|
|
memcpy(&cmd.peer_mld_address, sta->addr, ETH_ALEN);
|
|
memcpy(&cmd.peer_link_address, link_sta->addr, ETH_ALEN);
|
|
|
|
if (mld_sta->sta_state >= IEEE80211_STA_ASSOC)
|
|
cmd.assoc_id = cpu_to_le32(sta->aid);
|
|
|
|
if (sta->mfp || mld_sta->sta_state < IEEE80211_STA_AUTHORIZED)
|
|
cmd.mfp = cpu_to_le32(1);
|
|
|
|
switch (link_sta->rx_nss) {
|
|
case 1:
|
|
cmd.mimo = cpu_to_le32(0);
|
|
break;
|
|
case 2 ... 8:
|
|
cmd.mimo = cpu_to_le32(1);
|
|
break;
|
|
}
|
|
|
|
switch (link_sta->smps_mode) {
|
|
case IEEE80211_SMPS_AUTOMATIC:
|
|
case IEEE80211_SMPS_NUM_MODES:
|
|
WARN_ON(1);
|
|
break;
|
|
case IEEE80211_SMPS_STATIC:
|
|
/* override NSS */
|
|
cmd.mimo = cpu_to_le32(0);
|
|
break;
|
|
case IEEE80211_SMPS_DYNAMIC:
|
|
cmd.mimo_protection = cpu_to_le32(1);
|
|
break;
|
|
case IEEE80211_SMPS_OFF:
|
|
/* nothing */
|
|
break;
|
|
}
|
|
|
|
iwl_mld_fill_ampdu_size_and_dens(link_sta, link,
|
|
&cmd.tx_ampdu_max_size,
|
|
&cmd.tx_ampdu_spacing);
|
|
|
|
if (sta->wme) {
|
|
cmd.sp_length =
|
|
cpu_to_le32(sta->max_sp ? sta->max_sp * 2 : 128);
|
|
cmd.uapsd_acs = cpu_to_le32(iwl_mld_get_uapsd_acs(sta));
|
|
}
|
|
|
|
if (link_sta->he_cap.has_he) {
|
|
cmd.trig_rnd_alloc =
|
|
cpu_to_le32(link->uora_exists ? 1 : 0);
|
|
|
|
/* PPE Thresholds */
|
|
iwl_mld_fill_pkt_ext(mld, link_sta, &cmd.pkt_ext);
|
|
|
|
/* HTC flags */
|
|
cmd.htc_flags =
|
|
cpu_to_le32(iwl_mld_get_htc_flags(link_sta));
|
|
|
|
if (link_sta->he_cap.he_cap_elem.mac_cap_info[2] &
|
|
IEEE80211_HE_MAC_CAP2_ACK_EN)
|
|
cmd.ack_enabled = cpu_to_le32(1);
|
|
}
|
|
|
|
return iwl_mld_send_sta_cmd(mld, &cmd);
|
|
}
|
|
|
|
IWL_MLD_ALLOC_FN(link_sta, link_sta)
|
|
|
|
static int
|
|
iwl_mld_add_link_sta(struct iwl_mld *mld, struct ieee80211_link_sta *link_sta)
|
|
{
|
|
struct iwl_mld_sta *mld_sta = iwl_mld_sta_from_mac80211(link_sta->sta);
|
|
struct iwl_mld_link_sta *mld_link_sta;
|
|
int ret;
|
|
u8 fw_id;
|
|
|
|
lockdep_assert_wiphy(mld->wiphy);
|
|
|
|
/* We will fail to add it to the FW anyway */
|
|
if (iwl_mld_error_before_recovery(mld))
|
|
return -ENODEV;
|
|
|
|
mld_link_sta = iwl_mld_link_sta_from_mac80211(link_sta);
|
|
|
|
/* We need to preserve the fw sta ids during a restart, since the fw
|
|
* will recover SN/PN for them, this is why the mld_link_sta exists.
|
|
*/
|
|
if (mld_link_sta) {
|
|
/* But if we are not restarting, this is not OK */
|
|
WARN_ON(!mld->fw_status.in_hw_restart);
|
|
|
|
/* Avoid adding a STA that is already in FW to avoid an assert */
|
|
if (WARN_ON(mld_link_sta->in_fw))
|
|
return -EINVAL;
|
|
|
|
fw_id = mld_link_sta->fw_id;
|
|
goto add_to_fw;
|
|
}
|
|
|
|
/* Allocate a fw id and map it to the link_sta */
|
|
ret = iwl_mld_allocate_link_sta_fw_id(mld, &fw_id, link_sta);
|
|
if (ret)
|
|
return ret;
|
|
|
|
if (link_sta == &link_sta->sta->deflink) {
|
|
mld_link_sta = &mld_sta->deflink;
|
|
} else {
|
|
mld_link_sta = kzalloc(sizeof(*mld_link_sta), GFP_KERNEL);
|
|
if (!mld_link_sta)
|
|
return -ENOMEM;
|
|
}
|
|
|
|
mld_link_sta->fw_id = fw_id;
|
|
rcu_assign_pointer(mld_sta->link[link_sta->link_id], mld_link_sta);
|
|
|
|
add_to_fw:
|
|
ret = iwl_mld_add_modify_sta_cmd(mld, link_sta);
|
|
if (ret) {
|
|
RCU_INIT_POINTER(mld->fw_id_to_link_sta[fw_id], NULL);
|
|
RCU_INIT_POINTER(mld_sta->link[link_sta->link_id], NULL);
|
|
if (link_sta != &link_sta->sta->deflink)
|
|
kfree(mld_link_sta);
|
|
return ret;
|
|
}
|
|
mld_link_sta->in_fw = true;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int iwl_mld_rm_sta_from_fw(struct iwl_mld *mld, u8 fw_sta_id)
|
|
{
|
|
struct iwl_remove_sta_cmd cmd = {
|
|
.sta_id = cpu_to_le32(fw_sta_id),
|
|
};
|
|
int ret;
|
|
|
|
ret = iwl_mld_send_cmd_pdu(mld,
|
|
WIDE_ID(MAC_CONF_GROUP, STA_REMOVE_CMD),
|
|
&cmd);
|
|
if (ret)
|
|
IWL_ERR(mld, "Failed to remove station. Id=%d\n", fw_sta_id);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static void
|
|
iwl_mld_remove_link_sta(struct iwl_mld *mld,
|
|
struct ieee80211_link_sta *link_sta)
|
|
{
|
|
struct iwl_mld_sta *mld_sta = iwl_mld_sta_from_mac80211(link_sta->sta);
|
|
struct iwl_mld_link_sta *mld_link_sta =
|
|
iwl_mld_link_sta_from_mac80211(link_sta);
|
|
|
|
if (WARN_ON(!mld_link_sta))
|
|
return;
|
|
|
|
iwl_mld_rm_sta_from_fw(mld, mld_link_sta->fw_id);
|
|
mld_link_sta->in_fw = false;
|
|
|
|
/* Now that the STA doesn't exist in FW, we don't expect any new
|
|
* notifications for it. Cancel the ones that are already pending
|
|
*/
|
|
iwl_mld_cancel_notifications_of_object(mld, IWL_MLD_OBJECT_TYPE_STA,
|
|
mld_link_sta->fw_id);
|
|
|
|
/* This will not be done upon reconfig, so do it also when
|
|
* failed to remove from fw
|
|
*/
|
|
RCU_INIT_POINTER(mld->fw_id_to_link_sta[mld_link_sta->fw_id], NULL);
|
|
RCU_INIT_POINTER(mld_sta->link[link_sta->link_id], NULL);
|
|
if (mld_link_sta != &mld_sta->deflink)
|
|
kfree_rcu(mld_link_sta, rcu_head);
|
|
}
|
|
|
|
static void iwl_mld_set_max_amsdu_len(struct iwl_mld *mld,
|
|
struct ieee80211_link_sta *link_sta)
|
|
{
|
|
const struct ieee80211_sta_ht_cap *ht_cap = &link_sta->ht_cap;
|
|
|
|
/* For EHT, HE and VHT we can use the value as it was calculated by
|
|
* mac80211. For HT, mac80211 doesn't enforce to 4095, so force it
|
|
* here
|
|
*/
|
|
if (link_sta->eht_cap.has_eht || link_sta->he_cap.has_he ||
|
|
link_sta->vht_cap.vht_supported ||
|
|
!ht_cap->ht_supported ||
|
|
!(ht_cap->cap & IEEE80211_HT_CAP_MAX_AMSDU))
|
|
return;
|
|
|
|
link_sta->agg.max_amsdu_len = IEEE80211_MAX_MPDU_LEN_HT_BA;
|
|
ieee80211_sta_recalc_aggregates(link_sta->sta);
|
|
}
|
|
|
|
int iwl_mld_update_all_link_stations(struct iwl_mld *mld,
|
|
struct ieee80211_sta *sta)
|
|
{
|
|
struct iwl_mld_sta *mld_sta = iwl_mld_sta_from_mac80211(sta);
|
|
struct ieee80211_link_sta *link_sta;
|
|
int link_id;
|
|
|
|
for_each_sta_active_link(mld_sta->vif, sta, link_sta, link_id) {
|
|
int ret = iwl_mld_add_modify_sta_cmd(mld, link_sta);
|
|
|
|
if (ret)
|
|
return ret;
|
|
|
|
if (mld_sta->sta_state == IEEE80211_STA_ASSOC)
|
|
iwl_mld_set_max_amsdu_len(mld, link_sta);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static void iwl_mld_destroy_sta(struct ieee80211_sta *sta)
|
|
{
|
|
struct iwl_mld_sta *mld_sta = iwl_mld_sta_from_mac80211(sta);
|
|
|
|
kfree(mld_sta->dup_data);
|
|
kfree(mld_sta->mpdu_counters);
|
|
}
|
|
|
|
static int
|
|
iwl_mld_alloc_dup_data(struct iwl_mld *mld, struct iwl_mld_sta *mld_sta)
|
|
{
|
|
struct iwl_mld_rxq_dup_data *dup_data;
|
|
|
|
if (mld->fw_status.in_hw_restart)
|
|
return 0;
|
|
|
|
dup_data = kcalloc(mld->trans->info.num_rxqs, sizeof(*dup_data),
|
|
GFP_KERNEL);
|
|
if (!dup_data)
|
|
return -ENOMEM;
|
|
|
|
/* Initialize all the last_seq values to 0xffff which can never
|
|
* compare equal to the frame's seq_ctrl in the check in
|
|
* iwl_mld_is_dup() since the lower 4 bits are the fragment
|
|
* number and fragmented packets don't reach that function.
|
|
*
|
|
* This thus allows receiving a packet with seqno 0 and the
|
|
* retry bit set as the very first packet on a new TID.
|
|
*/
|
|
for (int q = 0; q < mld->trans->info.num_rxqs; q++)
|
|
memset(dup_data[q].last_seq, 0xff,
|
|
sizeof(dup_data[q].last_seq));
|
|
mld_sta->dup_data = dup_data;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void iwl_mld_alloc_mpdu_counters(struct iwl_mld *mld,
|
|
struct ieee80211_sta *sta)
|
|
{
|
|
struct iwl_mld_sta *mld_sta = iwl_mld_sta_from_mac80211(sta);
|
|
struct ieee80211_vif *vif = mld_sta->vif;
|
|
|
|
if (mld->fw_status.in_hw_restart)
|
|
return;
|
|
|
|
/* MPDUs are counted only when EMLSR is possible */
|
|
if (ieee80211_vif_type_p2p(vif) != NL80211_IFTYPE_STATION ||
|
|
sta->tdls || !ieee80211_vif_is_mld(vif))
|
|
return;
|
|
|
|
mld_sta->mpdu_counters = kcalloc(mld->trans->info.num_rxqs,
|
|
sizeof(*mld_sta->mpdu_counters),
|
|
GFP_KERNEL);
|
|
if (!mld_sta->mpdu_counters)
|
|
return;
|
|
|
|
for (int q = 0; q < mld->trans->info.num_rxqs; q++)
|
|
spin_lock_init(&mld_sta->mpdu_counters[q].lock);
|
|
}
|
|
|
|
static int
|
|
iwl_mld_init_sta(struct iwl_mld *mld, struct ieee80211_sta *sta,
|
|
struct ieee80211_vif *vif, enum iwl_fw_sta_type type)
|
|
{
|
|
struct iwl_mld_sta *mld_sta = iwl_mld_sta_from_mac80211(sta);
|
|
|
|
mld_sta->vif = vif;
|
|
mld_sta->sta_type = type;
|
|
mld_sta->mld = mld;
|
|
|
|
if (!mld->fw_status.in_hw_restart)
|
|
for (int i = 0; i < ARRAY_SIZE(sta->txq); i++)
|
|
iwl_mld_init_txq(iwl_mld_txq_from_mac80211(sta->txq[i]));
|
|
|
|
iwl_mld_alloc_mpdu_counters(mld, sta);
|
|
|
|
iwl_mld_toggle_tx_ant(mld, &mld_sta->data_tx_ant);
|
|
|
|
return iwl_mld_alloc_dup_data(mld, mld_sta);
|
|
}
|
|
|
|
int iwl_mld_add_sta(struct iwl_mld *mld, struct ieee80211_sta *sta,
|
|
struct ieee80211_vif *vif, enum iwl_fw_sta_type type)
|
|
{
|
|
struct iwl_mld_sta *mld_sta = iwl_mld_sta_from_mac80211(sta);
|
|
struct ieee80211_link_sta *link_sta;
|
|
int link_id;
|
|
int ret;
|
|
|
|
ret = iwl_mld_init_sta(mld, sta, vif, type);
|
|
if (ret)
|
|
return ret;
|
|
|
|
/* We could have add only the deflink link_sta, but it will not work
|
|
* in the restart case if the single link that is active during
|
|
* reconfig is not the deflink one.
|
|
*/
|
|
for_each_sta_active_link(mld_sta->vif, sta, link_sta, link_id) {
|
|
ret = iwl_mld_add_link_sta(mld, link_sta);
|
|
if (ret)
|
|
goto destroy_sta;
|
|
}
|
|
|
|
return 0;
|
|
|
|
destroy_sta:
|
|
iwl_mld_destroy_sta(sta);
|
|
|
|
return ret;
|
|
}
|
|
|
|
void iwl_mld_flush_sta_txqs(struct iwl_mld *mld, struct ieee80211_sta *sta)
|
|
{
|
|
struct iwl_mld_sta *mld_sta = iwl_mld_sta_from_mac80211(sta);
|
|
struct ieee80211_link_sta *link_sta;
|
|
int link_id;
|
|
|
|
for_each_sta_active_link(mld_sta->vif, sta, link_sta, link_id) {
|
|
int fw_sta_id = iwl_mld_fw_sta_id_from_link_sta(mld, link_sta);
|
|
|
|
if (fw_sta_id < 0)
|
|
continue;
|
|
|
|
iwl_mld_flush_link_sta_txqs(mld, fw_sta_id);
|
|
}
|
|
}
|
|
|
|
void iwl_mld_wait_sta_txqs_empty(struct iwl_mld *mld, struct ieee80211_sta *sta)
|
|
{
|
|
/* Avoid a warning in iwl_trans_wait_txq_empty if are anyway on the way
|
|
* to a restart.
|
|
*/
|
|
if (iwl_mld_error_before_recovery(mld))
|
|
return;
|
|
|
|
for (int i = 0; i < ARRAY_SIZE(sta->txq); i++) {
|
|
struct iwl_mld_txq *mld_txq =
|
|
iwl_mld_txq_from_mac80211(sta->txq[i]);
|
|
|
|
if (!mld_txq->status.allocated)
|
|
continue;
|
|
|
|
iwl_trans_wait_txq_empty(mld->trans, mld_txq->fw_id);
|
|
}
|
|
}
|
|
|
|
void iwl_mld_remove_sta(struct iwl_mld *mld, struct ieee80211_sta *sta)
|
|
{
|
|
struct iwl_mld_sta *mld_sta = iwl_mld_sta_from_mac80211(sta);
|
|
struct ieee80211_vif *vif = mld_sta->vif;
|
|
struct ieee80211_link_sta *link_sta;
|
|
u8 link_id;
|
|
|
|
lockdep_assert_wiphy(mld->wiphy);
|
|
|
|
/* Tell the HW to flush the queues */
|
|
iwl_mld_flush_sta_txqs(mld, sta);
|
|
|
|
/* Wait for trans to empty its queues */
|
|
iwl_mld_wait_sta_txqs_empty(mld, sta);
|
|
|
|
/* Now we can remove the queues */
|
|
for (int i = 0; i < ARRAY_SIZE(sta->txq); i++)
|
|
iwl_mld_remove_txq(mld, sta->txq[i]);
|
|
|
|
for_each_sta_active_link(vif, sta, link_sta, link_id) {
|
|
/* Mac8011 will remove the groupwise keys after the sta is
|
|
* removed, but FW expects all the keys to be removed before
|
|
* the STA is, so remove them all here.
|
|
*/
|
|
if (vif->type == NL80211_IFTYPE_STATION && !sta->tdls)
|
|
iwl_mld_remove_ap_keys(mld, vif, sta, link_id);
|
|
|
|
/* Remove the link_sta */
|
|
iwl_mld_remove_link_sta(mld, link_sta);
|
|
}
|
|
|
|
iwl_mld_destroy_sta(sta);
|
|
}
|
|
|
|
u32 iwl_mld_fw_sta_id_mask(struct iwl_mld *mld, struct ieee80211_sta *sta)
|
|
{
|
|
struct ieee80211_vif *vif = iwl_mld_sta_from_mac80211(sta)->vif;
|
|
struct ieee80211_link_sta *link_sta;
|
|
unsigned int link_id;
|
|
u32 result = 0;
|
|
|
|
KUNIT_STATIC_STUB_REDIRECT(iwl_mld_fw_sta_id_mask, mld, sta);
|
|
|
|
/* This function should only be used with the wiphy lock held,
|
|
* In other cases, it is not guaranteed that the link_sta will exist
|
|
* in the driver too, and it is checked in
|
|
* iwl_mld_fw_sta_id_from_link_sta.
|
|
*/
|
|
lockdep_assert_wiphy(mld->wiphy);
|
|
|
|
for_each_sta_active_link(vif, sta, link_sta, link_id) {
|
|
int fw_id = iwl_mld_fw_sta_id_from_link_sta(mld, link_sta);
|
|
|
|
if (!(fw_id < 0))
|
|
result |= BIT(fw_id);
|
|
}
|
|
|
|
return result;
|
|
}
|
|
EXPORT_SYMBOL_IF_IWLWIFI_KUNIT(iwl_mld_fw_sta_id_mask);
|
|
|
|
static void iwl_mld_count_mpdu(struct ieee80211_link_sta *link_sta, int queue,
|
|
u32 count, bool tx)
|
|
{
|
|
struct iwl_mld_per_q_mpdu_counter *queue_counter;
|
|
struct iwl_mld_per_link_mpdu_counter *link_counter;
|
|
struct iwl_mld_vif *mld_vif;
|
|
struct iwl_mld_sta *mld_sta;
|
|
struct iwl_mld_link *mld_link;
|
|
struct iwl_mld *mld;
|
|
int total_mpdus = 0;
|
|
|
|
if (WARN_ON(!link_sta))
|
|
return;
|
|
|
|
mld_sta = iwl_mld_sta_from_mac80211(link_sta->sta);
|
|
if (!mld_sta->mpdu_counters)
|
|
return;
|
|
|
|
mld_vif = iwl_mld_vif_from_mac80211(mld_sta->vif);
|
|
mld_link = iwl_mld_link_dereference_check(mld_vif, link_sta->link_id);
|
|
|
|
if (WARN_ON_ONCE(!mld_link))
|
|
return;
|
|
|
|
queue_counter = &mld_sta->mpdu_counters[queue];
|
|
|
|
mld = mld_vif->mld;
|
|
|
|
/* If it the window is over, first clear the counters.
|
|
* When we are not blocked by TPT, the window is managed by check_tpt_wk
|
|
*/
|
|
if ((mld_vif->emlsr.blocked_reasons & IWL_MLD_EMLSR_BLOCKED_TPT) &&
|
|
time_is_before_jiffies(queue_counter->window_start_time +
|
|
IWL_MLD_TPT_COUNT_WINDOW)) {
|
|
memset(queue_counter->per_link, 0,
|
|
sizeof(queue_counter->per_link));
|
|
queue_counter->window_start_time = jiffies;
|
|
|
|
IWL_DEBUG_INFO(mld, "MPDU counters are cleared\n");
|
|
}
|
|
|
|
link_counter = &queue_counter->per_link[mld_link->fw_id];
|
|
|
|
spin_lock_bh(&queue_counter->lock);
|
|
|
|
/* Update the statistics for this TPT measurement window */
|
|
if (tx)
|
|
link_counter->tx += count;
|
|
else
|
|
link_counter->rx += count;
|
|
|
|
/*
|
|
* Next, evaluate whether we should queue an unblock,
|
|
* skip this if we are not blocked due to low throughput.
|
|
*/
|
|
if (!(mld_vif->emlsr.blocked_reasons & IWL_MLD_EMLSR_BLOCKED_TPT))
|
|
goto unlock;
|
|
|
|
for (int i = 0; i <= IWL_FW_MAX_LINK_ID; i++)
|
|
total_mpdus += tx ? queue_counter->per_link[i].tx :
|
|
queue_counter->per_link[i].rx;
|
|
|
|
/* Unblock is already queued if the threshold was reached before */
|
|
if (total_mpdus - count >= IWL_MLD_ENTER_EMLSR_TPT_THRESH)
|
|
goto unlock;
|
|
|
|
if (total_mpdus >= IWL_MLD_ENTER_EMLSR_TPT_THRESH)
|
|
wiphy_work_queue(mld->wiphy, &mld_vif->emlsr.unblock_tpt_wk);
|
|
|
|
unlock:
|
|
spin_unlock_bh(&queue_counter->lock);
|
|
}
|
|
|
|
/* must be called under rcu_read_lock() */
|
|
void iwl_mld_count_mpdu_rx(struct ieee80211_link_sta *link_sta, int queue,
|
|
u32 count)
|
|
{
|
|
iwl_mld_count_mpdu(link_sta, queue, count, false);
|
|
}
|
|
|
|
/* must be called under rcu_read_lock() */
|
|
void iwl_mld_count_mpdu_tx(struct ieee80211_link_sta *link_sta, u32 count)
|
|
{
|
|
/* use queue 0 for all TX */
|
|
iwl_mld_count_mpdu(link_sta, 0, count, true);
|
|
}
|
|
|
|
static int iwl_mld_allocate_internal_txq(struct iwl_mld *mld,
|
|
struct iwl_mld_int_sta *internal_sta,
|
|
u8 tid)
|
|
{
|
|
u32 sta_mask = BIT(internal_sta->sta_id);
|
|
int queue, size;
|
|
|
|
size = max_t(u32, IWL_MGMT_QUEUE_SIZE,
|
|
mld->trans->mac_cfg->base->min_txq_size);
|
|
|
|
queue = iwl_trans_txq_alloc(mld->trans, 0, sta_mask, tid, size,
|
|
IWL_WATCHDOG_DISABLED);
|
|
|
|
if (queue >= 0)
|
|
IWL_DEBUG_TX_QUEUES(mld,
|
|
"Enabling TXQ #%d for sta mask 0x%x tid %d\n",
|
|
queue, sta_mask, tid);
|
|
return queue;
|
|
}
|
|
|
|
static int iwl_mld_send_aux_sta_cmd(struct iwl_mld *mld,
|
|
const struct iwl_mld_int_sta *internal_sta)
|
|
{
|
|
struct iwl_aux_sta_cmd cmd = {
|
|
.sta_id = cpu_to_le32(internal_sta->sta_id),
|
|
/* TODO: CDB - properly set the lmac_id */
|
|
.lmac_id = cpu_to_le32(IWL_LMAC_24G_INDEX),
|
|
};
|
|
|
|
return iwl_mld_send_cmd_pdu(mld, WIDE_ID(MAC_CONF_GROUP, AUX_STA_CMD),
|
|
&cmd);
|
|
}
|
|
|
|
static int
|
|
iwl_mld_add_internal_sta_to_fw(struct iwl_mld *mld,
|
|
const struct iwl_mld_int_sta *internal_sta,
|
|
u8 fw_link_id,
|
|
const u8 *addr)
|
|
{
|
|
struct iwl_sta_cfg_cmd cmd = {};
|
|
|
|
if (internal_sta->sta_type == STATION_TYPE_AUX)
|
|
return iwl_mld_send_aux_sta_cmd(mld, internal_sta);
|
|
|
|
cmd.sta_id = cpu_to_le32((u8)internal_sta->sta_id);
|
|
cmd.link_id = cpu_to_le32(fw_link_id);
|
|
cmd.station_type = cpu_to_le32(internal_sta->sta_type);
|
|
|
|
/* FW doesn't allow to add a IGTK/BIGTK if the sta isn't marked as MFP.
|
|
* On the other hand, FW will never check this flag during RX since
|
|
* an AP/GO doesn't receive protected broadcast management frames.
|
|
* So, we can set it unconditionally.
|
|
*/
|
|
if (internal_sta->sta_type == STATION_TYPE_BCAST_MGMT)
|
|
cmd.mfp = cpu_to_le32(1);
|
|
|
|
if (addr) {
|
|
memcpy(cmd.peer_mld_address, addr, ETH_ALEN);
|
|
memcpy(cmd.peer_link_address, addr, ETH_ALEN);
|
|
}
|
|
|
|
return iwl_mld_send_sta_cmd(mld, &cmd);
|
|
}
|
|
|
|
static int iwl_mld_add_internal_sta(struct iwl_mld *mld,
|
|
struct iwl_mld_int_sta *internal_sta,
|
|
enum iwl_fw_sta_type sta_type,
|
|
u8 fw_link_id, const u8 *addr, u8 tid)
|
|
{
|
|
int ret, queue_id;
|
|
|
|
ret = iwl_mld_allocate_link_sta_fw_id(mld,
|
|
&internal_sta->sta_id,
|
|
ERR_PTR(-EINVAL));
|
|
if (ret)
|
|
return ret;
|
|
|
|
internal_sta->sta_type = sta_type;
|
|
|
|
ret = iwl_mld_add_internal_sta_to_fw(mld, internal_sta, fw_link_id,
|
|
addr);
|
|
if (ret)
|
|
goto err;
|
|
|
|
queue_id = iwl_mld_allocate_internal_txq(mld, internal_sta, tid);
|
|
if (queue_id < 0) {
|
|
iwl_mld_rm_sta_from_fw(mld, internal_sta->sta_id);
|
|
ret = queue_id;
|
|
goto err;
|
|
}
|
|
|
|
internal_sta->queue_id = queue_id;
|
|
|
|
return 0;
|
|
err:
|
|
iwl_mld_free_internal_sta(mld, internal_sta);
|
|
return ret;
|
|
}
|
|
|
|
int iwl_mld_add_bcast_sta(struct iwl_mld *mld,
|
|
struct ieee80211_vif *vif,
|
|
struct ieee80211_bss_conf *link)
|
|
{
|
|
struct iwl_mld_link *mld_link = iwl_mld_link_from_mac80211(link);
|
|
const u8 bcast_addr[] = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF};
|
|
const u8 *addr;
|
|
|
|
if (WARN_ON(!mld_link))
|
|
return -EINVAL;
|
|
|
|
if (WARN_ON(vif->type != NL80211_IFTYPE_AP &&
|
|
vif->type != NL80211_IFTYPE_ADHOC))
|
|
return -EINVAL;
|
|
|
|
addr = vif->type == NL80211_IFTYPE_ADHOC ? link->bssid : bcast_addr;
|
|
|
|
return iwl_mld_add_internal_sta(mld, &mld_link->bcast_sta,
|
|
STATION_TYPE_BCAST_MGMT,
|
|
mld_link->fw_id, addr,
|
|
IWL_MGMT_TID);
|
|
}
|
|
|
|
int iwl_mld_add_mcast_sta(struct iwl_mld *mld,
|
|
struct ieee80211_vif *vif,
|
|
struct ieee80211_bss_conf *link)
|
|
{
|
|
struct iwl_mld_link *mld_link = iwl_mld_link_from_mac80211(link);
|
|
const u8 mcast_addr[] = {0x03, 0x00, 0x00, 0x00, 0x00, 0x00};
|
|
|
|
if (WARN_ON(!mld_link))
|
|
return -EINVAL;
|
|
|
|
if (WARN_ON(vif->type != NL80211_IFTYPE_AP &&
|
|
vif->type != NL80211_IFTYPE_ADHOC))
|
|
return -EINVAL;
|
|
|
|
return iwl_mld_add_internal_sta(mld, &mld_link->mcast_sta,
|
|
STATION_TYPE_MCAST,
|
|
mld_link->fw_id, mcast_addr, 0);
|
|
}
|
|
|
|
int iwl_mld_add_aux_sta(struct iwl_mld *mld,
|
|
struct iwl_mld_int_sta *internal_sta)
|
|
{
|
|
return iwl_mld_add_internal_sta(mld, internal_sta, STATION_TYPE_AUX,
|
|
0, NULL, IWL_MAX_TID_COUNT);
|
|
}
|
|
|
|
int iwl_mld_add_mon_sta(struct iwl_mld *mld,
|
|
struct ieee80211_vif *vif,
|
|
struct ieee80211_bss_conf *link)
|
|
{
|
|
struct iwl_mld_link *mld_link = iwl_mld_link_from_mac80211(link);
|
|
|
|
if (WARN_ON(!mld_link))
|
|
return -EINVAL;
|
|
|
|
if (WARN_ON(vif->type != NL80211_IFTYPE_MONITOR))
|
|
return -EINVAL;
|
|
|
|
return iwl_mld_add_internal_sta(mld, &mld_link->mon_sta,
|
|
STATION_TYPE_BCAST_MGMT,
|
|
mld_link->fw_id, NULL,
|
|
IWL_MAX_TID_COUNT);
|
|
}
|
|
|
|
static void iwl_mld_remove_internal_sta(struct iwl_mld *mld,
|
|
struct iwl_mld_int_sta *internal_sta,
|
|
bool flush, u8 tid)
|
|
{
|
|
if (WARN_ON_ONCE(internal_sta->sta_id == IWL_INVALID_STA ||
|
|
internal_sta->queue_id == IWL_MLD_INVALID_QUEUE))
|
|
return;
|
|
|
|
if (flush)
|
|
iwl_mld_flush_link_sta_txqs(mld, internal_sta->sta_id);
|
|
|
|
iwl_mld_free_txq(mld, BIT(internal_sta->sta_id),
|
|
tid, internal_sta->queue_id);
|
|
|
|
iwl_mld_rm_sta_from_fw(mld, internal_sta->sta_id);
|
|
|
|
iwl_mld_free_internal_sta(mld, internal_sta);
|
|
}
|
|
|
|
void iwl_mld_remove_bcast_sta(struct iwl_mld *mld,
|
|
struct ieee80211_vif *vif,
|
|
struct ieee80211_bss_conf *link)
|
|
{
|
|
struct iwl_mld_link *mld_link = iwl_mld_link_from_mac80211(link);
|
|
|
|
if (WARN_ON(!mld_link))
|
|
return;
|
|
|
|
if (WARN_ON(vif->type != NL80211_IFTYPE_AP &&
|
|
vif->type != NL80211_IFTYPE_ADHOC))
|
|
return;
|
|
|
|
iwl_mld_remove_internal_sta(mld, &mld_link->bcast_sta, true,
|
|
IWL_MGMT_TID);
|
|
}
|
|
|
|
void iwl_mld_remove_mcast_sta(struct iwl_mld *mld,
|
|
struct ieee80211_vif *vif,
|
|
struct ieee80211_bss_conf *link)
|
|
{
|
|
struct iwl_mld_link *mld_link = iwl_mld_link_from_mac80211(link);
|
|
|
|
if (WARN_ON(!mld_link))
|
|
return;
|
|
|
|
if (WARN_ON(vif->type != NL80211_IFTYPE_AP &&
|
|
vif->type != NL80211_IFTYPE_ADHOC))
|
|
return;
|
|
|
|
iwl_mld_remove_internal_sta(mld, &mld_link->mcast_sta, true, 0);
|
|
}
|
|
|
|
void iwl_mld_remove_aux_sta(struct iwl_mld *mld,
|
|
struct ieee80211_vif *vif)
|
|
{
|
|
struct iwl_mld_vif *mld_vif = iwl_mld_vif_from_mac80211(vif);
|
|
|
|
if (WARN_ON(vif->type != NL80211_IFTYPE_P2P_DEVICE &&
|
|
vif->type != NL80211_IFTYPE_STATION))
|
|
return;
|
|
|
|
iwl_mld_remove_internal_sta(mld, &mld_vif->aux_sta, false,
|
|
IWL_MAX_TID_COUNT);
|
|
}
|
|
|
|
void iwl_mld_remove_mon_sta(struct iwl_mld *mld,
|
|
struct ieee80211_vif *vif,
|
|
struct ieee80211_bss_conf *link)
|
|
{
|
|
struct iwl_mld_link *mld_link = iwl_mld_link_from_mac80211(link);
|
|
|
|
if (WARN_ON(!mld_link))
|
|
return;
|
|
|
|
if (WARN_ON(vif->type != NL80211_IFTYPE_MONITOR))
|
|
return;
|
|
|
|
iwl_mld_remove_internal_sta(mld, &mld_link->mon_sta, false,
|
|
IWL_MAX_TID_COUNT);
|
|
}
|
|
|
|
static int iwl_mld_update_sta_resources(struct iwl_mld *mld,
|
|
struct ieee80211_vif *vif,
|
|
struct ieee80211_sta *sta,
|
|
u32 old_sta_mask,
|
|
u32 new_sta_mask)
|
|
{
|
|
int ret;
|
|
|
|
ret = iwl_mld_update_sta_txqs(mld, sta, old_sta_mask, new_sta_mask);
|
|
if (ret)
|
|
return ret;
|
|
|
|
ret = iwl_mld_update_sta_keys(mld, vif, sta, old_sta_mask, new_sta_mask);
|
|
if (ret)
|
|
return ret;
|
|
|
|
return iwl_mld_update_sta_baids(mld, old_sta_mask, new_sta_mask);
|
|
}
|
|
|
|
int iwl_mld_update_link_stas(struct iwl_mld *mld,
|
|
struct ieee80211_vif *vif,
|
|
struct ieee80211_sta *sta,
|
|
u16 old_links, u16 new_links)
|
|
{
|
|
struct iwl_mld_sta *mld_sta = iwl_mld_sta_from_mac80211(sta);
|
|
struct iwl_mld_link_sta *mld_link_sta;
|
|
unsigned long links_to_add = ~old_links & new_links;
|
|
unsigned long links_to_rem = old_links & ~new_links;
|
|
unsigned long old_links_long = old_links;
|
|
unsigned long sta_mask_added = 0;
|
|
u32 current_sta_mask = 0, sta_mask_to_rem = 0;
|
|
unsigned int link_id, sta_id;
|
|
int ret;
|
|
|
|
lockdep_assert_wiphy(mld->wiphy);
|
|
|
|
for_each_set_bit(link_id, &old_links_long,
|
|
IEEE80211_MLD_MAX_NUM_LINKS) {
|
|
mld_link_sta =
|
|
iwl_mld_link_sta_dereference_check(mld_sta, link_id);
|
|
|
|
if (WARN_ON(!mld_link_sta))
|
|
return -EINVAL;
|
|
|
|
current_sta_mask |= BIT(mld_link_sta->fw_id);
|
|
if (links_to_rem & BIT(link_id))
|
|
sta_mask_to_rem |= BIT(mld_link_sta->fw_id);
|
|
}
|
|
|
|
if (sta_mask_to_rem) {
|
|
ret = iwl_mld_update_sta_resources(mld, vif, sta,
|
|
current_sta_mask,
|
|
current_sta_mask &
|
|
~sta_mask_to_rem);
|
|
if (ret)
|
|
return ret;
|
|
|
|
current_sta_mask &= ~sta_mask_to_rem;
|
|
}
|
|
|
|
for_each_set_bit(link_id, &links_to_rem, IEEE80211_MLD_MAX_NUM_LINKS) {
|
|
struct ieee80211_link_sta *link_sta =
|
|
link_sta_dereference_protected(sta, link_id);
|
|
|
|
if (WARN_ON(!link_sta))
|
|
return -EINVAL;
|
|
|
|
iwl_mld_remove_link_sta(mld, link_sta);
|
|
}
|
|
|
|
for_each_set_bit(link_id, &links_to_add, IEEE80211_MLD_MAX_NUM_LINKS) {
|
|
struct ieee80211_link_sta *link_sta =
|
|
link_sta_dereference_protected(sta, link_id);
|
|
struct ieee80211_bss_conf *link;
|
|
|
|
if (WARN_ON(!link_sta))
|
|
return -EINVAL;
|
|
|
|
ret = iwl_mld_add_link_sta(mld, link_sta);
|
|
if (ret)
|
|
goto remove_added_link_stas;
|
|
|
|
mld_link_sta =
|
|
iwl_mld_link_sta_dereference_check(mld_sta,
|
|
link_id);
|
|
|
|
link = link_conf_dereference_protected(mld_sta->vif,
|
|
link_sta->link_id);
|
|
|
|
iwl_mld_set_max_amsdu_len(mld, link_sta);
|
|
iwl_mld_config_tlc_link(mld, vif, link, link_sta);
|
|
|
|
sta_mask_added |= BIT(mld_link_sta->fw_id);
|
|
}
|
|
|
|
if (sta_mask_added) {
|
|
ret = iwl_mld_update_sta_resources(mld, vif, sta,
|
|
current_sta_mask,
|
|
current_sta_mask |
|
|
sta_mask_added);
|
|
if (ret)
|
|
goto remove_added_link_stas;
|
|
}
|
|
|
|
/* We couldn't activate the links before it has a STA. Now we can */
|
|
for_each_set_bit(link_id, &links_to_add, IEEE80211_MLD_MAX_NUM_LINKS) {
|
|
struct ieee80211_bss_conf *link =
|
|
link_conf_dereference_protected(mld_sta->vif, link_id);
|
|
|
|
if (WARN_ON(!link))
|
|
continue;
|
|
|
|
iwl_mld_activate_link(mld, link);
|
|
}
|
|
|
|
return 0;
|
|
|
|
remove_added_link_stas:
|
|
for_each_set_bit(sta_id, &sta_mask_added, mld->fw->ucode_capa.num_stations) {
|
|
struct ieee80211_link_sta *link_sta =
|
|
wiphy_dereference(mld->wiphy,
|
|
mld->fw_id_to_link_sta[sta_id]);
|
|
|
|
if (WARN_ON(!link_sta))
|
|
continue;
|
|
|
|
iwl_mld_remove_link_sta(mld, link_sta);
|
|
}
|
|
|
|
return ret;
|
|
}
|