* [PATCH wireless-next v1 1/4] wifi: nl80211: rename PROBE_CLIENT to PROBE_PEER and add STA-side probing support
2026-04-15 9:43 [PATCH wireless-next v1 0/4] wifi: nl80211: introduce PROBE_PEER for AP and STA with MLO support Priyansha Tiwari
@ 2026-04-15 9:43 ` Priyansha Tiwari
2026-04-15 9:53 ` Johannes Berg
2026-04-15 9:43 ` [PATCH wireless-next v1 2/4] wifi: cfg80211/nl80211: rename to probe_peer(), extend probe status, and update in-tree users Priyansha Tiwari
` (2 subsequent siblings)
3 siblings, 1 reply; 8+ messages in thread
From: Priyansha Tiwari @ 2026-04-15 9:43 UTC (permalink / raw)
To: johannes; +Cc: linux-wireless, quic_drohan
From: Priyansha Tiwari <priyansha.tiwari@oss.qualcomm.com>
Rename NL80211_CMD_PROBE_CLIENT to NL80211_CMD_PROBE_PEER to generalize
peer probing, AP/GO continue to probe associated STAs (legacy PROBE_CLIENT
behavior) while, when the driver advertises NL80211_EXT_FEATURE_PROBE_AP,
a STA/P2P-client may probe its currently associated AP to quickly verify link
responsiveness without waiting for traffic or long timeouts.
Userspace and cfg80211 rely on this feature flag to determine whether
STA-mode probing is supported. The command returns a cookie in the direct
reply and delivers an event that indicates ACK (and, if available,
signed-dBm ACK_SIGNAL).
For MLO connections the event carries MLO_LINK_ID identifying the link on
which the probe was ACKed. In AP/GO mode the peer MAC is required, while in
STA/P2P-client mode the MAC is omitted (AP implied).
NL80211_CMD_PROBE_CLIENT is retained as a compatibility alias.
Signed-off-by: Priyansha Tiwari <priyansha.tiwari@oss.qualcomm.com>
---
include/uapi/linux/nl80211.h | 27 +++++++++++++++++++++------
net/wireless/nl80211.c | 6 +++---
2 files changed, 24 insertions(+), 9 deletions(-)
diff --git a/include/uapi/linux/nl80211.h b/include/uapi/linux/nl80211.h
index 3d55bf4be36f..5c1b00bf8161 100644
--- a/include/uapi/linux/nl80211.h
+++ b/include/uapi/linux/nl80211.h
@@ -920,13 +920,23 @@
* and wasn't already in a 4-addr VLAN. The event will be sent similarly
* to the %NL80211_CMD_UNEXPECTED_FRAME event, to the same listener.
*
- * @NL80211_CMD_PROBE_CLIENT: Probe an associated station on an AP interface
+ * @NL80211_CMD_PROBE_PEER: Probe an associated station on an AP interface
* by sending a null data frame to it and reporting when the frame is
* acknowledged. This is used to allow timing out inactive clients. Uses
- * %NL80211_ATTR_IFINDEX and %NL80211_ATTR_MAC. The command returns a
- * direct reply with an %NL80211_ATTR_COOKIE that is later used to match
- * up the event with the request. The event includes the same data and
- * has %NL80211_ATTR_ACK set if the frame was ACKed.
+ * %NL80211_ATTR_IFINDEX and %NL80211_ATTR_MAC (required in AP/GO mode).
+ * This command can also be used on a STA/P2P-client interface to probe
+ * the currently associated AP/GO, when the driver indicates
+ * %NL80211_EXT_FEATURE_PROBE_AP; in that case %NL80211_ATTR_MAC must
+ * be omitted (the peer is implied by the association).
+ *
+ * The command returns a direct reply with an %NL80211_ATTR_COOKIE
+ * that is later used to match up the event with the request. The
+ * event includes the same data and has %NL80211_ATTR_ACK set if the
+ * frame was ACKed.
+ *
+ * In case of an MLO connection, the event includes
+ * %NL80211_ATTR_MLO_LINK_ID to indicate the link on which the null data
+ * frame was transmitted and ACKed.
*
* @NL80211_CMD_REGISTER_BEACONS: Register this socket to receive beacons from
* other BSSes when any interfaces are in AP mode. This helps implement
@@ -1548,7 +1558,7 @@ enum nl80211_commands {
NL80211_CMD_UNEXPECTED_FRAME,
- NL80211_CMD_PROBE_CLIENT,
+ NL80211_CMD_PROBE_PEER,
NL80211_CMD_REGISTER_BEACONS,
@@ -1716,6 +1726,7 @@ enum nl80211_commands {
#define NL80211_CMD_GET_MESH_PARAMS NL80211_CMD_GET_MESH_CONFIG
#define NL80211_CMD_SET_MESH_PARAMS NL80211_CMD_SET_MESH_CONFIG
#define NL80211_MESH_SETUP_VENDOR_PATH_SEL_IE NL80211_MESH_SETUP_IE
+#define NL80211_CMD_PROBE_CLIENT NL80211_CMD_PROBE_PEER
/**
* enum nl80211_attrs - nl80211 netlink attributes
@@ -7029,6 +7040,9 @@ enum nl80211_feature_flags {
* (NL80211_CMD_AUTHENTICATE) in non-AP STA mode, as specified in
* "IEEE P802.11bi/D4.0, 12.16.5".
*
+ * @NL80211_EXT_FEATURE_PROBE_AP: Driver supports probing the associated AP
+ * in STA mode using @NL80211_CMD_PROBE_PEER.
+ *
* @NUM_NL80211_EXT_FEATURES: number of extended features.
* @MAX_NL80211_EXT_FEATURES: highest extended feature index.
*/
@@ -7108,6 +7122,7 @@ enum nl80211_ext_feature_index {
NL80211_EXT_FEATURE_EPPKE,
NL80211_EXT_FEATURE_ASSOC_FRAME_ENCRYPTION,
NL80211_EXT_FEATURE_IEEE8021X_AUTH,
+ NL80211_EXT_FEATURE_PROBE_AP,
/* add new features before the definition below */
NUM_NL80211_EXT_FEATURES,
diff --git a/net/wireless/nl80211.c b/net/wireless/nl80211.c
index f334cdef8958..e7be43f023b6 100644
--- a/net/wireless/nl80211.c
+++ b/net/wireless/nl80211.c
@@ -15817,7 +15817,7 @@ static int nl80211_probe_client(struct sk_buff *skb,
return -ENOMEM;
hdr = nl80211hdr_put(msg, info->snd_portid, info->snd_seq, 0,
- NL80211_CMD_PROBE_CLIENT);
+ NL80211_CMD_PROBE_PEER);
if (!hdr) {
err = -ENOBUFS;
goto free_msg;
@@ -19627,7 +19627,7 @@ static const struct genl_small_ops nl80211_small_ops[] = {
.internal_flags = IFLAGS(NL80211_FLAG_NEED_NETDEV),
},
{
- .cmd = NL80211_CMD_PROBE_CLIENT,
+ .cmd = NL80211_CMD_PROBE_PEER,
.validate = GENL_DONT_VALIDATE_STRICT | GENL_DONT_VALIDATE_DUMP,
.doit = nl80211_probe_client,
.flags = GENL_UNS_ADMIN_PERM,
@@ -22181,7 +22181,7 @@ void cfg80211_probe_status(struct net_device *dev, const u8 *addr,
if (!msg)
return;
- hdr = nl80211hdr_put(msg, 0, 0, 0, NL80211_CMD_PROBE_CLIENT);
+ hdr = nl80211hdr_put(msg, 0, 0, 0, NL80211_CMD_PROBE_PEER);
if (!hdr) {
nlmsg_free(msg);
return;
--
2.34.1
^ permalink raw reply related [flat|nested] 8+ messages in thread* [PATCH wireless-next v1 2/4] wifi: cfg80211/nl80211: rename to probe_peer(), extend probe status, and update in-tree users
2026-04-15 9:43 [PATCH wireless-next v1 0/4] wifi: nl80211: introduce PROBE_PEER for AP and STA with MLO support Priyansha Tiwari
2026-04-15 9:43 ` [PATCH wireless-next v1 1/4] wifi: nl80211: rename PROBE_CLIENT to PROBE_PEER and add STA-side probing support Priyansha Tiwari
@ 2026-04-15 9:43 ` Priyansha Tiwari
2026-04-15 9:43 ` [PATCH wireless-next v1 3/4] wifi: mac80211: add per-link PROBE_PEER support Priyansha Tiwari
2026-04-15 9:43 ` [PATCH wireless-next v1 4/4] wifi: mac80211_hwsim: report TX status link_id Priyansha Tiwari
3 siblings, 0 replies; 8+ messages in thread
From: Priyansha Tiwari @ 2026-04-15 9:43 UTC (permalink / raw)
To: johannes; +Cc: linux-wireless, quic_drohan
From: Priyansha Tiwari <priyansha.tiwari@oss.qualcomm.com>
Rename probe_client() to probe_peer() to reflect that probing is no longer
limited to AP clients and may be used for any connected peer.
Extend cfg80211_probe_status() to include peer and link_id so drivers can
report per-link results.
Update nl80211 handler to NL80211_CMD_PROBE_PEER with AP/GO requiring MAC
and STA/P2P-client omitting MAC (AP implied), and gate STA-mode probing via
NL80211_EXT_FEATURE_PROBE_AP.
Update in-tree users (wil6210, mwifiex) and mac80211 so the tree continues
to build after this change.
mac80211 switches cfg80211_ops to .probe_peer and passes link_id = -1
at the probe status callsite to preserve existing behavior.
This change is otherwise behavior-neutral, per-link STA reporting will
follow in a subsequent patch.
Signed-off-by: Priyansha Tiwari <priyansha.tiwari@oss.qualcomm.com>
---
drivers/net/wireless/ath/wil6210/cfg80211.c | 11 +--
.../net/wireless/marvell/mwifiex/cfg80211.c | 8 +-
include/net/cfg80211.h | 16 ++--
net/mac80211/cfg.c | 6 +-
net/mac80211/status.c | 2 +-
net/wireless/nl80211.c | 87 +++++++++++++------
net/wireless/rdev-ops.h | 10 +--
net/wireless/trace.h | 2 +-
8 files changed, 89 insertions(+), 53 deletions(-)
diff --git a/drivers/net/wireless/ath/wil6210/cfg80211.c b/drivers/net/wireless/ath/wil6210/cfg80211.c
index 3d6e5aad48b1..7d04594e974f 100644
--- a/drivers/net/wireless/ath/wil6210/cfg80211.c
+++ b/drivers/net/wireless/ath/wil6210/cfg80211.c
@@ -2,6 +2,7 @@
/*
* Copyright (c) 2012-2017 Qualcomm Atheros, Inc.
* Copyright (c) 2018-2019, The Linux Foundation. All rights reserved.
+ * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
*/
#include <linux/etherdevice.h>
@@ -2325,7 +2326,7 @@ static void wil_probe_client_handle(struct wil6210_priv *wil,
*/
bool alive = (sta->status == wil_sta_connected);
- cfg80211_probe_status(ndev, sta->addr, req->cookie, alive,
+ cfg80211_probe_status(ndev, sta->addr, req->cookie, -1, alive,
0, false, GFP_KERNEL);
}
@@ -2378,9 +2379,9 @@ void wil_probe_client_flush(struct wil6210_vif *vif)
mutex_unlock(&vif->probe_client_mutex);
}
-static int wil_cfg80211_probe_client(struct wiphy *wiphy,
- struct net_device *dev,
- const u8 *peer, u64 *cookie)
+static int wil_cfg80211_probe_peer(struct wiphy *wiphy,
+ struct net_device *dev,
+ const u8 *peer, u64 *cookie)
{
struct wil6210_priv *wil = wiphy_to_wil(wiphy);
struct wil6210_vif *vif = ndev_to_vif(dev);
@@ -2659,7 +2660,7 @@ static const struct cfg80211_ops wil_cfg80211_ops = {
.add_station = wil_cfg80211_add_station,
.del_station = wil_cfg80211_del_station,
.change_station = wil_cfg80211_change_station,
- .probe_client = wil_cfg80211_probe_client,
+ .probe_peer = wil_cfg80211_probe_peer,
.change_bss = wil_cfg80211_change_bss,
/* P2P device */
.start_p2p_device = wil_cfg80211_start_p2p_device,
diff --git a/drivers/net/wireless/marvell/mwifiex/cfg80211.c b/drivers/net/wireless/marvell/mwifiex/cfg80211.c
index c9a651bdf882..dae10236f460 100644
--- a/drivers/net/wireless/marvell/mwifiex/cfg80211.c
+++ b/drivers/net/wireless/marvell/mwifiex/cfg80211.c
@@ -4557,9 +4557,9 @@ mwifiex_cfg80211_disassociate(struct wiphy *wiphy,
}
static int
-mwifiex_cfg80211_probe_client(struct wiphy *wiphy,
- struct net_device *dev, const u8 *peer,
- u64 *cookie)
+mwifiex_cfg80211_probe_peer(struct wiphy *wiphy,
+ struct net_device *dev, const u8 *peer,
+ u64 *cookie)
{
/* hostapd looks for NL80211_CMD_PROBE_CLIENT support; otherwise,
* it requires monitor-mode support (which mwifiex doesn't support).
@@ -4725,7 +4725,7 @@ int mwifiex_register_cfg80211(struct mwifiex_adapter *adapter)
ops->disassoc = mwifiex_cfg80211_disassociate;
ops->disconnect = NULL;
ops->connect = NULL;
- ops->probe_client = mwifiex_cfg80211_probe_client;
+ ops->probe_peer = mwifiex_cfg80211_probe_peer;
}
wiphy->max_scan_ssids = MWIFIEX_MAX_SSID_LIST_LENGTH;
wiphy->max_scan_ie_len = MWIFIEX_MAX_VSIE_LEN;
diff --git a/include/net/cfg80211.h b/include/net/cfg80211.h
index 9d3639ff9c28..6675c6021fa5 100644
--- a/include/net/cfg80211.h
+++ b/include/net/cfg80211.h
@@ -4921,8 +4921,8 @@ struct mgmt_frame_regs {
* @tdls_mgmt: Transmit a TDLS management frame.
* @tdls_oper: Perform a high-level TDLS operation (e.g. TDLS link setup).
*
- * @probe_client: probe an associated client, must return a cookie that it
- * later passes to cfg80211_probe_status().
+ * @probe_peer: probe a connected peer (AP: STA MAC required; STA: no MAC),
+ * must return a cookie that is later passed to cfg80211_probe_status().
*
* @set_noack_map: Set the NoAck Map for the TIDs.
*
@@ -5320,8 +5320,8 @@ struct cfg80211_ops {
int (*tdls_oper)(struct wiphy *wiphy, struct net_device *dev,
const u8 *peer, enum nl80211_tdls_operation oper);
- int (*probe_client)(struct wiphy *wiphy, struct net_device *dev,
- const u8 *peer, u64 *cookie);
+ int (*probe_peer)(struct wiphy *wiphy, struct net_device *dev,
+ const u8 *peer, u64 *cookie);
int (*set_noack_map)(struct wiphy *wiphy,
struct net_device *dev,
@@ -9602,15 +9602,17 @@ bool cfg80211_rx_unexpected_4addr_frame(struct net_device *dev, const u8 *addr,
/**
* cfg80211_probe_status - notify userspace about probe status
* @dev: the device the probe was sent on
- * @addr: the address of the peer
+ * @peer: The peer MAC address (or MLD address for MLO) or %NULL if not
+ * applicable (e.g. for STA/P2P-client)
* @cookie: the cookie filled in @probe_client previously
+ * @link_id: The link ID on which the probe was sent (or -1 for non-MLO)
* @acked: indicates whether probe was acked or not
* @ack_signal: signal strength (in dBm) of the ACK frame.
* @is_valid_ack_signal: indicates the ack_signal is valid or not.
* @gfp: allocation flags
*/
-void cfg80211_probe_status(struct net_device *dev, const u8 *addr,
- u64 cookie, bool acked, s32 ack_signal,
+void cfg80211_probe_status(struct net_device *dev, const u8 *peer, u64 cookie,
+ int link_id, bool acked, s32 ack_signal,
bool is_valid_ack_signal, gfp_t gfp);
/**
diff --git a/net/mac80211/cfg.c b/net/mac80211/cfg.c
index 7b77d57c9f96..abd0ac20f0da 100644
--- a/net/mac80211/cfg.c
+++ b/net/mac80211/cfg.c
@@ -4746,8 +4746,8 @@ static int ieee80211_set_rekey_data(struct wiphy *wiphy,
return 0;
}
-static int ieee80211_probe_client(struct wiphy *wiphy, struct net_device *dev,
- const u8 *peer, u64 *cookie)
+static int ieee80211_probe_peer(struct wiphy *wiphy, struct net_device *dev,
+ const u8 *peer, u64 *cookie)
{
struct ieee80211_sub_if_data *sdata = IEEE80211_DEV_TO_SUB_IF(dev);
struct ieee80211_local *local = sdata->local;
@@ -5835,7 +5835,7 @@ const struct cfg80211_ops mac80211_config_ops = {
.tdls_mgmt = ieee80211_tdls_mgmt,
.tdls_channel_switch = ieee80211_tdls_channel_switch,
.tdls_cancel_channel_switch = ieee80211_tdls_cancel_channel_switch,
- .probe_client = ieee80211_probe_client,
+ .probe_peer = ieee80211_probe_peer,
.set_noack_map = ieee80211_set_noack_map,
#ifdef CONFIG_PM
.set_wakeup = ieee80211_set_wakeup,
diff --git a/net/mac80211/status.c b/net/mac80211/status.c
index 4b38aa0e902a..4a64ac6d1451 100644
--- a/net/mac80211/status.c
+++ b/net/mac80211/status.c
@@ -654,7 +654,7 @@ 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, acked,
+ cookie, -1, acked,
info->status.ack_signal,
is_valid_ack_signal,
GFP_ATOMIC);
diff --git a/net/wireless/nl80211.c b/net/wireless/nl80211.c
index e7be43f023b6..a7623200d9cb 100644
--- a/net/wireless/nl80211.c
+++ b/net/wireless/nl80211.c
@@ -2400,7 +2400,7 @@ static int nl80211_add_commands_unsplit(struct cfg80211_registered_device *rdev,
}
if (rdev->wiphy.max_sched_scan_reqs)
CMD(sched_scan_start, START_SCHED_SCAN);
- CMD(probe_client, PROBE_CLIENT);
+ CMD(probe_peer, PROBE_PEER);
CMD(set_noack_map, SET_NOACK_MAP);
if (rdev->wiphy.flags & WIPHY_FLAG_REPORTS_OBSS) {
i++;
@@ -15790,26 +15790,44 @@ static int nl80211_register_unexpected_frame(struct sk_buff *skb,
return 0;
}
-static int nl80211_probe_client(struct sk_buff *skb,
- struct genl_info *info)
+static int nl80211_probe_peer(struct sk_buff *skb, struct genl_info *info)
{
struct cfg80211_registered_device *rdev = info->user_ptr[0];
struct net_device *dev = info->user_ptr[1];
struct wireless_dev *wdev = dev->ieee80211_ptr;
struct sk_buff *msg;
void *hdr;
- const u8 *addr;
+ const u8 *addr = NULL;
u64 cookie;
int err;
- if (wdev->iftype != NL80211_IFTYPE_AP &&
- wdev->iftype != NL80211_IFTYPE_P2P_GO)
- return -EOPNOTSUPP;
+ /* Allow in AP, STA, and their P2P counterparts (and MLO) */
+ switch (wdev->iftype) {
+ case NL80211_IFTYPE_AP:
+ case NL80211_IFTYPE_P2P_GO:
+ if (!info->attrs[NL80211_ATTR_MAC])
+ return -EINVAL;
- if (!info->attrs[NL80211_ATTR_MAC])
- return -EINVAL;
+ addr = nla_data(info->attrs[NL80211_ATTR_MAC]);
+ break;
+ case NL80211_IFTYPE_STATION:
+ case NL80211_IFTYPE_P2P_CLIENT:
+ if (!wiphy_ext_feature_isset(&rdev->wiphy,
+ NL80211_EXT_FEATURE_PROBE_AP))
+ return -EOPNOTSUPP;
- if (!rdev->ops->probe_client)
+ if (!wdev->connected)
+ return -ENOLINK;
+
+ /* STA/P2P-client probes the currently associated AP/GO. */
+ if (info->attrs[NL80211_ATTR_MAC])
+ return -EINVAL;
+ break;
+ default:
+ return -EOPNOTSUPP;
+ }
+
+ if (!rdev->ops->probe_peer)
return -EOPNOTSUPP;
msg = nlmsg_new(NLMSG_DEFAULT_SIZE, GFP_KERNEL);
@@ -15823,9 +15841,8 @@ static int nl80211_probe_client(struct sk_buff *skb,
goto free_msg;
}
- addr = nla_data(info->attrs[NL80211_ATTR_MAC]);
- err = rdev_probe_client(rdev, dev, addr, &cookie);
+ err = rdev_probe_peer(rdev, dev, addr, &cookie);
if (err)
goto free_msg;
@@ -19629,7 +19646,7 @@ static const struct genl_small_ops nl80211_small_ops[] = {
{
.cmd = NL80211_CMD_PROBE_PEER,
.validate = GENL_DONT_VALIDATE_STRICT | GENL_DONT_VALIDATE_DUMP,
- .doit = nl80211_probe_client,
+ .doit = nl80211_probe_peer,
.flags = GENL_UNS_ADMIN_PERM,
.internal_flags = IFLAGS(NL80211_FLAG_NEED_NETDEV_UP),
},
@@ -22165,8 +22182,8 @@ void cfg80211_sta_opmode_change_notify(struct net_device *dev, const u8 *mac,
}
EXPORT_SYMBOL(cfg80211_sta_opmode_change_notify);
-void cfg80211_probe_status(struct net_device *dev, const u8 *addr,
- u64 cookie, bool acked, s32 ack_signal,
+void cfg80211_probe_status(struct net_device *dev, const u8 *peer, u64 cookie,
+ int link_id, bool acked, s32 ack_signal,
bool is_valid_ack_signal, gfp_t gfp)
{
struct wireless_dev *wdev = dev->ieee80211_ptr;
@@ -22174,31 +22191,47 @@ void cfg80211_probe_status(struct net_device *dev, const u8 *addr,
struct sk_buff *msg;
void *hdr;
- trace_cfg80211_probe_status(dev, addr, cookie, acked);
+ trace_cfg80211_probe_status(dev, peer, cookie, acked);
- msg = nlmsg_new(NLMSG_DEFAULT_SIZE, gfp);
+ switch (wdev->iftype) {
+ case NL80211_IFTYPE_STATION:
+ case NL80211_IFTYPE_P2P_CLIENT:
+ if (WARN_ON(peer))
+ return;
+ break;
+ case NL80211_IFTYPE_AP:
+ case NL80211_IFTYPE_P2P_GO:
+ if (WARN_ON(!peer))
+ return;
+ break;
+ default:
+ break;
+ }
+ msg = nlmsg_new(NLMSG_DEFAULT_SIZE, gfp);
if (!msg)
return;
hdr = nl80211hdr_put(msg, 0, 0, 0, NL80211_CMD_PROBE_PEER);
- if (!hdr) {
- nlmsg_free(msg);
- return;
- }
+ if (!hdr)
+ goto nla_put_failure;
if (nla_put_u32(msg, NL80211_ATTR_WIPHY, rdev->wiphy_idx) ||
nla_put_u32(msg, NL80211_ATTR_IFINDEX, dev->ifindex) ||
- nla_put(msg, NL80211_ATTR_MAC, ETH_ALEN, addr) ||
+ (peer && nla_put(msg, NL80211_ATTR_MAC, ETH_ALEN, peer)) ||
nla_put_u64_64bit(msg, NL80211_ATTR_COOKIE, cookie,
- NL80211_ATTR_PAD) ||
- (acked && nla_put_flag(msg, NL80211_ATTR_ACK)) ||
- (is_valid_ack_signal && nla_put_s32(msg, NL80211_ATTR_ACK_SIGNAL,
- ack_signal)))
+ NL80211_ATTR_PAD))
goto nla_put_failure;
- genlmsg_end(msg, hdr);
+ if (link_id >= 0 &&
+ nla_put_u8(msg, NL80211_ATTR_MLO_LINK_ID, link_id))
+ goto nla_put_failure;
+ if ((acked && nla_put_flag(msg, NL80211_ATTR_ACK)) ||
+ (is_valid_ack_signal &&
+ nla_put_s32(msg, NL80211_ATTR_ACK_SIGNAL, ack_signal)))
+ goto nla_put_failure;
+ genlmsg_end(msg, hdr);
genlmsg_multicast_netns(&nl80211_fam, wiphy_net(&rdev->wiphy), msg, 0,
NL80211_MCGRP_MLME, gfp);
return;
diff --git a/net/wireless/rdev-ops.h b/net/wireless/rdev-ops.h
index bba239a068f6..f821411e6721 100644
--- a/net/wireless/rdev-ops.h
+++ b/net/wireless/rdev-ops.h
@@ -947,13 +947,13 @@ static inline int rdev_tdls_oper(struct cfg80211_registered_device *rdev,
return ret;
}
-static inline int rdev_probe_client(struct cfg80211_registered_device *rdev,
- struct net_device *dev, const u8 *peer,
- u64 *cookie)
+static inline int rdev_probe_peer(struct cfg80211_registered_device *rdev,
+ struct net_device *dev, const u8 *peer,
+ u64 *cookie)
{
int ret;
- trace_rdev_probe_client(&rdev->wiphy, dev, peer);
- ret = rdev->ops->probe_client(&rdev->wiphy, dev, peer, cookie);
+ trace_rdev_probe_peer(&rdev->wiphy, dev, peer);
+ ret = rdev->ops->probe_peer(&rdev->wiphy, dev, peer, cookie);
trace_rdev_return_int_cookie(&rdev->wiphy, ret, *cookie);
return ret;
}
diff --git a/net/wireless/trace.h b/net/wireless/trace.h
index eb5bedf9c92a..9635f53fa358 100644
--- a/net/wireless/trace.h
+++ b/net/wireless/trace.h
@@ -2122,7 +2122,7 @@ DECLARE_EVENT_CLASS(rdev_pmksa,
WIPHY_PR_ARG, NETDEV_PR_ARG, __entry->bssid)
);
-TRACE_EVENT(rdev_probe_client,
+TRACE_EVENT(rdev_probe_peer,
TP_PROTO(struct wiphy *wiphy, struct net_device *netdev,
const u8 *peer),
TP_ARGS(wiphy, netdev, peer),
--
2.34.1
^ permalink raw reply related [flat|nested] 8+ messages in thread* [PATCH wireless-next v1 3/4] wifi: mac80211: add per-link PROBE_PEER support
2026-04-15 9:43 [PATCH wireless-next v1 0/4] wifi: nl80211: introduce PROBE_PEER for AP and STA with MLO support Priyansha Tiwari
2026-04-15 9:43 ` [PATCH wireless-next v1 1/4] wifi: nl80211: rename PROBE_CLIENT to PROBE_PEER and add STA-side probing support Priyansha Tiwari
2026-04-15 9:43 ` [PATCH wireless-next v1 2/4] wifi: cfg80211/nl80211: rename to probe_peer(), extend probe status, and update in-tree users Priyansha Tiwari
@ 2026-04-15 9:43 ` Priyansha Tiwari
2026-04-15 9:43 ` [PATCH wireless-next v1 4/4] wifi: mac80211_hwsim: report TX status link_id Priyansha Tiwari
3 siblings, 0 replies; 8+ messages in thread
From: Priyansha Tiwari @ 2026-04-15 9:43 UTC (permalink / raw)
To: johannes; +Cc: linux-wireless, quic_drohan
From: Priyansha Tiwari <priyansha.tiwari@oss.qualcomm.com>
Extend ieee80211_probe_peer() to support STA/P2P-client mode. Reject the
request if a peer MAC address is provided or if the interface is not
associated.
For MLO STA/P2P-client, construct the nullfunc using MLD addresses and
set IEEE80211_LINK_UNSPECIFIED in the TX control flags so the driver can
choose the link. For non-MLO STA/P2P-client, use link 0 together with
the corresponding per-link BSSID and link address.
In the TX status path, report probe status for STA/P2P-client mode
without a peer address and include the transmitted link in
cfg80211_probe_status(). Add link_valid/link_id bitfields to
struct ieee80211_tx_info.status so drivers can report the link used
for the completed TX.
Signed-off-by: Priyansha Tiwari <priyansha.tiwari@oss.qualcomm.com>
---
include/net/mac80211.h | 2 +-
net/mac80211/cfg.c | 193 ++++++++++++++++++++++++++++-------------
net/mac80211/status.c | 30 ++++++-
3 files changed, 158 insertions(+), 67 deletions(-)
diff --git a/include/net/mac80211.h b/include/net/mac80211.h
index 40cb20d9309c..f93d15c61a52 100644
--- a/include/net/mac80211.h
+++ b/include/net/mac80211.h
@@ -1364,7 +1364,7 @@ struct ieee80211_tx_info {
u8 pad;
u16 tx_time;
u8 flags;
- u8 pad2;
+ u8 link_valid:1, link_id:4, pad2:3;
void *status_driver_data[16 / sizeof(void *)];
} status;
struct {
diff --git a/net/mac80211/cfg.c b/net/mac80211/cfg.c
index abd0ac20f0da..f5b3c9bd67f8 100644
--- a/net/mac80211/cfg.c
+++ b/net/mac80211/cfg.c
@@ -4753,101 +4753,174 @@ 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;
+ 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 *peer_addr = peer;
+ 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;
+ /* STA/P2P: userspace must NOT provide peer MAC, AP is implied. */
+ if (sdata->vif.type == NL80211_IFTYPE_STATION ||
+ sdata->vif.type == NL80211_IFTYPE_P2P_CLIENT) {
+ if (peer)
+ return -EINVAL;
+
+ if (!sdata->u.mgd.associated)
+ return -ENOTCONN;
+ } else if (!peer) {
+ /* AP/GO: must have a peer MAC. */
+ return -EINVAL;
}
- qos = sta->sta.wme;
+ guard(rcu)();
- if (ieee80211_vif_is_mld(&sdata->vif)) {
- if (sta->sta.mlo) {
- link_id = IEEE80211_LINK_UNSPECIFIED;
- } else {
- /*
- * For non-MLO clients connected to an AP MLD, band
- * information is not used; instead, sta->deflink is
- * used to send packets.
- */
- link_id = sta->deflink.link_id;
+ if (sdata->vif.type == NL80211_IFTYPE_AP ||
+ sdata->vif.type == NL80211_IFTYPE_P2P_GO) {
+ sta = sta_info_get_bss(sdata, peer);
+ if (!sta)
+ return -ENOLINK;
- conf = rcu_dereference(sdata->vif.link_conf[link_id]);
+ qos = sta->sta.wme;
- if (unlikely(!conf)) {
- ret = -ENOLINK;
- goto unlock;
+ if (ieee80211_vif_is_mld(&sdata->vif)) {
+ if (sta->sta.mlo) {
+ link_id = IEEE80211_LINK_UNSPECIFIED;
+ } else {
+ /*
+ * For non-MLO clients connected to an AP MLD,
+ * use the 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))
+ return -ENOLINK;
}
+ /* 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))
+ return -EINVAL;
+ band = chanctx_conf->def.chan->band;
+ link_id = 0;
+ }
+
+ size = sizeof(*nullfunc);
+ fc = cpu_to_le16(IEEE80211_FTYPE_DATA |
+ (qos ? IEEE80211_STYPE_QOS_NULLFUNC
+ : IEEE80211_STYPE_NULLFUNC) |
+ IEEE80211_FCTL_FROMDS);
+ if (!qos)
+ size -= 2;
+
+ skb = dev_alloc_skb(local->hw.extra_tx_headroom + size);
+ if (!skb)
+ return -ENOMEM;
+
+ skb->dev = dev;
+ skb_reserve(skb, local->hw.extra_tx_headroom);
+
+ nullfunc = skb_put(skb, size);
+ memset(nullfunc, 0, size);
+ nullfunc->frame_control = fc;
+
+ 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);
+ }
+
+ 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);
+ skb->priority = 7;
+ if (qos)
+ nullfunc->qos_ctrl = cpu_to_le16(7);
+
+ ret = ieee80211_attach_ack_skb(local, skb, cookie, GFP_ATOMIC);
+ if (ret) {
+ kfree_skb(skb);
+ return ret;
}
- /* MLD transmissions must not rely on the band */
+
+ local_bh_disable();
+ ieee80211_xmit(sdata, sta, skb);
+ local_bh_enable();
+
+ return 0;
+ }
+
+ /*
+ * STA/P2P: send a nullfunc to probe the AP/peer.
+ * For MLO, let the driver/firmware decide which link to use.
+ */
+ if (ieee80211_vif_is_mld(&sdata->vif)) {
+ link_id = IEEE80211_LINK_UNSPECIFIED;
+ peer_addr = sdata->vif.cfg.ap_addr;
+ src_addr = sdata->vif.addr;
band = 0;
+ sta = sta_info_get(sdata, sdata->vif.cfg.ap_addr);
} else {
- chanctx_conf = rcu_dereference(sdata->vif.bss_conf.chanctx_conf);
- if (WARN_ON(!chanctx_conf)) {
- ret = -EINVAL;
- goto unlock;
- }
- band = chanctx_conf->def.chan->band;
link_id = 0;
+ conf = rcu_dereference(sdata->vif.link_conf[0]);
+ if (!conf)
+ return -ENOLINK;
+ band = conf->chanreq.oper.chan->band;
+ peer_addr = conf->bssid;
+ src_addr = conf->addr;
+ sta = sta_info_get_bss(sdata, peer_addr);
}
- if (qos) {
- fc = cpu_to_le16(IEEE80211_FTYPE_DATA |
- IEEE80211_STYPE_QOS_NULLFUNC |
- IEEE80211_FCTL_FROMDS);
- } else {
+ qos = sta ? sta->sta.wme : false;
+
+ size = sizeof(*nullfunc);
+ fc = cpu_to_le16(IEEE80211_FTYPE_DATA |
+ (qos ? IEEE80211_STYPE_QOS_NULLFUNC
+ : IEEE80211_STYPE_NULLFUNC) |
+ 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);
+ memset(nullfunc, 0, 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;
- info = IEEE80211_SKB_CB(skb);
+ memcpy(nullfunc->addr1, peer_addr, ETH_ALEN);
+ memcpy(nullfunc->addr2, src_addr, ETH_ALEN);
+ memcpy(nullfunc->addr3, peer_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);
skb->priority = 7;
if (qos)
@@ -4856,18 +4929,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 4a64ac6d1451..e049ab946e7b 100644
--- a/net/mac80211/status.c
+++ b/net/mac80211/status.c
@@ -622,7 +622,8 @@ static void ieee80211_report_ack_skb(struct ieee80211_local *local,
return;
if (info->flags & IEEE80211_TX_INTFL_NL80211_FRAME_TX) {
- u64 cookie = IEEE80211_SKB_CB(skb)->ack.cookie;
+ struct ieee80211_tx_info *skb_info = IEEE80211_SKB_CB(skb);
+ u64 cookie = skb_info->ack.cookie;
struct ieee80211_sub_if_data *sdata;
struct ieee80211_hdr *hdr = (void *)skb->data;
bool is_valid_ack_signal =
@@ -652,12 +653,33 @@ static void ieee80211_report_ack_skb(struct ieee80211_local *local,
skb->len,
acked,
GFP_ATOMIC);
- else if (ieee80211_is_any_nullfunc(hdr->frame_control))
- cfg80211_probe_status(sdata->dev, hdr->addr1,
- cookie, -1, acked,
+ else if (ieee80211_is_any_nullfunc(hdr->frame_control)) {
+ const u8 *peer_addr;
+ int link_id = -1;
+
+ /*
+ * STA/P2P client: peer_addr omitted;
+ * AP/GO: report RA
+ */
+ if (sdata->vif.type ==
+ NL80211_IFTYPE_STATION ||
+ sdata->vif.type ==
+ NL80211_IFTYPE_P2P_CLIENT) {
+ peer_addr = NULL;
+
+ if (ieee80211_vif_is_mld(&sdata->vif) &&
+ info->status.link_valid)
+ link_id = info->status.link_id;
+ } else {
+ peer_addr = hdr->addr1;
+ }
+
+ cfg80211_probe_status(sdata->dev, peer_addr,
+ cookie, link_id, acked,
info->status.ack_signal,
is_valid_ack_signal,
GFP_ATOMIC);
+ }
else if (ieee80211_is_mgmt(hdr->frame_control))
cfg80211_mgmt_tx_status_ext(&sdata->wdev,
&status,
--
2.34.1
^ permalink raw reply related [flat|nested] 8+ messages in thread