Browse Source

qcacld-3.0: Implement P2P listen offload feature

To lower the power consumption caused by the P2P listen operation,
it is offloaded from supplicant and host driver to firmware,
so that supplicant and host driver can be in power save mode during the
listen operation.

Change-Id: Iedd990427afe22842faecc7b29e4f38220c75684
CRs-fixed: 1032877
Peng Xu 8 years ago
parent
commit
8fdaa49618

+ 268 - 0
core/hdd/src/wlan_hdd_cfg80211.c

@@ -1076,6 +1076,11 @@ static const struct nl80211_vendor_cmd_info wlan_hdd_cfg80211_vendor_events[] =
 		.subcmd = QCA_NL80211_VENDOR_SUBCMD_NDP
 	},
 #endif /* WLAN_FEATURE_NAN_DATAPATH */
+
+	[QCA_NL80211_VENDOR_SUBCMD_P2P_LO_EVENT_INDEX] = {
+		.vendor_id = QCA_NL80211_VENDOR_ID,
+		.subcmd = QCA_NL80211_VENDOR_SUBCMD_P2P_LISTEN_OFFLOAD_STOP
+	},
 };
 
 /**
@@ -2173,6 +2178,11 @@ __wlan_hdd_cfg80211_get_features(struct wiphy *wiphy,
 	if (wma_is_scan_simultaneous_capable())
 		wlan_hdd_cfg80211_set_feature(feature_flags,
 			QCA_WLAN_VENDOR_FEATURE_OFFCHANNEL_SIMULTANEOUS);
+
+	if (wma_is_p2p_lo_capable())
+		wlan_hdd_cfg80211_set_feature(feature_flags,
+			QCA_WLAN_VENDOR_FEATURE_P2P_LISTEN_OFFLOAD);
+
 	skb = cfg80211_vendor_cmd_alloc_reply_skb(wiphy, sizeof(feature_flags) +
 			NLMSG_HDRLEN);
 
@@ -4982,6 +4992,247 @@ static int wlan_hdd_cfg80211_txpower_scale_decr_db(struct wiphy *wiphy,
 
 	return ret;
 }
+
+/**
+ * __wlan_hdd_cfg80211_p2p_lo_start () - start P2P Listen Offload
+ * @wiphy: Pointer to wireless phy
+ * @wdev: Pointer to wireless device
+ * @data: Pointer to data
+ * @data_len: Data length
+ *
+ * This function is to process the p2p listen offload start vendor
+ * command. It parses the input parameters and invoke WMA API to
+ * send the command to firmware.
+ *
+ * Return: 0 on success, negative errno on failure
+ */
+static int __wlan_hdd_cfg80211_p2p_lo_start(struct wiphy *wiphy,
+						struct wireless_dev *wdev,
+						const void *data,
+						int data_len)
+{
+	int ret;
+	hdd_context_t *hdd_ctx = wiphy_priv(wiphy);
+	struct net_device *dev = wdev->netdev;
+	hdd_adapter_t *adapter;
+	struct nlattr *tb[QCA_WLAN_VENDOR_ATTR_P2P_LISTEN_OFFLOAD_MAX + 1];
+	struct sir_p2p_lo_start params;
+	QDF_STATUS status;
+
+	ENTER_DEV(dev);
+
+	ret = wlan_hdd_validate_context(hdd_ctx);
+	if (ret)
+		return ret;
+
+	if (QDF_GLOBAL_FTM_MODE == hdd_get_conparam()) {
+		hdd_err("Command not allowed in FTM mode");
+		return -EPERM;
+	}
+
+	adapter = WLAN_HDD_GET_PRIV_PTR(dev);
+	if ((adapter->device_mode != QDF_P2P_DEVICE_MODE) &&
+	    (adapter->device_mode != QDF_P2P_CLIENT_MODE) &&
+	    (adapter->device_mode != QDF_P2P_GO_MODE)) {
+		hdd_err("Invalid device mode %d", adapter->device_mode);
+		return -EINVAL;
+	}
+
+	if (nla_parse(tb, QCA_WLAN_VENDOR_ATTR_P2P_LISTEN_OFFLOAD_MAX,
+		      data, data_len, NULL)) {
+		hdd_err("Invalid ATTR");
+		return -EINVAL;
+	}
+
+	memset(&params, 0, sizeof(params));
+
+	if (!tb[QCA_WLAN_VENDOR_ATTR_P2P_LISTEN_OFFLOAD_CTRL_FLAG])
+		params.ctl_flags = 1;  /* set to default value */
+	else
+		params.ctl_flags = nla_get_u32(tb
+			[QCA_WLAN_VENDOR_ATTR_P2P_LISTEN_OFFLOAD_CTRL_FLAG]);
+
+	if (!tb[QCA_WLAN_VENDOR_ATTR_P2P_LISTEN_OFFLOAD_CHANNEL] ||
+	    !tb[QCA_WLAN_VENDOR_ATTR_P2P_LISTEN_OFFLOAD_PERIOD] ||
+	    !tb[QCA_WLAN_VENDOR_ATTR_P2P_LISTEN_OFFLOAD_INTERVAL] ||
+	    !tb[QCA_WLAN_VENDOR_ATTR_P2P_LISTEN_OFFLOAD_COUNT] ||
+	    !tb[QCA_WLAN_VENDOR_ATTR_P2P_LISTEN_OFFLOAD_DEVICE_TYPES] ||
+	    !tb[QCA_WLAN_VENDOR_ATTR_P2P_LISTEN_OFFLOAD_VENDOR_IE]) {
+		hdd_err("Attribute parsing failed");
+		return -EINVAL;
+	}
+
+	params.vdev_id = adapter->sessionId;
+	params.freq = nla_get_u32(tb
+		[QCA_WLAN_VENDOR_ATTR_P2P_LISTEN_OFFLOAD_CHANNEL]);
+	if ((params.freq != 2412) && (params.freq != 2437) &&
+		(params.freq != 2462)) {
+		hdd_err("Invalid listening channel: %d", params.freq);
+		return -EINVAL;
+	}
+
+	params.period = nla_get_u32(tb
+		[QCA_WLAN_VENDOR_ATTR_P2P_LISTEN_OFFLOAD_PERIOD]);
+	if (!((params.period > 0) && (params.period < UINT_MAX))) {
+		hdd_err("Invalid period: %d", params.period);
+		return -EINVAL;
+	}
+
+	params.interval = nla_get_u32(tb
+		[QCA_WLAN_VENDOR_ATTR_P2P_LISTEN_OFFLOAD_INTERVAL]);
+	if (!((params.interval > 0) && (params.interval < UINT_MAX))) {
+		hdd_err("Invalid interval: %d", params.interval);
+		return -EINVAL;
+	}
+
+	params.count = nla_get_u32(tb
+		[QCA_WLAN_VENDOR_ATTR_P2P_LISTEN_OFFLOAD_COUNT]);
+	if (!((params.count > 0) && (params.count < UINT_MAX))) {
+		hdd_err("Invalid count: %d", params.count);
+		return -EINVAL;
+	}
+
+	params.device_types = nla_data(tb
+		[QCA_WLAN_VENDOR_ATTR_P2P_LISTEN_OFFLOAD_DEVICE_TYPES]);
+	if (params.device_types == NULL) {
+		hdd_err("Invalid device types");
+		return -EINVAL;
+	}
+
+	params.dev_types_len = nla_len(tb
+		[QCA_WLAN_VENDOR_ATTR_P2P_LISTEN_OFFLOAD_DEVICE_TYPES]);
+	if (params.dev_types_len < 8) {
+		hdd_err("Invalid device type length: %d", params.dev_types_len);
+		return -EINVAL;
+	}
+
+	params.probe_resp_tmplt = nla_data(tb
+		[QCA_WLAN_VENDOR_ATTR_P2P_LISTEN_OFFLOAD_VENDOR_IE]);
+	if (params.probe_resp_tmplt == NULL) {
+		hdd_err("Invalid probe response template");
+		return -EINVAL;
+	}
+
+	params.probe_resp_len = nla_len(tb
+		[QCA_WLAN_VENDOR_ATTR_P2P_LISTEN_OFFLOAD_VENDOR_IE]);
+	if (params.probe_resp_len == 0) {
+		hdd_err("Invalid probe resp template length: %d",
+				params.probe_resp_len);
+		return -EINVAL;
+	}
+
+	hdd_debug("P2P LO params: freq=%d, period=%d, interval=%d, count=%d",
+		  params.freq, params.period, params.interval, params.count);
+
+	status = wma_p2p_lo_start(&params);
+
+	if (!QDF_IS_STATUS_SUCCESS(status)) {
+		hdd_err("P2P LO start failed");
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+
+/**
+ * wlan_hdd_cfg80211_p2p_lo_start () - start P2P Listen Offload
+ * @wiphy: Pointer to wireless phy
+ * @wdev: Pointer to wireless device
+ * @data: Pointer to data
+ * @data_len: Data length
+ *
+ * This function inovkes internal __wlan_hdd_cfg80211_p2p_lo_start()
+ * to process p2p listen offload start vendor command.
+ *
+ * Return: 0 on success, negative errno on failure
+ */
+static int wlan_hdd_cfg80211_p2p_lo_start(struct wiphy *wiphy,
+						struct wireless_dev *wdev,
+						const void *data,
+						int data_len)
+{
+	int ret = 0;
+
+	cds_ssr_protect(__func__);
+	ret = __wlan_hdd_cfg80211_p2p_lo_start(wiphy, wdev,
+					data, data_len);
+	cds_ssr_unprotect(__func__);
+
+	return ret;
+}
+
+/**
+ * __wlan_hdd_cfg80211_p2p_lo_stop () - stop P2P Listen Offload
+ * @wiphy: Pointer to wireless phy
+ * @wdev: Pointer to wireless device
+ * @data: Pointer to data
+ * @data_len: Data length
+ *
+ * This function is to process the p2p listen offload stop vendor
+ * command. It invokes WMA API to send command to firmware.
+ *
+ * Return: 0 on success, negative errno on failure
+ */
+static int __wlan_hdd_cfg80211_p2p_lo_stop(struct wiphy *wiphy,
+						struct wireless_dev *wdev,
+						const void *data,
+						int data_len)
+{
+	QDF_STATUS status;
+	hdd_adapter_t *adapter;
+	struct net_device *dev = wdev->netdev;
+
+	if (QDF_GLOBAL_FTM_MODE == hdd_get_conparam()) {
+		hdd_err("Command not allowed in FTM mode");
+		return -EPERM;
+	}
+
+	adapter = WLAN_HDD_GET_PRIV_PTR(dev);
+	if ((adapter->device_mode != QDF_P2P_DEVICE_MODE) &&
+	    (adapter->device_mode != QDF_P2P_CLIENT_MODE) &&
+	    (adapter->device_mode != QDF_P2P_GO_MODE)) {
+		hdd_err("Invalid device mode");
+		return -EINVAL;
+	}
+
+	status = wma_p2p_lo_stop(adapter->sessionId);
+
+	if (!QDF_IS_STATUS_SUCCESS(status)) {
+		hdd_err("P2P LO stop failed");
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+/**
+ * wlan_hdd_cfg80211_p2p_lo_stop () - stop P2P Listen Offload
+ * @wiphy: Pointer to wireless phy
+ * @wdev: Pointer to wireless device
+ * @data: Pointer to data
+ * @data_len: Data length
+ *
+ * This function inovkes internal __wlan_hdd_cfg80211_p2p_lo_stop()
+ * to process p2p listen offload stop vendor command.
+ *
+ * Return: 0 on success, negative errno on failure
+ */
+static int wlan_hdd_cfg80211_p2p_lo_stop(struct wiphy *wiphy,
+						struct wireless_dev *wdev,
+						const void *data,
+						int data_len)
+{
+	int ret = 0;
+
+	cds_ssr_protect(__func__);
+	ret = __wlan_hdd_cfg80211_p2p_lo_stop(wiphy, wdev,
+						data, data_len);
+	cds_ssr_unprotect(__func__);
+
+	return ret;
+}
+
 /*
  * define short names for the global vendor params
  * used by __wlan_hdd_cfg80211_bpf_offload()
@@ -5967,6 +6218,23 @@ const struct wiphy_vendor_command hdd_wiphy_vendor_commands[] = {
 			WIPHY_VENDOR_CMD_NEED_RUNNING,
 		.doit = wlan_hdd_cfg80211_sap_configuration_set
 	},
+	{
+		.info.subcmd =
+			QCA_NL80211_VENDOR_SUBCMD_P2P_LISTEN_OFFLOAD_START,
+		.flags = WIPHY_VENDOR_CMD_NEED_WDEV |
+				WIPHY_VENDOR_CMD_NEED_NETDEV |
+				WIPHY_VENDOR_CMD_NEED_RUNNING,
+		.doit = wlan_hdd_cfg80211_p2p_lo_start
+	},
+	{
+		.info.vendor_id = QCA_NL80211_VENDOR_ID,
+		.info.subcmd =
+			QCA_NL80211_VENDOR_SUBCMD_P2P_LISTEN_OFFLOAD_STOP,
+		.flags = WIPHY_VENDOR_CMD_NEED_WDEV |
+				WIPHY_VENDOR_CMD_NEED_NETDEV |
+				WIPHY_VENDOR_CMD_NEED_RUNNING,
+		.doit = wlan_hdd_cfg80211_p2p_lo_stop
+	},
 
 #ifdef WLAN_FEATURE_NAN_DATAPATH
 	{

+ 62 - 0
core/hdd/src/wlan_hdd_cfg80211.h

@@ -261,6 +261,14 @@ typedef enum {
  * @QCA_NL80211_VENDOR_SUBCMD_SET_SAP_CONFIG: SAP configuration
  * @QCA_NL80211_VENDOR_SUBCMD_TSF: TSF operations command
  * @QCA_NL80211_VENDOR_SUBCMD_WISA: WISA mode configuration
+ * @QCA_NL80211_VENDOR_SUBCMD_P2P_LISTEN_OFFLOAD_START: Command used to
+ *	start the P2P Listen Offload function in device and pass the listen
+ *	channel, period, interval, count, number of device types, device
+ *	types and vendor information elements to device driver and firmware.
+ * @QCA_NL80211_VENDOR_SUBCMD_P2P_LISTEN_OFFLOAD_STOP: Command/event used to
+ *	indicate stop request/response of the P2P Listen Offload function in
+ *	device. As an event, it indicates either the feature stopped after it
+ *	was already running or feature has actually failed to start.
  */
 
 enum qca_nl80211_vendor_subcmds {
@@ -384,6 +392,8 @@ enum qca_nl80211_vendor_subcmds {
 	QCA_NL80211_VENDOR_SUBCMD_SET_SAP_CONFIG  = 118,
 	QCA_NL80211_VENDOR_SUBCMD_TSF = 119,
 	QCA_NL80211_VENDOR_SUBCMD_WISA = 120,
+	QCA_NL80211_VENDOR_SUBCMD_P2P_LISTEN_OFFLOAD_START = 121,
+	QCA_NL80211_VENDOR_SUBCMD_P2P_LISTEN_OFFLOAD_STOP = 122,
 };
 
 /**
@@ -445,6 +455,8 @@ enum qca_nl80211_vendor_subcmds {
  * @QCA_NL80211_VENDOR_SUBCMD_GW_PARAM_CONFIG_INDEX:
  *	update gateway parameters index
  * @QCA_NL80211_VENDOR_SUBCMD_TSF_INDEX: TSF response events index
+ * @QCA_NL80211_VENDOR_SUBCMD_P2P_LO_EVENT_INDEX:
+ *      P2P listen offload index
  */
 
 enum qca_nl80211_vendor_subcmds_index {
@@ -520,6 +532,7 @@ enum qca_nl80211_vendor_subcmds_index {
 #ifdef WLAN_FEATURE_NAN_DATAPATH
 	QCA_NL80211_VENDOR_SUBCMD_NDP_INDEX,
 #endif /* WLAN_FEATURE_NAN_DATAPATH */
+	QCA_NL80211_VENDOR_SUBCMD_P2P_LO_EVENT_INDEX,
 };
 
 /**
@@ -1900,11 +1913,19 @@ enum qca_wlan_vendor_attr_link_properties {
  * after roaming, rather than having the supplicant do it.
  * @QCA_WLAN_VENDOR_FEATURE_OFFCHANNEL_SIMULTANEOUS: Device supports
  *        simultaneous off-channel operations.
+ * @QQCA_WLAN_VENDOR_FEATURE_P2P_LISTEN_OFFLOAD: Device supports P2P
+ *	Listen offload; a mechanism where the station's firmware
+ *	takes care of responding to incoming Probe Request frames received
+ *	from other P2P devices whilst in Listen state, rather than having the
+ *	user space wpa_supplicant do it. Information from received P2P
+ *	Requests are forwarded from firmware to host whenever the APPS
+ *	processor exits power collapse state.
  */
 enum qca_wlan_vendor_features {
 	QCA_WLAN_VENDOR_FEATURE_KEY_MGMT_OFFLOAD = 0,
 	QCA_WLAN_VENDOR_FEATURE_SUPPORT_HW_MODE_ANY = 1,
 	QCA_WLAN_VENDOR_FEATURE_OFFCHANNEL_SIMULTANEOUS = 2,
+	QCA_WLAN_VENDOR_FEATURE_P2P_LISTEN_OFFLOAD	= 3,
 	/* Additional features need to be added above this */
 	NUM_QCA_WLAN_VENDOR_FEATURES
 };
@@ -2385,6 +2406,47 @@ enum qca_wlan_vendor_attr_sap_config {
 	QCA_WLAN_VENDOR_ATTR_SAP_CONFIG_AFTER_LAST - 1,
 };
 
+/**
+ * enum qca_wlan_vendor_attr_p2p_listen_offload - vendor sub commands index
+ * @QCA_WLAN_VENDOR_ATTR_P2P_LISTEN_OFFLOAD_INVALID: invalid value
+ * @QCA_WLAN_VENDOR_ATTR_P2P_LISTEN_OFFLOAD_CHANNEL:
+ *     A 32-bit unsigned value; the P2P listen frequency (MHz); must be one
+ *     of the social channels.
+ * @QCA_WLAN_VENDOR_ATTR_P2P_LISTEN_OFFLOAD_PERIOD: listen offload period
+ *     A 32-bit unsigned value; the P2P listen offload period (ms).
+ * @QCA_WLAN_VENDOR_ATTR_P2P_LISTEN_OFFLOAD_INTERVAL:
+ *     A 32-bit unsigned value; the P2P listen interval duration (ms).
+ * @QCA_WLAN_VENDOR_ATTR_P2P_LISTEN_OFFLOAD_COUNT:
+ *     A 32-bit unsigned value; number of interval times the Firmware needs
+ *     to run the offloaded P2P listen operation before it stops.
+ * @QCA_WLAN_VENDOR_ATTR_P2P_LISTEN_OFFLOAD_DEVICE_TYPES: device types
+ *     An array of unsigned 8-bit characters; vendor information elements.
+ * @QCA_WLAN_VENDOR_ATTR_P2P_LISTEN_OFFLOAD_VENDOR_IE: vendor IEs
+ * @QCA_WLAN_VENDOR_ATTR_P2P_LISTEN_OFFLOAD_CTRL_FLAG: control flag for FW
+ *     A 32-bit unsigned value; a control flag to indicate whether listen
+ *     results need to be flushed to wpa_supplicant.
+ * @QCA_WLAN_VENDOR_ATTR_P2P_LISTEN_OFFLOAD_STOP_REASON: offload stop reason
+ *     A 8-bit unsigned value; reason code for P2P listen offload stop
+ *     event.
+ * @QCA_WLAN_VENDOR_ATTR_P2P_LISTEN_OFFLOAD_AFTER_LAST: last value
+ * @QCA_WLAN_VENDOR_ATTR_P2P_LISTEN_OFFLOAD_MAX: max value
+ */
+enum qca_wlan_vendor_attr_p2p_listen_offload {
+	QCA_WLAN_VENDOR_ATTR_P2P_LISTEN_OFFLOAD_INVALID = 0,
+	QCA_WLAN_VENDOR_ATTR_P2P_LISTEN_OFFLOAD_CHANNEL,
+	QCA_WLAN_VENDOR_ATTR_P2P_LISTEN_OFFLOAD_PERIOD,
+	QCA_WLAN_VENDOR_ATTR_P2P_LISTEN_OFFLOAD_INTERVAL,
+	QCA_WLAN_VENDOR_ATTR_P2P_LISTEN_OFFLOAD_COUNT,
+	QCA_WLAN_VENDOR_ATTR_P2P_LISTEN_OFFLOAD_DEVICE_TYPES,
+	QCA_WLAN_VENDOR_ATTR_P2P_LISTEN_OFFLOAD_VENDOR_IE,
+	QCA_WLAN_VENDOR_ATTR_P2P_LISTEN_OFFLOAD_CTRL_FLAG,
+	QCA_WLAN_VENDOR_ATTR_P2P_LISTEN_OFFLOAD_STOP_REASON,
+	/* keep last */
+	QCA_WLAN_VENDOR_ATTR_P2P_LISTEN_OFFLOAD_AFTER_LAST,
+	QCA_WLAN_VENDOR_ATTR_P2P_LISTEN_OFFLOAD_MAX =
+	QCA_WLAN_VENDOR_ATTR_P2P_LISTEN_OFFLOAD_AFTER_LAST - 1
+};
+
 struct cfg80211_bss *wlan_hdd_cfg80211_update_bss_db(hdd_adapter_t *pAdapter,
 						tCsrRoamInfo *pRoamInfo);
 

+ 51 - 0
core/hdd/src/wlan_hdd_main.c

@@ -6442,6 +6442,52 @@ out:
 	return ret;
 }
 
+/**
+ * wlan_hdd_p2p_lo_event_callback - P2P listen offload stop event handler
+ * @context_ptr - hdd context pointer
+ * @event_ptr - event structure pointer
+ *
+ * This is the p2p listen offload stop event handler, it sends vendor
+ * event back to supplicant to notify the stop reason.
+ *
+ * Return: None
+ */
+static void wlan_hdd_p2p_lo_event_callback(void *context_ptr,
+				void *event_ptr)
+{
+	hdd_context_t *hdd_ctx = (hdd_context_t *)context_ptr;
+	struct sir_p2p_lo_event *evt = event_ptr;
+	struct sk_buff *vendor_event;
+
+	ENTER();
+
+	if (hdd_ctx == NULL) {
+		hdd_err("Invalid HDD context pointer");
+		return;
+	}
+
+	vendor_event =
+		cfg80211_vendor_event_alloc(hdd_ctx->wiphy,
+			NULL, sizeof(uint32_t) + NLMSG_HDRLEN,
+			QCA_NL80211_VENDOR_SUBCMD_P2P_LO_EVENT_INDEX,
+			GFP_KERNEL);
+
+	if (!vendor_event) {
+		hdd_err("cfg80211_vendor_event_alloc failed");
+		return;
+	}
+
+	if (nla_put_u32(vendor_event,
+			QCA_WLAN_VENDOR_ATTR_P2P_LISTEN_OFFLOAD_STOP_REASON,
+			evt->reason_code)) {
+		hdd_err("nla put failed");
+		kfree_skb(vendor_event);
+		return;
+	}
+
+	cfg80211_vendor_event(vendor_event, GFP_KERNEL);
+}
+
 /**
  * hdd_adaptive_dwelltime_init() - initialization for adaptive dwell time config
  * @hdd_ctx: HDD context
@@ -6713,6 +6759,11 @@ int hdd_wlan_startup(struct device *dev, void *hif_sc)
 		}
 	}
 
+	/* register P2P Listen Offload event callback */
+	if (wma_is_p2p_lo_capable())
+		sme_register_p2p_lo_event(hdd_ctx->hHal, hdd_ctx,
+				wlan_hdd_p2p_lo_event_callback);
+
 	ret = hdd_register_notifiers(hdd_ctx);
 	if (ret)
 		goto err_debugfs_exit;

+ 35 - 0
core/mac/inc/sir_api.h

@@ -6227,4 +6227,39 @@ struct sme_ndp_peer_ind {
 
 #endif /* WLAN_FEATURE_NAN_DATAPATH */
 
+/**
+ * struct sir_p2p_lo_start - p2p listen offload start
+ * @vdev_id: vdev identifier
+ * @ctl_flags: control flag
+ * @freq: p2p listen frequency
+ * @period: listen offload period
+ * @interval: listen offload interval
+ * @count: number listen offload intervals
+ * @device_types: device types
+ * @dev_types_len: device types length
+ * @probe_resp_tmplt: probe response template
+ * @probe_resp_len: probe response template length
+ */
+struct sir_p2p_lo_start {
+	uint32_t vdev_id;
+	uint32_t ctl_flags;
+	uint32_t freq;
+	uint32_t period;
+	uint32_t interval;
+	uint32_t count;
+	uint8_t  *device_types;
+	uint32_t dev_types_len;
+	uint8_t  *probe_resp_tmplt;
+	uint32_t probe_resp_len;
+};
+
+/**
+ * struct sir_p2p_lo_event - P2P listen offload stop event
+ * @vdev_id: vdev identifier
+ * @reason_code: P2P listen offload stop reason
+ */
+struct sir_p2p_lo_event {
+	uint32_t vdev_id;
+	uint32_t reason_code;
+};
 #endif /* __SIR_API_H */

+ 2 - 0
core/sme/inc/sme_api.h

@@ -1129,4 +1129,6 @@ void sme_set_pdev_ht_vht_ies(tHalHandle hHal, bool enable2x2);
 
 void sme_update_vdev_type_nss(tHalHandle hal, uint8_t max_supp_nss,
 		uint32_t vdev_type_nss, eCsrBand band);
+void sme_register_p2p_lo_event(tHalHandle hHal, void *context,
+					p2p_lo_callback callback);
 #endif /* #if !defined( __SME_API_H ) */

+ 3 - 0
core/sme/inc/sme_internal.h

@@ -149,6 +149,7 @@ typedef void (*preferred_network_found_ind_cb)(void *callback_context,
 
 typedef void (*ocb_callback)(void *context, void *response);
 typedef void (*sme_set_thermal_level_callback)(void *context, u_int8_t level);
+typedef void (*p2p_lo_callback)(void *context, void *event);
 
 typedef struct tagSmeStruct {
 	eSmeState state;
@@ -236,6 +237,8 @@ typedef struct tagSmeStruct {
 	void *saved_scan_cmd;
 	void (*pbpf_get_offload_cb)(void *context,
 			struct sir_bpf_get_offload *);
+	p2p_lo_callback p2p_lo_event_callback;
+	void *p2p_lo_event_context;
 } tSmeStruct, *tpSmeStruct;
 
 #endif /* #if !defined( __SMEINTERNAL_H ) */

+ 23 - 0
core/sme/src/common/sme_api.c

@@ -15739,3 +15739,26 @@ void sme_update_vdev_type_nss(tHalHandle hal, uint8_t max_supp_nss,
 		vdev_nss->p2p_go, vdev_nss->p2p_dev, vdev_nss->ibss,
 		vdev_nss->tdls, vdev_nss->ocb);
 }
+
+/**
+ * sme_register_p2p_lo_event() - Register for the p2p lo event
+ * @hHal: reference to the HAL
+ * @context: the context of the call
+ * @callback: the callback to hdd
+ *
+ * This function registers the callback function for P2P listen
+ * offload stop event.
+ *
+ * Return: none
+ */
+void sme_register_p2p_lo_event(tHalHandle hHal, void *context,
+					p2p_lo_callback callback)
+{
+	tpAniSirGlobal pMac = PMAC_STRUCT(hHal);
+	QDF_STATUS status = QDF_STATUS_E_FAILURE;
+
+	status = sme_acquire_global_lock(&pMac->sme);
+	pMac->sme.p2p_lo_event_callback = callback;
+	pMac->sme.p2p_lo_event_context = context;
+	sme_release_global_lock(&pMac->sme);
+}

+ 1 - 0
core/wma/inc/wma.h

@@ -938,6 +938,7 @@ struct wma_txrx_node {
 	int32_t roam_synch_delay;
 	uint8_t nss_2g;
 	uint8_t nss_5g;
+	bool p2p_lo_in_progress;
 };
 
 #if defined(QCA_WIFI_FTM)

+ 3 - 0
core/wma/inc/wma_api.h

@@ -264,4 +264,7 @@ static inline QDF_STATUS wma_register_ndp_cb(QDF_STATUS (*pe_ndp_event_handler)
 }
 #endif
 
+bool wma_is_p2p_lo_capable(void);
+QDF_STATUS wma_p2p_lo_start(struct sir_p2p_lo_start *params);
+QDF_STATUS wma_p2p_lo_stop(u_int32_t vdev_id);
 #endif

+ 2 - 0
core/wma/inc/wma_internal.h

@@ -1214,4 +1214,6 @@ int wma_peer_delete_handler(void *handle, uint8_t *cmd_param_info,
 				uint32_t len);
 void wma_remove_req(tp_wma_handle wma, uint8_t vdev_id,
 			    uint8_t type);
+int wma_p2p_lo_event_handler(void *handle, uint8_t *event_buf,
+				uint32_t len);
 #endif

+ 200 - 0
core/wma/src/wma_features.c

@@ -3936,6 +3936,23 @@ bool wma_is_extscan_in_progress(tp_wma_handle wma, int vdev_id)
 }
 #endif
 
+/**
+ * wma_is_p2plo_in_progress(): check if P2P listen offload is in progress
+ * @wma: wma handle
+ * @vdev_id: vdev_id
+ *
+ * This function is to check if p2p listen offload is in progress,
+ *  true: p2p listen offload in progress
+ *  false: otherwise
+ *
+ * Return: TRUE/FALSE
+ */
+static inline
+bool wma_is_p2plo_in_progress(tp_wma_handle wma, int vdev_id)
+{
+	return wma->interfaces[vdev_id].p2p_lo_in_progress;
+}
+
 /**
  * wma_is_wow_applicable(): should enable wow
  * @wma: wma handle
@@ -3945,6 +3962,7 @@ bool wma_is_extscan_in_progress(tp_wma_handle wma, int vdev_id)
  *  2) Is any one of vdev in connected state (in STA mode) ?
  *  3) Is PNO in progress in any one of vdev ?
  *  4) Is Extscan in progress in any one of vdev ?
+ *  5) Is P2P listen offload in any one of vdev?
  *  If none of above conditions is true then return false
  *
  * Return: true if wma needs to configure wow false otherwise.
@@ -3967,6 +3985,9 @@ bool wma_is_wow_applicable(tp_wma_handle wma)
 		} else if (wma_is_extscan_in_progress(wma, vdev_id)) {
 			WMA_LOGD("EXT is in progress, enabling wow");
 			return true;
+		} else if (wma_is_p2plo_in_progress(wma, vdev_id)) {
+			WMA_LOGD("P2P LO is in progress, enabling wow");
+			return true;
 		}
 	}
 
@@ -7316,3 +7337,182 @@ QDF_STATUS wma_set_bpf_instructions(tp_wma_handle wma,
 	}
 	return QDF_STATUS_SUCCESS;
 }
+
+/**
+ *  wma_p2p_lo_start() - P2P listen offload start
+ *  @params: p2p listen offload parameters
+ *
+ *  This function sends WMI command to start P2P listen offload.
+ *
+ *  Return: QDF_STATUS enumeration
+ */
+QDF_STATUS wma_p2p_lo_start(struct sir_p2p_lo_start *params)
+{
+	wmi_buf_t buf;
+	wmi_p2p_lo_start_cmd_fixed_param *cmd;
+	int32_t len = sizeof(*cmd);
+	tp_wma_handle wma = cds_get_context(QDF_MODULE_ID_WMA);
+	uint8_t *buf_ptr;
+	int ret;
+
+	if (NULL == wma) {
+		WMA_LOGE("%s: wma context is NULL", __func__);
+		return QDF_STATUS_E_INVAL;
+	}
+
+	len += 2 * WMI_TLV_HDR_SIZE +
+	       qdf_roundup(params->dev_types_len, sizeof(A_UINT32)) +
+	       qdf_roundup(params->probe_resp_len, sizeof(A_UINT32));
+
+	buf = wmi_buf_alloc(wma->wmi_handle, len);
+	if (!buf) {
+		WMA_LOGP("%s: failed to allocate memory for p2p lo start",
+			 __func__);
+		return QDF_STATUS_E_NOMEM;
+	}
+
+	cmd = (wmi_p2p_lo_start_cmd_fixed_param *)wmi_buf_data(buf);
+	buf_ptr = (uint8_t *) wmi_buf_data(buf);
+
+	WMITLV_SET_HDR(&cmd->tlv_header,
+		 WMITLV_TAG_STRUC_wmi_p2p_lo_start_cmd_fixed_param,
+		 WMITLV_GET_STRUCT_TLVLEN(
+			wmi_p2p_lo_start_cmd_fixed_param));
+
+	cmd->vdev_id = params->vdev_id;
+	cmd->ctl_flags = params->ctl_flags;
+	cmd->channel = params->freq;
+	cmd->period = params->period;
+	cmd->interval = params->interval;
+	cmd->count = params->count;
+
+	buf_ptr += sizeof(wmi_p2p_lo_start_cmd_fixed_param);
+	WMITLV_SET_HDR(buf_ptr, WMITLV_TAG_ARRAY_BYTE,
+		qdf_roundup(params->dev_types_len, sizeof(A_UINT32)));
+	buf_ptr += WMI_TLV_HDR_SIZE;
+	qdf_mem_copy(buf_ptr, params->device_types, params->dev_types_len);
+
+	buf_ptr += qdf_roundup(params->dev_types_len, sizeof(A_UINT32));
+	WMITLV_SET_HDR(buf_ptr, WMITLV_TAG_ARRAY_BYTE,
+		qdf_roundup(params->probe_resp_len, sizeof(A_UINT32)));
+	buf_ptr += WMI_TLV_HDR_SIZE;
+	qdf_mem_copy(buf_ptr, params->probe_resp_tmplt, params->probe_resp_len);
+
+	WMA_LOGI("%s: Sending WMI_P2P_LO_START command, channel=%d, period=%d, interval=%d, count=%d",
+			__func__, cmd->channel, cmd->period,
+			cmd->interval, cmd->count);
+
+	ret = wmi_unified_cmd_send(wma->wmi_handle,
+				   buf, len,
+				   WMI_P2P_LISTEN_OFFLOAD_START_CMDID);
+	if (ret) {
+		WMA_LOGE("Failed to send p2p lo start: %d", ret);
+		wmi_buf_free(buf);
+	}
+
+	WMA_LOGI("%s: Successfully sent WMI_P2P_LO_START", __func__);
+	wma->interfaces[params->vdev_id].p2p_lo_in_progress = true;
+
+	return ret;
+}
+
+/**
+ *  wma_p2p_lo_stop() - P2P listen offload stop
+ *  @vdev_id: vdev identifier
+ *
+ *  This function sends WMI command to stop P2P listen offload.
+ *
+ *  Return: QDF_STATUS enumeration
+ */
+QDF_STATUS wma_p2p_lo_stop(u_int32_t vdev_id)
+{
+	wmi_buf_t buf;
+	wmi_p2p_lo_stop_cmd_fixed_param *cmd;
+	int32_t len;
+	tp_wma_handle wma = cds_get_context(QDF_MODULE_ID_WMA);
+	int ret;
+
+	if (NULL == wma) {
+		WMA_LOGE("%s: wma context is NULL", __func__);
+		return QDF_STATUS_E_INVAL;
+	}
+
+	len = sizeof(*cmd);
+	buf = wmi_buf_alloc(wma->wmi_handle, len);
+	if (!buf) {
+		WMA_LOGP("%s: failed to allocate memory for p2p lo stop",
+			 __func__);
+		return QDF_STATUS_E_NOMEM;
+	}
+	cmd = (wmi_p2p_lo_stop_cmd_fixed_param *)wmi_buf_data(buf);
+
+	WMITLV_SET_HDR(&cmd->tlv_header,
+		WMITLV_TAG_STRUC_wmi_p2p_lo_stop_cmd_fixed_param,
+		WMITLV_GET_STRUCT_TLVLEN(
+			wmi_p2p_lo_stop_cmd_fixed_param));
+
+	cmd->vdev_id = vdev_id;
+
+	WMA_LOGI("%s: Sending WMI_P2P_LO_STOP command", __func__);
+
+	ret = wmi_unified_cmd_send(wma->wmi_handle,
+				   buf, len,
+				   WMI_P2P_LISTEN_OFFLOAD_STOP_CMDID);
+	if (ret) {
+		WMA_LOGE("Failed to send p2p lo stop: %d", ret);
+		wmi_buf_free(buf);
+	}
+
+	WMA_LOGI("%s: Successfully sent WMI_P2P_LO_STOP", __func__);
+	wma->interfaces[vdev_id].p2p_lo_in_progress = false;
+
+	return ret;
+}
+
+/**
+ * wma_p2p_lo_event_handler() - p2p lo event
+ * @handle: the WMA handle
+ * @event_buf: buffer with the event parameters
+ * @len: length of the buffer
+ *
+ * This function receives P2P listen offload stop event from FW and
+ * pass the event information to upper layer.
+ *
+ * Return: 0 on success
+ */
+int wma_p2p_lo_event_handler(void *handle, uint8_t *event_buf,
+				uint32_t len)
+{
+	tp_wma_handle wma = cds_get_context(QDF_MODULE_ID_WMA);
+	struct sir_p2p_lo_event *event;
+	WMI_P2P_LISTEN_OFFLOAD_STOPPED_EVENTID_param_tlvs *param_tlvs;
+	wmi_p2p_lo_stopped_event_fixed_param *fix_param;
+	tpAniSirGlobal p_mac = cds_get_context(QDF_MODULE_ID_PE);
+
+	if (!p_mac) {
+		WMA_LOGE("%s: Invalid p_mac", __func__);
+		return -EINVAL;
+	}
+
+	if (!p_mac->sme.p2p_lo_event_callback) {
+		WMA_LOGE("%s: Callback not registered", __func__);
+		return -EINVAL;
+	}
+
+	param_tlvs = (WMI_P2P_LISTEN_OFFLOAD_STOPPED_EVENTID_param_tlvs *)
+								event_buf;
+	fix_param = param_tlvs->fixed_param;
+	event = qdf_mem_malloc(sizeof(*event));
+	if (event == NULL) {
+		WMA_LOGE("Event allocation failed");
+		return -ENOMEM;
+	}
+	event->vdev_id = fix_param->vdev_id;
+	event->reason_code = fix_param->reason;
+
+	p_mac->sme.p2p_lo_event_callback(p_mac->hHdd, event);
+
+	wma->interfaces[event->vdev_id].p2p_lo_in_progress = false;
+
+	return 0;
+}

+ 11 - 0
core/wma/src/wma_main.c

@@ -2858,6 +2858,17 @@ QDF_STATUS wma_start(void *cds_ctx)
 		goto end;
 	}
 
+	/* Initialize the P2P Listen Offload event handler */
+	status = wmi_unified_register_event_handler(wma_handle->wmi_handle,
+			WMI_P2P_LISTEN_OFFLOAD_STOPPED_EVENTID,
+			wma_p2p_lo_event_handler,
+			WMA_RX_SERIALIZER_CTX);
+	if (!QDF_IS_STATUS_SUCCESS(status)) {
+		WMA_LOGE("Failed to register p2p lo event cb");
+		qdf_status = QDF_STATUS_E_FAILURE;
+		goto end;
+	}
+
 end:
 	WMA_LOGD("%s: Exit", __func__);
 	return qdf_status;

+ 26 - 0
core/wma/src/wma_utils.c

@@ -3424,3 +3424,29 @@ wma_config_debug_module_cmd(wmi_unified_t wmi_handle, A_UINT32 param,
 
 	return wmi_unified_dbglog_cmd_send(wmi_handle, &dbg_param);
 }
+
+/**
+ * wma_is_p2p_lo_capable() - if driver is capable of p2p listen offload
+ *
+ * This function checks if driver is capable of p2p listen offload
+ *    true: capable of p2p offload
+ *    false: not capable
+ *
+ * Return: true - capable, false - not capable
+ */
+bool wma_is_p2p_lo_capable(void)
+{
+	tp_wma_handle wma;
+
+	wma = cds_get_context(QDF_MODULE_ID_WMA);
+	if (!wma) {
+		WMA_LOGE("%s: Invalid WMA handle", __func__);
+		return false;
+	}
+
+	if (WMI_SERVICE_IS_ENABLED(wma->wmi_service_bitmap,
+			WMI_SERVICE_P2P_LISTEN_OFFLOAD_SUPPORT))
+		return true;
+
+	return false;
+}