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