diff mbox series

[2/4] nl80211: fix scan request and its related events handling with MLO

Message ID 20240422111906.2928263-3-quic_adisi@quicinc.com
State Changes Requested
Headers show
Series MLO related fixes for scan/ACS | expand

Commit Message

Aditya Kumar Singh April 22, 2024, 11:19 a.m. UTC
Currently, whenever a scan is started, it uses drv's first BSS only
whether it is AP or STA interface. However with MLO AP related changes,
same drv could be used by other BSSes as well which needs scanning. Hence,
the current logic will not work since scan needs to be handled on
non-first BSS as well.

Add changes to move the logic of always using drv's first BSS during scan
events to using BSS on which the event arrived.

Also, for ML AP operation, even though BSS is same, link BSS also
needs to be identified. Hence add a back pointer in BSS struct which would
be used to point to the link BSS which requested scan on that BSS.
This will help in routing the scan events to appropriate BSS ctx.

Signed-off-by: Aditya Kumar Singh <quic_adisi@quicinc.com>
---
 src/ap/ap_drv_ops.c                |   6 ++
 src/drivers/driver.h               |   8 ++
 src/drivers/driver_nl80211.c       |  20 +++-
 src/drivers/driver_nl80211.h       |   3 +-
 src/drivers/driver_nl80211_event.c | 144 +++++++++++++++++++----------
 src/drivers/driver_nl80211_scan.c  |  27 +++++-
 wpa_supplicant/scan.c              |   1 +
 7 files changed, 152 insertions(+), 57 deletions(-)

Comments

Jouni Malinen June 11, 2024, 9:14 p.m. UTC | #1
On Mon, Apr 22, 2024 at 04:49:04PM +0530, Aditya Kumar Singh wrote:
> Currently, whenever a scan is started, it uses drv's first BSS only
> whether it is AP or STA interface. However with MLO AP related changes,
> same drv could be used by other BSSes as well which needs scanning. Hence,
> the current logic will not work since scan needs to be handled on
> non-first BSS as well.
> 
> Add changes to move the logic of always using drv's first BSS during scan
> events to using BSS on which the event arrived.
> 
> Also, for ML AP operation, even though BSS is same, link BSS also
> needs to be identified. Hence add a back pointer in BSS struct which would
> be used to point to the link BSS which requested scan on that BSS.
> This will help in routing the scan events to appropriate BSS ctx.


> diff --git a/src/ap/ap_drv_ops.c b/src/ap/ap_drv_ops.c
> @@ -781,6 +781,12 @@ bool hostapd_drv_nl80211(struct hostapd_data *hapd)
>  int hostapd_driver_scan(struct hostapd_data *hapd,
>  			struct wpa_driver_scan_params *params)
>  {
> +	params->mlo_link_id = -1;

Isn't that needed in wpa_supplicant/driver_i.h for wpa_drv_scan() as
well? This is going in as 0 now which does not feel correct.

> diff --git a/src/drivers/driver.h b/src/drivers/driver.h
> @@ -709,6 +709,14 @@ struct wpa_driver_scan_params {
>  	s8 link_id;
>  
> +	/**
> +	 * mlo_link_id - Link ID (in case of MLO)
> +	 *
> +	 * If this is set to value >= 0, after scan completion, this would be
> +	 * used to route the event to proper driver private data.
> +	 */
> +	u8 mlo_link_id;

That should be s8 for that >= 0 to make sense.

> diff --git a/src/drivers/driver_nl80211.c b/src/drivers/driver_nl80211.c
> index c6af0f02f619..b2efcddf058f 100644
> --- a/src/drivers/driver_nl80211.c
> +++ b/src/drivers/driver_nl80211.c
> @@ -4195,6 +4195,22 @@ struct i802_link * nl80211_get_link(struct i802_bss *bss, s8 link_id)

> @@ -10259,7 +10275,9 @@ static int wpa_driver_nl80211_get_survey(void *priv, unsigned int freq)
>  	if (err)
>  		wpa_printf(MSG_ERROR, "nl80211: Failed to process survey data");
>  	else
> -		wpa_supplicant_event(drv->ctx, EVENT_SURVEY, &data);
> +		wpa_supplicant_event((bss->scan_link && bss->scan_link->ctx) ?
> +						bss->scan_link->ctx : bss->ctx,
> +						EVENT_SURVEY, &data);

Wrong indentation level..

> diff --git a/src/drivers/driver_nl80211_event.c b/src/drivers/driver_nl80211_event.c

> +			wpa_printf(MSG_DEBUG, "nl80211: Scan event on unknown link");

Too long a line (>80 chars); should be split after the comma.

> +		if (mld_link->ctx) {
> +			u8 link_id = nl80211_get_link_id_from_link(bss, mld_link);

Too long a line; should be

u8 link_id;

link_id = ...

> +			wpa_printf(MSG_DEBUG, "nl80211: Scan event for link_id %d",

Too long a line.

> -static void do_process_drv_event(struct i802_bss *bss, int cmd,
> -				 struct nlattr **tb)
> +static void nl80211_scan_event(struct i802_bss *bss, enum nl80211_commands cmd,
> +			       struct nlattr *tb[])

This diff gets really confusing since this mixes is refactoring (moving
the scan event handlers to a separate function) and actual functionality
changes. Please do not mix such things into a single patch. This is much
clearer to review when that nl80211_scan_event() is not added and the
functionality within do_process_drv_event() is modified instead. A
separate patch can be proposed for refactoring, but to be honest, I
don't see the value of that for this particular case.

> diff --git a/src/drivers/driver_nl80211_scan.c b/src/drivers/driver_nl80211_scan.c
> @@ -440,11 +449,19 @@ int wpa_driver_nl80211_scan(struct i802_bss *bss,
> -	eloop_cancel_timeout(wpa_driver_nl80211_scan_timeout, drv, drv->ctx);
> +	eloop_cancel_timeout(wpa_driver_nl80211_scan_timeout, drv, bss->ctx);

This looks scary and/or incomplete.. There are many locations where the
eloop timeouts were not modified and the calls there still use drv->ctx.
This is the case for the vendor commands for scan and also for clearing
up eloop timeouts when deinitializing. It was far from obvious that this
would work correctly for all cases.
Aditya Kumar Singh June 12, 2024, 9:12 a.m. UTC | #2
On 6/12/24 02:44, Jouni Malinen wrote:
> On Mon, Apr 22, 2024 at 04:49:04PM +0530, Aditya Kumar Singh wrote:
>> Currently, whenever a scan is started, it uses drv's first BSS only
>> whether it is AP or STA interface. However with MLO AP related changes,
>> same drv could be used by other BSSes as well which needs scanning. Hence,
>> the current logic will not work since scan needs to be handled on
>> non-first BSS as well.
>>
>> Add changes to move the logic of always using drv's first BSS during scan
>> events to using BSS on which the event arrived.
>>
>> Also, for ML AP operation, even though BSS is same, link BSS also
>> needs to be identified. Hence add a back pointer in BSS struct which would
>> be used to point to the link BSS which requested scan on that BSS.
>> This will help in routing the scan events to appropriate BSS ctx.
> 
> 
>> diff --git a/src/ap/ap_drv_ops.c b/src/ap/ap_drv_ops.c
>> @@ -781,6 +781,12 @@ bool hostapd_drv_nl80211(struct hostapd_data *hapd)
>>   int hostapd_driver_scan(struct hostapd_data *hapd,
>>   			struct wpa_driver_scan_params *params)
>>   {
>> +	params->mlo_link_id = -1;
> 
> Isn't that needed in wpa_supplicant/driver_i.h for wpa_drv_scan() as
> well? This is going in as 0 now which does not feel correct.
> 

oh yes, I'll fix this in next version.

>> diff --git a/src/drivers/driver.h b/src/drivers/driver.h
>> @@ -709,6 +709,14 @@ struct wpa_driver_scan_params {
>>   	s8 link_id;
>>   
>> +	/**
>> +	 * mlo_link_id - Link ID (in case of MLO)
>> +	 *
>> +	 * If this is set to value >= 0, after scan completion, this would be
>> +	 * used to route the event to proper driver private data.
>> +	 */
>> +	u8 mlo_link_id;
> 
> That should be s8 for that >= 0 to make sense.

Sure. Thanks for pointing that out.

> 
>> diff --git a/src/drivers/driver_nl80211.c b/src/drivers/driver_nl80211.c
>> index c6af0f02f619..b2efcddf058f 100644
>> --- a/src/drivers/driver_nl80211.c
>> +++ b/src/drivers/driver_nl80211.c
>> @@ -4195,6 +4195,22 @@ struct i802_link * nl80211_get_link(struct i802_bss *bss, s8 link_id)
> 
>> @@ -10259,7 +10275,9 @@ static int wpa_driver_nl80211_get_survey(void *priv, unsigned int freq)
>>   	if (err)
>>   		wpa_printf(MSG_ERROR, "nl80211: Failed to process survey data");
>>   	else
>> -		wpa_supplicant_event(drv->ctx, EVENT_SURVEY, &data);
>> +		wpa_supplicant_event((bss->scan_link && bss->scan_link->ctx) ?
>> +						bss->scan_link->ctx : bss->ctx,
>> +						EVENT_SURVEY, &data);
> 
> Wrong indentation level..

Will rectify.

> 
>> diff --git a/src/drivers/driver_nl80211_event.c b/src/drivers/driver_nl80211_event.c
> 
>> +			wpa_printf(MSG_DEBUG, "nl80211: Scan event on unknown link");
> 
> Too long a line (>80 chars); should be split after the comma.

Sure will do.

> 
>> +		if (mld_link->ctx) {
>> +			u8 link_id = nl80211_get_link_id_from_link(bss, mld_link);
> 
> Too long a line; should be
> 
> u8 link_id;
> 
> link_id = ...

Sure, thanks for pointing it out.

> 
>> +			wpa_printf(MSG_DEBUG, "nl80211: Scan event for link_id %d",
> 
> Too long a line.
> 
>> -static void do_process_drv_event(struct i802_bss *bss, int cmd,
>> -				 struct nlattr **tb)
>> +static void nl80211_scan_event(struct i802_bss *bss, enum nl80211_commands cmd,
>> +			       struct nlattr *tb[])
> 
> This diff gets really confusing since this mixes is refactoring (moving
> the scan event handlers to a separate function) and actual functionality
> changes. Please do not mix such things into a single patch. This is much
> clearer to review when that nl80211_scan_event() is not added and the
> functionality within do_process_drv_event() is modified instead. A
> separate patch can be proposed for refactoring, but to be honest, I
> don't see the value of that for this particular case.

Okay will directly make changes without refactoring. Thanks for the 
suggestion.

> 
>> diff --git a/src/drivers/driver_nl80211_scan.c b/src/drivers/driver_nl80211_scan.c
>> @@ -440,11 +449,19 @@ int wpa_driver_nl80211_scan(struct i802_bss *bss,
>> -	eloop_cancel_timeout(wpa_driver_nl80211_scan_timeout, drv, drv->ctx);
>> +	eloop_cancel_timeout(wpa_driver_nl80211_scan_timeout, drv, bss->ctx);
> 
> This looks scary and/or incomplete.. There are many locations where the
> eloop timeouts were not modified and the calls there still use drv->ctx.
> This is the case for the vendor commands for scan and also for clearing
> up eloop timeouts when deinitializing. It was far from obvious that this
> would work correctly for all cases.
> 

Okay. Let me take a look from vendor scan perspective.
diff mbox series

Patch

diff --git a/src/ap/ap_drv_ops.c b/src/ap/ap_drv_ops.c
index c47349110e5f..a875422f0d45 100644
--- a/src/ap/ap_drv_ops.c
+++ b/src/ap/ap_drv_ops.c
@@ -781,6 +781,12 @@  bool hostapd_drv_nl80211(struct hostapd_data *hapd)
 int hostapd_driver_scan(struct hostapd_data *hapd,
 			struct wpa_driver_scan_params *params)
 {
+	params->mlo_link_id = -1;
+#ifdef CONFIG_IEEE80211BE
+	if (hapd->conf->mld_ap)
+		params->mlo_link_id = hapd->mld_link_id;
+#endif /* CONFIG_IEEE80211BE */
+
 	if (hapd->driver && hapd->driver->scan2)
 		return hapd->driver->scan2(hapd->drv_priv, params);
 	return -1;
diff --git a/src/drivers/driver.h b/src/drivers/driver.h
index 52632154ee4b..9873acb524fc 100644
--- a/src/drivers/driver.h
+++ b/src/drivers/driver.h
@@ -709,6 +709,14 @@  struct wpa_driver_scan_params {
 	 */
 	s8 link_id;
 
+	/**
+	 * mlo_link_id - Link ID (in case of MLO)
+	 *
+	 * If this is set to value >= 0, after scan completion, this would be
+	 * used to route the event to proper driver private data.
+	 */
+	u8 mlo_link_id;
+
 	/*
 	 * NOTE: Whenever adding new parameters here, please make sure
 	 * wpa_scan_clone_params() and wpa_scan_free_params() get updated with
diff --git a/src/drivers/driver_nl80211.c b/src/drivers/driver_nl80211.c
index c6af0f02f619..b2efcddf058f 100644
--- a/src/drivers/driver_nl80211.c
+++ b/src/drivers/driver_nl80211.c
@@ -4195,6 +4195,22 @@  struct i802_link * nl80211_get_link(struct i802_bss *bss, s8 link_id)
 }
 
 
+u8 nl80211_get_link_id_from_link(struct i802_bss *bss, struct i802_link *link)
+{
+	u8 link_id;
+
+	if (link == bss->flink)
+		return 0;
+
+	for_each_link(bss->valid_links, link_id) {
+		if (&bss->links[link_id] == link)
+			return link_id;
+	}
+
+	return 0;
+}
+
+
 static void nl80211_link_set_freq(struct i802_bss *bss, s8 link_id, int freq)
 {
 	struct i802_link *link = nl80211_get_link(bss, link_id);
@@ -10259,7 +10275,9 @@  static int wpa_driver_nl80211_get_survey(void *priv, unsigned int freq)
 	if (err)
 		wpa_printf(MSG_ERROR, "nl80211: Failed to process survey data");
 	else
-		wpa_supplicant_event(drv->ctx, EVENT_SURVEY, &data);
+		wpa_supplicant_event((bss->scan_link && bss->scan_link->ctx) ?
+						bss->scan_link->ctx : bss->ctx,
+						EVENT_SURVEY, &data);
 
 	clean_survey_results(survey_results);
 	return err;
diff --git a/src/drivers/driver_nl80211.h b/src/drivers/driver_nl80211.h
index 618746e67722..8e1426465586 100644
--- a/src/drivers/driver_nl80211.h
+++ b/src/drivers/driver_nl80211.h
@@ -67,7 +67,7 @@  struct i802_bss {
 
 	u16 valid_links;
 	struct i802_link links[MAX_NUM_MLD_LINKS];
-	struct i802_link *flink;
+	struct i802_link *flink, *scan_link;
 
 	int ifindex;
 	int br_ifindex;
@@ -352,6 +352,7 @@  const char * nl80211_iftype_str(enum nl80211_iftype mode);
 
 void nl80211_restore_ap_mode(struct i802_bss *bss);
 struct i802_link * nl80211_get_link(struct i802_bss *bss, s8 link_id);
+u8 nl80211_get_link_id_from_link(struct i802_bss *bss, struct i802_link *link);
 
 static inline bool nl80211_link_valid(u16 links, s8 link_id)
 {
diff --git a/src/drivers/driver_nl80211_event.c b/src/drivers/driver_nl80211_event.c
index 6e75748792bf..6432a3274d09 100644
--- a/src/drivers/driver_nl80211_event.c
+++ b/src/drivers/driver_nl80211_event.c
@@ -1959,9 +1959,10 @@  static void mlme_event_dh_event(struct wpa_driver_nl80211_data *drv,
 }
 
 
-static void send_scan_event(struct wpa_driver_nl80211_data *drv, int aborted,
+static void send_scan_event(struct i802_bss *bss, int aborted,
 			    struct nlattr *tb[], int external_scan)
 {
+	struct wpa_driver_nl80211_data *drv = bss->drv;
 	union wpa_event_data event;
 	struct nlattr *nl;
 	int rem;
@@ -1969,6 +1970,8 @@  static void send_scan_event(struct wpa_driver_nl80211_data *drv, int aborted,
 #define MAX_REPORT_FREQS 110
 	int freqs[MAX_REPORT_FREQS];
 	int num_freqs = 0;
+	struct i802_link *mld_link;
+	void *ctx = bss->ctx;
 
 	if (!external_scan && drv->scan_for_auth) {
 		drv->scan_for_auth = 0;
@@ -2032,7 +2035,25 @@  static void send_scan_event(struct wpa_driver_nl80211_data *drv, int aborted,
 			  ETH_ALEN);
 	}
 
-	wpa_supplicant_event(drv->ctx, EVENT_SCAN_RESULTS, &event);
+	/* need to pass to correct link ctx during AP operation */
+	if (is_ap_interface(drv->nlmode)) {
+		mld_link = bss->scan_link;
+		if (!mld_link) {
+			wpa_printf(MSG_DEBUG, "nl80211: Scan event on unknown link");
+			goto process_scan_event;
+		}
+
+		if (mld_link->ctx) {
+			u8 link_id = nl80211_get_link_id_from_link(bss, mld_link);
+
+			wpa_printf(MSG_DEBUG, "nl80211: Scan event for link_id %d",
+				   link_id);
+			ctx = mld_link->ctx;
+		}
+	}
+
+process_scan_event:
+	wpa_supplicant_event(ctx, EVENT_SCAN_RESULTS, &event);
 }
 
 
@@ -3833,48 +3854,16 @@  static void nl80211_color_change_announcement_completed(struct i802_bss *bss)
 #endif /* CONFIG_IEEE80211AX */
 
 
-static void do_process_drv_event(struct i802_bss *bss, int cmd,
-				 struct nlattr **tb)
+static void nl80211_scan_event(struct i802_bss *bss, enum nl80211_commands cmd,
+			       struct nlattr *tb[])
 {
 	struct wpa_driver_nl80211_data *drv = bss->drv;
 	int external_scan_event = 0;
-	struct nlattr *frame = tb[NL80211_ATTR_FRAME];
-
-	wpa_printf(MSG_DEBUG, "nl80211: Drv Event %d (%s) received for %s",
-		   cmd, nl80211_command_to_string(cmd), bss->ifname);
-
-#ifdef CONFIG_DRIVER_NL80211_QCA
-	if (cmd == NL80211_CMD_ROAM &&
-	    (drv->capa.flags & WPA_DRIVER_FLAGS_KEY_MGMT_OFFLOAD)) {
-		if (drv->pending_roam_data) {
-			wpa_printf(MSG_DEBUG,
-				   "nl80211: Process pending roam+auth vendor event");
-			qca_nl80211_key_mgmt_auth(drv, drv->pending_roam_data,
-						  drv->pending_roam_data_len);
-			os_free(drv->pending_roam_data);
-			drv->pending_roam_data = NULL;
-			return;
-		}
-		/*
-		 * Device will use roam+auth vendor event to indicate
-		 * roaming, so ignore the regular roam event.
-		 */
-		drv->roam_indication_done = true;
-		wpa_printf(MSG_DEBUG,
-			   "nl80211: Ignore roam event (cmd=%d), device will use vendor event roam+auth",
-			   cmd);
-		return;
-	}
-#endif /* CONFIG_DRIVER_NL80211_QCA */
-
-	if (drv->ap_scan_as_station != NL80211_IFTYPE_UNSPECIFIED &&
-	    (cmd == NL80211_CMD_NEW_SCAN_RESULTS ||
-	     cmd == NL80211_CMD_SCAN_ABORTED))
-		nl80211_restore_ap_mode(bss);
+	void *ctx = bss->ctx;
 
 	switch (cmd) {
 	case NL80211_CMD_TRIGGER_SCAN:
-		wpa_dbg(drv->ctx, MSG_DEBUG, "nl80211: Scan trigger");
+		wpa_dbg(ctx, MSG_DEBUG, "nl80211: Scan trigger");
 		drv->scan_state = SCAN_STARTED;
 		if (drv->scan_for_auth) {
 			/*
@@ -3886,40 +3875,40 @@  static void do_process_drv_event(struct i802_bss *bss, int cmd,
 			wpa_printf(MSG_DEBUG, "nl80211: Do not indicate scan-start event due to internal scan_for_auth");
 			break;
 		}
-		wpa_supplicant_event(drv->ctx, EVENT_SCAN_STARTED, NULL);
+		wpa_supplicant_event(ctx, EVENT_SCAN_STARTED, NULL);
 		break;
 	case NL80211_CMD_START_SCHED_SCAN:
-		wpa_dbg(drv->ctx, MSG_DEBUG, "nl80211: Sched scan started");
+		wpa_dbg(ctx, MSG_DEBUG, "nl80211: Sched scan started");
 		drv->scan_state = SCHED_SCAN_STARTED;
 		break;
 	case NL80211_CMD_SCHED_SCAN_STOPPED:
-		wpa_dbg(drv->ctx, MSG_DEBUG, "nl80211: Sched scan stopped");
+		wpa_dbg(ctx, MSG_DEBUG, "nl80211: Sched scan stopped");
 		drv->scan_state = SCHED_SCAN_STOPPED;
-		wpa_supplicant_event(drv->ctx, EVENT_SCHED_SCAN_STOPPED, NULL);
+		wpa_supplicant_event(ctx, EVENT_SCHED_SCAN_STOPPED, NULL);
 		break;
 	case NL80211_CMD_NEW_SCAN_RESULTS:
-		wpa_dbg(drv->ctx, MSG_DEBUG,
+		wpa_dbg(ctx, MSG_DEBUG,
 			"nl80211: New scan results available");
 		if (drv->last_scan_cmd != NL80211_CMD_VENDOR)
 			drv->scan_state = SCAN_COMPLETED;
 		drv->scan_complete_events = 1;
 		if (drv->last_scan_cmd == NL80211_CMD_TRIGGER_SCAN) {
 			eloop_cancel_timeout(wpa_driver_nl80211_scan_timeout,
-					     drv, drv->ctx);
+					     drv, bss->ctx);
 			drv->last_scan_cmd = 0;
 		} else {
 			external_scan_event = 1;
 		}
-		send_scan_event(drv, 0, tb, external_scan_event);
+		send_scan_event(bss, 0, tb, external_scan_event);
 		break;
 	case NL80211_CMD_SCHED_SCAN_RESULTS:
-		wpa_dbg(drv->ctx, MSG_DEBUG,
+		wpa_dbg(ctx, MSG_DEBUG,
 			"nl80211: New sched scan results available");
 		drv->scan_state = SCHED_SCAN_RESULTS;
-		send_scan_event(drv, 0, tb, 0);
+		send_scan_event(bss, 0, tb, 0);
 		break;
 	case NL80211_CMD_SCAN_ABORTED:
-		wpa_dbg(drv->ctx, MSG_DEBUG, "nl80211: Scan aborted");
+		wpa_dbg(ctx, MSG_DEBUG, "nl80211: Scan aborted");
 		if (drv->last_scan_cmd != NL80211_CMD_VENDOR)
 			drv->scan_state = SCAN_ABORTED;
 		if (drv->last_scan_cmd == NL80211_CMD_TRIGGER_SCAN) {
@@ -3928,12 +3917,67 @@  static void do_process_drv_event(struct i802_bss *bss, int cmd,
 			 * order not to make wpa_supplicant stop its scanning.
 			 */
 			eloop_cancel_timeout(wpa_driver_nl80211_scan_timeout,
-					     drv, drv->ctx);
+					     drv, bss->ctx);
 			drv->last_scan_cmd = 0;
 		} else {
 			external_scan_event = 1;
 		}
-		send_scan_event(drv, 1, tb, external_scan_event);
+		send_scan_event(bss, 1, tb, external_scan_event);
+		break;
+	default:
+		wpa_dbg(ctx, MSG_DEBUG, "nl80211: Unknown scan cmd %d (%s)",
+			cmd, nl80211_command_to_string(cmd));
+		break;
+	}
+}
+
+
+static void do_process_drv_event(struct i802_bss *bss, int cmd,
+				 struct nlattr **tb)
+{
+	struct wpa_driver_nl80211_data *drv = bss->drv;
+	struct nlattr *frame = tb[NL80211_ATTR_FRAME];
+
+	wpa_printf(MSG_DEBUG, "nl80211: Drv Event %d (%s) received for %s",
+		   cmd, nl80211_command_to_string(cmd), bss->ifname);
+
+#ifdef CONFIG_DRIVER_NL80211_QCA
+	if (cmd == NL80211_CMD_ROAM &&
+	    (drv->capa.flags & WPA_DRIVER_FLAGS_KEY_MGMT_OFFLOAD)) {
+		if (drv->pending_roam_data) {
+			wpa_printf(MSG_DEBUG,
+				   "nl80211: Process pending roam+auth vendor event");
+			qca_nl80211_key_mgmt_auth(drv, drv->pending_roam_data,
+						  drv->pending_roam_data_len);
+			os_free(drv->pending_roam_data);
+			drv->pending_roam_data = NULL;
+			return;
+		}
+		/*
+		 * Device will use roam+auth vendor event to indicate
+		 * roaming, so ignore the regular roam event.
+		 */
+		drv->roam_indication_done = true;
+		wpa_printf(MSG_DEBUG,
+			   "nl80211: Ignore roam event (cmd=%d), device will use vendor event roam+auth",
+			   cmd);
+		return;
+	}
+#endif /* CONFIG_DRIVER_NL80211_QCA */
+
+	if (drv->ap_scan_as_station != NL80211_IFTYPE_UNSPECIFIED &&
+	    (cmd == NL80211_CMD_NEW_SCAN_RESULTS ||
+	     cmd == NL80211_CMD_SCAN_ABORTED))
+		nl80211_restore_ap_mode(bss);
+
+	switch (cmd) {
+	case NL80211_CMD_TRIGGER_SCAN:
+	case NL80211_CMD_START_SCHED_SCAN:
+	case NL80211_CMD_SCHED_SCAN_STOPPED:
+	case NL80211_CMD_NEW_SCAN_RESULTS:
+	case NL80211_CMD_SCHED_SCAN_RESULTS:
+	case NL80211_CMD_SCAN_ABORTED:
+		nl80211_scan_event(bss, cmd, tb);
 		break;
 	case NL80211_CMD_AUTHENTICATE:
 	case NL80211_CMD_ASSOCIATE:
diff --git a/src/drivers/driver_nl80211_scan.c b/src/drivers/driver_nl80211_scan.c
index 1eb4374052e5..68deb2d9e296 100644
--- a/src/drivers/driver_nl80211_scan.c
+++ b/src/drivers/driver_nl80211_scan.c
@@ -153,6 +153,7 @@  fail:
 void wpa_driver_nl80211_scan_timeout(void *eloop_ctx, void *timeout_ctx)
 {
 	struct wpa_driver_nl80211_data *drv = eloop_ctx;
+	struct i802_bss *bss;
 
 	wpa_printf(MSG_DEBUG, "nl80211: Scan timeout - try to abort it");
 #ifdef CONFIG_DRIVER_NL80211_QCA
@@ -160,14 +161,22 @@  void wpa_driver_nl80211_scan_timeout(void *eloop_ctx, void *timeout_ctx)
 	    nl80211_abort_vendor_scan(drv, drv->vendor_scan_cookie) == 0)
 		return;
 #endif /* CONFIG_DRIVER_NL80211_QCA */
+
+	for (bss = drv->first_bss; bss; bss = bss->next) {
+		if (bss->scan_link)
+			break;
+	}
+
 	if (!drv->vendor_scan_cookie &&
-	    nl80211_abort_scan(drv->first_bss) == 0)
+	    nl80211_abort_scan(bss) == 0) {
+		bss->scan_link = NULL;
 		return;
+	}
 
 	wpa_printf(MSG_DEBUG, "nl80211: Failed to abort scan");
 
 	if (drv->ap_scan_as_station != NL80211_IFTYPE_UNSPECIFIED)
-		nl80211_restore_ap_mode(drv->first_bss);
+		nl80211_restore_ap_mode(bss);
 
 	wpa_printf(MSG_DEBUG, "nl80211: Try to get scan results");
 	wpa_supplicant_event(timeout_ctx, EVENT_SCAN_RESULTS, NULL);
@@ -347,7 +356,7 @@  int wpa_driver_nl80211_scan(struct i802_bss *bss,
 	int ret = -1, timeout;
 	struct nl_msg *msg = NULL;
 
-	wpa_dbg(drv->ctx, MSG_DEBUG, "nl80211: scan request");
+	wpa_dbg(bss->ctx, MSG_DEBUG, "nl80211: scan request");
 	drv->scan_for_auth = 0;
 
 	if (TEST_FAIL())
@@ -440,11 +449,19 @@  int wpa_driver_nl80211_scan(struct i802_bss *bss,
 	}
 	wpa_printf(MSG_DEBUG, "Scan requested (ret=%d) - scan timeout %d "
 		   "seconds", ret, timeout);
-	eloop_cancel_timeout(wpa_driver_nl80211_scan_timeout, drv, drv->ctx);
+	eloop_cancel_timeout(wpa_driver_nl80211_scan_timeout, drv, bss->ctx);
 	eloop_register_timeout(timeout, 0, wpa_driver_nl80211_scan_timeout,
-			       drv, drv->ctx);
+			       drv, bss->ctx);
 	drv->last_scan_cmd = NL80211_CMD_TRIGGER_SCAN;
 
+	bss->scan_link = bss->flink;
+	if (is_ap_interface(drv->nlmode) &&
+	    nl80211_link_valid(bss->valid_links, params->mlo_link_id)) {
+		wpa_dbg(bss->ctx, MSG_DEBUG, "nl80211: scan requested for link %d",
+			params->mlo_link_id);
+		bss->scan_link = nl80211_get_link(bss, params->mlo_link_id);
+	}
+
 fail:
 	nlmsg_free(msg);
 	return ret;
diff --git a/wpa_supplicant/scan.c b/wpa_supplicant/scan.c
index 8b59e409b81d..f97af36f9c8c 100644
--- a/wpa_supplicant/scan.c
+++ b/wpa_supplicant/scan.c
@@ -3390,6 +3390,7 @@  wpa_scan_clone_params(const struct wpa_driver_scan_params *src)
 	params->p2p_include_6ghz = src->p2p_include_6ghz;
 	params->non_coloc_6ghz = src->non_coloc_6ghz;
 	params->min_probe_req_content = src->min_probe_req_content;
+	params->mlo_link_id = src->mlo_link_id;
 	return params;
 
 failed: