Linux wireless drivers development
 help / color / mirror / Atom feed
* [syzbot] [wireless?] WARNING in cfg80211_chandef_create
From: syzbot @ 2026-03-17 17:43 UTC (permalink / raw)
  To: johannes, linux-kernel, linux-wireless, netdev, syzkaller-bugs

Hello,

syzbot found the following issue on:

HEAD commit:    b84a0ebe421c Add linux-next specific files for 20260313
git tree:       linux-next
console output: https://syzkaller.appspot.com/x/log.txt?x=11308216580000
kernel config:  https://syzkaller.appspot.com/x/.config?x=e7280ad1f68b2dce
dashboard link: https://syzkaller.appspot.com/bug?extid=d9f5fabbbcf4b377d01f
compiler:       Debian clang version 21.1.8 (++20251221033036+2078da43e25a-1~exp1~20251221153213.50), Debian LLD 21.1.8
syz repro:      https://syzkaller.appspot.com/x/repro.syz?x=1090c8da580000
C reproducer:   https://syzkaller.appspot.com/x/repro.c?x=179338da580000

Downloadable assets:
disk image: https://storage.googleapis.com/syzbot-assets/09145161a8a9/disk-b84a0ebe.raw.xz
vmlinux: https://storage.googleapis.com/syzbot-assets/b64c254e474c/vmlinux-b84a0ebe.xz
kernel image: https://storage.googleapis.com/syzbot-assets/a7c33f5f7f45/bzImage-b84a0ebe.xz

IMPORTANT: if you fix the issue, please add the following tag to the commit:
Reported-by: syzbot+d9f5fabbbcf4b377d01f@syzkaller.appspotmail.com

------------[ cut here ]------------
chan->band == NL80211_BAND_60GHZ || chan->band == NL80211_BAND_S1GHZ
WARNING: net/wireless/chan.c:35 at cfg80211_chandef_create+0x99/0x3d0 net/wireless/chan.c:34, CPU#1: syz.0.17/6021
Modules linked in:
CPU: 1 UID: 0 PID: 6021 Comm: syz.0.17 Not tainted syzkaller #0 PREEMPT(full) 
Hardware name: Google Google Compute Engine/Google Compute Engine, BIOS Google 02/12/2026
RIP: 0010:cfg80211_chandef_create+0x99/0x3d0 net/wireless/chan.c:34
Code: 8b 26 4c 89 e7 48 c7 c6 20 78 26 90 e8 60 be ad f6 49 83 fc 04 74 0d 41 83 fc 02 75 12 e8 cf b8 ad f6 eb 05 e8 c8 b8 ad f6 90 <0f> 0b 90 eb 05 e8 bd b8 ad f6 89 ef 48 c7 c6 40 78 26 90 e8 2f be
RSP: 0018:ffffc90003986f30 EFLAGS: 00010293
RAX: ffffffff8b193a38 RBX: ffffc900039870a0 RCX: ffff888032143d00
RDX: 0000000000000000 RSI: ffffffff90267820 RDI: 0000000000000004
RBP: 0000000000000002 R08: ffff888032143d00 R09: 0000000000000002
R10: 0000000000000004 R11: 0000000000000000 R12: 0000000000000004
R13: dffffc0000000000 R14: ffff888012a762f8 R15: ffffc900039870a8
FS:  0000555592955500(0000) GS:ffff888124ee0000(0000) knlGS:0000000000000000
CS:  0010 DS: 0000 ES: 0000 CR0: 0000000080050033
CR2: 00007fbf5f187600 CR3: 0000000077436000 CR4: 00000000003526f0
Call Trace:
 <TASK>
 _nl80211_parse_chandef+0x437/0x1300 net/wireless/nl80211.c:3637
 __nl80211_set_channel+0x248/0x880 net/wireless/nl80211.c:3761
 nl80211_set_wiphy+0x116b/0x2fa0 net/wireless/nl80211.c:-1
 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:2550
 genl_rcv+0x28/0x40 net/netlink/genetlink.c:1218
 netlink_unicast_kernel net/netlink/af_netlink.c:1318 [inline]
 netlink_unicast+0x80f/0x9b0 net/netlink/af_netlink.c:1344
 netlink_sendmsg+0x813/0xb40 net/netlink/af_netlink.c:1894
 sock_sendmsg_nosec+0x112/0x150 net/socket.c:796
 __sock_sendmsg net/socket.c:811 [inline]
 ____sys_sendmsg+0x589/0x8c0 net/socket.c:2668
 ___sys_sendmsg+0x2a5/0x360 net/socket.c:2722
 __sys_sendmsg net/socket.c:2754 [inline]
 __do_sys_sendmsg net/socket.c:2759 [inline]
 __se_sys_sendmsg net/socket.c:2757 [inline]
 __x64_sys_sendmsg+0x1bd/0x2a0 net/socket.c:2757
 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:0x7fbf5f19c799
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:00007ffe1eb2d0b8 EFLAGS: 00000246 ORIG_RAX: 000000000000002e
RAX: ffffffffffffffda RBX: 00007fbf5f415fa0 RCX: 00007fbf5f19c799
RDX: 0000000000000000 RSI: 0000200000000040 RDI: 0000000000000004
RBP: 00007fbf5f232c99 R08: 0000000000000000 R09: 0000000000000000
R10: 0000000000000000 R11: 0000000000000246 R12: 0000000000000000
R13: 00007fbf5f415fac R14: 00007fbf5f415fa0 R15: 00007fbf5f415fa0
 </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 syzbot to run the reproducer, reply with:
#syz test: git://repo/address.git branch-or-commit-hash
If you attach or paste a git patch, syzbot will apply it before testing.

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 v3 1/1] wifi: mt76: mt7921: fix txpower reporting from rate power configuration
From: Lucid Duck @ 2026-03-17 17:30 UTC (permalink / raw)
  To: linux-wireless
  Cc: nbd, sean.wang, lorenzo, linux-mediatek, morrownr, Lucid Duck
In-Reply-To: <20260317173016.136975-1-lucid_duck@justthetip.ca>

The mt7921 driver never updates phy->txpower_cur
when TX power rate configuration is sent to firmware. This causes
mt76_get_txpower() to report bogus values to userspace (typically
3 dBm) regardless of actual regulatory or SAR limits. User-set
txpower limits via iw are also not reflected.

Three root causes are addressed:

1. The rate power loop in mt76_connac_mcu_rate_txpower_band() computes
   the correct bounded TX power for each channel but discards the return
   value of mt76_get_rate_power_limits(). Fix: capture the return value
   and store it to phy->txpower_cur when processing the current channel.

2. mt7921 uses the chanctx model but its add_chanctx callback bypasses
   the common mt76_phy_update_channel(), leaving phy->chandef stale.
   Fix: update phy->chandef from ctx->def in both add_chanctx and
   change_chanctx, and trigger the rate power path to refresh
   txpower_cur. Also trigger on IEEE80211_CONF_CHANGE_CHANNEL in
   config(), matching mt7915.

3. For chanctx drivers, mac80211 routes user txpower changes through
   BSS_CHANGED_TXPOWER in bss_info_changed() -- not through
   IEEE80211_CONF_CHANGE_POWER in config(). hw->conf.power_level is
   never updated. Fix: handle BSS_CHANGED_TXPOWER in
   mt7921_bss_info_changed(), bridge bss_conf.txpower to
   hw->conf.power_level, and re-trigger the rate power path.

Tested on Alfa AWUS036AXML (MT7921AU), kernel 6.17.1-300.fc43:

  Before: iw dev wlan0 info shows "txpower 3.00 dBm" (wrong)
  After:  correct per-band values, user limits reflected

Test results (regulatory domain: Canada/CA):
  - 2.4GHz ch6:  33 dBm (30 dBm limit + 3 dBm 2x2 path delta)
  - 5GHz ch36:   26 dBm (23 dBm limit + 3 dBm path delta)
  - 6GHz ch5:    15 dBm (12 dBm limit + 3 dBm path delta)
  - Band switch: 100 cycles, 0 failures
  - Module reload: 50 cycles, 0 failures
  - 2-hour soak: 480 samples, zero drift
  - Regdomain switching: 10 countries, all correct
  - User txpower limits: reflected on all bands
  - Monitor mode: correct on all tested channels

Signed-off-by: Lucid Duck <lucid_duck@justthetip.ca>
---
 .../wireless/mediatek/mt76/mt76_connac_mcu.c  | 12 +++++++---
 .../net/wireless/mediatek/mt76/mt7921/main.c  | 22 ++++++++++++++++++-
 2 files changed, 30 insertions(+), 4 deletions(-)

diff --git a/drivers/net/wireless/mediatek/mt76/mt76_connac_mcu.c b/drivers/net/wireless/mediatek/mt76/mt76_connac_mcu.c
index 16db0f208..5856924a9 100644
--- a/drivers/net/wireless/mediatek/mt76/mt76_connac_mcu.c
+++ b/drivers/net/wireless/mediatek/mt76/mt76_connac_mcu.c
@@ -2193,14 +2193,20 @@ mt76_connac_mcu_rate_txpower_band(struct mt76_phy *phy,
 				.hw_value = ch_list[idx],
 				.band = band,
 			};
-			s8 reg_power, sar_power;
+			s8 reg_power, sar_power, max_power;
 
 			reg_power = mt76_connac_get_ch_power(phy, &chan,
 							     tx_power);
 			sar_power = mt76_get_sar_power(phy, &chan, reg_power);
 
-			mt76_get_rate_power_limits(phy, &chan, limits,
-						   sar_power);
+			max_power = mt76_get_rate_power_limits(phy, &chan,
+							       limits,
+							       sar_power);
+
+			if (phy->chandef.chan &&
+			    phy->chandef.chan->hw_value == ch_list[idx] &&
+			    phy->chandef.chan->band == band)
+				phy->txpower_cur = max_power;
 
 			tx_power_tlv.last_msg = ch_list[idx] == last_ch;
 			sku_tlbv.channel = ch_list[idx];
diff --git a/drivers/net/wireless/mediatek/mt76/mt7921/main.c b/drivers/net/wireless/mediatek/mt76/mt7921/main.c
index 5881040ac..38a59c6f2 100644
--- a/drivers/net/wireless/mediatek/mt76/mt7921/main.c
+++ b/drivers/net/wireless/mediatek/mt76/mt7921/main.c
@@ -638,7 +638,8 @@ static int mt7921_config(struct ieee80211_hw *hw, int radio_idx, u32 changed)
 
 	mt792x_mutex_acquire(dev);
 
-	if (changed & IEEE80211_CONF_CHANGE_POWER) {
+	if (changed & (IEEE80211_CONF_CHANGE_POWER |
+		       IEEE80211_CONF_CHANGE_CHANNEL)) {
 		ret = mt7921_set_tx_sar_pwr(hw, NULL);
 		if (ret)
 			goto out;
@@ -719,6 +720,14 @@ static void mt7921_bss_info_changed(struct ieee80211_hw *hw,
 	if (changed & BSS_CHANGED_CQM)
 		mt7921_mcu_set_rssimonitor(dev, vif);
 
+	if (changed & BSS_CHANGED_TXPOWER) {
+		int tx_power = info->txpower;
+
+		if (tx_power != INT_MIN && tx_power > 0)
+			hw->conf.power_level = tx_power;
+		mt7921_set_tx_sar_pwr(hw, NULL);
+	}
+
 	if (changed & BSS_CHANGED_ASSOC) {
 		mt7921_mcu_sta_update(dev, NULL, vif, true,
 				      MT76_STA_INFO_STATE_ASSOC);
@@ -1360,8 +1369,15 @@ mt7921_add_chanctx(struct ieee80211_hw *hw,
 		   struct ieee80211_chanctx_conf *ctx)
 {
 	struct mt792x_dev *dev = mt792x_hw_dev(hw);
+	struct mt76_phy *mphy = hw->priv;
 
 	dev->new_ctx = ctx;
+	mphy->chandef = ctx->def;
+
+	mt792x_mutex_acquire(dev);
+	mt7921_set_tx_sar_pwr(hw, NULL);
+	mt792x_mutex_release(dev);
+
 	return 0;
 }
 
@@ -1396,6 +1412,10 @@ mt7921_change_chanctx(struct ieee80211_hw *hw,
 		mt7921_mcu_config_sniffer(mvif, ctx);
 	else
 		mt76_connac_mcu_uni_set_chctx(mvif->phy->mt76, &mvif->bss_conf.mt76, ctx);
+
+	phy->mt76->chandef = ctx->def;
+	mt7921_set_tx_sar_pwr(hw, NULL);
+
 	mt792x_mutex_release(phy->dev);
 }
 
-- 
2.51.0


^ permalink raw reply related

* [PATCH v3 0/1] wifi: mt76: mt7921: fix txpower reporting from rate power configuration
From: Lucid Duck @ 2026-03-17 17:30 UTC (permalink / raw)
  To: linux-wireless
  Cc: nbd, sean.wang, lorenzo, linux-mediatek, morrownr, Lucid Duck
In-Reply-To: <CAGp9Lzp0LEac0DnAzs477fG5rmA+ZjdDHfdAPWccK3GKEY05rw@mail.gmail.com>

Sean, Felix,

Here's v3 of the txpower fix, reworked based on both of your feedback.

Felix pointed out in v1 review that the fix should determine the maximum
rate power from the loop in mt76_connac_mcu_rate_txpower_band(). Sean
confirmed this direction in v2 review and clarified that the value should
come from the same computation that configures the firmware's per-rate
power tables, not from bss_conf.txpower.

v3 does exactly that. The loop already computes the right value via
mt76_get_rate_power_limits() but discards the return. This patch
captures it and stores it to phy->txpower_cur for the current channel,
matching how mt7915 handles this in mt7915_mcu_set_txpower_sku().
Nick independently confirmed v2 produced correct values on his
MT7921AU -- v3 preserves those results while addressing the
source-of-truth concern.

Two additional issues came up during implementation:

- mt7921's chanctx callbacks don't update phy->chandef (the common
  mt76_add_chanctx does, but mt7921 overrides it with a minimal
  version). Fixed by syncing chandef from ctx->def and re-triggering
  the rate power path in add_chanctx and change_chanctx.

- For chanctx drivers, mac80211 routes iw set txpower through
  BSS_CHANGED_TXPOWER, not IEEE80211_CONF_CHANGE_POWER, and never
  updates hw->conf.power_level. Added a BSS_CHANGED_TXPOWER handler
  to bridge this into the rate power path.

Tested on Alfa AWUS036AXML (MT7921AU), kernel 6.17.1, Canada:

  Band       Auto       User limit (15dBm)   Stock (before)
  2.4GHz     33 dBm     18 dBm               3 dBm
  5GHz       26 dBm     18 dBm               3 dBm
  6GHz       15 dBm      8 dBm (5dBm limit)  3 dBm

  Values match regulatory limits + 3 dBm 2x2 path delta.

Test suite (973 tests, 3.5 hours):

  Regulatory accuracy:     correct on all 3 bands (2.4/5/6 GHz)

  User txpower sweep:      every 1 dBm from 1 to reg_max, all 3 bands

  Band switching:          100 cycles between 2.4 and 5 GHz, 0 failures

  Module reload:           50 rmmod/insmod/connect/verify cycles, 0 failures

  2-hour soak:             480 samples at 15s intervals, zero drift

  Band rotation soak:      30 rotations across all 3 bands over 1 hour,
                           240 samples, 0 failures

  Regdomain switching:     10 countries (CA/US/JP/DE/GB/AU/NZ/BR/KR/TW)
                           mid-session without reconnecting, all correct
                           (JP correctly dropped to 23 dBm on 5GHz UNII-1)

  Monitor mode:            correct on ch1/6/11 (2.4GHz) and ch36/44/149
                           (5GHz), txpower set works in monitor mode

  Edge cases:              min power (1dBm), over-max clamping, rapid
                           power changes (10 in 10s), all correct

  Firmware cross-check:    debugfs txpower_sku confirms per-rate tables
                           match regulatory limits on all bands

Lucid Duck (1):
  wifi: mt76: mt7921: fix txpower reporting from rate power
    configuration

 .../wireless/mediatek/mt76/mt76_connac_mcu.c  | 12 +++++++---
 .../net/wireless/mediatek/mt76/mt7921/main.c  | 22 ++++++++++++++++++-
 2 files changed, 30 insertions(+), 4 deletions(-)

--
2.51.0


^ permalink raw reply

* [PATCH 05/10 net-next v3] drivers: net: drop ipv6_stub usage and use direct function calls
From: Fernando Fernandez Mancera @ 2026-03-17 14:01 UTC (permalink / raw)
  To: netdev
  Cc: Fernando Fernandez Mancera, Ricardo B. Marlière,
	Jason A. Donenfeld, Antonio Quartulli, Edward Cree,
	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, Pablo Neira Ayuso,
	Harald Welte, Sabrina Dubroca, Oliver Neukum, David Ahern,
	Stanislav Yakovlev, Nikolay Aleksandrov, Vlad Dumitrescu,
	Edward Srouji, Parav Pandit, Kees Cook, Guillaume Nault,
	Jianbo Liu, Gal Pressman, Alexei Lazar, Cosmin Ratiu,
	Carolina Jubran, Alexandre Cassen, Stanislav Fomichev, linux-rdma,
	linux-kernel, oss-drivers, linux-net-drivers, osmocom-net-gprs,
	linux-usb, wireguard, linux-wireless, bridge
In-Reply-To: <20260317140141.5723-1-fmancera@suse.de>

As IPv6 is built-in only, the ipv6_stub infrastructure is no longer
necessary.

Convert all drivers currently utilizing ipv6_stub to make direct
function calls. The fallback functions introduced previously will
prevent linkage errors when CONFIG_IPV6 is disabled.

Signed-off-by: Fernando Fernandez Mancera <fmancera@suse.de>
Tested-by: Ricardo B. Marlière <rbm@suse.com>
Reviewed-by: Jason A. Donenfeld <Jason@zx2c4.com>
Reviewed-by: Antonio Quartulli <antonio@openvpn.net>
Reviewed-by: Edward Cree <ecree.xilinx@gmail.com>
---
 drivers/infiniband/core/addr.c                  |  3 +--
 drivers/infiniband/sw/rxe/rxe_net.c             |  6 +++---
 .../ethernet/mellanox/mlx5/core/en/rep/neigh.c  | 12 ++++++++----
 .../net/ethernet/mellanox/mlx5/core/en/tc_tun.c |  3 +--
 .../mellanox/mlx5/core/en/tc_tun_encap.c        |  2 +-
 .../mellanox/mlx5/core/en_accel/ipsec.c         |  1 -
 .../net/ethernet/mellanox/mlx5/core/en_rep.c    |  1 -
 drivers/net/ethernet/mellanox/mlx5/core/en_tc.c |  1 -
 .../ethernet/mellanox/mlxsw/spectrum_router.c   |  9 +++++----
 .../net/ethernet/mellanox/mlxsw/spectrum_span.c |  3 ++-
 .../net/ethernet/netronome/nfp/flower/action.c  |  2 +-
 .../ethernet/netronome/nfp/flower/tunnel_conf.c |  9 ++++-----
 drivers/net/ethernet/sfc/tc_counters.c          |  2 +-
 drivers/net/ethernet/sfc/tc_encap_actions.c     |  5 ++---
 drivers/net/geneve.c                            |  1 -
 drivers/net/gtp.c                               |  2 +-
 drivers/net/ovpn/peer.c                         |  3 +--
 drivers/net/ovpn/udp.c                          |  3 +--
 drivers/net/usb/cdc_mbim.c                      | 17 +++++++++--------
 drivers/net/vrf.c                               |  3 ++-
 drivers/net/vxlan/vxlan_core.c                  | 11 +++++------
 drivers/net/vxlan/vxlan_multicast.c             |  6 ++----
 drivers/net/wireguard/socket.c                  |  3 +--
 drivers/net/wireless/intel/ipw2x00/ipw2100.c    |  2 +-
 net/bridge/br_arp_nd_proxy.c                    |  3 +--
 25 files changed, 53 insertions(+), 60 deletions(-)

diff --git a/drivers/infiniband/core/addr.c b/drivers/infiniband/core/addr.c
index 866746695712..48d4b06384ec 100644
--- a/drivers/infiniband/core/addr.c
+++ b/drivers/infiniband/core/addr.c
@@ -41,7 +41,6 @@
 #include <net/neighbour.h>
 #include <net/route.h>
 #include <net/netevent.h>
-#include <net/ipv6_stubs.h>
 #include <net/ip6_route.h>
 #include <rdma/ib_addr.h>
 #include <rdma/ib_cache.h>
@@ -411,7 +410,7 @@ static int addr6_resolve(struct sockaddr *src_sock,
 	fl6.saddr = src_in->sin6_addr;
 	fl6.flowi6_oif = addr->bound_dev_if;
 
-	dst = ipv6_stub->ipv6_dst_lookup_flow(addr->net, NULL, &fl6, NULL);
+	dst = ip6_dst_lookup_flow(addr->net, NULL, &fl6, NULL);
 	if (IS_ERR(dst))
 		return PTR_ERR(dst);
 
diff --git a/drivers/infiniband/sw/rxe/rxe_net.c b/drivers/infiniband/sw/rxe/rxe_net.c
index 0bd0902b11f7..cbc646a30003 100644
--- a/drivers/infiniband/sw/rxe/rxe_net.c
+++ b/drivers/infiniband/sw/rxe/rxe_net.c
@@ -138,9 +138,9 @@ static struct dst_entry *rxe_find_route6(struct rxe_qp *qp,
 	memcpy(&fl6.daddr, daddr, sizeof(*daddr));
 	fl6.flowi6_proto = IPPROTO_UDP;
 
-	ndst = ipv6_stub->ipv6_dst_lookup_flow(sock_net(recv_sockets.sk6->sk),
-					       recv_sockets.sk6->sk, &fl6,
-					       NULL);
+	ndst = ip6_dst_lookup_flow(sock_net(recv_sockets.sk6->sk),
+				   recv_sockets.sk6->sk, &fl6,
+				   NULL);
 	if (IS_ERR(ndst)) {
 		rxe_dbg_qp(qp, "no route to %pI6\n", daddr);
 		return NULL;
diff --git a/drivers/net/ethernet/mellanox/mlx5/core/en/rep/neigh.c b/drivers/net/ethernet/mellanox/mlx5/core/en/rep/neigh.c
index d220b045b331..56930bad94eb 100644
--- a/drivers/net/ethernet/mellanox/mlx5/core/en/rep/neigh.c
+++ b/drivers/net/ethernet/mellanox/mlx5/core/en/rep/neigh.c
@@ -10,6 +10,7 @@
 #include <linux/notifier.h>
 #include <net/netevent.h>
 #include <net/arp.h>
+#include <net/ndisc.h>
 #include "neigh.h"
 #include "tc.h"
 #include "en_rep.h"
@@ -18,8 +19,10 @@
 
 static unsigned long mlx5e_rep_ipv6_interval(void)
 {
-	if (IS_ENABLED(CONFIG_IPV6) && ipv6_stub->nd_tbl)
-		return NEIGH_VAR(&ipv6_stub->nd_tbl->parms, DELAY_PROBE_TIME);
+	struct neigh_table *tbl = ipv6_get_nd_tbl();
+
+	if (IS_ENABLED(CONFIG_IPV6) && ipv6_mod_enabled())
+		return NEIGH_VAR(&tbl->parms, DELAY_PROBE_TIME);
 
 	return ~0UL;
 }
@@ -217,7 +220,7 @@ static int mlx5e_rep_netevent_event(struct notifier_block *nb,
 	case NETEVENT_NEIGH_UPDATE:
 		n = ptr;
 #if IS_ENABLED(CONFIG_IPV6)
-		if (n->tbl != ipv6_stub->nd_tbl && n->tbl != &arp_tbl)
+		if (n->tbl != ipv6_get_nd_tbl() && n->tbl != &arp_tbl)
 #else
 		if (n->tbl != &arp_tbl)
 #endif
@@ -238,7 +241,8 @@ static int mlx5e_rep_netevent_event(struct notifier_block *nb,
 		 * done per device delay prob time parameter.
 		 */
 #if IS_ENABLED(CONFIG_IPV6)
-		if (!p->dev || (p->tbl != ipv6_stub->nd_tbl && p->tbl != &arp_tbl))
+		if (!p->dev ||
+		    (p->tbl != ipv6_get_nd_tbl() && p->tbl != &arp_tbl))
 #else
 		if (!p->dev || p->tbl != &arp_tbl)
 #endif
diff --git a/drivers/net/ethernet/mellanox/mlx5/core/en/tc_tun.c b/drivers/net/ethernet/mellanox/mlx5/core/en/tc_tun.c
index a14f216048cd..de74dbfe7b20 100644
--- a/drivers/net/ethernet/mellanox/mlx5/core/en/tc_tun.c
+++ b/drivers/net/ethernet/mellanox/mlx5/core/en/tc_tun.c
@@ -453,8 +453,7 @@ static int mlx5e_route_lookup_ipv6_get(struct mlx5e_priv *priv,
 
 	if (tunnel && tunnel->get_remote_ifindex)
 		attr->fl.fl6.flowi6_oif = tunnel->get_remote_ifindex(dev);
-	dst = ipv6_stub->ipv6_dst_lookup_flow(dev_net(dev), NULL, &attr->fl.fl6,
-					      NULL);
+	dst = ip6_dst_lookup_flow(dev_net(dev), NULL, &attr->fl.fl6, NULL);
 	if (IS_ERR(dst))
 		return PTR_ERR(dst);
 
diff --git a/drivers/net/ethernet/mellanox/mlx5/core/en/tc_tun_encap.c b/drivers/net/ethernet/mellanox/mlx5/core/en/tc_tun_encap.c
index bfd401bee9e8..ce2a27124642 100644
--- a/drivers/net/ethernet/mellanox/mlx5/core/en/tc_tun_encap.c
+++ b/drivers/net/ethernet/mellanox/mlx5/core/en/tc_tun_encap.c
@@ -402,7 +402,7 @@ void mlx5e_tc_update_neigh_used_value(struct mlx5e_neigh_hash_entry *nhe)
 		tbl = &arp_tbl;
 #if IS_ENABLED(CONFIG_IPV6)
 	else if (m_neigh->family == AF_INET6)
-		tbl = ipv6_stub->nd_tbl;
+		tbl = ipv6_get_nd_tbl();
 #endif
 	else
 		return;
diff --git a/drivers/net/ethernet/mellanox/mlx5/core/en_accel/ipsec.c b/drivers/net/ethernet/mellanox/mlx5/core/en_accel/ipsec.c
index 64e13747084e..a52e12c3c95a 100644
--- a/drivers/net/ethernet/mellanox/mlx5/core/en_accel/ipsec.c
+++ b/drivers/net/ethernet/mellanox/mlx5/core/en_accel/ipsec.c
@@ -36,7 +36,6 @@
 #include <linux/inetdevice.h>
 #include <linux/netdevice.h>
 #include <net/netevent.h>
-#include <net/ipv6_stubs.h>
 
 #include "en.h"
 #include "eswitch.h"
diff --git a/drivers/net/ethernet/mellanox/mlx5/core/en_rep.c b/drivers/net/ethernet/mellanox/mlx5/core/en_rep.c
index 1db4ecb2356f..5ec5cae8d229 100644
--- a/drivers/net/ethernet/mellanox/mlx5/core/en_rep.c
+++ b/drivers/net/ethernet/mellanox/mlx5/core/en_rep.c
@@ -38,7 +38,6 @@
 #include <net/pkt_cls.h>
 #include <net/act_api.h>
 #include <net/devlink.h>
-#include <net/ipv6_stubs.h>
 
 #include "eswitch.h"
 #include "en.h"
diff --git a/drivers/net/ethernet/mellanox/mlx5/core/en_tc.c b/drivers/net/ethernet/mellanox/mlx5/core/en_tc.c
index 1434b65d4746..4e4ee1d520ce 100644
--- a/drivers/net/ethernet/mellanox/mlx5/core/en_tc.c
+++ b/drivers/net/ethernet/mellanox/mlx5/core/en_tc.c
@@ -40,7 +40,6 @@
 #include <linux/refcount.h>
 #include <linux/completion.h>
 #include <net/arp.h>
-#include <net/ipv6_stubs.h>
 #include <net/bareudp.h>
 #include <net/bonding.h>
 #include <net/dst_metadata.h>
diff --git a/drivers/net/ethernet/mellanox/mlxsw/spectrum_router.c b/drivers/net/ethernet/mellanox/mlxsw/spectrum_router.c
index 7bd87d0547d8..8531216f6389 100644
--- a/drivers/net/ethernet/mellanox/mlxsw/spectrum_router.c
+++ b/drivers/net/ethernet/mellanox/mlxsw/spectrum_router.c
@@ -2458,7 +2458,7 @@ static void mlxsw_sp_router_neigh_ent_ipv6_process(struct mlxsw_sp *mlxsw_sp,
 	}
 
 	dev = mlxsw_sp_rif_dev(mlxsw_sp->router->rifs[rif]);
-	n = neigh_lookup(&nd_tbl, &dip, dev);
+	n = neigh_lookup(ipv6_get_nd_tbl(), &dip, dev);
 	if (!n)
 		return;
 
@@ -3022,7 +3022,8 @@ static int mlxsw_sp_neigh_rif_made_sync(struct mlxsw_sp *mlxsw_sp,
 		goto err_arp;
 
 #if IS_ENABLED(CONFIG_IPV6)
-	neigh_for_each(&nd_tbl, mlxsw_sp_neigh_rif_made_sync_each, &rms);
+	neigh_for_each(ipv6_get_nd_tbl(),
+		       mlxsw_sp_neigh_rif_made_sync_each, &rms);
 #endif
 	if (rms.err)
 		goto err_nd;
@@ -5124,7 +5125,7 @@ mlxsw_sp_nexthop_obj_init(struct mlxsw_sp *mlxsw_sp,
 	case AF_INET6:
 		memcpy(&nh->gw_addr, &nh_obj->ipv6, sizeof(nh_obj->ipv6));
 #if IS_ENABLED(CONFIG_IPV6)
-		nh->neigh_tbl = &nd_tbl;
+		nh->neigh_tbl = ipv6_get_nd_tbl();
 #endif
 		break;
 	}
@@ -6980,7 +6981,7 @@ static int mlxsw_sp_nexthop6_init(struct mlxsw_sp *mlxsw_sp,
 	nh->nh_weight = rt->fib6_nh->fib_nh_weight;
 	memcpy(&nh->gw_addr, &rt->fib6_nh->fib_nh_gw6, sizeof(nh->gw_addr));
 #if IS_ENABLED(CONFIG_IPV6)
-	nh->neigh_tbl = &nd_tbl;
+	nh->neigh_tbl = ipv6_get_nd_tbl();
 #endif
 
 	err = mlxsw_sp_nexthop_counter_enable(mlxsw_sp, nh);
diff --git a/drivers/net/ethernet/mellanox/mlxsw/spectrum_span.c b/drivers/net/ethernet/mellanox/mlxsw/spectrum_span.c
index ae63d549b542..f05ccf3db876 100644
--- a/drivers/net/ethernet/mellanox/mlxsw/spectrum_span.c
+++ b/drivers/net/ethernet/mellanox/mlxsw/spectrum_span.c
@@ -576,7 +576,8 @@ mlxsw_sp_span_entry_gretap6_parms(struct mlxsw_sp *mlxsw_sp,
 	l3edev = mlxsw_sp_span_gretap6_route(to_dev, &saddr.addr6, &gw.addr6);
 	return mlxsw_sp_span_entry_tunnel_parms_common(l3edev, saddr, daddr, gw,
 						       tparm.hop_limit,
-						       &nd_tbl, sparmsp);
+						       ipv6_get_nd_tbl(),
+						       sparmsp);
 }
 
 static int
diff --git a/drivers/net/ethernet/netronome/nfp/flower/action.c b/drivers/net/ethernet/netronome/nfp/flower/action.c
index aca2a7417af3..ae2f8b31adfb 100644
--- a/drivers/net/ethernet/netronome/nfp/flower/action.c
+++ b/drivers/net/ethernet/netronome/nfp/flower/action.c
@@ -470,7 +470,7 @@ nfp_fl_set_tun(struct nfp_app *app, struct nfp_fl_set_tun *set_tun,
 
 		flow.daddr = ip_tun->key.u.ipv6.dst;
 		flow.flowi4_proto = IPPROTO_UDP;
-		dst = ipv6_stub->ipv6_dst_lookup_flow(net, NULL, &flow, NULL);
+		dst = ip6_dst_lookup_flow(net, NULL, &flow, NULL);
 		if (!IS_ERR(dst)) {
 			set_tun->ttl = ip6_dst_hoplimit(dst);
 			dst_release(dst);
diff --git a/drivers/net/ethernet/netronome/nfp/flower/tunnel_conf.c b/drivers/net/ethernet/netronome/nfp/flower/tunnel_conf.c
index 0cef0e2b85d0..053265e135f6 100644
--- a/drivers/net/ethernet/netronome/nfp/flower/tunnel_conf.c
+++ b/drivers/net/ethernet/netronome/nfp/flower/tunnel_conf.c
@@ -277,7 +277,7 @@ void nfp_tunnel_keep_alive_v6(struct nfp_app *app, struct sk_buff *skb)
 		if (!netdev)
 			continue;
 
-		n = neigh_lookup(&nd_tbl, ipv6_add, netdev);
+		n = neigh_lookup(ipv6_get_nd_tbl(), ipv6_add, netdev);
 		if (!n)
 			continue;
 
@@ -650,7 +650,7 @@ static void nfp_tun_neigh_update(struct work_struct *work)
 		flow6.daddr = *(struct in6_addr *)n->primary_key;
 		if (!neigh_invalid) {
 			struct dst_entry *dst;
-			/* Use ipv6_dst_lookup_flow to populate flow6->saddr
+			/* Use ip6_dst_lookup_flow to populate flow6->saddr
 			 * and other fields. This information is only needed
 			 * for new entries, lookup can be skipped when an entry
 			 * gets invalidated - as only the daddr is needed for
@@ -730,7 +730,7 @@ nfp_tun_neigh_event_handler(struct notifier_block *nb, unsigned long event,
 		return NOTIFY_DONE;
 	}
 #if IS_ENABLED(CONFIG_IPV6)
-	if (n->tbl != ipv6_stub->nd_tbl && n->tbl != &arp_tbl)
+	if (n->tbl != ipv6_get_nd_tbl() && n->tbl != &arp_tbl)
 #else
 	if (n->tbl != &arp_tbl)
 #endif
@@ -815,8 +815,7 @@ void nfp_tunnel_request_route_v6(struct nfp_app *app, struct sk_buff *skb)
 	flow.flowi6_proto = IPPROTO_UDP;
 
 #if IS_ENABLED(CONFIG_INET) && IS_ENABLED(CONFIG_IPV6)
-	dst = ipv6_stub->ipv6_dst_lookup_flow(dev_net(netdev), NULL, &flow,
-					      NULL);
+	dst = ip6_dst_lookup_flow(dev_net(netdev), NULL, &flow, NULL);
 	if (IS_ERR(dst))
 		goto fail_rcu_unlock;
 #else
diff --git a/drivers/net/ethernet/sfc/tc_counters.c b/drivers/net/ethernet/sfc/tc_counters.c
index d168282f30bf..d8a5f9fd1007 100644
--- a/drivers/net/ethernet/sfc/tc_counters.c
+++ b/drivers/net/ethernet/sfc/tc_counters.c
@@ -112,7 +112,7 @@ static void efx_tc_counter_work(struct work_struct *work)
 					 encap->neigh->egdev);
 		else
 #if IS_ENABLED(CONFIG_IPV6)
-			n = neigh_lookup(ipv6_stub->nd_tbl,
+			n = neigh_lookup(ipv6_get_nd_tbl(),
 					 &encap->neigh->dst_ip6,
 					 encap->neigh->egdev);
 #else
diff --git a/drivers/net/ethernet/sfc/tc_encap_actions.c b/drivers/net/ethernet/sfc/tc_encap_actions.c
index da35705cc5e1..63d8f794b869 100644
--- a/drivers/net/ethernet/sfc/tc_encap_actions.c
+++ b/drivers/net/ethernet/sfc/tc_encap_actions.c
@@ -149,8 +149,7 @@ static int efx_bind_neigh(struct efx_nic *efx,
 #if IS_ENABLED(CONFIG_IPV6)
 			struct dst_entry *dst;
 
-			dst = ipv6_stub->ipv6_dst_lookup_flow(net, NULL, &flow6,
-							      NULL);
+			dst = ip6_dst_lookup_flow(net, NULL, &flow6, NULL);
 			rc = PTR_ERR_OR_ZERO(dst);
 			if (rc) {
 				NL_SET_ERR_MSG_MOD(extack, "Failed to lookup route for IPv6 encap");
@@ -531,7 +530,7 @@ static int efx_neigh_event(struct efx_nic *efx, struct neighbour *n)
 	if (n->tbl == &arp_tbl) {
 		keysize = sizeof(keys.dst_ip);
 #if IS_ENABLED(CONFIG_IPV6)
-	} else if (n->tbl == ipv6_stub->nd_tbl) {
+	} else if (n->tbl == ipv6_get_nd_tbl()) {
 		ipv6 = true;
 		keysize = sizeof(keys.dst_ip6);
 #endif
diff --git a/drivers/net/geneve.c b/drivers/net/geneve.c
index 01cdd06102e0..c6563367d382 100644
--- a/drivers/net/geneve.c
+++ b/drivers/net/geneve.c
@@ -12,7 +12,6 @@
 #include <linux/module.h>
 #include <linux/etherdevice.h>
 #include <linux/hash.h>
-#include <net/ipv6_stubs.h>
 #include <net/dst_metadata.h>
 #include <net/gro_cells.h>
 #include <net/rtnetlink.h>
diff --git a/drivers/net/gtp.c b/drivers/net/gtp.c
index e8949f556209..70b9e58b9b78 100644
--- a/drivers/net/gtp.c
+++ b/drivers/net/gtp.c
@@ -374,7 +374,7 @@ static struct rt6_info *ip6_route_output_gtp(struct net *net,
 	fl6->saddr		= *saddr;
 	fl6->flowi6_proto	= sk->sk_protocol;
 
-	dst = ipv6_stub->ipv6_dst_lookup_flow(net, sk, fl6, NULL);
+	dst = ip6_dst_lookup_flow(net, sk, fl6, NULL);
 	if (IS_ERR(dst))
 		return ERR_PTR(-ENETUNREACH);
 
diff --git a/drivers/net/ovpn/peer.c b/drivers/net/ovpn/peer.c
index 3716a1d82801..6dd11c71204b 100644
--- a/drivers/net/ovpn/peer.c
+++ b/drivers/net/ovpn/peer.c
@@ -821,8 +821,7 @@ static struct in6_addr ovpn_nexthop_from_rt6(struct ovpn_priv *ovpn,
 		.daddr = dest,
 	};
 
-	entry = ipv6_stub->ipv6_dst_lookup_flow(dev_net(ovpn->dev), NULL, &fl,
-						NULL);
+	entry = ip6_dst_lookup_flow(dev_net(ovpn->dev), NULL, &fl, NULL);
 	if (IS_ERR(entry)) {
 		net_dbg_ratelimited("%s: no route to host %pI6c\n",
 				    netdev_name(ovpn->dev), &dest);
diff --git a/drivers/net/ovpn/udp.c b/drivers/net/ovpn/udp.c
index 272b535ecaad..059e896b4a2f 100644
--- a/drivers/net/ovpn/udp.c
+++ b/drivers/net/ovpn/udp.c
@@ -14,7 +14,6 @@
 #include <net/addrconf.h>
 #include <net/dst_cache.h>
 #include <net/route.h>
-#include <net/ipv6_stubs.h>
 #include <net/transp_v6.h>
 #include <net/udp.h>
 #include <net/udp_tunnel.h>
@@ -251,7 +250,7 @@ static int ovpn_udp6_output(struct ovpn_peer *peer, struct ovpn_bind *bind,
 		dst_cache_reset(cache);
 	}
 
-	dst = ipv6_stub->ipv6_dst_lookup_flow(sock_net(sk), sk, &fl, NULL);
+	dst = ip6_dst_lookup_flow(sock_net(sk), sk, &fl, NULL);
 	if (IS_ERR(dst)) {
 		ret = PTR_ERR(dst);
 		net_dbg_ratelimited("%s: no route to host %pISpc: %d\n",
diff --git a/drivers/net/usb/cdc_mbim.c b/drivers/net/usb/cdc_mbim.c
index dbf01210b0e7..877fb0ed7d3d 100644
--- a/drivers/net/usb/cdc_mbim.c
+++ b/drivers/net/usb/cdc_mbim.c
@@ -20,7 +20,6 @@
 #include <linux/usb/cdc_ncm.h>
 #include <net/ipv6.h>
 #include <net/addrconf.h>
-#include <net/ipv6_stubs.h>
 #include <net/ndisc.h>
 
 /* alternative VLAN for IP session 0 if not untagged */
@@ -302,6 +301,7 @@ static struct sk_buff *cdc_mbim_tx_fixup(struct usbnet *dev, struct sk_buff *skb
 	return NULL;
 }
 
+#if IS_ENABLED(CONFIG_IPV6)
 /* Some devices are known to send Neighbor Solicitation messages and
  * require Neighbor Advertisement replies.  The IPv6 core will not
  * respond since IFF_NOARP is set, so we must handle them ourselves.
@@ -342,12 +342,11 @@ static void do_neigh_solicit(struct usbnet *dev, u8 *buf, u16 tci)
 	is_router = !!READ_ONCE(in6_dev->cnf.forwarding);
 	in6_dev_put(in6_dev);
 
-	/* ipv6_stub != NULL if in6_dev_get returned an inet6_dev */
-	ipv6_stub->ndisc_send_na(netdev, &iph->saddr, &msg->target,
-				 is_router /* router */,
-				 true /* solicited */,
-				 false /* override */,
-				 true /* inc_opt */);
+	ndisc_send_na(netdev, &iph->saddr, &msg->target,
+		      is_router /* router */,
+		      true /* solicited */,
+		      false /* override */,
+		      true /* inc_opt */);
 out:
 	dev_put(netdev);
 }
@@ -362,7 +361,7 @@ static bool is_neigh_solicit(u8 *buf, size_t len)
 		msg->icmph.icmp6_code == 0 &&
 		msg->icmph.icmp6_type == NDISC_NEIGHBOUR_SOLICITATION);
 }
-
+#endif /* IPV6 */
 
 static struct sk_buff *cdc_mbim_process_dgram(struct usbnet *dev, u8 *buf, size_t len, u16 tci)
 {
@@ -378,8 +377,10 @@ static struct sk_buff *cdc_mbim_process_dgram(struct usbnet *dev, u8 *buf, size_
 			proto = htons(ETH_P_IP);
 			break;
 		case 0x60:
+#if IS_ENABLED(CONFIG_IPV6)
 			if (is_neigh_solicit(buf, len))
 				do_neigh_solicit(dev, buf, tci);
+#endif
 			proto = htons(ETH_P_IPV6);
 			break;
 		default:
diff --git a/drivers/net/vrf.c b/drivers/net/vrf.c
index 8c009bcaa8e7..68edb47cc4eb 100644
--- a/drivers/net/vrf.c
+++ b/drivers/net/vrf.c
@@ -616,7 +616,8 @@ static int vrf_finish_output6(struct net *net, struct sock *sk,
 	nexthop = rt6_nexthop(dst_rt6_info(dst), &ipv6_hdr(skb)->daddr);
 	neigh = __ipv6_neigh_lookup_noref(dst->dev, nexthop);
 	if (unlikely(!neigh))
-		neigh = __neigh_create(&nd_tbl, nexthop, dst->dev, false);
+		neigh = __neigh_create(ipv6_get_nd_tbl(), nexthop,
+				       dst->dev, false);
 	if (!IS_ERR(neigh)) {
 		sock_confirm_neigh(skb, neigh);
 		ret = neigh_output(neigh, skb, false);
diff --git a/drivers/net/vxlan/vxlan_core.c b/drivers/net/vxlan/vxlan_core.c
index 17c941aac32d..4ab94dfe0d12 100644
--- a/drivers/net/vxlan/vxlan_core.c
+++ b/drivers/net/vxlan/vxlan_core.c
@@ -19,7 +19,6 @@
 #include <net/arp.h>
 #include <net/ndisc.h>
 #include <net/gro.h>
-#include <net/ipv6_stubs.h>
 #include <net/ip.h>
 #include <net/icmp.h>
 #include <net/rtnetlink.h>
@@ -2045,7 +2044,7 @@ static int neigh_reduce(struct net_device *dev, struct sk_buff *skb, __be32 vni)
 	    ipv6_addr_is_multicast(&msg->target))
 		goto out;
 
-	n = neigh_lookup(ipv6_stub->nd_tbl, &msg->target, dev);
+	n = neigh_lookup(ipv6_get_nd_tbl(), &msg->target, dev);
 
 	if (n) {
 		struct vxlan_rdst *rdst = NULL;
@@ -2130,15 +2129,15 @@ static bool route_shortcircuit(struct net_device *dev, struct sk_buff *skb)
 	{
 		struct ipv6hdr *pip6;
 
-		/* check if nd_tbl is not initiliazed due to
-		 * ipv6.disable=1 set during boot
+		/* check if ipv6.disable=1 set during boot was set
+		 * during booting so nd_tbl is not initialized
 		 */
-		if (!ipv6_stub->nd_tbl)
+		if (!ipv6_mod_enabled())
 			return false;
 		if (!pskb_may_pull(skb, sizeof(struct ipv6hdr)))
 			return false;
 		pip6 = ipv6_hdr(skb);
-		n = neigh_lookup(ipv6_stub->nd_tbl, &pip6->daddr, dev);
+		n = neigh_lookup(ipv6_get_nd_tbl(), &pip6->daddr, dev);
 		if (!n && (vxlan->cfg.flags & VXLAN_F_L3MISS)) {
 			union vxlan_addr ipa = {
 				.sin6.sin6_addr = pip6->daddr,
diff --git a/drivers/net/vxlan/vxlan_multicast.c b/drivers/net/vxlan/vxlan_multicast.c
index a7f2d67dc61b..b0e80bca855c 100644
--- a/drivers/net/vxlan/vxlan_multicast.c
+++ b/drivers/net/vxlan/vxlan_multicast.c
@@ -39,8 +39,7 @@ int vxlan_igmp_join(struct vxlan_dev *vxlan, union vxlan_addr *rip,
 
 		sk = sock6->sock->sk;
 		lock_sock(sk);
-		ret = ipv6_stub->ipv6_sock_mc_join(sk, ifindex,
-						   &ip->sin6.sin6_addr);
+		ret = ipv6_sock_mc_join(sk, ifindex, &ip->sin6.sin6_addr);
 		release_sock(sk);
 #endif
 	}
@@ -73,8 +72,7 @@ int vxlan_igmp_leave(struct vxlan_dev *vxlan, union vxlan_addr *rip,
 
 		sk = sock6->sock->sk;
 		lock_sock(sk);
-		ret = ipv6_stub->ipv6_sock_mc_drop(sk, ifindex,
-						   &ip->sin6.sin6_addr);
+		ret = ipv6_sock_mc_drop(sk, ifindex, &ip->sin6.sin6_addr);
 		release_sock(sk);
 #endif
 	}
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",
diff --git a/drivers/net/wireless/intel/ipw2x00/ipw2100.c b/drivers/net/wireless/intel/ipw2x00/ipw2100.c
index 248a051da52d..c11428485dcc 100644
--- a/drivers/net/wireless/intel/ipw2x00/ipw2100.c
+++ b/drivers/net/wireless/intel/ipw2x00/ipw2100.c
@@ -4838,7 +4838,7 @@ static int ipw2100_system_config(struct ipw2100_priv *priv, int batch_mode)
 
 /* If IPv6 is configured in the kernel then we don't want to filter out all
  * of the multicast packets as IPv6 needs some. */
-#if !defined(CONFIG_IPV6) && !defined(CONFIG_IPV6_MODULE)
+#if !defined(CONFIG_IPV6)
 	cmd.host_command = ADD_MULTICAST;
 	cmd.host_command_sequence = 0;
 	cmd.host_command_length = 0;
diff --git a/net/bridge/br_arp_nd_proxy.c b/net/bridge/br_arp_nd_proxy.c
index 1e2b51769eec..494bf69a3017 100644
--- a/net/bridge/br_arp_nd_proxy.c
+++ b/net/bridge/br_arp_nd_proxy.c
@@ -17,7 +17,6 @@
 #include <linux/if_vlan.h>
 #include <linux/inetdevice.h>
 #include <net/addrconf.h>
-#include <net/ipv6_stubs.h>
 #if IS_ENABLED(CONFIG_IPV6)
 #include <net/ip6_checksum.h>
 #endif
@@ -455,7 +454,7 @@ void br_do_suppress_nd(struct sk_buff *skb, struct net_bridge *br,
 		return;
 	}
 
-	n = neigh_lookup(ipv6_stub->nd_tbl, &msg->target, vlandev);
+	n = neigh_lookup(ipv6_get_nd_tbl(), &msg->target, vlandev);
 	if (n) {
 		struct net_bridge_fdb_entry *f;
 
-- 
2.53.0


^ permalink raw reply related

* [PATCH] wifi: ath12k: fix channel list copy on big endian
From: Alexander Wilhelm @ 2026-03-17 12:54 UTC (permalink / raw)
  To: Jeff Johnson; +Cc: linux-wireless, ath12k, linux-kernel

The ath12k_wmi_scan_req_arg structure defines the channel list in
CPU-native order, while wmi_start_scan_cmd expects the values in
little-endian format. The simple memcpy causes the hardware scan to fail on
big-endian architectures. Set __le32* type for the tmp_ptr and swap channel
values to support both architectures correctly.

Signed-off-by: Alexander Wilhelm <alexander.wilhelm@westermo.com>
---
 drivers/net/wireless/ath/ath12k/wmi.c | 8 +++++---
 1 file changed, 5 insertions(+), 3 deletions(-)

diff --git a/drivers/net/wireless/ath/ath12k/wmi.c b/drivers/net/wireless/ath/ath12k/wmi.c
index 65a05a9520ff..9e1d3c662852 100644
--- a/drivers/net/wireless/ath/ath12k/wmi.c
+++ b/drivers/net/wireless/ath/ath12k/wmi.c
@@ -2571,7 +2571,8 @@ int ath12k_wmi_send_scan_start_cmd(struct ath12k *ar,
 	struct wmi_tlv *tlv;
 	void *ptr;
 	int i, ret, len;
-	u32 *tmp_ptr, extraie_len_with_pad = 0;
+	__le32 *tmp_ptr;
+	u32 extraie_len_with_pad = 0;
 	struct ath12k_wmi_hint_short_ssid_arg *s_ssid = NULL;
 	struct ath12k_wmi_hint_bssid_arg *hint_bssid = NULL;
 
@@ -2656,9 +2657,10 @@ int ath12k_wmi_send_scan_start_cmd(struct ath12k *ar,
 	tlv = ptr;
 	tlv->header = ath12k_wmi_tlv_hdr(WMI_TAG_ARRAY_UINT32, len);
 	ptr += TLV_HDR_SIZE;
-	tmp_ptr = (u32 *)ptr;
+	tmp_ptr = (__le32 *)ptr;
 
-	memcpy(tmp_ptr, arg->chan_list, arg->num_chan * 4);
+	for (i = 0; i < arg->num_chan; i++)
+		tmp_ptr[i] = cpu_to_le32(arg->chan_list[i]);
 
 	ptr += len;
 

---
base-commit: 702847e8cfd51856836a282db2073defd7cfd80c
change-id: 20260317-fix-channel-list-copy-cef5cad24fb6

Best regards,
-- 
Alexander Wilhelm <alexander.wilhelm@westermo.com>


^ permalink raw reply related

* Re: RTL8852BE fails to power on: "xtal si not ready" on ASUS TUF GAMING B650-PLUS WIFI
From: Jason Kakandris @ 2026-03-17 11:27 UTC (permalink / raw)
  To: Ping-Ke Shih; +Cc: linux-wireless@vger.kernel.org
In-Reply-To: <b8cb73b5c8bf42b38ad275220b1d559e@realtek.com>

Hello!

- The error appears on first boot, not after resume. It has never
worked on Linux.
- I have set up a udev rule to disable D3Cold for the device and will
test with a cold boot. I will also try the latest firmware
(v0.29.29.15) and report back.
Where can I obtain firmware v0.29.29.15? Is it rtw8852b_fw-2.bin from
linux-firmware.git?

Στις Τρί 17 Μαρ 2026 στις 5:03 π.μ., ο/η Ping-Ke Shih
<pkshih@realtek.com> έγραψε:
>
> Jason Kakandris <ikakandris@gmail.com> wrote:
> > System Info
> >
> > Distro: Linux Mint 22.3 Zena (Ubuntu 24.04 base)
> > Kernels tested: 6.14.0-37-generic, 6.17.0-14-generic (same failure on both)
> > Motherboard: ASUS TUF GAMING B650-PLUS WIFI (Rev 1.xx)
> > BIOS: v3827 (Feb 2026)
> > CPU: AMD Ryzen 7 7700X
> > Driver: rtw89 v7.0 (git commit d2f175e
> > https://github.com/morrownr/rtw89/commit/d2f175eafa0a4ef9cc65e7073a77e60238c
> > ae614)
> > WiFi works in Windows: Yes
> >
> >
> > Problem
> > The RTL8852BE WiFi card fails to initialize with xtal si not ready error. No
> > wireless interface is created.
> >
> >
> > dmesg output
> >
> > rtw89_8852be_git 0000:08:00.0: loaded firmware rtw89/rtw8852b_fw-1.bin
> > rtw89_8852be_git 0000:08:00.0: enabling device (0000 -> 0003)
>
> It looks like you didn't encounter D3Cold problem, but I think you can
> give it a try [1].
>
> [1] https://bugzilla.kernel.org/show_bug.cgi?id=221213
>
> > rtw89_8852be_git 0000:08:00.0: xtal si not ready(R): offset=41
> > rtw89_8852be_git 0000:08:00.0: xtal si not ready(W): offset=90 val=10 mask=10
> > rtw89_8852be_git 0000:08:00.0: failed to power on
> > rtw89_8852be_git 0000:08:00.0: failed to setup chip information
> > rtw89_8852be_git 0000:08:00.0: probe with driver rtw89_8852be_git failed with
> > error -110
>
> These messages appear when first booting or after system resume?
> Recently we update something related to suspend/resume problem.
> Please use the latest driver (kernel 7.0-rc4) with the latest
> firmware (v0.29.29.15).
>
> >
> >
> > lspci
> >
> > 08:00.0 Network controller [0280]: Realtek Semiconductor Co., Ltd. RTL8852BE
> > PCIe 802.11ax Wireless Network Controller [10ec:b852]
> > Subsystem: AzureWave RTL8852BE PCIe 802.11ax Wireless Network Controller
> > [1a3b:5471]
> >
> > What I've tried
> >
> > Kernel parameter pcie_aspm=off
> > Module parameters: disable_clkreq=Y disable_aspm_l1=Y disable_aspm_l1ss=Y
> > disable_ps_mode=y
>
> As you try this, please ensure that add a configuration file to /etc/module.d/,
> and cold reboot.
>
> > Updated BIOS from v3057 to v3827
> > Updated firmware files via make install_fw
> > Tested on kernels 6.14 and 6.17 — same failure on both
> > In-kernel driver and morrownr out-of-tree driver — same failure
> > WiFi works fine in Windows on the same hardware
>

^ permalink raw reply

* [PATCH] wifi: ath12k: fix MAC address copy on big endian
From: Alexander Wilhelm @ 2026-03-17 11:22 UTC (permalink / raw)
  To: Jeff Johnson; +Cc: linux-wireless, ath12k, linux-kernel

The ath12k_dp_get_mac_addr function performs a simple memcpy from a
CPU-native data types into an u8 array. On a big-endian architecture, this
later results in a null‑pointer dereference. Convert the data to
little‑endian first, then copy it into the target array.

Signed-off-by: Alexander Wilhelm <alexander.wilhelm@westermo.com>
---
 drivers/net/wireless/ath/ath12k/dp.h | 7 +++++--
 1 file changed, 5 insertions(+), 2 deletions(-)

diff --git a/drivers/net/wireless/ath/ath12k/dp.h b/drivers/net/wireless/ath/ath12k/dp.h
index f8cfc7bb29dd..50957915dbf4 100644
--- a/drivers/net/wireless/ath/ath12k/dp.h
+++ b/drivers/net/wireless/ath/ath12k/dp.h
@@ -647,8 +647,11 @@ int ath12k_dp_arch_rx_tid_delete_handler(struct ath12k_dp *dp,
 
 static inline void ath12k_dp_get_mac_addr(u32 addr_l32, u16 addr_h16, u8 *addr)
 {
-	memcpy(addr, &addr_l32, 4);
-	memcpy(addr + 4, &addr_h16, ETH_ALEN - 4);
+	__le32 le_addr_l32 = cpu_to_le32(addr_l32);
+	__le16 le_addr_h16 = cpu_to_le16(addr_h16);
+
+	memcpy(addr, &le_addr_l32, 4);
+	memcpy(addr + 4, &le_addr_h16, ETH_ALEN - 4);
 }
 
 static inline struct ath12k_dp *

---
base-commit: 702847e8cfd51856836a282db2073defd7cfd80c
change-id: 20260317-fix-mac-addr-copy-on-big-endian-f1a4fea40184

Best regards,
-- 
Alexander Wilhelm <alexander.wilhelm@westermo.com>


^ permalink raw reply related

* [PATCH v2] wifi: rtw89: retry efuse physical map dump on transient failure
From: Christian Hewitt @ 2026-03-17 11:21 UTC (permalink / raw)
  To: Ping-Ke Shih, Bitterblue Smith, linux-wireless, linux-kernel

On Radxa Rock 5B with a RTL8852BE combo WiFi/BT card, the efuse
physical map dump intermittently fails with -EBUSY during probe.
The failure occurs in rtw89_dump_physical_efuse_map_ddv() where
read_poll_timeout_atomic() times out waiting for the B_AX_EF_RDY
bit after 1 second.

The root cause is a timing race during boot: the WiFi driver's
chip initialization (firmware download via PCIe) overlaps with
Bluetooth firmware download to the same combo chip via USB. This
can leave the efuse controller temporarily unavailable when the
WiFi driver attempts to read the efuse map.

The firmware download path retries up to 5 times, but the efuse
read that follows has no similar logic. Address this by adding
retry loop logic (also up to 5 attempts) around physical efuse
map dump.

Signed-off-by: Christian Hewitt <christianshewitt@gmail.com>
---
Changes since v1 [0]:
- Adapt patch using suggestions from Pink-Ke Shih
- Simplify the patch description

[0] https://patchwork.kernel.org/project/linux-wireless/patch/20260301042422.195491-1-christianshewitt@gmail.com/

 drivers/net/wireless/realtek/rtw89/efuse.c | 23 ++++++++++++++++++++--
 1 file changed, 21 insertions(+), 2 deletions(-)

diff --git a/drivers/net/wireless/realtek/rtw89/efuse.c b/drivers/net/wireless/realtek/rtw89/efuse.c
index a2757a88d55d..6ed4b569c2d7 100644
--- a/drivers/net/wireless/realtek/rtw89/efuse.c
+++ b/drivers/net/wireless/realtek/rtw89/efuse.c
@@ -185,8 +185,8 @@ static int rtw89_dump_physical_efuse_map_dav(struct rtw89_dev *rtwdev, u8 *map,
 	return 0;
 }
 
-static int rtw89_dump_physical_efuse_map(struct rtw89_dev *rtwdev, u8 *map,
-					 u32 dump_addr, u32 dump_size, bool dav)
+static int __rtw89_dump_physical_efuse_map(struct rtw89_dev *rtwdev, u8 *map,
+					   u32 dump_addr, u32 dump_size, bool dav)
 {
 	int ret;
 
@@ -208,6 +208,25 @@ static int rtw89_dump_physical_efuse_map(struct rtw89_dev *rtwdev, u8 *map,
 	return 0;
 }
 
+static int rtw89_dump_physical_efuse_map(struct rtw89_dev *rtwdev, u8 *map,
+					 u32 dump_addr, u32 dump_size, bool dav)
+{
+	int retry;
+	int ret;
+
+	for (retry = 0; retry < 5; retry++) {
+		ret = __rtw89_dump_physical_efuse_map(rtwdev, map, dump_addr,
+						      dump_size, dav);
+		if (!ret)
+			return 0;
+
+		rtw89_warn(rtwdev, "efuse dump (dav=%d) failed, retrying (%d)\n",
+			   dav, retry);
+	}
+
+	return ret;
+}
+
 #define invalid_efuse_header(hdr1, hdr2) \
 	((hdr1) == 0xff || (hdr2) == 0xff)
 #define invalid_efuse_content(word_en, i) \
-- 
2.43.0

^ permalink raw reply related

* Re: [PATCH 13/16] carl9170: rx: gate data frame delivery on STARTED state
From: Johannes Berg @ 2026-03-17 11:12 UTC (permalink / raw)
  To: Masi Osmani, Christian Lamparter; +Cc: linux-wireless
In-Reply-To: <AM7PPF5613FA0B6ADFF8016B03CAA1A9DEB9441A@AM7PPF5613FA0B6.EURP251.PROD.OUTLOOK.COM>

On Tue, 2026-03-17 at 12:06 +0100, Masi Osmani wrote:
> Do not deliver data frames to mac80211 unless the device is fully
> started.  After carl9170_op_stop() the driver state drops to IDLE,
> but the USB RX path can still receive frames from the hardware.
> Without this gate, ieee80211_rx() may reference station data that
> sta_info_destroy_part2() is concurrently freeing during interface
> teardown, causing a use-after-free kernel panic.

You keep claiming this without ever showing how it happened, that's not
so useful ... I really don't think that should happen given there's RCU
protection involved everywhere. Please show what happens so we can fix
_that_ issue instead of papering over it.

johannes

^ permalink raw reply

* [PATCH 1/1] wifi: brcmfmac: silence warning for non-existent, optional firmware
From: Alexander Stein @ 2026-03-17 11:12 UTC (permalink / raw)
  To: Arend van Spriel
  Cc: Alexander Stein, linux-wireless, brcm80211,
	brcm80211-dev-list.pdl, linux-kernel

The driver tries to load optional firmware files, specific to
the actual board compatible. These might not exist resulting in a warning
like this:
brcmfmac mmc2:0001:1: Direct firmware load for brcm/brcmfmac4373-sdio.tq,imx93-tqma9352-mba93xxla-mini.bin failed with error -2

Silence this by using firmware_request_nowait_nowarn() for all firmware
loads which use brcmf_fw_request_done_alt_path() as callback. This one
handles optional firmware files.

Signed-off-by: Alexander Stein <alexander.stein@ew.tq-group.com>
---
 .../broadcom/brcm80211/brcmfmac/firmware.c         | 14 ++++++++------
 1 file changed, 8 insertions(+), 6 deletions(-)

diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/firmware.c b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/firmware.c
index 4bacd83db052e..e84cec58470c5 100644
--- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/firmware.c
+++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/firmware.c
@@ -714,9 +714,10 @@ static void brcmf_fw_request_done_alt_path(const struct firmware *fw, void *ctx)
 		if (!alt_path)
 			goto fallback;
 
-		ret = request_firmware_nowait(THIS_MODULE, true, alt_path,
-					      fwctx->dev, GFP_KERNEL, fwctx,
-					      brcmf_fw_request_done_alt_path);
+		ret = firmware_request_nowait_nowarn(THIS_MODULE,
+						     alt_path, fwctx->dev,
+						     GFP_KERNEL, fwctx,
+						     brcmf_fw_request_done_alt_path);
 		kfree(alt_path);
 
 		if (ret < 0)
@@ -779,9 +780,10 @@ int brcmf_fw_get_firmwares(struct device *dev, struct brcmf_fw_request *req,
 					    fwctx->req->board_types[0]);
 	if (alt_path) {
 		fwctx->board_index++;
-		ret = request_firmware_nowait(THIS_MODULE, true, alt_path,
-					      fwctx->dev, GFP_KERNEL, fwctx,
-					      brcmf_fw_request_done_alt_path);
+		ret = firmware_request_nowait_nowarn(THIS_MODULE,
+						     alt_path, fwctx->dev,
+						     GFP_KERNEL, fwctx,
+						     brcmf_fw_request_done_alt_path);
 		kfree(alt_path);
 	} else {
 		ret = request_firmware_nowait(THIS_MODULE, true, first->path,
-- 
2.43.0


^ permalink raw reply related

* [PATCH 16/16] carl9170: cmd: downgrade transient register I/O errors to wiphy_dbg
From: Masi Osmani @ 2026-03-17 11:06 UTC (permalink / raw)
  To: Christian Lamparter; +Cc: linux-wireless, Masi Osmani
In-Reply-To: <20260317110634.70347-1-mas-i@hotmail.de>

Register read/write failures during deauth/teardown transitions are
harmless — mac80211 tries to read survey stats or write slot_time
while the firmware is in a transitional state.  The command times
out with -EIO but the adapter recovers and re-authenticates normally.

Downgrade both "writing reg ... failed" and "reading regs failed"
from wiphy_err to wiphy_dbg to reduce dmesg noise.  The errors are
still visible with dynamic debug enabled for investigation.

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

diff --git a/drivers/net/wireless/ath/carl9170/cmd.c b/drivers/net/wireless/ath/carl9170/cmd.c
--- a/drivers/net/wireless/ath/carl9170/cmd.c
+++ b/drivers/net/wireless/ath/carl9170/cmd.c
@@ -52,7 +52,7 @@
 				(u8 *) buf, 0, NULL);
 	if (err) {
 		if (net_ratelimit()) {
-			wiphy_err(ar->hw->wiphy, "writing reg %#x "
+			wiphy_dbg(ar->hw->wiphy, "writing reg %#x "
 				"(val %#x) failed (%d)\n", reg, val, err);
 		}
 	}
@@ -78,7 +78,7 @@
 				4 * nregs, (u8 *)res);
 	if (err) {
 		if (net_ratelimit()) {
-			wiphy_err(ar->hw->wiphy, "reading regs failed (%d)\n",
+			wiphy_dbg(ar->hw->wiphy, "reading regs failed (%d)\n",
 				  err);
 		}
 		return err;

^ permalink raw reply

* [PATCH 15/16] carl9170: phy: warm BB reset and same-channel no-op
From: Masi Osmani @ 2026-03-17 11:06 UTC (permalink / raw)
  To: Christian Lamparter; +Cc: linux-wireless, Masi Osmani
In-Reply-To: <20260317110634.70347-1-mas-i@hotmail.de>

Three optimizations to carl9170_set_channel():

1. Same-channel no-op: return immediately if already on the
   requested channel and bandwidth. mac80211 sometimes sends
   redundant channel change requests.

2. Same-band warm BB reset: use AR9170_PWR_RESET_BB_WARM_RESET
   (BIT 10) instead of BB_COLD_RESET (BIT 11) for channel
   changes within the same band and HT mode. Warm reset
   preserves PHY state, skipping init_phy (20+ register writes)
   and init_rf_banks_0_7. Cross-band switches still use cold
   reset with full RF re-init.

3. Post-restart guard: null ar->channel and ar->chan_fail in
   carl9170_restart_work() to force cold reset after crash.

This replaces the previous cross-band scan skip approach
(BUG-003) which blocked all 5 GHz scanning while associated
on 2.4 GHz. The warm BB reset provides sufficient protection
against AGC calibration timeouts without preventing cross-band
scans.

Signed-off-by: Masi Osmani <mas-i@hotmail.de>
---
--- a/drivers/net/wireless/ath/carl9170/phy.c	2026-03-16 12:31:43.146715685 +0100
+++ b/drivers/net/wireless/ath/carl9170/phy.c	2026-03-16 12:32:14.020745785 +0100
@@ -1779,27 +1779,49 @@ int carl9170_set_channel(struct ar9170 *
 	/* may be NULL at first setup */
 	if (ar->channel) {
 		old_channel = ar->channel;
+
+		/* No-op if already on the requested channel and bandwidth */
+		if (old_channel == channel && ar->ht_settings == new_ht)
+			return 0;
+
 		ar->channel = NULL;
 	}
 
-	/* cold reset BB/ADDA */
-	err = carl9170_write_reg(ar, AR9170_PWR_REG_RESET,
-				 AR9170_PWR_RESET_BB_COLD_RESET);
-	if (err)
-		return err;
+	/*
+	 * Same-band warm path (inspired by ath9k FastCC):
+	 * Use warm BB reset to preserve PHY state, skipping
+	 * init_phy (20+ reg writes) and init_rf_banks_0_7.
+	 * Cold reset for first setup, cross-band, HT changes.
+	 */
+	if (old_channel && old_channel->band == channel->band &&
+	    ar->ht_settings == new_ht) {
+		err = carl9170_write_reg(ar, AR9170_PWR_REG_RESET,
+					 AR9170_PWR_RESET_BB_WARM_RESET);
+		if (err)
+			return err;
 
-	err = carl9170_write_reg(ar, AR9170_PWR_REG_RESET, 0x0);
-	if (err)
-		return err;
+		err = carl9170_write_reg(ar, AR9170_PWR_REG_RESET, 0x0);
+		if (err)
+			return err;
+	} else {
+		err = carl9170_write_reg(ar, AR9170_PWR_REG_RESET,
+					 AR9170_PWR_RESET_BB_COLD_RESET);
+		if (err)
+			return err;
 
-	err = carl9170_init_phy(ar, channel->band);
-	if (err)
-		return err;
+		err = carl9170_write_reg(ar, AR9170_PWR_REG_RESET, 0x0);
+		if (err)
+			return err;
 
-	err = carl9170_init_rf_banks_0_7(ar,
-					 channel->band == NL80211_BAND_5GHZ);
-	if (err)
-		return err;
+		err = carl9170_init_phy(ar, channel->band);
+		if (err)
+			return err;
+
+		err = carl9170_init_rf_banks_0_7(ar,
+						 channel->band == NL80211_BAND_5GHZ);
+		if (err)
+			return err;
+	}
 
 	err = carl9170_exec_cmd(ar, CARL9170_CMD_FREQ_START, 0, NULL, 0, NULL);
 	if (err)
--- a/drivers/net/wireless/ath/carl9170/main.c	2026-03-16 12:31:43.148358482 +0100
+++ b/drivers/net/wireless/ath/carl9170/main.c	2026-03-16 12:32:14.025829134 +0100
@@ -355,6 +355,8 @@ static int carl9170_op_start(struct ieee
 	/* "The first key is unique." */
 	ar->usedkeys = 1;
 	ar->filter_state = 0;
+	ar->channel = NULL;
+	ar->chan_fail = 0;
 	ar->ps.last_action = jiffies;
 	ar->ps.last_slept = jiffies;
 	ar->erp_mode = CARL9170_ERP_AUTO;
@@ -474,6 +476,8 @@ static void carl9170_restart_work(struct
 
 	ar->usedkeys = 0;
 	ar->filter_state = 0;
+	ar->channel = NULL;
+	ar->chan_fail = 0;
 	carl9170_cancel_worker(ar);
 
 	mutex_lock(&ar->mutex);

^ permalink raw reply

* [PATCH 14/16] carl9170: main: guard op_config and bss_info_changed against non-STARTED state
From: Masi Osmani @ 2026-03-17 11:06 UTC (permalink / raw)
  To: Christian Lamparter; +Cc: linux-wireless, Masi Osmani
In-Reply-To: <20260317110634.70347-1-mas-i@hotmail.de>

During driver deregistration (USB unbind or restart), mac80211 calls
carl9170_op_config() and carl9170_op_bss_info_changed() which write
hardware registers such as AR9170_MAC_REG_SLOT_TIME (0x1c36f0) via
carl9170_set_slot_time().  At this point the firmware is already dead:
carl9170_restart() has transitioned the state machine from STARTED to
IDLE, and IS_ACCEPTING_CMD() returns true for IDLE (state >= IDLE),
so the USB command is attempted but times out with -EIO:

  ieee80211 phy31: writing reg 0x1c36f0 (val 0x2400) failed (-5)
  ieee80211 phy31: writing reg 0x1c36f0 (val 0x5000) failed (-5)

When wavemon or NetworkManager trigger rapid USB unbind/rebind
recovery cycles, each deregistration produces these -EIO errors.
Over 30+ cycles this exhausts mac80211 resources and causes a
kernel panic.

Add early returns if !IS_STARTED(ar) to both carl9170_op_config()
and carl9170_op_bss_info_changed() to prevent all hardware register
writes when the device is not fully operational.

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

diff --git a/drivers/net/wireless/ath/carl9170/main.c b/drivers/net/wireless/ath/carl9170/main.c
index dcedcb1..a1b2c3d 100644
--- a/drivers/net/wireless/ath/carl9170/main.c
+++ b/drivers/net/wireless/ath/carl9170/main.c
@@ -926,6 +926,13 @@ static int carl9170_op_config(struct ieee80211_hw *hw, int radio_idx, u32 change
 	struct ar9170 *ar = hw->priv;
 	int err = 0;

+	/*
+	 * All register writes below require running firmware.
+	 * Bail out early during teardown or restart (state != STARTED).
+	 */
+	if (!IS_STARTED(ar))
+		return 0;
+
 	mutex_lock(&ar->mutex);
 	if (changed & IEEE80211_CONF_CHANGE_LISTEN_INTERVAL) {
 		/* TODO */
@@ -1077,6 +1084,13 @@ static void carl9170_op_bss_info_changed(struct ieee80211_hw *hw,
 	struct carl9170_vif_info *vif_priv;
 	struct ieee80211_vif *main_vif;

+	/*
+	 * All register writes below require running firmware.
+	 * Bail out early during teardown or restart (state != STARTED).
+	 */
+	if (!IS_STARTED(ar))
+		return;
+
 	mutex_lock(&ar->mutex);
 	vif_priv = (void *) vif->drv_priv;
 	main_vif = carl9170_get_main_vif(ar);
--
2.51.0

^ permalink raw reply related

* [PATCH 13/16] carl9170: rx: gate data frame delivery on STARTED state
From: Masi Osmani @ 2026-03-17 11:06 UTC (permalink / raw)
  To: Christian Lamparter; +Cc: linux-wireless, Masi Osmani

Do not deliver data frames to mac80211 unless the device is fully
started.  After carl9170_op_stop() the driver state drops to IDLE,
but the USB RX path can still receive frames from the hardware.
Without this gate, ieee80211_rx() may reference station data that
sta_info_destroy_part2() is concurrently freeing during interface
teardown, causing a use-after-free kernel panic.

The race occurs when ieee80211_handle_reconfig_failure() clears
IN_DRIVER flags without stopping the hardware: cfg80211 then tears
down interfaces via ieee80211_do_stop() which calls sta_info_flush()
while the driver's RX path still delivers frames.  This was observed
when carl9170 firmware deadlocks during a restart attempt and
ieee80211_reconfig() fails at drv_add_interface().

The gate is placed in carl9170_rx_untie_data() just before the
ieee80211_rx() call, not in the USB layer, because firmware command
responses (including CARL9170_RSP_BOOT needed for firmware upload)
must still flow through the shared RX path via
carl9170_handle_command_response() which returns before reaching
this point.

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

diff --git a/drivers/net/wireless/ath/carl9170/rx.c b/drivers/net/wireless/ath/carl9170/rx.c
index 683343013..19c6bd418 100644
--- a/drivers/net/wireless/ath/carl9170/rx.c
+++ b/drivers/net/wireless/ath/carl9170/rx.c
@@ -676,6 +676,14 @@ static int carl9170_handle_mpdu(struct ar9170 *ar, u8 *buf, int len,
 
 	carl9170_ba_check(ar, buf, len);
 
+	/*
+	 * Do not deliver data frames to mac80211 unless the device is
+	 * fully started.  After carl9170_op_stop() the state drops to
+	 * IDLE, preventing a use-after-free when sta_info_destroy_part2()
+	 * races with ieee80211_rx() during interface teardown.
+	 */
+	if (!IS_STARTED(ar))
+		return 0;
 	skb = carl9170_rx_copy_data(buf, len);
 	if (!skb)
 		return -ENOMEM;
-- 
2.51.0


^ permalink raw reply related

* [PATCH] wifi: ath12k: fix HE/EHT capability handling on big endian
From: Alexander Wilhelm @ 2026-03-17 10:59 UTC (permalink / raw)
  To: Jeff Johnson; +Cc: linux-wireless, ath12k, linux-kernel

Currently the driver uses u32 data types for the HE/EHT capabilities in
CPU‑native order. However, the ieee80211.h header defines these fields as
u8 arrays. This causes the ieee80211 registration failure on big‑endian
platforms, as shown in the following log:

    ath12k_pci 0001:01:00.0: BAR 0: assigned [mem 0xc00000000-0xc001fffff 64bit]
    ath12k_pci 0001:01:00.0: MSI vectors: 1
    ath12k_pci 0001:01:00.0: Hardware name: qcn9274 hw2.0
    ath12k_pci 0001:01:00.0: qmi dma allocation failed (29360128 B type 1), will try later with small size
    ath12k_pci 0001:01:00.0: memory type 10 not supported
    ath12k_pci 0001:01:00.0: chip_id 0x0 chip_family 0xb board_id 0x1005 soc_id 0x401a2200
    ath12k_pci 0001:01:00.0: fw_version 0x111300d6 fw_build_timestamp 2024-08-06 08:43 fw_build_id QC_IMAGE_VERSION_STRING=WLAN.WBE.1.1.1-00214-QCAHKSWPL_SILICONZ-1
    ath12k_pci 0001:01:00.0: leaving PCI ASPM disabled to avoid MHI M2 problems
    ath12k_pci 0001:01:00.0: Invalid module id 2
    ath12k_pci 0001:01:00.0: failed to parse tlv -22
    ath12k_pci 0001:01:00.0: ieee80211 registration failed: -22
    ath12k_pci 0001:01:00.0: failed register the radio with mac80211: -22
    ath12k_pci 0001:01:00.0: failed to create pdev core: -22
    ath12k_pci 0001:01:00.0: qmi failed set mode request, mode: 4, err = -110
    ath12k_pci 0001:01:00.0: qmi failed to send wlan mode off

Use the __le32 data type for the HE/EHT capabilities instead and avoid
swapping, so that both platform endiannesses are supported.

Signed-off-by: Alexander Wilhelm <alexander.wilhelm@westermo.com>
---
 drivers/net/wireless/ath/ath12k/core.h |  8 ++---
 drivers/net/wireless/ath/ath12k/wmi.c  | 58 ++++++++++++++++------------------
 drivers/net/wireless/ath/ath12k/wmi.h  |  4 +--
 3 files changed, 34 insertions(+), 36 deletions(-)

diff --git a/drivers/net/wireless/ath/ath12k/core.h b/drivers/net/wireless/ath/ath12k/core.h
index 59c193b24764..8481015dcda6 100644
--- a/drivers/net/wireless/ath/ath12k/core.h
+++ b/drivers/net/wireless/ath/ath12k/core.h
@@ -788,13 +788,13 @@ struct ath12k_band_cap {
 	u32 phy_id;
 	u32 max_bw_supported;
 	u32 ht_cap_info;
-	u32 he_cap_info[2];
+	__le32 he_cap_info[2];
 	u32 he_mcs;
-	u32 he_cap_phy_info[PSOC_HOST_MAX_PHY_SIZE];
+	__le32 he_cap_phy_info[PSOC_HOST_MAX_PHY_SIZE];
 	struct ath12k_wmi_ppe_threshold_arg he_ppet;
 	u16 he_6ghz_capa;
-	u32 eht_cap_mac_info[WMI_MAX_EHTCAP_MAC_SIZE];
-	u32 eht_cap_phy_info[WMI_MAX_EHTCAP_PHY_SIZE];
+	__le32 eht_cap_mac_info[WMI_MAX_EHTCAP_MAC_SIZE];
+	__le32 eht_cap_phy_info[WMI_MAX_EHTCAP_PHY_SIZE];
 	u32 eht_mcs_20_only;
 	u32 eht_mcs_80;
 	u32 eht_mcs_160;
diff --git a/drivers/net/wireless/ath/ath12k/wmi.c b/drivers/net/wireless/ath/ath12k/wmi.c
index 65a05a9520ff..f5cd1eb27685 100644
--- a/drivers/net/wireless/ath/ath12k/wmi.c
+++ b/drivers/net/wireless/ath/ath12k/wmi.c
@@ -487,12 +487,11 @@ ath12k_pull_mac_phy_cap_svc_ready_ext(struct ath12k_wmi_pdev *wmi_handle,
 		cap_band->phy_id = le32_to_cpu(mac_caps->phy_id);
 		cap_band->max_bw_supported = le32_to_cpu(mac_caps->max_bw_supported_2g);
 		cap_band->ht_cap_info = le32_to_cpu(mac_caps->ht_cap_info_2g);
-		cap_band->he_cap_info[0] = le32_to_cpu(mac_caps->he_cap_info_2g);
-		cap_band->he_cap_info[1] = le32_to_cpu(mac_caps->he_cap_info_2g_ext);
+		cap_band->he_cap_info[0] = mac_caps->he_cap_info_2g;
+		cap_band->he_cap_info[1] = mac_caps->he_cap_info_2g_ext;
 		cap_band->he_mcs = le32_to_cpu(mac_caps->he_supp_mcs_2g);
-		for (i = 0; i < WMI_MAX_HECAP_PHY_SIZE; i++)
-			cap_band->he_cap_phy_info[i] =
-				le32_to_cpu(mac_caps->he_cap_phy_info_2g[i]);
+		memcpy(&cap_band->he_cap_phy_info, &mac_caps->he_cap_phy_info_2g,
+		       sizeof(u32) * WMI_MAX_HECAP_PHY_SIZE);
 
 		cap_band->he_ppet.numss_m1 = le32_to_cpu(mac_caps->he_ppet2g.numss_m1);
 		cap_band->he_ppet.ru_bit_mask = le32_to_cpu(mac_caps->he_ppet2g.ru_info);
@@ -508,12 +507,11 @@ ath12k_pull_mac_phy_cap_svc_ready_ext(struct ath12k_wmi_pdev *wmi_handle,
 		cap_band->max_bw_supported =
 			le32_to_cpu(mac_caps->max_bw_supported_5g);
 		cap_band->ht_cap_info = le32_to_cpu(mac_caps->ht_cap_info_5g);
-		cap_band->he_cap_info[0] = le32_to_cpu(mac_caps->he_cap_info_5g);
-		cap_band->he_cap_info[1] = le32_to_cpu(mac_caps->he_cap_info_5g_ext);
+		cap_band->he_cap_info[0] = mac_caps->he_cap_info_5g;
+		cap_band->he_cap_info[1] = mac_caps->he_cap_info_5g_ext;
 		cap_band->he_mcs = le32_to_cpu(mac_caps->he_supp_mcs_5g);
-		for (i = 0; i < WMI_MAX_HECAP_PHY_SIZE; i++)
-			cap_band->he_cap_phy_info[i] =
-				le32_to_cpu(mac_caps->he_cap_phy_info_5g[i]);
+		memcpy(&cap_band->he_cap_phy_info, &mac_caps->he_cap_phy_info_5g,
+		       sizeof(u32) * WMI_MAX_HECAP_PHY_SIZE);
 
 		cap_band->he_ppet.numss_m1 = le32_to_cpu(mac_caps->he_ppet5g.numss_m1);
 		cap_band->he_ppet.ru_bit_mask = le32_to_cpu(mac_caps->he_ppet5g.ru_info);
@@ -526,12 +524,11 @@ ath12k_pull_mac_phy_cap_svc_ready_ext(struct ath12k_wmi_pdev *wmi_handle,
 		cap_band->max_bw_supported =
 			le32_to_cpu(mac_caps->max_bw_supported_5g);
 		cap_band->ht_cap_info = le32_to_cpu(mac_caps->ht_cap_info_5g);
-		cap_band->he_cap_info[0] = le32_to_cpu(mac_caps->he_cap_info_5g);
-		cap_band->he_cap_info[1] = le32_to_cpu(mac_caps->he_cap_info_5g_ext);
+		cap_band->he_cap_info[0] = mac_caps->he_cap_info_5g;
+		cap_band->he_cap_info[1] = mac_caps->he_cap_info_5g_ext;
 		cap_band->he_mcs = le32_to_cpu(mac_caps->he_supp_mcs_5g);
-		for (i = 0; i < WMI_MAX_HECAP_PHY_SIZE; i++)
-			cap_band->he_cap_phy_info[i] =
-				le32_to_cpu(mac_caps->he_cap_phy_info_5g[i]);
+		memcpy(&cap_band->he_cap_phy_info, &mac_caps->he_cap_phy_info_5g,
+		       sizeof(u32) * WMI_MAX_HECAP_PHY_SIZE);
 
 		cap_band->he_ppet.numss_m1 = le32_to_cpu(mac_caps->he_ppet5g.numss_m1);
 		cap_band->he_ppet.ru_bit_mask = le32_to_cpu(mac_caps->he_ppet5g.ru_info);
@@ -2226,14 +2223,13 @@ int ath12k_wmi_send_peer_assoc_cmd(struct ath12k *ar,
 	cmd->peer_phymode = cpu_to_le32(arg->peer_phymode);
 
 	/* Update 11ax capabilities */
-	cmd->peer_he_cap_info = cpu_to_le32(arg->peer_he_cap_macinfo[0]);
-	cmd->peer_he_cap_info_ext = cpu_to_le32(arg->peer_he_cap_macinfo[1]);
+	cmd->peer_he_cap_info = arg->peer_he_cap_macinfo[0];
+	cmd->peer_he_cap_info_ext = arg->peer_he_cap_macinfo[1];
 	cmd->peer_he_cap_info_internal = cpu_to_le32(arg->peer_he_cap_macinfo_internal);
 	cmd->peer_he_caps_6ghz = cpu_to_le32(arg->peer_he_caps_6ghz);
 	cmd->peer_he_ops = cpu_to_le32(arg->peer_he_ops);
-	for (i = 0; i < WMI_MAX_HECAP_PHY_SIZE; i++)
-		cmd->peer_he_cap_phy[i] =
-			cpu_to_le32(arg->peer_he_cap_phyinfo[i]);
+	memcpy(cmd->peer_he_cap_phy, arg->peer_he_cap_phyinfo,
+	       sizeof(u32) * WMI_MAX_HECAP_PHY_SIZE);
 	cmd->peer_ppet.numss_m1 = cpu_to_le32(arg->peer_ppet.numss_m1);
 	cmd->peer_ppet.ru_info = cpu_to_le32(arg->peer_ppet.ru_bit_mask);
 	for (i = 0; i < WMI_MAX_NUM_SS; i++)
@@ -5034,17 +5030,17 @@ static void ath12k_wmi_eht_caps_parse(struct ath12k_pdev *pdev, u32 band,
 	u8 i;
 
 	if (band == NL80211_BAND_6GHZ)
-		support_320mhz = cap_band->eht_cap_phy_info[0] &
-					IEEE80211_EHT_PHY_CAP0_320MHZ_IN_6GHZ;
+		support_320mhz = le32_to_cpu(cap_band->eht_cap_phy_info[0]) &
+				 IEEE80211_EHT_PHY_CAP0_320MHZ_IN_6GHZ;
 
-	for (i = 0; i < WMI_MAX_EHTCAP_MAC_SIZE; i++)
-		cap_band->eht_cap_mac_info[i] = le32_to_cpu(cap_mac_info[i]);
+	memcpy(cap_band->eht_cap_mac_info, cap_mac_info,
+	       sizeof(u32) * WMI_MAX_EHTCAP_MAC_SIZE);
 
-	for (i = 0; i < WMI_MAX_EHTCAP_PHY_SIZE; i++)
-		cap_band->eht_cap_phy_info[i] = le32_to_cpu(cap_phy_info[i]);
+	memcpy(cap_band->eht_cap_phy_info, cap_phy_info,
+	       sizeof(u32) * WMI_MAX_EHTCAP_PHY_SIZE);
 
 	if (band == NL80211_BAND_6GHZ)
-		cap_band->eht_cap_phy_info[0] |= support_320mhz;
+		cap_band->eht_cap_phy_info[0] |= cpu_to_le32(support_320mhz);
 
 	cap_band->eht_mcs_20_only = le32_to_cpu(supp_mcs[0]);
 	cap_band->eht_mcs_80 = le32_to_cpu(supp_mcs[1]);
@@ -5132,10 +5128,12 @@ static int ath12k_wmi_tlv_mac_phy_caps_ext(struct ath12k_base *ab, u16 tag,
 
 	if (ab->hw_params->single_pdev_only) {
 		if (caps->hw_mode_id == WMI_HOST_HW_MODE_SINGLE) {
-			support_320mhz = le32_to_cpu(caps->eht_cap_phy_info_5ghz[0]) &
-					 IEEE80211_EHT_PHY_CAP0_320MHZ_IN_6GHZ;
+			support_320mhz =
+				le32_to_cpu(caps->eht_cap_phy_info_5ghz[0]) &
+				IEEE80211_EHT_PHY_CAP0_320MHZ_IN_6GHZ;
 			cap_band = &ab->pdevs[0].cap.band[NL80211_BAND_6GHZ];
-			cap_band->eht_cap_phy_info[0] |= support_320mhz;
+			cap_band->eht_cap_phy_info[0] |=
+				cpu_to_le32(support_320mhz);
 		}
 
 		if (ab->wmi_ab.preferred_hw_mode != le32_to_cpu(caps->hw_mode_id))
diff --git a/drivers/net/wireless/ath/ath12k/wmi.h b/drivers/net/wireless/ath/ath12k/wmi.h
index 5ba9b7d3a888..ea680a1a5464 100644
--- a/drivers/net/wireless/ath/ath12k/wmi.h
+++ b/drivers/net/wireless/ath/ath12k/wmi.h
@@ -3911,11 +3911,11 @@ struct ath12k_wmi_peer_assoc_arg {
 	u8 peer_mac[ETH_ALEN];
 
 	bool he_flag;
-	u32 peer_he_cap_macinfo[2];
+	__le32 peer_he_cap_macinfo[2];
 	u32 peer_he_cap_macinfo_internal;
 	u32 peer_he_caps_6ghz;
 	u32 peer_he_ops;
-	u32 peer_he_cap_phyinfo[WMI_HOST_MAX_HECAP_PHY_SIZE];
+	__le32 peer_he_cap_phyinfo[WMI_HOST_MAX_HECAP_PHY_SIZE];
 	u32 peer_he_mcs_count;
 	u32 peer_he_rx_mcs_set[WMI_HOST_MAX_HE_RATE_SET];
 	u32 peer_he_tx_mcs_set[WMI_HOST_MAX_HE_RATE_SET];

---
base-commit: 702847e8cfd51856836a282db2073defd7cfd80c
change-id: 20260317-fix-he-eht-capabilities-on-big-endian-d941c42f65e5

Best regards,
-- 
Alexander Wilhelm <alexander.wilhelm@westermo.com>


^ permalink raw reply related

* [PATCH] wifi: rsi_91x_usb: do not pause rfkill polling when stopping mac80211
From: Ville Nummela @ 2026-03-17  9:20 UTC (permalink / raw)
  To: linux-wireless; +Cc: Ville Nummela

Removing rsi_91x USB adapter could cause rtnetlink to lock up.
When rsi_mac80211_stop is called, wiphy_lock is locked. Call to
wiphy_rfkill_stop_polling would wait until the work queue has
finished, but because the work queue waits for wiphy_lock, that
would never happen.

Moving the call to rsi_disconnect avoids the lock up.

Signed-off-by: Ville Nummela <ville.nummela@kempower.com>
---
 drivers/net/wireless/rsi/rsi_91x_mac80211.c | 17 ++++++++++++++++-
 drivers/net/wireless/rsi/rsi_91x_usb.c      |  2 ++
 drivers/net/wireless/rsi/rsi_common.h       |  1 +
 3 files changed, 19 insertions(+), 1 deletion(-)

diff --git a/drivers/net/wireless/rsi/rsi_91x_mac80211.c b/drivers/net/wireless/rsi/rsi_91x_mac80211.c
index c7ae8031436a..c1fbd16ac2f7 100644
--- a/drivers/net/wireless/rsi/rsi_91x_mac80211.c
+++ b/drivers/net/wireless/rsi/rsi_91x_mac80211.c
@@ -325,6 +325,22 @@ void rsi_mac80211_detach(struct rsi_hw *adapter)
 }
 EXPORT_SYMBOL_GPL(rsi_mac80211_detach);
 
+/**
+ * rsi_mac80211_rfkill_exit() - This function is used to stop rfkill polling
+ *                              when the device is removed.
+ * @adapter: Pointer to the adapter structure.
+ *
+ * Return: None.
+ */
+void rsi_mac80211_rfkill_exit(struct rsi_hw *adapter)
+{
+	struct ieee80211_hw *hw = adapter->hw;
+	if (hw) {
+		wiphy_rfkill_stop_polling(hw->wiphy);
+	}
+}
+EXPORT_SYMBOL_GPL(rsi_mac80211_rfkill_exit);
+
 /**
  * rsi_indicate_tx_status() - This function indicates the transmit status.
  * @adapter: Pointer to the adapter structure.
@@ -422,7 +438,6 @@ static void rsi_mac80211_stop(struct ieee80211_hw *hw, bool suspend)
 	rsi_dbg(ERR_ZONE, "===> Interface DOWN <===\n");
 	mutex_lock(&common->mutex);
 	common->iface_down = true;
-	wiphy_rfkill_stop_polling(hw->wiphy);
 
 	/* Block all rx frames */
 	rsi_send_rx_filter_frame(common, 0xffff);
diff --git a/drivers/net/wireless/rsi/rsi_91x_usb.c b/drivers/net/wireless/rsi/rsi_91x_usb.c
index d83204701e27..8765cac6f875 100644
--- a/drivers/net/wireless/rsi/rsi_91x_usb.c
+++ b/drivers/net/wireless/rsi/rsi_91x_usb.c
@@ -877,6 +877,8 @@ static void rsi_disconnect(struct usb_interface *pfunction)
 	if (!adapter)
 		return;
 
+	rsi_mac80211_rfkill_exit(adapter);
+
 	rsi_mac80211_detach(adapter);
 
 	if (IS_ENABLED(CONFIG_RSI_COEX) && adapter->priv->coex_mode > 1 &&
diff --git a/drivers/net/wireless/rsi/rsi_common.h b/drivers/net/wireless/rsi/rsi_common.h
index 7aa5124575cf..591602beeec6 100644
--- a/drivers/net/wireless/rsi/rsi_common.h
+++ b/drivers/net/wireless/rsi/rsi_common.h
@@ -79,6 +79,7 @@ static inline int rsi_kill_thread(struct rsi_thread *handle)
 }
 
 void rsi_mac80211_detach(struct rsi_hw *hw);
+void rsi_mac80211_rfkill_exit(struct rsi_hw *hw);
 u16 rsi_get_connected_channel(struct ieee80211_vif *vif);
 struct rsi_hw *rsi_91x_init(u16 oper_mode);
 void rsi_91x_deinit(struct rsi_hw *adapter);
-- 
2.34.1


^ permalink raw reply related

* Re: [PATCH v2 0/4] Use the QMI service IDs from the QMI header
From: Daniel Lezcano @ 2026-03-17  9:37 UTC (permalink / raw)
  To: Greg Kroah-Hartman
  Cc: Jakub Kicinski, konradybcio, andersson, linux-kernel, Alex Elder,
	Andrew Lunn, David S. Miller, Eric Dumazet, Paolo Abeni,
	Jeff Johnson, Mathieu Poirier, Srinivas Kandagatla,
	Jaroslav Kysela, Takashi Iwai, Kees Cook, Arnd Bergmann,
	Mark Brown, Wesley Cheng, netdev, linux-wireless, ath10k, ath11k,
	ath12k, linux-arm-msm, linux-remoteproc, linux-sound
In-Reply-To: <2026031717-ethanol-zoning-80b5@gregkh>


Hi Greg,

On 3/17/26 10:07, Greg Kroah-Hartman wrote:
> On Tue, Mar 17, 2026 at 09:51:32AM +0100, Daniel Lezcano wrote:
>> On 3/17/26 01:22, Jakub Kicinski wrote:
>>> On Mon, 16 Mar 2026 18:14:10 +0100 Daniel Lezcano wrote:
>>>> This series is based on the immutable branch [1] containing the QMI
>>>> service id definitions along with some drivers using them.
>>>>
>>>> How a patch can be merged ?
>>>
>>> Wait for the dependency to appear in respective trees after the merge
>>> window then repost the patches individually. I'm starting to get
>>> annoyed with all this cross-tree QMI/MHI noise.
>>
>> An ack is simpler for everyone, especially when they are trivial
> 
> Why isn't this 4 different patches, all for different branches/trees as
> there does not seem to be any dependencies here?
> 
> confused,

The dependency is on the definitions posted in v1 in patch1/8 [1]

The first version of this series was bigger. The definitions were in the 
first patch of the initial series. It has been picked up by Bjorn in the 
corresponding tree along with 4 other patches for subsystems he is in 
charge of and let the other patches left.

He put in place an immutable branch for those who want to pick the 
corresponding patch in their tree [2]

I sent this v2 with a subject prefix fixed and a pointer to the 
immutable branch. So people can just ack the patch or use the branch if 
they want to pick it through their tree.

   -- Daniel

[1] 
https://lore.kernel.org/all/20260309230346.3584252-2-daniel.lezcano@oss.qualcomm.com/

[2] https://lore.kernel.org/all/abdkE2qWX5Amf5Jo@baldur/



^ permalink raw reply

* [PATCH] wifi: rsi_91x_usb: do not pause rfkill polling when stopping mac80211
From: Ville Nummela @ 2026-03-17  9:36 UTC (permalink / raw)
  To: linux-wireless


Removing rsi_91x USB adapter could cause rtnetlink to lock up.
When rsi_mac80211_stop is called, wiphy_lock is locked. Call to
wiphy_rfkill_stop_polling would wait until the work queue has
finished, but because the work queue waits for wiphy_lock, that
would never happen.

Moving the call to rsi_disconnect avoids the lock up.

Signed-off-by: Ville Nummela <ville.nummela@kempower.com>
---
   drivers/net/wireless/rsi/rsi_91x_mac80211.c | 17 ++++++++++++++++-
   drivers/net/wireless/rsi/rsi_91x_usb.c      |  2 ++
   drivers/net/wireless/rsi/rsi_common.h       |  1 +
   3 files changed, 19 insertions(+), 1 deletion(-)

diff --git a/drivers/net/wireless/rsi/rsi_91x_mac80211.c  
b/drivers/net/wireless/rsi/rsi_91x_mac80211.c
index c7ae8031436a..c1fbd16ac2f7 100644
--- a/drivers/net/wireless/rsi/rsi_91x_mac80211.c
+++ b/drivers/net/wireless/rsi/rsi_91x_mac80211.c
@@ -325,6 +325,22 @@ void rsi_mac80211_detach(struct rsi_hw *adapter)
   }
   EXPORT_SYMBOL_GPL(rsi_mac80211_detach);

+/**
+ * rsi_mac80211_rfkill_exit() - This function is used to stop rfkill polling
+ *                              when the device is removed.
+ * @adapter: Pointer to the adapter structure.
+ *
+ * Return: None.
+ */
+void rsi_mac80211_rfkill_exit(struct rsi_hw *adapter)
+{
+	struct ieee80211_hw *hw = adapter->hw;
+	if (hw) {
+		wiphy_rfkill_stop_polling(hw->wiphy);
+	}
+}
+EXPORT_SYMBOL_GPL(rsi_mac80211_rfkill_exit);
+
   /**
    * rsi_indicate_tx_status() - This function indicates the transmit status.
    * @adapter: Pointer to the adapter structure.
@@ -422,7 +438,6 @@ static void rsi_mac80211_stop(struct ieee80211_hw  
*hw, bool suspend)
   	rsi_dbg(ERR_ZONE, "===> Interface DOWN <===\n");
   	mutex_lock(&common->mutex);
   	common->iface_down = true;
-	wiphy_rfkill_stop_polling(hw->wiphy);

   	/* Block all rx frames */
   	rsi_send_rx_filter_frame(common, 0xffff);
diff --git a/drivers/net/wireless/rsi/rsi_91x_usb.c  
b/drivers/net/wireless/rsi/rsi_91x_usb.c
index d83204701e27..8765cac6f875 100644
--- a/drivers/net/wireless/rsi/rsi_91x_usb.c
+++ b/drivers/net/wireless/rsi/rsi_91x_usb.c
@@ -877,6 +877,8 @@ static void rsi_disconnect(struct usb_interface  
*pfunction)
   	if (!adapter)
   		return;

+	rsi_mac80211_rfkill_exit(adapter);
+
   	rsi_mac80211_detach(adapter);

   	if (IS_ENABLED(CONFIG_RSI_COEX) && adapter->priv->coex_mode > 1 &&
diff --git a/drivers/net/wireless/rsi/rsi_common.h  
b/drivers/net/wireless/rsi/rsi_common.h
index 7aa5124575cf..591602beeec6 100644
--- a/drivers/net/wireless/rsi/rsi_common.h
+++ b/drivers/net/wireless/rsi/rsi_common.h
@@ -79,6 +79,7 @@ static inline int rsi_kill_thread(struct rsi_thread *handle)
   }

   void rsi_mac80211_detach(struct rsi_hw *hw);
+void rsi_mac80211_rfkill_exit(struct rsi_hw *hw);
   u16 rsi_get_connected_channel(struct ieee80211_vif *vif);
   struct rsi_hw *rsi_91x_init(u16 oper_mode);
   void rsi_91x_deinit(struct rsi_hw *adapter);
--
2.34.1



^ permalink raw reply related

* [PATCH 3/3] carlfw: disable buggy PSM to prevent USB command timeouts
From: iamdevnull @ 2026-03-17  9:11 UTC (permalink / raw)
  To: Christian Lamparter; +Cc: linux-wireless, Masi Osmani
In-Reply-To: <20260317091102.23894-1-mas-i@hotmail.de>

From: Masi Osmani <mas-i@hotmail.de>

The carl9170 firmware power save implementation causes the SH-2
processor to stop responding to USB commands after entering PS mode.
Powering down the ADDAC and synthesizer via rf_psm() makes the
device miss host command responses, triggering -ETIMEDOUT (-110)
on the host every 45-135 seconds during normal operation.

The kernel.org driver documentation confirms:
"Power Save Mode, It's implemented but buggy"

Three changes:
- fw.c: remove CARL9170FW_PSM and CARL9170FW_FIXED_5GHZ_PSM from
  firmware capability bitmask so the driver never enables PS
- rf.c: rf_psm() early return — never power down ADDAC/synthesizer
- hostif.c: accept but ignore CARL9170_CMD_PSM commands gracefully

With PSM disabled, the adapter stays fully responsive on USB.
Tested: 0 crashes in 180s (previously every 45-135s). The host
cannot force PS on even via iw set power_save on since the
firmware no longer advertises the capability.

Signed-off-by: Masi Osmani <mas-i@hotmail.de>
---
 carlfw/src/fw.c     | 6 ++++--
 carlfw/src/hostif.c | 4 ++--
 carlfw/src/rf.c     | 7 +++++++
 3 files changed, 13 insertions(+), 4 deletions(-)

--- a/carlfw/src/fw.c	2026-03-16 23:38:46.184137155 +0100
+++ b/carlfw/src/fw.c	2026-03-16 23:38:59.714232929 +0100
@@ -48,8 +48,10 @@ const struct carl9170_firmware_descripto
 #endif /* CONFIG_CARL9170FW_USB_DOWN_STREAM */
 #ifdef CONFIG_CARL9170FW_RADIO_FUNCTIONS
 					BIT(CARL9170FW_COMMAND_PHY) |
-					BIT(CARL9170FW_PSM) |
-					BIT(CARL9170FW_FIXED_5GHZ_PSM) |
+					/*
+					 * PSM capability removed — firmware
+					 * PS causes USB command timeouts.
+					 */
 #endif /* CONFIG_CARL9170FW_RADIO_FUNCTIONS */
 #ifdef CONFIG_CARL9170FW_SECURITY_ENGINE
 					BIT(CARL9170FW_COMMAND_CAM) |
--- a/carlfw/src/rf.c	2026-03-16 23:38:46.188101929 +0100
+++ b/carlfw/src/rf.c	2026-03-16 23:39:12.970421845 +0100
@@ -237,6 +237,13 @@ void rf_psm(void)
 {
 	u32 bank3;
 
+	/*
+	 * PSM disabled — powering down ADDAC/synthesizer causes the
+	 * SH-2 to miss USB command responses, triggering host-side
+	 * -ETIMEDOUT and device crash. Always stay awake.
+	 */
+	return;
+
 	if (fw.phy.psm.state == CARL9170_PSM_SOFTWARE) {
 		/* not enabled by the driver */
 		return;
--- a/carlfw/src/hostif.c	2026-03-16 23:38:46.192102245 +0100
+++ b/carlfw/src/hostif.c	2026-03-16 23:39:27.262628301 +0100
@@ -285,9 +285,9 @@ void handle_cmd(struct carl9170_rsp *res
 		break;
 
 	case CARL9170_CMD_PSM:
+		/* PSM commands accepted but ignored — PS is disabled
+		 * to prevent USB command timeout crashes. */
 		resp->hdr.len = 0;
-		fw.phy.psm.state = le32_to_cpu(cmd->psm.state);
-		rf_psm();
 		break;
 #endif /* CONFIG_CARL9170FW_RADIO_FUNCTIONS */
 

^ permalink raw reply

* [PATCH 2/3] carlfw: wlanrx: batch RX frame upload triggers
From: iamdevnull @ 2026-03-17  9:11 UTC (permalink / raw)
  To: Christian Lamparter; +Cc: linux-wireless, Masi Osmani
In-Reply-To: <20260317091102.23894-1-mas-i@hotmail.de>

From: Masi Osmani <mas-i@hotmail.de>

Call up_trigger() once after processing all pending RX descriptors
instead of per-frame. The PTA DMA transfers all queued descriptors
in a single USB transaction, reducing interrupt overhead on the host
by up to N (where N = frames per RX burst).

On a busy 2.4 GHz channel with 10+ APs visible, this reduces USB
interrupt rate during scan sweeps from ~200/s to ~30/s.

Signed-off-by: Masi Osmani <mas-i@hotmail.de>
---
 carlfw/src/wlanrx.c | 12 ++++++++++--
 1 file changed, 10 insertions(+), 2 deletions(-)

diff --git a/carlfw/src/wlanrx.c b/carlfw/src/wlanrx.c
index 1234567..abcdefg 100644
--- a/carlfw/src/wlanrx.c
+++ b/carlfw/src/wlanrx.c
@@ -160,14 +160,24 @@
 void handle_wlan_rx(void)
 {
 	struct dma_desc *desc;
+	bool queued = false;

 	for_each_desc_not_bits(desc, &fw.wlan.rx_queue, AR9170_OWN_BITS_HW) {
 		if (!(wlan_rx_filter(desc) & fw.wlan.rx_filter)) {
 			dma_put(&fw.pta.up_queue, desc);
-			up_trigger();
+			queued = true;
 		} else {
 			dma_reclaim(&fw.wlan.rx_queue, desc);
 			wlan_trigger(AR9170_DMA_TRIGGER_RXQ);
 		}
 	}
+
+	/*
+	 * Trigger USB upload once for the entire batch rather than
+	 * per frame.  The PTA DMA will transfer all queued descriptors
+	 * in a single USB transaction, reducing interrupt overhead on
+	 * the host by up to N (where N = frames per RX burst).
+	 */
+	if (queued)
+		up_trigger();
 }

^ permalink raw reply

* [PATCH 1/3] carlfw: add stability fixes for AR9170 USB adapters
From: iamdevnull @ 2026-03-17  9:11 UTC (permalink / raw)
  To: Christian Lamparter; +Cc: linux-wireless, Masi Osmani

From: Masi Osmani <mas-i@hotmail.de>

Five targeted fixes for stability issues observed on AR9170 USB hardware
(AVM Fritz!WLAN USB Stick N) during extended operation:

1. cam.c: Add timeout to CAM busy-wait loops. The original infinite
   loops block the entire SH-2 processor if the MAC CAM engine stalls,
   making the firmware unresponsive to USB commands. Add a 10000-cycle
   timeout to prevent firmware lockup.

2. main.c (tally): Use hardware cycle counter AR9170_MAC_REG_CHANNEL_BUSY
   for CCA busy time instead of single-bit polling via AR9170_MAC_BACKOFF_CCA.
   The register is a read-and-clear counter like AR9170_MAC_REG_RX_TOTAL,
   giving accurate channel utilization data to the driver's survey.

3. rf.c: On AGC calibration timeout, disable the baseband via
   AR9170_PHY_ACTIVE_DIS so the driver sees a clean failure instead of
   operating with a half-initialized PHY that produces corrupted frames.

4. wlan.c (RX overrun): Lower the MAC reset threshold from 100% frame
   loss to >50% frame loss. The original check (overruns == total) only
   triggered at complete RX blindness, leaving the adapter nearly
   non-functional at 95% loss without any recovery.

5. wlan.c (PSM wake): After rf_psm() transitions from PHY_OFF to
   PHY_ON, re-trigger TX DMA for queued frames. While the PHY was off,
   hardware could not transmit and DMA trigger bits were consumed
   without effect.

6. wlantx.c: Move wlan_tx_ampdu_reset() after retry queue drain to
   prevent clearing AMPDU state before retransmission completes.

7. usb/main.c: Implement usb_warm_reset() for USB bus reset handling.
   Unlike reboot() which destroys USB PHY state requiring re-plug,
   warm reset preserves the USB connection and jumps to start() for
   full firmware re-init. Includes WLAN MAC, DMA, baseband, and PTA
   reset with BB_WARM_RESET to prevent PHY lockups after repeated
   warm resets.

Tested on AVM Fritz!WLAN N (AR9170, AR9001U) with kernel 6.18.12.

Signed-off-by: Masi Osmani <mas-i@hotmail.de>
---
 carlfw/src/cam.c    | 17 +++++++----
 carlfw/src/main.c   |  7 ++++-
 carlfw/src/rf.c     |  9 ++++++
 carlfw/src/wlan.c   | 33 +++++++++++++++------
 carlfw/src/wlantx.c |  3 +-
 carlfw/usb/main.c   | 63 +++++++++++++++++++++++++++++++++++++++--
 6 files changed, 113 insertions(+), 19 deletions(-)

diff --git a/carlfw/src/cam.c b/carlfw/src/cam.c
index 44cbddd..5273031 100644
--- a/carlfw/src/cam.c
+++ b/carlfw/src/cam.c
@@ -42,31 +42,36 @@ static void enable_cam_user(const uint16_t userId)
 		orl(AR9170_MAC_REG_CAM_ROLL_CALL_TBL_H, (((uint32_t) 1) << (userId - 32)));
 }

+#define CAM_TIMEOUT	10000
+
 static void wait_for_cam_read_ready(void)
 {
-	while ((get(AR9170_MAC_REG_CAM_STATE) & AR9170_MAC_CAM_STATE_READ_PENDING) == 0) {
-		/*
-		 * wait
-		 */
+	unsigned int timeout = CAM_TIMEOUT;
+
+	while (((get(AR9170_MAC_REG_CAM_STATE) & AR9170_MAC_CAM_STATE_READ_PENDING) == 0) &&
+	       timeout--) {
+		/* wait */
 	}
 }

 static void wait_for_cam_write_ready(void)
 {
-	while ((get(AR9170_MAC_REG_CAM_STATE) & AR9170_MAC_CAM_STATE_WRITE_PENDING) == 0) {
-		/*
-		 * wait some more
-		 */
+	unsigned int timeout = CAM_TIMEOUT;
+
+	while (((get(AR9170_MAC_REG_CAM_STATE) & AR9170_MAC_CAM_STATE_WRITE_PENDING) == 0) &&
+	       timeout--) {
+		/* wait */
 	}
 }

 static void HW_CAM_Avail(void)
 {
+	unsigned int timeout = CAM_TIMEOUT;
 	uint32_t tmpValue;

 	do {
 		tmpValue = get(AR9170_MAC_REG_CAM_MODE);
-	} while (tmpValue & AR9170_MAC_CAM_HOST_PENDING);
+	} while ((tmpValue & AR9170_MAC_CAM_HOST_PENDING) && timeout--);
 }

 static void HW_CAM_Write128(const uint32_t address, const uint32_t *data)
diff --git a/carlfw/src/main.c b/carlfw/src/main.c
index 8c13bf8..addc883 100644
--- a/carlfw/src/main.c
+++ b/carlfw/src/main.c
@@ -94,11 +94,16 @@ static void tally_update(void)

 		fw.tally.active += delta;

+		/*
+		 * Use HW cycle counter for CCA busy time instead of
+		 * single-bit polling. AR9170_MAC_REG_CHANNEL_BUSY is
+		 * a read-and-clear counter like AR9170_MAC_REG_RX_TOTAL.
+		 */
+		fw.tally.cca += get(AR9170_MAC_REG_CHANNEL_BUSY);
+
 		boff = get(AR9170_MAC_REG_BACKOFF_STATUS);
 		if (boff & AR9170_MAC_BACKOFF_TX_PE)
 			fw.tally.tx_time += delta;
-		if (boff & AR9170_MAC_BACKOFF_CCA)
-			fw.tally.cca += delta;
 	}
 #endif /* CONFIG_CARL9170FW_RADIO_FUNCTIONS */
 	fw.tally_clock = time;
diff --git a/carlfw/src/rf.c b/carlfw/src/rf.c
index 5e8d3d8..d695742 100644
--- a/carlfw/src/rf.c
+++ b/carlfw/src/rf.c
@@ -190,6 +190,15 @@ static uint32_t rf_init(const uint32_t delta_slope_coeff_exp,

 	ret = AGC_calibration(finiteLoopCount);

+	if (ret) {
+		/*
+		 * Calibration timed out — PHY is in an undefined state.
+		 * Disable baseband so the driver sees a clean failure
+		 * instead of operating with a half-initialized PHY.
+		 */
+		set(AR9170_PHY_REG_ACTIVE, AR9170_PHY_ACTIVE_DIS);
+	}
+
 	set_channel_end();
 	return ret;
 }
diff --git a/carlfw/src/wlan.c b/carlfw/src/wlan.c
index 4e73a2b..7a01b09 100644
--- a/carlfw/src/wlan.c
+++ b/carlfw/src/wlan.c
@@ -77,7 +77,13 @@ static void wlan_check_rx_overrun(void)
 	fw.tally.rx_total += total = get(AR9170_MAC_REG_RX_TOTAL);
 	fw.tally.rx_overrun += overruns = get(AR9170_MAC_REG_RX_OVERRUN);
 	if (unlikely(overruns)) {
-		if (overruns == total) {
+		/*
+		 * Trigger MAC reset when more than half of received
+		 * frames are dropped.  The original check (overruns ==
+		 * total) only fired at 100 % loss, leaving the adapter
+		 * nearly blind at 95 % loss without any recovery.
+		 */
+		if (total && overruns > (total >> 1)) {
 			DBG("RX Overrun");
 			fw.wlan.mac_reset++;
 		}
@@ -100,10 +106,33 @@ static void handle_pretbtt(void)
 	fw.wlan.cab_flush_time = get_clock_counter();

 #ifdef CONFIG_CARL9170FW_RADIO_FUNCTIONS
-	rf_psm();
+	{
+		unsigned int prev_phy_state = fw.phy.state;
+
+		rf_psm();
+
+		/*
+		 * After PSM wake, re-trigger TX DMA for queued frames.
+		 * While the PHY was off, the hardware could not transmit
+		 * and DMA trigger bits were consumed without effect.
+		 */
+		if (prev_phy_state == CARL9170_PHY_OFF &&
+		    fw.phy.state == CARL9170_PHY_ON) {
+			int i;
+			uint32_t trigger = 0;
+
+			for (i = AR9170_TXQ0; i <= AR9170_TXQ_SPECIAL; i++) {
+				if (!queue_empty(&fw.wlan.tx_queue[i]))
+					trigger |= BIT(i);
+			}
+
+			if (trigger)
+				wlan_trigger(trigger);
+		}

-	send_cmd_to_host(4, CARL9170_RSP_PRETBTT, 0x00,
-			 (uint8_t *) &fw.phy.psm.state);
+		send_cmd_to_host(4, CARL9170_RSP_PRETBTT, 0x00,
+				 (uint8_t *) &fw.phy.psm.state);
+	}
 #endif /* CONFIG_CARL9170FW_RADIO_FUNCTIONS */
 }

diff --git a/carlfw/src/wlantx.c b/carlfw/src/wlantx.c
index a8d0952..2db111e 100644
--- a/carlfw/src/wlantx.c
+++ b/carlfw/src/wlantx.c
@@ -471,11 +471,10 @@ void handle_wlan_tx_completion(void)
 			}
 		}

-		wlan_tx_ampdu_reset(i);
-
 		for_each_desc(desc, &fw.wlan.tx_retry)
 			__wlan_tx(desc);

+		wlan_tx_ampdu_reset(i);
 		wlan_tx_ampdu_end(i);
 		if (!queue_empty(&fw.wlan.tx_queue[i]))
 			wlan_trigger(BIT(i));
diff --git a/carlfw/usb/main.c b/carlfw/usb/main.c
index 4199a21..9147c80 100644
--- a/carlfw/usb/main.c
+++ b/carlfw/usb/main.c
@@ -295,6 +295,63 @@ static void disable_watchdog(void)
 	set(AR9170_TIMER_REG_WATCH_DOG, 0xffff);
 }

+/*
+ * Warm reset: re-initialize firmware without destroying USB PHY state.
+ * This allows the host to re-enumerate the device after a USB bus reset
+ * without requiring a physical re-plug.
+ *
+ * Unlike reboot() which calls turn_power_off() and jump_to_bootcode(),
+ * this preserves the USB connection and jumps directly to start().
+ */
+static void __noreturn usb_warm_reset(void)
+{
+	disable_watchdog();
+
+	/* Disable baseband to stop PHY activity */
+	set(AR9170_PHY_REG_ACTIVE, AR9170_PHY_ACTIVE_DIS);
+
+	/* Stop WLAN DMA */
+	set(AR9170_MAC_REG_DMA_TRIGGER, 0);
+
+	/* Stop USB DMA without full power-off */
+	andl(AR9170_USB_REG_DMA_CTL, ~(AR9170_USB_DMA_CTL_ENABLE_TO_DEVICE |
+					AR9170_USB_DMA_CTL_ENABLE_FROM_DEVICE));
+
+	/* Reset PTA component */
+	orl(AR9170_PTA_REG_DMA_MODE_CTRL, AR9170_PTA_DMA_MODE_CTRL_RESET);
+	andl(AR9170_PTA_REG_DMA_MODE_CTRL, ~AR9170_PTA_DMA_MODE_CTRL_RESET);
+
+	/* Reset MAC power state */
+	set(AR9170_MAC_REG_POWER_STATE_CTRL,
+	    AR9170_MAC_POWER_STATE_CTRL_RESET);
+
+	/*
+	 * Hardware reset: WLAN MAC, DMA engine, and baseband.
+	 * Without this, the PHY/RF can lock up after repeated
+	 * warm resets, causing -ETIMEDOUT on register writes
+	 * and cascading driver reloads (phy0 -> phy29 -> crash).
+	 *
+	 * BB_WARM_RESET resets PHY logic while preserving
+	 * calibration-friendly state. start() -> init() will
+	 * reconfigure everything via the driver anyway.
+	 */
+	set(AR9170_PWR_REG_RESET, AR9170_PWR_RESET_COMMIT_RESET_MASK |
+				  AR9170_PWR_RESET_WLAN_MASK |
+				  AR9170_PWR_RESET_DMA_MASK |
+				  AR9170_PWR_RESET_BB_WARM_RESET);
+	set(AR9170_PWR_REG_RESET, 0x0);
+
+	/* Clean DMA memory */
+	memset(&dma_mem, 0, sizeof(dma_mem));
+
+	/* Clear firmware state */
+	memset(&fw, 0, sizeof(fw));
+
+	/* Re-enter firmware from start() which does full init
+	 * and sends CARL9170_RSP_BOOT to the host. */
+	start();
+}
+
 void __noreturn reboot(void)
 {
 	disable_watchdog();
@@ -377,7 +434,7 @@ static void usb_handler(uint8_t usb_interrupt_level1)
 		if (usb_interrupt_level2 & AR9170_USB_INTR_SRC7_USB_RESET) {
 			usb_reset_ack();
 			usb_reset_eps();
-			reboot();
+			usb_warm_reset();
 		}

 		if (usb_interrupt_level2 & AR9170_USB_INTR_SRC7_USB_SUSPEND) {
@@ -409,7 +466,7 @@ static void usb_handler(uint8_t usb_interrupt_level1)
 			fw.suspend_mode = CARL9170_HOST_AWAKE;
 			set(AR9170_USB_REG_WAKE_UP, 0);

-			reboot();
+			usb_warm_reset();
 		}
 	}
 }

^ permalink raw reply related

* [PATCH 12/12] carl9170: rx: handle zeroed PLCP CCK rate as corrupted frame
From: Masi Osmani @ 2026-03-17  9:10 UTC (permalink / raw)
  To: Christian Lamparter; +Cc: linux-wireless, Masi Osmani
In-Reply-To: <20260317091045.23696-1-mas-i@hotmail.de>

The firmware occasionally delivers frames tagged as CCK modulation
with a zeroed PLCP rate byte (plcp[0] == 0x00).  This typically
happens after PHY state degradation from a failed channel change or
from RF noise on weak signals.

Currently these frames fall through to the default case and produce
a rate-limited wiphy_err log:

  ieee80211 phy3: invalid plcp cck rate (0).

The frame is garbage regardless of the log level.  Handle plcp[0]
== 0x00 as a dedicated case: increment the rx_dropped counter
(visible via debugfs) and return -EINVAL silently.  Downgrade the
remaining default case log from wiphy_err to wiphy_dbg so that
genuinely unexpected PLCP values can still be investigated via
dynamic debug without polluting normal dmesg output.

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

--- a/drivers/net/wireless/ath/carl9170/rx.c	2026-03-15 23:51:23.599698582 +0100
+++ b/drivers/net/wireless/ath/carl9170/rx.c	2026-03-15 23:52:21.041912498 +0100
@@ -372,9 +372,18 @@ static int carl9170_rx_mac_status(struct
 		case AR9170_RX_PHY_RATE_CCK_11M:
 			status->rate_idx = 3;
 			break;
+		case 0x00:
+			/*
+			 * Zeroed PLCP rate byte: the firmware delivered a
+			 * corrupted frame, typically after PHY degradation
+			 * from a failed channel change or from RF noise on
+			 * weak signals.  Drop silently.
+			 */
+			ar->rx_dropped++;
+			return -EINVAL;
 		default:
 			if (net_ratelimit()) {
-				wiphy_err(ar->hw->wiphy, "invalid plcp cck "
+				wiphy_dbg(ar->hw->wiphy, "invalid plcp cck "
 				       "rate (%x).\n", head->plcp[0]);
 			}
 

^ permalink raw reply

* [PATCH 11/12] carl9170: skip cross-band channel changes during software scan
From: Masi Osmani @ 2026-03-17  9:10 UTC (permalink / raw)
  To: Christian Lamparter; +Cc: linux-wireless, Masi Osmani

The carl9170 relies on mac80211 software scanning because it does not
implement a hw_scan callback.  During a scan, mac80211 iterates all
supported channels across both bands, calling carl9170_op_config()
with IEEE80211_CONF_CHANGE_CHANNEL for each one.

Every channel change triggers a full baseband cold reset, RF bank
re-initialisation and AGC calibration via the firmware RF_INIT command
with a 200 ms timeout.  Cross-band switches (2.4 GHz <-> 5 GHz) are
especially expensive and error-prone: the AGC calibration frequently
times out (firmware returns error code 2), leaving the PHY in a
degraded state.  Subsequent channel changes -- even within the same
band -- then also fail, and after three consecutive failures the
driver restarts the device, causing a multi-second connectivity gap.

When the adapter is associated on a specific band, scanning channels
on the other band produces no useful roaming candidates for the
current BSS.  Add sw_scan_start/sw_scan_complete callbacks to track
the scanning state and skip cross-band channel changes while a
software scan is in progress.  Intentional cross-band association
changes (e.g. roaming from 2.4 GHz to 5 GHz on a dual-band SSID)
are not affected because they occur outside the scanning window.

Tested on Fritz\!WLAN N (AR9170) with 2.4 GHz association and
concurrent full-band scans: no channel change failures, no device
restarts, no PHY corruption.

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

--- a/drivers/net/wireless/ath/carl9170/carl9170.h	2026-03-15 23:51:23.598565789 +0100
+++ b/drivers/net/wireless/ath/carl9170/carl9170.h	2026-03-15 23:51:39.769123563 +0100
@@ -333,6 +333,7 @@ struct ar9170 {
 	/* PHY */
 	struct ieee80211_channel *channel;
 	unsigned int num_channels;
+	bool scanning;
 	int noise[4];
 	unsigned int chan_fail;
 	unsigned int total_chan_fail;
--- a/drivers/net/wireless/ath/carl9170/main.c	2026-03-15 23:51:23.597355728 +0100
+++ b/drivers/net/wireless/ath/carl9170/main.c	2026-03-15 23:52:02.845563524 +0100
@@ -916,6 +916,33 @@ static int carl9170_op_config(struct iee
 		enum nl80211_channel_type channel_type =
 			cfg80211_get_chandef_type(&hw->conf.chandef);
 
+		/*
+		 * Skip cross-band channel changes during software scan.
+		 *
+		 * mac80211 sw_scan iterates all channels including the
+		 * other band.  Each channel change requires a full BB
+		 * cold reset and AGC calibration via the firmware RF_INIT
+		 * command (200 ms timeout).  Cross-band switches
+		 * frequently cause AGC calibration timeouts (firmware
+		 * returns error 2), leaving the PHY in a degraded state
+		 * that cascades into failures on subsequent intra-band
+		 * channel changes and ultimately triggers a device
+		 * restart after three consecutive failures.
+		 *
+		 * When associated, scanning the other band yields no
+		 * useful roaming candidates for the current BSS.  Skip
+		 * the channel change so mac80211 advances to the next
+		 * scan channel harmlessly.
+		 */
+		if (ar->scanning && ar->channel &&
+		    hw->conf.chandef.chan->band != ar->channel->band) {
+			wiphy_dbg(ar->hw->wiphy,
+				  "skip cross-band scan: %d MHz -> %d MHz\n",
+				  ar->channel->center_freq,
+				  hw->conf.chandef.chan->center_freq);
+			goto out;
+		}
+
 		/* adjust slot time for 5 GHz */
 		err = carl9170_set_slot_time(ar);
 		if (err)
@@ -954,6 +981,27 @@ out:
 	return err;
 }
 
+static void carl9170_op_sw_scan_start(struct ieee80211_hw *hw,
+				      struct ieee80211_vif *vif,
+				      const u8 *mac_addr)
+{
+	struct ar9170 *ar = hw->priv;
+
+	mutex_lock(&ar->mutex);
+	ar->scanning = true;
+	mutex_unlock(&ar->mutex);
+}
+
+static void carl9170_op_sw_scan_complete(struct ieee80211_hw *hw,
+					 struct ieee80211_vif *vif)
+{
+	struct ar9170 *ar = hw->priv;
+
+	mutex_lock(&ar->mutex);
+	ar->scanning = false;
+	mutex_unlock(&ar->mutex);
+}
+
 static u64 carl9170_op_prepare_multicast(struct ieee80211_hw *hw,
 					 struct netdev_hw_addr_list *mc_list)
 {
@@ -1723,6 +1771,8 @@ static const struct ieee80211_ops carl91
 	.add_interface		= carl9170_op_add_interface,
 	.remove_interface	= carl9170_op_remove_interface,
 	.config			= carl9170_op_config,
+	.sw_scan_start		= carl9170_op_sw_scan_start,
+	.sw_scan_complete	= carl9170_op_sw_scan_complete,
 	.prepare_multicast	= carl9170_op_prepare_multicast,
 	.configure_filter	= carl9170_op_configure_filter,
 	.conf_tx		= carl9170_op_conf_tx,

^ permalink raw reply

* ath11k: trigger frame types
From: Alexander Wilhelm @ 2026-03-17  9:10 UTC (permalink / raw)
  To: Jeff Johnson; +Cc: ath11k, linux-wireless

Hello wireless devs,

I am currently learning about the topic of “Trigger Frames” and analyzing
the packets I captured with Wireshark. I am using the QCN9074 with the
`ath11k` driver and set up an AP with HE80, to which two clients (STA1 and
STA2) are connected. When I analyze the packets, I can see BSRP and MU‑BAR
trigger frames. What is interesting, however, is the output of htt_stats:

    $ echo 12 > htt_stats_type
    $ cat htt_stats
    HTT_TX_SELFGEN_CMN_STATS_TLV:
    mac_id = 0
    su_bar = 238
    rts = 0
    cts2self = 0
    qos_null = 0
    delayed_bar_1 = 0
    delayed_bar_2 = 0
    delayed_bar_3 = 0
    delayed_bar_4 = 0
    delayed_bar_5 = 0
    delayed_bar_6 = 0
    delayed_bar_7 = 0

    HTT_TX_SELFGEN_AC_STATS_TLV:
    ac_su_ndpa = 0
    ac_su_ndp = 0
    ac_mu_mimo_ndpa = 0
    ac_mu_mimo_ndp = 0
    ac_mu_mimo_brpoll_1 = 0
    ac_mu_mimo_brpoll_2 = 0
    ac_mu_mimo_brpoll_3 = 0

    HTT_TX_SELFGEN_AX_STATS_TLV:
    ax_su_ndpa = 234
    ax_su_ndp = 234
    ax_mu_mimo_ndpa = 0
    ax_mu_mimo_ndp = 0
    ax_mu_mimo_brpoll_1 = 0
    ax_mu_mimo_brpoll_2 = 0
    ax_mu_mimo_brpoll_3 = 0
    ax_mu_mimo_brpoll_4 = 0
    ax_mu_mimo_brpoll_5 = 0
    ax_mu_mimo_brpoll_6 = 0
    ax_mu_mimo_brpoll_7 = 0
    ax_basic_trigger = 0
    ax_ulmumimo_trigger = 0
    ax_bsr_trigger = 0
    ax_mu_bar_trigger = 0
    ax_mu_rts_trigger = 0

    HTT_TX_SELFGEN_AC_ERR_STATS_TLV:
    ac_su_ndp_err = 0
    ac_su_ndpa_err = 0
    ac_mu_mimo_ndpa_err = 0
    ac_mu_mimo_ndp_err = 0
    ac_mu_mimo_brp1_err = 0
    ac_mu_mimo_brp2_err = 0
    ac_mu_mimo_brp3_err = 0

    HTT_TX_SELFGEN_AX_ERR_STATS_TLV:
    ax_su_ndp_err = 97
    ax_su_ndpa_err = 0
    ax_mu_mimo_ndpa_err = 0
    ax_mu_mimo_ndp_err = 0
    ax_mu_mimo_brp1_err = 0
    ax_mu_mimo_brp2_err = 0
    ax_mu_mimo_brp3_err = 0
    ax_mu_mimo_brp4_err = 0
    ax_mu_mimo_brp5_err = 0
    ax_mu_mimo_brp6_err = 0
    ax_mu_mimo_brp7_err = 0
    ax_basic_trigger_err = 0
    ax_ulmumimo_trigger_err = 0
    ax_bsr_trigger_err = 45
    ax_mu_bar_trigger_err = 238
    ax_mu_rts_trigger_err = 0

I am wondering why the error counter keeps increasing even though the
packets are there. Maybe I am simply misinterpreting the firmware
statistics here.

It is quite difficult for me to set up an environment where I can reliably
receive specific trigger frames. Is there any way in `ath11k`, or in the
firmware via WMI commands, to force certain trigger frames or restrict the
use of specific ones?


Best regards
Alexander Wilhelm

^ permalink raw reply

* Re: [PATCH v2 0/4] Use the QMI service IDs from the QMI header
From: Greg Kroah-Hartman @ 2026-03-17  9:07 UTC (permalink / raw)
  To: Daniel Lezcano
  Cc: Jakub Kicinski, konradybcio, andersson, linux-kernel, Alex Elder,
	Andrew Lunn, David S. Miller, Eric Dumazet, Paolo Abeni,
	Jeff Johnson, Mathieu Poirier, Srinivas Kandagatla,
	Jaroslav Kysela, Takashi Iwai, Kees Cook, Arnd Bergmann,
	Mark Brown, Wesley Cheng, netdev, linux-wireless, ath10k, ath11k,
	ath12k, linux-arm-msm, linux-remoteproc, linux-sound
In-Reply-To: <8757aec7-8c36-446a-9a34-f0717f64202a@oss.qualcomm.com>

On Tue, Mar 17, 2026 at 09:51:32AM +0100, Daniel Lezcano wrote:
> On 3/17/26 01:22, Jakub Kicinski wrote:
> > On Mon, 16 Mar 2026 18:14:10 +0100 Daniel Lezcano wrote:
> > > This series is based on the immutable branch [1] containing the QMI
> > > service id definitions along with some drivers using them.
> > > 
> > > How a patch can be merged ?
> > 
> > Wait for the dependency to appear in respective trees after the merge
> > window then repost the patches individually. I'm starting to get
> > annoyed with all this cross-tree QMI/MHI noise.
> 
> An ack is simpler for everyone, especially when they are trivial

Why isn't this 4 different patches, all for different branches/trees as
there does not seem to be any dependencies here?

confused,

greg k-h

^ permalink raw reply


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