Linux wireless drivers development
 help / color / mirror / Atom feed
* [PATCH] mac80211: stop hardware before clearing driver state on reconfig failure
From: Masi Osmani @ 2026-03-12 14:30 UTC (permalink / raw)
  To: Johannes Berg; +Cc: linux-wireless, Christian Lamparter, Masi Osmani

When ieee80211_handle_reconfig_failure() is called after a failed HW
reconfiguration, it clears IEEE80211_SDATA_IN_DRIVER flags on all
interfaces but does not stop the hardware. This creates a race window:
cfg80211_shutdown_all_interfaces() subsequently calls ieee80211_do_stop()
which runs sta_info_flush() to destroy stations, while the driver's RX
path may still be delivering frames that reference station data being
freed.

This race was observed with the carl9170 driver: when firmware
deadlocks during a restart attempt, ieee80211_reconfig() fails
at drv_add_interface(). The subsequent interface teardown triggers
sta_info_destroy_part2() while the USB RX tasklet still calls
ieee80211_rx_napi(), causing a use-after-free kernel panic.

The fix stops the hardware in ieee80211_handle_reconfig_failure() before
clearing IN_DRIVER state, ensuring no driver can deliver RX frames once
the teardown begins. The drv_stop() call is guarded by local->started
since some call sites reach this function after drv_start() has already
failed (where the hardware was never started).

Signed-off-by: Masi Osmani <mas-i@hotmail.de>
---
 net/mac80211/util.c | 12 ++++++++++++
 1 file changed, 12 insertions(+)

diff --git a/net/mac80211/util.c b/net/mac80211/util.c
--- a/net/mac80211/util.c
+++ b/net/mac80211/util.c
@@ -1614,6 +1614,18 @@ static void ieee80211_handle_reconfig_failure(struct ieee80211_local *local)

 	local->resuming = false;
 	local->suspended = false;
+
+	/*
+	 * Stop the hardware before clearing IN_DRIVER state. Without this,
+	 * cfg80211_shutdown_all_interfaces() tears down stations via
+	 * sta_info_flush() while the driver's RX path may still deliver
+	 * frames referencing station data being freed, causing use-after-free.
+	 * Guard with local->started since this function can be reached when
+	 * drv_start() itself failed (hardware never started).
+	 */
+	if (local->started)
+		drv_stop(local, false);
+
 	local->in_reconfig = false;
 	local->reconfig_failure = true;


^ permalink raw reply

* Re: [PATCH] wifi: rtl8xxxu: Enable AP mode for RTL8188EU
From: Georg Müller @ 2026-03-12 14:29 UTC (permalink / raw)
  To: Jes.Sorensen; +Cc: linux-wireless, linux-kernel
In-Reply-To: <20260312142155.2642993-1-georgmueller@gmx.net>

Sorry, I missed the link:

Am 12.03.26 um 15:21 schrieb Georg Müller:
> The max_macid_num is conservative. The old driver used 32 [1], some
> other sources said 64. I have not enough adapters to test any of the
> higher values. Given the usage of this chipset in nano dongles, I think
> the 16 might be high enough.
> 

[1] https://github.com/lwfinger/rtl8188eu/blob/f5d1c8df2e2d8b217ea0113bf2cf3e37df8cb716/include/sta_info.h#L28

Link for max_macid_num = 64:
https://lore.kernel.org/linux-wireless/27e83382-4c84-1841-c428-d1e5143ea20c@gmail.com/


^ permalink raw reply

* [syzbot ci] Re: iwlwifi + mac80211 stability
From: syzbot ci @ 2026-03-12 14:25 UTC (permalink / raw)
  To: greearb, linux-wireless; +Cc: syzbot, syzkaller-bugs
In-Reply-To: <20260311230730.163348-1-greearb@candelatech.com>

syzbot ci has tested the following series

[v1] iwlwifi + mac80211 stability
https://lore.kernel.org/all/20260311230730.163348-1-greearb@candelatech.com
* [PATCH wireless-next 01/28] wifi: iwlwifi: mld:  Check for NULL before lookup.
* [PATCH wireless-next 02/28] wifi: iwlwifi: mld: Fix un-set return value in error case.
* [PATCH wireless-next 03/28] wifi: iwlwifi: mld: Add check for null vif in stats callback.
* [PATCH wireless-next 04/28] wifi: mac80211:  Check debugfs creation return values.
* [PATCH wireless-next 05/28] wifi: mac80211: do not fail taking sta to lower state.
* [PATCH wireless-next 06/28] wifi: mac80211: Mark sta as uploaded if single transition succeeds.
* [PATCH wireless-next 07/28] wifi: mac80211:  Fix use-after-free of debugfs inodes.
* [PATCH wireless-next 08/28] wifi: mac80211: Debugfs safety checks.
* [PATCH wireless-next 09/28] wifi: mac80211: Use warn-on-once in drv_remove_chanctxt
* [PATCH wireless-next 10/28] wifi: mac80211: Ensure sta debugfs is not double-freed.
* [PATCH wireless-next 11/28] wifi: iwlwifi: mld: Fix stale reference in fw_id_to_link_sta
* [PATCH wireless-next 12/28] wifi: iwlwifi: mld:  Improve logging in error cases.
* [PATCH wireless-next 13/28] wifi: iwlwifi: mld: Remove warning about BAID.
* [PATCH wireless-next 14/28] wifi: mac80211: Add dmesg log regarding warn-on in drv-stop.
* [PATCH wireless-next 15/28] wifi: iwlwifi: mld: Fix use-after-free of bss_conf
* [PATCH wireless-next 16/28] wifi: iwlwifi: mld: Check for null in iwl_mld_wait_sta_txqs_empty
* [PATCH wireless-next 17/28] wifi: iwlwifi: mld: use warn-on-once in error path.
* [PATCH wireless-next 18/28] wifi: iwlwifi: mld: Use warn-on-once in emlsr exit logic.
* [PATCH wireless-next 19/28] wifi: iwlwifi: mld: Improve error message in rx path.
* [PATCH wireless-next 20/28] wifi: iwlwifi: mld: Improve logging message.
* [PATCH wireless-next 21/28] wifi: iwlwifi: mld: Protect from null mld_sta
* [PATCH wireless-next 22/28] wifi: mac80211: Add force-cleanup call to driver.
* [PATCH wireless-next 23/28] wifi: iwlwifi: mld: Support force-cleanup op
* [PATCH wireless-next 24/28] wifi: iwlwifi: mld: Fix NPE in flush logic.
* [PATCH wireless-next 25/28] wifi: iwlwifi: mld: Fix bad return address in tx code.
* [PATCH wireless-next 26/28] wifi: mac80211: Ensure link work-items are only initialized once.
* [PATCH wireless-next 27/28] wifi: iwlwifi: mld: Convert to WARN_ONCE in link removal path.
* [PATCH wireless-next 28/28] wifi: mac80211: Decrease WARN spam.

and found the following issue:
WARNING in drv_add_interface

Full report is available here:
https://ci.syzbot.org/series/d3986751-1907-410b-b80c-976f38583b8c

***

WARNING in drv_add_interface

tree:      linux-next
URL:       https://kernel.googlesource.com/pub/scm/linux/kernel/git/next/linux-next
base:      97492c019da4b62df83255e968b23b81c0315530
arch:      amd64
compiler:  Debian clang version 21.1.8 (++20251221033036+2078da43e25a-1~exp1~20251221153213.50), Debian LLD 21.1.8
config:    https://ci.syzbot.org/builds/acf234a5-5041-402a-ace9-5766b71cadb4/config
C repro:   https://ci.syzbot.org/findings/1533841d-c00d-4811-84c1-419f7bccc86a/c_repro
syz repro: https://ci.syzbot.org/findings/1533841d-c00d-4811-84c1-419f7bccc86a/syz_repro

------------[ cut here ]------------
!sdata->vif.debugfs_dir
WARNING: net/mac80211/driver-ops.h:510 at drv_vif_add_debugfs net/mac80211/driver-ops.h:510 [inline], CPU#1: dhcpcd/5553
WARNING: net/mac80211/driver-ops.h:510 at drv_add_interface+0x5e5/0x910 net/mac80211/driver-ops.c:84, CPU#1: dhcpcd/5553
Modules linked in:
CPU: 1 UID: 0 PID: 5553 Comm: dhcpcd Not tainted syzkaller #0 PREEMPT(full) 
Hardware name: QEMU Standard PC (Q35 + ICH9, 2009), BIOS 1.16.2-debian-1.16.2-1 04/01/2014
RIP: 0010:drv_vif_add_debugfs net/mac80211/driver-ops.h:510 [inline]
RIP: 0010:drv_add_interface+0x5e5/0x910 net/mac80211/driver-ops.c:84
Code: f3 fa ff ff e8 9c 22 ae f6 48 8d 3d 85 f2 0a 05 67 48 0f b9 3a e9 c1 fc ff ff e8 86 22 ae f6 e9 19 fb ff ff e8 7c 22 ae f6 90 <0f> 0b 90 eb 94 e8 71 22 ae f6 4c 8d 35 7a f2 0a 05 49 8d bf 28 0a
RSP: 0018:ffffc90003b57678 EFLAGS: 00010293
RAX: ffffffff8b1776f4 RBX: ffff888172594dc0 RCX: ffff8881165657c0
RDX: 0000000000000000 RSI: 0000000000000002 RDI: 0000000000000006
RBP: 0000000000000000 R08: ffffffff901146b7 R09: 1ffffffff20228d6
R10: dffffc0000000000 R11: fffffbfff20228d7 R12: dffffc0000000000
R13: ffff888172597028 R14: ffff8881725957f8 R15: 0000000000000002
FS:  00007ff45a6f6740(0000) GS:ffff8882a9465000(0000) knlGS:0000000000000000
CS:  0010 DS: 0000 ES: 0000 CR0: 0000000080050033
CR2: 000055d00b0c3161 CR3: 000000010017c000 CR4: 00000000000006f0
Call Trace:
 <TASK>
 ieee80211_do_open+0x929/0x2490 net/mac80211/iface.c:1466
 ieee80211_open+0x15b/0x200 net/mac80211/iface.c:472
 __dev_open+0x44d/0x830 net/core/dev.c:1702
 __dev_change_flags+0x1f7/0x690 net/core/dev.c:9778
 netif_change_flags+0x88/0x1a0 net/core/dev.c:9841
 dev_change_flags+0x130/0x260 net/core/dev_api.c:68
 devinet_ioctl+0x9f2/0x1b30 net/ipv4/devinet.c:1199
 inet_ioctl+0x42a/0x560 net/ipv4/af_inet.c:1004
 sock_do_ioctl+0x101/0x320 net/socket.c:1253
 sock_ioctl+0x5c6/0x7f0 net/socket.c:1374
 vfs_ioctl fs/ioctl.c:51 [inline]
 __do_sys_ioctl fs/ioctl.c:597 [inline]
 __se_sys_ioctl+0xfc/0x170 fs/ioctl.c:583
 do_syscall_x64 arch/x86/entry/syscall_64.c:63 [inline]
 do_syscall_64+0x14d/0xf80 arch/x86/entry/syscall_64.c:94
 entry_SYSCALL_64_after_hwframe+0x77/0x7f
RIP: 0033:0x7ff45a7c4d49
Code: 5c c3 48 8d 44 24 08 48 89 54 24 e0 48 89 44 24 c0 48 8d 44 24 d0 48 89 44 24 c8 b8 10 00 00 00 c7 44 24 b8 10 00 00 00 0f 05 <41> 89 c0 3d 00 f0 ff ff 76 10 48 8b 15 ae 60 0d 00 f7 d8 41 83 c8
RSP: 002b:00007ffff8603cd8 EFLAGS: 00000246 ORIG_RAX: 0000000000000010
RAX: ffffffffffffffda RBX: 00007ff45a6f66c0 RCX: 00007ff45a7c4d49
RDX: 00007ffff8613ec8 RSI: 0000000000008914 RDI: 0000000000000011
RBP: 00007ffff8624088 R08: 00007ffff8613e88 R09: 00007ffff8613e38
R10: 0000000000000000 R11: 0000000000000246 R12: 0000000000000000
R13: 00007ffff8613ec8 R14: 0000000000000028 R15: 0000000000008914
 </TASK>


***

If these findings have caused you to resend the series or submit a
separate fix, please add the following tag to your commit message:
  Tested-by: syzbot@syzkaller.appspotmail.com

---
This report is generated by a bot. It may contain errors.
syzbot ci engineers can be reached at syzkaller@googlegroups.com.

^ permalink raw reply

* [PATCH] wifi: rtl8xxxu: Enable AP mode for RTL8188EU
From: Georg Müller @ 2026-03-12 14:21 UTC (permalink / raw)
  To: Jes.Sorensen; +Cc: linux-wireless, linux-kernel, Georg Müller

Allow devices with this driver to be used as a wireless access point.

I successfully tested this with a TP-Link TP-Link TL-WN725N adapter.

Experiments two years ago failed, but some other improvements to the
driver seemed to have resolved theses issues.

The max_macid_num is conservative. The old driver used 32 [1], some
other sources said 64. I have not enough adapters to test any of the
higher values. Given the usage of this chipset in nano dongles, I think
the 16 might be high enough.

Signed-off-by: Georg Müller <georgmueller@gmx.net>
---
 drivers/net/wireless/realtek/rtl8xxxu/8188e.c | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/drivers/net/wireless/realtek/rtl8xxxu/8188e.c b/drivers/net/wireless/realtek/rtl8xxxu/8188e.c
index 766a7a7c7d28..c6aecb28d28b 100644
--- a/drivers/net/wireless/realtek/rtl8xxxu/8188e.c
+++ b/drivers/net/wireless/realtek/rtl8xxxu/8188e.c
@@ -1867,6 +1867,8 @@ struct rtl8xxxu_fileops rtl8188eu_fops = {
 	.init_reg_pkt_life_time = 1,
 	.gen2_thermal_meter = 1,
 	.max_sec_cam_num = 32,
+	.supports_ap = 1,
+	.max_macid_num = 16,
 	.adda_1t_init = 0x0b1b25a0,
 	.adda_1t_path_on = 0x0bdb25a0,
 	/*
-- 
2.53.0


^ permalink raw reply related

* Re: [PATCH 05/10 net-next v2] drivers: net: drop ipv6_stub usage and use direct function calls
From: Jason A. Donenfeld @ 2026-03-12 13:48 UTC (permalink / raw)
  To: Fernando Fernandez Mancera
  Cc: netdev, rbm, Jason Gunthorpe, Leon Romanovsky, Zhu Yanjun,
	Saeed Mahameed, Tariq Toukan, Mark Bloch, Andrew Lunn,
	David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
	Boris Pismenny, Ido Schimmel, Petr Machata, Simon Horman,
	Edward Cree, Pablo Neira Ayuso, Harald Welte, Antonio Quartulli,
	Sabrina Dubroca, Oliver Neukum, David Ahern, Stanislav Yakovlev,
	Nikolay Aleksandrov, Parav Pandit, Edward Srouji, Vlad Dumitrescu,
	Kees Cook, Jianbo Liu, Gal Pressman, Guillaume Nault,
	Cosmin Ratiu, Carolina Jubran, Alexandre Cassen,
	Stanislav Fomichev, open list:INFINIBAND SUBSYSTEM, open list,
	open list:NETRONOME ETHERNET DRIVERS,
	open list:SFC NETWORK DRIVER,
	open list:GTP (GPRS Tunneling Protocol),
	open list:USB CDC ETHERNET DRIVER,
	open list:WIREGUARD SECURE NETWORK TUNNEL,
	open list:INTEL PRO/WIRELESS 2100, 2200BG, 2915ABG NETWOR...,
	open list:ETHERNET BRIDGE
In-Reply-To: <20260310153506.5181-6-fmancera@suse.de>

On Tue, Mar 10, 2026 at 04:34:28PM +0100, Fernando Fernandez Mancera wrote:
> diff --git a/drivers/net/wireguard/socket.c b/drivers/net/wireguard/socket.c
> index 253488f8c00f..c362c78d908e 100644
> --- a/drivers/net/wireguard/socket.c
> +++ b/drivers/net/wireguard/socket.c
> @@ -136,8 +136,7 @@ static int send6(struct wg_device *wg, struct sk_buff *skb,
>  			if (cache)
>  				dst_cache_reset(cache);
>  		}
> -		dst = ipv6_stub->ipv6_dst_lookup_flow(sock_net(sock), sock, &fl,
> -						      NULL);
> +		dst = ip6_dst_lookup_flow(sock_net(sock), sock, &fl, NULL);
>  		if (IS_ERR(dst)) {
>  			ret = PTR_ERR(dst);
>  			net_dbg_ratelimited("%s: No route to %pISpfsc, error %d\n",

Rest in peace, stub.

For the WireGuard part,

    Reviewed-by: Jason A. Donenfeld <Jason@zx2c4.com>

^ permalink raw reply

* Re: ath12k: handling of HE and EHT capabilities
From: Alexander Wilhelm @ 2026-03-12 13:00 UTC (permalink / raw)
  To: Johannes Berg; +Cc: Jeff Johnson, ath12k, linux-wireless, linux-kernel
In-Reply-To: <e8960517faf04ed4f1bf331e23a95c477113309f.camel@sipsolutions.net>

On Thu, Mar 12, 2026 at 01:10:21PM +0100, Johannes Berg wrote:
> Wait ...
> 
> > > I don’t see this in the function. For example, the MAC capabilities are a
> > > `u16 *` in CPU endianness, which is simply memcpy’d from the parsed
> > > `NL80211_BAND_IFTYPE_ATTR_HE_CAP_MAC`. Later, they are treated as `u16 *`,
> > > as shown in the following code:
> > > 
> > >     printf("%s\t\tHE MAC Capabilities (0x", pre);
> > >     for (i = 0; i < 3; i++)
> > >         printf("%04x", mac_cap[i]);
> > >     printf("):\n");
> 
> That's incorrect for sure. But iw code now actually reads
> 
>         printf("%s\t\tHE MAC Capabilities (0x", pre);
>         for (i = 0; i < 3; i++)
>                 printf("%04x", le16toh(mac_cap[i]));
>         printf("):\n");
> 
> 
> which is correct. HE PHY capabilities are printed as
> 
>         printf("%s\t\tHE PHY Capabilities: (0x", pre);
>         for (i = 0; i < 11; i++)
>                 printf("%02x", ((__u8 *)phy_cap)[i + 1]);
> 
> in my version of the code, and it seems to me the +1 is incorrect either
> way?
> 
> >         printf("%s\t\tEHT MAC Capabilities (0x", pre);
> >         for (i = 0; i < 2; i++)
> >                 printf("%02x", mac_cap[i]);
> 
> This was also correct, not incorrect as I stated, since mac_cap is u8 *,
> and EHT PHY capabilities are cast to u8 * first.
> 
> Maybe your iw is just really old?

Sorry, my fault. I'm using `OpenWrt v24.10.5` with `iw` version 6.9. The
latest master has the `le16toh` implemented. With my `ath12k` fix the PHY
capabilities and the respecitve descriptions are fine now. But I still
cannot get MAC capabilities correct. I'll analyze it further.


Best regards
Alexander wilhelm

^ permalink raw reply

* Re: [PATCH 00/61] treewide: Use IS_ERR_OR_NULL over manual NULL check - refactor
From: Jason Gunthorpe @ 2026-03-12 12:57 UTC (permalink / raw)
  To: Kuan-Wei Chiu
  Cc: Philipp Hahn, amd-gfx, apparmor, bpf, ceph-devel, cocci, dm-devel,
	dri-devel, gfs2, intel-gfx, intel-wired-lan, iommu, kvm,
	linux-arm-kernel, linux-block, linux-bluetooth, linux-btrfs,
	linux-cifs, linux-clk, linux-erofs, linux-ext4, linux-fsdevel,
	linux-gpio, linux-hyperv, linux-input, linux-kernel, linux-leds,
	linux-media, linux-mips, linux-mm, linux-modules, linux-mtd,
	linux-nfs, linux-omap, linux-phy, linux-pm, linux-rockchip,
	linux-s390, linux-scsi, linux-sctp, linux-security-module,
	linux-sh, linux-sound, linux-stm32, linux-trace-kernel, linux-usb,
	linux-wireless, netdev, ntfs3, samba-technical, sched-ext,
	target-devel, tipc-discussion, v9fs
In-Reply-To: <abBlpGKO842B3yl9@google.com>

On Wed, Mar 11, 2026 at 02:40:36AM +0800, Kuan-Wei Chiu wrote:

> IMHO, the necessity of IS_ERR_OR_NULL() often highlights a confusing or
> flawed API design. It usually implies that the caller is unsure whether
> a failure results in an error pointer or a NULL pointer. 

+1

IS_ERR_OR_NULL() should always be looked on with suspicion. Very
little should be returning some tri-state 'ERR' 'NULL' 'SUCCESS'
pointer. What does the middle condition even mean? IS_ERR_OR_NULL()
implies ERR and NULL are semanticly the same, so fix the things to
always use ERR.

If you want to improve things work to get rid of the NULL checks this
script identifies. Remove ERR or NULL because only one can ever
happen, or fix the source to consistently return ERR.

Jason

^ permalink raw reply

* Re: ath12k: handling of HE and EHT capabilities
From: Johannes Berg @ 2026-03-12 12:10 UTC (permalink / raw)
  To: Alexander Wilhelm; +Cc: Jeff Johnson, ath12k, linux-wireless, linux-kernel
In-Reply-To: <40b4b959b2ea5afd55381e6ae2d0c1908456734c.camel@sipsolutions.net>

Wait ...

> > I don’t see this in the function. For example, the MAC capabilities are a
> > `u16 *` in CPU endianness, which is simply memcpy’d from the parsed
> > `NL80211_BAND_IFTYPE_ATTR_HE_CAP_MAC`. Later, they are treated as `u16 *`,
> > as shown in the following code:
> > 
> >     printf("%s\t\tHE MAC Capabilities (0x", pre);
> >     for (i = 0; i < 3; i++)
> >         printf("%04x", mac_cap[i]);
> >     printf("):\n");

That's incorrect for sure. But iw code now actually reads

        printf("%s\t\tHE MAC Capabilities (0x", pre);
        for (i = 0; i < 3; i++)
                printf("%04x", le16toh(mac_cap[i]));
        printf("):\n");


which is correct. HE PHY capabilities are printed as

        printf("%s\t\tHE PHY Capabilities: (0x", pre);
        for (i = 0; i < 11; i++)
                printf("%02x", ((__u8 *)phy_cap)[i + 1]);

in my version of the code, and it seems to me the +1 is incorrect either
way?

>         printf("%s\t\tEHT MAC Capabilities (0x", pre);
>         for (i = 0; i < 2; i++)
>                 printf("%02x", mac_cap[i]);

This was also correct, not incorrect as I stated, since mac_cap is u8 *,
and EHT PHY capabilities are cast to u8 * first.

Maybe your iw is just really old?

johannes

^ permalink raw reply

* Re: ath12k: handling of HE and EHT capabilities
From: Alexander Wilhelm @ 2026-03-12 10:53 UTC (permalink / raw)
  To: Johannes Berg; +Cc: Jeff Johnson, ath12k, linux-wireless, linux-kernel
In-Reply-To: <b7f4c8f1a251ea9cccb32f021828896371953143.camel@sipsolutions.net>

On Thu, Mar 12, 2026 at 10:37:46AM +0100, Johannes Berg wrote:
> Hi,
> > For example, I use the `iw` tool to display the capabilities and their
> > descriptions. The code for that has the following function prototypes:
> > 
> >     * void print_ht_capability(__u16 cap);
> >     * void print_vht_info(__u32 capa, const __u8 *mcs);
> >     * static void __print_he_capa(const __u16 *mac_cap,
> >                                   const __u16 *phy_cap,
> >                                   const __u16 *mcs_set, size_t mcs_len,
> >                                   const __u8 *ppet, int ppet_len,
> >                                   bool indent);
> >     * static void __print_eht_capa(int band,
> >                                    const __u8 *mac_cap,
> >                                    const __u32 *phy_cap,
> >                                    const __u8 *mcs_set, size_t mcs_len,
> >                                    const __u8 *ppet, size_t ppet_len,
> >                                    const __u16 *he_phy_cap,
> >                                    bool indent);
> 
> This is perhaps a bit unfortunate, but note that the HE and EHT __u16
> and __u32 here are really little endian pointers, and the functions do
> byte-order conversion.

I don’t see this in the function. For example, the MAC capabilities are a
`u16 *` in CPU endianness, which is simply memcpy’d from the parsed
`NL80211_BAND_IFTYPE_ATTR_HE_CAP_MAC`. Later, they are treated as `u16 *`,
as shown in the following code:

    printf("%s\t\tHE MAC Capabilities (0x", pre);
    for (i = 0; i < 3; i++)
        printf("%04x", mac_cap[i]);
    printf("):\n");

Here is the result on little‑ vs. big‑endian platforms:

    Little endian:
    HE MAC Capabilities (0x081a010d030f):

    Big endian:
    HE MAC Capabilities (0x0b00189a4010):

For the PHY capabilities, they are also a `u16 *`, but they are treated as
a `u8 *`. However, later in the description printing, `PRINT_HE_CAP` does
not take endianness into account. This prints the correct hex values, but
the interpretation/description is wrong:

    Little endian:
    HE PHY Capabilities: (0x1c604c89ffdb839c110c00):
        HE40/HE80/5GHz
        HE160/5GHz
        HE160/HE80+80/5GHz
        LDPC Coding in Payload
        HE SU PPDU with 1x HE-LTF and 0.8us GI
        STBC Tx <= 80MHz
        STBC Rx <= 80MHz
        Full Bandwidth UL MU-MIMO
        DCM Max Constellation: 1
        DCM Max Constellation Rx: 1
        SU Beamformer
        SU Beamformee
        MU Beamformer
        Beamformee STS <= 80Mhz: 7
        Beamformee STS > 80Mhz: 7
        Sounding Dimensions <= 80Mhz: 3
        Sounding Dimensions > 80Mhz: 3
        Ng = 16 SU Feedback
        Ng = 16 MU Feedback
        Codebook Size SU Feedback
        Codebook Size MU Feedback
        PPE Threshold Present
        HE SU PPDU & HE PPDU 4x HE-LTF 0.8us GI
        Max NC: 3
        STBC Rx > 80MHz
        HE ER SU PPDU 4x HE-LTF 0.8us GI
        HE ER SU PPDU 1x HE-LTF 0.8us GI
        TX 1024-QAM
        RX 1024-QAM

    Big endian:
    HE PHY Capabilities: (0x1c604c89ffda839c110c00):
        Punctured Preamble RX: 12
        HE SU PPDU with 1x HE-LTF and 0.8us GI
        Doppler Rx
        Full Bandwidth UL MU-MIMO
        DCM Max Constellation: 3
        DCM Max NSS Tx: 1
        DCM Max Constellation Rx: 3
        DCM Max NSS Rx: 1
        Rx HE MU PPDU from Non-AP STA
        SU Beamformer
        SU Beamformee
        Beamformee STS <= 80Mhz: 2
        Beamformee STS > 80Mhz: 4
        Sounding Dimensions <= 80Mhz: 3
        Ng = 16 MU Feedback
        Codebook Size MU Feedback
        Triggered MU Beamforming Feedback
        Triggered CQI Feedback
        Partial Bandwidth DL MU-MIMO
        PPE Threshold Present
        SRP-based SR
        Max NC: 2
        20MHz in 160/80+80MHz HE PPDU
        80MHz in 160/80+80MHz HE PPDU
        HE ER SU PPDU 1x HE-LTF 0.8us GI
        DCM Max BW: 2

> >     struct ieee80211_sta_ht_cap {
> >         u16 cap; /* use IEEE80211_HT_CAP_ */
> >         bool ht_supported;
> >         u8 ampdu_factor;
> >         u8 ampdu_density;
> >         struct ieee80211_mcs_info mcs;
> >     };
> > 
> >     struct ieee80211_sta_vht_cap {
> >         bool vht_supported;
> >         u32 cap; /* use IEEE80211_VHT_CAP_ */
> >         struct ieee80211_vht_mcs_info vht_mcs;
> >     };
> > 
> > The structs for HT and VHT use `u16` and `u32` data types for the `cap`
> > variable, matching what `iw` does. That part is consistent.
> 
> Careful. There are different structs used in different places, notably
> HT/VHT and HE/EHT differ.
> 
> For HT and VHT, look at the start of nl80211_send_band_rateinfo(), which
> sends themas individual attributes, defined in enum nl80211_band_attr,
> and the values that are u16 (NL80211_BAND_ATTR_HT_CAPA) or u32
> (NL80211_BAND_ATTR_VHT_CAPA) are in host byte order, though both are
> actually documented misleadingly ("as in [V]HT information IE" is just
> all around wrong.)
> 
> For HE/EHT, you have it in nl80211_send_iftype_data() since it's per
> interface type, and all the individual values are just as they appear in
> the spec, regardless of their size.

Okay, I think I understand so far. I had also initially wondered why HE/EHT
capabilities were treated separately from the HT/VHT capabilities.

> Note that spec is generally in little endian, but sometimes has strange
> field lengths like MAC capabilities being 6 bytes in HE:
> 
> >     struct ieee80211_he_cap_elem {
> >         u8 mac_cap_info[6];
> >         u8 phy_cap_info[11];
> >     } __packed;
> > 
> >     struct ieee80211_he_6ghz_capa {
> >         /* uses IEEE80211_HE_6GHZ_CAP_* below */
> >         __le16 capa; }
> >     __packed;
> > 
> > However, for HE the types differ from the `iw` implementation. Here, `u8`
> > arrays are used instead of `u16` for MAC and PHY capabilities. The 6 GHz
> > capabilities use `u16`, which is also different.
> 
> That doesn't really matter, they're just a set of 6 or 11 bytes, and
> e.g. the HE MAC capabilities are treated by the kernel as a set of 6
> bytes, but by iw as a set of 3 __le16, which results in the same
> interpretation, or at least should.

Sure. I agree with you, it makes no difference whether I use `u8[6]` or
`__le16[3]`, as long as I use `memcpy` and don’t perform any CPU‑endian
swapping at this point.

> >     struct ieee80211_eht_cap_elem_fixed {
> >         u8 mac_cap_info[2];
> >         u8 phy_cap_info[9];
> >     } __packed;
> > 
> > For EHT, `u8` arrays are also used for both MAC and PHY caps, instead of
> > `u32` for the PHY caps as in the `iw` implementation.
> 
> Same thing here.
> 
> > The current `ath12k` implementation always uses `u32` values, which does
> > not work on big‑endian platforms:
> 
> Yeah, that seems problematic and not really fitting for something that's
> 6, 11, 2 or 9 bytes long?
> 
> > I want to address and fix this issue. However, I cannot apply the “never
> > break the userspace” rule here, as it seems, it is already broken.
> 
> I don't think it's broken, why do you say so?

Well, if `ath12k` uses `u32` in CPU‑native order, that’s a bug, and I can’t
get `ieee80211_hw` registered. If I use `__le32` in little-endian order
instead, I end up with incorrect capabilities and mismatched descriptions
shown by the `iw` tool (but I can get the driver running). So neither
approach seems to be a 100% solution at first glance. Did I misinterpret
the rule?

> What's (clearly) broken is how ath12k puts the data into the HE/EHT
> structs that the kernel expects, but per your dmesg:
> 
> >     ath12k_pci 0001:01:00.0: ieee80211 registration failed: -22
> >     ath12k_pci 0001:01:00.0: failed register the radio with mac80211: -22
> 
> it seems that even mac80211 doesn't like the capabilities, so the byte
> order issue already exists there.
> 
> It seems to me the issue is that ath12k_band_cap is in u32, converted,
> but then memcpy()d.

The `ath12k` driver uses `u32` arrays in CPU‑native order for this, so the
swap is effectively happening. Later, in `ieee80211_register_hw`, the
values are compared at the bit level, and that’s where it fails. I
understand that technically `__le32` could be used in `ath12k`, meaning no
swap, but since `u8` arrays are used in `cfg80211`, that might actually be
the better approach. I just wanted to provide a solution that is clean and
acceptable. The `iw` tool still needs to be updated accordingly.


Best regards
Alexander Wilhelm

^ permalink raw reply

* Re: ath12k: handling of HE and EHT capabilities
From: Johannes Berg @ 2026-03-12 11:05 UTC (permalink / raw)
  To: Alexander Wilhelm; +Cc: Jeff Johnson, ath12k, linux-wireless, linux-kernel
In-Reply-To: <abKbLNK0YrT6dr96@FUE-ALEWI-WINX>

On Thu, 2026-03-12 at 11:53 +0100, Alexander Wilhelm wrote:
> On Thu, Mar 12, 2026 at 10:37:46AM +0100, Johannes Berg wrote:
> > Hi,
> > > For example, I use the `iw` tool to display the capabilities and their
> > > descriptions. The code for that has the following function prototypes:
> > > 
> > >     * void print_ht_capability(__u16 cap);
> > >     * void print_vht_info(__u32 capa, const __u8 *mcs);
> > >     * static void __print_he_capa(const __u16 *mac_cap,
> > >                                   const __u16 *phy_cap,
> > >                                   const __u16 *mcs_set, size_t mcs_len,
> > >                                   const __u8 *ppet, int ppet_len,
> > >                                   bool indent);
> > >     * static void __print_eht_capa(int band,
> > >                                    const __u8 *mac_cap,
> > >                                    const __u32 *phy_cap,
> > >                                    const __u8 *mcs_set, size_t mcs_len,
> > >                                    const __u8 *ppet, size_t ppet_len,
> > >                                    const __u16 *he_phy_cap,
> > >                                    bool indent);
> > 
> > This is perhaps a bit unfortunate, but note that the HE and EHT __u16
> > and __u32 here are really little endian pointers, and the functions do
> > byte-order conversion.
> 
> I don’t see this in the function. For example, the MAC capabilities are a
> `u16 *` in CPU endianness, which is simply memcpy’d from the parsed
> `NL80211_BAND_IFTYPE_ATTR_HE_CAP_MAC`. Later, they are treated as `u16 *`,
> as shown in the following code:
> 
>     printf("%s\t\tHE MAC Capabilities (0x", pre);
>     for (i = 0; i < 3; i++)
>         printf("%04x", mac_cap[i]);
>     printf("):\n");
> 
> Here is the result on little‑ vs. big‑endian platforms:
> 
>     Little endian:
>     HE MAC Capabilities (0x081a010d030f):
> 
>     Big endian:
>     HE MAC Capabilities (0x0b00189a4010):

Oh, OK, so _that_ print is definitely wrong in iw. But the individual
prints are converted:

        #define PRINT_HE_CAP(_var, _idx, _bit, _str) \
        do { \
                if (le16toh(_var[_idx]) & BIT(_bit)) \
                        printf("%s\t\t\t" _str "\n", pre); \
        } while (0)

> For the PHY capabilities, they are also a `u16 *`, but they are treated as
> a `u8 *`. However, later in the description printing, `PRINT_HE_CAP` does
> not take endianness into account.

PRINT_HE_CAP *does* convert, there's le16toh() there. Same in
PRINT_HE_CAP_MASK.

It should convert, because it's from a u8[6] kernel API that just
carries the values as they are in the spec.

> > > I want to address and fix this issue. However, I cannot apply the “never
> > > break the userspace” rule here, as it seems, it is already broken.
> > 
> > I don't think it's broken, why do you say so?

Well, I see now that I missed the

        printf("%s\t\tHE MAC Capabilities (0x", pre);
        for (i = 0; i < 3; i++)
                printf("%04x", le16toh(mac_cap[i]));

and

        printf("%s\t\tEHT MAC Capabilities (0x", pre);
        for (i = 0; i < 2; i++)
                printf("%02x", mac_cap[i]);

parts, those are definitely broken in iw on big endian platforms. We
should fix those in iw. The actual individual prints seem fine though.

> Well, if `ath12k` uses `u32` in CPU‑native order, that’s a bug, and I can’t
> get `ieee80211_hw` registered. If I use `__le32` in little-endian order
> instead, I end up with incorrect capabilities and mismatched descriptions
> shown by the `iw` tool (but I can get the driver running). So neither
> approach seems to be a 100% solution at first glance. Did I misinterpret
> the rule?

The *descriptions* should be fine I think? Just the first line with the
hex would be messed up.

> > What's (clearly) broken is how ath12k puts the data into the HE/EHT
> > structs that the kernel expects, but per your dmesg:
> > 
> > >     ath12k_pci 0001:01:00.0: ieee80211 registration failed: -22
> > >     ath12k_pci 0001:01:00.0: failed register the radio with mac80211: -22
> > 
> > it seems that even mac80211 doesn't like the capabilities, so the byte
> > order issue already exists there.
> > 
> > It seems to me the issue is that ath12k_band_cap is in u32, converted,
> > but then memcpy()d.
> 
> The `ath12k` driver uses `u32` arrays in CPU‑native order for this, so the
> swap is effectively happening.

Yeah but the swap is wrong, since HE/EHT capabilities are just byte
arrays in spec byte order in cfg80211/nl80211.

>  Later, in `ieee80211_register_hw`, the
> values are compared at the bit level, and that’s where it fails. I
> understand that technically `__le32` could be used in `ath12k`, meaning no
> swap, but since `u8` arrays are used in `cfg80211`, that might actually be
> the better approach.

Sure, could use u8 in ath12k too, dunno, up to the maintainer. At least
if it was __le32 you could still memcpy() from it since no swap
happened, and wouldn't change the code structure that much.

johannes

^ permalink raw reply

* Re: [PATCH mt76 6/6] wifi: mt76: mt7996: fix issues with manually triggered radar detection
From: Lorenzo Bianconi @ 2026-03-12 10:56 UTC (permalink / raw)
  To: Shayne Chen
  Cc: Felix Fietkau, linux-wireless, Ryder Lee, Evelyn Tsai, Money Wang,
	linux-mediatek, StanleyYP Wang
In-Reply-To: <20260312095724.2117448-6-shayne.chen@mediatek.com>

[-- Attachment #1: Type: text/plain, Size: 2250 bytes --]

> From: StanleyYP Wang <StanleyYP.Wang@mediatek.com>
> 
> Disallow triggering radar detection on non-DFS channels to prevent paused
> TX queues from failing to resume, as a channel switch is not performed in
> this case.

I guess we are missing a Fixes tag here.

> 
> Signed-off-by: StanleyYP Wang <StanleyYP.Wang@mediatek.com>
> Signed-off-by: Shayne Chen <shayne.chen@mediatek.com>
> ---
>  .../wireless/mediatek/mt76/mt7996/debugfs.c   | 22 +++++++++++++++----
>  1 file changed, 18 insertions(+), 4 deletions(-)
> 
> diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/debugfs.c b/drivers/net/wireless/mediatek/mt76/mt7996/debugfs.c
> index 76d623b2cafb..e26bed6b97e7 100644
> --- a/drivers/net/wireless/mediatek/mt76/mt7996/debugfs.c
> +++ b/drivers/net/wireless/mediatek/mt76/mt7996/debugfs.c
> @@ -226,14 +226,23 @@ mt7996_radar_trigger(void *data, u64 val)
>  #define RADAR_BACKGROUND	2
>  	struct mt7996_dev *dev = data;
>  	struct mt7996_phy *phy = mt7996_band_phy(dev, NL80211_BAND_5GHZ);
> -	int rdd_idx;
> +	struct cfg80211_chan_def *chandef;
> +	int rdd_idx, ret;
>  
>  	if (!phy || !val || val > RADAR_BACKGROUND)
>  		return -EINVAL;
>  
> -	if (val == RADAR_BACKGROUND && !dev->rdd2_phy) {
> -		dev_err(dev->mt76.dev, "Background radar is not enabled\n");
> -		return -EINVAL;
> +	if (test_bit(MT76_SCANNING, &phy->mt76->state))
> +		return -EBUSY;
> +
> +	if (val == RADAR_BACKGROUND) {
> +		if (!dev->rdd2_phy || !cfg80211_chandef_valid(&dev->rdd2_chandef)) {
> +			dev_err(dev->mt76.dev, "Background radar is not enabled\n");

nit: I guess it is better to specify a different error message if rdd2_chandef
is  invalid.

Regards,
Lorenzo

> +			return -EINVAL;
> +		}
> +		chandef = &dev->rdd2_chandef;
> +	} else {
> +		chandef = &phy->mt76->chandef;
>  	}
>  
>  	rdd_idx = mt7996_get_rdd_idx(phy, val == RADAR_BACKGROUND);
> @@ -242,6 +251,11 @@ mt7996_radar_trigger(void *data, u64 val)
>  		return -EINVAL;
>  	}
>  
> +	ret = cfg80211_chandef_dfs_required(dev->mt76.hw->wiphy, chandef,
> +					    NL80211_IFTYPE_AP);
> +	if (ret <= 0)
> +		return ret;
> +
>  	return mt7996_mcu_rdd_cmd(dev, RDD_RADAR_EMULATE, rdd_idx, 0);
>  }
>  
> -- 
> 2.51.0
> 

[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 228 bytes --]

^ permalink raw reply

* Re: [PATCH mt76 5/6] wifi: mt76: mt7996: fix the temporary buffer for calibration-free data
From: Lorenzo Bianconi @ 2026-03-12 10:49 UTC (permalink / raw)
  To: Shayne Chen
  Cc: Felix Fietkau, linux-wireless, Ryder Lee, Evelyn Tsai, Money Wang,
	linux-mediatek, StanleyYP Wang
In-Reply-To: <20260312095724.2117448-5-shayne.chen@mediatek.com>

[-- Attachment #1: Type: text/plain, Size: 1939 bytes --]

> From: StanleyYP Wang <StanleyYP.Wang@mediatek.com>
> 
> Move the declaration of buf[] outside the for loop.
> 
> Fixes: 224c7c2be578 ("wifi: mt76: mt7996: apply calibration-free data from OTP")
> Signed-off-by: StanleyYP Wang <StanleyYP.Wang@mediatek.com>
> Signed-off-by: Shayne Chen <shayne.chen@mediatek.com>
> ---
>  drivers/net/wireless/mediatek/mt76/mt7996/mcu.c | 3 ++-
>  1 file changed, 2 insertions(+), 1 deletion(-)
> 
> diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/mcu.c b/drivers/net/wireless/mediatek/mt76/mt7996/mcu.c
> index 2a9c59d15894..4c733f51a03e 100644
> --- a/drivers/net/wireless/mediatek/mt76/mt7996/mcu.c
> +++ b/drivers/net/wireless/mediatek/mt76/mt7996/mcu.c
> @@ -4104,6 +4104,7 @@ mt7996_mcu_get_cal_free_data(struct mt7996_dev *dev)
>  	}
>  
>  	for (band = 0; band < __MT_MAX_BAND; band++) {
> +		u8 buf[MT7996_EEPROM_BLOCK_SIZE];

why are you moving buf here? It is only used in the inner block.

>  		const struct cal_free_data *cal;
>  		u16 prev_block_idx = -1;
>  		u16 adie_base;
> @@ -4126,13 +4127,13 @@ mt7996_mcu_get_cal_free_data(struct mt7996_dev *dev)
>  			u16 eep_offset = cal[i].eep_offs;
>  			u16 block_idx = adie_offset / MT7996_EEPROM_BLOCK_SIZE;
>  			u16 offset = adie_offset % MT7996_EEPROM_BLOCK_SIZE;
> -			u8 buf[MT7996_EEPROM_BLOCK_SIZE];

I think we should always define and initialize buf array here, otherwise we
could use it with uninitialized values. Something like:

			u8 buf[MT7996_EEPROM_BLOCK_SIZE] = {};

Regards,
Lorenzo

>  
>  			if (is_mt7996(&dev->mt76) && band == MT_BAND1 &&
>  			    dev->var.type == MT7996_VAR_TYPE_444)
>  				eep_offset -= MT_EE_7977BN_OFFSET;
>  
>  			if (prev_block_idx != block_idx) {
> +				memset(buf, 0, sizeof(buf));
>  				ret = mt7996_mcu_get_eeprom(dev, adie_offset, buf,
>  							    MT7996_EEPROM_BLOCK_SIZE,
>  							    EEPROM_MODE_EFUSE);
> -- 
> 2.51.0
> 

[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 228 bytes --]

^ permalink raw reply

* Re: [PATCH mt76 4/6] wifi: mt76: mt7996: adjust timeout value for boot-up calibration commands
From: Lorenzo Bianconi @ 2026-03-12 10:43 UTC (permalink / raw)
  To: Shayne Chen
  Cc: Felix Fietkau, linux-wireless, Ryder Lee, Evelyn Tsai, Money Wang,
	linux-mediatek, Rex Lu
In-Reply-To: <20260312095724.2117448-4-shayne.chen@mediatek.com>

[-- Attachment #1: Type: text/plain, Size: 1189 bytes --]

> From: Rex Lu <rex.lu@mediatek.com>
> 
> Align the vendor driver by adjusting the timeout values for the
> MCU_UNI_CMD_EFUSE_CTRL and MCU_UNI_CMD_EXT_EEPROM_CTRL commands.
> Without this adjustment, false positive command timeout errors may occur,
> especially on some iPA variants.

I guess we are missing a Fixes tag here.

Regards,
Lorenzo

> 
> Signed-off-by: Rex Lu <rex.lu@mediatek.com>
> Signed-off-by: Shayne Chen <shayne.chen@mediatek.com>
> ---
>  drivers/net/wireless/mediatek/mt76/mt7996/mcu.c | 3 ++-
>  1 file changed, 2 insertions(+), 1 deletion(-)
> 
> diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/mcu.c b/drivers/net/wireless/mediatek/mt76/mt7996/mcu.c
> index bdb9e30c54c1..2a9c59d15894 100644
> --- a/drivers/net/wireless/mediatek/mt76/mt7996/mcu.c
> +++ b/drivers/net/wireless/mediatek/mt76/mt7996/mcu.c
> @@ -271,7 +271,8 @@ mt7996_mcu_set_timeout(struct mt76_dev *mdev, int cmd)
>  		mdev->mcu.timeout = 2 * HZ;
>  		return;
>  	case MCU_UNI_CMD_EFUSE_CTRL:
> -		mdev->mcu.timeout = 20 * HZ;
> +	case MCU_UNI_CMD_EXT_EEPROM_CTRL:
> +		mdev->mcu.timeout = 30 * HZ;
>  		return;
>  	default:
>  		break;
> -- 
> 2.51.0
> 

[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 228 bytes --]

^ permalink raw reply

* Re: [PATCH mt76 3/6] wifi: mt76: mt7996: update WFSYS reset flow for MT7990 chipsets
From: Lorenzo Bianconi @ 2026-03-12 10:42 UTC (permalink / raw)
  To: Shayne Chen
  Cc: Felix Fietkau, linux-wireless, Ryder Lee, Evelyn Tsai, Money Wang,
	linux-mediatek, Peter Chiu
In-Reply-To: <20260312095724.2117448-3-shayne.chen@mediatek.com>

[-- Attachment #1: Type: text/plain, Size: 3068 bytes --]

> From: Peter Chiu <chui-hao.chiu@mediatek.com>
> 
> Skip WFSYS reset during bootup for MT7990 chipsets; only reset if L0.5
> recovery is triggered.
> Without this fix, the following kernel error may occur:
> Internal error: synchronous external abort.

I guess we are missing a Fixes tag here.

Regards,
Lorenzo

> 
> Signed-off-by: Peter Chiu <chui-hao.chiu@mediatek.com>
> Signed-off-by: Shayne Chen <shayne.chen@mediatek.com>
> ---
>  .../net/wireless/mediatek/mt76/mt7996/init.c  | 29 +++++++++++++++++--
>  .../net/wireless/mediatek/mt76/mt7996/regs.h  |  8 +++++
>  2 files changed, 34 insertions(+), 3 deletions(-)
> 
> diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/init.c b/drivers/net/wireless/mediatek/mt76/mt7996/init.c
> index f3239f530aea..8dfb81eabc9a 100644
> --- a/drivers/net/wireless/mediatek/mt76/mt7996/init.c
> +++ b/drivers/net/wireless/mediatek/mt76/mt7996/init.c
> @@ -791,11 +791,34 @@ static void mt7996_init_work(struct work_struct *work)
>  
>  void mt7996_wfsys_reset(struct mt7996_dev *dev)
>  {
> -	mt76_set(dev, MT_WF_SUBSYS_RST, 0x1);
> -	msleep(20);
> +	if (!is_mt7990(&dev->mt76)) {
> +		mt76_set(dev, MT_WF_SUBSYS_RST, 0x1);
> +		msleep(20);
> +
> +		mt76_clear(dev, MT_WF_SUBSYS_RST, 0x1);
> +		msleep(20);
> +
> +		return;
> +	}
>  
> -	mt76_clear(dev, MT_WF_SUBSYS_RST, 0x1);
> +	if (!dev->recovery.hw_full_reset)
> +		return;
> +
> +	mt76_set(dev, MT_WF_SUBSYS_RST,
> +		 MT_WF_SUBSYS_RST_WHOLE_PATH_RST_REVERT |
> +		 MT_WF_SUBSYS_RST_BYPASS_WFDMA_SLP_PROT |
> +		 MT_WF_SUBSYS_RST_BYPASS_WFDMA2_SLP_PROT);
> +	mt76_rmw(dev, MT_WF_SUBSYS_RST,
> +		 MT_WF_SUBSYS_RST_WHOLE_PATH_RST_REVERT_CYCLE,
> +		 u32_encode_bits(0x20, MT_WF_SUBSYS_RST_WHOLE_PATH_RST_REVERT_CYCLE));
> +	mt76_clear(dev, MT_WF_L05_RST, MT_WF_L05_RST_WF_RST_MASK);
> +	mt76_set(dev, MT_WF_SUBSYS_RST, MT_WF_SUBSYS_RST_WHOLE_PATH_RST);
>  	msleep(20);
> +
> +	if (mt76_poll(dev, MT_WF_L05_RST, MT_WF_L05_RST_WF_RST_MASK, 0x1a, 1000))
> +		return;
> +
> +	dev_err(dev->mt76.dev, "wfsys reset fail\n");
>  }
>  
>  static void mt7996_rro_hw_init_v3(struct mt7996_dev *dev)
> diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/regs.h b/drivers/net/wireless/mediatek/mt76/mt7996/regs.h
> index 393faae2d52b..c6379933b6c3 100644
> --- a/drivers/net/wireless/mediatek/mt76/mt7996/regs.h
> +++ b/drivers/net/wireless/mediatek/mt76/mt7996/regs.h
> @@ -736,7 +736,15 @@ enum offs_rev {
>  #define MT_HW_REV				0x70010204
>  #define MT_HW_REV1				0x8a00
>  
> +#define MT_WF_L05_RST				0x70028550
> +#define MT_WF_L05_RST_WF_RST_MASK		GENMASK(4, 0)
> +
>  #define MT_WF_SUBSYS_RST			0x70028600
> +#define MT_WF_SUBSYS_RST_WHOLE_PATH_RST		BIT(0)
> +#define MT_WF_SUBSYS_RST_WHOLE_PATH_RST_REVERT	BIT(5)
> +#define MT_WF_SUBSYS_RST_BYPASS_WFDMA_SLP_PROT	BIT(6)
> +#define MT_WF_SUBSYS_RST_BYPASS_WFDMA2_SLP_PROT	BIT(16)
> +#define MT_WF_SUBSYS_RST_WHOLE_PATH_RST_REVERT_CYCLE	GENMASK(15, 8)
>  
>  /* PCIE MAC */
>  #define MT_PCIE_MAC_BASE			0x74030000
> -- 
> 2.51.0
> 

[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 228 bytes --]

^ permalink raw reply

* Re: [PATCH mt76 2/6] wifi: mt76: mt7996: support critical packet mode for MT7990 chipsets
From: Lorenzo Bianconi @ 2026-03-12 10:42 UTC (permalink / raw)
  To: Shayne Chen
  Cc: Felix Fietkau, linux-wireless, Ryder Lee, Evelyn Tsai, Money Wang,
	linux-mediatek, Howard Hsu
In-Reply-To: <20260312095724.2117448-2-shayne.chen@mediatek.com>

[-- Attachment #1: Type: text/plain, Size: 1156 bytes --]

> From: Howard Hsu <howard-yh.hsu@mediatek.com>
> 
> For MT7990 chipsets, critical packet mode must be enabled. Without this,
> some higher priority packets may be placed in the wrong AC queue.
> 
> Signed-off-by: Howard Hsu <howard-yh.hsu@mediatek.com>
> Signed-off-by: Shayne Chen <shayne.chen@mediatek.com>

I guess we are missing a Fixes tag here.

Regards,
Lorenzo

> ---
>  drivers/net/wireless/mediatek/mt76/mt7996/main.c | 2 +-
>  1 file changed, 1 insertion(+), 1 deletion(-)
> 
> diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/main.c b/drivers/net/wireless/mediatek/mt76/mt7996/main.c
> index e1e51c9a0767..d286dedddd9b 100644
> --- a/drivers/net/wireless/mediatek/mt76/mt7996/main.c
> +++ b/drivers/net/wireless/mediatek/mt76/mt7996/main.c
> @@ -56,7 +56,7 @@ static int mt7996_start(struct ieee80211_hw *hw)
>  
>  	mutex_lock(&dev->mt76.mutex);
>  	ret = mt7996_mcu_set_hdr_trans(dev, true);
> -	if (!ret && is_mt7992(&dev->mt76)) {
> +	if (!ret && !is_mt7996(&dev->mt76)) {
>  		u8 queue = mt76_connac_lmac_mapping(IEEE80211_AC_VI);
>  
>  		ret = mt7996_mcu_cp_support(dev, queue);
> -- 
> 2.51.0
> 

[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 228 bytes --]

^ permalink raw reply

* Re: [PATCH mt76 1/6] wifi: mt76: mt7996: fix RRO EMU configuration
From: Lorenzo Bianconi @ 2026-03-12 10:39 UTC (permalink / raw)
  To: Shayne Chen
  Cc: Felix Fietkau, linux-wireless, Ryder Lee, Evelyn Tsai, Money Wang,
	linux-mediatek, Peter Chiu
In-Reply-To: <20260312095724.2117448-1-shayne.chen@mediatek.com>

[-- Attachment #1: Type: text/plain, Size: 1983 bytes --]

> From: Peter Chiu <chui-hao.chiu@mediatek.com>
> 
> Use the correct helper to update specific bitfields instead of
> overwriting the entire register.
> 
> Fixes: eedb427eb260 ("wifi: mt76: mt7996: Enable HW RRO for MT7992 chipset")
> Signed-off-by: Peter Chiu <chui-hao.chiu@mediatek.com>
> Signed-off-by: Shayne Chen <shayne.chen@mediatek.com>

Acked-by: Lorenzo Bianconi <lorenzo@kernel.org>

> ---
>  drivers/net/wireless/mediatek/mt76/mt7996/init.c | 3 +--
>  drivers/net/wireless/mediatek/mt76/mt7996/mac.c  | 2 +-
>  2 files changed, 2 insertions(+), 3 deletions(-)
> 
> diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/init.c b/drivers/net/wireless/mediatek/mt76/mt7996/init.c
> index 5aaa93767109..f3239f530aea 100644
> --- a/drivers/net/wireless/mediatek/mt76/mt7996/init.c
> +++ b/drivers/net/wireless/mediatek/mt76/mt7996/init.c
> @@ -873,8 +873,7 @@ void mt7996_rro_hw_init(struct mt7996_dev *dev)
>  			}
>  		} else {
>  			/* set emul 3.0 function */
> -			mt76_wr(dev, MT_RRO_3_0_EMU_CONF,
> -				MT_RRO_3_0_EMU_CONF_EN_MASK);
> +			mt76_set(dev, MT_RRO_3_0_EMU_CONF, MT_RRO_3_0_EMU_CONF_EN_MASK);
>  
>  			mt76_wr(dev, MT_RRO_ADDR_ARRAY_BASE0,
>  				dev->wed_rro.addr_elem[0].phy_addr);
> diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/mac.c b/drivers/net/wireless/mediatek/mt76/mt7996/mac.c
> index a415cc610eee..86aaf0f29e28 100644
> --- a/drivers/net/wireless/mediatek/mt76/mt7996/mac.c
> +++ b/drivers/net/wireless/mediatek/mt76/mt7996/mac.c
> @@ -2559,7 +2559,7 @@ void mt7996_mac_reset_work(struct work_struct *work)
>  	mt7996_dma_start(dev, false, false);
>  
>  	if (!is_mt7996(&dev->mt76) && dev->mt76.hwrro_mode == MT76_HWRRO_V3)
> -		mt76_wr(dev, MT_RRO_3_0_EMU_CONF, MT_RRO_3_0_EMU_CONF_EN_MASK);
> +		mt76_set(dev, MT_RRO_3_0_EMU_CONF, MT_RRO_3_0_EMU_CONF_EN_MASK);
>  
>  	if (mtk_wed_device_active(&dev->mt76.mmio.wed)) {
>  		u32 wed_irq_mask = MT_INT_TX_DONE_BAND2 |
> -- 
> 2.51.0
> 

[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 228 bytes --]

^ permalink raw reply

* [PATCH 10/10] carl9170: phy: add periodic runtime IQ calibration
From: Masi Osmani @ 2026-03-12 10:38 UTC (permalink / raw)
  To: Christian Lamparter; +Cc: linux-wireless, Masi Osmani, ath9k-devel
In-Reply-To: <cover.1773277728.git.mas-i@hotmail.de>

Add periodic runtime I/Q calibration triggered from the existing
survey statistics work handler (carl9170_stat_work).  The AR9170
hardware performs initial IQ calibration during channel setup, but
I/Q imbalance drifts with temperature over time, degrading EVM
and increasing packet error rate.

The new carl9170_run_iq_calibration() function sets the DO_IQCAL
bit in PHY_TIMING_CTRL4 for both chains, which triggers the
hardware to re-measure I/Q imbalance and update the correction
coefficients automatically.  This is a non-blocking operation --
the hardware runs the calibration in the background without
interrupting normal traffic.

The ath9k driver performs similar periodic calibration via its
longcal timer (every 30s).  carl9170_stat_work runs at a
comparable interval, making it a natural trigger point.

Signed-off-by: Masi Osmani <mas-i@hotmail.de>
---
 drivers/net/wireless/ath/carl9170/carl9170.h |  1 +
 drivers/net/wireless/ath/carl9170/main.c     |  2 ++
 drivers/net/wireless/ath/carl9170/phy.c      | 36 ++++++++++++++++++++
 3 files changed, 39 insertions(+)

diff --git a/drivers/net/wireless/ath/carl9170/carl9170.h b/drivers/net/wireless/ath/carl9170/carl9170.h
index 2eedb2f..0175f8e 100644
--- a/drivers/net/wireless/ath/carl9170/carl9170.h
+++ b/drivers/net/wireless/ath/carl9170/carl9170.h
@@ -605,6 +605,7 @@ int carl9170_set_channel(struct ar9170 *ar, struct ieee80211_channel *channel,
 			 enum nl80211_channel_type bw);
 int carl9170_get_noisefloor(struct ar9170 *ar);
 void carl9170_update_channel_maxpower(struct ar9170 *ar);
+int carl9170_run_iq_calibration(struct ar9170 *ar);
 
 /* FW */
 int carl9170_parse_firmware(struct ar9170 *ar);
diff --git a/drivers/net/wireless/ath/carl9170/main.c b/drivers/net/wireless/ath/carl9170/main.c
index ebf9fa9..50c0922 100644
--- a/drivers/net/wireless/ath/carl9170/main.c
+++ b/drivers/net/wireless/ath/carl9170/main.c
@@ -910,6 +910,8 @@ static void carl9170_stat_work(struct work_struct *work)
 
 	mutex_lock(&ar->mutex);
 	err = carl9170_update_survey(ar, false, true);
+	if (!err)
+		carl9170_run_iq_calibration(ar);
 	mutex_unlock(&ar->mutex);
 
 	if (err)
diff --git a/drivers/net/wireless/ath/carl9170/phy.c b/drivers/net/wireless/ath/carl9170/phy.c
index c294df7..b145e9e 100644
--- a/drivers/net/wireless/ath/carl9170/phy.c
+++ b/drivers/net/wireless/ath/carl9170/phy.c
@@ -1637,6 +1637,42 @@ void carl9170_update_channel_maxpower(struct ar9170 *ar)
 	}
 }
 
+int carl9170_run_iq_calibration(struct ar9170 *ar)
+{
+	u32 val;
+	int err;
+
+	if (!ar->channel)
+		return 0;
+
+	/*
+	 * Trigger runtime IQ calibration.  The hardware measures
+	 * I/Q imbalance and updates the correction coefficients
+	 * automatically when DO_IQCAL is set.  We trigger on both
+	 * chains and re-enable the IQ correction afterwards.
+	 */
+	err = carl9170_read_reg(ar, AR9170_PHY_REG_TIMING_CTRL4(0), &val);
+	if (err)
+		return err;
+
+	val |= AR9170_PHY_TIMING_CTRL4_DO_IQCAL;
+	err = carl9170_write_reg(ar, AR9170_PHY_REG_TIMING_CTRL4(0), val);
+	if (err)
+		return err;
+
+	/* chain 2 */
+	err = carl9170_read_reg(ar, AR9170_PHY_REG_TIMING_CTRL4(2), &val);
+	if (err)
+		return err;
+
+	val |= AR9170_PHY_TIMING_CTRL4_DO_IQCAL;
+	err = carl9170_write_reg(ar, AR9170_PHY_REG_TIMING_CTRL4(2), val);
+	if (err)
+		return err;
+
+	return 0;
+}
+
 static int carl9170_set_radar_detection(struct ar9170 *ar,
 					struct ieee80211_channel *channel)
 {
-- 
2.51.0


^ permalink raw reply related

* [PATCH 09/10] carl9170: fw: enable DFS radar detection
From: Masi Osmani @ 2026-03-12 10:38 UTC (permalink / raw)
  To: Christian Lamparter; +Cc: linux-wireless, Masi Osmani, ath9k-devel
In-Reply-To: <cover.1773277728.git.mas-i@hotmail.de>

Enable DFS (Dynamic Frequency Selection) radar detection on the
AR9170.  The hardware has radar detection registers (RADAR_0,
RADAR_1, RADAR_EXT) and the firmware already sends
CARL9170_RSP_RADAR events, but the driver never programmed the
detection parameters and only logged a "please report" message.

Changes:
- Program radar detection pulse parameters in phy.c when the
  current channel has IEEE80211_CHAN_RADAR set.  Values are
  based on ath9k defaults for FCC/ETSI compliance.
- Advertise radar_detect_widths in the interface combination
  (fw.c) for 20 MHz noHT, 20 MHz HT, and 40 MHz HT.
- Replace the old "please report" message with a call to
  ieee80211_radar_detected() so mac80211 can trigger the
  proper DFS state machine (channel switch / CAC).

Signed-off-by: Masi Osmani <mas-i@hotmail.de>
---
 drivers/net/wireless/ath/carl9170/fw.c  |  3 ++
 drivers/net/wireless/ath/carl9170/phy.c | 45 +++++++++++++++++++++++++
 drivers/net/wireless/ath/carl9170/rx.c  |  7 ++--
 3 files changed, 50 insertions(+), 5 deletions(-)

diff --git a/drivers/net/wireless/ath/carl9170/fw.c b/drivers/net/wireless/ath/carl9170/fw.c
index 419f553..a730593 100644
--- a/drivers/net/wireless/ath/carl9170/fw.c
+++ b/drivers/net/wireless/ath/carl9170/fw.c
@@ -215,6 +215,9 @@ static void carl9170_fw_set_if_combinations(struct ar9170 *ar,
 	ar->if_combs[0].max_interfaces = ar->fw.vif_num;
 	ar->if_combs[0].limits = ar->if_comb_limits;
 	ar->if_combs[0].n_limits = ARRAY_SIZE(ar->if_comb_limits);
+	ar->if_combs[0].radar_detect_widths = BIT(NL80211_CHAN_WIDTH_20_NOHT) |
+					      BIT(NL80211_CHAN_WIDTH_20) |
+					      BIT(NL80211_CHAN_WIDTH_40);
 
 	ar->hw->wiphy->iface_combinations = ar->if_combs;
 	ar->hw->wiphy->n_iface_combinations = ARRAY_SIZE(ar->if_combs);
diff --git a/drivers/net/wireless/ath/carl9170/phy.c b/drivers/net/wireless/ath/carl9170/phy.c
index bcd9066..c294df7 100644
--- a/drivers/net/wireless/ath/carl9170/phy.c
+++ b/drivers/net/wireless/ath/carl9170/phy.c
@@ -1637,6 +1637,47 @@ void carl9170_update_channel_maxpower(struct ar9170 *ar)
 	}
 }
 
+static int carl9170_set_radar_detection(struct ar9170 *ar,
+					struct ieee80211_channel *channel)
+{
+	bool enable = channel->flags & IEEE80211_CHAN_RADAR;
+
+	carl9170_regwrite_begin(ar);
+
+	if (enable) {
+		/*
+		 * Configure radar detection pulse parameters.
+		 * Values based on ath9k's defaults for FCC/ETSI.
+		 */
+		carl9170_regwrite(AR9170_PHY_REG_RADAR_0,
+				  AR9170_PHY_RADAR_0_ENA |
+				  AR9170_PHY_RADAR_0_FFT_ENA |
+				  SET_CONSTVAL(AR9170_PHY_RADAR_0_INBAND, 5) |
+				  SET_CONSTVAL(AR9170_PHY_RADAR_0_PRSSI, 1) |
+				  SET_CONSTVAL(AR9170_PHY_RADAR_0_HEIGHT, 6) |
+				  SET_CONSTVAL(AR9170_PHY_RADAR_0_RRSSI, 12) |
+				  SET_CONSTVAL(AR9170_PHY_RADAR_0_FIRPWR, 33));
+
+		carl9170_regwrite(AR9170_PHY_REG_RADAR_1,
+				  AR9170_PHY_RADAR_1_MAX_RRSSI |
+				  AR9170_PHY_RADAR_1_BLOCK_CHECK |
+				  AR9170_PHY_RADAR_1_RELSTEP_CHECK |
+				  SET_CONSTVAL(AR9170_PHY_RADAR_1_RELSTEP_THRESH, 8) |
+				  SET_CONSTVAL(AR9170_PHY_RADAR_1_RELPWR_THRESH, 12) |
+				  SET_CONSTVAL(AR9170_PHY_RADAR_1_MAXLEN, 255));
+
+		carl9170_regwrite(AR9170_PHY_REG_RADAR_EXT,
+				  AR9170_PHY_RADAR_EXT_ENA);
+	} else {
+		carl9170_regwrite(AR9170_PHY_REG_RADAR_0, 0);
+		carl9170_regwrite(AR9170_PHY_REG_RADAR_1, 0);
+		carl9170_regwrite(AR9170_PHY_REG_RADAR_EXT, 0);
+	}
+
+	carl9170_regwrite_finish();
+	return carl9170_regwrite_result();
+}
+
 int carl9170_get_noisefloor(struct ar9170 *ar)
 {
 	static const u32 phy_regs[] = {
@@ -1739,6 +1780,10 @@ int carl9170_set_channel(struct ar9170 *ar, struct ieee80211_channel *channel,
 	if (err)
 		return err;
 
+	err = carl9170_set_radar_detection(ar, channel);
+	if (err)
+		return err;
+
 	tmp = AR9170_PHY_TURBO_FC_SINGLE_HT_LTF1 |
 	      AR9170_PHY_TURBO_FC_HT_EN;
 
diff --git a/drivers/net/wireless/ath/carl9170/rx.c b/drivers/net/wireless/ath/carl9170/rx.c
index bb909b5..1fe727c 100644
--- a/drivers/net/wireless/ath/carl9170/rx.c
+++ b/drivers/net/wireless/ath/carl9170/rx.c
@@ -259,11 +259,8 @@ void carl9170_handle_command_response(struct ar9170 *ar, void *buf, u32 len)
 		break;
 
 	case CARL9170_RSP_RADAR:
-		if (!net_ratelimit())
-			break;
-
-		wiphy_info(ar->hw->wiphy, "FW: RADAR! Please report this "
-		       "incident to linux-wireless@vger.kernel.org !\n");
+		wiphy_info(ar->hw->wiphy, "FW: radar pulse detected\n");
+		ieee80211_radar_detected(ar->hw, NULL);
 		break;
 
 	case CARL9170_RSP_GPIO:
-- 
2.51.0


^ permalink raw reply related

* [PATCH 08/10] carl9170: phy: enable antenna diversity for 2-chain devices
From: Masi Osmani @ 2026-03-12 10:38 UTC (permalink / raw)
  To: Christian Lamparter; +Cc: linux-wireless, Masi Osmani, ath9k-devel
In-Reply-To: <cover.1773277728.git.mas-i@hotmail.de>

Enable fast antenna diversity on devices with two RX chains
(rx_mask == 3).  This programs the MULTICHAIN_GAIN_CTL register
to configure main/alternate LNA settings and enables the
CCK fast antenna diversity bit.

The AR9170 hardware has antenna diversity registers inherited
from the AR9285/AR9287 family, but the carl9170 driver never
programmed them.  For dual-chain devices this improves receive
performance in multipath environments by allowing the baseband
to quickly switch between antenna paths.

The diversity configuration mirrors what ath9k uses for the
AR9285 single-chip design:
- Main LNA: LNA1
- Alt LNA: LNA1+LNA2 (combined)
- Gain table: table 0 for both paths

Signed-off-by: Masi Osmani <mas-i@hotmail.de>
---
 drivers/net/wireless/ath/carl9170/phy.c | 25 +++++++++++++++++++++++++
 1 file changed, 25 insertions(+)

diff --git a/drivers/net/wireless/ath/carl9170/phy.c b/drivers/net/wireless/ath/carl9170/phy.c
index 290c336..bcd9066 100644
--- a/drivers/net/wireless/ath/carl9170/phy.c
+++ b/drivers/net/wireless/ath/carl9170/phy.c
@@ -536,6 +536,31 @@ static int carl9170_init_phy_from_eeprom(struct ar9170 *ar,
 	carl9170_regwrite(AR9170_PHY_REG_RX_CHAINMASK, ar->eeprom.rx_mask);
 	carl9170_regwrite(AR9170_PHY_REG_CAL_CHAINMASK, ar->eeprom.rx_mask);
 
+	/*
+	 * Enable fast antenna diversity for 2-chain devices.
+	 * Configure main/alt LNA with both chains for best
+	 * multipath performance.
+	 */
+	if (ar->eeprom.rx_mask == 3) {
+		val = carl9170_def_val(AR9170_PHY_REG_MULTICHAIN_GAIN_CTL,
+				       is_2ghz, is_40mhz);
+		val |= AR9170_PHY_9285_ANT_DIV_CTL;
+		SET_VAL(AR9170_PHY_9285_ANT_DIV_ALT_LNACONF, val,
+			AR9170_PHY_9285_ANT_DIV_LNA1_PLUS_LNA2);
+		SET_VAL(AR9170_PHY_9285_ANT_DIV_MAIN_LNACONF, val,
+			AR9170_PHY_9285_ANT_DIV_LNA1);
+		SET_VAL(AR9170_PHY_9285_ANT_DIV_ALT_GAINTB, val,
+			AR9170_PHY_9285_ANT_DIV_GAINTB_0);
+		SET_VAL(AR9170_PHY_9285_ANT_DIV_MAIN_GAINTB, val,
+			AR9170_PHY_9285_ANT_DIV_GAINTB_0);
+		carl9170_regwrite(AR9170_PHY_REG_MULTICHAIN_GAIN_CTL, val);
+
+		val = carl9170_def_val(AR9170_PHY_REG_CCK_DETECT,
+				       is_2ghz, is_40mhz);
+		val |= AR9170_PHY_CCK_DETECT_BB_ENABLE_ANT_FAST_DIV;
+		carl9170_regwrite(AR9170_PHY_REG_CCK_DETECT, val);
+	}
+
 	carl9170_regwrite_finish();
 	return carl9170_regwrite_result();
 }
-- 
2.51.0


^ permalink raw reply related

* [PATCH 07/10] carl9170: main: add exponential restart backoff
From: Masi Osmani @ 2026-03-12 10:38 UTC (permalink / raw)
  To: Christian Lamparter; +Cc: linux-wireless, Masi Osmani, ath9k-devel
In-Reply-To: <cover.1773277728.git.mas-i@hotmail.de>

When the AR9170 enters a bad state (firmware errors, command
timeouts, TX queue stalls), the driver can trigger rapid-fire
restarts that prevent the device from ever stabilizing.

Add exponential backoff to carl9170_restart(): if a restart
request arrives before the current backoff window has elapsed,
the request is throttled.  The backoff starts at 500 ms and
doubles on each restart, capping at 30 seconds.  A successful
restart resets the backoff to zero.

Additionally, use named constants for the firmware error
threshold (CARL9170_FW_ERROR_THRESHOLD) instead of a magic
number, and add a window-based counting approach to avoid
accumulating sporadic errors over long uptimes.

Signed-off-by: Masi Osmani <mas-i@hotmail.de>
---
 drivers/net/wireless/ath/carl9170/carl9170.h |  2 ++
 drivers/net/wireless/ath/carl9170/main.c     | 27 ++++++++++++++++++++
 drivers/net/wireless/ath/carl9170/rx.c       | 10 +++++++-
 3 files changed, 38 insertions(+), 1 deletion(-)

diff --git a/drivers/net/wireless/ath/carl9170/carl9170.h b/drivers/net/wireless/ath/carl9170/carl9170.h
index a2ffa62..2eedb2f 100644
--- a/drivers/net/wireless/ath/carl9170/carl9170.h
+++ b/drivers/net/wireless/ath/carl9170/carl9170.h
@@ -301,6 +301,8 @@ struct ar9170 {
 	bool needs_full_reset;
 	bool force_usb_reset;
 	atomic_t pending_restarts;
+	unsigned long last_restart_jiffies;
+	unsigned int restart_backoff_ms;
 
 	/* interface mode settings */
 	struct list_head vif_list;
diff --git a/drivers/net/wireless/ath/carl9170/main.c b/drivers/net/wireless/ath/carl9170/main.c
index dcedcb1..ebf9fa9 100644
--- a/drivers/net/wireless/ath/carl9170/main.c
+++ b/drivers/net/wireless/ath/carl9170/main.c
@@ -492,6 +492,7 @@ static void carl9170_restart_work(struct work_struct *work)
 	if (!err && !ar->force_usb_reset) {
 		ar->restart_counter++;
 		atomic_set(&ar->pending_restarts, 0);
+		ar->restart_backoff_ms = 0;
 
 		ieee80211_restart_hw(ar->hw);
 	} else {
@@ -505,6 +506,9 @@ static void carl9170_restart_work(struct work_struct *work)
 	}
 }
 
+#define CARL9170_RESTART_BACKOFF_INIT_MS	500
+#define CARL9170_RESTART_BACKOFF_MAX_MS		30000
+
 void carl9170_restart(struct ar9170 *ar, const enum carl9170_restart_reasons r)
 {
 	carl9170_set_state_when(ar, CARL9170_STARTED, CARL9170_IDLE);
@@ -519,6 +523,29 @@ void carl9170_restart(struct ar9170 *ar, const enum carl9170_restart_reasons r)
 		return;
 	}
 
+	/*
+	 * Exponential backoff: if restarts are happening too frequently,
+	 * increase the delay before accepting the next one.  This prevents
+	 * restart storms when the device is in a bad state.
+	 */
+	if (ar->last_restart_jiffies &&
+	    time_before(jiffies, ar->last_restart_jiffies +
+			msecs_to_jiffies(ar->restart_backoff_ms))) {
+		dev_warn(&ar->udev->dev,
+			 "restart (%d) throttled (backoff %u ms)\n",
+			 r, ar->restart_backoff_ms);
+		atomic_dec(&ar->pending_restarts);
+		return;
+	}
+
+	ar->last_restart_jiffies = jiffies;
+	if (ar->restart_backoff_ms == 0)
+		ar->restart_backoff_ms = CARL9170_RESTART_BACKOFF_INIT_MS;
+	else
+		ar->restart_backoff_ms = min(ar->restart_backoff_ms * 2,
+					     (unsigned int)
+					     CARL9170_RESTART_BACKOFF_MAX_MS);
+
 	ieee80211_stop_queues(ar->hw);
 
 	dev_err(&ar->udev->dev, "restart device (%d)\n", r);
diff --git a/drivers/net/wireless/ath/carl9170/rx.c b/drivers/net/wireless/ath/carl9170/rx.c
index 414d499..bb909b5 100644
--- a/drivers/net/wireless/ath/carl9170/rx.c
+++ b/drivers/net/wireless/ath/carl9170/rx.c
@@ -46,6 +46,14 @@
 #include "hw.h"
 #include "cmd.h"
 
+/*
+ * Time window for firmware error counting.  Sporadic errors are
+ * normal over long uptimes; only a burst of errors within a short
+ * window warrants a restart.
+ */
+#define CARL9170_FW_ERROR_WINDOW_MS	10000
+#define CARL9170_FW_ERROR_THRESHOLD	3
+
 static void carl9170_dbg_message(struct ar9170 *ar, const char *buf, u32 len)
 {
 	bool restart = false;
@@ -54,7 +62,7 @@ static void carl9170_dbg_message(struct ar9170 *ar, const char *buf, u32 len)
 	if (len > 3) {
 		if (memcmp(buf, CARL9170_ERR_MAGIC, 3) == 0) {
 			ar->fw.err_counter++;
-			if (ar->fw.err_counter > 3) {
+			if (ar->fw.err_counter >= CARL9170_FW_ERROR_THRESHOLD) {
 				restart = true;
 				reason = CARL9170_RR_TOO_MANY_FIRMWARE_ERRORS;
 			}
-- 
2.51.0


^ permalink raw reply related

* [PATCH 06/10] carl9170: phy: populate per-channel TX power from EEPROM
From: Masi Osmani @ 2026-03-12 10:38 UTC (permalink / raw)
  To: Christian Lamparter; +Cc: linux-wireless, Masi Osmani, ath9k-devel
In-Reply-To: <cover.1773277728.git.mas-i@hotmail.de>

Replace the hardcoded max_power = 18 dBm (marked XXX) in the
channel definitions with actual per-channel values derived from
the EEPROM calibration target power tables.

The new carl9170_get_max_tgt_power() function interpolates the
maximum target power for a given frequency from the EEPROM's
legacy OFDM and CCK target power tables, using the same frequency
encoding and interpolation helpers already used by the power
calibration code.  carl9170_update_channel_maxpower() iterates
all registered channels and updates their max_power fields.

This is called during EEPROM parsing, so mac80211 and userspace
see correct per-channel power limits from the start.  The CHAN
macro default of 18 dBm remains as a safe fallback for channels
where EEPROM data is missing.

Signed-off-by: Masi Osmani <mas-i@hotmail.de>
---
 drivers/net/wireless/ath/carl9170/carl9170.h |  1 +
 drivers/net/wireless/ath/carl9170/main.c     |  4 +-
 drivers/net/wireless/ath/carl9170/phy.c      | 88 ++++++++++++++++++++
 3 files changed, 92 insertions(+), 1 deletion(-)

diff --git a/drivers/net/wireless/ath/carl9170/carl9170.h b/drivers/net/wireless/ath/carl9170/carl9170.h
index eaac859..a2ffa62 100644
--- a/drivers/net/wireless/ath/carl9170/carl9170.h
+++ b/drivers/net/wireless/ath/carl9170/carl9170.h
@@ -602,6 +602,7 @@ int carl9170_led_set_state(struct ar9170 *ar, const u32 led_state);
 int carl9170_set_channel(struct ar9170 *ar, struct ieee80211_channel *channel,
 			 enum nl80211_channel_type bw);
 int carl9170_get_noisefloor(struct ar9170 *ar);
+void carl9170_update_channel_maxpower(struct ar9170 *ar);
 
 /* FW */
 int carl9170_parse_firmware(struct ar9170 *ar);
diff --git a/drivers/net/wireless/ath/carl9170/main.c b/drivers/net/wireless/ath/carl9170/main.c
index d75688c..dcedcb1 100644
--- a/drivers/net/wireless/ath/carl9170/main.c
+++ b/drivers/net/wireless/ath/carl9170/main.c
@@ -89,7 +89,7 @@ struct ieee80211_rate __carl9170_ratetable[] = {
 #define CHAN(_freq, _idx) {		\
 	.center_freq	= (_freq),	\
 	.hw_value	= (_idx),	\
-	.max_power	= 18, /* XXX */	\
+	.max_power	= 18,		\
 }
 
 static struct ieee80211_channel carl9170_2ghz_chantable[] = {
@@ -1930,6 +1930,8 @@ static int carl9170_parse_eeprom(struct ar9170 *ar)
 	if (!bands)
 		return -EINVAL;
 
+	carl9170_update_channel_maxpower(ar);
+
 	ar->survey = devm_kcalloc(&ar->udev->dev, chans,
 				  sizeof(struct survey_info), GFP_KERNEL);
 	if (!ar->survey)
diff --git a/drivers/net/wireless/ath/carl9170/phy.c b/drivers/net/wireless/ath/carl9170/phy.c
index 34d9fd7..290c336 100644
--- a/drivers/net/wireless/ath/carl9170/phy.c
+++ b/drivers/net/wireless/ath/carl9170/phy.c
@@ -1524,6 +1524,94 @@ static void carl9170_set_power_cal(struct ar9170 *ar, u32 freq,
 	carl9170_calc_ctl(ar, freq, bw);
 }
 
+static u8 carl9170_get_max_tgt_power(struct ar9170 *ar, u32 freq)
+{
+	struct ar9170_calibration_target_power_legacy *ctpl;
+	int ntargets, idx, n, i;
+	u8 f, max_power = 0;
+	u8 pwr_freqs[AR5416_MAX_NUM_TGT_PWRS];
+
+	if (freq < 3000)
+		f = freq - 2300;
+	else
+		f = (freq - 4800) / 5;
+
+	/* check legacy target powers (OFDM for 2G, 5G leg) */
+	for (i = 0; i < 2; i++) {
+		switch (i) {
+		case 0:
+			if (freq >= 3000) {
+				ctpl = &ar->eeprom.cal_tgt_pwr_5G[0];
+				ntargets = AR5416_NUM_5G_TARGET_PWRS;
+			} else {
+				ctpl = &ar->eeprom.cal_tgt_pwr_2G_ofdm[0];
+				ntargets = AR5416_NUM_2G_OFDM_TARGET_PWRS;
+			}
+			break;
+		case 1:
+			if (freq < 3000) {
+				ctpl = &ar->eeprom.cal_tgt_pwr_2G_cck[0];
+				ntargets = AR5416_NUM_2G_CCK_TARGET_PWRS;
+			} else {
+				continue;
+			}
+			break;
+		default:
+			continue;
+		}
+
+		for (n = 0; n < ntargets; n++) {
+			if (ctpl[n].freq == 0xff)
+				break;
+			pwr_freqs[n] = ctpl[n].freq;
+		}
+		ntargets = n;
+		if (ntargets < 2)
+			continue;
+
+		idx = carl9170_find_freq_idx(ntargets, pwr_freqs, f);
+		for (n = 0; n < 4; n++) {
+			u8 pwr;
+
+			pwr = carl9170_interpolate_u8(f,
+						     ctpl[idx + 0].freq,
+						     ctpl[idx + 0].power[n],
+						     ctpl[idx + 1].freq,
+						     ctpl[idx + 1].power[n]);
+			max_power = max(max_power, pwr);
+		}
+	}
+
+	/* target power is in half-dBm, max_power is in dBm */
+	return max_power / 2;
+}
+
+void carl9170_update_channel_maxpower(struct ar9170 *ar)
+{
+	struct ieee80211_supported_band *band;
+	int i;
+
+	band = ar->hw->wiphy->bands[NL80211_BAND_2GHZ];
+	if (band) {
+		for (i = 0; i < band->n_channels; i++) {
+			u8 pwr = carl9170_get_max_tgt_power(ar,
+				band->channels[i].center_freq);
+			if (pwr)
+				band->channels[i].max_power = pwr;
+		}
+	}
+
+	band = ar->hw->wiphy->bands[NL80211_BAND_5GHZ];
+	if (band) {
+		for (i = 0; i < band->n_channels; i++) {
+			u8 pwr = carl9170_get_max_tgt_power(ar,
+				band->channels[i].center_freq);
+			if (pwr)
+				band->channels[i].max_power = pwr;
+		}
+	}
+}
+
 int carl9170_get_noisefloor(struct ar9170 *ar)
 {
 	static const u32 phy_regs[] = {
-- 
2.51.0


^ permalink raw reply related

* [PATCH 05/10] carl9170: rx: track PHY errors via debugfs
From: Masi Osmani @ 2026-03-12 10:38 UTC (permalink / raw)
  To: Christian Lamparter; +Cc: linux-wireless, Masi Osmani, ath9k-devel
In-Reply-To: <cover.1773277728.git.mas-i@hotmail.de>

Count PHY errors reported by the hardware in the RX status and
expose the counter through debugfs as rx_phy_errors.  Previously,
PHY errors from ar9170_rx_phystatus were silently ignored (marked
with a TODO comment).

The counter helps diagnose RF environment issues (interference,
multipath, low SNR) without requiring monitor mode or additional
tooling.

Signed-off-by: Masi Osmani <mas-i@hotmail.de>
---
 drivers/net/wireless/ath/carl9170/carl9170.h | 1 +
 drivers/net/wireless/ath/carl9170/debug.c    | 2 ++
 drivers/net/wireless/ath/carl9170/rx.c       | 4 +++-
 3 files changed, 6 insertions(+), 1 deletion(-)

diff --git a/drivers/net/wireless/ath/carl9170/carl9170.h b/drivers/net/wireless/ath/carl9170/carl9170.h
index ba29b4a..eaac859 100644
--- a/drivers/net/wireless/ath/carl9170/carl9170.h
+++ b/drivers/net/wireless/ath/carl9170/carl9170.h
@@ -386,6 +386,7 @@ struct ar9170 {
 	unsigned int tx_ack_failures;
 	unsigned int tx_fcs_errors;
 	unsigned int rx_dropped;
+	unsigned int rx_phy_errors;
 
 	/* EEPROM */
 	struct ar9170_eeprom eeprom;
diff --git a/drivers/net/wireless/ath/carl9170/debug.c b/drivers/net/wireless/ath/carl9170/debug.c
index 2d73456..0498df2 100644
--- a/drivers/net/wireless/ath/carl9170/debug.c
+++ b/drivers/net/wireless/ath/carl9170/debug.c
@@ -794,6 +794,7 @@ DEBUGFS_READONLY_FILE(tx_janitor_last_run, 64, "last run:%d ms ago",
 DEBUGFS_READONLY_FILE(tx_dropped, 20, "%d", ar->tx_dropped);
 
 DEBUGFS_READONLY_FILE(rx_dropped, 20, "%d", ar->rx_dropped);
+DEBUGFS_READONLY_FILE(rx_phy_errors, 20, "%d", ar->rx_phy_errors);
 
 DEBUGFS_READONLY_FILE(sniffer_enabled, 20, "%d", ar->sniffer_enabled);
 DEBUGFS_READONLY_FILE(rx_software_decryption, 20, "%d",
@@ -830,6 +831,7 @@ void carl9170_debugfs_register(struct ar9170 *ar)
 	DEBUGFS_ADD(tx_ampdu_list_len);
 
 	DEBUGFS_ADD(rx_dropped);
+	DEBUGFS_ADD(rx_phy_errors);
 	DEBUGFS_ADD(sniffer_enabled);
 	DEBUGFS_ADD(rx_software_decryption);
 
diff --git a/drivers/net/wireless/ath/carl9170/rx.c b/drivers/net/wireless/ath/carl9170/rx.c
index c664014..414d499 100644
--- a/drivers/net/wireless/ath/carl9170/rx.c
+++ b/drivers/net/wireless/ath/carl9170/rx.c
@@ -455,7 +455,9 @@ static void carl9170_rx_phy_status(struct ar9170 *ar,
 		if (phy->rssi[i] & 0x80)
 			phy->rssi[i] = ((~phy->rssi[i] & 0x7f) + 1) & 0x7f;
 
-	/* TODO: we could do something with phy_errors */
+	if (phy->phy_err)
+		ar->rx_phy_errors++;
+
 	status->signal = ar->noise[0] + phy->rssi_combined;
 }
 
-- 
2.51.0


^ permalink raw reply related

* [PATCH 04/10] carl9170: rx: wire up dropped frame counter
From: Masi Osmani @ 2026-03-12 10:37 UTC (permalink / raw)
  To: Christian Lamparter; +Cc: linux-wireless, Masi Osmani, ath9k-devel
In-Reply-To: <cover.1773277728.git.mas-i@hotmail.de>

Increment ar->rx_dropped when frames with unrecognized error
codes are dropped in carl9170_rx_mac_status().  The counter
was already defined in the ar9170 struct and exported via
debugfs, but the actual increment was missing -- only a TODO
comment was in place.

This provides visibility into receive-path frame drops through
the existing debugfs interface.

Signed-off-by: Masi Osmani <mas-i@hotmail.de>
---
 drivers/net/wireless/ath/carl9170/rx.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/drivers/net/wireless/ath/carl9170/rx.c b/drivers/net/wireless/ath/carl9170/rx.c
index 6833430..c664014 100644
--- a/drivers/net/wireless/ath/carl9170/rx.c
+++ b/drivers/net/wireless/ath/carl9170/rx.c
@@ -340,7 +340,7 @@ static int carl9170_rx_mac_status(struct ar9170 *ar,
 
 	/* drop any other error frames */
 	if (unlikely(error)) {
-		/* TODO: update netdevice's RX dropped/errors statistics */
+		ar->rx_dropped++;
 
 		if (net_ratelimit())
 			wiphy_dbg(ar->hw->wiphy, "received frame with "
-- 
2.51.0


^ permalink raw reply related

* [PATCH 03/10] carl9170: mac80211: document spatial multiplexing power save handler
From: Masi Osmani @ 2026-03-12 10:37 UTC (permalink / raw)
  To: Christian Lamparter; +Cc: linux-wireless, Masi Osmani, ath9k-devel
In-Reply-To: <cover.1773277728.git.mas-i@hotmail.de>

Replace the bare TODO comment in the SMPS configuration handler
with documentation explaining why the driver accepts but does not
act on SMPS mode changes.

The AR9170 advertises SM_PS disabled (both chains always active)
in its HT capabilities.  While mac80211 may still send SMPS
configuration requests, implementing static or dynamic SMPS would
require firmware support for per-chain enable/disable that the
AR9170 firmware (v1.9.9) does not provide.

Signed-off-by: Masi Osmani <mas-i@hotmail.de>
---
 drivers/net/wireless/ath/carl9170/main.c | 8 +++++++-
 1 file changed, 7 insertions(+), 1 deletion(-)

diff --git a/drivers/net/wireless/ath/carl9170/main.c b/drivers/net/wireless/ath/carl9170/main.c
index 6324b38..d75688c 100644
--- a/drivers/net/wireless/ath/carl9170/main.c
+++ b/drivers/net/wireless/ath/carl9170/main.c
@@ -910,7 +910,13 @@ static int carl9170_op_config(struct ieee80211_hw *hw, int radio_idx, u32 change
 	}
 
 	if (changed & IEEE80211_CONF_CHANGE_SMPS) {
-		/* TODO */
+		/*
+		 * We advertise SM_PS disabled (all chains active).
+		 * mac80211 may still request mode changes, which we
+		 * accept but only support OFF (both chains active).
+		 * Static/dynamic SMPS would require firmware support
+		 * for chain control that the AR9170 does not provide.
+		 */
 		err = 0;
 	}
 
-- 
2.51.0


^ permalink raw reply related

* [PATCH 02/10] carl9170: mac80211: advertise RX STBC capability
From: Masi Osmani @ 2026-03-12 10:37 UTC (permalink / raw)
  To: Christian Lamparter; +Cc: linux-wireless, Masi Osmani, ath9k-devel
In-Reply-To: <cover.1773277728.git.mas-i@hotmail.de>

The AR9170 baseband supports receiving Space-Time Block Coded
streams (1 spatial stream).  The capability was not advertised
in the HT capabilities, causing peers to never use STBC when
transmitting to us.  Enabling RX STBC improves receive diversity
gain and link reliability, especially in multipath environments.

The ath9k driver for the same chipset family advertises RX STBC
based on the number of RX chains.  With 2 RX chains, the AR9170
can decode 1 STBC spatial stream.

Signed-off-by: Masi Osmani <mas-i@hotmail.de>
---
 drivers/net/wireless/ath/carl9170/main.c | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/drivers/net/wireless/ath/carl9170/main.c b/drivers/net/wireless/ath/carl9170/main.c
index aa7e481..6324b38 100644
--- a/drivers/net/wireless/ath/carl9170/main.c
+++ b/drivers/net/wireless/ath/carl9170/main.c
@@ -156,7 +156,8 @@ static struct ieee80211_channel carl9170_5ghz_chantable[] = {
 			  IEEE80211_HT_CAP_SGI_40 |			\
 			  IEEE80211_HT_CAP_SGI_20 |			\
 			  IEEE80211_HT_CAP_DSSSCCK40 |			\
-			  IEEE80211_HT_CAP_SM_PS,			\
+			  IEEE80211_HT_CAP_SM_PS |			\
+			  (1 << IEEE80211_HT_CAP_RX_STBC_SHIFT),	\
 	.ampdu_factor	= IEEE80211_HT_MAX_AMPDU_64K,			\
 	.ampdu_density	= IEEE80211_HT_MPDU_DENSITY_8,			\
 	.mcs		= {						\
-- 
2.51.0


^ permalink raw reply related


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