18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0 28c2ecf20Sopenharmony_ci#include <net/mac80211.h> 38c2ecf20Sopenharmony_ci#include <net/rtnetlink.h> 48c2ecf20Sopenharmony_ci 58c2ecf20Sopenharmony_ci#include "ieee80211_i.h" 68c2ecf20Sopenharmony_ci#include "mesh.h" 78c2ecf20Sopenharmony_ci#include "driver-ops.h" 88c2ecf20Sopenharmony_ci#include "led.h" 98c2ecf20Sopenharmony_ci 108c2ecf20Sopenharmony_cistatic void ieee80211_sched_scan_cancel(struct ieee80211_local *local) 118c2ecf20Sopenharmony_ci{ 128c2ecf20Sopenharmony_ci if (ieee80211_request_sched_scan_stop(local)) 138c2ecf20Sopenharmony_ci return; 148c2ecf20Sopenharmony_ci cfg80211_sched_scan_stopped_rtnl(local->hw.wiphy, 0); 158c2ecf20Sopenharmony_ci} 168c2ecf20Sopenharmony_ci 178c2ecf20Sopenharmony_ciint __ieee80211_suspend(struct ieee80211_hw *hw, struct cfg80211_wowlan *wowlan) 188c2ecf20Sopenharmony_ci{ 198c2ecf20Sopenharmony_ci struct ieee80211_local *local = hw_to_local(hw); 208c2ecf20Sopenharmony_ci struct ieee80211_sub_if_data *sdata; 218c2ecf20Sopenharmony_ci struct sta_info *sta; 228c2ecf20Sopenharmony_ci 238c2ecf20Sopenharmony_ci if (!local->open_count) 248c2ecf20Sopenharmony_ci goto suspend; 258c2ecf20Sopenharmony_ci 268c2ecf20Sopenharmony_ci ieee80211_scan_cancel(local); 278c2ecf20Sopenharmony_ci 288c2ecf20Sopenharmony_ci ieee80211_dfs_cac_cancel(local); 298c2ecf20Sopenharmony_ci 308c2ecf20Sopenharmony_ci ieee80211_roc_purge(local, NULL); 318c2ecf20Sopenharmony_ci 328c2ecf20Sopenharmony_ci ieee80211_del_virtual_monitor(local); 338c2ecf20Sopenharmony_ci 348c2ecf20Sopenharmony_ci if (ieee80211_hw_check(hw, AMPDU_AGGREGATION) && 358c2ecf20Sopenharmony_ci !(wowlan && wowlan->any)) { 368c2ecf20Sopenharmony_ci mutex_lock(&local->sta_mtx); 378c2ecf20Sopenharmony_ci list_for_each_entry(sta, &local->sta_list, list) { 388c2ecf20Sopenharmony_ci set_sta_flag(sta, WLAN_STA_BLOCK_BA); 398c2ecf20Sopenharmony_ci ieee80211_sta_tear_down_BA_sessions( 408c2ecf20Sopenharmony_ci sta, AGG_STOP_LOCAL_REQUEST); 418c2ecf20Sopenharmony_ci } 428c2ecf20Sopenharmony_ci mutex_unlock(&local->sta_mtx); 438c2ecf20Sopenharmony_ci } 448c2ecf20Sopenharmony_ci 458c2ecf20Sopenharmony_ci /* keep sched_scan only in case of 'any' trigger */ 468c2ecf20Sopenharmony_ci if (!(wowlan && wowlan->any)) 478c2ecf20Sopenharmony_ci ieee80211_sched_scan_cancel(local); 488c2ecf20Sopenharmony_ci 498c2ecf20Sopenharmony_ci ieee80211_stop_queues_by_reason(hw, 508c2ecf20Sopenharmony_ci IEEE80211_MAX_QUEUE_MAP, 518c2ecf20Sopenharmony_ci IEEE80211_QUEUE_STOP_REASON_SUSPEND, 528c2ecf20Sopenharmony_ci false); 538c2ecf20Sopenharmony_ci 548c2ecf20Sopenharmony_ci /* flush out all packets */ 558c2ecf20Sopenharmony_ci synchronize_net(); 568c2ecf20Sopenharmony_ci 578c2ecf20Sopenharmony_ci ieee80211_flush_queues(local, NULL, true); 588c2ecf20Sopenharmony_ci 598c2ecf20Sopenharmony_ci local->quiescing = true; 608c2ecf20Sopenharmony_ci /* make quiescing visible to timers everywhere */ 618c2ecf20Sopenharmony_ci mb(); 628c2ecf20Sopenharmony_ci 638c2ecf20Sopenharmony_ci flush_workqueue(local->workqueue); 648c2ecf20Sopenharmony_ci 658c2ecf20Sopenharmony_ci /* Don't try to run timers while suspended. */ 668c2ecf20Sopenharmony_ci del_timer_sync(&local->sta_cleanup); 678c2ecf20Sopenharmony_ci 688c2ecf20Sopenharmony_ci /* 698c2ecf20Sopenharmony_ci * Note that this particular timer doesn't need to be 708c2ecf20Sopenharmony_ci * restarted at resume. 718c2ecf20Sopenharmony_ci */ 728c2ecf20Sopenharmony_ci cancel_work_sync(&local->dynamic_ps_enable_work); 738c2ecf20Sopenharmony_ci del_timer_sync(&local->dynamic_ps_timer); 748c2ecf20Sopenharmony_ci 758c2ecf20Sopenharmony_ci local->wowlan = wowlan; 768c2ecf20Sopenharmony_ci if (local->wowlan) { 778c2ecf20Sopenharmony_ci int err; 788c2ecf20Sopenharmony_ci 798c2ecf20Sopenharmony_ci /* Drivers don't expect to suspend while some operations like 808c2ecf20Sopenharmony_ci * authenticating or associating are in progress. It doesn't 818c2ecf20Sopenharmony_ci * make sense anyway to accept that, since the authentication 828c2ecf20Sopenharmony_ci * or association would never finish since the driver can't do 838c2ecf20Sopenharmony_ci * that on its own. 848c2ecf20Sopenharmony_ci * Thus, clean up in-progress auth/assoc first. 858c2ecf20Sopenharmony_ci */ 868c2ecf20Sopenharmony_ci list_for_each_entry(sdata, &local->interfaces, list) { 878c2ecf20Sopenharmony_ci if (!ieee80211_sdata_running(sdata)) 888c2ecf20Sopenharmony_ci continue; 898c2ecf20Sopenharmony_ci if (sdata->vif.type != NL80211_IFTYPE_STATION) 908c2ecf20Sopenharmony_ci continue; 918c2ecf20Sopenharmony_ci ieee80211_mgd_quiesce(sdata); 928c2ecf20Sopenharmony_ci /* If suspended during TX in progress, and wowlan 938c2ecf20Sopenharmony_ci * is enabled (connection will be active) there 948c2ecf20Sopenharmony_ci * can be a race where the driver is put out 958c2ecf20Sopenharmony_ci * of power-save due to TX and during suspend 968c2ecf20Sopenharmony_ci * dynamic_ps_timer is cancelled and TX packet 978c2ecf20Sopenharmony_ci * is flushed, leaving the driver in ACTIVE even 988c2ecf20Sopenharmony_ci * after resuming until dynamic_ps_timer puts 998c2ecf20Sopenharmony_ci * driver back in DOZE. 1008c2ecf20Sopenharmony_ci */ 1018c2ecf20Sopenharmony_ci if (sdata->u.mgd.associated && 1028c2ecf20Sopenharmony_ci sdata->u.mgd.powersave && 1038c2ecf20Sopenharmony_ci !(local->hw.conf.flags & IEEE80211_CONF_PS)) { 1048c2ecf20Sopenharmony_ci local->hw.conf.flags |= IEEE80211_CONF_PS; 1058c2ecf20Sopenharmony_ci ieee80211_hw_config(local, 1068c2ecf20Sopenharmony_ci IEEE80211_CONF_CHANGE_PS); 1078c2ecf20Sopenharmony_ci } 1088c2ecf20Sopenharmony_ci } 1098c2ecf20Sopenharmony_ci 1108c2ecf20Sopenharmony_ci err = drv_suspend(local, wowlan); 1118c2ecf20Sopenharmony_ci if (err < 0) { 1128c2ecf20Sopenharmony_ci local->quiescing = false; 1138c2ecf20Sopenharmony_ci local->wowlan = false; 1148c2ecf20Sopenharmony_ci if (ieee80211_hw_check(hw, AMPDU_AGGREGATION)) { 1158c2ecf20Sopenharmony_ci mutex_lock(&local->sta_mtx); 1168c2ecf20Sopenharmony_ci list_for_each_entry(sta, 1178c2ecf20Sopenharmony_ci &local->sta_list, list) { 1188c2ecf20Sopenharmony_ci clear_sta_flag(sta, WLAN_STA_BLOCK_BA); 1198c2ecf20Sopenharmony_ci } 1208c2ecf20Sopenharmony_ci mutex_unlock(&local->sta_mtx); 1218c2ecf20Sopenharmony_ci } 1228c2ecf20Sopenharmony_ci ieee80211_wake_queues_by_reason(hw, 1238c2ecf20Sopenharmony_ci IEEE80211_MAX_QUEUE_MAP, 1248c2ecf20Sopenharmony_ci IEEE80211_QUEUE_STOP_REASON_SUSPEND, 1258c2ecf20Sopenharmony_ci false); 1268c2ecf20Sopenharmony_ci return err; 1278c2ecf20Sopenharmony_ci } else if (err > 0) { 1288c2ecf20Sopenharmony_ci WARN_ON(err != 1); 1298c2ecf20Sopenharmony_ci /* cfg80211 will call back into mac80211 to disconnect 1308c2ecf20Sopenharmony_ci * all interfaces, allow that to proceed properly 1318c2ecf20Sopenharmony_ci */ 1328c2ecf20Sopenharmony_ci ieee80211_wake_queues_by_reason(hw, 1338c2ecf20Sopenharmony_ci IEEE80211_MAX_QUEUE_MAP, 1348c2ecf20Sopenharmony_ci IEEE80211_QUEUE_STOP_REASON_SUSPEND, 1358c2ecf20Sopenharmony_ci false); 1368c2ecf20Sopenharmony_ci return err; 1378c2ecf20Sopenharmony_ci } else { 1388c2ecf20Sopenharmony_ci goto suspend; 1398c2ecf20Sopenharmony_ci } 1408c2ecf20Sopenharmony_ci } 1418c2ecf20Sopenharmony_ci 1428c2ecf20Sopenharmony_ci /* remove all interfaces that were created in the driver */ 1438c2ecf20Sopenharmony_ci list_for_each_entry(sdata, &local->interfaces, list) { 1448c2ecf20Sopenharmony_ci if (!ieee80211_sdata_running(sdata)) 1458c2ecf20Sopenharmony_ci continue; 1468c2ecf20Sopenharmony_ci switch (sdata->vif.type) { 1478c2ecf20Sopenharmony_ci case NL80211_IFTYPE_AP_VLAN: 1488c2ecf20Sopenharmony_ci case NL80211_IFTYPE_MONITOR: 1498c2ecf20Sopenharmony_ci continue; 1508c2ecf20Sopenharmony_ci case NL80211_IFTYPE_STATION: 1518c2ecf20Sopenharmony_ci ieee80211_mgd_quiesce(sdata); 1528c2ecf20Sopenharmony_ci break; 1538c2ecf20Sopenharmony_ci case NL80211_IFTYPE_WDS: 1548c2ecf20Sopenharmony_ci /* tear down aggregation sessions and remove STAs */ 1558c2ecf20Sopenharmony_ci mutex_lock(&local->sta_mtx); 1568c2ecf20Sopenharmony_ci sta = sdata->u.wds.sta; 1578c2ecf20Sopenharmony_ci if (sta && sta->uploaded) { 1588c2ecf20Sopenharmony_ci enum ieee80211_sta_state state; 1598c2ecf20Sopenharmony_ci 1608c2ecf20Sopenharmony_ci state = sta->sta_state; 1618c2ecf20Sopenharmony_ci for (; state > IEEE80211_STA_NOTEXIST; state--) 1628c2ecf20Sopenharmony_ci WARN_ON(drv_sta_state(local, sta->sdata, 1638c2ecf20Sopenharmony_ci sta, state, 1648c2ecf20Sopenharmony_ci state - 1)); 1658c2ecf20Sopenharmony_ci } 1668c2ecf20Sopenharmony_ci mutex_unlock(&local->sta_mtx); 1678c2ecf20Sopenharmony_ci break; 1688c2ecf20Sopenharmony_ci default: 1698c2ecf20Sopenharmony_ci break; 1708c2ecf20Sopenharmony_ci } 1718c2ecf20Sopenharmony_ci 1728c2ecf20Sopenharmony_ci flush_delayed_work(&sdata->dec_tailroom_needed_wk); 1738c2ecf20Sopenharmony_ci drv_remove_interface(local, sdata); 1748c2ecf20Sopenharmony_ci } 1758c2ecf20Sopenharmony_ci 1768c2ecf20Sopenharmony_ci /* 1778c2ecf20Sopenharmony_ci * We disconnected on all interfaces before suspend, all channel 1788c2ecf20Sopenharmony_ci * contexts should be released. 1798c2ecf20Sopenharmony_ci */ 1808c2ecf20Sopenharmony_ci WARN_ON(!list_empty(&local->chanctx_list)); 1818c2ecf20Sopenharmony_ci 1828c2ecf20Sopenharmony_ci /* stop hardware - this must stop RX */ 1838c2ecf20Sopenharmony_ci ieee80211_stop_device(local); 1848c2ecf20Sopenharmony_ci 1858c2ecf20Sopenharmony_ci suspend: 1868c2ecf20Sopenharmony_ci local->suspended = true; 1878c2ecf20Sopenharmony_ci /* need suspended to be visible before quiescing is false */ 1888c2ecf20Sopenharmony_ci barrier(); 1898c2ecf20Sopenharmony_ci local->quiescing = false; 1908c2ecf20Sopenharmony_ci 1918c2ecf20Sopenharmony_ci return 0; 1928c2ecf20Sopenharmony_ci} 1938c2ecf20Sopenharmony_ci 1948c2ecf20Sopenharmony_ci/* 1958c2ecf20Sopenharmony_ci * __ieee80211_resume() is a static inline which just calls 1968c2ecf20Sopenharmony_ci * ieee80211_reconfig(), which is also needed for hardware 1978c2ecf20Sopenharmony_ci * hang/firmware failure/etc. recovery. 1988c2ecf20Sopenharmony_ci */ 1998c2ecf20Sopenharmony_ci 2008c2ecf20Sopenharmony_civoid ieee80211_report_wowlan_wakeup(struct ieee80211_vif *vif, 2018c2ecf20Sopenharmony_ci struct cfg80211_wowlan_wakeup *wakeup, 2028c2ecf20Sopenharmony_ci gfp_t gfp) 2038c2ecf20Sopenharmony_ci{ 2048c2ecf20Sopenharmony_ci struct ieee80211_sub_if_data *sdata = vif_to_sdata(vif); 2058c2ecf20Sopenharmony_ci 2068c2ecf20Sopenharmony_ci cfg80211_report_wowlan_wakeup(&sdata->wdev, wakeup, gfp); 2078c2ecf20Sopenharmony_ci} 2088c2ecf20Sopenharmony_ciEXPORT_SYMBOL(ieee80211_report_wowlan_wakeup); 209