Linux wireless drivers development
 help / color / mirror / Atom feed
* [PATCH] wifi: rtw89: usb: fix TX flow control by tracking in-flight URBs
From: Lucid Duck @ 2026-01-25 22:19 UTC (permalink / raw)
  To: linux-wireless; +Cc: Ping-Ke Shih, Lucid Duck

rtw89_usb_ops_check_and_reclaim_tx_resource() currently returns a
hardcoded placeholder value of 42, violating mac80211's TX flow control
contract. This causes uncontrolled URB accumulation under sustained TX
load since mac80211 believes resources are always available.

Fix this by implementing proper TX backpressure:

- Add per-channel atomic counters (tx_inflight[]) to track URBs between
  submission and completion
- Increment counter before usb_submit_urb() with rollback on failure
- Decrement counter in completion callback
- Return available slots (max - inflight) to mac80211, or 0 at capacity
- Exclude firmware command channel (CH12) from flow control

Tested on D-Link DWA-X1850 (RTL8832AU) with:
- Sustained high-throughput traffic
- Module load/unload stress tests
- Hot-unplug during active transmission
- 30-minute soak test verifying counters balance at idle

Signed-off-by: Lucid Duck <lucid_duck@justthetip.ca>
---
 drivers/net/wireless/realtek/rtw89/usb.c | 27 ++++++++++++++++++++++--
 drivers/net/wireless/realtek/rtw89/usb.h |  6 ++++++
 2 files changed, 31 insertions(+), 2 deletions(-)

diff --git a/drivers/net/wireless/realtek/rtw89/usb.c b/drivers/net/wireless/realtek/rtw89/usb.c
index e77561a4d..6fcf32603 100644
--- a/drivers/net/wireless/realtek/rtw89/usb.c
+++ b/drivers/net/wireless/realtek/rtw89/usb.c
@@ -161,16 +161,25 @@ static u32
 rtw89_usb_ops_check_and_reclaim_tx_resource(struct rtw89_dev *rtwdev,
 					    u8 txch)
 {
+	struct rtw89_usb *rtwusb = rtw89_usb_priv(rtwdev);
+	int inflight;
+
+	/* Firmware command channel is not flow-controlled */
 	if (txch == RTW89_TXCH_CH12)
 		return 1;
 
-	return 42; /* TODO some kind of calculation? */
+	inflight = atomic_read(&rtwusb->tx_inflight[txch]);
+	if (inflight >= RTW89_USB_MAX_TX_URBS_PER_CH)
+		return 0;
+
+	return RTW89_USB_MAX_TX_URBS_PER_CH - inflight;
 }
 
 static void rtw89_usb_write_port_complete(struct urb *urb)
 {
 	struct rtw89_usb_tx_ctrl_block *txcb = urb->context;
 	struct rtw89_dev *rtwdev = txcb->rtwdev;
+	struct rtw89_usb *rtwusb = rtw89_usb_priv(rtwdev);
 	struct ieee80211_tx_info *info;
 	struct rtw89_txwd_body *txdesc;
 	struct sk_buff *skb;
@@ -229,6 +238,10 @@ static void rtw89_usb_write_port_complete(struct urb *urb)
 		break;
 	}
 
+	/* Decrement in-flight counter (skip firmware command channel) */
+	if (txcb->txch != RTW89_TXCH_CH12)
+		atomic_dec(&rtwusb->tx_inflight[txcb->txch]);
+
 	kfree(txcb);
 }
 
@@ -306,9 +319,17 @@ static void rtw89_usb_ops_tx_kick_off(struct rtw89_dev *rtwdev, u8 txch)
 
 		skb_queue_tail(&txcb->tx_ack_queue, skb);
 
+		/* Increment BEFORE submit to avoid race with completion */
+		if (txch != RTW89_TXCH_CH12)
+			atomic_inc(&rtwusb->tx_inflight[txch]);
+
 		ret = rtw89_usb_write_port(rtwdev, txch, skb->data, skb->len,
 					   txcb);
 		if (ret) {
+			/* Rollback increment on failure */
+			if (txch != RTW89_TXCH_CH12)
+				atomic_dec(&rtwusb->tx_inflight[txch]);
+
 			if (ret != -ENODEV)
 				rtw89_err(rtwdev, "write port txch %d failed: %d\n",
 					  txch, ret);
@@ -666,8 +687,10 @@ static void rtw89_usb_init_tx(struct rtw89_dev *rtwdev)
 	struct rtw89_usb *rtwusb = rtw89_usb_priv(rtwdev);
 	int i;
 
-	for (i = 0; i < ARRAY_SIZE(rtwusb->tx_queue); i++)
+	for (i = 0; i < ARRAY_SIZE(rtwusb->tx_queue); i++) {
 		skb_queue_head_init(&rtwusb->tx_queue[i]);
+		atomic_set(&rtwusb->tx_inflight[i], 0);
+	}
 }
 
 static void rtw89_usb_deinit_tx(struct rtw89_dev *rtwdev)
diff --git a/drivers/net/wireless/realtek/rtw89/usb.h b/drivers/net/wireless/realtek/rtw89/usb.h
index 203ec8e99..f72a8b1b2 100644
--- a/drivers/net/wireless/realtek/rtw89/usb.h
+++ b/drivers/net/wireless/realtek/rtw89/usb.h
@@ -20,6 +20,9 @@
 #define RTW89_MAX_ENDPOINT_NUM		9
 #define RTW89_MAX_BULKOUT_NUM		7
 
+/* TX flow control: max in-flight URBs per channel */
+#define RTW89_USB_MAX_TX_URBS_PER_CH	32
+
 struct rtw89_usb_info {
 	u32 usb_host_request_2;
 	u32 usb_wlan0_1;
@@ -63,6 +66,9 @@ struct rtw89_usb {
 	struct usb_anchor tx_submitted;
 
 	struct sk_buff_head tx_queue[RTW89_TXCH_NUM];
+
+	/* TX flow control: track in-flight URBs per channel */
+	atomic_t tx_inflight[RTW89_TXCH_NUM];
 };
 
 static inline struct rtw89_usb *rtw89_usb_priv(struct rtw89_dev *rtwdev)
-- 
2.52.0


^ permalink raw reply related

* [PATCH] wireless-regdb: Port RSA signing to cryptography lib
From: Bastian Germann @ 2026-01-25 21:26 UTC (permalink / raw)
  To: linux-wireless; +Cc: Bastian Germann, wireless-regdb

Port the RSA signing from the deprecated M2Crypto library to the
cryptography library.

M2Crypto is no longer actively maintained. The cryptography library is
the recommended replacement, offering better maintenance.

Remove unused hashlib import.

Signed-off-by: Bastian Germann <bage@debian.org>
---
 db2bin.py | 25 ++++++++++++++++---------
 1 file changed, 16 insertions(+), 9 deletions(-)

diff --git a/db2bin.py b/db2bin.py
index 29ae313..a4fa3e5 100755
--- a/db2bin.py
+++ b/db2bin.py
@@ -2,7 +2,6 @@
 
 from io import BytesIO, open
 import struct
-import hashlib
 from dbparse import DBParser
 import sys
 
@@ -125,19 +124,27 @@ if len(sys.argv) > 3:
     # Load RSA only now so people can use this script
     # without having those libraries installed to verify
     # their SQL changes
-    from M2Crypto import RSA
+    from cryptography.hazmat.primitives import hashes, serialization
+    from cryptography.hazmat.primitives.asymmetric import padding
+
+    # load the private key
+    with open(sys.argv[3], 'rb') as key_file:
+        key = serialization.load_pem_private_key(key_file.read(), password=None)
 
     # determine signature length
-    key = RSA.load_key(sys.argv[3])
-    hash = hashlib.sha1()
-    hash.update(output.getvalue())
-    sig = key.sign(hash.digest())
+    sig = key.sign(
+        output.getvalue(),
+        padding.PKCS1v15(),
+        hashes.SHA1()
+    )
     # write it to file
     siglen.set(len(sig))
     # sign again
-    hash = hashlib.sha1()
-    hash.update(output.getvalue())
-    sig = key.sign(hash.digest())
+    sig = key.sign(
+        output.getvalue(),
+        padding.PKCS1v15(),
+        hashes.SHA1()
+    )
 
     output.write(sig)
 else:

^ permalink raw reply related

* [PATCH] wifi: iwlegacy: add missing mutex protection in il4965_store_tx_power()
From: Ziyi Guo @ 2026-01-25 19:40 UTC (permalink / raw)
  To: Stanislaw Gruszka; +Cc: linux-wireless, linux-kernel, Ziyi Guo

il4965_store_tx_power() calls il_set_tx_power() without holding il->mutex.
However, il_set_tx_power() has lockdep_assert_held(&il->mutex) indicating
that callers must hold this lock.

All other callers of il_set_tx_power() properly acquire the mutex:
- il_bg_scan_completed() acquires mutex at common.c:1683
- il_mac_config() acquires mutex at common.c:5006
- il3945_commit_rxon() and il4965_commit_rxon() are called via work
  queues that hold the mutex (like il4965_bg_alive_start)

Add mutex_lock()/mutex_unlock() around the il_set_tx_power() call in
the sysfs store function to fix the missing lock protection.

Signed-off-by: Ziyi Guo <n7l8m4@u.northwestern.edu>
---
 drivers/net/wireless/intel/iwlegacy/4965-mac.c | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/drivers/net/wireless/intel/iwlegacy/4965-mac.c b/drivers/net/wireless/intel/iwlegacy/4965-mac.c
index 3588dec75ebd..57fa866efd9f 100644
--- a/drivers/net/wireless/intel/iwlegacy/4965-mac.c
+++ b/drivers/net/wireless/intel/iwlegacy/4965-mac.c
@@ -4606,7 +4606,9 @@ il4965_store_tx_power(struct device *d, struct device_attribute *attr,
 	if (ret)
 		IL_INFO("%s is not in decimal form.\n", buf);
 	else {
+		mutex_lock(&il->mutex);
 		ret = il_set_tx_power(il, val, false);
+		mutex_unlock(&il->mutex);
 		if (ret)
 			IL_ERR("failed setting tx power (0x%08x).\n", ret);
 		else
-- 
2.34.1


^ permalink raw reply related

* [PATCH] wifi: iwlegacy: add missing mutex protection in il3945_store_measurement()
From: Ziyi Guo @ 2026-01-25 19:30 UTC (permalink / raw)
  To: Stanislaw Gruszka; +Cc: linux-wireless, linux-kernel, Ziyi Guo

il3945_store_measurement() calls il3945_get_measurement() which internally
calls il_send_cmd_sync() without holding il->mutex. However,
il_send_cmd_sync() has lockdep_assert_held(&il->mutex) indicating that
callers must hold this lock.

Other sysfs store functions in the same file properly acquire the mutex:
- il3945_store_flags() acquires mutex at 3945-mac.c:3110
- il3945_store_filter_flags() acquires mutex at 3945-mac.c:3144

Add mutex_lock()/mutex_unlock() around the il3945_get_measurement() call
in the sysfs store function to fix the missing lock protection.

Signed-off-by: Ziyi Guo <n7l8m4@u.northwestern.edu>
---
 drivers/net/wireless/intel/iwlegacy/3945-mac.c | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/drivers/net/wireless/intel/iwlegacy/3945-mac.c b/drivers/net/wireless/intel/iwlegacy/3945-mac.c
index 104748fcdc33..54991f31c52c 100644
--- a/drivers/net/wireless/intel/iwlegacy/3945-mac.c
+++ b/drivers/net/wireless/intel/iwlegacy/3945-mac.c
@@ -3224,7 +3224,9 @@ il3945_store_measurement(struct device *d, struct device_attribute *attr,
 
 	D_INFO("Invoking measurement of type %d on " "channel %d (for '%s')\n",
 	       type, params.channel, buf);
+	mutex_lock(&il->mutex);
 	il3945_get_measurement(il, &params, type);
+	mutex_unlock(&il->mutex);
 
 	return count;
 }
-- 
2.34.1


^ permalink raw reply related

* [RFC v5 wireless-next 4/4] wifi: mac80211_hwsim: background CAC support
From: Janusz Dziedzic @ 2026-01-25 16:00 UTC (permalink / raw)
  To: linux-wireless; +Cc: johannes, Janusz Dziedzic
In-Reply-To: <20260125160353.34102-1-janusz.dziedzic@gmail.com>

Report background CAC support and add allow
to cancel background CAC and simulate radar.

echo cancel > /sys/kernel/debug/ieee80211/phy2/hwsim/dfs_background_cac
echo radar > /sys/kernel/debug/ieee80211/phy2/hwsim/dfs_background_cac

Signed-off-by: Janusz Dziedzic <janusz.dziedzic@gmail.com>
---
 drivers/net/wireless/virtual/mac80211_hwsim.c | 64 ++++++++++++++++++-
 1 file changed, 63 insertions(+), 1 deletion(-)

diff --git a/drivers/net/wireless/virtual/mac80211_hwsim.c b/drivers/net/wireless/virtual/mac80211_hwsim.c
index 4d9f5f87e814..d88e519008a2 100644
--- a/drivers/net/wireless/virtual/mac80211_hwsim.c
+++ b/drivers/net/wireless/virtual/mac80211_hwsim.c
@@ -715,6 +715,7 @@ struct mac80211_hwsim_data {
 	} ps;
 	bool ps_poll_pending;
 	struct dentry *debugfs;
+	struct cfg80211_chan_def radar_background_chandef;
 
 	atomic_t pending_cookie;
 	struct sk_buff_head pending;	/* packets pending */
@@ -1164,6 +1165,46 @@ static int hwsim_write_simulate_radar(void *dat, u64 val)
 DEFINE_DEBUGFS_ATTRIBUTE(hwsim_simulate_radar, NULL,
 			 hwsim_write_simulate_radar, "%llu\n");
 
+static ssize_t hwsim_background_cac_write(struct file *file,
+					  const char __user *user_buf,
+					  size_t count, loff_t *ppos)
+{
+	struct mac80211_hwsim_data *data = file->private_data;
+	size_t buf_size;
+	char buf[16];
+
+	buf_size = min(count, sizeof(buf) - 1);
+	if (copy_from_user(buf, user_buf, buf_size))
+		return -EFAULT;
+
+	buf[buf_size] = '\0';
+
+	/* Remove trailing newline if present */
+	if (buf_size > 0 && buf[buf_size - 1] == '\n')
+		buf[buf_size - 1] = '\0';
+
+	/* Check if background radar channel is configured */
+	if (!data->radar_background_chandef.chan)
+		return -ENOENT;
+
+	if (strcmp(buf, "radar") == 0)
+		cfg80211_background_radar_event(data->hw->wiphy,
+						&data->radar_background_chandef,
+						GFP_KERNEL);
+	else if (strcmp(buf, "cancel") == 0)
+		cfg80211_background_cac_abort(data->hw->wiphy);
+	else
+		return -EINVAL;
+
+	return count;
+}
+
+static const struct file_operations hwsim_background_cac_ops = {
+	.write = hwsim_background_cac_write,
+	.open = simple_open,
+	.llseek = default_llseek,
+};
+
 static int hwsim_fops_group_read(void *dat, u64 *val)
 {
 	struct mac80211_hwsim_data *data = dat;
@@ -4154,6 +4195,20 @@ static int mac80211_hwsim_change_nan_config(struct ieee80211_hw *hw,
 	return 0;
 }
 
+static int mac80211_hwsim_set_radar_background(struct ieee80211_hw *hw,
+					       struct cfg80211_chan_def *chan)
+{
+	struct mac80211_hwsim_data *data = hw->priv;
+
+	if (chan)
+		data->radar_background_chandef = *chan;
+	else
+		memset(&data->radar_background_chandef, 0,
+		       sizeof(data->radar_background_chandef));
+
+	return 0;
+}
+
 #ifdef CONFIG_MAC80211_DEBUGFS
 #define HWSIM_DEBUGFS_OPS					\
 	.link_add_debugfs = mac80211_hwsim_link_add_debugfs,
@@ -4189,6 +4244,7 @@ static int mac80211_hwsim_change_nan_config(struct ieee80211_hw *hw,
 	.start_nan = mac80211_hwsim_start_nan,                  \
 	.stop_nan = mac80211_hwsim_stop_nan,                    \
 	.nan_change_conf = mac80211_hwsim_change_nan_config,    \
+	.set_radar_background = mac80211_hwsim_set_radar_background, \
 	HWSIM_DEBUGFS_OPS
 
 #define HWSIM_NON_MLO_OPS					\
@@ -5794,6 +5850,8 @@ static int mac80211_hwsim_new_radio(struct genl_info *info,
 
 	wiphy_ext_feature_set(hw->wiphy,
 			      NL80211_EXT_FEATURE_DFS_CONCURRENT);
+	wiphy_ext_feature_set(hw->wiphy,
+			      NL80211_EXT_FEATURE_RADAR_BACKGROUND);
 
 	if (param->no_vif)
 		ieee80211_hw_set(hw, NO_AUTO_VIF);
@@ -5828,10 +5886,14 @@ static int mac80211_hwsim_new_radio(struct genl_info *info,
 			    &hwsim_fops_group);
 	debugfs_create_file("rx_rssi", 0666, data->debugfs, data,
 			    &hwsim_fops_rx_rssi);
-	if (!data->use_chanctx)
+	if (!data->use_chanctx) {
 		debugfs_create_file("dfs_simulate_radar", 0222,
 				    data->debugfs,
 				    data, &hwsim_simulate_radar);
+		debugfs_create_file("dfs_background_cac", 0200,
+				    data->debugfs,
+				    data, &hwsim_background_cac_ops);
+	}
 
 	if (param->pmsr_capa) {
 		data->pmsr_capa = *param->pmsr_capa;
-- 
2.43.0


^ permalink raw reply related

* [RFC v5 wireless-next 3/4] wifi: cfg80211: events, report background radar
From: Janusz Dziedzic @ 2026-01-25 16:00 UTC (permalink / raw)
  To: linux-wireless; +Cc: johannes, Janusz Dziedzic
In-Reply-To: <20260125160353.34102-1-janusz.dziedzic@gmail.com>

In case we report radar event add also information
this is connected with background one, so user mode
application like hostapd, could check it and behave
correctly.

Signed-off-by: Janusz Dziedzic <janusz.dziedzic@gmail.com>
---
 net/wireless/mlme.c    | 6 ++++--
 net/wireless/nl80211.c | 7 +++++++
 2 files changed, 11 insertions(+), 2 deletions(-)

diff --git a/net/wireless/mlme.c b/net/wireless/mlme.c
index cafb39596a40..da3786417713 100644
--- a/net/wireless/mlme.c
+++ b/net/wireless/mlme.c
@@ -1226,11 +1226,12 @@ void cfg80211_background_cac_done_wk(struct work_struct *work)
 	guard(wiphy)(&rdev->wiphy);
 
 	rdev_set_radar_background(rdev, NULL);
-	rdev->background_radar_wdev = NULL;
 
 	__cfg80211_background_cac_event(rdev, rdev->background_radar_wdev,
 					&rdev->background_radar_chandef,
 					NL80211_RADAR_CAC_FINISHED);
+
+	rdev->background_radar_wdev = NULL;
 }
 
 void cfg80211_background_cac_abort_wk(struct work_struct *work)
@@ -1330,11 +1331,12 @@ void cfg80211_stop_background_radar_detection(struct wireless_dev *wdev)
 		return;
 
 	rdev_set_radar_background(rdev, NULL);
-	rdev->background_radar_wdev = NULL; /* Release offchain ownership */
 
 	__cfg80211_background_cac_event(rdev, wdev,
 					&rdev->background_radar_chandef,
 					NL80211_RADAR_CAC_ABORTED);
+
+	rdev->background_radar_wdev = NULL;
 }
 
 int cfg80211_assoc_ml_reconf(struct cfg80211_registered_device *rdev,
diff --git a/net/wireless/nl80211.c b/net/wireless/nl80211.c
index a29419d7256a..84a981153eb9 100644
--- a/net/wireless/nl80211.c
+++ b/net/wireless/nl80211.c
@@ -21127,6 +21127,13 @@ nl80211_radar_notify(struct cfg80211_registered_device *rdev,
 			goto nla_put_failure;
 	}
 
+	if (rdev->background_radar_wdev &&
+	    cfg80211_chandef_identical(&rdev->background_radar_chandef,
+				       chandef)) {
+		if (nla_put_flag(msg, NL80211_ATTR_RADAR_BACKGROUND))
+			goto nla_put_failure;
+	}
+
 	if (nla_put_u32(msg, NL80211_ATTR_RADAR_EVENT, event))
 		goto nla_put_failure;
 
-- 
2.43.0


^ permalink raw reply related

* [RFC v5 wireless-next 2/4] wifi: cfg80211: set and report chandef CAC ongoing
From: Janusz Dziedzic @ 2026-01-25 16:00 UTC (permalink / raw)
  To: linux-wireless; +Cc: johannes, Janusz Dziedzic
In-Reply-To: <20260125160353.34102-1-janusz.dziedzic@gmail.com>

Allow to track and check CAC state from user mode by
simple check phy channels eg. using iw phy1 channels
command.
This is done for regular CAC and background CAC.
It is important for background CAC while we can start
it from any app (eg. iw or hostapd).

Signed-off-by: Janusz Dziedzic <janusz.dziedzic@gmail.com>
---
 include/net/cfg80211.h       |  3 +++
 include/uapi/linux/nl80211.h |  6 ++++++
 net/wireless/chan.c          | 27 +++++++++++++++++++++++++++
 net/wireless/core.h          |  4 ++++
 net/wireless/mlme.c          |  7 +++++++
 net/wireless/nl80211.c       |  7 +++++++
 6 files changed, 54 insertions(+)

diff --git a/include/net/cfg80211.h b/include/net/cfg80211.h
index 6d8e35a0dde4..c35ce6d620df 100644
--- a/include/net/cfg80211.h
+++ b/include/net/cfg80211.h
@@ -188,6 +188,8 @@ enum ieee80211_channel_flags {
  *	on this channel.
  * @dfs_state_entered: timestamp (jiffies) when the dfs state was entered.
  * @dfs_cac_ms: DFS CAC time in milliseconds, this is valid for DFS channels.
+ * @cac_ongoing_time: timestamp (CLOCK_BOOTTIME, nanoseconds) when CAC was
+ *	started on this channel. Zero when CAC is not in progress.
  * @psd: power spectral density (in dBm)
  */
 struct ieee80211_channel {
@@ -205,6 +207,7 @@ struct ieee80211_channel {
 	enum nl80211_dfs_state dfs_state;
 	unsigned long dfs_state_entered;
 	unsigned int dfs_cac_ms;
+	u64 cac_ongoing_time;
 	s8 psd;
 };
 
diff --git a/include/uapi/linux/nl80211.h b/include/uapi/linux/nl80211.h
index b0f050e36fa4..7c23fd1b8ce9 100644
--- a/include/uapi/linux/nl80211.h
+++ b/include/uapi/linux/nl80211.h
@@ -4452,6 +4452,10 @@ enum nl80211_wmm_rule {
  * @NL80211_FREQUENCY_ATTR_S1G_NO_PRIMARY: Channel is not permitted for use
  *	as a primary channel. Does not prevent the channel from existing
  *	as a non-primary subchannel. Only applicable to S1G channels.
+ * @NL80211_FREQUENCY_ATTR_CAC_START_TIME: Channel Availability Check (CAC)
+ *	start time (CLOCK_BOOTTIME, nanoseconds). Only present when CAC is
+ *	currently in progress on this channel.
+ * @NL80211_FREQUENCY_ATTR_PAD: attribute used for padding for 64-bit alignment
  * @NL80211_FREQUENCY_ATTR_MAX: highest frequency attribute number
  *	currently defined
  * @__NL80211_FREQUENCY_ATTR_AFTER_LAST: internal use
@@ -4501,6 +4505,8 @@ enum nl80211_frequency_attr {
 	NL80211_FREQUENCY_ATTR_NO_8MHZ,
 	NL80211_FREQUENCY_ATTR_NO_16MHZ,
 	NL80211_FREQUENCY_ATTR_S1G_NO_PRIMARY,
+	NL80211_FREQUENCY_ATTR_CAC_START_TIME,
+	NL80211_FREQUENCY_ATTR_PAD,
 
 	/* keep last */
 	__NL80211_FREQUENCY_ATTR_AFTER_LAST,
diff --git a/net/wireless/chan.c b/net/wireless/chan.c
index 68221b1ab45e..cd660b6e7a87 100644
--- a/net/wireless/chan.c
+++ b/net/wireless/chan.c
@@ -642,6 +642,33 @@ void cfg80211_set_dfs_state(struct wiphy *wiphy,
 	}
 }
 
+void cfg80211_set_cac_state(struct wiphy *wiphy,
+			    const struct cfg80211_chan_def *chandef,
+			    bool cac_ongoing)
+{
+	struct ieee80211_channel *c;
+	int width;
+	u64 cac_time;
+
+	if (WARN_ON(!cfg80211_chandef_valid(chandef)))
+		return;
+
+	width = cfg80211_chandef_get_width(chandef);
+	if (width < 0)
+		return;
+
+	/* Get the same timestamp for all subchannels */
+	cac_time = cac_ongoing ? ktime_get_boottime_ns() : 0;
+
+	for_each_subchan(chandef, freq, cf) {
+		c = ieee80211_get_channel_khz(wiphy, freq);
+		if (!c)
+			continue;
+
+		c->cac_ongoing_time = cac_time;
+	}
+}
+
 static bool
 cfg80211_dfs_permissive_check_wdev(struct cfg80211_registered_device *rdev,
 				   enum nl80211_iftype iftype,
diff --git a/net/wireless/core.h b/net/wireless/core.h
index 6ac57b7b2615..6cace846d7a3 100644
--- a/net/wireless/core.h
+++ b/net/wireless/core.h
@@ -481,6 +481,10 @@ void cfg80211_set_dfs_state(struct wiphy *wiphy,
 			    const struct cfg80211_chan_def *chandef,
 			    enum nl80211_dfs_state dfs_state);
 
+void cfg80211_set_cac_state(struct wiphy *wiphy,
+			    const struct cfg80211_chan_def *chandef,
+			    bool cac_ongoing);
+
 void cfg80211_dfs_channels_update_work(struct work_struct *work);
 
 void cfg80211_sched_dfs_chan_update(struct cfg80211_registered_device *rdev);
diff --git a/net/wireless/mlme.c b/net/wireless/mlme.c
index 212178d04efa..cafb39596a40 100644
--- a/net/wireless/mlme.c
+++ b/net/wireless/mlme.c
@@ -1162,9 +1162,11 @@ void cfg80211_cac_event(struct net_device *netdev,
 		fallthrough;
 	case NL80211_RADAR_CAC_ABORTED:
 		wdev->links[link_id].cac_started = false;
+		cfg80211_set_cac_state(wiphy, chandef, false);
 		break;
 	case NL80211_RADAR_CAC_STARTED:
 		wdev->links[link_id].cac_started = true;
+		cfg80211_set_cac_state(wiphy, chandef, true);
 		break;
 	default:
 		WARN_ON(1);
@@ -1192,15 +1194,18 @@ __cfg80211_background_cac_event(struct cfg80211_registered_device *rdev,
 	switch (event) {
 	case NL80211_RADAR_CAC_FINISHED:
 		cfg80211_set_dfs_state(wiphy, chandef, NL80211_DFS_AVAILABLE);
+		cfg80211_set_cac_state(wiphy, chandef, false);
 		memcpy(&rdev->cac_done_chandef, chandef, sizeof(*chandef));
 		queue_work(cfg80211_wq, &rdev->propagate_cac_done_wk);
 		cfg80211_sched_dfs_chan_update(rdev);
 		break;
 	case NL80211_RADAR_CAC_ABORTED:
+		cfg80211_set_cac_state(wiphy, chandef, false);
 		if (!cancel_delayed_work(&rdev->background_cac_done_wk))
 			return;
 		break;
 	case NL80211_RADAR_CAC_STARTED:
+		cfg80211_set_cac_state(wiphy, chandef, true);
 		break;
 	default:
 		return;
@@ -1306,7 +1311,9 @@ void cfg80211_stop_radar_detection(struct wireless_dev *wdev)
 			continue;
 
 		chandef = *wdev_chandef(wdev, link_id);
+		wdev->links[link_id].cac_started = false;
 		rdev_end_cac(rdev, wdev->netdev, link_id);
+		cfg80211_set_cac_state(wiphy, &chandef, false);
 		nl80211_radar_notify(rdev, &chandef, NL80211_RADAR_CAC_ABORTED,
 				     wdev->netdev, GFP_KERNEL);
 	}
diff --git a/net/wireless/nl80211.c b/net/wireless/nl80211.c
index 56cc5ed33ea3..a29419d7256a 100644
--- a/net/wireless/nl80211.c
+++ b/net/wireless/nl80211.c
@@ -1318,6 +1318,12 @@ static int nl80211_msg_put_channel(struct sk_buff *msg, struct wiphy *wiphy,
 		if ((chan->flags & IEEE80211_CHAN_S1G_NO_PRIMARY) &&
 		    nla_put_flag(msg, NL80211_FREQUENCY_ATTR_S1G_NO_PRIMARY))
 			goto nla_put_failure;
+		if (chan->cac_ongoing_time &&
+		    nla_put_u64_64bit(msg,
+				      NL80211_FREQUENCY_ATTR_CAC_START_TIME,
+				      chan->cac_ongoing_time,
+				      NL80211_FREQUENCY_ATTR_PAD))
+			goto nla_put_failure;
 	}
 
 	if (nla_put_u32(msg, NL80211_FREQUENCY_ATTR_MAX_TX_POWER,
@@ -11249,6 +11255,7 @@ static int nl80211_start_radar_detection(struct sk_buff *skb,
 	wdev->links[link_id].cac_started = true;
 	wdev->links[link_id].cac_start_time = jiffies;
 	wdev->links[link_id].cac_time_ms = cac_time_ms;
+	cfg80211_set_cac_state(wiphy, &chandef, true);
 
 	return 0;
 }
-- 
2.43.0


^ permalink raw reply related

* [RFC v5 wireless-next 1/4] wifi: cfg80211: fix background CAC
From: Janusz Dziedzic @ 2026-01-25 16:00 UTC (permalink / raw)
  To: linux-wireless; +Cc: johannes, Janusz Dziedzic
In-Reply-To: <20260125160353.34102-1-janusz.dziedzic@gmail.com>

Fix:
- Send CAC_ABORT event when background CAC is canceled
- Cancel CAC done workqueue when radar is detected
- Release background wdev ownership when CAC is aborted or passed
- Clean lower layer background radar state when CAC is aborted or passed
- Prevent sending abort event when radar event is sent

Signed-off-by: Janusz Dziedzic <janusz.dziedzic@gmail.com>
---
 net/wireless/mlme.c | 40 +++++++++++++++++++---------------------
 1 file changed, 19 insertions(+), 21 deletions(-)

diff --git a/net/wireless/mlme.c b/net/wireless/mlme.c
index 3fc175f9f868..212178d04efa 100644
--- a/net/wireless/mlme.c
+++ b/net/wireless/mlme.c
@@ -1115,8 +1115,10 @@ void __cfg80211_radar_event(struct wiphy *wiphy,
 	 */
 	cfg80211_set_dfs_state(wiphy, chandef, NL80211_DFS_UNAVAILABLE);
 
-	if (offchan)
+	if (offchan) {
+		cancel_delayed_work(&rdev->background_cac_done_wk);
 		queue_work(cfg80211_wq, &rdev->background_cac_abort_wk);
+	}
 
 	cfg80211_sched_dfs_chan_update(rdev);
 
@@ -1187,21 +1189,16 @@ __cfg80211_background_cac_event(struct cfg80211_registered_device *rdev,
 	if (!cfg80211_chandef_valid(chandef))
 		return;
 
-	if (!rdev->background_radar_wdev)
-		return;
-
 	switch (event) {
 	case NL80211_RADAR_CAC_FINISHED:
 		cfg80211_set_dfs_state(wiphy, chandef, NL80211_DFS_AVAILABLE);
 		memcpy(&rdev->cac_done_chandef, chandef, sizeof(*chandef));
 		queue_work(cfg80211_wq, &rdev->propagate_cac_done_wk);
 		cfg80211_sched_dfs_chan_update(rdev);
-		wdev = rdev->background_radar_wdev;
 		break;
 	case NL80211_RADAR_CAC_ABORTED:
 		if (!cancel_delayed_work(&rdev->background_cac_done_wk))
 			return;
-		wdev = rdev->background_radar_wdev;
 		break;
 	case NL80211_RADAR_CAC_STARTED:
 		break;
@@ -1213,17 +1210,6 @@ __cfg80211_background_cac_event(struct cfg80211_registered_device *rdev,
 	nl80211_radar_notify(rdev, chandef, event, netdev, GFP_KERNEL);
 }
 
-static void
-cfg80211_background_cac_event(struct cfg80211_registered_device *rdev,
-			      const struct cfg80211_chan_def *chandef,
-			      enum nl80211_radar_event event)
-{
-	guard(wiphy)(&rdev->wiphy);
-
-	__cfg80211_background_cac_event(rdev, rdev->background_radar_wdev,
-					chandef, event);
-}
-
 void cfg80211_background_cac_done_wk(struct work_struct *work)
 {
 	struct delayed_work *delayed_work = to_delayed_work(work);
@@ -1231,18 +1217,30 @@ void cfg80211_background_cac_done_wk(struct work_struct *work)
 
 	rdev = container_of(delayed_work, struct cfg80211_registered_device,
 			    background_cac_done_wk);
-	cfg80211_background_cac_event(rdev, &rdev->background_radar_chandef,
-				      NL80211_RADAR_CAC_FINISHED);
+
+	guard(wiphy)(&rdev->wiphy);
+
+	rdev_set_radar_background(rdev, NULL);
+	rdev->background_radar_wdev = NULL;
+
+	__cfg80211_background_cac_event(rdev, rdev->background_radar_wdev,
+					&rdev->background_radar_chandef,
+					NL80211_RADAR_CAC_FINISHED);
 }
 
 void cfg80211_background_cac_abort_wk(struct work_struct *work)
 {
 	struct cfg80211_registered_device *rdev;
+	struct wireless_dev *wdev;
 
 	rdev = container_of(work, struct cfg80211_registered_device,
 			    background_cac_abort_wk);
-	cfg80211_background_cac_event(rdev, &rdev->background_radar_chandef,
-				      NL80211_RADAR_CAC_ABORTED);
+
+	guard(wiphy)(&rdev->wiphy);
+
+	wdev = rdev->background_radar_wdev;
+	if (wdev)
+		cfg80211_stop_background_radar_detection(wdev);
 }
 
 void cfg80211_background_cac_abort(struct wiphy *wiphy)
-- 
2.43.0


^ permalink raw reply related

* [RFC v5 wireless-next 0/4] background CAC fixes
From: Janusz Dziedzic @ 2026-01-25 16:00 UTC (permalink / raw)
  To: linux-wireless; +Cc: johannes, Janusz Dziedzic

V5:
1) tested with mt7915
2) fixed locking
3) rebase with latest wireless-next

V4:
1) added proper locking for work queues
2) Added Fix: description
3) checkpatch fix (--max-line-length=80)

V3:
1) extended bgCAC cancelation patch to cover more issues detected
   when tested with hwsim, like skip CAC abort event when radar
   detected, or clearing lower level bgCAC correctly
2) Set CAC ongoing, so user mode don't have to guess if CAC ongoing
   For this one also have iw patch that will extend iw phyX channels
3) For test purpose extend mac80211_hwsim and report bgCAC support
  Allow to cancel bgCAC from debugfs or simulater radar when bgCAC.

Janusz Dziedzic (4):
  wifi: cfg80211: fix background CAC
  wifi: cfg80211: set and report chandef CAC ongoing
  wifi: cfg80211: events, report background radar
  wifi: mac80211_hwsim: background CAC support

 drivers/net/wireless/virtual/mac80211_hwsim.c | 64 ++++++++++++++++++-
 include/net/cfg80211.h                        |  3 +
 include/uapi/linux/nl80211.h                  |  6 ++
 net/wireless/chan.c                           | 27 ++++++++
 net/wireless/core.h                           |  4 ++
 net/wireless/mlme.c                           | 51 ++++++++-------
 net/wireless/nl80211.c                        | 14 ++++
 7 files changed, 146 insertions(+), 23 deletions(-)

-- 
2.43.0


^ permalink raw reply

* [PATCH wireless-next v2 2/2] wifi: mt76: mt7996: Add eMLSR support
From: Lorenzo Bianconi @ 2026-01-25 10:51 UTC (permalink / raw)
  To: Johannes Berg, Ryder Lee, Sean Wang, Matthias Brugger,
	AngeloGioacchino Del Regno
  Cc: linux-wireless, Felix Fietkau, Shayne Chen, Lorenzo Bianconi,
	Christian Marangi, linux-mediatek, linux-arm-kernel, MeiChia Chiu
In-Reply-To: <20260125-mac80211-emlsr-v2-0-466329d61c88@kernel.org>

From: MeiChia Chiu <MeiChia.Chiu@mediatek.com>

Implement set_eml_op_mode mac80211 callback in order to introduce eMLSR
support.

Signed-off-by: MeiChia Chiu <MeiChia.Chiu@mediatek.com>
Co-developed-by: Lorenzo Bianconi <lorenzo@kernel.org>
Signed-off-by: Lorenzo Bianconi <lorenzo@kernel.org>
---
 .../net/wireless/mediatek/mt76/mt76_connac_mcu.h   |  9 ++++
 drivers/net/wireless/mediatek/mt76/mt7996/main.c   | 16 +++++++
 drivers/net/wireless/mediatek/mt76/mt7996/mcu.c    | 54 ++++++++++++++++++++++
 drivers/net/wireless/mediatek/mt76/mt7996/mt7996.h |  5 ++
 4 files changed, 84 insertions(+)

diff --git a/drivers/net/wireless/mediatek/mt76/mt76_connac_mcu.h b/drivers/net/wireless/mediatek/mt76/mt76_connac_mcu.h
index 8d59cf43f0e2dc8d8e42d6b2ec7565ad69a79ff8..44697498b38a5b9eff92627b63c383ca8f48a760 100644
--- a/drivers/net/wireless/mediatek/mt76/mt76_connac_mcu.h
+++ b/drivers/net/wireless/mediatek/mt76/mt76_connac_mcu.h
@@ -628,6 +628,13 @@ struct sta_rec_tx_proc {
 	__le32 flag;
 } __packed;
 
+struct sta_rec_eml_op {
+	__le16 tag;
+	__le16 len;
+	u8 link_bitmap;
+	u8 link_ant_num[3];
+} __packed;
+
 /* wtbl_rec */
 
 struct wtbl_req_hdr {
@@ -796,6 +803,7 @@ struct wtbl_raw {
 					 sizeof(struct sta_rec_he_6g_capa) + \
 					 sizeof(struct sta_rec_pn_info) + \
 					 sizeof(struct sta_rec_tx_proc) + \
+					 sizeof(struct sta_rec_eml_op) + \
 					 sizeof(struct tlv) +		\
 					 MT76_CONNAC_WTBL_UPDATE_MAX_SIZE)
 
@@ -832,6 +840,7 @@ enum {
 	STA_REC_PN_INFO = 0x26,
 	STA_REC_KEY_V3 = 0x27,
 	STA_REC_HDRT = 0x28,
+	STA_REC_EML_OP = 0x29,
 	STA_REC_HDR_TRANS = 0x2B,
 	STA_REC_MAX_NUM
 };
diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/main.c b/drivers/net/wireless/mediatek/mt76/mt7996/main.c
index beed795edb24c67e1b7b44fe87fd5de125a21d94..d5b88687ba1801a1e50e81e5c27f1f67fa455ce1 100644
--- a/drivers/net/wireless/mediatek/mt76/mt7996/main.c
+++ b/drivers/net/wireless/mediatek/mt76/mt7996/main.c
@@ -2275,6 +2275,21 @@ mt7996_reconfig_complete(struct ieee80211_hw *hw,
 					     MT7996_WATCHDOG_TIME);
 }
 
+static int
+mt7996_set_eml_op_mode(struct ieee80211_hw *hw, struct ieee80211_vif *vif,
+		       struct ieee80211_sta *sta, unsigned int link_id,
+		       struct ieee80211_eml_params *eml_params)
+{
+	struct mt7996_dev *dev = mt7996_hw_dev(hw);
+	int ret;
+
+	mutex_lock(&dev->mt76.mutex);
+	ret = mt7996_mcu_set_emlsr_mode(dev, vif, sta, link_id, eml_params);
+	mutex_unlock(&dev->mt76.mutex);
+
+	return ret;
+}
+
 const struct ieee80211_ops mt7996_ops = {
 	.add_chanctx = mt76_add_chanctx,
 	.remove_chanctx = mt76_remove_chanctx,
@@ -2337,4 +2352,5 @@ const struct ieee80211_ops mt7996_ops = {
 	.change_vif_links = mt7996_change_vif_links,
 	.change_sta_links = mt7996_mac_sta_change_links,
 	.reconfig_complete = mt7996_reconfig_complete,
+	.set_eml_op_mode = mt7996_set_eml_op_mode,
 };
diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/mcu.c b/drivers/net/wireless/mediatek/mt76/mt7996/mcu.c
index 14a88ef79b6cb9ee60e62d695af4a90ab1d89846..54d6a41281841c3a86e5308971b1e456da8e0e73 100644
--- a/drivers/net/wireless/mediatek/mt76/mt7996/mcu.c
+++ b/drivers/net/wireless/mediatek/mt76/mt7996/mcu.c
@@ -1170,6 +1170,60 @@ int mt7996_mcu_add_bss_info(struct mt7996_phy *phy, struct ieee80211_vif *vif,
 				     MCU_WMWA_UNI_CMD(BSS_INFO_UPDATE), true);
 }
 
+int mt7996_mcu_set_emlsr_mode(struct mt7996_dev *dev,
+			      struct ieee80211_vif *vif,
+			      struct ieee80211_sta *sta,
+			      unsigned int link_id,
+			      struct ieee80211_eml_params *eml_params)
+{
+	struct mt7996_sta *msta = (struct mt7996_sta *)sta->drv_priv;
+	struct mt7996_sta_link *msta_link;
+	struct sta_rec_eml_op *eml_op;
+	struct mt7996_vif_link *link;
+	struct sk_buff *skb;
+	struct tlv *tlv;
+
+	msta_link = mt76_dereference(msta->link[link_id], &dev->mt76);
+	if (!msta_link)
+		return -EINVAL;
+
+	link = mt7996_vif_link(dev, vif, link_id);
+	if (!link)
+		return -EINVAL;
+
+	skb = __mt76_connac_mcu_alloc_sta_req(&dev->mt76, &link->mt76,
+					      &msta_link->wcid,
+					      MT7996_STA_UPDATE_MAX_SIZE);
+	if (IS_ERR(skb))
+		return PTR_ERR(skb);
+
+	tlv = mt76_connac_mcu_add_tlv(skb, STA_REC_EML_OP, sizeof(*eml_op));
+	eml_op = (struct sta_rec_eml_op *)tlv;
+	eml_op->link_bitmap = 0;
+
+	if (eml_params->control & IEEE80211_EML_CTRL_EMLSR_MODE) {
+		unsigned long link_bitmap = eml_params->link_bitmap;
+
+		for_each_set_bit(link_id, &link_bitmap,
+				 IEEE80211_MLD_MAX_NUM_LINKS) {
+			struct mt76_phy *mphy;
+
+			link = mt7996_vif_link(dev, vif, link_id);
+			if (!link)
+				continue;
+
+			mphy = mt76_vif_link_phy(&link->mt76);
+			if (!mphy)
+				continue;
+
+			eml_op->link_bitmap |= BIT(mphy->band_idx);
+		}
+	}
+
+	return mt76_mcu_skb_send_msg(&dev->mt76, skb,
+				     MCU_WMWA_UNI_CMD(STA_REC_UPDATE), true);
+}
+
 int mt7996_mcu_set_timing(struct mt7996_phy *phy, struct ieee80211_vif *vif,
 			  struct ieee80211_bss_conf *link_conf)
 {
diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/mt7996.h b/drivers/net/wireless/mediatek/mt76/mt7996/mt7996.h
index 7a884311800ea8cfc0e302b2a140a4072ce18b69..da8688e01c1502cbd895ea40561f77e929c6ee38 100644
--- a/drivers/net/wireless/mediatek/mt76/mt7996/mt7996.h
+++ b/drivers/net/wireless/mediatek/mt76/mt7996/mt7996.h
@@ -861,6 +861,11 @@ int mt7996_mcu_wtbl_update_hdr_trans(struct mt7996_dev *dev,
 				     struct mt7996_vif_link *link,
 				     struct mt7996_sta_link *msta_link);
 int mt7996_mcu_cp_support(struct mt7996_dev *dev, u8 mode);
+int mt7996_mcu_set_emlsr_mode(struct mt7996_dev *dev,
+			      struct ieee80211_vif *vif,
+			      struct ieee80211_sta *sta,
+			      unsigned int link_id,
+			      struct ieee80211_eml_params *eml_params);
 #ifdef CONFIG_MAC80211_DEBUGFS
 void mt7996_sta_add_debugfs(struct ieee80211_hw *hw, struct ieee80211_vif *vif,
 			    struct ieee80211_sta *sta, struct dentry *dir);

-- 
2.52.0


^ permalink raw reply related

* [PATCH wireless-next v2 1/2] wifi: mac80211: Add eMLSR/eMLMR action frame parsing support
From: Lorenzo Bianconi @ 2026-01-25 10:51 UTC (permalink / raw)
  To: Johannes Berg, Ryder Lee, Sean Wang, Matthias Brugger,
	AngeloGioacchino Del Regno
  Cc: linux-wireless, Felix Fietkau, Shayne Chen, Lorenzo Bianconi,
	Christian Marangi, linux-mediatek, linux-arm-kernel
In-Reply-To: <20260125-mac80211-emlsr-v2-0-466329d61c88@kernel.org>

Introduce support in AP mode for parsing of the Operating Mode Notification
frame sent by the client to enable/disable MLO eMLSR or eMLMR if supported
by both the AP and the client.
Add drv_set_eml_op_mode mac80211 callback in order to configure underlay
driver with eMLSR/eMLMR info.

Tested-by: Christian Marangi <ansuelsmth@gmail.com>
Signed-off-by: Lorenzo Bianconi <lorenzo@kernel.org>
---
 include/linux/ieee80211-eht.h |  28 +++++++++
 include/linux/ieee80211.h     |   6 ++
 include/net/mac80211.h        |  23 +++++++
 net/mac80211/driver-ops.h     |  22 +++++++
 net/mac80211/eht.c            | 135 ++++++++++++++++++++++++++++++++++++++++++
 net/mac80211/ieee80211_i.h    |   2 +
 net/mac80211/iface.c          |  10 +++-
 net/mac80211/rx.c             |   8 +++
 net/mac80211/trace.h          |  32 ++++++++++
 9 files changed, 265 insertions(+), 1 deletion(-)

diff --git a/include/linux/ieee80211-eht.h b/include/linux/ieee80211-eht.h
index f9782e46c5e5241bccc84c3bbc18b1cc9ec1879c..d78496d0307a1ff1e03a27b3491394166850f3e5 100644
--- a/include/linux/ieee80211-eht.h
+++ b/include/linux/ieee80211-eht.h
@@ -558,6 +558,34 @@ struct ieee80211_mle_tdls_common_info {
 
 #define IEEE80211_MLC_PRIO_ACCESS_PRES_AP_MLD_MAC_ADDR	0x0010
 
+#define IEEE80211_EML_CTRL_EMLSR_MODE		BIT(0)
+#define IEEE80211_EML_CTRL_EMLMR_MODE		BIT(1)
+#define IEEE80211_EML_CTRL_EMLSR_PARAM_UPDATE	BIT(2)
+#define IEEE80211_EML_CTRL_INDEV_COEX_ACT	BIT(3)
+
+#define IEEE80211_EML_EMLSR_PAD_DELAY		0x07
+#define IEEE80211_EML_EMLSR_TRANS_DELAY		0x38
+
+static inline u8 ieee80211_get_emlsr_pad_delay_update(u8 param)
+{
+	u8 pad_delay = FIELD_GET(IEEE80211_EML_EMLSR_PAD_DELAY, param);
+
+	if (pad_delay > IEEE80211_EML_CAP_EMLSR_PADDING_DELAY_256US)
+		pad_delay = 0;
+
+	return pad_delay;
+}
+
+static inline u32 ieee80211_get_emlsr_trans_delay_update(u8 param)
+{
+	u16 trans_delay = FIELD_GET(IEEE80211_EML_EMLSR_TRANS_DELAY, param);
+
+	if (trans_delay > IEEE80211_EML_CAP_EMLSR_TRANSITION_DELAY_256US)
+		trans_delay = 0;
+
+	return trans_delay;
+}
+
 /* no fixed fields in PRIO_ACCESS */
 
 /**
diff --git a/include/linux/ieee80211.h b/include/linux/ieee80211.h
index fbde215c25aa79efd339aa530896a29dbb1a8ff8..f2c6f34f39f24ce59cbb2650f70e898d24d12901 100644
--- a/include/linux/ieee80211.h
+++ b/include/linux/ieee80211.h
@@ -1186,6 +1186,12 @@ struct ieee80211_mgmt {
 					u8 action_code;
 					u8 variable[];
 				} __packed epcs;
+				struct {
+					u8 action_code;
+					u8 dialog_token;
+					u8 control;
+					u8 variable[];
+				} __packed eml_omn;
 			} u;
 		} __packed action;
 		DECLARE_FLEX_ARRAY(u8, body); /* Generic frame body */
diff --git a/include/net/mac80211.h b/include/net/mac80211.h
index 36ae7fe9ddf35190921f4fee0fe3294418007a56..fb7ba70ffa639dabba4aa5760c27b2d70f030c28 100644
--- a/include/net/mac80211.h
+++ b/include/net/mac80211.h
@@ -1902,6 +1902,21 @@ enum ieee80211_offload_flags {
 	IEEE80211_OFFLOAD_DECAP_ENABLED		= BIT(2),
 };
 
+struct ieee80211_eml_params {
+	u8 control;
+	u16 link_bitmap;
+	union {
+		struct {
+			u16 emlsr_pad_delay;
+			u16 emlsr_trans_delay;
+		};
+		struct {
+			u8 mcs_map_count;
+			u8 mcs_map_bw[9];
+		};
+	};
+};
+
 /**
  * struct ieee80211_vif_cfg - interface configuration
  * @assoc: association status
@@ -4513,6 +4528,9 @@ struct ieee80211_prep_tx_info {
  *      interface with the specified type would be added, and thus drivers that
  *      implement this callback need to handle such cases. The type is the full
  *      &enum nl80211_iftype.
+ * @set_eml_op_mode: Configure eMLSR/eMLMR operation mode in the underlay
+ *	driver according to the parameter received in the EML Operating mode
+ *	notification frame.
  */
 struct ieee80211_ops {
 	void (*tx)(struct ieee80211_hw *hw,
@@ -4908,6 +4926,11 @@ struct ieee80211_ops {
 			struct ieee80211_neg_ttlm *ttlm);
 	void (*prep_add_interface)(struct ieee80211_hw *hw,
 				   enum nl80211_iftype type);
+	int (*set_eml_op_mode)(struct ieee80211_hw *hw,
+			       struct ieee80211_vif *vif,
+			       struct ieee80211_sta *sta,
+			       unsigned int link_id,
+			       struct ieee80211_eml_params *eml_params);
 };
 
 /**
diff --git a/net/mac80211/driver-ops.h b/net/mac80211/driver-ops.h
index 55105d238d6bc5963eb2863575805bee72c42399..29f2b9bab859b50de5420beb4984211eb2270bae 100644
--- a/net/mac80211/driver-ops.h
+++ b/net/mac80211/driver-ops.h
@@ -1772,4 +1772,26 @@ drv_prep_add_interface(struct ieee80211_local *local,
 	trace_drv_return_void(local);
 }
 
+static inline int drv_set_eml_op_mode(struct ieee80211_sub_if_data *sdata,
+				      struct ieee80211_sta *sta,
+				      unsigned int link_id,
+				      struct ieee80211_eml_params *eml_params)
+{
+	struct ieee80211_local *local = sdata->local;
+	int ret = 0;
+
+	might_sleep();
+	lockdep_assert_wiphy(local->hw.wiphy);
+
+	trace_drv_set_eml_op_mode(local, sdata, sta, link_id,
+				  eml_params->control,
+				  eml_params->link_bitmap);
+	if (local->ops->set_eml_op_mode)
+		ret = local->ops->set_eml_op_mode(&local->hw, &sdata->vif,
+						  sta, link_id, eml_params);
+	trace_drv_return_int(local, ret);
+
+	return ret;
+}
+
 #endif /* __MAC80211_DRIVER_OPS */
diff --git a/net/mac80211/eht.c b/net/mac80211/eht.c
index fd41046e3b681b753e6cc7ddf82046e4bc5df9b3..29cb348e9f1bbd541f6a6d82899087f36be4ef22 100644
--- a/net/mac80211/eht.c
+++ b/net/mac80211/eht.c
@@ -5,6 +5,7 @@
  * Copyright(c) 2021-2025 Intel Corporation
  */
 
+#include "driver-ops.h"
 #include "ieee80211_i.h"
 
 void
@@ -102,3 +103,137 @@ ieee80211_eht_cap_ie_to_sta_eht_cap(struct ieee80211_sub_if_data *sdata,
 
 	ieee80211_sta_recalc_aggregates(&link_sta->sta->sta);
 }
+
+static void
+ieee80211_send_eml_op_mode_notif(struct ieee80211_sub_if_data *sdata,
+				 struct ieee80211_mgmt *req, u8 act_len)
+{
+	int hdr_len = offsetof(struct ieee80211_mgmt, u.action.u.eml_omn);
+	struct ieee80211_local *local = sdata->local;
+	struct ieee80211_mgmt *mgmt;
+	struct sk_buff *skb;
+
+	skb = dev_alloc_skb(local->tx_headroom + hdr_len + act_len);
+	if (!skb)
+		return;
+
+	skb_reserve(skb, local->tx_headroom);
+	mgmt = skb_put_zero(skb, hdr_len);
+	mgmt->frame_control = cpu_to_le16(IEEE80211_FTYPE_MGMT |
+					  IEEE80211_STYPE_ACTION);
+	memcpy(mgmt->da, req->sa, ETH_ALEN);
+	memcpy(mgmt->sa, sdata->vif.addr, ETH_ALEN);
+	memcpy(mgmt->bssid, sdata->vif.addr, ETH_ALEN);
+
+	mgmt->u.action.category = WLAN_CATEGORY_PROTECTED_EHT;
+	memcpy(&mgmt->u.action.u.eml_omn, &req->u.action.u.eml_omn, act_len);
+	mgmt->u.action.u.eml_omn.control &=
+		~(IEEE80211_EML_CTRL_EMLSR_PARAM_UPDATE |
+		  IEEE80211_EML_CTRL_INDEV_COEX_ACT);
+	ieee80211_tx_skb(sdata, skb);
+}
+
+void ieee80211_rx_eml_op_mode_notif(struct ieee80211_sub_if_data *sdata,
+				    struct sk_buff *skb)
+{
+	int hdr_len = offsetof(struct ieee80211_mgmt, u.action.u.eml_omn);
+	enum nl80211_iftype type = ieee80211_vif_type_p2p(&sdata->vif);
+	struct ieee80211_rx_status *status = IEEE80211_SKB_RXCB(skb);
+	const struct wiphy_iftype_ext_capab *ift_ext_capa;
+	struct ieee80211_mgmt *mgmt = (void *)skb->data;
+	struct ieee80211_local *local = sdata->local;
+	u8 control = mgmt->u.action.u.eml_omn.control;
+	u8 *ptr = mgmt->u.action.u.eml_omn.variable;
+	struct ieee80211_eml_params eml_params = {};
+	struct sta_info *sta;
+	u8 act_len = 3; /* action_code + dialog_token + control */
+
+	if (!ieee80211_vif_is_mld(&sdata->vif))
+		return;
+
+	/* eMLSR and eMLMR can't be enabled at the same time */
+	if ((control & IEEE80211_EML_CTRL_EMLSR_MODE) &&
+	    (control & IEEE80211_EML_CTRL_EMLMR_MODE))
+		return;
+
+	if ((control & IEEE80211_EML_CTRL_EMLMR_MODE) &&
+	    (control & IEEE80211_EML_CTRL_EMLSR_PARAM_UPDATE))
+		return;
+
+	ift_ext_capa = cfg80211_get_iftype_ext_capa(local->hw.wiphy, type);
+	if (!ift_ext_capa)
+		return;
+
+	if (!status->link_valid)
+		return;
+
+	sta = sta_info_get_bss(sdata, mgmt->sa);
+	if (!sta)
+		return;
+
+	if (control & IEEE80211_EML_CTRL_EMLSR_MODE) {
+		if (!(ift_ext_capa->eml_capabilities &
+		      IEEE80211_EML_CAP_EMLSR_SUPP))
+			return;
+
+		if (control & IEEE80211_EML_CTRL_EMLSR_PARAM_UPDATE) {
+			u16 eml_cap = sta->sta.eml_cap;
+			u8 pad_delay, trans_delay;
+
+			/* Update sta padding and transition delay */
+			pad_delay =
+				ieee80211_get_emlsr_pad_delay_update(ptr[3]);
+			trans_delay =
+				ieee80211_get_emlsr_pad_delay_update(ptr[3]);
+
+			eml_cap &= ~(IEEE80211_EML_CAP_EMLSR_PADDING_DELAY |
+				     IEEE80211_EML_CAP_EMLSR_TRANSITION_DELAY);
+			eml_cap |= FIELD_PREP(IEEE80211_EML_EMLSR_PAD_DELAY,
+					      pad_delay) |
+				   FIELD_PREP(IEEE80211_EML_EMLSR_TRANS_DELAY,
+					      trans_delay);
+			sta->sta.eml_cap = eml_cap;
+		}
+
+		eml_params.emlsr_pad_delay =
+			ieee80211_emlsr_pad_delay_in_us(sta->sta.eml_cap);
+		eml_params.emlsr_trans_delay =
+			ieee80211_emlsr_trans_delay_in_us(sta->sta.eml_cap);
+	}
+
+	if (control & IEEE80211_EML_CTRL_EMLMR_MODE) {
+		u8 mcs_map_size;
+
+		if (!(ift_ext_capa->eml_capabilities &
+		      IEEE80211_EML_CAP_EMLMR_SUPPORT))
+			return;
+
+		eml_params.mcs_map_count = ptr[3];
+		if (eml_params.mcs_map_count > 2)
+			return;
+
+		/* mcs_map_count_control and mcs_map_bw */
+		mcs_map_size = 3 * (1 + eml_params.mcs_map_count);
+		memcpy(eml_params.mcs_map_bw, &ptr[4], mcs_map_size);
+		act_len += 1 + mcs_map_size;
+	}
+
+	if ((control & IEEE80211_EML_CTRL_EMLSR_MODE) ||
+	    (control & IEEE80211_EML_CTRL_EMLMR_MODE)) {
+		eml_params.link_bitmap = get_unaligned_le16(ptr);
+		if (eml_params.link_bitmap &&
+		    !(eml_params.link_bitmap & sdata->vif.active_links))
+			return;
+
+		act_len += sizeof(__le16); /* eMLSR/eMLMR link_bitmap */
+	}
+
+	if (skb->len < hdr_len + act_len)
+		return;
+
+	if (drv_set_eml_op_mode(sdata, &sta->sta, status->link_id,
+				&eml_params))
+		return;
+
+	ieee80211_send_eml_op_mode_notif(sdata, mgmt, act_len);
+}
diff --git a/net/mac80211/ieee80211_i.h b/net/mac80211/ieee80211_i.h
index dc757cb329740d621f6a9deb4e9ffe258e1b7d67..3ef93cd1fb33e2614c3e06c51d6b1ebcafa4824c 100644
--- a/net/mac80211/ieee80211_i.h
+++ b/net/mac80211/ieee80211_i.h
@@ -2837,6 +2837,8 @@ void ieee80211_destroy_frag_cache(struct ieee80211_fragment_cache *cache);
 
 u8 ieee80211_ie_len_eht_cap(struct ieee80211_sub_if_data *sdata);
 
+void ieee80211_rx_eml_op_mode_notif(struct ieee80211_sub_if_data *sdata,
+				    struct sk_buff *skb);
 void
 ieee80211_eht_cap_ie_to_sta_eht_cap(struct ieee80211_sub_if_data *sdata,
 				    struct ieee80211_supported_band *sband,
diff --git a/net/mac80211/iface.c b/net/mac80211/iface.c
index 3ce94b95decd64eea4ea063ae98c111bdfd57e9c..17476fd9259b68b3d9c649eabad10efa42d56cd7 100644
--- a/net/mac80211/iface.c
+++ b/net/mac80211/iface.c
@@ -1664,7 +1664,15 @@ static void ieee80211_iface_process_skb(struct ieee80211_local *local,
 		}
 	} else if (ieee80211_is_action(mgmt->frame_control) &&
 		   mgmt->u.action.category == WLAN_CATEGORY_PROTECTED_EHT) {
-		if (sdata->vif.type == NL80211_IFTYPE_STATION) {
+		if (sdata->vif.type == NL80211_IFTYPE_AP) {
+			switch (mgmt->u.action.u.eml_omn.action_code) {
+			case WLAN_PROTECTED_EHT_ACTION_EML_OP_MODE_NOTIF:
+				ieee80211_rx_eml_op_mode_notif(sdata, skb);
+				break;
+			default:
+				break;
+			}
+		} else if (sdata->vif.type == NL80211_IFTYPE_STATION) {
 			switch (mgmt->u.action.u.ttlm_req.action_code) {
 			case WLAN_PROTECTED_EHT_ACTION_TTLM_REQ:
 				ieee80211_process_neg_ttlm_req(sdata, mgmt,
diff --git a/net/mac80211/rx.c b/net/mac80211/rx.c
index ac437256f5d5c4b34601fd4c8a6f6f80469b2522..521e44c9e8dac498122e7a595d1eb8bb5751789d 100644
--- a/net/mac80211/rx.c
+++ b/net/mac80211/rx.c
@@ -3928,6 +3928,14 @@ ieee80211_rx_h_action(struct ieee80211_rx_data *rx)
 					      u.action.u.epcs))
 				goto invalid;
 			goto queue;
+		case WLAN_PROTECTED_EHT_ACTION_EML_OP_MODE_NOTIF:
+			if (sdata->vif.type != NL80211_IFTYPE_AP)
+				break;
+
+			if (len < offsetofend(typeof(*mgmt),
+					      u.action.u.eml_omn))
+				goto invalid;
+			goto queue;
 		default:
 			break;
 		}
diff --git a/net/mac80211/trace.h b/net/mac80211/trace.h
index 0bfbce1574862b5a6a2ca39794abea7fe9a3f34a..c04d4547e8f4655ad77b83bed6e5a832d959e61b 100644
--- a/net/mac80211/trace.h
+++ b/net/mac80211/trace.h
@@ -3353,6 +3353,38 @@ TRACE_EVENT(drv_prep_add_interface,
 	)
 );
 
+TRACE_EVENT(drv_set_eml_op_mode,
+	    TP_PROTO(struct ieee80211_local *local,
+		     struct ieee80211_sub_if_data *sdata,
+		     struct ieee80211_sta *sta,
+		     unsigned int link_id,
+		     u8 control, u16 link_bitmap),
+
+	TP_ARGS(local, sdata, sta, link_id, control, link_bitmap),
+
+	TP_STRUCT__entry(LOCAL_ENTRY
+			 VIF_ENTRY
+			 STA_ENTRY
+			 __field(u32, link_id)
+			 __field(u8, control)
+			 __field(u16, link_bitmap)),
+
+	TP_fast_assign(LOCAL_ASSIGN;
+		       VIF_ASSIGN;
+		       STA_NAMED_ASSIGN(sta);
+		       __entry->link_id = link_id;
+		       __entry->control = control;
+		       __entry->link_bitmap = link_bitmap;
+	),
+
+	TP_printk(
+		LOCAL_PR_FMT  VIF_PR_FMT  STA_PR_FMT
+		" (link:%d control:%02x link_bitmap:%04x)",
+		LOCAL_PR_ARG, VIF_PR_ARG, STA_PR_ARG, __entry->link_id,
+		__entry->control, __entry->link_bitmap
+	)
+);
+
 #endif /* !__MAC80211_DRIVER_TRACE || TRACE_HEADER_MULTI_READ */
 
 #undef TRACE_INCLUDE_PATH

-- 
2.52.0


^ permalink raw reply related

* [PATCH wireless-next v2 0/2] wifi: mac80211: Introduce eMLSR/eMLMR parsing support in AP mode.
From: Lorenzo Bianconi @ 2026-01-25 10:51 UTC (permalink / raw)
  To: Johannes Berg, Ryder Lee, Sean Wang, Matthias Brugger,
	AngeloGioacchino Del Regno
  Cc: linux-wireless, Felix Fietkau, Shayne Chen, Lorenzo Bianconi,
	Christian Marangi, linux-mediatek, linux-arm-kernel, MeiChia Chiu

Introduce support in AP mode for parsing of the Operating Mode Notification
frame sent by the client to enable/disable MLO eMLSR or eMLMR if supported
by both the AP and the client.
Add drv_set_eml_op_mode mac80211 callback in order to configure underlay
driver with eMLSR info (control and bitmap).
Implement drv_set_eml_op_mode callback for MT7996 driver.

---
Changes in v2:
- Improve sanity check against device EML capabilities
- Squash patch 1/2 and 2/2
- Validate link_bitmap with vif->active_links
- Introduce ieee80211_eml_params struct as containe for EML info to pass
  to the underlay driver.
- Pass padding_delay and transition_delay to the underlay driver.
- Implement drv_set_eml_op_mode callback for MT7996 driver.
- Link to v1: https://lore.kernel.org/r/20260122-mac80211-emlsr-v1-0-f0e43bb6d95a@kernel.org

---
Lorenzo Bianconi (1):
      wifi: mac80211: Add eMLSR/eMLMR action frame parsing support

MeiChia Chiu (1):
      wifi: mt76: mt7996: Add eMLSR support

 .../net/wireless/mediatek/mt76/mt76_connac_mcu.h   |   9 ++
 drivers/net/wireless/mediatek/mt76/mt7996/main.c   |  16 +++
 drivers/net/wireless/mediatek/mt76/mt7996/mcu.c    |  54 +++++++++
 drivers/net/wireless/mediatek/mt76/mt7996/mt7996.h |   5 +
 include/linux/ieee80211-eht.h                      |  28 +++++
 include/linux/ieee80211.h                          |   6 +
 include/net/mac80211.h                             |  23 ++++
 net/mac80211/driver-ops.h                          |  22 ++++
 net/mac80211/eht.c                                 | 135 +++++++++++++++++++++
 net/mac80211/ieee80211_i.h                         |   2 +
 net/mac80211/iface.c                               |  10 +-
 net/mac80211/rx.c                                  |   8 ++
 net/mac80211/trace.h                               |  32 +++++
 13 files changed, 349 insertions(+), 1 deletion(-)
---
base-commit: 1e1dd9eeaab3908746d1dce5db6b0c29e0d28d6d
change-id: 20260121-mac80211-emlsr-5774082ff8cc

Best regards,
-- 
Lorenzo Bianconi <lorenzo@kernel.org>


^ permalink raw reply

* [BUG] mac80211: TTLM advertised T2L parsing reads 1 byte past element end with DEF_LINK_MAP
From: Ruikai Peng @ 2026-01-24  3:26 UTC (permalink / raw)
  To: linux-wireless; +Cc: johannes, linux-kernel

Hi,

I believe there is a remotely-triggerable out-of-bounds read in
mac80211 TTLM parsing.

ieee80211_parse_adv_t2l() unconditionally reads the first byte of
ttlm->optional:
link_map_presence = *pos; ieee80211_tid_to_link_map_size_ok() permits
DEF_LINK_MAP with no optional bytes
(minimum element length == sizeof(struct ieee80211_ttlm_elem)). So a
minimal TTLM element with DEF_LINK_MAP passes validation but causes
the parser to read 1 byte past the element boundary.

Input
- Extension IE: EID=0xFF, LEN=0x02, EXT_ID=0x6D (TTLM)
- CONTROL=0x06 (DIR=BOTH + DEF_LINK_MAP)
- Byte sequence: ff 02 6d 06

Reachability
- Assoc response path: ieee80211_assoc_success() -> ieee80211_parse_adv_t2l()
- Beacon path: ieee80211_process_adv_ttlm() -> ieee80211_parse_adv_t2l()

This would result in a OOB read of 1 byte (element-level; may still be
inside the skb buffer). Value is unused when DEF_LINK_MAP is set, so
exploitability appears low.

Repro:
MLO association using hwsim + hostapd/wpa_supplicant; Advertise the
minimal TTLM IE in beacon + assocresp; I used GDB confirms
ttlm->optional == end-of-element, then parser reads *pos.
(Additional writeup: https://bugs.pwno.io/private/95a10f11e34e89d5)

I recommend defer reading *pos until after DEF_LINK_MAP check, or
require at least 1 optional byte in validation when DEF_LINK_MAP is
set.

I can provide a small patch if you'd like.
- Ruikai Peng

^ permalink raw reply

* Re: [RFC v2 wireless-next 2/3] wifi: cfg80211: add initial UHR support
From: Jeff Johnson @ 2026-01-23 21:51 UTC (permalink / raw)
  To: Johannes Berg, linux-wireless
In-Reply-To: <802579416827936058d24a5e1ad24084e2d1e8ec.camel@sipsolutions.net>

On 1/23/2026 12:11 PM, Johannes Berg wrote:
> On Fri, 2026-01-23 at 12:00 -0800, Jeff Johnson wrote:
>> On 12/16/2025 8:23 AM, Johannes Berg wrote:
>>> +	static const u32 mcs_divisors[] = {
>>> +		[ 0] = 102399, /* 16.666666... */
>>> +		[ 1] =  51201, /*  8.333333... */
>>> +		[ 2] =  34134, /*  5.555555... */
>>> +		[ 3] =  25599, /*  4.166666... */
>>> +		[ 4] =  17067, /*  2.777777... */
>>> +		[ 5] =  12801, /*  2.083333... */
>>> +		[ 6] =  11377, /*  1.851725... */
>>> +		[ 7] =  10239, /*  1.666666... */
>>> +		[ 8] =   8532, /*  1.388888... */
>>> +		[ 9] =   7680, /*  1.250000... */
>>
>> you probably already know this, but checkpatch doesn't like the first 10:
>> ERROR:SPACING: space prohibited after that open square bracket '['
> 
> It probably also doesn't like the leading spaces after the = sign, but
> ... I don't _really_ care. It's way more readable this way IMHO.
> 
>> (yes, I'm running your series through my automation since we are vetting our
>> additional UHR changes on top of yours)
> 
> Do you want to post it all together (and maybe fix the uhr_oper thing
> from your other mail too), or do you prefer I repost this?

It would probably be better for you to repost since there are (trivial)
conflicts with things that have already landed in wireless-next.

Then we can supply support for UHR capability and operation
parsing helpers for DPS/DBE/P-EDCA, and add some hwsim support.

/jeff

^ permalink raw reply

* [PATCH wireless-next] wifi: mac80211: use u64_stats_t with u64_stats_sync properly
From: David Yang @ 2026-01-23 20:38 UTC (permalink / raw)
  To: linux-wireless; +Cc: David Yang, Johannes Berg, linux-kernel

On 64bit arches, struct u64_stats_sync is empty and provides no help
against load/store tearing. Convert to u64_stats_t to ensure atomic
operations.

Signed-off-by: David Yang <mmyangfl@gmail.com>
---
 net/mac80211/rx.c       |  8 ++++----
 net/mac80211/sta_info.c | 22 +++++++++++++++++-----
 net/mac80211/sta_info.h |  4 ++--
 3 files changed, 23 insertions(+), 11 deletions(-)

diff --git a/net/mac80211/rx.c b/net/mac80211/rx.c
index e0ccd9749853..577c505a5d10 100644
--- a/net/mac80211/rx.c
+++ b/net/mac80211/rx.c
@@ -1880,7 +1880,7 @@ ieee80211_rx_h_sta_process(struct ieee80211_rx_data *rx)
 	link_sta->rx_stats.fragments++;
 
 	u64_stats_update_begin(&link_sta->rx_stats.syncp);
-	link_sta->rx_stats.bytes += rx->skb->len;
+	u64_stats_add(&link_sta->rx_stats.bytes, rx->skb->len);
 	u64_stats_update_end(&link_sta->rx_stats.syncp);
 
 	if (!(status->flag & RX_FLAG_NO_SIGNAL_VAL)) {
@@ -2777,7 +2777,7 @@ ieee80211_deliver_skb(struct ieee80211_rx_data *rx)
 		 * frame, so count MSDUs.
 		 */
 		u64_stats_update_begin(&rx->link_sta->rx_stats.syncp);
-		rx->link_sta->rx_stats.msdu[rx->seqno_idx]++;
+		u64_stats_inc(&rx->link_sta->rx_stats.msdu[rx->seqno_idx]);
 		u64_stats_update_end(&rx->link_sta->rx_stats.syncp);
 	}
 
@@ -4868,8 +4868,8 @@ static void ieee80211_rx_8023(struct ieee80211_rx_data *rx,
 	 * frame, so count MSDUs.
 	 */
 	u64_stats_update_begin(&stats->syncp);
-	stats->msdu[rx->seqno_idx]++;
-	stats->bytes += orig_len;
+	u64_stats_inc(&stats->msdu[rx->seqno_idx]);
+	u64_stats_add(&stats->bytes, orig_len);
 	u64_stats_update_end(&stats->syncp);
 
 	if (fast_rx->internal_forward) {
diff --git a/net/mac80211/sta_info.c b/net/mac80211/sta_info.c
index 1a995bc301b1..22e8561ad6fc 100644
--- a/net/mac80211/sta_info.c
+++ b/net/mac80211/sta_info.c
@@ -360,7 +360,9 @@ static void sta_accumulate_removed_link_stats(struct sta_info *sta, int link_id)
 	struct link_sta_info *link_sta = wiphy_dereference(sta->local->hw.wiphy,
 							   sta->link[link_id]);
 	struct ieee80211_link_data *link;
+	unsigned int start;
 	int ac, tid;
+	u64 value;
 	u32 thr;
 
 	for (ac = 0; ac < IEEE80211_NUM_ACS; ac++) {
@@ -369,8 +371,13 @@ static void sta_accumulate_removed_link_stats(struct sta_info *sta, int link_id)
 		sta->rem_link_stats.tx_bytes += link_sta->tx_stats.bytes[ac];
 	}
 
+	do {
+		start = u64_stats_fetch_begin(&link_sta->rx_stats.syncp);
+		value = u64_stats_read(&link_sta->rx_stats.bytes);
+	} while (u64_stats_fetch_retry(&link_sta->rx_stats.syncp, start));
+
 	sta->rem_link_stats.rx_packets += link_sta->rx_stats.packets;
-	sta->rem_link_stats.rx_bytes += link_sta->rx_stats.bytes;
+	sta->rem_link_stats.rx_bytes += value;
 	sta->rem_link_stats.tx_retries += link_sta->status_stats.retry_count;
 	sta->rem_link_stats.tx_failed += link_sta->status_stats.retry_failed;
 	sta->rem_link_stats.rx_dropped_misc += link_sta->rx_stats.dropped;
@@ -380,8 +387,13 @@ static void sta_accumulate_removed_link_stats(struct sta_info *sta, int link_id)
 		sta->rem_link_stats.expected_throughput += thr;
 
 	for (tid = 0; tid < IEEE80211_NUM_TIDS; tid++) {
-		sta->rem_link_stats.pertid_stats.rx_msdu +=
-			link_sta->rx_stats.msdu[tid];
+		do {
+			start = u64_stats_fetch_begin(&link_sta->rx_stats.syncp);
+			value = u64_stats_read(&link_sta->rx_stats.msdu[tid]);
+		} while (u64_stats_fetch_retry(&link_sta->rx_stats.syncp,
+					       start));
+
+		sta->rem_link_stats.pertid_stats.rx_msdu += value;
 		sta->rem_link_stats.pertid_stats.tx_msdu +=
 			link_sta->tx_stats.msdu[tid];
 		sta->rem_link_stats.pertid_stats.tx_msdu_retries +=
@@ -2578,7 +2590,7 @@ static inline u64 sta_get_tidstats_msdu(struct ieee80211_sta_rx_stats *rxstats,
 
 	do {
 		start = u64_stats_fetch_begin(&rxstats->syncp);
-		value = rxstats->msdu[tid];
+		value = u64_stats_read(&rxstats->msdu[tid]);
 	} while (u64_stats_fetch_retry(&rxstats->syncp, start));
 
 	return value;
@@ -2654,7 +2666,7 @@ static inline u64 sta_get_stats_bytes(struct ieee80211_sta_rx_stats *rxstats)
 
 	do {
 		start = u64_stats_fetch_begin(&rxstats->syncp);
-		value = rxstats->bytes;
+		value = u64_stats_read(&rxstats->bytes);
 	} while (u64_stats_fetch_retry(&rxstats->syncp, start));
 
 	return value;
diff --git a/net/mac80211/sta_info.h b/net/mac80211/sta_info.h
index 5288d5286651..b1edf8ed102f 100644
--- a/net/mac80211/sta_info.h
+++ b/net/mac80211/sta_info.h
@@ -434,8 +434,8 @@ struct ieee80211_sta_rx_stats {
 	s8 chain_signal_last[IEEE80211_MAX_CHAINS];
 	u32 last_rate;
 	struct u64_stats_sync syncp;
-	u64 bytes;
-	u64 msdu[IEEE80211_NUM_TIDS + 1];
+	u64_stats_t bytes;
+	u64_stats_t msdu[IEEE80211_NUM_TIDS + 1];
 };
 
 /*
-- 
2.51.0


^ permalink raw reply related

* Re: [RFC v2 wireless-next 3/3] wifi: mac80211: add initial UHR support
From: Jeff Johnson @ 2026-01-23 20:12 UTC (permalink / raw)
  To: Johannes Berg, linux-wireless; +Cc: Johannes Berg
In-Reply-To: <20251216172421.6b2e221c1cd6.Ieec940b58dbf8115dab7e1e24cb5513f52c8cb2f@changeid>

On 12/16/2025 8:23 AM, Johannes Berg wrote:
> @@ -4475,6 +4482,31 @@ int ieee80211_put_eht_cap(struct sk_buff *skb,
>  	return 0;
>  }
>  
> +int ieee80211_put_uhr_cap(struct sk_buff *skb,
> +			  struct ieee80211_sub_if_data *sdata,
> +			  const struct ieee80211_supported_band *sband)
> +{
> +	const struct ieee80211_sta_uhr_cap *uhr_cap =
> +		ieee80211_get_uhr_iftype_cap_vif(sband, &sdata->vif);
> +	int len;
> +
> +	if (!uhr_cap)
> +		return 0;
> +
> +        len = 2 + 1 + sizeof(struct ieee80211_uhr_capa) +
> +              sizeof(struct ieee80211_uhr_capa_phy);
> +        if (skb_tailroom(skb) < len)
> +                return -ENOBUFS;

for the above 4 lines:
ERROR:CODE_INDENT: code indent should use tabs where possible
WARNING:LEADING_SPACE: please, no spaces at the start of a line

> +
> +	skb_put_u8(skb, WLAN_EID_EXTENSION);
> +	skb_put_u8(skb, len - 2);
> +	skb_put_u8(skb, WLAN_EID_EXT_UHR_CAPA);
> +	skb_put_data(skb, &uhr_cap->mac, sizeof(uhr_cap->mac));
> +	skb_put_data(skb, &uhr_cap->phy, sizeof(uhr_cap->phy));
> +
> +	return 0;
> +}
> +
>  const char *ieee80211_conn_mode_str(enum ieee80211_conn_mode mode)
>  {
>  	static const char * const modes[] = {

^ permalink raw reply

* Re: [RFC v2 wireless-next 2/3] wifi: cfg80211: add initial UHR support
From: Johannes Berg @ 2026-01-23 20:11 UTC (permalink / raw)
  To: Jeff Johnson, linux-wireless
In-Reply-To: <460791be-6a4e-4098-bc74-effd31c8c593@oss.qualcomm.com>

On Fri, 2026-01-23 at 12:00 -0800, Jeff Johnson wrote:
> On 12/16/2025 8:23 AM, Johannes Berg wrote:
> > +	static const u32 mcs_divisors[] = {
> > +		[ 0] = 102399, /* 16.666666... */
> > +		[ 1] =  51201, /*  8.333333... */
> > +		[ 2] =  34134, /*  5.555555... */
> > +		[ 3] =  25599, /*  4.166666... */
> > +		[ 4] =  17067, /*  2.777777... */
> > +		[ 5] =  12801, /*  2.083333... */
> > +		[ 6] =  11377, /*  1.851725... */
> > +		[ 7] =  10239, /*  1.666666... */
> > +		[ 8] =   8532, /*  1.388888... */
> > +		[ 9] =   7680, /*  1.250000... */
> 
> you probably already know this, but checkpatch doesn't like the first 10:
> ERROR:SPACING: space prohibited after that open square bracket '['

It probably also doesn't like the leading spaces after the = sign, but
... I don't _really_ care. It's way more readable this way IMHO.

> (yes, I'm running your series through my automation since we are vetting our
> additional UHR changes on top of yours)

Do you want to post it all together (and maybe fix the uhr_oper thing
from your other mail too), or do you prefer I repost this?

(I'm confused our kernel-doc automation didn't pick up on the uhr_oper
thing. I guess I need to check that.)

johannes

^ permalink raw reply

* Re: [RFC v2 wireless-next 2/3] wifi: cfg80211: add initial UHR support
From: Jeff Johnson @ 2026-01-23 20:00 UTC (permalink / raw)
  To: Johannes Berg, linux-wireless; +Cc: Johannes Berg
In-Reply-To: <20251216172421.5f35a099e950.I26126bebd83c7ab17e99827489f946ceabb3521f@changeid>

On 12/16/2025 8:23 AM, Johannes Berg wrote:
> +	static const u32 mcs_divisors[] = {
> +		[ 0] = 102399, /* 16.666666... */
> +		[ 1] =  51201, /*  8.333333... */
> +		[ 2] =  34134, /*  5.555555... */
> +		[ 3] =  25599, /*  4.166666... */
> +		[ 4] =  17067, /*  2.777777... */
> +		[ 5] =  12801, /*  2.083333... */
> +		[ 6] =  11377, /*  1.851725... */
> +		[ 7] =  10239, /*  1.666666... */
> +		[ 8] =   8532, /*  1.388888... */
> +		[ 9] =   7680, /*  1.250000... */

you probably already know this, but checkpatch doesn't like the first 10:
ERROR:SPACING: space prohibited after that open square bracket '['

> +		[10] =   6828, /*  1.111111... */
> +		[11] =   6144, /*  1.000000... */
> +		[12] =   5690, /*  0.926106... */
> +		[13] =   5120, /*  0.833333... */
> +		[14] = 409600, /* 66.666666... */
> +		[15] = 204800, /* 33.333333... */
> +		[17] =  38400, /*  6.250180... */
> +		[19] =  19200, /*  3.125090... */
> +		[20] =  15360, /*  2.500000... */
> +		[23] =   9600, /*  1.562545... */

(yes, I'm running your series through my automation since we are vetting our
additional UHR changes on top of yours)

/jeff

^ permalink raw reply

* Re: [RFC v2 wireless-next 2/3] wifi: cfg80211: add initial UHR support
From: Jeff Johnson @ 2026-01-23 19:55 UTC (permalink / raw)
  To: Johannes Berg, linux-wireless; +Cc: Johannes Berg
In-Reply-To: <20251216172421.5f35a099e950.I26126bebd83c7ab17e99827489f946ceabb3521f@changeid>

On 12/16/2025 8:23 AM, Johannes Berg wrote:
> @@ -1486,6 +1522,8 @@ struct cfg80211_s1g_short_beacon {
>   * @he_cap: HE capabilities (or %NULL if HE isn't enabled)
>   * @eht_cap: EHT capabilities (or %NULL if EHT isn't enabled)
>   * @eht_oper: EHT operation IE (or %NULL if EHT isn't enabled)
> + * @uhr_capa: UHR capabilities (or %NULL if UHR isn't enabled)
> + * @uhr_capa: UHR operation (or %NULL if UHR isn't enabled)

s/capa/oper/

Warning: include/net/cfg80211.h:1576 struct member 'uhr_oper' not described in
'cfg80211_ap_settings'

>   * @ht_required: stations must support HT
>   * @vht_required: stations must support VHT
>   * @twt_responder: Enable Target Wait Time

^ permalink raw reply

* Re: [PATCH ath-next] wifi: ath12k: avoid setting 320MHZ support on non 6GHz band
From: Johannes Berg @ 2026-01-23 19:29 UTC (permalink / raw)
  To: Pablo MARTIN-GOMEZ, Nicolas Escande, ath12k; +Cc: linux-wireless
In-Reply-To: <b0e79f6c-36d7-4e49-9d43-8e305a598201@freebox.fr>

On Fri, 2026-01-23 at 20:21 +0100, Pablo MARTIN-GOMEZ wrote:
> 
> > > > +	} else {
> > > > +		phy_cap[0] &= ~IEEE80211_EHT_PHY_CAP0_320MHZ_IN_6GHZ;
> > > > +		phy_cap[1] &= ~IEEE80211_EHT_PHY_CAP1_BEAMFORMEE_SS_320MHZ_MASK;
> > > > +		phy_cap[2] &= ~IEEE80211_EHT_PHY_CAP2_SOUNDING_DIM_320MHZ_MASK;
> > > > +		phy_cap[6] &= ~IEEE80211_EHT_PHY_CAP6_MCS15_SUPP_320MHZ;
> > > > +	}
> > > If you want to clear all 320 MHz fields, you'll also have to clear
> > > IEEE80211_EHT_PHY_CAP7_NON_OFDMA_UL_MU_MIMO_320MHZ and
> > > IEEE80211_EHT_PHY_CAP7_MU_BEAMFORMER_320MHZ (not sure why it is not done
> > > in mac80211)
> > > 
> > This is, effectively, a firmware workaround. It doesn't belong into
> > mac80211. All other drivers just have their capabilities managed in the
> > driver anyway.
> > 
> > johannes
> 
> I wasn't talking about putting this patch in mac80211 (I've seen the 
> discussion on the patch Nicolas sent on linux-wireless), I'm talking 
> about the function `ieee80211_put_eht_cap` clearing the Beamformee SS 
> and Sounding Dimension fields but not the Non-OFDMA UL MU-MIMO and MU 
> Beamformer fields for each bandwidth.

Ah, you're asking why mac80211 doesn't clear those bits ... I guess it
just doesn't matter. If you're not on 320 MHz I'd guess the bits are
never checked, so it's not really relevant at all, although then could
argue that you only need the first line here as well.

johannes

^ permalink raw reply

* Re: [PATCH ath-next] wifi: ath12k: avoid setting 320MHZ support on non 6GHz band
From: Pablo MARTIN-GOMEZ @ 2026-01-23 19:21 UTC (permalink / raw)
  To: Johannes Berg, Nicolas Escande, ath12k; +Cc: linux-wireless
In-Reply-To: <bfed10db742d29f0609acb04d3354695f87b24e2.camel@sipsolutions.net>

On 23/01/2026 20:08, Johannes Berg wrote:
> On Fri, 2026-01-23 at 19:33 +0100, Pablo MARTIN-GOMEZ wrote:
>>> @@ -4901,8 +4902,14 @@ static void ath12k_wmi_eht_caps_parse(struct ath12k_pdev *pdev, u32 band,
>>>    	for (i = 0; i < WMI_MAX_EHTCAP_PHY_SIZE; i++)
>>>    		cap_band->eht_cap_phy_info[i] = le32_to_cpu(cap_phy_info[i]);
>>>    
>>> -	if (band == NL80211_BAND_6GHZ)
>>> +	if (band == NL80211_BAND_6GHZ) {
>>>    		cap_band->eht_cap_phy_info[0] |= support_320mhz;
>>> +	} else {
>>> +		phy_cap[0] &= ~IEEE80211_EHT_PHY_CAP0_320MHZ_IN_6GHZ;
>>> +		phy_cap[1] &= ~IEEE80211_EHT_PHY_CAP1_BEAMFORMEE_SS_320MHZ_MASK;
>>> +		phy_cap[2] &= ~IEEE80211_EHT_PHY_CAP2_SOUNDING_DIM_320MHZ_MASK;
>>> +		phy_cap[6] &= ~IEEE80211_EHT_PHY_CAP6_MCS15_SUPP_320MHZ;
>>> +	}
>> If you want to clear all 320 MHz fields, you'll also have to clear
>> IEEE80211_EHT_PHY_CAP7_NON_OFDMA_UL_MU_MIMO_320MHZ and
>> IEEE80211_EHT_PHY_CAP7_MU_BEAMFORMER_320MHZ (not sure why it is not done
>> in mac80211)
>>
> This is, effectively, a firmware workaround. It doesn't belong into
> mac80211. All other drivers just have their capabilities managed in the
> driver anyway.
>
> johannes

I wasn't talking about putting this patch in mac80211 (I've seen the 
discussion on the patch Nicolas sent on linux-wireless), I'm talking 
about the function `ieee80211_put_eht_cap` clearing the Beamformee SS 
and Sounding Dimension fields but not the Non-OFDMA UL MU-MIMO and MU 
Beamformer fields for each bandwidth.

Pablo


^ permalink raw reply

* Re: [PATCH ath-next] wifi: ath12k: avoid setting 320MHZ support on non 6GHz band
From: Johannes Berg @ 2026-01-23 19:08 UTC (permalink / raw)
  To: Pablo MARTIN-GOMEZ, Nicolas Escande, ath12k; +Cc: linux-wireless
In-Reply-To: <d26ee979-b5d5-4f50-b423-f8783122f603@freebox.fr>

On Fri, 2026-01-23 at 19:33 +0100, Pablo MARTIN-GOMEZ wrote:
> 
> > @@ -4901,8 +4902,14 @@ static void ath12k_wmi_eht_caps_parse(struct ath12k_pdev *pdev, u32 band,
> >   	for (i = 0; i < WMI_MAX_EHTCAP_PHY_SIZE; i++)
> >   		cap_band->eht_cap_phy_info[i] = le32_to_cpu(cap_phy_info[i]);
> >   
> > -	if (band == NL80211_BAND_6GHZ)
> > +	if (band == NL80211_BAND_6GHZ) {
> >   		cap_band->eht_cap_phy_info[0] |= support_320mhz;
> > +	} else {
> > +		phy_cap[0] &= ~IEEE80211_EHT_PHY_CAP0_320MHZ_IN_6GHZ;
> > +		phy_cap[1] &= ~IEEE80211_EHT_PHY_CAP1_BEAMFORMEE_SS_320MHZ_MASK;
> > +		phy_cap[2] &= ~IEEE80211_EHT_PHY_CAP2_SOUNDING_DIM_320MHZ_MASK;
> > +		phy_cap[6] &= ~IEEE80211_EHT_PHY_CAP6_MCS15_SUPP_320MHZ;
> > +	}
> If you want to clear all 320 MHz fields, you'll also have to clear 
> IEEE80211_EHT_PHY_CAP7_NON_OFDMA_UL_MU_MIMO_320MHZ and 
> IEEE80211_EHT_PHY_CAP7_MU_BEAMFORMER_320MHZ (not sure why it is not done 
> in mac80211)
> 

This is, effectively, a firmware workaround. It doesn't belong into
mac80211. All other drivers just have their capabilities managed in the
driver anyway.

johannes

^ permalink raw reply

* [PATCH] wifi: wil6210: add missing mutex protection in WMI event handlers
From: Ziyi Guo @ 2026-01-23 19:01 UTC (permalink / raw)
  To: Kalle Valo; +Cc: Jeff Johnson, linux-wireless, linux-kernel, Ziyi Guo

wmi_evt_auth_status() and wmi_evt_reassoc_status() call wil6210_disconnect
in their error paths without holding wil->mutex. This eventually leads to
wil_ring_fini_tx() and wil_ring_free_edma() being called, both of which
require the mutex to be held as indicated by lockdep_assert_held().

Other callers of wil6210_disconnect() (wil_cfg80211_del_station,
wil_vif_remove, wil_reset) properly acquire the mutex before calling.

Add mutex_lock()/mutex_unlock() around the wil6210_disconnect() calls
in the WMI event handlers to fix the missing lock protection.

Signed-off-by: Ziyi Guo <n7l8m4@u.northwestern.edu>
---
 drivers/net/wireless/ath/wil6210/wmi.c | 4 ++++
 1 file changed, 4 insertions(+)

diff --git a/drivers/net/wireless/ath/wil6210/wmi.c b/drivers/net/wireless/ath/wil6210/wmi.c
index 6d376f85fbde..2bd09a225ad0 100644
--- a/drivers/net/wireless/ath/wil6210/wmi.c
+++ b/drivers/net/wireless/ath/wil6210/wmi.c
@@ -1703,7 +1703,9 @@ wmi_evt_auth_status(struct wil6210_vif *vif, int id, void *d, int len)
 	return;
 
 fail:
+	mutex_lock(&wil->mutex);
 	wil6210_disconnect(vif, NULL, WLAN_REASON_PREV_AUTH_NOT_VALID);
+	mutex_unlock(&wil->mutex);
 }
 
 static void
@@ -1832,7 +1834,9 @@ wmi_evt_reassoc_status(struct wil6210_vif *vif, int id, void *d, int len)
 	return;
 
 fail:
+	mutex_lock(&wil->mutex);
 	wil6210_disconnect(vif, NULL, WLAN_REASON_PREV_AUTH_NOT_VALID);
+	mutex_unlock(&wil->mutex);
 }
 
 static void
-- 
2.34.1


^ permalink raw reply related

* Re: [PATCH ath-next] wifi: ath12k: avoid setting 320MHZ support on non 6GHz band
From: Pablo MARTIN-GOMEZ @ 2026-01-23 18:33 UTC (permalink / raw)
  To: Nicolas Escande, ath12k; +Cc: linux-wireless
In-Reply-To: <20260123144224.2216923-1-nico.escande@gmail.com>

Hello,

On 23/01/2026 15:42, Nicolas Escande wrote:
> On a split phy qcn9274 (2.4GHz + 5GHz low), "iw phy" reports 320MHz
> realated features on the 5GHz band while it should not:
>
>      Wiphy phy1
>      [...]
>          Band 2:
>      [...]
>              EHT Iftypes: managed
>      [...]
>                  EHT PHY Capabilities: (0xe2ffdbe018778000):
>                      320MHz in 6GHz Supported
>      [...]
>                      Beamformee SS (320MHz): 7
>      [...]
>                      Number Of Sounding Dimensions (320MHz): 3
>      [...]
>                  EHT MCS/NSS: (0x22222222222222222200000000):
>
> This is also reflected in the beacons sent by a mesh interface started on
> that band. They erroneously advertise 320MHZ support too.
>
> This should not happen as the spec at section 9.4.2.323.3 says we should
> not set the 320MHz related fields when not operating on a 6GHz band.
> For example it says about Bit 0 "Support For 320 MHz In 6 GHz"
>
>    "Reserved if the EHT Capabilities element is indicating capabilities for
>     the 2.4 GHz or 5 GHz bands."
>
> Fix this by clearing the related bits when converting from WMI eht phy
> capabilities to mac80211 phy capabilities, for bands other than 6GHz.
>
> Tested-on: QCN9274 hw2.0 PCI WLAN.WBE.1.3.1-00218-QCAHKSWPL_SILICONZ-1
>
> Signed-off-by: Nicolas Escande <nico.escande@gmail.com>
> ---
>   drivers/net/wireless/ath/ath12k/wmi.c | 9 ++++++++-
>   1 file changed, 8 insertions(+), 1 deletion(-)
>
> diff --git a/drivers/net/wireless/ath/ath12k/wmi.c b/drivers/net/wireless/ath/ath12k/wmi.c
> index 84c29e4896a4..14947fdb9813 100644
> --- a/drivers/net/wireless/ath/ath12k/wmi.c
> +++ b/drivers/net/wireless/ath/ath12k/wmi.c
> @@ -4888,6 +4888,7 @@ static void ath12k_wmi_eht_caps_parse(struct ath12k_pdev *pdev, u32 band,
>   				       __le32 cap_info_internal)
>   {
>   	struct ath12k_band_cap *cap_band = &pdev->cap.band[band];
> +	u8 *phy_cap = (u8 *)&cap_band->eht_cap_phy_info[0];
>   	u32 support_320mhz;
>   	u8 i;
>   
> @@ -4901,8 +4902,14 @@ static void ath12k_wmi_eht_caps_parse(struct ath12k_pdev *pdev, u32 band,
>   	for (i = 0; i < WMI_MAX_EHTCAP_PHY_SIZE; i++)
>   		cap_band->eht_cap_phy_info[i] = le32_to_cpu(cap_phy_info[i]);
>   
> -	if (band == NL80211_BAND_6GHZ)
> +	if (band == NL80211_BAND_6GHZ) {
>   		cap_band->eht_cap_phy_info[0] |= support_320mhz;
> +	} else {
> +		phy_cap[0] &= ~IEEE80211_EHT_PHY_CAP0_320MHZ_IN_6GHZ;
> +		phy_cap[1] &= ~IEEE80211_EHT_PHY_CAP1_BEAMFORMEE_SS_320MHZ_MASK;
> +		phy_cap[2] &= ~IEEE80211_EHT_PHY_CAP2_SOUNDING_DIM_320MHZ_MASK;
> +		phy_cap[6] &= ~IEEE80211_EHT_PHY_CAP6_MCS15_SUPP_320MHZ;
> +	}
If you want to clear all 320 MHz fields, you'll also have to clear 
IEEE80211_EHT_PHY_CAP7_NON_OFDMA_UL_MU_MIMO_320MHZ and 
IEEE80211_EHT_PHY_CAP7_MU_BEAMFORMER_320MHZ (not sure why it is not done 
in mac80211)
>   
>   	cap_band->eht_mcs_20_only = le32_to_cpu(supp_mcs[0]);
>   	cap_band->eht_mcs_80 = le32_to_cpu(supp_mcs[1]);

Best regards,

Pablo MG


^ permalink raw reply

* [PATCH v2] wifi: ath10k: fix lock protection in ath10k_wmi_event_peer_sta_ps_state_chg()
From: Ziyi Guo @ 2026-01-23 17:56 UTC (permalink / raw)
  To: Jeff Johnson; +Cc: linux-wireless, ath10k, linux-kernel, Ziyi Guo

ath10k_wmi_event_peer_sta_ps_state_chg() uses lockdep_assert_held() to
assert that ar->data_lock should be held by the caller, but neither
ath10k_wmi_10_2_op_rx() nor ath10k_wmi_10_4_op_rx() acquire this lock
before calling this function.

The field arsta->peer_ps_state is documented as protected by
ar->data_lock in core.h, and other accessors (ath10k_peer_ps_state_disable,
ath10k_dbg_sta_read_peer_ps_state) properly acquire this lock.

Add spin_lock_bh()/spin_unlock_bh() around the peer_ps_state update,
and remove the lockdep_assert_held() to be aligned with new locking,
following the pattern used by other WMI event handlers in the driver.

Signed-off-by: Ziyi Guo <n7l8m4@u.northwestern.edu>
---
v2:
 - Remove lockdep_assert_held() as suggested, since
   we are now taking the lock internally.

 drivers/net/wireless/ath/ath10k/wmi.c | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/drivers/net/wireless/ath/ath10k/wmi.c b/drivers/net/wireless/ath/ath10k/wmi.c
index b4aad6604d6d..061a2fa8f00f 100644
--- a/drivers/net/wireless/ath/ath10k/wmi.c
+++ b/drivers/net/wireless/ath/ath10k/wmi.c
@@ -5289,7 +5289,6 @@ ath10k_wmi_event_peer_sta_ps_state_chg(struct ath10k *ar, struct sk_buff *skb)
 	struct ath10k_sta *arsta;
 	u8 peer_addr[ETH_ALEN];
 
-	lockdep_assert_held(&ar->data_lock);
 
 	ev = (struct wmi_peer_sta_ps_state_chg_event *)skb->data;
 	ether_addr_copy(peer_addr, ev->peer_macaddr.addr);
@@ -5305,7 +5304,9 @@ ath10k_wmi_event_peer_sta_ps_state_chg(struct ath10k *ar, struct sk_buff *skb)
 	}
 
 	arsta = (struct ath10k_sta *)sta->drv_priv;
+	spin_lock_bh(&ar->data_lock);
 	arsta->peer_ps_state = __le32_to_cpu(ev->peer_ps_state);
+	spin_unlock_bh(&ar->data_lock);
 
 exit:
 	rcu_read_unlock();
-- 
2.34.1


^ permalink raw reply related


This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox