[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