|
@@ -114,6 +114,9 @@ enum rx_mgmt_action {
|
|
|
|
|
|
/* caller must call cfg80211_send_assoc_timeout() */
|
|
|
RX_MGMT_CFG80211_ASSOC_TIMEOUT,
|
|
|
+
|
|
|
+ /* used when a processed beacon causes a deauth */
|
|
|
+ RX_MGMT_CFG80211_TX_DEAUTH,
|
|
|
};
|
|
|
|
|
|
/* utils */
|
|
@@ -174,79 +177,331 @@ static int ecw2cw(int ecw)
|
|
|
return (1 << ecw) - 1;
|
|
|
}
|
|
|
|
|
|
-static u32 ieee80211_config_ht_tx(struct ieee80211_sub_if_data *sdata,
|
|
|
- struct ieee80211_ht_operation *ht_oper,
|
|
|
- const u8 *bssid, bool reconfig)
|
|
|
+static u32 chandef_downgrade(struct cfg80211_chan_def *c)
|
|
|
+{
|
|
|
+ u32 ret;
|
|
|
+ int tmp;
|
|
|
+
|
|
|
+ switch (c->width) {
|
|
|
+ case NL80211_CHAN_WIDTH_20:
|
|
|
+ c->width = NL80211_CHAN_WIDTH_20_NOHT;
|
|
|
+ ret = IEEE80211_STA_DISABLE_HT | IEEE80211_STA_DISABLE_VHT;
|
|
|
+ break;
|
|
|
+ case NL80211_CHAN_WIDTH_40:
|
|
|
+ c->width = NL80211_CHAN_WIDTH_20;
|
|
|
+ c->center_freq1 = c->chan->center_freq;
|
|
|
+ ret = IEEE80211_STA_DISABLE_40MHZ |
|
|
|
+ IEEE80211_STA_DISABLE_VHT;
|
|
|
+ break;
|
|
|
+ case NL80211_CHAN_WIDTH_80:
|
|
|
+ tmp = (30 + c->chan->center_freq - c->center_freq1)/20;
|
|
|
+ /* n_P40 */
|
|
|
+ tmp /= 2;
|
|
|
+ /* freq_P40 */
|
|
|
+ c->center_freq1 = c->center_freq1 - 20 + 40 * tmp;
|
|
|
+ c->width = NL80211_CHAN_WIDTH_40;
|
|
|
+ ret = IEEE80211_STA_DISABLE_VHT;
|
|
|
+ break;
|
|
|
+ case NL80211_CHAN_WIDTH_80P80:
|
|
|
+ c->center_freq2 = 0;
|
|
|
+ c->width = NL80211_CHAN_WIDTH_80;
|
|
|
+ ret = IEEE80211_STA_DISABLE_80P80MHZ |
|
|
|
+ IEEE80211_STA_DISABLE_160MHZ;
|
|
|
+ break;
|
|
|
+ case NL80211_CHAN_WIDTH_160:
|
|
|
+ /* n_P20 */
|
|
|
+ tmp = (70 + c->chan->center_freq - c->center_freq1)/20;
|
|
|
+ /* n_P80 */
|
|
|
+ tmp /= 4;
|
|
|
+ c->center_freq1 = c->center_freq1 - 40 + 80 * tmp;
|
|
|
+ c->width = NL80211_CHAN_WIDTH_80;
|
|
|
+ ret = IEEE80211_STA_DISABLE_80P80MHZ |
|
|
|
+ IEEE80211_STA_DISABLE_160MHZ;
|
|
|
+ break;
|
|
|
+ default:
|
|
|
+ case NL80211_CHAN_WIDTH_20_NOHT:
|
|
|
+ WARN_ON_ONCE(1);
|
|
|
+ c->width = NL80211_CHAN_WIDTH_20_NOHT;
|
|
|
+ ret = IEEE80211_STA_DISABLE_HT | IEEE80211_STA_DISABLE_VHT;
|
|
|
+ break;
|
|
|
+ }
|
|
|
+
|
|
|
+ WARN_ON_ONCE(!cfg80211_chandef_valid(c));
|
|
|
+
|
|
|
+ return ret;
|
|
|
+}
|
|
|
+
|
|
|
+static u32
|
|
|
+ieee80211_determine_chantype(struct ieee80211_sub_if_data *sdata,
|
|
|
+ struct ieee80211_supported_band *sband,
|
|
|
+ struct ieee80211_channel *channel,
|
|
|
+ const struct ieee80211_ht_operation *ht_oper,
|
|
|
+ const struct ieee80211_vht_operation *vht_oper,
|
|
|
+ struct cfg80211_chan_def *chandef, bool verbose)
|
|
|
+{
|
|
|
+ struct cfg80211_chan_def vht_chandef;
|
|
|
+ u32 ht_cfreq, ret;
|
|
|
+
|
|
|
+ chandef->chan = channel;
|
|
|
+ chandef->width = NL80211_CHAN_WIDTH_20_NOHT;
|
|
|
+ chandef->center_freq1 = channel->center_freq;
|
|
|
+ chandef->center_freq2 = 0;
|
|
|
+
|
|
|
+ if (!ht_oper || !sband->ht_cap.ht_supported) {
|
|
|
+ ret = IEEE80211_STA_DISABLE_HT | IEEE80211_STA_DISABLE_VHT;
|
|
|
+ goto out;
|
|
|
+ }
|
|
|
+
|
|
|
+ chandef->width = NL80211_CHAN_WIDTH_20;
|
|
|
+
|
|
|
+ ht_cfreq = ieee80211_channel_to_frequency(ht_oper->primary_chan,
|
|
|
+ channel->band);
|
|
|
+ /* check that channel matches the right operating channel */
|
|
|
+ if (channel->center_freq != ht_cfreq) {
|
|
|
+ /*
|
|
|
+ * It's possible that some APs are confused here;
|
|
|
+ * Netgear WNDR3700 sometimes reports 4 higher than
|
|
|
+ * the actual channel in association responses, but
|
|
|
+ * since we look at probe response/beacon data here
|
|
|
+ * it should be OK.
|
|
|
+ */
|
|
|
+ if (verbose)
|
|
|
+ sdata_info(sdata,
|
|
|
+ "Wrong control channel: center-freq: %d ht-cfreq: %d ht->primary_chan: %d band: %d - Disabling HT\n",
|
|
|
+ channel->center_freq, ht_cfreq,
|
|
|
+ ht_oper->primary_chan, channel->band);
|
|
|
+ ret = IEEE80211_STA_DISABLE_HT | IEEE80211_STA_DISABLE_VHT;
|
|
|
+ goto out;
|
|
|
+ }
|
|
|
+
|
|
|
+ /* check 40 MHz support, if we have it */
|
|
|
+ if (sband->ht_cap.cap & IEEE80211_HT_CAP_SUP_WIDTH_20_40) {
|
|
|
+ switch (ht_oper->ht_param & IEEE80211_HT_PARAM_CHA_SEC_OFFSET) {
|
|
|
+ case IEEE80211_HT_PARAM_CHA_SEC_ABOVE:
|
|
|
+ chandef->width = NL80211_CHAN_WIDTH_40;
|
|
|
+ chandef->center_freq1 += 10;
|
|
|
+ break;
|
|
|
+ case IEEE80211_HT_PARAM_CHA_SEC_BELOW:
|
|
|
+ chandef->width = NL80211_CHAN_WIDTH_40;
|
|
|
+ chandef->center_freq1 -= 10;
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ /* 40 MHz (and 80 MHz) must be supported for VHT */
|
|
|
+ ret = IEEE80211_STA_DISABLE_VHT;
|
|
|
+ goto out;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (!vht_oper || !sband->vht_cap.vht_supported) {
|
|
|
+ ret = IEEE80211_STA_DISABLE_VHT;
|
|
|
+ goto out;
|
|
|
+ }
|
|
|
+
|
|
|
+ vht_chandef.chan = channel;
|
|
|
+ vht_chandef.center_freq1 =
|
|
|
+ ieee80211_channel_to_frequency(vht_oper->center_freq_seg1_idx,
|
|
|
+ channel->band);
|
|
|
+ vht_chandef.center_freq2 = 0;
|
|
|
+
|
|
|
+ if (vht_oper->center_freq_seg2_idx)
|
|
|
+ vht_chandef.center_freq2 =
|
|
|
+ ieee80211_channel_to_frequency(
|
|
|
+ vht_oper->center_freq_seg2_idx,
|
|
|
+ channel->band);
|
|
|
+
|
|
|
+ switch (vht_oper->chan_width) {
|
|
|
+ case IEEE80211_VHT_CHANWIDTH_USE_HT:
|
|
|
+ vht_chandef.width = chandef->width;
|
|
|
+ break;
|
|
|
+ case IEEE80211_VHT_CHANWIDTH_80MHZ:
|
|
|
+ vht_chandef.width = NL80211_CHAN_WIDTH_80;
|
|
|
+ break;
|
|
|
+ case IEEE80211_VHT_CHANWIDTH_160MHZ:
|
|
|
+ vht_chandef.width = NL80211_CHAN_WIDTH_160;
|
|
|
+ break;
|
|
|
+ case IEEE80211_VHT_CHANWIDTH_80P80MHZ:
|
|
|
+ vht_chandef.width = NL80211_CHAN_WIDTH_80P80;
|
|
|
+ break;
|
|
|
+ default:
|
|
|
+ if (verbose)
|
|
|
+ sdata_info(sdata,
|
|
|
+ "AP VHT operation IE has invalid channel width (%d), disable VHT\n",
|
|
|
+ vht_oper->chan_width);
|
|
|
+ ret = IEEE80211_STA_DISABLE_VHT;
|
|
|
+ goto out;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (!cfg80211_chandef_valid(&vht_chandef)) {
|
|
|
+ if (verbose)
|
|
|
+ sdata_info(sdata,
|
|
|
+ "AP VHT information is invalid, disable VHT\n");
|
|
|
+ ret = IEEE80211_STA_DISABLE_VHT;
|
|
|
+ goto out;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (cfg80211_chandef_identical(chandef, &vht_chandef)) {
|
|
|
+ ret = 0;
|
|
|
+ goto out;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (!cfg80211_chandef_compatible(chandef, &vht_chandef)) {
|
|
|
+ if (verbose)
|
|
|
+ sdata_info(sdata,
|
|
|
+ "AP VHT information doesn't match HT, disable VHT\n");
|
|
|
+ ret = IEEE80211_STA_DISABLE_VHT;
|
|
|
+ goto out;
|
|
|
+ }
|
|
|
+
|
|
|
+ *chandef = vht_chandef;
|
|
|
+
|
|
|
+ ret = 0;
|
|
|
+
|
|
|
+out:
|
|
|
+ /* don't print the message below for VHT mismatch if VHT is disabled */
|
|
|
+ if (ret & IEEE80211_STA_DISABLE_VHT)
|
|
|
+ vht_chandef = *chandef;
|
|
|
+
|
|
|
+ while (!cfg80211_chandef_usable(sdata->local->hw.wiphy, chandef,
|
|
|
+ IEEE80211_CHAN_DISABLED)) {
|
|
|
+ if (WARN_ON(chandef->width == NL80211_CHAN_WIDTH_20_NOHT)) {
|
|
|
+ ret = IEEE80211_STA_DISABLE_HT |
|
|
|
+ IEEE80211_STA_DISABLE_VHT;
|
|
|
+ goto out;
|
|
|
+ }
|
|
|
+
|
|
|
+ ret |= chandef_downgrade(chandef);
|
|
|
+ }
|
|
|
+
|
|
|
+ if (chandef->width != vht_chandef.width && verbose)
|
|
|
+ sdata_info(sdata,
|
|
|
+ "capabilities/regulatory prevented using AP HT/VHT configuration, downgraded\n");
|
|
|
+
|
|
|
+ WARN_ON_ONCE(!cfg80211_chandef_valid(chandef));
|
|
|
+ return ret;
|
|
|
+}
|
|
|
+
|
|
|
+static int ieee80211_config_bw(struct ieee80211_sub_if_data *sdata,
|
|
|
+ struct sta_info *sta,
|
|
|
+ const struct ieee80211_ht_operation *ht_oper,
|
|
|
+ const struct ieee80211_vht_operation *vht_oper,
|
|
|
+ const u8 *bssid, u32 *changed)
|
|
|
{
|
|
|
struct ieee80211_local *local = sdata->local;
|
|
|
+ struct ieee80211_if_managed *ifmgd = &sdata->u.mgd;
|
|
|
struct ieee80211_supported_band *sband;
|
|
|
- struct ieee80211_chanctx_conf *chanctx_conf;
|
|
|
struct ieee80211_channel *chan;
|
|
|
- struct sta_info *sta;
|
|
|
- u32 changed = 0;
|
|
|
+ struct cfg80211_chan_def chandef;
|
|
|
u16 ht_opmode;
|
|
|
- bool disable_40 = false;
|
|
|
+ u32 flags;
|
|
|
+ enum ieee80211_sta_rx_bandwidth new_sta_bw;
|
|
|
+ int ret;
|
|
|
|
|
|
- rcu_read_lock();
|
|
|
- chanctx_conf = rcu_dereference(sdata->vif.chanctx_conf);
|
|
|
- if (WARN_ON(!chanctx_conf)) {
|
|
|
- rcu_read_unlock();
|
|
|
+ /* if HT was/is disabled, don't track any bandwidth changes */
|
|
|
+ if (ifmgd->flags & IEEE80211_STA_DISABLE_HT || !ht_oper)
|
|
|
return 0;
|
|
|
- }
|
|
|
- chan = chanctx_conf->def.chan;
|
|
|
- rcu_read_unlock();
|
|
|
+
|
|
|
+ /* don't check VHT if we associated as non-VHT station */
|
|
|
+ if (ifmgd->flags & IEEE80211_STA_DISABLE_VHT)
|
|
|
+ vht_oper = NULL;
|
|
|
+
|
|
|
+ if (WARN_ON_ONCE(!sta))
|
|
|
+ return -EINVAL;
|
|
|
+
|
|
|
+ chan = sdata->vif.bss_conf.chandef.chan;
|
|
|
sband = local->hw.wiphy->bands[chan->band];
|
|
|
|
|
|
- switch (sdata->vif.bss_conf.chandef.width) {
|
|
|
+ /* calculate new channel (type) based on HT/VHT operation IEs */
|
|
|
+ flags = ieee80211_determine_chantype(sdata, sband, chan, ht_oper,
|
|
|
+ vht_oper, &chandef, false);
|
|
|
+
|
|
|
+ /*
|
|
|
+ * Downgrade the new channel if we associated with restricted
|
|
|
+ * capabilities. For example, if we associated as a 20 MHz STA
|
|
|
+ * to a 40 MHz AP (due to regulatory, capabilities or config
|
|
|
+ * reasons) then switching to a 40 MHz channel now won't do us
|
|
|
+ * any good -- we couldn't use it with the AP.
|
|
|
+ */
|
|
|
+ if (ifmgd->flags & IEEE80211_STA_DISABLE_80P80MHZ &&
|
|
|
+ chandef.width == NL80211_CHAN_WIDTH_80P80)
|
|
|
+ flags |= chandef_downgrade(&chandef);
|
|
|
+ if (ifmgd->flags & IEEE80211_STA_DISABLE_160MHZ &&
|
|
|
+ chandef.width == NL80211_CHAN_WIDTH_160)
|
|
|
+ flags |= chandef_downgrade(&chandef);
|
|
|
+ if (ifmgd->flags & IEEE80211_STA_DISABLE_40MHZ &&
|
|
|
+ chandef.width > NL80211_CHAN_WIDTH_20)
|
|
|
+ flags |= chandef_downgrade(&chandef);
|
|
|
+
|
|
|
+ if (cfg80211_chandef_identical(&chandef, &sdata->vif.bss_conf.chandef))
|
|
|
+ return 0;
|
|
|
+
|
|
|
+ sdata_info(sdata,
|
|
|
+ "AP %pM changed bandwidth, new config is %d MHz, width %d (%d/%d MHz)\n",
|
|
|
+ ifmgd->bssid, chandef.chan->center_freq, chandef.width,
|
|
|
+ chandef.center_freq1, chandef.center_freq2);
|
|
|
+
|
|
|
+ if (flags != (ifmgd->flags & (IEEE80211_STA_DISABLE_HT |
|
|
|
+ IEEE80211_STA_DISABLE_VHT |
|
|
|
+ IEEE80211_STA_DISABLE_40MHZ |
|
|
|
+ IEEE80211_STA_DISABLE_80P80MHZ |
|
|
|
+ IEEE80211_STA_DISABLE_160MHZ)) ||
|
|
|
+ !cfg80211_chandef_valid(&chandef)) {
|
|
|
+ sdata_info(sdata,
|
|
|
+ "AP %pM changed bandwidth in a way we can't support - disconnect\n",
|
|
|
+ ifmgd->bssid);
|
|
|
+ return -EINVAL;
|
|
|
+ }
|
|
|
+
|
|
|
+ switch (chandef.width) {
|
|
|
+ case NL80211_CHAN_WIDTH_20_NOHT:
|
|
|
+ case NL80211_CHAN_WIDTH_20:
|
|
|
+ new_sta_bw = IEEE80211_STA_RX_BW_20;
|
|
|
+ break;
|
|
|
case NL80211_CHAN_WIDTH_40:
|
|
|
- if (sdata->vif.bss_conf.chandef.chan->center_freq >
|
|
|
- sdata->vif.bss_conf.chandef.center_freq1 &&
|
|
|
- chan->flags & IEEE80211_CHAN_NO_HT40MINUS)
|
|
|
- disable_40 = true;
|
|
|
- if (sdata->vif.bss_conf.chandef.chan->center_freq <
|
|
|
- sdata->vif.bss_conf.chandef.center_freq1 &&
|
|
|
- chan->flags & IEEE80211_CHAN_NO_HT40PLUS)
|
|
|
- disable_40 = true;
|
|
|
+ new_sta_bw = IEEE80211_STA_RX_BW_40;
|
|
|
break;
|
|
|
- default:
|
|
|
+ case NL80211_CHAN_WIDTH_80:
|
|
|
+ new_sta_bw = IEEE80211_STA_RX_BW_80;
|
|
|
+ break;
|
|
|
+ case NL80211_CHAN_WIDTH_80P80:
|
|
|
+ case NL80211_CHAN_WIDTH_160:
|
|
|
+ new_sta_bw = IEEE80211_STA_RX_BW_160;
|
|
|
break;
|
|
|
+ default:
|
|
|
+ return -EINVAL;
|
|
|
}
|
|
|
|
|
|
- /* This can change during the lifetime of the BSS */
|
|
|
- if (!(ht_oper->ht_param & IEEE80211_HT_PARAM_CHAN_WIDTH_ANY))
|
|
|
- disable_40 = true;
|
|
|
-
|
|
|
- mutex_lock(&local->sta_mtx);
|
|
|
- sta = sta_info_get(sdata, bssid);
|
|
|
-
|
|
|
- WARN_ON_ONCE(!sta);
|
|
|
-
|
|
|
- if (sta && !sta->supports_40mhz)
|
|
|
- disable_40 = true;
|
|
|
+ if (new_sta_bw > sta->cur_max_bandwidth)
|
|
|
+ new_sta_bw = sta->cur_max_bandwidth;
|
|
|
|
|
|
- if (sta && (!reconfig ||
|
|
|
- (disable_40 != !(sta->sta.ht_cap.cap &
|
|
|
- IEEE80211_HT_CAP_SUP_WIDTH_20_40)))) {
|
|
|
+ if (new_sta_bw < sta->sta.bandwidth) {
|
|
|
+ sta->sta.bandwidth = new_sta_bw;
|
|
|
+ rate_control_rate_update(local, sband, sta,
|
|
|
+ IEEE80211_RC_BW_CHANGED);
|
|
|
+ }
|
|
|
|
|
|
- if (disable_40)
|
|
|
- sta->sta.ht_cap.cap &= ~IEEE80211_HT_CAP_SUP_WIDTH_20_40;
|
|
|
- else
|
|
|
- sta->sta.ht_cap.cap |= IEEE80211_HT_CAP_SUP_WIDTH_20_40;
|
|
|
+ ret = ieee80211_vif_change_bandwidth(sdata, &chandef, changed);
|
|
|
+ if (ret) {
|
|
|
+ sdata_info(sdata,
|
|
|
+ "AP %pM changed bandwidth to incompatible one - disconnect\n",
|
|
|
+ ifmgd->bssid);
|
|
|
+ return ret;
|
|
|
+ }
|
|
|
|
|
|
+ if (new_sta_bw > sta->sta.bandwidth) {
|
|
|
+ sta->sta.bandwidth = new_sta_bw;
|
|
|
rate_control_rate_update(local, sband, sta,
|
|
|
IEEE80211_RC_BW_CHANGED);
|
|
|
}
|
|
|
- mutex_unlock(&local->sta_mtx);
|
|
|
|
|
|
ht_opmode = le16_to_cpu(ht_oper->operation_mode);
|
|
|
|
|
|
/* if bss configuration changed store the new one */
|
|
|
- if (!reconfig || (sdata->vif.bss_conf.ht_operation_mode != ht_opmode)) {
|
|
|
- changed |= BSS_CHANGED_HT;
|
|
|
+ if (sdata->vif.bss_conf.ht_operation_mode != ht_opmode) {
|
|
|
+ *changed |= BSS_CHANGED_HT;
|
|
|
sdata->vif.bss_conf.ht_operation_mode = ht_opmode;
|
|
|
}
|
|
|
|
|
|
- return changed;
|
|
|
+ return 0;
|
|
|
}
|
|
|
|
|
|
/* frame sending functions */
|
|
@@ -790,10 +1045,10 @@ static void ieee80211_chswitch_timer(unsigned long data)
|
|
|
ieee80211_queue_work(&sdata->local->hw, &ifmgd->chswitch_work);
|
|
|
}
|
|
|
|
|
|
-void ieee80211_sta_process_chanswitch(struct ieee80211_sub_if_data *sdata,
|
|
|
- struct ieee80211_channel_sw_ie *sw_elem,
|
|
|
- struct ieee80211_bss *bss,
|
|
|
- u64 timestamp)
|
|
|
+void
|
|
|
+ieee80211_sta_process_chanswitch(struct ieee80211_sub_if_data *sdata,
|
|
|
+ const struct ieee80211_channel_sw_ie *sw_elem,
|
|
|
+ struct ieee80211_bss *bss, u64 timestamp)
|
|
|
{
|
|
|
struct cfg80211_bss *cbss =
|
|
|
container_of((void *)bss, struct cfg80211_bss, priv);
|
|
@@ -1212,16 +1467,30 @@ void ieee80211_dynamic_ps_timer(unsigned long data)
|
|
|
ieee80211_queue_work(&local->hw, &local->dynamic_ps_enable_work);
|
|
|
}
|
|
|
|
|
|
+void ieee80211_dfs_cac_timer_work(struct work_struct *work)
|
|
|
+{
|
|
|
+ struct delayed_work *delayed_work =
|
|
|
+ container_of(work, struct delayed_work, work);
|
|
|
+ struct ieee80211_sub_if_data *sdata =
|
|
|
+ container_of(delayed_work, struct ieee80211_sub_if_data,
|
|
|
+ dfs_cac_timer_work);
|
|
|
+
|
|
|
+ ieee80211_vif_release_channel(sdata);
|
|
|
+
|
|
|
+ cfg80211_cac_event(sdata->dev, NL80211_RADAR_CAC_FINISHED, GFP_KERNEL);
|
|
|
+}
|
|
|
+
|
|
|
/* MLME */
|
|
|
static bool ieee80211_sta_wmm_params(struct ieee80211_local *local,
|
|
|
struct ieee80211_sub_if_data *sdata,
|
|
|
- u8 *wmm_param, size_t wmm_param_len)
|
|
|
+ const u8 *wmm_param, size_t wmm_param_len)
|
|
|
{
|
|
|
struct ieee80211_tx_queue_params params;
|
|
|
struct ieee80211_if_managed *ifmgd = &sdata->u.mgd;
|
|
|
size_t left;
|
|
|
int count;
|
|
|
- u8 *pos, uapsd_queues = 0;
|
|
|
+ const u8 *pos;
|
|
|
+ u8 uapsd_queues = 0;
|
|
|
|
|
|
if (!local->ops->conf_tx)
|
|
|
return false;
|
|
@@ -1624,17 +1893,18 @@ void ieee80211_sta_tx_notify(struct ieee80211_sub_if_data *sdata,
|
|
|
if (!ieee80211_is_data(hdr->frame_control))
|
|
|
return;
|
|
|
|
|
|
- if (ack)
|
|
|
- ieee80211_sta_reset_conn_monitor(sdata);
|
|
|
-
|
|
|
if (ieee80211_is_nullfunc(hdr->frame_control) &&
|
|
|
sdata->u.mgd.probe_send_count > 0) {
|
|
|
if (ack)
|
|
|
- sdata->u.mgd.probe_send_count = 0;
|
|
|
+ ieee80211_sta_reset_conn_monitor(sdata);
|
|
|
else
|
|
|
sdata->u.mgd.nullfunc_failed = true;
|
|
|
ieee80211_queue_work(&sdata->local->hw, &sdata->work);
|
|
|
+ return;
|
|
|
}
|
|
|
+
|
|
|
+ if (ack)
|
|
|
+ ieee80211_sta_reset_conn_monitor(sdata);
|
|
|
}
|
|
|
|
|
|
static void ieee80211_mgd_probe_ap_send(struct ieee80211_sub_if_data *sdata)
|
|
@@ -1805,6 +2075,8 @@ static void __ieee80211_disconnect(struct ieee80211_sub_if_data *sdata)
|
|
|
WLAN_REASON_DISASSOC_DUE_TO_INACTIVITY,
|
|
|
true, frame_buf);
|
|
|
ifmgd->flags &= ~IEEE80211_STA_CSA_RECEIVED;
|
|
|
+ ieee80211_wake_queues_by_reason(&sdata->local->hw,
|
|
|
+ IEEE80211_QUEUE_STOP_REASON_CSA);
|
|
|
mutex_unlock(&ifmgd->mtx);
|
|
|
|
|
|
/*
|
|
@@ -1845,8 +2117,6 @@ static void ieee80211_csa_connection_drop_work(struct work_struct *work)
|
|
|
container_of(work, struct ieee80211_sub_if_data,
|
|
|
u.mgd.csa_connection_drop_work);
|
|
|
|
|
|
- ieee80211_wake_queues_by_reason(&sdata->local->hw,
|
|
|
- IEEE80211_QUEUE_STOP_REASON_CSA);
|
|
|
__ieee80211_disconnect(sdata);
|
|
|
}
|
|
|
|
|
@@ -1986,6 +2256,7 @@ ieee80211_rx_mgmt_auth(struct ieee80211_sub_if_data *sdata,
|
|
|
sdata_info(sdata, "authenticated\n");
|
|
|
ifmgd->auth_data->done = true;
|
|
|
ifmgd->auth_data->timeout = jiffies + IEEE80211_AUTH_WAIT_ASSOC;
|
|
|
+ ifmgd->auth_data->timeout_started = true;
|
|
|
run_again(ifmgd, ifmgd->auth_data->timeout);
|
|
|
|
|
|
if (ifmgd->auth_data->algorithm == WLAN_AUTH_SAE &&
|
|
@@ -2180,6 +2451,24 @@ static bool ieee80211_assoc_success(struct ieee80211_sub_if_data *sdata,
|
|
|
|
|
|
ifmgd->aid = aid;
|
|
|
|
|
|
+ /*
|
|
|
+ * We previously checked these in the beacon/probe response, so
|
|
|
+ * they should be present here. This is just a safety net.
|
|
|
+ */
|
|
|
+ if (!(ifmgd->flags & IEEE80211_STA_DISABLE_HT) &&
|
|
|
+ (!elems.wmm_param || !elems.ht_cap_elem || !elems.ht_operation)) {
|
|
|
+ sdata_info(sdata,
|
|
|
+ "HT AP is missing WMM params or HT capability/operation in AssocResp\n");
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (!(ifmgd->flags & IEEE80211_STA_DISABLE_VHT) &&
|
|
|
+ (!elems.vht_cap_elem || !elems.vht_operation)) {
|
|
|
+ sdata_info(sdata,
|
|
|
+ "VHT AP is missing VHT capability/operation in AssocResp\n");
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
mutex_lock(&sdata->local->sta_mtx);
|
|
|
/*
|
|
|
* station info was already allocated and inserted before
|
|
@@ -2193,17 +2482,36 @@ static bool ieee80211_assoc_success(struct ieee80211_sub_if_data *sdata,
|
|
|
|
|
|
sband = local->hw.wiphy->bands[ieee80211_get_sdata_band(sdata)];
|
|
|
|
|
|
+ /* Set up internal HT/VHT capabilities */
|
|
|
if (elems.ht_cap_elem && !(ifmgd->flags & IEEE80211_STA_DISABLE_HT))
|
|
|
ieee80211_ht_cap_ie_to_sta_ht_cap(sdata, sband,
|
|
|
- elems.ht_cap_elem, &sta->sta.ht_cap);
|
|
|
-
|
|
|
- sta->supports_40mhz =
|
|
|
- sta->sta.ht_cap.cap & IEEE80211_HT_CAP_SUP_WIDTH_20_40;
|
|
|
+ elems.ht_cap_elem, sta);
|
|
|
|
|
|
if (elems.vht_cap_elem && !(ifmgd->flags & IEEE80211_STA_DISABLE_VHT))
|
|
|
ieee80211_vht_cap_ie_to_sta_vht_cap(sdata, sband,
|
|
|
- elems.vht_cap_elem,
|
|
|
- &sta->sta.vht_cap);
|
|
|
+ elems.vht_cap_elem, sta);
|
|
|
+
|
|
|
+ /*
|
|
|
+ * Some APs, e.g. Netgear WNDR3700, report invalid HT operation data
|
|
|
+ * in their association response, so ignore that data for our own
|
|
|
+ * configuration. If it changed since the last beacon, we'll get the
|
|
|
+ * next beacon and update then.
|
|
|
+ */
|
|
|
+
|
|
|
+ /*
|
|
|
+ * If an operating mode notification IE is present, override the
|
|
|
+ * NSS calculation (that would be done in rate_control_rate_init())
|
|
|
+ * and use the # of streams from that element.
|
|
|
+ */
|
|
|
+ if (elems.opmode_notif &&
|
|
|
+ !(*elems.opmode_notif & IEEE80211_OPMODE_NOTIF_RX_NSS_TYPE_BF)) {
|
|
|
+ u8 nss;
|
|
|
+
|
|
|
+ nss = *elems.opmode_notif & IEEE80211_OPMODE_NOTIF_RX_NSS_MASK;
|
|
|
+ nss >>= IEEE80211_OPMODE_NOTIF_RX_NSS_SHIFT;
|
|
|
+ nss += 1;
|
|
|
+ sta->sta.rx_nss = nss;
|
|
|
+ }
|
|
|
|
|
|
rate_control_rate_init(sta);
|
|
|
|
|
@@ -2242,11 +2550,6 @@ static bool ieee80211_assoc_success(struct ieee80211_sub_if_data *sdata,
|
|
|
ieee80211_set_wmm_default(sdata, false);
|
|
|
changed |= BSS_CHANGED_QOS;
|
|
|
|
|
|
- if (elems.ht_operation && elems.wmm_param &&
|
|
|
- !(ifmgd->flags & IEEE80211_STA_DISABLE_HT))
|
|
|
- changed |= ieee80211_config_ht_tx(sdata, elems.ht_operation,
|
|
|
- cbss->bssid, false);
|
|
|
-
|
|
|
/* set AID and assoc capability,
|
|
|
* ieee80211_set_associated() will tell the driver */
|
|
|
bss_conf->aid = aid;
|
|
@@ -2320,6 +2623,7 @@ ieee80211_rx_mgmt_assoc_resp(struct ieee80211_sub_if_data *sdata,
|
|
|
"%pM rejected association temporarily; comeback duration %u TU (%u ms)\n",
|
|
|
mgmt->sa, tu, ms);
|
|
|
assoc_data->timeout = jiffies + msecs_to_jiffies(ms);
|
|
|
+ assoc_data->timeout_started = true;
|
|
|
if (ms > IEEE80211_ASSOC_TIMEOUT)
|
|
|
run_again(ifmgd, assoc_data->timeout);
|
|
|
return RX_MGMT_NONE;
|
|
@@ -2371,7 +2675,7 @@ static void ieee80211_rx_bss_info(struct ieee80211_sub_if_data *sdata,
|
|
|
need_ps = sdata->u.mgd.associated && !sdata->u.mgd.dtim_period;
|
|
|
|
|
|
if (elems->tim && !elems->parse_error) {
|
|
|
- struct ieee80211_tim_ie *tim_ie = elems->tim;
|
|
|
+ const struct ieee80211_tim_ie *tim_ie = elems->tim;
|
|
|
sdata->u.mgd.dtim_period = tim_ie->dtim_period;
|
|
|
}
|
|
|
}
|
|
@@ -2443,6 +2747,7 @@ static void ieee80211_rx_mgmt_probe_resp(struct ieee80211_sub_if_data *sdata,
|
|
|
sdata_info(sdata, "direct probe responded\n");
|
|
|
ifmgd->auth_data->tries = 0;
|
|
|
ifmgd->auth_data->timeout = jiffies;
|
|
|
+ ifmgd->auth_data->timeout_started = true;
|
|
|
run_again(ifmgd, ifmgd->auth_data->timeout);
|
|
|
}
|
|
|
}
|
|
@@ -2468,10 +2773,10 @@ static const u64 care_about_ies =
|
|
|
(1ULL << WLAN_EID_HT_CAPABILITY) |
|
|
|
(1ULL << WLAN_EID_HT_OPERATION);
|
|
|
|
|
|
-static void ieee80211_rx_mgmt_beacon(struct ieee80211_sub_if_data *sdata,
|
|
|
- struct ieee80211_mgmt *mgmt,
|
|
|
- size_t len,
|
|
|
- struct ieee80211_rx_status *rx_status)
|
|
|
+static enum rx_mgmt_action
|
|
|
+ieee80211_rx_mgmt_beacon(struct ieee80211_sub_if_data *sdata,
|
|
|
+ struct ieee80211_mgmt *mgmt, size_t len,
|
|
|
+ u8 *deauth_buf, struct ieee80211_rx_status *rx_status)
|
|
|
{
|
|
|
struct ieee80211_if_managed *ifmgd = &sdata->u.mgd;
|
|
|
struct ieee80211_bss_conf *bss_conf = &sdata->vif.bss_conf;
|
|
@@ -2480,6 +2785,7 @@ static void ieee80211_rx_mgmt_beacon(struct ieee80211_sub_if_data *sdata,
|
|
|
struct ieee80211_local *local = sdata->local;
|
|
|
struct ieee80211_chanctx_conf *chanctx_conf;
|
|
|
struct ieee80211_channel *chan;
|
|
|
+ struct sta_info *sta;
|
|
|
u32 changed = 0;
|
|
|
bool erp_valid;
|
|
|
u8 erp_value = 0;
|
|
@@ -2491,18 +2797,18 @@ static void ieee80211_rx_mgmt_beacon(struct ieee80211_sub_if_data *sdata,
|
|
|
/* Process beacon from the current BSS */
|
|
|
baselen = (u8 *) mgmt->u.beacon.variable - (u8 *) mgmt;
|
|
|
if (baselen > len)
|
|
|
- return;
|
|
|
+ return RX_MGMT_NONE;
|
|
|
|
|
|
rcu_read_lock();
|
|
|
chanctx_conf = rcu_dereference(sdata->vif.chanctx_conf);
|
|
|
if (!chanctx_conf) {
|
|
|
rcu_read_unlock();
|
|
|
- return;
|
|
|
+ return RX_MGMT_NONE;
|
|
|
}
|
|
|
|
|
|
if (rx_status->freq != chanctx_conf->def.chan->center_freq) {
|
|
|
rcu_read_unlock();
|
|
|
- return;
|
|
|
+ return RX_MGMT_NONE;
|
|
|
}
|
|
|
chan = chanctx_conf->def.chan;
|
|
|
rcu_read_unlock();
|
|
@@ -2528,13 +2834,14 @@ static void ieee80211_rx_mgmt_beacon(struct ieee80211_sub_if_data *sdata,
|
|
|
}
|
|
|
/* continue assoc process */
|
|
|
ifmgd->assoc_data->timeout = jiffies;
|
|
|
+ ifmgd->assoc_data->timeout_started = true;
|
|
|
run_again(ifmgd, ifmgd->assoc_data->timeout);
|
|
|
- return;
|
|
|
+ return RX_MGMT_NONE;
|
|
|
}
|
|
|
|
|
|
if (!ifmgd->associated ||
|
|
|
!ether_addr_equal(mgmt->bssid, ifmgd->associated->bssid))
|
|
|
- return;
|
|
|
+ return RX_MGMT_NONE;
|
|
|
bssid = ifmgd->associated->bssid;
|
|
|
|
|
|
/* Track average RSSI from the Beacon frames of the current AP */
|
|
@@ -2672,7 +2979,7 @@ static void ieee80211_rx_mgmt_beacon(struct ieee80211_sub_if_data *sdata,
|
|
|
}
|
|
|
|
|
|
if (ncrc == ifmgd->beacon_crc && ifmgd->beacon_crc_valid)
|
|
|
- return;
|
|
|
+ return RX_MGMT_NONE;
|
|
|
ifmgd->beacon_crc = ncrc;
|
|
|
ifmgd->beacon_crc_valid = true;
|
|
|
|
|
@@ -2718,11 +3025,22 @@ static void ieee80211_rx_mgmt_beacon(struct ieee80211_sub_if_data *sdata,
|
|
|
le16_to_cpu(mgmt->u.beacon.capab_info),
|
|
|
erp_valid, erp_value);
|
|
|
|
|
|
+ mutex_lock(&local->sta_mtx);
|
|
|
+ sta = sta_info_get(sdata, bssid);
|
|
|
|
|
|
- if (elems.ht_cap_elem && elems.ht_operation && elems.wmm_param &&
|
|
|
- !(ifmgd->flags & IEEE80211_STA_DISABLE_HT))
|
|
|
- changed |= ieee80211_config_ht_tx(sdata, elems.ht_operation,
|
|
|
- bssid, true);
|
|
|
+ if (ieee80211_config_bw(sdata, sta, elems.ht_operation,
|
|
|
+ elems.vht_operation, bssid, &changed)) {
|
|
|
+ mutex_unlock(&local->sta_mtx);
|
|
|
+ ieee80211_set_disassoc(sdata, IEEE80211_STYPE_DEAUTH,
|
|
|
+ WLAN_REASON_DEAUTH_LEAVING,
|
|
|
+ true, deauth_buf);
|
|
|
+ return RX_MGMT_CFG80211_TX_DEAUTH;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (sta && elems.opmode_notif)
|
|
|
+ ieee80211_vht_handle_opmode(sdata, sta, *elems.opmode_notif,
|
|
|
+ rx_status->band, true);
|
|
|
+ mutex_unlock(&local->sta_mtx);
|
|
|
|
|
|
if (elems.country_elem && elems.pwr_constr_elem &&
|
|
|
mgmt->u.probe_resp.capab_info &
|
|
@@ -2733,6 +3051,8 @@ static void ieee80211_rx_mgmt_beacon(struct ieee80211_sub_if_data *sdata,
|
|
|
elems.pwr_constr_elem);
|
|
|
|
|
|
ieee80211_bss_info_change_notify(sdata, changed);
|
|
|
+
|
|
|
+ return RX_MGMT_NONE;
|
|
|
}
|
|
|
|
|
|
void ieee80211_sta_rx_queued_mgmt(struct ieee80211_sub_if_data *sdata,
|
|
@@ -2743,6 +3063,7 @@ void ieee80211_sta_rx_queued_mgmt(struct ieee80211_sub_if_data *sdata,
|
|
|
struct ieee80211_mgmt *mgmt;
|
|
|
struct cfg80211_bss *bss = NULL;
|
|
|
enum rx_mgmt_action rma = RX_MGMT_NONE;
|
|
|
+ u8 deauth_buf[IEEE80211_DEAUTH_FRAME_LEN];
|
|
|
u16 fc;
|
|
|
|
|
|
rx_status = (struct ieee80211_rx_status *) skb->cb;
|
|
@@ -2753,7 +3074,8 @@ void ieee80211_sta_rx_queued_mgmt(struct ieee80211_sub_if_data *sdata,
|
|
|
|
|
|
switch (fc & IEEE80211_FCTL_STYPE) {
|
|
|
case IEEE80211_STYPE_BEACON:
|
|
|
- ieee80211_rx_mgmt_beacon(sdata, mgmt, skb->len, rx_status);
|
|
|
+ rma = ieee80211_rx_mgmt_beacon(sdata, mgmt, skb->len,
|
|
|
+ deauth_buf, rx_status);
|
|
|
break;
|
|
|
case IEEE80211_STYPE_PROBE_RESP:
|
|
|
ieee80211_rx_mgmt_probe_resp(sdata, skb);
|
|
@@ -2802,6 +3124,10 @@ void ieee80211_sta_rx_queued_mgmt(struct ieee80211_sub_if_data *sdata,
|
|
|
case RX_MGMT_CFG80211_ASSOC_TIMEOUT:
|
|
|
cfg80211_send_assoc_timeout(sdata->dev, mgmt->bssid);
|
|
|
break;
|
|
|
+ case RX_MGMT_CFG80211_TX_DEAUTH:
|
|
|
+ cfg80211_send_deauth(sdata->dev, deauth_buf,
|
|
|
+ sizeof(deauth_buf));
|
|
|
+ break;
|
|
|
default:
|
|
|
WARN(1, "unexpected: %d", rma);
|
|
|
}
|
|
@@ -2920,7 +3246,10 @@ static int ieee80211_probe_auth(struct ieee80211_sub_if_data *sdata)
|
|
|
|
|
|
if (!(local->hw.flags & IEEE80211_HW_REPORTS_TX_ACK_STATUS)) {
|
|
|
auth_data->timeout = jiffies + IEEE80211_AUTH_TIMEOUT;
|
|
|
+ ifmgd->auth_data->timeout_started = true;
|
|
|
run_again(ifmgd, auth_data->timeout);
|
|
|
+ } else {
|
|
|
+ auth_data->timeout_started = false;
|
|
|
}
|
|
|
|
|
|
return 0;
|
|
@@ -2954,7 +3283,10 @@ static int ieee80211_do_assoc(struct ieee80211_sub_if_data *sdata)
|
|
|
|
|
|
if (!(local->hw.flags & IEEE80211_HW_REPORTS_TX_ACK_STATUS)) {
|
|
|
assoc_data->timeout = jiffies + IEEE80211_ASSOC_TIMEOUT;
|
|
|
+ assoc_data->timeout_started = true;
|
|
|
run_again(&sdata->u.mgd, assoc_data->timeout);
|
|
|
+ } else {
|
|
|
+ assoc_data->timeout_started = false;
|
|
|
}
|
|
|
|
|
|
return 0;
|
|
@@ -2993,6 +3325,7 @@ void ieee80211_sta_work(struct ieee80211_sub_if_data *sdata)
|
|
|
} else {
|
|
|
ifmgd->auth_data->timeout = jiffies - 1;
|
|
|
}
|
|
|
+ ifmgd->auth_data->timeout_started = true;
|
|
|
} else if (ifmgd->assoc_data &&
|
|
|
(ieee80211_is_assoc_req(fc) ||
|
|
|
ieee80211_is_reassoc_req(fc))) {
|
|
@@ -3003,10 +3336,11 @@ void ieee80211_sta_work(struct ieee80211_sub_if_data *sdata)
|
|
|
} else {
|
|
|
ifmgd->assoc_data->timeout = jiffies - 1;
|
|
|
}
|
|
|
+ ifmgd->assoc_data->timeout_started = true;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- if (ifmgd->auth_data &&
|
|
|
+ if (ifmgd->auth_data && ifmgd->auth_data->timeout_started &&
|
|
|
time_after(jiffies, ifmgd->auth_data->timeout)) {
|
|
|
if (ifmgd->auth_data->done) {
|
|
|
/*
|
|
@@ -3025,10 +3359,10 @@ void ieee80211_sta_work(struct ieee80211_sub_if_data *sdata)
|
|
|
cfg80211_send_auth_timeout(sdata->dev, bssid);
|
|
|
mutex_lock(&ifmgd->mtx);
|
|
|
}
|
|
|
- } else if (ifmgd->auth_data)
|
|
|
+ } else if (ifmgd->auth_data && ifmgd->auth_data->timeout_started)
|
|
|
run_again(ifmgd, ifmgd->auth_data->timeout);
|
|
|
|
|
|
- if (ifmgd->assoc_data &&
|
|
|
+ if (ifmgd->assoc_data && ifmgd->assoc_data->timeout_started &&
|
|
|
time_after(jiffies, ifmgd->assoc_data->timeout)) {
|
|
|
if ((ifmgd->assoc_data->need_beacon &&
|
|
|
!ifmgd->assoc_data->have_beacon) ||
|
|
@@ -3043,7 +3377,7 @@ void ieee80211_sta_work(struct ieee80211_sub_if_data *sdata)
|
|
|
cfg80211_send_assoc_timeout(sdata->dev, bssid);
|
|
|
mutex_lock(&ifmgd->mtx);
|
|
|
}
|
|
|
- } else if (ifmgd->assoc_data)
|
|
|
+ } else if (ifmgd->assoc_data && ifmgd->assoc_data->timeout_started)
|
|
|
run_again(ifmgd, ifmgd->assoc_data->timeout);
|
|
|
|
|
|
if (ifmgd->flags & (IEEE80211_STA_BEACON_POLL |
|
|
@@ -3287,201 +3621,6 @@ int ieee80211_max_network_latency(struct notifier_block *nb,
|
|
|
return 0;
|
|
|
}
|
|
|
|
|
|
-static u32 chandef_downgrade(struct cfg80211_chan_def *c)
|
|
|
-{
|
|
|
- u32 ret;
|
|
|
- int tmp;
|
|
|
-
|
|
|
- switch (c->width) {
|
|
|
- case NL80211_CHAN_WIDTH_20:
|
|
|
- c->width = NL80211_CHAN_WIDTH_20_NOHT;
|
|
|
- ret = IEEE80211_STA_DISABLE_HT | IEEE80211_STA_DISABLE_VHT;
|
|
|
- break;
|
|
|
- case NL80211_CHAN_WIDTH_40:
|
|
|
- c->width = NL80211_CHAN_WIDTH_20;
|
|
|
- c->center_freq1 = c->chan->center_freq;
|
|
|
- ret = IEEE80211_STA_DISABLE_40MHZ |
|
|
|
- IEEE80211_STA_DISABLE_VHT;
|
|
|
- break;
|
|
|
- case NL80211_CHAN_WIDTH_80:
|
|
|
- tmp = (30 + c->chan->center_freq - c->center_freq1)/20;
|
|
|
- /* n_P40 */
|
|
|
- tmp /= 2;
|
|
|
- /* freq_P40 */
|
|
|
- c->center_freq1 = c->center_freq1 - 20 + 40 * tmp;
|
|
|
- c->width = NL80211_CHAN_WIDTH_40;
|
|
|
- ret = IEEE80211_STA_DISABLE_VHT;
|
|
|
- break;
|
|
|
- case NL80211_CHAN_WIDTH_80P80:
|
|
|
- c->center_freq2 = 0;
|
|
|
- c->width = NL80211_CHAN_WIDTH_80;
|
|
|
- ret = IEEE80211_STA_DISABLE_80P80MHZ |
|
|
|
- IEEE80211_STA_DISABLE_160MHZ;
|
|
|
- break;
|
|
|
- case NL80211_CHAN_WIDTH_160:
|
|
|
- /* n_P20 */
|
|
|
- tmp = (70 + c->chan->center_freq - c->center_freq1)/20;
|
|
|
- /* n_P80 */
|
|
|
- tmp /= 4;
|
|
|
- c->center_freq1 = c->center_freq1 - 40 + 80 * tmp;
|
|
|
- c->width = NL80211_CHAN_WIDTH_80;
|
|
|
- ret = IEEE80211_STA_DISABLE_80P80MHZ |
|
|
|
- IEEE80211_STA_DISABLE_160MHZ;
|
|
|
- break;
|
|
|
- default:
|
|
|
- case NL80211_CHAN_WIDTH_20_NOHT:
|
|
|
- WARN_ON_ONCE(1);
|
|
|
- c->width = NL80211_CHAN_WIDTH_20_NOHT;
|
|
|
- ret = IEEE80211_STA_DISABLE_HT | IEEE80211_STA_DISABLE_VHT;
|
|
|
- break;
|
|
|
- }
|
|
|
-
|
|
|
- WARN_ON_ONCE(!cfg80211_chandef_valid(c));
|
|
|
-
|
|
|
- return ret;
|
|
|
-}
|
|
|
-
|
|
|
-static u32
|
|
|
-ieee80211_determine_chantype(struct ieee80211_sub_if_data *sdata,
|
|
|
- struct ieee80211_supported_band *sband,
|
|
|
- struct ieee80211_channel *channel,
|
|
|
- const struct ieee80211_ht_operation *ht_oper,
|
|
|
- const struct ieee80211_vht_operation *vht_oper,
|
|
|
- struct cfg80211_chan_def *chandef)
|
|
|
-{
|
|
|
- struct cfg80211_chan_def vht_chandef;
|
|
|
- u32 ht_cfreq, ret;
|
|
|
-
|
|
|
- chandef->chan = channel;
|
|
|
- chandef->width = NL80211_CHAN_WIDTH_20_NOHT;
|
|
|
- chandef->center_freq1 = channel->center_freq;
|
|
|
- chandef->center_freq2 = 0;
|
|
|
-
|
|
|
- if (!ht_oper || !sband->ht_cap.ht_supported) {
|
|
|
- ret = IEEE80211_STA_DISABLE_HT | IEEE80211_STA_DISABLE_VHT;
|
|
|
- goto out;
|
|
|
- }
|
|
|
-
|
|
|
- chandef->width = NL80211_CHAN_WIDTH_20;
|
|
|
-
|
|
|
- ht_cfreq = ieee80211_channel_to_frequency(ht_oper->primary_chan,
|
|
|
- channel->band);
|
|
|
- /* check that channel matches the right operating channel */
|
|
|
- if (channel->center_freq != ht_cfreq) {
|
|
|
- /*
|
|
|
- * It's possible that some APs are confused here;
|
|
|
- * Netgear WNDR3700 sometimes reports 4 higher than
|
|
|
- * the actual channel in association responses, but
|
|
|
- * since we look at probe response/beacon data here
|
|
|
- * it should be OK.
|
|
|
- */
|
|
|
- sdata_info(sdata,
|
|
|
- "Wrong control channel: center-freq: %d ht-cfreq: %d ht->primary_chan: %d band: %d - Disabling HT\n",
|
|
|
- channel->center_freq, ht_cfreq,
|
|
|
- ht_oper->primary_chan, channel->band);
|
|
|
- ret = IEEE80211_STA_DISABLE_HT | IEEE80211_STA_DISABLE_VHT;
|
|
|
- goto out;
|
|
|
- }
|
|
|
-
|
|
|
- /* check 40 MHz support, if we have it */
|
|
|
- if (sband->ht_cap.cap & IEEE80211_HT_CAP_SUP_WIDTH_20_40) {
|
|
|
- switch (ht_oper->ht_param & IEEE80211_HT_PARAM_CHA_SEC_OFFSET) {
|
|
|
- case IEEE80211_HT_PARAM_CHA_SEC_ABOVE:
|
|
|
- chandef->width = NL80211_CHAN_WIDTH_40;
|
|
|
- chandef->center_freq1 += 10;
|
|
|
- break;
|
|
|
- case IEEE80211_HT_PARAM_CHA_SEC_BELOW:
|
|
|
- chandef->width = NL80211_CHAN_WIDTH_40;
|
|
|
- chandef->center_freq1 -= 10;
|
|
|
- break;
|
|
|
- }
|
|
|
- } else {
|
|
|
- /* 40 MHz (and 80 MHz) must be supported for VHT */
|
|
|
- ret = IEEE80211_STA_DISABLE_VHT;
|
|
|
- goto out;
|
|
|
- }
|
|
|
-
|
|
|
- if (!vht_oper || !sband->vht_cap.vht_supported) {
|
|
|
- ret = IEEE80211_STA_DISABLE_VHT;
|
|
|
- goto out;
|
|
|
- }
|
|
|
-
|
|
|
- vht_chandef.chan = channel;
|
|
|
- vht_chandef.center_freq1 =
|
|
|
- ieee80211_channel_to_frequency(vht_oper->center_freq_seg1_idx,
|
|
|
- channel->band);
|
|
|
- vht_chandef.center_freq2 = 0;
|
|
|
-
|
|
|
- if (vht_oper->center_freq_seg2_idx)
|
|
|
- vht_chandef.center_freq2 =
|
|
|
- ieee80211_channel_to_frequency(
|
|
|
- vht_oper->center_freq_seg2_idx,
|
|
|
- channel->band);
|
|
|
-
|
|
|
- switch (vht_oper->chan_width) {
|
|
|
- case IEEE80211_VHT_CHANWIDTH_USE_HT:
|
|
|
- vht_chandef.width = chandef->width;
|
|
|
- break;
|
|
|
- case IEEE80211_VHT_CHANWIDTH_80MHZ:
|
|
|
- vht_chandef.width = NL80211_CHAN_WIDTH_80;
|
|
|
- break;
|
|
|
- case IEEE80211_VHT_CHANWIDTH_160MHZ:
|
|
|
- vht_chandef.width = NL80211_CHAN_WIDTH_160;
|
|
|
- break;
|
|
|
- case IEEE80211_VHT_CHANWIDTH_80P80MHZ:
|
|
|
- vht_chandef.width = NL80211_CHAN_WIDTH_80P80;
|
|
|
- break;
|
|
|
- default:
|
|
|
- sdata_info(sdata,
|
|
|
- "AP VHT operation IE has invalid channel width (%d), disable VHT\n",
|
|
|
- vht_oper->chan_width);
|
|
|
- ret = IEEE80211_STA_DISABLE_VHT;
|
|
|
- goto out;
|
|
|
- }
|
|
|
-
|
|
|
- if (!cfg80211_chandef_valid(&vht_chandef)) {
|
|
|
- sdata_info(sdata,
|
|
|
- "AP VHT information is invalid, disable VHT\n");
|
|
|
- ret = IEEE80211_STA_DISABLE_VHT;
|
|
|
- goto out;
|
|
|
- }
|
|
|
-
|
|
|
- if (cfg80211_chandef_identical(chandef, &vht_chandef)) {
|
|
|
- ret = 0;
|
|
|
- goto out;
|
|
|
- }
|
|
|
-
|
|
|
- if (!cfg80211_chandef_compatible(chandef, &vht_chandef)) {
|
|
|
- sdata_info(sdata,
|
|
|
- "AP VHT information doesn't match HT, disable VHT\n");
|
|
|
- ret = IEEE80211_STA_DISABLE_VHT;
|
|
|
- goto out;
|
|
|
- }
|
|
|
-
|
|
|
- *chandef = vht_chandef;
|
|
|
-
|
|
|
- ret = 0;
|
|
|
-
|
|
|
-out:
|
|
|
- while (!cfg80211_chandef_usable(sdata->local->hw.wiphy, chandef,
|
|
|
- IEEE80211_CHAN_DISABLED)) {
|
|
|
- if (WARN_ON(chandef->width == NL80211_CHAN_WIDTH_20_NOHT)) {
|
|
|
- ret = IEEE80211_STA_DISABLE_HT |
|
|
|
- IEEE80211_STA_DISABLE_VHT;
|
|
|
- goto out;
|
|
|
- }
|
|
|
-
|
|
|
- ret |= chandef_downgrade(chandef);
|
|
|
- }
|
|
|
-
|
|
|
- if (chandef->width != vht_chandef.width)
|
|
|
- sdata_info(sdata,
|
|
|
- "capabilities/regulatory prevented using AP HT/VHT configuration, downgraded\n");
|
|
|
-
|
|
|
- WARN_ON_ONCE(!cfg80211_chandef_valid(chandef));
|
|
|
- return ret;
|
|
|
-}
|
|
|
-
|
|
|
static u8 ieee80211_ht_vht_rx_chains(struct ieee80211_sub_if_data *sdata,
|
|
|
struct cfg80211_bss *cbss)
|
|
|
{
|
|
@@ -3547,16 +3686,22 @@ static int ieee80211_prep_channel(struct ieee80211_sub_if_data *sdata,
|
|
|
|
|
|
if (!(ifmgd->flags & IEEE80211_STA_DISABLE_HT) &&
|
|
|
sband->ht_cap.ht_supported) {
|
|
|
- const u8 *ht_oper_ie;
|
|
|
+ const u8 *ht_oper_ie, *ht_cap;
|
|
|
|
|
|
ht_oper_ie = ieee80211_bss_get_ie(cbss, WLAN_EID_HT_OPERATION);
|
|
|
if (ht_oper_ie && ht_oper_ie[1] >= sizeof(*ht_oper))
|
|
|
ht_oper = (void *)(ht_oper_ie + 2);
|
|
|
+
|
|
|
+ ht_cap = ieee80211_bss_get_ie(cbss, WLAN_EID_HT_CAPABILITY);
|
|
|
+ if (!ht_cap || ht_cap[1] < sizeof(struct ieee80211_ht_cap)) {
|
|
|
+ ifmgd->flags |= IEEE80211_STA_DISABLE_HT;
|
|
|
+ ht_oper = NULL;
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
if (!(ifmgd->flags & IEEE80211_STA_DISABLE_VHT) &&
|
|
|
sband->vht_cap.vht_supported) {
|
|
|
- const u8 *vht_oper_ie;
|
|
|
+ const u8 *vht_oper_ie, *vht_cap;
|
|
|
|
|
|
vht_oper_ie = ieee80211_bss_get_ie(cbss,
|
|
|
WLAN_EID_VHT_OPERATION);
|
|
@@ -3566,15 +3711,21 @@ static int ieee80211_prep_channel(struct ieee80211_sub_if_data *sdata,
|
|
|
vht_oper = NULL;
|
|
|
sdata_info(sdata,
|
|
|
"AP advertised VHT without HT, disabling both\n");
|
|
|
- sdata->flags |= IEEE80211_STA_DISABLE_HT;
|
|
|
- sdata->flags |= IEEE80211_STA_DISABLE_VHT;
|
|
|
+ ifmgd->flags |= IEEE80211_STA_DISABLE_HT;
|
|
|
+ ifmgd->flags |= IEEE80211_STA_DISABLE_VHT;
|
|
|
+ }
|
|
|
+
|
|
|
+ vht_cap = ieee80211_bss_get_ie(cbss, WLAN_EID_VHT_CAPABILITY);
|
|
|
+ if (!vht_cap || vht_cap[1] < sizeof(struct ieee80211_vht_cap)) {
|
|
|
+ ifmgd->flags |= IEEE80211_STA_DISABLE_VHT;
|
|
|
+ vht_oper = NULL;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
ifmgd->flags |= ieee80211_determine_chantype(sdata, sband,
|
|
|
cbss->channel,
|
|
|
ht_oper, vht_oper,
|
|
|
- &chandef);
|
|
|
+ &chandef, true);
|
|
|
|
|
|
sdata->needed_rx_chains = min(ieee80211_ht_vht_rx_chains(sdata, cbss),
|
|
|
local->rx_chains);
|
|
@@ -4021,6 +4172,7 @@ int ieee80211_mgd_assoc(struct ieee80211_sub_if_data *sdata,
|
|
|
sdata_info(sdata, "waiting for beacon from %pM\n",
|
|
|
ifmgd->bssid);
|
|
|
assoc_data->timeout = TU_TO_EXP_TIME(req->bss->beacon_interval);
|
|
|
+ assoc_data->timeout_started = true;
|
|
|
assoc_data->need_beacon = true;
|
|
|
} else if (beacon_ies) {
|
|
|
const u8 *tim_ie = cfg80211_find_ie(WLAN_EID_TIM,
|
|
@@ -4036,6 +4188,7 @@ int ieee80211_mgd_assoc(struct ieee80211_sub_if_data *sdata,
|
|
|
}
|
|
|
assoc_data->have_beacon = true;
|
|
|
assoc_data->timeout = jiffies;
|
|
|
+ assoc_data->timeout_started = true;
|
|
|
|
|
|
if (local->hw.flags & IEEE80211_HW_TIMING_BEACON_ONLY) {
|
|
|
sdata->vif.bss_conf.sync_tsf = beacon_ies->tsf;
|
|
@@ -4045,6 +4198,7 @@ int ieee80211_mgd_assoc(struct ieee80211_sub_if_data *sdata,
|
|
|
}
|
|
|
} else {
|
|
|
assoc_data->timeout = jiffies;
|
|
|
+ assoc_data->timeout_started = true;
|
|
|
}
|
|
|
rcu_read_unlock();
|
|
|
|