55de908ab2
Instead of operating on a single channel only, use the new channel context infrastructure in all mac80211 code. This enables drivers that want to use the new channel context infrastructure to use multiple channels, while nothing should change for all the other drivers that don't support it. Right now this disables both TX power settings and spatial multiplexing powersave. Both need to be re-enabled on a channel context basis. Additionally, when channel contexts are used drop the connection when channel switch is received rather than trying to handle it. This will have to be improved later. [With fixes from Eliad and Emmanuel incorporated] Signed-off-by: Eliad Peller <eliad@wizery.com> Signed-off-by: Emmanuel Grumbach <emmanuel.grumbach@intel.com> Signed-off-by: Johannes Berg <johannes.berg@intel.com>
294 lines
6.9 KiB
C
294 lines
6.9 KiB
C
/*
|
|
* mac80211 - channel management
|
|
*/
|
|
|
|
#include <linux/nl80211.h>
|
|
#include <net/cfg80211.h>
|
|
#include "ieee80211_i.h"
|
|
#include "driver-ops.h"
|
|
|
|
static bool
|
|
ieee80211_channel_types_are_compatible(enum nl80211_channel_type chantype1,
|
|
enum nl80211_channel_type chantype2,
|
|
enum nl80211_channel_type *compat)
|
|
{
|
|
/*
|
|
* start out with chantype1 being the result,
|
|
* overwriting later if needed
|
|
*/
|
|
if (compat)
|
|
*compat = chantype1;
|
|
|
|
switch (chantype1) {
|
|
case NL80211_CHAN_NO_HT:
|
|
if (compat)
|
|
*compat = chantype2;
|
|
break;
|
|
case NL80211_CHAN_HT20:
|
|
/*
|
|
* allow any change that doesn't go to no-HT
|
|
* (if it already is no-HT no change is needed)
|
|
*/
|
|
if (chantype2 == NL80211_CHAN_NO_HT)
|
|
break;
|
|
if (compat)
|
|
*compat = chantype2;
|
|
break;
|
|
case NL80211_CHAN_HT40PLUS:
|
|
case NL80211_CHAN_HT40MINUS:
|
|
/* allow smaller bandwidth and same */
|
|
if (chantype2 == NL80211_CHAN_NO_HT)
|
|
break;
|
|
if (chantype2 == NL80211_CHAN_HT20)
|
|
break;
|
|
if (chantype2 == chantype1)
|
|
break;
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
static void ieee80211_change_chantype(struct ieee80211_local *local,
|
|
struct ieee80211_chanctx *ctx,
|
|
enum nl80211_channel_type chantype)
|
|
{
|
|
if (chantype == ctx->conf.channel_type)
|
|
return;
|
|
|
|
ctx->conf.channel_type = chantype;
|
|
drv_change_chanctx(local, ctx, IEEE80211_CHANCTX_CHANGE_CHANNEL_TYPE);
|
|
|
|
if (!local->use_chanctx) {
|
|
local->_oper_channel_type = chantype;
|
|
ieee80211_hw_config(local, 0);
|
|
}
|
|
}
|
|
|
|
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;
|
|
enum nl80211_channel_type compat_type;
|
|
|
|
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) {
|
|
compat_type = ctx->conf.channel_type;
|
|
|
|
if (ctx->mode == IEEE80211_CHANCTX_EXCLUSIVE)
|
|
continue;
|
|
if (ctx->conf.channel != channel)
|
|
continue;
|
|
if (!ieee80211_channel_types_are_compatible(ctx->conf.channel_type,
|
|
channel_type,
|
|
&compat_type))
|
|
continue;
|
|
|
|
ieee80211_change_chantype(local, ctx, compat_type);
|
|
|
|
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;
|
|
int err;
|
|
|
|
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;
|
|
|
|
if (!local->use_chanctx) {
|
|
local->_oper_channel_type = channel_type;
|
|
local->_oper_channel = channel;
|
|
ieee80211_hw_config(local, 0);
|
|
} else {
|
|
err = drv_add_chanctx(local, ctx);
|
|
if (err) {
|
|
kfree(ctx);
|
|
return ERR_PTR(err);
|
|
}
|
|
}
|
|
|
|
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);
|
|
|
|
if (!local->use_chanctx) {
|
|
local->_oper_channel_type = NL80211_CHAN_NO_HT;
|
|
ieee80211_hw_config(local, 0);
|
|
} else {
|
|
drv_remove_chanctx(local, ctx);
|
|
}
|
|
|
|
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 = sdata->local;
|
|
int ret;
|
|
|
|
lockdep_assert_held(&local->chanctx_mtx);
|
|
|
|
ret = drv_assign_vif_chanctx(local, sdata, ctx);
|
|
if (ret)
|
|
return ret;
|
|
|
|
rcu_assign_pointer(sdata->vif.chanctx_conf, &ctx->conf);
|
|
ctx->refcount++;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static enum nl80211_channel_type
|
|
ieee80211_calc_chantype(struct ieee80211_local *local,
|
|
struct ieee80211_chanctx *ctx)
|
|
{
|
|
struct ieee80211_chanctx_conf *conf = &ctx->conf;
|
|
struct ieee80211_sub_if_data *sdata;
|
|
enum nl80211_channel_type result = NL80211_CHAN_NO_HT;
|
|
|
|
lockdep_assert_held(&local->chanctx_mtx);
|
|
|
|
rcu_read_lock();
|
|
list_for_each_entry_rcu(sdata, &local->interfaces, list) {
|
|
if (!ieee80211_sdata_running(sdata))
|
|
continue;
|
|
if (rcu_access_pointer(sdata->vif.chanctx_conf) != conf)
|
|
continue;
|
|
|
|
WARN_ON_ONCE(!ieee80211_channel_types_are_compatible(
|
|
sdata->vif.bss_conf.channel_type,
|
|
result, &result));
|
|
}
|
|
rcu_read_unlock();
|
|
|
|
return result;
|
|
}
|
|
|
|
static void ieee80211_recalc_chanctx_chantype(struct ieee80211_local *local,
|
|
struct ieee80211_chanctx *ctx)
|
|
{
|
|
enum nl80211_channel_type chantype;
|
|
|
|
lockdep_assert_held(&local->chanctx_mtx);
|
|
|
|
chantype = ieee80211_calc_chantype(local, ctx);
|
|
ieee80211_change_chantype(local, ctx, chantype);
|
|
}
|
|
|
|
static void ieee80211_unassign_vif_chanctx(struct ieee80211_sub_if_data *sdata,
|
|
struct ieee80211_chanctx *ctx)
|
|
{
|
|
struct ieee80211_local *local = sdata->local;
|
|
|
|
lockdep_assert_held(&local->chanctx_mtx);
|
|
|
|
ctx->refcount--;
|
|
rcu_assign_pointer(sdata->vif.chanctx_conf, NULL);
|
|
|
|
drv_unassign_vif_chanctx(local, sdata, ctx);
|
|
|
|
if (ctx->refcount > 0)
|
|
ieee80211_recalc_chanctx_chantype(sdata->local, ctx);
|
|
}
|
|
|
|
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;
|
|
|
|
WARN_ON(sdata->dev && netif_carrier_ok(sdata->dev));
|
|
|
|
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;
|
|
}
|
|
|
|
sdata->vif.bss_conf.channel_type = channel_type;
|
|
|
|
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)
|
|
{
|
|
WARN_ON(sdata->dev && netif_carrier_ok(sdata->dev));
|
|
|
|
mutex_lock(&sdata->local->chanctx_mtx);
|
|
__ieee80211_vif_release_channel(sdata);
|
|
mutex_unlock(&sdata->local->chanctx_mtx);
|
|
}
|