[RFC PATCH 12/23] nl80211: Add Proximity Detection (PD) wdev support

Peddolla Harshavardhan Reddy peddolla.reddy at oss.qualcomm.com
Tue Mar 31 22:27:02 PDT 2026


From: Veerendranath Jakkam <vjakkam at qti.qualcomm.com>

Introduce driver and nl80211 support for a Proximity Detection (PD) virtual
interface, enabled under CONFIG_PR.

A new set of driver ops is added:
- pd_start(): create and activate a PD wdev (NL80211_IFTYPE_PD), returning
  the effective PD MAC address
- pd_stop(): stop and destroy the PD wdev
- get_pd_addr(): query the MAC address of the active PD wdev

On the nl80211 side:
- Create a PD-only i802_bss (drv->pd_bss) that is not part of the regular
  BSS list but is associated with the parent wpa_s (ctx).
- nl80211_pd_start() creates a non-netdev PD interface via
  nl80211_create_iface(), obtains its wdev_id and MAC address, and
  activates it via nl80211_set_pr_dev(..., 1) which sends
  NL80211_CMD_START_PD.
- nl80211_pd_stop() deactivates and removes the PD wdev via
  nl80211_set_pr_dev(..., 0) and nl80211_del_non_netdev().
- nl80211_get_pd_addr() exposes the current PD MAC address to upper layers.
- Add helper lookups nl80211_get_pd_bss_by_addr() and
  nl80211_get_pd_bss_by_wdev() to route events arriving on the PD wdev
  back to the parent context.

The wdev_info helper and its netlink callback are generalized and renamed
to nl80211_wdev_handler_info so they can be reused both for PD creation
and for other non-netdev interfaces (replacing the previous
nl80211_wdev_handler).

wpa_supplicant is updated with wpa_drv_pd_start() and wpa_drv_pd_stop()
wrappers that call into the driver if CONFIG_PR is enabled. All PD-related
operations are safe no-ops when no PD wdev is active.

Signed-off-by: Veerendranath Jakkam <vjakkam at qti.qualcomm.com>
---
 src/drivers/driver.h         |  35 +++++++
 src/drivers/driver_nl80211.c | 193 ++++++++++++++++++++++++++++++-----
 src/drivers/driver_nl80211.h |  29 ++++++
 wpa_supplicant/driver_i.h    |  20 ++++
 4 files changed, 250 insertions(+), 27 deletions(-)

diff --git a/src/drivers/driver.h b/src/drivers/driver.h
index 79e3230da..c7c4e7571 100644
--- a/src/drivers/driver.h
+++ b/src/drivers/driver.h
@@ -5677,6 +5677,41 @@ struct wpa_driver_ops {
 	 */
 	void (*nan_stop)(void *priv);
 #endif /* CONFIG_NAN */
+
+#ifdef CONFIG_PR
+	/**
+	 * pd_start - Create and activate a PD (Proximity Detection) wdev
+	 * @priv: Private driver interface data
+	 * @addr: Requested MAC address for the PD wdev
+	 * @pd_addr: Buffer (ETH_ALEN) to receive the actual assigned MAC
+	 * Returns: 0 on success, -1 on failure
+	 *
+	 * Creates a PD virtual interface in the driver and activates it via
+	 * NL80211_CMD_START_PD.  The PD wdev is associated with the parent
+	 * BSS; all PD operations (key install, TX, peer measurement) are
+	 * routed through it by matching the PD MAC address.
+	 */
+	int (*pd_start)(void *priv, const u8 *addr, u8 *pd_addr);
+
+	/**
+	 * pd_stop - Stop and destroy the PD wdev
+	 * @priv: Private driver interface data
+	 *
+	 * Sends NL80211_CMD_STOP_PD, deletes the PD wdev via
+	 * NL80211_CMD_DEL_INTERFACE, and frees all associated driver
+	 * resources.  Safe to call when no PD wdev is active (no-op).
+	 */
+	void (*pd_stop)(void *priv);
+
+	/**
+	 * get_pd_addr - Return the MAC address of the active PD wdev
+	 * @priv: Private driver interface data
+	 * @addr: Buffer (ETH_ALEN) to receive the PD wdev MAC address
+	 * Returns: 0 on success, -1 if no PD wdev is active
+	 */
+	int (*get_pd_addr)(void *priv, u8 *addr);
+#endif /* CONFIG_PR */
+
 };
 
 /**
diff --git a/src/drivers/driver_nl80211.c b/src/drivers/driver_nl80211.c
index aa1ea3c61..8bf2ffebc 100644
--- a/src/drivers/driver_nl80211.c
+++ b/src/drivers/driver_nl80211.c
@@ -199,6 +199,9 @@ static int nl80211_put_mesh_config(struct nl_msg *msg,
 #endif /* CONFIG_MESH */
 static int i802_sta_disassoc(void *priv, const u8 *own_addr, const u8 *addr,
 			     u16 reason, int link_id);
+#ifdef CONFIG_PR
+static void nl80211_pd_stop(void *priv);
+#endif /* CONFIG_PR */
 
 
 /* Converts nl80211_chan_width to a common format */
@@ -3186,6 +3189,31 @@ static int nl80211_set_p2pdev(struct i802_bss *bss, int start)
 	return ret;
 }
 
+struct wdev_info {
+	u64 wdev_id;
+	int wdev_id_set;
+	u8 macaddr[ETH_ALEN];
+};
+
+static int nl80211_wdev_handler_info(struct nl_msg *msg, void *arg)
+{
+	struct genlmsghdr *gnlh = nlmsg_data(nlmsg_hdr(msg));
+	struct nlattr *tb[NL80211_ATTR_MAX + 1];
+	struct wdev_info *wi = arg;
+
+	nla_parse(tb, NL80211_ATTR_MAX, genlmsg_attrdata(gnlh, 0),
+		  genlmsg_attrlen(gnlh, 0), NULL);
+	if (tb[NL80211_ATTR_WDEV]) {
+		wi->wdev_id = nla_get_u64(tb[NL80211_ATTR_WDEV]);
+		wi->wdev_id_set = 1;
+	}
+	if (tb[NL80211_ATTR_MAC])
+		os_memcpy(wi->macaddr, nla_data(tb[NL80211_ATTR_MAC]),
+			  ETH_ALEN);
+	return NL_SKIP;
+}
+
+
 static int nl80211_set_pr_dev(struct i802_bss *bss, int start)
 {
 	struct nl_msg *msg;
@@ -3203,6 +3231,134 @@ static int nl80211_set_pr_dev(struct i802_bss *bss, int start)
 }
 
 
+#ifdef CONFIG_PR
+/**
+ * nl80211_pd_start - Create and activate a PD wdev
+ *
+ * Creates a PD virtual interface (NL80211_IFTYPE_PD) in the kernel and
+ * activates it via NL80211_CMD_START_PD.  The resulting i802_bss is stored
+ * in drv->pd_bss with ctx pointing to the parent wpa_s so that events
+ * arriving on the PD wdev_id are routed back to the parent.
+ */
+static int nl80211_pd_start(void *priv, const u8 *addr, u8 *pd_addr)
+{
+	struct i802_bss *bss = priv;
+	struct wpa_driver_nl80211_data *drv = bss->drv;
+	struct i802_bss *pd_bss;
+	struct wdev_info info;
+	int ret;
+
+	if (drv->pd_bss) {
+		wpa_printf(MSG_DEBUG, "nl80211: PD wdev already active");
+		if (pd_addr)
+			os_memcpy(pd_addr, drv->pd_bss->addr, ETH_ALEN);
+		return 0;
+	}
+
+	pd_bss = os_zalloc(sizeof(*pd_bss));
+	if (!pd_bss)
+		return -1;
+
+	pd_bss->drv = drv;
+	pd_bss->ctx = bss->ctx; /* parent wpa_s */
+	pd_bss->flink = &pd_bss->links[0];
+	os_strlcpy(pd_bss->ifname, "pd-wdev", sizeof(pd_bss->ifname));
+
+	os_memset(&info, 0, sizeof(info));
+	ret = nl80211_create_iface(drv, "pd-wdev", NL80211_IFTYPE_PD, addr,
+				   0, nl80211_wdev_handler_info, &info, 0);
+	if (ret || !info.wdev_id_set) {
+		wpa_printf(MSG_ERROR,
+			   "nl80211: Failed to create PD wdev (ret=%d)", ret);
+		os_free(pd_bss);
+		return -1;
+	}
+
+	pd_bss->wdev_id = info.wdev_id;
+	pd_bss->wdev_id_set = 1;
+	if (!is_zero_ether_addr(info.macaddr))
+		os_memcpy(pd_bss->addr, info.macaddr, ETH_ALEN);
+	else if (addr)
+		os_memcpy(pd_bss->addr, addr, ETH_ALEN);
+	os_memcpy(pd_bss->flink->addr, pd_bss->addr, ETH_ALEN);
+
+	/* Activate the PD device */
+	ret = nl80211_set_pr_dev(pd_bss, 1);
+	if (ret) {
+		wpa_printf(MSG_ERROR,
+			   "nl80211: Failed to start PD device (ret=%d)", ret);
+		goto failed;
+	}
+
+	if (nl80211_init_bss(pd_bss)) {
+		wpa_printf(MSG_ERROR,
+			   "nl80211: Failed to init PD BSS (ret=%d)", ret);
+		goto failed;
+	}
+
+	if (nl80211_mgmt_subscribe_non_ap(pd_bss))
+		wpa_printf(MSG_DEBUG,
+			   "nl80211: Failed to register frame processing for PD interface - ignore for now");
+
+	drv->pd_bss = pd_bss;
+
+	if (pd_addr)
+		os_memcpy(pd_addr, pd_bss->addr, ETH_ALEN);
+
+	wpa_printf(MSG_DEBUG,
+		   "nl80211: PD wdev created wdev_id=0x%llx addr=" MACSTR,
+		   (unsigned long long) pd_bss->wdev_id,
+		   MAC2STR(pd_bss->addr));
+	return 0;
+
+failed:
+	nl80211_del_non_netdev(pd_bss);
+	os_free(pd_bss);
+	return -1;
+}
+
+
+/**
+ * nl80211_pd_stop - Stop and destroy the PD wdev
+ */
+static void nl80211_pd_stop(void *priv)
+{
+	struct i802_bss *bss = priv;
+	struct wpa_driver_nl80211_data *drv = bss->drv;
+
+	if (!drv->pd_bss)
+		return;
+
+	wpa_printf(MSG_DEBUG,
+		   "nl80211: Stopping PD wdev wdev_id=0x%llx addr=" MACSTR,
+		   (unsigned long long) drv->pd_bss->wdev_id,
+		   MAC2STR(drv->pd_bss->addr));
+
+	nl80211_destroy_bss(drv->pd_bss);
+	nl80211_set_pr_dev(drv->pd_bss, 0);
+	nl80211_del_non_netdev(drv->pd_bss);
+	os_free(drv->pd_bss);
+	drv->pd_bss = NULL;
+}
+
+
+/**
+ * nl80211_get_pd_addr - Return the MAC address of the active PD wdev
+ */
+static int nl80211_get_pd_addr(void *priv, u8 *addr)
+{
+	struct i802_bss *bss = priv;
+	struct wpa_driver_nl80211_data *drv = bss->drv;
+
+	if (!drv->pd_bss || !addr)
+		return -1;
+
+	os_memcpy(addr, drv->pd_bss->addr, ETH_ALEN);
+	return 0;
+}
+#endif /* CONFIG_PR */
+
+
 #ifdef CONFIG_NAN
 static void nl80211_nan_stop(struct i802_bss *bss)
 {
@@ -3567,6 +3723,10 @@ static void wpa_driver_nl80211_deinit(struct i802_bss *bss)
 		eloop_unregister_read_sock(drv->eapol_tx_sock);
 	if (drv->eapol_tx_sock >= 0)
 		close(drv->eapol_tx_sock);
+#ifdef CONFIG_PR
+	if (drv->pd_bss)
+		nl80211_pd_stop(bss);
+#endif /* CONFIG_PR */
 
 	if (bss->nl_preq)
 		wpa_driver_nl80211_probe_req_report(bss, 0);
@@ -9386,32 +9546,6 @@ static int nl80211_vif_addr(struct wpa_driver_nl80211_data *drv, u8 *new_addr)
 }
 
 
-struct wdev_info {
-	u64 wdev_id;
-	int wdev_id_set;
-	u8 macaddr[ETH_ALEN];
-};
-
-static int nl80211_wdev_handler(struct nl_msg *msg, void *arg)
-{
-	struct genlmsghdr *gnlh = nlmsg_data(nlmsg_hdr(msg));
-	struct nlattr *tb[NL80211_ATTR_MAX + 1];
-	struct wdev_info *wi = arg;
-
-	nla_parse(tb, NL80211_ATTR_MAX, genlmsg_attrdata(gnlh, 0),
-		  genlmsg_attrlen(gnlh, 0), NULL);
-	if (tb[NL80211_ATTR_WDEV]) {
-		wi->wdev_id = nla_get_u64(tb[NL80211_ATTR_WDEV]);
-		wi->wdev_id_set = 1;
-	}
-
-	if (tb[NL80211_ATTR_MAC])
-		os_memcpy(wi->macaddr, nla_data(tb[NL80211_ATTR_MAC]),
-			  ETH_ALEN);
-
-	return NL_SKIP;
-}
-
 
 static int wpa_driver_nl80211_if_add(void *priv, enum wpa_driver_if_type type,
 				     const char *ifname, const u8 *addr,
@@ -9434,7 +9568,7 @@ static int wpa_driver_nl80211_if_add(void *priv, enum wpa_driver_if_type type,
 
 		os_memset(&nonnetdev_info, 0, sizeof(nonnetdev_info));
 		ifidx = nl80211_create_iface(drv, ifname, nlmode, addr,
-					     0, nl80211_wdev_handler,
+					     0, nl80211_wdev_handler_info,
 					     &nonnetdev_info, use_existing);
 		if (!nonnetdev_info.wdev_id_set || ifidx != 0) {
 			wpa_printf(MSG_ERROR,
@@ -15717,4 +15851,9 @@ const struct wpa_driver_ops wpa_driver_nl80211_ops = {
 	.nan_stop = wpa_driver_nl80211_nan_stop,
 	.nan_change_config = wpa_driver_nl80211_nan_change_config,
 #endif /* CONFIG_NAN */
+#ifdef CONFIG_PR
+	.pd_start = nl80211_pd_start,
+	.pd_stop = nl80211_pd_stop,
+	.get_pd_addr = nl80211_get_pd_addr,
+#endif /* CONFIG_PR */
 };
diff --git a/src/drivers/driver_nl80211.h b/src/drivers/driver_nl80211.h
index c0544aeb0..1d1187f62 100644
--- a/src/drivers/driver_nl80211.h
+++ b/src/drivers/driver_nl80211.h
@@ -286,12 +286,41 @@ struct wpa_driver_nl80211_data {
 #ifdef CONFIG_NAN
 	unsigned int nan_started:1;
 #endif /* CONFIG_NAN */
+#ifdef CONFIG_PR
+	struct i802_bss *pd_bss; /* PD wdev; not in the BSS list */
+#endif /* CONFIG_PR */
 };
 
 struct nl80211_err_info {
 	int link_id;
 };
 
+static inline struct i802_bss *
+nl80211_get_pd_bss_by_addr(struct wpa_driver_nl80211_data *drv,
+			   const u8 *addr)
+{
+#ifdef CONFIG_PR
+	if (addr && drv->pd_bss && ether_addr_equal(addr, drv->pd_bss->addr))
+		return drv->pd_bss;
+#endif /* CONFIG_PR */
+
+	return NULL;
+}
+
+
+static inline struct i802_bss *
+nl80211_get_pd_bss_by_wdev(struct wpa_driver_nl80211_data *drv,
+			   u64 wdev_id, int wdev_id_set)
+{
+#ifdef CONFIG_PR
+	if (wdev_id_set && drv->pd_bss && drv->pd_bss->wdev_id_set &&
+	    wdev_id == drv->pd_bss->wdev_id)
+		return drv->pd_bss;
+#endif /* CONFIG_PR */
+
+	return NULL;
+}
+
 void * nl80211_cmd(struct wpa_driver_nl80211_data *drv,
 		   struct nl_msg *msg, int flags, uint8_t cmd);
 struct nl_msg * nl80211_cmd_msg(struct i802_bss *bss, int flags, uint8_t cmd);
diff --git a/wpa_supplicant/driver_i.h b/wpa_supplicant/driver_i.h
index 5a8298288..f2eba74dc 100644
--- a/wpa_supplicant/driver_i.h
+++ b/wpa_supplicant/driver_i.h
@@ -738,6 +738,26 @@ static inline int wpa_drv_wowlan(struct wpa_supplicant *wpa_s,
 	return wpa_s->driver->set_wowlan(wpa_s->drv_priv, triggers);
 }
 
+#ifdef CONFIG_PR
+
+static inline int
+wpa_drv_pd_start(struct wpa_supplicant *wpa_s, const u8 *addr, u8 *pd_addr)
+{
+	if (!wpa_s->driver->pd_start)
+		return -1;
+	return wpa_s->driver->pd_start(wpa_s->drv_priv, addr, pd_addr);
+}
+
+static inline void
+wpa_drv_pd_stop(struct wpa_supplicant *wpa_s)
+{
+	if (!wpa_s->driver->pd_stop)
+		return;
+	wpa_s->driver->pd_stop(wpa_s->drv_priv);
+}
+
+#endif /* CONFIG_PR */
+
 static inline int wpa_drv_vendor_cmd(struct wpa_supplicant *wpa_s,
 				     int vendor_id, int subcmd, const u8 *data,
 				     size_t data_len,
-- 
2.34.1




More information about the Hostap mailing list