diff --git a/include/net/cfg80211.h b/include/net/cfg80211.h
index ede7fcd..c3faa48 100644
--- a/include/net/cfg80211.h
+++ b/include/net/cfg80211.h
@@ -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)
diff --git a/include/uapi/linux/nl80211.h b/include/uapi/linux/nl80211.h
index 2b53c0e..b993b86f 100644
--- a/include/uapi/linux/nl80211.h
+++ b/include/uapi/linux/nl80211.h
@@ -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
diff --git a/net/wireless/mlme.c b/net/wireless/mlme.c
index 1615e50..ed36d91 100644
--- a/net/wireless/mlme.c
+++ b/net/wireless/mlme.c
@@ -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);
diff --git a/net/wireless/nl80211.c b/net/wireless/nl80211.c
index 5ec200e..bf25824 100644
--- a/net/wireless/nl80211.c
+++ b/net/wireless/nl80211.c
@@ -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)
diff --git a/net/wireless/nl80211.h b/net/wireless/nl80211.h
index 531c82d..e406592 100644
--- a/net/wireless/nl80211.h
+++ b/net/wireless/nl80211.h
@@ -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);
diff --git a/net/wireless/rdev-ops.h b/net/wireless/rdev-ops.h
index 5cb48d1..ebd9576 100644
--- a/net/wireless/rdev-ops.h
+++ b/net/wireless/rdev-ops.h
@@ -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 */
diff --git a/net/wireless/trace.h b/net/wireless/trace.h
index 44b2ce1..c841873 100644
--- a/net/wireless/trace.h
+++ b/net/wireless/trace.h
@@ -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		     *
  *************************************************************/
