#include "mld.h"
#include "iface.h"
#include "link.h"
#include "mlo.h"
#include "tlc.h"
#include "fw/api/mac-cfg.h"
#include "fw/api/mac.h"
#include "fw/api/rs.h"
#define IWL_NAN_DISOVERY_BEACON_INTERNVAL_TU 512
#define IWL_NAN_RSSI_CLOSE 55
#define IWL_NAN_RSSI_MIDDLE 70
bool iwl_mld_nan_supported(struct iwl_mld *mld)
{
const struct iwl_fw *fw = mld->fw;
if (fw_has_capa(&fw->ucode_capa, IWL_UCODE_TLV_CAPA_NAN_SYNC_SUPPORT) &&
iwl_fw_lookup_cmd_ver(fw, WIDE_ID(MAC_CONF_GROUP, NAN_SCHEDULE_CMD), 0) >= 1 &&
iwl_fw_lookup_cmd_ver(fw, WIDE_ID(MAC_CONF_GROUP, NAN_PEER_CMD), 0) >= 1 &&
iwl_fw_lookup_cmd_ver(fw, WIDE_ID(MAC_CONF_GROUP, STA_CONFIG_CMD), 0) >= 3 &&
iwl_fw_lookup_cmd_ver(fw, WIDE_ID(MAC_CONF_GROUP, MAC_CONFIG_CMD), 0) >= 4 &&
iwl_fw_lookup_cmd_ver(fw, WIDE_ID(DATA_PATH_GROUP, TLC_MNG_CONFIG_CMD), 0) >= 6 &&
mld->nvm_data->nan_phy_capa.ht.ht_supported)
return true;
return false;
}
static int iwl_mld_nan_send_config_cmd(struct iwl_mld *mld,
struct iwl_nan_config_cmd *cmd,
u8 *beacon_data, size_t beacon_data_len)
{
struct iwl_host_cmd hcmd = {
.id = WIDE_ID(MAC_CONF_GROUP, NAN_CFG_CMD),
};
hcmd.len[0] = sizeof(*cmd);
hcmd.data[0] = cmd;
if (beacon_data_len) {
hcmd.len[1] = beacon_data_len;
hcmd.data[1] = beacon_data;
hcmd.dataflags[1] = IWL_HCMD_DFL_DUP;
}
return iwl_mld_send_cmd(mld, &hcmd);
}
bool iwl_mld_nan_use_nan_stations(struct iwl_mld *mld)
{
return iwl_fw_lookup_cmd_ver(mld->fw,
WIDE_ID(MAC_CONF_GROUP,
NAN_CFG_CMD), 1) != 1;
}
static const struct iwl_mld_int_sta *
iwl_mld_nan_get_mgmt_sta(struct iwl_mld *mld, struct ieee80211_vif *vif)
{
struct iwl_mld_vif *mld_vif = iwl_mld_vif_from_mac80211(vif);
const struct iwl_mld_int_sta *sta;
if (iwl_mld_nan_use_nan_stations(mld))
sta = &mld_vif->nan.mgmt_sta;
else
sta = &mld_vif->aux_sta;
if (WARN_ON(sta->sta_id == IWL_INVALID_STA))
return NULL;
return sta;
}
int iwl_mld_nan_get_mgmt_queue(struct iwl_mld *mld, struct ieee80211_vif *vif)
{
const struct iwl_mld_int_sta *sta = iwl_mld_nan_get_mgmt_sta(mld, vif);
if (!sta)
return IWL_MLD_INVALID_QUEUE;
return sta->queue_id;
}
static void iwl_mld_nan_flush(struct iwl_mld *mld, struct ieee80211_vif *vif)
{
const struct iwl_mld_int_sta *sta = iwl_mld_nan_get_mgmt_sta(mld, vif);
if (!sta)
return;
if (WARN_ON(sta->queue_id == IWL_MLD_INVALID_QUEUE))
return;
IWL_DEBUG_INFO(mld, "NAN: flush queues for sta=%u\n",
sta->sta_id);
iwl_mld_flush_link_sta_txqs(mld, sta->sta_id);
}
static void iwl_mld_nan_remove_stations(struct iwl_mld *mld,
struct ieee80211_vif *vif)
{
struct iwl_mld_vif *mld_vif = iwl_mld_vif_from_mac80211(vif);
iwl_mld_nan_flush(mld, vif);
if (!iwl_mld_nan_use_nan_stations(mld)) {
iwl_mld_remove_aux_sta(mld, vif);
return;
}
iwl_mld_remove_nan_bcast_sta(mld, &mld_vif->nan.bcast_sta);
iwl_mld_remove_nan_mgmt_sta(mld, &mld_vif->nan.mgmt_sta);
}
static int iwl_mld_nan_add_stations(struct iwl_mld *mld,
struct ieee80211_vif *vif)
{
struct iwl_mld_vif *mld_vif = iwl_mld_vif_from_mac80211(vif);
int ret;
if (!iwl_mld_nan_use_nan_stations(mld))
return iwl_mld_add_aux_sta(mld, &mld_vif->aux_sta);
ret = iwl_mld_add_nan_bcast_sta(mld, &mld_vif->nan.bcast_sta);
if (ret)
return ret;
ret = iwl_mld_add_nan_mgmt_sta(mld, &mld_vif->nan.mgmt_sta);
if (ret)
iwl_mld_remove_nan_bcast_sta(mld, &mld_vif->nan.bcast_sta);
return ret;
}
static int iwl_mld_nan_config(struct iwl_mld *mld,
struct ieee80211_vif *vif,
struct cfg80211_nan_conf *conf,
enum iwl_ctxt_action action)
{
struct iwl_mld_vif *mld_vif = iwl_mld_vif_from_mac80211(vif);
struct iwl_nan_config_cmd cmd = {
.action = cpu_to_le32(action),
};
u8 *data __free(kfree) = NULL;
lockdep_assert_wiphy(mld->wiphy);
ether_addr_copy(cmd.nmi_addr, vif->addr);
cmd.master_pref = conf->master_pref;
memcpy(cmd.cluster_id, conf->cluster_id + 4,
sizeof(cmd.cluster_id));
cmd.scan_period = conf->scan_period < 255 ? conf->scan_period : 255;
cmd.dwell_time =
conf->scan_dwell_time < 255 ? conf->scan_dwell_time : 255;
if (conf->discovery_beacon_interval)
cmd.discovery_beacon_interval =
cpu_to_le32(conf->discovery_beacon_interval);
else
cmd.discovery_beacon_interval =
cpu_to_le32(IWL_NAN_DISOVERY_BEACON_INTERNVAL_TU);
if (conf->enable_dw_notification)
cmd.flags = IWL_NAN_FLAG_DW_END_NOTIF_ENABLED;
cmd.band_config[IWL_NAN_BAND_2GHZ].rssi_close =
abs(conf->band_cfgs[NL80211_BAND_2GHZ].rssi_close);
cmd.band_config[IWL_NAN_BAND_2GHZ].rssi_middle =
abs(conf->band_cfgs[NL80211_BAND_2GHZ].rssi_middle);
cmd.band_config[IWL_NAN_BAND_2GHZ].dw_interval =
conf->band_cfgs[NL80211_BAND_2GHZ].awake_dw_interval;
if (conf->band_cfgs[NL80211_BAND_5GHZ].chan) {
cmd.hb_channel =
conf->band_cfgs[NL80211_BAND_5GHZ].chan->hw_value;
cmd.band_config[IWL_NAN_BAND_5GHZ].rssi_close =
abs(conf->band_cfgs[NL80211_BAND_5GHZ].rssi_close);
cmd.band_config[IWL_NAN_BAND_5GHZ].rssi_middle =
abs(conf->band_cfgs[NL80211_BAND_5GHZ].rssi_middle);
cmd.band_config[IWL_NAN_BAND_5GHZ].dw_interval =
conf->band_cfgs[NL80211_BAND_5GHZ].awake_dw_interval;
}
if (conf->extra_nan_attrs_len || conf->vendor_elems_len) {
data = kmalloc(conf->extra_nan_attrs_len +
conf->vendor_elems_len, GFP_KERNEL);
if (!data)
return -ENOMEM;
cmd.nan_attr_len = cpu_to_le32(conf->extra_nan_attrs_len);
cmd.nan_vendor_elems_len = cpu_to_le32(conf->vendor_elems_len);
if (conf->extra_nan_attrs_len)
memcpy(data, conf->extra_nan_attrs,
conf->extra_nan_attrs_len);
if (conf->vendor_elems_len)
memcpy(data + conf->extra_nan_attrs_len,
conf->vendor_elems,
conf->vendor_elems_len);
}
if (!iwl_mld_nan_use_nan_stations(mld))
cmd.sta_id = mld_vif->aux_sta.sta_id;
return iwl_mld_nan_send_config_cmd(mld, &cmd, data,
conf->extra_nan_attrs_len +
conf->vendor_elems_len);
}
int iwl_mld_start_nan(struct ieee80211_hw *hw, struct ieee80211_vif *vif,
struct cfg80211_nan_conf *conf)
{
struct iwl_mld *mld = IWL_MAC80211_GET_MLD(hw);
int ret;
IWL_DEBUG_MAC80211(mld, "NAN: start: bands=0x%x\n", conf->bands);
ret = iwl_mld_update_emlsr_block(mld, true, IWL_MLD_EMLSR_BLOCKED_NAN);
if (ret)
return ret;
ret = iwl_mld_nan_add_stations(mld, vif);
if (ret)
goto unblock_emlsr;
ret = iwl_mld_nan_config(mld, vif, conf, FW_CTXT_ACTION_ADD);
if (ret) {
IWL_ERR(mld, "Failed to start NAN. ret=%d\n", ret);
goto remove_stas;
}
return 0;
remove_stas:
iwl_mld_nan_remove_stations(mld, vif);
unblock_emlsr:
iwl_mld_update_emlsr_block(mld, false, IWL_MLD_EMLSR_BLOCKED_NAN);
return ret;
}
int iwl_mld_nan_change_config(struct ieee80211_hw *hw,
struct ieee80211_vif *vif,
struct cfg80211_nan_conf *conf,
u32 changes)
{
struct iwl_mld *mld = IWL_MAC80211_GET_MLD(hw);
IWL_DEBUG_MAC80211(mld, "NAN: change: changes=0x%x, bands=0x%x\n",
changes, conf->bands);
return iwl_mld_nan_config(mld, vif, conf, FW_CTXT_ACTION_MODIFY);
}
int iwl_mld_stop_nan(struct ieee80211_hw *hw,
struct ieee80211_vif *vif)
{
struct iwl_mld *mld = IWL_MAC80211_GET_MLD(hw);
struct iwl_nan_config_cmd cmd = {
.action = cpu_to_le32(FW_CTXT_ACTION_REMOVE),
};
int ret;
lockdep_assert_wiphy(mld->wiphy);
ret = iwl_mld_send_cmd_pdu(mld,
WIDE_ID(MAC_CONF_GROUP, NAN_CFG_CMD),
&cmd);
if (ret)
IWL_ERR(mld, "NAN: Failed to stop NAN. ret=%d\n", ret);
iwl_mld_nan_remove_stations(mld, vif);
iwl_mld_cancel_notifications_of_object(mld,
IWL_MLD_OBJECT_TYPE_NAN,
0);
iwl_mld_update_emlsr_block(mld, false, IWL_MLD_EMLSR_BLOCKED_NAN);
return 0;
}
void iwl_mld_handle_nan_cluster_notif(struct iwl_mld *mld,
struct iwl_rx_packet *pkt)
{
struct iwl_nan_cluster_notif *notif = (void *)pkt->data;
struct wireless_dev *wdev = mld->nan_device_vif ?
ieee80211_vif_to_wdev(mld->nan_device_vif) : NULL;
bool new_cluster = !!(notif->flags &
IWL_NAN_CLUSTER_NOTIF_FLAG_NEW_CLUSTER);
u8 cluster_id[ETH_ALEN] = {
0x50, 0x6f, 0x9a, 0x01,
notif->cluster_id[0], notif->cluster_id[1]
};
IWL_DEBUG_INFO(mld,
"NAN: cluster event: cluster_id=%pM, flags=0x%x\n",
cluster_id, notif->flags);
if (IWL_FW_CHECK(mld, !wdev, "NAN: cluster event without wdev\n"))
return;
if (IWL_FW_CHECK(mld, !ieee80211_vif_nan_started(mld->nan_device_vif),
"NAN: cluster event without NAN started\n"))
return;
cfg80211_nan_cluster_joined(wdev, cluster_id, new_cluster, GFP_KERNEL);
}
bool iwl_mld_cancel_nan_cluster_notif(struct iwl_mld *mld,
struct iwl_rx_packet *pkt,
u32 obj_id)
{
return true;
}
bool iwl_mld_cancel_nan_dw_end_notif(struct iwl_mld *mld,
struct iwl_rx_packet *pkt,
u32 obj_id)
{
return true;
}
bool iwl_mld_cancel_nan_ulw_attr_notif(struct iwl_mld *mld,
struct iwl_rx_packet *pkt,
u32 obj_id)
{
return true;
}
void iwl_mld_handle_nan_ulw_attr_notif(struct iwl_mld *mld,
struct iwl_rx_packet *pkt)
{
struct iwl_nan_ulw_attr_notif *notif = (void *)pkt->data;
struct wireless_dev *wdev;
IWL_DEBUG_INFO(mld, "NAN: ULW attr update: len=%u\n", notif->attr_len);
if (IWL_FW_CHECK(mld, !mld->nan_device_vif,
"NAN: ULW attr update without NAN vif\n"))
return;
if (IWL_FW_CHECK(mld, !ieee80211_vif_nan_started(mld->nan_device_vif),
"NAN: ULW attr update without NAN started\n"))
return;
if (IWL_FW_CHECK(mld,
notif->attr_len > IWL_NAN_MAX_ENDLESS_ULW_ATTR_LEN,
"NAN: ULW attr update invalid len %u\n",
notif->attr_len))
return;
wdev = ieee80211_vif_to_wdev(mld->nan_device_vif);
cfg80211_nan_ulw_update(wdev, notif->attr, notif->attr_len, GFP_KERNEL);
}
void iwl_mld_handle_nan_dw_end_notif(struct iwl_mld *mld,
struct iwl_rx_packet *pkt)
{
struct iwl_nan_dw_end_notif *notif = (void *)pkt->data;
struct iwl_mld_vif *mld_vif = mld->nan_device_vif ?
iwl_mld_vif_from_mac80211(mld->nan_device_vif) :
NULL;
struct wireless_dev *wdev;
struct ieee80211_channel *chan;
IWL_DEBUG_INFO(mld, "NAN: DW end: band=%u\n", notif->band);
if (IWL_FW_CHECK(mld, !mld_vif, "NAN: DW end without mld_vif\n"))
return;
if (IWL_FW_CHECK(mld, !ieee80211_vif_nan_started(mld->nan_device_vif),
"NAN: DW end without NAN started\n"))
return;
iwl_mld_nan_flush(mld, mld->nan_device_vif);
switch (notif->band) {
case IWL_NAN_BAND_2GHZ:
chan = ieee80211_get_channel(mld->wiphy, 2437);
break;
case IWL_NAN_BAND_5GHZ:
chan = ieee80211_get_channel(mld->wiphy, 5745);
break;
default:
IWL_FW_CHECK(mld, false,
"NAN: Invalid band %u in DW end notif\n",
notif->band);
return;
}
if (WARN_ON_ONCE(!chan))
return;
wdev = ieee80211_vif_to_wdev(mld->nan_device_vif);
cfg80211_next_nan_dw_notif(wdev, chan, GFP_KERNEL);
}
static void iwl_mld_nan_fill_rates(struct iwl_link_config_cmd *cmd)
{
u32 ofdm = 0;
ofdm |= IWL_RATE_BIT_MSK(6) >> IWL_FIRST_OFDM_RATE;
ofdm |= IWL_RATE_BIT_MSK(9) >> IWL_FIRST_OFDM_RATE;
ofdm |= IWL_RATE_BIT_MSK(12) >> IWL_FIRST_OFDM_RATE;
ofdm |= IWL_RATE_BIT_MSK(18) >> IWL_FIRST_OFDM_RATE;
ofdm |= IWL_RATE_BIT_MSK(24) >> IWL_FIRST_OFDM_RATE;
ofdm |= IWL_RATE_BIT_MSK(36) >> IWL_FIRST_OFDM_RATE;
ofdm |= IWL_RATE_BIT_MSK(48) >> IWL_FIRST_OFDM_RATE;
ofdm |= IWL_RATE_BIT_MSK(54) >> IWL_FIRST_OFDM_RATE;
cmd->ofdm_rates = cpu_to_le32(ofdm);
cmd->short_slot = cpu_to_le32(1);
}
static void iwl_mld_nan_fill_qos(struct iwl_ac_qos *ac, __le32 *qos_flags)
{
ac[AC_BK].cw_min = cpu_to_le16(15);
ac[AC_BK].cw_max = cpu_to_le16(1023);
ac[AC_BK].aifsn = 7;
ac[AC_BK].fifos_mask = BIT(IWL_BZ_EDCA_TX_FIFO_BK);
ac[AC_BK].edca_txop = 0;
ac[AC_BE].cw_min = cpu_to_le16(15);
ac[AC_BE].cw_max = cpu_to_le16(1023);
ac[AC_BE].aifsn = 3;
ac[AC_BE].fifos_mask = BIT(IWL_BZ_EDCA_TX_FIFO_BE);
ac[AC_BE].edca_txop = 0;
ac[AC_VI].cw_min = cpu_to_le16(7);
ac[AC_VI].cw_max = cpu_to_le16(15);
ac[AC_VI].aifsn = 2;
ac[AC_VI].fifos_mask = BIT(IWL_BZ_EDCA_TX_FIFO_VI);
ac[AC_VI].edca_txop = cpu_to_le16(3008);
ac[AC_VO].cw_min = cpu_to_le16(3);
ac[AC_VO].cw_max = cpu_to_le16(7);
ac[AC_VO].aifsn = 2;
ac[AC_VO].fifos_mask = BIT(IWL_BZ_EDCA_TX_FIFO_VO);
ac[AC_VO].edca_txop = cpu_to_le16(1504);
*qos_flags |= cpu_to_le32(MAC_QOS_FLG_UPDATE_EDCA);
}
static void
iwl_mld_nan_link_prep_cmd(struct iwl_mld *mld,
struct iwl_mld_nan_link *nan_link,
struct iwl_link_config_cmd *cmd,
u32 modify_flags)
{
struct ieee80211_vif *vif = mld->nan_device_vif;
struct iwl_mld_vif *mld_vif;
if (WARN_ON_ONCE(!vif))
return;
mld_vif = iwl_mld_vif_from_mac80211(vif);
memset(cmd, 0, sizeof(*cmd));
if (!nan_link->chanctx) {
cmd->phy_id = cpu_to_le32(FW_CTXT_ID_INVALID);
} else {
struct iwl_mld_phy *mld_phy;
mld_phy = iwl_mld_phy_from_mac80211(nan_link->chanctx);
cmd->phy_id = cpu_to_le32(mld_phy->fw_id);
}
if (modify_flags & LINK_CONTEXT_MODIFY_RATES_INFO)
iwl_mld_nan_fill_rates(cmd);
if (modify_flags & LINK_CONTEXT_MODIFY_QOS_PARAMS)
iwl_mld_nan_fill_qos(cmd->ac, &cmd->qos_flags);
cmd->link_id = cpu_to_le32(nan_link->fw_id);
cmd->mac_id = cpu_to_le32(mld_vif->fw_id);
cmd->active = cpu_to_le32(nan_link->active);
ether_addr_copy(cmd->local_link_addr, vif->addr);
cmd->modify_mask = cpu_to_le32(modify_flags);
}
static struct iwl_mld_nan_link *
iwl_mld_nan_link_add(struct iwl_mld *mld,
struct iwl_mld_vif *mld_vif,
struct ieee80211_chanctx_conf *chanctx)
{
struct iwl_mld_nan_link *nan_link;
struct iwl_link_config_cmd cmd;
u8 fw_id;
int ret;
lockdep_assert_wiphy(mld->wiphy);
ret = iwl_mld_allocate_link_fw_id(mld, &fw_id, ERR_PTR(-ENODEV));
if (WARN_ON(ret < 0))
return NULL;
nan_link = &mld_vif->nan.links[fw_id];
if (WARN_ON_ONCE(nan_link->fw_id != FW_CTXT_ID_INVALID))
goto err;
nan_link->fw_id = fw_id;
nan_link->chanctx = chanctx;
iwl_mld_nan_link_prep_cmd(mld, nan_link, &cmd,
LINK_CONTEXT_MODIFY_RATES_INFO |
LINK_CONTEXT_MODIFY_QOS_PARAMS);
ret = iwl_mld_send_link_cmd(mld, &cmd, FW_CTXT_ACTION_ADD);
if (ret) {
nan_link->fw_id = FW_CTXT_ID_INVALID;
nan_link->chanctx = NULL;
goto err;
}
return nan_link;
err:
RCU_INIT_POINTER(mld->fw_id_to_bss_conf[fw_id], NULL);
return NULL;
}
static int iwl_mld_nan_link_set_active(struct iwl_mld *mld,
struct ieee80211_vif *vif,
struct iwl_mld_nan_link *nan_link,
bool active)
{
struct iwl_link_config_cmd cmd;
struct ieee80211_sta *sta;
int ret;
if (nan_link->active == active)
return 0;
if (active) {
for_each_station(sta, mld->hw) {
struct iwl_mld_sta *mld_sta = iwl_mld_sta_from_mac80211(sta);
if (mld_sta->sta_type == STATION_TYPE_NAN_PEER_NDI)
iwl_mld_config_tlc(mld, mld_sta->vif, sta);
}
}
nan_link->active = active;
iwl_mld_nan_link_prep_cmd(mld, nan_link, &cmd,
LINK_CONTEXT_MODIFY_ACTIVE);
ret = iwl_mld_send_link_cmd(mld, &cmd, FW_CTXT_ACTION_MODIFY);
if (ret) {
nan_link->active = !nan_link->active;
return ret;
}
if (!active) {
nan_link->chanctx = NULL;
}
return 0;
}
static void iwl_mld_nan_link_remove(struct iwl_mld *mld,
struct iwl_mld_nan_link *nan_link,
u32 link_id)
{
struct iwl_link_config_cmd cmd = {
.link_id = cpu_to_le32(link_id),
.phy_id = cpu_to_le32(FW_CTXT_ID_INVALID),
};
iwl_mld_send_link_cmd(mld, &cmd, FW_CTXT_ACTION_REMOVE);
RCU_INIT_POINTER(mld->fw_id_to_bss_conf[link_id], NULL);
nan_link->fw_id = FW_CTXT_ID_INVALID;
nan_link->active = false;
nan_link->chanctx = NULL;
}
static bool iwl_mld_nan_have_links(struct iwl_mld_vif *mld_vif)
{
struct iwl_mld_nan_link *nan_link;
for_each_mld_nan_valid_link(mld_vif, nan_link)
return true;
return false;
}
static struct iwl_mld_nan_link *
iwl_mld_nan_find_link(struct iwl_mld_vif *mld_vif,
struct ieee80211_chanctx_conf *chanctx)
{
struct iwl_mld_nan_link *nan_link;
for_each_mld_nan_valid_link(mld_vif, nan_link) {
if (nan_link->chanctx == chanctx)
return nan_link;
}
return NULL;
}
static void iwl_mld_nan_set_mcast_data_links(struct ieee80211_vif *vif)
{
struct iwl_mld_vif *mld_vif = iwl_mld_vif_from_mac80211(vif);
if (vif->type != NL80211_IFTYPE_NAN_DATA)
return;
iwl_mld_update_nan_mcast_data_sta(mld_vif->mld, vif->addr,
&mld_vif->nan.mcast_data_sta);
}
void iwl_mld_nan_vif_cfg_changed(struct iwl_mld *mld,
struct ieee80211_vif *vif,
u64 changes)
{
struct iwl_nan_schedule_cmd cmd = {};
struct iwl_mld_vif *mld_vif = iwl_mld_vif_from_mac80211(vif);
bool previously_empty_schedule = !iwl_mld_nan_have_links(mld_vif);
struct ieee80211_nan_sched_cfg *sched_cfg = &vif->cfg.nan_sched;
struct iwl_mld_nan_link *links[ARRAY_SIZE(sched_cfg->channels)] = {};
struct ieee80211_nan_channel **slots = sched_cfg->schedule;
bool link_used[ARRAY_SIZE(mld_vif->nan.links)] = {};
struct iwl_mld_nan_link *nan_link;
unsigned long remove_link_ids = 0;
bool added_links = false;
bool empty_schedule = true;
int ret, i;
u16 cmd_size;
u32 cmd_id = WIDE_ID(MAC_CONF_GROUP, NAN_SCHEDULE_CMD);
u8 version = iwl_fw_lookup_cmd_ver(mld->fw, cmd_id, 0);
if (!(changes & BSS_CHANGED_NAN_LOCAL_SCHED))
return;
switch (version) {
case 1:
if (sched_cfg->deferred) {
IWL_ERR(mld,
"NAN: deferred schedule not supported by FW\n");
return;
}
cmd_size = sizeof(struct iwl_nan_schedule_cmd_v1);
break;
case 2:
cmd_size = sizeof(struct iwl_nan_schedule_cmd);
if (sched_cfg->deferred)
cmd.deferred = 1;
if (sched_cfg->avail_blob_len &&
!WARN_ON(sched_cfg->avail_blob_len >
sizeof(cmd.avail_attr.attr))) {
cmd.avail_attr.attr_len = sched_cfg->avail_blob_len;
memcpy(cmd.avail_attr.attr, sched_cfg->avail_blob,
sched_cfg->avail_blob_len);
}
break;
default:
IWL_ERR(mld, "NAN: unsupported NAN schedule cmd version %d\n",
version);
return;
}
for (i = 0; i < ARRAY_SIZE(sched_cfg->channels); i++) {
if (!sched_cfg->channels[i].chanreq.oper.chan)
continue;
empty_schedule = false;
break;
}
if (!empty_schedule && previously_empty_schedule) {
WARN_ON(mld_vif->nan.mac_added);
ret = iwl_mld_add_nan_vif(mld, vif);
if (ret) {
IWL_ERR(mld, "NAN: failed to add MAC (%d)\n", ret);
return;
}
}
if (!mld_vif->nan.mac_added) {
return;
}
BUILD_BUG_ON(ARRAY_SIZE(sched_cfg->channels) !=
ARRAY_SIZE(cmd.channels));
if (empty_schedule)
cmd.avail_attr.attr_len = 0;
for (i = 0; i < ARRAY_SIZE(sched_cfg->channels); i++) {
struct ieee80211_chanctx_conf *chanctx;
struct iwl_mld_nan_link *link;
chanctx = sched_cfg->channels[i].chanctx_conf;
if (!chanctx)
continue;
link = iwl_mld_nan_find_link(mld_vif, chanctx);
links[i] = link;
if (link)
link_used[link->fw_id] = true;
}
for (i = 0; i < ARRAY_SIZE(sched_cfg->channels); i++) {
struct ieee80211_chanctx_conf *chanctx;
if (links[i])
continue;
chanctx = sched_cfg->channels[i].chanctx_conf;
if (!chanctx)
continue;
links[i] = iwl_mld_nan_link_add(mld, mld_vif, chanctx);
if (links[i]) {
added_links = true;
link_used[links[i]->fw_id] = true;
iwl_mld_nan_link_set_active(mld, vif, links[i], true);
}
}
for (i = 0; i < ARRAY_SIZE(sched_cfg->channels); i++) {
cmd.channels[i].link_id = FW_CTXT_ID_INVALID;
if (!sched_cfg->channels[i].chanreq.oper.chan)
continue;
memcpy(cmd.channels[i].channel_entry,
sched_cfg->channels[i].channel_entry, 6);
cmd.channels[i].link_id =
links[i] ? links[i]->fw_id : FW_CTXT_ID_INVALID;
}
for (i = 0; i < CFG80211_NAN_SCHED_NUM_TIME_SLOTS; i++) {
int chan_idx;
if (!slots[i])
continue;
chan_idx = slots[i] - sched_cfg->channels;
if (WARN_ON_ONCE(chan_idx < 0 ||
chan_idx >= ARRAY_SIZE(cmd.channels)))
continue;
cmd.channels[chan_idx].availability_map |= cpu_to_le32(BIT(i));
}
ret = iwl_mld_send_cmd_pdu(mld, cmd_id, &cmd, cmd_size);
if (ret)
IWL_ERR(mld, "NAN: failed to update schedule (%d)\n", ret);
for_each_mld_nan_valid_link(mld_vif, nan_link) {
if (!link_used[nan_link->fw_id]) {
iwl_mld_nan_link_set_active(mld, vif, nan_link, false);
remove_link_ids |= BIT(nan_link->fw_id);
nan_link->fw_id = FW_CTXT_ID_INVALID;
}
}
if (added_links || remove_link_ids) {
struct ieee80211_sta *sta;
for_each_station(sta, mld->hw) {
struct iwl_mld_sta *mld_sta = iwl_mld_sta_from_mac80211(sta);
if (mld_sta->sta_type == STATION_TYPE_NAN_PEER_NMI ||
mld_sta->sta_type == STATION_TYPE_NAN_PEER_NDI)
iwl_mld_add_modify_sta_cmd(mld, &sta->deflink);
}
if (iwl_mld_nan_use_nan_stations(mld) && !mld->fw_status.in_hw_restart) {
struct ieee80211_vif *iter;
for_each_active_interface(iter, mld->hw)
iwl_mld_nan_set_mcast_data_links(iter);
}
}
for_each_set_bit(i, &remove_link_ids, ARRAY_SIZE(mld_vif->nan.links))
iwl_mld_nan_link_remove(mld, &mld_vif->nan.links[i], i);
if (!previously_empty_schedule && empty_schedule) {
WARN_ON_ONCE(!mld_vif->nan.mac_added);
if (!WARN_ON_ONCE(mld->fw_status.in_hw_restart &&
!iwl_mld_error_before_recovery(mld)))
iwl_mld_rm_vif(mld, vif);
}
}
bool iwl_mld_cancel_nan_sched_update_completed_notif(struct iwl_mld *mld,
struct iwl_rx_packet *pkt,
u32 obj_id)
{
return true;
}
void iwl_mld_handle_nan_sched_update_completed_notif(struct iwl_mld *mld,
struct iwl_rx_packet *pkt)
{
struct iwl_nan_sched_update_completed_notif *notif = (void *)pkt->data;
struct ieee80211_vif *vif = mld->nan_device_vif;
if (IWL_FW_CHECK(mld, !vif,
"NAN: schedule update completed without NAN vif\n"))
return;
if (IWL_FW_CHECK(mld, !ieee80211_vif_nan_started(vif),
"NAN: schedule update completed without NAN started\n"))
return;
IWL_FW_CHECK(mld, notif->status != IWL_NAN_SCHED_UPDATE_SUCCESS,
"NAN: deferred schedule update failed\n");
if (WARN_ON(!vif->cfg.nan_sched.deferred))
return;
ieee80211_nan_sched_update_done(vif);
}
int iwl_mld_mac802111_nan_peer_sched_changed(struct ieee80211_hw *hw,
struct ieee80211_sta *sta)
{
struct iwl_mld_sta *mld_sta = iwl_mld_sta_from_mac80211(sta);
struct ieee80211_nan_peer_sched *sched = sta->nan_sched;
struct iwl_mld_vif *mld_vif = iwl_mld_vif_from_mac80211(mld_sta->vif);
struct iwl_mld *mld = IWL_MAC80211_GET_MLD(hw);
struct iwl_mld_nan_link *nan_link;
struct iwl_nan_peer_cmd cmd = {
.nmi_sta_id = mld_sta->deflink.fw_id,
.sequence_id = sched->seq_id,
.committed_dw_info = cpu_to_le16(sched->committed_dw),
.max_channel_switch_time = cpu_to_le16(sched->max_chan_switch),
.initial_ulw_size = cpu_to_le32(sched->ulw_size),
.per_phy[0 ... NUM_PHY_CTX - 1] = {
.map_id = CFG80211_NAN_INVALID_MAP_ID,
.link_id = FW_CTXT_ID_INVALID,
},
};
struct iwl_host_cmd hcmd = {
.id = WIDE_ID(MAC_CONF_GROUP, NAN_PEER_CMD),
.data[0] = &cmd,
.len[0] = sizeof(cmd),
.data[1] = sched->init_ulw,
.len[1] = sched->ulw_size,
.dataflags[1] = IWL_HCMD_DFL_DUP,
};
struct ieee80211_sta *iter;
for_each_station(iter, mld->hw) {
struct iwl_mld_sta *tmp = iwl_mld_sta_from_mac80211(iter);
if (tmp->sta_type == STATION_TYPE_NAN_PEER_NDI)
iwl_mld_config_tlc(mld, tmp->vif, iter);
}
for (int i = 0; i < ARRAY_SIZE(sched->maps); i++) {
if (sched->maps[i].map_id == CFG80211_NAN_INVALID_MAP_ID)
continue;
BUILD_BUG_ON(ARRAY_SIZE(sched->maps[i].slots) != 32);
for (int slot = 0;
slot < ARRAY_SIZE(sched->maps[i].slots);
slot++) {
struct ieee80211_chanctx_conf *ctx;
struct ieee80211_nan_channel *chan;
struct iwl_mld_phy *phy;
chan = sched->maps[i].slots[slot];
if (!chan)
continue;
ctx = chan->chanctx_conf;
if (!ctx)
continue;
phy = iwl_mld_phy_from_mac80211(ctx);
for_each_mld_nan_valid_link(mld_vif, nan_link) {
if (nan_link->chanctx == ctx) {
cmd.per_phy[phy->fw_id].link_id =
nan_link->fw_id;
break;
}
}
if (WARN_ON(cmd.per_phy[phy->fw_id].link_id ==
FW_CTXT_ID_INVALID))
continue;
if (WARN_ON(cmd.per_phy[phy->fw_id].map_id != CFG80211_NAN_INVALID_MAP_ID &&
cmd.per_phy[phy->fw_id].map_id != sched->maps[i].map_id))
continue;
cmd.per_phy[phy->fw_id].map_id = sched->maps[i].map_id;
memcpy(cmd.per_phy[phy->fw_id].channel_entry,
chan->channel_entry,
sizeof(cmd.per_phy[phy->fw_id].channel_entry));
cmd.per_phy[phy->fw_id].availability_map |=
cpu_to_le32(BIT(slot));
}
}
return iwl_mld_send_cmd(mld, &hcmd);
}