875 lines
24 KiB
C
875 lines
24 KiB
C
// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause
|
|
/*
|
|
* Copyright (C) 2024-2026 Intel Corporation
|
|
*/
|
|
#include <net/cfg80211.h>
|
|
|
|
#include "iface.h"
|
|
#include "nan.h"
|
|
#include "hcmd.h"
|
|
#include "key.h"
|
|
#include "mlo.h"
|
|
#include "mac80211.h"
|
|
|
|
#include "fw/api/context.h"
|
|
#include "fw/api/mac.h"
|
|
#include "fw/api/time-event.h"
|
|
#include "fw/api/datapath.h"
|
|
|
|
/* Cleanup function for struct iwl_mld_vif, will be called in restart */
|
|
void iwl_mld_cleanup_vif(void *data, u8 *mac, struct ieee80211_vif *vif)
|
|
{
|
|
struct iwl_mld_vif *mld_vif = iwl_mld_vif_from_mac80211(vif);
|
|
struct iwl_mld *mld = mld_vif->mld;
|
|
struct iwl_mld_link *link;
|
|
|
|
mld_vif->emlsr.blocked_reasons &= ~IWL_MLD_EMLSR_BLOCKED_ROC;
|
|
|
|
if (mld_vif->aux_sta.sta_id != IWL_INVALID_STA)
|
|
iwl_mld_free_internal_sta(mld, &mld_vif->aux_sta);
|
|
|
|
/* EMLSR is turned back on during recovery */
|
|
vif->driver_flags &= ~IEEE80211_VIF_EML_ACTIVE;
|
|
|
|
if (mld_vif->roc_activity != ROC_NUM_ACTIVITIES)
|
|
ieee80211_remain_on_channel_expired(mld->hw);
|
|
|
|
mld_vif->roc_activity = ROC_NUM_ACTIVITIES;
|
|
|
|
for_each_mld_vif_valid_link(mld_vif, link) {
|
|
iwl_mld_cleanup_link(mld_vif->mld, link);
|
|
|
|
/* Correctly allocated primary link in non-MLO mode */
|
|
if (!ieee80211_vif_is_mld(vif) &&
|
|
link_id == 0 && link == &mld_vif->deflink)
|
|
continue;
|
|
|
|
if (vif->active_links & BIT(link_id))
|
|
continue;
|
|
|
|
/* Should not happen as link removal should always succeed */
|
|
WARN_ON(1);
|
|
if (link != &mld_vif->deflink)
|
|
kfree_rcu(link, rcu_head);
|
|
RCU_INIT_POINTER(mld_vif->link[link_id], NULL);
|
|
}
|
|
|
|
ieee80211_iter_keys(mld->hw, vif, iwl_mld_cleanup_keys_iter, NULL);
|
|
|
|
if (vif->type == NL80211_IFTYPE_NAN) {
|
|
mld_vif->nan.mac_added = false;
|
|
/* Clean up NAN links */
|
|
for (int i = 0; i < ARRAY_SIZE(mld_vif->nan.links); i++)
|
|
iwl_mld_cleanup_nan_link(&mld_vif->nan.links[i]);
|
|
|
|
if (mld_vif->nan.bcast_sta.sta_id != IWL_INVALID_STA)
|
|
iwl_mld_free_internal_sta(mld, &mld_vif->nan.bcast_sta);
|
|
if (mld_vif->nan.mgmt_sta.sta_id != IWL_INVALID_STA)
|
|
iwl_mld_free_internal_sta(mld, &mld_vif->nan.mgmt_sta);
|
|
|
|
mld_vif->nan.tx_igtk = NULL;
|
|
}
|
|
|
|
if (vif->type == NL80211_IFTYPE_NAN_DATA &&
|
|
mld_vif->nan.mcast_data_sta.sta_id != IWL_INVALID_STA)
|
|
iwl_mld_free_internal_sta(mld, &mld_vif->nan.mcast_data_sta);
|
|
|
|
CLEANUP_STRUCT(mld_vif);
|
|
}
|
|
|
|
static int iwl_mld_send_mac_cmd(struct iwl_mld *mld,
|
|
struct iwl_mac_config_cmd *cmd)
|
|
{
|
|
u16 cmd_id = WIDE_ID(MAC_CONF_GROUP, MAC_CONFIG_CMD);
|
|
int len = sizeof(*cmd);
|
|
int ret;
|
|
|
|
lockdep_assert_wiphy(mld->wiphy);
|
|
|
|
if (iwl_fw_lookup_cmd_ver(mld->fw, cmd_id, 0) < 4) {
|
|
if (WARN_ON(cmd->mac_type == cpu_to_le32(FW_MAC_TYPE_NAN)))
|
|
return -EINVAL;
|
|
|
|
len = sizeof(struct iwl_mac_config_cmd_v3);
|
|
}
|
|
|
|
ret = iwl_mld_send_cmd_pdu(mld, cmd_id, cmd, len);
|
|
if (ret)
|
|
IWL_ERR(mld, "Failed to send MAC_CONFIG_CMD ret = %d\n", ret);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int iwl_mld_mac80211_iftype_to_fw(const struct ieee80211_vif *vif)
|
|
{
|
|
switch (vif->type) {
|
|
case NL80211_IFTYPE_STATION:
|
|
return vif->p2p ? FW_MAC_TYPE_P2P_STA : FW_MAC_TYPE_BSS_STA;
|
|
case NL80211_IFTYPE_AP:
|
|
return FW_MAC_TYPE_GO;
|
|
case NL80211_IFTYPE_MONITOR:
|
|
return FW_MAC_TYPE_LISTENER;
|
|
case NL80211_IFTYPE_P2P_DEVICE:
|
|
return FW_MAC_TYPE_P2P_DEVICE;
|
|
case NL80211_IFTYPE_ADHOC:
|
|
return FW_MAC_TYPE_IBSS;
|
|
case NL80211_IFTYPE_NAN:
|
|
return FW_MAC_TYPE_NAN;
|
|
default:
|
|
WARN_ON_ONCE(1);
|
|
}
|
|
return FW_MAC_TYPE_BSS_STA;
|
|
}
|
|
|
|
static bool iwl_mld_is_nic_ack_enabled(struct iwl_mld *mld,
|
|
struct ieee80211_vif *vif)
|
|
{
|
|
const struct ieee80211_supported_band *sband;
|
|
const struct ieee80211_sta_he_cap *own_he_cap;
|
|
|
|
lockdep_assert_wiphy(mld->wiphy);
|
|
|
|
/* This capability is the same for all bands,
|
|
* so take it from one of them.
|
|
*/
|
|
sband = mld->hw->wiphy->bands[NL80211_BAND_2GHZ];
|
|
own_he_cap = ieee80211_get_he_iftype_cap_vif(sband, vif);
|
|
|
|
return own_he_cap && (own_he_cap->he_cap_elem.mac_cap_info[2] &
|
|
IEEE80211_HE_MAC_CAP2_ACK_EN);
|
|
}
|
|
|
|
struct iwl_mld_mac_wifi_gen_sta_iter_data {
|
|
struct ieee80211_vif *vif;
|
|
struct iwl_mac_wifi_gen_support *support;
|
|
};
|
|
|
|
static void iwl_mld_mac_wifi_gen_sta_iter(void *_data,
|
|
struct ieee80211_sta *sta)
|
|
{
|
|
struct iwl_mld_sta *mld_sta = iwl_mld_sta_from_mac80211(sta);
|
|
struct iwl_mld_mac_wifi_gen_sta_iter_data *data = _data;
|
|
struct ieee80211_link_sta *link_sta;
|
|
unsigned int link_id;
|
|
|
|
if (mld_sta->vif != data->vif)
|
|
return;
|
|
|
|
for_each_sta_active_link(data->vif, sta, link_sta, link_id) {
|
|
if (link_sta->he_cap.has_he)
|
|
data->support->he_support = 1;
|
|
if (link_sta->eht_cap.has_eht)
|
|
data->support->eht_support = 1;
|
|
}
|
|
}
|
|
|
|
static void iwl_mld_set_wifi_gen(struct iwl_mld *mld,
|
|
struct ieee80211_vif *vif,
|
|
struct iwl_mac_wifi_gen_support *support)
|
|
{
|
|
struct iwl_mld_mac_wifi_gen_sta_iter_data sta_iter_data = {
|
|
.vif = vif,
|
|
.support = support,
|
|
};
|
|
struct ieee80211_bss_conf *link_conf;
|
|
unsigned int link_id;
|
|
|
|
switch (vif->type) {
|
|
case NL80211_IFTYPE_MONITOR:
|
|
/* for sniffer, set to HW capabilities */
|
|
support->he_support = 1;
|
|
support->eht_support = mld->trans->cfg->eht_supported;
|
|
break;
|
|
case NL80211_IFTYPE_AP:
|
|
/* for AP set according to the link configs */
|
|
for_each_vif_active_link(vif, link_conf, link_id) {
|
|
support->he_ap_support |= link_conf->he_support;
|
|
support->eht_support |= link_conf->eht_support;
|
|
}
|
|
break;
|
|
default:
|
|
/*
|
|
* If we have MLO enabled, then the firmware needs to enable
|
|
* address translation for the station(s) we add. That depends
|
|
* on having EHT enabled in firmware, which in turn depends on
|
|
* mac80211 in the iteration below.
|
|
* However, mac80211 doesn't enable capabilities on the AP STA
|
|
* until it has parsed the association response successfully,
|
|
* so set EHT (and HE as a pre-requisite for EHT) when the vif
|
|
* is an MLD.
|
|
*/
|
|
if (ieee80211_vif_is_mld(vif)) {
|
|
support->he_support = 1;
|
|
support->eht_support = 1;
|
|
}
|
|
|
|
ieee80211_iterate_stations_mtx(mld->hw,
|
|
iwl_mld_mac_wifi_gen_sta_iter,
|
|
&sta_iter_data);
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* fill the common part for all interface types */
|
|
static void iwl_mld_mac_cmd_fill_common(struct iwl_mld *mld,
|
|
struct ieee80211_vif *vif,
|
|
struct iwl_mac_config_cmd *cmd,
|
|
u32 action)
|
|
{
|
|
struct iwl_mld_vif *mld_vif = iwl_mld_vif_from_mac80211(vif);
|
|
|
|
lockdep_assert_wiphy(mld->wiphy);
|
|
|
|
cmd->id_and_color = cpu_to_le32(mld_vif->fw_id);
|
|
cmd->action = cpu_to_le32(action);
|
|
|
|
cmd->mac_type =
|
|
cpu_to_le32(iwl_mld_mac80211_iftype_to_fw(vif));
|
|
|
|
memcpy(cmd->local_mld_addr, vif->addr, ETH_ALEN);
|
|
|
|
if (iwlwifi_mod_params.disable_11ax)
|
|
return;
|
|
|
|
cmd->nic_not_ack_enabled =
|
|
cpu_to_le32(!iwl_mld_is_nic_ack_enabled(mld, vif));
|
|
|
|
iwl_mld_set_wifi_gen(mld, vif, &cmd->wifi_gen);
|
|
}
|
|
|
|
static void iwl_mld_fill_mac_cmd_sta(struct iwl_mld *mld,
|
|
struct ieee80211_vif *vif, u32 action,
|
|
struct iwl_mac_config_cmd *cmd)
|
|
{
|
|
struct ieee80211_bss_conf *link;
|
|
u32 twt_policy = 0;
|
|
int link_id;
|
|
|
|
lockdep_assert_wiphy(mld->wiphy);
|
|
|
|
WARN_ON(vif->type != NL80211_IFTYPE_STATION);
|
|
|
|
/* We always want to hear MCAST frames, if we're not authorized yet,
|
|
* we'll drop them.
|
|
*/
|
|
cmd->filter_flags |= cpu_to_le32(MAC_CFG_FILTER_ACCEPT_GRP);
|
|
|
|
/* Adding a MAC ctxt with is_assoc set is not allowed in fw
|
|
* (and shouldn't happen)
|
|
*/
|
|
if (vif->cfg.assoc && action != FW_CTXT_ACTION_ADD) {
|
|
cmd->client.is_assoc = 1;
|
|
|
|
if (!iwl_mld_vif_from_mac80211(vif)->authorized)
|
|
cmd->client.data_policy |=
|
|
cpu_to_le16(COEX_HIGH_PRIORITY_ENABLE);
|
|
} else {
|
|
/* Allow beacons to pass through as long as we are not
|
|
* associated
|
|
*/
|
|
cmd->filter_flags |= cpu_to_le32(MAC_CFG_FILTER_ACCEPT_BEACON);
|
|
}
|
|
|
|
cmd->client.assoc_id = cpu_to_le16(vif->cfg.aid);
|
|
|
|
if (ieee80211_vif_is_mld(vif)) {
|
|
u16 esr_transition_timeout =
|
|
u16_get_bits(vif->cfg.eml_cap,
|
|
IEEE80211_EML_CAP_TRANSITION_TIMEOUT);
|
|
|
|
cmd->client.esr_transition_timeout =
|
|
min_t(u16, IEEE80211_EML_CAP_TRANSITION_TIMEOUT_64TU,
|
|
esr_transition_timeout);
|
|
cmd->client.medium_sync_delay =
|
|
cpu_to_le16(vif->cfg.eml_med_sync_delay);
|
|
}
|
|
|
|
for_each_vif_active_link(vif, link, link_id) {
|
|
if (!link->he_support)
|
|
continue;
|
|
|
|
if (link->twt_requester)
|
|
twt_policy |= TWT_SUPPORTED;
|
|
if (link->twt_protected)
|
|
twt_policy |= PROTECTED_TWT_SUPPORTED;
|
|
if (link->twt_broadcast)
|
|
twt_policy |= BROADCAST_TWT_SUPPORTED;
|
|
}
|
|
|
|
if (!iwlwifi_mod_params.disable_11ax)
|
|
cmd->client.data_policy |= cpu_to_le16(twt_policy);
|
|
|
|
if (vif->probe_req_reg && vif->cfg.assoc && vif->p2p)
|
|
cmd->filter_flags |=
|
|
cpu_to_le32(MAC_CFG_FILTER_ACCEPT_PROBE_REQ);
|
|
}
|
|
|
|
static void iwl_mld_fill_mac_cmd_ap(struct iwl_mld *mld,
|
|
struct ieee80211_vif *vif,
|
|
struct iwl_mac_config_cmd *cmd)
|
|
{
|
|
struct iwl_mld_vif *mld_vif = iwl_mld_vif_from_mac80211(vif);
|
|
|
|
lockdep_assert_wiphy(mld->wiphy);
|
|
|
|
WARN_ON(vif->type != NL80211_IFTYPE_AP);
|
|
|
|
cmd->filter_flags |= cpu_to_le32(MAC_CFG_FILTER_ACCEPT_PROBE_REQ);
|
|
|
|
/* in AP mode, pass beacons from other APs (needed for ht protection).
|
|
* When there're no any associated station, which means that we are not
|
|
* TXing anyway, don't ask FW to pass beacons to prevent unnecessary
|
|
* wake-ups.
|
|
*/
|
|
if (mld_vif->num_associated_stas)
|
|
cmd->filter_flags |= cpu_to_le32(MAC_CFG_FILTER_ACCEPT_BEACON);
|
|
}
|
|
|
|
static void iwl_mld_go_iterator(void *_data, u8 *mac, struct ieee80211_vif *vif)
|
|
{
|
|
bool *go_active = _data;
|
|
|
|
if (ieee80211_vif_type_p2p(vif) == NL80211_IFTYPE_P2P_GO &&
|
|
iwl_mld_vif_from_mac80211(vif)->ap_ibss_active)
|
|
*go_active = true;
|
|
}
|
|
|
|
static bool iwl_mld_p2p_dev_has_extended_disc(struct iwl_mld *mld)
|
|
{
|
|
bool go_active = false;
|
|
|
|
/* This flag should be set to true when the P2P Device is
|
|
* discoverable and there is at least a P2P GO. Setting
|
|
* this flag will allow the P2P Device to be discoverable on other
|
|
* channels in addition to its listen channel.
|
|
* Note that this flag should not be set in other cases as it opens the
|
|
* Rx filters on all MAC and increases the number of interrupts.
|
|
*/
|
|
ieee80211_iterate_active_interfaces(mld->hw,
|
|
IEEE80211_IFACE_ITER_RESUME_ALL,
|
|
iwl_mld_go_iterator, &go_active);
|
|
|
|
return go_active;
|
|
}
|
|
|
|
static void iwl_mld_fill_mac_cmd_p2p_dev(struct iwl_mld *mld,
|
|
struct ieee80211_vif *vif,
|
|
struct iwl_mac_config_cmd *cmd)
|
|
{
|
|
bool ext_disc = iwl_mld_p2p_dev_has_extended_disc(mld);
|
|
|
|
lockdep_assert_wiphy(mld->wiphy);
|
|
|
|
/* Override the filter flags to accept all management frames. This is
|
|
* needed to support both P2P device discovery using probe requests and
|
|
* P2P service discovery using action frames
|
|
*/
|
|
cmd->filter_flags = cpu_to_le32(MAC_CFG_FILTER_ACCEPT_CONTROL_AND_MGMT);
|
|
|
|
if (ext_disc)
|
|
cmd->p2p_dev.is_disc_extended = cpu_to_le32(1);
|
|
}
|
|
|
|
static void iwl_mld_fill_mac_cmd_ibss(struct iwl_mld *mld,
|
|
struct ieee80211_vif *vif,
|
|
struct iwl_mac_config_cmd *cmd)
|
|
{
|
|
lockdep_assert_wiphy(mld->wiphy);
|
|
|
|
WARN_ON(vif->type != NL80211_IFTYPE_ADHOC);
|
|
|
|
cmd->filter_flags |= cpu_to_le32(MAC_CFG_FILTER_ACCEPT_BEACON |
|
|
MAC_CFG_FILTER_ACCEPT_PROBE_REQ |
|
|
MAC_CFG_FILTER_ACCEPT_GRP);
|
|
}
|
|
|
|
static int iwl_mld_fill_mac_cmd_nan(struct iwl_mld *mld,
|
|
struct ieee80211_vif *vif,
|
|
struct ieee80211_vif *ndi_being_added,
|
|
struct iwl_mac_config_cmd *cmd)
|
|
{
|
|
struct ieee80211_vif *iter;
|
|
u32 idx = 0;
|
|
|
|
cmd->filter_flags = cpu_to_le32(MAC_CFG_FILTER_ACCEPT_CONTROL_AND_MGMT);
|
|
|
|
/*
|
|
* A NAN_DATA vif might be in the process of being added - it won't
|
|
* be found by the iteration below since it's not yet active/in-driver.
|
|
* In hw restart, the iteration below will find the ndi_being_added.
|
|
*/
|
|
if (ndi_being_added && !mld->fw_status.in_hw_restart) {
|
|
memcpy(cmd->nan.ndi_addrs[idx].addr, ndi_being_added->addr, ETH_ALEN);
|
|
idx++;
|
|
}
|
|
|
|
for_each_active_interface(iter, mld->hw) {
|
|
if (iter->type != NL80211_IFTYPE_NAN_DATA)
|
|
continue;
|
|
|
|
if (WARN_ON_ONCE(idx >= ARRAY_SIZE(cmd->nan.ndi_addrs)))
|
|
return -EINVAL;
|
|
|
|
memcpy(cmd->nan.ndi_addrs[idx].addr, iter->addr, ETH_ALEN);
|
|
idx++;
|
|
}
|
|
|
|
cmd->nan.ndi_addrs_count = cpu_to_le32(idx);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
iwl_mld_rm_mac_from_fw(struct iwl_mld *mld, struct ieee80211_vif *vif)
|
|
{
|
|
struct iwl_mld_vif *mld_vif = iwl_mld_vif_from_mac80211(vif);
|
|
struct iwl_mac_config_cmd cmd = {
|
|
.action = cpu_to_le32(FW_CTXT_ACTION_REMOVE),
|
|
.id_and_color = cpu_to_le32(mld_vif->fw_id),
|
|
};
|
|
|
|
return iwl_mld_send_mac_cmd(mld, &cmd);
|
|
}
|
|
|
|
static int
|
|
__iwl_mld_mac_fw_action(struct iwl_mld *mld, struct ieee80211_vif *vif,
|
|
u32 action, struct ieee80211_vif *ndi_being_added)
|
|
{
|
|
struct iwl_mac_config_cmd cmd = {};
|
|
int ret;
|
|
|
|
lockdep_assert_wiphy(mld->wiphy);
|
|
|
|
/* NAN_DATA interface type is not known to FW */
|
|
if (WARN_ON(vif->type == NL80211_IFTYPE_NAN_DATA))
|
|
return -EINVAL;
|
|
|
|
/* ndi_being_added is only relevant for NAN and when adding a NAN_DATA interface */
|
|
if (WARN_ON(ndi_being_added &&
|
|
(vif->type != NL80211_IFTYPE_NAN || action != FW_CTXT_ACTION_MODIFY)))
|
|
return -EINVAL;
|
|
|
|
if (action == FW_CTXT_ACTION_REMOVE)
|
|
return iwl_mld_rm_mac_from_fw(mld, vif);
|
|
|
|
iwl_mld_mac_cmd_fill_common(mld, vif, &cmd, action);
|
|
|
|
switch (vif->type) {
|
|
case NL80211_IFTYPE_STATION:
|
|
iwl_mld_fill_mac_cmd_sta(mld, vif, action, &cmd);
|
|
break;
|
|
case NL80211_IFTYPE_AP:
|
|
iwl_mld_fill_mac_cmd_ap(mld, vif, &cmd);
|
|
break;
|
|
case NL80211_IFTYPE_MONITOR:
|
|
cmd.filter_flags =
|
|
cpu_to_le32(MAC_CFG_FILTER_PROMISC |
|
|
MAC_CFG_FILTER_ACCEPT_CONTROL_AND_MGMT |
|
|
MAC_CFG_FILTER_ACCEPT_BEACON |
|
|
MAC_CFG_FILTER_ACCEPT_PROBE_REQ |
|
|
MAC_CFG_FILTER_ACCEPT_GRP);
|
|
break;
|
|
case NL80211_IFTYPE_P2P_DEVICE:
|
|
iwl_mld_fill_mac_cmd_p2p_dev(mld, vif, &cmd);
|
|
break;
|
|
case NL80211_IFTYPE_ADHOC:
|
|
iwl_mld_fill_mac_cmd_ibss(mld, vif, &cmd);
|
|
break;
|
|
case NL80211_IFTYPE_NAN:
|
|
ret = iwl_mld_fill_mac_cmd_nan(mld, vif, ndi_being_added, &cmd);
|
|
if (ret)
|
|
return ret;
|
|
break;
|
|
default:
|
|
WARN(1, "not supported yet\n");
|
|
return -EOPNOTSUPP;
|
|
}
|
|
|
|
return iwl_mld_send_mac_cmd(mld, &cmd);
|
|
}
|
|
|
|
int iwl_mld_mac_fw_action(struct iwl_mld *mld, struct ieee80211_vif *vif,
|
|
u32 action)
|
|
{
|
|
return __iwl_mld_mac_fw_action(mld, vif, action, NULL);
|
|
}
|
|
|
|
static void iwl_mld_mlo_scan_start_wk(struct wiphy *wiphy,
|
|
struct wiphy_work *wk)
|
|
{
|
|
struct iwl_mld_vif *mld_vif = container_of(wk, struct iwl_mld_vif,
|
|
mlo_scan_start_wk.work);
|
|
struct ieee80211_hw *hw = wiphy_to_ieee80211_hw(wiphy);
|
|
struct iwl_mld *mld = IWL_MAC80211_GET_MLD(hw);
|
|
|
|
iwl_mld_int_mlo_scan(mld, iwl_mld_vif_to_mac80211(mld_vif));
|
|
}
|
|
|
|
IWL_MLD_ALLOC_FN_STATIC(vif, vif)
|
|
|
|
/* Constructor function for struct iwl_mld_vif */
|
|
static void
|
|
iwl_mld_init_vif(struct iwl_mld *mld, struct ieee80211_vif *vif)
|
|
{
|
|
struct iwl_mld_vif *mld_vif = iwl_mld_vif_from_mac80211(vif);
|
|
|
|
lockdep_assert_wiphy(mld->wiphy);
|
|
|
|
mld_vif->mld = mld;
|
|
mld_vif->fw_id = IWL_MLD_INVALID_FW_ID;
|
|
mld_vif->roc_activity = ROC_NUM_ACTIVITIES;
|
|
|
|
if (!mld->fw_status.in_hw_restart) {
|
|
wiphy_work_init(&mld_vif->emlsr.unblock_tpt_wk,
|
|
iwl_mld_emlsr_unblock_tpt_wk);
|
|
wiphy_delayed_work_init(&mld_vif->emlsr.check_tpt_wk,
|
|
iwl_mld_emlsr_check_tpt);
|
|
wiphy_delayed_work_init(&mld_vif->emlsr.prevent_done_wk,
|
|
iwl_mld_emlsr_prevent_done_wk);
|
|
wiphy_delayed_work_init(&mld_vif->emlsr.tmp_non_bss_done_wk,
|
|
iwl_mld_emlsr_tmp_non_bss_done_wk);
|
|
wiphy_delayed_work_init(&mld_vif->mlo_scan_start_wk,
|
|
iwl_mld_mlo_scan_start_wk);
|
|
}
|
|
|
|
if (vif->type == NL80211_IFTYPE_NAN) {
|
|
for (int i = 0; i < ARRAY_SIZE(mld_vif->nan.links); i++) {
|
|
memset(&mld_vif->nan.links[i], 0, sizeof(mld_vif->nan.links[i]));
|
|
mld_vif->nan.links[i].fw_id = FW_CTXT_ID_INVALID;
|
|
}
|
|
|
|
iwl_mld_init_internal_sta(&mld_vif->nan.bcast_sta);
|
|
iwl_mld_init_internal_sta(&mld_vif->nan.mgmt_sta);
|
|
} else if (vif->type == NL80211_IFTYPE_NAN_DATA) {
|
|
iwl_mld_init_internal_sta(&mld_vif->nan.mcast_data_sta);
|
|
}
|
|
|
|
iwl_mld_init_internal_sta(&mld_vif->aux_sta);
|
|
}
|
|
|
|
static int iwl_mld_update_nan_mac(struct iwl_mld *mld,
|
|
struct ieee80211_vif *ndi_being_added)
|
|
{
|
|
struct ieee80211_vif *vif = mld->nan_device_vif;
|
|
struct iwl_mld_vif *mld_vif;
|
|
|
|
if (WARN_ON_ONCE(!vif))
|
|
return -ENODEV;
|
|
|
|
mld_vif = iwl_mld_vif_from_mac80211(vif);
|
|
|
|
if (!iwl_mld_vif_fw_id_valid(mld_vif))
|
|
return 0;
|
|
|
|
return __iwl_mld_mac_fw_action(mld, vif, FW_CTXT_ACTION_MODIFY,
|
|
ndi_being_added);
|
|
}
|
|
|
|
int iwl_mld_add_vif(struct iwl_mld *mld, struct ieee80211_vif *vif)
|
|
{
|
|
struct iwl_mld_vif *mld_vif = iwl_mld_vif_from_mac80211(vif);
|
|
int ret;
|
|
|
|
lockdep_assert_wiphy(mld->wiphy);
|
|
|
|
iwl_mld_init_vif(mld, vif);
|
|
|
|
/* NAN MACs are added to FW only when a schedule is set */
|
|
if (vif->type == NL80211_IFTYPE_NAN)
|
|
return 0;
|
|
|
|
/* NAN_DATA interface type is not known to FW, but we need to update NAN MAC */
|
|
if (vif->type == NL80211_IFTYPE_NAN_DATA)
|
|
return iwl_mld_update_nan_mac(mld, vif);
|
|
|
|
ret = iwl_mld_allocate_vif_fw_id(mld, &mld_vif->fw_id, vif);
|
|
if (ret)
|
|
return ret;
|
|
|
|
ret = iwl_mld_mac_fw_action(mld, vif, FW_CTXT_ACTION_ADD);
|
|
if (ret)
|
|
RCU_INIT_POINTER(mld->fw_id_to_vif[mld_vif->fw_id], NULL);
|
|
|
|
return ret;
|
|
}
|
|
|
|
int iwl_mld_add_nan_vif(struct iwl_mld *mld, struct ieee80211_vif *vif)
|
|
{
|
|
struct iwl_mld_vif *mld_vif = iwl_mld_vif_from_mac80211(vif);
|
|
int ret;
|
|
|
|
lockdep_assert_wiphy(mld->wiphy);
|
|
|
|
if (WARN_ON(vif->type != NL80211_IFTYPE_NAN))
|
|
return -EINVAL;
|
|
|
|
ret = iwl_mld_allocate_vif_fw_id(mld, &mld_vif->fw_id, vif);
|
|
if (ret)
|
|
return ret;
|
|
|
|
ret = iwl_mld_mac_fw_action(mld, vif, FW_CTXT_ACTION_ADD);
|
|
if (ret) {
|
|
RCU_INIT_POINTER(mld->fw_id_to_vif[mld_vif->fw_id], NULL);
|
|
return ret;
|
|
}
|
|
|
|
mld_vif->nan.mac_added = true;
|
|
|
|
return 0;
|
|
}
|
|
|
|
void iwl_mld_rm_vif(struct iwl_mld *mld, struct ieee80211_vif *vif)
|
|
{
|
|
struct iwl_mld_vif *mld_vif = iwl_mld_vif_from_mac80211(vif);
|
|
|
|
lockdep_assert_wiphy(mld->wiphy);
|
|
|
|
if (vif->type == NL80211_IFTYPE_NAN_DATA) {
|
|
iwl_mld_update_nan_mac(mld, NULL);
|
|
return;
|
|
}
|
|
|
|
if (!iwl_mld_vif_fw_id_valid(mld_vif))
|
|
return;
|
|
|
|
iwl_mld_mac_fw_action(mld, vif, FW_CTXT_ACTION_REMOVE);
|
|
|
|
RCU_INIT_POINTER(mld->fw_id_to_vif[mld_vif->fw_id], NULL);
|
|
|
|
if (vif->type == NL80211_IFTYPE_NAN)
|
|
mld_vif->nan.mac_added = false;
|
|
|
|
iwl_mld_cancel_notifications_of_object(mld, IWL_MLD_OBJECT_TYPE_VIF,
|
|
mld_vif->fw_id);
|
|
}
|
|
|
|
void iwl_mld_set_vif_associated(struct iwl_mld *mld,
|
|
struct ieee80211_vif *vif)
|
|
{
|
|
struct ieee80211_bss_conf *link;
|
|
unsigned int link_id;
|
|
|
|
for_each_vif_active_link(vif, link, link_id) {
|
|
if (iwl_mld_link_set_associated(mld, vif, link))
|
|
IWL_ERR(mld, "failed to update link %d\n", link_id);
|
|
}
|
|
|
|
iwl_mld_recalc_multicast_filter(mld);
|
|
}
|
|
|
|
static void iwl_mld_get_fw_id_bss_bitmap_iter(void *_data, u8 *mac,
|
|
struct ieee80211_vif *vif)
|
|
{
|
|
u8 *fw_id_bitmap = _data;
|
|
struct iwl_mld_vif *mld_vif = iwl_mld_vif_from_mac80211(vif);
|
|
|
|
if (ieee80211_vif_type_p2p(vif) != NL80211_IFTYPE_STATION)
|
|
return;
|
|
|
|
*fw_id_bitmap |= BIT(mld_vif->fw_id);
|
|
}
|
|
|
|
u8 iwl_mld_get_fw_bss_vifs_ids(struct iwl_mld *mld)
|
|
{
|
|
u8 fw_id_bitmap = 0;
|
|
|
|
ieee80211_iterate_active_interfaces_mtx(mld->hw,
|
|
IEEE80211_IFACE_SKIP_SDATA_NOT_IN_DRIVER,
|
|
iwl_mld_get_fw_id_bss_bitmap_iter,
|
|
&fw_id_bitmap);
|
|
|
|
return fw_id_bitmap;
|
|
}
|
|
|
|
void iwl_mld_handle_probe_resp_data_notif(struct iwl_mld *mld,
|
|
struct iwl_rx_packet *pkt)
|
|
{
|
|
const struct iwl_probe_resp_data_notif *notif = (void *)pkt->data;
|
|
struct iwl_probe_resp_data *old_data, *new_data;
|
|
struct ieee80211_vif *vif;
|
|
struct iwl_mld_link *mld_link;
|
|
|
|
IWL_DEBUG_INFO(mld, "Probe response data notif: noa %d, csa %d\n",
|
|
notif->noa_active, notif->csa_counter);
|
|
|
|
if (IWL_FW_CHECK(mld, le32_to_cpu(notif->mac_id) >=
|
|
ARRAY_SIZE(mld->fw_id_to_vif),
|
|
"mac id is invalid: %d\n",
|
|
le32_to_cpu(notif->mac_id)))
|
|
return;
|
|
|
|
vif = wiphy_dereference(mld->wiphy,
|
|
mld->fw_id_to_vif[le32_to_cpu(notif->mac_id)]);
|
|
|
|
/* the firmware gives us the mac_id (and not the link_id), mac80211
|
|
* gets a vif and not a link, bottom line, this flow is not MLD ready
|
|
* yet.
|
|
*/
|
|
if (WARN_ON(!vif) || ieee80211_vif_is_mld(vif))
|
|
return;
|
|
|
|
if (notif->csa_counter != IWL_PROBE_RESP_DATA_NO_CSA &&
|
|
notif->csa_counter >= 1)
|
|
ieee80211_beacon_set_cntdwn(vif, notif->csa_counter);
|
|
|
|
if (!vif->p2p)
|
|
return;
|
|
|
|
mld_link = &iwl_mld_vif_from_mac80211(vif)->deflink;
|
|
|
|
/* len_low should be 2 + n*13 (where n is the number of descriptors.
|
|
* 13 is the size of a NoA descriptor). We can have either one or two
|
|
* descriptors.
|
|
*/
|
|
if (IWL_FW_CHECK(mld, notif->noa_active &&
|
|
notif->noa_attr.len_low != 2 +
|
|
sizeof(struct ieee80211_p2p_noa_desc) &&
|
|
notif->noa_attr.len_low != 2 +
|
|
sizeof(struct ieee80211_p2p_noa_desc) * 2,
|
|
"Invalid noa_attr.len_low (%d)\n",
|
|
notif->noa_attr.len_low))
|
|
return;
|
|
|
|
new_data = kzalloc_obj(*new_data);
|
|
if (!new_data)
|
|
return;
|
|
|
|
memcpy(&new_data->notif, notif, sizeof(new_data->notif));
|
|
|
|
/* noa_attr contains 1 reserved byte, need to substruct it */
|
|
new_data->noa_len = sizeof(struct ieee80211_vendor_ie) +
|
|
sizeof(new_data->notif.noa_attr) - 1;
|
|
|
|
/*
|
|
* If it's a one time NoA, only one descriptor is needed,
|
|
* adjust the length according to len_low.
|
|
*/
|
|
if (new_data->notif.noa_attr.len_low ==
|
|
sizeof(struct ieee80211_p2p_noa_desc) + 2)
|
|
new_data->noa_len -= sizeof(struct ieee80211_p2p_noa_desc);
|
|
|
|
old_data = wiphy_dereference(mld->wiphy, mld_link->probe_resp_data);
|
|
rcu_assign_pointer(mld_link->probe_resp_data, new_data);
|
|
|
|
if (old_data)
|
|
kfree_rcu(old_data, rcu_head);
|
|
}
|
|
|
|
void iwl_mld_handle_datapath_monitor_notif(struct iwl_mld *mld,
|
|
struct iwl_rx_packet *pkt)
|
|
{
|
|
struct iwl_datapath_monitor_notif *notif = (void *)pkt->data;
|
|
struct ieee80211_bss_conf *link;
|
|
struct ieee80211_supported_band *sband;
|
|
const struct ieee80211_sta_he_cap *he_cap;
|
|
struct ieee80211_vif *vif;
|
|
struct iwl_mld_vif *mld_vif;
|
|
|
|
if (notif->type != cpu_to_le32(IWL_DP_MON_NOTIF_TYPE_EXT_CCA))
|
|
return;
|
|
|
|
link = iwl_mld_fw_id_to_link_conf(mld, notif->link_id);
|
|
if (WARN_ON(!link))
|
|
return;
|
|
|
|
vif = link->vif;
|
|
if (WARN_ON(!vif) || vif->type != NL80211_IFTYPE_STATION ||
|
|
!vif->cfg.assoc)
|
|
return;
|
|
|
|
if (!link->chanreq.oper.chan ||
|
|
link->chanreq.oper.chan->band != NL80211_BAND_2GHZ ||
|
|
link->chanreq.oper.width < NL80211_CHAN_WIDTH_40)
|
|
return;
|
|
|
|
mld_vif = iwl_mld_vif_from_mac80211(vif);
|
|
|
|
/* this shouldn't happen *again*, ignore it */
|
|
if (mld_vif->cca_40mhz_workaround != CCA_40_MHZ_WA_NONE)
|
|
return;
|
|
|
|
mld_vif->cca_40mhz_workaround = CCA_40_MHZ_WA_RECONNECT;
|
|
|
|
/*
|
|
* This capability manipulation isn't really ideal, but it's the
|
|
* easiest choice - otherwise we'd have to do some major changes
|
|
* in mac80211 to support this, which isn't worth it. This does
|
|
* mean that userspace may have outdated information, but that's
|
|
* actually not an issue at all.
|
|
*/
|
|
sband = mld->wiphy->bands[NL80211_BAND_2GHZ];
|
|
|
|
WARN_ON(!sband->ht_cap.ht_supported);
|
|
WARN_ON(!(sband->ht_cap.cap & IEEE80211_HT_CAP_SUP_WIDTH_20_40));
|
|
sband->ht_cap.cap &= ~IEEE80211_HT_CAP_SUP_WIDTH_20_40;
|
|
|
|
he_cap = ieee80211_get_he_iftype_cap_vif(sband, vif);
|
|
|
|
if (he_cap) {
|
|
/* we know that ours is writable */
|
|
struct ieee80211_sta_he_cap *he = (void *)(uintptr_t)he_cap;
|
|
|
|
WARN_ON(!he->has_he);
|
|
WARN_ON(!(he->he_cap_elem.phy_cap_info[0] &
|
|
IEEE80211_HE_PHY_CAP0_CHANNEL_WIDTH_SET_40MHZ_IN_2G));
|
|
he->he_cap_elem.phy_cap_info[0] &=
|
|
~IEEE80211_HE_PHY_CAP0_CHANNEL_WIDTH_SET_40MHZ_IN_2G;
|
|
}
|
|
|
|
ieee80211_disconnect(vif, true);
|
|
}
|
|
|
|
void iwl_mld_reset_cca_40mhz_workaround(struct iwl_mld *mld,
|
|
struct ieee80211_vif *vif)
|
|
{
|
|
struct ieee80211_supported_band *sband;
|
|
const struct ieee80211_sta_he_cap *he_cap;
|
|
struct iwl_mld_vif *mld_vif = iwl_mld_vif_from_mac80211(vif);
|
|
|
|
if (vif->type != NL80211_IFTYPE_STATION)
|
|
return;
|
|
|
|
if (mld_vif->cca_40mhz_workaround == CCA_40_MHZ_WA_NONE)
|
|
return;
|
|
|
|
/* Now we are just reconnecting with the new capabilities,
|
|
* but remember to reset the capabilities when we disconnect for real
|
|
*/
|
|
if (mld_vif->cca_40mhz_workaround == CCA_40_MHZ_WA_RECONNECT) {
|
|
mld_vif->cca_40mhz_workaround = CCA_40_MHZ_WA_RESET;
|
|
return;
|
|
}
|
|
|
|
/* Now cca_40mhz_workaround == CCA_40_MHZ_WA_RESET */
|
|
|
|
sband = mld->wiphy->bands[NL80211_BAND_2GHZ];
|
|
|
|
sband->ht_cap.cap |= IEEE80211_HT_CAP_SUP_WIDTH_20_40;
|
|
|
|
he_cap = ieee80211_get_he_iftype_cap_vif(sband, vif);
|
|
|
|
if (he_cap) {
|
|
/* we know that ours is writable */
|
|
struct ieee80211_sta_he_cap *he = (void *)(uintptr_t)he_cap;
|
|
|
|
he->he_cap_elem.phy_cap_info[0] |=
|
|
IEEE80211_HE_PHY_CAP0_CHANNEL_WIDTH_SET_40MHZ_IN_2G;
|
|
}
|
|
|
|
mld_vif->cca_40mhz_workaround = CCA_40_MHZ_WA_NONE;
|
|
}
|
|
|
|
struct ieee80211_vif *iwl_mld_get_bss_vif(struct iwl_mld *mld)
|
|
{
|
|
unsigned long fw_id_bitmap = iwl_mld_get_fw_bss_vifs_ids(mld);
|
|
int fw_id;
|
|
|
|
if (hweight8(fw_id_bitmap) != 1)
|
|
return NULL;
|
|
|
|
fw_id = __ffs(fw_id_bitmap);
|
|
|
|
return rcu_dereference_wiphy(mld->wiphy,
|
|
mld->fw_id_to_vif[fw_id]);
|
|
}
|