diff options
Diffstat (limited to 'net/mac80211/pm.c')
| -rw-r--r-- | net/mac80211/pm.c | 119 | 
1 files changed, 79 insertions, 40 deletions
diff --git a/net/mac80211/pm.c b/net/mac80211/pm.c index e37355193ed..d478b880a0a 100644 --- a/net/mac80211/pm.c +++ b/net/mac80211/pm.c @@ -6,20 +6,42 @@  #include "driver-ops.h"  #include "led.h" -int __ieee80211_suspend(struct ieee80211_hw *hw) +int __ieee80211_suspend(struct ieee80211_hw *hw, struct cfg80211_wowlan *wowlan)  {  	struct ieee80211_local *local = hw_to_local(hw);  	struct ieee80211_sub_if_data *sdata;  	struct sta_info *sta; +	if (!local->open_count) +		goto suspend; +  	ieee80211_scan_cancel(local); +	ieee80211_dfs_cac_cancel(local); + +	ieee80211_roc_purge(local, NULL); + +	ieee80211_del_virtual_monitor(local); + +	if (hw->flags & IEEE80211_HW_AMPDU_AGGREGATION) { +		mutex_lock(&local->sta_mtx); +		list_for_each_entry(sta, &local->sta_list, list) { +			set_sta_flag(sta, WLAN_STA_BLOCK_BA); +			ieee80211_sta_tear_down_BA_sessions( +					sta, AGG_STOP_LOCAL_REQUEST); +		} +		mutex_unlock(&local->sta_mtx); +	} +  	ieee80211_stop_queues_by_reason(hw, -			IEEE80211_QUEUE_STOP_REASON_SUSPEND); +					IEEE80211_MAX_QUEUE_MAP, +					IEEE80211_QUEUE_STOP_REASON_SUSPEND);  	/* flush out all packets */  	synchronize_net(); +	ieee80211_flush_queues(local, NULL); +  	local->quiescing = true;  	/* make quiescing visible to timers everywhere */  	mb(); @@ -36,68 +58,75 @@ int __ieee80211_suspend(struct ieee80211_hw *hw)  	cancel_work_sync(&local->dynamic_ps_enable_work);  	del_timer_sync(&local->dynamic_ps_timer); -	/* disable keys */ -	list_for_each_entry(sdata, &local->interfaces, list) -		ieee80211_disable_keys(sdata); +	local->wowlan = wowlan && local->open_count; +	if (local->wowlan) { +		int err = drv_suspend(local, wowlan); +		if (err < 0) { +			local->quiescing = false; +			local->wowlan = false; +			if (hw->flags & IEEE80211_HW_AMPDU_AGGREGATION) { +				mutex_lock(&local->sta_mtx); +				list_for_each_entry(sta, +						    &local->sta_list, list) { +					clear_sta_flag(sta, WLAN_STA_BLOCK_BA); +				} +				mutex_unlock(&local->sta_mtx); +			} +			ieee80211_wake_queues_by_reason(hw, +					IEEE80211_MAX_QUEUE_MAP, +					IEEE80211_QUEUE_STOP_REASON_SUSPEND); +			return err; +		} else if (err > 0) { +			WARN_ON(err != 1); +			return err; +		} else { +			goto suspend; +		} +	}  	/* tear down aggregation sessions and remove STAs */  	mutex_lock(&local->sta_mtx);  	list_for_each_entry(sta, &local->sta_list, list) { -		if (hw->flags & IEEE80211_HW_AMPDU_AGGREGATION) { -			set_sta_flags(sta, WLAN_STA_BLOCK_BA); -			ieee80211_sta_tear_down_BA_sessions(sta, true); -		} -  		if (sta->uploaded) { -			sdata = sta->sdata; -			if (sdata->vif.type == NL80211_IFTYPE_AP_VLAN) -				sdata = container_of(sdata->bss, -					     struct ieee80211_sub_if_data, -					     u.ap); +			enum ieee80211_sta_state state; -			drv_sta_remove(local, sdata, &sta->sta); +			state = sta->sta_state; +			for (; state > IEEE80211_STA_NOTEXIST; state--) +				WARN_ON(drv_sta_state(local, sta->sdata, sta, +						      state, state - 1));  		} - -		mesh_plink_quiesce(sta);  	}  	mutex_unlock(&local->sta_mtx); -	/* remove all interfaces */ +	/* remove all interfaces that were created in the driver */  	list_for_each_entry(sdata, &local->interfaces, list) { -		cancel_work_sync(&sdata->work); - -		switch(sdata->vif.type) { -		case NL80211_IFTYPE_STATION: -			ieee80211_sta_quiesce(sdata); -			break; -		case NL80211_IFTYPE_ADHOC: -			ieee80211_ibss_quiesce(sdata); -			break; -		case NL80211_IFTYPE_MESH_POINT: -			ieee80211_mesh_quiesce(sdata); -			break; +		if (!ieee80211_sdata_running(sdata)) +			continue; +		switch (sdata->vif.type) {  		case NL80211_IFTYPE_AP_VLAN:  		case NL80211_IFTYPE_MONITOR: -			/* don't tell driver about this */  			continue; +		case NL80211_IFTYPE_STATION: +			ieee80211_mgd_quiesce(sdata); +			break;  		default:  			break;  		} -		if (!ieee80211_sdata_running(sdata)) -			continue; - -		/* disable beaconing */ -		ieee80211_bss_info_change_notify(sdata, -			BSS_CHANGED_BEACON_ENABLED); - -		drv_remove_interface(local, &sdata->vif); +		drv_remove_interface(local, sdata);  	} +	/* +	 * We disconnected on all interfaces before suspend, all channel +	 * contexts should be released. +	 */ +	WARN_ON(!list_empty(&local->chanctx_list)); +  	/* stop hardware - this must stop RX */  	if (local->open_count)  		ieee80211_stop_device(local); + suspend:  	local->suspended = true;  	/* need suspended to be visible before quiescing is false */  	barrier(); @@ -111,3 +140,13 @@ int __ieee80211_suspend(struct ieee80211_hw *hw)   * ieee80211_reconfig(), which is also needed for hardware   * hang/firmware failure/etc. recovery.   */ + +void ieee80211_report_wowlan_wakeup(struct ieee80211_vif *vif, +				    struct cfg80211_wowlan_wakeup *wakeup, +				    gfp_t gfp) +{ +	struct ieee80211_sub_if_data *sdata = vif_to_sdata(vif); + +	cfg80211_report_wowlan_wakeup(&sdata->wdev, wakeup, gfp); +} +EXPORT_SYMBOL(ieee80211_report_wowlan_wakeup);  | 
