]> git.kernelconcepts.de Git - karo-tx-linux.git/blobdiff - net/mac80211/mlme.c
Merge remote-tracking branch 'wireless-next/master' into mac80211-next
[karo-tx-linux.git] / net / mac80211 / mlme.c
index 9c4968938472683c6e07d1f35c9ec396720563f8..f7beb12abde23b05a0a932bc6fe7726c7014df99 100644 (file)
@@ -289,6 +289,8 @@ ieee80211_determine_chantype(struct ieee80211_sub_if_data *sdata,
        } else {
                /* 40 MHz (and 80 MHz) must be supported for VHT */
                ret = IEEE80211_STA_DISABLE_VHT;
+               /* also mark 40 MHz disabled */
+               ret |= IEEE80211_STA_DISABLE_40MHZ;
                goto out;
        }
 
@@ -303,12 +305,6 @@ ieee80211_determine_chantype(struct ieee80211_sub_if_data *sdata,
                                               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;
@@ -321,6 +317,10 @@ ieee80211_determine_chantype(struct ieee80211_sub_if_data *sdata,
                break;
        case IEEE80211_VHT_CHANWIDTH_80P80MHZ:
                vht_chandef.width = NL80211_CHAN_WIDTH_80P80;
+               vht_chandef.center_freq2 =
+                       ieee80211_channel_to_frequency(
+                               vht_oper->center_freq_seg2_idx,
+                               channel->band);
                break;
        default:
                if (verbose)
@@ -604,7 +604,6 @@ static void ieee80211_add_vht_ie(struct ieee80211_sub_if_data *sdata,
        u8 *pos;
        u32 cap;
        struct ieee80211_sta_vht_cap vht_cap;
-       int i;
 
        BUILD_BUG_ON(sizeof(vht_cap) != sizeof(sband->vht_cap));
 
@@ -632,37 +631,6 @@ static void ieee80211_add_vht_ie(struct ieee80211_sub_if_data *sdata,
                        cpu_to_le32(IEEE80211_VHT_CAP_SU_BEAMFORMER_CAPABLE)))
                cap &= ~IEEE80211_VHT_CAP_SU_BEAMFORMEE_CAPABLE;
 
-       if (!(ap_vht_cap->vht_cap_info &
-                       cpu_to_le32(IEEE80211_VHT_CAP_TXSTBC)))
-               cap &= ~(IEEE80211_VHT_CAP_RXSTBC_1 |
-                        IEEE80211_VHT_CAP_RXSTBC_3 |
-                        IEEE80211_VHT_CAP_RXSTBC_4);
-
-       for (i = 0; i < 8; i++) {
-               int shift = i * 2;
-               u16 mask = IEEE80211_VHT_MCS_NOT_SUPPORTED << shift;
-               u16 ap_mcs, our_mcs;
-
-               ap_mcs = (le16_to_cpu(ap_vht_cap->supp_mcs.tx_mcs_map) &
-                                                               mask) >> shift;
-               our_mcs = (le16_to_cpu(vht_cap.vht_mcs.rx_mcs_map) &
-                                                               mask) >> shift;
-
-               if (our_mcs == IEEE80211_VHT_MCS_NOT_SUPPORTED)
-                       continue;
-
-               switch (ap_mcs) {
-               default:
-                       if (our_mcs <= ap_mcs)
-                               break;
-                       /* fall through */
-               case IEEE80211_VHT_MCS_NOT_SUPPORTED:
-                       vht_cap.vht_mcs.rx_mcs_map &= cpu_to_le16(~mask);
-                       vht_cap.vht_mcs.rx_mcs_map |=
-                               cpu_to_le16(ap_mcs << shift);
-               }
-       }
-
        /* reserve and fill IE */
        pos = skb_put(skb, sizeof(struct ieee80211_vht_cap) + 2);
        ieee80211_ie_build_vht_cap(pos, &vht_cap, cap);
@@ -998,16 +966,7 @@ static void ieee80211_chswitch_work(struct work_struct *work)
        if (!ifmgd->associated)
                goto out;
 
-       /*
-        * FIXME: Here we are downgrading to NL80211_CHAN_WIDTH_20_NOHT
-        * and don't adjust our ht/vht settings
-        * This is wrong - we should behave according to the CSA params
-        */
-       local->_oper_chandef.chan = local->csa_channel;
-       local->_oper_chandef.width = NL80211_CHAN_WIDTH_20_NOHT;
-       local->_oper_chandef.center_freq1 =
-               local->_oper_chandef.chan->center_freq;
-       local->_oper_chandef.center_freq2 = 0;
+       local->_oper_chandef = local->csa_chandef;
 
        if (!local->ops->channel_switch) {
                /* call "hw_config" only if doing sw channel switch */
@@ -1054,56 +1013,193 @@ static void ieee80211_chswitch_timer(unsigned long data)
        ieee80211_queue_work(&sdata->local->hw, &sdata->u.mgd.chswitch_work);
 }
 
-void
+static 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)
+                                u64 timestamp, struct ieee802_11_elems *elems)
 {
-       struct cfg80211_bss *cbss =
-               container_of((void *)bss, struct cfg80211_bss, priv);
-       struct ieee80211_channel *new_ch;
+       struct ieee80211_local *local = sdata->local;
        struct ieee80211_if_managed *ifmgd = &sdata->u.mgd;
-       int new_freq = ieee80211_channel_to_frequency(sw_elem->new_ch_num,
-                                                     cbss->channel->band);
+       struct cfg80211_bss *cbss = ifmgd->associated;
+       struct ieee80211_bss *bss;
        struct ieee80211_chanctx *chanctx;
+       enum ieee80211_band new_band;
+       int new_freq;
+       u8 new_chan_no;
+       u8 count;
+       u8 mode;
+       struct ieee80211_channel *new_chan;
+       struct cfg80211_chan_def new_chandef = {};
+       struct cfg80211_chan_def new_vht_chandef = {};
+       const struct ieee80211_sec_chan_offs_ie *sec_chan_offs;
+       const struct ieee80211_wide_bw_chansw_ie *wide_bw_chansw_ie;
+       int secondary_channel_offset = -1;
 
        ASSERT_MGD_MTX(ifmgd);
 
-       if (!ifmgd->associated)
+       if (!cbss)
                return;
 
-       if (sdata->local->scanning)
+       if (local->scanning)
                return;
 
-       /* Disregard subsequent beacons if we are already running a timer
-          processing a CSA */
-
+       /* disregard subsequent announcements if we are already processing */
        if (ifmgd->flags & IEEE80211_STA_CSA_RECEIVED)
                return;
 
-       new_ch = ieee80211_get_channel(sdata->local->hw.wiphy, new_freq);
-       if (!new_ch || new_ch->flags & IEEE80211_CHAN_DISABLED) {
+       sec_chan_offs = elems->sec_chan_offs;
+       wide_bw_chansw_ie = elems->wide_bw_chansw_ie;
+
+       if (ifmgd->flags & (IEEE80211_STA_DISABLE_HT |
+                           IEEE80211_STA_DISABLE_40MHZ)) {
+               sec_chan_offs = NULL;
+               wide_bw_chansw_ie = NULL;
+       }
+
+       if (ifmgd->flags & IEEE80211_STA_DISABLE_VHT)
+               wide_bw_chansw_ie = NULL;
+
+       if (elems->ext_chansw_ie) {
+               if (!ieee80211_operating_class_to_band(
+                               elems->ext_chansw_ie->new_operating_class,
+                               &new_band)) {
+                       sdata_info(sdata,
+                                  "cannot understand ECSA IE operating class %d, disconnecting\n",
+                                  elems->ext_chansw_ie->new_operating_class);
+                       ieee80211_queue_work(&local->hw,
+                                            &ifmgd->csa_connection_drop_work);
+               }
+               new_chan_no = elems->ext_chansw_ie->new_ch_num;
+               count = elems->ext_chansw_ie->count;
+               mode = elems->ext_chansw_ie->mode;
+       } else if (elems->ch_switch_ie) {
+               new_band = cbss->channel->band;
+               new_chan_no = elems->ch_switch_ie->new_ch_num;
+               count = elems->ch_switch_ie->count;
+               mode = elems->ch_switch_ie->mode;
+       } else {
+               /* nothing here we understand */
+               return;
+       }
+
+       bss = (void *)cbss->priv;
+
+       new_freq = ieee80211_channel_to_frequency(new_chan_no, new_band);
+       new_chan = ieee80211_get_channel(sdata->local->hw.wiphy, new_freq);
+       if (!new_chan || new_chan->flags & IEEE80211_CHAN_DISABLED) {
                sdata_info(sdata,
                           "AP %pM switches to unsupported channel (%d MHz), disconnecting\n",
                           ifmgd->associated->bssid, new_freq);
-               ieee80211_queue_work(&sdata->local->hw,
+               ieee80211_queue_work(&local->hw,
+                                    &ifmgd->csa_connection_drop_work);
+               return;
+       }
+
+       if (sec_chan_offs) {
+               secondary_channel_offset = sec_chan_offs->sec_chan_offs;
+       } else if (!(ifmgd->flags & IEEE80211_STA_DISABLE_HT)) {
+               /* if HT is enabled and the IE not present, it's still HT */
+               secondary_channel_offset = IEEE80211_HT_PARAM_CHA_SEC_NONE;
+       }
+
+       switch (secondary_channel_offset) {
+       default:
+               /* secondary_channel_offset was present but is invalid */
+       case IEEE80211_HT_PARAM_CHA_SEC_NONE:
+               cfg80211_chandef_create(&new_chandef, new_chan,
+                                       NL80211_CHAN_HT20);
+               break;
+       case IEEE80211_HT_PARAM_CHA_SEC_ABOVE:
+               cfg80211_chandef_create(&new_chandef, new_chan,
+                                       NL80211_CHAN_HT40PLUS);
+               break;
+       case IEEE80211_HT_PARAM_CHA_SEC_BELOW:
+               cfg80211_chandef_create(&new_chandef, new_chan,
+                                       NL80211_CHAN_HT40MINUS);
+               break;
+       case -1:
+               cfg80211_chandef_create(&new_chandef, new_chan,
+                                       NL80211_CHAN_NO_HT);
+               break;
+       }
+
+       if (wide_bw_chansw_ie) {
+               new_vht_chandef.chan = new_chan;
+               new_vht_chandef.center_freq1 =
+                       ieee80211_channel_to_frequency(
+                               wide_bw_chansw_ie->new_center_freq_seg0,
+                               new_band);
+
+               switch (wide_bw_chansw_ie->new_channel_width) {
+               default:
+                       /* hmmm, ignore VHT and use HT if present */
+               case IEEE80211_VHT_CHANWIDTH_USE_HT:
+                       new_vht_chandef.chan = NULL;
+                       break;
+               case IEEE80211_VHT_CHANWIDTH_80MHZ:
+                       new_vht_chandef.width = NL80211_CHAN_WIDTH_80;
+                       break;
+               case IEEE80211_VHT_CHANWIDTH_160MHZ:
+                       new_vht_chandef.width = NL80211_CHAN_WIDTH_160;
+                       break;
+               case IEEE80211_VHT_CHANWIDTH_80P80MHZ:
+                       /* field is otherwise reserved */
+                       new_vht_chandef.center_freq2 =
+                               ieee80211_channel_to_frequency(
+                                       wide_bw_chansw_ie->new_center_freq_seg1,
+                                       new_band);
+                       new_vht_chandef.width = NL80211_CHAN_WIDTH_80P80;
+                       break;
+               }
+               if (ifmgd->flags & IEEE80211_STA_DISABLE_80P80MHZ &&
+                   new_vht_chandef.width == NL80211_CHAN_WIDTH_80P80)
+                       chandef_downgrade(&new_vht_chandef);
+               if (ifmgd->flags & IEEE80211_STA_DISABLE_160MHZ &&
+                   new_vht_chandef.width == NL80211_CHAN_WIDTH_160)
+                       chandef_downgrade(&new_vht_chandef);
+               if (ifmgd->flags & IEEE80211_STA_DISABLE_40MHZ &&
+                   new_vht_chandef.width > NL80211_CHAN_WIDTH_20)
+                       chandef_downgrade(&new_vht_chandef);
+       }
+
+       /* if VHT data is there validate & use it */
+       if (new_vht_chandef.chan) {
+               if (!cfg80211_chandef_compatible(&new_vht_chandef,
+                                                &new_chandef)) {
+                       sdata_info(sdata,
+                                  "AP %pM CSA has inconsistent channel data, disconnecting\n",
+                                  ifmgd->associated->bssid);
+                       ieee80211_queue_work(&local->hw,
+                                            &ifmgd->csa_connection_drop_work);
+                       return;
+               }
+               new_chandef = new_vht_chandef;
+       }
+
+       if (!cfg80211_chandef_usable(local->hw.wiphy, &new_chandef,
+                                    IEEE80211_CHAN_DISABLED)) {
+               sdata_info(sdata,
+                          "AP %pM switches to unsupported channel (%d MHz, width:%d, CF1/2: %d/%d MHz), disconnecting\n",
+                          ifmgd->associated->bssid, new_freq,
+                          new_chandef.width, new_chandef.center_freq1,
+                          new_chandef.center_freq2);
+               ieee80211_queue_work(&local->hw,
                                     &ifmgd->csa_connection_drop_work);
                return;
        }
 
        ifmgd->flags |= IEEE80211_STA_CSA_RECEIVED;
 
-       if (sdata->local->use_chanctx) {
+       if (local->use_chanctx) {
                sdata_info(sdata,
                           "not handling channel switch with channel contexts\n");
-               ieee80211_queue_work(&sdata->local->hw,
+               ieee80211_queue_work(&local->hw,
                                     &ifmgd->csa_connection_drop_work);
                return;
        }
 
-       mutex_lock(&sdata->local->chanctx_mtx);
+       mutex_lock(&local->chanctx_mtx);
        if (WARN_ON(!rcu_access_pointer(sdata->vif.chanctx_conf))) {
-               mutex_unlock(&sdata->local->chanctx_mtx);
+               mutex_unlock(&local->chanctx_mtx);
                return;
        }
        chanctx = container_of(rcu_access_pointer(sdata->vif.chanctx_conf),
@@ -1111,40 +1207,39 @@ ieee80211_sta_process_chanswitch(struct ieee80211_sub_if_data *sdata,
        if (chanctx->refcount > 1) {
                sdata_info(sdata,
                           "channel switch with multiple interfaces on the same channel, disconnecting\n");
-               ieee80211_queue_work(&sdata->local->hw,
+               ieee80211_queue_work(&local->hw,
                                     &ifmgd->csa_connection_drop_work);
-               mutex_unlock(&sdata->local->chanctx_mtx);
+               mutex_unlock(&local->chanctx_mtx);
                return;
        }
-       mutex_unlock(&sdata->local->chanctx_mtx);
+       mutex_unlock(&local->chanctx_mtx);
 
-       sdata->local->csa_channel = new_ch;
+       local->csa_chandef = new_chandef;
 
-       if (sw_elem->mode)
-               ieee80211_stop_queues_by_reason(&sdata->local->hw,
+       if (mode)
+               ieee80211_stop_queues_by_reason(&local->hw,
                                IEEE80211_MAX_QUEUE_MAP,
                                IEEE80211_QUEUE_STOP_REASON_CSA);
 
-       if (sdata->local->ops->channel_switch) {
+       if (local->ops->channel_switch) {
                /* use driver's channel switch callback */
                struct ieee80211_channel_switch ch_switch = {
                        .timestamp = timestamp,
-                       .block_tx = sw_elem->mode,
-                       .channel = new_ch,
-                       .count = sw_elem->count,
+                       .block_tx = mode,
+                       .chandef = new_chandef,
+                       .count = count,
                };
 
-               drv_channel_switch(sdata->local, &ch_switch);
+               drv_channel_switch(local, &ch_switch);
                return;
        }
 
        /* channel switch handled in software */
-       if (sw_elem->count <= 1)
-               ieee80211_queue_work(&sdata->local->hw, &ifmgd->chswitch_work);
+       if (count <= 1)
+               ieee80211_queue_work(&local->hw, &ifmgd->chswitch_work);
        else
                mod_timer(&ifmgd->chswitch_timer,
-                         TU_TO_EXP_TIME(sw_elem->count *
-                                        cbss->beacon_interval));
+                         TU_TO_EXP_TIME(count * cbss->beacon_interval));
 }
 
 static u32 ieee80211_handle_pwr_constr(struct ieee80211_sub_if_data *sdata,
@@ -2120,7 +2215,6 @@ void ieee80211_beacon_loss(struct ieee80211_vif *vif)
 
        trace_api_beacon_loss(sdata);
 
-       WARN_ON(hw->flags & IEEE80211_HW_CONNECTION_MONITOR);
        sdata->u.mgd.connection_loss = false;
        ieee80211_queue_work(hw, &sdata->u.mgd.beacon_connection_loss_work);
 }
@@ -2170,7 +2264,7 @@ static void ieee80211_auth_challenge(struct ieee80211_sub_if_data *sdata,
        u32 tx_flags = 0;
 
        pos = mgmt->u.auth.variable;
-       ieee802_11_parse_elems(pos, len - (pos - (u8 *) mgmt), &elems);
+       ieee802_11_parse_elems(pos, len - (pos - (u8 *) mgmt), false, &elems);
        if (!elems.challenge)
                return;
        auth_data->expected_transaction = 4;
@@ -2435,7 +2529,7 @@ static bool ieee80211_assoc_success(struct ieee80211_sub_if_data *sdata,
        }
 
        pos = mgmt->u.assoc_resp.variable;
-       ieee802_11_parse_elems(pos, len - (pos - (u8 *) mgmt), &elems);
+       ieee802_11_parse_elems(pos, len - (pos - (u8 *) mgmt), false, &elems);
 
        if (!elems.supp_rates) {
                sdata_info(sdata, "no SuppRates element in AssocResp\n");
@@ -2604,7 +2698,7 @@ ieee80211_rx_mgmt_assoc_resp(struct ieee80211_sub_if_data *sdata,
                   capab_info, status_code, (u16)(aid & ~(BIT(15) | BIT(14))));
 
        pos = mgmt->u.assoc_resp.variable;
-       ieee802_11_parse_elems(pos, len - (pos - (u8 *) mgmt), &elems);
+       ieee802_11_parse_elems(pos, len - (pos - (u8 *) mgmt), false, &elems);
 
        if (status_code == WLAN_STATUS_ASSOC_REJECTED_TEMPORARILY &&
            elems.timeout_int &&
@@ -2659,6 +2753,8 @@ static void ieee80211_rx_bss_info(struct ieee80211_sub_if_data *sdata,
        struct ieee80211_channel *channel;
        bool need_ps = false;
 
+       lockdep_assert_held(&sdata->u.mgd.mtx);
+
        if ((sdata->u.mgd.associated &&
             ether_addr_equal(mgmt->bssid, sdata->u.mgd.associated->bssid)) ||
            (sdata->u.mgd.assoc_data &&
@@ -2689,7 +2785,8 @@ static void ieee80211_rx_bss_info(struct ieee80211_sub_if_data *sdata,
        if (bss)
                ieee80211_rx_bss_put(local, bss);
 
-       if (!sdata->u.mgd.associated)
+       if (!sdata->u.mgd.associated ||
+           !ether_addr_equal(mgmt->bssid, sdata->u.mgd.associated->bssid))
                return;
 
        if (need_ps) {
@@ -2698,10 +2795,8 @@ static void ieee80211_rx_bss_info(struct ieee80211_sub_if_data *sdata,
                mutex_unlock(&local->iflist_mtx);
        }
 
-       if (elems->ch_switch_ie &&
-           memcmp(mgmt->bssid, sdata->u.mgd.associated->bssid, ETH_ALEN) == 0)
-               ieee80211_sta_process_chanswitch(sdata, elems->ch_switch_ie,
-                                                bss, rx_status->mactime);
+       ieee80211_sta_process_chanswitch(sdata, rx_status->mactime, elems);
+
 }
 
 
@@ -2726,7 +2821,7 @@ static void ieee80211_rx_mgmt_probe_resp(struct ieee80211_sub_if_data *sdata,
                return;
 
        ieee802_11_parse_elems(mgmt->u.probe_resp.variable, len - baselen,
-                               &elems);
+                              false, &elems);
 
        ieee80211_rx_bss_info(sdata, mgmt, len, rx_status, &elems);
 
@@ -2809,7 +2904,7 @@ ieee80211_rx_mgmt_beacon(struct ieee80211_sub_if_data *sdata,
        if (ifmgd->assoc_data && ifmgd->assoc_data->need_beacon &&
            ether_addr_equal(mgmt->bssid, ifmgd->assoc_data->bss->bssid)) {
                ieee802_11_parse_elems(mgmt->u.beacon.variable,
-                                      len - baselen, &elems);
+                                      len - baselen, false, &elems);
 
                ieee80211_rx_bss_info(sdata, mgmt, len, rx_status, &elems);
                ifmgd->assoc_data->have_beacon = true;
@@ -2919,7 +3014,7 @@ ieee80211_rx_mgmt_beacon(struct ieee80211_sub_if_data *sdata,
 
        ncrc = crc32_be(0, (void *)&mgmt->u.beacon.beacon_int, 4);
        ncrc = ieee802_11_parse_elems_crc(mgmt->u.beacon.variable,
-                                         len - baselen, &elems,
+                                         len - baselen, false, &elems,
                                          care_about_ies, ncrc);
 
        if (local->hw.flags & IEEE80211_HW_PS_NULLFUNC_STACK) {
@@ -3066,6 +3161,8 @@ void ieee80211_sta_rx_queued_mgmt(struct ieee80211_sub_if_data *sdata,
        enum rx_mgmt_action rma = RX_MGMT_NONE;
        u8 deauth_buf[IEEE80211_DEAUTH_FRAME_LEN];
        u16 fc;
+       struct ieee802_11_elems elems;
+       int ies_len;
 
        rx_status = (struct ieee80211_rx_status *) skb->cb;
        mgmt = (struct ieee80211_mgmt *) skb->data;
@@ -3095,14 +3192,48 @@ void ieee80211_sta_rx_queued_mgmt(struct ieee80211_sub_if_data *sdata,
                rma = ieee80211_rx_mgmt_assoc_resp(sdata, mgmt, skb->len, &bss);
                break;
        case IEEE80211_STYPE_ACTION:
-               switch (mgmt->u.action.category) {
-               case WLAN_CATEGORY_SPECTRUM_MGMT:
+               if (mgmt->u.action.category == WLAN_CATEGORY_SPECTRUM_MGMT) {
+                       ies_len = skb->len -
+                                 offsetof(struct ieee80211_mgmt,
+                                          u.action.u.chan_switch.variable);
+
+                       if (ies_len < 0)
+                               break;
+
+                       ieee802_11_parse_elems(
+                               mgmt->u.action.u.chan_switch.variable,
+                               ies_len, true, &elems);
+
+                       if (elems.parse_error)
+                               break;
+
                        ieee80211_sta_process_chanswitch(sdata,
-                                       &mgmt->u.action.u.chan_switch.sw_elem,
-                                       (void *)ifmgd->associated->priv,
-                                       rx_status->mactime);
-                       break;
+                                                        rx_status->mactime,
+                                                        &elems);
+               } else if (mgmt->u.action.category == WLAN_CATEGORY_PUBLIC) {
+                       ies_len = skb->len -
+                                 offsetof(struct ieee80211_mgmt,
+                                          u.action.u.ext_chan_switch.variable);
+
+                       if (ies_len < 0)
+                               break;
+
+                       ieee802_11_parse_elems(
+                               mgmt->u.action.u.ext_chan_switch.variable,
+                               ies_len, true, &elems);
+
+                       if (elems.parse_error)
+                               break;
+
+                       /* for the handling code pretend this was also an IE */
+                       elems.ext_chansw_ie =
+                               &mgmt->u.action.u.ext_chan_switch.data;
+
+                       ieee80211_sta_process_chanswitch(sdata,
+                                                        rx_status->mactime,
+                                                        &elems);
                }
+               break;
        }
        mutex_unlock(&ifmgd->mtx);