aboutsummaryrefslogtreecommitdiffstats
path: root/net/mac80211/mlme.c
diff options
context:
space:
mode:
authorJohannes Berg <johannes.berg@intel.com>2012-11-22 08:11:39 -0500
committerJohannes Berg <johannes.berg@intel.com>2012-11-27 05:56:07 -0500
commitf2d9d270c15ae0139b54a7e7466d738327e97e03 (patch)
treef2a758596a1393ad204576e93c8c6c9022156875 /net/mac80211/mlme.c
parent9f5e8f6efc7c2601136b27d9c7325c245f8fd19a (diff)
mac80211: support VHT association
Determine the VHT channel from the AP's VHT operation IE (if present) and configure the hardware to that channel if it is supported. If channel contexts cause a channel to not be usable, try a smaller bandwidth. Signed-off-by: Johannes Berg <johannes.berg@intel.com>
Diffstat (limited to 'net/mac80211/mlme.c')
-rw-r--r--net/mac80211/mlme.c368
1 files changed, 296 insertions, 72 deletions
diff --git a/net/mac80211/mlme.c b/net/mac80211/mlme.c
index d2a4f78b4b0f..35d8cffc973a 100644
--- a/net/mac80211/mlme.c
+++ b/net/mac80211/mlme.c
@@ -354,6 +354,16 @@ static void ieee80211_add_vht_ie(struct ieee80211_sub_if_data *sdata,
354 /* determine capability flags */ 354 /* determine capability flags */
355 cap = vht_cap.cap; 355 cap = vht_cap.cap;
356 356
357 if (sdata->u.mgd.flags & IEEE80211_STA_DISABLE_80P80MHZ) {
358 cap &= ~IEEE80211_VHT_CAP_SUPP_CHAN_WIDTH_160_80PLUS80MHZ;
359 cap |= IEEE80211_VHT_CAP_SUPP_CHAN_WIDTH_160MHZ;
360 }
361
362 if (sdata->u.mgd.flags & IEEE80211_STA_DISABLE_160MHZ) {
363 cap &= ~IEEE80211_VHT_CAP_SHORT_GI_160;
364 cap &= ~IEEE80211_VHT_CAP_SUPP_CHAN_WIDTH_160MHZ;
365 }
366
357 /* reserve and fill IE */ 367 /* reserve and fill IE */
358 pos = skb_put(skb, sizeof(struct ieee80211_vht_cap) + 2); 368 pos = skb_put(skb, sizeof(struct ieee80211_vht_cap) + 2);
359 ieee80211_ie_build_vht_cap(pos, &vht_cap, cap); 369 ieee80211_ie_build_vht_cap(pos, &vht_cap, cap);
@@ -543,6 +553,10 @@ static void ieee80211_send_assoc(struct ieee80211_sub_if_data *sdata)
543 offset = noffset; 553 offset = noffset;
544 } 554 }
545 555
556 if (WARN_ON_ONCE((ifmgd->flags & IEEE80211_STA_DISABLE_HT) &&
557 !(ifmgd->flags & IEEE80211_STA_DISABLE_VHT)))
558 ifmgd->flags |= IEEE80211_STA_DISABLE_VHT;
559
546 if (!(ifmgd->flags & IEEE80211_STA_DISABLE_HT)) 560 if (!(ifmgd->flags & IEEE80211_STA_DISABLE_HT))
547 ieee80211_add_ht_ie(sdata, skb, assoc_data->ap_ht_param, 561 ieee80211_add_ht_ie(sdata, skb, assoc_data->ap_ht_param,
548 sband, chan, sdata->smps_mode); 562 sband, chan, sdata->smps_mode);
@@ -3183,23 +3197,270 @@ int ieee80211_max_network_latency(struct notifier_block *nb,
3183 return 0; 3197 return 0;
3184} 3198}
3185 3199
3200static u32 chandef_downgrade(struct cfg80211_chan_def *c)
3201{
3202 u32 ret;
3203 int tmp;
3204
3205 switch (c->width) {
3206 case NL80211_CHAN_WIDTH_20:
3207 c->width = NL80211_CHAN_WIDTH_20_NOHT;
3208 ret = IEEE80211_STA_DISABLE_HT | IEEE80211_STA_DISABLE_VHT;
3209 break;
3210 case NL80211_CHAN_WIDTH_40:
3211 c->width = NL80211_CHAN_WIDTH_20;
3212 c->center_freq1 = c->chan->center_freq;
3213 ret = IEEE80211_STA_DISABLE_40MHZ |
3214 IEEE80211_STA_DISABLE_VHT;
3215 break;
3216 case NL80211_CHAN_WIDTH_80:
3217 tmp = (30 + c->chan->center_freq - c->center_freq1)/20;
3218 /* n_P40 */
3219 tmp /= 2;
3220 /* freq_P40 */
3221 c->center_freq1 = c->center_freq1 - 20 + 40 * tmp;
3222 c->width = NL80211_CHAN_WIDTH_40;
3223 ret = IEEE80211_STA_DISABLE_VHT;
3224 break;
3225 case NL80211_CHAN_WIDTH_80P80:
3226 c->center_freq2 = 0;
3227 c->width = NL80211_CHAN_WIDTH_80;
3228 ret = IEEE80211_STA_DISABLE_80P80MHZ |
3229 IEEE80211_STA_DISABLE_160MHZ;
3230 break;
3231 case NL80211_CHAN_WIDTH_160:
3232 /* n_P20 */
3233 tmp = (70 + c->chan->center_freq - c->center_freq1)/20;
3234 /* n_P80 */
3235 tmp /= 4;
3236 c->center_freq1 = c->center_freq1 - 40 + 80 * tmp;
3237 c->width = NL80211_CHAN_WIDTH_80;
3238 ret = IEEE80211_STA_DISABLE_80P80MHZ |
3239 IEEE80211_STA_DISABLE_160MHZ;
3240 break;
3241 default:
3242 case NL80211_CHAN_WIDTH_20_NOHT:
3243 WARN_ON_ONCE(1);
3244 c->width = NL80211_CHAN_WIDTH_20_NOHT;
3245 ret = IEEE80211_STA_DISABLE_HT | IEEE80211_STA_DISABLE_VHT;
3246 break;
3247 }
3248
3249 WARN_ON_ONCE(!cfg80211_chandef_valid(c));
3250
3251 return ret;
3252}
3253
3254static u32
3255ieee80211_determine_chantype(struct ieee80211_sub_if_data *sdata,
3256 struct ieee80211_supported_band *sband,
3257 struct ieee80211_channel *channel,
3258 const struct ieee80211_ht_operation *ht_oper,
3259 const struct ieee80211_vht_operation *vht_oper,
3260 struct cfg80211_chan_def *chandef)
3261{
3262 struct cfg80211_chan_def vht_chandef;
3263 u32 ht_cfreq, ret;
3264
3265 chandef->chan = channel;
3266 chandef->width = NL80211_CHAN_WIDTH_20_NOHT;
3267 chandef->center_freq1 = channel->center_freq;
3268 chandef->center_freq2 = 0;
3269
3270 if (!ht_oper || !sband->ht_cap.ht_supported) {
3271 ret = IEEE80211_STA_DISABLE_HT | IEEE80211_STA_DISABLE_VHT;
3272 goto out;
3273 }
3274
3275 chandef->width = NL80211_CHAN_WIDTH_20;
3276
3277 ht_cfreq = ieee80211_channel_to_frequency(ht_oper->primary_chan,
3278 channel->band);
3279 /* check that channel matches the right operating channel */
3280 if (channel->center_freq != ht_cfreq) {
3281 /*
3282 * It's possible that some APs are confused here;
3283 * Netgear WNDR3700 sometimes reports 4 higher than
3284 * the actual channel in association responses, but
3285 * since we look at probe response/beacon data here
3286 * it should be OK.
3287 */
3288 sdata_info(sdata,
3289 "Wrong control channel: center-freq: %d ht-cfreq: %d ht->primary_chan: %d band: %d - Disabling HT\n",
3290 channel->center_freq, ht_cfreq,
3291 ht_oper->primary_chan, channel->band);
3292 ret = IEEE80211_STA_DISABLE_HT | IEEE80211_STA_DISABLE_VHT;
3293 goto out;
3294 }
3295
3296 /* check 40 MHz support, if we have it */
3297 if (sband->ht_cap.cap & IEEE80211_HT_CAP_SUP_WIDTH_20_40) {
3298 switch (ht_oper->ht_param & IEEE80211_HT_PARAM_CHA_SEC_OFFSET) {
3299 case IEEE80211_HT_PARAM_CHA_SEC_ABOVE:
3300 chandef->width = NL80211_CHAN_WIDTH_40;
3301 chandef->center_freq1 += 10;
3302 break;
3303 case IEEE80211_HT_PARAM_CHA_SEC_BELOW:
3304 chandef->width = NL80211_CHAN_WIDTH_40;
3305 chandef->center_freq1 -= 10;
3306 break;
3307 }
3308 } else {
3309 /* 40 MHz (and 80 MHz) must be supported for VHT */
3310 ret = IEEE80211_STA_DISABLE_VHT;
3311 goto out;
3312 }
3313
3314 if (!vht_oper || !sband->vht_cap.vht_supported) {
3315 ret = IEEE80211_STA_DISABLE_VHT;
3316 goto out;
3317 }
3318
3319 vht_chandef.chan = channel;
3320 vht_chandef.center_freq1 =
3321 ieee80211_channel_to_frequency(vht_oper->center_freq_seg1_idx,
3322 channel->band);
3323 vht_chandef.center_freq2 = 0;
3324
3325 if (vht_oper->center_freq_seg2_idx)
3326 vht_chandef.center_freq2 =
3327 ieee80211_channel_to_frequency(
3328 vht_oper->center_freq_seg2_idx,
3329 channel->band);
3330
3331 switch (vht_oper->chan_width) {
3332 case IEEE80211_VHT_CHANWIDTH_USE_HT:
3333 vht_chandef.width = chandef->width;
3334 break;
3335 case IEEE80211_VHT_CHANWIDTH_80MHZ:
3336 vht_chandef.width = NL80211_CHAN_WIDTH_80;
3337 break;
3338 case IEEE80211_VHT_CHANWIDTH_160MHZ:
3339 vht_chandef.width = NL80211_CHAN_WIDTH_160;
3340 break;
3341 case IEEE80211_VHT_CHANWIDTH_80P80MHZ:
3342 vht_chandef.width = NL80211_CHAN_WIDTH_80P80;
3343 break;
3344 default:
3345 sdata_info(sdata,
3346 "AP VHT operation IE has invalid channel width (%d), disable VHT\n",
3347 vht_oper->chan_width);
3348 ret = IEEE80211_STA_DISABLE_VHT;
3349 goto out;
3350 }
3351
3352 if (!cfg80211_chandef_valid(&vht_chandef)) {
3353 sdata_info(sdata,
3354 "AP VHT information is invalid, disable VHT\n");
3355 ret = IEEE80211_STA_DISABLE_VHT;
3356 goto out;
3357 }
3358
3359 if (cfg80211_chandef_identical(chandef, &vht_chandef)) {
3360 ret = 0;
3361 goto out;
3362 }
3363
3364 if (!cfg80211_chandef_compatible(chandef, &vht_chandef)) {
3365 sdata_info(sdata,
3366 "AP VHT information doesn't match HT, disable VHT\n");
3367 ret = IEEE80211_STA_DISABLE_VHT;
3368 goto out;
3369 }
3370
3371 *chandef = vht_chandef;
3372
3373 ret = 0;
3374
3375 while (!cfg80211_chandef_usable(sdata->local->hw.wiphy, chandef,
3376 IEEE80211_CHAN_DISABLED)) {
3377 if (WARN_ON(chandef->width == NL80211_CHAN_WIDTH_20_NOHT)) {
3378 ret = IEEE80211_STA_DISABLE_HT |
3379 IEEE80211_STA_DISABLE_VHT;
3380 goto out;
3381 }
3382
3383 ret = chandef_downgrade(chandef);
3384 }
3385
3386 if (chandef->width != vht_chandef.width)
3387 sdata_info(sdata,
3388 "local regulatory prevented using AP HT/VHT configuration, downgraded\n");
3389
3390out:
3391 WARN_ON_ONCE(!cfg80211_chandef_valid(chandef));
3392 return ret;
3393}
3394
3395static u8 ieee80211_ht_vht_rx_chains(struct ieee80211_sub_if_data *sdata,
3396 struct cfg80211_bss *cbss)
3397{
3398 struct ieee80211_if_managed *ifmgd = &sdata->u.mgd;
3399 const u8 *ht_cap_ie, *vht_cap_ie;
3400 const struct ieee80211_ht_cap *ht_cap;
3401 const struct ieee80211_vht_cap *vht_cap;
3402 u8 chains = 1;
3403
3404 if (ifmgd->flags & IEEE80211_STA_DISABLE_HT)
3405 return chains;
3406
3407 ht_cap_ie = cfg80211_find_ie(WLAN_EID_HT_CAPABILITY,
3408 cbss->information_elements,
3409 cbss->len_information_elements);
3410 if (ht_cap_ie && ht_cap_ie[1] >= sizeof(*ht_cap)) {
3411 ht_cap = (void *)(ht_cap_ie + 2);
3412 chains = ieee80211_mcs_to_chains(&ht_cap->mcs);
3413 /*
3414 * TODO: use "Tx Maximum Number Spatial Streams Supported" and
3415 * "Tx Unequal Modulation Supported" fields.
3416 */
3417 }
3418
3419 if (ifmgd->flags & IEEE80211_STA_DISABLE_VHT)
3420 return chains;
3421
3422 vht_cap_ie = cfg80211_find_ie(WLAN_EID_VHT_CAPABILITY,
3423 cbss->information_elements,
3424 cbss->len_information_elements);
3425 if (vht_cap_ie && vht_cap_ie[1] >= sizeof(*vht_cap)) {
3426 u8 nss;
3427 u16 tx_mcs_map;
3428
3429 vht_cap = (void *)(vht_cap_ie + 2);
3430 tx_mcs_map = le16_to_cpu(vht_cap->supp_mcs.tx_mcs_map);
3431 for (nss = 8; nss > 0; nss--) {
3432 if (((tx_mcs_map >> (2 * (nss - 1))) & 3) !=
3433 IEEE80211_VHT_MCS_NOT_SUPPORTED)
3434 break;
3435 }
3436 /* TODO: use "Tx Highest Supported Long GI Data Rate" field? */
3437 chains = max(chains, nss);
3438 }
3439
3440 return chains;
3441}
3442
3186static int ieee80211_prep_channel(struct ieee80211_sub_if_data *sdata, 3443static int ieee80211_prep_channel(struct ieee80211_sub_if_data *sdata,
3187 struct cfg80211_bss *cbss) 3444 struct cfg80211_bss *cbss)
3188{ 3445{
3189 struct ieee80211_local *local = sdata->local; 3446 struct ieee80211_local *local = sdata->local;
3190 struct ieee80211_if_managed *ifmgd = &sdata->u.mgd; 3447 struct ieee80211_if_managed *ifmgd = &sdata->u.mgd;
3191 int ht_cfreq;
3192 enum nl80211_channel_type channel_type = NL80211_CHAN_NO_HT;
3193 const u8 *ht_oper_ie;
3194 const struct ieee80211_ht_operation *ht_oper = NULL; 3448 const struct ieee80211_ht_operation *ht_oper = NULL;
3449 const struct ieee80211_vht_operation *vht_oper = NULL;
3195 struct ieee80211_supported_band *sband; 3450 struct ieee80211_supported_band *sband;
3196 struct cfg80211_chan_def chandef; 3451 struct cfg80211_chan_def chandef;
3452 int ret;
3197 3453
3198 sband = local->hw.wiphy->bands[cbss->channel->band]; 3454 sband = local->hw.wiphy->bands[cbss->channel->band];
3199 3455
3200 ifmgd->flags &= ~IEEE80211_STA_DISABLE_40MHZ; 3456 ifmgd->flags &= ~(IEEE80211_STA_DISABLE_40MHZ |
3457 IEEE80211_STA_DISABLE_80P80MHZ |
3458 IEEE80211_STA_DISABLE_160MHZ);
3459
3460 if (!(ifmgd->flags & IEEE80211_STA_DISABLE_HT) &&
3461 sband->ht_cap.ht_supported) {
3462 const u8 *ht_oper_ie;
3201 3463
3202 if (sband->ht_cap.ht_supported) {
3203 ht_oper_ie = cfg80211_find_ie(WLAN_EID_HT_OPERATION, 3464 ht_oper_ie = cfg80211_find_ie(WLAN_EID_HT_OPERATION,
3204 cbss->information_elements, 3465 cbss->information_elements,
3205 cbss->len_information_elements); 3466 cbss->len_information_elements);
@@ -3207,82 +3468,45 @@ static int ieee80211_prep_channel(struct ieee80211_sub_if_data *sdata,
3207 ht_oper = (void *)(ht_oper_ie + 2); 3468 ht_oper = (void *)(ht_oper_ie + 2);
3208 } 3469 }
3209 3470
3210 if (ht_oper) { 3471 if (!(ifmgd->flags & IEEE80211_STA_DISABLE_VHT) &&
3211 ht_cfreq = ieee80211_channel_to_frequency(ht_oper->primary_chan, 3472 sband->vht_cap.vht_supported) {
3212 cbss->channel->band); 3473 const u8 *vht_oper_ie;
3213 /* check that channel matches the right operating channel */ 3474
3214 if (cbss->channel->center_freq != ht_cfreq) { 3475 vht_oper_ie = cfg80211_find_ie(WLAN_EID_VHT_OPERATION,
3215 /* 3476 cbss->information_elements,
3216 * It's possible that some APs are confused here; 3477 cbss->len_information_elements);
3217 * Netgear WNDR3700 sometimes reports 4 higher than 3478 if (vht_oper_ie && vht_oper_ie[1] >= sizeof(*vht_oper))
3218 * the actual channel in association responses, but 3479 vht_oper = (void *)(vht_oper_ie + 2);
3219 * since we look at probe response/beacon data here 3480 if (vht_oper && !ht_oper) {
3220 * it should be OK. 3481 vht_oper = NULL;
3221 */
3222 sdata_info(sdata, 3482 sdata_info(sdata,
3223 "Wrong control channel: center-freq: %d ht-cfreq: %d ht->primary_chan: %d band: %d - Disabling HT\n", 3483 "AP advertised VHT without HT, disabling both\n");
3224 cbss->channel->center_freq, 3484 sdata->flags |= IEEE80211_STA_DISABLE_HT;
3225 ht_cfreq, ht_oper->primary_chan, 3485 sdata->flags |= IEEE80211_STA_DISABLE_VHT;
3226 cbss->channel->band);
3227 ht_oper = NULL;
3228 } 3486 }
3229 } 3487 }
3230 3488
3231 if (ht_oper) { 3489 ifmgd->flags |= ieee80211_determine_chantype(sdata, sband,
3232 /* 3490 cbss->channel,
3233 * cfg80211 already verified that the channel itself can 3491 ht_oper, vht_oper,
3234 * be used, but it didn't check that we can do the right 3492 &chandef);
3235 * HT type, so do that here as well. If HT40 isn't allowed
3236 * on this channel, disable 40 MHz operation.
3237 */
3238 const u8 *ht_cap_ie;
3239 const struct ieee80211_ht_cap *ht_cap;
3240 u8 chains = 1;
3241
3242 channel_type = NL80211_CHAN_HT20;
3243
3244 if (sband->ht_cap.cap & IEEE80211_HT_CAP_SUP_WIDTH_20_40) {
3245 switch (ht_oper->ht_param &
3246 IEEE80211_HT_PARAM_CHA_SEC_OFFSET) {
3247 case IEEE80211_HT_PARAM_CHA_SEC_ABOVE:
3248 if (cbss->channel->flags &
3249 IEEE80211_CHAN_NO_HT40PLUS)
3250 ifmgd->flags |=
3251 IEEE80211_STA_DISABLE_40MHZ;
3252 else
3253 channel_type = NL80211_CHAN_HT40PLUS;
3254 break;
3255 case IEEE80211_HT_PARAM_CHA_SEC_BELOW:
3256 if (cbss->channel->flags &
3257 IEEE80211_CHAN_NO_HT40MINUS)
3258 ifmgd->flags |=
3259 IEEE80211_STA_DISABLE_40MHZ;
3260 else
3261 channel_type = NL80211_CHAN_HT40MINUS;
3262 break;
3263 }
3264 }
3265 3493
3266 ht_cap_ie = cfg80211_find_ie(WLAN_EID_HT_CAPABILITY, 3494 sdata->needed_rx_chains = min(ieee80211_ht_vht_rx_chains(sdata, cbss),
3267 cbss->information_elements, 3495 local->rx_chains);
3268 cbss->len_information_elements);
3269 if (ht_cap_ie && ht_cap_ie[1] >= sizeof(*ht_cap)) {
3270 ht_cap = (void *)(ht_cap_ie + 2);
3271 chains = ieee80211_mcs_to_chains(&ht_cap->mcs);
3272 }
3273 sdata->needed_rx_chains = min(chains, local->rx_chains);
3274 } else {
3275 sdata->needed_rx_chains = 1;
3276 sdata->u.mgd.flags |= IEEE80211_STA_DISABLE_HT;
3277 }
3278 3496
3279 /* will change later if needed */ 3497 /* will change later if needed */
3280 sdata->smps_mode = IEEE80211_SMPS_OFF; 3498 sdata->smps_mode = IEEE80211_SMPS_OFF;
3281 3499
3282 ieee80211_vif_release_channel(sdata); 3500 /*
3283 cfg80211_chandef_create(&chandef, cbss->channel, channel_type); 3501 * If this fails (possibly due to channel context sharing
3284 return ieee80211_vif_use_channel(sdata, &chandef, 3502 * on incompatible channels, e.g. 80+80 and 160 sharing the
3285 IEEE80211_CHANCTX_SHARED); 3503 * same control channel) try to use a smaller bandwidth.
3504 */
3505 ret = ieee80211_vif_use_channel(sdata, &chandef,
3506 IEEE80211_CHANCTX_SHARED);
3507 while (ret && chandef.width != NL80211_CHAN_WIDTH_20_NOHT)
3508 ifmgd->flags |= chandef_downgrade(&chandef);
3509 return ret;
3286} 3510}
3287 3511
3288static int ieee80211_prep_connection(struct ieee80211_sub_if_data *sdata, 3512static int ieee80211_prep_connection(struct ieee80211_sub_if_data *sdata,