summaryrefslogtreecommitdiffstats
path: root/net/mac80211/chan.c
diff options
context:
space:
mode:
authorJohannes Berg <johannes.berg@intel.com>2024-01-29 19:34:42 +0100
committerJohannes Berg <johannes.berg@intel.com>2024-02-08 13:07:37 +0100
commit761748f001800d925c2ee8b04407e7aee12c3ffb (patch)
treec27f1469ed37f340073d9b8b6ccc48c6fb8cd3f5 /net/mac80211/chan.c
parentd1256c1546a0e03f103e2f0381103dc747ea7f81 (diff)
downloadlinux-761748f001800d925c2ee8b04407e7aee12c3ffb.tar.gz
linux-761748f001800d925c2ee8b04407e7aee12c3ffb.tar.bz2
linux-761748f001800d925c2ee8b04407e7aee12c3ffb.zip
wifi: mac80211: support wider bandwidth OFDMA config
EHT requires that stations are able to participate in wider bandwidth OFDMA, i.e. parse downlink OFDMA and uplink OFDMA triggers when they're not capable of (or not connected at) the (wider) bandwidth that the AP is using. This requires hardware configuration, since the entity responsible for parsing (possibly hardware) needs to know the AP bandwidth. To support this, change the channel request to have the AP's bandwidth for clients, and track that in the channel context in mac80211. This means that the same chandef might need to be split up into two different contexts, if the APs are different. Interfaces other than client are not participating in OFDMA the same way, so they don't request any AP setting. Note that this doesn't introduce any API to split a channel context, so that there are cases where this might lead to a disconnect, e.g. if there are two client interfaces using the same channel context, e.g. both 160 MHz connected to different 320 MHz APs, and one of the APs switches to 160 MHz. Note also there are possible cases where this can be optimised, e.g. when using the upper or lower 160 Mhz, but I haven't been able to really fully understand the spec and/or hardware limitations. If, for some reason, there are no hardware limits on this because the OFDMA (downlink/trigger) parsing is done in firmware and can take the transmitter into account, then drivers can set the new flag IEEE80211_VIF_IGNORE_OFDMA_WIDER_BW on interfaces to not have them request any AP bandwidth in the channel context and ignore this issue entirely. The bss_conf still contains the AP configuration (if any, i.e. EHT) in the chanreq. Link: https://msgid.link/20240129194108.d3d5b35dd783.I939d04674f4ff06f39934b1591c8d36a30ce74c2@changeid Signed-off-by: Johannes Berg <johannes.berg@intel.com>
Diffstat (limited to 'net/mac80211/chan.c')
-rw-r--r--net/mac80211/chan.c167
1 files changed, 126 insertions, 41 deletions
diff --git a/net/mac80211/chan.c b/net/mac80211/chan.c
index c84449bdc928..fe1489ba58c6 100644
--- a/net/mac80211/chan.c
+++ b/net/mac80211/chan.c
@@ -81,22 +81,35 @@ ieee80211_link_get_chanctx(struct ieee80211_link_data *link)
return container_of(conf, struct ieee80211_chanctx, conf);
}
+bool ieee80211_chanreq_identical(const struct ieee80211_chan_req *a,
+ const struct ieee80211_chan_req *b)
+{
+ if (!cfg80211_chandef_identical(&a->oper, &b->oper))
+ return false;
+ if (!a->ap.chan && !b->ap.chan)
+ return true;
+ return cfg80211_chandef_identical(&a->ap, &b->ap);
+}
+
static const struct ieee80211_chan_req *
ieee80211_chanreq_compatible(const struct ieee80211_chan_req *a,
- const struct ieee80211_chan_req *b)
+ const struct ieee80211_chan_req *b,
+ struct ieee80211_chan_req *tmp)
{
const struct cfg80211_chan_def *compat;
- compat = cfg80211_chandef_compatible(&a->oper, &b->oper);
-
- if (compat == &a->oper)
- return a;
+ if (a->ap.chan && b->ap.chan &&
+ !cfg80211_chandef_identical(&a->ap, &b->ap))
+ return NULL;
- if (compat == &b->oper)
- return b;
+ compat = cfg80211_chandef_compatible(&a->oper, &b->oper);
+ if (!compat)
+ return NULL;
- WARN_ON(compat);
- return NULL;
+ /* Note: later code assumes this always fills & returns tmp if compat */
+ tmp->oper = *compat;
+ tmp->ap = a->ap.chan ? a->ap : b->ap;
+ return tmp;
}
static const struct ieee80211_chan_req *
@@ -104,17 +117,26 @@ ieee80211_chanctx_compatible(struct ieee80211_chanctx *ctx,
const struct ieee80211_chan_req *req,
struct ieee80211_chan_req *tmp)
{
+ const struct ieee80211_chan_req *ret;
+ struct ieee80211_chan_req tmp2;
+
*tmp = (struct ieee80211_chan_req){
.oper = ctx->conf.def,
+ .ap = ctx->conf.ap,
};
- return ieee80211_chanreq_compatible(tmp, req);
+ ret = ieee80211_chanreq_compatible(tmp, req, &tmp2);
+ if (!ret)
+ return NULL;
+ *tmp = *ret;
+ return tmp;
}
static const struct ieee80211_chan_req *
ieee80211_chanctx_reserved_chanreq(struct ieee80211_local *local,
struct ieee80211_chanctx *ctx,
- const struct ieee80211_chan_req *req)
+ const struct ieee80211_chan_req *req,
+ struct ieee80211_chan_req *tmp)
{
struct ieee80211_link_data *link;
@@ -124,7 +146,7 @@ ieee80211_chanctx_reserved_chanreq(struct ieee80211_local *local,
return NULL;
list_for_each_entry(link, &ctx->reserved_links, reserved_chanctx_list) {
- req = ieee80211_chanreq_compatible(&link->reserved, req);
+ req = ieee80211_chanreq_compatible(&link->reserved, req, tmp);
if (!req)
break;
}
@@ -135,7 +157,8 @@ ieee80211_chanctx_reserved_chanreq(struct ieee80211_local *local,
static const struct ieee80211_chan_req *
ieee80211_chanctx_non_reserved_chandef(struct ieee80211_local *local,
struct ieee80211_chanctx *ctx,
- const struct ieee80211_chan_req *compat)
+ const struct ieee80211_chan_req *compat,
+ struct ieee80211_chan_req *tmp)
{
struct ieee80211_link_data *link;
const struct ieee80211_chan_req *comp_def = compat;
@@ -149,7 +172,7 @@ ieee80211_chanctx_non_reserved_chandef(struct ieee80211_local *local,
continue;
comp_def = ieee80211_chanreq_compatible(&link_conf->chanreq,
- comp_def);
+ comp_def, tmp);
if (!comp_def)
break;
}
@@ -162,16 +185,18 @@ ieee80211_chanctx_can_reserve(struct ieee80211_local *local,
struct ieee80211_chanctx *ctx,
const struct ieee80211_chan_req *req)
{
+ struct ieee80211_chan_req tmp;
+
lockdep_assert_wiphy(local->hw.wiphy);
- if (!ieee80211_chanctx_reserved_chanreq(local, ctx, req))
+ if (!ieee80211_chanctx_reserved_chanreq(local, ctx, req, &tmp))
return false;
- if (!ieee80211_chanctx_non_reserved_chandef(local, ctx, req))
+ if (!ieee80211_chanctx_non_reserved_chandef(local, ctx, req, &tmp))
return false;
if (!list_empty(&ctx->reserved_links) &&
- ieee80211_chanctx_reserved_chanreq(local, ctx, req))
+ ieee80211_chanctx_reserved_chanreq(local, ctx, req, &tmp))
return true;
return false;
@@ -456,7 +481,11 @@ static void _ieee80211_change_chanctx(struct ieee80211_local *local,
struct ieee80211_link_data *rsvd_for)
{
const struct cfg80211_chan_def *chandef = &chanreq->oper;
- u32 changed;
+ struct ieee80211_chan_req ctx_req = {
+ .oper = ctx->conf.def,
+ .ap = ctx->conf.ap,
+ };
+ u32 changed = 0;
/* expected to handle only 20/40/80/160/320 channel widths */
switch (chandef->width) {
@@ -478,26 +507,31 @@ static void _ieee80211_change_chanctx(struct ieee80211_local *local,
*/
ieee80211_chan_bw_change(local, old_ctx, true);
- if (cfg80211_chandef_identical(&ctx->conf.def, chandef)) {
+ if (ieee80211_chanreq_identical(&ctx_req, chanreq)) {
ieee80211_recalc_chanctx_min_def(local, ctx, rsvd_for);
return;
}
- WARN_ON(!cfg80211_chandef_compatible(&ctx->conf.def, chandef));
+ WARN_ON(ieee80211_chanctx_refcount(local, ctx) > 1 &&
+ !cfg80211_chandef_compatible(&ctx->conf.def, &chanreq->oper));
ieee80211_remove_wbrf(local, &ctx->conf.def);
+ if (!cfg80211_chandef_identical(&ctx->conf.def, &chanreq->oper))
+ changed |= IEEE80211_CHANCTX_CHANGE_WIDTH;
+ if (!cfg80211_chandef_identical(&ctx->conf.ap, &chanreq->ap))
+ changed |= IEEE80211_CHANCTX_CHANGE_AP;
ctx->conf.def = *chandef;
+ ctx->conf.ap = chanreq->ap;
/* check if min chanctx also changed */
- changed = IEEE80211_CHANCTX_CHANGE_WIDTH |
- _ieee80211_recalc_chanctx_min_def(local, ctx, rsvd_for);
+ changed |= _ieee80211_recalc_chanctx_min_def(local, ctx, rsvd_for);
ieee80211_add_wbrf(local, &ctx->conf.def);
drv_change_chanctx(local, ctx, changed);
- /* check is BW wider */
+ /* check if BW is wider */
ieee80211_chan_bw_change(local, old_ctx, false);
}
@@ -536,7 +570,7 @@ ieee80211_find_chanctx(struct ieee80211_local *local,
continue;
compat = ieee80211_chanctx_reserved_chanreq(local, ctx,
- compat);
+ compat, &tmp);
if (!compat)
continue;
@@ -598,6 +632,7 @@ ieee80211_alloc_chanctx(struct ieee80211_local *local,
INIT_LIST_HEAD(&ctx->assigned_links);
INIT_LIST_HEAD(&ctx->reserved_links);
ctx->conf.def = chanreq->oper;
+ ctx->conf.ap = chanreq->ap;
ctx->conf.rx_chains_static = 1;
ctx->conf.rx_chains_dynamic = 1;
ctx->mode = mode;
@@ -683,9 +718,9 @@ void ieee80211_recalc_chanctx_chantype(struct ieee80211_local *local,
struct ieee80211_chanctx *ctx)
{
struct ieee80211_chanctx_conf *conf = &ctx->conf;
- const struct cfg80211_chan_def *compat = NULL;
+ const struct ieee80211_chan_req *compat = NULL;
struct ieee80211_link_data *link;
- struct ieee80211_chan_req chanreq = {};
+ struct ieee80211_chan_req tmp;
struct sta_info *sta;
lockdep_assert_wiphy(local->hw.wiphy);
@@ -702,10 +737,10 @@ void ieee80211_recalc_chanctx_chantype(struct ieee80211_local *local,
continue;
if (!compat)
- compat = &link_conf->chanreq.oper;
+ compat = &link_conf->chanreq;
- compat = cfg80211_chandef_compatible(&link_conf->chanreq.oper,
- compat);
+ compat = ieee80211_chanreq_compatible(&link_conf->chanreq,
+ compat, &tmp);
if (WARN_ON_ONCE(!compat))
return;
}
@@ -715,24 +750,23 @@ void ieee80211_recalc_chanctx_chantype(struct ieee80211_local *local,
/* TDLS peers can sometimes affect the chandef width */
list_for_each_entry(sta, &local->sta_list, list) {
+ struct ieee80211_chan_req tdls_chanreq = {};
if (!sta->uploaded ||
!test_sta_flag(sta, WLAN_STA_TDLS_WIDER_BW) ||
!test_sta_flag(sta, WLAN_STA_AUTHORIZED) ||
!sta->tdls_chandef.chan)
continue;
- compat = cfg80211_chandef_compatible(&sta->tdls_chandef,
- compat);
+ tdls_chanreq.oper = sta->tdls_chandef;
+
+ /* note this always fills and returns &tmp if compat */
+ compat = ieee80211_chanreq_compatible(&tdls_chanreq,
+ compat, &tmp);
if (WARN_ON_ONCE(!compat))
return;
}
- if (!compat)
- return;
-
- chanreq.oper = *compat;
-
- ieee80211_change_chanctx(local, ctx, ctx, &chanreq);
+ ieee80211_change_chanctx(local, ctx, ctx, compat);
}
static void ieee80211_recalc_radar_chanctx(struct ieee80211_local *local,
@@ -1133,6 +1167,7 @@ ieee80211_link_use_reserved_reassign(struct ieee80211_link_data *link)
struct ieee80211_vif_chanctx_switch vif_chsw[1] = {};
struct ieee80211_chanctx *old_ctx, *new_ctx;
const struct ieee80211_chan_req *chanreq;
+ struct ieee80211_chan_req tmp;
u64 changed = 0;
int err;
@@ -1155,7 +1190,8 @@ ieee80211_link_use_reserved_reassign(struct ieee80211_link_data *link)
return -EINVAL;
chanreq = ieee80211_chanctx_non_reserved_chandef(local, new_ctx,
- &link->reserved);
+ &link->reserved,
+ &tmp);
if (WARN_ON(!chanreq))
return -EINVAL;
@@ -1213,6 +1249,7 @@ ieee80211_link_use_reserved_assign(struct ieee80211_link_data *link)
struct ieee80211_local *local = sdata->local;
struct ieee80211_chanctx *old_ctx, *new_ctx;
const struct ieee80211_chan_req *chanreq;
+ struct ieee80211_chan_req tmp;
int err;
old_ctx = ieee80211_link_get_chanctx(link);
@@ -1232,7 +1269,8 @@ ieee80211_link_use_reserved_assign(struct ieee80211_link_data *link)
return -EINVAL;
chanreq = ieee80211_chanctx_non_reserved_chandef(local, new_ctx,
- &link->reserved);
+ &link->reserved,
+ &tmp);
if (WARN_ON(!chanreq))
return -EINVAL;
@@ -1770,6 +1808,52 @@ int ieee80211_link_use_reserved_context(struct ieee80211_link_data *link)
return 0;
}
+/*
+ * This is similar to ieee80211_chanctx_compatible(), but rechecks
+ * against all the links actually using it (except the one that's
+ * passed, since that one is changing).
+ * This is done in order to allow changes to the AP's bandwidth for
+ * wider bandwidth OFDMA purposes, which wouldn't be treated as
+ * compatible by ieee80211_chanctx_recheck() but is OK if the link
+ * requesting the update is the only one using it.
+ */
+static const struct ieee80211_chan_req *
+ieee80211_chanctx_recheck(struct ieee80211_local *local,
+ struct ieee80211_link_data *skip_link,
+ struct ieee80211_chanctx *ctx,
+ const struct ieee80211_chan_req *req,
+ struct ieee80211_chan_req *tmp)
+{
+ const struct ieee80211_chan_req *ret = req;
+ struct ieee80211_link_data *link;
+
+ lockdep_assert_wiphy(local->hw.wiphy);
+
+ for_each_sdata_link(local, link) {
+ if (link == skip_link)
+ continue;
+
+ if (rcu_access_pointer(link->conf->chanctx_conf) == &ctx->conf) {
+ ret = ieee80211_chanreq_compatible(ret,
+ &link->conf->chanreq,
+ tmp);
+ if (!ret)
+ return NULL;
+ }
+
+ if (link->reserved_chanctx == ctx) {
+ ret = ieee80211_chanreq_compatible(ret,
+ &link->reserved,
+ tmp);
+ if (!ret)
+ return NULL;
+ }
+ }
+
+ *tmp = *ret;
+ return tmp;
+}
+
int ieee80211_link_change_chanreq(struct ieee80211_link_data *link,
const struct ieee80211_chan_req *chanreq,
u64 *changed)
@@ -1806,13 +1890,14 @@ int ieee80211_link_change_chanreq(struct ieee80211_link_data *link,
ctx = container_of(conf, struct ieee80211_chanctx, conf);
- compat = ieee80211_chanctx_compatible(ctx, chanreq, &tmp);
+ compat = ieee80211_chanctx_recheck(local, link, ctx, chanreq, &tmp);
if (!compat)
return -EINVAL;
switch (ctx->replace_state) {
case IEEE80211_CHANCTX_REPLACE_NONE:
- if (!ieee80211_chanctx_reserved_chanreq(local, ctx, compat))
+ if (!ieee80211_chanctx_reserved_chanreq(local, ctx, compat,
+ &tmp))
return -EBUSY;
break;
case IEEE80211_CHANCTX_WILL_BE_REPLACED: