|
@@ -168,3 +168,150 @@ bool ieee80211_set_channel_type(struct ieee80211_local *local,
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
+
|
|
|
+static struct ieee80211_chanctx *
|
|
|
+ieee80211_find_chanctx(struct ieee80211_local *local,
|
|
|
+ struct ieee80211_channel *channel,
|
|
|
+ enum nl80211_channel_type channel_type,
|
|
|
+ enum ieee80211_chanctx_mode mode)
|
|
|
+{
|
|
|
+ struct ieee80211_chanctx *ctx;
|
|
|
+
|
|
|
+ lockdep_assert_held(&local->chanctx_mtx);
|
|
|
+
|
|
|
+ if (mode == IEEE80211_CHANCTX_EXCLUSIVE)
|
|
|
+ return NULL;
|
|
|
+ if (WARN_ON(!channel))
|
|
|
+ return NULL;
|
|
|
+
|
|
|
+ list_for_each_entry(ctx, &local->chanctx_list, list) {
|
|
|
+ if (ctx->mode == IEEE80211_CHANCTX_EXCLUSIVE)
|
|
|
+ continue;
|
|
|
+ if (ctx->conf.channel != channel)
|
|
|
+ continue;
|
|
|
+ if (ctx->conf.channel_type != channel_type)
|
|
|
+ continue;
|
|
|
+
|
|
|
+ return ctx;
|
|
|
+ }
|
|
|
+
|
|
|
+ return NULL;
|
|
|
+}
|
|
|
+
|
|
|
+static struct ieee80211_chanctx *
|
|
|
+ieee80211_new_chanctx(struct ieee80211_local *local,
|
|
|
+ struct ieee80211_channel *channel,
|
|
|
+ enum nl80211_channel_type channel_type,
|
|
|
+ enum ieee80211_chanctx_mode mode)
|
|
|
+{
|
|
|
+ struct ieee80211_chanctx *ctx;
|
|
|
+
|
|
|
+ lockdep_assert_held(&local->chanctx_mtx);
|
|
|
+
|
|
|
+ ctx = kzalloc(sizeof(*ctx) + local->hw.chanctx_data_size, GFP_KERNEL);
|
|
|
+ if (!ctx)
|
|
|
+ return ERR_PTR(-ENOMEM);
|
|
|
+
|
|
|
+ ctx->conf.channel = channel;
|
|
|
+ ctx->conf.channel_type = channel_type;
|
|
|
+ ctx->mode = mode;
|
|
|
+
|
|
|
+ list_add(&ctx->list, &local->chanctx_list);
|
|
|
+
|
|
|
+ return ctx;
|
|
|
+}
|
|
|
+
|
|
|
+static void ieee80211_free_chanctx(struct ieee80211_local *local,
|
|
|
+ struct ieee80211_chanctx *ctx)
|
|
|
+{
|
|
|
+ lockdep_assert_held(&local->chanctx_mtx);
|
|
|
+
|
|
|
+ WARN_ON_ONCE(ctx->refcount != 0);
|
|
|
+
|
|
|
+ list_del(&ctx->list);
|
|
|
+ kfree_rcu(ctx, rcu_head);
|
|
|
+}
|
|
|
+
|
|
|
+static int ieee80211_assign_vif_chanctx(struct ieee80211_sub_if_data *sdata,
|
|
|
+ struct ieee80211_chanctx *ctx)
|
|
|
+{
|
|
|
+ struct ieee80211_local *local __maybe_unused = sdata->local;
|
|
|
+
|
|
|
+ lockdep_assert_held(&local->chanctx_mtx);
|
|
|
+
|
|
|
+ rcu_assign_pointer(sdata->vif.chanctx_conf, &ctx->conf);
|
|
|
+ ctx->refcount++;
|
|
|
+
|
|
|
+ return 0;
|
|
|
+}
|
|
|
+
|
|
|
+static void ieee80211_unassign_vif_chanctx(struct ieee80211_sub_if_data *sdata,
|
|
|
+ struct ieee80211_chanctx *ctx)
|
|
|
+{
|
|
|
+ struct ieee80211_local *local __maybe_unused = sdata->local;
|
|
|
+
|
|
|
+ lockdep_assert_held(&local->chanctx_mtx);
|
|
|
+
|
|
|
+ ctx->refcount--;
|
|
|
+ rcu_assign_pointer(sdata->vif.chanctx_conf, NULL);
|
|
|
+}
|
|
|
+
|
|
|
+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;
|
|
|
+
|
|
|
+ lockdep_assert_held(&local->chanctx_mtx);
|
|
|
+
|
|
|
+ conf = rcu_dereference_protected(sdata->vif.chanctx_conf,
|
|
|
+ lockdep_is_held(&local->chanctx_mtx));
|
|
|
+ if (!conf)
|
|
|
+ return;
|
|
|
+
|
|
|
+ ctx = container_of(conf, struct ieee80211_chanctx, conf);
|
|
|
+
|
|
|
+ ieee80211_unassign_vif_chanctx(sdata, ctx);
|
|
|
+ if (ctx->refcount == 0)
|
|
|
+ ieee80211_free_chanctx(local, ctx);
|
|
|
+}
|
|
|
+
|
|
|
+int ieee80211_vif_use_channel(struct ieee80211_sub_if_data *sdata,
|
|
|
+ struct ieee80211_channel *channel,
|
|
|
+ enum nl80211_channel_type channel_type,
|
|
|
+ enum ieee80211_chanctx_mode mode)
|
|
|
+{
|
|
|
+ struct ieee80211_local *local = sdata->local;
|
|
|
+ struct ieee80211_chanctx *ctx;
|
|
|
+ int ret;
|
|
|
+
|
|
|
+ mutex_lock(&local->chanctx_mtx);
|
|
|
+ __ieee80211_vif_release_channel(sdata);
|
|
|
+
|
|
|
+ ctx = ieee80211_find_chanctx(local, channel, channel_type, mode);
|
|
|
+ if (!ctx)
|
|
|
+ ctx = ieee80211_new_chanctx(local, channel, channel_type, mode);
|
|
|
+ if (IS_ERR(ctx)) {
|
|
|
+ ret = PTR_ERR(ctx);
|
|
|
+ goto out;
|
|
|
+ }
|
|
|
+
|
|
|
+ ret = ieee80211_assign_vif_chanctx(sdata, ctx);
|
|
|
+ if (ret) {
|
|
|
+ /* if assign fails refcount stays the same */
|
|
|
+ if (ctx->refcount == 0)
|
|
|
+ ieee80211_free_chanctx(local, ctx);
|
|
|
+ goto out;
|
|
|
+ }
|
|
|
+
|
|
|
+ out:
|
|
|
+ mutex_unlock(&local->chanctx_mtx);
|
|
|
+ return ret;
|
|
|
+}
|
|
|
+
|
|
|
+void ieee80211_vif_release_channel(struct ieee80211_sub_if_data *sdata)
|
|
|
+{
|
|
|
+ mutex_lock(&sdata->local->chanctx_mtx);
|
|
|
+ __ieee80211_vif_release_channel(sdata);
|
|
|
+ mutex_unlock(&sdata->local->chanctx_mtx);
|
|
|
+}
|