[PATCH wireless-next v4 08/21] wifi: nl80211/cfg80211: pack SMD dynamic context along with frame

From: Pooventhiran G

Date: Fri Nov 06 2026 - 03:03:07 EST


While packing the received management frame in nl80211_send_mgmt() to
send to userspace, nest the SMD dynamic context along with the frame
message. This shall be used by userspace to transport the context to the
target AP MLD for programming the dynamic state of the STA session for
seamless roaming as part of SMD BSS Transition.

Suggested-by: Jouni Malinen <jouni.malinen@xxxxxxxxxxxxxxxx>
Co-developed-by: Aditya Sathish <asathish@xxxxxxxxxxxxxxxx>
Signed-off-by: Aditya Sathish <asathish@xxxxxxxxxxxxxxxx>
Signed-off-by: Pooventhiran G <pooventhiran.g@xxxxxxxxxxxxxxxx>
---
include/net/cfg80211.h | 13 +++
net/wireless/nl80211.c | 286 ++++++++++++++++++++++++++++++++++++++++++++++++-
2 files changed, 298 insertions(+), 1 deletion(-)

diff --git a/include/net/cfg80211.h b/include/net/cfg80211.h
index 938fbfe0e71d..877845477ce9 100644
--- a/include/net/cfg80211.h
+++ b/include/net/cfg80211.h
@@ -4893,6 +4893,17 @@ struct mgmt_frame_regs {
u32 global_mcast_stypes, interface_mcast_stypes;
};

+/**
+ * struct cfg80211_smd_transition_info - SMD BSS Transition info
+ *
+ * @ctx: Dynamic context to be transferred as part of ST
+ * @type: Type of ST indication
+ */
+struct cfg80211_smd_transition_info {
+ struct ieee80211_smd_ctx *ctx;
+ enum nl80211_smd_ctx_type type;
+};
+
/**
* struct cfg80211_ops - backend description for wireless configuration
*
@@ -9624,6 +9635,7 @@ void cfg80211_conn_failed(struct net_device *dev, const u8 *mac_addr,
* @rx_tstamp: Hardware timestamp of frame RX in nanoseconds
* @ack_tstamp: Hardware timestamp of ack TX in nanoseconds
* @no_sta: set if no station is known for the frame (relevant for MLD)
+ * @st_info: SMD BSS Transition data
*/
struct cfg80211_rx_info {
int freq;
@@ -9636,6 +9648,7 @@ struct cfg80211_rx_info {
u64 rx_tstamp;
u64 ack_tstamp;
bool no_sta;
+ struct cfg80211_smd_transition_info st_info;
};

/**
diff --git a/net/wireless/nl80211.c b/net/wireless/nl80211.c
index d2cd03d8b0c5..33e52b8bb5fa 100644
--- a/net/wireless/nl80211.c
+++ b/net/wireless/nl80211.c
@@ -20137,6 +20137,282 @@ nl80211_epcs_cfg(struct sk_buff *skb, struct genl_info *info)
return rdev_set_epcs(rdev, dev, val);
}

+static size_t _nl80211_smd_ctx_nl_ba_param_size(u8 n_tids, bool dl)
+{
+ size_t n = 0;
+
+ n += nla_total_size(0); /* ba_params nest header */
+ n += n_tids * (nla_total_size(0) + /* tid nest header */
+ nla_total_size(sizeof(u16)) + /* buff_size */
+ nla_total_size(sizeof(u8)) + /* policy */
+ nla_total_size(0) + /* amsdu_support */
+ nla_total_size(sizeof(u16)) + /* timeout */
+ nla_total_size(0) + /* ext_no_frag */
+ nla_total_size(sizeof(u8)) + /* ext_frag_lvl */
+ nla_total_size(sizeof(u16))); /* ext_buff_size */
+
+ if (dl)
+ n += n_tids * nla_total_size(sizeof(u16)); /* winstarto */
+
+ return n;
+}
+
+static size_t
+nl80211_smd_ctx_nl_size(struct cfg80211_smd_transition_info *st_info)
+{
+ const struct ieee80211_smd_ctx *ctx = st_info->ctx;
+ int n_dl_tids = 0, n_ul_tids = 0;
+ u8 pn_len = ctx->pn_len;
+ size_t n = 0;
+
+ /* count valid TIDs */
+ n_dl_tids = hweight8(ctx->dl.valid_tid_bmap);
+ n_ul_tids = hweight8(ctx->ul.valid_tid_bmap);
+
+ n += nla_total_size(sizeof(u8)); /* ST type */
+
+ /* outer NL80211_ATTR_SMD_CTX nest */
+ n += nla_total_size(0); /* nest header */
+
+ /* DL nest */
+ n += nla_total_size(0); /* DL nest header */
+
+ n += nla_total_size(sizeof(u8)); /* valid_tid_bitmap */
+
+ if (ctx->dl_sn_valid) {
+ n += nla_total_size(0); /* DL SN nest header */
+ n += n_dl_tids * nla_total_size(sizeof(u16)); /* DL SN */
+ if (ctx->mgmt_tid_valid)
+ n += nla_total_size(sizeof(u16)); /* DL mgmt_sn */
+ }
+
+ if (ctx->pn_valid)
+ n += nla_total_size(pn_len); /* DL PN */
+
+ if (ctx->ba_params_valid)
+ n += _nl80211_smd_ctx_nl_ba_param_size(n_dl_tids, true); /* DL BA */
+
+ /* UL nest */
+ n += nla_total_size(0); /* UL nest header */
+
+ n += nla_total_size(sizeof(u8)); /* valid_tid_bitmap */
+
+ if (ctx->ul_sn_valid) {
+ n += nla_total_size(0); /* UL SN nest header */
+ n += n_ul_tids * nla_total_size(sizeof(u16)); /* UL SN */
+ if (ctx->mgmt_tid_valid)
+ n += nla_total_size(sizeof(u16)); /* UL mgmt_sn */
+ }
+
+ if (ctx->pn_valid) {
+ n += nla_total_size(0); /* UL PN nest header */
+ n += n_ul_tids * nla_total_size(pn_len); /* UL PN */
+ if (ctx->mgmt_tid_valid)
+ n += nla_total_size(pn_len); /* UL mgmt_pn */
+ }
+
+ if (ctx->ba_params_valid)
+ n += _nl80211_smd_ctx_nl_ba_param_size(n_ul_tids, false); /* UL BA */
+
+ return n;
+}
+
+static int
+nl80211_put_smd_ctx_ba_params(struct sk_buff *msg,
+ const unsigned long *tid_bmap,
+ const struct ieee80211_smd_ctx_ba *ba_tids,
+ bool dl)
+{
+ u32 ba_params_attr = NL80211_SMD_CTX_DL_ATTR_BA_PARAMS;
+ struct nlattr *dl_ul_ba = NULL, *tid_ba = NULL;
+ u8 tid;
+
+ if (!dl)
+ ba_params_attr = NL80211_SMD_CTX_UL_ATTR_BA_PARAMS;
+
+ dl_ul_ba = nla_nest_start(msg, ba_params_attr);
+ if (!dl_ul_ba)
+ return -ENOBUFS;
+
+ for_each_set_bit(tid, tid_bmap, IEEE80211_SMD_CTX_NUM_TIDS) {
+ const struct ieee80211_smd_ctx_ba *ba = &ba_tids[tid];
+
+ tid_ba = nla_nest_start(msg, tid + 1);
+ if (!tid_ba)
+ goto nla_fail;
+
+ if (nla_put_u16(msg, NL80211_SMD_CTX_BA_ATTR_BUFF_SIZE,
+ ba->buffer_size) ||
+ nla_put_u8(msg, NL80211_SMD_CTX_BA_ATTR_POLICY,
+ ba->ba_policy) ||
+ (ba->amsdu_supported &&
+ nla_put_flag(msg,
+ NL80211_SMD_CTX_BA_ATTR_AMSDU_SUPPORT)) ||
+ nla_put_u16(msg, NL80211_SMD_CTX_BA_ATTR_TIMEOUT,
+ ba->timeout) ||
+ (ba->ext_no_frag &&
+ nla_put_flag(msg, NL80211_SMD_CTX_BA_ATTR_EXT_NO_FRAG)) ||
+ nla_put_u8(msg, NL80211_SMD_CTX_BA_ATTR_EXT_FRAG_LEVEL,
+ ba->extfrag_level) ||
+ nla_put_u16(msg, NL80211_SMD_CTX_BA_ATTR_EXT_BUFF_SIZE,
+ ba->ext_buffer_size) ||
+ (dl && nla_put_u16(msg, NL80211_SMD_CTX_BA_ATTR_WINSTARTO,
+ ba->winstarto))) {
+ nla_nest_cancel(msg, tid_ba);
+ goto nla_fail;
+ }
+
+ nla_nest_end(msg, tid_ba);
+ }
+
+ nla_nest_end(msg, dl_ul_ba);
+ return 0;
+
+nla_fail:
+ nla_nest_cancel(msg, dl_ul_ba);
+ return -ENOBUFS;
+}
+
+static int nl80211_put_smd_ctx(struct sk_buff *msg,
+ struct cfg80211_smd_transition_info *st_info)
+{
+ struct nlattr *dl_sn = NULL, *ul_sn = NULL, *ul_pn = NULL;
+ struct nlattr *smd_ctx = NULL, *dl = NULL, *ul = NULL;
+ const struct ieee80211_smd_ctx *ctx;
+ unsigned long dl_tid_bmap = 0, ul_tid_bmap = 0;
+ bool pn_valid;
+ u8 tid;
+
+ /* ST Type */
+ if (nla_put_u8(msg, NL80211_ATTR_SMD_CTX_TYPE, st_info->type))
+ return -ENOBUFS;
+
+ smd_ctx = nla_nest_start(msg, NL80211_ATTR_SMD_CTX);
+ if (!smd_ctx)
+ return -ENOBUFS;
+
+ ctx = st_info->ctx;
+
+ /* Build DL context */
+ dl_tid_bmap = ctx->dl.valid_tid_bmap;
+
+ dl = nla_nest_start(msg, NL80211_SMD_CTX_ATTR_DL);
+ if (!dl)
+ goto nla_fail;
+
+ if (nla_put_u8(msg, NL80211_SMD_CTX_DL_ATTR_VALID_TID_BITMAP,
+ ctx->dl.valid_tid_bmap))
+ goto nla_fail_dl;
+
+ if (ctx->dl_sn_valid) {
+ dl_sn = nla_nest_start(msg, NL80211_SMD_CTX_DL_ATTR_SN);
+ if (!dl_sn)
+ goto nla_fail_dl;
+
+ for_each_set_bit(tid, &dl_tid_bmap,
+ IEEE80211_SMD_CTX_NUM_TIDS) {
+ if (nla_put_u16(msg, tid + 1, ctx->dl.sn[tid])) {
+ nla_nest_cancel(msg, dl_sn);
+ goto nla_fail_dl;
+ }
+ }
+ nla_nest_end(msg, dl_sn);
+ }
+
+ pn_valid = (ctx->pn_len && ctx->pn_len <= IEEE80211_SMD_CTX_MAX_PN_LEN &&
+ ctx->pn_valid);
+
+ if (pn_valid &&
+ nla_put(msg, NL80211_SMD_CTX_DL_ATTR_PN, ctx->pn_len, ctx->dl.pn))
+ goto nla_fail_dl;
+
+ if (ctx->ba_params_valid &&
+ nl80211_put_smd_ctx_ba_params(msg, &dl_tid_bmap,
+ ctx->dl.ba, true))
+ goto nla_fail_dl;
+
+ if (ctx->dl_sn_valid && ctx->mgmt_tid_valid &&
+ nla_put_u16(msg, NL80211_SMD_CTX_DL_ATTR_MGMT_SN, ctx->dl.mgmt_sn))
+ goto nla_fail_dl;
+
+ nla_nest_end(msg, dl);
+
+ /* Build UL context */
+ ul_tid_bmap = ctx->ul.valid_tid_bmap;
+
+ ul = nla_nest_start(msg, NL80211_SMD_CTX_ATTR_UL);
+ if (!ul)
+ goto nla_fail;
+
+ if (nla_put_u8(msg, NL80211_SMD_CTX_UL_ATTR_VALID_TID_BITMAP,
+ ctx->ul.valid_tid_bmap))
+ goto nla_fail_ul;
+
+ if (ctx->ul_sn_valid) {
+ ul_sn = nla_nest_start(msg, NL80211_SMD_CTX_UL_ATTR_SN);
+ if (!ul_sn)
+ goto nla_fail_ul;
+
+ for_each_set_bit(tid, &ul_tid_bmap,
+ IEEE80211_SMD_CTX_NUM_TIDS) {
+ if (nla_put_u16(msg, tid + 1, ctx->ul.sn[tid])) {
+ nla_nest_cancel(msg, ul_sn);
+ goto nla_fail_ul;
+ }
+ }
+ nla_nest_end(msg, ul_sn);
+ }
+
+ if (pn_valid) {
+ ul_pn = nla_nest_start(msg, NL80211_SMD_CTX_UL_ATTR_PN);
+ if (!ul_pn)
+ goto nla_fail_ul;
+
+ for_each_set_bit(tid, &ul_tid_bmap,
+ IEEE80211_SMD_CTX_NUM_TIDS) {
+ if (nla_put(msg, tid + 1, ctx->pn_len,
+ ctx->ul.pn[tid])) {
+ nla_nest_cancel(msg, ul_pn);
+ goto nla_fail_ul;
+ }
+ }
+ nla_nest_end(msg, ul_pn);
+ }
+
+ if (ctx->ba_params_valid &&
+ nl80211_put_smd_ctx_ba_params(msg, &ul_tid_bmap,
+ ctx->ul.ba, false))
+ goto nla_fail_ul;
+
+ if (ctx->mgmt_tid_valid) {
+ if (ctx->ul_sn_valid &&
+ nla_put_u16(msg, NL80211_SMD_CTX_UL_ATTR_MGMT_SN,
+ ctx->ul.mgmt_sn))
+ goto nla_fail_ul;
+
+ if (pn_valid &&
+ nla_put(msg, NL80211_SMD_CTX_UL_ATTR_MGMT_PN,
+ ctx->pn_len, ctx->ul.mgmt_pn))
+ goto nla_fail_ul;
+ }
+
+ nla_nest_end(msg, ul);
+
+ nla_nest_end(msg, smd_ctx);
+
+ return 0;
+
+nla_fail_ul:
+ nla_nest_cancel(msg, ul);
+
+nla_fail_dl:
+ nla_nest_cancel(msg, dl);
+
+nla_fail:
+ nla_nest_cancel(msg, smd_ctx);
+ return -EINVAL;
+}
+
#define NL80211_FLAG_NEED_WIPHY 0x01
#define NL80211_FLAG_NEED_NETDEV 0x02
#define NL80211_FLAG_NEED_RTNL 0x04
@@ -22872,10 +23148,14 @@ int nl80211_send_mgmt(struct cfg80211_registered_device *rdev,
struct cfg80211_rx_info *info, gfp_t gfp)
{
struct net_device *netdev = wdev->netdev;
+ int st_roaming_data_len = 0;
struct sk_buff *msg;
void *hdr;

- msg = nlmsg_new(100 + info->len, gfp);
+ if (info->st_info.ctx)
+ st_roaming_data_len = nl80211_smd_ctx_nl_size(&info->st_info);
+
+ msg = nlmsg_new(100 + info->len + st_roaming_data_len, gfp);
if (!msg)
return -ENOMEM;

@@ -22911,6 +23191,10 @@ int nl80211_send_mgmt(struct cfg80211_registered_device *rdev,
nla_put_flag(msg, NL80211_ATTR_FRAME_NO_STA)))
goto nla_put_failure;

+ if (st_roaming_data_len &&
+ nl80211_put_smd_ctx(msg, &info->st_info))
+ goto nla_put_failure;
+
genlmsg_end(msg, hdr);

return genlmsg_unicast(wiphy_net(&rdev->wiphy), msg, nlportid);

--
2.34.1