1d1e879ecSMiri Korenblit // SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause 2d1e879ecSMiri Korenblit /* 3d1e879ecSMiri Korenblit * Copyright (C) 2024-2025 Intel Corporation 4d1e879ecSMiri Korenblit */ 5d1e879ecSMiri Korenblit #include <net/mac80211.h> 6d1e879ecSMiri Korenblit 7d1e879ecSMiri Korenblit #include "phy.h" 8d1e879ecSMiri Korenblit #include "hcmd.h" 9d1e879ecSMiri Korenblit #include "fw/api/phy-ctxt.h" 10d1e879ecSMiri Korenblit 11d1e879ecSMiri Korenblit int iwl_mld_allocate_fw_phy_id(struct iwl_mld *mld) 12d1e879ecSMiri Korenblit { 13d1e879ecSMiri Korenblit int id; 14d1e879ecSMiri Korenblit unsigned long used = mld->used_phy_ids; 15d1e879ecSMiri Korenblit 16d1e879ecSMiri Korenblit for_each_clear_bit(id, &used, NUM_PHY_CTX) { 17d1e879ecSMiri Korenblit mld->used_phy_ids |= BIT(id); 18d1e879ecSMiri Korenblit return id; 19d1e879ecSMiri Korenblit } 20d1e879ecSMiri Korenblit 21d1e879ecSMiri Korenblit return -ENOSPC; 22d1e879ecSMiri Korenblit } 23d1e879ecSMiri Korenblit EXPORT_SYMBOL_IF_IWLWIFI_KUNIT(iwl_mld_allocate_fw_phy_id); 24d1e879ecSMiri Korenblit 25*fd04fbeeSJohannes Berg struct iwl_mld_chanctx_usage_data { 26*fd04fbeeSJohannes Berg struct iwl_mld *mld; 27*fd04fbeeSJohannes Berg struct ieee80211_chanctx_conf *ctx; 28*fd04fbeeSJohannes Berg bool use_def; 29*fd04fbeeSJohannes Berg }; 30*fd04fbeeSJohannes Berg 31*fd04fbeeSJohannes Berg static bool iwl_mld_chanctx_fils_enabled(struct ieee80211_vif *vif, 32*fd04fbeeSJohannes Berg struct ieee80211_chanctx_conf *ctx) 33d1e879ecSMiri Korenblit { 34*fd04fbeeSJohannes Berg if (vif->type != NL80211_IFTYPE_AP) 35*fd04fbeeSJohannes Berg return false; 36*fd04fbeeSJohannes Berg 37*fd04fbeeSJohannes Berg return cfg80211_channel_is_psc(ctx->def.chan) || 38d1e879ecSMiri Korenblit (ctx->def.chan->band == NL80211_BAND_6GHZ && 39d1e879ecSMiri Korenblit ctx->def.width >= NL80211_CHAN_WIDTH_80); 40d1e879ecSMiri Korenblit } 41*fd04fbeeSJohannes Berg 42*fd04fbeeSJohannes Berg static void iwl_mld_chanctx_usage_iter(void *_data, u8 *mac, 43*fd04fbeeSJohannes Berg struct ieee80211_vif *vif) 44*fd04fbeeSJohannes Berg { 45*fd04fbeeSJohannes Berg struct iwl_mld_chanctx_usage_data *data = _data; 46*fd04fbeeSJohannes Berg struct ieee80211_bss_conf *link_conf; 47*fd04fbeeSJohannes Berg int link_id; 48*fd04fbeeSJohannes Berg 49*fd04fbeeSJohannes Berg for_each_vif_active_link(vif, link_conf, link_id) { 50*fd04fbeeSJohannes Berg if (rcu_access_pointer(link_conf->chanctx_conf) != data->ctx) 51*fd04fbeeSJohannes Berg continue; 52*fd04fbeeSJohannes Berg 53*fd04fbeeSJohannes Berg if (iwl_mld_chanctx_fils_enabled(vif, data->ctx)) 54*fd04fbeeSJohannes Berg data->use_def = true; 55*fd04fbeeSJohannes Berg } 56*fd04fbeeSJohannes Berg } 57*fd04fbeeSJohannes Berg 58*fd04fbeeSJohannes Berg struct cfg80211_chan_def * 59*fd04fbeeSJohannes Berg iwl_mld_get_chandef_from_chanctx(struct iwl_mld *mld, 60*fd04fbeeSJohannes Berg struct ieee80211_chanctx_conf *ctx) 61*fd04fbeeSJohannes Berg { 62*fd04fbeeSJohannes Berg struct iwl_mld_chanctx_usage_data data = { 63*fd04fbeeSJohannes Berg .mld = mld, 64*fd04fbeeSJohannes Berg .ctx = ctx, 65*fd04fbeeSJohannes Berg }; 66*fd04fbeeSJohannes Berg 67*fd04fbeeSJohannes Berg ieee80211_iterate_active_interfaces_mtx(mld->hw, 68*fd04fbeeSJohannes Berg IEEE80211_IFACE_ITER_NORMAL, 69*fd04fbeeSJohannes Berg iwl_mld_chanctx_usage_iter, 70*fd04fbeeSJohannes Berg &data); 71*fd04fbeeSJohannes Berg 72*fd04fbeeSJohannes Berg return data.use_def ? &ctx->def : &ctx->min_def; 73*fd04fbeeSJohannes Berg } 74d1e879ecSMiri Korenblit 75d1e879ecSMiri Korenblit static u8 76d1e879ecSMiri Korenblit iwl_mld_nl80211_width_to_fw(enum nl80211_chan_width width) 77d1e879ecSMiri Korenblit { 78d1e879ecSMiri Korenblit switch (width) { 79d1e879ecSMiri Korenblit case NL80211_CHAN_WIDTH_20_NOHT: 80d1e879ecSMiri Korenblit case NL80211_CHAN_WIDTH_20: 81d1e879ecSMiri Korenblit return IWL_PHY_CHANNEL_MODE20; 82d1e879ecSMiri Korenblit case NL80211_CHAN_WIDTH_40: 83d1e879ecSMiri Korenblit return IWL_PHY_CHANNEL_MODE40; 84d1e879ecSMiri Korenblit case NL80211_CHAN_WIDTH_80: 85d1e879ecSMiri Korenblit return IWL_PHY_CHANNEL_MODE80; 86d1e879ecSMiri Korenblit case NL80211_CHAN_WIDTH_160: 87d1e879ecSMiri Korenblit return IWL_PHY_CHANNEL_MODE160; 88d1e879ecSMiri Korenblit case NL80211_CHAN_WIDTH_320: 89d1e879ecSMiri Korenblit return IWL_PHY_CHANNEL_MODE320; 90d1e879ecSMiri Korenblit default: 91d1e879ecSMiri Korenblit WARN(1, "Invalid channel width=%u", width); 92d1e879ecSMiri Korenblit return IWL_PHY_CHANNEL_MODE20; 93d1e879ecSMiri Korenblit } 94d1e879ecSMiri Korenblit } 95d1e879ecSMiri Korenblit 96d1e879ecSMiri Korenblit /* Maps the driver specific control channel position (relative to the center 97d1e879ecSMiri Korenblit * freq) definitions to the fw values 98d1e879ecSMiri Korenblit */ 99d1e879ecSMiri Korenblit u8 iwl_mld_get_fw_ctrl_pos(const struct cfg80211_chan_def *chandef) 100d1e879ecSMiri Korenblit { 101d1e879ecSMiri Korenblit int offs = chandef->chan->center_freq - chandef->center_freq1; 102d1e879ecSMiri Korenblit int abs_offs = abs(offs); 103d1e879ecSMiri Korenblit u8 ret; 104d1e879ecSMiri Korenblit 105d1e879ecSMiri Korenblit if (offs == 0) { 106d1e879ecSMiri Korenblit /* The FW is expected to check the control channel position only 107d1e879ecSMiri Korenblit * when in HT/VHT and the channel width is not 20MHz. Return 108d1e879ecSMiri Korenblit * this value as the default one. 109d1e879ecSMiri Korenblit */ 110d1e879ecSMiri Korenblit return 0; 111d1e879ecSMiri Korenblit } 112d1e879ecSMiri Korenblit 113d1e879ecSMiri Korenblit /* this results in a value 0-7, i.e. fitting into 0b0111 */ 114d1e879ecSMiri Korenblit ret = (abs_offs - 10) / 20; 115d1e879ecSMiri Korenblit /* But we need the value to be in 0b1011 because 0b0100 is 116d1e879ecSMiri Korenblit * IWL_PHY_CTRL_POS_ABOVE, so shift bit 2 up to land in 117d1e879ecSMiri Korenblit * IWL_PHY_CTRL_POS_OFFS_EXT (0b1000) 118d1e879ecSMiri Korenblit */ 119d1e879ecSMiri Korenblit ret = (ret & IWL_PHY_CTRL_POS_OFFS_MSK) | 120d1e879ecSMiri Korenblit ((ret & BIT(2)) << 1); 121d1e879ecSMiri Korenblit /* and add the above bit */ 122d1e879ecSMiri Korenblit ret |= (offs > 0) * IWL_PHY_CTRL_POS_ABOVE; 123d1e879ecSMiri Korenblit 124d1e879ecSMiri Korenblit return ret; 125d1e879ecSMiri Korenblit } 126d1e879ecSMiri Korenblit 127d1e879ecSMiri Korenblit int iwl_mld_phy_fw_action(struct iwl_mld *mld, 128d1e879ecSMiri Korenblit struct ieee80211_chanctx_conf *ctx, u32 action) 129d1e879ecSMiri Korenblit { 130d1e879ecSMiri Korenblit struct iwl_mld_phy *phy = iwl_mld_phy_from_mac80211(ctx); 131d1e879ecSMiri Korenblit struct cfg80211_chan_def *chandef = &phy->chandef; 132d1e879ecSMiri Korenblit struct iwl_phy_context_cmd cmd = { 133d1e879ecSMiri Korenblit .id_and_color = cpu_to_le32(phy->fw_id), 134d1e879ecSMiri Korenblit .action = cpu_to_le32(action), 135d1e879ecSMiri Korenblit .puncture_mask = cpu_to_le16(chandef->punctured), 136d1e879ecSMiri Korenblit /* Channel info */ 137d1e879ecSMiri Korenblit .ci.channel = cpu_to_le32(chandef->chan->hw_value), 138d1e879ecSMiri Korenblit .ci.band = iwl_mld_nl80211_band_to_fw(chandef->chan->band), 139d1e879ecSMiri Korenblit .ci.width = iwl_mld_nl80211_width_to_fw(chandef->width), 140d1e879ecSMiri Korenblit .ci.ctrl_pos = iwl_mld_get_fw_ctrl_pos(chandef), 141d1e879ecSMiri Korenblit }; 142d1e879ecSMiri Korenblit int ret; 143d1e879ecSMiri Korenblit 144d1e879ecSMiri Korenblit if (ctx->ap.chan) { 145d1e879ecSMiri Korenblit cmd.sbb_bandwidth = 146d1e879ecSMiri Korenblit iwl_mld_nl80211_width_to_fw(ctx->ap.width); 147d1e879ecSMiri Korenblit cmd.sbb_ctrl_channel_loc = iwl_mld_get_fw_ctrl_pos(&ctx->ap); 148d1e879ecSMiri Korenblit } 149d1e879ecSMiri Korenblit 150d1e879ecSMiri Korenblit ret = iwl_mld_send_cmd_pdu(mld, PHY_CONTEXT_CMD, &cmd); 151d1e879ecSMiri Korenblit if (ret) 152d1e879ecSMiri Korenblit IWL_ERR(mld, "Failed to send PHY_CONTEXT_CMD ret = %d\n", ret); 153d1e879ecSMiri Korenblit 154d1e879ecSMiri Korenblit return ret; 155d1e879ecSMiri Korenblit } 156