Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 73da7d5b authored by Simon Wunderlich's avatar Simon Wunderlich Committed by Johannes Berg
Browse files

mac80211: add channel switch command and beacon callbacks



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: default avatarSimon Wunderlich <siwu@hrz.tu-chemnitz.de>
Signed-off-by: default avatarMathias Kretschmer <mathias.kretschmer@fokus.fraunhofer.de>
[small cleanups, catch identical chandef]
Signed-off-by: default avatarJohannes Berg <johannes.berg@intel.com>
parent 16ef1fe2
Loading
Loading
Loading
Loading
+37 −0
Original line number Original line Diff line number Diff line
@@ -152,11 +152,14 @@ struct ieee80211_low_level_stats {
 * @IEEE80211_CHANCTX_CHANGE_WIDTH: The channel width changed
 * @IEEE80211_CHANCTX_CHANGE_WIDTH: The channel width changed
 * @IEEE80211_CHANCTX_CHANGE_RX_CHAINS: The number of RX chains changed
 * @IEEE80211_CHANCTX_CHANGE_RX_CHAINS: The number of RX chains changed
 * @IEEE80211_CHANCTX_CHANGE_RADAR: radar detection flag 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 {
enum ieee80211_chanctx_change {
	IEEE80211_CHANCTX_CHANGE_WIDTH		= BIT(0),
	IEEE80211_CHANCTX_CHANGE_WIDTH		= BIT(0),
	IEEE80211_CHANCTX_CHANGE_RX_CHAINS	= BIT(1),
	IEEE80211_CHANCTX_CHANGE_RX_CHAINS	= BIT(1),
	IEEE80211_CHANCTX_CHANGE_RADAR		= BIT(2),
	IEEE80211_CHANCTX_CHANGE_RADAR		= BIT(2),
	IEEE80211_CHANCTX_CHANGE_CHANNEL	= BIT(3),
};
};


/**
/**
@@ -1084,6 +1087,7 @@ enum ieee80211_vif_flags {
 * @addr: address of this interface
 * @addr: address of this interface
 * @p2p: indicates whether this AP or STA interface is a p2p
 * @p2p: indicates whether this AP or STA interface is a p2p
 *	interface, i.e. a GO or p2p-sta respectively
 *	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,
 * @driver_flags: flags/capabilities the driver has for this interface,
 *	these need to be set (or cleared) when the interface is added
 *	these need to be set (or cleared) when the interface is added
 *	or, if supported by the driver, the interface type is changed
 *	or, if supported by the driver, the interface type is changed
@@ -1106,6 +1110,7 @@ struct ieee80211_vif {
	struct ieee80211_bss_conf bss_conf;
	struct ieee80211_bss_conf bss_conf;
	u8 addr[ETH_ALEN];
	u8 addr[ETH_ALEN];
	bool p2p;
	bool p2p;
	bool csa_active;


	u8 cab_queue;
	u8 cab_queue;
	u8 hw_queue[IEEE80211_NUM_ACS];
	u8 hw_queue[IEEE80211_NUM_ACS];
@@ -2637,6 +2642,16 @@ enum ieee80211_roc_type {
 * @ipv6_addr_change: IPv6 address assignment on the given interface changed.
 * @ipv6_addr_change: IPv6 address assignment on the given interface changed.
 *	Currently, this is only called for managed or P2P client interfaces.
 *	Currently, this is only called for managed or P2P client interfaces.
 *	This callback is optional; it must not sleep.
 *	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 {
struct ieee80211_ops {
	void (*tx)(struct ieee80211_hw *hw,
	void (*tx)(struct ieee80211_hw *hw,
@@ -2824,6 +2839,9 @@ struct ieee80211_ops {
				 struct ieee80211_vif *vif,
				 struct ieee80211_vif *vif,
				 struct inet6_dev *idev);
				 struct inet6_dev *idev);
#endif
#endif
	void (*channel_switch_beacon)(struct ieee80211_hw *hw,
				      struct ieee80211_vif *vif,
				      struct cfg80211_chan_def *chandef);
};
};


/**
/**
@@ -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);
	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
 * ieee80211_proberesp_get - retrieve a Probe Response template
 * @hw: pointer obtained from ieee80211_alloc_hw().
 * @hw: pointer obtained from ieee80211_alloc_hw().
+185 −2
Original line number Original line Diff line number Diff line
@@ -860,7 +860,7 @@ static int ieee80211_set_probe_resp(struct ieee80211_sub_if_data *sdata,
	return 0;
	return 0;
}
}


static int ieee80211_assign_beacon(struct ieee80211_sub_if_data *sdata,
int ieee80211_assign_beacon(struct ieee80211_sub_if_data *sdata,
			    struct cfg80211_beacon_data *params)
			    struct cfg80211_beacon_data *params)
{
{
	struct beacon_data *new, *old;
	struct beacon_data *new, *old;
@@ -1024,6 +1024,12 @@ static int ieee80211_change_beacon(struct wiphy *wiphy, struct net_device *dev,


	sdata = IEEE80211_DEV_TO_SUB_IF(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);
	old = rtnl_dereference(sdata->u.ap.beacon);
	if (!old)
	if (!old)
		return -ENOENT;
		return -ENOENT;
@@ -1048,6 +1054,10 @@ static int ieee80211_stop_ap(struct wiphy *wiphy, struct net_device *dev)
		return -ENOENT;
		return -ENOENT;
	old_probe_resp = rtnl_dereference(sdata->u.ap.probe_resp);
	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 */
	/* turn off carrier for this interface and dependent VLANs */
	list_for_each_entry(vlan, &sdata->u.ap.vlans, u.vlan.list)
	list_for_each_entry(vlan, &sdata->u.ap.vlans, u.vlan.list)
		netif_carrier_off(vlan->dev);
		netif_carrier_off(vlan->dev);
@@ -2775,6 +2785,178 @@ static int ieee80211_start_radar_detection(struct wiphy *wiphy,
	return 0;
	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,
static int ieee80211_mgmt_tx(struct wiphy *wiphy, struct wireless_dev *wdev,
			     struct ieee80211_channel *chan, bool offchan,
			     struct ieee80211_channel *chan, bool offchan,
			     unsigned int wait, const u8 *buf, size_t len,
			     unsigned int wait, const u8 *buf, size_t len,
@@ -3492,4 +3674,5 @@ struct cfg80211_ops mac80211_config_ops = {
	.get_et_strings = ieee80211_get_et_strings,
	.get_et_strings = ieee80211_get_et_strings,
	.get_channel = ieee80211_cfg_get_channel,
	.get_channel = ieee80211_cfg_get_channel,
	.start_radar_detection = ieee80211_start_radar_detection,
	.start_radar_detection = ieee80211_start_radar_detection,
	.channel_switch = ieee80211_channel_switch,
};
};
+58 −0
Original line number Original line Diff line number Diff line
@@ -410,6 +410,64 @@ int ieee80211_vif_use_channel(struct ieee80211_sub_if_data *sdata,
	return ret;
	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,
int ieee80211_vif_change_bandwidth(struct ieee80211_sub_if_data *sdata,
				   const struct cfg80211_chan_def *chandef,
				   const struct cfg80211_chan_def *chandef,
				   u32 *changed)
				   u32 *changed)
+13 −0
Original line number Original line Diff line number Diff line
@@ -1072,4 +1072,17 @@ static inline void drv_ipv6_addr_change(struct ieee80211_local *local,
}
}
#endif
#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 */
#endif /* __MAC80211_DRIVER_OPS */
+17 −0
Original line number Original line Diff line number Diff line
@@ -259,6 +259,8 @@ struct ieee80211_if_ap {
	struct beacon_data __rcu *beacon;
	struct beacon_data __rcu *beacon;
	struct probe_resp __rcu *probe_resp;
	struct probe_resp __rcu *probe_resp;


	/* to be used after channel switch. */
	struct cfg80211_beacon_data *next_beacon;
	struct list_head vlans;
	struct list_head vlans;


	struct ps_data ps;
	struct ps_data ps;
@@ -716,6 +718,11 @@ struct ieee80211_sub_if_data {


	struct ieee80211_tx_queue_params tx_conf[IEEE80211_NUM_ACS];
	struct ieee80211_tx_queue_params tx_conf[IEEE80211_NUM_ACS];


	struct work_struct csa_finalize_work;
	int csa_counter_offset_beacon;
	int csa_counter_offset_presp;
	bool csa_radar_required;

	/* used to reconfigure hardware SM PS */
	/* used to reconfigure hardware SM PS */
	struct work_struct recalc_smps;
	struct work_struct recalc_smps;


@@ -1372,6 +1379,9 @@ void ieee80211_roc_notify_destroy(struct ieee80211_roc_work *roc, bool free);
void ieee80211_sw_roc_work(struct work_struct *work);
void ieee80211_sw_roc_work(struct work_struct *work);
void ieee80211_handle_roc_started(struct ieee80211_roc_work *roc);
void ieee80211_handle_roc_started(struct ieee80211_roc_work *roc);


/* channel switch handling */
void ieee80211_csa_finalize_work(struct work_struct *work);

/* interface handling */
/* interface handling */
int ieee80211_iface_init(void);
int ieee80211_iface_init(void);
void ieee80211_iface_exit(void);
void ieee80211_iface_exit(void);
@@ -1393,6 +1403,8 @@ void ieee80211_del_virtual_monitor(struct ieee80211_local *local);


bool __ieee80211_recalc_txpower(struct ieee80211_sub_if_data *sdata);
bool __ieee80211_recalc_txpower(struct ieee80211_sub_if_data *sdata);
void ieee80211_recalc_txpower(struct ieee80211_sub_if_data *sdata);
void ieee80211_recalc_txpower(struct ieee80211_sub_if_data *sdata);
int ieee80211_assign_beacon(struct ieee80211_sub_if_data *sdata,
			    struct cfg80211_beacon_data *params);


static inline bool ieee80211_sdata_running(struct ieee80211_sub_if_data *sdata)
static inline bool ieee80211_sdata_running(struct ieee80211_sub_if_data *sdata)
{
{
@@ -1654,6 +1666,11 @@ int __must_check
ieee80211_vif_change_bandwidth(struct ieee80211_sub_if_data *sdata,
ieee80211_vif_change_bandwidth(struct ieee80211_sub_if_data *sdata,
			       const struct cfg80211_chan_def *chandef,
			       const struct cfg80211_chan_def *chandef,
			       u32 *changed);
			       u32 *changed);
/* NOTE: only use ieee80211_vif_change_channel() for channel switch */
int __must_check
ieee80211_vif_change_channel(struct ieee80211_sub_if_data *sdata,
			     const struct cfg80211_chan_def *chandef,
			     u32 *changed);
void ieee80211_vif_release_channel(struct ieee80211_sub_if_data *sdata);
void ieee80211_vif_release_channel(struct ieee80211_sub_if_data *sdata);
void ieee80211_vif_vlan_copy_chanctx(struct ieee80211_sub_if_data *sdata);
void ieee80211_vif_vlan_copy_chanctx(struct ieee80211_sub_if_data *sdata);
void ieee80211_vif_copy_chanctx_to_vlans(struct ieee80211_sub_if_data *sdata,
void ieee80211_vif_copy_chanctx_to_vlans(struct ieee80211_sub_if_data *sdata,
Loading