|
@@ -2384,3 +2384,90 @@ bool ieee80211_smps_is_restrictive(enum ieee80211_smps_mode smps_mode_old,
|
|
|
|
|
|
return false;
|
|
|
}
|
|
|
+
|
|
|
+int ieee80211_send_action_csa(struct ieee80211_sub_if_data *sdata,
|
|
|
+ struct cfg80211_csa_settings *csa_settings)
|
|
|
+{
|
|
|
+ struct sk_buff *skb;
|
|
|
+ struct ieee80211_mgmt *mgmt;
|
|
|
+ struct ieee80211_local *local = sdata->local;
|
|
|
+ int freq;
|
|
|
+ int hdr_len = offsetof(struct ieee80211_mgmt, u.action.u.chan_switch) +
|
|
|
+ sizeof(mgmt->u.action.u.chan_switch);
|
|
|
+ u8 *pos;
|
|
|
+
|
|
|
+ if (sdata->vif.type != NL80211_IFTYPE_ADHOC &&
|
|
|
+ sdata->vif.type != NL80211_IFTYPE_MESH_POINT)
|
|
|
+ return -EOPNOTSUPP;
|
|
|
+
|
|
|
+ skb = dev_alloc_skb(local->tx_headroom + hdr_len +
|
|
|
+ 5 + /* channel switch announcement element */
|
|
|
+ 3 + /* secondary channel offset element */
|
|
|
+ 8); /* mesh channel switch parameters element */
|
|
|
+ if (!skb)
|
|
|
+ return -ENOMEM;
|
|
|
+
|
|
|
+ skb_reserve(skb, local->tx_headroom);
|
|
|
+ mgmt = (struct ieee80211_mgmt *)skb_put(skb, hdr_len);
|
|
|
+ memset(mgmt, 0, hdr_len);
|
|
|
+ mgmt->frame_control = cpu_to_le16(IEEE80211_FTYPE_MGMT |
|
|
|
+ IEEE80211_STYPE_ACTION);
|
|
|
+
|
|
|
+ eth_broadcast_addr(mgmt->da);
|
|
|
+ memcpy(mgmt->sa, sdata->vif.addr, ETH_ALEN);
|
|
|
+ if (ieee80211_vif_is_mesh(&sdata->vif)) {
|
|
|
+ memcpy(mgmt->bssid, sdata->vif.addr, ETH_ALEN);
|
|
|
+ } else {
|
|
|
+ struct ieee80211_if_ibss *ifibss = &sdata->u.ibss;
|
|
|
+ memcpy(mgmt->bssid, ifibss->bssid, ETH_ALEN);
|
|
|
+ }
|
|
|
+ mgmt->u.action.category = WLAN_CATEGORY_SPECTRUM_MGMT;
|
|
|
+ mgmt->u.action.u.chan_switch.action_code = WLAN_ACTION_SPCT_CHL_SWITCH;
|
|
|
+ pos = skb_put(skb, 5);
|
|
|
+ *pos++ = WLAN_EID_CHANNEL_SWITCH; /* EID */
|
|
|
+ *pos++ = 3; /* IE length */
|
|
|
+ *pos++ = csa_settings->block_tx ? 1 : 0; /* CSA mode */
|
|
|
+ freq = csa_settings->chandef.chan->center_freq;
|
|
|
+ *pos++ = ieee80211_frequency_to_channel(freq); /* channel */
|
|
|
+ *pos++ = csa_settings->count; /* count */
|
|
|
+
|
|
|
+ if (csa_settings->chandef.width == NL80211_CHAN_WIDTH_40) {
|
|
|
+ enum nl80211_channel_type ch_type;
|
|
|
+
|
|
|
+ skb_put(skb, 3);
|
|
|
+ *pos++ = WLAN_EID_SECONDARY_CHANNEL_OFFSET; /* EID */
|
|
|
+ *pos++ = 1; /* IE length */
|
|
|
+ ch_type = cfg80211_get_chandef_type(&csa_settings->chandef);
|
|
|
+ if (ch_type == NL80211_CHAN_HT40PLUS)
|
|
|
+ *pos++ = IEEE80211_HT_PARAM_CHA_SEC_ABOVE;
|
|
|
+ else
|
|
|
+ *pos++ = IEEE80211_HT_PARAM_CHA_SEC_BELOW;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (ieee80211_vif_is_mesh(&sdata->vif)) {
|
|
|
+ struct ieee80211_if_mesh *ifmsh = &sdata->u.mesh;
|
|
|
+ __le16 pre_value;
|
|
|
+
|
|
|
+ skb_put(skb, 8);
|
|
|
+ *pos++ = WLAN_EID_CHAN_SWITCH_PARAM; /* EID */
|
|
|
+ *pos++ = 6; /* IE length */
|
|
|
+ *pos++ = sdata->u.mesh.mshcfg.dot11MeshTTL; /* Mesh TTL */
|
|
|
+ *pos = 0x00; /* Mesh Flag: Tx Restrict, Initiator, Reason */
|
|
|
+ *pos |= WLAN_EID_CHAN_SWITCH_PARAM_INITIATOR;
|
|
|
+ *pos++ |= csa_settings->block_tx ?
|
|
|
+ WLAN_EID_CHAN_SWITCH_PARAM_TX_RESTRICT : 0x00;
|
|
|
+ put_unaligned_le16(WLAN_REASON_MESH_CHAN, pos); /* Reason Cd */
|
|
|
+ pos += 2;
|
|
|
+ if (!ifmsh->pre_value)
|
|
|
+ ifmsh->pre_value = 1;
|
|
|
+ else
|
|
|
+ ifmsh->pre_value++;
|
|
|
+ pre_value = cpu_to_le16(ifmsh->pre_value);
|
|
|
+ memcpy(pos, &pre_value, 2); /* Precedence Value */
|
|
|
+ pos += 2;
|
|
|
+ ifmsh->chsw_init = true;
|
|
|
+ }
|
|
|
+
|
|
|
+ ieee80211_tx_skb(sdata, skb);
|
|
|
+ return 0;
|
|
|
+}
|