@@ -715,6 +715,17 @@ struct survey_info {
s8 noise;
};
+/**
+ * struct cfg80211_acs_params - ACS parameters
+ * @n_channels: total number of channels for ACS
+ * @channels: list of chan_def to run ACS on
+ */
+struct cfg80211_acs_params {
+ u32 n_channels;
+ /* keep last */
+ struct cfg80211_chan_def channels[0];
+};
+
#define CFG80211_MAX_WEP_KEYS 4
/**
@@ -3377,6 +3388,11 @@ struct cfg80211_pmsr_request {
* Statistics should be cumulative, currently no way to reset is provided.
* @start_pmsr: start peer measurement (e.g. FTM)
* @abort_pmsr: abort peer measurement
+ * @acs: run automatic channel selection offload measurement to find the best
+ * channel to start the AP on. Userspace provide a list of chan_defs, the
+ * driver should perform ACS on the provided list and select best channel.
+ * driver should call cfg80211_acs_result to complete this operation.
+ * (invoked with the wireless_dev mutex held)
*/
struct cfg80211_ops {
int (*suspend)(struct wiphy *wiphy, struct cfg80211_wowlan *wow);
@@ -3691,6 +3707,8 @@ struct cfg80211_ops {
struct cfg80211_pmsr_request *request);
void (*abort_pmsr)(struct wiphy *wiphy, struct wireless_dev *wdev,
struct cfg80211_pmsr_request *request);
+ int (*acs)(struct wiphy *wiphy, struct net_device *dev,
+ struct cfg80211_acs_params *params);
};
/*
@@ -4662,6 +4680,7 @@ struct wireless_dev {
unsigned long cac_start_time;
unsigned int cac_time_ms;
+ bool acs_started;
#ifdef CONFIG_CFG80211_WEXT
/* wext data */
struct {
@@ -6362,6 +6381,20 @@ void cfg80211_cac_event(struct net_device *netdev,
/**
+ * cfg80211_acs_result - ACS result notification
+ * @netdev: network device
+ * @chandef: chandef for the selected channel, NULL on unsuccessful operation
+ * @status: ACS status as specified in &enum nl80211_acs_status
+ * @gfp: context flags
+ *
+ * This function is called when ACS measurement is finished or aborted.
+ * This must be called to notify the completion of a ACS process.
+ */
+void cfg80211_acs_result(struct net_device *netdev,
+ const struct cfg80211_chan_def *chandef,
+ enum nl80211_acs_status status, gfp_t gfp);
+
+/**
* cfg80211_gtk_rekey_notify - notify userspace about driver rekeying
* @dev: network device
* @bssid: BSSID of AP (to avoid races)
@@ -1032,6 +1032,13 @@
* ht opmode or vht opmode changes using any of %NL80211_ATTR_SMPS_MODE,
* %NL80211_ATTR_CHANNEL_WIDTH,%NL80211_ATTR_NSS attributes with its
* address(specified in %NL80211_ATTR_MAC).
+ * @NL80211_CMD_ACS: For offloaded Automatic Channel Selection (ACS). Userspace
+ * sends this command before starting an AP, the kernel indicates this
+ * command after a channel was selected. When sent from userspace has
+ * attribute NL80211_ATTR_CHAN_DEF for a list of channels (chan_def).
+ * When sent from kernel has attributes NL80211_ATTR_ACS_STATUS which
+ * provides the operation status and NL80211_ATTR_WIPHY_FREQ and other
+ * chan_def attributes which describes the chosen channel.
*
* @NL80211_CMD_GET_FTM_RESPONDER_STATS: Retrieve FTM responder statistics, in
* the %NL80211_ATTR_FTM_RESPONDER_STATS attribute.
@@ -1277,6 +1284,7 @@ enum nl80211_commands {
NL80211_CMD_PEER_MEASUREMENT_START,
NL80211_CMD_PEER_MEASUREMENT_RESULT,
NL80211_CMD_PEER_MEASUREMENT_COMPLETE,
+ NL80211_CMD_ACS,
/* add new commands above here */
@@ -2273,6 +2281,13 @@ enum nl80211_commands {
* @NL80211_ATTR_HE_CAPABILITY: HE Capability information element (from
* association request when used with NL80211_CMD_NEW_STATION). Can be set
* only if %NL80211_STA_FLAG_WME is set.
+ * @NL80211_ATTR_ACS_STATUS: attribute in which kernel indicates ACS status
+ * as defined in &enum nl80211_acs_status
+ * @NL80211_ATTR_CHAN_DEF: attribute for nesting chan_def parameters
+ * as defined in &enum nl80211_ch_def_attr. The new attribute allows
+ * userspace to send a list of struct cfg80211_chan_def. For example, ACS
+ * command uses this attribute for sending a list of channels, for more
+ * details see &NL80211_CMD_ACS.
*
* @NL80211_ATTR_FTM_RESPONDER: nested attribute which user-space can include
* in %NL80211_CMD_START_AP or %NL80211_CMD_SET_BEACON for fine timing
@@ -2741,6 +2756,9 @@ enum nl80211_attrs {
NL80211_ATTR_PEER_MEASUREMENTS,
+ NL80211_ATTR_ACS_STATUS,
+ NL80211_ATTR_CHAN_DEF,
+
/* add attributes here, update the policy in nl80211.c */
__NL80211_ATTR_AFTER_LAST,
@@ -4548,6 +4566,35 @@ enum nl80211_packet_pattern_attr {
};
/**
+ * enum nl80211_ch_def_attr - channel def attribute
+ * @__NL80211_ATTR_CH_DEF_INVALID: invalid number for nested attribute
+ * @NL80211_ATTR_CH_DEF_FREQ: frequency of the selected channel in MHz,
+ * defines the channel together with the attributes
+ * %NL80211_ATTR_CH_DEF_WIDTH and if needed %NL80211_ATTR_CH_DEF_FREQ1 and
+ * %NL80211_ATTR_CH_DEF_FREQ2
+ * @NL80211_ATTR_CH_DEF_WIDTH: u32 attribute containing one of the values
+ * of &enum nl80211_chan_width, describing the channel width. See the
+ * documentation of the enum for more information.
+ * @NL80211_ATTR_CH_DEF_FREQ1: Center frequency of the first part of the
+ * channel, used for anything but 20 MHz bandwidth
+ * @NL80211_ATTR_CH_DEF_FREQ2: Center frequency of the second part of the
+ * channel, used only for 80+80 MHz bandwidth
+ * @__NL80211_ATTR_CH_DEF_AFTER_LAST: internal
+ * @NL80211_ATTR_CH_DEF_MAX: max attribute number
+ */
+enum nl80211_ch_def_attr {
+ __NL80211_ATTR_CH_DEF_INVALID,
+ NL80211_ATTR_CH_DEF_FREQ,
+ NL80211_ATTR_CH_DEF_WIDTH,
+ NL80211_ATTR_CH_DEF_FREQ1,
+ NL80211_ATTR_CH_DEF_FREQ2,
+
+ /* keep last */
+ __NL80211_ATTR_CH_DEF_AFTER_LAST,
+ NL80211_ATTR_CH_DEF_MAX = __NL80211_ATTR_CH_DEF_AFTER_LAST - 1
+};
+
+/**
* struct nl80211_pattern_support - packet pattern support information
* @max_patterns: maximum number of patterns supported
* @min_pattern_len: minimum length of each pattern
@@ -5308,6 +5355,8 @@ enum nl80211_feature_flags {
* able to rekey an in-use key correctly. Userspace must not rekey PTK keys
* if this flag is not set. Ignoring this can leak clear text packets and/or
* freeze the connection.
+ * @NL80211_EXT_FEATURE_ACS_OFFLOAD: The driver supports offload of ACS from
+ * a list of channels provided by the userspace.
*
* @NUM_NL80211_EXT_FEATURES: number of extended features.
* @MAX_NL80211_EXT_FEATURES: highest extended feature index.
@@ -5348,6 +5397,7 @@ enum nl80211_ext_feature_index {
NL80211_EXT_FEATURE_SCAN_MIN_PREQ_CONTENT,
NL80211_EXT_FEATURE_CAN_REPLACE_PTK0,
NL80211_EXT_FEATURE_ENABLE_FTM_RESPONDER,
+ NL80211_EXT_FEATURE_ACS_OFFLOAD,
/* add new features before the definition below */
NUM_NL80211_EXT_FEATURES,
@@ -5561,6 +5611,21 @@ enum nl80211_dfs_state {
};
/**
+ * enum nl80211_acs_status - ACS status
+ *
+ * status to be used to inform userspace about the result of the ACS offloaded
+ * measurement.
+ *
+ * @NL80211_ACS_SUCCESS: The ACS operation finished successfully
+ * @NL80211_ACS_FAILED: Failed to run the ACS. Userspace should choose a channel
+ * by itself.
+ */
+enum nl80211_acs_status {
+ NL80211_ACS_SUCCESS,
+ NL80211_ACS_FAILED,
+};
+
+/**
* enum enum nl80211_protocol_features - nl80211 protocol features
* @NL80211_PROTOCOL_FEATURE_SPLIT_WIPHY_DUMP: nl80211 supports splitting
* wiphy dumps (if requested by the application with the attribute
@@ -903,3 +903,34 @@ void cfg80211_cac_event(struct net_device *netdev,
nl80211_radar_notify(rdev, chandef, event, netdev, gfp);
}
EXPORT_SYMBOL(cfg80211_cac_event);
+
+void cfg80211_acs_result(struct net_device *netdev,
+ const struct cfg80211_chan_def *chandef,
+ enum nl80211_acs_status status, gfp_t gfp)
+{
+ struct wireless_dev *wdev = netdev->ieee80211_ptr;
+ struct wiphy *wiphy = wdev->wiphy;
+ struct cfg80211_registered_device *rdev = wiphy_to_rdev(wiphy);
+
+ if (WARN_ON(!wdev->acs_started))
+ return;
+
+ switch (status) {
+ case NL80211_ACS_SUCCESS:
+ if (!chandef) {
+ WARN_ON(1);
+ goto out;
+ }
+ case NL80211_ACS_FAILED:
+ break;
+ default:
+ WARN_ON(1);
+ goto out;
+ }
+ nl80211_acs_notify(rdev, chandef, status, netdev, gfp);
+
+out:
+ wdev->acs_started = false;
+ dev_put(netdev);
+}
+EXPORT_SYMBOL(cfg80211_acs_result);
@@ -557,6 +557,8 @@ static int validate_ie_attr(const struct nlattr *attr,
[NL80211_ATTR_PEER_MEASUREMENTS] =
NLA_POLICY_NESTED(NL80211_PMSR_FTM_REQ_ATTR_MAX,
nl80211_pmsr_attr_policy),
+
+ [NL80211_ATTR_CHAN_DEF] = { .type = NLA_NESTED },
};
/* policy for the key attributes */
@@ -697,6 +699,15 @@ static int validate_ie_attr(const struct nlattr *attr,
[NL80211_PKTPAT_OFFSET] = { .type = NLA_U32 },
};
+/* policy for channel attributes */
+static const struct nla_policy
+nl80211_ch_def_policy[NL80211_ATTR_CH_DEF_MAX + 1] = {
+ [NL80211_ATTR_CH_DEF_FREQ] = { .type = NLA_U32 },
+ [NL80211_ATTR_CH_DEF_WIDTH] = { .type = NLA_U32 },
+ [NL80211_ATTR_CH_DEF_FREQ1] = { .type = NLA_U32 },
+ [NL80211_ATTR_CH_DEF_FREQ2] = { .type = NLA_U32 },
+};
+
int nl80211_prepare_wdev_dump(struct netlink_callback *cb,
struct cfg80211_registered_device **rdev,
struct wireless_dev **wdev)
@@ -2561,6 +2572,66 @@ int nl80211_parse_chandef(struct cfg80211_registered_device *rdev,
return 0;
}
+static int
+nl80211_parse_chandef_new(struct cfg80211_registered_device *rdev,
+ struct genl_info *info, struct nlattr *channel,
+ struct cfg80211_chan_def *chandef)
+{
+ u32 control_freq;
+ struct nlattr *attrs[NL80211_ATTR_CH_DEF_MAX + 1];
+ int err;
+
+ if (!channel)
+ return -EINVAL;
+
+ err = nla_parse_nested(attrs, NL80211_ATTR_CH_DEF_MAX, channel,
+ nl80211_ch_def_policy, info->extack);
+ if (err)
+ return err;
+
+
+ if (!attrs[NL80211_ATTR_CH_DEF_FREQ])
+ return -EINVAL;
+
+ control_freq = nla_get_u32(attrs[NL80211_ATTR_CH_DEF_FREQ]);
+
+ chandef->chan = ieee80211_get_channel(&rdev->wiphy, control_freq);
+ chandef->width = NL80211_CHAN_WIDTH_20_NOHT;
+ chandef->center_freq1 = control_freq;
+ chandef->center_freq2 = 0;
+
+ /* Primary channel not allowed */
+ if (!chandef->chan || chandef->chan->flags & IEEE80211_CHAN_DISABLED)
+ return -EINVAL;
+
+ if (attrs[NL80211_ATTR_CH_DEF_WIDTH]) {
+ chandef->width =
+ nla_get_u32(attrs[NL80211_ATTR_CH_DEF_WIDTH]);
+ if (attrs[NL80211_ATTR_CH_DEF_FREQ1])
+ chandef->center_freq1 =
+ nla_get_u32(
+ attrs[NL80211_ATTR_CH_DEF_FREQ1]);
+ if (attrs[NL80211_ATTR_CH_DEF_FREQ2])
+ chandef->center_freq2 =
+ nla_get_u32(
+ attrs[NL80211_ATTR_CH_DEF_FREQ2]);
+ }
+
+ if (!cfg80211_chandef_valid(chandef))
+ return -EINVAL;
+
+ if (!cfg80211_chandef_usable(&rdev->wiphy, chandef,
+ IEEE80211_CHAN_DISABLED))
+ return -EINVAL;
+
+ if ((chandef->width == NL80211_CHAN_WIDTH_5 ||
+ chandef->width == NL80211_CHAN_WIDTH_10) &&
+ !(rdev->wiphy.flags & WIPHY_FLAG_SUPPORTS_5_10_MHZ))
+ return -EINVAL;
+
+ return 0;
+}
+
static int __nl80211_set_channel(struct cfg80211_registered_device *rdev,
struct net_device *dev,
struct genl_info *info)
@@ -13140,6 +13211,71 @@ static int nl80211_get_ftm_responder_stats(struct sk_buff *skb,
return -ENOBUFS;
}
+static int nl80211_acs(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_acs_params *request;
+ struct nlattr *attr;
+ struct wiphy *wiphy;
+ int ret, tmp, n_channels = 0, i = 0;
+
+ if (WARN_ON(wdev->acs_started))
+ return -EALREADY;
+
+ if (!rdev->ops->acs)
+ return -EOPNOTSUPP;
+
+ if (wdev->iftype != NL80211_IFTYPE_AP)
+ return -EOPNOTSUPP;
+
+ wiphy = &rdev->wiphy;
+
+ if (!info->attrs[NL80211_ATTR_CHAN_DEF])
+ return -EINVAL;
+
+ nla_for_each_nested(attr,
+ info->attrs[NL80211_ATTR_CHAN_DEF], tmp)
+ n_channels++;
+
+ if (!n_channels)
+ return -EINVAL;
+
+ request = kzalloc(sizeof(*request) +
+ sizeof(struct cfg80211_chan_def) * n_channels,
+ GFP_KERNEL);
+ if (!request)
+ return -ENOMEM;
+
+ nla_for_each_nested(attr,
+ info->attrs[NL80211_ATTR_CHAN_DEF], tmp) {
+ struct cfg80211_chan_def chandef;
+
+ ret = nl80211_parse_chandef_new(rdev, info, attr, &chandef);
+ if (ret)
+ goto out_free;
+
+ request->channels[i++] = chandef;
+ }
+ request->n_channels = i;
+
+ wdev->acs_started = true;
+ dev_hold(dev);
+
+ wdev_lock(wdev);
+ ret = rdev_acs(rdev, dev, request);
+ if (ret) {
+ wdev->acs_started = false;
+ dev_put(dev);
+ }
+ wdev_unlock(wdev);
+
+ out_free:
+ kfree(request);
+ return ret;
+}
+
#define NL80211_FLAG_NEED_WIPHY 0x01
#define NL80211_FLAG_NEED_NETDEV 0x02
#define NL80211_FLAG_NEED_RTNL 0x04
@@ -14066,6 +14202,15 @@ static void nl80211_post_doit(const struct genl_ops *ops, struct sk_buff *skb,
.internal_flags = NL80211_FLAG_NEED_WDEV_UP |
NL80211_FLAG_NEED_RTNL,
},
+ {
+ .cmd = NL80211_CMD_ACS,
+ .doit = nl80211_acs,
+ .policy = nl80211_policy,
+ .flags = GENL_UNS_ADMIN_PERM,
+ .internal_flags = NL80211_FLAG_NEED_NETDEV_UP |
+ NL80211_FLAG_NEED_RTNL,
+ },
+
};
static struct genl_family nl80211_fam __ro_after_init = {
@@ -15656,6 +15801,43 @@ void cfg80211_ch_switch_started_notify(struct net_device *dev,
nlmsg_free(msg);
}
+void
+nl80211_acs_notify(struct cfg80211_registered_device *rdev,
+ const struct cfg80211_chan_def *chandef,
+ enum nl80211_acs_status status,
+ struct net_device *netdev, gfp_t gfp)
+{
+ struct sk_buff *msg;
+ void *hdr;
+
+ msg = nlmsg_new(NLMSG_DEFAULT_SIZE, gfp);
+ if (!msg)
+ return;
+
+ hdr = nl80211hdr_put(msg, 0, 0, 0, NL80211_CMD_ACS);
+ if (!hdr) {
+ nlmsg_free(msg);
+ return;
+ }
+
+ if (nla_put_u32(msg, NL80211_ATTR_ACS_STATUS, status))
+ goto nla_put_failure;
+
+ if (nl80211_send_chandef(msg, chandef))
+ goto nla_put_failure;
+
+ genlmsg_end(msg, hdr);
+
+ genlmsg_multicast_netns(&nl80211_fam, wiphy_net(&rdev->wiphy), msg, 0,
+ NL80211_MCGRP_MLME, gfp);
+
+ return;
+
+nla_put_failure:
+ genlmsg_cancel(msg, hdr);
+ nlmsg_free(msg);
+}
+
void cfg80211_sta_opmode_change_notify(struct net_device *dev, const u8 *mac,
struct sta_opmode_info *sta_opmode,
gfp_t gfp)
@@ -119,6 +119,12 @@ int nl80211_send_mgmt(struct cfg80211_registered_device *rdev,
enum nl80211_radar_event event,
struct net_device *netdev, gfp_t gfp);
+void
+nl80211_acs_notify(struct cfg80211_registered_device *rdev,
+ const struct cfg80211_chan_def *chandef,
+ enum nl80211_acs_status status,
+ struct net_device *netdev, gfp_t gfp);
+
void nl80211_send_ap_stopped(struct wireless_dev *wdev);
void cfg80211_rdev_free_coalesce(struct cfg80211_registered_device *rdev);
@@ -1272,4 +1272,16 @@ static inline int rdev_del_pmk(struct cfg80211_registered_device *rdev,
trace_rdev_return_void(&rdev->wiphy);
}
+static inline int rdev_acs(struct cfg80211_registered_device *rdev,
+ struct net_device *dev,
+ struct cfg80211_acs_params *params)
+{
+ int ret;
+
+ trace_rdev_acs(&rdev->wiphy, dev, params);
+ ret = rdev->ops->acs(&rdev->wiphy, dev, params);
+ trace_rdev_return_int(&rdev->wiphy, ret);
+ return ret;
+}
+
#endif /* __CFG80211_RDEV_OPS */
@@ -2530,6 +2530,29 @@
TP_ARGS(wiphy, wdev, cookie)
);
+TRACE_EVENT(rdev_acs,
+ TP_PROTO(struct wiphy *wiphy, struct net_device *netdev,
+ struct cfg80211_acs_params *params),
+
+ TP_ARGS(wiphy, netdev, params),
+
+ TP_STRUCT__entry(
+ WIPHY_ENTRY
+ NETDEV_ENTRY
+ __field(u32, n_channels)
+ ),
+
+ TP_fast_assign(
+ WIPHY_ASSIGN;
+ NETDEV_ASSIGN;
+ __entry->n_channels = params->n_channels;
+ ),
+
+ TP_printk(WIPHY_PR_FMT ", " NETDEV_PR_FMT
+ ", num of channels: %u", WIPHY_PR_ARG, NETDEV_PR_ARG,
+ __entry->n_channels)
+);
+
/*************************************************************
* cfg80211 exported functions traces *
*************************************************************/
Add ACS offload to let the device select a channel for AP from a list of channels that the user-space provides. ACS can customize the method of AP channel selection and add parameters like traffic load, number of APs on channel and more. Change-Id: I18cb8460b9418512ac7ac9f76fe8f7f379f2478b Signed-off-by: Ahmad Masri <amasri@codeaurora.org> --- include/net/cfg80211.h | 33 ++++++++ include/uapi/linux/nl80211.h | 65 ++++++++++++++++ net/wireless/mlme.c | 31 ++++++++ net/wireless/nl80211.c | 182 +++++++++++++++++++++++++++++++++++++++++++ net/wireless/nl80211.h | 6 ++ net/wireless/rdev-ops.h | 12 +++ net/wireless/trace.h | 23 ++++++ 7 files changed, 352 insertions(+)