Parcourir la source

P2P: Allow P2P listen being offloaded to the driver/firmware

This allows P2P Listen to be offloaded to device to enhance power
saving.

To start P2P listen offload, from wpa_cli interface, issue the command:
	p2p_lo_start <freq> <period> <interval> <count>

To stop P2P listen offload, issue the command:
	p2p_lo_stop

Signed-off-by: Jouni Malinen <jouni@qca.qualcomm.com>
Peng Xu il y a 8 ans
Parent
commit
a6f5b1937a

+ 3 - 0
src/common/wpa_ctrl.h

@@ -271,6 +271,9 @@ extern "C" {
 
 #define AP_CSA_FINISHED "AP-CSA-FINISHED "
 
+#define P2P_EVENT_LISTEN_OFFLOAD_STOP "P2P-LISTEN-OFFLOAD-STOPPED "
+#define P2P_LISTEN_OFFLOAD_STOP_REASON "P2P-LISTEN-OFFLOAD-STOP-REASON "
+
 /* BSS Transition Management Response frame received */
 #define BSS_TM_RESP "BSS-TM-RESP "
 

+ 54 - 0
src/drivers/driver.h

@@ -1278,6 +1278,8 @@ struct wpa_driver_capa {
 #define WPA_DRIVER_FLAGS_OFFCHANNEL_SIMULTANEOUS	0x0000008000000000ULL
 /** Driver supports full AP client state */
 #define WPA_DRIVER_FLAGS_FULL_AP_CLIENT_STATE	0x0000010000000000ULL
+/** Driver supports P2P Listen offload */
+#define WPA_DRIVER_FLAGS_P2P_LISTEN_OFFLOAD     0x0000020000000000ULL
 	u64 flags;
 
 #define FULL_AP_CLIENT_STATE_SUPP(drv_flags) \
@@ -3573,6 +3575,32 @@ struct wpa_driver_ops {
 	int (*get_ext_capab)(void *priv, enum wpa_driver_if_type type,
 			     const u8 **ext_capab, const u8 **ext_capab_mask,
 			     unsigned int *ext_capab_len);
+
+	/**
+	 * p2p_lo_start - Start offloading P2P listen to device
+	 * @priv: Private driver interface data
+	 * @freq: Listening frequency (MHz) for P2P listen
+	 * @period: Length of the listen operation in milliseconds
+	 * @interval: Interval for running the listen operation in milliseconds
+	 * @count: Number of times to run the listen operation
+	 * @device_types: Device primary and secondary types
+	 * @dev_types_len: Number of bytes for device_types
+	 * @ies: P2P IE and WSC IE for Probe Response frames
+	 * @ies_len: Length of ies in bytes
+	 * Returns: 0 on success or -1 on failure
+	 */
+	int (*p2p_lo_start)(void *priv, unsigned int freq,
+			    unsigned int period, unsigned int interval,
+			    unsigned int count,
+			    const u8 *device_types, size_t dev_types_len,
+			    const u8 *ies, size_t ies_len);
+
+	/**
+	 * p2p_lo_stop - Stop P2P listen offload
+	 * @priv: Private driver interface data
+	 * Returns: 0 on success or -1 on failure
+	 */
+	int (*p2p_lo_stop)(void *priv);
 };
 
 
@@ -4057,6 +4085,11 @@ enum wpa_event_type {
 	 * on a DFS frequency by a driver that supports DFS Offload.
 	 */
 	EVENT_DFS_CAC_STARTED,
+
+	/**
+	 * EVENT_P2P_LO_STOP - Notify that P2P listen offload is stopped
+	 */
+	EVENT_P2P_LO_STOP,
 };
 
 
@@ -4782,6 +4815,27 @@ union wpa_event_data {
 		u16 ch_width;
 		enum hostapd_hw_mode hw_mode;
 	} acs_selected_channels;
+
+	/**
+	 * struct p2p_lo_stop - Reason code for P2P Listen offload stop event
+	 * @reason_code: Reason for stopping offload
+	 *	P2P_LO_STOPPED_REASON_COMPLETE: Listen offload finished as
+	 *	scheduled.
+	 *	P2P_LO_STOPPED_REASON_RECV_STOP_CMD: Host requested offload to
+	 *	be stopped.
+	 *	P2P_LO_STOPPED_REASON_INVALID_PARAM: Invalid listen offload
+	 *	parameters.
+	 *	P2P_LO_STOPPED_REASON_NOT_SUPPORTED: Listen offload not
+	 *	supported by device.
+	 */
+	struct p2p_lo_stop {
+		enum {
+			P2P_LO_STOPPED_REASON_COMPLETE = 0,
+			P2P_LO_STOPPED_REASON_RECV_STOP_CMD,
+			P2P_LO_STOPPED_REASON_INVALID_PARAM,
+			P2P_LO_STOPPED_REASON_NOT_SUPPORTED,
+		} reason_code;
+	} p2p_lo_stop;
 };
 
 /**

+ 1 - 0
src/drivers/driver_common.c

@@ -80,6 +80,7 @@ const char * event_to_string(enum wpa_event_type event)
 	E2S(NEW_PEER_CANDIDATE);
 	E2S(ACS_CHANNEL_SELECTED);
 	E2S(DFS_CAC_STARTED);
+	E2S(P2P_LO_STOP);
 	}
 
 	return "UNKNOWN";

+ 85 - 0
src/drivers/driver_nl80211.c

@@ -9117,6 +9117,89 @@ static int nl80211_set_prob_oper_freq(void *priv, unsigned int freq)
 	return 0;
 }
 
+
+static int nl80211_p2p_lo_start(void *priv, unsigned int freq,
+				unsigned int period, unsigned int interval,
+				unsigned int count, const u8 *device_types,
+				size_t dev_types_len,
+				const u8 *ies, size_t ies_len)
+{
+	struct i802_bss *bss = priv;
+	struct wpa_driver_nl80211_data *drv = bss->drv;
+	struct nl_msg *msg;
+	struct nlattr *container;
+	int ret;
+
+	wpa_printf(MSG_DEBUG,
+		   "nl80211: Start P2P Listen offload: freq=%u, period=%u, interval=%u, count=%u",
+		   freq, period, interval, count);
+
+	if (!(drv->capa.flags & WPA_DRIVER_FLAGS_P2P_LISTEN_OFFLOAD))
+		return -1;
+
+	if (!(msg = nl80211_drv_msg(drv, 0, NL80211_CMD_VENDOR)) ||
+	    nla_put_u32(msg, NL80211_ATTR_VENDOR_ID, OUI_QCA) ||
+	    nla_put_u32(msg, NL80211_ATTR_VENDOR_SUBCMD,
+			QCA_NL80211_VENDOR_SUBCMD_P2P_LISTEN_OFFLOAD_START))
+		goto fail;
+
+	container = nla_nest_start(msg, NL80211_ATTR_VENDOR_DATA);
+	if (!container)
+		goto fail;
+
+	if (nla_put_u32(msg, QCA_WLAN_VENDOR_ATTR_P2P_LISTEN_OFFLOAD_CHANNEL,
+			freq) ||
+	    nla_put_u32(msg, QCA_WLAN_VENDOR_ATTR_P2P_LISTEN_OFFLOAD_PERIOD,
+			period) ||
+	    nla_put_u32(msg, QCA_WLAN_VENDOR_ATTR_P2P_LISTEN_OFFLOAD_INTERVAL,
+			interval) ||
+	    nla_put_u32(msg, QCA_WLAN_VENDOR_ATTR_P2P_LISTEN_OFFLOAD_COUNT,
+			count) ||
+	    nla_put(msg, QCA_WLAN_VENDOR_ATTR_P2P_LISTEN_OFFLOAD_DEVICE_TYPES,
+		    dev_types_len, device_types) ||
+	    nla_put(msg, QCA_WLAN_VENDOR_ATTR_P2P_LISTEN_OFFLOAD_VENDOR_IE,
+		    ies_len, ies))
+		goto fail;
+
+	nla_nest_end(msg, container);
+	ret = send_and_recv_msgs(drv, msg, NULL, NULL);
+	msg = NULL;
+	if (ret) {
+		wpa_printf(MSG_DEBUG,
+			   "nl80211: Failed to send P2P Listen offload vendor command");
+		goto fail;
+	}
+
+	return 0;
+
+fail:
+	nlmsg_free(msg);
+	return -1;
+}
+
+
+static int nl80211_p2p_lo_stop(void *priv)
+{
+	struct i802_bss *bss = priv;
+	struct wpa_driver_nl80211_data *drv = bss->drv;
+	struct nl_msg *msg;
+
+	wpa_printf(MSG_DEBUG, "nl80211: Stop P2P Listen offload");
+
+	if (!(drv->capa.flags & WPA_DRIVER_FLAGS_P2P_LISTEN_OFFLOAD))
+		return -1;
+
+	if (!(msg = nl80211_drv_msg(drv, 0, NL80211_CMD_VENDOR)) ||
+	    nla_put_u32(msg, NL80211_ATTR_VENDOR_ID, OUI_QCA) ||
+	    nla_put_u32(msg, NL80211_ATTR_VENDOR_SUBCMD,
+			QCA_NL80211_VENDOR_SUBCMD_P2P_LISTEN_OFFLOAD_STOP)) {
+		nlmsg_free(msg);
+		return -1;
+	}
+
+	return send_and_recv_msgs(drv, msg, NULL, NULL);
+}
+
 #endif /* CONFIG_DRIVER_NL80211_QCA */
 
 
@@ -9357,6 +9440,8 @@ const struct wpa_driver_ops wpa_driver_nl80211_ops = {
 	.set_band = nl80211_set_band,
 	.get_pref_freq_list = nl80211_get_pref_freq_list,
 	.set_prob_oper_freq = nl80211_set_prob_oper_freq,
+	.p2p_lo_start = nl80211_p2p_lo_start,
+	.p2p_lo_stop = nl80211_p2p_lo_stop,
 #endif /* CONFIG_DRIVER_NL80211_QCA */
 	.configure_data_frame_filters = nl80211_configure_data_frame_filters,
 	.get_ext_capab = nl80211_get_ext_capab,

+ 2 - 0
src/drivers/driver_nl80211_capa.c

@@ -972,6 +972,8 @@ static void qca_nl80211_get_features(struct wpa_driver_nl80211_data *drv)
 	if (check_feature(QCA_WLAN_VENDOR_FEATURE_OFFCHANNEL_SIMULTANEOUS,
 			  &info))
 		drv->capa.flags |= WPA_DRIVER_FLAGS_OFFCHANNEL_SIMULTANEOUS;
+	if (check_feature(QCA_WLAN_VENDOR_FEATURE_P2P_LISTEN_OFFLOAD, &info))
+		drv->capa.flags |= WPA_DRIVER_FLAGS_P2P_LISTEN_OFFLOAD;
 	os_free(info.flags);
 }
 

+ 28 - 0
src/drivers/driver_nl80211_event.c

@@ -1876,6 +1876,31 @@ static void qca_nl80211_scan_done_event(struct wpa_driver_nl80211_data *drv,
 			       external_scan);
 }
 
+
+static void qca_nl80211_p2p_lo_stop_event(struct wpa_driver_nl80211_data *drv,
+					  u8 *data, size_t len)
+{
+	struct nlattr *tb[QCA_WLAN_VENDOR_ATTR_P2P_LISTEN_OFFLOAD_MAX + 1];
+	union wpa_event_data event;
+
+	wpa_printf(MSG_DEBUG,
+		   "nl80211: P2P listen offload stop vendor event received");
+
+	if (nla_parse(tb, QCA_WLAN_VENDOR_ATTR_P2P_LISTEN_OFFLOAD_MAX,
+		      (struct nlattr *) data, len, NULL) ||
+	    !tb[QCA_WLAN_VENDOR_ATTR_P2P_LISTEN_OFFLOAD_STOP_REASON])
+		return;
+
+	os_memset(&event, 0, sizeof(event));
+	event.p2p_lo_stop.reason_code =
+		nla_get_u8(tb[QCA_WLAN_VENDOR_ATTR_P2P_LISTEN_OFFLOAD_STOP_REASON]);
+
+	wpa_printf(MSG_DEBUG,
+		   "nl80211: P2P Listen offload stop reason: %d",
+		   event.p2p_lo_stop.reason_code);
+	wpa_supplicant_event(drv->ctx, EVENT_P2P_LO_STOP, &event);
+}
+
 #endif /* CONFIG_DRIVER_NL80211_QCA */
 
 
@@ -1909,6 +1934,9 @@ static void nl80211_vendor_event_qca(struct wpa_driver_nl80211_data *drv,
 	case QCA_NL80211_VENDOR_SUBCMD_SCAN_DONE:
 		qca_nl80211_scan_done_event(drv, data, len);
 		break;
+	case QCA_NL80211_VENDOR_SUBCMD_P2P_LISTEN_OFFLOAD_STOP:
+		qca_nl80211_p2p_lo_stop_event(drv, data, len);
+		break;
 #endif /* CONFIG_DRIVER_NL80211_QCA */
 	default:
 		wpa_printf(MSG_DEBUG,

+ 91 - 41
src/p2p/p2p.c

@@ -2234,6 +2234,58 @@ struct wpabuf * p2p_build_probe_resp_ies(struct p2p_data *p2p,
 	return buf;
 }
 
+static int p2p_build_probe_resp_buf(struct p2p_data *p2p, struct wpabuf *buf,
+				    struct wpabuf *ies,
+				    const u8 *addr, int rx_freq)
+{
+	struct ieee80211_mgmt *resp;
+	u8 channel, op_class;
+
+	resp = wpabuf_put(buf, offsetof(struct ieee80211_mgmt,
+					u.probe_resp.variable));
+
+	resp->frame_control = host_to_le16((WLAN_FC_TYPE_MGMT << 2) |
+					   (WLAN_FC_STYPE_PROBE_RESP << 4));
+	os_memcpy(resp->da, addr, ETH_ALEN);
+	os_memcpy(resp->sa, p2p->cfg->dev_addr, ETH_ALEN);
+	os_memcpy(resp->bssid, p2p->cfg->dev_addr, ETH_ALEN);
+	resp->u.probe_resp.beacon_int = host_to_le16(100);
+	/* hardware or low-level driver will setup seq_ctrl and timestamp */
+	resp->u.probe_resp.capab_info =
+	    host_to_le16(WLAN_CAPABILITY_SHORT_PREAMBLE |
+		     WLAN_CAPABILITY_PRIVACY |
+		     WLAN_CAPABILITY_SHORT_SLOT_TIME);
+
+	wpabuf_put_u8(buf, WLAN_EID_SSID);
+	wpabuf_put_u8(buf, P2P_WILDCARD_SSID_LEN);
+	wpabuf_put_data(buf, P2P_WILDCARD_SSID, P2P_WILDCARD_SSID_LEN);
+
+	wpabuf_put_u8(buf, WLAN_EID_SUPP_RATES);
+	wpabuf_put_u8(buf, 8);
+	wpabuf_put_u8(buf, (60 / 5) | 0x80);
+	wpabuf_put_u8(buf, 90 / 5);
+	wpabuf_put_u8(buf, (120 / 5) | 0x80);
+	wpabuf_put_u8(buf, 180 / 5);
+	wpabuf_put_u8(buf, (240 / 5) | 0x80);
+	wpabuf_put_u8(buf, 360 / 5);
+	wpabuf_put_u8(buf, 480 / 5);
+	wpabuf_put_u8(buf, 540 / 5);
+
+	if (!rx_freq) {
+		channel = p2p->cfg->channel;
+	} else if (p2p_freq_to_channel(rx_freq, &op_class, &channel)) {
+		p2p_err(p2p, "Failed to convert freq to channel");
+		return -1;
+	}
+
+	wpabuf_put_u8(buf, WLAN_EID_DS_PARAMS);
+	wpabuf_put_u8(buf, 1);
+	wpabuf_put_u8(buf, channel);
+
+	wpabuf_put_buf(buf, ies);
+
+	return 0;
+}
 
 static int p2p_service_find_asp(struct p2p_data *p2p, const u8 *hash)
 {
@@ -2267,10 +2319,8 @@ p2p_reply_probe(struct p2p_data *p2p, const u8 *addr, const u8 *dst,
 {
 	struct ieee802_11_elems elems;
 	struct wpabuf *buf;
-	struct ieee80211_mgmt *resp;
 	struct p2p_message msg;
 	struct wpabuf *ies;
-	u8 channel, op_class;
 
 	if (ieee802_11_parse_elems((u8 *) ie, ie_len, &elems, 0) ==
 	    ParseFailed) {
@@ -2414,49 +2464,12 @@ p2p_reply_probe(struct p2p_data *p2p, const u8 *addr, const u8 *dst,
 		return P2P_PREQ_NOT_PROCESSED;
 	}
 
-	resp = wpabuf_put(buf, offsetof(struct ieee80211_mgmt,
-					u.probe_resp.variable));
-
-	resp->frame_control = host_to_le16((WLAN_FC_TYPE_MGMT << 2) |
-					   (WLAN_FC_STYPE_PROBE_RESP << 4));
-	os_memcpy(resp->da, addr, ETH_ALEN);
-	os_memcpy(resp->sa, p2p->cfg->dev_addr, ETH_ALEN);
-	os_memcpy(resp->bssid, p2p->cfg->dev_addr, ETH_ALEN);
-	resp->u.probe_resp.beacon_int = host_to_le16(100);
-	/* hardware or low-level driver will setup seq_ctrl and timestamp */
-	resp->u.probe_resp.capab_info =
-		host_to_le16(WLAN_CAPABILITY_SHORT_PREAMBLE |
-			     WLAN_CAPABILITY_PRIVACY |
-			     WLAN_CAPABILITY_SHORT_SLOT_TIME);
-
-	wpabuf_put_u8(buf, WLAN_EID_SSID);
-	wpabuf_put_u8(buf, P2P_WILDCARD_SSID_LEN);
-	wpabuf_put_data(buf, P2P_WILDCARD_SSID, P2P_WILDCARD_SSID_LEN);
-
-	wpabuf_put_u8(buf, WLAN_EID_SUPP_RATES);
-	wpabuf_put_u8(buf, 8);
-	wpabuf_put_u8(buf, (60 / 5) | 0x80);
-	wpabuf_put_u8(buf, 90 / 5);
-	wpabuf_put_u8(buf, (120 / 5) | 0x80);
-	wpabuf_put_u8(buf, 180 / 5);
-	wpabuf_put_u8(buf, (240 / 5) | 0x80);
-	wpabuf_put_u8(buf, 360 / 5);
-	wpabuf_put_u8(buf, 480 / 5);
-	wpabuf_put_u8(buf, 540 / 5);
-
-	if (!rx_freq) {
-		channel = p2p->cfg->channel;
-	} else if (p2p_freq_to_channel(rx_freq, &op_class, &channel)) {
+	if (p2p_build_probe_resp_buf(p2p, buf, ies, addr, rx_freq)) {
 		wpabuf_free(ies);
 		wpabuf_free(buf);
 		return P2P_PREQ_NOT_PROCESSED;
 	}
 
-	wpabuf_put_u8(buf, WLAN_EID_DS_PARAMS);
-	wpabuf_put_u8(buf, 1);
-	wpabuf_put_u8(buf, channel);
-
-	wpabuf_put_buf(buf, ies);
 	wpabuf_free(ies);
 
 	p2p->cfg->send_probe_resp(p2p->cfg->cb_ctx, buf, rx_freq);
@@ -2470,12 +2483,18 @@ p2p_reply_probe(struct p2p_data *p2p, const u8 *addr, const u8 *dst,
 enum p2p_probe_req_status
 p2p_probe_req_rx(struct p2p_data *p2p, const u8 *addr, const u8 *dst,
 		 const u8 *bssid, const u8 *ie, size_t ie_len,
-		 unsigned int rx_freq)
+		 unsigned int rx_freq, int p2p_lo_started)
 {
 	enum p2p_probe_req_status res;
 
 	p2p_add_dev_from_probe_req(p2p, addr, ie, ie_len);
 
+	if (p2p_lo_started) {
+		p2p_dbg(p2p,
+			"Probe Response is offloaded, do not reply Probe Request");
+		return P2P_PREQ_PROCESSED;
+	}
+
 	res = p2p_reply_probe(p2p, addr, dst, bssid, ie, ie_len, rx_freq);
 	if (res != P2P_PREQ_PROCESSED && res != P2P_PREQ_NOT_PROCESSED)
 		return res;
@@ -5490,3 +5509,34 @@ void p2p_set_own_pref_freq_list(struct p2p_data *p2p,
 			i, p2p->pref_freq_list[i]);
 	}
 }
+
+
+struct wpabuf * p2p_build_probe_resp_template(struct p2p_data *p2p,
+					      unsigned int freq)
+{
+	struct wpabuf *ies, *buf;
+	u8 addr[] = { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF };
+	int ret;
+
+	ies = p2p_build_probe_resp_ies(p2p, NULL, 0);
+	if (!ies) {
+		wpa_printf(MSG_ERROR,
+			   "CTRL: Failed to build Probe Response IEs");
+		return NULL;
+	}
+
+	buf = wpabuf_alloc(200 + wpabuf_len(ies));
+	if (!buf) {
+		wpabuf_free(ies);
+		return NULL;
+	}
+
+	ret = p2p_build_probe_resp_buf(p2p, buf, ies, addr, freq);
+	wpabuf_free(ies);
+	if (ret) {
+		wpabuf_free(buf);
+		return NULL;
+	}
+
+	return buf;
+}

+ 5 - 1
src/p2p/p2p.h

@@ -1555,12 +1555,13 @@ enum p2p_probe_req_status {
  * @ie: Information elements from the Probe Request frame body
  * @ie_len: Length of ie buffer in octets
  * @rx_freq: Probe Request frame RX frequency
+ * @p2p_lo_started: Whether P2P Listen Offload is started
  * Returns: value indicating the type and status of the probe request
  */
 enum p2p_probe_req_status
 p2p_probe_req_rx(struct p2p_data *p2p, const u8 *addr, const u8 *dst,
 		 const u8 *bssid, const u8 *ie, size_t ie_len,
-		 unsigned int rx_freq);
+		 unsigned int rx_freq, int p2p_lo_started);
 
 /**
  * p2p_rx_action - Report received Action frame
@@ -2383,4 +2384,7 @@ void p2p_set_own_pref_freq_list(struct p2p_data *p2p,
 int p2p_group_get_common_freqs(struct p2p_group *group, int *common_freqs,
 			       unsigned int *num);
 
+struct wpabuf * p2p_build_probe_resp_template(struct p2p_data *p2p,
+					      unsigned int freq);
+
 #endif /* P2P_H */

+ 21 - 0
wpa_supplicant/ctrl_iface.c

@@ -6283,6 +6283,21 @@ static int p2p_ctrl_remove_client(struct wpa_supplicant *wpa_s, const char *cmd)
 	return 0;
 }
 
+
+static int p2p_ctrl_iface_p2p_lo_start(struct wpa_supplicant *wpa_s, char *cmd)
+{
+	int freq = 0, period = 0, interval = 0, count = 0;
+
+	if (sscanf(cmd, "%d %d %d %d", &freq, &period, &interval, &count) != 4)
+	{
+		wpa_printf(MSG_DEBUG,
+			   "CTRL: Invalid P2P LO Start parameter: '%s'", cmd);
+		return -1;
+	}
+
+	return wpas_p2p_lo_start(wpa_s, freq, period, interval, count);
+}
+
 #endif /* CONFIG_P2P */
 
 
@@ -8968,6 +8983,12 @@ char * wpa_supplicant_ctrl_iface_process(struct wpa_supplicant *wpa_s,
 	} else if (os_strncmp(buf, "P2P_REMOVE_CLIENT ", 18) == 0) {
 		if (p2p_ctrl_remove_client(wpa_s, buf + 18) < 0)
 			reply_len = -1;
+	} else if (os_strncmp(buf, "P2P_LO_START ", 13) == 0) {
+		if (p2p_ctrl_iface_p2p_lo_start(wpa_s, buf + 13))
+			reply_len = -1;
+	} else if (os_strcmp(buf, "P2P_LO_STOP") == 0) {
+		if (wpas_p2p_lo_stop(wpa_s))
+			reply_len = -1;
 #endif /* CONFIG_P2P */
 #ifdef CONFIG_WIFI_DISPLAY
 	} else if (os_strncmp(buf, "WFD_SUBELEM_SET ", 16) == 0) {

+ 23 - 0
wpa_supplicant/driver_i.h

@@ -946,4 +946,27 @@ static inline int wpa_drv_get_ext_capa(struct wpa_supplicant *wpa_s,
 					    &wpa_s->extended_capa_len);
 }
 
+static inline int wpa_drv_p2p_lo_start(struct wpa_supplicant *wpa_s,
+				       unsigned int channel,
+				       unsigned int period,
+				       unsigned int interval,
+				       unsigned int count,
+				       const u8 *device_types,
+				       size_t dev_types_len,
+				       const u8 *ies, size_t ies_len)
+{
+	if (!wpa_s->driver->p2p_lo_start)
+		return -1;
+	return wpa_s->driver->p2p_lo_start(wpa_s->drv_priv, channel, period,
+					   interval, count, device_types,
+					   dev_types_len, ies, ies_len);
+}
+
+static inline int wpa_drv_p2p_lo_stop(struct wpa_supplicant *wpa_s)
+{
+	if (!wpa_s->driver->p2p_lo_stop)
+		return -1;
+	return wpa_s->driver->p2p_lo_stop(wpa_s->drv_priv);
+}
+
 #endif /* DRIVER_I_H */

+ 8 - 0
wpa_supplicant/events.c

@@ -4067,6 +4067,14 @@ void wpa_supplicant_event(void *ctx, enum wpa_event_type event,
 					     &data->acs_selected_channels);
 #endif /* CONFIG_ACS */
 		break;
+	case EVENT_P2P_LO_STOP:
+#ifdef CONFIG_P2P
+		wpa_s->p2p_lo_started = 0;
+		wpa_msg(wpa_s, MSG_INFO, P2P_EVENT_LISTEN_OFFLOAD_STOP
+			P2P_LISTEN_OFFLOAD_STOP_REASON "reason=%d",
+			data->p2p_lo_stop.reason_code);
+#endif /* CONFIG_P2P */
+		break;
 	default:
 		wpa_msg(wpa_s, MSG_INFO, "Unknown event %d", event);
 		break;

+ 90 - 1
wpa_supplicant/p2p_supplicant.c

@@ -6632,6 +6632,12 @@ int wpas_p2p_listen(struct wpa_supplicant *wpa_s, unsigned int timeout)
 	if (wpa_s->global->p2p_disabled || wpa_s->global->p2p == NULL)
 		return -1;
 
+	if (wpa_s->p2p_lo_started) {
+		wpa_printf(MSG_DEBUG,
+			"P2P: Cannot start P2P listen, it is offloaded");
+		return -1;
+	}
+
 	wpa_supplicant_cancel_sched_scan(wpa_s);
 	wpas_p2p_clear_pending_action_tx(wpa_s);
 
@@ -6705,7 +6711,7 @@ int wpas_p2p_probe_req_rx(struct wpa_supplicant *wpa_s, const u8 *addr,
 		return 0;
 
 	switch (p2p_probe_req_rx(wpa_s->global->p2p, addr, dst, bssid,
-				 ie, ie_len, rx_freq)) {
+				 ie, ie_len, rx_freq, wpa_s->p2p_lo_started)) {
 	case P2P_PREQ_NOT_P2P:
 		wpas_notify_preq(wpa_s, addr, dst, bssid, ie, ie_len,
 				 ssi_signal);
@@ -9206,3 +9212,86 @@ void wpas_p2p_ap_deinit(struct wpa_supplicant *wpa_s)
 		wpa_s->ap_iface->bss[0]->p2p_group = NULL;
 	wpas_p2p_group_deinit(wpa_s);
 }
+
+
+int wpas_p2p_lo_start(struct wpa_supplicant *wpa_s, unsigned int freq,
+		      unsigned int period, unsigned int interval,
+		      unsigned int count)
+{
+	struct p2p_data *p2p = wpa_s->global->p2p;
+	u8 *device_types;
+	size_t dev_types_len;
+	struct wpabuf *buf;
+	int ret;
+
+	if (wpa_s->p2p_lo_started) {
+		wpa_dbg(wpa_s, MSG_DEBUG,
+			"P2P Listen offload is already started");
+		return 0;
+	}
+
+	if (wpa_s->global->p2p == NULL ||
+	    !(wpa_s->drv_flags & WPA_DRIVER_FLAGS_P2P_LISTEN_OFFLOAD)) {
+		wpa_printf(MSG_DEBUG, "P2P: Listen offload not supported");
+		return -1;
+	}
+
+	if (!p2p_supported_freq(wpa_s->global->p2p, freq)) {
+		wpa_printf(MSG_ERROR, "P2P: Input channel not supported: %u",
+			   freq);
+		return -1;
+	}
+
+	/* Get device type */
+	dev_types_len = (wpa_s->conf->num_sec_device_types + 1) *
+		WPS_DEV_TYPE_LEN;
+	device_types = os_malloc(dev_types_len);
+	if (!device_types)
+		return -1;
+	os_memcpy(device_types, wpa_s->conf->device_type, WPS_DEV_TYPE_LEN);
+	os_memcpy(&device_types[WPS_DEV_TYPE_LEN], wpa_s->conf->sec_device_type,
+		  wpa_s->conf->num_sec_device_types * WPS_DEV_TYPE_LEN);
+
+	/* Get Probe Response IE(s) */
+	buf = p2p_build_probe_resp_template(p2p, freq);
+	if (!buf) {
+		os_free(device_types);
+		return -1;
+	}
+
+	ret = wpa_drv_p2p_lo_start(wpa_s, freq, period, interval, count,
+				   device_types, dev_types_len,
+				   wpabuf_mhead_u8(buf), wpabuf_len(buf));
+	if (ret < 0)
+		wpa_dbg(wpa_s, MSG_DEBUG,
+			"P2P: Failed to start P2P listen offload");
+
+	os_free(device_types);
+	wpabuf_free(buf);
+
+	if (ret == 0) {
+		wpa_s->p2p_lo_started = 1;
+
+		/* Stop current P2P listen if any */
+		wpas_stop_listen(wpa_s);
+	}
+
+	return ret;
+}
+
+
+int wpas_p2p_lo_stop(struct wpa_supplicant *wpa_s)
+{
+	int ret;
+
+	if (!wpa_s->p2p_lo_started)
+		return 0;
+
+	ret = wpa_drv_p2p_lo_stop(wpa_s);
+	if (ret < 0)
+		wpa_dbg(wpa_s, MSG_DEBUG,
+			"P2P: Failed to stop P2P listen offload");
+
+	wpa_s->p2p_lo_started = 0;
+	return ret;
+}

+ 4 - 0
wpa_supplicant/p2p_supplicant.h

@@ -207,6 +207,10 @@ int wpas_p2p_wps_eapol_cb(struct wpa_supplicant *wpa_s);
 void wpas_p2p_wps_failed(struct wpa_supplicant *wpa_s,
 			 struct wps_event_fail *fail);
 int wpas_p2p_group_remove(struct wpa_supplicant *wpa_s, const char *ifname);
+int wpas_p2p_lo_start(struct wpa_supplicant *wpa_s, unsigned int freq,
+		      unsigned int period, unsigned int interval,
+		      unsigned int count);
+int wpas_p2p_lo_stop(struct wpa_supplicant *wpa_s);
 
 #else /* CONFIG_P2P */
 

+ 20 - 0
wpa_supplicant/wpa_cli.c

@@ -2892,6 +2892,20 @@ static int wpa_cli_cmd_get_pref_freq_list(struct wpa_ctrl *ctrl, int argc,
 }
 
 
+static int wpa_cli_cmd_p2p_lo_start(struct wpa_ctrl *ctrl, int argc,
+				    char *argv[])
+{
+	return wpa_cli_cmd(ctrl, "P2P_LO_START", 4, argc, argv);
+}
+
+
+static int wpa_cli_cmd_p2p_lo_stop(struct wpa_ctrl *ctrl, int argc,
+				   char *argv[])
+{
+	return wpa_cli_cmd(ctrl, "P2P_LO_STOP", 0, argc, argv);
+}
+
+
 enum wpa_cli_cmd_flags {
 	cli_cmd_flag_none		= 0x00,
 	cli_cmd_flag_sensitive		= 0x01
@@ -3477,6 +3491,12 @@ static const struct wpa_cli_cmd wpa_cli_commands[] = {
 	{ "get_pref_freq_list", wpa_cli_cmd_get_pref_freq_list, NULL,
 	  cli_cmd_flag_none,
 	  "<interface type> = retrieve preferred freq list for the specified interface type" },
+	{ "p2p_lo_start", wpa_cli_cmd_p2p_lo_start, NULL,
+	  cli_cmd_flag_none,
+	  "<freq> <period> <interval> <count> = start P2P listen offload" },
+	{ "p2p_lo_stop", wpa_cli_cmd_p2p_lo_stop, NULL,
+	  cli_cmd_flag_none,
+	  "= stop P2P listen offload" },
 	{ NULL, NULL, NULL, cli_cmd_flag_none, NULL }
 };
 

+ 1 - 0
wpa_supplicant/wpa_supplicant_i.h

@@ -893,6 +893,7 @@ struct wpa_supplicant {
 
 	unsigned int p2p_go_max_oper_chwidth;
 	unsigned int p2p_go_vht_center_freq2;
+	int p2p_lo_started;
 #endif /* CONFIG_P2P */
 
 	struct wpa_ssid *bgscan_ssid;