|
@@ -349,6 +349,11 @@ static const struct nla_policy nl80211_policy[NL80211_ATTR_MAX+1] = {
|
|
|
[NL80211_ATTR_IE_RIC] = { .type = NLA_BINARY,
|
|
|
.len = IEEE80211_MAX_DATA_LEN },
|
|
|
[NL80211_ATTR_PEER_AID] = { .type = NLA_U16 },
|
|
|
+ [NL80211_ATTR_CH_SWITCH_COUNT] = { .type = NLA_U32 },
|
|
|
+ [NL80211_ATTR_CH_SWITCH_BLOCK_TX] = { .type = NLA_FLAG },
|
|
|
+ [NL80211_ATTR_CSA_IES] = { .type = NLA_NESTED },
|
|
|
+ [NL80211_ATTR_CSA_C_OFF_BEACON] = { .type = NLA_U16 },
|
|
|
+ [NL80211_ATTR_CSA_C_OFF_PRESP] = { .type = NLA_U16 },
|
|
|
};
|
|
|
|
|
|
/* policy for the key attributes */
|
|
@@ -1422,6 +1427,8 @@ static int nl80211_send_wiphy(struct cfg80211_registered_device *dev,
|
|
|
if (state->split) {
|
|
|
CMD(crit_proto_start, CRIT_PROTOCOL_START);
|
|
|
CMD(crit_proto_stop, CRIT_PROTOCOL_STOP);
|
|
|
+ if (dev->wiphy.flags & WIPHY_FLAG_HAS_CHANNEL_SWITCH)
|
|
|
+ CMD(channel_switch, CHANNEL_SWITCH);
|
|
|
}
|
|
|
|
|
|
#ifdef CONFIG_NL80211_TESTMODE
|
|
@@ -5613,6 +5620,111 @@ static int nl80211_start_radar_detection(struct sk_buff *skb,
|
|
|
return err;
|
|
|
}
|
|
|
|
|
|
+static int nl80211_channel_switch(struct sk_buff *skb, struct genl_info *info)
|
|
|
+{
|
|
|
+ struct cfg80211_registered_device *rdev = info->user_ptr[0];
|
|
|
+ struct net_device *dev = info->user_ptr[1];
|
|
|
+ struct wireless_dev *wdev = dev->ieee80211_ptr;
|
|
|
+ struct cfg80211_csa_settings params;
|
|
|
+ /* csa_attrs is defined static to avoid waste of stack size - this
|
|
|
+ * function is called under RTNL lock, so this should not be a problem.
|
|
|
+ */
|
|
|
+ static struct nlattr *csa_attrs[NL80211_ATTR_MAX+1];
|
|
|
+ u8 radar_detect_width = 0;
|
|
|
+ int err;
|
|
|
+
|
|
|
+ if (!rdev->ops->channel_switch ||
|
|
|
+ !(rdev->wiphy.flags & WIPHY_FLAG_HAS_CHANNEL_SWITCH))
|
|
|
+ return -EOPNOTSUPP;
|
|
|
+
|
|
|
+ /* may add IBSS support later */
|
|
|
+ if (dev->ieee80211_ptr->iftype != NL80211_IFTYPE_AP &&
|
|
|
+ dev->ieee80211_ptr->iftype != NL80211_IFTYPE_P2P_GO)
|
|
|
+ return -EOPNOTSUPP;
|
|
|
+
|
|
|
+ memset(¶ms, 0, sizeof(params));
|
|
|
+
|
|
|
+ if (!info->attrs[NL80211_ATTR_WIPHY_FREQ] ||
|
|
|
+ !info->attrs[NL80211_ATTR_CH_SWITCH_COUNT])
|
|
|
+ return -EINVAL;
|
|
|
+
|
|
|
+ /* only important for AP, IBSS and mesh create IEs internally */
|
|
|
+ if (!info->attrs[NL80211_ATTR_CSA_IES])
|
|
|
+ return -EINVAL;
|
|
|
+
|
|
|
+ /* useless if AP is not running */
|
|
|
+ if (!wdev->beacon_interval)
|
|
|
+ return -EINVAL;
|
|
|
+
|
|
|
+ params.count = nla_get_u32(info->attrs[NL80211_ATTR_CH_SWITCH_COUNT]);
|
|
|
+
|
|
|
+ err = nl80211_parse_beacon(info->attrs, ¶ms.beacon_after);
|
|
|
+ if (err)
|
|
|
+ return err;
|
|
|
+
|
|
|
+ err = nla_parse_nested(csa_attrs, NL80211_ATTR_MAX,
|
|
|
+ info->attrs[NL80211_ATTR_CSA_IES],
|
|
|
+ nl80211_policy);
|
|
|
+ if (err)
|
|
|
+ return err;
|
|
|
+
|
|
|
+ err = nl80211_parse_beacon(csa_attrs, ¶ms.beacon_csa);
|
|
|
+ if (err)
|
|
|
+ return err;
|
|
|
+
|
|
|
+ if (!csa_attrs[NL80211_ATTR_CSA_C_OFF_BEACON])
|
|
|
+ return -EINVAL;
|
|
|
+
|
|
|
+ params.counter_offset_beacon =
|
|
|
+ nla_get_u16(csa_attrs[NL80211_ATTR_CSA_C_OFF_BEACON]);
|
|
|
+ if (params.counter_offset_beacon >= params.beacon_csa.tail_len)
|
|
|
+ return -EINVAL;
|
|
|
+
|
|
|
+ /* sanity check - counters should be the same */
|
|
|
+ if (params.beacon_csa.tail[params.counter_offset_beacon] !=
|
|
|
+ params.count)
|
|
|
+ return -EINVAL;
|
|
|
+
|
|
|
+ if (csa_attrs[NL80211_ATTR_CSA_C_OFF_PRESP]) {
|
|
|
+ params.counter_offset_presp =
|
|
|
+ nla_get_u16(csa_attrs[NL80211_ATTR_CSA_C_OFF_PRESP]);
|
|
|
+ if (params.counter_offset_presp >=
|
|
|
+ params.beacon_csa.probe_resp_len)
|
|
|
+ return -EINVAL;
|
|
|
+
|
|
|
+ if (params.beacon_csa.probe_resp[params.counter_offset_presp] !=
|
|
|
+ params.count)
|
|
|
+ return -EINVAL;
|
|
|
+ }
|
|
|
+
|
|
|
+ err = nl80211_parse_chandef(rdev, info, ¶ms.chandef);
|
|
|
+ if (err)
|
|
|
+ return err;
|
|
|
+
|
|
|
+ if (!cfg80211_reg_can_beacon(&rdev->wiphy, ¶ms.chandef))
|
|
|
+ return -EINVAL;
|
|
|
+
|
|
|
+ err = cfg80211_chandef_dfs_required(wdev->wiphy, ¶ms.chandef);
|
|
|
+ if (err < 0) {
|
|
|
+ return err;
|
|
|
+ } else if (err) {
|
|
|
+ radar_detect_width = BIT(params.chandef.width);
|
|
|
+ params.radar_required = true;
|
|
|
+ }
|
|
|
+
|
|
|
+ err = cfg80211_can_use_iftype_chan(rdev, wdev, wdev->iftype,
|
|
|
+ params.chandef.chan,
|
|
|
+ CHAN_MODE_SHARED,
|
|
|
+ radar_detect_width);
|
|
|
+ if (err)
|
|
|
+ return err;
|
|
|
+
|
|
|
+ if (info->attrs[NL80211_ATTR_CH_SWITCH_BLOCK_TX])
|
|
|
+ params.block_tx = true;
|
|
|
+
|
|
|
+ return rdev_channel_switch(rdev, dev, ¶ms);
|
|
|
+}
|
|
|
+
|
|
|
static int nl80211_send_bss(struct sk_buff *msg, struct netlink_callback *cb,
|
|
|
u32 seq, int flags,
|
|
|
struct cfg80211_registered_device *rdev,
|
|
@@ -9361,7 +9473,15 @@ static struct genl_ops nl80211_ops[] = {
|
|
|
.flags = GENL_ADMIN_PERM,
|
|
|
.internal_flags = NL80211_FLAG_NEED_WIPHY |
|
|
|
NL80211_FLAG_NEED_RTNL,
|
|
|
- }
|
|
|
+ },
|
|
|
+ {
|
|
|
+ .cmd = NL80211_CMD_CHANNEL_SWITCH,
|
|
|
+ .doit = nl80211_channel_switch,
|
|
|
+ .policy = nl80211_policy,
|
|
|
+ .flags = GENL_ADMIN_PERM,
|
|
|
+ .internal_flags = NL80211_FLAG_NEED_NETDEV_UP |
|
|
|
+ NL80211_FLAG_NEED_RTNL,
|
|
|
+ },
|
|
|
};
|
|
|
|
|
|
static struct genl_multicast_group nl80211_mlme_mcgrp = {
|