* RE: [PATCH rtw-next 2/3] wifi: rtlwifi: convert pci ID if-statement to table
From: Ping-Ke Shih @ 2026-06-15 1:37 UTC (permalink / raw)
To: William Hansen-Baird
Cc: linux-wireless@vger.kernel.org, linux-kernel@vger.kernel.org
In-Reply-To: <20260614135508.70307-3-william.hansen.baird@gmail.com>
William Hansen-Baird <william.hansen.baird@gmail.com> wrote:
[...]
> --- a/drivers/net/wireless/realtek/rtlwifi/pci.c
> +++ b/drivers/net/wireless/realtek/rtlwifi/pci.c
> @@ -31,6 +31,11 @@ static const u8 ac_to_hwq[] = {
> BK_QUEUE
> };
>
> +static const struct pci_device_id rtl8723be_aspm_quirks[] = {
> + { PCI_DEVICE_SUB(PCI_ANY_ID, PCI_ANY_ID, 0x11ad, 0x1723) },
Why not filling VID/PID explicitly? As there is only one listed in
rtl8723be_pci_ids[].
{RTL_PCI_DEVICE(PCI_VENDOR_ID_REALTEK, 0xB723, ...)},
> + { 0 }
> +};
> +
> static u8 _rtl_mac_to_hwqueue(struct ieee80211_hw *hw, struct sk_buff *skb)
> {
> struct rtl_hal *rtlhal = rtl_hal(rtl_priv(hw));
> @@ -327,14 +332,12 @@ static void rtl_pci_init_aspm(struct ieee80211_hw *hw)
>
> _rtl_pci_update_default_setting(hw);
>
> - /* RTL8723BE found on some ASUSTek laptops, such as F441U and
> - * X555UQ with subsystem ID 11ad:1723 are known to output large
> + /* RTL8723BE with certain subsytem IDs are known to output large
> * amounts of PCIe AER errors during and after boot up, causing
> * heavy lags, poor network throughput, and occasional lock-ups.
> */
> if (rtlpriv->rtlhal.hw_type == HARDWARE_TYPE_RTL8723BE &&
With above opinion, here just check pci_match_id(), no need to check RTL8723BE.
> - (rtlpci->pdev->subsystem_vendor == 0x11ad &&
> - rtlpci->pdev->subsystem_device == 0x1723)) {
> + pci_match_id(rtl8723be_aspm_quirks, rtlpci->pdev)) {
> rtl_pci_disable_aspm(hw);
> ppsc->support_aspm = false;
> }
> --
> 2.54.0
^ permalink raw reply
* RE: [PATCH rtw-next 1/3] wifi: rtlwifi: fix disabling of ASPM for RTL8723BE with AER flooding
From: Ping-Ke Shih @ 2026-06-15 1:26 UTC (permalink / raw)
To: William Hansen-Baird
Cc: linux-wireless@vger.kernel.org, linux-kernel@vger.kernel.org
In-Reply-To: <20260614135508.70307-2-william.hansen.baird@gmail.com>
William Hansen-Baird <william.hansen.baird@gmail.com> wrote:
> Commit 77a6407c6ab2 ("wifi: rtlwifi: disable ASPM for RTL8723BE with subsystem ID 11ad:1723")
> adds code which sets ppsc->support_aspm to false in
> _rtl_pci_update_default_setting() in order to disable ASPM.
> This does not, however, disable ASPM. Rather, it disables driver
> control of ASPM, and blocks calls to rtl_pci_enable_aspm()
> and rtl_pci_disable_aspm().
>
> In some cases, the pci device supplied to the probe function has
> ASPM enabled. The code would therefore not disable ASPM, as it means to,
> but rather just leave it enabled.
> This was discovered through testing on a Razer Blade 14 2017, where ASPM
> was enabled by default for the pci device.
>
> Move the code added in the previous commit to rtl_pci_init_aspm() to
> allow the adding of a call to rtl_pci_disable_aspm(hw) prior to disabling
> ppsc->pci_support. This makes sure ASPM is disabled before disabling
> driver control of ASPM to block it from being enabled later.
>
> Fixes: 77a6407c6ab2 ("wifi: rtlwifi: disable ASPM for RTL8723BE with subsystem ID 11ad:1723")
> Signed-off-by: William Hansen-Baird <william.hansen.baird@gmail.com>
> ---
> drivers/net/wireless/realtek/rtlwifi/pci.c | 23 +++++++++++++---------
> 1 file changed, 14 insertions(+), 9 deletions(-)
>
> diff --git a/drivers/net/wireless/realtek/rtlwifi/pci.c
> b/drivers/net/wireless/realtek/rtlwifi/pci.c
> index 73018a0498b4..4ef1faf649e9 100644
> --- a/drivers/net/wireless/realtek/rtlwifi/pci.c
> +++ b/drivers/net/wireless/realtek/rtlwifi/pci.c
> @@ -156,15 +156,6 @@ static void _rtl_pci_update_default_setting(struct ieee80211_hw *hw)
> PCI_EXP_LNKCTL_ASPM_L1 | PCI_EXP_LNKCTL_CCC))
> ppsc->support_aspm = false;
>
> - /* RTL8723BE found on some ASUSTek laptops, such as F441U and
> - * X555UQ with subsystem ID 11ad:1723 are known to output large
> - * amounts of PCIe AER errors during and after boot up, causing
> - * heavy lags, poor network throughput, and occasional lock-ups.
> - */
> - if (rtlpriv->rtlhal.hw_type == HARDWARE_TYPE_RTL8723BE &&
> - (rtlpci->pdev->subsystem_vendor == 0x11ad &&
> - rtlpci->pdev->subsystem_device == 0x1723))
> - ppsc->support_aspm = false;
> }
>
> static bool _rtl_pci_platform_switch_device_pci_aspm(
> @@ -330,10 +321,24 @@ static void rtl_pci_parse_configuration(struct pci_dev *pdev,
>
> static void rtl_pci_init_aspm(struct ieee80211_hw *hw)
> {
> + struct rtl_priv *rtlpriv = rtl_priv(hw);
> struct rtl_ps_ctl *ppsc = rtl_psc(rtl_priv(hw));
> + struct rtl_pci *rtlpci = rtl_pcidev(rtl_pcipriv(hw));
>
> _rtl_pci_update_default_setting(hw);
>
> + /* RTL8723BE found on some ASUSTek laptops, such as F441U and
> + * X555UQ with subsystem ID 11ad:1723 are known to output large
> + * amounts of PCIe AER errors during and after boot up, causing
> + * heavy lags, poor network throughput, and occasional lock-ups.
> + */
> + if (rtlpriv->rtlhal.hw_type == HARDWARE_TYPE_RTL8723BE &&
> + (rtlpci->pdev->subsystem_vendor == 0x11ad &&
> + rtlpci->pdev->subsystem_device == 0x1723)) {
> + rtl_pci_disable_aspm(hw);
The rtl_pci_disable_aspm() check condition of ppsc->support_aspm, so we
should consider the order seriously. I'd introduce a __rtl_pci_disable_aspm()
without checking, like:
--- a/drivers/net/wireless/realtek/rtlwifi/pci.c
+++ b/drivers/net/wireless/realtek/rtlwifi/pci.c
@@ -203,7 +203,7 @@ static void _rtl_pci_switch_clk_req(struct ieee80211_hw *hw, u16 value)
}
/*Disable RTL8192SE ASPM & Disable Pci Bridge ASPM*/
-static void rtl_pci_disable_aspm(struct ieee80211_hw *hw)
+static void __rtl_pci_disable_aspm(struct ieee80211_hw *hw)
{
struct rtl_priv *rtlpriv = rtl_priv(hw);
struct rtl_pci_priv *pcipriv = rtl_pcipriv(hw);
@@ -215,9 +215,6 @@ static void rtl_pci_disable_aspm(struct ieee80211_hw *hw)
u16 aspmlevel = 0;
u16 tmp_u1b = 0;
- if (!ppsc->support_aspm)
- return;
-
if (pcibridge_vendor == PCI_BRIDGE_VENDOR_UNKNOWN) {
rtl_dbg(rtlpriv, COMP_POWER, DBG_TRACE,
"PCI(Bridge) UNKNOWN\n");
@@ -240,6 +237,14 @@ static void rtl_pci_disable_aspm(struct ieee80211_hw *hw)
_rtl_pci_platform_switch_device_pci_aspm(hw, linkctrl_reg);
}
+static void rtl_pci_disable_aspm(struct ieee80211_hw *hw)
+{
+ if (!ppsc->support_aspm)
+ return;
+
+ __rtl_pci_disable_aspm(hw);
+}
+
Here, rtl_pci_init_aspm() can call
__rtl_pci_disable_aspm(hw);
> + ppsc->support_aspm = false;
> + }
> +
> if (ppsc->reg_rfps_level & RT_RF_PS_LEVEL_ALWAYS_ASPM) {
> /*Always enable ASPM & Clock Req. */
> rtl_pci_enable_aspm(hw);
> --
> 2.54.0
^ permalink raw reply
* RE: [PATCH] wifi: realtek: rtw8822c: replace msleep() with fsleep() for DPK delays
From: Ping-Ke Shih @ 2026-06-15 0:39 UTC (permalink / raw)
To: Chen Jung Ku
Cc: linux-wireless@vger.kernel.org, linux-kernel@vger.kernel.org,
Chen Jung Ku
In-Reply-To: <20260613170434.23645-1-ku.loong@gapp.nthu.edu.tw>
Chen Jung Ku <ku.loong@gapp.nthu.edu.tw> wrote:
> Replace msleep() with fsleep(), because msleep() may oversleep
> to as much as 20 ms when used for a 10 ms delay.
> According to the kernel documentation, fsleep() is more suitable
> and aligns better with modern kernel style.
>
> Documentation link: https://docs.kernel.org/timers/delay_sleep_functions.html
>
> Signed-off-by: Chen Jung Ku <ku.loong@gapp.nthu.edu.tw>
Acked-by: Ping-Ke Shih <pkshih@realtek.com>
^ permalink raw reply
* Re: [PATCH v2] wifi: ath6kl: fix invalid workqueue flags in ath6kl_usb_create()
From: Tejun Heo @ 2026-06-14 22:31 UTC (permalink / raw)
To: wuyankun, Tetsuo Handa, Jeff Johnson
Cc: johan, kees, linux-kernel, linux-wireless, sumanth.gavini,
syzbot+f80c62f371ba6a1e7d79, syzkaller-bugs
In-Reply-To: <20260611015545.111157-1-wuyankun@uniontech.com>
Applied to wq/for-7.2, thanks.
Holler if there are any concerns.
--
tejun
^ permalink raw reply
* [syzbot] [wireless?] WARNING in __ieee80211_start_scan (2)
From: syzbot @ 2026-06-14 16:49 UTC (permalink / raw)
To: johannes, linux-kernel, linux-wireless, netdev, syzkaller-bugs
Hello,
syzbot found the following issue on:
HEAD commit: 627366c51145 ptp: ocp: fix resource freeing order
git tree: net
console output: https://syzkaller.appspot.com/x/log.txt?x=1114f186580000
kernel config: https://syzkaller.appspot.com/x/.config?x=65472e27d1590a04
dashboard link: https://syzkaller.appspot.com/bug?extid=f961b9f94edbc266f1f8
compiler: Debian clang version 21.1.8 (++20251221033036+2078da43e25a-1~exp1~20251221153213.50), Debian LLD 21.1.8
Unfortunately, I don't have any reproducer for this issue yet.
Downloadable assets:
disk image: https://storage.googleapis.com/syzbot-assets/fdf7eb944feb/disk-627366c5.raw.xz
vmlinux: https://storage.googleapis.com/syzbot-assets/ab07a79e10f6/vmlinux-627366c5.xz
kernel image: https://storage.googleapis.com/syzbot-assets/270e46d829a1/bzImage-627366c5.xz
IMPORTANT: if you fix the issue, please add the following tag to the commit:
Reported-by: syzbot+f961b9f94edbc266f1f8@syzkaller.appspotmail.com
------------[ cut here ]------------
!ieee80211_prep_hw_scan(sdata)
WARNING: net/mac80211/scan.c:879 at __ieee80211_start_scan+0x1336/0x1d40 net/mac80211/scan.c:879, CPU#0: syz.0.5003/24116
Modules linked in:
CPU: 0 UID: 0 PID: 24116 Comm: syz.0.5003 Not tainted syzkaller #0 PREEMPT(full)
Hardware name: Google Google Compute Engine/Google Compute Engine, BIOS Google 05/09/2026
RIP: 0010:__ieee80211_start_scan+0x1336/0x1d40 net/mac80211/scan.c:879
Code: 06 90 e8 bd 74 b0 f6 48 83 fd 09 0f 84 41 07 00 00 83 fd 03 0f 84 3f 07 00 00 e8 25 6f b0 f6 e9 d5 f2 ff ff e8 1b 6f b0 f6 90 <0f> 0b 90 e9 0d fe ff ff 89 d9 80 e1 07 80 c1 03 38 c1 0f 8c 53 fa
RSP: 0018:ffffc9000674f170 EFLAGS: 00010293
RAX: ffffffff8b154795 RBX: ffff888053bf8e40 RCX: ffff888079661f00
RDX: 0000000000000000 RSI: 00000000fffffff4 RDI: 0000000000000000
RBP: ffff88804acc3024 R08: ffffffff903034f7 R09: 1ffffffff206069e
R10: dffffc0000000000 R11: fffffbfff206069f R12: ffff88804acc3060
R13: dffffc0000000000 R14: ffff88805c5a0f20 R15: ffff88805c5a2a88
FS: 00007fe61492d6c0(0000) GS:ffff8881252a0000(0000) knlGS:0000000000000000
CS: 0010 DS: 0000 ES: 0000 CR0: 0000000080050033
CR2: 00007fe613987cc0 CR3: 0000000075924000 CR4: 00000000003526f0
Call Trace:
<TASK>
rdev_scan+0x147/0x300 net/wireless/rdev-ops.h:467
nl80211_trigger_scan+0x1aa1/0x1f50 net/wireless/nl80211.c:11046
genl_family_rcv_msg_doit+0x22a/0x330 net/netlink/genetlink.c:1114
genl_family_rcv_msg net/netlink/genetlink.c:1194 [inline]
genl_rcv_msg+0x61c/0x7a0 net/netlink/genetlink.c:1209
netlink_rcv_skb+0x232/0x4b0 net/netlink/af_netlink.c:2555
genl_rcv+0x28/0x40 net/netlink/genetlink.c:1218
netlink_unicast_kernel net/netlink/af_netlink.c:1318 [inline]
netlink_unicast+0x75c/0x8e0 net/netlink/af_netlink.c:1344
netlink_sendmsg+0x813/0xb40 net/netlink/af_netlink.c:1899
sock_sendmsg_nosec net/socket.c:787 [inline]
__sock_sendmsg net/socket.c:802 [inline]
____sys_sendmsg+0x972/0x9f0 net/socket.c:2699
___sys_sendmsg+0x2a5/0x360 net/socket.c:2753
__sys_sendmsg net/socket.c:2785 [inline]
__do_sys_sendmsg net/socket.c:2790 [inline]
__se_sys_sendmsg net/socket.c:2788 [inline]
__x64_sys_sendmsg+0x1bd/0x2a0 net/socket.c:2788
do_syscall_x64 arch/x86/entry/syscall_64.c:63 [inline]
do_syscall_64+0x174/0x580 arch/x86/entry/syscall_64.c:94
entry_SYSCALL_64_after_hwframe+0x77/0x7f
RIP: 0033:0x7fe61399ce59
Code: ff c3 66 2e 0f 1f 84 00 00 00 00 00 0f 1f 44 00 00 48 89 f8 48 89 f7 48 89 d6 48 89 ca 4d 89 c2 4d 89 c8 4c 8b 4c 24 08 0f 05 <48> 3d 01 f0 ff ff 73 01 c3 48 c7 c1 e8 ff ff ff f7 d8 64 89 01 48
RSP: 002b:00007fe61492d028 EFLAGS: 00000246 ORIG_RAX: 000000000000002e
RAX: ffffffffffffffda RBX: 00007fe613c15fa0 RCX: 00007fe61399ce59
RDX: 0000000004000000 RSI: 0000200000000900 RDI: 0000000000000003
RBP: 00007fe61492d090 R08: 0000000000000000 R09: 0000000000000000
R10: 0000000000000000 R11: 0000000000000246 R12: 0000000000000001
R13: 00007fe613c16038 R14: 00007fe613c15fa0 R15: 00007fff3acb29d8
</TASK>
---
This report is generated by a bot. It may contain errors.
See https://goo.gl/tpsmEJ for more information about syzbot.
syzbot engineers can be reached at syzkaller@googlegroups.com.
syzbot will keep track of this issue. See:
https://goo.gl/tpsmEJ#status for how to communicate with syzbot.
If the report is already addressed, let syzbot know by replying with:
#syz fix: exact-commit-title
If you want to overwrite report's subsystems, reply with:
#syz set subsystems: new-subsystem
(See the list of subsystem names on the web dashboard)
If the report is a duplicate of another one, reply with:
#syz dup: exact-subject-of-another-report
If you want to undo deduplication, reply with:
#syz undup
^ permalink raw reply
* [PATCH rtw-next 3/3] wifi: rtlwifi: disable ASPM for RTL8723BE with subsystem ID 17aa:b736
From: William Hansen-Baird @ 2026-06-14 13:55 UTC (permalink / raw)
To: pkshih; +Cc: linux-wireless, linux-kernel, William Hansen-Baird
In-Reply-To: <20260614135508.70307-1-william.hansen.baird@gmail.com>
RTL8723BE outputs a large amount of PCIe AER errors during and
after boot, even before probe and when driver is never loaded.
This causes significant system slowdown.
The errors are the same as reported by
commit 77a6407c6ab2 ("wifi: rtlwifi: disable ASPM for RTL8723BE with subsystem ID 11ad:1723")
Add the subsystem ID 17aa:b736 to the rtl8723be quirk table to stop
the AER errors. AER errors can still be present prior to pci probe,
as the device by default may have ASPM enabled.
Testing on a Razer Blade 14 2017 which shipped from the
OEM equipped with an RTL8723BE card with this subsystem ID
confirms that this patch resolves the AER flood and allows the
wireless card to function normally once the driver takes over.
Signed-off-by: William Hansen-Baird <william.hansen.baird@gmail.com>
---
drivers/net/wireless/realtek/rtlwifi/pci.c | 1 +
1 file changed, 1 insertion(+)
diff --git a/drivers/net/wireless/realtek/rtlwifi/pci.c b/drivers/net/wireless/realtek/rtlwifi/pci.c
index 03b743809258..bbcd1922acb2 100644
--- a/drivers/net/wireless/realtek/rtlwifi/pci.c
+++ b/drivers/net/wireless/realtek/rtlwifi/pci.c
@@ -33,6 +33,7 @@ static const u8 ac_to_hwq[] = {
static const struct pci_device_id rtl8723be_aspm_quirks[] = {
{ PCI_DEVICE_SUB(PCI_ANY_ID, PCI_ANY_ID, 0x11ad, 0x1723) },
+ { PCI_DEVICE_SUB(PCI_ANY_ID, PCI_ANY_ID, 0x17aa, 0xb736) },
{ 0 }
};
--
2.54.0
^ permalink raw reply related
* [PATCH rtw-next 2/3] wifi: rtlwifi: convert pci ID if-statement to table
From: William Hansen-Baird @ 2026-06-14 13:55 UTC (permalink / raw)
To: pkshih; +Cc: linux-wireless, linux-kernel, William Hansen-Baird
In-Reply-To: <20260614135508.70307-1-william.hansen.baird@gmail.com>
Refactor the ASUSTek quirk logic from an if-statement to a standard
pci_device_id table. This allows future devices with the same quirk
to be added more easily and avoiding a large if-chain.
Signed-off-by: William Hansen-Baird <william.hansen.baird@gmail.com>
---
drivers/net/wireless/realtek/rtlwifi/pci.c | 11 +++++++----
1 file changed, 7 insertions(+), 4 deletions(-)
diff --git a/drivers/net/wireless/realtek/rtlwifi/pci.c b/drivers/net/wireless/realtek/rtlwifi/pci.c
index 4ef1faf649e9..03b743809258 100644
--- a/drivers/net/wireless/realtek/rtlwifi/pci.c
+++ b/drivers/net/wireless/realtek/rtlwifi/pci.c
@@ -31,6 +31,11 @@ static const u8 ac_to_hwq[] = {
BK_QUEUE
};
+static const struct pci_device_id rtl8723be_aspm_quirks[] = {
+ { PCI_DEVICE_SUB(PCI_ANY_ID, PCI_ANY_ID, 0x11ad, 0x1723) },
+ { 0 }
+};
+
static u8 _rtl_mac_to_hwqueue(struct ieee80211_hw *hw, struct sk_buff *skb)
{
struct rtl_hal *rtlhal = rtl_hal(rtl_priv(hw));
@@ -327,14 +332,12 @@ static void rtl_pci_init_aspm(struct ieee80211_hw *hw)
_rtl_pci_update_default_setting(hw);
- /* RTL8723BE found on some ASUSTek laptops, such as F441U and
- * X555UQ with subsystem ID 11ad:1723 are known to output large
+ /* RTL8723BE with certain subsytem IDs are known to output large
* amounts of PCIe AER errors during and after boot up, causing
* heavy lags, poor network throughput, and occasional lock-ups.
*/
if (rtlpriv->rtlhal.hw_type == HARDWARE_TYPE_RTL8723BE &&
- (rtlpci->pdev->subsystem_vendor == 0x11ad &&
- rtlpci->pdev->subsystem_device == 0x1723)) {
+ pci_match_id(rtl8723be_aspm_quirks, rtlpci->pdev)) {
rtl_pci_disable_aspm(hw);
ppsc->support_aspm = false;
}
--
2.54.0
^ permalink raw reply related
* [PATCH rtw-next 1/3] wifi: rtlwifi: fix disabling of ASPM for RTL8723BE with AER flooding
From: William Hansen-Baird @ 2026-06-14 13:55 UTC (permalink / raw)
To: pkshih; +Cc: linux-wireless, linux-kernel, William Hansen-Baird
In-Reply-To: <20260614135508.70307-1-william.hansen.baird@gmail.com>
Commit 77a6407c6ab2 ("wifi: rtlwifi: disable ASPM for RTL8723BE with subsystem ID 11ad:1723")
adds code which sets ppsc->support_aspm to false in
_rtl_pci_update_default_setting() in order to disable ASPM.
This does not, however, disable ASPM. Rather, it disables driver
control of ASPM, and blocks calls to rtl_pci_enable_aspm()
and rtl_pci_disable_aspm().
In some cases, the pci device supplied to the probe function has
ASPM enabled. The code would therefore not disable ASPM, as it means to,
but rather just leave it enabled.
This was discovered through testing on a Razer Blade 14 2017, where ASPM
was enabled by default for the pci device.
Move the code added in the previous commit to rtl_pci_init_aspm() to
allow the adding of a call to rtl_pci_disable_aspm(hw) prior to disabling
ppsc->pci_support. This makes sure ASPM is disabled before disabling
driver control of ASPM to block it from being enabled later.
Fixes: 77a6407c6ab2 ("wifi: rtlwifi: disable ASPM for RTL8723BE with subsystem ID 11ad:1723")
Signed-off-by: William Hansen-Baird <william.hansen.baird@gmail.com>
---
drivers/net/wireless/realtek/rtlwifi/pci.c | 23 +++++++++++++---------
1 file changed, 14 insertions(+), 9 deletions(-)
diff --git a/drivers/net/wireless/realtek/rtlwifi/pci.c b/drivers/net/wireless/realtek/rtlwifi/pci.c
index 73018a0498b4..4ef1faf649e9 100644
--- a/drivers/net/wireless/realtek/rtlwifi/pci.c
+++ b/drivers/net/wireless/realtek/rtlwifi/pci.c
@@ -156,15 +156,6 @@ static void _rtl_pci_update_default_setting(struct ieee80211_hw *hw)
PCI_EXP_LNKCTL_ASPM_L1 | PCI_EXP_LNKCTL_CCC))
ppsc->support_aspm = false;
- /* RTL8723BE found on some ASUSTek laptops, such as F441U and
- * X555UQ with subsystem ID 11ad:1723 are known to output large
- * amounts of PCIe AER errors during and after boot up, causing
- * heavy lags, poor network throughput, and occasional lock-ups.
- */
- if (rtlpriv->rtlhal.hw_type == HARDWARE_TYPE_RTL8723BE &&
- (rtlpci->pdev->subsystem_vendor == 0x11ad &&
- rtlpci->pdev->subsystem_device == 0x1723))
- ppsc->support_aspm = false;
}
static bool _rtl_pci_platform_switch_device_pci_aspm(
@@ -330,10 +321,24 @@ static void rtl_pci_parse_configuration(struct pci_dev *pdev,
static void rtl_pci_init_aspm(struct ieee80211_hw *hw)
{
+ struct rtl_priv *rtlpriv = rtl_priv(hw);
struct rtl_ps_ctl *ppsc = rtl_psc(rtl_priv(hw));
+ struct rtl_pci *rtlpci = rtl_pcidev(rtl_pcipriv(hw));
_rtl_pci_update_default_setting(hw);
+ /* RTL8723BE found on some ASUSTek laptops, such as F441U and
+ * X555UQ with subsystem ID 11ad:1723 are known to output large
+ * amounts of PCIe AER errors during and after boot up, causing
+ * heavy lags, poor network throughput, and occasional lock-ups.
+ */
+ if (rtlpriv->rtlhal.hw_type == HARDWARE_TYPE_RTL8723BE &&
+ (rtlpci->pdev->subsystem_vendor == 0x11ad &&
+ rtlpci->pdev->subsystem_device == 0x1723)) {
+ rtl_pci_disable_aspm(hw);
+ ppsc->support_aspm = false;
+ }
+
if (ppsc->reg_rfps_level & RT_RF_PS_LEVEL_ALWAYS_ASPM) {
/*Always enable ASPM & Clock Req. */
rtl_pci_enable_aspm(hw);
--
2.54.0
^ permalink raw reply related
* [PATCH rtw-next 0/3] wifi: rtlwifi: fix ASPM AER flooding on RTL8723BE devices
From: William Hansen-Baird @ 2026-06-14 13:55 UTC (permalink / raw)
To: pkshih; +Cc: linux-wireless, linux-kernel, William Hansen-Baird
This flood of AER error messages for the RTL8723BE is not unique to
subsystem ID 17aa:b736.
Commit 77a6407c6ab2 ("wifi: rtlwifi: disable ASPM for RTL8723BE with subsystem ID 11ad:1723")
targets the same issue for subsystem ID 11ad:1723.
The fix applied in that commit, however, does not correctly
disable ASPM. It merely disables driver control of ASPM.
Patch 1 fixes the previous commit such that it properly disables ASPM for
subsystem ID 11ad:1723.
Patch 2 converts the if-statement check of subsystem IDs to a
pci_device_id table matched with pci_match_id() such that future
subsystem IDs can easily be added if they have the same error.
Patch 3 adds the subsystem ID 17aa:b736 to the pci_device_id table
to disable ASPM for it. Testing on a Razer Blade 14 2017 showed this
stops the AER error message flood and wifi works as it should.
William Hansen-Baird (3):
wifi: rtlwifi: fix disabling of ASPM for RTL8723BE with AER flooding
wifi: rtlwifi: convert pci if-statement to ID table
wifi: rtlwifi: disable ASPM for RTL8723BE with subsystem ID 17aa:b736
drivers/net/wireless/realtek/rtlwifi/pci.c | 27 ++++++++++++++--------
1 file changed, 18 insertions(+), 9 deletions(-)
--
2.54.0
^ permalink raw reply
* Re: [PATCH 1/5] wifi: mt76: usb: size RX page-pool pages from queue buffer
From: Lorenzo Bianconi @ 2026-06-14 9:28 UTC (permalink / raw)
To: Sean Wang; +Cc: Felix Fietkau, linux-wireless, linux-mediatek, Sean Wang
In-Reply-To: <20260613224655.2405686-2-sean.wang@kernel.org>
[-- Attachment #1: Type: text/plain, Size: 1134 bytes --]
> From: Sean Wang <sean.wang@mediatek.com>
>
> Use the RX queue buffer size to select the page-pool allocation order.
> This lets USB devices use larger RX buffers without silently allocating
> undersized order-0 pages.
>
> Signed-off-by: Sean Wang <sean.wang@mediatek.com>
> ---
> drivers/net/wireless/mediatek/mt76/mac80211.c | 3 +++
> 1 file changed, 3 insertions(+)
>
> diff --git a/drivers/net/wireless/mediatek/mt76/mac80211.c b/drivers/net/wireless/mediatek/mt76/mac80211.c
> index 13c4e8abe281..6ff1eada6d09 100644
> --- a/drivers/net/wireless/mediatek/mt76/mac80211.c
> +++ b/drivers/net/wireless/mediatek/mt76/mac80211.c
> @@ -628,6 +628,9 @@ int mt76_create_page_pool(struct mt76_dev *dev, struct mt76_queue *q)
> if (!is_qrx && !mt76_queue_is_wed_tx_free(q))
> return 0;
>
> + if (q->buf_size > PAGE_SIZE)
> + pp_params.order = get_order(q->buf_size);
I guess you are interested just in usb devices here, right? Moreover, are you
interested just in MT_RXQ_MAIN queue?
Regards,
Lorenzo
> +
> switch (idx) {
> case MT_RXQ_MAIN:
> case MT_RXQ_BAND1:
> --
> 2.43.0
>
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 228 bytes --]
^ permalink raw reply
* Re: [PATCH 0/5] wifi: mt76: add USB RX aggregation support
From: Lorenzo Bianconi @ 2026-06-14 9:26 UTC (permalink / raw)
To: Sean Wang; +Cc: Felix Fietkau, linux-wireless, linux-mediatek
In-Reply-To: <20260613224655.2405686-1-sean.wang@kernel.org>
[-- Attachment #1: Type: text/plain, Size: 2292 bytes --]
> This series adds optional USB RX aggregation support to mt76 and enables
> it on mt7927u.
>
> RX aggregation allows multiple RX frames to be received from one USB URB,
> reducing USB completion overhead and improving RX efficiency for
> high-throughput RX traffic and monitor capture.
Hi Sean,
MT76 usb already supports Scatter-Gather (SG) for RX urbs in order to receive
multiple buffers in the same urb. Since this approach has a better memory
footprint, can we reuse this approach?
Moreover, can you please provide some performance comparison? (e.g. throughput
and memory usage).
Regards,
Lorenzo
>
> The common USB support remains opt-in, so existing USB drivers keep the
> current behavior unless they explicitly enable RX aggregation. The same
> settings work for both mt7927u and mt7925u, but this series enables the
> feature only on mt7927u for now.
>
> This series does the following:
>
> - size RX page-pool pages from the queue buffer size
> - support out-of-order RX URB completion
> - add optional USB RX aggregation parsing
> - add debugfs stats to verify aggregation behavior
> - enable USB RX aggregation on mt7927u
>
> The series is based on wireless-next commit:
>
> 21352612198c ("b43: add RF power offset for N-PHY r8 + radio 2057 r8")
>
> It also cherry-picks the following patch from patchwork as a dependency:
>
> wifi: mt76: mt76u: use a threaded NAPI for the RX path
> Link: https://lore.kernel.org/all/20260609105301.196302-1-phial@phiality.com/
>
> Sean Wang (5):
> wifi: mt76: usb: size RX page-pool pages from queue buffer
> wifi: mt76: usb: support out-of-order RX URB completion
> wifi: mt76: usb: add optional RX aggregation support
> wifi: mt76: usb: add debugfs aggregation stats
> wifi: mt76: mt7927u: enable USB RX aggregation
>
> drivers/net/wireless/mediatek/mt76/debugfs.c | 35 +++
> drivers/net/wireless/mediatek/mt76/mac80211.c | 3 +
> drivers/net/wireless/mediatek/mt76/mt76.h | 38 ++-
> .../net/wireless/mediatek/mt76/mt7925/usb.c | 18 +-
> .../net/wireless/mediatek/mt76/mt792x_usb.c | 23 +-
> drivers/net/wireless/mediatek/mt76/usb.c | 251 ++++++++++++++++--
> 6 files changed, 335 insertions(+), 33 deletions(-)
>
> --
> 2.43.0
>
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 228 bytes --]
^ permalink raw reply
* [PATCH wireless-next v2 5/5] wifi: cfg80211: support MAC address filtering in station dump for link stats
From: P Praneesh @ 2026-06-14 5:17 UTC (permalink / raw)
To: johannes; +Cc: linux-wireless, ath12k, praneesh.p
In-Reply-To: <20260614051739.3979947-1-praneesh.p@oss.qualcomm.com>
Currently, when userspace requests station information with
link statistics using NL80211_CMD_GET_STATION with the
NL80211_ATTR_STA_DUMP_LINK_STATS flag, the kernel uses the .doit callback
(nl80211_get_station) which sends a single netlink message. For MLO
stations with multiple links, the link statistics can be large and may
exceed the maximum netlink message size, causing the operation to fail
with -EMSGSIZE.
The .dumpit callback (nl80211_dump_station) already supports
fragmentation across multiple netlink messages, making it suitable
for handling large link statistics. However, it currently iterates over
all stations on the interface, which is inefficient when userspace only
wants information about a specific station.
Add support for MAC address filtering in nl80211_dump_station to allow
userspace to request fragmented link statistics for a specific station.
When NL80211_ATTR_MAC is present in a dump request, cache the MAC address
in the dump context and use rdev_get_station() to retrieve information for
only that station, instead of iterating over all stations with
rdev_dump_station().
This allows userspace tools (like iw) to use NL80211_CMD_GET_STATION with
NLM_F_DUMP flag to retrieve complete link statistics for a specific
station across multiple netlink messages, avoiding the message size
limitation.
Signed-off-by: P Praneesh <praneesh.p@oss.qualcomm.com>
---
net/wireless/nl80211.c | 41 +++++++++++++++++++++++++++++++++++++----
1 file changed, 37 insertions(+), 4 deletions(-)
diff --git a/net/wireless/nl80211.c b/net/wireless/nl80211.c
index 6910dfa7343e..f9818d4cca90 100644
--- a/net/wireless/nl80211.c
+++ b/net/wireless/nl80211.c
@@ -8474,6 +8474,8 @@ struct nl80211_dump_station_ctx {
int link_idx;
enum nl80211_dump_station_phase phase;
bool dump_link_stats;
+ bool filter_mac;
+ u8 filter_mac_addr[ETH_ALEN];
u8 mac_addr[ETH_ALEN];
struct station_info sinfo;
};
@@ -8543,10 +8545,22 @@ static int nl80211_dump_station(struct sk_buff *skb,
err = -ENOMEM;
goto out_err;
}
+ cb->args[2] = (long)ctx;
ctx->phase = NL80211_DUMP_STA_PHASE_AGGREGATED;
ctx->dump_link_stats =
!!attrbuf[NL80211_ATTR_STA_DUMP_LINK_STATS];
- cb->args[2] = (long)ctx;
+ if (attrbuf[NL80211_ATTR_MAC]) {
+ const u8 *mac = nla_data(attrbuf[NL80211_ATTR_MAC]);
+
+ if (!is_valid_ether_addr(mac)) {
+ kfree(ctx);
+ cb->args[2] = 0;
+ err = -EINVAL;
+ goto out_err;
+ }
+ ctx->filter_mac = true;
+ memcpy(ctx->filter_mac_addr, mac, ETH_ALEN);
+ }
}
if (!wdev->netdev && wdev->iftype != NL80211_IFTYPE_NAN) {
@@ -8554,7 +8568,12 @@ static int nl80211_dump_station(struct sk_buff *skb,
goto out_err;
}
- if (!rdev->ops->dump_station) {
+ if (ctx->filter_mac) {
+ if (!rdev->ops->get_station) {
+ err = -EOPNOTSUPP;
+ goto out_err;
+ }
+ } else if (!rdev->ops->dump_station) {
err = -EOPNOTSUPP;
goto out_err;
}
@@ -8575,8 +8594,22 @@ static int nl80211_dump_station(struct sk_buff *skb,
}
}
- err = rdev_dump_station(rdev, wdev, ctx->sta_idx,
- ctx->mac_addr, &ctx->sinfo);
+ if (ctx->filter_mac) {
+ if (ctx->sta_idx > 0) {
+ err = skb->len;
+ goto out_err_release;
+ }
+ err = rdev_get_station(rdev, wdev,
+ ctx->filter_mac_addr,
+ &ctx->sinfo);
+ if (!err)
+ memcpy(ctx->mac_addr,
+ ctx->filter_mac_addr, ETH_ALEN);
+ } else {
+ err = rdev_dump_station(rdev, wdev, ctx->sta_idx,
+ ctx->mac_addr,
+ &ctx->sinfo);
+ }
if (err == -ENOENT) {
err = skb->len;
goto out_err_release;
--
2.43.0
^ permalink raw reply related
* [PATCH wireless-next v2 4/5] wifi: cfg80211: Fragment per-link station stats in nl80211_dump_station()
From: P Praneesh @ 2026-06-14 5:17 UTC (permalink / raw)
To: johannes; +Cc: linux-wireless, ath12k, praneesh.p
In-Reply-To: <20260614051739.3979947-1-praneesh.p@oss.qualcomm.com>
In MLO scenarios, stations may have multiple links, each with distinct
statistics. When userspace tools like iw or hostapd request station dumps,
attempting to pack all per-link stats into a single netlink message can
easily exceed the default 4KB buffer limit, especially when more than two
links are active. This results in -EMSGSIZE errors and incomplete data
delivery.
To address this, fragment per-link station statistics across multiple
netlink messages to ensure reliable delivery of complete MLO station
information. Extend the stateful context with a two-phase dump mechanism:
phase 0 (AGGREGATED) sends combined MLO-level statistics and phase 1
(PER_LINK) sends individual per-link statistics for each active link.
The dump loop is structured to produce exactly one netlink message per
iteration, with a common header (ifindex, wdev, mac, generation) built
once and phase-specific payload added via a switch statement. This keeps
header construction in one place and makes the EMSGSIZE bail-out uniform.
Add a new request flag attribute, NL80211_ATTR_STA_DUMP_LINK_STATS
(NLA_FLAG), for NL80211_CMD_GET_STATION dump. Userspace can set this
flag to request per-link station statistics for MLO stations.
Extract this flag during the first dump invocation by passing an attrbuf
to nl80211_prepare_wdev_dump(); use __free(kfree) to avoid scattered
manual kfree() calls. Cache the boolean in the dump context to avoid
repeated parsing on subsequent invocations.
Per-link messages carry a single NL80211_ATTR_MLO_LINKS nest with the
link ID, link-specific MAC, and per-link NL80211_ATTR_STA_INFO payload.
The link-specific validity (is_valid_ether_addr) and null pointer guard
are checked in nl80211_put_link_station_payload() before any message
construction begins.
Also fix all nla_nest_start_noflag() calls in nl80211_fill_link_station()
for nested attribute types (STA_INFO, BSS_PARAM, TID_STATS, per-tid) to
use nla_nest_start() so the NLA_F_NESTED flag is set correctly.
Propagate the actual return value from nl80211_put_sta_info_common() in
the AGGREGATED phase rather than returning skb->len. Returning skb->len
signals netlink to re-invoke the dump with the same sta_idx, causing an
infinite loop when the aggregated payload is too large to fit; returning
the real error code (-EMSGSIZE or otherwise) terminates the dump cleanly.
Backward compatibility is seamlessly preserved for non-MLO stations.
Signed-off-by: P Praneesh <praneesh.p@oss.qualcomm.com>
---
include/uapi/linux/nl80211.h | 19 ++++
net/wireless/nl80211.c | 170 ++++++++++++++++++++++++++++-------
2 files changed, 157 insertions(+), 32 deletions(-)
diff --git a/include/uapi/linux/nl80211.h b/include/uapi/linux/nl80211.h
index 9998f6c0a665..1a501effd635 100644
--- a/include/uapi/linux/nl80211.h
+++ b/include/uapi/linux/nl80211.h
@@ -3165,6 +3165,23 @@ enum nl80211_commands {
* @NL80211_ATTR_NPCA_PRIMARY_FREQ: NPCA primary channel (u32)
* @NL80211_ATTR_NPCA_PUNCT_BITMAP: NPCA puncturing bitmap (u32)
*
+ * @NL80211_ATTR_STA_DUMP_LINK_STATS: Request flag for %NL80211_CMD_GET_STATION
+ * (dump mode only). When set on an MLD station, the dump produces two
+ * %NL80211_CMD_NEW_STATION messages per station per dump call:
+ *
+ * 1. An aggregated-stats message whose top-level %NL80211_ATTR_STA_INFO
+ * contains MLO-combined statistics (same content as a dump without
+ * this flag).
+ *
+ * 2. For each active link, a per-link message containing
+ * %NL80211_ATTR_MLO_LINKS with a single link entry. Each entry holds
+ * %NL80211_ATTR_MLO_LINK_ID, the link-specific %NL80211_ATTR_MAC,
+ * and %NL80211_ATTR_STA_INFO with per-link statistics (see
+ * &enum nl80211_sta_info).
+ *
+ * The aggregated message always precedes the per-link messages for the
+ * same station within a dump sequence.
+ *
* @NUM_NL80211_ATTR: total number of nl80211_attrs available
* @NL80211_ATTR_MAX: highest attribute number currently defined
* @__NL80211_ATTR_AFTER_LAST: internal use
@@ -3763,6 +3780,8 @@ enum nl80211_attrs {
NL80211_ATTR_NPCA_PRIMARY_FREQ,
NL80211_ATTR_NPCA_PUNCT_BITMAP,
+ NL80211_ATTR_STA_DUMP_LINK_STATS,
+
/* add attributes here, update the policy in nl80211.c */
__NL80211_ATTR_AFTER_LAST,
diff --git a/net/wireless/nl80211.c b/net/wireless/nl80211.c
index b4c4406d77bf..6910dfa7343e 100644
--- a/net/wireless/nl80211.c
+++ b/net/wireless/nl80211.c
@@ -1093,6 +1093,7 @@ static const struct nla_policy nl80211_policy[NUM_NL80211_ATTR] = {
[NL80211_ATTR_NPCA_PRIMARY_FREQ] = { .type = NLA_U32 },
[NL80211_ATTR_NPCA_PUNCT_BITMAP] =
NLA_POLICY_FULL_RANGE(NLA_U32, &nl80211_punct_bitmap_range),
+ [NL80211_ATTR_STA_DUMP_LINK_STATS] = { .type = NLA_FLAG },
};
/* policy for the key attributes */
@@ -7870,7 +7871,7 @@ static int nl80211_fill_link_station(struct sk_buff *msg,
goto nla_put_failure; \
} while (0)
- link_sinfoattr = nla_nest_start_noflag(msg, NL80211_ATTR_STA_INFO);
+ link_sinfoattr = nla_nest_start(msg, NL80211_ATTR_STA_INFO);
if (!link_sinfoattr)
goto nla_put_failure;
@@ -7936,8 +7937,8 @@ static int nl80211_fill_link_station(struct sk_buff *msg,
PUT_LINK_SINFO(BEACON_LOSS, beacon_loss_count, u32);
if (link_sinfo->filled & BIT_ULL(NL80211_STA_INFO_BSS_PARAM)) {
- bss_param = nla_nest_start_noflag(msg,
- NL80211_STA_INFO_BSS_PARAM);
+ bss_param = nla_nest_start(msg,
+ NL80211_STA_INFO_BSS_PARAM);
if (!bss_param)
goto nla_put_failure;
@@ -7979,8 +7980,7 @@ static int nl80211_fill_link_station(struct sk_buff *msg,
struct nlattr *tidsattr;
int tid;
- tidsattr = nla_nest_start_noflag(msg,
- NL80211_STA_INFO_TID_STATS);
+ tidsattr = nla_nest_start(msg, NL80211_STA_INFO_TID_STATS);
if (!tidsattr)
goto nla_put_failure;
@@ -7993,7 +7993,7 @@ static int nl80211_fill_link_station(struct sk_buff *msg,
if (!tidstats->filled)
continue;
- tidattr = nla_nest_start_noflag(msg, tid + 1);
+ tidattr = nla_nest_start(msg, tid + 1);
if (!tidattr)
goto nla_put_failure;
@@ -8464,21 +8464,74 @@ static void cfg80211_sta_set_mld_sinfo(struct station_info *sinfo)
sinfo->filled &= ~BIT_ULL(NL80211_STA_INFO_CHAIN_SIGNAL_AVG);
}
+enum nl80211_dump_station_phase {
+ NL80211_DUMP_STA_PHASE_AGGREGATED = 0,
+ NL80211_DUMP_STA_PHASE_PER_LINK = 1,
+};
+
struct nl80211_dump_station_ctx {
int sta_idx;
+ int link_idx;
+ enum nl80211_dump_station_phase phase;
+ bool dump_link_stats;
u8 mac_addr[ETH_ALEN];
struct station_info sinfo;
};
+static int nl80211_put_link_station_payload(struct sk_buff *msg,
+ struct cfg80211_registered_device *rdev,
+ struct station_info *sinfo,
+ int link_idx)
+{
+ struct link_station_info *link_sinfo = sinfo->links[link_idx];
+ struct nlattr *links, *link;
+
+ if (WARN_ON_ONCE(!link_sinfo))
+ return -ENOENT;
+
+ if (!is_valid_ether_addr(link_sinfo->addr))
+ return -EADDRNOTAVAIL;
+
+ links = nla_nest_start(msg, NL80211_ATTR_MLO_LINKS);
+ if (!links)
+ return -EMSGSIZE;
+
+ link = nla_nest_start(msg, link_idx + 1);
+ if (!link)
+ goto nla_put_failure;
+
+ if (nla_put_u8(msg, NL80211_ATTR_MLO_LINK_ID, link_idx) ||
+ nla_put(msg, NL80211_ATTR_MAC, ETH_ALEN, link_sinfo->addr))
+ goto nla_put_failure;
+
+ if (nl80211_fill_link_station(msg, rdev, link_sinfo))
+ goto nla_put_failure;
+
+ nla_nest_end(msg, link);
+ nla_nest_end(msg, links);
+ return 0;
+
+nla_put_failure:
+ nla_nest_cancel(msg, links);
+ return -EMSGSIZE;
+}
+
static int nl80211_dump_station(struct sk_buff *skb,
struct netlink_callback *cb)
{
struct cfg80211_registered_device *rdev;
struct wireless_dev *wdev;
struct nl80211_dump_station_ctx *ctx = (void *)cb->args[2];
+ struct nlattr **attrbuf __free(kfree) = NULL;
int err;
- err = nl80211_prepare_wdev_dump(cb, &rdev, &wdev, NULL);
+ if (!ctx) {
+ attrbuf = kzalloc_objs(*attrbuf, NUM_NL80211_ATTR);
+ if (!attrbuf)
+ return -ENOMEM;
+ }
+
+ err = nl80211_prepare_wdev_dump(cb, &rdev, &wdev, attrbuf);
if (err)
return err;
/* nl80211_prepare_wdev_dump acquired it in the successful case */
@@ -8490,6 +8543,9 @@ static int nl80211_dump_station(struct sk_buff *skb,
err = -ENOMEM;
goto out_err;
}
+ ctx->phase = NL80211_DUMP_STA_PHASE_AGGREGATED;
+ ctx->dump_link_stats =
+ !!attrbuf[NL80211_ATTR_STA_DUMP_LINK_STATS];
cb->args[2] = (long)ctx;
}
@@ -8505,34 +8561,53 @@ static int nl80211_dump_station(struct sk_buff *skb,
while (true) {
void *hdr;
+ int ret;
- memset(&ctx->sinfo, 0, sizeof(ctx->sinfo));
- for (int i = 0; i < IEEE80211_MLD_MAX_NUM_LINKS; i++) {
- ctx->sinfo.links[i] =
- kzalloc_obj(*ctx->sinfo.links[0]);
- if (!ctx->sinfo.links[i]) {
- err = -ENOMEM;
+ /* AGGREGATED phase: fetch sinfo from driver once per station */
+ if (ctx->phase == NL80211_DUMP_STA_PHASE_AGGREGATED) {
+ memset(&ctx->sinfo, 0, sizeof(ctx->sinfo));
+ for (int i = 0; i < IEEE80211_MLD_MAX_NUM_LINKS; i++) {
+ ctx->sinfo.links[i] =
+ kzalloc_obj(*ctx->sinfo.links[0]);
+ if (!ctx->sinfo.links[i]) {
+ err = -ENOMEM;
+ goto out_err_release;
+ }
+ }
+
+ err = rdev_dump_station(rdev, wdev, ctx->sta_idx,
+ ctx->mac_addr, &ctx->sinfo);
+ if (err == -ENOENT) {
+ err = skb->len;
goto out_err_release;
}
- }
+ if (err)
+ goto out_err_release;
- err = rdev_dump_station(rdev, wdev, ctx->sta_idx,
- ctx->mac_addr, &ctx->sinfo);
- if (err == -ENOENT) {
- err = skb->len;
- goto out_err_release;
+ if (ctx->sinfo.valid_links)
+ cfg80211_sta_set_mld_sinfo(&ctx->sinfo);
+ } else {
+ /* PER_LINK phase: advance to next valid link */
+ while (ctx->link_idx < IEEE80211_MLD_MAX_NUM_LINKS &&
+ !(ctx->sinfo.valid_links & BIT(ctx->link_idx)))
+ ctx->link_idx++;
+
+ if (ctx->link_idx >= IEEE80211_MLD_MAX_NUM_LINKS) {
+ cfg80211_sinfo_release_content(&ctx->sinfo);
+ ctx->sta_idx++;
+ ctx->phase = NL80211_DUMP_STA_PHASE_AGGREGATED;
+ continue;
+ }
}
- if (err)
- goto out_err_release;
-
- if (ctx->sinfo.valid_links)
- cfg80211_sta_set_mld_sinfo(&ctx->sinfo);
+ /* Build common header for both phases */
hdr = nl80211hdr_put(skb, NETLINK_CB(cb->skb).portid,
cb->nlh->nlmsg_seq, NLM_F_MULTI,
NL80211_CMD_NEW_STATION);
if (!hdr) {
err = skb->len;
+ if (ctx->phase == NL80211_DUMP_STA_PHASE_PER_LINK)
+ goto out_err;
goto out_err_release;
}
@@ -8546,18 +8621,49 @@ static int nl80211_dump_station(struct sk_buff *skb,
ctx->sinfo.generation)) {
genlmsg_cancel(skb, hdr);
err = skb->len;
+ if (ctx->phase == NL80211_DUMP_STA_PHASE_PER_LINK)
+ goto out_err;
goto out_err_release;
}
- if (nl80211_put_sta_info_common(skb, rdev, &ctx->sinfo)) {
- genlmsg_cancel(skb, hdr);
- err = skb->len;
- goto out_err_release;
- }
+ switch (ctx->phase) {
+ case NL80211_DUMP_STA_PHASE_AGGREGATED:
+ ret = nl80211_put_sta_info_common(skb, rdev, &ctx->sinfo);
+ if (ret) {
+ genlmsg_cancel(skb, hdr);
+ err = ret;
+ goto out_err_release;
+ }
+ genlmsg_end(skb, hdr);
+
+ if (ctx->dump_link_stats && ctx->sinfo.valid_links) {
+ ctx->phase = NL80211_DUMP_STA_PHASE_PER_LINK;
+ ctx->link_idx = 0;
+ } else {
+ cfg80211_sinfo_release_content(&ctx->sinfo);
+ ctx->sta_idx++;
+ }
+ break;
- genlmsg_end(skb, hdr);
- cfg80211_sinfo_release_content(&ctx->sinfo);
- ctx->sta_idx++;
+ case NL80211_DUMP_STA_PHASE_PER_LINK:
+ ret = nl80211_put_link_station_payload(skb, rdev,
+ &ctx->sinfo,
+ ctx->link_idx);
+ if (ret == -EMSGSIZE) {
+ genlmsg_cancel(skb, hdr);
+ err = skb->len;
+ goto out_err;
+ }
+ if (ret) {
+ /* skip invalid link, do not abort the dump */
+ genlmsg_cancel(skb, hdr);
+ ctx->link_idx++;
+ continue;
+ }
+ genlmsg_end(skb, hdr);
+ ctx->link_idx++;
+ break;
+ }
}
out_err_release:
--
2.43.0
^ permalink raw reply related
* [PATCH wireless-next v2 3/5] wifi: cfg80211: Refactor nl80211_dump_station() to prepare for per-link stats
From: P Praneesh @ 2026-06-14 5:17 UTC (permalink / raw)
To: johannes; +Cc: linux-wireless, ath12k, praneesh.p
In-Reply-To: <20260614051739.3979947-1-praneesh.p@oss.qualcomm.com>
Currently, nl80211_dump_station() relies on the netlink callback's generic
args array (cb->args[2]) to track the station index during dumps. It also
processes the entire sinfo structure and transmits it to userspace
immediately in a single pass.
This approach creates a bottleneck for MLO. When an MLD station has
multiple active links, the aggregated station information, combined
with the individual per-link statistics, can easily exceed the
maximum netlink message size limits. The current monolithic dump
iteration cannot pause and resume mid-station to fragment these large
per-link statistics across multiple netlink messages.
Introduce a stateful context structure (struct nl80211_dump_station_ctx)
allocated during the dump to track the iteration state. Store the context
pointer directly at cb->args[2], following the same pattern as
nl80211_dump_wiphy which stores its state pointer at cb->args[0].
Move the station index (sta_idx) tracking and the sinfo payload into this
context. The per-station netlink message is built inline in the loop:
common header attributes are assembled directly, then
nl80211_put_sta_info_common() adds the STA_INFO payload.
Furthermore, move the NL80211_CMD_GET_STATION command definition from
genl_small_ops to genl_ops to natively support the .done callback.
Implement nl80211_dump_station_done() to ensure the newly allocated state
context and its deeply allocated sinfo payload are safely freed when the
dump concludes or is aborted prematurely by userspace.
Note that the previous dump path used nl80211_send_station(), which
included NL80211_ATTR_IE and NL80211_ATTR_RESP_IE. These attributes are
not carried forward in this implementation. As documented, association
response IEs (assoc_resp_ies) are only relevant at station creation time
(e.g. via cfg80211_new_sta()) to notify userspace about association
details, and are not expected to be part of get_station()/dump_station()
callbacks. Aligning with this expectation, these IEs are intentionally
omitted here.
This refactoring maintains the existing netlink batching performance while
laying the stateful foundation required for per-link statistics
fragmentation in subsequent patches.
At out_err_release, cfg80211_sinfo_release_content() frees any
dynamically allocated sub-fields inside ctx->sinfo (including per-link
pointers in sinfo.links[]). Without the subsequent memset, those
pointers remain non-NULL in the embedded sinfo. When the dump concludes
or is aborted, nl80211_dump_station_done() calls
cfg80211_sinfo_release_content() a second time on the same ctx->sinfo,
which would free the already-released link memory. The
memset(&ctx->sinfo, 0, sizeof(ctx->sinfo)) zeroes all pointers so the
second release call hits kfree(NULL), which is a harmless no-op.
Signed-off-by: P Praneesh <praneesh.p@oss.qualcomm.com>
---
net/wireless/nl80211.c | 129 +++++++++++++++++++++++++++--------------
1 file changed, 85 insertions(+), 44 deletions(-)
diff --git a/net/wireless/nl80211.c b/net/wireless/nl80211.c
index 66f19bfc030a..b4c4406d77bf 100644
--- a/net/wireless/nl80211.c
+++ b/net/wireless/nl80211.c
@@ -8464,16 +8464,19 @@ static void cfg80211_sta_set_mld_sinfo(struct station_info *sinfo)
sinfo->filled &= ~BIT_ULL(NL80211_STA_INFO_CHAIN_SIGNAL_AVG);
}
+struct nl80211_dump_station_ctx {
+ int sta_idx;
+ u8 mac_addr[ETH_ALEN];
+ struct station_info sinfo;
+};
+
static int nl80211_dump_station(struct sk_buff *skb,
struct netlink_callback *cb)
{
- struct station_info sinfo;
struct cfg80211_registered_device *rdev;
struct wireless_dev *wdev;
- u8 mac_addr[ETH_ALEN];
- int sta_idx = cb->args[2];
- bool sinfo_alloc = false;
- int err, i;
+ struct nl80211_dump_station_ctx *ctx = (void *)cb->args[2];
+ int err;
err = nl80211_prepare_wdev_dump(cb, &rdev, &wdev, NULL);
if (err)
@@ -8481,6 +8484,15 @@ static int nl80211_dump_station(struct sk_buff *skb,
/* nl80211_prepare_wdev_dump acquired it in the successful case */
__acquire(&rdev->wiphy.mtx);
+ if (!ctx) {
+ ctx = kzalloc_obj(*ctx);
+ if (!ctx) {
+ err = -ENOMEM;
+ goto out_err;
+ }
+ cb->args[2] = (long)ctx;
+ }
+
if (!wdev->netdev && wdev->iftype != NL80211_IFTYPE_NAN) {
err = -EINVAL;
goto out_err;
@@ -8491,55 +8503,83 @@ static int nl80211_dump_station(struct sk_buff *skb,
goto out_err;
}
- while (1) {
- memset(&sinfo, 0, sizeof(sinfo));
+ while (true) {
+ void *hdr;
- for (i = 0; i < IEEE80211_MLD_MAX_NUM_LINKS; i++) {
- sinfo.links[i] =
- kzalloc_obj(*sinfo.links[0]);
- if (!sinfo.links[i]) {
+ memset(&ctx->sinfo, 0, sizeof(ctx->sinfo));
+ for (int i = 0; i < IEEE80211_MLD_MAX_NUM_LINKS; i++) {
+ ctx->sinfo.links[i] =
+ kzalloc_obj(*ctx->sinfo.links[0]);
+ if (!ctx->sinfo.links[i]) {
err = -ENOMEM;
- goto out_err;
+ goto out_err_release;
}
- sinfo_alloc = true;
}
- err = rdev_dump_station(rdev, wdev, sta_idx,
- mac_addr, &sinfo);
- if (err == -ENOENT)
- break;
+ err = rdev_dump_station(rdev, wdev, ctx->sta_idx,
+ ctx->mac_addr, &ctx->sinfo);
+ if (err == -ENOENT) {
+ err = skb->len;
+ goto out_err_release;
+ }
if (err)
- goto out_err;
+ goto out_err_release;
- if (sinfo.valid_links)
- cfg80211_sta_set_mld_sinfo(&sinfo);
+ if (ctx->sinfo.valid_links)
+ cfg80211_sta_set_mld_sinfo(&ctx->sinfo);
- /* reset the sinfo_alloc flag as nl80211_send_station()
- * always releases sinfo
- */
- sinfo_alloc = false;
+ hdr = nl80211hdr_put(skb, NETLINK_CB(cb->skb).portid,
+ cb->nlh->nlmsg_seq, NLM_F_MULTI,
+ NL80211_CMD_NEW_STATION);
+ if (!hdr) {
+ err = skb->len;
+ goto out_err_release;
+ }
- if (nl80211_send_station(skb, NL80211_CMD_NEW_STATION,
- NETLINK_CB(cb->skb).portid,
- cb->nlh->nlmsg_seq, NLM_F_MULTI,
- rdev, wdev, mac_addr,
- &sinfo) < 0)
- goto out;
+ if ((wdev->netdev &&
+ nla_put_u32(skb, NL80211_ATTR_IFINDEX,
+ wdev->netdev->ifindex)) ||
+ nla_put_u64_64bit(skb, NL80211_ATTR_WDEV,
+ wdev_id(wdev), NL80211_ATTR_PAD) ||
+ nla_put(skb, NL80211_ATTR_MAC, ETH_ALEN, ctx->mac_addr) ||
+ nla_put_u32(skb, NL80211_ATTR_GENERATION,
+ ctx->sinfo.generation)) {
+ genlmsg_cancel(skb, hdr);
+ err = skb->len;
+ goto out_err_release;
+ }
- sta_idx++;
+ if (nl80211_put_sta_info_common(skb, rdev, &ctx->sinfo)) {
+ genlmsg_cancel(skb, hdr);
+ err = skb->len;
+ goto out_err_release;
+ }
+
+ genlmsg_end(skb, hdr);
+ cfg80211_sinfo_release_content(&ctx->sinfo);
+ ctx->sta_idx++;
}
- out:
- cb->args[2] = sta_idx;
- err = skb->len;
- out_err:
- if (sinfo_alloc)
- cfg80211_sinfo_release_content(&sinfo);
+out_err_release:
+ cfg80211_sinfo_release_content(&ctx->sinfo);
+ memset(&ctx->sinfo, 0, sizeof(ctx->sinfo));
+out_err:
wiphy_unlock(&rdev->wiphy);
return err;
}
+static int nl80211_dump_station_done(struct netlink_callback *cb)
+{
+ struct nl80211_dump_station_ctx *ctx = (void *)cb->args[2];
+
+ if (ctx) {
+ cfg80211_sinfo_release_content(&ctx->sinfo);
+ kfree(ctx);
+ }
+ return 0;
+}
+
static int nl80211_get_station(struct sk_buff *skb, struct genl_info *info)
{
struct cfg80211_registered_device *rdev = info->user_ptr[0];
@@ -19504,6 +19544,14 @@ static const struct genl_ops nl80211_ops[] = {
/* can be retrieved by unprivileged users */
.internal_flags = IFLAGS(NL80211_FLAG_NEED_WIPHY),
},
+ {
+ .cmd = NL80211_CMD_GET_STATION,
+ .validate = GENL_DONT_VALIDATE_STRICT | GENL_DONT_VALIDATE_DUMP,
+ .doit = nl80211_get_station,
+ .dumpit = nl80211_dump_station,
+ .done = nl80211_dump_station_done,
+ .internal_flags = IFLAGS(NL80211_FLAG_NEED_WDEV),
+ },
};
static const struct genl_small_ops nl80211_small_ops[] = {
@@ -19603,13 +19651,6 @@ static const struct genl_small_ops nl80211_small_ops[] = {
.internal_flags = IFLAGS(NL80211_FLAG_NEED_NETDEV_UP |
NL80211_FLAG_MLO_VALID_LINK_ID),
},
- {
- .cmd = NL80211_CMD_GET_STATION,
- .validate = GENL_DONT_VALIDATE_STRICT | GENL_DONT_VALIDATE_DUMP,
- .doit = nl80211_get_station,
- .dumpit = nl80211_dump_station,
- .internal_flags = IFLAGS(NL80211_FLAG_NEED_WDEV),
- },
{
.cmd = NL80211_CMD_SET_STATION,
.validate = GENL_DONT_VALIDATE_STRICT | GENL_DONT_VALIDATE_DUMP,
--
2.43.0
^ permalink raw reply related
* [PATCH wireless-next v2 2/5] wifi: cfg80211: Add helper to pack station-level STA_INFO
From: P Praneesh @ 2026-06-14 5:17 UTC (permalink / raw)
To: johannes; +Cc: linux-wireless, ath12k, praneesh.p
In-Reply-To: <20260614051739.3979947-1-praneesh.p@oss.qualcomm.com>
Add a helper function nl80211_put_sta_info_common() to pack the
station-level (aggregated) STA information into a netlink message.
This prepares the code for future enhancements such as supporting
fragmented link statistics in nl80211_dump_station.
Signed-off-by: P Praneesh <praneesh.p@oss.qualcomm.com>
---
net/wireless/nl80211.c | 66 +++++++++++++++++++++++++-----------------
1 file changed, 39 insertions(+), 27 deletions(-)
diff --git a/net/wireless/nl80211.c b/net/wireless/nl80211.c
index 98548e69b5fc..66f19bfc030a 100644
--- a/net/wireless/nl80211.c
+++ b/net/wireless/nl80211.c
@@ -8029,32 +8029,15 @@ static int nl80211_fill_link_station(struct sk_buff *msg,
return -EMSGSIZE;
}
-static int nl80211_send_station(struct sk_buff *msg, u32 cmd, u32 portid,
- u32 seq, int flags,
- struct cfg80211_registered_device *rdev,
- struct wireless_dev *wdev,
- const u8 *mac_addr, struct station_info *sinfo)
+static int nl80211_put_sta_info_common(struct sk_buff *msg,
+ struct cfg80211_registered_device *rdev,
+ struct station_info *sinfo)
{
- void *hdr;
struct nlattr *sinfoattr, *bss_param;
- hdr = nl80211hdr_put(msg, portid, seq, flags, cmd);
- if (!hdr) {
- cfg80211_sinfo_release_content(sinfo);
- return -1;
- }
-
- if ((wdev->netdev &&
- nla_put_u32(msg, NL80211_ATTR_IFINDEX, wdev->netdev->ifindex)) ||
- nla_put_u64_64bit(msg, NL80211_ATTR_WDEV, wdev_id(wdev),
- NL80211_ATTR_PAD) ||
- nla_put(msg, NL80211_ATTR_MAC, ETH_ALEN, mac_addr) ||
- nla_put_u32(msg, NL80211_ATTR_GENERATION, sinfo->generation))
- goto nla_put_failure;
-
- sinfoattr = nla_nest_start_noflag(msg, NL80211_ATTR_STA_INFO);
+ sinfoattr = nla_nest_start(msg, NL80211_ATTR_STA_INFO);
if (!sinfoattr)
- goto nla_put_failure;
+ return -EMSGSIZE;
#define PUT_SINFO(attr, memb, type) do { \
BUILD_BUG_ON(sizeof(type) == sizeof(u64)); \
@@ -8145,8 +8128,7 @@ static int nl80211_send_station(struct sk_buff *msg, u32 cmd, u32 portid,
PUT_SINFO_U64(T_OFFSET, t_offset);
if (sinfo->filled & BIT_ULL(NL80211_STA_INFO_BSS_PARAM)) {
- bss_param = nla_nest_start_noflag(msg,
- NL80211_STA_INFO_BSS_PARAM);
+ bss_param = nla_nest_start(msg, NL80211_STA_INFO_BSS_PARAM);
if (!bss_param)
goto nla_put_failure;
@@ -8188,8 +8170,7 @@ static int nl80211_send_station(struct sk_buff *msg, u32 cmd, u32 portid,
struct nlattr *tidsattr;
int tid;
- tidsattr = nla_nest_start_noflag(msg,
- NL80211_STA_INFO_TID_STATS);
+ tidsattr = nla_nest_start(msg, NL80211_STA_INFO_TID_STATS);
if (!tidsattr)
goto nla_put_failure;
@@ -8202,7 +8183,7 @@ static int nl80211_send_station(struct sk_buff *msg, u32 cmd, u32 portid,
if (!tidstats->filled)
continue;
- tidattr = nla_nest_start_noflag(msg, tid + 1);
+ tidattr = nla_nest_start(msg, tid + 1);
if (!tidattr)
goto nla_put_failure;
@@ -8232,6 +8213,37 @@ static int nl80211_send_station(struct sk_buff *msg, u32 cmd, u32 portid,
}
nla_nest_end(msg, sinfoattr);
+ return 0;
+
+nla_put_failure:
+ nla_nest_cancel(msg, sinfoattr);
+ return -EMSGSIZE;
+}
+
+static int nl80211_send_station(struct sk_buff *msg, u32 cmd, u32 portid,
+ u32 seq, int flags,
+ struct cfg80211_registered_device *rdev,
+ struct wireless_dev *wdev,
+ const u8 *mac_addr, struct station_info *sinfo)
+{
+ void *hdr;
+
+ hdr = nl80211hdr_put(msg, portid, seq, flags, cmd);
+ if (!hdr) {
+ cfg80211_sinfo_release_content(sinfo);
+ return -1;
+ }
+
+ if ((wdev->netdev &&
+ nla_put_u32(msg, NL80211_ATTR_IFINDEX, wdev->netdev->ifindex)) ||
+ nla_put_u64_64bit(msg, NL80211_ATTR_WDEV, wdev_id(wdev),
+ NL80211_ATTR_PAD) ||
+ nla_put(msg, NL80211_ATTR_MAC, ETH_ALEN, mac_addr) ||
+ nla_put_u32(msg, NL80211_ATTR_GENERATION, sinfo->generation))
+ goto nla_put_failure;
+
+ if (nl80211_put_sta_info_common(msg, rdev, sinfo))
+ goto nla_put_failure;
if (sinfo->assoc_req_ies_len &&
nla_put(msg, NL80211_ATTR_IE, sinfo->assoc_req_ies_len,
--
2.43.0
^ permalink raw reply related
* [PATCH wireless-next v2 1/5] wifi: cfg80211: Drop unused link stats handling in nl80211_send_station()
From: P Praneesh @ 2026-06-14 5:17 UTC (permalink / raw)
To: johannes; +Cc: linux-wireless, ath12k, praneesh.p
In-Reply-To: <20260614051739.3979947-1-praneesh.p@oss.qualcomm.com>
Remove the link level statistics handling from
nl80211_send_station() and drop the unused link_stats parameter
from its signature and callers. The removed code iterated over
each MLO link and attempted to send link specific station data
through NL80211_ATTR_MLO_LINKS, but this logic was never used
because link_stats was always false.
This logic was introduced during early work on link level station
statistics with the intention of reporting information for each
link. Due to message size concerns when a station has multiple
links, the feature was disabled behind the link_stats flag and
remained unused.
The link level reporting block in nl80211_send_station() is dead
code and cannot support larger messages, so remove it. This
cleanup also prepares for proper link level statistics reporting
in nl80211_dump_station() in a later patch, where fragmentation
allows safe transmission of multi link data.
Also fix label indentation: the nla_put_failure label had an
erroneous leading space.
Signed-off-by: P Praneesh <praneesh.p@oss.qualcomm.com>
---
net/wireless/nl80211.c | 50 +++++-------------------------------------
1 file changed, 6 insertions(+), 44 deletions(-)
diff --git a/net/wireless/nl80211.c b/net/wireless/nl80211.c
index 53b4b3f76697..98548e69b5fc 100644
--- a/net/wireless/nl80211.c
+++ b/net/wireless/nl80211.c
@@ -8033,14 +8033,10 @@ static int nl80211_send_station(struct sk_buff *msg, u32 cmd, u32 portid,
u32 seq, int flags,
struct cfg80211_registered_device *rdev,
struct wireless_dev *wdev,
- const u8 *mac_addr, struct station_info *sinfo,
- bool link_stats)
+ const u8 *mac_addr, struct station_info *sinfo)
{
void *hdr;
struct nlattr *sinfoattr, *bss_param;
- struct link_station_info *link_sinfo;
- struct nlattr *links, *link;
- int link_id;
hdr = nl80211hdr_put(msg, portid, seq, flags, cmd);
if (!hdr) {
@@ -8258,45 +8254,11 @@ static int nl80211_send_station(struct sk_buff *msg, u32 cmd, u32 portid,
goto nla_put_failure;
}
- if (link_stats && sinfo->valid_links) {
- links = nla_nest_start(msg, NL80211_ATTR_MLO_LINKS);
- if (!links)
- goto nla_put_failure;
-
- for_each_valid_link(sinfo, link_id) {
- link_sinfo = sinfo->links[link_id];
-
- if (WARN_ON_ONCE(!link_sinfo))
- continue;
-
- if (!is_valid_ether_addr(link_sinfo->addr))
- continue;
-
- link = nla_nest_start(msg, link_id + 1);
- if (!link)
- goto nla_put_failure;
-
- if (nla_put_u8(msg, NL80211_ATTR_MLO_LINK_ID,
- link_id))
- goto nla_put_failure;
-
- if (nla_put(msg, NL80211_ATTR_MAC, ETH_ALEN,
- link_sinfo->addr))
- goto nla_put_failure;
-
- if (nl80211_fill_link_station(msg, rdev, link_sinfo))
- goto nla_put_failure;
-
- nla_nest_end(msg, link);
- }
- nla_nest_end(msg, links);
- }
-
cfg80211_sinfo_release_content(sinfo);
genlmsg_end(msg, hdr);
return 0;
- nla_put_failure:
+nla_put_failure:
cfg80211_sinfo_release_content(sinfo);
genlmsg_cancel(msg, hdr);
return -EMSGSIZE;
@@ -8549,7 +8511,7 @@ static int nl80211_dump_station(struct sk_buff *skb,
NETLINK_CB(cb->skb).portid,
cb->nlh->nlmsg_seq, NLM_F_MULTI,
rdev, wdev, mac_addr,
- &sinfo, false) < 0)
+ &sinfo) < 0)
goto out;
sta_idx++;
@@ -8613,7 +8575,7 @@ static int nl80211_get_station(struct sk_buff *skb, struct genl_info *info)
if (nl80211_send_station(msg, NL80211_CMD_NEW_STATION,
info->snd_portid, info->snd_seq, 0,
- rdev, wdev, mac_addr, &sinfo, false) < 0) {
+ rdev, wdev, mac_addr, &sinfo) < 0) {
nlmsg_free(msg);
return -ENOBUFS;
}
@@ -21635,7 +21597,7 @@ void cfg80211_new_sta(struct wireless_dev *wdev, const u8 *mac_addr,
return;
if (nl80211_send_station(msg, NL80211_CMD_NEW_STATION, 0, 0, 0,
- rdev, wdev, mac_addr, sinfo, false) < 0) {
+ rdev, wdev, mac_addr, sinfo) < 0) {
nlmsg_free(msg);
return;
}
@@ -21665,7 +21627,7 @@ void cfg80211_del_sta_sinfo(struct wireless_dev *wdev, const u8 *mac_addr,
}
if (nl80211_send_station(msg, NL80211_CMD_DEL_STATION, 0, 0, 0,
- rdev, wdev, mac_addr, sinfo, false) < 0) {
+ rdev, wdev, mac_addr, sinfo) < 0) {
nlmsg_free(msg);
return;
}
--
2.43.0
^ permalink raw reply related
* [PATCH wireless-next v2 0/5] wifi: cfg80211: Add fragmented per-link station stats in MLO
From: P Praneesh @ 2026-06-14 5:17 UTC (permalink / raw)
To: johannes; +Cc: linux-wireless, ath12k, praneesh.p
[-- Warning: decoded text below may be mangled, UTF-8 assumed --]
[-- Attachment #1: Type: text/plain; charset=yes, Size: 10811 bytes --]
This series introduces fragmentation support for Multi-Link Operation
(MLO) station statistics in nl80211.
The current nl80211_dump_station() implementation works well for legacy
(single-link) stations, but it does not scale for MLO peers. With
multiple links per station, dumping all per-link information in a single
netlink message can exceed size limits, resulting in -EMSGSIZE errors
and incomplete dumps.
With 802.11be (Wi-Fi 7), a single station may have multiple links, each
with its own statistics. To address this, this series introduces a
stateful dump mechanism that splits station information into multiple
messages when required.
The dump is performed in two phases:
- Phase 0: Aggregated (station-level) statistics
- Phase 1: Per-link statistics sent in separate messages
This ensures:
- Reliable delivery of complete station information for MLO peers
- Backward compatibility for legacy stations
- A scalable design for future extensions
Summary of changes:
- Drop unused per-link stats handling from nl80211_send_station()
- Introduce a helper to pack common station-level STA_INFO attributes
- Refactor nl80211_dump_station() to use a stateful dump context
- Implement per-link stats fragmentation using a two-phase approach
- Add optional MAC address filtering for per-link station dumps
Message Layout Examples:
Aggregated Stats (Phase 0)
NL80211_CMD_NEW_STATION
├─ NL80211_ATTR_IFINDEX
├─ NL80211_ATTR_MAC
├─ NL80211_ATTR_GENERATION
└─ NL80211_ATTR_STA_INFO
├─ <aggregated station attributes>
└─ ...
Note: No NL80211_ATTR_MLO_LINKS is included in this phase.
Per-link stats are sent in Phase 1.
Per-Link Stats (Phase 1) - one message per link
NL80211_CMD_NEW_STATION
├─ NL80211_ATTR_IFINDEX
├─ NL80211_ATTR_MAC (MLO MAC)
├─ NL80211_ATTR_GENERATION
└─ NL80211_ATTR_STA_INFO (empty - present for parser compat)
└─ NL80211_ATTR_MLO_LINKS
└─ [linkN]
├─ NL80211_ATTR_MLO_LINK_ID = N
├─ NL80211_ATTR_MAC = <linkN MAC>
└─ NL80211_ATTR_STA_INFO
├─ NL80211_STA_INFO_RX_BYTES
├─ NL80211_STA_INFO_TX_BYTES
└─ ... more linkN stats ..
Testing:
- Verified using "iw station dump" on a 3-link MLO station
- iw requires some updates to parse fragmented per-link stats
(e.g., NL80211_ATTR_MLO_LINKS handling). Those changes will be
submitted separately.
Sample station dump output:
Single link EHT STA:
~~~~~~~~~~~~~~~~~~~
Station ff:ff:ff:0f:f8:64 (on wlan0)
authorized: yes
authenticated: yes
associated: yes
preamble: long
WMM/WME: yes
MFP: yes
TDLS peer: no
inactive time: 182068 ms
rx bytes: 1567
rx packets: 15
tx bytes: 1083
tx packets: 6
tx retries: 0
tx failed: 0
rx drop misc: 0
signal: -30 dBm
signal avg: -27 dBm
tx bitrate: 6.0 MBit/s
tx duration: 3136 us
rx bitrate: 1441.3 MBit/s 80MHz EHT-MCS 7 EHT-NSS 4 EHT-GI 0
rx duration: 0 us
last ack signal:-31 dBm
avg ack signal: -31 dBm
DTIM period: 2
beacon interval:100
connected time: 185 seconds
associated at [boottime]: 157.326s
associated at: 157326 ms
current time: 342408 ms
Link 0:
address: ff:ff:ff:0f:f8:64
inactive time: 182068 ms
rx bytes: 1567
rx packets: 15
tx bytes: 1083
tx packets: 6
tx retries: 0
tx failed: 0
rx drop misc: 0
signal: -30 dBm
signal avg: -29 dBm
tx bitrate: 6.0 MBit/s
tx duration: 1568 us
rx duration: 0 us
last ack signal:-31 dBm
avg ack signal: -31 dBm
DTIM period: 2
beacon interval:100
3 link MLO EHT STA:
~~~~~~~~~~~~~~~~~~
Station ff:ff:ff:ff:5f:23 (on wlan0)
authorized: yes
authenticated: yes
associated: yes
preamble: long
WMM/WME: yes
MFP: yes
TDLS peer: no
inactive time: 173476 ms
rx bytes: 1036
rx packets: 9
tx bytes: 1694
tx packets: 6
tx retries: 1
tx failed: 1
rx drop misc: 0
signal: -20 dBm
signal avg: -22 dBm
tx bitrate: 6.0 MBit/s
tx duration: 2476 us
rx duration: 0 us
last ack signal:-22 dBm
avg ack signal: -22 dBm
DTIM period: 2
beacon interval:100
connected time: 174 seconds
associated at [boottime]: 168.236s
associated at: 168236 ms
current time: 342412 ms
Link 0:
address: ff:ff:ff:ff:5f:23
inactive time: 173476 ms
rx bytes: 138
rx packets: 3
tx bytes: 0
tx packets: 0
tx retries: 0
tx failed: 0
rx drop misc: 0
signal: -27 dBm
signal avg: -28 dBm
tx duration: 0 us
rx duration: 0 us
DTIM period: 2
beacon interval:100
Link 1:
address: ff:ff:ff:ff:5f:24
inactive time: 173976 ms
rx bytes: 898
rx packets: 6
tx bytes: 1694
tx packets: 6
tx retries: 0
tx failed: 0
rx drop misc: 0
signal: -20 dBm
signal avg: -22 dBm
tx bitrate: 6.0 MBit/s
tx duration: 2476 us
rx bitrate: 6.0 MBit/s
rx duration: 0 us
last ack signal:-22 dBm
avg ack signal: -22 dBm
DTIM period: 2
beacon interval:100
Link 2:
address: ff:ff:ff:ff:5f:25
inactive time: 174188 ms
rx bytes: 0
rx packets: 0
tx bytes: 0
tx packets: 0
tx retries: 1
tx failed: 1
rx drop misc: 0
signal: -30 dBm
signal avg: -31 dBm
tx bitrate: 12.0 MBit/s
tx duration: 1920 us
rx duration: 0 us
DTIM period: 2
beacon interval:100
With HE AP and STA:
~~~~~~~~~~~~~~~~~~
Station ff:ff:ff:f1:f8:f4 (on wlan0)
authorized: yes
authenticated: yes
associated: yes
preamble: long
WMM/WME: yes
MFP: yes
TDLS peer: no
inactive time: 10176 ms
rx bytes: 968
rx packets: 10
tx bytes: 856
tx packets: 6
tx retries: 0
tx failed: 0
rx drop misc: 0
signal: -24 dBm
signal avg: -27 dBm
tx bitrate: 6.0 MBit/s
tx duration: 1300 us
rx bitrate: 1297.1 MBit/s 80MHz HE-MCS 6 HE-NSS 4
rx duration: 0 us
last ack signal:-30 dBm
avg ack signal: -30 dBm
DTIM period: 2
beacon interval:100
short slot time:yes
connected time: 11 seconds
associated at [boottime]: 69.552s
associated at: 69530 ms
current time: 80780 ms
---
v2:
- In patch 1/5, Narrowed the scope: only drops the link stats from
nl80211_send_station() (formerly also deleted nl80211_fill_link_station();
that function is now moved to patch 4/5 and reused there). Also fix
the nla_put_failure label indentation (spurious leading space).
- In patch 2/5, Fix all nla_nest_start_noflag() calls in
nl80211_send_station() for nested attribute types (STA_INFO,
BSS_PARAM, TID_STATS, per-tid) to use nla_nest_start() so that
NLA_F_NESTED is set correctly.
- In patch 3/5, Drop nl80211_dump_station_cb wrapper struct and
nl80211_send_accumulated_station() helper; store the context pointer
directly at cb->args[2] following the nl80211_dump_wiphy pattern.
Build the common netlink header inline in the dump loop instead of
delegating to a helper. Add documentation explaining why
memset(&ctx->sinfo) is required after cfg80211_sinfo_release_content()
to prevent a double-free in nl80211_dump_station_done().
- In patch 4/5, Restructure the dump loop around a switch statement so
one netlink message is produced per iteration with header construction
in a single place. Replace nl80211_send_link_station() helper with a
leaner nl80211_put_link_station_payload() that only fills the
NL80211_ATTR_MLO_LINKS nest, leaving header assembly to the caller.
Fix all nla_nest_start_noflag() calls in nl80211_fill_link_station()
to use nla_nest_start(). Fix the AGGREGATED phase to propagate the
real error from nl80211_put_sta_info_common() instead of returning
skb->len, which would cause an infinite re-invocation loop on
-EMSGSIZE. Use __free(kfree) for attrbuf to avoid scattered manual
kfree() calls.
- In patch 5/5, Rename local mac_addr pointer to mac to avoid shadowing
the outer ctx->mac_addr field. Set cb->args[2] = 0 on the MAC
validation error path to prevent nl80211_dump_station_done() from
attempting to free a partially initialised context.
---
P Praneesh (5):
wifi: cfg80211: Drop unused link stats handling in
nl80211_send_station()
wifi: cfg80211: Add helper to pack station-level STA_INFO
wifi: cfg80211: Refactor nl80211_dump_station() to prepare for
per-link stats
wifi: cfg80211: Fragment per-link station stats in
nl80211_dump_station()
wifi: cfg80211: support MAC address filtering in station dump for link
stats
include/uapi/linux/nl80211.h | 19 ++
net/wireless/nl80211.c | 402 ++++++++++++++++++++++++-----------
2 files changed, 297 insertions(+), 124 deletions(-)
base-commit: 972c4dd19cb92e03d75b66c426cfade07582a1ba
--
2.43.0
^ permalink raw reply
* [PATCH 2/2] wifi: mt76: mt7927: use real monitor vifs for dual-band monitors
From: Sean Wang @ 2026-06-13 22:51 UTC (permalink / raw)
To: Felix Fietkau, Lorenzo Bianconi; +Cc: linux-wireless, linux-mediatek, Sean Wang
In-Reply-To: <20260613225144.2414283-1-sean.wang@kernel.org>
From: Sean Wang <sean.wang@mediatek.com>
MT7927 needs monitor interfaces to be passed to the driver as real vifs
so each monitor interface can be configured with its own band context.
This is required to support concurrent 2 GHz and 5 GHz monitor operation
on the same hw.
Keep the existing virtual monitor behavior for older chips.
Signed-off-by: Sean Wang <sean.wang@mediatek.com>
---
drivers/net/wireless/mediatek/mt76/mt792x_core.c | 5 ++++-
1 file changed, 4 insertions(+), 1 deletion(-)
diff --git a/drivers/net/wireless/mediatek/mt76/mt792x_core.c b/drivers/net/wireless/mediatek/mt76/mt792x_core.c
index b50825eccdaf..16afc90b7422 100644
--- a/drivers/net/wireless/mediatek/mt76/mt792x_core.c
+++ b/drivers/net/wireless/mediatek/mt76/mt792x_core.c
@@ -724,7 +724,10 @@ int mt792x_init_wiphy(struct ieee80211_hw *hw)
ieee80211_hw_set(hw, HAS_RATE_CONTROL);
ieee80211_hw_set(hw, SUPPORTS_TX_ENCAP_OFFLOAD);
ieee80211_hw_set(hw, SUPPORTS_RX_DECAP_OFFLOAD);
- ieee80211_hw_set(hw, WANT_MONITOR_VIF);
+ if (is_mt7927(&dev->mt76))
+ ieee80211_hw_set(hw, NO_VIRTUAL_MONITOR);
+ else
+ ieee80211_hw_set(hw, WANT_MONITOR_VIF);
ieee80211_hw_set(hw, SUPPORTS_PS);
ieee80211_hw_set(hw, SUPPORTS_DYNAMIC_PS);
ieee80211_hw_set(hw, SUPPORTS_VHT_EXT_NSS_BW);
--
2.43.0
^ permalink raw reply related
* [PATCH 1/2] wifi: mt76: mt7927: set band index for sniffer mode
From: Sean Wang @ 2026-06-13 22:51 UTC (permalink / raw)
To: Felix Fietkau, Lorenzo Bianconi; +Cc: linux-wireless, linux-mediatek, Sean Wang
From: Sean Wang <sean.wang@mediatek.com>
Use the active channel context to select the SNIFFER command band index on
MT7927, and fall back to the PHY chandef when no channel context is
available.
Also pass the same band index to the sniffer channel configuration. This
keeps monitor setup on the correct band, especially when multiple PHY band
contexts are present.
Fixes: 35a5dcc71735 ("wifi: mt76: mt7925: add MT7927 PCIe support")
Signed-off-by: Sean Wang <sean.wang@mediatek.com>
---
drivers/net/wireless/mediatek/mt76/mt7925/mcu.c | 14 ++++++++++++++
1 file changed, 14 insertions(+)
diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/mcu.c b/drivers/net/wireless/mediatek/mt76/mt7925/mcu.c
index e94fa544ff20..17bc7204f02a 100644
--- a/drivers/net/wireless/mediatek/mt76/mt7925/mcu.c
+++ b/drivers/net/wireless/mediatek/mt76/mt7925/mcu.c
@@ -2174,6 +2174,8 @@ int mt7925_get_txpwr_info(struct mt792x_dev *dev, u8 band_idx, struct mt7925_txp
int mt7925_mcu_set_sniffer(struct mt792x_dev *dev, struct ieee80211_vif *vif,
bool enable)
{
+ struct mt792x_vif *mvif = (struct mt792x_vif *)vif->drv_priv;
+ struct ieee80211_chanctx_conf *ctx = mvif->bss_conf.mt76.ctx;
struct {
struct {
u8 band_idx;
@@ -2196,6 +2198,15 @@ int mt7925_mcu_set_sniffer(struct mt792x_dev *dev, struct ieee80211_vif *vif,
},
};
+ if (is_mt7927(&dev->mt76)) {
+ struct ieee80211_channel *chan;
+
+ chan = ctx ? ctx->def.chan : mvif->phy->mt76->chandef.chan;
+
+ if (chan)
+ req.hdr.band_idx = mt7927_band_idx(chan->band);
+ }
+
return mt76_mcu_send_msg(&dev->mt76, MCU_UNI_CMD(SNIFFER), &req, sizeof(req),
true);
}
@@ -2255,6 +2266,9 @@ int mt7925_mcu_config_sniffer(struct mt792x_vif *vif,
},
};
+ if (is_mt7927(mphy->dev))
+ req.hdr.band_idx = mt7927_band_idx(chandef->chan->band);
+
if (chandef->chan->band < ARRAY_SIZE(ch_band))
req.tlv.ch_band = ch_band[chandef->chan->band];
if (chandef->width < ARRAY_SIZE(ch_width))
--
2.43.0
^ permalink raw reply related
* [PATCH 5/5] wifi: mt76: mt7927u: enable USB RX aggregation
From: Sean Wang @ 2026-06-13 22:46 UTC (permalink / raw)
To: Felix Fietkau, Lorenzo Bianconi; +Cc: linux-wireless, linux-mediatek, Sean Wang
In-Reply-To: <20260613224655.2405686-1-sean.wang@kernel.org>
From: Sean Wang <sean.wang@mediatek.com>
Enable USB RX aggregation on MT7927u with vendor driver parameters for
alignment, padding and buffer size. According to the vendor driver, the
hardware should run RX aggregation with USB SG disabled.
Signed-off-by: Sean Wang <sean.wang@mediatek.com>
---
drivers/net/wireless/mediatek/mt76/mt7925/usb.c | 6 +++++-
drivers/net/wireless/mediatek/mt76/usb.c | 11 +++++++++++
2 files changed, 16 insertions(+), 1 deletion(-)
diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/usb.c b/drivers/net/wireless/mediatek/mt76/mt7925/usb.c
index a0bfe6f09ae4..42d13bc6ebbc 100644
--- a/drivers/net/wireless/mediatek/mt76/mt7925/usb.c
+++ b/drivers/net/wireless/mediatek/mt76/mt7925/usb.c
@@ -5,7 +5,6 @@
#include <linux/module.h>
#include <linux/sizes.h>
#include <linux/usb.h>
-
#include "mt7925.h"
#include "mcu.h"
#include "mac.h"
@@ -235,6 +234,11 @@ static int mt7925u_probe(struct usb_interface *usb_intf,
mdev->rev = (0x7927 << 16) | (mdev->rev & 0xff);
}
+ if (is_mt7927(mdev))
+ mt76u_enable_rx_aggr(mdev, MT7927_USB_RX_AGGR_ALIGN,
+ MT7927_USB_RX_AGGR_PADDING,
+ MT7927_USB_RX_AGGR_BUF_SIZE);
+
if (mt76_get_field(dev, MT_CONN_ON_MISC, MT_TOP_MISC2_FW_N9_RDY)) {
ret = mt792xu_wfsys_reset(dev);
if (ret)
diff --git a/drivers/net/wireless/mediatek/mt76/usb.c b/drivers/net/wireless/mediatek/mt76/usb.c
index 10ad2b024985..f0df510904c5 100644
--- a/drivers/net/wireless/mediatek/mt76/usb.c
+++ b/drivers/net/wireless/mediatek/mt76/usb.c
@@ -1300,6 +1300,17 @@ static const struct mt76_queue_ops usb_queue_ops = {
.kick = mt76u_tx_kick,
};
+void mt76u_enable_rx_aggr(struct mt76_dev *dev, int align, int padding,
+ int buf_size)
+{
+ dev->usb.sg_en = false;
+ dev->usb.rx_aggr.enable = true;
+ dev->usb.rx_aggr.align = align;
+ dev->usb.rx_aggr.padding = padding;
+ dev->usb.rx_aggr.buf_size = buf_size;
+}
+EXPORT_SYMBOL_GPL(mt76u_enable_rx_aggr);
+
int __mt76u_init(struct mt76_dev *dev, struct usb_interface *intf,
struct mt76_bus_ops *ops)
{
--
2.43.0
^ permalink raw reply related
* [PATCH 4/5] wifi: mt76: usb: add debugfs aggregation stats
From: Sean Wang @ 2026-06-13 22:46 UTC (permalink / raw)
To: Felix Fietkau, Lorenzo Bianconi; +Cc: linux-wireless, linux-mediatek, Sean Wang
In-Reply-To: <20260613224655.2405686-1-sean.wang@kernel.org>
From: Sean Wang <sean.wang@mediatek.com>
Add USB debugfs counters for RX/TX URBs, packets, bytes and recent RX
aggregation frame counts.
These stats make it easier to verify whether USB RX aggregation are working
as expected, and to debug throughput issues without adding
temporary driver logs.
Signed-off-by: Sean Wang <sean.wang@mediatek.com>
---
drivers/net/wireless/mediatek/mt76/debugfs.c | 35 ++++++++++++++++++
drivers/net/wireless/mediatek/mt76/mt76.h | 12 ++++++
drivers/net/wireless/mediatek/mt76/usb.c | 39 ++++++++++++++++++++
3 files changed, 86 insertions(+)
diff --git a/drivers/net/wireless/mediatek/mt76/debugfs.c b/drivers/net/wireless/mediatek/mt76/debugfs.c
index a5ac6ca86735..b3f1bc3cd69b 100644
--- a/drivers/net/wireless/mediatek/mt76/debugfs.c
+++ b/drivers/net/wireless/mediatek/mt76/debugfs.c
@@ -88,6 +88,38 @@ static int mt76_rx_queues_read(struct seq_file *s, void *data)
return 0;
}
+static int mt76_usb_stats_read(struct seq_file *s, void *data)
+{
+ struct mt76_dev *dev = dev_get_drvdata(s->private);
+ struct mt76_usb *usb = &dev->usb;
+ u64 seq;
+ int i, n;
+
+ seq_printf(s, "rx_aggr\t%d\n", usb->rx_aggr.enable);
+ seq_printf(s, "sg_en\t%d\n", usb->sg_en);
+ seq_printf(s, "tx_urbs\t%lld\n", atomic64_read(&usb->stats.tx_urbs));
+ seq_printf(s, "tx_packets\t%lld\n",
+ atomic64_read(&usb->stats.tx_packets));
+ seq_printf(s, "tx_bytes\t%lld\n", atomic64_read(&usb->stats.tx_bytes));
+ seq_printf(s, "rx_urbs\t%lld\n", atomic64_read(&usb->stats.rx_urbs));
+ seq_printf(s, "rx_packets\t%lld\n",
+ atomic64_read(&usb->stats.rx_packets));
+ seq_printf(s, "rx_bytes\t%lld\n", atomic64_read(&usb->stats.rx_bytes));
+
+ seq = atomic64_read(&usb->stats.rx_aggr_seq);
+ seq_puts(s, "rx_aggr_nframes");
+ n = min_t(u64, seq, MT_USB_AGGR_STATS_LEN);
+ for (i = 0; i < n; i++) {
+ u64 idx = seq - n + i;
+ u64 slot = idx % MT_USB_AGGR_STATS_LEN;
+
+ seq_printf(s, " %u", READ_ONCE(usb->stats.rx_aggr_nframes[slot]));
+ }
+ seq_puts(s, "\n");
+
+ return 0;
+}
+
void mt76_seq_puts_array(struct seq_file *file, const char *str,
s8 *val, int len)
{
@@ -120,6 +152,9 @@ mt76_register_debugfs_fops(struct mt76_phy *phy,
debugfs_create_blob("otp", 0400, dir, &dev->otp);
debugfs_create_devm_seqfile(dev->dev, "rx-queues", dir,
mt76_rx_queues_read);
+ if (mt76_is_usb(dev))
+ debugfs_create_devm_seqfile(dev->dev, "usb-stats", dir,
+ mt76_usb_stats_read);
return dir;
}
diff --git a/drivers/net/wireless/mediatek/mt76/mt76.h b/drivers/net/wireless/mediatek/mt76/mt76.h
index 125c97dc1f28..c11a463ae092 100644
--- a/drivers/net/wireless/mediatek/mt76/mt76.h
+++ b/drivers/net/wireless/mediatek/mt76/mt76.h
@@ -664,6 +664,7 @@ struct mt76u_rx_entry {
#define MT_RX_SG_MAX_SIZE 4
#define MT_NUM_TX_ENTRIES 256
#define MT_NUM_RX_ENTRIES 128
+#define MT_USB_AGGR_STATS_LEN 128
#define MCU_RESP_URB_SIZE 1024
struct mt76_usb {
struct mutex usb_ctrl_mtx;
@@ -687,6 +688,17 @@ struct mt76_usb {
int buf_size;
} rx_aggr;
+ struct {
+ atomic64_t tx_urbs;
+ atomic64_t tx_packets;
+ atomic64_t tx_bytes;
+ atomic64_t rx_urbs;
+ atomic64_t rx_packets;
+ atomic64_t rx_bytes;
+ atomic64_t rx_aggr_seq;
+ u8 rx_aggr_nframes[MT_USB_AGGR_STATS_LEN];
+ } stats;
+
struct mt76u_mcu {
u8 *data;
/* multiple reads */
diff --git a/drivers/net/wireless/mediatek/mt76/usb.c b/drivers/net/wireless/mediatek/mt76/usb.c
index cbdd663fbb25..10ad2b024985 100644
--- a/drivers/net/wireless/mediatek/mt76/usb.c
+++ b/drivers/net/wireless/mediatek/mt76/usb.c
@@ -15,6 +15,36 @@ static bool disable_usb_sg;
module_param_named(disable_usb_sg, disable_usb_sg, bool, 0644);
MODULE_PARM_DESC(disable_usb_sg, "Disable usb scatter-gather support");
+static void mt76u_tx_stats_add(struct mt76_dev *dev, struct urb *urb,
+ unsigned int packets)
+{
+ atomic64_inc(&dev->usb.stats.tx_urbs);
+ atomic64_add(packets, &dev->usb.stats.tx_packets);
+ atomic64_add(urb->transfer_buffer_length, &dev->usb.stats.tx_bytes);
+}
+
+static void mt76u_rx_urb_stats_add(struct mt76_dev *dev, struct urb *urb)
+{
+ atomic64_inc(&dev->usb.stats.rx_urbs);
+ atomic64_add(urb->actual_length, &dev->usb.stats.rx_bytes);
+}
+
+static void mt76u_rx_packet_stats_add(struct mt76_dev *dev,
+ unsigned int packets)
+{
+ atomic64_add(packets, &dev->usb.stats.rx_packets);
+}
+
+static void mt76u_rx_aggr_stats_add(struct mt76_dev *dev, unsigned int packets)
+{
+ u64 idx, slot;
+
+ idx = atomic64_inc_return(&dev->usb.stats.rx_aggr_seq) - 1;
+ slot = idx % MT_USB_AGGR_STATS_LEN;
+ WRITE_ONCE(dev->usb.stats.rx_aggr_nframes[slot],
+ min_t(unsigned int, packets, U8_MAX));
+}
+
int __mt76u_vendor_request(struct mt76_dev *dev, u8 req, u8 req_type,
u16 val, u16 offset, void *buf, size_t len)
{
@@ -634,6 +664,10 @@ static int mt76u_process_rx_agg_entry(struct mt76_dev *dev, struct urb *urb)
mt76_put_page_pool_buf(urb->transfer_buffer, false);
urb->transfer_buffer = NULL;
+ if (nframes)
+ mt76u_rx_packet_stats_add(dev, nframes);
+ mt76u_rx_aggr_stats_add(dev, nframes);
+
return max(nframes, 1);
}
@@ -681,6 +715,7 @@ mt76u_process_rx_entry(struct mt76_dev *dev, struct urb *urb,
skb_mark_for_recycle(skb);
dev->drv->rx_skb(dev, MT_RXQ_MAIN, skb, NULL);
+ mt76u_rx_packet_stats_add(dev, 1);
return nsgs;
}
@@ -714,6 +749,9 @@ static void mt76u_complete_rx(struct urb *urb)
break;
}
+ if (!urb->status)
+ mt76u_rx_urb_stats_add(dev, urb);
+
spin_lock_irqsave(&q->lock, flags);
idx = e - q->entry;
pending = q->ndesc - q->queued;
@@ -1083,6 +1121,7 @@ static void mt76u_tx_kick(struct mt76_dev *dev, struct mt76_queue *q)
err);
break;
}
+ mt76u_tx_stats_add(dev, urb, 1);
q->first = (q->first + 1) % q->ndesc;
}
}
--
2.43.0
^ permalink raw reply related
* [PATCH 3/5] wifi: mt76: usb: add optional RX aggregation support
From: Sean Wang @ 2026-06-13 22:46 UTC (permalink / raw)
To: Felix Fietkau, Lorenzo Bianconi; +Cc: linux-wireless, linux-mediatek, Sean Wang
In-Reply-To: <20260613224655.2405686-1-sean.wang@kernel.org>
From: Sean Wang <sean.wang@mediatek.com>
Add common USB RX aggregation support and let drivers opt in by programming
the UDMA RX aggregation limit and timeout.
RX aggregation allows the device to pack multiple RX packets into one USB
transfer, reducing URB completion rate, USB interrupt/IO overhead, and host
RX scheduling pressure. This is especially useful at high throughput, where
per-packet USB RX handling can become a CPU bottleneck.
Keep it disabled by default so existing USB drivers keep the current RX
behavior unless they explicitly enable aggregation.
Signed-off-by: Sean Wang <sean.wang@mediatek.com>
---
drivers/net/wireless/mediatek/mt76/mt76.h | 21 ++-
.../net/wireless/mediatek/mt76/mt7925/usb.c | 12 ++
.../net/wireless/mediatek/mt76/mt792x_usb.c | 23 +++-
drivers/net/wireless/mediatek/mt76/usb.c | 124 +++++++++++++++++-
4 files changed, 169 insertions(+), 11 deletions(-)
diff --git a/drivers/net/wireless/mediatek/mt76/mt76.h b/drivers/net/wireless/mediatek/mt76/mt76.h
index 81740aa7df71..125c97dc1f28 100644
--- a/drivers/net/wireless/mediatek/mt76/mt76.h
+++ b/drivers/net/wireless/mediatek/mt76/mt76.h
@@ -680,6 +680,13 @@ struct mt76_usb {
void (*ctrl_timeout)(struct mt76_dev *dev, int err);
bool sg_en;
+ struct {
+ bool enable;
+ int align;
+ int padding;
+ int buf_size;
+ } rx_aggr;
+
struct mt76u_mcu {
u8 *data;
/* multiple reads */
@@ -1857,6 +1864,17 @@ mt76u_bulk_msg(struct mt76_dev *dev, void *data, int len, int *actual_len,
return usb_bulk_msg(udev, pipe, data, len, actual_len, timeout);
}
+static inline int
+mt76u_rx_aggr_buf_size(int max_mpdu, int aggr_limit, int aggr_pkt_limit,
+ int padding)
+{
+ int aggr_size;
+
+ aggr_size = min(aggr_limit, aggr_pkt_limit * (max_mpdu + padding));
+
+ return PAGE_ALIGN(max_mpdu + aggr_size);
+}
+
void mt76_ethtool_page_pool_stats(struct mt76_dev *dev, u64 *data, int *index);
void mt76_ethtool_worker(struct mt76_ethtool_worker_info *wi,
struct mt76_sta_stats *stats, bool eht);
@@ -1882,7 +1900,8 @@ void mt76u_stop_tx(struct mt76_dev *dev);
void mt76u_stop_rx(struct mt76_dev *dev);
int mt76u_resume_rx(struct mt76_dev *dev);
void mt76u_queues_deinit(struct mt76_dev *dev);
-
+void mt76u_enable_rx_aggr(struct mt76_dev *dev, int align, int padding,
+ int buf_size);
int mt76s_init(struct mt76_dev *dev, struct sdio_func *func,
const struct mt76_bus_ops *bus_ops);
int mt76s_alloc_rx_queue(struct mt76_dev *dev, enum mt76_rxq_id qid);
diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/usb.c b/drivers/net/wireless/mediatek/mt76/mt7925/usb.c
index 49ad4fe9eb1b..a0bfe6f09ae4 100644
--- a/drivers/net/wireless/mediatek/mt76/mt7925/usb.c
+++ b/drivers/net/wireless/mediatek/mt76/mt7925/usb.c
@@ -3,12 +3,24 @@
#include <linux/kernel.h>
#include <linux/module.h>
+#include <linux/sizes.h>
#include <linux/usb.h>
#include "mt7925.h"
#include "mcu.h"
#include "mac.h"
+#define MT7927_USB_RX_AGGR_ALIGN 16
+#define MT7927_USB_RX_AGGR_PADDING 12
+#define MT7927_USB_RX_AGGR_LIMIT SZ_32K
+#define MT7927_USB_RX_AGGR_PKT_LIMIT 30
+#define MT7927_USB_RX_MAX_MPDU (13 * SZ_1K)
+#define MT7927_USB_RX_AGGR_BUF_SIZE \
+ mt76u_rx_aggr_buf_size(MT7927_USB_RX_MAX_MPDU, \
+ MT7927_USB_RX_AGGR_LIMIT, \
+ MT7927_USB_RX_AGGR_PKT_LIMIT, \
+ MT7927_USB_RX_AGGR_PADDING)
+
static const struct usb_device_id mt7925u_device_table[] = {
{ USB_DEVICE_AND_INTERFACE_INFO(0x0e8d, 0x6639, 0xff, 0xff, 0xff),
.driver_info = (kernel_ulong_t)MT7925_FIRMWARE_WM },
diff --git a/drivers/net/wireless/mediatek/mt76/mt792x_usb.c b/drivers/net/wireless/mediatek/mt76/mt792x_usb.c
index 6280bc4bf78d..769e828e9449 100644
--- a/drivers/net/wireless/mediatek/mt76/mt792x_usb.c
+++ b/drivers/net/wireless/mediatek/mt76/mt792x_usb.c
@@ -13,6 +13,9 @@
#define MT792X_USB_TX_TIMEOUT_LIMIT 50000
#define MT792X_USB_UDMA_IDLE_TIMEOUT 1000
+#define MT792X_USB_RX_AGG_LIMIT 32
+#define MT792X_USB_RX_AGG_TIMEOUT 100
+#define MT792X_USB_RX_AGG_PKT_LIMIT 30
static int mt792xu_read32(struct mt76_dev *dev, u32 addr, void *buf)
{
@@ -403,9 +406,23 @@ int mt792xu_dma_init(struct mt792x_dev *dev, bool resume)
FIELD_PREP(MT_WL_TX_TMOUT_LMT,
MT792X_USB_TX_TIMEOUT_LIMIT));
mt76_set(dev, MT_UDMA_WLCFG_0, MT_WL_TX_TMOUT_FUNC_EN);
- mt76_clear(dev, MT_UDMA_WLCFG_0,
- MT_WL_RX_AGG_TO | MT_WL_RX_AGG_LMT);
- mt76_clear(dev, MT_UDMA_WLCFG_1, MT_WL_RX_AGG_PKT_LMT);
+
+ if (dev->mt76.usb.rx_aggr.enable) {
+ mt76_set(dev, MT_UDMA_WLCFG_0, MT_WL_RX_AGG_EN);
+ mt76_rmw(dev, MT_UDMA_WLCFG_0,
+ MT_WL_RX_AGG_TO | MT_WL_RX_AGG_LMT,
+ FIELD_PREP(MT_WL_RX_AGG_TO,
+ MT792X_USB_RX_AGG_TIMEOUT) |
+ FIELD_PREP(MT_WL_RX_AGG_LMT,
+ MT792X_USB_RX_AGG_LIMIT));
+ mt76_rmw(dev, MT_UDMA_WLCFG_1, MT_WL_RX_AGG_PKT_LMT,
+ FIELD_PREP(MT_WL_RX_AGG_PKT_LMT,
+ MT792X_USB_RX_AGG_PKT_LIMIT));
+ } else {
+ mt76_clear(dev, MT_UDMA_WLCFG_0, MT_WL_RX_AGG_EN |
+ MT_WL_RX_AGG_TO | MT_WL_RX_AGG_LMT);
+ mt76_clear(dev, MT_UDMA_WLCFG_1, MT_WL_RX_AGG_PKT_LMT);
+ }
if (resume)
return 0;
diff --git a/drivers/net/wireless/mediatek/mt76/usb.c b/drivers/net/wireless/mediatek/mt76/usb.c
index cab36630c978..cbdd663fbb25 100644
--- a/drivers/net/wireless/mediatek/mt76/usb.c
+++ b/drivers/net/wireless/mediatek/mt76/usb.c
@@ -371,6 +371,14 @@ mt76u_refill_rx(struct mt76_dev *dev, struct mt76_queue *q,
return mt76u_fill_rx_sg(dev, q, urb, nsgs);
urb->transfer_buffer_length = q->buf_size;
+ if (qid == MT_RXQ_MAIN && dev->usb.rx_aggr.enable) {
+ if (!urb->transfer_buffer)
+ urb->transfer_buffer =
+ mt76_get_page_pool_buf(q, &offset, q->buf_size);
+
+ return urb->transfer_buffer ? 0 : -ENOMEM;
+ }
+
urb->transfer_buffer = mt76_get_page_pool_buf(q, &offset, q->buf_size);
return urb->transfer_buffer ? 0 : -ENOMEM;
@@ -538,18 +546,113 @@ mt76u_build_rx_skb(struct mt76_dev *dev, void *data,
return skb;
}
+static struct sk_buff *
+mt76u_build_rx_skb_aggr(struct mt76_dev *dev, void *data, int data_len,
+ int buf_len)
+{
+ int head_room, drv_flags = dev->drv->drv_flags;
+ int len = min_t(int, data_len, MT_SKB_HEAD_LEN);
+ struct sk_buff *skb;
+
+ if (data_len <= 0)
+ return NULL;
+
+ head_room = drv_flags & MT_DRV_RX_DMA_HDR ? 0 : MT_DMA_HDR_LEN;
+ skb = alloc_skb(len, GFP_ATOMIC);
+ if (!skb)
+ return NULL;
+
+ data += head_room;
+ skb_put_data(skb, data, len);
+ if (data_len > len) {
+ struct page *page;
+
+ data += len;
+ page = virt_to_head_page(data);
+ skb_add_rx_frag(skb, skb_shinfo(skb)->nr_frags,
+ page, data - page_address(page),
+ data_len - len, buf_len);
+ get_page(page);
+ }
+
+ return skb;
+}
+
+static int mt76u_process_rx_agg_entry(struct mt76_dev *dev, struct urb *urb)
+{
+ int offset = 0, head_room, drv_flags = dev->drv->drv_flags;
+ int align = dev->usb.rx_aggr.align ?: 4;
+ int padding = dev->usb.rx_aggr.padding ?: 4;
+ u8 *data = urb->transfer_buffer;
+ int min_len;
+ int nframes = 0;
+
+ if (!test_bit(MT76_STATE_INITIALIZED, &dev->phy.state) ||
+ test_bit(MT76_REMOVED, &dev->phy.state))
+ return 0;
+
+ head_room = drv_flags & MT_DRV_RX_DMA_HDR ? 0 : MT_DMA_HDR_LEN;
+ min_len = head_room + MT_RX_RXWI_LEN;
+
+ while (urb->actual_length - offset >= min_len) {
+ struct sk_buff *skb;
+ int len, frame_len, agg_len;
+
+ len = mt76u_get_rx_entry_len(dev, data + offset,
+ urb->actual_length - offset);
+ if (len < 0) {
+ dev_warn_ratelimited(dev->dev,
+ "invalid USB RX aggregate at offset %d\n",
+ offset);
+ break;
+ }
+
+ frame_len = head_room + len;
+ if (frame_len > urb->actual_length - offset) {
+ dev_warn_ratelimited(dev->dev,
+ "truncated USB RX aggregate at offset %d\n",
+ offset);
+ break;
+ }
+
+ agg_len = ALIGN(frame_len, align) + padding;
+ if (dev->drv->rx_check &&
+ !dev->drv->rx_check(dev, data + offset + head_room, len))
+ goto next;
+
+ skb = mt76u_build_rx_skb_aggr(dev, data + offset, len,
+ agg_len);
+ if (skb) {
+ dev->drv->rx_skb(dev, MT_RXQ_MAIN, skb, NULL);
+ nframes++;
+ }
+
+next:
+ offset += agg_len;
+ }
+
+ mt76_put_page_pool_buf(urb->transfer_buffer, false);
+ urb->transfer_buffer = NULL;
+
+ return max(nframes, 1);
+}
+
static int
mt76u_process_rx_entry(struct mt76_dev *dev, struct urb *urb,
- int buf_size)
+ enum mt76_rxq_id qid, int buf_size)
{
u8 *data = urb->num_sgs ? sg_virt(&urb->sg[0]) : urb->transfer_buffer;
int data_len = urb->num_sgs ? urb->sg[0].length : urb->actual_length;
int len, nsgs = 1, head_room, drv_flags = dev->drv->drv_flags;
struct sk_buff *skb;
- if (!test_bit(MT76_STATE_INITIALIZED, &dev->phy.state))
+ if (!test_bit(MT76_STATE_INITIALIZED, &dev->phy.state) ||
+ test_bit(MT76_REMOVED, &dev->phy.state))
return 0;
+ if (qid == MT_RXQ_MAIN && dev->usb.rx_aggr.enable && !urb->num_sgs)
+ return mt76u_process_rx_agg_entry(dev, urb);
+
len = mt76u_get_rx_entry_len(dev, data, urb->actual_length);
if (len < 0)
return 0;
@@ -594,6 +697,9 @@ static void mt76u_complete_rx(struct urb *urb)
trace_rx_urb(dev, urb);
+ if (test_bit(MT76_REMOVED, &dev->phy.state))
+ return;
+
switch (urb->status) {
case -ECONNRESET:
case -ESHUTDOWN:
@@ -658,12 +764,14 @@ mt76u_process_rx_queue(struct mt76_dev *dev, struct mt76_queue *q)
if (!urb)
break;
- count = mt76u_process_rx_entry(dev, urb, q->buf_size);
+ count = mt76u_process_rx_entry(dev, urb, qid, q->buf_size);
if (count > 0) {
err = mt76u_refill_rx(dev, q, urb, count);
if (err < 0)
break;
}
+ if (test_bit(MT76_REMOVED, &dev->phy.state))
+ break;
mt76u_submit_rx_buf(dev, qid, urb);
}
}
@@ -729,10 +837,6 @@ mt76u_alloc_rx_queue(struct mt76_dev *dev, enum mt76_rxq_id qid)
struct mt76_queue *q = &dev->q_rx[qid];
int i, err;
- err = mt76_create_page_pool(dev, q);
- if (err)
- return err;
-
spin_lock_init(&q->lock);
q->entry = devm_kcalloc(dev->dev,
MT_NUM_RX_ENTRIES, sizeof(*q->entry),
@@ -742,6 +846,12 @@ mt76u_alloc_rx_queue(struct mt76_dev *dev, enum mt76_rxq_id qid)
q->ndesc = MT_NUM_RX_ENTRIES;
q->buf_size = PAGE_SIZE;
+ if (qid == MT_RXQ_MAIN && dev->usb.rx_aggr.enable)
+ q->buf_size = dev->usb.rx_aggr.buf_size ?: PAGE_SIZE;
+
+ err = mt76_create_page_pool(dev, q);
+ if (err)
+ return err;
for (i = 0; i < q->ndesc; i++) {
err = mt76u_rx_urb_alloc(dev, q, &q->entry[i]);
--
2.43.0
^ permalink raw reply related
* [PATCH 2/5] wifi: mt76: usb: support out-of-order RX URB completion
From: Sean Wang @ 2026-06-13 22:46 UTC (permalink / raw)
To: Felix Fietkau, Lorenzo Bianconi; +Cc: linux-wireless, linux-mediatek, Sean Wang
In-Reply-To: <20260613224655.2405686-1-sean.wang@kernel.org>
From: Sean Wang <sean.wang@mediatek.com>
Keep per-URB RX queue context and complete entries by their real queue
position instead of assuming the completed URB is always at q->head.
USB RX URBs can complete out of order, and advancing q->head too early
can corrupt RX queue accounting and process buffers in the wrong order.
Signed-off-by: Sean Wang <sean.wang@mediatek.com>
---
drivers/net/wireless/mediatek/mt76/mt76.h | 5 ++
drivers/net/wireless/mediatek/mt76/usb.c | 77 ++++++++++++++++-------
2 files changed, 61 insertions(+), 21 deletions(-)
diff --git a/drivers/net/wireless/mediatek/mt76/mt76.h b/drivers/net/wireless/mediatek/mt76/mt76.h
index 122e77a5f2f4..81740aa7df71 100644
--- a/drivers/net/wireless/mediatek/mt76/mt76.h
+++ b/drivers/net/wireless/mediatek/mt76/mt76.h
@@ -655,6 +655,11 @@ struct mt76_mcu {
wait_queue_head_t wait;
};
+struct mt76u_rx_entry {
+ struct mt76_queue_entry *e;
+ struct mt76_queue *q;
+};
+
#define MT_TX_SG_MAX_SIZE 8
#define MT_RX_SG_MAX_SIZE 4
#define MT_NUM_TX_ENTRIES 256
diff --git a/drivers/net/wireless/mediatek/mt76/usb.c b/drivers/net/wireless/mediatek/mt76/usb.c
index ce68e1d0c786..cab36630c978 100644
--- a/drivers/net/wireless/mediatek/mt76/usb.c
+++ b/drivers/net/wireless/mediatek/mt76/usb.c
@@ -397,11 +397,25 @@ mt76u_urb_alloc(struct mt76_dev *dev, struct mt76_queue_entry *e,
return 0;
}
+static void mt76u_urb_free(struct urb *urb)
+{
+ int i;
+
+ for (i = 0; i < urb->num_sgs; i++)
+ mt76_put_page_pool_buf(sg_virt(&urb->sg[i]), false);
+
+ if (urb->transfer_buffer)
+ mt76_put_page_pool_buf(urb->transfer_buffer, false);
+
+ usb_free_urb(urb);
+}
+
static int
mt76u_rx_urb_alloc(struct mt76_dev *dev, struct mt76_queue *q,
struct mt76_queue_entry *e)
{
enum mt76_rxq_id qid = q - &dev->q_rx[MT_RXQ_MAIN];
+ struct mt76u_rx_entry *rxe;
int err, sg_size;
sg_size = qid == MT_RXQ_MAIN ? MT_RX_SG_MAX_SIZE : 0;
@@ -409,20 +423,25 @@ mt76u_rx_urb_alloc(struct mt76_dev *dev, struct mt76_queue *q,
if (err)
return err;
- return mt76u_refill_rx(dev, q, e->urb, sg_size);
-}
-
-static void mt76u_urb_free(struct urb *urb)
-{
- int i;
+ rxe = kzalloc_obj(*rxe, GFP_KERNEL);
+ if (!rxe) {
+ usb_free_urb(e->urb);
+ e->urb = NULL;
+ return -ENOMEM;
+ }
- for (i = 0; i < urb->num_sgs; i++)
- mt76_put_page_pool_buf(sg_virt(&urb->sg[i]), false);
+ rxe->e = e;
+ rxe->q = q;
+ e->urb->context = rxe;
- if (urb->transfer_buffer)
- mt76_put_page_pool_buf(urb->transfer_buffer, false);
+ err = mt76u_refill_rx(dev, q, e->urb, sg_size);
+ if (err) {
+ kfree(rxe);
+ mt76u_urb_free(e->urb);
+ e->urb = NULL;
+ }
- usb_free_urb(urb);
+ return err;
}
static void
@@ -566,8 +585,12 @@ mt76u_process_rx_entry(struct mt76_dev *dev, struct urb *urb,
static void mt76u_complete_rx(struct urb *urb)
{
struct mt76_dev *dev = dev_get_drvdata(&urb->dev->dev);
- struct mt76_queue *q = urb->context;
+ struct mt76u_rx_entry *rxe = urb->context;
+ struct mt76_queue_entry *e = rxe->e;
+ unsigned int idx, pending, pos;
+ struct mt76_queue *q = rxe->q;
unsigned long flags;
+ bool wake = false;
trace_rx_urb(dev, urb);
@@ -586,18 +609,28 @@ static void mt76u_complete_rx(struct urb *urb)
}
spin_lock_irqsave(&q->lock, flags);
- if (WARN_ONCE(q->entry[q->head].urb != urb, "rx urb mismatch"))
+ idx = e - q->entry;
+ pending = q->ndesc - q->queued;
+ pos = (idx + q->ndesc - q->head) % q->ndesc;
+ if (WARN_ONCE(idx >= q->ndesc || pos >= pending, "rx urb mismatch"))
goto out;
- q->head = (q->head + 1) % q->ndesc;
- q->queued++;
-
- if (q == &dev->q_rx[MT_RXQ_MAIN])
- napi_schedule(&dev->napi[MT_RXQ_MAIN]);
- else
- mt76_worker_schedule(&dev->usb.rx_worker);
+ e->done = true;
+ while (q->entry[q->head].done) {
+ q->entry[q->head].done = false;
+ q->head = (q->head + 1) % q->ndesc;
+ q->queued++;
+ wake = true;
+ }
out:
spin_unlock_irqrestore(&q->lock, flags);
+
+ if (wake) {
+ if (q == &dev->q_rx[MT_RXQ_MAIN])
+ napi_schedule(&dev->napi[MT_RXQ_MAIN]);
+ else
+ mt76_worker_schedule(&dev->usb.rx_worker);
+ }
}
static int
@@ -607,7 +640,7 @@ mt76u_submit_rx_buf(struct mt76_dev *dev, enum mt76_rxq_id qid,
int ep = qid == MT_RXQ_MAIN ? MT_EP_IN_PKT_RX : MT_EP_IN_CMD_RESP;
mt76u_fill_bulk_urb(dev, USB_DIR_IN, ep, urb,
- mt76u_complete_rx, &dev->q_rx[qid]);
+ mt76u_complete_rx, urb->context);
trace_submit_urb(dev, urb);
return usb_submit_urb(urb, GFP_ATOMIC);
@@ -678,6 +711,7 @@ mt76u_submit_rx_buffers(struct mt76_dev *dev, enum mt76_rxq_id qid)
spin_lock_irqsave(&q->lock, flags);
for (i = 0; i < q->ndesc; i++) {
+ q->entry[i].done = false;
err = mt76u_submit_rx_buf(dev, qid, q->entry[i].urb);
if (err < 0)
break;
@@ -733,6 +767,7 @@ mt76u_free_rx_queue(struct mt76_dev *dev, struct mt76_queue *q)
if (!q->entry[i].urb)
continue;
+ kfree(q->entry[i].urb->context);
mt76u_urb_free(q->entry[i].urb);
q->entry[i].urb = NULL;
}
--
2.43.0
^ permalink raw reply related
* [PATCH 1/5] wifi: mt76: usb: size RX page-pool pages from queue buffer
From: Sean Wang @ 2026-06-13 22:46 UTC (permalink / raw)
To: Felix Fietkau, Lorenzo Bianconi; +Cc: linux-wireless, linux-mediatek, Sean Wang
In-Reply-To: <20260613224655.2405686-1-sean.wang@kernel.org>
From: Sean Wang <sean.wang@mediatek.com>
Use the RX queue buffer size to select the page-pool allocation order.
This lets USB devices use larger RX buffers without silently allocating
undersized order-0 pages.
Signed-off-by: Sean Wang <sean.wang@mediatek.com>
---
drivers/net/wireless/mediatek/mt76/mac80211.c | 3 +++
1 file changed, 3 insertions(+)
diff --git a/drivers/net/wireless/mediatek/mt76/mac80211.c b/drivers/net/wireless/mediatek/mt76/mac80211.c
index 13c4e8abe281..6ff1eada6d09 100644
--- a/drivers/net/wireless/mediatek/mt76/mac80211.c
+++ b/drivers/net/wireless/mediatek/mt76/mac80211.c
@@ -628,6 +628,9 @@ int mt76_create_page_pool(struct mt76_dev *dev, struct mt76_queue *q)
if (!is_qrx && !mt76_queue_is_wed_tx_free(q))
return 0;
+ if (q->buf_size > PAGE_SIZE)
+ pp_params.order = get_order(q->buf_size);
+
switch (idx) {
case MT_RXQ_MAIN:
case MT_RXQ_BAND1:
--
2.43.0
^ permalink raw reply related
* [PATCH 0/5] wifi: mt76: add USB RX aggregation support
From: Sean Wang @ 2026-06-13 22:46 UTC (permalink / raw)
To: Felix Fietkau, Lorenzo Bianconi; +Cc: linux-wireless, linux-mediatek, Sean Wang
This series adds optional USB RX aggregation support to mt76 and enables
it on mt7927u.
RX aggregation allows multiple RX frames to be received from one USB URB,
reducing USB completion overhead and improving RX efficiency for
high-throughput RX traffic and monitor capture.
The common USB support remains opt-in, so existing USB drivers keep the
current behavior unless they explicitly enable RX aggregation. The same
settings work for both mt7927u and mt7925u, but this series enables the
feature only on mt7927u for now.
This series does the following:
- size RX page-pool pages from the queue buffer size
- support out-of-order RX URB completion
- add optional USB RX aggregation parsing
- add debugfs stats to verify aggregation behavior
- enable USB RX aggregation on mt7927u
The series is based on wireless-next commit:
21352612198c ("b43: add RF power offset for N-PHY r8 + radio 2057 r8")
It also cherry-picks the following patch from patchwork as a dependency:
wifi: mt76: mt76u: use a threaded NAPI for the RX path
Link: https://lore.kernel.org/all/20260609105301.196302-1-phial@phiality.com/
Sean Wang (5):
wifi: mt76: usb: size RX page-pool pages from queue buffer
wifi: mt76: usb: support out-of-order RX URB completion
wifi: mt76: usb: add optional RX aggregation support
wifi: mt76: usb: add debugfs aggregation stats
wifi: mt76: mt7927u: enable USB RX aggregation
drivers/net/wireless/mediatek/mt76/debugfs.c | 35 +++
drivers/net/wireless/mediatek/mt76/mac80211.c | 3 +
drivers/net/wireless/mediatek/mt76/mt76.h | 38 ++-
.../net/wireless/mediatek/mt76/mt7925/usb.c | 18 +-
.../net/wireless/mediatek/mt76/mt792x_usb.c | 23 +-
drivers/net/wireless/mediatek/mt76/usb.c | 251 ++++++++++++++++--
6 files changed, 335 insertions(+), 33 deletions(-)
--
2.43.0
^ permalink raw reply
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