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 <priyansha.tiwari@oss.qualcomm.com>
Link: https://patch.msgid.link/20260709114228.672317-2-pritiwa@qti.qualcomm.com
Signed-off-by: Johannes Berg <johannes.berg@intel.com>
This commit is contained in:
Priyansha Tiwari 2026-07-09 17:12:27 +05:30 committed by Johannes Berg
parent 69bd85e933
commit 1c3f880ed0
3 changed files with 67 additions and 62 deletions

View File

@ -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 * @status.tx_time: airtime consumed for transmission; note this is only
* used for WMM AC, not for airtime fairness * used for WMM AC, not for airtime fairness
* @status.flags: status flags, see &enum mac80211_tx_status_flags * @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 * @status.status_driver_data: driver use area
* @ack: union part for pure ACK data * @ack: union part for pure ACK data
* @ack.cookie: cookie for the ACK * @ack.cookie: cookie for the ACK
@ -1402,7 +1407,7 @@ struct ieee80211_tx_info {
u8 pad; u8 pad;
u16 tx_time; u16 tx_time;
u8 flags; u8 flags;
u8 pad2; u8 link_valid:1, link_id:4;
void *status_driver_data[16 / sizeof(void *)]; void *status_driver_data[16 / sizeof(void *)];
} status; } status;
struct { struct {

View File

@ -4956,99 +4956,100 @@ static int ieee80211_probe_peer(struct wiphy *wiphy, struct net_device *dev,
struct ieee80211_local *local = sdata->local; struct ieee80211_local *local = sdata->local;
struct ieee80211_qos_hdr *nullfunc; struct ieee80211_qos_hdr *nullfunc;
struct sk_buff *skb; struct sk_buff *skb;
int size = sizeof(*nullfunc);
__le16 fc; __le16 fc;
bool qos; bool qos, fromds;
struct ieee80211_bss_conf *conf;
struct ieee80211_tx_info *info; struct ieee80211_tx_info *info;
struct sta_info *sta; struct sta_info *sta;
struct ieee80211_chanctx_conf *chanctx_conf; struct ieee80211_chanctx_conf *chanctx_conf;
struct ieee80211_bss_conf *conf;
enum nl80211_band band; enum nl80211_band band;
u8 link_id; const u8 *dst_addr;
const u8 *src_addr;
int link_id;
int size;
int ret; int ret;
/* the lock is needed to assign the cookie later */ /* the lock is needed to assign the cookie later */
lockdep_assert_wiphy(local->hw.wiphy); lockdep_assert_wiphy(local->hw.wiphy);
rcu_read_lock(); switch (ieee80211_vif_type_p2p(&sdata->vif)) {
sta = sta_info_get_bss(sdata, peer); case NL80211_IFTYPE_AP:
if (!sta) { fromds = true;
ret = -ENOLINK; break;
goto unlock; 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; qos = sta->sta.wme;
dst_addr = sta->sta.addr;
if (ieee80211_vif_is_mld(&sdata->vif)) { if (ieee80211_vif_is_mld(&sdata->vif)) {
if (sta->sta.mlo) { if (fromds && !sta->sta.mlo) {
link_id = IEEE80211_LINK_UNSPECIFIED;
} else {
/* /*
* For non-MLO clients connected to an AP MLD, band * AP mode, non-MLO client on AP MLD: use the
* information is not used; instead, sta->deflink is * per-link address for the client's link.
* used to send packets.
*/ */
link_id = sta->deflink.link_id; link_id = sta->deflink.link_id;
conf = wiphy_dereference(local->hw.wiphy,
conf = rcu_dereference(sdata->vif.link_conf[link_id]); sdata->vif.link_conf[link_id]);
if (!conf)
if (unlikely(!conf)) { return -ENOLINK;
ret = -ENOLINK; src_addr = conf->addr;
goto unlock; } 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 */ /* MLD transmissions must not rely on the band */
band = 0; band = 0;
} else { } else {
chanctx_conf = rcu_dereference(sdata->vif.bss_conf.chanctx_conf); chanctx_conf = wiphy_dereference(local->hw.wiphy,
if (WARN_ON(!chanctx_conf)) { sdata->vif.bss_conf.chanctx_conf);
ret = -EINVAL; if (WARN_ON(!chanctx_conf))
goto unlock; return -EINVAL;
}
band = chanctx_conf->def.chan->band; band = chanctx_conf->def.chan->band;
link_id = 0; link_id = 0;
src_addr = sdata->vif.addr;
} }
if (qos) { size = sizeof(*nullfunc);
fc = cpu_to_le16(IEEE80211_FTYPE_DATA | fc = cpu_to_le16(IEEE80211_FTYPE_DATA |
IEEE80211_STYPE_QOS_NULLFUNC | (qos ? IEEE80211_STYPE_QOS_NULLFUNC
IEEE80211_FCTL_FROMDS); : IEEE80211_STYPE_NULLFUNC) |
} else { (fromds ? IEEE80211_FCTL_FROMDS : IEEE80211_FCTL_TODS));
if (!qos)
size -= 2; 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); skb = dev_alloc_skb(local->hw.extra_tx_headroom + size);
if (!skb) { if (!skb)
ret = -ENOMEM; return -ENOMEM;
goto unlock;
}
skb->dev = dev; skb->dev = dev;
skb_reserve(skb, local->hw.extra_tx_headroom); skb_reserve(skb, local->hw.extra_tx_headroom);
nullfunc = skb_put(skb, size); nullfunc = skb_put_zero(skb, size);
nullfunc->frame_control = fc; nullfunc->frame_control = fc;
nullfunc->duration_id = 0;
memcpy(nullfunc->addr1, sta->sta.addr, ETH_ALEN); memcpy(nullfunc->addr1, dst_addr, ETH_ALEN);
if (ieee80211_vif_is_mld(&sdata->vif) && !sta->sta.mlo) { memcpy(nullfunc->addr2, src_addr, ETH_ALEN);
memcpy(nullfunc->addr2, conf->addr, ETH_ALEN); memcpy(nullfunc->addr3, fromds ? src_addr : dst_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;
info = IEEE80211_SKB_CB(skb); info = IEEE80211_SKB_CB(skb);
info->flags |= IEEE80211_TX_CTL_REQ_TX_STATUS | info->flags |= IEEE80211_TX_CTL_REQ_TX_STATUS |
IEEE80211_TX_INTFL_NL80211_FRAME_TX; IEEE80211_TX_INTFL_NL80211_FRAME_TX;
info->band = band; info->band = band;
info->control.flags |= u32_encode_bits(link_id, info->control.flags |= u32_encode_bits(link_id,
IEEE80211_TX_CTRL_MLO_LINK); IEEE80211_TX_CTRL_MLO_LINK);
skb_set_queue_mapping(skb, IEEE80211_AC_VO); 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); ret = ieee80211_attach_ack_skb(local, skb, cookie, GFP_ATOMIC);
if (ret) { if (ret) {
kfree_skb(skb); kfree_skb(skb);
goto unlock; return ret;
} }
local_bh_disable(); local_bh_disable();
ieee80211_xmit(sdata, sta, skb); ieee80211_xmit(sdata, sta, skb);
local_bh_enable(); local_bh_enable();
ret = 0; return 0;
unlock:
rcu_read_unlock();
return ret;
} }
static int ieee80211_cfg_get_channel(struct wiphy *wiphy, static int ieee80211_cfg_get_channel(struct wiphy *wiphy,

View File

@ -655,7 +655,10 @@ static void ieee80211_report_ack_skb(struct ieee80211_local *local,
GFP_ATOMIC); GFP_ATOMIC);
else if (ieee80211_is_any_nullfunc(hdr->frame_control)) else if (ieee80211_is_any_nullfunc(hdr->frame_control))
cfg80211_probe_status(sdata->dev, hdr->addr1, 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, info->status.ack_signal,
is_valid_ack_signal, is_valid_ack_signal,
GFP_ATOMIC); GFP_ATOMIC);