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
iwl_mld_allocate_fw_phy_id(struct iwl_mld * mld)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
iwl_mld_chanctx_fils_enabled(struct ieee80211_vif * vif,struct ieee80211_chanctx_conf * ctx)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
iwl_mld_chanctx_usage_iter(void * _data,u8 * mac,struct ieee80211_vif * vif)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 (iwl_mld_chanctx_fils_enabled(vif, data->ctx))
54 data->use_def = true;
55 }
56 }
57
58 struct cfg80211_chan_def *
iwl_mld_get_chandef_from_chanctx(struct iwl_mld * mld,struct ieee80211_chanctx_conf * ctx)59 iwl_mld_get_chandef_from_chanctx(struct iwl_mld *mld,
60 struct ieee80211_chanctx_conf *ctx)
61 {
62 struct iwl_mld_chanctx_usage_data data = {
63 .mld = mld,
64 .ctx = ctx,
65 };
66
67 ieee80211_iterate_active_interfaces_mtx(mld->hw,
68 IEEE80211_IFACE_ITER_NORMAL,
69 iwl_mld_chanctx_usage_iter,
70 &data);
71
72 return data.use_def ? &ctx->def : &ctx->min_def;
73 }
74
75 static u8
iwl_mld_nl80211_width_to_fw(enum nl80211_chan_width width)76 iwl_mld_nl80211_width_to_fw(enum nl80211_chan_width width)
77 {
78 switch (width) {
79 case NL80211_CHAN_WIDTH_20_NOHT:
80 case NL80211_CHAN_WIDTH_20:
81 return IWL_PHY_CHANNEL_MODE20;
82 case NL80211_CHAN_WIDTH_40:
83 return IWL_PHY_CHANNEL_MODE40;
84 case NL80211_CHAN_WIDTH_80:
85 return IWL_PHY_CHANNEL_MODE80;
86 case NL80211_CHAN_WIDTH_160:
87 return IWL_PHY_CHANNEL_MODE160;
88 case NL80211_CHAN_WIDTH_320:
89 return IWL_PHY_CHANNEL_MODE320;
90 default:
91 WARN(1, "Invalid channel width=%u", width);
92 return IWL_PHY_CHANNEL_MODE20;
93 }
94 }
95
96 /* Maps the driver specific control channel position (relative to the center
97 * freq) definitions to the fw values
98 */
iwl_mld_get_fw_ctrl_pos(const struct cfg80211_chan_def * chandef)99 u8 iwl_mld_get_fw_ctrl_pos(const struct cfg80211_chan_def *chandef)
100 {
101 int offs = chandef->chan->center_freq - chandef->center_freq1;
102 int abs_offs = abs(offs);
103 u8 ret;
104
105 if (offs == 0) {
106 /* The FW is expected to check the control channel position only
107 * when in HT/VHT and the channel width is not 20MHz. Return
108 * this value as the default one.
109 */
110 return 0;
111 }
112
113 /* this results in a value 0-7, i.e. fitting into 0b0111 */
114 ret = (abs_offs - 10) / 20;
115 /* But we need the value to be in 0b1011 because 0b0100 is
116 * IWL_PHY_CTRL_POS_ABOVE, so shift bit 2 up to land in
117 * IWL_PHY_CTRL_POS_OFFS_EXT (0b1000)
118 */
119 ret = (ret & IWL_PHY_CTRL_POS_OFFS_MSK) |
120 ((ret & BIT(2)) << 1);
121 /* and add the above bit */
122 ret |= (offs > 0) * IWL_PHY_CTRL_POS_ABOVE;
123
124 return ret;
125 }
126
iwl_mld_phy_fw_action(struct iwl_mld * mld,struct ieee80211_chanctx_conf * ctx,u32 action)127 int iwl_mld_phy_fw_action(struct iwl_mld *mld,
128 struct ieee80211_chanctx_conf *ctx, u32 action)
129 {
130 struct iwl_mld_phy *phy = iwl_mld_phy_from_mac80211(ctx);
131 struct cfg80211_chan_def *chandef = &phy->chandef;
132 struct iwl_phy_context_cmd cmd = {
133 .id_and_color = cpu_to_le32(phy->fw_id),
134 .action = cpu_to_le32(action),
135 .puncture_mask = cpu_to_le16(chandef->punctured),
136 /* Channel info */
137 .ci.channel = cpu_to_le32(chandef->chan->hw_value),
138 .ci.band = iwl_mld_nl80211_band_to_fw(chandef->chan->band),
139 .ci.width = iwl_mld_nl80211_width_to_fw(chandef->width),
140 .ci.ctrl_pos = iwl_mld_get_fw_ctrl_pos(chandef),
141 };
142 int ret;
143
144 if (ctx->ap.chan) {
145 cmd.sbb_bandwidth =
146 iwl_mld_nl80211_width_to_fw(ctx->ap.width);
147 cmd.sbb_ctrl_channel_loc = iwl_mld_get_fw_ctrl_pos(&ctx->ap);
148 }
149
150 ret = iwl_mld_send_cmd_pdu(mld, PHY_CONTEXT_CMD, &cmd);
151 if (ret)
152 IWL_ERR(mld, "Failed to send PHY_CONTEXT_CMD ret = %d\n", ret);
153
154 return ret;
155 }
156