Skip to content

Commit

Permalink
mac80211: add channel switch command and beacon callbacks
Browse files Browse the repository at this point in the history
The count field in CSA must be decremented with each beacon
transmitted. This patch implements the functionality for drivers
using ieee80211_beacon_get(). Other drivers must call back manually
after reaching count == 0.

This patch also contains the handling and finish worker for the channel
switch command, and mac80211/chanctx code to allow to change a channel
definition of an active channel context.

Signed-off-by: Simon Wunderlich <siwu@hrz.tu-chemnitz.de>
Signed-off-by: Mathias Kretschmer <mathias.kretschmer@fokus.fraunhofer.de>
[small cleanups, catch identical chandef]
Signed-off-by: Johannes Berg <johannes.berg@intel.com>
  • Loading branch information
Simon Wunderlich authored and ariknem committed Aug 12, 2013
1 parent 4c7369f commit 2e027f9
Show file tree
Hide file tree
Showing 8 changed files with 423 additions and 2 deletions.
37 changes: 37 additions & 0 deletions include/net/mac80211.h
Original file line number Diff line number Diff line change
Expand Up @@ -152,11 +152,14 @@ struct ieee80211_low_level_stats {
* @IEEE80211_CHANCTX_CHANGE_WIDTH: The channel width changed
* @IEEE80211_CHANCTX_CHANGE_RX_CHAINS: The number of RX chains changed
* @IEEE80211_CHANCTX_CHANGE_RADAR: radar detection flag changed
* @IEEE80211_CHANCTX_CHANGE_CHANNEL: switched to another operating channel,
* this is used only with channel switching with CSA
*/
enum ieee80211_chanctx_change {
IEEE80211_CHANCTX_CHANGE_WIDTH = BIT(0),
IEEE80211_CHANCTX_CHANGE_RX_CHAINS = BIT(1),
IEEE80211_CHANCTX_CHANGE_RADAR = BIT(2),
IEEE80211_CHANCTX_CHANGE_CHANNEL = BIT(3),
};

/**
Expand Down Expand Up @@ -1065,6 +1068,7 @@ enum ieee80211_vif_flags {
* @addr: address of this interface
* @p2p: indicates whether this AP or STA interface is a p2p
* interface, i.e. a GO or p2p-sta respectively
* @csa_active: marks whether a channel switch is going on
* @driver_flags: flags/capabilities the driver has for this interface,
* these need to be set (or cleared) when the interface is added
* or, if supported by the driver, the interface type is changed
Expand All @@ -1088,6 +1092,7 @@ struct ieee80211_vif {
struct ieee80211_bss_conf bss_conf;
u8 addr[ETH_ALEN];
bool p2p;
bool csa_active;

u8 cab_queue;
u8 hw_queue[IEEE80211_NUM_ACS];
Expand Down Expand Up @@ -2625,6 +2630,16 @@ enum ieee80211_roc_type {
* @ipv6_addr_change: IPv6 address assignment on the given interface changed.
* Currently, this is only called for managed or P2P client interfaces.
* This callback is optional; it must not sleep.
*
* @channel_switch_beacon: Starts a channel switch to a new channel.
* Beacons are modified to include CSA or ECSA IEs before calling this
* function. The corresponding count fields in these IEs must be
* decremented, and when they reach zero the driver must call
* ieee80211_csa_finish(). Drivers which use ieee80211_beacon_get()
* get the csa counter decremented by mac80211, but must check if it is
* zero using ieee80211_csa_is_complete() after the beacon has been
* transmitted and then call ieee80211_csa_finish().
*
*/
struct ieee80211_ops {
void (*tx)(struct ieee80211_hw *hw,
Expand Down Expand Up @@ -2813,6 +2828,9 @@ struct ieee80211_ops {
struct ieee80211_vif *vif,
struct inet6_dev *idev);
#endif
void (*channel_switch_beacon)(struct ieee80211_hw *hw,
struct ieee80211_vif *vif,
struct cfg80211_chan_def *chandef);
};

/**
Expand Down Expand Up @@ -3318,6 +3336,25 @@ static inline struct sk_buff *ieee80211_beacon_get(struct ieee80211_hw *hw,
return ieee80211_beacon_get_tim(hw, vif, NULL, NULL);
}

/**
* ieee80211_csa_finish - notify mac80211 about channel switch
* @vif: &struct ieee80211_vif pointer from the add_interface callback.
*
* After a channel switch announcement was scheduled and the counter in this
* announcement hit zero, this function must be called by the driver to
* notify mac80211 that the channel can be changed.
*/
void ieee80211_csa_finish(struct ieee80211_vif *vif);

/**
* ieee80211_csa_is_complete - find out if counters reached zero
* @vif: &struct ieee80211_vif pointer from the add_interface callback.
*
* This function returns whether the channel switch counters reached zero.
*/
bool ieee80211_csa_is_complete(struct ieee80211_vif *vif);


/**
* ieee80211_proberesp_get - retrieve a Probe Response template
* @hw: pointer obtained from ieee80211_alloc_hw().
Expand Down
187 changes: 185 additions & 2 deletions net/mac80211/cfg.c
Original file line number Diff line number Diff line change
Expand Up @@ -840,8 +840,8 @@ static int ieee80211_set_probe_resp(struct ieee80211_sub_if_data *sdata,
return 0;
}

static int ieee80211_assign_beacon(struct ieee80211_sub_if_data *sdata,
struct cfg80211_beacon_data *params)
int ieee80211_assign_beacon(struct ieee80211_sub_if_data *sdata,
struct cfg80211_beacon_data *params)
{
struct beacon_data *new, *old;
int new_head_len, new_tail_len;
Expand Down Expand Up @@ -1055,6 +1055,12 @@ static int ieee80211_change_beacon(struct wiphy *wiphy, struct net_device *dev,

sdata = IEEE80211_DEV_TO_SUB_IF(dev);

/* don't allow changing the beacon while CSA is in place - offset
* of channel switch counter may change
*/
if (sdata->vif.csa_active)
return -EBUSY;

old = rtnl_dereference(sdata->u.ap.beacon);
if (!old)
return -ENOENT;
Expand All @@ -1079,6 +1085,10 @@ static int ieee80211_stop_ap(struct wiphy *wiphy, struct net_device *dev)
return -ENOENT;
old_probe_resp = rtnl_dereference(sdata->u.ap.probe_resp);

/* abort any running channel switch */
sdata->vif.csa_active = false;
cancel_work_sync(&sdata->csa_finalize_work);

/* turn off carrier for this interface and dependent VLANs */
list_for_each_entry(vlan, &sdata->u.ap.vlans, u.vlan.list)
netif_carrier_off(vlan->dev);
Expand Down Expand Up @@ -2825,6 +2835,178 @@ static int ieee80211_start_radar_detection(struct wiphy *wiphy,
return 0;
}

static struct cfg80211_beacon_data *
cfg80211_beacon_dup(struct cfg80211_beacon_data *beacon)
{
struct cfg80211_beacon_data *new_beacon;
u8 *pos;
int len;

len = beacon->head_len + beacon->tail_len + beacon->beacon_ies_len +
beacon->proberesp_ies_len + beacon->assocresp_ies_len +
beacon->probe_resp_len;

new_beacon = kzalloc(sizeof(*new_beacon) + len, GFP_KERNEL);
if (!new_beacon)
return NULL;

pos = (u8 *)(new_beacon + 1);
if (beacon->head_len) {
new_beacon->head_len = beacon->head_len;
new_beacon->head = pos;
memcpy(pos, beacon->head, beacon->head_len);
pos += beacon->head_len;
}
if (beacon->tail_len) {
new_beacon->tail_len = beacon->tail_len;
new_beacon->tail = pos;
memcpy(pos, beacon->tail, beacon->tail_len);
pos += beacon->tail_len;
}
if (beacon->beacon_ies_len) {
new_beacon->beacon_ies_len = beacon->beacon_ies_len;
new_beacon->beacon_ies = pos;
memcpy(pos, beacon->beacon_ies, beacon->beacon_ies_len);
pos += beacon->beacon_ies_len;
}
if (beacon->proberesp_ies_len) {
new_beacon->proberesp_ies_len = beacon->proberesp_ies_len;
new_beacon->proberesp_ies = pos;
memcpy(pos, beacon->proberesp_ies, beacon->proberesp_ies_len);
pos += beacon->proberesp_ies_len;
}
if (beacon->assocresp_ies_len) {
new_beacon->assocresp_ies_len = beacon->assocresp_ies_len;
new_beacon->assocresp_ies = pos;
memcpy(pos, beacon->assocresp_ies, beacon->assocresp_ies_len);
pos += beacon->assocresp_ies_len;
}
if (beacon->probe_resp_len) {
new_beacon->probe_resp_len = beacon->probe_resp_len;
beacon->probe_resp = pos;
memcpy(pos, beacon->probe_resp, beacon->probe_resp_len);
pos += beacon->probe_resp_len;
}

return new_beacon;
}

void ieee80211_csa_finalize_work(struct work_struct *work)
{
struct ieee80211_sub_if_data *sdata =
container_of(work, struct ieee80211_sub_if_data,
csa_finalize_work);
struct ieee80211_local *local = sdata->local;
int err, changed;

if (!ieee80211_sdata_running(sdata))
return;

if (WARN_ON(sdata->vif.type != NL80211_IFTYPE_AP))
return;

sdata->radar_required = sdata->csa_radar_required;
err = ieee80211_vif_change_channel(sdata, &local->csa_chandef,
&changed);
if (WARN_ON(err < 0))
return;

err = ieee80211_assign_beacon(sdata, sdata->u.ap.next_beacon);
if (err < 0)
return;

changed |= err;
kfree(sdata->u.ap.next_beacon);
sdata->u.ap.next_beacon = NULL;
sdata->vif.csa_active = false;

ieee80211_wake_queues_by_reason(&sdata->local->hw,
IEEE80211_MAX_QUEUE_MAP,
IEEE80211_QUEUE_STOP_REASON_CSA);

ieee80211_bss_info_change_notify(sdata, changed);

cfg80211_ch_switch_notify(sdata->dev, &local->csa_chandef);
}

static int ieee80211_channel_switch(struct wiphy *wiphy, struct net_device *dev,
struct cfg80211_csa_settings *params)
{
struct ieee80211_sub_if_data *sdata = IEEE80211_DEV_TO_SUB_IF(dev);
struct ieee80211_local *local = sdata->local;
struct ieee80211_chanctx_conf *chanctx_conf;
struct ieee80211_chanctx *chanctx;
int err, num_chanctx;

if (!list_empty(&local->roc_list) || local->scanning)
return -EBUSY;

if (sdata->wdev.cac_started)
return -EBUSY;

if (cfg80211_chandef_identical(&params->chandef,
&sdata->vif.bss_conf.chandef))
return -EINVAL;

rcu_read_lock();
chanctx_conf = rcu_dereference(sdata->vif.chanctx_conf);
if (!chanctx_conf) {
rcu_read_unlock();
return -EBUSY;
}

/* don't handle for multi-VIF cases */
chanctx = container_of(chanctx_conf, struct ieee80211_chanctx, conf);
if (chanctx->refcount > 1) {
rcu_read_unlock();
return -EBUSY;
}
num_chanctx = 0;
list_for_each_entry_rcu(chanctx, &local->chanctx_list, list)
num_chanctx++;
rcu_read_unlock();

if (num_chanctx > 1)
return -EBUSY;

/* don't allow another channel switch if one is already active. */
if (sdata->vif.csa_active)
return -EBUSY;

/* only handle AP for now. */
switch (sdata->vif.type) {
case NL80211_IFTYPE_AP:
break;
default:
return -EOPNOTSUPP;
}

sdata->u.ap.next_beacon = cfg80211_beacon_dup(&params->beacon_after);
if (!sdata->u.ap.next_beacon)
return -ENOMEM;

sdata->csa_counter_offset_beacon = params->counter_offset_beacon;
sdata->csa_counter_offset_presp = params->counter_offset_presp;
sdata->csa_radar_required = params->radar_required;

if (params->block_tx)
ieee80211_stop_queues_by_reason(&local->hw,
IEEE80211_MAX_QUEUE_MAP,
IEEE80211_QUEUE_STOP_REASON_CSA);

err = ieee80211_assign_beacon(sdata, &params->beacon_csa);
if (err < 0)
return err;

local->csa_chandef = params->chandef;
sdata->vif.csa_active = true;

ieee80211_bss_info_change_notify(sdata, err);
drv_channel_switch_beacon(sdata, &params->chandef);

return 0;
}

static int ieee80211_mgmt_tx(struct wiphy *wiphy, struct wireless_dev *wdev,
struct ieee80211_channel *chan, bool offchan,
unsigned int wait, const u8 *buf, size_t len,
Expand Down Expand Up @@ -3542,4 +3724,5 @@ struct cfg80211_ops mac80211_config_ops = {
.get_et_strings = ieee80211_get_et_strings,
.get_channel = ieee80211_cfg_get_channel,
.start_radar_detection = ieee80211_start_radar_detection,
.channel_switch = ieee80211_channel_switch,
};
58 changes: 58 additions & 0 deletions net/mac80211/chan.c
Original file line number Diff line number Diff line change
Expand Up @@ -410,6 +410,64 @@ int ieee80211_vif_use_channel(struct ieee80211_sub_if_data *sdata,
return ret;
}

int ieee80211_vif_change_channel(struct ieee80211_sub_if_data *sdata,
const struct cfg80211_chan_def *chandef,
u32 *changed)
{
struct ieee80211_local *local = sdata->local;
struct ieee80211_chanctx_conf *conf;
struct ieee80211_chanctx *ctx;
int ret;
u32 chanctx_changed = 0;

/* should never be called if not performing a channel switch. */
if (WARN_ON(!sdata->vif.csa_active))
return -EINVAL;

if (!cfg80211_chandef_usable(sdata->local->hw.wiphy, chandef,
IEEE80211_CHAN_DISABLED))
return -EINVAL;

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;
}

ctx = container_of(conf, struct ieee80211_chanctx, conf);
if (ctx->refcount != 1) {
ret = -EINVAL;
goto out;
}

if (sdata->vif.bss_conf.chandef.width != chandef->width) {
chanctx_changed = IEEE80211_CHANCTX_CHANGE_WIDTH;
*changed |= BSS_CHANGED_BANDWIDTH;
}

sdata->vif.bss_conf.chandef = *chandef;
ctx->conf.def = *chandef;

chanctx_changed |= IEEE80211_CHANCTX_CHANGE_CHANNEL;
drv_change_chanctx(local, ctx, chanctx_changed);

if (!local->use_chanctx) {
local->_oper_chandef = *chandef;
ieee80211_hw_config(local, 0);
}

ieee80211_recalc_chanctx_chantype(local, ctx);
ieee80211_recalc_smps_chanctx(local, ctx);
ieee80211_recalc_radar_chanctx(local, ctx);

ret = 0;
out:
mutex_unlock(&local->chanctx_mtx);
return ret;
}

int ieee80211_vif_change_bandwidth(struct ieee80211_sub_if_data *sdata,
const struct cfg80211_chan_def *chandef,
u32 *changed)
Expand Down
13 changes: 13 additions & 0 deletions net/mac80211/driver-ops.h
Original file line number Diff line number Diff line change
Expand Up @@ -1072,4 +1072,17 @@ static inline void drv_ipv6_addr_change(struct ieee80211_local *local,
}
#endif

static inline void
drv_channel_switch_beacon(struct ieee80211_sub_if_data *sdata,
struct cfg80211_chan_def *chandef)
{
struct ieee80211_local *local = sdata->local;

if (local->ops->channel_switch_beacon) {
trace_drv_channel_switch_beacon(local, sdata, chandef);
local->ops->channel_switch_beacon(&local->hw, &sdata->vif,
chandef);
}
}

#endif /* __MAC80211_DRIVER_OPS */
Loading

0 comments on commit 2e027f9

Please sign in to comment.