[PATCH v3 24/46] PR: Add remain-on-channel support for PASN responder role

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


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

When PR PASN is triggered in the responder role the existing code
queues a radio work item to send M1, but there is no mechanism to
keep the radio on the target frequency long enough to receive the
incoming Auth1 frame from the initiator.

Add a dedicated radio work item ("pr-pasn-roc") that starts a
remain-on-channel operation on the requested frequency before the
PASN exchange begins. A new pr_responder_mode flag is set while the
ROC is active so that wpas_pr_pasn_auth_rx() can recognise the
first Auth1 frame, cancel the ROC, and hand control to the common
PASN layer. The ROC cancel/expiry event is routed through a new
wpas_pr_cancel_remain_on_channel_cb() hook, which clears the
responder state and releases the radio work slot if M1 has not yet
arrived.

This change makes the responder path functional on the existing
interface without requiring a dedicated virtual interface, and
serves as the foundation on which the PD wdev support is built.

Signed-off-by: Peddolla Harshavardhan Reddy <peddolla.reddy at oss.qualcomm.com>
---
 wpa_supplicant/events.c           |   4 +
 wpa_supplicant/pr_supplicant.c    | 256 ++++++++++++++++++++++++++++++
 wpa_supplicant/pr_supplicant.h    |   8 +
 wpa_supplicant/wpa_supplicant_i.h |   9 ++
 4 files changed, 277 insertions(+)

diff --git a/wpa_supplicant/events.c b/wpa_supplicant/events.c
index 0684ada71..22d6ce196 100644
--- a/wpa_supplicant/events.c
+++ b/wpa_supplicant/events.c
@@ -7293,6 +7293,10 @@ void wpa_supplicant_event(void *ctx, enum wpa_event_type event,
 #endif /* CONFIG_DPP */
 		wpas_nan_usd_cancel_remain_on_channel_cb(
 			wpa_s, data->remain_on_channel.freq);
+#ifdef CONFIG_PR
+		wpas_pr_cancel_remain_on_channel_cb(
+			wpa_s, data->remain_on_channel.freq);
+#endif /* CONFIG_PR */
 		break;
 	case EVENT_EAPOL_RX:
 		wpa_supplicant_rx_eapol(wpa_s, data->eapol_rx.src,
diff --git a/wpa_supplicant/pr_supplicant.c b/wpa_supplicant/pr_supplicant.c
index 217b8dddc..0c20be22f 100644
--- a/wpa_supplicant/pr_supplicant.c
+++ b/wpa_supplicant/pr_supplicant.c
@@ -21,6 +21,15 @@
 
 #ifdef CONFIG_PASN
 static void wpas_pr_pasn_timeout(void *eloop_ctx, void *timeout_ctx);
+
+/* Total listen window (ms) for the PASN responder ROC */
+#define PR_PASN_RESPONDER_ROC_DURATION 5000
+static void wpas_pr_pasn_roc_total_timeout(void *eloop_ctx,
+					   void *timeout_ctx);
+static void wpas_pr_schedule_responder_roc(struct wpa_supplicant *wpa_s,
+					   unsigned int freq);
+static void wpas_pr_pasn_roc_start_cb(struct wpa_radio_work *work,
+				      int deinit);
 #endif /* CONFIG_PASN */
 
 
@@ -551,6 +560,12 @@ struct wpa_pr_pasn_auth_work {
 };
 
 
+struct wpa_pr_pasn_roc_work {
+	unsigned int freq;
+	u8 src_addr[ETH_ALEN];
+};
+
+
 static void wpas_pr_pasn_free_auth_work(struct wpa_pr_pasn_auth_work *awork)
 {
 	if (!awork)
@@ -569,6 +584,169 @@ static void wpas_pr_pasn_cancel_auth_work(struct wpa_supplicant *wpa_s)
 }
 
 
+/**
+ * wpas_pr_pasn_roc_work_done - Idempotent helper to complete ROC radio work
+ */
+static void wpas_pr_pasn_roc_work_done(struct wpa_supplicant *wpa_s)
+{
+	struct wpa_pr_pasn_roc_work *rwork;
+
+	if (!wpa_s->pr_roc_work)
+		return;
+
+	rwork = wpa_s->pr_roc_work->ctx;
+	os_free(rwork);
+	wpa_s->pr_roc_work->ctx = NULL;
+	radio_work_done(wpa_s->pr_roc_work);
+	wpa_s->pr_roc_work = NULL;
+}
+
+
+/**
+ * wpas_pr_schedule_responder_roc - Queue next ROC chunk for the responder
+ */
+static void wpas_pr_schedule_responder_roc(struct wpa_supplicant *wpa_s,
+					   unsigned int freq)
+{
+	struct wpa_pr_pasn_roc_work *rwork;
+
+	rwork = os_zalloc(sizeof(*rwork));
+	if (!rwork) {
+		wpa_printf(MSG_ERROR, "PR PASN: OOM restarting ROC");
+		goto fail;
+	}
+	rwork->freq = freq;
+
+	if (radio_add_work(wpa_s, freq, "pr-pasn-roc", 0,
+			   wpas_pr_pasn_roc_start_cb, rwork) < 0) {
+		wpa_printf(MSG_ERROR,
+			   "PR PASN: Failed to reschedule ROC");
+		os_free(rwork);
+		goto fail;
+	}
+	return;
+
+fail:
+	wpa_s->pr_responder_mode = false;
+	eloop_cancel_timeout(wpas_pr_pasn_roc_total_timeout, wpa_s, NULL);
+}
+
+
+/**
+ * wpas_pr_pasn_roc_total_timeout - Total ROC budget expiry; stop responder
+ */
+static void wpas_pr_pasn_roc_total_timeout(void *eloop_ctx,
+					   void *timeout_ctx)
+{
+	struct wpa_supplicant *wpa_s = eloop_ctx;
+
+	wpa_printf(MSG_DEBUG,
+		   "PR PASN: Total ROC budget expired, "
+		   "stopping responder listen");
+	wpa_s->pr_responder_mode = false;
+
+	if (wpa_s->pr_roc_work) {
+		wpa_drv_cancel_remain_on_channel(wpa_s);
+		wpa_s->off_channel_freq = 0;
+		wpa_s->roc_waiting_drv_freq = 0;
+		wpas_pr_pasn_roc_work_done(wpa_s);
+	}
+}
+
+
+/**
+ * wpas_pr_pasn_roc_start_cb - Radio work callback to start the responder ROC
+ */
+static void wpas_pr_pasn_roc_start_cb(struct wpa_radio_work *work, int deinit)
+{
+	struct wpa_supplicant *wpa_s = work->wpa_s;
+	struct wpa_pr_pasn_roc_work *rwork = work->ctx;
+	unsigned int chunk_ms;
+
+	if (deinit) {
+		if (work->started) {
+			/*
+			 * ROC was already started but the work is being
+			 * cancelled (e.g., interface removal).  Cancel the
+			 * driver ROC and clear the channel state.
+			 */
+			wpa_s->pr_roc_work = NULL;
+			wpa_drv_cancel_remain_on_channel(wpa_s);
+			wpa_s->off_channel_freq = 0;
+			wpa_s->roc_waiting_drv_freq = 0;
+		}
+		/*
+		 * Clear responder state and cancel the total-budget timer
+		 * regardless of whether the work was started or not — the
+		 * ROC will never fire now.
+		 */
+		eloop_cancel_timeout(wpas_pr_pasn_roc_total_timeout,
+				     wpa_s, NULL);
+		wpa_s->pr_responder_mode = false;
+		os_free(rwork);
+		work->ctx = NULL;
+		return;
+	}
+
+	wpa_s->pr_roc_work = work;
+
+	/* Use max_remain_on_chan as per-chunk duration, matching DPP/P2P */
+	chunk_ms = wpa_s->max_remain_on_chan;
+
+	wpa_printf(MSG_DEBUG,
+		   "PR PASN: Starting ROC chunk at freq %u MHz duration %u ms%s",
+		   rwork->freq, chunk_ms,
+		   is_zero_ether_addr(rwork->src_addr) ? "" :
+		   " with MAC filter");
+
+	if (wpa_drv_remain_on_channel(wpa_s, rwork->freq, chunk_ms,
+				      is_zero_ether_addr(rwork->src_addr) ?
+				      NULL : rwork->src_addr) < 0) {
+		wpa_printf(MSG_ERROR,
+			   "PR PASN: Failed to start ROC for responder");
+		eloop_cancel_timeout(wpas_pr_pasn_roc_total_timeout,
+				     wpa_s, NULL);
+		wpa_s->pr_responder_mode = false;
+		os_free(rwork);
+		work->ctx = NULL;
+		radio_work_done(work);
+		wpa_s->pr_roc_work = NULL;
+		return;
+	}
+
+	wpa_s->off_channel_freq = 0;
+	wpa_s->roc_waiting_drv_freq = rwork->freq;
+}
+
+
+/**
+ * wpas_pr_cancel_remain_on_channel_cb - ROC cancel/expiry callback for PR
+ */
+void wpas_pr_cancel_remain_on_channel_cb(struct wpa_supplicant *wpa_s,
+					 unsigned int freq)
+{
+	wpa_printf(MSG_DEBUG,
+		   "PR PASN: Remain on channel cancel for %u MHz", freq);
+
+	if (!wpa_s->pr_roc_work)
+		return;
+
+	wpas_pr_pasn_roc_work_done(wpa_s);
+
+	if (wpa_s->pr_responder_mode) {
+		/* Total-budget timer still live — restart another chunk */
+		wpa_printf(MSG_DEBUG,
+			   "PR PASN: ROC chunk expired, "
+			   "restarting for next chunk");
+		wpas_pr_schedule_responder_roc(wpa_s, freq);
+		return;
+	}
+
+	wpa_printf(MSG_DEBUG,
+		   "PR PASN: ROC total timeout reached, responder done");
+}
+
+
 static void wpas_pr_pasn_timeout(void *eloop_ctx, void *timeout_ctx)
 {
 	struct wpa_supplicant *wpa_s = eloop_ctx;
@@ -594,6 +772,7 @@ static void wpas_pr_pasn_auth_start_cb(struct wpa_radio_work *work, int deinit)
 			eloop_cancel_timeout(wpas_pr_pasn_timeout, wpa_s, NULL);
 
 		wpas_pr_pasn_free_auth_work(awork);
+		work->ctx = NULL;
 		return;
 	}
 
@@ -629,6 +808,37 @@ int wpas_pr_initiate_pasn_auth(struct wpa_supplicant *wpa_s,
 {
 	struct wpa_pr_pasn_auth_work *awork;
 
+	if (pasn_role == PR_ROLE_PASN_RESPONDER) {
+		struct wpa_pr_pasn_roc_work *rwork;
+
+		rwork = os_zalloc(sizeof(*rwork));
+		if (!rwork)
+			return -1;
+
+		rwork->freq = freq;
+
+		wpa_s->pr_responder_mode = true;
+
+		if (radio_add_work(wpa_s, freq, "pr-pasn-roc", 0,
+				   wpas_pr_pasn_roc_start_cb, rwork) < 0) {
+			wpa_printf(MSG_ERROR,
+				   "PR PASN: Failed to schedule ROC for responder");
+			os_free(rwork);
+			wpa_s->pr_responder_mode = false;
+			return -1;
+		}
+
+		/*
+		 * Register the total-budget timer.  When it fires it clears
+		 * pr_responder_mode so the cancel callback stops restarting
+		 * chunks.
+		 */
+		eloop_register_timeout(0, PR_PASN_RESPONDER_ROC_DURATION * 1000,
+				       wpas_pr_pasn_roc_total_timeout,
+				       wpa_s, NULL);
+		return 0;
+	}
+
 	wpas_pr_pasn_cancel_auth_work(wpa_s);
 	wpa_s->pr_pasn_auth_work = NULL;
 
@@ -672,9 +882,55 @@ int wpas_pr_pasn_auth_rx(struct wpa_supplicant *wpa_s,
 			 int freq)
 {
 	struct pr_data *pr = wpa_s->global->pr;
+	u16 auth_transaction;
 
 	if (!pr)
 		return -2;
+
+	/*
+	 * Responder path: when we are waiting for PASN M1 on the parent
+	 * interface ROC, create the PD wdev on first receipt of Auth1 before
+	 * handing the frame to the common layer.
+	 */
+	if (wpa_s->pr_responder_mode &&
+	    len >= offsetof(struct ieee80211_mgmt, u.auth.variable)) {
+		auth_transaction =
+			le_to_host16(mgmt->u.auth.auth_transaction);
+
+		if (auth_transaction == WLAN_AUTH_TR_SEQ_PASN_AUTH1) {
+			/*
+			 * Cancel the total-budget timer first so it does not
+			 * fire after we have already handed off to the PASN
+			 * layer.
+			 */
+			eloop_cancel_timeout(wpas_pr_pasn_roc_total_timeout,
+					     wpa_s, NULL);
+
+			/*
+			 * Cancel ROC on the listening interface; the dedicated
+			 * PR interface (if created) will handle all subsequent
+			 * frames.  Then complete the radio work item so the
+			 * radio is released for other operations.
+			 *
+			 * wpas_pr_cancel_remain_on_channel_cb() may also fire
+			 * asynchronously when the driver processes the cancel
+			 * request, but wpas_pr_pasn_roc_work_done() is
+			 * idempotent (no-op if pr_roc_work is already NULL).
+			 */
+			wpa_drv_cancel_remain_on_channel(wpa_s);
+			wpa_s->off_channel_freq = 0;
+			wpa_s->roc_waiting_drv_freq = 0;
+			wpas_pr_pasn_roc_work_done(wpa_s);
+
+			/* Clear responder mode */
+			wpa_s->pr_responder_mode = false;
+
+			wpa_printf(MSG_DEBUG,
+				   "PR PASN: M1 processed, proceeding with "
+				   "PASN");
+		}
+	}
+
 	return pr_pasn_auth_rx(pr, mgmt, len, freq);
 }
 
diff --git a/wpa_supplicant/pr_supplicant.h b/wpa_supplicant/pr_supplicant.h
index ee40a9251..7cf29705e 100644
--- a/wpa_supplicant/pr_supplicant.h
+++ b/wpa_supplicant/pr_supplicant.h
@@ -36,6 +36,8 @@ int wpas_pr_pasn_auth_tx_status(struct wpa_supplicant *wpa_s, const u8 *data,
 int wpas_pr_pasn_auth_rx(struct wpa_supplicant *wpa_s,
 			 const struct ieee80211_mgmt *mgmt, size_t len,
 			 int freq);
+void wpas_pr_cancel_remain_on_channel_cb(struct wpa_supplicant *wpa_s,
+					 unsigned int freq);
 
 #else /* CONFIG_PR */
 
@@ -98,6 +100,12 @@ static inline int wpas_pr_pasn_auth_rx(struct wpa_supplicant *wpa_s,
 	return 0;
 }
 
+static inline void
+wpas_pr_cancel_remain_on_channel_cb(struct wpa_supplicant *wpa_s,
+				    unsigned int freq)
+{
+}
+
 #endif /* CONFIG_PR */
 
 #endif /* PR_SUPPLICANT_H */
diff --git a/wpa_supplicant/wpa_supplicant_i.h b/wpa_supplicant/wpa_supplicant_i.h
index 828795b19..85994de80 100644
--- a/wpa_supplicant/wpa_supplicant_i.h
+++ b/wpa_supplicant/wpa_supplicant_i.h
@@ -1681,6 +1681,7 @@ struct wpa_supplicant {
 	struct wpa_radio_work *p2p_pasn_auth_work;
 #endif /* CONFIG_P2P */
 	struct wpa_radio_work *pr_pasn_auth_work;
+	struct wpa_radio_work *pr_roc_work;
 #endif /* CONFIG_PASN */
 
 	bool is_6ghz_enabled;
@@ -1757,6 +1758,14 @@ struct wpa_supplicant {
 	u8 pmkid_anonce[NONCE_LEN];
 	bool pmkid_anonce_set;
 #endif /* CONFIG_PMKSA_PRIVACY */
+	/**
+	 * pr_responder_mode - Waiting for PASN M1 as responder
+	 *
+	 * Set when ROC has been started on this interface to listen for an
+	 * incoming PASN Auth1 frame.  Cleared once the dedicated PR interface
+	 * is created on M1 reception.
+	 */
+	bool pr_responder_mode;
 };
 
 
-- 
2.34.1




More information about the Hostap mailing list