xref: /openbmc/linux/net/mac80211/pm.c (revision 9d749629)
1 #include <net/mac80211.h>
2 #include <net/rtnetlink.h>
3 
4 #include "ieee80211_i.h"
5 #include "mesh.h"
6 #include "driver-ops.h"
7 #include "led.h"
8 
9 /* return value indicates whether the driver should be further notified */
10 static void ieee80211_quiesce(struct ieee80211_sub_if_data *sdata)
11 {
12 	switch (sdata->vif.type) {
13 	case NL80211_IFTYPE_STATION:
14 		ieee80211_sta_quiesce(sdata);
15 		break;
16 	case NL80211_IFTYPE_ADHOC:
17 		ieee80211_ibss_quiesce(sdata);
18 		break;
19 	case NL80211_IFTYPE_MESH_POINT:
20 		ieee80211_mesh_quiesce(sdata);
21 		break;
22 	default:
23 		break;
24 	}
25 
26 	cancel_work_sync(&sdata->work);
27 }
28 
29 int __ieee80211_suspend(struct ieee80211_hw *hw, struct cfg80211_wowlan *wowlan)
30 {
31 	struct ieee80211_local *local = hw_to_local(hw);
32 	struct ieee80211_sub_if_data *sdata;
33 	struct sta_info *sta;
34 	struct ieee80211_chanctx *ctx;
35 
36 	if (!local->open_count)
37 		goto suspend;
38 
39 	ieee80211_scan_cancel(local);
40 
41 	ieee80211_dfs_cac_cancel(local);
42 
43 	if (hw->flags & IEEE80211_HW_AMPDU_AGGREGATION) {
44 		mutex_lock(&local->sta_mtx);
45 		list_for_each_entry(sta, &local->sta_list, list) {
46 			set_sta_flag(sta, WLAN_STA_BLOCK_BA);
47 			ieee80211_sta_tear_down_BA_sessions(
48 					sta, AGG_STOP_LOCAL_REQUEST);
49 		}
50 		mutex_unlock(&local->sta_mtx);
51 	}
52 
53 	ieee80211_stop_queues_by_reason(hw,
54 			IEEE80211_QUEUE_STOP_REASON_SUSPEND);
55 
56 	/* flush out all packets */
57 	synchronize_net();
58 
59 	drv_flush(local, false);
60 
61 	local->quiescing = true;
62 	/* make quiescing visible to timers everywhere */
63 	mb();
64 
65 	flush_workqueue(local->workqueue);
66 
67 	/* Don't try to run timers while suspended. */
68 	del_timer_sync(&local->sta_cleanup);
69 
70 	 /*
71 	 * Note that this particular timer doesn't need to be
72 	 * restarted at resume.
73 	 */
74 	cancel_work_sync(&local->dynamic_ps_enable_work);
75 	del_timer_sync(&local->dynamic_ps_timer);
76 
77 	local->wowlan = wowlan && local->open_count;
78 	if (local->wowlan) {
79 		int err = drv_suspend(local, wowlan);
80 		if (err < 0) {
81 			local->quiescing = false;
82 			local->wowlan = false;
83 			if (hw->flags & IEEE80211_HW_AMPDU_AGGREGATION) {
84 				mutex_lock(&local->sta_mtx);
85 				list_for_each_entry(sta,
86 						    &local->sta_list, list) {
87 					clear_sta_flag(sta, WLAN_STA_BLOCK_BA);
88 				}
89 				mutex_unlock(&local->sta_mtx);
90 			}
91 			ieee80211_wake_queues_by_reason(hw,
92 					IEEE80211_QUEUE_STOP_REASON_SUSPEND);
93 			return err;
94 		} else if (err > 0) {
95 			WARN_ON(err != 1);
96 			local->wowlan = false;
97 		} else {
98 			list_for_each_entry(sdata, &local->interfaces, list)
99 				if (ieee80211_sdata_running(sdata))
100 					ieee80211_quiesce(sdata);
101 			goto suspend;
102 		}
103 	}
104 
105 	/* disable keys */
106 	list_for_each_entry(sdata, &local->interfaces, list)
107 		ieee80211_disable_keys(sdata);
108 
109 	/* tear down aggregation sessions and remove STAs */
110 	mutex_lock(&local->sta_mtx);
111 	list_for_each_entry(sta, &local->sta_list, list) {
112 		if (sta->uploaded) {
113 			enum ieee80211_sta_state state;
114 
115 			state = sta->sta_state;
116 			for (; state > IEEE80211_STA_NOTEXIST; state--)
117 				WARN_ON(drv_sta_state(local, sta->sdata, sta,
118 						      state, state - 1));
119 		}
120 
121 		mesh_plink_quiesce(sta);
122 	}
123 	mutex_unlock(&local->sta_mtx);
124 
125 	/* remove all interfaces */
126 	list_for_each_entry(sdata, &local->interfaces, list) {
127 		static u8 zero_addr[ETH_ALEN] = {};
128 		u32 changed = 0;
129 
130 		if (!ieee80211_sdata_running(sdata))
131 			continue;
132 
133 		switch (sdata->vif.type) {
134 		case NL80211_IFTYPE_AP_VLAN:
135 		case NL80211_IFTYPE_MONITOR:
136 			/* skip these */
137 			continue;
138 		case NL80211_IFTYPE_STATION:
139 			if (sdata->vif.bss_conf.assoc)
140 				changed = BSS_CHANGED_ASSOC |
141 					  BSS_CHANGED_BSSID |
142 					  BSS_CHANGED_IDLE;
143 			break;
144 		case NL80211_IFTYPE_AP:
145 		case NL80211_IFTYPE_ADHOC:
146 		case NL80211_IFTYPE_MESH_POINT:
147 			if (sdata->vif.bss_conf.enable_beacon)
148 				changed = BSS_CHANGED_BEACON_ENABLED;
149 			break;
150 		default:
151 			break;
152 		}
153 
154 		ieee80211_quiesce(sdata);
155 
156 		sdata->suspend_bss_conf = sdata->vif.bss_conf;
157 		memset(&sdata->vif.bss_conf, 0, sizeof(sdata->vif.bss_conf));
158 		sdata->vif.bss_conf.idle = true;
159 		if (sdata->suspend_bss_conf.bssid)
160 			sdata->vif.bss_conf.bssid = zero_addr;
161 
162 		/* disable beaconing or remove association */
163 		ieee80211_bss_info_change_notify(sdata, changed);
164 
165 		if (sdata->vif.type == NL80211_IFTYPE_AP &&
166 		    rcu_access_pointer(sdata->u.ap.beacon))
167 			drv_stop_ap(local, sdata);
168 
169 		if (local->use_chanctx) {
170 			struct ieee80211_chanctx_conf *conf;
171 
172 			mutex_lock(&local->chanctx_mtx);
173 			conf = rcu_dereference_protected(
174 					sdata->vif.chanctx_conf,
175 					lockdep_is_held(&local->chanctx_mtx));
176 			if (conf) {
177 				ctx = container_of(conf,
178 						   struct ieee80211_chanctx,
179 						   conf);
180 				drv_unassign_vif_chanctx(local, sdata, ctx);
181 			}
182 
183 			mutex_unlock(&local->chanctx_mtx);
184 		}
185 		drv_remove_interface(local, sdata);
186 	}
187 
188 	sdata = rtnl_dereference(local->monitor_sdata);
189 	if (sdata) {
190 		if (local->use_chanctx) {
191 			struct ieee80211_chanctx_conf *conf;
192 
193 			mutex_lock(&local->chanctx_mtx);
194 			conf = rcu_dereference_protected(
195 					sdata->vif.chanctx_conf,
196 					lockdep_is_held(&local->chanctx_mtx));
197 			if (conf) {
198 				ctx = container_of(conf,
199 						   struct ieee80211_chanctx,
200 						   conf);
201 				drv_unassign_vif_chanctx(local, sdata, ctx);
202 			}
203 
204 			mutex_unlock(&local->chanctx_mtx);
205 		}
206 
207 		drv_remove_interface(local, sdata);
208 	}
209 
210 	mutex_lock(&local->chanctx_mtx);
211 	list_for_each_entry(ctx, &local->chanctx_list, list)
212 		drv_remove_chanctx(local, ctx);
213 	mutex_unlock(&local->chanctx_mtx);
214 
215 	/* stop hardware - this must stop RX */
216 	if (local->open_count)
217 		ieee80211_stop_device(local);
218 
219  suspend:
220 	local->suspended = true;
221 	/* need suspended to be visible before quiescing is false */
222 	barrier();
223 	local->quiescing = false;
224 
225 	return 0;
226 }
227 
228 /*
229  * __ieee80211_resume() is a static inline which just calls
230  * ieee80211_reconfig(), which is also needed for hardware
231  * hang/firmware failure/etc. recovery.
232  */
233 
234 void ieee80211_report_wowlan_wakeup(struct ieee80211_vif *vif,
235 				    struct cfg80211_wowlan_wakeup *wakeup,
236 				    gfp_t gfp)
237 {
238 	struct ieee80211_sub_if_data *sdata = vif_to_sdata(vif);
239 
240 	cfg80211_report_wowlan_wakeup(&sdata->wdev, wakeup, gfp);
241 }
242 EXPORT_SYMBOL(ieee80211_report_wowlan_wakeup);
243