]> git.karo-electronics.de Git - karo-tx-linux.git/blobdiff - net/mac80211/mesh.c
Merge tag 'mac80211-next-for-davem-2017-06-13' of git://git.kernel.org/pub/scm/linux...
[karo-tx-linux.git] / net / mac80211 / mesh.c
index 737e1f082b0ddd55e03b30bf215a5fbb68943143..ad5d1cf391904d67cdad7da44044bc25cfc66a97 100644 (file)
@@ -690,6 +690,9 @@ ieee80211_mesh_build_beacon(struct ieee80211_if_mesh *ifmsh)
                   2 + sizeof(struct ieee80211_channel_sw_ie) +
                   /* Mesh Channel Switch Parameters */
                   2 + sizeof(struct ieee80211_mesh_chansw_params_ie) +
+                  /* Channel Switch Wrapper + Wide Bandwidth CSA IE */
+                  2 + 2 + sizeof(struct ieee80211_wide_bw_chansw_ie) +
+                  2 + sizeof(struct ieee80211_sec_chan_offs_ie) +
                   2 + 8 + /* supported rates */
                   2 + 3; /* DS params */
        tail_len = 2 + (IEEE80211_MAX_SUPP_RATES - 8) +
@@ -736,8 +739,12 @@ ieee80211_mesh_build_beacon(struct ieee80211_if_mesh *ifmsh)
        rcu_read_lock();
        csa = rcu_dereference(ifmsh->csa);
        if (csa) {
-               pos = skb_put(skb, 13);
-               memset(pos, 0, 13);
+               enum nl80211_channel_type ct;
+               struct cfg80211_chan_def *chandef;
+               int ie_len = 2 + sizeof(struct ieee80211_channel_sw_ie) +
+                            2 + sizeof(struct ieee80211_mesh_chansw_params_ie);
+
+               pos = skb_put_zero(skb, ie_len);
                *pos++ = WLAN_EID_CHANNEL_SWITCH;
                *pos++ = 3;
                *pos++ = 0x0;
@@ -760,6 +767,37 @@ ieee80211_mesh_build_beacon(struct ieee80211_if_mesh *ifmsh)
                pos += 2;
                put_unaligned_le16(ifmsh->pre_value, pos);
                pos += 2;
+
+               switch (csa->settings.chandef.width) {
+               case NL80211_CHAN_WIDTH_40:
+                       ie_len = 2 + sizeof(struct ieee80211_sec_chan_offs_ie);
+                       pos = skb_put_zero(skb, ie_len);
+
+                       *pos++ = WLAN_EID_SECONDARY_CHANNEL_OFFSET; /* EID */
+                       *pos++ = 1;                                 /* len */
+                       ct = cfg80211_get_chandef_type(&csa->settings.chandef);
+                       if (ct == NL80211_CHAN_HT40PLUS)
+                               *pos++ = IEEE80211_HT_PARAM_CHA_SEC_ABOVE;
+                       else
+                               *pos++ = IEEE80211_HT_PARAM_CHA_SEC_BELOW;
+                       break;
+               case NL80211_CHAN_WIDTH_80:
+               case NL80211_CHAN_WIDTH_80P80:
+               case NL80211_CHAN_WIDTH_160:
+                       /* Channel Switch Wrapper + Wide Bandwidth CSA IE */
+                       ie_len = 2 + 2 +
+                                sizeof(struct ieee80211_wide_bw_chansw_ie);
+                       pos = skb_put_zero(skb, ie_len);
+
+                       *pos++ = WLAN_EID_CHANNEL_SWITCH_WRAPPER; /* EID */
+                       *pos++ = 5;                               /* len */
+                       /* put sub IE */
+                       chandef = &csa->settings.chandef;
+                       ieee80211_ie_build_wide_bw_cs(pos, chandef);
+                       break;
+               default:
+                       break;
+               }
        }
        rcu_read_unlock();
 
@@ -916,6 +954,21 @@ void ieee80211_stop_mesh(struct ieee80211_sub_if_data *sdata)
        ieee80211_configure_filter(local);
 }
 
+static void ieee80211_mesh_csa_mark_radar(struct ieee80211_sub_if_data *sdata)
+{
+       int err;
+
+       /* if the current channel is a DFS channel, mark the channel as
+        * unavailable.
+        */
+       err = cfg80211_chandef_dfs_required(sdata->local->hw.wiphy,
+                                           &sdata->vif.bss_conf.chandef,
+                                           NL80211_IFTYPE_MESH_POINT);
+       if (err > 0)
+               cfg80211_radar_event(sdata->local->hw.wiphy,
+                                    &sdata->vif.bss_conf.chandef, GFP_ATOMIC);
+}
+
 static bool
 ieee80211_mesh_process_chnswitch(struct ieee80211_sub_if_data *sdata,
                                 struct ieee802_11_elems *elems, bool beacon)
@@ -933,19 +986,20 @@ ieee80211_mesh_process_chnswitch(struct ieee80211_sub_if_data *sdata,
        if (!sband)
                return false;
 
-       sta_flags = IEEE80211_STA_DISABLE_VHT;
+       sta_flags = 0;
        switch (sdata->vif.bss_conf.chandef.width) {
        case NL80211_CHAN_WIDTH_20_NOHT:
                sta_flags |= IEEE80211_STA_DISABLE_HT;
        case NL80211_CHAN_WIDTH_20:
                sta_flags |= IEEE80211_STA_DISABLE_40MHZ;
+       case NL80211_CHAN_WIDTH_40:
+               sta_flags |= IEEE80211_STA_DISABLE_VHT;
                break;
        default:
                break;
        }
 
        memset(&params, 0, sizeof(params));
-       memset(&csa_ie, 0, sizeof(csa_ie));
        err = ieee80211_parse_ch_switch_ie(sdata, elems, sband->band,
                                           sta_flags, sdata->vif.addr,
                                           &csa_ie);
@@ -954,11 +1008,19 @@ ieee80211_mesh_process_chnswitch(struct ieee80211_sub_if_data *sdata,
        if (err)
                return false;
 
+       /* Mark the channel unavailable if the reason for the switch is
+        * regulatory.
+        */
+       if (csa_ie.reason_code == WLAN_REASON_MESH_CHAN_REGULATORY)
+               ieee80211_mesh_csa_mark_radar(sdata);
+
        params.chandef = csa_ie.chandef;
        params.count = csa_ie.count;
 
        if (!cfg80211_chandef_usable(sdata->local->hw.wiphy, &params.chandef,
-                                    IEEE80211_CHAN_DISABLED)) {
+                                    IEEE80211_CHAN_DISABLED) ||
+           !cfg80211_reg_can_beacon(sdata->local->hw.wiphy, &params.chandef,
+                                    NL80211_IFTYPE_MESH_POINT)) {
                sdata_info(sdata,
                           "mesh STA %pM switches to unsupported channel (%d MHz, width:%d, CF1/2: %d/%d MHz), aborting\n",
                           sdata->vif.addr,
@@ -974,9 +1036,16 @@ ieee80211_mesh_process_chnswitch(struct ieee80211_sub_if_data *sdata,
                                            NL80211_IFTYPE_MESH_POINT);
        if (err < 0)
                return false;
-       if (err > 0)
-               /* TODO: DFS not (yet) supported */
+       if (err > 0 && !ifmsh->userspace_handles_dfs) {
+               sdata_info(sdata,
+                          "mesh STA %pM switches to channel requiring DFS (%d MHz, width:%d, CF1/2: %d/%d MHz), aborting\n",
+                          sdata->vif.addr,
+                          params.chandef.chan->center_freq,
+                          params.chandef.width,
+                          params.chandef.center_freq1,
+                          params.chandef.center_freq2);
                return false;
+       }
 
        params.radar_required = err;
 
@@ -1233,7 +1302,7 @@ static void mesh_rx_csa_frame(struct ieee80211_sub_if_data *sdata,
        pos = mgmt->u.action.u.chan_switch.variable;
        baselen = offsetof(struct ieee80211_mgmt,
                           u.action.u.chan_switch.variable);
-       ieee802_11_parse_elems(pos, len - baselen, false, &elems);
+       ieee802_11_parse_elems(pos, len - baselen, true, &elems);
 
        ifmsh->chsw_ttl = elems.mesh_chansw_params_ie->mesh_ttl;
        if (!--ifmsh->chsw_ttl)