Merge "Merge Android 14 QPR3 to AOSP main" into main
diff --git a/hostapd/android.config b/hostapd/android.config
index eaeda89..1c4dab2 100644
--- a/hostapd/android.config
+++ b/hostapd/android.config
@@ -141,6 +141,14 @@
 CONFIG_IEEE80211AX=y
 endif
 
+# IEEE 802.11be EHT (Extremely High Throughput) support
+# Note: Enabling CONFIG_IEEE80211BE requires CONFIG_IEEE80211AX to be enabled as well. Therefore,
+# the hostapd/Android.mk file automatically enables CONFIG_IEEE80211AX when CONFIG_IEEE80211BE is
+# enabled here.
+ifeq ($(WIFI_FEATURE_HOSTAPD_11BE), true)
+CONFIG_IEEE80211BE=y
+endif
+
 # Remove debugging code that is printing out debug messages to stdout.
 # This can be used to reduce the size of the hostapd considerably if debugging
 # code is not needed.
diff --git a/hostapd/config_file.c b/hostapd/config_file.c
index 580e41c..bfb152e 100644
--- a/hostapd/config_file.c
+++ b/hostapd/config_file.c
@@ -667,6 +667,10 @@
 			val |= WPA_KEY_MGMT_FT_IEEE8021X_SHA384;
 #endif /* CONFIG_SHA384 */
 #endif /* CONFIG_IEEE80211R_AP */
+#ifdef CONFIG_SHA384
+		else if (os_strcmp(start, "WPA-EAP-SHA384") == 0)
+			val |= WPA_KEY_MGMT_IEEE8021X_SHA384;
+#endif /* CONFIG_SHA384 */
 		else if (os_strcmp(start, "WPA-PSK-SHA256") == 0)
 			val |= WPA_KEY_MGMT_PSK_SHA256;
 		else if (os_strcmp(start, "WPA-EAP-SHA256") == 0)
@@ -2603,6 +2607,8 @@
 	} else if (os_strcmp(buf, "imsi_privacy_key") == 0) {
 		os_free(bss->imsi_privacy_key);
 		bss->imsi_privacy_key = os_strdup(pos);
+	} else if (os_strcmp(buf, "eap_sim_aka_fast_reauth_limit") == 0) {
+		bss->eap_sim_aka_fast_reauth_limit = atoi(pos);
 #endif /* EAP_SERVER_SIM */
 #ifdef EAP_SERVER_TNC
 	} else if (os_strcmp(buf, "tnc") == 0) {
@@ -4776,6 +4782,16 @@
 			return 1;
 		}
 		conf->punct_acs_threshold = val;
+	} else if (os_strcmp(buf, "mld_ap") == 0) {
+		bss->mld_ap = !!atoi(pos);
+	} else if (os_strcmp(buf, "mld_id") == 0) {
+		bss->mld_id = atoi(pos);
+	} else if (os_strcmp(buf, "mld_addr") == 0) {
+		if (hwaddr_aton(pos, bss->mld_addr)) {
+			wpa_printf(MSG_ERROR, "Line %d: Invalid mld_addr",
+				   line);
+			return 1;
+		}
 #endif /* CONFIG_IEEE80211BE */
 	} else {
 		wpa_printf(MSG_ERROR,
diff --git a/hostapd/ctrl_iface.c b/hostapd/ctrl_iface.c
index b46d921..57c9997 100644
--- a/hostapd/ctrl_iface.c
+++ b/hostapd/ctrl_iface.c
@@ -956,6 +956,14 @@
 		pos += ret;
 	}
 #endif /* CONFIG_DPP */
+#ifdef CONFIG_SHA384
+	if (hapd->conf->wpa_key_mgmt & WPA_KEY_MGMT_IEEE8021X_SHA384) {
+		ret = os_snprintf(pos, end - pos, "WPA-EAP-SHA384 ");
+		if (os_snprintf_error(end - pos, ret))
+			return pos - buf;
+		pos += ret;
+	}
+#endif /* CONFIG_SHA384 */
 
 	if (pos > buf && *(pos - 1) == ' ') {
 		*(pos - 1) = '\0';
@@ -1840,6 +1848,7 @@
 	int enabled = atoi(cmd);
 	char *pos;
 	const char *ifname;
+	const u8 *addr = hapd->own_addr;
 
 	if (!enabled) {
 		if (hapd->l2_test) {
@@ -1860,7 +1869,11 @@
 	else
 		ifname = hapd->conf->iface;
 
-	hapd->l2_test = l2_packet_init(ifname, hapd->own_addr,
+#ifdef CONFIG_IEEE80211BE
+	if (hapd->conf->mld_ap)
+		addr = hapd->mld_addr;
+#endif /* CONFIG_IEEE80211BE */
+	hapd->l2_test = l2_packet_init(ifname, addr,
 					ETHERTYPE_IP, hostapd_data_test_rx,
 					hapd, 1);
 	if (hapd->l2_test == NULL)
@@ -2714,6 +2727,155 @@
 }
 
 
+static u8 hostapd_maxnss(struct hostapd_data *hapd, struct sta_info *sta)
+{
+	u8 *mcs_set = NULL;
+	u16 mcs_map;
+	u8 ht_rx_nss = 0;
+	u8 vht_rx_nss = 1;
+	u8 mcs;
+	bool ht_supported = false;
+	bool vht_supported = false;
+	int i;
+
+	if (sta->ht_capabilities && (sta->flags & WLAN_STA_HT)) {
+		mcs_set = sta->ht_capabilities->supported_mcs_set;
+		ht_supported = true;
+	}
+
+	if (sta->vht_capabilities && (sta->flags & WLAN_STA_VHT)) {
+		mcs_map = le_to_host16(
+			sta->vht_capabilities->vht_supported_mcs_set.rx_map);
+		vht_supported = true;
+	}
+
+	if (ht_supported && mcs_set) {
+		if (mcs_set[0])
+			ht_rx_nss++;
+		if (mcs_set[1])
+			ht_rx_nss++;
+		if (mcs_set[2])
+			ht_rx_nss++;
+		if (mcs_set[3])
+			ht_rx_nss++;
+	}
+	if (vht_supported) {
+		for (i = 7; i >= 0; i--) {
+			mcs = (mcs_map >> (2 * i)) & 0x03;
+			if (mcs != 0x03) {
+				vht_rx_nss = i + 1;
+				break;
+			}
+		}
+	}
+
+	return ht_rx_nss > vht_rx_nss ? ht_rx_nss : vht_rx_nss;
+}
+
+
+static char hostapd_ctrl_iface_notify_cw_htaction(struct hostapd_data *hapd,
+						  const u8 *addr, u8 width)
+{
+	u8 buf[3];
+	char ret;
+
+	width = width >= 1 ? 1 : 0;
+
+	buf[0] = WLAN_ACTION_HT;
+	buf[1] = WLAN_HT_ACTION_NOTIFY_CHANWIDTH;
+	buf[2] = width;
+
+	ret = hostapd_drv_send_action(hapd, hapd->iface->freq, 0, addr,
+				      buf, sizeof(buf));
+	if (ret)
+		wpa_printf(MSG_DEBUG,
+			   "Failed to send Notify Channel Width frame to "
+			   MACSTR, MAC2STR(addr));
+
+	return ret;
+}
+
+
+static char hostapd_ctrl_iface_notify_cw_vhtaction(struct hostapd_data *hapd,
+						   const u8 *addr, u8 width)
+{
+	u8 buf[3];
+	char ret;
+
+	buf[0] = WLAN_ACTION_VHT;
+	buf[1] = WLAN_VHT_ACTION_OPMODE_NOTIF;
+	buf[2] = width;
+
+	ret = hostapd_drv_send_action(hapd, hapd->iface->freq, 0, addr,
+				      buf, sizeof(buf));
+	if (ret)
+		wpa_printf(MSG_DEBUG,
+			   "Failed to send Opeating Mode Notification frame to "
+			   MACSTR, MAC2STR(addr));
+
+	return ret;
+}
+
+
+static char hostapd_ctrl_iface_notify_cw_change(struct hostapd_data *hapd,
+						const char *cmd)
+{
+	u8 cw, operating_mode = 0, nss;
+	struct sta_info *sta;
+	enum hostapd_hw_mode hw_mode;
+
+	if (is_6ghz_freq(hapd->iface->freq)) {
+		wpa_printf(MSG_ERROR, "20/40 BSS coex not supported in 6 GHz");
+		return -1;
+	}
+
+	cw = atoi(cmd);
+	hw_mode = hapd->iface->current_mode->mode;
+	if ((hw_mode == HOSTAPD_MODE_IEEE80211G ||
+	     hw_mode == HOSTAPD_MODE_IEEE80211B) &&
+	    !(cw == 0 || cw == 1)) {
+		wpa_printf(MSG_ERROR,
+			   "Channel width should be either 20 MHz or 40 MHz for 2.4 GHz band");
+		return -1;
+	}
+
+	switch (cw) {
+	case 0:
+		operating_mode = 0;
+		break;
+	case 1:
+		operating_mode = VHT_OPMODE_CHANNEL_40MHZ;
+		break;
+	case 2:
+		operating_mode = VHT_OPMODE_CHANNEL_80MHZ;
+		break;
+	case 3:
+		operating_mode = VHT_OPMODE_CHANNEL_160MHZ;
+		break;
+	default:
+		wpa_printf(MSG_ERROR, "Channel width should be between 0 to 3");
+		return -1;
+	}
+
+	for (sta = hapd->sta_list; sta; sta = sta->next) {
+		if ((sta->flags & WLAN_STA_VHT) && sta->vht_capabilities) {
+			nss = hostapd_maxnss(hapd, sta) - 1;
+			hostapd_ctrl_iface_notify_cw_vhtaction(hapd, sta->addr,
+							       operating_mode |
+							       (u8) (nss << 4));
+			continue;
+		}
+
+		if ((sta->flags & (WLAN_STA_HT | WLAN_STA_VHT)) ==
+		    WLAN_STA_HT && sta->ht_capabilities)
+			hostapd_ctrl_iface_notify_cw_htaction(hapd, sta->addr,
+							      cw);
+	}
+
+	return 0;
+}
+
+
 static int hostapd_ctrl_iface_mib(struct hostapd_data *hapd, char *reply,
 				  int reply_size, const char *param)
 {
@@ -3560,6 +3722,9 @@
 	} else if (os_strncmp(buf, "CHAN_SWITCH ", 12) == 0) {
 		if (hostapd_ctrl_iface_chan_switch(hapd->iface, buf + 12))
 			reply_len = -1;
+	} else if (os_strncmp(buf, "NOTIFY_CW_CHANGE ", 17) == 0) {
+		if (hostapd_ctrl_iface_notify_cw_change(hapd, buf + 17))
+			reply_len = -1;
 	} else if (os_strncmp(buf, "VENDOR ", 7) == 0) {
 		reply_len = hostapd_ctrl_iface_vendor(hapd, buf + 7, reply,
 						      reply_size);
diff --git a/hostapd/defconfig b/hostapd/defconfig
index 6a41dcf..0a5ceee 100644
--- a/hostapd/defconfig
+++ b/hostapd/defconfig
@@ -415,7 +415,6 @@
 # Experimental implementation based on IEEE P802.11z/D2.6 and the protocol
 # design is still subject to change. As such, this should not yet be enabled in
 # production use.
-# This requires CONFIG_IEEE80211W=y to be enabled, too.
 #CONFIG_PASN=y
 
 # Device Provisioning Protocol (DPP) (also known as Wi-Fi Easy Connect)
diff --git a/hostapd/hostapd.conf b/hostapd/hostapd.conf
index 5868bfd..f02cd92 100644
--- a/hostapd/hostapd.conf
+++ b/hostapd/hostapd.conf
@@ -1040,6 +1040,20 @@
 # Default is 0, indicates that ACS algorithm should not puncture any channel.
 #punct_acs_threshold=75
 
+# AP MLD - Whether this AP is a part of an AP MLD
+# 0 = no (no MLO)
+# 1 = yes (MLO)
+#mld_ap=0
+
+# MLD ID - Affiliated MLD ID
+#mld_id=1
+
+# AP MLD MAC address
+# The configured address will be set as the interface hardware address and used
+# as the AP MLD MAC address. If not set, the current interface hardware address
+# will be used as the AP MLD MAC address.
+#mld_addr=02:03:04:05:06:07
+
 ##### IEEE 802.1X-2004 related configuration ##################################
 
 # Require IEEE 802.1X authorization
@@ -1451,12 +1465,25 @@
 # 1 = use pseudonyms, but not fast reauthentication
 # 2 = do not use pseudonyms, but use fast reauthentication
 # 3 = use pseudonyms and use fast reauthentication (default)
+# 4 = do not use pseudonyms or fast reauthentication and allow
+#     EAP-Response/Identity to be used without method specific identity exchange
+# 5 = use pseudonyms, but not fast reauthentication and allow
+#     EAP-Response/Identity to be used without method specific identity exchange
+# 6 = do not use pseudonyms, but use fast reauthentication and allow
+#     EAP-Response/Identity to be used without method specific identity exchange
+# 7 = use pseudonyms and use fast reauthentication and allow
+#     EAP-Response/Identity to be used without method specific identity exchange
 #eap_sim_id=3
 
 # IMSI privacy key (PEM encoded RSA 2048-bit private key) for decrypting
 # permanent identity when using EAP-SIM/AKA/AKA'.
 #imsi_privacy_key=imsi-privacy-key.pem
 
+# EAP-SIM and EAP-AKA fast re-authentication limit
+# Maximum number of fast re-authentications allowed after each full
+# authentication.
+#eap_sim_aka_fast_reauth_limit=1000
+
 # Trusted Network Connect (TNC)
 # If enabled, TNC validation will be required before the peer is allowed to
 # connect. Note: This is only used with EAP-TTLS and EAP-FAST. If any other
diff --git a/hostapd/hostapd_cli.c b/hostapd/hostapd_cli.c
index 646dfc5..45497cd 100644
--- a/hostapd/hostapd_cli.c
+++ b/hostapd/hostapd_cli.c
@@ -1207,6 +1207,13 @@
 }
 
 
+static int hostapd_cli_cmd_notify_cw_change(struct wpa_ctrl *ctrl,
+					    int argc, char *argv[])
+{
+	return hostapd_cli_cmd(ctrl, "NOTIFY_CW_CHANGE", 1, argc, argv);
+}
+
+
 static int hostapd_cli_cmd_enable(struct wpa_ctrl *ctrl, int argc,
 				      char *argv[])
 {
@@ -1386,6 +1393,13 @@
 }
 
 
+static int hostapd_cli_cmd_driver_flags2(struct wpa_ctrl *ctrl, int argc,
+					 char *argv[])
+{
+	return wpa_ctrl_command(ctrl, "DRIVER_FLAGS2");
+}
+
+
 #ifdef CONFIG_DPP
 
 static int hostapd_cli_cmd_dpp_qr_code(struct wpa_ctrl *ctrl, int argc,
@@ -1679,6 +1693,8 @@
 	  "<cs_count> <freq> [sec_channel_offset=] [center_freq1=]\n"
 	  "  [center_freq2=] [bandwidth=] [blocktx] [ht|vht]\n"
 	  "  = initiate channel switch announcement" },
+	{ "notify_cw_change", hostapd_cli_cmd_notify_cw_change, NULL,
+	  "<channel_width> = 0 - 20 MHz, 1 - 40 MHz, 2 - 80 MHz, 3 - 160 MHz" },
 	{ "hs20_wnm_notif", hostapd_cli_cmd_hs20_wnm_notif, NULL,
 	  "<addr> <url>\n"
 	  "  = send WNM-Notification Subscription Remediation Request" },
@@ -1719,6 +1735,8 @@
 	  " = send FTM range request"},
 	{ "driver_flags", hostapd_cli_cmd_driver_flags, NULL,
 	  " = show supported driver flags"},
+	{ "driver_flags2", hostapd_cli_cmd_driver_flags2, NULL,
+	  " = show supported driver flags2"},
 #ifdef CONFIG_DPP
 	{ "dpp_qr_code", hostapd_cli_cmd_dpp_qr_code, NULL,
 	  "report a scanned DPP URI from a QR Code" },
diff --git a/hostapd/main.c b/hostapd/main.c
index 18b2dd9..615dc2f 100644
--- a/hostapd/main.c
+++ b/hostapd/main.c
@@ -166,8 +166,61 @@
 		return -1;
 	}
 
+#ifdef CONFIG_IEEE80211BE
+	for (i = 0; conf->mld_ap && i < iface->interfaces->count; i++) {
+		struct hostapd_iface *h = iface->interfaces->iface[i];
+		struct hostapd_data *h_hapd = h->bss[0];
+		struct hostapd_bss_config *hconf = h_hapd->conf;
+
+		if (h == iface) {
+			wpa_printf(MSG_DEBUG, "MLD: Skip own interface");
+			continue;
+		}
+
+		if (!hconf->mld_ap || hconf->mld_id != conf->mld_id) {
+			wpa_printf(MSG_DEBUG,
+				   "MLD: Skip non matching mld_id");
+			continue;
+		}
+
+		wpa_printf(MSG_DEBUG, "MLD: Found matching MLD interface");
+		if (!h_hapd->drv_priv) {
+			wpa_printf(MSG_DEBUG,
+				   "MLD: Matching MLD BSS not initialized yet");
+			continue;
+		}
+
+		hapd->drv_priv = h_hapd->drv_priv;
+
+		/*
+		 * All interfaces participating in the AP MLD would have
+		 * the same MLD address, which is the interface hardware
+		 * address, while the interface address would be
+		 * derived from the original interface address if BSSID
+		 * is not configured, and otherwise it would be the
+		 * configured BSSID.
+		 */
+		os_memcpy(hapd->mld_addr, h_hapd->mld_addr, ETH_ALEN);
+		if (is_zero_ether_addr(b)) {
+			os_memcpy(hapd->own_addr, h_hapd->mld_addr, ETH_ALEN);
+			random_mac_addr_keep_oui(hapd->own_addr);
+		} else {
+			os_memcpy(hapd->own_addr, b, ETH_ALEN);
+		}
+
+		/*
+		 * Mark the interface as a secondary interface, as this
+		 * is needed for the de-initialization flow
+		 */
+		hapd->mld_first_bss = h_hapd;
+		hapd->mld_link_id = hapd->mld_first_bss->mld_next_link_id++;
+
+		goto setup_mld;
+	}
+#endif /* CONFIG_IEEE80211BE */
+
 	/* Initialize the driver interface */
-	if (!(b[0] | b[1] | b[2] | b[3] | b[4] | b[5]))
+	if (is_zero_ether_addr(b))
 		b = NULL;
 
 	os_memset(&params, 0, sizeof(params));
@@ -191,6 +244,15 @@
 		break;
 	}
 	params.bssid = b;
+#ifdef CONFIG_IEEE80211BE
+	/*
+	 * Use the configured MLD MAC address as the interface hardware address
+	 * if this AP is a part of an AP MLD.
+	 */
+	if (!is_zero_ether_addr(hapd->conf->mld_addr) && hapd->conf->mld_ap)
+		params.bssid = hapd->conf->mld_addr;
+#endif /* CONFIG_IEEE80211BE */
+
 	params.ifname = hapd->conf->iface;
 	params.driver_params = hapd->iconf->driver_params;
 	params.use_pae_group_addr = hapd->conf->use_pae_group_addr;
@@ -216,6 +278,26 @@
 		return -1;
 	}
 
+#ifdef CONFIG_IEEE80211BE
+	/*
+	 * This is the first interface added to the AP MLD, so have the
+	 * interface hardware address be the MLD address, while the link address
+	 * would be derived from the original interface address if BSSID is not
+	 * configured, and otherwise it would be the configured BSSID.
+	 */
+	if (hapd->conf->mld_ap) {
+		os_memcpy(hapd->mld_addr, hapd->own_addr, ETH_ALEN);
+		hapd->mld_next_link_id = 0;
+		hapd->mld_link_id = hapd->mld_next_link_id++;
+		if (!b)
+			random_mac_addr_keep_oui(hapd->own_addr);
+		else
+			os_memcpy(hapd->own_addr, b, ETH_ALEN);
+	}
+
+setup_mld:
+#endif /* CONFIG_IEEE80211BE */
+
 	if (hapd->driver->get_capa &&
 	    hapd->driver->get_capa(hapd->drv_priv, &capa) == 0) {
 		struct wowlan_triggers *triggs;
@@ -237,6 +319,8 @@
 		 */
 		hostapd_get_ext_capa(iface);
 
+		hostapd_get_mld_capa(iface);
+
 		triggs = wpa_get_wowlan_triggers(conf->wowlan_triggers, &capa);
 		if (triggs && hapd->driver->set_wowlan) {
 			if (hapd->driver->set_wowlan(hapd->drv_priv, triggs))
@@ -248,6 +332,25 @@
 		iface->ema_max_periodicity = capa.ema_max_periodicity;
 	}
 
+#ifdef CONFIG_IEEE80211BE
+	if (hapd->conf->mld_ap) {
+		if (!(iface->drv_flags2 & WPA_DRIVER_FLAGS2_MLO)) {
+			wpa_printf(MSG_INFO,
+				   "MLD: Not supported by the driver");
+			return -1;
+		}
+
+		wpa_printf(MSG_DEBUG,
+			   "MLD: Set link_id=%u, mld_addr=" MACSTR
+			   ", own_addr=" MACSTR,
+			   hapd->mld_link_id, MAC2STR(hapd->mld_addr),
+			   MAC2STR(hapd->own_addr));
+
+		hostapd_drv_link_add(hapd, hapd->mld_link_id,
+				     hapd->own_addr);
+	}
+#endif /* CONFIG_IEEE80211BE */
+
 	return 0;
 }
 
diff --git a/src/ap/acs.c b/src/ap/acs.c
index 1181c7d..e3cfe1d 100644
--- a/src/ap/acs.c
+++ b/src/ap/acs.c
@@ -256,7 +256,7 @@
 static const struct bw_item bw_40[] = {
 	{ 5180, 5200, 38 }, { 5220, 5240, 46 }, { 5260, 5280, 54 },
 	{ 5300, 5320, 62 }, { 5500, 5520, 102 }, { 5540, 5560, 110 },
-	{ 5580, 5600, 110 }, { 5620, 5640, 126}, { 5660, 5680, 134 },
+	{ 5580, 5600, 118 }, { 5620, 5640, 126 }, { 5660, 5680, 134 },
 	{ 5700, 5720, 142 }, { 5745, 5765, 151 }, { 5785, 5805, 159 },
 	{ 5825, 5845, 167 }, { 5865, 5885, 175 },
 	{ 5955, 5975, 3 }, { 5995, 6015, 11 }, { 6035, 6055, 19 },
@@ -1076,12 +1076,6 @@
 		return ideal_chan;
 	}
 
-#ifdef CONFIG_IEEE80211BE
-	if (iface->conf->punct_acs_threshold)
-		wpa_printf(MSG_DEBUG, "ACS: RU puncturing bitmap 0x%x",
-			   ideal_chan->punct_bitmap);
-#endif /* CONFIG_IEEE80211BE */
-
 	return rand_chan;
 }
 
@@ -1353,12 +1347,20 @@
 
 enum hostapd_chan_status acs_init(struct hostapd_iface *iface)
 {
+	int err;
+
 	wpa_printf(MSG_INFO, "ACS: Automatic channel selection started, this may take a bit");
 
 	if (iface->drv_flags & WPA_DRIVER_FLAGS_ACS_OFFLOAD) {
 		wpa_printf(MSG_INFO, "ACS: Offloading to driver");
-		if (hostapd_drv_do_acs(iface->bss[0]))
+
+		err = hostapd_drv_do_acs(iface->bss[0]);
+		if (err) {
+			if (err == 1)
+				return HOSTAPD_CHAN_INVALID_NO_IR;
 			return HOSTAPD_CHAN_INVALID;
+		}
+
 		return HOSTAPD_CHAN_ACS;
 	}
 
diff --git a/src/ap/ap_config.c b/src/ap/ap_config.c
index c3ee506..60d3566 100644
--- a/src/ap/ap_config.c
+++ b/src/ap/ap_config.c
@@ -90,6 +90,7 @@
 	bss->radius_server_auth_port = 1812;
 	bss->eap_sim_db_timeout = 1;
 	bss->eap_sim_id = 3;
+	bss->eap_sim_aka_fast_reauth_limit = 1000;
 	bss->ap_max_inactivity = AP_MAX_INACTIVITY;
 	bss->eapol_version = EAPOL_VERSION;
 
diff --git a/src/ap/ap_config.h b/src/ap/ap_config.h
index def5fd5..99a6d18 100644
--- a/src/ap/ap_config.h
+++ b/src/ap/ap_config.h
@@ -448,6 +448,7 @@
 	int eap_sim_aka_result_ind;
 	int eap_sim_id;
 	char *imsi_privacy_key;
+	int eap_sim_aka_fast_reauth_limit;
 	int tnc;
 	int fragment_size;
 	u16 pwd_group;
@@ -937,6 +938,17 @@
 	u8 rnr;
 	char *config_id;
 	bool xrates_supported;
+
+#ifdef CONFIG_IEEE80211BE
+	/* The AP is part of an AP MLD */
+	u8 mld_ap;
+
+	/* The MLD ID to which the AP MLD is affiliated with */
+	u8 mld_id;
+
+	/* The AP's MLD MAC address within the AP MLD */
+	u8 mld_addr[ETH_ALEN];
+#endif /* CONFIG_IEEE80211BE */
 };
 
 /**
diff --git a/src/ap/ap_drv_ops.c b/src/ap/ap_drv_ops.c
index aa4dbe9..8f9cc5b 100644
--- a/src/ap/ap_drv_ops.c
+++ b/src/ap/ap_drv_ops.c
@@ -430,7 +430,7 @@
 		    size_t eht_capab_len,
 		    const struct ieee80211_he_6ghz_band_cap *he_6ghz_capab,
 		    u32 flags, u8 qosinfo, u8 vht_opmode, int supp_p2p_ps,
-		    int set)
+		    int set, const u8 *link_addr, bool mld_link_sta)
 {
 	struct hostapd_sta_add_params params;
 
@@ -460,6 +460,19 @@
 	params.support_p2p_ps = supp_p2p_ps;
 	params.set = set;
 	params.mld_link_id = -1;
+
+#ifdef CONFIG_IEEE80211BE
+	/*
+	 * An AP MLD needs to always specify to what link the station needs
+	 * to be added.
+	 */
+	if (hapd->conf->mld_ap) {
+		params.mld_link_id = hapd->mld_link_id;
+		params.mld_link_addr = link_addr;
+		params.mld_link_sta = mld_link_sta;
+	}
+#endif /* CONFIG_IEEE80211BE */
+
 	return hapd->driver->sta_add(hapd->drv_priv, &params);
 }
 
@@ -540,12 +553,12 @@
 
 
 int hostapd_get_seqnum(const char *ifname, struct hostapd_data *hapd,
-		       const u8 *addr, int idx, u8 *seq)
+		       const u8 *addr, int idx, int link_id, u8 *seq)
 {
 	if (hapd->driver == NULL || hapd->driver->get_seqnum == NULL)
 		return 0;
 	return hapd->driver->get_seqnum(ifname, hapd->drv_priv, addr, idx,
-					seq);
+					link_id, seq);
 }
 
 
@@ -584,6 +597,17 @@
 		return 0;
 	if (hapd->driver->set_freq == NULL)
 		return 0;
+
+	data.link_id = -1;
+
+#ifdef CONFIG_IEEE80211BE
+	if (hapd->conf->mld_ap) {
+		data.link_id = hapd->mld_link_id;
+		wpa_printf(MSG_DEBUG,
+			   "hostapd_set_freq: link_id=%d", data.link_id);
+	}
+#endif /* CONFIG_IEEE80211BE */
+
 	return hapd->driver->set_freq(hapd->drv_priv, &data);
 }
 
@@ -635,10 +659,19 @@
 int hostapd_set_tx_queue_params(struct hostapd_data *hapd, int queue, int aifs,
 				int cw_min, int cw_max, int burst_time)
 {
+	int link_id = -1;
+
 	if (hapd->driver == NULL || hapd->driver->set_tx_queue_params == NULL)
 		return 0;
+
+#ifdef CONFIG_IEEE80211BE
+	if (hapd->conf->mld_ap)
+		link_id = hapd->mld_link_id;
+#endif /* CONFIG_IEEE80211BE */
+
 	return hapd->driver->set_tx_queue_params(hapd->drv_priv, queue, aifs,
-						 cw_min, cw_max, burst_time);
+						 cw_min, cw_max, burst_time,
+						 link_id);
 }
 
 
@@ -646,8 +679,8 @@
 hostapd_get_hw_feature_data(struct hostapd_data *hapd, u16 *num_modes,
 			    u16 *flags, u8 *dfs_domain)
 {
-	if (hapd->driver == NULL ||
-	    hapd->driver->get_hw_feature_data == NULL)
+	if (!hapd->driver || !hapd->driver->get_hw_feature_data ||
+	    !hapd->drv_priv)
 		return NULL;
 	return hapd->driver->get_hw_feature_data(hapd->drv_priv, num_modes,
 						 flags, dfs_domain);
@@ -727,6 +760,11 @@
 	params.key_flag = key_flag;
 	params.link_id = -1;
 
+#ifdef CONFIG_IEEE80211BE
+	if (hapd->conf->mld_ap && !(key_flag & KEY_FLAG_PAIRWISE))
+		params.link_id = hapd->mld_link_id;
+#endif /* CONFIG_IEEE80211BE */
+
 	return hapd->driver->set_key(hapd->drv_priv, &params);
 }
 
@@ -736,20 +774,35 @@
 			  const u16 *csa_offs, size_t csa_offs_len,
 			  int no_encrypt)
 {
+	int link_id = -1;
+
+#ifdef CONFIG_IEEE80211BE
+	if (hapd->conf->mld_ap)
+		link_id = hapd->mld_link_id;
+#endif /* CONFIG_IEEE80211BE */
+
 	if (!hapd->driver || !hapd->driver->send_mlme || !hapd->drv_priv)
 		return 0;
 	return hapd->driver->send_mlme(hapd->drv_priv, msg, len, noack, 0,
-				       csa_offs, csa_offs_len, no_encrypt, 0);
+				       csa_offs, csa_offs_len, no_encrypt, 0,
+				       link_id);
 }
 
 
 int hostapd_drv_sta_deauth(struct hostapd_data *hapd,
 			   const u8 *addr, int reason)
 {
+	int link_id = -1;
+
+#ifdef CONFIG_IEEE80211BE
+	if (hapd->conf->mld_ap)
+		link_id = hapd->mld_link_id;
+#endif /* CONFIG_IEEE80211BE */
+
 	if (!hapd->driver || !hapd->driver->sta_deauth || !hapd->drv_priv)
 		return 0;
 	return hapd->driver->sta_deauth(hapd->drv_priv, hapd->own_addr, addr,
-					reason);
+					reason, link_id);
 }
 
 
@@ -889,6 +942,7 @@
 				      int **freq_list)
 {
 	int i;
+	bool is_no_ir = false;
 
 	for (i = 0; i < mode->num_channels; i++) {
 		struct hostapd_channel_data *chan = &mode->channels[i];
@@ -917,7 +971,12 @@
 		      (chan->flag & HOSTAPD_CHAN_RADAR)) &&
 		    !(chan->max_tx_power < hapd->iface->conf->min_tx_power))
 			int_array_add_unique(freq_list, chan->freq);
+		else if ((chan->flag & HOSTAPD_CHAN_NO_IR) &&
+			 is_6ghz_freq(chan->freq))
+			is_no_ir = true;
 	}
+
+	hapd->iface->is_no_ir = is_no_ir;
 }
 
 
@@ -935,6 +994,24 @@
 }
 
 
+void hostapd_get_mld_capa(struct hostapd_iface *iface)
+{
+	struct hostapd_data *hapd = iface->bss[0];
+
+	if (!hapd->driver || !hapd->driver->get_mld_capab)
+		return;
+
+	hapd->driver->get_mld_capab(hapd->drv_priv, WPA_IF_AP_BSS,
+				    &iface->mld_eml_capa,
+				    &iface->mld_mld_capa);
+}
+
+
+/**
+ * hostapd_drv_do_acs - Start automatic channel selection
+ * @hapd: BSS data for the device initiating ACS
+ * Returns: 0 on success, -1 on failure, 1 on failure due to NO_IR (AFC)
+ */
 int hostapd_drv_do_acs(struct hostapd_data *hapd)
 {
 	struct drv_acs_params params;
@@ -972,6 +1049,12 @@
 						 false, &freq_list);
 	}
 
+	if (!freq_list && hapd->iface->is_no_ir) {
+		wpa_printf(MSG_ERROR,
+			   "NO_IR: Interface freq_list is empty. Failing do_acs.");
+		return 1;
+	}
+
 	params.freq_list = freq_list;
 	params.edmg_enabled = hapd->iface->conf->enable_edmg;
 
diff --git a/src/ap/ap_drv_ops.h b/src/ap/ap_drv_ops.h
index 023cbf1..331b0ea 100644
--- a/src/ap/ap_drv_ops.h
+++ b/src/ap/ap_drv_ops.h
@@ -47,7 +47,7 @@
 		    size_t eht_capab_len,
 		    const struct ieee80211_he_6ghz_band_cap *he_6ghz_capab,
 		    u32 flags, u8 qosinfo, u8 vht_opmode, int supp_p2p_ps,
-		    int set);
+		    int set, const u8 *link_addr, bool mld_link_sta);
 int hostapd_set_privacy(struct hostapd_data *hapd, int enabled);
 int hostapd_set_generic_elem(struct hostapd_data *hapd, const u8 *elem,
 			     size_t elem_len);
@@ -62,7 +62,7 @@
 int hostapd_set_ieee8021x(struct hostapd_data *hapd,
 			  struct wpa_bss_params *params);
 int hostapd_get_seqnum(const char *ifname, struct hostapd_data *hapd,
-		       const u8 *addr, int idx, u8 *seq);
+		       const u8 *addr, int idx, int link_id, u8 *seq);
 int hostapd_flush(struct hostapd_data *hapd);
 int hostapd_set_freq(struct hostapd_data *hapd, enum hostapd_hw_mode mode,
 		     int freq, int channel, int edmg, u8 edmg_channel,
@@ -155,6 +155,7 @@
 			    u8 qos_map_set_len);
 
 void hostapd_get_ext_capa(struct hostapd_iface *iface);
+void hostapd_get_mld_capa(struct hostapd_iface *iface);
 
 void hostapd_get_hw_mode_any_channels(struct hostapd_data *hapd,
 				      struct hostapd_hw_modes *mode,
@@ -172,12 +173,13 @@
 
 static inline int hostapd_drv_set_sta_vlan(const char *ifname,
 					   struct hostapd_data *hapd,
-					   const u8 *addr, int vlan_id)
+					   const u8 *addr, int vlan_id,
+					   int link_id)
 {
 	if (hapd->driver == NULL || hapd->driver->set_sta_vlan == NULL)
 		return 0;
 	return hapd->driver->set_sta_vlan(hapd->drv_priv, addr, ifname,
-					  vlan_id);
+					  vlan_id, link_id);
 }
 
 static inline int hostapd_drv_get_inact_sec(struct hostapd_data *hapd,
@@ -199,13 +201,13 @@
 static inline int hostapd_drv_hapd_send_eapol(struct hostapd_data *hapd,
 					      const u8 *addr, const u8 *data,
 					      size_t data_len, int encrypt,
-					      u32 flags)
+					      u32 flags, int link_id)
 {
 	if (hapd->driver == NULL || hapd->driver->hapd_send_eapol == NULL)
 		return 0;
 	return hapd->driver->hapd_send_eapol(hapd->drv_priv, addr, data,
 					     data_len, encrypt,
-					     hapd->own_addr, flags);
+					     hapd->own_addr, flags, link_id);
 }
 
 static inline int hostapd_drv_read_sta_data(
@@ -440,4 +442,16 @@
 }
 #endif /* CONFIG_TESTING_OPTIONS */
 
+#ifdef CONFIG_IEEE80211BE
+static inline int hostapd_drv_link_add(struct hostapd_data *hapd,
+				       u8 link_id, const u8 *addr)
+{
+	if (!hapd->driver || !hapd->drv_priv || !hapd->driver->link_add)
+		return -1;
+
+	return hapd->driver->link_add(hapd->drv_priv, link_id, addr);
+
+}
+#endif /* CONFIG_IEEE80211BE */
+
 #endif /* AP_DRV_OPS */
diff --git a/src/ap/authsrv.c b/src/ap/authsrv.c
index 4ab2a4a..1488dcc 100644
--- a/src/ap/authsrv.c
+++ b/src/ap/authsrv.c
@@ -106,6 +106,15 @@
 {
 	struct radius_server_conf srv;
 	struct hostapd_bss_config *conf = hapd->conf;
+
+	if (hapd->mld_first_bss) {
+		wpa_printf(MSG_DEBUG,
+			   "MLD: Using RADIUS server of the first BSS");
+
+		hapd->radius_srv = hapd->mld_first_bss->radius_srv;
+		return 0;
+	}
+
 	os_memset(&srv, 0, sizeof(srv));
 	srv.client_file = conf->radius_server_clients;
 	srv.auth_port = conf->radius_server_auth_port;
@@ -215,6 +224,8 @@
 	cfg->eap_sim_aka_result_ind = hapd->conf->eap_sim_aka_result_ind;
 	cfg->eap_sim_id = hapd->conf->eap_sim_id;
 	cfg->imsi_privacy_key = hapd->imsi_privacy_key;
+	cfg->eap_sim_aka_fast_reauth_limit =
+		hapd->conf->eap_sim_aka_fast_reauth_limit;
 	cfg->tnc = hapd->conf->tnc;
 	cfg->wps = hapd->wps;
 	cfg->fragment_size = hapd->conf->fragment_size;
@@ -238,6 +249,19 @@
 
 int authsrv_init(struct hostapd_data *hapd)
 {
+	if (hapd->mld_first_bss) {
+		wpa_printf(MSG_DEBUG, "MLD: Using auth_serv of the first BSS");
+
+#ifdef EAP_TLS_FUNCS
+		hapd->ssl_ctx = hapd->mld_first_bss->ssl_ctx;
+#endif /* EAP_TLS_FUNCS */
+		hapd->eap_cfg = hapd->mld_first_bss->eap_cfg;
+#ifdef EAP_SIM_DB
+		hapd->eap_sim_db_priv = hapd->mld_first_bss->eap_sim_db_priv;
+#endif /* EAP_SIM_DB */
+		return 0;
+	}
+
 #ifdef EAP_TLS_FUNCS
 	if (hapd->conf->eap_server &&
 	    (hapd->conf->ca_cert || hapd->conf->server_cert ||
@@ -352,6 +376,21 @@
 
 void authsrv_deinit(struct hostapd_data *hapd)
 {
+	if (hapd->mld_first_bss) {
+		wpa_printf(MSG_DEBUG,
+			   "MLD: Deinit auth_serv of a non-first BSS");
+
+		hapd->radius_srv = NULL;
+		hapd->eap_cfg = NULL;
+#ifdef EAP_SIM_DB
+		hapd->eap_sim_db_priv = NULL;
+#endif /* EAP_SIM_DB */
+#ifdef EAP_TLS_FUNCS
+		hapd->ssl_ctx = NULL;
+#endif /* EAP_TLS_FUNCS */
+		return;
+	}
+
 #ifdef RADIUS_SERVER
 	radius_server_deinit(hapd->radius_srv);
 	hapd->radius_srv = NULL;
diff --git a/src/ap/beacon.c b/src/ap/beacon.c
index de944fe..1b5cea9 100644
--- a/src/ap/beacon.c
+++ b/src/ap/beacon.c
@@ -17,6 +17,7 @@
 #include "common/ieee802_11_common.h"
 #include "common/hw_features_common.h"
 #include "common/wpa_ctrl.h"
+#include "crypto/sha1.h"
 #include "wps/wps_defs.h"
 #include "p2p/p2p.h"
 #include "hostapd.h"
@@ -87,6 +88,12 @@
 
 static u8 * hostapd_eid_ds_params(struct hostapd_data *hapd, u8 *eid)
 {
+	enum hostapd_hw_mode hw_mode = hapd->iconf->hw_mode;
+
+	if (hw_mode != HOSTAPD_MODE_IEEE80211G &&
+	    hw_mode != HOSTAPD_MODE_IEEE80211B)
+		return eid;
+
 	*eid++ = WLAN_EID_DS_PARAMS;
 	*eid++ = 1;
 	*eid++ = hapd->iconf->channel;
@@ -471,6 +478,7 @@
 	size_t len, rnr_len = 0;
 	u8 elem_count = 0, *elem = NULL, **elem_offset = NULL, *end;
 	u8 rnr_elem_count = 0, *rnr_elem = NULL, **rnr_elem_offset = NULL;
+	size_t i;
 
 	if (!iface->mbssid_max_interfaces ||
 	    iface->num_bss > iface->mbssid_max_interfaces ||
@@ -478,6 +486,14 @@
 	     !iface->ema_max_periodicity))
 		goto fail;
 
+	/* Make sure bss->xrates_supported is set for all BSSs to know whether
+	 * it need to be non-inherited. */
+	for (i = 0; i < iface->num_bss; i++) {
+		u8 buf[100];
+
+		hostapd_eid_ext_supp_rates(iface->bss[i], buf);
+	}
+
 	tx_bss = hostapd_mbssid_get_tx_bss(hapd);
 	len = hostapd_eid_mbssid_len(tx_bss, WLAN_FC_STYPE_BEACON, &elem_count,
 				     NULL, 0, &rnr_len);
@@ -605,6 +621,14 @@
 		buflen += 3 + sizeof(struct ieee80211_eht_operation);
 		if (hapd->iconf->punct_bitmap)
 			buflen += EHT_OPER_DISABLED_SUBCHAN_BITMAP_SIZE;
+
+		/*
+		 * TODO: Multi-Link element has variable length and can be
+		 * long based on the common info and number of per
+		 * station profiles. For now use 256.
+		 */
+		if (hapd->conf->mld_ap)
+			buflen += 256;
 	}
 #endif /* CONFIG_IEEE80211BE */
 
@@ -755,6 +779,8 @@
 
 #ifdef CONFIG_IEEE80211BE
 	if (hapd->iconf->ieee80211be && !hapd->conf->disable_11be) {
+		if (hapd->conf->mld_ap)
+			pos = hostapd_eid_eht_basic_ml(hapd, pos, NULL, true);
 		pos = hostapd_eid_eht_capab(hapd, pos, IEEE80211_MODE_AP);
 		pos = hostapd_eid_eht_operation(hapd, pos);
 	}
@@ -1365,10 +1391,128 @@
 
 #ifdef CONFIG_FILS
 
+static u16 hostapd_gen_fils_discovery_phy_index(struct hostapd_data *hapd)
+{
+#ifdef CONFIG_IEEE80211BE
+	if (hapd->iconf->ieee80211be && !hapd->conf->disable_11be)
+		return FD_CAP_PHY_INDEX_EHT;
+#endif /* CONFIG_IEEE80211BE */
+
+#ifdef CONFIG_IEEE80211AX
+	if (hapd->iconf->ieee80211ax && !hapd->conf->disable_11ax)
+		return FD_CAP_PHY_INDEX_HE;
+#endif /* CONFIG_IEEE80211AX */
+
+#ifdef CONFIG_IEEE80211AC
+	if (hapd->iconf->ieee80211ac && !hapd->conf->disable_11ac)
+		return FD_CAP_PHY_INDEX_VHT;
+#endif /* CONFIG_IEEE80211AC */
+
+	if (hapd->iconf->ieee80211n && !hapd->conf->disable_11n)
+		return FD_CAP_PHY_INDEX_HT;
+
+	return 0;
+}
+
+
+static u16 hostapd_gen_fils_discovery_nss(struct hostapd_hw_modes *mode,
+					  u16 phy_index, u8 he_mcs_nss_size)
+{
+	u16 nss = 0;
+
+	if (!mode)
+		return 0;
+
+	if (phy_index == FD_CAP_PHY_INDEX_HE) {
+		const u8 *he_mcs = mode->he_capab[IEEE80211_MODE_AP].mcs;
+		int i;
+		u16 mcs[6];
+
+		os_memset(mcs, 0xff, 6 * sizeof(u16));
+
+		if (he_mcs_nss_size == 4) {
+			mcs[0] = WPA_GET_LE16(&he_mcs[0]);
+			mcs[1] = WPA_GET_LE16(&he_mcs[2]);
+		}
+
+		if (he_mcs_nss_size == 8) {
+			mcs[2] = WPA_GET_LE16(&he_mcs[4]);
+			mcs[3] = WPA_GET_LE16(&he_mcs[6]);
+		}
+
+		if (he_mcs_nss_size == 12) {
+			mcs[4] = WPA_GET_LE16(&he_mcs[8]);
+			mcs[5] = WPA_GET_LE16(&he_mcs[10]);
+		}
+
+		for (i = 0; i < HE_NSS_MAX_STREAMS; i++) {
+			u16 nss_mask = 0x3 << (i * 2);
+
+			/*
+			 * If Tx and/or Rx indicate support for a given NSS,
+			 * count it towards the maximum NSS.
+			 */
+			if (he_mcs_nss_size == 4 &&
+			    (((mcs[0] & nss_mask) != nss_mask) ||
+			     ((mcs[1] & nss_mask) != nss_mask))) {
+				nss++;
+				continue;
+			}
+
+			if (he_mcs_nss_size == 8 &&
+			    (((mcs[2] & nss_mask) != nss_mask) ||
+			     ((mcs[3] & nss_mask) != nss_mask))) {
+				nss++;
+				continue;
+			}
+
+			if (he_mcs_nss_size == 12 &&
+			    (((mcs[4] & nss_mask) != nss_mask) ||
+			     ((mcs[5] & nss_mask) != nss_mask))) {
+				nss++;
+				continue;
+			}
+		}
+	} else if (phy_index == FD_CAP_PHY_INDEX_EHT) {
+		u8 rx_nss, tx_nss, max_nss = 0, i;
+		u8 *mcs = mode->eht_capab[IEEE80211_MODE_AP].mcs;
+
+		/*
+		 * The Supported EHT-MCS And NSS Set field for the AP contains
+		 * one to three EHT-MCS Map fields based on the supported
+		 * bandwidth. Check the first byte (max NSS for Rx/Tx that
+		 * supports EHT-MCS 0-9) for each bandwidth (<= 80,
+		 * 160, 320) to find the maximum NSS. This assumes that
+		 * the lowest MCS rates support the largest number of spatial
+		 * streams. If values are different between Tx, Rx or the
+		 * bandwidths, choose the highest value.
+		 */
+		for (i = 0; i < 3; i++) {
+			rx_nss = mcs[3 * i] & 0x0F;
+			if (rx_nss > max_nss)
+				max_nss = rx_nss;
+
+			tx_nss = (mcs[3 * i] & 0xF0) >> 4;
+			if (tx_nss > max_nss)
+				max_nss = tx_nss;
+		}
+
+		nss = max_nss;
+	}
+
+	if (nss > 4)
+		return FD_CAP_NSS_5_8 << FD_CAP_NSS_SHIFT;
+	if (nss)
+		return (nss - 1) << FD_CAP_NSS_SHIFT;
+
+	return 0;
+}
+
+
 static u16 hostapd_fils_discovery_cap(struct hostapd_data *hapd)
 {
-	u16 cap_info, phy_index = 0;
-	u8 chwidth = FD_CAP_BSS_CHWIDTH_20, mcs_nss_size = 4;
+	u16 cap_info, phy_index;
+	u8 chwidth = FD_CAP_BSS_CHWIDTH_20, he_mcs_nss_size = 4;
 	struct hostapd_hw_modes *mode = hapd->iface->current_mode;
 
 	cap_info = FD_CAP_ESS;
@@ -1376,17 +1520,15 @@
 		cap_info |= FD_CAP_PRIVACY;
 
 	if (is_6ghz_op_class(hapd->iconf->op_class)) {
-		phy_index = FD_CAP_PHY_INDEX_HE;
-
 		switch (hapd->iconf->op_class) {
 		case 137:
 			chwidth = FD_CAP_BSS_CHWIDTH_320;
 			break;
 		case 135:
-			mcs_nss_size += 4;
+			he_mcs_nss_size += 4;
 			/* fallthrough */
 		case 134:
-			mcs_nss_size += 4;
+			he_mcs_nss_size += 4;
 			chwidth = FD_CAP_BSS_CHWIDTH_160_80_80;
 			break;
 		case 133:
@@ -1399,10 +1541,10 @@
 	} else {
 		switch (hostapd_get_oper_chwidth(hapd->iconf)) {
 		case CONF_OPER_CHWIDTH_80P80MHZ:
-			mcs_nss_size += 4;
+			he_mcs_nss_size += 4;
 			/* fallthrough */
 		case CONF_OPER_CHWIDTH_160MHZ:
-			mcs_nss_size += 4;
+			he_mcs_nss_size += 4;
 			chwidth = FD_CAP_BSS_CHWIDTH_160_80_80;
 			break;
 		case CONF_OPER_CHWIDTH_80MHZ:
@@ -1417,79 +1559,13 @@
 		default:
 			break;
 		}
-
-#ifdef CONFIG_IEEE80211AX
-		if (hapd->iconf->ieee80211ax && !hapd->conf->disable_11ax)
-			phy_index = FD_CAP_PHY_INDEX_HE;
-#endif /* CONFIG_IEEE80211AX */
-#ifdef CONFIG_IEEE80211AC
-		if (!phy_index &&
-		    hapd->iconf->ieee80211ac && !hapd->conf->disable_11ac)
-			phy_index = FD_CAP_PHY_INDEX_VHT;
-#endif /* CONFIG_IEEE80211AC */
-		if (!phy_index &&
-		    hapd->iconf->ieee80211n && !hapd->conf->disable_11n)
-			phy_index = FD_CAP_PHY_INDEX_HT;
 	}
 
+	phy_index = hostapd_gen_fils_discovery_phy_index(hapd);
 	cap_info |= phy_index << FD_CAP_PHY_INDEX_SHIFT;
 	cap_info |= chwidth << FD_CAP_BSS_CHWIDTH_SHIFT;
-
-	if (mode && phy_index == FD_CAP_PHY_INDEX_HE) {
-		const u8 *he_mcs = mode->he_capab[IEEE80211_MODE_AP].mcs;
-		int i;
-		u16 nss = 0, mcs[6];
-
-		os_memset(mcs, 0xffff, 6 * sizeof(u16));
-
-		if (mcs_nss_size == 4) {
-			mcs[0] = WPA_GET_LE16(&he_mcs[0]);
-			mcs[1] = WPA_GET_LE16(&he_mcs[2]);
-		}
-
-		if (mcs_nss_size == 8) {
-			mcs[2] = WPA_GET_LE16(&he_mcs[4]);
-			mcs[3] = WPA_GET_LE16(&he_mcs[6]);
-		}
-
-		if (mcs_nss_size == 12) {
-			mcs[4] = WPA_GET_LE16(&he_mcs[8]);
-			mcs[5] = WPA_GET_LE16(&he_mcs[10]);
-		}
-
-		for (i = 0; i < HE_NSS_MAX_STREAMS; i++) {
-			u16 nss_mask = 0x3 << (i * 2);
-
-			/*
-			 * If NSS values supported by RX and TX are different
-			 * then choose the smaller of the two as the maximum
-			 * supported NSS as that is the value supported by
-			 * both RX and TX.
-			 */
-			if (mcs_nss_size == 4 &&
-			    (((mcs[0] & nss_mask) == nss_mask) ||
-			     ((mcs[1] & nss_mask) == nss_mask)))
-				continue;
-
-			if (mcs_nss_size == 8 &&
-			    (((mcs[2] & nss_mask) == nss_mask) ||
-			     ((mcs[3] & nss_mask) == nss_mask)))
-				continue;
-
-			if (mcs_nss_size == 12 &&
-			    (((mcs[4] & nss_mask) == nss_mask) ||
-			     ((mcs[5] & nss_mask) == nss_mask)))
-				continue;
-
-			nss++;
-		}
-
-		if (nss > 4)
-			cap_info |= FD_CAP_NSS_5_8 << FD_CAP_NSS_SHIFT;
-		else if (nss)
-			cap_info |= (nss - 1) << FD_CAP_NSS_SHIFT;
-	}
-
+	cap_info |= hostapd_gen_fils_discovery_nss(mode, phy_index,
+						   he_mcs_nss_size);
 	return cap_info;
 }
 
@@ -1711,6 +1787,14 @@
 		tail_len += 3 + sizeof(struct ieee80211_eht_operation);
 		if (hapd->iconf->punct_bitmap)
 			tail_len += EHT_OPER_DISABLED_SUBCHAN_BITMAP_SIZE;
+
+		/*
+		 * TODO: Multi-Link element has variable length and can be
+		 * long based on the common info and number of per
+		 * station profiles. For now use 256.
+		 */
+		if (hapd->conf->mld_ap)
+			tail_len += 256;
 	}
 #endif /* CONFIG_IEEE80211BE */
 
@@ -1881,6 +1965,9 @@
 
 #ifdef CONFIG_IEEE80211BE
 	if (hapd->iconf->ieee80211be && !hapd->conf->disable_11be) {
+		if (hapd->conf->mld_ap)
+			tailpos = hostapd_eid_eht_basic_ml(hapd, tailpos, NULL,
+							   true);
 		tailpos = hostapd_eid_eht_capab(hapd, tailpos,
 						IEEE80211_MODE_AP);
 		tailpos = hostapd_eid_eht_operation(hapd, tailpos);
@@ -1940,6 +2027,50 @@
 	resp = hostapd_probe_resp_offloads(hapd, &resp_len);
 #endif /* NEED_AP_MLME */
 
+	/* If key management offload is enabled, configure PSK to the driver. */
+	if (wpa_key_mgmt_wpa_psk_no_sae(hapd->conf->wpa_key_mgmt) &&
+	    (hapd->iface->drv_flags2 &
+	     WPA_DRIVER_FLAGS2_4WAY_HANDSHAKE_AP_PSK)) {
+		if (hapd->conf->ssid.wpa_psk && hapd->conf->ssid.wpa_psk_set) {
+			os_memcpy(params->psk, hapd->conf->ssid.wpa_psk->psk,
+				  PMK_LEN);
+			params->psk_len = PMK_LEN;
+		} else if (hapd->conf->ssid.wpa_passphrase &&
+			   pbkdf2_sha1(hapd->conf->ssid.wpa_passphrase,
+				       hapd->conf->ssid.ssid,
+				       hapd->conf->ssid.ssid_len, 4096,
+				       params->psk, PMK_LEN) == 0) {
+			params->psk_len = PMK_LEN;
+		}
+	}
+
+#ifdef CONFIG_SAE
+	/* If SAE offload is enabled, provide password to lower layer for
+	 * SAE authentication and PMK generation.
+	 */
+	if (wpa_key_mgmt_sae(hapd->conf->wpa_key_mgmt) &&
+	    (hapd->iface->drv_flags2 & WPA_DRIVER_FLAGS2_SAE_OFFLOAD_AP)) {
+		if (hostapd_sae_pk_in_use(hapd->conf)) {
+			wpa_printf(MSG_ERROR,
+				   "SAE PK not supported with SAE offload");
+			return -1;
+		}
+
+		if (hostapd_sae_pw_id_in_use(hapd->conf)) {
+			wpa_printf(MSG_ERROR,
+				   "SAE Password Identifiers not supported with SAE offload");
+			return -1;
+		}
+
+		params->sae_password = sae_get_password(hapd, NULL, NULL, NULL,
+							NULL, NULL);
+		if (!params->sae_password) {
+			wpa_printf(MSG_ERROR, "SAE password not configured for offload");
+			return -1;
+		}
+	}
+#endif /* CONFIG_SAE */
+
 	params->head = (u8 *) head;
 	params->head_len = head_len;
 	params->tail = tail;
@@ -2030,6 +2161,14 @@
 		}
 	}
 
+#ifdef CONFIG_IEEE80211BE
+	if (hapd->conf->mld_ap && hapd->iconf->ieee80211be &&
+	    !hapd->conf->disable_11be) {
+		params->mld_ap = true;
+		params->mld_link_id = hapd->mld_link_id;
+	}
+#endif /* CONFIG_IEEE80211BE */
+
 	return 0;
 }
 
@@ -2172,6 +2311,12 @@
 }
 
 
+void ieee802_11_set_beacon_per_bss_only(struct hostapd_data *hapd)
+{
+	__ieee802_11_set_beacon(hapd);
+}
+
+
 int ieee802_11_set_beacon(struct hostapd_data *hapd)
 {
 	struct hostapd_iface *iface = hapd->iface;
@@ -2186,21 +2331,29 @@
 	if (!iface->interfaces || iface->interfaces->count <= 1)
 		return 0;
 
-	/* Update Beacon frames in case of 6 GHz colocation */
+	/* Update Beacon frames in case of 6 GHz colocation or AP MLD */
 	is_6g = is_6ghz_op_class(iface->conf->op_class);
 	for (j = 0; j < iface->interfaces->count; j++) {
-		struct hostapd_iface *colocated;
+		struct hostapd_iface *other;
+		bool mld_ap = false;
 
-		colocated = iface->interfaces->iface[j];
-		if (colocated == iface || !colocated || !colocated->conf)
+		other = iface->interfaces->iface[j];
+		if (other == iface || !other || !other->conf)
 			continue;
 
-		if (is_6g == is_6ghz_op_class(colocated->conf->op_class))
+#ifdef CONFIG_IEEE80211BE
+		if (hapd->conf->mld_ap && other->bss[0]->conf->mld_ap &&
+		    hapd->conf->mld_id == other->bss[0]->conf->mld_id)
+			mld_ap = true;
+#endif /* CONFIG_IEEE80211BE */
+
+		if (is_6g == is_6ghz_op_class(other->conf->op_class) &&
+		    !mld_ap)
 			continue;
 
-		for (i = 0; i < colocated->num_bss; i++) {
-			if (colocated->bss[i] && colocated->bss[i]->started)
-				__ieee802_11_set_beacon(colocated->bss[i]);
+		for (i = 0; i < other->num_bss; i++) {
+			if (other->bss[i] && other->bss[i]->started)
+				__ieee802_11_set_beacon(other->bss[i]);
 		}
 	}
 
diff --git a/src/ap/beacon.h b/src/ap/beacon.h
index c320825..b32b2a7 100644
--- a/src/ap/beacon.h
+++ b/src/ap/beacon.h
@@ -15,6 +15,7 @@
 void handle_probe_req(struct hostapd_data *hapd,
 		      const struct ieee80211_mgmt *mgmt, size_t len,
 		      int ssi_signal);
+void ieee802_11_set_beacon_per_bss_only(struct hostapd_data *hapd);
 int ieee802_11_set_beacon(struct hostapd_data *hapd);
 int ieee802_11_set_beacons(struct hostapd_iface *iface);
 int ieee802_11_update_beacons(struct hostapd_iface *iface);
diff --git a/src/ap/bss_load.c b/src/ap/bss_load.c
index 725d3cd..e9baafc 100644
--- a/src/ap/bss_load.c
+++ b/src/ap/bss_load.c
@@ -55,7 +55,7 @@
 		return;
 	}
 
-	ieee802_11_set_beacon(hapd);
+	ieee802_11_set_beacon_per_bss_only(hapd);
 
 	if (get_bss_load_update_timeout(hapd, &sec, &usec) < 0)
 		return;
diff --git a/src/ap/ctrl_iface_ap.c b/src/ap/ctrl_iface_ap.c
index 6934a73..a6fcb7e 100644
--- a/src/ap/ctrl_iface_ap.c
+++ b/src/ap/ctrl_iface_ap.c
@@ -845,6 +845,16 @@
 		if (os_snprintf_error(buflen - len, ret))
 			return len;
 		len += ret;
+
+		if (!iconf->he_op.he_bss_color_disabled &&
+		    iconf->he_op.he_bss_color) {
+			ret = os_snprintf(buf + len, buflen - len,
+					  "he_bss_color=%d\n",
+					  iconf->he_op.he_bss_color);
+			if (os_snprintf_error(buflen - len, ret))
+				return len;
+			len += ret;
+		}
 	}
 #endif /* CONFIG_IEEE80211AX */
 
@@ -938,6 +948,21 @@
 		if (os_snprintf_error(buflen - len, ret))
 			return len;
 		len += ret;
+
+#ifdef CONFIG_IEEE80211BE
+		if (bss->conf->mld_ap) {
+			ret = os_snprintf(buf + len, buflen - len,
+					  "mld_addr[%d]=" MACSTR "\n"
+					  "mld_id[%d]=%d\n"
+					  "mld_link_id[%d]=%d\n",
+					  (int) i, MAC2STR(bss->mld_addr),
+					  (int) i, bss->conf->mld_id,
+					  (int) i, bss->mld_link_id);
+			if (os_snprintf_error(buflen - len, ret))
+				return len;
+			len += ret;
+		}
+#endif /* CONFIG_IEEE80211BE */
 	}
 
 	if (hapd->conf->chan_util_avg_period) {
diff --git a/src/ap/dfs.c b/src/ap/dfs.c
index e8c5ec9..9a5d3c8 100644
--- a/src/ap/dfs.c
+++ b/src/ap/dfs.c
@@ -34,7 +34,7 @@
 
 static bool dfs_use_radar_background(struct hostapd_iface *iface)
 {
-	return (iface->drv_flags2 & WPA_DRIVER_RADAR_BACKGROUND) &&
+	return (iface->drv_flags2 & WPA_DRIVER_FLAGS2_RADAR_BACKGROUND) &&
 		iface->conf->enable_background_radar;
 }
 
@@ -362,8 +362,9 @@
 	if (iface->conf->ieee80211n && iface->conf->secondary_channel == -1)
 		channel_no -= 4;
 
-	/* VHT/HE */
-	if (iface->conf->ieee80211ac || iface->conf->ieee80211ax) {
+	/* VHT/HE/EHT */
+	if (iface->conf->ieee80211ac || iface->conf->ieee80211ax ||
+	    iface->conf->ieee80211be) {
 		switch (hostapd_get_oper_chwidth(iface->conf)) {
 		case CONF_OPER_CHWIDTH_USE_HT:
 			break;
@@ -381,9 +382,13 @@
 			chan_seg1 = hostapd_get_oper_centr_freq_seg1_idx(
 				iface->conf) - 6;
 			break;
+		case CONF_OPER_CHWIDTH_320MHZ:
+			channel_no = hostapd_get_oper_centr_freq_seg0_idx(
+				iface->conf) - 30;
+			break;
 		default:
 			wpa_printf(MSG_INFO,
-				   "DFS only VHT20/40/80/160/80+80 is supported now");
+				   "DFS only EHT20/40/80/160/80+80/320 is supported now");
 			channel_no = -1;
 			break;
 		}
diff --git a/src/ap/dpp_hostapd.c b/src/ap/dpp_hostapd.c
index 70dd18e..7a8ea4e 100644
--- a/src/ap/dpp_hostapd.c
+++ b/src/ap/dpp_hostapd.c
@@ -2406,7 +2406,9 @@
 	char ssid_hex[2 * SSID_MAX_LEN + 1], *pass_hex = NULL;
 	char cmd[300];
 	const char *password = NULL;
+#ifdef CONFIG_SAE
 	struct sae_password_entry *e;
+#endif /* CONFIG_SAE */
 	int conf_id = -1;
 	bool sae = false, psk = false;
 	size_t len;
diff --git a/src/ap/drv_callbacks.c b/src/ap/drv_callbacks.c
index 510a06c..98794c2 100644
--- a/src/ap/drv_callbacks.c
+++ b/src/ap/drv_callbacks.c
@@ -59,9 +59,10 @@
 	if (!sta->fils_pending_assoc_req)
 		return;
 
-	ieee802_11_parse_elems(sta->fils_pending_assoc_req,
-			       sta->fils_pending_assoc_req_len, &elems, 0);
-	if (!elems.fils_session) {
+	if (ieee802_11_parse_elems(sta->fils_pending_assoc_req,
+				   sta->fils_pending_assoc_req_len, &elems,
+				   0) == ParseFailed ||
+	    !elems.fils_session) {
 		wpa_printf(MSG_DEBUG, "%s failed to find FILS Session element",
 			   __func__);
 		return;
@@ -131,8 +132,122 @@
 }
 
 
+#ifdef CONFIG_IEEE80211BE
+static int hostapd_update_sta_links_status(struct hostapd_data *hapd,
+					   struct sta_info *sta,
+					   const u8 *resp_ies,
+					   size_t resp_ies_len)
+{
+	struct mld_info *info = &sta->mld_info;
+	struct wpabuf *mlebuf;
+	const u8 *mle, *pos;
+	struct ieee802_11_elems elems;
+	size_t mle_len, rem_len;
+	int ret = 0;
+
+	if (!resp_ies) {
+		wpa_printf(MSG_DEBUG,
+			   "MLO: (Re)Association Response frame elements not available");
+		return -1;
+	}
+
+	if (ieee802_11_parse_elems(resp_ies, resp_ies_len, &elems, 0) ==
+	    ParseFailed) {
+		wpa_printf(MSG_DEBUG,
+			   "MLO: Failed to parse (Re)Association Response frame elements");
+		return -1;
+	}
+
+	mlebuf = ieee802_11_defrag_mle(&elems, MULTI_LINK_CONTROL_TYPE_BASIC);
+	if (!mlebuf) {
+		wpa_printf(MSG_ERROR,
+			   "MLO: Basic Multi-Link element not found in (Re)Association Response frame");
+		return -1;
+	}
+
+	mle = wpabuf_head(mlebuf);
+	mle_len = wpabuf_len(mlebuf);
+	if (mle_len < MULTI_LINK_CONTROL_LEN + 1 ||
+	    mle_len - MULTI_LINK_CONTROL_LEN < mle[MULTI_LINK_CONTROL_LEN]) {
+		wpa_printf(MSG_ERROR,
+			   "MLO: Invalid Multi-Link element in (Re)Association Response frame");
+		ret = -1;
+		goto out;
+	}
+
+	/* Skip Common Info */
+	pos = mle + MULTI_LINK_CONTROL_LEN + mle[MULTI_LINK_CONTROL_LEN];
+	rem_len = mle_len -
+		(MULTI_LINK_CONTROL_LEN + mle[MULTI_LINK_CONTROL_LEN]);
+
+	/* Parse Subelements */
+	while (rem_len > 2) {
+		size_t ie_len = 2 + pos[1];
+
+		if (rem_len < ie_len)
+			break;
+
+		if (pos[0] == MULTI_LINK_SUB_ELEM_ID_PER_STA_PROFILE) {
+			u8 link_id;
+			const u8 *sta_profile;
+			size_t sta_profile_len;
+			u16 sta_ctrl;
+
+			if (pos[1] < BASIC_MLE_STA_CTRL_LEN + 1) {
+				wpa_printf(MSG_DEBUG,
+					   "MLO: Invalid per-STA profile IE");
+				goto next_subelem;
+			}
+
+			sta_profile_len = pos[1];
+			sta_profile = &pos[2];
+			sta_ctrl = WPA_GET_LE16(sta_profile);
+			link_id = sta_ctrl & BASIC_MLE_STA_CTRL_LINK_ID_MASK;
+			if (link_id >= MAX_NUM_MLD_LINKS) {
+				wpa_printf(MSG_DEBUG,
+					   "MLO: Invalid link ID in per-STA profile IE");
+				goto next_subelem;
+			}
+
+			/* Skip STA Control and STA Info */
+			if (sta_profile_len - BASIC_MLE_STA_CTRL_LEN <
+			    sta_profile[BASIC_MLE_STA_CTRL_LEN]) {
+				wpa_printf(MSG_DEBUG,
+					   "MLO: Invalid STA info in per-STA profile IE");
+				goto next_subelem;
+			}
+
+			sta_profile_len = sta_profile_len -
+				(BASIC_MLE_STA_CTRL_LEN +
+				 sta_profile[BASIC_MLE_STA_CTRL_LEN]);
+			sta_profile = sta_profile + BASIC_MLE_STA_CTRL_LEN +
+				sta_profile[BASIC_MLE_STA_CTRL_LEN];
+
+			/* Skip Capabilities Information field */
+			if (sta_profile_len < 2)
+				goto next_subelem;
+			sta_profile_len -= 2;
+			sta_profile += 2;
+
+			/* Get status of the link */
+			info->links[link_id].status = WPA_GET_LE16(sta_profile);
+		}
+next_subelem:
+		pos += ie_len;
+		rem_len -= ie_len;
+	}
+
+out:
+	wpabuf_free(mlebuf);
+	return ret;
+}
+#endif /* CONFIG_IEEE80211BE */
+
+
 int hostapd_notif_assoc(struct hostapd_data *hapd, const u8 *addr,
-			const u8 *req_ies, size_t req_ies_len, int reassoc)
+			const u8 *req_ies, size_t req_ies_len,
+			const u8 *resp_ies, size_t resp_ies_len,
+			const u8 *link_addr, int reassoc)
 {
 	struct sta_info *sta;
 	int new_assoc;
@@ -145,6 +260,9 @@
 	u16 reason = WLAN_REASON_UNSPECIFIED;
 	int status = WLAN_STATUS_SUCCESS;
 	const u8 *p2p_dev_addr = NULL;
+#ifdef CONFIG_OWE
+	struct hostapd_iface *iface = hapd->iface;
+#endif /* CONFIG_OWE */
 
 	if (addr == NULL) {
 		/*
@@ -176,7 +294,12 @@
 	hostapd_logger(hapd, addr, HOSTAPD_MODULE_IEEE80211,
 		       HOSTAPD_LEVEL_INFO, "associated");
 
-	ieee802_11_parse_elems(req_ies, req_ies_len, &elems, 0);
+	if (ieee802_11_parse_elems(req_ies, req_ies_len, &elems, 0) ==
+	    ParseFailed) {
+		wpa_printf(MSG_DEBUG, "%s: Could not parse elements", __func__);
+		return -1;
+	}
+
 	if (elems.wps_ie) {
 		ie = elems.wps_ie - 2;
 		ielen = elems.wps_ie_len + 2;
@@ -220,6 +343,53 @@
 			return -1;
 		}
 	}
+
+	if (hapd->conf->wpa && check_sa_query_need(hapd, sta)) {
+		status = WLAN_STATUS_ASSOC_REJECTED_TEMPORARILY;
+		p = hostapd_eid_assoc_comeback_time(hapd, sta, p);
+		hostapd_sta_assoc(hapd, addr, reassoc, status, buf, p - buf);
+
+		return 0;
+	}
+
+#ifdef CONFIG_IEEE80211BE
+	if (link_addr) {
+		struct mld_info *info = &sta->mld_info;
+		int i, num_valid_links = 0;
+		u8 link_id = hapd->mld_link_id;
+
+		info->mld_sta = true;
+		sta->mld_assoc_link_id = link_id;
+		os_memcpy(info->common_info.mld_addr, addr, ETH_ALEN);
+		info->links[link_id].valid = true;
+		os_memcpy(info->links[link_id].peer_addr, link_addr, ETH_ALEN);
+		os_memcpy(info->links[link_id].local_addr, hapd->own_addr,
+			  ETH_ALEN);
+
+		if (!elems.basic_mle ||
+		    hostapd_process_ml_assoc_req(hapd, &elems, sta) !=
+		    WLAN_STATUS_SUCCESS) {
+			reason = WLAN_REASON_UNSPECIFIED;
+			wpa_printf(MSG_DEBUG,
+				   "Failed to get STA non-assoc links info");
+			goto fail;
+		}
+
+		for (i = 0 ; i < MAX_NUM_MLD_LINKS; i++) {
+			if (info->links[i].valid)
+				num_valid_links++;
+		}
+		if (num_valid_links > 1 &&
+		    hostapd_update_sta_links_status(hapd, sta, resp_ies,
+						    resp_ies_len)) {
+			wpa_printf(MSG_DEBUG,
+				   "Failed to get STA non-assoc links status info");
+			reason = WLAN_REASON_UNSPECIFIED;
+			goto fail;
+		}
+	}
+#endif /* CONFIG_IEEE80211BE */
+
 	sta->flags &= ~(WLAN_STA_WPS | WLAN_STA_MAYBE_WPS | WLAN_STA_WPS2);
 
 	/*
@@ -314,17 +484,6 @@
 		    os_memcmp(ie + 2, "\x00\x50\xf2\x04", 4) == 0) {
 			struct wpabuf *wps;
 
-			if (check_sa_query_need(hapd, sta)) {
-				status = WLAN_STATUS_ASSOC_REJECTED_TEMPORARILY;
-
-				p = hostapd_eid_assoc_comeback_time(hapd, sta,
-								    p);
-
-				hostapd_sta_assoc(hapd, addr, reassoc, status,
-						  buf, p - buf);
-				return 0;
-			}
-
 			sta->flags |= WLAN_STA_WPS;
 			wps = ieee802_11_vendor_ie_concat(ie, ielen,
 							  WPS_IE_VENDOR_TYPE);
@@ -340,16 +499,6 @@
 		}
 #endif /* CONFIG_WPS */
 
-		if (check_sa_query_need(hapd, sta)) {
-			status = WLAN_STATUS_ASSOC_REJECTED_TEMPORARILY;
-
-			p = hostapd_eid_assoc_comeback_time(hapd, sta, p);
-
-			hostapd_sta_assoc(hapd, addr, reassoc, status, buf,
-					  p - buf);
-			return 0;
-		}
-
 		if (sta->wpa_sm == NULL)
 			sta->wpa_sm = wpa_auth_sta_init(hapd->wpa_auth,
 							sta->addr,
@@ -359,6 +508,15 @@
 				   "Failed to initialize WPA state machine");
 			return -1;
 		}
+#ifdef CONFIG_IEEE80211BE
+		if (sta->mld_info.mld_sta) {
+			wpa_printf(MSG_DEBUG,
+				   "MLD: Set ML info in RSN Authenticator");
+			wpa_auth_set_ml_info(sta->wpa_sm, hapd->mld_addr,
+					     sta->mld_assoc_link_id,
+					     &sta->mld_info);
+		}
+#endif /* CONFIG_IEEE80211BE */
 		res = wpa_validate_wpa_ie(hapd->wpa_auth, sta->wpa_sm,
 					  hapd->iface->freq,
 					  ie, ielen,
@@ -617,6 +775,7 @@
 
 #ifdef CONFIG_OWE
 	if ((hapd->conf->wpa_key_mgmt & WPA_KEY_MGMT_OWE) &&
+	    !(iface->drv_flags2 & WPA_DRIVER_FLAGS2_OWE_OFFLOAD_AP) &&
 	    wpa_auth_sta_key_mgmt(sta->wpa_sm) == WPA_KEY_MGMT_OWE &&
 	    elems.owe_dh) {
 		u8 *npos;
@@ -863,7 +1022,7 @@
 {
 #ifdef NEED_AP_MLME
 	int channel, chwidth, is_dfs0, is_dfs;
-	u8 seg0_idx = 0, seg1_idx = 0;
+	u8 seg0_idx = 0, seg1_idx = 0, op_class, chan_no;
 	size_t i;
 
 	hostapd_logger(hapd, NULL, HOSTAPD_MODULE_IEEE80211,
@@ -917,6 +1076,12 @@
 		break;
 	}
 
+	/* The operating channel changed when CSA finished, so need to update
+	 * hw_mode for all following operations to cover the cases where the
+	 * driver changed the operating band. */
+	if (finished && hostapd_csa_update_hwmode(hapd->iface))
+		return;
+
 	switch (hapd->iface->current_mode->mode) {
 	case HOSTAPD_MODE_IEEE80211A:
 		if (cf1 == 5935)
@@ -941,9 +1106,9 @@
 
 	hapd->iconf->channel = channel;
 	hapd->iconf->ieee80211n = ht;
-	if (!ht) {
+	if (!ht)
 		hapd->iconf->ieee80211ac = 0;
-	} else if (hapd->iconf->ch_switch_vht_config) {
+	if (hapd->iconf->ch_switch_vht_config) {
 		/* CHAN_SWITCH VHT config */
 		if (hapd->iconf->ch_switch_vht_config &
 		    CH_SWITCH_VHT_ENABLED)
@@ -951,28 +1116,35 @@
 		else if (hapd->iconf->ch_switch_vht_config &
 			 CH_SWITCH_VHT_DISABLED)
 			hapd->iconf->ieee80211ac = 0;
-	} else if (hapd->iconf->ch_switch_he_config) {
+	}
+	if (hapd->iconf->ch_switch_he_config) {
 		/* CHAN_SWITCH HE config */
 		if (hapd->iconf->ch_switch_he_config &
-		    CH_SWITCH_HE_ENABLED)
+		    CH_SWITCH_HE_ENABLED) {
 			hapd->iconf->ieee80211ax = 1;
+			if (hapd->iface->freq > 4000 &&
+			    hapd->iface->freq < 5895)
+				hapd->iconf->ieee80211ac = 1;
+		}
 		else if (hapd->iconf->ch_switch_he_config &
 			 CH_SWITCH_HE_DISABLED)
 			hapd->iconf->ieee80211ax = 0;
+	}
 #ifdef CONFIG_IEEE80211BE
-	} else if (hapd->iconf->ch_switch_eht_config) {
+	if (hapd->iconf->ch_switch_eht_config) {
 		/* CHAN_SWITCH EHT config */
 		if (hapd->iconf->ch_switch_eht_config &
 		    CH_SWITCH_EHT_ENABLED) {
 			hapd->iconf->ieee80211be = 1;
 			hapd->iconf->ieee80211ax = 1;
-			if (!is_6ghz_freq(hapd->iface->freq))
+			if (!is_6ghz_freq(hapd->iface->freq) &&
+			    hapd->iface->freq > 4000)
 				hapd->iconf->ieee80211ac = 1;
 		} else if (hapd->iconf->ch_switch_eht_config &
 			   CH_SWITCH_EHT_DISABLED)
 			hapd->iconf->ieee80211be = 0;
-#endif /* CONFIG_IEEE80211BE */
 	}
+#endif /* CONFIG_IEEE80211BE */
 	hapd->iconf->ch_switch_vht_config = 0;
 	hapd->iconf->ch_switch_he_config = 0;
 	hapd->iconf->ch_switch_eht_config = 0;
@@ -985,6 +1157,10 @@
 		hapd->iconf->ht_capab &= ~HT_CAP_INFO_SUPP_CHANNEL_WIDTH_SET;
 
 	hapd->iconf->secondary_channel = offset;
+	if (ieee80211_freq_to_channel_ext(freq, offset, chwidth,
+					  &op_class, &chan_no) !=
+	    NUM_HOSTAPD_MODES)
+		hapd->iconf->op_class = op_class;
 	hostapd_set_oper_chwidth(hapd->iconf, chwidth);
 	hostapd_set_oper_centr_freq_seg0_idx(hapd->iconf, seg0_idx);
 	hostapd_set_oper_centr_freq_seg1_idx(hapd->iconf, seg1_idx);
@@ -1202,6 +1378,9 @@
 			hapd->iconf, acs_res->vht_seg1_center_ch);
 		hostapd_set_oper_centr_freq_seg1_idx(hapd->iconf, 0);
 	}
+
+	if (hapd->iface->conf->ieee80211be && acs_res->puncture_bitmap)
+		hapd->iconf->punct_bitmap = acs_res->puncture_bitmap;
 #endif /* CONFIG_IEEE80211BE */
 
 out:
@@ -1418,6 +1597,23 @@
 
 #ifdef NEED_AP_MLME
 
+static struct hostapd_data *
+switch_link_hapd(struct hostapd_data *hapd, int link_id)
+{
+#ifdef CONFIG_IEEE80211BE
+	if (hapd->conf->mld_ap && link_id >= 0) {
+		struct hostapd_data *link_bss;
+
+		link_bss = hostapd_mld_get_link_bss(hapd, link_id);
+		if (link_bss)
+			return link_bss;
+	}
+#endif /* CONFIG_IEEE80211BE */
+
+	return hapd;
+}
+
+
 #define HAPD_BROADCAST ((struct hostapd_data *) -1)
 
 static struct hostapd_data * get_hapd_bssid(struct hostapd_iface *iface,
@@ -1454,11 +1650,15 @@
 
 static int hostapd_mgmt_rx(struct hostapd_data *hapd, struct rx_mgmt *rx_mgmt)
 {
-	struct hostapd_iface *iface = hapd->iface;
+	struct hostapd_iface *iface;
 	const struct ieee80211_hdr *hdr;
 	const u8 *bssid;
 	struct hostapd_frame_info fi;
 	int ret;
+	bool is_mld = false;
+
+	hapd = switch_link_hapd(hapd, rx_mgmt->link_id);
+	iface = hapd->iface;
 
 #ifdef CONFIG_TESTING_OPTIONS
 	if (hapd->ext_mgmt_frame_handling) {
@@ -1480,8 +1680,16 @@
 	if (bssid == NULL)
 		return 0;
 
-	hapd = get_hapd_bssid(iface, bssid);
-	if (hapd == NULL) {
+#ifdef CONFIG_IEEE80211BE
+	if (hapd->conf->mld_ap &&
+	    os_memcmp(hapd->mld_addr, bssid, ETH_ALEN) == 0)
+		is_mld = true;
+#endif /* CONFIG_IEEE80211BE */
+
+	if (!is_mld)
+		hapd = get_hapd_bssid(iface, bssid);
+
+	if (!hapd) {
 		u16 fc = le_to_host16(hdr->frame_control);
 
 		/*
@@ -1526,15 +1734,34 @@
 
 
 static void hostapd_mgmt_tx_cb(struct hostapd_data *hapd, const u8 *buf,
-			       size_t len, u16 stype, int ok)
+			       size_t len, u16 stype, int ok, int link_id)
 {
 	struct ieee80211_hdr *hdr;
-	struct hostapd_data *orig_hapd = hapd;
+	struct hostapd_data *orig_hapd, *tmp_hapd;
+
+#ifdef CONFIG_IEEE80211BE
+	if (hapd->conf->mld_ap && link_id != -1) {
+		tmp_hapd = hostapd_mld_get_link_bss(hapd, link_id);
+		if (tmp_hapd)
+			hapd = tmp_hapd;
+	}
+#endif /* CONFIG_IEEE80211BE */
+	orig_hapd = hapd;
 
 	hdr = (struct ieee80211_hdr *) buf;
-	hapd = get_hapd_bssid(hapd->iface, get_hdr_bssid(hdr, len));
-	if (!hapd)
+	tmp_hapd = get_hapd_bssid(hapd->iface, get_hdr_bssid(hdr, len));
+	if (tmp_hapd) {
+		hapd = tmp_hapd;
+#ifdef CONFIG_IEEE80211BE
+	} else if (hapd->conf->mld_ap &&
+		   os_memcmp(hapd->mld_addr, get_hdr_bssid(hdr, len),
+			     ETH_ALEN) == 0) {
+		/* AP MLD address match - use hapd pointer as-is */
+#endif /* CONFIG_IEEE80211BE */
+	} else {
 		return;
+	}
+
 	if (hapd == HAPD_BROADCAST) {
 		if (stype != WLAN_FC_STYPE_ACTION || len <= 25 ||
 		    buf[24] != WLAN_ACTION_PUBLIC)
@@ -1575,20 +1802,75 @@
 }
 
 
-static void hostapd_event_eapol_rx(struct hostapd_data *hapd, const u8 *src,
-				   const u8 *data, size_t data_len,
-				   enum frame_encryption encrypted)
+static struct hostapd_data * hostapd_find_by_sta(struct hostapd_iface *iface,
+						 const u8 *src)
 {
-	struct hostapd_iface *iface = hapd->iface;
 	struct sta_info *sta;
-	size_t j;
+	unsigned int j;
 
 	for (j = 0; j < iface->num_bss; j++) {
 		sta = ap_get_sta(iface->bss[j], src);
-		if (sta && sta->flags & WLAN_STA_ASSOC) {
-			hapd = iface->bss[j];
-			break;
+		if (sta && sta->flags & WLAN_STA_ASSOC)
+			return iface->bss[j];
+	}
+
+	return NULL;
+}
+
+
+static void hostapd_event_eapol_rx(struct hostapd_data *hapd, const u8 *src,
+				   const u8 *data, size_t data_len,
+				   enum frame_encryption encrypted,
+				   int link_id)
+{
+	struct hostapd_data *orig_hapd = hapd;
+
+#ifdef CONFIG_IEEE80211BE
+	if (link_id != -1) {
+		struct hostapd_data *h_hapd;
+
+		hapd = switch_link_hapd(hapd, link_id);
+		h_hapd = hostapd_find_by_sta(hapd->iface, src);
+		if (!h_hapd)
+			h_hapd = hostapd_find_by_sta(orig_hapd->iface, src);
+		if (h_hapd)
+			hapd = h_hapd;
+	} else if (hapd->conf->mld_ap) {
+		unsigned int i;
+
+		/* Search for STA on other MLO BSSs */
+		for (i = 0; i < hapd->iface->interfaces->count; i++) {
+			struct hostapd_iface *h =
+				hapd->iface->interfaces->iface[i];
+			struct hostapd_data *h_hapd = h->bss[0];
+			struct hostapd_bss_config *hconf = h_hapd->conf;
+
+			if (!hconf->mld_ap ||
+			    hconf->mld_id != hapd->conf->mld_id)
+				continue;
+
+			h_hapd = hostapd_find_by_sta(h, src);
+			if (h_hapd) {
+				hapd = h_hapd;
+				break;
+			}
 		}
+	} else {
+		hapd = hostapd_find_by_sta(hapd->iface, src);
+	}
+#else /* CONFIG_IEEE80211BE */
+	hapd = hostapd_find_by_sta(hapd->iface, src);
+#endif /* CONFIG_IEEE80211BE */
+
+	if (!hapd) {
+		/* WLAN cases need to have an existing association, but non-WLAN
+		 * cases (mainly, wired IEEE 802.1X) need to be able to process
+		 * EAPOL frames from new devices that do not yet have a STA
+		 * entry and as such, do not get a match in
+		 * hostapd_find_by_sta(). */
+		wpa_printf(MSG_DEBUG,
+			   "No STA-specific hostapd instance for EAPOL RX found - fall back to initial context");
+		hapd = orig_hapd;
 	}
 
 	ieee802_1x_receive(hapd, src, data, data_len, encrypted);
@@ -1827,7 +2109,7 @@
 #ifdef CONFIG_OWE
 static int hostapd_notif_update_dh_ie(struct hostapd_data *hapd,
 				      const u8 *peer, const u8 *ie,
-				      size_t ie_len)
+				      size_t ie_len, const u8 *link_addr)
 {
 	u16 status;
 	struct sta_info *sta;
@@ -1877,15 +2159,31 @@
 	}
 	sta->flags &= ~(WLAN_STA_WPS | WLAN_STA_MAYBE_WPS | WLAN_STA_WPS2);
 
+#ifdef CONFIG_IEEE80211BE
+	if (link_addr) {
+		struct mld_info *info = &sta->mld_info;
+		u8 link_id = hapd->mld_link_id;
+
+		info->mld_sta = true;
+		sta->mld_assoc_link_id = link_id;;
+		os_memcpy(info->common_info.mld_addr, peer, ETH_ALEN);
+		info->links[link_id].valid = true;
+		os_memcpy(info->links[link_id].local_addr, hapd->own_addr,
+			  ETH_ALEN);
+		os_memcpy(info->links[link_id].peer_addr, link_addr, ETH_ALEN);
+	}
+#endif /* CONFIG_IEEE80211BE */
+
 	status = owe_process_rsn_ie(hapd, sta, elems.rsn_ie,
 				    elems.rsn_ie_len, elems.owe_dh,
-				    elems.owe_dh_len);
+				    elems.owe_dh_len, link_addr);
 	if (status != WLAN_STATUS_SUCCESS)
 		ap_free_sta(hapd, sta);
 
 	return 0;
 err:
-	hostapd_drv_update_dh_ie(hapd, peer, status, NULL, 0);
+	hostapd_drv_update_dh_ie(hapd, link_addr ? link_addr : peer, status,
+				 NULL, 0);
 	return 0;
 }
 #endif /* CONFIG_OWE */
@@ -1895,6 +2193,7 @@
 			  union wpa_event_data *data)
 {
 	struct hostapd_data *hapd = ctx;
+	struct sta_info *sta;
 #ifndef CONFIG_NO_STDOUT_DEBUG
 	int level = MSG_DEBUG;
 
@@ -1935,7 +2234,8 @@
 			hostapd_mgmt_tx_cb(hapd, data->tx_status.data,
 					   data->tx_status.data_len,
 					   data->tx_status.stype,
-					   data->tx_status.ack);
+					   data->tx_status.ack,
+					   data->tx_status.link_id);
 			break;
 		case WLAN_FC_TYPE_DATA:
 			hostapd_tx_status(hapd, data->tx_status.dst,
@@ -1946,6 +2246,7 @@
 		}
 		break;
 	case EVENT_EAPOL_TX_STATUS:
+		hapd = switch_link_hapd(hapd, data->eapol_tx_status.link_id);
 		hostapd_eapol_tx_status(hapd, data->eapol_tx_status.dst,
 					data->eapol_tx_status.data,
 					data->eapol_tx_status.data_len,
@@ -1987,23 +2288,60 @@
 		hostapd_event_eapol_rx(hapd, data->eapol_rx.src,
 				       data->eapol_rx.data,
 				       data->eapol_rx.data_len,
-				       data->eapol_rx.encrypted);
+				       data->eapol_rx.encrypted,
+				       data->eapol_rx.link_id);
 		break;
 	case EVENT_ASSOC:
 		if (!data)
 			return;
+#ifdef CONFIG_IEEE80211BE
+		if (data->assoc_info.assoc_link_id != -1) {
+			hapd = hostapd_mld_get_link_bss(
+				hapd, data->assoc_info.assoc_link_id);
+			if (!hapd) {
+				wpa_printf(MSG_ERROR,
+					   "MLD: Failed to get link BSS for EVENT_ASSOC");
+				return;
+			}
+		}
+#endif /* CONFIG_IEEE80211BE */
 		hostapd_notif_assoc(hapd, data->assoc_info.addr,
 				    data->assoc_info.req_ies,
 				    data->assoc_info.req_ies_len,
+				    data->assoc_info.resp_ies,
+				    data->assoc_info.resp_ies_len,
+				    data->assoc_info.link_addr,
 				    data->assoc_info.reassoc);
 		break;
+	case EVENT_PORT_AUTHORIZED:
+		/* Port authorized event for an associated STA */
+		sta = ap_get_sta(hapd, data->port_authorized.sta_addr);
+		if (sta)
+			ap_sta_set_authorized(hapd, sta, 1);
+		else
+			wpa_printf(MSG_DEBUG,
+				   "No STA info matching port authorized event found");
+		break;
 #ifdef CONFIG_OWE
 	case EVENT_UPDATE_DH:
 		if (!data)
 			return;
+#ifdef CONFIG_IEEE80211BE
+		if (data->update_dh.assoc_link_id != -1) {
+			hapd = hostapd_mld_get_link_bss(
+				hapd, data->update_dh.assoc_link_id);
+			if (!hapd) {
+				wpa_printf(MSG_ERROR,
+					   "MLD: Failed to get link BSS for EVENT_UPDATE_DH assoc_link_id=%d",
+					   data->update_dh.assoc_link_id);
+				return;
+			}
+		}
+#endif /* CONFIG_IEEE80211BE */
 		hostapd_notif_update_dh_ie(hapd, data->update_dh.peer,
 					   data->update_dh.ie,
-					   data->update_dh.ie_len);
+					   data->update_dh.ie_len,
+					   data->update_dh.link_addr);
 		break;
 #endif /* CONFIG_OWE */
 	case EVENT_DISASSOC:
@@ -2154,7 +2492,8 @@
 	case EVENT_CCA_NOTIFY:
 		wpa_printf(MSG_DEBUG, "CCA finished on on %s",
 			   hapd->conf->iface);
-		hapd->iface->conf->he_op.he_bss_color = hapd->cca_color;
+		if (hapd->cca_color)
+			hapd->iface->conf->he_op.he_bss_color = hapd->cca_color;
 		hostapd_cleanup_cca_params(hapd);
 		break;
 #endif /* CONFIG_IEEE80211AX */
diff --git a/src/ap/hostapd.c b/src/ap/hostapd.c
index 112e6fa..236381f 100644
--- a/src/ap/hostapd.c
+++ b/src/ap/hostapd.c
@@ -55,6 +55,7 @@
 #include "hs20.h"
 #include "airtime_policy.h"
 #include "wpa_auth_kay.h"
+#include "hw_features.h"
 
 
 static int hostapd_flush_old_stations(struct hostapd_data *hapd, u16 reason);
@@ -393,6 +394,25 @@
 #endif /* CONFIG_WEP */
 
 
+static void hostapd_clear_drv_priv(struct hostapd_data *hapd)
+{
+	unsigned int i;
+
+	for (i = 0; i < hapd->iface->interfaces->count; i++) {
+		struct hostapd_iface *iface = hapd->iface->interfaces->iface[i];
+
+		if (hapd->iface == iface || !iface)
+			continue;
+
+		if (iface->bss && iface->bss[0] &&
+		    iface->bss[0]->mld_first_bss == hapd)
+			iface->bss[0]->drv_priv = NULL;
+	}
+
+	hapd->drv_priv = NULL;
+}
+
+
 void hostapd_free_hapd_data(struct hostapd_data *hapd)
 {
 	os_free(hapd->probereq_cb);
@@ -420,9 +440,11 @@
 	vlan_deinit(hapd);
 	hostapd_acl_deinit(hapd);
 #ifndef CONFIG_NO_RADIUS
-	radius_client_deinit(hapd->radius);
+	if (!hapd->mld_first_bss) {
+		radius_client_deinit(hapd->radius);
+		radius_das_deinit(hapd->radius_das);
+	}
 	hapd->radius = NULL;
-	radius_das_deinit(hapd->radius_das);
 	hapd->radius_das = NULL;
 #endif /* CONFIG_NO_RADIUS */
 
@@ -449,7 +471,7 @@
 			 * driver wrapper may have removed its internal instance
 			 * and hapd->drv_priv is not valid anymore.
 			 */
-			hapd->drv_priv = NULL;
+			hostapd_clear_drv_priv(hapd);
 		}
 	}
 
@@ -1196,6 +1218,10 @@
 	u8 if_addr[ETH_ALEN];
 	int flush_old_stations = 1;
 
+	if (hapd->mld_first_bss)
+		wpa_printf(MSG_DEBUG,
+			   "MLD: %s: Setting non-first BSS", __func__);
+
 	wpa_printf(MSG_DEBUG, "%s(hapd=%p (%s), first=%d)",
 		   __func__, hapd, conf->iface, first);
 
@@ -1354,34 +1380,43 @@
 	}
 #endif /* CONFIG_SQLITE */
 
-	hapd->radius = radius_client_init(hapd, conf->radius);
-	if (hapd->radius == NULL) {
-		wpa_printf(MSG_ERROR, "RADIUS client initialization failed.");
-		return -1;
-	}
-
-	if (conf->radius_das_port) {
-		struct radius_das_conf das_conf;
-		os_memset(&das_conf, 0, sizeof(das_conf));
-		das_conf.port = conf->radius_das_port;
-		das_conf.shared_secret = conf->radius_das_shared_secret;
-		das_conf.shared_secret_len =
-			conf->radius_das_shared_secret_len;
-		das_conf.client_addr = &conf->radius_das_client_addr;
-		das_conf.time_window = conf->radius_das_time_window;
-		das_conf.require_event_timestamp =
-			conf->radius_das_require_event_timestamp;
-		das_conf.require_message_authenticator =
-			conf->radius_das_require_message_authenticator;
-		das_conf.ctx = hapd;
-		das_conf.disconnect = hostapd_das_disconnect;
-		das_conf.coa = hostapd_das_coa;
-		hapd->radius_das = radius_das_init(&das_conf);
-		if (hapd->radius_das == NULL) {
-			wpa_printf(MSG_ERROR, "RADIUS DAS initialization "
-				   "failed.");
+	if (!hapd->mld_first_bss) {
+		hapd->radius = radius_client_init(hapd, conf->radius);
+		if (!hapd->radius) {
+			wpa_printf(MSG_ERROR,
+				   "RADIUS client initialization failed.");
 			return -1;
 		}
+
+		if (conf->radius_das_port) {
+			struct radius_das_conf das_conf;
+
+			os_memset(&das_conf, 0, sizeof(das_conf));
+			das_conf.port = conf->radius_das_port;
+			das_conf.shared_secret = conf->radius_das_shared_secret;
+			das_conf.shared_secret_len =
+				conf->radius_das_shared_secret_len;
+			das_conf.client_addr = &conf->radius_das_client_addr;
+			das_conf.time_window = conf->radius_das_time_window;
+			das_conf.require_event_timestamp =
+				conf->radius_das_require_event_timestamp;
+			das_conf.require_message_authenticator =
+				conf->radius_das_require_message_authenticator;
+			das_conf.ctx = hapd;
+			das_conf.disconnect = hostapd_das_disconnect;
+			das_conf.coa = hostapd_das_coa;
+			hapd->radius_das = radius_das_init(&das_conf);
+			if (!hapd->radius_das) {
+				wpa_printf(MSG_ERROR,
+					   "RADIUS DAS initialization failed.");
+				return -1;
+			}
+		}
+	} else {
+		wpa_printf(MSG_DEBUG,
+			   "MLD: Using RADIUS client of the first BSS");
+		hapd->radius = hapd->mld_first_bss->radius;
+		hapd->radius_das = hapd->mld_first_bss->radius_das;
 	}
 #endif /* CONFIG_NO_RADIUS */
 
@@ -1607,6 +1642,125 @@
 }
 
 
+/* When NO_IR flag is set and AP is stopped, clean up BSS parameters without
+ * deinitializing the driver and the control interfaces. A subsequent
+ * REG_CHANGE event can bring the AP back up.
+ */
+static void hostapd_no_ir_cleanup(struct hostapd_data *bss)
+{
+	hostapd_bss_deinit_no_free(bss);
+	hostapd_free_hapd_data(bss);
+	hostapd_cleanup_iface_partial(bss->iface);
+}
+
+
+static int hostapd_no_ir_channel_list_updated(struct hostapd_iface *iface,
+					      void *ctx)
+{
+	bool all_no_ir, is_6ghz;
+	int i, j;
+	struct hostapd_hw_modes *mode = NULL;
+
+	if (hostapd_get_hw_features(iface))
+		return 0;
+
+	all_no_ir = true;
+	is_6ghz = false;
+
+	for (i = 0; i < iface->num_hw_features; i++) {
+		mode = &iface->hw_features[i];
+
+		if (mode->mode == iface->conf->hw_mode) {
+			if (iface->freq > 0 &&
+			    !hw_mode_get_channel(mode, iface->freq, NULL)) {
+				mode = NULL;
+				continue;
+			}
+
+			for (j = 0; j < mode->num_channels; j++) {
+				if (!(mode->channels[j].flag &
+				      HOSTAPD_CHAN_NO_IR))
+					all_no_ir = false;
+
+				if (is_6ghz_freq(mode->channels[j].freq))
+					is_6ghz = true;
+			}
+			break;
+		}
+	}
+
+	if (!mode || !is_6ghz)
+		return 0;
+	iface->current_mode = mode;
+
+	if (iface->state == HAPD_IFACE_ENABLED) {
+		if (!all_no_ir) {
+			struct hostapd_channel_data *chan;
+
+			chan = hw_get_channel_freq(iface->current_mode->mode,
+						   iface->freq, NULL,
+						   iface->hw_features,
+						   iface->num_hw_features);
+
+			if (!chan) {
+				wpa_printf(MSG_ERROR,
+					   "NO_IR: Could not derive chan from freq");
+				return 0;
+			}
+
+			if (!(chan->flag & HOSTAPD_CHAN_NO_IR))
+				return 0;
+			wpa_printf(MSG_DEBUG,
+				   "NO_IR: The current channel has NO_IR flag now, stop AP.");
+		} else {
+			wpa_printf(MSG_DEBUG,
+				   "NO_IR: All chan in new chanlist are NO_IR, stop AP.");
+		}
+
+		hostapd_set_state(iface, HAPD_IFACE_NO_IR);
+		iface->is_no_ir = true;
+		hostapd_drv_stop_ap(iface->bss[0]);
+		hostapd_no_ir_cleanup(iface->bss[0]);
+		wpa_msg(iface->bss[0]->msg_ctx, MSG_INFO, AP_EVENT_NO_IR);
+	} else if (iface->state == HAPD_IFACE_NO_IR) {
+		if (all_no_ir) {
+			wpa_printf(MSG_DEBUG,
+				   "NO_IR: AP in NO_IR and all chan in the new chanlist are NO_IR. Ignore");
+			return 0;
+		}
+
+		if (!iface->conf->acs) {
+			struct hostapd_channel_data *chan;
+
+			chan = hw_get_channel_freq(iface->current_mode->mode,
+						   iface->freq, NULL,
+						   iface->hw_features,
+						   iface->num_hw_features);
+			if (!chan) {
+				wpa_printf(MSG_ERROR,
+					   "NO_IR: Could not derive chan from freq");
+				return 0;
+			}
+
+			/* If the last operating channel is NO_IR, trigger ACS.
+			 */
+			if (chan->flag & HOSTAPD_CHAN_NO_IR) {
+				iface->freq = 0;
+				iface->conf->channel = 0;
+				if (acs_init(iface) != HOSTAPD_CHAN_ACS)
+					wpa_printf(MSG_ERROR,
+						   "NO_IR: Could not start ACS");
+				return 0;
+			}
+		}
+
+		setup_interface2(iface);
+	}
+
+	return 0;
+}
+
+
 static void channel_list_update_timeout(void *eloop_ctx, void *timeout_ctx)
 {
 	struct hostapd_iface *iface = eloop_ctx;
@@ -1627,6 +1781,13 @@
 
 void hostapd_channel_list_updated(struct hostapd_iface *iface, int initiator)
 {
+	if (initiator == REGDOM_SET_BY_DRIVER) {
+		hostapd_for_each_interface(iface->interfaces,
+					   hostapd_no_ir_channel_list_updated,
+					   NULL);
+		return;
+	}
+
 	if (!iface->wait_channel_update || initiator != REGDOM_SET_BY_USER)
 		return;
 
@@ -1776,6 +1937,7 @@
 static int setup_interface2(struct hostapd_iface *iface)
 {
 	iface->wait_channel_update = 0;
+	iface->is_no_ir = false;
 
 	if (hostapd_get_hw_features(iface)) {
 		/* Not all drivers support this yet, so continue without hw
@@ -1831,6 +1993,14 @@
 	return hostapd_setup_interface_complete(iface, 0);
 
 fail:
+	if (iface->is_no_ir) {
+		/* If AP is in NO_IR state, it can be reenabled by the driver
+		 * regulatory update and EVENT_CHANNEL_LIST_CHANGED. */
+		hostapd_set_state(iface, HAPD_IFACE_NO_IR);
+		wpa_msg(iface->bss[0]->msg_ctx, MSG_INFO, AP_EVENT_NO_IR);
+		return 0;
+	}
+
 	hostapd_set_state(iface, HAPD_IFACE_DISABLED);
 	wpa_msg(iface->bss[0]->msg_ctx, MSG_INFO, AP_EVENT_DISABLED);
 	if (iface->interfaces && iface->interfaces->terminate_on_error)
@@ -2317,10 +2487,20 @@
 	for (j = 0; j < iface->num_bss; j++)
 		hostapd_neighbor_set_own_report(iface->bss[j]);
 
+	if (iface->interfaces && iface->interfaces->count > 1)
+		ieee802_11_set_beacons(iface);
+
 	return 0;
 
 fail:
 	wpa_printf(MSG_ERROR, "Interface initialization failed");
+
+	if (iface->is_no_ir) {
+		hostapd_set_state(iface, HAPD_IFACE_NO_IR);
+		wpa_msg(hapd->msg_ctx, MSG_INFO, AP_EVENT_NO_IR);
+		return 0;
+	}
+
 	hostapd_set_state(iface, HAPD_IFACE_DISABLED);
 	wpa_msg(hapd->msg_ctx, MSG_INFO, AP_EVENT_DISABLED);
 #ifdef CONFIG_FST
@@ -2366,8 +2546,15 @@
 
 	if (err) {
 		wpa_printf(MSG_ERROR, "Interface initialization failed");
-		hostapd_set_state(iface, HAPD_IFACE_DISABLED);
 		iface->need_to_start_in_sync = 0;
+
+		if (iface->is_no_ir) {
+			hostapd_set_state(iface, HAPD_IFACE_NO_IR);
+			wpa_msg(hapd->msg_ctx, MSG_INFO, AP_EVENT_NO_IR);
+			return 0;
+		}
+
+		hostapd_set_state(iface, HAPD_IFACE_DISABLED);
 		wpa_msg(hapd->msg_ctx, MSG_INFO, AP_EVENT_DISABLED);
 		if (interfaces && interfaces->terminate_on_error)
 			eloop_terminate();
@@ -2536,6 +2723,7 @@
 
 	eloop_cancel_timeout(channel_list_update_timeout, iface, NULL);
 	iface->wait_channel_update = 0;
+	iface->is_no_ir = false;
 
 #ifdef CONFIG_FST
 	if (iface->fst) {
@@ -2800,8 +2988,9 @@
 	wpa_printf(MSG_DEBUG, "%s: driver=%p drv_priv=%p -> hapd_deinit",
 		   __func__, driver, drv_priv);
 	if (driver && driver->hapd_deinit && drv_priv) {
-		driver->hapd_deinit(drv_priv);
-		iface->bss[0]->drv_priv = NULL;
+		if (!iface->bss[0]->mld_first_bss)
+			driver->hapd_deinit(drv_priv);
+		hostapd_clear_drv_priv(iface->bss[0]);
 	}
 	hostapd_interface_free(iface);
 }
@@ -2816,13 +3005,14 @@
 	wpa_printf(MSG_DEBUG, "%s: driver=%p drv_priv=%p -> hapd_deinit",
 		   __func__, driver, drv_priv);
 	if (driver && driver->hapd_deinit && drv_priv) {
-		driver->hapd_deinit(drv_priv);
+		if (!hapd_iface->bss[0]->mld_first_bss)
+			driver->hapd_deinit(drv_priv);
 		for (j = 0; j < hapd_iface->num_bss; j++) {
 			wpa_printf(MSG_DEBUG, "%s:bss[%d]->drv_priv=%p",
 				   __func__, (int) j,
 				   hapd_iface->bss[j]->drv_priv);
 			if (hapd_iface->bss[j]->drv_priv == drv_priv) {
-				hapd_iface->bss[j]->drv_priv = NULL;
+				hostapd_clear_drv_priv(hapd_iface->bss[j]);
 				hapd_iface->extended_capa = NULL;
 				hapd_iface->extended_capa_mask = NULL;
 				hapd_iface->extended_capa_len = 0;
@@ -3163,8 +3353,14 @@
 		conf_file = ptr + 7;
 
 	for (i = 0; i < interfaces->count; i++) {
+		bool mld_ap = false;
+
+#ifdef CONFIG_IEEE80211BE
+		mld_ap = interfaces->iface[i]->conf->bss[0]->mld_ap;
+#endif /* CONFIG_IEEE80211BE */
+
 		if (!os_strcmp(interfaces->iface[i]->conf->bss[0]->iface,
-			       buf)) {
+			       buf) && !mld_ap) {
 			wpa_printf(MSG_INFO, "Cannot add interface - it "
 				   "already exists");
 			return -1;
@@ -3339,6 +3535,12 @@
 		return;
 	}
 
+#ifdef CONFIG_IEEE80211BE
+	if (hapd->conf->mld_ap && sta->mld_info.mld_sta &&
+	    sta->mld_assoc_link_id != hapd->mld_link_id)
+		return;
+#endif /* CONFIG_IEEE80211BE */
+
 	ap_sta_clear_disconnect_timeouts(hapd, sta);
 	sta->post_csa_sa_query = 0;
 
@@ -3371,8 +3573,12 @@
 		    sta->auth_alg != WLAN_AUTH_FILS_PK &&
 		    !(sta->flags & (WLAN_STA_WPS | WLAN_STA_MAYBE_WPS)))
 			wpa_auth_sm_event(sta->wpa_sm, WPA_REAUTH);
-	} else
+	} else if (!(hapd->iface->drv_flags2 &
+		     WPA_DRIVER_FLAGS2_4WAY_HANDSHAKE_AP_PSK)) {
+		/* The 4-way handshake offloaded case will have this handled
+		 * based on the port authorized event. */
 		wpa_auth_sta_associated(hapd->wpa_auth, sta->wpa_sm);
+	}
 
 	if (hapd->iface->drv_flags & WPA_DRIVER_FLAGS_WIRED) {
 		if (eloop_cancel_timeout(ap_handle_timer, hapd, sta) > 0) {
@@ -3421,6 +3627,8 @@
 		return "DFS";
 	case HAPD_IFACE_ENABLED:
 		return "ENABLED";
+	case HAPD_IFACE_NO_IR:
+		return "NO_IR";
 	}
 
 	return "UNKNOWN";
@@ -3572,6 +3780,7 @@
 	if (!channel)
 		return -1;
 
+	hostapd_determine_mode(hapd->iface);
 	mode = hapd->iface->current_mode;
 
 	/* if a pointer to old_params is provided we save previous state */
@@ -4063,3 +4272,26 @@
 	}
 }
 #endif /* CONFIG_OCV */
+
+
+#ifdef CONFIG_IEEE80211BE
+struct hostapd_data * hostapd_mld_get_link_bss(struct hostapd_data *hapd,
+					       u8 link_id)
+{
+	unsigned int i;
+
+	for (i = 0; i < hapd->iface->interfaces->count; i++) {
+		struct hostapd_iface *h = hapd->iface->interfaces->iface[i];
+		struct hostapd_data *h_hapd = h->bss[0];
+		struct hostapd_bss_config *hconf = h_hapd->conf;
+
+		if (!hconf->mld_ap || hconf->mld_id != hapd->conf->mld_id)
+			continue;
+
+		if (h_hapd->mld_link_id == link_id)
+			return h_hapd;
+	}
+
+	return NULL;
+}
+#endif /* CONFIG_IEEE80211BE */
diff --git a/src/ap/hostapd.h b/src/ap/hostapd.h
index b9a67b9..7f703be 100644
--- a/src/ap/hostapd.h
+++ b/src/ap/hostapd.h
@@ -99,6 +99,7 @@
 	HOSTAPD_CHAN_VALID = 0, /* channel is ready */
 	HOSTAPD_CHAN_INVALID = 1, /* no usable channel found */
 	HOSTAPD_CHAN_ACS = 2, /* ACS work being performed */
+	HOSTAPD_CHAN_INVALID_NO_IR = 3, /* channel invalid due to AFC NO IR */
 };
 
 struct hostapd_probereq_cb {
@@ -174,6 +175,12 @@
 	unsigned int reenable_beacon:1;
 
 	u8 own_addr[ETH_ALEN];
+	u8 mld_addr[ETH_ALEN];
+	u8 mld_link_id;
+	/* Used for mld_link_id assignment - valid on the first MLD BSS only */
+	u8 mld_next_link_id;
+
+	struct hostapd_data *mld_first_bss;
 
 	int num_sta; /* number of entries in sta_list */
 	struct sta_info *sta_list; /* STA info list head */
@@ -482,6 +489,7 @@
 	HAPD_IFACE_ACS,
 	HAPD_IFACE_HT_SCAN,
 	HAPD_IFACE_DFS,
+	HAPD_IFACE_NO_IR,
 	HAPD_IFACE_ENABLED
 };
 
@@ -495,7 +503,8 @@
 	struct hostapd_config *conf;
 	char phy[16]; /* Name of the PHY (radio) */
 
-        enum hostapd_iface_state state;
+	enum hostapd_iface_state state;
+
 #ifdef CONFIG_MESH
 	struct mesh_conf *mconf;
 #endif /* CONFIG_MESH */
@@ -542,6 +551,8 @@
 	const u8 *extended_capa, *extended_capa_mask;
 	unsigned int extended_capa_len;
 
+	u16 mld_eml_capa, mld_mld_capa;
+
 	unsigned int drv_max_acl_mac_addrs;
 
 	struct hostapd_hw_modes *hw_features;
@@ -652,6 +663,9 @@
 
 	int (*enable_iface_cb)(struct hostapd_iface *iface);
 	int (*disable_iface_cb)(struct hostapd_iface *iface);
+
+	/* Configured freq of interface is NO_IR */
+	bool is_no_ir;
 };
 
 /* hostapd.c */
@@ -712,13 +726,15 @@
 					   const u8 *ie, size_t ie_len,
 					   int ssi_signal),
 				 void *ctx);
-void hostapd_prune_associations(struct hostapd_data *hapd, const u8 *addr);
+void hostapd_prune_associations(struct hostapd_data *hapd, const u8 *addr,
+				int mld_assoc_link_id);
 
 /* drv_callbacks.c (TODO: move to somewhere else?) */
 void hostapd_notify_assoc_fils_finish(struct hostapd_data *hapd,
 				      struct sta_info *sta);
 int hostapd_notif_assoc(struct hostapd_data *hapd, const u8 *addr,
-			const u8 *ie, size_t ielen, int reassoc);
+			const u8 *req_ie, size_t req_ielen, const u8 *resp_ie,
+			size_t resp_ielen, const u8 *link_addr, int reassoc);
 void hostapd_notif_disassoc(struct hostapd_data *hapd, const u8 *addr);
 void hostapd_event_sta_low_ack(struct hostapd_data *hapd, const u8 *addr);
 void hostapd_event_connect_failed_reason(struct hostapd_data *hapd,
@@ -753,5 +769,7 @@
 int hostapd_set_acl(struct hostapd_data *hapd);
 struct hostapd_data * hostapd_mbssid_get_tx_bss(struct hostapd_data *hapd);
 int hostapd_mbssid_get_bss_index(struct hostapd_data *hapd);
+struct hostapd_data * hostapd_mld_get_link_bss(struct hostapd_data *hapd,
+					       u8 link_id);
 
 #endif /* HOSTAPD_H */
diff --git a/src/ap/hw_features.c b/src/ap/hw_features.c
index f836be4..9edbb5a 100644
--- a/src/ap/hw_features.c
+++ b/src/ap/hw_features.c
@@ -79,6 +79,9 @@
 	u16 num_modes, flags;
 	struct hostapd_hw_modes *modes;
 	u8 dfs_domain;
+	enum hostapd_hw_mode mode = HOSTAPD_MODE_IEEE80211ANY;
+	bool is_6ghz = false;
+	bool orig_mode_valid = false;
 
 	if (hostapd_drv_none(hapd))
 		return -1;
@@ -95,6 +98,20 @@
 	iface->hw_flags = flags;
 	iface->dfs_domain = dfs_domain;
 
+	if (iface->current_mode) {
+		/*
+		 * Received driver event CHANNEL_LIST_CHANGED when the current
+		 * hw mode is valid. Clear iface->current_mode temporarily as
+		 * the mode instance will be replaced with a new instance and
+		 * the current pointer would be pointing to freed memory.
+		 */
+		orig_mode_valid = true;
+		mode = iface->current_mode->mode;
+		is_6ghz = mode == HOSTAPD_MODE_IEEE80211A &&
+			iface->current_mode->num_channels > 0 &&
+			is_6ghz_freq(iface->current_mode->channels[0].freq);
+		iface->current_mode = NULL;
+	}
 	hostapd_free_hw_features(iface->hw_features, iface->num_hw_features);
 	iface->hw_features = modes;
 	iface->num_hw_features = num_modes;
@@ -104,6 +121,12 @@
 		int dfs_enabled = hapd->iconf->ieee80211h &&
 			(iface->drv_flags & WPA_DRIVER_FLAGS_RADAR);
 
+		/* Restore orignal mode if possible */
+		if (orig_mode_valid && feature->mode == mode &&
+		    feature->num_channels > 0 &&
+		    is_6ghz == is_6ghz_freq(feature->channels[0].freq))
+			iface->current_mode = feature;
+
 		/* set flag for channels we can use in current regulatory
 		 * domain */
 		for (j = 0; j < feature->num_channels; j++) {
@@ -141,6 +164,12 @@
 		}
 	}
 
+	if (orig_mode_valid && !iface->current_mode) {
+		wpa_printf(MSG_ERROR,
+			   "%s: Could not update iface->current_mode",
+			   __func__);
+	}
+
 	return 0;
 }
 
@@ -794,6 +823,11 @@
 }
 
 
+/* Returns:
+ * 1 = usable
+ * 0 = not usable
+ * -1 = not currently usable due to 6 GHz NO-IR
+ */
 static int hostapd_is_usable_chan(struct hostapd_iface *iface,
 				  int frequency, int primary)
 {
@@ -817,6 +851,10 @@
 		   chan->flag,
 		   chan->flag & HOSTAPD_CHAN_NO_IR ? " NO-IR" : "",
 		   chan->flag & HOSTAPD_CHAN_RADAR ? " RADAR" : "");
+
+	if (is_6ghz_freq(chan->freq) && (chan->flag & HOSTAPD_CHAN_NO_IR))
+		return -1;
+
 	return 0;
 }
 
@@ -826,6 +864,7 @@
 	int i, contiguous = 0;
 	int num_of_enabled = 0;
 	int max_contiguous = 0;
+	int err;
 	struct ieee80211_edmg_config edmg;
 	struct hostapd_channel_data *pri_chan;
 
@@ -865,8 +904,9 @@
 		if (num_of_enabled > 4)
 			return 0;
 
-		if (!hostapd_is_usable_chan(iface, freq, 1))
-			return 0;
+		err = hostapd_is_usable_chan(iface, freq, 1);
+		if (err <= 0)
+			return err;
 
 		if (contiguous > max_contiguous)
 			max_contiguous = contiguous;
@@ -897,7 +937,8 @@
 {
 #ifdef CONFIG_IEEE80211BE
 	struct hostapd_config *conf = iface->conf;
-	u8 bw, start_chan;
+	u16 bw;
+	u8 start_chan;
 
 	if (!conf->punct_bitmap)
 		return true;
@@ -914,21 +955,30 @@
 		return false;
 	}
 
-	switch (conf->eht_oper_chwidth) {
-	case 0:
-		wpa_printf(MSG_ERROR,
-			   "RU puncturing is supported only in 80 MHz and 160 MHz");
-		return false;
-	case 1:
-		bw = 80;
-		start_chan = conf->eht_oper_centr_freq_seg0_idx - 6;
-		break;
-	case 2:
-		bw = 160;
-		start_chan = conf->eht_oper_centr_freq_seg0_idx - 14;
-		break;
-	default:
-		return false;
+	/*
+	 * In the 6 GHz band, eht_oper_chwidth is ignored. Use operating class
+	 * to determine channel width.
+	 */
+	if (conf->op_class == 137) {
+		bw = 320;
+		start_chan = conf->eht_oper_centr_freq_seg0_idx - 30;
+	} else {
+		switch (conf->eht_oper_chwidth) {
+		case 0:
+			wpa_printf(MSG_ERROR,
+				   "RU puncturing is supported only in 80 MHz and 160 MHz");
+			return false;
+		case 1:
+			bw = 80;
+			start_chan = conf->eht_oper_centr_freq_seg0_idx - 6;
+			break;
+		case 2:
+			bw = 160;
+			start_chan = conf->eht_oper_centr_freq_seg0_idx - 14;
+			break;
+		default:
+			return false;
+		}
 	}
 
 	if (!is_punct_bitmap_valid(bw, (conf->channel - start_chan) / 4,
@@ -942,10 +992,16 @@
 }
 
 
+/* Returns:
+ * 1 = usable
+ * 0 = not usable
+ * -1 = not currently usable due to 6 GHz NO-IR
+ */
 static int hostapd_is_usable_chans(struct hostapd_iface *iface)
 {
 	int secondary_freq;
 	struct hostapd_channel_data *pri_chan;
+	int err;
 
 	if (!iface->current_mode)
 		return 0;
@@ -957,12 +1013,15 @@
 		wpa_printf(MSG_ERROR, "Primary frequency not present");
 		return 0;
 	}
-	if (!hostapd_is_usable_chan(iface, pri_chan->freq, 1)) {
+
+	err = hostapd_is_usable_chan(iface, pri_chan->freq, 1);
+	if (err <= 0) {
 		wpa_printf(MSG_ERROR, "Primary frequency not allowed");
-		return 0;
+		return err;
 	}
-	if (!hostapd_is_usable_edmg(iface))
-		return 0;
+	err = hostapd_is_usable_edmg(iface);
+	if (err <= 0)
+		return err;
 
 	if (!hostapd_is_usable_punct_bitmap(iface))
 		return 0;
@@ -970,8 +1029,9 @@
 	if (!iface->conf->secondary_channel)
 		return 1;
 
-	if (hostapd_is_usable_chan(iface, iface->freq +
-				   iface->conf->secondary_channel * 20, 0)) {
+	err = hostapd_is_usable_chan(iface, iface->freq +
+				     iface->conf->secondary_channel * 20, 0);
+	if (err > 0) {
 		if (iface->conf->secondary_channel == 1 &&
 		    (pri_chan->allowed_bw & HOSTAPD_CHAN_WIDTH_40P))
 			return 1;
@@ -980,24 +1040,24 @@
 			return 1;
 	}
 	if (!iface->conf->ht40_plus_minus_allowed)
-		return 0;
+		return err;
 
 	/* Both HT40+ and HT40- are set, pick a valid secondary channel */
 	secondary_freq = iface->freq + 20;
-	if (hostapd_is_usable_chan(iface, secondary_freq, 0) &&
-	    (pri_chan->allowed_bw & HOSTAPD_CHAN_WIDTH_40P)) {
+	err = hostapd_is_usable_chan(iface, secondary_freq, 0);
+	if (err > 0 && (pri_chan->allowed_bw & HOSTAPD_CHAN_WIDTH_40P)) {
 		iface->conf->secondary_channel = 1;
 		return 1;
 	}
 
 	secondary_freq = iface->freq - 20;
-	if (hostapd_is_usable_chan(iface, secondary_freq, 0) &&
-	    (pri_chan->allowed_bw & HOSTAPD_CHAN_WIDTH_40M)) {
+	err = hostapd_is_usable_chan(iface, secondary_freq, 0);
+	if (err > 0 && (pri_chan->allowed_bw & HOSTAPD_CHAN_WIDTH_40M)) {
 		iface->conf->secondary_channel = -1;
 		return 1;
 	}
 
-	return 0;
+	return err;
 }
 
 
@@ -1019,14 +1079,14 @@
 }
 
 
-static void hostapd_determine_mode(struct hostapd_iface *iface)
+int hostapd_determine_mode(struct hostapd_iface *iface)
 {
 	int i;
 	enum hostapd_hw_mode target_mode;
 
 	if (iface->current_mode ||
 	    iface->conf->hw_mode != HOSTAPD_MODE_IEEE80211ANY)
-		return;
+		return 0;
 
 	if (iface->freq < 4000)
 		target_mode = HOSTAPD_MODE_IEEE80211G;
@@ -1049,8 +1109,11 @@
 		}
 	}
 
-	if (!iface->current_mode)
-		wpa_printf(MSG_ERROR, "ACS: Cannot decide mode");
+	if (!iface->current_mode) {
+		wpa_printf(MSG_ERROR, "ACS/CSA: Cannot decide mode");
+		return -1;
+	}
+	return 0;
 }
 
 
@@ -1058,11 +1121,17 @@
 hostapd_check_chans(struct hostapd_iface *iface)
 {
 	if (iface->freq) {
+		int err;
+
 		hostapd_determine_mode(iface);
-		if (hostapd_is_usable_chans(iface))
-			return HOSTAPD_CHAN_VALID;
-		else
-			return HOSTAPD_CHAN_INVALID;
+
+		err = hostapd_is_usable_chans(iface);
+		if (err <= 0) {
+			if (!err)
+				return HOSTAPD_CHAN_INVALID;
+			return HOSTAPD_CHAN_INVALID_NO_IR;
+		}
+		return HOSTAPD_CHAN_VALID;
 	}
 
 	/*
@@ -1073,6 +1142,8 @@
 	switch (acs_init(iface)) {
 	case HOSTAPD_CHAN_ACS:
 		return HOSTAPD_CHAN_ACS;
+	case HOSTAPD_CHAN_INVALID_NO_IR:
+		return HOSTAPD_CHAN_INVALID_NO_IR;
 	case HOSTAPD_CHAN_VALID:
 	case HOSTAPD_CHAN_INVALID:
 	default:
@@ -1112,6 +1183,7 @@
 
 	switch (hostapd_check_chans(iface)) {
 	case HOSTAPD_CHAN_VALID:
+		iface->is_no_ir = false;
 		wpa_msg(iface->bss[0]->msg_ctx, MSG_INFO,
 			ACS_EVENT_COMPLETED "freq=%d channel=%d",
 			iface->freq, iface->conf->channel);
@@ -1121,6 +1193,9 @@
 		wpa_msg(iface->bss[0]->msg_ctx, MSG_INFO, ACS_EVENT_FAILED);
 		hostapd_notify_bad_chans(iface);
 		goto out;
+	case HOSTAPD_CHAN_INVALID_NO_IR:
+		iface->is_no_ir = true;
+		/* fall through */
 	case HOSTAPD_CHAN_INVALID:
 	default:
 		wpa_printf(MSG_ERROR, "ACS picked unusable channels");
@@ -1144,6 +1219,25 @@
 
 
 /**
+ * hostapd_csa_update_hwmode - Update hardware mode
+ * @iface: Pointer to interface data.
+ * Returns: 0 on success, < 0 on failure
+ *
+ * Update hardware mode when the operating channel changed because of CSA.
+ */
+int hostapd_csa_update_hwmode(struct hostapd_iface *iface)
+{
+	if (!iface || !iface->conf)
+		return -1;
+
+	iface->current_mode = NULL;
+	iface->conf->hw_mode = HOSTAPD_MODE_IEEE80211ANY;
+
+	return hostapd_determine_mode(iface);
+}
+
+
+/**
  * hostapd_select_hw_mode - Select the hardware mode
  * @iface: Pointer to interface data.
  * Returns: 0 on success, < 0 on failure
@@ -1206,9 +1300,13 @@
 
 	switch (hostapd_check_chans(iface)) {
 	case HOSTAPD_CHAN_VALID:
+		iface->is_no_ir = false;
 		return 0;
 	case HOSTAPD_CHAN_ACS: /* ACS will run and later complete */
 		return 1;
+	case HOSTAPD_CHAN_INVALID_NO_IR:
+		iface->is_no_ir = true;
+		/* fall through */
 	case HOSTAPD_CHAN_INVALID:
 	default:
 		hostapd_notify_bad_chans(iface);
diff --git a/src/ap/hw_features.h b/src/ap/hw_features.h
index ad0ddf7..c682c6d 100644
--- a/src/ap/hw_features.h
+++ b/src/ap/hw_features.h
@@ -15,6 +15,7 @@
 void hostapd_free_hw_features(struct hostapd_hw_modes *hw_features,
 			      size_t num_hw_features);
 int hostapd_get_hw_features(struct hostapd_iface *iface);
+int hostapd_csa_update_hwmode(struct hostapd_iface *iface);
 int hostapd_acs_completed(struct hostapd_iface *iface, int err);
 int hostapd_select_hw_mode(struct hostapd_iface *iface);
 const char * hostapd_hw_mode_txt(int mode);
@@ -28,6 +29,7 @@
 void hostapd_stop_setup_timers(struct hostapd_iface *iface);
 int hostapd_hw_skip_mode(struct hostapd_iface *iface,
 			 struct hostapd_hw_modes *mode);
+int hostapd_determine_mode(struct hostapd_iface *iface);
 #else /* NEED_AP_MLME */
 static inline void
 hostapd_free_hw_features(struct hostapd_hw_modes *hw_features,
@@ -40,6 +42,11 @@
 	return -1;
 }
 
+static inline int hostapd_csa_update_hwmode(struct hostapd_iface *iface)
+{
+	return 0;
+}
+
 static inline int hostapd_acs_completed(struct hostapd_iface *iface, int err)
 {
 	return -1;
@@ -91,6 +98,11 @@
 	return 0;
 }
 
+static inline int hostapd_determine_mode(struct hostapd_iface *iface)
+{
+	return 0;
+}
+
 #endif /* NEED_AP_MLME */
 
 #endif /* HW_FEATURES_H */
diff --git a/src/ap/ieee802_11.c b/src/ap/ieee802_11.c
index 93a6b4f..1f39107 100644
--- a/src/ap/ieee802_11.c
+++ b/src/ap/ieee802_11.c
@@ -83,6 +83,8 @@
 static void handle_auth(struct hostapd_data *hapd,
 			const struct ieee80211_mgmt *mgmt, size_t len,
 			int rssi, int from_queue);
+static int add_associated_sta(struct hostapd_data *hapd,
+			      struct sta_info *sta, int reassoc);
 
 
 u8 * hostapd_eid_multi_ap(struct hostapd_data *hapd, u8 *eid)
@@ -396,17 +398,38 @@
 	u8 *buf;
 	size_t rlen;
 	int reply_res = WLAN_STATUS_UNSPECIFIED_FAILURE;
+	const u8 *sa = hapd->own_addr;
+	struct wpabuf *ml_resp = NULL;
+
+#ifdef CONFIG_IEEE80211BE
+	/*
+	 * Once a non-AP MLD is added to the driver, the addressing should use
+	 * the MLD MAC address. Thus, use the MLD address instead of translating
+	 * the addresses.
+	 */
+	if (hapd->conf->mld_ap && sta && sta->mld_info.mld_sta) {
+		sa = hapd->mld_addr;
+
+		ml_resp = hostapd_ml_auth_resp(hapd);
+		if (!ml_resp)
+			return -1;
+	}
+#endif /* CONFIG_IEEE80211BE */
 
 	rlen = IEEE80211_HDRLEN + sizeof(reply->u.auth) + ies_len;
+	if (ml_resp)
+		rlen += wpabuf_len(ml_resp);
 	buf = os_zalloc(rlen);
-	if (buf == NULL)
+	if (!buf) {
+		wpabuf_free(ml_resp);
 		return -1;
+	}
 
 	reply = (struct ieee80211_mgmt *) buf;
 	reply->frame_control = IEEE80211_FC(WLAN_FC_TYPE_MGMT,
 					    WLAN_FC_STYPE_AUTH);
 	os_memcpy(reply->da, dst, ETH_ALEN);
-	os_memcpy(reply->sa, hapd->own_addr, ETH_ALEN);
+	os_memcpy(reply->sa, sa, ETH_ALEN);
 	os_memcpy(reply->bssid, bssid, ETH_ALEN);
 
 	reply->u.auth.auth_alg = host_to_le16(auth_alg);
@@ -416,6 +439,14 @@
 	if (ies && ies_len)
 		os_memcpy(reply->u.auth.variable, ies, ies_len);
 
+#ifdef CONFIG_IEEE80211BE
+	if (ml_resp)
+		os_memcpy(reply->u.auth.variable + ies_len,
+			  wpabuf_head(ml_resp), wpabuf_len(ml_resp));
+
+	wpabuf_free(ml_resp);
+#endif /* CONFIG_IEEE80211BE */
+
 	wpa_printf(MSG_DEBUG, "authentication reply: STA=" MACSTR
 		   " auth_alg=%d auth_transaction=%d resp=%d (IE len=%lu) (dbg=%s)",
 		   MAC2STR(dst), auth_alg, auth_transaction,
@@ -509,12 +540,12 @@
 }
 
 
-static const char * sae_get_password(struct hostapd_data *hapd,
-				     struct sta_info *sta,
-				     const char *rx_id,
-				     struct sae_password_entry **pw_entry,
-				     struct sae_pt **s_pt,
-				     const struct sae_pk **s_pk)
+const char * sae_get_password(struct hostapd_data *hapd,
+			      struct sta_info *sta,
+			      const char *rx_id,
+			      struct sae_password_entry **pw_entry,
+			      struct sae_pt **s_pt,
+			      const struct sae_pk **s_pk)
 {
 	const char *password = NULL;
 	struct sae_password_entry *pw;
@@ -524,7 +555,8 @@
 
 	for (pw = hapd->conf->sae_passwords; pw; pw = pw->next) {
 		if (!is_broadcast_ether_addr(pw->peer_addr) &&
-		    os_memcmp(pw->peer_addr, sta->addr, ETH_ALEN) != 0)
+		    (!sta ||
+		     os_memcmp(pw->peer_addr, sta->addr, ETH_ALEN) != 0))
 			continue;
 		if ((rx_id && !pw->identifier) || (!rx_id && pw->identifier))
 			continue;
@@ -542,7 +574,7 @@
 		pt = hapd->conf->ssid.pt;
 	}
 
-	if (!password) {
+	if (!password && sta) {
 		for (psk = sta->psk; psk; psk = psk->next) {
 			if (psk->is_passphrase) {
 				password = psk->passphrase;
@@ -573,12 +605,18 @@
 	int use_pt = 0;
 	struct sae_pt *pt = NULL;
 	const struct sae_pk *pk = NULL;
+	const u8 *own_addr = hapd->own_addr;
+
+#ifdef CONFIG_IEEE80211BE
+	if (hapd->conf->mld_ap && sta->mld_info.mld_sta)
+		own_addr = hapd->mld_addr;
+#endif /* CONFIG_IEEE80211BE */
 
 	if (sta->sae->tmp) {
 		rx_id = sta->sae->tmp->pw_id;
 		use_pt = sta->sae->h2e;
 #ifdef CONFIG_SAE_PK
-		os_memcpy(sta->sae->tmp->own_addr, hapd->own_addr, ETH_ALEN);
+		os_memcpy(sta->sae->tmp->own_addr, own_addr, ETH_ALEN);
 		os_memcpy(sta->sae->tmp->peer_addr, sta->addr, ETH_ALEN);
 #endif /* CONFIG_SAE_PK */
 	}
@@ -598,12 +636,12 @@
 	}
 
 	if (update && use_pt &&
-	    sae_prepare_commit_pt(sta->sae, pt, hapd->own_addr, sta->addr,
+	    sae_prepare_commit_pt(sta->sae, pt, own_addr, sta->addr,
 				  NULL, pk) < 0)
 		return NULL;
 
 	if (update && !use_pt &&
-	    sae_prepare_commit(hapd->own_addr, sta->addr,
+	    sae_prepare_commit(own_addr, sta->addr,
 			       (u8 *) password, os_strlen(password),
 			       sta->sae) < 0) {
 		wpa_printf(MSG_DEBUG, "SAE: Could not pick PWE");
@@ -837,7 +875,15 @@
 
 	os_memset(&params, 0, sizeof(params));
 	params.status = status;
-	params.bssid = sta->addr;
+
+#ifdef CONFIG_IEEE80211BE
+	if (sta->mld_info.mld_sta)
+		params.bssid =
+			sta->mld_info.links[sta->mld_assoc_link_id].peer_addr;
+#endif /* CONFIG_IEEE80211BE */
+	if (!params.bssid)
+		params.bssid = sta->addr;
+
 	if (status == WLAN_STATUS_SUCCESS && sta->sae &&
 	    !hapd->conf->disable_pmksa_caching)
 		params.pmkid = sta->sae->pmkid;
@@ -2748,6 +2794,8 @@
 	size_t resp_ies_len = 0;
 	u16 seq_ctrl;
 	struct radius_sta rad_info;
+	const u8 *dst, *sa, *bssid;
+	bool mld_sta = false;
 
 	if (len < IEEE80211_HDRLEN + sizeof(mgmt->u.auth)) {
 		wpa_printf(MSG_INFO, "handle_auth - too short payload (len=%lu)",
@@ -2765,6 +2813,20 @@
 	}
 #endif /* CONFIG_TESTING_OPTIONS */
 
+	sa = mgmt->sa;
+#ifdef CONFIG_IEEE80211BE
+	/*
+	 * Handle MLO authentication before the station is added to hostapd and
+	 * the driver so that the station MLD MAC address would be used in both
+	 * hostapd and the driver.
+	 */
+	sa = hostapd_process_ml_auth(hapd, mgmt, len);
+	if (sa)
+		mld_sta = true;
+	else
+		sa = mgmt->sa;
+#endif /* CONFIG_IEEE80211BE */
+
 	auth_alg = le_to_host16(mgmt->u.auth.auth_alg);
 	auth_transaction = le_to_host16(mgmt->u.auth.auth_transaction);
 	status_code = le_to_host16(mgmt->u.auth.status_code);
@@ -2780,7 +2842,7 @@
 	wpa_printf(MSG_DEBUG, "authentication: STA=" MACSTR " auth_alg=%d "
 		   "auth_transaction=%d status_code=%d wep=%d%s "
 		   "seq_ctrl=0x%x%s%s",
-		   MAC2STR(mgmt->sa), auth_alg, auth_transaction,
+		   MAC2STR(sa), auth_alg, auth_transaction,
 		   status_code, !!(fc & WLAN_FC_ISWEP),
 		   challenge ? " challenge" : "",
 		   seq_ctrl, (fc & WLAN_FC_RETRY) ? " retry" : "",
@@ -2846,7 +2908,17 @@
 
 	if (os_memcmp(mgmt->sa, hapd->own_addr, ETH_ALEN) == 0) {
 		wpa_printf(MSG_INFO, "Station " MACSTR " not allowed to authenticate",
-			   MAC2STR(mgmt->sa));
+			   MAC2STR(sa));
+		resp = WLAN_STATUS_UNSPECIFIED_FAILURE;
+		goto fail;
+	}
+
+	if (mld_sta &&
+	    (os_memcmp(sa, hapd->own_addr, ETH_ALEN) == 0 ||
+	     os_memcmp(sa, hapd->mld_addr, ETH_ALEN) == 0)) {
+		wpa_printf(MSG_INFO,
+			   "Station " MACSTR " not allowed to authenticate",
+			   MAC2STR(sa));
 		resp = WLAN_STATUS_UNSPECIFIED_FAILURE;
 		goto fail;
 	}
@@ -2854,7 +2926,7 @@
 	if (hapd->conf->no_auth_if_seen_on) {
 		struct hostapd_data *other;
 
-		other = sta_track_seen_on(hapd->iface, mgmt->sa,
+		other = sta_track_seen_on(hapd->iface, sa,
 					  hapd->conf->no_auth_if_seen_on);
 		if (other) {
 			u8 *pos;
@@ -2863,7 +2935,7 @@
 
 			wpa_printf(MSG_DEBUG, "%s: Reject authentication from "
 				   MACSTR " since STA has been seen on %s",
-				   hapd->conf->iface, MAC2STR(mgmt->sa),
+				   hapd->conf->iface, MAC2STR(sa),
 				   hapd->conf->no_auth_if_seen_on);
 
 			resp = WLAN_STATUS_REJECTED_WITH_SUGGESTED_BSS_TRANSITION;
@@ -2906,12 +2978,12 @@
 		}
 	}
 
-	res = ieee802_11_allowed_address(hapd, mgmt->sa, (const u8 *) mgmt, len,
+	res = ieee802_11_allowed_address(hapd, sa, (const u8 *) mgmt, len,
 					 &rad_info);
 	if (res == HOSTAPD_ACL_REJECT) {
 		wpa_msg(hapd->msg_ctx, MSG_DEBUG,
 			"Ignore Authentication frame from " MACSTR
-			" due to ACL reject", MAC2STR(mgmt->sa));
+			" due to ACL reject", MAC2STR(sa));
 		resp = WLAN_STATUS_UNSPECIFIED_FAILURE;
 		goto fail;
 	}
@@ -2921,7 +2993,7 @@
 #ifdef CONFIG_SAE
 	if (auth_alg == WLAN_AUTH_SAE && !from_queue &&
 	    (auth_transaction == 1 ||
-	     (auth_transaction == 2 && auth_sae_queued_addr(hapd, mgmt->sa)))) {
+	     (auth_transaction == 2 && auth_sae_queued_addr(hapd, sa)))) {
 		/* Handle SAE Authentication commit message through a queue to
 		 * provide more control for postponing the needed heavy
 		 * processing under a possible DoS attack scenario. In addition,
@@ -2934,7 +3006,7 @@
 	}
 #endif /* CONFIG_SAE */
 
-	sta = ap_get_sta(hapd, mgmt->sa);
+	sta = ap_get_sta(hapd, sa);
 	if (sta) {
 		sta->flags &= ~WLAN_STA_PENDING_FILS_ERP;
 		sta->ft_over_ds = 0;
@@ -2954,7 +3026,7 @@
 		    sta->plink_state == PLINK_BLOCKED) {
 			wpa_printf(MSG_DEBUG, "Mesh peer " MACSTR
 				   " is blocked - drop Authentication frame",
-				   MAC2STR(mgmt->sa));
+				   MAC2STR(sa));
 			return;
 		}
 #endif /* CONFIG_MESH */
@@ -2974,7 +3046,7 @@
 			 */
 			wpa_printf(MSG_DEBUG, "Mesh peer " MACSTR
 				   " not yet known - drop Authentication frame",
-				   MAC2STR(mgmt->sa));
+				   MAC2STR(sa));
 			/*
 			 * Save a copy of the frame so that it can be processed
 			 * if a new peer entry is added shortly after this.
@@ -2986,13 +3058,38 @@
 		}
 #endif /* CONFIG_MESH */
 
-		sta = ap_sta_add(hapd, mgmt->sa);
+		sta = ap_sta_add(hapd, sa);
 		if (!sta) {
 			wpa_printf(MSG_DEBUG, "ap_sta_add() failed");
 			resp = WLAN_STATUS_AP_UNABLE_TO_HANDLE_NEW_STA;
 			goto fail;
 		}
 	}
+
+#ifdef CONFIG_IEEE80211BE
+	if (auth_transaction == 1) {
+		os_memset(&sta->mld_info, 0, sizeof(sta->mld_info));
+
+		if (mld_sta) {
+			u8 link_id = hapd->mld_link_id;
+
+			sta->mld_info.mld_sta = true;
+			sta->mld_assoc_link_id = link_id;
+
+			/*
+			 * Set the MLD address as the station address and the
+			 * station addresses.
+			 */
+			os_memcpy(sta->mld_info.common_info.mld_addr, sa,
+				  ETH_ALEN);
+			os_memcpy(sta->mld_info.links[link_id].peer_addr,
+				  mgmt->sa, ETH_ALEN);
+			os_memcpy(sta->mld_info.links[link_id].local_addr,
+				  hapd->own_addr, ETH_ALEN);
+		}
+	}
+#endif /* CONFIG_IEEE80211BE */
+
 	sta->last_seq_ctrl = seq_ctrl;
 	sta->last_subtype = WLAN_FC_STYPE_AUTH;
 #ifdef CONFIG_MBO
@@ -3130,7 +3227,22 @@
 	}
 
  fail:
-	reply_res = send_auth_reply(hapd, sta, mgmt->sa, mgmt->bssid, auth_alg,
+	dst = mgmt->sa;
+	bssid = mgmt->bssid;
+
+#ifdef CONFIG_IEEE80211BE
+	 /*
+	  * Once a non-AP MLD is added to the driver, the addressing should use
+	  * the MLD MAC address. It is the responsibility of the driver to
+	  * handle the translations.
+	  */
+	if (hapd->conf->mld_ap && sta && sta->mld_info.mld_sta) {
+		dst = sta->addr;
+		bssid = hapd->mld_addr;
+	}
+#endif /* CONFIG_IEEE80211BE */
+
+	reply_res = send_auth_reply(hapd, sta, dst, bssid, auth_alg,
 				    auth_alg == WLAN_AUTH_SAE ?
 				    auth_transaction : auth_transaction + 1,
 				    resp, resp_ies, resp_ies_len,
@@ -3161,10 +3273,51 @@
 }
 
 
+static u32 hostapd_get_aid_word(struct hostapd_data *hapd,
+				struct sta_info *sta, int i)
+{
+#ifdef CONFIG_IEEE80211BE
+	u32 aid_word = 0;
+
+	/* Do not assign an AID that is in use on any of the affiliated links
+	 * when finding an AID for a non-AP MLD. */
+	if (hapd->conf->mld_ap) {
+		int j;
+
+		for (j = 0; j < MAX_NUM_MLD_LINKS; j++) {
+			struct hostapd_data *link_bss;
+
+			if (!sta->mld_info.links[j].valid)
+				continue;
+
+			link_bss = hostapd_mld_get_link_bss(hapd, j);
+			if (!link_bss) {
+				/* This shouldn't happen, just skip */
+				wpa_printf(MSG_ERROR,
+					   "MLD: Failed to get link BSS for AID");
+				continue;
+			}
+
+			aid_word |= link_bss->sta_aid[i];
+		}
+
+		return aid_word;
+	}
+#endif /* CONFIG_IEEE80211BE */
+
+	return hapd->sta_aid[i];
+}
+
+
 int hostapd_get_aid(struct hostapd_data *hapd, struct sta_info *sta)
 {
 	int i, j = 32, aid;
 
+	/* Transmitted and non-transmitted BSSIDs share the same AID pool, so
+	 * use the shared storage in the transmitted BSS to find the next
+	 * available value. */
+	hapd = hostapd_mbssid_get_tx_bss(hapd);
+
 	/* get a unique AID */
 	if (sta->aid > 0) {
 		wpa_printf(MSG_DEBUG, "  old AID %d", sta->aid);
@@ -3175,10 +3328,12 @@
 		return -1;
 
 	for (i = 0; i < AID_WORDS; i++) {
-		if (hapd->sta_aid[i] == (u32) -1)
+		u32 aid_word = hostapd_get_aid_word(hapd, sta, i);
+
+		if (aid_word == (u32) -1)
 			continue;
 		for (j = 0; j < 32; j++) {
-			if (!(hapd->sta_aid[i] & BIT(j)))
+			if (!(aid_word & BIT(j)))
 				break;
 		}
 		if (j < 32)
@@ -3548,7 +3703,8 @@
 u16 owe_process_rsn_ie(struct hostapd_data *hapd,
 		       struct sta_info *sta,
 		       const u8 *rsn_ie, size_t rsn_ie_len,
-		       const u8 *owe_dh, size_t owe_dh_len)
+		       const u8 *owe_dh, size_t owe_dh_len,
+		       const u8 *link_addr)
 {
 	u16 status;
 	u8 *owe_buf, ie[256 * 2];
@@ -3570,6 +3726,11 @@
 		status = WLAN_STATUS_UNSPECIFIED_FAILURE;
 		goto end;
 	}
+#ifdef CONFIG_IEEE80211BE
+	if (sta->mld_info.mld_sta)
+		wpa_auth_set_ml_info(sta->wpa_sm, hapd->mld_addr,
+				     sta->mld_assoc_link_id, &sta->mld_info);
+#endif /* CONFIG_IEEE80211BE */
 	rsn_ie -= 2;
 	rsn_ie_len += 2;
 	res = wpa_validate_wpa_ie(hapd->wpa_auth, sta->wpa_sm,
@@ -3614,8 +3775,9 @@
 end:
 	wpa_printf(MSG_DEBUG, "OWE: Update status %d, ie len %d for peer "
 			      MACSTR, status, (unsigned int) ie_len,
-			      MAC2STR(sta->addr));
-	hostapd_drv_update_dh_ie(hapd, sta->addr, status,
+			      MAC2STR(link_addr ? link_addr : sta->addr));
+	hostapd_drv_update_dh_ie(hapd, link_addr ? link_addr : sta->addr,
+				 status,
 				 status == WLAN_STATUS_SUCCESS ? ie : NULL,
 				 ie_len);
 
@@ -3655,7 +3817,8 @@
 
 static int __check_assoc_ies(struct hostapd_data *hapd, struct sta_info *sta,
 			     const u8 *ies, size_t ies_len,
-			     struct ieee802_11_elems *elems, int reassoc)
+			     struct ieee802_11_elems *elems, int reassoc,
+			     bool link)
 {
 	int resp;
 	const u8 *wpa_ie;
@@ -3757,6 +3920,12 @@
 					  elems->eht_capabilities_len);
 		if (resp != WLAN_STATUS_SUCCESS)
 			return resp;
+
+		if (!link) {
+			resp = hostapd_process_ml_assoc_req(hapd, elems, sta);
+			if (resp != WLAN_STATUS_SUCCESS)
+				return resp;
+		}
 	}
 #endif /* CONFIG_IEEE80211BE */
 
@@ -3790,8 +3959,6 @@
 	if (hapd->conf->wps_state && elems->wps_ie && ies && ies_len) {
 		wpa_printf(MSG_DEBUG, "STA included WPS IE in (Re)Association "
 			   "Request - assume WPS is used");
-		if (check_sa_query(hapd, sta, reassoc))
-			return WLAN_STATUS_ASSOC_REJECTED_TEMPORARILY;
 		sta->flags |= WLAN_STA_WPS;
 		wpabuf_free(sta->wps_ie);
 		sta->wps_ie = ieee802_11_vendor_ie_concat(ies, ies_len,
@@ -3823,20 +3990,36 @@
 	if (hapd->conf->wpa && wpa_ie) {
 		enum wpa_validate_result res;
 
-		if (check_sa_query(hapd, sta, reassoc))
-			return WLAN_STATUS_ASSOC_REJECTED_TEMPORARILY;
-
 		wpa_ie -= 2;
 		wpa_ie_len += 2;
-		if (sta->wpa_sm == NULL)
+
+		if (!sta->wpa_sm) {
+#ifdef CONFIG_IEEE80211BE
+			struct mld_info *info = &sta->mld_info;
+#endif /* CONFIG_IEEE80211BE */
+
 			sta->wpa_sm = wpa_auth_sta_init(hapd->wpa_auth,
 							sta->addr,
 							p2p_dev_addr);
-		if (sta->wpa_sm == NULL) {
-			wpa_printf(MSG_WARNING, "Failed to initialize WPA "
-				   "state machine");
-			return WLAN_STATUS_UNSPECIFIED_FAILURE;
+
+			if (!sta->wpa_sm) {
+				wpa_printf(MSG_WARNING,
+					   "Failed to initialize RSN state machine");
+				return WLAN_STATUS_UNSPECIFIED_FAILURE;
+			}
+
+#ifdef CONFIG_IEEE80211BE
+			if (info->mld_sta) {
+				wpa_printf(MSG_DEBUG,
+					   "MLD: Set ML info in RSN Authenticator");
+				wpa_auth_set_ml_info(sta->wpa_sm,
+						     hapd->mld_addr,
+						     sta->mld_assoc_link_id,
+						     info);
+			}
+#endif /* CONFIG_IEEE80211BE */
 		}
+
 		wpa_auth_set_auth_alg(sta->wpa_sm, sta->auth_alg);
 		res = wpa_validate_wpa_ie(hapd->wpa_auth, sta->wpa_sm,
 					  hapd->iface->freq,
@@ -3873,6 +4056,8 @@
 		}
 #endif /* CONFIG_IEEE80211R_AP */
 
+		if (link)
+			goto skip_sae_owe;
 #ifdef CONFIG_SAE
 		if (wpa_auth_uses_sae(sta->wpa_sm) && sta->sae &&
 		    sta->sae->state == SAE_ACCEPTED)
@@ -3922,6 +4107,7 @@
 				return resp;
 		}
 #endif /* CONFIG_OWE */
+	skip_sae_owe:
 
 #ifdef CONFIG_DPP2
 		dpp_pfs_free(sta->dpp_pfs);
@@ -4116,7 +4302,256 @@
 		return WLAN_STATUS_UNSPECIFIED_FAILURE;
 	}
 
-	return __check_assoc_ies(hapd, sta, ies, ies_len, &elems, reassoc);
+	return __check_assoc_ies(hapd, sta, ies, ies_len, &elems, reassoc,
+				 false);
+}
+
+
+#ifdef CONFIG_IEEE80211BE
+
+static size_t ieee80211_ml_build_assoc_resp(struct hostapd_data *hapd,
+					    u16 status_code,
+					    u8 *buf, size_t buflen)
+{
+	u8 *p = buf;
+
+	/* Capability Info */
+	WPA_PUT_LE16(p, hostapd_own_capab_info(hapd));
+	p += 2;
+
+	/* Status Code */
+	WPA_PUT_LE16(p, status_code);
+	p += 2;
+
+	if (status_code != WLAN_STATUS_SUCCESS)
+		return p - buf;
+
+	/* AID is not included */
+	p = hostapd_eid_supp_rates(hapd, p);
+	p = hostapd_eid_ext_supp_rates(hapd, p);
+	p = hostapd_eid_rm_enabled_capab(hapd, p, buf + buflen - p);
+	p = hostapd_eid_ht_capabilities(hapd, p);
+	p = hostapd_eid_ht_operation(hapd, p);
+
+	if (hapd->iconf->ieee80211ac && !hapd->conf->disable_11ac) {
+		p = hostapd_eid_vht_capabilities(hapd, p, 0);
+		p = hostapd_eid_vht_operation(hapd, p);
+	}
+
+	if (hapd->iconf->ieee80211ax && !hapd->conf->disable_11ax) {
+		p = hostapd_eid_he_capab(hapd, p, IEEE80211_MODE_AP);
+		p = hostapd_eid_he_operation(hapd, p);
+		p = hostapd_eid_spatial_reuse(hapd, p);
+		p = hostapd_eid_he_mu_edca_parameter_set(hapd, p);
+		p = hostapd_eid_he_6ghz_band_cap(hapd, p);
+		if (hapd->iconf->ieee80211be && !hapd->conf->disable_11be) {
+			p = hostapd_eid_eht_capab(hapd, p, IEEE80211_MODE_AP);
+			p = hostapd_eid_eht_operation(hapd, p);
+		}
+	}
+
+	p = hostapd_eid_ext_capab(hapd, p, false);
+	p = hostapd_eid_mbo(hapd, p, buf + buflen - p);
+	p = hostapd_eid_wmm(hapd, p);
+
+	if (hapd->conf->assocresp_elements &&
+	    (size_t) (buf + buflen - p) >=
+	    wpabuf_len(hapd->conf->assocresp_elements)) {
+		os_memcpy(p, wpabuf_head(hapd->conf->assocresp_elements),
+			  wpabuf_len(hapd->conf->assocresp_elements));
+		p += wpabuf_len(hapd->conf->assocresp_elements);
+	}
+
+	return p - buf;
+}
+
+
+static void ieee80211_ml_process_link(struct hostapd_data *hapd,
+				      struct sta_info *origin_sta,
+				      struct mld_link_info *link,
+				      const u8 *ies, size_t ies_len,
+				      bool reassoc)
+{
+	struct ieee802_11_elems elems;
+	struct wpabuf *mlbuf = NULL;
+	struct sta_info *sta = NULL;
+	u16 status = WLAN_STATUS_SUCCESS;
+
+	wpa_printf(MSG_DEBUG, "MLD: link: link_id=%u, peer=" MACSTR,
+		   hapd->mld_link_id, MAC2STR(link->peer_addr));
+
+	if (ieee802_11_parse_elems(ies, ies_len, &elems, 1) == ParseFailed) {
+		wpa_printf(MSG_DEBUG, "MLD: link: Element parsing failed");
+		status = WLAN_STATUS_UNSPECIFIED_FAILURE;
+		goto out;
+	}
+
+	sta = ap_get_sta(hapd, origin_sta->addr);
+	if (sta) {
+		wpa_printf(MSG_INFO, "MLD: link: Station already exists");
+		status = WLAN_STATUS_UNSPECIFIED_FAILURE;
+		sta = NULL;
+		goto out;
+	}
+
+	sta = ap_sta_add(hapd, origin_sta->addr);
+	if (!sta) {
+		wpa_printf(MSG_DEBUG, "MLD: link: ap_sta_add() failed");
+		status = WLAN_STATUS_AP_UNABLE_TO_HANDLE_NEW_STA;
+		goto out;
+	}
+
+	mlbuf = ieee802_11_defrag_mle(&elems, MULTI_LINK_CONTROL_TYPE_BASIC);
+	if (!mlbuf)
+		goto out;
+
+	if (ieee802_11_parse_link_assoc_req(ies, ies_len, &elems, mlbuf,
+					    hapd->mld_link_id, true) ==
+	    ParseFailed) {
+		wpa_printf(MSG_DEBUG,
+			   "MLD: link: Failed to parse association request Multi-Link element");
+		status = WLAN_STATUS_UNSPECIFIED_FAILURE;
+		goto out;
+	}
+
+	sta->flags |= origin_sta->flags | WLAN_STA_ASSOC_REQ_OK;
+	status = __check_assoc_ies(hapd, sta, NULL, 0, &elems, reassoc, true);
+	if (status != WLAN_STATUS_SUCCESS) {
+		wpa_printf(MSG_DEBUG, "MLD: link: Element check failed");
+		goto out;
+	}
+
+	sta->mld_info.mld_sta = true;
+	sta->mld_assoc_link_id = origin_sta->mld_assoc_link_id;
+
+	os_memcpy(&sta->mld_info, &origin_sta->mld_info, sizeof(sta->mld_info));
+
+	/*
+	 * Get the AID from the station on which the association was performed,
+	 * and mark it as used.
+	 */
+	sta->aid = origin_sta->aid;
+	if (sta->aid == 0) {
+		wpa_printf(MSG_DEBUG, "MLD: link: No AID assigned");
+		status = WLAN_STATUS_UNSPECIFIED_FAILURE;
+		goto out;
+	}
+	hapd->sta_aid[(sta->aid - 1) / 32] |= BIT((sta->aid - 1) % 32);
+	sta->listen_interval = origin_sta->listen_interval;
+	if (update_ht_state(hapd, sta) > 0)
+		ieee802_11_update_beacons(hapd->iface);
+
+	/* RSN Authenticator should always be the one on the original station */
+	wpa_auth_sta_deinit(sta->wpa_sm);
+	sta->wpa_sm = NULL;
+
+	/*
+	 * Do not initialize the EAPOL state machine.
+	 * TODO: Maybe it is needed?
+	 */
+	sta->eapol_sm = NULL;
+
+	wpa_printf(MSG_DEBUG, "MLD: link=%u, association OK (aid=%u)",
+		   hapd->mld_link_id, sta->aid);
+
+	/*
+	 * Get RSNE and RSNXE for the current BSS as they are required by the
+	 * Authenticator.
+	 */
+	link->rsne = hostapd_wpa_ie(hapd, WLAN_EID_RSN);
+	link->rsnxe = hostapd_wpa_ie(hapd, WLAN_EID_RSNX);
+
+	sta->flags |= WLAN_STA_AUTH | WLAN_STA_ASSOC_REQ_OK;
+
+	/* TODO: What other processing is required? */
+
+	if (add_associated_sta(hapd, sta, reassoc))
+		status = WLAN_STATUS_AP_UNABLE_TO_HANDLE_NEW_STA;
+out:
+	wpabuf_free(mlbuf);
+	link->status = status;
+
+	wpa_printf(MSG_DEBUG, "MLD: link: status=%u", status);
+	if (sta && status != WLAN_STATUS_SUCCESS)
+		ap_free_sta(hapd, sta);
+
+	link->resp_sta_profile_len =
+		ieee80211_ml_build_assoc_resp(hapd, link->status,
+					      link->resp_sta_profile,
+					      sizeof(link->resp_sta_profile));
+}
+
+
+bool hostapd_is_mld_ap(struct hostapd_data *hapd)
+{
+	if (!hapd->conf->mld_ap)
+		return false;
+
+	if (!hapd->iface || !hapd->iface->interfaces ||
+	    hapd->iface->interfaces->count <= 1)
+		return false;
+
+	return true;
+}
+
+#endif /* CONFIG_IEEE80211BE */
+
+
+static void hostapd_process_assoc_ml_info(struct hostapd_data *hapd,
+					  struct sta_info *sta,
+					  const u8 *ies, size_t ies_len,
+					  bool reassoc)
+{
+#ifdef CONFIG_IEEE80211BE
+	unsigned int i, j;
+
+	if (!hostapd_is_mld_ap(hapd))
+		return;
+
+	/*
+	 * This is not really needed, but make the interaction with the RSN
+	 * Authenticator more consistent
+	 */
+	sta->mld_info.links[hapd->mld_link_id].rsne =
+		hostapd_wpa_ie(hapd, WLAN_EID_RSN);
+	sta->mld_info.links[hapd->mld_link_id].rsnxe =
+		hostapd_wpa_ie(hapd, WLAN_EID_RSNX);
+
+	for (i = 0; i < MAX_NUM_MLD_LINKS; i++) {
+		struct hostapd_iface *iface = NULL;
+		struct mld_link_info *link = &sta->mld_info.links[i];
+
+		if (!link->valid)
+			continue;
+
+		for (j = 0; j < hapd->iface->interfaces->count; j++) {
+			iface = hapd->iface->interfaces->iface[j];
+
+			if (hapd->iface == iface)
+				continue;
+
+			if (iface->bss[0]->conf->mld_ap &&
+			    hapd->conf->mld_id == iface->bss[0]->conf->mld_id &&
+			    i == iface->bss[0]->mld_link_id)
+				break;
+		}
+
+		if (!iface || j == hapd->iface->interfaces->count) {
+			wpa_printf(MSG_DEBUG,
+				   "MLD: No link match for link_id=%u", i);
+
+			link->status = WLAN_STATUS_UNSPECIFIED_FAILURE;
+			link->resp_sta_profile_len =
+				ieee80211_ml_build_assoc_resp(
+					hapd, link->status,
+					link->resp_sta_profile,
+					sizeof(link->resp_sta_profile));
+		} else {
+			ieee80211_ml_process_link(iface->bss[0], sta, link,
+						  ies, ies_len, reassoc);
+		}
+	}
+#endif /* CONFIG_IEEE80211BE */
 }
 
 
@@ -4150,6 +4585,20 @@
 	struct ieee80211_he_capabilities he_cap;
 	struct ieee80211_eht_capabilities eht_cap;
 	int set = 1;
+	const u8 *mld_link_addr = NULL;
+	bool mld_link_sta = false;
+
+#ifdef CONFIG_IEEE80211BE
+	if (hapd->conf->mld_ap && sta->mld_info.mld_sta) {
+		u8 mld_link_id = hapd->mld_link_id;
+
+		mld_link_sta = sta->mld_assoc_link_id != mld_link_id;
+		mld_link_addr = sta->mld_info.links[mld_link_id].peer_addr;
+
+		if (hapd->mld_link_id != sta->mld_assoc_link_id)
+			set = 0;
+	}
+#endif /* CONFIG_IEEE80211BE */
 
 	/*
 	 * Remove the STA entry to ensure the STA PS state gets cleared and
@@ -4178,7 +4627,7 @@
 		   wpa_auth_sta_ft_tk_already_set(sta->wpa_sm),
 		   wpa_auth_sta_fils_tk_already_set(sta->wpa_sm));
 
-	if (!sta->added_unassoc &&
+	if (!mld_link_sta && !sta->added_unassoc &&
 	    (!(sta->flags & WLAN_STA_AUTHORIZED) ||
 	     (reassoc && sta->ft_over_ds && sta->auth_alg == WLAN_AUTH_FT) ||
 	     (!wpa_auth_sta_ft_tk_already_set(sta->wpa_sm) &&
@@ -4228,7 +4677,7 @@
 			    sta->he_6ghz_capab,
 			    sta->flags | WLAN_STA_ASSOC, sta->qosinfo,
 			    sta->vht_opmode, sta->p2p_ie ? 1 : 0,
-			    set)) {
+			    set, mld_link_addr, mld_link_sta)) {
 		hostapd_logger(hapd, sta->addr,
 			       HOSTAPD_MODULE_IEEE80211, HOSTAPD_LEVEL_NOTICE,
 			       "Could not %s STA to kernel driver",
@@ -4259,6 +4708,7 @@
 	struct ieee80211_mgmt *reply;
 	u8 *p;
 	u16 res = WLAN_STATUS_SUCCESS;
+	const u8 *sa = hapd->own_addr;
 
 	buflen = sizeof(struct ieee80211_mgmt) + 1024;
 #ifdef CONFIG_FILS
@@ -4294,9 +4744,19 @@
 		IEEE80211_FC(WLAN_FC_TYPE_MGMT,
 			     (reassoc ? WLAN_FC_STYPE_REASSOC_RESP :
 			      WLAN_FC_STYPE_ASSOC_RESP));
+
+#ifdef CONFIG_IEEE80211BE
+	/*
+	 * Once a non-AP MLD is added to the driver, the addressing should use
+	 * MLD MAC address.
+	 */
+	if (hapd->conf->mld_ap && sta && sta->mld_info.mld_sta)
+		sa = hapd->mld_addr;
+#endif /* CONFIG_IEEE80211BE */
+
 	os_memcpy(reply->da, addr, ETH_ALEN);
-	os_memcpy(reply->sa, hapd->own_addr, ETH_ALEN);
-	os_memcpy(reply->bssid, hapd->own_addr, ETH_ALEN);
+	os_memcpy(reply->sa, sa, ETH_ALEN);
+	os_memcpy(reply->bssid, sa, ETH_ALEN);
 
 	send_len = IEEE80211_HDRLEN;
 	send_len += sizeof(reply->u.assoc_resp);
@@ -4432,6 +4892,8 @@
 
 #ifdef CONFIG_IEEE80211BE
 	if (hapd->iconf->ieee80211be && !hapd->conf->disable_11be) {
+		if (hapd->conf->mld_ap)
+			p = hostapd_eid_eht_basic_ml(hapd, p, sta, false);
 		p = hostapd_eid_eht_capab(hapd, p, IEEE80211_MODE_AP);
 		p = hostapd_eid_eht_operation(hapd, p);
 	}
@@ -4704,6 +5166,7 @@
 	int delay_assoc = 0;
 #endif /* CONFIG_FILS */
 	int omit_rsnxe = 0;
+	bool set_beacon = false;
 
 	if (len < IEEE80211_HDRLEN + (reassoc ? sizeof(mgmt->u.reassoc_req) :
 				      sizeof(mgmt->u.assoc_req))) {
@@ -4879,6 +5342,11 @@
 	}
 #endif /* CONFIG_MBO */
 
+	if (hapd->conf->wpa && check_sa_query(hapd, sta, reassoc)) {
+		resp = WLAN_STATUS_ASSOC_REJECTED_TEMPORARILY;
+		goto fail;
+	}
+
 	/*
 	 * sta->capability is used in check_assoc_ies() for RRM enabled
 	 * capability element.
@@ -4940,7 +5408,7 @@
 		sta->nonerp_set = 1;
 		hapd->iface->num_sta_non_erp++;
 		if (hapd->iface->num_sta_non_erp == 1)
-			ieee802_11_set_beacons(hapd->iface);
+			set_beacon = true;
 	}
 
 	if (!(sta->capability & WLAN_CAPABILITY_SHORT_SLOT_TIME) &&
@@ -4951,7 +5419,7 @@
 		    hapd->iface->current_mode->mode ==
 		    HOSTAPD_MODE_IEEE80211G &&
 		    hapd->iface->num_sta_no_short_slot_time == 1)
-			ieee802_11_set_beacons(hapd->iface);
+			set_beacon = true;
 	}
 
 	if (sta->capability & WLAN_CAPABILITY_SHORT_PREAMBLE)
@@ -4966,10 +5434,11 @@
 		if (hapd->iface->current_mode &&
 		    hapd->iface->current_mode->mode == HOSTAPD_MODE_IEEE80211G
 		    && hapd->iface->num_sta_no_short_preamble == 1)
-			ieee802_11_set_beacons(hapd->iface);
+			set_beacon = true;
 	}
 
-	update_ht_state(hapd, sta);
+	if (update_ht_state(hapd, sta) > 0)
+		set_beacon = true;
 
 	hostapd_logger(hapd, sta->addr, HOSTAPD_MODULE_IEEE80211,
 		       HOSTAPD_LEVEL_DEBUG,
@@ -5008,6 +5477,9 @@
 	}
 #endif /* CONFIG_FILS */
 
+	if (set_beacon)
+		ieee802_11_set_beacons(hapd->iface);
+
  fail:
 
 	/*
@@ -5028,6 +5500,9 @@
 	 *    issues with processing other non-Data Class 3 frames during this
 	 *    window.
 	 */
+	if (resp == WLAN_STATUS_SUCCESS)
+		hostapd_process_assoc_ml_info(hapd, sta, pos, left, reassoc);
+
 	if (resp == WLAN_STATUS_SUCCESS && sta &&
 	    add_associated_sta(hapd, sta, reassoc))
 		resp = WLAN_STATUS_AP_UNABLE_TO_HANDLE_NEW_STA;
@@ -5087,27 +5562,37 @@
 }
 
 
-static void handle_disassoc(struct hostapd_data *hapd,
-			    const struct ieee80211_mgmt *mgmt, size_t len)
+static void hostapd_deauth_sta(struct hostapd_data *hapd,
+			       struct sta_info *sta,
+			       const struct ieee80211_mgmt *mgmt)
 {
-	struct sta_info *sta;
+	wpa_msg(hapd->msg_ctx, MSG_DEBUG,
+		"deauthentication: STA=" MACSTR " reason_code=%d",
+		MAC2STR(mgmt->sa), le_to_host16(mgmt->u.deauth.reason_code));
 
-	if (len < IEEE80211_HDRLEN + sizeof(mgmt->u.disassoc)) {
-		wpa_printf(MSG_INFO, "handle_disassoc - too short payload (len=%lu)",
-			   (unsigned long) len);
-		return;
-	}
+	ap_sta_set_authorized(hapd, sta, 0);
+	sta->last_seq_ctrl = WLAN_INVALID_MGMT_SEQ;
+	sta->flags &= ~(WLAN_STA_AUTH | WLAN_STA_ASSOC |
+			WLAN_STA_ASSOC_REQ_OK);
+	hostapd_set_sta_flags(hapd, sta);
+	wpa_auth_sm_event(sta->wpa_sm, WPA_DEAUTH);
+	hostapd_logger(hapd, sta->addr, HOSTAPD_MODULE_IEEE80211,
+		       HOSTAPD_LEVEL_DEBUG, "deauthenticated");
+	mlme_deauthenticate_indication(
+		hapd, sta, le_to_host16(mgmt->u.deauth.reason_code));
+	sta->acct_terminate_cause = RADIUS_ACCT_TERMINATE_CAUSE_USER_REQUEST;
+	ieee802_1x_notify_port_enabled(sta->eapol_sm, 0);
+	ap_free_sta(hapd, sta);
+}
 
-	wpa_printf(MSG_DEBUG, "disassocation: STA=" MACSTR " reason_code=%d",
-		   MAC2STR(mgmt->sa),
-		   le_to_host16(mgmt->u.disassoc.reason_code));
 
-	sta = ap_get_sta(hapd, mgmt->sa);
-	if (sta == NULL) {
-		wpa_printf(MSG_INFO, "Station " MACSTR " trying to disassociate, but it is not associated",
-			   MAC2STR(mgmt->sa));
-		return;
-	}
+static void hostapd_disassoc_sta(struct hostapd_data *hapd,
+				 struct sta_info *sta,
+				 const struct ieee80211_mgmt *mgmt)
+{
+	wpa_msg(hapd->msg_ctx, MSG_DEBUG,
+		"disassocation: STA=" MACSTR " reason_code=%d",
+		MAC2STR(mgmt->sa), le_to_host16(mgmt->u.disassoc.reason_code));
 
 	ap_sta_set_authorized(hapd, sta, 0);
 	sta->last_seq_ctrl = WLAN_INVALID_MGMT_SEQ;
@@ -5152,45 +5637,173 @@
 }
 
 
+#ifdef CONFIG_IEEE80211BE
+static struct sta_info *
+hostapd_ml_get_assoc_sta(struct hostapd_data *hapd, struct sta_info *sta,
+			 struct hostapd_data **assoc_hapd)
+{
+	struct hostapd_data *other_hapd = NULL;
+	struct sta_info *tmp_sta;
+
+	*assoc_hapd = hapd;
+
+	/* The station is the one on which the association was performed */
+	if (sta->mld_assoc_link_id == hapd->mld_link_id)
+		return sta;
+
+	other_hapd = hostapd_mld_get_link_bss(hapd, sta->mld_assoc_link_id);
+	if (!other_hapd) {
+		wpa_printf(MSG_DEBUG, "MLD: No link match for link_id=%u",
+			   sta->mld_assoc_link_id);
+		return sta;
+	}
+
+	/*
+	 * Iterate over the stations and find the one with the matching link ID
+	 * and association ID.
+	 */
+	for (tmp_sta = other_hapd->sta_list; tmp_sta; tmp_sta = tmp_sta->next) {
+		if (tmp_sta->mld_assoc_link_id == sta->mld_assoc_link_id &&
+		    tmp_sta->aid == sta->aid) {
+			*assoc_hapd = other_hapd;
+			return tmp_sta;
+		}
+	}
+
+	return sta;
+}
+#endif /* CONFIG_IEEE80211BE */
+
+
+static bool hostapd_ml_handle_disconnect(struct hostapd_data *hapd,
+					 struct sta_info *sta,
+					 const struct ieee80211_mgmt *mgmt,
+					 bool disassoc)
+{
+#ifdef CONFIG_IEEE80211BE
+	struct hostapd_data *assoc_hapd, *tmp_hapd;
+	struct sta_info *assoc_sta;
+	unsigned int i, link_id;
+
+	if (!hostapd_is_mld_ap(hapd))
+		return false;
+
+	/*
+	 * Get the station on which the association was performed, as it holds
+	 * the information about all the other links.
+	 */
+	assoc_sta = hostapd_ml_get_assoc_sta(hapd, sta, &assoc_hapd);
+
+	for (link_id = 0; link_id < MAX_NUM_MLD_LINKS; link_id++) {
+		for (i = 0; i < assoc_hapd->iface->interfaces->count; i++) {
+			struct sta_info *tmp_sta;
+
+			if (!assoc_sta->mld_info.links[link_id].valid)
+				continue;
+
+			tmp_hapd =
+				assoc_hapd->iface->interfaces->iface[i]->bss[0];
+
+			if (!tmp_hapd->conf->mld_ap ||
+			    assoc_hapd->conf->mld_id != tmp_hapd->conf->mld_id)
+				continue;
+
+			for (tmp_sta = tmp_hapd->sta_list; tmp_sta;
+			     tmp_sta = tmp_sta->next) {
+				/*
+				 * Remove the station on which the association
+				 * was done only after all other link stations
+				 * are removed. Since there is only a single
+				 * station per struct hostapd_hapd with the
+				 * same association link simply break out from
+				 * the loop.
+				 */
+				if (tmp_sta == assoc_sta)
+					break;
+
+				if (tmp_sta->mld_assoc_link_id !=
+				    assoc_sta->mld_assoc_link_id ||
+				    tmp_sta->aid != assoc_sta->aid)
+					continue;
+
+				if (!disassoc)
+					hostapd_deauth_sta(tmp_hapd, tmp_sta,
+							   mgmt);
+				else
+					hostapd_disassoc_sta(tmp_hapd, tmp_sta,
+							     mgmt);
+				break;
+			}
+		}
+	}
+
+	/* Remove the station on which the association was performed. */
+	if (!disassoc)
+		hostapd_deauth_sta(assoc_hapd, assoc_sta, mgmt);
+	else
+		hostapd_disassoc_sta(assoc_hapd, assoc_sta, mgmt);
+
+	return true;
+#else /* CONFIG_IEEE80211BE */
+	return false;
+#endif /* CONFIG_IEEE80211BE */
+}
+
+
+static void handle_disassoc(struct hostapd_data *hapd,
+			    const struct ieee80211_mgmt *mgmt, size_t len)
+{
+	struct sta_info *sta;
+
+	if (len < IEEE80211_HDRLEN + sizeof(mgmt->u.disassoc)) {
+		wpa_msg(hapd->msg_ctx, MSG_DEBUG,
+			   "handle_disassoc - too short payload (len=%lu)",
+			   (unsigned long) len);
+		return;
+	}
+
+	sta = ap_get_sta(hapd, mgmt->sa);
+	if (!sta) {
+		wpa_msg(hapd->msg_ctx, MSG_DEBUG, "Station " MACSTR
+			" trying to disassociate, but it is not associated",
+			MAC2STR(mgmt->sa));
+		return;
+	}
+
+	if (hostapd_ml_handle_disconnect(hapd, sta, mgmt, true))
+		return;
+
+	hostapd_disassoc_sta(hapd, sta, mgmt);
+}
+
+
 static void handle_deauth(struct hostapd_data *hapd,
 			  const struct ieee80211_mgmt *mgmt, size_t len)
 {
 	struct sta_info *sta;
 
 	if (len < IEEE80211_HDRLEN + sizeof(mgmt->u.deauth)) {
-		wpa_msg(hapd->msg_ctx, MSG_DEBUG, "handle_deauth - too short "
-			"payload (len=%lu)", (unsigned long) len);
+		wpa_msg(hapd->msg_ctx, MSG_DEBUG,
+			"handle_deauth - too short payload (len=%lu)",
+			(unsigned long) len);
 		return;
 	}
 
-	wpa_msg(hapd->msg_ctx, MSG_DEBUG, "deauthentication: STA=" MACSTR
-		" reason_code=%d",
-		MAC2STR(mgmt->sa), le_to_host16(mgmt->u.deauth.reason_code));
-
 	/* Clear the PTKSA cache entries for PASN */
 	ptksa_cache_flush(hapd->ptksa, mgmt->sa, WPA_CIPHER_NONE);
 
 	sta = ap_get_sta(hapd, mgmt->sa);
-	if (sta == NULL) {
-		wpa_msg(hapd->msg_ctx, MSG_DEBUG, "Station " MACSTR " trying "
-			"to deauthenticate, but it is not authenticated",
+	if (!sta) {
+		wpa_msg(hapd->msg_ctx, MSG_DEBUG, "Station " MACSTR
+			" trying to deauthenticate, but it is not authenticated",
 			MAC2STR(mgmt->sa));
 		return;
 	}
 
-	ap_sta_set_authorized(hapd, sta, 0);
-	sta->last_seq_ctrl = WLAN_INVALID_MGMT_SEQ;
-	sta->flags &= ~(WLAN_STA_AUTH | WLAN_STA_ASSOC |
-			WLAN_STA_ASSOC_REQ_OK);
-	hostapd_set_sta_flags(hapd, sta);
-	wpa_auth_sm_event(sta->wpa_sm, WPA_DEAUTH);
-	hostapd_logger(hapd, sta->addr, HOSTAPD_MODULE_IEEE80211,
-		       HOSTAPD_LEVEL_DEBUG, "deauthenticated");
-	mlme_deauthenticate_indication(
-		hapd, sta, le_to_host16(mgmt->u.deauth.reason_code));
-	sta->acct_terminate_cause = RADIUS_ACCT_TERMINATE_CAUSE_USER_REQUEST;
-	ieee802_1x_notify_port_enabled(sta->eapol_sm, 0);
-	ap_free_sta(hapd, sta);
+	if (hostapd_ml_handle_disconnect(hapd, sta, mgmt, false))
+		return;
+
+	hostapd_deauth_sta(hapd, sta, mgmt);
 }
 
 
@@ -5495,6 +6108,10 @@
 #ifdef CONFIG_MESH
 	    !(hapd->conf->mesh & MESH_ENABLED) &&
 #endif /* CONFIG_MESH */
+#ifdef CONFIG_IEEE80211BE
+	    !(hapd->conf->mld_ap &&
+	      os_memcmp(hapd->mld_addr, mgmt->bssid, ETH_ALEN) == 0) &&
+#endif /* CONFIG_IEEE80211BE */
 	    os_memcmp(mgmt->bssid, hapd->own_addr, ETH_ALEN) != 0) {
 		wpa_printf(MSG_INFO, "MGMT: BSSID=" MACSTR " not our address",
 			   MAC2STR(mgmt->bssid));
@@ -5514,6 +6131,10 @@
 
 	if ((!is_broadcast_ether_addr(mgmt->da) ||
 	     stype != WLAN_FC_STYPE_ACTION) &&
+#ifdef CONFIG_IEEE80211BE
+	    !(hapd->conf->mld_ap &&
+	      os_memcmp(hapd->mld_addr, mgmt->bssid, ETH_ALEN) == 0) &&
+#endif /* CONFIG_IEEE80211BE */
 	    os_memcmp(mgmt->da, hapd->own_addr, ETH_ALEN) != 0) {
 		hostapd_logger(hapd, mgmt->sa, HOSTAPD_MODULE_IEEE80211,
 			       HOSTAPD_LEVEL_DEBUG,
@@ -5658,6 +6279,90 @@
 }
 
 
+#ifdef CONFIG_IEEE80211BE
+static void ieee80211_ml_link_sta_assoc_cb(struct hostapd_data *hapd,
+					   struct sta_info *sta,
+					   struct mld_link_info *link,
+					   bool ok)
+{
+	if (!ok) {
+		hostapd_logger(hapd, link->peer_addr, HOSTAPD_MODULE_IEEE80211,
+			       HOSTAPD_LEVEL_DEBUG,
+			       "did not acknowledge association response");
+		sta->flags &= ~WLAN_STA_ASSOC_REQ_OK;
+
+		/* The STA is added only in case of SUCCESS */
+		if (link->status == WLAN_STATUS_SUCCESS)
+			hostapd_drv_sta_remove(hapd, sta->addr);
+
+		return;
+	}
+
+	if (link->status != WLAN_STATUS_SUCCESS)
+		return;
+
+	sta->flags |= WLAN_STA_ASSOC;
+	sta->flags &= ~WLAN_STA_WNM_SLEEP_MODE;
+
+	if (!hapd->conf->ieee802_1x && !hapd->conf->wpa)
+		ap_sta_set_authorized(hapd, sta, 1);
+
+	hostapd_set_sta_flags(hapd, sta);
+
+	/*
+	 * TODOs:
+	 * - IEEE 802.1X port enablement is not needed as done on the station
+	 *     doing the connection.
+	 * - Not handling accounting
+	 * - Need to handle VLAN configuration
+	 */
+}
+#endif /* CONFIG_IEEE80211BE */
+
+
+static void hostapd_ml_handle_assoc_cb(struct hostapd_data *hapd,
+				       struct sta_info *sta, bool ok)
+{
+#ifdef CONFIG_IEEE80211BE
+	unsigned int i, link_id;
+
+	if (!hostapd_is_mld_ap(hapd))
+		return;
+
+	for (link_id = 0; link_id < MAX_NUM_MLD_LINKS; link_id++) {
+		struct mld_link_info *link = &sta->mld_info.links[link_id];
+
+		if (!link->valid)
+			continue;
+
+		for (i = 0; i < hapd->iface->interfaces->count; i++) {
+			struct sta_info *tmp_sta;
+			struct hostapd_data *tmp_hapd =
+				hapd->iface->interfaces->iface[i]->bss[0];
+
+			if (tmp_hapd->conf->mld_ap ||
+			    hapd->conf->mld_id != tmp_hapd->conf->mld_id)
+				continue;
+
+			for (tmp_sta = tmp_hapd->sta_list; tmp_sta;
+			     tmp_sta = tmp_sta->next) {
+				if (tmp_sta == sta ||
+				    tmp_sta->mld_assoc_link_id !=
+				    sta->mld_assoc_link_id ||
+				    tmp_sta->aid != sta->aid)
+					continue;
+
+				ieee80211_ml_link_sta_assoc_cb(tmp_hapd,
+							       tmp_sta, link,
+							       ok);
+				break;
+			}
+		}
+	}
+#endif /* CONFIG_IEEE80211BE */
+}
+
+
 static void handle_assoc_cb(struct hostapd_data *hapd,
 			    const struct ieee80211_mgmt *mgmt,
 			    size_t len, int reassoc, int ok)
@@ -5673,6 +6378,17 @@
 		return;
 	}
 
+#ifdef CONFIG_IEEE80211BE
+	if (hapd->conf->mld_ap && sta->mld_info.mld_sta &&
+	    hapd->mld_link_id != sta->mld_assoc_link_id) {
+		/* See ieee80211_ml_link_sta_assoc_cb() for the MLD case */
+		wpa_printf(MSG_DEBUG,
+			   "%s: MLD: ignore on link station (%d != %d)",
+			   __func__, hapd->mld_link_id, sta->mld_assoc_link_id);
+		return;
+	}
+#endif /* CONFIG_IEEE80211BE */
+
 	if (len < IEEE80211_HDRLEN + (reassoc ? sizeof(mgmt->u.reassoc_resp) :
 				      sizeof(mgmt->u.assoc_resp))) {
 		wpa_printf(MSG_INFO,
@@ -5696,11 +6412,11 @@
 		if (status == WLAN_STATUS_SUCCESS)
 			hostapd_drv_sta_remove(hapd, sta->addr);
 
-		return;
+		goto handle_ml;
 	}
 
 	if (status != WLAN_STATUS_SUCCESS)
-		return;
+		goto handle_ml;
 
 	/* Stop previous accounting session, if one is started, and allocate
 	 * new session id for the new session. */
@@ -5742,11 +6458,11 @@
 		 * interface selection is not going to change anymore.
 		 */
 		if (ap_sta_bind_vlan(hapd, sta) < 0)
-			return;
+			goto handle_ml;
 	} else if (sta->vlan_id) {
 		/* VLAN ID already set (e.g., by PMKSA caching), so bind STA */
 		if (ap_sta_bind_vlan(hapd, sta) < 0)
-			return;
+			goto handle_ml;
 	}
 
 	hostapd_set_sta_flags(hapd, sta);
@@ -5762,7 +6478,7 @@
 	/* WPS not supported on backhaul BSS. Disable 4addr mode on fronthaul */
 	if ((sta->flags & WLAN_STA_WDS) ||
 	    (sta->flags & WLAN_STA_MULTI_AP &&
-	     !(hapd->conf->multi_ap & FRONTHAUL_BSS) &&
+	     (hapd->conf->multi_ap & BACKHAUL_BSS) &&
 	     !(sta->flags & WLAN_STA_WPS))) {
 		int ret;
 		char ifname_wds[IFNAMSIZ + 1];
@@ -5814,6 +6530,9 @@
 		os_free(sta->pending_eapol_rx);
 		sta->pending_eapol_rx = NULL;
 	}
+
+handle_ml:
+	hostapd_ml_handle_assoc_cb(hapd, sta, ok);
 }
 
 
@@ -6343,7 +7062,7 @@
 
 u8 * hostapd_eid_wb_chsw_wrapper(struct hostapd_data *hapd, u8 *eid)
 {
-	u8 bw, chan1, chan2 = 0;
+	u8 bw, chan1 = 0, chan2 = 0;
 	int freq1;
 
 	if (!hapd->cs_freq_params.channel ||
@@ -6352,20 +7071,17 @@
 	     !hapd->cs_freq_params.eht_enabled))
 		return eid;
 
-	/* bandwidth: 0: 40, 1: 80, 2: 160, 3: 80+80, 4: 320 */
+	/* bandwidth: 0: 40, 1: 80, 160, 80+80, 4: 320 as per
+	 * IEEE P802.11-REVme/D4.0, 9.4.2.159 and Table 9-314. */
 	switch (hapd->cs_freq_params.bandwidth) {
 	case 40:
 		bw = 0;
 		break;
 	case 80:
-		/* check if it's 80+80 */
-		if (!hapd->cs_freq_params.center_freq2)
-			bw = 1;
-		else
-			bw = 3;
+		bw = 1;
 		break;
 	case 160:
-		bw = 2;
+		bw = 1;
 		break;
 	case 320:
 		bw = 4;
@@ -6392,6 +7108,21 @@
 	*eid++ = WLAN_EID_WIDE_BW_CHSWITCH;
 	*eid++ = 3; /* Length of Wide Bandwidth Channel Switch element */
 	*eid++ = bw; /* New Channel Width */
+	if (hapd->cs_freq_params.bandwidth == 160) {
+		/* Update the CCFS0 and CCFS1 values in the element based on
+		 * IEEE P802.11-REVme/D4.0, Table 9-314 */
+
+		/* CCFS1 - The channel center frequency index of the 160 MHz
+		 * channel. */
+		chan2 = chan1;
+
+		/* CCFS0 - The channel center frequency index of the 80 MHz
+		 * channel segment that contains the primary channel. */
+		if (hapd->cs_freq_params.channel < chan1)
+			chan1 -= 8;
+		else
+			chan1 += 8;
+	}
 	*eid++ = chan1; /* New Channel Center Frequency Segment 0 */
 	*eid++ = chan2; /* New Channel Center Frequency Segment 1 */
 
@@ -6443,12 +7174,19 @@
 	size_t total_len = 0, len = *current_len;
 	int tbtt_count = 0;
 	size_t i, start = 0;
+	bool ap_mld = false;
+
+#ifdef CONFIG_IEEE80211BE
+	ap_mld = !!hapd->conf->mld_ap;
+#endif /* CONFIG_IEEE80211BE */
 
 	while (start < hapd->iface->num_bss) {
 		if (!len ||
-		    len + RNR_TBTT_HEADER_LEN + RNR_TBTT_INFO_LEN > 255) {
+		    len + RNR_TBTT_HEADER_LEN + RNR_TBTT_INFO_LEN > 255 ||
+		    tbtt_count >= RNR_TBTT_INFO_COUNT_MAX) {
 			len = RNR_HEADER_LEN;
 			total_len += RNR_HEADER_LEN;
+			tbtt_count = 0;
 		}
 
 		len += RNR_TBTT_HEADER_LEN;
@@ -6472,8 +7210,13 @@
 			    tbtt_count >= RNR_TBTT_INFO_COUNT_MAX)
 				break;
 
-			len += RNR_TBTT_INFO_LEN;
-			total_len += RNR_TBTT_INFO_LEN;
+			if (!ap_mld) {
+				len += RNR_TBTT_INFO_LEN;
+				total_len += RNR_TBTT_INFO_LEN;
+			} else {
+				len += RNR_TBTT_INFO_MLD_LEN;
+				total_len += RNR_TBTT_INFO_MLD_LEN;
+			}
 			tbtt_count++;
 		}
 		start = i;
@@ -6528,8 +7271,8 @@
 }
 
 
-static size_t hostapd_eid_rnr_colocation_len(struct hostapd_data *hapd,
-					     size_t *current_len)
+static size_t hostapd_eid_rnr_multi_iface_len(struct hostapd_data *hapd,
+					      size_t *current_len)
 {
 	struct hostapd_iface *iface;
 	size_t len = 0;
@@ -6540,9 +7283,16 @@
 
 	for (i = 0; i < hapd->iface->interfaces->count; i++) {
 		iface = hapd->iface->interfaces->iface[i];
+		bool ap_mld = false;
+
+#ifdef CONFIG_IEEE80211BE
+		if (hapd->conf->mld_ap && iface->bss[0]->conf->mld_ap &&
+		    hapd->conf->mld_id == iface->bss[0]->conf->mld_id)
+			ap_mld = true;
+#endif /* CONFIG_IEEE80211BE */
 
 		if (iface == hapd->iface ||
-		    !is_6ghz_op_class(iface->conf->op_class))
+		    !(is_6ghz_op_class(iface->conf->op_class) || ap_mld))
 			continue;
 
 		len += hostapd_eid_rnr_iface_len(iface->bss[0], hapd,
@@ -6557,6 +7307,11 @@
 {
 	size_t total_len = 0, current_len = 0;
 	enum colocation_mode mode = get_colocation_mode(hapd);
+	bool ap_mld = false;
+
+#ifdef CONFIG_IEEE80211BE
+	ap_mld = !!hapd->conf->mld_ap;
+#endif /* CONFIG_IEEE80211BE */
 
 	switch (type) {
 	case WLAN_FC_STYPE_BEACON:
@@ -6565,9 +7320,10 @@
 		/* fallthrough */
 
 	case WLAN_FC_STYPE_PROBE_RESP:
-		if (mode == COLOCATED_LOWER_BAND)
-			total_len += hostapd_eid_rnr_colocation_len(
-				hapd, &current_len);
+		if (mode == COLOCATED_LOWER_BAND || ap_mld)
+			total_len +=
+				hostapd_eid_rnr_multi_iface_len(hapd,
+								&current_len);
 
 		if (hapd->conf->rnr && hapd->iface->num_bss > 1 &&
 		    !hapd->iconf->mbssid)
@@ -6657,6 +7413,11 @@
 	size_t len = *current_len;
 	u8 *tbtt_count_pos, *eid_start = eid, *size_offset = (eid - len) + 1;
 	u8 tbtt_count = 0, op_class, channel, bss_param;
+	bool ap_mld = false;
+
+#ifdef CONFIG_IEEE80211BE
+	ap_mld = !!hapd->conf->mld_ap;
+#endif /* CONFIG_IEEE80211BE */
 
 	if (!(iface->drv_flags & WPA_DRIVER_FLAGS_AP_CSA) || !iface->freq)
 		return eid;
@@ -6670,7 +7431,8 @@
 
 	while (start < iface->num_bss) {
 		if (!len ||
-		    len + RNR_TBTT_HEADER_LEN + RNR_TBTT_INFO_LEN > 255) {
+		    len + RNR_TBTT_HEADER_LEN + RNR_TBTT_INFO_LEN > 255 ||
+		    tbtt_count >= RNR_TBTT_INFO_COUNT_MAX) {
 			eid_start = eid;
 			*eid++ = WLAN_EID_REDUCED_NEIGHBOR_REPORT;
 			size_offset = eid++;
@@ -6679,7 +7441,7 @@
 		}
 
 		tbtt_count_pos = eid++;
-		*eid++ = RNR_TBTT_INFO_LEN;
+		*eid++ = ap_mld ? RNR_TBTT_INFO_MLD_LEN : RNR_TBTT_INFO_LEN;
 		*eid++ = op_class;
 		*eid++ = hapd->iconf->channel;
 		len += RNR_TBTT_HEADER_LEN;
@@ -6728,7 +7490,18 @@
 
 			*eid++ = bss_param;
 			*eid++ = RNR_20_MHZ_PSD_MAX_TXPOWER - 1;
-			len += RNR_TBTT_INFO_LEN;
+
+			if (!ap_mld) {
+				len += RNR_TBTT_INFO_LEN;
+			} else {
+#ifdef CONFIG_IEEE80211BE
+				*eid++ = hapd->conf->mld_id;
+				*eid++ = hapd->mld_link_id | (1 << 4);
+				*eid++ = 0;
+				len += RNR_TBTT_INFO_MLD_LEN;
+#endif /* CONFIG_IEEE80211BE */
+			}
+
 			tbtt_count += 1;
 		}
 
@@ -6745,8 +7518,8 @@
 }
 
 
-static u8 * hostapd_eid_rnr_colocation(struct hostapd_data *hapd, u8 *eid,
-				       size_t *current_len)
+static u8 * hostapd_eid_rnr_multi_iface(struct hostapd_data *hapd, u8 *eid,
+					size_t *current_len)
 {
 	struct hostapd_iface *iface;
 	size_t i;
@@ -6756,9 +7529,16 @@
 
 	for (i = 0; i < hapd->iface->interfaces->count; i++) {
 		iface = hapd->iface->interfaces->iface[i];
+		bool ap_mld = false;
+
+#ifdef CONFIG_IEEE80211BE
+		if (hapd->conf->mld_ap && iface->bss[0]->conf->mld_ap &&
+		    hapd->conf->mld_id == iface->bss[0]->conf->mld_id)
+			ap_mld = true;
+#endif /* CONFIG_IEEE80211BE */
 
 		if (iface == hapd->iface ||
-		    !is_6ghz_op_class(iface->conf->op_class))
+		    !(is_6ghz_op_class(iface->conf->op_class) || ap_mld))
 			continue;
 
 		eid = hostapd_eid_rnr_iface(iface->bss[0], hapd, eid,
@@ -6774,6 +7554,11 @@
 	u8 *eid_start = eid;
 	size_t current_len = 0;
 	enum colocation_mode mode = get_colocation_mode(hapd);
+	bool ap_mld = false;
+
+#ifdef CONFIG_IEEE80211BE
+	ap_mld = !!hapd->conf->mld_ap;
+#endif /* CONFIG_IEEE80211BE */
 
 	switch (type) {
 	case WLAN_FC_STYPE_BEACON:
@@ -6782,9 +7567,9 @@
 		/* fallthrough */
 
 	case WLAN_FC_STYPE_PROBE_RESP:
-		if (mode == COLOCATED_LOWER_BAND)
-			eid = hostapd_eid_rnr_colocation(hapd, eid,
-							 &current_len);
+		if (mode == COLOCATED_LOWER_BAND || ap_mld)
+			eid = hostapd_eid_rnr_multi_iface(hapd, eid,
+							  &current_len);
 
 		if (hapd->conf->rnr && hapd->iface->num_bss > 1 &&
 		    !hapd->iconf->mbssid)
@@ -6979,7 +7764,13 @@
 			    (conf->dtim_period % elem_count))
 				conf->dtim_period = elem_count;
 			*eid++ = conf->dtim_period;
-			*eid++ = 0xFF; /* DTIM Count */
+			/* The driver is expected to update the DTIM Count
+			 * field for each BSS that corresponds to a
+			 * nontransmitted BSSID. The value is initialized to
+			 * 0 here so that the DTIM count would be somewhat
+			 * functional even if the driver were not to update
+			 * this. */
+			*eid++ = 0; /* DTIM Count */
 		} else {
 			/* Probe Request frame does not include DTIM Period and
 			 * DTIM Count fields. */
@@ -7010,11 +7801,12 @@
 			non_inherit_ie[ie_count++] = WLAN_EID_EXT_SUPP_RATES;
 		if (ie_count) {
 			*eid++ = WLAN_EID_EXTENSION;
-			*eid++ = 2 + ie_count;
+			*eid++ = 2 + ie_count + 1;
 			*eid++ = WLAN_EID_EXT_NON_INHERITANCE;
 			*eid++ = ie_count;
 			os_memcpy(eid, non_inherit_ie, ie_count);
 			eid += ie_count;
+			*eid++ = 0; /* No Element ID Extension List */
 		}
 
 		*eid_len_pos = (eid - eid_len_pos) - 1;
@@ -7099,8 +7891,8 @@
 		if (hapd->conf->rnr)
 			rnr_eid = hostapd_eid_nr_db(hapd, rnr_eid, &cur_len);
 		if (get_colocation_mode(hapd) == COLOCATED_LOWER_BAND)
-			rnr_eid = hostapd_eid_rnr_colocation(hapd, rnr_eid,
-							     &cur_len);
+			rnr_eid = hostapd_eid_rnr_multi_iface(hapd, rnr_eid,
+							      &cur_len);
 	}
 
 	return eid;
diff --git a/src/ap/ieee802_11.h b/src/ap/ieee802_11.h
index 1190a5e..4b58fee 100644
--- a/src/ap/ieee802_11.h
+++ b/src/ap/ieee802_11.h
@@ -19,6 +19,10 @@
 struct radius_sta;
 enum ieee80211_op_mode;
 enum oper_chan_width;
+struct ieee802_11_elems;
+struct sae_pk;
+struct sae_pt;
+struct sae_password_entry;
 
 int ieee802_11_mgmt(struct hostapd_data *hapd, const u8 *buf, size_t len,
 		    struct hostapd_frame_info *fi);
@@ -84,13 +88,22 @@
 			   const struct ieee80211_eht_capabilities *src,
 			   struct ieee80211_eht_capabilities *dest,
 			   size_t len);
+u8 * hostapd_eid_eht_basic_ml(struct hostapd_data *hapd, u8 *eid,
+			      struct sta_info *info, bool include_mld_id);
+struct wpabuf * hostapd_ml_auth_resp(struct hostapd_data *hapd);
+const u8 * hostapd_process_ml_auth(struct hostapd_data *hapd,
+				   const struct ieee80211_mgmt *mgmt,
+				   size_t len);
+u16 hostapd_process_ml_assoc_req(struct hostapd_data *hapd,
+				 struct ieee802_11_elems *elems,
+				 struct sta_info *sta);
 int hostapd_get_aid(struct hostapd_data *hapd, struct sta_info *sta);
 u16 copy_sta_ht_capab(struct hostapd_data *hapd, struct sta_info *sta,
 		      const u8 *ht_capab);
 u16 copy_sta_vendor_vht(struct hostapd_data *hapd, struct sta_info *sta,
 			const u8 *ie, size_t len);
 
-void update_ht_state(struct hostapd_data *hapd, struct sta_info *sta);
+int update_ht_state(struct hostapd_data *hapd, struct sta_info *sta);
 void ht40_intolerant_add(struct hostapd_iface *iface, struct sta_info *sta);
 void ht40_intolerant_remove(struct hostapd_iface *iface, struct sta_info *sta);
 u16 copy_sta_vht_capab(struct hostapd_data *hapd, struct sta_info *sta,
@@ -177,7 +190,8 @@
 			   u8 *owe_buf, size_t owe_buf_len, u16 *status);
 u16 owe_process_rsn_ie(struct hostapd_data *hapd, struct sta_info *sta,
 		       const u8 *rsn_ie, size_t rsn_ie_len,
-		       const u8 *owe_dh, size_t owe_dh_len);
+		       const u8 *owe_dh, size_t owe_dh_len,
+		       const u8 *link_addr);
 u16 owe_validate_request(struct hostapd_data *hapd, const u8 *peer,
 			 const u8 *rsn_ie, size_t rsn_ie_len,
 			 const u8 *owe_dh, size_t owe_dh_len);
@@ -226,5 +240,10 @@
 			u8 *rnr_count, u8 **rnr_offset, size_t rnr_len);
 void punct_update_legacy_bw(u16 bitmap, u8 pri_chan,
 			    enum oper_chan_width *width, u8 *seg0, u8 *seg1);
+bool hostapd_is_mld_ap(struct hostapd_data *hapd);
+const char * sae_get_password(struct hostapd_data *hapd,
+			      struct sta_info *sta, const char *rx_id,
+			      struct sae_password_entry **pw_entry,
+			      struct sae_pt **s_pt, const struct sae_pk **s_pk);
 
 #endif /* IEEE802_11_H */
diff --git a/src/ap/ieee802_11_eht.c b/src/ap/ieee802_11_eht.c
index 6ebe0f9..c1ccda4 100644
--- a/src/ap/ieee802_11_eht.c
+++ b/src/ap/ieee802_11_eht.c
@@ -8,6 +8,8 @@
 
 #include "utils/includes.h"
 #include "utils/common.h"
+#include "crypto/crypto.h"
+#include "crypto/dh_groups.h"
 #include "hostapd.h"
 #include "sta_info.h"
 #include "ieee802_11.h"
@@ -417,3 +419,732 @@
 	os_memset(dest, 0, sizeof(*dest));
 	os_memcpy(dest, src, len);
 }
+
+
+u8 * hostapd_eid_eht_basic_ml(struct hostapd_data *hapd, u8 *eid,
+			      struct sta_info *info, bool include_mld_id)
+{
+	struct wpabuf *buf;
+	u16 control;
+	u8 *pos = eid;
+	const u8 *ptr;
+	size_t len, slice_len;
+	u8 link_id;
+	u8 common_info_len;
+
+	/*
+	 * As the Multi-Link element can exceed the size of 255 bytes need to
+	 * first build it and then handle fragmentation.
+	 */
+	buf = wpabuf_alloc(1024);
+	if (!buf)
+		return pos;
+
+	/* Multi-Link Control field */
+	control = MULTI_LINK_CONTROL_TYPE_BASIC |
+		BASIC_MULTI_LINK_CTRL_PRES_LINK_ID |
+		BASIC_MULTI_LINK_CTRL_PRES_BSS_PARAM_CH_COUNT |
+		BASIC_MULTI_LINK_CTRL_PRES_EML_CAPA |
+		BASIC_MULTI_LINK_CTRL_PRES_MLD_CAPA;
+
+	/*
+	 * Set the basic Multi-Link common information. Hard code the common
+	 * info length to 13 based on the length of the present fields:
+	 * Length (1) + MLD address (6) + Link ID (1) +
+	 * BSS Parameters Change Count (1) + EML Capabilities (2) +
+	 * MLD Capabilities and Operations (2)
+	 */
+	common_info_len = 13;
+
+	if (include_mld_id) {
+		/* AP MLD ID */
+		control |= BASIC_MULTI_LINK_CTRL_PRES_AP_MLD_ID;
+		common_info_len++;
+	}
+
+	wpabuf_put_le16(buf, control);
+
+	wpabuf_put_u8(buf, common_info_len);
+
+	/* Own MLD MAC Address */
+	wpabuf_put_data(buf, hapd->mld_addr, ETH_ALEN);
+
+	/* Own Link ID */
+	wpabuf_put_u8(buf, hapd->mld_link_id);
+
+	/* Currently hard code the BSS Parameters Change Count to 0x1 */
+	wpabuf_put_u8(buf, 0x1);
+
+	wpa_printf(MSG_DEBUG, "MLD: EML Capabilities=0x%x",
+		   hapd->iface->mld_eml_capa);
+	wpabuf_put_le16(buf, hapd->iface->mld_eml_capa);
+
+	wpa_printf(MSG_DEBUG, "MLD: MLD Capabilities and Operations=0x%x",
+		   hapd->iface->mld_mld_capa);
+	wpabuf_put_le16(buf, hapd->iface->mld_mld_capa);
+
+	if (include_mld_id) {
+		wpa_printf(MSG_DEBUG, "MLD: AP MLD ID=0x%x",
+			   hapd->conf->mld_id);
+		wpabuf_put_u8(buf, hapd->conf->mld_id);
+	}
+
+	if (!info)
+		goto out;
+
+	/* Add link info for the other links */
+	for (link_id = 0; link_id < MAX_NUM_MLD_LINKS; link_id++) {
+		struct mld_link_info *link = &info->mld_info.links[link_id];
+		struct hostapd_data *link_bss;
+
+		/*
+		 * control (2) + station info length (1) + MAC address (6) +
+		 * beacon interval (2) + TSF offset (8) + DTIM info (2) + BSS
+		 * parameters change counter (1) + station profile length.
+		 */
+		const size_t fixed_len = 22;
+		size_t total_len = fixed_len + link->resp_sta_profile_len;
+
+		/* Skip the local one */
+		if (link_id == hapd->mld_link_id || !link->valid)
+			continue;
+
+		link_bss = hostapd_mld_get_link_bss(hapd, link_id);
+		if (!link_bss) {
+			wpa_printf(MSG_ERROR,
+				   "MLD: Couldn't find link BSS - skip it");
+			continue;
+		}
+
+		/* Per-STA Profile subelement */
+		wpabuf_put_u8(buf, EHT_ML_SUB_ELEM_PER_STA_PROFILE);
+
+		if (total_len <= 255)
+			wpabuf_put_u8(buf, total_len);
+		else
+			wpabuf_put_u8(buf, 255);
+
+		/* STA Control */
+		control = (link_id & 0xf) |
+			EHT_PER_STA_CTRL_MAC_ADDR_PRESENT_MSK |
+			EHT_PER_STA_CTRL_COMPLETE_PROFILE_MSK |
+			EHT_PER_STA_CTRL_TSF_OFFSET_PRESENT_MSK |
+			EHT_PER_STA_CTRL_BEACON_INTERVAL_PRESENT_MSK |
+			EHT_PER_STA_CTRL_DTIM_INFO_PRESENT_MSK |
+			EHT_PER_STA_CTRL_BSS_PARAM_CNT_PRESENT_MSK;
+		wpabuf_put_le16(buf, control);
+
+		/* STA Info */
+
+		/* STA Info Length */
+		wpabuf_put_u8(buf, fixed_len - 2);
+		wpabuf_put_data(buf, link->local_addr, ETH_ALEN);
+		wpabuf_put_le16(buf, link_bss->iconf->beacon_int);
+
+		/* TSF Offset */
+		/*
+		 * TODO: Currently setting TSF offset to zero. However, this
+		 * information needs to come from the driver.
+		 */
+		wpabuf_put_le64(buf, 0);
+
+		/* DTIM Info */
+		wpabuf_put_le16(buf, link_bss->conf->dtim_period);
+
+		/* BSS Parameters Change Count */
+		/* TODO: Currently hard code the BSS Parameters Change Count to
+		 * 0x1 */
+		wpabuf_put_u8(buf, 0x1);
+
+		/* Fragment the sub element if needed */
+		if (total_len <= 255) {
+			wpabuf_put_data(buf, link->resp_sta_profile,
+					link->resp_sta_profile_len);
+		} else {
+			ptr = link->resp_sta_profile;
+			len = link->resp_sta_profile_len;
+
+			slice_len = 255 - fixed_len;
+
+			wpabuf_put_data(buf, ptr, slice_len);
+			len -= slice_len;
+			ptr += slice_len;
+
+			while (len) {
+				if (len <= 255)
+					slice_len = len;
+				else
+					slice_len = 255;
+
+				wpabuf_put_u8(buf, EHT_ML_SUB_ELEM_FRAGMENT);
+				wpabuf_put_u8(buf, slice_len);
+				wpabuf_put_data(buf, ptr, slice_len);
+
+				len -= slice_len;
+				ptr += slice_len;
+			}
+		}
+	}
+
+out:
+	/* Fragment the Multi-Link element, if needed */
+	len = wpabuf_len(buf);
+	ptr = wpabuf_head(buf);
+
+	if (len <= 254)
+		slice_len = len;
+	else
+		slice_len = 254;
+
+	*pos++ = WLAN_EID_EXTENSION;
+	*pos++ = slice_len + 1;
+	*pos++ = WLAN_EID_EXT_MULTI_LINK;
+	os_memcpy(pos, ptr, slice_len);
+
+	ptr += slice_len;
+	pos += slice_len;
+	len -= slice_len;
+
+	while (len) {
+		if (len <= 255)
+			slice_len = len;
+		else
+			slice_len = 255;
+
+		*pos++ = WLAN_EID_FRAGMENT;
+		*pos++ = slice_len;
+		os_memcpy(pos, ptr, slice_len);
+
+		ptr += slice_len;
+		pos += slice_len;
+		len -= slice_len;
+	}
+
+	wpabuf_free(buf);
+	return pos;
+}
+
+
+struct wpabuf * hostapd_ml_auth_resp(struct hostapd_data *hapd)
+{
+	struct wpabuf *buf = wpabuf_alloc(12);
+
+	if (!buf)
+		return NULL;
+
+	wpabuf_put_u8(buf, WLAN_EID_EXTENSION);
+	wpabuf_put_u8(buf, 10);
+	wpabuf_put_u8(buf, WLAN_EID_EXT_MULTI_LINK);
+	wpabuf_put_le16(buf, MULTI_LINK_CONTROL_TYPE_BASIC);
+	wpabuf_put_u8(buf, ETH_ALEN + 1);
+	wpabuf_put_data(buf, hapd->mld_addr, ETH_ALEN);
+
+	return buf;
+}
+
+
+#ifdef CONFIG_SAE
+
+static const u8 *
+sae_commit_skip_fixed_fields(const struct ieee80211_mgmt *mgmt, size_t len,
+			     const u8 *pos, u16 status_code)
+{
+	u16 group;
+	size_t prime_len;
+	struct crypto_ec *ec;
+
+	if (status_code != WLAN_STATUS_SAE_HASH_TO_ELEMENT)
+		return pos;
+
+	/* SAE H2E commit message (group, scalar, FFE) */
+	if (len < 2) {
+		wpa_printf(MSG_DEBUG,
+			   "EHT: SAE Group is not present");
+		return NULL;
+	}
+
+	group = WPA_GET_LE16(pos);
+	pos += 2;
+
+	/* TODO: How to parse when the group is unknown? */
+	ec = crypto_ec_init(group);
+	if (!ec) {
+		const struct dh_group *dh = dh_groups_get(group);
+
+		if (!dh) {
+			wpa_printf(MSG_DEBUG, "EHT: Unknown SAE group %u",
+				   group);
+			return NULL;
+		}
+
+		prime_len = dh->prime_len;
+	} else {
+		prime_len = crypto_ec_prime_len(ec);
+	}
+
+	wpa_printf(MSG_DEBUG, "EHT: SAE scalar length is %zu", prime_len);
+
+	/* scalar */
+	pos += prime_len;
+
+	if (ec) {
+		pos += prime_len * 2;
+		crypto_ec_deinit(ec);
+	} else {
+		pos += prime_len;
+	}
+
+	if (pos - mgmt->u.auth.variable > (int) len) {
+		wpa_printf(MSG_DEBUG,
+			   "EHT: Too short SAE commit Authentication frame");
+		return NULL;
+	}
+
+	wpa_hexdump(MSG_DEBUG, "EHT: SAE: Authentication frame elements",
+		    pos, (int) len - (pos - mgmt->u.auth.variable));
+
+	return pos;
+}
+
+
+static const u8 *
+sae_confirm_skip_fixed_fields(struct hostapd_data *hapd,
+			      const struct ieee80211_mgmt *mgmt, size_t len,
+			      const u8 *pos, u16 status_code)
+{
+	struct sta_info *sta;
+
+	if (status_code == WLAN_STATUS_REJECTED_WITH_SUGGESTED_BSS_TRANSITION)
+		return pos;
+
+	/* send confirm integer */
+	pos += 2;
+
+	/*
+	 * At this stage we should already have an MLD station and actually SA
+	 * will be replaced with the MLD MAC address by the driver.
+	 */
+	sta = ap_get_sta(hapd, mgmt->sa);
+	if (!sta) {
+		wpa_printf(MSG_DEBUG, "SAE: No MLD STA for SAE confirm");
+		return NULL;
+	}
+
+	if (!sta->sae || sta->sae->state < SAE_COMMITTED || !sta->sae->tmp) {
+		if (sta->sae)
+			wpa_printf(MSG_DEBUG, "SAE: Invalid state=%u",
+				   sta->sae->state);
+		else
+			wpa_printf(MSG_DEBUG, "SAE: No SAE context");
+		return NULL;
+	}
+
+	wpa_printf(MSG_DEBUG, "SAE: confirm: kck_len=%zu",
+		   sta->sae->tmp->kck_len);
+
+	pos += sta->sae->tmp->kck_len;
+
+	if (pos - mgmt->u.auth.variable > (int) len) {
+		wpa_printf(MSG_DEBUG,
+			   "EHT: Too short SAE confirm Authentication frame");
+		return NULL;
+	}
+
+	return pos;
+}
+
+#endif /* CONFIG_SAE */
+
+
+static const u8 * auth_skip_fixed_fields(struct hostapd_data *hapd,
+					 const struct ieee80211_mgmt *mgmt,
+					 size_t len)
+{
+	u16 auth_alg = le_to_host16(mgmt->u.auth.auth_alg);
+#ifdef CONFIG_SAE
+	u16 auth_transaction = le_to_host16(mgmt->u.auth.auth_transaction);
+	u16 status_code = le_to_host16(mgmt->u.auth.status_code);
+#endif /* CONFIG_SAE */
+	const u8 *pos = mgmt->u.auth.variable;
+
+	/* Skip fixed fields as based on IEE P802.11-REVme/D3.0, Table 9-69
+	 * (Presence of fields and elements in Authentications frames) */
+	switch (auth_alg) {
+	case WLAN_AUTH_OPEN:
+		return pos;
+#ifdef CONFIG_SAE
+	case WLAN_AUTH_SAE:
+		if (auth_transaction == 1) {
+			if (status_code == WLAN_STATUS_SUCCESS) {
+				wpa_printf(MSG_DEBUG,
+					   "EHT: SAE H2E is mandatory for MLD");
+				goto out;
+			}
+
+			return sae_commit_skip_fixed_fields(mgmt, len, pos,
+							    status_code);
+		} else if (auth_transaction == 2) {
+			return sae_confirm_skip_fixed_fields(hapd, mgmt, len,
+							     pos, status_code);
+		}
+
+		return pos;
+#endif /* CONFIG_SAE */
+	/* TODO: Support additional algorithms that can be used for MLO */
+	case WLAN_AUTH_FT:
+	case WLAN_AUTH_FILS_SK:
+	case WLAN_AUTH_FILS_SK_PFS:
+	case WLAN_AUTH_FILS_PK:
+	case WLAN_AUTH_PASN:
+	default:
+		break;
+	}
+
+#ifdef CONFIG_SAE
+out:
+#endif /* CONFIG_SAE */
+	wpa_printf(MSG_DEBUG,
+		   "TODO: Authentication algorithm %u not supported with MLD",
+		   auth_alg);
+	return NULL;
+}
+
+
+const u8 * hostapd_process_ml_auth(struct hostapd_data *hapd,
+				   const struct ieee80211_mgmt *mgmt,
+				   size_t len)
+{
+	struct ieee802_11_elems elems;
+	const u8 *pos;
+
+	if (!hapd->conf->mld_ap)
+		return NULL;
+
+	len -= offsetof(struct ieee80211_mgmt, u.auth.variable);
+
+	pos = auth_skip_fixed_fields(hapd, mgmt, len);
+	if (!pos)
+		return NULL;
+
+	if (ieee802_11_parse_elems(pos,
+				   (int)len - (pos - mgmt->u.auth.variable),
+				   &elems, 0) == ParseFailed) {
+		wpa_printf(MSG_DEBUG,
+			   "MLD: Failed parsing Authentication frame");
+	}
+
+	if (!elems.basic_mle || !elems.basic_mle_len)
+		return NULL;
+
+	return get_basic_mle_mld_addr(elems.basic_mle, elems.basic_mle_len);
+}
+
+
+static int hostapd_mld_validate_assoc_info(struct hostapd_data *hapd,
+					   struct mld_info *info)
+{
+	u8 i, link_id;
+
+	if (!info->mld_sta) {
+		wpa_printf(MSG_DEBUG, "MLD: Not a non-AP MLD");
+		return 0;
+	}
+
+	/*
+	 * Iterate over the links negotiated in the (Re)Association Request
+	 * frame and validate that they are indeed valid links in the local AP
+	 * MLD.
+	 *
+	 * While at it, also update the local address for the links in the
+	 * mld_info, so it could be easily available for later flows, e.g., for
+	 * the RSN Authenticator, etc.
+	 */
+	for (link_id = 0; link_id < MAX_NUM_MLD_LINKS; link_id++) {
+		struct hostapd_data *other_hapd;
+
+		if (!info->links[link_id].valid)
+			continue;
+
+		for (i = 0; i < hapd->iface->interfaces->count; i++) {
+			other_hapd = hapd->iface->interfaces->iface[i]->bss[0];
+
+			if (hapd == other_hapd)
+				continue;
+
+			if (other_hapd->conf->mld_ap &&
+			    other_hapd->conf->mld_id == hapd->conf->mld_id &&
+			    link_id == other_hapd->mld_link_id)
+				break;
+		}
+
+		if (i == hapd->iface->interfaces->count &&
+		    link_id != hapd->mld_link_id) {
+			wpa_printf(MSG_DEBUG, "MLD: Invalid link ID=%u",
+				   link_id);
+			return -1;
+		}
+
+		if (i < hapd->iface->interfaces->count)
+			os_memcpy(info->links[link_id].local_addr,
+				  other_hapd->own_addr,
+				  ETH_ALEN);
+	}
+
+	return 0;
+}
+
+
+u16 hostapd_process_ml_assoc_req(struct hostapd_data *hapd,
+				 struct ieee802_11_elems *elems,
+				 struct sta_info *sta)
+{
+	struct wpabuf *mlbuf;
+	const struct ieee80211_eht_ml *ml;
+	const struct eht_ml_basic_common_info *common_info;
+	size_t ml_len, common_info_len;
+	struct mld_link_info *link_info;
+	struct mld_info *info = &sta->mld_info;
+	const u8 *pos;
+	int ret = -1;
+	u16 ml_control;
+
+	mlbuf = ieee802_11_defrag_mle(elems, MULTI_LINK_CONTROL_TYPE_BASIC);
+	if (!mlbuf)
+		return WLAN_STATUS_SUCCESS;
+
+	ml = wpabuf_head(mlbuf);
+	ml_len = wpabuf_len(mlbuf);
+
+	ml_control = le_to_host16(ml->ml_control);
+	if ((ml_control & MULTI_LINK_CONTROL_TYPE_MASK) !=
+	    MULTI_LINK_CONTROL_TYPE_BASIC) {
+		wpa_printf(MSG_DEBUG, "MLD: Invalid ML type=%u",
+			   ml_control & MULTI_LINK_CONTROL_TYPE_MASK);
+		goto out;
+	}
+
+	/* Common Info length and MLD MAC address must always be present */
+	common_info_len = 1 + ETH_ALEN;
+
+	if (ml_control & BASIC_MULTI_LINK_CTRL_PRES_LINK_ID) {
+		wpa_printf(MSG_DEBUG, "MLD: Link ID info not expected");
+		goto out;
+	}
+
+	if (ml_control & BASIC_MULTI_LINK_CTRL_PRES_BSS_PARAM_CH_COUNT) {
+		wpa_printf(MSG_DEBUG, "MLD: BSS params change not expected");
+		goto out;
+	}
+
+	if (ml_control & BASIC_MULTI_LINK_CTRL_PRES_MSD_INFO) {
+		wpa_printf(MSG_DEBUG, "MLD: Sync delay not expected");
+		goto out;
+	}
+
+	if (ml_control & BASIC_MULTI_LINK_CTRL_PRES_EML_CAPA) {
+		common_info_len += 2;
+	} else {
+		wpa_printf(MSG_DEBUG, "MLD: EML capabilities not present");
+	}
+
+	if (ml_control & BASIC_MULTI_LINK_CTRL_PRES_MLD_CAPA) {
+		common_info_len += 2;
+
+	} else {
+		wpa_printf(MSG_DEBUG, "MLD: MLD capabilities not present");
+		goto out;
+	}
+
+	wpa_printf(MSG_DEBUG, "MLD: expected_common_info_len=%zu",
+		   common_info_len);
+
+	if (sizeof(*ml) + common_info_len > ml_len) {
+		wpa_printf(MSG_DEBUG, "MLD: Not enough bytes for common info");
+		goto out;
+	}
+
+	common_info = (const struct eht_ml_basic_common_info *) ml->variable;
+
+	/* Common information length includes the length octet */
+	if (common_info->len != common_info_len) {
+		wpa_printf(MSG_DEBUG,
+			   "MLD: Invalid common info len=%u (expected %zu)",
+			   common_info->len, common_info_len);
+		goto out;
+	}
+
+	pos = common_info->variable;
+
+	if (ml_control & BASIC_MULTI_LINK_CTRL_PRES_EML_CAPA) {
+		info->common_info.eml_capa = WPA_GET_LE16(pos);
+		pos += 2;
+	} else {
+		info->common_info.eml_capa = 0;
+	}
+
+	info->common_info.mld_capa = WPA_GET_LE16(pos);
+	pos += 2;
+
+	wpa_printf(MSG_DEBUG, "MLD: addr=" MACSTR ", eml=0x%x, mld=0x%x",
+		   MAC2STR(info->common_info.mld_addr),
+		   info->common_info.eml_capa, info->common_info.mld_capa);
+
+	/* Check the MLD MAC Address */
+	if (os_memcmp(info->common_info.mld_addr, common_info->mld_addr,
+		      ETH_ALEN) != 0) {
+		wpa_printf(MSG_DEBUG,
+			   "MLD: MLD address mismatch between authentication ("
+			   MACSTR ") and association (" MACSTR ")",
+			   MAC2STR(info->common_info.mld_addr),
+			   MAC2STR(common_info->mld_addr));
+		goto out;
+	}
+
+	info->links[hapd->mld_link_id].valid = true;
+
+	/* Parse the link info field */
+	ml_len -= sizeof(*ml) + common_info_len;
+
+	while (ml_len > 2) {
+		size_t sub_elem_len = *(pos + 1);
+		size_t sta_info_len;
+		u16 control;
+
+		wpa_printf(MSG_DEBUG, "MLD: sub element len=%zu",
+			   sub_elem_len);
+
+		if (2 + sub_elem_len > ml_len) {
+			wpa_printf(MSG_DEBUG,
+				   "MLD: Invalid link info len: %zu %zu",
+				   2 + sub_elem_len, ml_len);
+			goto out;
+		}
+
+		if (*pos == MULTI_LINK_SUB_ELEM_ID_VENDOR) {
+			wpa_printf(MSG_DEBUG,
+				   "MLD: Skip vendor specific subelement");
+
+			pos += 2 + sub_elem_len;
+			ml_len -= 2 + sub_elem_len;
+			continue;
+		}
+
+		if (*pos != MULTI_LINK_SUB_ELEM_ID_PER_STA_PROFILE) {
+			wpa_printf(MSG_DEBUG,
+				   "MLD: Unexpected Multi-Link element subelement ID=%u",
+				   *pos);
+			goto out;
+		}
+
+		/* Skip the subelement ID and the length */
+		pos += 2;
+		ml_len -= 2;
+
+		/* Get the station control field */
+		if (sub_elem_len < 2) {
+			wpa_printf(MSG_DEBUG,
+				   "MLD: Too short Per-STA Profile subelement");
+			goto out;
+		}
+		control = WPA_GET_LE16(pos);
+		link_info = &info->links[control &
+					 EHT_PER_STA_CTRL_LINK_ID_MSK];
+		pos += 2;
+		ml_len -= 2;
+		sub_elem_len -= 2;
+
+		if (!(control & EHT_PER_STA_CTRL_COMPLETE_PROFILE_MSK)) {
+			wpa_printf(MSG_DEBUG,
+				   "MLD: Per-STA complete profile expected");
+			goto out;
+		}
+
+		if (!(control & EHT_PER_STA_CTRL_MAC_ADDR_PRESENT_MSK)) {
+			wpa_printf(MSG_DEBUG,
+				   "MLD: Per-STA MAC address not present");
+			goto out;
+		}
+
+		if ((control & (EHT_PER_STA_CTRL_BEACON_INTERVAL_PRESENT_MSK |
+				EHT_PER_STA_CTRL_DTIM_INFO_PRESENT_MSK))) {
+			wpa_printf(MSG_DEBUG,
+				   "MLD: Beacon/DTIM interval not expected");
+			goto out;
+		}
+
+		/* The length octet and the MAC address must be present */
+		sta_info_len = 1 + ETH_ALEN;
+
+		if (control & EHT_PER_STA_CTRL_NSTR_LINK_PAIR_PRESENT_MSK) {
+			if (control & EHT_PER_STA_CTRL_NSTR_BM_SIZE_MSK)
+				link_info->nstr_bitmap_len = 2;
+			else
+				link_info->nstr_bitmap_len = 1;
+		}
+
+		sta_info_len += link_info->nstr_bitmap_len;
+
+		if (sta_info_len > ml_len || sta_info_len != *pos ||
+		    sta_info_len > sub_elem_len) {
+			wpa_printf(MSG_DEBUG, "MLD: Invalid STA Info length");
+			goto out;
+		}
+
+		/* skip the length */
+		pos++;
+		ml_len--;
+
+		/* get the link address */
+		os_memcpy(link_info->peer_addr, pos, ETH_ALEN);
+		wpa_printf(MSG_DEBUG,
+			   "MLD: assoc: link id=%u, addr=" MACSTR,
+			   control & EHT_PER_STA_CTRL_LINK_ID_MSK,
+			   MAC2STR(link_info->peer_addr));
+
+		pos += ETH_ALEN;
+		ml_len -= ETH_ALEN;
+
+		/* Get the NSTR bitmap */
+		if (link_info->nstr_bitmap_len) {
+			os_memcpy(link_info->nstr_bitmap, pos,
+				  link_info->nstr_bitmap_len);
+			pos += link_info->nstr_bitmap_len;
+			ml_len -= link_info->nstr_bitmap_len;
+		}
+
+		sub_elem_len -= sta_info_len;
+
+		wpa_printf(MSG_DEBUG, "MLD: STA Profile len=%zu", sub_elem_len);
+		if (sub_elem_len > ml_len)
+			goto out;
+
+		if (sub_elem_len > 2)
+			link_info->capability = WPA_GET_LE16(pos);
+
+		pos += sub_elem_len;
+		ml_len -= sub_elem_len;
+
+		wpa_printf(MSG_DEBUG, "MLD: link ctrl=0x%x, " MACSTR
+			   ", nstr bitmap len=%zu",
+			   control, MAC2STR(link_info->peer_addr),
+			   link_info->nstr_bitmap_len);
+
+		link_info->valid = true;
+	}
+
+	if (ml_len) {
+		wpa_printf(MSG_DEBUG, "MLD: %zu bytes left after parsing. fail",
+			   ml_len);
+		goto out;
+	}
+
+	ret = hostapd_mld_validate_assoc_info(hapd, info);
+out:
+	wpabuf_free(mlbuf);
+	if (ret) {
+		os_memset(info, 0, sizeof(*info));
+		return WLAN_STATUS_UNSPECIFIED_FAILURE;
+	}
+
+	return WLAN_STATUS_SUCCESS;
+}
diff --git a/src/ap/ieee802_11_ht.c b/src/ap/ieee802_11_ht.c
index 59ecbdc..f90f125 100644
--- a/src/ap/ieee802_11_ht.c
+++ b/src/ap/ieee802_11_ht.c
@@ -479,15 +479,14 @@
 }
 
 
-void update_ht_state(struct hostapd_data *hapd, struct sta_info *sta)
+int update_ht_state(struct hostapd_data *hapd, struct sta_info *sta)
 {
 	if ((sta->flags & WLAN_STA_HT) && sta->ht_capabilities)
 		update_sta_ht(hapd, sta);
 	else
 		update_sta_no_ht(hapd, sta);
 
-	if (hostapd_ht_operation_update(hapd->iface) > 0)
-		ieee802_11_set_beacons(hapd->iface);
+	return hostapd_ht_operation_update(hapd->iface);
 }
 
 
diff --git a/src/ap/ieee802_1x.c b/src/ap/ieee802_1x.c
index 8b67669..052231e 100644
--- a/src/ap/ieee802_1x.c
+++ b/src/ap/ieee802_1x.c
@@ -95,39 +95,43 @@
 	if (sta->flags & WLAN_STA_PREAUTH) {
 		rsn_preauth_send(hapd, sta, buf, len);
 	} else {
+		int link_id = -1;
+
+#ifdef CONFIG_IEEE80211BE
+		link_id = hapd->conf->mld_ap ? hapd->mld_link_id : -1;
+#endif /* CONFIG_IEEE80211BE */
 		hostapd_drv_hapd_send_eapol(
 			hapd, sta->addr, buf, len,
-			encrypt, hostapd_sta_flags_to_drv(sta->flags));
+			encrypt, hostapd_sta_flags_to_drv(sta->flags), link_id);
 	}
 
 	os_free(buf);
 }
 
 
-void ieee802_1x_set_sta_authorized(struct hostapd_data *hapd,
-				   struct sta_info *sta, int authorized)
+static void ieee802_1x_set_authorized(struct hostapd_data *hapd,
+				      struct sta_info *sta,
+				      bool authorized, bool mld)
 {
 	int res;
 
 	if (sta->flags & WLAN_STA_PREAUTH)
 		return;
 
-	if (authorized) {
-		ap_sta_set_authorized(hapd, sta, 1);
-		res = hostapd_set_authorized(hapd, sta, 1);
-		hostapd_logger(hapd, sta->addr, HOSTAPD_MODULE_IEEE8021X,
-			       HOSTAPD_LEVEL_DEBUG, "authorizing port");
-	} else {
-		ap_sta_set_authorized(hapd, sta, 0);
-		res = hostapd_set_authorized(hapd, sta, 0);
-		hostapd_logger(hapd, sta->addr, HOSTAPD_MODULE_IEEE8021X,
-			       HOSTAPD_LEVEL_DEBUG, "unauthorizing port");
-	}
+	ap_sta_set_authorized(hapd, sta, authorized);
+	res = hostapd_set_authorized(hapd, sta, authorized);
+	hostapd_logger(hapd, sta->addr, HOSTAPD_MODULE_IEEE8021X,
+		       HOSTAPD_LEVEL_DEBUG, "%sauthorizing port",
+		       authorized ? "" : "un");
 
-	if (res && errno != ENOENT) {
+	if (!mld && res && errno != ENOENT) {
 		wpa_printf(MSG_DEBUG, "Could not set station " MACSTR
 			   " flags for kernel driver (errno=%d).",
 			   MAC2STR(sta->addr), errno);
+	} else if (mld && res) {
+		wpa_printf(MSG_DEBUG,
+			   "MLD: Could not set station " MACSTR " flags",
+			   MAC2STR(sta->addr));
 	}
 
 	if (authorized) {
@@ -137,6 +141,65 @@
 }
 
 
+static void ieee802_1x_ml_set_sta_authorized(struct hostapd_data *hapd,
+					     struct sta_info *sta,
+					     bool authorized)
+{
+#ifdef CONFIG_IEEE80211BE
+	unsigned int i, link_id;
+
+	if (!hostapd_is_mld_ap(hapd))
+		return;
+
+	/*
+	 * Authorizing the station should be done only in the station
+	 * performing the association
+	 */
+	if (authorized && hapd->mld_link_id != sta->mld_assoc_link_id)
+		return;
+
+	for (link_id = 0; link_id < MAX_NUM_MLD_LINKS; link_id++) {
+		struct mld_link_info *link = &sta->mld_info.links[link_id];
+
+		if (!link->valid)
+			continue;
+
+		for (i = 0; i < hapd->iface->interfaces->count; i++) {
+			struct sta_info *tmp_sta;
+			struct hostapd_data *tmp_hapd =
+				hapd->iface->interfaces->iface[i]->bss[0];
+
+			if (!tmp_hapd->conf->mld_ap ||
+			    hapd->conf->mld_id != tmp_hapd->conf->mld_id)
+				continue;
+
+			for (tmp_sta = tmp_hapd->sta_list; tmp_sta;
+			     tmp_sta = tmp_sta->next) {
+				if (tmp_sta == sta ||
+				    tmp_sta->mld_assoc_link_id !=
+				    sta->mld_assoc_link_id ||
+				    tmp_sta->aid != sta->aid)
+					continue;
+
+				ieee802_1x_set_authorized(tmp_hapd, tmp_sta,
+							  authorized, true);
+				break;
+			}
+		}
+	}
+#endif /* CONFIG_IEEE80211BE */
+}
+
+
+
+void ieee802_1x_set_sta_authorized(struct hostapd_data *hapd,
+				   struct sta_info *sta, int authorized)
+{
+	ieee802_1x_set_authorized(hapd, sta, authorized, false);
+	ieee802_1x_ml_set_sta_authorized(hapd, sta, !!authorized);
+}
+
+
 #ifdef CONFIG_WEP
 #ifndef CONFIG_FIPS
 #ifndef CONFIG_NO_RC4
@@ -2474,6 +2537,14 @@
 	struct eapol_auth_config conf;
 	struct eapol_auth_cb cb;
 
+	if (hapd->mld_first_bss) {
+		wpa_printf(MSG_DEBUG,
+			   "MLD: Using IEEE 802.1X state machine of the first BSS");
+
+		hapd->eapol_auth = hapd->mld_first_bss->eapol_auth;
+		return 0;
+	}
+
 	dl_list_init(&hapd->erp_keys);
 
 	os_memset(&conf, 0, sizeof(conf));
@@ -2558,6 +2629,14 @@
 
 void ieee802_1x_deinit(struct hostapd_data *hapd)
 {
+	if (hapd->mld_first_bss) {
+		wpa_printf(MSG_DEBUG,
+			   "MLD: Deinit IEEE 802.1X state machine of a non-first BSS");
+
+		hapd->eapol_auth = NULL;
+		return;
+	}
+
 #ifdef CONFIG_WEP
 	eloop_cancel_timeout(ieee802_1x_rekey, hapd, NULL);
 #endif /* CONFIG_WEP */
diff --git a/src/ap/pmksa_cache_auth.c b/src/ap/pmksa_cache_auth.c
index 32d291d..ee4232f 100644
--- a/src/ap/pmksa_cache_auth.c
+++ b/src/ap/pmksa_cache_auth.c
@@ -56,7 +56,9 @@
 	unsigned int hash;
 
 	pmksa->pmksa_count--;
-	pmksa->free_cb(entry, pmksa->ctx);
+
+	if (pmksa->free_cb)
+		pmksa->free_cb(entry, pmksa->ctx);
 
 	/* unlink from hash list */
 	hash = PMKID_HASH(entry->pmkid);
@@ -332,6 +334,10 @@
 		return NULL;
 	os_memcpy(entry->pmk, pmk, pmk_len);
 	entry->pmk_len = pmk_len;
+	if (kck && kck_len && kck_len < WPA_KCK_MAX_LEN) {
+		os_memcpy(entry->kck, kck, kck_len);
+		entry->kck_len = kck_len;
+	}
 	if (pmkid)
 		os_memcpy(entry->pmkid, pmkid, PMKID_LEN);
 	else if (akmp == WPA_KEY_MGMT_IEEE8021X_SUITE_B_192)
@@ -523,8 +529,17 @@
 				return entry;
 			continue;
 		}
-		rsn_pmkid(entry->pmk, entry->pmk_len, aa, spa, new_pmkid,
-			  entry->akmp);
+		if (entry->akmp == WPA_KEY_MGMT_IEEE8021X_SUITE_B_192 &&
+		    entry->kck_len > 0)
+			rsn_pmkid_suite_b_192(entry->kck, entry->kck_len,
+					      aa, spa, new_pmkid);
+		else if (wpa_key_mgmt_suite_b(entry->akmp) &&
+			 entry->kck_len > 0)
+		rsn_pmkid_suite_b(entry->kck, entry->kck_len, aa, spa,
+				  new_pmkid);
+		else
+			rsn_pmkid(entry->pmk, entry->pmk_len, aa, spa,
+				  new_pmkid, entry->akmp);
 		if (os_memcmp(new_pmkid, pmkid, PMKID_LEN) == 0)
 			return entry;
 	}
diff --git a/src/ap/pmksa_cache_auth.h b/src/ap/pmksa_cache_auth.h
index e3cee4a..e38e7ec 100644
--- a/src/ap/pmksa_cache_auth.h
+++ b/src/ap/pmksa_cache_auth.h
@@ -19,6 +19,8 @@
 	u8 pmkid[PMKID_LEN];
 	u8 pmk[PMK_LEN_MAX];
 	size_t pmk_len;
+	u8 kck[WPA_KCK_MAX_LEN];
+	size_t kck_len;
 	os_time_t expiration;
 	int akmp; /* WPA_KEY_MGMT_* */
 	u8 spa[ETH_ALEN];
diff --git a/src/ap/sta_info.c b/src/ap/sta_info.c
index 07100f2..a00f896 100644
--- a/src/ap/sta_info.c
+++ b/src/ap/sta_info.c
@@ -199,7 +199,7 @@
 
 	if ((sta->flags & WLAN_STA_WDS) ||
 	    (sta->flags & WLAN_STA_MULTI_AP &&
-	     !(hapd->conf->multi_ap & FRONTHAUL_BSS) &&
+	     (hapd->conf->multi_ap & BACKHAUL_BSS) &&
 	     !(sta->flags & WLAN_STA_WPS)))
 		hostapd_set_wds_sta(hapd, NULL, sta->addr, sta->aid, 0);
 
@@ -290,7 +290,7 @@
 #endif /* CONFIG_MESH */
 
 	if (set_beacon)
-		ieee802_11_set_beacons(hapd->iface);
+		ieee802_11_update_beacons(hapd->iface);
 
 	wpa_printf(MSG_DEBUG, "%s: cancel ap_handle_timer for " MACSTR,
 		   __func__, MAC2STR(sta->addr));
@@ -301,7 +301,15 @@
 	sae_clear_retransmit_timer(hapd, sta);
 
 	ieee802_1x_free_station(hapd, sta);
+
+#ifdef CONFIG_IEEE80211BE
+	if (!hapd->conf->mld_ap || !sta->mld_info.mld_sta ||
+	    hapd->mld_link_id == sta->mld_assoc_link_id)
+		wpa_auth_sta_deinit(sta->wpa_sm);
+#else
 	wpa_auth_sta_deinit(sta->wpa_sm);
+#endif /* CONFIG_IEEE80211BE */
+
 	rsn_preauth_free_station(hapd, sta);
 #ifndef CONFIG_NO_RADIUS
 	if (hapd->radius)
@@ -866,7 +874,14 @@
 			       ap_handle_timer, hapd, sta);
 	accounting_sta_stop(hapd, sta);
 	ieee802_1x_free_station(hapd, sta);
+#ifdef CONFIG_IEEE80211BE
+	if (!hapd->conf->mld_ap ||
+	    hapd->mld_link_id == sta->mld_assoc_link_id)
+		wpa_auth_sta_deinit(sta->wpa_sm);
+#else
 	wpa_auth_sta_deinit(sta->wpa_sm);
+#endif /* CONFIG_IEEE80211BE */
+
 	sta->wpa_sm = NULL;
 
 	sta->disassoc_reason = reason;
@@ -1071,6 +1086,12 @@
 	struct hostapd_vlan *vlan = NULL;
 	int ret;
 	int old_vlanid = sta->vlan_id_bound;
+	int mld_link_id = -1;
+
+#ifdef CONFIG_IEEE80211BE
+	if (hapd->conf->mld_ap)
+		mld_link_id = hapd->mld_link_id;
+#endif /* CONFIG_IEEE80211BE */
 
 	if ((sta->flags & WLAN_STA_WDS) && sta->vlan_id == 0) {
 		wpa_printf(MSG_DEBUG,
@@ -1128,7 +1149,8 @@
 	if (wpa_auth_sta_set_vlan(sta->wpa_sm, sta->vlan_id) < 0)
 		wpa_printf(MSG_INFO, "Failed to update VLAN-ID for WPA");
 
-	ret = hostapd_drv_set_sta_vlan(iface, hapd, sta->addr, sta->vlan_id);
+	ret = hostapd_drv_set_sta_vlan(iface, hapd, sta->addr, sta->vlan_id,
+				       mld_link_id);
 	if (ret < 0) {
 		hostapd_logger(hapd, sta->addr, HOSTAPD_MODULE_IEEE80211,
 			       HOSTAPD_LEVEL_DEBUG, "could not bind the STA "
@@ -1285,7 +1307,19 @@
 		return;
 
 	if (authorized) {
-		hostapd_prune_associations(hapd, sta->addr);
+		int mld_assoc_link_id = -1;
+
+#ifdef CONFIG_IEEE80211BE
+		if (hapd->conf->mld_ap && sta->mld_info.mld_sta) {
+			if (sta->mld_assoc_link_id == hapd->mld_link_id)
+				mld_assoc_link_id = sta->mld_assoc_link_id;
+			else
+				mld_assoc_link_id = -2;
+		}
+#endif /* CONFIG_IEEE80211BE */
+		if (mld_assoc_link_id != -2)
+			hostapd_prune_associations(hapd, sta->addr,
+						   mld_assoc_link_id);
 		sta->flags |= WLAN_STA_AUTHORIZED;
 	} else {
 		sta->flags &= ~WLAN_STA_AUTHORIZED;
@@ -1572,6 +1606,9 @@
 
 int ap_sta_re_add(struct hostapd_data *hapd, struct sta_info *sta)
 {
+	const u8 *mld_link_addr = NULL;
+	bool mld_link_sta = false;
+
 	/*
 	 * If a station that is already associated to the AP, is trying to
 	 * authenticate again, remove the STA entry, in order to make sure the
@@ -1579,6 +1616,16 @@
 	 * this, station's added_unassoc flag is cleared once the station has
 	 * completed association.
 	 */
+
+#ifdef CONFIG_IEEE80211BE
+	if (hapd->conf->mld_ap && sta->mld_info.mld_sta) {
+		u8 mld_link_id = hapd->mld_link_id;
+
+		mld_link_sta = sta->mld_assoc_link_id != mld_link_id;
+		mld_link_addr = sta->mld_info.links[mld_link_id].peer_addr;
+	}
+#endif /* CONFIG_IEEE80211BE */
+
 	ap_sta_set_authorized(hapd, sta, 0);
 	hostapd_drv_sta_remove(hapd, sta->addr);
 	sta->flags &= ~(WLAN_STA_ASSOC | WLAN_STA_AUTH | WLAN_STA_AUTHORIZED);
@@ -1587,7 +1634,8 @@
 			    sta->supported_rates,
 			    sta->supported_rates_len,
 			    0, NULL, NULL, NULL, 0, NULL, 0, NULL,
-			    sta->flags, 0, 0, 0, 0)) {
+			    sta->flags, 0, 0, 0, 0,
+			    mld_link_addr, mld_link_sta)) {
 		hostapd_logger(hapd, sta->addr,
 			       HOSTAPD_MODULE_IEEE80211,
 			       HOSTAPD_LEVEL_NOTICE,
diff --git a/src/ap/sta_info.h b/src/ap/sta_info.h
index 8433ff8..e2b9dde 100644
--- a/src/ap/sta_info.h
+++ b/src/ap/sta_info.h
@@ -69,6 +69,35 @@
 	enum frame_encryption encrypted;
 };
 
+#define EHT_ML_MAX_STA_PROF_LEN 1024
+struct mld_info {
+	bool mld_sta;
+
+	struct ml_common_info {
+		u8 mld_addr[ETH_ALEN];
+		u16 medium_sync_delay;
+		u16 eml_capa;
+		u16 mld_capa;
+	} common_info;
+
+	struct mld_link_info {
+		u8 valid;
+		u8 local_addr[ETH_ALEN];
+		u8 peer_addr[ETH_ALEN];
+
+		size_t nstr_bitmap_len;
+		u8 nstr_bitmap[2];
+
+		u16 capability;
+
+		u16 status;
+		size_t resp_sta_profile_len;
+		u8 resp_sta_profile[EHT_ML_MAX_STA_PROF_LEN];
+
+		const u8 *rsne, *rsnxe;
+	} links[MAX_NUM_MLD_LINKS];
+};
+
 struct sta_info {
 	struct sta_info *next; /* next entry in sta list */
 	struct sta_info *hnext; /* next entry in hash table list */
@@ -299,6 +328,11 @@
 #ifdef CONFIG_PASN
 	struct pasn_data *pasn;
 #endif /* CONFIG_PASN */
+
+#ifdef CONFIG_IEEE80211BE
+	struct mld_info mld_info;
+	u8 mld_assoc_link_id;
+#endif /* CONFIG_IEEE80211BE */
 };
 
 
diff --git a/src/ap/utils.c b/src/ap/utils.c
index bedad6e..e93e531 100644
--- a/src/ap/utils.c
+++ b/src/ap/utils.c
@@ -43,6 +43,7 @@
 struct prune_data {
 	struct hostapd_data *hapd;
 	const u8 *addr;
+	int mld_assoc_link_id;
 };
 
 static int prune_associations(struct hostapd_iface *iface, void *ctx)
@@ -72,6 +73,12 @@
 		if (!osta)
 			continue;
 
+#ifdef CONFIG_IEEE80211BE
+		if (data->mld_assoc_link_id >= 0 &&
+		    osta->mld_assoc_link_id == data->mld_assoc_link_id)
+			continue;
+#endif /* CONFIG_IEEE80211BE */
+
 		wpa_printf(MSG_INFO, "%s: Prune association for " MACSTR,
 			   ohapd->conf->iface, MAC2STR(osta->addr));
 		ap_sta_disassociate(ohapd, osta, WLAN_REASON_UNSPECIFIED);
@@ -84,15 +91,20 @@
  * hostapd_prune_associations - Remove extraneous associations
  * @hapd: Pointer to BSS data for the most recent association
  * @addr: Associated STA address
+ * @mld_assoc_link_id: MLD link id used for association or -1 for non MLO
  *
  * This function looks through all radios and BSS's for previous
  * (stale) associations of STA. If any are found they are removed.
  */
-void hostapd_prune_associations(struct hostapd_data *hapd, const u8 *addr)
+void hostapd_prune_associations(struct hostapd_data *hapd, const u8 *addr,
+				int mld_assoc_link_id)
 {
 	struct prune_data data;
+
 	data.hapd = hapd;
 	data.addr = addr;
+	data.mld_assoc_link_id = mld_assoc_link_id;
+
 	if (hapd->iface->interfaces &&
 	    hapd->iface->interfaces->for_each_interface)
 		hapd->iface->interfaces->for_each_interface(
diff --git a/src/ap/wpa_auth.c b/src/ap/wpa_auth.c
index 635a74a..a662201 100644
--- a/src/ap/wpa_auth.c
+++ b/src/ap/wpa_auth.c
@@ -29,6 +29,7 @@
 #include "drivers/driver.h"
 #include "ap_config.h"
 #include "ieee802_11.h"
+#include "sta_info.h"
 #include "wpa_auth.h"
 #include "pmksa_cache_auth.h"
 #include "wpa_auth_i.h"
@@ -84,12 +85,20 @@
 
 static const u8 * wpa_auth_get_aa(const struct wpa_state_machine *sm)
 {
+#ifdef CONFIG_IEEE80211BE
+	if (sm->mld_assoc_link_id >= 0)
+		return sm->own_mld_addr;
+#endif /* CONFIG_IEEE80211BE */
 	return sm->wpa_auth->addr;
 }
 
 
 static const u8 * wpa_auth_get_spa(const struct wpa_state_machine *sm)
 {
+#ifdef CONFIG_IEEE80211BE
+	if (sm->mld_assoc_link_id >= 0)
+		return sm->peer_mld_addr;
+#endif /* CONFIG_IEEE80211BE */
 	return sm->addr;
 }
 
@@ -699,6 +708,9 @@
 	sm->wpa_auth = wpa_auth;
 	sm->group = wpa_auth->group;
 	wpa_group_get(sm->wpa_auth, sm->group);
+#ifdef CONFIG_IEEE80211BE
+	sm->mld_assoc_link_id = -1;
+#endif /* CONFIG_IEEE80211BE */
 
 	return sm;
 }
@@ -1077,9 +1089,15 @@
 	const u8 *key_data;
 	size_t keyhdrlen, mic_len;
 	u8 *mic;
+	bool is_mld = false;
 
 	if (!wpa_auth || !wpa_auth->conf.wpa || !sm)
 		return;
+
+#ifdef CONFIG_IEEE80211BE
+	is_mld = sm->mld_assoc_link_id >= 0;
+#endif /* CONFIG_IEEE80211BE */
+
 	wpa_hexdump(MSG_MSGDUMP, "WPA: RX EAPOL data", data, data_len);
 
 	mic_len = wpa_mic_len(sm->wpa_key_mgmt, sm->pmk_len);
@@ -1149,6 +1167,11 @@
 		return;
 	}
 
+	/* TODO: Make this more robust for distinguising EAPOL-Key msg 2/4 from
+	 * 4/4. Secure=1 is used in msg 2/4 when doing PTK rekeying, so the
+	 * MLD mechanism here does not work without the somewhat undesired check
+	 * on wpa_ptk_state.. Would likely need to decrypt Key Data first to be
+	 * able to know which message this is in MLO cases.. */
 	if (key_info & WPA_KEY_INFO_REQUEST) {
 		msg = REQUEST;
 		msgtxt = "Request";
@@ -1157,7 +1180,9 @@
 		msgtxt = "2/2 Group";
 	} else if (key_data_length == 0 ||
 		   (mic_len == 0 && (key_info & WPA_KEY_INFO_ENCR_KEY_DATA) &&
-		    key_data_length == AES_BLOCK_SIZE)) {
+		    key_data_length == AES_BLOCK_SIZE) ||
+		   (is_mld && (key_info & WPA_KEY_INFO_SECURE) &&
+		    sm->wpa_ptk_state == WPA_PTK_PTKINITNEGOTIATING)) {
 		msg = PAIRWISE_4;
 		msgtxt = "4/4 Pairwise";
 	} else {
@@ -1779,6 +1804,15 @@
 }
 
 
+static int wpa_auth_get_sta_count(struct wpa_authenticator *wpa_auth)
+{
+	if (!wpa_auth->cb->get_sta_count)
+		return -1;
+
+	return wpa_auth->cb->get_sta_count(wpa_auth->cb_ctx);
+}
+
+
 static void wpa_send_eapol(struct wpa_authenticator *wpa_auth,
 			   struct wpa_state_machine *sm, int key_info,
 			   const u8 *key_rsc, const u8 *nonce,
@@ -1811,11 +1845,16 @@
 skip_tx:
 #endif /* CONFIG_TESTING_OPTIONS */
 
-	if (ctr == 1 && wpa_auth->conf.tx_status)
-		timeout_ms = pairwise ? eapol_key_timeout_first :
-			eapol_key_timeout_first_group;
-	else
+	if (ctr == 1 && wpa_auth->conf.tx_status) {
+		if (pairwise)
+			timeout_ms = eapol_key_timeout_first;
+		else if (wpa_auth_get_sta_count(wpa_auth) > 100)
+			timeout_ms = eapol_key_timeout_first_group * 2;
+		else
+			timeout_ms = eapol_key_timeout_first_group;
+	} else {
 		timeout_ms = eapol_key_timeout_subseq;
+	}
 	if (wpa_auth->conf.wpa_disable_eapol_key_retries &&
 	    (!pairwise || (key_info & WPA_KEY_INFO_MIC)))
 		timeout_ms = eapol_key_timeout_no_retrans;
@@ -2293,8 +2332,9 @@
 
 SM_STATE(WPA_PTK, PTKSTART)
 {
-	u8 buf[2 + RSN_SELECTOR_LEN + PMKID_LEN], *pmkid = NULL;
-	size_t pmkid_len = 0;
+	u8 buf[2 * (2 + RSN_SELECTOR_LEN) + PMKID_LEN + ETH_ALEN];
+	u8 *pmkid = NULL;
+	size_t kde_len = 0;
 	u16 key_info;
 
 	SM_ENTRY_MA(WPA_PTK, PTKSTART, wpa_ptk);
@@ -2332,7 +2372,7 @@
 	     wpa_key_mgmt_sae(sm->wpa_key_mgmt)) &&
 	    sm->wpa_key_mgmt != WPA_KEY_MGMT_OSEN) {
 		pmkid = buf;
-		pmkid_len = 2 + RSN_SELECTOR_LEN + PMKID_LEN;
+		kde_len = 2 + RSN_SELECTOR_LEN + PMKID_LEN;
 		pmkid[0] = WLAN_EID_VENDOR_SPECIFIC;
 		pmkid[1] = RSN_SELECTOR_LEN + PMKID_LEN;
 		RSN_SELECTOR_PUT(&pmkid[2], RSN_KEY_DATA_PMKID);
@@ -2400,12 +2440,24 @@
 		}
 	}
 	if (!pmkid)
-		pmkid_len = 0;
+		kde_len = 0;
+
+#ifdef CONFIG_IEEE80211BE
+	if (sm->mld_assoc_link_id >= 0) {
+		wpa_printf(MSG_DEBUG,
+			   "RSN: MLD: Add MAC Address KDE: kde_len=%zu",
+			   kde_len);
+		wpa_add_kde(buf + kde_len, RSN_KEY_DATA_MAC_ADDR,
+			    sm->own_mld_addr, ETH_ALEN, NULL, 0);
+		kde_len += 2 + RSN_SELECTOR_LEN + ETH_ALEN;
+	}
+#endif /* CONFIG_IEEE80211BE */
+
 	key_info = WPA_KEY_INFO_ACK | WPA_KEY_INFO_KEY_TYPE;
 	if (sm->pairwise_set && sm->wpa != WPA_VERSION_WPA)
 		key_info |= WPA_KEY_INFO_SECURE;
 	wpa_send_eapol(sm->wpa_auth, sm, key_info, NULL,
-		       sm->ANonce, pmkid, pmkid_len, 0, 0);
+		       sm->ANonce, kde_len ? buf : NULL, kde_len, 0, 0);
 }
 
 
@@ -3114,6 +3166,71 @@
 #endif /* CONFIG_OCV */
 
 
+static int wpa_auth_validate_ml_kdes_m2(struct wpa_state_machine *sm,
+					struct wpa_eapol_ie_parse *kde)
+{
+#ifdef CONFIG_IEEE80211BE
+	int i;
+	unsigned int n_links = 0;
+
+	if (sm->mld_assoc_link_id < 0)
+		return 0;
+
+	/* MLD MAC address must be the same */
+	if (!kde->mac_addr ||
+	    os_memcmp(kde->mac_addr, sm->peer_mld_addr, ETH_ALEN) != 0) {
+		wpa_printf(MSG_DEBUG, "RSN: MLD: Invalid MLD address");
+		return -1;
+	}
+
+	/* Find matching link ID and the MAC address for each link */
+	for (i = 0; i < MAX_NUM_MLD_LINKS; i++) {
+		if (!(kde->valid_mlo_links & BIT(i)))
+			continue;
+
+		/*
+		 * Each entry should contain the link information and the MAC
+		 * address.
+		 */
+		if (kde->mlo_link_len[i] != 1 + ETH_ALEN) {
+			wpa_printf(MSG_DEBUG,
+				   "RSN: MLD: Invalid MLO Link (ID %u) KDE len=%zu",
+				   i, kde->mlo_link_len[i]);
+			return -1;
+		}
+
+		if (!sm->mld_links[i].valid || i == sm->mld_assoc_link_id) {
+			wpa_printf(MSG_DEBUG,
+				   "RSN: MLD: Invalid link ID=%u", i);
+			return -1;
+		}
+
+		if (os_memcmp(sm->mld_links[i].peer_addr, kde->mlo_link[i] + 1,
+			      ETH_ALEN) != 0) {
+			wpa_printf(MSG_DEBUG,
+				   "RSN: MLD: invalid MAC address=" MACSTR
+				   " expected " MACSTR " (link ID %u)",
+				   MAC2STR(kde->mlo_link[i] + 1),
+				   MAC2STR(sm->mld_links[i].peer_addr), i);
+			return -1;
+		}
+
+		n_links++;
+	}
+
+	/* Must have the same number of MLO links (excluding the local one) */
+	if (n_links != sm->n_mld_affiliated_links) {
+		wpa_printf(MSG_DEBUG,
+			   "RSN: MLD: Expecting %u MLD links in msg 2, but got %u",
+			   sm->n_mld_affiliated_links, n_links);
+		return -1;
+	}
+#endif /* CONFIG_IEEE80211BE */
+
+	return 0;
+}
+
+
 SM_STATE(WPA_PTK, PTKCALCNEGOTIATING)
 {
 	struct wpa_authenticator *wpa_auth = sm->wpa_auth;
@@ -3384,6 +3501,12 @@
 	}
 #endif /* CONFIG_DPP2 */
 
+	if (wpa_auth_validate_ml_kdes_m2(sm, &kde) < 0) {
+		wpa_sta_disconnect(wpa_auth, sm->addr,
+				   WLAN_REASON_PREV_AUTH_NOT_VALID);
+		return;
+	}
+
 #ifdef CONFIG_IEEE80211R_AP
 	if (sm->wpa == WPA_VERSION_WPA2 && wpa_key_mgmt_ft(sm->wpa_key_mgmt)) {
 		/*
@@ -3478,6 +3601,11 @@
 	if (!sm->mgmt_frame_prot)
 		return pos;
 
+#ifdef CONFIG_IEEE80211BE
+	if (sm->mld_assoc_link_id >= 0)
+		return pos; /* Use per-link MLO KDEs instead */
+#endif /* CONFIG_IEEE80211BE */
+
 	igtk.keyid[0] = gsm->GN_igtk;
 	igtk.keyid[1] = 0;
 	if (gsm->wpa_group_state != WPA_GROUP_SETKEYSDONE ||
@@ -3599,6 +3727,346 @@
 #endif /* CONFIG_TESTING_OPTIONS */
 
 
+#ifdef CONFIG_IEEE80211BE
+
+void wpa_auth_ml_get_rsn_info(struct wpa_authenticator *a,
+			      struct wpa_auth_ml_link_rsn_info *info)
+{
+	info->rsn_ies = a->wpa_ie;
+	info->rsn_ies_len = a->wpa_ie_len;
+
+	wpa_printf(MSG_DEBUG, "RSN: MLD: link_id=%u, rsn_ies_len=%zu",
+		   info->link_id, info->rsn_ies_len);
+}
+
+
+static void wpa_auth_get_ml_rsn_info(struct wpa_authenticator *wpa_auth,
+				     struct wpa_auth_ml_rsn_info *info)
+{
+	if (!wpa_auth->cb->get_ml_rsn_info)
+		return;
+
+	wpa_auth->cb->get_ml_rsn_info(wpa_auth->cb_ctx, info);
+}
+
+
+void wpa_auth_ml_get_key_info(struct wpa_authenticator *a,
+			      struct wpa_auth_ml_link_key_info *info,
+			      bool mgmt_frame_prot, bool beacon_prot)
+{
+	struct wpa_group *gsm = a->group;
+	u8 rsc[WPA_KEY_RSC_LEN];
+
+	wpa_printf(MSG_DEBUG,
+		   "MLD: Get group key info: link_id=%u, IGTK=%u, BIGTK=%u",
+		   info->link_id, mgmt_frame_prot, beacon_prot);
+
+	info->gtkidx = gsm->GN & 0x03;
+	info->gtk = gsm->GTK[gsm->GN - 1];
+	info->gtk_len = gsm->GTK_len;
+
+	if (wpa_auth_get_seqnum(a, NULL, gsm->GN, rsc) < 0)
+		os_memset(info->pn, 0, sizeof(info->pn));
+	else
+		os_memcpy(info->pn, rsc, sizeof(info->pn));
+
+	if (!mgmt_frame_prot)
+		return;
+
+	info->igtkidx = gsm->GN_igtk;
+	info->igtk = gsm->IGTK[gsm->GN_igtk - 4];
+	info->igtk_len = wpa_cipher_key_len(a->conf.group_mgmt_cipher);
+
+	if (wpa_auth_get_seqnum(a, NULL, gsm->GN_igtk, rsc) < 0)
+		os_memset(info->ipn, 0, sizeof(info->ipn));
+	else
+		os_memcpy(info->ipn, rsc, sizeof(info->ipn));
+
+	if (!beacon_prot)
+		return;
+
+	info->bigtkidx = gsm->GN_bigtk;
+	info->bigtk = gsm->BIGTK[gsm->GN_bigtk - 6];
+
+	if (wpa_auth_get_seqnum(a, NULL, gsm->GN_bigtk, rsc) < 0)
+		os_memset(info->bipn, 0, sizeof(info->bipn));
+	else
+		os_memcpy(info->bipn, rsc, sizeof(info->bipn));
+}
+
+
+static void wpa_auth_get_ml_key_info(struct wpa_authenticator *wpa_auth,
+				     struct wpa_auth_ml_key_info *info)
+{
+	if (!wpa_auth->cb->get_ml_key_info)
+		return;
+
+	wpa_auth->cb->get_ml_key_info(wpa_auth->cb_ctx, info);
+}
+
+
+static size_t wpa_auth_ml_group_kdes_len(struct wpa_state_machine *sm)
+{
+	struct wpa_group *gsm = sm->group;
+	size_t gtk_len = gsm->GTK_len;
+	size_t igtk_len;
+	size_t kde_len;
+	unsigned int n_links;
+
+	if (sm->mld_assoc_link_id < 0)
+		return 0;
+
+	n_links = sm->n_mld_affiliated_links + 1;
+
+	/* MLO GTK KDE for each link */
+	kde_len = n_links * (2 + RSN_SELECTOR_LEN + 1 + 6 + gtk_len);
+
+	if (!sm->mgmt_frame_prot)
+		return kde_len;
+
+	/* MLO IGTK KDE for each link */
+	igtk_len = wpa_cipher_key_len(sm->wpa_auth->conf.group_mgmt_cipher);
+	kde_len += n_links * (2 + RSN_SELECTOR_LEN + 2 + 6 + 1 + igtk_len);
+
+	if (!sm->wpa_auth->conf.beacon_prot)
+		return kde_len;
+
+	/* MLO BIGTK KDE for each link */
+	kde_len += n_links * (2 + RSN_SELECTOR_LEN + 2 + 6 + 1 + igtk_len);
+
+	return kde_len;
+}
+
+
+static u8 * wpa_auth_ml_group_kdes(struct wpa_state_machine *sm, u8 *pos)
+{
+	struct wpa_auth_ml_key_info ml_key_info;
+	unsigned int i, link_id;
+
+	/* First fetch the key information from all the authenticators */
+	os_memset(&ml_key_info, 0, sizeof(ml_key_info));
+	ml_key_info.n_mld_links = sm->n_mld_affiliated_links + 1;
+
+	/*
+	 * Assume that management frame protection and beacon protection are the
+	 * same on all links.
+	 */
+	ml_key_info.mgmt_frame_prot = sm->mgmt_frame_prot;
+	ml_key_info.beacon_prot = sm->wpa_auth->conf.beacon_prot;
+
+	for (i = 0, link_id = 0; link_id < MAX_NUM_MLD_LINKS; link_id++) {
+		if (!sm->mld_links[link_id].valid)
+			continue;
+
+		ml_key_info.links[i++].link_id = link_id;
+	}
+
+	wpa_auth_get_ml_key_info(sm->wpa_auth, &ml_key_info);
+
+	/* Add MLO GTK KDEs */
+	for (i = 0, link_id = 0; link_id < MAX_NUM_MLD_LINKS; link_id++) {
+		if (!sm->mld_links[link_id].valid)
+			continue;
+
+		wpa_printf(MSG_DEBUG, "RSN: MLO GTK: link=%u", link_id);
+		wpa_hexdump_key(MSG_DEBUG, "RSN: MLO GTK",
+				ml_key_info.links[i].gtk,
+				ml_key_info.links[i].gtk_len);
+
+		*pos++ = WLAN_EID_VENDOR_SPECIFIC;
+		*pos++ = RSN_SELECTOR_LEN + 1 + 6 +
+			ml_key_info.links[i].gtk_len;
+
+		RSN_SELECTOR_PUT(pos, RSN_KEY_DATA_MLO_GTK);
+		pos += RSN_SELECTOR_LEN;
+
+		*pos++ = (ml_key_info.links[i].gtkidx & 0x3) | (link_id << 4);
+
+		os_memcpy(pos, ml_key_info.links[i].pn, 6);
+		pos += 6;
+
+		os_memcpy(pos, ml_key_info.links[i].gtk,
+			  ml_key_info.links[i].gtk_len);
+		pos += ml_key_info.links[i].gtk_len;
+
+		i++;
+	}
+
+	if (!sm->mgmt_frame_prot)
+		return pos;
+
+	/* Add MLO IGTK KDEs */
+	for (i = 0, link_id = 0; link_id < MAX_NUM_MLD_LINKS; link_id++) {
+		if (!sm->mld_links[link_id].valid)
+			continue;
+
+		wpa_printf(MSG_DEBUG, "RSN: MLO IGTK: link=%u", link_id);
+		wpa_hexdump_key(MSG_DEBUG, "RSN: MLO IGTK",
+				ml_key_info.links[i].igtk,
+				ml_key_info.links[i].igtk_len);
+
+		*pos++ = WLAN_EID_VENDOR_SPECIFIC;
+		*pos++ = RSN_SELECTOR_LEN + 2 + 1 +
+			sizeof(ml_key_info.links[i].ipn) +
+			ml_key_info.links[i].igtk_len;
+
+		RSN_SELECTOR_PUT(pos, RSN_KEY_DATA_MLO_IGTK);
+		pos += RSN_SELECTOR_LEN;
+
+		/* Add the Key ID */
+		*pos++ = ml_key_info.links[i].igtkidx;
+		*pos++ = 0;
+
+		/* Add the IPN */
+		os_memcpy(pos, ml_key_info.links[i].ipn,
+			  sizeof(ml_key_info.links[i].ipn));
+		pos += sizeof(ml_key_info.links[i].ipn);
+
+		*pos++ = ml_key_info.links[i].link_id << 4;
+
+		os_memcpy(pos, ml_key_info.links[i].igtk,
+			  ml_key_info.links[i].igtk_len);
+		pos += ml_key_info.links[i].igtk_len;
+
+		i++;
+	}
+
+	if (!sm->wpa_auth->conf.beacon_prot)
+		return pos;
+
+	/* Add MLO BIGTK KDEs */
+	for (i = 0, link_id = 0; link_id < MAX_NUM_MLD_LINKS; link_id++) {
+		if (!sm->mld_links[link_id].valid)
+			continue;
+
+		wpa_printf(MSG_DEBUG, "RSN: MLO BIGTK: link=%u", link_id);
+		wpa_hexdump_key(MSG_DEBUG, "RSN: MLO BIGTK",
+				ml_key_info.links[i].bigtk,
+				ml_key_info.links[i].igtk_len);
+
+		*pos++ = WLAN_EID_VENDOR_SPECIFIC;
+		*pos++ = RSN_SELECTOR_LEN + 2 + 1 +
+			sizeof(ml_key_info.links[i].bipn) +
+			ml_key_info.links[i].igtk_len;
+
+		RSN_SELECTOR_PUT(pos, RSN_KEY_DATA_MLO_BIGTK);
+		pos += RSN_SELECTOR_LEN;
+
+		/* Add the Key ID */
+		*pos++ = ml_key_info.links[i].bigtkidx;
+		*pos++ = 0;
+
+		/* Add the BIPN */
+		os_memcpy(pos, ml_key_info.links[i].bipn,
+			  sizeof(ml_key_info.links[i].bipn));
+		pos += sizeof(ml_key_info.links[i].bipn);
+
+		*pos++ = ml_key_info.links[i].link_id << 4;
+
+		os_memcpy(pos, ml_key_info.links[i].bigtk,
+			  ml_key_info.links[i].igtk_len);
+		pos += ml_key_info.links[i].igtk_len;
+
+		i++;
+	}
+
+	return pos;
+}
+
+#endif /* CONFIG_IEEE80211BE */
+
+
+static size_t wpa_auth_ml_kdes_len(struct wpa_state_machine *sm)
+{
+	size_t kde_len = 0;
+
+#ifdef CONFIG_IEEE80211BE
+	unsigned int link_id;
+
+	if (sm->mld_assoc_link_id < 0)
+		return 0;
+
+	/* For the MAC Address KDE */
+	kde_len = 2 + RSN_SELECTOR_LEN + ETH_ALEN;
+
+	/* MLO Link KDE for each link */
+	for (link_id = 0; link_id < MAX_NUM_MLD_LINKS; link_id++) {
+		if (!sm->mld_links[link_id].valid)
+			continue;
+
+		kde_len += 2 + RSN_SELECTOR_LEN + 1 + ETH_ALEN +
+			sm->mld_links[link_id].rsne_len +
+			sm->mld_links[link_id].rsnxe_len;
+	}
+
+	kde_len += wpa_auth_ml_group_kdes_len(sm);
+#endif /* CONFIG_IEEE80211BE */
+
+	return kde_len;
+}
+
+
+static u8 * wpa_auth_ml_kdes(struct wpa_state_machine *sm, u8 *pos)
+{
+#ifdef CONFIG_IEEE80211BE
+	u8 link_id;
+
+	if (sm->mld_assoc_link_id < 0)
+		return pos;
+
+	wpa_printf(MSG_DEBUG, "RSN: MLD: Adding MAC Address KDE");
+	pos = wpa_add_kde(pos, RSN_KEY_DATA_MAC_ADDR,
+			  sm->own_mld_addr, ETH_ALEN, NULL, 0);
+
+	for (link_id = 0; link_id < MAX_NUM_MLD_LINKS; link_id++) {
+		if (!sm->mld_links[link_id].valid)
+			continue;
+
+		wpa_printf(MSG_DEBUG,
+			   "RSN: MLO Link: link=%u, len=%zu", link_id,
+			   RSN_SELECTOR_LEN + 1 + ETH_ALEN +
+			   sm->mld_links[link_id].rsne_len +
+			   sm->mld_links[link_id].rsnxe_len);
+
+		*pos++ = WLAN_EID_VENDOR_SPECIFIC;
+		*pos++ = RSN_SELECTOR_LEN + 1 + ETH_ALEN +
+			sm->mld_links[link_id].rsne_len +
+			sm->mld_links[link_id].rsnxe_len;
+
+		RSN_SELECTOR_PUT(pos, RSN_KEY_DATA_MLO_LINK);
+		pos += RSN_SELECTOR_LEN;
+
+		/* Add the Link Information */
+		*pos = link_id;
+		if (sm->mld_links[link_id].rsne_len)
+			*pos |= RSN_MLO_LINK_KDE_LI_RSNE_INFO;
+		if (sm->mld_links[link_id].rsnxe_len)
+			*pos |= RSN_MLO_LINK_KDE_LI_RSNXE_INFO;
+
+		pos++;
+		os_memcpy(pos, sm->mld_links[link_id].own_addr, ETH_ALEN);
+		pos += ETH_ALEN;
+
+		if (sm->mld_links[link_id].rsne_len) {
+			os_memcpy(pos, sm->mld_links[link_id].rsne,
+				  sm->mld_links[link_id].rsne_len);
+			pos += sm->mld_links[link_id].rsne_len;
+		}
+
+		if (sm->mld_links[link_id].rsnxe_len) {
+			os_memcpy(pos, sm->mld_links[link_id].rsnxe,
+				  sm->mld_links[link_id].rsnxe_len);
+			pos += sm->mld_links[link_id].rsnxe_len;
+		}
+	}
+
+	pos = wpa_auth_ml_group_kdes(sm, pos);
+#endif /* CONFIG_IEEE80211BE */
+
+	return pos;
+}
+
+
 SM_STATE(WPA_PTK, PTKINITNEGOTIATING)
 {
 	u8 rsc[WPA_KEY_RSC_LEN], *_rsc, *gtk, *kde = NULL, *pos, stub_gtk[32];
@@ -3609,6 +4077,11 @@
 	u8 *wpa_ie_buf = NULL, *wpa_ie_buf2 = NULL;
 	u8 hdr[2];
 	struct wpa_auth_config *conf = &sm->wpa_auth->conf;
+#ifdef CONFIG_IEEE80211BE
+	bool is_mld = sm->mld_assoc_link_id >= 0;
+#else /* CONFIG_IEEE80211BE */
+	bool is_mld = false;
+#endif /* CONFIG_IEEE80211BE */
 
 	SM_ENTRY_MA(WPA_PTK, PTKINITNEGOTIATING, wpa_ptk);
 	sm->TimeoutEvt = false;
@@ -3714,6 +4187,7 @@
 		secure = 0;
 		gtk = NULL;
 		gtk_len = 0;
+		gtkidx = 0;
 		_rsc = NULL;
 		if (sm->rx_eapol_key_secure) {
 			/*
@@ -3757,13 +4231,17 @@
 		kde_len += 2 + RSN_SELECTOR_LEN + 2;
 #endif /* CONFIG_DPP2 */
 
+	kde_len += wpa_auth_ml_kdes_len(sm);
+
 	kde = os_malloc(kde_len);
 	if (!kde)
 		goto done;
 
 	pos = kde;
-	os_memcpy(pos, wpa_ie, wpa_ie_len);
-	pos += wpa_ie_len;
+	if (!is_mld) {
+		os_memcpy(pos, wpa_ie, wpa_ie_len);
+		pos += wpa_ie_len;
+	}
 #ifdef CONFIG_IEEE80211R_AP
 	if (wpa_key_mgmt_ft(sm->wpa_key_mgmt)) {
 		int res;
@@ -3787,7 +4265,7 @@
 		pos = wpa_add_kde(pos, RSN_KEY_DATA_KEYID, hdr, 2, NULL, 0);
 	}
 
-	if (gtk) {
+	if (gtk && !is_mld) {
 		hdr[0] = gtkidx & 0x03;
 		pos = wpa_add_kde(pos, RSN_KEY_DATA_GROUPKEY, hdr, 2,
 				  gtk, gtk_len);
@@ -3867,6 +4345,8 @@
 	}
 #endif /* CONFIG_DPP2 */
 
+	pos = wpa_auth_ml_kdes(sm, pos);
+
 	wpa_send_eapol(sm->wpa_auth, sm,
 		       (secure ? WPA_KEY_INFO_SECURE : 0) |
 		       (wpa_mic_len(sm->wpa_key_mgmt, sm->pmk_len) ?
@@ -3881,10 +4361,68 @@
 }
 
 
+static int wpa_auth_validate_ml_kdes_m4(struct wpa_state_machine *sm)
+{
+#ifdef CONFIG_IEEE80211BE
+	const struct ieee802_1x_hdr *hdr;
+	const struct wpa_eapol_key *key;
+	struct wpa_eapol_ie_parse kde;
+	const u8 *key_data, *mic;
+	u16 key_data_length;
+	size_t mic_len;
+
+	if (sm->mld_assoc_link_id < 0)
+		return 0;
+
+	/*
+	 * Note: last_rx_eapol_key length fields have already been validated in
+	 * wpa_receive().
+	 */
+	mic_len = wpa_mic_len(sm->wpa_key_mgmt, sm->pmk_len);
+
+	hdr = (const struct ieee802_1x_hdr *) sm->last_rx_eapol_key;
+	key = (const struct wpa_eapol_key *) (hdr + 1);
+	mic = (const u8 *) (key + 1);
+	key_data = mic + mic_len + 2;
+	key_data_length = WPA_GET_BE16(mic + mic_len);
+	if (key_data_length > sm->last_rx_eapol_key_len - sizeof(*hdr) -
+	    sizeof(*key) - mic_len - 2)
+		return -1;
+
+	if (wpa_parse_kde_ies(key_data, key_data_length, &kde) < 0) {
+		wpa_auth_vlogger(sm->wpa_auth, wpa_auth_get_spa(sm),
+				 LOGGER_INFO,
+				 "received EAPOL-Key msg 4/4 with invalid Key Data contents");
+		return -1;
+	}
+
+	/* MLD MAC address must be the same */
+	if (!kde.mac_addr ||
+	    os_memcmp(kde.mac_addr, sm->peer_mld_addr, ETH_ALEN) != 0) {
+		wpa_printf(MSG_DEBUG,
+			   "MLD: Mismatching or missing MLD address in EAPOL-Key msg 4/4");
+		return -1;
+	}
+
+	wpa_printf(MSG_DEBUG, "MLD: MLD address in EAPOL-Key msg 4/4: " MACSTR,
+		   MAC2STR(kde.mac_addr));
+#endif /* CONFIG_IEEE80211BE */
+
+	return 0;
+}
+
+
 SM_STATE(WPA_PTK, PTKINITDONE)
 {
 	SM_ENTRY_MA(WPA_PTK, PTKINITDONE, wpa_ptk);
 	sm->EAPOLKeyReceived = false;
+
+	if (wpa_auth_validate_ml_kdes_m4(sm) < 0) {
+		wpa_sta_disconnect(sm->wpa_auth, sm->addr,
+				   WLAN_REASON_PREV_AUTH_NOT_VALID);
+		return;
+	}
+
 	if (sm->Pair) {
 		enum wpa_alg alg = wpa_cipher_to_alg(sm->pairwise);
 		int klen = wpa_cipher_key_len(sm->pairwise);
@@ -4121,11 +4659,16 @@
 {
 	u8 rsc[WPA_KEY_RSC_LEN];
 	struct wpa_group *gsm = sm->group;
-	const u8 *kde;
+	const u8 *kde = NULL;
 	u8 *kde_buf = NULL, *pos, hdr[2];
 	size_t kde_len = 0;
 	u8 *gtk, stub_gtk[32];
 	struct wpa_auth_config *conf = &sm->wpa_auth->conf;
+	bool is_mld = false;
+
+#ifdef CONFIG_IEEE80211BE
+	is_mld = sm->mld_assoc_link_id >= 0;
+#endif /* CONFIG_IEEE80211BE */
 
 	SM_ENTRY_MA(WPA_PTK_GROUP, REKEYNEGOTIATING, wpa_ptk_group);
 
@@ -4160,7 +4703,8 @@
 			return;
 		gtk = stub_gtk;
 	}
-	if (sm->wpa == WPA_VERSION_WPA2) {
+
+	if (sm->wpa == WPA_VERSION_WPA2 && !is_mld) {
 		kde_len = 2 + RSN_SELECTOR_LEN + 2 + gsm->GTK_len +
 			ieee80211w_kde_len(sm) + ocv_oci_len(sm);
 		kde_buf = os_malloc(kde_len);
@@ -4179,6 +4723,18 @@
 			return;
 		}
 		kde_len = pos - kde;
+#ifdef CONFIG_IEEE80211BE
+	} else if (sm->wpa == WPA_VERSION_WPA2 && is_mld) {
+		kde_len = wpa_auth_ml_group_kdes_len(sm);
+		if (kde_len) {
+			kde_buf = os_malloc(kde_len);
+			if (!kde_buf)
+				return;
+
+			kde = pos = kde_buf;
+			wpa_auth_ml_group_kdes(sm, pos);
+		}
+#endif /* CONFIG_IEEE80211BE */
 	} else {
 		kde = gtk;
 		kde_len = gsm->GTK_len;
@@ -6050,3 +6606,81 @@
 
 	eloop_register_timeout(0, 0, wpa_sm_call_step, sm, NULL);
 }
+
+
+void wpa_auth_set_ml_info(struct wpa_state_machine *sm, const u8 *mld_addr,
+			  u8 mld_assoc_link_id, struct mld_info *info)
+{
+#ifdef CONFIG_IEEE80211BE
+	struct wpa_auth_ml_rsn_info ml_rsn_info;
+	unsigned int link_id, i;
+
+	if (!info)
+		return;
+
+	os_memset(sm->mld_links, 0, sizeof(sm->mld_links));
+
+	wpa_auth_logger(sm->wpa_auth, wpa_auth_get_spa(sm), LOGGER_DEBUG,
+			"MLD: Initialization");
+
+	os_memcpy(sm->own_mld_addr, mld_addr, ETH_ALEN);
+	os_memcpy(sm->peer_mld_addr, info->common_info.mld_addr, ETH_ALEN);
+
+	sm->mld_assoc_link_id = mld_assoc_link_id;
+
+	os_memset(&ml_rsn_info, 0, sizeof(ml_rsn_info));
+
+	for (i = 0, link_id = 0; link_id < MAX_NUM_MLD_LINKS; link_id++) {
+		struct mld_link_info *link = &info->links[link_id];
+		struct mld_link *sm_link = &sm->mld_links[link_id];
+
+		sm_link->valid = link->valid;
+		if (!link->valid)
+			continue;
+
+		os_memcpy(sm_link->peer_addr, link->peer_addr, ETH_ALEN);
+		os_memcpy(sm_link->own_addr, link->local_addr, ETH_ALEN);
+
+		wpa_printf(MSG_DEBUG,
+			   "WPA_AUTH: MLD: id=%u, addr=" MACSTR " peer=" MACSTR,
+			   link_id,
+			   MAC2STR(sm_link->own_addr),
+			   MAC2STR(sm_link->peer_addr));
+
+		if (link_id != mld_assoc_link_id)
+			sm->n_mld_affiliated_links++;
+
+		ml_rsn_info.links[i++].link_id = link_id;
+	}
+
+	ml_rsn_info.n_mld_links = i;
+
+	wpa_auth_get_ml_rsn_info(sm->wpa_auth, &ml_rsn_info);
+
+	for (i = 0; i < ml_rsn_info.n_mld_links; i++) {
+		struct mld_link *sm_link;
+		const u8 *rsn_ies;
+		u8 rsn_ies_len;
+
+		sm_link = &sm->mld_links[ml_rsn_info.links[i].link_id];
+		rsn_ies = ml_rsn_info.links[i].rsn_ies;
+		rsn_ies_len = ml_rsn_info.links[i].rsn_ies_len;
+
+		/* This should not really happen */
+		if (!rsn_ies || rsn_ies_len < 2 || rsn_ies[0] != WLAN_EID_RSN ||
+		    rsn_ies[1] + 2 > rsn_ies_len) {
+			wpa_printf(MSG_INFO, "WPA_AUTH: MLD: Invalid RSNE");
+			continue;
+		}
+
+		sm_link->rsne = rsn_ies;
+		sm_link->rsne_len = rsn_ies[1] + 2;
+
+		if (rsn_ies[1] + 2UL + 2UL < rsn_ies_len &&
+		    rsn_ies[rsn_ies[1] + 2] == WLAN_EID_RSNX) {
+			sm_link->rsnxe = rsn_ies + 2 + rsn_ies[1];
+			sm_link->rsnxe_len = sm_link->rsnxe[1] + 2;
+		}
+	}
+#endif /* CONFIG_IEEE80211BE */
+}
diff --git a/src/ap/wpa_auth.h b/src/ap/wpa_auth.h
index 3b32fe3..57fda8a 100644
--- a/src/ap/wpa_auth.h
+++ b/src/ap/wpa_auth.h
@@ -15,6 +15,7 @@
 #include "common/ieee802_11_defs.h"
 
 struct vlan_description;
+struct mld_info;
 
 #define MAX_OWN_IE_OVERRIDE 256
 
@@ -290,6 +291,40 @@
 	WPA_EAPOL_keyDone, WPA_EAPOL_inc_EapolFramesTx
 } wpa_eapol_variable;
 
+struct wpa_auth_ml_rsn_info {
+	unsigned int n_mld_links;
+
+	struct wpa_auth_ml_link_rsn_info {
+		unsigned int link_id;
+		const u8 *rsn_ies;
+		size_t rsn_ies_len;
+	} links[MAX_NUM_MLD_LINKS];
+};
+
+struct wpa_auth_ml_key_info {
+	unsigned int n_mld_links;
+	bool mgmt_frame_prot;
+	bool beacon_prot;
+
+	struct wpa_auth_ml_link_key_info {
+		u8 link_id;
+
+		u8 gtkidx;
+		u8 gtk_len;
+		u8 pn[6];
+		const u8 *gtk;
+
+		u8 igtkidx;
+		u8 igtk_len;
+		const u8 *igtk;
+		u8 ipn[6];
+
+		u8 bigtkidx;
+		const u8 *bigtk;
+		u8 bipn[6];
+	} links[MAX_NUM_MLD_LINKS];
+};
+
 struct wpa_auth_callbacks {
 	void (*logger)(void *ctx, const u8 *addr, logger_level level,
 		       const char *txt);
@@ -309,6 +344,7 @@
 	int (*get_seqnum)(void *ctx, const u8 *addr, int idx, u8 *seq);
 	int (*send_eapol)(void *ctx, const u8 *addr, const u8 *data,
 			  size_t data_len, int encrypt);
+	int (*get_sta_count)(void *ctx);
 	int (*for_each_sta)(void *ctx, int (*cb)(struct wpa_state_machine *sm,
 						 void *ctx), void *cb_ctx);
 	int (*for_each_auth)(void *ctx, int (*cb)(struct wpa_authenticator *a,
@@ -357,6 +393,11 @@
 	int (*set_ltf_keyseed)(void *ctx, const u8 *addr, const u8 *ltf_keyseed,
 			       size_t ltf_keyseed_len);
 #endif /* CONFIG_PASN */
+#ifdef CONFIG_IEEE80211BE
+	int (*get_ml_rsn_info)(void *ctx, struct wpa_auth_ml_rsn_info *info);
+	int (*get_ml_key_info)(void *ctx, struct wpa_auth_ml_key_info *info);
+#endif /* CONFIG_IEEE80211BE */
+	int (*get_drv_flags)(void *ctx, u64 *drv_flags, u64 *drv_flags2);
 };
 
 struct wpa_authenticator * wpa_init(const u8 *addr,
@@ -599,4 +640,12 @@
 
 void wpa_auth_sta_radius_psk_resp(struct wpa_state_machine *sm, bool success);
 
+void wpa_auth_set_ml_info(struct wpa_state_machine *sm, const u8 *mld_addr,
+			  u8 mld_assoc_link_id, struct mld_info *info);
+void wpa_auth_ml_get_rsn_info(struct wpa_authenticator *a,
+			      struct wpa_auth_ml_link_rsn_info *info);
+void wpa_auth_ml_get_key_info(struct wpa_authenticator *a,
+			      struct wpa_auth_ml_link_key_info *info,
+			      bool mgmt_frame_prot, bool beacon_prot);
+
 #endif /* WPA_AUTH_H */
diff --git a/src/ap/wpa_auth_ft.c b/src/ap/wpa_auth_ft.c
index 2402ad9..4b16f62 100644
--- a/src/ap/wpa_auth_ft.c
+++ b/src/ap/wpa_auth_ft.c
@@ -2398,7 +2398,7 @@
 	wpa_auth_get_seqnum(sm->wpa_auth, NULL, gsm->GN_bigtk, pos);
 	pos += 6;
 	*pos++ = bigtk_len;
-	bigtk = gsm->IGTK[gsm->GN_bigtk - 6];
+	bigtk = gsm->BIGTK[gsm->GN_bigtk - 6];
 	if (sm->wpa_key_mgmt == WPA_KEY_MGMT_OSEN) {
 		/*
 		 * Provide unique random BIGTK to each OSEN STA to prevent use
@@ -2805,7 +2805,7 @@
 
 	ric_start = pos;
 	if (wpa_ft_parse_ies(req_ies, req_ies_len, &parse,
-			     sm->wpa_key_mgmt) == 0 && parse.ric) {
+			     sm->wpa_key_mgmt, false) == 0 && parse.ric) {
 		pos = wpa_ft_process_ric(sm, pos, end, parse.ric,
 					 parse.ric_len);
 		if (auth_alg == WLAN_AUTH_FT)
@@ -2821,8 +2821,10 @@
 	} else {
 		res = wpa_write_rsnxe(&sm->wpa_auth->conf, rsnxe,
 				      sizeof(rsnxe_buf));
-		if (res < 0)
-			return NULL;
+		if (res < 0) {
+			pos = NULL;
+			goto fail;
+		}
 		rsnxe_len = res;
 	}
 #ifdef CONFIG_TESTING_OPTIONS
@@ -2851,17 +2853,23 @@
 		       rsnie, rsnie_len,
 		       ric_start, ric_start ? pos - ric_start : 0,
 		       rsnxe_len ? rsnxe : NULL, rsnxe_len,
+		       NULL,
 		       fte_mic) < 0) {
 		wpa_printf(MSG_DEBUG, "FT: Failed to calculate MIC");
-		return NULL;
+		pos = NULL;
+		goto fail;
 	}
 
 	os_free(sm->assoc_resp_ftie);
 	sm->assoc_resp_ftie = os_malloc(ftie_len);
-	if (!sm->assoc_resp_ftie)
-		return NULL;
+	if (!sm->assoc_resp_ftie) {
+		pos = NULL;
+		goto fail;
+	}
 	os_memcpy(sm->assoc_resp_ftie, ftie, ftie_len);
 
+fail:
+	wpa_ft_parse_ies_free(&parse);
 	return pos;
 }
 
@@ -3173,6 +3181,7 @@
 	const u8 *identity, *radius_cui;
 	size_t identity_len = 0, radius_cui_len = 0;
 	size_t pmk_r1_len, kdk_len, len;
+	int retval = WLAN_STATUS_UNSPECIFIED_FAILURE;
 
 	*resp_ies = NULL;
 	*resp_ies_len = 0;
@@ -3183,7 +3192,7 @@
 	wpa_hexdump(MSG_DEBUG, "FT: Received authentication frame IEs",
 		    ies, ies_len);
 
-	if (wpa_ft_parse_ies(ies, ies_len, &parse, 0)) {
+	if (wpa_ft_parse_ies(ies, ies_len, &parse, 0, false)) {
 		wpa_printf(MSG_DEBUG, "FT: Failed to parse FT IEs");
 		return WLAN_STATUS_UNSPECIFIED_FAILURE;
 	}
@@ -3194,17 +3203,20 @@
 		      sm->wpa_auth->conf.mobility_domain,
 		      MOBILITY_DOMAIN_ID_LEN) != 0) {
 		wpa_printf(MSG_DEBUG, "FT: Invalid MDIE");
-		return WLAN_STATUS_INVALID_MDIE;
+		retval = WLAN_STATUS_INVALID_MDIE;
+		goto out;
 	}
 
 	if (!parse.ftie || parse.ftie_len < sizeof(struct rsn_ftie)) {
 		wpa_printf(MSG_DEBUG, "FT: Invalid FTIE");
-		return WLAN_STATUS_INVALID_FTIE;
+		retval = WLAN_STATUS_INVALID_FTIE;
+		goto out;
 	}
 
 	if (parse.r0kh_id == NULL) {
 		wpa_printf(MSG_DEBUG, "FT: Invalid FTIE - no R0KH-ID");
-		return WLAN_STATUS_INVALID_FTIE;
+		retval = WLAN_STATUS_INVALID_FTIE;
+		goto out;
 	}
 
 	wpa_hexdump(MSG_DEBUG, "FT: STA R0KH-ID",
@@ -3214,11 +3226,12 @@
 
 	if (parse.rsn_pmkid == NULL) {
 		wpa_printf(MSG_DEBUG, "FT: No PMKID in RSNIE");
-		return WLAN_STATUS_INVALID_PMKID;
+		retval = WLAN_STATUS_INVALID_PMKID;
+		goto out;
 	}
 
 	if (wpa_ft_set_key_mgmt(sm, &parse) < 0)
-		return WLAN_STATUS_UNSPECIFIED_FAILURE;
+		goto out;
 
 	wpa_hexdump(MSG_DEBUG, "FT: Requested PMKR0Name",
 		    parse.rsn_pmkid, WPA_PMK_NAME_LEN);
@@ -3228,12 +3241,14 @@
 		if (wpa_derive_pmk_r1_name(parse.rsn_pmkid,
 					   sm->wpa_auth->conf.r1_key_holder,
 					   sm->addr, pmk_r1_name, PMK_LEN) < 0)
-			return WLAN_STATUS_UNSPECIFIED_FAILURE;
+			goto out;
 		if (wpa_ft_psk_pmk_r1(sm, pmk_r1_name, pmk_r1, &pairwise,
 				      &vlan, &identity, &identity_len,
 				      &radius_cui, &radius_cui_len,
-				      &session_timeout) < 0)
-			return WLAN_STATUS_INVALID_PMKID;
+				      &session_timeout) < 0) {
+			retval = WLAN_STATUS_INVALID_PMKID;
+			goto out;
+		}
 		pmk_r1_len = PMK_LEN;
 		wpa_printf(MSG_DEBUG,
 			   "FT: Generated PMK-R1 for FT-PSK locally");
@@ -3284,10 +3299,12 @@
 	if (wpa_ft_pull_pmk_r1(sm, ies, ies_len, parse.rsn_pmkid) < 0) {
 		wpa_printf(MSG_DEBUG,
 			   "FT: Did not have matching PMK-R1 and either unknown or blocked R0KH-ID or NAK from R0KH");
-		return WLAN_STATUS_INVALID_PMKID;
+		retval = WLAN_STATUS_INVALID_PMKID;
+		goto out;
 	}
 
-	return -1; /* Status pending */
+	retval = -1; /* Status pending */
+	goto out;
 
 pmk_r1_derived:
 	wpa_hexdump_key(MSG_DEBUG, "FT: Selected PMK-R1", pmk_r1, pmk_r1_len);
@@ -3299,7 +3316,7 @@
 	if (random_get_bytes(sm->ANonce, WPA_NONCE_LEN)) {
 		wpa_printf(MSG_DEBUG, "FT: Failed to get random data for "
 			   "ANonce");
-		return WLAN_STATUS_UNSPECIFIED_FAILURE;
+		goto out;
 	}
 
 	/* Now that we know the correct PMK-R1 length and as such, the length
@@ -3310,7 +3327,8 @@
 		ftie = (const struct rsn_ftie_sha512 *) parse.ftie;
 		if (!ftie || parse.ftie_len < sizeof(*ftie)) {
 			wpa_printf(MSG_DEBUG, "FT: Invalid FTIE");
-			return WLAN_STATUS_INVALID_FTIE;
+			retval = WLAN_STATUS_INVALID_FTIE;
+			goto out;
 		}
 
 		os_memcpy(sm->SNonce, ftie->snonce, WPA_NONCE_LEN);
@@ -3320,7 +3338,8 @@
 		ftie = (const struct rsn_ftie_sha384 *) parse.ftie;
 		if (!ftie || parse.ftie_len < sizeof(*ftie)) {
 			wpa_printf(MSG_DEBUG, "FT: Invalid FTIE");
-			return WLAN_STATUS_INVALID_FTIE;
+			retval = WLAN_STATUS_INVALID_FTIE;
+			goto out;
 		}
 
 		os_memcpy(sm->SNonce, ftie->snonce, WPA_NONCE_LEN);
@@ -3330,7 +3349,8 @@
 		ftie = (const struct rsn_ftie *) parse.ftie;
 		if (!ftie || parse.ftie_len < sizeof(*ftie)) {
 			wpa_printf(MSG_DEBUG, "FT: Invalid FTIE");
-			return WLAN_STATUS_INVALID_FTIE;
+			retval = WLAN_STATUS_INVALID_FTIE;
+			goto out;
 		}
 
 		os_memcpy(sm->SNonce, ftie->snonce, WPA_NONCE_LEN);
@@ -3352,14 +3372,14 @@
 			      sm->addr, sm->wpa_auth->addr, pmk_r1_name,
 			      &sm->PTK, ptk_name, parse.key_mgmt,
 			      pairwise, kdk_len) < 0)
-		return WLAN_STATUS_UNSPECIFIED_FAILURE;
+		goto out;
 
 #ifdef CONFIG_PASN
 	if (sm->wpa_auth->conf.secure_ltf &&
 	    ieee802_11_rsnx_capab(sm->rsnxe, WLAN_RSNX_CAPAB_SECURE_LTF) &&
 	    wpa_ltf_keyseed(&sm->PTK, parse.key_mgmt, pairwise)) {
 		wpa_printf(MSG_DEBUG, "FT: Failed to derive LTF keyseed");
-		return WLAN_STATUS_UNSPECIFIED_FAILURE;
+		goto out;
 	}
 #endif /* CONFIG_PASN */
 
@@ -3370,14 +3390,14 @@
 
 	if (wpa_ft_set_vlan(sm->wpa_auth, sm->addr, &vlan) < 0) {
 		wpa_printf(MSG_DEBUG, "FT: Failed to configure VLAN");
-		return WLAN_STATUS_UNSPECIFIED_FAILURE;
+		goto out;
 	}
 	if (wpa_ft_set_identity(sm->wpa_auth, sm->addr,
 				identity, identity_len) < 0 ||
 	    wpa_ft_set_radius_cui(sm->wpa_auth, sm->addr,
 				  radius_cui, radius_cui_len) < 0) {
 		wpa_printf(MSG_DEBUG, "FT: Failed to configure identity/CUI");
-		return WLAN_STATUS_UNSPECIFIED_FAILURE;
+		goto out;
 	}
 	wpa_ft_set_session_timeout(sm->wpa_auth, sm->addr, session_timeout);
 
@@ -3410,11 +3430,14 @@
 
 	*resp_ies_len = pos - *resp_ies;
 
-	return WLAN_STATUS_SUCCESS;
+	retval = WLAN_STATUS_SUCCESS;
+	goto out;
 fail:
 	os_free(*resp_ies);
 	*resp_ies = NULL;
-	return WLAN_STATUS_UNSPECIFIED_FAILURE;
+out:
+	wpa_ft_parse_ies_free(&parse);
+	return retval;
 }
 
 
@@ -3473,6 +3496,7 @@
 	const u8 *kck;
 	size_t kck_len;
 	struct wpa_auth_config *conf;
+	int retval = WLAN_STATUS_UNSPECIFIED_FAILURE;
 
 	if (sm == NULL)
 		return WLAN_STATUS_UNSPECIFIED_FAILURE;
@@ -3481,26 +3505,29 @@
 
 	wpa_hexdump(MSG_DEBUG, "FT: Reassoc Req IEs", ies, ies_len);
 
-	if (wpa_ft_parse_ies(ies, ies_len, &parse, sm->wpa_key_mgmt) < 0) {
+	if (wpa_ft_parse_ies(ies, ies_len, &parse, sm->wpa_key_mgmt,
+			     false) < 0) {
 		wpa_printf(MSG_DEBUG, "FT: Failed to parse FT IEs");
 		return WLAN_STATUS_UNSPECIFIED_FAILURE;
 	}
 
 	if (parse.rsn == NULL) {
 		wpa_printf(MSG_DEBUG, "FT: No RSNIE in Reassoc Req");
-		return WLAN_STATUS_UNSPECIFIED_FAILURE;
+		goto out;
 	}
 
 	if (parse.rsn_pmkid == NULL) {
 		wpa_printf(MSG_DEBUG, "FT: No PMKID in RSNIE");
-		return WLAN_STATUS_INVALID_PMKID;
+		retval = WLAN_STATUS_INVALID_PMKID;
+		goto out;
 	}
 
 	if (os_memcmp_const(parse.rsn_pmkid, sm->pmk_r1_name, WPA_PMK_NAME_LEN)
 	    != 0) {
 		wpa_printf(MSG_DEBUG, "FT: PMKID in Reassoc Req did not match "
 			   "with the PMKR1Name derived from auth request");
-		return WLAN_STATUS_INVALID_PMKID;
+		retval = WLAN_STATUS_INVALID_PMKID;
+		goto out;
 	}
 
 	mdie = (struct rsn_mdie *) parse.mdie;
@@ -3508,7 +3535,8 @@
 	    os_memcmp(mdie->mobility_domain, conf->mobility_domain,
 		      MOBILITY_DOMAIN_ID_LEN) != 0) {
 		wpa_printf(MSG_DEBUG, "FT: Invalid MDIE");
-		return WLAN_STATUS_INVALID_MDIE;
+		retval = WLAN_STATUS_INVALID_MDIE;
+		goto out;
 	}
 
 	if (sm->wpa_key_mgmt == WPA_KEY_MGMT_FT_SAE_EXT_KEY &&
@@ -3526,7 +3554,8 @@
 		wpa_printf(MSG_DEBUG,
 			   "FT: Invalid FTE (fte_mic_len=%zu mic_len=%zu)",
 			   parse.fte_mic_len, mic_len);
-		return WLAN_STATUS_INVALID_FTIE;
+		retval = WLAN_STATUS_INVALID_FTIE;
+		goto out;
 	}
 
 	if (os_memcmp(parse.fte_snonce, sm->SNonce, WPA_NONCE_LEN) != 0) {
@@ -3535,7 +3564,8 @@
 			    parse.fte_snonce, WPA_NONCE_LEN);
 		wpa_hexdump(MSG_DEBUG, "FT: Expected SNonce",
 			    sm->SNonce, WPA_NONCE_LEN);
-		return WLAN_STATUS_INVALID_FTIE;
+		retval = WLAN_STATUS_INVALID_FTIE;
+		goto out;
 	}
 
 	if (os_memcmp(parse.fte_anonce, sm->ANonce, WPA_NONCE_LEN) != 0) {
@@ -3544,12 +3574,14 @@
 			    parse.fte_anonce, WPA_NONCE_LEN);
 		wpa_hexdump(MSG_DEBUG, "FT: Expected ANonce",
 			    sm->ANonce, WPA_NONCE_LEN);
-		return WLAN_STATUS_INVALID_FTIE;
+		retval = WLAN_STATUS_INVALID_FTIE;
+		goto out;
 	}
 
 	if (parse.r0kh_id == NULL) {
 		wpa_printf(MSG_DEBUG, "FT: No R0KH-ID subelem in FTIE");
-		return WLAN_STATUS_INVALID_FTIE;
+		retval = WLAN_STATUS_INVALID_FTIE;
+		goto out;
 	}
 
 	if (parse.r0kh_id_len != sm->r0kh_id_len ||
@@ -3561,12 +3593,14 @@
 			    parse.r0kh_id, parse.r0kh_id_len);
 		wpa_hexdump(MSG_DEBUG, "FT: The current R0KH-ID",
 			    sm->r0kh_id, sm->r0kh_id_len);
-		return WLAN_STATUS_INVALID_FTIE;
+		retval = WLAN_STATUS_INVALID_FTIE;
+		goto out;
 	}
 
 	if (parse.r1kh_id == NULL) {
 		wpa_printf(MSG_DEBUG, "FT: No R1KH-ID subelem in FTIE");
-		return WLAN_STATUS_INVALID_FTIE;
+		retval = WLAN_STATUS_INVALID_FTIE;
+		goto out;
 	}
 
 	if (os_memcmp_const(parse.r1kh_id, conf->r1_key_holder,
@@ -3577,7 +3611,8 @@
 			    parse.r1kh_id, FT_R1KH_ID_LEN);
 		wpa_hexdump(MSG_DEBUG, "FT: Expected R1KH-ID",
 			    conf->r1_key_holder, FT_R1KH_ID_LEN);
-		return WLAN_STATUS_INVALID_FTIE;
+		retval = WLAN_STATUS_INVALID_FTIE;
+		goto out;
 	}
 
 	if (parse.rsn_pmkid == NULL ||
@@ -3585,7 +3620,8 @@
 	{
 		wpa_printf(MSG_DEBUG, "FT: No matching PMKR1Name (PMKID) in "
 			   "RSNIE (pmkid=%d)", !!parse.rsn_pmkid);
-		return WLAN_STATUS_INVALID_PMKID;
+		retval = WLAN_STATUS_INVALID_PMKID;
+		goto out;
 	}
 
 	count = 3;
@@ -3597,7 +3633,7 @@
 		wpa_printf(MSG_DEBUG, "FT: Unexpected IE count in MIC "
 			   "Control: received %u expected %u",
 			   parse.fte_elem_count, count);
-		return WLAN_STATUS_UNSPECIFIED_FAILURE;
+		goto out;
 	}
 
 	if (wpa_key_mgmt_fils(sm->wpa_key_mgmt)) {
@@ -3615,9 +3651,10 @@
 		       parse.ric, parse.ric_len,
 		       parse.rsnxe ? parse.rsnxe - 2 : NULL,
 		       parse.rsnxe ? parse.rsnxe_len + 2 : 0,
+		       NULL,
 		       mic) < 0) {
 		wpa_printf(MSG_DEBUG, "FT: Failed to calculate MIC");
-		return WLAN_STATUS_UNSPECIFIED_FAILURE;
+		goto out;
 	}
 
 	if (os_memcmp_const(mic, parse.fte_mic, mic_len) != 0) {
@@ -3636,7 +3673,8 @@
 		wpa_hexdump(MSG_MSGDUMP, "FT: RSNXE",
 			    parse.rsnxe ? parse.rsnxe - 2 : NULL,
 			    parse.rsnxe ? parse.rsnxe_len + 2 : 0);
-		return WLAN_STATUS_INVALID_FTIE;
+		retval = WLAN_STATUS_INVALID_FTIE;
+		goto out;
 	}
 
 	if (parse.fte_rsnxe_used &&
@@ -3645,7 +3683,8 @@
 	    !parse.rsnxe) {
 		wpa_printf(MSG_INFO,
 			   "FT: FTE indicated that STA uses RSNXE, but RSNXE was not included");
-		return -1; /* discard request */
+		retval = -1; /* discard request */
+		goto out;
 	}
 
 #ifdef CONFIG_OCV
@@ -3658,14 +3697,14 @@
 		if (wpa_channel_info(sm->wpa_auth, &ci) != 0) {
 			wpa_printf(MSG_WARNING,
 				   "Failed to get channel info to validate received OCI in (Re)Assoc Request");
-			return WLAN_STATUS_UNSPECIFIED_FAILURE;
+			goto out;
 		}
 
 		if (get_sta_tx_parameters(sm,
 					  channel_width_to_int(ci.chanwidth),
 					  ci.seg1_idx, &tx_chanwidth,
 					  &tx_seg1_idx) < 0)
-			return WLAN_STATUS_UNSPECIFIED_FAILURE;
+			goto out;
 
 		res = ocv_verify_tx_params(parse.oci, parse.oci_len, &ci,
 					   tx_chanwidth, tx_seg1_idx);
@@ -3681,12 +3720,16 @@
 					OCV_FAILURE "addr=" MACSTR
 					" frame=ft-reassoc-req error=%s",
 					MAC2STR(sm->addr), ocv_errorstr);
-			return WLAN_STATUS_INVALID_FTIE;
+			retval = WLAN_STATUS_INVALID_FTIE;
+			goto out;
 		}
 	}
 #endif /* CONFIG_OCV */
 
-	return WLAN_STATUS_SUCCESS;
+	retval = WLAN_STATUS_SUCCESS;
+out:
+	wpa_ft_parse_ies_free(&parse);
+	return retval;
 }
 
 
diff --git a/src/ap/wpa_auth_glue.c b/src/ap/wpa_auth_glue.c
index 9f6699b..82d79f2 100644
--- a/src/ap/wpa_auth_glue.c
+++ b/src/ap/wpa_auth_glue.c
@@ -513,7 +513,14 @@
 				       u8 *seq)
 {
 	struct hostapd_data *hapd = ctx;
-	return hostapd_get_seqnum(hapd->conf->iface, hapd, addr, idx, seq);
+	int link_id = -1;
+
+#ifdef CONFIG_IEEE80211BE
+	if (hapd->conf->mld_ap && idx)
+		link_id = hapd->mld_link_id;
+#endif /* CONFIG_IEEE80211BE */
+	return hostapd_get_seqnum(hapd->conf->iface, hapd, addr, idx, link_id,
+				  seq);
 }
 
 
@@ -524,6 +531,11 @@
 	struct hostapd_data *hapd = ctx;
 	struct sta_info *sta;
 	u32 flags = 0;
+	int link_id = -1;
+
+#ifdef CONFIG_IEEE80211BE
+	link_id = hapd->conf->mld_ap ? hapd->mld_link_id : -1;
+#endif /* CONFIG_IEEE80211BE */
 
 #ifdef CONFIG_TESTING_OPTIONS
 	if (hapd->ext_eapol_frame_io) {
@@ -541,11 +553,24 @@
 #endif /* CONFIG_TESTING_OPTIONS */
 
 	sta = ap_get_sta(hapd, addr);
-	if (sta)
+	if (sta) {
 		flags = hostapd_sta_flags_to_drv(sta->flags);
+#ifdef CONFIG_IEEE80211BE
+		if (sta->mld_info.mld_sta && (sta->flags & WLAN_STA_AUTHORIZED))
+			link_id = -1;
+#endif /* CONFIG_IEEE80211BE */
+	}
 
 	return hostapd_drv_hapd_send_eapol(hapd, addr, data, data_len,
-					   encrypt, flags);
+					   encrypt, flags, link_id);
+}
+
+
+static int hostapd_wpa_auth_get_sta_count(void *ctx)
+{
+	struct hostapd_data *hapd = ctx;
+
+	return hapd->num_sta;
 }
 
 
@@ -1488,6 +1513,110 @@
 #endif /* CONFIG_PASN */
 
 
+#ifdef CONFIG_IEEE80211BE
+
+static int hostapd_wpa_auth_get_ml_rsn_info(void *ctx,
+					    struct wpa_auth_ml_rsn_info *info)
+{
+	struct hostapd_data *hapd = ctx;
+	unsigned int i, j;
+
+	wpa_printf(MSG_DEBUG, "WPA_AUTH: MLD: Get RSN info CB: n_mld_links=%u",
+		   info->n_mld_links);
+
+	if (!hapd->conf->mld_ap || !hapd->iface || !hapd->iface->interfaces)
+		return -1;
+
+	for (i = 0; i < info->n_mld_links; i++) {
+		unsigned int link_id = info->links[i].link_id;
+
+		wpa_printf(MSG_DEBUG,
+			   "WPA_AUTH: MLD: Get link RSN CB: link_id=%u",
+			   link_id);
+
+		for (j = 0; j < hapd->iface->interfaces->count; j++) {
+			struct hostapd_iface *iface =
+				hapd->iface->interfaces->iface[j];
+
+			if (!iface->bss[0]->conf->mld_ap ||
+			    hapd->conf->mld_id != iface->bss[0]->conf->mld_id ||
+			    link_id != iface->bss[0]->mld_link_id)
+				continue;
+
+			wpa_auth_ml_get_rsn_info(iface->bss[0]->wpa_auth,
+						 &info->links[i]);
+			break;
+		}
+
+		if (j == hapd->iface->interfaces->count)
+			wpa_printf(MSG_DEBUG,
+				   "WPA_AUTH: MLD: link=%u not found", link_id);
+	}
+
+	return 0;
+}
+
+
+static int hostapd_wpa_auth_get_ml_key_info(void *ctx,
+					    struct wpa_auth_ml_key_info *info)
+{
+	struct hostapd_data *hapd = ctx;
+	unsigned int i, j;
+
+	wpa_printf(MSG_DEBUG, "WPA_AUTH: MLD: Get key info CB: n_mld_links=%u",
+		   info->n_mld_links);
+
+	if (!hapd->conf->mld_ap || !hapd->iface || !hapd->iface->interfaces)
+		return -1;
+
+	for (i = 0; i < info->n_mld_links; i++) {
+		u8 link_id = info->links[i].link_id;
+
+		wpa_printf(MSG_DEBUG,
+			   "WPA_AUTH: MLD: Get link info CB: link_id=%u",
+			   link_id);
+
+		for (j = 0; j < hapd->iface->interfaces->count; j++) {
+			struct hostapd_iface *iface =
+				hapd->iface->interfaces->iface[j];
+
+			if (!iface->bss[0]->conf->mld_ap ||
+			    hapd->conf->mld_id != iface->bss[0]->conf->mld_id ||
+			    link_id != iface->bss[0]->mld_link_id)
+				continue;
+
+			wpa_auth_ml_get_key_info(iface->bss[0]->wpa_auth,
+						 &info->links[i],
+						 info->mgmt_frame_prot,
+						 info->beacon_prot);
+			break;
+		}
+
+		if (j == hapd->iface->interfaces->count)
+			wpa_printf(MSG_DEBUG,
+				   "WPA_AUTH: MLD: link=%u not found", link_id);
+	}
+
+	return 0;
+}
+
+#endif /* CONFIG_IEEE80211BE */
+
+
+static int hostapd_wpa_auth_get_drv_flags(void *ctx,
+					  u64 *drv_flags, u64 *drv_flags2)
+{
+	struct hostapd_data *hapd = ctx;
+
+	if (drv_flags)
+		*drv_flags = hapd->iface->drv_flags;
+	if (drv_flags2)
+		*drv_flags2 = hapd->iface->drv_flags2;
+
+	return 0;
+}
+
+
 int hostapd_setup_wpa(struct hostapd_data *hapd)
 {
 	struct wpa_auth_config _conf;
@@ -1503,6 +1632,7 @@
 		.set_key = hostapd_wpa_auth_set_key,
 		.get_seqnum = hostapd_wpa_auth_get_seqnum,
 		.send_eapol = hostapd_wpa_auth_send_eapol,
+		.get_sta_count = hostapd_wpa_auth_get_sta_count,
 		.for_each_sta = hostapd_wpa_auth_for_each_sta,
 		.for_each_auth = hostapd_wpa_auth_for_each_auth,
 		.send_ether = hostapd_wpa_auth_send_ether,
@@ -1537,6 +1667,11 @@
 #ifdef CONFIG_PASN
 		.set_ltf_keyseed = hostapd_set_ltf_keyseed,
 #endif /* CONFIG_PASN */
+#ifdef CONFIG_IEEE80211BE
+		.get_ml_rsn_info = hostapd_wpa_auth_get_ml_rsn_info,
+		.get_ml_key_info = hostapd_wpa_auth_get_ml_key_info,
+#endif /* CONFIG_IEEE80211BE */
+		.get_drv_flags = hostapd_wpa_auth_get_drv_flags,
 	};
 	const u8 *wpa_ie;
 	size_t wpa_ie_len;
diff --git a/src/ap/wpa_auth_i.h b/src/ap/wpa_auth_i.h
index d401550..74ae5ad 100644
--- a/src/ap/wpa_auth_i.h
+++ b/src/ap/wpa_auth_i.h
@@ -172,6 +172,24 @@
 	void *eapol_status_cb_ctx1;
 	void *eapol_status_cb_ctx2;
 #endif /* CONFIG_TESTING_OPTIONS */
+
+#ifdef CONFIG_IEEE80211BE
+	u8 own_mld_addr[ETH_ALEN];
+	u8 peer_mld_addr[ETH_ALEN];
+	s8 mld_assoc_link_id;
+	u8 n_mld_affiliated_links;
+
+	struct mld_link {
+		bool valid;
+		u8 peer_addr[ETH_ALEN];
+		u8 own_addr[ETH_ALEN];
+
+		const u8 *rsne;
+		size_t rsne_len;
+		const u8 *rsnxe;
+		size_t rsnxe_len;
+	} mld_links[MAX_NUM_MLD_LINKS];
+#endif /* CONFIG_IEEE80211BE */
 };
 
 
diff --git a/src/ap/wpa_auth_ie.c b/src/ap/wpa_auth_ie.c
index 43ccec9..a5f2861 100644
--- a/src/ap/wpa_auth_ie.c
+++ b/src/ap/wpa_auth_ie.c
@@ -10,6 +10,7 @@
 
 #include "utils/common.h"
 #include "common/ieee802_11_defs.h"
+#include "drivers/driver.h"
 #include "eapol_auth/eapol_auth_sm.h"
 #include "ap_config.h"
 #include "ieee802_11.h"
@@ -212,6 +213,13 @@
 		num_suites++;
 	}
 #endif /* CONFIG_IEEE80211R_AP */
+#ifdef CONFIG_SHA384
+	if (conf->wpa_key_mgmt & WPA_KEY_MGMT_IEEE8021X_SHA384) {
+		RSN_SELECTOR_PUT(pos, RSN_AUTH_KEY_MGMT_802_1X_SHA384);
+		pos += RSN_SELECTOR_LEN;
+		num_suites++;
+	}
+#endif /* CONFIG_SHA384 */
 	if (conf->wpa_key_mgmt & WPA_KEY_MGMT_IEEE8021X_SHA256) {
 		RSN_SELECTOR_PUT(pos, RSN_AUTH_KEY_MGMT_802_1X_SHA256);
 		pos += RSN_SELECTOR_LEN;
@@ -705,6 +713,10 @@
 		else if (data.key_mgmt & WPA_KEY_MGMT_OSEN)
 			selector = RSN_AUTH_KEY_MGMT_OSEN;
 #endif /* CONFIG_HS20 */
+#ifdef CONFIG_SHA384
+		else if (data.key_mgmt & WPA_KEY_MGMT_IEEE8021X_SHA384)
+			selector = RSN_AUTH_KEY_MGMT_802_1X_SHA384;
+#endif /* CONFIG_SHA384 */
 		wpa_auth->dot11RSNAAuthenticationSuiteSelected = selector;
 
 		selector = wpa_cipher_to_suite(WPA_PROTO_RSN,
@@ -787,6 +799,10 @@
 	else if (key_mgmt & WPA_KEY_MGMT_FT_PSK)
 		sm->wpa_key_mgmt = WPA_KEY_MGMT_FT_PSK;
 #endif /* CONFIG_IEEE80211R_AP */
+#ifdef CONFIG_SHA384
+	else if (key_mgmt & WPA_KEY_MGMT_IEEE8021X_SHA384)
+		sm->wpa_key_mgmt = WPA_KEY_MGMT_IEEE8021X_SHA384;
+#endif /* CONFIG_SHA384 */
 	else if (key_mgmt & WPA_KEY_MGMT_IEEE8021X_SHA256)
 		sm->wpa_key_mgmt = WPA_KEY_MGMT_IEEE8021X_SHA256;
 	else if (key_mgmt & WPA_KEY_MGMT_PSK_SHA256)
@@ -998,11 +1014,24 @@
 	}
 
 #ifdef CONFIG_SAE
-	if (sm->wpa_key_mgmt == WPA_KEY_MGMT_SAE && data.num_pmkid &&
-	    !sm->pmksa) {
-		wpa_auth_vlogger(wpa_auth, sm->addr, LOGGER_DEBUG,
-				 "No PMKSA cache entry found for SAE");
-		return WPA_INVALID_PMKID;
+	if (sm->wpa_key_mgmt == WPA_KEY_MGMT_SAE ||
+	    sm->wpa_key_mgmt == WPA_KEY_MGMT_SAE_EXT_KEY) {
+		u64 drv_flags = 0;
+		u64 drv_flags2 = 0;
+		bool ap_sae_offload = false;
+
+		if (wpa_auth->cb->get_drv_flags &&
+		    wpa_auth->cb->get_drv_flags(wpa_auth->cb_ctx, &drv_flags,
+						&drv_flags2) == 0)
+			ap_sae_offload =
+				!!(drv_flags2 &
+				   WPA_DRIVER_FLAGS2_SAE_OFFLOAD_AP);
+
+		if (!ap_sae_offload && data.num_pmkid && !sm->pmksa) {
+			wpa_auth_vlogger(wpa_auth, sm->addr, LOGGER_DEBUG,
+					 "No PMKSA cache entry found for SAE");
+			return WPA_INVALID_PMKID;
+		}
 	}
 #endif /* CONFIG_SAE */
 
diff --git a/src/common/defs.h b/src/common/defs.h
index aa3c5cf..48d5d3c 100644
--- a/src/common/defs.h
+++ b/src/common/defs.h
@@ -52,6 +52,7 @@
 #define WPA_KEY_MGMT_PASN BIT(25)
 #define WPA_KEY_MGMT_SAE_EXT_KEY BIT(26)
 #define WPA_KEY_MGMT_FT_SAE_EXT_KEY BIT(27)
+#define WPA_KEY_MGMT_IEEE8021X_SHA384 BIT(28)
 
 
 #define WPA_KEY_MGMT_FT (WPA_KEY_MGMT_FT_PSK | \
@@ -75,7 +76,8 @@
 			 WPA_KEY_MGMT_FILS_SHA256 |
 			 WPA_KEY_MGMT_FILS_SHA384 |
 			 WPA_KEY_MGMT_FT_FILS_SHA256 |
-			 WPA_KEY_MGMT_FT_FILS_SHA384));
+			 WPA_KEY_MGMT_FT_FILS_SHA384 |
+			 WPA_KEY_MGMT_IEEE8021X_SHA384));
 }
 
 static inline int wpa_key_mgmt_wpa_psk_no_sae(int akm)
@@ -153,7 +155,8 @@
 	return !!(akm & (WPA_KEY_MGMT_IEEE8021X_SUITE_B_192 |
 			 WPA_KEY_MGMT_FT_IEEE8021X_SHA384 |
 			 WPA_KEY_MGMT_FILS_SHA384 |
-			 WPA_KEY_MGMT_FT_FILS_SHA384));
+			 WPA_KEY_MGMT_FT_FILS_SHA384 |
+			 WPA_KEY_MGMT_IEEE8021X_SHA384));
 }
 
 static inline int wpa_key_mgmt_suite_b(int akm)
diff --git a/src/common/dragonfly.c b/src/common/dragonfly.c
index 1e84271..d039e5f 100644
--- a/src/common/dragonfly.c
+++ b/src/common/dragonfly.c
@@ -67,12 +67,15 @@
 		}
 
 		res = crypto_bignum_legendre(tmp, prime);
-		if (res == 1 && !(*qr))
+		if (res == 1 && !(*qr)) {
 			*qr = tmp;
-		else if (res == -1 && !(*qnr))
+		} else if (res == -1 && !(*qnr)) {
 			*qnr = tmp;
-		else
+		} else {
 			crypto_bignum_deinit(tmp, 0);
+			if (res == -2)
+				break;
+		}
 	}
 
 	if (*qr && *qnr)
diff --git a/src/common/hw_features_common.c b/src/common/hw_features_common.c
index 584c6d2..57b5a8e 100644
--- a/src/common/hw_features_common.c
+++ b/src/common/hw_features_common.c
@@ -183,8 +183,8 @@
 
 	*pri_chan = *sec_chan = 0;
 
-	ieee802_11_parse_elems((u8 *) (bss + 1), bss->ie_len, &elems, 0);
-	if (elems.ht_operation) {
+	if (ieee802_11_parse_elems((u8 *) (bss + 1), bss->ie_len, &elems, 0) !=
+	    ParseFailed && elems.ht_operation) {
 		oper = (struct ieee80211_ht_operation *) elems.ht_operation;
 		*pri_chan = oper->primary_chan;
 		if (oper->ht_param & HT_INFO_HT_PARAM_STA_CHNL_WIDTH) {
@@ -273,7 +273,10 @@
 	if (bss->freq < start || bss->freq > end || bss->freq == pri_freq)
 		return 0;
 
-	ieee802_11_parse_elems((u8 *) (bss + 1), bss->ie_len, &elems, 0);
+	if (ieee802_11_parse_elems((u8 *) (bss + 1), bss->ie_len, &elems, 0) ==
+	    ParseFailed)
+		return 0;
+
 	if (!elems.ht_capabilities) {
 		wpa_printf(MSG_DEBUG, "Found overlapping legacy BSS: "
 			   MACSTR " freq=%d", MAC2STR(bss->bssid), bss->freq);
@@ -357,9 +360,9 @@
 			}
 		}
 
-		ieee802_11_parse_elems((u8 *) (bss + 1), bss->ie_len, &elems,
-				       0);
-		if (elems.ht_capabilities) {
+		if (ieee802_11_parse_elems((u8 *) (bss + 1), bss->ie_len,
+					   &elems, 0) != ParseFailed &&
+		    elems.ht_capabilities) {
 			struct ieee80211_ht_capabilities *ht_cap =
 				(struct ieee80211_ht_capabilities *)
 				elems.ht_capabilities;
diff --git a/src/common/ieee802_11_common.c b/src/common/ieee802_11_common.c
index dcadfbe..dbe7b29 100644
--- a/src/common/ieee802_11_common.c
+++ b/src/common/ieee802_11_common.c
@@ -429,6 +429,7 @@
 	for_each_element(elem, start, len) {
 		u8 id = elem->id, elen = elem->datalen;
 		const u8 *pos = elem->data;
+		size_t *total_len = NULL;
 
 		if (id == WLAN_EID_FRAGMENT && elems->num_frag_elems > 0) {
 			elems->num_frag_elems--;
@@ -512,6 +513,8 @@
 				break;
 			elems->ftie = pos;
 			elems->ftie_len = elen;
+			elems->fte_defrag_len = elen;
+			total_len = &elems->fte_defrag_len;
 			break;
 		case WLAN_EID_TIMEOUT_INTERVAL:
 			if (elen != 5)
@@ -657,6 +660,12 @@
 				   id, elen);
 			break;
 		}
+
+		if (elen == 255 && total_len)
+			*total_len += ieee802_11_fragments_length(
+				elems, pos + elen,
+				(start + len) - (pos + elen));
+
 	}
 
 	if (!for_each_element_completed(elem, start, len)) {
@@ -2498,6 +2507,35 @@
 
 
 /**
+ * get_ie_nth - Fetch a specified information element from IEs buffer
+ * @ies: Information elements buffer
+ * @len: Information elements buffer length
+ * @eid: Information element identifier (WLAN_EID_*)
+ * @nth: Return the nth element of the requested type (2 returns the second)
+ * Returns: Pointer to the information element (id field) or %NULL if not found
+ *
+ * This function returns the nth matching information element in the IEs
+ * buffer or %NULL in case the element is not found.
+ */
+const u8 * get_ie_nth(const u8 *ies, size_t len, u8 eid, int nth)
+{
+	const struct element *elem;
+	int sofar = 0;
+
+	if (!ies)
+		return NULL;
+
+	for_each_element_id(elem, eid, ies, len) {
+		sofar++;
+		if (sofar == nth)
+			return &elem->id;
+	}
+
+	return NULL;
+}
+
+
+/**
  * get_ie_ext - Fetch a specified extended information element from IEs buffer
  * @ies: Information elements buffer
  * @len: Information elements buffer length
@@ -2964,7 +3002,7 @@
 	for (i = 0; i < flen; i++)
 		capabs |= rsnxe[i] << (8 * i);
 
-	return capabs & BIT(capab);
+	return !!(capabs & BIT(capab));
 }
 
 
@@ -3648,3 +3686,50 @@
 
 	return ieee802_11_defrag_data(data, len, true);
 }
+
+
+unsigned int is_ap_t2lm_negotiation_supported(const u8 *mle, size_t mle_len)
+{
+	u16 ml_control;
+	u16 mld_capabilities;
+	size_t offset =
+		2 /* Multi Link Control */ +
+		1 /* Common Info Length field */ +
+		ETH_ALEN /* MLD MAC Address field */;
+
+	if(!mle || mle_len < offset)
+	    return 0;
+
+	ml_control = WPA_GET_LE16(mle);
+	wpa_printf(MSG_DEBUG, "%s: ML control field 0x%x", __func__, ml_control);
+
+	if (!(ml_control & BASIC_MULTI_LINK_CTRL_PRES_MLD_CAPA)) {
+	    wpa_printf(MSG_DEBUG, "MLD capabilities not present");
+	    return 0;
+	}
+
+	if (ml_control & BASIC_MULTI_LINK_CTRL_PRES_LINK_ID)
+	    offset++;
+
+	if (ml_control & BASIC_MULTI_LINK_CTRL_PRES_BSS_PARAM_CH_COUNT)
+	    offset++;
+
+	if (ml_control & BASIC_MULTI_LINK_CTRL_PRES_MSD_INFO)
+	    offset += 2;
+
+	if (ml_control & BASIC_MULTI_LINK_CTRL_PRES_EML_CAPA)
+	    offset += 2;
+
+	if (mle_len < (offset + 2)) {
+	    wpa_printf(MSG_ERROR, "Not suffcient length for MLD capabilities");
+	    return 0;
+	}
+
+	mld_capabilities = WPA_GET_LE16(mle + offset);
+	wpa_printf(MSG_DEBUG, "MLD capabilities 0x%x", mld_capabilities);
+	if(!(mld_capabilities &
+	     EHT_ML_MLD_CAPA_TID_TO_LINK_MAP_NEG_SUPP_MSK))
+	    return 0;
+
+	return 1;
+}
diff --git a/src/common/ieee802_11_common.h b/src/common/ieee802_11_common.h
index c7afd34..6f4fe80 100644
--- a/src/common/ieee802_11_common.h
+++ b/src/common/ieee802_11_common.h
@@ -174,6 +174,8 @@
 
 	struct mb_ies_info mb_ies;
 
+	size_t fte_defrag_len;
+
 	/*
 	 * The number of fragment elements to be skipped after a known
 	 * fragmented element.
@@ -259,6 +261,7 @@
 extern size_t global_op_class_size;
 
 const u8 * get_ie(const u8 *ies, size_t len, u8 eid);
+const u8 * get_ie_nth(const u8 *ies, size_t len, u8 eid, int nth);
 const u8 * get_ie_ext(const u8 *ies, size_t len, u8 ext);
 const u8 * get_vendor_ie(const u8 *ies, size_t len, u32 vendor_type);
 
@@ -370,4 +373,5 @@
 enum chan_width get_sta_operation_chan_width(enum chan_width ap_operation_chan_width,
 					     struct supported_chan_width sta_supported_width);
 
+unsigned int is_ap_t2lm_negotiation_supported(const u8 *mle, size_t mle_len);
 #endif /* IEEE802_11_COMMON_H */
diff --git a/src/common/ieee802_11_defs.h b/src/common/ieee802_11_defs.h
index b9bb226..244b38e 100644
--- a/src/common/ieee802_11_defs.h
+++ b/src/common/ieee802_11_defs.h
@@ -501,6 +501,7 @@
 #define WLAN_EID_EXT_EHT_CAPABILITIES 108
 #define WLAN_EID_EXT_TID_TO_LINK_MAPPING 109
 #define WLAN_EID_EXT_MULTI_LINK_TRAFFIC_INDICATION 110
+#define WLAN_EID_EXT_QOS_CHARACTERISTICS 113
 #define WLAN_EID_EXT_AKM_SUITE_SELECTOR 114
 
 /* Extended Capabilities field */
@@ -677,6 +678,19 @@
 #define WLAN_PA_FILS_DISCOVERY 34
 #define WLAN_PA_LOCATION_MEASUREMENT_REPORT 47
 
+/* HT Action field values (IEEE P802.11-REVme/D4.0, 9.6.11.1, Table 9-491) */
+#define WLAN_HT_ACTION_NOTIFY_CHANWIDTH 0
+#define WLAN_HT_ACTION_SMPS 1
+#define WLAN_HT_ACTION_CSI 4
+#define WLAN_HT_ACTION_NONCOMPRESSED_BF 5
+#define WLAN_HT_ACTION_COMPRESSED_BF 6
+#define WLAN_HT_ACTION_ASEL_IDX_FEEDBACK 7
+
+/* VHT Action field values (IEEE P802.11-REVme/D4.0, 9.6.22.1, Table 9-579) */
+#define WLAN_VHT_ACTION_COMPRESSED_BF 0
+#define WLAN_VHT_ACTION_GROUP_ID_MGMT 1
+#define WLAN_VHT_ACTION_OPMODE_NOTIF 2
+
 /* Protected Dual of Public Action frames (IEEE Std 802.11-2016, 9.6.11,
  * Table 9-332) */
 #define WLAN_PROT_DSE_ENABLEMENT 1
@@ -1190,9 +1204,6 @@
 	 */
 } STRUCT_PACKED;
 
-#ifdef _MSC_VER
-#pragma pack(pop)
-#endif /* _MSC_VER */
 
 #define ERP_INFO_NON_ERP_PRESENT BIT(0)
 #define ERP_INFO_USE_PROTECTION BIT(1)
@@ -1362,6 +1373,10 @@
 
 #define VHT_RX_NSS_MAX_STREAMS			    8
 
+#define VHT_OPMODE_CHANNEL_40MHZ		    ((u8) BIT(0))
+#define VHT_OPMODE_CHANNEL_80MHZ		    ((u8) BIT(1))
+#define VHT_OPMODE_CHANNEL_160MHZ		    ((u8) BIT(1) | BIT(2))
+
 /* VHT operation information - channel widths */
 #define CHANWIDTH_USE_HT	0
 #define CHANWIDTH_80MHZ		1
@@ -2479,9 +2494,14 @@
  */
 #define RNR_HEADER_LEN                              2
 #define RNR_TBTT_HEADER_LEN                         4
+#define RNR_TBTT_INFO_HDR_TYPE_MSK                  0x03
+#define RNR_TBTT_INFO_HDR_FILTERED_AP               0x04
+#define RNR_TBTT_INFO_HDR_CNT_MSK                   0xf0
 #define RNR_TBTT_INFO_COUNT(x)                      (((x) & 0xf) << 4)
 #define RNR_TBTT_INFO_COUNT_MAX                     16
+#define RNR_TBTT_INFO_COUNT_VAL(x)                  (((x) & 0xf0) >> 4)
 #define RNR_TBTT_INFO_LEN                           13
+#define RNR_TBTT_INFO_MLD_LEN                       16
 #define RNR_NEIGHBOR_AP_OFFSET_UNKNOWN              255
 /* Figure 9-632a - BSS Parameters subfield format */
 #define RNR_BSS_PARAM_OCT_RECOMMENDED               BIT(0)
@@ -2641,6 +2661,7 @@
  * STA Control field definitions of Per-STA Profile subelement in Basic
  * Multi-Link element as described in Figure 9-1002n: STA Control field format.
  */
+#define BASIC_MLE_STA_CTRL_LEN				2
 #define BASIC_MLE_STA_CTRL_LINK_ID_MASK			0x000F
 #define BASIC_MLE_STA_CTRL_COMPLETE_PROFILE		0x0010
 #define BASIC_MLE_STA_CTRL_PRES_STA_MAC			0x0020
@@ -2694,6 +2715,16 @@
 #define EHT_ML_MLD_CAPA_FREQ_SEP_FOR_STR_MASK         0x0f80
 #define EHT_ML_MLD_CAPA_AAR_SUPP                      0x1000
 
+#define EHT_PER_STA_CTRL_LINK_ID_MSK                  0x000f
+#define EHT_PER_STA_CTRL_COMPLETE_PROFILE_MSK         0x0010
+#define EHT_PER_STA_CTRL_MAC_ADDR_PRESENT_MSK         0x0020
+#define EHT_PER_STA_CTRL_BEACON_INTERVAL_PRESENT_MSK  0x0040
+#define EHT_PER_STA_CTRL_TSF_OFFSET_PRESENT_MSK       0x0080
+#define EHT_PER_STA_CTRL_DTIM_INFO_PRESENT_MSK        0x0100
+#define EHT_PER_STA_CTRL_NSTR_LINK_PAIR_PRESENT_MSK   0x0200
+#define EHT_PER_STA_CTRL_NSTR_BM_SIZE_MSK             0x0400
+#define EHT_PER_STA_CTRL_BSS_PARAM_CNT_PRESENT_MSK    0x0800
+
 /* IEEE P802.11be/D2.0, 9.4.2.312.2.4 - Per-STA Profile subelement format */
 struct ieee80211_eht_per_sta_profile {
 	le16 sta_control;
@@ -2718,9 +2749,17 @@
 	u8 variable[];
 } STRUCT_PACKED;
 
-/* IEEE P802.11be/D2.0, 9.4.2.312.4 - Reconfiguration Multi-Link element */
+/* IEEE P802.11be/D4.0, 9.4.2.312.4 - Reconfiguration Multi-Link element */
 
-#define EHT_ML_PRES_BM_RECONFIGURE_MLD_ADDRESS 0x0001
+#define RECONF_MULTI_LINK_CTRL_PRES_MLD_MAC_ADDR   0x0001
+
+#define EHT_PER_STA_RECONF_CTRL_LINK_ID_MSK        0x000f
+#define EHT_PER_STA_RECONF_CTRL_COMPLETE_PROFILE   0x0010
+#define EHT_PER_STA_RECONF_CTRL_MAC_ADDR           0x0020
+#define EHT_PER_STA_RECONF_CTRL_AP_REMOVAL_TIMER   0x0040
+#define EHT_PER_STA_RECONF_CTRL_OP_UPDATE_TYPE_MSK 0x0780
+#define EHT_PER_STA_RECONF_CTRL_OP_PARAMS          0x0800
+#define EHT_PER_STA_RECONF_CTRL_NSTR_BITMAP_SIZE   0x1000
 
 /* IEEE P802.11be/D2.0, 9.4.2.312.1 - Multi-Link element / General */
 
@@ -2791,6 +2830,39 @@
 	SCS_REQ_CHANGE = 2,
 };
 
+/*
+ * IEEE P802.11be/D4.0, 9.4.2.316 QoS Characteristics element,
+ * Table 9-404s (Direction subfield encoding)
+ */
+enum scs_direction {
+	SCS_DIRECTION_UP = 0,
+	SCS_DIRECTION_DOWN = 1,
+	SCS_DIRECTION_DIRECT = 2,
+};
+
+/*
+ * IEEE P802.11be/D4.0, 9.4.2.316 QoS Characteristics element,
+ * Figure 9-1001av (Control Info field format)
+ */
+#define EHT_QOS_CONTROL_INFO_DIRECTION_OFFSET		0
+#define EHT_QOS_CONTROL_INFO_TID_OFFSET			2
+#define EHT_QOS_CONTROL_INFO_USER_PRIORITY_OFFSET	6
+#define EHT_QOS_CONTROL_INFO_PRESENCE_MASK_OFFSET	9
+#define EHT_QOS_CONTROL_INFO_LINK_ID_OFFSET		25
+
+/*
+ * IEEE P802.11be/D4.0, 9.4.2.316 QoS Characteristics element,
+ * Presence Bitmap Of Additional Parameters
+ */
+#define SCS_QOS_BIT_MAX_MSDU_SIZE			((u16) BIT(0))
+#define SCS_QOS_BIT_SERVICE_START_TIME			((u16) BIT(1))
+#define SCS_QOS_BIT_SERVICE_START_TIME_LINKID		((u16) BIT(2))
+#define SCS_QOS_BIT_MEAN_DATA_RATE			((u16) BIT(3))
+#define SCS_QOS_BIT_DELAYED_BOUNDED_BURST_SIZE		((u16) BIT(4))
+#define SCS_QOS_BIT_MSDU_LIFETIME			((u16) BIT(5))
+#define SCS_QOS_BIT_MSDU_DELIVERY_INFO			((u16) BIT(6))
+#define SCS_QOS_BIT_MEDIUM_TIME				((u16) BIT(7))
+
 /* Optional subelement IDs for MSCS Descriptor element */
 enum mscs_description_subelem {
 	MCSC_SUBELEM_STATUS = 1,
@@ -2837,6 +2909,7 @@
 #define FD_CAP_PHY_INDEX_HT				2
 #define FD_CAP_PHY_INDEX_VHT				3
 #define FD_CAP_PHY_INDEX_HE				4 /* P802.11ax */
+#define FD_CAP_PHY_INDEX_EHT				5 /* P802.11be */
 #define FD_CAP_PHY_INDEX_SHIFT				10
 
 /*
@@ -2880,6 +2953,7 @@
 /* Wi-Fi Alliance Capabilities element - Capabilities field */
 #define WFA_CAPA_QM_DSCP_POLICY BIT(0)
 #define WFA_CAPA_QM_UNSOLIC_DSCP BIT(1)
+#define WFA_CAPA_QM_NON_EHT_SCS_TRAFFIC_DESC BIT(2)
 
 #if defined(CONFIG_DRIVER_NL80211_BRCM) || defined(CONFIG_DRIVER_NL80211_SYNA)
 #define WPA_KEY_MGMT_CROSS_AKM_ROAM (WPA_KEY_MGMT_SAE | WPA_KEY_MGMT_PSK)
@@ -2897,4 +2971,8 @@
 	u8 data[0];
 } STRUCT_PACKED;
 
+#ifdef _MSC_VER
+#pragma pack(pop)
+#endif /* _MSC_VER */
+
 #endif /* IEEE802_11_DEFS_H */
diff --git a/src/common/qca-vendor.h b/src/common/qca-vendor.h
index 2fcdfbe..7620f03 100644
--- a/src/common/qca-vendor.h
+++ b/src/common/qca-vendor.h
@@ -64,6 +64,42 @@
  *	encapsulated inside any attribute. Attribute QCA_WLAN_VENDOR_ATTR_NAN
  *	is used when receiving vendor events in userspace from the driver.
  *
+ * @QCA_NL80211_VENDOR_SUBCMD_TDLS_ENABLE: This command is used to enable TDLS
+ *	capability or to form a session with the specified peer.
+ *	If %NL80211_ATTR_VENDOR_DATA is sent as an empty nested attribute this
+ *	indicates to enable TDLS capability on the interface.
+ *	If %QCA_WLAN_VENDOR_ATTR_TDLS_ENABLE_MAC_ADDR is nested in
+ *	%NL80211_ATTR_VENDOR_DATA this indicates the userspace requests to
+ *	form a TDLS session with the specified peer MAC address.
+ *	The attributes used with this command are defined in
+ *	enum qca_wlan_vendor_attr_tdls_enable.
+ *
+ * @QCA_NL80211_VENDOR_SUBCMD_TDLS_DISABLE: This command is used to disable TDLS
+ *	capability or to terminate the session with the specified peer.
+ *	If %NL80211_ATTR_VENDOR_DATA is sent as an empty nested attribute this
+ *	indicates to disable TDLS capability on the interface.
+ *	If %QCA_WLAN_VENDOR_ATTR_TDLS_DISABLE_MAC_ADDR is nested in
+ *	%NL80211_ATTR_VENDOR_DATA this indicates the userspace requests to
+ *	terminate TDLS session with the specified peer MAC address.
+ *	The attributes used with this command are defined in
+ *	enum qca_wlan_vendor_attr_tdls_disable.
+ *
+ * @QCA_NL80211_VENDOR_SUBCMD_TDLS_GET_STATUS: This command is to get the TDLS
+ *	status at the interface level or with the specific peer.
+ *	If %NL80211_ATTR_VENDOR_DATA is sent as an empty nested attribute this
+ *	indicates the TDLS status query is at interface level.
+ *	If %QCA_WLAN_VENDOR_ATTR_TDLS_GET_STATUS_MAC_ADDR is nested in
+ *	%NL80211_ATTR_VENDOR_DATA this indicates the userspace requests to
+ *	get TDLS session status with the specified peer MAC address.
+ *	The attributes used with this command are defined in
+ *	enum qca_wlan_vendor_attr_tdls_get_status.
+ *
+ * @QCA_NL80211_VENDOR_SUBCMD_TDLS_STATE: This event is to indicate the result
+ *	of the TDLS session request with the peer sent by userspace in
+ *	%QCA_NL80211_VENDOR_SUBCMD_TDLS_ENABLE.
+ *	The attributes used with this command are defined in
+ *	enum qca_wlan_vendor_attr_tdls_state.
+ *
  * @QCA_NL80211_VENDOR_SUBCMD_KEY_MGMT_SET_KEY: Set key operation that can be
  *	used to configure PMK to the driver even when not connected. This can
  *	be used to request offloading of key management operations. Only used
@@ -106,6 +142,32 @@
  * @QCA_NL80211_VENDOR_SUBCMD_GET_WIFI_INFO: Get information from the driver.
  *	Attributes defined in enum qca_wlan_vendor_attr_get_wifi_info.
  *
+ * @QCA_NL80211_VENDOR_SUBCMD_SET_WIFI_CONFIGURATION: This command is used to
+ *	configure various wiphy or interface level configurations. Attributes
+ *	are defined in enum qca_wlan_vendor_attr_config. Userspace can send one
+ *	or more configuration attributes with a single command. The driver
+ *	accepts the command only if all the configurations are known, otherwise
+ *	it rejects the command. The driver returns success only if processing of
+ *	all the configurations succeeds. The driver continues to process all the
+ *	configurations even if processing of some configurations failed and
+ *	returns the last error occurred while processing the failed
+ *	configurations.
+ *
+ * @QCA_NL80211_VENDOR_SUBCMD_GET_WIFI_CONFIGURATION: This command is used to
+ *	get the current values of the various wiphy or interface level
+ *	configurations. Attributes are defined in enum
+ *	qca_wlan_vendor_attr_config. Userspace needs to specify the
+ *	configuration attributes for which it wants to get the values in the
+ *	command, there is no significance for the value sent in the attribute
+ *	unless explicitly specified in the corresponding configuration
+ *	attribute documentation. The driver accepts the command only if all the
+ *	configurations are known, otherwise it rejects the command. The driver
+ *	returns success only if fetching of all configuration values succeeds
+ *	and indicates the configuration values in corresponding attributes in
+ *	the response. The driver continues to process all the configurations
+ *	even if processing of some configurations failed and returns the last
+ *	error occurred while processing the failed configurations.
+ *
  * @QCA_NL80211_VENDOR_SUBCMD_GET_LOGGER_FEATURE_SET: Get the feature bitmap
  *	based on enum wifi_logger_supported_features. Attributes defined in
  *	enum qca_wlan_vendor_attr_get_logger_features.
@@ -858,7 +920,9 @@
  *	In some implementations, MLO has multiple netdevs out of which one
  *	netdev is designated as primary to provide a unified interface to the
  *	bridge. In those implementations this event is sent on every MLO peer
- *	connection.
+ *	connection. User applications on an AP MLD will use this event to get
+ *	info for all the links from non-AP MLD that were negotiated to be used
+ *	for the ML association.
  *
  *	The attributes used with this event are defined in
  *	enum qca_wlan_vendor_attr_mlo_peer_prim_netdev_event.
@@ -954,6 +1018,12 @@
  *
  *	Uses the attributes defined in
  *	enum qca_wlan_vendor_attr_tdls_disc_rsp_ext.
+ *
+ * @QCA_NL80211_VENDOR_SUBCMD_TX_LATENCY: This vendor subcommand is used to
+ *	configure, retrieve, and report per-link transmit latency statistics.
+ *
+ *	The attributes used with this subcommand are defined in
+ *	enum qca_wlan_vendor_attr_tx_latency.
  */
 enum qca_nl80211_vendor_subcmds {
 	QCA_NL80211_VENDOR_SUBCMD_UNSPEC = 0,
@@ -1169,6 +1239,9 @@
 	QCA_NL80211_VENDOR_SUBCMD_TID_TO_LINK_MAP = 229,
 	QCA_NL80211_VENDOR_SUBCMD_LINK_RECONFIG = 230,
 	QCA_NL80211_VENDOR_SUBCMD_TDLS_DISC_RSP_EXT = 231,
+	/* 232 - reserved for QCA */
+	QCA_NL80211_VENDOR_SUBCMD_TX_LATENCY = 233,
+	/* 234 - reserved for QCA */
 };
 
 /* Compatibility defines for previously used subcmd names.
@@ -1381,6 +1454,23 @@
 	 */
 	QCA_WLAN_VENDOR_ATTR_SETBAND_MASK = 43,
 
+	/* Unsigned 8-bit used by QCA_NL80211_VENDOR_SUBCMD_GET_FEATURES.
+	 * This field describes the maximum number of links supported by the
+	 * chip for MLO association.
+	 * This is an optional attribute.
+	 */
+	QCA_WLAN_VENDOR_ATTR_MLO_CAPABILITY_MAX_ASSOCIATION_COUNT = 44,
+
+	/* Unsigned 8-bit used by QCA_NL80211_VENDOR_SUBCMD_GET_FEATURES.
+	 * This field describes the maximum number of Simultaneous Transmit
+	 * and Receive (STR) links used in Multi-Link Operation.
+	 * The maximum number of STR links used can be different
+	 * from the maximum number of radios supported by the chip.
+	 * This is a static configuration of the chip.
+	 * This is an optional attribute.
+	 */
+	QCA_WLAN_VENDOR_ATTR_MLO_CAPABILITY_MAX_STR_LINK_COUNT = 45,
+
 	/* keep last */
 	QCA_WLAN_VENDOR_ATTR_AFTER_LAST,
 	QCA_WLAN_VENDOR_ATTR_MAX	= QCA_WLAN_VENDOR_ATTR_AFTER_LAST - 1,
@@ -2382,9 +2472,11 @@
 	 * used to calculate statistics like average the TSF offset or average
 	 * number of frame leaked.
 	 * For instance, upon Beacon frame reception:
-	 * current_avg = ((beacon_TSF - TBTT) * factor + previous_avg * (0x10000 - factor) ) / 0x10000
+	 * current_avg = ((beacon_TSF - TBTT) * factor +
+	 *                previous_avg * (0x10000 - factor)) / 0x10000
 	 * For instance, when evaluating leaky APs:
-	 * current_avg = ((num frame received within guard time) * factor + previous_avg * (0x10000 - factor)) / 0x10000
+	 * current_avg = ((num frame received within guard time) * factor +
+	 *                previous_avg * (0x10000 - factor)) / 0x10000
 	 */
 	QCA_WLAN_VENDOR_ATTR_CONFIG_STATS_AVG_FACTOR = 2,
 	/* Unsigned 32-bit value to configure guard time, i.e., when
@@ -2513,7 +2605,10 @@
 	/* 32-bit unsigned value to set reorder timeout for AC_BK */
 	QCA_WLAN_VENDOR_ATTR_CONFIG_RX_REORDER_TIMEOUT_BACKGROUND = 34,
 	/* 6-byte MAC address to point out the specific peer */
-	QCA_WLAN_VENDOR_ATTR_CONFIG_RX_BLOCKSIZE_PEER_MAC = 35,
+	QCA_WLAN_VENDOR_ATTR_CONFIG_PEER_MAC = 35,
+	/* Backward compatibility with the original name */
+	QCA_WLAN_VENDOR_ATTR_CONFIG_RX_BLOCKSIZE_PEER_MAC =
+	QCA_WLAN_VENDOR_ATTR_CONFIG_PEER_MAC,
 	/* 32-bit unsigned value to set window size for specific peer */
 	QCA_WLAN_VENDOR_ATTR_CONFIG_RX_BLOCKSIZE_WINLIMIT = 36,
 	/* 8-bit unsigned value to set the beacon miss threshold in 2.4 GHz */
@@ -2691,6 +2786,9 @@
 	 * state, it should not exceed the negotiated channel width. If it is
 	 * configured when STA is in disconnected state, the configured value
 	 * will take effect for the next immediate connection.
+	 * This configuration can be sent inside
+	 * %QCA_WLAN_VENDOR_ATTR_CONFIG_MLO_LINKS to specify the maximum
+	 * supported channel width per-MLO link.
 	 *
 	 * This uses values defined in enum nl80211_chan_width.
 	 */
@@ -2767,8 +2865,13 @@
 	 * configure the asymmetric NSS configuration (such as 1X2).
 	 */
 	QCA_WLAN_VENDOR_ATTR_CONFIG_NSS = 70,
-	/* 8-bit unsigned value to trigger Optimized Power Management:
-	 * 1-Enable, 0-Disable
+	/* 8-bit unsigned value to configure Optimized Power Management mode:
+	 * Modes are defined by enum qca_wlan_vendor_opm_mode.
+	 *
+	 * This attribute shall be configured along with
+	 * %QCA_WLAN_VENDOR_ATTR_CONFIG_OPM_ITO and
+	 * %QCA_WLAN_VENDOR_ATTR_CONFIG_OPM_SPEC_WAKE_INTERVAL attributes
+	 * when its value is set to %QCA_WLAN_VENDOR_OPM_MODE_USER_DEFINED.
 	 */
 	QCA_WLAN_VENDOR_ATTR_CONFIG_OPTIMIZED_POWER_MANAGEMENT = 71,
 
@@ -2976,8 +3079,13 @@
 	QCA_WLAN_VENDOR_ATTR_CONFIG_EHT_MLO_MAX_SIMULTANEOUS_LINKS = 88,
 
 	/* 8-bit unsigned value to configure the driver with EHT MLO maximum
-	 * number of links to be used for MLO connection.
-	 * The range of the value is 1 to 16.
+	 * number of links to be used for MLO connection. Value 0 restores the
+	 * default value of the maximum MLO links capability of the device.
+	 * The range of the value is 0 to 15.
+	 *
+	 * 0 - Restore default device limit.
+	 * 1 to 15 - Set the maximum number of links to be used for an MLO
+	 * connection.
 	 */
 	QCA_WLAN_VENDOR_ATTR_CONFIG_EHT_MLO_MAX_NUM_LINKS = 89,
 
@@ -3037,6 +3145,163 @@
 	 */
 	QCA_WLAN_VENDOR_ATTR_CONFIG_CTS_CHANNEL_WIDTH = 94,
 
+	/* 8-bit unsigned value. This attribute is used to dynamically
+	 * enable/suspend trigger based UL MU transmission.
+	 * This is supported in STA mode and the device sends Operating
+	 * Mode Indication to inform the change as described in
+	 * IEEE Std 802.11ax-2021, 26.9.
+	 *
+	 * This attribute can be configured when the STA is associated
+	 * to an AP and the configuration is maintained until the current
+	 * association terminates.
+	 *
+	 * By default all UL MU transmissions are enabled.
+	 *
+	 * Uses enum qca_ul_mu_config values.
+	 */
+	QCA_WLAN_VENDOR_ATTR_CONFIG_UL_MU_CONFIG = 95,
+
+	/* 8-bit unsigned value. Optionally specified along with
+	 * %QCA_WLAN_VENDOR_ATTR_CONFIG_CHANNEL_WIDTH when STA is in connected
+	 * state. This configuration is applicable only for the current
+	 * connection. This configuration not allowed in disconnected state.
+	 * This configuration can be sent inside
+	 * %QCA_WLAN_VENDOR_ATTR_CONFIG_MLO_LINKS to specify the maximum
+	 * supported channel width update type per-MLO link.
+	 *
+	 * Uses enum qca_chan_width_update_type values.
+	 */
+	QCA_WLAN_VENDOR_ATTR_CONFIG_CHAN_WIDTH_UPDATE_TYPE = 96,
+
+	/* 8-bit unsigned value to set EPCS (Emergency Preparedness
+	 * Communications Service) feature capability
+	 * 1 - Enable, 0 - Disable.
+	 *
+	 * This configuration is used for testing purposes.
+	 */
+	QCA_WLAN_VENDOR_ATTR_CONFIG_EPCS_CAPABILITY = 97,
+
+	/* 8-bit unsigned value to enable/disable EPCS priority access
+	 * 1 - Enable, 0 - Disable.
+	 * The EPCS priority access shall be enabled only when EPCS feature
+	 * capability is also enabled (see
+	 * QCA_WLAN_VENDOR_ATTR_CONFIG_EPCS_CAPABILITY).
+	 *
+	 * This configuration is used for testing purposes.
+	 */
+	QCA_WLAN_VENDOR_ATTR_CONFIG_EPCS_FUNCTION = 98,
+
+	/* 8-bit unsigned value. Used to specify the MLO link ID of a link
+	 * that is being configured. This attribute must be included in each
+	 * record nested inside %QCA_WLAN_VENDOR_ATTR_CONFIG_MLO_LINKS, and
+	 * may be included without nesting to indicate the link that is the
+	 * target of other configuration attributes.
+	 */
+	QCA_WLAN_VENDOR_ATTR_CONFIG_MLO_LINK_ID = 99,
+
+	/* Array of nested links each identified by
+	 * %QCA_WLAN_VENDOR_ATTR_CONFIG_MLO_LINK_ID. This uses values defined in
+	 * enum qca_wlan_vendor_attr_config, explicit documentation shall be
+	 * added for the attributes in enum qca_wlan_vendor_attr_config to
+	 * support per-MLO link configuration through
+	 * %QCA_WLAN_VENDOR_ATTR_CONFIG_MLO_LINKS.
+	 *
+	 * Userspace can configure a single link or multiple links with this
+	 * attribute by nesting the corresponding configuration attributes and
+	 * %QCA_WLAN_VENDOR_ATTR_CONFIG_MLO_LINK_ID for each link.
+	 *
+	 * Userspace can fetch the configuration attribute values for a single
+	 * link or multiple links with this attribute by nesting the
+	 * corresponding configuration attributes and
+	 * %QCA_WLAN_VENDOR_ATTR_CONFIG_MLO_LINK_ID for each link.
+	 *
+	 * For STA interface, this attribute is applicable only in connected
+	 * state when the current connection is MLO capable. The valid values of
+	 * %QCA_WLAN_VENDOR_ATTR_CONFIG_MLO_LINK_ID are the link IDs of the
+	 * connected AP MLD links.
+	 *
+	 * For AP interface, this configuration applicable only after adding
+	 * MLO links to the AP interface with %NL80211_CMD_ADD_LINK and the
+	 * valid values of %QCA_WLAN_VENDOR_ATTR_CONFIG_MLO_LINK_ID are the link
+	 * IDs specified in %NL80211_CMD_ADD_LINK while adding the MLO links to
+	 * the AP interface.
+	 */
+	QCA_WLAN_VENDOR_ATTR_CONFIG_MLO_LINKS = 100,
+
+	/* 16-bit unsigned value to configure power save inactivity timeout in
+	 * milliseconds.
+	 *
+	 * STA enters into power save mode (PM=1) after TX/RX inactivity of time
+	 * duration specified by %QCA_WLAN_VENDOR_ATTR_CONFIG_OPM_ITO.
+	 *
+	 * This attribute shall be configured along with
+	 * %QCA_WLAN_VENDOR_ATTR_CONFIG_OPM_SPEC_WAKE_INTERVAL when
+	 * %QCA_WLAN_VENDOR_ATTR_CONFIG_OPTIMIZED_POWER_MANAGEMENT
+	 * is set to %QCA_WLAN_VENDOR_OPM_MODE_USER_DEFINED mode.
+	 */
+	QCA_WLAN_VENDOR_ATTR_CONFIG_OPM_ITO = 101,
+
+	/* 16-bit unsigned value to configure speculative wake interval in
+	 * milliseconds.
+	 *
+	 * STA speculatively wakes up to look for buffered data by AP at
+	 * %QCA_WLAN_VENDOR_ATTR_CONFIG_OPM_SPEC_WAKE_INTERVAL interval after
+	 * entering into power save. If configured zero, STA wakes up at
+	 * upcoming DTIM beacon.
+	 *
+	 * This attribute shall be configured along with
+	 * %QCA_WLAN_VENDOR_ATTR_CONFIG_OPM_ITO and
+	 * %QCA_WLAN_VENDOR_ATTR_CONFIG_OPTIMIZED_POWER_MANAGEMENT
+	 * to %QCA_WLAN_VENDOR_OPM_MODE_USER_DEFINED mode.
+	 */
+	QCA_WLAN_VENDOR_ATTR_CONFIG_OPM_SPEC_WAKE_INTERVAL = 102,
+
+	/*
+	 * 16-bit unsigned value to configure TX max A-MPDU count.
+	 *
+	 * For STA interface, this attribute is applicable only in connected
+	 * state, peer MAC address is not required to be provided.
+	 *
+	 * For AP interface, this attribute is applicable only in started
+	 * state and one of the associated peer STAs must be specified with
+	 * QCA_WLAN_VENDOR_ATTR_CONFIG_PEER_MAC. If this is for an ML
+	 * association, the peer MAC address provided is the link address of
+	 * the non-AP MLD.
+	 *
+	 * This attribute runtime configures the TX maximum aggregation size.
+	 * The value must be in range of 1 to BA window size for the specific
+	 * peer.
+	 */
+	QCA_WLAN_VENDOR_ATTR_CONFIG_PEER_AMPDU_CNT = 103,
+
+	/*
+	 * 8-bit unsigned value to configure TID-to-link mapping negotiation
+	 * type.
+	 * Uses enum qca_wlan_ttlm_negotiation_support values.
+	 *
+	 * This value applies to the complete AP/non-AP MLD interface, and the
+	 * MLD advertises it within the Basic Multi-Link element in the
+	 * association frames. If a new value is configured during an active
+	 * connection, it will take effect in the subsequent associations and
+	 * is not reset during disconnection.
+	 *
+	 * This attribute is used for testing purposes.
+	 */
+	QCA_WLAN_VENDOR_ATTR_CONFIG_TTLM_NEGOTIATION_SUPPORT = 104,
+
+	/* 8-bit unsigned value.
+	 *
+	 * This attribute configures a traffic shaping mode
+	 * applied during coex scenarios.
+	 * By default all coex traffic shaping modes are enabled,
+	 * i.e., shape WLAN traffic based on coex traffic pattern and priority.
+	 * To shape traffic, STA may enter in power save mode
+	 * and AP may send CTS-to-self frame.
+	 *
+	 * Uses enum qca_coex_traffic_shaping_mode values.
+	 */
+	QCA_WLAN_VENDOR_ATTR_CONFIG_COEX_TRAFFIC_SHAPING_MODE = 105,
+
 	/* keep last */
 	QCA_WLAN_VENDOR_ATTR_CONFIG_AFTER_LAST,
 	QCA_WLAN_VENDOR_ATTR_CONFIG_MAX =
@@ -3052,6 +3317,16 @@
 	QCA_WLAN_VENDOR_ATTR_CONFIG_BEACON_REPORT_FAIL
 
 /**
+ * enum qca_ul_mu_config - UL MU configuration
+ * @QCA_UL_MU_SUSPEND - All trigger based UL MU transmission is suspended
+ * @QCA_UL_MU_ENABLE - All trigger based UL MU transmission is enabled
+ */
+enum qca_ul_mu_config {
+	QCA_UL_MU_SUSPEND = 0,
+	QCA_UL_MU_ENABLE = 1,
+};
+
+/**
  * enum qca_dbam_config - Specifies DBAM config mode
  * @QCA_DBAM_DISABLE: Firmware disables DBAM
  * @QCA_DBAM_ENABLE: Firmware enables DBAM opportunistically when
@@ -4152,16 +4427,16 @@
  * statistics depending on the peer_mac.
  */
 enum qca_wlan_ll_stats_clr_req_bitmap {
-	QCA_WLAN_LL_STATS_CLR_REQ_BITMAP_RADIO = 		BIT(0),
-	QCA_WLAN_LL_STATS_CLR_REQ_BITMAP_RADIO_CCA = 		BIT(1),
-	QCA_WLAN_LL_STATS_CLR_REQ_BITMAP_RADIO_CHANNELS = 	BIT(2),
-	QCA_WLAN_LL_STATS_CLR_REQ_BITMAP_RADIO_SCAN = 		BIT(3),
-	QCA_WLAN_LL_STATS_CLR_REQ_BITMAP_IFACE = 		BIT(4),
-	QCA_WLAN_LL_STATS_CLR_REQ_BITMAP_IFACE_TXRATE = 	BIT(5),
-	QCA_WLAN_LL_STATS_CLR_REQ_BITMAP_IFACE_AC = 		BIT(6),
+	QCA_WLAN_LL_STATS_CLR_REQ_BITMAP_RADIO =		BIT(0),
+	QCA_WLAN_LL_STATS_CLR_REQ_BITMAP_RADIO_CCA =		BIT(1),
+	QCA_WLAN_LL_STATS_CLR_REQ_BITMAP_RADIO_CHANNELS =	BIT(2),
+	QCA_WLAN_LL_STATS_CLR_REQ_BITMAP_RADIO_SCAN =		BIT(3),
+	QCA_WLAN_LL_STATS_CLR_REQ_BITMAP_IFACE =		BIT(4),
+	QCA_WLAN_LL_STATS_CLR_REQ_BITMAP_IFACE_TXRATE =		BIT(5),
+	QCA_WLAN_LL_STATS_CLR_REQ_BITMAP_IFACE_AC =		BIT(6),
 	QCA_WLAN_LL_STATS_CLR_REQ_BITMAP_IFACE_CONTENTION =	BIT(7),
-	QCA_WLAN_LL_STATS_CLR_REQ_BITMAP_IFACE_ALL_PEER = 	BIT(8),
-	QCA_WLAN_LL_STATS_CLR_REQ_BITMAP_IFACE_PER_PEER = 	BIT(9),
+	QCA_WLAN_LL_STATS_CLR_REQ_BITMAP_IFACE_ALL_PEER =	BIT(8),
+	QCA_WLAN_LL_STATS_CLR_REQ_BITMAP_IFACE_PER_PEER =	BIT(9),
 };
 
 enum qca_wlan_vendor_attr_ll_stats_clr {
@@ -4202,8 +4477,8 @@
 enum qca_wlan_ll_stats_get_req_bitmap {
 	QCA_WLAN_LL_STATS_GET_REQ_BITMAP_RADIO =	BIT(0),
 	QCA_WLAN_LL_STATS_GET_REQ_BITMAP_IFACE =	BIT(1),
-	QCA_WLAN_LL_STATS_GET_REQ_BITMAP_ALL_PEER = 	BIT(2),
-	QCA_WLAN_LL_STATS_GET_REQ_BITMAP_PER_PEER = 	BIT(3),
+	QCA_WLAN_LL_STATS_GET_REQ_BITMAP_ALL_PEER =	BIT(2),
+	QCA_WLAN_LL_STATS_GET_REQ_BITMAP_PER_PEER =	BIT(3),
 };
 
 enum qca_wlan_vendor_attr_ll_stats_get {
@@ -5072,7 +5347,10 @@
  *	due to poor RSSI of the connected AP.
  * @QCA_ROAM_TRIGGER_REASON_BETTER_RSSI: Set if the roam has to be triggered
  *	upon finding a BSSID with a better RSSI than the connected BSSID.
- *	Here the RSSI of the current BSSID need not be poor.
+ *	Also, set if the roam has to be triggered due to the high RSSI of the
+ *	current connected AP (better than
+ *	QCA_ATTR_ROAM_CONTROL_CONNECTED_HIGH_RSSI_OFFSET). Here the RSSI of
+ *	the current BSSID need not be poor.
  * @QCA_ROAM_TRIGGER_REASON_PERIODIC: Set if the roam has to be triggered
  *	by triggering a periodic scan to find a better AP to roam.
  * @QCA_ROAM_TRIGGER_REASON_DENSE: Set if the roam has to be triggered
@@ -5415,7 +5693,11 @@
  * @QCA_ATTR_ROAM_CONTROL_CONNECTED_RSSI_THRESHOLD: Signed 32-bit value in dBm,
  *	signifying the RSSI threshold of the current connected AP, indicating
  *	the driver to trigger roam only when the current connected AP's RSSI
- *	is less than this threshold.
+ *	is less than this threshold. The RSSI threshold through this attribute
+ *	is only used by the STA when the connected AP asks it to roam through
+ *	a BTM request. Based on this threshold, the STA can either honor or
+ *	reject the AP's request to roam, and notify the status to the AP in a
+ *	BTM response.
  *
  * @QCA_ATTR_ROAM_CONTROL_CANDIDATE_RSSI_THRESHOLD: Signed 32-bit value in dBm,
  *	signifying the RSSI threshold of the candidate AP, indicating
@@ -5550,12 +5832,50 @@
  *	   discovered.
  *	The default behavior if this flag is not specified is to include all
  *	the supported 6 GHz PSC frequencies in the roam full scan.
+ *
+ * @QCA_ATTR_ROAM_CONTROL_CONNECTED_LOW_RSSI_THRESHOLD: Signed 32-bit value
+ *	in dBm.
+ *	This attribute configures the low RSSI threshold of the connected AP,
+ *	based on which the STA can start looking for the neighbor APs and
+ *	trigger the roam eventually. STA keeps monitoring for the connected
+ *	AP's RSSI and will start scanning for neighboring APs once the RSSI
+ *	falls below this threshold. This attribute differs from
+ *	QCA_ATTR_ROAM_CONTROL_CONNECTED_RSSI_THRESHOLD where the configured
+ *	threshold is used only when the connected AP asks the STA to roam
+ *	through a BTM request.
+ *
+ * @QCA_ATTR_ROAM_CONTROL_CANDIDATE_ROAM_RSSI_DIFF: Unsigned 8-bit value.
+ *	This attribute signifies the RSSI difference threshold between the
+ *	connected AP and the new candidate AP. This parameter influences the
+ *	STA to roam to the new candidate only when its RSSI is better than
+ *	the current connected one by this threshold.
+ *	This parameter configures the roam behavior among the 2.4/5/6 GHz bands.
+ *
+ * @QCA_ATTR_ROAM_CONTROL_6GHZ_CANDIDATE_ROAM_RSSI_DIFF: Unsigned 8-bit value.
+ *	This attribute signifies the RSSI difference threshold between the
+ *	connected AP in the 2.4/5 GHz bands and the new candidate AP in the
+ *	6 GHz band. This parameter influences the STA to roam to the new 6 GHz
+ *	candidate only when its RSSI is better than the current connected one
+ *	by this threshold. This threshold overrides
+ *	QCA_ATTR_ROAM_CONTROL_CANDIDATE_ROAM_RSSI_DIFF for the roam from 2.4/5
+ *	GHz to 6 GHz alone with the intention to have a different value to roam
+ *	to the preferred 6 GHz band.
+ *
+ * @QCA_ATTR_ROAM_CONTROL_CONNECTED_HIGH_RSSI_OFFSET: Unsigned 8-bit value.
+ *	This attribute signifies the RSSI offset that is added to low RSSI
+ *	threshold (QCA_ATTR_ROAM_CONTROL_CONNECTED_LOW_RSSI_THRESHOLD) to imply
+ *	high RSSI threshold. STA is expected to trigger roam if the current
+ *	connected AP's RSSI gets above this high RSSI threshold. STA's roam
+ *	attempt on high RSSI threshold aims to find candidates from other
+ *	better Wi-Fi bands. E.g., STA would initially connect to a 2.4 GHz BSSID
+ *	and would migrate to 5/6 GHz when it comes closer to the AP (high RSSI
+ *	for 2.4 GHz BSS).
  */
 enum qca_vendor_attr_roam_control {
 	QCA_ATTR_ROAM_CONTROL_ENABLE = 1,
 	QCA_ATTR_ROAM_CONTROL_STATUS = 2,
 	QCA_ATTR_ROAM_CONTROL_CLEAR_ALL = 3,
-	QCA_ATTR_ROAM_CONTROL_FREQ_LIST_SCHEME= 4,
+	QCA_ATTR_ROAM_CONTROL_FREQ_LIST_SCHEME = 4,
 	QCA_ATTR_ROAM_CONTROL_SCAN_PERIOD = 5,
 	QCA_ATTR_ROAM_CONTROL_FULL_SCAN_PERIOD = 6,
 	QCA_ATTR_ROAM_CONTROL_TRIGGERS = 7,
@@ -5579,6 +5899,10 @@
 	QCA_ATTR_ROAM_CONTROL_HO_DELAY_FOR_RX = 25,
 	QCA_ATTR_ROAM_CONTROL_FULL_SCAN_NO_REUSE_PARTIAL_SCAN_FREQ = 26,
 	QCA_ATTR_ROAM_CONTROL_FULL_SCAN_6GHZ_ONLY_ON_PRIOR_DISCOVERY = 27,
+	QCA_ATTR_ROAM_CONTROL_CONNECTED_LOW_RSSI_THRESHOLD = 28,
+	QCA_ATTR_ROAM_CONTROL_CANDIDATE_ROAM_RSSI_DIFF = 29,
+	QCA_ATTR_ROAM_CONTROL_6GHZ_CANDIDATE_ROAM_RSSI_DIFF = 30,
+	QCA_ATTR_ROAM_CONTROL_CONNECTED_HIGH_RSSI_OFFSET = 31,
 
 	/* keep last */
 	QCA_ATTR_ROAM_CONTROL_AFTER_LAST,
@@ -8012,7 +8336,12 @@
 enum qca_wlan_tdls_caps_features_supported {
 	WIFI_TDLS_SUPPORT = (1 << (0)),
 	WIFI_TDLS_EXTERNAL_CONTROL_SUPPORT = (1 << (1)),
-	WIFI_TDLS_OFFCHANNEL_SUPPORT = (1 << (2))
+	WIFI_TDLS_OFFCHANNEL_SUPPORT = (1 << (2)),
+
+	/* Indicates if the TDLS session can be formed with the peer using
+	 * higher bandwidth than the bandwidth of the AP path.
+	 */
+	WIFI_TDLS_WIDER_BW_SUPPORT = (1 << (3)),
 };
 
 /**
@@ -8225,6 +8554,30 @@
 	 * This attribute is used and optional for ndp indication.
 	 */
 	QCA_WLAN_VENDOR_ATTR_NDP_SERVICE_ID = 31,
+	/* Unsigned 8-bit value for Cipher Suite capabilities.
+	 * u8 attribute.
+	 * This attribute is used and optional in ndp request, ndp response,
+	 * ndp indication, and ndp confirm.
+	 * This attribute is used to indicate the Capabilities field of Cipher
+	 * Suite Information attribute (CSIA) of NDP frames as defined in
+	 * Wi-Fi Aware Specification v4.0, 9.5.21.2, Table 122.
+	 * Firmware can accept or ignore any of the capability bits.
+	 */
+	QCA_WLAN_VENDOR_ATTR_NDP_CSIA_CAPABILITIES = 32,
+	/* Indicate that GTK protection is required for NDP.
+	 * NLA_FLAG attribute.
+	 * This attribute can be used in ndp request, ndp response, ndp
+	 * indication, and ndp confirm.
+	 * GTK protection required is indicated in the NDPE attribute of NAN
+	 * action frame (NAF) during NDP negotiation as defined in
+	 * Wi-Fi Aware Specification v4.0, 9.5.16.2.
+	 * If the device and peer supports GTKSA and if GTK protection required
+	 * bit is set in NDPE IE, devices will share GTK to each other in SKDA
+	 * of Data Path Security Confirm and Data Path Security Install frames
+	 * of NDP negotiation to send and receive protected group addressed data
+	 * frames from each other.
+	 */
+	QCA_WLAN_VENDOR_ATTR_NDP_GTK_REQUIRED = 33,
 
 	/* keep last */
 	QCA_WLAN_VENDOR_ATTR_NDP_PARAMS_AFTER_LAST,
@@ -8877,6 +9230,31 @@
 };
 
 /**
+ * enum qca_wlan_ttlm_negotiation_support: TID-To-Link Mapping Negotiation
+ * support
+ * @QCA_WLAN_TTLM_DISABLE: TTLM disabled
+ * @QCA_WLAN_TTLM_SAME_LINK_SET: Mapping of all TIDs to the same link set,
+ * both DL and UL
+ * @QCA_WLAN_TTLM_SAME_DIFF_LINK_SET: Mapping of each TID to the same or
+ * different link set
+ */
+enum qca_wlan_ttlm_negotiation_support {
+	QCA_WLAN_TTLM_DISABLE = 0,
+	QCA_WLAN_TTLM_SAME_LINK_SET = 1,
+	QCA_WLAN_TTLM_SAME_DIFF_LINK_SET = 2,
+};
+
+/**
+ * enum qca_coex_traffic_shaping_mode: Coex traffic shaping mode
+ * @QCA_COEX_TRAFFIC_SHAPING_MODE_DISABLE: Coex policies disabled
+ * @QCA_COEX_TRAFFIC_SHAPING_MODE_ENABLE: All coex policies enabled
+ */
+enum qca_coex_traffic_shaping_mode {
+	QCA_COEX_TRAFFIC_SHAPING_MODE_DISABLE = 0,
+	QCA_COEX_TRAFFIC_SHAPING_MODE_ENABLE = 1,
+};
+
+/**
  * enum qca_wlan_vendor_attr_omi_tx: Represents attributes for HE and
  * EHT operating mode control transmit request. These attributes are
  * sent as part of QCA_WLAN_VENDOR_ATTR_WIFI_TEST_CONFIG_OMI_TX and
@@ -9593,6 +9971,24 @@
 	 */
 	QCA_WLAN_VENDOR_ATTR_WIFI_TEST_CONFIG_EHT_MLO_STR_TX = 70,
 
+	/* Nested attribute to indicate EHT MLO links on which powersave to be
+	 * enabled. It contains link ID attributes. These nested attributes are
+	 * of the type u8 and are used to enable the powersave on associated
+	 * MLO links corresponding to the link IDs provided in the command.
+	 * This attribute is used to configure the testbed device.
+	 */
+	QCA_WLAN_VENDOR_ATTR_WIFI_TEST_CONFIG_EHT_MLO_LINK_POWER_SAVE = 71,
+
+	/* 8-bit unsigned value to configure the MLD ID of the BSS whose link
+	 * info is requested in the ML Probe Request frame. In the MLO-MBSSID
+	 * testcase, STA can request information of non-Tx BSS through Tx BSS
+	 * by configuring non-Tx BSS MLD ID within the ML probe request that
+	 * is transmitted via host initiated scan request.
+	 *
+	 * This attribute is used for testing purposes.
+	 */
+	QCA_WLAN_VENDOR_ATTR_WIFI_TEST_CONFIG_MLD_ID_ML_PROBE_REQ = 72,
+
 	/* keep last */
 	QCA_WLAN_VENDOR_ATTR_WIFI_TEST_CONFIG_AFTER_LAST,
 	QCA_WLAN_VENDOR_ATTR_WIFI_TEST_CONFIG_MAX =
@@ -11620,6 +12016,12 @@
 	 */
 	QCA_WLAN_VENDOR_ATTR_ADD_STA_NODE_AUTH_ALGO = 2,
 
+	/*
+	 * This flag attribute is set if the node being added is an
+	 * MLD STA node.
+	 */
+	QCA_WLAN_VENDOR_ATTR_ADD_STA_NODE_IS_ML = 3,
+
 	/* keep last */
 	QCA_WLAN_VENDOR_ATTR_ADD_STA_NODE_PARAM_AFTER_LAST,
 	QCA_WLAN_VENDOR_ATTR_ADD_STA_NODE_PARAM_MAX =
@@ -11870,7 +12272,7 @@
  * the disconnected state.
  *
  * @QCA_WLAN_VENDOR_ATTR_GET_STA_INFO_TARGET_POWER_5G_MCS0: u32, used in the
- * STA mode. This represents the Target power in dBm for for transmissions done
+ * STA mode. This represents the Target power in dBm for transmissions done
  * to the AP in 5 GHz at MCS0 rate. This data is maintained per connect session.
  * Represents the count of last connected session, when queried in the
  * disconnected state.
@@ -12208,7 +12610,7 @@
 };
 
 /**
- * enum qca_wlan_tspec_ack_policy - MAC acknowledgement policy in TSPEC
+ * enum qca_wlan_tspec_ack_policy - MAC acknowledgment policy in TSPEC
  * As what is defined in IEEE Std 802.11-2016, Table 9-141.
  *
  * Values for %QCA_WLAN_VENDOR_ATTR_CONFIG_TSPEC_ACK_POLICY.
@@ -13018,26 +13420,38 @@
  * enum qca_wlan_roam_stats_frame_subtype - Roam frame subtypes. These values
  * are used by the attribute %QCA_WLAN_VENDOR_ATTR_ROAM_STATS_FRAME_SUBTYPE.
  *
- * @QCA_WLAN_ROAM_STATS_FRAME_SUBTYPE_PREAUTH: Pre-authentication frame
- * @QCA_WLAN_ROAM_STATS_FRAME_SUBTYPE_REASSOC: Reassociation frame
+ * @QCA_WLAN_ROAM_STATS_FRAME_SUBTYPE_AUTH_RESP: Authentication Response frame
+ * @QCA_WLAN_ROAM_STATS_FRAME_SUBTYPE_REASSOC_RESP: Reassociation Response frame
  * @QCA_WLAN_ROAM_STATS_FRAME_SUBTYPE_EAPOL_M1: EAPOL-Key M1 frame
  * @QCA_WLAN_ROAM_STATS_FRAME_SUBTYPE_EAPOL_M2: EAPOL-Key M2 frame
  * @QCA_WLAN_ROAM_STATS_FRAME_SUBTYPE_EAPOL_M3: EAPOL-Key M3 frame
  * @QCA_WLAN_ROAM_STATS_FRAME_SUBTYPE_EAPOL_M4: EAPOL-Key M4 frame
  * @QCA_WLAN_ROAM_STATS_FRAME_SUBTYPE_EAPOL_GTK_M1: EAPOL-Key GTK M1 frame
  * @QCA_WLAN_ROAM_STATS_FRAME_SUBTYPE_EAPOL_GTK_M2: EAPOL-Key GTK M2 frame
+ * @QCA_WLAN_ROAM_STATS_FRAME_SUBTYPE_AUTH_REQ: Authentication Request frame
+ * @QCA_WLAN_ROAM_STATS_FRAME_SUBTYPE_REASSOC_REQ: Reassociation Request frame
  */
 enum qca_wlan_roam_stats_frame_subtype {
-	QCA_WLAN_ROAM_STATS_FRAME_SUBTYPE_PREAUTH = 1,
-	QCA_WLAN_ROAM_STATS_FRAME_SUBTYPE_REASSOC = 2,
+	QCA_WLAN_ROAM_STATS_FRAME_SUBTYPE_AUTH_RESP = 1,
+	QCA_WLAN_ROAM_STATS_FRAME_SUBTYPE_REASSOC_RESP = 2,
 	QCA_WLAN_ROAM_STATS_FRAME_SUBTYPE_EAPOL_M1 = 3,
 	QCA_WLAN_ROAM_STATS_FRAME_SUBTYPE_EAPOL_M2 = 4,
 	QCA_WLAN_ROAM_STATS_FRAME_SUBTYPE_EAPOL_M3 = 5,
 	QCA_WLAN_ROAM_STATS_FRAME_SUBTYPE_EAPOL_M4 = 6,
 	QCA_WLAN_ROAM_STATS_FRAME_SUBTYPE_EAPOL_GTK_M1 = 7,
 	QCA_WLAN_ROAM_STATS_FRAME_SUBTYPE_EAPOL_GTK_M2 = 8,
+	QCA_WLAN_ROAM_STATS_FRAME_SUBTYPE_AUTH_REQ = 9,
+	QCA_WLAN_ROAM_STATS_FRAME_SUBTYPE_REASSOC_REQ = 10,
 };
 
+/* Compatibility defines for previously used names.
+ * These values should not be used in any new implementation.
+ */
+#define QCA_WLAN_ROAM_STATS_FRAME_SUBTYPE_PREAUTH \
+	QCA_WLAN_ROAM_STATS_FRAME_SUBTYPE_AUTH_RESP
+#define QCA_WLAN_ROAM_STATS_FRAME_SUBTYPE_REASSOC \
+	QCA_WLAN_ROAM_STATS_FRAME_SUBTYPE_REASSOC_RESP
+
 /**
  * enum roam_frame_status - Specifies the valid values the vendor roam frame
  * attribute QCA_WLAN_VENDOR_ATTR_ROAM_STATS_FRAME_STATUS can take.
@@ -13074,6 +13488,13 @@
 	QCA_WLAN_VENDOR_ATTR_ROAM_STATS_FRAME_TIMESTAMP = 3,
 	/* Attribute used for padding for 64-bit alignment */
 	QCA_WLAN_VENDOR_ATTR_ROAM_STATS_FRAME_PAD = 4,
+	/* This attribute indicates a 6-byte MAC address representing
+	 * the BSSID of the AP.
+	 * For non-MLO scenario, it indicates the AP BSSID.
+	 * For MLO scenario, it indicates the AP BSSID which may be the primary
+	 * link BSSID or a nonprimary link BSSID.
+	 */
+	QCA_WLAN_VENDOR_ATTR_ROAM_STATS_FRAME_BSSID = 5,
 
 	/* keep last */
 	QCA_WLAN_VENDOR_ATTR_ROAM_STATS_FRAME_INFO_AFTER_LAST,
@@ -13285,6 +13706,38 @@
 	QCA_WLAN_VENDOR_ATTR_ROAM_STATS_FRAME_INFO = 43,
 	/* Attribute used for padding for 64-bit alignment */
 	QCA_WLAN_VENDOR_ATTR_ROAM_STATS_PAD = 44,
+	/* 6-byte MAC address used by the driver to send roam stats information
+	 * of the original AP BSSID. The original AP is the connected AP before
+	 * roam happens, regardless of the roam resulting in success or failure.
+	 * This attribute is only present when
+	 * QCA_WLAN_VENDOR_ATTR_ROAM_STATS_ROAM_STATUS has a value of
+	 * 0 (success) or 1 (failure).
+	 * For non-MLO scenario, it indicates the original connected AP BSSID.
+	 * For MLO scenario, it indicates the original BSSID of the link
+	 * for which the reassociation occurred during the roam.
+	 */
+	QCA_WLAN_VENDOR_ATTR_ROAM_STATS_ORIGINAL_BSSID = 45,
+	/* 6-byte MAC address used by the driver to send roam stats information
+	 * of the roam candidate AP BSSID when roam failed. This is only present
+	 * when QCA_WLAN_VENDOR_ATTR_ROAM_STATS_ROAM_STATUS has a value of
+	 * 1 (failure). If the firmware updates more than one candidate AP BSSID
+	 * to the driver, the driver only fills the last candidate AP BSSID and
+	 * reports it to user space.
+	 * For non-MLO scenario, it indicates the last candidate AP BSSID.
+	 * For MLO scenario, it indicates the AP BSSID which may be the primary
+	 * link BSSID or a nonprimary link BSSID.
+	 */
+	QCA_WLAN_VENDOR_ATTR_ROAM_STATS_CANDIDATE_BSSID = 46,
+	/* 6-byte MAC address used by the driver to send roam stats information
+	 * of the roamed AP BSSID when roam succeeds. This is only present when
+	 * QCA_WLAN_VENDOR_ATTR_ROAM_STATS_ROAM_STATUS has a value of
+	 * 0 (success).
+	 * For non-MLO scenario, it indicates the new AP BSSID to which has
+	 * been successfully roamed.
+	 * For MLO scenario, it indicates the new AP BSSID of the link on
+	 * which the reassociation occurred during the roam.
+	 */
+	QCA_WLAN_VENDOR_ATTR_ROAM_STATS_ROAMED_BSSID = 47,
 
 	/* keep last */
 	QCA_WLAN_VENDOR_ATTR_ROAM_STATS_AFTER_LAST,
@@ -13706,7 +14159,7 @@
 	/* keep last */
 	QCA_WLAN_VENDOR_ATTR_ROAM_EVENTS_AFTER_LAST,
 	QCA_WLAN_VENDOR_ATTR_ROAM_EVENTS_MAX =
-	QCA_WLAN_VENDOR_ATTR_ROAM_EVENTS_AFTER_LAST -1,
+	QCA_WLAN_VENDOR_ATTR_ROAM_EVENTS_AFTER_LAST - 1,
 };
 
 /**
@@ -14833,12 +15286,25 @@
  * MLD MAC address of the peer.
  * @QCA_WLAN_VENDOR_ATTR_MLO_PEER_PRIM_NETDEV_EVENT_PRIM_IFINDEX: u32 attribute,
  * used to pass ifindex of the primary netdev.
+ * @QCA_WLAN_VENDOR_ATTR_MLO_PEER_PRIM_NETDEV_EVENT_MLD_IFINDEX: u32 attribute,
+ * used to pass ifindex of the MLD netdev.
+ * @QCA_WLAN_VENDOR_ATTR_MLO_PEER_PRIM_NETDEV_EVENT_NUM_LINKS: u8 attribute,
+ * used to indicate the number of links that the non-AP MLD negotiated to be
+ * used in the ML connection.
+ * @QCA_WLAN_VENDOR_ATTR_MLO_PEER_PRIM_NETDEV_EVENT_LINK_INFO: Nested
+ * attribute, contains information regarding links of the non-AP MLD.
+ * User applications need to know all the links of a non-AP MLD that are
+ * participating in the ML association. The possible attributes inside this
+ * attribute are defined in enum qca_wlan_vendor_attr_mlo_link_info.
  */
 enum qca_wlan_vendor_attr_mlo_peer_prim_netdev_event {
 	QCA_WLAN_VENDOR_ATTR_MLO_PEER_PRIM_NETDEV_EVENT_INVALID = 0,
 	QCA_WLAN_VENDOR_ATTR_MLO_PEER_PRIM_NETDEV_EVENT_MACADDR = 1,
 	QCA_WLAN_VENDOR_ATTR_MLO_PEER_PRIM_NETDEV_EVENT_MLD_MAC_ADDR = 2,
 	QCA_WLAN_VENDOR_ATTR_MLO_PEER_PRIM_NETDEV_EVENT_PRIM_IFINDEX = 3,
+	QCA_WLAN_VENDOR_ATTR_MLO_PEER_PRIM_NETDEV_EVENT_MLD_IFINDEX = 4,
+	QCA_WLAN_VENDOR_ATTR_MLO_PEER_PRIM_NETDEV_EVENT_NUM_LINKS = 5,
+	QCA_WLAN_VENDOR_ATTR_MLO_PEER_PRIM_NETDEV_EVENT_LINK_INFO = 6,
 
 	/* keep last */
 	QCA_WLAN_VENDOR_ATTR_MLO_PEER_PRIM_NETDEV_EVENT_AFTER_LAST,
@@ -14847,6 +15313,27 @@
 };
 
 /**
+ * enum qca_wlan_vendor_attr_mlo_link_info - Defines attributes for
+ * non-AP MLD link parameters used by the attribute
+ * %QCA_WLAN_VENDOR_ATTR_MLO_PEER_PRIM_NETDEV_EVENT_LINK_INFO.
+ *
+ * @QCA_WLAN_VENDOR_ATTR_MLO_LINK_INFO_IFINDEX: u32 attribute, used
+ * to pass the netdev ifindex of the non-AP MLD link.
+ * @QCA_WLAN_VENDOR_ATTR_MLO_LINK_INFO_MACADDR: 6 byte MAC address of
+ * the non-AP MLD link.
+ */
+enum qca_wlan_vendor_attr_mlo_link_info {
+	QCA_WLAN_VENDOR_ATTR_MLO_LINK_INFO_INVALID = 0,
+	QCA_WLAN_VENDOR_ATTR_MLO_LINK_INFO_IFINDEX = 1,
+	QCA_WLAN_VENDOR_ATTR_MLO_LINK_INFO_MACADDR = 2,
+
+	/* keep last */
+	QCA_WLAN_VENDOR_ATTR_MLO_LINK_INFO_AFTER_LAST,
+	QCA_WLAN_VENDOR_ATTR_MLO_LINK_INFO_MAX =
+	QCA_WLAN_VENDOR_ATTR_MLO_LINK_INFO_AFTER_LAST - 1,
+};
+
+/**
  * enum qca_wlan_vendor_attr_afc_freq_psd_info: This enum is used with
  * nested attributes QCA_WLAN_VENDOR_ATTR_AFC_RESP_FREQ_PSD_INFO and
  * QCA_WLAN_VENDOR_ATTR_AFC_EVENT_FREQ_RANGE_LIST to update the frequency range
@@ -15574,7 +16061,6 @@
  * A bitmap of the removed setup links link IDs.
  */
 enum qca_wlan_vendor_attr_link_reconfig {
-
 	QCA_WLAN_VENDOR_ATTR_LINK_RECONFIG_INVALID = 0,
 	QCA_WLAN_VENDOR_ATTR_LINK_RECONFIG_AP_MLD_ADDR = 1,
 	QCA_WLAN_VENDOR_ATTR_LINK_RECONFIG_REMOVED_LINKS = 2,
@@ -15603,4 +16089,517 @@
 	QCA_WLAN_VENDOR_ATTR_TDLS_DISC_RSP_EXT_AFTER_LAST - 1,
 };
 
+/**
+ * enum qca_wlan_vendor_tdls_state - Represents the possible TDLS states.
+ *
+ * @QCA_WLAN_VENDOR_TDLS_STATE_DISABLED: TDLS is not enabled, default status
+ * for all stations.
+ *
+ * @QCA_WLAN_VENDOR_TDLS_STATE_ENABLED: TDLS is enabled, but not yet tried to
+ * establish the session.
+ *
+ * @QCA_WLAN_VENDOR_TDLS_STATE_ESTABLISHED: Direct link TDLS session is
+ * established.
+ *
+ * @QCA_WLAN_VENDOR_TDLS_STATE_ESTABLISHED_OFF_CHANNEL: Direct link TDLS
+ * session is established using MCC.
+ *
+ * @QCA_WLAN_VENDOR_TDLS_STATE_DROPPED: Direct link TDLS session was
+ * established, but is temporarily dropped currently.
+ *
+ * @QCA_WLAN_VENDOR_TDLS_STATE_FAILED: TDLS session is failed to establish.
+ */
+enum qca_wlan_vendor_tdls_state {
+	QCA_WLAN_VENDOR_TDLS_STATE_DISABLED = 1,
+	QCA_WLAN_VENDOR_TDLS_STATE_ENABLED = 2,
+	QCA_WLAN_VENDOR_TDLS_STATE_ESTABLISHED = 3,
+	QCA_WLAN_VENDOR_TDLS_STATE_ESTABLISHED_OFF_CHANNEL = 4,
+	QCA_WLAN_VENDOR_TDLS_STATE_DROPPED = 5,
+	QCA_WLAN_VENDOR_TDLS_STATE_FAILED = 6,
+};
+
+/**
+ * enum qca_wlan_vendor_tdls_reason - Represents the possible TDLS reasons.
+ *
+ * @QCA_WLAN_TDLS_REASON_SUCCESS: TDLS session is successfully established.
+ *
+ * @QCA_WLAN_TDLS_REASON_UNSPECIFIED: Unspecified reason.
+ *
+ * @QCA_WLAN_TDLS_REASON_NOT_SUPPORTED: TDLS is not supported.
+ *
+ * @QCA_WLAN_TDLS_REASON_UNSUPPORTED_BAND: The specified band is not supported.
+ *
+ * @QCA_WLAN_TDLS_REASON_NOT_BENEFICIAL: Packets going through AP is better
+ * than through direct link.
+ *
+ * @QCA_WLAN_TDLS_REASON_DROPPED_BY_REMOTE: Peer station doesn't want the TDLS
+ * session anymore.
+ */
+
+enum qca_wlan_vendor_tdls_reason {
+	QCA_WLAN_TDLS_REASON_SUCCESS = 0,
+	QCA_WLAN_TDLS_REASON_UNSPECIFIED = -1,
+	QCA_WLAN_TDLS_REASON_NOT_SUPPORTED = -2,
+	QCA_WLAN_TDLS_REASON_UNSUPPORTED_BAND = -3,
+	QCA_WLAN_TDLS_REASON_NOT_BENEFICIAL = -4,
+	QCA_WLAN_TDLS_REASON_DROPPED_BY_REMOTE = -5,
+};
+
+/**
+ * enum qca_wlan_vendor_attr_tdls_enable - Attributes used by
+ * %QCA_NL80211_VENDOR_SUBCMD_TDLS_ENABLE vendor command.
+ *
+ * @QCA_WLAN_VENDOR_ATTR_TDLS_ENABLE_MAC_ADDR: 6-byte MAC address of the peer
+ * station to enable the TDLS session. Optional attribute. The driver sends the
+ * TDLS session result as an asynchronous response using the command
+ * %QCA_NL80211_VENDOR_SUBCMD_TDLS_STATE when this peer MAC is provided in
+ * %QCA_NL80211_VENDOR_SUBCMD_TDLS_ENABLE command.
+ *
+ * @QCA_WLAN_VENDOR_ATTR_TDLS_ENABLE_CHANNEL: u32 attribute. Indicates the
+ * channel on which the TDLS session to be established. Required only when
+ * %QCA_WLAN_VENDOR_ATTR_TDLS_ENABLE_MAC_ADDR is present.
+ *
+ * @QCA_WLAN_VENDOR_ATTR_TDLS_ENABLE_GLOBAL_OPERATING_CLASS: u32 attribute.
+ * Indicates the global operating class of the TDLS session to be established.
+ * Required only when %QCA_WLAN_VENDOR_ATTR_TDLS_ENABLE_MAC_ADDR is present.
+ *
+ * @QCA_WLAN_VENDOR_ATTR_TDLS_ENABLE_MAX_LATENCY_MS: u32 attribute. Indicates
+ * the maximum latency of the WLAN packets to be transmitted/received in
+ * milliseconds on TDLS session. Required only when
+ * %QCA_WLAN_VENDOR_ATTR_TDLS_ENABLE_MAC_ADDR is present.
+ *
+ * @QCA_WLAN_VENDOR_ATTR_TDLS_ENABLE_MIN_BANDWIDTH_KBPS: u32 attribute.
+ * Indicates the minimum bandwidth to be used to establish the TDLS session
+ * in kbps. Required only when %QCA_WLAN_VENDOR_ATTR_TDLS_ENABLE_MAC_ADDR is
+ * present.
+ */
+enum qca_wlan_vendor_attr_tdls_enable {
+	QCA_WLAN_VENDOR_ATTR_TDLS_ENABLE_INVALID = 0,
+	QCA_WLAN_VENDOR_ATTR_TDLS_ENABLE_MAC_ADDR = 1,
+	QCA_WLAN_VENDOR_ATTR_TDLS_ENABLE_CHANNEL = 2,
+	QCA_WLAN_VENDOR_ATTR_TDLS_ENABLE_GLOBAL_OPERATING_CLASS = 3,
+	QCA_WLAN_VENDOR_ATTR_TDLS_ENABLE_MAX_LATENCY_MS = 4,
+	QCA_WLAN_VENDOR_ATTR_TDLS_ENABLE_MIN_BANDWIDTH_KBPS = 5,
+
+	/* keep last */
+	QCA_WLAN_VENDOR_ATTR_TDLS_ENABLE_AFTER_LAST,
+	QCA_WLAN_VENDOR_ATTR_TDLS_ENABLE_MAX =
+	QCA_WLAN_VENDOR_ATTR_TDLS_ENABLE_AFTER_LAST - 1,
+};
+
+/**
+ * enum qca_wlan_vendor_attr_tdls_disable - Attributes used by
+ * %QCA_NL80211_VENDOR_SUBCMD_TDLS_DISABLE vendor command.
+ *
+ * @QCA_WLAN_VENDOR_ATTR_TDLS_DISABLE_MAC_ADDR: 6-byte MAC address of the peer
+ * station to disable the TDLS session. Optional attribute.
+ */
+enum qca_wlan_vendor_attr_tdls_disable {
+	QCA_WLAN_VENDOR_ATTR_TDLS_DISABLE_INVALID = 0,
+	QCA_WLAN_VENDOR_ATTR_TDLS_DISABLE_MAC_ADDR = 1,
+
+	/* keep last */
+	QCA_WLAN_VENDOR_ATTR_TDLS_DISABLE_AFTER_LAST,
+	QCA_WLAN_VENDOR_ATTR_TDLS_DISABLE_MAX =
+	QCA_WLAN_VENDOR_ATTR_TDLS_DISABLE_AFTER_LAST - 1,
+};
+
+/**
+ * enum qca_wlan_vendor_attr_tdls_get_status - Attributes used by
+ * %QCA_NL80211_VENDOR_SUBCMD_TDLS_GET_STATUS vendor command.
+ *
+ * @QCA_WLAN_VENDOR_ATTR_TDLS_GET_STATUS_MAC_ADDR: 6-byte MAC address of the
+ * peer station. Optional attribute. Used in
+ * %QCA_NL80211_VENDOR_SUBCMD_TDLS_GET_STATUS request and response.
+ *
+ * @QCA_WLAN_VENDOR_ATTR_TDLS_GET_STATUS_STATE: u32 attribute. Indicates the
+ * TDLS session state with the peer specified in
+ * %QCA_WLAN_VENDOR_ATTR_TDLS_GET_STATUS_MAC_ADDR. Uses the values from
+ * enum qca_wlan_vendor_tdls_state. Used in
+ * %QCA_NL80211_VENDOR_SUBCMD_TDLS_GET_STATUS response.
+ *
+ * @QCA_WLAN_VENDOR_ATTR_TDLS_GET_STATUS_REASON: s32 attribute. Indicates the
+ * reason for the TDLS session state indicated in
+ * %QCA_WLAN_VENDOR_ATTR_TDLS_GET_STATUS_STATE. Uses the values from enum
+ * qca_wlan_vendor_tdls_reason. Used in
+ * %QCA_NL80211_VENDOR_SUBCMD_TDLS_GET_STATUS response.
+ *
+ * @QCA_WLAN_VENDOR_ATTR_TDLS_GET_STATUS_CHANNEL: u32 attribute. Indicates the
+ * channel of the TDLS session established with
+ * %QCA_WLAN_VENDOR_ATTR_TDLS_GET_STATUS_MAC_ADDR. Used in
+ * %QCA_NL80211_VENDOR_SUBCMD_TDLS_GET_STATUS response.
+ *
+ * @QCA_WLAN_VENDOR_ATTR_TDLS_GET_STATUS_GLOBAL_OPERATING_CLASS: u32 attribute.
+ * Indicates the global operating class of the TDLS session established with
+ * %QCA_WLAN_VENDOR_ATTR_TDLS_GET_STATUS_MAC_ADDR. Used in
+ * %QCA_NL80211_VENDOR_SUBCMD_TDLS_GET_STATUS response.
+ *
+ * @QCA_WLAN_VENDOR_ATTR_TDLS_GET_STATUS_NUM_SESSIONS: u32 attribute. Indicates
+ * the current number of active TDLS sessions. This is indicated in the response
+ * when %QCA_NL80211_VENDOR_SUBCMD_TDLS_GET_STATUS is requested with
+ * %NL80211_ATTR_VENDOR_DATA as an empty nested attribute.
+ *
+ * @QCA_WLAN_VENDOR_ATTR_TDLS_GET_STATUS_AVAILABLE: Flag attribute. Indicates
+ * whether the driver can initiate new TDLS session. This is indicated in the
+ * response when %QCA_NL80211_VENDOR_SUBCMD_TDLS_GET_STATUS is requested with
+ * %NL80211_ATTR_VENDOR_DATA as an empty nested attribute.
+ */
+enum qca_wlan_vendor_attr_tdls_get_status {
+	QCA_WLAN_VENDOR_ATTR_TDLS_GET_STATUS_INVALID = 0,
+	QCA_WLAN_VENDOR_ATTR_TDLS_GET_STATUS_MAC_ADDR = 1,
+	QCA_WLAN_VENDOR_ATTR_TDLS_GET_STATUS_STATE = 2,
+	QCA_WLAN_VENDOR_ATTR_TDLS_GET_STATUS_REASON = 3,
+	QCA_WLAN_VENDOR_ATTR_TDLS_GET_STATUS_CHANNEL = 4,
+	QCA_WLAN_VENDOR_ATTR_TDLS_GET_STATUS_GLOBAL_OPERATING_CLASS = 5,
+	QCA_WLAN_VENDOR_ATTR_TDLS_GET_STATUS_NUM_SESSIONS = 6,
+	QCA_WLAN_VENDOR_ATTR_TDLS_GET_STATUS_AVAILABLE = 7,
+
+	/* keep last */
+	QCA_WLAN_VENDOR_ATTR_TDLS_GET_STATUS_AFTER_LAST,
+	QCA_WLAN_VENDOR_ATTR_TDLS_GET_STATUS_MAX =
+	QCA_WLAN_VENDOR_ATTR_TDLS_GET_STATUS_AFTER_LAST - 1,
+};
+
+/**
+ * enum qca_wlan_vendor_attr_tdls_state - Attributes used by
+ * %QCA_NL80211_VENDOR_SUBCMD_TDLS_STATE vendor command.
+ *
+ * @QCA_WLAN_VENDOR_ATTR_TDLS_STATE_MAC_ADDR: 6-byte MAC address of the
+ * peer station. Required attribute.
+ *
+ * @QCA_WLAN_VENDOR_ATTR_TDLS_STATE_CURRENT_STATE: u32 attribute. Indicates
+ * the current TDLS state. Required attribute. Uses the values from
+ * enum qca_wlan_vendor_tdls_state.
+ *
+ * @QCA_WLAN_VENDOR_ATTR_TDLS_STATE_REASON: s32 attribute. Indicates the
+ * reason of the current TDLS session state. Required attribute. Uses the values
+ * from enum qca_wlan_vendor_tdls_reason.
+ *
+ * @QCA_WLAN_VENDOR_ATTR_TDLS_STATE_CHANNEL: u32 attribute. Indicates the
+ * TDLS session channel. Required attribute.
+ *
+ * @QCA_WLAN_VENDOR_ATTR_TDLS_STATE_GLOBAL_OPERATING_CLASS: u32 attribute.
+ * Indicates the TDLS session global operating class. Required attribute.
+ */
+enum qca_wlan_vendor_attr_tdls_state {
+	QCA_WLAN_VENDOR_ATTR_TDLS_STATE_INVALID = 0,
+	QCA_WLAN_VENDOR_ATTR_TDLS_STATE_MAC_ADDR = 1,
+	QCA_WLAN_VENDOR_ATTR_TDLS_STATE_NEW_STATE = 2,
+	QCA_WLAN_VENDOR_ATTR_TDLS_STATE_REASON = 3,
+	QCA_WLAN_VENDOR_ATTR_TDLS_STATE_CHANNEL = 4,
+	QCA_WLAN_VENDOR_ATTR_TDLS_STATE_GLOBAL_OPERATING_CLASS = 5,
+
+	/* keep last */
+	QCA_WLAN_VENDOR_ATTR_TDLS_STATE_AFTER_LAST,
+	QCA_WLAN_VENDOR_ATTR_TDLS_STATE_MAX =
+	QCA_WLAN_VENDOR_ATTR_TDLS_STATE_AFTER_LAST - 1,
+};
+
+/*
+ * enum qca_wlan_vendor_opm_mode - Modes supported by
+ * %QCA_WLAN_VENDOR_ATTR_CONFIG_OPTIMIZED_POWER_MANAGEMENT vendor attribute.
+ *
+ * @QCA_WLAN_VENDOR_OPM_MODE_DISABLE: OPM Disabled
+ * @QCA_WLAN_VENDOR_OPM_MODE_ENABLE: OPM Enabled
+ * @QCA_WLAN_VENDOR_OPM_MODE_USER_DEFINED: User defined mode which allows user
+ *	to configure power save inactivity timeout and speculative wake up
+ *	interval through %QCA_WLAN_VENDOR_ATTR_CONFIG_OPM_ITO and
+ *	%QCA_WLAN_VENDOR_ATTR_CONFIG_OPM_SPEC_WAKE_INTERVAL attributes.
+ */
+
+enum qca_wlan_vendor_opm_mode {
+	QCA_WLAN_VENDOR_OPM_MODE_DISABLE = 0,
+	QCA_WLAN_VENDOR_OPM_MODE_ENABLE = 1,
+	QCA_WLAN_VENDOR_OPM_MODE_USER_DEFINED = 2,
+};
+
+/*
+ * enum qca_wlan_vendor_tx_latency_type - Represents the possible latency
+ * types.
+ *
+ * @QCA_WLAN_VENDOR_TX_LATENCY_TYPE_DRIVER: Per MSDU latency
+ * from: An MSDU is presented to the driver
+ * to: the MSDU is queued into TCL SRNG
+ *
+ * @QCA_WLAN_VENDOR_TX_LATENCY_TYPE_RING: Per MSDU latency
+ * from: the MSDU is queued into TCL SRNG
+ * to: the MSDU is released by the driver
+ *
+ * @QCA_WLAN_VENDOR_TX_LATENCY_TYPE_HW: Per MSDU latency
+ * from: the MSDU is presented to the hardware
+ * to: the MSDU is released by the hardware
+ *
+ * @QCA_WLAN_VENDOR_TX_LATENCY_TYPE_CCA: Per PPDU latency
+ * The time spent on Clear Channel Assessment, the maximum value is 50000 (us)
+ * from: A PPDU is presented to the hardware LMAC
+ * to: over-the-air transmission is started for the PPDU
+ */
+enum qca_wlan_vendor_tx_latency_type {
+	QCA_WLAN_VENDOR_TX_LATENCY_TYPE_DRIVER = 0,
+	QCA_WLAN_VENDOR_TX_LATENCY_TYPE_RING = 1,
+	QCA_WLAN_VENDOR_TX_LATENCY_TYPE_HW = 2,
+	QCA_WLAN_VENDOR_TX_LATENCY_TYPE_CCA = 3,
+};
+
+/**
+ * enum qca_wlan_vendor_attr_tx_latency_bucket - Definition of attributes
+ * used inside nested attributes
+ * %QCA_WLAN_VENDOR_ATTR_TX_LATENCY_BUCKETS and
+ * %QCA_WLAN_VENDOR_ATTR_TX_LATENCY_LINK_STAT_BUCKETS.
+ *
+ * @QCA_WLAN_VENDOR_ATTR_TX_LATENCY_BUCKET_TYPE: u8 attribute.
+ * Indicates the latency type.
+ * See enum qca_wlan_vendor_tx_latency_type for the supported types.
+ *
+ * @QCA_WLAN_VENDOR_ATTR_TX_LATENCY_BUCKET_GRANULARITY: u32 attribute.
+ * Indicates the granularity (in microseconds) of the distribution for the
+ * type (specified by %QCA_WLAN_VENDOR_ATTR_TX_LATENCY_BUCKET_TYPE), the value
+ * must be positive.
+ * If %QCA_WLAN_VENDOR_ATTR_TX_LATENCY_BUCKET_TYPE is
+ * %QCA_WLAN_VENDOR_TX_LATENCY_TYPE_CCA, the value must be an integer multiple
+ * of 1000, and the maximum allowed value is 15000 (us).
+ *
+ * @QCA_WLAN_VENDOR_ATTR_TX_LATENCY_BUCKET_AVERAGE: u32 attribute.
+ * Indicates the average of the latency (in microseconds) for the type
+ * (specified by %QCA_WLAN_VENDOR_ATTR_TX_LATENCY_BUCKET_TYPE) within a cycle.
+ * If there is no transmitted MSDUs/MPDUs during a cycle, this average is 0;
+ * otherwise, it represents the quotient of <accumulated latency of the
+ * transmitted MSDUs/MPDUs in a cycle> divided by <the number of the transmitted
+ * MSDUs/MPDUs in a cycle>.
+ *
+ * @QCA_WLAN_VENDOR_ATTR_TX_LATENCY_BUCKET_DISTRIBUTION:
+ * Array of u32, 4 elements in total, represents the latency distribution for
+ * the type (specified by %QCA_WLAN_VENDOR_ATTR_TX_LATENCY_BUCKET_TYPE).
+ * Each element holds the count of MSDUs/PPDUs (according to the latency type)
+ * within a range:
+ * element[0]: latency >= 0 && latency < granularity
+ * element[1]: latency >= granularity && latency < granularity * 2
+ * element[2]: latency >= granularity * 2 && latency < granularity * 3
+ * element[3]: latency >= granularity * 3
+ */
+enum qca_wlan_vendor_attr_tx_latency_bucket {
+	QCA_WLAN_VENDOR_ATTR_TX_LATENCY_BUCKET_INVALID = 0,
+	QCA_WLAN_VENDOR_ATTR_TX_LATENCY_BUCKET_TYPE = 1,
+	QCA_WLAN_VENDOR_ATTR_TX_LATENCY_BUCKET_GRANULARITY = 2,
+	QCA_WLAN_VENDOR_ATTR_TX_LATENCY_BUCKET_AVERAGE = 3,
+	QCA_WLAN_VENDOR_ATTR_TX_LATENCY_BUCKET_DISTRIBUTION = 4,
+
+	/* keep last */
+	QCA_WLAN_VENDOR_ATTR_TX_LATENCY_BUCKET_AFTER_LAST,
+	QCA_WLAN_VENDOR_ATTR_TX_LATENCY_BUCKET_MAX =
+	QCA_WLAN_VENDOR_ATTR_TX_LATENCY_BUCKET_AFTER_LAST - 1,
+};
+
+/**
+ * enum qca_wlan_vendor_attr_tx_latency_link - Definition of attributes
+ * used inside nested attribute %QCA_WLAN_VENDOR_ATTR_TX_LATENCY_LINKS.
+ *
+ * @QCA_WLAN_VENDOR_ATTR_TX_LATENCY_LINK_MAC_REMOTE: 6-byte MAC address.
+ * Indicates link MAC address of the remote peer. For example, when running
+ * in station mode, it's the BSSID of the link; while when running in AP
+ * mode, it's the link MAC address of the remote station.
+ *
+ * @QCA_WLAN_VENDOR_ATTR_TX_LATENCY_LINK_STAT_BUCKETS:
+ * Array of nested attribute.
+ * Represents the transmit latency statistics for the link specified by
+ * %QCA_WLAN_VENDOR_ATTR_TX_LATENCY_LINK_MAC_REMOTE.
+ * Each entry represents the statistics for one of the types defined in
+ * enum qca_wlan_vendor_tx_latency_type.
+ * Each defined type has and must have one entry.
+ * See enum qca_wlan_vendor_attr_tx_latency_bucket for nested attributes.
+ */
+enum qca_wlan_vendor_attr_tx_latency_link {
+	QCA_WLAN_VENDOR_ATTR_TX_LATENCY_LINK_INVALID = 0,
+	QCA_WLAN_VENDOR_ATTR_TX_LATENCY_LINK_MAC_REMOTE = 1,
+	QCA_WLAN_VENDOR_ATTR_TX_LATENCY_LINK_STAT_BUCKETS = 2,
+
+	/* keep last */
+	QCA_WLAN_VENDOR_ATTR_TX_LATENCY_LINK_AFTER_LAST,
+	QCA_WLAN_VENDOR_ATTR_TX_LATENCY_LINK_MAX =
+	QCA_WLAN_VENDOR_ATTR_TX_LATENCY_LINK_AFTER_LAST - 1,
+};
+
+/**
+ * enum qca_wlan_vendor_tx_latency_action - Represents the possible actions
+ * for %QCA_NL80211_VENDOR_SUBCMD_TX_LATENCY.
+ *
+ * @QCA_WLAN_VENDOR_TX_LATENCY_ACTION_DISABLE:
+ * Disable transmit latency monitoring.
+ *
+ * @QCA_WLAN_VENDOR_TX_LATENCY_ACTION_ENABLE:
+ * Enable transmit latency monitoring.
+ *
+ * @QCA_WLAN_VENDOR_TX_LATENCY_ACTION_GET:
+ * Get transmit latency statistics of the last cycle (period is specified by
+ * %QCA_WLAN_VENDOR_ATTR_TX_LATENCY_PERIOD).
+ */
+enum qca_wlan_vendor_tx_latency_action {
+	QCA_WLAN_VENDOR_TX_LATENCY_ACTION_DISABLE = 0,
+	QCA_WLAN_VENDOR_TX_LATENCY_ACTION_ENABLE = 1,
+	QCA_WLAN_VENDOR_TX_LATENCY_ACTION_GET = 2,
+};
+
+/**
+ * enum qca_wlan_vendor_attr_tx_latency - Definition of attributes used by
+ * %QCA_NL80211_VENDOR_SUBCMD_TX_LATENCY to configure, retrieve, and report
+ * per-link transmit latency statistics.
+ *
+ * There are 6 uses of %QCA_NL80211_VENDOR_SUBCMD_TX_LATENCY:
+ * 1) used as a command to enable the feature
+ * Precondition(s):
+ *	%QCA_WLAN_VENDOR_ATTR_TX_LATENCY_ACTION is
+ *	%QCA_WLAN_VENDOR_TX_LATENCY_ACTION_ENABLE
+ * Mandatory attribute(s):
+ *	%QCA_WLAN_VENDOR_ATTR_TX_LATENCY_ACTION,
+ *	%QCA_WLAN_VENDOR_ATTR_TX_LATENCY_PERIOD,
+ *	%QCA_WLAN_VENDOR_ATTR_TX_LATENCY_BUCKETS with nested attributes
+ *	%QCA_WLAN_VENDOR_ATTR_TX_LATENCY_BUCKET_TYPE,
+ *	%QCA_WLAN_VENDOR_ATTR_TX_LATENCY_BUCKET_GRANULARITY.
+ * Notes:
+ *	The driver will monitor the transmit latency for the active links
+ *	and save the statistics for each cycle (period is set by
+ *	%QCA_WLAN_VENDOR_ATTR_TX_LATENCY_PERIOD) when the feature is enabled.
+ *	Set flag %QCA_WLAN_VENDOR_ATTR_TX_LATENCY_PERIODIC_REPORT if periodical
+ *	report is required.
+ *
+ * 2) used as a command to disable the feature
+ * Precondition(s):
+ *	%QCA_WLAN_VENDOR_ATTR_TX_LATENCY_ACTION is
+ *	%QCA_WLAN_VENDOR_TX_LATENCY_ACTION_DISABLE
+ * Mandatory attribute(s):
+ *	%QCA_WLAN_VENDOR_ATTR_TX_LATENCY_ACTION
+ *
+ * 3) used as a command to retrieve the statistics for all the active links on
+ *    the requested interface
+ * Precondition(s):
+ *	%QCA_WLAN_VENDOR_ATTR_TX_LATENCY_ACTION is
+ *	QCA_WLAN_VENDOR_TX_LATENCY_ACTION_GET and
+ *	%QCA_WLAN_VENDOR_ATTR_TX_LATENCY_LINKS is NOT present.
+ * Mandatory attribute(s):
+ *	%QCA_WLAN_VENDOR_ATTR_TX_LATENCY_ACTION
+ * Notes:
+ *	The driver returns failure directly if the feature is not enabled or
+ *	there is no active link.
+ *	The driver returns the statistics of the last cycle in the case of
+ *	success.
+ *
+ * 4) used as a command to retrieve the statistics for the specified links
+ * Precondition(s):
+ *	%QCA_WLAN_VENDOR_ATTR_TX_LATENCY_ACTION is
+ *	QCA_WLAN_VENDOR_TX_LATENCY_ACTION_GET and
+ *	%QCA_WLAN_VENDOR_ATTR_TX_LATENCY_LINKS is present.
+ * Mandatory attribute(s):
+ *	%QCA_WLAN_VENDOR_ATTR_TX_LATENCY_ACTION,
+ *	%QCA_WLAN_VENDOR_ATTR_TX_LATENCY_LINKS, with nested attribute
+ *	%QCA_WLAN_VENDOR_ATTR_TX_LATENCY_LINK_MAC_REMOTE.
+ * Notes:
+ *	The driver returns failure directly if the feature is not enabled or
+ *	any of the links (specified by %QCA_WLAN_VENDOR_ATTR_TX_LATENCY_LINKS)
+ *	does not exist or is not in active state.
+ *
+ * 5) used as a command response for #3 or #4
+ * Precondition(s):
+ *	Userspace issues command #3 or #4, and the driver gets corresponding
+ *	statistics successfully.
+ * Mandatory attribute(s):
+ *	%QCA_WLAN_VENDOR_ATTR_TX_LATENCY_LINKS, with nested attributes
+ *	%QCA_WLAN_VENDOR_ATTR_TX_LATENCY_LINK_MAC_REMOTE,
+ *	%QCA_WLAN_VENDOR_ATTR_TX_LATENCY_LINK_STAT_BUCKETS with nested
+ *	attributes %QCA_WLAN_VENDOR_ATTR_TX_LATENCY_BUCKET_TYPE,
+ *	%QCA_WLAN_VENDOR_ATTR_TX_LATENCY_BUCKET_GRANULARITY,
+ *	%QCA_WLAN_VENDOR_ATTR_TX_LATENCY_BUCKET_AVERAGE and
+ *	%QCA_WLAN_VENDOR_ATTR_TX_LATENCY_BUCKET_DISTRIBUTION.
+ *
+ * 6) used as an asynchronous event to report the statistics periodically
+ * Precondition(s):
+ *	Userspace set flag %QCA_WLAN_VENDOR_ATTR_TX_LATENCY_PERIODIC_REPORT in
+ *	#1.
+ *	One or more links are in active state.
+ * Mandatory attribute(s):
+ *	%QCA_WLAN_VENDOR_ATTR_TX_LATENCY_LINKS, with nested attributes
+ *	%QCA_WLAN_VENDOR_ATTR_TX_LATENCY_LINK_MAC_REMOTE,
+ *	%QCA_WLAN_VENDOR_ATTR_TX_LATENCY_LINK_STAT_BUCKETS with nested
+ *	attributes %QCA_WLAN_VENDOR_ATTR_TX_LATENCY_BUCKET_TYPE,
+ *	%QCA_WLAN_VENDOR_ATTR_TX_LATENCY_BUCKET_GRANULARITY,
+ *	%QCA_WLAN_VENDOR_ATTR_TX_LATENCY_BUCKET_AVERAGE and
+ *	%QCA_WLAN_VENDOR_ATTR_TX_LATENCY_BUCKET_DISTRIBUTION.
+ *
+ * @QCA_WLAN_VENDOR_ATTR_TX_LATENCY_INVALID: Invalid attribute
+ *
+ * @QCA_WLAN_VENDOR_ATTR_TX_LATENCY_ACTION: u32 attribute.
+ * Action to take in this vendor command.
+ * See enum qca_wlan_vendor_tx_latency_action for supported actions.
+ *
+ * @QCA_WLAN_VENDOR_ATTR_TX_LATENCY_PERIODIC_REPORT: Flag attribute.
+ * Enable (flag attribute present) - The driver needs to report transmit latency
+ * statistics at the end of each statistical period.
+ * Disable (flag attribute not present) - The driver doesn't need to report
+ * transmit latency statistics periodically.
+ *
+ * @QCA_WLAN_VENDOR_ATTR_TX_LATENCY_PERIOD: u32 attribute.
+ * Indicates statistical period for transmit latency in terms of milliseconds,
+ * the minimal allowed value is 100 and the maximum allowed value is 60000.
+ *
+ * @QCA_WLAN_VENDOR_ATTR_TX_LATENCY_BUCKETS: Array of nested attribute.
+ * Each entry represents the latency buckets configuration for one of the types
+ * defined in enum qca_wlan_vendor_tx_latency_type.
+ * Each defined type has and must have one entry.
+ * See enum qca_wlan_vendor_attr_tx_latency_bucket for the list of
+ * supported attributes.
+ *
+ * @QCA_WLAN_VENDOR_ATTR_TX_LATENCY_LINKS: Array of nested attribute.
+ * Information of the links, each entry represents for one link.
+ * See enum qca_wlan_vendor_attr_tx_latency_link for the list of
+ * supported attributes for each entry.
+ */
+enum qca_wlan_vendor_attr_tx_latency {
+	QCA_WLAN_VENDOR_ATTR_TX_LATENCY_INVALID = 0,
+	QCA_WLAN_VENDOR_ATTR_TX_LATENCY_ACTION = 1,
+	QCA_WLAN_VENDOR_ATTR_TX_LATENCY_PERIODIC_REPORT = 2,
+	QCA_WLAN_VENDOR_ATTR_TX_LATENCY_PERIOD = 3,
+	QCA_WLAN_VENDOR_ATTR_TX_LATENCY_BUCKETS = 4,
+	QCA_WLAN_VENDOR_ATTR_TX_LATENCY_LINKS = 5,
+
+	/* keep last */
+	QCA_WLAN_VENDOR_ATTR_TX_LATENCY_AFTER_LAST,
+	QCA_WLAN_VENDOR_ATTR_TX_LATENCY_MAX =
+	QCA_WLAN_VENDOR_ATTR_TX_LATENCY_AFTER_LAST - 1,
+};
+
+/**
+ * enum qca_chan_width_update_type - Represents the possible values for
+ * %QCA_WLAN_VENDOR_ATTR_CONFIG_CHAN_WIDTH_UPDATE_TYPE.
+ *
+ * @QCA_CHAN_WIDTH_UPDATE_TYPE_TX_RX: The maximum allowed bandwidth change is
+ * applicable for both Tx and Rx paths. The driver shall conduct OMI operation
+ * as defined in 26.9 (Operating mode indication) or OMN operation as defined in
+ * 11.40 (Notification of operating mode changes) in IEEE P802.11-REVme/D2.0
+ * with AP to indicate the change in the maximum allowed operating bandwidth.
+ *
+ * @QCA_CHAN_WIDTH_UPDATE_TYPE_TX_ONLY: Limit the change in maximum allowed
+ * bandwidth only to Tx path. In this case the driver doesn't need to conduct
+ * OMI/OMN operation since %QCA_WLAN_VENDOR_ATTR_CONFIG_CHANNEL_WIDTH is
+ * expected to be less than the current connection maximum negotiated bandwidth.
+ * For example: Negotiated maximum bandwidth is 160 MHz and the new maximum
+ * bandwidth configured is 80 MHz, now the driver limits the maximum bandwidth
+ * to 80 MHz only in the Tx path.
+ *
+ * @QCA_CHAN_WIDTH_UPDATE_TYPE_TX_RX_EXT: This is similar to
+ * %QCA_CHAN_WIDTH_UPDATE_TYPE_TX_RX but the driver doesn't change current
+ * phymode bandwidth to avoid interoperability issues with APs which don't
+ * handle the maximum bandwidth change indication correctly.
+ * For example: Negotiated maximum bandwidth is 40 MHz and the new maximum
+ * bandwidth configured is 20 MHz, now the driver indicates the change in
+ * maximum allowed bandwidth to the AP and limits the bandwidth to 20 MHz in the
+ * Tx path but keeps the phymode bandwidth as 40 MHz. This will avoid
+ * interoperability issues with APs which still use 40 MHz for sending the
+ * frames though it received maximum allowed bandwidth indication as 20 MHz
+ * from the STA.
+ */
+enum qca_chan_width_update_type {
+	QCA_CHAN_WIDTH_UPDATE_TYPE_TX_RX = 0,
+	QCA_CHAN_WIDTH_UPDATE_TYPE_TX_ONLY = 1,
+	QCA_CHAN_WIDTH_UPDATE_TYPE_TX_RX_EXT = 2,
+};
+
 #endif /* QCA_VENDOR_H */
diff --git a/src/common/wpa_common.c b/src/common/wpa_common.c
index 367af8f..d897e0e 100644
--- a/src/common/wpa_common.c
+++ b/src/common/wpa_common.c
@@ -17,6 +17,7 @@
 #include "crypto/aes_wrap.h"
 #include "crypto/crypto.h"
 #include "ieee802_11_defs.h"
+#include "ieee802_11_common.h"
 #include "defs.h"
 #include "wpa_common.h"
 
@@ -25,6 +26,7 @@
 {
 	switch (akmp) {
 	case WPA_KEY_MGMT_IEEE8021X_SUITE_B_192:
+	case WPA_KEY_MGMT_IEEE8021X_SHA384:
 	case WPA_KEY_MGMT_FT_IEEE8021X_SHA384:
 		return 24;
 	case WPA_KEY_MGMT_FILS_SHA256:
@@ -70,6 +72,7 @@
 	case WPA_KEY_MGMT_FILS_SHA256:
 	case WPA_KEY_MGMT_FT_FILS_SHA256:
 	case WPA_KEY_MGMT_FT_IEEE8021X_SHA384:
+	case WPA_KEY_MGMT_IEEE8021X_SHA384:
 		return 32;
 	case WPA_KEY_MGMT_DPP:
 		return pmk_len <= 32 ? 16 : 32;
@@ -104,6 +107,7 @@
 	switch (akmp) {
 	case WPA_KEY_MGMT_IEEE8021X_SUITE_B_192:
 	case WPA_KEY_MGMT_FT_IEEE8021X_SHA384:
+	case WPA_KEY_MGMT_IEEE8021X_SHA384:
 		return 24;
 	case WPA_KEY_MGMT_FILS_SHA256:
 	case WPA_KEY_MGMT_FILS_SHA384:
@@ -134,6 +138,7 @@
 		akmp == WPA_KEY_MGMT_OWE ||
 		akmp == WPA_KEY_MGMT_DPP ||
 		akmp == WPA_KEY_MGMT_FT_IEEE8021X_SHA384 ||
+		akmp == WPA_KEY_MGMT_IEEE8021X_SHA384 ||
 		wpa_key_mgmt_sae(akmp) ||
 		wpa_key_mgmt_suite_b(akmp) ||
 		wpa_key_mgmt_fils(akmp);
@@ -172,6 +177,7 @@
 	return akmp == WPA_KEY_MGMT_OSEN ||
 		akmp == WPA_KEY_MGMT_OWE ||
 		akmp == WPA_KEY_MGMT_DPP ||
+		akmp == WPA_KEY_MGMT_IEEE8021X_SHA384 ||
 		wpa_key_mgmt_ft(akmp) ||
 		wpa_key_mgmt_sha256(akmp) ||
 		wpa_key_mgmt_sae(akmp) ||
@@ -330,15 +336,18 @@
 			os_memcpy(mic, hash, key_len);
 			break;
 #endif /* CONFIG_DPP */
-#if defined(CONFIG_IEEE80211R) && defined(CONFIG_SHA384)
+#ifdef CONFIG_SHA384
+		case WPA_KEY_MGMT_IEEE8021X_SHA384:
+#ifdef CONFIG_IEEE80211R
 		case WPA_KEY_MGMT_FT_IEEE8021X_SHA384:
+#endif /* CONFIG_IEEE80211R */
 			wpa_printf(MSG_DEBUG,
-				   "WPA: EAPOL-Key MIC using HMAC-SHA384 (AKM-defined - FT 802.1X SHA384)");
+				   "WPA: EAPOL-Key MIC using HMAC-SHA384 (AKM-defined - 802.1X SHA384)");
 			if (hmac_sha384(key, key_len, buf, len, hash))
 				return -1;
 			os_memcpy(mic, hash, 24);
 			break;
-#endif /* CONFIG_IEEE80211R && CONFIG_SHA384 */
+#endif /* CONFIG_SHA384 */
 		default:
 			wpa_printf(MSG_DEBUG,
 				   "WPA: EAPOL-Key MIC algorithm not known (AKM-defined - akmp=0x%x)",
@@ -453,14 +462,14 @@
 	ptk_len = ptk->kck_len + ptk->kek_len + ptk->tk_len + ptk->kdk_len;
 
 	if (wpa_key_mgmt_sha384(akmp)) {
-#if defined(CONFIG_SUITEB192) || defined(CONFIG_FILS)
+#ifdef CONFIG_SHA384
 		wpa_printf(MSG_DEBUG, "WPA: PTK derivation using PRF(SHA384)");
 		if (sha384_prf(pmk, pmk_len, label, data, data_len,
 			       tmp, ptk_len) < 0)
 			return -1;
-#else /* CONFIG_SUITEB192 || CONFIG_FILS */
+#else /* CONFIG_SHA384 */
 		return -1;
-#endif /* CONFIG_SUITEB192 || CONFIG_FILS */
+#endif /* CONFIG_SHA384 */
 	} else if (wpa_key_mgmt_sha256(akmp)) {
 		wpa_printf(MSG_DEBUG, "WPA: PTK derivation using PRF(SHA256)");
 		if (sha256_prf(pmk, pmk_len, label, data, data_len,
@@ -889,10 +898,11 @@
 	       const u8 *rsnie, size_t rsnie_len,
 	       const u8 *ric, size_t ric_len,
 	       const u8 *rsnxe, size_t rsnxe_len,
+	       const struct wpabuf *extra,
 	       u8 *mic)
 {
-	const u8 *addr[10];
-	size_t len[10];
+	const u8 *addr[11];
+	size_t len[11];
 	size_t i, num_elem = 0;
 	u8 zero_mic[32];
 	size_t mic_len, fte_fixed_len;
@@ -970,6 +980,12 @@
 		num_elem++;
 	}
 
+	if (extra) {
+		addr[num_elem] = wpabuf_head(extra);
+		len[num_elem] = wpabuf_len(extra);
+		num_elem++;
+	}
+
 	for (i = 0; i < num_elem; i++)
 		wpa_hexdump(MSG_MSGDUMP, "FT: MIC data", addr[i], len[i]);
 	res = -1;
@@ -1013,6 +1029,7 @@
 			     struct wpa_ft_ies *parse, const u8 *opt)
 {
 	const u8 *end, *pos;
+	u8 link_id;
 
 	parse->ftie = ie;
 	parse->ftie_len = ie_len;
@@ -1078,6 +1095,51 @@
 			parse->bigtk = pos;
 			parse->bigtk_len = len;
 			break;
+		case FTIE_SUBELEM_MLO_GTK:
+			if (len < 2 + 1 + 1 + 8) {
+				wpa_printf(MSG_DEBUG,
+					   "FT: Too short MLO GTK in FTE");
+				return -1;
+			}
+			link_id = pos[2] & 0x0f;
+			wpa_printf(MSG_DEBUG, "FT: MLO GTK (Link ID %u)",
+				   link_id);
+			if (link_id >= MAX_NUM_MLO_LINKS)
+				break;
+			parse->valid_mlo_gtks |= BIT(link_id);
+			parse->mlo_gtk[link_id] = pos;
+			parse->mlo_gtk_len[link_id] = len;
+			break;
+		case FTIE_SUBELEM_MLO_IGTK:
+			if (len < 2 + 6 + 1 + 1) {
+				wpa_printf(MSG_DEBUG,
+					   "FT: Too short MLO IGTK in FTE");
+				return -1;
+			}
+			link_id = pos[2 + 6] & 0x0f;
+			wpa_printf(MSG_DEBUG, "FT: MLO IGTK (Link ID %u)",
+				   link_id);
+			if (link_id >= MAX_NUM_MLO_LINKS)
+				break;
+			parse->valid_mlo_igtks |= BIT(link_id);
+			parse->mlo_igtk[link_id] = pos;
+			parse->mlo_igtk_len[link_id] = len;
+			break;
+		case FTIE_SUBELEM_MLO_BIGTK:
+			if (len < 2 + 6 + 1 + 1) {
+				wpa_printf(MSG_DEBUG,
+					   "FT: Too short MLO BIGTK in FTE");
+				return -1;
+			}
+			link_id = pos[2 + 6] & 0x0f;
+			wpa_printf(MSG_DEBUG, "FT: MLO BIGTK (Link ID %u)",
+				   link_id);
+			if (link_id >= MAX_NUM_MLO_LINKS)
+				break;
+			parse->valid_mlo_bigtks |= BIT(link_id);
+			parse->mlo_bigtk[link_id] = pos;
+			parse->mlo_bigtk_len[link_id] = len;
+			break;
 		default:
 			wpa_printf(MSG_DEBUG, "FT: Unknown subelem id %u", id);
 			break;
@@ -1151,17 +1213,26 @@
 
 
 int wpa_ft_parse_ies(const u8 *ies, size_t ies_len, struct wpa_ft_ies *parse,
-		     int key_mgmt)
+		     int key_mgmt, bool reassoc_resp)
 {
 	const u8 *end, *pos;
 	struct wpa_ie_data data;
 	int ret;
 	int prot_ie_count = 0;
+	const u8 *fte = NULL;
+	size_t fte_len = 0;
+	bool is_fte = false;
+	struct ieee802_11_elems elems;
 
 	os_memset(parse, 0, sizeof(*parse));
 	if (ies == NULL)
 		return 0;
 
+	if (ieee802_11_parse_elems(ies, ies_len, &elems, 0) == ParseFailed) {
+		wpa_printf(MSG_DEBUG, "FT: Failed to parse elements");
+		goto fail;
+	}
+
 	pos = ies;
 	end = ies + ies_len;
 	while (end - pos >= 2) {
@@ -1172,6 +1243,10 @@
 		if (len > end - pos)
 			break;
 
+		if (id != WLAN_EID_FAST_BSS_TRANSITION &&
+		    id != WLAN_EID_FRAGMENT)
+			is_fte = false;
+
 		switch (id) {
 		case WLAN_EID_RSN:
 			wpa_hexdump(MSG_DEBUG, "FT: RSNE", pos, len);
@@ -1183,7 +1258,7 @@
 			if (ret < 0) {
 				wpa_printf(MSG_DEBUG, "FT: Failed to parse "
 					   "RSN IE: %d", ret);
-				return -1;
+				goto fail;
 			}
 			parse->rsn_capab = data.capabilities;
 			if (data.num_pmkid == 1 && data.pmkid)
@@ -1203,7 +1278,7 @@
 		case WLAN_EID_MOBILITY_DOMAIN:
 			wpa_hexdump(MSG_DEBUG, "FT: MDE", pos, len);
 			if (len < sizeof(struct rsn_mdie))
-				return -1;
+				goto fail;
 			parse->mdie = pos;
 			parse->mdie_len = len;
 			break;
@@ -1217,12 +1292,19 @@
 			 * using the struct rsn_ftie* definitions. */
 
 			if (len < 2)
-				return -1;
+				goto fail;
 			prot_ie_count = pos[1]; /* Element Count field in
 						 * MIC Control */
-
-			if (wpa_ft_parse_fte(key_mgmt, pos, len, parse) < 0)
-				return -1;
+			is_fte = true;
+			fte = pos;
+			fte_len = len;
+			break;
+		case WLAN_EID_FRAGMENT:
+			if (is_fte) {
+				wpa_hexdump(MSG_DEBUG, "FT: FTE fragment",
+					    pos, len);
+				fte_len += 2 + len;
+			}
 			break;
 		case WLAN_EID_TIMEOUT_INTERVAL:
 			wpa_hexdump(MSG_DEBUG, "FT: Timeout Interval",
@@ -1241,6 +1323,25 @@
 		pos += len;
 	}
 
+	if (fte) {
+		int res;
+
+		if (fte_len < 255) {
+			res = wpa_ft_parse_fte(key_mgmt, fte, fte_len, parse);
+		} else {
+			parse->fte_buf = ieee802_11_defrag_data(fte, fte_len,
+								false);
+			if (!parse->fte_buf)
+				goto fail;
+			res = wpa_ft_parse_fte(key_mgmt,
+					       wpabuf_head(parse->fte_buf),
+					       wpabuf_len(parse->fte_buf),
+					       parse);
+		}
+		if (res < 0)
+			goto fail;
+	}
+
 	if (prot_ie_count == 0)
 		return 0; /* no MIC */
 
@@ -1248,24 +1349,39 @@
 	 * Check that the protected IE count matches with IEs included in the
 	 * frame.
 	 */
-	if (parse->rsn)
-		prot_ie_count--;
+	if (reassoc_resp && elems.basic_mle) {
+		unsigned int link_id;
+
+		/* TODO: This count should be done based on all _requested_,
+		 * not _accepted_ links. */
+		for (link_id = 0; link_id < MAX_NUM_MLO_LINKS; link_id++) {
+			if (parse->mlo_gtk[link_id]) {
+				if (parse->rsn)
+					prot_ie_count--;
+				if (parse->rsnxe)
+					prot_ie_count--;
+			}
+		}
+	} else {
+		if (parse->rsn)
+			prot_ie_count--;
+		if (parse->rsnxe)
+			prot_ie_count--;
+	}
 	if (parse->mdie)
 		prot_ie_count--;
 	if (parse->ftie)
 		prot_ie_count--;
-	if (parse->rsnxe)
-		prot_ie_count--;
 	if (prot_ie_count < 0) {
 		wpa_printf(MSG_DEBUG, "FT: Some required IEs not included in "
 			   "the protected IE count");
-		return -1;
+		goto fail;
 	}
 
 	if (prot_ie_count == 0 && parse->ric) {
 		wpa_printf(MSG_DEBUG, "FT: RIC IE(s) in the frame, but not "
 			   "included in protected IE count");
-		return -1;
+		goto fail;
 	}
 
 	/* Determine the end of the RIC IE(s) */
@@ -1281,11 +1397,25 @@
 	if (prot_ie_count) {
 		wpa_printf(MSG_DEBUG, "FT: %d protected IEs missing from "
 			   "frame", (int) prot_ie_count);
-		return -1;
+		goto fail;
 	}
 
 	return 0;
+
+fail:
+	wpa_ft_parse_ies_free(parse);
+	return -1;
 }
+
+
+void wpa_ft_parse_ies_free(struct wpa_ft_ies *parse)
+{
+	if (!parse)
+		return;
+	wpabuf_free(parse->fte_buf);
+	parse->fte_buf = NULL;
+}
+
 #endif /* CONFIG_IEEE80211R */
 
 
@@ -1649,6 +1779,10 @@
 		return WPA_KEY_MGMT_FT_IEEE8021X_SHA384;
 #endif /* CONFIG_SHA384 */
 #endif /* CONFIG_IEEE80211R */
+#ifdef CONFIG_SHA384
+	if (RSN_SELECTOR_GET(s) == RSN_AUTH_KEY_MGMT_802_1X_SHA384)
+		return WPA_KEY_MGMT_IEEE8021X_SHA384;
+#endif /* CONFIG_SHA384 */
 	if (RSN_SELECTOR_GET(s) == RSN_AUTH_KEY_MGMT_802_1X_SHA256)
 		return WPA_KEY_MGMT_IEEE8021X_SHA256;
 	if (RSN_SELECTOR_GET(s) == RSN_AUTH_KEY_MGMT_PSK_SHA256)
@@ -2665,6 +2799,8 @@
 		return "DPP";
 	case WPA_KEY_MGMT_PASN:
 		return "PASN";
+	case WPA_KEY_MGMT_IEEE8021X_SHA384:
+		return "WPA2-EAP-SHA384";
 	default:
 		return "UNKNOWN";
 	}
@@ -2679,6 +2815,8 @@
 		return RSN_AUTH_KEY_MGMT_FT_802_1X;
 	if (akm & WPA_KEY_MGMT_FT_PSK)
 		return RSN_AUTH_KEY_MGMT_FT_PSK;
+	if (akm & WPA_KEY_MGMT_IEEE8021X_SHA384)
+		return RSN_AUTH_KEY_MGMT_802_1X_SHA384;
 	if (akm & WPA_KEY_MGMT_IEEE8021X_SHA256)
 		return RSN_AUTH_KEY_MGMT_802_1X_SHA256;
 	if (akm & WPA_KEY_MGMT_IEEE8021X)
@@ -3584,6 +3722,11 @@
 			   sizeof(struct ieee80211_he_6ghz_band_cap) &&
 			   pos[2] == WLAN_EID_EXT_HE_6GHZ_BAND_CAP) {
 			ie->he_6ghz_capabilities = pos + 3;
+		} else if (*pos == WLAN_EID_EXTENSION &&
+			   pos[1] >= 1 + IEEE80211_EHT_CAPAB_MIN_LEN &&
+			   pos[2] == WLAN_EID_EXT_EHT_CAPABILITIES) {
+			ie->eht_capabilities = pos + 3;
+			ie->eht_capab_len = pos[1] - 1;
 		} else if (*pos == WLAN_EID_QOS && pos[1] >= 1) {
 			ie->qosinfo = pos[2];
 		} else if (*pos == WLAN_EID_SUPPORTED_CHANNELS) {
diff --git a/src/common/wpa_common.h b/src/common/wpa_common.h
index 05b1a8a..1269bf9 100644
--- a/src/common/wpa_common.h
+++ b/src/common/wpa_common.h
@@ -427,6 +427,9 @@
 #define FTIE_SUBELEM_IGTK 4
 #define FTIE_SUBELEM_OCI 5
 #define FTIE_SUBELEM_BIGTK 6
+#define FTIE_SUBELEM_MLO_GTK 8
+#define FTIE_SUBELEM_MLO_IGTK 9
+#define FTIE_SUBELEM_MLO_BIGTK 10
 
 struct rsn_rdie {
 	u8 id;
@@ -482,6 +485,7 @@
 	       const u8 *rsnie, size_t rsnie_len,
 	       const u8 *ric, size_t ric_len,
 	       const u8 *rsnxe, size_t rsnxe_len,
+	       const struct wpabuf *extra,
 	       u8 *mic);
 int wpa_derive_pmk_r0(const u8 *xxkey, size_t xxkey_len,
 		      const u8 *ssid, size_t ssid_len,
@@ -553,6 +557,8 @@
 		       const u8 *ie2, size_t ie2len);
 int wpa_insert_pmkid(u8 *ies, size_t *ies_len, const u8 *pmkid);
 
+#define MAX_NUM_MLO_LINKS 15
+
 struct wpa_ft_ies {
 	const u8 *mdie;
 	size_t mdie_len;
@@ -589,6 +595,17 @@
 	int pairwise_cipher;
 	const u8 *rsnxe;
 	size_t rsnxe_len;
+	u16 valid_mlo_gtks; /* bitmap of valid link GTK subelements */
+	const u8 *mlo_gtk[MAX_NUM_MLO_LINKS];
+	size_t mlo_gtk_len[MAX_NUM_MLO_LINKS];
+	u16 valid_mlo_igtks; /* bitmap of valid link IGTK subelements */
+	const u8 *mlo_igtk[MAX_NUM_MLO_LINKS];
+	size_t mlo_igtk_len[MAX_NUM_MLO_LINKS];
+	u16 valid_mlo_bigtks; /* bitmap of valid link BIGTK subelements */
+	const u8 *mlo_bigtk[MAX_NUM_MLO_LINKS];
+	size_t mlo_bigtk_len[MAX_NUM_MLO_LINKS];
+
+	struct wpabuf *fte_buf;
 };
 
 /* IEEE P802.11az/D2.6 - 9.4.2.303 PASN Parameters element */
@@ -624,7 +641,8 @@
 #define WPA_PASN_PUBKEY_UNCOMPRESSED 0x04
 
 int wpa_ft_parse_ies(const u8 *ies, size_t ies_len, struct wpa_ft_ies *parse,
-		     int key_mgmt);
+		     int key_mgmt, bool reassoc_resp);
+void wpa_ft_parse_ies_free(struct wpa_ft_ies *parse);
 
 struct wpa_eapol_ie_parse {
 	const u8 *wpa_ie;
@@ -671,6 +689,8 @@
 	const u8 *he_capabilities;
 	size_t he_capab_len;
 	const u8 *he_6ghz_capabilities;
+	const u8 *eht_capabilities;
+	size_t eht_capab_len;
 	const u8 *supp_channels;
 	size_t supp_channels_len;
 	const u8 *supp_oper_classes;
@@ -679,7 +699,6 @@
 	u16 aid;
 	const u8 *wmm;
 	size_t wmm_len;
-#define MAX_NUM_MLO_LINKS 15
 	u16 valid_mlo_gtks; /* bitmap of valid link GTK KDEs */
 	const u8 *mlo_gtk[MAX_NUM_MLO_LINKS];
 	size_t mlo_gtk_len[MAX_NUM_MLO_LINKS];
diff --git a/src/common/wpa_ctrl.h b/src/common/wpa_ctrl.h
index 06149ec..416e0d6 100644
--- a/src/common/wpa_ctrl.h
+++ b/src/common/wpa_ctrl.h
@@ -359,6 +359,7 @@
 
 #define AP_EVENT_ENABLED "AP-ENABLED "
 #define AP_EVENT_DISABLED "AP-DISABLED "
+#define AP_EVENT_NO_IR "AP-NO_IR"
 
 #define INTERFACE_ENABLED "INTERFACE-ENABLED "
 #define INTERFACE_DISABLED "INTERFACE-DISABLED "
@@ -472,6 +473,7 @@
 #define WPA_BSS_MASK_FILS_INDICATION	BIT(24)
 #define WPA_BSS_MASK_RNR		BIT(25)
 #define WPA_BSS_MASK_ML			BIT(26)
+#define WPA_BSS_MASK_AP_MLD_ADDR	BIT(27)
 
 
 /* VENDOR_ELEM_* frame id values */
diff --git a/src/crypto/crypto.h b/src/crypto/crypto.h
index ff0869c..0ac8fc1 100644
--- a/src/crypto/crypto.h
+++ b/src/crypto/crypto.h
@@ -21,6 +21,8 @@
 #ifndef CRYPTO_H
 #define CRYPTO_H
 
+#define HMAC_VECTOR_MAX_ELEM 11
+
 /**
  * md4_vector - MD4 hash for data vector
  * @num_elem: Number of elements in the data vector
diff --git a/src/crypto/crypto_wolfssl.c b/src/crypto/crypto_wolfssl.c
index f47beeb..2691743 100644
--- a/src/crypto/crypto_wolfssl.c
+++ b/src/crypto/crypto_wolfssl.c
@@ -10,25 +10,148 @@
 
 #include "common.h"
 #include "crypto.h"
+#include "tls/asn1.h"
 
 /* wolfSSL headers */
-#include <wolfssl/options.h>
+#include <wolfssl/options.h> /* options.h needs to be included first */
+#include <wolfssl/version.h>
+#include <wolfssl/openssl/bn.h>
+#include <wolfssl/wolfcrypt/aes.h>
+#include <wolfssl/wolfcrypt/arc4.h>
+#include <wolfssl/wolfcrypt/asn_public.h>
+#include <wolfssl/wolfcrypt/cmac.h>
+#include <wolfssl/wolfcrypt/des3.h>
+#include <wolfssl/wolfcrypt/dh.h>
+#include <wolfssl/wolfcrypt/ecc.h>
+#include <wolfssl/wolfcrypt/error-crypt.h>
+#include <wolfssl/wolfcrypt/hmac.h>
 #include <wolfssl/wolfcrypt/md4.h>
 #include <wolfssl/wolfcrypt/md5.h>
+#include <wolfssl/wolfcrypt/pkcs7.h>
+#include <wolfssl/wolfcrypt/pwdbased.h>
 #include <wolfssl/wolfcrypt/sha.h>
 #include <wolfssl/wolfcrypt/sha256.h>
 #include <wolfssl/wolfcrypt/sha512.h>
-#include <wolfssl/wolfcrypt/hmac.h>
-#include <wolfssl/wolfcrypt/pwdbased.h>
-#include <wolfssl/wolfcrypt/arc4.h>
-#include <wolfssl/wolfcrypt/des3.h>
-#include <wolfssl/wolfcrypt/aes.h>
-#include <wolfssl/wolfcrypt/dh.h>
-#include <wolfssl/wolfcrypt/cmac.h>
-#include <wolfssl/wolfcrypt/ecc.h>
-#include <wolfssl/wolfcrypt/asn_public.h>
-#include <wolfssl/wolfcrypt/error-crypt.h>
-#include <wolfssl/openssl/bn.h>
+
+#ifdef CONFIG_FIPS
+#ifndef HAVE_FIPS
+#warning "You are compiling wpa_supplicant/hostapd in FIPS mode but wolfSSL is not configured for FIPS mode."
+#endif /* HAVE_FIPS */
+#endif /* CONFIG_FIPS */
+
+
+#ifdef CONFIG_FIPS
+#if !defined(HAVE_FIPS_VERSION) || HAVE_FIPS_VERSION <= 2
+#define WOLFSSL_OLD_FIPS
+#endif
+#endif
+
+#if LIBWOLFSSL_VERSION_HEX < 0x05004000
+static int wc_EccPublicKeyToDer_ex(ecc_key *key, byte *output,
+				   word32 inLen, int with_AlgCurve,
+				   int comp)
+{
+	return wc_EccPublicKeyToDer(key, output, inLen, with_AlgCurve);
+}
+#endif /* version < 5.4.0 */
+
+#define LOG_WOLF_ERROR_VA(msg, ...) \
+	wpa_printf(MSG_ERROR, "wolfSSL: %s:%d " msg, \
+		   __func__, __LINE__, __VA_ARGS__)
+
+#define LOG_WOLF_ERROR(msg) \
+	LOG_WOLF_ERROR_VA("%s", (msg))
+
+#define LOG_WOLF_ERROR_FUNC(func, err) \
+	LOG_WOLF_ERROR_VA(#func " failed with err: %d %s", \
+			  (err), wc_GetErrorString(err))
+
+#define LOG_WOLF_ERROR_FUNC_NULL(func) \
+	LOG_WOLF_ERROR(#func " failed with NULL return")
+
+#define LOG_INVALID_PARAMETERS() \
+	LOG_WOLF_ERROR("invalid input parameters")
+
+
+/* Helper functions to make type allocation uniform */
+
+static WC_RNG * wc_rng_init(void)
+{
+	WC_RNG *ret;
+
+#ifdef CONFIG_FIPS
+	ret = os_zalloc(sizeof(WC_RNG));
+	if (!ret) {
+		LOG_WOLF_ERROR_FUNC_NULL(os_zalloc);
+	} else {
+		int err;
+
+		err = wc_InitRng(ret);
+		if (err != 0) {
+			LOG_WOLF_ERROR_FUNC(wc_InitRng, err);
+			os_free(ret);
+			ret = NULL;
+		}
+	}
+#else /* CONFIG_FIPS */
+	ret = wc_rng_new(NULL, 0, NULL);
+	if (!ret)
+		LOG_WOLF_ERROR_FUNC_NULL(wc_rng_new);
+#endif /* CONFIG_FIPS */
+
+	return ret;
+}
+
+
+static void wc_rng_deinit(WC_RNG *rng)
+{
+#ifdef CONFIG_FIPS
+	wc_FreeRng(rng);
+	os_free(rng);
+#else /* CONFIG_FIPS */
+	wc_rng_free(rng);
+#endif /* CONFIG_FIPS */
+}
+
+
+static ecc_key * ecc_key_init(void)
+{
+	ecc_key *ret;
+#ifdef CONFIG_FIPS
+	int err;
+
+	ret = os_zalloc(sizeof(ecc_key));
+	if (!ret) {
+		LOG_WOLF_ERROR_FUNC_NULL(os_zalloc);
+	} else {
+		err = wc_ecc_init_ex(ret, NULL, INVALID_DEVID);
+		if (err != 0) {
+			LOG_WOLF_ERROR("wc_ecc_init_ex failed");
+			os_free(ret);
+			ret = NULL;
+		}
+	}
+#else /* CONFIG_FIPS */
+	ret = wc_ecc_key_new(NULL);
+	if (!ret)
+		LOG_WOLF_ERROR_FUNC_NULL(wc_ecc_key_new);
+#endif /* CONFIG_FIPS */
+
+	return ret;
+}
+
+
+static void ecc_key_deinit(ecc_key *key)
+{
+#ifdef CONFIG_FIPS
+	wc_ecc_free(key);
+	os_free(key);
+#else /* CONFIG_FIPS */
+	wc_ecc_key_free(key);
+#endif /* CONFIG_FIPS */
+}
+
+/* end of helper functions */
 
 
 #ifndef CONFIG_FIPS
@@ -56,18 +179,36 @@
 {
 	wc_Md5 md5;
 	size_t i;
+	int err;
+	int ret = -1;
 
 	if (TEST_FAIL())
 		return -1;
 
-	wc_InitMd5(&md5);
+	err = wc_InitMd5(&md5);
+	if (err != 0) {
+		LOG_WOLF_ERROR_FUNC(wc_InitMd5, err);
+		return -1;
+	}
 
-	for (i = 0; i < num_elem; i++)
-		wc_Md5Update(&md5, addr[i], len[i]);
+	for (i = 0; i < num_elem; i++) {
+		err = wc_Md5Update(&md5, addr[i], len[i]);
+		if (err != 0) {
+			LOG_WOLF_ERROR_FUNC(wc_Md5Update, err);
+			goto fail;
+		}
+	}
 
-	wc_Md5Final(&md5, mac);
+	err = wc_Md5Final(&md5, mac);
+	if (err != 0) {
+		LOG_WOLF_ERROR_FUNC(wc_Md5Final, err);
+		goto fail;
+	}
 
-	return 0;
+	ret = 0;
+fail:
+	wc_Md5Free(&md5);
+	return ret;
 }
 
 #endif /* CONFIG_FIPS */
@@ -77,19 +218,36 @@
 {
 	wc_Sha sha;
 	size_t i;
+	int err;
+	int ret = -1;
 
 	if (TEST_FAIL())
 		return -1;
 
-	wc_InitSha(&sha);
+	err = wc_InitSha(&sha);
+	if (err != 0) {
+		LOG_WOLF_ERROR_FUNC(wc_InitSha, err);
+		return -1;
+	}
 
-	for (i = 0; i < num_elem; i++)
-		wc_ShaUpdate(&sha, addr[i], len[i]);
+	for (i = 0; i < num_elem; i++) {
+		err = wc_ShaUpdate(&sha, addr[i], len[i]);
+		if (err != 0) {
+			LOG_WOLF_ERROR_FUNC(wc_ShaUpdate, err);
+			goto fail;
+		}
+	}
 
-	wc_ShaFinal(&sha, mac);
+	err = wc_ShaFinal(&sha, mac);
+	if (err != 0) {
+		LOG_WOLF_ERROR_FUNC(wc_ShaFinal, err);
+		goto fail;
+	}
+
+	ret = 0;
+fail:
 	wc_ShaFree(&sha);
-
-	return 0;
+	return ret;
 }
 
 
@@ -99,19 +257,36 @@
 {
 	wc_Sha256 sha256;
 	size_t i;
+	int err;
+	int ret = -1;
 
 	if (TEST_FAIL())
 		return -1;
 
-	wc_InitSha256(&sha256);
+	err = wc_InitSha256(&sha256);
+	if (err != 0) {
+		LOG_WOLF_ERROR_FUNC(wc_InitSha256, err);
+		return -1;
+	}
 
-	for (i = 0; i < num_elem; i++)
-		wc_Sha256Update(&sha256, addr[i], len[i]);
+	for (i = 0; i < num_elem; i++) {
+		err = wc_Sha256Update(&sha256, addr[i], len[i]);
+		if (err != 0) {
+			LOG_WOLF_ERROR_FUNC(wc_Sha256Update, err);
+			goto fail;
+		}
+	}
 
-	wc_Sha256Final(&sha256, mac);
+	err = wc_Sha256Final(&sha256, mac);
+	if (err != 0) {
+		LOG_WOLF_ERROR_FUNC(wc_Sha256Final, err);
+		goto fail;
+	}
+
+	ret = 0;
+fail:
 	wc_Sha256Free(&sha256);
-
-	return 0;
+	return ret;
 }
 #endif /* NO_SHA256_WRAPPER */
 
@@ -122,19 +297,36 @@
 {
 	wc_Sha384 sha384;
 	size_t i;
+	int err;
+	int ret = -1;
 
 	if (TEST_FAIL())
 		return -1;
 
-	wc_InitSha384(&sha384);
+	err = wc_InitSha384(&sha384);
+	if (err != 0) {
+		LOG_WOLF_ERROR_FUNC(wc_InitSha384, err);
+		return -1;
+	}
 
-	for (i = 0; i < num_elem; i++)
-		wc_Sha384Update(&sha384, addr[i], len[i]);
+	for (i = 0; i < num_elem; i++) {
+		err = wc_Sha384Update(&sha384, addr[i], len[i]);
+		if (err != 0) {
+			LOG_WOLF_ERROR_FUNC(wc_Sha384Update, err);
+			goto fail;
+		}
+	}
 
-	wc_Sha384Final(&sha384, mac);
+	err = wc_Sha384Final(&sha384, mac);
+	if (err != 0) {
+		LOG_WOLF_ERROR_FUNC(wc_Sha384Final, err);
+		goto fail;
+	}
+
+	ret = 0;
+fail:
 	wc_Sha384Free(&sha384);
-
-	return 0;
+	return ret;
 }
 #endif /* CONFIG_SHA384 */
 
@@ -145,19 +337,36 @@
 {
 	wc_Sha512 sha512;
 	size_t i;
+	int err;
+	int ret = -1;
 
 	if (TEST_FAIL())
 		return -1;
 
-	wc_InitSha512(&sha512);
+	err = wc_InitSha512(&sha512);
+	if (err != 0) {
+		LOG_WOLF_ERROR_FUNC(wc_InitSha512, err);
+		return -1;
+	}
 
-	for (i = 0; i < num_elem; i++)
-		wc_Sha512Update(&sha512, addr[i], len[i]);
+	for (i = 0; i < num_elem; i++) {
+		err = wc_Sha512Update(&sha512, addr[i], len[i]);
+		if (err != 0) {
+			LOG_WOLF_ERROR_FUNC(wc_Sha512Update, err);
+			goto fail;
+		}
+	}
 
-	wc_Sha512Final(&sha512, mac);
+	err = wc_Sha512Final(&sha512, mac);
+	if (err != 0) {
+		LOG_WOLF_ERROR_FUNC(wc_Sha512Final, err);
+		goto fail;
+	}
+
+	ret = 0;
+fail:
 	wc_Sha512Free(&sha512);
-
-	return 0;
+	return ret;
 }
 #endif /* CONFIG_SHA512 */
 
@@ -169,23 +378,43 @@
 {
 	Hmac hmac;
 	size_t i;
+	int err;
+	int ret = -1;
 
 	(void) mdlen;
 
 	if (TEST_FAIL())
 		return -1;
 
-	if (wc_HmacInit(&hmac, NULL, INVALID_DEVID) != 0 ||
-	    wc_HmacSetKey(&hmac, type, key, (word32) key_len) != 0)
+	err = wc_HmacInit(&hmac, NULL, INVALID_DEVID);
+	if (err != 0) {
+		LOG_WOLF_ERROR_FUNC(wc_HmacInit, err);
 		return -1;
-	for (i = 0; i < num_elem; i++)
-		if (wc_HmacUpdate(&hmac, addr[i], len[i]) != 0)
-			return -1;
-	if (wc_HmacFinal(&hmac, mac) != 0)
-		return -1;
-	wc_HmacFree(&hmac);
+	}
 
-	return 0;
+	err = wc_HmacSetKey(&hmac, type, key, (word32) key_len);
+	if (err != 0) {
+		LOG_WOLF_ERROR_FUNC(wc_HmacSetKey, err);
+		goto fail;
+	}
+
+	for (i = 0; i < num_elem; i++) {
+		err = wc_HmacUpdate(&hmac, addr[i], len[i]);
+		if (err != 0) {
+			LOG_WOLF_ERROR_FUNC(wc_HmacUpdate, err);
+			goto fail;
+		}
+	}
+	err = wc_HmacFinal(&hmac, mac);
+	if (err != 0) {
+		LOG_WOLF_ERROR_FUNC(wc_HmacFinal, err);
+		goto fail;
+	}
+
+	ret = 0;
+fail:
+	wc_HmacFree(&hmac);
+	return ret;
 }
 
 
@@ -289,9 +518,8 @@
 			ssid, ssid_len, iterations, buflen, WC_SHA);
 	if (ret != 0) {
 		if (ret == HMAC_MIN_KEYLEN_E) {
-			wpa_printf(MSG_ERROR,
-				   "wolfSSL: Password is too short. Make sure your password is at least %d characters long. This is a requirement for FIPS builds.",
-				   HMAC_FIPS_MIN_KEY);
+			LOG_WOLF_ERROR_VA("wolfSSL: Password is too short. Make sure your password is at least %d characters long. This is a requirement for FIPS builds.",
+					  HMAC_FIPS_MIN_KEY);
 		}
 		return -1;
 	}
@@ -326,15 +554,20 @@
 void * aes_encrypt_init(const u8 *key, size_t len)
 {
 	Aes *aes;
+	int err;
 
 	if (TEST_FAIL())
 		return NULL;
 
 	aes = os_malloc(sizeof(Aes));
-	if (!aes)
+	if (!aes) {
+		LOG_WOLF_ERROR_FUNC_NULL(os_malloc);
 		return NULL;
+	}
 
-	if (wc_AesSetKey(aes, key, len, NULL, AES_ENCRYPTION) < 0) {
+	err = wc_AesSetKey(aes, key, len, NULL, AES_ENCRYPTION);
+	if (err < 0) {
+		LOG_WOLF_ERROR_FUNC(wc_AesSetKey, err);
 		os_free(aes);
 		return NULL;
 	}
@@ -345,7 +578,18 @@
 
 int aes_encrypt(void *ctx, const u8 *plain, u8 *crypt)
 {
+#if defined(HAVE_FIPS) && \
+    (!defined(HAVE_FIPS_VERSION) || (HAVE_FIPS_VERSION <= 2))
+	/* Old FIPS has void return on this API */
 	wc_AesEncryptDirect(ctx, crypt, plain);
+#else
+	int err = wc_AesEncryptDirect(ctx, crypt, plain);
+
+	if (err != 0) {
+		LOG_WOLF_ERROR_FUNC(wc_AesEncryptDirect, err);
+		return -1;
+	}
+#endif
 	return 0;
 }
 
@@ -359,15 +603,20 @@
 void * aes_decrypt_init(const u8 *key, size_t len)
 {
 	Aes *aes;
+	int err;
 
 	if (TEST_FAIL())
 		return NULL;
 
 	aes = os_malloc(sizeof(Aes));
-	if (!aes)
+	if (!aes) {
+		LOG_WOLF_ERROR_FUNC_NULL(os_malloc);
 		return NULL;
+	}
 
-	if (wc_AesSetKey(aes, key, len, NULL, AES_DECRYPTION) < 0) {
+	err = wc_AesSetKey(aes, key, len, NULL, AES_DECRYPTION);
+	if (err < 0) {
+		LOG_WOLF_ERROR_FUNC(wc_AesSetKey, err);
 		os_free(aes);
 		return NULL;
 	}
@@ -378,7 +627,18 @@
 
 int aes_decrypt(void *ctx, const u8 *crypt, u8 *plain)
 {
+#if defined(HAVE_FIPS) && \
+    (!defined(HAVE_FIPS_VERSION) || (HAVE_FIPS_VERSION <= 2))
+	/* Old FIPS has void return on this API */
 	wc_AesDecryptDirect(ctx, plain, crypt);
+#else
+	int err = wc_AesDecryptDirect(ctx, plain, crypt);
+
+	if (err != 0) {
+		LOG_WOLF_ERROR_FUNC(wc_AesDecryptDirect, err);
+		return -1;
+	}
+#endif
 	return 0;
 }
 
@@ -1334,17 +1594,52 @@
 
 #ifdef CONFIG_ECC
 
+static int crypto_ec_group_2_id(int group)
+{
+	switch (group) {
+	case 19:
+		return ECC_SECP256R1;
+	case 20:
+		return ECC_SECP384R1;
+	case 21:
+		return ECC_SECP521R1;
+	case 25:
+		return ECC_SECP192R1;
+	case 26:
+		return ECC_SECP224R1;
+#ifdef HAVE_ECC_BRAINPOOL
+	case 27:
+		return ECC_BRAINPOOLP224R1;
+	case 28:
+		return ECC_BRAINPOOLP256R1;
+	case 29:
+		return ECC_BRAINPOOLP384R1;
+	case 30:
+		return ECC_BRAINPOOLP512R1;
+#endif /* HAVE_ECC_BRAINPOOL */
+	default:
+		LOG_WOLF_ERROR_VA("Unsupported curve (id=%d) in EC key", group);
+		return ECC_CURVE_INVALID;
+	}
+}
+
+
 int ecc_map(ecc_point *, mp_int *, mp_digit);
 int ecc_projective_add_point(ecc_point *P, ecc_point *Q, ecc_point *R,
 			     mp_int *a, mp_int *modulus, mp_digit mp);
 
 struct crypto_ec {
-	ecc_key key;
+	ecc_key *key;
+#ifdef CONFIG_DPP
+	ecc_point *g; /* Only used in DPP for now */
+#endif /* CONFIG_DPP */
 	mp_int a;
 	mp_int prime;
 	mp_int order;
 	mp_digit mont_b;
 	mp_int b;
+	int curve_id;
+	bool own_key; /* Should we free the `key` */
 };
 
 
@@ -1352,59 +1647,98 @@
 {
 	int built = 0;
 	struct crypto_ec *e;
-	int curve_id;
+	int curve_id = crypto_ec_group_2_id(group);
+	int err;
 
-	/* Map from IANA registry for IKE D-H groups to OpenSSL NID */
-	switch (group) {
-	case 19:
-		curve_id = ECC_SECP256R1;
-		break;
-	case 20:
-		curve_id = ECC_SECP384R1;
-		break;
-	case 21:
-		curve_id = ECC_SECP521R1;
-		break;
-	case 25:
-		curve_id = ECC_SECP192R1;
-		break;
-	case 26:
-		curve_id = ECC_SECP224R1;
-		break;
-#ifdef HAVE_ECC_BRAINPOOL
-	case 27:
-		curve_id = ECC_BRAINPOOLP224R1;
-		break;
-	case 28:
-		curve_id = ECC_BRAINPOOLP256R1;
-		break;
-	case 29:
-		curve_id = ECC_BRAINPOOLP384R1;
-		break;
-	case 30:
-		curve_id = ECC_BRAINPOOLP512R1;
-		break;
-#endif /* HAVE_ECC_BRAINPOOL */
-	default:
+	if (curve_id == ECC_CURVE_INVALID) {
+		LOG_INVALID_PARAMETERS();
 		return NULL;
 	}
 
 	e = os_zalloc(sizeof(*e));
-	if (!e)
+	if (!e) {
+		LOG_WOLF_ERROR_FUNC_NULL(os_zalloc);
 		return NULL;
+	}
 
-	if (wc_ecc_init(&e->key) != 0 ||
-	    wc_ecc_set_curve(&e->key, 0, curve_id) != 0 ||
-	    mp_init(&e->a) != MP_OKAY ||
-	    mp_init(&e->prime) != MP_OKAY ||
-	    mp_init(&e->order) != MP_OKAY ||
-	    mp_init(&e->b) != MP_OKAY ||
-	    mp_read_radix(&e->a, e->key.dp->Af, 16) != MP_OKAY ||
-	    mp_read_radix(&e->b, e->key.dp->Bf, 16) != MP_OKAY ||
-	    mp_read_radix(&e->prime, e->key.dp->prime, 16) != MP_OKAY ||
-	    mp_read_radix(&e->order, e->key.dp->order, 16) != MP_OKAY ||
-	    mp_montgomery_setup(&e->prime, &e->mont_b) != MP_OKAY)
+	e->curve_id = curve_id;
+	e->own_key = true;
+	e->key = ecc_key_init();
+	if (!e->key) {
+		LOG_WOLF_ERROR_FUNC_NULL(ecc_key_init);
 		goto done;
+	}
+
+	err = wc_ecc_set_curve(e->key, 0, curve_id);
+	if (err != 0) {
+		LOG_WOLF_ERROR_FUNC(wc_ecc_set_curve, err);
+		goto done;
+	}
+#ifdef CONFIG_DPP
+	e->g = wc_ecc_new_point();
+	if (!e->g) {
+		LOG_WOLF_ERROR_FUNC_NULL(wc_ecc_new_point);
+		goto done;
+	}
+#ifdef CONFIG_FIPS
+	/* Setup generator manually in FIPS mode */
+	if (!e->key->dp) {
+		LOG_WOLF_ERROR_FUNC_NULL(e->key->dp);
+		goto done;
+	}
+	err = mp_read_radix(e->g->x, e->key->dp->Gx, MP_RADIX_HEX);
+	if (err != MP_OKAY) {
+		LOG_WOLF_ERROR_FUNC(mp_read_radix, err);
+		goto done;
+	}
+	err = mp_read_radix(e->g->y, e->key->dp->Gy, MP_RADIX_HEX);
+	if (err != MP_OKAY) {
+		LOG_WOLF_ERROR_FUNC(mp_read_radix, err);
+		goto done;
+	}
+	err = mp_set(e->g->z, 1);
+	if (err != MP_OKAY) {
+		LOG_WOLF_ERROR_FUNC(mp_set, err);
+		goto done;
+	}
+#else /* CONFIG_FIPS */
+	err = wc_ecc_get_generator(e->g, wc_ecc_get_curve_idx(curve_id));
+	if (err != MP_OKAY) {
+		LOG_WOLF_ERROR_FUNC(wc_ecc_get_generator, err);
+		goto done;
+	}
+#endif /* CONFIG_FIPS */
+#endif /* CONFIG_DPP */
+	err = mp_init_multi(&e->a, &e->prime, &e->order, &e->b, NULL, NULL);
+	if (err != MP_OKAY) {
+		LOG_WOLF_ERROR_FUNC(mp_init_multi, err);
+		goto done;
+	}
+	err = mp_read_radix(&e->a, e->key->dp->Af, 16);
+	if (err != MP_OKAY) {
+		LOG_WOLF_ERROR_FUNC(mp_read_radix, err);
+		goto done;
+	}
+	err = mp_read_radix(&e->b, e->key->dp->Bf, 16);
+	if (err != MP_OKAY) {
+		LOG_WOLF_ERROR_FUNC(mp_read_radix, err);
+		goto done;
+	}
+	err = mp_read_radix(&e->prime, e->key->dp->prime, 16);
+	if (err != MP_OKAY) {
+		LOG_WOLF_ERROR_FUNC(mp_read_radix, err);
+		goto done;
+	}
+	err = mp_read_radix(&e->order, e->key->dp->order, 16);
+	if (err != MP_OKAY) {
+		LOG_WOLF_ERROR_FUNC(mp_read_radix, err);
+		goto done;
+	}
+	err = mp_montgomery_setup(&e->prime, &e->mont_b);
+	if (err != MP_OKAY) {
+		LOG_WOLF_ERROR_FUNC(mp_montgomery_setup, err);
+		goto done;
+	}
 
 	built = 1;
 done:
@@ -1425,7 +1759,11 @@
 	mp_clear(&e->order);
 	mp_clear(&e->prime);
 	mp_clear(&e->a);
-	wc_ecc_free(&e->key);
+#ifdef CONFIG_DPP
+	wc_ecc_del_point(e->g);
+#endif /* CONFIG_DPP */
+	if (e->own_key)
+		ecc_key_deinit(e->key);
 	os_free(e);
 }
 
@@ -1490,14 +1828,26 @@
 		return;
 
 	if (clear) {
+#ifdef CONFIG_FIPS
 		mp_forcezero(point->x);
 		mp_forcezero(point->y);
 		mp_forcezero(point->z);
+#else /* CONFIG_FIPS */
+		wc_ecc_forcezero_point(point);
+#endif /* CONFIG_FIPS */
 	}
 	wc_ecc_del_point(point);
 }
 
 
+#ifdef CONFIG_DPP
+const struct crypto_ec_point * crypto_ec_get_generator(struct crypto_ec *e)
+{
+	return (const struct crypto_ec_point *) e->g;
+}
+#endif /* CONFIG_DPP */
+
+
 int crypto_ec_point_x(struct crypto_ec *e, const struct crypto_ec_point *p,
 		      struct crypto_bignum *x)
 {
@@ -1509,27 +1859,41 @@
 			   const struct crypto_ec_point *point, u8 *x, u8 *y)
 {
 	ecc_point *p = (ecc_point *) point;
+	int len;
+	int err;
 
 	if (TEST_FAIL())
 		return -1;
 
 	if (!mp_isone(p->z)) {
-		if (ecc_map(p, &e->prime, e->mont_b) != MP_OKAY)
+		err = ecc_map(p, &e->prime, e->mont_b);
+		if (err != MP_OKAY) {
+			LOG_WOLF_ERROR_FUNC(ecc_map, err);
 			return -1;
+		}
+	}
+
+	len = wc_ecc_get_curve_size_from_id(e->curve_id);
+	if (len <= 0) {
+		LOG_WOLF_ERROR_FUNC(wc_ecc_get_curve_size_from_id, len);
+		LOG_WOLF_ERROR_VA("wc_ecc_get_curve_size_from_id error for curve_id %d", e->curve_id);
+		return -1;
 	}
 
 	if (x) {
 		if (crypto_bignum_to_bin((struct crypto_bignum *)p->x, x,
-					 e->key.dp->size,
-					 e->key.dp->size) <= 0)
+					 (size_t) len, (size_t) len) <= 0) {
+			LOG_WOLF_ERROR_FUNC(crypto_bignum_to_bin, -1);
 			return -1;
+		}
 	}
 
 	if (y) {
 		if (crypto_bignum_to_bin((struct crypto_bignum *) p->y, y,
-					 e->key.dp->size,
-					 e->key.dp->size) <= 0)
+					 (size_t) len, (size_t) len) <= 0) {
+			LOG_WOLF_ERROR_FUNC(crypto_bignum_to_bin, -1);
 			return -1;
+		}
 	}
 
 	return 0;
@@ -1549,10 +1913,10 @@
 	if (!point)
 		goto done;
 
-	if (mp_read_unsigned_bin(point->x, val, e->key.dp->size) != MP_OKAY)
+	if (mp_read_unsigned_bin(point->x, val, e->key->dp->size) != MP_OKAY)
 		goto done;
-	val += e->key.dp->size;
-	if (mp_read_unsigned_bin(point->y, val, e->key.dp->size) != MP_OKAY)
+	val += e->key->dp->size;
+	if (mp_read_unsigned_bin(point->y, val, e->key->dp->size) != MP_OKAY)
 		goto done;
 	mp_set(point->z, 1);
 
@@ -1710,53 +2074,123 @@
 	return wc_ecc_cmp_point((ecc_point *) a, (ecc_point *) b);
 }
 
+struct crypto_ec_key {
+	ecc_key *eckey;
+	WC_RNG *rng; /* Needs to be initialized before use.
+		      * *NOT* initialized in crypto_ec_key_init */
+};
+
 
 struct crypto_ecdh {
 	struct crypto_ec *ec;
-	WC_RNG rng;
+	WC_RNG *rng;
 };
 
-struct crypto_ecdh * crypto_ecdh_init(int group)
+static struct crypto_ecdh * _crypto_ecdh_init(int group)
 {
 	struct crypto_ecdh *ecdh = NULL;
+#if defined(ECC_TIMING_RESISTANT) && !defined(WOLFSSL_OLD_FIPS)
 	int ret;
+#endif /* ECC_TIMING_RESISTANT && !WOLFSSL_OLD_FIPS */
 
 	ecdh = os_zalloc(sizeof(*ecdh));
-	if (!ecdh)
-		goto fail;
+	if (!ecdh) {
+		LOG_WOLF_ERROR_FUNC_NULL(os_zalloc);
+		return NULL;
+	}
 
-	if (wc_InitRng(&ecdh->rng) != 0)
+	ecdh->rng = wc_rng_init();
+	if (!ecdh->rng) {
+		LOG_WOLF_ERROR_FUNC_NULL(wc_rng_init);
 		goto fail;
+	}
 
 	ecdh->ec = crypto_ec_init(group);
-	if (!ecdh->ec)
+	if (!ecdh->ec) {
+		LOG_WOLF_ERROR_FUNC_NULL(crypto_ec_init);
 		goto fail;
+	}
 
-	ret = wc_ecc_make_key_ex(&ecdh->rng, ecdh->ec->key.dp->size,
-				 &ecdh->ec->key, ecdh->ec->key.dp->id);
-	if (ret < 0)
+#if defined(ECC_TIMING_RESISTANT) && !defined(WOLFSSL_OLD_FIPS)
+	ret = wc_ecc_set_rng(ecdh->ec->key, ecdh->rng);
+	if (ret != 0) {
+		LOG_WOLF_ERROR_FUNC(wc_ecc_set_rng, ret);
 		goto fail;
+	}
+#endif /* ECC_TIMING_RESISTANT && !WOLFSSL_OLD_FIPS */
 
-#if defined(ECC_TIMING_RESISTANT) && !defined(CONFIG_FIPS)
-	ret = wc_ecc_set_rng(&ecdh->ec->key, &ecdh->rng);
-	if (ret < 0)
-		goto fail;
-#endif /* ECC_TIMING_RESISTANT && !CONFIG_FIPS */
-
-done:
 	return ecdh;
 fail:
 	crypto_ecdh_deinit(ecdh);
-	ecdh = NULL;
-	goto done;
+	return NULL;
+}
+
+
+struct crypto_ecdh * crypto_ecdh_init(int group)
+{
+	struct crypto_ecdh *ret = NULL;
+	int err;
+
+	ret = _crypto_ecdh_init(group);
+
+	if (!ret) {
+		LOG_WOLF_ERROR_FUNC_NULL(_crypto_ecdh_init);
+		return NULL;
+	}
+
+	err = wc_ecc_make_key_ex(ret->rng, 0, ret->ec->key,
+				 crypto_ec_group_2_id(group));
+	if (err != MP_OKAY) {
+		LOG_WOLF_ERROR_FUNC(wc_ecc_make_key_ex, err);
+		crypto_ecdh_deinit(ret);
+		ret = NULL;
+	}
+
+	return ret;
+}
+
+
+struct crypto_ecdh * crypto_ecdh_init2(int group, struct crypto_ec_key *own_key)
+{
+	struct crypto_ecdh *ret = NULL;
+
+	if (!own_key || crypto_ec_key_group(own_key) != group) {
+		LOG_INVALID_PARAMETERS();
+		return NULL;
+	}
+
+	ret = _crypto_ecdh_init(group);
+	if (ret) {
+		/* Already init'ed to the right group. Enough to substitute the
+		 * key. */
+		ecc_key_deinit(ret->ec->key);
+		ret->ec->key = own_key->eckey;
+		ret->ec->own_key = false;
+#if defined(ECC_TIMING_RESISTANT) && !defined(WOLFSSL_OLD_FIPS)
+		if (!ret->ec->key->rng) {
+			int err = wc_ecc_set_rng(ret->ec->key, ret->rng);
+
+			if (err != 0)
+				LOG_WOLF_ERROR_FUNC(wc_ecc_set_rng, err);
+		}
+#endif /* ECC_TIMING_RESISTANT && !CONFIG_FIPS */
+	}
+
+	return ret;
 }
 
 
 void crypto_ecdh_deinit(struct crypto_ecdh *ecdh)
 {
 	if (ecdh) {
+#if defined(ECC_TIMING_RESISTANT) && !defined(WOLFSSL_OLD_FIPS)
+		/* Disassociate the rng */
+		if (ecdh->ec && ecdh->ec->key &&
+		    ecdh->ec->key->rng == ecdh->rng)
+			(void) wc_ecc_set_rng(ecdh->ec->key, NULL);
+#endif /* ECC_TIMING_RESISTANT && !WOLFSSL_OLD_FIPS */
 		crypto_ec_deinit(ecdh->ec);
-		wc_FreeRng(&ecdh->rng);
+		wc_rng_deinit(ecdh->rng);
 		os_free(ecdh);
 	}
 }
@@ -1766,20 +2200,20 @@
 {
 	struct wpabuf *buf = NULL;
 	int ret;
-	int len = ecdh->ec->key.dp->size;
+	int len = ecdh->ec->key->dp->size;
 
 	buf = wpabuf_alloc(inc_y ? 2 * len : len);
 	if (!buf)
 		goto fail;
 
 	ret = crypto_bignum_to_bin((struct crypto_bignum *)
-				   ecdh->ec->key.pubkey.x, wpabuf_put(buf, len),
+				   ecdh->ec->key->pubkey.x, wpabuf_put(buf, len),
 				   len, len);
 	if (ret < 0)
 		goto fail;
 	if (inc_y) {
 		ret = crypto_bignum_to_bin((struct crypto_bignum *)
-					   ecdh->ec->key.pubkey.y,
+					   ecdh->ec->key->pubkey.y,
 					   wpabuf_put(buf, len), len, len);
 		if (ret < 0)
 			goto fail;
@@ -1800,35 +2234,47 @@
 	int ret;
 	struct wpabuf *pubkey = NULL;
 	struct wpabuf *secret = NULL;
-	word32 key_len = ecdh->ec->key.dp->size;
+	word32 key_len = ecdh->ec->key->dp->size;
 	ecc_point *point = NULL;
 	size_t need_key_len = inc_y ? 2 * key_len : key_len;
 
-	if (len < need_key_len)
+	if (len < need_key_len) {
+		LOG_WOLF_ERROR("key len too small");
 		goto fail;
+	}
 	pubkey = wpabuf_alloc(1 + 2 * key_len);
-	if (!pubkey)
+	if (!pubkey) {
+		LOG_WOLF_ERROR_FUNC_NULL(wpabuf_alloc);
 		goto fail;
+	}
 	wpabuf_put_u8(pubkey, inc_y ? ECC_POINT_UNCOMP : ECC_POINT_COMP_EVEN);
 	wpabuf_put_data(pubkey, key, need_key_len);
 
 	point = wc_ecc_new_point();
-	if (!point)
+	if (!point) {
+		LOG_WOLF_ERROR_FUNC_NULL(wc_ecc_new_point);
 		goto fail;
+	}
 
 	ret = wc_ecc_import_point_der(wpabuf_mhead(pubkey), 1 + 2 * key_len,
-				      ecdh->ec->key.idx, point);
-	if (ret != MP_OKAY)
+				      ecdh->ec->key->idx, point);
+	if (ret != MP_OKAY) {
+		LOG_WOLF_ERROR_FUNC(wc_ecc_import_point_der, ret);
 		goto fail;
+	}
 
 	secret = wpabuf_alloc(key_len);
-	if (!secret)
+	if (!secret) {
+		LOG_WOLF_ERROR_FUNC_NULL(wpabuf_alloc);
 		goto fail;
+	}
 
-	ret = wc_ecc_shared_secret_ex(&ecdh->ec->key, point,
+	ret = wc_ecc_shared_secret_ex(ecdh->ec->key, point,
 				      wpabuf_put(secret, key_len), &key_len);
-	if (ret != MP_OKAY)
+	if (ret != MP_OKAY) {
+		LOG_WOLF_ERROR_FUNC(wc_ecc_shared_secret_ex, ret);
 		goto fail;
+	}
 
 done:
 	wc_ecc_del_point(point);
@@ -1846,41 +2292,21 @@
 	return crypto_ec_prime_len(ecdh->ec);
 }
 
-
-struct crypto_ec_key {
-	ecc_key *eckey;
-	WC_RNG *rng; /* Needs to be initialized before use.
-		      * *NOT* initialized in crypto_ec_key_init */
-};
-
-
 static struct crypto_ec_key * crypto_ec_key_init(void)
 {
 	struct crypto_ec_key *key;
 
 	key = os_zalloc(sizeof(struct crypto_ec_key));
 	if (key) {
-#ifdef CONFIG_FIPS
-		key->eckey = os_zalloc(sizeof(ecc_key));
-#else /* CONFIG_FIPS */
-		key->eckey = wc_ecc_key_new(NULL);
-#endif /* CONFIG_FIPS */
+		key->eckey = ecc_key_init();
 		/* Omit key->rng initialization because it seeds itself and thus
 		 * consumes entropy that may never be used. Lazy initialize when
 		 * necessary. */
 		if (!key->eckey) {
-			wpa_printf(MSG_ERROR,
-				   "wolfSSL: crypto_ec_key_init() failed");
+			LOG_WOLF_ERROR_FUNC_NULL(ecc_key_init);
 			crypto_ec_key_deinit(key);
 			key = NULL;
 		}
-#ifdef CONFIG_FIPS
-		else if (wc_ecc_init_ex(key->eckey, NULL, INVALID_DEVID) != 0) {
-			wpa_printf(MSG_ERROR, "wolfSSL: wc_ecc_init_ex failed");
-			crypto_ec_key_deinit(key);
-			key = NULL;
-		}
-#endif /* CONFIG_FIPS */
 	}
 	return key;
 }
@@ -1889,32 +2315,40 @@
 void crypto_ec_key_deinit(struct crypto_ec_key *key)
 {
 	if (key) {
-#ifdef CONFIG_FIPS
-		os_free(key->rng);
-		os_free(key->eckey);
-#else /* CONFIG_FIPS */
-		wc_rng_free(key->rng);
-		wc_ecc_key_free(key->eckey);
-#endif /* CONFIG_FIPS */
+		ecc_key_deinit(key->eckey);
+		wc_rng_deinit(key->rng);
 		os_free(key);
 	}
 }
 
 
+static WC_RNG * crypto_ec_key_init_rng(struct crypto_ec_key *key)
+{
+	if (!key->rng) {
+		/* Lazy init key->rng */
+		key->rng = wc_rng_init();
+		if (!key->rng)
+			LOG_WOLF_ERROR_FUNC_NULL(wc_rng_init);
+	}
+	return key->rng;
+}
+
+
 struct crypto_ec_key * crypto_ec_key_parse_priv(const u8 *der, size_t der_len)
 {
 	struct crypto_ec_key *ret;
 	word32 idx = 0;
+	int err;
 
 	ret = crypto_ec_key_init();
 	if (!ret) {
-		wpa_printf(MSG_ERROR, "wolfSSL: crypto_ec_key_init failed");
+		LOG_WOLF_ERROR_FUNC_NULL(crypto_ec_key_init);
 		goto fail;
 	}
 
-	if (wc_EccPrivateKeyDecode(der, &idx, ret->eckey, (word32) der_len) !=
-	    0) {
-		wpa_printf(MSG_ERROR, "wolfSSL: wc_EccPrivateKeyDecode failed");
+	err = wc_EccPrivateKeyDecode(der, &idx, ret->eckey, (word32) der_len);
+	if (err != 0) {
+		LOG_WOLF_ERROR_FUNC(wc_EccPrivateKeyDecode, err);
 		goto fail;
 	}
 
@@ -1930,8 +2364,7 @@
 {
 
 	if (!key || !key->eckey || !key->eckey->dp) {
-		wpa_printf(MSG_ERROR, "wolfSSL: %s: invalid input parameters",
-			   __func__);
+		LOG_INVALID_PARAMETERS();
 		return -1;
 	}
 
@@ -1942,54 +2375,114 @@
 		return 20;
 	case ECC_SECP521R1:
 		return 21;
+	case ECC_SECP192R1:
+		return 25;
+	case ECC_SECP224R1:
+		return 26;
+#ifdef HAVE_ECC_BRAINPOOL
+	case ECC_BRAINPOOLP224R1:
+		return 27;
 	case ECC_BRAINPOOLP256R1:
 		return 28;
 	case ECC_BRAINPOOLP384R1:
 		return 29;
 	case ECC_BRAINPOOLP512R1:
 		return 30;
+#endif /* HAVE_ECC_BRAINPOOL */
 	}
 
-	wpa_printf(MSG_ERROR, "wolfSSL: Unsupported curve (id=%d) in EC key",
-		   key->eckey->dp->id);
+	LOG_WOLF_ERROR_VA("Unsupported curve (id=%d) in EC key",
+			  key->eckey->dp->id);
 	return -1;
 }
 
 
+static int crypto_ec_key_gen_public_key(struct crypto_ec_key *key)
+{
+	int err;
+
+#ifdef WOLFSSL_OLD_FIPS
+	err = wc_ecc_make_pub(key->eckey, NULL);
+#else /* WOLFSSL_OLD_FIPS */
+	/* Have wolfSSL generate the public key to make it available for output
+	 */
+	if (!crypto_ec_key_init_rng(key)) {
+		LOG_WOLF_ERROR_FUNC_NULL(crypto_ec_key_init_rng);
+		return -1;
+	}
+
+	err = wc_ecc_make_pub_ex(key->eckey, NULL, key->rng);
+#endif /* WOLFSSL_OLD_FIPS */
+
+	if (err != MP_OKAY) {
+		LOG_WOLF_ERROR_FUNC(wc_ecc_make_pub_ex, err);
+		return -1;
+	}
+
+	return 0;
+}
+
+
 struct wpabuf * crypto_ec_key_get_subject_public_key(struct crypto_ec_key *key)
 {
-	byte *der = NULL;
 	int der_len;
 	struct wpabuf *ret = NULL;
+	int err;
 
 	if (!key || !key->eckey) {
-		wpa_printf(MSG_ERROR, "wolfSSL: %s: invalid input parameters",
-			   __func__);
+		LOG_INVALID_PARAMETERS();
 		goto fail;
 	}
 
-	der_len = wc_EccPublicKeyDerSize(key->eckey, 1);
-	if (der_len <= 0) {
-		wpa_printf(MSG_ERROR, "wolfSSL: wc_EccPublicKeyDerSize failed");
+#ifdef WOLFSSL_OLD_FIPS
+	if (key->eckey->type == ECC_PRIVATEKEY_ONLY &&
+	    crypto_ec_key_gen_public_key(key) != 0) {
+		LOG_WOLF_ERROR_FUNC(crypto_ec_key_gen_public_key, -1);
+		goto fail;
+	}
+#endif /* WOLFSSL_OLD_FIPS */
+
+	der_len = err = wc_EccPublicKeyToDer_ex(key->eckey, NULL, 0, 1, 1);
+	if (err == ECC_PRIVATEONLY_E) {
+		if (crypto_ec_key_gen_public_key(key) != 0) {
+			LOG_WOLF_ERROR_FUNC(crypto_ec_key_gen_public_key, -1);
+			goto fail;
+		}
+		der_len = err = wc_EccPublicKeyToDer_ex(key->eckey, NULL, 0, 1,
+							1);
+	}
+	if (err <= 0) {
+		LOG_WOLF_ERROR_FUNC(wc_EccPublicKeyDerSize, err);
 		goto fail;
 	}
 
-	der = os_malloc(der_len);
-	if (!der)
-		goto fail;
-
-	der_len = wc_EccPublicKeyToDer(key->eckey, der, der_len, 1);
-	if (der_len <= 0) {
-		wpa_printf(MSG_ERROR, "wolfSSL: wc_EccPublicKeyToDer failed");
+	ret = wpabuf_alloc(der_len);
+	if (!ret) {
+		LOG_WOLF_ERROR_FUNC_NULL(wpabuf_alloc);
 		goto fail;
 	}
 
-	ret = wpabuf_alloc_copy(der, der_len);
-	os_free(der);
+	err = wc_EccPublicKeyToDer_ex(key->eckey, wpabuf_mhead(ret), der_len, 1,
+				      1);
+	if (err == ECC_PRIVATEONLY_E) {
+		if (crypto_ec_key_gen_public_key(key) != 0) {
+			LOG_WOLF_ERROR_FUNC(crypto_ec_key_gen_public_key, -1);
+			goto fail;
+		}
+		err = wc_EccPublicKeyToDer_ex(key->eckey, wpabuf_mhead(ret),
+					      der_len, 1, 1);
+	}
+	if (err <= 0) {
+		LOG_WOLF_ERROR_FUNC(wc_EccPublicKeyToDer, err);
+		goto fail;
+	}
+	der_len = err;
+	wpabuf_put(ret, der_len);
+
 	return ret;
 
 fail:
-	os_free(der);
+	wpabuf_free(ret);
 	return NULL;
 }
 
@@ -1998,16 +2491,17 @@
 {
 	word32 idx = 0;
 	struct crypto_ec_key *ret = NULL;
+	int err;
 
 	ret = crypto_ec_key_init();
 	if (!ret) {
-		wpa_printf(MSG_ERROR, "wolfSSL: crypto_ec_key_init failed");
+		LOG_WOLF_ERROR_FUNC_NULL(crypto_ec_key_init);
 		goto fail;
 	}
 
-	if (wc_EccPublicKeyDecode(der, &idx, ret->eckey, (word32) der_len) != 0)
-	{
-		wpa_printf(MSG_ERROR, "wolfSSL: wc_EccPublicKeyDecode failed");
+	err = wc_EccPublicKeyDecode(der, &idx, ret->eckey, (word32) der_len);
+	if (err != 0) {
+		LOG_WOLF_ERROR_FUNC(wc_EccPublicKeyDecode, err);
 		goto fail;
 	}
 
@@ -2021,60 +2515,45 @@
 struct wpabuf * crypto_ec_key_sign(struct crypto_ec_key *key, const u8 *data,
 				   size_t len)
 {
-	byte *der = NULL;
 	int der_len;
+	int err;
 	word32 w32_der_len;
 	struct wpabuf *ret = NULL;
 
 	if (!key || !key->eckey || !data || len == 0) {
-		wpa_printf(MSG_ERROR, "wolfSSL: %s: invalid input parameters",
-			   __func__);
+		LOG_INVALID_PARAMETERS();
 		goto fail;
 	}
 
-	if (!key->rng) {
-		/* Lazy init key->rng */
-#ifdef CONFIG_FIPS
-		key->rng = os_zalloc(sizeof(WC_RNG));
-#else /* CONFIG_FIPS */
-		key->rng = wc_rng_new(NULL, 0, NULL);
-#endif /* CONFIG_FIPS */
-		if (!key->rng) {
-			wpa_printf(MSG_ERROR, "wolfSSL: wc_rng_new failed");
-			goto fail;
-		}
-#ifdef CONFIG_FIPS
-		if (wc_InitRng(key->rng) != 0) {
-			wpa_printf(MSG_ERROR, "wolfSSL: wc_InitRng failed");
-			goto fail;
-		}
-#endif /* CONFIG_FIPS */
+	if (!crypto_ec_key_init_rng(key)) {
+		LOG_WOLF_ERROR_FUNC_NULL(crypto_ec_key_init_rng);
+		goto fail;
 	}
 
 	der_len = wc_ecc_sig_size(key->eckey);
 	if (der_len <= 0) {
-		wpa_printf(MSG_ERROR, "wolfSSL: wc_ecc_sig_size failed");
+		LOG_WOLF_ERROR_FUNC(wc_ecc_sig_size, der_len);
 		goto fail;
 	}
 
-	der = os_malloc(der_len);
-	if (!der)
+	ret = wpabuf_alloc(der_len);
+	if (!ret) {
+		LOG_WOLF_ERROR_FUNC_NULL(wpabuf_alloc);
 		goto fail;
+	}
 
 	w32_der_len = (word32) der_len;
-	if (wc_ecc_sign_hash(data, len, der, &w32_der_len, key->rng, key->eckey)
-	    != 0) {
-		wpa_printf(MSG_ERROR, "wolfSSL: wc_ecc_sign_hash failed");
+	err = wc_ecc_sign_hash(data, len, wpabuf_mhead(ret), &w32_der_len,
+			       key->rng, key->eckey);
+	if (err != 0) {
+		LOG_WOLF_ERROR_FUNC(wc_ecc_sign_hash, err);
 		goto fail;
 	}
+	wpabuf_put(ret, w32_der_len);
 
-	ret = wpabuf_alloc_copy(der, der_len);
-	os_free(der);
-	if (!ret)
-		wpa_printf(MSG_ERROR, "wolfSSL: wpabuf_alloc_copy failed");
 	return ret;
 fail:
-	os_free(der);
+	wpabuf_free(ret);
 	return NULL;
 }
 
@@ -2085,26 +2564,996 @@
 	int res = 0;
 
 	if (!key || !key->eckey || !data || len == 0 || !sig || sig_len == 0) {
-		wpa_printf(MSG_ERROR, "wolfSSL: %s: invalid input parameters",
-			   __func__);
+		LOG_INVALID_PARAMETERS();
 		return -1;
 	}
 
 	if (wc_ecc_verify_hash(sig, sig_len, data, len, &res, key->eckey) != 0)
 	{
-		wpa_printf(MSG_ERROR, "wolfSSL: wc_ecc_verify_hash failed");
+		LOG_WOLF_ERROR("wc_ecc_verify_hash failed");
 		return -1;
 	}
 
 	if (res != 1)
-		wpa_printf(MSG_DEBUG,
-			   "wolfSSL: crypto_ec_key_verify_signature failed");
+		LOG_WOLF_ERROR("crypto_ec_key_verify_signature failed");
 
 	return res;
 }
 
 #endif /* CONFIG_ECC */
 
+#ifdef CONFIG_DPP
+
+struct wpabuf * crypto_ec_key_get_ecprivate_key(struct crypto_ec_key *key,
+						bool include_pub)
+{
+	int len;
+	int err;
+	struct wpabuf *ret = NULL;
+
+	if (!key || !key->eckey) {
+		LOG_INVALID_PARAMETERS();
+		return NULL;
+	}
+
+#ifdef WOLFSSL_OLD_FIPS
+	if (key->eckey->type != ECC_PRIVATEKEY &&
+	    key->eckey->type != ECC_PRIVATEKEY_ONLY) {
+		LOG_INVALID_PARAMETERS();
+		return NULL;
+	}
+#endif /* WOLFSSL_OLD_FIPS */
+
+	len = err = wc_EccKeyDerSize(key->eckey, include_pub);
+	if (err == ECC_PRIVATEONLY_E && include_pub) {
+		if (crypto_ec_key_gen_public_key(key) != 0) {
+			LOG_WOLF_ERROR_FUNC(crypto_ec_key_gen_public_key, -1);
+			return NULL;
+		}
+		len = err = wc_EccKeyDerSize(key->eckey, include_pub);
+	}
+	if (err <= 0) {
+		/* Exception for BAD_FUNC_ARG because higher levels blindly call
+		 * this function to determine if this is a private key or not.
+		 * BAD_FUNC_ARG most probably means that key->eckey is a public
+		 * key not private. */
+		if (err != BAD_FUNC_ARG)
+			LOG_WOLF_ERROR_FUNC(wc_EccKeyDerSize, err);
+		return NULL;
+	}
+
+	ret = wpabuf_alloc(len);
+	if (!ret) {
+		LOG_WOLF_ERROR_FUNC_NULL(wpabuf_alloc);
+		return NULL;
+	}
+
+	if (include_pub)
+		err = wc_EccKeyToDer(key->eckey, wpabuf_put(ret, len), len);
+	else
+		err = wc_EccPrivateKeyToDer(key->eckey, wpabuf_put(ret, len),
+					    len);
+
+	if (err != len) {
+		LOG_WOLF_ERROR_VA("%s failed with err: %d", include_pub ?
+				  "wc_EccKeyToDer" : "wc_EccPrivateKeyToDer",
+				  err);
+		wpabuf_free(ret);
+		ret = NULL;
+	}
+
+	return ret;
+}
+
+
+struct wpabuf * crypto_ec_key_get_pubkey_point(struct crypto_ec_key *key,
+					       int prefix)
+{
+	int err;
+	word32 len = 0;
+	struct wpabuf *ret = NULL;
+
+	if (!key || !key->eckey) {
+		LOG_INVALID_PARAMETERS();
+		return NULL;
+	}
+
+	err = wc_ecc_export_x963(key->eckey, NULL, &len);
+	if (err != LENGTH_ONLY_E) {
+		LOG_WOLF_ERROR_FUNC(wc_ecc_export_x963, err);
+		goto fail;
+	}
+
+	ret = wpabuf_alloc(len);
+	if (!ret) {
+		LOG_WOLF_ERROR_FUNC_NULL(wpabuf_alloc);
+		goto fail;
+	}
+
+	err = wc_ecc_export_x963(key->eckey, wpabuf_mhead(ret), &len);
+	if (err == ECC_PRIVATEONLY_E) {
+		if (crypto_ec_key_gen_public_key(key) != 0) {
+			LOG_WOLF_ERROR_FUNC(crypto_ec_key_gen_public_key, -1);
+			goto fail;
+		}
+		err = wc_ecc_export_x963(key->eckey, wpabuf_mhead(ret), &len);
+	}
+	if (err != MP_OKAY) {
+		LOG_WOLF_ERROR_FUNC(wc_ecc_export_x963, err);
+		goto fail;
+	}
+
+	if (!prefix)
+		os_memmove(wpabuf_mhead(ret), wpabuf_mhead_u8(ret) + 1,
+			   (size_t)--len);
+	wpabuf_put(ret, len);
+
+	return ret;
+
+fail:
+	wpabuf_free(ret);
+	return NULL;
+}
+
+
+struct crypto_ec_key * crypto_ec_key_set_pub(int group, const u8 *x,
+					     const u8 *y, size_t len)
+{
+	struct crypto_ec_key *ret = NULL;
+	int curve_id = crypto_ec_group_2_id(group);
+	int err;
+
+	if (!x || !y || len == 0 || curve_id == ECC_CURVE_INVALID ||
+	    wc_ecc_get_curve_size_from_id(curve_id) != (int) len) {
+		LOG_INVALID_PARAMETERS();
+		return NULL;
+	}
+
+	ret = crypto_ec_key_init();
+	if (!ret) {
+		LOG_WOLF_ERROR_FUNC_NULL(crypto_ec_key_init);
+		return NULL;
+	}
+
+	/* Cast necessary for FIPS API */
+	err = wc_ecc_import_unsigned(ret->eckey, (u8 *) x, (u8 *) y, NULL,
+				     curve_id);
+	if (err != MP_OKAY) {
+		LOG_WOLF_ERROR_FUNC(wc_ecc_import_unsigned, err);
+		crypto_ec_key_deinit(ret);
+		return NULL;
+	}
+
+	return ret;
+}
+
+
+int crypto_ec_key_cmp(struct crypto_ec_key *key1, struct crypto_ec_key *key2)
+{
+	int ret;
+	struct wpabuf *key1_buf = crypto_ec_key_get_subject_public_key(key1);
+	struct wpabuf *key2_buf = crypto_ec_key_get_subject_public_key(key2);
+
+	if ((key1 && !key1_buf) || (key2 && !key2_buf)) {
+		LOG_WOLF_ERROR("crypto_ec_key_get_subject_public_key failed");
+		return -1;
+	}
+
+	ret = wpabuf_cmp(key1_buf, key2_buf);
+	if (ret != 0)
+		ret = -1; /* Default to -1 for different keys */
+
+	wpabuf_clear_free(key1_buf);
+	wpabuf_clear_free(key2_buf);
+	return ret;
+}
+
+
+/* wolfSSL doesn't have a pretty print function for keys so just print out the
+ * PEM of the private key. */
+void crypto_ec_key_debug_print(const struct crypto_ec_key *key,
+			       const char *title)
+{
+	struct wpabuf * key_buf;
+	struct wpabuf * out = NULL;
+	int err;
+	int pem_len;
+
+	if (!key || !key->eckey) {
+		LOG_INVALID_PARAMETERS();
+		return;
+	}
+
+	if (key->eckey->type == ECC_PUBLICKEY)
+		key_buf = crypto_ec_key_get_subject_public_key(
+			(struct crypto_ec_key *) key);
+	else
+		key_buf = crypto_ec_key_get_ecprivate_key(
+			(struct crypto_ec_key *) key, 1);
+
+	if (!key_buf) {
+		LOG_WOLF_ERROR_VA("%s has returned NULL",
+				  key->eckey->type == ECC_PUBLICKEY ?
+				  "crypto_ec_key_get_subject_public_key" :
+				  "crypto_ec_key_get_ecprivate_key");
+		goto fail;
+	}
+
+	if (!title)
+		title = "";
+
+	err = wc_DerToPem(wpabuf_head(key_buf), wpabuf_len(key_buf), NULL, 0,
+			  ECC_TYPE);
+	if (err <= 0) {
+		LOG_WOLF_ERROR_FUNC(wc_DerToPem, err);
+		goto fail;
+	}
+	pem_len = err;
+
+	out = wpabuf_alloc(pem_len + 1);
+	if (!out) {
+		LOG_WOLF_ERROR_FUNC_NULL(wc_DerToPem);
+		goto fail;
+	}
+
+	err = wc_DerToPem(wpabuf_head(key_buf), wpabuf_len(key_buf),
+			  wpabuf_mhead(out), pem_len, ECC_TYPE);
+	if (err <= 0) {
+		LOG_WOLF_ERROR_FUNC(wc_DerToPem, err);
+		goto fail;
+	}
+
+	wpabuf_mhead_u8(out)[err] = '\0';
+	wpabuf_put(out, err + 1);
+	wpa_printf(MSG_DEBUG, "%s:\n%s", title,
+		   (const char *) wpabuf_head(out));
+
+fail:
+	wpabuf_clear_free(key_buf);
+	wpabuf_clear_free(out);
+}
+
+
+void crypto_ec_point_debug_print(const struct crypto_ec *e,
+				 const struct crypto_ec_point *p,
+				 const char *title)
+{
+	u8 x[ECC_MAXSIZE];
+	u8 y[ECC_MAXSIZE];
+	int coord_size;
+	int err;
+
+	if (!p || !e) {
+		LOG_INVALID_PARAMETERS();
+		return;
+	}
+
+	coord_size = e->key->dp->size;
+
+	if (!title)
+		title = "";
+
+	err = crypto_ec_point_to_bin((struct crypto_ec *)e, p, x, y);
+	if (err != 0) {
+		LOG_WOLF_ERROR_FUNC(crypto_ec_point_to_bin, err);
+		return;
+	}
+
+	wpa_hexdump(MSG_DEBUG, title, x, coord_size);
+	wpa_hexdump(MSG_DEBUG, title, y, coord_size);
+}
+
+
+struct crypto_ec_key * crypto_ec_key_gen(int group)
+{
+	int curve_id = crypto_ec_group_2_id(group);
+	int err;
+	struct crypto_ec_key * ret = NULL;
+
+	if (curve_id == ECC_CURVE_INVALID) {
+		LOG_INVALID_PARAMETERS();
+		return NULL;
+	}
+
+	ret = crypto_ec_key_init();
+	if (!ret) {
+		LOG_WOLF_ERROR_FUNC_NULL(crypto_ec_key_init);
+		return NULL;
+	}
+
+	if (!crypto_ec_key_init_rng(ret)) {
+		LOG_WOLF_ERROR_FUNC_NULL(crypto_ec_key_init_rng);
+		goto fail;
+	}
+
+	err = wc_ecc_make_key_ex(ret->rng, 0, ret->eckey, curve_id);
+	if (err != MP_OKAY) {
+		LOG_WOLF_ERROR_FUNC(wc_ecc_make_key_ex, err);
+		goto fail;
+	}
+
+	return ret;
+fail:
+	crypto_ec_key_deinit(ret);
+	return NULL;
+}
+
+
+int crypto_ec_key_verify_signature_r_s(struct crypto_ec_key *key,
+				       const u8 *data, size_t len,
+				       const u8 *r, size_t r_len,
+				       const u8 *s, size_t s_len)
+{
+	int err;
+	u8 sig[ECC_MAX_SIG_SIZE];
+	word32 sig_len = ECC_MAX_SIG_SIZE;
+
+	if (!key || !key->eckey || !data || !len || !r || !r_len ||
+	    !s || !s_len) {
+		LOG_INVALID_PARAMETERS();
+		return -1;
+	}
+
+	err = wc_ecc_rs_raw_to_sig(r, r_len, s, s_len, sig, &sig_len);
+	if (err != MP_OKAY) {
+		LOG_WOLF_ERROR_FUNC(wc_ecc_rs_raw_to_sig, err);
+		return -1;
+	}
+
+	return crypto_ec_key_verify_signature(key, data, len, sig, sig_len);
+}
+
+
+struct crypto_ec_point * crypto_ec_key_get_public_key(struct crypto_ec_key *key)
+{
+	ecc_point *point = NULL;
+	int err;
+	u8 *der = NULL;
+	word32 der_len = 0;
+
+	if (!key || !key->eckey || !key->eckey->dp) {
+		LOG_INVALID_PARAMETERS();
+		goto fail;
+	}
+
+	err = wc_ecc_export_x963(key->eckey, NULL, &der_len);
+	if (err != LENGTH_ONLY_E) {
+		LOG_WOLF_ERROR_FUNC(wc_ecc_export_x963, err);
+		goto fail;
+	}
+
+	der = os_malloc(der_len);
+	if (!der) {
+		LOG_WOLF_ERROR_FUNC_NULL(os_malloc);
+		goto fail;
+	}
+
+	err = wc_ecc_export_x963(key->eckey, der, &der_len);
+	if (err == ECC_PRIVATEONLY_E) {
+		if (crypto_ec_key_gen_public_key(key) != 0) {
+			LOG_WOLF_ERROR_FUNC(crypto_ec_key_gen_public_key, -1);
+			goto fail;
+		}
+		err = wc_ecc_export_x963(key->eckey, der, &der_len);
+	}
+	if (err != MP_OKAY) {
+		LOG_WOLF_ERROR_FUNC(wc_ecc_export_x963, err);
+		goto fail;
+	}
+
+	point = wc_ecc_new_point();
+	if (!point) {
+		LOG_WOLF_ERROR_FUNC_NULL(wc_ecc_new_point);
+		goto fail;
+	}
+
+	err = wc_ecc_import_point_der(der, der_len, key->eckey->idx, point);
+	if (err != MP_OKAY) {
+		LOG_WOLF_ERROR_FUNC(wc_ecc_import_point_der, err);
+		goto fail;
+	}
+
+	os_free(der);
+	return (struct crypto_ec_point *) point;
+
+fail:
+	os_free(der);
+	if (point)
+		wc_ecc_del_point(point);
+	return NULL;
+}
+
+
+struct crypto_bignum * crypto_ec_key_get_private_key(struct crypto_ec_key *key)
+{
+	u8 priv[ECC_MAXSIZE];
+	word32 priv_len = ECC_MAXSIZE;
+#ifdef WOLFSSL_OLD_FIPS
+	/* Needed to be compliant with the old API */
+	u8 qx[ECC_MAXSIZE];
+	word32 qx_len = ECC_MAXSIZE;
+	u8 qy[ECC_MAXSIZE];
+	word32 qy_len = ECC_MAXSIZE;
+#endif /* WOLFSSL_OLD_FIPS */
+	struct crypto_bignum *ret = NULL;
+	int err;
+
+	if (!key || !key->eckey) {
+		LOG_INVALID_PARAMETERS();
+		return NULL;
+	}
+
+#ifndef WOLFSSL_OLD_FIPS
+	err = wc_ecc_export_private_raw(key->eckey, NULL, NULL, NULL, NULL,
+					priv, &priv_len);
+#else /* WOLFSSL_OLD_FIPS */
+	err = wc_ecc_export_private_raw(key->eckey, qx, &qx_len, qy, &qy_len,
+					priv, &priv_len);
+#endif /* WOLFSSL_OLD_FIPS */
+	if (err != MP_OKAY) {
+		LOG_WOLF_ERROR_FUNC(wc_ecc_export_private_raw, err);
+		return NULL;
+	}
+
+	ret = crypto_bignum_init_set(priv, priv_len);
+	forced_memzero(priv, priv_len);
+	return ret;
+}
+
+
+struct wpabuf * crypto_ec_key_sign_r_s(struct crypto_ec_key *key,
+				       const u8 *data, size_t len)
+{
+	int err;
+	u8 success = 0;
+	mp_int r;
+	mp_int s;
+	u8 rs_init = 0;
+	int sz;
+	struct wpabuf * ret = NULL;
+
+	if (!key || !key->eckey || !key->eckey->dp || !data || !len) {
+		LOG_INVALID_PARAMETERS();
+		return NULL;
+	}
+
+	sz = key->eckey->dp->size;
+
+	if (!crypto_ec_key_init_rng(key)) {
+		LOG_WOLF_ERROR_FUNC_NULL(crypto_ec_key_init_rng);
+		goto fail;
+	}
+
+	err = mp_init_multi(&r, &s, NULL, NULL, NULL, NULL);
+	if (err != MP_OKAY) {
+		LOG_WOLF_ERROR_FUNC(mp_init_multi, err);
+		goto fail;
+	}
+	rs_init = 1;
+
+	err = wc_ecc_sign_hash_ex(data, len, key->rng, key->eckey, &r, &s);
+	if (err != MP_OKAY) {
+		LOG_WOLF_ERROR_FUNC(wc_ecc_sign_hash_ex, err);
+		goto fail;
+	}
+
+	if (mp_unsigned_bin_size(&r) > sz || mp_unsigned_bin_size(&s) > sz) {
+		LOG_WOLF_ERROR_VA("Unexpected size of r or s (%d %d %d)", sz,
+				  mp_unsigned_bin_size(&r),
+				  mp_unsigned_bin_size(&s));
+		goto fail;
+	}
+
+	ret = wpabuf_alloc(2 * sz);
+	if (!ret) {
+		LOG_WOLF_ERROR_FUNC_NULL(wpabuf_alloc);
+		goto fail;
+	}
+
+	err = mp_to_unsigned_bin_len(&r, wpabuf_put(ret, sz), sz);
+	if (err == MP_OKAY)
+		err = mp_to_unsigned_bin_len(&s, wpabuf_put(ret, sz), sz);
+	if (err != MP_OKAY) {
+		LOG_WOLF_ERROR_FUNC(wc_ecc_sign_hash_ex, err);
+		goto fail;
+	}
+
+	success = 1;
+fail:
+	if (rs_init) {
+		mp_free(&r);
+		mp_free(&s);
+	}
+	if (!success) {
+		wpabuf_free(ret);
+		ret = NULL;
+	}
+
+	return ret;
+}
+
+
+struct crypto_ec_key *
+crypto_ec_key_set_pub_point(struct crypto_ec *e,
+			    const struct crypto_ec_point *pub)
+{
+	struct crypto_ec_key  *ret = NULL;
+	int err;
+	byte *buf = NULL;
+	word32 buf_len = 0;
+
+	if (!e || !pub) {
+		LOG_INVALID_PARAMETERS();
+		return NULL;
+	}
+
+	/* Export to DER to not mess with wolfSSL internals */
+	err = wc_ecc_export_point_der(wc_ecc_get_curve_idx(e->curve_id),
+				      (ecc_point *) pub, NULL, &buf_len);
+	if (err != LENGTH_ONLY_E || !buf_len) {
+		LOG_WOLF_ERROR_FUNC(wc_ecc_export_point_der, err);
+		goto fail;
+	}
+
+	buf = os_malloc(buf_len);
+	if (!buf) {
+		LOG_WOLF_ERROR_FUNC_NULL(os_malloc);
+		goto fail;
+	}
+
+	err = wc_ecc_export_point_der(wc_ecc_get_curve_idx(e->curve_id),
+			(ecc_point *) pub, buf, &buf_len);
+	if (err != MP_OKAY) {
+		LOG_WOLF_ERROR_FUNC(wc_ecc_export_point_der, err);
+		goto fail;
+	}
+
+	ret = crypto_ec_key_init();
+	if (!ret) {
+		LOG_WOLF_ERROR_FUNC_NULL(crypto_ec_key_init);
+		goto fail;
+	}
+
+	err = wc_ecc_import_x963_ex(buf, buf_len, ret->eckey, e->curve_id);
+	if (err != MP_OKAY) {
+		LOG_WOLF_ERROR_FUNC(wc_ecc_import_x963_ex, err);
+		goto fail;
+	}
+
+	os_free(buf);
+	return ret;
+
+fail:
+	os_free(buf);
+	crypto_ec_key_deinit(ret);
+	return NULL;
+}
+
+
+struct wpabuf * crypto_pkcs7_get_certificates(const struct wpabuf *pkcs7)
+{
+	PKCS7 *p7 = NULL;
+	struct wpabuf *ret = NULL;
+	int err = 0;
+	int total_sz = 0;
+	int i;
+
+	if (!pkcs7) {
+		LOG_INVALID_PARAMETERS();
+		return NULL;
+	}
+
+	p7 = wc_PKCS7_New(NULL, INVALID_DEVID);
+	if (!p7) {
+		LOG_WOLF_ERROR_FUNC_NULL(wc_PKCS7_New);
+		return NULL;
+	}
+
+	err = wc_PKCS7_VerifySignedData(p7, (byte *) wpabuf_head(pkcs7),
+					wpabuf_len(pkcs7));
+	if (err != 0) {
+		LOG_WOLF_ERROR_FUNC(wc_PKCS7_VerifySignedData, err);
+		wc_PKCS7_Free(p7);
+		goto fail;
+	}
+
+	/* Need to access p7 members directly */
+	for (i = 0; i < MAX_PKCS7_CERTS; i++) {
+		if (p7->certSz[i] == 0)
+			continue;
+		err = wc_DerToPem(p7->cert[i], p7->certSz[i], NULL, 0,
+				  CERT_TYPE);
+		if (err > 0) {
+			total_sz += err;
+		} else {
+			LOG_WOLF_ERROR_FUNC(wc_DerToPem, err);
+			goto fail;
+		}
+	}
+
+	if (total_sz == 0) {
+		LOG_WOLF_ERROR("No certificates found in PKCS7 input");
+		goto fail;
+	}
+
+	ret = wpabuf_alloc(total_sz);
+	if (!ret) {
+		LOG_WOLF_ERROR_FUNC_NULL(wpabuf_alloc);
+		goto fail;
+	}
+
+	/* Need to access p7 members directly */
+	for (i = 0; i < MAX_PKCS7_CERTS; i++) {
+		if (p7->certSz[i] == 0)
+			continue;
+		/* Not using wpabuf_put() here so that wpabuf_overflow() isn't
+		 * called in case of a size mismatch. wc_DerToPem() checks if
+		 * the output is large enough internally. */
+		err = wc_DerToPem(p7->cert[i], p7->certSz[i],
+				  wpabuf_mhead_u8(ret) + wpabuf_len(ret),
+				  wpabuf_tailroom(ret),
+				  CERT_TYPE);
+		if (err > 0) {
+			wpabuf_put(ret, err);
+		} else {
+			LOG_WOLF_ERROR_FUNC(wc_DerToPem, err);
+			wpabuf_free(ret);
+			ret = NULL;
+			goto fail;
+		}
+	}
+
+fail:
+	if (p7)
+		wc_PKCS7_Free(p7);
+	return ret;
+}
+
+
+/* BEGIN Certificate Signing Request (CSR) APIs */
+
+enum cert_type {
+	cert_type_none = 0,
+	cert_type_decoded_cert,
+	cert_type_cert,
+};
+
+struct crypto_csr {
+	union {
+		/* For parsed csr should be read-only for higher levels */
+		DecodedCert dc;
+		Cert c; /* For generating a csr */
+	} req;
+	enum cert_type type;
+	struct crypto_ec_key *pubkey;
+};
+
+
+/* Helper function to make sure that the correct type is initialized */
+static void crypto_csr_init_type(struct crypto_csr *csr, enum cert_type type,
+				 const byte *source, word32 in_sz)
+{
+	int err;
+
+	if (csr->type == type)
+		return; /* Already correct type */
+
+	switch (csr->type) {
+	case cert_type_decoded_cert:
+		wc_FreeDecodedCert(&csr->req.dc);
+		break;
+	case cert_type_cert:
+#ifdef WOLFSSL_CERT_GEN_CACHE
+		wc_SetCert_Free(&csr->req.c);
+#endif /* WOLFSSL_CERT_GEN_CACHE */
+		break;
+	case cert_type_none:
+		break;
+	}
+
+	switch (type) {
+	case cert_type_decoded_cert:
+		wc_InitDecodedCert(&csr->req.dc, source, in_sz, NULL);
+		break;
+	case cert_type_cert:
+		err = wc_InitCert(&csr->req.c);
+		if (err != 0)
+			LOG_WOLF_ERROR_FUNC(wc_InitCert, err);
+		break;
+	case cert_type_none:
+		break;
+	}
+
+	csr->type = type;
+}
+
+
+struct crypto_csr * crypto_csr_init(void)
+{
+	struct crypto_csr *ret = os_malloc(sizeof(struct crypto_csr));
+
+	if (!ret) {
+		LOG_WOLF_ERROR_FUNC_NULL(os_malloc);
+		return NULL;
+	}
+
+	ret->type = cert_type_none;
+	crypto_csr_init_type(ret, cert_type_cert, NULL, 0);
+	ret->pubkey = NULL;
+
+	return ret;
+}
+
+
+void crypto_csr_deinit(struct crypto_csr *csr)
+{
+	if (csr) {
+		crypto_csr_init_type(csr, cert_type_none, NULL, 0);
+		crypto_ec_key_deinit(csr->pubkey);
+		os_free(csr);
+	}
+}
+
+
+int crypto_csr_set_ec_public_key(struct crypto_csr *csr,
+				 struct crypto_ec_key *key)
+{
+	struct wpabuf *der = NULL;
+
+	if (!csr || !key || !key->eckey) {
+		LOG_INVALID_PARAMETERS();
+		return -1;
+	}
+
+	if (csr->pubkey) {
+		crypto_ec_key_deinit(csr->pubkey);
+		csr->pubkey = NULL;
+	}
+
+	/* Create copy of key to mitigate use-after-free errors */
+	der = crypto_ec_key_get_subject_public_key(key);
+	if (!der) {
+		LOG_WOLF_ERROR_FUNC_NULL(crypto_ec_key_get_subject_public_key);
+		return -1;
+	}
+
+	csr->pubkey = crypto_ec_key_parse_pub(wpabuf_head(der),
+					      wpabuf_len(der));
+	wpabuf_free(der);
+	if (!csr->pubkey) {
+		LOG_WOLF_ERROR_FUNC_NULL(crypto_ec_key_parse_pub);
+		return -1;
+	}
+
+	return 0;
+}
+
+
+int crypto_csr_set_name(struct crypto_csr *csr, enum crypto_csr_name type,
+			const char *name)
+{
+	int name_len;
+	char *dest;
+
+	if (!csr || !name) {
+		LOG_INVALID_PARAMETERS();
+		return -1;
+	}
+
+	if (csr->type != cert_type_cert) {
+		LOG_WOLF_ERROR_VA("csr is incorrect type (%d)", csr->type);
+		return -1;
+	}
+
+	name_len = os_strlen(name);
+	if (name_len >= CTC_NAME_SIZE) {
+		LOG_WOLF_ERROR("name input too long");
+		return -1;
+	}
+
+	switch (type) {
+	case CSR_NAME_CN:
+		dest = csr->req.c.subject.commonName;
+		break;
+	case CSR_NAME_SN:
+		dest = csr->req.c.subject.sur;
+		break;
+	case CSR_NAME_C:
+		dest = csr->req.c.subject.country;
+		break;
+	case CSR_NAME_O:
+		dest = csr->req.c.subject.org;
+		break;
+	case CSR_NAME_OU:
+		dest = csr->req.c.subject.unit;
+		break;
+	default:
+		LOG_INVALID_PARAMETERS();
+		return -1;
+	}
+
+	os_memcpy(dest, name, name_len);
+	dest[name_len] = '\0';
+
+	return 0;
+}
+
+
+int crypto_csr_set_attribute(struct crypto_csr *csr, enum crypto_csr_attr attr,
+			     int attr_type, const u8 *value, size_t len)
+{
+	if (!csr || attr_type != ASN1_TAG_UTF8STRING || !value ||
+	    len >= CTC_NAME_SIZE) {
+		LOG_INVALID_PARAMETERS();
+		return -1;
+	}
+
+	if (csr->type != cert_type_cert) {
+		LOG_WOLF_ERROR_VA("csr is incorrect type (%d)", csr->type);
+		return -1;
+	}
+
+	switch (attr) {
+	case CSR_ATTR_CHALLENGE_PASSWORD:
+		os_memcpy(csr->req.c.challengePw, value, len);
+		csr->req.c.challengePw[len] = '\0';
+		break;
+	default:
+		return -1;
+	}
+
+	return 0;
+}
+
+
+const u8 * crypto_csr_get_attribute(struct crypto_csr *csr,
+				    enum crypto_csr_attr attr,
+				    size_t *len, int *type)
+{
+	if (!csr || !len || !type) {
+		LOG_INVALID_PARAMETERS();
+		return NULL;;
+	}
+
+	switch (attr) {
+	case CSR_ATTR_CHALLENGE_PASSWORD:
+		switch (csr->type) {
+		case cert_type_decoded_cert:
+			*type = ASN1_TAG_UTF8STRING;
+			*len = csr->req.dc.cPwdLen;
+			return (const u8 *) csr->req.dc.cPwd;
+		case cert_type_cert:
+			*type = ASN1_TAG_UTF8STRING;
+			*len = os_strlen(csr->req.c.challengePw);
+			return (const u8 *) csr->req.c.challengePw;
+		case cert_type_none:
+			return NULL;
+		}
+		break;
+	}
+	return NULL;
+}
+
+
+struct wpabuf * crypto_csr_sign(struct crypto_csr *csr,
+				struct crypto_ec_key *key,
+				enum crypto_hash_alg algo)
+{
+	int err;
+	int len;
+	u8 *buf = NULL;
+	int buf_len;
+	struct wpabuf *ret = NULL;
+
+	if (!csr || !key || !key->eckey) {
+		LOG_INVALID_PARAMETERS();
+		return NULL;
+	}
+
+	if (csr->type != cert_type_cert) {
+		LOG_WOLF_ERROR_VA("csr is incorrect type (%d)", csr->type);
+		return NULL;
+	}
+
+	if (!crypto_ec_key_init_rng(key)) {
+		LOG_WOLF_ERROR_FUNC_NULL(crypto_ec_key_init_rng);
+		return NULL;
+	}
+
+	switch (algo) {
+	case CRYPTO_HASH_ALG_SHA256:
+		csr->req.c.sigType = CTC_SHA256wECDSA;
+		break;
+	case CRYPTO_HASH_ALG_SHA384:
+		csr->req.c.sigType = CTC_SHA384wECDSA;
+		break;
+	case CRYPTO_HASH_ALG_SHA512:
+		csr->req.c.sigType = CTC_SHA512wECDSA;
+		break;
+	default:
+		LOG_INVALID_PARAMETERS();
+		return NULL;
+	}
+
+	/* Pass in large value that is guaranteed to be larger than the
+	 * necessary buffer */
+	err = wc_MakeCertReq(&csr->req.c, NULL, 100000, NULL,
+			     csr->pubkey->eckey);
+	if (err <= 0) {
+		LOG_WOLF_ERROR_FUNC(wc_MakeCertReq, err);
+		goto fail;
+	}
+	len = err;
+
+	buf_len = len + MAX_SEQ_SZ * 2 + MAX_ENCODED_SIG_SZ;
+	buf = os_malloc(buf_len);
+	if (!buf) {
+		LOG_WOLF_ERROR_FUNC_NULL(os_malloc);
+		goto fail;
+	}
+
+	err = wc_MakeCertReq(&csr->req.c, buf, buf_len, NULL,
+			     csr->pubkey->eckey);
+	if (err <= 0) {
+		LOG_WOLF_ERROR_FUNC(wc_MakeCertReq, err);
+		goto fail;
+	}
+	len = err;
+
+	err = wc_SignCert(len, csr->req.c.sigType, buf, buf_len, NULL,
+			  key->eckey, key->rng);
+	if (err <= 0) {
+		LOG_WOLF_ERROR_FUNC(wc_SignCert, err);
+		goto fail;
+	}
+	len = err;
+
+	ret = wpabuf_alloc_copy(buf, len);
+	if (!ret) {
+		LOG_WOLF_ERROR_FUNC_NULL(wpabuf_alloc_copy);
+		goto fail;
+	}
+
+fail:
+	os_free(buf);
+	return ret;
+}
+
+
+struct crypto_csr * crypto_csr_verify(const struct wpabuf *req)
+{
+	struct crypto_csr *csr = NULL;
+	int err;
+
+	if (!req) {
+		LOG_INVALID_PARAMETERS();
+		return NULL;
+	}
+
+	csr = crypto_csr_init();
+	if (!csr) {
+		LOG_WOLF_ERROR_FUNC_NULL(crypto_csr_init);
+		goto fail;
+	}
+
+	crypto_csr_init_type(csr, cert_type_decoded_cert,
+			     wpabuf_head(req), wpabuf_len(req));
+	err = wc_ParseCert(&csr->req.dc, CERTREQ_TYPE, VERIFY, NULL);
+	if (err != 0) {
+		LOG_WOLF_ERROR_FUNC(wc_ParseCert, err);
+		goto fail;
+	}
+
+	return csr;
+fail:
+	crypto_csr_deinit(csr);
+	return NULL;
+}
+
+/* END Certificate Signing Request (CSR) APIs */
+
+#endif /* CONFIG_DPP */
+
 
 void crypto_unload(void)
 {
diff --git a/src/crypto/sha256.c b/src/crypto/sha256.c
index 1ad1068..1e8a122 100644
--- a/src/crypto/sha256.c
+++ b/src/crypto/sha256.c
@@ -28,11 +28,11 @@
 {
 	unsigned char k_pad[64]; /* padding - key XORd with ipad/opad */
 	unsigned char tk[32];
-	const u8 *_addr[11];
-	size_t _len[11], i;
+	const u8 *_addr[HMAC_VECTOR_MAX_ELEM + 1];
+	size_t _len[HMAC_VECTOR_MAX_ELEM + 1], i;
 	int ret;
 
-	if (num_elem > 10) {
+	if (num_elem > HMAC_VECTOR_MAX_ELEM) {
 		/*
 		 * Fixed limit on the number of fragments to avoid having to
 		 * allocate memory (which could fail).
diff --git a/src/crypto/sha384.c b/src/crypto/sha384.c
index fd84b82..be07e9c 100644
--- a/src/crypto/sha384.c
+++ b/src/crypto/sha384.c
@@ -28,10 +28,10 @@
 {
 	unsigned char k_pad[128]; /* padding - key XORd with ipad/opad */
 	unsigned char tk[48];
-	const u8 *_addr[11];
-	size_t _len[11], i;
+	const u8 *_addr[HMAC_VECTOR_MAX_ELEM + 1];
+	size_t _len[HMAC_VECTOR_MAX_ELEM + 1], i;
 
-	if (num_elem > 10) {
+	if (num_elem > HMAC_VECTOR_MAX_ELEM) {
 		/*
 		 * Fixed limit on the number of fragments to avoid having to
 		 * allocate memory (which could fail).
diff --git a/src/crypto/sha512.c b/src/crypto/sha512.c
index f60a576..73b54c7 100644
--- a/src/crypto/sha512.c
+++ b/src/crypto/sha512.c
@@ -28,10 +28,10 @@
 {
 	unsigned char k_pad[128]; /* padding - key XORd with ipad/opad */
 	unsigned char tk[64];
-	const u8 *_addr[11];
-	size_t _len[11], i;
+	const u8 *_addr[HMAC_VECTOR_MAX_ELEM + 1];
+	size_t _len[HMAC_VECTOR_MAX_ELEM + 1], i;
 
-	if (num_elem > 10) {
+	if (num_elem > HMAC_VECTOR_MAX_ELEM) {
 		/*
 		 * Fixed limit on the number of fragments to avoid having to
 		 * allocate memory (which could fail).
diff --git a/src/crypto/tls.h b/src/crypto/tls.h
index 82276c5..f43aefe 100644
--- a/src/crypto/tls.h
+++ b/src/crypto/tls.h
@@ -80,9 +80,15 @@
 };
 
 struct tls_config {
+#ifndef CONFIG_OPENSC_ENGINE_PATH
 	const char *opensc_engine_path;
+#endif /* CONFIG_OPENSC_ENGINE_PATH */
+#ifndef CONFIG_PKCS11_ENGINE_PATH
 	const char *pkcs11_engine_path;
+#endif /* CONFIG_PKCS11_ENGINE_PATH */
+#ifndef CONFIG_PKCS11_MODULE_PATH
 	const char *pkcs11_module_path;
+#endif /* CONFIG_PKCS11_MODULE_PATH */
 	int fips_mode;
 	int cert_in_cb;
 	const char *openssl_ciphers;
diff --git a/src/crypto/tls_openssl.c b/src/crypto/tls_openssl.c
index b378356..103c333 100644
--- a/src/crypto/tls_openssl.c
+++ b/src/crypto/tls_openssl.c
@@ -1029,6 +1029,26 @@
 	SSL_CTX *ssl;
 	struct tls_context *context;
 	const char *ciphers;
+#ifndef OPENSSL_NO_ENGINE
+#ifdef CONFIG_OPENSC_ENGINE_PATH
+	char const * const opensc_engine_path = CONFIG_OPENSC_ENGINE_PATH;
+#else /* CONFIG_OPENSC_ENGINE_PATH */
+	char const * const opensc_engine_path =
+		conf ? conf->opensc_engine_path : NULL;
+#endif /* CONFIG_OPENSC_ENGINE_PATH */
+#ifdef CONFIG_PKCS11_ENGINE_PATH
+	char const * const pkcs11_engine_path = CONFIG_PKCS11_ENGINE_PATH;
+#else /* CONFIG_PKCS11_ENGINE_PATH */
+	char const * const pkcs11_engine_path =
+		conf ? conf->pkcs11_engine_path : NULL;
+#endif /* CONFIG_PKCS11_ENGINE_PATH */
+#ifdef CONFIG_PKCS11_MODULE_PATH
+	char const * const pkcs11_module_path = CONFIG_PKCS11_MODULE_PATH;
+#else /* CONFIG_PKCS11_MODULE_PATH */
+	char const * const pkcs11_module_path =
+		conf ? conf->pkcs11_module_path : NULL;
+#endif /* CONFIG_PKCS11_MODULE_PATH */
+#endif /* OPENSSL_NO_ENGINE */
 
 	if (tls_openssl_ref_count == 0) {
 		void openssl_load_legacy_provider(void);
@@ -1171,12 +1191,10 @@
 	wpa_printf(MSG_DEBUG, "ENGINE: Loading builtin engines");
 	ENGINE_load_builtin_engines();
 
-	if (conf &&
-	    (conf->opensc_engine_path || conf->pkcs11_engine_path ||
-	     conf->pkcs11_module_path)) {
-		if (tls_engine_load_dynamic_opensc(conf->opensc_engine_path) ||
-		    tls_engine_load_dynamic_pkcs11(conf->pkcs11_engine_path,
-						   conf->pkcs11_module_path)) {
+	if (opensc_engine_path || pkcs11_engine_path || pkcs11_module_path) {
+		if (tls_engine_load_dynamic_opensc(opensc_engine_path) ||
+		    tls_engine_load_dynamic_pkcs11(pkcs11_engine_path,
+						   pkcs11_module_path)) {
 			tls_deinit(data);
 			return NULL;
 		}
diff --git a/src/crypto/tls_wolfssl.c b/src/crypto/tls_wolfssl.c
index b4f1bbe..0b2947d 100644
--- a/src/crypto/tls_wolfssl.c
+++ b/src/crypto/tls_wolfssl.c
@@ -284,6 +284,7 @@
 		ciphers = conf->openssl_ciphers;
 	else
 		ciphers = "ALL";
+	wpa_printf(MSG_DEBUG, "wolfSSL: cipher suites: %s", ciphers);
 	if (wolfSSL_CTX_set_cipher_list(ssl_ctx, ciphers) != 1) {
 		wpa_printf(MSG_ERROR,
 			   "wolfSSL: Failed to set cipher string '%s'",
@@ -1323,6 +1324,8 @@
 		return -1;
 	}
 
+	wpa_printf(MSG_DEBUG, "wolfSSL: cipher suites: %s",
+		   params->openssl_ciphers ? params->openssl_ciphers : "N/A");
 	if (params->openssl_ciphers &&
 	    wolfSSL_set_cipher_list(conn->ssl, params->openssl_ciphers) != 1) {
 		wpa_printf(MSG_INFO,
@@ -1553,6 +1556,8 @@
 		return -1;
 	}
 
+	wpa_printf(MSG_DEBUG, "wolfSSL: cipher suites: %s",
+		   params->openssl_ciphers ? params->openssl_ciphers : "N/A");
 	if (params->openssl_ciphers &&
 	    wolfSSL_CTX_set_cipher_list(tls_ctx,
 					params->openssl_ciphers) != 1) {
@@ -1665,20 +1670,31 @@
 		wpa_printf(MSG_DEBUG, "SSL: wolfSSL_connect: %d", res);
 	}
 
-	if (res != 1) {
+	if (res != WOLFSSL_SUCCESS) {
 		int err = wolfSSL_get_error(conn->ssl, res);
 
-		if (err == SSL_ERROR_WANT_READ) {
+		if (err == WOLFSSL_ERROR_NONE) {
 			wpa_printf(MSG_DEBUG,
-				   "SSL: wolfSSL_connect - want more data");
-		} else if (err == SSL_ERROR_WANT_WRITE) {
+				   "SSL: %s - WOLFSSL_ERROR_NONE (%d)",
+				   server ? "wolfSSL_accept" :
+				   "wolfSSL_connect", res);
+		} else if (err == WOLFSSL_ERROR_WANT_READ) {
 			wpa_printf(MSG_DEBUG,
-				   "SSL: wolfSSL_connect - want to write");
+				   "SSL: %s - want more data",
+				   server ? "wolfSSL_accept" :
+				   "wolfSSL_connect");
+		} else if (err == WOLFSSL_ERROR_WANT_WRITE) {
+			wpa_printf(MSG_DEBUG,
+				   "SSL: %s - want to write",
+				   server ? "wolfSSL_accept" :
+				   "wolfSSL_connect");
 		} else {
 			char msg[80];
 
 			wpa_printf(MSG_DEBUG,
-				   "SSL: wolfSSL_connect - failed %s",
+				   "SSL: %s - failed %s",
+				   server ? "wolfSSL_accept" :
+				   "wolfSSL_connect",
 				   wolfSSL_ERR_error_string(err, msg));
 			conn->failed++;
 		}
diff --git a/src/drivers/driver.h b/src/drivers/driver.h
index 3c4de7a..7ae7d90 100644
--- a/src/drivers/driver.h
+++ b/src/drivers/driver.h
@@ -334,6 +334,8 @@
  * @flags: information flags about the BSS/IBSS (WPA_SCAN_*)
  * @bssid: BSSID
  * @freq: frequency of the channel in MHz (e.g., 2412 = channel 1)
+ * @max_cw: the max channel width of the connection (calculated during scan
+ * result processing)
  * @beacon_int: beacon interval in TUs (host byte order)
  * @caps: capability information field in host byte order
  * @qual: signal quality
@@ -370,6 +372,7 @@
 	unsigned int flags;
 	u8 bssid[ETH_ALEN];
 	int freq;
+	enum chan_width max_cw;
 	u16 beacon_int;
 	u16 caps;
 	int qual;
@@ -685,6 +688,13 @@
 	 */
 	unsigned int non_coloc_6ghz:1;
 
+	/**
+	 * min_probe_req_content - Minimize probe request content to only have
+	 * minimal requirement elements, e.g., supported rates etc., and no
+	 * additional elements other then those provided by user space.
+	 */
+	unsigned int min_probe_req_content:1;
+
 	/*
 	 * NOTE: Whenever adding new parameters here, please make sure
 	 * wpa_scan_clone_params() and wpa_scan_free_params() get updated with
@@ -841,6 +851,11 @@
 	 * eht_enabled - Whether EHT is enabled
 	 */
 	bool eht_enabled;
+
+	/**
+	 * link_id: If >=0 indicates the link of the AP MLD to configure
+	 */
+	int link_id;
 };
 
 /**
@@ -1117,6 +1132,23 @@
 	const u8 *psk;
 
 	/**
+	 * sae_password - Password for SAE authentication
+	 *
+	 * This value is made available only for WPA3-Personal (SAE) and only
+	 * for drivers that set WPA_DRIVER_FLAGS2_SAE_OFFLOAD.
+	 */
+	const char *sae_password;
+
+	/**
+	 * sae_password_id - Password Identifier for SAE authentication
+	 *
+	 * This value is made available only for WPA3-Personal (SAE) and only
+	 * for drivers that set WPA_DRIVER_FLAGS2_SAE_OFFLOAD. If %NULL, SAE
+	 * password identifier is not used.
+	 */
+	const char *sae_password_id;
+
+	/**
 	 * drop_unencrypted - Enable/disable unencrypted frame filtering
 	 *
 	 * Configure the driver to drop all non-EAPOL frames (both receive and
@@ -1777,6 +1809,31 @@
 	 * channels whenever performing operations like ACS and DFS.
 	 */
 	int *allowed_freqs;
+
+	/*
+	 * mld_ap - Whether operating as an AP MLD
+	 */
+	bool mld_ap;
+
+	/**
+	 * mld_link_id - Link id for MLD BSS's
+	 */
+	u8 mld_link_id;
+
+	/**
+	 * psk - PSK passed to the driver for 4-way handshake offload
+	 */
+	u8 psk[PMK_LEN];
+
+	/**
+	 * psk_len - PSK length in bytes (0 = not set)
+	 */
+	size_t psk_len;
+
+	/**
+	 * sae_password - SAE password for SAE offload
+	 */
+	const char *sae_password;
 };
 
 struct wpa_driver_mesh_bss_params {
@@ -2233,7 +2290,7 @@
 /** Driver handles SA Query procedures in AP mode */
 #define WPA_DRIVER_FLAGS2_SA_QUERY_OFFLOAD_AP	0x0000000000000200ULL
 /** Driver supports background radar/CAC detection */
-#define WPA_DRIVER_RADAR_BACKGROUND		0x0000000000000400ULL
+#define WPA_DRIVER_FLAGS2_RADAR_BACKGROUND	0x0000000000000400ULL
 /** Driver supports secure LTF in STA mode */
 #define WPA_DRIVER_FLAGS2_SEC_LTF_STA		0x0000000000000800ULL
 /** Driver supports secure RTT measurement exchange in STA mode */
@@ -2245,6 +2302,18 @@
 #define WPA_DRIVER_FLAGS2_PROT_RANGE_NEG_STA	0x0000000000002000ULL
 /** Driver supports MLO in station/AP mode */
 #define WPA_DRIVER_FLAGS2_MLO			0x0000000000004000ULL
+/** Driver supports minimal scan request probe content  */
+#define WPA_DRIVER_FLAGS2_SCAN_MIN_PREQ         0x0000000000008000ULL
+/** Driver supports SAE authentication offload in STA mode */
+#define WPA_DRIVER_FLAGS2_SAE_OFFLOAD_STA	0x0000000000010000ULL
+/** Driver support AP_PSK authentication offload */
+#define WPA_DRIVER_FLAGS2_4WAY_HANDSHAKE_AP_PSK	0x0000000000020000ULL
+/** Driver supports OWE STA offload */
+#define WPA_DRIVER_FLAGS2_OWE_OFFLOAD_STA	0x0000000000040000ULL
+/** Driver supports OWE AP offload */
+#define WPA_DRIVER_FLAGS2_OWE_OFFLOAD_AP	0x0000000000080000ULL
+/** Driver support AP SAE authentication offload */
+#define WPA_DRIVER_FLAGS2_SAE_OFFLOAD_AP	0x0000000000100000ULL
 	u64 flags2;
 
 #define FULL_AP_CLIENT_STATE_SUPP(drv_flags) \
@@ -3268,12 +3337,13 @@
 	 * @no_encrypt: Do not encrypt frame even if appropriate key exists
 	 *	(used only for testing purposes)
 	 * @wait: Time to wait off-channel for a response (in ms), or zero
+	 * @link_id: Link ID to use for TX, or -1 if not set
 	 * Returns: 0 on success, -1 on failure
 	 */
 	int (*send_mlme)(void *priv, const u8 *data, size_t data_len,
 			 int noack, unsigned int freq, const u16 *csa_offs,
 			 size_t csa_offs_len, int no_encrypt,
-			 unsigned int wait);
+			 unsigned int wait, int link_id);
 
 	/**
 	 * update_ft_ies - Update FT (IEEE 802.11r) IEs
@@ -3479,6 +3549,7 @@
 	 * @priv: Private driver interface data
 	 * @addr: MAC address of the station or %NULL for group keys
 	 * @idx: Key index
+	 * @link_id: Link ID for a group key, or -1 if not set
 	 * @seq: Buffer for returning the latest used TSC/packet number
 	 * Returns: 0 on success, -1 on failure
 	 *
@@ -3488,7 +3559,7 @@
 	 * unicast keys (i.e., addr != %NULL).
 	 */
 	int (*get_seqnum)(const char *ifname, void *priv, const u8 *addr,
-			  int idx, u8 *seq);
+			  int idx, int link_id, u8 *seq);
 
 	/**
 	 * flush - Flush all association stations (AP only)
@@ -3535,6 +3606,7 @@
 	 * @buf: Frame payload starting from IEEE 802.1X header
 	 * @len: Frame payload length
 	 * @no_encrypt: Do not encrypt frame
+	 * @link_id: Link ID to use for TX, or -1 if not set
 	 *
 	 * Returns 0 on success, else an error
 	 *
@@ -3552,7 +3624,7 @@
 	 */
 	int (*tx_control_port)(void *priv, const u8 *dest,
 			       u16 proto, const u8 *buf, size_t len,
-			       int no_encrypt);
+			       int no_encrypt, int link_id);
 
 	/**
 	 * hapd_send_eapol - Send an EAPOL packet (AP only)
@@ -3563,26 +3635,28 @@
 	 * @encrypt: Whether the frame should be encrypted
 	 * @own_addr: Source MAC address
 	 * @flags: WPA_STA_* flags for the destination station
+	 * @link_id: Link ID to use for TX, or -1 if not set
 	 *
 	 * Returns: 0 on success, -1 on failure
 	 */
 	int (*hapd_send_eapol)(void *priv, const u8 *addr, const u8 *data,
 			       size_t data_len, int encrypt,
-			       const u8 *own_addr, u32 flags);
+			       const u8 *own_addr, u32 flags, int link_id);
 
 	/**
 	 * sta_deauth - Deauthenticate a station (AP only)
 	 * @priv: Private driver interface data
 	 * @own_addr: Source address and BSSID for the Deauthentication frame
 	 * @addr: MAC address of the station to deauthenticate
-	 * @reason: Reason code for the Deauthentiation frame
+	 * @reason: Reason code for the Deauthentication frame
+	 * @link_id: Link ID to use for Deauthentication frame, or -1 if not set
 	 * Returns: 0 on success, -1 on failure
 	 *
 	 * This function requests a specific station to be deauthenticated and
 	 * a Deauthentication frame to be sent to it.
 	 */
 	int (*sta_deauth)(void *priv, const u8 *own_addr, const u8 *addr,
-			  u16 reason);
+			  u16 reason, int link_id);
 
 	/**
 	 * sta_disassoc - Disassociate a station (AP only)
@@ -3731,9 +3805,10 @@
 	 * @cw_min: cwMin
 	 * @cw_max: cwMax
 	 * @burst_time: Maximum length for bursting in 0.1 msec units
+	 * @link_id: Link ID to use, or -1 for non MLD.
 	 */
 	int (*set_tx_queue_params)(void *priv, int queue, int aifs, int cw_min,
-				   int cw_max, int burst_time);
+				   int cw_max, int burst_time, int link_id);
 
 	/**
 	 * if_add - Add a virtual interface
@@ -3776,6 +3851,7 @@
 	 * @ifname: Interface (main or virtual BSS or VLAN)
 	 * @addr: MAC address of the associated station
 	 * @vlan_id: VLAN ID
+	 * @link_id: The link ID or -1 for non-MLO
 	 * Returns: 0 on success, -1 on failure
 	 *
 	 * This function is used to bind a station to a specific virtual
@@ -3785,7 +3861,7 @@
 	 * domains to be used with a single BSS.
 	 */
 	int (*set_sta_vlan)(void *priv, const u8 *addr, const char *ifname,
-			    int vlan_id);
+			    int vlan_id, int link_id);
 
 	/**
 	 * commit - Optional commit changes handler (AP only)
@@ -4091,6 +4167,8 @@
 	 * @initiator: Is the current end the TDLS link initiator
 	 * @buf: TDLS IEs to add to the message
 	 * @len: Length of buf in octets
+	 * @link_id: If >= 0 indicates the link of the AP MLD to specify the
+	 * operating channel on which to send a TDLS Discovery Response frame.
 	 * Returns: 0 on success, negative (<0) on failure
 	 *
 	 * This optional function can be used to send packet to driver which is
@@ -4098,7 +4176,8 @@
 	 */
 	int (*send_tdls_mgmt)(void *priv, const u8 *dst, u8 action_code,
 			      u8 dialog_token, u16 status_code, u32 peer_capab,
-			      int initiator, const u8 *buf, size_t len);
+			      int initiator, const u8 *buf, size_t len,
+			      int link_id);
 
 	/**
 	 * tdls_oper - Ask the driver to perform high-level TDLS operations
@@ -4865,6 +4944,17 @@
 			     unsigned int *ext_capab_len);
 
 	/**
+	 * get_mld_capab - Get MLD capabilities for the specified interface
+	 * @priv: Private driver interface data
+	 * @type: Interface type for which to get MLD capabilities
+	 * @eml_capa: EML capabilities
+	 * @mld_capa_and_ops: MLD Capabilities and Operations
+	 * Returns: 0 on success or -1 on failure
+	 */
+	int (*get_mld_capab)(void *priv, enum wpa_driver_if_type type,
+			     u16 *eml_capa, u16 *mld_capa_and_ops);
+
+	/**
 	 * p2p_lo_start - Start offloading P2P listen to device
 	 * @priv: Private driver interface data
 	 * @freq: Listening frequency (MHz) for P2P listen
@@ -5930,6 +6020,17 @@
 		 * fils_pmkid - PMKID used or generated in FILS authentication
 		 */
 		const u8 *fils_pmkid;
+
+		/**
+		 * link_addr - Link address of the STA
+		 */
+		const u8 *link_addr;
+
+		/**
+		 * assoc_link_id - Association link id of the MLO association or
+		 *	-1 if MLO is not used
+		 */
+		int assoc_link_id;
 	} assoc_info;
 
 	/**
@@ -6165,6 +6266,7 @@
 		const u8 *data;
 		size_t data_len;
 		int ack;
+		int link_id;
 	} tx_status;
 
 	/**
@@ -6382,6 +6484,7 @@
 	 * @data: Data starting with IEEE 802.1X header (!)
 	 * @data_len: Length of data
 	 * @ack: Indicates ack or lost frame
+	 * @link_id: MLD link id used to transmit the frame or -1 for non MLO
 	 *
 	 * This corresponds to hapd_send_eapol if the frame sent
 	 * there isn't just reported as EVENT_TX_STATUS.
@@ -6391,6 +6494,7 @@
 		const u8 *data;
 		int data_len;
 		int ack;
+		int link_id;
 	} eapol_tx_status;
 
 	/**
@@ -6514,6 +6618,7 @@
 		u8 vht_seg1_center_ch;
 		u16 ch_width;
 		enum hostapd_hw_mode hw_mode;
+		u16 puncture_bitmap;
 	} acs_selected_channels;
 
 	/**
@@ -6575,6 +6680,8 @@
 		const u8 *peer;
 		const u8 *ie;
 		size_t ie_len;
+		int assoc_link_id;
+		const u8 *link_addr;
 	} update_dh;
 
 	/**
@@ -6598,10 +6705,19 @@
 
 	/**
 	 * struct port_authorized - Data for EVENT_PORT_AUTHORIZED
+	 * @td_bitmap: For STA mode, transition disable bitmap, if received in
+	 *	EAPOL-Key msg 3/4
+	 * @td_bitmap_len: For STA mode, length of td_bitmap
+	 * @sta_addr: For AP mode, the MAC address of the associated STA
+	 *
+	 * This event is used to indicate that the port is authorized and
+	 * open for normal data in STA and AP modes when 4-way handshake is
+	 * offloaded to the driver.
 	 */
 	struct port_authorized {
 		const u8 *td_bitmap;
 		size_t td_bitmap_len;
+		const u8 *sta_addr;
 	} port_authorized;
 
 	/**
@@ -6645,15 +6761,21 @@
  * event indication for some of the common events.
  */
 
-static inline void drv_event_assoc(void *ctx, const u8 *addr, const u8 *ie,
-				   size_t ielen, int reassoc)
+static inline void drv_event_assoc(void *ctx, const u8 *addr, const u8 *req_ies,
+				   size_t req_ielen, const u8 *resp_ies,
+				   size_t resp_ielen, const u8 *link_addr,
+				   int assoc_link_id, int reassoc)
 {
 	union wpa_event_data event;
 	os_memset(&event, 0, sizeof(event));
 	event.assoc_info.reassoc = reassoc;
-	event.assoc_info.req_ies = ie;
-	event.assoc_info.req_ies_len = ielen;
+	event.assoc_info.req_ies = req_ies;
+	event.assoc_info.req_ies_len = req_ielen;
+	event.assoc_info.resp_ies = resp_ies;
+	event.assoc_info.resp_ies_len = resp_ielen;
 	event.assoc_info.addr = addr;
+	event.assoc_info.link_addr = link_addr;
+	event.assoc_info.assoc_link_id = assoc_link_id;
 	wpa_supplicant_event(ctx, EVENT_ASSOC, &event);
 }
 
diff --git a/src/drivers/driver_atheros.c b/src/drivers/driver_atheros.c
index cb66dfa..59f65b8 100644
--- a/src/drivers/driver_atheros.c
+++ b/src/drivers/driver_atheros.c
@@ -81,7 +81,7 @@
 };
 
 static int atheros_sta_deauth(void *priv, const u8 *own_addr, const u8 *addr,
-			      u16 reason_code);
+			      u16 reason_code, int link_id);
 static int atheros_set_privacy(void *priv, int enabled);
 
 static const char * athr_get_ioctl_name(int op)
@@ -586,7 +586,7 @@
 
 static int
 atheros_get_seqnum(const char *ifname, void *priv, const u8 *addr, int idx,
-		   u8 *seq)
+		   int link_id, u8 *seq)
 {
 	struct atheros_driver_data *drv = priv;
 	struct ieee80211req_key wk;
@@ -637,7 +637,7 @@
 	u8 allsta[IEEE80211_ADDR_LEN];
 	os_memset(allsta, 0xff, IEEE80211_ADDR_LEN);
 	return atheros_sta_deauth(priv, NULL, allsta,
-				  IEEE80211_REASON_AUTH_LEAVE);
+				  IEEE80211_REASON_AUTH_LEAVE, -1);
 }
 
 
@@ -756,7 +756,7 @@
 
 static int
 atheros_sta_deauth(void *priv, const u8 *own_addr, const u8 *addr,
-		   u16 reason_code)
+		   u16 reason_code, int link_id)
 {
 	struct atheros_driver_data *drv = priv;
 	struct ieee80211req_mlme mlme;
@@ -913,14 +913,16 @@
 			break;
 		ielen = len - (IEEE80211_HDRLEN + sizeof(mgmt->u.assoc_req));
 		iebuf = mgmt->u.assoc_req.variable;
-		drv_event_assoc(drv->hapd, mgmt->sa, iebuf, ielen, 0);
+		drv_event_assoc(drv->hapd, mgmt->sa, iebuf, ielen, NULL, 0,
+				NULL, -1, 0);
 		break;
 	case WLAN_FC_STYPE_REASSOC_REQ:
 		if (len < IEEE80211_HDRLEN + sizeof(mgmt->u.reassoc_req))
 			break;
 		ielen = len - (IEEE80211_HDRLEN + sizeof(mgmt->u.reassoc_req));
 		iebuf = mgmt->u.reassoc_req.variable;
-		drv_event_assoc(drv->hapd, mgmt->sa, iebuf, ielen, 1);
+		drv_event_assoc(drv->hapd, mgmt->sa, iebuf, ielen, NULL, 0,
+				NULL, -1, 1);
 		break;
 	case WLAN_FC_STYPE_AUTH:
 		if (len < IEEE80211_HDRLEN + sizeof(mgmt->u.auth))
@@ -1222,7 +1224,7 @@
 		ielen += 2;
 
 no_ie:
-	drv_event_assoc(hapd, addr, iebuf, ielen, 0);
+	drv_event_assoc(hapd, addr, iebuf, ielen, NULL, 0, NULL, -1, 0);
 
 	if (os_memcmp(addr, drv->acct_mac, ETH_ALEN) == 0) {
 		/* Cached accounting data is not valid anymore. */
@@ -1644,7 +1646,7 @@
 
 static int
 atheros_send_eapol(void *priv, const u8 *addr, const u8 *data, size_t data_len,
-		   int encrypt, const u8 *own_addr, u32 flags)
+		   int encrypt, const u8 *own_addr, u32 flags, int link_id)
 {
 	struct atheros_driver_data *drv = priv;
 	unsigned char buf[3000];
@@ -1964,7 +1966,7 @@
 static int atheros_send_mgmt(void *priv, const u8 *frm, size_t data_len,
 			     int noack, unsigned int freq,
 			     const u16 *csa_offs, size_t csa_offs_len,
-			     int no_encrypt, unsigned int wait)
+			     int no_encrypt, unsigned int wait, int link_id)
 {
 	struct atheros_driver_data *drv = priv;
 	u8 buf[1510];
diff --git a/src/drivers/driver_bsd.c b/src/drivers/driver_bsd.c
index 00d970a..850637f 100644
--- a/src/drivers/driver_bsd.c
+++ b/src/drivers/driver_bsd.c
@@ -553,12 +553,12 @@
 		ielen += 2;
 
 no_ie:
-	drv_event_assoc(ctx, addr, iebuf, ielen, 0);
+	drv_event_assoc(ctx, addr, iebuf, ielen, NULL, 0, NULL, -1, 0);
 }
 
 static int
 bsd_send_eapol(void *priv, const u8 *addr, const u8 *data, size_t data_len,
-	       int encrypt, const u8 *own_addr, u32 flags)
+	       int encrypt, const u8 *own_addr, u32 flags, int link_id)
 {
 	struct bsd_driver_data *drv = priv;
 
@@ -882,7 +882,7 @@
 #undef WPA_OUI_TYPE
 
 static int bsd_sta_deauth(void *priv, const u8 *own_addr, const u8 *addr,
-			  u16 reason_code);
+			  u16 reason_code, int link_id);
 
 static const char *
 ether_sprintf(const u8 *addr)
@@ -906,7 +906,7 @@
 
 static int
 bsd_get_seqnum(const char *ifname, void *priv, const u8 *addr, int idx,
-	       u8 *seq)
+	       int link_id, u8 *seq)
 {
 	struct ieee80211req_key wk;
 
@@ -951,7 +951,8 @@
 	u8 allsta[IEEE80211_ADDR_LEN];
 
 	memset(allsta, 0xff, IEEE80211_ADDR_LEN);
-	return bsd_sta_deauth(priv, NULL, allsta, IEEE80211_REASON_AUTH_LEAVE);
+	return bsd_sta_deauth(priv, NULL, allsta, IEEE80211_REASON_AUTH_LEAVE,
+			      -1);
 }
 
 
@@ -974,7 +975,8 @@
 }
 
 static int
-bsd_sta_deauth(void *priv, const u8 *own_addr, const u8 *addr, u16 reason_code)
+bsd_sta_deauth(void *priv, const u8 *own_addr, const u8 *addr, u16 reason_code,
+	       int link_id)
 {
 	return bsd_send_mlme_param(priv, IEEE80211_MLME_DEAUTH, reason_code,
 				   addr);
diff --git a/src/drivers/driver_common.c b/src/drivers/driver_common.c
index f3625e8..9bc5a73 100644
--- a/src/drivers/driver_common.c
+++ b/src/drivers/driver_common.c
@@ -358,6 +358,21 @@
 	switch (flag2) {
 	DF2S(CONTROL_PORT_RX);
 	DF2S(CONTROL_PORT_TX_STATUS);
+	DF2S(SEC_LTF_AP);
+	DF2S(SEC_RTT_AP);
+	DF2S(PROT_RANGE_NEG_AP);
+	DF2S(BEACON_RATE_HE);
+	DF2S(BEACON_PROTECTION_CLIENT);
+	DF2S(OCV);
+	DF2S(AP_SME);
+	DF2S(SA_QUERY_OFFLOAD_AP);
+	DF2S(RADAR_BACKGROUND);
+	DF2S(SEC_LTF_STA);
+	DF2S(SEC_RTT_STA);
+	DF2S(PROT_RANGE_NEG_STA);
+	DF2S(MLO);
+	DF2S(SCAN_MIN_PREQ);
+	DF2S(SAE_OFFLOAD_STA);
 	}
 	return "UNKNOWN";
 #undef DF2S
diff --git a/src/drivers/driver_hostap.c b/src/drivers/driver_hostap.c
index b9c42e4..d3520aa 100644
--- a/src/drivers/driver_hostap.c
+++ b/src/drivers/driver_hostap.c
@@ -264,7 +264,7 @@
 static int hostap_send_mlme(void *priv, const u8 *msg, size_t len, int noack,
 			    unsigned int freq,
 			    const u16 *csa_offs, size_t csa_offs_len,
-			    int no_encrypt, unsigned int wait)
+			    int no_encrypt, unsigned int wait, int link_id)
 {
 	struct hostap_driver_data *drv = priv;
 	struct ieee80211_hdr *hdr = (struct ieee80211_hdr *) msg;
@@ -281,7 +281,7 @@
 
 static int hostap_send_eapol(void *priv, const u8 *addr, const u8 *data,
 			     size_t data_len, int encrypt, const u8 *own_addr,
-			     u32 flags)
+			     u32 flags, int link_id)
 {
 	struct hostap_driver_data *drv = priv;
 	struct ieee80211_hdr *hdr;
@@ -313,7 +313,7 @@
 	pos += 2;
 	memcpy(pos, data, data_len);
 
-	res = hostap_send_mlme(drv, (u8 *) hdr, len, 0, 0, NULL, 0, 0, 0);
+	res = hostap_send_mlme(drv, (u8 *) hdr, len, 0, 0, NULL, 0, 0, 0, -1);
 	if (res < 0) {
 		wpa_printf(MSG_ERROR, "hostap_send_eapol - packet len: %lu - "
 			   "failed: %d (%s)",
@@ -459,7 +459,7 @@
 
 
 static int hostap_get_seqnum(const char *ifname, void *priv, const u8 *addr,
-			     int idx, u8 *seq)
+			     int idx, int link_id, u8 *seq)
 {
 	struct hostap_driver_data *drv = priv;
 	struct prism2_hostapd_param *param;
@@ -1032,7 +1032,7 @@
 
 
 static int hostap_sta_deauth(void *priv, const u8 *own_addr, const u8 *addr,
-			     u16 reason)
+			     u16 reason, int link_id)
 {
 	struct hostap_driver_data *drv = priv;
 	struct ieee80211_mgmt mgmt;
@@ -1055,7 +1055,7 @@
 	memcpy(mgmt.bssid, own_addr, ETH_ALEN);
 	mgmt.u.deauth.reason_code = host_to_le16(reason);
 	return hostap_send_mlme(drv, (u8 *) &mgmt, IEEE80211_HDRLEN +
-				sizeof(mgmt.u.deauth), 0, 0, NULL, 0, 0, 0);
+				sizeof(mgmt.u.deauth), 0, 0, NULL, 0, 0, 0, -1);
 }
 
 
@@ -1093,7 +1093,8 @@
 	memcpy(mgmt.bssid, own_addr, ETH_ALEN);
 	mgmt.u.disassoc.reason_code = host_to_le16(reason);
 	return  hostap_send_mlme(drv, (u8 *) &mgmt, IEEE80211_HDRLEN +
-				 sizeof(mgmt.u.disassoc), 0, 0, NULL, 0, 0, 0);
+				 sizeof(mgmt.u.disassoc), 0, 0, NULL, 0, 0, 0,
+				 -1);
 }
 
 
@@ -1173,7 +1174,8 @@
 	os_memcpy(hdr.IEEE80211_BSSID_FROMDS, own_addr, ETH_ALEN);
 	os_memcpy(hdr.IEEE80211_SA_FROMDS, own_addr, ETH_ALEN);
 
-	hostap_send_mlme(priv, (u8 *)&hdr, sizeof(hdr), 0, 0, NULL, 0, 0, 0);
+	hostap_send_mlme(priv, (u8 *)&hdr, sizeof(hdr), 0, 0, NULL, 0, 0, 0,
+			 -1);
 }
 
 
diff --git a/src/drivers/driver_macsec_linux.c b/src/drivers/driver_macsec_linux.c
index c79e873..c867154 100644
--- a/src/drivers/driver_macsec_linux.c
+++ b/src/drivers/driver_macsec_linux.c
@@ -1640,7 +1640,7 @@
 
 static int macsec_drv_send_eapol(void *priv, const u8 *addr,
 				 const u8 *data, size_t data_len, int encrypt,
-				 const u8 *own_addr, u32 flags)
+				 const u8 *own_addr, u32 flags, int link_id)
 {
 	struct macsec_drv_data *drv = priv;
 	struct ieee8023_hdr *hdr;
diff --git a/src/drivers/driver_macsec_qca.c b/src/drivers/driver_macsec_qca.c
index eccaf63..58c48c2 100644
--- a/src/drivers/driver_macsec_qca.c
+++ b/src/drivers/driver_macsec_qca.c
@@ -366,7 +366,7 @@
 
 static int macsec_qca_send_eapol(void *priv, const u8 *addr,
 				 const u8 *data, size_t data_len, int encrypt,
-				 const u8 *own_addr, u32 flags)
+				 const u8 *own_addr, u32 flags, int link_id)
 {
 	struct macsec_qca_data *drv = priv;
 	struct ieee8023_hdr *hdr;
diff --git a/src/drivers/driver_nl80211.c b/src/drivers/driver_nl80211.c
index 1acc43b..3b96d55 100644
--- a/src/drivers/driver_nl80211.c
+++ b/src/drivers/driver_nl80211.c
@@ -175,8 +175,6 @@
 				  const u16 *csa_offs, size_t csa_offs_len);
 static int wpa_driver_nl80211_probe_req_report(struct i802_bss *bss,
 					       int report);
-static int nl80211_put_freq_params(struct nl_msg *msg,
-				   const struct hostapd_freq_params *freq);
 
 #define IFIDX_ANY -1
 
@@ -3160,9 +3158,9 @@
 
 	os_free(drv->extended_capa);
 	os_free(drv->extended_capa_mask);
-	for (i = 0; i < drv->num_iface_ext_capa; i++) {
-		os_free(drv->iface_ext_capa[i].ext_capa);
-		os_free(drv->iface_ext_capa[i].ext_capa_mask);
+	for (i = 0; i < drv->num_iface_capa; i++) {
+		os_free(drv->iface_capa[i].ext_capa);
+		os_free(drv->iface_capa[i].ext_capa_mask);
 	}
 	os_free(drv->first_bss);
 #ifdef CONFIG_DRIVER_NL80211_QCA
@@ -3292,6 +3290,7 @@
 	__AKM(OWE, OWE);
 	__AKM(DPP, DPP);
 	__AKM(FT_IEEE8021X_SHA384, FT_802_1X_SHA384);
+	__AKM(IEEE8021X_SHA384, 802_1X_SHA384);
 #undef __AKM
 
 	return num_suites;
@@ -4233,13 +4232,60 @@
 }
 
 
+struct i802_link * nl80211_get_link(struct i802_bss *bss, s8 link_id)
+{
+	unsigned int i;
+
+	for (i = 0; i < bss->n_links; i++) {
+		if (bss->links[i].link_id != link_id)
+			continue;
+
+		return &bss->links[i];
+	}
+
+	return bss->flink;
+}
+
+
+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);
+
+	link->freq = freq;
+}
+
+
+static int nl80211_get_link_freq(struct i802_bss *bss, const u8 *addr,
+				 bool bss_freq_debug)
+{
+	size_t i;
+
+	for (i = 0; i < bss->n_links; i++) {
+		if (os_memcmp(bss->links[i].addr, addr, ETH_ALEN) == 0) {
+			wpa_printf(MSG_DEBUG,
+				   "nl80211: Use link freq=%d for address "
+				   MACSTR,
+				   bss->links[i].freq, MAC2STR(addr));
+			return bss->links[i].freq;
+		}
+	}
+
+	if (bss_freq_debug)
+		wpa_printf(MSG_DEBUG, "nl80211: Use bss->freq=%d",
+			   bss->flink->freq);
+
+	return bss->flink->freq;
+}
+
+
 static int wpa_driver_nl80211_send_mlme(struct i802_bss *bss, const u8 *data,
 					size_t data_len, int noack,
 					unsigned int freq, int no_cck,
 					int offchanok,
 					unsigned int wait_time,
 					const u16 *csa_offs,
-					size_t csa_offs_len, int no_encrypt)
+					size_t csa_offs_len, int no_encrypt,
+					int link_id)
 {
 	struct wpa_driver_nl80211_data *drv = bss->drv;
 	struct ieee80211_mgmt *mgmt;
@@ -4247,6 +4293,7 @@
 	u16 fc;
 	int use_cookie = 1;
 	int res;
+	struct i802_link *link = nl80211_get_link(bss, link_id);
 
 	mgmt = (struct ieee80211_mgmt *) data;
 	fc = le_to_host16(mgmt->frame_control);
@@ -4277,13 +4324,15 @@
 	}
 
 	if (drv->device_ap_sme && is_ap_interface(drv->nlmode)) {
-		if (freq == 0) {
-			wpa_printf(MSG_DEBUG, "nl80211: Use bss->freq=%d",
-				   bss->flink->freq);
-			freq = bss->flink->freq;
-		}
-		if ((int) freq == bss->flink->freq)
+		unsigned int link_freq = nl80211_get_link_freq(bss, mgmt->sa,
+							       !freq);
+
+		if (!freq)
+			freq = link_freq;
+
+		if (freq == link_freq)
 			wait_time = 0;
+
 		goto send_frame_cmd;
 	}
 
@@ -4344,20 +4393,21 @@
 	}
 	if (freq == 0) {
 		wpa_printf(MSG_DEBUG, "nl80211: send_mlme - Use bss->freq=%u",
-			   bss->flink->freq);
-		freq = bss->flink->freq;
+			   link->freq);
+		freq = link->freq;
 	}
 
 	if (drv->use_monitor && is_ap_interface(drv->nlmode)) {
 		wpa_printf(MSG_DEBUG,
 			   "nl80211: send_frame(freq=%u bss->freq=%u) -> send_monitor",
-			   freq, bss->flink->freq);
+			   freq, link->freq);
 		return nl80211_send_monitor(drv, data, data_len, encrypt,
 					    noack);
 	}
 
-	if (noack || WLAN_FC_GET_TYPE(fc) != WLAN_FC_TYPE_MGMT ||
-	    WLAN_FC_GET_STYPE(fc) != WLAN_FC_STYPE_ACTION)
+	if ((noack || WLAN_FC_GET_TYPE(fc) != WLAN_FC_TYPE_MGMT ||
+	     WLAN_FC_GET_STYPE(fc) != WLAN_FC_STYPE_ACTION) &&
+	    link_id == NL80211_DRV_LINK_ID_NA)
 		use_cookie = 0;
 send_frame_cmd:
 #ifdef CONFIG_TESTING_OPTIONS
@@ -4377,6 +4427,8 @@
 	res = nl80211_send_frame_cmd(bss, freq, wait_time, data, data_len,
 				     use_cookie, no_cck, noack, offchanok,
 				     csa_offs, csa_offs_len);
+	if (!res)
+		drv->send_frame_link_id = link_id;
 
 	return res;
 }
@@ -4593,7 +4645,7 @@
 		}
 
 		if (nla_put_u8(msg, NL80211_TXRATE_LEGACY,
-			       (u8) params->beacon_rate / 5) ||
+			       (u8) (params->beacon_rate / 5)) ||
 		    nla_put(msg, NL80211_TXRATE_HT, 0, NULL) ||
 		    (params->freq->vht_enabled &&
 		     nla_put(msg, NL80211_TXRATE_VHT, sizeof(vht_rate),
@@ -4916,11 +4968,118 @@
 #endif /* CONFIG_DRIVER_NL80211_QCA */
 
 
+static int nl80211_put_freq_params(struct nl_msg *msg,
+				   const struct hostapd_freq_params *freq)
+{
+	enum hostapd_hw_mode hw_mode;
+	int is_24ghz;
+	u8 channel;
+
+	wpa_printf(MSG_DEBUG, "  * freq=%d", freq->freq);
+	if (nla_put_u32(msg, NL80211_ATTR_WIPHY_FREQ, freq->freq))
+		return -ENOBUFS;
+
+	wpa_printf(MSG_DEBUG, "  * eht_enabled=%d", freq->eht_enabled);
+	wpa_printf(MSG_DEBUG, "  * he_enabled=%d", freq->he_enabled);
+	wpa_printf(MSG_DEBUG, "  * vht_enabled=%d", freq->vht_enabled);
+	wpa_printf(MSG_DEBUG, "  * ht_enabled=%d", freq->ht_enabled);
+	wpa_printf(MSG_DEBUG, "  * radar_background=%d",
+		   freq->radar_background);
+
+	hw_mode = ieee80211_freq_to_chan(freq->freq, &channel);
+	is_24ghz = hw_mode == HOSTAPD_MODE_IEEE80211G ||
+		hw_mode == HOSTAPD_MODE_IEEE80211B;
+
+	if (freq->vht_enabled ||
+	    ((freq->he_enabled || freq->eht_enabled) && !is_24ghz)) {
+		enum nl80211_chan_width cw;
+
+		wpa_printf(MSG_DEBUG, "  * bandwidth=%d", freq->bandwidth);
+		switch (freq->bandwidth) {
+		case 20:
+			cw = NL80211_CHAN_WIDTH_20;
+			break;
+		case 40:
+			cw = NL80211_CHAN_WIDTH_40;
+			break;
+		case 80:
+			if (freq->center_freq2)
+				cw = NL80211_CHAN_WIDTH_80P80;
+			else
+				cw = NL80211_CHAN_WIDTH_80;
+			break;
+		case 160:
+			cw = NL80211_CHAN_WIDTH_160;
+			break;
+		case 320:
+			cw = NL80211_CHAN_WIDTH_320;
+			break;
+		default:
+			return -EINVAL;
+		}
+
+		wpa_printf(MSG_DEBUG, "  * channel_width=%d", cw);
+		wpa_printf(MSG_DEBUG, "  * center_freq1=%d",
+			   freq->center_freq1);
+		wpa_printf(MSG_DEBUG, "  * center_freq2=%d",
+			   freq->center_freq2);
+		if (nla_put_u32(msg, NL80211_ATTR_CHANNEL_WIDTH, cw) ||
+		    nla_put_u32(msg, NL80211_ATTR_CENTER_FREQ1,
+				freq->center_freq1) ||
+		    (freq->center_freq2 &&
+		     nla_put_u32(msg, NL80211_ATTR_CENTER_FREQ2,
+				 freq->center_freq2)))
+			return -ENOBUFS;
+	} else if (freq->ht_enabled) {
+		enum nl80211_channel_type ct;
+
+		wpa_printf(MSG_DEBUG, "  * sec_channel_offset=%d",
+			   freq->sec_channel_offset);
+		switch (freq->sec_channel_offset) {
+		case -1:
+			ct = NL80211_CHAN_HT40MINUS;
+			break;
+		case 1:
+			ct = NL80211_CHAN_HT40PLUS;
+			break;
+		default:
+			ct = NL80211_CHAN_HT20;
+			break;
+		}
+
+		wpa_printf(MSG_DEBUG, "  * channel_type=%d", ct);
+		if (nla_put_u32(msg, NL80211_ATTR_WIPHY_CHANNEL_TYPE, ct))
+			return -ENOBUFS;
+	} else if (freq->edmg.channels && freq->edmg.bw_config) {
+		wpa_printf(MSG_DEBUG,
+			   "  * EDMG configuration: channels=0x%x bw_config=%d",
+			   freq->edmg.channels, freq->edmg.bw_config);
+		if (nla_put_u8(msg, NL80211_ATTR_WIPHY_EDMG_CHANNELS,
+			       freq->edmg.channels) ||
+		    nla_put_u8(msg, NL80211_ATTR_WIPHY_EDMG_BW_CONFIG,
+			       freq->edmg.bw_config))
+			return -1;
+	} else {
+		wpa_printf(MSG_DEBUG, "  * channel_type=%d",
+			   NL80211_CHAN_NO_HT);
+		if (nla_put_u32(msg, NL80211_ATTR_WIPHY_CHANNEL_TYPE,
+				NL80211_CHAN_NO_HT))
+			return -ENOBUFS;
+	}
+	if (freq->radar_background &&
+	    nla_put_flag(msg, NL80211_ATTR_RADAR_BACKGROUND))
+		return -ENOBUFS;
+
+	return 0;
+}
+
+
 static int wpa_driver_nl80211_set_ap(void *priv,
 				     struct wpa_driver_ap_params *params)
 {
 	struct i802_bss *bss = priv;
 	struct wpa_driver_nl80211_data *drv = bss->drv;
+	struct i802_link *link = bss->flink;
 	struct nl_msg *msg;
 	u8 cmd = NL80211_CMD_NEW_BEACON;
 	int ret = -ENOBUFS;
@@ -4932,7 +5091,24 @@
 	struct wpa_driver_mesh_bss_params mesh_params;
 #endif /* CONFIG_MESH */
 
-	beacon_set = params->reenable ? 0 : bss->flink->beacon_set;
+	if (params->mld_ap) {
+		size_t i;
+
+		for (i = 0; i < bss->n_links; i++) {
+			if (bss->links[i].link_id == params->mld_link_id) {
+				link = &bss->links[i];
+				break;
+			}
+		}
+
+		if (i == bss->n_links) {
+			wpa_printf(MSG_DEBUG, "nl80211: Link ID=%u not found",
+				   params->mld_link_id);
+			return -EINVAL;
+		}
+	}
+
+	beacon_set = params->reenable ? 0 : link->beacon_set;
 
 	wpa_printf(MSG_DEBUG, "nl80211: Set beacon (beacon_set=%d)",
 		   beacon_set);
@@ -4964,6 +5140,21 @@
 	    nl80211_put_dtim_period(msg, params->dtim_period) ||
 	    nla_put(msg, NL80211_ATTR_SSID, params->ssid_len, params->ssid))
 		goto fail;
+
+	if (params->mld_ap) {
+		wpa_printf(MSG_DEBUG, "nl80211: link_id=%u",
+			   params->mld_link_id);
+
+		if (nla_put_u8(msg, NL80211_ATTR_MLO_LINK_ID,
+			       params->mld_link_id) ||
+		    (params->freq &&
+		     nl80211_put_freq_params(msg, params->freq) < 0))
+			goto fail;
+
+		nl80211_link_set_freq(bss, params->mld_link_id,
+				      params->freq->freq);
+	}
+
 	if (params->proberesp && params->proberesp_len) {
 		wpa_hexdump(MSG_DEBUG, "nl80211: proberesp (offload)",
 			    params->proberesp, params->proberesp_len);
@@ -5032,6 +5223,27 @@
 			 suites))
 		goto fail;
 
+	if ((params->key_mgmt_suites & WPA_KEY_MGMT_PSK) &&
+	    (drv->capa.flags2 & WPA_DRIVER_FLAGS2_4WAY_HANDSHAKE_AP_PSK) &&
+	    params->psk_len) {
+		if (nla_put(msg, NL80211_ATTR_PMK, params->psk_len, params->psk)) {
+			wpa_printf(MSG_ERROR, "nl80211: Setting PSK failed");
+			goto fail;
+		} else
+			wpa_printf(MSG_DEBUG, "nl80211: Setting PSK for offload");
+	}
+
+	if (wpa_key_mgmt_sae(params->key_mgmt_suites) &&
+	    (drv->capa.flags2 & WPA_DRIVER_FLAGS2_SAE_OFFLOAD_AP) &&
+	    params->sae_password) {
+		if (nla_put(msg, NL80211_ATTR_SAE_PASSWORD,
+		    os_strlen(params->sae_password), params->sae_password)) {
+			wpa_printf(MSG_ERROR, "nl80211: Setting SAE password failed");
+			goto fail;
+		} else
+			wpa_printf(MSG_DEBUG, "nl80211: SAE password for offload");
+	}
+
 	if (params->key_mgmt_suites & WPA_KEY_MGMT_IEEE8021X_NO_WPA &&
 	    (!params->pairwise_ciphers ||
 	     params->pairwise_ciphers & (WPA_CIPHER_WEP104 | WPA_CIPHER_WEP40)) &&
@@ -5249,17 +5461,17 @@
 		wpa_printf(MSG_DEBUG, "nl80211: Beacon set failed: %d (%s)",
 			   ret, strerror(-ret));
 	} else {
-		bss->flink->beacon_set = 1;
+		link->beacon_set = 1;
 		nl80211_set_bss(bss, params->cts_protect, params->preamble,
 				params->short_slot_time, params->ht_opmode,
 				params->isolate, params->basic_rates);
 		nl80211_set_multicast_to_unicast(bss,
 						 params->multicast_to_unicast);
 		if (beacon_set && params->freq &&
-		    params->freq->bandwidth != bss->flink->bandwidth) {
+		    params->freq->bandwidth != link->bandwidth) {
 			wpa_printf(MSG_DEBUG,
 				   "nl80211: Update BSS %s bandwidth: %d -> %d",
-				   bss->ifname, bss->flink->bandwidth,
+				   bss->ifname, link->bandwidth,
 				   params->freq->bandwidth);
 			ret = nl80211_set_channel(bss, params->freq, 1);
 			if (ret) {
@@ -5269,7 +5481,7 @@
 			} else {
 				wpa_printf(MSG_DEBUG,
 					   "nl80211: Frequency set succeeded for ht2040 coex");
-				bss->flink->bandwidth = params->freq->bandwidth;
+				link->bandwidth = params->freq->bandwidth;
 			}
 		} else if (!beacon_set && params->freq) {
 			/*
@@ -5277,7 +5489,7 @@
 			 * mode only at the point when beaconing is started, so
 			 * set the initial value here.
 			 */
-			bss->flink->bandwidth = params->freq->bandwidth;
+			link->bandwidth = params->freq->bandwidth;
 		}
 	}
 
@@ -5299,109 +5511,24 @@
 }
 
 
-static int nl80211_put_freq_params(struct nl_msg *msg,
-				   const struct hostapd_freq_params *freq)
+static bool nl80211_link_valid(struct i802_bss *bss, s8 link_id)
 {
-	enum hostapd_hw_mode hw_mode;
-	int is_24ghz;
-	u8 channel;
+	unsigned int i;
 
-	wpa_printf(MSG_DEBUG, "  * freq=%d", freq->freq);
-	if (nla_put_u32(msg, NL80211_ATTR_WIPHY_FREQ, freq->freq))
-		return -ENOBUFS;
+	if (link_id < 0)
+		return false;
 
-	wpa_printf(MSG_DEBUG, "  * eht_enabled=%d", freq->eht_enabled);
-	wpa_printf(MSG_DEBUG, "  * he_enabled=%d", freq->he_enabled);
-	wpa_printf(MSG_DEBUG, "  * vht_enabled=%d", freq->vht_enabled);
-	wpa_printf(MSG_DEBUG, "  * ht_enabled=%d", freq->ht_enabled);
-	wpa_printf(MSG_DEBUG, "  * radar_background=%d",
-		   freq->radar_background);
+	for (i = 0; i < bss->n_links; i++) {
+		wpa_printf(MSG_DEBUG, "nl80211: %s - i=%u, link_id=%u",
+			   __func__, i, bss->links[i].link_id);
+		if (bss->links[i].link_id == NL80211_DRV_LINK_ID_NA)
+			continue;
 
-	hw_mode = ieee80211_freq_to_chan(freq->freq, &channel);
-	is_24ghz = hw_mode == HOSTAPD_MODE_IEEE80211G ||
-		hw_mode == HOSTAPD_MODE_IEEE80211B;
-
-	if (freq->vht_enabled ||
-	    ((freq->he_enabled || freq->eht_enabled) && !is_24ghz)) {
-		enum nl80211_chan_width cw;
-
-		wpa_printf(MSG_DEBUG, "  * bandwidth=%d", freq->bandwidth);
-		switch (freq->bandwidth) {
-		case 20:
-			cw = NL80211_CHAN_WIDTH_20;
-			break;
-		case 40:
-			cw = NL80211_CHAN_WIDTH_40;
-			break;
-		case 80:
-			if (freq->center_freq2)
-				cw = NL80211_CHAN_WIDTH_80P80;
-			else
-				cw = NL80211_CHAN_WIDTH_80;
-			break;
-		case 160:
-			cw = NL80211_CHAN_WIDTH_160;
-			break;
-		case 320:
-			cw = NL80211_CHAN_WIDTH_320;
-			break;
-		default:
-			return -EINVAL;
-		}
-
-		wpa_printf(MSG_DEBUG, "  * channel_width=%d", cw);
-		wpa_printf(MSG_DEBUG, "  * center_freq1=%d",
-			   freq->center_freq1);
-		wpa_printf(MSG_DEBUG, "  * center_freq2=%d",
-			   freq->center_freq2);
-		if (nla_put_u32(msg, NL80211_ATTR_CHANNEL_WIDTH, cw) ||
-		    nla_put_u32(msg, NL80211_ATTR_CENTER_FREQ1,
-				freq->center_freq1) ||
-		    (freq->center_freq2 &&
-		     nla_put_u32(msg, NL80211_ATTR_CENTER_FREQ2,
-				 freq->center_freq2)))
-			return -ENOBUFS;
-	} else if (freq->ht_enabled) {
-		enum nl80211_channel_type ct;
-
-		wpa_printf(MSG_DEBUG, "  * sec_channel_offset=%d",
-			   freq->sec_channel_offset);
-		switch (freq->sec_channel_offset) {
-		case -1:
-			ct = NL80211_CHAN_HT40MINUS;
-			break;
-		case 1:
-			ct = NL80211_CHAN_HT40PLUS;
-			break;
-		default:
-			ct = NL80211_CHAN_HT20;
-			break;
-		}
-
-		wpa_printf(MSG_DEBUG, "  * channel_type=%d", ct);
-		if (nla_put_u32(msg, NL80211_ATTR_WIPHY_CHANNEL_TYPE, ct))
-			return -ENOBUFS;
-	} else if (freq->edmg.channels && freq->edmg.bw_config) {
-		wpa_printf(MSG_DEBUG,
-			   "  * EDMG configuration: channels=0x%x bw_config=%d",
-			   freq->edmg.channels, freq->edmg.bw_config);
-		if (nla_put_u8(msg, NL80211_ATTR_WIPHY_EDMG_CHANNELS,
-			       freq->edmg.channels) ||
-		    nla_put_u8(msg, NL80211_ATTR_WIPHY_EDMG_BW_CONFIG,
-			       freq->edmg.bw_config))
-			return -1;
-	} else {
-		wpa_printf(MSG_DEBUG, "  * channel_type=%d",
-			   NL80211_CHAN_NO_HT);
-		if (nla_put_u32(msg, NL80211_ATTR_WIPHY_CHANNEL_TYPE,
-				NL80211_CHAN_NO_HT))
-			return -ENOBUFS;
+		if (bss->links[i].link_id == link_id)
+			return true;
 	}
-	if (freq->radar_background &&
-	    nla_put_flag(msg, NL80211_ATTR_RADAR_BACKGROUND))
-		return -ENOBUFS;
 
-	return 0;
+	return false;
 }
 
 
@@ -5425,9 +5552,19 @@
 		return -1;
 	}
 
+	if (nl80211_link_valid(bss, freq->link_id)) {
+		wpa_printf(MSG_DEBUG, "nl80211: Set link_id=%u for freq",
+			   freq->link_id);
+
+		if (nla_put_u8(msg, NL80211_ATTR_MLO_LINK_ID, freq->link_id)) {
+			nlmsg_free(msg);
+			return -ENOBUFS;
+		}
+	}
+
 	ret = send_and_recv_msgs(drv, msg, NULL, NULL, NULL, NULL);
 	if (ret == 0) {
-		bss->flink->freq = freq->freq;
+		nl80211_link_set_freq(bss, freq->link_id, freq->freq);
 		return 0;
 	}
 	wpa_printf(MSG_DEBUG, "nl80211: Failed to set channel (freq=%d): "
@@ -6105,7 +6242,7 @@
 
 static int nl80211_tx_control_port(void *priv, const u8 *dest,
 				   u16 proto, const u8 *buf, size_t len,
-				   int no_encrypt)
+				   int no_encrypt, int link_id)
 {
 	struct nl80211_ack_ext_arg ext_arg;
 	struct i802_bss *bss = priv;
@@ -6124,7 +6261,9 @@
 	    nla_put(msg, NL80211_ATTR_MAC, ETH_ALEN, dest) ||
 	    nla_put(msg, NL80211_ATTR_FRAME, len, buf) ||
 	    (no_encrypt &&
-	     nla_put_flag(msg, NL80211_ATTR_CONTROL_PORT_NO_ENCRYPT))) {
+	     nla_put_flag(msg, NL80211_ATTR_CONTROL_PORT_NO_ENCRYPT)) ||
+	    (link_id != NL80211_DRV_LINK_ID_NA &&
+	     nla_put_u8(msg, NL80211_ATTR_MLO_LINK_ID, link_id))) {
 		nlmsg_free(msg);
 		return -ENOBUFS;
 	}
@@ -6144,6 +6283,7 @@
 			   "nl80211: tx_control_port cookie=0x%llx",
 			   (long long unsigned int) cookie);
 		drv->eapol_tx_cookie = cookie;
+		drv->eapol_tx_link_id = link_id;
 	}
 
 	return ret;
@@ -6182,7 +6322,8 @@
 
 static int wpa_driver_nl80211_hapd_send_eapol(
 	void *priv, const u8 *addr, const u8 *data,
-	size_t data_len, int encrypt, const u8 *own_addr, u32 flags)
+	size_t data_len, int encrypt, const u8 *own_addr, u32 flags,
+	int link_id)
 {
 	struct i802_bss *bss = priv;
 	struct wpa_driver_nl80211_data *drv = bss->drv;
@@ -6197,7 +6338,8 @@
 	if (drv->control_port_ap &&
 	    (drv->capa.flags & WPA_DRIVER_FLAGS_CONTROL_PORT))
 		return nl80211_tx_control_port(bss, addr, ETH_P_EAPOL,
-					       data, data_len, !encrypt);
+					       data, data_len, !encrypt,
+					       link_id);
 
 	if (drv->device_ap_sme || !drv->use_monitor)
 		return nl80211_send_eapol_data(bss, addr, data, data_len);
@@ -6496,7 +6638,8 @@
 	if (params->key_mgmt_suite == WPA_KEY_MGMT_IEEE8021X ||
 	    params->key_mgmt_suite == WPA_KEY_MGMT_PSK ||
 	    params->key_mgmt_suite == WPA_KEY_MGMT_IEEE8021X_SHA256 ||
-	    params->key_mgmt_suite == WPA_KEY_MGMT_PSK_SHA256) {
+	    params->key_mgmt_suite == WPA_KEY_MGMT_PSK_SHA256 ||
+	    params->key_mgmt_suite == WPA_KEY_MGMT_IEEE8021X_SHA384) {
 		wpa_printf(MSG_DEBUG, "  * control port");
 		if (nla_put_flag(msg, NL80211_ATTR_CONTROL_PORT))
 			goto fail;
@@ -6796,7 +6939,8 @@
 	    params->key_mgmt_suite == WPA_KEY_MGMT_FT_FILS_SHA256 ||
 	    params->key_mgmt_suite == WPA_KEY_MGMT_FT_FILS_SHA384 ||
 	    params->key_mgmt_suite == WPA_KEY_MGMT_OWE ||
-	    params->key_mgmt_suite == WPA_KEY_MGMT_DPP) {
+	    params->key_mgmt_suite == WPA_KEY_MGMT_DPP ||
+	    params->key_mgmt_suite == WPA_KEY_MGMT_IEEE8021X_SHA384) {
 		u32 *mgmt;
 		unsigned int akm_count = 1, i;
 
@@ -6880,6 +7024,9 @@
 		case WPA_KEY_MGMT_DPP:
 			mgmt[0] = RSN_AUTH_KEY_MGMT_DPP;
 			break;
+		case WPA_KEY_MGMT_IEEE8021X_SHA384:
+			mgmt[0] = RSN_AUTH_KEY_MGMT_802_1X_SHA384;
+			break;
 		case WPA_KEY_MGMT_PSK:
 		default:
 			mgmt[0] = RSN_AUTH_KEY_MGMT_PSK_OVER_802_1X;
@@ -7040,6 +7187,27 @@
 	     wpa_key_mgmt_sae(params->allowed_key_mgmts)) &&
 	    nl80211_put_sae_pwe(msg, params->sae_pwe) < 0)
 		goto fail;
+
+	/* Add SAE password in case of SAE authentication offload */
+	if ((params->sae_password || params->passphrase) &&
+	    (drv->capa.flags2 & WPA_DRIVER_FLAGS2_SAE_OFFLOAD_STA)) {
+		const char *password;
+		size_t pwd_len;
+
+		if (params->sae_password && params->sae_password_id) {
+			wpa_printf(MSG_INFO,
+				   "nl80211: Use of SAE password identifiers not supported with driver-based SAE");
+			goto fail;
+		}
+
+		password = params->sae_password;
+		if (!password)
+			password = params->passphrase;
+		pwd_len = os_strlen(password);
+		wpa_printf(MSG_DEBUG, "  * SAE password");
+		if (nla_put(msg, NL80211_ATTR_SAE_PASSWORD, pwd_len, password))
+			goto fail;
+	}
 #endif /* CONFIG_SAE */
 
 	algs = 0;
@@ -7053,6 +7221,8 @@
 		algs++;
 	if (params->auth_alg & WPA_AUTH_ALG_FT)
 		algs++;
+	if (params->auth_alg & WPA_AUTH_ALG_SAE)
+		algs++;
 	if (algs > 1) {
 		wpa_printf(MSG_DEBUG, "  * Leave out Auth Type for automatic "
 			   "selection");
@@ -7532,24 +7702,33 @@
 
 
 static int i802_get_seqnum(const char *iface, void *priv, const u8 *addr,
-			   int idx, u8 *seq)
+			   int idx, int link_id, u8 *seq)
 {
 	struct i802_bss *bss = priv;
 	struct wpa_driver_nl80211_data *drv = bss->drv;
 	struct nl_msg *msg;
+	int res;
 
 	msg = nl80211_ifindex_msg(drv, if_nametoindex(iface), 0,
 				  NL80211_CMD_GET_KEY);
 	if (!msg ||
 	    (addr && nla_put(msg, NL80211_ATTR_MAC, ETH_ALEN, addr)) ||
+	    (link_id != NL80211_DRV_LINK_ID_NA &&
+	     nla_put_u8(msg, NL80211_ATTR_MLO_LINK_ID, link_id)) ||
 	    nla_put_u8(msg, NL80211_ATTR_KEY_IDX, idx)) {
 		nlmsg_free(msg);
 		return -ENOBUFS;
 	}
 
-	memset(seq, 0, 6);
+	os_memset(seq, 0, 6);
+	res = send_and_recv_msgs(drv, msg, get_key_handler, seq, NULL, NULL);
+	if (res) {
+		wpa_printf(MSG_DEBUG,
+			   "nl80211: Failed to get current TX sequence for a key (link_id=%d idx=%d): %d (%s)",
+			   link_id, idx, res, strerror(-res));
+	}
 
-	return send_and_recv_msgs(drv, msg, get_key_handler, seq, NULL, NULL);
+	return res;
 }
 
 
@@ -7975,7 +8154,8 @@
 
 
 static int i802_set_tx_queue_params(void *priv, int queue, int aifs,
-				    int cw_min, int cw_max, int burst_time)
+				    int cw_min, int cw_max, int burst_time,
+				    int link_id)
 {
 	struct i802_bss *bss = priv;
 	struct wpa_driver_nl80211_data *drv = bss->drv;
@@ -8027,6 +8207,10 @@
 
 	nla_nest_end(msg, txq);
 
+	if (link_id != NL80211_DRV_LINK_ID_NA &&
+	    nla_put_u8(msg, NL80211_ATTR_MLO_LINK_ID, link_id))
+		goto fail;
+
 	res = send_and_recv_msgs(drv, msg, NULL, NULL, NULL, NULL);
 	wpa_printf(MSG_DEBUG,
 		   "nl80211: TX queue param set: queue=%d aifs=%d cw_min=%d cw_max=%d burst_time=%d --> res=%d",
@@ -8041,7 +8225,7 @@
 
 
 static int i802_set_sta_vlan(struct i802_bss *bss, const u8 *addr,
-			     const char *ifname, int vlan_id)
+			     const char *ifname, int vlan_id, int link_id)
 {
 	struct wpa_driver_nl80211_data *drv = bss->drv;
 	struct nl_msg *msg;
@@ -8055,6 +8239,8 @@
 	    nla_put(msg, NL80211_ATTR_MAC, ETH_ALEN, addr) ||
 	    (vlan_id && (drv->capa.flags & WPA_DRIVER_FLAGS_VLAN_OFFLOAD) &&
 	     nla_put_u16(msg, NL80211_ATTR_VLAN_ID, vlan_id)) ||
+	    (link_id != NL80211_DRV_LINK_ID_NA &&
+	     nla_put_u8(msg, NL80211_ATTR_MLO_LINK_ID, link_id)) ||
 	    nla_put_u32(msg, NL80211_ATTR_STA_VLAN, if_nametoindex(ifname))) {
 		nlmsg_free(msg);
 		return -ENOBUFS;
@@ -8097,14 +8283,15 @@
 
 
 static int i802_sta_deauth(void *priv, const u8 *own_addr, const u8 *addr,
-			   u16 reason)
+			   u16 reason, int link_id)
 {
 	struct i802_bss *bss = priv;
 	struct wpa_driver_nl80211_data *drv = bss->drv;
 	struct ieee80211_mgmt mgmt;
 	u8 channel;
+	struct i802_link *link = nl80211_get_link(bss, link_id);
 
-	if (ieee80211_freq_to_chan(bss->flink->freq, &channel) ==
+	if (ieee80211_freq_to_chan(link->freq, &channel) ==
 	    HOSTAPD_MODE_IEEE80211AD) {
 		/* Deauthentication is not used in DMG/IEEE 802.11ad;
 		 * disassociate the STA instead. */
@@ -8127,7 +8314,7 @@
 	return wpa_driver_nl80211_send_mlme(bss, (u8 *) &mgmt,
 					    IEEE80211_HDRLEN +
 					    sizeof(mgmt.u.deauth), 0, 0, 0, 0,
-					    0, NULL, 0, 0);
+					    0, NULL, 0, 0, -1);
 }
 
 
@@ -8154,7 +8341,7 @@
 	return wpa_driver_nl80211_send_mlme(bss, (u8 *) &mgmt,
 					    IEEE80211_HDRLEN +
 					    sizeof(mgmt.u.disassoc), 0, 0, 0, 0,
-					    0, NULL, 0, 0);
+					    0, NULL, 0, 0, -1);
 }
 
 
@@ -8312,7 +8499,8 @@
 			wpa_printf(MSG_ERROR, "nl80211: Failed to set WDS STA "
 				   "interface %s up", name);
 		}
-		return i802_set_sta_vlan(priv, addr, name, 0);
+		return i802_set_sta_vlan(priv, addr, name, 0,
+					 NL80211_DRV_LINK_ID_NA);
 	} else {
 		if (bridge_ifname &&
 		    linux_br_del_if(drv->global->ioctl_sock, bridge_ifname,
@@ -8321,7 +8509,8 @@
 				   "nl80211: Failed to remove interface %s from bridge %s: %s",
 				   name, bridge_ifname, strerror(errno));
 
-		i802_set_sta_vlan(priv, addr, bss->ifname, 0);
+		i802_set_sta_vlan(priv, addr, bss->ifname, 0,
+				  NL80211_DRV_LINK_ID_NA);
 		nl80211_remove_iface(drv, if_nametoindex(name));
 		os_memset(&event, 0, sizeof(event));
 		event.wds_sta_interface.sta_addr = addr;
@@ -9028,7 +9217,7 @@
 	     !drv->use_monitor))
 		ret = wpa_driver_nl80211_send_mlme(bss, buf, 24 + data_len,
 						   0, freq, no_cck, offchanok,
-						   wait_time, NULL, 0, 0);
+						   wait_time, NULL, 0, 0, -1);
 	else
 		ret = nl80211_send_frame_cmd(bss, freq, wait_time, buf,
 					     24 + data_len,
@@ -10127,7 +10316,7 @@
 	os_memcpy(nulldata.hdr.IEEE80211_SA_FROMDS, own_addr, ETH_ALEN);
 
 	if (wpa_driver_nl80211_send_mlme(bss, (u8 *) &nulldata, size, 0, 0, 0,
-					 0, 0, NULL, 0, 0) < 0)
+					 0, 0, NULL, 0, 0, -1) < 0)
 		wpa_printf(MSG_DEBUG, "nl80211_send_null_frame: Failed to "
 			   "send poll frame");
 }
@@ -10270,10 +10459,46 @@
 }
 
 
+static int
+nl80211_tdls_set_discovery_resp_link(struct wpa_driver_nl80211_data *drv,
+				     int link_id)
+{
+#ifdef CONFIG_DRIVER_NL80211_QCA
+	struct nl_msg *msg;
+	struct nlattr *params;
+
+	wpa_printf(MSG_DEBUG, "nl80211: TDLS Discovery Response Tx link ID %u",
+		   link_id);
+
+	if (!(msg = nl80211_drv_msg(drv, 0, NL80211_CMD_VENDOR)) ||
+	    nla_put_u32(msg, NL80211_ATTR_IFINDEX, drv->ifindex) ||
+	    nla_put_u32(msg, NL80211_ATTR_VENDOR_ID, OUI_QCA) ||
+	    nla_put_u32(msg, NL80211_ATTR_VENDOR_SUBCMD,
+			QCA_NL80211_VENDOR_SUBCMD_TDLS_DISC_RSP_EXT) ||
+	    !(params = nla_nest_start(msg, NL80211_ATTR_VENDOR_DATA)) ||
+	    nla_put_u8(msg, QCA_WLAN_VENDOR_ATTR_TDLS_DISC_RSP_EXT_TX_LINK,
+		       link_id)) {
+		wpa_printf(MSG_ERROR,
+			   "%s: err in adding vendor_cmd and vendor_data",
+			   __func__);
+		nlmsg_free(msg);
+		return -1;
+	}
+	nla_nest_end(msg, params);
+
+	return send_and_recv_msgs(drv, msg, NULL, NULL, NULL, NULL);
+#else /* CONFIG_DRIVER_NL80211_QCA */
+	wpa_printf(MSG_ERROR,
+		   "nl80211: Setting TX link for TDLS Discovery Response not supported");
+	return -1;
+#endif /* CONFIG_DRIVER_NL80211_QCA */
+}
+
+
 static int nl80211_send_tdls_mgmt(void *priv, const u8 *dst, u8 action_code,
 				  u8 dialog_token, u16 status_code,
 				  u32 peer_capab, int initiator, const u8 *buf,
-				  size_t len)
+				  size_t len, int link_id)
 {
 	struct i802_bss *bss = priv;
 	struct wpa_driver_nl80211_data *drv = bss->drv;
@@ -10285,6 +10510,10 @@
 	if (!dst)
 		return -EINVAL;
 
+	if (link_id >= 0 &&
+	    nl80211_tdls_set_discovery_resp_link(drv, link_id) < 0)
+		return -EOPNOTSUPP;
+
 	if (!(msg = nl80211_drv_msg(drv, 0, NL80211_CMD_TDLS_MGMT)) ||
 	    nla_put(msg, NL80211_ATTR_MAC, ETH_ALEN, dst) ||
 	    nla_put_u8(msg, NL80211_ATTR_TDLS_ACTION, action_code) ||
@@ -10476,12 +10705,13 @@
 				    size_t data_len, int noack,
 				    unsigned int freq,
 				    const u16 *csa_offs, size_t csa_offs_len,
-				    int no_encrypt, unsigned int wait)
+				    int no_encrypt, unsigned int wait,
+				    int link_id)
 {
 	struct i802_bss *bss = priv;
 	return wpa_driver_nl80211_send_mlme(bss, data, data_len, noack,
 					    freq, 0, 0, wait, csa_offs,
-					    csa_offs_len, no_encrypt);
+					    csa_offs_len, no_encrypt, link_id);
 }
 
 
@@ -10493,10 +10723,11 @@
 
 
 static int driver_nl80211_set_sta_vlan(void *priv, const u8 *addr,
-				       const char *ifname, int vlan_id)
+				       const char *ifname, int vlan_id,
+				       int link_id)
 {
 	struct i802_bss *bss = priv;
-	return i802_set_sta_vlan(bss, addr, ifname, vlan_id);
+	return i802_set_sta_vlan(bss, addr, ifname, vlan_id, link_id);
 }
 
 
@@ -10773,6 +11004,7 @@
 				  "capa.enc=0x%x\n"
 				  "capa.auth=0x%x\n"
 				  "capa.flags=0x%llx\n"
+				  "capa.flags2=0x%llx\n"
 				  "capa.rrm_flags=0x%x\n"
 				  "capa.max_scan_ssids=%d\n"
 				  "capa.max_sched_scan_ssids=%d\n"
@@ -10797,6 +11029,7 @@
 				  drv->capa.enc,
 				  drv->capa.auth,
 				  (unsigned long long) drv->capa.flags,
+				  (unsigned long long) drv->capa.flags2,
 				  drv->capa.rrm_flags,
 				  drv->capa.max_scan_ssids,
 				  drv->capa.max_sched_scan_ssids,
@@ -11293,7 +11526,7 @@
 	wpa_hexdump(MSG_DEBUG, "nl80211: Setting QoS Map",
 		    qos_map_set, qos_map_set_len);
 
-	if (!(msg = nl80211_drv_msg(drv, 0, NL80211_CMD_SET_QOS_MAP)) ||
+	if (!(msg = nl80211_cmd_msg(bss, 0, NL80211_CMD_SET_QOS_MAP)) ||
 	    nla_put(msg, NL80211_ATTR_QOS_MAP, qos_map_set_len, qos_map_set)) {
 		nlmsg_free(msg);
 		return -ENOBUFS;
@@ -11614,7 +11847,7 @@
 	}
 
 	wpa_printf(MSG_DEBUG, "nl80211: set_mac_addr for %s to " MACSTR,
-		bss->ifname, MAC2STR(addr));
+		   bss->ifname, MAC2STR(addr));
 	drv->addr_changed = new_addr;
 	os_memcpy(bss->prev_addr, bss->addr, ETH_ALEN);
 	os_memcpy(bss->addr, addr, ETH_ALEN);
@@ -11622,8 +11855,9 @@
 	if (linux_set_iface_flags(drv->global->ioctl_sock, bss->ifname, 1) < 0)
 	{
 		wpa_printf(MSG_DEBUG,
-			"nl80211: Could not restore interface UP after set_mac_addr");
+			   "nl80211: Could not restore interface UP after set_mac_addr");
 	}
+
 	return 0;
 }
 
@@ -13289,11 +13523,42 @@
 	*ext_capa_len = drv->extended_capa_len;
 
 	/* Replace the default value if a per-interface type value exists */
-	for (i = 0; i < drv->num_iface_ext_capa; i++) {
-		if (nlmode == drv->iface_ext_capa[i].iftype) {
-			*ext_capa = drv->iface_ext_capa[i].ext_capa;
-			*ext_capa_mask = drv->iface_ext_capa[i].ext_capa_mask;
-			*ext_capa_len = drv->iface_ext_capa[i].ext_capa_len;
+	for (i = 0; i < drv->num_iface_capa; i++) {
+		if (nlmode == drv->iface_capa[i].iftype) {
+			*ext_capa = drv->iface_capa[i].ext_capa;
+			*ext_capa_mask = drv->iface_capa[i].ext_capa_mask;
+			*ext_capa_len = drv->iface_capa[i].ext_capa_len;
+			break;
+		}
+	}
+
+	return 0;
+}
+
+
+static int nl80211_get_mld_capab(void *priv, enum wpa_driver_if_type type,
+				 u16 *eml_capa, u16 *mld_capa_and_ops)
+{
+	struct i802_bss *bss = priv;
+	struct wpa_driver_nl80211_data *drv = bss->drv;
+	enum nl80211_iftype nlmode;
+	unsigned int i;
+
+	if (!eml_capa || !mld_capa_and_ops)
+		return -1;
+
+	nlmode = wpa_driver_nl80211_if_type(type);
+
+	/* By default, set to zero */
+	*eml_capa = 0;
+	*mld_capa_and_ops = 0;
+
+	/* Replace the default value if a per-interface type value exists */
+	for (i = 0; i < drv->num_iface_capa; i++) {
+		if (nlmode == drv->iface_capa[i].iftype) {
+			*eml_capa = drv->iface_capa[i].eml_capa;
+			*mld_capa_and_ops =
+				drv->iface_capa[i].mld_capa_and_ops;
 			break;
 		}
 	}
@@ -13773,6 +14038,7 @@
 	.do_acs = nl80211_do_acs,
 	.configure_data_frame_filters = nl80211_configure_data_frame_filters,
 	.get_ext_capab = nl80211_get_ext_capab,
+	.get_mld_capab = nl80211_get_mld_capab,
 	.update_connect_params = nl80211_update_connection_params,
 	.send_external_auth_status = nl80211_send_external_auth_status,
 	.set_4addr_mode = nl80211_set_4addr_mode,
diff --git a/src/drivers/driver_nl80211.h b/src/drivers/driver_nl80211.h
index c597295..d922912 100644
--- a/src/drivers/driver_nl80211.h
+++ b/src/drivers/driver_nl80211.h
@@ -119,12 +119,14 @@
 	struct wpa_driver_capa capa;
 	u8 *extended_capa, *extended_capa_mask;
 	unsigned int extended_capa_len;
-	struct drv_nl80211_ext_capa {
+	struct drv_nl80211_iface_capa {
 		enum nl80211_iftype iftype;
 		u8 *ext_capa, *ext_capa_mask;
 		unsigned int ext_capa_len;
-	} iface_ext_capa[NL80211_IFTYPE_MAX];
-	unsigned int num_iface_ext_capa;
+		u16 eml_capa;
+		u16 mld_capa_and_ops;
+	} iface_capa[NL80211_IFTYPE_MAX];
+	unsigned int num_iface_capa;
 
 	int has_capability;
 	int has_driver_key_mgmt;
@@ -204,10 +206,12 @@
 	u64 vendor_scan_cookie;
 	u64 remain_on_chan_cookie;
 	u64 send_frame_cookie;
+	int send_frame_link_id;
 #define MAX_SEND_FRAME_COOKIES 20
 	u64 send_frame_cookies[MAX_SEND_FRAME_COOKIES];
 	unsigned int num_send_frame_cookies;
 	u64 eapol_tx_cookie;
+	int eapol_tx_link_id;
 
 	unsigned int last_mgmt_freq;
 
@@ -324,6 +328,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);
 
 #ifdef ANDROID
 int android_nl_socket_set_nonblocking(struct nl_sock *handle);
diff --git a/src/drivers/driver_nl80211_capa.c b/src/drivers/driver_nl80211_capa.c
index 09771bb..fdfe8f6 100644
--- a/src/drivers/driver_nl80211_capa.c
+++ b/src/drivers/driver_nl80211_capa.c
@@ -600,6 +600,10 @@
 		capa->flags |= WPA_DRIVER_FLAGS_4WAY_HANDSHAKE_8021X;
 
 	if (ext_feature_isset(ext_features, len,
+			      NL80211_EXT_FEATURE_SAE_OFFLOAD))
+		capa->flags2 |= WPA_DRIVER_FLAGS2_SAE_OFFLOAD_STA;
+
+	if (ext_feature_isset(ext_features, len,
 			      NL80211_EXT_FEATURE_MFP_OPTIONAL))
 		capa->flags |= WPA_DRIVER_FLAGS_MFP_OPTIONAL;
 
@@ -676,7 +680,7 @@
 
 	if (ext_feature_isset(ext_features, len,
 			      NL80211_EXT_FEATURE_RADAR_BACKGROUND))
-		capa->flags2 |= WPA_DRIVER_RADAR_BACKGROUND;
+		capa->flags2 |= WPA_DRIVER_FLAGS2_RADAR_BACKGROUND;
 
 	if (ext_feature_isset(ext_features, len,
 			      NL80211_EXT_FEATURE_SECURE_LTF)) {
@@ -696,6 +700,26 @@
 		capa->flags2 |= WPA_DRIVER_FLAGS2_PROT_RANGE_NEG_STA;
 		capa->flags2 |= WPA_DRIVER_FLAGS2_PROT_RANGE_NEG_AP;
 	}
+
+	if (ext_feature_isset(ext_features, len,
+			      NL80211_EXT_FEATURE_SCAN_MIN_PREQ_CONTENT))
+		capa->flags2 |= WPA_DRIVER_FLAGS2_SCAN_MIN_PREQ;
+
+	if (ext_feature_isset(ext_features, len,
+			      NL80211_EXT_FEATURE_4WAY_HANDSHAKE_AP_PSK))
+		capa->flags2 |= WPA_DRIVER_FLAGS2_4WAY_HANDSHAKE_AP_PSK;
+
+	if (ext_feature_isset(ext_features, len,
+			      NL80211_EXT_FEATURE_OWE_OFFLOAD))
+		capa->flags2 |= WPA_DRIVER_FLAGS2_OWE_OFFLOAD_STA;
+
+	if (ext_feature_isset(ext_features, len,
+			      NL80211_EXT_FEATURE_OWE_OFFLOAD_AP))
+		capa->flags2 |= WPA_DRIVER_FLAGS2_OWE_OFFLOAD_AP;
+
+	if (ext_feature_isset(ext_features, len,
+			      NL80211_EXT_FEATURE_SAE_OFFLOAD_AP))
+		capa->flags2 |= WPA_DRIVER_FLAGS2_SAE_OFFLOAD_AP;
 }
 
 
@@ -816,12 +840,12 @@
 	int rem = 0, i;
 	struct nlattr *tb1[NL80211_ATTR_MAX + 1], *attr;
 
-	if (!tb || drv->num_iface_ext_capa == NL80211_IFTYPE_MAX)
+	if (!tb || drv->num_iface_capa == NL80211_IFTYPE_MAX)
 		return;
 
 	nla_for_each_nested(attr, tb, rem) {
 		unsigned int len;
-		struct drv_nl80211_ext_capa *capa;
+		struct drv_nl80211_iface_capa *capa;
 
 		nla_parse(tb1, NL80211_ATTR_MAX, nla_data(attr),
 			  nla_len(attr), NULL);
@@ -831,7 +855,7 @@
 		    !tb1[NL80211_ATTR_EXT_CAPA_MASK])
 			continue;
 
-		capa = &drv->iface_ext_capa[drv->num_iface_ext_capa];
+		capa = &drv->iface_capa[drv->num_iface_capa];
 		capa->iftype = nla_get_u32(tb1[NL80211_ATTR_IFTYPE]);
 		wpa_printf(MSG_DEBUG,
 			   "nl80211: Driver-advertised extended capabilities for interface type %s",
@@ -857,8 +881,16 @@
 		wpa_hexdump(MSG_DEBUG, "nl80211: Extended capabilities mask",
 			    capa->ext_capa_mask, capa->ext_capa_len);
 
-		drv->num_iface_ext_capa++;
-		if (drv->num_iface_ext_capa == NL80211_IFTYPE_MAX)
+		if (tb1[NL80211_ATTR_EML_CAPABILITY] &&
+		    tb1[NL80211_ATTR_MLD_CAPA_AND_OPS]) {
+			capa->eml_capa =
+				nla_get_u16(tb1[NL80211_ATTR_EML_CAPABILITY]);
+			capa->mld_capa_and_ops =
+				nla_get_u16(tb1[NL80211_ATTR_MLD_CAPA_AND_OPS]);
+		}
+
+		drv->num_iface_capa++;
+		if (drv->num_iface_capa == NL80211_IFTYPE_MAX)
 			break;
 	}
 
@@ -867,13 +899,13 @@
 err:
 	/* Cleanup allocated memory on error */
 	for (i = 0; i < NL80211_IFTYPE_MAX; i++) {
-		os_free(drv->iface_ext_capa[i].ext_capa);
-		drv->iface_ext_capa[i].ext_capa = NULL;
-		os_free(drv->iface_ext_capa[i].ext_capa_mask);
-		drv->iface_ext_capa[i].ext_capa_mask = NULL;
-		drv->iface_ext_capa[i].ext_capa_len = 0;
+		os_free(drv->iface_capa[i].ext_capa);
+		drv->iface_capa[i].ext_capa = NULL;
+		os_free(drv->iface_capa[i].ext_capa_mask);
+		drv->iface_capa[i].ext_capa_mask = NULL;
+		drv->iface_capa[i].ext_capa_len = 0;
 	}
-	drv->num_iface_ext_capa = 0;
+	drv->num_iface_capa = 0;
 }
 
 
@@ -1560,9 +1592,10 @@
 #endif /* CONFIG_DRIVER_NL80211_QCA */
 
 	wpa_printf(MSG_DEBUG,
-		   "nl80211: key_mgmt=0x%x enc=0x%x auth=0x%x flags=0x%llx rrm_flags=0x%x probe_resp_offloads=0x%x max_stations=%u max_remain_on_chan=%u max_scan_ssids=%d",
+		   "nl80211: key_mgmt=0x%x enc=0x%x auth=0x%x flags=0x%llx flags2=0x%llx rrm_flags=0x%x probe_resp_offloads=0x%x max_stations=%u max_remain_on_chan=%u max_scan_ssids=%d",
 		   drv->capa.key_mgmt, drv->capa.enc, drv->capa.auth,
-		   (unsigned long long) drv->capa.flags, drv->capa.rrm_flags,
+		   (unsigned long long) drv->capa.flags,
+		   (unsigned long long) drv->capa.flags2, drv->capa.rrm_flags,
 		   drv->capa.probe_resp_offloads, drv->capa.max_stations,
 		   drv->capa.max_remain_on_chan, drv->capa.max_scan_ssids);
 	return 0;
@@ -2557,8 +2590,7 @@
 		for (j = 0; j < mode->num_channels; j++) {
 			struct hostapd_channel_data *chan = &mode->channels[j];
 
-			if (chan->freq >= 5925 && chan->freq <= 7125 &&
-			    !(chan->flag & HOSTAPD_CHAN_DISABLED))
+			if (is_6ghz_freq(chan->freq))
 				drv->uses_6ghz = true;
 			res = os_snprintf(pos, end - pos, " %d%s%s%s",
 					  chan->freq,
diff --git a/src/drivers/driver_nl80211_event.c b/src/drivers/driver_nl80211_event.c
index 16d6f5b..fdaf07b 100644
--- a/src/drivers/driver_nl80211_event.c
+++ b/src/drivers/driver_nl80211_event.c
@@ -184,6 +184,7 @@
 	C2S(NL80211_CMD_MODIFY_LINK_STA)
 	C2S(NL80211_CMD_REMOVE_LINK_STA)
 	C2S(NL80211_CMD_SET_HW_TIMESTAMP)
+	C2S(NL80211_CMD_LINKS_REMOVED)
 	C2S(__NL80211_CMD_AFTER_LAST)
 	}
 #undef C2S
@@ -1269,7 +1270,7 @@
 	if (finished)
 		bss->flink->freq = data.ch_switch.freq;
 
-	if (link) {
+	if (link && is_sta_interface(drv->nlmode)) {
 		u8 link_id = nla_get_u8(link);
 
 		if (link_id < MAX_NUM_MLD_LINKS &&
@@ -1424,6 +1425,8 @@
 	event.tx_status.data = frame;
 	event.tx_status.data_len = len;
 	event.tx_status.ack = ack != NULL;
+	event.tx_status.link_id = cookie_val == drv->send_frame_cookie ?
+		drv->send_frame_link_id : NL80211_DRV_LINK_ID_NA;
 	wpa_supplicant_event(drv->ctx, EVENT_TX_STATUS, &event);
 }
 
@@ -1611,6 +1614,21 @@
 }
 
 
+static struct i802_link *
+nl80211_get_mld_link_by_freq(struct i802_bss *bss, unsigned int freq)
+{
+	unsigned int i;
+
+	for (i = 0; i < bss->n_links; i++) {
+		if ((unsigned int) bss->links[i].freq == freq &&
+		    bss->links[i].link_id != -1)
+			return &bss->links[i];
+	}
+
+	return NULL;
+}
+
+
 static void mlme_event(struct i802_bss *bss,
 		       enum nl80211_commands cmd, struct nlattr *frame,
 		       struct nlattr *addr, struct nlattr *timed_out,
@@ -1623,7 +1641,8 @@
 	u16 stype = 0, auth_type = 0;
 	const u8 *data;
 	size_t len;
-	int link_id;
+	int link_id = -1;
+	struct i802_link *mld_link = NULL;
 
 	if (timed_out && addr) {
 		mlme_timeout_event(drv, cmd, addr);
@@ -1637,10 +1656,15 @@
 		return;
 	}
 
+	/* Determine the MLD link either by an explicitly provided link id or
+	 * finding a match based on the frequency. */
 	if (link)
-		link_id = nla_get_u8(link);
-	else
-		link_id = -1;
+		mld_link = nl80211_get_link(bss, nla_get_u8(link));
+	else if (freq)
+		mld_link = nl80211_get_mld_link_by_freq(bss, nla_get_u32(freq));
+
+	if (mld_link)
+		link_id = mld_link->link_id;
 
 	data = nla_data(frame);
 	len = nla_len(frame);
@@ -1683,7 +1707,9 @@
 		   os_memcmp(bss->addr, data + 4 + ETH_ALEN, ETH_ALEN) != 0 &&
 		   (is_zero_ether_addr(drv->first_bss->prev_addr) ||
 		    os_memcmp(bss->prev_addr, data + 4 + ETH_ALEN,
-			      ETH_ALEN) != 0)) {
+			      ETH_ALEN) != 0) &&
+		   (!mld_link ||
+		    os_memcmp(mld_link->addr, data + 4, ETH_ALEN) != 0)) {
 		wpa_printf(MSG_MSGDUMP, "nl80211: %s: Ignore MLME frame event "
 			   "for foreign address", bss->ifname);
 		return;
@@ -1880,6 +1906,8 @@
 				struct nlattr *tb[])
 {
 	union wpa_event_data data;
+	u8 *addr, *link_addr = NULL;
+	int assoc_link_id = -1;
 
 	if (!is_ap_interface(drv->nlmode))
 		return;
@@ -1887,9 +1915,37 @@
 		return;
 
 	os_memset(&data, 0, sizeof(data));
-	data.update_dh.peer = nla_data(tb[NL80211_ATTR_MAC]);
+	addr = nla_data(tb[NL80211_ATTR_MAC]);
+
+	if (bss->links[0].link_id == NL80211_DRV_LINK_ID_NA &&
+	    (tb[NL80211_ATTR_MLO_LINK_ID] ||
+	     tb[NL80211_ATTR_MLD_ADDR])) {
+		wpa_printf(MSG_ERROR,
+			   "nl80211: Link info not expected for DH event for non-MLD AP");
+		return;
+	}
+
+	if (tb[NL80211_ATTR_MLO_LINK_ID]) {
+		assoc_link_id = nla_get_u8(tb[NL80211_ATTR_MLO_LINK_ID]);
+		wpa_printf(MSG_DEBUG,
+			   "nl80211: STA assoc link ID %d in UPDATE_OWE_INFO event",
+			   assoc_link_id);
+
+		if (assoc_link_id != NL80211_DRV_LINK_ID_NA &&
+		    tb[NL80211_ATTR_MLD_ADDR]) {
+			link_addr = addr;
+			addr = nla_data(tb[NL80211_ATTR_MLD_ADDR]);
+			wpa_printf(MSG_DEBUG,
+				   "nl80211: STA assoc link addr " MACSTR,
+				   MAC2STR(link_addr));
+		}
+	}
+
+	data.update_dh.peer = addr;
 	data.update_dh.ie = nla_data(tb[NL80211_ATTR_IE]);
 	data.update_dh.ie_len = nla_len(tb[NL80211_ATTR_IE]);
+	data.update_dh.assoc_link_id = assoc_link_id;
+	data.update_dh.link_addr = link_addr;
 
 	wpa_printf(MSG_DEBUG, "nl80211: DH event - peer " MACSTR,
 		   MAC2STR(data.update_dh.peer));
@@ -2110,23 +2166,60 @@
 				      struct i802_bss *bss,
 				      struct nlattr **tb)
 {
-	u8 *addr;
+	u8 *peer_addr;
 	union wpa_event_data data;
 
 	if (tb[NL80211_ATTR_MAC] == NULL)
 		return;
-	addr = nla_data(tb[NL80211_ATTR_MAC]);
-	wpa_printf(MSG_DEBUG, "nl80211: New station " MACSTR, MAC2STR(addr));
+	peer_addr = nla_data(tb[NL80211_ATTR_MAC]);
+	wpa_printf(MSG_DEBUG, "nl80211: New station " MACSTR,
+		   MAC2STR(peer_addr));
 
 	if (is_ap_interface(drv->nlmode) && drv->device_ap_sme) {
-		u8 *ies = NULL;
-		size_t ies_len = 0;
-		if (tb[NL80211_ATTR_IE]) {
-			ies = nla_data(tb[NL80211_ATTR_IE]);
-			ies_len = nla_len(tb[NL80211_ATTR_IE]);
+		u8 *link_addr = NULL;
+		int assoc_link_id = -1;
+		u8 *req_ies = NULL, *resp_ies = NULL;
+		size_t req_ies_len = 0, resp_ies_len = 0;
+
+		if (bss->links[0].link_id == NL80211_DRV_LINK_ID_NA &&
+		    (tb[NL80211_ATTR_MLO_LINK_ID] ||
+		     tb[NL80211_ATTR_MLD_ADDR])) {
+			wpa_printf(MSG_ERROR,
+				   "nl80211: MLO info not expected for new station event for non-MLD AP");
+			return;
 		}
-		wpa_hexdump(MSG_DEBUG, "nl80211: Assoc Req IEs", ies, ies_len);
-		drv_event_assoc(bss->ctx, addr, ies, ies_len, 0);
+
+		if (tb[NL80211_ATTR_MLO_LINK_ID]) {
+			assoc_link_id =
+				nla_get_u8(tb[NL80211_ATTR_MLO_LINK_ID]);
+			wpa_printf(MSG_DEBUG, "nl80211: STA assoc link ID %d",
+				   assoc_link_id);
+			if (tb[NL80211_ATTR_MLD_ADDR]) {
+				peer_addr = nla_data(tb[NL80211_ATTR_MLD_ADDR]);
+				link_addr = nla_data(tb[NL80211_ATTR_MAC]);
+				wpa_printf(MSG_DEBUG,
+					   "nl80211: STA MLD address " MACSTR,
+					   MAC2STR(peer_addr));
+			}
+		}
+
+		if (tb[NL80211_ATTR_IE]) {
+			req_ies = nla_data(tb[NL80211_ATTR_IE]);
+			req_ies_len = nla_len(tb[NL80211_ATTR_IE]);
+			wpa_hexdump(MSG_DEBUG, "nl80211: Assoc Req IEs",
+				    req_ies, req_ies_len);
+		}
+
+		if (tb[NL80211_ATTR_RESP_IE]) {
+			resp_ies = nla_data(tb[NL80211_ATTR_RESP_IE]);
+			resp_ies_len = nla_len(tb[NL80211_ATTR_RESP_IE]);
+			wpa_hexdump(MSG_DEBUG, "nl80211: Assoc Resp IEs",
+				    resp_ies, resp_ies_len);
+		}
+
+		drv_event_assoc(bss->ctx, peer_addr, req_ies, req_ies_len,
+				resp_ies, resp_ies_len, link_addr,
+				assoc_link_id, 0);
 		return;
 	}
 
@@ -2134,7 +2227,7 @@
 		return;
 
 	os_memset(&data, 0, sizeof(data));
-	os_memcpy(data.ibss_rsn_start.peer, addr, ETH_ALEN);
+	os_memcpy(data.ibss_rsn_start.peer, peer_addr, ETH_ALEN);
 	wpa_supplicant_event(bss->ctx, EVENT_IBSS_RSN_START, &data);
 }
 
@@ -2626,15 +2719,19 @@
 	if (tb[QCA_WLAN_VENDOR_ATTR_ACS_CHWIDTH])
 		event.acs_selected_channels.ch_width =
 			nla_get_u16(tb[QCA_WLAN_VENDOR_ATTR_ACS_CHWIDTH]);
+	if (tb[QCA_WLAN_VENDOR_ATTR_ACS_PUNCTURE_BITMAP])
+		event.acs_selected_channels.puncture_bitmap =
+			nla_get_u16(tb[QCA_WLAN_VENDOR_ATTR_ACS_PUNCTURE_BITMAP]);
 	wpa_printf(MSG_INFO,
-		   "nl80211: ACS Results: PFreq: %d SFreq: %d BW: %d VHT0: %d VHT1: %d HW_MODE: %d EDMGCH: %d",
+		   "nl80211: ACS Results: PFreq: %d SFreq: %d BW: %d VHT0: %d VHT1: %d HW_MODE: %d EDMGCH: %d PUNCBITMAP: 0x%x",
 		   event.acs_selected_channels.pri_freq,
 		   event.acs_selected_channels.sec_freq,
 		   event.acs_selected_channels.ch_width,
 		   event.acs_selected_channels.vht_seg0_center_ch,
 		   event.acs_selected_channels.vht_seg1_center_ch,
 		   event.acs_selected_channels.hw_mode,
-		   event.acs_selected_channels.edmg_channel);
+		   event.acs_selected_channels.edmg_channel,
+		   event.acs_selected_channels.puncture_bitmap);
 
 	/* Ignore ACS channel list check for backwards compatibility */
 
@@ -3410,7 +3507,13 @@
 	}
 
 	addr = nla_data(tb[NL80211_ATTR_MAC]);
-	if (os_memcmp(addr, connected_addr, ETH_ALEN) != 0) {
+	if (is_ap_interface(drv->nlmode) && drv->device_ap_sme) {
+		event.port_authorized.sta_addr = addr;
+		wpa_printf(MSG_DEBUG,
+			   "nl80211: Port authorized for STA addr "  MACSTR,
+			MAC2STR(addr));
+	} else if (is_sta_interface(drv->nlmode) &&
+		   os_memcmp(addr, connected_addr, ETH_ALEN) != 0) {
 		wpa_printf(MSG_DEBUG,
 			   "nl80211: Ignore port authorized event for " MACSTR
 			   " (not the currently connected BSSID " MACSTR ")",
@@ -3567,6 +3670,10 @@
 	event.eapol_tx_status.data = frame + ETH_HLEN;
 	event.eapol_tx_status.data_len = len - ETH_HLEN;
 	event.eapol_tx_status.ack = ack != NULL;
+	event.eapol_tx_status.link_id =
+		nla_get_u64(cookie) == drv->eapol_tx_cookie ?
+		drv->eapol_tx_link_id : NL80211_DRV_LINK_ID_NA;
+
 	wpa_supplicant_event(drv->ctx, EVENT_EAPOL_TX_STATUS, &event);
 }
 
@@ -3621,7 +3728,7 @@
 
 #ifdef CONFIG_IEEE80211AX
 
-static void nl80211_obss_color_collision(struct wpa_driver_nl80211_data *drv,
+static void nl80211_obss_color_collision(struct i802_bss *bss,
 					 struct nlattr *tb[])
 {
 	union wpa_event_data data;
@@ -3635,37 +3742,34 @@
 
 	wpa_printf(MSG_DEBUG, "nl80211: BSS color collision - bitmap %08llx",
 		   (long long unsigned int) data.bss_color_collision.bitmap);
-	wpa_supplicant_event(drv->ctx, EVENT_BSS_COLOR_COLLISION, &data);
+	wpa_supplicant_event(bss->ctx, EVENT_BSS_COLOR_COLLISION, &data);
 }
 
 
-static void
-nl80211_color_change_announcement_started(struct wpa_driver_nl80211_data *drv)
+static void nl80211_color_change_announcement_started(struct i802_bss *bss)
 {
 	union wpa_event_data data = {};
 
 	wpa_printf(MSG_DEBUG, "nl80211: CCA started");
-	wpa_supplicant_event(drv->ctx, EVENT_CCA_STARTED_NOTIFY, &data);
+	wpa_supplicant_event(bss->ctx, EVENT_CCA_STARTED_NOTIFY, &data);
 }
 
 
-static void
-nl80211_color_change_announcement_aborted(struct wpa_driver_nl80211_data *drv)
+static void nl80211_color_change_announcement_aborted(struct i802_bss *bss)
 {
 	union wpa_event_data data = {};
 
 	wpa_printf(MSG_DEBUG, "nl80211: CCA aborted");
-	wpa_supplicant_event(drv->ctx, EVENT_CCA_ABORTED_NOTIFY, &data);
+	wpa_supplicant_event(bss->ctx, EVENT_CCA_ABORTED_NOTIFY, &data);
 }
 
 
-static void
-nl80211_color_change_announcement_completed(struct wpa_driver_nl80211_data *drv)
+static void nl80211_color_change_announcement_completed(struct i802_bss *bss)
 {
 	union wpa_event_data data = {};
 
 	wpa_printf(MSG_DEBUG, "nl80211: CCA completed");
-	wpa_supplicant_event(drv->ctx, EVENT_CCA_NOTIFY, &data);
+	wpa_supplicant_event(bss->ctx, EVENT_CCA_NOTIFY, &data);
 }
 
 #endif /* CONFIG_IEEE80211AX */
@@ -3925,16 +4029,16 @@
 		break;
 #ifdef CONFIG_IEEE80211AX
 	case NL80211_CMD_OBSS_COLOR_COLLISION:
-		nl80211_obss_color_collision(drv, tb);
+		nl80211_obss_color_collision(bss, tb);
 		break;
 	case NL80211_CMD_COLOR_CHANGE_STARTED:
-		nl80211_color_change_announcement_started(drv);
+		nl80211_color_change_announcement_started(bss);
 		break;
 	case NL80211_CMD_COLOR_CHANGE_ABORTED:
-		nl80211_color_change_announcement_aborted(drv);
+		nl80211_color_change_announcement_aborted(bss);
 		break;
 	case NL80211_CMD_COLOR_CHANGE_COMPLETED:
-		nl80211_color_change_announcement_completed(drv);
+		nl80211_color_change_announcement_completed(bss);
 		break;
 #endif /* CONFIG_IEEE80211AX */
 	default:
diff --git a/src/drivers/driver_nl80211_scan.c b/src/drivers/driver_nl80211_scan.c
index 4d33b14..4907e3c 100644
--- a/src/drivers/driver_nl80211_scan.c
+++ b/src/drivers/driver_nl80211_scan.c
@@ -41,7 +41,7 @@
 	struct nl80211_noise_info *info = arg;
 
 	if (info->count >= MAX_NL80211_NOISE_FREQS)
-		return NL_STOP;
+		return NL_SKIP;
 
 	nla_parse(tb, NL80211_ATTR_MAX, genlmsg_attrdata(gnlh, 0),
 		  genlmsg_attrlen(gnlh, 0), NULL);
@@ -315,6 +315,14 @@
 			NL80211_SCAN_FLAG_OCE_PROBE_REQ_DEFERRAL_SUPPRESSION;
 	}
 
+	if (params->min_probe_req_content) {
+		if (drv->capa.flags2 & WPA_DRIVER_FLAGS2_SCAN_MIN_PREQ)
+			scan_flags |= NL80211_SCAN_FLAG_MIN_PREQ_CONTENT;
+		else
+			wpa_printf(MSG_DEBUG,
+				   "nl80211: NL80211_SCAN_FLAG_MIN_PREQ_CONTENT not supported");
+	}
+
 	if (scan_flags &&
 	    nla_put_u32(msg, NL80211_ATTR_SCAN_FLAGS, scan_flags))
 		goto fail;
@@ -377,7 +385,15 @@
 	if (params->bssid) {
 		wpa_printf(MSG_DEBUG, "nl80211: Scan for a specific BSSID: "
 			   MACSTR, MAC2STR(params->bssid));
-		if (nla_put(msg, NL80211_ATTR_MAC, ETH_ALEN, params->bssid))
+		if (nla_put(msg, NL80211_ATTR_BSSID, ETH_ALEN, params->bssid))
+			goto fail;
+		/* NL80211_ATTR_MAC was used for this purpose initially and the
+		 * NL80211_ATTR_BSSID was added in 2016 when MAC address
+		 * randomization was added. For compatibility with older kernel
+		 * versions, add the NL80211_ATTR_MAC attribute as well when
+		 * the conflicting functionality is not in use. */
+		if (!params->mac_addr_rand &&
+		    nla_put(msg, NL80211_ATTR_MAC, ETH_ALEN, params->bssid))
 			goto fail;
 	}
 
diff --git a/src/drivers/driver_wired.c b/src/drivers/driver_wired.c
index c7537b7..03366b8 100644
--- a/src/drivers/driver_wired.c
+++ b/src/drivers/driver_wired.c
@@ -285,7 +285,7 @@
 
 static int wired_send_eapol(void *priv, const u8 *addr,
 			    const u8 *data, size_t data_len, int encrypt,
-			    const u8 *own_addr, u32 flags)
+			    const u8 *own_addr, u32 flags, int link_id)
 {
 	struct wpa_driver_wired_data *drv = priv;
 	struct ieee8023_hdr *hdr;
diff --git a/src/drivers/nl80211_copy.h b/src/drivers/nl80211_copy.h
index c59fec4..dced2c4 100644
--- a/src/drivers/nl80211_copy.h
+++ b/src/drivers/nl80211_copy.h
@@ -11,7 +11,7 @@
  * Copyright 2008 Jouni Malinen <jouni.malinen@atheros.com>
  * Copyright 2008 Colin McCabe <colin@cozybit.com>
  * Copyright 2015-2017	Intel Deutschland GmbH
- * Copyright (C) 2018-2022 Intel Corporation
+ * Copyright (C) 2018-2023 Intel Corporation
  *
  * Permission to use, copy, modify, and/or distribute this software for any
  * purpose with or without fee is hereby granted, provided that the above
@@ -167,7 +167,7 @@
  * following events occur.
  * a) Expiration of hardware timer whose expiration time is set to maximum
  * coalescing delay of matching coalesce rule.
- * b) Coalescing buffer in hardware reaches it's limit.
+ * b) Coalescing buffer in hardware reaches its limit.
  * c) Packet doesn't match any of the configured coalesce rules.
  *
  * User needs to configure following parameters for creating a coalesce
@@ -326,7 +326,7 @@
 /**
  * DOC: Multi-Link Operation
  *
- * In Multi-Link Operation, a connection between to MLDs utilizes multiple
+ * In Multi-Link Operation, a connection between two MLDs utilizes multiple
  * links. To use this in nl80211, various commands and responses now need
  * to or will include the new %NL80211_ATTR_MLO_LINKS attribute.
  * Additionally, various commands that need to operate on a specific link
@@ -335,6 +335,15 @@
  */
 
 /**
+ * DOC: OWE DH IE handling offload
+ *
+ * By setting @NL80211_EXT_FEATURE_OWE_OFFLOAD flag, drivers can indicate
+ * kernel/application space to avoid DH IE handling. When this flag is
+ * advertised, the driver/device will take care of DH IE inclusion and
+ * processing of peer DH IE to generate PMK.
+ */
+
+/**
  * enum nl80211_commands - supported nl80211 commands
  *
  * @NL80211_CMD_UNSPEC: unspecified command to catch errors
@@ -1309,6 +1318,11 @@
  *	The number of peers that HW timestamping can be enabled for concurrently
  *	is indicated by %NL80211_ATTR_MAX_HW_TIMESTAMP_PEERS.
  *
+ * @NL80211_CMD_LINKS_REMOVED: Notify userspace about the removal of STA MLD
+ *	setup links due to AP MLD removing the corresponding affiliated APs with
+ *	Multi-Link reconfiguration. %NL80211_ATTR_MLO_LINKS is used to provide
+ *	information about the removed STA MLD setup links.
+ *
  * @NL80211_CMD_MAX: highest used command number
  * @__NL80211_CMD_AFTER_LAST: internal use
  */
@@ -1562,6 +1576,8 @@
 
 	NL80211_CMD_SET_HW_TIMESTAMP,
 
+	NL80211_CMD_LINKS_REMOVED,
+
 	/* add new commands above here */
 
 	/* used to define NL80211_CMD_MAX below */
@@ -2683,11 +2699,13 @@
  *
  * @NL80211_ATTR_FILS_DISCOVERY: Optional parameter to configure FILS
  *	discovery. It is a nested attribute, see
- *	&enum nl80211_fils_discovery_attributes.
+ *	&enum nl80211_fils_discovery_attributes. Userspace should pass an empty
+ *	nested attribute to disable this feature and delete the templates.
  *
  * @NL80211_ATTR_UNSOL_BCAST_PROBE_RESP: Optional parameter to configure
  *	unsolicited broadcast probe response. It is a nested attribute, see
- *	&enum nl80211_unsol_bcast_probe_resp_attributes.
+ *	&enum nl80211_unsol_bcast_probe_resp_attributes. Userspace should pass an empty
+ *	nested attribute to disable this feature and delete the templates.
  *
  * @NL80211_ATTR_S1G_CAPABILITY: S1G Capability information element (from
  *	association request when used with NL80211_CMD_NEW_STATION)
@@ -2805,6 +2823,9 @@
  *	index. If the userspace includes more RNR elements than number of
  *	MBSSID elements then these will be added in every EMA beacon.
  *
+ * @NL80211_ATTR_MLO_LINK_DISABLED: Flag attribute indicating that the link is
+ *	disabled.
+ *
  * @NUM_NL80211_ATTR: total number of nl80211_attrs available
  * @NL80211_ATTR_MAX: highest attribute number currently defined
  * @__NL80211_ATTR_AFTER_LAST: internal use
@@ -3341,6 +3362,8 @@
 
 	NL80211_ATTR_EMA_RNR_ELEMS,
 
+	NL80211_ATTR_MLO_LINK_DISABLED,
+
 	/* add attributes here, update the policy in nl80211.c */
 
 	__NL80211_ATTR_AFTER_LAST,
@@ -3667,6 +3690,13 @@
  *	(u8, see &enum nl80211_eht_gi)
  * @NL80211_RATE_INFO_EHT_RU_ALLOC: EHT RU allocation, if not present then
  *	non-OFDMA was used (u8, see &enum nl80211_eht_ru_alloc)
+ * @NL80211_RATE_INFO_S1G_MCS: S1G MCS index (u8, 0-10)
+ * @NL80211_RATE_INFO_S1G_NSS: S1G NSS value (u8, 1-4)
+ * @NL80211_RATE_INFO_1_MHZ_WIDTH: 1 MHz S1G rate
+ * @NL80211_RATE_INFO_2_MHZ_WIDTH: 2 MHz S1G rate
+ * @NL80211_RATE_INFO_4_MHZ_WIDTH: 4 MHz S1G rate
+ * @NL80211_RATE_INFO_8_MHZ_WIDTH: 8 MHz S1G rate
+ * @NL80211_RATE_INFO_16_MHZ_WIDTH: 16 MHz S1G rate
  * @__NL80211_RATE_INFO_AFTER_LAST: internal use
  */
 enum nl80211_rate_info {
@@ -3693,6 +3723,13 @@
 	NL80211_RATE_INFO_EHT_NSS,
 	NL80211_RATE_INFO_EHT_GI,
 	NL80211_RATE_INFO_EHT_RU_ALLOC,
+	NL80211_RATE_INFO_S1G_MCS,
+	NL80211_RATE_INFO_S1G_NSS,
+	NL80211_RATE_INFO_1_MHZ_WIDTH,
+	NL80211_RATE_INFO_2_MHZ_WIDTH,
+	NL80211_RATE_INFO_4_MHZ_WIDTH,
+	NL80211_RATE_INFO_8_MHZ_WIDTH,
+	NL80211_RATE_INFO_16_MHZ_WIDTH,
 
 	/* keep last */
 	__NL80211_RATE_INFO_AFTER_LAST,
@@ -4187,6 +4224,8 @@
  *	as the primary or any of the secondary channels isn't possible
  * @NL80211_FREQUENCY_ATTR_NO_EHT: EHT operation is not allowed on this channel
  *	in current regulatory domain.
+ * @NL80211_FREQUENCY_ATTR_PSD: Power spectral density (in dBm) that
+ *	is allowed on this channel in current regulatory domain.
  * @NL80211_FREQUENCY_ATTR_MAX: highest frequency attribute number
  *	currently defined
  * @__NL80211_FREQUENCY_ATTR_AFTER_LAST: internal use
@@ -4225,6 +4264,7 @@
 	NL80211_FREQUENCY_ATTR_16MHZ,
 	NL80211_FREQUENCY_ATTR_NO_320MHZ,
 	NL80211_FREQUENCY_ATTR_NO_EHT,
+	NL80211_FREQUENCY_ATTR_PSD,
 
 	/* keep last */
 	__NL80211_FREQUENCY_ATTR_AFTER_LAST,
@@ -4325,6 +4365,8 @@
  * 	a given frequency range. The value is in mBm (100 * dBm).
  * @NL80211_ATTR_DFS_CAC_TIME: DFS CAC time in milliseconds.
  *	If not present or 0 default CAC time will be used.
+ * @NL80211_ATTR_POWER_RULE_PSD: power spectral density (in dBm).
+ *	This could be negative.
  * @NL80211_REG_RULE_ATTR_MAX: highest regulatory rule attribute number
  *	currently defined
  * @__NL80211_REG_RULE_ATTR_AFTER_LAST: internal use
@@ -4342,6 +4384,8 @@
 
 	NL80211_ATTR_DFS_CAC_TIME,
 
+	NL80211_ATTR_POWER_RULE_PSD,
+
 	/* keep last */
 	__NL80211_REG_RULE_ATTR_AFTER_LAST,
 	NL80211_REG_RULE_ATTR_MAX = __NL80211_REG_RULE_ATTR_AFTER_LAST - 1
@@ -4424,6 +4468,8 @@
  * @NL80211_RRF_NO_160MHZ: 160MHz operation not allowed
  * @NL80211_RRF_NO_HE: HE operation not allowed
  * @NL80211_RRF_NO_320MHZ: 320MHz operation not allowed
+ * @NL80211_RRF_NO_EHT: EHT operation not allowed
+ * @NL80211_RRF_PSD: Ruleset has power spectral density value
  */
 enum nl80211_reg_rule_flags {
 	NL80211_RRF_NO_OFDM		= 1<<0,
@@ -4443,6 +4489,8 @@
 	NL80211_RRF_NO_160MHZ		= 1<<16,
 	NL80211_RRF_NO_HE		= 1<<17,
 	NL80211_RRF_NO_320MHZ		= 1<<18,
+	NL80211_RRF_NO_EHT		= 1<<19,
+	NL80211_RRF_PSD			= 1<<20,
 };
 
 #define NL80211_RRF_PASSIVE_SCAN	NL80211_RRF_NO_IR
@@ -5010,7 +5058,7 @@
  *	elements from a Beacon frame (bin); not present if no Beacon frame has
  *	yet been received
  * @NL80211_BSS_CHAN_WIDTH: channel width of the control channel
- *	(u32, enum nl80211_bss_scan_width)
+ *	(u32, enum nl80211_bss_scan_width) - No longer used!
  * @NL80211_BSS_BEACON_TSF: TSF of the last received beacon (u64)
  *	(not present if no beacon frame has been received yet)
  * @NL80211_BSS_PRESP_DATA: the data in @NL80211_BSS_INFORMATION_ELEMENTS and
@@ -6372,6 +6420,12 @@
  *	in authentication and deauthentication frames sent to unassociated peer
  *	using @NL80211_CMD_FRAME.
  *
+ * @NL80211_EXT_FEATURE_OWE_OFFLOAD: Driver/Device wants to do OWE DH IE
+ *	handling in station mode.
+ *
+ * @NL80211_EXT_FEATURE_OWE_OFFLOAD_AP: Driver/Device wants to do OWE DH IE
+ *	handling in AP mode.
+ *
  * @NUM_NL80211_EXT_FEATURES: number of extended features.
  * @MAX_NL80211_EXT_FEATURES: highest extended feature index.
  */
@@ -6443,6 +6497,8 @@
 	NL80211_EXT_FEATURE_PUNCT,
 	NL80211_EXT_FEATURE_SECURE_NAN,
 	NL80211_EXT_FEATURE_AUTH_AND_DEAUTH_RANDOM_TA,
+	NL80211_EXT_FEATURE_OWE_OFFLOAD,
+	NL80211_EXT_FEATURE_OWE_OFFLOAD_AP,
 
 	/* add new features before the definition below */
 	NUM_NL80211_EXT_FEATURES,
@@ -7578,7 +7634,7 @@
  * @NL80211_FILS_DISCOVERY_ATTR_INT_MIN: Minimum packet interval (u32, TU).
  *	Allowed range: 0..10000 (TU = Time Unit)
  * @NL80211_FILS_DISCOVERY_ATTR_INT_MAX: Maximum packet interval (u32, TU).
- *	Allowed range: 0..10000 (TU = Time Unit)
+ *	Allowed range: 0..10000 (TU = Time Unit). If set to 0, the feature is disabled.
  * @NL80211_FILS_DISCOVERY_ATTR_TMPL: Template data for FILS discovery action
  *	frame including the headers.
  *
@@ -7611,7 +7667,8 @@
  *
  * @NL80211_UNSOL_BCAST_PROBE_RESP_ATTR_INT: Maximum packet interval (u32, TU).
  *	Allowed range: 0..20 (TU = Time Unit). IEEE P802.11ax/D6.0
- *	26.17.2.3.2 (AP behavior for fast passive scanning).
+ *	26.17.2.3.2 (AP behavior for fast passive scanning). If set to 0, the feature is
+ *	disabled.
  * @NL80211_UNSOL_BCAST_PROBE_RESP_ATTR_TMPL: Unsolicited broadcast probe response
  *	frame template (binary).
  *
diff --git a/src/eap_peer/eap.c b/src/eap_peer/eap.c
index ff7dc1e..4a09908 100644
--- a/src/eap_peer/eap.c
+++ b/src/eap_peer/eap.c
@@ -1773,6 +1773,10 @@
 	wpabuf_put_data(resp, identity, identity_len);
 	wpabuf_free(privacy_identity);
 
+	os_free(sm->identity);
+	sm->identity = os_memdup(identity, identity_len);
+	sm->identity_len = identity_len;
+
 	return resp;
 }
 
@@ -2248,9 +2252,15 @@
 	dl_list_init(&sm->erp_keys);
 
 	os_memset(&tlsconf, 0, sizeof(tlsconf));
+#ifndef CONFIG_OPENSC_ENGINE_PATH
 	tlsconf.opensc_engine_path = conf->opensc_engine_path;
+#endif /* CONFIG_OPENSC_ENGINE_PATH */
+#ifndef CONFIG_PKCS11_ENGINE_PATH
 	tlsconf.pkcs11_engine_path = conf->pkcs11_engine_path;
+#endif /* CONFIG_PKCS11_ENGINE_PATH */
+#ifndef CONFIG_PKCS11_MODULE_PATH
 	tlsconf.pkcs11_module_path = conf->pkcs11_module_path;
+#endif /* CONFIG_PKCS11_MODULE_PATH */
 	tlsconf.openssl_ciphers = conf->openssl_ciphers;
 #ifdef CONFIG_FIPS
 	tlsconf.fips_mode = 1;
@@ -2297,6 +2307,7 @@
 		tls_deinit(sm->ssl_ctx2);
 	tls_deinit(sm->ssl_ctx);
 	eap_peer_erp_free_keys(sm);
+	os_free(sm->identity);
 	os_free(sm);
 }
 
diff --git a/src/eap_peer/eap_aka.c b/src/eap_peer/eap_aka.c
index 49338cf..8c52582 100644
--- a/src/eap_peer/eap_aka.c
+++ b/src/eap_peer/eap_aka.c
@@ -41,8 +41,8 @@
 	size_t reauth_id_len;
 	int reauth;
 	unsigned int counter, counter_too_small;
-	u8 *last_eap_identity;
-	size_t last_eap_identity_len;
+	u8 *mk_identity;
+	size_t mk_identity_len;
 	enum {
 		CONTINUE, RESULT_SUCCESS, SUCCESS, FAILURE
 	} state;
@@ -140,6 +140,13 @@
 		}
 	}
 
+	if (sm->identity) {
+		/* Use the EAP-Response/Identity in MK derivation if AT_IDENTITY
+		 * is not used. */
+		data->mk_identity = os_memdup(sm->identity, sm->identity_len);
+		data->mk_identity_len = sm->identity_len;
+	}
+
 	return data;
 }
 
@@ -177,7 +184,7 @@
 	if (data) {
 		os_free(data->pseudonym);
 		os_free(data->reauth_id);
-		os_free(data->last_eap_identity);
+		os_free(data->mk_identity);
 		wpabuf_free(data->id_msgs);
 		os_free(data->network_name);
 		eap_aka_clear_keys(data, 0);
@@ -373,7 +380,6 @@
 
 #define CLEAR_PSEUDONYM	0x01
 #define CLEAR_REAUTH_ID	0x02
-#define CLEAR_EAP_ID	0x04
 
 static void eap_aka_clear_identities(struct eap_sm *sm,
 				     struct eap_aka_data *data, int id)
@@ -392,12 +398,6 @@
 		data->reauth_id = NULL;
 		data->reauth_id_len = 0;
 	}
-	if ((id & CLEAR_EAP_ID) && data->last_eap_identity) {
-		wpa_printf(MSG_DEBUG, "EAP-AKA: forgetting old eap_id");
-		os_free(data->last_eap_identity);
-		data->last_eap_identity = NULL;
-		data->last_eap_identity_len = 0;
-	}
 }
 
 
@@ -694,6 +694,8 @@
 	size_t identity_len = 0;
 	struct eap_sim_msg *msg;
 	struct wpabuf *enc_identity = NULL;
+	struct eap_peer_config *config = NULL;
+	bool use_imsi_identity = false;
 
 	data->reauth = 0;
 	if (id_req == ANY_ID && data->reauth_id) {
@@ -723,10 +725,13 @@
 						       data->pseudonym_len))
 				ids &= ~CLEAR_PSEUDONYM;
 			eap_aka_clear_identities(sm, data, ids);
+
+			config = eap_get_config(sm);
+			if (config && config->imsi_identity)
+				use_imsi_identity = true;
 		}
 #ifdef CRYPTO_RSA_OAEP_SHA256
 		if (identity && data->imsi_privacy_key) {
-			struct eap_peer_config *config;
 			const char *attr = NULL;
 
 			config = eap_get_config(sm);
@@ -742,13 +747,16 @@
 					data, id,
 					EAP_AKA_UNABLE_TO_PROCESS_PACKET);
 			}
+			/* Use the real identity, not the encrypted one, in MK
+			 * derivation. */
+			os_free(data->mk_identity);
+			data->mk_identity = os_memdup(identity, identity_len);
+			data->mk_identity_len = identity_len;
 			identity = wpabuf_head(enc_identity);
 			identity_len = wpabuf_len(enc_identity);
 		}
 #endif /* CRYPTO_RSA_OAEP_SHA256 */
 	}
-	if (id_req != NO_ID_REQ)
-		eap_aka_clear_identities(sm, data, CLEAR_EAP_ID);
 
 	wpa_printf(MSG_DEBUG, "Generating EAP-AKA Identity (id=%d)", id);
 	msg = eap_sim_msg_init(EAP_CODE_RESPONSE, id, data->eap_method,
@@ -759,6 +767,22 @@
 				  identity, identity_len);
 		eap_sim_msg_add(msg, EAP_SIM_AT_IDENTITY, identity_len,
 				identity, identity_len);
+		if (use_imsi_identity && config && config->imsi_identity) {
+			/* Use the IMSI identity override, i.e., the not
+			 * encrypted one, in MK derivation, when using
+			 * externally encrypted identity in configuration. */
+			os_free(data->mk_identity);
+			data->mk_identity = os_memdup(
+				config->imsi_identity,
+				config->imsi_identity_len);
+			data->mk_identity_len = config->imsi_identity_len;
+		} else if (!enc_identity) {
+			/* Use the last AT_IDENTITY value as the identity in
+			 * MK derivation. */
+			os_free(data->mk_identity);
+			data->mk_identity = os_memdup(identity, identity_len);
+			data->mk_identity_len = identity_len;
+		}
 	}
 	wpabuf_free(enc_identity);
 
@@ -1148,25 +1172,9 @@
 						 data->network_name_len);
 	}
 #endif /* EAP_AKA_PRIME */
-	if (data->last_eap_identity) {
-		identity = data->last_eap_identity;
-		identity_len = data->last_eap_identity_len;
-	} else if (data->pseudonym &&
-		   !eap_sim_anonymous_username(data->pseudonym,
-					       data->pseudonym_len)) {
-		identity = data->pseudonym;
-		identity_len = data->pseudonym_len;
-	} else {
-		struct eap_peer_config *config;
 
-		config = eap_get_config(sm);
-		if (config && config->imsi_identity) {
-			identity = config->imsi_identity;
-			identity_len = config->imsi_identity_len;
-		} else {
-			identity = eap_get_config_identity(sm, &identity_len);
-		}
-	}
+	identity = data->mk_identity;
+	identity_len = data->mk_identity_len;
 	wpa_hexdump_ascii(MSG_DEBUG, "EAP-AKA: Selected identity for MK "
 			  "derivation", identity, identity_len);
 	if (data->eap_method == EAP_TYPE_AKA_PRIME) {
@@ -1195,7 +1203,7 @@
 	 * other words, if no new identities are received, full
 	 * authentication will be used on next reauthentication (using
 	 * pseudonym identity or permanent identity). */
-	eap_aka_clear_identities(sm, data, CLEAR_REAUTH_ID | CLEAR_EAP_ID);
+	eap_aka_clear_identities(sm, data, CLEAR_REAUTH_ID);
 
 	if (attr->encr_data) {
 		u8 *decrypted;
@@ -1405,14 +1413,8 @@
 
 		/* Reply using Re-auth w/ AT_COUNTER_TOO_SMALL. The current
 		 * reauth_id must not be used to start a new reauthentication.
-		 * However, since it was used in the last EAP-Response-Identity
-		 * packet, it has to saved for the following fullauth to be
-		 * used in MK derivation. */
-		os_free(data->last_eap_identity);
-		data->last_eap_identity = data->reauth_id;
-		data->last_eap_identity_len = data->reauth_id_len;
-		data->reauth_id = NULL;
-		data->reauth_id_len = 0;
+		 */
+		eap_aka_clear_identities(sm, data, CLEAR_REAUTH_ID);
 
 		res = eap_aka_response_reauth(data, id, 1, eattr.nonce_s);
 		os_free(decrypted);
@@ -1437,7 +1439,7 @@
 					   data->nonce_s, data->mk,
 					   data->msk, data->emsk);
 	}
-	eap_aka_clear_identities(sm, data, CLEAR_REAUTH_ID | CLEAR_EAP_ID);
+	eap_aka_clear_identities(sm, data, CLEAR_REAUTH_ID);
 	eap_aka_learn_ids(sm, data, &eattr);
 
 	if (data->result_ind && attr->result_ind)
@@ -1453,8 +1455,7 @@
 	if (data->counter > EAP_AKA_MAX_FAST_REAUTHS) {
 		wpa_printf(MSG_DEBUG, "EAP-AKA: Maximum number of "
 			   "fast reauths performed - force fullauth");
-		eap_aka_clear_identities(sm, data,
-					 CLEAR_REAUTH_ID | CLEAR_EAP_ID);
+		eap_aka_clear_identities(sm, data, CLEAR_REAUTH_ID);
 	}
 	os_free(decrypted);
 	return eap_aka_response_reauth(data, id, 0, data->nonce_s);
@@ -1570,7 +1571,10 @@
 static void eap_aka_deinit_for_reauth(struct eap_sm *sm, void *priv)
 {
 	struct eap_aka_data *data = priv;
-	eap_aka_clear_identities(sm, data, CLEAR_EAP_ID);
+
+	os_free(data->mk_identity);
+	data->mk_identity = NULL;
+	data->mk_identity_len = 0;
 	data->prev_id = -1;
 	wpabuf_free(data->id_msgs);
 	data->id_msgs = NULL;
@@ -1583,6 +1587,15 @@
 static void * eap_aka_init_for_reauth(struct eap_sm *sm, void *priv)
 {
 	struct eap_aka_data *data = priv;
+
+	if (sm->identity) {
+		/* Use the EAP-Response/Identity in MK derivation if AT_IDENTITY
+		 * is not used. */
+		os_free(data->mk_identity);
+		data->mk_identity = os_memdup(sm->identity, sm->identity_len);
+		data->mk_identity_len = sm->identity_len;
+	}
+
 	data->num_id_req = 0;
 	data->num_notification = 0;
 	eap_aka_state(data, CONTINUE);
diff --git a/src/eap_peer/eap_i.h b/src/eap_peer/eap_i.h
index 3fe5f77..15d1cb7 100644
--- a/src/eap_peer/eap_i.h
+++ b/src/eap_peer/eap_i.h
@@ -390,6 +390,10 @@
 	unsigned int use_machine_cred:1;
 
 	struct dl_list erp_keys; /* struct eap_erp_key */
+
+	/* Identity used in EAP-Response/Identity */
+	u8 *identity;
+	size_t identity_len;
 };
 
 const u8 * eap_get_config_identity(struct eap_sm *sm, size_t *len);
diff --git a/src/eap_peer/eap_sim.c b/src/eap_peer/eap_sim.c
index 6f18ebf..1a5f81a 100644
--- a/src/eap_peer/eap_sim.c
+++ b/src/eap_peer/eap_sim.c
@@ -43,8 +43,8 @@
 	size_t reauth_id_len;
 	int reauth;
 	unsigned int counter, counter_too_small;
-	u8 *last_eap_identity;
-	size_t last_eap_identity_len;
+	u8 *mk_identity;
+	size_t mk_identity_len;
 	enum {
 		CONTINUE, START_DONE, RESULT_SUCCESS, SUCCESS, FAILURE
 	} state;
@@ -158,6 +158,13 @@
 		}
 	}
 
+	if (sm->identity) {
+		/* Use the EAP-Response/Identity in MK derivation if AT_IDENTITY
+		 * is not used. */
+		data->mk_identity = os_memdup(sm->identity, sm->identity_len);
+		data->mk_identity_len = sm->identity_len;
+	}
+
 	eap_sim_state(data, CONTINUE);
 
 	return data;
@@ -185,7 +192,7 @@
 		os_free(data->ver_list);
 		os_free(data->pseudonym);
 		os_free(data->reauth_id);
-		os_free(data->last_eap_identity);
+		os_free(data->mk_identity);
 		eap_sim_clear_keys(data, 0);
 #ifdef CRYPTO_RSA_OAEP_SHA256
 		crypto_rsa_key_free(data->imsi_privacy_key);
@@ -399,7 +406,6 @@
 
 #define CLEAR_PSEUDONYM	0x01
 #define CLEAR_REAUTH_ID	0x02
-#define CLEAR_EAP_ID	0x04
 
 static void eap_sim_clear_identities(struct eap_sm *sm,
 				     struct eap_sim_data *data, int id)
@@ -418,12 +424,6 @@
 		data->reauth_id = NULL;
 		data->reauth_id_len = 0;
 	}
-	if ((id & CLEAR_EAP_ID) && data->last_eap_identity) {
-		wpa_printf(MSG_DEBUG, "EAP-SIM: forgetting old eap_id");
-		os_free(data->last_eap_identity);
-		data->last_eap_identity = NULL;
-		data->last_eap_identity_len = 0;
-	}
 }
 
 
@@ -562,6 +562,8 @@
 	struct eap_sim_msg *msg;
 	struct wpabuf *resp;
 	struct wpabuf *enc_identity = NULL;
+	struct eap_peer_config *config = NULL;
+	bool use_imsi_identity = false;
 
 	data->reauth = 0;
 	if (id_req == ANY_ID && data->reauth_id) {
@@ -591,10 +593,13 @@
 						       data->pseudonym_len))
 				ids &= ~CLEAR_PSEUDONYM;
 			eap_sim_clear_identities(sm, data, ids);
+
+			config = eap_get_config(sm);
+			if (config && config->imsi_identity)
+				use_imsi_identity = true;
 		}
 #ifdef CRYPTO_RSA_OAEP_SHA256
 		if (identity && data->imsi_privacy_key) {
-			struct eap_peer_config *config;
 			const char *attr = NULL;
 
 			config = eap_get_config(sm);
@@ -610,13 +615,16 @@
 					data, id,
 					EAP_SIM_UNABLE_TO_PROCESS_PACKET);
 			}
+			/* Use the real identity, not the encrypted one, in MK
+			 * derivation. */
+			os_free(data->mk_identity);
+			data->mk_identity = os_memdup(identity, identity_len);
+			data->mk_identity_len = identity_len;
 			identity = wpabuf_head(enc_identity);
 			identity_len = wpabuf_len(enc_identity);
 		}
 #endif /* CRYPTO_RSA_OAEP_SHA256 */
 	}
-	if (id_req != NO_ID_REQ)
-		eap_sim_clear_identities(sm, data, CLEAR_EAP_ID);
 
 	wpa_printf(MSG_DEBUG, "Generating EAP-SIM Start (id=%d)", id);
 	msg = eap_sim_msg_init(EAP_CODE_RESPONSE, id,
@@ -626,6 +634,22 @@
 				  identity, identity_len);
 		eap_sim_msg_add(msg, EAP_SIM_AT_IDENTITY, identity_len,
 				identity, identity_len);
+		if (use_imsi_identity && config && config->imsi_identity) {
+			/* Use the IMSI identity override, i.e., the not
+			 * encrypted one, in MK derivation, when using
+			 * externally encrypted identity in configuration. */
+			os_free(data->mk_identity);
+			data->mk_identity = os_memdup(
+				config->imsi_identity,
+				config->imsi_identity_len);
+			data->mk_identity_len = config->imsi_identity_len;
+		} else if (!enc_identity) {
+			/* Use the last AT_IDENTITY value as the identity in
+			 * MK derivation. */
+			os_free(data->mk_identity);
+			data->mk_identity = os_memdup(identity, identity_len);
+			data->mk_identity_len = identity_len;
+		}
 	}
 	wpabuf_free(enc_identity);
 	if (!data->reauth) {
@@ -887,25 +911,9 @@
 		return eap_sim_client_error(data, id,
 					    EAP_SIM_UNABLE_TO_PROCESS_PACKET);
 	}
-	if (data->last_eap_identity) {
-		identity = data->last_eap_identity;
-		identity_len = data->last_eap_identity_len;
-	} else if (data->pseudonym &&
-		   !eap_sim_anonymous_username(data->pseudonym,
-					       data->pseudonym_len)) {
-		identity = data->pseudonym;
-		identity_len = data->pseudonym_len;
-	} else {
-		struct eap_peer_config *config;
 
-		config = eap_get_config(sm);
-		if (config && config->imsi_identity) {
-			identity = config->imsi_identity;
-			identity_len = config->imsi_identity_len;
-		} else {
-			identity = eap_get_config_identity(sm, &identity_len);
-		}
-	}
+	identity = data->mk_identity;
+	identity_len = data->mk_identity_len;
 	wpa_hexdump_ascii(MSG_DEBUG, "EAP-SIM: Selected identity for MK "
 			  "derivation", identity, identity_len);
 	eap_sim_derive_mk(identity, identity_len, data->nonce_mt,
@@ -931,7 +939,7 @@
 	 * other words, if no new reauth identity is received, full
 	 * authentication will be used on next reauthentication (using
 	 * pseudonym identity or permanent identity). */
-	eap_sim_clear_identities(sm, data, CLEAR_REAUTH_ID | CLEAR_EAP_ID);
+	eap_sim_clear_identities(sm, data, CLEAR_REAUTH_ID);
 
 	if (attr->encr_data) {
 		u8 *decrypted;
@@ -1141,14 +1149,8 @@
 
 		/* Reply using Re-auth w/ AT_COUNTER_TOO_SMALL. The current
 		 * reauth_id must not be used to start a new reauthentication.
-		 * However, since it was used in the last EAP-Response-Identity
-		 * packet, it has to saved for the following fullauth to be
-		 * used in MK derivation. */
-		os_free(data->last_eap_identity);
-		data->last_eap_identity = data->reauth_id;
-		data->last_eap_identity_len = data->reauth_id_len;
-		data->reauth_id = NULL;
-		data->reauth_id_len = 0;
+		 */
+		eap_sim_clear_identities(sm, data, CLEAR_REAUTH_ID);
 
 		res = eap_sim_response_reauth(data, id, 1, eattr.nonce_s);
 		os_free(decrypted);
@@ -1165,7 +1167,7 @@
 				   data->reauth_id, data->reauth_id_len,
 				   data->nonce_s, data->mk, data->msk,
 				   data->emsk);
-	eap_sim_clear_identities(sm, data, CLEAR_REAUTH_ID | CLEAR_EAP_ID);
+	eap_sim_clear_identities(sm, data, CLEAR_REAUTH_ID);
 	eap_sim_learn_ids(sm, data, &eattr);
 
 	if (data->result_ind && attr->result_ind)
@@ -1181,8 +1183,7 @@
 	if (data->counter > EAP_SIM_MAX_FAST_REAUTHS) {
 		wpa_printf(MSG_DEBUG, "EAP-SIM: Maximum number of "
 			   "fast reauths performed - force fullauth");
-		eap_sim_clear_identities(sm, data,
-					 CLEAR_REAUTH_ID | CLEAR_EAP_ID);
+		eap_sim_clear_identities(sm, data, CLEAR_REAUTH_ID);
 	}
 	os_free(decrypted);
 	return eap_sim_response_reauth(data, id, 0, data->nonce_s);
@@ -1291,7 +1292,10 @@
 static void eap_sim_deinit_for_reauth(struct eap_sm *sm, void *priv)
 {
 	struct eap_sim_data *data = priv;
-	eap_sim_clear_identities(sm, data, CLEAR_EAP_ID);
+
+	os_free(data->mk_identity);
+	data->mk_identity = NULL;
+	data->mk_identity_len = 0;
 	data->use_result_ind = 0;
 	eap_sim_clear_keys(data, 1);
 }
@@ -1306,6 +1310,15 @@
 		eap_sim_deinit(sm, data);
 		return NULL;
 	}
+
+	if (sm->identity) {
+		/* Use the EAP-Response/Identity in MK derivation if AT_IDENTITY
+		 * is not used. */
+		os_free(data->mk_identity);
+		data->mk_identity = os_memdup(sm->identity, sm->identity_len);
+		data->mk_identity_len = sm->identity_len;
+	}
+
 	data->num_id_req = 0;
 	data->num_notification = 0;
 	eap_sim_state(data, CONTINUE);
diff --git a/src/eap_server/eap.h b/src/eap_server/eap.h
index 3696e1d..d965a25 100644
--- a/src/eap_server/eap.h
+++ b/src/eap_server/eap.h
@@ -220,6 +220,10 @@
 	int eap_sim_aka_result_ind;
 	int eap_sim_id;
 
+	/* Maximum number of fast re-authentications allowed after each full
+	 * EAP-SIM/AKA authentication. */
+	int eap_sim_aka_fast_reauth_limit;
+
 	/**
 	 * tnc - Trusted Network Connect (TNC)
 	 *
diff --git a/src/eap_server/eap_i.h b/src/eap_server/eap_i.h
index 1c59bb0..10affa4 100644
--- a/src/eap_server/eap_i.h
+++ b/src/eap_server/eap_i.h
@@ -160,6 +160,7 @@
 	size_t identity_len;
 	char *serial_num;
 	char imsi[20];
+	char sim_aka_permanent[20];
 	/* Whether Phase 2 method should validate identity match */
 	int require_identity_match;
 	int lastId; /* Identifier used in the last EAP-Packet */
diff --git a/src/eap_server/eap_server_aka.c b/src/eap_server/eap_server_aka.c
index 5fb19e9..880ffa3 100644
--- a/src/eap_server/eap_server_aka.c
+++ b/src/eap_server/eap_server_aka.c
@@ -110,7 +110,29 @@
 		return 0;
 	}
 
-	wpa_printf(MSG_DEBUG, "EAP-AKA: Using fast re-authentication");
+	if (data->reauth->counter > sm->cfg->eap_sim_aka_fast_reauth_limit) {
+		wpa_printf(MSG_DEBUG,
+			   "EAP-AKA: Too many fast re-authentication attemps - fall back to full authentication");
+		if (sm->cfg->eap_sim_id & 0x04) {
+			wpa_printf(MSG_DEBUG,
+				   "EAP-AKA: Permanent identity recognized - skip AKA-Identity exchange");
+			os_strlcpy(data->permanent, data->reauth->permanent,
+				   sizeof(data->permanent));
+			os_strlcpy(sm->sim_aka_permanent,
+				   data->reauth->permanent,
+				   sizeof(sm->sim_aka_permanent));
+			eap_sim_db_remove_reauth(sm->cfg->eap_sim_db_priv,
+						 data->reauth);
+			data->reauth = NULL;
+			eap_aka_fullauth(sm, data);
+			return 1;
+		}
+		return 0;
+	}
+
+	wpa_printf(MSG_DEBUG,
+		   "EAP-AKA: Using fast re-authentication (counter=%d)",
+		   data->reauth->counter);
 	os_strlcpy(data->permanent, data->reauth->permanent,
 		   sizeof(data->permanent));
 	data->counter = data->reauth->counter;
@@ -134,10 +156,17 @@
 				   struct eap_aka_data *data)
 {
 	char *username;
+	const u8 *identity = sm->identity;
+	size_t identity_len = sm->identity_len;
+
+	if (sm->sim_aka_permanent[0]) {
+		identity = (const u8 *) sm->sim_aka_permanent;
+		identity_len = os_strlen(sm->sim_aka_permanent);
+	}
 
 	/* Check if we already know the identity from EAP-Response/Identity */
 
-	username = sim_get_username(sm->identity, sm->identity_len);
+	username = sim_get_username(identity, identity_len);
 	if (username == NULL)
 		return;
 
@@ -150,6 +179,16 @@
 		return;
 	}
 
+	if (sm->sim_aka_permanent[0] && data->state == IDENTITY) {
+		/* Skip AKA/Identity exchange since the permanent identity
+		 * was recognized. */
+		os_free(username);
+		os_strlcpy(data->permanent, sm->sim_aka_permanent,
+			   sizeof(data->permanent));
+		eap_aka_fullauth(sm, data);
+		return;
+	}
+
 	if ((data->eap_method == EAP_TYPE_AKA_PRIME &&
 	     username[0] == EAP_AKA_PRIME_PSEUDONYM_PREFIX) ||
 	    (data->eap_method == EAP_TYPE_AKA &&
diff --git a/src/eap_server/eap_server_sim.c b/src/eap_server/eap_server_sim.c
index 1bcf26c..e418c07 100644
--- a/src/eap_server/eap_server_sim.c
+++ b/src/eap_server/eap_server_sim.c
@@ -106,12 +106,28 @@
 {
 	struct eap_sim_msg *msg;
 	u8 ver[2];
+	bool id_req = true;
 
 	wpa_printf(MSG_DEBUG, "EAP-SIM: Generating Start");
 	msg = eap_sim_msg_init(EAP_CODE_REQUEST, id, EAP_TYPE_SIM,
 			       EAP_SIM_SUBTYPE_START);
 	data->start_round++;
-	if (data->start_round == 1) {
+
+	if (data->start_round == 1 && (sm->cfg->eap_sim_id & 0x04)) {
+		char *username;
+
+		username = sim_get_username(sm->identity, sm->identity_len);
+		if (username && username[0] == EAP_SIM_REAUTH_ID_PREFIX &&
+		    eap_sim_db_get_reauth_entry(sm->cfg->eap_sim_db_priv,
+						username))
+			id_req = false;
+
+		os_free(username);
+	}
+
+	if (!id_req) {
+		wpa_printf(MSG_DEBUG, "   No identity request");
+	} else if (data->start_round == 1) {
 		/*
 		 * RFC 4186, Chap. 4.2.4 recommends that identity from EAP is
 		 * ignored and the SIM/Start is used to request the identity.
@@ -434,6 +450,7 @@
 				  struct wpabuf *respData,
 				  struct eap_sim_attrs *attr)
 {
+	const u8 *identity;
 	size_t identity_len;
 	u8 ver_list[2];
 	u8 *new_identity;
@@ -449,9 +466,13 @@
 		goto skip_id_update;
 	}
 
+	if ((sm->cfg->eap_sim_id & 0x04) &&
+	    (!attr->identity || attr->identity_len == 0))
+		goto skip_id_attr;
+
 	/*
-	 * We always request identity in SIM/Start, so the peer is required to
-	 * have replied with one.
+	 * Unless explicitly configured otherwise, we always request identity
+	 * in SIM/Start, so the peer is required to have replied with one.
 	 */
 	if (!attr->identity || attr->identity_len == 0) {
 		wpa_printf(MSG_DEBUG, "EAP-SIM: Peer did not provide any "
@@ -467,9 +488,17 @@
 	os_memcpy(sm->identity, attr->identity, attr->identity_len);
 	sm->identity_len = attr->identity_len;
 
+skip_id_attr:
+	if (sm->sim_aka_permanent[0]) {
+		identity = (const u8 *) sm->sim_aka_permanent;
+		identity_len = os_strlen(sm->sim_aka_permanent);
+	} else {
+		identity = sm->identity;
+		identity_len = sm->identity_len;
+	}
 	wpa_hexdump_ascii(MSG_DEBUG, "EAP-SIM: Identity",
-			  sm->identity, sm->identity_len);
-	username = sim_get_username(sm->identity, sm->identity_len);
+			  identity, identity_len);
+	username = sim_get_username(identity, identity_len);
 	if (username == NULL)
 		goto failed;
 
@@ -485,7 +514,30 @@
 			/* Remain in START state for another round */
 			return;
 		}
-		wpa_printf(MSG_DEBUG, "EAP-SIM: Using fast re-authentication");
+
+		if (data->reauth->counter >
+		    sm->cfg->eap_sim_aka_fast_reauth_limit &&
+		    (sm->cfg->eap_sim_id & 0x04)) {
+			wpa_printf(MSG_DEBUG,
+				   "EAP-SIM: Too many fast re-authentication attemps - fall back to full authentication");
+			wpa_printf(MSG_DEBUG,
+				   "EAP-SIM: Permanent identity recognized - skip new Identity query");
+			os_strlcpy(data->permanent,
+				   data->reauth->permanent,
+				   sizeof(data->permanent));
+			os_strlcpy(sm->sim_aka_permanent,
+				   data->reauth->permanent,
+				   sizeof(sm->sim_aka_permanent));
+			eap_sim_db_remove_reauth(
+				sm->cfg->eap_sim_db_priv,
+				data->reauth);
+			data->reauth = NULL;
+			goto skip_id_update;
+		}
+
+		wpa_printf(MSG_DEBUG,
+			   "EAP-SIM: Using fast re-authentication (counter=%d)",
+			   data->reauth->counter);
 		os_strlcpy(data->permanent, data->reauth->permanent,
 			   sizeof(data->permanent));
 		data->counter = data->reauth->counter;
diff --git a/src/eapol_supp/eapol_supp_sm.c b/src/eapol_supp/eapol_supp_sm.c
index 334ce6c..f033233 100644
--- a/src/eapol_supp/eapol_supp_sm.c
+++ b/src/eapol_supp/eapol_supp_sm.c
@@ -2180,9 +2180,15 @@
 	sm->authPeriod = 30;
 
 	os_memset(&conf, 0, sizeof(conf));
+#ifndef CONFIG_OPENSC_ENGINE_PATH
 	conf.opensc_engine_path = ctx->opensc_engine_path;
+#endif /* CONFIG_OPENSC_ENGINE_PATH */
+#ifndef CONFIG_PKCS11_ENGINE_PATH
 	conf.pkcs11_engine_path = ctx->pkcs11_engine_path;
+#endif /* CONFIG_PKCS11_ENGINE_PATH */
+#ifndef CONFIG_PKCS11_MODULE_PATH
 	conf.pkcs11_module_path = ctx->pkcs11_module_path;
+#endif /* CONFIG_PKCS11_MODULE_PATH */
 	conf.openssl_ciphers = ctx->openssl_ciphers;
 	conf.wps = ctx->wps;
 	conf.cert_in_cb = ctx->cert_in_cb;
diff --git a/src/eapol_supp/eapol_supp_sm.h b/src/eapol_supp/eapol_supp_sm.h
index fe34ec9..b229426 100644
--- a/src/eapol_supp/eapol_supp_sm.h
+++ b/src/eapol_supp/eapol_supp_sm.h
@@ -188,6 +188,7 @@
 	 */
 	void (*aborted_cached)(void *ctx);
 
+#ifndef CONFIG_OPENSC_ENGINE_PATH
 	/**
 	 * opensc_engine_path - Path to the OpenSSL engine for opensc
 	 *
@@ -195,7 +196,9 @@
 	 * engine (engine_opensc.so); if %NULL, this engine is not loaded.
 	 */
 	const char *opensc_engine_path;
+#endif /* CONFIG_OPENSC_ENGINE_PATH */
 
+#ifndef CONFIG_PKCS11_ENGINE_PATH
 	/**
 	 * pkcs11_engine_path - Path to the OpenSSL engine for PKCS#11
 	 *
@@ -203,7 +206,9 @@
 	 * engine (engine_pkcs11.so); if %NULL, this engine is not loaded.
 	 */
 	const char *pkcs11_engine_path;
+#endif /* CONFIG_PKCS11_ENGINE_PATH */
 
+#ifndef CONFIG_PKCS11_MODULE_PATH
 	/**
 	 * pkcs11_module_path - Path to the OpenSSL OpenSC/PKCS#11 module
 	 *
@@ -212,6 +217,7 @@
 	 * module is not loaded.
 	 */
 	const char *pkcs11_module_path;
+#endif /* CONFIG_PKCS11_MODULE_PATH */
 
 	/**
 	 * openssl_ciphers - OpenSSL cipher string
diff --git a/src/l2_packet/l2_packet_freebsd.c b/src/l2_packet/l2_packet_freebsd.c
index 60de9fe..3f0b299 100644
--- a/src/l2_packet/l2_packet_freebsd.c
+++ b/src/l2_packet/l2_packet_freebsd.c
@@ -20,6 +20,7 @@
 #include <sys/sysctl.h>
 #endif /* __sun__ */
 
+#include <net/ethernet.h>
 #include <net/if.h>
 #include <net/if_dl.h>
 #include <net/route.h>
@@ -94,6 +95,13 @@
 	} else {
 		buf = (unsigned char *) (ethhdr + 1);
 		len = hdr.caplen - sizeof(*ethhdr);
+
+		/* Handle IEEE 802.1Q encapsulated frames */
+		if (len >= ETHER_VLAN_ENCAP_LEN &&
+		    ethhdr->h_proto == htons(ETH_P_8021Q)) {
+			buf += ETHER_VLAN_ENCAP_LEN;
+			len -= ETHER_VLAN_ENCAP_LEN;
+		}
 	}
 	l2->rx_callback(l2->rx_callback_ctx, ethhdr->h_source, buf, len);
 }
@@ -122,10 +130,10 @@
 	os_snprintf(pcap_filter, sizeof(pcap_filter),
 		    "not ether src " MACSTR " and "
 		    "( ether dst " MACSTR " or ether dst " MACSTR " ) and "
-		    "ether proto 0x%x",
+		    "( ether proto 0x%x or ( vlan 0 and ether proto 0x%x ) )",
 		    MAC2STR(l2->own_addr), /* do not receive own packets */
 		    MAC2STR(l2->own_addr), MAC2STR(pae_group_addr),
-		    protocol);
+		    protocol, protocol);
 	if (pcap_compile(l2->pcap, &pcap_fp, pcap_filter, 1, pcap_netp) < 0) {
 		fprintf(stderr, "pcap_compile: %s\n", pcap_geterr(l2->pcap));
 		return -1;
diff --git a/src/p2p/p2p.c b/src/p2p/p2p.c
index a1fe121..267399d 100644
--- a/src/p2p/p2p.c
+++ b/src/p2p/p2p.c
@@ -1502,11 +1502,12 @@
 	} else {
 		/* Select any random available channel from the first available
 		 * operating class */
-		p2p_channel_select(&p2p->cfg->channels, NULL,
-				   &p2p->op_reg_class,
-				   &p2p->op_channel);
-		p2p_dbg(p2p, "Select random available channel %d from operating class %d as operating channel preference",
-			p2p->op_channel, p2p->op_reg_class);
+		if (p2p_channel_select(&p2p->cfg->channels, NULL,
+				       &p2p->op_reg_class,
+				       &p2p->op_channel) == 0)
+			p2p_dbg(p2p,
+				"Select random available channel %d from operating class %d as operating channel preference",
+				p2p->op_channel, p2p->op_reg_class);
 	}
 
 	p2p_copy_channels(&p2p->channels, &p2p->cfg->channels, p2p->allow_6ghz);
diff --git a/src/p2p/p2p_parse.c b/src/p2p/p2p_parse.c
index 5d2299c..07d6ca0 100644
--- a/src/p2p/p2p_parse.c
+++ b/src/p2p/p2p_parse.c
@@ -93,6 +93,12 @@
 			return -1;
 		}
 		msg->listen_channel = data;
+		if (has_ctrl_char(data, 2)) {
+			wpa_printf(MSG_DEBUG,
+				   "P2P: * Listen Channel: Country(binary) %02x %02x (0x%02x) Regulatory Class %d Channel Number %d",
+				   data[0], data[1], data[2], data[3], data[4]);
+			break;
+		}
 		wpa_printf(MSG_DEBUG, "P2P: * Listen Channel: "
 			   "Country %c%c(0x%02x) Regulatory "
 			   "Class %d Channel Number %d", data[0], data[1],
@@ -110,6 +116,12 @@
 			return -1;
 		}
 		msg->operating_channel = data;
+		if (has_ctrl_char(data, 2)) {
+			wpa_printf(MSG_DEBUG,
+				   "P2P: * Operating Channel: Country(binary) %02x %02x (0x%02x) Regulatory Class %d Channel Number %d",
+				   data[0], data[1], data[2], data[3], data[4]);
+			break;
+		}
 		wpa_printf(MSG_DEBUG, "P2P: * Operating Channel: "
 			   "Country %c%c(0x%02x) Regulatory "
 			   "Class %d Channel Number %d", data[0], data[1],
@@ -123,8 +135,15 @@
 		}
 		msg->channel_list = data;
 		msg->channel_list_len = len;
-		wpa_printf(MSG_DEBUG, "P2P: * Channel List: Country String "
-			   "'%c%c(0x%02x)'", data[0], data[1], data[2]);
+		if (has_ctrl_char(data, 2)) {
+			wpa_printf(MSG_DEBUG,
+				   "P2P: * Channel List: Country String (binary) %02x %02x (0x%02x)",
+				   data[0], data[1], data[2]);
+		} else {
+			wpa_printf(MSG_DEBUG,
+				   "P2P: * Channel List: Country String '%c%c(0x%02x)'",
+				   data[0], data[1], data[2]);
+		}
 		wpa_hexdump(MSG_MSGDUMP, "P2P: Channel List",
 			    msg->channel_list, msg->channel_list_len);
 		break;
@@ -526,7 +545,9 @@
 {
 	struct ieee802_11_elems elems;
 
-	ieee802_11_parse_elems(data, len, &elems, 0);
+	if (ieee802_11_parse_elems(data, len, &elems, 0) == ParseFailed)
+		return -1;
+
 	if (elems.ds_params)
 		msg->ds_params = elems.ds_params;
 	if (elems.ssid)
diff --git a/src/pae/aidl/aidl_psk.cpp b/src/pae/aidl/aidl_psk.cpp
index 67afef0..53f4f4f 100644
--- a/src/pae/aidl/aidl_psk.cpp
+++ b/src/pae/aidl/aidl_psk.cpp
@@ -74,6 +74,7 @@
 	if (pskPlugin == NULL)
 		return -ENODEV;
 
+        n++;
 	n = n * 8;
 	if (n < 8)
 		return -ENODEV;
diff --git a/src/pae/ieee802_1x_kay.c b/src/pae/ieee802_1x_kay.c
index 741b093..f75fd8f 100644
--- a/src/pae/ieee802_1x_kay.c
+++ b/src/pae/ieee802_1x_kay.c
@@ -1856,6 +1856,18 @@
 	kay->rcvd_keys++;
 	participant->to_use_sak = true;
 
+	/*
+	 * The key server may not include dist sak and use sak in one packet.
+	 * Meanwhile, after dist sak, the current participant (non-key server)
+	 * will install SC or SA(s) after decoding the dist sak which may take
+	 * few seconds in real physical platforms. Meanwhile, the peer expire
+	 * time is always initialized at adding the key server to peer list.
+	 * The gap between adding the key server to peer list and processing
+	 * next use sak packet may exceed the threshold of MKA_LIFE_TIME (6 s).
+	 * It will cause an unexpected cleanup (delete SC and SA(s)), so,
+	 * update the expire timeout at dist sak also. */
+	peer->expire = time(NULL) + MKA_LIFE_TIME / 1000;
+
 	return 0;
 }
 
@@ -2584,6 +2596,7 @@
 	struct ieee802_1x_kay_peer *peer, *pre_peer;
 	struct os_reltime now;
 	bool lp_changed;
+	bool key_server_removed;
 	struct receive_sc *rxsc, *pre_rxsc;
 	struct transmit_sa *txsa, *pre_txsa;
 
@@ -2610,6 +2623,7 @@
 	}
 
 	lp_changed = false;
+	key_server_removed = false;
 	dl_list_for_each_safe(peer, pre_peer, &participant->live_peers,
 			      struct ieee802_1x_kay_peer, list) {
 		if (now.sec > peer->expire) {
@@ -2625,12 +2639,32 @@
 						participant, rxsc);
 				}
 			}
+			key_server_removed |= peer->is_key_server;
 			dl_list_del(&peer->list);
 			os_free(peer);
 			lp_changed = true;
 		}
 	}
 
+	/* The key server may be removed due to the ingress packets delay.
+	 * In this situation, the endpoint of the key server may not be aware
+	 * of this participant who has removed the key server from the peer
+	 * list. Because the egress traffic is normal, the key server will not
+	 * remove this participant from the peer list of the key server. So in
+	 * the next MKA message, the key server will not dispatch a new SAK to
+	 * this participant. And this participant cannot be aware that that is
+	 * a new round of communication so it will not update its MI at
+	 * re-adding the key server to its peer list. So we need to update MI
+	 * to avoid the failure of the re-establishment MKA session. */
+	if (key_server_removed) {
+		if (!reset_participant_mi(participant))
+			wpa_printf(MSG_WARNING,
+				   "KaY: Could not update mi on key server removal");
+		else
+			wpa_printf(MSG_DEBUG,
+				   "KaY: Update mi on key server removal");
+	}
+
 	if (lp_changed) {
 		if (dl_list_empty(&participant->live_peers)) {
 			participant->advised_desired = false;
diff --git a/src/pasn/pasn_responder.c b/src/pasn/pasn_responder.c
index 78a9dd7..47be403 100644
--- a/src/pasn/pasn_responder.c
+++ b/src/pasn/pasn_responder.c
@@ -335,6 +335,8 @@
 		}
 	}
 
+	pasn->pmk_len = pmk_len;
+	os_memcpy(pasn->pmk, pmk, pmk_len);
 	ret = pasn_pmk_to_ptk(pmk, pmk_len, peer_addr, own_addr,
 			      wpabuf_head(secret), wpabuf_len(secret),
 			      &pasn->ptk, pasn->akmp,
diff --git a/src/rsn_supp/pmksa_cache.c b/src/rsn_supp/pmksa_cache.c
index eb434fa..b2c4809 100644
--- a/src/rsn_supp/pmksa_cache.c
+++ b/src/rsn_supp/pmksa_cache.c
@@ -134,6 +134,23 @@
 	if (!pmksa->sm)
 		return;
 
+	if (pmksa->sm->driver_bss_selection) {
+		struct rsn_pmksa_cache_entry *entry;
+
+		entry = pmksa->sm->cur_pmksa ?
+			pmksa->sm->cur_pmksa :
+			pmksa_cache_get(pmksa, pmksa->sm->bssid, NULL, NULL,
+					NULL, 0);
+		if (entry && wpa_key_mgmt_sae(entry->akmp)) {
+			wpa_printf(MSG_DEBUG,
+				   "RSN: remove reauth threshold passed PMKSA from the driver for SAE");
+			entry->sae_reauth_scheduled = true;
+			wpa_sm_remove_pmkid(pmksa->sm, entry->network_ctx,
+					    entry->aa, entry->pmkid, NULL);
+			return;
+		}
+	}
+
 	pmksa->sm->cur_pmksa = NULL;
 	eapol_sm_request_reauth(pmksa->sm->eapol);
 }
@@ -180,7 +197,10 @@
 
 	entry = pmksa->sm->cur_pmksa ? pmksa->sm->cur_pmksa :
 		pmksa_cache_get(pmksa, pmksa->sm->bssid, NULL, NULL, NULL, 0);
-	if (entry && !wpa_key_mgmt_sae(entry->akmp)) {
+	if (entry &&
+	    (!wpa_key_mgmt_sae(entry->akmp) ||
+	     (pmksa->sm->driver_bss_selection &&
+	      !entry->sae_reauth_scheduled))) {
 		sec = pmksa->pmksa->reauth_time - now.sec;
 		if (sec < 0)
 			sec = 0;
@@ -224,6 +244,9 @@
 	if (pmk_len > PMK_LEN_MAX)
 		return NULL;
 
+	if (kck_len > WPA_KCK_MAX_LEN)
+		return NULL;
+
 	entry = os_zalloc(sizeof(*entry));
 	if (entry == NULL)
 		return NULL;
@@ -232,11 +255,17 @@
 	if (pmkid) {
  		os_memcpy(entry->pmkid, pmkid, PMKID_LEN);
 	} else if (akmp == WPA_KEY_MGMT_IEEE8021X_SUITE_B_192) {
-		if (kck)
+		if (kck) {
 			rsn_pmkid_suite_b_192(kck, kck_len, aa, spa, entry->pmkid);
+			os_memcpy(entry->kck, kck, kck_len);
+			entry->kck_len = kck_len;
+		}
 	} else if (wpa_key_mgmt_suite_b(akmp)) {
-		if (kck)
+		if (kck) {
 			rsn_pmkid_suite_b(kck, kck_len, aa, spa, entry->pmkid);
+			os_memcpy(entry->kck, kck, kck_len);
+			entry->kck_len = kck_len;
+		}
 	} else {
  		rsn_pmkid(pmk, pmk_len, aa, spa, entry->pmkid, akmp);
 	}
@@ -490,7 +519,7 @@
 	    wpa_key_mgmt_fils(old_entry->akmp))
 		pmkid = old_entry->pmkid;
 	new_entry = pmksa_cache_add(pmksa, old_entry->pmk, old_entry->pmk_len,
-				    pmkid, NULL, 0,
+				    pmkid, old_entry->kck, old_entry->kck_len,
 				    aa, pmksa->sm->own_addr,
 				    old_entry->network_ctx, old_entry->akmp,
 				    old_entry->fils_cache_id_set ?
@@ -613,12 +642,13 @@
  * @network_ctx: Network configuration context
  * @try_opportunistic: Whether to allow opportunistic PMKSA caching
  * @fils_cache_id: Pointer to FILS Cache Identifier or %NULL if not used
+ * @associated: Whether the device is associated
  * Returns: 0 if PMKSA was found or -1 if no matching entry was found
  */
 int pmksa_cache_set_current(struct wpa_sm *sm, const u8 *pmkid,
 			    const u8 *bssid, void *network_ctx,
 			    int try_opportunistic, const u8 *fils_cache_id,
-			    int akmp)
+			    int akmp, bool associated)
 {
 	struct rsn_pmksa_cache *pmksa = sm->pmksa;
 	wpa_printf(MSG_DEBUG, "RSN: PMKSA cache search - network_ctx=%p "
@@ -656,13 +686,29 @@
 		if (wpa_key_mgmt_sae(sm->cur_pmksa->akmp) &&
 		    os_get_reltime(&now) == 0 &&
 		    sm->cur_pmksa->reauth_time < now.sec) {
-			wpa_printf(MSG_DEBUG,
-				   "RSN: Do not allow PMKSA cache entry for "
-				   MACSTR
-				   " to be used for SAE since its reauth threshold has passed",
-				   MAC2STR(sm->cur_pmksa->aa));
-			sm->cur_pmksa = NULL;
-			return -1;
+			/* Driver-based roaming might have used a PMKSA entry
+			 * that is already past the reauthentication threshold.
+			 * Remove the related PMKID from the driver to avoid
+			 * further uses for this PMKSA, but allow the
+			 * association to continue since the PMKSA has not yet
+			 * expired. */
+			wpa_sm_remove_pmkid(sm, sm->cur_pmksa->network_ctx,
+					    sm->cur_pmksa->aa,
+					    sm->cur_pmksa->pmkid, NULL);
+			if (associated) {
+				wpa_printf(MSG_DEBUG,
+					   "RSN: Associated with " MACSTR
+					   " using reauth threshold passed PMKSA cache entry",
+					   MAC2STR(sm->cur_pmksa->aa));
+			} else {
+				wpa_printf(MSG_DEBUG,
+					   "RSN: Do not allow PMKSA cache entry for "
+					   MACSTR
+					   " to be used for SAE since its reauth threshold has passed",
+					   MAC2STR(sm->cur_pmksa->aa));
+				sm->cur_pmksa = NULL;
+				return -1;
+			}
 		}
 
 		wpa_hexdump(MSG_DEBUG, "RSN: PMKSA cache entry found - PMKID",
diff --git a/src/rsn_supp/pmksa_cache.h b/src/rsn_supp/pmksa_cache.h
index 48c9e04..6ba48f7 100644
--- a/src/rsn_supp/pmksa_cache.h
+++ b/src/rsn_supp/pmksa_cache.h
@@ -17,6 +17,8 @@
 	u8 pmkid[PMKID_LEN];
 	u8 pmk[PMK_LEN_MAX];
 	size_t pmk_len;
+	u8 kck[WPA_KCK_MAX_LEN];
+	size_t kck_len;
 	os_time_t expiration;
 	int akmp; /* WPA_KEY_MGMT_* */
 	u8 aa[ETH_ALEN];
@@ -45,6 +47,13 @@
 	void *network_ctx;
 	int opportunistic;
 	bool external;
+
+	/**
+	 * This field is used to avoid duplicate pmksa_cache_reauth() calls for
+	 * every 10 minutes during the periodic expiration check of the current
+	 * PMKSA for SAE.
+	 */
+	bool sae_reauth_scheduled;
 };
 
 struct rsn_pmksa_cache;
@@ -86,7 +95,7 @@
 int pmksa_cache_set_current(struct wpa_sm *sm, const u8 *pmkid,
 			    const u8 *bssid, void *network_ctx,
 			    int try_opportunistic, const u8 *fils_cache_id,
-			    int akmp);
+			    int akmp, bool associated);
 struct rsn_pmksa_cache_entry *
 pmksa_cache_get_opportunistic(struct rsn_pmksa_cache *pmksa,
 			      void *network_ctx, const u8 *aa, int akmp);
@@ -164,7 +173,7 @@
 					  void *network_ctx,
 					  int try_opportunistic,
 					  const u8 *fils_cache_id,
-					  int akmp)
+					  int akmp, bool associated)
 {
 	return -1;
 }
diff --git a/src/rsn_supp/preauth.c b/src/rsn_supp/preauth.c
index 8f86820..1a28884 100644
--- a/src/rsn_supp/preauth.c
+++ b/src/rsn_supp/preauth.c
@@ -54,7 +54,8 @@
 	return !!(akmp & (WPA_KEY_MGMT_IEEE8021X |
 			  WPA_KEY_MGMT_IEEE8021X_SHA256 |
 			  WPA_KEY_MGMT_IEEE8021X_SUITE_B |
-			  WPA_KEY_MGMT_IEEE8021X_SUITE_B_192));
+			  WPA_KEY_MGMT_IEEE8021X_SUITE_B_192 |
+			  WPA_KEY_MGMT_IEEE8021X_SHA384));
 }
 
 
diff --git a/src/rsn_supp/tdls.c b/src/rsn_supp/tdls.c
index 1531f51..e6f5877 100644
--- a/src/rsn_supp/tdls.c
+++ b/src/rsn_supp/tdls.c
@@ -139,6 +139,8 @@
 	struct ieee80211_he_capabilities *he_capabilities;
 	size_t he_capab_len;
 	struct ieee80211_he_6ghz_band_cap *he_6ghz_band_capabilities;
+	struct ieee80211_eht_capabilities *eht_capabilities;
+	size_t eht_capab_len;
 
 	u8 qos_info;
 
@@ -157,9 +159,19 @@
 
 	/* channel switch currently enabled */
 	int chan_switch_enabled;
+
+	int mld_link_id;
 };
 
 
+static const u8 * wpa_tdls_get_link_bssid(struct wpa_sm *sm, int link_id)
+{
+	if (link_id >= 0)
+		return sm->mlo.links[link_id].bssid;
+	return sm->bssid;
+}
+
+
 static int wpa_tdls_get_privacy(struct wpa_sm *sm)
 {
 	/*
@@ -245,17 +257,19 @@
 static int wpa_tdls_send_tpk_msg(struct wpa_sm *sm, const u8 *dst,
 				 u8 action_code, u8 dialog_token,
 				 u16 status_code, u32 peer_capab,
-				 int initiator, const u8 *buf, size_t len)
+				 int initiator, const u8 *buf, size_t len,
+				 int link_id)
 {
 	return wpa_sm_send_tdls_mgmt(sm, dst, action_code, dialog_token,
 				     status_code, peer_capab, initiator, buf,
-				     len);
+				     len, link_id);
 }
 
 
 static int wpa_tdls_tpk_send(struct wpa_sm *sm, const u8 *dest, u8 action_code,
 			     u8 dialog_token, u16 status_code, u32 peer_capab,
-			     int initiator, const u8 *msg, size_t msg_len)
+			     int initiator, const u8 *msg, size_t msg_len,
+			     int link_id)
 {
 	struct wpa_tdls_peer *peer;
 
@@ -267,7 +281,7 @@
 
 	if (wpa_tdls_send_tpk_msg(sm, dest, action_code, dialog_token,
 				  status_code, peer_capab, initiator, msg,
-				  msg_len)) {
+				  msg_len, link_id)) {
 		wpa_printf(MSG_INFO, "TDLS: Failed to send message "
 			   "(action_code=%u)", action_code);
 		return -1;
@@ -364,7 +378,7 @@
 					  peer->sm_tmr.peer_capab,
 					  peer->initiator,
 					  peer->sm_tmr.buf,
-					  peer->sm_tmr.buf_len)) {
+					  peer->sm_tmr.buf_len, -1)) {
 			wpa_printf(MSG_INFO, "TDLS: Failed to retry "
 				   "transmission");
 		}
@@ -716,6 +730,8 @@
 	peer->he_capabilities = NULL;
 	os_free(peer->he_6ghz_band_capabilities);
 	peer->he_6ghz_band_capabilities = NULL;
+	os_free(peer->eht_capabilities);
+	peer->eht_capabilities = NULL;
 	os_free(peer->ext_capab);
 	peer->ext_capab = NULL;
 	os_free(peer->supp_channels);
@@ -731,6 +747,7 @@
 	os_memset(&peer->tpk, 0, sizeof(peer->tpk));
 	os_memset(peer->inonce, 0, WPA_NONCE_LEN);
 	os_memset(peer->rnonce, 0, WPA_NONCE_LEN);
+	peer->mld_link_id = -1;
 }
 
 
@@ -747,7 +764,8 @@
 {
 	lnkid->ie_type = WLAN_EID_LINK_ID;
 	lnkid->ie_len = 3 * ETH_ALEN;
-	os_memcpy(lnkid->bssid, sm->bssid, ETH_ALEN);
+	os_memcpy(lnkid->bssid, wpa_tdls_get_link_bssid(sm, peer->mld_link_id),
+		  ETH_ALEN);
 	if (peer->initiator) {
 		os_memcpy(lnkid->init_sta, sm->own_addr, ETH_ALEN);
 		os_memcpy(lnkid->resp_sta, peer->addr, ETH_ALEN);
@@ -846,7 +864,8 @@
 
 	/* request driver to send Teardown using this FTIE */
 	wpa_tdls_tpk_send(sm, addr, WLAN_TDLS_TEARDOWN, 0,
-			  reason_code, 0, peer->initiator, rbuf, pos - rbuf);
+			  reason_code, 0, peer->initiator, rbuf, pos - rbuf,
+			  -1);
 	os_free(rbuf);
 
 	return 0;
@@ -1041,7 +1060,7 @@
 		   " (action=%u status=%u)",
 		   MAC2STR(dst), tdls_action, status);
 	return wpa_tdls_tpk_send(sm, dst, tdls_action, dialog_token, status,
-				 0, initiator, NULL, 0);
+				 0, initiator, NULL, 0, -1);
 }
 
 
@@ -1068,6 +1087,7 @@
 		return NULL;
 
 	os_memcpy(peer->addr, addr, ETH_ALEN);
+	peer->mld_link_id = -1;
 	peer->next = sm->tdls;
 	sm->tdls = peer;
 
@@ -1249,7 +1269,8 @@
 		   MAC2STR(peer->addr));
 
 	status = wpa_tdls_tpk_send(sm, peer->addr, WLAN_TDLS_SETUP_REQUEST,
-				   1, 0, 0, peer->initiator, rbuf, pos - rbuf);
+				   1, 0, 0, peer->initiator, rbuf, pos - rbuf,
+				   -1);
 	os_free(rbuf);
 
 	return status;
@@ -1341,7 +1362,7 @@
 skip_ies:
 	status = wpa_tdls_tpk_send(sm, src_addr, WLAN_TDLS_SETUP_RESPONSE,
 				   dtoken, 0, 0, peer->initiator, rbuf,
-				   pos - rbuf);
+				   pos - rbuf, -1);
 	os_free(rbuf);
 
 	return status;
@@ -1442,7 +1463,7 @@
 
 	status = wpa_tdls_tpk_send(sm, src_addr, WLAN_TDLS_SETUP_CONFIRM,
 				   dtoken, 0, peer_capab, peer->initiator,
-				   rbuf, pos - rbuf);
+				   rbuf, pos - rbuf, -1);
 	os_free(rbuf);
 
 	return status;
@@ -1451,7 +1472,7 @@
 
 static int wpa_tdls_send_discovery_response(struct wpa_sm *sm,
 					    struct wpa_tdls_peer *peer,
-					    u8 dialog_token)
+					    u8 dialog_token, int link_id)
 {
 	size_t buf_len = 0;
 	struct wpa_tdls_timeoutie timeoutie;
@@ -1528,13 +1549,46 @@
 	wpa_printf(MSG_DEBUG, "TDLS: TPK lifetime %u seconds", peer->lifetime);
 skip_ies:
 	status = wpa_tdls_tpk_send(sm, peer->addr, WLAN_TDLS_DISCOVERY_RESPONSE,
-				   dialog_token, 0, 0, 0, rbuf, pos - rbuf);
+				   dialog_token, 0, 0, 0, rbuf, pos - rbuf,
+				   link_id);
 	os_free(rbuf);
 
 	return status;
 }
 
 
+static bool wpa_tdls_is_lnkid_bss_valid(struct wpa_sm *sm,
+					const struct wpa_tdls_lnkid *lnkid,
+					int *link_id)
+{
+	*link_id = -1;
+
+	if (!sm->mlo.valid_links) {
+		if (os_memcmp(sm->bssid, lnkid->bssid, ETH_ALEN) != 0)
+			return false;
+	} else {
+		int i;
+
+		for (i = 0; i < MAX_NUM_MLD_LINKS; i++) {
+			if ((sm->mlo.valid_links & BIT(i)) &&
+			    os_memcmp(lnkid->bssid, sm->mlo.links[i].bssid,
+				      ETH_ALEN) == 0) {
+				*link_id = i;
+				break;
+			}
+		}
+		if (*link_id < 0) {
+			wpa_printf(MSG_DEBUG,
+				   "TDLS: MLD link not found for linkid BSS "
+				   MACSTR, MAC2STR(lnkid->bssid));
+			return false;
+		}
+	}
+
+	return true;
+}
+
+
 static int
 wpa_tdls_process_discovery_request(struct wpa_sm *sm, const u8 *addr,
 				   const u8 *buf, size_t len)
@@ -1545,6 +1599,7 @@
 	size_t min_req_len = sizeof(struct wpa_tdls_frame) +
 		1 /* dialog token */ + sizeof(struct wpa_tdls_lnkid);
 	u8 dialog_token;
+	int link_id = -1;
 
 	wpa_printf(MSG_DEBUG, "TDLS: Discovery Request from " MACSTR,
 		   MAC2STR(addr));
@@ -1577,17 +1632,19 @@
 
 	lnkid = (const struct wpa_tdls_lnkid *) kde.lnkid;
 
-	if (os_memcmp(sm->bssid, lnkid->bssid, ETH_ALEN) != 0) {
-		wpa_printf(MSG_DEBUG, "TDLS: Discovery Request from different "
-			   " BSS " MACSTR, MAC2STR(lnkid->bssid));
-		return -1;
+	if (!wpa_tdls_is_lnkid_bss_valid(sm, lnkid, &link_id)) {
+		wpa_printf(MSG_DEBUG,
+			   "TDLS: Discovery Request from different BSS "
+			   MACSTR, MAC2STR(lnkid->bssid));
+			return -1;
 	}
 
 	peer = wpa_tdls_add_peer(sm, addr, NULL);
 	if (peer == NULL)
 		return -1;
 
-	return wpa_tdls_send_discovery_response(sm, peer, dialog_token);
+	return wpa_tdls_send_discovery_response(sm, peer, dialog_token,
+						link_id);
 }
 
 
@@ -1599,7 +1656,7 @@
 	wpa_printf(MSG_DEBUG, "TDLS: Sending Discovery Request to peer "
 		   MACSTR, MAC2STR(addr));
 	return wpa_tdls_tpk_send(sm, addr, WLAN_TDLS_DISCOVERY_REQUEST,
-				 1, 0, 0, 1, NULL, 0);
+				 1, 0, 0, 1, NULL, 0, -1);
 }
 
 
@@ -1745,6 +1802,29 @@
 }
 
 
+static int copy_peer_eht_capab(const struct wpa_eapol_ie_parse *kde,
+			       struct wpa_tdls_peer *peer)
+{
+	if (!kde->eht_capabilities) {
+		wpa_printf(MSG_DEBUG, "TDLS: No EHT capabilities received");
+		return 0;
+	}
+
+	os_free(peer->eht_capabilities);
+	peer->eht_capab_len = 0;
+	peer->eht_capabilities = os_memdup(kde->eht_capabilities,
+					   kde->eht_capab_len);
+	if (!peer->eht_capabilities)
+		return -1;
+
+	peer->eht_capab_len = kde->eht_capab_len;
+	wpa_hexdump(MSG_DEBUG, "TDLS: Peer EHT capabilities",
+		    peer->eht_capabilities, peer->eht_capab_len);
+
+	return 0;
+}
+
+
 static int copy_peer_wmm_capab(const struct wpa_eapol_ie_parse *kde,
 			       struct wpa_tdls_peer *peer)
 {
@@ -1838,7 +1918,10 @@
 				       peer->supp_channels,
 				       peer->supp_channels_len,
 				       peer->supp_oper_classes,
-				       peer->supp_oper_classes_len);
+				       peer->supp_oper_classes_len,
+				       peer->eht_capabilities,
+				       peer->eht_capab_len,
+				       peer->mld_link_id);
 }
 
 
@@ -1878,6 +1961,7 @@
 	u16 status = WLAN_STATUS_UNSPECIFIED_FAILURE;
 	int tdls_prohibited = sm->tdls_prohibited;
 	int existing_peer = 0;
+	int link_id = -1;
 
 	if (len < 3 + 3)
 		return -1;
@@ -1953,12 +2037,15 @@
 	wpa_hexdump(MSG_DEBUG, "TDLS: Link ID Received from TPK M1",
 		    kde.lnkid, kde.lnkid_len);
 	lnkid = (struct wpa_tdls_lnkid *) kde.lnkid;
-	if (os_memcmp(sm->bssid, lnkid->bssid, ETH_ALEN) != 0) {
-		wpa_printf(MSG_INFO, "TDLS: TPK M1 from diff BSS");
+
+	if (!wpa_tdls_is_lnkid_bss_valid(sm, lnkid, &link_id)) {
+		wpa_printf(MSG_INFO, "TDLS: TPK M1 from diff BSS "
+				MACSTR, MAC2STR(lnkid->bssid));
 		status = WLAN_STATUS_REQUEST_DECLINED;
 		goto error;
 	}
 
+	peer->mld_link_id = link_id;
 	wpa_printf(MSG_DEBUG, "TDLS: TPK M1 - TPK initiator " MACSTR,
 		   MAC2STR(src_addr));
 
@@ -1973,6 +2060,9 @@
 	    copy_peer_he_6ghz_band_capab(&kde, peer) < 0)
 		goto error;
 
+	if (copy_peer_eht_capab(&kde, peer) < 0)
+		goto error;
+
 	if (copy_peer_ext_capab(&kde, peer) < 0)
 		goto error;
 
@@ -2000,7 +2090,7 @@
 		peer->initiator = 1;
 		wpa_sm_tdls_peer_addset(sm, peer->addr, 1, 0, 0, NULL, 0, NULL,
 					NULL, NULL, 0, NULL, 0, 0, NULL, 0,
-					NULL, 0, NULL, 0);
+					NULL, 0, NULL, 0, NULL, 0, link_id);
 		if (wpa_tdls_send_tpk_m1(sm, peer) == -2) {
 			peer = NULL;
 			goto error;
@@ -2181,7 +2271,11 @@
 
 	peer->lifetime = lifetime;
 
-	wpa_tdls_generate_tpk(peer, sm->own_addr, sm->bssid);
+	if (peer->mld_link_id >= 0)
+		wpa_printf(MSG_DEBUG, "TDLS: Use link ID %u for TPK derivation",
+			   peer->mld_link_id);
+	wpa_tdls_generate_tpk(peer, sm->own_addr,
+			      wpa_tdls_get_link_bssid(sm, peer->mld_link_id));
 
 skip_rsn_check:
 #ifdef CONFIG_TDLS_TESTING
@@ -2366,7 +2460,8 @@
 		    kde.lnkid, kde.lnkid_len);
 	lnkid = (struct wpa_tdls_lnkid *) kde.lnkid;
 
-	if (os_memcmp(sm->bssid, lnkid->bssid, ETH_ALEN) != 0) {
+	if (os_memcmp(sm->bssid, wpa_tdls_get_link_bssid(sm, peer->mld_link_id),
+		      ETH_ALEN) != 0) {
 		wpa_printf(MSG_INFO, "TDLS: TPK M2 from different BSS");
 		status = WLAN_STATUS_NOT_IN_SAME_BSS;
 		goto error;
@@ -2383,6 +2478,9 @@
 	    copy_peer_he_6ghz_band_capab(&kde, peer) < 0)
 		goto error;
 
+	if (copy_peer_eht_capab(&kde, peer) < 0)
+		goto error;
+
 	if (copy_peer_ext_capab(&kde, peer) < 0)
 		goto error;
 
@@ -2490,7 +2588,11 @@
 		goto error;
 	}
 
-	wpa_tdls_generate_tpk(peer, sm->own_addr, sm->bssid);
+	if (peer->mld_link_id >= 0)
+		wpa_printf(MSG_DEBUG, "TDLS: Use link ID %u for TPK derivation",
+			   peer->mld_link_id);
+	wpa_tdls_generate_tpk(peer, sm->own_addr,
+			      wpa_tdls_get_link_bssid(sm, peer->mld_link_id));
 
 	/* Process MIC check to see if TPK M2 is right */
 	if (wpa_supplicant_verify_tdls_mic(2, peer, (const u8 *) lnkid,
@@ -2611,7 +2713,8 @@
 		    (u8 *) kde.lnkid, kde.lnkid_len);
 	lnkid = (struct wpa_tdls_lnkid *) kde.lnkid;
 
-	if (os_memcmp(sm->bssid, lnkid->bssid, ETH_ALEN) != 0) {
+	if (os_memcmp(wpa_tdls_get_link_bssid(sm, peer->mld_link_id),
+		      lnkid->bssid, ETH_ALEN) != 0) {
 		wpa_printf(MSG_INFO, "TDLS: TPK M3 from diff BSS");
 		goto error;
 	}
@@ -2770,7 +2873,7 @@
 	/* add the peer to the driver as a "setup in progress" peer */
 	if (wpa_sm_tdls_peer_addset(sm, peer->addr, 1, 0, 0, NULL, 0, NULL,
 				    NULL, NULL, 0, NULL, 0, 0, NULL, 0, NULL, 0,
-				    NULL, 0)) {
+				    NULL, 0, NULL, 0, peer->mld_link_id)) {
 		wpa_tdls_disable_peer_link(sm, peer);
 		return -1;
 	}
@@ -3080,6 +3183,63 @@
 }
 
 
+int wpa_tdls_process_discovery_response(struct wpa_sm *sm, const u8 *addr,
+					const u8 *buf, size_t len)
+{
+	struct ieee802_11_elems elems;
+	struct wpa_tdls_lnkid lnkid;
+	struct wpa_tdls_peer *peer;
+	size_t min_req_len = 1 /* Dialog Token */ + 2 /* Capability */ +
+		sizeof(struct wpa_tdls_lnkid);
+	int link_id = -1;
+
+	wpa_printf(MSG_DEBUG, "TDLS: Process Discovery Response from " MACSTR,
+		   MAC2STR(addr));
+
+	if (len < min_req_len) {
+		wpa_printf(MSG_DEBUG, "TDLS Discovery Resp is too short: %zu",
+			   len);
+		return -1;
+	}
+
+	/* Elements start after the three octets of fixed field (one octet for
+	 * the Dialog Token field and two octets for the Capability field. */
+	if (ieee802_11_parse_elems(buf + 3, len - 3, &elems, 1) ==
+	    ParseFailed) {
+		wpa_printf(MSG_DEBUG,
+			   "TDLS: Failed to parse IEs in Discovery Response");
+		return -1;
+	}
+
+	if (!elems.link_id) {
+		wpa_printf(MSG_DEBUG,
+			   "TDLS: Link Identifier element not found in Discovery Response");
+		return -1;
+	}
+
+	os_memcpy(&lnkid.bssid[0], elems.link_id, sizeof(lnkid) - 2);
+
+	if (!wpa_tdls_is_lnkid_bss_valid(sm, &lnkid, &link_id)) {
+		wpa_printf(MSG_DEBUG,
+			   "TDLS: Discovery Response from different BSS "
+			   MACSTR, MAC2STR(lnkid.bssid));
+		return -1;
+	}
+
+	peer = wpa_tdls_add_peer(sm, addr, NULL);
+	if (!peer) {
+		wpa_printf(MSG_DEBUG, "TDLS: Could not add peer entry");
+		return -1;
+	}
+
+	peer->mld_link_id = link_id;
+	wpa_printf(MSG_DEBUG, "TDLS: Link identifier BSS: " MACSTR
+		   " , link id: %u", MAC2STR(lnkid.bssid), link_id);
+
+	return 0;
+}
+
+
 int wpa_tdls_enable_chan_switch(struct wpa_sm *sm, const u8 *addr,
 				u8 oper_class,
 				struct hostapd_freq_params *freq_params)
diff --git a/src/rsn_supp/wpa.c b/src/rsn_supp/wpa.c
index 5a28ef5..9f49cf9 100644
--- a/src/rsn_supp/wpa.c
+++ b/src/rsn_supp/wpa.c
@@ -223,7 +223,7 @@
 	size_t mic_len, hdrlen, rlen;
 	struct wpa_eapol_key *reply;
 	int key_info, ver;
-	u8 bssid[ETH_ALEN], *rbuf, *key_mic, *mic;
+	u8 *rbuf, *key_mic, *mic;
 
 	if (pairwise && sm->wpa_deny_ptk0_rekey && !sm->use_ext_key_id &&
 	    wpa_sm_get_state(sm) == WPA_COMPLETED && !error) {
@@ -243,12 +243,6 @@
 	else
 		ver = WPA_KEY_INFO_TYPE_HMAC_MD5_RC4;
 
-	if (wpa_sm_get_bssid(sm, bssid) < 0) {
-		wpa_msg(sm->ctx->msg_ctx, MSG_WARNING,
-			"Failed to read BSSID for EAPOL-Key request");
-		return;
-	}
-
 	mic_len = wpa_mic_len(sm->key_mgmt, sm->pmk_len);
 	hdrlen = sizeof(*reply) + mic_len + 2;
 	rbuf = wpa_sm_alloc_eapol(sm, IEEE802_1X_TYPE_EAPOL_KEY, NULL,
@@ -285,8 +279,8 @@
 		"WPA: Sending EAPOL-Key Request (error=%d "
 		"pairwise=%d ptk_set=%d len=%lu)",
 		error, pairwise, sm->ptk_set, (unsigned long) rlen);
-	wpa_eapol_key_send(sm, &sm->ptk, ver, bssid, ETH_P_EAPOL, rbuf, rlen,
-			   key_mic);
+	wpa_eapol_key_send(sm, &sm->ptk, ver, wpa_sm_get_auth_addr(sm),
+			   ETH_P_EAPOL, rbuf, rlen, key_mic);
 }
 
 
@@ -2745,8 +2739,8 @@
 #endif /* CONFIG_OCV */
 
 	wpa_dbg(sm->ctx->msg_ctx, MSG_DEBUG, "WPA: Sending EAPOL-Key 2/2");
-	return wpa_eapol_key_send(sm, &sm->ptk, ver, sm->bssid, ETH_P_EAPOL,
-				  rbuf, rlen, key_mic);
+	return wpa_eapol_key_send(sm, &sm->ptk, ver, wpa_sm_get_auth_addr(sm),
+				  ETH_P_EAPOL, rbuf, rlen, key_mic);
 }
 
 
@@ -3829,6 +3823,8 @@
 		return RSN_AUTH_KEY_MGMT_802_1X_SUITE_B;
 	case WPA_KEY_MGMT_IEEE8021X_SUITE_B_192:
 		return RSN_AUTH_KEY_MGMT_802_1X_SUITE_B_192;
+	case WPA_KEY_MGMT_IEEE8021X_SHA384:
+		return RSN_AUTH_KEY_MGMT_802_1X_SHA384;
 	default:
 		return 0;
 	}
@@ -4074,6 +4070,8 @@
 	os_memset(&sm->gtk_wnm_sleep, 0, sizeof(sm->gtk_wnm_sleep));
 	os_memset(&sm->igtk, 0, sizeof(sm->igtk));
 	os_memset(&sm->igtk_wnm_sleep, 0, sizeof(sm->igtk_wnm_sleep));
+	os_memset(&sm->bigtk, 0, sizeof(sm->bigtk));
+	os_memset(&sm->bigtk_wnm_sleep, 0, sizeof(sm->bigtk_wnm_sleep));
 	sm->tk_set = false;
 	for (i = 0; i < MAX_NUM_MLD_LINKS; i++) {
 		os_memset(&sm->mlo.links[i].gtk, 0,
@@ -4084,6 +4082,10 @@
 			  sizeof(sm->mlo.links[i].igtk));
 		os_memset(&sm->mlo.links[i].igtk_wnm_sleep, 0,
 			  sizeof(sm->mlo.links[i].igtk_wnm_sleep));
+		os_memset(&sm->mlo.links[i].bigtk, 0,
+			  sizeof(sm->mlo.links[i].bigtk));
+		os_memset(&sm->mlo.links[i].bigtk_wnm_sleep, 0,
+			  sizeof(sm->mlo.links[i].bigtk_wnm_sleep));
 	}
 }
 
@@ -5414,6 +5416,11 @@
 	const u8 *g_ap = NULL;
 	size_t g_ap_len = 0, kdk_len;
 	struct wpabuf *pub = NULL;
+#ifdef CONFIG_IEEE80211R
+	struct wpa_ft_ies parse;
+
+	os_memset(&parse, 0, sizeof(parse));
+#endif /* CONFIG_IEEE80211R */
 
 	os_memcpy(sm->bssid, bssid, ETH_ALEN);
 
@@ -5492,15 +5499,13 @@
 
 #ifdef CONFIG_IEEE80211R
 	if (wpa_key_mgmt_ft(sm->key_mgmt)) {
-		struct wpa_ft_ies parse;
-
 		if (!elems.mdie || !elems.ftie) {
 			wpa_printf(MSG_DEBUG, "FILS+FT: No MDE or FTE");
 			goto fail;
 		}
 
 		if (wpa_ft_parse_ies(pos, end - pos, &parse,
-				     sm->key_mgmt) < 0) {
+				     sm->key_mgmt, false) < 0) {
 			wpa_printf(MSG_DEBUG, "FILS+FT: Failed to parse IEs");
 			goto fail;
 		}
@@ -5704,10 +5709,16 @@
 			       &sm->fils_key_auth_len);
 	wpabuf_free(pub);
 	forced_memzero(ick, sizeof(ick));
+#ifdef CONFIG_IEEE80211R
+	wpa_ft_parse_ies_free(&parse);
+#endif /* CONFIG_IEEE80211R */
 	return res;
 fail:
 	wpabuf_free(pub);
 	wpabuf_clear_free(dh_ss);
+#ifdef CONFIG_IEEE80211R
+	wpa_ft_parse_ies_free(&parse);
+#endif /* CONFIG_IEEE80211R */
 	return -1;
 }
 
@@ -6538,3 +6549,11 @@
 	if (sm)
 		sm->cur_pmksa = entry;
 }
+
+
+void wpa_sm_set_driver_bss_selection(struct wpa_sm *sm,
+				     bool driver_bss_selection)
+{
+	if (sm)
+		sm->driver_bss_selection = driver_bss_selection;
+}
diff --git a/src/rsn_supp/wpa.h b/src/rsn_supp/wpa.h
index 47d2344..d8d0a15 100644
--- a/src/rsn_supp/wpa.h
+++ b/src/rsn_supp/wpa.h
@@ -64,7 +64,8 @@
 	int (*send_tdls_mgmt)(void *ctx, const u8 *dst,
 			      u8 action_code, u8 dialog_token,
 			      u16 status_code, u32 peer_capab,
-			      int initiator, const u8 *buf, size_t len);
+			      int initiator, const u8 *buf, size_t len,
+			      int link_id);
 	int (*tdls_oper)(void *ctx, int oper, const u8 *peer);
 	int (*tdls_peer_addset)(void *ctx, const u8 *addr, int add, u16 aid,
 				u16 capability, const u8 *supp_rates,
@@ -78,7 +79,9 @@
 				size_t ext_capab_len, const u8 *supp_channels,
 				size_t supp_channels_len,
 				const u8 *supp_oper_classes,
-				size_t supp_oper_classes_len);
+				size_t supp_oper_classes_len,
+				const struct ieee80211_eht_capabilities *eht_capab,
+				size_t eht_capab_len, int mld_link_id);
 	int (*tdls_enable_channel_switch)(
 		void *ctx, const u8 *addr, u8 oper_class,
 		const struct hostapd_freq_params *params);
@@ -593,6 +596,8 @@
 				u8 oper_class,
 				struct hostapd_freq_params *freq_params);
 int wpa_tdls_disable_chan_switch(struct wpa_sm *sm, const u8 *addr);
+int wpa_tdls_process_discovery_response(struct wpa_sm *sm, const u8 *addr,
+					const u8 *buf, size_t len);
 #ifdef CONFIG_TDLS_TESTING
 extern unsigned int tdls_testing;
 #endif /* CONFIG_TDLS_TESTING */
@@ -626,5 +631,7 @@
 void wpa_sm_set_cur_pmksa(struct wpa_sm *sm,
 			  struct rsn_pmksa_cache_entry *entry);
 const u8 * wpa_sm_get_auth_addr(struct wpa_sm *sm);
+void wpa_sm_set_driver_bss_selection(struct wpa_sm *sm,
+				     bool driver_bss_selection);
 
 #endif /* WPA_H */
diff --git a/src/rsn_supp/wpa_ft.c b/src/rsn_supp/wpa_ft.c
index 56a30c8..3d1dbc6 100644
--- a/src/rsn_supp/wpa_ft.c
+++ b/src/rsn_supp/wpa_ft.c
@@ -127,11 +127,13 @@
 		return 0;
 	}
 
-	if (wpa_ft_parse_ies(ies, ies_len, &ft, sm->key_mgmt) < 0)
+	if (wpa_ft_parse_ies(ies, ies_len, &ft, sm->key_mgmt, false) < 0)
 		return -1;
 
-	if (ft.mdie_len < MOBILITY_DOMAIN_ID_LEN + 1)
+	if (ft.mdie_len < MOBILITY_DOMAIN_ID_LEN + 1) {
+		wpa_ft_parse_ies_free(&ft);
 		return -1;
+	}
 
 	wpa_hexdump(MSG_DEBUG, "FT: Mobility domain",
 		    ft.mdie, MOBILITY_DOMAIN_ID_LEN);
@@ -179,6 +181,7 @@
 			    sm->assoc_resp_ies, sm->assoc_resp_ies_len);
 	}
 
+	wpa_ft_parse_ies_free(&ft);
 	return 0;
 }
 
@@ -472,6 +475,7 @@
 			       ftie_pos, 2 + *ftie_len,
 			       (u8 *) rsnie, 2 + rsnie->len, ric_ies,
 			       ric_ies_len, rsnxe_len ? rsnxe : NULL, rsnxe_len,
+			       NULL,
 			       fte_mic) < 0) {
 			wpa_printf(MSG_INFO, "FT: Failed to calculate MIC");
 			os_free(buf);
@@ -586,11 +590,13 @@
 	struct wpa_ft_ies parse;
 	struct rsn_mdie *mdie;
 	u8 ptk_name[WPA_PMK_NAME_LEN];
-	int ret;
+	int ret = -1, res;
 	const u8 *bssid;
 	const u8 *kck;
 	size_t kck_len, kdk_len;
 
+	os_memset(&parse, 0, sizeof(parse));
+
 	wpa_hexdump(MSG_DEBUG, "FT: Response IEs", ies, ies_len);
 	wpa_hexdump(MSG_DEBUG, "FT: RIC IEs", ric_ies, ric_ies_len);
 
@@ -598,26 +604,27 @@
 		if (!sm->over_the_ds_in_progress) {
 			wpa_printf(MSG_DEBUG, "FT: No over-the-DS in progress "
 				   "- drop FT Action Response");
-			return -1;
+			goto fail;
 		}
 
 		if (os_memcmp(target_ap, sm->target_ap, ETH_ALEN) != 0) {
 			wpa_printf(MSG_DEBUG, "FT: No over-the-DS in progress "
 				   "with this Target AP - drop FT Action "
 				   "Response");
-			return -1;
+			goto fail;
 		}
 	}
 
 	if (!wpa_key_mgmt_ft(sm->key_mgmt)) {
 		wpa_printf(MSG_DEBUG, "FT: Reject FT IEs since FT is not "
 			   "enabled for this connection");
-		return -1;
+		goto fail;
 	}
 
-	if (wpa_ft_parse_ies(ies, ies_len, &parse, sm->key_mgmt) < 0) {
+	if (wpa_ft_parse_ies(ies, ies_len, &parse, sm->key_mgmt,
+			     !ft_action) < 0) {
 		wpa_printf(MSG_DEBUG, "FT: Failed to parse IEs");
-		return -1;
+		goto fail;
 	}
 
 	mdie = (struct rsn_mdie *) parse.mdie;
@@ -625,12 +632,12 @@
 	    os_memcmp(mdie->mobility_domain, sm->mobility_domain,
 		      MOBILITY_DOMAIN_ID_LEN) != 0) {
 		wpa_printf(MSG_DEBUG, "FT: Invalid MDIE");
-		return -1;
+		goto fail;
 	}
 
 	if (!parse.ftie || !parse.fte_anonce || !parse.fte_snonce) {
 		wpa_printf(MSG_DEBUG, "FT: Invalid FTE");
-		return -1;
+		goto fail;
 	}
 
 	if (os_memcmp(parse.fte_snonce, sm->snonce, WPA_NONCE_LEN) != 0) {
@@ -639,12 +646,12 @@
 			    parse.fte_snonce, WPA_NONCE_LEN);
 		wpa_hexdump(MSG_DEBUG, "FT: Expected SNonce",
 			    sm->snonce, WPA_NONCE_LEN);
-		return -1;
+		goto fail;
 	}
 
 	if (parse.r0kh_id == NULL) {
 		wpa_printf(MSG_DEBUG, "FT: No R0KH-ID subelem in FTIE");
-		return -1;
+		goto fail;
 	}
 
 	if (parse.r0kh_id_len != sm->r0kh_id_len ||
@@ -656,12 +663,12 @@
 			    parse.r0kh_id, parse.r0kh_id_len);
 		wpa_hexdump(MSG_DEBUG, "FT: The current R0KH-ID",
 			    sm->r0kh_id, sm->r0kh_id_len);
-		return -1;
+		goto fail;
 	}
 
 	if (parse.r1kh_id == NULL) {
 		wpa_printf(MSG_DEBUG, "FT: No R1KH-ID subelem in FTIE");
-		return -1;
+		goto fail;
 	}
 
 	if (parse.rsn_pmkid == NULL ||
@@ -669,13 +676,13 @@
 	{
 		wpa_printf(MSG_DEBUG, "FT: No matching PMKR0Name (PMKID) in "
 			   "RSNIE");
-		return -1;
+		goto fail;
 	}
 
 	if (sm->mfp == 2 && !(parse.rsn_capab & WPA_CAPABILITY_MFPC)) {
 		wpa_printf(MSG_INFO,
 			   "FT: Target AP does not support PMF, but local configuration requires that");
-		return -1;
+		goto fail;
 	}
 
 	os_memcpy(sm->r1kh_id, parse.r1kh_id, FT_R1KH_ID_LEN);
@@ -686,7 +693,7 @@
 	if (wpa_derive_pmk_r1(sm->pmk_r0, sm->pmk_r0_len, sm->pmk_r0_name,
 			      sm->r1kh_id, sm->own_addr, sm->pmk_r1,
 			      sm->pmk_r1_name) < 0)
-		return -1;
+		goto fail;
 	sm->pmk_r1_len = sm->pmk_r0_len;
 
 	bssid = target_ap;
@@ -706,7 +713,7 @@
 			      sm->pmk_r1_name, &sm->ptk, ptk_name, sm->key_mgmt,
 			      sm->pairwise_cipher,
 			      kdk_len) < 0)
-		return -1;
+		goto fail;
 
 	os_memcpy(sm->key_mobility_domain, sm->mobility_domain,
 		  MOBILITY_DOMAIN_ID_LEN);
@@ -716,7 +723,7 @@
 	    ieee802_11_rsnx_capab(sm->ap_rsnxe, WLAN_RSNX_CAPAB_SECURE_LTF) &&
 	    wpa_ltf_keyseed(&sm->ptk, sm->key_mgmt, sm->pairwise_cipher)) {
 		wpa_printf(MSG_DEBUG, "FT: Failed to derive LTF keyseed");
-		return -1;
+		goto fail;
 	}
 #endif /* CONFIG_PASN */
 
@@ -740,8 +747,8 @@
 	}
 
 	wpa_sm_mark_authenticated(sm, bssid);
-	ret = wpa_ft_install_ptk(sm, bssid);
-	if (ret) {
+	res = wpa_ft_install_ptk(sm, bssid);
+	if (res) {
 		/*
 		 * Some drivers do not support key configuration when we are
 		 * not associated with the target AP. Work around this by
@@ -763,7 +770,10 @@
 		os_memcpy(sm->bssid, target_ap, ETH_ALEN);
 	}
 
-	return 0;
+	ret = 0;
+fail:
+	wpa_ft_parse_ies_free(&parse);
+	return ret;
 }
 
 
@@ -1037,13 +1047,16 @@
 	size_t kck_len;
 	int own_rsnxe_used;
 	size_t mic_len;
+	int ret = -1;
+
+	os_memset(&parse, 0, sizeof(parse));
 
 	wpa_hexdump(MSG_DEBUG, "FT: Response IEs", ies, ies_len);
 
 	if (!wpa_key_mgmt_ft(sm->key_mgmt)) {
 		wpa_printf(MSG_DEBUG, "FT: Reject FT IEs since FT is not "
 			   "enabled for this connection");
-		return -1;
+		goto fail;
 	}
 
 	if (sm->ft_reassoc_completed) {
@@ -1051,9 +1064,9 @@
 		return 0;
 	}
 
-	if (wpa_ft_parse_ies(ies, ies_len, &parse, sm->key_mgmt) < 0) {
+	if (wpa_ft_parse_ies(ies, ies_len, &parse, sm->key_mgmt, true) < 0) {
 		wpa_printf(MSG_DEBUG, "FT: Failed to parse IEs");
-		return -1;
+		goto fail;
 	}
 
 	mdie = (struct rsn_mdie *) parse.mdie;
@@ -1061,7 +1074,7 @@
 	    os_memcmp(mdie->mobility_domain, sm->mobility_domain,
 		      MOBILITY_DOMAIN_ID_LEN) != 0) {
 		wpa_printf(MSG_DEBUG, "FT: Invalid MDIE");
-		return -1;
+		goto fail;
 	}
 
 	if (sm->key_mgmt == WPA_KEY_MGMT_FT_SAE_EXT_KEY &&
@@ -1079,7 +1092,7 @@
 		wpa_printf(MSG_DEBUG,
 			   "FT: Invalid FTE (fte_mic_len=%zu mic_len=%zu)",
 			   parse.fte_mic_len, mic_len);
-		return -1;
+		goto fail;
 	}
 
 	if (os_memcmp(parse.fte_snonce, sm->snonce, WPA_NONCE_LEN) != 0) {
@@ -1088,7 +1101,7 @@
 			    parse.fte_snonce, WPA_NONCE_LEN);
 		wpa_hexdump(MSG_DEBUG, "FT: Expected SNonce",
 			    sm->snonce, WPA_NONCE_LEN);
-		return -1;
+		goto fail;
 	}
 
 	if (os_memcmp(parse.fte_anonce, sm->anonce, WPA_NONCE_LEN) != 0) {
@@ -1097,12 +1110,12 @@
 			    parse.fte_anonce, WPA_NONCE_LEN);
 		wpa_hexdump(MSG_DEBUG, "FT: Expected ANonce",
 			    sm->anonce, WPA_NONCE_LEN);
-		return -1;
+		goto fail;
 	}
 
 	if (parse.r0kh_id == NULL) {
 		wpa_printf(MSG_DEBUG, "FT: No R0KH-ID subelem in FTIE");
-		return -1;
+		goto fail;
 	}
 
 	if (parse.r0kh_id_len != sm->r0kh_id_len ||
@@ -1114,18 +1127,18 @@
 			    parse.r0kh_id, parse.r0kh_id_len);
 		wpa_hexdump(MSG_DEBUG, "FT: The current R0KH-ID",
 			    sm->r0kh_id, sm->r0kh_id_len);
-		return -1;
+		goto fail;
 	}
 
 	if (parse.r1kh_id == NULL) {
 		wpa_printf(MSG_DEBUG, "FT: No R1KH-ID subelem in FTIE");
-		return -1;
+		goto fail;
 	}
 
 	if (os_memcmp_const(parse.r1kh_id, sm->r1kh_id, FT_R1KH_ID_LEN) != 0) {
 		wpa_printf(MSG_DEBUG, "FT: Unknown R1KH-ID used in "
 			   "ReassocResp");
-		return -1;
+		goto fail;
 	}
 
 	if (parse.rsn_pmkid == NULL ||
@@ -1133,7 +1146,7 @@
 	{
 		wpa_printf(MSG_DEBUG, "FT: No matching PMKR1Name (PMKID) in "
 			   "RSNIE (pmkid=%d)", !!parse.rsn_pmkid);
-		return -1;
+		goto fail;
 	}
 
 	count = 3;
@@ -1145,7 +1158,7 @@
 		wpa_printf(MSG_DEBUG, "FT: Unexpected IE count in MIC "
 			   "Control: received %u expected %u",
 			   parse.fte_elem_count, count);
-		return -1;
+		goto fail;
 	}
 
 	if (wpa_key_mgmt_fils(sm->key_mgmt)) {
@@ -1163,9 +1176,10 @@
 		       parse.ric, parse.ric_len,
 		       parse.rsnxe ? parse.rsnxe - 2 : NULL,
 		       parse.rsnxe ? parse.rsnxe_len + 2 : 0,
+		       NULL,
 		       mic) < 0) {
 		wpa_printf(MSG_DEBUG, "FT: Failed to calculate MIC");
-		return -1;
+		goto fail;
 	}
 
 	if (os_memcmp_const(mic, parse.fte_mic, mic_len) != 0) {
@@ -1173,13 +1187,13 @@
 		wpa_hexdump(MSG_MSGDUMP, "FT: Received MIC",
 			    parse.fte_mic, mic_len);
 		wpa_hexdump(MSG_MSGDUMP, "FT: Calculated MIC", mic, mic_len);
-		return -1;
+		goto fail;
 	}
 
 	if (parse.fte_rsnxe_used && !sm->ap_rsnxe) {
 		wpa_printf(MSG_INFO,
 			   "FT: FTE indicated that AP uses RSNXE, but RSNXE was not included in Beacon/Probe Response frames");
-		return -1;
+		goto fail;
 	}
 
 	if (!sm->ap_rsn_ie) {
@@ -1188,7 +1202,7 @@
 		if (wpa_sm_get_beacon_ie(sm) < 0) {
 			wpa_msg(sm->ctx->msg_ctx, MSG_WARNING,
 				"FT: Could not find AP from the scan results");
-			return -1;
+			goto fail;
 		}
 		wpa_msg(sm->ctx->msg_ctx, MSG_DEBUG,
 			"FT: Found the current AP from updated scan results");
@@ -1206,7 +1220,7 @@
 			    "RSNE in FT protocol Reassociation Response frame",
 			    parse.rsn ? parse.rsn - 2 : NULL,
 			    parse.rsn ? parse.rsn_len + 2 : 0);
-		return -1;
+		goto fail;
 	}
 
 	own_rsnxe_used = wpa_key_mgmt_sae(sm->key_mgmt) &&
@@ -1226,7 +1240,7 @@
 			    "RSNXE in FT protocol Reassociation Response frame",
 			    parse.rsnxe ? parse.rsnxe - 2 : NULL,
 			    parse.rsnxe ? parse.rsnxe_len + 2 : 0);
-		return -1;
+		goto fail;
 	}
 
 #ifdef CONFIG_OCV
@@ -1236,7 +1250,7 @@
 		if (wpa_sm_channel_info(sm, &ci) != 0) {
 			wpa_printf(MSG_WARNING,
 				   "Failed to get channel info to validate received OCI in (Re)Assoc Response");
-			return -1;
+			goto fail;
 		}
 
 		if (ocv_verify_tx_params(parse.oci, parse.oci_len, &ci,
@@ -1245,7 +1259,7 @@
 			wpa_msg(sm->ctx->msg_ctx, MSG_INFO, OCV_FAILURE
 				"addr=" MACSTR " frame=ft-assoc error=%s",
 				MAC2STR(src_addr), ocv_errorstr);
-			return -1;
+			goto fail;
 		}
 	}
 #endif /* CONFIG_OCV */
@@ -1255,13 +1269,13 @@
 	if (wpa_ft_process_gtk_subelem(sm, parse.gtk, parse.gtk_len) < 0 ||
 	    wpa_ft_process_igtk_subelem(sm, parse.igtk, parse.igtk_len) < 0 ||
 	    wpa_ft_process_bigtk_subelem(sm, parse.bigtk, parse.bigtk_len) < 0)
-		return -1;
+		goto fail;
 
 	if (sm->set_ptk_after_assoc) {
 		wpa_printf(MSG_DEBUG, "FT: Try to set PTK again now that we "
 			   "are associated");
 		if (wpa_ft_install_ptk(sm, src_addr) < 0)
-			return -1;
+			goto fail;
 		sm->set_ptk_after_assoc = 0;
 	}
 
@@ -1274,7 +1288,10 @@
 
 	wpa_printf(MSG_DEBUG, "FT: Completed successfully");
 
-	return 0;
+	ret = 0;
+fail:
+	wpa_ft_parse_ies_free(&parse);
+	return ret;
 }
 
 
diff --git a/src/rsn_supp/wpa_i.h b/src/rsn_supp/wpa_i.h
index 300ef54..5fe6182 100644
--- a/src/rsn_supp/wpa_i.h
+++ b/src/rsn_supp/wpa_i.h
@@ -222,6 +222,7 @@
 	struct wpa_sm_mlo mlo;
 
 	bool wmm_enabled;
+	bool driver_bss_selection;
 };
 
 
@@ -380,13 +381,13 @@
 					u8 action_code, u8 dialog_token,
 					u16 status_code, u32 peer_capab,
 					int initiator, const u8 *buf,
-					size_t len)
+					size_t len, int link_id)
 {
 	if (sm->ctx->send_tdls_mgmt)
 		return sm->ctx->send_tdls_mgmt(sm->ctx->ctx, dst, action_code,
 					       dialog_token, status_code,
 					       peer_capab, initiator, buf,
-					       len);
+					       len, link_id);
 	return -1;
 }
 
@@ -410,7 +411,9 @@
 			u8 qosinfo, int wmm, const u8 *ext_capab,
 			size_t ext_capab_len, const u8 *supp_channels,
 			size_t supp_channels_len, const u8 *supp_oper_classes,
-			size_t supp_oper_classes_len)
+			size_t supp_oper_classes_len,
+			const struct ieee80211_eht_capabilities *eht_capab,
+			size_t eht_capab_len, int mld_link_id)
 {
 	if (sm->ctx->tdls_peer_addset)
 		return sm->ctx->tdls_peer_addset(sm->ctx->ctx, addr, add,
@@ -423,7 +426,9 @@
 						 supp_channels,
 						 supp_channels_len,
 						 supp_oper_classes,
-						 supp_oper_classes_len);
+						 supp_oper_classes_len,
+						 eht_capab, eht_capab_len,
+						 mld_link_id);
 	return -1;
 }
 
diff --git a/src/rsn_supp/wpa_ie.c b/src/rsn_supp/wpa_ie.c
index 2a6c79b..d1510aa 100644
--- a/src/rsn_supp/wpa_ie.c
+++ b/src/rsn_supp/wpa_ie.c
@@ -230,6 +230,10 @@
 	} else if (key_mgmt & WPA_KEY_MGMT_OSEN) {
 		RSN_SELECTOR_PUT(pos, RSN_AUTH_KEY_MGMT_OSEN);
 #endif /* CONFIG_HS20 */
+#ifdef CONFIG_SHA384
+	} else if (key_mgmt == WPA_KEY_MGMT_IEEE8021X_SHA384) {
+		RSN_SELECTOR_PUT(pos, RSN_AUTH_KEY_MGMT_802_1X_SHA384);
+#endif /* CONFIG_SHA384 */
 	} else {
 		wpa_printf(MSG_WARNING, "Invalid key management type (%d).",
 			   key_mgmt);
diff --git a/src/utils/common.h b/src/utils/common.h
index 435a9a8..bede21e 100644
--- a/src/utils/common.h
+++ b/src/utils/common.h
@@ -244,6 +244,18 @@
 	a[2] = val & 0xff;
 }
 
+static inline u32 WPA_GET_LE24(const u8 *a)
+{
+	return (a[2] << 16) | (a[1] << 8) | a[0];
+}
+
+static inline void WPA_PUT_LE24(u8 *a, u32 val)
+{
+	a[2] = (val >> 16) & 0xff;
+	a[1] = (val >> 8) & 0xff;
+	a[0] = val & 0xff;
+}
+
 static inline u32 WPA_GET_BE32(const u8 *a)
 {
 	return ((u32) a[0] << 24) | (a[1] << 16) | (a[2] << 8) | a[3];
diff --git a/src/utils/wpabuf.h b/src/utils/wpabuf.h
index eb1db80..88d72bd 100644
--- a/src/utils/wpabuf.h
+++ b/src/utils/wpabuf.h
@@ -127,6 +127,12 @@
 	WPA_PUT_LE16(pos, data);
 }
 
+static inline void wpabuf_put_le24(struct wpabuf *buf, u32 data)
+{
+	u8 *pos = (u8 *) wpabuf_put(buf, 3);
+	WPA_PUT_LE24(pos, data);
+}
+
 static inline void wpabuf_put_le32(struct wpabuf *buf, u32 data)
 {
 	u8 *pos = (u8 *) wpabuf_put(buf, 4);
diff --git a/src/wps/wps_attr_parse.c b/src/wps/wps_attr_parse.c
index fd51635..d364430 100644
--- a/src/wps/wps_attr_parse.c
+++ b/src/wps/wps_attr_parse.c
@@ -599,10 +599,15 @@
 	u16 type, len;
 #ifdef WPS_WORKAROUNDS
 	u16 prev_type = 0;
+	size_t last_nonzero = 0;
+	const u8 *start;
 #endif /* WPS_WORKAROUNDS */
 
 	os_memset(attr, 0, sizeof(*attr));
 	pos = wpabuf_head(msg);
+#ifdef WPS_WORKAROUNDS
+	start = pos;
+#endif /* WPS_WORKAROUNDS */
 	end = pos + wpabuf_len(msg);
 
 	while (pos < end) {
@@ -649,9 +654,15 @@
 			 * end of M1. Skip those to avoid interop issues.
 			 */
 			int i;
+
+			if (last_nonzero > (size_t) (pos - start))
+				continue;
+
 			for (i = 0; i < end - pos; i++) {
-				if (pos[i])
+				if (pos[i]) {
+					last_nonzero = pos - start + i;
 					break;
+				}
 			}
 			if (i == end - pos) {
 				wpa_printf(MSG_DEBUG, "WPS: Workaround - skip "
diff --git a/wpa_supplicant/Android.bp b/wpa_supplicant/Android.bp
index 8f8c317..ca56d14 100644
--- a/wpa_supplicant/Android.bp
+++ b/wpa_supplicant/Android.bp
@@ -445,4 +445,4 @@
         "wpa_supplicant.c",
         "wps_supplicant.c",
     ],
-}
\ No newline at end of file
+}
diff --git a/wpa_supplicant/Makefile b/wpa_supplicant/Makefile
index c682f73..8c417b8 100644
--- a/wpa_supplicant/Makefile
+++ b/wpa_supplicant/Makefile
@@ -449,6 +449,34 @@
 CFLAGS += -DCONFIG_NO_ROAMING
 endif
 
+ifdef CONFIG_OPENSC_ENGINE_PATH
+CFLAGS += -DCONFIG_OPENSC_ENGINE_PATH=\"$(CONFIG_OPENSC_ENGINE_PATH)\"
+endif
+
+ifdef CONFIG_NO_OPENSC_ENGINE_PATH
+CFLAGS += -DCONFIG_OPENSC_ENGINE_PATH=NULL
+endif
+
+ifdef CONFIG_PKCS11_ENGINE_PATH
+CFLAGS += -DCONFIG_PKCS11_ENGINE_PATH=\"$(CONFIG_PKCS11_ENGINE_PATH)\"
+endif
+
+ifdef CONFIG_NO_PKCS11_ENGINE_PATH
+CFLAGS += -DCONFIG_PKCS11_ENGINE_PATH=NULL
+endif
+
+ifdef CONFIG_PKCS11_MODULE_PATH
+CFLAGS += -DCONFIG_PKCS11_MODULE_PATH=\"$(CONFIG_PKCS11_MODULE_PATH)\"
+endif
+
+ifdef CONFIG_NO_PKCS11_MODULE_PATH
+CFLAGS += -DCONFIG_PKCS11_MODULE_PATH=NULL
+endif
+
+ifdef CONFIG_NO_LOAD_DYNAMIC_EAP
+CFLAGS += -DCONFIG_NO_LOAD_DYNAMIC_EAP
+endif
+
 include ../src/drivers/drivers.mak
 ifdef CONFIG_AP
 OBJS_d += $(DRV_BOTH_OBJS)
diff --git a/wpa_supplicant/aidl/aidl_manager.cpp b/wpa_supplicant/aidl/aidl_manager.cpp
index cac9ddb..707299d 100644
--- a/wpa_supplicant/aidl/aidl_manager.cpp
+++ b/wpa_supplicant/aidl/aidl_manager.cpp
@@ -16,6 +16,7 @@
 #include <android/binder_process.h>
 #include <android/binder_manager.h>
 #include <aidl/android/hardware/wifi/supplicant/IpVersion.h>
+#include <cutils/properties.h>
 
 extern "C" {
 #include "scan.h"
@@ -38,20 +39,12 @@
 constexpr size_t kUmtsAutnLenBytes = EAP_AKA_AUTN_LEN;
 const std::vector<uint8_t> kZeroBssid = {0, 0, 0, 0, 0, 0};
 int32_t aidl_service_version = 0;
+int32_t aidl_client_version = 0;
 
 using aidl::android::hardware::wifi::supplicant::GsmRand;
 using aidl::android::hardware::wifi::supplicant::KeyMgmtMask;
 
 /**
-  * Check that the AIDL service is running at least the expected version.
-  * Use to avoid the case where the AIDL interface version
-  * is greater than the version implemented by the service.
-  */
-inline int32_t isAidlServiceVersionAtLeast(int32_t expected_version)
-{
-	return expected_version <= aidl_service_version;
-}
-/**
  * Check if the provided |wpa_supplicant| structure represents a P2P iface or
  * not.
  */
@@ -94,6 +87,10 @@
 		return 1;
 	}
 	callback_list.push_back(callback);
+	if (aidl_client_version == 0) {
+	    callback->getInterfaceVersion(&aidl_client_version);
+	    wpa_printf(MSG_INFO, "AIDL client version: %d", aidl_client_version);
+	}
 	return 0;
 }
 
@@ -413,6 +410,21 @@
 	instance_ = NULL;
 }
 
+/**
+ * Check that the AIDL service is running at least the expected version.
+ * Use to avoid the case where the AIDL interface version
+ * is greater than the version implemented by the service.
+ */
+int32_t AidlManager::isAidlServiceVersionAtLeast(int32_t expected_version)
+{
+	return expected_version <= aidl_service_version;
+}
+
+int32_t AidlManager::isAidlClientVersionAtLeast(int32_t expected_version)
+{
+	return expected_version <= aidl_client_version;
+}
+
 int AidlManager::registerAidlService(struct wpa_global *global)
 {
 	// Create the main aidl service object and register it.
@@ -1344,7 +1356,7 @@
 			std::back_inserter(aidl_vendor_elems));
 	}
 
-	if (isAidlServiceVersionAtLeast(3)) {
+	if (isAidlServiceVersionAtLeast(3) && isAidlClientVersionAtLeast(3)) {
 		P2pDeviceFoundEventParams params;
 		params.srcAddress = macAddrToArray(addr);
 		params.p2pDeviceAddress = macAddrToArray(info->p2p_device_addr);
@@ -1362,6 +1374,7 @@
 			&ISupplicantP2pIfaceCallback::onDeviceFoundWithParams,
 			std::placeholders::_1, params));
 	} else {
+	    // Use legacy callback if service or client interface version < 3
 		const std::function<
 			ndk::ScopedAStatus(std::shared_ptr<ISupplicantP2pIfaceCallback>)>
 			func = std::bind(
@@ -1606,12 +1619,12 @@
 	}
 	bool aidl_is_request = (request == 1);
 
-	if (isAidlServiceVersionAtLeast(3)) {
+	if (isAidlServiceVersionAtLeast(3) && isAidlClientVersionAtLeast(3)) {
 		P2pProvisionDiscoveryCompletedEventParams params;
 		params.p2pDeviceAddress =  macAddrToArray(dev_addr);
 		params.isRequest = aidl_is_request;
 		params.status = static_cast<P2pProvDiscStatusCode>(status);
-		params.configMethods = static_cast<WpsConfigMethods>(config_methods);
+		params.configMethods = config_methods;
 		params.generatedPin = aidl_generated_pin;
 		if (group_ifname != NULL) {
 			params.groupInterfaceName = misc_utils::charBufToString(group_ifname);
@@ -1622,7 +1635,7 @@
 			&ISupplicantP2pIfaceCallback::onProvisionDiscoveryCompletedEvent,
 			std::placeholders::_1, params));
 	} else {
-		// Use legacy callback if interface version < 3
+		// Use legacy callback if service or client interface version < 3
 		callWithEachP2pIfaceCallback(
 			misc_utils::charBufToString(wpa_s->ifname),
 			std::bind(
@@ -1662,7 +1675,7 @@
 	if (!wpa_s)
 		return;
 
-	if (isAidlServiceVersionAtLeast(3)) {
+	if (isAidlServiceVersionAtLeast(3) && isAidlClientVersionAtLeast(3)) {
 		P2pPeerClientJoinedEventParams params;
 		params.groupInterfaceName = misc_utils::charBufToString(wpa_group_s->ifname);
 		params.clientInterfaceAddress = macAddrToArray(sta);
@@ -1679,7 +1692,7 @@
 			&ISupplicantP2pIfaceCallback::onPeerClientJoined,
 			std::placeholders::_1, params));
 	} else {
-		// Use legacy callback if interface version < 3
+		// Use legacy callback if service or client interface version < 3
 		callWithEachP2pIfaceCallback(
 			misc_utils::charBufToString(wpa_s->ifname),
 			std::bind(
@@ -1698,7 +1711,7 @@
 	if (!wpa_s)
 		return;
 
-	if (isAidlServiceVersionAtLeast(3)) {
+	if (isAidlServiceVersionAtLeast(3) && isAidlClientVersionAtLeast(3)) {
 		P2pPeerClientDisconnectedEventParams params;
 		params.groupInterfaceName = misc_utils::charBufToString(wpa_group_s->ifname);
 		params.clientInterfaceAddress = macAddrToArray(sta);
@@ -1711,7 +1724,7 @@
 			&ISupplicantP2pIfaceCallback::onPeerClientDisconnected,
 			std::placeholders::_1, params));
 	} else {
-		// Use legacy callback if interface version < 3
+		// Use legacy callback if service or client interface version < 3
 		callWithEachP2pIfaceCallback(
 			misc_utils::charBufToString(wpa_s->ifname),
 			std::bind(
diff --git a/wpa_supplicant/aidl/aidl_manager.h b/wpa_supplicant/aidl/aidl_manager.h
index cd9d5f6..b3dfa82 100644
--- a/wpa_supplicant/aidl/aidl_manager.h
+++ b/wpa_supplicant/aidl/aidl_manager.h
@@ -171,6 +171,8 @@
 				       enum mlo_info_change_reason reason);
 
 	// Methods called from aidl objects.
+	int32_t isAidlServiceVersionAtLeast(int32_t expected_version);
+	int32_t isAidlClientVersionAtLeast(int32_t expected_version);
 	void notifyExtRadioWorkStart(struct wpa_supplicant *wpa_s, uint32_t id);
 	void notifyExtRadioWorkTimeout(
 		struct wpa_supplicant *wpa_s, uint32_t id);
diff --git a/wpa_supplicant/aidl/p2p_iface.cpp b/wpa_supplicant/aidl/p2p_iface.cpp
index ec52b44..7afc8a1 100644
--- a/wpa_supplicant/aidl/p2p_iface.cpp
+++ b/wpa_supplicant/aidl/p2p_iface.cpp
@@ -830,6 +830,29 @@
 		&P2pIface::findWithParamsInternal, in_discoveryInfo);
 }
 
+::ndk::ScopedAStatus P2pIface::configureExtListenWithParams(
+		const P2pExtListenInfo& in_extListenInfo)
+{
+	return validateAndCall(
+		this, SupplicantStatusCode::FAILURE_IFACE_INVALID,
+		&P2pIface::configureExtListenWithParamsInternal, in_extListenInfo);
+}
+
+::ndk::ScopedAStatus P2pIface::addGroupWithConfigurationParams(
+		const P2pAddGroupConfigurationParams& in_groupConfigurationParams)
+{
+	return validateAndCall(
+		this, SupplicantStatusCode::FAILURE_IFACE_INVALID,
+		&P2pIface::addGroupWithConfigurationParamsInternal, in_groupConfigurationParams);
+}
+
+::ndk::ScopedAStatus P2pIface::createGroupOwner(
+		const P2pCreateGroupOwnerInfo& in_groupOwnerInfo)
+{
+	return validateAndCall(
+		this, SupplicantStatusCode::FAILURE_IFACE_INVALID,
+		&P2pIface::createGroupOwnerInternal, in_groupOwnerInfo);
+}
 std::pair<std::string, ndk::ScopedAStatus> P2pIface::getNameInternal()
 {
 	return {ifname_, ndk::ScopedAStatus::ok()};
@@ -1894,6 +1917,32 @@
 	}
 }
 
+ndk::ScopedAStatus P2pIface::configureExtListenWithParamsInternal(
+	const P2pExtListenInfo& extListenInfo)
+{
+	return configureExtListenInternal(extListenInfo.periodMs, extListenInfo.intervalMs);
+}
+
+ndk::ScopedAStatus P2pIface::addGroupWithConfigurationParamsInternal(
+	const P2pAddGroupConfigurationParams& groupConfigurationParams)
+{
+	std::vector<uint8_t> goInterfaceAddressVec {
+		groupConfigurationParams.goInterfaceAddress.begin(),
+		groupConfigurationParams.goInterfaceAddress.end()};
+	return addGroupWithConfigInternal(
+		groupConfigurationParams.ssid, groupConfigurationParams.passphrase,
+		groupConfigurationParams.isPersistent, groupConfigurationParams.frequencyMHzOrBand,
+		goInterfaceAddressVec,
+		groupConfigurationParams.joinExistingGroup);
+}
+
+ndk::ScopedAStatus P2pIface::createGroupOwnerInternal(
+	const P2pCreateGroupOwnerInfo& groupOwnerInfo)
+{
+	return addGroupInternal(
+		groupOwnerInfo.persistent, groupOwnerInfo.persistentNetworkId);
+}
+
 /**
  * Retrieve the underlying |wpa_supplicant| struct
  * pointer for this iface.
diff --git a/wpa_supplicant/aidl/p2p_iface.h b/wpa_supplicant/aidl/p2p_iface.h
index c4f036c..98556d1 100644
--- a/wpa_supplicant/aidl/p2p_iface.h
+++ b/wpa_supplicant/aidl/p2p_iface.h
@@ -178,6 +178,13 @@
 	::ndk::ScopedAStatus connectWithParams(
 		const P2pConnectInfo& in_connectInfo, std::string* _aidl_return) override;
 	::ndk::ScopedAStatus findWithParams(const P2pDiscoveryInfo& in_discoveryInfo) override;
+	::ndk::ScopedAStatus configureExtListenWithParams(
+		const P2pExtListenInfo& in_extListenInfo) override;
+	::ndk::ScopedAStatus addGroupWithConfigurationParams(
+		const P2pAddGroupConfigurationParams& in_groupConfigurationParams) override;
+	::ndk::ScopedAStatus createGroupOwner(
+		const P2pCreateGroupOwnerInfo& in_groupOwnerInfo) override;
+
 
 private:
 	// Corresponding worker functions for the AIDL methods.
@@ -304,6 +311,11 @@
 	std::pair<std::string, ndk::ScopedAStatus> connectWithParamsInternal(
 		const P2pConnectInfo& connectInfo);
 	ndk::ScopedAStatus findWithParamsInternal(const P2pDiscoveryInfo& discoveryInfo);
+	ndk::ScopedAStatus configureExtListenWithParamsInternal(const P2pExtListenInfo& extListenInfo);
+	ndk::ScopedAStatus addGroupWithConfigurationParamsInternal(
+		const P2pAddGroupConfigurationParams& groupConfigurationParams);
+	ndk::ScopedAStatus createGroupOwnerInternal(
+		const P2pCreateGroupOwnerInfo& groupOwnerInfo);
 
 	struct wpa_supplicant* retrieveIfacePtr();
 	struct wpa_supplicant* retrieveGroupIfacePtr(
diff --git a/wpa_supplicant/aidl/sta_iface.cpp b/wpa_supplicant/aidl/sta_iface.cpp
index 78e6cd3..a4c2c36 100644
--- a/wpa_supplicant/aidl/sta_iface.cpp
+++ b/wpa_supplicant/aidl/sta_iface.cpp
@@ -41,7 +41,8 @@
 using aidl::android::hardware::wifi::supplicant::WifiTechnology;
 using aidl::android::hardware::wifi::supplicant::misc_utils::createStatus;
 
-// TODO (b/204810426): Import from wifi vendor AIDL interface when it exists
+// Enum definition copied from the Vendor HAL interface.
+// See android.hardware.wifi.WifiChannelWidthInMhz
 enum WifiChannelWidthInMhz {
   WIDTH_20	= 0,
   WIDTH_40	= 1,
@@ -1880,6 +1881,7 @@
 		}
 		capa.maxNumberRxSpatialStreams = wpa_s->connection_max_nss_rx;
 		capa.maxNumberTxSpatialStreams = wpa_s->connection_max_nss_tx;
+		capa.apTidToLinkMapNegotiationSupported = wpa_s->ap_t2lm_negotiation_support;
 	} else {
 		capa.technology = WifiTechnology::UNKNOWN;
 		capa.channelBandwidth = WifiChannelWidthInMhz::WIDTH_20;
@@ -2265,9 +2267,73 @@
 	return 0;
 }
 
+inline bool hasOptQosCharField(QosCharacteristics chars, QosCharacteristics::QosCharacteristicsMask field) {
+	return chars.optionalFieldMask & static_cast<uint32_t>(field);
+}
+
+static int parseQosCharacteristics(struct scs_desc_elem *descElem, QosPolicyScsData qosPolicy) {
+	struct qos_characteristics* suppChars = &descElem->qos_char_elem;
+	if (!qosPolicy.QosCharacteristics) {
+		suppChars->available = false;
+		return 0;
+	}
+
+	QosCharacteristics inputChars = qosPolicy.QosCharacteristics.value();
+	suppChars->available = true;
+
+	if (qosPolicy.direction == QosPolicyScsData::LinkDirection::DOWNLINK) {
+		suppChars->direction = SCS_DIRECTION_DOWN;
+	} else if (qosPolicy.direction == QosPolicyScsData::LinkDirection::UPLINK) {
+		suppChars->direction = SCS_DIRECTION_UP;
+	} else {
+		wpa_printf(MSG_ERROR, "Invalid QoS direction: %d", static_cast<int>(qosPolicy.direction));
+		return -1;
+	}
+
+	// Mandatory fields
+	suppChars->min_si = inputChars.minServiceIntervalUs;
+	suppChars->max_si = inputChars.maxServiceIntervalUs;
+	suppChars->min_data_rate = inputChars.minDataRateKbps;
+	suppChars->delay_bound = inputChars.delayBoundUs;
+
+	// Optional fields
+	uint16_t suppMask = 0;
+	if (hasOptQosCharField(inputChars, QosCharacteristics::QosCharacteristicsMask::MAX_MSDU_SIZE)) {
+		suppMask |= SCS_QOS_BIT_MAX_MSDU_SIZE;
+		suppChars->max_msdu_size = inputChars.maxMsduSizeOctets;
+	}
+	if (hasOptQosCharField(inputChars, QosCharacteristics::QosCharacteristicsMask::SERVICE_START_TIME)) {
+		// Client must provide both the service start time and the link ID if this field exists.
+		suppMask |= SCS_QOS_BIT_SERVICE_START_TIME | SCS_QOS_BIT_SERVICE_START_TIME_LINKID;
+		suppChars->service_start_time = inputChars.serviceStartTimeUs;
+		suppChars->service_start_time_link_id = inputChars.serviceStartTimeLinkId;
+	}
+	if (hasOptQosCharField(inputChars, QosCharacteristics::QosCharacteristicsMask::MEAN_DATA_RATE)) {
+		suppMask |= SCS_QOS_BIT_MEAN_DATA_RATE;
+		suppChars->mean_data_rate = inputChars.meanDataRateKbps;
+	}
+	if (hasOptQosCharField(inputChars, QosCharacteristics::QosCharacteristicsMask::BURST_SIZE)) {
+		suppMask |= SCS_QOS_BIT_DELAYED_BOUNDED_BURST_SIZE;
+		suppChars->burst_size = inputChars.burstSizeOctets;
+	}
+	if (hasOptQosCharField(inputChars, QosCharacteristics::QosCharacteristicsMask::MSDU_LIFETIME)) {
+		suppMask |= SCS_QOS_BIT_MSDU_LIFETIME;
+		suppChars->msdu_lifetime = inputChars.msduLifetimeMs;
+	}
+	if (hasOptQosCharField(inputChars, QosCharacteristics::QosCharacteristicsMask::MSDU_DELIVERY_INFO)) {
+		suppMask |= SCS_QOS_BIT_MSDU_DELIVERY_INFO;
+		// Expects the delivery ratio in the lower 4 bits and the count exponent
+		// in the upper 4 bits. See Figure 9-1001aw in the 802.11be spec.
+		suppChars->msdu_delivery_info = inputChars.msduDeliveryInfo.countExponent << 4
+			| (uint8_t) inputChars.msduDeliveryInfo.deliveryRatio;
+	}
+	suppChars->mask = suppMask;
+	return 0;
+}
+
 /**
  * This is a request to the AP (if it supports the feature) to apply the QoS policy
- * on traffic in the Downlink.
+ * on traffic in the Downlink or Uplink direction.
  */
 std::pair<std::vector<QosPolicyScsRequestStatus>, ndk::ScopedAStatus>
 StaIface::addQosPolicyRequestForScsInternal(const std::vector<QosPolicyScsData>& qosPolicyData)
@@ -2286,6 +2352,11 @@
 	}
 	free_up_scs_desc(scs_data);
 
+	// Uplink policies are not supported before AIDL V3.
+	AidlManager *aidl_manager = AidlManager::getInstance();
+	WPA_ASSERT(aidl_manager);
+	bool supportsUplink = aidl_manager->isAidlServiceVersionAtLeast(3);
+
 	/**
 	 * format:
 	 * [scs_id=<decimal number>] [scs_up=<0-7>]
@@ -2321,39 +2392,49 @@
 		}
 
 		status.qosPolicyScsRequestStatusCode = QosPolicyScsRequestStatusCode::INVALID;
-		user_priority = qosPolicyData[i].userPriority;
-		if (user_priority < 0 || user_priority > 7) {
-			wpa_printf(MSG_ERROR,
-				   "Intra-Access user priority invalid %d", user_priority);
+		if (parseQosCharacteristics(&desc_elem, qosPolicyData[i])) {
 			reports.push_back(status);
 			continue;
 		}
 
-		desc_elem.intra_access_priority = user_priority;
-		desc_elem.scs_up_avail = true;
+		// TCLAS elements only need to be processed for downlink policies.
+		QosPolicyScsData::LinkDirection policyDirection = supportsUplink
+			? qosPolicyData[i].direction : QosPolicyScsData::LinkDirection::DOWNLINK;
+		if (policyDirection == QosPolicyScsData::LinkDirection::DOWNLINK) {
+			user_priority = qosPolicyData[i].userPriority;
+			if (user_priority < 0 || user_priority > 7) {
+				wpa_printf(MSG_ERROR,
+					"Intra-Access user priority invalid %d", user_priority);
+				reports.push_back(status);
+				continue;
+			}
 
-		/**
-		 * Supported classifier type 4.
-		 */
-		desc_elem.tclas_elems = (struct tclas_element *) os_malloc(sizeof(struct tclas_element));
-		if (!desc_elem.tclas_elems) {
-			wpa_printf(MSG_ERROR,
-				   "Classifier type4 failed with Bad malloc");
-			reports.push_back(status);
-			continue;
+			desc_elem.intra_access_priority = user_priority;
+			desc_elem.scs_up_avail = true;
+
+			/**
+			* Supported classifier type 4.
+			*/
+			desc_elem.tclas_elems = (struct tclas_element *) os_malloc(sizeof(struct tclas_element));
+			if (!desc_elem.tclas_elems) {
+				wpa_printf(MSG_ERROR,
+					"Classifier type4 failed with Bad malloc");
+				reports.push_back(status);
+				continue;
+			}
+
+			elem = desc_elem.tclas_elems;
+			memset(elem, 0, sizeof(struct tclas_element));
+			elem->classifier_type = 4;
+			if (scs_parse_type4(elem, qosPolicyData[i]) < 0) {
+				os_free(elem);
+				reports.push_back(status);
+				continue;
+			}
+
+			desc_elem.num_tclas_elem = 1;
 		}
 
-		elem = desc_elem.tclas_elems;
-		memset(elem, 0, sizeof(struct tclas_element));
-		elem->classifier_type = 4;
-		if (scs_parse_type4(elem, qosPolicyData[i]) < 0) {
-			os_free(elem);
-			reports.push_back(status);
-			continue;
-		}
-
-		desc_elem.num_tclas_elem = 1;
-
 		/* Reallocate memory to scs_desc_elems to accomodate further policies */
 		new_desc_elems = static_cast<struct scs_desc_elem *>(os_realloc(scs_data->scs_desc_elems,
 				(num_scs_ids + 1) * sizeof(struct scs_desc_elem)));
@@ -2440,11 +2521,46 @@
 }
 
 ::ndk::ScopedAStatus StaIface::configureMscsInternal(const MscsParams& params) {
-	return createStatus(SupplicantStatusCode::FAILURE_UNSUPPORTED);
+	struct wpa_supplicant *wpa_s = retrieveIfacePtr();
+	struct robust_av_data *robust_av = &wpa_s->robust_av;
+	os_memset(robust_av, 0, sizeof(struct robust_av_data));
+
+	if (params.upLimit < 0 || params.upLimit > 7) {
+		wpa_printf(MSG_ERROR, "Invalid MSCS params - upLimit=%d", params.upLimit);
+		return createStatus(SupplicantStatusCode::FAILURE_ARGS_INVALID);
+	}
+	if (params.streamTimeoutUs < 0 || params.streamTimeoutUs > 60000000 /* 60 sec */) {
+		wpa_printf(MSG_ERROR, "Invalid MSCS params - streamTimeoutUs=%d", params.streamTimeoutUs);
+		return createStatus(SupplicantStatusCode::FAILURE_ARGS_INVALID);
+	}
+
+	robust_av->request_type = SCS_REQ_ADD;
+	robust_av->up_bitmap = params.upBitmap;
+	robust_av->up_limit = params.upLimit;
+	robust_av->stream_timeout = params.streamTimeoutUs;
+	robust_av->frame_classifier[0] = params.frameClassifierMask;  // single type-4 frame classifier mask
+	robust_av->frame_classifier_len = 1;
+
+	int status = wpas_send_mscs_req(wpa_s);
+	wpa_printf(MSG_INFO, "MSCS add request status: %d", status);
+
+	// Mark config as invalid to avoid retransmitting automatically.
+	robust_av->valid_config = false;
+	return ndk::ScopedAStatus::ok();
 }
 
 ::ndk::ScopedAStatus StaIface::disableMscsInternal() {
-	return createStatus(SupplicantStatusCode::FAILURE_UNSUPPORTED);
+	struct wpa_supplicant *wpa_s = retrieveIfacePtr();
+	struct robust_av_data *robust_av = &wpa_s->robust_av;
+	os_memset(robust_av, 0, sizeof(struct robust_av_data));
+
+	robust_av->request_type = SCS_REQ_REMOVE;
+	robust_av->valid_config = false;
+
+	int status = wpas_send_mscs_req(wpa_s);
+	wpa_printf(MSG_INFO, "MSCS remove request status: %d", status);
+
+	return ndk::ScopedAStatus::ok();
 }
 
 /**
diff --git a/wpa_supplicant/ap.c b/wpa_supplicant/ap.c
index 62d2e90..1b94fe5 100644
--- a/wpa_supplicant/ap.c
+++ b/wpa_supplicant/ap.c
@@ -314,8 +314,20 @@
 #ifdef CONFIG_HT_OVERRIDES
 		if (ssid->disable_ht)
 			ssid->ht = 0;
+		if (ssid->disable_ht40)
+			ssid->ht40 = 0;
 #endif /* CONFIG_HT_OVERRIDES */
 
+#ifdef CONFIG_VHT_OVERRIDES
+		if (ssid->disable_vht)
+			ssid->vht = 0;
+#endif /* CONFIG_VHT_OVERRIDES */
+
+#ifdef CONFIG_HE_OVERRIDES
+		if (ssid->disable_he)
+			ssid->he = 0;
+#endif /* CONFIG_HE_OVERRIDES */
+
 		if (!ssid->ht) {
 			wpa_printf(MSG_DEBUG,
 				   "HT not enabled in network profile");
@@ -1048,6 +1060,7 @@
 		return -1;
 	hapd_iface->owner = wpa_s;
 	hapd_iface->drv_flags = wpa_s->drv_flags;
+	hapd_iface->drv_flags2 = wpa_s->drv_flags2;
 	hapd_iface->probe_resp_offloads = wpa_s->probe_resp_offloads;
 	hapd_iface->extended_capa = wpa_s->extended_capa;
 	hapd_iface->extended_capa_mask = wpa_s->extended_capa_mask;
diff --git a/wpa_supplicant/bgscan_learn.c b/wpa_supplicant/bgscan_learn.c
index 75bdec1..9830c4a 100644
--- a/wpa_supplicant/bgscan_learn.c
+++ b/wpa_supplicant/bgscan_learn.c
@@ -305,7 +305,7 @@
 	}
 
 	wpa_printf(MSG_DEBUG, "bgscan learn: Request a background scan");
-	if (wpa_supplicant_trigger_scan(wpa_s, &params)) {
+	if (wpa_supplicant_trigger_scan(wpa_s, &params, true, false)) {
 		wpa_printf(MSG_DEBUG, "bgscan learn: Failed to trigger scan");
 		eloop_register_timeout(data->scan_interval, 0,
 				       bgscan_learn_timeout, data, NULL);
diff --git a/wpa_supplicant/bgscan_simple.c b/wpa_supplicant/bgscan_simple.c
index 5a8f97c..f398b85 100644
--- a/wpa_supplicant/bgscan_simple.c
+++ b/wpa_supplicant/bgscan_simple.c
@@ -49,7 +49,7 @@
 	 */
 
 	wpa_printf(MSG_DEBUG, "bgscan simple: Request a background scan");
-	if (wpa_supplicant_trigger_scan(wpa_s, &params)) {
+	if (wpa_supplicant_trigger_scan(wpa_s, &params, true, false)) {
 		wpa_printf(MSG_DEBUG, "bgscan simple: Failed to trigger scan");
 		eloop_register_timeout(data->scan_interval, 0,
 				       bgscan_simple_timeout, data, NULL);
diff --git a/wpa_supplicant/bss.c b/wpa_supplicant/bss.c
index 3204414..c1be660 100644
--- a/wpa_supplicant/bss.c
+++ b/wpa_supplicant/bss.c
@@ -183,9 +183,8 @@
 }
 
 
-static void wpa_bss_update_pending_connect(struct wpa_supplicant *wpa_s,
-					   struct wpa_bss *old_bss,
-					   struct wpa_bss *new_bss)
+static struct wpa_connect_work *
+wpa_bss_check_pending_connect(struct wpa_supplicant *wpa_s, struct wpa_bss *bss)
 {
 	struct wpa_radio_work *work;
 	struct wpa_connect_work *cwork;
@@ -194,12 +193,19 @@
 	if (!work)
 		work = radio_work_pending(wpa_s, "connect");
 	if (!work)
-		return;
+		return NULL;
 
 	cwork = work->ctx;
-	if (cwork->bss != old_bss)
-		return;
+	if (cwork->bss != bss)
+		return NULL;
 
+	return cwork;
+}
+
+
+static void wpa_bss_update_pending_connect(struct wpa_connect_work *cwork,
+					   struct wpa_bss *new_bss)
+{
 	wpa_printf(MSG_DEBUG,
 		   "Update BSS pointer for the pending connect radio work");
 	cwork->bss = new_bss;
@@ -211,6 +217,8 @@
 void wpa_bss_remove(struct wpa_supplicant *wpa_s, struct wpa_bss *bss,
 		    const char *reason)
 {
+	struct wpa_connect_work *cwork;
+
 	if (wpa_s->last_scan_res) {
 		unsigned int i;
 		for (i = 0; i < wpa_s->last_scan_res_used; i++) {
@@ -224,7 +232,9 @@
 			}
 		}
 	}
-	wpa_bss_update_pending_connect(wpa_s, bss, NULL);
+	cwork = wpa_bss_check_pending_connect(wpa_s, bss);
+	if (cwork)
+		wpa_bss_update_pending_connect(cwork, NULL);
 	dl_list_del(&bss->list);
 	dl_list_del(&bss->list_id);
 	wpa_s->num_bss--;
@@ -286,6 +296,7 @@
 	dst->flags = src->flags;
 	os_memcpy(dst->bssid, src->bssid, ETH_ALEN);
 	dst->freq = src->freq;
+	dst->max_cw = src->max_cw;
 	dst->beacon_int = src->beacon_int;
 	dst->caps = src->caps;
 	dst->qual = src->qual;
@@ -385,6 +396,9 @@
 	if (bss == wpa_s->current_bss)
 		return 1;
 
+	if (bss == wpa_s->ml_connect_probe_bss)
+		return 1;
+
 	if (wpa_s->current_bss &&
 	    (bss->ssid_len != wpa_s->current_bss->ssid_len ||
 	     os_memcmp(bss->ssid, wpa_s->current_bss->ssid,
@@ -725,20 +739,34 @@
 	} else {
 		struct wpa_bss *nbss;
 		struct dl_list *prev = bss->list_id.prev;
+		struct wpa_connect_work *cwork;
+		unsigned int i;
+		bool update_current_bss = wpa_s->current_bss == bss;
+		bool update_ml_probe_bss = wpa_s->ml_connect_probe_bss == bss;
+
+		cwork = wpa_bss_check_pending_connect(wpa_s, bss);
+
+		for (i = 0; i < wpa_s->last_scan_res_used; i++) {
+			if (wpa_s->last_scan_res[i] == bss)
+				break;
+		}
+
 		dl_list_del(&bss->list_id);
 		nbss = os_realloc(bss, sizeof(*bss) + res->ie_len +
 				  res->beacon_ie_len);
 		if (nbss) {
-			unsigned int i;
-			for (i = 0; i < wpa_s->last_scan_res_used; i++) {
-				if (wpa_s->last_scan_res[i] == bss) {
-					wpa_s->last_scan_res[i] = nbss;
-					break;
-				}
-			}
-			if (wpa_s->current_bss == bss)
+			if (i != wpa_s->last_scan_res_used)
+				wpa_s->last_scan_res[i] = nbss;
+
+			if (update_current_bss)
 				wpa_s->current_bss = nbss;
-			wpa_bss_update_pending_connect(wpa_s, bss, nbss);
+
+			if (update_ml_probe_bss)
+				wpa_s->ml_connect_probe_bss = nbss;
+
+			if (cwork)
+				wpa_bss_update_pending_connect(cwork, nbss);
+
 			bss = nbss;
 			os_memcpy(bss->ies, res + 1,
 				  res->ie_len + res->beacon_ie_len);
@@ -1194,6 +1222,22 @@
 
 
 /**
+ * wpa_bss_get_ie_nth - Fetch a specified information element from a BSS entry
+ * @bss: BSS table entry
+ * @ie: Information element identitifier (WLAN_EID_*)
+ * @nth: Return the nth element of the requested type (2 returns the second)
+ * Returns: Pointer to the information element (id field) or %NULL if not found
+ *
+ * This function returns the nth matching information element in the BSS
+ * entry.
+ */
+const u8 * wpa_bss_get_ie_nth(const struct wpa_bss *bss, u8 ie, int nth)
+{
+	return get_ie_nth(wpa_bss_ie_ptr(bss), bss->ie_len, ie, nth);
+}
+
+
+/**
  * wpa_bss_get_ie_ext - Fetch a specified extended IE from a BSS entry
  * @bss: BSS table entry
  * @ext: Information element extension identifier (WLAN_EID_EXT_*)
@@ -1468,3 +1512,315 @@
 
 	return ieee802_11_defrag_mle(&elems, type);
 }
+
+
+static void
+wpa_bss_parse_ml_rnr_ap_info(struct wpa_supplicant *wpa_s,
+			     struct wpa_bss *bss, u8 mbssid_idx,
+			     const struct ieee80211_neighbor_ap_info *ap_info,
+			     size_t len, u16 *seen, u16 *missing)
+{
+	const u8 *pos, *end;
+	const u8 *mld_params;
+	u8 count, mld_params_offset;
+	u8 i, type, link_id;
+
+	count = RNR_TBTT_INFO_COUNT_VAL(ap_info->tbtt_info_hdr) + 1;
+	type = ap_info->tbtt_info_hdr & RNR_TBTT_INFO_HDR_TYPE_MSK;
+
+	/* MLD information is at offset 13 or at start */
+	if (type == 0 && ap_info->tbtt_info_len >= RNR_TBTT_INFO_MLD_LEN) {
+		/* MLD info is appended */
+		mld_params_offset = RNR_TBTT_INFO_LEN;
+	} else {
+		/* TODO: Support NSTR AP */
+		return;
+	}
+
+	pos = (const u8 *) ap_info;
+	end = pos + len;
+	pos += sizeof(*ap_info);
+
+	for (i = 0; i < count; i++) {
+		if (bss->n_mld_links >= MAX_NUM_MLD_LINKS)
+			return;
+
+		if (end - pos < ap_info->tbtt_info_len)
+			break;
+
+		mld_params = pos + mld_params_offset;
+
+		link_id = *(mld_params + 1) & EHT_ML_LINK_ID_MSK;
+
+		if (*mld_params != mbssid_idx) {
+			wpa_printf(MSG_DEBUG,
+				   "MLD: Reported link not part of MLD");
+		} else if (!(BIT(link_id) & *seen)) {
+			struct wpa_bss *neigh_bss =
+				wpa_bss_get_bssid(wpa_s, ap_info->data + 1);
+
+			*seen |= BIT(link_id);
+			wpa_printf(MSG_DEBUG, "MLD: mld ID=%u, link ID=%u",
+				   *mld_params, link_id);
+
+			if (neigh_bss) {
+				struct mld_link *l;
+
+				l = &bss->mld_links[bss->n_mld_links];
+				l->link_id = link_id;
+				os_memcpy(l->bssid, ap_info->data + 1,
+					  ETH_ALEN);
+				l->freq = neigh_bss->freq;
+				bss->n_mld_links++;
+			} else {
+				*missing |= BIT(link_id);
+			}
+		}
+
+		pos += ap_info->tbtt_info_len;
+	}
+}
+
+
+/**
+ * wpa_bss_parse_basic_ml_element - Parse the Basic Multi-Link element
+ * @wpa_s: Pointer to wpa_supplicant data
+ * @bss: BSS table entry
+ * @mld_addr: AP MLD address (or %NULL)
+ * @link_info: Array to store link information (or %NULL),
+ *   should be initialized and #MAX_NUM_MLD_LINKS elements long
+ * @missing_links: Result bitmask of links that were not discovered (or %NULL)
+ * Returns: 0 on success or -1 for non-MLD or parsing failures
+ *
+ * Parses the Basic Multi-Link element of the BSS into @link_info using the scan
+ * information stored in the wpa_supplicant data to fill in information for
+ * links where possible. The @missing_links out parameter will contain any links
+ * for which no corresponding BSS was found.
+ */
+int wpa_bss_parse_basic_ml_element(struct wpa_supplicant *wpa_s,
+				   struct wpa_bss *bss,
+				   u8 *ap_mld_addr,
+				   u16 *missing_links)
+{
+	struct ieee802_11_elems elems;
+	struct wpabuf *mlbuf;
+	const struct element *elem;
+	u8 mbssid_idx = 0;
+	u8 ml_ie_len;
+	const struct ieee80211_eht_ml *eht_ml;
+	const struct eht_ml_basic_common_info *ml_basic_common_info;
+	u8 i, link_id;
+	const u16 control_mask =
+		MULTI_LINK_CONTROL_TYPE_MASK |
+		BASIC_MULTI_LINK_CTRL_PRES_LINK_ID |
+		BASIC_MULTI_LINK_CTRL_PRES_BSS_PARAM_CH_COUNT |
+		BASIC_MULTI_LINK_CTRL_PRES_MLD_CAPA;
+	const u16 control =
+		MULTI_LINK_CONTROL_TYPE_BASIC |
+		BASIC_MULTI_LINK_CTRL_PRES_LINK_ID |
+		BASIC_MULTI_LINK_CTRL_PRES_BSS_PARAM_CH_COUNT |
+		BASIC_MULTI_LINK_CTRL_PRES_MLD_CAPA;
+	u16 missing = 0;
+	u16 seen;
+	const u8 *ies_pos = wpa_bss_ie_ptr(bss);
+	size_t ies_len = bss->ie_len ? bss->ie_len : bss->beacon_ie_len;
+	int ret = -1;
+	struct mld_link *l;
+
+	if (ieee802_11_parse_elems(ies_pos, ies_len, &elems, 1) ==
+	    ParseFailed) {
+		wpa_dbg(wpa_s, MSG_DEBUG, "MLD: Failed to parse elements");
+		return ret;
+	}
+
+	mlbuf = ieee802_11_defrag_mle(&elems, MULTI_LINK_CONTROL_TYPE_BASIC);
+	if (!mlbuf) {
+		wpa_dbg(wpa_s, MSG_DEBUG, "MLD: No Multi-Link element");
+		return ret;
+	}
+
+	ml_ie_len = wpabuf_len(mlbuf);
+
+	/*
+	 * for ext ID + 2 control + common info len + MLD address +
+	 * link info
+	 */
+	if (ml_ie_len < 2UL + 1UL + ETH_ALEN + 1UL)
+		goto out;
+
+	eht_ml = (const struct ieee80211_eht_ml *) wpabuf_head(mlbuf);
+	if ((le_to_host16(eht_ml->ml_control) & control_mask) != control) {
+		wpa_printf(MSG_DEBUG,
+			   "MLD: Unexpected Multi-Link element control=0x%x (mask 0x%x expected 0x%x)",
+			   le_to_host16(eht_ml->ml_control), control_mask,
+			   control);
+		goto out;
+	}
+
+	ml_basic_common_info =
+		(const struct eht_ml_basic_common_info *) eht_ml->variable;
+
+	/* Common info length should be valid */
+	if (ml_basic_common_info->len < ETH_ALEN + 1UL)
+		goto out;
+
+	/* Get the MLD address and MLD link ID */
+	if (ap_mld_addr)
+		os_memcpy(ap_mld_addr, ml_basic_common_info->mld_addr,
+			  ETH_ALEN);
+
+	bss->n_mld_links = 0;
+	l = &bss->mld_links[bss->n_mld_links];
+	link_id = ml_basic_common_info->variable[0] & EHT_ML_LINK_ID_MSK;
+	l->link_id = link_id;
+	os_memcpy(l->bssid, bss->bssid, ETH_ALEN);
+	l->freq = bss->freq;
+
+	seen = BIT(link_id);
+	bss->n_mld_links++;
+
+	/*
+	 * The AP MLD ID in the RNR corresponds to the MBSSID index, see
+	 * IEEE P802.11be/D4.0, 9.4.2.169.2 (Neighbor AP Information field).
+	 *
+	 * For the transmitting BSSID it is clear that both the MBSSID index
+	 * and the AP MLD ID in the RNR are zero.
+	 *
+	 * For nontransmitted BSSIDs we will have a BSS generated from the
+	 * MBSSID element(s) using inheritance rules. Included in the elements
+	 * is the MBSSID Index Element. The RNR is copied from the Beacon/Probe
+	 * Response frame that was send by the transmitting BSSID. As such, the
+	 * reported AP MLD ID in the RNR will match the value in the MBSSID
+	 * Index Element.
+	 */
+	elem = (const struct element *)
+		wpa_bss_get_ie(bss, WLAN_EID_MULTIPLE_BSSID_INDEX);
+	if (elem && elem->datalen >= 1)
+		mbssid_idx = elem->data[0];
+
+	for_each_element_id(elem, WLAN_EID_REDUCED_NEIGHBOR_REPORT,
+			    wpa_bss_ie_ptr(bss),
+			    bss->ie_len ? bss->ie_len : bss->beacon_ie_len) {
+		const struct ieee80211_neighbor_ap_info *ap_info;
+		const u8 *pos = elem->data;
+		size_t len = elem->datalen;
+
+		/* RNR IE may contain more than one Neighbor AP Info */
+		while (sizeof(*ap_info) <= len) {
+			size_t ap_info_len = sizeof(*ap_info);
+			u8 count;
+
+			ap_info = (const struct ieee80211_neighbor_ap_info *)
+				pos;
+			count = RNR_TBTT_INFO_COUNT_VAL(ap_info->tbtt_info_hdr) + 1;
+			ap_info_len += count * ap_info->tbtt_info_len;
+
+			if (ap_info_len > len)
+				goto out;
+
+			wpa_bss_parse_ml_rnr_ap_info(wpa_s, bss, mbssid_idx,
+						     ap_info, len, &seen,
+						     &missing);
+
+			pos += ap_info_len;
+			len -= ap_info_len;
+		}
+	}
+
+	wpa_printf(MSG_DEBUG, "MLD: n_mld_links=%u (unresolved: 0x%04hx)",
+		   bss->n_mld_links, missing);
+
+	for (i = 0; i < bss->n_mld_links; i++) {
+		wpa_printf(MSG_DEBUG, "MLD: link=%u, bssid=" MACSTR,
+			   bss->mld_links[i].link_id,
+			   MAC2STR(bss->mld_links[i].bssid));
+	}
+
+	if (missing_links)
+		*missing_links = missing;
+
+	ret = 0;
+out:
+	wpabuf_free(mlbuf);
+	return ret;
+}
+
+
+/*
+ * wpa_bss_parse_reconf_ml_element - Parse the Reconfiguration ML element
+ * @wpa_s: Pointer to wpa_supplicant data
+ * @bss: BSS table entry
+ * Returns: The bitmap of links that are going to be removed
+ */
+u16 wpa_bss_parse_reconf_ml_element(struct wpa_supplicant *wpa_s,
+				    struct wpa_bss *bss)
+{
+	struct ieee802_11_elems elems;
+	struct wpabuf *mlbuf;
+	const u8 *pos = wpa_bss_ie_ptr(bss);
+	size_t len = bss->ie_len ? bss->ie_len : bss->beacon_ie_len;
+	const struct ieee80211_eht_ml *ml;
+	u16 removed_links = 0;
+	u8 ml_common_len;
+
+	if (ieee802_11_parse_elems(pos, len, &elems, 1) == ParseFailed)
+		return 0;
+
+	if (!elems.reconf_mle || !elems.reconf_mle_len)
+		return 0;
+
+	mlbuf = ieee802_11_defrag_mle(&elems, MULTI_LINK_CONTROL_TYPE_RECONF);
+	if (!mlbuf)
+		return 0;
+
+	ml = (const struct ieee80211_eht_ml *) wpabuf_head(mlbuf);
+	len = wpabuf_len(mlbuf);
+
+	if (len < sizeof(*ml))
+		goto out;
+
+	ml_common_len = 1;
+	if (ml->ml_control & RECONF_MULTI_LINK_CTRL_PRES_MLD_MAC_ADDR)
+		ml_common_len += ETH_ALEN;
+
+	if (len < sizeof(*ml) + ml_common_len) {
+		wpa_printf(MSG_DEBUG,
+			   "MLD: Unexpected Reconfiguration ML element length: (%zu < %zu)",
+			   len, sizeof(*ml) + ml_common_len);
+		goto out;
+	}
+
+	pos = ml->variable + ml_common_len;
+	len -= sizeof(*ml) + ml_common_len;
+
+	while (len >= 2 + sizeof(struct ieee80211_eht_per_sta_profile)) {
+		size_t sub_elem_len = *(pos + 1);
+
+		if (2 + sub_elem_len > len) {
+			wpa_printf(MSG_DEBUG,
+				   "MLD: Invalid link info len: %zu %zu",
+				   2 + sub_elem_len, len);
+			goto out;
+		}
+
+		if  (*pos == EHT_ML_SUB_ELEM_PER_STA_PROFILE) {
+			const struct ieee80211_eht_per_sta_profile *sta_prof =
+				(const struct ieee80211_eht_per_sta_profile *)
+				(pos + 2);
+			u16 control = le_to_host16(sta_prof->sta_control);
+			u8 link_id;
+
+			link_id = control & EHT_PER_STA_RECONF_CTRL_LINK_ID_MSK;
+			removed_links |= BIT(link_id);
+		}
+
+		pos += 2 + sub_elem_len;
+		len -= 2 + sub_elem_len;
+	}
+
+	wpa_printf(MSG_DEBUG, "MLD: Reconfiguration: removed_links=0x%x",
+		   removed_links);
+out:
+	wpabuf_free(mlbuf);
+	return removed_links;
+}
diff --git a/wpa_supplicant/bss.h b/wpa_supplicant/bss.h
index 611da88..042b5d2 100644
--- a/wpa_supplicant/bss.h
+++ b/wpa_supplicant/bss.h
@@ -96,6 +96,8 @@
 	size_t ssid_len;
 	/** Frequency of the channel in MHz (e.g., 2412 = channel 1) */
 	int freq;
+	/** The max channel width supported by both the AP and the STA */
+	enum chan_width max_cw;
 	/** Beacon interval in TUs (host byte order) */
 	u16 beacon_int;
 	/** Capability information field in host byte order */
@@ -124,6 +126,15 @@
 	size_t beacon_ie_len;
 	/** MLD address of the AP */
 	u8 mld_addr[ETH_ALEN];
+
+	/** An array of MLD links */
+	u8 n_mld_links;
+	struct mld_link {
+		u8 link_id;
+		u8 bssid[ETH_ALEN];
+		int freq;
+	} mld_links[MAX_NUM_MLD_LINKS];
+
 	/* followed by ie_len octets of IEs */
 	/* followed by beacon_ie_len octets of IEs */
 	u8 ies[];
@@ -160,6 +171,7 @@
 struct wpa_bss * wpa_bss_get_id_range(struct wpa_supplicant *wpa_s,
 				      unsigned int idf, unsigned int idl);
 const u8 * wpa_bss_get_ie(const struct wpa_bss *bss, u8 ie);
+const u8 * wpa_bss_get_ie_nth(const struct wpa_bss *bss, u8 ie, int nth);
 const u8 * wpa_bss_get_ie_ext(const struct wpa_bss *bss, u8 ext);
 const u8 * wpa_bss_get_vendor_ie(const struct wpa_bss *bss, u32 vendor_type);
 const u8 * wpa_bss_get_vendor_ie_beacon(const struct wpa_bss *bss,
@@ -202,5 +214,11 @@
 			   struct os_reltime *update_time);
 
 struct wpabuf * wpa_bss_defrag_mle(const struct wpa_bss *bss, u8 type);
+int wpa_bss_parse_basic_ml_element(struct wpa_supplicant *wpa_s,
+				   struct wpa_bss *bss,
+				   u8 *ap_mld_addr,
+				   u16 *missing_links);
+u16 wpa_bss_parse_reconf_ml_element(struct wpa_supplicant *wpa_s,
+				    struct wpa_bss *bss);
 
 #endif /* BSS_H */
diff --git a/wpa_supplicant/config.c b/wpa_supplicant/config.c
index c843cb0..d1d8ca3 100644
--- a/wpa_supplicant/config.c
+++ b/wpa_supplicant/config.c
@@ -793,6 +793,10 @@
 			val |= WPA_KEY_MGMT_FT_IEEE8021X_SHA384;
 #endif /* CONFIG_SHA384 */
 #endif /* CONFIG_IEEE80211R */
+#ifdef CONFIG_SHA384
+		else if (os_strcmp(start, "WPA-EAP-SHA384") == 0)
+			val |= WPA_KEY_MGMT_IEEE8021X_SHA384;
+#endif /* CONFIG_SHA384 */
 		else if (os_strcmp(start, "WPA-PSK-SHA256") == 0)
 			val |= WPA_KEY_MGMT_PSK_SHA256;
 		else if (os_strcmp(start, "WPA-EAP-SHA256") == 0)
@@ -965,6 +969,18 @@
 #endif /* CONFIG_SHA384 */
 #endif /* CONFIG_IEEE80211R */
 
+#ifdef CONFIG_SHA384
+	if (ssid->key_mgmt & WPA_KEY_MGMT_IEEE8021X_SHA384) {
+		ret = os_snprintf(pos, end - pos, "%sWPA-EAP-SHA384",
+				  pos == buf ? "" : " ");
+		if (os_snprintf_error(end - pos, ret)) {
+			end[-1] = '\0';
+			return buf;
+		}
+		pos += ret;
+	}
+#endif /* CONFIG_SHA384 */
+
 	if (ssid->key_mgmt & WPA_KEY_MGMT_PSK_SHA256) {
 		ret = os_snprintf(pos, end - pos, "%sWPA-PSK-SHA256",
 				  pos == buf ? "" : " ");
@@ -3008,9 +3024,15 @@
 		wpabuf_free(config->wps_vendor_ext[i]);
 	os_free(config->ctrl_interface);
 	os_free(config->ctrl_interface_group);
+#ifndef CONFIG_OPENSC_ENGINE_PATH
 	os_free(config->opensc_engine_path);
+#endif /* CONFIG_OPENSC_ENGINE_PATH */
+#ifndef CONFIG_PKCS11_ENGINE_PATH
 	os_free(config->pkcs11_engine_path);
+#endif /* CONFIG_PKCS11_ENGINE_PATH */
+#ifndef CONFIG_PKCS11_MODULE_PATH
 	os_free(config->pkcs11_module_path);
+#endif /* CONFIG_PKCS11_MODULE_PATH */
 	os_free(config->openssl_ciphers);
 	os_free(config->pcsc_reader);
 	str_clear_free(config->pcsc_pin);
@@ -4933,6 +4955,7 @@
 }
 
 
+#ifndef CONFIG_NO_LOAD_DYNAMIC_EAP
 static int wpa_config_process_load_dynamic_eap(
 	const struct global_parse_data *data, struct wpa_config *config,
 	int line, const char *so)
@@ -4951,6 +4974,7 @@
 
 	return 0;
 }
+#endif /* CONFIG_NO_LOAD_DYNAMIC_EAP */
 
 
 #ifdef CONFIG_WPS
@@ -5331,9 +5355,15 @@
 #endif /* CONFIG_MESH */
 	{ INT(disable_scan_offload), 0 },
 	{ INT(fast_reauth), 0 },
+#ifndef CONFIG_OPENSC_ENGINE_PATH
 	{ STR(opensc_engine_path), 0 },
+#endif /* CONFIG_OPENSC_ENGINE_PATH */
+#ifndef CONFIG_PKCS11_ENGINE_PATH
 	{ STR(pkcs11_engine_path), 0 },
+#endif /* CONFIG_PKCS11_ENGINE_PATH */
+#ifndef CONFIG_PKCS11_MODULE_PATH
 	{ STR(pkcs11_module_path), 0 },
+#endif /* CONFIG_PKCS11_MODULE_PATH */
 	{ STR(openssl_ciphers), 0 },
 	{ STR(pcsc_reader), 0 },
 	{ STR(pcsc_pin), 0 },
@@ -5345,7 +5375,9 @@
 #ifndef CONFIG_NO_CONFIG_WRITE
 	{ INT(update_config), 0 },
 #endif /* CONFIG_NO_CONFIG_WRITE */
+#ifndef CONFIG_NO_LOAD_DYNAMIC_EAP
 	{ FUNC_NO_VAR(load_dynamic_eap), 0 },
+#endif /* CONFIG_NO_LOAD_DYNAMIC_EAP */
 #ifdef CONFIG_WPS
 	{ FUNC(uuid), CFG_CHANGED_UUID },
 	{ INT_RANGE(auto_uuid, 0, 1), 0 },
@@ -5632,6 +5664,7 @@
 					   line);
 				return -1;
 			}
+			return ret;
 		}
 
 		if (os_strncmp(pos, "wmm_ac_", 7) == 0) {
diff --git a/wpa_supplicant/config.h b/wpa_supplicant/config.h
index e1f30c2..09bcbc8 100644
--- a/wpa_supplicant/config.h
+++ b/wpa_supplicant/config.h
@@ -629,6 +629,7 @@
 	 */
 	int fast_reauth;
 
+#ifndef CONFIG_OPENSC_ENGINE_PATH
 	/**
 	 * opensc_engine_path - Path to the OpenSSL engine for opensc
 	 *
@@ -636,7 +637,9 @@
 	 * engine (engine_opensc.so); if %NULL, this engine is not loaded.
 	 */
 	char *opensc_engine_path;
+#endif /* CONFIG_OPENSC_ENGINE_PATH */
 
+#ifndef CONFIG_PKCS11_ENGINE_PATH
 	/**
 	 * pkcs11_engine_path - Path to the OpenSSL engine for PKCS#11
 	 *
@@ -644,7 +647,9 @@
 	 * engine (engine_pkcs11.so); if %NULL, this engine is not loaded.
 	 */
 	char *pkcs11_engine_path;
+#endif /* CONFIG_PKCS11_ENGINE_PATH */
 
+#ifndef CONFIG_PKCS11_MODULE_PATH
 	/**
 	 * pkcs11_module_path - Path to the OpenSSL OpenSC/PKCS#11 module
 	 *
@@ -653,6 +658,7 @@
 	 * module is not loaded.
 	 */
 	char *pkcs11_module_path;
+#endif /* CONFIG_PKCS11_MODULE_PATH */
 
 	/**
 	 * openssl_ciphers - OpenSSL cipher string
diff --git a/wpa_supplicant/config_file.c b/wpa_supplicant/config_file.c
index c7b7826..ebad5f1 100644
--- a/wpa_supplicant/config_file.c
+++ b/wpa_supplicant/config_file.c
@@ -1127,15 +1127,21 @@
 			config->disable_scan_offload);
 	if (config->fast_reauth != DEFAULT_FAST_REAUTH)
 		fprintf(f, "fast_reauth=%d\n", config->fast_reauth);
+#ifndef CONFIG_OPENSC_ENGINE_PATH
 	if (config->opensc_engine_path)
 		fprintf(f, "opensc_engine_path=%s\n",
 			config->opensc_engine_path);
+#endif /* CONFIG_OPENSC_ENGINE_PATH */
+#ifndef CONFIG_PKCS11_ENGINE_PATH
 	if (config->pkcs11_engine_path)
 		fprintf(f, "pkcs11_engine_path=%s\n",
 			config->pkcs11_engine_path);
+#endif /* CONFIG_PKCS11_ENGINE_PATH */
+#ifndef CONFIG_PKCS11_MODULE_PATH
 	if (config->pkcs11_module_path)
 		fprintf(f, "pkcs11_module_path=%s\n",
 			config->pkcs11_module_path);
+#endif /* CONFIG_PKCS11_MODULE_PATH */
 	if (config->openssl_ciphers)
 		fprintf(f, "openssl_ciphers=%s\n", config->openssl_ciphers);
 	if (config->pcsc_reader)
diff --git a/wpa_supplicant/ctrl_iface.c b/wpa_supplicant/ctrl_iface.c
index 9abfeb2..a68802e 100644
--- a/wpa_supplicant/ctrl_iface.c
+++ b/wpa_supplicant/ctrl_iface.c
@@ -2967,6 +2967,16 @@
 		pos += ret;
 	}
 
+#ifdef CONFIG_SHA384
+	if (data.key_mgmt & WPA_KEY_MGMT_IEEE8021X_SHA384) {
+		ret = os_snprintf(pos, end - pos, "%sEAP-SHA384",
+				  pos == start ? "" : "+");
+		if (os_snprintf_error(end - pos, ret))
+			return pos;
+		pos += ret;
+	}
+#endif /* CONFIG_SHA384 */
+
 	pos = wpa_supplicant_cipher_txt(pos, end, data.pairwise_cipher);
 
 	if (data.capabilities & WPA_CAPABILITY_PREAUTH) {
@@ -3609,6 +3619,7 @@
 
 	if (os_strcmp(name, "bssid") != 0 &&
 	    os_strcmp(name, "bssid_hint") != 0 &&
+	    os_strcmp(name, "scan_freq") != 0 &&
 	    os_strcmp(name, "priority") != 0) {
 		wpa_sm_pmksa_cache_flush(wpa_s->wpa, ssid);
 
@@ -5686,7 +5697,8 @@
 	}
 #endif /* CONFIG_FILS */
 
-	if (!is_zero_ether_addr(bss->mld_addr)) {
+	if (!is_zero_ether_addr(bss->mld_addr) &&
+	    (mask & WPA_BSS_MASK_AP_MLD_ADDR)) {
 		ret = os_snprintf(pos, end - pos,
 				  "ap_mld_addr=" MACSTR "\n",
 				  MAC2STR(bss->mld_addr));
@@ -11528,7 +11540,16 @@
 	 * [scs_id=<decimal number>] <add|remove|change> [scs_up=<0-7>]
 	 * [classifier_type=<4|10>]
 	 * [classifier params based on classifier type]
-	 * [tclas_processing=<0|1>] [scs_id=<decimal number>] ...
+	 * [tclas_processing=<0|1>]
+	 * [qos_characteristics] <up/down/direct> [min_si=<decimal number>]
+	 * [max_si=<decimal number>] [min_data_rate=<decimal number>]
+	 * [delay_bound=<decimal number>] [max_msdu=<decimal number>]
+	 * [service_start_time=<decimal number>]
+	 * [service_start_time_link_id=<decimal number>]
+	 * [mean_data_rate=<decimal number>] [burst_size=<decimal number>]
+	 * [msdu_lifetime=<decimal number>]
+	 * [msdu_delivery_info=<decimal number>] [medium_time=<decimal number>]
+	 * [scs_id=<decimal number>] ...
 	 */
 	pos1 = os_strstr(cmd, "scs_id=");
 	if (!pos1) {
@@ -11541,9 +11562,10 @@
 	while (pos1) {
 		struct scs_desc_elem *n1;
 		struct active_scs_elem *active_scs_desc;
-		char *next_scs_desc;
+		char *next_scs_desc, *pos2;
 		unsigned int num_tclas_elem = 0;
-		bool scsid_active = false;
+		bool scsid_active = false, tclas_present = false;
+		struct qos_characteristics *qos_elem = &desc_elem.qos_char_elem;
 
 		desc_elem.scs_id = atoi(pos1 + 7);
 		pos1 += 7;
@@ -11619,8 +11641,9 @@
 		pos = os_strstr(pos1, "classifier_type=");
 		if (!pos) {
 			wpa_printf(MSG_ERROR, "classifier type empty");
-			goto free_scs_desc;
+			goto qos_characteristics;
 		}
+		tclas_present = true;
 
 		while (pos) {
 			struct tclas_element elem = { 0 }, *n;
@@ -11684,6 +11707,117 @@
 			desc_elem.tclas_processing = val;
 		}
 
+	qos_characteristics:
+		pos1 = os_strstr(pos1, "qos_characteristics");
+		if (!pos1 && !tclas_present)
+			goto free_scs_desc;
+		if (!pos1)
+			goto scs_desc_end;
+
+		qos_elem->available = true;
+		if (os_strstr(pos1, "up ")) {
+			qos_elem->direction = SCS_DIRECTION_UP;
+			if (tclas_present) {
+				wpa_printf(MSG_ERROR,
+					   "TCLAS with direction:UP not allowed");
+				goto free_scs_desc;
+			}
+		} else if (os_strstr(pos1, "down ")) {
+			qos_elem->direction = SCS_DIRECTION_DOWN;
+		} else if (os_strstr(pos1, "direct ")) {
+			qos_elem->direction = SCS_DIRECTION_DIRECT;
+		}
+
+		pos1 = os_strstr(pos1, "min_si=");
+		if (!pos1) {
+			wpa_printf(MSG_ERROR, "Min SI is required");
+			goto free_scs_desc;
+		}
+		qos_elem->min_si = atoi(pos1 + 7);
+
+		pos1 = os_strstr(pos1, "max_si=");
+		if (!pos1) {
+			wpa_printf(MSG_ERROR, "Max SI is required");
+			goto free_scs_desc;
+		}
+		qos_elem->max_si = atoi(pos1 + 7);
+
+		if (qos_elem->min_si && qos_elem->max_si &&
+		    qos_elem->max_si < qos_elem->min_si) {
+			wpa_printf(MSG_ERROR, "Invalid Max SI");
+			goto free_scs_desc;
+		}
+
+		pos1 = os_strstr(pos1, "min_data_rate=");
+		if (!pos1) {
+			wpa_printf(MSG_ERROR, "Min data rate is required");
+			goto free_scs_desc;
+		}
+		qos_elem->min_data_rate = atoi(pos1 + 14);
+
+		pos1 = os_strstr(pos1, "delay_bound=");
+		if (!pos1) {
+			wpa_printf(MSG_ERROR, "Delay Bound is required");
+			goto free_scs_desc;
+		}
+		qos_elem->delay_bound = atoi(pos1 + 12);
+
+		if (qos_elem->min_data_rate >= BIT(24) ||
+		    qos_elem->delay_bound >= BIT(24)) {
+			wpa_printf(MSG_ERROR,
+				   "Invalid min_data_rate or delay_bound");
+			goto free_scs_desc;
+		}
+
+		pos2 = os_strstr(pos1, "max_msdu=");
+		if (pos2) {
+			qos_elem->max_msdu_size = atoi(pos2 + 9);
+			qos_elem->mask |= SCS_QOS_BIT_MAX_MSDU_SIZE;
+		}
+
+		pos2 = os_strstr(pos1, "service_start_time=");
+		if (pos2) {
+			qos_elem->service_start_time = atoi(pos2 + 19);
+			qos_elem->mask |= SCS_QOS_BIT_SERVICE_START_TIME;
+		}
+
+		pos2 = os_strstr(pos1, "service_start_time_link_id=");
+		if (pos2) {
+			qos_elem->service_start_time_link_id = atoi(pos2 + 27);
+			qos_elem->mask |= SCS_QOS_BIT_SERVICE_START_TIME_LINKID;
+		}
+
+		pos2 = os_strstr(pos1, "mean_data_rate=");
+		if (pos2) {
+			qos_elem->mean_data_rate = atoi(pos2 + 15);
+			qos_elem->mask |= SCS_QOS_BIT_MEAN_DATA_RATE;
+		}
+
+		pos2 = os_strstr(pos1, "burst_size=");
+		if (pos2) {
+			qos_elem->burst_size = atoi(pos2 + 11);
+			qos_elem->mask |=
+				SCS_QOS_BIT_DELAYED_BOUNDED_BURST_SIZE;
+		}
+
+		pos2 = os_strstr(pos1, "msdu_lifetime=");
+		if (pos2) {
+			qos_elem->msdu_lifetime = atoi(pos2 + 14);
+			qos_elem->mask |= SCS_QOS_BIT_MSDU_LIFETIME;
+		}
+
+		pos2 = os_strstr(pos1, "msdu_delivery_info=");
+		if (pos2) {
+			qos_elem->msdu_delivery_info = atoi(pos2 + 19);
+			qos_elem->mask |= SCS_QOS_BIT_MSDU_DELIVERY_INFO;
+		}
+
+		pos2 = os_strstr(pos1, "medium_time=");
+		if (pos2) {
+			qos_elem->medium_time = atoi(pos2 + 12);
+			qos_elem->mask |= SCS_QOS_BIT_MEDIUM_TIME;
+		}
+
 scs_desc_end:
 		n1 = os_realloc(scs_data->scs_desc_elems, (num_scs_desc + 1) *
 				sizeof(struct scs_desc_elem));
@@ -11913,6 +12047,80 @@
 }
 
 
+#ifdef CONFIG_TESTING_OPTIONS
+static int wpas_ctrl_ml_probe(struct wpa_supplicant *wpa_s, char *cmd)
+{
+	char *token, *context = NULL;
+	u8 bssid[ETH_ALEN];
+	int mld_id = -1, link_id = -1;
+	struct wpa_bss *bss;
+	int *freqs;
+
+	os_memset(bssid, 0, sizeof(bssid));
+
+	while ((token = str_token(cmd, " ", &context))) {
+		if (os_strncmp(token, "bssid=", 6) == 0) {
+			if (hwaddr_aton(token + 6, bssid))
+				return -1;
+		} else if (os_strncmp(token, "mld_id=", 7) == 0) {
+			mld_id = atoi(token + 7);
+		} else if (os_strncmp(token, "link_id=", 8) == 0) {
+			link_id = atoi(token + 8);
+		}
+	}
+
+	if (mld_id < 0 || is_zero_ether_addr(bssid)) {
+		wpa_printf(MSG_DEBUG,
+			   "MLD: Failed parsing ML probe request arguments");
+		return -1;
+	}
+
+	bss = wpa_bss_get_bssid(wpa_s, bssid);
+	if (!bss) {
+		wpa_printf(MSG_DEBUG,
+			   "MLD: Unknown BSS for " MACSTR, MAC2STR(bssid));
+		return -1;
+	}
+
+	if (wpa_s->sched_scanning || wpa_s->scanning ||
+	    (wpa_s->wpa_state > WPA_SCANNING &&
+	     wpa_s->wpa_state != WPA_COMPLETED)) {
+		wpa_printf(MSG_DEBUG,
+			   "MLO: Ongoing scan: Reject ML probe request");
+		return -1;
+	}
+
+	freqs = os_malloc(sizeof(int) * 2);
+	if (!freqs)
+		return -1;
+
+	freqs[0] = bss->freq;
+	freqs[1] = 0;
+
+	wpa_s->manual_scan_passive = 0;
+	wpa_s->manual_scan_use_id = 0;
+	wpa_s->manual_scan_only_new = 0;
+	wpa_s->scan_id_count = 0;
+	wpa_s->scan_res_handler = scan_only_handler;
+	os_free(wpa_s->manual_scan_freqs);
+	wpa_s->manual_scan_freqs = freqs;
+
+	os_memcpy(wpa_s->ml_probe_bssid, bssid, ETH_ALEN);
+	wpa_s->ml_probe_mld_id = mld_id;
+	if (link_id >= 0)
+		wpa_s->ml_probe_links = BIT(link_id);
+
+	wpa_s->normal_scans = 0;
+	wpa_s->scan_req = MANUAL_SCAN_REQ;
+	wpa_s->after_wps = 0;
+	wpa_s->known_wps_freq = 0;
+	wpa_supplicant_req_scan(wpa_s, 0, 0);
+
+	return 0;
+}
+#endif /* CONFIG_TESTING_OPTIONS */
+
+
 char * wpa_supplicant_ctrl_iface_process(struct wpa_supplicant *wpa_s,
 					 char *buf, size_t *resp_len)
 {
@@ -12718,6 +12926,9 @@
 	} else if (os_strcmp(buf, "TWT_TEARDOWN") == 0) {
 		if (wpas_ctrl_iface_send_twt_teardown(wpa_s, ""))
 			reply_len = -1;
+	} else if (os_strncmp(buf, "ML_PROBE_REQ ", 13) == 0) {
+		if (wpas_ctrl_ml_probe(wpa_s, buf + 13))
+			reply_len = -1;
 #endif /* CONFIG_TESTING_OPTIONS */
 	} else if (os_strncmp(buf, "VENDOR_ELEM_ADD ", 16) == 0) {
 		if (wpas_ctrl_vendor_elem_add(wpa_s, buf + 16) < 0)
diff --git a/wpa_supplicant/dbus/dbus_dict_helpers.c b/wpa_supplicant/dbus/dbus_dict_helpers.c
index 0e47534..fdf7b12 100644
--- a/wpa_supplicant/dbus/dbus_dict_helpers.c
+++ b/wpa_supplicant/dbus/dbus_dict_helpers.c
@@ -362,6 +362,27 @@
 
 
 /**
+ * Add a double entry to the dict.
+ *
+ * @param iter_dict A valid DBusMessageIter returned from
+ *    wpa_dbus_dict_open_write()
+ * @param key The key of the dict item
+ * @param value The double value
+ * @return TRUE on success, FALSE on failure
+ *
+ */
+dbus_bool_t wpa_dbus_dict_append_double(DBusMessageIter *iter_dict,
+					const char *key,
+					const double value)
+{
+	if (!key)
+		return FALSE;
+	return _wpa_dbus_add_dict_entry_basic(iter_dict, key, DBUS_TYPE_DOUBLE,
+					      &value);
+}
+
+
+/**
  * Begin an array entry in the dict
  *
  * @param iter_dict A valid DBusMessageIter returned from
diff --git a/wpa_supplicant/dbus/dbus_dict_helpers.h b/wpa_supplicant/dbus/dbus_dict_helpers.h
index 44685ea..cc9e26f 100644
--- a/wpa_supplicant/dbus/dbus_dict_helpers.h
+++ b/wpa_supplicant/dbus/dbus_dict_helpers.h
@@ -59,6 +59,10 @@
 					    const char *value,
 					    const dbus_uint32_t value_len);
 
+dbus_bool_t wpa_dbus_dict_append_double(DBusMessageIter *iter_dict,
+					const char *key,
+					const double value);
+
 /* Manual construction and addition of array elements */
 dbus_bool_t wpa_dbus_dict_begin_array(DBusMessageIter *iter_dict,
 				      const char *key, const char *type,
diff --git a/wpa_supplicant/dbus/dbus_new_handlers.c b/wpa_supplicant/dbus/dbus_new_handlers.c
index 67ce970..6ad49a1 100644
--- a/wpa_supplicant/dbus/dbus_new_handlers.c
+++ b/wpa_supplicant/dbus/dbus_new_handlers.c
@@ -1594,10 +1594,10 @@
 }
 
 
-static int wpas_dbus_get_scan_allow_roam(DBusMessage *message,
-					 DBusMessageIter *var,
-					 dbus_bool_t *allow,
-					 DBusMessage **reply)
+static int wpas_dbus_get_scan_boolean(DBusMessage *message,
+				      DBusMessageIter *var,
+				      dbus_bool_t *allow,
+				      DBusMessage **reply)
 {
 	if (dbus_message_iter_get_arg_type(var) != DBUS_TYPE_BOOLEAN) {
 		wpa_printf(MSG_DEBUG, "%s[dbus]: Type must be a boolean",
@@ -1629,7 +1629,10 @@
 	char *key = NULL, *type = NULL;
 	struct wpa_driver_scan_params params;
 	size_t i;
-	dbus_bool_t allow_roam = 1;
+	dbus_bool_t allow_roam = TRUE;
+	dbus_bool_t non_coloc_6ghz = FALSE;
+	dbus_bool_t scan_6ghz_only = FALSE;
+	bool custom_ies = false;
 
 	os_memset(&params, 0, sizeof(params));
 
@@ -1656,15 +1659,28 @@
 			if (wpas_dbus_get_scan_ies(message, &variant_iter,
 						   &params, &reply) < 0)
 				goto out;
+			custom_ies = true;
 		} else if (os_strcmp(key, "Channels") == 0) {
 			if (wpas_dbus_get_scan_channels(message, &variant_iter,
 							&params, &reply) < 0)
 				goto out;
 		} else if (os_strcmp(key, "AllowRoam") == 0) {
-			if (wpas_dbus_get_scan_allow_roam(message,
-							  &variant_iter,
-							  &allow_roam,
-							  &reply) < 0)
+			if (wpas_dbus_get_scan_boolean(message,
+						       &variant_iter,
+						       &allow_roam,
+						       &reply) < 0)
+				goto out;
+		} else if (os_strcmp(key, "NonColoc6GHz") == 0) {
+			if (wpas_dbus_get_scan_boolean(message,
+						       &variant_iter,
+						       &non_coloc_6ghz,
+						       &reply) < 0)
+				goto out;
+		} else if (os_strcmp(key, "6GHzOnly") == 0) {
+			if (wpas_dbus_get_scan_boolean(message,
+						       &variant_iter,
+						       &scan_6ghz_only,
+						       &reply) < 0)
 				goto out;
 		} else {
 			wpa_printf(MSG_DEBUG, "%s[dbus]: Unknown argument %s",
@@ -1683,6 +1699,13 @@
 		goto out;
 	}
 
+	if (non_coloc_6ghz)
+		params.non_coloc_6ghz = 1;
+
+	if (scan_6ghz_only && !params.freqs)
+		wpa_add_scan_freqs_list(wpa_s, HOSTAPD_MODE_IEEE80211A, &params,
+					true, false, false);
+
 	if (os_strcmp(type, "passive") == 0) {
 		if (params.num_ssids || params.extra_ies_len) {
 			wpa_printf(MSG_DEBUG,
@@ -1703,7 +1726,8 @@
 			if (params.freqs && params.freqs[0]) {
 				wpa_s->last_scan_req = MANUAL_SCAN_REQ;
 				if (wpa_supplicant_trigger_scan(wpa_s,
-								&params)) {
+								&params,
+								false, false)) {
 					reply = wpas_dbus_error_scan_error(
 						message,
 						"Scan request rejected");
@@ -1729,7 +1753,8 @@
 		}
 
 		wpa_s->last_scan_req = MANUAL_SCAN_REQ;
-		if (wpa_supplicant_trigger_scan(wpa_s, &params)) {
+		if (wpa_supplicant_trigger_scan(wpa_s, &params, !custom_ies,
+						false)) {
 			reply = wpas_dbus_error_scan_error(
 				message, "Scan request rejected");
 		}
@@ -4318,11 +4343,18 @@
 	const struct wpa_dbus_property_desc *property_desc,
 	DBusMessageIter *iter, DBusError *error, void *user_data)
 {
+
+#ifndef CONFIG_PKCS11_ENGINE_PATH
 	struct wpa_supplicant *wpa_s = user_data;
 
 	return wpas_dbus_string_property_getter(iter,
 						wpa_s->conf->pkcs11_engine_path,
 						error);
+#else /* CONFIG_PKCS11_ENGINE_PATH */
+	return wpas_dbus_string_property_getter(iter,
+						CONFIG_PKCS11_ENGINE_PATH,
+						error);
+#endif /* CONFIG_PKCS11_ENGINE_PATH */
 }
 
 
@@ -4339,11 +4371,17 @@
 	const struct wpa_dbus_property_desc *property_desc,
 	DBusMessageIter *iter, DBusError *error, void *user_data)
 {
+#ifndef CONFIG_PKCS11_MODULE_PATH
 	struct wpa_supplicant *wpa_s = user_data;
 
 	return wpas_dbus_string_property_getter(iter,
 						wpa_s->conf->pkcs11_module_path,
 						error);
+#else /* CONFIG_PKCS11_MODULE_PATH */
+	return wpas_dbus_string_property_getter(iter,
+						CONFIG_PKCS11_MODULE_PATH,
+						error);
+#endif /* CONFIG_PKCS11_MODULE_PATH */
 }
 
 
@@ -5278,7 +5316,7 @@
 	DBusMessageIter iter_dict, variant_iter;
 	const char *group;
 	const char *pairwise[5]; /* max 5 pairwise ciphers is supported */
-	const char *key_mgmt[18]; /* max 18 key managements may be supported */
+	const char *key_mgmt[19]; /* max 19 key managements may be supported */
 	int n;
 
 	if (!dbus_message_iter_open_container(iter, DBUS_TYPE_VARIANT,
@@ -5341,6 +5379,10 @@
 #endif /* CONFIG_OWE */
 	if (ie_data->key_mgmt & WPA_KEY_MGMT_NONE)
 		key_mgmt[n++] = "wpa-none";
+#ifdef CONFIG_SHA384
+	if (ie_data->key_mgmt & WPA_KEY_MGMT_IEEE8021X_SHA384)
+		key_mgmt[n++] = "wpa-eap-sha384";
+#endif /* CONFIG_SHA384 */
 
 	if (!wpa_dbus_dict_append_string_array(&iter_dict, "KeyMgmt",
 					       key_mgmt, n))
diff --git a/wpa_supplicant/dbus/dbus_new_helpers.c b/wpa_supplicant/dbus/dbus_new_helpers.c
index 06f74ad..7fb0669 100644
--- a/wpa_supplicant/dbus/dbus_new_helpers.c
+++ b/wpa_supplicant/dbus/dbus_new_helpers.c
@@ -1026,6 +1026,23 @@
 }
 
 
+static double guard_interval_to_double(enum guard_interval value)
+{
+	switch (value) {
+	case GUARD_INTERVAL_0_4:
+		return 0.4;
+	case GUARD_INTERVAL_0_8:
+		return 0.8;
+	case GUARD_INTERVAL_1_6:
+		return 1.6;
+	case GUARD_INTERVAL_3_2:
+		return 3.2;
+	default:
+		return 0;
+	}
+}
+
+
 /**
  * wpas_dbus_new_from_signal_information - Adds a wpa_signal_info
  * to a DBusMessage.
@@ -1149,6 +1166,20 @@
 	    (si->data.avg_ack_signal &&
 	     !wpa_dbus_dict_append_int32(&iter_dict, "avg-ack-rssi",
 					 si->data.avg_ack_signal)) ||
+	    (si->data.rx_guard_interval &&
+	     !wpa_dbus_dict_append_double(
+		     &iter_dict, "rx-guard-interval",
+		     guard_interval_to_double(si->data.rx_guard_interval))) ||
+	    (si->data.tx_guard_interval &&
+	     !wpa_dbus_dict_append_double(
+		     &iter_dict, "tx-guard-interval",
+		     guard_interval_to_double(si->data.tx_guard_interval))) ||
+	    ((si->data.flags & STA_DRV_DATA_RX_HE_DCM) &&
+	     !wpa_dbus_dict_append_bool(&iter_dict, "rx-dcm",
+					si->data.rx_dcm)) ||
+	    ((si->data.flags & STA_DRV_DATA_TX_HE_DCM) &&
+	     !wpa_dbus_dict_append_bool(&iter_dict, "tx-dcm",
+					si->data.tx_dcm)) ||
 	    !wpa_dbus_dict_close_write(&variant_iter, &iter_dict) ||
 	    !dbus_message_iter_close_container(iter, &variant_iter))
 		return -ENOMEM;
diff --git a/wpa_supplicant/defconfig b/wpa_supplicant/defconfig
index 95dc4e3..1ae5a9f 100644
--- a/wpa_supplicant/defconfig
+++ b/wpa_supplicant/defconfig
@@ -212,6 +212,9 @@
 # Development testing
 #CONFIG_EAPOL_TEST=y
 
+# Support IPv6
+CONFIG_IPV6=y
+
 # Select control interface backend for external programs, e.g, wpa_cli:
 # unix = UNIX domain sockets (default for Linux/*BSD)
 # udp = UDP sockets using localhost (127.0.0.1)
@@ -398,6 +401,22 @@
 # amount of memory/flash.
 #CONFIG_DYNAMIC_EAP_METHODS=y
 
+# Dynamic library loading
+
+# Add the ability to configure libraries to load at compile time.
+# If set, these disable dynamic configuration.
+#CONFIG_PKCS11_ENGINE_PATH - pkcs11_engine library location.
+#CONFIG_PKCS11_MODULE_PATH - pkcs11_module library location.
+#CONFIG_OPENSC_ENGINE_PATH - opensc_engine library location.
+#
+# Prevent library loading at runtime
+#CONFIG_NO_PKCS11_ENGINE_PATH=y # prevents loading pkcs11_engine library.
+#CONFIG_NO_PKCS11_MODULE_PATH=y # prevents loading pkcs11_module library.
+# CONFIG_NO_OPENSC_ENGINE_PATH=y # prevents loading opensc_engine library.
+
+# Prevents loading EAP libraries at runtime
+#CONFIG_NO_LOAD_DYNAMIC_EAP=y
+
 # IEEE Std 802.11r-2008 (Fast BSS Transition) for station mode
 CONFIG_IEEE80211R=y
 
diff --git a/wpa_supplicant/dpp_supplicant.c b/wpa_supplicant/dpp_supplicant.c
index 895d5fa..4f5be0a 100644
--- a/wpa_supplicant/dpp_supplicant.c
+++ b/wpa_supplicant/dpp_supplicant.c
@@ -441,8 +441,9 @@
 }
 
 
-void wpas_dpp_connected(struct wpa_supplicant *wpa_s)
+static void wpas_dpp_connected_timeout(void *eloop_ctx, void *timeout_ctx)
 {
+	struct wpa_supplicant *wpa_s = eloop_ctx;
 	struct dpp_authentication *auth = wpa_s->dpp_auth;
 
 	if ((auth && auth->conn_status_requested) ||
@@ -450,9 +451,42 @@
 		wpas_dpp_send_conn_status_result(wpa_s, DPP_STATUS_OK);
 }
 
+
+void wpas_dpp_connected(struct wpa_supplicant *wpa_s)
+{
+	struct dpp_authentication *auth = wpa_s->dpp_auth;
+
+	if ((auth && auth->conn_status_requested) ||
+	    dpp_tcp_conn_status_requested(wpa_s->dpp)) {
+		/* Report connection result from an eloop timeout to avoid delay
+		 * to completing all connection completion steps since this
+		 * function is called in a middle of the post 4-way handshake
+		 * processing. */
+		eloop_register_timeout(0, 0, wpas_dpp_connected_timeout,
+				       wpa_s, NULL);
+	}
+}
+
 #endif /* CONFIG_DPP2 */
 
 
+static void wpas_dpp_drv_wait_timeout(void *eloop_ctx, void *timeout_ctx)
+{
+	struct wpa_supplicant *wpa_s = eloop_ctx;
+	struct dpp_authentication *auth = wpa_s->dpp_auth;
+
+	if (auth && auth->waiting_auth_resp) {
+		wpa_printf(MSG_DEBUG,
+			   "DPP: Call wpas_dpp_auth_init_next() from %s",
+			   __func__);
+		wpas_dpp_auth_init_next(wpa_s);
+	} else {
+		wpa_printf(MSG_DEBUG, "DPP: %s, but no waiting_auth_resp",
+			   __func__);
+	}
+}
+
+
 static void wpas_dpp_tx_status(struct wpa_supplicant *wpa_s,
 			       unsigned int freq, const u8 *dst,
 			       const u8 *src, const u8 *bssid,
@@ -532,7 +566,12 @@
 			/* In case of DPP Authentication Request frame, move to
 			 * the next channel immediately. */
 			offchannel_send_action_done(wpa_s);
-			wpas_dpp_auth_init_next(wpa_s);
+			/* Call wpas_dpp_auth_init_next(wpa_s) from driver event
+			 * notifying frame wait was completed or from eloop
+			 * timeout. */
+			eloop_register_timeout(0, 10000,
+					       wpas_dpp_drv_wait_timeout,
+					       wpa_s, NULL);
 			return;
 		}
 		if (auth->waiting_auth_conf) {
@@ -695,6 +734,8 @@
 	unsigned int wait_time, max_wait_time, freq, max_tries, used;
 	struct os_reltime now, diff;
 
+	eloop_cancel_timeout(wpas_dpp_drv_wait_timeout, wpa_s, NULL);
+
 	wpa_s->dpp_in_response_listen = 0;
 	if (!auth)
 		return -1;
@@ -1234,8 +1275,19 @@
 	struct dpp_authentication *auth = wpa_s->dpp_auth;
 	int freq;
 
-	if (!wpa_s->dpp_gas_server || !auth)
+	if (!wpa_s->dpp_gas_server || !auth) {
+		if (auth && auth->waiting_auth_resp &&
+		    eloop_is_timeout_registered(wpas_dpp_drv_wait_timeout,
+						wpa_s, NULL)) {
+			eloop_cancel_timeout(wpas_dpp_drv_wait_timeout,
+					     wpa_s, NULL);
+			wpa_printf(MSG_DEBUG,
+				   "DPP: Call wpas_dpp_auth_init_next() from %s",
+				   __func__);
+			wpas_dpp_auth_init_next(wpa_s);
+		}
 		return;
+	}
 
 	freq = auth->neg_freq > 0 ? auth->neg_freq : auth->curr_freq;
 	if (wpa_s->dpp_listen_work || (int) wpa_s->dpp_listen_freq == freq)
@@ -1377,7 +1429,7 @@
 
 		ssid->key_mgmt = WPA_KEY_MGMT_IEEE8021X |
 			WPA_KEY_MGMT_IEEE8021X_SHA256 |
-			WPA_KEY_MGMT_IEEE8021X_SHA256;
+			WPA_KEY_MGMT_IEEE8021X_SHA384;
 		ssid->ieee80211w = MGMT_FRAME_PROTECTION_OPTIONAL;
 
 		if (conf->cacert) {
@@ -4752,6 +4804,7 @@
 	eloop_cancel_timeout(wpas_dpp_auth_resp_retry_timeout, wpa_s, NULL);
 	eloop_cancel_timeout(wpas_dpp_gas_initial_resp_timeout, wpa_s, NULL);
 	eloop_cancel_timeout(wpas_dpp_gas_client_timeout, wpa_s, NULL);
+	eloop_cancel_timeout(wpas_dpp_drv_wait_timeout, wpa_s, NULL);
 #ifdef CONFIG_DPP2
 	eloop_cancel_timeout(wpas_dpp_config_result_wait_timeout, wpa_s, NULL);
 	eloop_cancel_timeout(wpas_dpp_conn_status_result_wait_timeout,
@@ -4760,6 +4813,7 @@
 	eloop_cancel_timeout(wpas_dpp_reconfig_reply_wait_timeout,
 			     wpa_s, NULL);
 	eloop_cancel_timeout(wpas_dpp_build_csr, wpa_s, NULL);
+	eloop_cancel_timeout(wpas_dpp_connected_timeout, wpa_s, NULL);
 	dpp_pfs_free(wpa_s->dpp_pfs);
 	wpa_s->dpp_pfs = NULL;
 	wpas_dpp_chirp_stop(wpa_s);
diff --git a/wpa_supplicant/driver_i.h b/wpa_supplicant/driver_i.h
index d707cf5..dcf5764 100644
--- a/wpa_supplicant/driver_i.h
+++ b/wpa_supplicant/driver_i.h
@@ -60,6 +60,9 @@
 				    struct wpa_driver_associate_params *params)
 {
 	if (wpa_s->driver->associate) {
+		if (params)
+			params->freq.link_id = -1;
+
 		return wpa_s->driver->associate(wpa_s->drv_priv, params);
 	}
 	return -1;
@@ -189,7 +192,7 @@
 {
 	if (wpa_s->driver->get_seqnum)
 		return wpa_s->driver->get_seqnum(wpa_s->ifname, wpa_s->drv_priv,
-						 addr, idx, seq);
+						 addr, idx, -1, seq);
 	return -1;
 }
 
@@ -199,7 +202,7 @@
 	if (wpa_s->driver->sta_deauth) {
 		return wpa_s->driver->sta_deauth(wpa_s->drv_priv,
 						 wpa_s->own_addr, addr,
-						 reason_code);
+						 reason_code, -1);
 	}
 	return -1;
 }
@@ -325,7 +328,7 @@
 	if (wpa_s->driver->send_mlme)
 		return wpa_s->driver->send_mlme(wpa_s->drv_priv,
 						data, data_len, noack,
-						freq, NULL, 0, 0, wait);
+						freq, NULL, 0, 0, wait, -1);
 	return -1;
 }
 
@@ -351,8 +354,9 @@
 				  struct hostapd_sta_add_params *params)
 {
 	if (wpa_s->driver->sta_add) {
-		/* Set link_id to -1 as it's needed for AP only */
-		params->mld_link_id = -1;
+		/* Set link_id to -1 for non-TDLS peers */
+		if (!(params->flags & WPA_STA_TDLS_PEER))
+			params->mld_link_id = -1;
 		return wpa_s->driver->sta_add(wpa_s->drv_priv, params);
 	}
 	return -1;
@@ -374,7 +378,7 @@
 	if (!wpa_s->driver->tx_control_port)
 		return -1;
 	return wpa_s->driver->tx_control_port(wpa_s->drv_priv, dest, proto,
-					      buf, len, no_encrypt);
+					      buf, len, no_encrypt, -1);
 }
 
 static inline int wpa_drv_hapd_send_eapol(struct wpa_supplicant *wpa_s,
@@ -385,7 +389,7 @@
 	if (wpa_s->driver->hapd_send_eapol)
 		return wpa_s->driver->hapd_send_eapol(wpa_s->drv_priv, addr,
 						      data, data_len, encrypt,
-						      own_addr, flags);
+						      own_addr, flags, -1);
 	return -1;
 }
 
@@ -582,13 +586,14 @@
 					 const u8 *dst, u8 action_code,
 					 u8 dialog_token, u16 status_code,
 					 u32 peer_capab, int initiator,
-					 const u8 *buf, size_t len)
+					 const u8 *buf, size_t len, int link_id)
 {
 	if (wpa_s->driver->send_tdls_mgmt) {
 		return wpa_s->driver->send_tdls_mgmt(wpa_s->drv_priv, dst,
 						     action_code, dialog_token,
 						     status_code, peer_capab,
-						     initiator, buf, len);
+						     initiator, buf, len,
+						     link_id);
 	}
 	return -1;
 }
@@ -1098,6 +1103,10 @@
 {
 	if (!wpa_s->driver->update_connect_params)
 		return -1;
+
+	if (params)
+		params->freq.link_id = -1;
+
 	return wpa_s->driver->update_connect_params(wpa_s->drv_priv, params,
 						    mask);
 }
diff --git a/wpa_supplicant/events.c b/wpa_supplicant/events.c
index eefae53..fc93305 100644
--- a/wpa_supplicant/events.c
+++ b/wpa_supplicant/events.c
@@ -22,6 +22,7 @@
 #include "common/wpa_ctrl.h"
 #include "eap_peer/eap.h"
 #include "ap/hostapd.h"
+#include "ap/sta_info.h"
 #include "p2p/p2p.h"
 #include "fst/fst.h"
 #include "wnm_sta.h"
@@ -58,12 +59,10 @@
 
 #ifndef CONFIG_NO_SCAN_PROCESSING
 static int wpas_select_network_from_last_scan(struct wpa_supplicant *wpa_s,
-					      int new_scan, int own_request);
+					      int new_scan, int own_request,
+					      bool trigger_6ghz_scan,
+					      union wpa_event_data *data);
 #endif /* CONFIG_NO_SCAN_PROCESSING */
-#ifdef CONFIG_OWE
-static void owe_trans_ssid(struct wpa_supplicant *wpa_s, struct wpa_bss *bss,
-			   const u8 **ret_ssid, size_t *ret_ssid_len);
-#endif /* CONFIG_OWE */
 
 
 int wpas_temp_disabled(struct wpa_supplicant *wpa_s, struct wpa_ssid *ssid)
@@ -143,8 +142,13 @@
 {
 	struct wpa_bss *bss = NULL;
 	struct wpa_ssid *ssid = wpa_s->current_ssid;
+	u8 drv_ssid[SSID_MAX_LEN];
+	int res;
 
-	if (ssid && ssid->ssid_len > 0)
+	res = wpa_drv_get_ssid(wpa_s, drv_ssid);
+	if (res > 0)
+		bss = wpa_bss_get(wpa_s, bssid, drv_ssid, res);
+	if (!bss && ssid && ssid->ssid_len > 0)
 		bss = wpa_bss_get(wpa_s, bssid, ssid->ssid, ssid->ssid_len);
 	if (!bss)
 		bss = wpa_bss_get_bssid(wpa_s, bssid);
@@ -215,14 +219,6 @@
 			return 0; /* current profile still in use */
 
 #ifdef CONFIG_OWE
-		if (wpa_s->current_bss &&
-		    !(wpa_s->current_bss->flags & WPA_BSS_OWE_TRANSITION)) {
-			const u8 *match_ssid;
-			size_t match_ssid_len;
-
-			owe_trans_ssid(wpa_s, wpa_s->current_bss,
-				       &match_ssid, &match_ssid_len);
-		}
 		if ((wpa_s->current_ssid->key_mgmt & WPA_KEY_MGMT_OWE) &&
 		    wpa_s->current_bss &&
 		    (wpa_s->current_bss->flags & WPA_BSS_OWE_TRANSITION) &&
@@ -427,7 +423,8 @@
 	for (i = 0; i < ie.num_pmkid; i++) {
 		pmksa_set = pmksa_cache_set_current(wpa_s->wpa,
 						    ie.pmkid + i * PMKID_LEN,
-						    NULL, NULL, 0, NULL, 0);
+						    NULL, NULL, 0, NULL, 0,
+						    true);
 		if (pmksa_set == 0) {
 			eapol_sm_notify_pmkid_attempt(wpa_s->eapol);
 			if (authorized)
@@ -1090,7 +1087,6 @@
 #ifdef CONFIG_OWE
 	const u8 *owe, *pos, *end, *bssid;
 	u8 ssid_len;
-	struct wpa_bss *open_bss;
 
 	owe = wpa_bss_get_vendor_ie(bss, OWE_IE_VENDOR_TYPE);
 	if (!owe || !wpa_bss_get_ie(bss, WLAN_EID_RSN))
@@ -1131,52 +1127,30 @@
 			}
 		}
 	}
-
-	if (bss->ssid_len > 0)
-		return;
-
-	open_bss = wpa_bss_get_bssid_latest(wpa_s, bssid);
-	if (!open_bss)
-		return;
-	if (ssid_len != open_bss->ssid_len ||
-	    os_memcmp(pos, open_bss->ssid, ssid_len) != 0) {
-		wpa_dbg(wpa_s, MSG_DEBUG,
-			"OWE: transition mode SSID mismatch: %s",
-			wpa_ssid_txt(open_bss->ssid, open_bss->ssid_len));
-		return;
-	}
-
-	owe = wpa_bss_get_vendor_ie(open_bss, OWE_IE_VENDOR_TYPE);
-	if (!owe || wpa_bss_get_ie(open_bss, WLAN_EID_RSN)) {
-		wpa_dbg(wpa_s, MSG_DEBUG,
-			"OWE: transition mode open BSS unexpected info");
-		return;
-	}
-
-	pos = owe + 6;
-	end = owe + 2 + owe[1];
-
-	if (end - pos < ETH_ALEN + 1)
-		return;
-	if (os_memcmp(pos, bss->bssid, ETH_ALEN) != 0) {
-		wpa_dbg(wpa_s, MSG_DEBUG,
-			"OWE: transition mode BSSID mismatch: " MACSTR,
-			MAC2STR(pos));
-		return;
-	}
-	pos += ETH_ALEN;
-	ssid_len = *pos++;
-	if (end - pos < ssid_len || ssid_len > SSID_MAX_LEN)
-		return;
-	wpa_dbg(wpa_s, MSG_DEBUG, "OWE: learned transition mode OWE SSID: %s",
-		wpa_ssid_txt(pos, ssid_len));
-	os_memcpy(bss->ssid, pos, ssid_len);
-	bss->ssid_len = ssid_len;
-	bss->flags |= WPA_BSS_OWE_TRANSITION;
 #endif /* CONFIG_OWE */
 }
 
 
+static bool wpas_valid_ml_bss(struct wpa_supplicant *wpa_s, struct wpa_bss *bss)
+
+{
+	u16 removed_links;
+
+	if (wpa_bss_parse_basic_ml_element(wpa_s, bss, NULL, NULL))
+		return true;
+
+	if (bss->n_mld_links == 0)
+		return true;
+
+	/* Check if the current BSS is going to be removed */
+	removed_links = wpa_bss_parse_reconf_ml_element(wpa_s, bss);
+	if (BIT(bss->mld_links[0].link_id) & removed_links)
+		return false;
+
+	return true;
+}
+
+
 int disabled_freq(struct wpa_supplicant *wpa_s, int freq)
 {
 	int i, j;
@@ -1576,12 +1550,35 @@
 #endif /* CONFIG_SAE_PK */
 
 	if (bss->ssid_len == 0) {
+#ifdef CONFIG_OWE
+		const u8 *owe_ssid = NULL;
+		size_t owe_ssid_len = 0;
+
+		owe_trans_ssid(wpa_s, bss, &owe_ssid, &owe_ssid_len);
+		if (owe_ssid && owe_ssid_len &&
+		    owe_ssid_len == ssid->ssid_len &&
+		    os_memcmp(owe_ssid, ssid->ssid, owe_ssid_len) == 0) {
+			if (debug_print)
+				wpa_dbg(wpa_s, MSG_DEBUG,
+					"   skip - no SSID in BSS entry for a possible OWE transition mode BSS");
+			int_array_add_unique(&wpa_s->owe_trans_scan_freq,
+					     bss->freq);
+			return false;
+		}
+#endif /* CONFIG_OWE */
 		if (debug_print)
 			wpa_dbg(wpa_s, MSG_DEBUG,
 				"   skip - no SSID known for the BSS");
 		return false;
 	}
 
+	if (!wpas_valid_ml_bss(wpa_s, bss)) {
+		if (debug_print)
+			wpa_dbg(wpa_s, MSG_DEBUG,
+				"   skip - ML BSS going to be removed");
+		return false;
+	}
+
 	/* Matching configuration found */
 	return true;
 }
@@ -1835,6 +1832,82 @@
 }
 
 
+static bool ml_link_probe_scan(struct wpa_supplicant *wpa_s)
+{
+	if (!wpa_s->ml_connect_probe_ssid || !wpa_s->ml_connect_probe_bss)
+		return false;
+
+	wpa_msg(wpa_s, MSG_DEBUG,
+		"Request association with " MACSTR " after ML probe",
+		MAC2STR(wpa_s->ml_connect_probe_bss->bssid));
+
+	wpa_supplicant_associate(wpa_s, wpa_s->ml_connect_probe_bss,
+				 wpa_s->ml_connect_probe_ssid);
+
+	wpa_s->ml_connect_probe_ssid = NULL;
+	wpa_s->ml_connect_probe_bss = NULL;
+
+	return true;
+}
+
+
+static int wpa_supplicant_connect_ml_missing(struct wpa_supplicant *wpa_s,
+					     struct wpa_bss *selected,
+					     struct wpa_ssid *ssid)
+{
+	int *freqs;
+	u16 missing_links = 0, removed_links;
+
+	if (!((wpa_s->drv_flags2 & WPA_DRIVER_FLAGS2_MLO) &&
+	      (wpa_s->drv_flags & WPA_DRIVER_FLAGS_SME)))
+		return 0;
+
+	/* Try to resolve any missing link information */
+	if (wpa_bss_parse_basic_ml_element(wpa_s, selected, NULL,
+					   &missing_links) || !missing_links)
+		return 0;
+
+	removed_links = wpa_bss_parse_reconf_ml_element(wpa_s, selected);
+	missing_links &= ~removed_links;
+
+	if (!missing_links)
+		return 0;
+
+	wpa_dbg(wpa_s, MSG_DEBUG,
+		"MLD: Doing an ML probe for missing links 0x%04x",
+		missing_links);
+
+	freqs = os_malloc(sizeof(int) * 2);
+	if (!freqs)
+		return 0;
+
+	wpa_s->ml_connect_probe_ssid = ssid;
+	wpa_s->ml_connect_probe_bss = selected;
+
+	freqs[0] = selected->freq;
+	freqs[1] = 0;
+
+	wpa_s->manual_scan_passive = 0;
+	wpa_s->manual_scan_use_id = 0;
+	wpa_s->manual_scan_only_new = 0;
+	wpa_s->scan_id_count = 0;
+	os_free(wpa_s->manual_scan_freqs);
+	wpa_s->manual_scan_freqs = freqs;
+
+	os_memcpy(wpa_s->ml_probe_bssid, selected->bssid, ETH_ALEN);
+	wpa_s->ml_probe_mld_id = -1;
+	wpa_s->ml_probe_links = missing_links;
+
+	wpa_s->normal_scans = 0;
+	wpa_s->scan_req = MANUAL_SCAN_REQ;
+	wpa_s->after_wps = 0;
+	wpa_s->known_wps_freq = 0;
+	wpa_supplicant_req_scan(wpa_s, 0, 0);
+
+	return 1;
+}
+
+
 int wpa_supplicant_connect(struct wpa_supplicant *wpa_s,
 			   struct wpa_bss *selected,
 			   struct wpa_ssid *ssid)
@@ -1891,6 +1964,10 @@
 			wpa_supplicant_req_new_scan(wpa_s, 10, 0);
 			return 0;
 		}
+
+		if (wpa_supplicant_connect_ml_missing(wpa_s, selected, ssid))
+			return 0;
+
 		wpa_msg(wpa_s, MSG_DEBUG, "Request association with " MACSTR,
 			MAC2STR(selected->bssid));
 		wpa_supplicant_associate(wpa_s, selected, ssid);
@@ -1966,9 +2043,15 @@
 
 static int wpas_get_snr_signal_info(u32 frequency, int avg_signal, int noise)
 {
-	if (noise == WPA_INVALID_NOISE)
-		noise = IS_5GHZ(frequency) ? DEFAULT_NOISE_FLOOR_5GHZ :
-			DEFAULT_NOISE_FLOOR_2GHZ;
+	if (noise == WPA_INVALID_NOISE) {
+		if (IS_5GHZ(frequency)) {
+			noise = DEFAULT_NOISE_FLOOR_5GHZ;
+		} else if (is_6ghz_freq(frequency)) {
+			noise = DEFAULT_NOISE_FLOOR_6GHZ;
+		} else {
+			noise = DEFAULT_NOISE_FLOOR_2GHZ;
+		}
+	}
 	return avg_signal - noise;
 }
 
@@ -1980,8 +2063,10 @@
 	int rate = wpa_bss_get_max_rate(bss);
 	const u8 *ies = wpa_bss_ie_ptr(bss);
 	size_t ie_len = bss->ie_len ? bss->ie_len : bss->beacon_ie_len;
+	enum chan_width max_cw = CHAN_WIDTH_UNKNOWN;
 
-	return wpas_get_est_tpt(wpa_s, ies, ie_len, rate, snr, bss->freq);
+	return wpas_get_est_tpt(wpa_s, ies, ie_len, rate, snr, bss->freq,
+				&max_cw);
 }
 
 
@@ -1991,11 +2076,17 @@
 {
 	int min_diff, diff;
 	int to_5ghz, to_6ghz;
-	int cur_level;
+	int cur_level, sel_level;
 	unsigned int cur_est, sel_est;
 	struct wpa_signal_info si;
 	int cur_snr = 0;
 	int ret = 0;
+	const u8 *cur_ies = wpa_bss_ie_ptr(current_bss);
+	const u8 *sel_ies = wpa_bss_ie_ptr(selected);
+	size_t cur_ie_len = current_bss->ie_len ? current_bss->ie_len :
+		current_bss->beacon_ie_len;
+	size_t sel_ie_len = selected->ie_len ? selected->ie_len :
+		selected->beacon_ie_len;
 
 	wpa_dbg(wpa_s, MSG_DEBUG, "Considering within-ESS reassociation");
 	wpa_dbg(wpa_s, MSG_DEBUG, "Current BSS: " MACSTR
@@ -2016,10 +2107,6 @@
 		return 1;
 	}
 
-	cur_level = current_bss->level;
-	cur_est = current_bss->est_throughput;
-	sel_est = selected->est_throughput;
-
 	/*
 	 * Try to poll the signal from the driver since this will allow to get
 	 * more accurate values. In some cases, there can be big differences
@@ -2037,8 +2124,15 @@
 	 */
 	if (wpa_drv_signal_poll(wpa_s, &si) == 0 &&
 	    (si.data.avg_beacon_signal || si.data.avg_signal)) {
+		/*
+		 * Normalize avg_signal to the RSSI over 20 MHz, as the
+		 * throughput is estimated based on the RSSI over 20 MHz
+		 */
 		cur_level = si.data.avg_beacon_signal ?
-			si.data.avg_beacon_signal : si.data.avg_signal;
+			si.data.avg_beacon_signal :
+			(si.data.avg_signal -
+			 wpas_channel_width_rssi_bump(cur_ies, cur_ie_len,
+						      si.chanwidth));
 		cur_snr = wpas_get_snr_signal_info(si.frequency, cur_level,
 						   si.current_noise);
 
@@ -2048,8 +2142,24 @@
 		wpa_dbg(wpa_s, MSG_DEBUG,
 			"Using signal poll values for the current BSS: level=%d snr=%d est_throughput=%u",
 			cur_level, cur_snr, cur_est);
+	} else {
+		/* Level and SNR are measured over 20 MHz channel */
+		cur_level = current_bss->level;
+		cur_snr = current_bss->snr;
+		cur_est = current_bss->est_throughput;
 	}
 
+	/* Adjust the SNR of BSSes based on the channel width. */
+	cur_level += wpas_channel_width_rssi_bump(cur_ies, cur_ie_len,
+						  current_bss->max_cw);
+	cur_snr = wpas_adjust_snr_by_chanwidth(cur_ies, cur_ie_len,
+					       current_bss->max_cw, cur_snr);
+
+	sel_est = selected->est_throughput;
+	sel_level = selected->level +
+		wpas_channel_width_rssi_bump(sel_ies, sel_ie_len,
+					     selected->max_cw);
+
 	if (sel_est > cur_est + 5000) {
 		wpa_dbg(wpa_s, MSG_DEBUG,
 			"Allow reassociation - selected BSS has better estimated throughput");
@@ -2061,7 +2171,7 @@
 		!is_6ghz_freq(current_bss->freq);
 
 	if (cur_level < 0 &&
-	    cur_level > selected->level + to_5ghz * 2 + to_6ghz * 2 &&
+	    cur_level > sel_level + to_5ghz * 2 + to_6ghz * 2 &&
 	    sel_est < cur_est * 1.2) {
 		wpa_dbg(wpa_s, MSG_DEBUG, "Skip roam - Current BSS has better "
 			"signal level");
@@ -2115,7 +2225,7 @@
 		min_diff -= 2;
 	if (to_6ghz)
 		min_diff -= 2;
-	diff = selected->level - cur_level;
+	diff = sel_level - cur_level;
 	if (diff < min_diff) {
 		wpa_dbg(wpa_s, MSG_DEBUG,
 			"Skip roam - too small difference in signal level (%d < %d)",
@@ -2134,7 +2244,7 @@
 		     MAC2STR(current_bss->bssid),
 		     current_bss->freq, cur_level, cur_est,
 		     MAC2STR(selected->bssid),
-		     selected->freq, selected->level, sel_est);
+		     selected->freq, sel_level, sel_est);
 	return ret;
 }
 
@@ -2200,6 +2310,7 @@
 	struct wpa_scan_results *scan_res = NULL;
 	int ret = 0;
 	int ap = 0;
+	bool trigger_6ghz_scan;
 #ifndef CONFIG_NO_RANDOM_POOL
 	size_t i, num;
 #endif /* CONFIG_NO_RANDOM_POOL */
@@ -2209,6 +2320,11 @@
 		ap = 1;
 #endif /* CONFIG_AP */
 
+	trigger_6ghz_scan = wpa_s->crossed_6ghz_dom &&
+		wpa_s->last_scan_all_chan;
+	wpa_s->crossed_6ghz_dom = false;
+	wpa_s->last_scan_all_chan = false;
+
 	wpa_supplicant_notify_scanning(wpa_s, 0);
 
 	scan_res = wpa_supplicant_get_scan_results(wpa_s,
@@ -2312,6 +2428,9 @@
 	    wpas_beacon_rep_scan_process(wpa_s, scan_res, &data->scan_info) > 0)
 		goto scan_work_done;
 
+	if (ml_link_probe_scan(wpa_s))
+		goto scan_work_done;
+
 	if ((wpa_s->conf->ap_scan == 2 && !wpas_wps_searching(wpa_s)))
 		goto scan_work_done;
 
@@ -2359,7 +2478,8 @@
 	if (wpa_s->supp_pbc_active && !wpas_wps_partner_link_scan_done(wpa_s))
 		return ret;
 
-	return wpas_select_network_from_last_scan(wpa_s, 1, own_request);
+	return wpas_select_network_from_last_scan(wpa_s, 1, own_request,
+						  trigger_6ghz_scan, data);
 
 scan_work_done:
 	wpa_scan_results_free(scan_res);
@@ -2372,8 +2492,44 @@
 }
 
 
+static int wpas_trigger_6ghz_scan(struct wpa_supplicant *wpa_s,
+				  union wpa_event_data *data)
+{
+	struct wpa_driver_scan_params params;
+	unsigned int j;
+
+	wpa_dbg(wpa_s, MSG_INFO, "Triggering 6GHz-only scan");
+	os_memset(&params, 0, sizeof(params));
+	params.non_coloc_6ghz = wpa_s->last_scan_non_coloc_6ghz;
+	for (j = 0; j < data->scan_info.num_ssids; j++)
+		params.ssids[j] = data->scan_info.ssids[j];
+	params.num_ssids = data->scan_info.num_ssids;
+	wpa_add_scan_freqs_list(wpa_s, HOSTAPD_MODE_IEEE80211A, &params,
+				true, !wpa_s->last_scan_non_coloc_6ghz, false);
+	if (!wpa_supplicant_trigger_scan(wpa_s, &params, true, true)) {
+		os_free(params.freqs);
+		return 1;
+	}
+	wpa_dbg(wpa_s, MSG_INFO, "Failed to trigger 6GHz-only scan");
+	os_free(params.freqs);
+	return 0;
+}
+
+
+/**
+ * Select a network from the last scan
+ * @wpa_s: Pointer to wpa_supplicant data
+ * @new_scan: Whether this function was called right after a scan has finished
+ * @own_request: Whether the scan was requested by this interface
+ * @trigger_6ghz_scan: Whether to trigger a 6ghz-only scan when applicable
+ * @data: Scan data from scan that finished if applicable
+ *
+ * See _wpa_supplicant_event_scan_results() for return values.
+ */
 static int wpas_select_network_from_last_scan(struct wpa_supplicant *wpa_s,
-					      int new_scan, int own_request)
+					      int new_scan, int own_request,
+					      bool trigger_6ghz_scan,
+					      union wpa_event_data *data)
 {
 	struct wpa_bss *selected;
 	struct wpa_ssid *ssid = NULL;
@@ -2393,6 +2549,10 @@
 		return 0; /* no normal connection on p2p_mgmt interface */
 
 	wpa_s->owe_transition_search = 0;
+#ifdef CONFIG_OWE
+	os_free(wpa_s->owe_trans_scan_freq);
+	wpa_s->owe_trans_scan_freq = NULL;
+#endif /* CONFIG_OWE */
 	selected = wpa_supplicant_pick_network(wpa_s, &ssid);
 
 #ifdef CONFIG_MESH
@@ -2445,6 +2605,10 @@
 			if (new_scan)
 				wpa_supplicant_rsn_preauth_scan_results(wpa_s);
 		} else if (own_request) {
+			if (wpa_s->support_6ghz && trigger_6ghz_scan && data &&
+			    wpas_trigger_6ghz_scan(wpa_s, data) < 0)
+				return 1;
+
 			/*
 			 * No SSID found. If SCAN results are as a result of
 			 * own scan request and not due to a scan request on
@@ -2508,6 +2672,13 @@
 					"OWE: Use shorter wait during transition mode search");
 				timeout_sec = 0;
 				timeout_usec = 500000;
+				if (wpa_s->owe_trans_scan_freq) {
+					os_free(wpa_s->next_scan_freqs);
+					wpa_s->next_scan_freqs =
+						wpa_s->owe_trans_scan_freq;
+					wpa_s->owe_trans_scan_freq = NULL;
+					timeout_usec = 100000;
+				}
 				wpa_supplicant_req_new_scan(wpa_s, timeout_sec,
 							    timeout_usec);
 				return 0;
@@ -2593,7 +2764,7 @@
 		return -1;
 	}
 
-	return wpas_select_network_from_last_scan(wpa_s, 0, 1);
+	return wpas_select_network_from_last_scan(wpa_s, 0, 1, false, NULL);
 #endif /* CONFIG_NO_SCAN_PROCESSING */
 }
 
@@ -2603,7 +2774,7 @@
 #ifdef CONFIG_NO_SCAN_PROCESSING
 	return -1;
 #else /* CONFIG_NO_SCAN_PROCESSING */
-	return wpas_select_network_from_last_scan(wpa_s, 1, 1);
+	return wpas_select_network_from_last_scan(wpa_s, 1, 1, false, NULL);
 #endif /* CONFIG_NO_SCAN_PROCESSING */
 }
 
@@ -3178,6 +3349,11 @@
 			} else {
 				wpa_s->connection_channel_bandwidth = CHAN_WIDTH_20;
 			}
+			if (req_elems.rrm_enabled)
+				wpa_s->rrm.rrm_used = 1;
+			wpa_s->ap_t2lm_negotiation_support =
+				is_ap_t2lm_negotiation_supported(resp_elems.basic_mle,
+				resp_elems.basic_mle_len);
 		}
 	}
 
@@ -3284,6 +3460,7 @@
 
 #ifdef CONFIG_OWE
 	if (wpa_s->key_mgmt == WPA_KEY_MGMT_OWE &&
+	    !(wpa_s->drv_flags2 & WPA_DRIVER_FLAGS2_OWE_OFFLOAD_STA) &&
 	    (!bssid_known ||
 	     owe_process_assoc_resp(wpa_s->wpa,
 				    wpa_s->valid_links ?
@@ -3674,8 +3851,8 @@
 		hostapd_notif_assoc(wpa_s->ap_iface->bss[0],
 				    data->assoc_info.addr,
 				    data->assoc_info.req_ies,
-				    data->assoc_info.req_ies_len,
-				    data->assoc_info.reassoc);
+				    data->assoc_info.req_ies_len, NULL, 0,
+				    NULL, data->assoc_info.reassoc);
 		return;
 	}
 #endif /* CONFIG_AP */
@@ -3691,7 +3868,7 @@
 		int use_sha384 = wpa_key_mgmt_sha384(wpa_s->wpa->key_mgmt);
 		if (wpa_key_mgmt_ft(wpa_s->key_mgmt) && (wpa_s->key_mgmt == ie.key_mgmt)) {
 			if (wpa_ft_parse_ies(data->assoc_info.resp_ies,
-				data->assoc_info.resp_ies_len, &parse, use_sha384) < 0) {
+				data->assoc_info.resp_ies_len, &parse, use_sha384, false) < 0) {
 				wpa_printf(MSG_DEBUG, "Failed to parse FT IEs");
 				return;
 			}
@@ -4272,7 +4449,7 @@
 	wpa_msg(wpa_s, MSG_WARNING, "Michael MIC failure detected");
 	pairwise = (data && data->michael_mic_failure.unicast);
 	os_get_reltime(&t);
-	if ((wpa_s->last_michael_mic_error.sec &&
+	if ((os_reltime_initialized(&wpa_s->last_michael_mic_error) &&
 	     !os_reltime_expired(&t, &wpa_s->last_michael_mic_error, 60)) ||
 	    wpa_s->pending_mic_error_report) {
 		if (wpa_s->pending_mic_error_report) {
@@ -4824,12 +5001,17 @@
 
 	dl_list_for_each(ifs, &wpa_s->radio->ifaces, struct wpa_supplicant,
 			 radio_list) {
+		bool was_6ghz_enabled;
+
 		wpa_printf(MSG_DEBUG, "%s: Updating hw mode",
 			   ifs->ifname);
 		free_hw_features(ifs);
 		ifs->hw.modes = wpa_drv_get_hw_feature_data(
 			ifs, &ifs->hw.num_modes, &ifs->hw.flags, &dfs_domain);
 
+		was_6ghz_enabled = ifs->is_6ghz_enabled;
+		ifs->is_6ghz_enabled = wpas_is_6ghz_supported(ifs, true);
+
 		/* Restart PNO/sched_scan with updated channel list */
 		if (ifs->pno) {
 			wpas_stop_pno(ifs);
@@ -4838,6 +5020,12 @@
 			wpa_dbg(ifs, MSG_DEBUG,
 				"Channel list changed - restart sched_scan");
 			wpas_scan_restart_sched_scan(ifs);
+		} else if (ifs->scanning && !was_6ghz_enabled &&
+			   ifs->is_6ghz_enabled) {
+			/* Look for APs in the 6 GHz band */
+			wpa_dbg(ifs, MSG_INFO,
+				"Channel list changed - trigger 6 GHz-only scan");
+			ifs->crossed_6ghz_dom = true;
 		}
 	}
 
@@ -4916,6 +5104,12 @@
 		wpa_dbg(wpa_s, MSG_DEBUG,
 			"TDLS: Received Discovery Response from " MACSTR,
 			MAC2STR(mgmt->sa));
+		if (wpa_s->valid_links &&
+		    wpa_tdls_process_discovery_response(wpa_s->wpa, mgmt->sa,
+							&payload[1], plen - 1))
+			wpa_dbg(wpa_s, MSG_ERROR,
+				"TDLS: Discovery Response process failed for "
+				MACSTR, MAC2STR(mgmt->sa));
 		return;
 	}
 #endif /* CONFIG_TDLS */
@@ -5164,7 +5358,7 @@
 			/* Update the current PMKSA used for this connection */
 			pmksa_cache_set_current(wpa_s->wpa,
 						data->assoc_info.fils_pmkid,
-						NULL, NULL, 0, NULL, 0);
+						NULL, NULL, 0, NULL, 0, true);
 		}
 	}
 #endif /* CONFIG_FILS */
@@ -6378,6 +6572,21 @@
 		break;
 #endif /* CONFIG_PASN */
 	case EVENT_PORT_AUTHORIZED:
+#ifdef CONFIG_AP
+		if (wpa_s->ap_iface && wpa_s->ap_iface->bss[0]) {
+			struct sta_info *sta;
+
+			sta = ap_get_sta(wpa_s->ap_iface->bss[0],
+					 data->port_authorized.sta_addr);
+			if (sta)
+				ap_sta_set_authorized(wpa_s->ap_iface->bss[0],
+						      sta, 1);
+			else
+				wpa_printf(MSG_DEBUG,
+					   "No STA info matching port authorized event found");
+			break;
+		}
+#endif /* CONFIG_AP */
 #ifndef CONFIG_NO_WPA
 		if (data->port_authorized.td_bitmap_len) {
 			wpa_printf(MSG_DEBUG,
diff --git a/wpa_supplicant/mesh_mpm.c b/wpa_supplicant/mesh_mpm.c
index c9e14d5..138c013 100644
--- a/wpa_supplicant/mesh_mpm.c
+++ b/wpa_supplicant/mesh_mpm.c
@@ -16,6 +16,7 @@
 #include "ap/hostapd.h"
 #include "ap/sta_info.h"
 #include "ap/ieee802_11.h"
+#include "ap/beacon.h"
 #include "ap/wpa_auth.h"
 #include "wpa_supplicant_i.h"
 #include "driver_i.h"
@@ -561,8 +562,11 @@
 	int reason = WLAN_REASON_MESH_PEERING_CANCELLED;
 
 	if (sta) {
-		if (sta->plink_state == PLINK_ESTAB)
+		if (sta->plink_state == PLINK_ESTAB) {
 			hapd->num_plinks--;
+			wpas_notify_mesh_peer_disconnected(
+				wpa_s, sta->addr, WLAN_REASON_UNSPECIFIED);
+		}
 		wpa_mesh_set_plink_state(wpa_s, sta, PLINK_HOLDING);
 		mesh_mpm_send_plink_action(wpa_s, sta, PLINK_CLOSE, reason);
 		wpa_printf(MSG_DEBUG, "MPM closing plink sta=" MACSTR,
@@ -766,7 +770,8 @@
 		set_disable_ht40(sta->ht_capabilities, 1);
 	}
 
-	update_ht_state(data, sta);
+	if (update_ht_state(data, sta) > 0)
+		ieee802_11_update_beacons(data->iface);
 
 #ifdef CONFIG_IEEE80211AC
 	copy_sta_vht_capab(data, sta, elems->vht_capabilities);
@@ -951,11 +956,6 @@
 	peer_add_timer(wpa_s, NULL);
 	eloop_cancel_timeout(plink_timer, wpa_s, sta);
 
-	/* Send ctrl event */
-	wpa_msg(wpa_s, MSG_INFO, MESH_PEER_CONNECTED MACSTR,
-		MAC2STR(sta->addr));
-
-	/* Send D-Bus event */
 	wpas_notify_mesh_peer_connected(wpa_s, sta->addr);
 }
 
@@ -1106,10 +1106,6 @@
 				" closed with reason %d",
 				MAC2STR(sta->addr), reason);
 
-			wpa_msg(wpa_s, MSG_INFO, MESH_PEER_DISCONNECTED MACSTR,
-				MAC2STR(sta->addr));
-
-			/* Send D-Bus event */
 			wpas_notify_mesh_peer_disconnected(wpa_s, sta->addr,
 							   reason);
 
@@ -1428,8 +1424,13 @@
 /* called by ap_free_sta */
 void mesh_mpm_free_sta(struct hostapd_data *hapd, struct sta_info *sta)
 {
-	if (sta->plink_state == PLINK_ESTAB)
+	struct wpa_supplicant *wpa_s = hapd->iface->owner;
+
+	if (sta->plink_state == PLINK_ESTAB) {
 		hapd->num_plinks--;
+		wpas_notify_mesh_peer_disconnected(
+			wpa_s, sta->addr, WLAN_REASON_UNSPECIFIED);
+	}
 	eloop_cancel_timeout(plink_timer, ELOOP_ALL_CTX, sta);
 	eloop_cancel_timeout(mesh_auth_timer, ELOOP_ALL_CTX, sta);
 }
diff --git a/wpa_supplicant/notify.c b/wpa_supplicant/notify.c
index a20f1c0..a1746da 100644
--- a/wpa_supplicant/notify.c
+++ b/wpa_supplicant/notify.c
@@ -463,12 +463,29 @@
 		wpa_s->last_ssid = NULL;
 	if (wpa_s->current_ssid == ssid)
 		wpa_s->current_ssid = NULL;
+	if (wpa_s->ml_connect_probe_ssid == ssid) {
+		wpa_s->ml_connect_probe_ssid = NULL;
+		wpa_s->ml_connect_probe_bss = NULL;
+	}
 #if defined(CONFIG_SME) && defined(CONFIG_SAE)
 	if (wpa_s->sme.ext_auth_wpa_ssid == ssid)
 		wpa_s->sme.ext_auth_wpa_ssid = NULL;
 #endif /* CONFIG_SME && CONFIG_SAE */
-	if (wpa_s->wpa)
+	if (wpa_s->wpa) {
+		if ((wpa_key_mgmt_sae(ssid->key_mgmt) &&
+		     (wpa_s->drv_flags2 & WPA_DRIVER_FLAGS2_SAE_OFFLOAD_STA)) ||
+		    ((ssid->key_mgmt & WPA_KEY_MGMT_OWE) &&
+		     (wpa_s->drv_flags2 & WPA_DRIVER_FLAGS2_OWE_OFFLOAD_STA))) {
+			/* For cases when PMK is generated at the driver */
+			struct wpa_pmkid_params params;
+
+			os_memset(&params, 0, sizeof(params));
+			params.ssid = ssid->ssid;
+			params.ssid_len = ssid->ssid_len;
+			wpa_drv_remove_pmkid(wpa_s, &params);
+		}
 		wpa_sm_pmksa_cache_flush(wpa_s->wpa, ssid);
+	}
 	if (!ssid->p2p_group && wpa_s->global->p2p_group_formation != wpa_s &&
 	    !wpa_s->p2p_mgmt) {
 		wpas_dbus_unregister_network(wpa_s, ssid->id);
@@ -1126,6 +1143,8 @@
 	if (wpa_s->p2p_mgmt)
 		return;
 
+	wpa_msg(wpa_s, MSG_INFO, MESH_PEER_CONNECTED MACSTR,
+		MAC2STR(peer_addr));
 	wpas_dbus_signal_mesh_peer_connected(wpa_s, peer_addr);
 }
 
@@ -1136,6 +1155,8 @@
 	if (wpa_s->p2p_mgmt)
 		return;
 
+	wpa_msg(wpa_s, MSG_INFO, MESH_PEER_DISCONNECTED MACSTR,
+		MAC2STR(peer_addr));
 	wpas_dbus_signal_mesh_peer_disconnected(wpa_s, peer_addr, reason_code);
 }
 
diff --git a/wpa_supplicant/p2p_supplicant.c b/wpa_supplicant/p2p_supplicant.c
index db99177..c150863 100644
--- a/wpa_supplicant/p2p_supplicant.c
+++ b/wpa_supplicant/p2p_supplicant.c
@@ -1088,6 +1088,7 @@
 		 * Likewise, we don't send out network removed signals for such
 		 * network objects.
 		 */
+		wpas_notify_network_removed(wpa_s, ssid);
 		wpa_config_remove_network(wpa_s->conf, id);
 		wpa_supplicant_clear_status(wpa_s);
 		wpa_supplicant_cancel_sched_scan(wpa_s);
@@ -2827,6 +2828,12 @@
 		wpa_drv_probe_req_report(wpa_s, 0);
 
 	wpas_p2p_listen_work_done(wpa_s);
+
+	if (radio_work_pending(wpa_s, "p2p-listen")) {
+		wpa_printf(MSG_DEBUG,
+			   "P2P: p2p-listen is still pending - remove it");
+		radio_remove_works(wpa_s, "p2p-listen", 0);
+	}
 }
 
 
@@ -6324,7 +6331,7 @@
 
 		res = wpa_drv_get_pref_freq_list(wpa_s, WPA_IF_P2P_GO,
 						 &size, pref_freq_list);
-		if (!is_p2p_allow_6ghz(wpa_s->global->p2p))
+		if (!res && size > 0 && !is_p2p_allow_6ghz(wpa_s->global->p2p))
 			size = p2p_remove_6ghz_channels(pref_freq_list, size);
 
 		if (!res && size > 0) {
diff --git a/wpa_supplicant/preauth_test.c b/wpa_supplicant/preauth_test.c
index 3aa6675..f266ce4 100644
--- a/wpa_supplicant/preauth_test.c
+++ b/wpa_supplicant/preauth_test.c
@@ -357,7 +357,7 @@
 		ret = -2;
 	else {
 		ret = pmksa_cache_set_current(wpa_s.wpa, NULL, bssid, NULL, 0,
-					      NULL, 0) ? 0 : -3;
+					      NULL, 0, false) ? 0 : -3;
 	}
 
 	test_eapol_clean(&wpa_s);
diff --git a/wpa_supplicant/robust_av.c b/wpa_supplicant/robust_av.c
index ff648b2..58edd9b 100644
--- a/wpa_supplicant/robust_av.c
+++ b/wpa_supplicant/robust_av.c
@@ -98,12 +98,27 @@
 }
 
 
+static bool tclas_elem_required(const struct qos_characteristics *qos_elem)
+{
+	if (!qos_elem || !qos_elem->available)
+		return true;
+
+	if (qos_elem->direction == SCS_DIRECTION_DOWN)
+		return true;
+
+	return false;
+}
+
+
 static int wpas_populate_scs_descriptor_ie(struct scs_desc_elem *desc_elem,
-					   struct wpabuf *buf)
+					   struct wpabuf *buf,
+					   bool allow_scs_traffic_desc)
 {
 	u8 *len, *len1;
 	struct tclas_element *tclas_elem;
 	unsigned int i;
+	struct qos_characteristics *qos_elem;
+	u32 control_info = 0;
 
 	/* SCS Descriptor element */
 	wpabuf_put_u8(buf, WLAN_EID_SCS_DESCRIPTOR);
@@ -113,6 +128,9 @@
 	if (desc_elem->request_type == SCS_REQ_REMOVE)
 		goto end;
 
+	if (!tclas_elem_required(&desc_elem->qos_char_elem))
+		goto skip_tclas_elem;
+
 	if (desc_elem->intra_access_priority || desc_elem->scs_up_avail) {
 		wpabuf_put_u8(buf, WLAN_EID_INTRA_ACCESS_CATEGORY_PRIORITY);
 		wpabuf_put_u8(buf, 1);
@@ -165,6 +183,81 @@
 		wpabuf_put_u8(buf, desc_elem->tclas_processing);
 	}
 
+skip_tclas_elem:
+	if (allow_scs_traffic_desc && desc_elem->qos_char_elem.available) {
+		qos_elem = &desc_elem->qos_char_elem;
+		/* Element ID, Length, and Element ID Extension */
+		wpabuf_put_u8(buf, WLAN_EID_EXTENSION);
+		len1 = wpabuf_put(buf, 1);
+		wpabuf_put_u8(buf, WLAN_EID_EXT_QOS_CHARACTERISTICS);
+
+		/* Remove invalid mask bits */
+
+		/* Medium Time is applicable only for direct link */
+		if ((qos_elem->mask & SCS_QOS_BIT_MEDIUM_TIME) &&
+		    qos_elem->direction != SCS_DIRECTION_DIRECT)
+			qos_elem->mask &= ~SCS_QOS_BIT_MEDIUM_TIME;
+
+		/* Service Start Time LinkID is valid only when Service Start
+		 * Time is present.
+		 */
+		if ((qos_elem->mask & SCS_QOS_BIT_SERVICE_START_TIME_LINKID) &&
+		    !(qos_elem->mask & SCS_QOS_BIT_SERVICE_START_TIME))
+			qos_elem->mask &=
+				~SCS_QOS_BIT_SERVICE_START_TIME_LINKID;
+
+		/* IEEE P802.11be/D4.0, 9.4.2.316 QoS Characteristics element,
+		 * Figure 9-1001av (Control Info field format)
+		 */
+		control_info = ((u32) qos_elem->direction <<
+				EHT_QOS_CONTROL_INFO_DIRECTION_OFFSET);
+		control_info |= ((u32) desc_elem->intra_access_priority <<
+				 EHT_QOS_CONTROL_INFO_TID_OFFSET);
+		control_info |= ((u32) desc_elem->intra_access_priority <<
+				 EHT_QOS_CONTROL_INFO_USER_PRIORITY_OFFSET);
+		control_info |= ((u32) qos_elem->mask <<
+				 EHT_QOS_CONTROL_INFO_PRESENCE_MASK_OFFSET);
+
+		/* Control Info */
+		wpabuf_put_le32(buf, control_info);
+		/* Minimum Service Interval */
+		wpabuf_put_le32(buf, qos_elem->min_si);
+		/* Maximum Service Interval */
+		wpabuf_put_le32(buf, qos_elem->max_si);
+		/* Minimum Data Rate */
+		wpabuf_put_le24(buf, qos_elem->min_data_rate);
+		/* Delay Bound */
+		wpabuf_put_le24(buf, qos_elem->delay_bound);
+
+		/* Maximum MSDU Size */
+		if (qos_elem->mask & SCS_QOS_BIT_MAX_MSDU_SIZE)
+			wpabuf_put_le16(buf, qos_elem->max_msdu_size);
+		/* Start Service Time */
+		if (qos_elem->mask & SCS_QOS_BIT_SERVICE_START_TIME)
+			wpabuf_put_le32(buf, qos_elem->service_start_time);
+		/* Service Start Time LinkID */
+		if (qos_elem->mask & SCS_QOS_BIT_SERVICE_START_TIME_LINKID)
+			wpabuf_put_u8(buf,
+				      qos_elem->service_start_time_link_id);
+		/* Mean Data Rate */
+		if (qos_elem->mask & SCS_QOS_BIT_MEAN_DATA_RATE)
+			wpabuf_put_le24(buf, qos_elem->mean_data_rate);
+		/* Delayed Bounded Burst Size */
+		if (qos_elem->mask & SCS_QOS_BIT_DELAYED_BOUNDED_BURST_SIZE)
+			wpabuf_put_le32(buf, qos_elem->burst_size);
+		/* MSDU Lifetime */
+		if (qos_elem->mask & SCS_QOS_BIT_MSDU_LIFETIME)
+			wpabuf_put_le16(buf, qos_elem->msdu_lifetime);
+		/* MSDU Delivery Info */
+		if (qos_elem->mask & SCS_QOS_BIT_MSDU_DELIVERY_INFO)
+			wpabuf_put_u8(buf, qos_elem->msdu_delivery_info);
+		/* Medium Time */
+		if (qos_elem->mask & SCS_QOS_BIT_MEDIUM_TIME)
+			wpabuf_put_le16(buf, qos_elem->medium_time);
+
+		*len1 = (u8 *) wpabuf_put(buf, 0) - len1 - 1;
+	}
+
 end:
 	*len = (u8 *) wpabuf_put(buf, 0) - len - 1;
 	return 0;
@@ -274,8 +367,51 @@
 }
 
 
+static size_t qos_char_len(const struct qos_characteristics *qos_elem)
+{
+	size_t buf_len = 0;
+
+	buf_len += 1 +	/* Element ID */
+		1 +	/* Length */
+		1 +	/* Element ID Extension */
+		4 +	/* Control Info */
+		4 +	/* Minimum Service Interval */
+		4 +	/* Maximum Service Interval */
+		3 +	/* Minimum Data Rate */
+		3;	/* Delay Bound */
+
+	if (qos_elem->mask & SCS_QOS_BIT_MAX_MSDU_SIZE)
+		buf_len += 2;	 /* Maximum MSDU Size */
+
+	if (qos_elem->mask & SCS_QOS_BIT_SERVICE_START_TIME) {
+		buf_len += 4;	 /* Service Start Time */
+		if (qos_elem->mask & SCS_QOS_BIT_SERVICE_START_TIME_LINKID)
+			buf_len++;	/* Service Start Time LinkID */
+	}
+
+	if (qos_elem->mask & SCS_QOS_BIT_MEAN_DATA_RATE)
+		buf_len += 3;	 /* Mean Data Rate */
+
+	if (qos_elem->mask & SCS_QOS_BIT_DELAYED_BOUNDED_BURST_SIZE)
+		buf_len += 4;	 /* Delayed Bounded Burst Size */
+
+	if (qos_elem->mask & SCS_QOS_BIT_MSDU_LIFETIME)
+		buf_len += 2;	 /* MSDU Lifetime */
+
+	if (qos_elem->mask & SCS_QOS_BIT_MSDU_DELIVERY_INFO)
+		buf_len++;	 /* MSDU Delivery Info */
+
+	if (qos_elem->mask & SCS_QOS_BIT_MEDIUM_TIME &&
+	    qos_elem->direction == SCS_DIRECTION_DIRECT)
+		buf_len += 2;	 /* Medium Time */
+
+	return buf_len;
+}
+
+
 static struct wpabuf * allocate_scs_buf(struct scs_desc_elem *desc_elem,
-					unsigned int num_scs_desc)
+					unsigned int num_scs_desc,
+					bool allow_scs_traffic_desc)
 {
 	struct wpabuf *buf;
 	size_t buf_len = 0;
@@ -293,6 +429,13 @@
 		if (desc_elem->request_type == SCS_REQ_REMOVE)
 			continue;
 
+		if (allow_scs_traffic_desc &&
+		    desc_elem->qos_char_elem.available)
+			buf_len += qos_char_len(&desc_elem->qos_char_elem);
+
+		if (!tclas_elem_required(&desc_elem->qos_char_elem))
+			continue;
+
 		if (desc_elem->intra_access_priority || desc_elem->scs_up_avail)
 			buf_len += 3;
 
@@ -370,8 +513,11 @@
 {
 	struct wpabuf *buf = NULL;
 	struct scs_desc_elem *desc_elem = NULL;
+	const struct ieee80211_eht_capabilities *eht;
+	const u8 *eht_ie;
 	int ret = -1;
 	unsigned int i;
+	bool allow_scs_traffic_desc = false;
 
 	if (wpa_s->wpa_state != WPA_COMPLETED || !wpa_s->current_ssid)
 		return -1;
@@ -386,8 +532,28 @@
 	if (!desc_elem)
 		return -1;
 
+	if (wpa_is_non_eht_scs_traffic_desc_supported(wpa_s->current_bss))
+		allow_scs_traffic_desc = true;
+
+	/* Allow SCS Traffic descriptor support for EHT connection */
+	eht_ie = wpa_bss_get_ie_ext(wpa_s->current_bss,
+				    WLAN_EID_EXT_EHT_CAPABILITIES);
+	if (wpa_s->connection_eht && eht_ie &&
+	    eht_ie[1] >= 1 + IEEE80211_EHT_CAPAB_MIN_LEN) {
+		eht = (const struct ieee80211_eht_capabilities *) &eht_ie[3];
+		if (eht->mac_cap & EHT_MACCAP_SCS_TRAFFIC_DESC)
+			allow_scs_traffic_desc = true;
+	}
+
+	if (!allow_scs_traffic_desc && desc_elem->qos_char_elem.available) {
+		wpa_dbg(wpa_s, MSG_INFO,
+			"Connection does not support EHT/non-EHT SCS Traffic Description - could not send SCS Request with QoS Characteristics");
+		return -1;
+	}
+
 	buf = allocate_scs_buf(desc_elem,
-			       wpa_s->scs_robust_av_req.num_scs_desc);
+			       wpa_s->scs_robust_av_req.num_scs_desc,
+			       allow_scs_traffic_desc);
 	if (!buf)
 		return -1;
 
@@ -401,7 +567,8 @@
 	for (i = 0; i < wpa_s->scs_robust_av_req.num_scs_desc;
 	     i++, desc_elem++) {
 		/* SCS Descriptor element */
-		if (wpas_populate_scs_descriptor_ie(desc_elem, buf) < 0)
+		if (wpas_populate_scs_descriptor_ie(desc_elem, buf,
+						    allow_scs_traffic_desc) < 0)
 			goto end;
 	}
 
diff --git a/wpa_supplicant/rrm.c b/wpa_supplicant/rrm.c
index 238fe68..8e51717 100644
--- a/wpa_supplicant/rrm.c
+++ b/wpa_supplicant/rrm.c
@@ -1033,7 +1033,7 @@
 	}
 	os_get_reltime(&wpa_s->beacon_rep_scan);
 	if (wpa_s->scanning || wpas_p2p_in_progress(wpa_s) ||
-	    wpa_supplicant_trigger_scan(wpa_s, params))
+	    wpa_supplicant_trigger_scan(wpa_s, params, true, false))
 		wpas_rrm_refuse_request(wpa_s);
 	params->duration = prev_duration;
 }
diff --git a/wpa_supplicant/scan.c b/wpa_supplicant/scan.c
index c3984a4..bab6a23 100644
--- a/wpa_supplicant/scan.c
+++ b/wpa_supplicant/scan.c
@@ -24,6 +24,8 @@
 #include "scan.h"
 #include "mesh.h"
 
+static struct wpabuf * wpa_supplicant_extra_ies(struct wpa_supplicant *wpa_s);
+
 
 static void wpa_supplicant_gen_assoc_event(struct wpa_supplicant *wpa_s)
 {
@@ -278,22 +280,49 @@
  * wpa_supplicant_trigger_scan - Request driver to start a scan
  * @wpa_s: Pointer to wpa_supplicant data
  * @params: Scan parameters
+ * @default_ies: Whether or not to use the default IEs in the Probe Request
+ * frames. Note that this will free any existing IEs set in @params, so this
+ * shouldn't be set if the IEs have already been set with
+ * wpa_supplicant_extra_ies(). Otherwise, wpabuf_free() will lead to a
+ * double-free.
+ * @next: Whether or not to perform this scan as the next radio work
  * Returns: 0 on success, -1 on failure
  */
 int wpa_supplicant_trigger_scan(struct wpa_supplicant *wpa_s,
-				struct wpa_driver_scan_params *params)
+				struct wpa_driver_scan_params *params,
+				bool default_ies, bool next)
 {
 	struct wpa_driver_scan_params *ctx;
+	struct wpabuf *ies = NULL;
 
 	if (wpa_s->scan_work) {
 		wpa_dbg(wpa_s, MSG_INFO, "Reject scan trigger since one is already pending");
 		return -1;
 	}
 
+	if (default_ies) {
+		if (params->extra_ies_len) {
+			os_free((u8 *) params->extra_ies);
+			params->extra_ies = NULL;
+			params->extra_ies_len = 0;
+		}
+		ies = wpa_supplicant_extra_ies(wpa_s);
+		if (ies) {
+			params->extra_ies = wpabuf_head(ies);
+			params->extra_ies_len = wpabuf_len(ies);
+		}
+	}
 	ctx = wpa_scan_clone_params(params);
+	if (ies) {
+		wpabuf_free(ies);
+		params->extra_ies = NULL;
+		params->extra_ies_len = 0;
+	}
+	wpa_s->last_scan_all_chan = !params->freqs;
+	wpa_s->last_scan_non_coloc_6ghz = params->non_coloc_6ghz;
 	if (!ctx ||
-	    radio_add_work(wpa_s, 0, "scan", 0, wpas_trigger_scan_cb, ctx) < 0)
-	{
+	    radio_add_work(wpa_s, 0, "scan", next, wpas_trigger_scan_cb,
+			   ctx) < 0) {
 		wpa_scan_free_params(ctx);
 		wpa_msg(wpa_s, MSG_INFO, WPA_EVENT_SCAN_FAILED "ret=-1");
 		return -1;
@@ -394,29 +423,6 @@
 }
 
 
-#ifdef CONFIG_P2P
-static bool is_6ghz_supported(struct wpa_supplicant *wpa_s)
-{
-	struct hostapd_channel_data *chnl;
-	int i, j;
-
-	for (i = 0; i < wpa_s->hw.num_modes; i++) {
-		if (wpa_s->hw.modes[i].mode == HOSTAPD_MODE_IEEE80211A) {
-			chnl = wpa_s->hw.modes[i].channels;
-			for (j = 0; j < wpa_s->hw.modes[i].num_channels; j++) {
-				if (chnl[j].flag & HOSTAPD_CHAN_DISABLED)
-					continue;
-				if (is_6ghz_freq(chnl[j].freq))
-					return true;
-			}
-		}
-	}
-
-	return false;
-}
-#endif /* CONFIG_P2P */
-
-
 static void wpa_supplicant_optimize_freqs(
 	struct wpa_supplicant *wpa_s, struct wpa_driver_scan_params *params)
 {
@@ -615,7 +621,7 @@
 	wpa_drv_get_ext_capa(wpa_s, type);
 
 	ext_capab_len = wpas_build_ext_capab(wpa_s, ext_capab,
-					     sizeof(ext_capab));
+					     sizeof(ext_capab), NULL);
 	if (ext_capab_len > 0 &&
 	    wpabuf_resize(&default_ies, ext_capab_len) == 0)
 		wpabuf_put_data(default_ies, ext_capab, ext_capab_len);
@@ -649,6 +655,67 @@
 }
 
 
+static struct wpabuf * wpa_supplicant_ml_probe_ie(int mld_id, u16 links)
+{
+	struct wpabuf *extra_ie;
+	u16 control = MULTI_LINK_CONTROL_TYPE_PROBE_REQ;
+	size_t len = 3 + 4 + 4 * MAX_NUM_MLD_LINKS;
+	u8 link_id;
+	u8 *len_pos;
+
+	if (mld_id >= 0) {
+		control |= EHT_ML_PRES_BM_PROBE_REQ_AP_MLD_ID;
+		len++;
+	}
+
+	extra_ie = wpabuf_alloc(len);
+	if (!extra_ie)
+		return NULL;
+
+	wpabuf_put_u8(extra_ie, WLAN_EID_EXTENSION);
+	len_pos = wpabuf_put(extra_ie, 1);
+	wpabuf_put_u8(extra_ie, WLAN_EID_EXT_MULTI_LINK);
+
+	wpabuf_put_le16(extra_ie, control);
+
+	/* common info length and MLD ID (if requested) */
+	if (mld_id >= 0) {
+		wpabuf_put_u8(extra_ie, 2);
+		wpabuf_put_u8(extra_ie, mld_id);
+
+		wpa_printf(MSG_DEBUG, "MLD: ML probe targeted at MLD ID %d",
+			   mld_id);
+	} else {
+		wpabuf_put_u8(extra_ie, 1);
+
+		wpa_printf(MSG_DEBUG, "MLD: ML probe targeted at receiving AP");
+	}
+
+	if (!links)
+		wpa_printf(MSG_DEBUG, "MLD: Probing all links");
+	else
+		wpa_printf(MSG_DEBUG, "MLD: Probing links 0x%04x", links);
+
+	for (link_id = 0; link_id < MAX_NUM_MLD_LINKS; link_id++) {
+		if (!(links & BIT(link_id)))
+			continue;
+
+		wpabuf_put_u8(extra_ie, EHT_ML_SUB_ELEM_PER_STA_PROFILE);
+
+		/* Subelement length includes only the control */
+		wpabuf_put_u8(extra_ie, 2);
+
+		control = link_id | EHT_PER_STA_CTRL_COMPLETE_PROFILE_MSK;
+
+		wpabuf_put_le16(extra_ie, control);
+	}
+
+	*len_pos = (u8 *) wpabuf_put(extra_ie, 0) - len_pos - 1;
+
+	return extra_ie;
+}
+
+
 static struct wpabuf * wpa_supplicant_extra_ies(struct wpa_supplicant *wpa_s)
 {
 	struct wpabuf *extra_ie = NULL;
@@ -659,6 +726,15 @@
 	enum wps_request_type req_type = WPS_REQ_ENROLLEE_INFO;
 #endif /* CONFIG_WPS */
 
+	if (!is_zero_ether_addr(wpa_s->ml_probe_bssid)) {
+		extra_ie = wpa_supplicant_ml_probe_ie(wpa_s->ml_probe_mld_id,
+						      wpa_s->ml_probe_links);
+
+		/* No other elements should be included in the probe request */
+		wpa_printf(MSG_DEBUG, "MLD: Scan including only ML element");
+		return extra_ie;
+	}
+
 #ifdef CONFIG_P2P
 	if (wpa_s->p2p_group_interface == P2P_GROUP_INTERFACE_CLIENT)
 		wpa_drv_get_ext_capa(wpa_s, WPA_IF_P2P_CLIENT);
@@ -667,7 +743,7 @@
 		wpa_drv_get_ext_capa(wpa_s, WPA_IF_STATION);
 
 	ext_capab_len = wpas_build_ext_capab(wpa_s, ext_capab,
-					     sizeof(ext_capab));
+					     sizeof(ext_capab), NULL);
 	if (ext_capab_len > 0 &&
 	    wpabuf_resize(&extra_ie, ext_capab_len) == 0)
 		wpabuf_put_data(extra_ie, ext_capab, ext_capab_len);
@@ -1114,6 +1190,10 @@
 		params.ssids[0].ssid = wpa_s->go_params->ssid;
 		params.ssids[0].ssid_len = wpa_s->go_params->ssid_len;
 		params.num_ssids = 1;
+		params.bssid = wpa_s->go_params->peer_interface_addr;
+		wpa_printf(MSG_DEBUG, "P2P: Use specific BSSID " MACSTR
+			   " (peer interface address) for scan",
+			   MAC2STR(params.bssid));
 		goto ssid_list_set;
 	}
 
@@ -1124,6 +1204,12 @@
 			params.ssids[0].ssid_len =
 				wpa_s->current_ssid->ssid_len;
 			params.num_ssids = 1;
+			if (wpa_s->current_ssid->bssid_set) {
+				params.bssid = wpa_s->current_ssid->bssid;
+				wpa_printf(MSG_DEBUG, "P2P: Use specific BSSID "
+					   MACSTR " for scan",
+					   MAC2STR(params.bssid));
+			}
 		} else {
 			wpa_printf(MSG_DEBUG, "P2P: No specific SSID known for scan during invitation");
 		}
@@ -1397,8 +1483,13 @@
 				"Scan a previously specified BSSID " MACSTR,
 				MAC2STR(params.bssid));
 		}
+	} else if (!is_zero_ether_addr(wpa_s->ml_probe_bssid)) {
+		wpa_printf(MSG_DEBUG, "Scanning for ML probe request");
+		params.bssid = wpa_s->ml_probe_bssid;
+		params.min_probe_req_content = true;
 	}
 
+
 	if (wpa_s->last_scan_req == MANUAL_SCAN_REQ &&
 	    wpa_s->manual_non_coloc_6ghz) {
 		wpa_dbg(wpa_s, MSG_DEBUG, "Collocated 6 GHz logic is disabled");
@@ -1444,12 +1535,12 @@
 		}
 	}
 
-	if (!params.freqs && is_6ghz_supported(wpa_s) &&
+	if (!params.freqs && wpas_is_6ghz_supported(wpa_s, true) &&
 	    (wpa_s->p2p_in_invitation || wpa_s->p2p_in_provisioning))
 		wpas_p2p_scan_freqs(wpa_s, &params, true);
 #endif /* CONFIG_P2P */
 
-	ret = wpa_supplicant_trigger_scan(wpa_s, scan_params);
+	ret = wpa_supplicant_trigger_scan(wpa_s, scan_params, false, false);
 
 	if (ret && wpa_s->last_scan_req == MANUAL_SCAN_REQ && params.freqs &&
 	    !wpa_s->manual_scan_freqs) {
@@ -1480,6 +1571,10 @@
 		if (params.bssid)
 			os_memset(wpa_s->next_scan_bssid, 0, ETH_ALEN);
 	}
+
+	wpa_s->ml_probe_mld_id = -1;
+	wpa_s->ml_probe_links = 0;
+	os_memset(wpa_s->ml_probe_bssid, 0, sizeof(wpa_s->ml_probe_bssid));
 }
 
 
@@ -2118,6 +2213,160 @@
 }
 
 
+static int wpas_channel_width_offset(enum chan_width cw)
+{
+	switch (cw) {
+	case CHAN_WIDTH_40:
+		return 1;
+	case CHAN_WIDTH_80:
+		return 2;
+	case CHAN_WIDTH_80P80:
+	case CHAN_WIDTH_160:
+		return 3;
+	case CHAN_WIDTH_320:
+		return 4;
+	default:
+		return 0;
+	}
+}
+
+
+/**
+ * wpas_channel_width_tx_pwr - Calculate the max transmit power at the channel
+ * width
+ * @ies: Information elements
+ * @ies_len: Length of elements
+ * @cw: The channel width
+ * Returns: The max transmit power at the channel width, TX_POWER_NO_CONSTRAINT
+ * if it is not constrained.
+ *
+ * This function is only used to estimate the actual signal RSSI when associated
+ * based on the beacon RSSI at the STA. Beacon frames are transmitted on 20 MHz
+ * channels, while the Data frames usually use higher channel width. Therefore
+ * their RSSIs may be different. Assuming there is a fixed gap between the TX
+ * power limit of the STA defined by the Transmit Power Envelope element and the
+ * TX power of the AP, the difference in the TX power of X MHz and Y MHz at the
+ * STA equals to the difference at the AP, and the difference in the signal RSSI
+ * at the STA. tx_pwr is a floating point number in the standard, but the error
+ * of casting to int is trivial in comparing two BSSes.
+ */
+static int wpas_channel_width_tx_pwr(const u8 *ies, size_t ies_len,
+				     enum chan_width cw)
+{
+#define MIN(a, b) (a < b ? a : b)
+	int offset = wpas_channel_width_offset(cw);
+	const struct element *elem;
+	int max_tx_power = TX_POWER_NO_CONSTRAINT, tx_pwr = 0;
+
+	for_each_element_id(elem, WLAN_EID_TRANSMIT_POWER_ENVELOPE, ies,
+			    ies_len) {
+		int max_tx_pwr_count;
+		enum max_tx_pwr_interpretation tx_pwr_intrpn;
+		enum reg_6g_client_type client_type;
+
+		if (elem->datalen < 1)
+			continue;
+
+		/*
+		 * IEEE Std 802.11ax-2021, 9.4.2.161 (Transmit Power Envelope
+		 * element) defines Maximum Transmit Power Count (B0-B2),
+		 * Maximum Transmit Power Interpretation (B3-B5), and Maximum
+		 * Transmit Power Category (B6-B7).
+		 */
+		max_tx_pwr_count = elem->data[0] & 0x07;
+		tx_pwr_intrpn = (elem->data[0] >> 3) & 0x07;
+		client_type = (elem->data[0] >> 6) & 0x03;
+
+		if (client_type != REG_DEFAULT_CLIENT)
+			continue;
+
+		if (tx_pwr_intrpn == LOCAL_EIRP ||
+		    tx_pwr_intrpn == REGULATORY_CLIENT_EIRP) {
+			int offs;
+
+			max_tx_pwr_count = MIN(max_tx_pwr_count, 3);
+			offs = MIN(offset, max_tx_pwr_count) + 1;
+			if (elem->datalen <= offs)
+				continue;
+			tx_pwr = (signed char) elem->data[offs];
+			/*
+			 * Maximum Transmit Power subfield is encoded as an
+			 * 8-bit 2s complement signed integer in the range -64
+			 * dBm to 63 dBm with a 0.5 dB step. 63.5 dBm means no
+			 * local maximum transmit power constraint.
+			 */
+			if (tx_pwr == 127)
+				continue;
+			tx_pwr /= 2;
+			max_tx_power = MIN(max_tx_power, tx_pwr);
+		} else if (tx_pwr_intrpn == LOCAL_EIRP_PSD ||
+			   tx_pwr_intrpn == REGULATORY_CLIENT_EIRP_PSD) {
+			if (elem->datalen < 2)
+				continue;
+
+			tx_pwr = (signed char) elem->data[1];
+			/*
+			 * Maximum Transmit PSD subfield is encoded as an 8-bit
+			 * 2s complement signed integer. -128 indicates that the
+			 * corresponding 20 MHz channel cannot be used for
+			 * transmission. +127 indicates that no maximum PSD
+			 * limit is specified for the corresponding 20 MHz
+			 * channel.
+			 */
+			if (tx_pwr == 127 || tx_pwr == -128)
+				continue;
+
+			/*
+			 * The Maximum Transmit PSD subfield indicates the
+			 * maximum transmit PSD for the 20 MHz channel. Suppose
+			 * the PSD value is X dBm/MHz, the TX power of N MHz is
+			 * X + 10*log10(N) = X + 10*log10(20) + 10*log10(N/20) =
+			 * X + 13 + 3*log2(N/20)
+			 */
+			tx_pwr = tx_pwr / 2 + 13 + offset * 3;
+			max_tx_power = MIN(max_tx_power, tx_pwr);
+		}
+	}
+
+	return max_tx_power;
+#undef MIN
+}
+
+
+/**
+ * Estimate the RSSI bump of channel width |cw| with respect to 20 MHz channel.
+ * If the TX power has no constraint, it is unable to estimate the RSSI bump.
+ */
+int wpas_channel_width_rssi_bump(const u8 *ies, size_t ies_len,
+				 enum chan_width cw)
+{
+	int max_20mhz_tx_pwr = wpas_channel_width_tx_pwr(ies, ies_len,
+							 CHAN_WIDTH_20);
+	int max_cw_tx_pwr = wpas_channel_width_tx_pwr(ies, ies_len, cw);
+
+	return (max_20mhz_tx_pwr == TX_POWER_NO_CONSTRAINT ||
+		max_cw_tx_pwr == TX_POWER_NO_CONSTRAINT) ?
+		0 : (max_cw_tx_pwr - max_20mhz_tx_pwr);
+}
+
+
+int wpas_adjust_snr_by_chanwidth(const u8 *ies, size_t ies_len,
+				 enum chan_width max_cw, int snr)
+{
+	int rssi_bump = wpas_channel_width_rssi_bump(ies, ies_len, max_cw);
+	/*
+	 * The noise has uniform power spectral density (PSD) across the
+	 * frequency band, its power is proportional to the channel width.
+	 * Suppose the PSD of noise is X dBm/MHz, the noise power of N MHz is
+	 * X + 10*log10(N), and the noise power bump with respect to 20 MHz is
+	 * 10*log10(N) - 10*log10(20) = 10*log10(N/20) = 3*log2(N/20)
+	 */
+	int noise_bump = 3 * wpas_channel_width_offset(max_cw);
+
+	return snr + rssi_bump - noise_bump;
+}
+
+
 /* Compare function for sorting scan results. Return >0 if @b is considered
  * better. */
 static int wpa_scan_result_compar(const void *a, const void *b)
@@ -2129,6 +2378,7 @@
 	struct wpa_scan_res *wb = *_wb;
 	int wpa_a, wpa_b;
 	int snr_a, snr_b, snr_a_full, snr_b_full;
+	size_t ies_len;
 
 	/* WPA/WPA2 support preferred */
 	wpa_a = wpa_scan_get_vendor_ie(wa, WPA_IE_VENDOR_TYPE) != NULL ||
@@ -2150,10 +2400,21 @@
 		return -1;
 
 	if (wa->flags & wb->flags & WPA_SCAN_LEVEL_DBM) {
-		snr_a_full = wa->snr;
-		snr_a = MIN(wa->snr, GREAT_SNR);
-		snr_b_full = wb->snr;
-		snr_b = MIN(wb->snr, GREAT_SNR);
+		/*
+		 * The scan result estimates SNR over 20 MHz, while Data frames
+		 * usually use wider channel width. The TX power and noise power
+		 * are both affected by the channel width.
+		 */
+		ies_len = wa->ie_len ? wa->ie_len : wa->beacon_ie_len;
+		snr_a_full = wpas_adjust_snr_by_chanwidth((const u8 *) (wa + 1),
+							  ies_len, wa->max_cw,
+							  wa->snr);
+		snr_a = MIN(snr_a_full, GREAT_SNR);
+		ies_len = wb->ie_len ? wb->ie_len : wb->beacon_ie_len;
+		snr_b_full = wpas_adjust_snr_by_chanwidth((const u8 *) (wb + 1),
+							  ies_len, wb->max_cw,
+							  wb->snr);
+		snr_b = MIN(snr_b_full, GREAT_SNR);
 	} else {
 		/* Level is not in dBm, so we can't calculate
 		 * SNR. Just use raw level (units unknown). */
@@ -2606,11 +2867,17 @@
 
 unsigned int wpas_get_est_tpt(const struct wpa_supplicant *wpa_s,
 			      const u8 *ies, size_t ies_len, int rate,
-			      int snr, int freq)
+			      int snr, int freq, enum chan_width *max_cw)
 {
 	struct hostapd_hw_modes *hw_mode;
 	unsigned int est, tmp;
 	const u8 *ie;
+	/*
+	 * No need to apply a bump to the noise here because the
+	 * minsnr_bitrate_entry tables are based on MCS tables where this has
+	 * been taken into account.
+	 */
+	int adjusted_snr;
 
 	/* Limit based on estimated SNR */
 	if (rate > 1 * 2 && snr < 1)
@@ -2659,6 +2926,7 @@
 	if (hw_mode && hw_mode->ht_capab) {
 		ie = get_ie(ies, ies_len, WLAN_EID_HT_CAP);
 		if (ie) {
+			*max_cw = CHAN_WIDTH_20;
 			tmp = max_ht20_rate(snr, false);
 			if (tmp > est)
 				est = tmp;
@@ -2670,7 +2938,11 @@
 		ie = get_ie(ies, ies_len, WLAN_EID_HT_OPERATION);
 		if (ie && ie[1] >= 2 &&
 		    (ie[3] & HT_INFO_HT_PARAM_SECONDARY_CHNL_OFF_MASK)) {
-			tmp = max_ht40_rate(snr, false);
+			*max_cw = CHAN_WIDTH_40;
+			adjusted_snr = snr +
+				wpas_channel_width_rssi_bump(ies, ies_len,
+							     CHAN_WIDTH_40);
+			tmp = max_ht40_rate(adjusted_snr, false);
 			if (tmp > est)
 				est = tmp;
 		}
@@ -2682,6 +2954,8 @@
 		if (ie) {
 			bool vht80 = false, vht160 = false;
 
+			if (*max_cw == CHAN_WIDTH_UNKNOWN)
+				*max_cw = CHAN_WIDTH_20;
 			tmp = max_ht20_rate(snr, true) + 1;
 			if (tmp > est)
 				est = tmp;
@@ -2690,7 +2964,11 @@
 			if (ie && ie[1] >= 2 &&
 			    (ie[3] &
 			     HT_INFO_HT_PARAM_SECONDARY_CHNL_OFF_MASK)) {
-				tmp = max_ht40_rate(snr, true) + 1;
+				*max_cw = CHAN_WIDTH_40;
+				adjusted_snr = snr +
+					wpas_channel_width_rssi_bump(
+						ies, ies_len, CHAN_WIDTH_40);
+				tmp = max_ht40_rate(adjusted_snr, true) + 1;
 				if (tmp > est)
 					est = tmp;
 			}
@@ -2716,7 +2994,11 @@
 			}
 
 			if (vht80) {
-				tmp = max_vht80_rate(snr) + 1;
+				*max_cw = CHAN_WIDTH_80;
+				adjusted_snr = snr +
+					wpas_channel_width_rssi_bump(
+						ies, ies_len, CHAN_WIDTH_80);
+				tmp = max_vht80_rate(adjusted_snr) + 1;
 				if (tmp > est)
 					est = tmp;
 			}
@@ -2725,7 +3007,11 @@
 			    (hw_mode->vht_capab &
 			     (VHT_CAP_SUPP_CHAN_WIDTH_160MHZ |
 			      VHT_CAP_SUPP_CHAN_WIDTH_160_80PLUS80MHZ))) {
-				tmp = max_vht160_rate(snr) + 1;
+				*max_cw = CHAN_WIDTH_160;
+				adjusted_snr = snr +
+					wpas_channel_width_rssi_bump(
+						ies, ies_len, CHAN_WIDTH_160);
+				tmp = max_vht160_rate(adjusted_snr) + 1;
 				if (tmp > est)
 					est = tmp;
 			}
@@ -2758,6 +3044,8 @@
 			}
 		}
 
+		if (*max_cw == CHAN_WIDTH_UNKNOWN)
+			*max_cw = CHAN_WIDTH_20;
 		tmp = max_he_eht_rate(he20_table, snr, is_eht) + boost;
 		if (tmp > est)
 			est = tmp;
@@ -2767,14 +3055,26 @@
 		if (cw &
 		    (IS_2P4GHZ(freq) ? HE_PHYCAP_CHANNEL_WIDTH_SET_40MHZ_IN_2G :
 		     HE_PHYCAP_CHANNEL_WIDTH_SET_40MHZ_80MHZ_IN_5G)) {
-			tmp = max_he_eht_rate(he40_table, snr, is_eht) + boost;
+			if (*max_cw == CHAN_WIDTH_UNKNOWN ||
+			    *max_cw < CHAN_WIDTH_40)
+				*max_cw = CHAN_WIDTH_40;
+			adjusted_snr = snr + wpas_channel_width_rssi_bump(
+				ies, ies_len, CHAN_WIDTH_40);
+			tmp = max_he_eht_rate(he40_table, adjusted_snr,
+					      is_eht) + boost;
 			if (tmp > est)
 				est = tmp;
 		}
 
 		if (!IS_2P4GHZ(freq) &&
 		    (cw & HE_PHYCAP_CHANNEL_WIDTH_SET_40MHZ_80MHZ_IN_5G)) {
-			tmp = max_he_eht_rate(he80_table, snr, is_eht) + boost;
+			if (*max_cw == CHAN_WIDTH_UNKNOWN ||
+			    *max_cw < CHAN_WIDTH_80)
+				*max_cw = CHAN_WIDTH_80;
+			adjusted_snr = snr + wpas_channel_width_rssi_bump(
+				ies, ies_len, CHAN_WIDTH_80);
+			tmp = max_he_eht_rate(he80_table, adjusted_snr,
+					      is_eht) + boost;
 			if (tmp > est)
 				est = tmp;
 		}
@@ -2782,7 +3082,13 @@
 		if (!IS_2P4GHZ(freq) &&
 		    (cw & (HE_PHYCAP_CHANNEL_WIDTH_SET_160MHZ_IN_5G |
 			   HE_PHYCAP_CHANNEL_WIDTH_SET_80PLUS80MHZ_IN_5G))) {
-			tmp = max_he_eht_rate(he160_table, snr, is_eht) + boost;
+			if (*max_cw == CHAN_WIDTH_UNKNOWN ||
+			    *max_cw < CHAN_WIDTH_160)
+				*max_cw = CHAN_WIDTH_160;
+			adjusted_snr = snr + wpas_channel_width_rssi_bump(
+				ies, ies_len, CHAN_WIDTH_160);
+			tmp = max_he_eht_rate(he160_table, adjusted_snr,
+					      is_eht) + boost;
 			if (tmp > est)
 				est = tmp;
 		}
@@ -2795,7 +3101,12 @@
 		if (is_6ghz_freq(freq) &&
 		    (eht->phy_cap[EHT_PHYCAP_320MHZ_IN_6GHZ_SUPPORT_IDX] &
 		     EHT_PHYCAP_320MHZ_IN_6GHZ_SUPPORT_MASK)) {
-			tmp = max_he_eht_rate(eht320_table, snr, true);
+			if (*max_cw == CHAN_WIDTH_UNKNOWN ||
+			    *max_cw < CHAN_WIDTH_320)
+				*max_cw = CHAN_WIDTH_320;
+			adjusted_snr = snr + wpas_channel_width_rssi_bump(
+				ies, ies_len, CHAN_WIDTH_320);
+			tmp = max_he_eht_rate(eht320_table, adjusted_snr, true);
 			if (tmp > est)
 				est = tmp;
 		}
@@ -2821,8 +3132,8 @@
 
 	if (!ie_len)
 		ie_len = res->beacon_ie_len;
-	res->est_throughput =
-		wpas_get_est_tpt(wpa_s, ies, ie_len, rate, snr, res->freq);
+	res->est_throughput = wpas_get_est_tpt(wpa_s, ies, ie_len, rate, snr,
+					       res->freq, &res->max_cw);
 
 	/* TODO: channel utilization and AP load (e.g., from AP Beacon) */
 }
@@ -3045,6 +3356,7 @@
 	params->relative_adjust_rssi = src->relative_adjust_rssi;
 	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;
 	return params;
 
 failed:
diff --git a/wpa_supplicant/scan.h b/wpa_supplicant/scan.h
index 30f4395..f1739fa 100644
--- a/wpa_supplicant/scan.h
+++ b/wpa_supplicant/scan.h
@@ -30,6 +30,14 @@
  */
 #define GREAT_SNR 25
 
+/*
+ * IEEE Sts 802.11ax-2021, 9.4.2.161 (Transmit Power Envelope element) indicates
+ * no max TX power limit if Maximum Transmit Power field is 63.5 dBm.
+ * The default TX power if it is not constrained by Transmit Power Envelope
+ * element.
+ */
+#define TX_POWER_NO_CONSTRAINT 64
+
 #define IS_2P4GHZ(n) (n >= 2412 && n <= 2484)
 #define IS_5GHZ(n) (n > 4000 && n < 5895)
 
@@ -45,7 +53,8 @@
 				    int scanning);
 struct wpa_driver_scan_params;
 int wpa_supplicant_trigger_scan(struct wpa_supplicant *wpa_s,
-				struct wpa_driver_scan_params *params);
+				struct wpa_driver_scan_params *params,
+				bool default_ies, bool next);
 struct wpa_scan_results *
 wpa_supplicant_get_scan_results(struct wpa_supplicant *wpa_s,
 				struct scan_info *info, int new_scan);
@@ -87,12 +96,16 @@
 			 struct wpa_scan_res *res);
 unsigned int wpas_get_est_tpt(const struct wpa_supplicant *wpa_s,
 			      const u8 *ies, size_t ies_len, int rate,
-			      int snr, int freq);
+			      int snr, int freq, enum chan_width *max_cw);
 void wpa_supplicant_set_default_scan_ies(struct wpa_supplicant *wpa_s);
 int wpa_add_scan_freqs_list(struct wpa_supplicant *wpa_s,
 			    enum hostapd_hw_mode band,
 			    struct wpa_driver_scan_params *params,
 			    bool is_6ghz, bool only_6ghz_psc,
 			    bool exclude_radar);
+int wpas_channel_width_rssi_bump(const u8 *ies, size_t ies_len,
+				 enum chan_width cw);
+int wpas_adjust_snr_by_chanwidth(const u8 *ies, size_t ies_len,
+				 enum chan_width max_cw, int snr);
 
 #endif /* SCAN_H */
diff --git a/wpa_supplicant/sme.c b/wpa_supplicant/sme.c
index 8068f1e..df2c68f 100644
--- a/wpa_supplicant/sme.c
+++ b/wpa_supplicant/sme.c
@@ -378,13 +378,111 @@
 }
 
 
+static void wpas_process_tbtt_info(struct wpa_supplicant *wpa_s, const u8 *data)
+{
+	struct wpa_bss *neigh_bss;
+	const u8 *bssid;
+	u8 bss_params;
+	u8 link_id;
+
+	/* TBTT Information field
+	 * Neighbor AP TBTT Offset[1]
+	 * BSSID[6]
+	 * Short SSID[4]
+	 * BSS parameters[1]
+	 * 20 MHz PSD[1]
+	 * MLD Parameters[3]
+	 *   B0..B7: AP MLD ID
+	 *   B7..B11: Link ID
+	 *   B12..B19: BSS Parameters Change Count
+	 *   B20: All Updates Included
+	 *   B21: Disabled Link Indication */
+
+	bssid = data + 1;
+	bss_params = data[1 + ETH_ALEN + 4];
+
+	data += 13; /* MLD Parameters */
+	link_id = *(data + 1) & 0xF;
+
+	wpa_dbg(wpa_s, MSG_DEBUG,
+		"MLD: mld ID=%u, link ID=%u, bssid=" MACSTR ", bss_params=0x%x",
+		*data, link_id, MAC2STR(bssid), bss_params);
+
+	if (*data) {
+		wpa_printf(MSG_DEBUG, "MLD: Reported link not part of MLD");
+		return;
+	}
+
+	neigh_bss = wpa_bss_get_bssid(wpa_s, bssid);
+	if (!neigh_bss) {
+		wpa_printf(MSG_DEBUG, "MLD: Neighbor not found in scan");
+		return;
+	}
+
+	if (!((bss_params & RNR_BSS_PARAM_SAME_SSID) &&
+	      (bss_params & RNR_BSS_PARAM_CO_LOCATED)) &&
+	    !wpa_scan_res_match(wpa_s, 0, neigh_bss, wpa_s->current_ssid,
+				1, 0)) {
+		wpa_printf(MSG_DEBUG,
+			   "MLD: Neighbor doesn't match current SSID - skip link");
+		return;
+	}
+
+	wpa_s->valid_links |= BIT(link_id);
+	os_memcpy(wpa_s->links[link_id].bssid, bssid, ETH_ALEN);
+	wpa_s->links[link_id].freq = neigh_bss->freq;
+}
+
+
+static void wpas_process_rnr(struct wpa_supplicant *wpa_s, const u8 *pos,
+			     size_t rnr_ie_len)
+{
+	while (rnr_ie_len > sizeof(struct ieee80211_neighbor_ap_info)) {
+		const struct ieee80211_neighbor_ap_info *ap_info =
+			(const struct ieee80211_neighbor_ap_info *) pos;
+		/* The first TBTT Information field */
+		const u8 *data = ap_info->data;
+		u8 tbtt_count;
+		size_t len;
+		int tbtt_i;
+
+		if (rnr_ie_len < sizeof(struct ieee80211_neighbor_ap_info))
+			break;
+
+		tbtt_count = (ap_info->tbtt_info_hdr >> 4) + 1;
+		len = sizeof(struct ieee80211_neighbor_ap_info) +
+			ap_info->tbtt_info_len * tbtt_count;
+
+		wpa_printf(MSG_DEBUG, "MLD: op_class=%u, channel=%u",
+			   ap_info->op_class, ap_info->channel);
+
+		if (len > rnr_ie_len)
+			break;
+
+		if (ap_info->tbtt_info_len < 16) {
+			rnr_ie_len -= len;
+			pos += len;
+			continue;
+		}
+
+		for (tbtt_i = 0; tbtt_i < tbtt_count; tbtt_i++) {
+			wpas_process_tbtt_info(wpa_s, data);
+			data += ap_info->tbtt_info_len;
+		}
+
+		rnr_ie_len -= len;
+		pos += len;
+	}
+}
+
+
 static bool wpas_ml_element(struct wpa_supplicant *wpa_s, struct wpa_bss *bss,
 			    struct wpa_ssid *ssid)
 {
 	struct wpabuf *mlbuf;
-	const u8 *rnr_ie, *pos, *rsn_ie;
+	const u8 *rnr_ie, *rsn_ie;
 	struct wpa_ie_data ie;
-	u8 ml_ie_len, rnr_ie_len;
+	u8 ml_ie_len;
 	const struct ieee80211_eht_ml *eht_ml;
 	const struct eht_ml_basic_common_info *ml_basic_common_info;
 	u8 i;
@@ -394,6 +492,7 @@
 			     BASIC_MULTI_LINK_CTRL_PRES_BSS_PARAM_CH_COUNT |
 			     BASIC_MULTI_LINK_CTRL_PRES_MLD_CAPA);
 	bool ret = false;
+	int rnr_idx;
 
 	if (!(wpa_s->drv_flags2 & WPA_DRIVER_FLAGS2_MLO))
 		return false;
@@ -459,69 +558,22 @@
 
 	wpa_s->valid_links = BIT(wpa_s->mlo_assoc_link_id);
 
-	rnr_ie = wpa_bss_get_ie(bss, WLAN_EID_REDUCED_NEIGHBOR_REPORT);
-	if (!rnr_ie) {
-		wpa_dbg(wpa_s, MSG_DEBUG, "MLD: No RNR element");
-		ret = true;
-		goto out;
-	}
+	ret = true;
 
-	rnr_ie_len = rnr_ie[1];
-	pos = rnr_ie + 2;
-
-	while (rnr_ie_len > sizeof(struct ieee80211_neighbor_ap_info)) {
-		const struct ieee80211_neighbor_ap_info *ap_info =
-			(const struct ieee80211_neighbor_ap_info *) pos;
-		const u8 *data = ap_info->data;
-		size_t len = sizeof(struct ieee80211_neighbor_ap_info) +
-			ap_info->tbtt_info_len;
-
-		wpa_printf(MSG_DEBUG, "MLD: op_class=%u, channel=%u",
-			   ap_info->op_class, ap_info->channel);
-
-		if (len > rnr_ie_len)
-			break;
-
-		if (ap_info->tbtt_info_len < 16) {
-			rnr_ie_len -= len;
-			pos += len;
-			continue;
-		}
-
-		data += 13;
-
-		wpa_printf(MSG_DEBUG, "MLD: mld ID=%u, link ID=%u",
-			   *data, *(data + 1) & 0xF);
-
-		if (*data) {
-			wpa_printf(MSG_DEBUG,
-				   "MLD: Reported link not part of MLD");
-		} else {
-			struct wpa_bss *neigh_bss =
-				wpa_bss_get_bssid(wpa_s, ap_info->data + 1);
-			u8 link_id = *(data + 1) & 0xF;
-
-			if (neigh_bss) {
-				if (wpa_scan_res_match(wpa_s, 0, neigh_bss,
-						       wpa_s->current_ssid,
-						       1, 0)) {
-					wpa_s->valid_links |= BIT(link_id);
-					os_memcpy(wpa_s->links[link_id].bssid,
-						  ap_info->data + 1, ETH_ALEN);
-					wpa_s->links[link_id].freq =
-						neigh_bss->freq;
-				} else {
-					wpa_printf(MSG_DEBUG,
-						   "MLD: Neighbor doesn't match current SSID - skip link");
-				}
-			} else {
-				wpa_printf(MSG_DEBUG,
-					   "MLD: Neighbor not found in scan");
+	/* Process all Reduced Neighbor Report elements */
+	for (rnr_idx = 1; ; rnr_idx++) {
+		rnr_ie = wpa_bss_get_ie_nth(bss,
+					    WLAN_EID_REDUCED_NEIGHBOR_REPORT,
+					    rnr_idx);
+		if (!rnr_ie) {
+			if (rnr_idx == 0) {
+				wpa_dbg(wpa_s, MSG_DEBUG,
+					"MLD: No RNR element");
+				goto out;
 			}
+			break;
 		}
-
-		rnr_ie_len -= len;
-		pos += len;
+		wpas_process_rnr(wpa_s, rnr_ie + 2, rnr_ie[1]);
 	}
 
 	wpa_printf(MSG_DEBUG, "MLD: valid_links=0x%x", wpa_s->valid_links);
@@ -534,13 +586,21 @@
 			   i, MAC2STR(wpa_s->links[i].bssid));
 	}
 
-	ret = true;
 out:
 	wpabuf_free(mlbuf);
 	return ret;
 }
 
 
+static void wpas_ml_handle_removed_links(struct wpa_supplicant *wpa_s,
+					 struct wpa_bss *bss)
+{
+	u16 removed_links = wpa_bss_parse_reconf_ml_element(wpa_s, bss);
+
+	wpa_s->valid_links &= ~removed_links;
+}
+
+
 static void wpas_sme_ml_auth(struct wpa_supplicant *wpa_s,
 			     union wpa_event_data *data,
 			     int ie_offset)
@@ -639,6 +699,7 @@
 		params.mld = true;
 		params.mld_link_id = wpa_s->mlo_assoc_link_id;
 		params.ap_mld_addr = wpa_s->ap_mld_addr;
+		wpas_ml_handle_removed_links(wpa_s, bss);
 	}
 
 	if (wpa_s->sme.ssid_len != params.ssid_len ||
@@ -731,7 +792,7 @@
 					    bss->bssid,
 					    wpa_s->current_ssid,
 					    try_opportunistic, cache_id,
-					    0) == 0)
+					    0, false) == 0)
 			eapol_sm_notify_pmkid_attempt(wpa_s->eapol);
 		wpa_s->sme.assoc_req_ie_len = sizeof(wpa_s->sme.assoc_req_ie);
 		if (wpa_supplicant_set_suites(wpa_s, bss, ssid,
@@ -920,7 +981,7 @@
 		wpa_drv_get_ext_capa(wpa_s, WPA_IF_STATION);
 
 	ext_capab_len = wpas_build_ext_capab(wpa_s, ext_capab,
-					     sizeof(ext_capab));
+					     sizeof(ext_capab), bss);
 	if (ext_capab_len > 0) {
 		u8 *pos = wpa_s->sme.assoc_req_ie;
 		if (wpa_s->sme.assoc_req_ie_len > 0 && pos[0] == WLAN_EID_RSN)
@@ -1041,7 +1102,7 @@
 				    NULL,
 				    wpa_key_mgmt_sae(wpa_s->key_mgmt) ?
 				    wpa_s->key_mgmt :
-				    (int) WPA_KEY_MGMT_SAE) == 0) {
+				    (int) WPA_KEY_MGMT_SAE, false) == 0) {
 		wpa_dbg(wpa_s, MSG_DEBUG,
 			"PMKSA cache entry found - try to use PMKSA caching instead of new SAE authentication");
 		wpa_sm_set_pmk_from_pmksa(wpa_s->wpa);
@@ -1139,7 +1200,7 @@
 					    bss->bssid,
 					    ssid, 0,
 					    wpa_bss_get_fils_cache_id(bss),
-					    0) == 0)
+					    0, false) == 0)
 			wpa_printf(MSG_DEBUG,
 				   "SME: Try to use FILS with PMKSA caching");
 		resp = fils_build_auth(wpa_s->wpa, ssid->fils_dh_group, md);
@@ -3087,7 +3148,7 @@
 	params.low_priority = 1;
 	wpa_printf(MSG_DEBUG, "SME OBSS: Request an OBSS scan");
 
-	if (wpa_supplicant_trigger_scan(wpa_s, &params))
+	if (wpa_supplicant_trigger_scan(wpa_s, &params, true, false))
 		wpa_printf(MSG_DEBUG, "SME OBSS: Failed to trigger scan");
 	else
 		wpa_s->sme.sched_obss_scan = 1;
diff --git a/wpa_supplicant/wnm_sta.c b/wpa_supplicant/wnm_sta.c
index 72a119d..56183ff 100644
--- a/wpa_supplicant/wnm_sta.c
+++ b/wpa_supplicant/wnm_sta.c
@@ -614,22 +614,6 @@
 		wpa_s->wnm_neighbor_report_elements[i].acceptable = 0;
 }
 
-
-static struct wpa_bss * get_first_acceptable(struct wpa_supplicant *wpa_s)
-{
-	unsigned int i;
-	struct neighbor_report *nei;
-
-	for (i = 0; i < wpa_s->wnm_num_neighbor_report; i++) {
-		nei = &wpa_s->wnm_neighbor_report_elements[i];
-		if (nei->acceptable)
-			return wpa_bss_get_bssid(wpa_s, nei->bssid);
-	}
-
-	return NULL;
-}
-
-
 #ifdef CONFIG_MBO
 static struct wpa_bss *
 get_mbo_transition_candidate(struct wpa_supplicant *wpa_s,
@@ -724,6 +708,29 @@
 #endif /* CONFIG_MBO */
 
 
+static struct wpa_bss * find_better_target(struct wpa_bss *a,
+					   struct wpa_bss *b)
+{
+	if (!a)
+		return b;
+	if (!b)
+		return a;
+
+	if (a->est_throughput > b->est_throughput) {
+		wpa_printf(MSG_DEBUG, "WNM: A is better: " MACSTR
+			   " est-tput: %d  B: " MACSTR " est-tput: %d",
+			   MAC2STR(a->bssid), a->est_throughput,
+			   MAC2STR(b->bssid), b->est_throughput);
+		return a;
+	}
+
+	wpa_printf(MSG_DEBUG, "WNM: B is better, A: " MACSTR
+		   " est-tput: %d  B: " MACSTR " est-tput: %d",
+		   MAC2STR(a->bssid), a->est_throughput,
+		   MAC2STR(b->bssid), b->est_throughput);
+	return b;
+}
+
 static struct wpa_bss *
 compare_scan_neighbor_results(struct wpa_supplicant *wpa_s, os_time_t age_secs,
 			      enum mbo_transition_reject_reason *reason)
@@ -731,6 +738,8 @@
 	u8 i;
 	struct wpa_bss *bss = wpa_s->current_bss;
 	struct wpa_bss *target;
+	struct wpa_bss *best_target = NULL;
+	struct wpa_bss *bss_in_list = NULL;
 
 	if (!bss)
 		return NULL;
@@ -817,25 +826,44 @@
 		}
 
 		nei->acceptable = 1;
+
+		best_target = find_better_target(target, best_target);
+		if (target == bss)
+			bss_in_list = bss;
 	}
 
 #ifdef CONFIG_MBO
 	if (wpa_s->wnm_mbo_trans_reason_present)
 		target = get_mbo_transition_candidate(wpa_s, reason);
 	else
-		target = get_first_acceptable(wpa_s);
+		target = best_target;
 #else /* CONFIG_MBO */
-	target = get_first_acceptable(wpa_s);
+	target = best_target;
 #endif /* CONFIG_MBO */
 
-	if (target) {
-		wpa_printf(MSG_DEBUG,
-			   "WNM: Found an acceptable preferred transition candidate BSS "
-			   MACSTR " (RSSI %d)",
-			   MAC2STR(target->bssid), target->level);
+	if (!target)
+		return NULL;
+
+	wpa_printf(MSG_DEBUG,
+		   "WNM: Found an acceptable preferred transition candidate BSS "
+		   MACSTR " (RSSI %d, tput: %d  bss-tput: %d)",
+		   MAC2STR(target->bssid), target->level,
+		   target->est_throughput, bss->est_throughput);
+
+	if (!bss_in_list)
+		return target;
+
+	if ((!target->est_throughput && !bss_in_list->est_throughput) ||
+	    (target->est_throughput > bss_in_list->est_throughput &&
+	     target->est_throughput - bss_in_list->est_throughput >
+	     bss_in_list->est_throughput >> 4)) {
+		/* It is more than 100/16 percent better, so switch. */
+		return target;
 	}
 
-	return target;
+	wpa_printf(MSG_DEBUG,
+		   "WNM: Stay with our current BSS, not enough change in estimated throughput to switch");
+	return bss_in_list;
 }
 
 
@@ -1207,6 +1235,11 @@
 	const struct neighbor_report *aa = a;
 	const struct neighbor_report *bb = b;
 
+	if (aa->disassoc_imminent && !bb->disassoc_imminent)
+		return 1;
+	if (bb->disassoc_imminent && !aa->disassoc_imminent)
+		return -1;
+
 	if (!aa->preference_present && !bb->preference_present)
 		return 0;
 	if (!aa->preference_present)
@@ -1502,9 +1535,8 @@
 	if (wpa_s->wnm_mode & WNM_BSS_TM_REQ_DISASSOC_IMMINENT) {
 		wpa_msg(wpa_s, MSG_INFO, "WNM: Disassociation Imminent - "
 			"Disassociation Timer %u", wpa_s->wnm_dissoc_timer);
-		if (wpa_s->wnm_dissoc_timer && !wpa_s->scanning) {
-			/* TODO: mark current BSS less preferred for
-			 * selection */
+		if (wpa_s->wnm_dissoc_timer && !wpa_s->scanning &&
+		    (!wpa_s->current_ssid || !wpa_s->current_ssid->bssid_set)) {
 			wpa_printf(MSG_DEBUG, "Trying to find another BSS");
 			wpa_supplicant_req_scan(wpa_s, 0, 0);
 		}
@@ -1538,6 +1570,12 @@
 				rep = &wpa_s->wnm_neighbor_report_elements[
 					wpa_s->wnm_num_neighbor_report];
 				wnm_parse_neighbor_report(wpa_s, pos, len, rep);
+				if ((wpa_s->wnm_mode &
+				     WNM_BSS_TM_REQ_DISASSOC_IMMINENT) &&
+				    os_memcmp(rep->bssid, wpa_s->bssid,
+					      ETH_ALEN) == 0)
+					rep->disassoc_imminent = 1;
+
 				wpa_s->wnm_num_neighbor_report++;
 #ifdef CONFIG_MBO
 				if (wpa_s->wnm_mbo_trans_reason_present &&
@@ -1564,6 +1602,17 @@
 			return;
 		}
 
+		if (wpa_s->current_ssid && wpa_s->current_ssid->bssid_set) {
+			wpa_printf(MSG_DEBUG,
+				   "WNM: Configuration prevents roaming (BSSID set)");
+			wnm_send_bss_transition_mgmt_resp(
+				wpa_s, wpa_s->wnm_dialog_token,
+				WNM_BSS_TM_REJECT_NO_SUITABLE_CANDIDATES,
+				MBO_TRANSITION_REJECT_REASON_UNSPECIFIED, 0,
+				NULL);
+			return;
+		}
+
 		wnm_sort_cand_list(wpa_s);
 		wnm_dump_cand_list(wpa_s);
 		valid_ms = valid_int * beacon_int * 128 / 125;
diff --git a/wpa_supplicant/wnm_sta.h b/wpa_supplicant/wnm_sta.h
index 29625f8..e4957e4 100644
--- a/wpa_supplicant/wnm_sta.h
+++ b/wpa_supplicant/wnm_sta.h
@@ -37,6 +37,7 @@
 	u32 distance; /* valid if bearing_present=1 */
 	u64 bss_term_tsf; /* valid if bss_term_present=1 */
 	u16 bss_term_dur; /* valid if bss_term_present=1 */
+	unsigned int disassoc_imminent:1;
 	unsigned int preference_present:1;
 	unsigned int tsf_present:1;
 	unsigned int country_present:1;
diff --git a/wpa_supplicant/wpa_cli.c b/wpa_supplicant/wpa_cli.c
index effc7b3..65078ed 100644
--- a/wpa_supplicant/wpa_cli.c
+++ b/wpa_supplicant/wpa_cli.c
@@ -551,6 +551,13 @@
 }
 
 
+static int wpa_cli_cmd_driver_flags2(struct wpa_ctrl *ctrl, int argc,
+				     char *argv[])
+{
+	return wpa_ctrl_command(ctrl, "DRIVER_FLAGS2");
+}
+
+
 static int wpa_cli_cmd_get(struct wpa_ctrl *ctrl, int argc, char *argv[])
 {
 	return wpa_cli_cmd(ctrl, "GET", 1, argc, argv);
@@ -2091,10 +2098,19 @@
 
 
 #ifdef CONFIG_TESTING_OPTIONS
+
 static int wpa_cli_cmd_drop_sa(struct wpa_ctrl *ctrl, int argc, char *argv[])
 {
 	return wpa_ctrl_command(ctrl, "DROP_SA");
 }
+
+
+static int wpa_cli_cmd_ml_probe_req(struct wpa_ctrl *ctrl,
+				    int argc, char *argv[])
+{
+	return wpa_cli_cmd(ctrl, "ML_PROBE_REQ", 2, argc, argv);
+}
+
 #endif /* CONFIG_TESTING_OPTIONS */
 
 
@@ -3375,6 +3391,9 @@
 	{ "driver_flags", wpa_cli_cmd_driver_flags, NULL,
 	  cli_cmd_flag_none,
 	  "= list driver flags" },
+	{ "driver_flags2", wpa_cli_cmd_driver_flags2, NULL,
+	  cli_cmd_flag_none,
+	  "= list driver flags2" },
 	{ "logon", wpa_cli_cmd_logon, NULL,
 	  cli_cmd_flag_none,
 	  "= IEEE 802.1X EAPOL state machine logon" },
@@ -3673,6 +3692,8 @@
 #ifdef CONFIG_TESTING_OPTIONS
 	{ "drop_sa", wpa_cli_cmd_drop_sa, NULL, cli_cmd_flag_none,
 	  "= drop SA without deauth/disassoc (test command)" },
+	{ "ml_probe_req", wpa_cli_cmd_ml_probe_req, NULL, cli_cmd_flag_none,
+	  "= send Multi-Link Probe request <bssid=addr> <mld_id=id> [link_id=id] (test command)" },
 #endif /* CONFIG_TESTING_OPTIONS */
 	{ "roam", wpa_cli_cmd_roam, wpa_cli_complete_bss,
 	  cli_cmd_flag_none,
diff --git a/wpa_supplicant/wpa_supplicant.c b/wpa_supplicant/wpa_supplicant.c
index a3645d1..6e34e87 100644
--- a/wpa_supplicant/wpa_supplicant.c
+++ b/wpa_supplicant/wpa_supplicant.c
@@ -751,6 +751,11 @@
 #endif /* CONFIG_PASN */
 	wpas_scs_deinit(wpa_s);
 	wpas_dscp_deinit(wpa_s);
+
+#ifdef CONFIG_OWE
+	os_free(wpa_s->owe_trans_scan_freq);
+	wpa_s->owe_trans_scan_freq = NULL;
+#endif /* CONFIG_OWE */
 }
 
 
@@ -1766,7 +1771,8 @@
 
 	sel = ie.key_mgmt & ssid->key_mgmt;
 #ifdef CONFIG_SAE
-	if (!(wpa_s->drv_flags & WPA_DRIVER_FLAGS_SAE) ||
+	if ((!(wpa_s->drv_flags & WPA_DRIVER_FLAGS_SAE) &&
+	     !(wpa_s->drv_flags2 & WPA_DRIVER_FLAGS2_SAE_OFFLOAD_STA)) ||
 	    wpas_is_sae_avoided(wpa_s, ssid, &ie))
 		sel &= ~(WPA_KEY_MGMT_SAE | WPA_KEY_MGMT_SAE_EXT_KEY |
 			 WPA_KEY_MGMT_FT_SAE | WPA_KEY_MGMT_FT_SAE_EXT_KEY);
@@ -1809,6 +1815,12 @@
 		wpa_dbg(wpa_s, MSG_DEBUG,
 			"WPA: using KEY_MGMT 802.1X with Suite B");
 #endif /* CONFIG_SUITEB */
+#ifdef CONFIG_SHA384
+	} else if (sel & WPA_KEY_MGMT_IEEE8021X_SHA384) {
+		wpa_s->key_mgmt = WPA_KEY_MGMT_IEEE8021X_SHA384;
+		wpa_dbg(wpa_s, MSG_DEBUG,
+			"WPA: using KEY_MGMT 802.1X with SHA384");
+#endif /* CONFIG_SHA384 */
 #ifdef CONFIG_FILS
 #ifdef CONFIG_IEEE80211R
 	} else if (sel & WPA_KEY_MGMT_FT_FILS_SHA384) {
@@ -2094,7 +2106,8 @@
 }
 
 
-static void wpas_ext_capab_byte(struct wpa_supplicant *wpa_s, u8 *pos, int idx)
+static void wpas_ext_capab_byte(struct wpa_supplicant *wpa_s, u8 *pos, int idx,
+				struct wpa_bss *bss)
 {
 	bool scs = true, mscs = true;
 
@@ -2145,6 +2158,13 @@
 		if (wpa_s->disable_scs_support)
 			scs = false;
 #endif /* CONFIG_TESTING_OPTIONS */
+		if (bss && !wpa_bss_ext_capab(bss, WLAN_EXT_CAPAB_SCS)) {
+			/* Drop own SCS capability indication since the AP does
+			 * not support it. This is needed to avoid
+			 * interoperability issues with APs that get confused
+			 * with Extended Capabilities element. */
+			scs = false;
+		}
 		if (scs)
 			*pos |= 0x40; /* Bit 54 - SCS */
 		break;
@@ -2167,6 +2187,13 @@
 		if (wpa_s->disable_mscs_support)
 			mscs = false;
 #endif /* CONFIG_TESTING_OPTIONS */
+		if (bss && !wpa_bss_ext_capab(bss, WLAN_EXT_CAPAB_MSCS)) {
+			/* Drop own MSCS capability indication since the AP does
+			 * not support it. This is needed to avoid
+			 * interoperability issues with APs that get confused
+			 * with Extended Capabilities element. */
+			mscs = false;
+		}
 		if (mscs)
 			*pos |= 0x20; /* Bit 85 - Mirrored SCS */
 		break;
@@ -2174,7 +2201,8 @@
 }
 
 
-int wpas_build_ext_capab(struct wpa_supplicant *wpa_s, u8 *buf, size_t buflen)
+int wpas_build_ext_capab(struct wpa_supplicant *wpa_s, u8 *buf,
+			  size_t buflen, struct wpa_bss *bss)
 {
 	u8 *pos = buf;
 	u8 len = 11, i;
@@ -2190,7 +2218,7 @@
 	*pos++ = WLAN_EID_EXT_CAPAB;
 	*pos++ = len;
 	for (i = 0; i < len; i++, pos++) {
-		wpas_ext_capab_byte(wpa_s, pos, i);
+		wpas_ext_capab_byte(wpa_s, pos, i, bss);
 
 		if (i < wpa_s->extended_capa_len) {
 			*pos &= ~wpa_s->extended_capa_mask[i];
@@ -2797,8 +2825,8 @@
 	int i, res;
 	unsigned int j;
 	static const int ht40plus[] = {
-		36, 44, 52, 60, 100, 108, 116, 124, 132, 149, 157, 165, 173,
-		184, 192
+		36, 44, 52, 60, 100, 108, 116, 124, 132, 140,
+		149, 157, 165, 173, 184, 192
 	};
 	int ht40 = -1;
 
@@ -3190,6 +3218,29 @@
 #endif /* CONFIG_FILS */
 
 
+bool wpa_is_non_eht_scs_traffic_desc_supported(struct wpa_bss *bss)
+{
+	const u8 *wfa_capa;
+
+	if (!bss)
+		return false;
+
+	/* Get WFA capability from Beacon or Probe Response frame elements */
+	wfa_capa = wpa_bss_get_vendor_ie(bss, WFA_CAPA_IE_VENDOR_TYPE);
+	if (!wfa_capa)
+		wfa_capa = wpa_bss_get_vendor_ie_beacon(
+			bss, WFA_CAPA_IE_VENDOR_TYPE);
+
+	if (!wfa_capa || wfa_capa[1] < 6 || wfa_capa[6] < 1 ||
+	    !(wfa_capa[7] & WFA_CAPA_QM_NON_EHT_SCS_TRAFFIC_DESC)) {
+		/* AP does not enable QM non EHT traffic description policy */
+		return false;
+	}
+
+	return true;
+}
+
+
 static int wpas_populate_wfa_capa(struct wpa_supplicant *wpa_s,
 				  struct wpa_bss *bss,
 				  u8 *wpa_ie, size_t wpa_ie_len,
@@ -3203,6 +3254,9 @@
 	if (wpa_s->enable_dscp_policy_capa)
 		wfa_capa[0] |= WFA_CAPA_QM_DSCP_POLICY;
 
+	if (wpa_is_non_eht_scs_traffic_desc_supported(bss))
+		wfa_capa[0] |= WFA_CAPA_QM_NON_EHT_SCS_TRAFFIC_DESC;
+
 	if (!wfa_capa[0])
 		return wpa_ie_len;
 
@@ -3301,7 +3355,7 @@
 #endif /* CONFIG_FILS */
 		if (pmksa_cache_set_current(wpa_s->wpa, NULL, addr,
 					    ssid, try_opportunistic,
-					    cache_id, 0) == 0) {
+					    cache_id, 0, false) == 0) {
 			eapol_sm_notify_pmkid_attempt(wpa_s->eapol);
 #if defined(CONFIG_SAE) || defined(CONFIG_FILS)
 			pmksa_cached = 1;
@@ -3486,7 +3540,7 @@
 		u8 ext_capab[18];
 		int ext_capab_len;
 		ext_capab_len = wpas_build_ext_capab(wpa_s, ext_capab,
-						     sizeof(ext_capab));
+						     sizeof(ext_capab), bss);
 		if (ext_capab_len > 0 &&
 		    wpa_ie_len + ext_capab_len <= max_wpa_ie_len) {
 			u8 *pos = wpa_ie;
@@ -3582,7 +3636,8 @@
 	} else
 #endif /* CONFIG_TESTING_OPTIONS */
 	if (algs == WPA_AUTH_ALG_OPEN &&
-	    ssid->key_mgmt == WPA_KEY_MGMT_OWE) {
+	    ssid->key_mgmt == WPA_KEY_MGMT_OWE &&
+	    !(wpa_s->drv_flags2 & WPA_DRIVER_FLAGS2_OWE_OFFLOAD_STA)) {
 		struct wpabuf *owe_ie;
 		u16 group;
 
@@ -4265,14 +4320,17 @@
 	    (params.key_mgmt_suite == WPA_KEY_MGMT_IEEE8021X ||
 	     params.key_mgmt_suite == WPA_KEY_MGMT_IEEE8021X_SHA256 ||
 	     params.key_mgmt_suite == WPA_KEY_MGMT_IEEE8021X_SUITE_B ||
-	     params.key_mgmt_suite == WPA_KEY_MGMT_IEEE8021X_SUITE_B_192))
+	     params.key_mgmt_suite == WPA_KEY_MGMT_IEEE8021X_SUITE_B_192 ||
+	     params.key_mgmt_suite == WPA_KEY_MGMT_IEEE8021X_SHA384))
 		params.req_handshake_offload = 1;
 
 	if (wpa_s->conf->key_mgmt_offload) {
 		if (params.key_mgmt_suite == WPA_KEY_MGMT_IEEE8021X ||
 		    params.key_mgmt_suite == WPA_KEY_MGMT_IEEE8021X_SHA256 ||
 		    params.key_mgmt_suite == WPA_KEY_MGMT_IEEE8021X_SUITE_B ||
-		    params.key_mgmt_suite == WPA_KEY_MGMT_IEEE8021X_SUITE_B_192)
+		    params.key_mgmt_suite ==
+		    WPA_KEY_MGMT_IEEE8021X_SUITE_B_192 ||
+		    params.key_mgmt_suite == WPA_KEY_MGMT_IEEE8021X_SHA384)
 			params.req_key_mgmt_offload =
 				ssid->proactive_key_caching < 0 ?
 				wpa_s->conf->okc : ssid->proactive_key_caching;
@@ -4291,13 +4349,25 @@
 			params.psk = psk;
 	}
 
+	if ((wpa_s->drv_flags2 & WPA_DRIVER_FLAGS2_SAE_OFFLOAD_STA) &&
+	    wpa_key_mgmt_sae(params.key_mgmt_suite)) {
+		params.auth_alg = WPA_AUTH_ALG_SAE;
+		if (ssid->sae_password) {
+			params.sae_password = ssid->sae_password;
+			params.sae_password_id = ssid->sae_password_id;
+		} else if (ssid->passphrase) {
+			params.passphrase = ssid->passphrase;
+		}
+	}
+
 	params.drop_unencrypted = use_crypt;
 
 	params.mgmt_frame_protection = wpas_get_ssid_pmf(wpa_s, ssid);
 	if (params.mgmt_frame_protection != NO_MGMT_FRAME_PROTECTION && bss) {
 		const u8 *rsn = wpa_bss_get_ie(bss, WLAN_EID_RSN);
 		struct wpa_ie_data ie;
-		if (rsn && wpa_parse_wpa_ie(rsn, 2 + rsn[1], &ie) == 0 &&
+		if (!wpas_driver_bss_selection(wpa_s) && rsn &&
+		    wpa_parse_wpa_ie(rsn, 2 + rsn[1], &ie) == 0 &&
 		    ie.capabilities &
 		    (WPA_CAPABILITY_MFPC | WPA_CAPABILITY_MFPR)) {
 			wpa_dbg(wpa_s, MSG_DEBUG, "WPA: Selected AP supports "
@@ -4462,6 +4532,8 @@
 {
 	struct wpa_ssid *old_ssid;
 
+	wpa_s->ml_connect_probe_ssid = NULL;
+	wpa_s->ml_connect_probe_bss = NULL;
 	wpas_connect_work_done(wpa_s);
 	wpa_clear_keys(wpa_s, addr);
 	old_ssid = wpa_s->current_ssid;
@@ -4678,6 +4750,11 @@
 {
 	struct wpa_ssid *ssid;
 
+	if (wpa_s->drv_flags2 &
+	    (WPA_DRIVER_FLAGS2_SAE_OFFLOAD_STA |
+	     WPA_DRIVER_FLAGS2_OWE_OFFLOAD_STA))
+		wpa_drv_flush_pmkid(wpa_s);
+
 	if (wpa_s->sched_scanning)
 		wpa_supplicant_cancel_sched_scan(wpa_s);
 
@@ -4996,10 +5073,14 @@
 		}
 	}
 
+#ifndef CONFIG_PKCS11_ENGINE_PATH
 	os_free(wpa_s->conf->pkcs11_engine_path);
-	os_free(wpa_s->conf->pkcs11_module_path);
 	wpa_s->conf->pkcs11_engine_path = pkcs11_engine_path_copy;
+#endif /* CONFIG_PKCS11_ENGINE_PATH */
+#ifndef CONFIG_PKCS11_MODULE_PATH
+	os_free(wpa_s->conf->pkcs11_module_path);
 	wpa_s->conf->pkcs11_module_path = pkcs11_module_path_copy;
+#endif /* CONFIG_PKCS11_MODULE_PATH */
 
 	wpa_sm_set_eapol(wpa_s->wpa, NULL);
 	eapol_sm_deinit(wpa_s->eapol);
@@ -5256,8 +5337,9 @@
 
 #ifdef CONFIG_OWE
 		if (!wpas_network_disabled(wpa_s, entry) &&
-		    owe_trans_ssid_match(wpa_s, bssid, entry->ssid,
-		    entry->ssid_len) &&
+		    (entry->ssid &&
+		     owe_trans_ssid_match(wpa_s, bssid, entry->ssid,
+					  entry->ssid_len)) &&
 		    (!entry->bssid_set ||
 		     os_memcmp(bssid, entry->bssid, ETH_ALEN) == 0))
 			return entry;
@@ -5774,6 +5856,7 @@
 	dl_list_init(&wpa_s->drv_signal_override);
 #endif /* CONFIG_TESTING_OPTIONS */
 	dl_list_init(&wpa_s->active_scs_ids);
+	wpa_s->ml_probe_mld_id = -1;
 
 	return wpa_s;
 }
@@ -7121,6 +7204,7 @@
 				 wpa_s->hw_capab == CAPAB_NO_HT_VHT)
 				wpa_s->hw_capab = CAPAB_HT;
 		}
+		wpa_s->support_6ghz = wpas_is_6ghz_supported(wpa_s, false);
 	}
 
 	capa_res = wpa_drv_get_capa(wpa_s, &capa);
@@ -7167,6 +7251,9 @@
 #ifdef CONFIG_PASN
 	wpa_pasn_sm_set_caps(wpa_s->wpa, wpa_s->drv_flags2);
 #endif /* CONFIG_PASN */
+	wpa_sm_set_driver_bss_selection(wpa_s->wpa,
+					!!(wpa_s->drv_flags &
+					   WPA_DRIVER_FLAGS_BSS_SELECTION));
 	if (wpa_s->max_remain_on_chan == 0)
 		wpa_s->max_remain_on_chan = 1000;
 
@@ -8781,6 +8868,7 @@
 	wpa_s->disconnected = 1;
 	wpa_supplicant_cancel_sched_scan(wpa_s);
 	wpa_supplicant_cancel_scan(wpa_s);
+	wpas_abort_ongoing_scan(wpa_s);
 	wpa_supplicant_deauthenticate(wpa_s, WLAN_REASON_DEAUTH_LEAVING);
 	eloop_cancel_timeout(wpas_network_reenabled, wpa_s, NULL);
 	radio_remove_works(wpa_s, "connect", 0);
@@ -9310,3 +9398,25 @@
 	return wpa_s->driver->send_action(wpa_s->drv_priv, freq, wait, dst, src,
 					  bssid, data, data_len, no_cck);
 }
+
+
+bool wpas_is_6ghz_supported(struct wpa_supplicant *wpa_s, bool only_enabled)
+{
+	struct hostapd_channel_data *chnl;
+	int i, j;
+
+	for (i = 0; i < wpa_s->hw.num_modes; i++) {
+		if (wpa_s->hw.modes[i].mode == HOSTAPD_MODE_IEEE80211A) {
+			chnl = wpa_s->hw.modes[i].channels;
+			for (j = 0; j < wpa_s->hw.modes[i].num_channels; j++) {
+				if (only_enabled &&
+				    (chnl[j].flag & HOSTAPD_CHAN_DISABLED))
+					continue;
+				if (is_6ghz_freq(chnl[j].freq))
+					return true;
+			}
+		}
+	}
+
+	return false;
+}
diff --git a/wpa_supplicant/wpa_supplicant.conf b/wpa_supplicant/wpa_supplicant.conf
index a8abf9a..f6e4f83 100644
--- a/wpa_supplicant/wpa_supplicant.conf
+++ b/wpa_supplicant/wpa_supplicant.conf
@@ -503,12 +503,17 @@
 # 0 = use permanent MAC address
 # 1 = use random MAC address for each ESS connection
 # 2 = like 1, but maintain OUI (with local admin bit set)
+# 3 = use dedicated/pregenerated MAC address (see mac_value)
 #
 # By default, permanent MAC address is used unless policy is changed by
 # the per-network mac_addr parameter. Global mac_addr=1 can be used to
 # change this default behavior.
 #mac_addr=0
 
+# Local MAC address to use whenever connecting with this network profile
+# This is used with mac_addr=3.
+#mac_value=02:12:34:56:78:9a
+
 # Lifetime of random MAC address in seconds (default: 60)
 #rand_addr_lifetime=60
 
diff --git a/wpa_supplicant/wpa_supplicant_i.h b/wpa_supplicant/wpa_supplicant_i.h
index 01b3148..0890110 100644
--- a/wpa_supplicant/wpa_supplicant_i.h
+++ b/wpa_supplicant/wpa_supplicant_i.h
@@ -598,6 +598,40 @@
 };
 
 
+struct qos_characteristics {
+	bool available;
+
+	/* Control Info Direction */
+	u8 direction;
+	/* Presence Bitmap Of Additional Parameters */
+	u16 mask;
+	/* Minimum Service Interval */
+	u32 min_si;
+	/* Maximum Service Interval */
+	u32 max_si;
+	/* Minimum Data Rate */
+	u32 min_data_rate;
+	/* Delay Bound */
+	u32 delay_bound;
+	/* Maximum MSDU Size */
+	u16 max_msdu_size;
+	/* Service Start Time */
+	u32 service_start_time;
+	/* Service Start Time LinkID */
+	u8 service_start_time_link_id;
+	/* Mean Data Rate */
+	u32 mean_data_rate;
+	/* Delayed Bounded Burst Size */
+	u32 burst_size;
+	/* MSDU Lifetime */
+	u16 msdu_lifetime;
+	/* MSDU Delivery Info */
+	u8 msdu_delivery_info;
+	/* Medium Time */
+	u16 medium_time;
+};
+
+
 struct scs_desc_elem {
 	u8 scs_id;
 	enum scs_request_type request_type;
@@ -606,6 +640,7 @@
 	struct tclas_element *tclas_elems;
 	unsigned int num_tclas_elem;
 	u8 tclas_processing;
+	struct qos_characteristics qos_char_elem;
 };
 
 
@@ -886,6 +921,10 @@
 	unsigned int suitable_network;
 	unsigned int no_suitable_network;
 
+	u8 ml_probe_bssid[ETH_ALEN];
+	int ml_probe_mld_id;
+	u16 ml_probe_links;
+
 	u64 drv_flags;
 	u64 drv_flags2;
 	unsigned int drv_enc;
@@ -947,6 +986,7 @@
 	unsigned int connection_channel_bandwidth:5;
 	unsigned int disable_mbo_oce:1;
 	unsigned int connection_11b_only:1;
+	unsigned int ap_t2lm_negotiation_support:1;
 
 	struct os_reltime last_mac_addr_change;
 	enum wpas_mac_addr_style last_mac_addr_style;
@@ -1553,8 +1593,22 @@
 	unsigned int enable_dscp_policy_capa:1;
 	unsigned int connection_dscp:1;
 	unsigned int wait_for_dscp_req:1;
+	bool is_6ghz_enabled;
+	bool crossed_6ghz_dom;
+	bool last_scan_all_chan;
+	bool last_scan_non_coloc_6ghz;
+	bool support_6ghz;
 
 	struct wpa_signal_info last_signal_info;
+
+	struct wpa_ssid *ml_connect_probe_ssid;
+	struct wpa_bss *ml_connect_probe_bss;
+
+#ifdef CONFIG_OWE
+	/* An array of frequencies to scan for OWE transition mode BSSs when
+	 * owe_transition_search == 1 */
+	int *owe_trans_scan_freq;
+#endif /* CONFIG_OWE */
 };
 
 
@@ -1675,8 +1729,10 @@
 		    size_t ssid_len);
 void wpas_request_connection(struct wpa_supplicant *wpa_s);
 void wpas_request_disconnection(struct wpa_supplicant *wpa_s);
-int wpas_build_ext_capab(struct wpa_supplicant *wpa_s, u8 *buf, size_t buflen);
-int wpas_update_random_addr(struct wpa_supplicant *wpa_s, int style,
+int wpas_build_ext_capab(struct wpa_supplicant *wpa_s, u8 *buf, size_t buflen,
+			 struct wpa_bss *bss);
+int wpas_update_random_addr(struct wpa_supplicant *wpa_s,
+			    enum wpas_mac_addr_style style,
 			    struct wpa_ssid *ssid);
 int wpas_update_random_addr_disassoc(struct wpa_supplicant *wpa_s);
 void add_freq(int *freqs, int *num_freqs, int freq);
@@ -1956,5 +2012,8 @@
 void wpas_pasn_auth_trigger(struct wpa_supplicant *wpa_s,
 			    struct pasn_auth *pasn_auth);
 void wpas_pasn_auth_work_done(struct wpa_supplicant *wpa_s, int status);
+bool wpas_is_6ghz_supported(struct wpa_supplicant *wpa_s, bool only_enabled);
+
+bool wpa_is_non_eht_scs_traffic_desc_supported(struct wpa_bss *bss);
 
 #endif /* WPA_SUPPLICANT_I_H */
diff --git a/wpa_supplicant/wpas_glue.c b/wpa_supplicant/wpas_glue.c
index 0047531..9804b91 100644
--- a/wpa_supplicant/wpas_glue.c
+++ b/wpa_supplicant/wpas_glue.c
@@ -785,12 +785,12 @@
 					 u8 action_code, u8 dialog_token,
 					 u16 status_code, u32 peer_capab,
 					 int initiator, const u8 *buf,
-					 size_t len)
+					 size_t len, int link_id)
 {
 	struct wpa_supplicant *wpa_s = ctx;
 	return wpa_drv_send_tdls_mgmt(wpa_s, dst, action_code, dialog_token,
 				      status_code, peer_capab, initiator, buf,
-				      len);
+				      len, link_id);
 }
 
 
@@ -811,7 +811,9 @@
 	const struct ieee80211_he_6ghz_band_cap *he_6ghz_he_capab,
 	u8 qosinfo, int wmm, const u8 *ext_capab, size_t ext_capab_len,
 	const u8 *supp_channels, size_t supp_channels_len,
-	const u8 *supp_oper_classes, size_t supp_oper_classes_len)
+	const u8 *supp_oper_classes, size_t supp_oper_classes_len,
+	const struct ieee80211_eht_capabilities *eht_capab,
+	size_t eht_capab_len, int mld_link_id)
 {
 	struct wpa_supplicant *wpa_s = ctx;
 	struct hostapd_sta_add_params params;
@@ -846,6 +848,9 @@
 	params.supp_channels_len = supp_channels_len;
 	params.supp_oper_classes = supp_oper_classes;
 	params.supp_oper_classes_len = supp_oper_classes_len;
+	params.eht_capab = eht_capab;
+	params.eht_capab_len = eht_capab_len;
+	params.mld_link_id = mld_link_id;
 
 	return wpa_drv_sta_add(wpa_s, &params);
 }
@@ -1224,9 +1229,15 @@
 	ctx->get_config_blob = wpa_supplicant_get_config_blob;
 #endif /* CONFIG_NO_CONFIG_BLOBS */
 	ctx->aborted_cached = wpa_supplicant_aborted_cached;
+#ifndef CONFIG_OPENSC_ENGINE_PATH
 	ctx->opensc_engine_path = wpa_s->conf->opensc_engine_path;
+#endif /* CONFIG_OPENSC_ENGINE_PATH */
+#ifndef CONFIG_PKCS11_ENGINE_PATH
 	ctx->pkcs11_engine_path = wpa_s->conf->pkcs11_engine_path;
+#endif /* CONFIG_PKCS11_ENGINE_PATH */
+#ifndef CONFIG_PKCS11_MODULE_PATH
 	ctx->pkcs11_module_path = wpa_s->conf->pkcs11_module_path;
+#endif /* CONFIG_PKCS11_MODULE_PATH */
 	ctx->openssl_ciphers = wpa_s->conf->openssl_ciphers;
 	ctx->wps = wpa_s->wps;
 	ctx->eap_param_needed = wpa_supplicant_eap_param_needed;
@@ -1375,7 +1386,8 @@
 	    wpa_key_mgmt_wpa_ieee8021x(wpa_s->key_mgmt) &&
 	    (ssid->key_mgmt & (WPA_KEY_MGMT_IEEE8021X |
 			       WPA_KEY_MGMT_FT_IEEE8021X |
-			       WPA_KEY_MGMT_IEEE8021X_SHA256)) &&
+			       WPA_KEY_MGMT_IEEE8021X_SHA256 |
+			       WPA_KEY_MGMT_IEEE8021X_SHA384)) &&
 	    (ssid->ieee80211w != MGMT_FRAME_PROTECTION_REQUIRED ||
 	     (ssid->group_cipher & WPA_CIPHER_TKIP))) {
 		disable_wpa_wpa2(ssid);