118334Speter// SPDX-License-Identifier: GPL-2.0
2132718Skan/*
318334Speter * Portions
418334Speter * Copyright (C) 2020-2021, 2023 Intel Corporation
518334Speter */
618334Speter#include <net/mac80211.h>
718334Speter#include <net/rtnetlink.h>
818334Speter
918334Speter#include "ieee80211_i.h"
1018334Speter#include "mesh.h"
1118334Speter#include "driver-ops.h"
1218334Speter#include "led.h"
1318334Speter
1418334Speterstatic void ieee80211_sched_scan_cancel(struct ieee80211_local *local)
1518334Speter{
16169689Skan	if (ieee80211_request_sched_scan_stop(local))
1718334Speter		return;
18132718Skan	cfg80211_sched_scan_stopped_locked(local->hw.wiphy, 0);
1950397Sobrien}
20132718Skan
21132718Skanint __ieee80211_suspend(struct ieee80211_hw *hw, struct cfg80211_wowlan *wowlan)
2218334Speter{
2318334Speter	struct ieee80211_local *local = hw_to_local(hw);
2418334Speter	struct ieee80211_sub_if_data *sdata;
2518334Speter	struct sta_info *sta;
2618334Speter
2718334Speter	if (!local->open_count)
2818334Speter		goto suspend;
29132718Skan
3018334Speter	local->suspending = true;
3118334Speter	mb(); /* make suspending visible before any cancellation */
3218334Speter
3318334Speter	ieee80211_scan_cancel(local);
3418334Speter
3518334Speter	ieee80211_dfs_cac_cancel(local);
3618334Speter
37117395Skan	ieee80211_roc_purge(local, NULL);
3890075Sobrien
3918334Speter	ieee80211_del_virtual_monitor(local);
4018334Speter
4118334Speter	if (ieee80211_hw_check(hw, AMPDU_AGGREGATION) &&
4218334Speter	    !(wowlan && wowlan->any)) {
4318334Speter		lockdep_assert_wiphy(local->hw.wiphy);
44132718Skan		list_for_each_entry(sta, &local->sta_list, list) {
4518334Speter			set_sta_flag(sta, WLAN_STA_BLOCK_BA);
4690075Sobrien			ieee80211_sta_tear_down_BA_sessions(
4790075Sobrien					sta, AGG_STOP_LOCAL_REQUEST);
4890075Sobrien		}
4990075Sobrien	}
5018334Speter
5118334Speter	/* keep sched_scan only in case of 'any' trigger */
5218334Speter	if (!(wowlan && wowlan->any))
5318334Speter		ieee80211_sched_scan_cancel(local);
54117395Skan
5518334Speter	ieee80211_stop_queues_by_reason(hw,
5618334Speter					IEEE80211_MAX_QUEUE_MAP,
5718334Speter					IEEE80211_QUEUE_STOP_REASON_SUSPEND,
58132718Skan					false);
5918334Speter
6018334Speter	/* flush out all packets */
6190075Sobrien	synchronize_net();
6218334Speter
6318334Speter	ieee80211_flush_queues(local, NULL, true);
6418334Speter
6590075Sobrien	local->quiescing = true;
6618334Speter	/* make quiescing visible to timers everywhere */
6790075Sobrien	mb();
6818334Speter
6918334Speter	flush_workqueue(local->workqueue);
7018334Speter
7190075Sobrien	/* Don't try to run timers while suspended. */
7218334Speter	del_timer_sync(&local->sta_cleanup);
7318334Speter
7418334Speter	 /*
7518334Speter	 * Note that this particular timer doesn't need to be
7618334Speter	 * restarted at resume.
77132718Skan	 */
7818334Speter	wiphy_work_cancel(local->hw.wiphy, &local->dynamic_ps_enable_work);
7918334Speter	del_timer_sync(&local->dynamic_ps_timer);
8090075Sobrien
8118334Speter	local->wowlan = wowlan;
8218334Speter	if (local->wowlan) {
8318334Speter		int err;
8418334Speter
8518334Speter		/* Drivers don't expect to suspend while some operations like
8618334Speter		 * authenticating or associating are in progress. It doesn't
8718334Speter		 * make sense anyway to accept that, since the authentication
8818334Speter		 * or association would never finish since the driver can't do
8918334Speter		 * that on its own.
9018334Speter		 * Thus, clean up in-progress auth/assoc first.
9118334Speter		 */
9218334Speter		list_for_each_entry(sdata, &local->interfaces, list) {
9318334Speter			if (!ieee80211_sdata_running(sdata))
9418334Speter				continue;
9518334Speter			if (sdata->vif.type != NL80211_IFTYPE_STATION)
9618334Speter				continue;
9718334Speter			ieee80211_mgd_quiesce(sdata);
9818334Speter			/* If suspended during TX in progress, and wowlan
9990075Sobrien			 * is enabled (connection will be active) there
10018334Speter			 * can be a race where the driver is put out
10190075Sobrien			 * of power-save due to TX and during suspend
10218334Speter			 * dynamic_ps_timer is cancelled and TX packet
10318334Speter			 * is flushed, leaving the driver in ACTIVE even
10418334Speter			 * after resuming until dynamic_ps_timer puts
10518334Speter			 * driver back in DOZE.
10650397Sobrien			 */
10718334Speter			if (sdata->u.mgd.associated &&
10818334Speter			    sdata->u.mgd.powersave &&
109132718Skan			     !(local->hw.conf.flags & IEEE80211_CONF_PS)) {
11018334Speter				local->hw.conf.flags |= IEEE80211_CONF_PS;
11118334Speter				ieee80211_hw_config(local,
11218334Speter						    IEEE80211_CONF_CHANGE_PS);
11318334Speter			}
11418334Speter		}
11518334Speter
11618334Speter		err = drv_suspend(local, wowlan);
11718334Speter		if (err < 0) {
11818334Speter			local->quiescing = false;
11918334Speter			local->wowlan = false;
12018334Speter			if (ieee80211_hw_check(hw, AMPDU_AGGREGATION)) {
12118334Speter				lockdep_assert_wiphy(local->hw.wiphy);
12218334Speter				list_for_each_entry(sta,
12318334Speter						    &local->sta_list, list) {
12418334Speter					clear_sta_flag(sta, WLAN_STA_BLOCK_BA);
12518334Speter				}
12618334Speter			}
12718334Speter			ieee80211_wake_queues_by_reason(hw,
12818334Speter					IEEE80211_MAX_QUEUE_MAP,
12918334Speter					IEEE80211_QUEUE_STOP_REASON_SUSPEND,
13018334Speter					false);
13118334Speter			return err;
13218334Speter		} else if (err > 0) {
13318334Speter			WARN_ON(err != 1);
13418334Speter			/* cfg80211 will call back into mac80211 to disconnect
13518334Speter			 * all interfaces, allow that to proceed properly
13618334Speter			 */
13718334Speter			ieee80211_wake_queues_by_reason(hw,
13818334Speter					IEEE80211_MAX_QUEUE_MAP,
13918334Speter					IEEE80211_QUEUE_STOP_REASON_SUSPEND,
14018334Speter					false);
14118334Speter			return err;
14218334Speter		} else {
14318334Speter			goto suspend;
14418334Speter		}
145132718Skan	}
14618334Speter
14718334Speter	/* remove all interfaces that were created in the driver */
14890075Sobrien	list_for_each_entry(sdata, &local->interfaces, list) {
14918334Speter		if (!ieee80211_sdata_running(sdata))
15018334Speter			continue;
15118334Speter		switch (sdata->vif.type) {
15218334Speter		case NL80211_IFTYPE_AP_VLAN:
15318334Speter		case NL80211_IFTYPE_MONITOR:
15490075Sobrien			continue;
15518334Speter		case NL80211_IFTYPE_STATION:
15690075Sobrien			ieee80211_mgd_quiesce(sdata);
15718334Speter			break;
15818334Speter		default:
15918334Speter			break;
16018334Speter		}
16118334Speter
162132718Skan		wiphy_delayed_work_flush(local->hw.wiphy,
16318334Speter					 &sdata->dec_tailroom_needed_wk);
16418334Speter		drv_remove_interface(local, sdata);
16590075Sobrien	}
16618334Speter
16718334Speter	/*
16818334Speter	 * We disconnected on all interfaces before suspend, all channel
16918334Speter	 * contexts should be released.
17018334Speter	 */
17118334Speter	WARN_ON(!list_empty(&local->chanctx_list));
17218334Speter
17318334Speter	/* stop hardware - this must stop RX */
17418334Speter	ieee80211_stop_device(local);
17518334Speter
17618334Speter suspend:
17718334Speter	local->suspended = true;
17818334Speter	/* need suspended to be visible before quiescing is false */
17918334Speter	barrier();
18018334Speter	local->quiescing = false;
18118334Speter	local->suspending = false;
18218334Speter
18318334Speter	return 0;
18418334Speter}
18518334Speter
18618334Speter/*
18718334Speter * __ieee80211_resume() is a static inline which just calls
18818334Speter * ieee80211_reconfig(), which is also needed for hardware
18918334Speter * hang/firmware failure/etc. recovery.
19018334Speter */
19118334Speter
19218334Spetervoid ieee80211_report_wowlan_wakeup(struct ieee80211_vif *vif,
19318334Speter				    struct cfg80211_wowlan_wakeup *wakeup,
19418334Speter				    gfp_t gfp)
19518334Speter{
19618334Speter	struct ieee80211_sub_if_data *sdata = vif_to_sdata(vif);
19718334Speter
19818334Speter	cfg80211_report_wowlan_wakeup(&sdata->wdev, wakeup, gfp);
19950397Sobrien}
20018334SpeterEXPORT_SYMBOL(ieee80211_report_wowlan_wakeup);
20118334Speter