summaryrefslogtreecommitdiffstats
path: root/net/mac80211/chan.c
diff options
context:
space:
mode:
authorMichal Kazior <michal.kazior@tieto.com>2012-06-26 14:37:16 +0200
committerJohannes Berg <johannes.berg@intel.com>2012-10-16 20:22:41 +0200
commitd01a1e658606a0a69100f49c2ef09aacaf74d3e7 (patch)
tree30de068699ed95997ad44f4d0f703a6deb0ad95b /net/mac80211/chan.c
parentddffeb8c4d0331609ef2581d84de4d763607bd37 (diff)
mac80211: introduce channel context skeleton code
Channel context are the foundation for multi-channel operation. They are are immutable and are re-created (or re-used if other interfaces are bound to a certain channel and a compatible channel type) on channel switching. This is an initial implementation and more features will come in separate patches. Signed-off-by: Michal Kazior <michal.kazior@tieto.com> [some changes including RCU protection] Signed-off-by: Johannes Berg <johannes.berg@intel.com>
Diffstat (limited to 'net/mac80211/chan.c')
-rw-r--r--net/mac80211/chan.c147
1 files changed, 147 insertions, 0 deletions
diff --git a/net/mac80211/chan.c b/net/mac80211/chan.c
index 0bfc914ddd1..4ae94860a16 100644
--- a/net/mac80211/chan.c
+++ b/net/mac80211/chan.c
@@ -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);
+}