|
@@ -63,6 +63,20 @@ static bool ieee80211_can_create_new_chanctx(struct ieee80211_local *local)
|
|
|
return ieee80211_num_chanctx(local) < ieee80211_max_num_channels(local);
|
|
|
}
|
|
|
|
|
|
+static struct ieee80211_chanctx *
|
|
|
+ieee80211_vif_get_chanctx(struct ieee80211_sub_if_data *sdata)
|
|
|
+{
|
|
|
+ struct ieee80211_local *local = sdata->local;
|
|
|
+ struct ieee80211_chanctx_conf *conf;
|
|
|
+
|
|
|
+ conf = rcu_dereference_protected(sdata->vif.chanctx_conf,
|
|
|
+ lockdep_is_held(&local->chanctx_mtx));
|
|
|
+ if (!conf)
|
|
|
+ return NULL;
|
|
|
+
|
|
|
+ return container_of(conf, struct ieee80211_chanctx, conf);
|
|
|
+}
|
|
|
+
|
|
|
static const struct cfg80211_chan_def *
|
|
|
ieee80211_chanctx_reserved_chandef(struct ieee80211_local *local,
|
|
|
struct ieee80211_chanctx *ctx,
|
|
@@ -160,6 +174,9 @@ ieee80211_find_reservation_chanctx(struct ieee80211_local *local,
|
|
|
return NULL;
|
|
|
|
|
|
list_for_each_entry(ctx, &local->chanctx_list, list) {
|
|
|
+ if (ctx->replace_state == IEEE80211_CHANCTX_WILL_BE_REPLACED)
|
|
|
+ continue;
|
|
|
+
|
|
|
if (ctx->mode == IEEE80211_CHANCTX_EXCLUSIVE)
|
|
|
continue;
|
|
|
|
|
@@ -347,6 +364,9 @@ ieee80211_find_chanctx(struct ieee80211_local *local,
|
|
|
list_for_each_entry(ctx, &local->chanctx_list, list) {
|
|
|
const struct cfg80211_chan_def *compat;
|
|
|
|
|
|
+ if (ctx->replace_state != IEEE80211_CHANCTX_REPLACE_NONE)
|
|
|
+ continue;
|
|
|
+
|
|
|
if (ctx->mode == IEEE80211_CHANCTX_EXCLUSIVE)
|
|
|
continue;
|
|
|
|
|
@@ -622,6 +642,7 @@ static void __ieee80211_vif_release_channel(struct ieee80211_sub_if_data *sdata)
|
|
|
struct ieee80211_local *local = sdata->local;
|
|
|
struct ieee80211_chanctx_conf *conf;
|
|
|
struct ieee80211_chanctx *ctx;
|
|
|
+ bool use_reserved_switch = false;
|
|
|
|
|
|
lockdep_assert_held(&local->chanctx_mtx);
|
|
|
|
|
@@ -632,12 +653,23 @@ static void __ieee80211_vif_release_channel(struct ieee80211_sub_if_data *sdata)
|
|
|
|
|
|
ctx = container_of(conf, struct ieee80211_chanctx, conf);
|
|
|
|
|
|
- if (sdata->reserved_chanctx)
|
|
|
+ if (sdata->reserved_chanctx) {
|
|
|
+ if (sdata->reserved_chanctx->replace_state ==
|
|
|
+ IEEE80211_CHANCTX_REPLACES_OTHER &&
|
|
|
+ ieee80211_chanctx_num_reserved(local,
|
|
|
+ sdata->reserved_chanctx) > 1)
|
|
|
+ use_reserved_switch = true;
|
|
|
+
|
|
|
ieee80211_vif_unreserve_chanctx(sdata);
|
|
|
+ }
|
|
|
|
|
|
ieee80211_assign_vif_chanctx(sdata, NULL);
|
|
|
if (ieee80211_chanctx_refcount(local, ctx) == 0)
|
|
|
ieee80211_free_chanctx(local, ctx);
|
|
|
+
|
|
|
+ /* Unreserving may ready an in-place reservation. */
|
|
|
+ if (use_reserved_switch)
|
|
|
+ ieee80211_vif_use_reserved_switch(local);
|
|
|
}
|
|
|
|
|
|
void ieee80211_recalc_smps_chanctx(struct ieee80211_local *local,
|
|
@@ -905,8 +937,25 @@ int ieee80211_vif_unreserve_chanctx(struct ieee80211_sub_if_data *sdata)
|
|
|
list_del(&sdata->reserved_chanctx_list);
|
|
|
sdata->reserved_chanctx = NULL;
|
|
|
|
|
|
- if (ieee80211_chanctx_refcount(sdata->local, ctx) == 0)
|
|
|
- ieee80211_free_chanctx(sdata->local, ctx);
|
|
|
+ if (ieee80211_chanctx_refcount(sdata->local, ctx) == 0) {
|
|
|
+ if (ctx->replace_state == IEEE80211_CHANCTX_REPLACES_OTHER) {
|
|
|
+ if (WARN_ON(!ctx->replace_ctx))
|
|
|
+ return -EINVAL;
|
|
|
+
|
|
|
+ WARN_ON(ctx->replace_ctx->replace_state !=
|
|
|
+ IEEE80211_CHANCTX_WILL_BE_REPLACED);
|
|
|
+ WARN_ON(ctx->replace_ctx->replace_ctx != ctx);
|
|
|
+
|
|
|
+ ctx->replace_ctx->replace_ctx = NULL;
|
|
|
+ ctx->replace_ctx->replace_state =
|
|
|
+ IEEE80211_CHANCTX_REPLACE_NONE;
|
|
|
+
|
|
|
+ list_del_rcu(&ctx->list);
|
|
|
+ kfree_rcu(ctx, rcu_head);
|
|
|
+ } else {
|
|
|
+ ieee80211_free_chanctx(sdata->local, ctx);
|
|
|
+ }
|
|
|
+ }
|
|
|
|
|
|
return 0;
|
|
|
}
|
|
@@ -917,40 +966,84 @@ int ieee80211_vif_reserve_chanctx(struct ieee80211_sub_if_data *sdata,
|
|
|
bool radar_required)
|
|
|
{
|
|
|
struct ieee80211_local *local = sdata->local;
|
|
|
- struct ieee80211_chanctx_conf *conf;
|
|
|
- struct ieee80211_chanctx *new_ctx, *curr_ctx;
|
|
|
- int ret = 0;
|
|
|
+ struct ieee80211_chanctx *new_ctx, *curr_ctx, *ctx;
|
|
|
|
|
|
- mutex_lock(&local->chanctx_mtx);
|
|
|
-
|
|
|
- conf = rcu_dereference_protected(sdata->vif.chanctx_conf,
|
|
|
- lockdep_is_held(&local->chanctx_mtx));
|
|
|
- if (!conf) {
|
|
|
- ret = -EINVAL;
|
|
|
- goto out;
|
|
|
- }
|
|
|
+ lockdep_assert_held(&local->chanctx_mtx);
|
|
|
|
|
|
- curr_ctx = container_of(conf, struct ieee80211_chanctx, conf);
|
|
|
+ curr_ctx = ieee80211_vif_get_chanctx(sdata);
|
|
|
+ if (curr_ctx && local->use_chanctx && !local->ops->switch_vif_chanctx)
|
|
|
+ return -ENOTSUPP;
|
|
|
|
|
|
new_ctx = ieee80211_find_reservation_chanctx(local, chandef, mode);
|
|
|
if (!new_ctx) {
|
|
|
- if (ieee80211_chanctx_refcount(local, curr_ctx) == 1 &&
|
|
|
- (local->hw.flags & IEEE80211_HW_CHANGE_RUNNING_CHANCTX)) {
|
|
|
- /* if we're the only users of the chanctx and
|
|
|
- * the driver supports changing a running
|
|
|
- * context, reserve our current context
|
|
|
- */
|
|
|
- new_ctx = curr_ctx;
|
|
|
- } else if (ieee80211_can_create_new_chanctx(local)) {
|
|
|
- /* create a new context and reserve it */
|
|
|
+ if (ieee80211_can_create_new_chanctx(local)) {
|
|
|
new_ctx = ieee80211_new_chanctx(local, chandef, mode);
|
|
|
- if (IS_ERR(new_ctx)) {
|
|
|
- ret = PTR_ERR(new_ctx);
|
|
|
- goto out;
|
|
|
- }
|
|
|
+ if (IS_ERR(new_ctx))
|
|
|
+ return PTR_ERR(new_ctx);
|
|
|
} else {
|
|
|
- ret = -EBUSY;
|
|
|
- goto out;
|
|
|
+ if (!curr_ctx ||
|
|
|
+ (curr_ctx->replace_state ==
|
|
|
+ IEEE80211_CHANCTX_WILL_BE_REPLACED) ||
|
|
|
+ !list_empty(&curr_ctx->reserved_vifs)) {
|
|
|
+ /*
|
|
|
+ * Another vif already requested this context
|
|
|
+ * for a reservation. Find another one hoping
|
|
|
+ * all vifs assigned to it will also switch
|
|
|
+ * soon enough.
|
|
|
+ *
|
|
|
+ * TODO: This needs a little more work as some
|
|
|
+ * cases (more than 2 chanctx capable devices)
|
|
|
+ * may fail which could otherwise succeed
|
|
|
+ * provided some channel context juggling was
|
|
|
+ * performed.
|
|
|
+ *
|
|
|
+ * Consider ctx1..3, vif1..6, each ctx has 2
|
|
|
+ * vifs. vif1 and vif2 from ctx1 request new
|
|
|
+ * different chandefs starting 2 in-place
|
|
|
+ * reserations with ctx4 and ctx5 replacing
|
|
|
+ * ctx1 and ctx2 respectively. Next vif5 and
|
|
|
+ * vif6 from ctx3 reserve ctx4. If vif3 and
|
|
|
+ * vif4 remain on ctx2 as they are then this
|
|
|
+ * fails unless `replace_ctx` from ctx5 is
|
|
|
+ * replaced with ctx3.
|
|
|
+ */
|
|
|
+ list_for_each_entry(ctx, &local->chanctx_list,
|
|
|
+ list) {
|
|
|
+ if (ctx->replace_state !=
|
|
|
+ IEEE80211_CHANCTX_REPLACE_NONE)
|
|
|
+ continue;
|
|
|
+
|
|
|
+ if (!list_empty(&ctx->reserved_vifs))
|
|
|
+ continue;
|
|
|
+
|
|
|
+ curr_ctx = ctx;
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /*
|
|
|
+ * If that's true then all available contexts already
|
|
|
+ * have reservations and cannot be used.
|
|
|
+ */
|
|
|
+ if (!curr_ctx ||
|
|
|
+ (curr_ctx->replace_state ==
|
|
|
+ IEEE80211_CHANCTX_WILL_BE_REPLACED) ||
|
|
|
+ !list_empty(&curr_ctx->reserved_vifs))
|
|
|
+ return -EBUSY;
|
|
|
+
|
|
|
+ new_ctx = ieee80211_alloc_chanctx(local, chandef, mode);
|
|
|
+ if (!new_ctx)
|
|
|
+ return -ENOMEM;
|
|
|
+
|
|
|
+ new_ctx->replace_ctx = curr_ctx;
|
|
|
+ new_ctx->replace_state =
|
|
|
+ IEEE80211_CHANCTX_REPLACES_OTHER;
|
|
|
+
|
|
|
+ curr_ctx->replace_ctx = new_ctx;
|
|
|
+ curr_ctx->replace_state =
|
|
|
+ IEEE80211_CHANCTX_WILL_BE_REPLACED;
|
|
|
+
|
|
|
+ list_add_rcu(&new_ctx->list, &local->chanctx_list);
|
|
|
}
|
|
|
}
|
|
|
|
|
@@ -958,82 +1051,567 @@ int ieee80211_vif_reserve_chanctx(struct ieee80211_sub_if_data *sdata,
|
|
|
sdata->reserved_chanctx = new_ctx;
|
|
|
sdata->reserved_chandef = *chandef;
|
|
|
sdata->reserved_radar_required = radar_required;
|
|
|
-out:
|
|
|
- mutex_unlock(&local->chanctx_mtx);
|
|
|
- return ret;
|
|
|
+ sdata->reserved_ready = false;
|
|
|
+
|
|
|
+ return 0;
|
|
|
}
|
|
|
|
|
|
-int ieee80211_vif_use_reserved_context(struct ieee80211_sub_if_data *sdata,
|
|
|
- u32 *changed)
|
|
|
+static int
|
|
|
+ieee80211_vif_use_reserved_reassign(struct ieee80211_sub_if_data *sdata)
|
|
|
{
|
|
|
struct ieee80211_local *local = sdata->local;
|
|
|
- struct ieee80211_chanctx *ctx;
|
|
|
- struct ieee80211_chanctx *old_ctx;
|
|
|
- struct ieee80211_chanctx_conf *conf;
|
|
|
- int ret;
|
|
|
- u32 tmp_changed = *changed;
|
|
|
-
|
|
|
- /* TODO: need to recheck if the chandef is usable etc.? */
|
|
|
+ struct ieee80211_vif_chanctx_switch vif_chsw[1] = {};
|
|
|
+ struct ieee80211_chanctx *old_ctx, *new_ctx;
|
|
|
+ const struct cfg80211_chan_def *chandef;
|
|
|
+ u32 changed = 0;
|
|
|
+ int err;
|
|
|
|
|
|
lockdep_assert_held(&local->mtx);
|
|
|
+ lockdep_assert_held(&local->chanctx_mtx);
|
|
|
|
|
|
- mutex_lock(&local->chanctx_mtx);
|
|
|
+ new_ctx = sdata->reserved_chanctx;
|
|
|
+ old_ctx = ieee80211_vif_get_chanctx(sdata);
|
|
|
|
|
|
- ctx = sdata->reserved_chanctx;
|
|
|
- if (WARN_ON(!ctx)) {
|
|
|
- ret = -EINVAL;
|
|
|
- goto out;
|
|
|
- }
|
|
|
+ if (WARN_ON(!sdata->reserved_ready))
|
|
|
+ return -EBUSY;
|
|
|
|
|
|
- conf = rcu_dereference_protected(sdata->vif.chanctx_conf,
|
|
|
- lockdep_is_held(&local->chanctx_mtx));
|
|
|
- if (!conf) {
|
|
|
- ret = -EINVAL;
|
|
|
- goto out;
|
|
|
+ if (WARN_ON(!new_ctx))
|
|
|
+ return -EINVAL;
|
|
|
+
|
|
|
+ if (WARN_ON(!old_ctx))
|
|
|
+ return -EINVAL;
|
|
|
+
|
|
|
+ if (WARN_ON(new_ctx->replace_state ==
|
|
|
+ IEEE80211_CHANCTX_REPLACES_OTHER))
|
|
|
+ return -EINVAL;
|
|
|
+
|
|
|
+ chandef = ieee80211_chanctx_non_reserved_chandef(local, new_ctx,
|
|
|
+ &sdata->reserved_chandef);
|
|
|
+ if (WARN_ON(!chandef))
|
|
|
+ return -EINVAL;
|
|
|
+
|
|
|
+ vif_chsw[0].vif = &sdata->vif;
|
|
|
+ vif_chsw[0].old_ctx = &old_ctx->conf;
|
|
|
+ vif_chsw[0].new_ctx = &new_ctx->conf;
|
|
|
+
|
|
|
+ list_del(&sdata->reserved_chanctx_list);
|
|
|
+ sdata->reserved_chanctx = NULL;
|
|
|
+
|
|
|
+ err = drv_switch_vif_chanctx(local, vif_chsw, 1,
|
|
|
+ CHANCTX_SWMODE_REASSIGN_VIF);
|
|
|
+ if (err) {
|
|
|
+ if (ieee80211_chanctx_refcount(local, new_ctx) == 0)
|
|
|
+ ieee80211_free_chanctx(local, new_ctx);
|
|
|
+
|
|
|
+ return err;
|
|
|
}
|
|
|
|
|
|
- old_ctx = container_of(conf, struct ieee80211_chanctx, conf);
|
|
|
+ list_move(&sdata->assigned_chanctx_list, &new_ctx->assigned_vifs);
|
|
|
+ rcu_assign_pointer(sdata->vif.chanctx_conf, &new_ctx->conf);
|
|
|
+
|
|
|
+ if (sdata->vif.type == NL80211_IFTYPE_AP)
|
|
|
+ __ieee80211_vif_copy_chanctx_to_vlans(sdata, false);
|
|
|
+
|
|
|
+ if (ieee80211_chanctx_refcount(local, old_ctx) == 0)
|
|
|
+ ieee80211_free_chanctx(local, old_ctx);
|
|
|
|
|
|
if (sdata->vif.bss_conf.chandef.width != sdata->reserved_chandef.width)
|
|
|
- tmp_changed |= BSS_CHANGED_BANDWIDTH;
|
|
|
+ changed = BSS_CHANGED_BANDWIDTH;
|
|
|
|
|
|
sdata->vif.bss_conf.chandef = sdata->reserved_chandef;
|
|
|
|
|
|
- /* unref our reservation */
|
|
|
- sdata->reserved_chanctx = NULL;
|
|
|
- sdata->radar_required = sdata->reserved_radar_required;
|
|
|
+ if (changed)
|
|
|
+ ieee80211_bss_info_change_notify(sdata, changed);
|
|
|
+
|
|
|
+ return err;
|
|
|
+}
|
|
|
+
|
|
|
+static int
|
|
|
+ieee80211_vif_use_reserved_assign(struct ieee80211_sub_if_data *sdata)
|
|
|
+{
|
|
|
+ struct ieee80211_local *local = sdata->local;
|
|
|
+ struct ieee80211_chanctx *old_ctx, *new_ctx;
|
|
|
+ const struct cfg80211_chan_def *chandef;
|
|
|
+ int err;
|
|
|
+
|
|
|
+ old_ctx = ieee80211_vif_get_chanctx(sdata);
|
|
|
+ new_ctx = sdata->reserved_chanctx;
|
|
|
+
|
|
|
+ if (WARN_ON(!sdata->reserved_ready))
|
|
|
+ return -EINVAL;
|
|
|
+
|
|
|
+ if (WARN_ON(old_ctx))
|
|
|
+ return -EINVAL;
|
|
|
+
|
|
|
+ if (WARN_ON(!new_ctx))
|
|
|
+ return -EINVAL;
|
|
|
+
|
|
|
+ if (WARN_ON(new_ctx->replace_state ==
|
|
|
+ IEEE80211_CHANCTX_REPLACES_OTHER))
|
|
|
+ return -EINVAL;
|
|
|
+
|
|
|
+ chandef = ieee80211_chanctx_non_reserved_chandef(local, new_ctx,
|
|
|
+ &sdata->reserved_chandef);
|
|
|
+ if (WARN_ON(!chandef))
|
|
|
+ return -EINVAL;
|
|
|
+
|
|
|
list_del(&sdata->reserved_chanctx_list);
|
|
|
+ sdata->reserved_chanctx = NULL;
|
|
|
|
|
|
- if (old_ctx == ctx) {
|
|
|
- /* This is our own context, just change it */
|
|
|
- ret = __ieee80211_vif_change_channel(sdata, old_ctx,
|
|
|
- &tmp_changed);
|
|
|
- if (ret)
|
|
|
- goto out;
|
|
|
- } else {
|
|
|
- ret = ieee80211_assign_vif_chanctx(sdata, ctx);
|
|
|
- if (ieee80211_chanctx_refcount(local, old_ctx) == 0)
|
|
|
- ieee80211_free_chanctx(local, old_ctx);
|
|
|
- if (ret) {
|
|
|
- /* if assign fails refcount stays the same */
|
|
|
- if (ieee80211_chanctx_refcount(local, ctx) == 0)
|
|
|
- ieee80211_free_chanctx(local, ctx);
|
|
|
+ err = ieee80211_assign_vif_chanctx(sdata, new_ctx);
|
|
|
+ if (err) {
|
|
|
+ if (ieee80211_chanctx_refcount(local, new_ctx) == 0)
|
|
|
+ ieee80211_free_chanctx(local, new_ctx);
|
|
|
+
|
|
|
+ goto out;
|
|
|
+ }
|
|
|
+
|
|
|
+out:
|
|
|
+ return err;
|
|
|
+}
|
|
|
+
|
|
|
+static bool
|
|
|
+ieee80211_vif_has_in_place_reservation(struct ieee80211_sub_if_data *sdata)
|
|
|
+{
|
|
|
+ struct ieee80211_chanctx *old_ctx, *new_ctx;
|
|
|
+
|
|
|
+ lockdep_assert_held(&sdata->local->chanctx_mtx);
|
|
|
+
|
|
|
+ new_ctx = sdata->reserved_chanctx;
|
|
|
+ old_ctx = ieee80211_vif_get_chanctx(sdata);
|
|
|
+
|
|
|
+ if (!old_ctx)
|
|
|
+ return false;
|
|
|
+
|
|
|
+ if (WARN_ON(!new_ctx))
|
|
|
+ return false;
|
|
|
+
|
|
|
+ if (old_ctx->replace_state != IEEE80211_CHANCTX_WILL_BE_REPLACED)
|
|
|
+ return false;
|
|
|
+
|
|
|
+ if (new_ctx->replace_state != IEEE80211_CHANCTX_REPLACES_OTHER)
|
|
|
+ return false;
|
|
|
+
|
|
|
+ return true;
|
|
|
+}
|
|
|
+
|
|
|
+static int ieee80211_chsw_switch_hwconf(struct ieee80211_local *local,
|
|
|
+ struct ieee80211_chanctx *new_ctx)
|
|
|
+{
|
|
|
+ const struct cfg80211_chan_def *chandef;
|
|
|
+
|
|
|
+ lockdep_assert_held(&local->mtx);
|
|
|
+ lockdep_assert_held(&local->chanctx_mtx);
|
|
|
+
|
|
|
+ chandef = ieee80211_chanctx_reserved_chandef(local, new_ctx, NULL);
|
|
|
+ if (WARN_ON(!chandef))
|
|
|
+ return -EINVAL;
|
|
|
+
|
|
|
+ local->hw.conf.radar_enabled = new_ctx->conf.radar_enabled;
|
|
|
+ local->_oper_chandef = *chandef;
|
|
|
+ ieee80211_hw_config(local, 0);
|
|
|
+
|
|
|
+ return 0;
|
|
|
+}
|
|
|
+
|
|
|
+static int ieee80211_chsw_switch_vifs(struct ieee80211_local *local,
|
|
|
+ int n_vifs)
|
|
|
+{
|
|
|
+ struct ieee80211_vif_chanctx_switch *vif_chsw;
|
|
|
+ struct ieee80211_sub_if_data *sdata;
|
|
|
+ struct ieee80211_chanctx *ctx, *old_ctx;
|
|
|
+ int i, err;
|
|
|
+
|
|
|
+ lockdep_assert_held(&local->mtx);
|
|
|
+ lockdep_assert_held(&local->chanctx_mtx);
|
|
|
+
|
|
|
+ vif_chsw = kzalloc(sizeof(vif_chsw[0]) * n_vifs, GFP_KERNEL);
|
|
|
+ if (!vif_chsw)
|
|
|
+ return -ENOMEM;
|
|
|
+
|
|
|
+ i = 0;
|
|
|
+ list_for_each_entry(ctx, &local->chanctx_list, list) {
|
|
|
+ if (ctx->replace_state != IEEE80211_CHANCTX_REPLACES_OTHER)
|
|
|
+ continue;
|
|
|
+
|
|
|
+ if (WARN_ON(!ctx->replace_ctx)) {
|
|
|
+ err = -EINVAL;
|
|
|
goto out;
|
|
|
}
|
|
|
|
|
|
- if (sdata->vif.type == NL80211_IFTYPE_AP)
|
|
|
- __ieee80211_vif_copy_chanctx_to_vlans(sdata, false);
|
|
|
+ list_for_each_entry(sdata, &ctx->reserved_vifs,
|
|
|
+ reserved_chanctx_list) {
|
|
|
+ if (!ieee80211_vif_has_in_place_reservation(
|
|
|
+ sdata))
|
|
|
+ continue;
|
|
|
+
|
|
|
+ old_ctx = ieee80211_vif_get_chanctx(sdata);
|
|
|
+ vif_chsw[i].vif = &sdata->vif;
|
|
|
+ vif_chsw[i].old_ctx = &old_ctx->conf;
|
|
|
+ vif_chsw[i].new_ctx = &ctx->conf;
|
|
|
+
|
|
|
+ i++;
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
- *changed = tmp_changed;
|
|
|
+ err = drv_switch_vif_chanctx(local, vif_chsw, n_vifs,
|
|
|
+ CHANCTX_SWMODE_SWAP_CONTEXTS);
|
|
|
|
|
|
- ieee80211_recalc_chanctx_chantype(local, ctx);
|
|
|
- ieee80211_recalc_smps_chanctx(local, ctx);
|
|
|
- ieee80211_recalc_radar_chanctx(local, ctx);
|
|
|
- ieee80211_recalc_chanctx_min_def(local, ctx);
|
|
|
out:
|
|
|
- mutex_unlock(&local->chanctx_mtx);
|
|
|
- return ret;
|
|
|
+ kfree(vif_chsw);
|
|
|
+ return err;
|
|
|
+}
|
|
|
+
|
|
|
+static int ieee80211_chsw_switch_ctxs(struct ieee80211_local *local)
|
|
|
+{
|
|
|
+ struct ieee80211_chanctx *ctx;
|
|
|
+ int err;
|
|
|
+
|
|
|
+ lockdep_assert_held(&local->mtx);
|
|
|
+ lockdep_assert_held(&local->chanctx_mtx);
|
|
|
+
|
|
|
+ list_for_each_entry(ctx, &local->chanctx_list, list) {
|
|
|
+ if (ctx->replace_state != IEEE80211_CHANCTX_REPLACES_OTHER)
|
|
|
+ continue;
|
|
|
+
|
|
|
+ if (!list_empty(&ctx->replace_ctx->assigned_vifs))
|
|
|
+ continue;
|
|
|
+
|
|
|
+ ieee80211_del_chanctx(local, ctx->replace_ctx);
|
|
|
+ err = ieee80211_add_chanctx(local, ctx);
|
|
|
+ if (err)
|
|
|
+ goto err;
|
|
|
+ }
|
|
|
+
|
|
|
+ return 0;
|
|
|
+
|
|
|
+err:
|
|
|
+ WARN_ON(ieee80211_add_chanctx(local, ctx));
|
|
|
+ list_for_each_entry_continue_reverse(ctx, &local->chanctx_list, list) {
|
|
|
+ if (ctx->replace_state != IEEE80211_CHANCTX_REPLACES_OTHER)
|
|
|
+ continue;
|
|
|
+
|
|
|
+ if (!list_empty(&ctx->replace_ctx->assigned_vifs))
|
|
|
+ continue;
|
|
|
+
|
|
|
+ ieee80211_del_chanctx(local, ctx);
|
|
|
+ WARN_ON(ieee80211_add_chanctx(local, ctx->replace_ctx));
|
|
|
+ }
|
|
|
+
|
|
|
+ return err;
|
|
|
+}
|
|
|
+
|
|
|
+int
|
|
|
+ieee80211_vif_use_reserved_switch(struct ieee80211_local *local)
|
|
|
+{
|
|
|
+ struct ieee80211_sub_if_data *sdata, *sdata_tmp;
|
|
|
+ struct ieee80211_chanctx *ctx, *ctx_tmp, *old_ctx;
|
|
|
+ struct ieee80211_chanctx *new_ctx = NULL;
|
|
|
+ int i, err, n_assigned, n_reserved, n_ready;
|
|
|
+ int n_ctx = 0, n_vifs_switch = 0, n_vifs_assign = 0, n_vifs_ctxless = 0;
|
|
|
+
|
|
|
+ lockdep_assert_held(&local->mtx);
|
|
|
+ lockdep_assert_held(&local->chanctx_mtx);
|
|
|
+
|
|
|
+ /*
|
|
|
+ * If there are 2 independent pairs of channel contexts performing
|
|
|
+ * cross-switch of their vifs this code will still wait until both are
|
|
|
+ * ready even though it could be possible to switch one before the
|
|
|
+ * other is ready.
|
|
|
+ *
|
|
|
+ * For practical reasons and code simplicity just do a single huge
|
|
|
+ * switch.
|
|
|
+ */
|
|
|
+
|
|
|
+ /*
|
|
|
+ * Verify if the reservation is still feasible.
|
|
|
+ * - if it's not then disconnect
|
|
|
+ * - if it is but not all vifs necessary are ready then defer
|
|
|
+ */
|
|
|
+
|
|
|
+ list_for_each_entry(ctx, &local->chanctx_list, list) {
|
|
|
+ if (ctx->replace_state != IEEE80211_CHANCTX_REPLACES_OTHER)
|
|
|
+ continue;
|
|
|
+
|
|
|
+ if (WARN_ON(!ctx->replace_ctx)) {
|
|
|
+ err = -EINVAL;
|
|
|
+ goto err;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (!local->use_chanctx)
|
|
|
+ new_ctx = ctx;
|
|
|
+
|
|
|
+ n_ctx++;
|
|
|
+
|
|
|
+ n_assigned = 0;
|
|
|
+ n_reserved = 0;
|
|
|
+ n_ready = 0;
|
|
|
+
|
|
|
+ list_for_each_entry(sdata, &ctx->replace_ctx->assigned_vifs,
|
|
|
+ assigned_chanctx_list) {
|
|
|
+ n_assigned++;
|
|
|
+ if (sdata->reserved_chanctx) {
|
|
|
+ n_reserved++;
|
|
|
+ if (sdata->reserved_ready)
|
|
|
+ n_ready++;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ if (n_assigned != n_reserved) {
|
|
|
+ if (n_ready == n_reserved) {
|
|
|
+ wiphy_info(local->hw.wiphy,
|
|
|
+ "channel context reservation cannot be finalized because some interfaces aren't switching\n");
|
|
|
+ err = -EBUSY;
|
|
|
+ goto err;
|
|
|
+ }
|
|
|
+
|
|
|
+ return -EAGAIN;
|
|
|
+ }
|
|
|
+
|
|
|
+ ctx->conf.radar_enabled = false;
|
|
|
+ list_for_each_entry(sdata, &ctx->reserved_vifs,
|
|
|
+ reserved_chanctx_list) {
|
|
|
+ if (ieee80211_vif_has_in_place_reservation(sdata) &&
|
|
|
+ !sdata->reserved_ready)
|
|
|
+ return -EAGAIN;
|
|
|
+
|
|
|
+ old_ctx = ieee80211_vif_get_chanctx(sdata);
|
|
|
+ if (old_ctx) {
|
|
|
+ if (old_ctx->replace_state ==
|
|
|
+ IEEE80211_CHANCTX_WILL_BE_REPLACED)
|
|
|
+ n_vifs_switch++;
|
|
|
+ else
|
|
|
+ n_vifs_assign++;
|
|
|
+ } else {
|
|
|
+ n_vifs_ctxless++;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (sdata->reserved_radar_required)
|
|
|
+ ctx->conf.radar_enabled = true;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ if (WARN_ON(n_ctx == 0) ||
|
|
|
+ WARN_ON(n_vifs_switch == 0 &&
|
|
|
+ n_vifs_assign == 0 &&
|
|
|
+ n_vifs_ctxless == 0) ||
|
|
|
+ WARN_ON(n_ctx > 1 && !local->use_chanctx) ||
|
|
|
+ WARN_ON(!new_ctx && !local->use_chanctx)) {
|
|
|
+ err = -EINVAL;
|
|
|
+ goto err;
|
|
|
+ }
|
|
|
+
|
|
|
+ /*
|
|
|
+ * All necessary vifs are ready. Perform the switch now depending on
|
|
|
+ * reservations and driver capabilities.
|
|
|
+ */
|
|
|
+
|
|
|
+ if (local->use_chanctx) {
|
|
|
+ if (n_vifs_switch > 0) {
|
|
|
+ err = ieee80211_chsw_switch_vifs(local, n_vifs_switch);
|
|
|
+ if (err)
|
|
|
+ goto err;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (n_vifs_assign > 0 || n_vifs_ctxless > 0) {
|
|
|
+ err = ieee80211_chsw_switch_ctxs(local);
|
|
|
+ if (err)
|
|
|
+ goto err;
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ err = ieee80211_chsw_switch_hwconf(local, new_ctx);
|
|
|
+ if (err)
|
|
|
+ goto err;
|
|
|
+ }
|
|
|
+
|
|
|
+ /*
|
|
|
+ * Update all structures, values and pointers to point to new channel
|
|
|
+ * context(s).
|
|
|
+ */
|
|
|
+
|
|
|
+ i = 0;
|
|
|
+ list_for_each_entry(ctx, &local->chanctx_list, list) {
|
|
|
+ if (ctx->replace_state != IEEE80211_CHANCTX_REPLACES_OTHER)
|
|
|
+ continue;
|
|
|
+
|
|
|
+ if (WARN_ON(!ctx->replace_ctx)) {
|
|
|
+ err = -EINVAL;
|
|
|
+ goto err;
|
|
|
+ }
|
|
|
+
|
|
|
+ list_for_each_entry(sdata, &ctx->reserved_vifs,
|
|
|
+ reserved_chanctx_list) {
|
|
|
+ u32 changed = 0;
|
|
|
+
|
|
|
+ if (!ieee80211_vif_has_in_place_reservation(sdata))
|
|
|
+ continue;
|
|
|
+
|
|
|
+ rcu_assign_pointer(sdata->vif.chanctx_conf, &ctx->conf);
|
|
|
+
|
|
|
+ if (sdata->vif.type == NL80211_IFTYPE_AP)
|
|
|
+ __ieee80211_vif_copy_chanctx_to_vlans(sdata,
|
|
|
+ false);
|
|
|
+
|
|
|
+ sdata->radar_required = sdata->reserved_radar_required;
|
|
|
+
|
|
|
+ if (sdata->vif.bss_conf.chandef.width !=
|
|
|
+ sdata->reserved_chandef.width)
|
|
|
+ changed = BSS_CHANGED_BANDWIDTH;
|
|
|
+
|
|
|
+ sdata->vif.bss_conf.chandef = sdata->reserved_chandef;
|
|
|
+ if (changed)
|
|
|
+ ieee80211_bss_info_change_notify(sdata,
|
|
|
+ changed);
|
|
|
+
|
|
|
+ ieee80211_recalc_txpower(sdata);
|
|
|
+ }
|
|
|
+
|
|
|
+ ieee80211_recalc_chanctx_chantype(local, ctx);
|
|
|
+ ieee80211_recalc_smps_chanctx(local, ctx);
|
|
|
+ ieee80211_recalc_radar_chanctx(local, ctx);
|
|
|
+ ieee80211_recalc_chanctx_min_def(local, ctx);
|
|
|
+
|
|
|
+ list_for_each_entry_safe(sdata, sdata_tmp, &ctx->reserved_vifs,
|
|
|
+ reserved_chanctx_list) {
|
|
|
+ if (ieee80211_vif_get_chanctx(sdata) != ctx)
|
|
|
+ continue;
|
|
|
+
|
|
|
+ list_del(&sdata->reserved_chanctx_list);
|
|
|
+ list_move(&sdata->assigned_chanctx_list,
|
|
|
+ &new_ctx->assigned_vifs);
|
|
|
+ sdata->reserved_chanctx = NULL;
|
|
|
+ }
|
|
|
+
|
|
|
+ /*
|
|
|
+ * This context might have been a dependency for an already
|
|
|
+ * ready re-assign reservation interface that was deferred. Do
|
|
|
+ * not propagate error to the caller though. The in-place
|
|
|
+ * reservation for originally requested interface has already
|
|
|
+ * succeeded at this point.
|
|
|
+ */
|
|
|
+ list_for_each_entry_safe(sdata, sdata_tmp, &ctx->reserved_vifs,
|
|
|
+ reserved_chanctx_list) {
|
|
|
+ if (WARN_ON(ieee80211_vif_has_in_place_reservation(
|
|
|
+ sdata)))
|
|
|
+ continue;
|
|
|
+
|
|
|
+ if (WARN_ON(sdata->reserved_chanctx != ctx))
|
|
|
+ continue;
|
|
|
+
|
|
|
+ if (!sdata->reserved_ready)
|
|
|
+ continue;
|
|
|
+
|
|
|
+ if (ieee80211_vif_get_chanctx(sdata))
|
|
|
+ err = ieee80211_vif_use_reserved_reassign(
|
|
|
+ sdata);
|
|
|
+ else
|
|
|
+ err = ieee80211_vif_use_reserved_assign(sdata);
|
|
|
+
|
|
|
+ if (err) {
|
|
|
+ sdata_info(sdata,
|
|
|
+ "failed to finalize (re-)assign reservation (err=%d)\n",
|
|
|
+ err);
|
|
|
+ ieee80211_vif_unreserve_chanctx(sdata);
|
|
|
+ cfg80211_stop_iface(local->hw.wiphy,
|
|
|
+ &sdata->wdev,
|
|
|
+ GFP_KERNEL);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /*
|
|
|
+ * Finally free old contexts
|
|
|
+ */
|
|
|
+
|
|
|
+ list_for_each_entry_safe(ctx, ctx_tmp, &local->chanctx_list, list) {
|
|
|
+ if (ctx->replace_state != IEEE80211_CHANCTX_WILL_BE_REPLACED)
|
|
|
+ continue;
|
|
|
+
|
|
|
+ ctx->replace_ctx->replace_ctx = NULL;
|
|
|
+ ctx->replace_ctx->replace_state =
|
|
|
+ IEEE80211_CHANCTX_REPLACE_NONE;
|
|
|
+
|
|
|
+ list_del_rcu(&ctx->list);
|
|
|
+ kfree_rcu(ctx, rcu_head);
|
|
|
+ }
|
|
|
+
|
|
|
+ return 0;
|
|
|
+
|
|
|
+err:
|
|
|
+ list_for_each_entry(ctx, &local->chanctx_list, list) {
|
|
|
+ if (ctx->replace_state != IEEE80211_CHANCTX_REPLACES_OTHER)
|
|
|
+ continue;
|
|
|
+
|
|
|
+ list_for_each_entry_safe(sdata, sdata_tmp, &ctx->reserved_vifs,
|
|
|
+ reserved_chanctx_list)
|
|
|
+ ieee80211_vif_unreserve_chanctx(sdata);
|
|
|
+ }
|
|
|
+
|
|
|
+ return err;
|
|
|
+}
|
|
|
+
|
|
|
+int ieee80211_vif_use_reserved_context(struct ieee80211_sub_if_data *sdata)
|
|
|
+{
|
|
|
+ struct ieee80211_local *local = sdata->local;
|
|
|
+ struct ieee80211_chanctx *new_ctx;
|
|
|
+ struct ieee80211_chanctx *old_ctx;
|
|
|
+ int err;
|
|
|
+
|
|
|
+ lockdep_assert_held(&local->mtx);
|
|
|
+ lockdep_assert_held(&local->chanctx_mtx);
|
|
|
+
|
|
|
+ new_ctx = sdata->reserved_chanctx;
|
|
|
+ old_ctx = ieee80211_vif_get_chanctx(sdata);
|
|
|
+
|
|
|
+ if (WARN_ON(!new_ctx))
|
|
|
+ return -EINVAL;
|
|
|
+
|
|
|
+ if (WARN_ON(new_ctx->replace_state ==
|
|
|
+ IEEE80211_CHANCTX_WILL_BE_REPLACED))
|
|
|
+ return -EINVAL;
|
|
|
+
|
|
|
+ if (WARN_ON(sdata->reserved_ready))
|
|
|
+ return -EINVAL;
|
|
|
+
|
|
|
+ sdata->reserved_ready = true;
|
|
|
+
|
|
|
+ if (new_ctx->replace_state == IEEE80211_CHANCTX_REPLACE_NONE) {
|
|
|
+ if (old_ctx)
|
|
|
+ err = ieee80211_vif_use_reserved_reassign(sdata);
|
|
|
+ else
|
|
|
+ err = ieee80211_vif_use_reserved_assign(sdata);
|
|
|
+
|
|
|
+ if (err)
|
|
|
+ return err;
|
|
|
+ }
|
|
|
+
|
|
|
+ /*
|
|
|
+ * In-place reservation may need to be finalized now either if:
|
|
|
+ * a) sdata is taking part in the swapping itself and is the last one
|
|
|
+ * b) sdata has switched with a re-assign reservation to an existing
|
|
|
+ * context readying in-place switching of old_ctx
|
|
|
+ *
|
|
|
+ * In case of (b) do not propagate the error up because the requested
|
|
|
+ * sdata already switched successfully. Just spill an extra warning.
|
|
|
+ * The ieee80211_vif_use_reserved_switch() already stops all necessary
|
|
|
+ * interfaces upon failure.
|
|
|
+ */
|
|
|
+ if ((old_ctx &&
|
|
|
+ old_ctx->replace_state == IEEE80211_CHANCTX_WILL_BE_REPLACED) ||
|
|
|
+ new_ctx->replace_state == IEEE80211_CHANCTX_REPLACES_OTHER) {
|
|
|
+ err = ieee80211_vif_use_reserved_switch(local);
|
|
|
+ if (err && err != -EAGAIN) {
|
|
|
+ if (new_ctx->replace_state ==
|
|
|
+ IEEE80211_CHANCTX_REPLACES_OTHER)
|
|
|
+ return err;
|
|
|
+
|
|
|
+ wiphy_info(local->hw.wiphy,
|
|
|
+ "depending in-place reservation failed (err=%d)\n",
|
|
|
+ err);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ return 0;
|
|
|
}
|
|
|
|
|
|
int ieee80211_vif_change_bandwidth(struct ieee80211_sub_if_data *sdata,
|
|
@@ -1043,6 +1621,7 @@ int ieee80211_vif_change_bandwidth(struct ieee80211_sub_if_data *sdata,
|
|
|
struct ieee80211_local *local = sdata->local;
|
|
|
struct ieee80211_chanctx_conf *conf;
|
|
|
struct ieee80211_chanctx *ctx;
|
|
|
+ const struct cfg80211_chan_def *compat;
|
|
|
int ret;
|
|
|
|
|
|
if (!cfg80211_chandef_usable(sdata->local->hw.wiphy, chandef,
|
|
@@ -1069,11 +1648,33 @@ int ieee80211_vif_change_bandwidth(struct ieee80211_sub_if_data *sdata,
|
|
|
}
|
|
|
|
|
|
ctx = container_of(conf, struct ieee80211_chanctx, conf);
|
|
|
- if (!cfg80211_chandef_compatible(&conf->def, chandef)) {
|
|
|
+
|
|
|
+ compat = cfg80211_chandef_compatible(&conf->def, chandef);
|
|
|
+ if (!compat) {
|
|
|
ret = -EINVAL;
|
|
|
goto out;
|
|
|
}
|
|
|
|
|
|
+ switch (ctx->replace_state) {
|
|
|
+ case IEEE80211_CHANCTX_REPLACE_NONE:
|
|
|
+ if (!ieee80211_chanctx_reserved_chandef(local, ctx, compat)) {
|
|
|
+ ret = -EBUSY;
|
|
|
+ goto out;
|
|
|
+ }
|
|
|
+ break;
|
|
|
+ case IEEE80211_CHANCTX_WILL_BE_REPLACED:
|
|
|
+ /* TODO: Perhaps the bandwith change could be treated as a
|
|
|
+ * reservation itself? */
|
|
|
+ ret = -EBUSY;
|
|
|
+ goto out;
|
|
|
+ case IEEE80211_CHANCTX_REPLACES_OTHER:
|
|
|
+ /* channel context that is going to replace another channel
|
|
|
+ * context doesn't really exist and shouldn't be assigned
|
|
|
+ * anywhere yet */
|
|
|
+ WARN_ON(1);
|
|
|
+ break;
|
|
|
+ }
|
|
|
+
|
|
|
sdata->vif.bss_conf.chandef = *chandef;
|
|
|
|
|
|
ieee80211_recalc_chanctx_chantype(local, ctx);
|