[RFC 30/30] nl80211: Support SMD commands and events

Ilan Peer ilan.peer at intel.com
Wed Oct 21 06:43:48 PDT 2026


Signed-off-by: Ilan Peer <ilan.peer at intel.com>
---
 src/drivers/driver_nl80211.c       | 181 +++++++++++++++++++++++++++
 src/drivers/driver_nl80211_event.c | 188 +++++++++++++++++++++++++++++
 2 files changed, 369 insertions(+)

diff --git a/src/drivers/driver_nl80211.c b/src/drivers/driver_nl80211.c
index bc873242b332..7e990eee9a6d 100644
--- a/src/drivers/driver_nl80211.c
+++ b/src/drivers/driver_nl80211.c
@@ -15793,6 +15793,184 @@ error:
 #endif /* CONFIG_IEEE80211BE */
 
 
+static int nl80211_st_prepare(void *priv, struct st_prepare_params *params)
+{
+	struct i802_bss *bss = priv;
+	struct wpa_driver_nl80211_data *drv = bss->drv;
+	struct nl_msg *msg;
+	struct nlattr *links, *attr;
+	int ret;
+	u8 link_id;
+
+	if (!drv->sta_mlo_info.valid_links)
+		return -1;
+
+	wpa_printf(MSG_DEBUG,
+		   "nl80211: ST Prepare (target=" MACSTR " valid_links=0x%x)",
+		   MAC2STR(params->target_ap_mld_addr),
+		   params->valid_links);
+
+	msg = nl80211_bss_msg(bss, 0, NL80211_CMD_PREPARE_SMD_TRANSITION);
+	if (!msg)
+		return -1;
+
+	if (nla_put(msg, NL80211_ATTR_MLD_ADDR, ETH_ALEN,
+		    params->target_ap_mld_addr))
+		goto error;
+
+	if (params->ies &&
+	    nla_put(msg, NL80211_ATTR_IE, params->ies_len, params->ies))
+		goto error;
+
+	if (params->ext_mld_capa_ops &&
+	    nla_put_u16(msg, NL80211_ATTR_EXT_MLD_CAPA_AND_OPS,
+			params->ext_mld_capa_ops))
+		goto error;
+
+	links = nla_nest_start(msg, NL80211_ATTR_MLO_LINKS);
+	if (!links)
+		goto error;
+
+	for_each_link(params->valid_links, link_id) {
+		attr = nla_nest_start(msg, 0);
+		if (!attr)
+			goto error;
+
+		if (nla_put_u8(msg, NL80211_ATTR_MLO_LINK_ID, link_id))
+			goto error;
+
+		if (params->mld_links[link_id].bssid &&
+		    nla_put(msg, NL80211_ATTR_MAC, ETH_ALEN,
+			    params->mld_links[link_id].bssid))
+			goto error;
+
+		if (params->mld_links[link_id].freq &&
+		    nla_put_u32(msg, NL80211_ATTR_WIPHY_FREQ,
+				params->mld_links[link_id].freq))
+			goto error;
+
+		if (params->mld_links[link_id].ies &&
+		    nla_put(msg, NL80211_ATTR_IE,
+			    params->mld_links[link_id].ies_len,
+			    params->mld_links[link_id].ies))
+			goto error;
+
+		if (params->mld_links[link_id].disabled &&
+		    nla_put_flag(msg, NL80211_ATTR_MLO_LINK_DISABLED))
+			goto error;
+
+		nla_nest_end(msg, attr);
+	}
+
+	nla_nest_end(msg, links);
+
+	ret = send_and_recv(drv, bss->nl_connect, msg, NULL, NULL, NULL, NULL,
+			    NULL);
+	if (ret) {
+		wpa_printf(MSG_INFO,
+			   "nl80211: ST Prepare failed err=%d (%s)",
+			   ret, strerror(-ret));
+		return ret;
+	}
+
+	return 0;
+
+error:
+	nlmsg_free(msg);
+	wpa_printf(MSG_ERROR, "nl80211: Could not build ST Prepare message");
+	return -1;
+}
+
+
+static int nl80211_st_execute(void *priv, struct st_execute_params *params)
+{
+	struct i802_bss *bss = priv;
+	struct wpa_driver_nl80211_data *drv = bss->drv;
+	struct nl_msg *msg;
+	int ret;
+
+	if (!drv->sta_mlo_info.valid_links)
+		return -1;
+
+	wpa_printf(MSG_DEBUG, "nl80211: ST Execute (target=" MACSTR ")",
+		   MAC2STR(params->target_ap_mld_addr));
+
+	msg = nl80211_bss_msg(bss, 0, NL80211_CMD_EXECUTE_SMD_TRANSITION);
+	if (!msg)
+		return -1;
+
+	if (nla_put(msg, NL80211_ATTR_MLD_ADDR, ETH_ALEN,
+		    params->target_ap_mld_addr))
+		goto error;
+
+	if (params->ies &&
+	    nla_put(msg, NL80211_ATTR_IE, params->ies_len, params->ies))
+		goto error;
+
+	if (params->drain_tids &&
+	    nla_put_u16(msg, NL80211_ATTR_TID_BITMAP, params->drain_tids))
+		goto error;
+
+	ret = send_and_recv(drv, bss->nl_connect, msg, NULL, NULL, NULL, NULL,
+			    NULL);
+	if (ret) {
+		wpa_printf(MSG_INFO,
+			   "nl80211: ST Execute failed err=%d (%s)",
+			   ret, strerror(-ret));
+		return ret;
+	}
+
+	return 0;
+
+error:
+	nlmsg_free(msg);
+	wpa_printf(MSG_ERROR, "nl80211: Could not build ST Execute message");
+	return -1;
+}
+
+
+static int nl80211_st_abandon(void *priv, const u8 *addr, bool local_only)
+{
+	struct i802_bss *bss = priv;
+	struct wpa_driver_nl80211_data *drv = bss->drv;
+	struct nl_msg *msg;
+	int ret;
+
+	if (!drv->sta_mlo_info.valid_links)
+		return -1;
+
+	wpa_printf(MSG_DEBUG, "nl80211: ST Abandon (target=" MACSTR ")",
+		   MAC2STR(addr));
+
+	msg = nl80211_bss_msg(bss, 0, NL80211_CMD_ABANDON_SMD_TRANSITION);
+	if (!msg)
+		return -1;
+
+	if (nla_put(msg, NL80211_ATTR_MLD_ADDR, ETH_ALEN, addr))
+		goto error;
+
+	if (local_only &&
+	    nla_put_flag(msg, NL80211_ATTR_LOCAL_STATE_CHANGE))
+		goto error;
+
+	ret = send_and_recv(drv, bss->nl_connect, msg, NULL, NULL, NULL, NULL,
+			    NULL);
+	if (ret) {
+		wpa_printf(MSG_INFO,
+			   "nl80211: ST Abandon failed err=%d (%s)",
+			   ret, strerror(-ret));
+		return ret;
+	}
+
+	return 0;
+
+error:
+	nlmsg_free(msg);
+	wpa_printf(MSG_ERROR, "nl80211: Could not build ST Abandon message");
+	return -1;
+}
+
+
 #ifdef CONFIG_TESTING_OPTIONS
 
 static int testing_nl80211_register_frame(void *priv, u16 type,
@@ -16989,6 +17167,9 @@ const struct wpa_driver_ops wpa_driver_nl80211_ops = {
 	.radio_disable = testing_nl80211_radio_disable,
 #endif /* CONFIG_TESTING_OPTIONS */
 	.get_multi_hw_info = wpa_driver_get_multi_hw_info,
+	.st_prepare = nl80211_st_prepare,
+	.st_execute = nl80211_st_execute,
+	.st_abandon = nl80211_st_abandon,
 #ifdef CONFIG_NAN
 	.nan_start = wpa_driver_nl80211_nan_start,
 	.nan_stop = wpa_driver_nl80211_nan_stop,
diff --git a/src/drivers/driver_nl80211_event.c b/src/drivers/driver_nl80211_event.c
index 86f7372b59cf..8136e0370b5d 100644
--- a/src/drivers/driver_nl80211_event.c
+++ b/src/drivers/driver_nl80211_event.c
@@ -1828,6 +1828,185 @@ static void mlme_event_link_addition(struct i802_bss *bss, const u8 *frame,
 }
 
 
+static void mlme_event_st_prep_status(struct wpa_driver_nl80211_data *drv,
+				      struct nlattr **tb)
+{
+	union wpa_event_data event;
+
+	os_memset(&event, 0, sizeof(event));
+
+	if (tb[NL80211_ATTR_MLD_ADDR])
+		event.st_prep_status_info.ap_mld_addr =
+			nla_data(tb[NL80211_ATTR_MLD_ADDR]);
+
+	if (tb[NL80211_ATTR_REQ_IE]) {
+		event.st_prep_status_info.req_ie =
+			nla_data(tb[NL80211_ATTR_REQ_IE]);
+		event.st_prep_status_info.req_ie_len =
+			nla_len(tb[NL80211_ATTR_REQ_IE]);
+	}
+
+	if (tb[NL80211_ATTR_FRAME]) {
+		event.st_prep_status_info.resp_frame =
+			nla_data(tb[NL80211_ATTR_FRAME]);
+		event.st_prep_status_info.resp_frame_len =
+			nla_len(tb[NL80211_ATTR_FRAME]);
+		event.st_prep_status_info.status = 0;
+	} else {
+		event.st_prep_status_info.status = -1;
+	}
+
+	wpa_printf(MSG_DEBUG, "nl80211: ST Prep Status event (status=%d)",
+		   event.st_prep_status_info.status);
+
+	wpa_supplicant_event(drv->ctx, EVENT_ST_PREP_STATUS, &event);
+}
+
+
+static void mlme_event_st_uhr_reconf(struct wpa_driver_nl80211_data *drv,
+				      struct nlattr **tb)
+{
+	union wpa_event_data event;
+
+	os_memset(&event, 0, sizeof(event));
+
+	if (tb[NL80211_ATTR_MLD_ADDR])
+		event.st_exec_status_info.ap_mld_addr =
+			nla_data(tb[NL80211_ATTR_MLD_ADDR]);
+
+	if (tb[NL80211_ATTR_FRAME]) {
+		event.st_exec_status_info.resp_frame =
+			nla_data(tb[NL80211_ATTR_FRAME]);
+		event.st_exec_status_info.resp_frame_len =
+			nla_len(tb[NL80211_ATTR_FRAME]);
+		event.st_exec_status_info.status = 0;
+	} else {
+		event.st_exec_status_info.status = -1;
+	}
+
+	if (tb[NL80211_ATTR_MLO_LINKS]) {
+		struct nlattr *link;
+		int rem_links;
+
+		nla_for_each_nested(link, tb[NL80211_ATTR_MLO_LINKS],
+				    rem_links) {
+			struct nlattr *tb2[NL80211_ATTR_MAX + 1];
+			u8 link_id;
+
+			nla_parse(tb2, NL80211_ATTR_MAX, nla_data(link),
+				  nla_len(link), NULL);
+
+			if (!tb2[NL80211_ATTR_MLO_LINK_ID])
+				continue;
+
+			link_id = nla_get_u8(tb2[NL80211_ATTR_MLO_LINK_ID]);
+			if (link_id >= MAX_NUM_MLD_LINKS)
+				continue;
+
+			event.st_exec_status_info.links |= BIT(link_id);
+
+			if (tb2[NL80211_ATTR_BSSID])
+				os_memcpy(event.st_exec_status_info.link_info[link_id].bssid,
+					  nla_data(tb2[NL80211_ATTR_BSSID]),
+					  ETH_ALEN);
+			if (tb2[NL80211_ATTR_MAC])
+				os_memcpy(event.st_exec_status_info.link_info[link_id].addr,
+					  nla_data(tb2[NL80211_ATTR_MAC]),
+					  ETH_ALEN);
+		}
+	}
+
+	wpa_printf(MSG_DEBUG, "nl80211: ST UHR Reconf event (status=%d)",
+		   event.st_exec_status_info.status);
+
+	wpa_supplicant_event(drv->ctx, EVENT_ST_EXEC_STATUS, &event);
+}
+
+
+static void mlme_event_st_done(struct wpa_driver_nl80211_data *drv,
+			       struct nlattr **tb)
+{
+	union wpa_event_data event;
+	struct driver_sta_mlo_info *mlo = &drv->sta_mlo_info;
+	struct nlattr *link;
+	int rem_links;
+
+	int i;
+
+	if (!tb[NL80211_ATTR_MLD_ADDR] || !tb[NL80211_ATTR_MLO_LINKS]) {
+		wpa_printf(MSG_DEBUG,
+			   "nl80211: Ignore ST Done event without AP MLD address or link info");
+		return;
+	}
+
+	/*
+	 * The transition replaces the full MLO connection state - there is no
+	 * (re)association with the target AP MLD, so the previous link info
+	 * is simply dropped here.
+	 */
+	mlo->valid_links = 0;
+	mlo->req_links = 0;
+	nl80211_parse_mlo_link_info(mlo, tb[NL80211_ATTR_MLO_LINKS]);
+	if (!mlo->valid_links) {
+		wpa_printf(MSG_INFO,
+			   "nl80211: No valid links in ST Done event");
+		return;
+	}
+	mlo->req_links = mlo->valid_links;
+
+	os_memcpy(mlo->ap_mld_addr, nla_data(tb[NL80211_ATTR_MLD_ADDR]),
+		  ETH_ALEN);
+
+	/*
+	 * There is no association link with the target AP MLD, so just use the
+	 * lowest valid link for the cases that need a single BSSID.
+	 */
+	for_each_link(mlo->valid_links, i) {
+		mlo->assoc_link_id = i;
+		os_memcpy(drv->bssid, mlo->links[i].bssid, ETH_ALEN);
+		os_memcpy(drv->prev_bssid, drv->bssid, ETH_ALEN);
+		break;
+	}
+
+	/* The links can be operating on different channels now */
+	nl80211_get_assoc_freq(drv);
+	drv->first_bss->flink->freq = drv->assoc_freq;
+
+	wpa_printf(MSG_DEBUG,
+		   "nl80211: ST Done event (ap_mld=" MACSTR
+		   ", valid_links=0x%x, assoc_link_id=%u)",
+		   MAC2STR(mlo->ap_mld_addr), mlo->valid_links,
+		   mlo->assoc_link_id);
+
+	/* Prepare the event data */
+	os_memset(&event, 0, sizeof(event));
+
+	event.st_done_info.ap_mld_addr = nla_data(tb[NL80211_ATTR_MLD_ADDR]);
+	nla_for_each_nested(link, tb[NL80211_ATTR_MLO_LINKS],
+			    rem_links) {
+		struct nlattr *tb2[NL80211_ATTR_MAX + 1];
+		u8 link_id;
+
+		nla_parse(tb2, NL80211_ATTR_MAX, nla_data(link),
+			  nla_len(link), NULL);
+
+		if (!tb2[NL80211_ATTR_MLO_LINK_ID] ||
+		    !tb2[NL80211_ATTR_BSSID])
+			continue;
+
+		link_id = nla_get_u8(tb2[NL80211_ATTR_MLO_LINK_ID]);
+		if (link_id >= MAX_NUM_MLD_LINKS)
+			continue;
+
+		event.st_done_info.valid_links |= BIT(link_id);
+		os_memcpy(event.st_done_info.links[link_id].bssid,
+			  nla_data(tb2[NL80211_ATTR_BSSID]), ETH_ALEN);
+	}
+
+	wpa_supplicant_event(drv->ctx, EVENT_ST_DONE, &event);
+}
+
+
 static s8
 nl80211_get_link_id_by_freq(struct i802_bss *bss, unsigned int freq)
 {
@@ -5263,6 +5442,15 @@ static void do_process_drv_event(struct i802_bss *bss, int cmd,
 	case NL80211_CMD_ASSOC_MLO_RECONF:
 		mlme_event_link_addition(bss, nla_data(frame), nla_len(frame));
 		break;
+	case NL80211_CMD_PREPARE_SMD_TRANSITION:
+		mlme_event_st_prep_status(drv, tb);
+		break;
+	case NL80211_CMD_EXECUTE_SMD_TRANSITION:
+		mlme_event_st_uhr_reconf(drv, tb);
+		break;
+	case NL80211_CMD_COMPLETED_SMD_TRANSITION:
+		mlme_event_st_done(drv, tb);
+		break;
 #ifdef CONFIG_NAN
 	case NL80211_CMD_NAN_CLUSTER_JOINED:
 		nl80211_nan_cluster_joined_event(drv, tb);
-- 
2.55.0




More information about the Hostap mailing list