1089 lines · 28.5 KB · C
RawDownload
// SPDX-License-Identifier: GPL-2.0-only/* * Off-channel operation helpers * * Copyright 2003, Jouni Malinen <jkmaline@cc.hut.fi> * Copyright 2004, Instant802 Networks, Inc. * Copyright 2005, Devicescape Software, Inc. * Copyright 2006-2007	Jiri Benc <jbenc@suse.cz> * Copyright 2007, Michael Wu <flamingice@sourmilk.net> * Copyright 2009	Johannes Berg <johannes@sipsolutions.net> * Copyright (C) 2019, 2022-2026 Intel Corporation */#include <linux/export.h>#include <net/mac80211.h>#include "ieee80211_i.h"#include "driver-ops.h" /* * Tell our hardware to disable PS. * Optionally inform AP that we will go to sleep so that it will buffer * the frames while we are doing off-channel work.  This is optional * because we *may* be doing work on-operating channel, and want our * hardware unconditionally awake, but still let the AP send us normal frames. */static void ieee80211_offchannel_ps_enable(struct ieee80211_sub_if_data *sdata){	struct ieee80211_local *local = sdata->local;	struct ieee80211_if_managed *ifmgd = &sdata->u.mgd;	bool offchannel_ps_enabled = false; 	/* FIXME: what to do when local->pspolling is true? */ 	timer_delete_sync(&local->dynamic_ps_timer);	timer_delete_sync(&ifmgd->bcn_mon_timer);	timer_delete_sync(&ifmgd->conn_mon_timer); 	wiphy_work_cancel(local->hw.wiphy, &local->dynamic_ps_enable_work); 	if (local->hw.conf.flags & IEEE80211_CONF_PS) {		offchannel_ps_enabled = true;		local->hw.conf.flags &= ~IEEE80211_CONF_PS;		ieee80211_hw_config(local, -1, IEEE80211_CONF_CHANGE_PS);	} 	if (!offchannel_ps_enabled ||	    !ieee80211_hw_check(&local->hw, PS_NULLFUNC_STACK))		/*		 * If power save was enabled, no need to send a nullfunc		 * frame because AP knows that we are sleeping. But if the		 * hardware is creating the nullfunc frame for power save		 * status (ie. IEEE80211_HW_PS_NULLFUNC_STACK is not		 * enabled) and power save was enabled, the firmware just		 * sent a null frame with power save disabled. So we need		 * to send a new nullfunc frame to inform the AP that we		 * are again sleeping.		 */		ieee80211_send_nullfunc(local, sdata, true);} /* inform AP that we are awake again */static void ieee80211_offchannel_ps_disable(struct ieee80211_sub_if_data *sdata){	struct ieee80211_local *local = sdata->local; 	if (!local->ps_sdata)		ieee80211_send_nullfunc(local, sdata, false);	else if (local->hw.conf.dynamic_ps_timeout > 0) {		/*		 * the dynamic_ps_timer had been running before leaving the		 * operating channel, restart the timer now and send a nullfunc		 * frame to inform the AP that we are awake so that AP sends		 * the buffered packets (if any).		 */		ieee80211_send_nullfunc(local, sdata, false);		mod_timer(&local->dynamic_ps_timer, jiffies +			  msecs_to_jiffies(local->hw.conf.dynamic_ps_timeout));	} 	ieee80211_sta_reset_beacon_monitor(sdata);	ieee80211_sta_reset_conn_monitor(sdata);} void ieee80211_offchannel_stop_vifs(struct ieee80211_local *local){	struct ieee80211_sub_if_data *sdata; 	lockdep_assert_wiphy(local->hw.wiphy); 	if (WARN_ON(!local->emulate_chanctx))		return; 	/*	 * notify the AP about us leaving the channel and stop all	 * STA interfaces.	 */ 	/*	 * Stop queues and transmit all frames queued by the driver	 * before sending nullfunc to enable powersave at the AP.	 */	ieee80211_stop_queues_by_reason(&local->hw, IEEE80211_MAX_QUEUE_MAP,					IEEE80211_QUEUE_STOP_REASON_OFFCHANNEL,					false);	ieee80211_flush_queues(local, NULL, false); 	list_for_each_entry(sdata, &local->interfaces, list) {		if (!ieee80211_sdata_running(sdata))			continue; 		if (sdata->vif.type == NL80211_IFTYPE_P2P_DEVICE ||		    sdata->vif.type == NL80211_IFTYPE_NAN)			continue; 		if (sdata->vif.type != NL80211_IFTYPE_MONITOR)			set_bit(SDATA_STATE_OFFCHANNEL, &sdata->state); 		/* Check to see if we should disable beaconing. */		if (sdata->vif.bss_conf.enable_beacon) {			set_bit(SDATA_STATE_OFFCHANNEL_BEACON_STOPPED,				&sdata->state);			sdata->vif.bss_conf.enable_beacon = false;			ieee80211_link_info_change_notify(				sdata, &sdata->deflink,				BSS_CHANGED_BEACON_ENABLED);		} 		if (sdata->vif.type == NL80211_IFTYPE_STATION &&		    sdata->u.mgd.associated)			ieee80211_offchannel_ps_enable(sdata);	}} void ieee80211_offchannel_return(struct ieee80211_local *local){	struct ieee80211_sub_if_data *sdata; 	lockdep_assert_wiphy(local->hw.wiphy); 	if (WARN_ON(!local->emulate_chanctx))		return; 	list_for_each_entry(sdata, &local->interfaces, list) {		if (sdata->vif.type == NL80211_IFTYPE_P2P_DEVICE)			continue; 		if (sdata->vif.type != NL80211_IFTYPE_MONITOR)			clear_bit(SDATA_STATE_OFFCHANNEL, &sdata->state); 		if (!ieee80211_sdata_running(sdata))			continue; 		/* Tell AP we're back */		if (sdata->vif.type == NL80211_IFTYPE_STATION &&		    sdata->u.mgd.associated)			ieee80211_offchannel_ps_disable(sdata); 		if (test_and_clear_bit(SDATA_STATE_OFFCHANNEL_BEACON_STOPPED,				       &sdata->state)) {			sdata->vif.bss_conf.enable_beacon = true;			ieee80211_link_info_change_notify(				sdata, &sdata->deflink,				BSS_CHANGED_BEACON_ENABLED);		}	} 	ieee80211_wake_queues_by_reason(&local->hw, IEEE80211_MAX_QUEUE_MAP,					IEEE80211_QUEUE_STOP_REASON_OFFCHANNEL,					false);} static void ieee80211_roc_notify_destroy(struct ieee80211_roc_work *roc){	/* was never transmitted */	if (roc->frame) {		cfg80211_mgmt_tx_status(&roc->sdata->wdev, roc->mgmt_tx_cookie,					roc->frame->data, roc->frame->len,					false, GFP_KERNEL);		ieee80211_free_txskb(&roc->sdata->local->hw, roc->frame);	} 	if (!roc->mgmt_tx_cookie)		cfg80211_remain_on_channel_expired(&roc->sdata->wdev,						   roc->cookie, roc->chan,						   GFP_KERNEL);	else		cfg80211_tx_mgmt_expired(&roc->sdata->wdev,					 roc->mgmt_tx_cookie,					 roc->chan, GFP_KERNEL); 	list_del(&roc->list);	kfree(roc);} static unsigned long ieee80211_end_finished_rocs(struct ieee80211_local *local,						 unsigned long now){	struct ieee80211_roc_work *roc, *tmp;	long remaining_dur_min = LONG_MAX; 	lockdep_assert_wiphy(local->hw.wiphy); 	list_for_each_entry_safe(roc, tmp, &local->roc_list, list) {		long remaining; 		if (!roc->started)			break; 		remaining = roc->start_time +			    msecs_to_jiffies(roc->duration) -			    now; 		/* In case of HW ROC, it is possible that the HW finished the		 * ROC session before the actual requested time. In such a case		 * end the ROC session (disregarding the remaining time).		 */		if (roc->abort || roc->hw_begun || remaining <= 0)			ieee80211_roc_notify_destroy(roc);		else			remaining_dur_min = min(remaining_dur_min, remaining);	} 	return remaining_dur_min;} static bool ieee80211_recalc_sw_work(struct ieee80211_local *local,				     unsigned long now){	long dur = ieee80211_end_finished_rocs(local, now); 	if (dur == LONG_MAX)		return false; 	wiphy_delayed_work_queue(local->hw.wiphy, &local->roc_work, dur);	return true;} static void ieee80211_handle_roc_started(struct ieee80211_roc_work *roc,					 unsigned long start_time){	if (WARN_ON(roc->notified))		return; 	roc->start_time = start_time;	roc->started = true; 	if (roc->mgmt_tx_cookie) {		if (!WARN_ON(!roc->frame)) {			ieee80211_tx_skb_tid_band(roc->sdata, roc->frame, 7,						  roc->chan->band);			roc->frame = NULL;		}	} else {		cfg80211_ready_on_channel(&roc->sdata->wdev, roc->cookie,					  roc->chan, roc->req_duration,					  GFP_KERNEL);	} 	roc->notified = true;} static void ieee80211_hw_roc_start(struct wiphy *wiphy, struct wiphy_work *work){	struct ieee80211_local *local =		container_of(work, struct ieee80211_local, hw_roc_start);	struct ieee80211_roc_work *roc; 	lockdep_assert_wiphy(local->hw.wiphy); 	list_for_each_entry(roc, &local->roc_list, list) {		if (!roc->started)			break; 		roc->hw_begun = true;		ieee80211_handle_roc_started(roc, local->hw_roc_start_time);	}} void ieee80211_ready_on_channel(struct ieee80211_hw *hw){	struct ieee80211_local *local = hw_to_local(hw); 	local->hw_roc_start_time = jiffies; 	trace_api_ready_on_channel(local); 	wiphy_work_queue(hw->wiphy, &local->hw_roc_start);}EXPORT_SYMBOL_GPL(ieee80211_ready_on_channel); static void _ieee80211_start_next_roc(struct ieee80211_local *local){	struct ieee80211_roc_work *roc, *tmp;	enum ieee80211_roc_type type;	u32 min_dur, max_dur; 	lockdep_assert_wiphy(local->hw.wiphy); 	if (WARN_ON(list_empty(&local->roc_list)))		return; 	roc = list_first_entry(&local->roc_list, struct ieee80211_roc_work,			       list); 	if (WARN_ON(roc->started))		return; 	min_dur = roc->duration;	max_dur = roc->duration;	type = roc->type; 	list_for_each_entry(tmp, &local->roc_list, list) {		if (tmp == roc)			continue;		if (tmp->sdata != roc->sdata || tmp->chan != roc->chan)			break;		max_dur = max(tmp->duration, max_dur);		min_dur = min(tmp->duration, min_dur);		type = max(tmp->type, type);	} 	if (local->ops->remain_on_channel) {		int ret = drv_remain_on_channel(local, roc->sdata, roc->chan,						max_dur, type); 		if (ret) {			wiphy_warn(local->hw.wiphy,				   "failed to start next HW ROC (%d)\n", ret);			/*			 * queue the work struct again to avoid recursion			 * when multiple failures occur			 */			list_for_each_entry(tmp, &local->roc_list, list) {				if (tmp->sdata != roc->sdata ||				    tmp->chan != roc->chan)					break;				tmp->started = true;				tmp->abort = true;			}			wiphy_work_queue(local->hw.wiphy, &local->hw_roc_done);			return;		} 		/* we'll notify about the start once the HW calls back */		list_for_each_entry(tmp, &local->roc_list, list) {			if (tmp->sdata != roc->sdata || tmp->chan != roc->chan)				break;			tmp->started = true;		}	} else {		/* If actually operating on the desired channel (with at least		 * 20 MHz channel width) don't stop all the operations but still		 * treat it as though the ROC operation started properly, so		 * other ROC operations won't interfere with this one.		 *		 * Note: scan can't run, tmp_channel is what we use, so this		 * must be the currently active channel.		 */		roc->on_channel = roc->chan == local->hw.conf.chandef.chan; 		/* start this ROC */		ieee80211_recalc_idle(local); 		if (!roc->on_channel) {			ieee80211_offchannel_stop_vifs(local); 			local->tmp_channel = roc->chan;			ieee80211_hw_conf_chan(local);		} 		wiphy_delayed_work_queue(local->hw.wiphy, &local->roc_work,					 msecs_to_jiffies(min_dur)); 		/* tell userspace or send frame(s) */		list_for_each_entry(tmp, &local->roc_list, list) {			if (tmp->sdata != roc->sdata || tmp->chan != roc->chan)				break; 			tmp->on_channel = roc->on_channel;			ieee80211_handle_roc_started(tmp, jiffies);		}	}} void ieee80211_start_next_roc(struct ieee80211_local *local){	struct ieee80211_roc_work *roc; 	lockdep_assert_wiphy(local->hw.wiphy); 	if (list_empty(&local->roc_list)) {		ieee80211_run_deferred_scan(local);		return;	} 	/* defer roc if driver is not started (i.e. during reconfig) */	if (local->in_reconfig)		return; 	roc = list_first_entry(&local->roc_list, struct ieee80211_roc_work,			       list); 	if (WARN_ON_ONCE(roc->started))		return; 	if (local->ops->remain_on_channel) {		_ieee80211_start_next_roc(local);	} else {		/* delay it a bit */		wiphy_delayed_work_queue(local->hw.wiphy, &local->roc_work,					 round_jiffies_relative(HZ / 2));	}} void ieee80211_reconfig_roc(struct ieee80211_local *local){	struct ieee80211_roc_work *roc, *tmp; 	/*	 * In the software implementation can just continue with the	 * interruption due to reconfig, roc_work is still queued if	 * needed.	 */	if (!local->ops->remain_on_channel)		return; 	/* flush work so nothing from the driver is still pending */	wiphy_work_flush(local->hw.wiphy, &local->hw_roc_start);	wiphy_work_flush(local->hw.wiphy, &local->hw_roc_done); 	list_for_each_entry_safe(roc, tmp, &local->roc_list, list) {		if (!roc->started)			break; 		if (!roc->hw_begun) {			/* it didn't start in HW yet, so we can restart it */			roc->started = false;			continue;		} 		/* otherwise destroy it and tell userspace */		ieee80211_roc_notify_destroy(roc);	} 	ieee80211_start_next_roc(local);} static void __ieee80211_roc_work(struct ieee80211_local *local){	struct ieee80211_roc_work *roc;	bool on_channel; 	lockdep_assert_wiphy(local->hw.wiphy); 	if (WARN_ON(local->ops->remain_on_channel))		return; 	roc = list_first_entry_or_null(&local->roc_list,				       struct ieee80211_roc_work, list);	if (!roc)		return; 	if (!roc->started) {		WARN_ON(!local->emulate_chanctx);		_ieee80211_start_next_roc(local);	} else {		on_channel = roc->on_channel;		if (ieee80211_recalc_sw_work(local, jiffies))			return; 		/* careful - roc pointer became invalid during recalc */ 		if (!on_channel) {			ieee80211_flush_queues(local, NULL, false); 			local->tmp_channel = NULL;			ieee80211_hw_conf_chan(local); 			ieee80211_offchannel_return(local);		} 		ieee80211_recalc_idle(local);		ieee80211_start_next_roc(local);	}} static void ieee80211_roc_work(struct wiphy *wiphy, struct wiphy_work *work){	struct ieee80211_local *local =		container_of(work, struct ieee80211_local, roc_work.work); 	lockdep_assert_wiphy(local->hw.wiphy); 	__ieee80211_roc_work(local);} static void ieee80211_hw_roc_done(struct wiphy *wiphy, struct wiphy_work *work){	struct ieee80211_local *local =		container_of(work, struct ieee80211_local, hw_roc_done); 

Lines 1–500 of 1089.