[PATCH v3 10/46] nl80211: Parse Peer Measurement (PMSR) capabilities

Kavita Kavita kavita.kavita at oss.qualcomm.com
Wed May 13 02:59:34 PDT 2026


From: Peddolla Harshavardhan Reddy <peddolla.reddy at oss.qualcomm.com>

Add support for parsing FTM capabilities for Proximity Ranging (PR).
Parse PMSR attributes and store them in nested structs (ista, rsta,
ranging_type). This includes support for:
- ISTA and RSTA role capabilities (NTB, TB, EDCA support)
- Per-role peer limits
- Ranging type support (infrastructure and proximity detection)
- Concurrent ISTA/RSTA support
- 6GHz support
- Proximity Detection preambles and bandwidths
- Minimum ranging intervals for EDCA and NTB

The parsed values are stored in wpa_driver_capa.

Signed-off-by: Peddolla Harshavardhan Reddy <peddolla.reddy at oss.qualcomm.com>
---
 src/common/proximity_ranging.h    |  14 +++
 src/drivers/driver.h              |  29 +++++
 src/drivers/driver_nl80211_capa.c | 170 ++++++++++++++++++++++++++++++
 wpa_supplicant/pr_supplicant.c    |   8 ++
 wpa_supplicant/wpa_supplicant.c   |   6 +-
 5 files changed, 226 insertions(+), 1 deletion(-)

diff --git a/src/common/proximity_ranging.h b/src/common/proximity_ranging.h
index 14106d2fb..85fc40770 100644
--- a/src/common/proximity_ranging.h
+++ b/src/common/proximity_ranging.h
@@ -358,6 +358,8 @@ struct pr_config {
 
 	u8 edca_format_and_bw;
 
+	u32 edca_min_ranging_interval;
+
 	u8 max_tx_antenna;
 
 	u8 max_rx_antenna;
@@ -368,6 +370,16 @@ struct pr_config {
 
 	bool ntb_rsta_support;
 
+	bool concurrent_ista_rsta;
+
+	u32 pmsr_max_peers;
+
+	u32 pr_max_peer_ista_role;
+
+	u32 pr_max_peer_rsta_role;
+
+	u8 max_ftms_per_burst;
+
 	bool secure_he_ltf;
 
 	u8 max_tx_ltf_repetations;
@@ -388,6 +400,8 @@ struct pr_config {
 
 	u8 ntb_format_and_bw;
 
+	u32 ntb_min_ranging_interval;
+
 	struct pr_channels ntb_channels;
 
 	bool support_6ghz;
diff --git a/src/drivers/driver.h b/src/drivers/driver.h
index bbab06e7b..32cdbe800 100644
--- a/src/drivers/driver.h
+++ b/src/drivers/driver.h
@@ -2660,6 +2660,7 @@ struct wpa_driver_capa {
 	u8 edca_format_and_bw;
 	u8 max_tx_antenna;
 	u8 max_rx_antenna;
+	u32 edca_min_ranging_interval;
 
 	/* NTB based ranging capabilities */
 	u8 ntb_format_and_bw;
@@ -2671,6 +2672,34 @@ struct wpa_driver_capa {
 	u8 max_rx_sts_gt_80;
 	u8 max_tx_sts_le_80;
 	u8 max_tx_sts_gt_80;
+	u32 ntb_min_ranging_interval;
+
+	/* Peer measurement capabilities */
+	u32 pmsr_max_peers;
+	u8 max_ftms_per_burst;
+	bool concurrent_ista_rsta;
+	bool support_6ghz;
+	u32 pd_preambles;
+	u32 pd_bandwidths;
+
+	struct {
+		bool support_ntb;
+		bool support_tb;
+		bool support_edca;
+		u32 max_peers;
+	} ista;
+
+	struct {
+		bool support_ntb;
+		bool support_tb;
+		bool support_edca;
+		u32 max_peers;
+	} rsta;
+
+	struct {
+		bool infra_support;
+		bool pd_support;
+	} ranging_type;
 
 #ifdef CONFIG_NAN
 	struct nan_capa nan_capa;
diff --git a/src/drivers/driver_nl80211_capa.c b/src/drivers/driver_nl80211_capa.c
index 1799c5395..281f06d22 100644
--- a/src/drivers/driver_nl80211_capa.c
+++ b/src/drivers/driver_nl80211_capa.c
@@ -18,6 +18,7 @@
 #include "common/qca-vendor-attr.h"
 #include "common/brcm_vendor.h"
 #include "driver_nl80211.h"
+#include "common/proximity_ranging.h"
 
 
 static int protocol_feature_handler(struct nl_msg *msg, void *arg)
@@ -1136,6 +1137,170 @@ out:
 #endif /* CONFIG_NAN */
 
 
+#ifdef CONFIG_PR
+
+static void pmsr_type_ftm_handler(struct wpa_driver_nl80211_data *drv,
+				  struct nlattr *capa)
+{
+	u32 max_rx_sts, max_tx_sts;
+	struct nlattr *tb[NL80211_PMSR_FTM_CAPA_ATTR_MAX + 1];
+
+	if (nla_parse_nested(tb, NL80211_PMSR_FTM_CAPA_ATTR_MAX, capa, NULL))
+		return;
+
+	/* Parse ISTA capabilities */
+	if (tb[NL80211_PMSR_FTM_CAPA_ATTR_ISTA_CAPS]) {
+		struct nlattr *ista_caps[NL80211_PMSR_FTM_CAPA_ATTR_MAX + 1];
+
+		if (!nla_parse_nested(ista_caps, NL80211_PMSR_FTM_CAPA_ATTR_MAX,
+				      tb[NL80211_PMSR_FTM_CAPA_ATTR_ISTA_CAPS],
+				      NULL)) {
+			drv->capa.ista.support_ntb =
+				!!ista_caps[NL80211_PMSR_FTM_CAPA_ATTR_SUPPORT_NTB];
+			drv->capa.ista.support_tb =
+				!!ista_caps[NL80211_PMSR_FTM_CAPA_ATTR_SUPPORT_TB];
+			drv->capa.ista.support_edca =
+				!!ista_caps[NL80211_PMSR_FTM_CAPA_ATTR_SUPPORT_EDCA];
+			if (ista_caps[NL80211_PMSR_ATTR_MAX_PEER_ISTA_ROLE])
+				drv->capa.ista.max_peers =
+					nla_get_u32(ista_caps[NL80211_PMSR_ATTR_MAX_PEER_ISTA_ROLE]);
+		}
+	}
+
+	/* Parse ranging type capabilities */
+	if (tb[NL80211_PMSR_FTM_CAPA_ATTR_TYPE_CAPS]) {
+		struct nlattr *type_caps[NL80211_PMSR_FTM_TYPE_CAPA_ATTR_MAX + 1];
+
+		if (!nla_parse_nested(type_caps,
+				      NL80211_PMSR_FTM_TYPE_CAPA_ATTR_MAX,
+				      tb[NL80211_PMSR_FTM_CAPA_ATTR_TYPE_CAPS],
+				      NULL)) {
+			drv->capa.ranging_type.infra_support =
+				!!type_caps[NL80211_PMSR_FTM_TYPE_CAPA_ATTR_INFRA_SUPPORT];
+			drv->capa.ranging_type.pd_support =
+				!!type_caps[NL80211_PMSR_FTM_TYPE_CAPA_ATTR_PD_SUPPORT];
+		}
+	}
+
+	drv->capa.concurrent_ista_rsta =
+		!!tb[NL80211_PMSR_FTM_CAPA_ATTR_CONCURRENT_ISTA_RSTA_SUPPORT];
+
+	if (tb[NL80211_PMSR_FTM_CAPA_ATTR_MAX_TX_LTF_REP])
+		drv->capa.max_tx_ltf_repetations =
+			nla_get_u32(tb[NL80211_PMSR_FTM_CAPA_ATTR_MAX_TX_LTF_REP]);
+	if (tb[NL80211_PMSR_FTM_CAPA_ATTR_MAX_RX_LTF_REP])
+		drv->capa.max_rx_ltf_repetations =
+			nla_get_u32(tb[NL80211_PMSR_FTM_CAPA_ATTR_MAX_RX_LTF_REP]);
+
+	if (tb[NL80211_PMSR_FTM_CAPA_ATTR_MAX_RX_STS]) {
+		max_rx_sts = nla_get_u32(tb[NL80211_PMSR_FTM_CAPA_ATTR_MAX_RX_STS]);
+		drv->capa.max_rx_sts_le_80 = max_rx_sts;
+		drv->capa.max_rx_sts_gt_80 = max_rx_sts;
+	}
+	if (tb[NL80211_PMSR_FTM_CAPA_ATTR_MAX_TX_STS]) {
+		max_tx_sts = nla_get_u32(tb[NL80211_PMSR_FTM_CAPA_ATTR_MAX_TX_STS]);
+		drv->capa.max_tx_sts_le_80 = max_tx_sts;
+		drv->capa.max_tx_sts_gt_80 = max_tx_sts;
+	}
+
+	if (tb[NL80211_PMSR_FTM_CAPA_ATTR_MAX_TOTAL_LTF_TX])
+		drv->capa.max_tx_ltf_total =
+			nla_get_u32(tb[NL80211_PMSR_FTM_CAPA_ATTR_MAX_TOTAL_LTF_TX]);
+	if (tb[NL80211_PMSR_FTM_CAPA_ATTR_MAX_TOTAL_LTF_RX])
+		drv->capa.max_rx_ltf_total =
+			nla_get_u32(tb[NL80211_PMSR_FTM_CAPA_ATTR_MAX_TOTAL_LTF_RX]);
+	if (tb[NL80211_PMSR_FTM_CAPA_ATTR_MAX_FTMS_PER_BURST])
+		drv->capa.max_ftms_per_burst =
+			nla_get_u8(tb[NL80211_PMSR_FTM_CAPA_ATTR_MAX_FTMS_PER_BURST]);
+
+	/* Parse RSTA capabilities */
+	if (tb[NL80211_PMSR_FTM_CAPA_ATTR_RSTA_SUPPORT] &&
+	    tb[NL80211_PMSR_FTM_CAPA_ATTR_RSTA_CAPS]) {
+		struct nlattr *rsta_caps[NL80211_PMSR_FTM_CAPA_ATTR_MAX + 1];
+
+		if (!nla_parse_nested(rsta_caps, NL80211_PMSR_FTM_CAPA_ATTR_MAX,
+				      tb[NL80211_PMSR_FTM_CAPA_ATTR_RSTA_CAPS],
+				      NULL)) {
+			drv->capa.rsta.support_ntb =
+				!!rsta_caps[NL80211_PMSR_FTM_CAPA_ATTR_SUPPORT_NTB];
+			drv->capa.rsta.support_tb =
+				!!rsta_caps[NL80211_PMSR_FTM_CAPA_ATTR_SUPPORT_TB];
+			drv->capa.rsta.support_edca =
+				!!rsta_caps[NL80211_PMSR_FTM_CAPA_ATTR_SUPPORT_EDCA];
+			if (rsta_caps[NL80211_PMSR_ATTR_MAX_PEER_RSTA_ROLE])
+				drv->capa.rsta.max_peers =
+					nla_get_u32(rsta_caps[NL80211_PMSR_ATTR_MAX_PEER_RSTA_ROLE]);
+		}
+	}
+
+	if (tb[NL80211_PMSR_FTM_CAPA_ATTR_MAX_NUM_TX_ANTENNAS])
+		drv->capa.max_tx_antenna =
+			nla_get_u8(tb[NL80211_PMSR_FTM_CAPA_ATTR_MAX_NUM_TX_ANTENNAS]);
+	if (tb[NL80211_PMSR_FTM_CAPA_ATTR_MAX_NUM_RX_ANTENNAS])
+		drv->capa.max_rx_antenna =
+			nla_get_u8(tb[NL80211_PMSR_FTM_CAPA_ATTR_MAX_NUM_RX_ANTENNAS]);
+
+	if (tb[NL80211_PMSR_FTM_CAPA_ATTR_MIN_INTERVAL_EDCA])
+		drv->capa.edca_min_ranging_interval =
+			nla_get_u32(tb[NL80211_PMSR_FTM_CAPA_ATTR_MIN_INTERVAL_EDCA]);
+	if (tb[NL80211_PMSR_FTM_CAPA_ATTR_MIN_INTERVAL_NTB])
+		drv->capa.ntb_min_ranging_interval =
+			nla_get_u32(tb[NL80211_PMSR_FTM_CAPA_ATTR_MIN_INTERVAL_NTB]);
+
+	/* Parse additional ranging capabilities */
+	drv->capa.support_6ghz =
+		!!tb[NL80211_PMSR_FTM_CAPA_ATTR_6GHZ_SUPPORT];
+
+	if (tb[NL80211_PMSR_FTM_CAPA_ATTR_PD_PREAMBLES])
+		drv->capa.pd_preambles =
+			nla_get_u32(tb[NL80211_PMSR_FTM_CAPA_ATTR_PD_PREAMBLES]);
+	if (tb[NL80211_PMSR_FTM_CAPA_ATTR_PD_BANDWIDTHS])
+		drv->capa.pd_bandwidths =
+			nla_get_u32(tb[NL80211_PMSR_FTM_CAPA_ATTR_PD_BANDWIDTHS]);
+}
+
+
+static void wiphy_info_pmsr_type_capa(struct wpa_driver_nl80211_data *drv,
+				      struct nlattr *attr)
+{
+	struct nlattr *pos;
+	int rem;
+
+	nla_for_each_nested(pos, attr, rem) {
+		if (nla_type(pos) == NL80211_PMSR_TYPE_FTM)
+			pmsr_type_ftm_handler(drv, pos);
+	}
+}
+
+
+static void wiphy_info_pmsr_capa(struct wpa_driver_nl80211_data *drv,
+				 struct nlattr *tb[])
+{
+	struct nlattr *pmsr_capa[NL80211_PMSR_ATTR_MAX + 1];
+	static struct nla_policy
+	pmsr_policy[NL80211_PMSR_ATTR_MAX + 1] = {
+		[NL80211_PMSR_ATTR_MAX_PEERS] = { .type = NLA_U32 },
+		[NL80211_PMSR_ATTR_TYPE_CAPA] = { .type = NLA_NESTED },
+	};
+
+	if (nla_parse_nested(pmsr_capa, NL80211_PMSR_ATTR_MAX,
+			     tb[NL80211_ATTR_PEER_MEASUREMENTS],
+			     pmsr_policy)) {
+		wpa_printf(MSG_DEBUG, "nl80211: Failed to parse PMSR capabilities");
+		return;
+	}
+
+	if (pmsr_capa[NL80211_PMSR_ATTR_MAX_PEERS])
+		drv->capa.pmsr_max_peers =
+			nla_get_u32(pmsr_capa[NL80211_PMSR_ATTR_MAX_PEERS]);
+	if (pmsr_capa[NL80211_PMSR_ATTR_TYPE_CAPA])
+		wiphy_info_pmsr_type_capa(drv,
+					  pmsr_capa[NL80211_PMSR_ATTR_TYPE_CAPA]);
+}
+
+#endif /* CONFIG_PR */
+
+
 static int wiphy_info_handler(struct nl_msg *msg, void *arg)
 {
 	struct nlattr *tb[NL80211_ATTR_MAX + 1];
@@ -1262,6 +1427,11 @@ static int wiphy_info_handler(struct nl_msg *msg, void *arg)
 
 	wiphy_info_extended_capab(drv, tb[NL80211_ATTR_IFTYPE_EXT_CAPA]);
 
+#ifdef CONFIG_PR
+	if (tb[NL80211_ATTR_PEER_MEASUREMENTS])
+		wiphy_info_pmsr_capa(drv, tb);
+#endif /* CONFIG_PR */
+
 	if (tb[NL80211_ATTR_VENDOR_DATA]) {
 		struct nlattr *nl;
 		int rem;
diff --git a/wpa_supplicant/pr_supplicant.c b/wpa_supplicant/pr_supplicant.c
index f47407e5c..0af25f5d4 100644
--- a/wpa_supplicant/pr_supplicant.c
+++ b/wpa_supplicant/pr_supplicant.c
@@ -375,6 +375,14 @@ int wpas_pr_init(struct wpa_global *global, struct wpa_supplicant *wpa_s,
 	pr.max_tx_sts_le_80 = capa->max_tx_sts_le_80;
 	pr.max_tx_sts_gt_80 = capa->max_tx_sts_gt_80;
 
+	pr.edca_min_ranging_interval = capa->edca_min_ranging_interval;
+	pr.ntb_min_ranging_interval = capa->ntb_min_ranging_interval;
+	pr.concurrent_ista_rsta = capa->concurrent_ista_rsta;
+	pr.pmsr_max_peers = capa->pmsr_max_peers;
+	pr.pr_max_peer_ista_role = capa->ista.max_peers;
+	pr.pr_max_peer_rsta_role = capa->rsta.max_peers;
+	pr.max_ftms_per_burst = capa->max_ftms_per_burst;
+
 	pr.support_6ghz = wpas_is_6ghz_supported(wpa_s, true);
 
 	pr.pasn_send_mgmt = wpas_pr_pasn_send_mgmt;
diff --git a/wpa_supplicant/wpa_supplicant.c b/wpa_supplicant/wpa_supplicant.c
index 5414eab0f..f54cb02ed 100644
--- a/wpa_supplicant/wpa_supplicant.c
+++ b/wpa_supplicant/wpa_supplicant.c
@@ -8071,8 +8071,12 @@ static int wpa_supplicant_init_iface(struct wpa_supplicant *wpa_s,
 		return -1;
 	}
 
-	if (wpas_pr_init(wpa_s->global, wpa_s, &capa) < 0)
+	if (!capa.ranging_type.pd_support) {
+		wpa_printf(MSG_DEBUG,
+			   "PR: Driver does not support Proximity Ranging - PR disabled");
+	} else if (wpas_pr_init(wpa_s->global, wpa_s, &capa) < 0) {
 		return -1;
+	}
 
 	if (wpa_bss_init(wpa_s) < 0)
 		return -1;
-- 
2.34.1




More information about the Hostap mailing list