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