From 1c3f880ed00ef704d140a10777274937b8de29cc Mon Sep 17 00:00:00 2001 From: Priyansha Tiwari Date: Thu, 9 Jul 2026 17:12:27 +0530 Subject: [PATCH] wifi: mac80211: implement STA-mode peer probing Add STA/P2P-client support to ieee80211_probe_peer(): when called for a station interface, send a null-data frame (TODS) to the associated AP and report the ACK via cfg80211_probe_status(). For MLO connections the driver/firmware selects the link (IEEE80211_LINK_UNSPECIFIED); for non-MLO the single link is used. Signed-off-by: Priyansha Tiwari Link: https://patch.msgid.link/20260709114228.672317-2-pritiwa@qti.qualcomm.com Signed-off-by: Johannes Berg --- include/net/mac80211.h | 7 ++- net/mac80211/cfg.c | 117 ++++++++++++++++++++--------------------- net/mac80211/status.c | 5 +- 3 files changed, 67 insertions(+), 62 deletions(-) diff --git a/include/net/mac80211.h b/include/net/mac80211.h index 948a7cdab27c..999e5189f113 100644 --- a/include/net/mac80211.h +++ b/include/net/mac80211.h @@ -1348,6 +1348,11 @@ ieee80211_rate_get_vht_nss(const struct ieee80211_tx_rate *rate) * @status.tx_time: airtime consumed for transmission; note this is only * used for WMM AC, not for airtime fairness * @status.flags: status flags, see &enum mac80211_tx_status_flags + * @status.link_valid: if the link which is identified by @status.link_id is + * valid. This flag is set by the driver in the TX status callback when the + * connection is MLO and the driver knows which link was used for TX. + * @status.link_id: id of the link used to transmit the packet. This is used + * along with @status.link_valid. * @status.status_driver_data: driver use area * @ack: union part for pure ACK data * @ack.cookie: cookie for the ACK @@ -1402,7 +1407,7 @@ struct ieee80211_tx_info { u8 pad; u16 tx_time; u8 flags; - u8 pad2; + u8 link_valid:1, link_id:4; void *status_driver_data[16 / sizeof(void *)]; } status; struct { diff --git a/net/mac80211/cfg.c b/net/mac80211/cfg.c index 9c311c8290f7..df68a5bdeb2f 100644 --- a/net/mac80211/cfg.c +++ b/net/mac80211/cfg.c @@ -4956,99 +4956,100 @@ static int ieee80211_probe_peer(struct wiphy *wiphy, struct net_device *dev, struct ieee80211_local *local = sdata->local; struct ieee80211_qos_hdr *nullfunc; struct sk_buff *skb; - int size = sizeof(*nullfunc); __le16 fc; - bool qos; + bool qos, fromds; + struct ieee80211_bss_conf *conf; struct ieee80211_tx_info *info; struct sta_info *sta; struct ieee80211_chanctx_conf *chanctx_conf; - struct ieee80211_bss_conf *conf; enum nl80211_band band; - u8 link_id; + const u8 *dst_addr; + const u8 *src_addr; + int link_id; + int size; int ret; /* the lock is needed to assign the cookie later */ lockdep_assert_wiphy(local->hw.wiphy); - rcu_read_lock(); - sta = sta_info_get_bss(sdata, peer); - if (!sta) { - ret = -ENOLINK; - goto unlock; + switch (ieee80211_vif_type_p2p(&sdata->vif)) { + case NL80211_IFTYPE_AP: + fromds = true; + break; + case NL80211_IFTYPE_STATION: + /* For STA, the peer is always the associated AP/GO */ + peer = sdata->vif.cfg.ap_addr; + fromds = false; + break; + default: + return -EOPNOTSUPP; } + sta = sta_info_get_bss(sdata, peer); + if (!sta) + return -ENOLINK; + qos = sta->sta.wme; + dst_addr = sta->sta.addr; if (ieee80211_vif_is_mld(&sdata->vif)) { - if (sta->sta.mlo) { - link_id = IEEE80211_LINK_UNSPECIFIED; - } else { + if (fromds && !sta->sta.mlo) { /* - * For non-MLO clients connected to an AP MLD, band - * information is not used; instead, sta->deflink is - * used to send packets. + * AP mode, non-MLO client on AP MLD: use the + * per-link address for the client's link. */ link_id = sta->deflink.link_id; - - conf = rcu_dereference(sdata->vif.link_conf[link_id]); - - if (unlikely(!conf)) { - ret = -ENOLINK; - goto unlock; - } + conf = wiphy_dereference(local->hw.wiphy, + sdata->vif.link_conf[link_id]); + if (!conf) + return -ENOLINK; + src_addr = conf->addr; + } else { + /* + * MLO client (AP or STA mode), or STA mode: + * always use LINK_UNSPECIFIED and MLD address. + */ + link_id = IEEE80211_LINK_UNSPECIFIED; + src_addr = sdata->vif.addr; } /* MLD transmissions must not rely on the band */ band = 0; } else { - chanctx_conf = rcu_dereference(sdata->vif.bss_conf.chanctx_conf); - if (WARN_ON(!chanctx_conf)) { - ret = -EINVAL; - goto unlock; - } + chanctx_conf = wiphy_dereference(local->hw.wiphy, + sdata->vif.bss_conf.chanctx_conf); + if (WARN_ON(!chanctx_conf)) + return -EINVAL; band = chanctx_conf->def.chan->band; link_id = 0; + src_addr = sdata->vif.addr; } - if (qos) { - fc = cpu_to_le16(IEEE80211_FTYPE_DATA | - IEEE80211_STYPE_QOS_NULLFUNC | - IEEE80211_FCTL_FROMDS); - } else { + size = sizeof(*nullfunc); + fc = cpu_to_le16(IEEE80211_FTYPE_DATA | + (qos ? IEEE80211_STYPE_QOS_NULLFUNC + : IEEE80211_STYPE_NULLFUNC) | + (fromds ? IEEE80211_FCTL_FROMDS : IEEE80211_FCTL_TODS)); + if (!qos) size -= 2; - fc = cpu_to_le16(IEEE80211_FTYPE_DATA | - IEEE80211_STYPE_NULLFUNC | - IEEE80211_FCTL_FROMDS); - } skb = dev_alloc_skb(local->hw.extra_tx_headroom + size); - if (!skb) { - ret = -ENOMEM; - goto unlock; - } + if (!skb) + return -ENOMEM; skb->dev = dev; - skb_reserve(skb, local->hw.extra_tx_headroom); - nullfunc = skb_put(skb, size); + nullfunc = skb_put_zero(skb, size); nullfunc->frame_control = fc; - nullfunc->duration_id = 0; - memcpy(nullfunc->addr1, sta->sta.addr, ETH_ALEN); - if (ieee80211_vif_is_mld(&sdata->vif) && !sta->sta.mlo) { - memcpy(nullfunc->addr2, conf->addr, ETH_ALEN); - memcpy(nullfunc->addr3, conf->addr, ETH_ALEN); - } else { - memcpy(nullfunc->addr2, sdata->vif.addr, ETH_ALEN); - memcpy(nullfunc->addr3, sdata->vif.addr, ETH_ALEN); - } - nullfunc->seq_ctrl = 0; + + memcpy(nullfunc->addr1, dst_addr, ETH_ALEN); + memcpy(nullfunc->addr2, src_addr, ETH_ALEN); + memcpy(nullfunc->addr3, fromds ? src_addr : dst_addr, ETH_ALEN); info = IEEE80211_SKB_CB(skb); - info->flags |= IEEE80211_TX_CTL_REQ_TX_STATUS | IEEE80211_TX_INTFL_NL80211_FRAME_TX; info->band = band; - info->control.flags |= u32_encode_bits(link_id, IEEE80211_TX_CTRL_MLO_LINK); skb_set_queue_mapping(skb, IEEE80211_AC_VO); @@ -5059,18 +5060,14 @@ static int ieee80211_probe_peer(struct wiphy *wiphy, struct net_device *dev, ret = ieee80211_attach_ack_skb(local, skb, cookie, GFP_ATOMIC); if (ret) { kfree_skb(skb); - goto unlock; + return ret; } local_bh_disable(); ieee80211_xmit(sdata, sta, skb); local_bh_enable(); - ret = 0; -unlock: - rcu_read_unlock(); - - return ret; + return 0; } static int ieee80211_cfg_get_channel(struct wiphy *wiphy, diff --git a/net/mac80211/status.c b/net/mac80211/status.c index c3d29aed93fe..d635490f59d3 100644 --- a/net/mac80211/status.c +++ b/net/mac80211/status.c @@ -655,7 +655,10 @@ static void ieee80211_report_ack_skb(struct ieee80211_local *local, GFP_ATOMIC); else if (ieee80211_is_any_nullfunc(hdr->frame_control)) cfg80211_probe_status(sdata->dev, hdr->addr1, - cookie, -1, acked, + cookie, + info->status.link_valid ? + info->status.link_id : -1, + acked, info->status.ack_signal, is_valid_ack_signal, GFP_ATOMIC);