xref: /openbmc/linux/net/mac80211/pm.c (revision 03ab8e6297acd1bc0eedaa050e2a1635c576fd11)
1b2441318SGreg Kroah-Hartman // SPDX-License-Identifier: GPL-2.0
2a05829a7SJohannes Berg /*
3a05829a7SJohannes Berg  * Portions
4a05829a7SJohannes Berg  * Copyright (C) 2020-2021 Intel Corporation
5a05829a7SJohannes Berg  */
6665af4fcSBob Copeland #include <net/mac80211.h>
7665af4fcSBob Copeland #include <net/rtnetlink.h>
8665af4fcSBob Copeland 
9665af4fcSBob Copeland #include "ieee80211_i.h"
105bb644a0SJohannes Berg #include "mesh.h"
1124487981SJohannes Berg #include "driver-ops.h"
12665af4fcSBob Copeland #include "led.h"
13665af4fcSBob Copeland 
ieee80211_sched_scan_cancel(struct ieee80211_local * local)140d440ea2SEliad Peller static void ieee80211_sched_scan_cancel(struct ieee80211_local *local)
150d440ea2SEliad Peller {
160d440ea2SEliad Peller 	if (ieee80211_request_sched_scan_stop(local))
170d440ea2SEliad Peller 		return;
18a05829a7SJohannes Berg 	cfg80211_sched_scan_stopped_locked(local->hw.wiphy, 0);
190d440ea2SEliad Peller }
200d440ea2SEliad Peller 
__ieee80211_suspend(struct ieee80211_hw * hw,struct cfg80211_wowlan * wowlan)21eecc4800SJohannes Berg int __ieee80211_suspend(struct ieee80211_hw *hw, struct cfg80211_wowlan *wowlan)
22665af4fcSBob Copeland {
23665af4fcSBob Copeland 	struct ieee80211_local *local = hw_to_local(hw);
24665af4fcSBob Copeland 	struct ieee80211_sub_if_data *sdata;
25665af4fcSBob Copeland 	struct sta_info *sta;
26665af4fcSBob Copeland 
2794f9b97bSJohannes Berg 	if (!local->open_count)
2894f9b97bSJohannes Berg 		goto suspend;
2994f9b97bSJohannes Berg 
30*b33fb28cSLoic Poulain 	local->suspending = true;
31*b33fb28cSLoic Poulain 	mb(); /* make suspending visible before any cancellation */
32*b33fb28cSLoic Poulain 
335bb644a0SJohannes Berg 	ieee80211_scan_cancel(local);
345bb644a0SJohannes Berg 
35164eb02dSSimon Wunderlich 	ieee80211_dfs_cac_cancel(local);
36164eb02dSSimon Wunderlich 
37c8f994eeSJohannes Berg 	ieee80211_roc_purge(local, NULL);
38c8f994eeSJohannes Berg 
393c3e21e7SJohannes Berg 	ieee80211_del_virtual_monitor(local);
403c3e21e7SJohannes Berg 
4127392719SEliad Peller 	if (ieee80211_hw_check(hw, AMPDU_AGGREGATION) &&
4227392719SEliad Peller 	    !(wowlan && wowlan->any)) {
43ca45de77SJohannes Berg 		mutex_lock(&local->sta_mtx);
44ca45de77SJohannes Berg 		list_for_each_entry(sta, &local->sta_list, list) {
45c2c98fdeSJohannes Berg 			set_sta_flag(sta, WLAN_STA_BLOCK_BA);
46c82c4a80SJohannes Berg 			ieee80211_sta_tear_down_BA_sessions(
47c82c4a80SJohannes Berg 					sta, AGG_STOP_LOCAL_REQUEST);
48ca45de77SJohannes Berg 		}
49ca45de77SJohannes Berg 		mutex_unlock(&local->sta_mtx);
50ca45de77SJohannes Berg 	}
51ca45de77SJohannes Berg 
520d440ea2SEliad Peller 	/* keep sched_scan only in case of 'any' trigger */
530d440ea2SEliad Peller 	if (!(wowlan && wowlan->any))
540d440ea2SEliad Peller 		ieee80211_sched_scan_cancel(local);
550d440ea2SEliad Peller 
5625420604SJohannes Berg 	ieee80211_stop_queues_by_reason(hw,
57445ea4e8SJohannes Berg 					IEEE80211_MAX_QUEUE_MAP,
58cca07b00SLuciano Coelho 					IEEE80211_QUEUE_STOP_REASON_SUSPEND,
59cca07b00SLuciano Coelho 					false);
6025420604SJohannes Berg 
61d34ba216SJohannes Berg 	/* flush out all packets */
628ceb5955SBob Copeland 	synchronize_net();
635bb644a0SJohannes Berg 
643b24f4c6SEmmanuel Grumbach 	ieee80211_flush_queues(local, NULL, true);
65ca45de77SJohannes Berg 
665bb644a0SJohannes Berg 	local->quiescing = true;
675bb644a0SJohannes Berg 	/* make quiescing visible to timers everywhere */
685bb644a0SJohannes Berg 	mb();
695bb644a0SJohannes Berg 
7042935ecaSLuis R. Rodriguez 	flush_workqueue(local->workqueue);
71665af4fcSBob Copeland 
725bb644a0SJohannes Berg 	/* Don't try to run timers while suspended. */
735bb644a0SJohannes Berg 	del_timer_sync(&local->sta_cleanup);
745bb644a0SJohannes Berg 
755bb644a0SJohannes Berg 	 /*
765bb644a0SJohannes Berg 	 * Note that this particular timer doesn't need to be
775bb644a0SJohannes Berg 	 * restarted at resume.
785bb644a0SJohannes Berg 	 */
795bb644a0SJohannes Berg 	cancel_work_sync(&local->dynamic_ps_enable_work);
805bb644a0SJohannes Berg 	del_timer_sync(&local->dynamic_ps_timer);
815bb644a0SJohannes Berg 
828bb6f4b9SLuciano Coelho 	local->wowlan = wowlan;
83eecc4800SJohannes Berg 	if (local->wowlan) {
84ef7c6725SJohannes Berg 		int err;
85ef7c6725SJohannes Berg 
86ef7c6725SJohannes Berg 		/* Drivers don't expect to suspend while some operations like
87ef7c6725SJohannes Berg 		 * authenticating or associating are in progress. It doesn't
88ef7c6725SJohannes Berg 		 * make sense anyway to accept that, since the authentication
89ef7c6725SJohannes Berg 		 * or association would never finish since the driver can't do
90ef7c6725SJohannes Berg 		 * that on its own.
91ef7c6725SJohannes Berg 		 * Thus, clean up in-progress auth/assoc first.
92ef7c6725SJohannes Berg 		 */
93ef7c6725SJohannes Berg 		list_for_each_entry(sdata, &local->interfaces, list) {
94ef7c6725SJohannes Berg 			if (!ieee80211_sdata_running(sdata))
95ef7c6725SJohannes Berg 				continue;
96ef7c6725SJohannes Berg 			if (sdata->vif.type != NL80211_IFTYPE_STATION)
97ef7c6725SJohannes Berg 				continue;
98ef7c6725SJohannes Berg 			ieee80211_mgd_quiesce(sdata);
99541b6ed7SChaitanya T K 			/* If suspended during TX in progress, and wowlan
100541b6ed7SChaitanya T K 			 * is enabled (connection will be active) there
101541b6ed7SChaitanya T K 			 * can be a race where the driver is put out
102541b6ed7SChaitanya T K 			 * of power-save due to TX and during suspend
103541b6ed7SChaitanya T K 			 * dynamic_ps_timer is cancelled and TX packet
104541b6ed7SChaitanya T K 			 * is flushed, leaving the driver in ACTIVE even
105541b6ed7SChaitanya T K 			 * after resuming until dynamic_ps_timer puts
106541b6ed7SChaitanya T K 			 * driver back in DOZE.
107541b6ed7SChaitanya T K 			 */
108541b6ed7SChaitanya T K 			if (sdata->u.mgd.associated &&
109541b6ed7SChaitanya T K 			    sdata->u.mgd.powersave &&
110541b6ed7SChaitanya T K 			     !(local->hw.conf.flags & IEEE80211_CONF_PS)) {
111541b6ed7SChaitanya T K 				local->hw.conf.flags |= IEEE80211_CONF_PS;
112541b6ed7SChaitanya T K 				ieee80211_hw_config(local,
113541b6ed7SChaitanya T K 						    IEEE80211_CONF_CHANGE_PS);
114541b6ed7SChaitanya T K 			}
115ef7c6725SJohannes Berg 		}
116ef7c6725SJohannes Berg 
117ef7c6725SJohannes Berg 		err = drv_suspend(local, wowlan);
1182b4562dfSJohannes Berg 		if (err < 0) {
119eecc4800SJohannes Berg 			local->quiescing = false;
1203b08cf6bSPontus Fuchs 			local->wowlan = false;
12130686bf7SJohannes Berg 			if (ieee80211_hw_check(hw, AMPDU_AGGREGATION)) {
1229ea4fa15SEyal Shapira 				mutex_lock(&local->sta_mtx);
1239ea4fa15SEyal Shapira 				list_for_each_entry(sta,
1249ea4fa15SEyal Shapira 						    &local->sta_list, list) {
1259ea4fa15SEyal Shapira 					clear_sta_flag(sta, WLAN_STA_BLOCK_BA);
1269ea4fa15SEyal Shapira 				}
1279ea4fa15SEyal Shapira 				mutex_unlock(&local->sta_mtx);
1289ea4fa15SEyal Shapira 			}
1299ea4fa15SEyal Shapira 			ieee80211_wake_queues_by_reason(hw,
130445ea4e8SJohannes Berg 					IEEE80211_MAX_QUEUE_MAP,
131cca07b00SLuciano Coelho 					IEEE80211_QUEUE_STOP_REASON_SUSPEND,
132cca07b00SLuciano Coelho 					false);
133eecc4800SJohannes Berg 			return err;
1342b4562dfSJohannes Berg 		} else if (err > 0) {
1352b4562dfSJohannes Berg 			WARN_ON(err != 1);
13623e37098SJohannes Berg 			/* cfg80211 will call back into mac80211 to disconnect
13723e37098SJohannes Berg 			 * all interfaces, allow that to proceed properly
13823e37098SJohannes Berg 			 */
13923e37098SJohannes Berg 			ieee80211_wake_queues_by_reason(hw,
14023e37098SJohannes Berg 					IEEE80211_MAX_QUEUE_MAP,
14123e37098SJohannes Berg 					IEEE80211_QUEUE_STOP_REASON_SUSPEND,
14223e37098SJohannes Berg 					false);
14381256969SStanislaw Gruszka 			return err;
1442b4562dfSJohannes Berg 		} else {
145eecc4800SJohannes Berg 			goto suspend;
146eecc4800SJohannes Berg 		}
1472b4562dfSJohannes Berg 	}
148eecc4800SJohannes Berg 
149cd34f647SStanislaw Gruszka 	/* remove all interfaces that were created in the driver */
150665af4fcSBob Copeland 	list_for_each_entry(sdata, &local->interfaces, list) {
1511a1cb744SJohannes Berg 		if (!ieee80211_sdata_running(sdata))
1525bb644a0SJohannes Berg 			continue;
1531a1cb744SJohannes Berg 		switch (sdata->vif.type) {
1541a1cb744SJohannes Berg 		case NL80211_IFTYPE_AP_VLAN:
1551a1cb744SJohannes Berg 		case NL80211_IFTYPE_MONITOR:
1561a1cb744SJohannes Berg 			continue;
1571a1cb744SJohannes Berg 		case NL80211_IFTYPE_STATION:
1581a1cb744SJohannes Berg 			ieee80211_mgd_quiesce(sdata);
1591a1cb744SJohannes Berg 			break;
1601a1cb744SJohannes Berg 		default:
1611a1cb744SJohannes Berg 			break;
1621a1cb744SJohannes Berg 		}
163cd34f647SStanislaw Gruszka 
164a9e9200dSMatt Chen 		flush_delayed_work(&sdata->dec_tailroom_needed_wk);
1657b7eab6fSJohannes Berg 		drv_remove_interface(local, sdata);
166665af4fcSBob Copeland 	}
167665af4fcSBob Copeland 
16812e7f517SStanislaw Gruszka 	/*
16912e7f517SStanislaw Gruszka 	 * We disconnected on all interfaces before suspend, all channel
17012e7f517SStanislaw Gruszka 	 * contexts should be released.
17112e7f517SStanislaw Gruszka 	 */
17212e7f517SStanislaw Gruszka 	WARN_ON(!list_empty(&local->chanctx_list));
1734b6f1dd6SJohannes Berg 
17489c3a8acSJohannes Berg 	/* stop hardware - this must stop RX */
17584f6a01cSJohannes Berg 	ieee80211_stop_device(local);
17689c3a8acSJohannes Berg 
177eecc4800SJohannes Berg  suspend:
1785bb644a0SJohannes Berg 	local->suspended = true;
17989c3a8acSJohannes Berg 	/* need suspended to be visible before quiescing is false */
18089c3a8acSJohannes Berg 	barrier();
1815bb644a0SJohannes Berg 	local->quiescing = false;
182*b33fb28cSLoic Poulain 	local->suspending = false;
183e874e658SBob Copeland 
184665af4fcSBob Copeland 	return 0;
185665af4fcSBob Copeland }
186665af4fcSBob Copeland 
187f2753ddbSJohannes Berg /*
188f2753ddbSJohannes Berg  * __ieee80211_resume() is a static inline which just calls
189f2753ddbSJohannes Berg  * ieee80211_reconfig(), which is also needed for hardware
190f2753ddbSJohannes Berg  * hang/firmware failure/etc. recovery.
191f2753ddbSJohannes Berg  */
192cd8f7cb4SJohannes Berg 
ieee80211_report_wowlan_wakeup(struct ieee80211_vif * vif,struct cfg80211_wowlan_wakeup * wakeup,gfp_t gfp)193cd8f7cb4SJohannes Berg void ieee80211_report_wowlan_wakeup(struct ieee80211_vif *vif,
194cd8f7cb4SJohannes Berg 				    struct cfg80211_wowlan_wakeup *wakeup,
195cd8f7cb4SJohannes Berg 				    gfp_t gfp)
196cd8f7cb4SJohannes Berg {
197cd8f7cb4SJohannes Berg 	struct ieee80211_sub_if_data *sdata = vif_to_sdata(vif);
198cd8f7cb4SJohannes Berg 
199cd8f7cb4SJohannes Berg 	cfg80211_report_wowlan_wakeup(&sdata->wdev, wakeup, gfp);
200cd8f7cb4SJohannes Berg }
201cd8f7cb4SJohannes Berg EXPORT_SYMBOL(ieee80211_report_wowlan_wakeup);
202