diff options
author | Johannes Berg <johannes@sipsolutions.net> | 2010-05-05 09:28:27 -0400 |
---|---|---|
committer | John W. Linville <linville@tuxdriver.com> | 2010-05-07 14:55:51 -0400 |
commit | 0aaffa9b9699894aab3266195a529baf9f96ac29 (patch) | |
tree | 26fe5f5277ac6d7061ea723f92d4038b0c28b0b8 /net | |
parent | f444de05d20e27cdd960c13fcbcfca3099f03143 (diff) |
mac80211: improve HT channel handling
Currently, when one interface switches HT mode,
all others will follow along. This is clearly
undesirable, since the new one might switch to
no-HT while another one is operating in HT.
Address this issue by keeping track of the HT
mode per interface, and allowing only changes
that are compatible, i.e. switching into HT40+
is not possible when another interface is in
HT40-, in that case the second one needs to
fall back to HT20.
Also, to allow drivers to know what's going on,
store the per-interface HT mode (channel type)
in the virtual interface's bss_conf.
Signed-off-by: Johannes Berg <johannes@sipsolutions.net>
Signed-off-by: John W. Linville <linville@tuxdriver.com>
Diffstat (limited to 'net')
-rw-r--r-- | net/mac80211/cfg.c | 23 | ||||
-rw-r--r-- | net/mac80211/chan.c | 70 | ||||
-rw-r--r-- | net/mac80211/ibss.c | 5 | ||||
-rw-r--r-- | net/mac80211/ieee80211_i.h | 5 | ||||
-rw-r--r-- | net/mac80211/main.c | 2 | ||||
-rw-r--r-- | net/mac80211/mlme.c | 44 |
6 files changed, 118 insertions, 31 deletions
diff --git a/net/mac80211/cfg.c b/net/mac80211/cfg.c index 414b7dd7d7fd..ab166c6d9399 100644 --- a/net/mac80211/cfg.c +++ b/net/mac80211/cfg.c | |||
@@ -1166,23 +1166,34 @@ static int ieee80211_set_channel(struct wiphy *wiphy, | |||
1166 | enum nl80211_channel_type channel_type) | 1166 | enum nl80211_channel_type channel_type) |
1167 | { | 1167 | { |
1168 | struct ieee80211_local *local = wiphy_priv(wiphy); | 1168 | struct ieee80211_local *local = wiphy_priv(wiphy); |
1169 | struct ieee80211_sub_if_data *sdata = NULL; | ||
1170 | |||
1171 | if (netdev) | ||
1172 | sdata = IEEE80211_DEV_TO_SUB_IF(netdev); | ||
1169 | 1173 | ||
1170 | switch (ieee80211_get_channel_mode(local, NULL)) { | 1174 | switch (ieee80211_get_channel_mode(local, NULL)) { |
1171 | case CHAN_MODE_HOPPING: | 1175 | case CHAN_MODE_HOPPING: |
1172 | return -EBUSY; | 1176 | return -EBUSY; |
1173 | case CHAN_MODE_FIXED: | 1177 | case CHAN_MODE_FIXED: |
1174 | if (local->oper_channel == chan && | 1178 | if (local->oper_channel != chan) |
1175 | local->oper_channel_type == channel_type) | 1179 | return -EBUSY; |
1180 | if (!sdata && local->_oper_channel_type == channel_type) | ||
1176 | return 0; | 1181 | return 0; |
1177 | return -EBUSY; | 1182 | break; |
1178 | case CHAN_MODE_UNDEFINED: | 1183 | case CHAN_MODE_UNDEFINED: |
1179 | break; | 1184 | break; |
1180 | } | 1185 | } |
1181 | 1186 | ||
1182 | local->oper_channel = chan; | 1187 | local->oper_channel = chan; |
1183 | local->oper_channel_type = channel_type; | ||
1184 | 1188 | ||
1185 | return ieee80211_hw_config(local, IEEE80211_CONF_CHANGE_CHANNEL); | 1189 | if (!ieee80211_set_channel_type(local, sdata, channel_type)) |
1190 | return -EBUSY; | ||
1191 | |||
1192 | ieee80211_hw_config(local, IEEE80211_CONF_CHANGE_CHANNEL); | ||
1193 | if (sdata && sdata->vif.type != NL80211_IFTYPE_MONITOR) | ||
1194 | ieee80211_bss_info_change_notify(sdata, BSS_CHANGED_HT); | ||
1195 | |||
1196 | return 0; | ||
1186 | } | 1197 | } |
1187 | 1198 | ||
1188 | #ifdef CONFIG_PM | 1199 | #ifdef CONFIG_PM |
@@ -1406,7 +1417,7 @@ int __ieee80211_request_smps(struct ieee80211_sub_if_data *sdata, | |||
1406 | * association, there's no need to send an action frame. | 1417 | * association, there's no need to send an action frame. |
1407 | */ | 1418 | */ |
1408 | if (!sdata->u.mgd.associated || | 1419 | if (!sdata->u.mgd.associated || |
1409 | sdata->local->oper_channel_type == NL80211_CHAN_NO_HT) { | 1420 | sdata->vif.bss_conf.channel_type == NL80211_CHAN_NO_HT) { |
1410 | mutex_lock(&sdata->local->iflist_mtx); | 1421 | mutex_lock(&sdata->local->iflist_mtx); |
1411 | ieee80211_recalc_smps(sdata->local, sdata); | 1422 | ieee80211_recalc_smps(sdata->local, sdata); |
1412 | mutex_unlock(&sdata->local->iflist_mtx); | 1423 | mutex_unlock(&sdata->local->iflist_mtx); |
diff --git a/net/mac80211/chan.c b/net/mac80211/chan.c index 08f3832661a5..5d218c530a4e 100644 --- a/net/mac80211/chan.c +++ b/net/mac80211/chan.c | |||
@@ -2,6 +2,7 @@ | |||
2 | * mac80211 - channel management | 2 | * mac80211 - channel management |
3 | */ | 3 | */ |
4 | 4 | ||
5 | #include <linux/nl80211.h> | ||
5 | #include "ieee80211_i.h" | 6 | #include "ieee80211_i.h" |
6 | 7 | ||
7 | enum ieee80211_chan_mode | 8 | enum ieee80211_chan_mode |
@@ -55,3 +56,72 @@ ieee80211_get_channel_mode(struct ieee80211_local *local, | |||
55 | 56 | ||
56 | return mode; | 57 | return mode; |
57 | } | 58 | } |
59 | |||
60 | bool ieee80211_set_channel_type(struct ieee80211_local *local, | ||
61 | struct ieee80211_sub_if_data *sdata, | ||
62 | enum nl80211_channel_type chantype) | ||
63 | { | ||
64 | struct ieee80211_sub_if_data *tmp; | ||
65 | enum nl80211_channel_type superchan = NL80211_CHAN_NO_HT; | ||
66 | bool result; | ||
67 | |||
68 | mutex_lock(&local->iflist_mtx); | ||
69 | |||
70 | list_for_each_entry(tmp, &local->interfaces, list) { | ||
71 | if (tmp == sdata) | ||
72 | continue; | ||
73 | |||
74 | if (!ieee80211_sdata_running(tmp)) | ||
75 | continue; | ||
76 | |||
77 | switch (tmp->vif.bss_conf.channel_type) { | ||
78 | case NL80211_CHAN_NO_HT: | ||
79 | case NL80211_CHAN_HT20: | ||
80 | superchan = tmp->vif.bss_conf.channel_type; | ||
81 | break; | ||
82 | case NL80211_CHAN_HT40PLUS: | ||
83 | WARN_ON(superchan == NL80211_CHAN_HT40MINUS); | ||
84 | superchan = NL80211_CHAN_HT40PLUS; | ||
85 | break; | ||
86 | case NL80211_CHAN_HT40MINUS: | ||
87 | WARN_ON(superchan == NL80211_CHAN_HT40PLUS); | ||
88 | superchan = NL80211_CHAN_HT40MINUS; | ||
89 | break; | ||
90 | } | ||
91 | } | ||
92 | |||
93 | switch (superchan) { | ||
94 | case NL80211_CHAN_NO_HT: | ||
95 | case NL80211_CHAN_HT20: | ||
96 | /* | ||
97 | * allow any change that doesn't go to no-HT | ||
98 | * (if it already is no-HT no change is needed) | ||
99 | */ | ||
100 | if (chantype == NL80211_CHAN_NO_HT) | ||
101 | break; | ||
102 | superchan = chantype; | ||
103 | break; | ||
104 | case NL80211_CHAN_HT40PLUS: | ||
105 | case NL80211_CHAN_HT40MINUS: | ||
106 | /* allow smaller bandwidth and same */ | ||
107 | if (chantype == NL80211_CHAN_NO_HT) | ||
108 | break; | ||
109 | if (chantype == NL80211_CHAN_HT20) | ||
110 | break; | ||
111 | if (superchan == chantype) | ||
112 | break; | ||
113 | result = false; | ||
114 | goto out; | ||
115 | } | ||
116 | |||
117 | local->_oper_channel_type = superchan; | ||
118 | |||
119 | if (sdata) | ||
120 | sdata->vif.bss_conf.channel_type = chantype; | ||
121 | |||
122 | result = true; | ||
123 | out: | ||
124 | mutex_unlock(&local->iflist_mtx); | ||
125 | |||
126 | return result; | ||
127 | } | ||
diff --git a/net/mac80211/ibss.c b/net/mac80211/ibss.c index d5855ae387e8..36745f494f63 100644 --- a/net/mac80211/ibss.c +++ b/net/mac80211/ibss.c | |||
@@ -102,7 +102,7 @@ static void __ieee80211_sta_join_ibss(struct ieee80211_sub_if_data *sdata, | |||
102 | sdata->drop_unencrypted = capability & WLAN_CAPABILITY_PRIVACY ? 1 : 0; | 102 | sdata->drop_unencrypted = capability & WLAN_CAPABILITY_PRIVACY ? 1 : 0; |
103 | 103 | ||
104 | local->oper_channel = chan; | 104 | local->oper_channel = chan; |
105 | local->oper_channel_type = NL80211_CHAN_NO_HT; | 105 | WARN_ON(!ieee80211_set_channel_type(local, sdata, NL80211_CHAN_NO_HT)); |
106 | ieee80211_hw_config(local, IEEE80211_CONF_CHANGE_CHANNEL); | 106 | ieee80211_hw_config(local, IEEE80211_CONF_CHANGE_CHANNEL); |
107 | 107 | ||
108 | sband = local->hw.wiphy->bands[chan->band]; | 108 | sband = local->hw.wiphy->bands[chan->band]; |
@@ -910,7 +910,8 @@ int ieee80211_ibss_join(struct ieee80211_sub_if_data *sdata, | |||
910 | /* fix ourselves to that channel now already */ | 910 | /* fix ourselves to that channel now already */ |
911 | if (params->channel_fixed) { | 911 | if (params->channel_fixed) { |
912 | sdata->local->oper_channel = params->channel; | 912 | sdata->local->oper_channel = params->channel; |
913 | sdata->local->oper_channel_type = NL80211_CHAN_NO_HT; | 913 | WARN_ON(!ieee80211_set_channel_type(sdata->local, sdata, |
914 | NL80211_CHAN_NO_HT)); | ||
914 | } | 915 | } |
915 | 916 | ||
916 | if (params->ie) { | 917 | if (params->ie) { |
diff --git a/net/mac80211/ieee80211_i.h b/net/mac80211/ieee80211_i.h index 359edff31471..69e7f4131f46 100644 --- a/net/mac80211/ieee80211_i.h +++ b/net/mac80211/ieee80211_i.h | |||
@@ -768,7 +768,7 @@ struct ieee80211_local { | |||
768 | enum mac80211_scan_state next_scan_state; | 768 | enum mac80211_scan_state next_scan_state; |
769 | struct delayed_work scan_work; | 769 | struct delayed_work scan_work; |
770 | struct ieee80211_sub_if_data *scan_sdata; | 770 | struct ieee80211_sub_if_data *scan_sdata; |
771 | enum nl80211_channel_type oper_channel_type; | 771 | enum nl80211_channel_type _oper_channel_type; |
772 | struct ieee80211_channel *oper_channel, *csa_channel; | 772 | struct ieee80211_channel *oper_channel, *csa_channel; |
773 | 773 | ||
774 | /* Temporary remain-on-channel for off-channel operations */ | 774 | /* Temporary remain-on-channel for off-channel operations */ |
@@ -1239,6 +1239,9 @@ enum ieee80211_chan_mode { | |||
1239 | enum ieee80211_chan_mode | 1239 | enum ieee80211_chan_mode |
1240 | ieee80211_get_channel_mode(struct ieee80211_local *local, | 1240 | ieee80211_get_channel_mode(struct ieee80211_local *local, |
1241 | struct ieee80211_sub_if_data *ignore); | 1241 | struct ieee80211_sub_if_data *ignore); |
1242 | bool ieee80211_set_channel_type(struct ieee80211_local *local, | ||
1243 | struct ieee80211_sub_if_data *sdata, | ||
1244 | enum nl80211_channel_type chantype); | ||
1242 | 1245 | ||
1243 | #ifdef CONFIG_MAC80211_NOINLINE | 1246 | #ifdef CONFIG_MAC80211_NOINLINE |
1244 | #define debug_noinline noinline | 1247 | #define debug_noinline noinline |
diff --git a/net/mac80211/main.c b/net/mac80211/main.c index 353b6b42d9c5..d763d76e809f 100644 --- a/net/mac80211/main.c +++ b/net/mac80211/main.c | |||
@@ -111,7 +111,7 @@ int ieee80211_hw_config(struct ieee80211_local *local, u32 changed) | |||
111 | channel_type = local->tmp_channel_type; | 111 | channel_type = local->tmp_channel_type; |
112 | } else { | 112 | } else { |
113 | chan = local->oper_channel; | 113 | chan = local->oper_channel; |
114 | channel_type = local->oper_channel_type; | 114 | channel_type = local->_oper_channel_type; |
115 | } | 115 | } |
116 | 116 | ||
117 | if (chan != local->hw.conf.channel || | 117 | if (chan != local->hw.conf.channel || |
diff --git a/net/mac80211/mlme.c b/net/mac80211/mlme.c index 17cb8ae912bc..6e149b49d4f0 100644 --- a/net/mac80211/mlme.c +++ b/net/mac80211/mlme.c | |||
@@ -136,11 +136,14 @@ static u32 ieee80211_enable_ht(struct ieee80211_sub_if_data *sdata, | |||
136 | struct sta_info *sta; | 136 | struct sta_info *sta; |
137 | u32 changed = 0; | 137 | u32 changed = 0; |
138 | u16 ht_opmode; | 138 | u16 ht_opmode; |
139 | bool enable_ht = true, ht_changed; | 139 | bool enable_ht = true; |
140 | enum nl80211_channel_type prev_chantype; | ||
140 | enum nl80211_channel_type channel_type = NL80211_CHAN_NO_HT; | 141 | enum nl80211_channel_type channel_type = NL80211_CHAN_NO_HT; |
141 | 142 | ||
142 | sband = local->hw.wiphy->bands[local->hw.conf.channel->band]; | 143 | sband = local->hw.wiphy->bands[local->hw.conf.channel->band]; |
143 | 144 | ||
145 | prev_chantype = sdata->vif.bss_conf.channel_type; | ||
146 | |||
144 | /* HT is not supported */ | 147 | /* HT is not supported */ |
145 | if (!sband->ht_cap.ht_supported) | 148 | if (!sband->ht_cap.ht_supported) |
146 | enable_ht = false; | 149 | enable_ht = false; |
@@ -171,38 +174,37 @@ static u32 ieee80211_enable_ht(struct ieee80211_sub_if_data *sdata, | |||
171 | } | 174 | } |
172 | } | 175 | } |
173 | 176 | ||
174 | ht_changed = conf_is_ht(&local->hw.conf) != enable_ht || | ||
175 | channel_type != local->hw.conf.channel_type; | ||
176 | |||
177 | if (local->tmp_channel) | 177 | if (local->tmp_channel) |
178 | local->tmp_channel_type = channel_type; | 178 | local->tmp_channel_type = channel_type; |
179 | local->oper_channel_type = channel_type; | ||
180 | 179 | ||
181 | if (ht_changed) { | 180 | if (!ieee80211_set_channel_type(local, sdata, channel_type)) { |
182 | /* channel_type change automatically detected */ | 181 | /* can only fail due to HT40+/- mismatch */ |
183 | ieee80211_hw_config(local, 0); | 182 | channel_type = NL80211_CHAN_HT20; |
183 | WARN_ON(!ieee80211_set_channel_type(local, sdata, channel_type)); | ||
184 | } | ||
184 | 185 | ||
186 | /* channel_type change automatically detected */ | ||
187 | ieee80211_hw_config(local, 0); | ||
188 | |||
189 | if (prev_chantype != channel_type) { | ||
185 | rcu_read_lock(); | 190 | rcu_read_lock(); |
186 | sta = sta_info_get(sdata, bssid); | 191 | sta = sta_info_get(sdata, bssid); |
187 | if (sta) | 192 | if (sta) |
188 | rate_control_rate_update(local, sband, sta, | 193 | rate_control_rate_update(local, sband, sta, |
189 | IEEE80211_RC_HT_CHANGED, | 194 | IEEE80211_RC_HT_CHANGED, |
190 | local->oper_channel_type); | 195 | channel_type); |
191 | rcu_read_unlock(); | 196 | rcu_read_unlock(); |
192 | } | 197 | } |
193 | |||
194 | /* disable HT */ | ||
195 | if (!enable_ht) | ||
196 | return 0; | ||
197 | 198 | ||
198 | ht_opmode = le16_to_cpu(hti->operation_mode); | 199 | ht_opmode = le16_to_cpu(hti->operation_mode); |
199 | 200 | ||
200 | /* if bss configuration changed store the new one */ | 201 | /* if bss configuration changed store the new one */ |
201 | if (!sdata->ht_opmode_valid || | 202 | if (sdata->ht_opmode_valid != enable_ht || |
202 | sdata->vif.bss_conf.ht_operation_mode != ht_opmode) { | 203 | sdata->vif.bss_conf.ht_operation_mode != ht_opmode || |
204 | prev_chantype != channel_type) { | ||
203 | changed |= BSS_CHANGED_HT; | 205 | changed |= BSS_CHANGED_HT; |
204 | sdata->vif.bss_conf.ht_operation_mode = ht_opmode; | 206 | sdata->vif.bss_conf.ht_operation_mode = ht_opmode; |
205 | sdata->ht_opmode_valid = true; | 207 | sdata->ht_opmode_valid = enable_ht; |
206 | } | 208 | } |
207 | 209 | ||
208 | return changed; | 210 | return changed; |
@@ -865,7 +867,7 @@ static void ieee80211_set_disassoc(struct ieee80211_sub_if_data *sdata, | |||
865 | ieee80211_set_wmm_default(sdata); | 867 | ieee80211_set_wmm_default(sdata); |
866 | 868 | ||
867 | /* channel(_type) changes are handled by ieee80211_hw_config */ | 869 | /* channel(_type) changes are handled by ieee80211_hw_config */ |
868 | local->oper_channel_type = NL80211_CHAN_NO_HT; | 870 | WARN_ON(!ieee80211_set_channel_type(local, sdata, NL80211_CHAN_NO_HT)); |
869 | 871 | ||
870 | /* on the next assoc, re-program HT parameters */ | 872 | /* on the next assoc, re-program HT parameters */ |
871 | sdata->ht_opmode_valid = false; | 873 | sdata->ht_opmode_valid = false; |
@@ -882,8 +884,8 @@ static void ieee80211_set_disassoc(struct ieee80211_sub_if_data *sdata, | |||
882 | 884 | ||
883 | ieee80211_hw_config(local, config_changed); | 885 | ieee80211_hw_config(local, config_changed); |
884 | 886 | ||
885 | /* And the BSSID changed -- not very interesting here */ | 887 | /* The BSSID (not really interesting) and HT changed */ |
886 | changed |= BSS_CHANGED_BSSID; | 888 | changed |= BSS_CHANGED_BSSID | BSS_CHANGED_HT; |
887 | ieee80211_bss_info_change_notify(sdata, changed); | 889 | ieee80211_bss_info_change_notify(sdata, changed); |
888 | 890 | ||
889 | if (remove_sta) | 891 | if (remove_sta) |
@@ -2265,7 +2267,7 @@ int ieee80211_mgd_action(struct ieee80211_sub_if_data *sdata, | |||
2265 | if ((chan != local->tmp_channel || | 2267 | if ((chan != local->tmp_channel || |
2266 | channel_type != local->tmp_channel_type) && | 2268 | channel_type != local->tmp_channel_type) && |
2267 | (chan != local->oper_channel || | 2269 | (chan != local->oper_channel || |
2268 | channel_type != local->oper_channel_type)) | 2270 | channel_type != local->_oper_channel_type)) |
2269 | return -EBUSY; | 2271 | return -EBUSY; |
2270 | 2272 | ||
2271 | skb = dev_alloc_skb(local->hw.extra_tx_headroom + len); | 2273 | skb = dev_alloc_skb(local->hw.extra_tx_headroom + len); |