mirror of
https://github.com/torvalds/linux.git
synced 2024-11-16 00:52:01 +00:00
c7a6ee27ab
Some drivers might support 80 or 160 MHz only on some channels for whatever reason, so allow them to disable these channel widths. Also maintain the new flags when regulatory bandwidth limitations would disable these wide channels. Reviewed-by: Luis R. Rodriguez <mcgrof@do-not-panic.com> Signed-off-by: Johannes Berg <johannes.berg@intel.com>
511 lines
12 KiB
C
511 lines
12 KiB
C
/*
|
|
* This file contains helper code to handle channel
|
|
* settings and keeping track of what is possible at
|
|
* any point in time.
|
|
*
|
|
* Copyright 2009 Johannes Berg <johannes@sipsolutions.net>
|
|
*/
|
|
|
|
#include <linux/export.h>
|
|
#include <net/cfg80211.h>
|
|
#include "core.h"
|
|
#include "rdev-ops.h"
|
|
|
|
void cfg80211_chandef_create(struct cfg80211_chan_def *chandef,
|
|
struct ieee80211_channel *chan,
|
|
enum nl80211_channel_type chan_type)
|
|
{
|
|
if (WARN_ON(!chan))
|
|
return;
|
|
|
|
chandef->chan = chan;
|
|
chandef->center_freq2 = 0;
|
|
|
|
switch (chan_type) {
|
|
case NL80211_CHAN_NO_HT:
|
|
chandef->width = NL80211_CHAN_WIDTH_20_NOHT;
|
|
chandef->center_freq1 = chan->center_freq;
|
|
break;
|
|
case NL80211_CHAN_HT20:
|
|
chandef->width = NL80211_CHAN_WIDTH_20;
|
|
chandef->center_freq1 = chan->center_freq;
|
|
break;
|
|
case NL80211_CHAN_HT40PLUS:
|
|
chandef->width = NL80211_CHAN_WIDTH_40;
|
|
chandef->center_freq1 = chan->center_freq + 10;
|
|
break;
|
|
case NL80211_CHAN_HT40MINUS:
|
|
chandef->width = NL80211_CHAN_WIDTH_40;
|
|
chandef->center_freq1 = chan->center_freq - 10;
|
|
break;
|
|
default:
|
|
WARN_ON(1);
|
|
}
|
|
}
|
|
EXPORT_SYMBOL(cfg80211_chandef_create);
|
|
|
|
bool cfg80211_chandef_valid(const struct cfg80211_chan_def *chandef)
|
|
{
|
|
u32 control_freq;
|
|
|
|
if (!chandef->chan)
|
|
return false;
|
|
|
|
control_freq = chandef->chan->center_freq;
|
|
|
|
switch (chandef->width) {
|
|
case NL80211_CHAN_WIDTH_20:
|
|
case NL80211_CHAN_WIDTH_20_NOHT:
|
|
if (chandef->center_freq1 != control_freq)
|
|
return false;
|
|
if (chandef->center_freq2)
|
|
return false;
|
|
break;
|
|
case NL80211_CHAN_WIDTH_40:
|
|
if (chandef->center_freq1 != control_freq + 10 &&
|
|
chandef->center_freq1 != control_freq - 10)
|
|
return false;
|
|
if (chandef->center_freq2)
|
|
return false;
|
|
break;
|
|
case NL80211_CHAN_WIDTH_80P80:
|
|
if (chandef->center_freq1 != control_freq + 30 &&
|
|
chandef->center_freq1 != control_freq + 10 &&
|
|
chandef->center_freq1 != control_freq - 10 &&
|
|
chandef->center_freq1 != control_freq - 30)
|
|
return false;
|
|
if (!chandef->center_freq2)
|
|
return false;
|
|
/* adjacent is not allowed -- that's a 160 MHz channel */
|
|
if (chandef->center_freq1 - chandef->center_freq2 == 80 ||
|
|
chandef->center_freq2 - chandef->center_freq1 == 80)
|
|
return false;
|
|
break;
|
|
case NL80211_CHAN_WIDTH_80:
|
|
if (chandef->center_freq1 != control_freq + 30 &&
|
|
chandef->center_freq1 != control_freq + 10 &&
|
|
chandef->center_freq1 != control_freq - 10 &&
|
|
chandef->center_freq1 != control_freq - 30)
|
|
return false;
|
|
if (chandef->center_freq2)
|
|
return false;
|
|
break;
|
|
case NL80211_CHAN_WIDTH_160:
|
|
if (chandef->center_freq1 != control_freq + 70 &&
|
|
chandef->center_freq1 != control_freq + 50 &&
|
|
chandef->center_freq1 != control_freq + 30 &&
|
|
chandef->center_freq1 != control_freq + 10 &&
|
|
chandef->center_freq1 != control_freq - 10 &&
|
|
chandef->center_freq1 != control_freq - 30 &&
|
|
chandef->center_freq1 != control_freq - 50 &&
|
|
chandef->center_freq1 != control_freq - 70)
|
|
return false;
|
|
if (chandef->center_freq2)
|
|
return false;
|
|
break;
|
|
default:
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
EXPORT_SYMBOL(cfg80211_chandef_valid);
|
|
|
|
static void chandef_primary_freqs(const struct cfg80211_chan_def *c,
|
|
int *pri40, int *pri80)
|
|
{
|
|
int tmp;
|
|
|
|
switch (c->width) {
|
|
case NL80211_CHAN_WIDTH_40:
|
|
*pri40 = c->center_freq1;
|
|
*pri80 = 0;
|
|
break;
|
|
case NL80211_CHAN_WIDTH_80:
|
|
case NL80211_CHAN_WIDTH_80P80:
|
|
*pri80 = c->center_freq1;
|
|
/* n_P20 */
|
|
tmp = (30 + c->chan->center_freq - c->center_freq1)/20;
|
|
/* n_P40 */
|
|
tmp /= 2;
|
|
/* freq_P40 */
|
|
*pri40 = c->center_freq1 - 20 + 40 * tmp;
|
|
break;
|
|
case NL80211_CHAN_WIDTH_160:
|
|
/* n_P20 */
|
|
tmp = (70 + c->chan->center_freq - c->center_freq1)/20;
|
|
/* n_P40 */
|
|
tmp /= 2;
|
|
/* freq_P40 */
|
|
*pri40 = c->center_freq1 - 60 + 40 * tmp;
|
|
/* n_P80 */
|
|
tmp /= 2;
|
|
*pri80 = c->center_freq1 - 40 + 80 * tmp;
|
|
break;
|
|
default:
|
|
WARN_ON_ONCE(1);
|
|
}
|
|
}
|
|
|
|
static int cfg80211_chandef_get_width(const struct cfg80211_chan_def *c)
|
|
{
|
|
int width;
|
|
|
|
switch (c->width) {
|
|
case NL80211_CHAN_WIDTH_20:
|
|
case NL80211_CHAN_WIDTH_20_NOHT:
|
|
width = 20;
|
|
break;
|
|
case NL80211_CHAN_WIDTH_40:
|
|
width = 40;
|
|
break;
|
|
case NL80211_CHAN_WIDTH_80P80:
|
|
case NL80211_CHAN_WIDTH_80:
|
|
width = 80;
|
|
break;
|
|
case NL80211_CHAN_WIDTH_160:
|
|
width = 160;
|
|
break;
|
|
default:
|
|
WARN_ON_ONCE(1);
|
|
return -1;
|
|
}
|
|
return width;
|
|
}
|
|
|
|
const struct cfg80211_chan_def *
|
|
cfg80211_chandef_compatible(const struct cfg80211_chan_def *c1,
|
|
const struct cfg80211_chan_def *c2)
|
|
{
|
|
u32 c1_pri40, c1_pri80, c2_pri40, c2_pri80;
|
|
|
|
/* If they are identical, return */
|
|
if (cfg80211_chandef_identical(c1, c2))
|
|
return c1;
|
|
|
|
/* otherwise, must have same control channel */
|
|
if (c1->chan != c2->chan)
|
|
return NULL;
|
|
|
|
/*
|
|
* If they have the same width, but aren't identical,
|
|
* then they can't be compatible.
|
|
*/
|
|
if (c1->width == c2->width)
|
|
return NULL;
|
|
|
|
if (c1->width == NL80211_CHAN_WIDTH_20_NOHT ||
|
|
c1->width == NL80211_CHAN_WIDTH_20)
|
|
return c2;
|
|
|
|
if (c2->width == NL80211_CHAN_WIDTH_20_NOHT ||
|
|
c2->width == NL80211_CHAN_WIDTH_20)
|
|
return c1;
|
|
|
|
chandef_primary_freqs(c1, &c1_pri40, &c1_pri80);
|
|
chandef_primary_freqs(c2, &c2_pri40, &c2_pri80);
|
|
|
|
if (c1_pri40 != c2_pri40)
|
|
return NULL;
|
|
|
|
WARN_ON(!c1_pri80 && !c2_pri80);
|
|
if (c1_pri80 && c2_pri80 && c1_pri80 != c2_pri80)
|
|
return NULL;
|
|
|
|
if (c1->width > c2->width)
|
|
return c1;
|
|
return c2;
|
|
}
|
|
EXPORT_SYMBOL(cfg80211_chandef_compatible);
|
|
|
|
static void cfg80211_set_chans_dfs_state(struct wiphy *wiphy, u32 center_freq,
|
|
u32 bandwidth,
|
|
enum nl80211_dfs_state dfs_state)
|
|
{
|
|
struct ieee80211_channel *c;
|
|
u32 freq;
|
|
|
|
for (freq = center_freq - bandwidth/2 + 10;
|
|
freq <= center_freq + bandwidth/2 - 10;
|
|
freq += 20) {
|
|
c = ieee80211_get_channel(wiphy, freq);
|
|
if (!c || !(c->flags & IEEE80211_CHAN_RADAR))
|
|
continue;
|
|
|
|
c->dfs_state = dfs_state;
|
|
c->dfs_state_entered = jiffies;
|
|
}
|
|
}
|
|
|
|
void cfg80211_set_dfs_state(struct wiphy *wiphy,
|
|
const struct cfg80211_chan_def *chandef,
|
|
enum nl80211_dfs_state dfs_state)
|
|
{
|
|
int width;
|
|
|
|
if (WARN_ON(!cfg80211_chandef_valid(chandef)))
|
|
return;
|
|
|
|
width = cfg80211_chandef_get_width(chandef);
|
|
if (width < 0)
|
|
return;
|
|
|
|
cfg80211_set_chans_dfs_state(wiphy, chandef->center_freq1,
|
|
width, dfs_state);
|
|
|
|
if (!chandef->center_freq2)
|
|
return;
|
|
cfg80211_set_chans_dfs_state(wiphy, chandef->center_freq2,
|
|
width, dfs_state);
|
|
}
|
|
|
|
static int cfg80211_get_chans_dfs_required(struct wiphy *wiphy,
|
|
u32 center_freq,
|
|
u32 bandwidth)
|
|
{
|
|
struct ieee80211_channel *c;
|
|
u32 freq;
|
|
|
|
for (freq = center_freq - bandwidth/2 + 10;
|
|
freq <= center_freq + bandwidth/2 - 10;
|
|
freq += 20) {
|
|
c = ieee80211_get_channel(wiphy, freq);
|
|
if (!c)
|
|
return -EINVAL;
|
|
|
|
if (c->flags & IEEE80211_CHAN_RADAR)
|
|
return 1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
|
|
int cfg80211_chandef_dfs_required(struct wiphy *wiphy,
|
|
const struct cfg80211_chan_def *chandef)
|
|
{
|
|
int width;
|
|
int r;
|
|
|
|
if (WARN_ON(!cfg80211_chandef_valid(chandef)))
|
|
return -EINVAL;
|
|
|
|
width = cfg80211_chandef_get_width(chandef);
|
|
if (width < 0)
|
|
return -EINVAL;
|
|
|
|
r = cfg80211_get_chans_dfs_required(wiphy, chandef->center_freq1,
|
|
width);
|
|
if (r)
|
|
return r;
|
|
|
|
if (!chandef->center_freq2)
|
|
return 0;
|
|
|
|
return cfg80211_get_chans_dfs_required(wiphy, chandef->center_freq2,
|
|
width);
|
|
}
|
|
|
|
static bool cfg80211_secondary_chans_ok(struct wiphy *wiphy,
|
|
u32 center_freq, u32 bandwidth,
|
|
u32 prohibited_flags)
|
|
{
|
|
struct ieee80211_channel *c;
|
|
u32 freq;
|
|
|
|
for (freq = center_freq - bandwidth/2 + 10;
|
|
freq <= center_freq + bandwidth/2 - 10;
|
|
freq += 20) {
|
|
c = ieee80211_get_channel(wiphy, freq);
|
|
if (!c)
|
|
return false;
|
|
|
|
/* check for radar flags */
|
|
if ((prohibited_flags & c->flags & IEEE80211_CHAN_RADAR) &&
|
|
(c->dfs_state != NL80211_DFS_AVAILABLE))
|
|
return false;
|
|
|
|
/* check for the other flags */
|
|
if (c->flags & prohibited_flags & ~IEEE80211_CHAN_RADAR)
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool cfg80211_chandef_usable(struct wiphy *wiphy,
|
|
const struct cfg80211_chan_def *chandef,
|
|
u32 prohibited_flags)
|
|
{
|
|
struct ieee80211_sta_ht_cap *ht_cap;
|
|
struct ieee80211_sta_vht_cap *vht_cap;
|
|
u32 width, control_freq;
|
|
|
|
if (WARN_ON(!cfg80211_chandef_valid(chandef)))
|
|
return false;
|
|
|
|
ht_cap = &wiphy->bands[chandef->chan->band]->ht_cap;
|
|
vht_cap = &wiphy->bands[chandef->chan->band]->vht_cap;
|
|
|
|
control_freq = chandef->chan->center_freq;
|
|
|
|
switch (chandef->width) {
|
|
case NL80211_CHAN_WIDTH_20:
|
|
if (!ht_cap->ht_supported)
|
|
return false;
|
|
case NL80211_CHAN_WIDTH_20_NOHT:
|
|
width = 20;
|
|
break;
|
|
case NL80211_CHAN_WIDTH_40:
|
|
width = 40;
|
|
if (!ht_cap->ht_supported)
|
|
return false;
|
|
if (!(ht_cap->cap & IEEE80211_HT_CAP_SUP_WIDTH_20_40) ||
|
|
ht_cap->cap & IEEE80211_HT_CAP_40MHZ_INTOLERANT)
|
|
return false;
|
|
if (chandef->center_freq1 < control_freq &&
|
|
chandef->chan->flags & IEEE80211_CHAN_NO_HT40MINUS)
|
|
return false;
|
|
if (chandef->center_freq1 > control_freq &&
|
|
chandef->chan->flags & IEEE80211_CHAN_NO_HT40PLUS)
|
|
return false;
|
|
break;
|
|
case NL80211_CHAN_WIDTH_80P80:
|
|
if (!(vht_cap->cap & IEEE80211_VHT_CAP_SUPP_CHAN_WIDTH_160_80PLUS80MHZ))
|
|
return false;
|
|
case NL80211_CHAN_WIDTH_80:
|
|
if (!vht_cap->vht_supported)
|
|
return false;
|
|
prohibited_flags |= IEEE80211_CHAN_NO_80MHZ;
|
|
width = 80;
|
|
break;
|
|
case NL80211_CHAN_WIDTH_160:
|
|
if (!vht_cap->vht_supported)
|
|
return false;
|
|
if (!(vht_cap->cap & IEEE80211_VHT_CAP_SUPP_CHAN_WIDTH_160MHZ))
|
|
return false;
|
|
prohibited_flags |= IEEE80211_CHAN_NO_160MHZ;
|
|
width = 160;
|
|
break;
|
|
default:
|
|
WARN_ON_ONCE(1);
|
|
return false;
|
|
}
|
|
|
|
/*
|
|
* TODO: What if there are only certain 80/160/80+80 MHz channels
|
|
* allowed by the driver, or only certain combinations?
|
|
* For 40 MHz the driver can set the NO_HT40 flags, but for
|
|
* 80/160 MHz and in particular 80+80 MHz this isn't really
|
|
* feasible and we only have NO_80MHZ/NO_160MHZ so far but
|
|
* no way to cover 80+80 MHz or more complex restrictions.
|
|
* Note that such restrictions also need to be advertised to
|
|
* userspace, for example for P2P channel selection.
|
|
*/
|
|
|
|
if (width > 20)
|
|
prohibited_flags |= IEEE80211_CHAN_NO_OFDM;
|
|
|
|
if (!cfg80211_secondary_chans_ok(wiphy, chandef->center_freq1,
|
|
width, prohibited_flags))
|
|
return false;
|
|
|
|
if (!chandef->center_freq2)
|
|
return true;
|
|
return cfg80211_secondary_chans_ok(wiphy, chandef->center_freq2,
|
|
width, prohibited_flags);
|
|
}
|
|
EXPORT_SYMBOL(cfg80211_chandef_usable);
|
|
|
|
bool cfg80211_reg_can_beacon(struct wiphy *wiphy,
|
|
struct cfg80211_chan_def *chandef)
|
|
{
|
|
bool res;
|
|
|
|
trace_cfg80211_reg_can_beacon(wiphy, chandef);
|
|
|
|
res = cfg80211_chandef_usable(wiphy, chandef,
|
|
IEEE80211_CHAN_DISABLED |
|
|
IEEE80211_CHAN_PASSIVE_SCAN |
|
|
IEEE80211_CHAN_NO_IBSS |
|
|
IEEE80211_CHAN_RADAR);
|
|
|
|
trace_cfg80211_return_bool(res);
|
|
return res;
|
|
}
|
|
EXPORT_SYMBOL(cfg80211_reg_can_beacon);
|
|
|
|
int cfg80211_set_monitor_channel(struct cfg80211_registered_device *rdev,
|
|
struct cfg80211_chan_def *chandef)
|
|
{
|
|
if (!rdev->ops->set_monitor_channel)
|
|
return -EOPNOTSUPP;
|
|
if (!cfg80211_has_monitors_only(rdev))
|
|
return -EBUSY;
|
|
|
|
return rdev_set_monitor_channel(rdev, chandef);
|
|
}
|
|
|
|
void
|
|
cfg80211_get_chan_state(struct wireless_dev *wdev,
|
|
struct ieee80211_channel **chan,
|
|
enum cfg80211_chan_mode *chanmode)
|
|
{
|
|
*chan = NULL;
|
|
*chanmode = CHAN_MODE_UNDEFINED;
|
|
|
|
ASSERT_WDEV_LOCK(wdev);
|
|
|
|
if (wdev->netdev && !netif_running(wdev->netdev))
|
|
return;
|
|
|
|
switch (wdev->iftype) {
|
|
case NL80211_IFTYPE_ADHOC:
|
|
if (wdev->current_bss) {
|
|
*chan = wdev->current_bss->pub.channel;
|
|
*chanmode = wdev->ibss_fixed
|
|
? CHAN_MODE_SHARED
|
|
: CHAN_MODE_EXCLUSIVE;
|
|
return;
|
|
}
|
|
case NL80211_IFTYPE_STATION:
|
|
case NL80211_IFTYPE_P2P_CLIENT:
|
|
if (wdev->current_bss) {
|
|
*chan = wdev->current_bss->pub.channel;
|
|
*chanmode = CHAN_MODE_SHARED;
|
|
return;
|
|
}
|
|
break;
|
|
case NL80211_IFTYPE_AP:
|
|
case NL80211_IFTYPE_P2P_GO:
|
|
if (wdev->cac_started) {
|
|
*chan = wdev->channel;
|
|
*chanmode = CHAN_MODE_SHARED;
|
|
} else if (wdev->beacon_interval) {
|
|
*chan = wdev->channel;
|
|
*chanmode = CHAN_MODE_SHARED;
|
|
}
|
|
return;
|
|
case NL80211_IFTYPE_MESH_POINT:
|
|
if (wdev->mesh_id_len) {
|
|
*chan = wdev->channel;
|
|
*chanmode = CHAN_MODE_SHARED;
|
|
}
|
|
return;
|
|
case NL80211_IFTYPE_MONITOR:
|
|
case NL80211_IFTYPE_AP_VLAN:
|
|
case NL80211_IFTYPE_WDS:
|
|
/* these interface types don't really have a channel */
|
|
return;
|
|
case NL80211_IFTYPE_P2P_DEVICE:
|
|
if (wdev->wiphy->features &
|
|
NL80211_FEATURE_P2P_DEVICE_NEEDS_CHANNEL)
|
|
*chanmode = CHAN_MODE_EXCLUSIVE;
|
|
return;
|
|
case NL80211_IFTYPE_UNSPECIFIED:
|
|
case NUM_NL80211_IFTYPES:
|
|
WARN_ON(1);
|
|
}
|
|
|
|
return;
|
|
}
|