Jouni Malinen | b203ffc | 2009-12-23 13:15:40 +0100 | [diff] [blame] | 1 | /* |
| 2 | * Off-channel operation helpers |
| 3 | * |
| 4 | * Copyright 2003, Jouni Malinen <jkmaline@cc.hut.fi> |
| 5 | * Copyright 2004, Instant802 Networks, Inc. |
| 6 | * Copyright 2005, Devicescape Software, Inc. |
| 7 | * Copyright 2006-2007 Jiri Benc <jbenc@suse.cz> |
| 8 | * Copyright 2007, Michael Wu <flamingice@sourmilk.net> |
| 9 | * Copyright 2009 Johannes Berg <johannes@sipsolutions.net> |
| 10 | * |
| 11 | * This program is free software; you can redistribute it and/or modify |
| 12 | * it under the terms of the GNU General Public License version 2 as |
| 13 | * published by the Free Software Foundation. |
| 14 | */ |
| 15 | #include <net/mac80211.h> |
| 16 | #include "ieee80211_i.h" |
| 17 | |
| 18 | /* |
| 19 | * inform AP that we will go to sleep so that it will buffer the frames |
| 20 | * while we scan |
| 21 | */ |
| 22 | static void ieee80211_offchannel_ps_enable(struct ieee80211_sub_if_data *sdata) |
| 23 | { |
| 24 | struct ieee80211_local *local = sdata->local; |
| 25 | |
| 26 | local->offchannel_ps_enabled = false; |
| 27 | |
| 28 | /* FIXME: what to do when local->pspolling is true? */ |
| 29 | |
| 30 | del_timer_sync(&local->dynamic_ps_timer); |
| 31 | cancel_work_sync(&local->dynamic_ps_enable_work); |
| 32 | |
| 33 | if (local->hw.conf.flags & IEEE80211_CONF_PS) { |
| 34 | local->offchannel_ps_enabled = true; |
| 35 | local->hw.conf.flags &= ~IEEE80211_CONF_PS; |
| 36 | ieee80211_hw_config(local, IEEE80211_CONF_CHANGE_PS); |
| 37 | } |
| 38 | |
| 39 | if (!(local->offchannel_ps_enabled) || |
| 40 | !(local->hw.flags & IEEE80211_HW_PS_NULLFUNC_STACK)) |
| 41 | /* |
| 42 | * If power save was enabled, no need to send a nullfunc |
| 43 | * frame because AP knows that we are sleeping. But if the |
| 44 | * hardware is creating the nullfunc frame for power save |
| 45 | * status (ie. IEEE80211_HW_PS_NULLFUNC_STACK is not |
| 46 | * enabled) and power save was enabled, the firmware just |
| 47 | * sent a null frame with power save disabled. So we need |
| 48 | * to send a new nullfunc frame to inform the AP that we |
| 49 | * are again sleeping. |
| 50 | */ |
| 51 | ieee80211_send_nullfunc(local, sdata, 1); |
| 52 | } |
| 53 | |
| 54 | /* inform AP that we are awake again, unless power save is enabled */ |
| 55 | static void ieee80211_offchannel_ps_disable(struct ieee80211_sub_if_data *sdata) |
| 56 | { |
| 57 | struct ieee80211_local *local = sdata->local; |
| 58 | |
| 59 | if (!local->ps_sdata) |
| 60 | ieee80211_send_nullfunc(local, sdata, 0); |
| 61 | else if (local->offchannel_ps_enabled) { |
| 62 | /* |
| 63 | * In !IEEE80211_HW_PS_NULLFUNC_STACK case the hardware |
| 64 | * will send a nullfunc frame with the powersave bit set |
| 65 | * even though the AP already knows that we are sleeping. |
| 66 | * This could be avoided by sending a null frame with power |
| 67 | * save bit disabled before enabling the power save, but |
| 68 | * this doesn't gain anything. |
| 69 | * |
| 70 | * When IEEE80211_HW_PS_NULLFUNC_STACK is enabled, no need |
| 71 | * to send a nullfunc frame because AP already knows that |
| 72 | * we are sleeping, let's just enable power save mode in |
| 73 | * hardware. |
| 74 | */ |
| 75 | local->hw.conf.flags |= IEEE80211_CONF_PS; |
| 76 | ieee80211_hw_config(local, IEEE80211_CONF_CHANGE_PS); |
| 77 | } else if (local->hw.conf.dynamic_ps_timeout > 0) { |
| 78 | /* |
| 79 | * If IEEE80211_CONF_PS was not set and the dynamic_ps_timer |
| 80 | * had been running before leaving the operating channel, |
| 81 | * restart the timer now and send a nullfunc frame to inform |
| 82 | * the AP that we are awake. |
| 83 | */ |
| 84 | ieee80211_send_nullfunc(local, sdata, 0); |
| 85 | mod_timer(&local->dynamic_ps_timer, jiffies + |
| 86 | msecs_to_jiffies(local->hw.conf.dynamic_ps_timeout)); |
| 87 | } |
| 88 | } |
| 89 | |
| 90 | void ieee80211_offchannel_stop_beaconing(struct ieee80211_local *local) |
| 91 | { |
| 92 | struct ieee80211_sub_if_data *sdata; |
| 93 | |
| 94 | mutex_lock(&local->iflist_mtx); |
| 95 | list_for_each_entry(sdata, &local->interfaces, list) { |
| 96 | if (!ieee80211_sdata_running(sdata)) |
| 97 | continue; |
| 98 | |
| 99 | /* disable beaconing */ |
| 100 | if (sdata->vif.type == NL80211_IFTYPE_AP || |
| 101 | sdata->vif.type == NL80211_IFTYPE_ADHOC || |
| 102 | sdata->vif.type == NL80211_IFTYPE_MESH_POINT) |
| 103 | ieee80211_bss_info_change_notify( |
| 104 | sdata, BSS_CHANGED_BEACON_ENABLED); |
| 105 | |
| 106 | /* |
| 107 | * only handle non-STA interfaces here, STA interfaces |
| 108 | * are handled in ieee80211_offchannel_stop_station(), |
Johannes Berg | b8bc4b0 | 2009-12-23 13:15:42 +0100 | [diff] [blame^] | 109 | * e.g., from the background scan state machine. |
| 110 | * |
| 111 | * In addition, do not stop monitor interface to allow it to be |
| 112 | * used from user space controlled off-channel operations. |
Jouni Malinen | b203ffc | 2009-12-23 13:15:40 +0100 | [diff] [blame] | 113 | */ |
Johannes Berg | b8bc4b0 | 2009-12-23 13:15:42 +0100 | [diff] [blame^] | 114 | if (sdata->vif.type != NL80211_IFTYPE_STATION && |
| 115 | sdata->vif.type != NL80211_IFTYPE_MONITOR) |
Jouni Malinen | b203ffc | 2009-12-23 13:15:40 +0100 | [diff] [blame] | 116 | netif_stop_queue(sdata->dev); |
| 117 | } |
| 118 | mutex_unlock(&local->iflist_mtx); |
| 119 | } |
| 120 | |
| 121 | void ieee80211_offchannel_stop_station(struct ieee80211_local *local) |
| 122 | { |
| 123 | struct ieee80211_sub_if_data *sdata; |
| 124 | |
| 125 | /* |
| 126 | * notify the AP about us leaving the channel and stop all STA interfaces |
| 127 | */ |
| 128 | mutex_lock(&local->iflist_mtx); |
| 129 | list_for_each_entry(sdata, &local->interfaces, list) { |
| 130 | if (!ieee80211_sdata_running(sdata)) |
| 131 | continue; |
| 132 | |
| 133 | if (sdata->vif.type == NL80211_IFTYPE_STATION) { |
| 134 | netif_stop_queue(sdata->dev); |
| 135 | if (sdata->u.mgd.associated) |
| 136 | ieee80211_offchannel_ps_enable(sdata); |
| 137 | } |
| 138 | } |
| 139 | mutex_unlock(&local->iflist_mtx); |
| 140 | } |
| 141 | |
| 142 | void ieee80211_offchannel_return(struct ieee80211_local *local, |
| 143 | bool enable_beaconing) |
| 144 | { |
| 145 | struct ieee80211_sub_if_data *sdata; |
| 146 | |
| 147 | mutex_lock(&local->iflist_mtx); |
| 148 | list_for_each_entry(sdata, &local->interfaces, list) { |
| 149 | if (!ieee80211_sdata_running(sdata)) |
| 150 | continue; |
| 151 | |
| 152 | /* Tell AP we're back */ |
| 153 | if (sdata->vif.type == NL80211_IFTYPE_STATION) { |
| 154 | if (sdata->u.mgd.associated) |
| 155 | ieee80211_offchannel_ps_disable(sdata); |
| 156 | netif_wake_queue(sdata->dev); |
| 157 | } |
| 158 | |
| 159 | /* re-enable beaconing */ |
| 160 | if (enable_beaconing && |
| 161 | (sdata->vif.type == NL80211_IFTYPE_AP || |
| 162 | sdata->vif.type == NL80211_IFTYPE_ADHOC || |
| 163 | sdata->vif.type == NL80211_IFTYPE_MESH_POINT)) |
| 164 | ieee80211_bss_info_change_notify( |
| 165 | sdata, BSS_CHANGED_BEACON_ENABLED); |
| 166 | } |
| 167 | mutex_unlock(&local->iflist_mtx); |
| 168 | } |