* [PATCH 2/2] wifi: mt76: mt7996: bound the device EEPROM address before the EFUSE copy
From: Bryam Vargas via B4 Relay @ 2026-06-25 12:10 UTC (permalink / raw)
To: Lorenzo Bianconi, Ryder Lee, Felix Fietkau
Cc: Sean Wang, linux-mediatek, Shayne Chen, linux-wireless,
linux-kernel
In-Reply-To: <20260625-b4-disp-16f99062-v1-0-aee52ecf61b9@proton.me>
From: Bryam Vargas <hexlabsecurity@proton.me>
mt7996_mcu_get_eeprom() derives the destination of the EFUSE/EXT block
copy from the address reported by the MCU response (event->addr, a
device-controlled __le32) and clamps only the copy length, never the
destination offset into dev->mt76.eeprom.data. A malicious or
malfunctioning device can report an arbitrary address and drive an
out-of-bounds write of up to MT7996_EXT_EEPROM_BLOCK_SIZE bytes past
eeprom.data.
Reject a response whose address would place the copy outside eeprom.data
before deriving the destination pointer. Devices that echo the requested
in-bounds offset are unaffected.
Fixes: 98686cd21624 ("wifi: mt76: mt7996: add driver for MediaTek Wi-Fi 7 (802.11be) devices")
Cc: stable@vger.kernel.org
Signed-off-by: Bryam Vargas <hexlabsecurity@proton.me>
---
drivers/net/wireless/mediatek/mt76/mt7996/mcu.c | 9 ++++++++-
1 file changed, 8 insertions(+), 1 deletion(-)
diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/mcu.c b/drivers/net/wireless/mediatek/mt76/mt7996/mcu.c
index f119f023bcd5..01c9adbca68b 100644
--- a/drivers/net/wireless/mediatek/mt76/mt7996/mcu.c
+++ b/drivers/net/wireless/mediatek/mt76/mt7996/mcu.c
@@ -4345,11 +4345,18 @@ int mt7996_mcu_get_eeprom(struct mt7996_dev *dev, u32 offset, u8 *buf, u32 buf_l
event = (struct mt7996_mcu_eeprom_access_event *)skb->data;
if (event->valid) {
u32 ret_len = le32_to_cpu(event->eeprom.ext_eeprom.data_len);
+ u32 block = mode == EEPROM_MODE_EXT ? MT7996_EXT_EEPROM_BLOCK_SIZE :
+ MT7996_EEPROM_BLOCK_SIZE;
addr = le32_to_cpu(event->addr);
- if (!buf)
+ if (!buf) {
+ if (addr > dev->mt76.eeprom.size - block) {
+ dev_kfree_skb(skb);
+ return -EINVAL;
+ }
buf = (u8 *)dev->mt76.eeprom.data + addr;
+ }
switch (mode) {
case EEPROM_MODE_EFUSE:
--
2.43.0
^ permalink raw reply related
* [PATCH 1/2] wifi: mt76: mt7915: bound the device EEPROM address before the EFUSE copy
From: Bryam Vargas via B4 Relay @ 2026-06-25 12:10 UTC (permalink / raw)
To: Lorenzo Bianconi, Ryder Lee, Felix Fietkau
Cc: Sean Wang, linux-mediatek, Shayne Chen, linux-wireless,
linux-kernel
In-Reply-To: <20260625-b4-disp-16f99062-v1-0-aee52ecf61b9@proton.me>
From: Bryam Vargas <hexlabsecurity@proton.me>
mt7915_mcu_get_eeprom() copies a fixed EFUSE block into the driver's
dev->mt76.eeprom.data buffer at the offset reported by the MCU response
(res->addr, a device-controlled __le32) without checking it against the
buffer size. A malicious or malfunctioning device can report an arbitrary
address and drive a 16-byte out-of-bounds write past eeprom.data.
Reject a response whose address would place the copy outside eeprom.data
before deriving the destination pointer. Devices that echo the requested
in-bounds offset are unaffected.
Fixes: e57b7901469f ("mt76: add mac80211 driver for MT7915 PCIe-based chipsets")
Cc: stable@vger.kernel.org
Signed-off-by: Bryam Vargas <hexlabsecurity@proton.me>
---
drivers/net/wireless/mediatek/mt76/mt7915/mcu.c | 11 +++++++++--
1 file changed, 9 insertions(+), 2 deletions(-)
diff --git a/drivers/net/wireless/mediatek/mt76/mt7915/mcu.c b/drivers/net/wireless/mediatek/mt76/mt7915/mcu.c
index 4a381d351e61..f39eae3c4c1c 100644
--- a/drivers/net/wireless/mediatek/mt76/mt7915/mcu.c
+++ b/drivers/net/wireless/mediatek/mt76/mt7915/mcu.c
@@ -2909,8 +2909,15 @@ int mt7915_mcu_get_eeprom(struct mt7915_dev *dev, u32 offset, u8 *read_buf)
return ret;
res = (struct mt7915_mcu_eeprom_info *)skb->data;
- if (!buf)
- buf = dev->mt76.eeprom.data + le32_to_cpu(res->addr);
+ if (!buf) {
+ u32 addr = le32_to_cpu(res->addr);
+
+ if (addr > dev->mt76.eeprom.size - MT7915_EEPROM_BLOCK_SIZE) {
+ dev_kfree_skb(skb);
+ return -EINVAL;
+ }
+ buf = dev->mt76.eeprom.data + addr;
+ }
memcpy(buf, res->data, MT7915_EEPROM_BLOCK_SIZE);
dev_kfree_skb(skb);
--
2.43.0
^ permalink raw reply related
* [PATCH 0/2] wifi: mt76: bound the device-reported EEPROM address
From: Bryam Vargas via B4 Relay @ 2026-06-25 12:10 UTC (permalink / raw)
To: Lorenzo Bianconi, Ryder Lee, Felix Fietkau
Cc: Sean Wang, linux-mediatek, Shayne Chen, linux-wireless,
linux-kernel
Both mt76 get_eeprom handlers copy a device-reported EFUSE block into
dev->mt76.eeprom.data at an offset taken from the MCU response (res->addr /
event->addr, a device-controlled __le32). They clamp the copy length but
never the destination offset, so an adapter that reports an out-of-range
address drives an out-of-bounds write past eeprom.data -- 16 bytes on mt7915,
up to 1024 on mt7996. Both patches reject such an address before deriving the
pointer; a device that echoes the requested in-bounds offset is unaffected.
It is adapter-side only -- there is no unprivileged user path -- so this
hardens against a malicious or compromised device, not a remote attacker.
An out-of-tree KASAN module that reproduces each handler's destination
arithmetic faults the unpatched path (slab-out-of-bounds write past
eeprom.data) and runs clean both with the bound and on an in-range control.
---
Bryam Vargas (2):
wifi: mt76: mt7915: bound the device EEPROM address before the EFUSE copy
wifi: mt76: mt7996: bound the device EEPROM address before the EFUSE copy
drivers/net/wireless/mediatek/mt76/mt7915/mcu.c | 11 +++++++++--
drivers/net/wireless/mediatek/mt76/mt7996/mcu.c | 9 ++++++++-
2 files changed, 17 insertions(+), 3 deletions(-)
---
base-commit: 502d801f0ab03e4f32f9a33d203154ce84887921
change-id: 20260625-b4-disp-16f99062-0dd6169db97b
Best regards,
--
Bryam Vargas <hexlabsecurity@proton.me>
^ permalink raw reply
* Re: [PATCH] wifi: cfg80211: replace BOOL_TO_STR macro with str_true_false()
From: Johannes Berg @ 2026-06-25 11:22 UTC (permalink / raw)
To: Serhat Kumral, linux-wireless; +Cc: linux-kernel
In-Reply-To: <20260624204938.15222-1-serhatkumral1@gmail.com>
On Wed, 2026-06-24 at 23:49 +0300, Serhat Kumral wrote:
> Remove the local BOOL_TO_STR macro and replace all its usages with
> the kernel's str_true_false() helper from <linux/string_choices.h>.
>
> No functional change intended.
>
I believe this breaks trace-cmd reporting. Please check and resend
indicating that you have.
johannes
^ permalink raw reply
* Re: [PATCH v10] Add device-specific reset for Qualcomm devices
From: Jose Ignacio Tornos Martinez @ 2026-06-25 10:35 UTC (permalink / raw)
To: baochen.qiang
Cc: alex, ath11k, ath12k, bhelgaas, jjohnson, jtornosm, linux-kernel,
linux-pci, linux-wireless, mani, mhi
In-Reply-To: <4cdfb71b-2ef8-4985-8294-c4a29e37faa3@oss.qualcomm.com>
Hello Baochen and Mani,
> QUALCOMM_WIFI_PCIE_SOC_GLOBAL_RESET is beyond the first 4K bar area hence requires MHI
> wakeup before accessing, see [1]. the wakeup callback for WCN6855 is
> ath11k_pci_bus_wake_up() which calls mhi_device_get_sync(). Not sure how this can be done
> here. Maybe Mani can provide some hints?
I've analyzed the driver code and see that ath11k_pci_power_down() calls
ath11k_pci_force_wake() before sw_reset().
I can add the same force_wake sequence to the WiFi quirk before accessing
the reset register:
/* Force wake before accessing registers beyond 4K boundary */
iowrite32(1, bar + QUALCOMM_WIFI_PCIE_SOC_WAKE_PCIE_LOCAL_REG); // 0x3004
ioread32(bar + QUALCOMM_WIFI_PCIE_SOC_WAKE_PCIE_LOCAL_REG); // Flush
msleep(5);
With this addition, both WCN6855 (ath11k) and WCN7850 (ath12k) show successful
reset and shutdown cycles in VFIO scenarios, same stability as without it.
Do you consider this addition necessary, or is the current v10 implementation
sufficient given that testing shows direct register access works without
wakeup in VFIO scenarios (where no driver is loaded)?
If you recommend including it, I can send v11 with the force_wake sequence
added.
Thanks
Best regards
Jose Ignacio
^ permalink raw reply
* [PATCH wireless-next] wifi: cfg80211: fix regulatory.db async firmware request blocking __usermodehelper_disable()
From: Kavita Kavita @ 2026-06-25 9:29 UTC (permalink / raw)
To: johannes; +Cc: linux-wireless
cfg80211 schedules an asynchronous request_firmware() work item for
regulatory.db via request_firmware_work_func(). When the direct
firmware load fails, _request_firmware() falls back to the sysfs
fallback path via firmware_fallback_sysfs(), which blocks indefinitely
in wait_for_completion_killable_timeout() waiting for userspace to
supply the firmware through the sysfs interface.
While this work item is pending, any caller of
__usermodehelper_disable() will deadlock attempting to acquire the
usermodehelper rwsem for writing, since the sysfs firmware fallback
path holds the rwsem for reading and is blocked waiting for userspace
response that can never arrive while usermode helpers are being
disabled.
Observed call traces where system suspend blocked due to regulatory.db
async firmware request:
kworker/6:3 (pid 194) holding usermodehelper rwsem read lock, blocked
waiting for userspace firmware response:
wait_for_completion_killable_timeout+0x48
firmware_fallback_sysfs+0x270
_request_firmware+0x790
request_firmware_work_func+0x44
process_one_work[jt]+0x59c
worker_thread+0x260
kthread+0x150
ret_from_fork+0x10
Caller blocked in __usermodehelper_disable() during system suspend:
rwsem_down_write_slowpath+0x768
down_write+0x98
__usermodehelper_disable+0x3c
freeze_processes+0x18
pm_suspend+0x320
state_store+0x104
kernfs_fop_write_iter[jt]+0x168
vfs_write+0x270
ksys_write+0x78
Any service or kernel subsystem invoking __usermodehelper_disable() is
susceptible to this hang as long as the regulatory.db sysfs fallback
request remains outstanding.
Fix this by replacing the unconditional uevent-based load with a
two-step approach. First, attempt a synchronous load via
request_firmware_direct() at init time. This is fast and
non-blocking, if the file is present in standard paths it is loaded
immediately with no delay. If not found, the load is deferred to
wiphy_regulatory_register() and triggered via
firmware_request_nowait_nowarn() only when the first non-self-managed
wiphy registers.
For self-managed drivers (REGULATORY_WIPHY_SELF_MANAGED), the hang is
avoided because wiphy_regulatory_register() handles them separately
and the deferred load path is never reached, so the file load is not
attempted at all. For this case, regulatory information is obtained
from driver/firmware during wiphy registration. For non-self-managed
drivers, the file is required and is expected to be present. The
deferred load via firmware_request_nowait_nowarn() is triggered only
when the first such wiphy registers. This ensures the database is
loaded only when it is actually needed, avoiding the sysfs fallback
path on systems where the file is absent at init time.
Also refactor regdb_fw_cb() into two functions: regdb_load() which
validates and stores the firmware image, and regdb_fw_cb_restore()
which is the async callback that calls restore_regulatory_settings()
to replay all pending regulatory requests (country hints from core,
user, driver and country IE) that arrived while the database was not
yet available.
NOTE:
This issue was observed on Android platforms where regulatory.db is
absent.
Steps to reproduce on Android platforms:
1. Power off the device completely.
2. Connect the charger; the device enters off-mode charging.
3. While in off-mode charging, short press the power key.
Signed-off-by: Kavita Kavita <kavita.kavita@oss.qualcomm.com>
---
net/wireless/reg.c | 132 ++++++++++++++++++++++++++-------------------
1 file changed, 78 insertions(+), 54 deletions(-)
diff --git a/net/wireless/reg.c b/net/wireless/reg.c
index 1e8214d6b6d8..d7b864d32ba1 100644
--- a/net/wireless/reg.c
+++ b/net/wireless/reg.c
@@ -988,78 +988,98 @@ static int query_regdb(const char *alpha2)
return -ENODATA;
}
-static void regdb_fw_cb(const struct firmware *fw, void *context)
+MODULE_FIRMWARE("regulatory.db");
+
+/* Validate and store firmware image as regdb. Used by all load paths. */
+static int regdb_load(const struct firmware *fw)
{
- int set_error = 0;
- bool restore = true;
void *db;
- if (!fw) {
- pr_info("failed to load regulatory.db\n");
- set_error = -ENODATA;
- } else if (!valid_regdb(fw->data, fw->size)) {
+ ASSERT_RTNL();
+
+ if (!valid_regdb(fw->data, fw->size)) {
pr_info("loaded regulatory.db is malformed or signature is missing/invalid\n");
- set_error = -EINVAL;
+ return -EINVAL;
}
+ db = kmemdup(fw->data, fw->size, GFP_KERNEL);
+ if (!db)
+ return -ENOMEM;
+
+ regdb = db;
+ return 0;
+}
+
+static void regdb_fw_cb_restore(const struct firmware *fw, void *context)
+{
+ int err;
+
rtnl_lock();
- if (regdb && !IS_ERR(regdb)) {
- /* negative case - a bug
- * positive case - can happen due to race in case of multiple cb's in
- * queue, due to usage of asynchronous callback
- *
- * Either case, just restore and free new db.
- */
- } else if (set_error) {
- regdb = ERR_PTR(set_error);
- } else if (fw) {
- db = kmemdup(fw->data, fw->size, GFP_KERNEL);
- if (db) {
- regdb = db;
- restore = context && query_regdb(context);
- } else {
- restore = true;
- }
- }
- if (restore)
+ /* Skip if a concurrent wiphy registration already loaded the db. */
+ if (regdb && !IS_ERR(regdb))
+ goto out_unlock;
+
+ /*
+ * Replay all pending regulatory hints that arrived while the
+ * database was not yet available, regardless of load outcome.
+ */
+ if (!fw) {
+ pr_info("failed to load regulatory.db\n");
+ regdb = ERR_PTR(-ENODATA);
restore_regulatory_settings(true, false);
+ goto out_unlock;
+ }
- rtnl_unlock();
+ err = regdb_load(fw);
+ if (err)
+ regdb = ERR_PTR(err);
- kfree(context);
+ restore_regulatory_settings(true, false);
+out_unlock:
+ rtnl_unlock();
release_firmware(fw);
}
-MODULE_FIRMWARE("regulatory.db");
-
static int query_regdb_file(const char *alpha2)
{
+ const struct firmware *fw;
int err;
ASSERT_RTNL();
- if (regdb)
+ if (regdb && !IS_ERR(regdb))
return query_regdb(alpha2);
- alpha2 = kmemdup(alpha2, 2, GFP_KERNEL);
- if (!alpha2)
- return -ENOMEM;
+ /*
+ * Load failed or async udev load in progress. If -EINPROGRESS,
+ * hints are preserved and replayed once the udev load completes.
+ */
+ if (IS_ERR(regdb) && PTR_ERR(regdb) != -EINPROGRESS)
+ return PTR_ERR(regdb);
- err = request_firmware_nowait(THIS_MODULE, true, "regulatory.db",
- ®_fdev->dev, GFP_KERNEL,
- (void *)alpha2, regdb_fw_cb);
+ /*
+ * Preserve the hint if the file is not found on direct paths;
+ * an async udev load will be triggered on wiphy registration
+ * and will replay all pending hints on completion.
+ */
+ err = request_firmware_direct(&fw, "regulatory.db", ®_fdev->dev);
if (err)
- kfree(alpha2);
+ return 0;
+ err = regdb_load(fw);
+ release_firmware(fw);
+ if (err) {
+ regdb = ERR_PTR(err);
+ return err;
+ }
- return err;
+ return query_regdb(alpha2);
}
int reg_reload_regdb(void)
{
const struct firmware *fw;
- void *db;
int err;
const struct ieee80211_regdomain *current_regdomain;
struct regulatory_request *request;
@@ -1068,21 +1088,14 @@ int reg_reload_regdb(void)
if (err)
return err;
- if (!valid_regdb(fw->data, fw->size)) {
- err = -ENODATA;
- goto out;
- }
-
- db = kmemdup(fw->data, fw->size, GFP_KERNEL);
- if (!db) {
- err = -ENOMEM;
- goto out;
- }
-
rtnl_lock();
if (!IS_ERR_OR_NULL(regdb))
kfree(regdb);
- regdb = db;
+ err = regdb_load(fw);
+ if (err) {
+ regdb = ERR_PTR(err);
+ goto out_unlock;
+ }
/* reset regulatory domain */
current_regdomain = get_cfg80211_regdom();
@@ -1103,7 +1116,6 @@ int reg_reload_regdb(void)
out_unlock:
rtnl_unlock();
- out:
release_firmware(fw);
return err;
}
@@ -4085,6 +4097,8 @@ void wiphy_regulatory_register(struct wiphy *wiphy)
{
struct regulatory_request *lr = get_last_request();
+ ASSERT_RTNL();
+
/* self-managed devices ignore beacon hints and country IE */
if (wiphy->regulatory_flags & REGULATORY_WIPHY_SELF_MANAGED) {
wiphy->regulatory_flags |= REGULATORY_DISABLE_BEACON_HINTS |
@@ -4097,6 +4111,16 @@ void wiphy_regulatory_register(struct wiphy *wiphy)
*/
if (lr->initiator == NL80211_REGDOM_SET_BY_USER)
reg_call_notifier(wiphy, lr);
+ } else if (!regdb) {
+ /*
+ * regulatory.db not yet loaded; trigger an async udev
+ * request to load it when the first wiphy registers.
+ */
+ if (!firmware_request_nowait_nowarn(THIS_MODULE,
+ "regulatory.db",
+ ®_fdev->dev, GFP_KERNEL,
+ NULL, regdb_fw_cb_restore))
+ regdb = ERR_PTR(-EINPROGRESS);
}
if (!reg_dev_ignore_cell_hint(wiphy))
base-commit: 972c4dd19cb92e03d75b66c426cfade07582a1ba
--
2.34.1
^ permalink raw reply related
* [PATCH rtw-next 10/10] wifi: rtw89: wow: only WiFi 6 chips initialize RF registers in WoWLAN mode
From: Ping-Ke Shih @ 2026-06-25 6:15 UTC (permalink / raw)
To: linux-wireless; +Cc: gary.chang, timlee, dian_syuan0116, kevin_yang
In-Reply-To: <20260625061545.44808-1-pkshih@realtek.com>
From: Chih-Kang Chang <gary.chang@realtek.com>
Only the WiFi 6 chips need to initialize RF register when WoWLAN download
FW for some power save issue. Applying the same initialization flow to
WiFi 7 chips might trigger the error
'RF parameters exceed size. path=1, idx=1500.'.
This happens because normal mode uses rtw89_phy_config_rf_reg_v1(), which
skips registers with addresses below 0x100. However, WoWLAN mode uses
rtw89_phy_config_rf_reg_noio(), and WiFi 7 chips do not satisfy the
rtw89_chip_rf_v1() condition. As a result, more RF registers are
configured, causing the size overflow error.
Signed-off-by: Chih-Kang Chang <gary.chang@realtek.com>
Signed-off-by: Ping-Ke Shih <pkshih@realtek.com>
---
drivers/net/wireless/realtek/rtw89/wow.c | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/drivers/net/wireless/realtek/rtw89/wow.c b/drivers/net/wireless/realtek/rtw89/wow.c
index 8dadd8df4fc6..a7539f91264d 100644
--- a/drivers/net/wireless/realtek/rtw89/wow.c
+++ b/drivers/net/wireless/realtek/rtw89/wow.c
@@ -1299,7 +1299,8 @@ static int rtw89_wow_swap_fw(struct rtw89_dev *rtwdev, bool wow)
if (disable_intr_for_dlfw)
rtw89_hci_enable_intr(rtwdev);
- rtw89_phy_init_rf_reg(rtwdev, true);
+ if (chip->chip_gen == RTW89_CHIP_AX)
+ rtw89_phy_init_rf_reg(rtwdev, true);
ret = rtw89_fw_h2c_role_maintain(rtwdev, rtwvif_link, rtwsta_link,
RTW89_ROLE_FW_RESTORE);
--
2.25.1
^ permalink raw reply related
* [PATCH rtw-next 09/10] wifi: rtw89: wow: add QoS control field to WoWLAN ARP response for MLO
From: Ping-Ke Shih @ 2026-06-25 6:15 UTC (permalink / raw)
To: linux-wireless; +Cc: gary.chang, timlee, dian_syuan0116, kevin_yang
In-Reply-To: <20260625061545.44808-1-pkshih@realtek.com>
From: Chin-Yen Lee <timlee@realtek.com>
Some MLO APs expect WoWLAN ARP response frames to be transmitted as
QoS data frames and may discard frames that do not contain a QoS
Control field.
Add a QoS Control field and use the QoS Data subtype when generating
WoWLAN ARP responses for MLD vifs. Keep the existing frame format
unchanged for non-MLO connections.
This allows WoWLAN ARP responses to be accepted by MLO APs while
preserving compatibility with legacy APs.
Signed-off-by: Chin-Yen Lee <timlee@realtek.com>
Signed-off-by: Ping-Ke Shih <pkshih@realtek.com>
---
drivers/net/wireless/realtek/rtw89/fw.c | 31 +++++++++++++++++++------
1 file changed, 24 insertions(+), 7 deletions(-)
diff --git a/drivers/net/wireless/realtek/rtw89/fw.c b/drivers/net/wireless/realtek/rtw89/fw.c
index 5d335105b589..f40c2f1c4dd7 100644
--- a/drivers/net/wireless/realtek/rtw89/fw.c
+++ b/drivers/net/wireless/realtek/rtw89/fw.c
@@ -2928,6 +2928,7 @@ static struct sk_buff *rtw89_sa_query_get(struct rtw89_dev *rtwdev,
static struct sk_buff *rtw89_arp_response_get(struct rtw89_dev *rtwdev,
struct rtw89_vif_link *rtwvif_link)
{
+ struct ieee80211_vif *vif = rtwvif_to_vif(rtwvif_link->rtwvif);
struct rtw89_vif *rtwvif = rtwvif_link->rtwvif;
u8 sec_hdr_len = rtw89_wow_get_sec_hdr_len(rtwdev);
struct rtw89_wow_param *rtw_wow = &rtwdev->wow;
@@ -2935,26 +2936,42 @@ static struct sk_buff *rtw89_arp_response_get(struct rtw89_dev *rtwdev,
struct rtw89_arp_rsp *arp_skb;
struct arphdr *arp_hdr;
struct sk_buff *skb;
- __le16 fc;
+ bool with_qos;
+ u16 fc;
- skb = dev_alloc_skb(sizeof(*hdr) + sec_hdr_len + sizeof(*arp_skb));
+ with_qos = ieee80211_vif_is_mld(vif);
+
+ rtw89_debug(rtwdev, RTW89_DBG_WOW, "[arp_reply] with qos field: %s\n",
+ str_yes_no(with_qos));
+
+ skb = dev_alloc_skb(sizeof(*hdr) + sec_hdr_len + sizeof(*arp_skb) +
+ (with_qos ? 2 : 0));
if (!skb)
return NULL;
hdr = skb_put_zero(skb, sizeof(*hdr));
+ fc = IEEE80211_FTYPE_DATA | IEEE80211_FCTL_TODS;
+
if (rtw_wow->ptk_alg)
- fc = cpu_to_le16(IEEE80211_FTYPE_DATA | IEEE80211_FCTL_TODS |
- IEEE80211_FCTL_PROTECTED);
+ fc |= IEEE80211_FCTL_PROTECTED;
+
+ if (with_qos)
+ fc |= IEEE80211_STYPE_QOS_DATA;
else
- fc = cpu_to_le16(IEEE80211_FTYPE_DATA | IEEE80211_FCTL_TODS);
+ fc |= IEEE80211_STYPE_DATA;
+
+ hdr->frame_control = cpu_to_le16(fc);
- hdr->frame_control = fc;
ether_addr_copy(hdr->addr1, rtwvif_link->bssid);
ether_addr_copy(hdr->addr2, rtwvif_link->mac_addr);
ether_addr_copy(hdr->addr3, rtwvif_link->bssid);
- skb_put_zero(skb, sec_hdr_len);
+ if (with_qos)
+ skb_put_zero(skb, sizeof(__le16));
+
+ if (sec_hdr_len)
+ skb_put_zero(skb, sec_hdr_len);
arp_skb = skb_put_zero(skb, sizeof(*arp_skb));
memcpy(arp_skb->llc_hdr, rfc1042_header, sizeof(rfc1042_header));
--
2.25.1
^ permalink raw reply related
* [PATCH rtw-next 08/10] wifi: rtw89: wow: use MLD address in WoWLAN ARP replies for MLO stations
From: Ping-Ke Shih @ 2026-06-25 6:15 UTC (permalink / raw)
To: linux-wireless; +Cc: gary.chang, timlee, dian_syuan0116, kevin_yang
In-Reply-To: <20260625061545.44808-1-pkshih@realtek.com>
From: Chin-Yen Lee <timlee@realtek.com>
Currently, WoWLAN ARP replies for MLO stations use the link address in
the ARP hardware address fields.
As a result, peers may learn the link address from the ARP reply and
use it as the destination address for subsequent traffic. Some APs may
not forward frames addressed to the link address, causing connectivity
issues.
Use the MLD address instead when generating WoWLAN ARP replies so peers
learn the correct address for MLO stations.
Signed-off-by: Chin-Yen Lee <timlee@realtek.com>
Signed-off-by: Ping-Ke Shih <pkshih@realtek.com>
---
drivers/net/wireless/realtek/rtw89/fw.c | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/drivers/net/wireless/realtek/rtw89/fw.c b/drivers/net/wireless/realtek/rtw89/fw.c
index 4f5995f2cb13..5d335105b589 100644
--- a/drivers/net/wireless/realtek/rtw89/fw.c
+++ b/drivers/net/wireless/realtek/rtw89/fw.c
@@ -2967,9 +2967,9 @@ static struct sk_buff *rtw89_arp_response_get(struct rtw89_dev *rtwdev,
arp_hdr->ar_pln = 4;
arp_hdr->ar_op = htons(ARPOP_REPLY);
- ether_addr_copy(arp_skb->sender_hw, rtwvif_link->mac_addr);
+ ether_addr_copy(arp_skb->sender_hw, rtwvif->mac_addr);
arp_skb->sender_ip = rtwvif->ip_addr;
- ether_addr_copy(arp_skb->target_hw, rtwvif_link->mac_addr);
+ ether_addr_copy(arp_skb->target_hw, rtwvif->mac_addr);
arp_skb->target_ip = rtwvif->ip_addr;
return skb;
--
2.25.1
^ permalink raw reply related
* [PATCH rtw-next 07/10] wifi: rtw89: fw: fix link ID filling for LPS MLO common info
From: Ping-Ke Shih @ 2026-06-25 6:15 UTC (permalink / raw)
To: linux-wireless; +Cc: gary.chang, timlee, dian_syuan0116, kevin_yang
In-Reply-To: <20260625061545.44808-1-pkshih@realtek.com>
From: Zong-Zhe Yang <kevin_yang@realtek.com>
The link ID field in H2C command of LPS MLO common info is incorrectly
filled with the PHY index. Fix it with the target link ID.
Fixes: 20380a039ddd ("wifi: rtw89: phy: add H2C command to send detail RX gain and link parameters for PS mode")
Signed-off-by: Zong-Zhe Yang <kevin_yang@realtek.com>
Signed-off-by: Ping-Ke Shih <pkshih@realtek.com>
---
drivers/net/wireless/realtek/rtw89/fw.c | 4 ++--
drivers/net/wireless/realtek/rtw89/fw.h | 2 ++
2 files changed, 4 insertions(+), 2 deletions(-)
diff --git a/drivers/net/wireless/realtek/rtw89/fw.c b/drivers/net/wireless/realtek/rtw89/fw.c
index 342f95d41c02..4f5995f2cb13 100644
--- a/drivers/net/wireless/realtek/rtw89/fw.c
+++ b/drivers/net/wireless/realtek/rtw89/fw.c
@@ -3439,7 +3439,7 @@ int rtw89_fw_h2c_lps_ml_cmn_info_v1(struct rtw89_dev *rtwdev,
h2c->rfe_type = efuse->rfe_type;
h2c->rssi_main = U8_MAX;
- memset(h2c->link_id, 0xfe, RTW89_BB_PS_LINK_BUF_MAX);
+ memset(h2c->link_id, RTW89_BB_PS_LINK_ID_SKIP, RTW89_BB_PS_LINK_BUF_MAX);
rtw89_vif_for_each_link(rtwvif, rtwvif_link, link_id) {
u8 phy_idx = rtwvif_link->phy_idx;
@@ -3447,7 +3447,7 @@ int rtw89_fw_h2c_lps_ml_cmn_info_v1(struct rtw89_dev *rtwdev,
bb = rtw89_get_bb_ctx(rtwdev, phy_idx);
chan = rtw89_chan_get(rtwdev, rtwvif_link->chanctx_idx);
- h2c->link_id[phy_idx] = phy_idx;
+ h2c->link_id[phy_idx] = link_id;
h2c->central_ch[phy_idx] = chan->channel;
h2c->pri_ch[phy_idx] = chan->primary_channel;
h2c->band[phy_idx] = chan->band_type;
diff --git a/drivers/net/wireless/realtek/rtw89/fw.h b/drivers/net/wireless/realtek/rtw89/fw.h
index 20721d5209aa..5873301fc472 100644
--- a/drivers/net/wireless/realtek/rtw89/fw.h
+++ b/drivers/net/wireless/realtek/rtw89/fw.h
@@ -2053,6 +2053,8 @@ enum rtw89_bb_link_rx_gain_table_type {
RTW89_BB_PS_LINK_RX_GAIN_TAB_MAX,
};
+#define RTW89_BB_PS_LINK_ID_SKIP 0xfe
+
enum rtw89_bb_ps_link_buf_id {
RTW89_BB_PS_LINK_BUF_0 = 0x00,
RTW89_BB_PS_LINK_BUF_1 = 0x01,
--
2.25.1
^ permalink raw reply related
* [PATCH rtw-next 06/10] wifi: rtw89: pci: disable phy error flag related to refclk
From: Ping-Ke Shih @ 2026-06-25 6:15 UTC (permalink / raw)
To: linux-wireless; +Cc: gary.chang, timlee, dian_syuan0116, kevin_yang
In-Reply-To: <20260625061545.44808-1-pkshih@realtek.com>
From: Chih-Kang Chang <gary.chang@realtek.com>
On some platforms, refclk is not available up to 15 ms after entering
suspend. The delayed clock cause the hardware to detect falsely error
and trigger an unexpected hardware reset. Disable the phy error flag
related to refclk to fix it.
Signed-off-by: Chih-Kang Chang <gary.chang@realtek.com>
Signed-off-by: Ping-Ke Shih <pkshih@realtek.com>
---
drivers/net/wireless/realtek/rtw89/pci.h | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/drivers/net/wireless/realtek/rtw89/pci.h b/drivers/net/wireless/realtek/rtw89/pci.h
index c3f2d0df5846..92c30c7f9fb2 100644
--- a/drivers/net/wireless/realtek/rtw89/pci.h
+++ b/drivers/net/wireless/realtek/rtw89/pci.h
@@ -58,7 +58,7 @@
#define B_AX_DIV GENMASK(15, 14)
#define RAC_SET_PPR_V1 0x31
#define RAC_ANA40 0x40
-#define PHY_ERR_IMR_DIS (BIT(9) | BIT(8) | BIT(0))
+#define PHY_ERR_IMR_DIS (BIT(9) | BIT(2) | BIT(1) | BIT(0))
#define RAC_ANA41 0x41
#define PHY_ERR_FLAG_EN BIT(6)
--
2.25.1
^ permalink raw reply related
* [PATCH rtw-next 05/10] wifi: rtw89: disable sniffer mode in RX filter when initialization for Wi-Fi 7 chips
From: Ping-Ke Shih @ 2026-06-25 6:15 UTC (permalink / raw)
To: linux-wireless; +Cc: gary.chang, timlee, dian_syuan0116, kevin_yang
In-Reply-To: <20260625061545.44808-1-pkshih@realtek.com>
From: Chih-Kang Chang <gary.chang@realtek.com>
Sniffer mode is enabled by default in the RX filter on Wi-Fi 7 chips,
which causes all packets to be received regardless of the ADDR_CAM
lookup result. This may result in unexpected packets being received.
Therefore, disable it by default.
Signed-off-by: Chih-Kang Chang <gary.chang@realtek.com>
Signed-off-by: Ping-Ke Shih <pkshih@realtek.com>
---
drivers/net/wireless/realtek/rtw89/mac_be.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/drivers/net/wireless/realtek/rtw89/mac_be.c b/drivers/net/wireless/realtek/rtw89/mac_be.c
index d9c93adb58ee..14f1e30066e9 100644
--- a/drivers/net/wireless/realtek/rtw89/mac_be.c
+++ b/drivers/net/wireless/realtek/rtw89/mac_be.c
@@ -1333,7 +1333,7 @@ static int rx_fltr_init_be(struct rtw89_dev *rtwdev, u8 mac_idx)
reg = rtw89_mac_reg_by_idx(rtwdev, R_BE_RX_FLTR_OPT, mac_idx);
val = B_BE_A_BC_CAM_MATCH | B_BE_A_UC_CAM_MATCH | B_BE_A_MC |
- B_BE_A_BC | B_BE_A_A1_MATCH | B_BE_SNIFFER_MODE |
+ B_BE_A_BC | B_BE_A_A1_MATCH |
u32_encode_bits(15, B_BE_UID_FILTER_MASK);
rtw89_write32(rtwdev, reg, val);
u32p_replace_bits(&rtwdev->hal.rx_fltr, 15, B_BE_UID_FILTER_MASK);
--
2.25.1
^ permalink raw reply related
* [PATCH rtw-next 04/10] wifi: rtw89: drop packet offload entry on H2C addition failure to avoid scan issue
From: Ping-Ke Shih @ 2026-06-25 6:15 UTC (permalink / raw)
To: linux-wireless; +Cc: gary.chang, timlee, dian_syuan0116, kevin_yang
In-Reply-To: <20260625061545.44808-1-pkshih@realtek.com>
From: Dian-Syuan Yang <dian_syuan0116@realtek.com>
A special case is when C2H done ack has been completed, but the
corresponding packet offload response has not actually been received,
which causes the add packet offload to fail. In this state, firmware
treats the entry as added, so subsequent add requests for the same id
are rejected as duplicates. To recover from this, send a delete packet
offload H2C command to roll back the normal state. It has been tested and
verified to have no functional side effect.
Signed-off-by: Dian-Syuan Yang <dian_syuan0116@realtek.com>
Signed-off-by: Ping-Ke Shih <pkshih@realtek.com>
---
drivers/net/wireless/realtek/rtw89/fw.c | 26 ++++++++++++++++++++++---
1 file changed, 23 insertions(+), 3 deletions(-)
diff --git a/drivers/net/wireless/realtek/rtw89/fw.c b/drivers/net/wireless/realtek/rtw89/fw.c
index d6a594b75ab2..342f95d41c02 100644
--- a/drivers/net/wireless/realtek/rtw89/fw.c
+++ b/drivers/net/wireless/realtek/rtw89/fw.c
@@ -6443,8 +6443,8 @@ int rtw89_fw_h2c_del_pkt_offload(struct rtw89_dev *rtwdev, u8 id)
return 0;
}
-int rtw89_fw_h2c_add_pkt_offload(struct rtw89_dev *rtwdev, u8 *id,
- struct sk_buff *skb_ofld)
+static int __rtw89_fw_h2c_add_pkt_offload(struct rtw89_dev *rtwdev, u8 *id,
+ struct sk_buff *skb_ofld)
{
struct rtw89_wait_info *wait = &rtwdev->mac.fw_ofld_wait;
struct sk_buff *skb;
@@ -6486,13 +6486,33 @@ int rtw89_fw_h2c_add_pkt_offload(struct rtw89_dev *rtwdev, u8 *id,
rtw89_debug(rtwdev, RTW89_DBG_FW,
"failed to add pkt ofld: id %d, ret %d\n",
alloc_id, ret);
+ /*
+ * Firmware may consider that it has added this entry
+ * successfully even though the H2C return timeout.
+ * Send a delete H2C command to drop it, and thus the
+ * next add on the same id won't be rejected as duplicate.
+ */
+ rtw89_fw_h2c_del_pkt_offload(rtwdev, alloc_id);
rtw89_core_release_bit_map(rtwdev->pkt_offload, alloc_id);
- return ret;
+
+ return -EAGAIN;
}
return 0;
}
+int rtw89_fw_h2c_add_pkt_offload(struct rtw89_dev *rtwdev, u8 *id,
+ struct sk_buff *skb_ofld)
+{
+ int ret;
+
+ ret = __rtw89_fw_h2c_add_pkt_offload(rtwdev, id, skb_ofld);
+ if (ret == -EAGAIN)
+ ret = __rtw89_fw_h2c_add_pkt_offload(rtwdev, id, skb_ofld);
+
+ return ret;
+}
+
static
int rtw89_fw_h2c_scan_list_offload_ax(struct rtw89_dev *rtwdev, int ch_num,
struct list_head *chan_list)
--
2.25.1
^ permalink raw reply related
* [PATCH rtw-next 03/10] wifi: rtw89: fw: lower debug level for UDM1 debug register
From: Ping-Ke Shih @ 2026-06-25 6:15 UTC (permalink / raw)
To: linux-wireless; +Cc: gary.chang, timlee, dian_syuan0116, kevin_yang
In-Reply-To: <20260625061545.44808-1-pkshih@realtek.com>
The UDM1 is user define message to record count of H2C command sent by
driver and received by firmware. Normally, this value should be zero.
Otherwise, throw a warning.
For the new chip RTL8922DE, its default value is not zero, causing a
warning at first time probe. Since this is a debug purpose and the
value will be set to zero right after this checking, lower the debug level.
Signed-off-by: Ping-Ke Shih <pkshih@realtek.com>
---
drivers/net/wireless/realtek/rtw89/mac_be.c | 6 ++++--
1 file changed, 4 insertions(+), 2 deletions(-)
diff --git a/drivers/net/wireless/realtek/rtw89/mac_be.c b/drivers/net/wireless/realtek/rtw89/mac_be.c
index 4cb48cf9415a..d9c93adb58ee 100644
--- a/drivers/net/wireless/realtek/rtw89/mac_be.c
+++ b/drivers/net/wireless/realtek/rtw89/mac_be.c
@@ -652,8 +652,10 @@ static int wcpu_on(struct rtw89_dev *rtwdev, u8 boot_reason, bool dlfw)
}
val32 = rtw89_read32(rtwdev, R_BE_UDM1);
if (val32) {
- rtw89_warn(rtwdev, "[SER] AON L2 Debug register not empty before Boot.\n");
- rtw89_warn(rtwdev, "[SER] %s: R_BE_UDM1 = 0x%x\n", __func__, val32);
+ rtw89_debug(rtwdev, RTW89_DBG_UNEXP,
+ "[SER] AON L2 Debug register not empty before Boot.\n");
+ rtw89_debug(rtwdev, RTW89_DBG_UNEXP,
+ "[SER] %s: R_BE_UDM1 = 0x%x\n", __func__, val32);
}
val32 = rtw89_read32(rtwdev, R_BE_UDM2);
if (val32) {
--
2.25.1
^ permalink raw reply related
* [PATCH rtw-next 02/10] wifi: rtw89: mac: pass chip version to firmware
From: Ping-Ke Shih @ 2026-06-25 6:15 UTC (permalink / raw)
To: linux-wireless; +Cc: gary.chang, timlee, dian_syuan0116, kevin_yang
In-Reply-To: <20260625061545.44808-1-pkshih@realtek.com>
Set chip version to register shared with firmware before downloading
firmware, so firmware can run proper flow according to the version.
Signed-off-by: Ping-Ke Shih <pkshih@realtek.com>
---
drivers/net/wireless/realtek/rtw89/mac_be.c | 3 +++
1 file changed, 3 insertions(+)
diff --git a/drivers/net/wireless/realtek/rtw89/mac_be.c b/drivers/net/wireless/realtek/rtw89/mac_be.c
index 70e6c9d21986..4cb48cf9415a 100644
--- a/drivers/net/wireless/realtek/rtw89/mac_be.c
+++ b/drivers/net/wireless/realtek/rtw89/mac_be.c
@@ -641,6 +641,7 @@ static void set_cpu_en(struct rtw89_dev *rtwdev, bool include_bb)
static int wcpu_on(struct rtw89_dev *rtwdev, u8 boot_reason, bool dlfw)
{
const struct rtw89_chip_info *chip = rtwdev->chip;
+ struct rtw89_hal *hal = &rtwdev->hal;
u32 val32;
int ret;
@@ -683,6 +684,8 @@ static int wcpu_on(struct rtw89_dev *rtwdev, u8 boot_reason, bool dlfw)
if (chip->chip_id != RTL8922A)
rtw89_write32_set(rtwdev, R_BE_WCPU_FW_CTRL, B_BE_HOST_EXIST);
+ rtw89_write32_mask(rtwdev, R_BE_WCPU_FW_CTRL,
+ B_BE_WCPU_ROM_CUT_VAL_MASK, hal->cv + 1);
rtw89_write16_mask(rtwdev, R_BE_BOOT_REASON, B_BE_BOOT_REASON_MASK, boot_reason);
rtw89_write32_clr(rtwdev, R_BE_PLATFORM_ENABLE, B_BE_WCPU_EN);
rtw89_write32_clr(rtwdev, R_BE_PLATFORM_ENABLE, B_BE_HOLD_AFTER_RESET);
--
2.25.1
^ permalink raw reply related
* [PATCH rtw-next 01/10] wifi: rtw89: mac: finish active TX immediately without waiting for DMAC
From: Ping-Ke Shih @ 2026-06-25 6:15 UTC (permalink / raw)
To: linux-wireless; +Cc: gary.chang, timlee, dian_syuan0116, kevin_yang
In-Reply-To: <20260625061545.44808-1-pkshih@realtek.com>
Currently active TX only finishes after ensuring PCIE and DMAC become idle.
However, the waiting time might be long. Since the packet is already
transmitted over the air, update the registers to finish active TX
immediately, regardless of the PCIE/DMAC status.
Signed-off-by: Ping-Ke Shih <pkshih@realtek.com>
---
drivers/net/wireless/realtek/rtw89/mac_be.c | 3 +++
drivers/net/wireless/realtek/rtw89/reg.h | 28 +++++++++++++++++++++
2 files changed, 31 insertions(+)
diff --git a/drivers/net/wireless/realtek/rtw89/mac_be.c b/drivers/net/wireless/realtek/rtw89/mac_be.c
index f24c119b99f1..70e6c9d21986 100644
--- a/drivers/net/wireless/realtek/rtw89/mac_be.c
+++ b/drivers/net/wireless/realtek/rtw89/mac_be.c
@@ -1196,6 +1196,9 @@ static int scheduler_init_be(struct rtw89_dev *rtwdev, u8 mac_idx)
rtw89_io_pack(rtwdev);
+ reg = rtw89_mac_reg_by_idx(rtwdev, R_BE_MISC_1, mac_idx);
+ rtw89_write32_set(rtwdev, reg, B_BE_EN_TX_FINISH_PRD_RESP);
+
if (chip->chip_id == RTL8922D) {
reg = rtw89_mac_reg_by_idx(rtwdev, R_BE_SCH_EXT_CTRL, mac_idx);
rtw89_write32_set(rtwdev, reg, B_BE_CWCNT_PLUS_MODE);
diff --git a/drivers/net/wireless/realtek/rtw89/reg.h b/drivers/net/wireless/realtek/rtw89/reg.h
index 086ef77ebb9f..bf1c6cb0ae9c 100644
--- a/drivers/net/wireless/realtek/rtw89/reg.h
+++ b/drivers/net/wireless/realtek/rtw89/reg.h
@@ -6858,6 +6858,34 @@
#define B_BE_MUEDCA_EN_MASK GENMASK(1, 0)
#define B_BE_MUEDCA_EN_0 BIT(0)
+#define R_BE_MISC_1 0x1037C
+#define R_BE_MISC_1_C1 0x1437C
+#define B_BE_PPS_REMAIN_TIME_MODE BIT(31)
+#define B_BE_PPS_IDLE_SORT_EN BIT(30)
+#define B_BE_SR_TXOP_USE_SR_PERIOD_EN BIT(29)
+#define B_BE_SCH_CCA_PIFS_CLK_GATING_DIS BIT(28)
+#define B_BE_SR_TXOP_EN BIT(27)
+#define B_BE_SCH_ABORT_CNT_SIFS_EN BIT(26)
+#define B_BE_SCH_ABORT_CNT_TB_EN BIT(25)
+#define B_BE_SCH_ABORT_CNT_CTN_EN BIT(24)
+#define B_BE_SR_CCA_PER20_BITMAP_EN BIT(23)
+#define B_BE_SR_CCA_S80_EN BIT(22)
+#define B_BE_SR_CCA_S40_EN BIT(21)
+#define B_BE_SR_CCA_S20_EN BIT(20)
+#define B_BE_EN_TX_FINISH_PRD_RESP BIT(18)
+#define B_BE_RESP_TX_ABORT_NON_IDLE BIT(17)
+#define B_BE_RESP_TX_ABORT_QUICK_EN BIT(16)
+#define B_BE_PREBKF_CHK_LINK_BUSY BIT(15)
+#define B_BE_SCH_MSD_PRD_RST_EDCA_EN BIT(14)
+#define B_BE_LINK_BUSY_RST_EDCA_EN_MASK GENMASK(13, 12)
+#define B_BE_RX_TSFT_SYNC_BYPASS_FCS BIT(11)
+#define B_BE_RX_TSFT_DIFF_THD_MASK GENMASK(10, 8)
+#define B_BE_CAL_TBTT_OV_EN BIT(5)
+#define B_BE_SUBBCN_MS_CNT_MODE BIT(3)
+#define B_BE_CAL_ALWAYS_EN BIT(2)
+#define B_BE_SIFS_TIMER_AUTO_RST_EN BIT(1)
+#define B_BE_CHK_HAS_SIFS_TX_ABORT BIT(0)
+
#define R_BE_CTN_DRV_TXEN 0x10398
#define R_BE_CTN_DRV_TXEN_C1 0x14398
#define B_BE_CTN_TXEN_TWT_3 BIT(17)
--
2.25.1
^ permalink raw reply related
* [PATCH rtw-next 00/10] wifi: rtw89: update some MAC, FW and WoWLAN settings
From: Ping-Ke Shih @ 2026-06-25 6:15 UTC (permalink / raw)
To: linux-wireless; +Cc: gary.chang, timlee, dian_syuan0116, kevin_yang
Most patches are to configure MAC/FW/PCI settings. Notable patches are
patch 4/10 to resolve hw_scan issue in certain platforms; patch 7/10 is
to fix typo of previous patch, but no problem for legacy connection.
The last three patches are related to WoWLAN, and the first two are to
support WoWLAN with MLO AP that uses MLD address.
Chih-Kang Chang (3):
wifi: rtw89: disable sniffer mode in RX filter when initialization for
Wi-Fi 7 chips
wifi: rtw89: pci: disable phy error flag related to refclk
wifi: rtw89: wow: only WiFi 6 chips initialize RF registers in WoWLAN
mode
Chin-Yen Lee (2):
wifi: rtw89: wow: use MLD address in WoWLAN ARP replies for MLO
stations
wifi: rtw89: wow: add QoS control field to WoWLAN ARP response for MLO
Dian-Syuan Yang (1):
wifi: rtw89: drop packet offload entry on H2C addition failure to
avoid scan issue
Ping-Ke Shih (3):
wifi: rtw89: mac: finish active TX immediately without waiting for
DMAC
wifi: rtw89: mac: pass chip version to firmware
wifi: rtw89: fw: lower debug level for UDM1 debug register
Zong-Zhe Yang (1):
wifi: rtw89: fw: fix link ID filling for LPS MLO common info
drivers/net/wireless/realtek/rtw89/fw.c | 65 ++++++++++++++++-----
drivers/net/wireless/realtek/rtw89/fw.h | 2 +
drivers/net/wireless/realtek/rtw89/mac_be.c | 14 ++++-
drivers/net/wireless/realtek/rtw89/pci.h | 2 +-
drivers/net/wireless/realtek/rtw89/reg.h | 28 +++++++++
drivers/net/wireless/realtek/rtw89/wow.c | 3 +-
6 files changed, 95 insertions(+), 19 deletions(-)
base-commit: 972c4dd19cb92e03d75b66c426cfade07582a1ba
--
2.25.1
^ permalink raw reply
* [DESIGN RFC] EHT/UHR Critical update handling
From: Manish Dharanenthiran @ 2026-06-25 5:03 UTC (permalink / raw)
To: johannes; +Cc: linux-wireless, ath12k, Manish Dharanenthiran
Hi,
This RFC outlines a proposed approach for handling EHT/UHR Critical Update (CU)
procedures across different driver models. The focus is on ensuring correct and
consistent behavior of beacon and management frames, while addressing timing
constraints and scalability challenges observed in the previously discussed
approaches.
================================================================
Background and Motivation
================================================================
For handling EHT/UHR Critical Updates (CU) across both beacon offload and
non-offload drivers, Johannes previously proposed a design in [1], where the
responsibility for managing CU-related updates resides entirely in
user space (hostapd). In this approach, the kernel/driver provides timing
information (e.g., TSF and TBTT), which hostapd uses to compute and update
beacon contents. As part of the CU procedure, hostapd is expected to update
beacon contents at every beacon interval until the CU session completes.
While this approach is functionally applicable to both offloaded (where
firmware handles beacon transmission) and non-offloaded drivers, but it
introduces scalability concerns. Specifically, in real-time scenarios involving
low-power chipsets operating at high load (e.g., supporting up to 256 clients
with multiple virtual interfaces), repeatedly processing CU updates in user space
at every beacon interval can incur significant overhead. This may impact system
efficiency and responsiveness, particularly under constrained resources.
[1] https://lore.kernel.org/all/1cf0ae795b0e3e95b38cb7abf84ffad34c187fdf.camel@sipsolutions.net/
==================================================================
Prior Alternative and Limitations
==================================================================
An alternative approach was proposed in [2], where mac80211 would take
responsibility for stitching or updating beacon contents during CU. This design
aimed to address timing constraints by moving the processing closer to the
driver, thereby reducing reliance on user space scheduling and latency.
However, this proposal was not accepted, primarily due to concerns about
increasing complexity and processing overhead within mac80211. The consensus was
that such logic is better suited for user space, keeping mac80211 lightweight
and focused on its core responsibilities.
[2] https://lore.kernel.org/all/20250717045540.27208-1-aditya.kumar.singh@oss.qualcomm.com/
Problem Statement:
===================
Given the above, there remains a gap between:
- Meeting strict timing requirements for CU operations, and
- Avoiding excessive overhead in either user space (hostapd) or
kernel(mac80211).
Additionally, any solution must work seamlessly across both:
* Beacon offload drivers (firmware-driven beaconing), and
* Non-offload drivers (e.g., hwsim and similar mac80211-based drivers).
Proposed Direction:
===================
To address these challenges, a new design is proposed that strikes a balance
between timing accuracy, scalability, and implementation complexity.
The objective is to:
- Minimize per-beacon processing overhead in user space,
- Avoid adding significant processing burden to mac80211, and
- Ensure compatibility across both offloaded and non-offloaded driver models.
This approach is designed to meet CU timing requirements efficiently, while
remaining scalable for high-density and resource-constrained deployments.
======================================================================
ATH12K Design
======================================================================
Currently in the ath12k driver [3], beacon updates during an ongoing CU session
are handled entirely by firmware. The firmware is responsible for
composing (“stitching”) the beacon contents based on inputs provided by
userspace.
For example, consider a Multi-Link (ML) reconfiguration scenario where Link A
initiates a link removal. In this case:
- Link A includes the Multi-Link Reconfiguration element in its beacon.
- At the same time, Link B also incorporates the same element in its beacon
and updates the BPCC value within the Reduced Neighbor Report (RNR)
corresponding to Link A.
Link A and Link B (part of MLD) originally beaconing just fine:
┌──────────────────────────────┐ ┌───────────────────────────────┐
│ Link A's beacon │ │ Link B's beacon │
│ │ │ │
│...... │ │...... │
│┌────────────────────────────┐│ │┌─────────────────────────────┐│
││RNR Element: ││ ││RNR Element: ││
││ TBTT INFO: ││ ││ TBTT INFO: ││
││ MLD INFO: ││ ││ MLD INFO: ││
││ LINK ID: B ││ ││ LINK ID: A ││
││ BPCC: 1 ││ ││ BPCC: 1 ││
│└────────────────────────────┘│ │└─────────────────────────────┘│
│...... │ │...... │
│┌────────────────────────────┐│ │┌─────────────────────────────┐│
││MULTI LINK Element: ││ ││MULTI LINK Element: ││
││ COMMON INFO: ││ ││ COMMON INFO: ││
││ LINK ID: A ││ ││ LINK ID: B ││
││ BPCC: 1 ││ ││ BPCC: 1 ││
││ ││ ││ ││
││ Basic STA profile count: 0││ ││ Basic STA profile count: 0 ││
│└────────────────────────────┘│ │└─────────────────────────────┘│
└──────────────────────────────┘ └───────────────────────────────┘
Link A started link removal, during link removal, the beacon would look like
┌──────────────────────────────┐ ┌──────────────────────────────┐
│ Link A's beacon │ │ Link B's beacon │
│ │ │ │
│┌────────────────────────────┐│ │┌────────────────────────────┐│
││Capabilities Info: ││ ││Capabilities Info: ││
││ BIT 6: CU Bit: True ││ ││ BIT 6: CU Bit: True ││
│└────────────────────────────┘│ │└────────────────────────────┘│
│ │ │ │
│┌────────────────────────────┐│ │┌────────────────────────────┐│
││RNR Element: ││ ││RNR Element: ││
││ TBTT INFO: ││ ││ TBTT INFO: ││
││ MLD INFO: ││ ││ MLD INFO: ││
││ LINK ID: B ││ ││ LINK ID: A ││
││ BPCC: 1 ││ ││ BPCC: 2 ││
│└────────────────────────────┘│ │└────────────────────────────┘│
│ │ │ │
│┌────────────────────────────┐│ │┌────────────────────────────┐│
││MULTI LINK Element: ││ ││MULTI LINK Element: ││
││ COMMON INFO: ││ ││ COMMON INFO: ││
││ LINK ID: A ││ ││ LINK ID: B ││
││ BPCC: 2 ││ ││ BPCC: 1 ││
││ ││ ││ ││
││ Basic STA profile count: 0││ ││ Basic STA profile count: 0││
│└────────────────────────────┘│ │└────────────────────────────┘│
│┌────────────────────────────┐│ │┌────────────────────────────┐│
││MULTI LINK Element: ││ ││MULTI LINK Element: ││
││ Type: Reconfiguration (2) ││ ││ Type: Reconfiguration (2) ││
││ Per-STA Profile 1 ││ ││ Per-STA Profile 1 ││
││ Link ID: <Link A's ID> ││ ││ Link ID: <Link A's ID> ││
││ STA Info: ││ ││ STA Info: ││
││ Length: ││ ││ Length: ││
││ AP Removal timer: ││ ││ AP Removal timer: ││
│└────────────────────────────┘│ │└────────────────────────────┘│
└──────────────────────────────┘ └──────────────────────────────┘
From the user space perspective, hostapd constructs the Multi-Link
Reconfiguration element and conveys only the relevant element to the firmware.
The firmware then integrates this element into the corresponding beacon frames
for all affiliated links and continues transmitting updated beacons within the
configured TBTT window for the CU session.
Firmware Notifications:
=====================
During the CU lifecycle, the firmware provides explicit notifications to both
kernel and user space to ensure proper synchronization:
STARTED:
--------
Indicates the initiation of the link reconfiguration process. This notification
allows both kernel and user space to transition into the CU handling state.
COMPLETED:
----------
Triggered after the final beacon corresponding to TBTT = 1 is transmitted. At
this point, the firmware removes the Multi-Link Reconfiguration element from
all affiliated AP MLD links and signals completion to kernel and user space.
RX MGMT Event:
-------------
While a CU session is in progress, if a Probe Request or (Re)Association
Request is received on the AP MLD, the firmware forwards an RX management
event to userspace. This event includes the current TBTT and BPCC values,
which hostapd uses to construct appropriate Probe or (Re)Association Response
frames.
[3] https://lore.kernel.org/all/20240807034521.2091751-1-quic_mdharane@quicinc.com/
======================================================================
DESIGN PROPOSAL
======================================================================
Introduction:
=============
The design proposal ensures that during an ongoing Critical Update (CU) session,
beacon frames are dynamically updated by the driver or firmware to include the
required CU elements and indication bits. It establishes coordination between
kernel, driver, and user space to track CU state, handle lifecycle notifications
(start/completion), and maintain consistency. Additionally, management responses
such as Probe and (Re)Assoc are updated with accurate CU parameters
(e.g., TBTT), ensuring synchronized signaling without requiring full state
re-transmission from user space.
Beacon Update Handling:
=============================
For non-offload drivers (e.g., hwsim), beacon updates during a CU session are
handled in a manner analogous to firmware-assisted designs. The driver is
responsible for stitching the required EHT/UHR CU elements into the beacon
prior to transmission.
Assumptions:
===========
- A mechanism can be added to notify the driver when a CU session is active.
- A common interface can be added to retrieve the required Critical Update
elements from mac80211.
Compiling Beacon frame:
======================
Upon receiving the reconfiguration element from hostapd, the driver retrieves
the beacon template using ieee80211_beacon_get(). If a CU session is in
progress, the beacon is updated as follows:
1) Retrieve the base beacon using ieee80211_beacon_get()..
2) Compute the updated length: size_t new_len = skb->len + ie_len;
3) Allocate new_skb:
struct sk_buff *new_skb = dev_alloc_skb(hw->extra_tx_headroom + new_len);
4) Insert the new element in the respective position.
5) Set the CU indication and transmit the updated beacon for the duration of
the TBTT window.
6) Remove the CU flag after the DTIM beacon is sent out.
Notifying user-space:
=====================
Since user space (hostapd) primarily initiates the CU procedure, the kernel is
expected to provide explicit notifications for CU lifecycle events:
* STARTED — Indicates that the CU session has begun.
* COMPLETED — Indicates that the CU session has ended.
For instance, in ML reconfigure, hostapd can trigger a BTM frame to let the
associated stations know that this link will be removed after a period
indicated by TSF offset.
Thus, adding a notification framework for indicating started/completed will be
helpful for user-space to take necessary actions like sending BTM broadcast
frame with link removal imminent bit set after link reconfiguration removal is
started.
Example design flow for link reconfigure in the proposed model:
* (Initiate ML reconfigure)
*
* [*] = New NL command/attribute
*
* +---------+ +---------------------------+ +-------+
* | hostapd | | nl80211/cfg80211/mac80211 | | hwsim |
* +----+----+ +-------------+-------------+ +---+---+
* | | |
* |--. | |
* | Initiate ML reconfigure | |
* | + build ML Reconfig | |
* | element | |
* |<--' | |
* | | |
* +-------------------------+ |
* | [*] | |
* | NL80211_CMD_CRITICAL_UPDATE |
* | attrs: | |
* | NL80211_ATTR_MLO_LINK_ID, |
* | NL80211_ATTR_AP_REMOVAL_COUNT, |
* | NL80211_ATTR_IE, | |
* | NL80211_ATTR_CU_TYPE (e.g |
* | reconfig/TTLM/UHR) | |
* +-------------------------+ |
* | | |
* NL80211_CMD_CRITICAL_UPDATE |
* |-------------------------> |
* | | |
* | |--. |
* | | Based on CU_TYPE |
* | | (EHT [Reconfigure/ |
* | | TTLM etc.]/UHR |
* | | params), choose |
* | | nl80211_remove_ |
* | | link() for link |
* | | reconfigure |
* | |<--' |
* | |--------------------->|
* | +--------------------------------+ |
* | | [NEW] Send element to hwsim | |
* | | driver via ops | |
* | +--------------------------------+ |
* | |--------------------->|
* | | |--.
* | | | Compile
* | | | beacon
* | | | with new
* | | | element
* | | | and CU
* | | | bit set
* | | |<--'
* | | |
* +-----------------------------------------------+|
* | [*] NL80211_CMD_CRITICAL_UPDATE_NOTIFY |
* +-----------------------------------------------+|
* | | |
* | |<---------------------|
* | | notify started with|
* | | tbtt set to > 0 |
* |<------------------------| |
* | | |
* | EVENT_LINK_REMOVE_STARTED |
* | <start BTM and other respective actions |
* | that need to be completed before TBTT window |
* | ends> |
* | | |
* | | |
* | | |--.
* | | | Remove CU
* | | | bit after
* | | | DTIM
* | | | period
* | | |<--'
* | | |
* | |<---------------------|
* | | notify completed |
* | | after beacon with |
* | | tbtt=1 is sent |
* | | <completed will be |
* | | denoted by setting|
* | | tbtt=0 while |
* | | sending to user- |
* | | space> |
* | | |
* | | |--.
* | | | Remove
* | | | reconfigure
* | | | element
* | | | from
* | | | partner
* | | | profile
* | | |<--'
* |<------------------------| |
* | | |
* | EVENT_LINK_REMOVE_COMPLETED |
* | | |
* |--. | |
* | (cleanup/disable/ | |
* | remove link) | |
* |<--' | |
* | | |
* |------------------------>| |
* | Set beacon for all | |
* | enabled BSSs | |
* | | |
Note: If a new CU update is triggered while an existing CU session is ongoing,
hostapd only provides the delta (i.e., the new element). The driver (or firmware,
where applicable) is responsible for combining this with the existing state when
generating subsequent beacons. Re-sending previously applied CU parameters from
user space is not required. Also, it would internally handle the MBSSID
cases as well.
Adding current TBTT in Probe/(Re)Assoc response:
================================================
In the proposed model, the driver/firmware is responsible for maintaining the
current TBTT state while updating beacon frames during an ongoing CU session.
Consequently, any Probe Response or (Re)Association Response transmitted during
this period must also carry the corresponding CU element with the correct and
up-to-date TBTT value.
Two approaches are considered:
1) Updating TBTT in Probe/(Re)Assoc response in mac80211:
=========================================================
In this approach, mac80211 updates the TBTT value before transmitting management
responses:
When hostapd queues a Probe/(Re)Assoc Response via NL80211_CMD_FRAME, mac80211
checks if a CU session is active. If active, mac80211 retrieves the latest TBTT
from the driver/hwsim. The TBTT value in the response frame is then updated
before passing the frame to the driver for transmission.
To support this, NL80211_CMD_FRAME is extended to include an array of offset
attributes indicating where TBTT values must be updated within the frame. This
is required to handle:
Multiple concurrent CU contexts (e.g., EHT ML reconfiguration and UHR CU), and
Updates within per-STA profiles (e.g., partner link CU scenarios).
For updating associated counters two options exist:
1) Fetch the latest value from the driver at the time of transmission, or
2) Pass the value from lower layers along with the frame.
The former is preferred, as it ensures the most accurate state at transmission
time.
+-----------+ +------------+ +----------------+
| hostapd | | mac80211 | | firmware/hwsim |
+-----------+ +------------+ +----------------+
| | |
| | |
|<-----------------------------| |
| NL80211_CMD_FRAME | |
+---->| Probe/(Re)Assoc request | |
| | | |
Compile | | |
response | | |
- | | |
+---->|----------------------------->| |
| NL80211_CMD_FRAME | |
| Probe/(Re)Assoc response | |
| | |
+------------------------------+ |
|*NL80211_ATTR_CU_TBTT_OFFSET | |
| | |
| Array of offset attributes | |
| to denote the TBTT | |
| placeholder for updating the | |
| latest count | |
+------------------------------+ |
| |----+ |
| | v |
| |Update |
| |response frame |
| | ^ |
| |----+ |
| | |
| |----------------->|
| | Queue response |
| | frame |
| | |
2) Propagating TBTT to user-space:
==================================
To reduce per-frame processing in mac80211, this approach propagates the
current TBTT value to user space:
The kernel includes the current TBTT and CU type as part of
NL80211_CMD_CRITICAL_UPDATE notifications. Hostapd uses this information to
construct Probe/(Re)Assoc Responses with the appropriate TBTT values before
sending them via NL80211_CMD_FRAME.
While this avoids additional processing in mac80211, it introduces potential
latency issues. Specifically:
The response may be transmitted after the CU state has changed
(e.g., TBTT progressed or CU completed), resulting in stale parameters. The
driver/firmware must therefore handle such cases, including rejecting frames
containing outdated CU information if the CU session has already completed.
A similar user space–driven model was previously proposed in [4]. While it
requires refactoring to align with the current design, it provides a high-level
reference for propagating CU state to user space.
[4] https://lore.kernel.org/all/20240403162225.3096228-1-quic_rrchinan@quicinc.com/
With this design of stitching beacon in hwsim/firmware, we believe the proposed
design can effectively meet real-time requirements, including high client
density scenarios on low-power chipsets, while also enabling validation through
simulation-based test environments (e.g., hwsim) for various Critical Update
cases.
Please let us know your thoughts on the same.
[Note: EHT Link reconfiguration is used as an example to depict the
proposed design. The same will be used across other Critical Update
(EHT/UHR) handling.]
^ permalink raw reply
* [PATCH ath-next] ath9k: return ath_buf to pool on A-MPDU subframe retry
From: Rosen Penev @ 2026-06-25 2:37 UTC (permalink / raw)
To: linux-wireless; +Cc: Toke Høiland-Jørgensen, open list
When an A-MPDU subframe needs retransmission, its ath_buf descriptor was
moved to a local bf_head list that went out of scope without returning
the buffer to the free pool (sc->tx.txbuf). This progressively depletes
the 512-entry TX buffer pool under normal retransmission conditions,
eventually stalling all TX.
Unmap the DMA mapping (a new one will be created on retry), clear the
buffer references, and return it to the pool via ath_tx_return_buffer.
Assisted-by: opencode:big-pickle
Signed-off-by: Rosen Penev <rosenp@gmail.com>
---
drivers/net/wireless/ath/ath9k/xmit.c | 9 +++++++++
1 file changed, 9 insertions(+)
diff --git a/drivers/net/wireless/ath/ath9k/xmit.c b/drivers/net/wireless/ath/ath9k/xmit.c
index 57e451548958..350eed276cc0 100644
--- a/drivers/net/wireless/ath/ath9k/xmit.c
+++ b/drivers/net/wireless/ath/ath9k/xmit.c
@@ -665,6 +665,15 @@ static void ath_tx_complete_aggr(struct ath_softc *sc, struct ath_txq *txq,
* queue to retain ordering
*/
__skb_queue_tail(&bf_pending, skb);
+
+ if (!list_empty(&bf_head)) {
+ dma_unmap_single(sc->dev, bf->bf_buf_addr,
+ skb->len, DMA_TO_DEVICE);
+ bf->bf_buf_addr = 0;
+ bf->bf_mpdu = NULL;
+ list_del(&bf->list);
+ ath_tx_return_buffer(sc, bf);
+ }
}
bf = bf_next;
--
2.54.0
^ permalink raw reply related
* [PATCHv2 ath-next] wifi: ath9k: eeprom: drop static from local pdadc and vpdTable arrays
From: Rosen Penev @ 2026-06-25 2:13 UTC (permalink / raw)
To: linux-wireless
Cc: Toke Høiland-Jørgensen, Nathan Chancellor,
Nick Desaulniers, Bill Wendling, Justin Stitt, open list,
open list:CLANG/LLVM BUILD SUPPORT:Keyword:b(?i:clang|llvm)b
Remove the static qualifier from mutable local arrays in three EEPROM
power-calibration functions. These arrays are written to during normal
operation, so static storage is both unnecessary and misleading: it
implies sharing across calls when no such sharing is intended, and it
makes the code subtly non-reentrant. The sibling function in
eeprom_9287.c already uses an automatic (stack-local) pdadcValues,
confirming this is the correct pattern.
This keeps ~1 KB of data off the static data section at the cost of
stack usage, consistent with the rest of the driver's coding style.
As a safety measure, also add bounds validation for the EEPROM-derived
loop limits and indices that drive these arrays. Without these guards,
a malformed EEPROM calibration dataset can cause stack buffer overflows
(vpdTable rows are 64 bytes but the fill loop runs up to 128 iterations),
out-of-bounds reads when the VPD table has fewer than 2 entries, a
negative-index fallback when numXpdGains == 0, and unbounded shifts in
the pdadc adjustment functions. All of these are reachable through
on-device EEPROM data and were latent as BSS corruptions before the
stack move.
Also alias vpdTableI onto vpdTableL to shrink stack frame
vpdTableL, vpdTableR, and vpdTableI are never live simultaneously.
vpdTableL and vpdTableR are consumed during the frequency-interpolation
step that writes vpdTableI; after the if/else they are never read
again. Reuse vpdTableL for the interpolated result (what was
vpdTableI), reducing the stack frame by one 256-byte array.
The read-via-write in the else branch is safe: ath9k_hw_interpolate()
receives vpdTableL[i][j] by value as a function argument before the
return value is written back to vpdTableL[i][j].
Stack frame size change (x86_64, clang):
before: 0x440 (1088 B)
after: 0x330 (816 B)
Assisted-by: opencode:big-pickle
Signed-off-by: Rosen Penev <rosenp@gmail.com>
---
v2: add bounds checks
drivers/net/wireless/ath/ath9k/eeprom.c | 51 ++++++++++++--------
drivers/net/wireless/ath/ath9k/eeprom_4k.c | 2 +-
drivers/net/wireless/ath/ath9k/eeprom_9287.c | 20 +++++---
drivers/net/wireless/ath/ath9k/eeprom_def.c | 22 ++++++---
4 files changed, 59 insertions(+), 36 deletions(-)
diff --git a/drivers/net/wireless/ath/ath9k/eeprom.c b/drivers/net/wireless/ath/ath9k/eeprom.c
index df58dc02e104..1f76afb68e8f 100644
--- a/drivers/net/wireless/ath/ath9k/eeprom.c
+++ b/drivers/net/wireless/ath/ath9k/eeprom.c
@@ -241,11 +241,18 @@ void ath9k_hw_fill_vpd_table(u8 pwrMin, u8 pwrMax, u8 *pPwrList,
u8 *pVpdList, u16 numIntercepts,
u8 *pRetVpdList)
{
- u16 i, k;
+ u16 i, k, maxIndex;
u8 currPwr = pwrMin;
u16 idxL = 0, idxR = 0;
+ if ((pwrMax - pwrMin) / 2 >= AR5416_MAX_PWR_RANGE_IN_HALF_DB)
+ pr_warn_ratelimited("ath9k: VPD table range %u exceeds maximum %u\n",
+ (pwrMax - pwrMin) / 2,
+ AR5416_MAX_PWR_RANGE_IN_HALF_DB - 1);
- for (i = 0; i <= (pwrMax - pwrMin) / 2; i++) {
+ maxIndex = min_t(u16, (pwrMax - pwrMin) / 2,
+ AR5416_MAX_PWR_RANGE_IN_HALF_DB - 1);
+
+ for (i = 0; i <= maxIndex; i++) {
ath9k_hw_get_lower_upper_index(currPwr, pPwrList,
numIntercepts, &(idxL),
&(idxR));
@@ -460,12 +467,8 @@ void ath9k_hw_get_gain_boundaries_pdadcs(struct ath_hw *ah,
int i, j, k;
int16_t ss;
u16 idxL = 0, idxR = 0, numPiers;
- static u8 vpdTableL[AR5416_NUM_PD_GAINS]
- [AR5416_MAX_PWR_RANGE_IN_HALF_DB];
- static u8 vpdTableR[AR5416_NUM_PD_GAINS]
- [AR5416_MAX_PWR_RANGE_IN_HALF_DB];
- static u8 vpdTableI[AR5416_NUM_PD_GAINS]
- [AR5416_MAX_PWR_RANGE_IN_HALF_DB];
+ u8 vpdTableL[AR5416_NUM_PD_GAINS][AR5416_MAX_PWR_RANGE_IN_HALF_DB];
+ u8 vpdTableR[AR5416_NUM_PD_GAINS][AR5416_MAX_PWR_RANGE_IN_HALF_DB];
u8 *pVpdL, *pVpdR, *pPwrL, *pPwrR;
u8 minPwrT4[AR5416_NUM_PD_GAINS];
@@ -509,7 +512,7 @@ void ath9k_hw_get_gain_boundaries_pdadcs(struct ath_hw *ah,
data_9287[idxL].pwrPdg[i],
data_9287[idxL].vpdPdg[i],
intercepts,
- vpdTableI[i]);
+ vpdTableL[i]);
}
} else if (eeprom_4k) {
for (i = 0; i < numXpdGains; i++) {
@@ -519,7 +522,7 @@ void ath9k_hw_get_gain_boundaries_pdadcs(struct ath_hw *ah,
data_4k[idxL].pwrPdg[i],
data_4k[idxL].vpdPdg[i],
intercepts,
- vpdTableI[i]);
+ vpdTableL[i]);
}
} else {
for (i = 0; i < numXpdGains; i++) {
@@ -529,7 +532,7 @@ void ath9k_hw_get_gain_boundaries_pdadcs(struct ath_hw *ah,
data_def[idxL].pwrPdg[i],
data_def[idxL].vpdPdg[i],
intercepts,
- vpdTableI[i]);
+ vpdTableL[i]);
}
}
} else {
@@ -568,7 +571,7 @@ void ath9k_hw_get_gain_boundaries_pdadcs(struct ath_hw *ah,
vpdTableR[i]);
for (j = 0; j <= (maxPwrT4[i] - minPwrT4[i]) / 2; j++) {
- vpdTableI[i][j] =
+ vpdTableL[i][j] =
(u8)(ath9k_hw_interpolate((u16)
FREQ2FBIN(centers.
synth_center,
@@ -605,33 +608,39 @@ void ath9k_hw_get_gain_boundaries_pdadcs(struct ath_hw *ah,
(minPwrT4[i] / 2)) -
tPdGainOverlap + 1 + minDelta);
}
- vpdStep = (int16_t)(vpdTableI[i][1] - vpdTableI[i][0]);
+ sizeCurrVpdTable = (u8)((maxPwrT4[i] - minPwrT4[i]) / 2 + 1);
+
+ if (sizeCurrVpdTable >= 2)
+ vpdStep = (int16_t)(vpdTableL[i][1] - vpdTableL[i][0]);
+ else
+ vpdStep = 1;
vpdStep = (int16_t)((vpdStep < 1) ? 1 : vpdStep);
while ((ss < 0) && (k < (AR5416_NUM_PDADC_VALUES - 1))) {
- tmpVal = (int16_t)(vpdTableI[i][0] + ss * vpdStep);
+ tmpVal = (int16_t)(vpdTableL[i][0] + ss * vpdStep);
pPDADCValues[k++] = (u8)((tmpVal < 0) ? 0 : tmpVal);
ss++;
}
-
- sizeCurrVpdTable = (u8) ((maxPwrT4[i] - minPwrT4[i]) / 2 + 1);
tgtIndex = (u8)(pPdGainBoundaries[i] + tPdGainOverlap -
(minPwrT4[i] / 2));
maxIndex = (tgtIndex < sizeCurrVpdTable) ?
tgtIndex : sizeCurrVpdTable;
while ((ss < maxIndex) && (k < (AR5416_NUM_PDADC_VALUES - 1))) {
- pPDADCValues[k++] = vpdTableI[i][ss++];
+ pPDADCValues[k++] = vpdTableL[i][ss++];
}
- vpdStep = (int16_t)(vpdTableI[i][sizeCurrVpdTable - 1] -
- vpdTableI[i][sizeCurrVpdTable - 2]);
+ if (sizeCurrVpdTable >= 2)
+ vpdStep = (int16_t)(vpdTableL[i][sizeCurrVpdTable - 1] -
+ vpdTableL[i][sizeCurrVpdTable - 2]);
+ else
+ vpdStep = 1;
vpdStep = (int16_t)((vpdStep < 1) ? 1 : vpdStep);
if (tgtIndex >= maxIndex) {
while ((ss <= tgtIndex) &&
(k < (AR5416_NUM_PDADC_VALUES - 1))) {
- tmpVal = (int16_t)((vpdTableI[i][sizeCurrVpdTable - 1] +
+ tmpVal = (int16_t)((vpdTableL[i][sizeCurrVpdTable - 1] +
(ss - maxIndex + 1) * vpdStep));
pPDADCValues[k++] = (u8)((tmpVal > 255) ?
255 : tmpVal);
@@ -650,6 +659,8 @@ void ath9k_hw_get_gain_boundaries_pdadcs(struct ath_hw *ah,
i++;
}
+ if (k == 0)
+ pPDADCValues[k++] = 0;
while (k < AR5416_NUM_PDADC_VALUES) {
pPDADCValues[k] = pPDADCValues[k - 1];
k++;
diff --git a/drivers/net/wireless/ath/ath9k/eeprom_4k.c b/drivers/net/wireless/ath/ath9k/eeprom_4k.c
index 3e16cfe059f3..eec7efdc21c3 100644
--- a/drivers/net/wireless/ath/ath9k/eeprom_4k.c
+++ b/drivers/net/wireless/ath/ath9k/eeprom_4k.c
@@ -288,7 +288,7 @@ static void ath9k_hw_set_4k_power_cal_table(struct ath_hw *ah,
struct cal_data_per_freq_4k *pRawDataset;
u8 *pCalBChans = NULL;
u16 pdGainOverlap_t2;
- static u8 pdadcValues[AR5416_NUM_PDADC_VALUES];
+ u8 pdadcValues[AR5416_NUM_PDADC_VALUES];
u16 gainBoundaries[AR5416_PD_GAINS_IN_MASK];
u16 numPiers, i, j;
u16 numXpdGain, xpdMask;
diff --git a/drivers/net/wireless/ath/ath9k/eeprom_9287.c b/drivers/net/wireless/ath/ath9k/eeprom_9287.c
index c139ac49ccf6..9ee272d5c751 100644
--- a/drivers/net/wireless/ath/ath9k/eeprom_9287.c
+++ b/drivers/net/wireless/ath/ath9k/eeprom_9287.c
@@ -463,13 +463,19 @@ static void ath9k_hw_set_ar9287_power_cal_table(struct ath_hw *ah,
(int32_t)AR9287_PWR_TABLE_OFFSET_DB);
diff *= 2;
- for (j = 0; j < ((u16)AR5416_NUM_PDADC_VALUES-diff); j++)
- pdadcValues[j] = pdadcValues[j+diff];
-
- for (j = (u16)(AR5416_NUM_PDADC_VALUES-diff);
- j < AR5416_NUM_PDADC_VALUES; j++)
- pdadcValues[j] =
- pdadcValues[AR5416_NUM_PDADC_VALUES-diff];
+ if (diff >= 0 && diff < AR5416_NUM_PDADC_VALUES) {
+ for (j = 0; j < ((u16)AR5416_NUM_PDADC_VALUES - diff); j++)
+ pdadcValues[j] = pdadcValues[j + diff];
+
+ for (j = (u16)(AR5416_NUM_PDADC_VALUES - diff);
+ j < AR5416_NUM_PDADC_VALUES; j++)
+ pdadcValues[j] =
+ pdadcValues[AR5416_NUM_PDADC_VALUES - diff];
+ } else {
+ ath_warn(ath9k_hw_common(ah),
+ "ignoring invalid PDADC offset %d\n",
+ diff);
+ }
}
if (!ath9k_hw_ar9287_get_eeprom(ah, EEP_OL_PWRCTRL)) {
diff --git a/drivers/net/wireless/ath/ath9k/eeprom_def.c b/drivers/net/wireless/ath/ath9k/eeprom_def.c
index 5ba467cb7425..be3e6ab11562 100644
--- a/drivers/net/wireless/ath/ath9k/eeprom_def.c
+++ b/drivers/net/wireless/ath/ath9k/eeprom_def.c
@@ -744,14 +744,20 @@ static void ath9k_adjust_pdadc_values(struct ath_hw *ah,
*/
if (AR_SREV_9280_20_OR_LATER(ah)) {
if (AR5416_PWR_TABLE_OFFSET_DB != pwr_table_offset) {
- /* shift the table to start at the new offset */
- for (k = 0; k < (u16)NUM_PDADC(diff); k++ ) {
- pdadcValues[k] = pdadcValues[k + diff];
- }
+ if (diff < 0 || diff >= AR5416_NUM_PDADC_VALUES) {
+ ath_warn(ath9k_hw_common(ah),
+ "ignoring invalid PDADC offset %d\n",
+ diff);
+ } else {
+ /* shift the table to start at the new offset */
+ for (k = 0; k < (u16)NUM_PDADC(diff); k++ ) {
+ pdadcValues[k] = pdadcValues[k + diff];
+ }
- /* fill the back of the table */
- for (k = (u16)NUM_PDADC(diff); k < NUM_PDADC(0); k++) {
- pdadcValues[k] = pdadcValues[NUM_PDADC(diff)];
+ /* fill the back of the table */
+ for (k = (u16)NUM_PDADC(diff); k < NUM_PDADC(0); k++) {
+ pdadcValues[k] = pdadcValues[NUM_PDADC(diff)];
+ }
}
}
}
@@ -769,7 +775,7 @@ static void ath9k_hw_set_def_power_cal_table(struct ath_hw *ah,
struct cal_data_per_freq *pRawDataset;
u8 *pCalBChans = NULL;
u16 pdGainOverlap_t2;
- static u8 pdadcValues[AR5416_NUM_PDADC_VALUES];
+ u8 pdadcValues[AR5416_NUM_PDADC_VALUES];
u16 gainBoundaries[AR5416_PD_GAINS_IN_MASK];
u16 numPiers, i, j;
int16_t diff = 0;
--
2.54.0
^ permalink raw reply related
* [RFC PATCH] wifi: mt76: fail channel switch when TX queues do not drain
From: Pengpeng Hou @ 2026-06-25 0:38 UTC (permalink / raw)
To: Felix Fietkau
Cc: pengpeng, Lorenzo Bianconi, Ryder Lee, Shayne Chen, Sean Wang,
Matthias Brugger, AngeloGioacchino Del Regno, linux-wireless,
linux-kernel, linux-arm-kernel, linux-mediatek
__mt76_set_channel() disables the TX worker, schedules pending TX queues,
sets MT76_RESET, and waits for pending TX to drain before changing the PHY
channel state and calling the driver set_channel() callback.
The wait result is ignored. If TX does not drain within the timeout, the
function still publishes the new channel state and asks the driver to
switch channels while old-channel TX may remain pending.
Return -ETIMEDOUT when the drain wait expires. Keep the survey update on
the old channel, then clear MT76_RESET and re-enable the TX worker through
the existing exit path.
This is marked RFC because the existing channel switch path treats the TX
drain wait as best-effort. Maintainer feedback is needed on whether a TX
drain timeout should fail the channel switch for all mt76 devices or
whether the current force-forward behavior is intentional.
Signed-off-by: Pengpeng Hou <pengpeng@iscas.ac.cn>
---
drivers/net/wireless/mediatek/mt76/mac80211.c | 9 ++++++++-
1 file changed, 8 insertions(+), 1 deletion(-)
diff --git a/drivers/net/wireless/mediatek/mt76/mac80211.c b/drivers/net/wireless/mediatek/mt76/mac80211.c
index 13c4e8abe..37474d64c 100644
--- a/drivers/net/wireless/mediatek/mt76/mac80211.c
+++ b/drivers/net/wireless/mediatek/mt76/mac80211.c
@@ -1033,6 +1033,7 @@ int __mt76_set_channel(struct mt76_phy *phy, struct cfg80211_chan_def *chandef,
bool offchannel)
{
struct mt76_dev *dev = phy->dev;
+ long time_left;
int timeout = HZ / 5;
int ret;
@@ -1040,8 +1041,13 @@ int __mt76_set_channel(struct mt76_phy *phy, struct cfg80211_chan_def *chandef,
mt76_txq_schedule_pending(phy);
set_bit(MT76_RESET, &phy->state);
- wait_event_timeout(dev->tx_wait, !mt76_has_tx_pending(phy), timeout);
+ time_left = wait_event_timeout(dev->tx_wait, !mt76_has_tx_pending(phy),
+ timeout);
mt76_update_survey(phy);
+ if (!time_left) {
+ ret = -ETIMEDOUT;
+ goto out;
+ }
if (phy->chandef.chan->center_freq != chandef->chan->center_freq ||
phy->chandef.width != chandef->width)
@@ -1059,6 +1065,7 @@ int __mt76_set_channel(struct mt76_phy *phy, struct cfg80211_chan_def *chandef,
ret = dev->drv->set_channel(phy);
+out:
clear_bit(MT76_RESET, &phy->state);
mt76_worker_enable(&dev->tx_worker);
mt76_worker_schedule(&dev->tx_worker);
--
2.50.1 (Apple Git-155)
^ permalink raw reply related
* [PATCH v2 9/9] wifi: mt76: mt792x: advertise NAN data support
From: Sean Wang @ 2026-06-25 0:18 UTC (permalink / raw)
To: Felix Fietkau, Lorenzo Bianconi
Cc: chengwei.yu, yu-ching.liu, jenhao.yang, posh.sun, linux-wireless,
linux-mediatek, Sean Wang
In-Reply-To: <20260625001834.475094-1-sean.wang@kernel.org>
From: Sean Wang <sean.wang@mediatek.com>
Advertise NAN and NAN data support when firmware exposes NAN
capability.
Add NAN interface combinations on top of the dynamic combination
framework, advertise 2.4 GHz and 5 GHz NAN bands, and enable secure
NAN.
Keep the base interface combinations unchanged when NAN is unavailable
so existing STA/AP/P2P modes keep the same limits.
Co-developed-by: Stella Liu <yu-ching.liu@mediatek.com>
Signed-off-by: Stella Liu <yu-ching.liu@mediatek.com>
Co-developed-by: Jeremy Yu <chengwei.yu@mediatek.com>
Signed-off-by: Jeremy Yu <chengwei.yu@mediatek.com>
Signed-off-by: Sean Wang <sean.wang@mediatek.com>
---
.../net/wireless/mediatek/mt76/mt792x_core.c | 96 ++++++++++++++++++-
1 file changed, 92 insertions(+), 4 deletions(-)
diff --git a/drivers/net/wireless/mediatek/mt76/mt792x_core.c b/drivers/net/wireless/mediatek/mt76/mt792x_core.c
index ffe0bcdf1df6..411c04640add 100644
--- a/drivers/net/wireless/mediatek/mt76/mt792x_core.c
+++ b/drivers/net/wireless/mediatek/mt76/mt792x_core.c
@@ -3,6 +3,7 @@
#include <linux/module.h>
#include <linux/firmware.h>
+#include <linux/slab.h>
#include "mt792x.h"
#include "dma.h"
@@ -60,6 +61,40 @@ static const struct ieee80211_iface_limit if_limits_chanctx_scc[] = {
}
};
+static const struct ieee80211_iface_limit if_limits_nan_mcc[] = {
+ {
+ .max = 2,
+ .types = BIT(NL80211_IFTYPE_STATION),
+ },
+ {
+ .max = 1,
+ .types = BIT(NL80211_IFTYPE_NAN),
+ },
+ {
+ .max = 2,
+ .types = BIT(NL80211_IFTYPE_NAN_DATA),
+ },
+};
+
+static const struct ieee80211_iface_limit if_limits_nan_scc[] = {
+ {
+ .max = 2,
+ .types = BIT(NL80211_IFTYPE_STATION),
+ },
+ {
+ .max = 1,
+ .types = BIT(NL80211_IFTYPE_NAN),
+ },
+ {
+ .max = 2,
+ .types = BIT(NL80211_IFTYPE_NAN_DATA),
+ },
+ {
+ .max = 1,
+ .types = BIT(NL80211_IFTYPE_AP),
+ },
+};
+
static const struct ieee80211_iface_combination if_comb_chanctx_base[] = {
{
.limits = if_limits_chanctx_mcc,
@@ -77,9 +112,31 @@ static const struct ieee80211_iface_combination if_comb_chanctx_base[] = {
}
};
-static int mt792x_setup_iface_combinations(struct mt792x_dev *dev)
+static const struct ieee80211_iface_combination if_comb_chanctx_nan[] = {
+ {
+ .limits = if_limits_nan_mcc,
+ .n_limits = ARRAY_SIZE(if_limits_nan_mcc),
+ .max_interfaces = MT792x_MAX_INTERFACES,
+ .num_different_channels = 2,
+ .beacon_int_infra_match = false,
+ },
+ {
+ .limits = if_limits_nan_scc,
+ .n_limits = ARRAY_SIZE(if_limits_nan_scc),
+ .max_interfaces = MT792x_MAX_INTERFACES,
+ .num_different_channels = 1,
+ .beacon_int_infra_match = false,
+ },
+};
+
+static int mt792x_setup_iface_combinations(struct mt792x_dev *dev,
+ struct wiphy *wiphy)
{
const bool cnm = !!(dev->fw_features & MT792x_FW_CAP_CNM);
+ const bool nan = !!(dev->fw_features & MT792x_FW_CAP_NAN);
+ const int n_base = ARRAY_SIZE(if_comb_chanctx_base);
+ const int n_nan = ARRAY_SIZE(if_comb_chanctx_nan);
+ struct ieee80211_iface_combination *comb;
if (!cnm) {
dev->iface_combinations = if_comb;
@@ -87,8 +144,24 @@ static int mt792x_setup_iface_combinations(struct mt792x_dev *dev)
return 0;
}
- dev->iface_combinations = if_comb_chanctx_base;
- dev->n_iface_combinations = ARRAY_SIZE(if_comb_chanctx_base);
+ /* CNM enabled, NAN optional */
+ if (!nan) {
+ dev->iface_combinations = if_comb_chanctx_base;
+ dev->n_iface_combinations = ARRAY_SIZE(if_comb_chanctx_base);
+ return 0;
+ }
+
+ /* CNM + NAN: dynamically build base + nan list */
+ comb = devm_kcalloc(&wiphy->dev, n_base + n_nan, sizeof(*comb),
+ GFP_KERNEL);
+ if (!comb)
+ return -ENOMEM;
+
+ memcpy(comb, if_comb_chanctx_base, sizeof(if_comb_chanctx_base));
+ memcpy(comb + n_base, if_comb_chanctx_nan, sizeof(if_comb_chanctx_nan));
+
+ dev->iface_combinations = comb;
+ dev->n_iface_combinations = n_base + n_nan;
return 0;
}
@@ -705,7 +778,7 @@ int mt792x_init_wiphy(struct ieee80211_hw *hw)
else
wiphy->flags &= ~WIPHY_FLAG_HAS_REMAIN_ON_CHANNEL;
- err = mt792x_setup_iface_combinations(dev);
+ err = mt792x_setup_iface_combinations(dev, wiphy);
if (err)
return err;
@@ -719,6 +792,21 @@ int mt792x_init_wiphy(struct ieee80211_hw *hw)
BIT(NL80211_IFTYPE_P2P_GO) |
BIT(NL80211_IFTYPE_P2P_DEVICE);
+ if ((dev->fw_features & MT792x_FW_CAP_CNM) &&
+ (dev->fw_features & MT792x_FW_CAP_NAN)) {
+ wiphy->interface_modes |= BIT(NL80211_IFTYPE_NAN) |
+ BIT(NL80211_IFTYPE_NAN_DATA);
+ wiphy->nan_supported_bands = BIT(NL80211_BAND_2GHZ) |
+ BIT(NL80211_BAND_5GHZ);
+ wiphy->nan_capa.flags = WIPHY_NAN_FLAGS_CONFIGURABLE_SYNC |
+ WIPHY_NAN_FLAGS_USERSPACE_DE;
+ wiphy->nan_capa.op_mode = NAN_OP_MODE_PHY_MODE_MASK;
+ wiphy->nan_capa.n_antennas = 0x22;
+ wiphy->nan_capa.max_channel_switch_time = 12;
+ wiphy->nan_capa.dev_capabilities = NAN_DEV_CAPA_EXT_KEY_ID_SUPPORTED;
+ wiphy_ext_feature_set(wiphy, NL80211_EXT_FEATURE_SECURE_NAN);
+ }
+
wiphy->max_scan_ie_len = MT76_CONNAC_SCAN_IE_LEN;
wiphy->max_scan_ssids = 4;
wiphy->max_sched_scan_plan_interval =
--
2.43.0
^ permalink raw reply related
* [PATCH v2 8/9] wifi: mt76: mt792x: build iface combinations dynamically
From: Sean Wang @ 2026-06-25 0:18 UTC (permalink / raw)
To: Felix Fietkau, Lorenzo Bianconi
Cc: chengwei.yu, yu-ching.liu, jenhao.yang, posh.sun, linux-wireless,
linux-mediatek, Sean Wang
In-Reply-To: <20260625001834.475094-1-sean.wang@kernel.org>
From: Sean Wang <sean.wang@mediatek.com>
Move mt792x interface combination selection into a helper and store the
selected table in mt792x device state.
This keeps the existing non-CNM and CNM combinations unchanged while
making later firmware-gated extensions add combinations without touching
the common wiphy setup path.
Co-developed-by: Stella Liu <yu-ching.liu@mediatek.com>
Signed-off-by: Stella Liu <yu-ching.liu@mediatek.com>
Co-developed-by: Jeremy Yu <chengwei.yu@mediatek.com>
Signed-off-by: Jeremy Yu <chengwei.yu@mediatek.com>
Signed-off-by: Sean Wang <sean.wang@mediatek.com>
---
drivers/net/wireless/mediatek/mt76/mt792x.h | 2 ++
.../net/wireless/mediatek/mt76/mt792x_core.c | 36 ++++++++++++++-----
2 files changed, 30 insertions(+), 8 deletions(-)
diff --git a/drivers/net/wireless/mediatek/mt76/mt792x.h b/drivers/net/wireless/mediatek/mt76/mt792x.h
index 9d5a2adc81f6..73f2333c2970 100644
--- a/drivers/net/wireless/mediatek/mt76/mt792x.h
+++ b/drivers/net/wireless/mediatek/mt76/mt792x.h
@@ -324,6 +324,8 @@ struct mt792x_dev {
struct ieee80211_chanctx_conf *new_ctx;
struct ieee80211_vif *nan_vif;
+ const struct ieee80211_iface_combination *iface_combinations;
+ int n_iface_combinations;
};
static inline struct mt792x_bss_conf *
diff --git a/drivers/net/wireless/mediatek/mt76/mt792x_core.c b/drivers/net/wireless/mediatek/mt76/mt792x_core.c
index a0db815c27bc..ffe0bcdf1df6 100644
--- a/drivers/net/wireless/mediatek/mt76/mt792x_core.c
+++ b/drivers/net/wireless/mediatek/mt76/mt792x_core.c
@@ -60,7 +60,7 @@ static const struct ieee80211_iface_limit if_limits_chanctx_scc[] = {
}
};
-static const struct ieee80211_iface_combination if_comb_chanctx[] = {
+static const struct ieee80211_iface_combination if_comb_chanctx_base[] = {
{
.limits = if_limits_chanctx_mcc,
.n_limits = ARRAY_SIZE(if_limits_chanctx_mcc),
@@ -77,6 +77,22 @@ static const struct ieee80211_iface_combination if_comb_chanctx[] = {
}
};
+static int mt792x_setup_iface_combinations(struct mt792x_dev *dev)
+{
+ const bool cnm = !!(dev->fw_features & MT792x_FW_CAP_CNM);
+
+ if (!cnm) {
+ dev->iface_combinations = if_comb;
+ dev->n_iface_combinations = ARRAY_SIZE(if_comb);
+ return 0;
+ }
+
+ dev->iface_combinations = if_comb_chanctx_base;
+ dev->n_iface_combinations = ARRAY_SIZE(if_comb_chanctx_base);
+
+ return 0;
+}
+
void mt792x_tx(struct ieee80211_hw *hw, struct ieee80211_tx_control *control,
struct sk_buff *skb)
{
@@ -663,6 +679,7 @@ int mt792x_init_wiphy(struct ieee80211_hw *hw)
struct mt792x_phy *phy = mt792x_hw_phy(hw);
struct mt792x_dev *dev = phy->dev;
struct wiphy *wiphy = hw->wiphy;
+ int err;
hw->queues = 4;
if (dev->has_eht) {
@@ -683,15 +700,17 @@ int mt792x_init_wiphy(struct ieee80211_hw *hw)
hw->vif_data_size = sizeof(struct mt792x_vif);
hw->chanctx_data_size = sizeof(struct mt792x_chanctx);
- if (dev->fw_features & MT792x_FW_CAP_CNM) {
+ if (dev->fw_features & MT792x_FW_CAP_CNM)
wiphy->flags |= WIPHY_FLAG_HAS_REMAIN_ON_CHANNEL;
- wiphy->iface_combinations = if_comb_chanctx;
- wiphy->n_iface_combinations = ARRAY_SIZE(if_comb_chanctx);
- } else {
+ else
wiphy->flags &= ~WIPHY_FLAG_HAS_REMAIN_ON_CHANNEL;
- wiphy->iface_combinations = if_comb;
- wiphy->n_iface_combinations = ARRAY_SIZE(if_comb);
- }
+
+ err = mt792x_setup_iface_combinations(dev);
+ if (err)
+ return err;
+
+ wiphy->iface_combinations = dev->iface_combinations;
+ wiphy->n_iface_combinations = dev->n_iface_combinations;
wiphy->flags &= ~(WIPHY_FLAG_IBSS_RSN | WIPHY_FLAG_4ADDR_AP |
WIPHY_FLAG_4ADDR_STATION);
wiphy->interface_modes = BIT(NL80211_IFTYPE_STATION) |
@@ -699,6 +718,7 @@ int mt792x_init_wiphy(struct ieee80211_hw *hw)
BIT(NL80211_IFTYPE_P2P_CLIENT) |
BIT(NL80211_IFTYPE_P2P_GO) |
BIT(NL80211_IFTYPE_P2P_DEVICE);
+
wiphy->max_scan_ie_len = MT76_CONNAC_SCAN_IE_LEN;
wiphy->max_scan_ssids = 4;
wiphy->max_sched_scan_plan_interval =
--
2.43.0
^ permalink raw reply related
* [PATCH v2 7/9] wifi: mt76: mt7925: wire up NAN operations
From: Sean Wang @ 2026-06-25 0:18 UTC (permalink / raw)
To: Felix Fietkau, Lorenzo Bianconi
Cc: chengwei.yu, yu-ching.liu, jenhao.yang, posh.sun, linux-wireless,
linux-mediatek, Sean Wang
In-Reply-To: <20260625001834.475094-1-sean.wang@kernel.org>
From: Sean Wang <sean.wang@mediatek.com>
Wire mac80211 NAN start, stop and change_conf callbacks to the mt7925 NAN
MCU helpers. Track the active NAN vif and notify mac80211 on cluster join
events.
Initialize NAN PHY capabilities after the supported bands are ready.
Co-developed-by: Stella Liu <yu-ching.liu@mediatek.com>
Signed-off-by: Stella Liu <yu-ching.liu@mediatek.com>
Co-developed-by: Jeremy Yu <chengwei.yu@mediatek.com>
Signed-off-by: Jeremy Yu <chengwei.yu@mediatek.com>
Signed-off-by: Sean Wang <sean.wang@mediatek.com>
---
.../net/wireless/mediatek/mt76/mt7925/init.c | 29 +++
.../net/wireless/mediatek/mt76/mt7925/main.c | 201 ++++++++++++++-
.../net/wireless/mediatek/mt76/mt7925/nan.c | 239 +++++++++++++++---
.../net/wireless/mediatek/mt76/mt7925/nan.h | 2 +
drivers/net/wireless/mediatek/mt76/mt792x.h | 3 +
5 files changed, 430 insertions(+), 44 deletions(-)
diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/init.c b/drivers/net/wireless/mediatek/mt76/mt7925/init.c
index e85b0d104fbe..1b44f5c8fb0d 100644
--- a/drivers/net/wireless/mediatek/mt76/mt7925/init.c
+++ b/drivers/net/wireless/mediatek/mt76/mt7925/init.c
@@ -152,6 +152,33 @@ static int mt7925_init_hardware(struct mt792x_dev *dev)
return 0;
}
+static int mt7925_init_nan_cap(struct mt76_dev *mdev)
+{
+ struct mt792x_dev *dev = container_of(mdev, struct mt792x_dev, mt76);
+ const struct ieee80211_sta_he_cap *he_cap;
+ struct ieee80211_supported_band *sband;
+ struct wiphy *wiphy = mdev->hw->wiphy;
+
+ if (!(dev->fw_features & MT792x_FW_CAP_NAN))
+ return 0;
+
+ sband = wiphy->bands[NL80211_BAND_2GHZ];
+ if (sband)
+ wiphy->nan_capa.phy.ht = sband->ht_cap;
+
+ sband = wiphy->bands[NL80211_BAND_5GHZ];
+ if (sband)
+ wiphy->nan_capa.phy.vht = sband->vht_cap;
+
+ sband = wiphy->bands[NL80211_BAND_2GHZ];
+ he_cap = sband ? ieee80211_get_he_iftype_cap(sband, NL80211_IFTYPE_NAN)
+ : NULL;
+ if (he_cap)
+ wiphy->nan_capa.phy.he = *he_cap;
+
+ return 0;
+}
+
static void mt7925_init_work(struct work_struct *work)
{
struct mt792x_dev *dev = container_of(work, struct mt792x_dev,
@@ -172,6 +199,8 @@ static void mt7925_init_work(struct work_struct *work)
return;
}
+ dev->mt76.init_wiphy = mt7925_init_nan_cap;
+
ret = mt76_register_device(&dev->mt76, true, mt76_rates,
ARRAY_SIZE(mt76_rates));
if (ret) {
diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/main.c b/drivers/net/wireless/mediatek/mt76/mt7925/main.c
index a9059866b701..ddb637d6a3c3 100644
--- a/drivers/net/wireless/mediatek/mt76/mt7925/main.c
+++ b/drivers/net/wireless/mediatek/mt76/mt7925/main.c
@@ -11,6 +11,7 @@
#include "regd.h"
#include "mcu.h"
#include "mac.h"
+#include "nan.h"
static void
mt7925_init_he_caps(struct mt792x_phy *phy, enum nl80211_band band,
@@ -412,7 +413,8 @@ static int mt7925_mac_link_bss_add(struct mt792x_dev *dev,
0 : mconf->mt76.idx % MT76_CONNAC_MAX_WMM_SETS;
mconf->mt76.link_idx = hweight16(mvif->valid_links);
- if (mvif->phy->mt76->chandef.chan->band != NL80211_BAND_2GHZ)
+ if (mvif->phy->mt76->chandef.chan &&
+ mvif->phy->mt76->chandef.chan->band != NL80211_BAND_2GHZ)
mconf->mt76.basic_rates_idx = MT792x_BASIC_RATES_TBL + 4;
else
mconf->mt76.basic_rates_idx = MT792x_BASIC_RATES_TBL;
@@ -474,12 +476,32 @@ mt7925_add_interface(struct ieee80211_hw *hw, struct ieee80211_vif *vif)
INIT_WORK(&mvif->csa_work, mt7925_csa_work);
timer_setup(&mvif->csa_timer, mt792x_csa_timer, 0);
+ if (vif->type == NL80211_IFTYPE_NAN)
+ dev->nan_vif = vif;
out:
mt792x_mutex_release(dev);
return ret;
}
+static void
+mt7925_remove_interface(struct ieee80211_hw *hw, struct ieee80211_vif *vif)
+{
+ struct mt792x_vif *mvif = (struct mt792x_vif *)vif->drv_priv;
+ struct mt792x_dev *dev = mt792x_hw_dev(hw);
+ struct mt792x_bss_conf *mconf;
+
+ mt792x_mutex_acquire(dev);
+
+ if (dev->nan_vif == vif)
+ dev->nan_vif = NULL;
+
+ mconf = mt792x_link_conf_to_mconf(&vif->bss_conf);
+ mt792x_mac_link_bss_remove(dev, mconf, &mvif->sta.deflink);
+
+ mt792x_mutex_release(dev);
+}
+
static void mt7925_roc_iter(void *priv, u8 *mac,
struct ieee80211_vif *vif)
{
@@ -1217,19 +1239,36 @@ static void mt7925_mac_link_sta_assoc(struct mt76_dev *mdev,
int mt7925_mac_sta_event(struct mt76_dev *mdev, struct ieee80211_vif *vif,
struct ieee80211_sta *sta, enum mt76_sta_event ev)
{
+ struct mt792x_dev *dev = container_of(mdev, struct mt792x_dev, mt76);
struct ieee80211_link_sta *link_sta = &sta->deflink;
- if (ev != MT76_STA_EVENT_ASSOC)
- return 0;
+ switch (ev) {
+ case MT76_STA_EVENT_ASSOC:
+ if (ieee80211_vif_is_mld(vif)) {
+ struct mt792x_sta *msta =
+ (struct mt792x_sta *)sta->drv_priv;
- if (ieee80211_vif_is_mld(vif)) {
- struct mt792x_sta *msta = (struct mt792x_sta *)sta->drv_priv;
+ link_sta = mt792x_sta_to_link_sta(vif, sta,
+ msta->deflink_id);
+ mt7925_mac_set_links(mdev, vif);
+ }
- link_sta = mt792x_sta_to_link_sta(vif, sta, msta->deflink_id);
- mt7925_mac_set_links(mdev, vif);
- }
+ mt7925_mac_link_sta_assoc(mdev, vif, link_sta);
+ break;
+ case MT76_STA_EVENT_AUTHORIZE:
+ if (vif->type == NL80211_IFTYPE_NAN_DATA) {
+ int ret;
- mt7925_mac_link_sta_assoc(mdev, vif, link_sta);
+ mt792x_mutex_acquire(dev);
+ ret = mt792x_nan_map_sta_rec(mdev, vif, sta);
+ mt792x_mutex_release(dev);
+
+ return ret;
+ }
+ break;
+ default:
+ break;
+ }
return 0;
}
@@ -1357,6 +1396,36 @@ void mt7925_mac_sta_remove(struct mt76_dev *mdev, struct ieee80211_vif *vif,
struct mt792x_sta *msta = (struct mt792x_sta *)sta->drv_priv;
struct mt792x_vif *mvif = (struct mt792x_vif *)vif->drv_priv;
+ /* Release NAN peer record before tearing down the STA. */
+ if (vif->type == NL80211_IFTYPE_NAN ||
+ vif->type == NL80211_IFTYPE_NAN_DATA) {
+ int ret = mt792x_nan_set_peer_rec(mdev, sta);
+
+ if (ret)
+ dev_err(mdev->dev,
+ "NAN: failed to deactivate peer record: %d\n",
+ ret);
+ }
+
+ /* Release NDP context ID for NAN_DATA sta. */
+ if (vif->type == NL80211_IFTYPE_NAN_DATA) {
+ struct ieee80211_sta *nmi_sta;
+
+ rcu_read_lock();
+ nmi_sta = rcu_dereference(sta->nmi);
+ if (nmi_sta) {
+ struct mt792x_sta *nmi_msta =
+ (struct mt792x_sta *)nmi_sta->drv_priv;
+
+ if (msta->nan_sched.ndp_ctx_assigned) {
+ clear_bit(msta->nan_sched.ndp_ctx_id,
+ &nmi_msta->nan_sched.ndp_ctx_bitmap);
+ msta->nan_sched.ndp_ctx_assigned = false;
+ }
+ }
+ rcu_read_unlock();
+ }
+
if (ieee80211_vif_is_mld(vif)) {
mt7925_mac_sta_remove_links(dev, vif, sta, msta->valid_links);
mt7925_mcu_del_dev(mdev, vif);
@@ -2059,6 +2128,11 @@ static void mt7925_vif_cfg_changed(struct ieee80211_hw *hw,
}
mt792x_mutex_release(dev);
+
+ if (vif->type == NL80211_IFTYPE_NAN &&
+ changed & BSS_CHANGED_NAN_LOCAL_SCHED) {
+ mt7925_nan_local_sched_changed(dev, vif);
+ }
}
static void mt7925_link_info_changed(struct ieee80211_hw *hw,
@@ -2481,12 +2555,115 @@ static void mt7925_channel_switch_rx_beacon(struct ieee80211_hw *hw,
}
}
+static int mt7925_start_nan(struct ieee80211_hw *hw,
+ struct ieee80211_vif *vif,
+ struct cfg80211_nan_conf *conf)
+{
+ struct ieee80211_bss_conf *link_conf = &vif->bss_conf;
+ struct mt792x_dev *dev = mt792x_hw_dev(hw);
+ struct ieee80211_channel *chan;
+ int err = 0;
+
+ mt792x_mutex_acquire(dev);
+
+ chan = conf->band_cfgs[NL80211_BAND_2GHZ].chan;
+ if (!chan) {
+ err = -EINVAL;
+ goto out;
+ }
+
+ cfg80211_chandef_create(&link_conf->chanreq.oper, chan,
+ NL80211_CHAN_NO_HT);
+
+ err = mt7925_mcu_add_bss_info(&dev->phy, NULL, link_conf,
+ NULL, true);
+ if (err < 0)
+ goto out;
+
+ dev->nan_vif = vif;
+
+ err = mt7925_nan_set_nmi_addr(dev, vif->addr);
+ if (err)
+ goto rollback_bss;
+
+ err = mt7925_nan_enable(vif, dev, conf);
+ if (err)
+ goto rollback_bss;
+
+ goto out;
+
+rollback_bss:
+ dev->nan_vif = NULL;
+ mt7925_mcu_add_bss_info(&dev->phy, NULL, link_conf, NULL, false);
+
+out:
+ mt792x_mutex_release(dev);
+
+ return err;
+}
+
+static int mt7925_stop_nan(struct ieee80211_hw *hw,
+ struct ieee80211_vif *vif)
+{
+ struct ieee80211_bss_conf *link_conf = &vif->bss_conf;
+ struct mt792x_dev *dev = mt792x_hw_dev(hw);
+ int err, ret;
+
+ mt792x_mutex_acquire(dev);
+
+ err = mt7925_nan_disable(vif, dev);
+
+ ret = mt7925_mcu_add_bss_info(&dev->phy, NULL, link_conf,
+ NULL, false);
+ if (!err)
+ err = ret;
+
+ if (dev->nan_vif == vif)
+ dev->nan_vif = NULL;
+
+ mt792x_mutex_release(dev);
+
+ return err;
+}
+
+static int mt7925_nan_change_conf(struct ieee80211_hw *hw,
+ struct ieee80211_vif *vif,
+ struct cfg80211_nan_conf *conf,
+ u32 changes)
+{
+ struct mt792x_dev *dev = mt792x_hw_dev(hw);
+ int err = 0;
+
+ mt792x_mutex_acquire(dev);
+
+ err = mt7925_nan_change_configure(vif, dev, conf);
+
+ mt792x_mutex_release(dev);
+
+ return err;
+}
+
+static int mt7925_nan_peer_sched_changed(struct ieee80211_hw *hw,
+ struct ieee80211_sta *sta)
+{
+ struct mt792x_dev *dev = mt792x_hw_dev(hw);
+ int err = 0;
+
+ mt792x_mutex_acquire(dev);
+
+ err = mt792x_nan_set_peer_schedule(dev, sta);
+
+ mt792x_mutex_release(dev);
+
+ return err;
+}
+
const struct ieee80211_ops mt7925_ops = {
.tx = mt792x_tx,
.start = mt7925_start,
.stop = mt792x_stop,
.add_interface = mt7925_add_interface,
- .remove_interface = mt792x_remove_interface,
+ .remove_interface = mt7925_remove_interface,
.config = mt7925_config,
.conf_tx = mt7925_conf_tx,
.configure_filter = mt7925_configure_filter,
@@ -2550,6 +2727,10 @@ const struct ieee80211_ops mt7925_ops = {
.channel_switch = mt7925_channel_switch,
.abort_channel_switch = mt7925_abort_channel_switch,
.channel_switch_rx_beacon = mt7925_channel_switch_rx_beacon,
+ .start_nan = mt7925_start_nan,
+ .stop_nan = mt7925_stop_nan,
+ .nan_change_conf = mt7925_nan_change_conf,
+ .nan_peer_sched_changed = mt7925_nan_peer_sched_changed,
};
EXPORT_SYMBOL_GPL(mt7925_ops);
diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/nan.c b/drivers/net/wireless/mediatek/mt76/mt7925/nan.c
index dc7aa2cd9449..849952c6ac21 100644
--- a/drivers/net/wireless/mediatek/mt76/mt7925/nan.c
+++ b/drivers/net/wireless/mediatek/mt76/mt7925/nan.c
@@ -31,6 +31,8 @@ static void mt7925_nan_set_5g_channel(struct mt792x_dev *dev,
if (!mt7925_regd_is_valid_channel(dev, NL80211_BAND_5GHZ, chan))
return;
+ req->config_support_5g = 1;
+ req->support_5g_val = 1;
req->config_5g_channel = 1;
if (chan->hw_value == NAN_5G_LOW_DISC_CHANNEL)
@@ -41,6 +43,16 @@ static void mt7925_nan_set_5g_channel(struct mt792x_dev *dev,
req->channel_5g_val = cpu_to_le32(ch5g);
}
+static void mt7925_nan_set_2g_support(struct mt7925_nan_enable_req_tlv *req,
+ struct cfg80211_nan_conf *conf)
+{
+ if (!conf->band_cfgs[NL80211_BAND_2GHZ].chan)
+ return;
+
+ req->config_2dot4g_support = 1;
+ req->support_2dot4g_val = 1;
+}
+
static void mt7925_nan_set_cluster_id(struct mt7925_nan_enable_req_tlv *req,
const u8 *cluster_id)
{
@@ -131,6 +143,38 @@ mt7925_nan_update_conf(struct mt792x_vif *mvif,
memcpy(mvif->nan.conf.cluster_id, conf->cluster_id, ETH_ALEN);
}
+int mt7925_nan_set_nmi_addr(struct mt792x_dev *dev, const u8 *addr)
+{
+ struct mt76_dev *mdev;
+ struct {
+ u8 rsv[4];
+ struct mt7925_nan_nmi_addr_tlv nmi_addr_tlv;
+ } nmi_cmd = {
+ .rsv = { 0 },
+ .nmi_addr_tlv = {
+ .tag = cpu_to_le16(NAN_UNI_CMD_CHANGE_NMI_ADDRESS),
+ .len = cpu_to_le16(sizeof(struct mt7925_nan_nmi_addr_tlv)),
+ },
+ };
+ int ret;
+
+ if (!dev || !addr)
+ return -EINVAL;
+
+ if (is_zero_ether_addr(addr) || is_multicast_ether_addr(addr)) {
+ dev_err(dev->mt76.dev, "NAN: invalid NMI address %pM\n", addr);
+ return -EINVAL;
+ }
+
+ mdev = &dev->mt76;
+ memcpy(nmi_cmd.nmi_addr_tlv.nmi_addr, addr, ETH_ALEN);
+
+ ret = mt76_mcu_send_msg(mdev, MCU_UNI_CMD(NAN), &nmi_cmd,
+ sizeof(nmi_cmd), true);
+
+ return ret;
+}
+
int mt7925_nan_enable(struct ieee80211_vif *vif,
struct mt792x_dev *dev,
struct cfg80211_nan_conf *conf)
@@ -152,12 +196,14 @@ int mt7925_nan_enable(struct ieee80211_vif *vif,
},
};
struct mt7925_nan_enable_req_tlv *p_nan_req_tlv = &nan_cmd.nan_req_tlv;
+ int ret;
if (!vif || !dev || !conf)
return -EINVAL;
p_nan_req_tlv->master_pref = conf->master_pref;
+ mt7925_nan_set_2g_support(p_nan_req_tlv, conf);
mt7925_nan_set_5g_channel(dev, p_nan_req_tlv, conf);
mt7925_nan_set_cluster_id(p_nan_req_tlv, conf->cluster_id);
mt7925_nan_set_dw_interval(p_nan_req_tlv, conf);
@@ -167,7 +213,9 @@ int mt7925_nan_enable(struct ieee80211_vif *vif,
mt7925_nan_update_conf(mvif, conf);
- return mt76_mcu_send_msg(mdev, MCU_UNI_CMD(NAN), &nan_cmd, sizeof(nan_cmd), true);
+ ret = mt76_mcu_send_msg(mdev, MCU_UNI_CMD(NAN), &nan_cmd, sizeof(nan_cmd), true);
+
+ return ret;
}
int mt7925_nan_disable(struct ieee80211_vif *vif, struct mt792x_dev *dev)
@@ -427,7 +475,7 @@ mt7925_nan_mcu_handle_de_event(struct mt792x_dev *dev, struct tlv *tlv)
if (de_evt->event_type != NAN_EVENT_ID_JOINED_CLUSTER)
return;
- if (!ieee80211_vif_nan_started(dev->nan_vif)) {
+ if (!dev->nan_vif || !ieee80211_vif_nan_started(dev->nan_vif)) {
dev_warn(dev->mt76.dev, "nan: joined-cluster event but NAN not started\n");
return;
}
@@ -592,16 +640,21 @@ void mt7925_nan_local_sched_changed(struct mt792x_dev *dev,
{
struct mt7925_nan_common_hdr *hdr;
struct mt76_dev *mdev;
+ bool deferred;
struct sk_buff *skb;
+ int ret = -ENOMEM;
if (!dev || !vif)
return;
mdev = &dev->mt76;
+ deferred = vif->cfg.nan_sched.deferred;
+
+ mt792x_mutex_acquire(dev);
skb = mt76_mcu_msg_alloc(mdev, NULL, MT7925_NAN_AVAIL_MAX_SIZE);
if (!skb)
- return;
+ goto out;
hdr = (struct mt7925_nan_common_hdr *)skb_put(skb, sizeof(*hdr));
memset(hdr, 0, sizeof(*hdr));
@@ -609,11 +662,22 @@ void mt7925_nan_local_sched_changed(struct mt792x_dev *dev,
if (mt7925_nan_avail_ctrl_tlv(skb, vif) ||
mt7925_nan_avail_tlv(skb, vif)) {
dev_kfree_skb(skb);
- return;
+ goto out;
}
- mt76_mcu_skb_send_msg(mdev, skb,
- MCU_UNI_CMD(NAN), true);
+ ret = mt76_mcu_skb_send_msg(mdev, skb,
+ MCU_UNI_CMD(NAN), true);
+out:
+ mt792x_mutex_release(dev);
+
+ if (deferred) {
+ if (ret)
+ dev_err(mdev->dev,
+ "NAN: local schedule update failed: %d\n",
+ ret);
+
+ ieee80211_nan_sched_update_done(vif);
+ }
}
static int mt7925_nan_peer_rec_tlv(struct sk_buff *skb,
@@ -641,6 +705,23 @@ static int mt7925_nan_peer_rec_tlv(struct sk_buff *skb,
return 0;
}
+static u8 mt7925_nan_get_supported_bands(struct mt792x_vif *mvif)
+{
+ struct wiphy *wiphy;
+ u8 bands = 0;
+
+ if (!mvif || !mvif->phy)
+ return BIT(NAN_SUPPORTED_BAND_ID_2P4G);
+
+ wiphy = mvif->phy->mt76->hw->wiphy;
+ if (wiphy->nan_supported_bands & BIT(NL80211_BAND_2GHZ))
+ bands |= BIT(NAN_SUPPORTED_BAND_ID_2P4G);
+ if (wiphy->nan_supported_bands & BIT(NL80211_BAND_5GHZ))
+ bands |= BIT(NAN_SUPPORTED_BAND_ID_5G);
+
+ return bands ?: BIT(NAN_SUPPORTED_BAND_ID_2P4G);
+}
+
static int mt7925_nan_peer_cap_tlv(struct sk_buff *skb,
struct ieee80211_sta *sta,
struct mt792x_sta *msta)
@@ -667,7 +748,8 @@ static int mt7925_nan_peer_cap_tlv(struct sk_buff *skb,
peer_cap_tlv = (struct mt7925_nan_sched_update_peer_cap_tlv *)tlv;
peer_cap_tlv->sch_idx = msta->nan_sched.sch_idx;
- peer_cap_tlv->supported_bands = BIT(NAN_SUPPORTED_BAND_ID_2P4G);
+ peer_cap_tlv->supported_bands =
+ mt7925_nan_get_supported_bands(msta->vif);
peer_cap_tlv->max_chnl_switch_time = sched->max_chan_switch;
for (i = 0; i < sched->n_channels; i++) {
@@ -696,38 +778,52 @@ static int mt7925_nan_peer_cap_tlv(struct sk_buff *skb,
static void
mt7925_nan_fill_crb_committed(struct mt7925_nan_sched_update_crb_tlv *crb_tlv,
+ struct ieee80211_vif *vif,
struct ieee80211_nan_peer_sched *sched)
{
+ struct ieee80211_nan_sched_cfg *local_sched;
+ u8 local_map_id;
u32 m, slot;
- if (!sched)
+ if (!vif || !sched)
return;
+ local_sched = &vif->cfg.nan_sched;
+ local_map_id = mt7925_nan_avail_attr_ctrl(local_sched) &
+ NAN_AVAIL_CTRL_MAPID;
+
for (m = 0; m < CFG80211_NAN_MAX_PEER_MAPS &&
m < NAN_TIMELINE_MGMT_SIZE; m++) {
struct mt7925_nan_sched_timeline *tl =
&crb_tlv->comm_faw_timeline[m];
struct ieee80211_nan_peer_map *map = &sched->maps[m];
+ u32 avail_map = 0;
if (map->map_id == CFG80211_NAN_INVALID_MAP_ID)
continue;
tl->map_id = map->map_id;
+ tl->local_map_id = local_map_id;
- /*
- * Convert peer schedule slots to FW avail_map bitmap.
- * Each bit in avail_map[0] represents one time slot where
- * the peer has committed availability.
- */
for (slot = 0; slot < CFG80211_NAN_SCHED_NUM_TIME_SLOTS;
slot++) {
- struct ieee80211_nan_channel *ch = map->slots[slot];
+ struct ieee80211_nan_channel *local_ch;
+ struct ieee80211_nan_channel *peer_ch;
+
+ local_ch = local_sched->schedule[slot];
+ peer_ch = map->slots[slot];
- if (!ch || !ch->chanctx_conf)
+ if (!local_ch || !local_ch->chanctx_conf ||
+ !peer_ch || !peer_ch->chanctx_conf)
continue;
- tl->avail_map[0] |= cpu_to_le32(BIT(slot));
+ if (local_ch->chanctx_conf != peer_ch->chanctx_conf)
+ continue;
+
+ avail_map |= BIT(slot);
}
+
+ tl->avail_map[0] = cpu_to_le32(avail_map);
}
}
@@ -753,7 +849,8 @@ static int mt7925_nan_update_crb_tlv(struct sk_buff *skb,
crb_tlv->is_use_ranging = false;
crb_tlv->comm_ndc_ctrl.is_valid = false;
- mt7925_nan_fill_crb_committed(crb_tlv, sta->nan_sched);
+ mt7925_nan_fill_crb_committed(crb_tlv, msta->vif->phy->dev->nan_vif,
+ sta->nan_sched);
return 0;
}
@@ -762,10 +859,12 @@ int mt792x_nan_set_peer_schedule(struct mt792x_dev *dev,
struct ieee80211_sta *sta)
{
struct mt7925_nan_common_hdr *hdr;
+ bool idx_allocated = false;
struct mt792x_sta *msta;
struct mt792x_nan *nan;
struct mt76_dev *mdev;
struct sk_buff *skb;
+ int ret;
if (!dev || !sta)
return -EINVAL;
@@ -794,21 +893,36 @@ int mt792x_nan_set_peer_schedule(struct mt792x_dev *dev,
set_bit(idx, &nan->conn_bitmap);
msta->nan_sched.sch_idx = idx;
msta->nan_sched.idx_assigned = true;
+ idx_allocated = true;
if (mt7925_nan_peer_rec_tlv(skb, sta, msta, true) ||
mt7925_nan_peer_cap_tlv(skb, sta, msta)) {
- dev_kfree_skb(skb);
- return -ENOMEM;
+ ret = -ENOMEM;
+ goto free_skb;
}
}
if (mt7925_nan_update_crb_tlv(skb, sta, msta)) {
- dev_kfree_skb(skb);
- return -ENOMEM;
+ ret = -ENOMEM;
+ goto free_skb;
}
- return mt76_mcu_skb_send_msg(mdev, skb,
- MCU_UNI_CMD(NAN), true);
+ ret = mt76_mcu_skb_send_msg(mdev, skb, MCU_UNI_CMD(NAN), true);
+ if (ret && idx_allocated)
+ goto clear_idx;
+
+ return ret;
+
+free_skb:
+ dev_kfree_skb(skb);
+ if (!idx_allocated)
+ return ret;
+
+clear_idx:
+ clear_bit(msta->nan_sched.sch_idx, &nan->conn_bitmap);
+ msta->nan_sched.idx_assigned = false;
+
+ return ret;
}
int mt792x_nan_set_peer_rec(struct mt76_dev *mdev,
@@ -818,6 +932,7 @@ int mt792x_nan_set_peer_rec(struct mt76_dev *mdev,
struct mt792x_sta *msta;
struct mt792x_nan *nan;
struct sk_buff *skb;
+ int ret;
if (!mdev || !sta)
return -EINVAL;
@@ -844,11 +959,14 @@ int mt792x_nan_set_peer_rec(struct mt76_dev *mdev,
return -ENOMEM;
}
+ ret = mt76_mcu_skb_send_msg(mdev, skb, MCU_UNI_CMD(NAN), true);
+ if (ret)
+ return ret;
+
clear_bit(msta->nan_sched.sch_idx, &nan->conn_bitmap);
msta->nan_sched.idx_assigned = false;
- return mt76_mcu_skb_send_msg(mdev, skb,
- MCU_UNI_CMD(NAN), true);
+ return 0;
}
int mt792x_nan_map_sta_rec(struct mt76_dev *mdev,
@@ -859,16 +977,21 @@ int mt792x_nan_map_sta_rec(struct mt76_dev *mdev,
struct mt7925_nan_common_hdr *hdr;
struct ieee80211_sta *nmi_sta;
struct mt792x_sta *nmi_msta;
+ struct mt792x_vif *mvif;
struct mt792x_sta *msta;
u8 nmi_addr[ETH_ALEN];
struct sk_buff *skb;
int ndp_ctx_id = 0;
+ int ret = -ENOMEM;
+ struct mt792x_dev *dev;
struct tlv *tlv;
if (!mdev || !vif || !sta)
return -EINVAL;
+ dev = container_of(mdev, struct mt792x_dev, mt76);
msta = (struct mt792x_sta *)sta->drv_priv;
+ mvif = (struct mt792x_vif *)vif->drv_priv;
rcu_read_lock();
nmi_sta = rcu_dereference(sta->nmi);
@@ -882,21 +1005,51 @@ int mt792x_nan_map_sta_rec(struct mt76_dev *mdev,
memcpy(nmi_addr, nmi_sta->addr, ETH_ALEN);
nmi_msta = (struct mt792x_sta *)nmi_sta->drv_priv;
+ if (!nmi_msta->nan_sched.idx_assigned) {
+ if (!nmi_sta->nan_sched) {
+ rcu_read_unlock();
+ dev_err(mdev->dev,
+ "NAN: peer schedule missing for NDI sta %pM\n",
+ sta->addr);
+ return -EAGAIN;
+ }
+
+ rcu_read_unlock();
+ ret = mt792x_nan_set_peer_schedule(dev, nmi_sta);
+ if (ret)
+ return ret;
+
+ rcu_read_lock();
+ nmi_sta = rcu_dereference(sta->nmi);
+ if (!nmi_sta) {
+ rcu_read_unlock();
+ dev_err(mdev->dev,
+ "NAN: NMI sta not found for NDI sta %pM\n",
+ sta->addr);
+ return -EINVAL;
+ }
+
+ nmi_msta = (struct mt792x_sta *)nmi_sta->drv_priv;
+ }
+
ndp_ctx_id = find_first_zero_bit(&nmi_msta->nan_sched.ndp_ctx_bitmap,
NAN_MAX_NDP_CXT);
- if (ndp_ctx_id < NAN_MAX_NDP_CXT)
- set_bit(ndp_ctx_id, &nmi_msta->nan_sched.ndp_ctx_bitmap);
- else
- ndp_ctx_id = 0;
+ if (ndp_ctx_id >= NAN_MAX_NDP_CXT) {
+ rcu_read_unlock();
+ return -ENOSPC;
+ }
+
+ set_bit(ndp_ctx_id, &nmi_msta->nan_sched.ndp_ctx_bitmap);
rcu_read_unlock();
msta->nan_sched.ndp_ctx_id = ndp_ctx_id;
+ msta->nan_sched.ndp_ctx_assigned = true;
skb = mt76_mcu_msg_alloc(mdev, NULL,
sizeof(struct mt7925_nan_common_hdr) +
sizeof(struct mt7925_nan_sched_map_sta_rec_tlv));
if (!skb)
- return -ENOMEM;
+ goto clear_ndp_ctx;
hdr = (struct mt7925_nan_common_hdr *)skb_put(skb, sizeof(*hdr));
memset(hdr, 0, sizeof(*hdr));
@@ -905,16 +1058,34 @@ int mt792x_nan_map_sta_rec(struct mt76_dev *mdev,
sizeof(struct mt7925_nan_sched_map_sta_rec_tlv));
if (!tlv) {
dev_kfree_skb(skb);
- return -ENOMEM;
+ ret = -ENOMEM;
+ goto clear_ndp_ctx;
}
map_tlv = (struct mt7925_nan_sched_map_sta_rec_tlv *)tlv;
memcpy(map_tlv->nmi_addr, nmi_addr, ETH_ALEN);
map_tlv->sta_rec_idx = msta->deflink.wcid.idx;
map_tlv->ndp_ctx_id = ndp_ctx_id;
- map_tlv->role_idx = 0;
+ map_tlv->role_idx = cpu_to_le32(mvif->bss_conf.mt76.idx);
memcpy(map_tlv->ndi_addr, vif->addr, ETH_ALEN);
- return mt76_mcu_skb_send_msg(mdev, skb,
- MCU_UNI_CMD(NAN), true);
+ ret = mt76_mcu_skb_send_msg(mdev, skb,
+ MCU_UNI_CMD(NAN), true);
+ if (ret)
+ goto clear_ndp_ctx;
+
+ return 0;
+
+clear_ndp_ctx:
+ rcu_read_lock();
+ nmi_sta = rcu_dereference(sta->nmi);
+ if (nmi_sta) {
+ nmi_msta = (struct mt792x_sta *)nmi_sta->drv_priv;
+ clear_bit(msta->nan_sched.ndp_ctx_id,
+ &nmi_msta->nan_sched.ndp_ctx_bitmap);
+ }
+ rcu_read_unlock();
+ msta->nan_sched.ndp_ctx_assigned = false;
+
+ return ret ?: -ENOMEM;
}
diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/nan.h b/drivers/net/wireless/mediatek/mt76/mt7925/nan.h
index 1895d0be8ee4..d308eadb3636 100644
--- a/drivers/net/wireless/mediatek/mt76/mt7925/nan.h
+++ b/drivers/net/wireless/mediatek/mt76/mt7925/nan.h
@@ -422,6 +422,8 @@ int mt7925_nan_change_configure(struct ieee80211_vif *vif,
void mt7925_nan_mcu_event(struct mt792x_dev *dev, struct sk_buff *skb);
+int mt7925_nan_set_nmi_addr(struct mt792x_dev *dev, const u8 *addr);
+
void mt7925_nan_local_sched_changed(struct mt792x_dev *dev,
struct ieee80211_vif *vif);
diff --git a/drivers/net/wireless/mediatek/mt76/mt792x.h b/drivers/net/wireless/mediatek/mt76/mt792x.h
index 89c3f84a776a..9d5a2adc81f6 100644
--- a/drivers/net/wireless/mediatek/mt76/mt792x.h
+++ b/drivers/net/wireless/mediatek/mt76/mt792x.h
@@ -23,6 +23,7 @@
#define MT792x_CFEND_RATE_11B 0x03 /* 11B LP, 11M */
#define MT792x_FW_TAG_FEATURE 4
+#define MT792x_FW_CAP_NAN BIT(5)
#define MT792x_FW_CAP_CNM BIT(7)
#define MT792x_CHIP_CAP_CLC_EVT_EN BIT(0)
@@ -116,10 +117,12 @@ struct mt792x_link_sta {
};
struct mt792x_sta_nan_sched {
+ /* protects NAN peer schedule state */
u16 committed_dw;
u32 sch_idx;
bool idx_assigned;
unsigned long ndp_ctx_bitmap;
+ bool ndp_ctx_assigned;
u8 ndp_ctx_id; /* assigned NDP context ID (for NDI sta) */
struct {
u8 map_id;
--
2.43.0
^ permalink raw reply related
* [PATCH v2 6/9] wifi: mt76: add init_wiphy callback
From: Sean Wang @ 2026-06-25 0:18 UTC (permalink / raw)
To: Felix Fietkau, Lorenzo Bianconi
Cc: chengwei.yu, yu-ching.liu, jenhao.yang, posh.sun, linux-wireless,
linux-mediatek, Sean Wang
In-Reply-To: <20260625001834.475094-1-sean.wang@kernel.org>
From: Sean Wang <sean.wang@mediatek.com>
Add an optional callback for drivers to finalize wiphy state after mt76
has initialized the supported bands and before registration.
Co-developed-by: Stella Liu <yu-ching.liu@mediatek.com>
Signed-off-by: Stella Liu <yu-ching.liu@mediatek.com>
Co-developed-by: Jeremy Yu <chengwei.yu@mediatek.com>
Signed-off-by: Jeremy Yu <chengwei.yu@mediatek.com>
Signed-off-by: Sean Wang <sean.wang@mediatek.com>
---
drivers/net/wireless/mediatek/mt76/mac80211.c | 7 +++++++
drivers/net/wireless/mediatek/mt76/mt76.h | 3 +++
2 files changed, 10 insertions(+)
diff --git a/drivers/net/wireless/mediatek/mt76/mac80211.c b/drivers/net/wireless/mediatek/mt76/mac80211.c
index 13c4e8abe281..c4cbf7195b80 100644
--- a/drivers/net/wireless/mediatek/mt76/mac80211.c
+++ b/drivers/net/wireless/mediatek/mt76/mac80211.c
@@ -681,6 +681,7 @@ mt76_alloc_device(struct device *pdev, unsigned int size,
dev = hw->priv;
dev->hw = hw;
dev->dev = pdev;
+ dev->init_wiphy = NULL;
dev->drv = drv_ops;
dev->dma_dev = pdev;
@@ -779,6 +780,12 @@ int mt76_register_device(struct mt76_dev *dev, bool vht,
mt76_check_sband(&dev->phy, &phy->sband_5g, NL80211_BAND_5GHZ);
mt76_check_sband(&dev->phy, &phy->sband_6g, NL80211_BAND_6GHZ);
+ if (dev->init_wiphy) {
+ ret = dev->init_wiphy(dev);
+ if (ret)
+ return ret;
+ }
+
if (IS_ENABLED(CONFIG_MT76_LEDS)) {
ret = mt76_led_init(phy);
if (ret)
diff --git a/drivers/net/wireless/mediatek/mt76/mt76.h b/drivers/net/wireless/mediatek/mt76/mt76.h
index 07955555f84d..0d185675689a 100644
--- a/drivers/net/wireless/mediatek/mt76/mt76.h
+++ b/drivers/net/wireless/mediatek/mt76/mt76.h
@@ -940,6 +940,9 @@ struct mt76_dev {
const struct mt76_bus_ops *bus;
const struct mt76_driver_ops *drv;
const struct mt76_mcu_ops *mcu_ops;
+
+ /* Optional callback to finalize wiphy state before registration. */
+ int (*init_wiphy)(struct mt76_dev *dev);
struct device *dev;
struct device *dma_dev;
--
2.43.0
^ permalink raw reply related
page: next (older) | prev (newer) | latest
- recent:[subjects (threaded)|topics (new)|topics (active)]
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox